diff --git a/base/DEPS b/base/DEPS new file mode 100644 index 0000000000..e1545aee51 --- /dev/null +++ b/base/DEPS @@ -0,0 +1,15 @@ +include_rules = [ + "+jni", + "+native_client/src/untrusted/irt/irt.h", + "+third_party/ashmem", + "+third_party/apple_apsl", + "+third_party/libevent", + "+third_party/dmg_fp", + "+third_party/GTM", + "+third_party/mach_override", + "+third_party/modp_b64", + "+third_party/tcmalloc", + + # ICU dependendencies must be separate from the rest of base. + "-i18n", +] diff --git a/base/OWNERS b/base/OWNERS new file mode 100644 index 0000000000..7e0e67fb6c --- /dev/null +++ b/base/OWNERS @@ -0,0 +1,29 @@ +mark@chromium.org +darin@chromium.org +brettw@chromium.org +willchan@chromium.org +jar@chromium.org + +per-file *.isolate=csharp@chromium.org +per-file *.isolate=maruel@chromium.org +per-file bind.h=ajwong@chromium.org +per-file bind_helpers.cc=ajwong@chromium.org +per-file bind_helpers.h=ajwong@chromium.org +per-file bind_helpers_unittest.cc=ajwong@chromium.org +per-file bind.h.pump=ajwong@chromium.org +per-file bind_internal.h=ajwong@chromium.org +per-file bind_internal.h.pump=ajwong@chromium.org +per-file bind_internal_win.h=ajwong@chromium.org +per-file bind_internal_win.h.pump=ajwong@chromium.org +per-file bind_unittest.cc=ajwong@chromium.org +per-file bind_unittest.nc=ajwong@chromium.org +per-file callback_forward.h=ajwong@chromium.org +per-file callback.h=ajwong@chromium.org +per-file callback_helpers.h=ajwong@chromium.org +per-file callback.h.pump=ajwong@chromium.org +per-file callback_internal.cc=ajwong@chromium.org +per-file callback_internal.h=ajwong@chromium.org +per-file callback_unittest.cc=ajwong@chromium.org +per-file callback_unittest.h=ajwong@chromium.org +per-file callback_unittest.nc=ajwong@chromium.org +per-file security_unittest.cc=jln@chromium.org diff --git a/base/PRESUBMIT.py b/base/PRESUBMIT.py new file mode 100644 index 0000000000..7137c5a010 --- /dev/null +++ b/base/PRESUBMIT.py @@ -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. + +"""Chromium presubmit script for src/base. + +See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts +for more details on the presubmit API built into gcl. +""" + +def _CheckNoInterfacesInBase(input_api, output_api): + """Checks to make sure no files in libbase.a have |@interface|.""" + pattern = input_api.re.compile(r'^\s*@interface', input_api.re.MULTILINE) + files = [] + for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile): + if (f.LocalPath().startswith('base/') and + not "/test/" in f.LocalPath() and + not f.LocalPath().endswith('_unittest.mm') and + not f.LocalPath().endswith('mac/sdk_forward_declarations.h')): + contents = input_api.ReadFile(f) + if pattern.search(contents): + files.append(f) + + if len(files): + return [ output_api.PresubmitError( + 'Objective-C interfaces or categories are forbidden in libbase. ' + + 'See http://groups.google.com/a/chromium.org/group/chromium-dev/' + + 'browse_thread/thread/efb28c10435987fd', + files) ] + return [] + + +def _CommonChecks(input_api, output_api): + """Checks common to both upload and commit.""" + results = [] + results.extend(_CheckNoInterfacesInBase(input_api, output_api)) + return results + +def CheckChangeOnUpload(input_api, output_api): + results = [] + results.extend(_CommonChecks(input_api, output_api)) + return results + + +def CheckChangeOnCommit(input_api, output_api): + results = [] + results.extend(_CommonChecks(input_api, output_api)) + return results + + +def GetPreferredTrySlaves(): + return [ + 'linux_rel:sync_integration_tests', + 'mac_rel:sync_integration_tests', + 'win_rel:sync_integration_tests', + ] diff --git a/base/allocator/README b/base/allocator/README new file mode 100644 index 0000000000..ec8a707f41 --- /dev/null +++ b/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/base/allocator/allocator.gyp b/base/allocator/allocator.gyp new file mode 100644 index 0000000000..ef98d09482 --- /dev/null +++ b/base/allocator/allocator.gyp @@ -0,0 +1,676 @@ +# Copyright (c) 2012 The Chromium 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_android.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'], + }, + '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%': 0, + }, + 'conditions': [ + # TODO(phajdan.jr): Also enable on Windows. + ['disable_debugallocation==0 and OS!="win"', { + '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" or OS=="android"', { + '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', + ]}, + }], + # Need to distinguish a non-SDK build for Android WebView + # due to differences in C include files. + ['OS=="android" and android_webview_build==1', { + 'defines': ['ANDROID_NON_SDK_BUILD'], + }], + [ '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', + ], + }], + ['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_arch)', + ], + }, + ], + }, + { + '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': '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=="win" and target_arch=="ia32"', { + 'targets': [ + { + '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', + }, + }, + }, + ], + }], + ['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/base/allocator/allocator_extension.cc b/base/allocator/allocator_extension.cc new file mode 100644 index 0000000000..83e460ac82 --- /dev/null +++ b/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(NULL)); + thunks::SetGetAllocatorWasteSizeFunction(get_allocator_waste_size_function); +} + +void SetGetStatsFunction(thunks::GetStatsFunction get_stats_function) { + DCHECK_EQ(thunks::GetGetStatsFunction(), + reinterpret_cast(NULL)); + thunks::SetGetStatsFunction(get_stats_function); +} + +void SetReleaseFreeMemoryFunction( + thunks::ReleaseFreeMemoryFunction release_free_memory_function) { + DCHECK_EQ(thunks::GetReleaseFreeMemoryFunction(), + reinterpret_cast(NULL)); + thunks::SetReleaseFreeMemoryFunction(release_free_memory_function); +} + +} // namespace allocator +} // namespace base diff --git a/base/allocator/allocator_extension.h b/base/allocator/allocator_extension.h new file mode 100644 index 0000000000..de3119f85e --- /dev/null +++ b/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 // 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/base/allocator/allocator_extension_thunks.cc b/base/allocator/allocator_extension_thunks.cc new file mode 100644 index 0000000000..e4024fb332 --- /dev/null +++ b/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 // 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/base/allocator/allocator_extension_thunks.h b/base/allocator/allocator_extension_thunks.h new file mode 100644 index 0000000000..1e97a84b63 --- /dev/null +++ b/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 // 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/base/allocator/allocator_shim.cc b/base/allocator/allocator_shim.cc new file mode 100644 index 0000000000..1d8229117d --- /dev/null +++ b/base/allocator/allocator_shim.cc @@ -0,0 +1,446 @@ +// Copyright (c) 2012 The Chromium Authors. All 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 +#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. +#if (defined(ADDRESS_SANITIZER) && defined(OS_WIN)) +// The Windows implementation of Asan requires the use of "WINHEAP". +static Allocator allocator = WINHEAP; +#else +static Allocator allocator = TCMALLOC; +#endif +// 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); +} + +// 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 false; + 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) + return false; +} + +extern "C" { +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 +// Don't use the environment variable if ADDRESS_SANITIZER is defined on +// Windows, as the implementation requires Winheap to be the allocator. +#if !(defined(ADDRESS_SANITIZER) && defined(OS_WIN)) + 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; + } +#endif + + 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(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(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) { + // Don't use the environment variable if ADDRESS_SANITIZER is defined on + // Windows, as the implementation require Winheap to be the allocator. +#if !(defined(ADDRESS_SANITIZER) && defined(OS_WIN)) + const char* secondary_value = secondary_length ? buffer : "TCMALLOC"; + // Force renderer (or other subprocesses) to use secondary_value. +#else + const char* secondary_value = "WINHEAP"; +#endif + 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/base/allocator/allocator_shim.h b/base/allocator/allocator_shim.h new file mode 100644 index 0000000000..ca70ab0e10 --- /dev/null +++ b/base/allocator/allocator_shim.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_ALLOCATOR_ALLOCATOR_SHIM_H_ +#define BASE_ALLOCATOR_ALLOCATOR_SHIM_H_ + +#include + +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/base/allocator/allocator_unittests.cc b/base/allocator/allocator_unittests.cc new file mode 100644 index 0000000000..cf8b74d7f4 --- /dev/null +++ b/base/allocator/allocator_unittests.cc @@ -0,0 +1,521 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include // 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(0); +// Maximum positive size of a size_t if it were signed. +static const size_t kMaxSignedSize = ((size_t(1) << (kSizeBits-1)) - 1); +// 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(0); + +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(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; + } +} + +#define GG_ULONGLONG(x) static_cast(x) + +template +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 +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 +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 +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 +static void TestStore() { + const AtomicType kVal1 = static_cast(0xa5a5a5a5a5a5a5a5LL); + const AtomicType kVal2 = static_cast(-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 +static void TestLoad() { + const AtomicType kVal1 = static_cast(0xa5a5a5a5a5a5a5a5LL); + const AtomicType kVal2 = static_cast(-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 +static void TestAtomicOps() { + TestCompareAndSwap(); + TestAtomicExchange(); + TestAtomicIncrementBounds(); + TestStore(); + TestLoad(); +} + +static void TestCalloc(size_t n, size_t s, bool ok) { + char* p = reinterpret_cast(calloc(n, s)); + if (!ok) { + EXPECT_EQ(NULL, p) << "calloc(n, s) should not succeed"; + } else { + EXPECT_NE(reinterpret_cast(NULL), p) << + "calloc(n, s) should succeed"; + for (int i = 0; i < n*s; i++) { + EXPECT_EQ('\0', p[i]); + } + free(p); + } +} + + +// 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(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); +} + +} // namespace + +//----------------------------------------------------------------------------- + +TEST(Atomics, AtomicIncrementWord) { + TestAtomicIncrement(); +} + +TEST(Atomics, AtomicIncrement32) { + TestAtomicIncrement(); +} + +TEST(Atomics, AtomicOpsWord) { + TestAtomicIncrement(); +} + +TEST(Atomics, AtomicOps32) { + TestAtomicIncrement(); +} + +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(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); +} + +TEST(Allocators, New) { + TestNothrowNew(&::operator new); + TestNothrowNew(&::operator new[]); +} + +// 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); + } +} + +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(malloc(src_size)); + Fill(src, src_size); + unsigned char* dst = + reinterpret_cast(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(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(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(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); +} + +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(malloc(size)); + EXPECT_NE(static_cast(NULL), ptr); + ptr = reinterpret_cast(realloc(ptr, 0)); + EXPECT_NE(static_cast(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(_recalloc(NULL, 1, src_size)); + EXPECT_TRUE(IsZeroed(src, src_size)); + Fill(src, src_size); + unsigned char* dst = + reinterpret_cast(_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( + _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( + _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 + + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/base/allocator/debugallocation_shim.cc b/base/allocator/debugallocation_shim.cc new file mode 100644 index 0000000000..d1cf52a23e --- /dev/null +++ b/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/base/allocator/generic_allocators.cc b/base/allocator/generic_allocators.cc new file mode 100644 index 0000000000..d4cf19e952 --- /dev/null +++ b/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/base/allocator/prep_libc.py b/base/allocator/prep_libc.py new file mode 100755 index 0000000000..e13e9e3b7e --- /dev/null +++ b/base/allocator/prep_libc.py @@ -0,0 +1,67 @@ +#!/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 +# +# VCLibDir is the path where VC is installed, something like: +# C:\Program Files\Microsoft Visual Studio 8\VC\lib +# OutputDir is the directory where the modified libcmt file should be stored. +# arch is either 'ia32' or 'x64' + +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(): + bindir = 'SELF_X86' + objdir = 'INTEL' + vs_install_dir = sys.argv[1] + outdir = sys.argv[2] + if "x64" in sys.argv[3]: + bindir = 'SELF_64_amd64' + objdir = 'amd64' + vs_install_dir = os.path.join(vs_install_dir, 'amd64') + 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\\' + bindir + \ + '\\crt\\src\\build\\' + objdir + '\\mt_obj\\', + 'F:\\dd\\vctools\\crt_bld\\' + bindir + \ + '\\crt\\src\\build\\' + objdir + '\\mt_obj\\nativec\\\\', + 'F:\\dd\\vctools\\crt_bld\\' + bindir + \ + '\\crt\\src\\build\\' + objdir + '\\mt_obj\\nativecpp\\\\' ] + + objfiles = ['malloc', 'free', 'realloc', 'new', 'delete', 'new2', 'delete2', + 'align', 'msize', 'heapinit', 'expand', 'heapchk', 'heapwalk', + 'heapmin', 'sbheap', 'calloc', 'recalloc', 'calloc_impl', + 'new_mode', 'newopnt', 'newaopnt'] + 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/base/allocator/tcmalloc_unittest.cc b/base/allocator/tcmalloc_unittest.cc new file mode 100644 index 0000000000..053a9d50d7 --- /dev/null +++ b/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 +#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( + 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( + 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( + 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(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(TCMallocDoMallocForTest(size)); + ASSERT_DEATH(TCMallocDoFreeForTest(p); TCMallocDoFreeForTest(p), + "Memory corrupted"); + } + + for (; size <= ExcludeSpaceForMarkForTest(kMaxSize); size <<= 1) { + char* p = reinterpret_cast(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/base/allocator/type_profiler.cc b/base/allocator/type_profiler.cc new file mode 100644 index 0000000000..635fbcf5ed --- /dev/null +++ b/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 + +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/base/allocator/type_profiler.h b/base/allocator/type_profiler.h new file mode 100644 index 0000000000..86b5711a9d --- /dev/null +++ b/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 // for size_t +#include // 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/base/allocator/type_profiler_control.cc b/base/allocator/type_profiler_control.cc new file mode 100644 index 0000000000..6be79840ed --- /dev/null +++ b/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/base/allocator/type_profiler_control.h b/base/allocator/type_profiler_control.h new file mode 100644 index 0000000000..17cf5b65e4 --- /dev/null +++ b/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/base/allocator/type_profiler_map_unittests.cc b/base/allocator/type_profiler_map_unittests.cc new file mode 100644 index 0000000000..5ac5dd0c26 --- /dev/null +++ b/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(NULL); + +TEST(TypeProfilerMapTest, NormalOperation) { + // Allocate an object just to get a valid address. + // This 'new' is not profiled by type_profiler. + scoped_ptr 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 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 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 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 + +#endif // defined(TYPE_PROFILING) + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/base/allocator/type_profiler_tcmalloc.cc b/base/allocator/type_profiler_tcmalloc.cc new file mode 100644 index 0000000000..e5e10e0d12 --- /dev/null +++ b/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/base/allocator/type_profiler_tcmalloc.h b/base/allocator/type_profiler_tcmalloc.h new file mode 100644 index 0000000000..ac55995c82 --- /dev/null +++ b/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 // for size_t +#include // 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/base/allocator/type_profiler_unittests.cc b/base/allocator/type_profiler_unittests.cc new file mode 100644 index 0000000000..e8f06eddd6 --- /dev/null +++ b/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(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(dummy_char), static_cast(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 + +#endif // defined(TYPE_PROFILING) + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/base/allocator/unittest_utils.cc b/base/allocator/unittest_utils.cc new file mode 100644 index 0000000000..130ba15f53 --- /dev/null +++ b/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 + +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/base/allocator/win_allocator.cc b/base/allocator/win_allocator.cc new file mode 100644 index 0000000000..899b867d11 --- /dev/null +++ b/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(ptr) + sizeof(void*); + aligned_ptr += + alignment - reinterpret_cast(aligned_ptr) & (alignment - 1); + + reinterpret_cast(aligned_ptr)[-1] = ptr; + return aligned_ptr; +} + +void win_heap_memalign_free(void* ptr) { + if (ptr) + win_heap_free(static_cast(ptr)[-1]); +} + +} // extern "C" diff --git a/base/android/OWNERS b/base/android/OWNERS new file mode 100644 index 0000000000..ebc6e2624f --- /dev/null +++ b/base/android/OWNERS @@ -0,0 +1,3 @@ +bulach@chromium.org +joth@chromium.org +yfriedman@chromium.org diff --git a/base/android/activity_state_list.h b/base/android/activity_state_list.h new file mode 100644 index 0000000000..43c0f803da --- /dev/null +++ b/base/android/activity_state_list.h @@ -0,0 +1,16 @@ +// 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 file intentionally does not have header guards, it's included +// inside a macro to generate enum values. + +#ifndef DEFINE_ACTIVITY_STATE +#error "DEFINE_ACTIVITY_STATE should be defined before including this file" +#endif +DEFINE_ACTIVITY_STATE(CREATED, 1) +DEFINE_ACTIVITY_STATE(STARTED, 2) +DEFINE_ACTIVITY_STATE(RESUMED, 3) +DEFINE_ACTIVITY_STATE(PAUSED, 4) +DEFINE_ACTIVITY_STATE(STOPPED, 5) +DEFINE_ACTIVITY_STATE(DESTROYED, 6) diff --git a/base/android/activity_status.cc b/base/android/activity_status.cc new file mode 100644 index 0000000000..4d0be32ef9 --- /dev/null +++ b/base/android/activity_status.cc @@ -0,0 +1,66 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/android/activity_status.h" + +#include + +#include "base/memory/singleton.h" +#include "jni/ActivityStatus_jni.h" + +namespace base { +namespace android { + +ActivityStatus::Listener::Listener( + const ActivityStatus::StateChangeCallback& callback) + : callback_(callback) { + ActivityStatus::GetInstance()->RegisterListener(this); +} + +ActivityStatus::Listener::~Listener() { + ActivityStatus::GetInstance()->UnregisterListener(this); +} + +void ActivityStatus::Listener::Notify(ActivityState state) { + callback_.Run(state); +} + +// static +ActivityStatus* ActivityStatus::GetInstance() { + return Singleton >::get(); +} + +static void OnActivityStateChange(JNIEnv* env, jclass clazz, int new_state) { + ActivityStatus* activity_status = ActivityStatus::GetInstance(); + ActivityState activity_state = static_cast(new_state); + activity_status->OnActivityStateChange(activity_state); +} + +bool ActivityStatus::RegisterBindings(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +ActivityStatus::ActivityStatus() + : observers_(new ObserverListThreadSafe()) { + Java_ActivityStatus_registerThreadSafeNativeStateListener( + base::android::AttachCurrentThread()); +} + +ActivityStatus::~ActivityStatus() {} + +void ActivityStatus::RegisterListener(Listener* listener) { + observers_->AddObserver(listener); +} + +void ActivityStatus::UnregisterListener(Listener* listener) { + observers_->RemoveObserver(listener); +} + +void ActivityStatus::OnActivityStateChange(ActivityState new_state) { + observers_->Notify(&ActivityStatus::Listener::Notify, new_state); +} + +} // namespace android +} // namespace base diff --git a/base/android/activity_status.h b/base/android/activity_status.h new file mode 100644 index 0000000000..7975a789cd --- /dev/null +++ b/base/android/activity_status.h @@ -0,0 +1,98 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_ANDROID_ACTIVITY_STATUS_H_ +#define BASE_ANDROID_ACTIVITY_STATUS_H_ + +#include + +#include "base/android/jni_android.h" +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/memory/singleton.h" +#include "base/observer_list_threadsafe.h" + +namespace base { +namespace android { + +// Define activity state values like ACTIVITY_STATE_CREATED in a +// way that ensures they're always the same than their Java counterpart. +enum ActivityState { +#define DEFINE_ACTIVITY_STATE(x, y) ACTIVITY_STATE_##x = y, +#include "base/android/activity_state_list.h" +#undef DEFINE_ACTIVITY_STATE +}; + +// A native helper class to listen to state changes of the current +// Android Activity. This mirrors org.chromium.base.ActivityStatus. +// any thread. +// +// To start listening, create a new instance, passing a callback to a +// function that takes an ActivityState parameter. To stop listening, +// simply delete the listener object. The implementation guarantees +// that the callback will always be called on the thread that created +// the listener. +// +// Example: +// +// void OnActivityStateChange(ActivityState state) { +// ... +// } +// +// // Start listening. +// ActivityStatus::Listener* my_listener = +// new ActivityStatus::Listener(base::Bind(&OnActivityStateChange)); +// +// ... +// +// // Stop listening. +// delete my_listener +// +class BASE_EXPORT ActivityStatus { + public: + typedef base::Callback StateChangeCallback; + + class Listener { + public: + explicit Listener(const StateChangeCallback& callback); + ~Listener(); + + private: + friend class ActivityStatus; + + void Notify(ActivityState state); + + StateChangeCallback callback_; + + DISALLOW_COPY_AND_ASSIGN(Listener); + }; + + // NOTE: The Java ActivityStatus is a singleton too. + static ActivityStatus* GetInstance(); + + // Internal use: must be public to be called from base_jni_registrar.cc + static bool RegisterBindings(JNIEnv* env); + + // Internal use only: must be public to be called from JNI and unit tests. + void OnActivityStateChange(ActivityState new_state); + + private: + friend struct DefaultSingletonTraits; + + ActivityStatus(); + ~ActivityStatus(); + + void RegisterListener(Listener* listener); + void UnregisterListener(Listener* listener); + + scoped_refptr > observers_; + + DISALLOW_COPY_AND_ASSIGN(ActivityStatus); +}; + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_ACTIVITY_STATUS_H_ diff --git a/base/android/activity_status_unittest.cc b/base/android/activity_status_unittest.cc new file mode 100644 index 0000000000..3eb0d10f73 --- /dev/null +++ b/base/android/activity_status_unittest.cc @@ -0,0 +1,128 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/android/activity_status.h" +#include "base/bind.h" +#include "base/callback_forward.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/run_loop.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace android { + +namespace { + +using base::android::ScopedJavaLocalRef; + +// An invalid ActivityState value. +const ActivityState kInvalidActivityState = static_cast(100); + +// Used to generate a callback that stores the new state at a given location. +void StoreStateTo(ActivityState* target, ActivityState state) { + *target = state; +} + +void RunTasksUntilIdle() { + RunLoop run_loop; + run_loop.RunUntilIdle(); +} + +// Shared state for the multi-threaded test. +// This uses a thread to register for events and listen to them, while state +// changes are forced on the main thread. +class MultiThreadedTest { + public: + MultiThreadedTest() + : activity_status_(ActivityStatus::GetInstance()), + state_(kInvalidActivityState), + event_(false, false), + thread_("ActivityStatusTest thread"), + main_() { + } + + void Run() { + // Start the thread and tell it to register for events. + thread_.Start(); + thread_.message_loop() + ->PostTask(FROM_HERE, + base::Bind(&MultiThreadedTest::RegisterThreadForEvents, + base::Unretained(this))); + + // Wait for its completion. + event_.Wait(); + + // Change state, then wait for the thread to modify state. + activity_status_->OnActivityStateChange(ACTIVITY_STATE_CREATED); + event_.Wait(); + EXPECT_EQ(ACTIVITY_STATE_CREATED, state_); + + // Again + activity_status_->OnActivityStateChange(ACTIVITY_STATE_DESTROYED); + event_.Wait(); + EXPECT_EQ(ACTIVITY_STATE_DESTROYED, state_); + } + + private: + void ExpectOnThread() { + EXPECT_EQ(thread_.message_loop(), base::MessageLoop::current()); + } + + void RegisterThreadForEvents() { + ExpectOnThread(); + listener_.reset(new ActivityStatus::Listener(base::Bind( + &MultiThreadedTest::StoreStateAndSignal, base::Unretained(this)))); + EXPECT_TRUE(listener_.get()); + event_.Signal(); + } + + void StoreStateAndSignal(ActivityState state) { + ExpectOnThread(); + state_ = state; + event_.Signal(); + } + + ActivityStatus* const activity_status_; + ActivityState state_; + base::WaitableEvent event_; + base::Thread thread_; + base::MessageLoop main_; + scoped_ptr listener_; +}; + +} // namespace + +TEST(ActivityStatusTest, SingleThread) { + MessageLoop message_loop; + + ActivityState result = kInvalidActivityState; + + // Create a new listener that stores the new state into |result| on every + // state change. + ActivityStatus::Listener listener( + base::Bind(&StoreStateTo, base::Unretained(&result))); + + EXPECT_EQ(kInvalidActivityState, result); + + ActivityStatus* const activity_status = ActivityStatus::GetInstance(); + activity_status->OnActivityStateChange(ACTIVITY_STATE_CREATED); + RunTasksUntilIdle(); + EXPECT_EQ(ACTIVITY_STATE_CREATED, result); + + activity_status->OnActivityStateChange(ACTIVITY_STATE_DESTROYED); + RunTasksUntilIdle(); + EXPECT_EQ(ACTIVITY_STATE_DESTROYED, result); +} + +TEST(ActivityStatusTest, TwoThreads) { + MultiThreadedTest test; + test.Run(); +} + +} // namespace android +} // namespace base diff --git a/base/android/base_jni_registrar.cc b/base/android/base_jni_registrar.cc new file mode 100644 index 0000000000..0645c73030 --- /dev/null +++ b/base/android/base_jni_registrar.cc @@ -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. + +#include "base/android/base_jni_registrar.h" + +#include "base/android/activity_status.h" +#include "base/android/build_info.h" +#include "base/android/cpu_features.h" +#include "base/android/important_file_writer_android.h" +#include "base/android/java_handler_thread.h" +#include "base/android/jni_android.h" +#include "base/android/jni_registrar.h" +#include "base/android/memory_pressure_listener_android.h" +#include "base/android/path_service_android.h" +#include "base/android/path_utils.h" +#include "base/android/sys_utils.h" +#include "base/android/thread_utils.h" +#include "base/basictypes.h" +#include "base/debug/trace_event.h" +#include "base/message_loop/message_pump_android.h" +#include "base/power_monitor/power_monitor_device_source_android.h" + +#if defined(GOOGLE_TV) +#include "base/android/context_types.h" +#endif + +namespace base { +namespace android { + +static RegistrationMethod kBaseRegisteredMethods[] = { + { "ActivityStatus", base::android::ActivityStatus::RegisterBindings }, + { "BuildInfo", base::android::BuildInfo::RegisterBindings }, +#if defined(GOOGLE_TV) + { "ContextTypes", base::android::RegisterContextTypes }, +#endif + { "CpuFeatures", base::android::RegisterCpuFeatures }, + { "ImportantFileWriterAndroid", + base::android::RegisterImportantFileWriterAndroid }, + { "MemoryPressureListenerAndroid", + base::android::MemoryPressureListenerAndroid::Register }, + { "JavaHandlerThread", base::android::JavaHandlerThread::RegisterBindings }, + { "PathService", base::android::RegisterPathService }, + { "PathUtils", base::android::RegisterPathUtils }, + { "SystemMessageHandler", base::MessagePumpForUI::RegisterBindings }, + { "SysUtils", base::android::SysUtils::Register }, + { "PowerMonitor", base::RegisterPowerMonitor }, + { "ThreadUtils", base::RegisterThreadUtils }, +}; + +bool RegisterJni(JNIEnv* env) { + TRACE_EVENT0("startup", "base_android::RegisterJni"); + return RegisterNativeMethods(env, kBaseRegisteredMethods, + arraysize(kBaseRegisteredMethods)); +} + +} // namespace android +} // namespace base diff --git a/base/android/base_jni_registrar.h b/base/android/base_jni_registrar.h new file mode 100644 index 0000000000..fdaf5f2c6c --- /dev/null +++ b/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 + +#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/base/android/build_info.cc b/base/android/build_info.cc new file mode 100644 index 0000000000..cdde6a9005 --- /dev/null +++ b/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 + +#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& 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::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/base/android/build_info.h b/base/android/build_info.h new file mode 100644 index 0000000000..a5f44c2e0a --- /dev/null +++ b/base/android/build_info.h @@ -0,0 +1,115 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_ANDROID_BUILD_INFO_H_ +#define BASE_ANDROID_BUILD_INFO_H_ + +#include + +#include + +#include "base/base_export.h" +#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 BASE_EXPORT 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/base/android/context_types.cc b/base/android/context_types.cc new file mode 100644 index 0000000000..084b1365ce --- /dev/null +++ b/base/android/context_types.cc @@ -0,0 +1,26 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/android/context_types.h" + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/files/file_path.h" +#include "jni/ContextTypes_jni.h" + +namespace base { +namespace android { + +bool IsRunningInWebapp() { + JNIEnv* env = AttachCurrentThread(); + return static_cast( + Java_ContextTypes_isRunningInWebapp(env, GetApplicationContext())); +} + +bool RegisterContextTypes(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace base diff --git a/base/android/context_types.h b/base/android/context_types.h new file mode 100644 index 0000000000..a132167199 --- /dev/null +++ b/base/android/context_types.h @@ -0,0 +1,22 @@ +// 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_ANDROID_CONTEXT_TYPES_H_ +#define BASE_ANDROID_CONTEXT_TYPES_H_ + +#include + +#include "base/base_export.h" + +namespace base { +namespace android { + +BASE_EXPORT bool IsRunningInWebapp(); + +bool RegisterContextTypes(JNIEnv* env); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_CONTEXT_TYPES_H_ diff --git a/base/android/cpu_features.cc b/base/android/cpu_features.cc new file mode 100644 index 0000000000..6a1869534b --- /dev/null +++ b/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 + +#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/base/android/cpu_features.h b/base/android/cpu_features.h new file mode 100644 index 0000000000..0a278228d2 --- /dev/null +++ b/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 + +namespace base { +namespace android { + +bool RegisterCpuFeatures(JNIEnv* env); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_CPU_FEATURES_H_ diff --git a/base/android/fifo_utils.cc b/base/android/fifo_utils.cc new file mode 100644 index 0000000000..8f3e95f92d --- /dev/null +++ b/base/android/fifo_utils.cc @@ -0,0 +1,25 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/android/fifo_utils.h" + +#include + +#include "base/files/file_path.h" + +namespace base { +namespace android { + +bool CreateFIFO(const FilePath& path, int mode) { + // Default permissions for mkfifo() is ignored, chmod() is required. + return mkfifo(path.value().c_str(), mode) == 0 && + chmod(path.value().c_str(), mode) == 0; +} + +bool RedirectStream(FILE* stream, const FilePath& path, const char* mode) { + return freopen(path.value().c_str(), mode, stream) != NULL; +} + +} // namespace android +} // namespace base diff --git a/base/android/fifo_utils.h b/base/android/fifo_utils.h new file mode 100644 index 0000000000..1936defaf6 --- /dev/null +++ b/base/android/fifo_utils.h @@ -0,0 +1,32 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_ANDROID_FIFO_UTILS_H_ +#define BASE_ANDROID_FIFO_UTILS_H_ + +#include + +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace base { + +class FilePath; + +namespace android { + +// Creates a fifo at the given |path| with POSIX permissions set to |mode|, +// returning true if it was successfully created and permissions were set. +BASE_EXPORT bool CreateFIFO(const FilePath& path, int mode); + +// Redirects the |stream| to the file provided by |path| with |mode| +// permissions, returning true if successful. +BASE_EXPORT bool RedirectStream(FILE* stream, + const FilePath& path, + const char* mode); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_FIFO_UTILS_H_ diff --git a/base/android/important_file_writer_android.cc b/base/android/important_file_writer_android.cc new file mode 100644 index 0000000000..bcbd785da0 --- /dev/null +++ b/base/android/important_file_writer_android.cc @@ -0,0 +1,42 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/android/important_file_writer_android.h" + +#include + +#include "base/android/jni_string.h" +#include "base/files/important_file_writer.h" +#include "base/threading/thread_restrictions.h" +#include "jni/ImportantFileWriterAndroid_jni.h" + +namespace base { +namespace android { + +static jboolean WriteFileAtomically(JNIEnv* env, + jclass /* clazz */, + jstring file_name, + jbyteArray data) { + // This is called on the UI thread during shutdown to save tab data, so + // needs to enable IO. + base::ThreadRestrictions::ScopedAllowIO allow_io; + std::string native_file_name; + base::android::ConvertJavaStringToUTF8(env, file_name, &native_file_name); + base::FilePath path(native_file_name); + int data_length = env->GetArrayLength(data); + jbyte* native_data = env->GetByteArrayElements(data, NULL); + std::string native_data_string(reinterpret_cast(native_data), + data_length); + bool result = base::ImportantFileWriter::WriteFileAtomically( + path, native_data_string); + env->ReleaseByteArrayElements(data, native_data, JNI_ABORT); + return result; +} + +bool RegisterImportantFileWriterAndroid(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace base diff --git a/base/android/important_file_writer_android.h b/base/android/important_file_writer_android.h new file mode 100644 index 0000000000..20956babce --- /dev/null +++ b/base/android/important_file_writer_android.h @@ -0,0 +1,18 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NATIVE_FRAMEWORK_CHROME_IMPORTANT_FILE_WRITE_ANDROID_H_ +#define NATIVE_FRAMEWORK_CHROME_IMPORTANT_FILE_WRITE_ANDROID_H_ + +#include + +namespace base { +namespace android { + +bool RegisterImportantFileWriterAndroid(JNIEnv* env); + +} // namespace android +} // namespace base + +#endif // NATIVE_FRAMEWORK_CHROME_IMPORTANT_FILE_WRITE_ANDROID_H_ diff --git a/base/android/java/src/org/chromium/base/AccessedByNative.java b/base/android/java/src/org/chromium/base/AccessedByNative.java new file mode 100644 index 0000000000..0a73258721 --- /dev/null +++ b/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.CLASS) +public @interface AccessedByNative { + public String value() default ""; +} diff --git a/base/android/java/src/org/chromium/base/ActivityState.template b/base/android/java/src/org/chromium/base/ActivityState.template new file mode 100644 index 0000000000..adf990a576 --- /dev/null +++ b/base/android/java/src/org/chromium/base/ActivityState.template @@ -0,0 +1,14 @@ +// 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. + +package org.chromium.base; + +// A simple auto-generated interface used to list the various +// states of an activity as used by both org.chromium.base.ActivityStatus +// and base/android/activity_status.h +interface ActivityState { +#define DEFINE_ACTIVITY_STATE(x,y) public final int x = y; +#include "base/android/activity_state_list.h" +#undef DEFINE_ACTIVITY_STATE +} diff --git a/base/android/java/src/org/chromium/base/ActivityStatus.java b/base/android/java/src/org/chromium/base/ActivityStatus.java new file mode 100644 index 0000000000..47472342b2 --- /dev/null +++ b/base/android/java/src/org/chromium/base/ActivityStatus.java @@ -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. + +package org.chromium.base; + +import android.app.Activity; +import android.os.Handler; +import android.os.Looper; + +/** + * Provides information about the current activity's status, and a way + * to register / unregister listeners for state changes. + */ +@JNINamespace("base::android") +public class ActivityStatus { + + // Constants matching activity states reported to StateListener.onStateChange + // As an implementation detail, these are now defined in the auto-generated + // ActivityState interface, to be shared with C++. + public static final int CREATED = ActivityState.CREATED; + public static final int STARTED = ActivityState.STARTED; + public static final int RESUMED = ActivityState.RESUMED; + public static final int PAUSED = ActivityState.PAUSED; + public static final int STOPPED = ActivityState.STOPPED; + public static final int DESTROYED = ActivityState.DESTROYED; + + // 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 ObserverList sStateListeners = + new ObserverList(); + + /** + * Interface to be implemented by listeners. + */ + 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 (sActivity != activity) { + // ActivityStatus is notified with the CREATED event very late during the main activity + // creation to avoid making startup performance worse than it is by notifying observers + // that could do some expensive work. This can lead to non-CREATED events being fired + // before the CREATED event which is problematic. + // TODO(pliard): fix http://crbug.com/176837. + 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.addObserver(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.removeObserver(listener); + } + + /** + * Registers the single thread-safe native activity status listener. + * This handles the case where the caller is not on the main thread. + * Note that this is used by a leaky singleton object from the native + * side, hence lifecycle management is greatly simplified. + */ + @CalledByNative + private static void registerThreadSafeNativeStateListener() { + ThreadUtils.runOnUiThread(new Runnable () { + @Override + public void run() { + // Register a new listener that calls nativeOnActivityStateChange. + sStateListeners.addObserver(new StateListener() { + @Override + public void onActivityStateChange(int newState) { + nativeOnActivityStateChange(newState); + } + }); + } + }); + } + + // Called to notify the native side of state changes. + // IMPORTANT: This is always called on the main thread! + private static native void nativeOnActivityStateChange(int newState); +} diff --git a/base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java b/base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java new file mode 100644 index 0000000000..8c4ac0fe01 --- /dev/null +++ b/base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java @@ -0,0 +1,140 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.base; + +import android.app.Notification; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.view.View; +import android.view.ViewGroup.MarginLayoutParams; +import android.view.ViewTreeObserver; + +/** + * Utility class to use new APIs that were added after ICS (API level 14). + */ +public class ApiCompatibilityUtils { + + private ApiCompatibilityUtils() { + } + + /** + * Returns true if view's layout direction is right-to-left. + * + * @param view the View whose layout is being considered + */ + public static boolean isLayoutRtl(View view) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + return view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; + } else { + // All layouts are LTR before JB MR1. + return false; + } + } + + /** + * @see android.view.View#setLayoutDirection(int) + */ + public static void setLayoutDirection(View view, int layoutDirection) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + view.setLayoutDirection(layoutDirection); + } else { + // Do nothing. RTL layouts aren't supported before JB MR1. + } + } + + /** + * @see android.view.ViewGroup.MarginLayoutParams#setMarginEnd(int) + */ + public static void setMarginEnd(MarginLayoutParams layoutParams, int end) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + layoutParams.setMarginEnd(end); + } else { + layoutParams.rightMargin = end; + } + } + + /** + * @see android.view.ViewGroup.MarginLayoutParams#getMarginEnd() + */ + public static int getMarginEnd(MarginLayoutParams layoutParams) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + return layoutParams.getMarginEnd(); + } else { + return layoutParams.rightMargin; + } + } + + /** + * @see android.view.ViewGroup.MarginLayoutParams#setMarginStart(int) + */ + public static void setMarginStart(MarginLayoutParams layoutParams, int start) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + layoutParams.setMarginStart(start); + } else { + layoutParams.leftMargin = start; + } + } + + /** + * @see android.view.ViewGroup.MarginLayoutParams#getMarginStart() + */ + public static int getMarginStart(MarginLayoutParams layoutParams) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + return layoutParams.getMarginStart(); + } else { + return layoutParams.leftMargin; + } + } + + /** + * @see android.view.View#postInvalidateOnAnimation() + */ + public static void postInvalidateOnAnimation(View view) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + view.postInvalidateOnAnimation(); + } else { + view.postInvalidate(); + } + } + + // These methods have a new name, and the old name is deprecated. + + /** + * @see android.view.View#setBackground(Drawable) + */ + @SuppressWarnings("deprecation") + public static void setBackgroundForView(View view, Drawable drawable) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + view.setBackground(drawable); + } else { + view.setBackgroundDrawable(drawable); + } + } + + /** + * @see android.view.ViewTreeObserver#removeOnGlobalLayoutListener() + */ + @SuppressWarnings("deprecation") + public static void removeOnGlobalLayoutListener( + View view, ViewTreeObserver.OnGlobalLayoutListener listener) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + view.getViewTreeObserver().removeOnGlobalLayoutListener(listener); + } else { + view.getViewTreeObserver().removeGlobalOnLayoutListener(listener); + } + } + + /** + * @see android.app.Notification.Builder#build() + */ + @SuppressWarnings("deprecation") + public static Notification buildNotification(Notification.Builder builder) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + return builder.build(); + } else { + return builder.getNotification(); + } + } +} diff --git a/base/android/java/src/org/chromium/base/BuildInfo.java b/base/android/java/src/org/chromium/base/BuildInfo.java new file mode 100644 index 0000000000..81a8f34559 --- /dev/null +++ b/base/android/java/src/org/chromium/base/BuildInfo.java @@ -0,0 +1,117 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +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 = ""; + if (pi.versionCode > 0) { + msg = Integer.toString(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/base/android/java/src/org/chromium/base/CalledByNative.java b/base/android/java/src/org/chromium/base/CalledByNative.java new file mode 100644 index 0000000000..db01b0d072 --- /dev/null +++ b/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.CLASS) +public @interface CalledByNative { + /* + * If present, tells which inner class the method belongs to. + */ + public String value() default ""; +} diff --git a/base/android/java/src/org/chromium/base/CalledByNativeUnchecked.java b/base/android/java/src/org/chromium/base/CalledByNativeUnchecked.java new file mode 100644 index 0000000000..38bb0c0450 --- /dev/null +++ b/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.CLASS) +public @interface CalledByNativeUnchecked { + /* + * If present, tells which inner class the method belongs to. + */ + public String value() default ""; +} diff --git a/base/android/java/src/org/chromium/base/ChromiumActivity.java b/base/android/java/src/org/chromium/base/ChromiumActivity.java new file mode 100644 index 0000000000..65f5ce929b --- /dev/null +++ b/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/base/android/java/src/org/chromium/base/ContextTypes.java b/base/android/java/src/org/chromium/base/ContextTypes.java new file mode 100644 index 0000000000..35ecd8fd82 --- /dev/null +++ b/base/android/java/src/org/chromium/base/ContextTypes.java @@ -0,0 +1,96 @@ +// 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. + +package org.chromium.base; + +import android.content.Context; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.Map; + +/** + * Maintains the {@link Context}-to-"context type" mapping. The context type + * {@code MODE_APP} is chosen for the application context associated with + * the activity running in application mode, while {@code MODE_NORMAL} for main + * Chromium activity. + * + *

Used as singleton instance. + */ +public class ContextTypes { + + // Available context types. + public static final int CONTEXT_TYPE_NORMAL = 1; + public static final int CONTEXT_TYPE_WEBAPP = 2; + + private final Map mContextMap; + + private ContextTypes() { + mContextMap = new ConcurrentHashMap(); + } + + private static class ContextTypesHolder { + private static final ContextTypes INSTANCE = new ContextTypes(); + } + + public static ContextTypes getInstance() { + return ContextTypesHolder.INSTANCE; + } + + /** + * Adds the mapping for the given {@link Context}. + * + * @param context {@link Context} in interest + * @param type the type associated with the context + * @throws IllegalArgumentException if type is not a valid one. + */ + public void put(Context context, int type) throws IllegalArgumentException { + if (type != CONTEXT_TYPE_NORMAL && type != CONTEXT_TYPE_WEBAPP) { + throw new IllegalArgumentException("Wrong context type"); + } + mContextMap.put(context, type); + } + + /** + * Removes the mapping for the given context. + * + * @param context {@link Context} in interest + */ + public void remove(Context context) { + mContextMap.remove(context); + } + + /** + * Returns type of the given context. + * + * @param context {@link Context} in interest + * @return type associated with the context. Returns {@code MODE_NORMAL} by + * default if the mapping for the queried context is not present. + */ + public int getType(Context context) { + Integer contextType = mContextMap.get(context); + return contextType == null ? CONTEXT_TYPE_NORMAL : contextType; + } + + /** + * Returns whether activity is running in web app mode. + * + * @param appContext {@link Context} in interest + * @return {@code true} when activity is running in web app mode. + */ + @CalledByNative + public static boolean isRunningInWebapp(Context appContext) { + return ContextTypes.getInstance().getType(appContext) + == CONTEXT_TYPE_WEBAPP; + } + + /** + * Checks if the mapping exists for the given context. + * + * @param context {@link Context} in interest + * @return {@code true} if the mapping exists; otherwise {@code false} + */ + public boolean contains(Context context) { + return mContextMap.containsKey(context); + } +} diff --git a/base/android/java/src/org/chromium/base/CpuFeatures.java b/base/android/java/src/org/chromium/base/CpuFeatures.java new file mode 100644 index 0000000000..f298fb1e16 --- /dev/null +++ b/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/base/android/java/src/org/chromium/base/ImportantFileWriterAndroid.java b/base/android/java/src/org/chromium/base/ImportantFileWriterAndroid.java new file mode 100644 index 0000000000..1c7c0185da --- /dev/null +++ b/base/android/java/src/org/chromium/base/ImportantFileWriterAndroid.java @@ -0,0 +1,29 @@ +// 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. + +package org.chromium.base; + +/** + * This class provides an interface to the native class for writing + * important data files without risking data loss. + */ +@JNINamespace("base::android") +public class ImportantFileWriterAndroid { + + /** + * Write a binary file atomically. + * + * This either writes all the data or leaves the file unchanged. + * + * @param fileName The complete path of the file to be written + * @param data The data to be written to the file + * @return true if the data was written to the file, false if not. + */ + public static boolean writeFileAtomically(String fileName, byte[] data) { + return nativeWriteFileAtomically(fileName, data); + } + + private static native boolean nativeWriteFileAtomically( + String fileName, byte[] data); +} diff --git a/base/android/java/src/org/chromium/base/JNINamespace.java b/base/android/java/src/org/chromium/base/JNINamespace.java new file mode 100644 index 0000000000..cfffc91a34 --- /dev/null +++ b/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/base/android/java/src/org/chromium/base/JavaHandlerThread.java b/base/android/java/src/org/chromium/base/JavaHandlerThread.java new file mode 100644 index 0000000000..5f9960e53c --- /dev/null +++ b/base/android/java/src/org/chromium/base/JavaHandlerThread.java @@ -0,0 +1,41 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.base; + +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; + +/** + * This class is an internal detail of the native counterpart. + * It is instantiated and owned by the native object. + */ +@JNINamespace("base::android") +class JavaHandlerThread { + final HandlerThread mThread; + + private JavaHandlerThread(String name) { + mThread = new HandlerThread(name); + } + + @CalledByNative + private static JavaHandlerThread create(String name) { + return new JavaHandlerThread(name); + } + + @CalledByNative + private void start(final int nativeThread, final int nativeEvent) { + mThread.start(); + new Handler(mThread.getLooper()).post(new Runnable() { + @Override + public void run() { + nativeInitializeThread(nativeThread, nativeEvent); + } + }); + } + + private native void nativeInitializeThread(int nativeJavaHandlerThread, int nativeEvent); +} \ No newline at end of file diff --git a/base/android/java/src/org/chromium/base/MemoryPressureLevelList.template b/base/android/java/src/org/chromium/base/MemoryPressureLevelList.template new file mode 100644 index 0000000000..cebca842e3 --- /dev/null +++ b/base/android/java/src/org/chromium/base/MemoryPressureLevelList.template @@ -0,0 +1,12 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.base; + +class MemoryPressureLevelList { +#define DEFINE_MEMORY_PRESSURE_LEVEL(name, value) \ + static final int name = value; +#include "base/memory/memory_pressure_level_list.h" +#undef DEFINE_MEMORY_PRESSURE_LEVEL +} diff --git a/base/android/java/src/org/chromium/base/MemoryPressureListener.java b/base/android/java/src/org/chromium/base/MemoryPressureListener.java new file mode 100644 index 0000000000..311b0f6ff4 --- /dev/null +++ b/base/android/java/src/org/chromium/base/MemoryPressureListener.java @@ -0,0 +1,55 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.base; + +import android.content.ComponentCallbacks2; +import android.content.Context; +import android.content.res.Configuration; + + +/** + * This is an internal implementation of the C++ counterpart. + * It registers a ComponentCallbacks2 with the system, and dispatches into + * native. + */ +public class MemoryPressureListener { + @CalledByNative + private static void registerSystemCallback(Context context) { + context.registerComponentCallbacks( + new ComponentCallbacks2() { + @Override + public void onTrimMemory(int level) { + maybeNotifyMemoryPresure(level); + } + + @Override + public void onLowMemory() { + nativeOnMemoryPressure(MemoryPressureLevelList.MEMORY_PRESSURE_CRITICAL); + } + + @Override + public void onConfigurationChanged(Configuration configuration) { + } + }); + } + + /** + * Used by applications to simulate a memory pressure signal. + */ + public static void simulateMemoryPressureSignal(int level) { + maybeNotifyMemoryPresure(level); + } + + private static void maybeNotifyMemoryPresure(int level) { + if (level == ComponentCallbacks2.TRIM_MEMORY_COMPLETE) { + nativeOnMemoryPressure(MemoryPressureLevelList.MEMORY_PRESSURE_CRITICAL); + } else if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND || + level == ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) { + nativeOnMemoryPressure(MemoryPressureLevelList.MEMORY_PRESSURE_MODERATE); + } + } + + private static native void nativeOnMemoryPressure(int memoryPressureType); +} diff --git a/base/android/java/src/org/chromium/base/NativeClassQualifiedName.java b/base/android/java/src/org/chromium/base/NativeClassQualifiedName.java new file mode 100644 index 0000000000..309169bb1d --- /dev/null +++ b/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/base/android/java/src/org/chromium/base/ObserverList.java b/base/android/java/src/org/chromium/base/ObserverList.java new file mode 100644 index 0000000000..13a81c5c19 --- /dev/null +++ b/base/android/java/src/org/chromium/base/ObserverList.java @@ -0,0 +1,177 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.base; + +import java.lang.Iterable; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +import javax.annotation.concurrent.NotThreadSafe; + +/** + * A container for a list of observers. + *

+ * This container can be modified during iteration without invalidating the iterator. + * So, it safely handles the case of an observer removing itself or other observers from the list + * while observers are being notified. + *

+ * The implementation (and the interface) is heavily influenced by the C++ ObserverList. + * Notable differences: + * - The iterator implements NOTIFY_EXISTING_ONLY. + * - The FOR_EACH_OBSERVER closure is left to the clients to implement in terms of iterator(). + *

+ * This class is not threadsafe. Observers MUST be added, removed and will be notified on the same + * thread this is created. + */ +@NotThreadSafe +public class ObserverList implements Iterable { + public final List mObservers = new ArrayList(); + private int mIterationDepth = 0; + + public ObserverList() {} + + /** + * Add an observer to the list. + *

+ * An observer should not be added to the same list more than once. If an iteration is already + * in progress, this observer will be not be visible during that iteration. + */ + public void addObserver(E obs) { + // Avoid adding null elements to the list as they may be removed on a compaction. + if (obs == null || mObservers.contains(obs)) { + assert false; + return; + } + + // Structurally modifying the underlying list here. This means we + // cannot use the underlying list's iterator to iterate over the list. + mObservers.add(obs); + } + + /** + * Remove an observer from the list if it is in the list. + */ + public void removeObserver(E obs) { + int index = mObservers.indexOf(obs); + + if (index == -1) + return; + + if (mIterationDepth == 0) { + // No one is iterating over the list. + mObservers.remove(obs); + } else { + mObservers.set(index, null); + } + } + + public boolean hasObserver(E obs) { + return mObservers.contains(obs); + } + + public void clear() { + if (mIterationDepth == 0) { + mObservers.clear(); + return; + } + + int size = mObservers.size(); + for (int i = 0; i < size; i++) + mObservers.set(i, null); + } + + @Override + public Iterator iterator() { + return new ObserverListIterator(); + } + + /** + * Compact the underlying list be removing null elements. + *

+ * Should only be called when mIterationDepth is zero. + */ + private void compact() { + assert mIterationDepth == 0; + // Safe to use the underlying list's iterator, as we know that no-one else + // is iterating over the list. + Iterator it = mObservers.iterator(); + while (it.hasNext()) { + E el = it.next(); + if (el == null) + it.remove(); + } + } + + private void incrementIterationDepth() { + mIterationDepth++; + } + + private void decrementIterationDepthAndCompactIfNeeded() { + mIterationDepth--; + assert mIterationDepth >= 0; + if (mIterationDepth == 0) + compact(); + } + + private int getSize() { + return mObservers.size(); + } + + private E getObserverAt(int index) { + return mObservers.get(index); + } + + private class ObserverListIterator implements Iterator { + private final int mListEndMarker; + private int mIndex = 0; + private boolean mIsExhausted = false; + + private ObserverListIterator() { + ObserverList.this.incrementIterationDepth(); + mListEndMarker = ObserverList.this.getSize(); + } + + @Override + public boolean hasNext() { + int lookupIndex = mIndex; + while (lookupIndex < mListEndMarker && + ObserverList.this.getObserverAt(lookupIndex) == null) + lookupIndex++; + if (lookupIndex < mListEndMarker) + return true; + + // We have reached the end of the list, allow for compaction. + compactListIfNeeded(); + return false; + } + + @Override + public E next() { + // Advance if the current element is null. + while (mIndex < mListEndMarker && ObserverList.this.getObserverAt(mIndex) == null) + mIndex++; + if (mIndex < mListEndMarker) + return ObserverList.this.getObserverAt(mIndex++); + + // We have reached the end of the list, allow for compaction. + compactListIfNeeded(); + throw new NoSuchElementException(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + private void compactListIfNeeded() { + if (!mIsExhausted) { + mIsExhausted = true; + ObserverList.this.decrementIterationDepthAndCompactIfNeeded(); + } + } + } +} diff --git a/base/android/java/src/org/chromium/base/PathService.java b/base/android/java/src/org/chromium/base/PathService.java new file mode 100644 index 0000000000..dfda736d01 --- /dev/null +++ b/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/base/android/java/src/org/chromium/base/PathUtils.java b/base/android/java/src/org/chromium/base/PathUtils.java new file mode 100644 index 0000000000..aee5c050c3 --- /dev/null +++ b/base/android/java/src/org/chromium/base/PathUtils.java @@ -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. + +package org.chromium.base; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.os.Environment; + +import java.io.File; + +/** + * This class provides the path related methods for the native library. + */ +public abstract class PathUtils { + + private static String sDataDirectorySuffix; + private static String sWebappDirectorySuffix; + private static String sWebappCacheDirectory; + + // 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; + } + + /** + * Sets the directory info used for chrome process running in application mode. + * + * @param webappSuffix The suffix of the directory used for storing webapp-specific profile + * @param cacheDir Cache directory name for web apps. + */ + public static void setWebappDirectoryInfo(String webappSuffix, String cacheDir) { + sWebappDirectorySuffix = webappSuffix; + sWebappCacheDirectory = cacheDir; + } + + /** + * @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 + public static String getCacheDirectory(Context appContext) { + if (ContextTypes.getInstance().getType(appContext) == ContextTypes.CONTEXT_TYPE_NORMAL) { + return appContext.getCacheDir().getPath(); + } + if (sWebappDirectorySuffix == null || sWebappCacheDirectory == null) { + throw new IllegalStateException( + "setWebappDirectoryInfo must be called before getCacheDirectory"); + } + return new File(appContext.getDir(sWebappDirectorySuffix, appContext.MODE_PRIVATE), + sWebappCacheDirectory).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/base/android/java/src/org/chromium/base/PowerMonitor.java b/base/android/java/src/org/chromium/base/PowerMonitor.java new file mode 100644 index 0000000000..b7a691e3ce --- /dev/null +++ b/base/android/java/src/org/chromium/base/PowerMonitor.java @@ -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. + +package org.chromium.base; + +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 PowerMonitor with the java side. + */ +@JNINamespace("base::android") +public class PowerMonitor implements ActivityStatus.StateListener { + private static final long SUSPEND_DELAY_MS = 1 * 60 * 1000; // 1 minute. + private static class LazyHolder { + private static final PowerMonitor INSTANCE = new PowerMonitor(); + } + private static PowerMonitor 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 = LazyHolder.INSTANCE; + } + + public static void create(Context context) { + if (sInstance == null) { + sInstance = LazyHolder.INSTANCE; + ActivityStatus.registerStateListener(sInstance); + IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); + Intent batteryStatusIntent = context.registerReceiver(null, ifilter); + onBatteryChargingChanged(batteryStatusIntent); + } + } + + private PowerMonitor() { + } + + 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/base/android/java/src/org/chromium/base/PowerStatusReceiver.java b/base/android/java/src/org/chromium/base/PowerStatusReceiver.java new file mode 100644 index 0000000000..f36c146273 --- /dev/null +++ b/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 + * PowerMonitor. + * 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) { + PowerMonitor.onBatteryChargingChanged(intent); + } +} diff --git a/base/android/java/src/org/chromium/base/SysUtils.java b/base/android/java/src/org/chromium/base/SysUtils.java new file mode 100644 index 0000000000..7af5a40069 --- /dev/null +++ b/base/android/java/src/org/chromium/base/SysUtils.java @@ -0,0 +1,30 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.base; + +import android.app.ActivityManager; +import android.app.ActivityManager.MemoryInfo; +import android.content.Context; +import android.os.Build; + +/** + * Exposes system related information about the current device. + */ +public class SysUtils { + private static Boolean sLowEndDevice; + + private SysUtils() { } + + /** + * @return Whether or not this device should be considered a low end device. + */ + public static boolean isLowEndDevice() { + if (sLowEndDevice == null) sLowEndDevice = nativeIsLowEndDevice(); + + return sLowEndDevice.booleanValue(); + } + + private static native boolean nativeIsLowEndDevice(); +} diff --git a/base/android/java/src/org/chromium/base/SystemMessageHandler.java b/base/android/java/src/org/chromium/base/SystemMessageHandler.java new file mode 100644 index 0000000000..fc25ff8457 --- /dev/null +++ b/base/android/java/src/org/chromium/base/SystemMessageHandler.java @@ -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. + +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; + + private SystemMessageHandler(int messagePumpDelegateNative) { + mMessagePumpDelegateNative = messagePumpDelegateNative; + } + + @Override + public void handleMessage(Message msg) { + nativeDoRunLoopOnce(mMessagePumpDelegateNative); + } + + @SuppressWarnings("unused") + @CalledByNative + private void setTimer() { + sendEmptyMessage(TIMER_MESSAGE); + } + + @SuppressWarnings("unused") + @CalledByNative + private void setDelayedTimer(long millis) { + removeMessages(DELAYED_TIMER_MESSAGE); + sendEmptyMessageDelayed(DELAYED_TIMER_MESSAGE, millis); + } + + @SuppressWarnings("unused") + @CalledByNative + private void removeTimer() { + removeMessages(TIMER_MESSAGE); + } + + @CalledByNative + private static SystemMessageHandler create(int messagePumpDelegateNative) { + return new SystemMessageHandler(messagePumpDelegateNative); + } + + private native void nativeDoRunLoopOnce(int messagePumpDelegateNative); +} diff --git a/base/android/java/src/org/chromium/base/ThreadUtils.java b/base/android/java/src/org/chromium/base/ThreadUtils.java new file mode 100644 index 0000000000..a880ede1e3 --- /dev/null +++ b/base/android/java/src/org/chromium/base/ThreadUtils.java @@ -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. + +package org.chromium.base; + +import android.os.Handler; +import android.os.Looper; +import android.os.Process; + +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 task = new FutureTask(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 runOnUiThreadBlockingNoException(Callable 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 runOnUiThreadBlocking(Callable c) throws ExecutionException { + FutureTask task = new FutureTask(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 FutureTask runOnUiThread(FutureTask 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 FutureTask runOnUiThread(Callable c) { + return runOnUiThread(new FutureTask(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 FutureTask postOnUiThread(FutureTask 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); + } + + /** + * Post the supplied Runnable to run on the main thread after the given amount of time. The + * method will not block, even if called on the UI thread. + * + * @param task The Runnable to run + * @param delayMillis The delay in milliseconds until the Runnable will be run + */ + public static void postOnUiThreadDelayed(Runnable r, long delayMillis) { + LazyHolder.sUiThreadHandler.postDelayed(r, delayMillis); + } + + /** + * 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(); + } + + /** + * Set thread priority to audio. + */ + @CalledByNative + public static void setThreadPriorityAudio(int tid) { + Process.setThreadPriority(tid, Process.THREAD_PRIORITY_AUDIO); + } + + private static class LazyHolder { + private static Handler sUiThreadHandler = new Handler(Looper.getMainLooper()); + } +} diff --git a/base/android/java/src/org/chromium/base/WeakContext.java b/base/android/java/src/org/chromium/base/WeakContext.java new file mode 100644 index 0000000000..d660cc9940 --- /dev/null +++ b/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 sWeakContext; + + public static void initializeWeakContext(final Context context) { + sWeakContext = new WeakReference(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() { + @Override + public Object call() { + return context.getSystemService(name); + } + }); + } +} diff --git a/base/android/java_handler_thread.cc b/base/android/java_handler_thread.cc new file mode 100644 index 0000000000..0528fe74ba --- /dev/null +++ b/base/android/java_handler_thread.cc @@ -0,0 +1,62 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/android/java_handler_thread.h" + +#include + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/message_loop/message_loop.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread_restrictions.h" +#include "jni/JavaHandlerThread_jni.h" + +namespace base { + +namespace android { + +JavaHandlerThread::JavaHandlerThread(const char* name) { + JNIEnv* env = base::android::AttachCurrentThread(); + + java_thread_.Reset(Java_JavaHandlerThread_create( + env, ConvertUTF8ToJavaString(env, name).Release())); +} + +JavaHandlerThread::~JavaHandlerThread() { +} + +void JavaHandlerThread::Start() { + // Check the thread has not already been started. + DCHECK(!message_loop_); + + JNIEnv* env = base::android::AttachCurrentThread(); + base::WaitableEvent initialize_event(false, false); + Java_JavaHandlerThread_start(env, + java_thread_.obj(), + reinterpret_cast(this), + reinterpret_cast(&initialize_event)); + // Wait for thread to be initialized so it is ready to be used when Start + // returns. + base::ThreadRestrictions::ScopedAllowWait wait_allowed; + initialize_event.Wait(); +} + +void JavaHandlerThread::Stop() { +} + +void JavaHandlerThread::InitializeThread(JNIEnv* env, jobject obj, jint event) { + // TYPE_JAVA to get the Android java style message loop. + message_loop_.reset(new base::MessageLoop(base::MessageLoop::TYPE_JAVA)); + static_cast(message_loop_.get())->Start(); + reinterpret_cast(event)->Signal(); +} + +// static +bool JavaHandlerThread::RegisterBindings(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace base diff --git a/base/android/java_handler_thread.h b/base/android/java_handler_thread.h new file mode 100644 index 0000000000..9f66d660c8 --- /dev/null +++ b/base/android/java_handler_thread.h @@ -0,0 +1,48 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_THREADING_JAVA_THREAD_H_ +#define BASE_THREADING_JAVA_THREAD_H_ + +#include + +#include "base/android/scoped_java_ref.h" +#include "base/memory/scoped_ptr.h" + +namespace base { + +class MessageLoop; +class WaitableEvent; + +namespace android { + +// A Java Thread with a native message loop. To run tasks, post them +// to the message loop and they will be scheduled along with Java tasks +// on the thread. +// This is useful for callbacks where the receiver expects a thread +// with a prepared Looper. +class BASE_EXPORT JavaHandlerThread { + public: + JavaHandlerThread(const char* name); + virtual ~JavaHandlerThread(); + + base::MessageLoop* message_loop() const { return message_loop_.get(); } + void Start(); + void Stop(); + + // Called from java on the newly created thread. + // Start() will not return before this methods has finished. + void InitializeThread(JNIEnv* env, jobject obj, jint event); + + static bool RegisterBindings(JNIEnv* env); + + private: + scoped_ptr message_loop_; + ScopedJavaGlobalRef java_thread_; +}; + +} // namespace android +} // namespace base + +#endif // BASE_THREADING_JAVA_THREAD_H_ diff --git a/base/android/javatests/src/org/chromium/base/ContextTypesTest.java b/base/android/javatests/src/org/chromium/base/ContextTypesTest.java new file mode 100644 index 0000000000..a26b3177d8 --- /dev/null +++ b/base/android/javatests/src/org/chromium/base/ContextTypesTest.java @@ -0,0 +1,43 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.base; + +import android.content.Context; +import android.test.AndroidTestCase; +import android.test.mock.MockContext; +import android.test.suitebuilder.annotation.SmallTest; + +import static org.chromium.base.ContextTypes.CONTEXT_TYPE_NORMAL; +import static org.chromium.base.ContextTypes.CONTEXT_TYPE_WEBAPP; + +public class ContextTypesTest extends AndroidTestCase { + + @SmallTest + public void testReturnsExpectedType() { + ContextTypes contextTypes = ContextTypes.getInstance(); + Context normal = new MockContext(); + Context webapp = new MockContext(); + contextTypes.put(normal, CONTEXT_TYPE_NORMAL); + contextTypes.put(webapp, CONTEXT_TYPE_WEBAPP); + assertEquals(CONTEXT_TYPE_NORMAL, contextTypes.getType(normal)); + assertEquals(CONTEXT_TYPE_WEBAPP, contextTypes.getType(webapp)); + } + + @SmallTest + public void testAbsentContextReturnsNormalType() { + assertEquals(CONTEXT_TYPE_NORMAL, ContextTypes.getInstance().getType(new MockContext())); + } + + @SmallTest + public void testPutInvalidTypeThrowsException() { + boolean exceptionThrown = false; + try { + ContextTypes.getInstance().put(new MockContext(), -1); + } catch (IllegalArgumentException e) { + exceptionThrown = true; + } + assertTrue(exceptionThrown); + } +} diff --git a/base/android/javatests/src/org/chromium/base/ObserverListTest.java b/base/android/javatests/src/org/chromium/base/ObserverListTest.java new file mode 100644 index 0000000000..10c898c2a3 --- /dev/null +++ b/base/android/javatests/src/org/chromium/base/ObserverListTest.java @@ -0,0 +1,180 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.base; + +import android.test.InstrumentationTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import org.chromium.base.test.util.Feature; + +import java.lang.Iterable; +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Tests for (@link ObserverList}. + */ +public class ObserverListTest extends InstrumentationTestCase { + interface Observer { + void observe(int x); + } + + private static class Foo implements Observer { + private final int mScalar; + private int mTotal = 0; + + Foo(int scalar) { + mScalar = scalar; + } + + @Override + public void observe(int x) { + mTotal += x * mScalar; + } + } + + /** + * An observer which add a given Observer object to the list when observe is called. + */ + private static class FooAdder implements Observer { + private final ObserverList mList; + private final Observer mLucky; + + FooAdder(ObserverList list, Observer oblivious) { + mList = list; + mLucky = oblivious; + } + + @Override + public void observe(int x) { + mList.addObserver(mLucky); + } + } + + /** + * An observer which removes a given Observer object from the list when observe is called. + */ + private static class FooRemover implements Observer { + private final ObserverList mList; + private final Observer mDoomed; + + FooRemover(ObserverList list, Observer innocent) { + mList = list; + mDoomed = innocent; + } + + @Override + public void observe(int x) { + mList.removeObserver(mDoomed); + } + } + + private static int getSizeOfIterable(Iterable iterable) { + int num = 0; + for (T el : iterable) + num++; + return num; + } + + @SmallTest + @Feature({"Android-AppBase"}) + public void testRemoveWhileIteration() { + ObserverList observerList = new ObserverList(); + Foo a = new Foo(1); + Foo b = new Foo(-1); + Foo c = new Foo(1); + Foo d = new Foo(-1); + Foo e = new Foo(-1); + FooRemover evil = new FooRemover(observerList, c); + + observerList.addObserver(a); + observerList.addObserver(b); + + for (Observer obs : observerList) + obs.observe(10); + + // Removing an observer not in the list should do nothing. + observerList.removeObserver(e); + + observerList.addObserver(evil); + observerList.addObserver(c); + observerList.addObserver(d); + + for (Observer obs : observerList) + obs.observe(10); + + // observe should be called twice on a. + assertEquals(20, a.mTotal); + // observe should be called twice on b. + assertEquals(-20, b.mTotal); + // evil removed c from the observerList before it got any callbacks. + assertEquals(0, c.mTotal); + // observe should be called once on d. + assertEquals(-10, d.mTotal); + // e was never added to the list, observe should not be called. + assertEquals(0, e.mTotal); + } + + @SmallTest + @Feature({"Android-AppBase"}) + public void testAddWhileIteration() { + ObserverList observerList = new ObserverList(); + Foo a = new Foo(1); + Foo b = new Foo(-1); + Foo c = new Foo(1); + FooAdder evil = new FooAdder(observerList, c); + + observerList.addObserver(evil); + observerList.addObserver(a); + observerList.addObserver(b); + + for (Observer obs : observerList) + obs.observe(10); + + assertTrue(observerList.hasObserver(c)); + assertEquals(10, a.mTotal); + assertEquals(-10, b.mTotal); + assertEquals(0, c.mTotal); + } + + @SmallTest + @Feature({"Android-AppBase"}) + public void testIterator() { + ObserverList observerList = new ObserverList(); + observerList.addObserver(5); + observerList.addObserver(10); + observerList.addObserver(15); + assertEquals(3, getSizeOfIterable(observerList)); + + observerList.removeObserver(10); + assertEquals(2, getSizeOfIterable(observerList)); + + Iterator it = observerList.iterator(); + assertTrue(it.hasNext()); + assertTrue(5 == it.next()); + assertTrue(it.hasNext()); + assertTrue(15 == it.next()); + assertFalse(it.hasNext()); + + boolean removeExceptionThrown = false; + try { + it.remove(); + fail("Expecting UnsupportedOperationException to be thrown here."); + } catch (UnsupportedOperationException e) { + removeExceptionThrown = true; + } + assertTrue(removeExceptionThrown); + assertEquals(2, getSizeOfIterable(observerList)); + + boolean noElementExceptionThrown = false; + try { + it.next(); + fail("Expecting NoSuchElementException to be thrown here."); + } catch (NoSuchElementException e) { + noElementExceptionThrown = true; + } + assertTrue(noElementExceptionThrown); + } +} diff --git a/base/android/jni_android.cc b/base/android/jni_android.cc new file mode 100644 index 0000000000..fc89b5403f --- /dev/null +++ b/base/android/jni_android.cc @@ -0,0 +1,323 @@ +// Copyright (c) 2012 The Chromium Authors. All 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 + +#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 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 >::Leaky + g_application_context = LAZY_INSTANCE_INITIALIZER; +base::LazyInstance g_method_id_map = LAZY_INSTANCE_INITIALIZER; + +std::string GetJavaExceptionInfo(JNIEnv* env, jthrowable java_throwable) { + ScopedJavaLocalRef throwable_clazz = + GetClass(env, "java/lang/Throwable"); + jmethodID throwable_printstacktrace = + MethodID::Get( + env, throwable_clazz.obj(), "printStackTrace", + "(Ljava/io/PrintStream;)V"); + + // Create an instance of ByteArrayOutputStream. + ScopedJavaLocalRef bytearray_output_stream_clazz = + GetClass(env, "java/io/ByteArrayOutputStream"); + jmethodID bytearray_output_stream_constructor = + MethodID::Get( + env, bytearray_output_stream_clazz.obj(), "", "()V"); + jmethodID bytearray_output_stream_tostring = + MethodID::Get( + env, bytearray_output_stream_clazz.obj(), "toString", + "()Ljava/lang/String;"); + ScopedJavaLocalRef bytearray_output_stream(env, + env->NewObject(bytearray_output_stream_clazz.obj(), + bytearray_output_stream_constructor)); + + // Create an instance of PrintStream. + ScopedJavaLocalRef printstream_clazz = + GetClass(env, "java/io/PrintStream"); + jmethodID printstream_constructor = + MethodID::Get( + env, printstream_clazz.obj(), "", + "(Ljava/io/OutputStream;)V"); + ScopedJavaLocalRef 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 exception_string( + env, static_cast( + 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& 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 GetClass(JNIEnv* env, const char* class_name) { + jclass clazz = env->FindClass(class_name); + CHECK(!ClearException(env) && clazz) << "Failed to find class " << class_name; + return ScopedJavaLocalRef(env, clazz); +} + +bool HasClass(JNIEnv* env, const char* class_name) { + ScopedJavaLocalRef clazz(env, env->FindClass(class_name)); + if (!clazz.obj()) { + ClearException(env); + return false; + } + bool error = ClearException(env); + DCHECK(!error); + return true; +} + +template +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 +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(value); + jmethodID id = MethodID::Get(env, clazz, method_name, jni_signature); + base::subtle::Release_Store( + atomic_method_id, reinterpret_cast(id)); + return id; +} + +// Various template instantiations. +template jmethodID MethodID::Get( + JNIEnv* env, jclass clazz, const char* method_name, + const char* jni_signature); + +template jmethodID MethodID::Get( + JNIEnv* env, jclass clazz, const char* method_name, + const char* jni_signature); + +template jmethodID MethodID::LazyGet( + JNIEnv* env, jclass clazz, const char* method_name, + const char* jni_signature, base::subtle::AtomicWord* atomic_method_id); + +template jmethodID MethodID::LazyGet( + JNIEnv* env, jclass clazz, const char* method_name, + const char* jni_signature, base::subtle::AtomicWord* atomic_method_id); + +jfieldID GetFieldID(JNIEnv* env, + const JavaRef& 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& 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& 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 clazz(env, env->FindClass(class_name)); + jmethodID id = MethodID::Get( + 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 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/base/android/jni_android.h b/base/android/jni_android.h new file mode 100644 index 0000000000..e8f68acfb7 --- /dev/null +++ b/base/android/jni_android.h @@ -0,0 +1,132 @@ +// Copyright (c) 2012 The Chromium Authors. All 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 +#include + +#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& 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 GetClass(JNIEnv* env, + const char* class_name); + +// 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 + 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 + 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& 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& 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& 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/base/android/jni_android_unittest.cc b/base/android/jni_android_unittest.cc new file mode 100644 index 0000000000..920b395659 --- /dev/null +++ b/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 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(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/base/android/jni_array.cc b/base/android/jni_array.cc new file mode 100644 index 0000000000..bbe00c6129 --- /dev/null +++ b/base/android/jni_array.cc @@ -0,0 +1,186 @@ +// Copyright (c) 2012 The Chromium Authors. All 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 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(env, byte_array); +} + +ScopedJavaLocalRef ToJavaLongArray( + JNIEnv* env, const int64* longs, size_t len) { + jlongArray long_array = env->NewLongArray(len); + CheckException(env); + DCHECK(long_array); + + jlong* elements = env->GetLongArrayElements(long_array, NULL); + memcpy(elements, longs, len * sizeof(*longs)); + env->ReleaseLongArrayElements(long_array, elements, 0); + CheckException(env); + + return ScopedJavaLocalRef(env, long_array); +} + +// Returns a new Java long array converted from the given int64 array. +BASE_EXPORT ScopedJavaLocalRef ToJavaLongArray( + JNIEnv* env, const std::vector& longs) { + return ToJavaLongArray(env, longs.begin(), longs.size()); +} + +ScopedJavaLocalRef ToJavaArrayOfByteArray( + JNIEnv* env, const std::vector& v) { + ScopedJavaLocalRef 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 byte_array = ToJavaByteArray(env, + reinterpret_cast(v[i].data()), v[i].length()); + env->SetObjectArrayElement(joa, i, byte_array.obj()); + } + return ScopedJavaLocalRef(env, joa); +} + +ScopedJavaLocalRef ToJavaArrayOfStrings( + JNIEnv* env, const std::vector& v) { + ScopedJavaLocalRef 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 item = ConvertUTF8ToJavaString(env, v[i]); + env->SetObjectArrayElement(joa, i, item.obj()); + } + return ScopedJavaLocalRef(env, joa); +} + +ScopedJavaLocalRef ToJavaArrayOfStrings( + JNIEnv* env, const std::vector& v) { + ScopedJavaLocalRef 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 item = ConvertUTF16ToJavaString(env, v[i]); + env->SetObjectArrayElement(joa, i, item.obj()); + } + return ScopedJavaLocalRef(env, joa); +} + +void AppendJavaStringArrayToStringVector(JNIEnv* env, + jobjectArray array, + std::vector* 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 str(env, + static_cast(env->GetObjectArrayElement(array, i))); + ConvertJavaStringToUTF16(env, str.obj(), &((*out)[back + i])); + } +} + +void AppendJavaStringArrayToStringVector(JNIEnv* env, + jobjectArray array, + std::vector* 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 str(env, + static_cast(env->GetObjectArrayElement(array, i))); + ConvertJavaStringToUTF8(env, str.obj(), &((*out)[back + i])); + } +} + +void AppendJavaByteArrayToByteVector(JNIEnv* env, + jbyteArray byte_array, + std::vector* 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* out) { + DCHECK(out); + out->clear(); + AppendJavaByteArrayToByteVector(env, byte_array, out); +} + +void JavaIntArrayToIntVector(JNIEnv* env, + jintArray int_array, + std::vector* out) { + DCHECK(out); + out->clear(); + jsize len = env->GetArrayLength(int_array); + jint* ints = env->GetIntArrayElements(int_array, NULL); + for (jsize i = 0; i < len; ++i) { + out->push_back(static_cast(ints[i])); + } + env->ReleaseIntArrayElements(int_array, ints, JNI_ABORT); +} + +void JavaFloatArrayToFloatVector(JNIEnv* env, + jfloatArray float_array, + std::vector* out) { + DCHECK(out); + out->clear(); + jsize len = env->GetArrayLength(float_array); + jfloat* floats = env->GetFloatArrayElements(float_array, NULL); + for (jsize i = 0; i < len; ++i) { + out->push_back(static_cast(floats[i])); + } + env->ReleaseFloatArrayElements(float_array, floats, JNI_ABORT); +} + +void JavaArrayOfByteArrayToStringVector( + JNIEnv* env, + jobjectArray array, + std::vector* 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( + env->GetObjectArrayElement(array, i)); + jsize bytes_len = env->GetArrayLength(bytes_array); + jbyte* bytes = env->GetByteArrayElements(bytes_array, NULL); + (*out)[i].assign(reinterpret_cast(bytes), bytes_len); + env->ReleaseByteArrayElements(bytes_array, bytes, JNI_ABORT); + } +} + +} // namespace android +} // namespace base diff --git a/base/android/jni_array.h b/base/android/jni_array.h new file mode 100644 index 0000000000..efa6aa8ffe --- /dev/null +++ b/base/android/jni_array.h @@ -0,0 +1,86 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_ANDROID_JNI_ARRAY_H_ +#define BASE_ANDROID_JNI_ARRAY_H_ + +#include +#include +#include + +#include "base/android/scoped_java_ref.h" +#include "base/basictypes.h" +#include "base/strings/string16.h" + +namespace base { +namespace android { + +// Returns a new Java byte array converted from the given bytes array. +BASE_EXPORT ScopedJavaLocalRef ToJavaByteArray( + JNIEnv* env, const uint8* bytes, size_t len); + +// Returns a new Java long array converted from the given int64 array. +BASE_EXPORT ScopedJavaLocalRef ToJavaLongArray( + JNIEnv* env, const int64* longs, size_t len); + +BASE_EXPORT ScopedJavaLocalRef ToJavaLongArray( + JNIEnv* env, const std::vector& longs); + +// Returns a array of Java byte array converted from |v|. +BASE_EXPORT ScopedJavaLocalRef ToJavaArrayOfByteArray( + JNIEnv* env, const std::vector& v); + +BASE_EXPORT ScopedJavaLocalRef ToJavaArrayOfStrings( + JNIEnv* env, const std::vector& v); + +BASE_EXPORT ScopedJavaLocalRef ToJavaArrayOfStrings( + JNIEnv* env, const std::vector& v); + +// Converts a Java string array to a native array. +BASE_EXPORT void AppendJavaStringArrayToStringVector( + JNIEnv* env, + jobjectArray array, + std::vector* out); + +BASE_EXPORT void AppendJavaStringArrayToStringVector( + JNIEnv* env, + jobjectArray array, + std::vector* out); + +// Appends the Java bytes in |bytes_array| onto the end of |out|. +BASE_EXPORT void AppendJavaByteArrayToByteVector( + JNIEnv* env, + jbyteArray byte_array, + std::vector* out); + +// Replaces the content of |out| with the Java bytes in |bytes_array|. +BASE_EXPORT void JavaByteArrayToByteVector( + JNIEnv* env, + jbyteArray byte_array, + std::vector* out); + +// Replaces the content of |out| with the Java ints in |int_array|. +BASE_EXPORT void JavaIntArrayToIntVector( + JNIEnv* env, + jintArray int_array, + std::vector* out); + +// Replaces the content of |out| with the Java floats in |float_array|. +BASE_EXPORT void JavaFloatArrayToFloatVector( + JNIEnv* env, + jfloatArray float_array, + std::vector* 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. +BASE_EXPORT void JavaArrayOfByteArrayToStringVector( + JNIEnv* env, + jobjectArray array, + std::vector* out); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_JNI_ARRAY_H_ diff --git a/base/android/jni_array_unittest.cc b/base/android/jni_array_unittest.cc new file mode 100644 index 0000000000..3ac7855e40 --- /dev/null +++ b/base/android/jni_array_unittest.cc @@ -0,0 +1,148 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/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 bytes = ToJavaByteArray(env, kBytes, kLen); + ASSERT_TRUE(bytes.obj()); + + std::vector vec(5); + JavaByteArrayToByteVector(env, bytes.obj(), &vec); + EXPECT_EQ(4U, vec.size()); + EXPECT_EQ(std::vector(kBytes, kBytes + kLen), vec); + + AppendJavaByteArrayToByteVector(env, bytes.obj(), &vec); + EXPECT_EQ(8U, vec.size()); +} + +void CheckLongConversion( + JNIEnv* env, + const int64* long_array, + const size_t len, + const ScopedJavaLocalRef& longs) { + ASSERT_TRUE(longs.obj()); + + jsize java_array_len = env->GetArrayLength(longs.obj()); + ASSERT_EQ(static_cast(len), java_array_len); + + jlong value; + for (size_t i = 0; i < len; ++i) { + env->GetLongArrayRegion(longs.obj(), i, 1, &value); + ASSERT_EQ(long_array[i], value); + } +} + +TEST(JniArray, LongConversions) { + const int64 kLongs[] = { 0, 1, -1, kint64min, kint64max}; + const size_t kLen = arraysize(kLongs); + + JNIEnv* env = AttachCurrentThread(); + CheckLongConversion(env, kLongs, kLen, ToJavaLongArray(env, kLongs, kLen)); + + const std::vector vec(kLongs, kLongs + kLen); + CheckLongConversion(env, kLongs, kLen, ToJavaLongArray(env, vec)); +} + +TEST(JniArray, JavaIntArrayToIntVector) { + const int kInts[] = {0, 1, -1}; + const size_t kLen = arraysize(kInts); + + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef jints(env, env->NewIntArray(kLen)); + ASSERT_TRUE(jints.obj()); + + for (size_t i = 0; i < kLen; ++i) { + jint j = static_cast(kInts[i]); + env->SetIntArrayRegion(jints.obj(), i, 1, &j); + ASSERT_FALSE(HasException(env)); + } + + std::vector ints; + JavaIntArrayToIntVector(env, jints.obj(), &ints); + + ASSERT_EQ(static_cast(ints.size()), env->GetArrayLength(jints.obj())); + + jint value; + for (size_t i = 0; i < kLen; ++i) { + env->GetIntArrayRegion(jints.obj(), i, 1, &value); + ASSERT_EQ(ints[i], value); + } +} + +TEST(JniArray, JavaFloatArrayToFloatVector) { + const float kFloats[] = {0.0, 0.5, -0.5}; + const size_t kLen = arraysize(kFloats); + + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef jfloats(env, env->NewFloatArray(kLen)); + ASSERT_TRUE(jfloats.obj()); + + for (size_t i = 0; i < kLen; ++i) { + jfloat j = static_cast(kFloats[i]); + env->SetFloatArrayRegion(jfloats.obj(), i, 1, &j); + ASSERT_FALSE(HasException(env)); + } + + std::vector floats; + JavaFloatArrayToFloatVector(env, jfloats.obj(), &floats); + + ASSERT_EQ(static_cast(floats.size()), + env->GetArrayLength(jfloats.obj())); + + jfloat value; + for (size_t i = 0; i < kLen; ++i) { + env->GetFloatArrayRegion(jfloats.obj(), i, 1, &value); + ASSERT_EQ(floats[i], value); + } +} + +TEST(JniArray, JavaArrayOfByteArrayToStringVector) { + const int kMaxItems = 50; + JNIEnv* env = AttachCurrentThread(); + + // Create a byte[][] object. + ScopedJavaLocalRef byte_array_clazz(env, env->FindClass("[B")); + ASSERT_TRUE(byte_array_clazz.obj()); + + ScopedJavaLocalRef 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 byte_array = ToJavaByteArray( + env, reinterpret_cast(text), + static_cast(strlen(text))); + ASSERT_TRUE(byte_array.obj()); + + env->SetObjectArrayElement(array.obj(), i, byte_array.obj()); + ASSERT_FALSE(HasException(env)); + } + + // Convert to std::vector, check the content. + std::vector vec; + JavaArrayOfByteArrayToStringVector(env, array.obj(), &vec); + + EXPECT_EQ(static_cast(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/base/android/jni_generator/golden_sample_for_tests_jni.h b/base/android/jni_generator/golden_sample_for_tests_jni.h new file mode 100644 index 0000000000..2c5f6c0025 --- /dev/null +++ b/base/android/jni_generator/golden_sample_for_tests_jni.h @@ -0,0 +1,373 @@ +// Copyright (c) 2012 The Chromium 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 + +#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 kInnerStructAClassPath[] = + "org/chromium/example/jni_generator/SampleForTests$InnerStructA"; +const char kSampleForTestsClassPath[] = + "org/chromium/example/jni_generator/SampleForTests"; +const char kInnerStructBClassPath[] = + "org/chromium/example/jni_generator/SampleForTests$InnerStructB"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +jclass g_InnerStructA_clazz = NULL; +// Leaking this jclass as we cannot use LazyInstance from some threads. +jclass g_SampleForTests_clazz = NULL; +// Leaking this jclass as we cannot use LazyInstance from some threads. +jclass g_InnerStructB_clazz = NULL; +} // namespace + +namespace base { +namespace android { + +static jint Init(JNIEnv* env, jobject obj, + jstring param); + +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); + +// Step 2: method stubs. +static void Destroy(JNIEnv* env, jobject obj, + jint nativeCPPClass) { + DCHECK(nativeCPPClass) << "Destroy"; + CPPClass* native = reinterpret_cast(nativeCPPClass); + return native->Destroy(env, obj); +} + +static jint Method(JNIEnv* env, jobject obj, + jint nativeCPPClass) { + DCHECK(nativeCPPClass) << "Method"; + CPPClass* native = reinterpret_cast(nativeCPPClass); + return native->Method(env, obj); +} + +static jdouble MethodOtherP0(JNIEnv* env, jobject obj, + jint nativePtr) { + DCHECK(nativePtr) << "MethodOtherP0"; + CPPClass::InnerClass* native = + reinterpret_cast(nativePtr); + return native->MethodOtherP0(env, obj); +} + +static void AddStructB(JNIEnv* env, jobject obj, + jint nativeCPPClass, + jobject b) { + DCHECK(nativeCPPClass) << "AddStructB"; + CPPClass* native = reinterpret_cast(nativeCPPClass); + return native->AddStructB(env, obj, b); +} + +static void IterateAndDoSomethingWithStructB(JNIEnv* env, jobject obj, + jint nativeCPPClass) { + DCHECK(nativeCPPClass) << "IterateAndDoSomethingWithStructB"; + CPPClass* native = reinterpret_cast(nativeCPPClass); + return native->IterateAndDoSomethingWithStructB(env, obj); +} + +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_InnerStructA_create = 0; +static ScopedJavaLocalRef Java_InnerStructA_create(JNIEnv* env, jlong + l, + jint i, + jstring s) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_InnerStructA_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, g_InnerStructA_clazz, + "create", + +"(" +"J" +"I" +"Ljava/lang/String;" +")" +"Lorg/chromium/example/jni_generator/SampleForTests$InnerStructA;", + &g_InnerStructA_create); + + jobject ret = + env->CallStaticObjectMethod(g_InnerStructA_clazz, + method_id, l, i, s); + base::android::CheckException(env); + return ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_SampleForTests_addStructA = 0; +static void Java_SampleForTests_addStructA(JNIEnv* env, jobject obj, jobject a) + { + /* Must call RegisterNativesImpl() */ + DCHECK(g_SampleForTests_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_SampleForTests_clazz, + "addStructA", + +"(" +"Lorg/chromium/example/jni_generator/SampleForTests$InnerStructA;" +")" +"V", + &g_SampleForTests_addStructA); + + env->CallVoidMethod(obj, + method_id, a); + base::android::CheckException(env); + +} + +static base::subtle::AtomicWord g_SampleForTests_iterateAndDoSomething = 0; +static void Java_SampleForTests_iterateAndDoSomething(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, + "iterateAndDoSomething", + +"(" +")" +"V", + &g_SampleForTests_iterateAndDoSomething); + + env->CallVoidMethod(obj, + method_id); + base::android::CheckException(env); + +} + +static base::subtle::AtomicWord g_InnerStructB_getKey = 0; +static jlong Java_InnerStructB_getKey(JNIEnv* env, jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_InnerStructB_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_InnerStructB_clazz, + "getKey", + +"(" +")" +"J", + &g_InnerStructB_getKey); + + jlong ret = + env->CallLongMethod(obj, + method_id); + base::android::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_InnerStructB_getValue = 0; +static ScopedJavaLocalRef Java_InnerStructB_getValue(JNIEnv* env, + jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_InnerStructB_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_InnerStructB_clazz, + "getValue", + +"(" +")" +"Ljava/lang/String;", + &g_InnerStructB_getValue); + + jstring ret = + static_cast(env->CallObjectMethod(obj, + method_id)); + base::android::CheckException(env); + return ScopedJavaLocalRef(env, ret); +} + +// Step 3: RegisterNatives. + +static bool RegisterNativesImpl(JNIEnv* env) { + + g_InnerStructA_clazz = reinterpret_cast(env->NewGlobalRef( + base::android::GetClass(env, kInnerStructAClassPath).obj())); + g_SampleForTests_clazz = reinterpret_cast(env->NewGlobalRef( + base::android::GetClass(env, kSampleForTestsClassPath).obj())); + g_InnerStructB_clazz = reinterpret_cast(env->NewGlobalRef( + base::android::GetClass(env, kInnerStructBClassPath).obj())); + static const JNINativeMethod kMethodsSampleForTests[] = { + { "nativeInit", +"(" +"Ljava/lang/String;" +")" +"I", reinterpret_cast(Init) }, + { "nativeDestroy", +"(" +"I" +")" +"V", reinterpret_cast(Destroy) }, + { "nativeGetDoubleFunction", +"(" +")" +"D", reinterpret_cast(GetDoubleFunction) }, + { "nativeGetFloatFunction", +"(" +")" +"F", reinterpret_cast(GetFloatFunction) }, + { "nativeSetNonPODDatatype", +"(" +"Landroid/graphics/Rect;" +")" +"V", reinterpret_cast(SetNonPODDatatype) }, + { "nativeGetNonPODDatatype", +"(" +")" +"Ljava/lang/Object;", reinterpret_cast(GetNonPODDatatype) }, + { "nativeMethod", +"(" +"I" +")" +"I", reinterpret_cast(Method) }, + { "nativeMethodOtherP0", +"(" +"I" +")" +"D", reinterpret_cast(MethodOtherP0) }, + { "nativeAddStructB", +"(" +"I" +"Lorg/chromium/example/jni_generator/SampleForTests$InnerStructB;" +")" +"V", reinterpret_cast(AddStructB) }, + { "nativeIterateAndDoSomethingWithStructB", +"(" +"I" +")" +"V", reinterpret_cast(IterateAndDoSomethingWithStructB) }, + }; + 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/base/android/jni_generator/java/src/org/chromium/example/jni_generator/SampleForTests.java b/base/android/jni_generator/java/src/org/chromium/example/jni_generator/SampleForTests.java new file mode 100644 index 0000000000..71cfc72d7e --- /dev/null +++ b/base/android/jni_generator/java/src/org/chromium/example/jni_generator/SampleForTests.java @@ -0,0 +1,288 @@ +// Copyright (c) 2012 The Chromium 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; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.chromium.base.AccessedByNative; +import org.chromium.base.CalledByNative; +import org.chromium.base.CalledByNativeUnchecked; +import org.chromium.base.JNINamespace; +import org.chromium.base.NativeClassQualifiedName; + + +// 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. +// +// Binding C/C++ with Java is not trivial, specially when ownership and object lifetime +// semantics needs to be managed across boundaries. +// Following a few guidelines will make the code simpler and less buggy: +// +// - Never write any JNI "by hand". Rely on the bindings generator to have a thin +// layer of type-safety. +// +// - Treat the types from the other side as "opaque" as possible. Do not inspect any +// object directly, but rather, rely on well-defined getters / setters. +// +// - Minimize the surface API between the two sides, and rather than calling multiple +// functions across boundaries, call only one (and then, internally in the other side, +// call as many little functions as required). +// +// - If a Java object "owns" a native object, stash the pointer in a "int mNativeClassName". +// Note that it needs to have a "destruction path", i.e., it must eventually call a method +// to delete the native object (for example, the java object has a "close()" method that +// in turn deletes the native object). Avoid relying on finalizers: those run in a different +// thread and makes the native lifetime management more difficult. +// +// - For native object "owning" java objects: +// - If there's a strong 1:1 to relationship between native and java, the best way is to +// stash the java object into a base::android::ScopedJavaGlobalRef. This will ensure the +// java object can be GC'd once the native object is destroyed but note that this global strong +// ref implies a new GC root, so be sure it will not leak and it must never rely on being +// triggered (transitively) from a java side GC. +// - In all other cases, the native side should keep a JavaObjectWeakGlobalRef, and check whether +// that reference is still valid before de-referencing it. Note that you will need another +// java-side object to be holding a strong reference to this java object while it is in use, to +// avoid unpredictable GC of the object before native side has finished with it. +// +// - The best way to pass "compound" datatypes across in either direction is to create an inner +// class with PODs and a factory function. If possible, make it immutable (i.e., mark all the +// fields as "final"). See examples with "InnerStructB" below. +// +// - It's simpler to create thin wrappers with a well defined JNI interface than to +// expose a lot of internal details. This is specially significant for system classes where it's +// simpler to wrap factory methods and a few getters / setters than expose the entire class. +// +// - Use static factory functions annotated with @CalledByNative rather than calling the +// constructors directly. +// +// - Iterate over containers where they are originally owned, then create inner structs or +// directly call methods on the other side. It's much simpler than trying to amalgamate +// java and stl containers. +// +// 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 mNativeCPPObject; + + // 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. + mNativeCPPObject = 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(mNativeCPPObject); + } + + public void finishExample() { + // We're done, so let's destroy nativePtr object. + nativeDestroy(mNativeCPPObject); + } + + // ----------------------------------------------------------------------------------------------- + // 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__" + // 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(String param); + + // 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 GetNonPODDatatype(JNIEnv* env, jobject obj); + // The jobject parameter refers back to this java side object instance. + // Note that it returns a ScopedJavaLocalRef 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); + + // This "struct" will be created by the native side using |createInnerStructA|, + // and used by the java-side somehow. + // Note that |@CalledByNative| has to contain the inner class name. + static class InnerStructA { + private final long mLong; + private final int mInt; + private final String mString; + + private InnerStructA(long l, int i, String s) { + mLong = l; + mInt = i; + mString = s; + } + + @CalledByNative("InnerStructA") + private static InnerStructA create(long l, int i, String s) { + return new InnerStructA(l, i, s); + } + } + + private List mListInnerStructA = new ArrayList(); + + @CalledByNative + private void addStructA(InnerStructA a) { + // Called by the native side to append another element. + mListInnerStructA.add(a); + } + + @CalledByNative + private void iterateAndDoSomething() { + Iterator it = mListInnerStructA.iterator(); + while (it.hasNext()) { + InnerStructA element = it.next(); + // Now, do something with element. + } + // Done, clear the list. + mListInnerStructA.clear(); + } + + // This "struct" will be created by the java side passed to native, which + // will use its getters. + // Note that |@CalledByNative| has to contain the inner class name. + static class InnerStructB { + private final long mKey; + private final String mValue; + + private InnerStructB(long k, String v) { + mKey = k; + mValue = v; + } + + @CalledByNative("InnerStructB") + private long getKey() { + return mKey; + } + + @CalledByNative("InnerStructB") + private String getValue() { + return mValue; + } + } + + List mListInnerStructB = new ArrayList(); + + void iterateAndDoSomethingWithMap() { + Iterator it = mListInnerStructB.iterator(); + while (it.hasNext()) { + InnerStructB element = it.next(); + // Now, do something with element. + nativeAddStructB(mNativeCPPObject, element); + } + nativeIterateAndDoSomethingWithStructB(mNativeCPPObject); + } + + native void nativeAddStructB(int nativeCPPClass, InnerStructB b); + native void nativeIterateAndDoSomethingWithStructB(int nativeCPPClass); +} diff --git a/base/android/jni_generator/jni_generator.gyp b/base/android/jni_generator/jni_generator.gyp new file mode 100644 index 0000000000..4bad0dc483 --- /dev/null +++ b/base/android/jni_generator/jni_generator.gyp @@ -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. + +{ + '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', + 'java/src/org/chromium/example/jni_generator/SampleForTests.java', + 'golden_sample_for_tests_jni.h', + ], + 'outputs': [ + '', + ], + 'action': [ + 'python', 'jni_generator_tests.py', + ], + }, + ], + }, + { + 'target_name': 'jni_sample_header', + 'type': 'none', + 'sources': [ + 'java/src/org/chromium/example/jni_generator/SampleForTests.java', + ], + 'variables': { + 'jni_gen_package': 'example', + }, + 'includes': [ '../../../build/jni_generator.gypi' ], + }, + { + 'target_name': 'jni_sample_java', + 'type': 'none', + 'variables': { + 'java_in_dir': '../../../base/android/jni_generator/java', + }, + 'dependencies': [ + '<(DEPTH)/base/base.gyp:base_java', + ], + 'includes': [ '../../../build/java.gypi' ], + }, + { + 'target_name': 'jni_generator_tests', + 'type': 'executable', + 'dependencies': [ + '../../base.gyp:test_support_base', + 'jni_generator_py_tests', + 'jni_sample_header', + 'jni_sample_java', + ], + 'include_dirs': [ + '<(SHARED_INTERMEDIATE_DIR)/example', + ], + 'sources': [ + 'sample_for_tests.cc', + ], + }, + ], +} diff --git a/base/android/jni_generator/jni_generator.py b/base/android/jni_generator/jni_generator.py new file mode 100755 index 0000000000..de865d527b --- /dev/null +++ b/base/android/jni_generator/jni_generator.py @@ -0,0 +1,1065 @@ +#!/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 errno +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', + 'char': 'jchar', + 'short': 'jshort', + '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 = [] + _remappings = [] + + @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\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\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', + 'char': 'C', + 'short': 'S', + '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', + ] + prefix = '' + # Array? + while 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 prefix + 'L' + JniParams.RemapClassName(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 + JniParams.RemapClassName(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 + JniParams.RemapClassName(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 + JniParams.RemapClassName(qualified_name) + + '$' + inner + ';') + + # Type not found, falling back to same package as this class. + return (prefix + 'L' + + JniParams.RemapClassName(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 + + @staticmethod + def RemapClassName(class_name): + """Remaps class names using the jarjar mapping table.""" + for old, new in JniParams._remappings: + if old in class_name: + return class_name.replace(old, new, 1) + return class_name + + @staticmethod + def SetJarJarMappings(mappings): + """Parse jarjar mappings from a string.""" + JniParams._remappings = [] + for line in mappings.splitlines(): + keyword, src, dest = line.split() + if keyword != 'rule': + continue + assert src.endswith('.**') + src = src[:-2].replace('.', '/') + dest = dest.replace('.', '/') + if dest.endswith('@0'): + JniParams._remappings.append((src, dest[:-2] + src)) + else: + assert dest.endswith('@1') + JniParams._remappings.append((src, dest[:-2])) + + +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.*?)\"\))?\s*' + '(@NativeCall(\(\"(?P.*?)\"\)))?\s*' + '(?P\w+\s\w+|\w+|\s+)\s*?native ' + '(?P\S*?) ' + '(?P\w+?)\((?P.*?)\);') + 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_type'), + name=match.group('name').replace('native', ''), + params=JniParams.Parse(match.group('params'))) + natives += [native] + return natives + + +def GetStaticCastForReturnType(return_type): + type_map = { 'String' : 'jstring', + 'java/lang/String' : 'jstring', + 'boolean[]': 'jbooleanArray', + 'byte[]': 'jbyteArray', + 'char[]': 'jcharArray', + 'short[]': 'jshortArray', + 'int[]': 'jintArray', + 'long[]': 'jlongArray', + 'double[]': 'jdoubleArray' } + ret = type_map.get(return_type, None) + if ret: + return ret + if 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)*?)(?:\("(?P.*)"\))?' + '\s+(?P[\w ]*?)' + '\s*(?P\S+?)' + '\s+(?P\w+)' + '\s*\((?P[^\)]*)\)') + + +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|interface) (?P.*?)( |{)', + 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.*?)(?P\S+?) (?P\w+?)' + '\((?P.*?)\)') + 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.*?)\)') + 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 + +#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(${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': JniParams.RemapClassName(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(env->NewGlobalRef( + base::android::GetClass(env, k${JAVA_CLASS}ClassPath).obj()));""") + 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 = '' + 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)) + try: + os.makedirs(out_dir) + except OSError as e: + if e.errno != errno.EEXIST: + raise + 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, skip_if_same): + 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))) + if skip_if_same and os.path.exists(output_file): + with file(output_file, 'r') as f: + existing_content = f.read() + if existing_content == content: + return + 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') + option_parser.add_option('--optimize_generation', type="int", + default=0, help='Whether we should optimize JNI ' + 'generation by not regenerating files if they have ' + 'not changed.') + option_parser.add_option('--jarjar', + help='Path to optional jarjar rules file.') + 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' + if options.jarjar: + with open(options.jarjar) as f: + JniParams.SetJarJarMappings(f.read()) + GenerateJNIHeader(input_file, output_file, options.namespace, + options.optimize_generation) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/base/android/jni_generator/jni_generator_tests.py b/base/android/jni_generator/jni_generator_tests.py new file mode 100755 index 0000000000..f008f394ce --- /dev/null +++ b/base/android/jni_generator/jni_generator_tests.py @@ -0,0 +1,2084 @@ +#!/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, JniParams, 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 + +#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(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(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(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(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(nativeDataFetcherImplAndroid); + return native->GotOrientation(env, obj, alpha, beta, gamma); +} + +// Step 3: RegisterNatives. + +static bool RegisterNativesImpl(JNIEnv* env) { + + g_TestJni_clazz = reinterpret_cast(env->NewGlobalRef( + base::android::GetClass(env, kTestJniClassPath).obj())); + static const JNINativeMethod kMethodsTestJni[] = { + { "nativeInit", +"(" +")" +"I", reinterpret_cast(Init) }, + { "nativeDestroy", +"(" +"I" +")" +"V", reinterpret_cast(Destroy) }, + { "nativeAddBookmark", +"(" +"I" +"Ljava/lang/String;" +"Ljava/lang/String;" +"Z" +"J" +")" +"J", reinterpret_cast(AddBookmark) }, + { "nativeGetDomainAndRegistry", +"(" +"Ljava/lang/String;" +")" +"Ljava/lang/String;", reinterpret_cast(GetDomainAndRegistry) }, + { "nativeCreateHistoricalTabFromState", +"(" +"[B" +"I" +")" +"V", reinterpret_cast(CreateHistoricalTabFromState) }, + { "nativeGetStateAsByteArray", +"(" +"Landroid/view/View;" +")" +"[B", reinterpret_cast(GetStateAsByteArray) }, + { "nativeGetAutofillProfileGUIDs", +"(" +")" +"[Ljava/lang/String;", reinterpret_cast(GetAutofillProfileGUIDs) }, + { "nativeSetRecognitionResults", +"(" +"I" +"[Ljava/lang/String;" +")" +"V", reinterpret_cast(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(AddBookmarkFromAPI) }, + { "nativeFindAll", +"(" +"Ljava/lang/String;" +")" +"I", reinterpret_cast(FindAll) }, + { "nativeGetInnerClass", +"(" +")" +"Lorg/chromium/example/jni_generator/SampleForTests$OnFrameAvailableListener;", + reinterpret_cast(GetInnerClass) }, + { "nativeQueryBitmap", +"(" +"I" +"[Ljava/lang/String;" +"Ljava/lang/String;" +"[Ljava/lang/String;" +"Ljava/lang/String;" +")" +"Landroid/graphics/Bitmap;", reinterpret_cast(QueryBitmap) }, + { "nativeGotOrientation", +"(" +"I" +"D" +"D" +"D" +")" +"V", reinterpret_cast(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 + +#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(env->NewGlobalRef( + base::android::GetClass(env, kTestJniClassPath).obj())); + static const JNINativeMethod kMethodsMyInnerClass[] = { + { "nativeInit", +"(" +")" +"I", reinterpret_cast(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 + +#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(env->NewGlobalRef( + base::android::GetClass(env, kTestJniClassPath).obj())); + static const JNINativeMethod kMethodsMyOtherInnerClass[] = { + { "nativeInit", +"(" +")" +"I", reinterpret_cast(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(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 + +#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(env->NewGlobalRef( + base::android::GetClass(env, kTestJniClassPath).obj())); + static const JNINativeMethod kMethodsMyOtherInnerClass[] = { + { "nativeInit", +"(" +")" +"I", reinterpret_cast(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(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; + import java.util.List; + + 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); + + @CalledByNative + public byte[] returnByteArray(); + + @CalledByNative + public boolean[] returnBooleanArray(); + + @CalledByNative + public char[] returnCharArray(); + + @CalledByNative + public short[] returnShortArray(); + + @CalledByNative + public int[] returnIntArray(); + + @CalledByNative + public long[] returnLongArray(); + + @CalledByNative + public double[] returnDoubleArray(); + + @CalledByNative + public Object[] returnObjectArray(); + + @CalledByNative + public byte[][] returnArrayOfByteArray(); + + @CalledByNative + public Bitmap.CompressFormat getCompressFormat(); + + @CalledByNative + public List getCompressFormatList(); + """ + 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, + ), + CalledByNative( + return_type='byte[]', + system_class=False, + static=False, + name='returnByteArray', + method_id_var_name='returnByteArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='boolean[]', + system_class=False, + static=False, + name='returnBooleanArray', + method_id_var_name='returnBooleanArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='char[]', + system_class=False, + static=False, + name='returnCharArray', + method_id_var_name='returnCharArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='short[]', + system_class=False, + static=False, + name='returnShortArray', + method_id_var_name='returnShortArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='int[]', + system_class=False, + static=False, + name='returnIntArray', + method_id_var_name='returnIntArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='long[]', + system_class=False, + static=False, + name='returnLongArray', + method_id_var_name='returnLongArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='double[]', + system_class=False, + static=False, + name='returnDoubleArray', + method_id_var_name='returnDoubleArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='Object[]', + system_class=False, + static=False, + name='returnObjectArray', + method_id_var_name='returnObjectArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='byte[][]', + system_class=False, + static=False, + name='returnArrayOfByteArray', + method_id_var_name='returnArrayOfByteArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='Bitmap.CompressFormat', + system_class=False, + static=False, + name='getCompressFormat', + method_id_var_name='getCompressFormat', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='List', + system_class=False, + static=False, + name='getCompressFormatList', + method_id_var_name='getCompressFormatList', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + ] + 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 + +#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 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(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_showAutoLoginInfoBar = 0; +static ScopedJavaLocalRef 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(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 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(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); + +} + +static base::subtle::AtomicWord g_TestJni_returnByteArray = 0; +static ScopedJavaLocalRef Java_TestJni_returnByteArray(JNIEnv* env, + jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_TestJni_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_TestJni_clazz, + "returnByteArray", + +"(" +")" +"[B", + &g_TestJni_returnByteArray); + + jbyteArray ret = + static_cast(env->CallObjectMethod(obj, + method_id)); + base::android::CheckException(env); + return ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_returnBooleanArray = 0; +static ScopedJavaLocalRef Java_TestJni_returnBooleanArray(JNIEnv* + env, jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_TestJni_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_TestJni_clazz, + "returnBooleanArray", + +"(" +")" +"[Z", + &g_TestJni_returnBooleanArray); + + jbooleanArray ret = + static_cast(env->CallObjectMethod(obj, + method_id)); + base::android::CheckException(env); + return ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_returnCharArray = 0; +static ScopedJavaLocalRef Java_TestJni_returnCharArray(JNIEnv* env, + jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_TestJni_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_TestJni_clazz, + "returnCharArray", + +"(" +")" +"[C", + &g_TestJni_returnCharArray); + + jcharArray ret = + static_cast(env->CallObjectMethod(obj, + method_id)); + base::android::CheckException(env); + return ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_returnShortArray = 0; +static ScopedJavaLocalRef Java_TestJni_returnShortArray(JNIEnv* + env, jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_TestJni_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_TestJni_clazz, + "returnShortArray", + +"(" +")" +"[S", + &g_TestJni_returnShortArray); + + jshortArray ret = + static_cast(env->CallObjectMethod(obj, + method_id)); + base::android::CheckException(env); + return ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_returnIntArray = 0; +static ScopedJavaLocalRef Java_TestJni_returnIntArray(JNIEnv* env, + jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_TestJni_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_TestJni_clazz, + "returnIntArray", + +"(" +")" +"[I", + &g_TestJni_returnIntArray); + + jintArray ret = + static_cast(env->CallObjectMethod(obj, + method_id)); + base::android::CheckException(env); + return ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_returnLongArray = 0; +static ScopedJavaLocalRef Java_TestJni_returnLongArray(JNIEnv* env, + jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_TestJni_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_TestJni_clazz, + "returnLongArray", + +"(" +")" +"[J", + &g_TestJni_returnLongArray); + + jlongArray ret = + static_cast(env->CallObjectMethod(obj, + method_id)); + base::android::CheckException(env); + return ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_returnDoubleArray = 0; +static ScopedJavaLocalRef Java_TestJni_returnDoubleArray(JNIEnv* + env, jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_TestJni_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_TestJni_clazz, + "returnDoubleArray", + +"(" +")" +"[D", + &g_TestJni_returnDoubleArray); + + jdoubleArray ret = + static_cast(env->CallObjectMethod(obj, + method_id)); + base::android::CheckException(env); + return ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_returnObjectArray = 0; +static ScopedJavaLocalRef Java_TestJni_returnObjectArray(JNIEnv* + env, jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_TestJni_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_TestJni_clazz, + "returnObjectArray", + +"(" +")" +"[Ljava/lang/Object;", + &g_TestJni_returnObjectArray); + + jobjectArray ret = + static_cast(env->CallObjectMethod(obj, + method_id)); + base::android::CheckException(env); + return ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_returnArrayOfByteArray = 0; +static ScopedJavaLocalRef + Java_TestJni_returnArrayOfByteArray(JNIEnv* env, jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_TestJni_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_TestJni_clazz, + "returnArrayOfByteArray", + +"(" +")" +"[[B", + &g_TestJni_returnArrayOfByteArray); + + jobjectArray ret = + static_cast(env->CallObjectMethod(obj, + method_id)); + base::android::CheckException(env); + return ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_getCompressFormat = 0; +static ScopedJavaLocalRef Java_TestJni_getCompressFormat(JNIEnv* env, + jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_TestJni_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_TestJni_clazz, + "getCompressFormat", + +"(" +")" +"Landroid/graphics/Bitmap$CompressFormat;", + &g_TestJni_getCompressFormat); + + jobject ret = + env->CallObjectMethod(obj, + method_id); + base::android::CheckException(env); + return ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_getCompressFormatList = 0; +static ScopedJavaLocalRef Java_TestJni_getCompressFormatList(JNIEnv* + env, jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_TestJni_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_TestJni_clazz, + "getCompressFormatList", + +"(" +")" +"Ljava/util/List;", + &g_TestJni_getCompressFormatList); + + jobject ret = + env->CallObjectMethod(obj, + method_id); + base::android::CheckException(env); + return ScopedJavaLocalRef(env, ret); +} + +// Step 3: RegisterNatives. + +static bool RegisterNativesImpl(JNIEnv* env) { + + g_TestJni_clazz = reinterpret_cast(env->NewGlobalRef( + base::android::GetClass(env, kTestJniClassPath).obj())); + g_InfoBar_clazz = reinterpret_cast(env->NewGlobalRef( + base::android::GetClass(env, kInfoBarClassPath).obj())); + 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 + +#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 Java_InputStream_Constructor(JNIEnv* env) + __attribute__ ((unused)); +static ScopedJavaLocalRef 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, + "", + +"(" +")" +"V", + &g_InputStream_Constructor); + + jobject ret = + env->NewObject(g_InputStream_clazz, + method_id); + base::android::CheckException(env); + return ScopedJavaLocalRef(env, ret); +} + +// Step 3: RegisterNatives. + +static bool RegisterNativesImpl(JNIEnv* env) { + + g_InputStream_clazz = reinterpret_cast(env->NewGlobalRef( + base::android::GetClass(env, kInputStreamClassPath).obj())); + 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, + 'java/src/org/chromium/example/jni_generator/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 testJarJarRemapping(self): + test_data = """ + package org.chromium.example.jni_generator; + + import org.chromium.example2.Test; + + class Example { + private static native void nativeTest(Test t); + } + """ + jni_generator.JniParams.SetJarJarMappings( + """rule org.chromium.example.** com.test.@1 + rule org.chromium.example2.** org.test2.@0""") + jni_from_java = jni_generator.JNIFromJavaSource( + test_data, 'org/chromium/example/jni_generator/Example') + jni_generator.JniParams.SetJarJarMappings('') + 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/example/jni_generator/Example + +#ifndef org_chromium_example_jni_generator_Example_JNI +#define org_chromium_example_jni_generator_Example_JNI + +#include + +#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 kExampleClassPath[] = "com/test/jni_generator/Example"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +jclass g_Example_clazz = NULL; +} // namespace + +static void Test(JNIEnv* env, jclass clazz, + jobject t); + +// Step 2: method stubs. + +// Step 3: RegisterNatives. + +static bool RegisterNativesImpl(JNIEnv* env) { + + g_Example_clazz = reinterpret_cast(env->NewGlobalRef( + base::android::GetClass(env, kExampleClassPath).obj())); + static const JNINativeMethod kMethodsExample[] = { + { "nativeTest", +"(" +"Lorg/test2/org/chromium/example2/Test;" +")" +"V", reinterpret_cast(Test) }, + }; + const int kMethodsExampleSize = arraysize(kMethodsExample); + + if (env->RegisterNatives(g_Example_clazz, + kMethodsExample, + kMethodsExampleSize) < 0) { + LOG(ERROR) << "RegisterNatives failed in " << __FILE__; + return false; + } + + return true; +} + +#endif // org_chromium_example_jni_generator_Example_JNI +""" + self.assertTextEquals(golden_content, jni_from_java.GetContent()) + + 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.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') + + def testJniParamsJavaToJni(self): + self.assertTextEquals('I', JniParams.JavaToJni('int')) + self.assertTextEquals('[B', JniParams.JavaToJni('byte[]')) + self.assertTextEquals( + '[Ljava/nio/ByteBuffer;', JniParams.JavaToJni('java/nio/ByteBuffer[]')) + + +if __name__ == '__main__': + unittest.main() diff --git a/base/android/jni_generator/sample_for_tests.cc b/base/android/jni_generator/sample_for_tests.cc new file mode 100644 index 0000000000..30b9940635 --- /dev/null +++ b/base/android/jni_generator/sample_for_tests.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/android/jni_generator/sample_for_tests.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/android/scoped_java_ref.h" +#include "jni/SampleForTests_jni.h" + + +using base::android::AttachCurrentThread; +using base::android::ScopedJavaLocalRef; + +namespace base { +namespace android { + +jdouble CPPClass::InnerClass::MethodOtherP0(JNIEnv* env, jobject obj) { + return 0.0; +} + +CPPClass::CPPClass() { +} + +CPPClass::~CPPClass() { +} + +void CPPClass::Destroy(JNIEnv* env, jobject obj) { + delete this; +} + +jint CPPClass::Method(JNIEnv* env, jobject obj) { + return 0; +} + +void CPPClass::AddStructB(JNIEnv* env, jobject obj, jobject structb) { + long key = Java_InnerStructB_getKey(env, structb); + std::string value = ConvertJavaStringToUTF8( + env, Java_InnerStructB_getValue(env, structb).obj()); + map_[key] = value; +} + +void CPPClass::IterateAndDoSomethingWithStructB(JNIEnv* env, jobject obj) { + // Iterate over the elements and do something with them. + for (std::map::const_iterator it = map_.begin(); + it != map_.end(); ++it) { + long key = it->first; + std::string value = it->second; + } + map_.clear(); +} + +// Static free functions declared and called directly from java. +static jint Init(JNIEnv* env, jobject obj, jstring param) { + 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 + +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); + + for (int i = 0; i < 10; ++i) { + // Creates a "struct" that will then be used by the java side. + ScopedJavaLocalRef struct_a = + base::android::Java_InnerStructA_create( + env, 0, 1, + base::android::ConvertUTF8ToJavaString(env, "test").obj()); + base::android::Java_SampleForTests_addStructA( + env, my_java_object, struct_a.obj()); + } + base::android::Java_SampleForTests_iterateAndDoSomething(env, my_java_object); + return 0; +} diff --git a/base/android/jni_generator/sample_for_tests.h b/base/android/jni_generator/sample_for_tests.h new file mode 100644 index 0000000000..278008b2df --- /dev/null +++ b/base/android/jni_generator/sample_for_tests.h @@ -0,0 +1,46 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include + +#include "base/basictypes.h" + +namespace base { +namespace android { + +// This file is used to: +// - document the best practices and guidelines on JNI usage. +// - ensure sample_for_tests_jni.h compiles and the functions declared in it +// as expected. +// +// All methods are called directly from Java. See more documentation in +// SampleForTests.java. +class CPPClass { + public: + CPPClass(); + ~CPPClass(); + + class InnerClass { + public: + jdouble MethodOtherP0(JNIEnv* env, jobject obj); + }; + + void Destroy(JNIEnv* env, jobject obj); + + jint Method(JNIEnv* env, jobject obj); + + void AddStructB(JNIEnv* env, jobject obj, jobject structb); + + void IterateAndDoSomethingWithStructB(JNIEnv* env, jobject obj); + + private: + std::map map_; + + DISALLOW_COPY_AND_ASSIGN(CPPClass); +}; + +} // namespace android +} // namespace base diff --git a/base/android/jni_helper.cc b/base/android/jni_helper.cc new file mode 100644 index 0000000000..fcb610e188 --- /dev/null +++ b/base/android/jni_helper.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_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) + : obj_(NULL) { + 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 + JavaObjectWeakGlobalRef::get(JNIEnv* env) const { + return GetRealObject(env, obj_); +} + +base::android::ScopedJavaLocalRef 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(env, real); +} + +void JavaObjectWeakGlobalRef::Assign(const JavaObjectWeakGlobalRef& other) { + if (&other == this) + return; + + JNIEnv* env = AttachCurrentThread(); + if (obj_) + env->DeleteWeakGlobalRef(obj_); + + obj_ = other.obj_ ? env->NewWeakGlobalRef(other.obj_) : NULL; +} diff --git a/base/android/jni_helper.h b/base/android/jni_helper.h new file mode 100644 index 0000000000..895bf95a9d --- /dev/null +++ b/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 + +#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 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 GetRealObject( + JNIEnv* env, jweak obj); + +#endif // BASE_ANDROID_JNI_HELPER_H_ diff --git a/base/android/jni_registrar.cc b/base/android/jni_registrar.cc new file mode 100644 index 0000000000..0f32089d18 --- /dev/null +++ b/base/android/jni_registrar.cc @@ -0,0 +1,30 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/android/jni_registrar.h" + +#include "base/debug/trace_event.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) { + TRACE_EVENT0("startup", "base_android::RegisterNativeMethods") + const RegistrationMethod* end = method + count; + while (method != end) { + if (!method->func(env)) { + DLOG(ERROR) << method->name << " failed registration!"; + return false; + } + method++; + } + return true; +} + +} // namespace android +} // namespace base diff --git a/base/android/jni_registrar.h b/base/android/jni_registrar.h new file mode 100644 index 0000000000..849d07f939 --- /dev/null +++ b/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 +#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/base/android/jni_string.cc b/base/android/jni_string.cc new file mode 100644 index 0000000000..d25fed822b --- /dev/null +++ b/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/strings/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& str) { + return ConvertJavaStringToUTF8(AttachCurrentThread(), str.obj()); +} + +ScopedJavaLocalRef 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(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& str) { + return ConvertJavaStringToUTF16(AttachCurrentThread(), str.obj()); +} + +ScopedJavaLocalRef ConvertUTF16ToJavaString( + JNIEnv* env, + const base::StringPiece16& str) { + return ScopedJavaLocalRef(env, + ConvertUTF16ToJavaStringImpl(env, str)); +} + +} // namespace android +} // namespace base diff --git a/base/android/jni_string.h b/base/android/jni_string.h new file mode 100644 index 0000000000..89af5b0a89 --- /dev/null +++ b/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 +#include + +#include "base/android/scoped_java_ref.h" +#include "base/base_export.h" +#include "base/strings/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& str); + +// Convert a std string to Java string. +BASE_EXPORT ScopedJavaLocalRef 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& str); + +// Convert a string16 to a Java string. +BASE_EXPORT ScopedJavaLocalRef ConvertUTF16ToJavaString( + JNIEnv* env, + const base::StringPiece16& str); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_JNI_STRING_H_ diff --git a/base/android/jni_string_unittest.cc b/base/android/jni_string_unittest.cc new file mode 100644 index 0000000000..abd0683170 --- /dev/null +++ b/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/strings/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/base/android/memory_pressure_listener_android.cc b/base/android/memory_pressure_listener_android.cc new file mode 100644 index 0000000000..80c07bcf9c --- /dev/null +++ b/base/android/memory_pressure_listener_android.cc @@ -0,0 +1,31 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/android/memory_pressure_listener_android.h" + +#include "base/memory/memory_pressure_listener.h" +#include "jni/MemoryPressureListener_jni.h" + +// Defined and called by JNI. +static void OnMemoryPressure( + JNIEnv* env, jclass clazz, jint memory_pressure_level) { + base::MemoryPressureListener::NotifyMemoryPressure( + static_cast( + memory_pressure_level)); +} + +namespace base { +namespace android { + +bool MemoryPressureListenerAndroid::Register(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +void MemoryPressureListenerAndroid::RegisterSystemCallback(JNIEnv* env) { + Java_MemoryPressureListener_registerSystemCallback( + env, GetApplicationContext()); +} + +} // namespace android +} // namespace base diff --git a/base/android/memory_pressure_listener_android.h b/base/android/memory_pressure_listener_android.h new file mode 100644 index 0000000000..eed8dbb575 --- /dev/null +++ b/base/android/memory_pressure_listener_android.h @@ -0,0 +1,30 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_ANDROID_MEMORY_PRESSURE_LISTENER_ANDROID_H_ +#define BASE_ANDROID_MEMORY_PRESSURE_LISTENER_ANDROID_H_ + +#include "base/android/jni_android.h" + +namespace base { +namespace android { + +// Implements the C++ counter part of MemoryPressureListener.java +class BASE_EXPORT MemoryPressureListenerAndroid { + public: + static bool Register(JNIEnv* env); + + static void RegisterSystemCallback(JNIEnv* env); + + // Called by JNI. + static void OnMemoryPressure(int memory_pressure_type); + + private: + DISALLOW_COPY_AND_ASSIGN(MemoryPressureListenerAndroid); +}; + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_MEMORY_PRESSURE_LISTENER_ANDROID_H_ diff --git a/base/android/path_service_android.cc b/base/android/path_service_android.cc new file mode 100644 index 0000000000..18ca70c8ab --- /dev/null +++ b/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/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/files/file_path.h" +#include "base/path_service.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/base/android/path_service_android.h b/base/android/path_service_android.h new file mode 100644 index 0000000000..26040f97be --- /dev/null +++ b/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 + +namespace base { +namespace android { + +bool RegisterPathService(JNIEnv* env); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_PATH_SERVICE_ANDROID_H_ diff --git a/base/android/path_utils.cc b/base/android/path_utils.cc new file mode 100644 index 0000000000..5737227f38 --- /dev/null +++ b/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/files/file_path.h" + +#include "jni/PathUtils_jni.h" + +namespace base { +namespace android { + +bool GetDataDirectory(FilePath* result) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef path = + Java_PathUtils_getDataDirectory(env, GetApplicationContext()); + FilePath data_path(ConvertJavaStringToUTF8(path)); + *result = data_path; + return true; +} + +bool GetCacheDirectory(FilePath* result) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef path = + Java_PathUtils_getCacheDirectory(env, GetApplicationContext()); + FilePath cache_path(ConvertJavaStringToUTF8(path)); + *result = cache_path; + return true; +} + +bool GetDownloadsDirectory(FilePath* result) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef path = + Java_PathUtils_getDownloadsDirectory(env, GetApplicationContext()); + FilePath downloads_path(ConvertJavaStringToUTF8(path)); + *result = downloads_path; + return true; +} + +bool GetNativeLibraryDirectory(FilePath* result) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef path = + Java_PathUtils_getNativeLibraryDirectory(env, GetApplicationContext()); + FilePath library_path(ConvertJavaStringToUTF8(path)); + *result = library_path; + return true; +} + +bool GetExternalStorageDirectory(FilePath* result) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef 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/base/android/path_utils.h b/base/android/path_utils.h new file mode 100644 index 0000000000..60f8a79b1e --- /dev/null +++ b/base/android/path_utils.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_ANDROID_PATH_UTILS_H_ +#define BASE_ANDROID_PATH_UTILS_H_ + +#include + +#include "base/base_export.h" + +namespace base { + +class FilePath; + +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'. +BASE_EXPORT 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/base/android/path_utils_unittest.cc b/base/android/path_utils_unittest.cc new file mode 100644 index 0000000000..c4c12fea13 --- /dev/null +++ b/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_util.h" +#include "base/files/file_path.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(base::PathExists(path.Append(("libbase_unittests.so")))); +} + +} // namespace android +} // namespace base diff --git a/base/android/scoped_java_ref.cc b/base/android/scoped_java_ref.cc new file mode 100644 index 0000000000..21b466e958 --- /dev/null +++ b/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::JavaRef() : obj_(NULL) {} + +JavaRef::JavaRef(JNIEnv* env, jobject obj) : obj_(obj) { + if (obj) { + DCHECK(env && env->GetObjectRefType(obj) == JNILocalRefType); + } +} + +JavaRef::~JavaRef() { +} + +JNIEnv* JavaRef::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::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::ResetLocalRef(JNIEnv* env) { + if (obj_) { + DCHECK_EQ(env, AttachCurrentThread()); // Is |env| on correct thread. + env->DeleteLocalRef(obj_); + obj_ = NULL; + } +} + +void JavaRef::ResetGlobalRef() { + if (obj_) { + AttachCurrentThread()->DeleteGlobalRef(obj_); + obj_ = NULL; + } +} + +jobject JavaRef::ReleaseInternal() { + jobject obj = obj_; + obj_ = NULL; + return obj; +} + +} // namespace android +} // namespace base diff --git a/base/android/scoped_java_ref.h b/base/android/scoped_java_ref.h new file mode 100644 index 0000000000..a5d71e2d23 --- /dev/null +++ b/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 +#include + +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace base { +namespace android { + +// Forward declare the generic java reference template class. +template 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 into a function taking const JavaRef& +template<> +class BASE_EXPORT JavaRef { + 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 +class JavaRef : public JavaRef { + public: + T obj() const { return static_cast(JavaRef::obj()); } + + protected: + JavaRef() {} + ~JavaRef() {} + + JavaRef(JNIEnv* env, T obj) : JavaRef(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 +class ScopedJavaLocalRef : public JavaRef { + 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& other) + : env_(other.env_) { + this->SetNewLocalRef(env_, other.obj()); + } + + template + 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(env, obj), env_(env) {} + + ~ScopedJavaLocalRef() { + this->Reset(); + } + + // Overloaded assignment operator defined for consistency with the implicit + // copy constructor. + void operator=(const ScopedJavaLocalRef& other) { + this->Reset(other); + } + + void Reset() { + this->ResetLocalRef(env_); + } + + template + void Reset(const ScopedJavaLocalRef& 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 + 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 + void Reset(JNIEnv* env, U obj) { + implicit_cast(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(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 +class ScopedJavaGlobalRef : public JavaRef { + public: + ScopedJavaGlobalRef() {} + + explicit ScopedJavaGlobalRef(const ScopedJavaGlobalRef& other) { + this->Reset(other); + } + + template + explicit ScopedJavaGlobalRef(const U& other) { + this->Reset(other); + } + + ~ScopedJavaGlobalRef() { + this->Reset(); + } + + void Reset() { + this->ResetGlobalRef(); + } + + template + void Reset(const U& other) { + this->Reset(NULL, other.obj()); + } + + template + void Reset(JNIEnv* env, U obj) { + implicit_cast(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(this->ReleaseInternal()); + } +}; + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_SCOPED_JAVA_REF_H_ diff --git a/base/android/scoped_java_ref_unittest.cc b/base/android/scoped_java_ref_unittest.cc new file mode 100644 index 0000000000..36f253c4e9 --- /dev/null +++ b/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 str = ConvertUTF8ToJavaString(env, "string"); + ScopedJavaGlobalRef global(str); + { + ScopedJavaGlobalRef global_obj(str); + ScopedJavaLocalRef local_obj(global); + const JavaRef& obj_ref1(str); + const JavaRef& 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& str_ref = str; + EXPECT_EQ("string", ConvertJavaStringToUTF8(str_ref)); + str.Reset(); +} + +TEST_F(ScopedJavaRefTest, RefCounts) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef 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 global_str(str); + ScopedJavaGlobalRef global_obj(global_str); + EXPECT_EQ(1, g_local_refs); + EXPECT_EQ(2, g_global_refs); + + ScopedJavaLocalRef str2(env, str.Release()); + EXPECT_EQ(1, g_local_refs); + { + ScopedJavaLocalRef 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 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/base/android/sys_utils.cc b/base/android/sys_utils.cc new file mode 100644 index 0000000000..c23125390d --- /dev/null +++ b/base/android/sys_utils.cc @@ -0,0 +1,31 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/android/sys_utils.h" + +#include "base/sys_info.h" +#include "jni/SysUtils_jni.h" + +const int64 kLowEndMemoryThreshold = 1024 * 1024 * 512; // 512 mb. + +// Defined and called by JNI +static jboolean IsLowEndDevice(JNIEnv* env, jclass clazz) { + return base::android::SysUtils::IsLowEndDevice(); +} + +namespace base { +namespace android { + +bool SysUtils::Register(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +bool SysUtils::IsLowEndDevice() { + return SysInfo::AmountOfPhysicalMemory() <= kLowEndMemoryThreshold; +} + +SysUtils::SysUtils() { } + +} // namespace android +} // namespace base diff --git a/base/android/sys_utils.h b/base/android/sys_utils.h new file mode 100644 index 0000000000..78122ff572 --- /dev/null +++ b/base/android/sys_utils.h @@ -0,0 +1,26 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_ANDROID_SYS_UTILS_H_ +#define BASE_ANDROID_SYS_UTILS_H_ + +#include "base/android/jni_android.h" + +namespace base { +namespace android { + +class BASE_EXPORT SysUtils { + public: + static bool Register(JNIEnv* env); + + static bool IsLowEndDevice(); + + private: + SysUtils(); +}; + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_SYS_UTILS_H_ diff --git a/base/android/thread_utils.h b/base/android/thread_utils.h new file mode 100644 index 0000000000..cbe65f0813 --- /dev/null +++ b/base/android/thread_utils.h @@ -0,0 +1,16 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_ANDROID_THREAD_UTILS_H_ +#define BASE_ANDROID_THREAD_UTILS_H_ + +#include "base/android/jni_android.h" + +namespace base { + +bool RegisterThreadUtils(JNIEnv* env); + +} // namespace base + +#endif // BASE_ANDROID_THREAD_UTILS_H_ diff --git a/base/at_exit.cc b/base/at_exit.cc new file mode 100644 index 0000000000..0fba355698 --- /dev/null +++ b/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 +#include + +#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/base/at_exit.h b/base/at_exit.h new file mode 100644 index 0000000000..6fe7361a85 --- /dev/null +++ b/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 + +#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 stack_; + AtExitManager* next_manager_; // Stack of managers to allow shadowing. + + DISALLOW_COPY_AND_ASSIGN(AtExitManager); +}; + +#if defined(UNIT_TEST) +class ShadowingAtExitManager : public AtExitManager { + public: + ShadowingAtExitManager() : AtExitManager(true) {} +}; +#endif // defined(UNIT_TEST) + +} // namespace base + +#endif // BASE_AT_EXIT_H_ diff --git a/base/at_exit_unittest.cc b/base/at_exit_unittest.cc new file mode 100644 index 0000000000..cda73403fb --- /dev/null +++ b/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(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/base/atomic_ref_count.h b/base/atomic_ref_count.h new file mode 100644 index 0000000000..5130860ab0 --- /dev/null +++ b/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/base/atomic_sequence_num.h b/base/atomic_sequence_num.h new file mode 100644 index 0000000000..7bf2778991 --- /dev/null +++ b/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( + 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/base/atomicops.h b/base/atomicops.h new file mode 100644 index 0000000000..7f03492e84 --- /dev/null +++ b/base/atomicops.h @@ -0,0 +1,164 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// 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) +// 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(__ILP32__) || 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_NACL) +#include "base/atomicops_internals_gcc.h" +#elif defined(COMPILER_GCC) && defined(ARCH_CPU_ARM_FAMILY) +#include "base/atomicops_internals_arm_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/base/atomicops_internals_arm_gcc.h b/base/atomicops_internals_arm_gcc.h new file mode 100644 index 0000000000..9f4fe2e586 --- /dev/null +++ b/base/atomicops_internals_arm_gcc.h @@ -0,0 +1,285 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file is an internal atomic implementation, use base/atomicops.h instead. +// +// LinuxKernelCmpxchg and Barrier_AtomicIncrement are from Google Gears. + +#ifndef BASE_ATOMICOPS_INTERNALS_ARM_GCC_H_ +#define BASE_ATOMICOPS_INTERNALS_ARM_GCC_H_ + +namespace base { +namespace subtle { + +// Memory barriers on ARM are funky, but the kernel is here to help: +// +// * ARMv5 didn't support SMP, there is no memory barrier instruction at +// all on this architecture, or when targeting its machine code. +// +// * Some ARMv6 CPUs support SMP. A full memory barrier can be produced by +// writing a random value to a very specific coprocessor register. +// +// * On ARMv7, the "dmb" instruction is used to perform a full memory +// barrier (though writing to the co-processor will still work). +// However, on single core devices (e.g. Nexus One, or Nexus S), +// this instruction will take up to 200 ns, which is huge, even though +// it's completely un-needed on these devices. +// +// * There is no easy way to determine at runtime if the device is +// single or multi-core. However, the kernel provides a useful helper +// function at a fixed memory address (0xffff0fa0), which will always +// perform a memory barrier in the most efficient way. I.e. on single +// core devices, this is an empty function that exits immediately. +// On multi-core devices, it implements a full memory barrier. +// +// * This source could be compiled to ARMv5 machine code that runs on a +// multi-core ARMv6 or ARMv7 device. In this case, memory barriers +// are needed for correct execution. Always call the kernel helper, even +// when targeting ARMv5TE. +// + +inline void MemoryBarrier() { + // Note: This is a function call, which is also an implicit compiler + // barrier. + typedef void (*KernelMemoryBarrierFunc)(); + ((KernelMemoryBarrierFunc)0xffff0fa0)(); +} + +// An ARM toolchain would only define one of these depending on which +// variant of the target architecture is being used. This tests against +// any known ARMv6 or ARMv7 variant, where it is possible to directly +// use ldrex/strex instructions to implement fast atomic operations. +#if defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || \ + defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) || \ + defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || \ + defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) || \ + defined(__ARM_ARCH_6KZ__) || defined(__ARM_ARCH_6T2__) + +inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 prev_value; + int reloop; + do { + // The following is equivalent to: + // + // prev_value = LDREX(ptr) + // reloop = 0 + // if (prev_value != old_value) + // reloop = STREX(ptr, new_value) + __asm__ __volatile__(" ldrex %0, [%3]\n" + " mov %1, #0\n" + " cmp %0, %4\n" +#ifdef __thumb2__ + " it eq\n" +#endif + " strexeq %1, %5, [%3]\n" + : "=&r"(prev_value), "=&r"(reloop), "+m"(*ptr) + : "r"(ptr), "r"(old_value), "r"(new_value) + : "cc", "memory"); + } while (reloop != 0); + return prev_value; +} + +inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 result = NoBarrier_CompareAndSwap(ptr, old_value, new_value); + MemoryBarrier(); + return result; +} + +inline Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + MemoryBarrier(); + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + +inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + Atomic32 value; + int reloop; + do { + // Equivalent to: + // + // value = LDREX(ptr) + // value += increment + // reloop = STREX(ptr, value) + // + __asm__ __volatile__(" ldrex %0, [%3]\n" + " add %0, %0, %4\n" + " strex %1, %0, [%3]\n" + : "=&r"(value), "=&r"(reloop), "+m"(*ptr) + : "r"(ptr), "r"(increment) + : "cc", "memory"); + } while (reloop); + return value; +} + +inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + // TODO(digit): Investigate if it's possible to implement this with + // a single MemoryBarrier() operation between the LDREX and STREX. + // See http://crbug.com/246514 + MemoryBarrier(); + Atomic32 result = NoBarrier_AtomicIncrement(ptr, increment); + MemoryBarrier(); + return result; +} + +inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr, + Atomic32 new_value) { + Atomic32 old_value; + int reloop; + do { + // old_value = LDREX(ptr) + // reloop = STREX(ptr, new_value) + __asm__ __volatile__(" ldrex %0, [%3]\n" + " strex %1, %4, [%3]\n" + : "=&r"(old_value), "=&r"(reloop), "+m"(*ptr) + : "r"(ptr), "r"(new_value) + : "cc", "memory"); + } while (reloop != 0); + return old_value; +} + +// This tests against any known ARMv5 variant. +#elif defined(__ARM_ARCH_5__) || defined(__ARM_ARCH_5T__) || \ + defined(__ARM_ARCH_5TE__) || defined(__ARM_ARCH_5TEJ__) + +// The kernel also provides a helper function to perform an atomic +// compare-and-swap operation at the hard-wired address 0xffff0fc0. +// On ARMv5, this is implemented by a special code path that the kernel +// detects and treats specially when thread pre-emption happens. +// On ARMv6 and higher, it uses LDREX/STREX instructions instead. +// +// Note that this always perform a full memory barrier, there is no +// need to add calls MemoryBarrier() before or after it. It also +// returns 0 on success, and 1 on exit. +// +// Available and reliable since Linux 2.6.24. Both Android and ChromeOS +// use newer kernel revisions, so this should not be a concern. +namespace { + +inline int LinuxKernelCmpxchg(Atomic32 old_value, + Atomic32 new_value, + volatile Atomic32* ptr) { + typedef int (*KernelCmpxchgFunc)(Atomic32, Atomic32, volatile Atomic32*); + return ((KernelCmpxchgFunc)0xffff0fc0)(old_value, new_value, ptr); +} + +} // namespace + +inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 prev_value; + for (;;) { + prev_value = *ptr; + if (prev_value != old_value) + return prev_value; + if (!LinuxKernelCmpxchg(old_value, new_value, ptr)) + return old_value; + } +} + +inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr, + Atomic32 new_value) { + Atomic32 old_value; + do { + old_value = *ptr; + } while (LinuxKernelCmpxchg(old_value, new_value, ptr)); + 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 (!LinuxKernelCmpxchg(old_value, new_value, ptr)) { + // 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) { + Atomic32 prev_value; + for (;;) { + prev_value = *ptr; + if (prev_value != old_value) { + // Always ensure acquire semantics. + MemoryBarrier(); + return prev_value; + } + if (!LinuxKernelCmpxchg(old_value, new_value, ptr)) + return old_value; + } +} + +inline Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + // This could be implemented as: + // MemoryBarrier(); + // return NoBarrier_CompareAndSwap(); + // + // But would use 3 barriers per succesful CAS. To save performance, + // use Acquire_CompareAndSwap(). Its implementation guarantees that: + // - A succesful swap uses only 2 barriers (in the kernel helper). + // - An early return due to (prev_value != old_value) performs + // a memory barrier with no store, which is equivalent to the + // generic implementation above. + return Acquire_CompareAndSwap(ptr, old_value, new_value); +} + +#else +# error "Your CPU's ARM architecture is not supported yet" +#endif + +// NOTE: Atomicity of the following load and store operations is only +// guaranteed in case of 32-bit alignement of |ptr| values. + +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; +} + +} // namespace base::subtle +} // namespace base + +#endif // BASE_ATOMICOPS_INTERNALS_ARM_GCC_H_ diff --git a/base/atomicops_internals_atomicword_compat.h b/base/atomicops_internals_atomicword_compat.h new file mode 100644 index 0000000000..e02d11d29b --- /dev/null +++ b/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(ptr), old_value, new_value); +} + +inline AtomicWord NoBarrier_AtomicExchange(volatile AtomicWord* ptr, + AtomicWord new_value) { + return NoBarrier_AtomicExchange( + reinterpret_cast(ptr), new_value); +} + +inline AtomicWord NoBarrier_AtomicIncrement(volatile AtomicWord* ptr, + AtomicWord increment) { + return NoBarrier_AtomicIncrement( + reinterpret_cast(ptr), increment); +} + +inline AtomicWord Barrier_AtomicIncrement(volatile AtomicWord* ptr, + AtomicWord increment) { + return Barrier_AtomicIncrement( + reinterpret_cast(ptr), increment); +} + +inline AtomicWord Acquire_CompareAndSwap(volatile AtomicWord* ptr, + AtomicWord old_value, + AtomicWord new_value) { + return base::subtle::Acquire_CompareAndSwap( + reinterpret_cast(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(ptr), old_value, new_value); +} + +inline void NoBarrier_Store(volatile AtomicWord *ptr, AtomicWord value) { + NoBarrier_Store( + reinterpret_cast(ptr), value); +} + +inline void Acquire_Store(volatile AtomicWord* ptr, AtomicWord value) { + return base::subtle::Acquire_Store( + reinterpret_cast(ptr), value); +} + +inline void Release_Store(volatile AtomicWord* ptr, AtomicWord value) { + return base::subtle::Release_Store( + reinterpret_cast(ptr), value); +} + +inline AtomicWord NoBarrier_Load(volatile const AtomicWord *ptr) { + return NoBarrier_Load( + reinterpret_cast(ptr)); +} + +inline AtomicWord Acquire_Load(volatile const AtomicWord* ptr) { + return base::subtle::Acquire_Load( + reinterpret_cast(ptr)); +} + +inline AtomicWord Release_Load(volatile const AtomicWord* ptr) { + return base::subtle::Release_Load( + reinterpret_cast(ptr)); +} + +} // namespace base::subtle +} // namespace base + +#endif // !defined(ARCH_CPU_64_BITS) + +#endif // BASE_ATOMICOPS_INTERNALS_ATOMICWORD_COMPAT_H_ diff --git a/base/atomicops_internals_gcc.h b/base/atomicops_internals_gcc.h new file mode 100644 index 0000000000..ed1b2d7913 --- /dev/null +++ b/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/base/atomicops_internals_mac.h b/base/atomicops_internals_mac.h new file mode 100644 index 0000000000..658ed54879 --- /dev/null +++ b/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 + +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(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(ptr))); + return old_value; +} + +inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32 *ptr, + Atomic32 increment) { + return OSAtomicAdd32(increment, const_cast(ptr)); +} + +inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32 *ptr, + Atomic32 increment) { + return OSAtomicAdd32Barrier(increment, const_cast(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(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(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(ptr))); + return old_value; +} + +inline Atomic64 NoBarrier_AtomicIncrement(volatile Atomic64 *ptr, + Atomic64 increment) { + return OSAtomicAdd64(increment, reinterpret_cast(ptr)); +} + +inline Atomic64 Barrier_AtomicIncrement(volatile Atomic64 *ptr, + Atomic64 increment) { + return OSAtomicAdd64Barrier(increment, + reinterpret_cast(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(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/base/atomicops_internals_mips_gcc.h b/base/atomicops_internals_mips_gcc.h new file mode 100644 index 0000000000..29947b37af --- /dev/null +++ b/base/atomicops_internals_mips_gcc.h @@ -0,0 +1,154 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// 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_ + +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) { + MemoryBarrier(); + Atomic32 res = NoBarrier_AtomicIncrement(ptr, increment); + MemoryBarrier(); + 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) { + Atomic32 res = NoBarrier_CompareAndSwap(ptr, old_value, new_value); + MemoryBarrier(); + return res; +} + +inline Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + MemoryBarrier(); + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + +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 + +#endif // BASE_ATOMICOPS_INTERNALS_MIPS_GCC_H_ diff --git a/base/atomicops_internals_tsan.h b/base/atomicops_internals_tsan.h new file mode 100644 index 0000000000..44d64009c7 --- /dev/null +++ b/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/base/atomicops_internals_x86_gcc.cc b/base/atomicops_internals_x86_gcc.cc new file mode 100644 index 0000000000..933ca51896 --- /dev/null +++ b/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 + +#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/base/atomicops_internals_x86_gcc.h b/base/atomicops_internals_x86_gcc.h new file mode 100644 index 0000000000..ac02b17f5d --- /dev/null +++ b/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/base/atomicops_internals_x86_msvc.h b/base/atomicops_internals_x86_msvc.h new file mode 100644 index 0000000000..3a2c72ddb4 --- /dev/null +++ b/base/atomicops_internals_x86_msvc.h @@ -0,0 +1,194 @@ +// 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_ + +#include + +#if defined(ARCH_CPU_64_BITS) +// 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 { + +inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + LONG result = InterlockedCompareExchange( + reinterpret_cast(ptr), + static_cast(new_value), + static_cast(old_value)); + return static_cast(result); +} + +inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr, + Atomic32 new_value) { + LONG result = InterlockedExchange( + reinterpret_cast(ptr), + static_cast(new_value)); + return static_cast(result); +} + +inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + return InterlockedExchangeAdd( + reinterpret_cast(ptr), + static_cast(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) + // 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(ptr), + reinterpret_cast(new_value), reinterpret_cast(old_value)); + return reinterpret_cast(result); +} + +inline Atomic64 NoBarrier_AtomicExchange(volatile Atomic64* ptr, + Atomic64 new_value) { + PVOID result = InterlockedExchangePointer( + reinterpret_cast(ptr), + reinterpret_cast(new_value)); + return reinterpret_cast(result); +} + +inline Atomic64 Barrier_AtomicIncrement(volatile Atomic64* ptr, + Atomic64 increment) { + return InterlockedExchangeAdd64( + reinterpret_cast(ptr), + static_cast(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 + +#endif // BASE_ATOMICOPS_INTERNALS_X86_MSVC_H_ diff --git a/base/atomicops_unittest.cc b/base/atomicops_unittest.cc new file mode 100644 index 0000000000..d73a098c48 --- /dev/null +++ b/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 + +#include "base/port.h" +#include "testing/gtest/include/gtest/gtest.h" + +template +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 +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 +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 +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 +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 +static void TestStore() { + const AtomicType kVal1 = TestFillValue(); + const AtomicType kVal2 = static_cast(-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 +static void TestLoad() { + const AtomicType kVal1 = TestFillValue(); + const AtomicType kVal2 = static_cast(-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(); + TestAtomicIncrement(); +} + +TEST(AtomicOpsTest, CompareAndSwap) { + TestCompareAndSwap(); + TestCompareAndSwap(); +} + +TEST(AtomicOpsTest, Exchange) { + TestAtomicExchange(); + TestAtomicExchange(); +} + +TEST(AtomicOpsTest, IncrementBounds) { + TestAtomicIncrementBounds(); + TestAtomicIncrementBounds(); +} + +TEST(AtomicOpsTest, Store) { + TestStore(); + TestStore(); +} + +TEST(AtomicOpsTest, Load) { + TestLoad(); + TestLoad(); +} diff --git a/base/auto_reset.h b/base/auto_reset.h new file mode 100644 index 0000000000..32f138e244 --- /dev/null +++ b/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 +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/base/base.gyp b/base/base.gyp new file mode 100644 index 0000000000..e2af06bb6f --- /dev/null +++ b/base/base.gyp @@ -0,0 +1,1315 @@ +# Copyright (c) 2012 The Chromium 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$'] ] + }], + ['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', + 'xdg_mime', + ], + 'defines': [ + 'USE_SYMBOLIZE', + ], + 'cflags': [ + '-Wno-write-strings', + ], + 'export_dependent_settings': [ + '../build/linux/system.gyp:glib', + ], + }, { # use_glib!=1 + 'sources/': [ + ['exclude', '/xdg_user_dirs/'], + ['exclude', '_nss\\.cc$'], + ], + }], + ['use_x11==1', { + 'dependencies': [ + '../build/linux/system.gyp:x11', + ], + 'export_dependent_settings': [ + '../build/linux/system.gyp:x11', + ], + }], + ['OS == "android" and _toolset == "host"', { + # Always build base as a static_library for host toolset, even if + # we're doing a component build. Specifically, we only care about the + # target toolset using components since that's what developers are + # focusing on. In theory we should do this more generally for all + # targets when building for host, but getting the gyp magic + # per-toolset for the "component" variable is hard, and we really only + # need base on host. + 'type': 'static_library', + # 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" and _toolset == "target"', { + 'conditions': [ + ['target_arch == "ia32"', { + 'sources/': [ + ['include', '^atomicops_internals_x86_gcc\\.cc$'], + ], + }], + ['target_arch == "mipsel"', { + 'sources/': [ + ['include', '^atomicops_internals_mips_gcc\\.cc$'], + ], + }], + ], + 'dependencies': [ + 'base_jni_headers', + '../third_party/ashmem/ashmem.gyp:ashmem', + ], + 'include_dirs': [ + '<(SHARED_INTERMEDIATE_DIR)/base', + ], + 'link_settings': { + 'libraries': [ + '-llog', + ], + }, + 'sources!': [ + 'debug/stack_trace_posix.cc', + ], + 'includes': [ + '../build/android/cpufeatures.gypi', + ], + }], + ['OS == "android" and _toolset == "target" and android_webview_build == 0', { + 'dependencies': [ + 'base_java', + ], + }], + ['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', + ], + }, + 'conditions': [ + ['linux_use_tcmalloc==0', { + 'defines': [ + 'NO_TCMALLOC', + ], + 'direct_dependent_settings': { + 'defines': [ + 'NO_TCMALLOC', + ], + }, + }], + ], + }], + ['OS == "mac" or (OS == "ios" and _toolset == "host")', { + '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" and _toolset != "host"', { + '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', + ], + }], + ], + }], + ['use_system_nspr==1', { + 'dependencies': [ + 'third_party/nspr/nspr.gyp:nspr', + ], + }], + ], + '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_loop/message_pump_android.cc', + 'message_loop/message_pump_android.h', + 'message_loop/message_pump_glib.cc', + 'message_loop/message_pump_glib.h', + 'message_loop/message_pump_gtk.cc', + 'message_loop/message_pump_gtk.h', + 'message_loop/message_pump_io_ios.cc', + 'message_loop/message_pump_io_ios.h', + 'message_loop/message_pump_observer.h', + 'message_loop/message_pump_aurax11.cc', + 'message_loop/message_pump_aurax11.h', + 'message_loop/message_pump_libevent.cc', + 'message_loop/message_pump_libevent.h', + 'message_loop/message_pump_mac.h', + 'message_loop/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 == "win"', { + # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. + 'msvs_disabled_warnings': [ + 4267, + ], + }], + ], + '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/i18n_constants.cc', + 'i18n/i18n_constants.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_compare.cc', + 'i18n/string_compare.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/base_prefs_export.h', + '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_change_registrar.cc', + 'prefs/pref_change_registrar.h', + 'prefs/pref_member.cc', + 'prefs/pref_member.h', + 'prefs/pref_notifier.h', + 'prefs/pref_notifier_impl.cc', + 'prefs/pref_notifier_impl.h', + 'prefs/pref_observer.h', + 'prefs/pref_registry.cc', + 'prefs/pref_registry.h', + 'prefs/pref_registry_simple.cc', + 'prefs/pref_registry_simple.h', + 'prefs/pref_service.cc', + 'prefs/pref_service.h', + 'prefs/pref_service_builder.cc', + 'prefs/pref_service_builder.h', + 'prefs/pref_store.cc', + 'prefs/pref_store.h', + 'prefs/pref_value_map.cc', + 'prefs/pref_value_map.h', + 'prefs/pref_value_store.cc', + 'prefs/pref_value_store.h', + 'prefs/value_map_pref_store.cc', + 'prefs/value_map_pref_store.h', + ], + }, + { + 'target_name': 'base_prefs_test_support', + 'type': 'static_library', + 'dependencies': [ + 'base', + 'base_prefs', + '../testing/gmock.gyp:gmock', + ], + 'sources': [ + 'prefs/mock_pref_change_callback.cc', + 'prefs/pref_store_observer_mock.cc', + 'prefs/pref_store_observer_mock.h', + 'prefs/testing_pref_service.cc', + 'prefs/testing_pref_service.h', + 'prefs/testing_pref_store.cc', + 'prefs/testing_pref_store.h', + ], + }, + { + # 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': [ + '..', + ], + }, + # 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/activity_status_unittest.cc', + '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/hash_tables_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/crash_logging_unittest.cc', + 'debug/leak_tracker_unittest.cc', + 'debug/proc_maps_linux_unittest.cc', + 'debug/stack_trace_unittest.cc', + 'debug/trace_event_memory_unittest.cc', + 'debug/trace_event_unittest.cc', + 'debug/trace_event_unittest.h', + 'debug/trace_event_win_unittest.cc', + 'deferred_sequenced_task_runner_unittest.cc', + 'environment_unittest.cc', + 'file_util_unittest.cc', + 'file_version_info_unittest.cc', + 'files/dir_reader_posix_unittest.cc', + 'files/file_path_unittest.cc', + 'files/file_util_proxy_unittest.cc', + 'files/important_file_writer_unittest.cc', + 'files/scoped_temp_dir_unittest.cc', + 'gmock_unittest.cc', + 'guid_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', + 'ini_parser_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_nsobject_unittest.mm', + 'mac/scoped_sending_event_unittest.mm', + 'md5_unittest.cc', + 'memory/aligned_memory_unittest.cc', + 'memory/discardable_memory_unittest.cc', + 'memory/linked_ptr_unittest.cc', + 'memory/ref_counted_memory_unittest.cc', + 'memory/ref_counted_unittest.cc', + 'memory/scoped_ptr_unittest.cc', + 'memory/scoped_ptr_unittest.nc', + 'memory/scoped_vector_unittest.cc', + 'memory/shared_memory_unittest.cc', + 'memory/singleton_unittest.cc', + 'memory/weak_ptr_unittest.cc', + 'memory/weak_ptr_unittest.nc', + 'message_loop/message_loop_proxy_impl_unittest.cc', + 'message_loop/message_loop_proxy_unittest.cc', + 'message_loop/message_loop_unittest.cc', + 'message_loop/message_pump_glib_unittest.cc', + 'message_loop/message_pump_io_ios_unittest.cc', + 'message_loop/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_base_unittest.cc', + 'metrics/histogram_unittest.cc', + 'metrics/sparse_histogram_unittest.cc', + 'metrics/stats_table_unittest.cc', + 'metrics/statistics_recorder_unittest.cc', + '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', + 'posix/unix_domain_socket_linux_unittest.cc', + 'power_monitor/power_monitor_unittest.cc', + 'prefs/default_pref_store_unittest.cc', + 'prefs/json_pref_store_unittest.cc', + 'prefs/mock_pref_change_callback.h', + 'prefs/overlay_user_pref_store_unittest.cc', + 'prefs/pref_change_registrar_unittest.cc', + 'prefs/pref_member_unittest.cc', + 'prefs/pref_notifier_impl_unittest.cc', + 'prefs/pref_service_unittest.cc', + 'prefs/pref_value_map_unittest.cc', + 'prefs/pref_value_store_unittest.cc', + 'process/memory_unittest.cc', + 'process/memory_unittest_mac.h', + 'process/memory_unittest_mac.mm', + 'process/process_util_unittest.cc', + 'process/process_util_unittest_ios.cc', + 'profiler/tracked_time_unittest.cc', + 'rand_util_unittest.cc', + 'safe_numerics_unittest.cc', + 'safe_numerics_unittest.nc', + 'scoped_clear_errno_unittest.cc', + 'scoped_native_library_unittest.cc', + 'scoped_observer.h', + 'security_unittest.cc', + 'sequence_checker_unittest.cc', + 'sha1_unittest.cc', + 'stl_util_unittest.cc', + 'strings/nullable_string16_unittest.cc', + 'strings/string16_unittest.cc', + 'strings/stringprintf_unittest.cc', + 'strings/string_number_conversions_unittest.cc', + 'strings/string_piece_unittest.cc', + 'strings/string_split_unittest.cc', + 'strings/string_tokenizer_unittest.cc', + 'strings/string_util_unittest.cc', + 'strings/stringize_macros_unittest.cc', + 'strings/sys_string_conversions_mac_unittest.mm', + 'strings/sys_string_conversions_unittest.cc', + 'strings/utf_offset_string_conversions_unittest.cc', + 'strings/utf_string_conversions_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', + 'system_monitor/system_monitor_unittest.cc', + 'task_runner_util_unittest.cc', + 'template_util_unittest.cc', + 'test/expectations/expectation_unittest.cc', + 'test/expectations/parser_unittest.cc', + 'test/trace_event_analyzer_unittest.cc', + '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_id_name_manager_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/pr_time_unittest.cc', + 'time/time_unittest.cc', + 'time/time_win_unittest.cc', + 'timer/hi_res_timer_manager_unittest.cc', + 'timer/timer_unittest.cc', + 'tools_sanity_unittest.cc', + 'tracked_objects_unittest.cc', + 'tuple_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/message_window_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_handle_unittest.cc', + 'win/scoped_process_information_unittest.cc', + 'win/scoped_variant_unittest.cc', + 'win/shortcut_unittest.cc', + 'win/startup_information_unittest.cc', + 'win/win_util_unittest.cc', + 'win/wrapped_window_proc_unittest.cc', + ], + 'dependencies': [ + 'base', + 'base_i18n', + 'base_prefs', + 'base_prefs_test_support', + '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': [ + ['use_glib==1', { + 'defines': [ + 'USE_SYMBOLIZE', + ], + }], + ['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', + ], + }], + ], + }], + ['OS == "ios" and _toolset != "host"', { + 'sources/': [ + # Only test the iOS-meaningful portion of process_utils. + ['exclude', '^process/memory_unittest'], + ['exclude', '^process/process_util_unittest\\.cc$'], + ['include', '^process/process_util_unittest_ios\\.cc$'], + # Requires spawning processes. + ['exclude', '^metrics/stats_table_unittest\\.cc$'], + # iOS does not use message_pump_libevent. + ['exclude', '^message_loop/message_pump_libevent_unittest\\.cc$'], + ], + 'conditions': [ + ['coverage != 0', { + 'sources!': [ + # These sources can't be built with coverage due to a toolchain + # bug: http://openradar.appspot.com/radar?id=1499403 + 'json/json_reader_unittest.cc', + 'strings/string_piece_unittest.cc', + + # These tests crash when run with coverage turned on due to an + # issue with llvm_gcda_increment_indirect_counter: + # http://crbug.com/156058 + 'debug/trace_event_unittest.cc', + 'debug/trace_event_unittest.h', + 'logging_unittest.cc', + 'string_util_unittest.cc', + 'test/trace_event_analyzer_unittest.cc', + 'utf_offset_string_conversions_unittest.cc', + ], + }], + ], + 'actions': [ + { + 'action_name': 'copy_test_data', + 'variables': { + 'test_data_files': [ + 'test/data', + ], + 'test_data_prefix': 'base', + }, + 'includes': [ '../build/copy_test_data_ios.gypi' ], + }, + ], + }], + ['use_glib==1', { + 'sources!': [ + 'file_version_info_unittest.cc', + ], + 'conditions': [ + [ '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_loop/message_pump_glib_unittest.cc', + ] + }], + ['use_ozone == 1', { + 'sources!': [ + 'message_loop/message_pump_glib_unittest.cc', + ] + }], + ['OS == "linux" and linux_use_tcmalloc==1', { + 'dependencies': [ + 'allocator/allocator.gyp:allocator', + ], + }, + ], + ['OS == "win"', { + # This is needed to trigger the dll copy step on windows. + # TODO(mark): This should not be necessary. + '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_loop/message_pump_libevent_unittest.cc', + ], + # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. + 'msvs_disabled_warnings': [ + 4267, + ], + # This is needed so base_unittests uses the allocator shim, as + # SecurityTest.MemoryAllocationRestriction* tests are dependent + # on tcmalloc. + # TODO(wfh): crbug.com/246278 Move tcmalloc specific tests into + # their own test suite. + 'conditions': [ + ['win_use_allocator_shim==1', { + 'dependencies': [ + 'allocator/allocator.gyp:allocator', + ], + }], + ], + }, { # OS != "win" + 'dependencies': [ + '../third_party/libevent/libevent.gyp:libevent' + ], + 'sources/': [ + ['exclude', '^win/'], + ], + 'sources!': [ + 'debug/trace_event_win_unittest.cc', + 'time/time_win_unittest.cc', + 'win/win_util_unittest.cc', + ], + }], + ['use_system_nspr==1', { + 'dependencies': [ + 'third_party/nspr/nspr.gyp:nspr', + ], + }], + ], # conditions + 'target_conditions': [ + ['OS == "ios" and _toolset != "host"', { + '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', '^mac/scoped_nsobject_unittest\\.mm$'], + ['include', '^sys_string_conversions_mac_unittest\\.mm$'], + ], + }], + ['OS == "android"', { + 'sources/': [ + ['include', '^debug/proc_maps_linux_unittest\\.cc$'], + ], + }], + ], # target_conditions + }, + { + 'target_name': 'test_support_base', + 'type': 'static_library', + 'dependencies': [ + 'base', + 'base_static', + 'base_i18n', + '../testing/gmock.gyp:gmock', + '../testing/gtest.gyp:gtest', + 'third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', + ], + 'export_dependent_settings': [ + 'base', + ], + 'conditions': [ + ['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', + ], + }], + ], + 'sources': [ + 'perftimer.cc', + 'test/expectations/expectation.cc', + 'test/expectations/expectation.h', + 'test/expectations/parser.cc', + 'test/expectations/parser.h', + '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/null_task_runner.cc', + 'test/null_task_runner.h', + 'test/perf_test_suite.cc', + 'test/perf_test_suite.h', + 'test/power_monitor_test_base.cc', + 'test/power_monitor_test_base.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/sequenced_worker_pool_owner.cc', + 'test/sequenced_worker_pool_owner.h', + 'test/simple_test_clock.cc', + 'test/simple_test_clock.h', + 'test/simple_test_tick_clock.cc', + 'test/simple_test_tick_clock.h', + 'test/task_runner_test_template.cc', + 'test/task_runner_test_template.h', + 'test/test_file_util.cc', + '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_launcher.cc', + 'test/test_launcher.h', + 'test/test_listener_ios.h', + 'test/test_listener_ios.mm', + 'test/test_pending_task.cc', + 'test/test_pending_task.h', + 'test/test_process_killer_win.cc', + 'test/test_process_killer_win.h', + 'test/test_reg_util_win.cc', + 'test/test_reg_util_win.h', + 'test/test_shortcut_win.cc', + 'test/test_shortcut_win.h', + 'test/test_simple_task_runner.cc', + 'test/test_simple_task_runner.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$'], + ], + }], + ], # 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', + ], + }], + ], + }, + ], + 'conditions': [ + ['OS!="ios"', { + 'targets': [ + { + 'target_name': 'check_example', + 'type': 'executable', + 'sources': [ + 'check_example.cc', + ], + 'dependencies': [ + 'base', + ], + }, + ], + }], + ['OS == "win" and target_arch=="ia32"', { + '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', + }, + }, + }, + { + # 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, + ], + }, + ], + }], + ['os_posix==1 and OS!="mac" and OS!="ios"', { + '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"', { + 'targets': [ + { + 'target_name': 'base_jni_headers', + 'type': 'none', + 'sources': [ + 'android/java/src/org/chromium/base/ActivityStatus.java', + 'android/java/src/org/chromium/base/BuildInfo.java', + 'android/java/src/org/chromium/base/CpuFeatures.java', + 'android/java/src/org/chromium/base/ImportantFileWriterAndroid.java', + 'android/java/src/org/chromium/base/MemoryPressureListener.java', + 'android/java/src/org/chromium/base/JavaHandlerThread.java', + 'android/java/src/org/chromium/base/PathService.java', + 'android/java/src/org/chromium/base/PathUtils.java', + 'android/java/src/org/chromium/base/PowerMonitor.java', + 'android/java/src/org/chromium/base/SystemMessageHandler.java', + 'android/java/src/org/chromium/base/SysUtils.java', + 'android/java/src/org/chromium/base/ThreadUtils.java', + ], + 'conditions': [ + ['google_tv==1', { + 'sources': [ + 'android/java/src/org/chromium/base/ContextTypes.java', + ], + }], + ], + 'variables': { + 'jni_gen_package': 'base', + }, + 'includes': [ '../build/jni_generator.gypi' ], + }, + { + 'target_name': 'base_java', + 'type': 'none', + 'variables': { + 'java_in_dir': '../base/android/java', + }, + 'dependencies': [ + 'base_java_activity_state', + 'base_java_memory_pressure_level_list', + ], + 'includes': [ '../build/java.gypi' ], + 'conditions': [ + ['android_webview_build==0', { + 'dependencies': [ + '../third_party/jsr-305/jsr-305.gyp:jsr_305_javalib', + ], + }] + ], + }, + { + 'target_name': 'base_java_activity_state', + 'type': 'none', + # This target is used to auto-generate ActivityState.java + # from a template file. The source file contains a list of + # Java constant declarations matching the ones in + # android/activity_state_list.h. + 'sources': [ + 'android/java/src/org/chromium/base/ActivityState.template', + ], + 'variables': { + 'package_name': 'org/chromium/base', + 'template_deps': ['android/activity_state_list.h'], + }, + 'includes': [ '../build/android/java_cpp_template.gypi' ], + }, + { + 'target_name': 'base_java_memory_pressure_level_list', + 'type': 'none', + 'sources': [ + 'android/java/src/org/chromium/base/MemoryPressureLevelList.template', + ], + 'variables': { + 'package_name': 'org/chromium/base', + 'template_deps': ['memory/memory_pressure_level_list.h'], + }, + 'includes': [ '../build/android/java_cpp_template.gypi' ], + }, + { + 'target_name': 'base_java_test_support', + 'type': 'none', + 'dependencies': [ + 'base_java', + ], + 'variables': { + 'java_in_dir': '../base/test/android/javatests', + }, + 'includes': [ '../build/java.gypi' ], + }, + { + 'target_name': 'base_javatests', + 'type': 'none', + 'dependencies': [ + 'base_java', + 'base_java_test_support', + ], + 'variables': { + 'java_in_dir': '../base/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 + }, + }, + }, + ], + }], + # 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"', { + '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/base/base.gypi b/base/base.gypi new file mode 100644 index 0000000000..6d82ee335b --- /dev/null +++ b/base/base.gypi @@ -0,0 +1,894 @@ +# Copyright (c) 2012 The Chromium 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/prcpucfg.h', + 'third_party/nspr/prcpucfg_freebsd.h', + 'third_party/nspr/prcpucfg_linux.h', + 'third_party/nspr/prcpucfg_mac.h', + 'third_party/nspr/prcpucfg_nacl.h', + 'third_party/nspr/prcpucfg_openbsd.h', + 'third_party/nspr/prcpucfg_solaris.h', + 'third_party/nspr/prcpucfg_win.h', + 'third_party/nspr/prtime.cc', + 'third_party/nspr/prtime.h', + 'third_party/nspr/prtypes.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/activity_status.cc', + 'android/activity_status.h', + 'android/base_jni_registrar.cc', + 'android/base_jni_registrar.h', + 'android/build_info.cc', + 'android/build_info.h', + 'android/cpu_features.cc', + 'android/fifo_utils.cc', + 'android/fifo_utils.h', + 'android/important_file_writer_android.cc', + 'android/important_file_writer_android.h', + '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/memory_pressure_listener_android.cc', + 'android/memory_pressure_listener_android.h', + 'android/java_handler_thread.cc', + 'android/java_handler_thread.h', + 'android/path_service_android.cc', + 'android/path_service_android.h', + 'android/path_utils.cc', + 'android/path_utils.h', + 'android/sys_utils.cc', + 'android/sys_utils.h', + 'android/thread_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_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/hash_tables.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/crash_logging.cc', + 'debug/crash_logging.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/proc_maps_linux.cc', + 'debug/proc_maps_linux.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.h', + 'debug/trace_event_android.cc', + 'debug/trace_event_impl.cc', + 'debug/trace_event_impl.h', + 'debug/trace_event_impl_constants.cc', + 'debug/trace_event_memory.cc', + 'debug/trace_event_memory.h', + 'debug/trace_event_win.cc', + 'deferred_sequenced_task_runner.cc', + 'deferred_sequenced_task_runner.h', + 'environment.cc', + 'environment.h', + 'file_descriptor_posix.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_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_linux.h', + 'files/dir_reader_posix.h', + 'files/file_enumerator.cc', + 'files/file_enumerator.h', + 'files/file_enumerator_posix.cc', + 'files/file_enumerator_win.cc', + 'files/file_path.cc', + 'files/file_path.h', + 'files/file_path_constants.cc', + '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/file_util_proxy.cc', + 'files/file_util_proxy.h', + 'files/important_file_writer.h', + 'files/important_file_writer.cc', + 'files/memory_mapped_file.cc', + 'files/memory_mapped_file.h', + 'files/memory_mapped_file_posix.cc', + 'files/memory_mapped_file_win.cc', + 'files/scoped_platform_file_closer.cc', + 'files/scoped_platform_file_closer.h', + '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', + 'id_map.h', + 'ini_parser.cc', + 'ini_parser.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/bundle_locations.h', + 'mac/bundle_locations.mm', + 'mac/cocoa_protocols.h', + 'mac/foundation_util.h', + 'mac/foundation_util.mm', + 'mac/launch_services_util.cc', + 'mac/launch_services_util.h', + '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_block.h', + 'mac/scoped_cftyperef.h', + 'mac/scoped_ioobject.h', + 'mac/scoped_ioplugininterface.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_nsobject.h', + 'mac/scoped_sending_event.h', + 'mac/scoped_sending_event.mm', + 'mac/sdk_forward_declarations.h', + 'memory/aligned_memory.cc', + 'memory/aligned_memory.h', + 'memory/discardable_memory.cc', + 'memory/discardable_memory.h', + 'memory/discardable_memory_android.cc', + 'memory/discardable_memory_mac.cc', + 'memory/linked_ptr.h', + 'memory/manual_constructor.h', + 'memory/memory_pressure_listener.cc', + 'memory/memory_pressure_listener.h', + 'memory/raw_scoped_refptr_mismatch_checker.h', + 'memory/ref_counted.cc', + 'memory/ref_counted.h', + 'memory/ref_counted_delete_on_message_loop.h', + 'memory/ref_counted_memory.cc', + 'memory/ref_counted_memory.h', + 'memory/scoped_handle.h', + 'memory/scoped_open_process.h', + 'memory/scoped_policy.h', + 'memory/scoped_ptr.h', + 'memory/scoped_vector.h', + 'memory/shared_memory.h', + 'memory/shared_memory_android.cc', + 'memory/shared_memory_nacl.cc', + 'memory/shared_memory_posix.cc', + 'memory/shared_memory_win.cc', + 'memory/singleton.cc', + 'memory/singleton.h', + 'memory/weak_ptr.cc', + 'memory/weak_ptr.h', + 'message_loop/incoming_task_queue.cc', + 'message_loop/incoming_task_queue.h', + 'message_loop/message_loop.cc', + 'message_loop/message_loop.h', + 'message_loop/message_loop_proxy.cc', + 'message_loop/message_loop_proxy.h', + 'message_loop/message_loop_proxy_impl.cc', + 'message_loop/message_loop_proxy_impl.h', + 'message_loop/message_pump.cc', + 'message_loop/message_pump.h', + 'message_loop/message_pump_android.cc', + 'message_loop/message_pump_android.h', + 'message_loop/message_pump_default.cc', + 'message_loop/message_pump_default.h', + 'message_loop/message_pump_ozone.cc', + 'message_loop/message_pump_ozone.h', + 'message_loop/message_pump_win.cc', + 'message_loop/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', + 'nix/mime_util_xdg.cc', + 'nix/mime_util_xdg.h', + 'nix/xdg_util.cc', + 'nix/xdg_util.h', + '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_linux.cc', + 'posix/unix_domain_socket_linux.h', + 'power_monitor/power_monitor.cc', + 'power_monitor/power_monitor.h', + 'power_monitor/power_monitor_device_source_android.cc', + 'power_monitor/power_monitor_device_source_android.h', + 'power_monitor/power_monitor_device_source.cc', + 'power_monitor/power_monitor_device_source.h', + 'power_monitor/power_monitor_device_source_ios.mm', + 'power_monitor/power_monitor_device_source_mac.mm', + 'power_monitor/power_monitor_device_source_posix.cc', + 'power_monitor/power_monitor_device_source_win.cc', + 'power_monitor/power_monitor_source.cc', + 'power_monitor/power_monitor_source.h', + 'power_monitor/power_observer.h', + 'process/internal_linux.cc', + 'process/internal_linux.h', + 'process/kill.cc', + 'process/kill.h', + 'process/kill_mac.cc', + 'process/kill_posix.cc', + 'process/kill_win.cc', + 'process/launch.h', + 'process/launch_ios.cc', + 'process/launch_mac.cc', + 'process/launch_posix.cc', + 'process/launch_win.cc', + 'process/memory.h', + 'process/memory_linux.cc', + 'process/memory_mac.mm', + 'process/memory_win.cc', + 'process/process.h', + 'process/process_handle_freebsd.cc', + 'process/process_handle_linux.cc', + 'process/process_handle_mac.cc', + 'process/process_handle_openbsd.cc', + 'process/process_handle_posix.cc', + 'process/process_handle_win.cc', + 'process/process_info.h', + 'process/process_info_linux.cc', + 'process/process_info_mac.cc', + 'process/process_info_win.cc', + 'process/process_iterator.cc', + 'process/process_iterator.h', + 'process/process_iterator_freebsd.cc', + 'process/process_iterator_linux.cc', + 'process/process_iterator_mac.cc', + 'process/process_iterator_openbsd.cc', + 'process/process_iterator_win.cc', + 'process/process_linux.cc', + 'process/process_metrics.h', + 'process/process_metrics_freebsd.cc', + 'process/process_metrics_ios.cc', + 'process/process_metrics_linux.cc', + 'process/process_metrics_mac.cc', + 'process/process_metrics_openbsd.cc', + 'process/process_metrics_posix.cc', + 'process/process_metrics_win.cc', + 'process/process_posix.cc', + 'process/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_numerics.h', + 'safe_strerror_posix.cc', + 'safe_strerror_posix.h', + 'scoped_native_library.cc', + 'scoped_native_library.h', + 'sequence_checker.h', + 'sequence_checker_impl.cc', + 'sequence_checker_impl.h', + 'sequenced_task_runner.cc', + 'sequenced_task_runner.h', + 'sequenced_task_runner_helpers.h', + 'sha1.h', + 'sha1_portable.cc', + 'sha1_win.cc', + 'single_thread_task_runner.h', + 'stl_util.h', + 'strings/latin1_string_conversions.cc', + 'strings/latin1_string_conversions.h', + 'strings/nullable_string16.cc', + 'strings/nullable_string16.h', + 'strings/string16.cc', + 'strings/string16.h', + 'strings/string_number_conversions.cc', + 'strings/string_split.cc', + 'strings/string_split.h', + 'strings/string_number_conversions.h', + 'strings/string_piece.cc', + 'strings/string_piece.h', + 'strings/string_tokenizer.h', + 'strings/string_util.cc', + 'strings/string_util.h', + 'strings/string_util_constants.cc', + 'strings/string_util_posix.h', + 'strings/string_util_win.h', + 'strings/stringize_macros.h', + 'strings/stringprintf.cc', + 'strings/stringprintf.h', + 'strings/sys_string_conversions.h', + 'strings/sys_string_conversions_mac.mm', + 'strings/sys_string_conversions_posix.cc', + 'strings/sys_string_conversions_win.cc', + 'strings/utf_offset_string_conversions.cc', + 'strings/utf_offset_string_conversions.h', + 'strings/utf_string_conversion_utils.cc', + 'strings/utf_string_conversion_utils.h', + 'strings/utf_string_conversions.cc', + 'strings/utf_string_conversions.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/spin_wait.h', + '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', + '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', + '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_android.cc', + 'threading/platform_thread_linux.cc', + '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_id_name_manager.cc', + 'threading/thread_id_name_manager.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/clock.cc', + 'time/clock.h', + 'time/default_clock.cc', + 'time/default_clock.h', + 'time/default_tick_clock.cc', + 'time/default_tick_clock.h', + 'time/tick_clock.cc', + 'time/tick_clock.h', + 'time/time.cc', + 'time/time.h', + 'time/time_mac.cc', + 'time/time_posix.cc', + 'time/time_win.cc', + 'timer/hi_res_timer_manager_posix.cc', + 'timer/hi_res_timer_manager_win.cc', + 'timer/hi_res_timer_manager.h', + 'timer/timer.cc', + 'timer/timer.h', + 'tracked_objects.cc', + 'tracked_objects.h', + 'tracking_info.cc', + 'tracking_info.h', + 'tuple.h', + 'values.cc', + 'values.h', + 'value_conversions.cc', + 'value_conversions.h', + 'version.cc', + 'version.h', + 'vlog.cc', + 'vlog.h', + 'win/enum_variant.cc', + 'win/enum_variant.h', + '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.cc', + 'win/iunknown_impl.h', + 'win/message_window.cc', + 'win/message_window.h', + '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_propvariant.h', + 'win/scoped_select_object.h', + 'win/scoped_variant.cc', + 'win/scoped_variant.h', + 'win/shortcut.cc', + 'win/shortcut.h', + 'win/startup_information.cc', + 'win/startup_information.h', + 'win/text_services_message_filter.cc', + 'win/text_services_message_filter.h', + 'win/win_util.cc', + 'win/win_util.h', + 'win/windows_version.cc', + 'win/windows_version.h', + 'win/wrapped_window_proc.cc', + 'win/wrapped_window_proc.h', + ], + 'conditions': [ + ['google_tv==1', { + 'sources': [ + 'android/context_types.cc', + 'android/context_types.h', + ], + }], + ], + 'defines': [ + 'BASE_IMPLEMENTATION', + ], + 'include_dirs': [ + '..', + ], + 'msvs_disabled_warnings': [ + 4018, + ], + 'target_conditions': [ + ['<(use_glib)==0 or >(nacl_untrusted_build)==1', { + 'sources/': [ + ['exclude', '^nix/'], + ], + 'sources!': [ + 'atomicops_internals_x86_gcc.cc', + 'message_loop/message_pump_glib.cc', + 'message_loop/message_pump_aurax11.cc', + ], + }], + ['<(toolkit_uses_gtk)==0 or >(nacl_untrusted_build)==1', { + 'sources!': ['message_loop/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', + 'cpu.cc', + 'debug/stack_trace_posix.cc', + 'file_util.cc', + 'file_util_posix.cc', + 'files/file_enumerator_posix.cc', + 'files/file_path_watcher_kqueue.cc', + 'files/file_util_proxy.cc', + 'memory/shared_memory_posix.cc', + 'native_library_posix.cc', + 'path_service.cc', + 'posix/unix_domain_socket_linux.cc', + 'process/kill_posix.cc', + 'process/launch_posix.cc', + 'process/process_metrics_posix.cc', + 'process/process_posix.cc', + 'rand_util_posix.cc', + 'scoped_native_library.cc', + 'files/scoped_temp_dir.cc', + 'sys_info_posix.cc', + 'third_party/dynamic_annotations/dynamic_annotations.c', + ], + 'sources/': [ + # Metrics won't work in the NaCl sandbox. + ['exclude', '^metrics/'], + ['include', '^threading/platform_thread_linux\\.cc$'], + ], + }], + ['OS == "android" and >(nacl_untrusted_build)==0', { + 'sources!': [ + 'base_paths_posix.cc', + 'files/file_path_watcher_kqueue.cc', + 'files/file_path_watcher_stub.cc', + 'power_monitor/power_monitor_device_source_posix.cc', + ], + 'sources/': [ + ['include', '^debug/proc_maps_linux\\.cc$'], + ['include', '^files/file_path_watcher_linux\\.cc$'], + ['include', '^process/memory_linux\\.cc$'], + ['include', '^process/internal_linux\\.cc$'], + ['include', '^process/process_handle_linux\\.cc$'], + ['include', '^process/process_iterator\\.cc$'], + ['include', '^process/process_iterator_linux\\.cc$'], + ['include', '^process/process_metrics_linux\\.cc$'], + ['include', '^posix/unix_domain_socket_linux\\.cc$'], + ['include', '^strings/sys_string_conversions_posix\\.cc$'], + ['include', '^sys_info_linux\\.cc$'], + ['include', '^worker_pool_linux\\.cc$'], + ], + }], + ['OS == "android" and _toolset == "host" and host_os == "linux"', { + 'sources/': [ + # Pull in specific files for host builds. + ['include', '^threading/platform_thread_linux\\.cc$'], + ], + }], + ['OS == "ios" and _toolset != "host"', { + '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/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', '^mac/scoped_nsobject\\.'], + ['include', '^memory/discardable_memory_mac\\.'], + ['include', '^message_loop/message_pump_mac\\.'], + ['include', '^strings/sys_string_conversions_mac\\.'], + ['include', '^threading/platform_thread_mac\\.'], + ['include', '^time/time_mac\\.'], + ['include', '^worker_pool_mac\\.'], + # Exclude all process/ except the minimal implementation + # needed on iOS (mostly for unit tests). + ['exclude', '^process/.*'], + ['include', '^process/.*_ios\.(cc|mm)$'], + ['include', '^process/memory_stubs\.cc$'], + ['include', '^process/process_handle_posix\.cc$'], + ], + 'sources': [ + 'process/memory_stubs.cc', + ], + 'sources!': [ + 'message_loop/message_pump_libevent.cc' + ], + }], + ['OS == "ios" and _toolset == "host"', { + 'sources/': [ + # Copied filename_rules to switch from iOS to Mac inclusions. + ['include', '_(cocoa|mac)(_unittest)?\\.(h|cc|mm?)$'], + ['include', '(^|/)(cocoa|mac)/'], + ['exclude', '_ios(_unittest)?\\.(h|cc|mm?)$'], + ['exclude', '(^|/)ios/'], + ] + }], + ['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', { + '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_loop/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', + 'strings/string16.cc', + ], + },], + ['<(use_ozone) == 1', { + 'sources!': [ + 'message_loop/message_pump_glib.cc', + 'message_loop/message_pump_aurax11.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', '^strings/sys_string_conversions_posix\\.cc$'], + ], + }], + ['<(os_bsd)==1 and >(nacl_untrusted_build)==0', { + 'sources': [ + 'process/memory_stubs.cc', + ], + 'sources/': [ + ['exclude', '^files/file_path_watcher_linux\\.cc$'], + ['exclude', '^files/file_path_watcher_stub\\.cc$'], + ['exclude', '^file_util_linux\\.cc$'], + ['exclude', '^process/process_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$'] ], + }], + ['<(use_system_nspr)==1 and >(nacl_untrusted_build)==0', { + 'sources/': [ + ['exclude', '^third_party/nspr/'], + ], + }], + ], + }], + ], + }, +} diff --git a/base/base64.cc b/base/base64.cc new file mode 100644 index 0000000000..9514b0a5c2 --- /dev/null +++ b/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(input.size()); + + // modp_b64_encode_len() returns at least 1, so temp[0] is safe to use. + size_t output_size = modp_b64_encode(&(temp[0]), input.data(), input_size); + if (output_size == MODP_B64_ERROR) + return false; + + 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! + size_t input_size = input.size(); + size_t output_size = modp_b64_decode(&(temp[0]), input.data(), input_size); + if (output_size == MODP_B64_ERROR) + return false; + + temp.resize(output_size); + output->swap(temp); + return true; +} + +} // namespace base diff --git a/base/base64.h b/base/base64.h new file mode 100644 index 0000000000..cc78edce5c --- /dev/null +++ b/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 + +#include "base/base_export.h" +#include "base/strings/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/base/base64_unittest.cc b/base/base64_unittest.cc new file mode 100644 index 0000000000..9a5dd818e0 --- /dev/null +++ b/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/base/base_export.h b/base/base_export.h new file mode 100644 index 0000000000..723b38a60f --- /dev/null +++ b/base/base_export.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_BASE_EXPORT_H_ +#define BASE_BASE_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#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/base/base_paths.cc b/base/base_paths.cc new file mode 100644 index 0000000000..9f2b250561 --- /dev/null +++ b/base/base_paths.cc @@ -0,0 +1,47 @@ +// 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_util.h" +#include "base/files/file_path.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 DIR_EXE: + PathService::Get(FILE_EXE, &cur); + cur = cur.DirName(); + break; + case DIR_MODULE: + PathService::Get(FILE_MODULE, &cur); + cur = cur.DirName(); + break; + case DIR_TEMP: + if (!file_util::GetTempDir(&cur)) + return false; + break; + case DIR_TEST_DATA: + if (!PathService::Get(DIR_SOURCE_ROOT, &cur)) + return false; + cur = cur.Append(FILE_PATH_LITERAL("base")); + cur = cur.Append(FILE_PATH_LITERAL("test")); + cur = cur.Append(FILE_PATH_LITERAL("data")); + if (!base::PathExists(cur)) // We don't want to create this. + return false; + break; + default: + return false; + } + + *result = cur; + return true; +} + +} // namespace base diff --git a/base/base_paths.h b/base/base_paths.h new file mode 100644 index 0000000000..f9601a290a --- /dev/null +++ b/base/base_paths.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_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) +#include "base/base_paths_android.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. + + DIR_TEST_DATA, // Used only for testing. + + PATH_END +}; + +} // namespace base + +#endif // BASE_BASE_PATHS_H_ diff --git a/base/base_paths_android.cc b/base/base_paths_android.cc new file mode 100644 index 0000000000..d0a709fc3a --- /dev/null +++ b/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 + +#include "base/android/jni_android.h" +#include "base/android/path_utils.h" +#include "base/base_paths.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/process/process_metrics.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/base/base_paths_android.h b/base/base_paths_android.h new file mode 100644 index 0000000000..7a9ac4a674 --- /dev/null +++ b/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/base/base_paths_mac.h b/base/base_paths_mac.h new file mode 100644 index 0000000000..ac75402f3c --- /dev/null +++ b/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/base/base_paths_mac.mm b/base/base_paths_mac.mm new file mode 100644 index 0000000000..5d4461cb23 --- /dev/null +++ b/base/base_paths_mac.mm @@ -0,0 +1,117 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Defines base::PathProviderMac which replaces base::PathProviderPosix for Mac +// in base/path_service.cc. + +#include +#import +#include + +#include "base/base_paths.h" +#include "base/compiler_specific.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/mac/foundation_util.h" +#include "base/path_service.h" +#include "base/strings/string_util.h" +#include "build/build_config.h" + +namespace { + +void GetNSExecutablePath(base::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); + + // _NSGetExecutablePath may return paths containing ./ or ../ which makes + // FilePath::DirName() work incorrectly, convert it to absolute path so that + // paths such as DIR_SOURCE_ROOT can work, since we expect absolute paths to + // be returned here. + *path = base::MakeAbsoluteFilePath(base::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(base::FilePath* path, + const void* address) WARN_UNUSED_RESULT; + +bool GetModulePathForAddress(base::FilePath* path, const void* address) { + Dl_info info; + if (dladdr(address, &info) == 0) + return false; + *path = base::FilePath(info.dli_fname); + return true; +} + +} // namespace + +namespace base { + +bool PathProviderMac(int key, base::FilePath* result) { + switch (key) { + case base::FILE_EXE: + GetNSExecutablePath(result); + return true; + case base::FILE_MODULE: + return GetModulePathForAddress(result, + reinterpret_cast(&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 && !base::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/base/base_paths_posix.cc b/base/base_paths_posix.cc new file mode 100644 index 0000000000..2d5fcbd26d --- /dev/null +++ b/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 +#include + +#include "base/base_paths.h" +#include "base/environment.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/nix/xdg_util.h" +#include "base/path_service.h" +#include "base/process/process_metrics.h" +#include "build/build_config.h" + +#if defined(OS_FREEBSD) +#include +#include +#elif defined(OS_SOLARIS) +#include +#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 env(base::Environment::Create()); + std::string cr_source_root; + if (env->GetVar("CR_SOURCE_ROOT", &cr_source_root)) { + path = FilePath(cr_source_root); + if (base::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 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/base/base_paths_posix.h b/base/base_paths_posix.h new file mode 100644 index 0000000000..811c8cb894 --- /dev/null +++ b/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/base/base_paths_win.cc b/base/base_paths_win.cc new file mode 100644 index 0000000000..bc883358ad --- /dev/null +++ b/base/base_paths_win.cc @@ -0,0 +1,208 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include "base/base_paths.h" +#include "base/files/file_path.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; + +using base::FilePath; + +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(-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(&__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 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/base/base_paths_win.h b/base/base_paths_win.h new file mode 100644 index 0000000000..11bc111d66 --- /dev/null +++ b/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\\ + // Start Menu\Programs" + DIR_APP_DATA, // Application Data directory under the user profile. + DIR_PROFILE, // Usually "C:\Documents and settings\. + 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/base/base_switches.cc b/base/base_switches.cc new file mode 100644 index 0000000000..bdd7b62d47 --- /dev/null +++ b/base/base_switches.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/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"; + +// Sends a pretty-printed version of tracing info to the console. +const char kTraceToConsole[] = "trace-to-console"; + +#if defined(OS_POSIX) +// A flag, generated internally for renderer and other helper process command +// lines on Linux and Mac. It tells the helper process to enable crash dumping +// and reporting, because helpers cannot access the files needed to make this +// decision. +const char kEnableCrashReporter[] = "enable-crash-reporter"; +#endif + +} // namespace switches diff --git a/base/base_switches.h b/base/base_switches.h new file mode 100644 index 0000000000..7686e76316 --- /dev/null +++ b/base/base_switches.h @@ -0,0 +1,31 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Defines all the "base" command-line switches. + +#ifndef BASE_BASE_SWITCHES_H_ +#define BASE_BASE_SWITCHES_H_ + +#include "build/build_config.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[]; +extern const char kTraceToConsole[]; + +#if defined(OS_POSIX) +extern const char kEnableCrashReporter[]; +#endif + +} // namespace switches + +#endif // BASE_BASE_SWITCHES_H_ diff --git a/base/base_unittests.isolate b/base/base_unittests.isolate new file mode 100644 index 0000000000..852997b106 --- /dev/null +++ b/base/base_unittests.isolate @@ -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. +{ + 'conditions': [ + ['OS=="android" or OS=="linux" or OS=="mac" or OS=="win"', { + 'variables': { + 'isolate_dependency_untracked': [ + 'data/', + 'test/data/', + ], + }, + }], + ['OS=="linux"', { + 'variables': { + 'command': [ + '../testing/xvfb.py', + '<(PRODUCT_DIR)', + '../tools/swarm_client/googletest/run_test_cases.py', + '<(PRODUCT_DIR)/base_unittests<(EXECUTABLE_SUFFIX)', + ], + 'isolate_dependency_tracked': [ + '../testing/xvfb.py', + '<(PRODUCT_DIR)/xdisplaycheck<(EXECUTABLE_SUFFIX)', + ], + }, + }], + ['OS=="linux" or OS=="mac" or OS=="win"', { + 'variables': { + 'isolate_dependency_tracked': [ + '../testing/test_env.py', + '../tools/swarm_client/run_isolated.py', + '../tools/swarm_client/googletest/run_test_cases.py', + '<(PRODUCT_DIR)/base_unittests<(EXECUTABLE_SUFFIX)', + ], + }, + }], + ['OS=="mac" or OS=="win"', { + 'variables': { + 'command': [ + '../testing/test_env.py', + '../tools/swarm_client/googletest/run_test_cases.py', + '<(PRODUCT_DIR)/base_unittests<(EXECUTABLE_SUFFIX)', + ], + }, + }], + ['OS=="win"', { + 'variables': { + 'isolate_dependency_tracked': [ + '<(PRODUCT_DIR)/icudt.dll', + ], + }, + }], + ], +} diff --git a/base/base_untrusted.gyp b/base/base_untrusted.gyp new file mode 100644 index 0000000000..e4b005bc29 --- /dev/null +++ b/base/base_untrusted.gyp @@ -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. + +{ + '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, + 'build_irt': 1, + 'sources': [ + 'base_switches.cc', + 'base_switches.h', + 'strings/string16.cc', + 'sync_socket_nacl.cc', + 'time/time_posix.cc', + ], + }, + 'dependencies': [ + '<(DEPTH)/native_client/tools.gyp:prep_toolchain', + ], + }, + ], + }], + ], +} diff --git a/base/basictypes.h b/base/basictypes.h new file mode 100644 index 0000000000..1bed65c6a5 --- /dev/null +++ b/base/basictypes.h @@ -0,0 +1,369 @@ +// Copyright (c) 2011 The Chromium Authors. All 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 // So we can set the bounds of our types +#include // For size_t +#include // 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 // 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 +// 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: It is DANGEROUS to compare signed with unsigned types in loop +// conditions and other conditional expressions, and it is DANGEROUS to +// compute object/allocation sizes, indices, and offsets with signed types. +// Integer overflow behavior for signed types is UNDEFINED in the C/C++ +// standards, but is defined for unsigned types. +// +// Use the unsigned types if your variable represents a bit pattern (e.g. a +// hash value), object or allocation size, object count, offset, +// array/vector index, etc. +// +// Do NOT use 'unsigned' to express "this value should always be positive"; +// use assertions for this. +// +// See the Chromium style guide for more information. +// https://sites.google.com/a/chromium.org/dev/developers/coding-style + +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; + +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)); + +// Put this in the private: declarations for a class to be uncopyable. +#define DISALLOW_COPY(TypeName) \ + TypeName(const TypeName&) + +// Put this in the private: declarations for a class to be unassignable. +#define DISALLOW_ASSIGN(TypeName) \ + void operator=(const TypeName&) + +// 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 banned 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 +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 +char (&ArraySizeHelper(const T (&array)[N]))[N]; +#endif + +#define arraysize(array) (sizeof(ArraySizeHelper(array))) + +// 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(!(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 convertible 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(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 +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 +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 +// +// 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 is a template function that implements the +// equivalent of "*reinterpret_cast(&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(f); +// // i = 0x40490fdb +// +// The classical address-casting method is: +// +// // WRONG +// float f = 3.14159265358979; // WRONG +// int i = * reinterpret_cast(&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(&f). And it is particularly true for +// conversions between 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 +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 my_var = ...; +// if (TakeOwnership(my_var.get()) == SUCCESS) +// ignore_result(my_var.release()); +// +template +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/base/bind.h b/base/bind.h new file mode 100644 index 0000000000..5cf124df32 --- /dev/null +++ b/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 +base::Callback< + typename internal::BindState< + typename internal::FunctorTraits::RunnableType, + typename internal::FunctorTraits::RunType, + void()> + ::UnboundRunType> +Bind(Functor functor) { + // Typedefs for how to store and run the functor. + typedef typename internal::FunctorTraits::RunnableType RunnableType; + typedef typename internal::FunctorTraits::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 + BoundFunctorTraits; + + typedef internal::BindState BindState; + + + return Callback( + new BindState(internal::MakeRunnable(functor))); +} + +template +base::Callback< + typename internal::BindState< + typename internal::FunctorTraits::RunnableType, + typename internal::FunctorTraits::RunType, + void(typename internal::CallbackParamTraits::StorageType)> + ::UnboundRunType> +Bind(Functor functor, const P1& p1) { + // Typedefs for how to store and run the functor. + typedef typename internal::FunctorTraits::RunnableType RunnableType; + typedef typename internal::FunctorTraits::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 + 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::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::value || + !internal::NeedsScopedRefptrButGetsRawPtr::value, + p1_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::HasIsMethodTag::value || + !is_array::value, + first_bound_argument_to_method_cannot_be_array); + typedef internal::BindState::StorageType)> BindState; + + + return Callback( + new BindState(internal::MakeRunnable(functor), p1)); +} + +template +base::Callback< + typename internal::BindState< + typename internal::FunctorTraits::RunnableType, + typename internal::FunctorTraits::RunType, + void(typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::StorageType)> + ::UnboundRunType> +Bind(Functor functor, const P1& p1, const P2& p2) { + // Typedefs for how to store and run the functor. + typedef typename internal::FunctorTraits::RunnableType RunnableType; + typedef typename internal::FunctorTraits::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 + 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::value || + is_non_const_reference::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::value || + !internal::NeedsScopedRefptrButGetsRawPtr::value, + p1_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::HasIsMethodTag::value || + !is_array::value, + first_bound_argument_to_method_cannot_be_array); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr::value, + p2_is_refcounted_type_and_needs_scoped_refptr); + typedef internal::BindState::StorageType, + typename internal::CallbackParamTraits::StorageType)> BindState; + + + return Callback( + new BindState(internal::MakeRunnable(functor), p1, p2)); +} + +template +base::Callback< + typename internal::BindState< + typename internal::FunctorTraits::RunnableType, + typename internal::FunctorTraits::RunType, + void(typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::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::RunnableType RunnableType; + typedef typename internal::FunctorTraits::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 + 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::value || + is_non_const_reference::value || + is_non_const_reference::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::value || + !internal::NeedsScopedRefptrButGetsRawPtr::value, + p1_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::HasIsMethodTag::value || + !is_array::value, + first_bound_argument_to_method_cannot_be_array); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr::value, + p2_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr::value, + p3_is_refcounted_type_and_needs_scoped_refptr); + typedef internal::BindState::StorageType, + typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::StorageType)> BindState; + + + return Callback( + new BindState(internal::MakeRunnable(functor), p1, p2, p3)); +} + +template +base::Callback< + typename internal::BindState< + typename internal::FunctorTraits::RunnableType, + typename internal::FunctorTraits::RunType, + void(typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::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::RunnableType RunnableType; + typedef typename internal::FunctorTraits::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 + 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::value || + is_non_const_reference::value || + is_non_const_reference::value || + is_non_const_reference::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::value || + !internal::NeedsScopedRefptrButGetsRawPtr::value, + p1_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::HasIsMethodTag::value || + !is_array::value, + first_bound_argument_to_method_cannot_be_array); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr::value, + p2_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr::value, + p3_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr::value, + p4_is_refcounted_type_and_needs_scoped_refptr); + typedef internal::BindState::StorageType, + typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::StorageType)> BindState; + + + return Callback( + new BindState(internal::MakeRunnable(functor), p1, p2, p3, p4)); +} + +template +base::Callback< + typename internal::BindState< + typename internal::FunctorTraits::RunnableType, + typename internal::FunctorTraits::RunType, + void(typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::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::RunnableType RunnableType; + typedef typename internal::FunctorTraits::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 + 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::value || + is_non_const_reference::value || + is_non_const_reference::value || + is_non_const_reference::value || + is_non_const_reference::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::value || + !internal::NeedsScopedRefptrButGetsRawPtr::value, + p1_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::HasIsMethodTag::value || + !is_array::value, + first_bound_argument_to_method_cannot_be_array); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr::value, + p2_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr::value, + p3_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr::value, + p4_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr::value, + p5_is_refcounted_type_and_needs_scoped_refptr); + typedef internal::BindState::StorageType, + typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::StorageType)> BindState; + + + return Callback( + new BindState(internal::MakeRunnable(functor), p1, p2, p3, p4, p5)); +} + +template +base::Callback< + typename internal::BindState< + typename internal::FunctorTraits::RunnableType, + typename internal::FunctorTraits::RunType, + void(typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::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::RunnableType RunnableType; + typedef typename internal::FunctorTraits::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 + 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::value || + is_non_const_reference::value || + is_non_const_reference::value || + is_non_const_reference::value || + is_non_const_reference::value || + is_non_const_reference::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::value || + !internal::NeedsScopedRefptrButGetsRawPtr::value, + p1_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::HasIsMethodTag::value || + !is_array::value, + first_bound_argument_to_method_cannot_be_array); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr::value, + p2_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr::value, + p3_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr::value, + p4_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr::value, + p5_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr::value, + p6_is_refcounted_type_and_needs_scoped_refptr); + typedef internal::BindState::StorageType, + typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::StorageType)> BindState; + + + return Callback( + new BindState(internal::MakeRunnable(functor), p1, p2, p3, p4, p5, p6)); +} + +template +base::Callback< + typename internal::BindState< + typename internal::FunctorTraits::RunnableType, + typename internal::FunctorTraits::RunType, + void(typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::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::RunnableType RunnableType; + typedef typename internal::FunctorTraits::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 + 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::value || + is_non_const_reference::value || + is_non_const_reference::value || + is_non_const_reference::value || + is_non_const_reference::value || + is_non_const_reference::value || + is_non_const_reference::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::value || + !internal::NeedsScopedRefptrButGetsRawPtr::value, + p1_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::HasIsMethodTag::value || + !is_array::value, + first_bound_argument_to_method_cannot_be_array); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr::value, + p2_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr::value, + p3_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr::value, + p4_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr::value, + p5_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr::value, + p6_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr::value, + p7_is_refcounted_type_and_needs_scoped_refptr); + typedef internal::BindState::StorageType, + typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::StorageType, + typename internal::CallbackParamTraits::StorageType)> BindState; + + + return Callback( + new BindState(internal::MakeRunnable(functor), p1, p2, p3, p4, p5, p6, + p7)); +} + +} // namespace base + +#endif // BASE_BIND_H_ diff --git a/base/bind.h.pump b/base/bind.h.pump new file mode 100644 index 0000000000..b321649aea --- /dev/null +++ b/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 0 [[, ]] $for ARG , [[typename P$(ARG)]]> +base::Callback< + typename internal::BindState< + typename internal::FunctorTraits::RunnableType, + typename internal::FunctorTraits::RunType, + void($for ARG , [[typename internal::CallbackParamTraits::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::RunnableType RunnableType; + typedef typename internal::FunctorTraits::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 + 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::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::value || + !internal::NeedsScopedRefptrButGetsRawPtr::value, + p$(ARG)_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::HasIsMethodTag::value || + !is_array::value, + first_bound_argument_to_method_cannot_be_array); +]] $else [[ + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr::value, + p$(ARG)_is_refcounted_type_and_needs_scoped_refptr); +]] $$ $if ARG + +]] $$ $for ARG + + typedef internal::BindState::StorageType]])> [[]] +BindState; + + + return Callback( + new BindState(internal::MakeRunnable(functor)[[]] +$if ARITY > 0 [[, ]] $for ARG , [[p$(ARG)]])); +} + +]] $$ for ARITY + +} // namespace base + +#endif // BASE_BIND_H_ diff --git a/base/bind_helpers.cc b/base/bind_helpers.cc new file mode 100644 index 0000000000..f2fc3bb0da --- /dev/null +++ b/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/base/bind_helpers.h b/base/bind_helpers.h new file mode 100644 index 0000000000..0cfaab7ece --- /dev/null +++ b/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 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 arg) { } +// scoped_ptr CreateFoo() { return scoped_ptr(new Foo()); } +// +// scoped_ptr 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() - 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 +// Yes NotTheCheckWeWant(Helper<&C::TargetFunc>*); +// +// template +// No NotTheCheckWeWant(...); +// +// static const bool value = sizeof(NotTheCheckWeWant(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 +// No GoodCheck(Helper<&C::TargetFunc>*); +// +// template +// Yes GoodCheck(...); +// +// static const bool value = sizeof(GoodCheck(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(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 +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) +#pragma warning(push) +#pragma warning(disable:4624) +#endif + struct Base : public T, public BaseMixin { + }; +#if defined(OS_WIN) +#pragma warning(pop) +#endif + + template struct Helper {}; + + template + static No& Check(Helper<&C::AddRef>*); + + template + static Yes& Check(...); + + public: + static const bool value = sizeof(Check(0)) == sizeof(Yes); +}; + +// Helpers to assert that arguments of a recounted type are bound with a +// scoped_refptr. +template +struct UnsafeBindtoRefCountedArgHelper : false_type { +}; + +template +struct UnsafeBindtoRefCountedArgHelper + : integral_constant::value> { +}; + +template +struct UnsafeBindtoRefCountedArg : false_type { +}; + +template +struct UnsafeBindtoRefCountedArg + : UnsafeBindtoRefCountedArgHelper::value, T> { +}; + +template +class HasIsMethodTag { + typedef char Yes[1]; + typedef char No[2]; + + template + static Yes& Check(typename U::IsMethod*); + + template + static No& Check(...); + + public: + static const bool value = sizeof(Check(0)) == sizeof(Yes); +}; + +template +class UnretainedWrapper { + public: + explicit UnretainedWrapper(T* o) : ptr_(o) {} + T* get() const { return ptr_; } + private: + T* ptr_; +}; + +template +class ConstRefWrapper { + public: + explicit ConstRefWrapper(const T& o) : ptr_(&o) {} + const T& get() const { return *ptr_; } + private: + const T* ptr_; +}; + +template +struct IgnoreResultHelper { + explicit IgnoreResultHelper(T functor) : functor_(functor) {} + + T functor_; +}; + +template +struct IgnoreResultHelper > { + explicit IgnoreResultHelper(const Callback& functor) : functor_(functor) {} + + const Callback& 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 +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 +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 +struct UnwrapTraits { + typedef const T& ForwardType; + static ForwardType Unwrap(const T& o) { return o; } +}; + +template +struct UnwrapTraits > { + typedef T* ForwardType; + static ForwardType Unwrap(UnretainedWrapper unretained) { + return unretained.get(); + } +}; + +template +struct UnwrapTraits > { + typedef const T& ForwardType; + static ForwardType Unwrap(ConstRefWrapper const_ref) { + return const_ref.get(); + } +}; + +template +struct UnwrapTraits > { + typedef T* ForwardType; + static ForwardType Unwrap(const scoped_refptr& o) { return o.get(); } +}; + +template +struct UnwrapTraits > { + typedef const WeakPtr& ForwardType; + static ForwardType Unwrap(const WeakPtr& o) { return o; } +}; + +template +struct UnwrapTraits > { + typedef T* ForwardType; + static ForwardType Unwrap(const OwnedWrapper& o) { + return o.get(); + } +}; + +template +struct UnwrapTraits > { + typedef T ForwardType; + static T Unwrap(PassedWrapper& o) { + return o.Pass(); + } +}; + +// Utility for handling different refcounting semantics in the Bind() +// function. +template +struct MaybeRefcount; + +template +struct MaybeRefcount { + static void AddRef(const T&) {} + static void Release(const T&) {} +}; + +template +struct MaybeRefcount { + static void AddRef(const T*) {} + static void Release(const T*) {} +}; + +template +struct MaybeRefcount { + static void AddRef(const T&) {} + static void Release(const T&) {} +}; + +template +struct MaybeRefcount { + 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 +struct MaybeRefcount > { + static void AddRef(const scoped_refptr& o) {} + static void Release(const scoped_refptr& o) {} +}; + +template +struct MaybeRefcount { + 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 +struct IsWeakMethod : public false_type {}; + +template +struct IsWeakMethod > : public true_type {}; + +template +struct IsWeakMethod > > : public true_type {}; + +} // namespace internal + +template +static inline internal::UnretainedWrapper Unretained(T* o) { + return internal::UnretainedWrapper(o); +} + +template +static inline internal::ConstRefWrapper ConstRef(const T& o) { + return internal::ConstRefWrapper(o); +} + +template +static inline internal::OwnedWrapper Owned(T* o) { + return internal::OwnedWrapper(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 +static inline internal::PassedWrapper Passed(T scoper) { + return internal::PassedWrapper(scoper.Pass()); +} +template +static inline internal::PassedWrapper Passed(T* scoper) { + return internal::PassedWrapper(scoper->Pass()); +} + +template +static inline internal::IgnoreResultHelper IgnoreResult(T data) { + return internal::IgnoreResultHelper(data); +} + +template +static inline internal::IgnoreResultHelper > +IgnoreResult(const Callback& data) { + return internal::IgnoreResultHelper >(data); +} + +BASE_EXPORT void DoNothing(); + +template +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/base/bind_helpers_unittest.cc b/base/bind_helpers_unittest.cc new file mode 100644 index 0000000000..3ef2d75435 --- /dev/null +++ b/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/base/bind_internal.h b/base/bind_internal.h new file mode 100644 index 0000000000..ae17ebf86c --- /dev/null +++ b/base/bind_internal.h @@ -0,0 +1,2789 @@ +// 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(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 +class RunnableAdapter; + +// Function: Arity 0. +template +class RunnableAdapter { + public: + typedef R (RunType)(); + + explicit RunnableAdapter(R(*function)()) + : function_(function) { + } + + R Run() { + return function_(); + } + + private: + R (*function_)(); +}; + +// Method: Arity 0. +template +class RunnableAdapter { + 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 +class RunnableAdapter { + 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 +class RunnableAdapter { + public: + typedef R (RunType)(A1); + + explicit RunnableAdapter(R(*function)(A1)) + : function_(function) { + } + + R Run(typename CallbackParamTraits::ForwardType a1) { + return function_(CallbackForward(a1)); + } + + private: + R (*function_)(A1); +}; + +// Method: Arity 1. +template +class RunnableAdapter { + public: + typedef R (RunType)(T*, A1); + typedef true_type IsMethod; + + explicit RunnableAdapter(R(T::*method)(A1)) + : method_(method) { + } + + R Run(T* object, typename CallbackParamTraits::ForwardType a1) { + return (object->*method_)(CallbackForward(a1)); + } + + private: + R (T::*method_)(A1); +}; + +// Const Method: Arity 1. +template +class RunnableAdapter { + 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::ForwardType a1) { + return (object->*method_)(CallbackForward(a1)); + } + + private: + R (T::*method_)(A1) const; +}; + +// Function: Arity 2. +template +class RunnableAdapter { + public: + typedef R (RunType)(A1, A2); + + explicit RunnableAdapter(R(*function)(A1, A2)) + : function_(function) { + } + + R Run(typename CallbackParamTraits::ForwardType a1, + typename CallbackParamTraits::ForwardType a2) { + return function_(CallbackForward(a1), CallbackForward(a2)); + } + + private: + R (*function_)(A1, A2); +}; + +// Method: Arity 2. +template +class RunnableAdapter { + 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::ForwardType a1, + typename CallbackParamTraits::ForwardType a2) { + return (object->*method_)(CallbackForward(a1), CallbackForward(a2)); + } + + private: + R (T::*method_)(A1, A2); +}; + +// Const Method: Arity 2. +template +class RunnableAdapter { + 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::ForwardType a1, + typename CallbackParamTraits::ForwardType a2) { + return (object->*method_)(CallbackForward(a1), CallbackForward(a2)); + } + + private: + R (T::*method_)(A1, A2) const; +}; + +// Function: Arity 3. +template +class RunnableAdapter { + public: + typedef R (RunType)(A1, A2, A3); + + explicit RunnableAdapter(R(*function)(A1, A2, A3)) + : function_(function) { + } + + R Run(typename CallbackParamTraits::ForwardType a1, + typename CallbackParamTraits::ForwardType a2, + typename CallbackParamTraits::ForwardType a3) { + return function_(CallbackForward(a1), CallbackForward(a2), + CallbackForward(a3)); + } + + private: + R (*function_)(A1, A2, A3); +}; + +// Method: Arity 3. +template +class RunnableAdapter { + 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::ForwardType a1, + typename CallbackParamTraits::ForwardType a2, + typename CallbackParamTraits::ForwardType a3) { + return (object->*method_)(CallbackForward(a1), CallbackForward(a2), + CallbackForward(a3)); + } + + private: + R (T::*method_)(A1, A2, A3); +}; + +// Const Method: Arity 3. +template +class RunnableAdapter { + 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::ForwardType a1, + typename CallbackParamTraits::ForwardType a2, + typename CallbackParamTraits::ForwardType a3) { + return (object->*method_)(CallbackForward(a1), CallbackForward(a2), + CallbackForward(a3)); + } + + private: + R (T::*method_)(A1, A2, A3) const; +}; + +// Function: Arity 4. +template +class RunnableAdapter { + public: + typedef R (RunType)(A1, A2, A3, A4); + + explicit RunnableAdapter(R(*function)(A1, A2, A3, A4)) + : function_(function) { + } + + R Run(typename CallbackParamTraits::ForwardType a1, + typename CallbackParamTraits::ForwardType a2, + typename CallbackParamTraits::ForwardType a3, + typename CallbackParamTraits::ForwardType a4) { + return function_(CallbackForward(a1), CallbackForward(a2), + CallbackForward(a3), CallbackForward(a4)); + } + + private: + R (*function_)(A1, A2, A3, A4); +}; + +// Method: Arity 4. +template +class RunnableAdapter { + 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::ForwardType a1, + typename CallbackParamTraits::ForwardType a2, + typename CallbackParamTraits::ForwardType a3, + typename CallbackParamTraits::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 +class RunnableAdapter { + 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::ForwardType a1, + typename CallbackParamTraits::ForwardType a2, + typename CallbackParamTraits::ForwardType a3, + typename CallbackParamTraits::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 +class RunnableAdapter { + public: + typedef R (RunType)(A1, A2, A3, A4, A5); + + explicit RunnableAdapter(R(*function)(A1, A2, A3, A4, A5)) + : function_(function) { + } + + R Run(typename CallbackParamTraits::ForwardType a1, + typename CallbackParamTraits::ForwardType a2, + typename CallbackParamTraits::ForwardType a3, + typename CallbackParamTraits::ForwardType a4, + typename CallbackParamTraits::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 +class RunnableAdapter { + 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::ForwardType a1, + typename CallbackParamTraits::ForwardType a2, + typename CallbackParamTraits::ForwardType a3, + typename CallbackParamTraits::ForwardType a4, + typename CallbackParamTraits::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 +class RunnableAdapter { + 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::ForwardType a1, + typename CallbackParamTraits::ForwardType a2, + typename CallbackParamTraits::ForwardType a3, + typename CallbackParamTraits::ForwardType a4, + typename CallbackParamTraits::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 +class RunnableAdapter { + 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::ForwardType a1, + typename CallbackParamTraits::ForwardType a2, + typename CallbackParamTraits::ForwardType a3, + typename CallbackParamTraits::ForwardType a4, + typename CallbackParamTraits::ForwardType a5, + typename CallbackParamTraits::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 +class RunnableAdapter { + 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::ForwardType a1, + typename CallbackParamTraits::ForwardType a2, + typename CallbackParamTraits::ForwardType a3, + typename CallbackParamTraits::ForwardType a4, + typename CallbackParamTraits::ForwardType a5, + typename CallbackParamTraits::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 +class RunnableAdapter { + 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::ForwardType a1, + typename CallbackParamTraits::ForwardType a2, + typename CallbackParamTraits::ForwardType a3, + typename CallbackParamTraits::ForwardType a4, + typename CallbackParamTraits::ForwardType a5, + typename CallbackParamTraits::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 +class RunnableAdapter { + 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::ForwardType a1, + typename CallbackParamTraits::ForwardType a2, + typename CallbackParamTraits::ForwardType a3, + typename CallbackParamTraits::ForwardType a4, + typename CallbackParamTraits::ForwardType a5, + typename CallbackParamTraits::ForwardType a6, + typename CallbackParamTraits::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 +class RunnableAdapter { + 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::ForwardType a1, + typename CallbackParamTraits::ForwardType a2, + typename CallbackParamTraits::ForwardType a3, + typename CallbackParamTraits::ForwardType a4, + typename CallbackParamTraits::ForwardType a5, + typename CallbackParamTraits::ForwardType a6, + typename CallbackParamTraits::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 +class RunnableAdapter { + 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::ForwardType a1, + typename CallbackParamTraits::ForwardType a2, + typename CallbackParamTraits::ForwardType a3, + typename CallbackParamTraits::ForwardType a4, + typename CallbackParamTraits::ForwardType a5, + typename CallbackParamTraits::ForwardType a6, + typename CallbackParamTraits::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 +struct FunctionTraits; + +template +struct FunctionTraits { + typedef R ReturnType; +}; + +template +struct FunctionTraits { + typedef R ReturnType; + typedef A1 A1Type; +}; + +template +struct FunctionTraits { + typedef R ReturnType; + typedef A1 A1Type; + typedef A2 A2Type; +}; + +template +struct FunctionTraits { + typedef R ReturnType; + typedef A1 A1Type; + typedef A2 A2Type; + typedef A3 A3Type; +}; + +template +struct FunctionTraits { + typedef R ReturnType; + typedef A1 A1Type; + typedef A2 A2Type; + typedef A3 A3Type; + typedef A4 A4Type; +}; + +template +struct FunctionTraits { + typedef R ReturnType; + typedef A1 A1Type; + typedef A2 A2Type; + typedef A3 A3Type; + typedef A4 A4Type; + typedef A5 A5Type; +}; + +template +struct FunctionTraits { + typedef R ReturnType; + typedef A1 A1Type; + typedef A2 A2Type; + typedef A3 A3Type; + typedef A4 A4Type; + typedef A5 A5Type; + typedef A6 A6Type; +}; + +template +struct FunctionTraits { + 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 +struct ForceVoidReturn; + +template +struct ForceVoidReturn { + typedef void(RunType)(); +}; + +template +struct ForceVoidReturn { + typedef void(RunType)(A1); +}; + +template +struct ForceVoidReturn { + typedef void(RunType)(A1, A2); +}; + +template +struct ForceVoidReturn { + typedef void(RunType)(A1, A2, A3); +}; + +template +struct ForceVoidReturn { + typedef void(RunType)(A1, A2, A3, A4); +}; + +template +struct ForceVoidReturn { + typedef void(RunType)(A1, A2, A3, A4, A5); +}; + +template +struct ForceVoidReturn { + typedef void(RunType)(A1, A2, A3, A4, A5, A6); +}; + +template +struct ForceVoidReturn { + typedef void(RunType)(A1, A2, A3, A4, A5, A6, A7); +}; + + +// FunctorTraits<> +// +// See description at top of file. +template +struct FunctorTraits { + typedef RunnableAdapter RunnableType; + typedef typename RunnableType::RunType RunType; +}; + +template +struct FunctorTraits > { + typedef typename FunctorTraits::RunnableType RunnableType; + typedef typename ForceVoidReturn< + typename RunnableType::RunType>::RunType RunType; +}; + +template +struct FunctorTraits > { + typedef Callback RunnableType; + typedef typename Callback::RunType RunType; +}; + + +// MakeRunnable<> +// +// Converts a passed in functor to a RunnableType using type inference. + +template +typename FunctorTraits::RunnableType MakeRunnable(const T& t) { + return RunnableAdapter(t); +} + +template +typename FunctorTraits::RunnableType +MakeRunnable(const IgnoreResultHelper& t) { + return MakeRunnable(t.functor_); +} + +template +const typename FunctorTraits >::RunnableType& +MakeRunnable(const Callback& 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 +struct InvokeHelper; + +template +struct InvokeHelper { + static ReturnType MakeItSo(Runnable runnable) { + return runnable.Run(); + } +}; + +template +struct InvokeHelper { + static void MakeItSo(Runnable runnable) { + runnable.Run(); + } +}; + +template +struct InvokeHelper { + static ReturnType MakeItSo(Runnable runnable, A1 a1) { + return runnable.Run(CallbackForward(a1)); + } +}; + +template +struct InvokeHelper { + static void MakeItSo(Runnable runnable, A1 a1) { + runnable.Run(CallbackForward(a1)); + } +}; + +template +struct InvokeHelper { + static void MakeItSo(Runnable runnable, BoundWeakPtr weak_ptr) { + if (!weak_ptr.get()) { + return; + } + runnable.Run(weak_ptr.get()); + } +}; + +template +struct InvokeHelper { + static ReturnType MakeItSo(Runnable runnable, A1 a1, A2 a2) { + return runnable.Run(CallbackForward(a1), CallbackForward(a2)); + } +}; + +template +struct InvokeHelper { + static void MakeItSo(Runnable runnable, A1 a1, A2 a2) { + runnable.Run(CallbackForward(a1), CallbackForward(a2)); + } +}; + +template +struct InvokeHelper { + static void MakeItSo(Runnable runnable, BoundWeakPtr weak_ptr, A2 a2) { + if (!weak_ptr.get()) { + return; + } + runnable.Run(weak_ptr.get(), CallbackForward(a2)); + } +}; + +template +struct InvokeHelper { + static ReturnType MakeItSo(Runnable runnable, A1 a1, A2 a2, A3 a3) { + return runnable.Run(CallbackForward(a1), CallbackForward(a2), + CallbackForward(a3)); + } +}; + +template +struct InvokeHelper { + static void MakeItSo(Runnable runnable, A1 a1, A2 a2, A3 a3) { + runnable.Run(CallbackForward(a1), CallbackForward(a2), CallbackForward(a3)); + } +}; + +template +struct InvokeHelper { + static void MakeItSo(Runnable runnable, BoundWeakPtr weak_ptr, A2 a2, A3 a3) { + if (!weak_ptr.get()) { + return; + } + runnable.Run(weak_ptr.get(), CallbackForward(a2), CallbackForward(a3)); + } +}; + +template +struct InvokeHelper { + 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 +struct InvokeHelper { + static void MakeItSo(Runnable runnable, A1 a1, A2 a2, A3 a3, A4 a4) { + runnable.Run(CallbackForward(a1), CallbackForward(a2), CallbackForward(a3), + CallbackForward(a4)); + } +}; + +template +struct InvokeHelper { + static void MakeItSo(Runnable runnable, BoundWeakPtr weak_ptr, A2 a2, A3 a3, + A4 a4) { + if (!weak_ptr.get()) { + return; + } + runnable.Run(weak_ptr.get(), CallbackForward(a2), CallbackForward(a3), + CallbackForward(a4)); + } +}; + +template +struct InvokeHelper { + 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 +struct InvokeHelper { + 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 +struct InvokeHelper { + static void MakeItSo(Runnable runnable, BoundWeakPtr weak_ptr, A2 a2, A3 a3, + A4 a4, A5 a5) { + if (!weak_ptr.get()) { + return; + } + runnable.Run(weak_ptr.get(), CallbackForward(a2), CallbackForward(a3), + CallbackForward(a4), CallbackForward(a5)); + } +}; + +template +struct InvokeHelper { + 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 +struct InvokeHelper { + 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 +struct InvokeHelper { + static void MakeItSo(Runnable runnable, BoundWeakPtr weak_ptr, A2 a2, A3 a3, + A4 a4, A5 a5, A6 a6) { + if (!weak_ptr.get()) { + return; + } + runnable.Run(weak_ptr.get(), CallbackForward(a2), CallbackForward(a3), + CallbackForward(a4), CallbackForward(a5), CallbackForward(a6)); + } +}; + +template +struct InvokeHelper { + 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 +struct InvokeHelper { + 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 +struct InvokeHelper { + static void MakeItSo(Runnable runnable, BoundWeakPtr weak_ptr, A2 a2, A3 a3, + A4 a4, A5 a5, A6 a6, A7 a7) { + if (!weak_ptr.get()) { + return; + } + runnable.Run(weak_ptr.get(), CallbackForward(a2), CallbackForward(a3), + CallbackForward(a4), CallbackForward(a5), CallbackForward(a6), + CallbackForward(a7)); + } +}; + +#if !defined(_MSC_VER) + +template +struct InvokeHelper { + // 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::value, + weak_ptrs_can_only_bind_to_methods_without_return_values); +}; + +#endif + +// Invoker<> +// +// See description at the top of the file. +template +struct Invoker; + +// Arity 0 -> 0. +template +struct Invoker<0, StorageType, R()> { + typedef R(RunType)(BindStateBase*); + + typedef R(UnboundRunType)(); + + static R Run(BindStateBase* base) { + StorageType* storage = static_cast(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 + ::MakeItSo(storage->runnable_); + } +}; + +// Arity 1 -> 1. +template +struct Invoker<0, StorageType, R(X1)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits::ForwardType); + + typedef R(UnboundRunType)(X1); + + static R Run(BindStateBase* base, + typename CallbackParamTraits::ForwardType x1) { + StorageType* storage = static_cast(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::ForwardType x1)> + ::MakeItSo(storage->runnable_, CallbackForward(x1)); + } +}; + +// Arity 1 -> 0. +template +struct Invoker<1, StorageType, R(X1)> { + typedef R(RunType)(BindStateBase*); + + typedef R(UnboundRunType)(); + + static R Run(BindStateBase* base) { + StorageType* storage = static_cast(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 + ::MakeItSo(storage->runnable_, CallbackForward(x1)); + } +}; + +// Arity 2 -> 2. +template +struct Invoker<0, StorageType, R(X1, X2)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType); + + typedef R(UnboundRunType)(X1, X2); + + static R Run(BindStateBase* base, + typename CallbackParamTraits::ForwardType x1, + typename CallbackParamTraits::ForwardType x2) { + StorageType* storage = static_cast(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::ForwardType x1, + typename CallbackParamTraits::ForwardType x2)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2)); + } +}; + +// Arity 2 -> 1. +template +struct Invoker<1, StorageType, R(X1, X2)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits::ForwardType); + + typedef R(UnboundRunType)(X2); + + static R Run(BindStateBase* base, + typename CallbackParamTraits::ForwardType x2) { + StorageType* storage = static_cast(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::ForwardType x2)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2)); + } +}; + +// Arity 2 -> 0. +template +struct Invoker<2, StorageType, R(X1, X2)> { + typedef R(RunType)(BindStateBase*); + + typedef R(UnboundRunType)(); + + static R Run(BindStateBase* base) { + StorageType* storage = static_cast(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 + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2)); + } +}; + +// Arity 3 -> 3. +template +struct Invoker<0, StorageType, R(X1, X2, X3)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType); + + typedef R(UnboundRunType)(X1, X2, X3); + + static R Run(BindStateBase* base, + typename CallbackParamTraits::ForwardType x1, + typename CallbackParamTraits::ForwardType x2, + typename CallbackParamTraits::ForwardType x3) { + StorageType* storage = static_cast(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::ForwardType x1, + typename CallbackParamTraits::ForwardType x2, + typename CallbackParamTraits::ForwardType x3)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3)); + } +}; + +// Arity 3 -> 2. +template +struct Invoker<1, StorageType, R(X1, X2, X3)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType); + + typedef R(UnboundRunType)(X2, X3); + + static R Run(BindStateBase* base, + typename CallbackParamTraits::ForwardType x2, + typename CallbackParamTraits::ForwardType x3) { + StorageType* storage = static_cast(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::ForwardType x2, + typename CallbackParamTraits::ForwardType x3)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3)); + } +}; + +// Arity 3 -> 1. +template +struct Invoker<2, StorageType, R(X1, X2, X3)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits::ForwardType); + + typedef R(UnboundRunType)(X3); + + static R Run(BindStateBase* base, + typename CallbackParamTraits::ForwardType x3) { + StorageType* storage = static_cast(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::ForwardType x3)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3)); + } +}; + +// Arity 3 -> 0. +template +struct Invoker<3, StorageType, R(X1, X2, X3)> { + typedef R(RunType)(BindStateBase*); + + typedef R(UnboundRunType)(); + + static R Run(BindStateBase* base) { + StorageType* storage = static_cast(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 + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3)); + } +}; + +// Arity 4 -> 4. +template +struct Invoker<0, StorageType, R(X1, X2, X3, X4)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType); + + typedef R(UnboundRunType)(X1, X2, X3, X4); + + static R Run(BindStateBase* base, + typename CallbackParamTraits::ForwardType x1, + typename CallbackParamTraits::ForwardType x2, + typename CallbackParamTraits::ForwardType x3, + typename CallbackParamTraits::ForwardType x4) { + StorageType* storage = static_cast(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::ForwardType x1, + typename CallbackParamTraits::ForwardType x2, + typename CallbackParamTraits::ForwardType x3, + typename CallbackParamTraits::ForwardType x4)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4)); + } +}; + +// Arity 4 -> 3. +template +struct Invoker<1, StorageType, R(X1, X2, X3, X4)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType); + + typedef R(UnboundRunType)(X2, X3, X4); + + static R Run(BindStateBase* base, + typename CallbackParamTraits::ForwardType x2, + typename CallbackParamTraits::ForwardType x3, + typename CallbackParamTraits::ForwardType x4) { + StorageType* storage = static_cast(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::ForwardType x2, + typename CallbackParamTraits::ForwardType x3, + typename CallbackParamTraits::ForwardType x4)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4)); + } +}; + +// Arity 4 -> 2. +template +struct Invoker<2, StorageType, R(X1, X2, X3, X4)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType); + + typedef R(UnboundRunType)(X3, X4); + + static R Run(BindStateBase* base, + typename CallbackParamTraits::ForwardType x3, + typename CallbackParamTraits::ForwardType x4) { + StorageType* storage = static_cast(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::ForwardType x3, + typename CallbackParamTraits::ForwardType x4)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4)); + } +}; + +// Arity 4 -> 1. +template +struct Invoker<3, StorageType, R(X1, X2, X3, X4)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits::ForwardType); + + typedef R(UnboundRunType)(X4); + + static R Run(BindStateBase* base, + typename CallbackParamTraits::ForwardType x4) { + StorageType* storage = static_cast(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::ForwardType x4)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4)); + } +}; + +// Arity 4 -> 0. +template +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(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 + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4)); + } +}; + +// Arity 5 -> 5. +template +struct Invoker<0, StorageType, R(X1, X2, X3, X4, X5)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType); + + typedef R(UnboundRunType)(X1, X2, X3, X4, X5); + + static R Run(BindStateBase* base, + typename CallbackParamTraits::ForwardType x1, + typename CallbackParamTraits::ForwardType x2, + typename CallbackParamTraits::ForwardType x3, + typename CallbackParamTraits::ForwardType x4, + typename CallbackParamTraits::ForwardType x5) { + StorageType* storage = static_cast(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::ForwardType x1, + typename CallbackParamTraits::ForwardType x2, + typename CallbackParamTraits::ForwardType x3, + typename CallbackParamTraits::ForwardType x4, + typename CallbackParamTraits::ForwardType x5)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5)); + } +}; + +// Arity 5 -> 4. +template +struct Invoker<1, StorageType, R(X1, X2, X3, X4, X5)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType); + + typedef R(UnboundRunType)(X2, X3, X4, X5); + + static R Run(BindStateBase* base, + typename CallbackParamTraits::ForwardType x2, + typename CallbackParamTraits::ForwardType x3, + typename CallbackParamTraits::ForwardType x4, + typename CallbackParamTraits::ForwardType x5) { + StorageType* storage = static_cast(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::ForwardType x2, + typename CallbackParamTraits::ForwardType x3, + typename CallbackParamTraits::ForwardType x4, + typename CallbackParamTraits::ForwardType x5)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5)); + } +}; + +// Arity 5 -> 3. +template +struct Invoker<2, StorageType, R(X1, X2, X3, X4, X5)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType); + + typedef R(UnboundRunType)(X3, X4, X5); + + static R Run(BindStateBase* base, + typename CallbackParamTraits::ForwardType x3, + typename CallbackParamTraits::ForwardType x4, + typename CallbackParamTraits::ForwardType x5) { + StorageType* storage = static_cast(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::ForwardType x3, + typename CallbackParamTraits::ForwardType x4, + typename CallbackParamTraits::ForwardType x5)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5)); + } +}; + +// Arity 5 -> 2. +template +struct Invoker<3, StorageType, R(X1, X2, X3, X4, X5)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType); + + typedef R(UnboundRunType)(X4, X5); + + static R Run(BindStateBase* base, + typename CallbackParamTraits::ForwardType x4, + typename CallbackParamTraits::ForwardType x5) { + StorageType* storage = static_cast(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::ForwardType x4, + typename CallbackParamTraits::ForwardType x5)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5)); + } +}; + +// Arity 5 -> 1. +template +struct Invoker<4, StorageType, R(X1, X2, X3, X4, X5)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits::ForwardType); + + typedef R(UnboundRunType)(X5); + + static R Run(BindStateBase* base, + typename CallbackParamTraits::ForwardType x5) { + StorageType* storage = static_cast(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::ForwardType x5)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5)); + } +}; + +// Arity 5 -> 0. +template +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(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 + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5)); + } +}; + +// Arity 6 -> 6. +template +struct Invoker<0, StorageType, R(X1, X2, X3, X4, X5, X6)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType); + + typedef R(UnboundRunType)(X1, X2, X3, X4, X5, X6); + + static R Run(BindStateBase* base, + typename CallbackParamTraits::ForwardType x1, + typename CallbackParamTraits::ForwardType x2, + typename CallbackParamTraits::ForwardType x3, + typename CallbackParamTraits::ForwardType x4, + typename CallbackParamTraits::ForwardType x5, + typename CallbackParamTraits::ForwardType x6) { + StorageType* storage = static_cast(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::ForwardType x1, + typename CallbackParamTraits::ForwardType x2, + typename CallbackParamTraits::ForwardType x3, + typename CallbackParamTraits::ForwardType x4, + typename CallbackParamTraits::ForwardType x5, + typename CallbackParamTraits::ForwardType x6)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5), + CallbackForward(x6)); + } +}; + +// Arity 6 -> 5. +template +struct Invoker<1, StorageType, R(X1, X2, X3, X4, X5, X6)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType); + + typedef R(UnboundRunType)(X2, X3, X4, X5, X6); + + static R Run(BindStateBase* base, + typename CallbackParamTraits::ForwardType x2, + typename CallbackParamTraits::ForwardType x3, + typename CallbackParamTraits::ForwardType x4, + typename CallbackParamTraits::ForwardType x5, + typename CallbackParamTraits::ForwardType x6) { + StorageType* storage = static_cast(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::ForwardType x2, + typename CallbackParamTraits::ForwardType x3, + typename CallbackParamTraits::ForwardType x4, + typename CallbackParamTraits::ForwardType x5, + typename CallbackParamTraits::ForwardType x6)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5), + CallbackForward(x6)); + } +}; + +// Arity 6 -> 4. +template +struct Invoker<2, StorageType, R(X1, X2, X3, X4, X5, X6)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType); + + typedef R(UnboundRunType)(X3, X4, X5, X6); + + static R Run(BindStateBase* base, + typename CallbackParamTraits::ForwardType x3, + typename CallbackParamTraits::ForwardType x4, + typename CallbackParamTraits::ForwardType x5, + typename CallbackParamTraits::ForwardType x6) { + StorageType* storage = static_cast(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::ForwardType x3, + typename CallbackParamTraits::ForwardType x4, + typename CallbackParamTraits::ForwardType x5, + typename CallbackParamTraits::ForwardType x6)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5), + CallbackForward(x6)); + } +}; + +// Arity 6 -> 3. +template +struct Invoker<3, StorageType, R(X1, X2, X3, X4, X5, X6)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType); + + typedef R(UnboundRunType)(X4, X5, X6); + + static R Run(BindStateBase* base, + typename CallbackParamTraits::ForwardType x4, + typename CallbackParamTraits::ForwardType x5, + typename CallbackParamTraits::ForwardType x6) { + StorageType* storage = static_cast(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::ForwardType x4, + typename CallbackParamTraits::ForwardType x5, + typename CallbackParamTraits::ForwardType x6)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5), + CallbackForward(x6)); + } +}; + +// Arity 6 -> 2. +template +struct Invoker<4, StorageType, R(X1, X2, X3, X4, X5, X6)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType); + + typedef R(UnboundRunType)(X5, X6); + + static R Run(BindStateBase* base, + typename CallbackParamTraits::ForwardType x5, + typename CallbackParamTraits::ForwardType x6) { + StorageType* storage = static_cast(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::ForwardType x5, + typename CallbackParamTraits::ForwardType x6)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5), + CallbackForward(x6)); + } +}; + +// Arity 6 -> 1. +template +struct Invoker<5, StorageType, R(X1, X2, X3, X4, X5, X6)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits::ForwardType); + + typedef R(UnboundRunType)(X6); + + static R Run(BindStateBase* base, + typename CallbackParamTraits::ForwardType x6) { + StorageType* storage = static_cast(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::ForwardType x6)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5), + CallbackForward(x6)); + } +}; + +// Arity 6 -> 0. +template +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(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 + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5), + CallbackForward(x6)); + } +}; + +// Arity 7 -> 7. +template +struct Invoker<0, StorageType, R(X1, X2, X3, X4, X5, X6, X7)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType); + + typedef R(UnboundRunType)(X1, X2, X3, X4, X5, X6, X7); + + static R Run(BindStateBase* base, + typename CallbackParamTraits::ForwardType x1, + typename CallbackParamTraits::ForwardType x2, + typename CallbackParamTraits::ForwardType x3, + typename CallbackParamTraits::ForwardType x4, + typename CallbackParamTraits::ForwardType x5, + typename CallbackParamTraits::ForwardType x6, + typename CallbackParamTraits::ForwardType x7) { + StorageType* storage = static_cast(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::ForwardType x1, + typename CallbackParamTraits::ForwardType x2, + typename CallbackParamTraits::ForwardType x3, + typename CallbackParamTraits::ForwardType x4, + typename CallbackParamTraits::ForwardType x5, + typename CallbackParamTraits::ForwardType x6, + typename CallbackParamTraits::ForwardType x7)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5), + CallbackForward(x6), CallbackForward(x7)); + } +}; + +// Arity 7 -> 6. +template +struct Invoker<1, StorageType, R(X1, X2, X3, X4, X5, X6, X7)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType); + + typedef R(UnboundRunType)(X2, X3, X4, X5, X6, X7); + + static R Run(BindStateBase* base, + typename CallbackParamTraits::ForwardType x2, + typename CallbackParamTraits::ForwardType x3, + typename CallbackParamTraits::ForwardType x4, + typename CallbackParamTraits::ForwardType x5, + typename CallbackParamTraits::ForwardType x6, + typename CallbackParamTraits::ForwardType x7) { + StorageType* storage = static_cast(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::ForwardType x2, + typename CallbackParamTraits::ForwardType x3, + typename CallbackParamTraits::ForwardType x4, + typename CallbackParamTraits::ForwardType x5, + typename CallbackParamTraits::ForwardType x6, + typename CallbackParamTraits::ForwardType x7)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5), + CallbackForward(x6), CallbackForward(x7)); + } +}; + +// Arity 7 -> 5. +template +struct Invoker<2, StorageType, R(X1, X2, X3, X4, X5, X6, X7)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType); + + typedef R(UnboundRunType)(X3, X4, X5, X6, X7); + + static R Run(BindStateBase* base, + typename CallbackParamTraits::ForwardType x3, + typename CallbackParamTraits::ForwardType x4, + typename CallbackParamTraits::ForwardType x5, + typename CallbackParamTraits::ForwardType x6, + typename CallbackParamTraits::ForwardType x7) { + StorageType* storage = static_cast(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::ForwardType x3, + typename CallbackParamTraits::ForwardType x4, + typename CallbackParamTraits::ForwardType x5, + typename CallbackParamTraits::ForwardType x6, + typename CallbackParamTraits::ForwardType x7)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5), + CallbackForward(x6), CallbackForward(x7)); + } +}; + +// Arity 7 -> 4. +template +struct Invoker<3, StorageType, R(X1, X2, X3, X4, X5, X6, X7)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType); + + typedef R(UnboundRunType)(X4, X5, X6, X7); + + static R Run(BindStateBase* base, + typename CallbackParamTraits::ForwardType x4, + typename CallbackParamTraits::ForwardType x5, + typename CallbackParamTraits::ForwardType x6, + typename CallbackParamTraits::ForwardType x7) { + StorageType* storage = static_cast(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::ForwardType x4, + typename CallbackParamTraits::ForwardType x5, + typename CallbackParamTraits::ForwardType x6, + typename CallbackParamTraits::ForwardType x7)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5), + CallbackForward(x6), CallbackForward(x7)); + } +}; + +// Arity 7 -> 3. +template +struct Invoker<4, StorageType, R(X1, X2, X3, X4, X5, X6, X7)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType); + + typedef R(UnboundRunType)(X5, X6, X7); + + static R Run(BindStateBase* base, + typename CallbackParamTraits::ForwardType x5, + typename CallbackParamTraits::ForwardType x6, + typename CallbackParamTraits::ForwardType x7) { + StorageType* storage = static_cast(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::ForwardType x5, + typename CallbackParamTraits::ForwardType x6, + typename CallbackParamTraits::ForwardType x7)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5), + CallbackForward(x6), CallbackForward(x7)); + } +}; + +// Arity 7 -> 2. +template +struct Invoker<5, StorageType, R(X1, X2, X3, X4, X5, X6, X7)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits::ForwardType, + typename CallbackParamTraits::ForwardType); + + typedef R(UnboundRunType)(X6, X7); + + static R Run(BindStateBase* base, + typename CallbackParamTraits::ForwardType x6, + typename CallbackParamTraits::ForwardType x7) { + StorageType* storage = static_cast(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::ForwardType x6, + typename CallbackParamTraits::ForwardType x7)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5), + CallbackForward(x6), CallbackForward(x7)); + } +}; + +// Arity 7 -> 1. +template +struct Invoker<6, StorageType, R(X1, X2, X3, X4, X5, X6, X7)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits::ForwardType); + + typedef R(UnboundRunType)(X7); + + static R Run(BindStateBase* base, + typename CallbackParamTraits::ForwardType x7) { + StorageType* storage = static_cast(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::ForwardType x7)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5), + CallbackForward(x6), CallbackForward(x7)); + } +}; + +// Arity 7 -> 0. +template +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(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 + ::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 +struct BindState; + +template +struct BindState : 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 +struct BindState : public BindStateBase { + typedef Runnable RunnableType; + typedef IsWeakMethod::value, P1> IsWeakCall; + typedef Invoker<1, BindState, RunType> InvokerType; + typedef typename InvokerType::UnboundRunType UnboundRunType; + + // Convenience typedefs for bound argument types. + typedef UnwrapTraits Bound1UnwrapTraits; + + BindState(const Runnable& runnable, const P1& p1) + : runnable_(runnable), + p1_(p1) { + MaybeRefcount::value, P1>::AddRef(p1_); + } + + virtual ~BindState() { MaybeRefcount::value, + P1>::Release(p1_); } + + RunnableType runnable_; + P1 p1_; +}; + +template +struct BindState : public BindStateBase { + typedef Runnable RunnableType; + typedef IsWeakMethod::value, P1> IsWeakCall; + typedef Invoker<2, BindState, RunType> InvokerType; + typedef typename InvokerType::UnboundRunType UnboundRunType; + + // Convenience typedefs for bound argument types. + typedef UnwrapTraits Bound1UnwrapTraits; + typedef UnwrapTraits Bound2UnwrapTraits; + + BindState(const Runnable& runnable, const P1& p1, const P2& p2) + : runnable_(runnable), + p1_(p1), + p2_(p2) { + MaybeRefcount::value, P1>::AddRef(p1_); + } + + virtual ~BindState() { MaybeRefcount::value, + P1>::Release(p1_); } + + RunnableType runnable_; + P1 p1_; + P2 p2_; +}; + +template +struct BindState : public BindStateBase { + typedef Runnable RunnableType; + typedef IsWeakMethod::value, P1> IsWeakCall; + typedef Invoker<3, BindState, RunType> InvokerType; + typedef typename InvokerType::UnboundRunType UnboundRunType; + + // Convenience typedefs for bound argument types. + typedef UnwrapTraits Bound1UnwrapTraits; + typedef UnwrapTraits Bound2UnwrapTraits; + typedef UnwrapTraits Bound3UnwrapTraits; + + BindState(const Runnable& runnable, const P1& p1, const P2& p2, const P3& p3) + : runnable_(runnable), + p1_(p1), + p2_(p2), + p3_(p3) { + MaybeRefcount::value, P1>::AddRef(p1_); + } + + virtual ~BindState() { MaybeRefcount::value, + P1>::Release(p1_); } + + RunnableType runnable_; + P1 p1_; + P2 p2_; + P3 p3_; +}; + +template +struct BindState : public BindStateBase { + typedef Runnable RunnableType; + typedef IsWeakMethod::value, P1> IsWeakCall; + typedef Invoker<4, BindState, RunType> InvokerType; + typedef typename InvokerType::UnboundRunType UnboundRunType; + + // Convenience typedefs for bound argument types. + typedef UnwrapTraits Bound1UnwrapTraits; + typedef UnwrapTraits Bound2UnwrapTraits; + typedef UnwrapTraits Bound3UnwrapTraits; + typedef UnwrapTraits 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::value, P1>::AddRef(p1_); + } + + virtual ~BindState() { MaybeRefcount::value, + P1>::Release(p1_); } + + RunnableType runnable_; + P1 p1_; + P2 p2_; + P3 p3_; + P4 p4_; +}; + +template +struct BindState : public BindStateBase { + typedef Runnable RunnableType; + typedef IsWeakMethod::value, P1> IsWeakCall; + typedef Invoker<5, BindState, RunType> InvokerType; + typedef typename InvokerType::UnboundRunType UnboundRunType; + + // Convenience typedefs for bound argument types. + typedef UnwrapTraits Bound1UnwrapTraits; + typedef UnwrapTraits Bound2UnwrapTraits; + typedef UnwrapTraits Bound3UnwrapTraits; + typedef UnwrapTraits Bound4UnwrapTraits; + typedef UnwrapTraits 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::value, P1>::AddRef(p1_); + } + + virtual ~BindState() { MaybeRefcount::value, + P1>::Release(p1_); } + + RunnableType runnable_; + P1 p1_; + P2 p2_; + P3 p3_; + P4 p4_; + P5 p5_; +}; + +template +struct BindState : public BindStateBase { + typedef Runnable RunnableType; + typedef IsWeakMethod::value, P1> IsWeakCall; + typedef Invoker<6, BindState, RunType> InvokerType; + typedef typename InvokerType::UnboundRunType UnboundRunType; + + // Convenience typedefs for bound argument types. + typedef UnwrapTraits Bound1UnwrapTraits; + typedef UnwrapTraits Bound2UnwrapTraits; + typedef UnwrapTraits Bound3UnwrapTraits; + typedef UnwrapTraits Bound4UnwrapTraits; + typedef UnwrapTraits Bound5UnwrapTraits; + typedef UnwrapTraits 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::value, P1>::AddRef(p1_); + } + + virtual ~BindState() { MaybeRefcount::value, + P1>::Release(p1_); } + + RunnableType runnable_; + P1 p1_; + P2 p2_; + P3 p3_; + P4 p4_; + P5 p5_; + P6 p6_; +}; + +template +struct BindState : public BindStateBase { + typedef Runnable RunnableType; + typedef IsWeakMethod::value, P1> IsWeakCall; + typedef Invoker<7, BindState, RunType> InvokerType; + typedef typename InvokerType::UnboundRunType UnboundRunType; + + // Convenience typedefs for bound argument types. + typedef UnwrapTraits Bound1UnwrapTraits; + typedef UnwrapTraits Bound2UnwrapTraits; + typedef UnwrapTraits Bound3UnwrapTraits; + typedef UnwrapTraits Bound4UnwrapTraits; + typedef UnwrapTraits Bound5UnwrapTraits; + typedef UnwrapTraits Bound6UnwrapTraits; + typedef UnwrapTraits 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::value, P1>::AddRef(p1_); + } + + virtual ~BindState() { MaybeRefcount::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/base/bind_internal.h.pump b/base/bind_internal.h.pump new file mode 100644 index 0000000000..f632b99e55 --- /dev/null +++ b/base/bind_internal.h.pump @@ -0,0 +1,500 @@ +$$ 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(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 +class RunnableAdapter; + +$for ARITY [[ +$range ARG 1..ARITY + +// Function: Arity $(ARITY). +template 0[[, ]] $for ARG , [[typename A$(ARG)]]> +class RunnableAdapter { + public: + typedef R (RunType)($for ARG , [[A$(ARG)]]); + + explicit RunnableAdapter(R(*function)($for ARG , [[A$(ARG)]])) + : function_(function) { + } + + R Run($for ARG , [[typename CallbackParamTraits::ForwardType a$(ARG)]]) { + return function_($for ARG , [[CallbackForward(a$(ARG))]]); + } + + private: + R (*function_)($for ARG , [[A$(ARG)]]); +}; + +// Method: Arity $(ARITY). +template 0[[, ]] $for ARG , [[typename A$(ARG)]]> +class RunnableAdapter { + 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::ForwardType a$(ARG)]]) { + return (object->*method_)($for ARG , [[CallbackForward(a$(ARG))]]); + } + + private: + R (T::*method_)($for ARG , [[A$(ARG)]]); +}; + +// Const Method: Arity $(ARITY). +template 0[[, ]] $for ARG , [[typename A$(ARG)]]> +class RunnableAdapter { + 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::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 +struct FunctionTraits; + +$for ARITY [[ +$range ARG 1..ARITY + +template 0[[, ]] $for ARG , [[typename A$(ARG)]]> +struct FunctionTraits { + 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 +struct ForceVoidReturn; + +$for ARITY [[ +$range ARG 1..ARITY + +template 0[[, ]] $for ARG , [[typename A$(ARG)]]> +struct ForceVoidReturn { + typedef void(RunType)($for ARG , [[A$(ARG)]]); +}; + +]] $$ for ARITY + + +// FunctorTraits<> +// +// See description at top of file. +template +struct FunctorTraits { + typedef RunnableAdapter RunnableType; + typedef typename RunnableType::RunType RunType; +}; + +template +struct FunctorTraits > { + typedef typename FunctorTraits::RunnableType RunnableType; + typedef typename ForceVoidReturn< + typename RunnableType::RunType>::RunType RunType; +}; + +template +struct FunctorTraits > { + typedef Callback RunnableType; + typedef typename Callback::RunType RunType; +}; + + +// MakeRunnable<> +// +// Converts a passed in functor to a RunnableType using type inference. + +template +typename FunctorTraits::RunnableType MakeRunnable(const T& t) { + return RunnableAdapter(t); +} + +template +typename FunctorTraits::RunnableType +MakeRunnable(const IgnoreResultHelper& t) { + return MakeRunnable(t.functor_); +} + +template +const typename FunctorTraits >::RunnableType& +MakeRunnable(const Callback& 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 +struct InvokeHelper; + +$for ARITY [[ +$range ARG 1..ARITY +$range WEAKCALL_ARG 2..ARITY + +template 0 [[,]] $for ARG , [[typename A$(ARG)]]> +struct InvokeHelper { + static ReturnType MakeItSo(Runnable runnable[[]] +$if ARITY > 0[[, ]] $for ARG , [[A$(ARG) a$(ARG)]]) { + return runnable.Run($for ARG , [[CallbackForward(a$(ARG))]]); + } +}; + +template 0 [[,]] $for ARG , [[typename A$(ARG)]]> +struct InvokeHelper { + 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 1[[, ]] $for WEAKCALL_ARG , [[typename A$(WEAKCALL_ARG)]]> +struct InvokeHelper 1[[, ]] $for WEAKCALL_ARG , [[A$(WEAKCALL_ARG)]])> { + static void MakeItSo(Runnable runnable, BoundWeakPtr weak_ptr +$if ARITY > 1[[, ]] $for WEAKCALL_ARG , [[A$(WEAKCALL_ARG) a$(WEAKCALL_ARG)]]) { + if (!weak_ptr.get()) { + return; + } + runnable.Run(weak_ptr.get() +$if ARITY > 1[[, ]] $for WEAKCALL_ARG , [[CallbackForward(a$(WEAKCALL_ARG))]]); + } +}; + +]] + +]] $$ for ARITY + +#if !defined(_MSC_VER) + +template +struct InvokeHelper { + // 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::value, + weak_ptrs_can_only_bind_to_methods_without_return_values); +}; + +#endif + +// Invoker<> +// +// See description at the top of the file. +template +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 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::ForwardType]]); + + typedef R(UnboundRunType)($for UNBOUND_ARG , [[X$(UNBOUND_ARG)]]); + + static R Run(BindStateBase* base[[]] +$if UNBOUND != 0 [[, ]][[]] +$for UNBOUND_ARG , [[ +typename CallbackParamTraits::ForwardType x$(UNBOUND_ARG) +]][[]] +) { + StorageType* storage = static_cast(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 0 [[$if BOUND > 0 [[, ]]]][[]] + +$for UNBOUND_ARG , [[ +typename CallbackParamTraits::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 +struct BindState; + +$for ARITY [[ +$range ARG 1..ARITY + +template 0[[, ]] $for ARG , [[typename P$(ARG)]]> +struct BindState : public BindStateBase { + typedef Runnable RunnableType; + +$if ARITY > 0 [[ + typedef IsWeakMethod::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 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::value, P1>::AddRef(p1_); + +]] + } + + virtual ~BindState() { +$if ARITY > 0 [[ + MaybeRefcount::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/base/bind_internal_win.h b/base/bind_internal_win.h new file mode 100644 index 0000000000..7a8486a309 --- /dev/null +++ b/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 +class RunnableAdapter; + +// __stdcall Function: Arity 0. +template +class RunnableAdapter { + public: + typedef R (RunType)(); + + explicit RunnableAdapter(R(__stdcall *function)()) + : function_(function) { + } + + R Run() { + return function_(); + } + + private: + R (__stdcall *function_)(); +}; + +// __fastcall Function: Arity 0. +template +class RunnableAdapter { + public: + typedef R (RunType)(); + + explicit RunnableAdapter(R(__fastcall *function)()) + : function_(function) { + } + + R Run() { + return function_(); + } + + private: + R (__fastcall *function_)(); +}; + +// __stdcall Function: Arity 1. +template +class RunnableAdapter { + public: + typedef R (RunType)(A1); + + explicit RunnableAdapter(R(__stdcall *function)(A1)) + : function_(function) { + } + + R Run(typename CallbackParamTraits::ForwardType a1) { + return function_(a1); + } + + private: + R (__stdcall *function_)(A1); +}; + +// __fastcall Function: Arity 1. +template +class RunnableAdapter { + public: + typedef R (RunType)(A1); + + explicit RunnableAdapter(R(__fastcall *function)(A1)) + : function_(function) { + } + + R Run(typename CallbackParamTraits::ForwardType a1) { + return function_(a1); + } + + private: + R (__fastcall *function_)(A1); +}; + +// __stdcall Function: Arity 2. +template +class RunnableAdapter { + public: + typedef R (RunType)(A1, A2); + + explicit RunnableAdapter(R(__stdcall *function)(A1, A2)) + : function_(function) { + } + + R Run(typename CallbackParamTraits::ForwardType a1, + typename CallbackParamTraits::ForwardType a2) { + return function_(a1, a2); + } + + private: + R (__stdcall *function_)(A1, A2); +}; + +// __fastcall Function: Arity 2. +template +class RunnableAdapter { + public: + typedef R (RunType)(A1, A2); + + explicit RunnableAdapter(R(__fastcall *function)(A1, A2)) + : function_(function) { + } + + R Run(typename CallbackParamTraits::ForwardType a1, + typename CallbackParamTraits::ForwardType a2) { + return function_(a1, a2); + } + + private: + R (__fastcall *function_)(A1, A2); +}; + +// __stdcall Function: Arity 3. +template +class RunnableAdapter { + public: + typedef R (RunType)(A1, A2, A3); + + explicit RunnableAdapter(R(__stdcall *function)(A1, A2, A3)) + : function_(function) { + } + + R Run(typename CallbackParamTraits::ForwardType a1, + typename CallbackParamTraits::ForwardType a2, + typename CallbackParamTraits::ForwardType a3) { + return function_(a1, a2, a3); + } + + private: + R (__stdcall *function_)(A1, A2, A3); +}; + +// __fastcall Function: Arity 3. +template +class RunnableAdapter { + public: + typedef R (RunType)(A1, A2, A3); + + explicit RunnableAdapter(R(__fastcall *function)(A1, A2, A3)) + : function_(function) { + } + + R Run(typename CallbackParamTraits::ForwardType a1, + typename CallbackParamTraits::ForwardType a2, + typename CallbackParamTraits::ForwardType a3) { + return function_(a1, a2, a3); + } + + private: + R (__fastcall *function_)(A1, A2, A3); +}; + +// __stdcall Function: Arity 4. +template +class RunnableAdapter { + public: + typedef R (RunType)(A1, A2, A3, A4); + + explicit RunnableAdapter(R(__stdcall *function)(A1, A2, A3, A4)) + : function_(function) { + } + + R Run(typename CallbackParamTraits::ForwardType a1, + typename CallbackParamTraits::ForwardType a2, + typename CallbackParamTraits::ForwardType a3, + typename CallbackParamTraits::ForwardType a4) { + return function_(a1, a2, a3, a4); + } + + private: + R (__stdcall *function_)(A1, A2, A3, A4); +}; + +// __fastcall Function: Arity 4. +template +class RunnableAdapter { + public: + typedef R (RunType)(A1, A2, A3, A4); + + explicit RunnableAdapter(R(__fastcall *function)(A1, A2, A3, A4)) + : function_(function) { + } + + R Run(typename CallbackParamTraits::ForwardType a1, + typename CallbackParamTraits::ForwardType a2, + typename CallbackParamTraits::ForwardType a3, + typename CallbackParamTraits::ForwardType a4) { + return function_(a1, a2, a3, a4); + } + + private: + R (__fastcall *function_)(A1, A2, A3, A4); +}; + +// __stdcall Function: Arity 5. +template +class RunnableAdapter { + 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::ForwardType a1, + typename CallbackParamTraits::ForwardType a2, + typename CallbackParamTraits::ForwardType a3, + typename CallbackParamTraits::ForwardType a4, + typename CallbackParamTraits::ForwardType a5) { + return function_(a1, a2, a3, a4, a5); + } + + private: + R (__stdcall *function_)(A1, A2, A3, A4, A5); +}; + +// __fastcall Function: Arity 5. +template +class RunnableAdapter { + 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::ForwardType a1, + typename CallbackParamTraits::ForwardType a2, + typename CallbackParamTraits::ForwardType a3, + typename CallbackParamTraits::ForwardType a4, + typename CallbackParamTraits::ForwardType a5) { + return function_(a1, a2, a3, a4, a5); + } + + private: + R (__fastcall *function_)(A1, A2, A3, A4, A5); +}; + +// __stdcall Function: Arity 6. +template +class RunnableAdapter { + 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::ForwardType a1, + typename CallbackParamTraits::ForwardType a2, + typename CallbackParamTraits::ForwardType a3, + typename CallbackParamTraits::ForwardType a4, + typename CallbackParamTraits::ForwardType a5, + typename CallbackParamTraits::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 +class RunnableAdapter { + 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::ForwardType a1, + typename CallbackParamTraits::ForwardType a2, + typename CallbackParamTraits::ForwardType a3, + typename CallbackParamTraits::ForwardType a4, + typename CallbackParamTraits::ForwardType a5, + typename CallbackParamTraits::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 +class RunnableAdapter { + 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::ForwardType a1, + typename CallbackParamTraits::ForwardType a2, + typename CallbackParamTraits::ForwardType a3, + typename CallbackParamTraits::ForwardType a4, + typename CallbackParamTraits::ForwardType a5, + typename CallbackParamTraits::ForwardType a6, + typename CallbackParamTraits::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 +class RunnableAdapter { + 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::ForwardType a1, + typename CallbackParamTraits::ForwardType a2, + typename CallbackParamTraits::ForwardType a3, + typename CallbackParamTraits::ForwardType a4, + typename CallbackParamTraits::ForwardType a5, + typename CallbackParamTraits::ForwardType a6, + typename CallbackParamTraits::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/base/bind_internal_win.h.pump b/base/bind_internal_win.h.pump new file mode 100644 index 0000000000..cd108b6abe --- /dev/null +++ b/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 +class RunnableAdapter; + +$range ARITY 0..MAX_ARITY +$for ARITY [[ +$range ARG 1..ARITY + +// __stdcall Function: Arity $(ARITY). +template 0[[, ]] $for ARG , [[typename A$(ARG)]]> +class RunnableAdapter { + 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::ForwardType a$(ARG)]]) { + return function_($for ARG , [[a$(ARG)]]); + } + + private: + R (__stdcall *function_)($for ARG , [[A$(ARG)]]); +}; + +// __fastcall Function: Arity $(ARITY). +template 0[[, ]] $for ARG , [[typename A$(ARG)]]> +class RunnableAdapter { + 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::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/base/bind_unittest.cc b/base/bind_unittest.cc new file mode 100644 index 0000000000..2c93d53ee4 --- /dev/null +++ b/base/bind_unittest.cc @@ -0,0 +1,833 @@ +// Copyright (c) 2012 The Chromium Authors. All 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. + explicit 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 +T PassThru(T scoper) { + return scoper.Pass(); +} + +// Some test functions that we can Bind to. +template +T PolymorphicIdentity(T t) { + return t; +} + +template +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 o, int n) { + return n; +} + +int FunctionWithScopedRefptrFirstParam(const scoped_refptr& 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 no_ref_; + StrictMock has_ref_; + const HasRef* const_has_ref_ptr_; + const NoRef* const_no_ref_ptr_; + StrictMock static_func_mock_; + + // Used by the static functions to perform expectations. + static StrictMock* static_func_mock_ptr; + + private: + DISALLOW_COPY_AND_ASSIGN(BindTest); +}; + +StrictMock* BindTest::static_func_mock_ptr; + +// Sanity check that we can instantiate a callback for each arity. +TEST_F(BindTest, ArityTest) { + Callback c0 = Bind(&Sum, 32, 16, 8, 4, 2, 1); + EXPECT_EQ(63, c0.Run()); + + Callback c1 = Bind(&Sum, 32, 16, 8, 4, 2); + EXPECT_EQ(75, c1.Run(13)); + + Callback c2 = Bind(&Sum, 32, 16, 8, 4); + EXPECT_EQ(85, c2.Run(13, 12)); + + Callback c3 = Bind(&Sum, 32, 16, 8); + EXPECT_EQ(92, c3.Run(13, 12, 11)); + + Callback c4 = Bind(&Sum, 32, 16); + EXPECT_EQ(94, c4.Run(13, 12, 11, 10)); + + Callback c5 = Bind(&Sum, 32); + EXPECT_EQ(87, c5.Run(13, 12, 11, 10, 9)); + + Callback 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 c6 = Bind(&Sum); + EXPECT_EQ(69, c6.Run(13, 12, 11, 10, 9, 14)); + + Callback c5 = Bind(c6, 32); + EXPECT_EQ(87, c5.Run(13, 12, 11, 10, 9)); + + Callback c4 = Bind(c5, 16); + EXPECT_EQ(94, c4.Run(13, 12, 11, 10)); + + Callback c3 = Bind(c4, 8); + EXPECT_EQ(92, c3.Run(13, 12, 11)); + + Callback c2 = Bind(c3, 4); + EXPECT_EQ(85, c2.Run(13, 12)); + + Callback c1 = Bind(c2, 2); + EXPECT_EQ(75, c1.Run(13)); + + Callback 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 normal_non_refcounted_cb = + Bind(&PolymorphicIdentity, &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 normal_cb = Bind(&IntFunc0); + Callback method_cb = Bind(&HasRef::IntMethod0, &has_ref_); + Callback const_method_nonconst_obj_cb = + Bind(&HasRef::IntConstMethod0, &has_ref_); + Callback 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 weak_factory(&no_ref_); + WeakPtrFactory 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 bind_primitive_cb = Bind(&Identity, n); + EXPECT_EQ(n, bind_primitive_cb.Run()); + + Callback bind_primitive_pointer_cb = + Bind(&PolymorphicIdentity, &n); + EXPECT_EQ(&n, bind_primitive_pointer_cb.Run()); + + Callback bind_int_literal_cb = Bind(&Identity, 3); + EXPECT_EQ(3, bind_int_literal_cb.Run()); + + Callback bind_string_literal_cb = + Bind(&CStringIdentity, "hi"); + EXPECT_STREQ("hi", bind_string_literal_cb.Run()); + + Callback bind_template_function_cb = + Bind(&PolymorphicIdentity, 4); + EXPECT_EQ(4, bind_template_function_cb.Run()); + + NoRefParent p; + p.value = 5; + Callback bind_object_cb = Bind(&UnwrapNoRefParent, p); + EXPECT_EQ(5, bind_object_cb.Run()); + + IncompleteType* incomplete_ptr = reinterpret_cast(123); + Callback bind_incomplete_ptr_cb = + Bind(&PolymorphicIdentity, incomplete_ptr); + EXPECT_EQ(incomplete_ptr, bind_incomplete_ptr_cb.Run()); + + NoRefChild c; + c.value = 6; + Callback bind_promotes_cb = Bind(&UnwrapNoRefParent, c); + EXPECT_EQ(6, bind_promotes_cb.Run()); + + c.value = 7; + Callback bind_pointer_promotes_cb = + Bind(&UnwrapNoRefParentPtr, &c); + EXPECT_EQ(7, bind_pointer_promotes_cb.Run()); + + c.value = 8; + Callback 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 unbound_value_cb = Bind(&VoidPolymorphic1); + Callback unbound_pointer_cb = Bind(&VoidPolymorphic1); + Callback unbound_ref_cb = Bind(&VoidPolymorphic1); + Callback unbound_const_ref_cb = + Bind(&VoidPolymorphic1); + Callback unbound_unsized_array_cb = + Bind(&VoidPolymorphic1); + Callback unbound_sized_array_cb = + Bind(&VoidPolymorphic1); + Callback unbound_array_of_arrays_cb = + Bind(&VoidPolymorphic1); +} + +// Function with unbound reference parameter. +// - Original parameter is modified by callback. +TEST_F(BindTest, UnboundReferenceSupport) { + int n = 0; + Callback 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 ref_copies_cb = Bind(&Identity, ref_n); + EXPECT_EQ(n, ref_copies_cb.Run()); + n++; + EXPECT_EQ(n - 1, ref_copies_cb.Run()); + + Callback 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 array_cb = Bind(&ArrayGet, array, 1); + EXPECT_EQ(1, array_cb.Run()); + + Callback 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::value); + EXPECT_FALSE(internal::SupportsAddRefAndRelease::value); + + // StrictMock is a derived class of T. So, we use StrictMock and + // StrictMock to test that SupportsAddRefAndRelease works over + // inheritance. + EXPECT_TRUE(internal::SupportsAddRefAndRelease >::value); + EXPECT_FALSE(internal::SupportsAddRefAndRelease >::value); + + // This matters because the implementation creates a dummy class that + // inherits from the template type. + EXPECT_TRUE(internal::SupportsAddRefAndRelease::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 method_cb = + Bind(&NoRef::VoidMethod0, Unretained(&no_ref_)); + method_cb.Run(); + + Callback const_method_cb = + Bind(&NoRef::VoidConstMethod0, Unretained(&no_ref_)); + const_method_cb.Run(); + + Callback 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 weak_factory(&no_ref_); + WeakPtrFactory 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 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 copy_cb = Bind(&Identity, n); + Callback 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 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_F(BindTest, ScopedRefptr) { + // BUG: The scoped_refptr should cause the only AddRef()/Release() pair. But + // due to a bug in base::Bind(), there's an extra call when invoking the + // callback. + // https://code.google.com/p/chromium/issues/detail?id=251937 + EXPECT_CALL(has_ref_, AddRef()).Times(2); + EXPECT_CALL(has_ref_, Release()).Times(2); + + const scoped_refptr > refptr(&has_ref_); + + Callback scoped_refptr_const_ref_cb = + Bind(&FunctionWithScopedRefptrFirstParam, base::ConstRef(refptr), 1); + EXPECT_EQ(1, scoped_refptr_const_ref_cb.Run()); +} + +// 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 no_capture_cb = + Bind(&PolymorphicIdentity, 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 ptr(new DeleteCounter(&deletes)); + Callback(void)> unused_callback = + Bind(&PassThru >, 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(void)> callback = + Bind(&PassThru >, + Passed(scoped_ptr(counter))); + EXPECT_FALSE(ptr.get()); + EXPECT_EQ(0, deletes); + + // Check that ownership can be transferred back out. + scoped_ptr 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)> cb_unbound = + Bind(&PassThru >); + 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 copy_cb = + Bind(&VoidPolymorphic1, counter); + EXPECT_GE(1, copies); + EXPECT_EQ(0, assigns); + + copies = 0; + assigns = 0; + Callback forward_cb = + Bind(&VoidPolymorphic1); + forward_cb.Run(counter); + EXPECT_GE(1, copies); + EXPECT_EQ(0, assigns); + + copies = 0; + assigns = 0; + DerivedCopyCounter dervied(&copies, &assigns); + Callback coerce_cb = + Bind(&VoidPolymorphic1); + coerce_cb.Run(CopyCounter(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 fastcall_cb = Bind(&FastCallFunc, 1); + EXPECT_EQ(1, fastcall_cb.Run()); + + Callback 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 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/base/bind_unittest.nc b/base/bind_unittest.nc new file mode 100644 index 0000000000..033acfaa05 --- /dev/null +++ b/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 { +}; + +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 +T PolymorphicIdentity(T t) { + return t; +} + +int UnwrapParentRef(Parent& p) { + return p.value; +} + +template +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 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 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 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 pointer_same_cb = + Bind(&PolymorphicIdentity, 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 pointer_super_cb = + Bind(&PolymorphicIdentity, 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 ref_arg_cb = Bind(&UnwrapParentRef); + ref_arg_cb.Run(p); +} + +#elif defined(NCTEST_DISALLOW_BIND_TO_NON_CONST_REF_PARAM) // [r"size of array is negative"] + +// Binding functions with reference parameters, unsupported. +// +// See comment in NCTEST_DISALLOW_NON_CONST_REF_PARAM +void WontCompile() { + Parent p; + Callback ref_cb = Bind(&UnwrapParentRef, p); + ref_cb.Run(); +} + +#elif defined(NCTEST_NO_IMPLICIT_ARRAY_PTR_CONVERSION) // [r"size of array is negative"] + +// 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 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"size of array is negative"] + +// Refcounted types should not be bound as a raw pointer. +void WontCompile() { + HasRef for_raw_ptr; + int a; + Callback ref_count_as_raw_ptr_a = + Bind(&VoidPolymorphic1, &a); + Callback ref_count_as_raw_ptr = + Bind(&VoidPolymorphic1, &for_raw_ptr); +} + +#elif defined(NCTEST_WEAKPTR_BIND_MUST_RETURN_VOID) // [r"size of array is negative"] + +// WeakPtrs cannot be bound to methods with return types. +void WontCompile() { + NoRef no_ref; + WeakPtrFactory weak_factory(&no_ref); + Callback 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_DIFFERENT_TYPES) // [r"conversion from 'base::Callback' to non-scalar type"] + +// Bind result cannot be assigned to Callbacks with a mismatching type. +void WontCompile() { + Closure callback_mismatches_bind_type = Bind(&VoidPolymorphic1); +} + +#endif + +} // namespace base diff --git a/base/bits.h b/base/bits.h new file mode 100644 index 0000000000..b2209e8ed7 --- /dev/null +++ b/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/base/bits_unittest.cc b/base/bits_unittest.cc new file mode 100644 index 0000000000..e913d6ae59 --- /dev/null +++ b/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/base/build_time.cc b/base/build_time.cc new file mode 100644 index 0000000000..760269a3ff --- /dev/null +++ b/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/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/base/build_time.h b/base/build_time.h new file mode 100644 index 0000000000..73c01b052b --- /dev/null +++ b/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/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/base/build_time_unittest.cc b/base/build_time_unittest.cc new file mode 100644 index 0000000000..399a53f953 --- /dev/null +++ b/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/base/callback.h b/base/callback.h new file mode 100644 index 0000000000..ade3f8c1fc --- /dev/null +++ b/base/callback.h @@ -0,0 +1,765 @@ +// 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) +// 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 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 { +// public: +// int Foo() { return 3; } +// void PrintBye() { LOG(INFO) << "bye."; } +// }; +// scoped_refptr ref = new Ref(); +// base::Callback 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& 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& 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 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 cb = base::Bind(&MyFunc, 23, "hello world"); +// cb.Run(); +// +// A callback with no unbound input parameters (base::Callback) +// 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 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) 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 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 arg) {} +// scoped_ptr 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 arg) {} +// scoped_refptr 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 is convertible to tr1::function). +// +// 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 +class Callback; + +namespace internal { +template +struct BindState; +} // namespace internal + +template +class Callback : 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 + Callback(internal::BindState* 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 + ::InvokerType::Run; + polymorphic_invoke_ = reinterpret_cast(invoke_func); + } + + bool Equals(const Callback& other) const { + return CallbackBase::Equals(other); + } + + R Run() const { + PolymorphicInvoke f = + reinterpret_cast(polymorphic_invoke_); + + return f(bind_state_.get()); + } + + private: + typedef R(*PolymorphicInvoke)( + internal::BindStateBase*); + +}; + +template +class Callback : 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 + Callback(internal::BindState* 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 + ::InvokerType::Run; + polymorphic_invoke_ = reinterpret_cast(invoke_func); + } + + bool Equals(const Callback& other) const { + return CallbackBase::Equals(other); + } + + R Run(typename internal::CallbackParamTraits::ForwardType a1) const { + PolymorphicInvoke f = + reinterpret_cast(polymorphic_invoke_); + + return f(bind_state_.get(), internal::CallbackForward(a1)); + } + + private: + typedef R(*PolymorphicInvoke)( + internal::BindStateBase*, + typename internal::CallbackParamTraits::ForwardType); + +}; + +template +class Callback : 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 + Callback(internal::BindState* 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 + ::InvokerType::Run; + polymorphic_invoke_ = reinterpret_cast(invoke_func); + } + + bool Equals(const Callback& other) const { + return CallbackBase::Equals(other); + } + + R Run(typename internal::CallbackParamTraits::ForwardType a1, + typename internal::CallbackParamTraits::ForwardType a2) const { + PolymorphicInvoke f = + reinterpret_cast(polymorphic_invoke_); + + return f(bind_state_.get(), internal::CallbackForward(a1), + internal::CallbackForward(a2)); + } + + private: + typedef R(*PolymorphicInvoke)( + internal::BindStateBase*, + typename internal::CallbackParamTraits::ForwardType, + typename internal::CallbackParamTraits::ForwardType); + +}; + +template +class Callback : 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 + Callback(internal::BindState* 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 + ::InvokerType::Run; + polymorphic_invoke_ = reinterpret_cast(invoke_func); + } + + bool Equals(const Callback& other) const { + return CallbackBase::Equals(other); + } + + R Run(typename internal::CallbackParamTraits::ForwardType a1, + typename internal::CallbackParamTraits::ForwardType a2, + typename internal::CallbackParamTraits::ForwardType a3) const { + PolymorphicInvoke f = + reinterpret_cast(polymorphic_invoke_); + + return f(bind_state_.get(), internal::CallbackForward(a1), + internal::CallbackForward(a2), + internal::CallbackForward(a3)); + } + + private: + typedef R(*PolymorphicInvoke)( + internal::BindStateBase*, + typename internal::CallbackParamTraits::ForwardType, + typename internal::CallbackParamTraits::ForwardType, + typename internal::CallbackParamTraits::ForwardType); + +}; + +template +class Callback : 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 + Callback(internal::BindState* 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 + ::InvokerType::Run; + polymorphic_invoke_ = reinterpret_cast(invoke_func); + } + + bool Equals(const Callback& other) const { + return CallbackBase::Equals(other); + } + + R Run(typename internal::CallbackParamTraits::ForwardType a1, + typename internal::CallbackParamTraits::ForwardType a2, + typename internal::CallbackParamTraits::ForwardType a3, + typename internal::CallbackParamTraits::ForwardType a4) const { + PolymorphicInvoke f = + reinterpret_cast(polymorphic_invoke_); + + 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::ForwardType, + typename internal::CallbackParamTraits::ForwardType, + typename internal::CallbackParamTraits::ForwardType, + typename internal::CallbackParamTraits::ForwardType); + +}; + +template +class Callback : 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 + Callback(internal::BindState* 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 + ::InvokerType::Run; + polymorphic_invoke_ = reinterpret_cast(invoke_func); + } + + bool Equals(const Callback& other) const { + return CallbackBase::Equals(other); + } + + R Run(typename internal::CallbackParamTraits::ForwardType a1, + typename internal::CallbackParamTraits::ForwardType a2, + typename internal::CallbackParamTraits::ForwardType a3, + typename internal::CallbackParamTraits::ForwardType a4, + typename internal::CallbackParamTraits::ForwardType a5) const { + PolymorphicInvoke f = + reinterpret_cast(polymorphic_invoke_); + + 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::ForwardType, + typename internal::CallbackParamTraits::ForwardType, + typename internal::CallbackParamTraits::ForwardType, + typename internal::CallbackParamTraits::ForwardType, + typename internal::CallbackParamTraits::ForwardType); + +}; + +template +class Callback : 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 + Callback(internal::BindState* 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 + ::InvokerType::Run; + polymorphic_invoke_ = reinterpret_cast(invoke_func); + } + + bool Equals(const Callback& other) const { + return CallbackBase::Equals(other); + } + + R Run(typename internal::CallbackParamTraits::ForwardType a1, + typename internal::CallbackParamTraits::ForwardType a2, + typename internal::CallbackParamTraits::ForwardType a3, + typename internal::CallbackParamTraits::ForwardType a4, + typename internal::CallbackParamTraits::ForwardType a5, + typename internal::CallbackParamTraits::ForwardType a6) const { + PolymorphicInvoke f = + reinterpret_cast(polymorphic_invoke_); + + 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::ForwardType, + typename internal::CallbackParamTraits::ForwardType, + typename internal::CallbackParamTraits::ForwardType, + typename internal::CallbackParamTraits::ForwardType, + typename internal::CallbackParamTraits::ForwardType, + typename internal::CallbackParamTraits::ForwardType); + +}; + +template +class Callback : 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 + Callback(internal::BindState* 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 + ::InvokerType::Run; + polymorphic_invoke_ = reinterpret_cast(invoke_func); + } + + bool Equals(const Callback& other) const { + return CallbackBase::Equals(other); + } + + R Run(typename internal::CallbackParamTraits::ForwardType a1, + typename internal::CallbackParamTraits::ForwardType a2, + typename internal::CallbackParamTraits::ForwardType a3, + typename internal::CallbackParamTraits::ForwardType a4, + typename internal::CallbackParamTraits::ForwardType a5, + typename internal::CallbackParamTraits::ForwardType a6, + typename internal::CallbackParamTraits::ForwardType a7) const { + PolymorphicInvoke f = + reinterpret_cast(polymorphic_invoke_); + + 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::ForwardType, + typename internal::CallbackParamTraits::ForwardType, + typename internal::CallbackParamTraits::ForwardType, + typename internal::CallbackParamTraits::ForwardType, + typename internal::CallbackParamTraits::ForwardType, + typename internal::CallbackParamTraits::ForwardType, + typename internal::CallbackParamTraits::ForwardType); + +}; + + +// Syntactic sugar to make Callbacks easier to declare since it +// will be used in a lot of APIs with delayed execution. +typedef Callback Closure; + +} // namespace base + +#endif // BASE_CALLBACK_H diff --git a/base/callback.h.pump b/base/callback.h.pump new file mode 100644 index 0000000000..41d2ee9aec --- /dev/null +++ b/base/callback.h.pump @@ -0,0 +1,436 @@ +$$ 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) +// 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 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 { +// public: +// int Foo() { return 3; } +// void PrintBye() { LOG(INFO) << "bye."; } +// }; +// scoped_refptr ref = new Ref(); +// base::Callback 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& 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& 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 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 cb = base::Bind(&MyFunc, 23, "hello world"); +// cb.Run(); +// +// A callback with no unbound input parameters (base::Callback) +// 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 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) 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 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 arg) {} +// scoped_ptr 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 arg) {} +// scoped_refptr 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 is convertible to tr1::function). +// +// 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 +class Callback; + +namespace internal { +template +struct BindState; +} // namespace internal + + +$range ARITY 0..MAX_ARITY +$for ARITY [[ +$range ARG 1..ARITY + +$if ARITY == 0 [[ +template +class Callback : public internal::CallbackBase { +]] $else [[ +template +class Callback : 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 + Callback(internal::BindState* 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 + ::InvokerType::Run; + polymorphic_invoke_ = reinterpret_cast(invoke_func); + } + + bool Equals(const Callback& other) const { + return CallbackBase::Equals(other); + } + + R Run($for ARG , + [[typename internal::CallbackParamTraits::ForwardType a$(ARG)]]) const { + PolymorphicInvoke f = + reinterpret_cast(polymorphic_invoke_); + + 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::ForwardType]]); + +}; + + +]] $$ for ARITY + +// Syntactic sugar to make Callbacks easier to declare since it +// will be used in a lot of APIs with delayed execution. +typedef Callback Closure; + +} // namespace base + +#endif // BASE_CALLBACK_H diff --git a/base/callback_forward.h b/base/callback_forward.h new file mode 100644 index 0000000000..79832481af --- /dev/null +++ b/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 +class Callback; + +typedef Callback Closure; + +} // namespace base + +#endif // BASE_CALLBACK_FORWARD_H diff --git a/base/callback_helpers.h b/base/callback_helpers.h new file mode 100644 index 0000000000..52cb71bbf2 --- /dev/null +++ b/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 +base::Callback ResetAndReturn(base::Callback* cb) { + base::Callback ret(*cb); + cb->Reset(); + return ret; +} + +} // namespace base + +#endif // BASE_CALLBACK_HELPERS_H_ diff --git a/base/callback_internal.cc b/base/callback_internal.cc new file mode 100644 index 0000000000..5acade0ccb --- /dev/null +++ b/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_.get() || bind_state_->HasOneRef()); +} + +CallbackBase::~CallbackBase() { +} + +} // namespace internal +} // namespace base diff --git a/base/callback_internal.h b/base/callback_internal.h new file mode 100644 index 0000000000..5993824a5a --- /dev/null +++ b/base/callback_internal.h @@ -0,0 +1,184 @@ +// Copyright (c) 2012 The Chromium 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 + +#include "base/base_export.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" + +template +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 { + protected: + friend class RefCountedThreadSafe; + 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 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 +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 +struct CallbackParamTraits { + 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 +struct CallbackParamTraits { + typedef const T* ForwardType; + typedef const T* StorageType; +}; + +// See comment for CallbackParamTraits. +template +struct CallbackParamTraits { + 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 +struct CallbackParamTraits > { + typedef scoped_ptr ForwardType; + typedef scoped_ptr StorageType; +}; + +template +struct CallbackParamTraits > { + typedef scoped_ptr_malloc ForwardType; + typedef scoped_ptr_malloc StorageType; +}; + +template +struct CallbackParamTraits > { + typedef ScopedVector ForwardType; + typedef ScopedVector 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 +T& CallbackForward(T& t) { return t; } + +template +scoped_ptr CallbackForward(scoped_ptr& p) { return p.Pass(); } + +template +scoped_ptr_malloc CallbackForward(scoped_ptr_malloc& p) { + return p.Pass(); +} + +template +ScopedVector CallbackForward(ScopedVector& p) { return p.Pass(); } + +} // namespace internal +} // namespace base + +#endif // BASE_CALLBACK_INTERNAL_H_ diff --git a/base/callback_unittest.cc b/base/callback_unittest.cc new file mode 100644 index 0000000000..6103b2e967 --- /dev/null +++ b/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 +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 + : public BindStateBase { + public: + typedef FakeInvoker InvokerType; +}; + +template <> +struct BindState + : public BindStateBase { + public: + typedef FakeInvoker InvokerType; +}; +} // namespace internal + +namespace { + +typedef internal::BindState + FakeBindState1; +typedef internal::BindState + FakeBindState2; + +class CallbackTest : public ::testing::Test { + public: + CallbackTest() + : callback_a_(new FakeBindState1()), + callback_b_(new FakeBindState2()) { + } + + virtual ~CallbackTest() { + } + + protected: + Callback callback_a_; + const Callback callback_b_; // Ensure APIs work with const. + Callback 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 c0; + Callback c1; + Callback c2; + Callback c3; + Callback c4; + Callback c5; + Callback 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 callback_c(new FakeBindState1()); + Callback 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 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 { + public: + explicit 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; + 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/base/callback_unittest.nc b/base/callback_unittest.nc new file mode 100644 index 0000000000..9bddd1f836 --- /dev/null +++ b/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::Equals\(base::Callback&\)'"] + +// 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 c2; + c1.Equals(c2); +} + +#elif defined(NCTEST_CONSTRUCTION_FROM_SUBTYPE) // [r"conversion from 'base::Callback' to non-scalar type 'base::Callback'"] + +// Construction of Callback from Callback 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 cb_a; + Callback cb_b = cb_a; +} + +#elif defined(NCTEST_ASSIGNMENT_FROM_SUBTYPE) // [r"no match for 'operator=' in 'cb_a = cb_b'"] + +// Assignment of Callback from Callback if A is supertype of B. +// See explanation for NCTEST_CONSTRUCTION_FROM_SUBTYPE +void WontCompile() { + Callback cb_a; + Callback cb_b; + cb_a = cb_b; +} + +#endif + +} // namespace base diff --git a/base/cancelable_callback.h b/base/cancelable_callback.h new file mode 100644 index 0000000000..8ef01996a9 --- /dev/null +++ b/base/cancelable_callback.h @@ -0,0 +1,272 @@ +// Copyright (c) 2011 The Chromium 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()->QuitWhenIdle(); +// } +// +// 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 +class CancelableCallback; + +template <> +class CancelableCallback { + public: + CancelableCallback() : weak_factory_(this) {} + + // |callback| must not be null. + explicit CancelableCallback(const base::Callback& callback) + : 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& 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& 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::Forward, + weak_factory_.GetWeakPtr()); + } + + // Used to ensure Forward() is not run when this object is destroyed. + base::WeakPtrFactory > weak_factory_; + + // The wrapper closure. + base::Callback forwarder_; + + // The stored closure that may be cancelled. + base::Callback callback_; + + DISALLOW_COPY_AND_ASSIGN(CancelableCallback); +}; + +template +class CancelableCallback { + public: + CancelableCallback() : weak_factory_(this) {} + + // |callback| must not be null. + explicit CancelableCallback(const base::Callback& callback) + : 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& 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& 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::Forward, + weak_factory_.GetWeakPtr()); + } + + // Used to ensure Forward() is not run when this object is destroyed. + base::WeakPtrFactory > weak_factory_; + + // The wrapper closure. + base::Callback forwarder_; + + // The stored closure that may be cancelled. + base::Callback callback_; + + DISALLOW_COPY_AND_ASSIGN(CancelableCallback); +}; + +template +class CancelableCallback { + public: + CancelableCallback() : weak_factory_(this) {} + + // |callback| must not be null. + explicit CancelableCallback(const base::Callback& callback) + : 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& 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& callback() const { + return forwarder_; + } + + private: + void Forward(A1 a1, A2 a2) const { + callback_.Run(a1, a2); + } + + // Helper method to bind |forwarder_| using a weak pointer from + // |weak_factory_|. + void InitializeForwarder() { + forwarder_ = base::Bind(&CancelableCallback::Forward, + weak_factory_.GetWeakPtr()); + } + + // Used to ensure Forward() is not run when this object is destroyed. + base::WeakPtrFactory > weak_factory_; + + // The wrapper closure. + base::Callback forwarder_; + + // The stored closure that may be cancelled. + base::Callback callback_; + + DISALLOW_COPY_AND_ASSIGN(CancelableCallback); +}; + +typedef CancelableCallback CancelableClosure; + +} // namespace base + +#endif // BASE_CANCELABLE_CALLBACK_H_ diff --git a/base/cancelable_callback_unittest.cc b/base/cancelable_callback_unittest.cc new file mode 100644 index 0000000000..89c603c953 --- /dev/null +++ b/base/cancelable_callback_unittest.cc @@ -0,0 +1,184 @@ +// Copyright (c) 2011 The Chromium Authors. All 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/message_loop.h" +#include "base/run_loop.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace { + +class TestRefCounted : public RefCountedThreadSafe { + private: + friend class RefCountedThreadSafe; + ~TestRefCounted() {}; +}; + +void Increment(int* count) { (*count)++; } +void IncrementBy(int* count, int n) { (*count) += n; } +void RefCountedParam(const scoped_refptr& 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 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()); + RunLoop().RunUntilIdle(); + + EXPECT_EQ(1, count); + + MessageLoop::current()->PostTask(FROM_HERE, cancelable.callback()); + + // Cancel before running the message loop. + cancelable.Cancel(); + RunLoop().RunUntilIdle(); + + // Callback never ran due to cancellation; count is the same. + EXPECT_EQ(1, count); +} + +} // namespace +} // namespace base diff --git a/base/check_example.cc b/base/check_example.cc new file mode 100644 index 0000000000..4b3f4287db --- /dev/null +++ b/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/base/chromeos/chromeos_version.cc b/base/chromeos/chromeos_version.cc new file mode 100644 index 0000000000..4a70cd5312 --- /dev/null +++ b/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 +#include + +#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/base/chromeos/chromeos_version.h b/base/chromeos/chromeos_version.h new file mode 100644 index 0000000000..25acd43a9f --- /dev/null +++ b/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/base/command_line.cc b/base/command_line.cc new file mode 100644 index 0000000000..36ac88f12c --- /dev/null +++ b/base/command_line.cc @@ -0,0 +1,424 @@ +// Copyright (c) 2012 The Chromium Authors. All 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 +#include + +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "build/build_config.h" + +#if defined(OS_WIN) +#include +#include +#endif + +using base::FilePath; + +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) +// 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) + 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) + 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) + 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_; +} + +// static +bool CommandLine::InitializedForCurrentProcess() { + 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); + switches_.clear(); + 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) + 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) + 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) + 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/base/command_line.h b/base/command_line.h new file mode 100644 index 0000000000..ed46c4f0d1 --- /dev/null +++ b/base/command_line.h @@ -0,0 +1,178 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// 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 +#include +#include +#include + +#include "base/base_export.h" +#include "build/build_config.h" + +namespace base { +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) + typedef std::string StringType; +#endif + + typedef StringType::value_type CharType; + typedef std::vector StringVector; + typedef std::map 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 base::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(); + + // Returns true if the CommandLine has been initialized for the given process. + static bool InitializedForCurrentProcess(); + +#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). + base::FilePath GetProgram() const; + void SetProgram(const base::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; + base::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 base::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 base::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/base/command_line_unittest.cc b/base/command_line_unittest.cc new file mode 100644 index 0000000000..2c947fc39a --- /dev/null +++ b/base/command_line_unittest.cc @@ -0,0 +1,360 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include "base/basictypes.h" +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::FilePath; + +// 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::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::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) + 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/base/compiler_specific.h b/base/compiler_specific.h new file mode 100644 index 0000000000..07b680b784 --- /dev/null +++ b/base/compiler_specific.h @@ -0,0 +1,184 @@ +// Copyright (c) 2012 The Chromium Authors. All 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 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 + +#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 NON_EXPORTED_BASE(code) code + +#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) +#define OVERRIDE override +#elif defined(__clang__) +#define OVERRIDE override +#else +#define OVERRIDE +#endif + +// Annotate a virtual method indicating that subclasses must not override it, +// or annotate a class to indicate that it cannot be subclassed. +// Use like: +// virtual void foo() FINAL; +// class B FINAL : public A {}; +#if defined(COMPILER_MSVC) +// TODO(jered): Change this to "final" when chromium no longer uses MSVC 2010. +#define FINAL sealed +#elif defined(__clang__) +#define FINAL final +#else +#define FINAL +#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 . +#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))) + + +// MemorySanitizer annotations. +#ifdef MEMORY_SANITIZER +extern "C" { +void __msan_unpoison(const void *p, unsigned long s); +} // extern "C" + +// Mark a memory region fully initialized. +// Use this to annotate code that deliberately reads uninitialized data, for +// example a GC scavenging root set pointers from the stack. +#define MSAN_UNPOISON(p, s) __msan_unpoison(p, s) +#else // MEMORY_SANITIZER +#define MSAN_UNPOISON(p, s) +#endif // MEMORY_SANITIZER + +#endif // BASE_COMPILER_SPECIFIC_H_ diff --git a/base/containers/hash_tables.h b/base/containers/hash_tables.h new file mode 100644 index 0000000000..2a2b52f9da --- /dev/null +++ b/base/containers/hash_tables.h @@ -0,0 +1,264 @@ +// Copyright (c) 2011 The Chromium 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 my_map; +// base::hash_set 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_CONTAINERS_HASH_TABLES_H_ +#define BASE_CONTAINERS_HASH_TABLES_H_ + +#include + +#include "base/basictypes.h" +#include "base/strings/string16.h" +#include "build/build_config.h" + +#if defined(COMPILER_MSVC) +#include +#include + +#define BASE_HASH_NAMESPACE stdext + +#elif defined(COMPILER_GCC) +#if defined(OS_ANDROID) +#define BASE_HASH_NAMESPACE std +#else +#define BASE_HASH_NAMESPACE __gnu_cxx +#endif + +// 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 and . +#ifdef __DEPRECATED +#define CHROME_OLD__DEPRECATED __DEPRECATED +#undef __DEPRECATED +#endif + +#if defined(OS_ANDROID) +#include +#include +#else +#include +#include +#endif + +#include + +#ifdef CHROME_OLD__DEPRECATED +#define __DEPRECATED CHROME_OLD__DEPRECATED +#undef CHROME_OLD__DEPRECATED +#endif + +namespace BASE_HASH_NAMESPACE { + +#if !defined(OS_ANDROID) +// 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 { \ + std::size_t operator()(integral_type value) const { \ + return static_cast(value); \ + } \ + } + +DEFINE_TRIVIAL_HASH(long long); +DEFINE_TRIVIAL_HASH(unsigned long long); + +#undef DEFINE_TRIVIAL_HASH +#endif // !defined(OS_ANDROID) + +// 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 . It is duplicated here because GCC +// versions prior to 4.3.2 are unable to compile when RTTI +// is disabled, as it is in our build. + +#define DEFINE_STRING_HASH(string_type) \ + template<> \ + struct hash { \ + 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; \ + } \ + } + +DEFINE_STRING_HASH(std::string); +DEFINE_STRING_HASH(string16); + +#undef DEFINE_STRING_HASH + +} // namespace BASE_HASH_NAMESPACE + +#else // COMPILER +#error define BASE_HASH_NAMESPACE for your compiler +#endif // COMPILER + +namespace base { +using BASE_HASH_NAMESPACE::hash_map; +using BASE_HASH_NAMESPACE::hash_multimap; +using BASE_HASH_NAMESPACE::hash_multiset; +using BASE_HASH_NAMESPACE::hash_set; + +// Implement hashing for pairs of at-most 32 bit integer values. +// When size_t is 32 bits, we turn the 64-bit hash code into 32 bits by using +// multiply-add hashing. This algorithm, as described in +// Theorem 4.3.3 of the thesis "Ãœber die Komplexität der Multiplikation in +// eingeschränkten Branchingprogrammmodellen" by Woelfel, is: +// +// h32(x32, y32) = (h64(x32, y32) * rand_odd64 + rand16 * 2^16) % 2^64 / 2^32 +// +// Contact danakj@chromium.org for any questions. +inline std::size_t HashInts32(uint32 value1, uint32 value2) { + uint64 value1_64 = value1; + uint64 hash64 = (value1_64 << 32) | value2; + + if (sizeof(std::size_t) >= sizeof(uint64)) + return static_cast(hash64); + + uint64 odd_random = 481046412LL << 32 | 1025306955LL; + uint32 shift_random = 10121U << 16; + + hash64 = hash64 * odd_random + shift_random; + std::size_t high_bits = static_cast( + hash64 >> (sizeof(uint64) - sizeof(std::size_t))); + return high_bits; +} + +// Implement hashing for pairs of up-to 64-bit integer values. +// We use the compound integer hash method to produce a 64-bit hash code, by +// breaking the two 64-bit inputs into 4 32-bit values: +// http://opendatastructures.org/versions/edition-0.1d/ods-java/node33.html#SECTION00832000000000000000 +// Then we reduce our result to 32 bits if required, similar to above. +inline std::size_t HashInts64(uint64 value1, uint64 value2) { + uint32 short_random1 = 842304669U; + uint32 short_random2 = 619063811U; + uint32 short_random3 = 937041849U; + uint32 short_random4 = 3309708029U; + + uint32 value1a = static_cast(value1 & 0xffffffff); + uint32 value1b = static_cast((value1 >> 32) & 0xffffffff); + uint32 value2a = static_cast(value2 & 0xffffffff); + uint32 value2b = static_cast((value2 >> 32) & 0xffffffff); + + uint64 product1 = static_cast(value1a) * short_random1; + uint64 product2 = static_cast(value1b) * short_random2; + uint64 product3 = static_cast(value2a) * short_random3; + uint64 product4 = static_cast(value2b) * short_random4; + + uint64 hash64 = product1 + product2 + product3 + product4; + + if (sizeof(std::size_t) >= sizeof(uint64)) + return static_cast(hash64); + + uint64 odd_random = 1578233944LL << 32 | 194370989LL; + uint32 shift_random = 20591U << 16; + + hash64 = hash64 * odd_random + shift_random; + std::size_t high_bits = static_cast( + hash64 >> (sizeof(uint64) - sizeof(std::size_t))); + return high_bits; +} + +#define DEFINE_32BIT_PAIR_HASH(Type1, Type2) \ +inline std::size_t HashPair(Type1 value1, Type2 value2) { \ + return HashInts32(value1, value2); \ +} + +DEFINE_32BIT_PAIR_HASH(int16, int16); +DEFINE_32BIT_PAIR_HASH(int16, uint16); +DEFINE_32BIT_PAIR_HASH(int16, int32); +DEFINE_32BIT_PAIR_HASH(int16, uint32); +DEFINE_32BIT_PAIR_HASH(uint16, int16); +DEFINE_32BIT_PAIR_HASH(uint16, uint16); +DEFINE_32BIT_PAIR_HASH(uint16, int32); +DEFINE_32BIT_PAIR_HASH(uint16, uint32); +DEFINE_32BIT_PAIR_HASH(int32, int16); +DEFINE_32BIT_PAIR_HASH(int32, uint16); +DEFINE_32BIT_PAIR_HASH(int32, int32); +DEFINE_32BIT_PAIR_HASH(int32, uint32); +DEFINE_32BIT_PAIR_HASH(uint32, int16); +DEFINE_32BIT_PAIR_HASH(uint32, uint16); +DEFINE_32BIT_PAIR_HASH(uint32, int32); +DEFINE_32BIT_PAIR_HASH(uint32, uint32); + +#undef DEFINE_32BIT_PAIR_HASH + +#define DEFINE_64BIT_PAIR_HASH(Type1, Type2) \ +inline std::size_t HashPair(Type1 value1, Type2 value2) { \ + return HashInts64(value1, value2); \ +} + +DEFINE_64BIT_PAIR_HASH(int16, int64); +DEFINE_64BIT_PAIR_HASH(int16, uint64); +DEFINE_64BIT_PAIR_HASH(uint16, int64); +DEFINE_64BIT_PAIR_HASH(uint16, uint64); +DEFINE_64BIT_PAIR_HASH(int32, int64); +DEFINE_64BIT_PAIR_HASH(int32, uint64); +DEFINE_64BIT_PAIR_HASH(uint32, int64); +DEFINE_64BIT_PAIR_HASH(uint32, uint64); +DEFINE_64BIT_PAIR_HASH(int64, int16); +DEFINE_64BIT_PAIR_HASH(int64, uint16); +DEFINE_64BIT_PAIR_HASH(int64, int32); +DEFINE_64BIT_PAIR_HASH(int64, uint32); +DEFINE_64BIT_PAIR_HASH(int64, int64); +DEFINE_64BIT_PAIR_HASH(int64, uint64); +DEFINE_64BIT_PAIR_HASH(uint64, int16); +DEFINE_64BIT_PAIR_HASH(uint64, uint16); +DEFINE_64BIT_PAIR_HASH(uint64, int32); +DEFINE_64BIT_PAIR_HASH(uint64, uint32); +DEFINE_64BIT_PAIR_HASH(uint64, int64); +DEFINE_64BIT_PAIR_HASH(uint64, uint64); + +#undef DEFINE_64BIT_PAIR_HASH +} // namespace base + +namespace BASE_HASH_NAMESPACE { + +// Implement methods for hashing a pair of integers, so they can be used as +// keys in STL containers. + +#if defined(COMPILER_MSVC) + +template +inline std::size_t hash_value(const std::pair& value) { + return base::HashPair(value.first, value.second); +} + +#elif defined(COMPILER_GCC) +template +struct hash > { + std::size_t operator()(std::pair value) const { + return base::HashPair(value.first, value.second); + } +}; + +#else +#error define hash > for your compiler +#endif // COMPILER + +} + +#undef DEFINE_PAIR_HASH_FUNCTION_START +#undef DEFINE_PAIR_HASH_FUNCTION_END + +#endif // BASE_CONTAINERS_HASH_TABLES_H_ diff --git a/base/containers/hash_tables_unittest.cc b/base/containers/hash_tables_unittest.cc new file mode 100644 index 0000000000..65724ecae2 --- /dev/null +++ b/base/containers/hash_tables_unittest.cc @@ -0,0 +1,53 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/containers/hash_tables.h" + +#include "base/basictypes.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class HashPairTest : public testing::Test { +}; + +#define INSERT_PAIR_TEST(Type, value1, value2) \ + { \ + Type pair(value1, value2); \ + base::hash_map map; \ + map[pair] = 1; \ + } + +// Verify that a hash_map can be constructed for pairs of integers of various +// sizes. +TEST_F(HashPairTest, IntegerPairs) { + typedef std::pair Int16Int16Pair; + typedef std::pair Int16Int32Pair; + typedef std::pair Int16Int64Pair; + + INSERT_PAIR_TEST(Int16Int16Pair, 4, 6); + INSERT_PAIR_TEST(Int16Int32Pair, 9, (1 << 29) + 378128932); + INSERT_PAIR_TEST(Int16Int64Pair, 10, + (GG_INT64_C(1) << 60) + GG_INT64_C(78931732321)); + + typedef std::pair Int32Int16Pair; + typedef std::pair Int32Int32Pair; + typedef std::pair Int32Int64Pair; + + INSERT_PAIR_TEST(Int32Int16Pair, 4, 6); + INSERT_PAIR_TEST(Int32Int32Pair, 9, (1 << 29) + 378128932); + INSERT_PAIR_TEST(Int32Int64Pair, 10, + (GG_INT64_C(1) << 60) + GG_INT64_C(78931732321)); + + typedef std::pair Int64Int16Pair; + typedef std::pair Int64Int32Pair; + typedef std::pair Int64Int64Pair; + + INSERT_PAIR_TEST(Int64Int16Pair, 4, 6); + INSERT_PAIR_TEST(Int64Int32Pair, 9, (1 << 29) + 378128932); + INSERT_PAIR_TEST(Int64Int64Pair, 10, + (GG_INT64_C(1) << 60) + GG_INT64_C(78931732321)); +} + +} // namespace diff --git a/base/containers/linked_list.h b/base/containers/linked_list.h new file mode 100644 index 0000000000..25bbe762cb --- /dev/null +++ b/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 { +// ... +// }; +// +// Next, to keep track of the list's head/tail, use a LinkedList instance: +// +// LinkedList list; +// +// To add elements to the list, use any of LinkedList::Append, +// LinkNode::InsertBefore, or LinkNode::InsertAfter: +// +// LinkNode* n1 = ...; +// LinkNode* n2 = ...; +// LinkNode* n3 = ...; +// +// list.Append(n1); +// list.Append(n3); +// n3->InsertBefore(n3); +// +// Lastly, to iterate through the linked list forwards: +// +// for (LinkNode* node = list.head(); +// node != list.end(); +// node = node->next()) { +// MyNodeType* value = node->value(); +// ... +// } +// +// Or to iterate the linked list backwards: +// +// for (LinkNode* 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 to std::list: +// +// * Erasing an element of type T* from base::LinkedList is +// an O(1) operation. Whereas for std::list it is O(n). +// That is because with std::list you must obtain an +// iterator to the T* element before you can call erase(iterator). +// +// * Insertion operations with base::LinkedList 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, the type being inserted already reserves +// space for the "next" and "previous" pointers (base::LinkNode*). +// Whereas with std::list 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 +class LinkNode { + public: + LinkNode() : previous_(0), next_(0) {} + LinkNode(LinkNode* previous, LinkNode* next) + : previous_(previous), next_(next) {} + + // Insert |this| into the linked list, before |e|. + void InsertBefore(LinkNode* 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* 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* previous() const { + return previous_; + } + + LinkNode* next() const { + return next_; + } + + // Cast from the node-type to the value type. + const T* value() const { + return static_cast(this); + } + + T* value() { + return static_cast(this); + } + + private: + LinkNode* previous_; + LinkNode* next_; +}; + +template +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* e) { + e->InsertBefore(&root_); + } + + LinkNode* head() const { + return root_.next(); + } + + LinkNode* tail() const { + return root_.previous(); + } + + const LinkNode* end() const { + return &root_; + } + + private: + LinkNode root_; +}; + +} // namespace base + +#endif // BASE_CONTAINERS_LINKED_LIST_H_ diff --git a/base/containers/linked_list_unittest.cc b/base/containers/linked_list_unittest.cc new file mode 100644 index 0000000000..801e3028a0 --- /dev/null +++ b/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 { + 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 { + 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& list, + int num_nodes, const int* node_ids, bool forward) { + int i = 0; + for (const LinkNode* 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& 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 list; + EXPECT_EQ(list.end(), list.head()); + EXPECT_EQ(list.end(), list.tail()); + ExpectListContents(list, 0, NULL); +} + +TEST(LinkedList, Append) { + LinkedList 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 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 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 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/base/containers/mru_cache.h b/base/containers/mru_cache.h new file mode 100644 index 0000000000..e59e909839 --- /dev/null +++ b/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 +#include +#include + +#include "base/basictypes.h" +#include "base/containers/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 +struct MRUCacheStandardMap { + typedef std::map 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 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 value_type; + + private: + typedef std::list PayloadList; + typedef typename MapType::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 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 MRUCache : public MRUCacheBase > { + private: + typedef MRUCacheBase > 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 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 OwningMRUCache + : public MRUCacheBase > { + private: + typedef MRUCacheBase > 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 +struct MRUCacheHashMap { + typedef base::hash_map 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 HashingMRUCache : public MRUCacheBase, + MRUCacheHashMap> { + private: + typedef MRUCacheBase, + 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/base/containers/mru_cache_unittest.cc b/base/containers/mru_cache_unittest.cc new file mode 100644 index 0000000000..a8e893213c --- /dev/null +++ b/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++; + } + + explicit 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 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 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 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 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 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 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/base/containers/small_map.h b/base/containers/small_map.h new file mode 100644 index 0000000000..df3d22ae9a --- /dev/null +++ b/base/containers/small_map.h @@ -0,0 +1,652 @@ +// Copyright (c) 2012 The Chromium Authors. All 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 +#include +#include + +#include "base/basictypes.h" +#include "base/containers/hash_tables.h" +#include "base/logging.h" +#include "base/memory/manual_constructor.h" + +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, 4, std::equal_to > +// +// +// 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* 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 > 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 +class SmallMapDefaultInit { + public: + void operator()(ManualConstructor* map) const { + map->Init(); + } +}; + +// has_key_equal::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 +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 static big test(typename U::key_equal*); + template 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(0)) == sizeof(big)); +}; +template const bool has_key_equal::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 +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 +struct select_equal_key< std::map, false> { + struct equal_key { + bool operator()(const KeyType& left, const KeyType& right) { + return left == right; + } + }; +}; +template +struct select_equal_key< base::hash_map, 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 +struct select_equal_key { + typedef typename M::key_equal equal_key; +}; + +} // namespace internal + +template ::value>::equal_key, + typename MapInit = internal::SmallMapDefaultInit > +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, 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* init) + : array_iter_(init) {} + inline explicit iterator(const typename NormalMap::iterator& init) + : array_iter_(NULL), hash_iter_(init) {} + + ManualConstructor* 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* init) + : array_iter_(init) {} + inline explicit const_iterator( + const typename NormalMap::const_iterator& init) + : array_iter_(NULL), hash_iter_(init) {} + + const ManualConstructor* 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 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 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 ret = map_->insert(x); + return std::make_pair(iterator(ret.first), ret.second); + } + } + + // Invalidates iterators. + template + 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) { + int i = position.array_iter_ - array_; + 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_); + } 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 dummy_; + ManualConstructor array_[kArraySize]; + ManualConstructor map_; + }; + + void ConvertToRealMap() { + // Move the current elements into a temporary array. + ManualConstructor 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 +inline bool SmallMap::iterator::operator==( + const const_iterator& other) const { + return other == *this; +} +template +inline bool SmallMap::iterator::operator!=( + const const_iterator& other) const { + return other != *this; +} + +} // namespace base + +#endif // BASE_CONTAINERS_SMALL_MAP_H_ diff --git a/base/containers/small_map_unittest.cc b/base/containers/small_map_unittest.cc new file mode 100644 index 0000000000..b911bee655 --- /dev/null +++ b/base/containers/small_map_unittest.cc @@ -0,0 +1,492 @@ +// Copyright (c) 2012 The Chromium Authors. All 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 + +#include +#include +#include + +#include "base/containers/hash_tables.h" +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +TEST(SmallMap, General) { + SmallMap > 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 >::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 >& ref = m; + EXPECT_TRUE(ref.find(1234) != m.end()); + EXPECT_TRUE(ref.find(5678) == m.end()); +} + +TEST(SmallMap, PostFixIteratorIncrement) { + SmallMap > m; + m[0] = 5; + m[2] = 3; + + { + SmallMap >::iterator iter(m.begin()); + SmallMap >::iterator last(iter++); + ++last; + EXPECT_TRUE(last == iter); + } + + { + SmallMap >::const_iterator iter(m.begin()); + SmallMap >::const_iterator last(iter++); + ++last; + EXPECT_TRUE(last == iter); + } +} + +// Based on the General testcase. +TEST(SmallMap, CopyConstructor) { + SmallMap > src; + + { + SmallMap > m(src); + EXPECT_TRUE(m.empty()); + } + + src[0] = 5; + + { + SmallMap > m(src); + EXPECT_FALSE(m.empty()); + EXPECT_EQ(m.size(), 1u); + } + + src[9] = 2; + + { + SmallMap > 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 > 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 +static void SmallMapToMap(SmallMap const& src, inner* dest) { + typename SmallMap::const_iterator it; + for (it = src.begin(); it != src.end(); ++it) { + dest->insert(std::make_pair(it->first, it->second)); + } +} + +template +static bool SmallMapEqual(SmallMap const& a, + SmallMap 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 > src_small; + SmallMap > 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 > dest_small; + dest_small = src_small; + EXPECT_TRUE(SmallMapEqual(dest_small, src_small)); + EXPECT_EQ(dest_small.UsingFullMap(), + src_small.UsingFullMap()); + + SmallMap > 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 > 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 >::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 >::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(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 normal_map; + for (int i = 1; i <= elements; ++i) { + normal_map.insert(std::make_pair(i, 100*i)); + } + + SmallMap > 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 > m; + SmallMap >::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, 4, std::equal_to > 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, 4, std::equal_to >::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 > hm; + EXPECT_EQ(0u, hm.size()); + SmallMap > m; + EXPECT_EQ(0u, m.size()); +} + +namespace { + +class hash_map_add_item : public hash_map { + public: + hash_map_add_item() : hash_map() {} + explicit hash_map_add_item(const std::pair& item) + : hash_map() { + insert(item); + } +}; + +void InitMap(ManualConstructor* 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* map_ctor) const { + map_ctor->Init(std::make_pair(item_, item_)); + } + + int item_; +}; + +} // anonymous namespace + +TEST(SmallMap, SubclassInitializationWithFunctionPointer) { + SmallMap, + void (&)(ManualConstructor*)> 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_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/base/containers/stack_container.h b/base/containers/stack_container.h new file mode 100644 index 0000000000..f0106d73f2 --- /dev/null +++ b/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 +#include + +#include "base/basictypes.h" +#include "base/memory/aligned_memory.h" +#include "base/strings/string16.h" +#include "build/build_config.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 +class StackAllocator : public std::allocator { + public: + typedef typename std::allocator::pointer pointer; + typedef typename std::allocator::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(); } + const T* stack_buffer() const { + return stack_buffer_.template data_as(); + } + + // 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 stack_buffer_; +#if defined(__GNUC__) && !defined(ARCH_CPU_X86_FAMILY) + 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 + struct rebind { + typedef StackAllocator other; + }; + + // For the straight up copy c-tor, we can share storage. + StackAllocator(const StackAllocator& rhs) + : std::allocator(), 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 ) + // 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 + StackAllocator(const StackAllocator& 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::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::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 +class StackContainer { + public: + typedef TContainerType ContainerType; + typedef typename ContainerType::value_type ContainedType; + typedef StackAllocator 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 +class StackString : public StackContainer< + std::basic_string, + StackAllocator >, + stack_capacity> { + public: + StackString() : StackContainer< + std::basic_string, + StackAllocator >, + stack_capacity>() { + } + + private: + DISALLOW_COPY_AND_ASSIGN(StackString); +}; + +// StackStrin16 ---------------------------------------------------------------- + +template +class StackString16 : public StackContainer< + std::basic_string >, + stack_capacity> { + public: + StackString16() : StackContainer< + std::basic_string >, + stack_capacity>() { + } + + private: + DISALLOW_COPY_AND_ASSIGN(StackString16); +}; + +// StackVector ----------------------------------------------------------------- + +// Example: +// StackVector foo; +// foo->push_back(22); // we have overloaded operator-> +// foo[0] = 10; // as well as operator[] +template +class StackVector : public StackContainer< + std::vector >, + stack_capacity> { + public: + StackVector() : StackContainer< + std::vector >, + 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& other) + : StackContainer< + std::vector >, + stack_capacity>() { + this->container().assign(other->begin(), other->end()); + } + + StackVector& operator=( + const StackVector& 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/base/containers/stack_container_unittest.cc b/base/containers/stack_container_unittest.cc new file mode 100644 index 0000000000..e6c19145bf --- /dev/null +++ b/base/containers/stack_container_unittest.cc @@ -0,0 +1,143 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/containers/stack_container.h" + +#include + +#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 { + public: + explicit Dummy(int* alive) : alive_(alive) { + ++*alive_; + } + + private: + friend class base::RefCounted; + + ~Dummy() { + --*alive_; + } + + int* const alive_; +}; + +} // namespace + +TEST(StackContainer, Vector) { + const int stack_size = 3; + StackVector 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(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 > 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, 2> Vector; + typedef Vector::ContainerType Container; + Vector vect; + + int alive = 0; + scoped_refptr 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 +class AlignedData { + public: + AlignedData() { memset(data_.void_data(), 0, alignment); } + ~AlignedData() {} + base::AlignedMemory data_; +}; + +} // anonymous namespace + +#define EXPECT_ALIGNED(ptr, align) \ + EXPECT_EQ(0u, reinterpret_cast(ptr) & (align - 1)) + +TEST(StackContainer, BufferAlignment) { + StackVector text; + text->push_back(L'A'); + EXPECT_ALIGNED(&text[0], ALIGNOF(wchar_t)); + + StackVector doubles; + doubles->push_back(0.0); + EXPECT_ALIGNED(&doubles[0], ALIGNOF(double)); + + StackVector, 1> aligned16; + aligned16->push_back(AlignedData<16>()); + EXPECT_ALIGNED(&aligned16[0], 16); + +#if !defined(__GNUC__) || defined(ARCH_CPU_X86_FAMILY) + // It seems that non-X86 gcc doesn't respect greater than 16 byte alignment. + // See http://gcc.gnu.org/bugzilla/show_bug.cgi?id=33721 for details. + // TODO(sbc):re-enable this if GCC starts respecting higher alignments. + StackVector, 1> aligned256; + aligned256->push_back(AlignedData<256>()); + EXPECT_ALIGNED(&aligned256[0], 256); +#endif +} + +template class StackVector; +template class StackVector, 2>; + +} // namespace base diff --git a/base/cpu.cc b/base/cpu.cc new file mode 100644 index 0000000000..1761529e04 --- /dev/null +++ b/base/cpu.cc @@ -0,0 +1,162 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/cpu.h" + +#include + +#include + +#include "build/build_config.h" + +#if defined(ARCH_CPU_X86_FAMILY) +#if defined(_MSC_VER) +#include +#endif +#endif + +namespace base { + +CPU::CPU() + : signature_(0), + 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), + has_non_stop_time_stamp_counter_(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); + signature_ = cpu_info[0]; + 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; + has_avx_ = (cpu_info[2] & 0x10000000) != 0; + } + + // Get the brand string of the cpu. + __cpuid(cpu_info, 0x80000000); + const int parameter_end = 0x80000004; + int max_parameter = cpu_info[0]; + + 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); + } + + const int parameter_containing_non_stop_time_stamp_counter = 0x80000007; + if (max_parameter >= parameter_containing_non_stop_time_stamp_counter) { + __cpuid(cpu_info, parameter_containing_non_stop_time_stamp_counter); + has_non_stop_time_stamp_counter_ = (cpu_info[3] & (1 << 8)) != 0; + } +#endif +} + +CPU::IntelMicroArchitecture CPU::GetIntelMicroArchitecture() const { + if (has_avx()) return AVX; + if (has_sse42()) return SSE42; + if (has_sse41()) return SSE41; + if (has_ssse3()) return SSSE3; + if (has_sse3()) return SSE3; + if (has_sse2()) return SSE2; + if (has_sse()) return SSE; + return PENTIUM; +} + +} // namespace base diff --git a/base/cpu.h b/base/cpu.h new file mode 100644 index 0000000000..509763e170 --- /dev/null +++ b/base/cpu.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_CPU_H_ +#define BASE_CPU_H_ + +#include + +#include "base/base_export.h" + +namespace base { + +// Query information about the processor. +class BASE_EXPORT CPU { + public: + // Constructor + CPU(); + + enum IntelMicroArchitecture { + PENTIUM, + SSE, + SSE2, + SSE3, + SSSE3, + SSE41, + SSE42, + AVX, + MAX_INTEL_MICRO_ARCHITECTURE + }; + + // Accessors for CPU information. + const std::string& vendor_name() const { return cpu_vendor_; } + int signature() const { return signature_; } + 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_; } + bool has_avx() const { return has_avx_; } + bool has_non_stop_time_stamp_counter() const { + return has_non_stop_time_stamp_counter_; + } + IntelMicroArchitecture GetIntelMicroArchitecture() const; + const std::string& cpu_brand() const { return cpu_brand_; } + + private: + // Query the processor for CPUID information. + void Initialize(); + + int signature_; // raw form of type, family, model, and stepping + 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_; + bool has_avx_; + bool has_non_stop_time_stamp_counter_; + std::string cpu_vendor_; + std::string cpu_brand_; +}; + +} // namespace base + +#endif // BASE_CPU_H_ diff --git a/base/cpu_unittest.cc b/base/cpu_unittest.cc new file mode 100644 index 0000000000..18bf959a55 --- /dev/null +++ b/base/cpu_unittest.cc @@ -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. + +#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; + +// TODO(jschuh): crbug.com/168866 Find a way to enable this on Win64. +#if defined(OS_WIN) && !defined(_M_X64) + 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/base/critical_closure.h b/base/critical_closure.h new file mode 100644 index 0000000000..ca51ed5cef --- /dev/null +++ b/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/base/critical_closure_ios.mm b/base/critical_closure_ios.mm new file mode 100644 index 0000000000..d605cad0a2 --- /dev/null +++ b/base/critical_closure_ios.mm @@ -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/critical_closure.h" + +#import + +#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 { + public: + explicit CriticalClosure(base::Closure* closure) : closure_(closure) { + } + + void Run() { + closure_->Run(); + } + + private: + friend class base::RefCountedThreadSafe; + + virtual ~CriticalClosure() {} + + base::ios::ScopedCriticalAction criticial_action_; + scoped_ptr closure_; + + DISALLOW_COPY_AND_ASSIGN(CriticalClosure); +}; + +} // namespace + +namespace base { + +base::Closure MakeCriticalClosure(const base::Closure& closure) { + DCHECK([[UIDevice currentDevice] isMultitaskingSupported]); + scoped_refptr critical_closure( + new CriticalClosure(new base::Closure(closure))); + return base::Bind(&CriticalClosure::Run, critical_closure.get()); +} + +} // namespace base diff --git a/base/data/file_version_info_unittest/FileVersionInfoTest1.dll b/base/data/file_version_info_unittest/FileVersionInfoTest1.dll new file mode 100755 index 0000000000..bdf8dc0346 Binary files /dev/null and b/base/data/file_version_info_unittest/FileVersionInfoTest1.dll differ diff --git a/base/data/file_version_info_unittest/FileVersionInfoTest2.dll b/base/data/file_version_info_unittest/FileVersionInfoTest2.dll new file mode 100755 index 0000000000..51e7966ce4 Binary files /dev/null and b/base/data/file_version_info_unittest/FileVersionInfoTest2.dll differ diff --git a/base/debug/OWNERS b/base/debug/OWNERS new file mode 100644 index 0000000000..4976ab1e77 --- /dev/null +++ b/base/debug/OWNERS @@ -0,0 +1,3 @@ +per-file trace_event*=nduca@chromium.org +per-file trace_event*=dsinclair@chromium.org +per-file trace_event_android.cc=wangxianzhu@chromium.org diff --git a/base/debug/alias.cc b/base/debug/alias.cc new file mode 100644 index 0000000000..6b0caaa6d1 --- /dev/null +++ b/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/base/debug/alias.h b/base/debug/alias.h new file mode 100644 index 0000000000..3b2ab64f33 --- /dev/null +++ b/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/base/debug/crash_logging.cc b/base/debug/crash_logging.cc new file mode 100644 index 0000000000..6a36a916c6 --- /dev/null +++ b/base/debug/crash_logging.cc @@ -0,0 +1,201 @@ +// Copyright (c) 2012 The Chromium Authors. All 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/crash_logging.h" + +#include +#include + +#include "base/debug/stack_trace.h" +#include "base/format_macros.h" +#include "base/logging.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" + +namespace base { +namespace debug { + +namespace { + +// Global map of crash key names to registration entries. +typedef std::map CrashKeyMap; +CrashKeyMap* g_crash_keys_ = NULL; + +// The maximum length of a single chunk. +size_t g_chunk_max_length_ = 0; + +// String used to format chunked key names. +const char kChunkFormatString[] = "%s-%" PRIuS; + +// The functions that are called to actually set the key-value pairs in the +// crash reportng system. +SetCrashKeyValueFuncT g_set_key_func_ = NULL; +ClearCrashKeyValueFuncT g_clear_key_func_ = NULL; + +// For a given |length|, computes the number of chunks a value of that size +// will occupy. +size_t NumChunksForLength(size_t length) { + return std::ceil(length / static_cast(g_chunk_max_length_)); +} + +// The longest max_length allowed by the system. +const size_t kLargestValueAllowed = 1024; + +} // namespace + +void SetCrashKeyValue(const base::StringPiece& key, + const base::StringPiece& value) { + if (!g_set_key_func_) + return; + + const CrashKey* crash_key = LookupCrashKey(key); + + // TODO(rsesek): Do this: + //DCHECK(crash_key) << "All crash keys must be registered before use " + // << "(key = " << key << ")"; + + // Handle the un-chunked case. + if (!crash_key || crash_key->max_length <= g_chunk_max_length_) { + g_set_key_func_(key, value); + return; + } + + // Unset the unused chunks. + std::vector chunks = + ChunkCrashKeyValue(*crash_key, value, g_chunk_max_length_); + for (size_t i = chunks.size(); + i < NumChunksForLength(crash_key->max_length); + ++i) { + g_clear_key_func_(base::StringPrintf(kChunkFormatString, key.data(), i+1)); + } + + // Set the chunked keys. + for (size_t i = 0; i < chunks.size(); ++i) { + g_set_key_func_(base::StringPrintf(kChunkFormatString, key.data(), i+1), + chunks[i]); + } +} + +void ClearCrashKey(const base::StringPiece& key) { + if (!g_clear_key_func_) + return; + + const CrashKey* crash_key = LookupCrashKey(key); + + // Handle the un-chunked case. + if (!crash_key || crash_key->max_length <= g_chunk_max_length_) { + g_clear_key_func_(key); + return; + } + + for (size_t i = 0; i < NumChunksForLength(crash_key->max_length); ++i) { + g_clear_key_func_(base::StringPrintf(kChunkFormatString, key.data(), i+1)); + } +} + +void SetCrashKeyToStackTrace(const base::StringPiece& key, + const StackTrace& trace) { + size_t count = 0; + const void* const* addresses = trace.Addresses(&count); + SetCrashKeyFromAddresses(key, addresses, count); +} + +void SetCrashKeyFromAddresses(const base::StringPiece& key, + const void* const* addresses, + size_t count) { + std::string value = ""; + if (addresses && count) { + const size_t kBreakpadValueMax = 255; + + std::vector hex_backtrace; + size_t length = 0; + + for (size_t i = 0; i < count; ++i) { + std::string s = base::StringPrintf("%p", addresses[i]); + length += s.length() + 1; + if (length > kBreakpadValueMax) + break; + hex_backtrace.push_back(s); + } + + value = JoinString(hex_backtrace, ' '); + + // Warn if this exceeds the breakpad limits. + DCHECK_LE(value.length(), kBreakpadValueMax); + } + + SetCrashKeyValue(key, value); +} + +ScopedCrashKey::ScopedCrashKey(const base::StringPiece& key, + const base::StringPiece& value) + : key_(key.as_string()) { + SetCrashKeyValue(key, value); +} + +ScopedCrashKey::~ScopedCrashKey() { + ClearCrashKey(key_); +} + +size_t InitCrashKeys(const CrashKey* const keys, size_t count, + size_t chunk_max_length) { + DCHECK(!g_crash_keys_) << "Crash logging may only be initialized once"; + if (!keys) { + delete g_crash_keys_; + g_crash_keys_ = NULL; + return 0; + } + + g_crash_keys_ = new CrashKeyMap; + g_chunk_max_length_ = chunk_max_length; + + size_t total_keys = 0; + for (size_t i = 0; i < count; ++i) { + g_crash_keys_->insert(std::make_pair(keys[i].key_name, keys[i])); + total_keys += NumChunksForLength(keys[i].max_length); + DCHECK_LT(keys[i].max_length, kLargestValueAllowed); + } + DCHECK_EQ(count, g_crash_keys_->size()) + << "Duplicate crash keys were registered"; + + return total_keys; +} + +const CrashKey* LookupCrashKey(const base::StringPiece& key) { + CrashKeyMap::const_iterator it = g_crash_keys_->find(key.as_string()); + if (it == g_crash_keys_->end()) + return NULL; + return &(it->second); +} + +void SetCrashKeyReportingFunctions( + SetCrashKeyValueFuncT set_key_func, + ClearCrashKeyValueFuncT clear_key_func) { + g_set_key_func_ = set_key_func; + g_clear_key_func_ = clear_key_func; +} + +std::vector ChunkCrashKeyValue(const CrashKey& crash_key, + const base::StringPiece& value, + size_t chunk_max_length) { + std::string value_string = value.substr(0, crash_key.max_length).as_string(); + std::vector chunks; + for (size_t offset = 0; offset < value_string.length(); ) { + std::string chunk = value_string.substr(offset, chunk_max_length); + chunks.push_back(chunk); + offset += chunk.length(); + } + return chunks; +} + +void ResetCrashLoggingForTesting() { + delete g_crash_keys_; + g_crash_keys_ = NULL; + g_chunk_max_length_ = 0; + g_set_key_func_ = NULL; + g_clear_key_func_ = NULL; +} + +} // namespace debug +} // namespace base diff --git a/base/debug/crash_logging.h b/base/debug/crash_logging.h new file mode 100644 index 0000000000..376d011655 --- /dev/null +++ b/base/debug/crash_logging.h @@ -0,0 +1,104 @@ +// Copyright (c) 2012 The Chromium Authors. All 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_CRASH_LOGGING_H_ +#define BASE_DEBUG_CRASH_LOGGING_H_ + +#include +#include + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/strings/string_piece.h" + +// These functions add metadata to the upload payload when sending crash reports +// to the crash server. +// +// IMPORTANT: On OS X and Linux, the key/value pairs are only sent as part of +// the upload and are not included in the minidump! + +namespace base { +namespace debug { + +class StackTrace; + +// Set or clear a specific key-value pair from the crash metadata. Keys and +// values are terminated at the null byte. +BASE_EXPORT void SetCrashKeyValue(const base::StringPiece& key, + const base::StringPiece& value); +BASE_EXPORT void ClearCrashKey(const base::StringPiece& key); + +// Records the given StackTrace into a crash key. +BASE_EXPORT void SetCrashKeyToStackTrace(const base::StringPiece& key, + const StackTrace& trace); + +// Formats |count| instruction pointers from |addresses| using %p and +// sets the resulting string as a value for crash key |key|. A maximum of 23 +// items will be encoded, since breakpad limits values to 255 bytes. +BASE_EXPORT void SetCrashKeyFromAddresses(const base::StringPiece& key, + const void* const* addresses, + size_t count); + +// A scoper that sets the specified key to value for the lifetime of the +// object, and clears it on destruction. +class BASE_EXPORT ScopedCrashKey { + public: + ScopedCrashKey(const base::StringPiece& key, const base::StringPiece& value); + ~ScopedCrashKey(); + + private: + std::string key_; + + DISALLOW_COPY_AND_ASSIGN(ScopedCrashKey); +}; + +// Before setting values for a key, all the keys must be registered. +struct BASE_EXPORT CrashKey { + // The name of the crash key, used in the above functions. + const char* const key_name; + + // The maximum length for a value. If the value is longer than this, it will + // be truncated. If the value is larger than the |chunk_max_length| passed to + // InitCrashKeys() but less than this value, it will be split into multiple + // numbered chunks. + size_t max_length; +}; + +// Before the crash key logging mechanism can be used, all crash keys must be +// registered with this function. The function returns the amount of space +// the crash reporting implementation should allocate space for the registered +// crash keys. |chunk_max_length| is the maximum size that a value in a single +// chunk can be. +BASE_EXPORT size_t InitCrashKeys(const CrashKey* const keys, size_t count, + size_t chunk_max_length); + +// Returns the correspnding crash key object or NULL for a given key. +BASE_EXPORT const CrashKey* LookupCrashKey(const base::StringPiece& key); + +// In the platform crash reporting implementation, these functions set and +// clear the NUL-termianted key-value pairs. +typedef void (*SetCrashKeyValueFuncT)(const base::StringPiece&, + const base::StringPiece&); +typedef void (*ClearCrashKeyValueFuncT)(const base::StringPiece&); + +// Sets the function pointers that are used to integrate with the platform- +// specific crash reporting libraries. +BASE_EXPORT void SetCrashKeyReportingFunctions( + SetCrashKeyValueFuncT set_key_func, + ClearCrashKeyValueFuncT clear_key_func); + +// Helper function that breaks up a value according to the parameters +// specified by the crash key object. +BASE_EXPORT std::vector ChunkCrashKeyValue( + const CrashKey& crash_key, + const base::StringPiece& value, + size_t chunk_max_length); + +// Resets the crash key system so it can be reinitialized. For testing only. +BASE_EXPORT void ResetCrashLoggingForTesting(); + +} // namespace debug +} // namespace base + +#endif // BASE_DEBUG_CRASH_LOGGING_H_ diff --git a/base/debug/crash_logging_unittest.cc b/base/debug/crash_logging_unittest.cc new file mode 100644 index 0000000000..8c252f02d8 --- /dev/null +++ b/base/debug/crash_logging_unittest.cc @@ -0,0 +1,182 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/debug/crash_logging.h" + +#include +#include + +#include "base/bind.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +std::map* key_values_ = NULL; + +} // namespace + +class CrashLoggingTest : public testing::Test { + public: + virtual void SetUp() { + key_values_ = new std::map; + base::debug::SetCrashKeyReportingFunctions( + &CrashLoggingTest::SetKeyValue, + &CrashLoggingTest::ClearKeyValue); + } + + virtual void TearDown() { + base::debug::ResetCrashLoggingForTesting(); + + delete key_values_; + key_values_ = NULL; + } + + private: + static void SetKeyValue(const base::StringPiece& key, + const base::StringPiece& value) { + (*key_values_)[key.as_string()] = value.as_string(); + } + + static void ClearKeyValue(const base::StringPiece& key) { + key_values_->erase(key.as_string()); + } +}; + +TEST_F(CrashLoggingTest, SetClearSingle) { + const char* kTestKey = "test-key"; + base::debug::CrashKey keys[] = { { kTestKey, 255 } }; + base::debug::InitCrashKeys(keys, arraysize(keys), 255); + + base::debug::SetCrashKeyValue(kTestKey, "value"); + EXPECT_EQ("value", (*key_values_)[kTestKey]); + + base::debug::ClearCrashKey(kTestKey); + EXPECT_TRUE(key_values_->end() == key_values_->find(kTestKey)); +} + +TEST_F(CrashLoggingTest, SetChunked) { + const char* kTestKey = "chunky"; + const char* kChunk1 = "chunky-1"; + const char* kChunk2 = "chunky-2"; + const char* kChunk3 = "chunky-3"; + base::debug::CrashKey keys[] = { { kTestKey, 15 } }; + base::debug::InitCrashKeys(keys, arraysize(keys), 5); + + std::map& values = *key_values_; + + // Fill only the first chunk. + base::debug::SetCrashKeyValue(kTestKey, "foo"); + EXPECT_EQ(1u, values.size()); + EXPECT_EQ("foo", values[kChunk1]); + EXPECT_TRUE(values.end() == values.find(kChunk2)); + EXPECT_TRUE(values.end() == values.find(kChunk3)); + + // Fill three chunks with truncation (max length is 15, this string is 20). + base::debug::SetCrashKeyValue(kTestKey, "five four three two"); + EXPECT_EQ(3u, values.size()); + EXPECT_EQ("five ", values[kChunk1]); + EXPECT_EQ("four ", values[kChunk2]); + EXPECT_EQ("three", values[kChunk3]); + + // Clear everything. + base::debug::ClearCrashKey(kTestKey); + EXPECT_EQ(0u, values.size()); + EXPECT_TRUE(values.end() == values.find(kChunk1)); + EXPECT_TRUE(values.end() == values.find(kChunk2)); + EXPECT_TRUE(values.end() == values.find(kChunk3)); + + // Refill all three chunks with truncation, then test that setting a smaller + // value clears the third chunk. + base::debug::SetCrashKeyValue(kTestKey, "five four three two"); + base::debug::SetCrashKeyValue(kTestKey, "allays"); + EXPECT_EQ(2u, values.size()); + EXPECT_EQ("allay", values[kChunk1]); + EXPECT_EQ("s", values[kChunk2]); + EXPECT_TRUE(values.end() == values.find(kChunk3)); + + // Clear everything. + base::debug::ClearCrashKey(kTestKey); + EXPECT_EQ(0u, values.size()); + EXPECT_TRUE(values.end() == values.find(kChunk1)); + EXPECT_TRUE(values.end() == values.find(kChunk2)); + EXPECT_TRUE(values.end() == values.find(kChunk3)); +} + +TEST_F(CrashLoggingTest, ScopedCrashKey) { + const char* kTestKey = "test-key"; + base::debug::CrashKey keys[] = { { kTestKey, 255 } }; + base::debug::InitCrashKeys(keys, arraysize(keys), 255); + + EXPECT_EQ(0u, key_values_->size()); + EXPECT_TRUE(key_values_->end() == key_values_->find(kTestKey)); + { + base::debug::ScopedCrashKey scoped_crash_key(kTestKey, "value"); + EXPECT_EQ("value", (*key_values_)[kTestKey]); + EXPECT_EQ(1u, key_values_->size()); + } + EXPECT_EQ(0u, key_values_->size()); + EXPECT_TRUE(key_values_->end() == key_values_->find(kTestKey)); +} + +TEST_F(CrashLoggingTest, InitSize) { + base::debug::CrashKey keys[] = { + { "chunked-3", 15 }, + { "single", 5 }, + { "chunked-6", 30 }, + }; + + size_t num_keys = base::debug::InitCrashKeys(keys, arraysize(keys), 5); + + EXPECT_EQ(10u, num_keys); + + EXPECT_TRUE(base::debug::LookupCrashKey("chunked-3")); + EXPECT_TRUE(base::debug::LookupCrashKey("single")); + EXPECT_TRUE(base::debug::LookupCrashKey("chunked-6")); + EXPECT_FALSE(base::debug::LookupCrashKey("chunked-6-4")); +} + +TEST_F(CrashLoggingTest, ChunkValue) { + using base::debug::ChunkCrashKeyValue; + + // Test truncation. + base::debug::CrashKey key = { "chunky", 10 }; + std::vector results = + ChunkCrashKeyValue(key, "hello world", 64); + ASSERT_EQ(1u, results.size()); + EXPECT_EQ("hello worl", results[0]); + + // Test short string. + results = ChunkCrashKeyValue(key, "hi", 10); + ASSERT_EQ(1u, results.size()); + EXPECT_EQ("hi", results[0]); + + // Test chunk pair. + key.max_length = 6; + results = ChunkCrashKeyValue(key, "foobar", 3); + ASSERT_EQ(2u, results.size()); + EXPECT_EQ("foo", results[0]); + EXPECT_EQ("bar", results[1]); + + // Test chunk pair truncation. + results = ChunkCrashKeyValue(key, "foobared", 3); + ASSERT_EQ(2u, results.size()); + EXPECT_EQ("foo", results[0]); + EXPECT_EQ("bar", results[1]); + + // Test extra chunks. + key.max_length = 100; + results = ChunkCrashKeyValue(key, "hello world", 3); + ASSERT_EQ(4u, results.size()); + EXPECT_EQ("hel", results[0]); + EXPECT_EQ("lo ", results[1]); + EXPECT_EQ("wor", results[2]); + EXPECT_EQ("ld", results[3]); +} + +TEST_F(CrashLoggingTest, ChunkRounding) { + // If max_length=12 and max_chunk_length=5, there should be 3 chunks, + // not 2. + base::debug::CrashKey key = { "round", 12 }; + EXPECT_EQ(3u, base::debug::InitCrashKeys(&key, 1, 5)); +} diff --git a/base/debug/debug_on_start_win.cc b/base/debug/debug_on_start_win.cc new file mode 100644 index 0000000000..6ca88dde20 --- /dev/null +++ b/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 + +#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/base/debug/debug_on_start_win.h b/base/debug/debug_on_start_win.h new file mode 100644 index 0000000000..edcaa0aa95 --- /dev/null +++ b/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/base/debug/debugger.cc b/base/debug/debugger.cc new file mode 100644 index 0000000000..79233c58c5 --- /dev/null +++ b/base/debug/debugger.cc @@ -0,0 +1,41 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#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(getpid()) + << ")"; +#endif + for (int i = 0; i < wait_seconds * 10; ++i) { + if (BeingDebugged()) { + if (!silent) + 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/base/debug/debugger.h b/base/debug/debugger.h new file mode 100644 index 0000000000..4f368d986d --- /dev/null +++ b/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/base/debug/debugger_posix.cc b/base/debug/debugger_posix.cc new file mode 100644 index 0000000000..066c592bd4 --- /dev/null +++ b/base/debug/debugger_posix.cc @@ -0,0 +1,261 @@ +// Copyright (c) 2012 The Chromium Authors. All 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" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#if defined(__GLIBCXX__) +#include +#endif + +#if defined(OS_MACOSX) +#include +#endif + +#if defined(OS_MACOSX) || defined(OS_BSD) +#include +#endif + +#if defined(OS_FREEBSD) +#include +#endif + +#include + +#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/strings/string_piece.h" +#include "base/strings/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 + 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'; +} + +#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(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(OS_ANDROID) +// Though Android has a "helpful" process called debuggerd to catch native +// signals on the general assumption that they are fatal errors. If no debugger +// is attached, we call abort since Breakpad needs SIGABRT to create a dump. +// When debugger is attached, for ARM platform 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; for X86 platform, use +// "int3" to setup breakpiont and raise SIGTRAP. +namespace { +void DebugBreak() { + if (!BeingDebugged()) { + abort(); + } else { +#if defined(ARCH_CPU_X86_FAMILY) + asm("int3"); +#else + volatile int go = 0; + while (!go) { + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); + } +#endif + } +} +} // namespace +#define DEBUG_BREAK() DebugBreak() +#elif defined(ARCH_CPU_ARM_FAMILY) +// ARM && !ANDROID +#define DEBUG_BREAK() asm("bkpt 0") +#elif defined(ARCH_CPU_MIPS_FAMILY) +#define DEBUG_BREAK() asm("break 2") +#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) + // 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/base/debug/debugger_win.cc b/base/debug/debugger_win.cc new file mode 100644 index 0000000000..b13dbfd148 --- /dev/null +++ b/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 +#include + +#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(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/base/debug/leak_annotations.h b/base/debug/leak_annotations.h new file mode 100644 index 0000000000..86e8ea911c --- /dev/null +++ b/base/debug/leak_annotations.h @@ -0,0 +1,65 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_DEBUG_LEAK_ANNOTATIONS_H_ +#define BASE_DEBUG_LEAK_ANNOTATIONS_H_ + +#include "build/build_config.h" + +// This file defines macros which can be used to annotate intentional memory +// leaks. Support for annotations is implemented in HeapChecker and +// LeakSanitizer. Annotated objects will be treated as a source of live +// pointers, i.e. any heap objects reachable by following pointers from an +// annotated object will not be reported as leaks. +// +// ANNOTATE_SCOPED_MEMORY_LEAK: all allocations made in the current scope +// will be annotated as leaks. +// ANNOTATE_LEAKING_OBJECT_PTR(X): the heap object referenced by pointer X will +// be annotated as a leak. +// +// Note that HeapChecker will report a fatal error if an object which has been +// annotated with ANNOTATE_LEAKING_OBJECT_PTR is later deleted (but +// LeakSanitizer won't). + +#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_NACL) && \ + defined(USE_HEAPCHECKER) + +#include "third_party/tcmalloc/chromium/src/gperftools/heap-checker.h" + +#define ANNOTATE_SCOPED_MEMORY_LEAK \ + HeapLeakChecker::Disabler heap_leak_checker_disabler; static_cast(0) + +#define ANNOTATE_LEAKING_OBJECT_PTR(X) \ + HeapLeakChecker::IgnoreObject(X) + +#elif defined(LEAK_SANITIZER) && !defined(OS_NACL) + +extern "C" { +void __lsan_disable(); +void __lsan_enable(); +void __lsan_ignore_object(const void *p); +} // extern "C" + +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(0) + +#define ANNOTATE_LEAKING_OBJECT_PTR(X) __lsan_ignore_object(X); + +#else + +// If neither HeapChecker nor LSan are 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/base/debug/leak_tracker.h b/base/debug/leak_tracker.h new file mode 100644 index 0000000000..93cea39a0a --- /dev/null +++ b/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. +// +// class URLRequest { +// ... +// private: +// base::LeakTracker leak_tracker_; +// }; +// +// +// Next, when we believe all instances of net::URLRequest have been deleted: +// +// LeakTracker::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 +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 +class LeakTracker : public LinkNode > { + 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 >* 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 >* node = instances()->head(); + node != instances()->end(); + node = node->next()) { + ++count; + } + return count; + } + + private: + // Each specialization of LeakTracker gets its own static storage. + static LinkedList >* instances() { + static LinkedList > list; + return &list; + } + + StackTrace allocation_stack_; +}; + +#endif // ENABLE_LEAK_TRACKER + +} // namespace debug +} // namespace base + +#endif // BASE_DEBUG_LEAK_TRACKER_H_ diff --git a/base/debug/leak_tracker_unittest.cc b/base/debug/leak_tracker_unittest.cc new file mode 100644 index 0000000000..99df4c17e1 --- /dev/null +++ b/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 leak_tracker_; +}; + +class ClassB { + private: + LeakTracker leak_tracker_; +}; + +#ifndef ENABLE_LEAK_TRACKER + +// If leak tracking is disabled, we should do nothing. +TEST(LeakTrackerTest, NotEnabled) { + EXPECT_EQ(-1, LeakTracker::NumLiveInstances()); + EXPECT_EQ(-1, LeakTracker::NumLiveInstances()); + + // Use scoped_ptr so compiler doesn't complain about unused variables. + scoped_ptr a1(new ClassA); + scoped_ptr b1(new ClassB); + scoped_ptr b2(new ClassB); + + EXPECT_EQ(-1, LeakTracker::NumLiveInstances()); + EXPECT_EQ(-1, LeakTracker::NumLiveInstances()); +} + +#else + +TEST(LeakTrackerTest, Basic) { + { + ClassA a1; + + EXPECT_EQ(1, LeakTracker::NumLiveInstances()); + EXPECT_EQ(0, LeakTracker::NumLiveInstances()); + + ClassB b1; + ClassB b2; + + EXPECT_EQ(1, LeakTracker::NumLiveInstances()); + EXPECT_EQ(2, LeakTracker::NumLiveInstances()); + + scoped_ptr a2(new ClassA); + + EXPECT_EQ(2, LeakTracker::NumLiveInstances()); + EXPECT_EQ(2, LeakTracker::NumLiveInstances()); + + a2.reset(); + + EXPECT_EQ(1, LeakTracker::NumLiveInstances()); + EXPECT_EQ(2, LeakTracker::NumLiveInstances()); + } + + EXPECT_EQ(0, LeakTracker::NumLiveInstances()); + EXPECT_EQ(0, LeakTracker::NumLiveInstances()); +} + +// Try some orderings of create/remove to hit different cases in the linked-list +// assembly. +TEST(LeakTrackerTest, LinkedList) { + EXPECT_EQ(0, LeakTracker::NumLiveInstances()); + + scoped_ptr a1(new ClassA); + scoped_ptr a2(new ClassA); + scoped_ptr a3(new ClassA); + scoped_ptr a4(new ClassA); + + EXPECT_EQ(4, LeakTracker::NumLiveInstances()); + + // Remove the head of the list (a1). + a1.reset(); + EXPECT_EQ(3, LeakTracker::NumLiveInstances()); + + // Remove the tail of the list (a4). + a4.reset(); + EXPECT_EQ(2, LeakTracker::NumLiveInstances()); + + // Append to the new tail of the list (a3). + scoped_ptr a5(new ClassA); + EXPECT_EQ(3, LeakTracker::NumLiveInstances()); + + a2.reset(); + a3.reset(); + + EXPECT_EQ(1, LeakTracker::NumLiveInstances()); + + a5.reset(); + EXPECT_EQ(0, LeakTracker::NumLiveInstances()); +} + +TEST(LeakTrackerTest, NoOpCheckForLeaks) { + // There are no live instances of ClassA, so this should do nothing. + LeakTracker::CheckForLeaks(); +} + +#endif // ENABLE_LEAK_TRACKER + +} // namespace + +} // namespace debug +} // namespace base diff --git a/base/debug/proc_maps_linux.cc b/base/debug/proc_maps_linux.cc new file mode 100644 index 0000000000..9557feb025 --- /dev/null +++ b/base/debug/proc_maps_linux.cc @@ -0,0 +1,101 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/debug/proc_maps_linux.h" + +#if defined(OS_LINUX) +#include +#endif + +#include "base/file_util.h" +#include "base/strings/string_split.h" + +#if defined(OS_ANDROID) +// Bionic's inttypes.h defines PRI/SCNxPTR as an unsigned long int, which +// is incompatible with Bionic's stdint.h defining uintptr_t as a unsigned int: +// https://code.google.com/p/android/issues/detail?id=57218 +#undef SCNxPTR +#define SCNxPTR "x" +#endif + +namespace base { +namespace debug { + +bool ReadProcMaps(std::string* proc_maps) { + FilePath proc_maps_path("/proc/self/maps"); + return file_util::ReadFileToString(proc_maps_path, proc_maps); +} + +bool ParseProcMaps(const std::string& input, + std::vector* regions_out) { + std::vector regions; + + // This isn't async safe nor terribly efficient, but it doesn't need to be at + // this point in time. + std::vector lines; + SplitString(input, '\n', &lines); + + for (size_t i = 0; i < lines.size(); ++i) { + // Due to splitting on '\n' the last line should be empty. + if (i == lines.size() - 1) { + if (!lines[i].empty()) + return false; + break; + } + + MappedMemoryRegion region; + const char* line = lines[i].c_str(); + char permissions[5] = {'\0'}; // Ensure NUL-terminated string. + uint8 dev_major = 0; + uint8 dev_minor = 0; + long inode = 0; + int path_index = 0; + + // Sample format from man 5 proc: + // + // address perms offset dev inode pathname + // 08048000-08056000 r-xp 00000000 03:0c 64593 /usr/sbin/gpm + // + // The final %n term captures the offset in the input string, which is used + // to determine the path name. It *does not* increment the return value. + // Refer to man 3 sscanf for details. + if (sscanf(line, "%" SCNxPTR "-%" SCNxPTR " %4c %llx %hhx:%hhx %ld %n", + ®ion.start, ®ion.end, permissions, ®ion.offset, + &dev_major, &dev_minor, &inode, &path_index) < 7) { + return false; + } + + region.permissions = 0; + + if (permissions[0] == 'r') + region.permissions |= MappedMemoryRegion::READ; + else if (permissions[0] != '-') + return false; + + if (permissions[1] == 'w') + region.permissions |= MappedMemoryRegion::WRITE; + else if (permissions[1] != '-') + return false; + + if (permissions[2] == 'x') + region.permissions |= MappedMemoryRegion::EXECUTE; + else if (permissions[2] != '-') + return false; + + if (permissions[3] == 'p') + region.permissions |= MappedMemoryRegion::PRIVATE; + else if (permissions[3] != 's' && permissions[3] != 'S') // Shared memory. + return false; + + // Pushing then assigning saves us a string copy. + regions.push_back(region); + regions.back().path.assign(line + path_index); + } + + regions_out->swap(regions); + return true; +} + +} // namespace debug +} // namespace base diff --git a/base/debug/proc_maps_linux.h b/base/debug/proc_maps_linux.h new file mode 100644 index 0000000000..d055712444 --- /dev/null +++ b/base/debug/proc_maps_linux.h @@ -0,0 +1,56 @@ +// 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_DEBUG_PROC_MAPS_LINUX_H_ +#define BASE_DEBUG_PROC_MAPS_LINUX_H_ + +#include +#include + +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace base { +namespace debug { + +// Describes a region of mapped memory and the path of the file mapped. +struct MappedMemoryRegion { + enum Permission { + READ = 1 << 0, + WRITE = 1 << 1, + EXECUTE = 1 << 2, + PRIVATE = 1 << 3, // If set, region is private, otherwise it is shared. + }; + + // The address range [start,end) of mapped memory. + uintptr_t start; + uintptr_t end; + + // Byte offset into |path| of the range mapped into memory. + unsigned long long offset; + + // Bitmask of read/write/execute/private/shared permissions. + uint8 permissions; + + // Name of the file mapped into memory. + // + // NOTE: path names aren't guaranteed to point at valid files. For example, + // "[heap]" and "[stack]" are used to represent the location of the process' + // heap and stack, respectively. + std::string path; +}; + +// Reads the data from /proc/self/maps and stores the result in |proc_maps|. +// Returns true if successful, false otherwise. +BASE_EXPORT bool ReadProcMaps(std::string* proc_maps); + +// Parses /proc//maps input data and stores in |regions|. Returns true +// and updates |regions| if and only if all of |input| was successfully parsed. +BASE_EXPORT bool ParseProcMaps(const std::string& input, + std::vector* regions); + +} // namespace debug +} // namespace base + +#endif // BASE_DEBUG_PROC_MAPS_LINUX_H_ diff --git a/base/debug/proc_maps_linux_unittest.cc b/base/debug/proc_maps_linux_unittest.cc new file mode 100644 index 0000000000..9b7d7dcc4a --- /dev/null +++ b/base/debug/proc_maps_linux_unittest.cc @@ -0,0 +1,281 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/debug/proc_maps_linux.h" +#include "base/files/file_path.h" +#include "base/path_service.h" +#include "base/strings/stringprintf.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace debug { + +TEST(ProcMapsTest, Empty) { + std::vector regions; + EXPECT_TRUE(ParseProcMaps("", ®ions)); + EXPECT_EQ(0u, regions.size()); +} + +TEST(ProcMapsTest, NoSpaces) { + static const char kNoSpaces[] = + "00400000-0040b000 r-xp 00002200 fc:00 794418 /bin/cat\n"; + + std::vector regions; + ASSERT_TRUE(ParseProcMaps(kNoSpaces, ®ions)); + ASSERT_EQ(1u, regions.size()); + + EXPECT_EQ(0x00400000u, regions[0].start); + EXPECT_EQ(0x0040b000u, regions[0].end); + EXPECT_EQ(0x00002200u, regions[0].offset); + EXPECT_EQ("/bin/cat", regions[0].path); +} + +TEST(ProcMapsTest, Spaces) { + static const char kSpaces[] = + "00400000-0040b000 r-xp 00002200 fc:00 794418 /bin/space cat\n"; + + std::vector regions; + ASSERT_TRUE(ParseProcMaps(kSpaces, ®ions)); + ASSERT_EQ(1u, regions.size()); + + EXPECT_EQ(0x00400000u, regions[0].start); + EXPECT_EQ(0x0040b000u, regions[0].end); + EXPECT_EQ(0x00002200u, regions[0].offset); + EXPECT_EQ("/bin/space cat", regions[0].path); +} + +TEST(ProcMapsTest, NoNewline) { + static const char kNoSpaces[] = + "00400000-0040b000 r-xp 00002200 fc:00 794418 /bin/cat"; + + std::vector regions; + ASSERT_FALSE(ParseProcMaps(kNoSpaces, ®ions)); +} + +TEST(ProcMapsTest, NoPath) { + static const char kNoPath[] = + "00400000-0040b000 rw-p 00000000 00:00 0 \n"; + + std::vector regions; + ASSERT_TRUE(ParseProcMaps(kNoPath, ®ions)); + ASSERT_EQ(1u, regions.size()); + + EXPECT_EQ(0x00400000u, regions[0].start); + EXPECT_EQ(0x0040b000u, regions[0].end); + EXPECT_EQ(0x00000000u, regions[0].offset); + EXPECT_EQ("", regions[0].path); +} + +TEST(ProcMapsTest, Heap) { + static const char kHeap[] = + "022ac000-022cd000 rw-p 00000000 00:00 0 [heap]\n"; + + std::vector regions; + ASSERT_TRUE(ParseProcMaps(kHeap, ®ions)); + ASSERT_EQ(1u, regions.size()); + + EXPECT_EQ(0x022ac000u, regions[0].start); + EXPECT_EQ(0x022cd000u, regions[0].end); + EXPECT_EQ(0x00000000u, regions[0].offset); + EXPECT_EQ("[heap]", regions[0].path); +} + +#if defined(ARCH_CPU_32_BITS) +TEST(ProcMapsTest, Stack32) { + static const char kStack[] = + "beb04000-beb25000 rw-p 00000000 00:00 0 [stack]\n"; + + std::vector regions; + ASSERT_TRUE(ParseProcMaps(kStack, ®ions)); + ASSERT_EQ(1u, regions.size()); + + EXPECT_EQ(0xbeb04000u, regions[0].start); + EXPECT_EQ(0xbeb25000u, regions[0].end); + EXPECT_EQ(0x00000000u, regions[0].offset); + EXPECT_EQ("[stack]", regions[0].path); +} +#elif defined(ARCH_CPU_64_BITS) +TEST(ProcMapsTest, Stack64) { + static const char kStack[] = + "7fff69c5b000-7fff69c7d000 rw-p 00000000 00:00 0 [stack]\n"; + + std::vector regions; + ASSERT_TRUE(ParseProcMaps(kStack, ®ions)); + ASSERT_EQ(1u, regions.size()); + + EXPECT_EQ(0x7fff69c5b000u, regions[0].start); + EXPECT_EQ(0x7fff69c7d000u, regions[0].end); + EXPECT_EQ(0x00000000u, regions[0].offset); + EXPECT_EQ("[stack]", regions[0].path); +} +#endif + +TEST(ProcMapsTest, Multiple) { + static const char kMultiple[] = + "00400000-0040b000 r-xp 00000000 fc:00 794418 /bin/cat\n" + "0060a000-0060b000 r--p 0000a000 fc:00 794418 /bin/cat\n" + "0060b000-0060c000 rw-p 0000b000 fc:00 794418 /bin/cat\n"; + + std::vector regions; + ASSERT_TRUE(ParseProcMaps(kMultiple, ®ions)); + ASSERT_EQ(3u, regions.size()); + + EXPECT_EQ(0x00400000u, regions[0].start); + EXPECT_EQ(0x0040b000u, regions[0].end); + EXPECT_EQ(0x00000000u, regions[0].offset); + EXPECT_EQ("/bin/cat", regions[0].path); + + EXPECT_EQ(0x0060a000u, regions[1].start); + EXPECT_EQ(0x0060b000u, regions[1].end); + EXPECT_EQ(0x0000a000u, regions[1].offset); + EXPECT_EQ("/bin/cat", regions[1].path); + + EXPECT_EQ(0x0060b000u, regions[2].start); + EXPECT_EQ(0x0060c000u, regions[2].end); + EXPECT_EQ(0x0000b000u, regions[2].offset); + EXPECT_EQ("/bin/cat", regions[2].path); +} + +TEST(ProcMapsTest, Permissions) { + static struct { + const char* input; + uint8 permissions; + } kTestCases[] = { + {"00400000-0040b000 ---s 00000000 fc:00 794418 /bin/cat\n", 0}, + {"00400000-0040b000 ---S 00000000 fc:00 794418 /bin/cat\n", 0}, + {"00400000-0040b000 r--s 00000000 fc:00 794418 /bin/cat\n", + MappedMemoryRegion::READ}, + {"00400000-0040b000 -w-s 00000000 fc:00 794418 /bin/cat\n", + MappedMemoryRegion::WRITE}, + {"00400000-0040b000 --xs 00000000 fc:00 794418 /bin/cat\n", + MappedMemoryRegion::EXECUTE}, + {"00400000-0040b000 rwxs 00000000 fc:00 794418 /bin/cat\n", + MappedMemoryRegion::READ | MappedMemoryRegion::WRITE | + MappedMemoryRegion::EXECUTE}, + {"00400000-0040b000 ---p 00000000 fc:00 794418 /bin/cat\n", + MappedMemoryRegion::PRIVATE}, + {"00400000-0040b000 r--p 00000000 fc:00 794418 /bin/cat\n", + MappedMemoryRegion::READ | MappedMemoryRegion::PRIVATE}, + {"00400000-0040b000 -w-p 00000000 fc:00 794418 /bin/cat\n", + MappedMemoryRegion::WRITE | MappedMemoryRegion::PRIVATE}, + {"00400000-0040b000 --xp 00000000 fc:00 794418 /bin/cat\n", + MappedMemoryRegion::EXECUTE | MappedMemoryRegion::PRIVATE}, + {"00400000-0040b000 rwxp 00000000 fc:00 794418 /bin/cat\n", + MappedMemoryRegion::READ | MappedMemoryRegion::WRITE | + MappedMemoryRegion::EXECUTE | MappedMemoryRegion::PRIVATE}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); ++i) { + SCOPED_TRACE( + base::StringPrintf("kTestCases[%zu] = %s", i, kTestCases[i].input)); + + std::vector regions; + EXPECT_TRUE(ParseProcMaps(kTestCases[i].input, ®ions)); + EXPECT_EQ(1u, regions.size()); + if (regions.empty()) + continue; + EXPECT_EQ(kTestCases[i].permissions, regions[0].permissions); + } +} + +// ProcMapsTest.ReadProcMaps fails under TSan on Linux, +// see http://crbug.com/258451. +#if defined(THREAD_SANITIZER) +#define MAYBE_ReadProcMaps DISABLED_ReadProcMaps +#else +#define MAYBE_ReadProcMaps ReadProcMaps +#endif +TEST(ProcMapsTest, MAYBE_ReadProcMaps) { + std::string proc_maps; + ASSERT_TRUE(ReadProcMaps(&proc_maps)); + + std::vector regions; + ASSERT_TRUE(ParseProcMaps(proc_maps, ®ions)); + ASSERT_FALSE(regions.empty()); + + // We should be able to find both the current executable as well as the stack + // mapped into memory. Use the address of |proc_maps| as a way of finding the + // stack. + FilePath exe_path; + EXPECT_TRUE(PathService::Get(FILE_EXE, &exe_path)); + uintptr_t address = reinterpret_cast(&proc_maps); + bool found_exe = false; + bool found_stack = false; + bool found_address = false; + for (size_t i = 0; i < regions.size(); ++i) { + if (regions[i].path == exe_path.value()) { + // It's OK to find the executable mapped multiple times as there'll be + // multiple sections (e.g., text, data). + found_exe = true; + } + + if (regions[i].path == "[stack]") { + // Only check if |address| lies within the real stack when not running + // Valgrind, otherwise |address| will be on a stack that Valgrind creates. + if (!RunningOnValgrind()) { + EXPECT_GE(address, regions[i].start); + EXPECT_LT(address, regions[i].end); + } + + EXPECT_TRUE(regions[i].permissions & MappedMemoryRegion::READ); + EXPECT_TRUE(regions[i].permissions & MappedMemoryRegion::WRITE); + EXPECT_FALSE(regions[i].permissions & MappedMemoryRegion::EXECUTE); + EXPECT_TRUE(regions[i].permissions & MappedMemoryRegion::PRIVATE); + EXPECT_FALSE(found_stack) << "Found duplicate stacks"; + found_stack = true; + } + + if (address >= regions[i].start && address < regions[i].end) { + EXPECT_FALSE(found_address) << "Found same address in multiple regions"; + found_address = true; + } + } + + EXPECT_TRUE(found_exe); + EXPECT_TRUE(found_stack); + EXPECT_TRUE(found_address); +} + +TEST(ProcMapsTest, MissingFields) { + static const char* kTestCases[] = { + "00400000\n", // Missing end + beyond. + "00400000-0040b000\n", // Missing perms + beyond. + "00400000-0040b000 r-xp\n", // Missing offset + beyond. + "00400000-0040b000 r-xp 00000000\n", // Missing device + beyond. + "00400000-0040b000 r-xp 00000000 fc:00\n", // Missing inode + beyond. + "00400000-0040b000 00000000 fc:00 794418 /bin/cat\n", // Missing perms. + "00400000-0040b000 r-xp fc:00 794418 /bin/cat\n", // Missing offset. + "00400000-0040b000 r-xp 00000000 fc:00 /bin/cat\n", // Missing inode. + "00400000 r-xp 00000000 fc:00 794418 /bin/cat\n", // Missing end. + "-0040b000 r-xp 00000000 fc:00 794418 /bin/cat\n", // Missing start. + "00400000-0040b000 r-xp 00000000 794418 /bin/cat\n", // Missing device. + }; + + for (size_t i = 0; i < arraysize(kTestCases); ++i) { + SCOPED_TRACE(base::StringPrintf("kTestCases[%zu] = %s", i, kTestCases[i])); + std::vector regions; + EXPECT_FALSE(ParseProcMaps(kTestCases[i], ®ions)); + } +} + +TEST(ProcMapsTest, InvalidInput) { + static const char* kTestCases[] = { + "thisisal-0040b000 rwxp 00000000 fc:00 794418 /bin/cat\n", + "0040000d-linvalid rwxp 00000000 fc:00 794418 /bin/cat\n", + "00400000-0040b000 inpu 00000000 fc:00 794418 /bin/cat\n", + "00400000-0040b000 rwxp tforproc fc:00 794418 /bin/cat\n", + "00400000-0040b000 rwxp 00000000 ma:ps 794418 /bin/cat\n", + "00400000-0040b000 rwxp 00000000 fc:00 parse! /bin/cat\n", + }; + + for (size_t i = 0; i < arraysize(kTestCases); ++i) { + SCOPED_TRACE(base::StringPrintf("kTestCases[%zu] = %s", i, kTestCases[i])); + std::vector regions; + EXPECT_FALSE(ParseProcMaps(kTestCases[i], ®ions)); + } +} + +} // namespace debug +} // namespace base diff --git a/base/debug/profiler.cc b/base/debug/profiler.cc new file mode 100644 index 0000000000..096e343e00 --- /dev/null +++ b/base/debug/profiler.cc @@ -0,0 +1,217 @@ +// Copyright (c) 2012 The Chromium Authors. All 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 + +#include "base/process/process_handle.h" +#include "base/strings/string_util.h" +#include "base/strings/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; +} + +DynamicFunctionEntryHook GetProfilerDynamicFunctionEntryHookFunc() { + return NULL; +} + +AddDynamicSymbol GetProfilerAddDynamicSymbolFunc() { + return NULL; +} + +MoveDynamicSymbol GetProfilerMoveDynamicSymbolFunc() { + 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(&__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; +} + +namespace { + +struct FunctionSearchContext { + const char* name; + FARPROC function; +}; + +// Callback function to PEImage::EnumImportChunks. +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) { + FunctionSearchContext* context = + reinterpret_cast(cookie); + + DCHECK_NE(static_cast(NULL), context); + DCHECK_EQ(static_cast(NULL), context->function); + + // 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(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. + FARPROC exported_func = ::GetProcAddress(module, context->name); + if (exported_func != NULL) { + // We found it, return the function and terminate the enumeration. + context->function = exported_func; + return false; + } + + // Keep going. + return true; +} + +template +FunctionType FindFunctionInImports(const char* function_name) { + if (!IsBinaryInstrumented()) + return NULL; + + HMODULE this_module = reinterpret_cast(&__ImageBase); + base::win::PEImage image(this_module); + + FunctionSearchContext ctx = { function_name, NULL }; + image.EnumImportChunks(FindResolutionFunctionInImports, &ctx); + + return reinterpret_cast(ctx.function); +} + +} // namespace + +ReturnAddressLocationResolver GetProfilerReturnAddrResolutionFunc() { + return FindFunctionInImports( + "ResolveReturnAddressLocation"); +} + +DynamicFunctionEntryHook GetProfilerDynamicFunctionEntryHookFunc() { + return FindFunctionInImports( + "OnDynamicFunctionEntry"); +} + +AddDynamicSymbol GetProfilerAddDynamicSymbolFunc() { + return FindFunctionInImports( + "AddDynamicSymbol"); +} + +MoveDynamicSymbol GetProfilerMoveDynamicSymbolFunc() { + return FindFunctionInImports( + "MoveDynamicSymbol"); +} + +#endif // defined(OS_WIN) + +} // namespace debug +} // namespace base diff --git a/base/debug/profiler.h b/base/debug/profiler.h new file mode 100644 index 0000000000..e1dda89742 --- /dev/null +++ b/base/debug/profiler.h @@ -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. + +#ifndef BASE_DEBUG_PROFILER_H +#define BASE_DEBUG_PROFILER_H + +#include + +#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); + +// This type declaration must match V8's FunctionEntryHook. +typedef void (*DynamicFunctionEntryHook)(uintptr_t function, + uintptr_t return_addr_location); + +// The functions below here are to support profiling V8-generated code. +// V8 has provisions for generating a call to an entry hook for newly generated +// JIT code, and it can push symbol information on code generation and advise +// when the garbage collector moves code. The functions declarations below here +// make glue between V8's facilities and a profiler. + +// This type declaration must match V8's FunctionEntryHook. +typedef void (*DynamicFunctionEntryHook)(uintptr_t function, + uintptr_t return_addr_location); + +typedef void (*AddDynamicSymbol)(const void* address, + size_t length, + const char* name, + size_t name_len); +typedef void (*MoveDynamicSymbol)(const void* address, const void* new_address); + + +// If this binary is instrumented and the instrumentation supplies a function +// for each of those purposes, find and return the function in question. +// Otherwise returns NULL. +BASE_EXPORT ReturnAddressLocationResolver GetProfilerReturnAddrResolutionFunc(); +BASE_EXPORT DynamicFunctionEntryHook GetProfilerDynamicFunctionEntryHookFunc(); +BASE_EXPORT AddDynamicSymbol GetProfilerAddDynamicSymbolFunc(); +BASE_EXPORT MoveDynamicSymbol GetProfilerMoveDynamicSymbolFunc(); + +} // namespace debug +} // namespace base + +#endif // BASE_DEBUG_DEBUGGER_H diff --git a/base/debug/stack_trace.cc b/base/debug/stack_trace.cc new file mode 100644 index 0000000000..6fab183503 --- /dev/null +++ b/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 + +#include +#include + +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/base/debug/stack_trace.h b/base/debug/stack_trace.h new file mode 100644 index 0000000000..53bed3c5ac --- /dev/null +++ b/base/debug/stack_trace.h @@ -0,0 +1,101 @@ +// Copyright (c) 2012 The Chromium Authors. All 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 +#include + +#include "base/base_export.h" +#include "build/build_config.h" + +#if defined(OS_POSIX) +#include +#endif + +#if defined(OS_WIN) +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) + // 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, + size_t padding); +#endif // defined(OS_POSIX) && !defined(OS_ANDROID) + +} // namespace internal + +} // namespace debug +} // namespace base + +#endif // BASE_DEBUG_STACK_TRACE_H_ diff --git a/base/debug/stack_trace_android.cc b/base/debug/stack_trace_android.cc new file mode 100644 index 0000000000..4c53d4a94c --- /dev/null +++ b/base/debug/stack_trace_android.cc @@ -0,0 +1,132 @@ +// Copyright (c) 2012 The Chromium Authors. All 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 +#include + +#include "base/debug/proc_maps_linux.h" +#include "base/strings/stringprintf.h" +#include "base/threading/thread_restrictions.h" + +namespace { + +struct StackCrawlState { + StackCrawlState(uintptr_t* frames, size_t max_depth) + : frames(frames), + frame_count(0), + max_depth(max_depth), + have_skipped_self(false) {} + + uintptr_t* frames; + size_t frame_count; + size_t max_depth; + bool have_skipped_self; +}; + +// Clang's unwind.h doesn't provide _Unwind_GetIP on ARM, refer to +// http://llvm.org/bugs/show_bug.cgi?id=16564 for details. +#if defined(__clang__) +uintptr_t _Unwind_GetIP(_Unwind_Context* context) { + uintptr_t ip = 0; + _Unwind_VRS_Get(context, _UVRSC_CORE, 15, _UVRSD_UINT32, &ip); + return ip & ~static_cast(0x1); // Remove thumb mode bit. +} +#endif + +_Unwind_Reason_Code TraceStackFrame(_Unwind_Context* context, void* arg) { + StackCrawlState* state = static_cast(arg); + uintptr_t ip = _Unwind_GetIP(context); + + // The first stack frame is this function itself. Skip it. + if (ip != 0 && !state->have_skipped_self) { + state->have_skipped_self = true; + return _URC_NO_REASON; + } + + state->frames[state->frame_count++] = ip; + if (state->frame_count >= state->max_depth) + return _URC_END_OF_STACK; + return _URC_NO_REASON; +} + +} // namespace + +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() { + StackCrawlState state(reinterpret_cast(trace_), kMaxTraces); + _Unwind_Backtrace(&TraceStackFrame, &state); + count_ = state.frame_count; +} + +void StackTrace::PrintBacktrace() const { + std::string backtrace = ToString(); + __android_log_write(ANDROID_LOG_ERROR, "chromium", backtrace.c_str()); +} + +// NOTE: Native libraries in APKs are stripped before installing. Print out the +// relocatable address and library names so host computers can use tools to +// symbolize and demangle (e.g., addr2line, c++filt). +void StackTrace::OutputToStream(std::ostream* os) const { + std::string proc_maps; + std::vector regions; + // Allow IO to read /proc/self/maps. Reading this file doesn't hit the disk + // since it lives in procfs, and this is currently used to print a stack trace + // on fatal log messages in debug builds only. If the restriction is enabled + // then it will recursively trigger fatal failures when this enters on the + // UI thread. + base::ThreadRestrictions::ScopedAllowIO allow_io; + if (!ReadProcMaps(&proc_maps)) { + __android_log_write( + ANDROID_LOG_ERROR, "chromium", "Failed to read /proc/self/maps"); + } else if (!ParseProcMaps(proc_maps, ®ions)) { + __android_log_write( + ANDROID_LOG_ERROR, "chromium", "Failed to parse /proc/self/maps"); + } + + for (size_t i = 0; i < count_; ++i) { + // Subtract one as return address of function may be in the next + // function when a function is annotated as noreturn. + uintptr_t address = reinterpret_cast(trace_[i]) - 1; + + std::vector::iterator iter = regions.begin(); + while (iter != regions.end()) { + if (address >= iter->start && address < iter->end && + !iter->path.empty()) { + break; + } + ++iter; + } + + *os << base::StringPrintf("#%02d 0x%08x ", i, address); + + if (iter != regions.end()) { + uintptr_t rel_pc = address - iter->start + iter->offset; + const char* path = iter->path.c_str(); + *os << base::StringPrintf("%s+0x%08x", path, rel_pc); + } else { + *os << ""; + } + + *os << "\n"; + } +} + +} // namespace debug +} // namespace base diff --git a/base/debug/stack_trace_ios.mm b/base/debug/stack_trace_ios.mm new file mode 100644 index 0000000000..998dd0dfc6 --- /dev/null +++ b/base/debug/stack_trace_ios.mm @@ -0,0 +1,53 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import +#include +#include + +#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/base/debug/stack_trace_posix.cc b/base/debug/stack_trace_posix.cc new file mode 100644 index 0000000000..8a8ae47710 --- /dev/null +++ b/base/debug/stack_trace_posix.cc @@ -0,0 +1,556 @@ +// Copyright (c) 2012 The Chromium Authors. All 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if defined(__GLIBCXX__) +#include +#endif + +#if defined(OS_MACOSX) +#include +#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/strings/string_number_conversions.h" + +#if defined(USE_SYMBOLIZE) +#include "base/third_party/symbolize/symbolize.h" +#endif + +namespace base { +namespace debug { + +namespace { + +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 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(pointer), + buf, sizeof(buf), 16, 12); + 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) { + OutputPointer(trace[i], handler); + handler->HandleOutput(" "); + + 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(trace[i]) - 1; + if (google::Symbolize(address, buf, sizeof(buf))) + handler->HandleOutput(buf); + else + handler->HandleOutput(""); + + 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) { + scoped_ptr_malloc 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; + } + } + + if (!printed) { + for (int i = 0; i < size; ++i) { + OutputPointer(trace[i], handler); + handler->HandleOutput("\n"); + } + } +#endif // defined(USE_SYMBOLIZE) +} + +void PrintToStderr(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)))); +} + +void StackDumpSignalHandler(int signal, siginfo_t* info, void* void_context) { + // NOTE: This code MUST be async-signal safe. + // NO malloc or stdio is allowed here. + + // 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(); + + PrintToStderr("Received signal "); + char buf[1024] = { 0 }; + internal::itoa_r(signal, buf, sizeof(buf), 10, 0); + PrintToStderr(buf); + if (signal == SIGBUS) { + if (info->si_code == BUS_ADRALN) + PrintToStderr(" BUS_ADRALN "); + else if (info->si_code == BUS_ADRERR) + PrintToStderr(" BUS_ADRERR "); + else if (info->si_code == BUS_OBJERR) + PrintToStderr(" BUS_OBJERR "); + else + PrintToStderr(" "); + } else if (signal == SIGFPE) { + if (info->si_code == FPE_FLTDIV) + PrintToStderr(" FPE_FLTDIV "); + else if (info->si_code == FPE_FLTINV) + PrintToStderr(" FPE_FLTINV "); + else if (info->si_code == FPE_FLTOVF) + PrintToStderr(" FPE_FLTOVF "); + else if (info->si_code == FPE_FLTRES) + PrintToStderr(" FPE_FLTRES "); + else if (info->si_code == FPE_FLTSUB) + PrintToStderr(" FPE_FLTSUB "); + else if (info->si_code == FPE_FLTUND) + PrintToStderr(" FPE_FLTUND "); + else if (info->si_code == FPE_INTDIV) + PrintToStderr(" FPE_INTDIV "); + else if (info->si_code == FPE_INTOVF) + PrintToStderr(" FPE_INTOVF "); + else + PrintToStderr(" "); + } else if (signal == SIGILL) { + if (info->si_code == ILL_BADSTK) + PrintToStderr(" ILL_BADSTK "); + else if (info->si_code == ILL_COPROC) + PrintToStderr(" ILL_COPROC "); + else if (info->si_code == ILL_ILLOPN) + PrintToStderr(" ILL_ILLOPN "); + else if (info->si_code == ILL_ILLADR) + PrintToStderr(" ILL_ILLADR "); + else if (info->si_code == ILL_ILLTRP) + PrintToStderr(" ILL_ILLTRP "); + else if (info->si_code == ILL_PRVOPC) + PrintToStderr(" ILL_PRVOPC "); + else if (info->si_code == ILL_PRVREG) + PrintToStderr(" ILL_PRVREG "); + else + PrintToStderr(" "); + } else if (signal == SIGSEGV) { + if (info->si_code == SEGV_MAPERR) + PrintToStderr(" SEGV_MAPERR "); + else if (info->si_code == SEGV_ACCERR) + PrintToStderr(" SEGV_ACCERR "); + else + PrintToStderr(" "); + } + if (signal == SIGBUS || signal == SIGFPE || + signal == SIGILL || signal == SIGSEGV) { + internal::itoa_r(reinterpret_cast(info->si_addr), + buf, sizeof(buf), 16, 12); + PrintToStderr(buf); + } + PrintToStderr("\n"); + + debug::StackTrace().PrintBacktrace(); + +#if defined(OS_LINUX) +#if ARCH_CPU_X86_FAMILY + ucontext_t* context = reinterpret_cast(void_context); + const struct { + const char* label; + greg_t value; + } registers[] = { +#if ARCH_CPU_32_BITS + { " gs: ", context->uc_mcontext.gregs[REG_GS] }, + { " fs: ", context->uc_mcontext.gregs[REG_FS] }, + { " es: ", context->uc_mcontext.gregs[REG_ES] }, + { " ds: ", context->uc_mcontext.gregs[REG_DS] }, + { " edi: ", context->uc_mcontext.gregs[REG_EDI] }, + { " esi: ", context->uc_mcontext.gregs[REG_ESI] }, + { " ebp: ", context->uc_mcontext.gregs[REG_EBP] }, + { " esp: ", context->uc_mcontext.gregs[REG_ESP] }, + { " ebx: ", context->uc_mcontext.gregs[REG_EBX] }, + { " edx: ", context->uc_mcontext.gregs[REG_EDX] }, + { " ecx: ", context->uc_mcontext.gregs[REG_ECX] }, + { " eax: ", context->uc_mcontext.gregs[REG_EAX] }, + { " trp: ", context->uc_mcontext.gregs[REG_TRAPNO] }, + { " err: ", context->uc_mcontext.gregs[REG_ERR] }, + { " ip: ", context->uc_mcontext.gregs[REG_EIP] }, + { " cs: ", context->uc_mcontext.gregs[REG_CS] }, + { " efl: ", context->uc_mcontext.gregs[REG_EFL] }, + { " usp: ", context->uc_mcontext.gregs[REG_UESP] }, + { " ss: ", context->uc_mcontext.gregs[REG_SS] }, +#elif ARCH_CPU_64_BITS + { " r8: ", context->uc_mcontext.gregs[REG_R8] }, + { " r9: ", context->uc_mcontext.gregs[REG_R9] }, + { " r10: ", context->uc_mcontext.gregs[REG_R10] }, + { " r11: ", context->uc_mcontext.gregs[REG_R11] }, + { " r12: ", context->uc_mcontext.gregs[REG_R12] }, + { " r13: ", context->uc_mcontext.gregs[REG_R13] }, + { " r14: ", context->uc_mcontext.gregs[REG_R14] }, + { " r15: ", context->uc_mcontext.gregs[REG_R15] }, + { " di: ", context->uc_mcontext.gregs[REG_RDI] }, + { " si: ", context->uc_mcontext.gregs[REG_RSI] }, + { " bp: ", context->uc_mcontext.gregs[REG_RBP] }, + { " bx: ", context->uc_mcontext.gregs[REG_RBX] }, + { " dx: ", context->uc_mcontext.gregs[REG_RDX] }, + { " ax: ", context->uc_mcontext.gregs[REG_RAX] }, + { " cx: ", context->uc_mcontext.gregs[REG_RCX] }, + { " sp: ", context->uc_mcontext.gregs[REG_RSP] }, + { " ip: ", context->uc_mcontext.gregs[REG_RIP] }, + { " efl: ", context->uc_mcontext.gregs[REG_EFL] }, + { " cgf: ", context->uc_mcontext.gregs[REG_CSGSFS] }, + { " erf: ", context->uc_mcontext.gregs[REG_ERR] }, + { " trp: ", context->uc_mcontext.gregs[REG_TRAPNO] }, + { " msk: ", context->uc_mcontext.gregs[REG_OLDMASK] }, + { " cr2: ", context->uc_mcontext.gregs[REG_CR2] }, +#endif + }; + +#if ARCH_CPU_32_BITS + const int kRegisterPadding = 8; +#elif ARCH_CPU_64_BITS + const int kRegisterPadding = 16; +#endif + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(registers); i++) { + PrintToStderr(registers[i].label); + internal::itoa_r(registers[i].value, buf, sizeof(buf), + 16, kRegisterPadding); + PrintToStderr(buf); + + if ((i + 1) % 4 == 0) + PrintToStderr("\n"); + } + PrintToStderr("\n"); +#endif +#elif defined(OS_MACOSX) + // TODO(shess): Port to 64-bit. +#if ARCH_CPU_X86_FAMILY && ARCH_CPU_32_BITS + ucontext_t* context = reinterpret_cast(void_context); + 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( + 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( + 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( + 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); +} + +class PrintBacktraceOutputHandler : public BacktraceOutputHandler { + public: + PrintBacktraceOutputHandler() {} + + virtual void HandleOutput(const char* output) OVERRIDE { + // 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. + PrintToStderr(output); + } + + private: + DISALLOW_COPY_AND_ASSIGN(PrintBacktraceOutputHandler); +}; + +class StreamBacktraceOutputHandler : public BacktraceOutputHandler { + public: + explicit StreamBacktraceOutputHandler(std::ostream* os) : os_(os) { + } + + virtual void HandleOutput(const char* output) OVERRIDE { + (*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 + StackTrace stack_trace; +} + +} // namespace + +#if !defined(OS_IOS) +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 sigpipe_action; + memset(&sigpipe_action, 0, sizeof(sigpipe_action)); + sigpipe_action.sa_handler = SIG_IGN; + sigemptyset(&sigpipe_action.sa_mask); + bool success = (sigaction(SIGPIPE, &sigpipe_action, NULL) == 0); + + // Avoid hangs during backtrace initialization, see above. + WarmUpBacktrace(); + + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_flags = SA_RESETHAND | SA_SIGINFO; + action.sa_sigaction = &StackDumpSignalHandler; + sigemptyset(&action.sa_mask); + + success &= (sigaction(SIGILL, &action, NULL) == 0); + success &= (sigaction(SIGABRT, &action, NULL) == 0); + success &= (sigaction(SIGFPE, &action, NULL) == 0); + success &= (sigaction(SIGBUS, &action, NULL) == 0); + success &= (sigaction(SIGSEGV, &action, NULL) == 0); + success &= (sigaction(SIGSYS, &action, NULL) == 0); + + 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, size_t padding) { + // 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; + + if (padding > 0) + padding--; + } while (j > 0 || padding > 0); + + // 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/base/debug/stack_trace_unittest.cc b/base/debug/stack_trace_unittest.cc new file mode 100644 index 0000000000..b676327d7a --- /dev/null +++ b/base/debug/stack_trace_unittest.cc @@ -0,0 +1,229 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include + +#include "base/debug/stack_trace.h" +#include "base/logging.h" +#include "base/process/kill.h" +#include "base/process/process_handle.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) +#include "base/test/multiprocess_test.h" +#endif + +namespace base { +namespace debug { + +#if defined(OS_POSIX) && !defined(OS_ANDROID) && !defined(OS_IOS) +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) +#if !defined(OS_IOS) +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) + +namespace { + +std::string itoa_r_wrapper(intptr_t i, size_t sz, int base, size_t padding) { + char buffer[1024]; + CHECK_LE(sz, sizeof(buffer)); + + char* result = internal::itoa_r(i, buffer, sz, base, padding); + EXPECT_TRUE(result); + return std::string(buffer); +} + +} // namespace + +TEST_F(StackTraceTest, itoa_r) { + EXPECT_EQ("0", itoa_r_wrapper(0, 128, 10, 0)); + EXPECT_EQ("-1", itoa_r_wrapper(-1, 128, 10, 0)); + + // Test edge cases. + if (sizeof(intptr_t) == 4) { + EXPECT_EQ("ffffffff", itoa_r_wrapper(-1, 128, 16, 0)); + EXPECT_EQ("-2147483648", + itoa_r_wrapper(std::numeric_limits::min(), 128, 10, 0)); + EXPECT_EQ("2147483647", + itoa_r_wrapper(std::numeric_limits::max(), 128, 10, 0)); + + EXPECT_EQ("80000000", + itoa_r_wrapper(std::numeric_limits::min(), 128, 16, 0)); + EXPECT_EQ("7fffffff", + itoa_r_wrapper(std::numeric_limits::max(), 128, 16, 0)); + } else if (sizeof(intptr_t) == 8) { + EXPECT_EQ("ffffffffffffffff", itoa_r_wrapper(-1, 128, 16, 0)); + EXPECT_EQ("-9223372036854775808", + itoa_r_wrapper(std::numeric_limits::min(), 128, 10, 0)); + EXPECT_EQ("9223372036854775807", + itoa_r_wrapper(std::numeric_limits::max(), 128, 10, 0)); + + EXPECT_EQ("8000000000000000", + itoa_r_wrapper(std::numeric_limits::min(), 128, 16, 0)); + EXPECT_EQ("7fffffffffffffff", + itoa_r_wrapper(std::numeric_limits::max(), 128, 16, 0)); + } 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, 0)); + EXPECT_EQ("deadbeef", itoa_r_wrapper(0xdeadbeef, 128, 16, 0)); + + // Check that itoa_r respects passed buffer size limit. + char buffer[1024]; + EXPECT_TRUE(internal::itoa_r(0xdeadbeef, buffer, 10, 16, 0)); + EXPECT_TRUE(internal::itoa_r(0xdeadbeef, buffer, 9, 16, 0)); + EXPECT_FALSE(internal::itoa_r(0xdeadbeef, buffer, 8, 16, 0)); + EXPECT_FALSE(internal::itoa_r(0xdeadbeef, buffer, 7, 16, 0)); + EXPECT_TRUE(internal::itoa_r(0xbeef, buffer, 5, 16, 4)); + EXPECT_FALSE(internal::itoa_r(0xbeef, buffer, 5, 16, 5)); + EXPECT_FALSE(internal::itoa_r(0xbeef, buffer, 5, 16, 6)); + + // Test padding. + EXPECT_EQ("1", itoa_r_wrapper(1, 128, 10, 0)); + EXPECT_EQ("1", itoa_r_wrapper(1, 128, 10, 1)); + EXPECT_EQ("01", itoa_r_wrapper(1, 128, 10, 2)); + EXPECT_EQ("001", itoa_r_wrapper(1, 128, 10, 3)); + EXPECT_EQ("0001", itoa_r_wrapper(1, 128, 10, 4)); + EXPECT_EQ("00001", itoa_r_wrapper(1, 128, 10, 5)); + EXPECT_EQ("688", itoa_r_wrapper(0x688, 128, 16, 0)); + EXPECT_EQ("688", itoa_r_wrapper(0x688, 128, 16, 1)); + EXPECT_EQ("688", itoa_r_wrapper(0x688, 128, 16, 2)); + EXPECT_EQ("688", itoa_r_wrapper(0x688, 128, 16, 3)); + EXPECT_EQ("0688", itoa_r_wrapper(0x688, 128, 16, 4)); + EXPECT_EQ("00688", itoa_r_wrapper(0x688, 128, 16, 5)); +} +#endif // defined(OS_POSIX) && !defined(OS_ANDROID) + +} // namespace debug +} // namespace base diff --git a/base/debug/stack_trace_win.cc b/base/debug/stack_trace_win.cc new file mode 100644 index 0000000000..986241b0a3 --- /dev/null +++ b/base/debug/stack_trace_win.cc @@ -0,0 +1,271 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/debug/stack_trace.h" + +#include +#include + +#include + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/memory/singleton.h" +#include "base/path_service.h" +#include "base/process/launch.h" +#include "base/strings/string_util.h" +#include "base/synchronization/lock.h" +#include "base/win/windows_version.h" + +namespace base { +namespace debug { + +namespace { + +// 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; +} + +// 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 >::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: + // + // 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(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(&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() : 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_ = 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_; + return; + } + + init_error_ = ERROR_SUCCESS; + + // Work around a mysterious hang on Windows XP. + if (base::win::GetVersion() < base::win::VERSION_VISTA) + return; + + // When transferring the binaries e.g. between bots, path put + // into the executable will get off. To still retrieve symbols correctly, + // add the directory of the executable to symbol search path. + // All following errors are non-fatal. + wchar_t symbols_path[1024]; + + // Note: The below function takes buffer size as number of characters, + // not number of bytes! + if (!SymGetSearchPathW(GetCurrentProcess(), + symbols_path, + arraysize(symbols_path))) { + DLOG(WARNING) << "SymGetSearchPath failed: "; + return; + } + + FilePath module_path; + if (!PathService::Get(FILE_EXE, &module_path)) { + DLOG(WARNING) << "PathService::Get(FILE_EXE) failed."; + return; + } + + std::wstring new_path(std::wstring(symbols_path) + + L";" + module_path.DirName().value()); + if (!SymSetSearchPathW(GetCurrentProcess(), new_path.c_str())) { + DLOG(WARNING) << "SymSetSearchPath failed."; + return; + } + } + + DWORD init_error_; + base::Lock lock_; + DISALLOW_COPY_AND_ASSIGN(SymbolContext); +}; + +} // namespace + +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; +} + +// 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) { + // 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(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/base/debug/trace_event.h b/base/debug/trace_event.h new file mode 100644 index 0000000000..6805513b66 --- /dev/null +++ b/base/debug/trace_event.h @@ -0,0 +1,1542 @@ +// Copyright (c) 2012 The Chromium 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 file defines the set of trace_event macros without specifying +// how the events actually get collected and stored. If you need to expose trace +// events to some other universe, you can copy-and-paste this file as well as +// trace_event.h, modifying the macros contained there as necessary 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", +// TRACE_EVENT_SCOPE_THREAD) +// +// It is often the case that one trace may belong in multiple categories at the +// same time. The first argument to the trace can be a comma-separated list of +// categories, forming a category group, like: +// +// TRACE_EVENT_INSTANT0("input,views", "OnMouseOver", TRACE_EVENT_SCOPE_THREAD) +// +// We can enable/disable tracing of OnMouseOver by enabling/disabling either +// category. +// +// 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 disambiguate 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_group, name, and arg_names. Thus, the following code will +// cause problems: +// char* str = strdup("importantName"); +// 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")); +// +// +// Convertible notes: +// Converting a large data type to a string can be costly. To help with this, +// the trace framework provides an interface ConvertableToTraceFormat. If you +// inherit from it and implement the AppendAsTraceFormat method the trace +// framework will call back to your object to convert a trace output time. This +// means, if the category for the event is disabled, the conversion will not +// happen. +// +// class MyData : public base::debug::ConvertableToTraceFormat { +// public: +// MyData() {} +// virtual ~MyData() {} +// virtual void AppendAsTraceFormat(std::string* out) const OVERRIDE { +// out->append("{\"foo\":1}"); +// } +// private: +// DISALLOW_COPY_AND_ASSIGN(MyData); +// }; +// +// scoped_ptr data(new MyData()); +// TRACE_EVENT1("foo", "bar", "data", +// data.PassAs()); +// +// The trace framework will take ownership if the passed pointer and it will +// be free'd when the trace buffer is flushed. +// +// Note, we only do the conversion when the buffer is flushed, so the provided +// data object should not be modified after it's passed to the trace framework. +// +// +// 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_group_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 acquiring a lock +// and resolving the category. + +#ifndef BASE_DEBUG_TRACE_EVENT_H_ +#define BASE_DEBUG_TRACE_EVENT_H_ + +#include + +#include "base/atomicops.h" +#include "base/debug/trace_event_impl.h" +#include "base/debug/trace_event_memory.h" +#include "build/build_config.h" + +// 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) + +// This will mark the trace event as disabled by default. The user will need +// to explicitly enable the event. +#define TRACE_DISABLED_BY_DEFAULT(name) "disabled-by-default-" name + +// 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) + +// By default, pointers are mangled with the Process ID in TRACE_EVENT_ASYNC +// macros. Use this macro to prevent Process ID mangling. +#define TRACE_ID_DONT_MANGLE(id) \ + trace_event_internal::TraceID::DontMangle(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_group, name) \ + INTERNAL_TRACE_MEMORY(category_group, name) \ + INTERNAL_TRACE_EVENT_ADD_SCOPED(category_group, name) +#define TRACE_EVENT1(category_group, name, arg1_name, arg1_val) \ + INTERNAL_TRACE_MEMORY(category_group, name) \ + INTERNAL_TRACE_EVENT_ADD_SCOPED(category_group, name, arg1_name, arg1_val) +#define TRACE_EVENT2( \ + category_group, name, arg1_name, arg1_val, arg2_name, arg2_val) \ + INTERNAL_TRACE_MEMORY(category_group, name) \ + INTERNAL_TRACE_EVENT_ADD_SCOPED( \ + category_group, name, arg1_name, arg1_val, arg2_name, arg2_val) + +// UNSHIPPED_TRACE_EVENT* are like TRACE_EVENT* except that they are not +// included in official builds. + +#if OFFICIAL_BUILD +#undef TRACING_IS_OFFICIAL_BUILD +#define TRACING_IS_OFFICIAL_BUILD 1 +#elif !defined(TRACING_IS_OFFICIAL_BUILD) +#define TRACING_IS_OFFICIAL_BUILD 0 +#endif + +#if TRACING_IS_OFFICIAL_BUILD +#define UNSHIPPED_TRACE_EVENT0(category_group, name) (void)0 +#define UNSHIPPED_TRACE_EVENT1(category_group, name, arg1_name, arg1_val) \ + (void)0 +#define UNSHIPPED_TRACE_EVENT2(category_group, name, arg1_name, arg1_val, \ + arg2_name, arg2_val) (void)0 +#define UNSHIPPED_TRACE_EVENT_INSTANT0(category_group, name, scope) (void)0 +#define UNSHIPPED_TRACE_EVENT_INSTANT1(category_group, name, scope, \ + arg1_name, arg1_val) (void)0 +#define UNSHIPPED_TRACE_EVENT_INSTANT2(category_group, name, scope, \ + arg1_name, arg1_val, \ + arg2_name, arg2_val) (void)0 +#else +#define UNSHIPPED_TRACE_EVENT0(category_group, name) \ + TRACE_EVENT0(category_group, name) +#define UNSHIPPED_TRACE_EVENT1(category_group, name, arg1_name, arg1_val) \ + TRACE_EVENT1(category_group, name, arg1_name, arg1_val) +#define UNSHIPPED_TRACE_EVENT2(category_group, name, arg1_name, arg1_val, \ + arg2_name, arg2_val) \ + TRACE_EVENT2(category_group, name, arg1_name, arg1_val, arg2_name, arg2_val) +#define UNSHIPPED_TRACE_EVENT_INSTANT0(category_group, name, scope) \ + TRACE_EVENT_INSTANT0(category_group, name, scope) +#define UNSHIPPED_TRACE_EVENT_INSTANT1(category_group, name, scope, \ + arg1_name, arg1_val) \ + TRACE_EVENT_INSTANT1(category_group, name, scope, arg1_name, arg1_val) +#define UNSHIPPED_TRACE_EVENT_INSTANT2(category_group, name, scope, \ + arg1_name, arg1_val, \ + arg2_name, arg2_val) \ + TRACE_EVENT_INSTANT2(category_group, name, scope, 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_group, name, scope) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_INSTANT, \ + category_group, name, TRACE_EVENT_FLAG_NONE | scope) +#define TRACE_EVENT_INSTANT1(category_group, name, scope, arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_INSTANT, \ + category_group, name, TRACE_EVENT_FLAG_NONE | scope, \ + arg1_name, arg1_val) +#define TRACE_EVENT_INSTANT2(category_group, name, scope, arg1_name, arg1_val, \ + arg2_name, arg2_val) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_INSTANT, \ + category_group, name, TRACE_EVENT_FLAG_NONE | scope, \ + arg1_name, arg1_val, arg2_name, arg2_val) +#define TRACE_EVENT_COPY_INSTANT0(category_group, name, scope) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_INSTANT, \ + category_group, name, TRACE_EVENT_FLAG_COPY | scope) +#define TRACE_EVENT_COPY_INSTANT1(category_group, name, scope, \ + arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_INSTANT, \ + category_group, name, TRACE_EVENT_FLAG_COPY | scope, arg1_name, \ + arg1_val) +#define TRACE_EVENT_COPY_INSTANT2(category_group, name, scope, \ + arg1_name, arg1_val, \ + arg2_name, arg2_val) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_INSTANT, \ + category_group, name, TRACE_EVENT_FLAG_COPY | scope, \ + arg1_name, arg1_val, arg2_name, arg2_val) + +// Sets the current sample state to the given category and name (both must be +// constant strings). These states are intended for a sampling profiler. +// Implementation note: we store category and name together because we don't +// want the inconsistency/expense of storing two pointers. +// |thread_bucket| is [0..2] and is used to statically isolate samples in one +// thread from others. +#define TRACE_EVENT_SET_SAMPLING_STATE_FOR_BUCKET( \ + bucket_number, category, name) \ + trace_event_internal:: \ + TraceEventSamplingStateScope::Set(category "\0" name) + +// Returns a current sampling state of the given bucket. +#define TRACE_EVENT_GET_SAMPLING_STATE_FOR_BUCKET(bucket_number) \ + trace_event_internal::TraceEventSamplingStateScope::Current() + +// Creates a scope of a sampling state of the given bucket. +// +// { // The sampling state is set within this scope. +// TRACE_EVENT_SAMPLING_STATE_SCOPE_FOR_BUCKET(0, "category", "name"); +// ...; +// } +#define TRACE_EVENT_SCOPED_SAMPLING_STATE_FOR_BUCKET( \ + bucket_number, category, name) \ + trace_event_internal::TraceEventSamplingStateScope \ + traceEventSamplingScope(category "\0" name); + +// Syntactic sugars for the sampling tracing in the main thread. +#define TRACE_EVENT_SCOPED_SAMPLING_STATE(category, name) \ + TRACE_EVENT_SCOPED_SAMPLING_STATE_FOR_BUCKET(0, category, name) +#define TRACE_EVENT_GET_SAMPLING_STATE() \ + TRACE_EVENT_GET_SAMPLING_STATE_FOR_BUCKET(0) +#define TRACE_EVENT_SET_SAMPLING_STATE(category, name) \ + TRACE_EVENT_SET_SAMPLING_STATE_FOR_BUCKET(0, category, name) + + +// 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_group, name) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_BEGIN, \ + category_group, name, TRACE_EVENT_FLAG_NONE) +#define TRACE_EVENT_BEGIN1(category_group, name, arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_BEGIN, \ + category_group, name, TRACE_EVENT_FLAG_NONE, arg1_name, arg1_val) +#define TRACE_EVENT_BEGIN2(category_group, name, arg1_name, arg1_val, \ + arg2_name, arg2_val) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_BEGIN, \ + category_group, name, TRACE_EVENT_FLAG_NONE, arg1_name, arg1_val, \ + arg2_name, arg2_val) +#define TRACE_EVENT_COPY_BEGIN0(category_group, name) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_BEGIN, \ + category_group, name, TRACE_EVENT_FLAG_COPY) +#define TRACE_EVENT_COPY_BEGIN1(category_group, name, arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_BEGIN, \ + category_group, name, TRACE_EVENT_FLAG_COPY, arg1_name, arg1_val) +#define TRACE_EVENT_COPY_BEGIN2(category_group, name, arg1_name, arg1_val, \ + arg2_name, arg2_val) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_BEGIN, \ + category_group, name, TRACE_EVENT_FLAG_COPY, arg1_name, arg1_val, \ + arg2_name, arg2_val) + +// Similar to TRACE_EVENT_BEGINx but with a custom |at| timestamp provided. +// - |id| is used to match the _BEGIN event with the _END event. +// Events are considered to match if their category_group, 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. +#define TRACE_EVENT_BEGIN_WITH_ID_TID_AND_TIMESTAMP0(category_group, \ + name, id, thread_id, timestamp) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID_TID_AND_TIMESTAMP( \ + TRACE_EVENT_PHASE_ASYNC_BEGIN, category_group, name, id, thread_id, \ + timestamp, TRACE_EVENT_FLAG_NONE) +#define TRACE_EVENT_COPY_BEGIN_WITH_ID_TID_AND_TIMESTAMP0( \ + category_group, name, id, thread_id, timestamp) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID_TID_AND_TIMESTAMP( \ + TRACE_EVENT_PHASE_ASYNC_BEGIN, category_group, name, id, thread_id, \ + timestamp, TRACE_EVENT_FLAG_COPY) + +// 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_group, name) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_END, \ + category_group, name, TRACE_EVENT_FLAG_NONE) +#define TRACE_EVENT_END1(category_group, name, arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_END, \ + category_group, name, TRACE_EVENT_FLAG_NONE, arg1_name, arg1_val) +#define TRACE_EVENT_END2(category_group, name, arg1_name, arg1_val, \ + arg2_name, arg2_val) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_END, \ + category_group, name, TRACE_EVENT_FLAG_NONE, arg1_name, arg1_val, \ + arg2_name, arg2_val) +#define TRACE_EVENT_COPY_END0(category_group, name) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_END, \ + category_group, name, TRACE_EVENT_FLAG_COPY) +#define TRACE_EVENT_COPY_END1(category_group, name, arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_END, \ + category_group, name, TRACE_EVENT_FLAG_COPY, arg1_name, arg1_val) +#define TRACE_EVENT_COPY_END2(category_group, name, arg1_name, arg1_val, \ + arg2_name, arg2_val) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_END, \ + category_group, name, TRACE_EVENT_FLAG_COPY, arg1_name, arg1_val, \ + arg2_name, arg2_val) + +// Similar to TRACE_EVENT_ENDx but with a custom |at| timestamp provided. +// - |id| is used to match the _BEGIN event with the _END event. +// Events are considered to match if their category_group, 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. +#define TRACE_EVENT_END_WITH_ID_TID_AND_TIMESTAMP0(category_group, \ + name, id, thread_id, timestamp) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID_TID_AND_TIMESTAMP( \ + TRACE_EVENT_PHASE_ASYNC_END, category_group, name, id, thread_id, \ + timestamp, TRACE_EVENT_FLAG_NONE) +#define TRACE_EVENT_COPY_END_WITH_ID_TID_AND_TIMESTAMP0( \ + category_group, name, id, thread_id, timestamp) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID_TID_AND_TIMESTAMP( \ + TRACE_EVENT_PHASE_ASYNC_END, category_group, name, id, thread_id, \ + timestamp, TRACE_EVENT_FLAG_COPY) + +// 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_group, name, value) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_COUNTER, \ + category_group, name, TRACE_EVENT_FLAG_NONE, \ + "value", static_cast(value)) +#define TRACE_COPY_COUNTER1(category_group, name, value) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_COUNTER, \ + category_group, name, TRACE_EVENT_FLAG_COPY, \ + "value", static_cast(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_group, name, value1_name, value1_val, \ + value2_name, value2_val) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_COUNTER, \ + category_group, name, TRACE_EVENT_FLAG_NONE, \ + value1_name, static_cast(value1_val), \ + value2_name, static_cast(value2_val)) +#define TRACE_COPY_COUNTER2(category_group, name, value1_name, value1_val, \ + value2_name, value2_val) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_COUNTER, \ + category_group, name, TRACE_EVENT_FLAG_COPY, \ + value1_name, static_cast(value1_val), \ + value2_name, static_cast(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_group, name, id, value) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_COUNTER, \ + category_group, name, id, TRACE_EVENT_FLAG_NONE, \ + "value", static_cast(value)) +#define TRACE_COPY_COUNTER_ID1(category_group, name, id, value) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_COUNTER, \ + category_group, name, id, TRACE_EVENT_FLAG_COPY, \ + "value", static_cast(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_group, name, id, value1_name, value1_val, \ + value2_name, value2_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_COUNTER, \ + category_group, name, id, TRACE_EVENT_FLAG_NONE, \ + value1_name, static_cast(value1_val), \ + value2_name, static_cast(value2_val)) +#define TRACE_COPY_COUNTER_ID2(category_group, name, id, value1_name, \ + value1_val, value2_name, value2_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_COUNTER, \ + category_group, name, id, TRACE_EVENT_FLAG_COPY, \ + value1_name, static_cast(value1_val), \ + value2_name, static_cast(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_group, 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_group, name, id) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_BEGIN, \ + category_group, name, id, TRACE_EVENT_FLAG_NONE) +#define TRACE_EVENT_ASYNC_BEGIN1(category_group, name, id, arg1_name, \ + arg1_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_BEGIN, \ + category_group, name, id, TRACE_EVENT_FLAG_NONE, arg1_name, arg1_val) +#define TRACE_EVENT_ASYNC_BEGIN2(category_group, name, id, arg1_name, \ + arg1_val, arg2_name, arg2_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_BEGIN, \ + category_group, name, id, TRACE_EVENT_FLAG_NONE, \ + arg1_name, arg1_val, arg2_name, arg2_val) +#define TRACE_EVENT_COPY_ASYNC_BEGIN0(category_group, name, id) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_BEGIN, \ + category_group, name, id, TRACE_EVENT_FLAG_COPY) +#define TRACE_EVENT_COPY_ASYNC_BEGIN1(category_group, name, id, arg1_name, \ + arg1_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_BEGIN, \ + category_group, name, id, TRACE_EVENT_FLAG_COPY, \ + arg1_name, arg1_val) +#define TRACE_EVENT_COPY_ASYNC_BEGIN2(category_group, name, id, arg1_name, \ + arg1_val, arg2_name, arg2_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_BEGIN, \ + category_group, name, id, TRACE_EVENT_FLAG_COPY, \ + arg1_name, arg1_val, arg2_name, arg2_val) + +// Records a single ASYNC_STEP event for |step| immediately. If the category +// is not enabled, then this does nothing. The |name| and |id| must match the +// ASYNC_BEGIN event above. The |step| param identifies this step within the +// async event. This should be called at the beginning of the next phase of an +// asynchronous operation. +#define TRACE_EVENT_ASYNC_STEP0(category_group, name, id, step) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_STEP, \ + category_group, name, id, TRACE_EVENT_FLAG_NONE, "step", step) +#define TRACE_EVENT_ASYNC_STEP1(category_group, name, id, step, \ + arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_STEP, \ + category_group, name, id, TRACE_EVENT_FLAG_NONE, "step", step, \ + arg1_name, arg1_val) + +#define TRACE_EVENT_COPY_ASYNC_STEP0(category_group, name, id, step) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_STEP, \ + category_group, name, id, TRACE_EVENT_FLAG_COPY, "step", step) +#define TRACE_EVENT_COPY_ASYNC_STEP1(category_group, name, id, step, \ + arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_STEP, \ + category_group, name, id, TRACE_EVENT_FLAG_COPY, "step", step, \ + 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_group, name, id) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_END, \ + category_group, name, id, TRACE_EVENT_FLAG_NONE) +#define TRACE_EVENT_ASYNC_END1(category_group, name, id, arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_END, \ + category_group, name, id, TRACE_EVENT_FLAG_NONE, arg1_name, arg1_val) +#define TRACE_EVENT_ASYNC_END2(category_group, name, id, arg1_name, arg1_val, \ + arg2_name, arg2_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_END, \ + category_group, name, id, TRACE_EVENT_FLAG_NONE, \ + arg1_name, arg1_val, arg2_name, arg2_val) +#define TRACE_EVENT_COPY_ASYNC_END0(category_group, name, id) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_END, \ + category_group, name, id, TRACE_EVENT_FLAG_COPY) +#define TRACE_EVENT_COPY_ASYNC_END1(category_group, name, id, arg1_name, \ + arg1_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_END, \ + category_group, name, id, TRACE_EVENT_FLAG_COPY, \ + arg1_name, arg1_val) +#define TRACE_EVENT_COPY_ASYNC_END2(category_group, name, id, arg1_name, \ + arg1_val, arg2_name, arg2_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_END, \ + category_group, 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_group, 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_group, name, id) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_BEGIN, \ + category_group, name, id, TRACE_EVENT_FLAG_NONE) +#define TRACE_EVENT_FLOW_BEGIN1(category_group, name, id, arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_BEGIN, \ + category_group, name, id, TRACE_EVENT_FLAG_NONE, arg1_name, arg1_val) +#define TRACE_EVENT_FLOW_BEGIN2(category_group, name, id, arg1_name, arg1_val, \ + arg2_name, arg2_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_BEGIN, \ + category_group, name, id, TRACE_EVENT_FLAG_NONE, \ + arg1_name, arg1_val, arg2_name, arg2_val) +#define TRACE_EVENT_COPY_FLOW_BEGIN0(category_group, name, id) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_BEGIN, \ + category_group, name, id, TRACE_EVENT_FLAG_COPY) +#define TRACE_EVENT_COPY_FLOW_BEGIN1(category_group, name, id, arg1_name, \ + arg1_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_BEGIN, \ + category_group, name, id, TRACE_EVENT_FLAG_COPY, \ + arg1_name, arg1_val) +#define TRACE_EVENT_COPY_FLOW_BEGIN2(category_group, name, id, arg1_name, \ + arg1_val, arg2_name, arg2_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_BEGIN, \ + category_group, 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_group, name, id, step) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_STEP, \ + category_group, name, id, TRACE_EVENT_FLAG_NONE, "step", step) +#define TRACE_EVENT_FLOW_STEP1(category_group, name, id, step, \ + arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_STEP, \ + category_group, name, id, TRACE_EVENT_FLAG_NONE, "step", step, \ + arg1_name, arg1_val) +#define TRACE_EVENT_COPY_FLOW_STEP0(category_group, name, id, step) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_STEP, \ + category_group, name, id, TRACE_EVENT_FLAG_COPY, "step", step) +#define TRACE_EVENT_COPY_FLOW_STEP1(category_group, name, id, step, \ + arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_STEP, \ + category_group, 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_group, name, id) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_END, \ + category_group, name, id, TRACE_EVENT_FLAG_NONE) +#define TRACE_EVENT_FLOW_END1(category_group, name, id, arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_END, \ + category_group, name, id, TRACE_EVENT_FLAG_NONE, arg1_name, arg1_val) +#define TRACE_EVENT_FLOW_END2(category_group, name, id, arg1_name, arg1_val, \ + arg2_name, arg2_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_END, \ + category_group, name, id, TRACE_EVENT_FLAG_NONE, \ + arg1_name, arg1_val, arg2_name, arg2_val) +#define TRACE_EVENT_COPY_FLOW_END0(category_group, name, id) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_END, \ + category_group, name, id, TRACE_EVENT_FLAG_COPY) +#define TRACE_EVENT_COPY_FLOW_END1(category_group, name, id, arg1_name, \ + arg1_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_END, \ + category_group, name, id, TRACE_EVENT_FLAG_COPY, \ + arg1_name, arg1_val) +#define TRACE_EVENT_COPY_FLOW_END2(category_group, name, id, arg1_name, \ + arg1_val, arg2_name, arg2_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_END, \ + category_group, name, id, TRACE_EVENT_FLAG_COPY, \ + arg1_name, arg1_val, arg2_name, arg2_val) + +// Macros to track the life time and value of arbitrary client objects. +// See also TraceTrackableObject. +#define TRACE_EVENT_OBJECT_CREATED_WITH_ID(category_group, name, id) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_CREATE_OBJECT, \ + category_group, name, TRACE_ID_DONT_MANGLE(id), TRACE_EVENT_FLAG_NONE) + +#define TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID(category_group, name, id, snapshot) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_SNAPSHOT_OBJECT, \ + category_group, name, TRACE_ID_DONT_MANGLE(id), TRACE_EVENT_FLAG_NONE,\ + "snapshot", snapshot) + +#define TRACE_EVENT_OBJECT_DELETED_WITH_ID(category_group, name, id) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_DELETE_OBJECT, \ + category_group, name, TRACE_ID_DONT_MANGLE(id), TRACE_EVENT_FLAG_NONE) + + +// Macro to efficiently determine if a given category group is enabled. +#define TRACE_EVENT_CATEGORY_GROUP_ENABLED(category_group, ret) \ + do { \ + INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \ + if (*INTERNAL_TRACE_EVENT_UID(catstatic)) { \ + *ret = true; \ + } else { \ + *ret = false; \ + } \ + } while (0) + +// Macro to efficiently determine, through polling, if a new trace has begun. +#define TRACE_EVENT_IS_NEW_TRACE(ret) \ + do { \ + static int INTERNAL_TRACE_EVENT_UID(lastRecordingNumber) = 0; \ + int num_traces_recorded = TRACE_EVENT_API_GET_NUM_TRACES_RECORDED(); \ + if (num_traces_recorded != -1 && \ + num_traces_recorded != \ + INTERNAL_TRACE_EVENT_UID(lastRecordingNumber)) { \ + INTERNAL_TRACE_EVENT_UID(lastRecordingNumber) = \ + num_traces_recorded; \ + *ret = true; \ + } else { \ + *ret = false; \ + } \ + } while (0) + +//////////////////////////////////////////////////////////////////////////////// +// 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 group. 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_GROUP_ENABLED(const char* category_group) +#define TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED \ + base::debug::TraceLog::GetCategoryGroupEnabled + +// Get the number of times traces have been recorded. This is used to implement +// the TRACE_EVENT_IS_NEW_TRACE facility. +// unsigned int TRACE_EVENT_API_GET_NUM_TRACES_RECORDED() +#define TRACE_EVENT_API_GET_NUM_TRACES_RECORDED \ + base::debug::TraceLog::GetInstance()->GetNumTracesRecorded + +// Add a trace event to the platform tracing system. +// void TRACE_EVENT_API_ADD_TRACE_EVENT( +// char phase, +// const unsigned char* category_group_enabled, +// const char* name, +// unsigned long long id, +// int num_args, +// const char** arg_names, +// const unsigned char* arg_types, +// const unsigned long long* arg_values, +// unsigned char flags) +#define TRACE_EVENT_API_ADD_TRACE_EVENT \ + base::debug::TraceLog::GetInstance()->AddTraceEvent + +// Add a trace event to the platform tracing system. +// void TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_TIMESTAMP( +// char phase, +// const unsigned char* category_group_enabled, +// const char* name, +// unsigned long long id, +// int thread_id, +// const TimeTicks& timestamp, +// 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_WITH_THREAD_ID_AND_TIMESTAMP \ + base::debug::TraceLog::GetInstance()->AddTraceEventWithThreadIdAndTimestamp + +// Defines atomic operations used internally by the tracing system. +#define TRACE_EVENT_API_ATOMIC_WORD base::subtle::AtomicWord +#define TRACE_EVENT_API_ATOMIC_LOAD(var) base::subtle::NoBarrier_Load(&(var)) +#define TRACE_EVENT_API_ATOMIC_STORE(var, value) \ + base::subtle::NoBarrier_Store(&(var), (value)) + +// Defines visibility for classes in trace_event.h +#define TRACE_EVENT_API_CLASS_EXPORT BASE_EXPORT + +// The thread buckets for the sampling profiler. +TRACE_EVENT_API_CLASS_EXPORT extern \ + TRACE_EVENT_API_ATOMIC_WORD g_trace_state[3]; + +#define TRACE_EVENT_API_THREAD_BUCKET(thread_bucket) \ + g_trace_state[thread_bucket] + +//////////////////////////////////////////////////////////////////////////////// + +// 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 collisions. +#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_group) \ + static TRACE_EVENT_API_ATOMIC_WORD INTERNAL_TRACE_EVENT_UID(atomic) = 0; \ + const unsigned char* INTERNAL_TRACE_EVENT_UID(catstatic) = \ + reinterpret_cast(TRACE_EVENT_API_ATOMIC_LOAD( \ + INTERNAL_TRACE_EVENT_UID(atomic))); \ + if (!INTERNAL_TRACE_EVENT_UID(catstatic)) { \ + INTERNAL_TRACE_EVENT_UID(catstatic) = \ + TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(category_group); \ + TRACE_EVENT_API_ATOMIC_STORE(INTERNAL_TRACE_EVENT_UID(atomic), \ + reinterpret_cast( \ + INTERNAL_TRACE_EVENT_UID(catstatic))); \ + } + +// Implementation detail: internal macro to create static category and add +// event if the category is enabled. +#define INTERNAL_TRACE_EVENT_ADD(phase, category_group, name, flags, ...) \ + do { \ + INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \ + if (*INTERNAL_TRACE_EVENT_UID(catstatic)) { \ + 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_group, name, ...) \ + INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \ + trace_event_internal::TraceEndOnScopeClose \ + INTERNAL_TRACE_EVENT_UID(profileScope); \ + if (*INTERNAL_TRACE_EVENT_UID(catstatic)) { \ + trace_event_internal::AddTraceEvent( \ + TRACE_EVENT_PHASE_BEGIN, \ + INTERNAL_TRACE_EVENT_UID(catstatic), \ + 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_group, name, id, \ + flags, ...) \ + do { \ + INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \ + 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) + +// Implementation detail: internal macro to create static category and add +// event if the category is enabled. +#define INTERNAL_TRACE_EVENT_ADD_WITH_ID_TID_AND_TIMESTAMP(phase, \ + category_group, name, id, thread_id, timestamp, flags, ...) \ + do { \ + INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \ + 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::AddTraceEventWithThreadIdAndTimestamp( \ + phase, INTERNAL_TRACE_EVENT_UID(catstatic), \ + name, trace_event_trace_id.data(), \ + thread_id, base::TimeTicks::FromInternalValue(timestamp), \ + trace_event_flags, ##__VA_ARGS__); \ + } \ + } while (0) + +// 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') +#define TRACE_EVENT_PHASE_SAMPLE ('P') +#define TRACE_EVENT_PHASE_CREATE_OBJECT ('N') +#define TRACE_EVENT_PHASE_SNAPSHOT_OBJECT ('O') +#define TRACE_EVENT_PHASE_DELETE_OBJECT ('D') + +// Flags for changing the behavior of TRACE_EVENT_API_ADD_TRACE_EVENT. +#define TRACE_EVENT_FLAG_NONE (static_cast(0)) +#define TRACE_EVENT_FLAG_COPY (static_cast(1 << 0)) +#define TRACE_EVENT_FLAG_HAS_ID (static_cast(1 << 1)) +#define TRACE_EVENT_FLAG_MANGLE_ID (static_cast(1 << 2)) +#define TRACE_EVENT_FLAG_SCOPE_OFFSET (static_cast(1 << 3)) + +#define TRACE_EVENT_FLAG_SCOPE_MASK (static_cast( \ + TRACE_EVENT_FLAG_SCOPE_OFFSET | (TRACE_EVENT_FLAG_SCOPE_OFFSET << 1))) + +// Type values for identifying types in the TraceValue union. +#define TRACE_VALUE_TYPE_BOOL (static_cast(1)) +#define TRACE_VALUE_TYPE_UINT (static_cast(2)) +#define TRACE_VALUE_TYPE_INT (static_cast(3)) +#define TRACE_VALUE_TYPE_DOUBLE (static_cast(4)) +#define TRACE_VALUE_TYPE_POINTER (static_cast(5)) +#define TRACE_VALUE_TYPE_STRING (static_cast(6)) +#define TRACE_VALUE_TYPE_COPY_STRING (static_cast(7)) +#define TRACE_VALUE_TYPE_CONVERTABLE (static_cast(8)) + +// Enum reflecting the scope of an INSTANT event. Must fit within +// TRACE_EVENT_FLAG_SCOPE_MASK. +#define TRACE_EVENT_SCOPE_GLOBAL (static_cast(0 << 3)) +#define TRACE_EVENT_SCOPE_PROCESS (static_cast(1 << 3)) +#define TRACE_EVENT_SCOPE_THREAD (static_cast(2 << 3)) + +#define TRACE_EVENT_SCOPE_NAME_GLOBAL ('g') +#define TRACE_EVENT_SCOPE_NAME_PROCESS ('p') +#define TRACE_EVENT_SCOPE_NAME_THREAD ('t') + +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 by default 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 DontMangle { + public: + explicit DontMangle(void* id) + : data_(static_cast( + reinterpret_cast(id))) {} + explicit DontMangle(unsigned long long id) : data_(id) {} + explicit DontMangle(unsigned long id) : data_(id) {} + explicit DontMangle(unsigned int id) : data_(id) {} + explicit DontMangle(unsigned short id) : data_(id) {} + explicit DontMangle(unsigned char id) : data_(id) {} + explicit DontMangle(long long id) + : data_(static_cast(id)) {} + explicit DontMangle(long id) + : data_(static_cast(id)) {} + explicit DontMangle(int id) + : data_(static_cast(id)) {} + explicit DontMangle(short id) + : data_(static_cast(id)) {} + explicit DontMangle(signed char id) + : data_(static_cast(id)) {} + unsigned long long data() const { return data_; } + private: + unsigned long long data_; + }; + + 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(id)) {} + explicit ForceMangle(long id) + : data_(static_cast(id)) {} + explicit ForceMangle(int id) + : data_(static_cast(id)) {} + explicit ForceMangle(short id) + : data_(static_cast(id)) {} + explicit ForceMangle(signed char id) + : data_(static_cast(id)) {} + unsigned long long data() const { return data_; } + private: + unsigned long long data_; + }; + + TraceID(const void* id, unsigned char* flags) + : data_(static_cast( + reinterpret_cast(id))) { + *flags |= TRACE_EVENT_FLAG_MANGLE_ID; + } + TraceID(ForceMangle id, unsigned char* flags) : data_(id.data()) { + *flags |= TRACE_EVENT_FLAG_MANGLE_ID; + } + TraceID(DontMangle id, unsigned char* flags) : data_(id.data()) { + } + TraceID(unsigned long long id, unsigned char* flags) + : data_(id) { (void)flags; } + TraceID(unsigned long id, unsigned char* flags) + : data_(id) { (void)flags; } + TraceID(unsigned int id, unsigned char* flags) + : data_(id) { (void)flags; } + TraceID(unsigned short id, unsigned char* flags) + : data_(id) { (void)flags; } + TraceID(unsigned char id, unsigned char* flags) + : data_(id) { (void)flags; } + TraceID(long long id, unsigned char* flags) + : data_(static_cast(id)) { (void)flags; } + TraceID(long id, unsigned char* flags) + : data_(static_cast(id)) { (void)flags; } + TraceID(int id, unsigned char* flags) + : data_(static_cast(id)) { (void)flags; } + TraceID(short id, unsigned char* flags) + : data_(static_cast(id)) { (void)flags; } + TraceID(signed char id, unsigned char* flags) + : data_(static_cast(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(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 and AddTraceEventWithThreadIdAndTimestamp 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 AddTraceEventWithThreadIdAndTimestamp( + char phase, + const unsigned char* category_group_enabled, + const char* name, + unsigned long long id, + int thread_id, + const base::TimeTicks& timestamp, + unsigned char flags, + const char* arg1_name, + scoped_ptr arg1_val) { + const int num_args = 1; + unsigned char arg_types[1] = { TRACE_VALUE_TYPE_CONVERTABLE }; + scoped_ptr convertable_values[1]; + convertable_values[0].reset(arg1_val.release()); + + TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP( + phase, category_group_enabled, name, id, thread_id, timestamp, + num_args, &arg1_name, arg_types, NULL, convertable_values, flags); +} + +static inline void AddTraceEvent( + char phase, + const unsigned char* category_group_enabled, + const char* name, + unsigned long long id, + unsigned char flags, + const char* arg1_name, + scoped_ptr arg1_val) { + int thread_id = static_cast(base::PlatformThread::CurrentId()); + base::TimeTicks now = base::TimeTicks::NowFromSystemTraceTime(); + AddTraceEventWithThreadIdAndTimestamp(phase, category_group_enabled, name, id, + thread_id, now, flags, arg1_name, + arg1_val.Pass()); +} + +template +static inline void AddTraceEventWithThreadIdAndTimestamp( + char phase, + const unsigned char* category_group_enabled, + const char* name, + unsigned long long id, + int thread_id, + const base::TimeTicks& timestamp, + unsigned char flags, + const char* arg1_name, + ARG1_TYPE arg1_val, + const char* arg2_name, + scoped_ptr 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]; + SetTraceValue(arg1_val, &arg_types[0], &arg_values[0]); + arg_types[1] = TRACE_VALUE_TYPE_CONVERTABLE; + + scoped_ptr convertable_values[2]; + convertable_values[1].reset(arg2_val.release()); + + TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP( + phase, category_group_enabled, name, id, thread_id, timestamp, + num_args, arg_names, arg_types, arg_values, convertable_values, flags); +} + +template +static inline void AddTraceEvent( + char phase, + const unsigned char* category_group_enabled, + const char* name, + unsigned long long id, + unsigned char flags, + const char* arg1_name, + ARG1_TYPE arg1_val, + const char* arg2_name, + scoped_ptr arg2_val) { + int thread_id = static_cast(base::PlatformThread::CurrentId()); + base::TimeTicks now = base::TimeTicks::NowFromSystemTraceTime(); + AddTraceEventWithThreadIdAndTimestamp(phase, category_group_enabled, name, id, + thread_id, now, flags, + arg1_name, arg1_val, + arg2_name, arg2_val.Pass()); +} + +template +static inline void AddTraceEventWithThreadIdAndTimestamp( + char phase, + const unsigned char* category_group_enabled, + const char* name, + unsigned long long id, + int thread_id, + const base::TimeTicks& timestamp, + unsigned char flags, + const char* arg1_name, + scoped_ptr arg1_val, + const char* arg2_name, + 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]; + arg_types[0] = TRACE_VALUE_TYPE_CONVERTABLE; + arg_values[0] = 0; + SetTraceValue(arg2_val, &arg_types[1], &arg_values[1]); + + scoped_ptr convertable_values[2]; + convertable_values[0].reset(arg1_val.release()); + + TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP( + phase, category_group_enabled, name, id, thread_id, timestamp, + num_args, arg_names, arg_types, arg_values, convertable_values, flags); +} + +template +static inline void AddTraceEvent( + char phase, + const unsigned char* category_group_enabled, + const char* name, + unsigned long long id, + unsigned char flags, + const char* arg1_name, + scoped_ptr arg1_val, + const char* arg2_name, + ARG2_TYPE arg2_val) { + int thread_id = static_cast(base::PlatformThread::CurrentId()); + base::TimeTicks now = base::TimeTicks::NowFromSystemTraceTime(); + AddTraceEventWithThreadIdAndTimestamp(phase, category_group_enabled, name, id, + thread_id, now, flags, + arg1_name, arg1_val.Pass(), + arg2_name, arg2_val); +} + +static inline void AddTraceEventWithThreadIdAndTimestamp( + char phase, + const unsigned char* category_group_enabled, + const char* name, + unsigned long long id, + int thread_id, + const base::TimeTicks& timestamp, + unsigned char flags, + const char* arg1_name, + scoped_ptr arg1_val, + const char* arg2_name, + scoped_ptr arg2_val) { + const int num_args = 2; + const char* arg_names[2] = { arg1_name, arg2_name }; + unsigned char arg_types[2] = + { TRACE_VALUE_TYPE_CONVERTABLE, TRACE_VALUE_TYPE_CONVERTABLE }; + scoped_ptr convertable_values[2]; + convertable_values[0].reset(arg1_val.release()); + convertable_values[1].reset(arg2_val.release()); + + TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP( + phase, category_group_enabled, name, id, thread_id, timestamp, + num_args, arg_names, arg_types, NULL, convertable_values, flags); +} + +static inline void AddTraceEvent( + char phase, + const unsigned char* category_group_enabled, + const char* name, + unsigned long long id, + unsigned char flags, + const char* arg1_name, + scoped_ptr arg1_val, + const char* arg2_name, + scoped_ptr arg2_val) { + int thread_id = static_cast(base::PlatformThread::CurrentId()); + base::TimeTicks now = base::TimeTicks::NowFromSystemTraceTime(); + AddTraceEventWithThreadIdAndTimestamp(phase, category_group_enabled, name, id, + thread_id, now, flags, + arg1_name, arg1_val.Pass(), + arg2_name, arg2_val.Pass()); +} + +static inline void AddTraceEventWithThreadIdAndTimestamp( + char phase, + const unsigned char* category_group_enabled, + const char* name, + unsigned long long id, + int thread_id, + const base::TimeTicks& timestamp, + unsigned char flags) { + TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP( + phase, category_group_enabled, name, id, thread_id, timestamp, + kZeroNumArgs, NULL, NULL, NULL, NULL, flags); +} + +static inline void AddTraceEvent(char phase, + const unsigned char* category_group_enabled, + const char* name, + unsigned long long id, + unsigned char flags) { + int thread_id = static_cast(base::PlatformThread::CurrentId()); + base::TimeTicks now = base::TimeTicks::NowFromSystemTraceTime(); + AddTraceEventWithThreadIdAndTimestamp(phase, category_group_enabled, name, id, + thread_id, now, flags); +} + +template +static inline void AddTraceEventWithThreadIdAndTimestamp( + char phase, + const unsigned char* category_group_enabled, + const char* name, + unsigned long long id, + int thread_id, + const base::TimeTicks& timestamp, + 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]; + SetTraceValue(arg1_val, &arg_types[0], &arg_values[0]); + TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP( + phase, category_group_enabled, name, id, thread_id, timestamp, + num_args, &arg1_name, arg_types, arg_values, NULL, flags); +} + +template +static inline void AddTraceEvent(char phase, + const unsigned char* category_group_enabled, + const char* name, + unsigned long long id, + unsigned char flags, + const char* arg1_name, + const ARG1_TYPE& arg1_val) { + int thread_id = static_cast(base::PlatformThread::CurrentId()); + base::TimeTicks now = base::TimeTicks::NowFromSystemTraceTime(); + AddTraceEventWithThreadIdAndTimestamp(phase, category_group_enabled, name, id, + thread_id, now, flags, arg1_name, + arg1_val); +} + +template +static inline void AddTraceEventWithThreadIdAndTimestamp( + char phase, + const unsigned char* category_group_enabled, + const char* name, + unsigned long long id, + int thread_id, + const base::TimeTicks& timestamp, + 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]; + SetTraceValue(arg1_val, &arg_types[0], &arg_values[0]); + SetTraceValue(arg2_val, &arg_types[1], &arg_values[1]); + TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP( + phase, category_group_enabled, name, id, thread_id, timestamp, + num_args, arg_names, arg_types, arg_values, NULL, flags); +} + +template +static inline void AddTraceEvent(char phase, + const unsigned char* category_group_enabled, + const char* name, + unsigned long long id, + unsigned char flags, + const char* arg1_name, + const ARG1_TYPE& arg1_val, + const char* arg2_name, + const ARG2_TYPE& arg2_val) { + int thread_id = static_cast(base::PlatformThread::CurrentId()); + base::TimeTicks now = base::TimeTicks::NowFromSystemTraceTime(); + AddTraceEventWithThreadIdAndTimestamp(phase, category_group_enabled, name, id, + thread_id, now, flags, arg1_name, + arg1_val, arg2_name, arg2_val); +} + +// Used by TRACE_EVENTx macro. Do not use directly. +class TRACE_EVENT_API_CLASS_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_group_enabled, + const char* name) { + data_.category_group_enabled = category_group_enabled; + data_.name = name; + p_data_ = &data_; + } + + private: + // Add the end event if the category is still enabled. + void AddEventIfEnabled() { + // Only called when p_data_ is non-null. + if (*p_data_->category_group_enabled) { + TRACE_EVENT_API_ADD_TRACE_EVENT( + TRACE_EVENT_PHASE_END, // phase + p_data_->category_group_enabled, // category enabled + p_data_->name, // name + kNoEventId, // id + kZeroNumArgs, // num_args + NULL, // arg_names + NULL, // arg_types + NULL, // arg_values + NULL, // convertable_values + TRACE_EVENT_FLAG_NONE); // flags + } + } + + // This Data struct workaround is to avoid initializing all the members + // in Data during construction of this object, since this object is always + // constructed, even when tracing is disabled. If the members of Data were + // members of this class instead, compiler warnings occur about potential + // uninitialized accesses. + struct Data { + const unsigned char* category_group_enabled; + const char* name; + }; + Data* p_data_; + Data data_; +}; + +// Used by TRACE_EVENT_BINARY_EFFICIENTx macro. Do not use directly. +class TRACE_EVENT_API_CLASS_EXPORT ScopedTrace { + public: + ScopedTrace(TRACE_EVENT_API_ATOMIC_WORD* event_uid, const char* name); + ~ScopedTrace(); + + private: + const unsigned char* category_group_enabled_; + const char* name_; +}; + +// A support macro for TRACE_EVENT_BINARY_EFFICIENTx +#define INTERNAL_TRACE_EVENT_BINARY_EFFICIENT_ADD_SCOPED( \ + category_group, name, ...) \ + static TRACE_EVENT_API_ATOMIC_WORD INTERNAL_TRACE_EVENT_UID(atomic) = 0; \ + trace_event_internal::ScopedTrace \ + INTERNAL_TRACE_EVENT_UID(profileScope)( \ + &INTERNAL_TRACE_EVENT_UID(atomic), name); \ + +// This macro generates less code then TRACE_EVENT0 but is also +// slower to execute when tracing is off. It should generally only be +// used with code that is seldom executed or conditionally executed +// when debugging. +#define TRACE_EVENT_BINARY_EFFICIENT0(category_group, name) \ + INTERNAL_TRACE_EVENT_BINARY_EFFICIENT_ADD_SCOPED(category_group, name) + +// TraceEventSamplingStateScope records the current sampling state +// and sets a new sampling state. When the scope exists, it restores +// the sampling state having recorded. +template +class TraceEventSamplingStateScope { + public: + TraceEventSamplingStateScope(const char* category_and_name) { + previous_state_ = TraceEventSamplingStateScope::Current(); + TraceEventSamplingStateScope::Set(category_and_name); + } + + ~TraceEventSamplingStateScope() { + TraceEventSamplingStateScope::Set(previous_state_); + } + + static inline const char* Current() { + return reinterpret_cast(TRACE_EVENT_API_ATOMIC_LOAD( + g_trace_state[BucketNumber])); + } + + static inline void Set(const char* category_and_name) { + TRACE_EVENT_API_ATOMIC_STORE( + g_trace_state[BucketNumber], + reinterpret_cast( + const_cast(category_and_name))); + } + + private: + const char* previous_state_; +}; + +} // namespace trace_event_internal + +namespace base { +namespace debug { + +template class TraceScopedTrackableObject { + public: + TraceScopedTrackableObject(const char* category_group, const char* name, + IDType id) + : category_group_(category_group), + name_(name), + id_(id) { + TRACE_EVENT_OBJECT_CREATED_WITH_ID(category_group_, name_, id_); + } + + template void snapshot(ArgType snapshot) { + TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID(category_group_, name_, id_, snapshot); + } + + ~TraceScopedTrackableObject() { + TRACE_EVENT_OBJECT_DELETED_WITH_ID(category_group_, name_, id_); + } + + private: + const char* category_group_; + const char* name_; + IDType id_; + + DISALLOW_COPY_AND_ASSIGN(TraceScopedTrackableObject); +}; + +} // namespace debug +} // namespace base + +#endif /* BASE_DEBUG_TRACE_EVENT_H_ */ diff --git a/base/debug/trace_event_android.cc b/base/debug/trace_event_android.cc new file mode 100644 index 0000000000..78d9de6674 --- /dev/null +++ b/base/debug/trace_event_android.cc @@ -0,0 +1,158 @@ +// Copyright (c) 2012 The Chromium Authors. All 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 + +#include "base/debug/trace_event.h" +#include "base/format_macros.h" +#include "base/logging.h" +#include "base/strings/stringprintf.h" + +namespace { + +int g_atrace_fd = -1; +const char* kATraceMarkerFile = "/sys/kernel/debug/tracing/trace_marker"; + +void WriteEvent( + char phase, + const char* category_group, + const char* name, + unsigned long long id, + int num_args, + const char** arg_names, + const unsigned char* arg_types, + const unsigned long long* arg_values, + scoped_ptr convertable_values[], + unsigned char flags) { + std::string out = base::StringPrintf("%c|%d|%s", phase, getpid(), name); + if (flags & TRACE_EVENT_FLAG_HAS_ID) + base::StringAppendF(&out, "-%" PRIx64, static_cast(id)); + out += '|'; + + for (int i = 0; i < num_args; ++i) { + if (i) + out += ';'; + out += arg_names[i]; + out += '='; + std::string::size_type value_start = out.length(); + if (arg_types[i] == TRACE_VALUE_TYPE_CONVERTABLE) { + convertable_values[i]->AppendAsTraceFormat(&out); + } else { + base::debug::TraceEvent::TraceValue value; + value.as_uint = arg_values[i]; + base::debug::TraceEvent::AppendValueAsJSON(arg_types[i], value, &out); + } + // 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(), '|', '!'); + } + + out += '|'; + out += category_group; + write(g_atrace_fd, out.c_str(), out.size()); +} + +} // namespace + +namespace base { +namespace debug { + +void TraceLog::StartATrace() { + AutoLock lock(lock_); + if (g_atrace_fd == -1) { + g_atrace_fd = open(kATraceMarkerFile, O_WRONLY); + if (g_atrace_fd == -1) { + LOG(WARNING) << "Couldn't open " << kATraceMarkerFile; + } else { + UpdateCategoryGroupEnabledFlags(); + } + } +} + +void TraceLog::StopATrace() { + AutoLock lock(lock_); + if (g_atrace_fd != -1) { + close(g_atrace_fd); + g_atrace_fd = -1; + UpdateCategoryGroupEnabledFlags(); + } +} + +void TraceLog::SendToATrace( + char phase, + const char* category_group, + const char* name, + unsigned long long id, + int num_args, + const char** arg_names, + const unsigned char* arg_types, + const unsigned long long* arg_values, + scoped_ptr convertable_values[], + unsigned char flags) { + if (g_atrace_fd == -1) + return; + + switch (phase) { + case TRACE_EVENT_PHASE_BEGIN: + WriteEvent('B', category_group, name, id, + num_args, arg_names, arg_types, arg_values, convertable_values, + flags); + break; + + case TRACE_EVENT_PHASE_END: + // Though a single 'E' is enough, here append pid, name and + // category_group etc. So that unpaired events can be found easily. + WriteEvent('E', category_group, name, id, + num_args, arg_names, arg_types, arg_values, convertable_values, + flags); + break; + + case TRACE_EVENT_PHASE_INSTANT: + // Simulate an instance event with a pair of begin/end events. + WriteEvent('B', category_group, name, id, + num_args, arg_names, arg_types, arg_values, convertable_values, + flags); + write(g_atrace_fd, "E", 1); + break; + + case TRACE_EVENT_PHASE_COUNTER: + for (int i = 0; i < num_args; ++i) { + DCHECK(arg_types[i] == TRACE_VALUE_TYPE_INT); + std::string out = base::StringPrintf("C|%d|%s-%s", + getpid(), name, arg_names[i]); + if (flags & TRACE_EVENT_FLAG_HAS_ID) + StringAppendF(&out, "-%" PRIx64, static_cast(id)); + StringAppendF(&out, "|%d|%s", + static_cast(arg_values[i]), category_group); + write(g_atrace_fd, out.c_str(), out.size()); + } + break; + + default: + // Do nothing. + break; + } +} + +// Must be called with lock_ locked. +void TraceLog::ApplyATraceEnabledFlag(unsigned char* category_group_enabled) { + if (g_atrace_fd == -1) + return; + + // Don't enable disabled-by-default categories for atrace. + const char* category_group = GetCategoryGroupName(category_group_enabled); + if (strncmp(category_group, TRACE_DISABLED_BY_DEFAULT(""), + strlen(TRACE_DISABLED_BY_DEFAULT(""))) == 0) + return; + + *category_group_enabled |= ATRACE_ENABLED; +} + +} // namespace debug +} // namespace base diff --git a/base/debug/trace_event_impl.cc b/base/debug/trace_event_impl.cc new file mode 100644 index 0000000000..cc22424f15 --- /dev/null +++ b/base/debug/trace_event_impl.cc @@ -0,0 +1,1692 @@ +// Copyright (c) 2012 The Chromium Authors. All 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 + +#include "base/base_switches.h" +#include "base/bind.h" +#include "base/command_line.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/process_metrics.h" +#include "base/stl_util.h" +#include "base/strings/string_split.h" +#include "base/strings/string_tokenizer.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/synchronization/cancellation_flag.h" +#include "base/synchronization/waitable_event.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_id_name_manager.h" +#include "base/threading/thread_local.h" +#include "base/time/time.h" + +#if defined(OS_WIN) +#include "base/debug/trace_event_win.h" +#endif + +class DeleteTraceLogForTesting { + public: + static void Delete() { + Singleton >::OnExit(0); + } +}; + +// The thread buckets for the sampling profiler. +BASE_EXPORT TRACE_EVENT_API_ATOMIC_WORD g_trace_state[3]; + +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; +const size_t kTraceEventInitialBufferSize = 1024; + +#define MAX_CATEGORY_GROUPS 100 + +namespace { + +// Parallel arrays g_category_groups and g_category_group_enabled are separate +// so that a pointer to a member of g_category_group_enabled can be easily +// converted to an index into g_category_groups. This allows macros to deal +// only with char enabled pointers from g_category_group_enabled, and we can +// convert internally to determine the category name from the char enabled +// pointer. +const char* g_category_groups[MAX_CATEGORY_GROUPS] = { + "tracing already shutdown", + "tracing categories exhausted; must increase MAX_CATEGORY_GROUPS", + "__metadata", +}; + +// The enabled flag is char instead of bool so that the API can be used from C. +unsigned char g_category_group_enabled[MAX_CATEGORY_GROUPS] = { 0 }; +const int g_category_already_shutdown = 0; +const int g_category_categories_exhausted = 1; +const int g_category_metadata = 2; +const int g_num_builtin_categories = 3; +int g_category_index = g_num_builtin_categories; // Skip default categories. + +// The name of the current thread. This is used to decide if the current +// thread name has changed. We combine all the seen thread names into the +// output name for the thread. +LazyInstance >::Leaky + g_current_thread_name = LAZY_INSTANCE_INITIALIZER; + +const char kRecordUntilFull[] = "record-until-full"; +const char kRecordContinuously[] = "record-continuously"; +const char kEnableSampling[] = "enable-sampling"; + +size_t NextIndex(size_t index) { + index++; + if (index >= kTraceEventBufferSize) + index = 0; + return index; +} + +} // namespace + +class TraceBufferRingBuffer : public TraceBuffer { + public: + TraceBufferRingBuffer() + : unused_event_index_(0), + oldest_event_index_(0) { + logged_events_.reserve(kTraceEventInitialBufferSize); + } + + virtual ~TraceBufferRingBuffer() {} + + virtual void AddEvent(const TraceEvent& event) OVERRIDE { + if (unused_event_index_ < Size()) + logged_events_[unused_event_index_] = event; + else + logged_events_.push_back(event); + + unused_event_index_ = NextIndex(unused_event_index_); + if (unused_event_index_ == oldest_event_index_) { + oldest_event_index_ = NextIndex(oldest_event_index_); + } + } + + virtual bool HasMoreEvents() const OVERRIDE { + return oldest_event_index_ != unused_event_index_; + } + + virtual const TraceEvent& NextEvent() OVERRIDE { + DCHECK(HasMoreEvents()); + + size_t next = oldest_event_index_; + oldest_event_index_ = NextIndex(oldest_event_index_); + return GetEventAt(next); + } + + virtual bool IsFull() const OVERRIDE { + return false; + } + + virtual size_t CountEnabledByName( + const unsigned char* category, + const std::string& event_name) const OVERRIDE { + size_t notify_count = 0; + size_t index = oldest_event_index_; + while (index != unused_event_index_) { + const TraceEvent& event = GetEventAt(index); + if (category == event.category_group_enabled() && + strcmp(event_name.c_str(), event.name()) == 0) { + ++notify_count; + } + index = NextIndex(index); + } + return notify_count; + } + + virtual const TraceEvent& GetEventAt(size_t index) const OVERRIDE { + DCHECK(index < logged_events_.size()); + return logged_events_[index]; + } + + virtual size_t Size() const OVERRIDE { + return logged_events_.size(); + } + + private: + size_t unused_event_index_; + size_t oldest_event_index_; + std::vector logged_events_; + + DISALLOW_COPY_AND_ASSIGN(TraceBufferRingBuffer); +}; + +class TraceBufferVector : public TraceBuffer { + public: + TraceBufferVector() : current_iteration_index_(0) { + logged_events_.reserve(kTraceEventInitialBufferSize); + } + + virtual ~TraceBufferVector() { + } + + virtual void AddEvent(const TraceEvent& event) OVERRIDE { + // Note, we have two callers which need to be handled. The first is + // AddTraceEventWithThreadIdAndTimestamp() which checks Size() and does an + // early exit if full. The second is AddThreadNameMetadataEvents(). + // We can not DECHECK(!IsFull()) because we have to add the metadata + // events even if the buffer is full. + logged_events_.push_back(event); + } + + virtual bool HasMoreEvents() const OVERRIDE { + return current_iteration_index_ < Size(); + } + + virtual const TraceEvent& NextEvent() OVERRIDE { + DCHECK(HasMoreEvents()); + return GetEventAt(current_iteration_index_++); + } + + virtual bool IsFull() const OVERRIDE { + return Size() >= kTraceEventBufferSize; + } + + virtual size_t CountEnabledByName( + const unsigned char* category, + const std::string& event_name) const OVERRIDE { + size_t notify_count = 0; + for (size_t i = 0; i < Size(); i++) { + const TraceEvent& event = GetEventAt(i); + if (category == event.category_group_enabled() && + strcmp(event_name.c_str(), event.name()) == 0) { + ++notify_count; + } + } + return notify_count; + } + + virtual const TraceEvent& GetEventAt(size_t index) const OVERRIDE { + DCHECK(index < logged_events_.size()); + return logged_events_[index]; + } + + virtual size_t Size() const OVERRIDE { + return logged_events_.size(); + } + + private: + size_t current_iteration_index_; + std::vector logged_events_; + + DISALLOW_COPY_AND_ASSIGN(TraceBufferVector); +}; + +class TraceBufferDiscardsEvents : public TraceBuffer { + public: + virtual ~TraceBufferDiscardsEvents() { } + + virtual void AddEvent(const TraceEvent& event) OVERRIDE {} + virtual bool HasMoreEvents() const OVERRIDE { return false; } + + virtual const TraceEvent& NextEvent() OVERRIDE { + NOTREACHED(); + return *static_cast(NULL); + } + + virtual bool IsFull() const OVERRIDE { return false; } + + virtual size_t CountEnabledByName( + const unsigned char* category, + const std::string& event_name) const OVERRIDE { + return 0; + } + + virtual size_t Size() const OVERRIDE { return 0; } + + virtual const TraceEvent& GetEventAt(size_t index) const OVERRIDE { + NOTREACHED(); + return *static_cast(NULL); + } +}; + +//////////////////////////////////////////////////////////////////////////////// +// +// 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(written), end - *buffer); + *member = *buffer; + *buffer += written; + } +} + +} // namespace + +TraceEvent::TraceEvent() + : id_(0u), + category_group_enabled_(NULL), + name_(NULL), + thread_id_(0), + phase_(TRACE_EVENT_PHASE_BEGIN), + flags_(0) { + arg_names_[0] = NULL; + arg_names_[1] = NULL; + memset(arg_values_, 0, sizeof(arg_values_)); +} + +TraceEvent::TraceEvent( + int thread_id, + TimeTicks timestamp, + char phase, + const unsigned char* category_group_enabled, + const char* name, + unsigned long long id, + int num_args, + const char** arg_names, + const unsigned char* arg_types, + const unsigned long long* arg_values, + scoped_ptr convertable_values[], + unsigned char flags) + : timestamp_(timestamp), + id_(id), + category_group_enabled_(category_group_enabled), + name_(name), + thread_id_(thread_id), + phase_(phase), + flags_(flags) { + // Clamp num_args since it may have been set by a third_party library. + num_args = (num_args > kTraceMaxNumArgs) ? kTraceMaxNumArgs : num_args; + int i = 0; + for (; i < num_args; ++i) { + arg_names_[i] = arg_names[i]; + arg_types_[i] = arg_types[i]; + + if (arg_types[i] == TRACE_VALUE_TYPE_CONVERTABLE) + convertable_values_[i].reset(convertable_values[i].release()); + else + arg_values_[i].as_uint = arg_values[i]; + } + for (; i < kTraceMaxNumArgs; ++i) { + arg_names_[i] = NULL; + arg_values_[i].as_uint = 0u; + convertable_values_[i].reset(); + 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) { + // No copying of convertable types, we retain ownership. + if (arg_types_[i] == TRACE_VALUE_TYPE_CONVERTABLE) + continue; + + // 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 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_types_[i] == TRACE_VALUE_TYPE_CONVERTABLE) + continue; + if (arg_is_copy[i]) + CopyTraceEventParameter(&ptr, &arg_values_[i].as_string, end); + } + DCHECK_EQ(end, ptr) << "Overrun by " << ptr - end; + } +} + +TraceEvent::TraceEvent(const TraceEvent& other) + : timestamp_(other.timestamp_), + id_(other.id_), + category_group_enabled_(other.category_group_enabled_), + name_(other.name_), + thread_id_(other.thread_id_), + phase_(other.phase_), + flags_(other.flags_) { + parameter_copy_storage_ = other.parameter_copy_storage_; + + for (int i = 0; i < kTraceMaxNumArgs; ++i) { + arg_values_[i] = other.arg_values_[i]; + arg_names_[i] = other.arg_names_[i]; + arg_types_[i] = other.arg_types_[i]; + + if (arg_types_[i] == TRACE_VALUE_TYPE_CONVERTABLE) { + convertable_values_[i].reset( + const_cast(&other)->convertable_values_[i].release()); + } else { + convertable_values_[i].reset(); + } + } +} + +TraceEvent& TraceEvent::operator=(const TraceEvent& other) { + if (this == &other) + return *this; + + timestamp_ = other.timestamp_; + id_ = other.id_; + category_group_enabled_ = other.category_group_enabled_; + name_ = other.name_; + parameter_copy_storage_ = other.parameter_copy_storage_; + thread_id_ = other.thread_id_; + phase_ = other.phase_; + flags_ = other.flags_; + + for (int i = 0; i < kTraceMaxNumArgs; ++i) { + arg_values_[i] = other.arg_values_[i]; + arg_names_[i] = other.arg_names_[i]; + arg_types_[i] = other.arg_types_[i]; + + if (arg_types_[i] == TRACE_VALUE_TYPE_CONVERTABLE) { + convertable_values_[i].reset( + const_cast(&other)->convertable_values_[i].release()); + } else { + convertable_values_[i].reset(); + } + } + return *this; +} + +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(value.as_uint)); + break; + case TRACE_VALUE_TYPE_INT: + StringAppendF(out, "%" PRId64, static_cast(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, "\"0x%" PRIx64 "\"", static_cast( + reinterpret_cast( + 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::AppendAsJSON(std::string* out) const { + int64 time_int64 = timestamp_.ToInternalValue(); + int process_id = TraceLog::GetInstance()->process_id(); + // Category group checked at category creation time. + DCHECK(!strchr(name_, '"')); + StringAppendF(out, + "{\"cat\":\"%s\",\"pid\":%i,\"tid\":%i,\"ts\":%" PRId64 "," + "\"ph\":\"%c\",\"name\":\"%s\",\"args\":{", + TraceLog::GetCategoryGroupName(category_group_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 += "\":"; + + if (arg_types_[i] == TRACE_VALUE_TYPE_CONVERTABLE) + convertable_values_[i]->AppendAsTraceFormat(out); + else + 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\":\"0x%" PRIx64 "\"", static_cast(id_)); + + // Instant events also output their scope. + if (phase_ == TRACE_EVENT_PHASE_INSTANT) { + char scope = '?'; + switch (flags_ & TRACE_EVENT_FLAG_SCOPE_MASK) { + case TRACE_EVENT_SCOPE_GLOBAL: + scope = TRACE_EVENT_SCOPE_NAME_GLOBAL; + break; + + case TRACE_EVENT_SCOPE_PROCESS: + scope = TRACE_EVENT_SCOPE_NAME_PROCESS; + break; + + case TRACE_EVENT_SCOPE_THREAD: + scope = TRACE_EVENT_SCOPE_NAME_THREAD; + break; + } + StringAppendF(out, ",\"s\":\"%c\"", scope); + } + + *out += "}"; +} + +void TraceEvent::AppendPrettyPrinted(std::ostringstream* out) const { + *out << name_ << "["; + *out << TraceLog::GetCategoryGroupName(category_group_enabled_); + *out << "]"; + if (arg_names_[0]) { + *out << ", {"; + for (int i = 0; i < kTraceMaxNumArgs && arg_names_[i]; ++i) { + if (i > 0) + *out << ", "; + *out << arg_names_[i] << ":"; + std::string value_as_text; + + if (arg_types_[i] == TRACE_VALUE_TYPE_CONVERTABLE) + convertable_values_[i]->AppendAsTraceFormat(&value_as_text); + else + AppendValueAsJSON(arg_types_[i], arg_values_[i], &value_as_text); + + *out << value_as_text; + } + *out << "}"; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +// TraceResultBuffer +// +//////////////////////////////////////////////////////////////////////////////// + +TraceResultBuffer::OutputCallback + TraceResultBuffer::SimpleOutput::GetCallback() { + return Bind(&SimpleOutput::Append, 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("]"); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// TraceSamplingThread +// +//////////////////////////////////////////////////////////////////////////////// +class TraceBucketData; +typedef base::Callback TraceSampleCallback; + +class TraceBucketData { + public: + TraceBucketData(base::subtle::AtomicWord* bucket, + const char* name, + TraceSampleCallback callback); + ~TraceBucketData(); + + TRACE_EVENT_API_ATOMIC_WORD* bucket; + const char* bucket_name; + TraceSampleCallback callback; +}; + +// This object must be created on the IO thread. +class TraceSamplingThread : public PlatformThread::Delegate { + public: + TraceSamplingThread(); + virtual ~TraceSamplingThread(); + + // Implementation of PlatformThread::Delegate: + virtual void ThreadMain() OVERRIDE; + + static void DefaultSampleCallback(TraceBucketData* bucekt_data); + + void Stop(); + void InstallWaitableEventForSamplingTesting(WaitableEvent* waitable_event); + + private: + friend class TraceLog; + + void GetSamples(); + // Not thread-safe. Once the ThreadMain has been called, this can no longer + // be called. + void RegisterSampleBucket(TRACE_EVENT_API_ATOMIC_WORD* bucket, + const char* const name, + TraceSampleCallback callback); + // Splits a combined "category\0name" into the two component parts. + static void ExtractCategoryAndName(const char* combined, + const char** category, + const char** name); + std::vector sample_buckets_; + bool thread_running_; + scoped_ptr cancellation_flag_; + scoped_ptr waitable_event_for_testing_; +}; + + +TraceSamplingThread::TraceSamplingThread() + : thread_running_(false) { + cancellation_flag_.reset(new CancellationFlag); +} + +TraceSamplingThread::~TraceSamplingThread() { +} + +void TraceSamplingThread::ThreadMain() { + PlatformThread::SetName("Sampling Thread"); + thread_running_ = true; + const int kSamplingFrequencyMicroseconds = 1000; + while (!cancellation_flag_->IsSet()) { + PlatformThread::Sleep( + TimeDelta::FromMicroseconds(kSamplingFrequencyMicroseconds)); + GetSamples(); + if (waitable_event_for_testing_.get()) + waitable_event_for_testing_->Signal(); + } +} + +// static +void TraceSamplingThread::DefaultSampleCallback(TraceBucketData* bucket_data) { + TRACE_EVENT_API_ATOMIC_WORD category_and_name = + TRACE_EVENT_API_ATOMIC_LOAD(*bucket_data->bucket); + if (!category_and_name) + return; + const char* const combined = + reinterpret_cast(category_and_name); + const char* category_group; + const char* name; + ExtractCategoryAndName(combined, &category_group, &name); + TRACE_EVENT_API_ADD_TRACE_EVENT(TRACE_EVENT_PHASE_SAMPLE, + TraceLog::GetCategoryGroupEnabled(category_group), + name, 0, 0, NULL, NULL, NULL, NULL, 0); +} + +void TraceSamplingThread::GetSamples() { + for (size_t i = 0; i < sample_buckets_.size(); ++i) { + TraceBucketData* bucket_data = &sample_buckets_[i]; + bucket_data->callback.Run(bucket_data); + } +} + +void TraceSamplingThread::RegisterSampleBucket( + TRACE_EVENT_API_ATOMIC_WORD* bucket, + const char* const name, + TraceSampleCallback callback) { + DCHECK(!thread_running_); + sample_buckets_.push_back(TraceBucketData(bucket, name, callback)); +} + +// static +void TraceSamplingThread::ExtractCategoryAndName(const char* combined, + const char** category, + const char** name) { + *category = combined; + *name = &combined[strlen(combined) + 1]; +} + +void TraceSamplingThread::Stop() { + cancellation_flag_->Set(); +} + +void TraceSamplingThread::InstallWaitableEventForSamplingTesting( + WaitableEvent* waitable_event) { + waitable_event_for_testing_.reset(waitable_event); +} + + +TraceBucketData::TraceBucketData(base::subtle::AtomicWord* bucket, + const char* name, + TraceSampleCallback callback) + : bucket(bucket), + bucket_name(name), + callback(callback) { +} + +TraceBucketData::~TraceBucketData() { +} + +//////////////////////////////////////////////////////////////////////////////// +// +// 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 >::get(); +} + +// static +// Note, if you add more options here you also need to update: +// content/browser/devtools/devtools_tracing_handler:TraceOptionsFromString +TraceLog::Options TraceLog::TraceOptionsFromString(const std::string& options) { + std::vector split; + base::SplitString(options, ',', &split); + int ret = 0; + for (std::vector::iterator iter = split.begin(); + iter != split.end(); + ++iter) { + if (*iter == kRecordUntilFull) { + ret |= RECORD_UNTIL_FULL; + } else if (*iter == kRecordContinuously) { + ret |= RECORD_CONTINUOUSLY; + } else if (*iter == kEnableSampling) { + ret |= ENABLE_SAMPLING; + } else { + NOTREACHED(); // Unknown option provided. + } + } + if (!(ret & RECORD_UNTIL_FULL) && !(ret & RECORD_CONTINUOUSLY)) + ret |= RECORD_UNTIL_FULL; // Default when no options are specified. + + return static_cast(ret); +} + +TraceLog::TraceLog() + : enable_count_(0), + num_traces_recorded_(0), + event_callback_(NULL), + dispatching_to_observer_list_(false), + process_sort_index_(0), + watch_category_(NULL), + trace_options_(RECORD_UNTIL_FULL), + sampling_thread_handle_(0), + category_filter_(CategoryFilter::kDefaultCategoryFilterString) { + // 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_group_enabled, + // sizeof(g_category_group_enabled), + // "trace_event category enabled"); + for (int i = 0; i < MAX_CATEGORY_GROUPS; ++i) { + ANNOTATE_BENIGN_RACE(&g_category_group_enabled[i], + "trace_event category enabled"); + } +#if defined(OS_NACL) // NaCl shouldn't expose the process id. + SetProcessID(0); +#else + SetProcessID(static_cast(GetCurrentProcId())); + + // NaCl also shouldn't access the command line. + if (CommandLine::InitializedForCurrentProcess() && + CommandLine::ForCurrentProcess()->HasSwitch(switches::kTraceToConsole)) { + std::string category_string = + CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + switches::kTraceToConsole); + + if (category_string.empty()) + category_string = "*"; + + SetEnabled(CategoryFilter(category_string), ECHO_TO_CONSOLE); + } +#endif + + logged_events_.reset(GetTraceBuffer()); +} + +TraceLog::~TraceLog() { +} + +const unsigned char* TraceLog::GetCategoryGroupEnabled( + const char* category_group) { + TraceLog* tracelog = GetInstance(); + if (!tracelog) { + DCHECK(!g_category_group_enabled[g_category_already_shutdown]); + return &g_category_group_enabled[g_category_already_shutdown]; + } + return tracelog->GetCategoryGroupEnabledInternal(category_group); +} + +const char* TraceLog::GetCategoryGroupName( + const unsigned char* category_group_enabled) { + // Calculate the index of the category group by finding + // category_group_enabled in g_category_group_enabled array. + uintptr_t category_begin = + reinterpret_cast(g_category_group_enabled); + uintptr_t category_ptr = reinterpret_cast(category_group_enabled); + DCHECK(category_ptr >= category_begin && + category_ptr < reinterpret_cast( + g_category_group_enabled + MAX_CATEGORY_GROUPS)) << + "out of bounds category pointer"; + uintptr_t category_index = + (category_ptr - category_begin) / sizeof(g_category_group_enabled[0]); + return g_category_groups[category_index]; +} + +void TraceLog::UpdateCategoryGroupEnabledFlag(int category_index) { + bool is_enabled = enable_count_ && category_filter_.IsCategoryGroupEnabled( + g_category_groups[category_index]); + SetCategoryGroupEnabled(category_index, is_enabled); +} + +void TraceLog::UpdateCategoryGroupEnabledFlags() { + for (int i = 0; i < g_category_index; i++) + UpdateCategoryGroupEnabledFlag(i); +} + +void TraceLog::SetCategoryGroupEnabled(int category_index, bool is_enabled) { + g_category_group_enabled[category_index] = + is_enabled ? CATEGORY_GROUP_ENABLED : 0; + +#if defined(OS_ANDROID) + ApplyATraceEnabledFlag(&g_category_group_enabled[category_index]); +#endif +} + +bool TraceLog::IsCategoryGroupEnabled( + const unsigned char* category_group_enabled) { + // On Android, ATrace and normal trace can be enabled independently. + // This function checks if the normal trace is enabled. + return *category_group_enabled & CATEGORY_GROUP_ENABLED; +} + +const unsigned char* TraceLog::GetCategoryGroupEnabledInternal( + const char* category_group) { + DCHECK(!strchr(category_group, '"')) << + "Category groups may not contain double quote"; + AutoLock lock(lock_); + + unsigned char* category_group_enabled = NULL; + // Search for pre-existing category group. + for (int i = 0; i < g_category_index; i++) { + if (strcmp(g_category_groups[i], category_group) == 0) { + category_group_enabled = &g_category_group_enabled[i]; + break; + } + } + + if (!category_group_enabled) { + // Create a new category group + DCHECK(g_category_index < MAX_CATEGORY_GROUPS) << + "must increase MAX_CATEGORY_GROUPS"; + if (g_category_index < MAX_CATEGORY_GROUPS) { + int new_index = g_category_index++; + // Don't hold on to the category_group pointer, so that we can create + // category groups with strings not known at compile time (this is + // required by SetWatchEvent). + const char* new_group = strdup(category_group); + ANNOTATE_LEAKING_OBJECT_PTR(new_group); + g_category_groups[new_index] = new_group; + DCHECK(!g_category_group_enabled[new_index]); + // Note that if both included and excluded patterns in the + // CategoryFilter are empty, we exclude nothing, + // thereby enabling this category group. + UpdateCategoryGroupEnabledFlag(new_index); + category_group_enabled = &g_category_group_enabled[new_index]; + } else { + category_group_enabled = + &g_category_group_enabled[g_category_categories_exhausted]; + } + } + return category_group_enabled; +} + +void TraceLog::GetKnownCategoryGroups( + std::vector* category_groups) { + AutoLock lock(lock_); + for (int i = g_num_builtin_categories; i < g_category_index; i++) + category_groups->push_back(g_category_groups[i]); +} + +void TraceLog::SetEnabled(const CategoryFilter& category_filter, + Options options) { + std::vector observer_list; + { + AutoLock lock(lock_); + + if (enable_count_++ > 0) { + if (options != trace_options_) { + DLOG(ERROR) << "Attemting to re-enable tracing with a different " + << "set of options."; + } + + category_filter_.Merge(category_filter); + UpdateCategoryGroupEnabledFlags(); + return; + } + + if (options != trace_options_) { + trace_options_ = options; + logged_events_.reset(GetTraceBuffer()); + } + + if (dispatching_to_observer_list_) { + DLOG(ERROR) << + "Cannot manipulate TraceLog::Enabled state from an observer."; + return; + } + + num_traces_recorded_++; + + category_filter_ = CategoryFilter(category_filter); + UpdateCategoryGroupEnabledFlags(); + + if (options & ENABLE_SAMPLING) { + sampling_thread_.reset(new TraceSamplingThread); + sampling_thread_->RegisterSampleBucket( + &g_trace_state[0], + "bucket0", + Bind(&TraceSamplingThread::DefaultSampleCallback)); + sampling_thread_->RegisterSampleBucket( + &g_trace_state[1], + "bucket1", + Bind(&TraceSamplingThread::DefaultSampleCallback)); + sampling_thread_->RegisterSampleBucket( + &g_trace_state[2], + "bucket2", + Bind(&TraceSamplingThread::DefaultSampleCallback)); + if (!PlatformThread::Create( + 0, sampling_thread_.get(), &sampling_thread_handle_)) { + DCHECK(false) << "failed to create thread"; + } + } + + dispatching_to_observer_list_ = true; + observer_list = enabled_state_observer_list_; + } + // Notify observers outside the lock in case they trigger trace events. + for (size_t i = 0; i < observer_list.size(); ++i) + observer_list[i]->OnTraceLogEnabled(); + + { + AutoLock lock(lock_); + dispatching_to_observer_list_ = false; + } +} + +const CategoryFilter& TraceLog::GetCurrentCategoryFilter() { + AutoLock lock(lock_); + DCHECK(enable_count_ > 0); + return category_filter_; +} + +void TraceLog::SetDisabled() { + std::vector observer_list; + { + AutoLock lock(lock_); + DCHECK(enable_count_ > 0); + if (--enable_count_ != 0) + return; + + if (dispatching_to_observer_list_) { + DLOG(ERROR) + << "Cannot manipulate TraceLog::Enabled state from an observer."; + return; + } + + if (sampling_thread_.get()) { + // Stop the sampling thread. + sampling_thread_->Stop(); + lock_.Release(); + PlatformThread::Join(sampling_thread_handle_); + lock_.Acquire(); + sampling_thread_handle_ = PlatformThreadHandle(); + sampling_thread_.reset(); + } + + category_filter_.Clear(); + watch_category_ = NULL; + watch_event_name_ = ""; + UpdateCategoryGroupEnabledFlags(); + AddMetadataEvents(); + + dispatching_to_observer_list_ = true; + observer_list = enabled_state_observer_list_; + } + + // Dispatch to observers outside the lock in case the observer triggers a + // trace event. + for (size_t i = 0; i < observer_list.size(); ++i) + observer_list[i]->OnTraceLogDisabled(); + + { + AutoLock lock(lock_); + dispatching_to_observer_list_ = false; + } +} + +int TraceLog::GetNumTracesRecorded() { + AutoLock lock(lock_); + if (enable_count_ == 0) + return -1; + return num_traces_recorded_; +} + +void TraceLog::AddEnabledStateObserver(EnabledStateObserver* listener) { + enabled_state_observer_list_.push_back(listener); +} + +void TraceLog::RemoveEnabledStateObserver(EnabledStateObserver* listener) { + std::vector::iterator it = + std::find(enabled_state_observer_list_.begin(), + enabled_state_observer_list_.end(), + listener); + if (it != enabled_state_observer_list_.end()) + enabled_state_observer_list_.erase(it); +} + +bool TraceLog::HasEnabledStateObserver(EnabledStateObserver* listener) const { + std::vector::const_iterator it = + std::find(enabled_state_observer_list_.begin(), + enabled_state_observer_list_.end(), + listener); + return it != enabled_state_observer_list_.end(); +} + +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; +} + +TraceBuffer* TraceLog::GetTraceBuffer() { + if (trace_options_ & RECORD_CONTINUOUSLY) + return new TraceBufferRingBuffer(); + else if (trace_options_ & ECHO_TO_CONSOLE) + return new TraceBufferDiscardsEvents(); + return new TraceBufferVector(); +} + +void TraceLog::SetEventCallback(EventCallback cb) { + AutoLock lock(lock_); + event_callback_ = cb; +}; + +void TraceLog::Flush(const TraceLog::OutputCallback& cb) { + // Ignore memory allocations from here down. + INTERNAL_TRACE_MEMORY(TRACE_DISABLED_BY_DEFAULT("memory"), + TRACE_MEMORY_IGNORE); + scoped_ptr previous_logged_events; + { + AutoLock lock(lock_); + previous_logged_events.swap(logged_events_); + logged_events_.reset(GetTraceBuffer()); + } // release lock + + while (previous_logged_events->HasMoreEvents()) { + scoped_refptr json_events_str_ptr = + new RefCountedString(); + + for (size_t i = 0; i < kTraceEventBatchSize; ++i) { + if (i > 0) + *(&(json_events_str_ptr->data())) += ","; + + previous_logged_events->NextEvent().AppendAsJSON( + &(json_events_str_ptr->data())); + + if (!previous_logged_events->HasMoreEvents()) + break; + } + + cb.Run(json_events_str_ptr); + } +} + +void TraceLog::AddTraceEvent( + char phase, + const unsigned char* category_group_enabled, + const char* name, + unsigned long long id, + int num_args, + const char** arg_names, + const unsigned char* arg_types, + const unsigned long long* arg_values, + scoped_ptr convertable_values[], + unsigned char flags) { + int thread_id = static_cast(base::PlatformThread::CurrentId()); + base::TimeTicks now = base::TimeTicks::NowFromSystemTraceTime(); + AddTraceEventWithThreadIdAndTimestamp(phase, category_group_enabled, name, id, + thread_id, now, num_args, arg_names, + arg_types, arg_values, + convertable_values, flags); +} + +void TraceLog::AddTraceEventWithThreadIdAndTimestamp( + char phase, + const unsigned char* category_group_enabled, + const char* name, + unsigned long long id, + int thread_id, + const TimeTicks& timestamp, + int num_args, + const char** arg_names, + const unsigned char* arg_types, + const unsigned long long* arg_values, + scoped_ptr convertable_values[], + unsigned char flags) { + DCHECK(name); + + if (flags & TRACE_EVENT_FLAG_MANGLE_ID) + id ^= process_id_hash_; + +#if defined(OS_ANDROID) + SendToATrace(phase, GetCategoryGroupName(category_group_enabled), name, id, + num_args, arg_names, arg_types, arg_values, convertable_values, + flags); +#endif + + if (!IsCategoryGroupEnabled(category_group_enabled)) + return; + + TimeTicks now = timestamp - time_offset_; + EventCallback event_callback_copy; + + NotificationHelper notifier(this); + + // Check and update the current thread name only if the event is for the + // current thread to avoid locks in most cases. + if (thread_id == static_cast(PlatformThread::CurrentId())) { + const char* new_name = ThreadIdNameManager::GetInstance()-> + GetName(thread_id); + // 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); + + AutoLock lock(lock_); + hash_map::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 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); + } + } + } + } + + TraceEvent trace_event(thread_id, + now, phase, category_group_enabled, name, id, + num_args, arg_names, arg_types, arg_values, + convertable_values, flags); + + do { + AutoLock lock(lock_); + + event_callback_copy = event_callback_; + if (logged_events_->IsFull()) + break; + + logged_events_->AddEvent(trace_event); + + if (trace_options_ & ECHO_TO_CONSOLE) { + TimeDelta duration; + if (phase == TRACE_EVENT_PHASE_END) { + duration = timestamp - thread_event_start_times_[thread_id].top(); + thread_event_start_times_[thread_id].pop(); + } + + std::string thread_name = thread_names_[thread_id]; + if (thread_colors_.find(thread_name) == thread_colors_.end()) + thread_colors_[thread_name] = (thread_colors_.size() % 6) + 1; + + std::ostringstream log; + log << base::StringPrintf("%s: \x1b[0;3%dm", + thread_name.c_str(), + thread_colors_[thread_name]); + + size_t depth = 0; + if (thread_event_start_times_.find(thread_id) != + thread_event_start_times_.end()) + depth = thread_event_start_times_[thread_id].size(); + + for (size_t i = 0; i < depth; ++i) + log << "| "; + + trace_event.AppendPrettyPrinted(&log); + if (phase == TRACE_EVENT_PHASE_END) + log << base::StringPrintf(" (%.3f ms)", duration.InMillisecondsF()); + + LOG(ERROR) << log.str() << "\x1b[0;m"; + + if (phase == TRACE_EVENT_PHASE_BEGIN) + thread_event_start_times_[thread_id].push(timestamp); + } + + if (logged_events_->IsFull()) + notifier.AddNotificationWhileLocked(TRACE_BUFFER_FULL); + + if (watch_category_ == category_group_enabled && watch_event_name_ == name) + notifier.AddNotificationWhileLocked(EVENT_WATCH_NOTIFICATION); + } while (0); // release lock + + notifier.SendNotificationIfAny(); + if (event_callback_copy != NULL) { + event_callback_copy(phase, category_group_enabled, name, id, + num_args, arg_names, arg_types, arg_values, + flags); + } +} + +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 = GetCategoryGroupEnabled( + category_name.c_str()); + size_t 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. + notify_count = logged_events_->CountEnabledByName(category, event_name); + } // release lock + + // Send notification for each event found. + for (size_t i = 0; i < notify_count; ++i) { + NotificationHelper notifier(this); + lock_.Acquire(); + notifier.AddNotificationWhileLocked(EVENT_WATCH_NOTIFICATION); + lock_.Release(); + notifier.SendNotificationIfAny(); + } +} + +void TraceLog::CancelWatchEvent() { + AutoLock lock(lock_); + watch_category_ = NULL; + watch_event_name_ = ""; +} + +namespace { + +template +void AddMetadataEventToBuffer( + TraceBuffer* logged_events, + int thread_id, + const char* metadata_name, const char* arg_name, + const T& value) { + int num_args = 1; + unsigned char arg_type; + unsigned long long arg_value; + trace_event_internal::SetTraceValue(value, &arg_type, &arg_value); + logged_events->AddEvent(TraceEvent( + thread_id, + TimeTicks(), TRACE_EVENT_PHASE_METADATA, + &g_category_group_enabled[g_category_metadata], + metadata_name, trace_event_internal::kNoEventId, + num_args, &arg_name, &arg_type, &arg_value, NULL, + TRACE_EVENT_FLAG_NONE)); +} + +} + +void TraceLog::AddMetadataEvents() { + lock_.AssertAcquired(); + + int current_thread_id = static_cast(base::PlatformThread::CurrentId()); + if (process_sort_index_ != 0) { + AddMetadataEventToBuffer(logged_events_.get(), + current_thread_id, + "process_sort_index", "sort_index", + process_sort_index_); + } + + if (process_name_.size()) { + AddMetadataEventToBuffer(logged_events_.get(), + current_thread_id, + "process_name", "name", + process_name_); + } + + if (process_labels_.size() > 0) { + std::vector labels; + for(base::hash_map::iterator it = process_labels_.begin(); + it != process_labels_.end(); + it++) { + labels.push_back(it->second); + } + AddMetadataEventToBuffer(logged_events_.get(), + current_thread_id, + "process_labels", "labels", + JoinString(labels, ',')); + } + + // Thread sort indices. + for(hash_map::iterator it = thread_sort_indices_.begin(); + it != thread_sort_indices_.end(); + it++) { + if (it->second == 0) + continue; + AddMetadataEventToBuffer(logged_events_.get(), + it->first, + "thread_sort_index", "sort_index", + it->second); + } + + // Thread names. + for(hash_map::iterator it = thread_names_.begin(); + it != thread_names_.end(); + it++) { + if (it->second.empty()) + continue; + AddMetadataEventToBuffer(logged_events_.get(), + it->first, + "thread_name", "name", + it->second); + } +} + +void TraceLog::InstallWaitableEventForSamplingTesting( + WaitableEvent* waitable_event) { + sampling_thread_->InstallWaitableEventForSamplingTesting(waitable_event); +} + +void TraceLog::DeleteForTesting() { + DeleteTraceLogForTesting::Delete(); +} + +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(process_id_); + process_id_hash_ = (offset_basis ^ pid) * fnv_prime; +} + +void TraceLog::SetProcessSortIndex(int sort_index) { + AutoLock lock(lock_); + process_sort_index_ = sort_index; +} + +void TraceLog::SetProcessName(const std::string& process_name) { + AutoLock lock(lock_); + process_name_ = process_name; +} + +void TraceLog::UpdateProcessLabel( + int label_id, const std::string& current_label) { + if(!current_label.length()) + return RemoveProcessLabel(label_id); + + AutoLock lock(lock_); + process_labels_[label_id] = current_label; +} + +void TraceLog::RemoveProcessLabel(int label_id) { + AutoLock lock(lock_); + base::hash_map::iterator it = process_labels_.find( + label_id); + if (it == process_labels_.end()) + return; + + process_labels_.erase(it); +} + +void TraceLog::SetThreadSortIndex(PlatformThreadId thread_id, int sort_index) { + AutoLock lock(lock_); + thread_sort_indices_[static_cast(thread_id)] = sort_index; +} + +void TraceLog::SetTimeOffset(TimeDelta offset) { + time_offset_ = offset; +} + +size_t TraceLog::GetObserverCountForTest() const { + return enabled_state_observer_list_.size(); +} + +bool CategoryFilter::IsEmptyOrContainsLeadingOrTrailingWhitespace( + const std::string& str) { + return str.empty() || + str.at(0) == ' ' || + str.at(str.length() - 1) == ' '; +} + +bool CategoryFilter::DoesCategoryGroupContainCategory( + const char* category_group, + const char* category) const { + DCHECK(category); + CStringTokenizer category_group_tokens(category_group, + category_group + strlen(category_group), ","); + while (category_group_tokens.GetNext()) { + std::string category_group_token = category_group_tokens.token(); + // Don't allow empty tokens, nor tokens with leading or trailing space. + DCHECK(!CategoryFilter::IsEmptyOrContainsLeadingOrTrailingWhitespace( + category_group_token)) + << "Disallowed category string"; + if (MatchPattern(category_group_token.c_str(), category)) + return true; + } + return false; +} + +CategoryFilter::CategoryFilter(const std::string& filter_string) { + if (!filter_string.empty()) + Initialize(filter_string); + else + Initialize(CategoryFilter::kDefaultCategoryFilterString); +} + +CategoryFilter::CategoryFilter(const CategoryFilter& cf) + : included_(cf.included_), + disabled_(cf.disabled_), + excluded_(cf.excluded_) { +} + +CategoryFilter::~CategoryFilter() { +} + +CategoryFilter& CategoryFilter::operator=(const CategoryFilter& rhs) { + if (this == &rhs) + return *this; + + included_ = rhs.included_; + disabled_ = rhs.disabled_; + excluded_ = rhs.excluded_; + return *this; +} + +void CategoryFilter::Initialize(const std::string& filter_string) { + // Tokenize list of categories, delimited by ','. + StringTokenizer tokens(filter_string, ","); + // Add each token to the appropriate list (included_,excluded_). + while (tokens.GetNext()) { + std::string category = tokens.token(); + // Ignore empty categories. + if (category.empty()) + continue; + // Excluded categories start with '-'. + if (category.at(0) == '-') { + // Remove '-' from category string. + category = category.substr(1); + excluded_.push_back(category); + } else if (category.compare(0, strlen(TRACE_DISABLED_BY_DEFAULT("")), + TRACE_DISABLED_BY_DEFAULT("")) == 0) { + disabled_.push_back(category); + } else { + included_.push_back(category); + } + } +} + +void CategoryFilter::WriteString(const StringList& values, + std::string* out, + bool included) const { + bool prepend_comma = !out->empty(); + int token_cnt = 0; + for (StringList::const_iterator ci = values.begin(); + ci != values.end(); ++ci) { + if (token_cnt > 0 || prepend_comma) + StringAppendF(out, ","); + StringAppendF(out, "%s%s", (included ? "" : "-"), ci->c_str()); + ++token_cnt; + } +} + +std::string CategoryFilter::ToString() const { + std::string filter_string; + WriteString(included_, &filter_string, true); + WriteString(disabled_, &filter_string, true); + WriteString(excluded_, &filter_string, false); + return filter_string; +} + +bool CategoryFilter::IsCategoryGroupEnabled( + const char* category_group_name) const { + // TraceLog should call this method only as part of enabling/disabling + // categories. + StringList::const_iterator ci; + + // Check the disabled- filters and the disabled-* wildcard first so that a + // "*" filter does not include the disabled. + for (ci = disabled_.begin(); ci != disabled_.end(); ++ci) { + if (DoesCategoryGroupContainCategory(category_group_name, ci->c_str())) + return true; + } + if (DoesCategoryGroupContainCategory(category_group_name, + TRACE_DISABLED_BY_DEFAULT("*"))) + return false; + + for (ci = included_.begin(); ci != included_.end(); ++ci) { + if (DoesCategoryGroupContainCategory(category_group_name, ci->c_str())) + return true; + } + + for (ci = excluded_.begin(); ci != excluded_.end(); ++ci) { + if (DoesCategoryGroupContainCategory(category_group_name, ci->c_str())) + return false; + } + // If the category group is not excluded, and there are no included patterns + // we consider this pattern enabled. + return included_.empty(); +} + +bool CategoryFilter::HasIncludedPatterns() const { + return !included_.empty(); +} + +void CategoryFilter::Merge(const CategoryFilter& nested_filter) { + // Keep included patterns only if both filters have an included entry. + // Otherwise, one of the filter was specifying "*" and we want to honour the + // broadest filter. + if (HasIncludedPatterns() && nested_filter.HasIncludedPatterns()) { + included_.insert(included_.end(), + nested_filter.included_.begin(), + nested_filter.included_.end()); + } else { + included_.clear(); + } + + disabled_.insert(disabled_.end(), + nested_filter.disabled_.begin(), + nested_filter.disabled_.end()); + excluded_.insert(excluded_.end(), + nested_filter.excluded_.begin(), + nested_filter.excluded_.end()); +} + +void CategoryFilter::Clear() { + included_.clear(); + disabled_.clear(); + excluded_.clear(); +} + +} // namespace debug +} // namespace base + +namespace trace_event_internal { + +ScopedTrace::ScopedTrace( + TRACE_EVENT_API_ATOMIC_WORD* event_uid, const char* name) { + category_group_enabled_ = + reinterpret_cast(TRACE_EVENT_API_ATOMIC_LOAD( + *event_uid)); + if (!category_group_enabled_) { + category_group_enabled_ = TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED("gpu"); + TRACE_EVENT_API_ATOMIC_STORE( + *event_uid, + reinterpret_cast(category_group_enabled_)); + } + if (*category_group_enabled_) { + name_ = name; + TRACE_EVENT_API_ADD_TRACE_EVENT( + TRACE_EVENT_PHASE_BEGIN, // phase + category_group_enabled_, // category enabled + name, // name + 0, // id + 0, // num_args + NULL, // arg_names + NULL, // arg_types + NULL, // arg_values + NULL, // convertable_values + TRACE_EVENT_FLAG_NONE); // flags + } else { + category_group_enabled_ = NULL; + } +} + +ScopedTrace::~ScopedTrace() { + if (category_group_enabled_ && *category_group_enabled_) { + TRACE_EVENT_API_ADD_TRACE_EVENT( + TRACE_EVENT_PHASE_END, // phase + category_group_enabled_, // category enabled + name_, // name + 0, // id + 0, // num_args + NULL, // arg_names + NULL, // arg_types + NULL, // arg_values + NULL, // convertable values + TRACE_EVENT_FLAG_NONE); // flags + } +} + +} // namespace trace_event_internal diff --git a/base/debug/trace_event_impl.h b/base/debug/trace_event_impl.h new file mode 100644 index 0000000000..be1a104937 --- /dev/null +++ b/base/debug/trace_event_impl.h @@ -0,0 +1,595 @@ +// Copyright (c) 2012 The Chromium Authors. All 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 +#include +#include + +#include "base/callback.h" +#include "base/containers/hash_tables.h" +#include "base/gtest_prod_util.h" +#include "base/memory/ref_counted_memory.h" +#include "base/memory/scoped_vector.h" +#include "base/observer_list.h" +#include "base/strings/string_util.h" +#include "base/synchronization/condition_variable.h" +#include "base/synchronization/lock.h" +#include "base/threading/thread.h" +#include "base/timer/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(id), extra) + +#define TRACE_EVENT_END_ETW(name, id, extra) \ + base::debug::TraceLog::AddTraceEventEtw( \ + TRACE_EVENT_PHASE_END, \ + name, reinterpret_cast(id), extra) + +#define TRACE_EVENT_INSTANT_ETW(name, id, extra) \ + base::debug::TraceLog::AddTraceEventEtw( \ + TRACE_EVENT_PHASE_INSTANT, \ + name, reinterpret_cast(id), extra) + +template +struct DefaultSingletonTraits; + +namespace base { + +class WaitableEvent; + +namespace debug { + +// For any argument of type TRACE_VALUE_TYPE_CONVERTABLE the provided +// class must implement this interface. +class ConvertableToTraceFormat { + public: + virtual ~ConvertableToTraceFormat() {} + + // Append the class info to the provided |out| string. The appended + // data must be a valid JSON object. Strings must be properly quoted, and + // escaped. There is no processing applied to the content after it is + // appended. + virtual void AppendAsTraceFormat(std::string* out) const = 0; +}; + +const int kTraceMaxNumArgs = 2; + +// Output records are "Events" and can be obtained via the +// OutputCallback whenever the tracing system decides to flush. This +// can happen at any time, on any thread, or you can programmatically +// force it to happen. +class BASE_EXPORT TraceEvent { + public: + union TraceValue { + 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_group_enabled, + const char* name, + unsigned long long id, + int num_args, + const char** arg_names, + const unsigned char* arg_types, + const unsigned long long* arg_values, + scoped_ptr convertable_values[], + unsigned char flags); + TraceEvent(const TraceEvent& other); + TraceEvent& operator=(const TraceEvent& other); + ~TraceEvent(); + + // Serialize event data to JSON + static void AppendEventsAsJSON(const std::vector& events, + size_t start, + size_t count, + std::string* out); + void AppendAsJSON(std::string* out) const; + void AppendPrettyPrinted(std::ostringstream* 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_group_enabled() const { + return category_group_enabled_; + } + + const char* name() const { return name_; } + + 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]; + scoped_ptr convertable_values_[kTraceMaxNumArgs]; + const unsigned char* category_group_enabled_; + const char* name_; + scoped_refptr parameter_copy_storage_; + int thread_id_; + char phase_; + unsigned char flags_; + unsigned char arg_types_[kTraceMaxNumArgs]; +}; + +// TraceBuffer holds the events as they are collected. +class BASE_EXPORT TraceBuffer { + public: + virtual ~TraceBuffer() {} + + virtual void AddEvent(const TraceEvent& event) = 0; + virtual bool HasMoreEvents() const = 0; + virtual const TraceEvent& NextEvent() = 0; + virtual bool IsFull() const = 0; + virtual size_t CountEnabledByName(const unsigned char* category, + const std::string& event_name) const = 0; + virtual size_t Size() const = 0; + virtual const TraceEvent& GetEventAt(size_t index) const = 0; +}; + +// TraceResultBuffer collects and converts trace fragments returned by TraceLog +// to JSON output. +class BASE_EXPORT TraceResultBuffer { + public: + typedef base::Callback 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 CategoryFilter { + public: + // The default category filter, used when none is provided. + // Allows all categories through, except if they end in the suffix 'Debug' or + // 'Test'. + static const char* kDefaultCategoryFilterString; + + // |filter_string| 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: CategoryFilter"test_MyTest*"); + // Example: CategoryFilter("test_MyTest*,test_OtherStuff"); + // Example: CategoryFilter("-excluded_category1,-excluded_category2"); + // Example: CategoryFilter("-*,webkit"); would disable everything but webkit. + // Example: CategoryFilter("-webkit"); would enable everything but webkit. + explicit CategoryFilter(const std::string& filter_string); + + CategoryFilter(const CategoryFilter& cf); + + ~CategoryFilter(); + + CategoryFilter& operator=(const CategoryFilter& rhs); + + // Writes the string representation of the CategoryFilter. This is a comma + // separated string, similar in nature to the one used to determine + // enabled/disabled category patterns, except here there is an arbitrary + // order, included categories go first, then excluded categories. Excluded + // categories are distinguished from included categories by the prefix '-'. + std::string ToString() const; + + // Determines whether category group would be enabled or + // disabled by this category filter. + bool IsCategoryGroupEnabled(const char* category_group) const; + + // Merges nested_filter with the current CategoryFilter + void Merge(const CategoryFilter& nested_filter); + + // Clears both included/excluded pattern lists. This would be equivalent to + // creating a CategoryFilter with an empty string, through the constructor. + // i.e: CategoryFilter(""). + // + // When using an empty filter, all categories are considered included as we + // are not excluding anything. + void Clear(); + + private: + FRIEND_TEST_ALL_PREFIXES(TraceEventTestFixture, CategoryFilter); + + static bool IsEmptyOrContainsLeadingOrTrailingWhitespace( + const std::string& str); + + typedef std::vector StringList; + + void Initialize(const std::string& filter_string); + void WriteString(const StringList& values, + std::string* out, + bool included) const; + bool HasIncludedPatterns() const; + + bool DoesCategoryGroupContainCategory(const char* category_group, + const char* category) const; + + StringList included_; + StringList disabled_; + StringList excluded_; +}; + +class TraceSamplingThread; + +class BASE_EXPORT TraceLog { + public: + // Notification is a mask of one or more of the following events. + enum Notification { + // The trace buffer does not flush dynamically, so when it fills up, + // subsequent trace events will be dropped. This callback is generated when + // the trace buffer is full. The callback must be thread safe. + TRACE_BUFFER_FULL = 1 << 0, + // A subscribed trace-event occurred. + EVENT_WATCH_NOTIFICATION = 1 << 1 + }; + + // Options determines how the trace buffer stores data. + enum Options { + // Record until the trace buffer is full. + RECORD_UNTIL_FULL = 1 << 0, + + // Record until the user ends the trace. The trace buffer is a fixed size + // and we use it as a ring buffer during recording. + RECORD_CONTINUOUSLY = 1 << 1, + + // Enable the sampling profiler. + ENABLE_SAMPLING = 1 << 2, + + // Echo to console. Events are discarded. + ECHO_TO_CONSOLE = 1 << 3 + }; + + static TraceLog* GetInstance(); + + // Convert the given string to trace options. Defaults to RECORD_UNTIL_FULL if + // the string does not provide valid options. + static Options TraceOptionsFromString(const std::string& str); + + // Get set of known category groups. This can change as new code paths are + // reached. The known category groups are inserted into |category_groups|. + void GetKnownCategoryGroups(std::vector* category_groups); + + // Retrieves the current CategoryFilter. + const CategoryFilter& GetCurrentCategoryFilter(); + + Options trace_options() const { return trace_options_; } + + // Enables tracing. See CategoryFilter comments for details + // on how to control what categories will be traced. + void SetEnabled(const CategoryFilter& category_filter, Options options); + + // Disable tracing for all categories. + void SetDisabled(); + bool IsEnabled() { return !!enable_count_; } + + // The number of times we have begun recording traces. If tracing is off, + // returns -1. If tracing is on, then it returns the number of times we have + // recorded a trace. By watching for this number to increment, you can + // passively discover when a new trace has begun. This is then used to + // implement the TRACE_EVENT_IS_NEW_TRACE() primitive. + int GetNumTracesRecorded(); + +#if defined(OS_ANDROID) + void StartATrace(); + void StopATrace(); +#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 EnabledStateObserver { + public: + // Called just after the tracing system becomes enabled, outside of the + // |lock_|. TraceLog::IsEnabled() is true at this point. + virtual void OnTraceLogEnabled() = 0; + + // Called just after the tracing system disables, outside of the |lock_|. + // TraceLog::IsEnabled() is false at this point. + virtual void OnTraceLogDisabled() = 0; + }; + void AddEnabledStateObserver(EnabledStateObserver* listener); + void RemoveEnabledStateObserver(EnabledStateObserver* listener); + bool HasEnabledStateObserver(EnabledStateObserver* listener) const; + + float GetBufferPercentFull() const; + + // Set the thread-safe notification callback. The callback can occur at any + // time and from any thread. WARNING: It is possible for the previously set + // callback to be called during OR AFTER a call to SetNotificationCallback. + // Therefore, the target of the callback must either be a global function, + // ref-counted object or a LazyInstance with Leaky traits (or equivalent). + typedef base::Callback NotificationCallback; + void SetNotificationCallback(const NotificationCallback& cb); + + // Not using base::Callback because of its limited by 7 parameters. + // Also, using primitive type allows directly passing callback from WebCore. + // WARNING: It is possible for the previously set callback to be called + // after a call to SetEventCallback() that replaces or clears the callback. + // This callback may be invoked on any thread. + typedef void (*EventCallback)(char phase, + const unsigned char* category_group_enabled, + const char* name, + unsigned long long id, + int num_args, + const char* const arg_names[], + const unsigned char arg_types[], + const unsigned long long arg_values[], + unsigned char flags); + void SetEventCallback(EventCallback 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&)> + OutputCallback; + void Flush(const OutputCallback& cb); + + // Called by TRACE_EVENT* macros, don't call this directly. + // The name parameter is a category group for example: + // TRACE_EVENT0("renderer,webkit", "WebViewImpl::HandleInputEvent") + static const unsigned char* GetCategoryGroupEnabled(const char* name); + static const char* GetCategoryGroupName( + const unsigned char* category_group_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_group_enabled, + const char* name, + unsigned long long id, + int num_args, + const char** arg_names, + const unsigned char* arg_types, + const unsigned long long* arg_values, + scoped_ptr convertable_values[], + unsigned char flags); + void AddTraceEventWithThreadIdAndTimestamp( + char phase, + const unsigned char* category_group_enabled, + const char* name, + unsigned long long id, + int thread_id, + const TimeTicks& timestamp, + int num_args, + const char** arg_names, + const unsigned char* arg_types, + const unsigned long long* arg_values, + scoped_ptr convertable_values[], + unsigned char flags); + static void AddTraceEventEtw(char phase, + const char* category_group, + const void* id, + const char* extra); + static void AddTraceEventEtw(char phase, + const char* category_group, + 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: + + void InstallWaitableEventForSamplingTesting(WaitableEvent* waitable_event); + + // Allows deleting our singleton instance. + static void DeleteForTesting(); + + // Allow tests to inspect TraceEvents. + size_t GetEventsSize() const { return logged_events_->Size(); } + const TraceEvent& GetEventAt(size_t index) const { + return logged_events_->GetEventAt(index); + } + + void SetProcessID(int process_id); + + // Process sort indices, if set, override the order of a process will appear + // relative to other processes in the trace viewer. Processes are sorted first + // on their sort index, ascending, then by their name, and then tid. + void SetProcessSortIndex(int sort_index); + + // Sets the name of the process. + void SetProcessName(const std::string& process_name); + + // Processes can have labels in addition to their names. Use labels, for + // instance, to list out the web page titles that a process is handling. + void UpdateProcessLabel(int label_id, const std::string& current_label); + void RemoveProcessLabel(int label_id); + + // Thread sort indices, if set, override the order of a thread will appear + // within its process in the trace viewer. Threads are sorted first on their + // sort index, ascending, then by their name, and then tid. + void SetThreadSortIndex(PlatformThreadId , int sort_index); + + // Allow setting an offset between the current TimeTicks time and the time + // that should be reported. + void SetTimeOffset(TimeDelta offset); + + size_t GetObserverCountForTest() const; + + private: + // This allows constructor and destructor to be private and usable only + // by the Singleton class. + friend struct DefaultSingletonTraits; + + // Enable/disable each category group based on the current enable_count_ + // and category_filter_. Disable the category group if enabled_count_ is 0, or + // if the category group contains a category that matches an included category + // pattern, that category group will be enabled. + // On Android, ATRACE_ENABLED flag will be applied if atrace is started. + void UpdateCategoryGroupEnabledFlags(); + void UpdateCategoryGroupEnabledFlag(int category_index); + + static void SetCategoryGroupEnabled(int category_index, bool enabled); + static bool IsCategoryGroupEnabled( + const unsigned char* category_group_enabled); + + // The pointer returned from GetCategoryGroupEnabledInternal() points to a + // value with zero or more of the following bits. Used in this class only. + // The TRACE_EVENT macros should only use the value as a bool. + enum CategoryGroupEnabledFlags { + // Normal enabled flag for category groups enabled with Enable(). + CATEGORY_GROUP_ENABLED = 1 << 0, + // On Android if ATrace is enabled, all categories will have this bit. + // Not used on other platforms. + ATRACE_ENABLED = 1 << 1 + }; + + // Helper class for managing notification_thread_count_ and running + // notification callbacks. This is very similar to a reader-writer lock, but + // shares the lock with TraceLog and manages the notification flags. + class NotificationHelper { + public: + inline explicit NotificationHelper(TraceLog* trace_log); + inline ~NotificationHelper(); + + // Called only while TraceLog::lock_ is held. This ORs the given + // notification with any existing notifications. + inline void AddNotificationWhileLocked(int notification); + + // Called only while TraceLog::lock_ is NOT held. If there are any pending + // notifications from previous calls to AddNotificationWhileLocked, this + // will call the NotificationCallback. + inline void SendNotificationIfAny(); + + private: + TraceLog* trace_log_; + NotificationCallback callback_copy_; + int notification_; + }; + + TraceLog(); + ~TraceLog(); + const unsigned char* GetCategoryGroupEnabledInternal(const char* name); + void AddMetadataEvents(); + +#if defined(OS_ANDROID) + void SendToATrace(char phase, + const char* category_group, + const char* name, + unsigned long long id, + int num_args, + const char** arg_names, + const unsigned char* arg_types, + const unsigned long long* arg_values, + scoped_ptr convertable_values[], + unsigned char flags); + static void ApplyATraceEnabledFlag(unsigned char* category_group_enabled); +#endif + + TraceBuffer* GetTraceBuffer(); + + // TODO(nduca): switch to per-thread trace buffers to reduce thread + // synchronization. + // This lock protects TraceLog member accesses from arbitrary threads. + Lock lock_; + int enable_count_; + int num_traces_recorded_; + NotificationCallback notification_callback_; + scoped_ptr logged_events_; + EventCallback event_callback_; + bool dispatching_to_observer_list_; + std::vector enabled_state_observer_list_; + + std::string process_name_; + base::hash_map process_labels_; + int process_sort_index_; + base::hash_map thread_sort_indices_; + + base::hash_map thread_names_; + base::hash_map > thread_event_start_times_; + base::hash_map thread_colors_; + + // 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_; + + Options trace_options_; + + // Sampling thread handles. + scoped_ptr sampling_thread_; + PlatformThreadHandle sampling_thread_handle_; + + CategoryFilter category_filter_; + + DISALLOW_COPY_AND_ASSIGN(TraceLog); +}; + +} // namespace debug +} // namespace base + +#endif // BASE_DEBUG_TRACE_EVENT_IMPL_H_ diff --git a/base/debug/trace_event_impl_constants.cc b/base/debug/trace_event_impl_constants.cc new file mode 100644 index 0000000000..71e9e08db7 --- /dev/null +++ b/base/debug/trace_event_impl_constants.cc @@ -0,0 +1,14 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/debug/trace_event_impl.h" + +namespace base { +namespace debug { + +// Enable everything but debug and test categories by default. +const char* CategoryFilter::kDefaultCategoryFilterString = "-*Debug,-*Test"; + +} // namespace debug +} // namespace base diff --git a/base/debug/trace_event_memory.cc b/base/debug/trace_event_memory.cc new file mode 100644 index 0000000000..fb5f65bbc1 --- /dev/null +++ b/base/debug/trace_event_memory.cc @@ -0,0 +1,414 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/debug/trace_event_memory.h" + +#include "base/debug/leak_annotations.h" +#include "base/debug/trace_event.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/threading/thread_local_storage.h" + +namespace base { +namespace debug { + +namespace { + +// Maximum number of nested TRACE_MEMORY scopes to record. Must be greater than +// or equal to HeapProfileTable::kMaxStackDepth. +const size_t kMaxStackSize = 32; + +///////////////////////////////////////////////////////////////////////////// +// Holds a memory dump until the tracing system needs to serialize it. +class MemoryDumpHolder : public base::debug::ConvertableToTraceFormat { + public: + // Takes ownership of dump, which must be a JSON string, allocated with + // malloc() and NULL terminated. + explicit MemoryDumpHolder(char* dump) : dump_(dump) {} + virtual ~MemoryDumpHolder() { free(dump_); } + + // base::debug::ConvertableToTraceFormat overrides: + virtual void AppendAsTraceFormat(std::string* out) const OVERRIDE { + AppendHeapProfileAsTraceFormat(dump_, out); + } + + private: + char* dump_; + + DISALLOW_COPY_AND_ASSIGN(MemoryDumpHolder); +}; + +///////////////////////////////////////////////////////////////////////////// +// Records a stack of TRACE_MEMORY events. One per thread is required. +struct TraceMemoryStack { + TraceMemoryStack() : index_(0) { + memset(category_stack_, 0, kMaxStackSize * sizeof(category_stack_[0])); + } + + // Points to the next free entry. + size_t index_; + const char* category_stack_[kMaxStackSize]; +}; + +// Pointer to a TraceMemoryStack per thread. +base::ThreadLocalStorage::StaticSlot tls_trace_memory_stack = TLS_INITIALIZER; + +// Clean up memory pointed to by our thread-local storage. +void DeleteStackOnThreadCleanup(void* value) { + TraceMemoryStack* stack = static_cast(value); + delete stack; +} + +// Initializes the thread-local TraceMemoryStack pointer. Returns true on +// success or if it is already initialized. +bool InitThreadLocalStorage() { + if (tls_trace_memory_stack.initialized()) + return true; + // Initialize the thread-local storage key, returning true on success. + return tls_trace_memory_stack.Initialize(&DeleteStackOnThreadCleanup); +} + +// Clean up thread-local-storage in the main thread. +void CleanupThreadLocalStorage() { + if (!tls_trace_memory_stack.initialized()) + return; + TraceMemoryStack* stack = + static_cast(tls_trace_memory_stack.Get()); + delete stack; + tls_trace_memory_stack.Set(NULL); + // Intentionally do not release the thread-local-storage key here, that is, + // do not call tls_trace_memory_stack.Free(). Other threads have lazily + // created pointers in thread-local-storage via GetTraceMemoryStack() below. + // Those threads need to run the DeleteStack() destructor function when they + // exit. If we release the key the destructor will not be called and those + // threads will not clean up their memory. +} + +// Returns the thread-local trace memory stack for the current thread, creating +// one if needed. Returns NULL if the thread-local storage key isn't +// initialized, which indicates that heap profiling isn't running. +TraceMemoryStack* GetTraceMemoryStack() { + TraceMemoryStack* stack = + static_cast(tls_trace_memory_stack.Get()); + // Lazily initialize TraceMemoryStack objects for new threads. + if (!stack) { + stack = new TraceMemoryStack; + tls_trace_memory_stack.Set(stack); + } + return stack; +} + +// Returns a "pseudo-stack" of pointers to trace events. +// TODO(jamescook): Record both category and name, perhaps in a pair for speed. +int GetPseudoStack(int skip_count_ignored, void** stack_out) { + // If the tracing system isn't fully initialized, just skip this allocation. + // Attempting to initialize will allocate memory, causing this function to + // be called recursively from inside the allocator. + if (!tls_trace_memory_stack.initialized() || !tls_trace_memory_stack.Get()) + return 0; + TraceMemoryStack* stack = + static_cast(tls_trace_memory_stack.Get()); + // Copy at most kMaxStackSize stack entries. + const size_t count = std::min(stack->index_, kMaxStackSize); + // Notes that memcpy() works for zero bytes. + memcpy(stack_out, + stack->category_stack_, + count * sizeof(stack->category_stack_[0])); + // Function must return an int to match the signature required by tcmalloc. + return static_cast(count); +} + +} // namespace + +////////////////////////////////////////////////////////////////////////////// + +TraceMemoryController::TraceMemoryController( + scoped_refptr message_loop_proxy, + HeapProfilerStartFunction heap_profiler_start_function, + HeapProfilerStopFunction heap_profiler_stop_function, + GetHeapProfileFunction get_heap_profile_function) + : message_loop_proxy_(message_loop_proxy), + heap_profiler_start_function_(heap_profiler_start_function), + heap_profiler_stop_function_(heap_profiler_stop_function), + get_heap_profile_function_(get_heap_profile_function), + weak_factory_(this) { + // Force the "memory" category to show up in the trace viewer. + TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("memory"), "init"); + // Watch for the tracing system being enabled. + TraceLog::GetInstance()->AddEnabledStateObserver(this); +} + +TraceMemoryController::~TraceMemoryController() { + if (dump_timer_.IsRunning()) + StopProfiling(); + TraceLog::GetInstance()->RemoveEnabledStateObserver(this); +} + + // base::debug::TraceLog::EnabledStateChangedObserver overrides: +void TraceMemoryController::OnTraceLogEnabled() { + // Check to see if tracing is enabled for the memory category. + bool enabled; + TRACE_EVENT_CATEGORY_GROUP_ENABLED(TRACE_DISABLED_BY_DEFAULT("memory"), + &enabled); + if (!enabled) + return; + DVLOG(1) << "OnTraceLogEnabled"; + message_loop_proxy_->PostTask( + FROM_HERE, + base::Bind(&TraceMemoryController::StartProfiling, + weak_factory_.GetWeakPtr())); +} + +void TraceMemoryController::OnTraceLogDisabled() { + // The memory category is always disabled before OnTraceLogDisabled() is + // called, so we cannot tell if it was enabled before. Always try to turn + // off profiling. + DVLOG(1) << "OnTraceLogDisabled"; + message_loop_proxy_->PostTask( + FROM_HERE, + base::Bind(&TraceMemoryController::StopProfiling, + weak_factory_.GetWeakPtr())); +} + +void TraceMemoryController::StartProfiling() { + // Watch for the tracing framework sending enabling more than once. + if (dump_timer_.IsRunning()) + return; + DVLOG(1) << "Starting trace memory"; + if (!InitThreadLocalStorage()) + return; + ScopedTraceMemory::set_enabled(true); + // Call ::HeapProfilerWithPseudoStackStart(). + heap_profiler_start_function_(&GetPseudoStack); + const int kDumpIntervalSeconds = 5; + dump_timer_.Start(FROM_HERE, + TimeDelta::FromSeconds(kDumpIntervalSeconds), + base::Bind(&TraceMemoryController::DumpMemoryProfile, + weak_factory_.GetWeakPtr())); +} + +void TraceMemoryController::DumpMemoryProfile() { + // Don't trace allocations here in the memory tracing system. + INTERNAL_TRACE_MEMORY(TRACE_DISABLED_BY_DEFAULT("memory"), + TRACE_MEMORY_IGNORE); + + DVLOG(1) << "DumpMemoryProfile"; + // MemoryDumpHolder takes ownership of this string. See GetHeapProfile() in + // tcmalloc for details. + char* dump = get_heap_profile_function_(); + scoped_ptr dump_holder(new MemoryDumpHolder(dump)); + const int kSnapshotId = 1; + TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID( + TRACE_DISABLED_BY_DEFAULT("memory"), + "memory::Heap", + kSnapshotId, + dump_holder.PassAs()); +} + +void TraceMemoryController::StopProfiling() { + // Watch for the tracing framework sending disabled more than once. + if (!dump_timer_.IsRunning()) + return; + DVLOG(1) << "Stopping trace memory"; + dump_timer_.Stop(); + ScopedTraceMemory::set_enabled(false); + CleanupThreadLocalStorage(); + // Call ::HeapProfilerStop(). + heap_profiler_stop_function_(); +} + +bool TraceMemoryController::IsTimerRunningForTest() const { + return dump_timer_.IsRunning(); +} + +///////////////////////////////////////////////////////////////////////////// + +// static +bool ScopedTraceMemory::enabled_ = false; + +ScopedTraceMemory::ScopedTraceMemory(const char* category) { + // Not enabled indicates that the trace system isn't running, so don't + // record anything. + if (!enabled_) + return; + // Get our thread's copy of the stack. + TraceMemoryStack* trace_memory_stack = GetTraceMemoryStack(); + const size_t index = trace_memory_stack->index_; + // Allow deep nesting of stacks (needed for tests), but only record + // |kMaxStackSize| entries. + if (index < kMaxStackSize) + trace_memory_stack->category_stack_[index] = category; + trace_memory_stack->index_++; +} + +ScopedTraceMemory::~ScopedTraceMemory() { + // Not enabled indicates that the trace system isn't running, so don't + // record anything. + if (!enabled_) + return; + // Get our thread's copy of the stack. + TraceMemoryStack* trace_memory_stack = GetTraceMemoryStack(); + // The tracing system can be turned on with ScopedTraceMemory objects + // allocated on the stack, so avoid potential underflow as they are destroyed. + if (trace_memory_stack->index_ > 0) + trace_memory_stack->index_--; +} + +// static +void ScopedTraceMemory::InitForTest() { + InitThreadLocalStorage(); + enabled_ = true; +} + +// static +void ScopedTraceMemory::CleanupForTest() { + enabled_ = false; + CleanupThreadLocalStorage(); +} + +// static +int ScopedTraceMemory::GetStackIndexForTest() { + TraceMemoryStack* stack = GetTraceMemoryStack(); + return static_cast(stack->index_); +} + +// static +const char* ScopedTraceMemory::GetItemForTest(int index) { + TraceMemoryStack* stack = GetTraceMemoryStack(); + return stack->category_stack_[index]; +} + +///////////////////////////////////////////////////////////////////////////// + +void AppendHeapProfileAsTraceFormat(const char* input, std::string* output) { + // Heap profile output has a header total line, then a list of stacks with + // memory totals, like this: + // + // heap profile: 357: 55227 [ 14653: 2624014] @ heapprofile + // 95: 40940 [ 649: 114260] @ 0x7fa7f4b3be13 + // 77: 32546 [ 742: 106234] @ + // 68: 4195 [ 1087: 98009] @ 0x7fa7fa9b9ba0 0x7fa7f4b3be13 + // + // MAPPED_LIBRARIES: + // 1be411fc1000-1be4139e4000 rw-p 00000000 00:00 0 + // 1be4139e4000-1be4139e5000 ---p 00000000 00:00 0 + // ... + // + // Skip input after MAPPED_LIBRARIES. + std::string input_string; + const char* mapped_libraries = strstr(input, "MAPPED_LIBRARIES"); + if (mapped_libraries) { + input_string.assign(input, mapped_libraries - input); + } else { + input_string.assign(input); + } + + std::vector lines; + size_t line_count = Tokenize(input_string, "\n", &lines); + if (line_count == 0) { + DLOG(WARNING) << "No lines found"; + return; + } + + // Handle the initial summary line. + output->append("["); + AppendHeapProfileTotalsAsTraceFormat(lines[0], output); + + // Handle the following stack trace lines. + for (size_t i = 1; i < line_count; ++i) { + const std::string& line = lines[i]; + AppendHeapProfileLineAsTraceFormat(line, output); + } + output->append("]\n"); +} + +void AppendHeapProfileTotalsAsTraceFormat(const std::string& line, + std::string* output) { + // This is what a line looks like: + // heap profile: 357: 55227 [ 14653: 2624014] @ heapprofile + // + // The numbers represent total allocations since profiling was enabled. + // From the example above: + // 357 = Outstanding allocations (mallocs - frees) + // 55227 = Outstanding bytes (malloc bytes - free bytes) + // 14653 = Total allocations (mallocs) + // 2624014 = Total bytes (malloc bytes) + std::vector tokens; + Tokenize(line, " :[]@", &tokens); + if (tokens.size() < 4) { + DLOG(WARNING) << "Invalid totals line " << line; + return; + } + DCHECK_EQ(tokens[0], "heap"); + DCHECK_EQ(tokens[1], "profile"); + output->append("{\"current_allocs\": "); + output->append(tokens[2]); + output->append(", \"current_bytes\": "); + output->append(tokens[3]); + output->append(", \"trace\": \"\"}"); +} + +bool AppendHeapProfileLineAsTraceFormat(const std::string& line, + std::string* output) { + // This is what a line looks like: + // 68: 4195 [ 1087: 98009] @ 0x7fa7fa9b9ba0 0x7fa7f4b3be13 + // + // The numbers represent allocations for a particular stack trace since + // profiling was enabled. From the example above: + // 68 = Outstanding allocations (mallocs - frees) + // 4195 = Outstanding bytes (malloc bytes - free bytes) + // 1087 = Total allocations (mallocs) + // 98009 = Total bytes (malloc bytes) + // + // 0x7fa7fa9b9ba0 0x7fa7f4b3be13 = Stack trace represented as pointers to + // static strings from trace event names. + std::vector tokens; + Tokenize(line, " :[]@", &tokens); + // It's valid to have no stack addresses, so only require 4 tokens. + if (tokens.size() < 4) { + DLOG(WARNING) << "Invalid line " << line; + return false; + } + // Don't bother with stacks that have no current allocations. + if (tokens[0] == "0") + return false; + output->append(",\n"); + output->append("{\"current_allocs\": "); + output->append(tokens[0]); + output->append(", \"current_bytes\": "); + output->append(tokens[1]); + output->append(", \"trace\": \""); + + // Convert the "stack addresses" into strings. + const std::string kSingleQuote = "'"; + for (size_t t = 4; t < tokens.size(); ++t) { + // Each stack address is a pointer to a constant trace name string. + uint64 address = 0; + if (!base::HexStringToUInt64(tokens[t], &address)) + break; + // This is ugly but otherwise tcmalloc would need to gain a special output + // serializer for pseudo-stacks. Note that this cast also handles 64-bit to + // 32-bit conversion if necessary. Tests use a null address. + const char* trace_name = + address ? reinterpret_cast(address) : "null"; + + // Some trace name strings have double quotes, convert them to single. + std::string trace_name_string(trace_name); + ReplaceChars(trace_name_string, "\"", kSingleQuote, &trace_name_string); + + output->append(trace_name_string); + + // Trace viewer expects a trailing space. + output->append(" "); + } + output->append("\"}"); + return true; +} + +} // namespace debug +} // namespace base diff --git a/base/debug/trace_event_memory.h b/base/debug/trace_event_memory.h new file mode 100644 index 0000000000..0d82198988 --- /dev/null +++ b/base/debug/trace_event_memory.h @@ -0,0 +1,152 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_DEBUG_TRACE_EVENT_MEMORY_H_ +#define BASE_DEBUG_TRACE_EVENT_MEMORY_H_ + +#include "base/base_export.h" +#include "base/debug/trace_event_impl.h" +#include "base/gtest_prod_util.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/timer/timer.h" + +// TODO(jamescook): Windows support for memory tracing. +#if !defined(NO_TCMALLOC) && !defined(OS_NACL) && \ + (defined(OS_LINUX) || defined(OS_ANDROID)) +#define TCMALLOC_TRACE_MEMORY_SUPPORTED 1 +#endif + +namespace base { + +class MessageLoopProxy; + +namespace debug { + +// Watches for chrome://tracing to be enabled or disabled. When tracing is +// enabled, also enables tcmalloc heap profiling. This class is the preferred +// way to turn trace-base heap memory profiling on and off. +class BASE_EXPORT TraceMemoryController + : public TraceLog::EnabledStateObserver { + public: + typedef int (*StackGeneratorFunction)(int skip_count, void** stack); + typedef void (*HeapProfilerStartFunction)(StackGeneratorFunction callback); + typedef void (*HeapProfilerStopFunction)(); + typedef char* (*GetHeapProfileFunction)(); + + // |message_loop_proxy| must be a proxy to the primary thread for the client + // process, e.g. the UI thread in a browser. The function pointers must be + // pointers to tcmalloc heap profiling functions; by avoiding direct calls to + // these functions we avoid a dependency on third_party/tcmalloc from base. + TraceMemoryController( + scoped_refptr message_loop_proxy, + HeapProfilerStartFunction heap_profiler_start_function, + HeapProfilerStopFunction heap_profiler_stop_function, + GetHeapProfileFunction get_heap_profile_function); + virtual ~TraceMemoryController(); + + // base::debug::TraceLog::EnabledStateChangedObserver overrides: + virtual void OnTraceLogEnabled() OVERRIDE; + virtual void OnTraceLogDisabled() OVERRIDE; + + // Starts heap memory profiling. + void StartProfiling(); + + // Captures a heap profile. + void DumpMemoryProfile(); + + // If memory tracing is enabled, dumps a memory profile to the tracing system. + void StopProfiling(); + + private: + FRIEND_TEST_ALL_PREFIXES(TraceMemoryTest, TraceMemoryController); + + bool IsTimerRunningForTest() const; + + // Ensures the observer starts and stops tracing on the primary thread. + scoped_refptr message_loop_proxy_; + + // Pointers to tcmalloc heap profiling functions. Allows this class to use + // tcmalloc functions without introducing a dependency from base to tcmalloc. + HeapProfilerStartFunction heap_profiler_start_function_; + HeapProfilerStopFunction heap_profiler_stop_function_; + GetHeapProfileFunction get_heap_profile_function_; + + // Timer to schedule memory profile dumps. + RepeatingTimer dump_timer_; + + WeakPtrFactory weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(TraceMemoryController); +}; + +////////////////////////////////////////////////////////////////////////////// + +// A scoped context for memory tracing. Pushes the name onto a stack for +// recording by tcmalloc heap profiling. +class BASE_EXPORT ScopedTraceMemory { + public: + // Memory for |name| must be static, for example, a literal string in + // a TRACE_EVENT macro. + explicit ScopedTraceMemory(const char* name); + ~ScopedTraceMemory(); + + // Enables the storing of trace names on a per-thread stack. + static void set_enabled(bool enabled) { enabled_ = enabled; } + + // Testing interface: + static void InitForTest(); + static void CleanupForTest(); + static int GetStackIndexForTest(); + static const char* GetItemForTest(int index); + + private: + static bool enabled_; + DISALLOW_COPY_AND_ASSIGN(ScopedTraceMemory); +}; + +////////////////////////////////////////////////////////////////////////////// + +// Converts tcmalloc's heap profiler data with pseudo-stacks in |input| to +// trace event compatible JSON and appends to |output|. Visible for testing. +BASE_EXPORT void AppendHeapProfileAsTraceFormat(const char* input, + std::string* output); + +// Converts the first |line| of heap profiler data, which contains totals for +// all allocations in a special format, into trace event compatible JSON and +// appends to |output|. Visible for testing. +BASE_EXPORT void AppendHeapProfileTotalsAsTraceFormat(const std::string& line, + std::string* output); + +// Converts a single |line| of heap profiler data into trace event compatible +// JSON and appends to |output|. Returns true if the line was valid and has a +// non-zero number of current allocations. Visible for testing. +BASE_EXPORT bool AppendHeapProfileLineAsTraceFormat(const std::string& line, + std::string* output); + +} // namespace debug +} // namespace base + +// Make local variables with unique names based on the line number. Note that +// the extra level of redirection is needed. +#define INTERNAL_TRACE_MEMORY_ID3(line) trace_memory_unique_##line +#define INTERNAL_TRACE_MEMORY_ID2(line) INTERNAL_TRACE_MEMORY_ID3(line) +#define INTERNAL_TRACE_MEMORY_ID INTERNAL_TRACE_MEMORY_ID2(__LINE__) + +// This is the core macro that adds a scope to each TRACE_EVENT location. +// It generates a unique local variable name using the macros above. +// TODO(jamescook): Make it record both category and name. +#if defined(TCMALLOC_TRACE_MEMORY_SUPPORTED) +#define INTERNAL_TRACE_MEMORY(category, name) \ + base::debug::ScopedTraceMemory INTERNAL_TRACE_MEMORY_ID(name); +#else +#define INTERNAL_TRACE_MEMORY(category, name) +#endif // defined(TRACE_MEMORY_SUPPORTED) + +// A special trace name that allows us to ignore memory allocations inside +// the memory dump system itself. The allocations are recorded, but the +// visualizer skips them. Must match the value in heap.js. +#define TRACE_MEMORY_IGNORE "trace-memory-ignore" + +#endif // BASE_DEBUG_TRACE_EVENT_MEMORY_H_ diff --git a/base/debug/trace_event_memory_unittest.cc b/base/debug/trace_event_memory_unittest.cc new file mode 100644 index 0000000000..7c4eae6bb4 --- /dev/null +++ b/base/debug/trace_event_memory_unittest.cc @@ -0,0 +1,211 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/debug/trace_event_memory.h" + +#include +#include + +#include "base/debug/trace_event_impl.h" +#include "base/message_loop/message_loop.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(TCMALLOC_TRACE_MEMORY_SUPPORTED) +#include "third_party/tcmalloc/chromium/src/gperftools/heap-profiler.h" +#endif + +namespace base { +namespace debug { + +// Tests for the trace event memory tracking system. Exists as a class so it +// can be a friend of TraceMemoryController. +class TraceMemoryTest : public testing::Test { + public: + TraceMemoryTest() {} + virtual ~TraceMemoryTest() {} + + private: + DISALLOW_COPY_AND_ASSIGN(TraceMemoryTest); +}; + +////////////////////////////////////////////////////////////////////////////// + +#if defined(TCMALLOC_TRACE_MEMORY_SUPPORTED) + +TEST_F(TraceMemoryTest, TraceMemoryController) { + MessageLoop message_loop; + + // Start with no observers of the TraceLog. + EXPECT_EQ(0u, TraceLog::GetInstance()->GetObserverCountForTest()); + + // Creating a controller adds it to the TraceLog observer list. + scoped_ptr controller( + new TraceMemoryController( + message_loop.message_loop_proxy(), + ::HeapProfilerWithPseudoStackStart, + ::HeapProfilerStop, + ::GetHeapProfile)); + EXPECT_EQ(1u, TraceLog::GetInstance()->GetObserverCountForTest()); + EXPECT_TRUE( + TraceLog::GetInstance()->HasEnabledStateObserver(controller.get())); + + // By default the observer isn't dumping memory profiles. + EXPECT_FALSE(controller->IsTimerRunningForTest()); + + // Simulate enabling tracing. + controller->StartProfiling(); + message_loop.RunUntilIdle(); + EXPECT_TRUE(controller->IsTimerRunningForTest()); + + // Simulate disabling tracing. + controller->StopProfiling(); + message_loop.RunUntilIdle(); + EXPECT_FALSE(controller->IsTimerRunningForTest()); + + // Deleting the observer removes it from the TraceLog observer list. + controller.reset(); + EXPECT_EQ(0u, TraceLog::GetInstance()->GetObserverCountForTest()); +} + +TEST_F(TraceMemoryTest, ScopedTraceMemory) { + ScopedTraceMemory::InitForTest(); + + // Start with an empty stack. + EXPECT_EQ(0, ScopedTraceMemory::GetStackIndexForTest()); + + { + // Push an item. + const char kScope1[] = "scope1"; + ScopedTraceMemory scope1(kScope1); + EXPECT_EQ(1, ScopedTraceMemory::GetStackIndexForTest()); + EXPECT_EQ(kScope1, ScopedTraceMemory::GetItemForTest(0)); + + { + // One more item. + const char kScope2[] = "scope2"; + ScopedTraceMemory scope2(kScope2); + EXPECT_EQ(2, ScopedTraceMemory::GetStackIndexForTest()); + EXPECT_EQ(kScope2, ScopedTraceMemory::GetItemForTest(1)); + } + + // Ended scope 2. + EXPECT_EQ(1, ScopedTraceMemory::GetStackIndexForTest()); + } + + // Ended scope 1. + EXPECT_EQ(0, ScopedTraceMemory::GetStackIndexForTest()); + + ScopedTraceMemory::CleanupForTest(); +} + +void TestDeepScopeNesting(int current, int depth) { + EXPECT_EQ(current, ScopedTraceMemory::GetStackIndexForTest()); + const char kCategory[] = "foo"; + ScopedTraceMemory scope(kCategory); + if (current < depth) + TestDeepScopeNesting(current + 1, depth); + EXPECT_EQ(current + 1, ScopedTraceMemory::GetStackIndexForTest()); +} + +TEST_F(TraceMemoryTest, DeepScopeNesting) { + ScopedTraceMemory::InitForTest(); + + // Ensure really deep scopes don't crash. + TestDeepScopeNesting(0, 100); + + ScopedTraceMemory::CleanupForTest(); +} + +#endif // defined(TRACE_MEMORY_SUPPORTED) + +///////////////////////////////////////////////////////////////////////////// + +TEST_F(TraceMemoryTest, AppendHeapProfileTotalsAsTraceFormat) { + // Empty input gives empty output. + std::string empty_output; + AppendHeapProfileTotalsAsTraceFormat("", &empty_output); + EXPECT_EQ("", empty_output); + + // Typical case. + const char input[] = + "heap profile: 357: 55227 [ 14653: 2624014] @ heapprofile"; + const std::string kExpectedOutput = + "{\"current_allocs\": 357, \"current_bytes\": 55227, \"trace\": \"\"}"; + std::string output; + AppendHeapProfileTotalsAsTraceFormat(input, &output); + EXPECT_EQ(kExpectedOutput, output); +} + +TEST_F(TraceMemoryTest, AppendHeapProfileLineAsTraceFormat) { + // Empty input gives empty output. + std::string empty_output; + EXPECT_FALSE(AppendHeapProfileLineAsTraceFormat("", &empty_output)); + EXPECT_EQ("", empty_output); + + // Invalid input returns false. + std::string junk_output; + EXPECT_FALSE(AppendHeapProfileLineAsTraceFormat("junk", &junk_output)); + + // Input with the addresses of name1 and name2. + const char kName1[] = "name1"; + const char kName2[] = "name2"; + std::ostringstream input; + input << " 68: 4195 [ 1087: 98009] @ " << &kName1 << " " << &kName2; + const std::string kExpectedOutput = + ",\n" + "{" + "\"current_allocs\": 68, " + "\"current_bytes\": 4195, " + "\"trace\": \"name1 name2 \"" + "}"; + std::string output; + EXPECT_TRUE( + AppendHeapProfileLineAsTraceFormat(input.str().c_str(), &output)); + EXPECT_EQ(kExpectedOutput, output); + + // Zero current allocations is skipped. + std::ostringstream zero_input; + zero_input << " 0: 0 [ 1087: 98009] @ " << &kName1 << " " + << &kName2; + std::string zero_output; + EXPECT_FALSE(AppendHeapProfileLineAsTraceFormat(zero_input.str().c_str(), + &zero_output)); + EXPECT_EQ("", zero_output); +} + +TEST_F(TraceMemoryTest, AppendHeapProfileAsTraceFormat) { + // Empty input gives empty output. + std::string empty_output; + AppendHeapProfileAsTraceFormat("", &empty_output); + EXPECT_EQ("", empty_output); + + // Typical case. + const char input[] = + "heap profile: 357: 55227 [ 14653: 2624014] @ heapprofile\n" + " 95: 40940 [ 649: 114260] @\n" + " 77: 32546 [ 742: 106234] @ 0x0 0x0\n" + " 0: 0 [ 132: 4236] @ 0x0\n" + "\n" + "MAPPED_LIBRARIES:\n" + "1be411fc1000-1be4139e4000 rw-p 00000000 00:00 0\n" + "1be4139e4000-1be4139e5000 ---p 00000000 00:00 0\n"; + const std::string kExpectedOutput = + "[{" + "\"current_allocs\": 357, " + "\"current_bytes\": 55227, " + "\"trace\": \"\"},\n" + "{\"current_allocs\": 95, " + "\"current_bytes\": 40940, " + "\"trace\": \"\"},\n" + "{\"current_allocs\": 77, " + "\"current_bytes\": 32546, " + "\"trace\": \"null null \"" + "}]\n"; + std::string output; + AppendHeapProfileAsTraceFormat(input, &output); + EXPECT_EQ(kExpectedOutput, output); +} + +} // namespace debug +} // namespace base diff --git a/base/debug/trace_event_unittest.cc b/base/debug/trace_event_unittest.cc new file mode 100644 index 0000000000..b5909845f2 --- /dev/null +++ b/base/debug/trace_event_unittest.cc @@ -0,0 +1,2024 @@ +// Copyright (c) 2012 The Chromium Authors. All 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 + +#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/process_handle.h" +#include "base/strings/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; +}; + +const int kThreadId = 42; +const int kAsyncId = 5; +const char kAsyncIdStr[] = "0x5"; +const int kAsyncId2 = 6; +const char kAsyncId2Str[] = "0x6"; + +class TraceEventTestFixture : public testing::Test { + public: + void OnTraceDataCollected( + const scoped_refptr& events_str); + void OnTraceNotification(int notification) { + if (notification & TraceLog::EVENT_WATCH_NOTIFICATION) + ++event_watch_notification_; + notifications_received_ |= 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() { + BeginSpecificTrace("*"); + } + + void BeginSpecificTrace(const std::string& filter) { + event_watch_notification_ = 0; + notifications_received_ = 0; + TraceLog::GetInstance()->SetEnabled(CategoryFilter(filter), + TraceLog::RECORD_UNTIL_FULL); + } + + void EndTraceAndFlush() { + while (TraceLog::GetInstance()->IsEnabled()) + TraceLog::GetInstance()->SetDisabled(); + TraceLog::GetInstance()->Flush( + base::Bind(&TraceEventTestFixture::OnTraceDataCollected, + base::Unretained(this))); + } + + virtual void SetUp() OVERRIDE { + const char* name = PlatformThread::GetName(); + old_thread_name_ = name ? strdup(name) : NULL; + notifications_received_ = 0; + + TraceLog::DeleteForTesting(); + TraceLog* tracelog = TraceLog::GetInstance(); + ASSERT_TRUE(tracelog); + ASSERT_FALSE(tracelog->IsEnabled()); + tracelog->SetNotificationCallback( + base::Bind(&TraceEventTestFixture::OnTraceNotification, + base::Unretained(this))); + trace_buffer_.SetOutputCallback(json_output_.GetCallback()); + } + virtual void TearDown() OVERRIDE { + if (TraceLog::GetInstance()) + EXPECT_FALSE(TraceLog::GetInstance()->IsEnabled()); + PlatformThread::SetName(old_thread_name_ ? old_thread_name_ : ""); + free(old_thread_name_); + old_thread_name_ = NULL; + // We want our singleton torn down after each test. + TraceLog::DeleteForTesting(); + } + + char* old_thread_name_; + ListValue trace_parsed_; + base::debug::TraceResultBuffer trace_buffer_; + base::debug::TraceResultBuffer::SimpleOutput json_output_; + int event_watch_notification_; + int notifications_received_; + + private: + // We want our singleton torn down after each test. + ShadowingAtExitManager at_exit_manager_; + Lock lock_; +}; + +void TraceEventTestFixture::OnTraceDataCollected( + const scoped_refptr& events_str) { + AutoLock lock(lock_); + json_output_.json_output.clear(); + trace_buffer_.Start(); + trace_buffer_.AddFragment(events_str->data()); + trace_buffer_.Finish(); + + scoped_ptr 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()) { + scoped_ptr item; + root_list->Remove(0, &item); + trace_parsed_.Append(item.release()); + } +} + +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(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::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) { + if (it.key().find(string_to_match) != std::string::npos) + return true; + + std::string value_str; + it.value().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(value); + + if (IsStringInDict(string_to_match, dict)) + return dict; + } + return NULL; +} + +std::vector FindTraceEntries( + const ListValue& trace_parsed, + const char* string_to_match) { + std::vector 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(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_SCOPE_GLOBAL); + TRACE_EVENT_INSTANT1("all", "TRACE_EVENT_INSTANT1 call", + TRACE_EVENT_SCOPE_PROCESS, "name1", "value1"); + TRACE_EVENT_INSTANT2("all", "TRACE_EVENT_INSTANT2 call", + TRACE_EVENT_SCOPE_THREAD, + "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", kAsyncId); + TRACE_EVENT_ASYNC_BEGIN1("all", "TRACE_EVENT_ASYNC_BEGIN1 call", kAsyncId, + "name1", "value1"); + TRACE_EVENT_ASYNC_BEGIN2("all", "TRACE_EVENT_ASYNC_BEGIN2 call", kAsyncId, + "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", kAsyncId); + TRACE_EVENT_ASYNC_END1("all", "TRACE_EVENT_ASYNC_END1 call", kAsyncId, + "name1", "value1"); + TRACE_EVENT_ASYNC_END2("all", "TRACE_EVENT_ASYNC_END2 call", kAsyncId, + "name1", "value1", + "name2", "value2"); + + TRACE_EVENT_BEGIN_ETW("TRACE_EVENT_BEGIN_ETW0 call", kAsyncId, NULL); + TRACE_EVENT_BEGIN_ETW("TRACE_EVENT_BEGIN_ETW1 call", kAsyncId, "value"); + TRACE_EVENT_END_ETW("TRACE_EVENT_END_ETW0 call", kAsyncId, NULL); + TRACE_EVENT_END_ETW("TRACE_EVENT_END_ETW1 call", kAsyncId, "value"); + TRACE_EVENT_INSTANT_ETW("TRACE_EVENT_INSTANT_ETW0 call", kAsyncId, NULL); + TRACE_EVENT_INSTANT_ETW("TRACE_EVENT_INSTANT_ETW1 call", kAsyncId, "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); + + TRACE_EVENT_COPY_BEGIN_WITH_ID_TID_AND_TIMESTAMP0("all", + "TRACE_EVENT_COPY_BEGIN_WITH_ID_TID_AND_TIMESTAMP0 call", + kAsyncId, kThreadId, 12345); + TRACE_EVENT_COPY_END_WITH_ID_TID_AND_TIMESTAMP0("all", + "TRACE_EVENT_COPY_END_WITH_ID_TID_AND_TIMESTAMP0 call", + kAsyncId, kThreadId, 23456); + + TRACE_EVENT_BEGIN_WITH_ID_TID_AND_TIMESTAMP0("all", + "TRACE_EVENT_BEGIN_WITH_ID_TID_AND_TIMESTAMP0 call", + kAsyncId2, kThreadId, 34567); + TRACE_EVENT_END_WITH_ID_TID_AND_TIMESTAMP0("all", + "TRACE_EVENT_END_WITH_ID_TID_AND_TIMESTAMP0 call", + kAsyncId2, kThreadId, 45678); + + TRACE_EVENT_OBJECT_CREATED_WITH_ID("all", "tracked object 1", 0x42); + TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID( + "all", "tracked object 1", 0x42, "hello"); + TRACE_EVENT_OBJECT_DELETED_WITH_ID("all", "tracked object 1", 0x42); + + TraceScopedTrackableObject trackable("all", "tracked object 2", + 0x2128506); + trackable.snapshot("world"); + } // 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("0x1122", 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"); + { + std::string scope; + EXPECT_TRUE((item && item->GetString("s", &scope))); + EXPECT_EQ("g", scope); + } + EXPECT_FIND_("TRACE_EVENT_INSTANT1 call"); + { + std::string scope; + EXPECT_TRUE((item && item->GetString("s", &scope))); + EXPECT_EQ("p", scope); + } + EXPECT_SUB_FIND_("name1"); + EXPECT_SUB_FIND_("value1"); + EXPECT_FIND_("TRACE_EVENT_INSTANT2 call"); + { + std::string scope; + EXPECT_TRUE((item && item->GetString("s", &scope))); + EXPECT_EQ("t", scope); + } + 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_(kAsyncIdStr); + EXPECT_FIND_("TRACE_EVENT_ASYNC_BEGIN1 call"); + EXPECT_SUB_FIND_("id"); + EXPECT_SUB_FIND_(kAsyncIdStr); + EXPECT_SUB_FIND_("name1"); + EXPECT_SUB_FIND_("value1"); + EXPECT_FIND_("TRACE_EVENT_ASYNC_BEGIN2 call"); + EXPECT_SUB_FIND_("id"); + EXPECT_SUB_FIND_(kAsyncIdStr); + 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_(kAsyncIdStr); + EXPECT_SUB_FIND_("step1"); + EXPECT_FIND_("TRACE_EVENT_ASYNC_STEP1 call"); + EXPECT_SUB_FIND_("id"); + EXPECT_SUB_FIND_(kAsyncIdStr); + 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_(kAsyncIdStr); + EXPECT_FIND_("TRACE_EVENT_ASYNC_END1 call"); + EXPECT_SUB_FIND_("id"); + EXPECT_SUB_FIND_(kAsyncIdStr); + EXPECT_SUB_FIND_("name1"); + EXPECT_SUB_FIND_("value1"); + EXPECT_FIND_("TRACE_EVENT_ASYNC_END2 call"); + EXPECT_SUB_FIND_("id"); + EXPECT_SUB_FIND_(kAsyncIdStr); + 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_(kAsyncIdStr); + EXPECT_SUB_FIND_("extra"); + EXPECT_SUB_FIND_("NULL"); + EXPECT_FIND_("TRACE_EVENT_BEGIN_ETW1 call"); + EXPECT_SUB_FIND_("id"); + EXPECT_SUB_FIND_(kAsyncIdStr); + EXPECT_SUB_FIND_("extra"); + EXPECT_SUB_FIND_("value"); + EXPECT_FIND_("TRACE_EVENT_END_ETW0 call"); + EXPECT_SUB_FIND_("id"); + EXPECT_SUB_FIND_(kAsyncIdStr); + EXPECT_SUB_FIND_("extra"); + EXPECT_SUB_FIND_("NULL"); + EXPECT_FIND_("TRACE_EVENT_END_ETW1 call"); + EXPECT_SUB_FIND_("id"); + EXPECT_SUB_FIND_(kAsyncIdStr); + EXPECT_SUB_FIND_("extra"); + EXPECT_SUB_FIND_("value"); + EXPECT_FIND_("TRACE_EVENT_INSTANT_ETW0 call"); + EXPECT_SUB_FIND_("id"); + EXPECT_SUB_FIND_(kAsyncIdStr); + EXPECT_SUB_FIND_("extra"); + EXPECT_SUB_FIND_("NULL"); + EXPECT_FIND_("TRACE_EVENT_INSTANT_ETW1 call"); + EXPECT_SUB_FIND_("id"); + EXPECT_SUB_FIND_(kAsyncIdStr); + 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("0x319009", 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("0x319009", 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); + } + + EXPECT_FIND_("TRACE_EVENT_COPY_BEGIN_WITH_ID_TID_AND_TIMESTAMP0 call"); + { + int val; + EXPECT_TRUE((item && item->GetInteger("ts", &val))); + EXPECT_EQ(12345, val); + EXPECT_TRUE((item && item->GetInteger("tid", &val))); + EXPECT_EQ(kThreadId, val); + std::string id; + EXPECT_TRUE((item && item->GetString("id", &id))); + EXPECT_EQ(kAsyncIdStr, id); + } + + EXPECT_FIND_("TRACE_EVENT_COPY_END_WITH_ID_TID_AND_TIMESTAMP0 call"); + { + int val; + EXPECT_TRUE((item && item->GetInteger("ts", &val))); + EXPECT_EQ(23456, val); + EXPECT_TRUE((item && item->GetInteger("tid", &val))); + EXPECT_EQ(kThreadId, val); + std::string id; + EXPECT_TRUE((item && item->GetString("id", &id))); + EXPECT_EQ(kAsyncIdStr, id); + } + + EXPECT_FIND_("TRACE_EVENT_BEGIN_WITH_ID_TID_AND_TIMESTAMP0 call"); + { + int val; + EXPECT_TRUE((item && item->GetInteger("ts", &val))); + EXPECT_EQ(34567, val); + EXPECT_TRUE((item && item->GetInteger("tid", &val))); + EXPECT_EQ(kThreadId, val); + std::string id; + EXPECT_TRUE((item && item->GetString("id", &id))); + EXPECT_EQ(kAsyncId2Str, id); + } + + EXPECT_FIND_("TRACE_EVENT_END_WITH_ID_TID_AND_TIMESTAMP0 call"); + { + int val; + EXPECT_TRUE((item && item->GetInteger("ts", &val))); + EXPECT_EQ(45678, val); + EXPECT_TRUE((item && item->GetInteger("tid", &val))); + EXPECT_EQ(kThreadId, val); + std::string id; + EXPECT_TRUE((item && item->GetString("id", &id))); + EXPECT_EQ(kAsyncId2Str, id); + } + + EXPECT_FIND_("tracked object 1"); + { + std::string phase; + std::string id; + std::string snapshot; + + EXPECT_TRUE((item && item->GetString("ph", &phase))); + EXPECT_EQ("N", phase); + EXPECT_TRUE((item && item->GetString("id", &id))); + EXPECT_EQ("0x42", id); + + item = FindTraceEntry(trace_parsed, "tracked object 1", item); + EXPECT_TRUE(item); + EXPECT_TRUE(item && item->GetString("ph", &phase)); + EXPECT_EQ("O", phase); + EXPECT_TRUE(item && item->GetString("id", &id)); + EXPECT_EQ("0x42", id); + EXPECT_TRUE(item && item->GetString("args.snapshot", &snapshot)); + EXPECT_EQ("hello", snapshot); + + item = FindTraceEntry(trace_parsed, "tracked object 1", item); + EXPECT_TRUE(item); + EXPECT_TRUE(item && item->GetString("ph", &phase)); + EXPECT_EQ("D", phase); + EXPECT_TRUE(item && item->GetString("id", &id)); + EXPECT_EQ("0x42", id); + } + + EXPECT_FIND_("tracked object 2"); + { + std::string phase; + std::string id; + std::string snapshot; + + EXPECT_TRUE(item && item->GetString("ph", &phase)); + EXPECT_EQ("N", phase); + EXPECT_TRUE(item && item->GetString("id", &id)); + EXPECT_EQ("0x2128506", id); + + item = FindTraceEntry(trace_parsed, "tracked object 2", item); + EXPECT_TRUE(item); + EXPECT_TRUE(item && item->GetString("ph", &phase)); + EXPECT_EQ("O", phase); + EXPECT_TRUE(item && item->GetString("id", &id)); + EXPECT_EQ("0x2128506", id); + EXPECT_TRUE(item && item->GetString("args.snapshot", &snapshot)); + EXPECT_EQ("world", snapshot); + + item = FindTraceEntry(trace_parsed, "tracked object 2", item); + EXPECT_TRUE(item); + EXPECT_TRUE(item && item->GetString("ph", &phase)); + EXPECT_EQ("D", phase); + EXPECT_TRUE(item && item->GetString("id", &id)); + EXPECT_EQ("0x2128506", id); + } +} + +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", + TRACE_EVENT_SCOPE_THREAD, + "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 > 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(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_SCOPE_THREAD); + 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) { + TraceLog::GetInstance()->SetEnabled(CategoryFilter("*"), + TraceLog::RECORD_UNTIL_FULL); + + TraceWithAllMacroVariants(NULL); + + EndTraceAndFlush(); + + ValidateAllTraceMacrosCreatedData(trace_parsed_); +} + +class MockEnabledStateChangedObserver : + public base::debug::TraceLog::EnabledStateObserver { + public: + MOCK_METHOD0(OnTraceLogEnabled, void()); + MOCK_METHOD0(OnTraceLogDisabled, void()); +}; + +TEST_F(TraceEventTestFixture, EnabledObserverFiresOnEnable) { + MockEnabledStateChangedObserver observer; + TraceLog::GetInstance()->AddEnabledStateObserver(&observer); + + EXPECT_CALL(observer, OnTraceLogEnabled()) + .Times(1); + TraceLog::GetInstance()->SetEnabled(CategoryFilter("*"), + TraceLog::RECORD_UNTIL_FULL); + testing::Mock::VerifyAndClear(&observer); + EXPECT_TRUE(TraceLog::GetInstance()->IsEnabled()); + + // Cleanup. + TraceLog::GetInstance()->RemoveEnabledStateObserver(&observer); + TraceLog::GetInstance()->SetDisabled(); +} + +TEST_F(TraceEventTestFixture, EnabledObserverDoesntFireOnSecondEnable) { + TraceLog::GetInstance()->SetEnabled(CategoryFilter("*"), + TraceLog::RECORD_UNTIL_FULL); + + testing::StrictMock observer; + TraceLog::GetInstance()->AddEnabledStateObserver(&observer); + + EXPECT_CALL(observer, OnTraceLogEnabled()) + .Times(0); + EXPECT_CALL(observer, OnTraceLogDisabled()) + .Times(0); + TraceLog::GetInstance()->SetEnabled(CategoryFilter("*"), + TraceLog::RECORD_UNTIL_FULL); + testing::Mock::VerifyAndClear(&observer); + EXPECT_TRUE(TraceLog::GetInstance()->IsEnabled()); + + // Cleanup. + TraceLog::GetInstance()->RemoveEnabledStateObserver(&observer); + TraceLog::GetInstance()->SetDisabled(); + TraceLog::GetInstance()->SetDisabled(); +} + +TEST_F(TraceEventTestFixture, EnabledObserverDoesntFireOnNestedDisable) { + CategoryFilter cf_inc_all("*"); + TraceLog::GetInstance()->SetEnabled(cf_inc_all, TraceLog::RECORD_UNTIL_FULL); + TraceLog::GetInstance()->SetEnabled(cf_inc_all, TraceLog::RECORD_UNTIL_FULL); + + testing::StrictMock observer; + TraceLog::GetInstance()->AddEnabledStateObserver(&observer); + + EXPECT_CALL(observer, OnTraceLogEnabled()) + .Times(0); + EXPECT_CALL(observer, OnTraceLogDisabled()) + .Times(0); + TraceLog::GetInstance()->SetDisabled(); + testing::Mock::VerifyAndClear(&observer); + + // Cleanup. + TraceLog::GetInstance()->RemoveEnabledStateObserver(&observer); + TraceLog::GetInstance()->SetDisabled(); +} + +TEST_F(TraceEventTestFixture, EnabledObserverFiresOnDisable) { + TraceLog::GetInstance()->SetEnabled(CategoryFilter("*"), + TraceLog::RECORD_UNTIL_FULL); + + MockEnabledStateChangedObserver observer; + TraceLog::GetInstance()->AddEnabledStateObserver(&observer); + + EXPECT_CALL(observer, OnTraceLogDisabled()) + .Times(1); + TraceLog::GetInstance()->SetDisabled(); + testing::Mock::VerifyAndClear(&observer); + + // Cleanup. + TraceLog::GetInstance()->RemoveEnabledStateObserver(&observer); +} + +// Tests the IsEnabled() state of TraceLog changes before callbacks. +class AfterStateChangeEnabledStateObserver + : public base::debug::TraceLog::EnabledStateObserver { + public: + AfterStateChangeEnabledStateObserver() {} + virtual ~AfterStateChangeEnabledStateObserver() {} + + // base::debug::TraceLog::EnabledStateObserver overrides: + virtual void OnTraceLogEnabled() OVERRIDE { + EXPECT_TRUE(TraceLog::GetInstance()->IsEnabled()); + } + + virtual void OnTraceLogDisabled() OVERRIDE { + EXPECT_FALSE(TraceLog::GetInstance()->IsEnabled()); + } +}; + +TEST_F(TraceEventTestFixture, ObserversFireAfterStateChange) { + AfterStateChangeEnabledStateObserver observer; + TraceLog::GetInstance()->AddEnabledStateObserver(&observer); + + TraceLog::GetInstance()->SetEnabled(CategoryFilter("*"), + TraceLog::RECORD_UNTIL_FULL); + EXPECT_TRUE(TraceLog::GetInstance()->IsEnabled()); + + TraceLog::GetInstance()->SetDisabled(); + EXPECT_FALSE(TraceLog::GetInstance()->IsEnabled()); + + TraceLog::GetInstance()->RemoveEnabledStateObserver(&observer); +} + +// Tests that a state observer can remove itself during a callback. +class SelfRemovingEnabledStateObserver + : public base::debug::TraceLog::EnabledStateObserver { + public: + SelfRemovingEnabledStateObserver() {} + virtual ~SelfRemovingEnabledStateObserver() {} + + // base::debug::TraceLog::EnabledStateObserver overrides: + virtual void OnTraceLogEnabled() OVERRIDE {} + + virtual void OnTraceLogDisabled() OVERRIDE { + TraceLog::GetInstance()->RemoveEnabledStateObserver(this); + } +}; + +TEST_F(TraceEventTestFixture, SelfRemovingObserver) { + ASSERT_EQ(0u, TraceLog::GetInstance()->GetObserverCountForTest()); + + SelfRemovingEnabledStateObserver observer; + TraceLog::GetInstance()->AddEnabledStateObserver(&observer); + EXPECT_EQ(1u, TraceLog::GetInstance()->GetObserverCountForTest()); + + TraceLog::GetInstance()->SetEnabled(CategoryFilter("*"), + TraceLog::RECORD_UNTIL_FULL); + TraceLog::GetInstance()->SetDisabled(); + // The observer removed itself on disable. + EXPECT_EQ(0u, TraceLog::GetInstance()->GetObserverCountForTest()); +} + +bool IsNewTrace() { + bool is_new_trace; + TRACE_EVENT_IS_NEW_TRACE(&is_new_trace); + return is_new_trace; +} + +TEST_F(TraceEventTestFixture, NewTraceRecording) { + ASSERT_FALSE(IsNewTrace()); + TraceLog::GetInstance()->SetEnabled(CategoryFilter("*"), + TraceLog::RECORD_UNTIL_FULL); + // First call to IsNewTrace() should succeed. But, the second shouldn't. + ASSERT_TRUE(IsNewTrace()); + ASSERT_FALSE(IsNewTrace()); + EndTraceAndFlush(); + + // IsNewTrace() should definitely be false now. + ASSERT_FALSE(IsNewTrace()); + + // Start another trace. IsNewTrace() should become true again, briefly, as + // before. + TraceLog::GetInstance()->SetEnabled(CategoryFilter("*"), + TraceLog::RECORD_UNTIL_FULL); + ASSERT_TRUE(IsNewTrace()); + ASSERT_FALSE(IsNewTrace()); + + // Cleanup. + EndTraceAndFlush(); +} + + +// Test that categories work. +TEST_F(TraceEventTestFixture, Categories) { + // 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_SCOPE_THREAD); + TRACE_EVENT_INSTANT0("c2", "name", TRACE_EVENT_SCOPE_THREAD); + BeginTrace(); + TRACE_EVENT_INSTANT0("c3", "name", TRACE_EVENT_SCOPE_THREAD); + TRACE_EVENT_INSTANT0("c4", "name", TRACE_EVENT_SCOPE_THREAD); + // Category groups containing more than one category. + TRACE_EVENT_INSTANT0("c5,c6", "name", TRACE_EVENT_SCOPE_THREAD); + TRACE_EVENT_INSTANT0("c7,c8", "name", TRACE_EVENT_SCOPE_THREAD); + TRACE_EVENT_INSTANT0(TRACE_DISABLED_BY_DEFAULT("c9"), "name", + TRACE_EVENT_SCOPE_THREAD); + + EndTraceAndFlush(); + std::vector cat_groups; + TraceLog::GetInstance()->GetKnownCategoryGroups(&cat_groups); + EXPECT_TRUE(std::find(cat_groups.begin(), + cat_groups.end(), "c1") != cat_groups.end()); + EXPECT_TRUE(std::find(cat_groups.begin(), + cat_groups.end(), "c2") != cat_groups.end()); + EXPECT_TRUE(std::find(cat_groups.begin(), + cat_groups.end(), "c3") != cat_groups.end()); + EXPECT_TRUE(std::find(cat_groups.begin(), + cat_groups.end(), "c4") != cat_groups.end()); + EXPECT_TRUE(std::find(cat_groups.begin(), + cat_groups.end(), "c5,c6") != cat_groups.end()); + EXPECT_TRUE(std::find(cat_groups.begin(), + cat_groups.end(), "c7,c8") != cat_groups.end()); + EXPECT_TRUE(std::find(cat_groups.begin(), + cat_groups.end(), + "disabled-by-default-c9") != cat_groups.end()); + // Make sure metadata isn't returned. + EXPECT_TRUE(std::find(cat_groups.begin(), + cat_groups.end(), "__metadata") == cat_groups.end()); + + const std::vector empty_categories; + std::vector included_categories; + std::vector excluded_categories; + + // Test that category filtering works. + + // Include nonexistent category -> no events + Clear(); + included_categories.clear(); + TraceLog::GetInstance()->SetEnabled(CategoryFilter("not_found823564786"), + TraceLog::RECORD_UNTIL_FULL); + TRACE_EVENT_INSTANT0("cat1", "name", TRACE_EVENT_SCOPE_THREAD); + TRACE_EVENT_INSTANT0("cat2", "name", TRACE_EVENT_SCOPE_THREAD); + EndTraceAndFlush(); + EXPECT_TRUE(trace_parsed_.empty()); + + // Include existent category -> only events of that category + Clear(); + included_categories.clear(); + TraceLog::GetInstance()->SetEnabled(CategoryFilter("inc"), + TraceLog::RECORD_UNTIL_FULL); + TRACE_EVENT_INSTANT0("inc", "name", TRACE_EVENT_SCOPE_THREAD); + TRACE_EVENT_INSTANT0("inc2", "name", TRACE_EVENT_SCOPE_THREAD); + EndTraceAndFlush(); + EXPECT_TRUE(FindMatchingValue("cat", "inc")); + EXPECT_FALSE(FindNonMatchingValue("cat", "inc")); + + // Include existent wildcard -> all categories matching wildcard + Clear(); + included_categories.clear(); + TraceLog::GetInstance()->SetEnabled( + CategoryFilter("inc_wildcard_*,inc_wildchar_?_end"), + TraceLog::RECORD_UNTIL_FULL); + TRACE_EVENT_INSTANT0("inc_wildcard_abc", "included", + TRACE_EVENT_SCOPE_THREAD); + TRACE_EVENT_INSTANT0("inc_wildcard_", "included", TRACE_EVENT_SCOPE_THREAD); + TRACE_EVENT_INSTANT0("inc_wildchar_x_end", "included", + TRACE_EVENT_SCOPE_THREAD); + TRACE_EVENT_INSTANT0("inc_wildchar_bla_end", "not_inc", + TRACE_EVENT_SCOPE_THREAD); + TRACE_EVENT_INSTANT0("cat1", "not_inc", TRACE_EVENT_SCOPE_THREAD); + TRACE_EVENT_INSTANT0("cat2", "not_inc", TRACE_EVENT_SCOPE_THREAD); + TRACE_EVENT_INSTANT0("inc_wildcard_category,other_category", "included", + TRACE_EVENT_SCOPE_THREAD); + TRACE_EVENT_INSTANT0( + "non_included_category,inc_wildcard_category", "included", + TRACE_EVENT_SCOPE_THREAD); + 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")); + EXPECT_TRUE(FindMatchingValue("cat", "inc_wildcard_category,other_category")); + EXPECT_TRUE(FindMatchingValue("cat", + "non_included_category,inc_wildcard_category")); + + included_categories.clear(); + + // Exclude nonexistent category -> all events + Clear(); + TraceLog::GetInstance()->SetEnabled(CategoryFilter("-not_found823564786"), + TraceLog::RECORD_UNTIL_FULL); + TRACE_EVENT_INSTANT0("cat1", "name", TRACE_EVENT_SCOPE_THREAD); + TRACE_EVENT_INSTANT0("cat2", "name", TRACE_EVENT_SCOPE_THREAD); + TRACE_EVENT_INSTANT0("category1,category2", "name", TRACE_EVENT_SCOPE_THREAD); + EndTraceAndFlush(); + EXPECT_TRUE(FindMatchingValue("cat", "cat1")); + EXPECT_TRUE(FindMatchingValue("cat", "cat2")); + EXPECT_TRUE(FindMatchingValue("cat", "category1,category2")); + + // Exclude existent category -> only events of other categories + Clear(); + TraceLog::GetInstance()->SetEnabled(CategoryFilter("-inc"), + TraceLog::RECORD_UNTIL_FULL); + TRACE_EVENT_INSTANT0("inc", "name", TRACE_EVENT_SCOPE_THREAD); + TRACE_EVENT_INSTANT0("inc2", "name", TRACE_EVENT_SCOPE_THREAD); + TRACE_EVENT_INSTANT0("inc2,inc", "name", TRACE_EVENT_SCOPE_THREAD); + TRACE_EVENT_INSTANT0("inc,inc2", "name", TRACE_EVENT_SCOPE_THREAD); + EndTraceAndFlush(); + EXPECT_TRUE(FindMatchingValue("cat", "inc2")); + EXPECT_FALSE(FindMatchingValue("cat", "inc")); + EXPECT_FALSE(FindMatchingValue("cat", "inc2,inc")); + EXPECT_FALSE(FindMatchingValue("cat", "inc,inc2")); + + // Exclude existent wildcard -> all categories not matching wildcard + Clear(); + TraceLog::GetInstance()->SetEnabled( + CategoryFilter("-inc_wildcard_*,-inc_wildchar_?_end"), + TraceLog::RECORD_UNTIL_FULL); + TRACE_EVENT_INSTANT0("inc_wildcard_abc", "not_inc", + TRACE_EVENT_SCOPE_THREAD); + TRACE_EVENT_INSTANT0("inc_wildcard_", "not_inc", + TRACE_EVENT_SCOPE_THREAD); + TRACE_EVENT_INSTANT0("inc_wildchar_x_end", "not_inc", + TRACE_EVENT_SCOPE_THREAD); + TRACE_EVENT_INSTANT0("inc_wildchar_bla_end", "included", + TRACE_EVENT_SCOPE_THREAD); + TRACE_EVENT_INSTANT0("cat1", "included", TRACE_EVENT_SCOPE_THREAD); + TRACE_EVENT_INSTANT0("cat2", "included", TRACE_EVENT_SCOPE_THREAD); + 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) { + // Basic one occurrence. + BeginTrace(); + TraceLog::GetInstance()->SetWatchEvent("cat", "event"); + TRACE_EVENT_INSTANT0("cat", "event", TRACE_EVENT_SCOPE_THREAD); + EndTraceAndFlush(); + EXPECT_EQ(event_watch_notification_, 1); + + // Basic one occurrence before Set. + BeginTrace(); + TRACE_EVENT_INSTANT0("cat", "event", TRACE_EVENT_SCOPE_THREAD); + TraceLog::GetInstance()->SetWatchEvent("cat", "event"); + EndTraceAndFlush(); + EXPECT_EQ(event_watch_notification_, 1); + + // Auto-reset after end trace. + BeginTrace(); + TraceLog::GetInstance()->SetWatchEvent("cat", "event"); + EndTraceAndFlush(); + BeginTrace(); + TRACE_EVENT_INSTANT0("cat", "event", TRACE_EVENT_SCOPE_THREAD); + 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", TRACE_EVENT_SCOPE_THREAD); + EndTraceAndFlush(); + EXPECT_EQ(event_watch_notification_, num_occurrences); + + // Wrong category. + BeginTrace(); + TraceLog::GetInstance()->SetWatchEvent("cat", "event"); + TRACE_EVENT_INSTANT0("wrong_cat", "event", TRACE_EVENT_SCOPE_THREAD); + EndTraceAndFlush(); + EXPECT_EQ(event_watch_notification_, 0); + + // Wrong name. + BeginTrace(); + TraceLog::GetInstance()->SetWatchEvent("cat", "event"); + TRACE_EVENT_INSTANT0("cat", "wrong_event", TRACE_EVENT_SCOPE_THREAD); + EndTraceAndFlush(); + EXPECT_EQ(event_watch_notification_, 0); + + // Canceled. + BeginTrace(); + TraceLog::GetInstance()->SetWatchEvent("cat", "event"); + TraceLog::GetInstance()->CancelWatchEvent(); + TRACE_EVENT_INSTANT0("cat", "event", TRACE_EVENT_SCOPE_THREAD); + EndTraceAndFlush(); + EXPECT_EQ(event_watch_notification_, 0); +} + +// Test ASYNC_BEGIN/END events +TEST_F(TraceEventTestFixture, AsyncBeginEndEvents) { + 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, "0x%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", "0x0")); + + // BEGIN events should not have id + EXPECT_FALSE(FindNamePhaseKeyValue("name2", "B", "id", "0")); +} + +// Test ASYNC_BEGIN/END events +TEST_F(TraceEventTestFixture, AsyncBeginEndPointerMangling) { + 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) { + 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) { + 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) { + 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) { + // 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 items = + FindTraceEntries(trace_parsed_, "thread_name"); + for (int i = 0; i < static_cast(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(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(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) { + BeginTrace(); + + PlatformThread::SetName(""); + TRACE_EVENT_INSTANT0("drink", "water", TRACE_EVENT_SCOPE_THREAD); + + PlatformThread::SetName("cafe"); + TRACE_EVENT_INSTANT0("drink", "coffee", TRACE_EVENT_SCOPE_THREAD); + + PlatformThread::SetName("shop"); + // No event here, so won't appear in combined name. + + PlatformThread::SetName("pub"); + TRACE_EVENT_INSTANT0("drink", "beer", TRACE_EVENT_SCOPE_THREAD); + TRACE_EVENT_INSTANT0("drink", "wine", TRACE_EVENT_SCOPE_THREAD); + + PlatformThread::SetName(" bar"); + TRACE_EVENT_INSTANT0("drink", "whisky", TRACE_EVENT_SCOPE_THREAD); + + EndTraceAndFlush(); + + std::vector 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(tid)); + + std::string expected_name = "cafe,pub, bar"; + std::string tmp; + EXPECT_TRUE(item->GetString("args.name", &tmp)); + EXPECT_EQ(expected_name, tmp); +} + +// Test that the disabled trace categories are included/excluded from the +// trace output correctly. +TEST_F(TraceEventTestFixture, DisabledCategories) { + BeginTrace(); + TRACE_EVENT_INSTANT0(TRACE_DISABLED_BY_DEFAULT("cc"), "first", + TRACE_EVENT_SCOPE_THREAD); + TRACE_EVENT_INSTANT0("included", "first", TRACE_EVENT_SCOPE_THREAD); + EndTraceAndFlush(); + { + const DictionaryValue* item = NULL; + ListValue& trace_parsed = trace_parsed_; + EXPECT_NOT_FIND_("disabled-by-default-cc"); + EXPECT_FIND_("included"); + } + Clear(); + + BeginSpecificTrace("disabled-by-default-cc"); + TRACE_EVENT_INSTANT0(TRACE_DISABLED_BY_DEFAULT("cc"), "second", + TRACE_EVENT_SCOPE_THREAD); + TRACE_EVENT_INSTANT0("other_included", "second", TRACE_EVENT_SCOPE_THREAD); + EndTraceAndFlush(); + + { + const DictionaryValue* item = NULL; + ListValue& trace_parsed = trace_parsed_; + EXPECT_FIND_("disabled-by-default-cc"); + EXPECT_FIND_("other_included"); + } +} + +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. + std::string name_string("event name"); + + BeginTrace(); + TRACE_EVENT_INSTANT0("category", name_string.c_str(), + TRACE_EVENT_SCOPE_THREAD); + + // 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) { + 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_SCOPE_THREAD); + 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) { + 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]"); +} + +// Test that trace_event parameters are not evaluated if the tracing +// system is disabled. +TEST_F(TraceEventTestFixture, TracingIsLazy) { + BeginTrace(); + + int a = 0; + TRACE_EVENT_INSTANT1("category", "test", TRACE_EVENT_SCOPE_THREAD, "a", a++); + EXPECT_EQ(1, a); + + TraceLog::GetInstance()->SetDisabled(); + + TRACE_EVENT_INSTANT1("category", "test", TRACE_EVENT_SCOPE_THREAD, "a", a++); + EXPECT_EQ(1, a); + + EndTraceAndFlush(); +} + +TEST_F(TraceEventTestFixture, TraceEnableDisable) { + TraceLog* trace_log = TraceLog::GetInstance(); + CategoryFilter cf_inc_all("*"); + trace_log->SetEnabled(cf_inc_all, TraceLog::RECORD_UNTIL_FULL); + EXPECT_TRUE(trace_log->IsEnabled()); + trace_log->SetDisabled(); + EXPECT_FALSE(trace_log->IsEnabled()); + + trace_log->SetEnabled(cf_inc_all, TraceLog::RECORD_UNTIL_FULL); + EXPECT_TRUE(trace_log->IsEnabled()); + const std::vector empty; + trace_log->SetEnabled(CategoryFilter(""), TraceLog::RECORD_UNTIL_FULL); + EXPECT_TRUE(trace_log->IsEnabled()); + trace_log->SetDisabled(); + EXPECT_TRUE(trace_log->IsEnabled()); + trace_log->SetDisabled(); + EXPECT_FALSE(trace_log->IsEnabled()); +} + +TEST_F(TraceEventTestFixture, TraceCategoriesAfterNestedEnable) { + TraceLog* trace_log = TraceLog::GetInstance(); + trace_log->SetEnabled(CategoryFilter("foo,bar"), TraceLog::RECORD_UNTIL_FULL); + EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("foo")); + EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("bar")); + EXPECT_FALSE(*trace_log->GetCategoryGroupEnabled("baz")); + trace_log->SetEnabled(CategoryFilter("foo2"), TraceLog::RECORD_UNTIL_FULL); + EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("foo2")); + EXPECT_FALSE(*trace_log->GetCategoryGroupEnabled("baz")); + // The "" becomes the default catergory set when applied. + trace_log->SetEnabled(CategoryFilter(""), TraceLog::RECORD_UNTIL_FULL); + EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("foo")); + EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("baz")); + EXPECT_STREQ("-*Debug,-*Test", + trace_log->GetCurrentCategoryFilter().ToString().c_str()); + trace_log->SetDisabled(); + trace_log->SetDisabled(); + trace_log->SetDisabled(); + EXPECT_FALSE(*trace_log->GetCategoryGroupEnabled("foo")); + EXPECT_FALSE(*trace_log->GetCategoryGroupEnabled("baz")); + + trace_log->SetEnabled(CategoryFilter("-foo,-bar"), + TraceLog::RECORD_UNTIL_FULL); + EXPECT_FALSE(*trace_log->GetCategoryGroupEnabled("foo")); + EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("baz")); + trace_log->SetEnabled(CategoryFilter("moo"), TraceLog::RECORD_UNTIL_FULL); + EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("baz")); + EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("moo")); + EXPECT_FALSE(*trace_log->GetCategoryGroupEnabled("foo")); + EXPECT_STREQ("-foo,-bar", + trace_log->GetCurrentCategoryFilter().ToString().c_str()); + trace_log->SetDisabled(); + trace_log->SetDisabled(); + + // Make sure disabled categories aren't cleared if we set in the second. + trace_log->SetEnabled(CategoryFilter("disabled-by-default-cc,foo"), + TraceLog::RECORD_UNTIL_FULL); + EXPECT_FALSE(*trace_log->GetCategoryGroupEnabled("bar")); + trace_log->SetEnabled(CategoryFilter("disabled-by-default-gpu"), + TraceLog::RECORD_UNTIL_FULL); + EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("disabled-by-default-cc")); + EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("disabled-by-default-gpu")); + EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("bar")); + EXPECT_STREQ("disabled-by-default-cc,disabled-by-default-gpu", + trace_log->GetCurrentCategoryFilter().ToString().c_str()); + trace_log->SetDisabled(); + trace_log->SetDisabled(); +} + +TEST_F(TraceEventTestFixture, TraceOptionsParsing) { + EXPECT_EQ(TraceLog::RECORD_UNTIL_FULL, + TraceLog::TraceOptionsFromString(std::string())); + + EXPECT_EQ(TraceLog::RECORD_UNTIL_FULL, + TraceLog::TraceOptionsFromString("record-until-full")); + EXPECT_EQ(TraceLog::RECORD_CONTINUOUSLY, + TraceLog::TraceOptionsFromString("record-continuously")); + EXPECT_EQ(TraceLog::RECORD_UNTIL_FULL | TraceLog::ENABLE_SAMPLING, + TraceLog::TraceOptionsFromString("enable-sampling")); + EXPECT_EQ(TraceLog::RECORD_CONTINUOUSLY | TraceLog::ENABLE_SAMPLING, + TraceLog::TraceOptionsFromString( + "record-continuously,enable-sampling")); +} + +TEST_F(TraceEventTestFixture, TraceSampling) { + event_watch_notification_ = 0; + TraceLog::GetInstance()->SetEnabled( + CategoryFilter("*"), + TraceLog::Options(TraceLog::RECORD_UNTIL_FULL | + TraceLog::ENABLE_SAMPLING)); + + WaitableEvent* sampled = new WaitableEvent(false, false); + TraceLog::GetInstance()->InstallWaitableEventForSamplingTesting(sampled); + + TRACE_EVENT_SET_SAMPLING_STATE_FOR_BUCKET(1, "cc", "Stuff"); + sampled->Wait(); + TRACE_EVENT_SET_SAMPLING_STATE_FOR_BUCKET(1, "cc", "Things"); + sampled->Wait(); + + EndTraceAndFlush(); + + // Make sure we hit at least once. + EXPECT_TRUE(FindNamePhase("Stuff", "P")); + EXPECT_TRUE(FindNamePhase("Things", "P")); +} + +TEST_F(TraceEventTestFixture, TraceSamplingScope) { + event_watch_notification_ = 0; + TraceLog::GetInstance()->SetEnabled( + CategoryFilter("*"), + TraceLog::Options(TraceLog::RECORD_UNTIL_FULL | + TraceLog::ENABLE_SAMPLING)); + + WaitableEvent* sampled = new WaitableEvent(false, false); + TraceLog::GetInstance()->InstallWaitableEventForSamplingTesting(sampled); + + TRACE_EVENT_SCOPED_SAMPLING_STATE("AAA", "name"); + sampled->Wait(); + { + EXPECT_STREQ(TRACE_EVENT_GET_SAMPLING_STATE(), "AAA"); + TRACE_EVENT_SCOPED_SAMPLING_STATE("BBB", "name"); + sampled->Wait(); + EXPECT_STREQ(TRACE_EVENT_GET_SAMPLING_STATE(), "BBB"); + } + sampled->Wait(); + { + EXPECT_STREQ(TRACE_EVENT_GET_SAMPLING_STATE(), "AAA"); + TRACE_EVENT_SCOPED_SAMPLING_STATE("CCC", "name"); + sampled->Wait(); + EXPECT_STREQ(TRACE_EVENT_GET_SAMPLING_STATE(), "CCC"); + } + sampled->Wait(); + { + EXPECT_STREQ(TRACE_EVENT_GET_SAMPLING_STATE(), "AAA"); + TRACE_EVENT_SET_SAMPLING_STATE("DDD", "name"); + sampled->Wait(); + EXPECT_STREQ(TRACE_EVENT_GET_SAMPLING_STATE(), "DDD"); + } + sampled->Wait(); + EXPECT_STREQ(TRACE_EVENT_GET_SAMPLING_STATE(), "DDD"); + + EndTraceAndFlush(); +} + +class MyData : public base::debug::ConvertableToTraceFormat { + public: + MyData() {} + virtual ~MyData() {} + + virtual void AppendAsTraceFormat(std::string* out) const OVERRIDE { + out->append("{\"foo\":1}"); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MyData); +}; + +TEST_F(TraceEventTestFixture, ConvertableTypes) { + TraceLog::GetInstance()->SetEnabled(CategoryFilter("*"), + TraceLog::RECORD_UNTIL_FULL); + + scoped_ptr data(new MyData()); + scoped_ptr data1(new MyData()); + scoped_ptr data2(new MyData()); + TRACE_EVENT1("foo", "bar", "data", + data.PassAs()); + TRACE_EVENT2("foo", "baz", + "data1", data1.PassAs(), + "data2", data2.PassAs()); + + + scoped_ptr convertData1(new MyData()); + scoped_ptr convertData2(new MyData()); + TRACE_EVENT2( + "foo", + "string_first", + "str", + "string value 1", + "convert", + convertData1.PassAs()); + TRACE_EVENT2( + "foo", + "string_second", + "convert", + convertData2.PassAs(), + "str", + "string value 2"); + EndTraceAndFlush(); + + // One arg version. + DictionaryValue* dict = FindNamePhase("bar", "B"); + ASSERT_TRUE(dict); + + const DictionaryValue* args_dict = NULL; + dict->GetDictionary("args", &args_dict); + ASSERT_TRUE(args_dict); + + const Value* value = NULL; + const DictionaryValue* convertable_dict = NULL; + EXPECT_TRUE(args_dict->Get("data", &value)); + ASSERT_TRUE(value->GetAsDictionary(&convertable_dict)); + + int foo_val; + EXPECT_TRUE(convertable_dict->GetInteger("foo", &foo_val)); + EXPECT_EQ(1, foo_val); + + // Two arg version. + dict = FindNamePhase("baz", "B"); + ASSERT_TRUE(dict); + + args_dict = NULL; + dict->GetDictionary("args", &args_dict); + ASSERT_TRUE(args_dict); + + value = NULL; + convertable_dict = NULL; + EXPECT_TRUE(args_dict->Get("data1", &value)); + ASSERT_TRUE(value->GetAsDictionary(&convertable_dict)); + + value = NULL; + convertable_dict = NULL; + EXPECT_TRUE(args_dict->Get("data2", &value)); + ASSERT_TRUE(value->GetAsDictionary(&convertable_dict)); + + // Convertable with other types. + dict = FindNamePhase("string_first", "B"); + ASSERT_TRUE(dict); + + args_dict = NULL; + dict->GetDictionary("args", &args_dict); + ASSERT_TRUE(args_dict); + + std::string str_value; + EXPECT_TRUE(args_dict->GetString("str", &str_value)); + EXPECT_STREQ("string value 1", str_value.c_str()); + + value = NULL; + convertable_dict = NULL; + foo_val = 0; + EXPECT_TRUE(args_dict->Get("convert", &value)); + ASSERT_TRUE(value->GetAsDictionary(&convertable_dict)); + EXPECT_TRUE(convertable_dict->GetInteger("foo", &foo_val)); + EXPECT_EQ(1, foo_val); + + dict = FindNamePhase("string_second", "B"); + ASSERT_TRUE(dict); + + args_dict = NULL; + dict->GetDictionary("args", &args_dict); + ASSERT_TRUE(args_dict); + + EXPECT_TRUE(args_dict->GetString("str", &str_value)); + EXPECT_STREQ("string value 2", str_value.c_str()); + + value = NULL; + convertable_dict = NULL; + foo_val = 0; + EXPECT_TRUE(args_dict->Get("convert", &value)); + ASSERT_TRUE(value->GetAsDictionary(&convertable_dict)); + EXPECT_TRUE(convertable_dict->GetInteger("foo", &foo_val)); + EXPECT_EQ(1, foo_val); +} + +class TraceEventCallbackTest : public TraceEventTestFixture { + public: + virtual void SetUp() OVERRIDE { + TraceEventTestFixture::SetUp(); + ASSERT_EQ(NULL, s_instance); + s_instance = this; + } + virtual void TearDown() OVERRIDE { + while (TraceLog::GetInstance()->IsEnabled()) + TraceLog::GetInstance()->SetDisabled(); + ASSERT_TRUE(!!s_instance); + s_instance = NULL; + TraceEventTestFixture::TearDown(); + } + + protected: + std::vector collected_events_; + + static TraceEventCallbackTest* s_instance; + static void Callback(char phase, + const unsigned char* category_enabled, + const char* name, + unsigned long long id, + int num_args, + const char* const arg_names[], + const unsigned char arg_types[], + const unsigned long long arg_values[], + unsigned char flags) { + s_instance->collected_events_.push_back(name); + } +}; + +TraceEventCallbackTest* TraceEventCallbackTest::s_instance; + +TEST_F(TraceEventCallbackTest, TraceEventCallback) { + TRACE_EVENT_INSTANT0("all", "before enable", TRACE_EVENT_SCOPE_THREAD); + TraceLog::GetInstance()->SetEnabled(CategoryFilter("*"), + TraceLog::RECORD_UNTIL_FULL); + TRACE_EVENT_INSTANT0("all", "before callback set", TRACE_EVENT_SCOPE_THREAD); + TraceLog::GetInstance()->SetEventCallback(Callback); + TRACE_EVENT_INSTANT0("all", "event1", TRACE_EVENT_SCOPE_GLOBAL); + TRACE_EVENT_INSTANT0("all", "event2", TRACE_EVENT_SCOPE_GLOBAL); + TraceLog::GetInstance()->SetEventCallback(NULL); + TRACE_EVENT_INSTANT0("all", "after callback removed", + TRACE_EVENT_SCOPE_GLOBAL); + ASSERT_EQ(2u, collected_events_.size()); + EXPECT_EQ("event1", collected_events_[0]); + EXPECT_EQ("event2", collected_events_[1]); +} + +TEST_F(TraceEventCallbackTest, TraceEventCallbackWhileFull) { + TraceLog::GetInstance()->SetEnabled(CategoryFilter("*"), + TraceLog::RECORD_UNTIL_FULL); + do { + TRACE_EVENT_INSTANT0("all", "badger badger", TRACE_EVENT_SCOPE_GLOBAL); + } while ((notifications_received_ & TraceLog::TRACE_BUFFER_FULL) == 0); + TraceLog::GetInstance()->SetEventCallback(Callback); + TRACE_EVENT_INSTANT0("all", "a snake", TRACE_EVENT_SCOPE_GLOBAL); + TraceLog::GetInstance()->SetEventCallback(NULL); + ASSERT_EQ(1u, collected_events_.size()); + EXPECT_EQ("a snake", collected_events_[0]); +} + +// TODO(dsinclair): Continuous Tracing unit test. + +// Test the category filter. +TEST_F(TraceEventTestFixture, CategoryFilter) { + // Using the default filter. + CategoryFilter default_cf = CategoryFilter( + CategoryFilter::kDefaultCategoryFilterString); + std::string category_filter_str = default_cf.ToString(); + EXPECT_STREQ("-*Debug,-*Test", category_filter_str.c_str()); + EXPECT_TRUE(default_cf.IsCategoryGroupEnabled("not-excluded-category")); + EXPECT_FALSE( + default_cf.IsCategoryGroupEnabled("disabled-by-default-category")); + EXPECT_FALSE(default_cf.IsCategoryGroupEnabled("Category1,CategoryDebug")); + EXPECT_FALSE(default_cf.IsCategoryGroupEnabled("CategoryDebug,Category1")); + EXPECT_FALSE(default_cf.IsCategoryGroupEnabled("CategoryTest,Category2")); + + // Make sure that upon an empty string, we fall back to the default filter. + default_cf = CategoryFilter(""); + category_filter_str = default_cf.ToString(); + EXPECT_STREQ("-*Debug,-*Test", category_filter_str.c_str()); + EXPECT_TRUE(default_cf.IsCategoryGroupEnabled("not-excluded-category")); + EXPECT_FALSE(default_cf.IsCategoryGroupEnabled("Category1,CategoryDebug")); + EXPECT_FALSE(default_cf.IsCategoryGroupEnabled("CategoryDebug,Category1")); + EXPECT_FALSE(default_cf.IsCategoryGroupEnabled("CategoryTest,Category2")); + + // Using an arbitrary non-empty filter. + CategoryFilter cf("included,-excluded,inc_pattern*,-exc_pattern*"); + category_filter_str = cf.ToString(); + EXPECT_STREQ("included,inc_pattern*,-excluded,-exc_pattern*", + category_filter_str.c_str()); + EXPECT_TRUE(cf.IsCategoryGroupEnabled("included")); + EXPECT_TRUE(cf.IsCategoryGroupEnabled("inc_pattern_category")); + EXPECT_FALSE(cf.IsCategoryGroupEnabled("exc_pattern_category")); + EXPECT_FALSE(cf.IsCategoryGroupEnabled("excluded")); + EXPECT_FALSE(cf.IsCategoryGroupEnabled("not-excluded-nor-included")); + EXPECT_FALSE(cf.IsCategoryGroupEnabled("Category1,CategoryDebug")); + EXPECT_FALSE(cf.IsCategoryGroupEnabled("CategoryDebug,Category1")); + EXPECT_FALSE(cf.IsCategoryGroupEnabled("CategoryTest,Category2")); + + cf.Merge(default_cf); + category_filter_str = cf.ToString(); + EXPECT_STREQ("-excluded,-exc_pattern*,-*Debug,-*Test", + category_filter_str.c_str()); + cf.Clear(); + + CategoryFilter reconstructed_cf(category_filter_str); + category_filter_str = reconstructed_cf.ToString(); + EXPECT_STREQ("-excluded,-exc_pattern*,-*Debug,-*Test", + category_filter_str.c_str()); + + // One included category. + CategoryFilter one_inc_cf("only_inc_cat"); + category_filter_str = one_inc_cf.ToString(); + EXPECT_STREQ("only_inc_cat", category_filter_str.c_str()); + + // One excluded category. + CategoryFilter one_exc_cf("-only_exc_cat"); + category_filter_str = one_exc_cf.ToString(); + EXPECT_STREQ("-only_exc_cat", category_filter_str.c_str()); + + // Enabling a disabled- category does not require all categories to be traced + // to be included. + CategoryFilter disabled_cat("disabled-by-default-cc,-excluded"); + EXPECT_STREQ("disabled-by-default-cc,-excluded", + disabled_cat.ToString().c_str()); + EXPECT_TRUE(disabled_cat.IsCategoryGroupEnabled("disabled-by-default-cc")); + EXPECT_TRUE(disabled_cat.IsCategoryGroupEnabled("some_other_group")); + EXPECT_FALSE(disabled_cat.IsCategoryGroupEnabled("excluded")); + + // Enabled a disabled- category and also including makes all categories to + // be traced require including. + CategoryFilter disabled_inc_cat("disabled-by-default-cc,included"); + EXPECT_STREQ("included,disabled-by-default-cc", + disabled_inc_cat.ToString().c_str()); + EXPECT_TRUE( + disabled_inc_cat.IsCategoryGroupEnabled("disabled-by-default-cc")); + EXPECT_TRUE(disabled_inc_cat.IsCategoryGroupEnabled("included")); + EXPECT_FALSE(disabled_inc_cat.IsCategoryGroupEnabled("other_included")); + + // Test that IsEmptyOrContainsLeadingOrTrailingWhitespace actually catches + // categories that are explicitly forbiden. + // This method is called in a DCHECK to assert that we don't have these types + // of strings as categories. + EXPECT_TRUE(CategoryFilter::IsEmptyOrContainsLeadingOrTrailingWhitespace( + " bad_category ")); + EXPECT_TRUE(CategoryFilter::IsEmptyOrContainsLeadingOrTrailingWhitespace( + " bad_category")); + EXPECT_TRUE(CategoryFilter::IsEmptyOrContainsLeadingOrTrailingWhitespace( + "bad_category ")); + EXPECT_TRUE(CategoryFilter::IsEmptyOrContainsLeadingOrTrailingWhitespace( + " bad_category")); + EXPECT_TRUE(CategoryFilter::IsEmptyOrContainsLeadingOrTrailingWhitespace( + "bad_category ")); + EXPECT_TRUE(CategoryFilter::IsEmptyOrContainsLeadingOrTrailingWhitespace( + " bad_category ")); + EXPECT_TRUE(CategoryFilter::IsEmptyOrContainsLeadingOrTrailingWhitespace( + "")); + EXPECT_FALSE(CategoryFilter::IsEmptyOrContainsLeadingOrTrailingWhitespace( + "good_category")); +} + +} // namespace debug +} // namespace base diff --git a/base/debug/trace_event_unittest.h b/base/debug/trace_event_unittest.h new file mode 100644 index 0000000000..599fec7dcd --- /dev/null +++ b/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/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/base/debug/trace_event_win.cc b/base/debug/trace_event_win.cc new file mode 100644 index 0000000000..d5a21f4926 --- /dev/null +++ b/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 // 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 >::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::Resurrect(); +} + +} // namespace debug +} // namespace base diff --git a/base/debug/trace_event_win.h b/base/debug/trace_event_win.h new file mode 100644 index 0000000000..2a900bb431 --- /dev/null +++ b/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 + +#include "base/base_export.h" +#include "base/debug/trace_event.h" +#include "base/win/event_trace_provider.h" + +// Fwd. +template +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(); + + 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/base/debug/trace_event_win_unittest.cc b/base/debug/trace_event_win_unittest.cc new file mode 100644 index 0000000000..531c5f7d86 --- /dev/null +++ b/base/debug/trace_event_win_unittest.cc @@ -0,0 +1,319 @@ +// Copyright (c) 2011 The Chromium Authors. All 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 + +#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 // 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 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 { + 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()) + base::DeleteFile(log_file_, false); + + // We want our singleton torn down after each test. + TraceLog::DeleteForTesting(); + } + + 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(&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(kName), + TRACE_EVENT_PHASE_END, + kId, + static_cast(kExtra)); + + ExpectEvent(kTraceEventClass32, + kTraceEventTypeEnd, + kName, strlen(kName), + kId, + kExtra, strlen(kExtra)); + + // std::string extra version. + TraceEventETWProvider::Trace(static_cast(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/base/debug_message.cc b/base/debug_message.cc new file mode 100644 index 0000000000..10f441d315 --- /dev/null +++ b/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 + +// 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/base/deferred_sequenced_task_runner.cc b/base/deferred_sequenced_task_runner.cc new file mode 100644 index 0000000000..c96704c54d --- /dev/null +++ b/base/deferred_sequenced_task_runner.cc @@ -0,0 +1,99 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/deferred_sequenced_task_runner.h" + +#include "base/bind.h" +#include "base/logging.h" + +namespace base { + +DeferredSequencedTaskRunner::DeferredTask::DeferredTask() { +} + +DeferredSequencedTaskRunner::DeferredTask::~DeferredTask() { +} + +DeferredSequencedTaskRunner::DeferredSequencedTaskRunner( + const scoped_refptr& target_task_runner) + : started_(false), + target_task_runner_(target_task_runner) { +} + +DeferredSequencedTaskRunner::~DeferredSequencedTaskRunner() { +} + +bool DeferredSequencedTaskRunner::PostDelayedTask( + const tracked_objects::Location& from_here, + const Closure& task, + TimeDelta delay) { + AutoLock lock(lock_); + if (started_) { + DCHECK(deferred_tasks_queue_.empty()); + return target_task_runner_->PostDelayedTask(from_here, task, delay); + } + + QueueDeferredTask(from_here, task, delay, false /* is_non_nestable */); + return true; +} + +bool DeferredSequencedTaskRunner::RunsTasksOnCurrentThread() const { + return target_task_runner_->RunsTasksOnCurrentThread(); +} + +bool DeferredSequencedTaskRunner::PostNonNestableDelayedTask( + const tracked_objects::Location& from_here, + const Closure& task, + TimeDelta delay) { + AutoLock lock(lock_); + if (started_) { + DCHECK(deferred_tasks_queue_.empty()); + return target_task_runner_->PostNonNestableDelayedTask(from_here, + task, + delay); + } + QueueDeferredTask(from_here, task, delay, true /* is_non_nestable */); + return true; +} + +void DeferredSequencedTaskRunner::QueueDeferredTask( + const tracked_objects::Location& from_here, + const Closure& task, + TimeDelta delay, + bool is_non_nestable) { + DeferredTask deferred_task; + deferred_task.posted_from = from_here; + deferred_task.task = task; + deferred_task.delay = delay; + deferred_task.is_non_nestable = is_non_nestable; + deferred_tasks_queue_.push_back(deferred_task); +} + + +void DeferredSequencedTaskRunner::Start() { + AutoLock lock(lock_); + DCHECK(!started_); + started_ = true; + for (std::vector::iterator i = deferred_tasks_queue_.begin(); + i != deferred_tasks_queue_.end(); + ++i) { + const DeferredTask& task = *i; + if (task.is_non_nestable) { + target_task_runner_->PostNonNestableDelayedTask(task.posted_from, + task.task, + task.delay); + } else { + target_task_runner_->PostDelayedTask(task.posted_from, + task.task, + task.delay); + } + // Replace the i-th element in the |deferred_tasks_queue_| with an empty + // |DelayedTask| to ensure that |task| is destroyed before the next task + // is posted. + *i = DeferredTask(); + } + deferred_tasks_queue_.clear(); +} + +} // namespace base diff --git a/base/deferred_sequenced_task_runner.h b/base/deferred_sequenced_task_runner.h new file mode 100644 index 0000000000..00ab0507de --- /dev/null +++ b/base/deferred_sequenced_task_runner.h @@ -0,0 +1,79 @@ +// 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_DEFERRED_SEQUENCED_TASKRUNNER_H_ +#define BASE_DEFERRED_SEQUENCED_TASKRUNNER_H_ + +#include + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/sequenced_task_runner.h" +#include "base/synchronization/lock.h" +#include "base/time/time.h" +#include "base/tracked_objects.h" + +namespace base { + +// A DeferredSequencedTaskRunner is a subclass of SequencedTaskRunner that +// queues up all requests until the first call to Start() is issued. +class BASE_EXPORT DeferredSequencedTaskRunner : public SequencedTaskRunner { + public: + explicit DeferredSequencedTaskRunner( + const scoped_refptr& target_runner); + + // TaskRunner implementation + virtual bool PostDelayedTask(const tracked_objects::Location& from_here, + const Closure& task, + TimeDelta delay) OVERRIDE; + virtual bool RunsTasksOnCurrentThread() const OVERRIDE; + + // SequencedTaskRunner implementation + virtual bool PostNonNestableDelayedTask( + const tracked_objects::Location& from_here, + const Closure& task, + TimeDelta delay) OVERRIDE; + + // Start the execution - posts all queued tasks to the target executor. The + // deferred tasks are posted with their initial delay, meaning that the task + // execution delay is actually measured from Start. + // Fails when called a second time. + void Start(); + + private: + struct DeferredTask { + DeferredTask(); + ~DeferredTask(); + + tracked_objects::Location posted_from; + Closure task; + // The delay this task was initially posted with. + TimeDelta delay; + bool is_non_nestable; + }; + + virtual ~DeferredSequencedTaskRunner(); + + // Creates a |Task| object and adds it to |deferred_tasks_queue_|. + void QueueDeferredTask(const tracked_objects::Location& from_here, + const Closure& task, + TimeDelta delay, + bool is_non_nestable); + + // // Protects |started_| and |deferred_tasks_queue_|. + mutable Lock lock_; + + bool started_; + const scoped_refptr target_task_runner_; + std::vector deferred_tasks_queue_; + + DISALLOW_COPY_AND_ASSIGN(DeferredSequencedTaskRunner); +}; + +} // namespace base + +#endif // BASE_DEFERRED_SEQUENCED_TASKRUNNER_H_ diff --git a/base/deferred_sequenced_task_runner_unittest.cc b/base/deferred_sequenced_task_runner_unittest.cc new file mode 100644 index 0000000000..81f2a0a00c --- /dev/null +++ b/base/deferred_sequenced_task_runner_unittest.cc @@ -0,0 +1,184 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/deferred_sequenced_task_runner.h" + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/threading/non_thread_safe.h" +#include "base/threading/thread.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class DeferredSequencedTaskRunnerTest : public testing::Test, + public base::NonThreadSafe { + public: + class ExecuteTaskOnDestructor : + public base::RefCounted { + public: + ExecuteTaskOnDestructor( + DeferredSequencedTaskRunnerTest* executor, + int task_id) + : executor_(executor), + task_id_(task_id) { + } + private: + friend class base::RefCounted; + virtual ~ExecuteTaskOnDestructor() { + executor_->ExecuteTask(task_id_); + } + DeferredSequencedTaskRunnerTest* executor_; + int task_id_; + }; + + void ExecuteTask(int task_id) { + base::AutoLock lock(lock_); + executed_task_ids_.push_back(task_id); + } + + void PostExecuteTask(int task_id) { + runner_->PostTask(FROM_HERE, + base::Bind(&DeferredSequencedTaskRunnerTest::ExecuteTask, + base::Unretained(this), + task_id)); + } + + void StartRunner() { + runner_->Start(); + } + + void DoNothing(ExecuteTaskOnDestructor* object) { + } + + protected: + DeferredSequencedTaskRunnerTest() : + loop_(), + runner_( + new base::DeferredSequencedTaskRunner(loop_.message_loop_proxy())) { + } + + base::MessageLoop loop_; + scoped_refptr runner_; + mutable base::Lock lock_; + std::vector executed_task_ids_; +}; + +TEST_F(DeferredSequencedTaskRunnerTest, Stopped) { + PostExecuteTask(1); + loop_.RunUntilIdle(); + EXPECT_THAT(executed_task_ids_, testing::ElementsAre()); +} + +TEST_F(DeferredSequencedTaskRunnerTest, Start) { + StartRunner(); + PostExecuteTask(1); + loop_.RunUntilIdle(); + EXPECT_THAT(executed_task_ids_, testing::ElementsAre(1)); +} + +TEST_F(DeferredSequencedTaskRunnerTest, StartWithMultipleElements) { + StartRunner(); + for (int i = 1; i < 5; ++i) + PostExecuteTask(i); + + loop_.RunUntilIdle(); + EXPECT_THAT(executed_task_ids_, testing::ElementsAre(1, 2, 3, 4)); +} + +TEST_F(DeferredSequencedTaskRunnerTest, DeferredStart) { + PostExecuteTask(1); + loop_.RunUntilIdle(); + EXPECT_THAT(executed_task_ids_, testing::ElementsAre()); + + StartRunner(); + loop_.RunUntilIdle(); + EXPECT_THAT(executed_task_ids_, testing::ElementsAre(1)); + + PostExecuteTask(2); + loop_.RunUntilIdle(); + EXPECT_THAT(executed_task_ids_, testing::ElementsAre(1, 2)); +} + +TEST_F(DeferredSequencedTaskRunnerTest, DeferredStartWithMultipleElements) { + for (int i = 1; i < 5; ++i) + PostExecuteTask(i); + loop_.RunUntilIdle(); + EXPECT_THAT(executed_task_ids_, testing::ElementsAre()); + + StartRunner(); + for (int i = 5; i < 9; ++i) + PostExecuteTask(i); + loop_.RunUntilIdle(); + EXPECT_THAT(executed_task_ids_, testing::ElementsAre(1, 2, 3, 4, 5, 6, 7, 8)); +} + +TEST_F(DeferredSequencedTaskRunnerTest, DeferredStartWithMultipleThreads) { + { + base::Thread thread1("DeferredSequencedTaskRunnerTestThread1"); + base::Thread thread2("DeferredSequencedTaskRunnerTestThread2"); + thread1.Start(); + thread2.Start(); + for (int i = 0; i < 5; ++i) { + thread1.message_loop()->PostTask( + FROM_HERE, + base::Bind(&DeferredSequencedTaskRunnerTest::PostExecuteTask, + base::Unretained(this), + 2 * i)); + thread2.message_loop()->PostTask( + FROM_HERE, + base::Bind(&DeferredSequencedTaskRunnerTest::PostExecuteTask, + base::Unretained(this), + 2 * i + 1)); + if (i == 2) { + thread1.message_loop()->PostTask( + FROM_HERE, + base::Bind(&DeferredSequencedTaskRunnerTest::StartRunner, + base::Unretained(this))); + } + } + } + + loop_.RunUntilIdle(); + EXPECT_THAT(executed_task_ids_, + testing::WhenSorted(testing::ElementsAre(0, 1, 2, 3, 4, 5, 6, 7, 8, 9))); +} + +TEST_F(DeferredSequencedTaskRunnerTest, ObjectDestructionOrder) { + { + base::Thread thread("DeferredSequencedTaskRunnerTestThread"); + thread.Start(); + runner_ = + new base::DeferredSequencedTaskRunner(thread.message_loop_proxy()); + for (int i = 0; i < 5; ++i) { + { + // Use a block to ensure that no reference to |short_lived_object| + // is kept on the main thread after it is posted to |runner_|. + scoped_refptr short_lived_object = + new ExecuteTaskOnDestructor(this, 2 * i); + runner_->PostTask( + FROM_HERE, + base::Bind(&DeferredSequencedTaskRunnerTest::DoNothing, + base::Unretained(this), + short_lived_object)); + } + // |short_lived_object| with id |2 * i| should be destroyed before the + // task |2 * i + 1| is executed. + PostExecuteTask(2 * i + 1); + } + StartRunner(); + } + + // All |short_lived_object| with id |2 * i| are destroyed before the task + // |2 * i + 1| is executed. + EXPECT_THAT(executed_task_ids_, + testing::ElementsAre(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)); +} + +} // namespace diff --git a/base/environment.cc b/base/environment.cc new file mode 100644 index 0000000000..c1f2008b4c --- /dev/null +++ b/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 +#elif defined(OS_WIN) +#include +#endif + +#include "base/strings/string_util.h" + +#if defined(OS_WIN) +#include "base/memory/scoped_ptr.h" +#include "base/strings/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_ptr 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/base/environment.h b/base/environment.h new file mode 100644 index 0000000000..5160ff2469 --- /dev/null +++ b/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 + +#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/base/environment_unittest.cc b/base/environment_unittest.cc new file mode 100644 index 0000000000..b6654c99f4 --- /dev/null +++ b/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 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 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 env(base::Environment::Create()); + EXPECT_TRUE(env->HasVar("PATH")); +} + +TEST_F(EnvironmentTest, SetVar) { + scoped_ptr 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 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/base/event_recorder.h b/base/event_recorder.h new file mode 100644 index 0000000000..bff87ed494 --- /dev/null +++ b/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 +#include +#include +#endif + +namespace base { + +class FilePath; + +// 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. + 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/base/event_recorder_stubs.cc b/base/event_recorder_stubs.cc new file mode 100644 index 0000000000..91f2e072fc --- /dev/null +++ b/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/base/event_recorder_win.cc b/base/event_recorder_win.cc new file mode 100644 index 0000000000..11bf0f0cb1 --- /dev/null +++ b/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 +#include +#include + +#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(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(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/base/event_types.h b/base/event_types.h new file mode 100644 index 0000000000..af586e46ec --- /dev/null +++ b/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 +#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/base/file_descriptor_posix.h b/base/file_descriptor_posix.h new file mode 100644 index 0000000000..abc07893fa --- /dev/null +++ b/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/base/file_util.cc b/base/file_util.cc new file mode 100644 index 0000000000..2623b64391 --- /dev/null +++ b/base/file_util.cc @@ -0,0 +1,264 @@ +// Copyright (c) 2012 The Chromium Authors. All 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 +#endif +#include + +#include + +#include "base/files/file_enumerator.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/strings/string_piece.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" + +namespace base { + +namespace { + +const FilePath::CharType kExtensionSeparator = FILE_PATH_LITERAL('.'); + +// The maximum number of 'uniquified' files we will try to create. +// This is used when the filename we're trying to download is already in use, +// so we create a new unique filename by appending " (nnn)" before the +// extension, where 1 <= nnn <= kMaxUniqueFiles. +// Also used by code that cleans up said files. +static const int kMaxUniqueFiles = 100; + +} // namespace + +bool g_bug108724_debug = false; + +int64 ComputeDirectorySize(const FilePath& root_path) { + int64 running_size = 0; + FileEnumerator file_iter(root_path, true, FileEnumerator::FILES); + while (!file_iter.Next().empty()) + running_size += file_iter.GetInfo().GetSize(); + return running_size; +} + +bool Move(const FilePath& from_path, const FilePath& to_path) { + if (from_path.ReferencesParent() || to_path.ReferencesParent()) + return false; + return internal::MoveUnsafe(from_path, to_path); +} + +bool CopyFile(const FilePath& from_path, const FilePath& to_path) { + if (from_path.ReferencesParent() || to_path.ReferencesParent()) + return false; + return internal::CopyFileUnsafe(from_path, to_path); +} + +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; +} + +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; +} + +} // namespace base + +// ----------------------------------------------------------------------------- + +namespace file_util { + +using base::FileEnumerator; +using base::FilePath; +using base::kExtensionSeparator; +using base::kMaxUniqueFiles; + +bool ReadFileToString(const FilePath& path, std::string* contents) { + if (path.ReferencesParent()) + return false; + FILE* file = OpenFile(path, "rb"); + if (!file) { + return false; + } + + char buf[1 << 16]; + size_t len; + while ((len = fread(buf, 1, sizeof(buf), file)) > 0) { + if (contents) + contents->append(buf, len); + } + CloseFile(file); + + return true; +} + +bool IsDirectoryEmpty(const FilePath& dir_path) { + FileEnumerator files(dir_path, false, + FileEnumerator::FILES | FileEnumerator::DIRECTORIES); + if (files.Next().empty()) + return true; + return false; +} + +FILE* CreateAndOpenTemporaryFile(FilePath* path) { + FilePath directory; + if (!GetTempDir(&directory)) + return NULL; + + return CreateAndOpenTemporaryFileInDir(directory, path); +} + +bool CreateDirectory(const base::FilePath& full_path) { + return CreateDirectoryAndGetError(full_path, NULL); +} + +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 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(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; +} + +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(base::StringPrintf(" (%d)", count)); + if (!PathExists(new_path) && + (!have_suffix || !PathExists(FilePath(new_path.value() + suffix)))) { + return count; + } + } + + return -1; +} + +} // namespace file_util diff --git a/base/file_util.h b/base/file_util.h new file mode 100644 index 0000000000..9673a6111f --- /dev/null +++ b/base/file_util.h @@ -0,0 +1,464 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// 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 +#elif defined(OS_POSIX) +#include +#include +#endif + +#include + +#include +#include +#include + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "base/memory/scoped_ptr.h" +#include "base/platform_file.h" +#include "base/strings/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; + +extern bool g_bug108724_debug; + +//----------------------------------------------------------------------------- +// Functions that involve filesystem access or modification: + +// Returns an absolute version of a relative path. Returns an empty path on +// error. On POSIX, this function fails if the path does not exist. This +// function can result in I/O so it can be slow. +BASE_EXPORT FilePath MakeAbsoluteFilePath(const FilePath& input); + +// 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); + +// 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. It is considered successful +// to attempt to delete a file that does not exist. +// +// 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 DeleteFile(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 DeleteFileAfterReboot(const FilePath& path); +#endif + +// 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. +// This function fails if either path contains traversal components ('..'). +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, leaving *error unchanged. +// Returns false on failure and sets *error appropriately, if it is non-NULL. +BASE_EXPORT bool ReplaceFile(const FilePath& from_path, + const FilePath& to_path, + PlatformFileError* error); + +// Copies a single file. Use CopyDirectory to copy directories. +// This function fails if either path contains traversal components ('..'). +BASE_EXPORT bool CopyFile(const FilePath& from_path, const FilePath& to_path); + +// 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. Wildcards on the names are not supported. +// +// 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); + +// 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); + +// 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); + +// 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); + +} // namespace base + +// ----------------------------------------------------------------------------- + +namespace file_util { + +// 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 base::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 successfully 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 base::FilePath& target, + const base::FilePath& symlink); + +// Reads the given |symlink| and returns where it points to in |target|. +// Returns false upon failure. +BASE_EXPORT bool ReadSymbolicLink(const base::FilePath& symlink, + base::FilePath* target); + +// 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 base::FilePath& path, + int* mode); +// Sets the permission of the given |path|. If |path| is symbolic link, sets +// the permission of a file which the symlink points to. +BASE_EXPORT bool SetPosixFilePermissions(const base::FilePath& path, + int mode); +#endif // defined(OS_POSIX) + +// Return true if the given directory is empty +BASE_EXPORT bool IsDirectoryEmpty(const base::FilePath& dir_path); + +// Get the temporary directory provided by the system. +// WARNING: DON'T USE THIS. If you want to create a temporary file, use one of +// the functions below. +BASE_EXPORT bool GetTempDir(base::FilePath* path); +// Get a temporary directory for shared memory files. +// Only useful on POSIX; redirects to GetTempDir() on Windows. +BASE_EXPORT bool GetShmemTempDir(base::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 base::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(base::FilePath* path); + +// Same as CreateTemporaryFile but the file is created in |dir|. +BASE_EXPORT bool CreateTemporaryFileInDir(const base::FilePath& dir, + base::FilePath* temp_file); + +// Create and open a temporary file. File is opened for read/write. +// The full path is placed in |path|. +// Returns a handle to the opened file or NULL if an error occurred. +BASE_EXPORT FILE* CreateAndOpenTemporaryFile(base::FilePath* path); +// Like above but for shmem files. Only useful for POSIX. +// The executable flag says the file needs to support using +// mprotect with PROT_EXEC after mapping. +BASE_EXPORT FILE* CreateAndOpenTemporaryShmemFile(base::FilePath* path, + bool executable); +// Similar to CreateAndOpenTemporaryFile, but the file is created in |dir|. +BASE_EXPORT FILE* CreateAndOpenTemporaryFileInDir(const base::FilePath& dir, + base::FilePath* path); + +// Create a new directory. If prefix is provided, the new directory name is in +// the format of prefixyyyy. +// NOTE: prefix is ignored in the POSIX implementation. +// If success, return true and output the full path of the directory created. +BASE_EXPORT bool CreateNewTempDirectory( + const base::FilePath::StringType& prefix, + base::FilePath* new_temp_path); + +// Create a directory within another directory. +// Extra characters will be appended to |prefix| to ensure that the +// new directory does not have the same name as an existing directory. +BASE_EXPORT bool CreateTemporaryDirInDir( + const base::FilePath& base_dir, + const base::FilePath::StringType& prefix, + base::FilePath* new_dir); + +// Creates a directory, as well as creating any parent directories, if they +// don't exist. Returns 'true' on successful creation, or if the directory +// already exists. The directory is only readable by the current user. +// Returns true on success, leaving *error unchanged. +// Returns false on failure and sets *error appropriately, if it is non-NULL. +BASE_EXPORT bool CreateDirectoryAndGetError(const base::FilePath& full_path, + base::PlatformFileError* error); + +// Backward-compatible convenience method for the above. +BASE_EXPORT bool CreateDirectory(const base::FilePath& full_path); + +// Returns the file size. Returns true on success. +BASE_EXPORT bool GetFileSize(const base::FilePath& file_path, int64* file_size); + +// Sets |real_path| to |path| with symbolic links and junctions expanded. +// On windows, make sure the path starts with a lettered drive. +// |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 base::FilePath& path, + base::FilePath* real_path); + +#if defined(OS_WIN) + +// Given a path in NT native form ("\Device\HarddiskVolumeXX\..."), +// return in |drive_letter_path| the equivalent path that starts with +// a drive letter ("C:\..."). Return false if no such path exists. +BASE_EXPORT bool DevicePathToDriveLetterPath(const base::FilePath& device_path, + base::FilePath* drive_letter_path); + +// Given an existing file in |path|, set |real_path| to the path +// in native NT format, of the form "\Device\HarddiskVolumeXX\..". +// Returns false if the path can not be found. Empty files cannot +// be resolved with this function. +BASE_EXPORT bool NormalizeToNativeFilePath(const base::FilePath& path, + base::FilePath* nt_path); +#endif + +// This function will return if the given file is a symlink or not. +BASE_EXPORT bool IsLink(const base::FilePath& file_path); + +// Returns information about the given file path. +BASE_EXPORT bool GetFileInfo(const base::FilePath& file_path, + base::PlatformFileInfo* info); + +// Sets the time of the last access and the time of the last modification. +BASE_EXPORT bool TouchFile(const base::FilePath& path, + const base::Time& last_accessed, + const base::Time& last_modified); + +// Set the time of the last modification. Useful for unit tests. +BASE_EXPORT bool SetLastModifiedTime(const base::FilePath& path, + const base::Time& last_modified); + +#if defined(OS_POSIX) +// Store inode number of |path| in |inode|. Return true on success. +BASE_EXPORT bool GetInode(const base::FilePath& path, ino_t* inode); +#endif + +// Wrapper for fopen-like calls. Returns non-NULL FILE* on success. +BASE_EXPORT FILE* OpenFile(const base::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); + +// 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 base::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 base::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 base::FilePath& filename, + const char* data, int size); + +// Gets the current working directory for the process. +BASE_EXPORT bool GetCurrentDirectory(base::FilePath* path); + +// Sets the current working directory for the process. +BASE_EXPORT bool SetCurrentDirectory(const base::FilePath& path); + +// 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 base::FilePath& path, + const base::FilePath::StringType& suffix); + +#if defined(OS_POSIX) +// Creates a directory with a guaranteed unique name based on |path|, returning +// the pathname if successful, or an empty path if there was an error creating +// the directory. Does not create parent directories. +BASE_EXPORT base::FilePath MakeUniqueDirectory(const base::FilePath& path); +#endif + +#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 member 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 base::FilePath& base, + const base::FilePath& path, + uid_t owner_uid, + const std::set& 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 base::FilePath& path); +#endif // defined(OS_MACOSX) && !defined(OS_IOS) + +// Returns the maximum length of path component on the volume containing +// the directory |path|, in the number of FilePath::CharType, or -1 on failure. +BASE_EXPORT int GetMaximumPathComponentLength(const base::FilePath& path); + +// 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 ScopedFILE; + +#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 ScopedFD; +#endif // OS_POSIX + +#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 base::FilePath& path, + FileSystemType* type); +#endif + +} // namespace file_util + +// Internal -------------------------------------------------------------------- + +namespace base { +namespace internal { + +// Same as Move but allows paths with traversal components. +// Use only with extreme care. +BASE_EXPORT bool MoveUnsafe(const FilePath& from_path, + const FilePath& to_path); + +// Same as CopyFile but allows paths with traversal components. +// Use only with extreme care. +BASE_EXPORT bool CopyFileUnsafe(const FilePath& from_path, + const FilePath& to_path); + +#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 function is not transactional. +BASE_EXPORT bool CopyAndDeleteDirectory(const FilePath& from_path, + const FilePath& to_path); +#endif // defined(OS_WIN) + +} // namespace internal +} // namespace base + +#endif // BASE_FILE_UTIL_H_ diff --git a/base/file_util_android.cc b/base/file_util_android.cc new file mode 100644 index 0000000000..6ac9def8c1 --- /dev/null +++ b/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/files/file_path.h" +#include "base/path_service.h" + +namespace file_util { + +bool GetShmemTempDir(base::FilePath* path, bool executable) { + return PathService::Get(base::DIR_CACHE, path); +} + +} // namespace file_util diff --git a/base/file_util_linux.cc b/base/file_util_linux.cc new file mode 100644 index 0000000000..e4f0e28b12 --- /dev/null +++ b/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/files/file_path.h" + +#include +#include + +namespace file_util { + +bool GetFileSystemType(const base::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/base/file_util_mac.mm b/base/file_util_mac.mm new file mode 100644 index 0000000000..9d9ac3d99b --- /dev/null +++ b/base/file_util_mac.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. + +#include "base/file_util.h" + +#import +#include + +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "base/mac/foundation_util.h" +#include "base/strings/string_util.h" +#include "base/threading/thread_restrictions.h" + +namespace base { +namespace internal { + +bool CopyFileUnsafe(const FilePath& from_path, const FilePath& to_path) { + ThreadRestrictions::AssertIOAllowed(); + return (copyfile(from_path.value().c_str(), + to_path.value().c_str(), NULL, COPYFILE_ALL) == 0); +} + +} // namespace internal +} // namepsace base + +namespace file_util { + +bool GetTempDir(base::FilePath* path) { + NSString* tmp = NSTemporaryDirectory(); + if (tmp == nil) + return false; + *path = base::mac::NSStringToFilePath(tmp); + return true; +} + +bool GetShmemTempDir(base::FilePath* path, bool executable) { + return GetTempDir(path); +} + +} // namespace diff --git a/base/file_util_posix.cc b/base/file_util_posix.cc new file mode 100644 index 0000000000..762700ae42 --- /dev/null +++ b/base/file_util_posix.cc @@ -0,0 +1,958 @@ +// Copyright (c) 2012 The Chromium Authors. All 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(OS_MACOSX) +#include +#include "base/mac/foundation_util.h" +#elif !defined(OS_ANDROID) +#include +#endif + +#include + +#include "base/basictypes.h" +#include "base/files/file_enumerator.h" +#include "base/files/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/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/sys_string_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/thread_restrictions.h" +#include "base/time/time.h" + +#if defined(OS_ANDROID) +#include "base/os_compat_android.h" +#endif + +#if !defined(OS_IOS) +#include +#endif + +#if defined(OS_CHROMEOS) +#include "base/chromeos/chromeos_version.h" +#endif + +namespace base { + +namespace { + +#if defined(OS_BSD) || defined(OS_MACOSX) +typedef struct stat stat_wrapper_t; +static int CallStat(const char *path, stat_wrapper_t *sb) { + ThreadRestrictions::AssertIOAllowed(); + return stat(path, sb); +} +static int CallLstat(const char *path, stat_wrapper_t *sb) { + ThreadRestrictions::AssertIOAllowed(); + return lstat(path, sb); +} +#else +typedef struct stat64 stat_wrapper_t; +static int CallStat(const char *path, stat_wrapper_t *sb) { + ThreadRestrictions::AssertIOAllowed(); + return stat64(path, sb); +} +static int CallLstat(const char *path, stat_wrapper_t *sb) { + ThreadRestrictions::AssertIOAllowed(); + return lstat64(path, sb); +} +#endif + +// Helper for NormalizeFilePath(), defined below. +bool RealPath(const FilePath& path, FilePath* real_path) { + ThreadRestrictions::AssertIOAllowed(); // For realpath(). + FilePath::CharType buf[PATH_MAX]; + if (!realpath(path.value().c_str(), buf)) + return false; + + *real_path = FilePath(buf); + return true; +} + +// Helper for VerifyPathControlledByUser. +bool VerifySpecificPathControlledByUser(const FilePath& path, + uid_t owner_uid, + const std::set& 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; +} + +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 +} + +} // namespace + +FilePath MakeAbsoluteFilePath(const FilePath& input) { + ThreadRestrictions::AssertIOAllowed(); + char full_path[PATH_MAX]; + if (realpath(input.value().c_str(), full_path) == NULL) + return FilePath(); + return FilePath(full_path); +} + +// 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 DeleteFile(const FilePath& path, bool recursive) { + 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 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()) { + if (traversal.GetInfo().IsDirectory()) + 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 ReplaceFile(const FilePath& from_path, + const FilePath& to_path, + PlatformFileError* error) { + ThreadRestrictions::AssertIOAllowed(); + if (rename(from_path.value().c_str(), to_path.value().c_str()) == 0) + return true; + if (error) + *error = ErrnoToPlatformFileError(errno); + return false; +} + +bool CopyDirectory(const FilePath& from_path, + const FilePath& to_path, + bool recursive) { + 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 (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)) { + real_to_path = MakeAbsoluteFilePath(real_to_path); + if (real_to_path.empty()) + return false; + } else { + real_to_path = MakeAbsoluteFilePath(real_to_path.DirName()); + if (real_to_path.empty()) + return false; + } + FilePath real_from_path = MakeAbsoluteFilePath(from_path); + if (real_from_path.empty()) + 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|. + struct stat from_stat; + FilePath current = from_path; + if (stat(from_path.value().c_str(), &from_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(from_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(from_stat.st_mode)) { + if (mkdir(target_path.value().c_str(), from_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(from_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(); + if (!current.empty()) + from_stat = traversal.GetInfo().stat(); + } + + return success; +} + +bool PathExists(const FilePath& path) { + ThreadRestrictions::AssertIOAllowed(); + return access(path.value().c_str(), F_OK) == 0; +} + +bool PathIsWritable(const FilePath& path) { + ThreadRestrictions::AssertIOAllowed(); + return access(path.value().c_str(), W_OK) == 0; +} + +bool DirectoryExists(const FilePath& path) { + 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; +} + +} // namespace base + +// ----------------------------------------------------------------------------- + +namespace file_util { + +using base::stat_wrapper_t; +using base::CallStat; +using base::CallLstat; +using base::DirectoryExists; +using base::FileEnumerator; +using base::FilePath; +using base::MakeAbsoluteFilePath; +using base::RealPath; +using base::VerifySpecificPathControlledByUser; + +bool ReadFromFD(int fd, char* buffer, size_t bytes) { + size_t total_read = 0; + while (total_read < bytes) { + 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(base::TempFileName()); + const std::string& tmpdir_string = path->value(); + // this should be OK since mkstemp just replaces characters in place + char* buffer = const_cast(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; +} + +FILE* CreateAndOpenTemporaryShmemFile(FilePath* path, bool executable) { + FilePath directory; + if (!GetShmemTempDir(&directory, executable)) + return NULL; + + return CreateAndOpenTemporaryFileInDir(directory, path); +} + +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(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, base::TempFileName(), + new_temp_path); +} + +bool CreateDirectoryAndGetError(const FilePath& full_path, + base::PlatformFileError* error) { + base::ThreadRestrictions::AssertIOAllowed(); // For call to mkdir(). + std::vector 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::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. + int saved_errno = errno; + if (!DirectoryExists(*i)) { + if (error) + *error = base::ErrnoToPlatformFileError(saved_errno); + return false; + } + } + return true; +} + +base::FilePath MakeUniqueDirectory(const base::FilePath& path) { + const int kMaxAttempts = 20; + for (int attempts = 0; attempts < kMaxAttempts; attempts++) { + int uniquifier = + GetUniquePathNumber(path, base::FilePath::StringType()); + if (uniquifier < 0) + break; + base::FilePath test_path = (uniquifier == 0) ? path : + path.InsertBeforeExtensionASCII( + base::StringPrintf(" (%d)", uniquifier)); + if (mkdir(test_path.value().c_str(), 0777) == 0) + return test_path; + else if (errno != EEXIST) + break; + } + return base::FilePath(); +} + +// 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; +#if defined(OS_MACOSX) + results->last_modified = base::Time::FromTimeSpec(file_info.st_mtimespec); + results->last_accessed = base::Time::FromTimeSpec(file_info.st_atimespec); + results->creation_time = base::Time::FromTimeSpec(file_info.st_ctimespec); +#elif defined(OS_ANDROID) + results->last_modified = base::Time::FromTimeT(file_info.st_mtime); + results->last_accessed = base::Time::FromTimeT(file_info.st_atime); + results->creation_time = base::Time::FromTimeT(file_info.st_ctime); +#else + results->last_modified = base::Time::FromTimeSpec(file_info.st_mtim); + results->last_accessed = base::Time::FromTimeSpec(file_info.st_atim); + results->creation_time = base::Time::FromTimeSpec(file_info.st_ctim); +#endif + return true; +} + +bool GetInode(const FilePath& path, ino_t* inode) { + base::ThreadRestrictions::AssertIOAllowed(); // For call to stat(). + struct stat buffer; + int result = stat(path.value().c_str(), &buffer); + if (result < 0) + return false; + + *inode = buffer.st_ino; + return true; +} + +FILE* OpenFile(const std::string& filename, const char* mode) { + return OpenFile(FilePath(filename), mode); +} + +FILE* OpenFile(const FilePath& filename, const char* mode) { + base::ThreadRestrictions::AssertIOAllowed(); + 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; +} + +bool NormalizeFilePath(const FilePath& path, FilePath* normalized_path) { + FilePath real_path_result; + if (!RealPath(path, &real_path_result)) + return false; + + // To be consistant with windows, fail if |real_path_result| is a + // directory. + stat_wrapper_t file_info; + if (CallStat(real_path_result.value().c_str(), &file_info) != 0 || + S_ISDIR(file_info.st_mode)) + return false; + + *normalized_path = real_path_result; + return true; +} + +#if !defined(OS_MACOSX) +bool GetTempDir(FilePath* path) { + const char* tmp = getenv("TMPDIR"); + if (tmp) + *path = FilePath(tmp); + else +#if defined(OS_ANDROID) + return PathService::Get(base::DIR_CACHE, path); +#else + *path = FilePath("/tmp"); +#endif + return true; +} + +#if !defined(OS_ANDROID) + +#if defined(OS_LINUX) +// Determine if /dev/shm files can be mapped and then mprotect'd PROT_EXEC. +// This depends on the mount options used for /dev/shm, which vary among +// different Linux distributions and possibly local configuration. It also +// depends on details of kernel--ChromeOS uses the noexec option for /dev/shm +// but its kernel allows mprotect with PROT_EXEC anyway. + +namespace { + +bool DetermineDevShmExecutable() { + bool result = false; + FilePath path; + int fd = CreateAndOpenFdForTemporaryFile(FilePath("/dev/shm"), &path); + if (fd >= 0) { + ScopedFD shm_fd_closer(&fd); + DeleteFile(path, false); + long sysconf_result = sysconf(_SC_PAGESIZE); + CHECK_GE(sysconf_result, 0); + size_t pagesize = static_cast(sysconf_result); + CHECK_GE(sizeof(pagesize), sizeof(sysconf_result)); + void *mapping = mmap(NULL, pagesize, PROT_READ, MAP_SHARED, fd, 0); + if (mapping != MAP_FAILED) { + if (mprotect(mapping, pagesize, PROT_READ | PROT_EXEC) == 0) + result = true; + munmap(mapping, pagesize); + } + } + return result; +} + +}; // namespace +#endif // defined(OS_LINUX) + +bool GetShmemTempDir(FilePath* path, bool executable) { +#if defined(OS_LINUX) + bool use_dev_shm = true; + if (executable) { + static const bool s_dev_shm_executable = DetermineDevShmExecutable(); + use_dev_shm = s_dev_shm_executable; + } + if (use_dev_shm) { + *path = FilePath("/dev/shm"); + return true; + } +#endif + return GetTempDir(path); +} +#endif // !defined(OS_ANDROID) + +FilePath GetHomeDir() { +#if defined(OS_CHROMEOS) + if (base::chromeos::IsRunningOnChromeOS()) + return FilePath("/home/chronos/user"); +#endif + + const char* home_dir = getenv("HOME"); + if (home_dir && home_dir[0]) + return FilePath(home_dir); + +#if defined(OS_ANDROID) + DLOG(WARNING) << "OS_ANDROID: Home directory lookup not yet implemented."; +#else + // g_get_home_dir calls getpwent, which can fall through to LDAP calls. + base::ThreadRestrictions::AssertIOAllowed(); + + home_dir = g_get_home_dir(); + if (home_dir && home_dir[0]) + return FilePath(home_dir); +#endif + + FilePath rv; + if (file_util::GetTempDir(&rv)) + return rv; + + // Last resort. + return FilePath("/tmp"); +} +#endif // !defined(OS_MACOSX) + +bool VerifyPathControlledByUser(const FilePath& base, + const FilePath& path, + uid_t owner_uid, + const std::set& 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 base_components; + std::vector path_components; + + base.GetComponents(&base_components); + path.GetComponents(&path_components); + + std::vector::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 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) + +int GetMaximumPathComponentLength(const FilePath& path) { + base::ThreadRestrictions::AssertIOAllowed(); + return pathconf(path.value().c_str(), _PC_NAME_MAX); +} + +} // namespace file_util + +namespace base { +namespace internal { + +bool MoveUnsafe(const FilePath& from_path, const FilePath& to_path) { + 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; + + DeleteFile(from_path, true); + return true; +} + +#if !defined(OS_MACOSX) +// Mac has its own implementation, this is for all other Posix systems. +bool CopyFileUnsafe(const FilePath& from_path, const FilePath& to_path) { + 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 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) + +} // namespace internal +} // namespace base diff --git a/base/file_util_unittest.cc b/base/file_util_unittest.cc new file mode 100644 index 0000000000..787b6d50ba --- /dev/null +++ b/base/file_util_unittest.cc @@ -0,0 +1,2426 @@ +// Copyright (c) 2012 The Chromium Authors. 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 +#include +#include +#include +#include +#endif + +#include +#include +#include + +#include "base/base_paths.h" +#include "base/file_util.h" +#include "base/files/file_enumerator.h" +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/path_service.h" +#include "base/strings/utf_string_conversions.h" +#include "base/test/test_file_util.h" +#include "base/threading/platform_thread.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" + +#if defined(OS_WIN) +#include "base/win/scoped_handle.h" +#include "base/win/windows_version.h" +#endif + +// This macro helps avoid wrapped lines in the test structs. +#define FPL(x) FILE_PATH_LITERAL(x) + +using base::DirectoryExists; +using base::FileEnumerator; +using base::FilePath; +using base::PathIsWritable; +using base::TextContentsEqual; + +namespace { + +// 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(wcslen(target)) * sizeof(target[0]); + char buffer[2000] = {0}; + DWORD returned; + + REPARSE_DATA_BUFFER* data = reinterpret_cast(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; +} + +// Manages a reparse point for a test. +class ReparsePoint { + public: + // Creates a reparse point from |source| (an empty directory) to |target|. + ReparsePoint(const FilePath& source, const FilePath& target) { + dir_.Set( + ::CreateFile(source.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)); + created_ = dir_.IsValid() && SetReparsePoint(dir_, target); + } + + ~ReparsePoint() { + if (created_) + DeleteReparsePoint(dir_); + } + + bool IsValid() { return created_; } + + private: + base::win::ScopedHandle dir_; + bool created_; + DISALLOW_COPY_AND_ASSIGN(ReparsePoint); +}; + +#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 = + FileEnumerator::FILES | 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(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(files_.size()); + } + + private: + std::set files_; +}; + +// Simple function to dump some text into a new file. +void CreateTextFile(const FilePath& filename, + const std::wstring& contents) { + std::wofstream file; + file.open(filename.value().c_str()); + ASSERT_TRUE(file.is_open()); + file << contents; + file.close(); +} + +// Simple function to take out some text from a file. +std::wstring ReadTextFile(const FilePath& filename) { + wchar_t contents[64]; + std::wifstream file; + file.open(filename.value().c_str()); + EXPECT_TRUE(file.is_open()); + file.getline(contents, arraysize(contents)); + file.close(); + 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 + +const struct append_case { + const wchar_t* path; + const wchar_t* ending; + const wchar_t* result; +} append_cases[] = { +#if defined(OS_WIN) + {L"c:\\colon\\backslash", L"path", L"c:\\colon\\backslash\\path"}, + {L"c:\\colon\\backslash\\", L"path", L"c:\\colon\\backslash\\path"}, + {L"c:\\colon\\backslash\\\\", L"path", L"c:\\colon\\backslash\\\\path"}, + {L"c:\\colon\\backslash\\", L"", L"c:\\colon\\backslash\\"}, + {L"c:\\colon\\backslash", L"", L"c:\\colon\\backslash\\"}, + {L"", L"path", L"\\path"}, + {L"", L"", L"\\"}, +#elif defined(OS_POSIX) + {L"/foo/bar", L"path", L"/foo/bar/path"}, + {L"/foo/bar/", L"path", L"/foo/bar/path"}, + {L"/foo/bar//", L"path", L"/foo/bar//path"}, + {L"/foo/bar/", L"", L"/foo/bar/"}, + {L"/foo/bar", L"", L"/foo/bar/"}, + {L"", L"path", L"/path"}, + {L"", L"", L"/"}, +#endif +}; + +static const struct filename_case { + const wchar_t* path; + const wchar_t* filename; +} filename_cases[] = { +#if defined(OS_WIN) + {L"c:\\colon\\backslash", L"backslash"}, + {L"c:\\colon\\backslash\\", L""}, + {L"\\\\filename.exe", L"filename.exe"}, + {L"filename.exe", L"filename.exe"}, + {L"", L""}, + {L"\\\\\\", L""}, + {L"c:/colon/backslash", L"backslash"}, + {L"c:/colon/backslash/", L""}, + {L"//////", L""}, + {L"///filename.exe", L"filename.exe"}, +#elif defined(OS_POSIX) + {L"/foo/bar", L"bar"}, + {L"/foo/bar/", L""}, + {L"/filename.exe", L"filename.exe"}, + {L"filename.exe", L"filename.exe"}, + {L"", L""}, + {L"/", L""}, +#endif +}; + +// Test finding the file type from a path name +static const struct extension_case { + const wchar_t* path; + const wchar_t* extension; +} extension_cases[] = { +#if defined(OS_WIN) + {L"C:\\colon\\backslash\\filename.extension", L"extension"}, + {L"C:\\colon\\backslash\\filename.", L""}, + {L"C:\\colon\\backslash\\filename", L""}, + {L"C:\\colon\\backslash\\", L""}, + {L"C:\\colon\\backslash.\\", L""}, + {L"C:\\colon\\backslash\filename.extension.extension2", L"extension2"}, +#elif defined(OS_POSIX) + {L"/foo/bar/filename.extension", L"extension"}, + {L"/foo/bar/filename.", L""}, + {L"/foo/bar/filename", L""}, + {L"/foo/bar/", L""}, + {L"/foo/bar./", L""}, + {L"/foo/bar/filename.extension.extension2", L"extension2"}, + {L".", L""}, + {L"..", L""}, + {L"./foo", L""}, + {L"./foo.extension", L"extension"}, + {L"/foo.extension1/bar.extension2", L"extension2"}, +#endif +}; + +// Test finding the directory component of a path +static const struct dir_case { + const wchar_t* full_path; + const wchar_t* directory; +} dir_cases[] = { +#if defined(OS_WIN) + {L"C:\\WINDOWS\\system32\\gdi32.dll", L"C:\\WINDOWS\\system32"}, + {L"C:\\WINDOWS\\system32\\not_exist_thx_1138", L"C:\\WINDOWS\\system32"}, + {L"C:\\WINDOWS\\system32\\", L"C:\\WINDOWS\\system32"}, + {L"C:\\WINDOWS\\system32\\\\", L"C:\\WINDOWS\\system32"}, + {L"C:\\WINDOWS\\system32", L"C:\\WINDOWS"}, + {L"C:\\WINDOWS\\system32.\\", L"C:\\WINDOWS\\system32."}, + {L"C:\\", L"C:\\"}, +#elif defined(OS_POSIX) + {L"/foo/bar/gdi32.dll", L"/foo/bar"}, + {L"/foo/bar/not_exist_thx_1138", L"/foo/bar"}, + {L"/foo/bar/", L"/foo/bar"}, + {L"/foo/bar//", L"/foo/bar"}, + {L"/foo/bar", L"/foo"}, + {L"/foo/bar./", L"/foo/bar."}, + {L"/", L"/"}, + {L".", L"."}, + {L"..", L"."}, // yes, ".." technically lives in "." +#endif +}; + +TEST_F(FileUtilTest, FileAndDirectorySize) { + // Create three files of 20, 30 and 3 chars (utf8). ComputeDirectorySize + // should return 53 bytes. + FilePath file_01 = temp_dir_.path().Append(FPL("The file 01.txt")); + CreateTextFile(file_01, L"12345678901234567890"); + int64 size_f1 = 0; + ASSERT_TRUE(file_util::GetFileSize(file_01, &size_f1)); + 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 = base::ComputeDirectorySize(temp_dir_.path()); + EXPECT_EQ(size_f1 + size_f2 + 3, computed_size); +} + +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(base::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(base::PathExists(file_a_path)); + ASSERT_TRUE(file_util::NormalizeFilePath(file_a_path, + &normalized_file_a_path)); + + CreateTextFile(file_b_path, bogus_content); + ASSERT_TRUE(base::PathExists(file_b_path)); + ASSERT_TRUE(file_util::NormalizeFilePath(file_b_path, + &normalized_file_b_path)); + + // 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())); +} + +#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)); + FilePath normalized_path; + { + ReparsePoint reparse_to_sub_a(to_sub_a, sub_a); + ASSERT_TRUE(reparse_to_sub_a.IsValid()); + + FilePath to_base_b = base_b.Append(FPL("to_base_b")); + ASSERT_TRUE(file_util::CreateDirectory(to_base_b)); + ReparsePoint reparse_to_base_b(to_base_b, base_b); + ASSERT_TRUE(reparse_to_base_b.IsValid()); + + FilePath to_sub_long = base_b.Append(FPL("to_sub_long")); + ASSERT_TRUE(file_util::CreateDirectory(to_sub_long)); + ReparsePoint reparse_to_sub_long(to_sub_long, sub_long); + ASSERT_TRUE(reparse_to_sub_long.IsValid()); + + // Normalize a junction free path: base_a\sub_a\file.txt . + ASSERT_TRUE(file_util::NormalizeFilePath(file_txt, &normalized_path)); + ASSERT_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_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(base::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(base::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) + +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); + EXPECT_EQ(bogus_content, contents); + + FilePath result; + ASSERT_TRUE(file_util::ReadSymbolicLink(link_from, &result)); + EXPECT_EQ(link_to.value(), result.value()); + + // Link to a directory. + link_from = temp_dir_.path().Append(FPL("from_dir")); + link_to = temp_dir_.path().Append(FPL("to_dir")); + ASSERT_TRUE(file_util::CreateDirectory(link_to)); + ASSERT_TRUE(file_util::CreateSymbolicLink(link_to, link_from)) + << "Failed to create directory symlink."; + + // Test failures. + EXPECT_FALSE(file_util::CreateSymbolicLink(link_to, link_to)); + EXPECT_FALSE(file_util::ReadSymbolicLink(link_to, &result)); + FilePath missing = temp_dir_.path().Append(FPL("missing")); + EXPECT_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) { + // 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. + FilePath normalized_path; + ASSERT_TRUE(file_util::NormalizeFilePath(link_from, &normalized_path)); + EXPECT_NE(link_from, link_to); + EXPECT_EQ(link_to.BaseName().value(), normalized_path.BaseName().value()); + EXPECT_EQ(link_to.BaseName().value(), normalized_path.BaseName().value()); + + // Link to a directory. + link_from = temp_dir_.path().Append(FPL("from_dir")); + link_to = temp_dir_.path().Append(FPL("to_dir")); + ASSERT_TRUE(file_util::CreateDirectory(link_to)); + ASSERT_TRUE(file_util::CreateSymbolicLink(link_to, link_from)) + << "Failed to create directory symlink."; + + EXPECT_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! + EXPECT_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(base::PathExists(non_existent)); + + EXPECT_TRUE(base::DeleteFile(non_existent, false)); + ASSERT_FALSE(base::PathExists(non_existent)); + EXPECT_TRUE(base::DeleteFile(non_existent, true)); + ASSERT_FALSE(base::PathExists(non_existent)); +} + +TEST_F(FileUtilTest, DeleteFile) { + // Create a file + FilePath file_name = temp_dir_.path().Append(FPL("Test DeleteFile 1.txt")); + CreateTextFile(file_name, bogus_content); + ASSERT_TRUE(base::PathExists(file_name)); + + // Make sure it's deleted + EXPECT_TRUE(base::DeleteFile(file_name, false)); + EXPECT_FALSE(base::PathExists(file_name)); + + // Test recursive case, create a new file + file_name = temp_dir_.path().Append(FPL("Test DeleteFile 2.txt")); + CreateTextFile(file_name, bogus_content); + ASSERT_TRUE(base::PathExists(file_name)); + + // Make sure it's deleted + EXPECT_TRUE(base::DeleteFile(file_name, true)); + EXPECT_FALSE(base::PathExists(file_name)); +} + +#if defined(OS_POSIX) +TEST_F(FileUtilTest, DeleteSymlinkToExistentFile) { + // Create a file. + FilePath file_name = temp_dir_.path().Append(FPL("Test DeleteFile 2.txt")); + CreateTextFile(file_name, bogus_content); + ASSERT_TRUE(base::PathExists(file_name)); + + // 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(base::DeleteFile(file_link, false)); + + // Make sure original file is not deleted. + EXPECT_FALSE(base::PathExists(file_link)); + EXPECT_TRUE(base::PathExists(file_name)); +} + +TEST_F(FileUtilTest, DeleteSymlinkToNonExistentFile) { + // Create a non-existent file path. + FilePath non_existent = temp_dir_.path().Append(FPL("Test DeleteFile 3.txt")); + EXPECT_FALSE(base::PathExists(non_existent)); + + // 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(base::PathExists(file_link)); + + // Delete the symbolic link. + EXPECT_TRUE(base::DeleteFile(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(base::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(kData.length()), + file_util::WriteFile(file_name, kData.data(), kData.length())); + EXPECT_TRUE(base::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(kData.length()), + file_util::ReadFile(file_name, buffer, buffer_size)); + + // Delete the file. + EXPECT_TRUE(base::DeleteFile(file_name, false)); + EXPECT_FALSE(base::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(base::PathExists(file_name)); + + const std::string kData("hello"); + + // Write file. + EXPECT_EQ(static_cast(kData.length()), + file_util::WriteFile(file_name, kData.data(), kData.length())); + EXPECT_TRUE(base::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(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(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(kData.length()), + file_util::WriteFile(file_name, kData.data(), kData.length())); + EXPECT_TRUE(PathIsWritable(file_name)); + + // Delete the file. + EXPECT_TRUE(base::DeleteFile(file_name, false)); + EXPECT_FALSE(base::PathExists(file_name)); +} + +TEST_F(FileUtilTest, ChangeDirectoryPermissionsAndEnumerate) { + // Create a directory path. + FilePath subdir_path = + temp_dir_.path().Append(FPL("PermissionTest1")); + file_util::CreateDirectory(subdir_path); + ASSERT_TRUE(base::PathExists(subdir_path)); + + // Create a dummy file to enumerate. + FilePath file_name = subdir_path.Append(FPL("Test Readable File.txt")); + EXPECT_FALSE(base::PathExists(file_name)); + const std::string kData("hello"); + EXPECT_EQ(static_cast(kData.length()), + file_util::WriteFile(file_name, kData.data(), kData.length())); + EXPECT_TRUE(base::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. + FileEnumerator f1(subdir_path, true, FileEnumerator::FILES); + EXPECT_TRUE(base::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. + FileEnumerator f2(subdir_path, true, FileEnumerator::FILES); + FindResultCollector c2(f2); + EXPECT_TRUE(c2.HasFile(file_name)); + EXPECT_EQ(c2.size(), 1); + + // Delete the file. + EXPECT_TRUE(base::DeleteFile(subdir_path, true)); + EXPECT_FALSE(base::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(base::PathExists(file_name)); + + FilePath subdir_path = temp_dir_.path().Append(FPL("DeleteWildCardDir")); + file_util::CreateDirectory(subdir_path); + ASSERT_TRUE(base::PathExists(subdir_path)); + + // Create the wildcard path + FilePath directory_contents = temp_dir_.path(); + directory_contents = directory_contents.Append(FPL("*")); + + // Delete non-recursively and check that only the file is deleted + EXPECT_TRUE(base::DeleteFile(directory_contents, false)); + EXPECT_FALSE(base::PathExists(file_name)); + EXPECT_TRUE(base::PathExists(subdir_path)); + + // Delete recursively and make sure all contents are deleted + EXPECT_TRUE(base::DeleteFile(directory_contents, true)); + EXPECT_FALSE(base::PathExists(file_name)); + EXPECT_FALSE(base::PathExists(subdir_path)); +} + +// 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(base::PathExists(subdir_path)); + + // Create the wildcard path + FilePath directory_contents = subdir_path; + directory_contents = directory_contents.Append(FPL("*")); + + // Delete non-recursively and check nothing got deleted + EXPECT_TRUE(base::DeleteFile(directory_contents, false)); + EXPECT_TRUE(base::PathExists(subdir_path)); + + // Delete recursively and check nothing got deleted + EXPECT_TRUE(base::DeleteFile(directory_contents, true)); + EXPECT_TRUE(base::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(base::PathExists(test_subdir)); + + FilePath file_name = test_subdir.Append(FPL("Test DeleteDir.txt")); + CreateTextFile(file_name, bogus_content); + ASSERT_TRUE(base::PathExists(file_name)); + + FilePath subdir_path1 = test_subdir.Append(FPL("TestSubDir1")); + file_util::CreateDirectory(subdir_path1); + ASSERT_TRUE(base::PathExists(subdir_path1)); + + FilePath subdir_path2 = test_subdir.Append(FPL("TestSubDir2")); + file_util::CreateDirectory(subdir_path2); + ASSERT_TRUE(base::PathExists(subdir_path2)); + + // Delete non-recursively and check that the empty dir got deleted + EXPECT_TRUE(base::DeleteFile(subdir_path2, false)); + EXPECT_FALSE(base::PathExists(subdir_path2)); + + // Delete non-recursively and check that nothing got deleted + EXPECT_FALSE(base::DeleteFile(test_subdir, false)); + EXPECT_TRUE(base::PathExists(test_subdir)); + EXPECT_TRUE(base::PathExists(file_name)); + EXPECT_TRUE(base::PathExists(subdir_path1)); +} + +// Tests recursive Delete() for a directory. +TEST_F(FileUtilTest, DeleteDirRecursive) { + // Create a subdirectory and put a file and two directories inside. + FilePath test_subdir = temp_dir_.path().Append(FPL("DeleteDirRecursive")); + file_util::CreateDirectory(test_subdir); + ASSERT_TRUE(base::PathExists(test_subdir)); + + FilePath file_name = test_subdir.Append(FPL("Test DeleteDirRecursive.txt")); + CreateTextFile(file_name, bogus_content); + ASSERT_TRUE(base::PathExists(file_name)); + + FilePath subdir_path1 = test_subdir.Append(FPL("TestSubDir1")); + file_util::CreateDirectory(subdir_path1); + ASSERT_TRUE(base::PathExists(subdir_path1)); + + FilePath subdir_path2 = test_subdir.Append(FPL("TestSubDir2")); + file_util::CreateDirectory(subdir_path2); + ASSERT_TRUE(base::PathExists(subdir_path2)); + + // Delete recursively and check that the empty dir got deleted + EXPECT_TRUE(base::DeleteFile(subdir_path2, true)); + EXPECT_FALSE(base::PathExists(subdir_path2)); + + // Delete recursively and check that everything got deleted + EXPECT_TRUE(base::DeleteFile(test_subdir, true)); + EXPECT_FALSE(base::PathExists(file_name)); + EXPECT_FALSE(base::PathExists(subdir_path1)); + EXPECT_FALSE(base::PathExists(test_subdir)); +} + +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(base::PathExists(file_name_from)); + + // The destination. + FilePath file_name_to = temp_dir_.path().Append( + FILE_PATH_LITERAL("Move_Test_File_Destination.txt")); + ASSERT_FALSE(base::PathExists(file_name_to)); + + EXPECT_TRUE(base::Move(file_name_from, file_name_to)); + + // Check everything has been moved. + EXPECT_FALSE(base::PathExists(file_name_from)); + EXPECT_TRUE(base::PathExists(file_name_to)); +} + +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(base::PathExists(file_name_from)); + + // The destination name. + FilePath file_name_to = temp_dir_.path().Append( + FILE_PATH_LITERAL("Move_Test_File_Destination.txt")); + CreateTextFile(file_name_to, L"Old file content"); + ASSERT_TRUE(base::PathExists(file_name_to)); + + EXPECT_TRUE(base::Move(file_name_from, file_name_to)); + + // Check everything has been moved. + EXPECT_FALSE(base::PathExists(file_name_from)); + EXPECT_TRUE(base::PathExists(file_name_to)); + EXPECT_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(base::PathExists(file_name_from)); + + // The destination directory + FilePath dir_name_to = + temp_dir_.path().Append(FILE_PATH_LITERAL("Destination")); + file_util::CreateDirectory(dir_name_to); + ASSERT_TRUE(base::PathExists(dir_name_to)); + + EXPECT_FALSE(base::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(base::PathExists(dir_name_from)); + + // Create a file under the directory + FilePath txt_file_name(FILE_PATH_LITERAL("Move_Test_File.txt")); + FilePath file_name_from = dir_name_from.Append(txt_file_name); + CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(base::PathExists(file_name_from)); + + // 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(base::PathExists(dir_name_to)); + + EXPECT_TRUE(base::Move(dir_name_from, dir_name_to)); + + // Check everything has been moved. + EXPECT_FALSE(base::PathExists(dir_name_from)); + EXPECT_FALSE(base::PathExists(file_name_from)); + EXPECT_TRUE(base::PathExists(dir_name_to)); + EXPECT_TRUE(base::PathExists(file_name_to)); + + // Test path traversal. + file_name_from = dir_name_to.Append(txt_file_name); + file_name_to = dir_name_to.Append(FILE_PATH_LITERAL("..")); + file_name_to = file_name_to.Append(txt_file_name); + EXPECT_FALSE(base::Move(file_name_from, file_name_to)); + EXPECT_TRUE(base::PathExists(file_name_from)); + EXPECT_FALSE(base::PathExists(file_name_to)); + EXPECT_TRUE(base::internal::MoveUnsafe(file_name_from, file_name_to)); + EXPECT_FALSE(base::PathExists(file_name_from)); + EXPECT_TRUE(base::PathExists(file_name_to)); +} + +TEST_F(FileUtilTest, MoveExist) { + // Create a directory + FilePath dir_name_from = + temp_dir_.path().Append(FILE_PATH_LITERAL("Move_From_Subdir")); + file_util::CreateDirectory(dir_name_from); + ASSERT_TRUE(base::PathExists(dir_name_from)); + + // Create a file under the directory + FilePath file_name_from = + dir_name_from.Append(FILE_PATH_LITERAL("Move_Test_File.txt")); + CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(base::PathExists(file_name_from)); + + // 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(base::PathExists(dir_name_exists)); + + EXPECT_TRUE(base::Move(dir_name_from, dir_name_to)); + + // Check everything has been moved. + EXPECT_FALSE(base::PathExists(dir_name_from)); + EXPECT_FALSE(base::PathExists(file_name_from)); + EXPECT_TRUE(base::PathExists(dir_name_to)); + EXPECT_TRUE(base::PathExists(file_name_to)); +} + +TEST_F(FileUtilTest, CopyDirectoryRecursivelyNew) { + // Create a directory. + FilePath dir_name_from = + temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_From_Subdir")); + file_util::CreateDirectory(dir_name_from); + ASSERT_TRUE(base::PathExists(dir_name_from)); + + // Create a file under the directory. + FilePath file_name_from = + dir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(base::PathExists(file_name_from)); + + // Create a subdirectory. + FilePath subdir_name_from = + dir_name_from.Append(FILE_PATH_LITERAL("Subdir")); + file_util::CreateDirectory(subdir_name_from); + ASSERT_TRUE(base::PathExists(subdir_name_from)); + + // Create a file under the subdirectory. + FilePath file_name2_from = + subdir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + CreateTextFile(file_name2_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(base::PathExists(file_name2_from)); + + // 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(base::PathExists(dir_name_to)); + + EXPECT_TRUE(base::CopyDirectory(dir_name_from, dir_name_to, true)); + + // Check everything has been copied. + EXPECT_TRUE(base::PathExists(dir_name_from)); + EXPECT_TRUE(base::PathExists(file_name_from)); + EXPECT_TRUE(base::PathExists(subdir_name_from)); + EXPECT_TRUE(base::PathExists(file_name2_from)); + EXPECT_TRUE(base::PathExists(dir_name_to)); + EXPECT_TRUE(base::PathExists(file_name_to)); + EXPECT_TRUE(base::PathExists(subdir_name_to)); + EXPECT_TRUE(base::PathExists(file_name2_to)); +} + +TEST_F(FileUtilTest, CopyDirectoryRecursivelyExists) { + // Create a directory. + FilePath dir_name_from = + temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_From_Subdir")); + file_util::CreateDirectory(dir_name_from); + ASSERT_TRUE(base::PathExists(dir_name_from)); + + // Create a file under the directory. + FilePath file_name_from = + dir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(base::PathExists(file_name_from)); + + // Create a subdirectory. + FilePath subdir_name_from = + dir_name_from.Append(FILE_PATH_LITERAL("Subdir")); + file_util::CreateDirectory(subdir_name_from); + ASSERT_TRUE(base::PathExists(subdir_name_from)); + + // Create a file under the subdirectory. + FilePath file_name2_from = + subdir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + CreateTextFile(file_name2_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(base::PathExists(file_name2_from)); + + // 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(base::PathExists(dir_name_exists)); + + EXPECT_TRUE(base::CopyDirectory(dir_name_from, dir_name_exists, true)); + + // Check everything has been copied. + EXPECT_TRUE(base::PathExists(dir_name_from)); + EXPECT_TRUE(base::PathExists(file_name_from)); + EXPECT_TRUE(base::PathExists(subdir_name_from)); + EXPECT_TRUE(base::PathExists(file_name2_from)); + EXPECT_TRUE(base::PathExists(dir_name_to)); + EXPECT_TRUE(base::PathExists(file_name_to)); + EXPECT_TRUE(base::PathExists(subdir_name_to)); + EXPECT_TRUE(base::PathExists(file_name2_to)); +} + +TEST_F(FileUtilTest, CopyDirectoryNew) { + // Create a directory. + FilePath dir_name_from = + temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_From_Subdir")); + file_util::CreateDirectory(dir_name_from); + ASSERT_TRUE(base::PathExists(dir_name_from)); + + // Create a file under the directory. + FilePath file_name_from = + dir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(base::PathExists(file_name_from)); + + // Create a subdirectory. + FilePath subdir_name_from = + dir_name_from.Append(FILE_PATH_LITERAL("Subdir")); + file_util::CreateDirectory(subdir_name_from); + ASSERT_TRUE(base::PathExists(subdir_name_from)); + + // Create a file under the subdirectory. + FilePath file_name2_from = + subdir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + CreateTextFile(file_name2_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(base::PathExists(file_name2_from)); + + // 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(base::PathExists(dir_name_to)); + + EXPECT_TRUE(base::CopyDirectory(dir_name_from, dir_name_to, false)); + + // Check everything has been copied. + EXPECT_TRUE(base::PathExists(dir_name_from)); + EXPECT_TRUE(base::PathExists(file_name_from)); + EXPECT_TRUE(base::PathExists(subdir_name_from)); + EXPECT_TRUE(base::PathExists(file_name2_from)); + EXPECT_TRUE(base::PathExists(dir_name_to)); + EXPECT_TRUE(base::PathExists(file_name_to)); + EXPECT_FALSE(base::PathExists(subdir_name_to)); +} + +TEST_F(FileUtilTest, CopyDirectoryExists) { + // Create a directory. + FilePath dir_name_from = + temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_From_Subdir")); + file_util::CreateDirectory(dir_name_from); + ASSERT_TRUE(base::PathExists(dir_name_from)); + + // Create a file under the directory. + FilePath file_name_from = + dir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(base::PathExists(file_name_from)); + + // Create a subdirectory. + FilePath subdir_name_from = + dir_name_from.Append(FILE_PATH_LITERAL("Subdir")); + file_util::CreateDirectory(subdir_name_from); + ASSERT_TRUE(base::PathExists(subdir_name_from)); + + // Create a file under the subdirectory. + FilePath file_name2_from = + subdir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + CreateTextFile(file_name2_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(base::PathExists(file_name2_from)); + + // 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(base::PathExists(dir_name_to)); + + EXPECT_TRUE(base::CopyDirectory(dir_name_from, dir_name_to, false)); + + // Check everything has been copied. + EXPECT_TRUE(base::PathExists(dir_name_from)); + EXPECT_TRUE(base::PathExists(file_name_from)); + EXPECT_TRUE(base::PathExists(subdir_name_from)); + EXPECT_TRUE(base::PathExists(file_name2_from)); + EXPECT_TRUE(base::PathExists(dir_name_to)); + EXPECT_TRUE(base::PathExists(file_name_to)); + EXPECT_FALSE(base::PathExists(subdir_name_to)); +} + +TEST_F(FileUtilTest, 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(base::PathExists(file_name_from)); + + // The destination name + FilePath file_name_to = temp_dir_.path().Append( + FILE_PATH_LITERAL("Copy_Test_File_Destination.txt")); + ASSERT_FALSE(base::PathExists(file_name_to)); + + EXPECT_TRUE(base::CopyDirectory(file_name_from, file_name_to, true)); + + // Check the has been copied + EXPECT_TRUE(base::PathExists(file_name_to)); +} + +TEST_F(FileUtilTest, 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(base::PathExists(file_name_from)); + + // The destination name + FilePath file_name_to = temp_dir_.path().Append( + FILE_PATH_LITERAL("Copy_Test_File_Destination.txt")); + CreateTextFile(file_name_to, L"Old file content"); + ASSERT_TRUE(base::PathExists(file_name_to)); + + EXPECT_TRUE(base::CopyDirectory(file_name_from, file_name_to, true)); + + // Check the has been copied + EXPECT_TRUE(base::PathExists(file_name_to)); + EXPECT_TRUE(L"Gooooooooooooooooooooogle" == ReadTextFile(file_name_to)); +} + +TEST_F(FileUtilTest, 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(base::PathExists(file_name_from)); + + // The destination + FilePath dir_name_to = + temp_dir_.path().Append(FILE_PATH_LITERAL("Destination")); + file_util::CreateDirectory(dir_name_to); + ASSERT_TRUE(base::PathExists(dir_name_to)); + FilePath file_name_to = + dir_name_to.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + + EXPECT_TRUE(base::CopyDirectory(file_name_from, dir_name_to, true)); + + // Check the has been copied + EXPECT_TRUE(base::PathExists(file_name_to)); +} + +TEST_F(FileUtilTest, CopyDirectoryWithTrailingSeparators) { + // Create a directory. + FilePath dir_name_from = + temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_From_Subdir")); + file_util::CreateDirectory(dir_name_from); + ASSERT_TRUE(base::PathExists(dir_name_from)); + + // Create a file under the directory. + FilePath file_name_from = + dir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(base::PathExists(file_name_from)); + + // 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(base::CopyDirectory(from_path, dir_name_to, true)); + + // Check everything has been copied. + EXPECT_TRUE(base::PathExists(dir_name_from)); + EXPECT_TRUE(base::PathExists(file_name_from)); + EXPECT_TRUE(base::PathExists(dir_name_to)); + EXPECT_TRUE(base::PathExists(file_name_to)); +} + +TEST_F(FileUtilTest, CopyFile) { + // Create a directory + FilePath dir_name_from = + temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_From_Subdir")); + file_util::CreateDirectory(dir_name_from); + ASSERT_TRUE(base::PathExists(dir_name_from)); + + // Create a file under the directory + FilePath file_name_from = + dir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + const std::wstring file_contents(L"Gooooooooooooooooooooogle"); + CreateTextFile(file_name_from, file_contents); + ASSERT_TRUE(base::PathExists(file_name_from)); + + // Copy the file. + FilePath dest_file = dir_name_from.Append(FILE_PATH_LITERAL("DestFile.txt")); + ASSERT_TRUE(base::CopyFile(file_name_from, dest_file)); + + // Copy the file to another location using '..' in the path. + FilePath dest_file2(dir_name_from); + dest_file2 = dest_file2.AppendASCII(".."); + dest_file2 = dest_file2.AppendASCII("DestFile.txt"); + ASSERT_FALSE(base::CopyFile(file_name_from, dest_file2)); + ASSERT_TRUE(base::internal::CopyFileUnsafe(file_name_from, dest_file2)); + + FilePath dest_file2_test(dir_name_from); + dest_file2_test = dest_file2_test.DirName(); + dest_file2_test = dest_file2_test.AppendASCII("DestFile.txt"); + + // Check everything has been copied. + EXPECT_TRUE(base::PathExists(file_name_from)); + EXPECT_TRUE(base::PathExists(dest_file)); + const std::wstring read_contents = ReadTextFile(dest_file); + EXPECT_EQ(file_contents, read_contents); + EXPECT_TRUE(base::PathExists(dest_file2_test)); + EXPECT_TRUE(base::PathExists(dest_file2)); +} + +// file_util winds up using autoreleased objects on the Mac, so this needs +// to be a PlatformTest. +typedef PlatformTest ReadOnlyFileUtilTest; + +TEST_F(ReadOnlyFileUtilTest, ContentsEqual) { + FilePath data_dir; + ASSERT_TRUE(PathService::Get(base::DIR_TEST_DATA, &data_dir)); + data_dir = data_dir.AppendASCII("file_util"); + ASSERT_TRUE(base::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(ContentsEqual(original_file, original_file)); + EXPECT_TRUE(ContentsEqual(original_file, same_file)); + EXPECT_FALSE(ContentsEqual(original_file, same_length_file)); + EXPECT_FALSE(ContentsEqual(original_file, different_file)); + EXPECT_FALSE(ContentsEqual(FilePath(FILE_PATH_LITERAL("bogusname")), + FilePath(FILE_PATH_LITERAL("bogusname")))); + EXPECT_FALSE(ContentsEqual(original_file, different_first_file)); + EXPECT_FALSE(ContentsEqual(original_file, different_last_file)); + EXPECT_TRUE(ContentsEqual(empty1_file, empty2_file)); + EXPECT_FALSE(ContentsEqual(original_file, shortened_file)); + EXPECT_FALSE(ContentsEqual(shortened_file, original_file)); + EXPECT_TRUE(ContentsEqual(binary_file, binary_file_same)); + EXPECT_FALSE(ContentsEqual(binary_file, binary_file_diff)); +} + +TEST_F(ReadOnlyFileUtilTest, TextContentsEqual) { + FilePath data_dir; + ASSERT_TRUE(PathService::Get(base::DIR_TEST_DATA, &data_dir)); + data_dir = data_dir.AppendASCII("file_util"); + ASSERT_TRUE(base::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(TextContentsEqual(original_file, same_file)); + EXPECT_TRUE(TextContentsEqual(original_file, crlf_file)); + EXPECT_FALSE(TextContentsEqual(original_file, shortened_file)); + EXPECT_FALSE(TextContentsEqual(original_file, different_file)); + EXPECT_FALSE(TextContentsEqual(original_file, different_first_file)); + EXPECT_FALSE(TextContentsEqual(original_file, different_last_file)); + EXPECT_FALSE(TextContentsEqual(first1_file, first2_file)); + EXPECT_TRUE(TextContentsEqual(empty1_file, empty2_file)); + EXPECT_FALSE(TextContentsEqual(original_file, empty1_file)); + EXPECT_TRUE(TextContentsEqual(blank_line_file, blank_line_crlf_file)); +} + +// 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(base::PathExists(dir_name_from)); + + // Create a file under the directory + FilePath file_name_from = + dir_name_from.Append(FILE_PATH_LITERAL("CopyAndDelete_Test_File.txt")); + CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(base::PathExists(file_name_from)); + + // 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(base::PathExists(dir_name_to)); + + EXPECT_TRUE(base::internal::CopyAndDeleteDirectory(dir_name_from, + dir_name_to)); + + // Check everything has been moved. + EXPECT_FALSE(base::PathExists(dir_name_from)); + EXPECT_FALSE(base::PathExists(file_name_from)); + EXPECT_TRUE(base::PathExists(dir_name_to)); + EXPECT_TRUE(base::PathExists(file_name_to)); +} + +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(base::PathExists(temp_files[i])); + EXPECT_FALSE(DirectoryExists(temp_files[i])); + } + for (int i = 0; i < 3; i++) + EXPECT_FALSE(temp_files[i] == temp_files[(i+1)%3]); + for (int i = 0; i < 3; i++) + EXPECT_TRUE(base::DeleteFile(temp_files[i], false)); +} + +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(base::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(base::DeleteFile(names[i], false)); + } +} + +TEST_F(FileUtilTest, CreateNewTempDirectoryTest) { + FilePath temp_dir; + ASSERT_TRUE(file_util::CreateNewTempDirectory(FilePath::StringType(), + &temp_dir)); + EXPECT_TRUE(base::PathExists(temp_dir)); + EXPECT_TRUE(base::DeleteFile(temp_dir, false)); +} + +TEST_F(FileUtilTest, CreateNewTemporaryDirInDirTest) { + FilePath new_dir; + ASSERT_TRUE(file_util::CreateTemporaryDirInDir( + temp_dir_.path(), + FILE_PATH_LITERAL("CreateNewTemporaryDirInDirTest"), + &new_dir)); + EXPECT_TRUE(base::PathExists(new_dir)); + EXPECT_TRUE(temp_dir_.path().IsParent(new_dir)); + EXPECT_TRUE(base::DeleteFile(new_dir, false)); +} + +TEST_F(FileUtilTest, GetShmemTempDirTest) { + FilePath dir; + EXPECT_TRUE(file_util::GetShmemTempDir(&dir, false)); + EXPECT_TRUE(DirectoryExists(dir)); +} + +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/")); +#endif + + EXPECT_FALSE(base::PathExists(test_path)); + EXPECT_TRUE(file_util::CreateDirectory(test_path)); + EXPECT_TRUE(base::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(base::PathExists(test_path)); + CreateTextFile(test_path, L"test file"); + EXPECT_TRUE(base::PathExists(test_path)); + EXPECT_FALSE(file_util::CreateDirectory(test_path)); + + EXPECT_TRUE(base::DeleteFile(test_root, true)); + EXPECT_FALSE(base::PathExists(test_root)); + EXPECT_FALSE(base::PathExists(test_path)); + + // Verify assumptions made by the Windows implementation: + // 1. The current directory always exists. + // 2. The root directory always exists. + ASSERT_TRUE(DirectoryExists(FilePath(FilePath::kCurrentDirectory))); + FilePath top_level = test_root; + while (top_level != top_level.DirName()) { + top_level = top_level.DirName(); + } + ASSERT_TRUE(DirectoryExists(top_level)); + + // Given these assumptions hold, it should be safe to + // test that "creating" these directories succeeds. + EXPECT_TRUE(file_util::CreateDirectory( + FilePath(FilePath::kCurrentDirectory))); + 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 (!base::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(base::PathExists(test_root)); + EXPECT_TRUE(file_util::CreateDirectory(test_root)); + EXPECT_TRUE(base::PathExists(test_root)); + EXPECT_TRUE(DirectoryExists(test_root)); + // Check a file + FilePath test_path = + test_root.Append(FILE_PATH_LITERAL("foobar.txt")); + EXPECT_FALSE(base::PathExists(test_path)); + CreateTextFile(test_path, L"test file"); + EXPECT_TRUE(base::PathExists(test_path)); + EXPECT_FALSE(DirectoryExists(test_path)); + EXPECT_TRUE(base::DeleteFile(test_path, false)); + + EXPECT_TRUE(base::DeleteFile(test_root, true)); +} + +TEST_F(FileUtilTest, FileEnumeratorTest) { + // Test an empty directory. + FileEnumerator f0(temp_dir_.path(), true, FILES_AND_DIRECTORIES); + EXPECT_EQ(f0.Next().value(), FPL("")); + EXPECT_EQ(f0.Next().value(), FPL("")); + + // Test an empty directory, non-recursively, including "..". + FileEnumerator f0_dotdot(temp_dir_.path(), false, + FILES_AND_DIRECTORIES | FileEnumerator::INCLUDE_DOT_DOT); + EXPECT_EQ(temp_dir_.path().Append(FPL("..")).value(), + f0_dotdot.Next().value()); + EXPECT_EQ(FPL(""), f0_dotdot.Next().value()); + + // create the directories + FilePath dir1 = temp_dir_.path().Append(FPL("dir1")); + EXPECT_TRUE(file_util::CreateDirectory(dir1)); + FilePath dir2 = temp_dir_.path().Append(FPL("dir2")); + EXPECT_TRUE(file_util::CreateDirectory(dir2)); + FilePath dir2inner = dir2.Append(FPL("inner")); + EXPECT_TRUE(file_util::CreateDirectory(dir2inner)); + + // create the files + FilePath dir2file = dir2.Append(FPL("dir2file.txt")); + CreateTextFile(dir2file, std::wstring()); + FilePath dir2innerfile = dir2inner.Append(FPL("innerfile.txt")); + CreateTextFile(dir2innerfile, std::wstring()); + FilePath file1 = temp_dir_.path().Append(FPL("file1.txt")); + CreateTextFile(file1, std::wstring()); + FilePath file2_rel = dir2.Append(FilePath::kParentDirectory) + .Append(FPL("file2.txt")); + CreateTextFile(file2_rel, std::wstring()); + FilePath file2_abs = temp_dir_.path().Append(FPL("file2.txt")); + + // Only enumerate files. + FileEnumerator f1(temp_dir_.path(), true, 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. + FileEnumerator f2(temp_dir_.path(), true, 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. + FileEnumerator f2_non_recursive( + temp_dir_.path(), false, 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); + + // Only enumerate directories, non-recursively, including "..". + FileEnumerator f2_dotdot(temp_dir_.path(), false, + FileEnumerator::DIRECTORIES | + 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(FPL("..")))); + EXPECT_EQ(c2_dotdot.size(), 3); + + // Enumerate files and directories. + 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. + 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); + + // Enumerate with a pattern. + FileEnumerator f5(temp_dir_.path(), true, FILES_AND_DIRECTORIES, FPL("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); + +#if defined(OS_WIN) + { + // Make dir1 point to dir2. + ReparsePoint reparse_point(dir1, dir2); + EXPECT_TRUE(reparse_point.IsValid()); + + if ((base::win::GetVersion() >= base::win::VERSION_VISTA)) { + // There can be a delay for the enumeration code to see the change on + // the file system so skip this test for XP. + // Enumerate the reparse point. + FileEnumerator f6(dir1, true, FILES_AND_DIRECTORIES); + FindResultCollector c6(f6); + FilePath inner2 = dir1.Append(FPL("inner")); + EXPECT_TRUE(c6.HasFile(inner2)); + EXPECT_TRUE(c6.HasFile(inner2.Append(FPL("innerfile.txt")))); + EXPECT_TRUE(c6.HasFile(dir1.Append(FPL("dir2file.txt")))); + EXPECT_EQ(c6.size(), 3); + } + + // No changes for non recursive operation. + FileEnumerator f7(temp_dir_.path(), false, FILES_AND_DIRECTORIES); + FindResultCollector c7(f7); + EXPECT_TRUE(c7.HasFile(dir2)); + EXPECT_TRUE(c7.HasFile(dir2)); + EXPECT_TRUE(c7.HasFile(file1)); + EXPECT_TRUE(c7.HasFile(file2_abs)); + EXPECT_EQ(c7.size(), 4); + + // Should not enumerate inside dir1 when using recursion. + FileEnumerator f8(temp_dir_.path(), true, FILES_AND_DIRECTORIES); + FindResultCollector c8(f8); + EXPECT_TRUE(c8.HasFile(dir1)); + EXPECT_TRUE(c8.HasFile(dir2)); + EXPECT_TRUE(c8.HasFile(file1)); + EXPECT_TRUE(c8.HasFile(file2_abs)); + EXPECT_TRUE(c8.HasFile(dir2file)); + EXPECT_TRUE(c8.HasFile(dir2inner)); + EXPECT_TRUE(c8.HasFile(dir2innerfile)); + EXPECT_EQ(c8.size(), 7); + } +#endif + + // Make sure the destructor closes the find handle while in the middle of a + // query to allow TearDown to delete the directory. + FileEnumerator f9(temp_dir_.path(), true, FILES_AND_DIRECTORIES); + EXPECT_FALSE(f9.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 (base::PathExists(data_dir)) { + ASSERT_TRUE(base::DeleteFile(data_dir, true)); + } + ASSERT_TRUE(file_util::CreateDirectory(data_dir)); + + // Create a fresh, empty copy of this directory. + if (base::PathExists(data_dir)) { + ASSERT_TRUE(base::DeleteFile(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(data.length()), + file_util::WriteFile(foobar, data.c_str(), data.length())); + EXPECT_EQ(static_cast(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, TouchFile) { + FilePath data_dir = + temp_dir_.path().Append(FILE_PATH_LITERAL("FilePathTest")); + + // Create a fresh, empty copy of this directory. + if (base::PathExists(data_dir)) { + ASSERT_TRUE(base::DeleteFile(data_dir, true)); + } + 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)); + + 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()); +} + +TEST_F(FileUtilTest, IsDirectoryEmpty) { + FilePath empty_dir = temp_dir_.path().Append(FILE_PATH_LITERAL("EmptyDir")); + + ASSERT_FALSE(base::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) + +// 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 ok_gids_; + std::set 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_)); +} + +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(base::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 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 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 // defined(OS_POSIX) + +} // namespace diff --git a/base/file_util_win.cc b/base/file_util_win.cc new file mode 100644 index 0000000000..39317a3a93 --- /dev/null +++ b/base/file_util_win.cc @@ -0,0 +1,759 @@ +// Copyright (c) 2012 The Chromium Authors. All 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 +#include +#include +#include +#include + +#include +#include +#include + +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/metrics/histogram.h" +#include "base/process/process_handle.h" +#include "base/rand_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/thread_restrictions.h" +#include "base/time/time.h" +#include "base/win/scoped_handle.h" +#include "base/win/windows_version.h" + +namespace base { + +namespace { + +const DWORD kFileShareAll = + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; + +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(); + + 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); +} + +} // namespace + +FilePath MakeAbsoluteFilePath(const FilePath& input) { + ThreadRestrictions::AssertIOAllowed(); + wchar_t file_path[MAX_PATH]; + if (!_wfullpath(file_path, input.value().c_str(), MAX_PATH)) + return FilePath(); + return FilePath(file_path); +} + +bool DeleteFile(const FilePath& path, bool recursive) { + 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. + PlatformFileInfo file_info; + if (file_util::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 DeleteFileAfterReboot(const FilePath& path) { + 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 ReplaceFile(const FilePath& from_path, + const FilePath& to_path, + PlatformFileError* error) { + 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; + } + if (error) + *error = LastErrorToPlatformFileError(GetLastError()); + return false; +} + +bool CopyDirectory(const FilePath& from_path, const FilePath& to_path, + bool recursive) { + 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) + file_util::CreateDirectory(to_path); + else + ShellCopy(from_path, to_path, false); + } + + FilePath directory = from_path.Append(L"*.*"); + return ShellCopy(directory, to_path, false); +} + +bool PathExists(const FilePath& path) { + ThreadRestrictions::AssertIOAllowed(); + return (GetFileAttributes(path.value().c_str()) != INVALID_FILE_ATTRIBUTES); +} + +bool PathIsWritable(const FilePath& path) { + 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) { + ThreadRestrictions::AssertIOAllowed(); + DWORD fileattr = GetFileAttributes(path.value().c_str()); + if (fileattr != INVALID_FILE_ATTRIBUTES) + return (fileattr & FILE_ATTRIBUTE_DIRECTORY) != 0; + return false; +} + +} // namespace base + +// ----------------------------------------------------------------------------- + +namespace file_util { + +using base::DirectoryExists; +using base::FilePath; +using base::kFileShareAll; + +bool GetTempDir(FilePath* path) { + base::ThreadRestrictions::AssertIOAllowed(); + + wchar_t temp_path[MAX_PATH + 1]; + DWORD path_len = ::GetTempPath(MAX_PATH, temp_path); + if (path_len >= MAX_PATH || path_len <= 0) + 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; + + 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(base::RandInt(0, 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 CreateDirectoryAndGetError(const FilePath& full_path, + base::PlatformFileError* error) { + 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."; + if (error) { + *error = base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY; + } + 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()) { + if (error) { + *error = base::PLATFORM_FILE_ERROR_NOT_FOUND; + } + return false; + } + if (!CreateDirectoryAndGetError(parent_path, error)) { + DLOG(WARNING) << "Failed to create one of the parent directories."; + if (error) { + DCHECK(*error != base::PLATFORM_FILE_OK); + } + 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 { + if (error) + *error = base::LastErrorToPlatformFileError(error_code); + 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(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_GETLASTERROR(WARNING) << "CreateFile failed for path " + << filename.value(); + return -1; + } + + DWORD written; + BOOL result = ::WriteFile(file, data, size, &written, NULL); + if (result && static_cast(written) == size) + return written; + + if (!result) { + // WriteFile failed. + DLOG_GETLASTERROR(WARNING) << "writing file " << filename.value() + << " failed"; + } 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_GETLASTERROR(WARNING) << "CreateFile failed for path " + << filename.value(); + return -1; + } + + DWORD written; + BOOL result = ::WriteFile(file, data, size, &written, NULL); + if (result && static_cast(written) == size) + return written; + + if (!result) { + // WriteFile failed. + DLOG_GETLASTERROR(WARNING) << "writing file " << filename.value() + << " failed"; + } 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; +} + +bool NormalizeFilePath(const FilePath& path, FilePath* real_path) { + base::ThreadRestrictions::AssertIOAllowed(); + FilePath mapped_file; + if (!NormalizeToNativeFilePath(path, &mapped_file)) + return false; + // NormalizeToNativeFilePath() will return a path that starts with + // "\Device\Harddisk...". Helper DevicePathToDriveLetterPath() + // will find a drive letter which maps to the path's device, so + // that we return a path starting with a drive letter. + return DevicePathToDriveLetterPath(mapped_file, real_path); +} + +bool DevicePathToDriveLetterPath(const FilePath& nt_device_path, + FilePath* out_drive_letter_path) { + base::ThreadRestrictions::AssertIOAllowed(); + + // Get the mapping of drive letters to device paths. + const int kDriveMappingSize = 1024; + wchar_t drive_mapping[kDriveMappingSize] = {'\0'}; + if (!::GetLogicalDriveStrings(kDriveMappingSize - 1, drive_mapping)) { + DLOG(ERROR) << "Failed to get drive mapping."; + return false; + } + + // The drive mapping is a sequence of null terminated strings. + // The last string is empty. + wchar_t* drive_map_ptr = drive_mapping; + wchar_t device_path_as_string[MAX_PATH]; + wchar_t drive[] = L" :"; + + // For each string in the drive mapping, get the junction that links + // to it. If that junction is a prefix of |device_path|, then we + // know that |drive| is the real path prefix. + while (*drive_map_ptr) { + drive[0] = drive_map_ptr[0]; // Copy the drive letter. + + if (QueryDosDevice(drive, device_path_as_string, MAX_PATH)) { + FilePath device_path(device_path_as_string); + if (device_path == nt_device_path || + device_path.IsParent(nt_device_path)) { + *out_drive_letter_path = FilePath(drive + + nt_device_path.value().substr(wcslen(device_path_as_string))); + return true; + } + } + // Move to the next drive letter string, which starts one + // increment after the '\0' that terminates the current string. + while (*drive_map_ptr++); + } + + // No drive matched. The path does not start with a device junction + // that is mounted as a drive letter. This means there is no drive + // letter path to the volume that holds |device_path|, so fail. + return false; +} + +bool NormalizeToNativeFilePath(const FilePath& path, FilePath* nt_path) { + base::ThreadRestrictions::AssertIOAllowed(); + // In Vista, GetFinalPathNameByHandle() would give us the real path + // from a file handle. If we ever deprecate XP, consider changing the + // code below to a call to GetFinalPathNameByHandle(). The method this + // function uses is explained in the following msdn article: + // http://msdn.microsoft.com/en-us/library/aa366789(VS.85).aspx + base::win::ScopedHandle file_handle( + ::CreateFile(path.value().c_str(), + GENERIC_READ, + kFileShareAll, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL)); + if (!file_handle) + return false; + + // Create a file mapping object. Can't easily use MemoryMappedFile, because + // we only map the first byte, and need direct access to the handle. You can + // not map an empty file, this call fails in that case. + base::win::ScopedHandle file_map_handle( + ::CreateFileMapping(file_handle.Get(), + NULL, + PAGE_READONLY, + 0, + 1, // Just one byte. No need to look at the data. + NULL)); + if (!file_map_handle) + return false; + + // Use a view of the file to get the path to the file. + void* file_view = MapViewOfFile(file_map_handle.Get(), + FILE_MAP_READ, 0, 0, 1); + if (!file_view) + return false; + + // The expansion of |path| into a full path may make it longer. + // GetMappedFileName() will fail if the result is longer than MAX_PATH. + // Pad a bit to be safe. If kMaxPathLength is ever changed to be less + // than MAX_PATH, it would be nessisary to test that GetMappedFileName() + // not return kMaxPathLength. This would mean that only part of the + // path fit in |mapped_file_path|. + const int kMaxPathLength = MAX_PATH + 10; + wchar_t mapped_file_path[kMaxPathLength]; + bool success = false; + HANDLE cp = GetCurrentProcess(); + if (::GetMappedFileNameW(cp, file_view, mapped_file_path, kMaxPathLength)) { + *nt_path = FilePath(mapped_file_path); + success = true; + } + ::UnmapViewOfFile(file_view); + return success; +} + +int GetMaximumPathComponentLength(const FilePath& path) { + base::ThreadRestrictions::AssertIOAllowed(); + + wchar_t volume_path[MAX_PATH]; + if (!GetVolumePathNameW(path.NormalizePathSeparators().value().c_str(), + volume_path, + arraysize(volume_path))) { + return -1; + } + + DWORD max_length = 0; + if (!GetVolumeInformationW(volume_path, NULL, 0, NULL, &max_length, NULL, + NULL, 0)) { + return -1; + } + + // Length of |path| with path separator appended. + size_t prefix = path.StripTrailingSeparators().value().size() + 1; + // The whole path string must be shorter than MAX_PATH. That is, it must be + // prefix + component_length < MAX_PATH (or equivalently, <= MAX_PATH - 1). + int whole_path_limit = std::max(0, MAX_PATH - 1 - static_cast(prefix)); + return std::min(whole_path_limit, static_cast(max_length)); +} + +} // namespace file_util + +namespace base { +namespace internal { + +bool MoveUnsafe(const FilePath& from_path, const FilePath& to_path) { + 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 = internal::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 CopyFileUnsafe(const FilePath& from_path, const FilePath& to_path) { + 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 CopyAndDeleteDirectory(const FilePath& from_path, + const FilePath& to_path) { + ThreadRestrictions::AssertIOAllowed(); + if (CopyDirectory(from_path, to_path, true)) { + if (DeleteFile(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; +} + +} // namespace internal +} // namespace base diff --git a/base/file_version_info.h b/base/file_version_info.h new file mode 100644 index 0000000000..59cd45df07 --- /dev/null +++ b/base/file_version_info.h @@ -0,0 +1,87 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_FILE_VERSION_INFO_H__ +#define BASE_FILE_VERSION_INFO_H__ + +#include "build/build_config.h" + +#if defined(OS_WIN) +#include +// http://blogs.msdn.com/oldnewthing/archive/2004/10/25/247180.aspx +extern "C" IMAGE_DOS_HEADER __ImageBase; +#endif // OS_WIN + +#include + +#include "base/base_export.h" +#include "base/strings/string16.h" + +namespace base { +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 +// 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 base::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(&__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/base/file_version_info_mac.h b/base/file_version_info_mac.h new file mode 100644 index 0000000000..f488cce4e5 --- /dev/null +++ b/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 + +#include "base/file_version_info.h" +#include "base/mac/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); + + base::scoped_nsobject bundle_; + + DISALLOW_COPY_AND_ASSIGN(FileVersionInfoMac); +}; + +#endif // BASE_FILE_VERSION_INFO_MAC_H_ diff --git a/base/file_version_info_mac.mm b/base/file_version_info_mac.mm new file mode 100644 index 0000000000..0c6f8d4d3c --- /dev/null +++ b/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 + +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/mac/bundle_locations.h" +#include "base/mac/foundation_util.h" +#include "base/strings/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 base::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/base/file_version_info_unittest.cc b/base/file_version_info_unittest.cc new file mode 100644 index 0000000000..5c899ba3dd --- /dev/null +++ b/base/file_version_info_unittest.cc @@ -0,0 +1,140 @@ +// Copyright (c) 2011 The Chromium Authors. All 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.h" +#include "base/files/file_path.h" +#include "base/memory/scoped_ptr.h" +#include "base/path_service.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_WIN) +#include "base/file_version_info_win.h" +#endif + +using base::FilePath; + +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 + +} // namespace + +#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 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 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 version_info( + FileVersionInfo::CreateFileVersionInfo(dll_path)); + + // Test few existing properties. + std::wstring str; + FileVersionInfoWin* version_info_win = + static_cast(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/base/file_version_info_win.cc b/base/file_version_info_win.cc new file mode 100644 index 0000000000..80dbaeaadb --- /dev/null +++ b/base/file_version_info_win.cc @@ -0,0 +1,189 @@ +// Copyright (c) 2011 The Chromium Authors. All 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 + +#include "base/file_version_info.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/threading/thread_restrictions.h" + +using base::FilePath; + +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(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/base/file_version_info_win.h b/base/file_version_info_win.h new file mode 100644 index 0000000000..a37857783a --- /dev/null +++ b/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 + +#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 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/base/files/OWNERS b/base/files/OWNERS new file mode 100644 index 0000000000..7260260bb4 --- /dev/null +++ b/base/files/OWNERS @@ -0,0 +1,2 @@ +# for file_path_watcher* +mnissler@chromium.org diff --git a/base/files/dir_reader_fallback.h b/base/files/dir_reader_fallback.h new file mode 100644 index 0000000000..a435f25963 --- /dev/null +++ b/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/base/files/dir_reader_linux.h b/base/files/dir_reader_linux.h new file mode 100644 index 0000000000..3e0721ec26 --- /dev/null +++ b/base/files/dir_reader_linux.h @@ -0,0 +1,98 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_FILES_DIR_READER_LINUX_H_ +#define BASE_FILES_DIR_READER_LINUX_H_ + +#include +#include +#include +#include +#include + +#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(&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(&buf_[offset_]); + return dirent->d_name; + } + + int fd() const { + return fd_; + } + + static bool IsFallback() { + return false; + } + + 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/base/files/dir_reader_posix.h b/base/files/dir_reader_posix.h new file mode 100644 index 0000000000..6a20ced022 --- /dev/null +++ b/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/base/files/dir_reader_posix_unittest.cc b/base/files/dir_reader_posix_unittest.cc new file mode 100644 index 0000000000..0685031a98 --- /dev/null +++ b/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 +#include +#include +#include +#include + +#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 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/base/files/file_enumerator.cc b/base/files/file_enumerator.cc new file mode 100644 index 0000000000..e49f465f67 --- /dev/null +++ b/base/files/file_enumerator.cc @@ -0,0 +1,21 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/files/file_enumerator.h" + +#include "base/file_util.h" + +namespace base { + +FileEnumerator::FileInfo::~FileInfo() { +} + +bool FileEnumerator::ShouldSkip(const FilePath& path) { + FilePath::StringType basename = path.BaseName().value(); + return basename == FILE_PATH_LITERAL(".") || + (basename == FILE_PATH_LITERAL("..") && + !(INCLUDE_DOT_DOT & file_type_)); +} + +} // namespace base diff --git a/base/files/file_enumerator.h b/base/files/file_enumerator.h new file mode 100644 index 0000000000..ce9bd1fd26 --- /dev/null +++ b/base/files/file_enumerator.h @@ -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. + +#ifndef BASE_FILES_FILE_ENUMERATOR_H_ +#define BASE_FILES_FILE_ENUMERATOR_H_ + +#include +#include + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "base/time/time.h" +#include "build/build_config.h" + +#if defined(OS_WIN) +#include +#elif defined(OS_POSIX) +#include +#include +#endif + +namespace base { + +// A class for enumerating the files in a provided path. The order of the +// results is not guaranteed. +// +// This is blocking. Do not use on critical threads. +// +// Example: +// +// base::FileEnumerator enum(my_dir, false, base::FileEnumerator::FILES, +// FILE_PATH_LITERAL("*.txt")); +// for (base::FilePath name = enum.Next(); !name.empty(); name = enum.Next()) +// ... +class BASE_EXPORT FileEnumerator { + public: + // Note: copy & assign supported. + class BASE_EXPORT FileInfo { + public: + FileInfo(); + ~FileInfo(); + + bool IsDirectory() const; + + // The name of the file. This will not include any path information. This + // is in constrast to the value returned by FileEnumerator.Next() which + // includes the |root_path| passed into the FileEnumerator constructor. + FilePath GetName() const; + + int64 GetSize() const; + Time GetLastModifiedTime() const; + +#if defined(OS_WIN) + const WIN32_FIND_DATA& find_data() const { return find_data_; } +#elif defined(OS_POSIX) + const struct stat& stat() const { return stat_; } +#endif + + private: + friend class FileEnumerator; + +#if defined(OS_WIN) + WIN32_FIND_DATA find_data_; +#elif defined(OS_POSIX) + struct stat stat_; + FilePath filename_; +#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 the next file or an empty string if there are no more results. + // + // The returned path will incorporate the |root_path| passed in the + // constructor: "/file_name.txt". If the |root_path| is absolute, + // then so will be the result of Next(). + FilePath Next(); + + // Write the file info into |info|. + FileInfo GetInfo() const; + + 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) + + // Read the filenames in source into the vector of DirectoryEntryInfo's + static bool ReadDirectory(std::vector* entries, + const FilePath& source, bool show_links); + + // The files in the current directory + std::vector 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 pending_paths_; + + DISALLOW_COPY_AND_ASSIGN(FileEnumerator); +}; + +} // namespace base + +#endif // BASE_FILES_FILE_ENUMERATOR_H_ diff --git a/base/files/file_enumerator_posix.cc b/base/files/file_enumerator_posix.cc new file mode 100644 index 0000000000..7533a24c35 --- /dev/null +++ b/base/files/file_enumerator_posix.cc @@ -0,0 +1,160 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/files/file_enumerator.h" + +#include +#include +#include + +#include "base/logging.h" +#include "base/threading/thread_restrictions.h" + +namespace base { + +// FileEnumerator::FileInfo ---------------------------------------------------- + +FileEnumerator::FileInfo::FileInfo() { + memset(&stat_, 0, sizeof(stat_)); +} + +bool FileEnumerator::FileInfo::IsDirectory() const { + return S_ISDIR(stat_.st_mode); +} + +FilePath FileEnumerator::FileInfo::GetName() const { + return filename_; +} + +int64 FileEnumerator::FileInfo::GetSize() const { + return stat_.st_size; +} + +base::Time FileEnumerator::FileInfo::GetLastModifiedTime() const { + return base::Time::FromTimeT(stat_.st_mtime); +} + +// 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 entries; + if (!ReadDirectory(&entries, root_path_, file_type_ & SHOW_SYM_LINKS)) + continue; + + directory_entries_.clear(); + current_directory_entry_ = 0; + for (std::vector::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_); +} + +FileEnumerator::FileInfo FileEnumerator::GetInfo() const { + return directory_entries_[current_directory_entry_]; +} + +bool FileEnumerator::ReadDirectory(std::vector* 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) + #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) { + FileInfo 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; +} + +} // namespace base diff --git a/base/files/file_enumerator_win.cc b/base/files/file_enumerator_win.cc new file mode 100644 index 0000000000..e47f5421a7 --- /dev/null +++ b/base/files/file_enumerator_win.cc @@ -0,0 +1,151 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/files/file_enumerator.h" + +#include + +#include "base/logging.h" +#include "base/threading/thread_restrictions.h" + +namespace base { + +// FileEnumerator::FileInfo ---------------------------------------------------- + +FileEnumerator::FileInfo::FileInfo() { + memset(&find_data_, 0, sizeof(find_data_)); +} + +bool FileEnumerator::FileInfo::IsDirectory() const { + return (find_data_.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; +} + +FilePath FileEnumerator::FileInfo::GetName() const { + return FilePath(find_data_.cFileName); +} + +int64 FileEnumerator::FileInfo::GetSize() const { + ULARGE_INTEGER size; + size.HighPart = find_data_.nFileSizeHigh; + size.LowPart = find_data_.nFileSizeLow; + DCHECK_LE(size.QuadPart, std::numeric_limits::max()); + return static_cast(size.QuadPart); +} + +base::Time FileEnumerator::FileInfo::GetLastModifiedTime() const { + return base::Time::FromFileTime(find_data_.ftLastWriteTime); +} + +// 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_); +} + +FileEnumerator::FileInfo FileEnumerator::GetInfo() const { + if (!has_find_data_) { + NOTREACHED(); + return FileInfo(); + } + FileInfo ret; + memcpy(&ret.find_data_, &find_data_, sizeof(find_data_)); + return ret; +} + +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. However, don't do recursion through reparse points or we + // may end up with an infinite cycle. + if (!(find_data_.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) + pending_paths_.push(cur_file); + } + if (file_type_ & FileEnumerator::DIRECTORIES) + return cur_file; + } else if (file_type_ & FileEnumerator::FILES) { + return cur_file; + } + } + + return FilePath(); +} + +} // namespace base diff --git a/base/files/file_path.cc b/base/files/file_path.cc new file mode 100644 index 0000000000..be34634293 --- /dev/null +++ b/base/files/file_path.cc @@ -0,0 +1,1300 @@ +// Copyright (c) 2012 The Chromium Authors. All 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.h" + +#include +#include + +#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/strings/string_piece.h" +#include "base/strings/string_util.h" +#include "base/strings/sys_string_conversions.h" +#include "base/strings/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 +#elif defined(OS_MACOSX) +#include +#endif + +namespace base { + +typedef FilePath::StringType StringType; + +namespace { + +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, + FilePath::kSeparatorsLength - 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 < kSeparatorsLength - 1; ++i) { + if (character == kSeparators[i]) { + return true; + } + } + + return false; +} + +void FilePath::GetComponents(std::vector* components) const { + DCHECK(components); + if (!components) + return; + components->clear(); + if (value().empty()) + return; + + std::vector 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(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 parent_components; + std::vector child_components; + GetComponents(&parent_components); + child.GetComponents(&child_components); + + if (parent_components.empty() || + parent_components.size() >= child_components.size()) + return false; + + std::vector::const_iterator parent_comp = + parent_components.begin(); + std::vector::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, + kSeparatorsLength - 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, + kSeparatorsLength - 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 StringPiece& suffix) + const { + DCHECK(IsStringASCII(suffix)); +#if defined(OS_WIN) + return InsertBeforeExtension(ASCIIToUTF16(suffix.as_string())); +#elif defined(OS_POSIX) + 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 StringPiece& component) const { + DCHECK(IsStringASCII(component)); +#if defined(OS_WIN) + return Append(ASCIIToUTF16(component.as_string())); +#elif defined(OS_POSIX) + return Append(component.as_string()); +#endif +} + +bool FilePath::IsAbsolute() const { + return IsPathAbsolute(path_); +} + +bool FilePath::EndsWithSeparator() const { + if (empty()) + return false; + return IsSeparator(path_[path_.size() - 1]); +} + +FilePath FilePath::AsEndingWithSeparator() const { + if (EndsWithSeparator() || path_.empty()) + return *this; + + StringType path_str; + path_str.reserve(path_.length() + 1); // Only allocate string once. + + path_str = path_; + path_str.append(&kSeparators[0], 1); + return FilePath(path_str); +} + +FilePath FilePath::StripTrailingSeparators() const { + FilePath new_path(path_); + new_path.StripTrailingSeparatorsInternal(); + + return new_path; +} + +bool FilePath::ReferencesParent() const { + std::vector components; + GetComponents(&components); + + std::vector::const_iterator it = components.begin(); + for (; it != components.end(); ++it) { + const StringType& component = *it; + // Windows has odd, undocumented behavior with path components containing + // only whitespace and . characters. So, if all we see is . and + // whitespace, then we treat any .. sequence as referencing parent. + // For simplicity we enforce this on all platforms. + if (component.find_first_not_of(FILE_PATH_LITERAL(". \n\r\t")) == + std::string::npos && + component.find(kParentDirectory) != std::string::npos) { + return true; + } + } + return false; +} + +#if defined(OS_POSIX) +// 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(SysNativeMBToWide(path_)); +} + +std::string FilePath::MaybeAsASCII() const { + if (IsStringASCII(path_)) + return path_; + return std::string(); +} + +std::string FilePath::AsUTF8Unsafe() const { +#if defined(OS_MACOSX) || defined(OS_CHROMEOS) + return value(); +#else + return WideToUTF8(SysNativeMBToWide(value())); +#endif +} + +string16 FilePath::AsUTF16Unsafe() const { +#if defined(OS_MACOSX) || defined(OS_CHROMEOS) + return UTF8ToUTF16(value()); +#else + return WideToUTF16(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(SysWideToNativeMB(wstring)); +} + +// static +FilePath FilePath::FromUTF8Unsafe(const std::string& utf8) { +#if defined(OS_MACOSX) || defined(OS_CHROMEOS) + return FilePath(utf8); +#else + return FilePath(SysWideToNativeMB(UTF8ToWide(utf8))); +#endif +} + +// static +FilePath FilePath::FromUTF16Unsafe(const string16& utf16) { +#if defined(OS_MACOSX) || defined(OS_CHROMEOS) + return FilePath(UTF16ToUTF8(utf16)); +#else + return FilePath(SysWideToNativeMB(UTF16ToWide(utf16))); +#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()); +} + +string16 FilePath::AsUTF16Unsafe() const { + return value(); +} + +// static +FilePath FilePath::FromWStringHack(const std::wstring& wstring) { + return FilePath(wstring); +} + +// static +FilePath FilePath::FromUTF8Unsafe(const std::string& utf8) { + return FilePath(UTF8ToWide(utf8)); +} + +// static +FilePath FilePath::FromUTF16Unsafe(const string16& utf16) { + return FilePath(utf16); +} +#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) { + ScopedCFTypeRef cfstring( + CFStringCreateWithBytesNoCopy( + NULL, + reinterpret_cast(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(); + ScopedCFTypeRef cfstring1( + CFStringCreateWithBytesNoCopy( + NULL, + reinterpret_cast(string1.c_str()), + string1.length(), + kCFStringEncodingUTF8, + false, + kCFAllocatorNull)); + ScopedCFTypeRef cfstring2( + CFStringCreateWithBytesNoCopy( + NULL, + reinterpret_cast(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 < kSeparatorsLength; ++i) { + std::replace(copy.begin(), copy.end(), kSeparators[i], kSeparators[0]); + } + return FilePath(copy); +#else + return *this; +#endif +} + +} // namespace base + +void PrintTo(const base::FilePath& path, std::ostream* out) { + *out << path.value(); +} diff --git a/base/files/file_path.h b/base/files/file_path.h new file mode 100644 index 0000000000..8b69ea493a --- /dev/null +++ b/base/files/file_path.h @@ -0,0 +1,458 @@ +// Copyright (c) 2012 The Chromium 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_FILES_FILE_PATH_H_ +#define BASE_FILES_FILE_PATH_H_ + +#include +#include +#include + +#include "base/base_export.h" +#include "base/compiler_specific.h" +#include "base/containers/hash_tables.h" +#include "base/strings/string16.h" +#include "base/strings/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) +#define FILE_PATH_USES_DRIVE_LETTERS +#define FILE_PATH_USES_WIN_SEPARATORS +#endif // OS_WIN + +class Pickle; +class PickleIterator; + +namespace base { + +// An abstraction to isolate users from the differences between native +// pathnames on different platforms. +class BASE_EXPORT FilePath { + public: +#if defined(OS_POSIX) + // 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[]; + + // arraysize(kSeparators). + static const size_t kSeparatorsLength; + + // 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* 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 true if the patch ends with a path separator character. + bool EndsWithSeparator() const WARN_UNUSED_RESULT; + + // Returns a copy of this FilePath that ends with a trailing separator. If + // the input path is empty, an empty FilePath will be returned. + FilePath AsEndingWithSeparator() const WARN_UNUSED_RESULT; + + // 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; + + // Similar to AsUTF8Unsafe, but returns UTF-16 instead. + string16 AsUTF16Unsafe() const; + + // Older Chromium code assumes that paths are always wstrings. + // This function converts wstrings to FilePaths, and is + // useful to smooth porting that old code to the FilePath API. + // It has "Hack" its name so people feel bad about using it. + // http://code.google.com/p/chromium/issues/detail?id=24672 + // + // If you are trying to be a good citizen and remove these, ask yourself: + // - Am I interacting with other Chrome code that deals with files? Then + // try to convert the API into using FilePath. + // - Am I interacting with OS-native calls? Then use value() to get at an + // OS-native string format. + // - Am I using well-known file names, like "config.ini"? Then use the + // ASCII functions (we require paths to always be supersets of ASCII). + // - Am I displaying a string to the user in some UI? Then use the + // LossyDisplayName() function, but keep in mind that you can't + // ever use the result of that again as a path. + static FilePath FromWStringHack(const std::wstring& wstring); + + // Returns a FilePath object from a path name in UTF-8. This function + // should only be used for cases where you are sure that the input + // string is UTF-8. + // + // 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); + + // Similar to FromUTF8Unsafe, but accepts UTF-16 instead. + static FilePath FromUTF16Unsafe(const string16& utf16); + + 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_; +}; + +} // namespace base + +// This is required by googletest to print a readable output on test failures. +BASE_EXPORT extern void PrintTo(const base::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) +#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(COMPILER_GCC) + +template<> +struct hash { + size_t operator()(const base::FilePath& f) const { + return hash()(f.value()); + } +}; + +#elif defined(COMPILER_MSVC) + +inline size_t hash_value(const base::FilePath& f) { + return hash_value(f.value()); +} + +#endif // COMPILER + +} // namespace BASE_HASH_NAMESPACE + +#endif // BASE_FILES_FILE_PATH_H_ diff --git a/base/files/file_path_constants.cc b/base/files/file_path_constants.cc new file mode 100644 index 0000000000..34b17a60b7 --- /dev/null +++ b/base/files/file_path_constants.cc @@ -0,0 +1,22 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/files/file_path.h" + +namespace base { + +#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 size_t FilePath::kSeparatorsLength = arraysize(kSeparators); + +const FilePath::CharType FilePath::kCurrentDirectory[] = FILE_PATH_LITERAL("."); +const FilePath::CharType FilePath::kParentDirectory[] = FILE_PATH_LITERAL(".."); + +const FilePath::CharType FilePath::kExtensionSeparator = FILE_PATH_LITERAL('.'); + +} // namespace base diff --git a/base/files/file_path_unittest.cc b/base/files/file_path_unittest.cc new file mode 100644 index 0000000000..8b2fcf531c --- /dev/null +++ b/base/files/file_path_unittest.cc @@ -0,0 +1,1231 @@ +// Copyright (c) 2012 The Chromium Authors. All 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/files/file_path.h" +#include "base/strings/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) + +namespace base { + +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) + 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 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() => "SS", NOT ! + // However, neither Windows nor Mac OSX converts these. + // (or even have glyphs for ) + { { FPL("\u00DF"), FPL("\u00DF") }, 0}, + { { FPL("\u1E9E"), FPL("\u1E9E") }, 0}, + { { FPL("\u00DF"), FPL("\u1E9E") }, -1}, + { { FPL("SS"), FPL("\u00DF") }, -1}, + { { FPL("SS"), FPL("\u1E9E") }, -1}, +#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(".. "), true }, + { FPL(" .."), true }, + { 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" }, + // "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" }, + }; + + 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_EQ(3U, FPS("a\0b").length()); + + // Test constructor strips '\0' + FilePath path(FPS("a\0b")); + EXPECT_EQ(1U, path.value().length()); + EXPECT_EQ(FPL("a"), path.value()); +} + +TEST_F(FilePathTest, AppendWithNUL) { + // Assert FPS() works. + ASSERT_EQ(3U, FPS("b\0b").length()); + + // Test Append() strips '\0' + FilePath path(FPL("a")); + path = path.Append(FPS("b\0b")); + EXPECT_EQ(3U, path.value().length()); +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + EXPECT_EQ(FPL("a\\b"), path.value()); +#else + EXPECT_EQ(FPL("a/b"), path.value()); +#endif +} + +TEST_F(FilePathTest, ReferencesParentWithNUL) { + // Assert FPS() works. + ASSERT_EQ(3U, FPS("..\0").length()); + + // 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 + +TEST_F(FilePathTest, EndsWithSeparator) { + const UnaryBooleanTestData cases[] = { + { FPL(""), false }, + { FPL("/"), true }, + { FPL("foo/"), true }, + { FPL("bar"), false }, + { FPL("/foo/bar"), false }, + }; + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath input = FilePath(cases[i].input).NormalizePathSeparators(); + EXPECT_EQ(cases[i].expected, input.EndsWithSeparator()); + } +} + +TEST_F(FilePathTest, AsEndingWithSeparator) { + const UnaryTestData cases[] = { + { FPL(""), FPL("") }, + { FPL("/"), FPL("/") }, + { FPL("foo"), FPL("foo/") }, + { FPL("foo/"), FPL("foo/") } + }; + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath input = FilePath(cases[i].input).NormalizePathSeparators(); + FilePath expected = FilePath(cases[i].expected).NormalizePathSeparators(); + EXPECT_EQ(expected.value(), input.AsEndingWithSeparator().value()); + } +} + +} // namespace base diff --git a/base/files/file_path_watcher.cc b/base/files/file_path_watcher.cc new file mode 100644 index 0000000000..49e0a237f6 --- /dev/null +++ b/base/files/file_path_watcher.cc @@ -0,0 +1,39 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// 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/message_loop.h" + +namespace base { + +FilePathWatcher::~FilePathWatcher() { + impl_->Cancel(); +} + +// static +void FilePathWatcher::CancelWatch( + const scoped_refptr& delegate) { + delegate->CancelOnMessageLoopThread(); +} + +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, callback); +} + +} // namespace base diff --git a/base/files/file_path_watcher.h b/base/files/file_path_watcher.h new file mode 100644 index 0000000000..3c1941f012 --- /dev/null +++ b/base/files/file_path_watcher.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. + +// 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/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop/message_loop_proxy.h" + +namespace base { + +// This class lets you register interest in changes on a FilePath. +// The callback 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 Callback; + + // Used internally to encapsulate different members on different platforms. + class PlatformDelegate : public base::RefCountedThreadSafe { + public: + PlatformDelegate(); + + // Start watching for the given |path| and notify |delegate| about changes. + virtual bool Watch(const FilePath& path, + bool recursive, + const Callback& callback) 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; + 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 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 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& delegate); + + // 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 impl_; + + DISALLOW_COPY_AND_ASSIGN(FilePathWatcher); +}; + +} // namespace base + +#endif // BASE_FILES_FILE_PATH_WATCHER_H_ diff --git a/base/files/file_path_watcher_browsertest.cc b/base/files/file_path_watcher_browsertest.cc new file mode 100644 index 0000000000..69ff80608a --- /dev/null +++ b/base/files/file_path_watcher_browsertest.cc @@ -0,0 +1,909 @@ +// Copyright (c) 2012 The Chromium Authors. All 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 +#include +#elif defined(OS_POSIX) +#include +#endif + +#include + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/compiler_specific.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/run_loop.h" +#include "base/stl_util.h" +#include "base/strings/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 { + +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 { + 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() {} + + 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::QuitWhenIdleClosure()); + } + + // Set of registered delegates. + std::set delegates_; + + // Set of signaled delegates. + std::set signaled_; + + // The loop we should break after all delegates signaled. + scoped_refptr loop_; +}; + +class TestDelegateBase : public SupportsWeakPtr { + 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); + } + virtual ~TestDelegate() {} + + virtual void OnFileChanged(const FilePath& path, bool error) OVERRIDE { + if (error) + ADD_FAILURE() << "Error " << path.value(); + else + collector_->OnChange(this); + } + + private: + scoped_refptr 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->QuitWhenIdleClosure()); +} + +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 { + RunLoop().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(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 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 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 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 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(base::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 delegate(new TestDelegate(collector())); + ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get(), false)); + + // Now make sure we get notified if the file is deleted. + base::DeleteFile(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) { + } + virtual ~Deleter() {} + + virtual void OnFileChanged(const FilePath&, bool) OVERRIDE { + watcher_.reset(); + loop_->PostTask(FROM_HERE, MessageLoop::QuitWhenIdleClosure()); + } + + FilePathWatcher* watcher() const { return watcher_.get(); } + + private: + scoped_ptr 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(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 (and ARM linux): http://crbug.com/85930 +TEST_F(FilePathWatcherTest, DISABLED_DestroyWithPendingNotification) { + scoped_ptr 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 delegate1(new TestDelegate(collector())); + scoped_ptr 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 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(base::DeleteFile(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 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 delegate(new TestDelegate(collector())); + ASSERT_TRUE(SetupWatch(file, &watcher, delegate.get(), false)); + + FilePath sub_path(temp_dir_.path()); + for (std::vector::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 delegate(new TestDelegate(collector())); + ASSERT_TRUE(SetupWatch(file, &watcher, delegate.get(), false)); + + ASSERT_TRUE(base::DeleteFile(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 delegate(new TestDelegate(collector())); + ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get(), false)); + + ASSERT_TRUE(base::DeleteFile(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 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(base::DeleteFile(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 file_delegate(new TestDelegate(collector())); + ASSERT_TRUE(SetupWatch(file, &file_watcher, file_delegate.get(), false)); + scoped_ptr 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. + base::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 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(base::DeleteFile(subdir_file1, false)); + ASSERT_TRUE(WaitForEvents()); + + // Delete "$dir/subdir/subdir_child_dir/child_dir_file1". + ASSERT_TRUE(base::DeleteFile(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 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 file_delegate(new TestDelegate(collector())); + ASSERT_TRUE(SetupWatch(dest_file, &file_watcher, file_delegate.get(), false)); + scoped_ptr 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(base::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 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 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 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(base::DeleteFile(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 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 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 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(base::DeleteFile(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 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(base::DeleteFile(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 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(base::DeleteFile(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 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(base::DeleteFile(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(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(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 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::QuitWhenIdleClosure(), + 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 base diff --git a/base/files/file_path_watcher_kqueue.cc b/base/files/file_path_watcher_kqueue.cc new file mode 100644 index 0000000000..2ffb83622c --- /dev/null +++ b/base/files/file_path_watcher_kqueue.cc @@ -0,0 +1,518 @@ +// Copyright (c) 2012 The Chromium Authors. All 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 +#include +#include + +#include + +#include "base/bind.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/strings/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 { + +// 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, + const FilePathWatcher::Callback& callback) 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 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 or 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 uintptr_t FileDescriptorForPath(const FilePath& path); + + static const uintptr_t kNoFileDescriptor = static_cast(-1); + + // Closes |*fd| and sets |*fd| to -1. + static void CloseFileDescriptor(uintptr_t* fd); + + // Returns true if kevent has open file descriptor. + static bool IsKeventFileDescriptorOpen(const struct kevent& event) { + return event.ident != kNoFileDescriptor; + } + + static EventData* EventDataForKevent(const struct kevent& event) { + return reinterpret_cast(event.udata); + } + + EventVector events_; + scoped_refptr io_message_loop_; + MessageLoopForIO::FileDescriptorWatcher kqueue_watcher_; + FilePathWatcher::Callback callback_; + FilePath target_; + int kqueue_; + + DISALLOW_COPY_AND_ASSIGN(FilePathWatcherImpl); +}; + +void FilePathWatcherImpl::ReleaseEvent(struct kevent& event) { + CloseFileDescriptor(&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 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::iterator i = components.begin(); + i != components.end(); ++i) { + if (i == components.begin()) { + built_path = FilePath(*i); + } else { + built_path = built_path.Append(*i); + } + uintptr_t fd = kNoFileDescriptor; + if (path_still_exists) { + fd = FileDescriptorForPath(built_path); + if (fd == kNoFileDescriptor) { + 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; +} + +uintptr_t FilePathWatcherImpl::FileDescriptorForPath(const FilePath& path) { + int fd = HANDLE_EINTR(open(path.value().c_str(), O_EVTONLY)); + if (fd == -1) + return kNoFileDescriptor; + return fd; +} + +void FilePathWatcherImpl::CloseFileDescriptor(uintptr_t* fd) { + if (*fd == kNoFileDescriptor) { + return; + } + + if (HANDLE_EINTR(close(*fd)) != 0) { + DPLOG(ERROR) << "close"; + } + *fd = kNoFileDescriptor; +} + +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 %ld", reinterpret_cast(&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. + uintptr_t have_access = FileDescriptorForPath(next_event_data->path_); + if (have_access == kNoFileDescriptor) { + *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(&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(&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; + // Check to see if it already has a valid file descriptor. + if (!IsKeventFileDescriptorOpen(*next_event)) { + EventData* next_event_data = EventDataForKevent(*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)) { + callback_.Run(target_, true /* error */); + 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 (event == events_.end() || !IsKeventFileDescriptorOpen(*event)) { + // 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)) { + callback_.Run(target_, true /* error */); + Cancel(); + } + } + + if (send_notification) { + callback_.Run(target_, false); + } +} + +void FilePathWatcherImpl::OnFileCanWriteWithoutBlocking(int fd) { + NOTREACHED(); +} + +void FilePathWatcherImpl::WillDestroyCurrentMessageLoop() { + CancelOnMessageLoopThread(); +} + +bool FilePathWatcherImpl::Watch(const FilePath& path, + bool recursive, + const FilePathWatcher::Callback& callback) { + DCHECK(MessageLoopForIO::current()); + DCHECK(target_.value().empty()); // Can only watch one path. + DCHECK(!callback.is_null()); + DCHECK_EQ(kqueue_, -1); + + if (recursive) { + // Recursive watch is not supported on this platform. + NOTIMPLEMENTED(); + return false; + } + + callback_ = callback; + 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(); + if (HANDLE_EINTR(close(kqueue_)) != 0) { + DPLOG(ERROR) << "close kqueue"; + } + kqueue_ = -1; + std::for_each(events_.begin(), events_.end(), ReleaseEvent); + events_.clear(); + io_message_loop_ = NULL; + MessageLoop::current()->RemoveDestructionObserver(this); + callback_.Reset(); + } +} + +} // namespace + +FilePathWatcher::FilePathWatcher() { + impl_ = new FilePathWatcherImpl(); +} + +} // namespace base diff --git a/base/files/file_path_watcher_linux.cc b/base/files/file_path_watcher_linux.cc new file mode 100644 index 0000000000..86a4226bf3 --- /dev/null +++ b/base/files/file_path_watcher_linux.cc @@ -0,0 +1,487 @@ +// Copyright (c) 2012 The Chromium Authors. All 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "base/bind.h" +#include "base/containers/hash_tables.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/lazy_instance.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/posix/eintr_wrapper.h" +#include "base/synchronization/lock.h" +#include "base/threading/thread.h" + +namespace base { + +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; + + typedef std::set WatcherSet; + + InotifyReader(); + ~InotifyReader(); + + // We keep track of which delegates want to be notified on which watches. + base::hash_map 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, + const FilePathWatcher::Callback& callback) 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 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; + + // Callback to notify upon changes. + FilePathWatcher::Callback callback_; + + // 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 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(&buffer[i]); + size_t event_size = sizeof(inotify_event) + event->len; + DCHECK(i + event_size <= static_cast(bytes_read)); + reader->OnInotifyEvent(event); + i += event_size; + } + } +} + +static base::LazyInstance::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() { +} + +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()) { + callback_.Run(target_, true /* error */); + 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 && PathExists(target_))) { + callback_.Run(target_, false); + return; + } + } + } +} + +bool FilePathWatcherImpl::Watch(const FilePath& path, + bool recursive, + const FilePathWatcher::Callback& callback) { + 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().get()); + callback_ = callback; + target_ = path; + MessageLoop::current()->AddDestructionObserver(this); + + std::vector comps; + target_.GetComponents(&comps); + DCHECK(!comps.empty()); + std::vector::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 (callback_.is_null()) { + // 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 (!callback_.is_null()) { + MessageLoop::current()->RemoveDestructionObserver(this); + callback_.Reset(); + } + + 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 base diff --git a/base/files/file_path_watcher_stub.cc b/base/files/file_path_watcher_stub.cc new file mode 100644 index 0000000000..afca0dab96 --- /dev/null +++ b/base/files/file_path_watcher_stub.cc @@ -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. + +// 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 { + +class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate { + public: + virtual bool Watch(const FilePath& path, + bool recursive, + const FilePathWatcher::Callback& callback) OVERRIDE { + return false; + } + + virtual void Cancel() OVERRIDE {} + + virtual void CancelOnMessageLoopThread() OVERRIDE {} + + protected: + virtual ~FilePathWatcherImpl() {} +}; + +} // namespace + +FilePathWatcher::FilePathWatcher() { + impl_ = new FilePathWatcherImpl(); +} + +} // namespace base diff --git a/base/files/file_path_watcher_win.cc b/base/files/file_path_watcher_win.cc new file mode 100644 index 0000000000..ac092a931b --- /dev/null +++ b/base/files/file_path_watcher_win.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. + +#include "base/files/file_path_watcher.h" + +#include "base/bind.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/time/time.h" +#include "base/win/object_watcher.h" + +namespace base { + +namespace { + +class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate, + public base::win::ObjectWatcher::Delegate, + public MessageLoop::DestructionObserver { + public: + FilePathWatcherImpl() + : handle_(INVALID_HANDLE_VALUE), + recursive_watch_(false) {} + + // FilePathWatcher::PlatformDelegate overrides. + virtual bool Watch(const FilePath& path, + bool recursive, + const FilePathWatcher::Callback& callback) 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; + + // Callback to notify upon changes. + FilePathWatcher::Callback callback_; + + // Path we're supposed to watch (passed to callback). + 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, + const FilePathWatcher::Callback& callback) { + DCHECK(target_.value().empty()); // Can only watch one path. + + set_message_loop(base::MessageLoopProxy::current()); + callback_ = callback; + target_ = path; + recursive_watch_ = recursive; + MessageLoop::current()->AddDestructionObserver(this); + + if (!UpdateWatch()) + return false; + + watcher_.StartWatching(handle_, this); + + return true; +} + +void FilePathWatcherImpl::Cancel() { + if (callback_.is_null()) { + // 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 (!callback_.is_null()) { + MessageLoop::current()->RemoveDestructionObserver(this); + callback_.Reset(); + } +} + +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 keep_alive(this); + + if (!UpdateWatch()) { + callback_.Run(target_, true /* error */); + return; + } + + // Check whether the event applies to |target_| and notify the callback. + 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(); + callback_.Run(target_, false); + } else if (file_exists && !first_notification_.is_null()) { + // The target's last modification time is equal to what's on record. This + // 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(); + } + callback_.Run(target_, false); + } else if (!file_exists && !last_modified_.is_null()) { + last_modified_ = base::Time(); + callback_.Run(target_, false); + } + + // 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 (!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 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 base diff --git a/base/files/file_util_proxy.cc b/base/files/file_util_proxy.cc new file mode 100644 index 0000000000..5f6d405a90 --- /dev/null +++ b/base/files/file_util_proxy.cc @@ -0,0 +1,414 @@ +// Copyright (c) 2012 The Chromium Authors. All 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_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/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) { + DCHECK(!callback.is_null()); + callback.Run(error_, PassPlatformFile(&file_handle_), created_); + } + + private: + scoped_refptr 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_.get(), 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 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 (!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_ptr 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_ptr 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 (!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 (!PathExists(file_path)) { + return PLATFORM_FILE_ERROR_NOT_FOUND; + } + if (!base::DeleteFile(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::DeleteFile(TaskRunner* task_runner, + const FilePath& file_path, + bool recursive, + const StatusCallback& callback) { + return base::PostTaskAndReplyWithResult( + task_runner, FROM_HERE, + Bind(&DeleteAdapter, file_path, 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)); +} + +// 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)); +} + +// 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::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)); +} + +// 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/base/files/file_util_proxy.h b/base/files/file_util_proxy.h new file mode 100644 index 0000000000..bded161200 --- /dev/null +++ b/base/files/file_util_proxy.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_FILES_FILE_UTIL_PROXY_H_ +#define BASE_FILES_FILE_UTIL_PROXY_H_ + +#include "base/base_export.h" +#include "base/callback_forward.h" +#include "base/files/file_path.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: + // 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 StatusCallback; + + typedef Callback CreateOrOpenCallback; + typedef Callback CreateTemporaryCallback; + typedef Callback GetFileInfoCallback; + typedef Callback ReadCallback; + typedef Callback WriteCallback; + + typedef Callback CreateOrOpenTask; + typedef Callback CloseTask; + typedef Callback 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. + // + // This returns false if task posting to |task_runner| has failed. + 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. + // + // This returns false if task posting to |task_runner| has failed. + static bool CreateTemporary( + TaskRunner* task_runner, + int additional_file_flags, + const CreateTemporaryCallback& callback); + + // Close the given file handle. + // This returns false if task posting to |task_runner| has failed. + static bool Close(TaskRunner* task_runner, + PlatformFile, + const StatusCallback& callback); + + // Retrieves the information about a file. It is invalid to pass a null + // callback. + // This returns false if task posting to |task_runner| has failed. + static bool GetFileInfo( + TaskRunner* task_runner, + const FilePath& file_path, + const GetFileInfoCallback& callback); + + // Does the same as GetFileInfo but takes PlatformFile instead of FilePath. + // This returns false if task posting to |task_runner| has failed. + 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. + // This returns false if task posting to |task_runner| has failed. + static bool DeleteFile(TaskRunner* task_runner, + const FilePath& file_path, + bool recursive, + 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. + // + // This returns false if |bytes_to_read| is less than zero, or + // if task posting to |task_runner| has failed. + 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. + // + // This returns false if |bytes_to_write| is less than or equal to zero, + // if |buffer| is NULL, or if task posting to |task_runner| has failed. + static bool Write( + TaskRunner* task_runner, + PlatformFile file, + int64 offset, + const char* buffer, + int bytes_to_write, + const WriteCallback& callback); + + // Touches a file. The callback can be null. + // This returns false if task posting to |task_runner| has failed. + 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. + // This returns false if task posting to |task_runner| has failed. + static bool Touch( + TaskRunner* task_runner, + const FilePath& file_path, + const Time& last_access_time, + const Time& last_modified_time, + 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. + // This returns false if task posting to |task_runner| has failed. + 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. + // This returns false if task posting to |task_runner| has failed. + static bool Truncate( + TaskRunner* task_runner, + const FilePath& path, + int64 length, + const StatusCallback& callback); + + // Flushes a file. The callback can be null. + // This returns false if task posting to |task_runner| has failed. + static bool Flush( + TaskRunner* task_runner, + PlatformFile file, + const StatusCallback& callback); + + // Relay helpers. + // They return false if posting a given task to |task_runner| has failed. + 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_FILES_FILE_UTIL_PROXY_H_ diff --git a/base/files/file_util_proxy_unittest.cc b/base/files/file_util_proxy_unittest.cc new file mode 100644 index 0000000000..3c67075f48 --- /dev/null +++ b/base/files/file_util_proxy_unittest.cc @@ -0,0 +1,411 @@ +// Copyright (c) 2012 The Chromium Authors. All 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_util_proxy.h" + +#include + +#include "base/bind.h" +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/logging.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/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_(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()->QuitWhenIdle(); + } + + void DidCreateOrOpen(PlatformFileError error, + PassPlatformFile file, + bool created) { + error_ = error; + file_ = file.ReleaseValue(); + created_ = created; + MessageLoop::current()->QuitWhenIdle(); + } + + void DidCreateTemporary(PlatformFileError error, + PassPlatformFile file, + const FilePath& path) { + error_ = error; + file_ = file.ReleaseValue(); + path_ = path; + MessageLoop::current()->QuitWhenIdle(); + } + + void DidGetFileInfo(PlatformFileError error, + const PlatformFileInfo& file_info) { + error_ = error; + file_info_ = file_info; + MessageLoop::current()->QuitWhenIdle(); + } + + void DidRead(PlatformFileError error, + const char* data, + int bytes_read) { + error_ = error; + buffer_.resize(bytes_read); + memcpy(&buffer_[0], data, bytes_read); + MessageLoop::current()->QuitWhenIdle(); + } + + void DidWrite(PlatformFileError error, + int bytes_written) { + error_ = error; + bytes_written_ = bytes_written; + MessageLoop::current()->QuitWhenIdle(); + } + + 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 buffer_; + int bytes_written_; + WeakPtrFactory 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(PathExists(test_path())); +} + +TEST_F(FileUtilProxyTest, CreateOrOpen_Open) { + // Creates a file. + file_util::WriteFile(test_path(), NULL, 0); + ASSERT_TRUE(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(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(base::Move(test_path(), + test_dir_path().AppendASCII("new"))); +#endif + + FileUtilProxy::Close( + file_task_runner(), + file, + Bind(&FileUtilProxyTest::DidFinish, weak_factory_.GetWeakPtr())); + MessageLoop::current()->Run(); + EXPECT_EQ(PLATFORM_FILE_OK, error_); + + // Now it should pass on all platforms. + EXPECT_TRUE(base::Move(test_path(), test_dir_path().AppendASCII("new"))); +} + +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(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(base::DeleteFile(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(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_); + + // 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]); + } +} + +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(last_modified_time.ToDoubleT()), + static_cast(info.last_modified.ToDoubleT())); + EXPECT_EQ(static_cast(last_accessed_time.ToDoubleT()), + static_cast(info.last_accessed.ToDoubleT())); +} + +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); + + 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); + + 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/base/files/important_file_writer.cc b/base/files/important_file_writer.cc new file mode 100644 index 0000000000..d88498506b --- /dev/null +++ b/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 + +#include + +#include "base/bind.h" +#include "base/critical_closure.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/metrics/histogram.h" +#include "base/strings/string_number_conversions.h" +#include "base/task_runner.h" +#include "base/threading/thread.h" +#include "base/time/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(kint32max)); + int bytes_written = WritePlatformFile( + tmp_file, 0, data.data(), static_cast(data.length())); + FlushPlatformFile(tmp_file); // Ignore return value. + + if (!ClosePlatformFile(tmp_file)) { + LogFailure(path, FAILED_CLOSING, "failed to close temporary file"); + base::DeleteFile(tmp_file_path, false); + return false; + } + + if (bytes_written < static_cast(data.length())) { + LogFailure(path, FAILED_WRITING, "error writing, bytes_written=" + + IntToString(bytes_written)); + base::DeleteFile(tmp_file_path, false); + return false; + } + + if (!base::ReplaceFile(tmp_file_path, path, NULL)) { + LogFailure(path, FAILED_RENAMING, "could not rename temporary file"); + base::DeleteFile(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(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/base/files/important_file_writer.h b/base/files/important_file_writer.h new file mode 100644 index 0000000000..ba1c745a4e --- /dev/null +++ b/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 + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/threading/non_thread_safe.h" +#include "base/time/time.h" +#include "base/timer/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 task_runner_; + + // Timer used to schedule commit after ScheduleWrite. + OneShotTimer 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/base/files/important_file_writer_unittest.cc b/base/files/important_file_writer_unittest.cc new file mode 100644 index 0000000000..e8f3d122c6 --- /dev/null +++ b/base/files/important_file_writer_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/files/important_file_writer.h" + +#include "base/compiler_specific.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/threading/thread.h" +#include "base/time/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) OVERRIDE { + 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().get()); + EXPECT_FALSE(PathExists(writer.path())); + writer.WriteNow("foo"); + RunLoop().RunUntilIdle(); + + ASSERT_TRUE(PathExists(writer.path())); + EXPECT_EQ("foo", GetFileContent(writer.path())); +} + +TEST_F(ImportantFileWriterTest, ScheduleWrite) { + ImportantFileWriter writer(file_, MessageLoopProxy::current().get()); + 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::QuitWhenIdleClosure(), + TimeDelta::FromMilliseconds(100)); + MessageLoop::current()->Run(); + EXPECT_FALSE(writer.HasPendingWrite()); + ASSERT_TRUE(PathExists(writer.path())); + EXPECT_EQ("foo", GetFileContent(writer.path())); +} + +TEST_F(ImportantFileWriterTest, DoScheduledWrite) { + ImportantFileWriter writer(file_, MessageLoopProxy::current().get()); + EXPECT_FALSE(writer.HasPendingWrite()); + DataSerializer serializer("foo"); + writer.ScheduleWrite(&serializer); + EXPECT_TRUE(writer.HasPendingWrite()); + writer.DoScheduledWrite(); + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + MessageLoop::QuitWhenIdleClosure(), + TimeDelta::FromMilliseconds(100)); + MessageLoop::current()->Run(); + EXPECT_FALSE(writer.HasPendingWrite()); + ASSERT_TRUE(PathExists(writer.path())); + EXPECT_EQ("foo", GetFileContent(writer.path())); +} + +TEST_F(ImportantFileWriterTest, BatchingWrites) { + ImportantFileWriter writer(file_, MessageLoopProxy::current().get()); + 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::QuitWhenIdleClosure(), + TimeDelta::FromMilliseconds(100)); + MessageLoop::current()->Run(); + ASSERT_TRUE(PathExists(writer.path())); + EXPECT_EQ("baz", GetFileContent(writer.path())); +} + +} // namespace base diff --git a/base/files/memory_mapped_file.cc b/base/files/memory_mapped_file.cc new file mode 100644 index 0000000000..a48ec0ceb2 --- /dev/null +++ b/base/files/memory_mapped_file.cc @@ -0,0 +1,58 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/files/memory_mapped_file.h" + +#include "base/files/file_path.h" +#include "base/logging.h" + +namespace base { + +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(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_ = CreatePlatformFile(file_name, PLATFORM_FILE_OPEN | PLATFORM_FILE_READ, + NULL, NULL); + + if (file_ == kInvalidPlatformFileValue) { + DLOG(ERROR) << "Couldn't open " << file_name.AsUTF8Unsafe(); + return false; + } + + return MapFileToMemoryInternal(); +} + +} // namespace base diff --git a/base/files/memory_mapped_file.h b/base/files/memory_mapped_file.h new file mode 100644 index 0000000000..6df1bad635 --- /dev/null +++ b/base/files/memory_mapped_file.h @@ -0,0 +1,76 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_FILES_MEMORY_MAPPED_FILE_H_ +#define BASE_FILES_MEMORY_MAPPED_FILE_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/platform_file.h" +#include "build/build_config.h" + +#if defined(OS_WIN) +#include +#endif + +namespace base { + +class FilePath; + +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(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 + PlatformFile file_; + uint8* data_; + size_t length_; + + DISALLOW_COPY_AND_ASSIGN(MemoryMappedFile); +}; + +} // namespace base + +#endif // BASE_FILES_MEMORY_MAPPED_FILE_H_ diff --git a/base/files/memory_mapped_file_posix.cc b/base/files/memory_mapped_file_posix.cc new file mode 100644 index 0000000000..38b271691c --- /dev/null +++ b/base/files/memory_mapped_file_posix.cc @@ -0,0 +1,54 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/files/memory_mapped_file.h" + +#include +#include +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/threading/thread_restrictions.h" + +namespace base { + +MemoryMappedFile::MemoryMappedFile() + : file_(kInvalidPlatformFileValue), + data_(NULL), + length_(0) { +} + +bool MemoryMappedFile::MapFileToMemoryInternal() { + ThreadRestrictions::AssertIOAllowed(); + + struct stat file_stat; + if (fstat(file_, &file_stat) == kInvalidPlatformFileValue) { + DLOG(ERROR) << "Couldn't fstat " << file_ << ", errno " << errno; + return false; + } + length_ = file_stat.st_size; + + data_ = static_cast( + 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() { + ThreadRestrictions::AssertIOAllowed(); + + if (data_ != NULL) + munmap(data_, length_); + if (file_ != kInvalidPlatformFileValue) + ignore_result(HANDLE_EINTR(close(file_))); + + data_ = NULL; + length_ = 0; + file_ = kInvalidPlatformFileValue; +} + +} // namespace base diff --git a/base/files/memory_mapped_file_win.cc b/base/files/memory_mapped_file_win.cc new file mode 100644 index 0000000000..694212950d --- /dev/null +++ b/base/files/memory_mapped_file_win.cc @@ -0,0 +1,87 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/files/memory_mapped_file.h" + +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/metrics/histogram.h" +#include "base/strings/string16.h" +#include "base/threading/thread_restrictions.h" + +namespace base { + +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_ = CreatePlatformFile(file_name, PLATFORM_FILE_OPEN | PLATFORM_FILE_READ, + NULL, NULL); + + if (file_ == kInvalidPlatformFileValue) { + DLOG(ERROR) << "Couldn't open " << file_name.AsUTF8Unsafe(); + return false; + } + + if (!MapFileToMemoryInternalEx(SEC_IMAGE)) { + CloseHandles(); + return false; + } + + return true; +} + +bool MemoryMappedFile::MapFileToMemoryInternal() { + return MapFileToMemoryInternalEx(0); +} + +bool MemoryMappedFile::MapFileToMemoryInternalEx(int flags) { + 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( + ::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; +} + +} // namespace base diff --git a/base/files/scoped_platform_file_closer.cc b/base/files/scoped_platform_file_closer.cc new file mode 100644 index 0000000000..44f67521b8 --- /dev/null +++ b/base/files/scoped_platform_file_closer.cc @@ -0,0 +1,16 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/files/scoped_platform_file_closer.h" + +namespace base { +namespace internal { + +void PlatformFileCloser::operator()(PlatformFile* file) const { + if (file && *file != kInvalidPlatformFileValue) + ClosePlatformFile(*file); +} + +} // namespace internal +} // namespace base diff --git a/base/files/scoped_platform_file_closer.h b/base/files/scoped_platform_file_closer.h new file mode 100644 index 0000000000..8fe4a28511 --- /dev/null +++ b/base/files/scoped_platform_file_closer.h @@ -0,0 +1,26 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_FILES_SCOPED_PLATFORM_FILE_CLOSER_H_ +#define BASE_FILES_SCOPED_PLATFORM_FILE_CLOSER_H_ + +#include "base/memory/scoped_ptr.h" +#include "base/platform_file.h" + +namespace base { + +namespace internal { + +struct BASE_EXPORT PlatformFileCloser { + void operator()(PlatformFile* file) const; +}; + +} // namespace internal + +typedef scoped_ptr + ScopedPlatformFileCloser; + +} // namespace base + +#endif // BASE_FILES_SCOPED_PLATFORM_FILE_CLOSER_H_ diff --git a/base/files/scoped_temp_dir.cc b/base/files/scoped_temp_dir.cc new file mode 100644 index 0000000000..497799e9f7 --- /dev/null +++ b/base/files/scoped_temp_dir.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/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 (!DirectoryExists(path) && !file_util::CreateDirectory(path)) + return false; + + path_ = path; + return true; +} + +bool ScopedTempDir::Delete() { + if (path_.empty()) + return false; + + bool ret = base::DeleteFile(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() && DirectoryExists(path_); +} + +} // namespace base diff --git a/base/files/scoped_temp_dir.h b/base/files/scoped_temp_dir.h new file mode 100644 index 0000000000..5f63e09cff --- /dev/null +++ b/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/files/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/base/files/scoped_temp_dir_unittest.cc b/base/files/scoped_temp_dir_unittest.cc new file mode 100644 index 0000000000..0c9131c3a8 --- /dev/null +++ b/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 + +#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(DirectoryExists(test_path)); + { + ScopedTempDir dir; + EXPECT_TRUE(dir.Set(test_path)); + EXPECT_TRUE(dir.IsValid()); + } + EXPECT_FALSE(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(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(DirectoryExists(test_path)); + + // Clean up. + { + ScopedTempDir dir; + EXPECT_TRUE(dir.Set(test_path)); + } + EXPECT_FALSE(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(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(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(DirectoryExists(test_path)); + EXPECT_TRUE(base_path.IsParent(test_path)); + EXPECT_TRUE(test_path.value().find(base_path.value()) != std::string::npos); + } + EXPECT_FALSE(DirectoryExists(test_path)); + base::DeleteFile(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/base/float_util.h b/base/float_util.h new file mode 100644 index 0000000000..90273106cd --- /dev/null +++ b/base/float_util.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_FLOAT_UTIL_H_ +#define BASE_FLOAT_UTIL_H_ + +#include "build/build_config.h" + +#include + +#include + +namespace base { + +template +inline bool IsFinite(const Float& number) { +#if defined(OS_POSIX) + return std::isfinite(number) != 0; +#elif defined(OS_WIN) + return _finite(number) != 0; +#endif +} + +template +inline bool IsNaN(const Float& number) { +#if defined(OS_POSIX) + return std::isnan(number) != 0; +#elif defined(OS_WIN) + return _isnan(number) != 0; +#endif +} + +} // namespace base + +#endif // BASE_FLOAT_UTIL_H_ diff --git a/base/format_macros.h b/base/format_macros.h new file mode 100644 index 0000000000..466d79be73 --- /dev/null +++ b/base/format_macros.h @@ -0,0 +1,73 @@ +// 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_POSIX) + +#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 + +// 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 + +#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/base/glibc-x86-32.base_untrusted.source_list.gypcmd b/base/glibc-x86-32.base_untrusted.source_list.gypcmd new file mode 100644 index 0000000000..8dbe0ca926 --- /dev/null +++ b/base/glibc-x86-32.base_untrusted.source_list.gypcmd @@ -0,0 +1,353 @@ +../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/prcpucfg.h +third_party/nspr/prcpucfg_freebsd.h +third_party/nspr/prcpucfg_nacl.h +third_party/nspr/prcpucfg_openbsd.h +third_party/nspr/prcpucfg_solaris.h +third_party/nspr/prtime.cc +third_party/nspr/prtime.h +third_party/nspr/prtypes.h +third_party/xdg_mime/xdgmime.h +allocator/allocator_extension.cc +allocator/allocator_extension.h +at_exit.cc +at_exit.h +atomic_ref_count.h +atomic_sequence_num.h +atomicops.h +atomicops_internals_gcc.h +atomicops_internals_tsan.h +atomicops_internals_x86_gcc.h +atomicops_internals_x86_msvc.h +base_export.h +base_paths.h +base_paths_android.h +base_paths_posix.cc +base_paths_posix.h +base_switches.h +base64.cc +base64.h +basictypes.h +bind.h +bind_helpers.cc +bind_helpers.h +bind_internal.h +bits.h +build_time.cc +build_time.h +callback.h +callback_helpers.h +callback_internal.cc +callback_internal.h +cancelable_callback.h +command_line.cc +command_line.h +compiler_specific.h +containers/hash_tables.h +containers/linked_list.h +containers/mru_cache.h +containers/small_map.h +containers/stack_container.h +cpu.h +critical_closure.h +debug/alias.cc +debug/alias.h +debug/crash_logging.cc +debug/crash_logging.h +debug/debugger.cc +debug/debugger.h +debug/debugger_posix.cc +debug/leak_annotations.h +debug/leak_tracker.h +debug/profiler.cc +debug/profiler.h +debug/stack_trace.cc +debug/stack_trace.h +debug/trace_event.h +debug/trace_event_impl.cc +debug/trace_event_impl.h +debug/trace_event_impl_constants.cc +debug/trace_event_memory.cc +debug/trace_event_memory.h +deferred_sequenced_task_runner.cc +deferred_sequenced_task_runner.h +environment.cc +environment.h +file_descriptor_posix.h +file_util.h +file_version_info.h +files/dir_reader_fallback.h +files/dir_reader_posix.h +files/file_enumerator.cc +files/file_enumerator.h +files/file_path.cc +files/file_path.h +files/file_path_constants.cc +files/file_path_watcher.cc +files/file_path_watcher.h +files/file_path_watcher_stub.cc +files/file_util_proxy.h +files/important_file_writer.h +files/important_file_writer.cc +files/memory_mapped_file.cc +files/memory_mapped_file.h +files/memory_mapped_file_posix.cc +files/scoped_platform_file_closer.cc +files/scoped_platform_file_closer.h +files/scoped_temp_dir.h +float_util.h +format_macros.h +gtest_prod_util.h +guid.cc +guid.h +guid_posix.cc +hash.cc +hash.h +id_map.h +ini_parser.cc +ini_parser.h +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 +memory/aligned_memory.cc +memory/aligned_memory.h +memory/discardable_memory.cc +memory/discardable_memory.h +memory/linked_ptr.h +memory/manual_constructor.h +memory/memory_pressure_listener.cc +memory/memory_pressure_listener.h +memory/raw_scoped_refptr_mismatch_checker.h +memory/ref_counted.cc +memory/ref_counted.h +memory/ref_counted_delete_on_message_loop.h +memory/ref_counted_memory.cc +memory/ref_counted_memory.h +memory/scoped_handle.h +memory/scoped_open_process.h +memory/scoped_policy.h +memory/scoped_ptr.h +memory/scoped_vector.h +memory/shared_memory.h +memory/shared_memory_nacl.cc +memory/singleton.cc +memory/singleton.h +memory/weak_ptr.cc +memory/weak_ptr.h +message_loop/incoming_task_queue.cc +message_loop/incoming_task_queue.h +message_loop/message_loop.cc +message_loop/message_loop.h +message_loop/message_loop_proxy.cc +message_loop/message_loop_proxy.h +message_loop/message_loop_proxy_impl.cc +message_loop/message_loop_proxy_impl.h +message_loop/message_pump.cc +message_loop/message_pump.h +message_loop/message_pump_android.h +message_loop/message_pump_default.cc +message_loop/message_pump_default.h +move.h +native_library.h +observer_list.h +observer_list_threadsafe.h +os_compat_android.h +os_compat_nacl.cc +os_compat_nacl.h +path_service.h +pending_task.cc +pending_task.h +pickle.cc +pickle.h +platform_file.cc +platform_file.h +platform_file_posix.cc +port.h +posix/eintr_wrapper.h +posix/global_descriptors.cc +posix/global_descriptors.h +power_monitor/power_monitor.cc +power_monitor/power_monitor.h +power_monitor/power_monitor_device_source_android.h +power_monitor/power_monitor_device_source.cc +power_monitor/power_monitor_device_source.h +power_monitor/power_monitor_device_source_posix.cc +power_monitor/power_monitor_source.cc +power_monitor/power_monitor_source.h +power_monitor/power_observer.h +process/kill.cc +process/kill.h +process/launch.h +process/memory.h +process/process.h +process/process_handle_posix.cc +process/process_info.h +process/process_iterator.cc +process/process_iterator.h +process/process_metrics.h +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 +run_loop.cc +run_loop.h +safe_numerics.h +safe_strerror_posix.cc +safe_strerror_posix.h +scoped_native_library.h +sequence_checker.h +sequence_checker_impl.cc +sequence_checker_impl.h +sequenced_task_runner.cc +sequenced_task_runner.h +sequenced_task_runner_helpers.h +sha1.h +sha1_portable.cc +single_thread_task_runner.h +stl_util.h +strings/latin1_string_conversions.cc +strings/latin1_string_conversions.h +strings/nullable_string16.cc +strings/nullable_string16.h +strings/string16.cc +strings/string16.h +strings/string_number_conversions.cc +strings/string_split.cc +strings/string_split.h +strings/string_number_conversions.h +strings/string_piece.cc +strings/string_piece.h +strings/string_tokenizer.h +strings/string_util.cc +strings/string_util.h +strings/string_util_constants.cc +strings/string_util_posix.h +strings/stringize_macros.h +strings/stringprintf.cc +strings/stringprintf.h +strings/sys_string_conversions.h +strings/sys_string_conversions_posix.cc +strings/utf_offset_string_conversions.cc +strings/utf_offset_string_conversions.h +strings/utf_string_conversion_utils.cc +strings/utf_string_conversion_utils.h +strings/utf_string_conversions.cc +strings/utf_string_conversions.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/lock.cc +synchronization/lock.h +synchronization/lock_impl.h +synchronization/lock_impl_posix.cc +synchronization/spin_wait.h +synchronization/waitable_event.h +synchronization/waitable_event_posix.cc +synchronization/waitable_event_watcher.h +synchronization/waitable_event_watcher_posix.cc +system_monitor/system_monitor.cc +system_monitor/system_monitor.h +sys_byteorder.h +sys_info.cc +sys_info.h +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_linux.cc +threading/platform_thread_posix.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_id_name_manager.cc +threading/thread_id_name_manager.h +threading/thread_local.h +threading/thread_local_posix.cc +threading/thread_local_storage.h +threading/thread_local_storage_posix.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 +time/clock.cc +time/clock.h +time/default_clock.cc +time/default_clock.h +time/default_tick_clock.cc +time/default_tick_clock.h +time/tick_clock.cc +time/tick_clock.h +time/time.cc +time/time.h +time/time_posix.cc +timer/hi_res_timer_manager_posix.cc +timer/hi_res_timer_manager.h +timer/timer.cc +timer/timer.h +tracked_objects.cc +tracked_objects.h +tracking_info.cc +tracking_info.h +tuple.h +values.cc +values.h +value_conversions.cc +value_conversions.h +version.cc +version.h +vlog.cc +vlog.h +base_switches.cc +base_switches.h +strings/string16.cc +sync_socket_nacl.cc +time/time_posix.cc diff --git a/base/glibc-x86-64.base_untrusted.source_list.gypcmd b/base/glibc-x86-64.base_untrusted.source_list.gypcmd new file mode 100644 index 0000000000..8dbe0ca926 --- /dev/null +++ b/base/glibc-x86-64.base_untrusted.source_list.gypcmd @@ -0,0 +1,353 @@ +../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/prcpucfg.h +third_party/nspr/prcpucfg_freebsd.h +third_party/nspr/prcpucfg_nacl.h +third_party/nspr/prcpucfg_openbsd.h +third_party/nspr/prcpucfg_solaris.h +third_party/nspr/prtime.cc +third_party/nspr/prtime.h +third_party/nspr/prtypes.h +third_party/xdg_mime/xdgmime.h +allocator/allocator_extension.cc +allocator/allocator_extension.h +at_exit.cc +at_exit.h +atomic_ref_count.h +atomic_sequence_num.h +atomicops.h +atomicops_internals_gcc.h +atomicops_internals_tsan.h +atomicops_internals_x86_gcc.h +atomicops_internals_x86_msvc.h +base_export.h +base_paths.h +base_paths_android.h +base_paths_posix.cc +base_paths_posix.h +base_switches.h +base64.cc +base64.h +basictypes.h +bind.h +bind_helpers.cc +bind_helpers.h +bind_internal.h +bits.h +build_time.cc +build_time.h +callback.h +callback_helpers.h +callback_internal.cc +callback_internal.h +cancelable_callback.h +command_line.cc +command_line.h +compiler_specific.h +containers/hash_tables.h +containers/linked_list.h +containers/mru_cache.h +containers/small_map.h +containers/stack_container.h +cpu.h +critical_closure.h +debug/alias.cc +debug/alias.h +debug/crash_logging.cc +debug/crash_logging.h +debug/debugger.cc +debug/debugger.h +debug/debugger_posix.cc +debug/leak_annotations.h +debug/leak_tracker.h +debug/profiler.cc +debug/profiler.h +debug/stack_trace.cc +debug/stack_trace.h +debug/trace_event.h +debug/trace_event_impl.cc +debug/trace_event_impl.h +debug/trace_event_impl_constants.cc +debug/trace_event_memory.cc +debug/trace_event_memory.h +deferred_sequenced_task_runner.cc +deferred_sequenced_task_runner.h +environment.cc +environment.h +file_descriptor_posix.h +file_util.h +file_version_info.h +files/dir_reader_fallback.h +files/dir_reader_posix.h +files/file_enumerator.cc +files/file_enumerator.h +files/file_path.cc +files/file_path.h +files/file_path_constants.cc +files/file_path_watcher.cc +files/file_path_watcher.h +files/file_path_watcher_stub.cc +files/file_util_proxy.h +files/important_file_writer.h +files/important_file_writer.cc +files/memory_mapped_file.cc +files/memory_mapped_file.h +files/memory_mapped_file_posix.cc +files/scoped_platform_file_closer.cc +files/scoped_platform_file_closer.h +files/scoped_temp_dir.h +float_util.h +format_macros.h +gtest_prod_util.h +guid.cc +guid.h +guid_posix.cc +hash.cc +hash.h +id_map.h +ini_parser.cc +ini_parser.h +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 +memory/aligned_memory.cc +memory/aligned_memory.h +memory/discardable_memory.cc +memory/discardable_memory.h +memory/linked_ptr.h +memory/manual_constructor.h +memory/memory_pressure_listener.cc +memory/memory_pressure_listener.h +memory/raw_scoped_refptr_mismatch_checker.h +memory/ref_counted.cc +memory/ref_counted.h +memory/ref_counted_delete_on_message_loop.h +memory/ref_counted_memory.cc +memory/ref_counted_memory.h +memory/scoped_handle.h +memory/scoped_open_process.h +memory/scoped_policy.h +memory/scoped_ptr.h +memory/scoped_vector.h +memory/shared_memory.h +memory/shared_memory_nacl.cc +memory/singleton.cc +memory/singleton.h +memory/weak_ptr.cc +memory/weak_ptr.h +message_loop/incoming_task_queue.cc +message_loop/incoming_task_queue.h +message_loop/message_loop.cc +message_loop/message_loop.h +message_loop/message_loop_proxy.cc +message_loop/message_loop_proxy.h +message_loop/message_loop_proxy_impl.cc +message_loop/message_loop_proxy_impl.h +message_loop/message_pump.cc +message_loop/message_pump.h +message_loop/message_pump_android.h +message_loop/message_pump_default.cc +message_loop/message_pump_default.h +move.h +native_library.h +observer_list.h +observer_list_threadsafe.h +os_compat_android.h +os_compat_nacl.cc +os_compat_nacl.h +path_service.h +pending_task.cc +pending_task.h +pickle.cc +pickle.h +platform_file.cc +platform_file.h +platform_file_posix.cc +port.h +posix/eintr_wrapper.h +posix/global_descriptors.cc +posix/global_descriptors.h +power_monitor/power_monitor.cc +power_monitor/power_monitor.h +power_monitor/power_monitor_device_source_android.h +power_monitor/power_monitor_device_source.cc +power_monitor/power_monitor_device_source.h +power_monitor/power_monitor_device_source_posix.cc +power_monitor/power_monitor_source.cc +power_monitor/power_monitor_source.h +power_monitor/power_observer.h +process/kill.cc +process/kill.h +process/launch.h +process/memory.h +process/process.h +process/process_handle_posix.cc +process/process_info.h +process/process_iterator.cc +process/process_iterator.h +process/process_metrics.h +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 +run_loop.cc +run_loop.h +safe_numerics.h +safe_strerror_posix.cc +safe_strerror_posix.h +scoped_native_library.h +sequence_checker.h +sequence_checker_impl.cc +sequence_checker_impl.h +sequenced_task_runner.cc +sequenced_task_runner.h +sequenced_task_runner_helpers.h +sha1.h +sha1_portable.cc +single_thread_task_runner.h +stl_util.h +strings/latin1_string_conversions.cc +strings/latin1_string_conversions.h +strings/nullable_string16.cc +strings/nullable_string16.h +strings/string16.cc +strings/string16.h +strings/string_number_conversions.cc +strings/string_split.cc +strings/string_split.h +strings/string_number_conversions.h +strings/string_piece.cc +strings/string_piece.h +strings/string_tokenizer.h +strings/string_util.cc +strings/string_util.h +strings/string_util_constants.cc +strings/string_util_posix.h +strings/stringize_macros.h +strings/stringprintf.cc +strings/stringprintf.h +strings/sys_string_conversions.h +strings/sys_string_conversions_posix.cc +strings/utf_offset_string_conversions.cc +strings/utf_offset_string_conversions.h +strings/utf_string_conversion_utils.cc +strings/utf_string_conversion_utils.h +strings/utf_string_conversions.cc +strings/utf_string_conversions.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/lock.cc +synchronization/lock.h +synchronization/lock_impl.h +synchronization/lock_impl_posix.cc +synchronization/spin_wait.h +synchronization/waitable_event.h +synchronization/waitable_event_posix.cc +synchronization/waitable_event_watcher.h +synchronization/waitable_event_watcher_posix.cc +system_monitor/system_monitor.cc +system_monitor/system_monitor.h +sys_byteorder.h +sys_info.cc +sys_info.h +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_linux.cc +threading/platform_thread_posix.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_id_name_manager.cc +threading/thread_id_name_manager.h +threading/thread_local.h +threading/thread_local_posix.cc +threading/thread_local_storage.h +threading/thread_local_storage_posix.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 +time/clock.cc +time/clock.h +time/default_clock.cc +time/default_clock.h +time/default_tick_clock.cc +time/default_tick_clock.h +time/tick_clock.cc +time/tick_clock.h +time/time.cc +time/time.h +time/time_posix.cc +timer/hi_res_timer_manager_posix.cc +timer/hi_res_timer_manager.h +timer/timer.cc +timer/timer.h +tracked_objects.cc +tracked_objects.h +tracking_info.cc +tracking_info.h +tuple.h +values.cc +values.h +value_conversions.cc +value_conversions.h +version.cc +version.h +vlog.cc +vlog.h +base_switches.cc +base_switches.h +strings/string16.cc +sync_socket_nacl.cc +time/time_posix.cc diff --git a/base/gmock_unittest.cc b/base/gmock_unittest.cc new file mode 100644 index 0000000000..855380a975 --- /dev/null +++ b/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/base/gtest_prod_util.h b/base/gtest_prod_util.h new file mode 100644 index 0000000000..3289e6301d --- /dev/null +++ b/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/base/guid.cc b/base/guid.cc new file mode 100644 index 0000000000..657383fc24 --- /dev/null +++ b/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/strings/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/base/guid.h b/base/guid.h new file mode 100644 index 0000000000..468b65ff61 --- /dev/null +++ b/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 + +#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) +// 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/base/guid_posix.cc b/base/guid_posix.cc new file mode 100644 index 0000000000..1b6d56fdea --- /dev/null +++ b/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/strings/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(bytes[0] >> 32), + static_cast((bytes[0] >> 16) & 0x0000ffff), + static_cast(bytes[0] & 0x0000ffff), + static_cast(bytes[1] >> 48), + bytes[1] & 0x0000ffffffffffffULL); +} + +} // namespace guid diff --git a/base/guid_unittest.cc b/base/guid_unittest.cc new file mode 100644 index 0000000000..18c04a9d33 --- /dev/null +++ b/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 + +#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/base/guid_win.cc b/base/guid_win.cc new file mode 100644 index 0000000000..cdb860a0b9 --- /dev/null +++ b/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 + +#include +#include + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/strings/string_util.h" +#include "base/strings/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/base/hash.cc b/base/hash.cc new file mode 100644 index 0000000000..2c87065045 --- /dev/null +++ b/base/hash.cc @@ -0,0 +1,73 @@ +// 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(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(*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/base/hash.h b/base/hash.h new file mode 100644 index 0000000000..cf8ea3a26e --- /dev/null +++ b/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 + +#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(length)); +} + +inline uint32 Hash(const std::string& key) { + if (key.empty()) + return 0; + return SuperFastHash(key.data(), static_cast(key.size())); +} + +} // namespace base + +#endif // BASE_HASH_H_ diff --git a/base/i18n/OWNERS b/base/i18n/OWNERS new file mode 100644 index 0000000000..d717b8dabb --- /dev/null +++ b/base/i18n/OWNERS @@ -0,0 +1 @@ +jshin@chromium.org diff --git a/base/i18n/base_i18n_export.h b/base/i18n/base_i18n_export.h new file mode 100644 index 0000000000..e8a2adda17 --- /dev/null +++ b/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(WIN32) + +#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/base/i18n/bidi_line_iterator.cc b/base/i18n/bidi_line_iterator.cc new file mode 100644 index 0000000000..e81a36b77a --- /dev/null +++ b/base/i18n/bidi_line_iterator.cc @@ -0,0 +1,60 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/i18n/bidi_line_iterator.h" + +#include "base/logging.h" + +namespace base { +namespace i18n { + +BiDiLineIterator::BiDiLineIterator() : bidi_(NULL) { +} + +BiDiLineIterator::~BiDiLineIterator() { + if (bidi_) { + ubidi_close(bidi_); + bidi_ = NULL; + } +} + +bool BiDiLineIterator::Open(const string16& text, + bool right_to_left, + bool url) { + DCHECK(!bidi_); + UErrorCode error = U_ZERO_ERROR; + bidi_ = ubidi_openSized(static_cast(text.length()), 0, &error); + if (U_FAILURE(error)) + return false; + if (right_to_left && url) + ubidi_setReorderingMode(bidi_, UBIDI_REORDER_RUNS_ONLY); + ubidi_setPara(bidi_, text.data(), static_cast(text.length()), + right_to_left ? UBIDI_DEFAULT_RTL : UBIDI_DEFAULT_LTR, + 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/base/i18n/bidi_line_iterator.h b/base/i18n/bidi_line_iterator.h new file mode 100644 index 0000000000..d5a2a077d7 --- /dev/null +++ b/base/i18n/bidi_line_iterator.h @@ -0,0 +1,46 @@ +// Copyright (c) 2011 The Chromium Authors. All 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 "base/basictypes.h" +#include "base/i18n/base_i18n_export.h" +#include "base/strings/string16.h" +#include "third_party/icu/source/common/unicode/ubidi.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, bool right_to_left, bool url); + + // 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/base/i18n/break_iterator.cc b/base/i18n/break_iterator.cc new file mode 100644 index 0000000000..2c4d466973 --- /dev/null +++ b/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 "third_party/icu/source/common/unicode/ubrk.h" +#include "third_party/icu/source/common/unicode/uchar.h" +#include "third_party/icu/source/common/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(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(string_.size()), + &status); + if (U_FAILURE(status)) { + NOTREACHED() << "ubrk_open failed"; + return false; + } + // Move the iterator to the beginning of the string. + ubrk_first(static_cast(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(iter_)); + if (pos == UBRK_DONE) { + pos_ = npos; + return false; + } + pos_ = static_cast(pos); + return true; + case BREAK_NEWLINE: + do { + pos = ubrk_next(static_cast(iter_)); + if (pos == UBRK_DONE) + break; + pos_ = static_cast(pos); + status = ubrk_getRuleStatus(static_cast(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(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(iter_); + UBool boundary = ubrk_isBoundary(iter, static_cast(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(iter_); + UBool boundary = ubrk_isBoundary(iter, static_cast(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/base/i18n/break_iterator.h b/base/i18n/break_iterator.h new file mode 100644 index 0000000000..96bdeaa9b3 --- /dev/null +++ b/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/i18n/base_i18n_export.h" +#include "base/strings/string16.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/base/i18n/break_iterator_unittest.cc b/base/i18n/break_iterator_unittest.cc new file mode 100644 index 0000000000..6dcae18460 --- /dev/null +++ b/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/strings/string_piece.h" +#include "base/strings/stringprintf.h" +#include "base/strings/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 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/base/i18n/case_conversion.cc b/base/i18n/case_conversion.cc new file mode 100644 index 0000000000..5debc2ec8d --- /dev/null +++ b/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/strings/string16.h" +#include "third_party/icu/source/common/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/base/i18n/case_conversion.h b/base/i18n/case_conversion.h new file mode 100644 index 0000000000..5d538ccf1d --- /dev/null +++ b/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/strings/string16.h" +#include "base/strings/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/base/i18n/case_conversion_unittest.cc b/base/i18n/case_conversion_unittest.cc new file mode 100644 index 0000000000..2139bbe7b3 --- /dev/null +++ b/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/strings/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/base/i18n/char_iterator.cc b/base/i18n/char_iterator.cc new file mode 100644 index 0000000000..25efc51869 --- /dev/null +++ b/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 "third_party/icu/source/common/unicode/utf8.h" +#include "third_party/icu/source/common/unicode/utf16.h" + +namespace base { +namespace i18n { + +UTF8CharIterator::UTF8CharIterator(const std::string* str) + : str_(reinterpret_cast(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(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/base/i18n/char_iterator.h b/base/i18n/char_iterator.h new file mode 100644 index 0000000000..46928b37d0 --- /dev/null +++ b/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 + +#include "base/basictypes.h" +#include "base/i18n/base_i18n_export.h" +#include "base/strings/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. + explicit 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. + explicit 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/base/i18n/char_iterator_unittest.cc b/base/i18n/char_iterator_unittest.cc new file mode 100644 index 0000000000..0cf8e6c07d --- /dev/null +++ b/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/strings/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/base/i18n/file_util_icu.cc b/base/i18n/file_util_icu.cc new file mode 100644 index 0000000000..4b2ca3ac0b --- /dev/null +++ b/base/i18n/file_util_icu.cc @@ -0,0 +1,170 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// File utilities that use the ICU library go in this file. + +#include "base/i18n/file_util_icu.h" + +#include "base/files/file_path.h" +#include "base/i18n/icu_string_conversions.h" +#include "base/i18n/string_compare.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/singleton.h" +#include "base/strings/string_util.h" +#include "base/strings/sys_string_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "build/build_config.h" +#include "third_party/icu/source/common/unicode/uniset.h" +#include "third_party/icu/source/i18n/unicode/coll.h" + +namespace { + +class IllegalCharacters { + public: + static IllegalCharacters* GetInstance() { + return Singleton::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; + friend struct DefaultSingletonTraits; + + IllegalCharacters(); + ~IllegalCharacters() { } + + scoped_ptr 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(); +} + +} // namespace + +namespace file_util { + +bool IsFilenameLegal(const string16& file_name) { + return IllegalCharacters::GetInstance()->containsNone(file_name); +} + +void ReplaceIllegalCharactersInPath(base::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(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(file_name->length()), + code_point); +#elif defined(OS_WIN) + // Windows uses UTF-16 encoding for filenames. + U16_NEXT(file_name->data(), cursor, static_cast(file_name->length()), + code_point); +#elif defined(OS_POSIX) + // Linux doesn't actually define an encoding. It basically allows anything + // except for a few special ASCII characters. + unsigned char cur_char = static_cast((*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 base::FilePath& a, + const base::FilePath& b) { + 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. + scoped_ptr collator(icu::Collator::createInstance(error_code)); + DCHECK(U_SUCCESS(error_code)); + // Make it case-sensitive. + collator->setStrength(icu::Collator::TERTIARY); + +#if defined(OS_WIN) + return base::i18n::CompareString16WithCollator(collator.get(), + WideToUTF16(a.value()), WideToUTF16(b.value())) == UCOL_LESS; + +#elif defined(OS_POSIX) + // On linux, the file system encoding is not defined. We assume + // SysNativeMBToWide takes care of it. + return base::i18n::CompareString16WithCollator(collator.get(), + WideToUTF16(base::SysNativeMBToWide(a.value().c_str())), + WideToUTF16(base::SysNativeMBToWide(b.value().c_str()))) == UCOL_LESS; +#else + #error Not implemented on your system +#endif +} + +void NormalizeFileNameEncoding(base::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(base::FilePath(normalized_str)); + } +#endif +} + +} // namespace diff --git a/base/i18n/file_util_icu.h b/base/i18n/file_util_icu.h new file mode 100644 index 0000000000..7f246c7f1c --- /dev/null +++ b/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/files/file_path.h" +#include "base/i18n/base_i18n_export.h" +#include "base/strings/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( + base::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 base::FilePath& a, + const base::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(base::FilePath* file_name); + +} // namespace file_util + +#endif // BASE_I18N_FILE_UTIL_ICU_H_ diff --git a/base/i18n/file_util_icu_unittest.cc b/base/i18n/file_util_icu_unittest.cc new file mode 100644 index 0000000000..e3af9adf94 --- /dev/null +++ b/base/i18n/file_util_icu_unittest.cc @@ -0,0 +1,107 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/i18n/file_util_icu.h" + +#include "base/file_util.h" +#include "base/strings/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++) { + base::FilePath path(kNormalizeFileNameEncodingTestCases[i].original_path); + file_util::NormalizeFileNameEncoding(&path); + EXPECT_EQ( + base::FilePath(kNormalizeFileNameEncodingTestCases[i].normalized_path), + path); + } +} + +#endif diff --git a/base/i18n/i18n_constants.cc b/base/i18n/i18n_constants.cc new file mode 100644 index 0000000000..9b8c571564 --- /dev/null +++ b/base/i18n/i18n_constants.cc @@ -0,0 +1,15 @@ +// Copyright (c) 2011 The Chromium Authors. All 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/i18n_constants.h" + +namespace base { + +const char kCodepageLatin1[] = "ISO-8859-1"; +const char kCodepageUTF8[] = "UTF-8"; +const char kCodepageUTF16BE[] = "UTF-16BE"; +const char kCodepageUTF16LE[] = "UTF-16LE"; + +} // namespace base + diff --git a/base/i18n/i18n_constants.h b/base/i18n/i18n_constants.h new file mode 100644 index 0000000000..c2de842da1 --- /dev/null +++ b/base/i18n/i18n_constants.h @@ -0,0 +1,20 @@ +// 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_I18N_I18N_CONSTANTS_H_ +#define BASE_I18N_I18N_CONSTANTS_H_ + +#include "base/i18n/base_i18n_export.h" + +namespace base { + +// 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[]; + +} // namespace base + +#endif // BASE_I18N_I18N_CONSTANTS_H_ diff --git a/base/i18n/icu_encoding_detection.cc b/base/i18n/icu_encoding_detection.cc new file mode 100644 index 0000000000..ccd5cde209 --- /dev/null +++ b/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 + +#include "base/strings/string_util.h" +#include "third_party/icu/source/i18n/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(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* encodings) { + UErrorCode status = U_ZERO_ERROR; + UCharsetDetector* detector = ucsdet_open(&status); + ucsdet_setText(detector, text.data(), static_cast(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 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/base/i18n/icu_encoding_detection.h b/base/i18n/icu_encoding_detection.h new file mode 100644 index 0000000000..6d1e71ca2b --- /dev/null +++ b/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 +#include + +#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* encodings); + +} // namespace base + +#endif // BASE_I18N_ICU_ENCODING_DETECTION_H_ diff --git a/base/i18n/icu_string_conversions.cc b/base/i18n/icu_string_conversions.cc new file mode 100644 index 0000000000..1530117f1e --- /dev/null +++ b/base/i18n/icu_string_conversions.cc @@ -0,0 +1,292 @@ +// Copyright (c) 2012 The Chromium Authors. All 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 + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "third_party/icu/source/common/unicode/ucnv.h" +#include "third_party/icu/source/common/unicode/ucnv_cb.h" +#include "third_party/icu/source/common/unicode/ucnv_err.h" +#include "third_party/icu/source/common/unicode/unorm.h" +#include "third_party/icu/source/common/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(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 + +// 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(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_ptr buffer(new char16[uchar_max_length]); + int actual_size = ucnv_toUChars(converter, buffer.get(), + static_cast(uchar_max_length), encoded.data(), + static_cast(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 utf16(wide.length() * 2 + 1); + u_strFromUTF32(&utf16[0], utf16.size(), &utf16_len, + reinterpret_cast(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_ptr buffer(new wchar_t[wchar_max_length]); + int actual_size = ucnv_toAlgorithmic(utf32_platform_endian(), converter, + reinterpret_cast(buffer.get()), + static_cast(wchar_max_length) * sizeof(wchar_t), encoded.data(), + static_cast(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_ptr buffer(new char16[max_length]); + int actual_length = unorm_normalize( + utf16.c_str(), utf16.length(), UNORM_NFC, 0, + buffer.get(), static_cast(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/base/i18n/icu_string_conversions.h b/base/i18n/icu_string_conversions.h new file mode 100644 index 0000000000..fd2ae46c52 --- /dev/null +++ b/base/i18n/icu_string_conversions.h @@ -0,0 +1,70 @@ +// Copyright (c) 2011 The Chromium Authors. All 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 + +#include "base/i18n/base_i18n_export.h" +#include "base/i18n/i18n_constants.h" +#include "base/strings/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(); +}; + +// 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/base/i18n/icu_string_conversions_unittest.cc b/base/i18n/icu_string_conversions_unittest.cc new file mode 100644 index 0000000000..62e055ea6f --- /dev/null +++ b/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 +#include + +#include +#include + +#include "base/basictypes.h" +#include "base/format_macros.h" +#include "base/i18n/icu_string_conversions.h" +#include "base/logging.h" +#include "base/strings/string_piece.h" +#include "base/strings/stringprintf.h" +#include "base/strings/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(*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\x8F\xB0\xA1\x8E\xA6", + OnStringConversionError::FAIL, + true, + L"\x3053\x3093\x306B\x3061\x306F\x4E00\x4E02\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 "]: ", 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 "]: ", 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 "]: ", 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/base/i18n/icu_util.cc b/base/i18n/icu_util.cc new file mode 100644 index 0000000000..76016d0ce6 --- /dev/null +++ b/base/i18n/icu_util.cc @@ -0,0 +1,135 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/i18n/icu_util.h" + +#include "build/build_config.h" + +#if defined(OS_WIN) +#include +#endif + +#include + +#include "base/files/file_path.h" +#include "base/files/memory_mapped_file.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/strings/string_util.h" +#include "base/strings/sys_string_conversions.h" +#include "third_party/icu/source/common/unicode/putil.h" +#include "third_party/icu/source/common/unicode/udata.h" + +#if defined(OS_MACOSX) +#include "base/mac/foundation_util.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) +#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 + +using base::FilePath; + +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(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(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. + CR_DEFINE_STATIC_LOCAL(base::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(mapped_file.data()), &err); + return err == U_ZERO_ERROR; +#endif // OS check +#endif +} + +} // namespace icu_util diff --git a/base/i18n/icu_util.h b/base/i18n/icu_util.h new file mode 100644 index 0000000000..f6356f1bb6 --- /dev/null +++ b/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/base/i18n/icu_util_nacl_win64.cc b/base/i18n/icu_util_nacl_win64.cc new file mode 100644 index 0000000000..6e0bb6b2f2 --- /dev/null +++ b/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/base/i18n/number_formatting.cc b/base/i18n/number_formatting.cc new file mode 100644 index 0000000000..47aa14cab2 --- /dev/null +++ b/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/lazy_instance.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "third_party/icu/source/common/unicode/ustring.h" +#include "third_party/icu/source/i18n/unicode/numfmt.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 number_format; +}; + +LazyInstance g_number_format_int = + LAZY_INSTANCE_INITIALIZER; +LazyInstance 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(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(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/base/i18n/number_formatting.h b/base/i18n/number_formatting.h new file mode 100644 index 0000000000..556f9c24d2 --- /dev/null +++ b/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/strings/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/base/i18n/number_formatting_unittest.cc b/base/i18n/number_formatting_unittest.cc new file mode 100644 index 0000000000..da6397df8a --- /dev/null +++ b/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 + +#include "base/i18n/number_formatting.h" +#include "base/i18n/rtl.h" +#include "base/strings/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::max(), + "9,223,372,036,854,775,807", "9.223.372.036.854.775.807"}, + {std::numeric_limits::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) + // 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::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::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/base/i18n/rtl.cc b/base/i18n/rtl.cc new file mode 100644 index 0000000000..d9818e800e --- /dev/null +++ b/base/i18n/rtl.cc @@ -0,0 +1,383 @@ +// Copyright (c) 2011 The Chromium Authors. All 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/files/file_path.h" +#include "base/logging.h" +#include "base/strings/string_util.h" +#include "base/strings/sys_string_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "third_party/icu/source/common/unicode/locid.h" +#include "third_party/icu/source/common/unicode/uchar.h" +#include "third_party/icu/source/common/unicode/uscript.h" +#include "third_party/icu/source/i18n/unicode/coll.h" + +#if defined(TOOLKIT_GTK) +#include +#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; +} + +// Returns LEFT_TO_RIGHT or RIGHT_TO_LEFT if |character| has strong +// directionality, returns UNKNOWN_DIRECTION if it doesn't. Please refer to +// http://unicode.org/reports/tr9/ for more information. +base::i18n::TextDirection GetCharacterDirection(UChar32 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 base::i18n::RIGHT_TO_LEFT; + } else if ((property == U_LEFT_TO_RIGHT) || + (property == U_LEFT_TO_RIGHT_EMBEDDING) || + (property == U_LEFT_TO_RIGHT_OVERRIDE)) { + return base::i18n::LEFT_TO_RIGHT; + } + return base::i18n::UNKNOWN_DIRECTION; +} + +} // 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) { + 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; +} + +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); + TextDirection direction = GetCharacterDirection(character); + if (direction != UNKNOWN_DIRECTION) + return direction; + position = next_position; + } + return LEFT_TO_RIGHT; +} + +TextDirection GetStringDirection(const string16& text) { + const UChar* string = text.c_str(); + size_t length = text.length(); + size_t position = 0; + + TextDirection result(UNKNOWN_DIRECTION); + while (position < length) { + UChar32 character; + size_t next_position = position; + U16_NEXT(string, next_position, length, character); + TextDirection direction = GetCharacterDirection(character); + if (direction != UNKNOWN_DIRECTION) { + if (result != UNKNOWN_DIRECTION && result != direction) + return UNKNOWN_DIRECTION; + result = direction; + } + position = next_position; + } + + // Handle the case of a string not containing any strong directionality + // characters defaulting to LEFT_TO_RIGHT. + if (result == UNKNOWN_DIRECTION) + return LEFT_TO_RIGHT; + + return result; +} + +#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/base/i18n/rtl.h b/base/i18n/rtl.h new file mode 100644 index 0000000000..c80d2f8577 --- /dev/null +++ b/base/i18n/rtl.h @@ -0,0 +1,147 @@ +// Copyright (c) 2011 The Chromium Authors. All 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 + +#include "base/compiler_specific.h" +#include "base/i18n/base_i18n_export.h" +#include "base/strings/string16.h" +#include "build/build_config.h" + +namespace base { + +class FilePath; + +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; + +// Locale.java mirrored this enum TextDirection. Please keep in sync. +enum TextDirection { + UNKNOWN_DIRECTION = 0, + RIGHT_TO_LEFT = 1, + LEFT_TO_RIGHT = 2, + TEXT_DIRECTION_NUM_DIRECTIONS = 3, +}; + +// 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|, returns LEFT_TO_RIGHT or RIGHT_TO_LEFT if all the +// strong directionality characters in the string are of the same +// directionality. It returns UNKNOWN_DIRECTION if the string contains a mix of +// LTR and RTL strong directionality characters. Defaults to LEFT_TO_RIGHT if +// the string does not contain directionality characters. Please refer to +// http://unicode.org/reports/tr9/ for more information. +BASE_I18N_EXPORT TextDirection GetStringDirection(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/base/i18n/rtl_unittest.cc b/base/i18n/rtl_unittest.cc new file mode 100644 index 0000000000..58772b0583 --- /dev/null +++ b/base/i18n/rtl_unittest.cc @@ -0,0 +1,385 @@ +// Copyright (c) 2011 The Chromium Authors. All 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 + +#include "base/files/file_path.h" +#include "base/strings/string_util.h" +#include "base/strings/sys_string_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" +#include "third_party/icu/source/i18n/unicode/usearch.h" + +#if defined(TOOLKIT_GTK) +#include +#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, GetStringDirection) { + struct { + const wchar_t* text; + TextDirection direction; + } cases[] = { + // Test pure LTR string. + { L"foobar", LEFT_TO_RIGHT }, + { L".foobar", LEFT_TO_RIGHT }, + { L"foo, bar", LEFT_TO_RIGHT }, + // Test pure LTR with strong directionality characters of type LRE. + { L"\x202a\x202a", LEFT_TO_RIGHT }, + { L".\x202a\x202a", LEFT_TO_RIGHT }, + { L"\x202a, \x202a", LEFT_TO_RIGHT }, + // Test pure LTR with strong directionality characters of type LRO. + { L"\x202d\x202d", LEFT_TO_RIGHT }, + { L".\x202d\x202d", LEFT_TO_RIGHT }, + { L"\x202d, \x202d", LEFT_TO_RIGHT }, + // Test pure LTR with various types of strong directionality characters. + { L"foo \x202a\x202d", LEFT_TO_RIGHT }, + { L".\x202d foo \x202a", LEFT_TO_RIGHT }, + { L"\x202a, \x202d foo", LEFT_TO_RIGHT }, + // Test pure RTL with strong directionality characters of type R. + { L"\x05d0\x05d0", RIGHT_TO_LEFT }, + { L".\x05d0\x05d0", RIGHT_TO_LEFT }, + { L"\x05d0, \x05d0", RIGHT_TO_LEFT }, + // Test pure RTL with strong directionality characters of type RLE. + { L"\x202b\x202b", RIGHT_TO_LEFT }, + { L".\x202b\x202b", RIGHT_TO_LEFT }, + { L"\x202b, \x202b", RIGHT_TO_LEFT }, + // Test pure RTL with strong directionality characters of type RLO. + { L"\x202e\x202e", RIGHT_TO_LEFT }, + { L".\x202e\x202e", RIGHT_TO_LEFT }, + { L"\x202e, \x202e", RIGHT_TO_LEFT }, + // Test pure RTL with strong directionality characters of type AL. + { L"\x0622\x0622", RIGHT_TO_LEFT }, + { L".\x0622\x0622", RIGHT_TO_LEFT }, + { L"\x0622, \x0622", RIGHT_TO_LEFT }, + // Test pure RTL with various types of strong directionality characters. + { L"\x05d0\x202b\x202e\x0622", RIGHT_TO_LEFT }, + { L".\x202b\x202e\x0622\x05d0", RIGHT_TO_LEFT }, + { L"\x0622\x202e, \x202b\x05d0", RIGHT_TO_LEFT }, + // Test bidi strings. + { L"foo \x05d0 bar", UNKNOWN_DIRECTION }, + { L"\x202b foo bar", UNKNOWN_DIRECTION }, + { L"!foo \x0622 bar", UNKNOWN_DIRECTION }, + { L"\x202a\x202b", UNKNOWN_DIRECTION }, + { L"\x202e\x202d", UNKNOWN_DIRECTION }, + { L"\x0622\x202a", UNKNOWN_DIRECTION }, + { L"\x202d\x05d0", UNKNOWN_DIRECTION }, + // Test a string without strong directionality characters. + { L",!.{}", LEFT_TO_RIGHT }, + // Test empty string. + { L"", LEFT_TO_RIGHT }, + { +#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 + UNKNOWN_DIRECTION }, + { +#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, + GetStringDirection(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/base/i18n/string_compare.cc b/base/i18n/string_compare.cc new file mode 100644 index 0000000000..1ac7284359 --- /dev/null +++ b/base/i18n/string_compare.cc @@ -0,0 +1,29 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/i18n/string_compare.h" + +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" + +namespace base { +namespace i18n { + +// Compares the character data stored in two different string16 strings by +// specified Collator instance. +UCollationResult CompareString16WithCollator(const icu::Collator* collator, + const string16& lhs, + const string16& rhs) { + DCHECK(collator); + UErrorCode error = U_ZERO_ERROR; + UCollationResult result = collator->compare( + static_cast(lhs.c_str()), static_cast(lhs.length()), + static_cast(rhs.c_str()), static_cast(rhs.length()), + error); + DCHECK(U_SUCCESS(error)); + return result; +} + +} // namespace i18n +} // namespace base diff --git a/base/i18n/string_compare.h b/base/i18n/string_compare.h new file mode 100644 index 0000000000..f0f3e297f0 --- /dev/null +++ b/base/i18n/string_compare.h @@ -0,0 +1,28 @@ +// 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_I18N_STRING_COMPARE_H_ +#define BASE_I18N_STRING_COMPARE_H_ + +#include +#include +#include + +#include "base/i18n/base_i18n_export.h" +#include "base/strings/string16.h" +#include "third_party/icu/source/i18n/unicode/coll.h" + +namespace base { +namespace i18n { + +// Compares the two strings using the specified collator. +BASE_I18N_EXPORT UCollationResult CompareString16WithCollator( + const icu::Collator* collator, + const string16& lhs, + const string16& rhs); + +} // namespace i18n +} // namespace base + +#endif // BASE_I18N_STRING_COMPARATOR_H_ diff --git a/base/i18n/string_search.cc b/base/i18n/string_search.cc new file mode 100644 index 0000000000..121dfce586 --- /dev/null +++ b/base/i18n/string_search.cc @@ -0,0 +1,80 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/i18n/string_search.h" +#include "base/logging.h" + +#include "third_party/icu/source/i18n/unicode/usearch.h" + +namespace base { +namespace i18n { + +FixedPatternStringSearchIgnoringCaseAndAccents:: +FixedPatternStringSearchIgnoringCaseAndAccents(const string16& find_this) + : find_this_(find_this) { + // usearch_open requires a valid string argument to be searched, even if we + // want to set it by usearch_setText afterwards. So, supplying a dummy text. + const string16& dummy = find_this_; + + UErrorCode status = U_ZERO_ERROR; + search_ = usearch_open(find_this_.data(), find_this_.size(), + dummy.data(), dummy.size(), + uloc_getDefault(), + NULL, // breakiter + &status); + if (U_SUCCESS(status)) { + UCollator* collator = usearch_getCollator(search_); + ucol_setStrength(collator, UCOL_PRIMARY); + usearch_reset(search_); + } +} + +FixedPatternStringSearchIgnoringCaseAndAccents:: +~FixedPatternStringSearchIgnoringCaseAndAccents() { + if (search_) + usearch_close(search_); +} + +bool FixedPatternStringSearchIgnoringCaseAndAccents::Search( + const string16& in_this, size_t* match_index, size_t* match_length) { + UErrorCode status = U_ZERO_ERROR; + usearch_setText(search_, in_this.data(), in_this.size(), &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; + } + } + + int32_t index = usearch_first(search_, &status); + if (!U_SUCCESS(status) || index == USEARCH_DONE) + return false; + if (match_index) + *match_index = static_cast(index); + if (match_length) + *match_length = static_cast(usearch_getMatchedLength(search_)); + return true; +} + +bool StringSearchIgnoringCaseAndAccents(const string16& find_this, + const string16& in_this, + size_t* match_index, + size_t* match_length) { + return FixedPatternStringSearchIgnoringCaseAndAccents(find_this).Search( + in_this, match_index, match_length); +} + +} // namespace i18n +} // namespace base diff --git a/base/i18n/string_search.h b/base/i18n/string_search.h new file mode 100644 index 0000000000..138606f8c6 --- /dev/null +++ b/base/i18n/string_search.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_I18N_STRING_SEARCH_H_ +#define BASE_I18N_STRING_SEARCH_H_ + +#include "base/i18n/base_i18n_export.h" +#include "base/strings/string16.h" + +struct UStringSearch; + +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); + +// This class is for speeding up multiple StringSearchIgnoringCaseAndAccents() +// with the same |find_this| argument. |find_this| is passed as the constructor +// argument, and precomputation for searching is done only at that timing. +class BASE_I18N_EXPORT FixedPatternStringSearchIgnoringCaseAndAccents { + public: + explicit FixedPatternStringSearchIgnoringCaseAndAccents( + const string16& find_this); + ~FixedPatternStringSearchIgnoringCaseAndAccents(); + + // 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. + bool Search(const string16& in_this, + size_t* match_index, + size_t* match_length); + + private: + string16 find_this_; + UStringSearch* search_; +}; + +} // namespace i18n +} // namespace base + +#endif // BASE_I18N_STRING_SEARCH_H_ diff --git a/base/i18n/string_search_unittest.cc b/base/i18n/string_search_unittest.cc new file mode 100644 index 0000000000..9419b267cb --- /dev/null +++ b/base/i18n/string_search_unittest.cc @@ -0,0 +1,226 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/i18n/rtl.h" +#include "base/i18n/string_search.h" +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/icu/source/i18n/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); +} + +TEST(StringSearchTest, FixedPatternMultipleSearch) { + 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; + + // Search "hello" over multiple texts. + FixedPatternStringSearchIgnoringCaseAndAccents query(ASCIIToUTF16("hello")); + EXPECT_TRUE(query.Search(ASCIIToUTF16("12hello34"), &index, &length)); + EXPECT_EQ(2U, index); + EXPECT_EQ(5U, length); + EXPECT_FALSE(query.Search(ASCIIToUTF16("bye"), &index, &length)); + EXPECT_TRUE(query.Search(ASCIIToUTF16("hELLo"), &index, &length)); + EXPECT_EQ(0U, index); + EXPECT_EQ(5U, length); + + if (locale_is_posix) + SetICUDefaultLocale(default_locale.data()); +} + +} // namespace i18n +} // namespace base diff --git a/base/i18n/time_formatting.cc b/base/i18n/time_formatting.cc new file mode 100644 index 0000000000..3973dd2508 --- /dev/null +++ b/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/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "third_party/icu/source/i18n/unicode/datefmt.h" +#include "third_party/icu/source/i18n/unicode/dtptngen.h" +#include "third_party/icu/source/i18n/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(time.ToDoubleT() * 1000), date_string); + return string16(date_string.getBuffer(), + static_cast(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(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(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 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 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 formatter( + icu::DateFormat::createDateInstance(icu::DateFormat::kMedium)); + return TimeFormat(formatter.get(), time); +} + +string16 TimeFormatShortDateNumeric(const Time& time) { + scoped_ptr formatter( + icu::DateFormat::createDateInstance(icu::DateFormat::kShort)); + return TimeFormat(formatter.get(), time); +} + +string16 TimeFormatShortDateAndTime(const Time& time) { + scoped_ptr formatter( + icu::DateFormat::createDateTimeInstance(icu::DateFormat::kShort)); + return TimeFormat(formatter.get(), time); +} + +string16 TimeFormatFriendlyDateAndTime(const Time& time) { + scoped_ptr formatter( + icu::DateFormat::createDateTimeInstance(icu::DateFormat::kFull)); + return TimeFormat(formatter.get(), time); +} + +string16 TimeFormatFriendlyDate(const Time& time) { + scoped_ptr 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 formatter( + static_cast( + 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/base/i18n/time_formatting.h b/base/i18n/time_formatting.h new file mode 100644 index 0000000000..226d1a91a2 --- /dev/null +++ b/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/strings/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/base/i18n/time_formatting_unittest.cc b/base/i18n/time_formatting_unittest.cc new file mode 100644 index 0000000000..03a3aa3ec7 --- /dev/null +++ b/base/i18n/time_formatting_unittest.cc @@ -0,0 +1,175 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/i18n/time_formatting.h" + +#include "base/i18n/rtl.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/icu/source/common/unicode/uversion.h" + +namespace base { +namespace { + +const Time::Exploded kTestDateTimeExploded = { + 2011, 4, 6, 30, // Sat, Apr 30, 2011 + 15, 42, 7, 0 // 15:42:07.000 +}; + +TEST(TimeFormattingTest, 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, 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")); +#if U_ICU_VERSION_MAJOR_NUM >= 50 + string16 clock12h_pm(ASCIIToUTF16("3:42 pm")); +#else + // TODO(phajdan.jr): Clean up after bundled ICU gets updated to 50. + string16 clock12h_pm(ASCIIToUTF16("3:42 PM")); +#endif + 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, 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, 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)); + +#if U_ICU_VERSION_MAJOR_NUM >= 50 + EXPECT_EQ(ASCIIToUTF16("4/30/11, 3:42:07 PM"), + TimeFormatShortDateAndTime(time)); +#else + // TODO(phajdan.jr): Clean up after bundled ICU gets updated to 50. + EXPECT_EQ(ASCIIToUTF16("4/30/11 3:42:07 PM"), + TimeFormatShortDateAndTime(time)); +#endif + +#if U_ICU_VERSION_MAJOR_NUM >= 50 + EXPECT_EQ(ASCIIToUTF16("Saturday, April 30, 2011 at 3:42:07 PM"), + TimeFormatFriendlyDateAndTime(time)); +#else + // TODO(phajdan.jr): Clean up after bundled ICU gets updated to 50. + EXPECT_EQ(ASCIIToUTF16("Saturday, April 30, 2011 3:42:07 PM"), + TimeFormatFriendlyDateAndTime(time)); +#endif + + EXPECT_EQ(ASCIIToUTF16("Saturday, April 30, 2011"), + TimeFormatFriendlyDate(time)); +} + +TEST(TimeFormattingTest, 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/base/id_map.h b/base/id_map.h new file mode 100644 index 0000000000..27098d2976 --- /dev/null +++ b/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 + +#include "base/basictypes.h" +#include "base/containers/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 +class IDMap : public base::NonThreadSafe { + private: + typedef int32 KeyType; + typedef base::hash_map 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::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::release(i->second); + data_.erase(i); + } else { + removed_ids_.insert(id); + } + } + + void Clear() { + DCHECK(CalledOnValidThread()); + if (iteration_depth_ == 0) { + Releaser::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 Iterator { + public: + Iterator(IDMap* 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* map_; + typename HashTable::const_iterator iter_; + }; + + typedef Iterator iterator; + typedef Iterator const_iterator; + + private: + + // The dummy parameter is there because C++ standard does not allow + // explicitly specialized templates inside classes + template struct Releaser { + static inline void release(T* ptr) {} + static inline void release_all(HashTable* table) {} + }; + + template struct Releaser { + 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::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 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/base/id_map_unittest.cc b/base/id_map_unittest.cc new file mode 100644 index 0000000000..76d7c3ed4e --- /dev/null +++ b/base/id_map_unittest.cc @@ -0,0 +1,335 @@ +// Copyright (c) 2011 The Chromium Authors. All 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 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 map; + + TestObject obj1; + TestObject obj2; + TestObject obj3; + + map.Add(&obj1); + map.Add(&obj2); + map.Add(&obj3); + + { + IDMap::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 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::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 map; + + TestObject obj1; + TestObject obj2; + TestObject obj3; + + map.Add(&obj1); + map.Add(&obj2); + map.Add(&obj3); + + EXPECT_EQ(0, map.iteration_depth()); + + { + IDMap::const_iterator iter1(&map); + EXPECT_EQ(1, map.iteration_depth()); + + // Make sure that copying the iterator correctly increments + // map's iteration depth. + IDMap::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 map; + + TestObject obj1; + TestObject obj2; + TestObject obj3; + + map.Add(&obj1); + map.Add(&obj2); + map.Add(&obj3); + + EXPECT_EQ(0, map.iteration_depth()); + + { + IDMap::const_iterator iter1(&map); + EXPECT_EQ(1, map.iteration_depth()); + + IDMap::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()); +} + +TEST(IDMapTest, IteratorRemainsValidWhenClearing) { + IDMap 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::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()); +} + +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 map_external; + IDMap 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 map_external; + IDMap 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 map_external; + IDMap 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/base/ini_parser.cc b/base/ini_parser.cc new file mode 100644 index 0000000000..7a2bd1c911 --- /dev/null +++ b/base/ini_parser.cc @@ -0,0 +1,66 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/ini_parser.h" + +#include "base/logging.h" +#include "base/strings/string_tokenizer.h" + +namespace base { + +INIParser::INIParser() : used_(false) {} + +INIParser::~INIParser() {} + +void INIParser::Parse(const std::string& content) { + DCHECK(!used_); + used_ = true; + base::StringTokenizer tokenizer(content, "\r\n"); + + std::string current_section; + while (tokenizer.GetNext()) { + std::string line = tokenizer.token(); + if (line.empty()) { + // Skips the empty line. + continue; + } + if (line[0] == '#' || line[0] == ';') { + // This line is a comment. + continue; + } + if (line[0] == '[') { + // It is a section header. + current_section = line.substr(1); + size_t end = current_section.rfind(']'); + if (end != std::string::npos) + current_section.erase(end); + } else { + std::string key, value; + size_t equal = line.find('='); + if (equal != std::string::npos) { + key = line.substr(0, equal); + value = line.substr(equal + 1); + HandleTriplet(current_section, key, value); + } + } + } +} + +DictionaryValueINIParser::DictionaryValueINIParser() {} + +DictionaryValueINIParser::~DictionaryValueINIParser() {} + +void DictionaryValueINIParser::HandleTriplet(const std::string& section, + const std::string& key, + const std::string& value) { + + // Checks whether the section and key contain a '.' character. + // Those sections and keys break DictionaryValue's path format when not + // using the *WithoutPathExpansion methods. + if (section.find('.') == std::string::npos && + key.find('.') == std::string::npos) + root_.SetString(section + "." + key, value); +} + +} // namespace base diff --git a/base/ini_parser.h b/base/ini_parser.h new file mode 100644 index 0000000000..0aa7754d34 --- /dev/null +++ b/base/ini_parser.h @@ -0,0 +1,69 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_INI_PARSER_H_ +#define BASE_INI_PARSER_H_ + +#include + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/values.h" + +namespace base { + +// Parses INI files in a string. Users should in inherit from this class. +// This is a very basic INI parser with these characteristics: +// - Ignores blank lines. +// - Ignores comment lines beginning with '#' or ';'. +// - Duplicate key names in the same section will simply cause repeated calls +// to HandleTriplet with the same |section| and |key| parameters. +// - No escape characters supported. +// - Global properties result in calls to HandleTriplet with an empty string in +// the |section| argument. +// - Section headers begin with a '[' character. It is recommended, but +// not required to close the header bracket with a ']' character. All +// characters after a closing ']' character is ignored. +// - Key value pairs are indicated with an '=' character. Whitespace is not +// ignored. Quoting is not supported. Everything before the first '=' +// is considered the |key|, and everything after is the |value|. +class BASE_EXPORT INIParser { + public: + INIParser(); + virtual ~INIParser(); + + // May only be called once per instance. + void Parse(const std::string& content); + + private: + virtual void HandleTriplet(const std::string& section, + const std::string& key, + const std::string& value) = 0; + + bool used_; +}; + +// Parsed values are stored as strings at the "section.key" path. Triplets with +// |section| or |key| parameters containing '.' are ignored. +class BASE_EXPORT DictionaryValueINIParser : public INIParser { + public: + DictionaryValueINIParser(); + virtual ~DictionaryValueINIParser(); + + const DictionaryValue& root() const { return root_; } + + private: + // INIParser implementation. + virtual void HandleTriplet(const std::string& section, + const std::string& key, + const std::string& value) OVERRIDE; + + DictionaryValue root_; + + DISALLOW_COPY_AND_ASSIGN(DictionaryValueINIParser); +}; + +} // namespace base + +#endif // BASE_INI_PARSER_H_ diff --git a/base/ini_parser_unittest.cc b/base/ini_parser_unittest.cc new file mode 100644 index 0000000000..ff90f6c8fc --- /dev/null +++ b/base/ini_parser_unittest.cc @@ -0,0 +1,131 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include "base/ini_parser.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace { + +struct TestTriplet { + TestTriplet(const std::string& section, + const std::string& key, + const std::string& value) + : section(section), + key(key), + value(value) { + } + + std::string section; + std::string key; + std::string value; +}; + +class TestINIParser : public INIParser { + public: + explicit TestINIParser( + const std::vector& expected_triplets) + : expected_triplets_(expected_triplets), + pair_i_(0) { + } + virtual ~TestINIParser() {} + + size_t pair_i() { + return pair_i_; + } + + private: + virtual void HandleTriplet(const std::string& section, const std::string& key, + const std::string& value) OVERRIDE { + EXPECT_EQ(expected_triplets_[pair_i_].section, section); + EXPECT_EQ(expected_triplets_[pair_i_].key, key); + EXPECT_EQ(expected_triplets_[pair_i_].value, value); + ++pair_i_; + } + + std::vector expected_triplets_; + size_t pair_i_; +}; + +TEST(INIParserTest, BasicValid) { + std::vector expected_triplets; + expected_triplets.push_back(TestTriplet("section1", "key1", "value1")); + expected_triplets.push_back(TestTriplet("section1", "key2", "value2")); + expected_triplets.push_back(TestTriplet("section1", "key3", "value3")); + expected_triplets.push_back(TestTriplet("section2", "key4", "value4")); + expected_triplets.push_back(TestTriplet("section2", "key5", + "value=with=equals")); + expected_triplets.push_back(TestTriplet("section2", "key6", "value6")); + TestINIParser test_parser(expected_triplets); + + test_parser.Parse( + "[section1]\n" + "key1=value1\n" + "key2=value2\r\n" // Testing DOS "\r\n" line endings. + "key3=value3\n" + "[section2\n" // Testing omitted closing bracket. + "key4=value4\r" // Testing "\r" line endings. + "key5=value=with=equals\n" + "key6=value6"); // Testing omitted final line ending. +} + +TEST(INIParserTest, IgnoreBlankLinesAndComments) { + std::vector expected_triplets; + expected_triplets.push_back(TestTriplet("section1", "key1", "value1")); + expected_triplets.push_back(TestTriplet("section1", "key2", "value2")); + expected_triplets.push_back(TestTriplet("section1", "key3", "value3")); + expected_triplets.push_back(TestTriplet("section2", "key4", "value4")); + expected_triplets.push_back(TestTriplet("section2", "key5", "value5")); + expected_triplets.push_back(TestTriplet("section2", "key6", "value6")); + TestINIParser test_parser(expected_triplets); + + test_parser.Parse( + "\n" + "[section1]\n" + "key1=value1\n" + "\n" + "\n" + "key2=value2\n" + "key3=value3\n" + "\n" + ";Comment1" + "\n" + "[section2]\n" + "key4=value4\n" + "#Comment2\n" + "key5=value5\n" + "\n" + "key6=value6\n"); +} + +TEST(INIParserTest, DictionaryValueINIParser) { + DictionaryValueINIParser test_parser; + + test_parser.Parse( + "[section1]\n" + "key1=value1\n" + "key.2=value2\n" + "key3=va.lue3\n" + "[se.ction2]\n" + "key.4=value4\n" + "key5=value5\n"); + + const DictionaryValue& root = test_parser.root(); + std::string value; + EXPECT_TRUE(root.GetString("section1.key1", &value)); + EXPECT_EQ("value1", value); + EXPECT_FALSE(root.GetString("section1.key.2", &value)); + EXPECT_TRUE(root.GetString("section1.key3", &value)); + EXPECT_EQ("va.lue3", value); + EXPECT_FALSE(root.GetString("se.ction2.key.4", &value)); + EXPECT_FALSE(root.GetString("se.ction2.key5", &value)); +} + +} // namespace + +} // namespace base diff --git a/base/ios/OWNERS b/base/ios/OWNERS new file mode 100644 index 0000000000..625640026f --- /dev/null +++ b/base/ios/OWNERS @@ -0,0 +1,3 @@ +qsr@chromium.org +rohitrao@chromium.org +stuartmorgan@chromium.org diff --git a/base/ios/device_util.h b/base/ios/device_util.h new file mode 100644 index 0000000000..fe4833dd36 --- /dev/null +++ b/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 + +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/base/ios/device_util.mm b/base/ios/device_util.mm new file mode 100644 index 0000000000..96008332be --- /dev/null +++ b/base/ios/device_util.mm @@ -0,0 +1,165 @@ +// Copyright (c) 2012 The Chromium Authors. All 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 +#import + +#include +#include +#include +#include +#include + +#include "base/ios/ios_util.h" +#include "base/logging.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/sys_string_conversions.h" + +namespace { + +// Client ID key in the user preferences. +NSString* const kLegacyClientIdPreferenceKey = @"ChromiumClientID"; +NSString* const kClientIdPreferenceKey = @"ChromeClientID"; +// Current hardware type. This is used to detect that a device has been backed +// up and restored to another device, and allows regenerating a new device id. +NSString* const kHardwareTypePreferenceKey = @"ClientIDGenerationHardwareType"; +// 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(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( + &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::ScopedCFTypeRef uuid_object( + CFUUIDCreate(kCFAllocatorDefault)); + base::ScopedCFTypeRef uuid_string( + CFUUIDCreateString(kCFAllocatorDefault, uuid_object)); + return base::SysCFStringRefToUTF8(uuid_string); +} + +std::string GetDeviceIdentifier(const char* salt) { + NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; + + NSString* last_seen_hardware = + [defaults stringForKey:kHardwareTypePreferenceKey]; + NSString* current_hardware = base::SysUTF8ToNSString(GetPlatform()); + if (!last_seen_hardware) { + last_seen_hardware = current_hardware; + [defaults setObject:current_hardware forKey:kHardwareTypePreferenceKey]; + [defaults synchronize]; + } + + NSString* client_id = [defaults stringForKey:kClientIdPreferenceKey]; + + if (!client_id || ![last_seen_hardware isEqualToString:current_hardware]) { + client_id = GenerateClientId(); + [defaults setObject:client_id forKey:kClientIdPreferenceKey]; + [defaults setObject:current_hardware forKey:kHardwareTypePreferenceKey]; + [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(hash); + + base::ScopedCFTypeRef uuid_object( + CFUUIDCreateFromUUIDBytes(kCFAllocatorDefault, *uuid_bytes)); + base::ScopedCFTypeRef device_id( + CFUUIDCreateString(kCFAllocatorDefault, uuid_object)); + return base::SysCFStringRefToUTF8(device_id); +} + +} // namespace device_util +} // namespace ios diff --git a/base/ios/device_util_unittest.mm b/base/ios/device_util_unittest.mm new file mode 100644 index 0000000000..c2aefe0f73 --- /dev/null +++ b/base/ios/device_util_unittest.mm @@ -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. + +#import + +#include "base/ios/device_util.h" +#include "base/ios/ios_util.h" +#include "base/strings/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 removeObjectForKey:@"ClientIDGenerationHardwareType"]; + [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(); +} + +TEST_F(DeviceUtilTest, CheckDeviceMigration) { + CleanNSUserDefaultsForDeviceId(); + + NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; + [defaults setObject:@"10000000-0000-0000-0000-000000000000" + forKey:@"ChromeClientID"]; + [defaults synchronize]; + std::string base_id = ios::device_util::GetDeviceIdentifier(NULL); + [defaults setObject:@"Foo" forKey:@"ClientIDGenerationHardwareType"]; + [defaults synchronize]; + std::string new_id = ios::device_util::GetDeviceIdentifier(NULL); + EXPECT_NE(new_id, base_id); + + CleanNSUserDefaultsForDeviceId(); +} + +} // namespace diff --git a/base/ios/ios_util.h b/base/ios/ios_util.h new file mode 100644 index 0000000000..7e2e621dfd --- /dev/null +++ b/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 operating system is iOS 6 or later. +BASE_EXPORT bool IsRunningOnIOS6OrLater(); + +// Returns whether the operating system is iOS 7 or later. +BASE_EXPORT bool IsRunningOnIOS7OrLater(); + +// Returns whether the operating 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/base/ios/ios_util.mm b/base/ios/ios_util.mm new file mode 100644 index 0000000000..a76911017f --- /dev/null +++ b/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 IsRunningOnIOS6OrLater() { + return IsRunningOnOrLater(6, 0, 0); +} + +bool IsRunningOnIOS7OrLater() { + return IsRunningOnOrLater(7, 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/base/ios/scoped_critical_action.h b/base/ios/scoped_critical_action.h new file mode 100644 index 0000000000..660a83ad02 --- /dev/null +++ b/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/base/ios/scoped_critical_action.mm b/base/ios/scoped_critical_action.mm new file mode 100644 index 0000000000..734c0a215b --- /dev/null +++ b/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 + +#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/base/irt-x86-32.base_untrusted.source_list.gypcmd b/base/irt-x86-32.base_untrusted.source_list.gypcmd new file mode 100644 index 0000000000..8dbe0ca926 --- /dev/null +++ b/base/irt-x86-32.base_untrusted.source_list.gypcmd @@ -0,0 +1,353 @@ +../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/prcpucfg.h +third_party/nspr/prcpucfg_freebsd.h +third_party/nspr/prcpucfg_nacl.h +third_party/nspr/prcpucfg_openbsd.h +third_party/nspr/prcpucfg_solaris.h +third_party/nspr/prtime.cc +third_party/nspr/prtime.h +third_party/nspr/prtypes.h +third_party/xdg_mime/xdgmime.h +allocator/allocator_extension.cc +allocator/allocator_extension.h +at_exit.cc +at_exit.h +atomic_ref_count.h +atomic_sequence_num.h +atomicops.h +atomicops_internals_gcc.h +atomicops_internals_tsan.h +atomicops_internals_x86_gcc.h +atomicops_internals_x86_msvc.h +base_export.h +base_paths.h +base_paths_android.h +base_paths_posix.cc +base_paths_posix.h +base_switches.h +base64.cc +base64.h +basictypes.h +bind.h +bind_helpers.cc +bind_helpers.h +bind_internal.h +bits.h +build_time.cc +build_time.h +callback.h +callback_helpers.h +callback_internal.cc +callback_internal.h +cancelable_callback.h +command_line.cc +command_line.h +compiler_specific.h +containers/hash_tables.h +containers/linked_list.h +containers/mru_cache.h +containers/small_map.h +containers/stack_container.h +cpu.h +critical_closure.h +debug/alias.cc +debug/alias.h +debug/crash_logging.cc +debug/crash_logging.h +debug/debugger.cc +debug/debugger.h +debug/debugger_posix.cc +debug/leak_annotations.h +debug/leak_tracker.h +debug/profiler.cc +debug/profiler.h +debug/stack_trace.cc +debug/stack_trace.h +debug/trace_event.h +debug/trace_event_impl.cc +debug/trace_event_impl.h +debug/trace_event_impl_constants.cc +debug/trace_event_memory.cc +debug/trace_event_memory.h +deferred_sequenced_task_runner.cc +deferred_sequenced_task_runner.h +environment.cc +environment.h +file_descriptor_posix.h +file_util.h +file_version_info.h +files/dir_reader_fallback.h +files/dir_reader_posix.h +files/file_enumerator.cc +files/file_enumerator.h +files/file_path.cc +files/file_path.h +files/file_path_constants.cc +files/file_path_watcher.cc +files/file_path_watcher.h +files/file_path_watcher_stub.cc +files/file_util_proxy.h +files/important_file_writer.h +files/important_file_writer.cc +files/memory_mapped_file.cc +files/memory_mapped_file.h +files/memory_mapped_file_posix.cc +files/scoped_platform_file_closer.cc +files/scoped_platform_file_closer.h +files/scoped_temp_dir.h +float_util.h +format_macros.h +gtest_prod_util.h +guid.cc +guid.h +guid_posix.cc +hash.cc +hash.h +id_map.h +ini_parser.cc +ini_parser.h +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 +memory/aligned_memory.cc +memory/aligned_memory.h +memory/discardable_memory.cc +memory/discardable_memory.h +memory/linked_ptr.h +memory/manual_constructor.h +memory/memory_pressure_listener.cc +memory/memory_pressure_listener.h +memory/raw_scoped_refptr_mismatch_checker.h +memory/ref_counted.cc +memory/ref_counted.h +memory/ref_counted_delete_on_message_loop.h +memory/ref_counted_memory.cc +memory/ref_counted_memory.h +memory/scoped_handle.h +memory/scoped_open_process.h +memory/scoped_policy.h +memory/scoped_ptr.h +memory/scoped_vector.h +memory/shared_memory.h +memory/shared_memory_nacl.cc +memory/singleton.cc +memory/singleton.h +memory/weak_ptr.cc +memory/weak_ptr.h +message_loop/incoming_task_queue.cc +message_loop/incoming_task_queue.h +message_loop/message_loop.cc +message_loop/message_loop.h +message_loop/message_loop_proxy.cc +message_loop/message_loop_proxy.h +message_loop/message_loop_proxy_impl.cc +message_loop/message_loop_proxy_impl.h +message_loop/message_pump.cc +message_loop/message_pump.h +message_loop/message_pump_android.h +message_loop/message_pump_default.cc +message_loop/message_pump_default.h +move.h +native_library.h +observer_list.h +observer_list_threadsafe.h +os_compat_android.h +os_compat_nacl.cc +os_compat_nacl.h +path_service.h +pending_task.cc +pending_task.h +pickle.cc +pickle.h +platform_file.cc +platform_file.h +platform_file_posix.cc +port.h +posix/eintr_wrapper.h +posix/global_descriptors.cc +posix/global_descriptors.h +power_monitor/power_monitor.cc +power_monitor/power_monitor.h +power_monitor/power_monitor_device_source_android.h +power_monitor/power_monitor_device_source.cc +power_monitor/power_monitor_device_source.h +power_monitor/power_monitor_device_source_posix.cc +power_monitor/power_monitor_source.cc +power_monitor/power_monitor_source.h +power_monitor/power_observer.h +process/kill.cc +process/kill.h +process/launch.h +process/memory.h +process/process.h +process/process_handle_posix.cc +process/process_info.h +process/process_iterator.cc +process/process_iterator.h +process/process_metrics.h +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 +run_loop.cc +run_loop.h +safe_numerics.h +safe_strerror_posix.cc +safe_strerror_posix.h +scoped_native_library.h +sequence_checker.h +sequence_checker_impl.cc +sequence_checker_impl.h +sequenced_task_runner.cc +sequenced_task_runner.h +sequenced_task_runner_helpers.h +sha1.h +sha1_portable.cc +single_thread_task_runner.h +stl_util.h +strings/latin1_string_conversions.cc +strings/latin1_string_conversions.h +strings/nullable_string16.cc +strings/nullable_string16.h +strings/string16.cc +strings/string16.h +strings/string_number_conversions.cc +strings/string_split.cc +strings/string_split.h +strings/string_number_conversions.h +strings/string_piece.cc +strings/string_piece.h +strings/string_tokenizer.h +strings/string_util.cc +strings/string_util.h +strings/string_util_constants.cc +strings/string_util_posix.h +strings/stringize_macros.h +strings/stringprintf.cc +strings/stringprintf.h +strings/sys_string_conversions.h +strings/sys_string_conversions_posix.cc +strings/utf_offset_string_conversions.cc +strings/utf_offset_string_conversions.h +strings/utf_string_conversion_utils.cc +strings/utf_string_conversion_utils.h +strings/utf_string_conversions.cc +strings/utf_string_conversions.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/lock.cc +synchronization/lock.h +synchronization/lock_impl.h +synchronization/lock_impl_posix.cc +synchronization/spin_wait.h +synchronization/waitable_event.h +synchronization/waitable_event_posix.cc +synchronization/waitable_event_watcher.h +synchronization/waitable_event_watcher_posix.cc +system_monitor/system_monitor.cc +system_monitor/system_monitor.h +sys_byteorder.h +sys_info.cc +sys_info.h +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_linux.cc +threading/platform_thread_posix.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_id_name_manager.cc +threading/thread_id_name_manager.h +threading/thread_local.h +threading/thread_local_posix.cc +threading/thread_local_storage.h +threading/thread_local_storage_posix.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 +time/clock.cc +time/clock.h +time/default_clock.cc +time/default_clock.h +time/default_tick_clock.cc +time/default_tick_clock.h +time/tick_clock.cc +time/tick_clock.h +time/time.cc +time/time.h +time/time_posix.cc +timer/hi_res_timer_manager_posix.cc +timer/hi_res_timer_manager.h +timer/timer.cc +timer/timer.h +tracked_objects.cc +tracked_objects.h +tracking_info.cc +tracking_info.h +tuple.h +values.cc +values.h +value_conversions.cc +value_conversions.h +version.cc +version.h +vlog.cc +vlog.h +base_switches.cc +base_switches.h +strings/string16.cc +sync_socket_nacl.cc +time/time_posix.cc diff --git a/base/irt-x86-64.base_untrusted.source_list.gypcmd b/base/irt-x86-64.base_untrusted.source_list.gypcmd new file mode 100644 index 0000000000..8dbe0ca926 --- /dev/null +++ b/base/irt-x86-64.base_untrusted.source_list.gypcmd @@ -0,0 +1,353 @@ +../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/prcpucfg.h +third_party/nspr/prcpucfg_freebsd.h +third_party/nspr/prcpucfg_nacl.h +third_party/nspr/prcpucfg_openbsd.h +third_party/nspr/prcpucfg_solaris.h +third_party/nspr/prtime.cc +third_party/nspr/prtime.h +third_party/nspr/prtypes.h +third_party/xdg_mime/xdgmime.h +allocator/allocator_extension.cc +allocator/allocator_extension.h +at_exit.cc +at_exit.h +atomic_ref_count.h +atomic_sequence_num.h +atomicops.h +atomicops_internals_gcc.h +atomicops_internals_tsan.h +atomicops_internals_x86_gcc.h +atomicops_internals_x86_msvc.h +base_export.h +base_paths.h +base_paths_android.h +base_paths_posix.cc +base_paths_posix.h +base_switches.h +base64.cc +base64.h +basictypes.h +bind.h +bind_helpers.cc +bind_helpers.h +bind_internal.h +bits.h +build_time.cc +build_time.h +callback.h +callback_helpers.h +callback_internal.cc +callback_internal.h +cancelable_callback.h +command_line.cc +command_line.h +compiler_specific.h +containers/hash_tables.h +containers/linked_list.h +containers/mru_cache.h +containers/small_map.h +containers/stack_container.h +cpu.h +critical_closure.h +debug/alias.cc +debug/alias.h +debug/crash_logging.cc +debug/crash_logging.h +debug/debugger.cc +debug/debugger.h +debug/debugger_posix.cc +debug/leak_annotations.h +debug/leak_tracker.h +debug/profiler.cc +debug/profiler.h +debug/stack_trace.cc +debug/stack_trace.h +debug/trace_event.h +debug/trace_event_impl.cc +debug/trace_event_impl.h +debug/trace_event_impl_constants.cc +debug/trace_event_memory.cc +debug/trace_event_memory.h +deferred_sequenced_task_runner.cc +deferred_sequenced_task_runner.h +environment.cc +environment.h +file_descriptor_posix.h +file_util.h +file_version_info.h +files/dir_reader_fallback.h +files/dir_reader_posix.h +files/file_enumerator.cc +files/file_enumerator.h +files/file_path.cc +files/file_path.h +files/file_path_constants.cc +files/file_path_watcher.cc +files/file_path_watcher.h +files/file_path_watcher_stub.cc +files/file_util_proxy.h +files/important_file_writer.h +files/important_file_writer.cc +files/memory_mapped_file.cc +files/memory_mapped_file.h +files/memory_mapped_file_posix.cc +files/scoped_platform_file_closer.cc +files/scoped_platform_file_closer.h +files/scoped_temp_dir.h +float_util.h +format_macros.h +gtest_prod_util.h +guid.cc +guid.h +guid_posix.cc +hash.cc +hash.h +id_map.h +ini_parser.cc +ini_parser.h +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 +memory/aligned_memory.cc +memory/aligned_memory.h +memory/discardable_memory.cc +memory/discardable_memory.h +memory/linked_ptr.h +memory/manual_constructor.h +memory/memory_pressure_listener.cc +memory/memory_pressure_listener.h +memory/raw_scoped_refptr_mismatch_checker.h +memory/ref_counted.cc +memory/ref_counted.h +memory/ref_counted_delete_on_message_loop.h +memory/ref_counted_memory.cc +memory/ref_counted_memory.h +memory/scoped_handle.h +memory/scoped_open_process.h +memory/scoped_policy.h +memory/scoped_ptr.h +memory/scoped_vector.h +memory/shared_memory.h +memory/shared_memory_nacl.cc +memory/singleton.cc +memory/singleton.h +memory/weak_ptr.cc +memory/weak_ptr.h +message_loop/incoming_task_queue.cc +message_loop/incoming_task_queue.h +message_loop/message_loop.cc +message_loop/message_loop.h +message_loop/message_loop_proxy.cc +message_loop/message_loop_proxy.h +message_loop/message_loop_proxy_impl.cc +message_loop/message_loop_proxy_impl.h +message_loop/message_pump.cc +message_loop/message_pump.h +message_loop/message_pump_android.h +message_loop/message_pump_default.cc +message_loop/message_pump_default.h +move.h +native_library.h +observer_list.h +observer_list_threadsafe.h +os_compat_android.h +os_compat_nacl.cc +os_compat_nacl.h +path_service.h +pending_task.cc +pending_task.h +pickle.cc +pickle.h +platform_file.cc +platform_file.h +platform_file_posix.cc +port.h +posix/eintr_wrapper.h +posix/global_descriptors.cc +posix/global_descriptors.h +power_monitor/power_monitor.cc +power_monitor/power_monitor.h +power_monitor/power_monitor_device_source_android.h +power_monitor/power_monitor_device_source.cc +power_monitor/power_monitor_device_source.h +power_monitor/power_monitor_device_source_posix.cc +power_monitor/power_monitor_source.cc +power_monitor/power_monitor_source.h +power_monitor/power_observer.h +process/kill.cc +process/kill.h +process/launch.h +process/memory.h +process/process.h +process/process_handle_posix.cc +process/process_info.h +process/process_iterator.cc +process/process_iterator.h +process/process_metrics.h +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 +run_loop.cc +run_loop.h +safe_numerics.h +safe_strerror_posix.cc +safe_strerror_posix.h +scoped_native_library.h +sequence_checker.h +sequence_checker_impl.cc +sequence_checker_impl.h +sequenced_task_runner.cc +sequenced_task_runner.h +sequenced_task_runner_helpers.h +sha1.h +sha1_portable.cc +single_thread_task_runner.h +stl_util.h +strings/latin1_string_conversions.cc +strings/latin1_string_conversions.h +strings/nullable_string16.cc +strings/nullable_string16.h +strings/string16.cc +strings/string16.h +strings/string_number_conversions.cc +strings/string_split.cc +strings/string_split.h +strings/string_number_conversions.h +strings/string_piece.cc +strings/string_piece.h +strings/string_tokenizer.h +strings/string_util.cc +strings/string_util.h +strings/string_util_constants.cc +strings/string_util_posix.h +strings/stringize_macros.h +strings/stringprintf.cc +strings/stringprintf.h +strings/sys_string_conversions.h +strings/sys_string_conversions_posix.cc +strings/utf_offset_string_conversions.cc +strings/utf_offset_string_conversions.h +strings/utf_string_conversion_utils.cc +strings/utf_string_conversion_utils.h +strings/utf_string_conversions.cc +strings/utf_string_conversions.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/lock.cc +synchronization/lock.h +synchronization/lock_impl.h +synchronization/lock_impl_posix.cc +synchronization/spin_wait.h +synchronization/waitable_event.h +synchronization/waitable_event_posix.cc +synchronization/waitable_event_watcher.h +synchronization/waitable_event_watcher_posix.cc +system_monitor/system_monitor.cc +system_monitor/system_monitor.h +sys_byteorder.h +sys_info.cc +sys_info.h +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_linux.cc +threading/platform_thread_posix.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_id_name_manager.cc +threading/thread_id_name_manager.h +threading/thread_local.h +threading/thread_local_posix.cc +threading/thread_local_storage.h +threading/thread_local_storage_posix.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 +time/clock.cc +time/clock.h +time/default_clock.cc +time/default_clock.h +time/default_tick_clock.cc +time/default_tick_clock.h +time/tick_clock.cc +time/tick_clock.h +time/time.cc +time/time.h +time/time_posix.cc +timer/hi_res_timer_manager_posix.cc +timer/hi_res_timer_manager.h +timer/timer.cc +timer/timer.h +tracked_objects.cc +tracked_objects.h +tracking_info.cc +tracking_info.h +tuple.h +values.cc +values.h +value_conversions.cc +value_conversions.h +version.cc +version.h +vlog.cc +vlog.h +base_switches.cc +base_switches.h +strings/string16.cc +sync_socket_nacl.cc +time/time_posix.cc diff --git a/base/json/json_file_value_serializer.cc b/base/json/json_file_value_serializer.cc new file mode 100644 index 0000000000..37371e8752 --- /dev/null +++ b/base/json/json_file_value_serializer.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/json/json_file_value_serializer.h" + +#include "base/file_util.h" +#include "base/json/json_string_value_serializer.h" +#include "base/logging.h" + +using base::FilePath; + +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 base::Value& root) { + return SerializeInternal(root, false); +} + +bool JSONFileValueSerializer::SerializeAndOmitBinaryValues( + const base::Value& root) { + return SerializeInternal(root, true); +} + +bool JSONFileValueSerializer::SerializeInternal(const base::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(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 (!base::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 ""; + } +} + +base::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/base/json/json_file_value_serializer.h b/base/json/json_file_value_serializer.h new file mode 100644 index 0000000000..4a0c334871 --- /dev/null +++ b/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 + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/files/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 base::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 base::Value& root) OVERRIDE; + + // Equivalent to Serialize(root) except binary values are omitted from the + // output. + bool SerializeAndOmitBinaryValues(const base::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 base::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 base::Value& root, bool omit_binary_values); + + base::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/base/json/json_parser.cc b/base/json/json_parser.cc new file mode 100644 index 0000000000..f1f43337c4 --- /dev/null +++ b/base/json/json_parser.cc @@ -0,0 +1,965 @@ +// Copyright (c) 2012 The Chromium Authors. All 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/strings/string_number_conversions.h" +#include "base/strings/string_piece.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversion_utils.h" +#include "base/strings/utf_string_conversions.h" +#include "base/third_party/icu/icu_utf.h" +#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(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 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, + scoped_ptr* 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. + scoped_ptr out_owned; + if (!DictionaryValue::RemoveWithoutPathExpansion(key, &out_owned)) + return false; + + out->reset(out_owned->DeepCopy()); + + return true; + } + + private: + scoped_ptr 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(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 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, scoped_ptr* 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. + scoped_ptr out_owned; + if (!ListValue::Remove(index, &out_owned)) + return false; + + out->reset(out_owned->DeepCopy()); + + return true; + } + + private: + scoped_ptr 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 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(*pos_) == 0xEF && + static_cast(*(pos_ + 1)) == 0xBB && + static_cast(*(pos_ + 2)) == 0xBF) { + NextNChars(3); + } + + // Parse the first and any nested tokens. + scoped_ptr 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_; + // Don't increment line_number_ twice for "\r\n". + if (!(*pos_ == '\n' && pos_ > start_pos_ && *(pos_ - 1) == '\r')) + ++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 == '*') { + char previous_char = '\0'; + // Block comment, read until end marker. + while (CanConsume(1)) { + next_char = *NextChar(); + if (previous_char == '*' && next_char == '/') { + // 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; + } + previous_char = next_char; + } + + // 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 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 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(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(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(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/base/json/json_parser.h b/base/json/json_parser.h new file mode 100644 index 0000000000..b4d0b1bf97 --- /dev/null +++ b/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 + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/json/json_reader.h" +#include "base/strings/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 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/base/json/json_parser_unittest.cc b/base/json/json_parser_unittest.cc new file mode 100644 index 0000000000..74e2026301 --- /dev/null +++ b/base/json/json_parser_unittest.cc @@ -0,0 +1,319 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/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 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 parser(NewTestParser(input)); + scoped_ptr 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 parser(NewTestParser(input)); + scoped_ptr 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 parser(NewTestParser(input)); + scoped_ptr 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 parser(NewTestParser(input)); + scoped_ptr 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 parser(NewTestParser(input)); + scoped_ptr 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 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); + + error_code = 0; + error_message = ""; + // Test line and column counting with "\r\n" line ending + const char big_json_crlf[] = + "[\r\n0,\r\n1,\r\n2,\r\n3,4,5,6 7,\r\n8,\r\n9\r\n]"; + // error here ----------------------^ + root.reset(JSONReader::ReadAndReturnError(big_json_crlf, 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 root( + JSONReader::ReadAndReturnError(kUtf8Data, JSON_PARSE_RFC, &error_code, + &error_message)); + EXPECT_TRUE(root.get()) << error_message; +} + +} // namespace internal +} // namespace base diff --git a/base/json/json_reader.cc b/base/json/json_reader.cc new file mode 100644 index 0000000000..593273ebd4 --- /dev/null +++ b/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/base/json/json_reader.h b/base/json/json_reader.h new file mode 100644 index 0000000000..e602846278 --- /dev/null +++ b/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 + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_piece.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 parser_; +}; + +} // namespace base + +#endif // BASE_JSON_JSON_READER_H_ diff --git a/base/json/json_reader_unittest.cc b/base/json/json_reader_unittest.cc new file mode 100644 index 0000000000..527cb97f2a --- /dev/null +++ b/base/json/json_reader_unittest.cc @@ -0,0 +1,656 @@ +// Copyright (c) 2012 The Chromium Authors. All 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/strings/string_piece.h" +#include "base/strings/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 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(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(root.get()); + EXPECT_EQ(3u, list->GetSize()); + root.reset(JSONReader().ReadToValue("/* comment **/42")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_INTEGER)); + EXPECT_TRUE(root->GetAsInteger(&int_val)); + EXPECT_EQ(42, int_val); + root.reset(JSONReader().ReadToValue( + "/* comment **/\n" + "// */ 43\n" + "44")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_INTEGER)); + EXPECT_TRUE(root->GetAsInteger(&int_val)); + EXPECT_EQ(44, int_val); + + // 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(root.get()); + EXPECT_EQ(3U, list->GetSize()); + + // Test with trailing comma. Should be parsed the same as above. + scoped_ptr 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(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(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(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(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(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(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(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(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_TEST_DATA, &path)); + path = path.AppendASCII("json"); + ASSERT_TRUE(base::PathExists(path)); + + std::string input; + ASSERT_TRUE(file_util::ReadFileToString( + path.Append(FILE_PATH_LITERAL("bom_feff.json")), &input)); + + JSONReader reader; + scoped_ptr 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) { + scoped_ptr dict_literal_0; + scoped_ptr dict_literal_1; + scoped_ptr dict_string_0; + scoped_ptr dict_string_1; + scoped_ptr list_value_0; + scoped_ptr list_value_1; + + { + scoped_ptr 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_literal_0)); + EXPECT_TRUE(dict->Remove("bar", &dict_literal_1)); + EXPECT_TRUE(dict->Remove("baz", &dict_string_0)); + EXPECT_TRUE(dict->Remove("moo", &dict_string_1)); + + ASSERT_EQ(2u, list->GetSize()); + EXPECT_TRUE(list->Remove(0, &list_value_0)); + EXPECT_TRUE(list->Remove(0, &list_value_1)); + } + + bool b = false; + double d = 0; + std::string s; + + EXPECT_TRUE(dict_literal_0->GetAsBoolean(&b)); + EXPECT_TRUE(b); + + EXPECT_TRUE(dict_literal_1->GetAsDouble(&d)); + EXPECT_EQ(3.14, d); + + EXPECT_TRUE(dict_string_0->GetAsString(&s)); + EXPECT_EQ("bat", s); + + EXPECT_TRUE(dict_string_1->GetAsString(&s)); + EXPECT_EQ("cow", s); + + EXPECT_TRUE(list_value_0->GetAsString(&s)); + EXPECT_EQ("a", s); + EXPECT_TRUE(list_value_1->GetAsString(&s)); + EXPECT_EQ("b", s); +} + +// 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/base/json/json_string_value_serializer.cc b/base/json/json_string_value_serializer.cc new file mode 100644 index 0000000000..7611fbeed6 --- /dev/null +++ b/base/json/json_string_value_serializer.cc @@ -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. + +#include "base/json/json_string_value_serializer.h" + +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "base/logging.h" + +using base::Value; + +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/base/json/json_string_value_serializer.h b/base/json/json_string_value_serializer.h new file mode 100644 index 0000000000..8aa3f95bee --- /dev/null +++ b/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 + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/files/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(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 base::Value& root) OVERRIDE; + + // Equivalent to Serialize(root) except binary values are omitted from the + // output. + bool SerializeAndOmitBinaryValues(const base::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 base::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 base::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/base/json/json_value_converter.h b/base/json/json_value_converter.h new file mode 100644 index 0000000000..cf3c74f0c7 --- /dev/null +++ b/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 +#include + +#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/strings/string16.h" +#include "base/strings/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* 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* 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 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( +// "your_enum", &Message::ye, &ConvertFunc); +// } +// }; + +namespace base { + +template +class JSONValueConverter; + +namespace internal { + +template +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 +class ValueConverter { + public: + virtual ~ValueConverter() {} + virtual bool Convert(const base::Value& value, FieldType* field) const = 0; +}; + +template +class FieldConverter : public FieldConverterBase { + public: + explicit FieldConverter(const std::string& path, + FieldType StructType::* field, + ValueConverter* converter) + : FieldConverterBase(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 > value_converter_; + DISALLOW_COPY_AND_ASSIGN(FieldConverter); +}; + +template +class BasicValueConverter; + +template <> +class BasicValueConverter : public ValueConverter { + 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 : public ValueConverter { + 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 : public ValueConverter { + 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 : public ValueConverter { + 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 : public ValueConverter { + public: + BasicValueConverter() {} + + virtual bool Convert(const base::Value& value, bool* field) const OVERRIDE { + return value.GetAsBoolean(field); + } + + private: + DISALLOW_COPY_AND_ASSIGN(BasicValueConverter); +}; + +template +class ValueFieldConverter : public ValueConverter { + 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 +class CustomFieldConverter : public ValueConverter { + 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 +class NestedValueConverter : public ValueConverter { + public: + NestedValueConverter() {} + + virtual bool Convert( + const base::Value& value, NestedType* field) const OVERRIDE { + return converter_.Convert(value, field); + } + + private: + JSONValueConverter converter_; + DISALLOW_COPY_AND_ASSIGN(NestedValueConverter); +}; + +template +class RepeatedValueConverter : public ValueConverter > { + public: + RepeatedValueConverter() {} + + virtual bool Convert( + const base::Value& value, ScopedVector* 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 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 basic_converter_; + DISALLOW_COPY_AND_ASSIGN(RepeatedValueConverter); +}; + +template +class RepeatedMessageConverter + : public ValueConverter > { + public: + RepeatedMessageConverter() {} + + virtual bool Convert(const base::Value& value, + ScopedVector* 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 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 converter_; + DISALLOW_COPY_AND_ASSIGN(RepeatedMessageConverter); +}; + +template +class RepeatedCustomValueConverter + : public ValueConverter > { + 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* 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 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 JSONValueConverter { + public: + JSONValueConverter() { + StructType::RegisterJSONConverter(this); + } + + void RegisterIntField(const std::string& field_name, + int StructType::* field) { + fields_.push_back(new internal::FieldConverter( + field_name, field, new internal::BasicValueConverter)); + } + + void RegisterStringField(const std::string& field_name, + std::string StructType::* field) { + fields_.push_back(new internal::FieldConverter( + field_name, field, new internal::BasicValueConverter)); + } + + void RegisterStringField(const std::string& field_name, + string16 StructType::* field) { + fields_.push_back(new internal::FieldConverter( + field_name, field, new internal::BasicValueConverter)); + } + + void RegisterBoolField(const std::string& field_name, + bool StructType::* field) { + fields_.push_back(new internal::FieldConverter( + field_name, field, new internal::BasicValueConverter)); + } + + void RegisterDoubleField(const std::string& field_name, + double StructType::* field) { + fields_.push_back(new internal::FieldConverter( + field_name, field, new internal::BasicValueConverter)); + } + + template + void RegisterNestedField( + const std::string& field_name, NestedType StructType::* field) { + fields_.push_back(new internal::FieldConverter( + field_name, + field, + new internal::NestedValueConverter)); + } + + template + void RegisterCustomField( + const std::string& field_name, + FieldType StructType::* field, + bool (*convert_func)(const StringPiece&, FieldType*)) { + fields_.push_back(new internal::FieldConverter( + field_name, + field, + new internal::CustomFieldConverter(convert_func))); + } + + template + void RegisterCustomValueField( + const std::string& field_name, + FieldType StructType::* field, + bool (*convert_func)(const base::Value*, FieldType*)) { + fields_.push_back(new internal::FieldConverter( + field_name, + field, + new internal::ValueFieldConverter(convert_func))); + } + + void RegisterRepeatedInt(const std::string& field_name, + ScopedVector StructType::* field) { + fields_.push_back( + new internal::FieldConverter >( + field_name, field, new internal::RepeatedValueConverter)); + } + + void RegisterRepeatedString(const std::string& field_name, + ScopedVector StructType::* field) { + fields_.push_back( + new internal::FieldConverter >( + field_name, + field, + new internal::RepeatedValueConverter)); + } + + void RegisterRepeatedString(const std::string& field_name, + ScopedVector StructType::* field) { + fields_.push_back( + new internal::FieldConverter >( + field_name, + field, + new internal::RepeatedValueConverter)); + } + + void RegisterRepeatedDouble(const std::string& field_name, + ScopedVector StructType::* field) { + fields_.push_back( + new internal::FieldConverter >( + field_name, field, new internal::RepeatedValueConverter)); + } + + void RegisterRepeatedBool(const std::string& field_name, + ScopedVector StructType::* field) { + fields_.push_back( + new internal::FieldConverter >( + field_name, field, new internal::RepeatedValueConverter)); + } + + template + void RegisterRepeatedCustomValue( + const std::string& field_name, + ScopedVector StructType::* field, + bool (*convert_func)(const base::Value*, NestedType*)) { + fields_.push_back( + new internal::FieldConverter >( + field_name, + field, + new internal::RepeatedCustomValueConverter( + convert_func))); + } + + template + void RegisterRepeatedMessage(const std::string& field_name, + ScopedVector StructType::* field) { + fields_.push_back( + new internal::FieldConverter >( + field_name, + field, + new internal::RepeatedMessageConverter)); + } + + 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* 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 > fields_; + + DISALLOW_COPY_AND_ASSIGN(JSONValueConverter); +}; + +} // namespace base + +#endif // BASE_JSON_JSON_VALUE_CONVERTER_H_ diff --git a/base/json/json_value_converter_unittest.cc b/base/json/json_value_converter_unittest.cc new file mode 100644 index 0000000000..7d48f39a30 --- /dev/null +++ b/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 +#include + +#include "base/json/json_reader.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/strings/string_piece.h" +#include "base/values.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 ints; + ScopedVector 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* converter) { + converter->RegisterIntField("foo", &SimpleMessage::foo); + converter->RegisterStringField("bar", &SimpleMessage::bar); + converter->RegisterBoolField("baz", &SimpleMessage::baz); + converter->RegisterCustomField( + "simple_enum", &SimpleMessage::simple_enum, &ParseSimpleEnum); + converter->RegisterRepeatedInt("ints", &SimpleMessage::ints); + converter->RegisterCustomValueField("bstruct", + &SimpleMessage::bstruct, + &HasFieldPresent); + converter->RegisterRepeatedCustomValue( + "string_values", + &SimpleMessage::string_values, + &GetValueString); + } +}; + +// For nested messages. +struct NestedMessage { + double foo; + SimpleMessage child; + ScopedVector children; + + NestedMessage() : foo(0) {} + + static void RegisterJSONConverter( + base::JSONValueConverter* 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(base::JSONReader::Read(normal_data)); + SimpleMessage message; + base::JSONValueConverter 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(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(base::JSONReader::Read(normal_data)); + NestedMessage message; + base::JSONValueConverter 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(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(base::JSONReader::Read(normal_data)); + SimpleMessage message; + base::JSONValueConverter 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(base::JSONReader::Read(normal_data)); + SimpleMessage message; + base::JSONValueConverter 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(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(base::JSONReader::Read(normal_data)); + SimpleMessage message; + base::JSONValueConverter 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(base::JSONReader::Read(normal_data)); + SimpleMessage message; + base::JSONValueConverter converter; + EXPECT_FALSE(converter.Convert(*value.get(), &message)); + // No check the values as mentioned above. +} + +} // namespace base diff --git a/base/json/json_value_serializer_unittest.cc b/base/json/json_value_serializer_unittest.cc new file mode 100644 index 0000000000..314cd07a5b --- /dev/null +++ b/base/json/json_value_serializer_unittest.cc @@ -0,0 +1,471 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#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/json/json_writer.h" +#include "base/memory/scoped_ptr.h" +#include "base/path_service.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.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); +} + +void ValidateJsonList(const std::string& json) { + scoped_ptr root(JSONReader::Read(json)); + ASSERT_TRUE(root.get() && root->IsType(Value::TYPE_LIST)); + ListValue* list = static_cast(root.get()); + ASSERT_EQ(1U, list->GetSize()); + Value* elt = NULL; + ASSERT_TRUE(list->Get(0, &elt)); + int value = 0; + ASSERT_TRUE(elt && elt->GetAsInteger(&value)); + ASSERT_EQ(1, value); +} + +// 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( + 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( + 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(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( + 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(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( + 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); +} + +TEST(JSONValueSerializerTest, Roundtrip) { + const std::string original_serialization = + "{\"bool\":true,\"double\":3.14,\"int\":42,\"list\":[1,2],\"null\":null}"; + JSONStringValueSerializer serializer(original_serialization); + scoped_ptr root(serializer.Deserialize(NULL, NULL)); + ASSERT_TRUE(root.get()); + ASSERT_TRUE(root->IsType(Value::TYPE_DICTIONARY)); + + DictionaryValue* root_dict = static_cast(root.get()); + + Value* null_value = NULL; + ASSERT_TRUE(root_dict->Get("null", &null_value)); + ASSERT_TRUE(null_value); + ASSERT_TRUE(null_value->IsType(Value::TYPE_NULL)); + + bool bool_value = false; + ASSERT_TRUE(root_dict->GetBoolean("bool", &bool_value)); + ASSERT_TRUE(bool_value); + + int int_value = 0; + ASSERT_TRUE(root_dict->GetInteger("int", &int_value)); + ASSERT_EQ(42, int_value); + + double double_value = 0.0; + ASSERT_TRUE(root_dict->GetDouble("double", &double_value)); + ASSERT_DOUBLE_EQ(3.14, double_value); + + // We shouldn't be able to write using this serializer, since it was + // initialized with a const string. + ASSERT_FALSE(serializer.Serialize(*root_dict)); + + std::string test_serialization; + JSONStringValueSerializer mutable_serializer(&test_serialization); + ASSERT_TRUE(mutable_serializer.Serialize(*root_dict)); + ASSERT_EQ(original_serialization, test_serialization); + + mutable_serializer.set_pretty_print(true); + ASSERT_TRUE(mutable_serializer.Serialize(*root_dict)); + // JSON output 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 + const std::string pretty_serialization = + "{" JSON_NEWLINE + " \"bool\": true," JSON_NEWLINE + " \"double\": 3.14," JSON_NEWLINE + " \"int\": 42," JSON_NEWLINE + " \"list\": [ 1, 2 ]," JSON_NEWLINE + " \"null\": null" JSON_NEWLINE + "}" JSON_NEWLINE; +#undef JSON_NEWLINE + ASSERT_EQ(pretty_serialization, test_serialization); +} + +TEST(JSONValueSerializerTest, StringEscape) { + string16 all_chars; + for (int i = 1; i < 256; ++i) { + all_chars += static_cast(i); + } + // Generated in in Firefox using the following js (with an extra backslash for + // double quote): + // var s = ''; + // for (var i = 1; i < 256; ++i) { s += String.fromCharCode(i); } + // uneval(s).replace(/\\/g, "\\\\"); + std::string all_chars_expected = + "\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000B\\f\\r" + "\\u000E\\u000F\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017" + "\\u0018\\u0019\\u001A\\u001B\\u001C\\u001D\\u001E\\u001F !\\\"" + "#$%&'()*+,-./0123456789:;\\u003C=\\u003E?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\" + "\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\\u007F\\u0080\\u0081\\u0082\\u0083" + "\\u0084\\u0085\\u0086\\u0087\\u0088\\u0089\\u008A\\u008B\\u008C\\u008D" + "\\u008E\\u008F\\u0090\\u0091\\u0092\\u0093\\u0094\\u0095\\u0096\\u0097" + "\\u0098\\u0099\\u009A\\u009B\\u009C\\u009D\\u009E\\u009F\\u00A0\\u00A1" + "\\u00A2\\u00A3\\u00A4\\u00A5\\u00A6\\u00A7\\u00A8\\u00A9\\u00AA\\u00AB" + "\\u00AC\\u00AD\\u00AE\\u00AF\\u00B0\\u00B1\\u00B2\\u00B3\\u00B4\\u00B5" + "\\u00B6\\u00B7\\u00B8\\u00B9\\u00BA\\u00BB\\u00BC\\u00BD\\u00BE\\u00BF" + "\\u00C0\\u00C1\\u00C2\\u00C3\\u00C4\\u00C5\\u00C6\\u00C7\\u00C8\\u00C9" + "\\u00CA\\u00CB\\u00CC\\u00CD\\u00CE\\u00CF\\u00D0\\u00D1\\u00D2\\u00D3" + "\\u00D4\\u00D5\\u00D6\\u00D7\\u00D8\\u00D9\\u00DA\\u00DB\\u00DC\\u00DD" + "\\u00DE\\u00DF\\u00E0\\u00E1\\u00E2\\u00E3\\u00E4\\u00E5\\u00E6\\u00E7" + "\\u00E8\\u00E9\\u00EA\\u00EB\\u00EC\\u00ED\\u00EE\\u00EF\\u00F0\\u00F1" + "\\u00F2\\u00F3\\u00F4\\u00F5\\u00F6\\u00F7\\u00F8\\u00F9\\u00FA\\u00FB" + "\\u00FC\\u00FD\\u00FE\\u00FF"; + + std::string expected_output = "{\"all_chars\":\"" + all_chars_expected + + "\"}"; + // Test JSONWriter interface + std::string output_js; + DictionaryValue valueRoot; + valueRoot.SetString("all_chars", all_chars); + JSONWriter::Write(&valueRoot, &output_js); + ASSERT_EQ(expected_output, output_js); + + // Test JSONValueSerializer interface (uses JSONWriter). + JSONStringValueSerializer serializer(&output_js); + ASSERT_TRUE(serializer.Serialize(valueRoot)); + ASSERT_EQ(expected_output, output_js); +} + +TEST(JSONValueSerializerTest, UnicodeStrings) { + // unicode string json -> escaped ascii text + DictionaryValue root; + string16 test(WideToUTF16(L"\x7F51\x9875")); + root.SetString("web", test); + + std::string expected = "{\"web\":\"\\u7F51\\u9875\"}"; + + std::string actual; + JSONStringValueSerializer serializer(&actual); + ASSERT_TRUE(serializer.Serialize(root)); + ASSERT_EQ(expected, actual); + + // escaped ascii text -> json + JSONStringValueSerializer deserializer(expected); + scoped_ptr deserial_root(deserializer.Deserialize(NULL, NULL)); + ASSERT_TRUE(deserial_root.get()); + DictionaryValue* dict_root = + static_cast(deserial_root.get()); + string16 web_value; + ASSERT_TRUE(dict_root->GetString("web", &web_value)); + ASSERT_EQ(test, web_value); +} + +TEST(JSONValueSerializerTest, HexStrings) { + // hex string json -> escaped ascii text + DictionaryValue root; + string16 test(WideToUTF16(L"\x01\x02")); + root.SetString("test", test); + + std::string expected = "{\"test\":\"\\u0001\\u0002\"}"; + + std::string actual; + JSONStringValueSerializer serializer(&actual); + ASSERT_TRUE(serializer.Serialize(root)); + ASSERT_EQ(expected, actual); + + // escaped ascii text -> json + JSONStringValueSerializer deserializer(expected); + scoped_ptr deserial_root(deserializer.Deserialize(NULL, NULL)); + ASSERT_TRUE(deserial_root.get()); + DictionaryValue* dict_root = + static_cast(deserial_root.get()); + string16 test_value; + ASSERT_TRUE(dict_root->GetString("test", &test_value)); + ASSERT_EQ(test, test_value); + + // Test converting escaped regular chars + std::string escaped_chars = "{\"test\":\"\\u0067\\u006f\"}"; + JSONStringValueSerializer deserializer2(escaped_chars); + deserial_root.reset(deserializer2.Deserialize(NULL, NULL)); + ASSERT_TRUE(deserial_root.get()); + dict_root = static_cast(deserial_root.get()); + ASSERT_TRUE(dict_root->GetString("test", &test_value)); + ASSERT_EQ(ASCIIToUTF16("go"), test_value); +} + +TEST(JSONValueSerializerTest, AllowTrailingComma) { + scoped_ptr root; + scoped_ptr root_expected; + std::string test_with_commas("{\"key\": [true,],}"); + std::string test_no_commas("{\"key\": [true]}"); + + JSONStringValueSerializer serializer(test_with_commas); + serializer.set_allow_trailing_comma(true); + JSONStringValueSerializer serializer_expected(test_no_commas); + root.reset(serializer.Deserialize(NULL, NULL)); + ASSERT_TRUE(root.get()); + root_expected.reset(serializer_expected.Deserialize(NULL, NULL)); + ASSERT_TRUE(root_expected.get()); + ASSERT_TRUE(root->Equals(root_expected.get())); +} + +TEST(JSONValueSerializerTest, JSONReaderComments) { + ValidateJsonList("[ // 2, 3, ignore me ] \n1 ]"); + ValidateJsonList("[ /* 2, \n3, ignore me ]*/ \n1 ]"); + ValidateJsonList("//header\n[ // 2, \n// 3, \n1 ]// footer"); + ValidateJsonList("/*\n[ // 2, \n// 3, \n1 ]*/[1]"); + ValidateJsonList("[ 1 /* one */ ] /* end */"); + ValidateJsonList("[ 1 //// ,2\r\n ]"); + + scoped_ptr root; + + // It's ok to have a comment in a string. + root.reset(JSONReader::Read("[\"// ok\\n /* foo */ \"]")); + ASSERT_TRUE(root.get() && root->IsType(Value::TYPE_LIST)); + ListValue* list = static_cast(root.get()); + ASSERT_EQ(1U, list->GetSize()); + Value* elt = NULL; + ASSERT_TRUE(list->Get(0, &elt)); + std::string value; + ASSERT_TRUE(elt && elt->GetAsString(&value)); + ASSERT_EQ("// ok\n /* foo */ ", value); + + // You can't nest comments. + root.reset(JSONReader::Read("/* /* inner */ outer */ [ 1 ]")); + ASSERT_FALSE(root.get()); + + // Not a open comment token. + root.reset(JSONReader::Read("/ * * / [1]")); + ASSERT_FALSE(root.get()); +} + +class JSONFileValueSerializerTest : public testing::Test { + protected: + virtual void SetUp() OVERRIDE { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + } + + base::ScopedTempDir temp_dir_; +}; + +TEST_F(JSONFileValueSerializerTest, Roundtrip) { + base::FilePath original_file_path; + ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &original_file_path)); + original_file_path = + original_file_path.Append(FILE_PATH_LITERAL("serializer_test.json")); + + ASSERT_TRUE(PathExists(original_file_path)); + + JSONFileValueSerializer deserializer(original_file_path); + scoped_ptr root; + root.reset(deserializer.Deserialize(NULL, NULL)); + + ASSERT_TRUE(root.get()); + ASSERT_TRUE(root->IsType(Value::TYPE_DICTIONARY)); + + DictionaryValue* root_dict = static_cast(root.get()); + + Value* null_value = NULL; + ASSERT_TRUE(root_dict->Get("null", &null_value)); + ASSERT_TRUE(null_value); + ASSERT_TRUE(null_value->IsType(Value::TYPE_NULL)); + + bool bool_value = false; + ASSERT_TRUE(root_dict->GetBoolean("bool", &bool_value)); + ASSERT_TRUE(bool_value); + + int int_value = 0; + ASSERT_TRUE(root_dict->GetInteger("int", &int_value)); + ASSERT_EQ(42, int_value); + + std::string string_value; + ASSERT_TRUE(root_dict->GetString("string", &string_value)); + ASSERT_EQ("hello", string_value); + + // Now try writing. + const base::FilePath written_file_path = + temp_dir_.path().Append(FILE_PATH_LITERAL("test_output.js")); + + ASSERT_FALSE(PathExists(written_file_path)); + JSONFileValueSerializer serializer(written_file_path); + ASSERT_TRUE(serializer.Serialize(*root)); + ASSERT_TRUE(PathExists(written_file_path)); + + // Now compare file contents. + EXPECT_TRUE(TextContentsEqual(original_file_path, written_file_path)); + EXPECT_TRUE(base::DeleteFile(written_file_path, false)); +} + +TEST_F(JSONFileValueSerializerTest, RoundtripNested) { + base::FilePath original_file_path; + ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &original_file_path)); + original_file_path = original_file_path.Append( + FILE_PATH_LITERAL("serializer_nested_test.json")); + + ASSERT_TRUE(PathExists(original_file_path)); + + JSONFileValueSerializer deserializer(original_file_path); + scoped_ptr root; + root.reset(deserializer.Deserialize(NULL, NULL)); + ASSERT_TRUE(root.get()); + + // Now try writing. + base::FilePath written_file_path = temp_dir_.path().Append( + FILE_PATH_LITERAL("test_output.json")); + + ASSERT_FALSE(PathExists(written_file_path)); + JSONFileValueSerializer serializer(written_file_path); + ASSERT_TRUE(serializer.Serialize(*root)); + ASSERT_TRUE(PathExists(written_file_path)); + + // Now compare file contents. + EXPECT_TRUE(TextContentsEqual(original_file_path, written_file_path)); + EXPECT_TRUE(base::DeleteFile(written_file_path, false)); +} + +TEST_F(JSONFileValueSerializerTest, NoWhitespace) { + base::FilePath source_file_path; + ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &source_file_path)); + source_file_path = source_file_path.Append( + FILE_PATH_LITERAL("serializer_test_nowhitespace.json")); + ASSERT_TRUE(PathExists(source_file_path)); + JSONFileValueSerializer serializer(source_file_path); + scoped_ptr root; + root.reset(serializer.Deserialize(NULL, NULL)); + ASSERT_TRUE(root.get()); +} + +} // namespace + +} // namespace base diff --git a/base/json/json_writer.cc b/base/json/json_writer.cc new file mode 100644 index 0000000000..6a9cc6aa47 --- /dev/null +++ b/base/json/json_writer.cc @@ -0,0 +1,233 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/json/json_writer.h" + +#include + +#include "base/json/string_escape.h" +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.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(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(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(node); + bool first_entry = true; + for (DictionaryValue::Iterator itr(*dict); !itr.IsAtEnd(); + itr.Advance(), first_entry = false) { + if (omit_binary_values_ && + itr.value().GetType() == Value::TYPE_BINARY) { + continue; + } + + if (!first_entry) { + json_string_->append(","); + if (pretty_print_) + json_string_->append(kPrettyPrintLineEnding); + } + + if (pretty_print_) + IndentLine(depth + 1); + AppendQuotedString(itr.key()); + if (pretty_print_) { + json_string_->append(": "); + } else { + json_string_->append(":"); + } + BuildJSONString(&itr.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/base/json/json_writer.h b/base/json/json_writer.h new file mode 100644 index 0000000000..94052c8034 --- /dev/null +++ b/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 + +#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/base/json/json_writer_unittest.cc b/base/json/json_writer_unittest.cc new file mode 100644 index 0000000000..7ddd7b462d --- /dev/null +++ b/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/base/json/string_escape.cc b/base/json/string_escape.cc new file mode 100644 index 0000000000..eaa73ce0a0 --- /dev/null +++ b/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 + +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.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 +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 +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::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(c); + base::StringAppendF(dst, "\\u%04X", as_uint); + } else { + unsigned char ascii = static_cast(*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/base/json/string_escape.h b/base/json/string_escape.h new file mode 100644 index 0000000000..5efc350dea --- /dev/null +++ b/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 + +#include "base/base_export.h" +#include "base/strings/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/base/json/string_escape_unittest.cc b/base/json/string_escape_unittest.cc new file mode 100644 index 0000000000..8952ee76d2 --- /dev/null +++ b/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/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace { + +const struct json_narrow_test_data { + const char* to_escape; + const char* escaped; +} json_narrow_cases[] = { + {"\b\001aZ\"\\wee", "\\b\\u0001aZ\\\"\\\\wee"}, + {"a\b\f\n\r\t\v\1\\.\"z", + "a\\b\\f\\n\\r\\t\\u000B\\u0001\\\\.\\\"z"}, + {"b\x0f\x7f\xf0\xff!", "b\\u000F\\u007F\\u00F0\\u00FF!"}, + {"c<>d", "c\\u003C\\u003Ed"}, +}; + +} // namespace + +TEST(StringEscapeTest, JsonDoubleQuoteNarrow) { + for (size_t i = 0; i < arraysize(json_narrow_cases); ++i) { + 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/base/lazy_instance.cc b/base/lazy_instance.cc new file mode 100644 index 0000000000..a81cb8c2d0 --- /dev/null +++ b/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/base/lazy_instance.h b/base/lazy_instance.h new file mode 100644 index 0000000000..3935780a55 --- /dev/null +++ b/base/lazy_instance.h @@ -0,0 +1,212 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// The LazyInstance 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 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 // For placement new. + +#include "base/atomicops.h" +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/debug/leak_annotations.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 +struct DefaultLazyInstanceTraits { + static const bool kRegisterOnExit = true; + static const bool kAllowedToAccessOnNonjoinableThread = false; + + static Type* New(void* instance) { + DCHECK_EQ(reinterpret_cast(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 " + " 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::Leaky for a less-verbose call-site typedef; e.g.: +// base::LazyInstance::Leaky my_leaky_lazy_instance; +// instead of: +// base::LazyInstance > +// 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 +struct LeakyLazyInstanceTraits { + static const bool kRegisterOnExit = false; + static const bool kAllowedToAccessOnNonjoinableThread = true; + + static Type* New(void* instance) { + ANNOTATE_SCOPED_MEMORY_LEAK; + return DefaultLazyInstanceTraits::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 > +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 > 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( + 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(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 private_buf_; + + private: + Type* instance() { + return reinterpret_cast(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* me = + reinterpret_cast*>(lazy_instance); + Traits::Delete(me->instance()); + subtle::NoBarrier_Store(&me->private_instance_, 0); + } +}; + +} // namespace base + +#endif // BASE_LAZY_INSTANCE_H_ diff --git a/base/lazy_instance_unittest.cc b/base/lazy_instance_unittest.cc new file mode 100644 index 0000000000..e25366e228 --- /dev/null +++ b/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* lazy) + : lazy_(lazy) {} + + virtual void Run() OVERRIDE { + EXPECT_EQ(12, lazy_->Get().some_int()); + EXPECT_EQ(12, lazy_->Pointer()->some_int()); + } + + private: + base::LazyInstance* lazy_; +}; + +} // namespace + +static base::LazyInstance 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 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 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::Leaky + test = LAZY_INSTANCE_INITIALIZER; + test.Get().SetDeletedPtr(&deleted2); + } + EXPECT_FALSE(deleted2); +} + +namespace { + +template +class AlignedData { + public: + AlignedData() {} + ~AlignedData() {} + base::AlignedMemory data_; +}; + +} // anonymous namespace + +#define EXPECT_ALIGNED(ptr, align) \ + EXPECT_EQ(0u, reinterpret_cast(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 > align4 = LAZY_INSTANCE_INITIALIZER; + static LazyInstance > align32 = LAZY_INSTANCE_INITIALIZER; + static LazyInstance > align4096 = LAZY_INSTANCE_INITIALIZER; + + EXPECT_ALIGNED(align4.Pointer(), 4); + EXPECT_ALIGNED(align32.Pointer(), 32); + EXPECT_ALIGNED(align4096.Pointer(), 4096); +} diff --git a/base/linux_util.cc b/base/linux_util.cc new file mode 100644 index 0000000000..5f9ecd43ab --- /dev/null +++ b/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 +#include +#include +#include +#include +#include +#include + +#include + +#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/launch.h" +#include "base/strings/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::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 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: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 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::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(sizeof(buf))) { + continue; + } + + ino_t fd_inode = static_cast(-1); + 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 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_ptr syscall_data(new char[expected_data.length()]); + for (std::vector::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/base/linux_util.h b/base/linux_util.h new file mode 100644 index 0000000000..b9ba56dfc9 --- /dev/null +++ b/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 +#include + +#include + +#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/base/location.cc b/base/location.cc new file mode 100644 index 0000000000..b5da027ee8 --- /dev/null +++ b/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 , but that breaks the VS2005 build. +extern "C" { + void* _ReturnAddress(); +} +#endif + +#include "base/location.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/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) + return __builtin_extract_return_addr(__builtin_return_address(0)); +#endif // COMPILER_GCC + + return NULL; +} + +} // namespace tracked_objects diff --git a/base/location.h b/base/location.h new file mode 100644 index 0000000000..05a4f66109 --- /dev/null +++ b/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 + +#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/base/logging.cc b/base/logging.cc new file mode 100644 index 0000000000..e836092e76 --- /dev/null +++ b/base/logging.cc @@ -0,0 +1,866 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/logging.h" + +#if defined(OS_WIN) +#include +#include +typedef HANDLE FileHandle; +typedef HANDLE MutexHandle; +// Windows warns on using write(). It prefers _write(). +#define write(fd, buf, count) _write(fd, buf, static_cast(count)) +// Windows doesn't define STDERR_FILENO. Define it here. +#define STDERR_FILENO 2 +#elif defined(OS_MACOSX) +#include +#include +#include +#elif defined(OS_POSIX) +#if defined(OS_NACL) +#include // timespec doesn't seem to be in +#else +#include +#endif +#include +#endif + +#if defined(OS_POSIX) +#include +#include +#include +#include +#include +#include +#define MAX_PATH PATH_MAX +typedef FILE* FileHandle; +typedef pthread_mutex_t* MutexHandle; +#endif + +#include +#include +#include +#include +#include + +#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/strings/string_piece.h" +#include "base/strings/utf_string_conversions.h" +#include "base/synchronization/lock_impl.h" +#include "base/threading/platform_thread.h" +#include "base/vlog.h" +#if defined(OS_POSIX) +#include "base/safe_strerror_posix.h" +#endif + +#if defined(OS_ANDROID) +#include +#endif + +namespace logging { + +DcheckState g_dcheck_state = DISABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS; + +DcheckState get_dcheck_state() { + return g_dcheck_state; +} + +void set_dcheck_state(DcheckState state) { + g_dcheck_state = state; +} + +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; + +LoggingDestination logging_destination = LOG_DEFAULT; + +// 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(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_POSIX) + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + + uint64 absolute_micro = + static_cast(ts.tv_sec) * 1000000 + + static_cast(ts.tv_nsec) / 1000; + + return absolute_micro; +#endif +} + +void DeleteFilePath(const PathString& log_name) { +#if defined(OS_WIN) + DeleteFile(log_name.c_str()); +#elif defined (OS_NACL) + // Do nothing; unlink() isn't supported on NaCl. +#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_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); +#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); +#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; +#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; +#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 ((logging_destination & LOG_TO_FILE) != 0) { +#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; +#endif + } + + return true; +} + +void CloseFile(FileHandle log) { +#if defined(OS_WIN) + CloseHandle(log); +#else + fclose(log); +#endif +} + +void CloseLogFileUnlocked() { + if (!log_file) + return; + + CloseFile(log_file); + log_file = NULL; +} + +} // namespace + +LoggingSettings::LoggingSettings() + : logging_dest(LOG_DEFAULT), + log_file(NULL), + lock_log(LOCK_LOG_FILE), + delete_old(APPEND_TO_OLD_LOG_FILE), + dcheck_state(DISABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS) {} + +bool BaseInitLoggingImpl(const LoggingSettings& settings) { +#if defined(OS_NACL) + // Can log only to the system debug log. + CHECK_EQ(settings.logging_dest & ~LOG_TO_SYSTEM_DEBUG_LOG, 0); +#endif + g_dcheck_state = settings.dcheck_state; + 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); + } + + logging_destination = settings.logging_dest; + + // ignore file options unless logging to file is set. + if ((logging_destination & LOG_TO_FILE) == 0) + return true; + + LoggingLock::Init(settings.lock_log, settings.log_file); + LoggingLock logging_lock; + + // Calling InitLogging twice or after some log call has already opened the + // default log file will re-initialize to the new options. + CloseLogFileUnlocked(); + + if (!log_file_name) + log_file_name = new PathString(); + *log_file_name = settings.log_file; + if (settings.delete_old == DELETE_OLD_LOG_FILE) + DeleteFilePath(*log_file_name); + + return InitializeLogFileHandle(); +} + +void SetMinLogLevel(int level) { + min_log_level = std::min(LOG_ERROR_REPORT, level); +} + +int GetMinLogLevel() { + return min_log_level; +} + +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( + const int&, const int&, const char* names); +template std::string* MakeCheckOpString( + const unsigned long&, const unsigned long&, const char* names); +template std::string* MakeCheckOpString( + const unsigned long&, const unsigned int&, const char* names); +template std::string* MakeCheckOpString( + const unsigned int&, const unsigned long&, const char* names); +template std::string* MakeCheckOpString( + 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() { +#if !defined(NDEBUG) && !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_TO_SYSTEM_DEBUG_LOG) != 0) { +#if defined(OS_WIN) + OutputDebugStringA(str_newline.c_str()); +#elif defined(OS_ANDROID) + android_LogPriority priority = + (severity_ < 0) ? ANDROID_LOG_VERBOSE : 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 + fprintf(stderr, "%s", str_newline.c_str()); + fflush(stderr); + } 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); + } + + // write to log file + if ((logging_destination & LOG_TO_FILE) != 0) { + // 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); + LoggingLock logging_lock; + if (InitializeLogFileHandle()) { +#if defined(OS_WIN) + SetFilePointer(log_file, 0, 0, SEEK_END); + DWORD num_written; + WriteFile(log_file, + static_cast(str_newline.c_str()), + static_cast(str_newline.length()), + &num_written, + NULL); +#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) { + 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; + 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; +#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(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_); +} +#endif // OS_WIN + +void CloseLogFile() { + LoggingLock logging_lock; + CloseLogFileUnlocked(); +} + +void RawLog(int level, const char* message) { + if (level >= min_log_level) { + 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); + } + } + + if (level == LOG_FATAL) + base::debug::BreakDebugger(); +} + +// This was defined at the beginning of this file. +#undef write + +#if defined(OS_WIN) +std::wstring GetLogFileFullPath() { + if (log_file_name) + return *log_file_name; + return std::wstring(); +} +#endif + +} // namespace logging + +std::ostream& operator<<(std::ostream& out, const wchar_t* wstr) { + return out << WideToUTF8(std::wstring(wstr)); +} diff --git a/base/logging.h b/base/logging.h new file mode 100644 index 0000000000..859f2602fc --- /dev/null +++ b/base/logging.h @@ -0,0 +1,1022 @@ +// Copyright (c) 2012 The Chromium Authors. All 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 +#include +#include +#include + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/debug/debugger.h" +#include "build/build_config.h" + +// +// 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(). 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 { + +// 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 + +// Where to record logging output? A flat file and/or system debug log +// via OutputDebugString. +enum LoggingDestination { + LOG_NONE = 0, + LOG_TO_FILE = 1 << 0, + LOG_TO_SYSTEM_DEBUG_LOG = 1 << 1, + + LOG_TO_ALL = LOG_TO_FILE | LOG_TO_SYSTEM_DEBUG_LOG, + + // 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) + LOG_DEFAULT = LOG_TO_FILE, +#elif defined(OS_POSIX) + LOG_DEFAULT = LOG_TO_SYSTEM_DEBUG_LOG, +#endif +}; + +// Indicates that the log file should be locked when being written to. +// Unless there is only one single-threaded process that is logging to +// the log file, 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 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 +}; + +struct BASE_EXPORT LoggingSettings { + // The defaults values are: + // + // logging_dest: LOG_DEFAULT + // log_file: NULL + // lock_log: LOCK_LOG_FILE + // delete_old: APPEND_TO_OLD_LOG_FILE + // dcheck_state: DISABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS + LoggingSettings(); + + LoggingDestination logging_dest; + + // The three settings below have an effect only when LOG_TO_FILE is + // set in |logging_dest|. + const PathChar* log_file; + LogLockingState lock_log; + OldFileDeletionState delete_old; + + DcheckState dcheck_state; +}; + +// 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 NDEBUG +#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 LoggingSettings& settings); + +// 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 LoggingSettings& settings) { + return BaseInitLoggingImpl(settings); +} + +// 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 +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 +#ifdef NDEBUG +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) + +#if defined(OS_WIN) +// 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; +#endif + +// 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() +#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) +#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)) + +#if !defined(NDEBUG) +// Debug builds always include DCHECK and DLOG. +#undef LOGGING_IS_OFFICIAL_BUILD +#define LOGGING_IS_OFFICIAL_BUILD 0 +#elif defined(OFFICIAL_BUILD) +// Official release builds always disable and remove DCHECK and DLOG. +#undef LOGGING_IS_OFFICIAL_BUILD +#define LOGGING_IS_OFFICIAL_BUILD 1 +#elif !defined(LOGGING_IS_OFFICIAL_BUILD) +// Unless otherwise specified, unofficial release builds include +// DCHECK and DLOG. +#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 + +#define CHECK(condition) \ + LAZY_STREAM(LOG_STREAM(FATAL), !(condition)) \ + << "Check failed: " #condition ". " + +#define PCHECK(condition) \ + LAZY_STREAM(PLOG_STREAM(FATAL), !(condition)) \ + << "Check failed: " #condition ". " + +// 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 +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) +// Commonly used instantiations of MakeCheckOpString<>. Explicitly instantiated +// in logging.cc. +extern template BASE_EXPORT std::string* MakeCheckOpString( + const int&, const int&, const char* names); +extern template BASE_EXPORT +std::string* MakeCheckOpString( + const unsigned long&, const unsigned long&, const char* names); +extern template BASE_EXPORT +std::string* MakeCheckOpString( + const unsigned long&, const unsigned int&, const char* names); +extern template BASE_EXPORT +std::string* MakeCheckOpString( + const unsigned int&, const unsigned long&, const char* names); +extern template BASE_EXPORT +std::string* MakeCheckOpString( + 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 \ + 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); \ + } +DEFINE_CHECK_OP_IMPL(EQ, ==) +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 +// In order to have optimized code for official builds, remove DLOGs and +// DCHECKs. +#define ENABLE_DLOG 0 +#define ENABLE_DCHECK 0 + +#elif defined(NDEBUG) +// 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) + +BASE_EXPORT DcheckState get_dcheck_state(); +BASE_EXPORT void set_dcheck_state(DcheckState 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::get_dcheck_state() == \ + ::logging::ENABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS) && \ + LOG_IS_ON(DCHECK)) + +#endif // defined(DCHECK_ALWAYS_ON) + +#else // defined(NDEBUG) + +// 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) + +#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. + +#define DCHECK(condition) \ + LAZY_STREAM(LOG_STREAM(DCHECK), DCHECK_IS_ON() && !(condition)) \ + << "Check failed: " #condition ". " + +#define DPCHECK(condition) \ + LAZY_STREAM(PLOG_STREAM(DCHECK), DCHECK_IS_ON() && !(condition)) \ + << "Check failed: " #condition ". " + +// 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; +#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); +}; +#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) + +#if defined(OS_WIN) +// Returns the default log file path. +BASE_EXPORT std::wstring GetLogFileFullPath(); +#endif + +} // 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(); +} + +// 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) +#define NOTIMPLEMENTED_POLICY 0 +#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__ +#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 bool logged_once = false;\ + LOG_IF(ERROR, !logged_once) << NOTIMPLEMENTED_MSG;\ + logged_once = true;\ +} while(0);\ +EAT_STREAM_PARAMETERS +#endif + +#endif // BASE_LOGGING_H_ diff --git a/base/logging_unittest.cc b/base/logging_unittest.cc new file mode 100644 index 0000000000..bff10ebdf8 --- /dev/null +++ b/base/logging_unittest.cc @@ -0,0 +1,261 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/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_TRUE(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) + 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_TRUE(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_TRUE(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_TRUE(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) + 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. + set_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(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) + // Unofficial release build. + set_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. + set_dcheck_state(ENABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS); + SetLogAssertHandler(&LogSink); + EXPECT_TRUE(DCHECK_IS_ON()); + EXPECT_FALSE(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) + + 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); +} + +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/base/logging_win.cc b/base/logging_win.cc new file mode 100644 index 0000000000..a714665788 --- /dev/null +++ b/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 // 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 >::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/base/logging_win.h b/base/logging_win.h new file mode 100644 index 0000000000..38508a3246 --- /dev/null +++ b/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 + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/win/event_trace_provider.h" +#include "base/logging.h" + +template +struct StaticMemorySingletonTraits; + +namespace logging { + +// Event ID for the log messages we generate. +EXTERN_C BASE_EXPORT 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; + DISALLOW_COPY_AND_ASSIGN(LogEventProvider); +}; + +} // namespace logging + +#endif // BASE_LOGGING_WIN_H_ diff --git a/base/mac/OWNERS b/base/mac/OWNERS new file mode 100644 index 0000000000..a3fc32fcee --- /dev/null +++ b/base/mac/OWNERS @@ -0,0 +1,2 @@ +mark@chromium.org +thakis@chromium.org diff --git a/base/mac/authorization_util.h b/base/mac/authorization_util.h new file mode 100644 index 0000000000..b34348d175 --- /dev/null +++ b/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 +#include +#include +#include + +#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/base/mac/authorization_util.mm b/base/mac/authorization_util.mm new file mode 100644 index 0000000000..c292589620 --- /dev/null +++ b/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 +#include + +#include + +#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/strings/string_number_conversions.h" +#include "base/strings/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 + // 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/base/mac/bind_objc_block.h b/base/mac/bind_objc_block.h new file mode 100644 index 0000000000..75da437b5c --- /dev/null +++ b/base/mac/bind_objc_block.h @@ -0,0 +1,54 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MAC_BIND_OBJC_BLOCK_H_ +#define BASE_MAC_BIND_OBJC_BLOCK_H_ + +#include + +#include "base/bind.h" +#include "base/callback_forward.h" +#include "base/mac/scoped_block.h" + +// BindBlock builds a callback from an Objective-C block. Example usages: +// +// Closure closure = BindBlock(^{DoSomething();}); +// Callback callback = BindBlock(^{return 42;}); + +namespace base { + +namespace internal { + +// Helper functions to run the block contained in the parameter. +template +R RunBlock(base::mac::ScopedBlock block) { + R(^extracted_block)() = block.get(); + return extracted_block(); +} + +template +R RunBlock(base::mac::ScopedBlock block, A1 a) { + R(^extracted_block)(A1) = block.get(); + return extracted_block(a); +} + +} // namespace internal + +// Construct a callback with no argument from an objective-C block. +template +base::Callback BindBlock(R(^block)()) { + return base::Bind(&base::internal::RunBlock, + base::mac::ScopedBlock(Block_copy(block))); +} + +// Construct a callback with one argument from an objective-C block. +template +base::Callback BindBlock(R(^block)(A1)) { + return base::Bind(&base::internal::RunBlock, + base::mac::ScopedBlock(Block_copy(block))); +} + +} // namespace base + +#endif // BASE_MAC_BIND_OBJC_BLOCK_H_ diff --git a/base/mac/bind_objc_block_unittest.mm b/base/mac/bind_objc_block_unittest.mm new file mode 100644 index 0000000000..888b3dcff4 --- /dev/null +++ b/base/mac/bind_objc_block_unittest.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. + +#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); +} + +TEST(BindObjcBlockTest, TestReturnValue) { + const int kReturnValue = 42; + base::Callback c = base::BindBlock(^{return kReturnValue;}); + EXPECT_EQ(kReturnValue, c.Run()); +} + +TEST(BindObjcBlockTest, TestArgument) { + const int kArgument = 42; + base::Callback c = base::BindBlock(^(int a){return a + 1;}); + EXPECT_EQ(kArgument + 1, c.Run(kArgument)); +} + +} // namespace diff --git a/base/mac/bundle_locations.h b/base/mac/bundle_locations.h new file mode 100644 index 0000000000..276290b0e6 --- /dev/null +++ b/base/mac/bundle_locations.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_MAC_BUNDLE_LOCATIONS_H_ +#define BASE_MAC_BUNDLE_LOCATIONS_H_ + +#include "base/base_export.h" +#include "base/files/file_path.h" + +#if defined(__OBJC__) +#import +#else // __OBJC__ +class NSBundle; +class NSString; +#endif // __OBJC__ + +namespace base { + +class FilePath; + +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/base/mac/bundle_locations.mm b/base/mac/bundle_locations.mm new file mode 100644 index 0000000000..54021b85ee --- /dev/null +++ b/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/strings/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/base/mac/cocoa_protocols.h b/base/mac/cocoa_protocols.h new file mode 100644 index 0000000000..e83fcbb299 --- /dev/null +++ b/base/mac/cocoa_protocols.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_COCOA_PROTOCOLS_MAC_H_ +#define BASE_COCOA_PROTOCOLS_MAC_H_ + +#import + +// 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) +DEFINE_EMPTY_PROTOCOL(ICCameraDeviceDownloadDelegate) + +#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/base/mac/foundation_util.h b/base/mac/foundation_util.h new file mode 100644 index 0000000000..447f7bf0d8 --- /dev/null +++ b/base/mac/foundation_util.h @@ -0,0 +1,369 @@ +// Copyright (c) 2012 The Chromium Authors. All 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 + +#include +#include + +#include "base/base_export.h" +#include "base/logging.h" +#include "base/mac/scoped_cftyperef.h" + +#if defined(__OBJC__) +#import +#else // __OBJC__ +#include +class NSBundle; +class NSString; +#endif // __OBJC__ + +#if defined(OS_IOS) +#include +#else +#include +#endif + +// Adapted from NSObjCRuntime.h NS_ENUM definition (used in Foundation starting +// with the OS X 10.8 SDK and the iOS 6.0 SDK). +#if __has_extension(cxx_strong_enums) && \ + (defined(OS_IOS) || (defined(MAC_OS_X_VERSION_10_8) && \ + MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8)) +#define CR_FORWARD_ENUM(_type, _name) enum _name : _type _name +#else +#define CR_FORWARD_ENUM(_type, _name) _type _name +#endif + +// Adapted from NSPathUtilities.h and NSObjCRuntime.h. +#if __LP64__ || NS_BUILD_32_LIKE_64 +typedef CR_FORWARD_ENUM(unsigned long, NSSearchPathDirectory); +typedef unsigned long NSSearchPathDomainMask; +#else +typedef CR_FORWARD_ENUM(unsigned int, NSSearchPathDirectory); +typedef unsigned int NSSearchPathDomainMask; +#endif + +typedef struct OpaqueSecTrustRef* SecACLRef; +typedef struct OpaqueSecTrustedApplicationRef* SecTrustedApplicationRef; + +namespace base { + +class FilePath; + +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( +// CFArrayGetValueAtIndex(array, index)); +// +// CFTypeRef hello = CFSTR("hello world"); +// CFStringRef some_string = base::mac::CFCastStrict(hello); + +template +T CFCast(const CFTypeRef& cf_val); + +template +T CFCastStrict(const CFTypeRef& cf_val); + +#define CF_CAST_DECL(TypeCF) \ +template<> BASE_EXPORT TypeCF##Ref \ +CFCast(const CFTypeRef& cf_val);\ +\ +template<> BASE_EXPORT TypeCF##Ref \ +CFCastStrict(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( +// [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"]); +// +// NSString* str = base::mac::ObjCCastStrict( +// [ns_arr_of_ns_strs objectAtIndex:0]); +template +T* ObjCCast(id objc_val) { + if ([objc_val isKindOfClass:[T class]]) { + return reinterpret_cast(objc_val); + } + return nil; +} + +template +T* ObjCCastStrict(id objc_val) { + T* rv = ObjCCast(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 +T GetValueFromDictionary(CFDictionaryRef dict, CFStringRef key) { + CFTypeRef value = CFDictionaryGetValue(dict, key); + T value_specific = CFCast(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/base/mac/foundation_util.mm b/base/mac/foundation_util.mm new file mode 100644 index 0000000000..1897b6f784 --- /dev/null +++ b/base/mac/foundation_util.mm @@ -0,0 +1,400 @@ +// Copyright (c) 2012 The Chromium Authors. All 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 +#include + +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/mac/bundle_locations.h" +#include "base/mac/mac_logging.h" +#include "base/strings/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 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::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 nsobj = static_cast >(obj); + [nsobj retain]; +} + +void NSObjectRelease(void* obj) { + id nsobj = static_cast >(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(reinterpret_cast(cf_val)); \ + return ns_val; \ +} \ +\ +TypeCF##Ref NSToCFCast(TypeNS* ns_val) { \ + TypeCF##Ref cf_val = reinterpret_cast(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(cf_val); \ + return ns_val; \ +} \ +\ +CFMutable##name##Ref NSToCFCast(NSMutable##name* ns_val) { \ + CFMutable##name##Ref cf_val = \ + reinterpret_cast(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(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(const CFTypeRef& cf_val) { \ + TypeCF##Ref rv = CFCast(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 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::ScopedCFTypeRef desc(CFErrorCopyDescription(err)); + base::ScopedCFTypeRef user_info(CFErrorCopyUserInfo(err)); + CFStringRef errorDesc = NULL; + if (user_info.get()) { + errorDesc = reinterpret_cast( + CFDictionaryGetValue(user_info.get(), kCFErrorDescriptionKey)); + } + o << "Code: " << CFErrorGetCode(err) + << " Domain: " << CFErrorGetDomain(err) + << " Desc: " << desc.get(); + if(errorDesc) { + o << "(" << errorDesc << ")"; + } + return o; +} diff --git a/base/mac/foundation_util_unittest.mm b/base/mac/foundation_util_unittest.mm new file mode 100644 index 0000000000..fc7dd099c5 --- /dev/null +++ b/base/mac/foundation_util_unittest.mm @@ -0,0 +1,319 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/mac/foundation_util.h" + +#include "base/basictypes.h" +#include "base/files/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" + +namespace base { +namespace mac { + +TEST(FoundationUtilTest, CFCast) { + // Build out the CF types to be tested as empty containers. + ScopedCFTypeRef test_array( + CFArrayCreate(NULL, NULL, 0, &kCFTypeArrayCallBacks)); + ScopedCFTypeRef test_array_mutable( + CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks)); + ScopedCFTypeRef test_bag( + CFBagCreate(NULL, NULL, 0, &kCFTypeBagCallBacks)); + ScopedCFTypeRef test_bag_mutable( + CFBagCreateMutable(NULL, 0, &kCFTypeBagCallBacks)); + CFTypeRef test_bool = kCFBooleanTrue; + ScopedCFTypeRef test_data( + CFDataCreate(NULL, NULL, 0)); + ScopedCFTypeRef test_data_mutable( + CFDataCreateMutable(NULL, 0)); + ScopedCFTypeRef test_date( + CFDateCreate(NULL, 0)); + ScopedCFTypeRef test_dict( + CFDictionaryCreate(NULL, NULL, NULL, 0, + &kCFCopyStringDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + ScopedCFTypeRef test_dict_mutable( + CFDictionaryCreateMutable(NULL, 0, + &kCFCopyStringDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + int int_val = 256; + ScopedCFTypeRef test_number( + CFNumberCreate(NULL, kCFNumberIntType, &int_val)); + CFTypeRef test_null = kCFNull; + ScopedCFTypeRef test_set( + CFSetCreate(NULL, NULL, 0, &kCFTypeSetCallBacks)); + ScopedCFTypeRef test_set_mutable( + CFSetCreateMutable(NULL, 0, &kCFTypeSetCallBacks)); + ScopedCFTypeRef test_str( + CFStringCreateWithBytes(NULL, NULL, 0, kCFStringEncodingASCII, false)); + CFTypeRef test_str_const = CFSTR("hello"); + ScopedCFTypeRef 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, CFCast(test_array)); + EXPECT_EQ(test_array_mutable, CFCast(test_array_mutable)); + EXPECT_EQ(test_bag, CFCast(test_bag)); + EXPECT_EQ(test_bag_mutable, CFCast(test_bag_mutable)); + EXPECT_EQ(test_bool, CFCast(test_bool)); + EXPECT_EQ(test_data, CFCast(test_data)); + EXPECT_EQ(test_data_mutable, CFCast(test_data_mutable)); + EXPECT_EQ(test_date, CFCast(test_date)); + EXPECT_EQ(test_dict, CFCast(test_dict)); + EXPECT_EQ(test_dict_mutable, CFCast(test_dict_mutable)); + EXPECT_EQ(test_number, CFCast(test_number)); + EXPECT_EQ(test_null, CFCast(test_null)); + EXPECT_EQ(test_set, CFCast(test_set)); + EXPECT_EQ(test_set_mutable, CFCast(test_set_mutable)); + EXPECT_EQ(test_str, CFCast(test_str)); + EXPECT_EQ(test_str_const, CFCast(test_str_const)); + EXPECT_EQ(test_str_mutable, CFCast(test_str_mutable)); + + // When given an incorrect CF cast, provide NULL. + EXPECT_FALSE(CFCast(test_array)); + EXPECT_FALSE(CFCast(test_array_mutable)); + EXPECT_FALSE(CFCast(test_bag)); + EXPECT_FALSE(CFCast(test_bag_mutable)); + EXPECT_FALSE(CFCast(test_bool)); + EXPECT_FALSE(CFCast(test_data)); + EXPECT_FALSE(CFCast(test_data_mutable)); + EXPECT_FALSE(CFCast(test_date)); + EXPECT_FALSE(CFCast(test_dict)); + EXPECT_FALSE(CFCast(test_dict_mutable)); + EXPECT_FALSE(CFCast(test_number)); + EXPECT_FALSE(CFCast(test_null)); + EXPECT_FALSE(CFCast(test_set)); + EXPECT_FALSE(CFCast(test_set_mutable)); + EXPECT_FALSE(CFCast(test_str)); + EXPECT_FALSE(CFCast(test_str_const)); + EXPECT_FALSE(CFCast(test_str_mutable)); + + // Giving a NULL provides a NULL. + EXPECT_FALSE(CFCast(NULL)); + EXPECT_FALSE(CFCast(NULL)); + EXPECT_FALSE(CFCast(NULL)); + EXPECT_FALSE(CFCast(NULL)); + EXPECT_FALSE(CFCast(NULL)); + EXPECT_FALSE(CFCast(NULL)); + EXPECT_FALSE(CFCast(NULL)); + EXPECT_FALSE(CFCast(NULL)); + EXPECT_FALSE(CFCast(NULL)); + EXPECT_FALSE(CFCast(NULL)); + + // CFCastStrict: correct cast results in correct pointer being returned. + EXPECT_EQ(test_array, CFCastStrict(test_array)); + EXPECT_EQ(test_array_mutable, CFCastStrict(test_array_mutable)); + EXPECT_EQ(test_bag, CFCastStrict(test_bag)); + EXPECT_EQ(test_bag_mutable, CFCastStrict(test_bag_mutable)); + EXPECT_EQ(test_bool, CFCastStrict(test_bool)); + EXPECT_EQ(test_data, CFCastStrict(test_data)); + EXPECT_EQ(test_data_mutable, CFCastStrict(test_data_mutable)); + EXPECT_EQ(test_date, CFCastStrict(test_date)); + EXPECT_EQ(test_dict, CFCastStrict(test_dict)); + EXPECT_EQ(test_dict_mutable, + CFCastStrict(test_dict_mutable)); + EXPECT_EQ(test_number, CFCastStrict(test_number)); + EXPECT_EQ(test_null, CFCastStrict(test_null)); + EXPECT_EQ(test_set, CFCastStrict(test_set)); + EXPECT_EQ(test_set_mutable, CFCastStrict(test_set_mutable)); + EXPECT_EQ(test_str, CFCastStrict(test_str)); + EXPECT_EQ(test_str_const, CFCastStrict(test_str_const)); + EXPECT_EQ(test_str_mutable, CFCastStrict(test_str_mutable)); + + // CFCastStrict: Giving a NULL provides a NULL. + EXPECT_FALSE(CFCastStrict(NULL)); + EXPECT_FALSE(CFCastStrict(NULL)); + EXPECT_FALSE(CFCastStrict(NULL)); + EXPECT_FALSE(CFCastStrict(NULL)); + EXPECT_FALSE(CFCastStrict(NULL)); + EXPECT_FALSE(CFCastStrict(NULL)); + EXPECT_FALSE(CFCastStrict(NULL)); + EXPECT_FALSE(CFCastStrict(NULL)); + EXPECT_FALSE(CFCastStrict(NULL)); + EXPECT_FALSE(CFCastStrict(NULL)); +} + +TEST(FoundationUtilTest, ObjCCast) { + 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, ObjCCast(test_array)); + EXPECT_EQ(test_array_mutable, ObjCCast(test_array_mutable)); + EXPECT_EQ(test_data, ObjCCast(test_data)); + EXPECT_EQ(test_data_mutable, ObjCCast(test_data_mutable)); + EXPECT_EQ(test_date, ObjCCast(test_date)); + EXPECT_EQ(test_dict, ObjCCast(test_dict)); + EXPECT_EQ(test_dict_mutable, ObjCCast(test_dict_mutable)); + EXPECT_EQ(test_number, ObjCCast(test_number)); + EXPECT_EQ(test_null, ObjCCast(test_null)); + EXPECT_EQ(test_set, ObjCCast(test_set)); + EXPECT_EQ(test_set_mutable, ObjCCast(test_set_mutable)); + EXPECT_EQ(test_str, ObjCCast(test_str)); + EXPECT_EQ(test_str_const, ObjCCast(test_str_const)); + EXPECT_EQ(test_str_mutable, ObjCCast(test_str_mutable)); + + // When given an incorrect ObjC cast, provide nil. + EXPECT_FALSE(ObjCCast(test_array)); + EXPECT_FALSE(ObjCCast(test_array_mutable)); + EXPECT_FALSE(ObjCCast(test_data)); + EXPECT_FALSE(ObjCCast(test_data_mutable)); + EXPECT_FALSE(ObjCCast(test_date)); + EXPECT_FALSE(ObjCCast(test_dict)); + EXPECT_FALSE(ObjCCast(test_dict_mutable)); + EXPECT_FALSE(ObjCCast(test_number)); + EXPECT_FALSE(ObjCCast(test_null)); + EXPECT_FALSE(ObjCCast(test_set)); + EXPECT_FALSE(ObjCCast(test_set_mutable)); + EXPECT_FALSE(ObjCCast(test_str)); + EXPECT_FALSE(ObjCCast(test_str_const)); + EXPECT_FALSE(ObjCCast(test_str_mutable)); + + // Giving a nil provides a nil. + EXPECT_FALSE(ObjCCast(nil)); + EXPECT_FALSE(ObjCCast(nil)); + EXPECT_FALSE(ObjCCast(nil)); + EXPECT_FALSE(ObjCCast(nil)); + EXPECT_FALSE(ObjCCast(nil)); + EXPECT_FALSE(ObjCCast(nil)); + EXPECT_FALSE(ObjCCast(nil)); + EXPECT_FALSE(ObjCCast(nil)); + + // ObjCCastStrict: correct cast results in correct pointer being returned. + EXPECT_EQ(test_array, ObjCCastStrict(test_array)); + EXPECT_EQ(test_array_mutable, + ObjCCastStrict(test_array_mutable)); + EXPECT_EQ(test_data, ObjCCastStrict(test_data)); + EXPECT_EQ(test_data_mutable, + ObjCCastStrict(test_data_mutable)); + EXPECT_EQ(test_date, ObjCCastStrict(test_date)); + EXPECT_EQ(test_dict, ObjCCastStrict(test_dict)); + EXPECT_EQ(test_dict_mutable, + ObjCCastStrict(test_dict_mutable)); + EXPECT_EQ(test_number, ObjCCastStrict(test_number)); + EXPECT_EQ(test_null, ObjCCastStrict(test_null)); + EXPECT_EQ(test_set, ObjCCastStrict(test_set)); + EXPECT_EQ(test_set_mutable, + ObjCCastStrict(test_set_mutable)); + EXPECT_EQ(test_str, ObjCCastStrict(test_str)); + EXPECT_EQ(test_str_const, + ObjCCastStrict(test_str_const)); + EXPECT_EQ(test_str_mutable, + ObjCCastStrict(test_str_mutable)); + + // ObjCCastStrict: Giving a nil provides a nil. + EXPECT_FALSE(ObjCCastStrict(nil)); + EXPECT_FALSE(ObjCCastStrict(nil)); + EXPECT_FALSE(ObjCCastStrict(nil)); + EXPECT_FALSE(ObjCCastStrict(nil)); + EXPECT_FALSE(ObjCCastStrict(nil)); + EXPECT_FALSE(ObjCCastStrict(nil)); + EXPECT_FALSE(ObjCCastStrict(nil)); + EXPECT_FALSE(ObjCCastStrict(nil)); +} + +TEST(FoundationUtilTest, GetValueFromDictionary) { + int one = 1, two = 2, three = 3; + + ScopedCFTypeRef cf_one( + CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &one)); + ScopedCFTypeRef cf_two( + CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &two)); + ScopedCFTypeRef 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); + + ScopedCFTypeRef test_dict( + CFDictionaryCreate(kCFAllocatorDefault, + reinterpret_cast(keys), + reinterpret_cast(values), + arraysize(values), + &kCFCopyStringDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + + // GetValueFromDictionary<>(_, _) should produce the correct + // expected output. + EXPECT_EQ(values[0], + GetValueFromDictionary(test_dict, CFSTR("one"))); + EXPECT_EQ(values[1], + GetValueFromDictionary(test_dict, CFSTR("two"))); + EXPECT_EQ(values[2], + GetValueFromDictionary(test_dict, CFSTR("three"))); + + // Bad input should produce bad output. + EXPECT_FALSE(GetValueFromDictionary(test_dict, CFSTR("four"))); + EXPECT_FALSE(GetValueFromDictionary(test_dict, CFSTR("one"))); +} + +TEST(FoundationUtilTest, FilePathToNSString) { + EXPECT_NSEQ(nil, FilePathToNSString(FilePath())); + EXPECT_NSEQ(@"/a/b", FilePathToNSString(FilePath("/a/b"))); +} + +// http://crbug.com/173983 Fails consistently under Mac ASAN. +TEST(FoundationUtilTest, DISABLED_NSStringToFilePath) { + EXPECT_EQ(FilePath(), NSStringToFilePath(nil)); + EXPECT_EQ(FilePath(), NSStringToFilePath(@"")); + EXPECT_EQ(FilePath("/a/b"), NSStringToFilePath(@"/a/b")); +} + +} // namespace mac +} // namespace base diff --git a/base/mac/launch_services_util.cc b/base/mac/launch_services_util.cc new file mode 100644 index 0000000000..6121081404 --- /dev/null +++ b/base/mac/launch_services_util.cc @@ -0,0 +1,65 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/mac/launch_services_util.h" + +#include "base/logging.h" +#include "base/mac/mac_logging.h" +#include "base/mac/mac_util.h" +#include "base/strings/sys_string_conversions.h" + +namespace base { +namespace mac { + +bool OpenApplicationWithPath(const base::FilePath& bundle_path, + const CommandLine& command_line, + LSLaunchFlags launch_flags, + ProcessSerialNumber* out_psn) { + FSRef app_fsref; + if (!base::mac::FSRefFromPath(bundle_path.value(), &app_fsref)) { + LOG(ERROR) << "base::mac::FSRefFromPath failed for " << bundle_path.value(); + return false; + } + + std::vector argv = command_line.argv(); + int argc = argv.size(); + base::ScopedCFTypeRef launch_args( + CFArrayCreateMutable(NULL, argc - 1, &kCFTypeArrayCallBacks)); + if (!launch_args) { + LOG(ERROR) << "CFArrayCreateMutable failed, size was " << argc; + return false; + } + + for (int i = 1; i < argc; ++i) { + const std::string& arg(argv[i]); + + base::ScopedCFTypeRef arg_cf(base::SysUTF8ToCFStringRef(arg)); + if (!arg_cf) { + LOG(ERROR) << "base::SysUTF8ToCFStringRef failed for " << arg; + return false; + } + CFArrayAppendValue(launch_args, arg_cf); + } + + LSApplicationParameters ls_parameters = { + 0, // version + launch_flags, + &app_fsref, + NULL, // asyncLaunchRefCon + NULL, // environment + launch_args, + NULL // initialEvent + }; + // TODO(jeremya): this opens a new browser window if Chrome is already + // running without any windows open. + OSStatus status = LSOpenApplication(&ls_parameters, out_psn); + if (status != noErr) { + OSSTATUS_LOG(ERROR, status) << "LSOpenApplication"; + return false; + } + return true; +} + +} // namespace mac +} // namespace base diff --git a/base/mac/launch_services_util.h b/base/mac/launch_services_util.h new file mode 100644 index 0000000000..d4aa9ffcbe --- /dev/null +++ b/base/mac/launch_services_util.h @@ -0,0 +1,33 @@ +// 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_MAC_LAUNCH_SERVICES_UTIL_H_ +#define BASE_MAC_LAUNCH_SERVICES_UTIL_H_ + +#include + +#include "base/base_export.h" +#include "base/command_line.h" +#include "base/files/file_path.h" + +struct ProcessSerialNumber; + +namespace base { +namespace mac { + +// Launches the application bundle at |bundle_path|, passing argv[1..] from +// |command_line| as command line arguments if the app isn't already running. +// |launch_flags| are passed directly to LSApplicationParameters. +// |out_psn|, if not NULL, will be set to the process serial number of the +// application's main process if the app was successfully launched. +// Returns true if the app was successfully launched. +BASE_EXPORT bool OpenApplicationWithPath(const base::FilePath& bundle_path, + const CommandLine& command_line, + LSLaunchFlags launch_flags, + ProcessSerialNumber* out_psn); + +} // namespace mac +} // namespace base + +#endif // BASE_MAC_LAUNCH_SERVICES_UTIL_H_ diff --git a/base/mac/launchd.cc b/base/mac/launchd.cc new file mode 100644 index 0000000000..1d384c93a2 --- /dev/null +++ b/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/base/mac/launchd.h b/base/mac/launchd.h new file mode 100644 index 0000000000..9e4514e839 --- /dev/null +++ b/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 +#include + +#include + +#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/base/mac/libdispatch_task_runner.cc b/base/mac/libdispatch_task_runner.cc new file mode 100644 index 0000000000..4b5abaf30c --- /dev/null +++ b/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(context); + self->queue_finalized_.Signal(); +} + +} // namespace mac +} // namespace base diff --git a/base/mac/libdispatch_task_runner.h b/base/mac/libdispatch_task_runner.h new file mode 100644 index 0000000000..b1d90e29ac --- /dev/null +++ b/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 + +#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/base/mac/libdispatch_task_runner_unittest.cc b/base/mac/libdispatch_task_runner_unittest.cc new file mode 100644 index 0000000000..a4f3202ac5 --- /dev/null +++ b/base/mac/libdispatch_task_runner_unittest.cc @@ -0,0 +1,224 @@ +// Copyright (c) 2012 The Chromium Authors. All 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/message_loop.h" +#include "base/strings/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, + base::MessageLoop::QuitWhenIdleClosure()); + }); + 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("", task_order_[i]) << " (i=" << i << ")"; + } + } + } + + // The message loop for the test main thread. + base::MessageLoop message_loop_; + + // The task runner under test. + scoped_refptr task_runner_; + + // Vector that records data from TaskOrderMarker. + std::vector 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", base::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, + base::MessageLoop::QuitWhenIdleClosure()); + })); + })); + 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, + base::MessageLoop::QuitWhenIdleClosure()); + }), 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/base/mac/mac_logging.cc b/base/mac/mac_logging.cc new file mode 100644 index 0000000000..d58220fe89 --- /dev/null +++ b/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 + +#if !defined(OS_IOS) +#include +#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/base/mac/mac_logging.h b/base/mac/mac_logging.h new file mode 100644 index 0000000000..9a0003e9ad --- /dev/null +++ b/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 +#else +#include +#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/base/mac/mac_util.h b/base/mac/mac_util.h new file mode 100644 index 0000000000..23b57edaee --- /dev/null +++ b/base/mac/mac_util.h @@ -0,0 +1,204 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MAC_MAC_UTIL_H_ +#define BASE_MAC_MAC_UTIL_H_ + +#include +#include +#include + +#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 +#else // __OBJC__ +class NSImage; +#endif // __OBJC__ + +namespace base { + +class FilePath; + +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 generic RGB color space. The return value is a static value; do +// not release it! +BASE_EXPORT CGColorSpaceRef GetGenericRGBColorSpace(); + +// 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(); + +// Remove the quarantine xattr from the given file. Returns false if there was +// an error, or true otherwise. +BASE_EXPORT bool RemoveQuarantineAttribute(const FilePath& file_path); + +// 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/base/mac/mac_util.mm b/base/mac/mac_util.mm new file mode 100644 index 0000000000..04311ecf11 --- /dev/null +++ b/base/mac/mac_util.mm @@ -0,0 +1,703 @@ +// Copyright (c) 2012 The Chromium Authors. All 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 +#import + +#include +#include +#include +#include + +#include "base/files/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/mac/scoped_ioobject.h" +#include "base/mac/scoped_nsobject.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_piece.h" +#include "base/strings/sys_string_conversions.h" + +namespace base { +namespace mac { + +// Replicate specific 10.7 SDK declarations for building with prior SDKs. +#if !defined(MAC_OS_X_VERSION_10_7) || \ + MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 + +enum { + NSApplicationPresentationFullScreen = 1 << 10 +}; + +#endif // MAC_OS_X_VERSION_10_7 + +namespace { + +// The current count of outstanding requests for full screen mode from browser +// windows, plugins, etc. +int g_full_screen_requests[kNumFullScreenModes] = { 0 }; + +// Sets the appropriate application presentation option based on the current +// full screen requests. Since only one presentation option 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 +// presentation option is already set, does nothing. +void SetUIMode() { + NSApplicationPresentationOptions current_options = + [NSApp presentationOptions]; + + // 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. + NSApplicationPresentationOptions desired_options = + NSApplicationPresentationDefault; + if (g_full_screen_requests[kFullScreenModeAutoHideAll] > 0) { + desired_options = NSApplicationPresentationHideDock | + NSApplicationPresentationAutoHideMenuBar; + } else if (g_full_screen_requests[kFullScreenModeHideDock] > 0) { + desired_options = NSApplicationPresentationHideDock; + } else if (g_full_screen_requests[kFullScreenModeHideAll] > 0) { + desired_options = NSApplicationPresentationHideDock | + NSApplicationPresentationHideMenuBar; + } + + // Mac OS X bug: if the window is fullscreened (Lion-style) and + // NSApplicationPresentationDefault is requested, the result is that the menu + // bar doesn't auto-hide. rdar://13576498 http://www.openradar.me/13576498 + // + // As a workaround, in that case, explicitly set the presentation options to + // the ones that are set by the system as it fullscreens a window. + if (desired_options == NSApplicationPresentationDefault && + current_options & NSApplicationPresentationFullScreen) { + desired_options |= NSApplicationPresentationFullScreen | + NSApplicationPresentationAutoHideMenuBar | + NSApplicationPresentationAutoHideDock; + } + + if (current_options != desired_options) + [NSApp setPresentationOptions: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 login_items(LSSharedFileListCreate( + NULL, kLSSharedFileListSessionLoginItems, NULL)); + + if (!login_items.get()) { + DLOG(ERROR) << "Couldn't get a Login Items list."; + return NULL; + } + + base::scoped_nsobject 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( + [login_items_array objectAtIndex:i]); + CFURLRef item_url_ref = NULL; + + if (LSSharedFileListItemResolve(item, 0, &item_url_ref, NULL) == noErr) { + ScopedCFTypeRef item_url(item_url_ref); + if (CFEqual(item_url, url)) { + CFRetain(item); + return item; + } + } + } + + return NULL; +} + +bool IsHiddenLoginItem(LSSharedFileListItemRef item) { + ScopedCFTypeRef hidden(reinterpret_cast( + LSSharedFileListItemCopyProperty(item, + reinterpret_cast(kLSSharedFileListLoginItemHidden)))); + + return hidden && hidden == kCFBooleanTrue; +} + +} // namespace + +std::string PathFromFSRef(const FSRef& ref) { + ScopedCFTypeRef 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 GetGenericRGBColorSpace() { + // Leaked. That's OK, it's scoped to the lifetime of the application. + static CGColorSpaceRef g_color_space_generic_rgb( + CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB)); + DLOG_IF(ERROR, !g_color_space_generic_rgb) << + "Couldn't get the generic RGB color space"; + return g_color_space_generic_rgb; +} + +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); + if (mode < 0) + return; + + 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_GE(g_full_screen_requests[mode], 0); + if (mode < 0) + return; + + 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( + 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( + CFBundleGetFunctionPointerForName( + launch_services_bundle, + CFSTR("_LSSetApplicationInformationItem"))); + if (!ls_set_application_information_item_func) + DLOG(ERROR) << "Could not find _LSSetApplicationInformationItem"; + + CFStringRef* key_pointer = reinterpret_cast( + 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 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 item(GetLoginItemForApp()); + if (!item.get()) + return false; + + if (is_hidden) + *is_hidden = IsHiddenLoginItem(item); + + return true; +} + +void AddToLoginItems(bool hide_on_startup) { + ScopedCFTypeRef item(GetLoginItemForApp()); + if (item.get() && (IsHiddenLoginItem(item) == hide_on_startup)) { + return; // Already is a login item with required hide flag. + } + + ScopedCFTypeRef 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 new_item; + new_item.reset(LSSharedFileListInsertItemURL( + login_items, kLSSharedFileListItemLast, NULL, NULL, + reinterpret_cast(url), + reinterpret_cast(properties), NULL)); + + if (!new_item.get()) { + DLOG(ERROR) << "Couldn't insert current app into Login Items list."; + } +} + +void RemoveFromLoginItems() { + ScopedCFTypeRef item(GetLoginItemForApp()); + if (!item.get()) + return; + + ScopedCFTypeRef 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 }; + + base::scoped_nsobject process_info( + CFToNSCast(ProcessInformationCopyDictionary( + &psn, kProcessDictionaryIncludeAllInformationMask))); + + long long temp = [[process_info objectForKey:@"ParentPSN"] longLongValue]; + ProcessSerialNumber parent_psn = + { (temp >> 32) & 0x00000000FFFFFFFFLL, temp & 0x00000000FFFFFFFFLL }; + + base::scoped_nsobject 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 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); +} + +bool RemoveQuarantineAttribute(const FilePath& file_path) { + const char kQuarantineAttrName[] = "com.apple.quarantine"; + int status = removexattr(file_path.value().c_str(), kQuarantineAttrName, 0); + return status == 0 || errno == ENOATTR; +} + +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 + +std::string GetModelIdentifier() { + std::string return_string; + ScopedIOObject platform_expert( + IOServiceGetMatchingService(kIOMasterPortDefault, + IOServiceMatching("IOPlatformExpertDevice"))); + if (platform_expert) { + ScopedCFTypeRef model_data( + static_cast(IORegistryEntryCreateCFProperty( + platform_expert, + CFSTR("model"), + kCFAllocatorDefault, + 0))); + if (model_data) { + return_string = + reinterpret_cast(CFDataGetBytePtr(model_data)); + } + } + return return_string; +} + +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/base/mac/mac_util_unittest.mm b/base/mac/mac_util_unittest.mm new file mode 100644 index 0000000000..d246757870 --- /dev/null +++ b/base/mac/mac_util_unittest.mm @@ -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. + +#import + +#include "base/mac/mac_util.h" + +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/mac/foundation_util.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/mac/scoped_nsobject.h" +#include "base/sys_info.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" + +#include +#include + +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(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) { + base::scoped_nsobject nsImage( + [[NSImage alloc] initWithSize:NSMakeSize(20, 20)]); + [nsImage lockFocus]; + [[NSColor redColor] set]; + NSRect rect = NSZeroRect; + rect.size = [nsImage size]; + NSRectFill(rect); + [nsImage unlockFocus]; + + ScopedCFTypeRef cgImage(CopyNSImageToCGImage(nsImage.get())); + EXPECT_TRUE(cgImage.get()); +} + +TEST_F(MacUtilTest, NSObjectRetainRelease) { + base::scoped_nsobject 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); +} + +TEST_F(MacUtilTest, TestRemoveQuarantineAttribute) { + ScopedTempDir temp_dir_; + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + FilePath dummy_folder_path = temp_dir_.path().Append("DummyFolder"); + ASSERT_TRUE(file_util::CreateDirectory(dummy_folder_path)); + const char* quarantine_str = "0000;4b392bb2;Chromium;|org.chromium.Chromium"; + const char* file_path_str = dummy_folder_path.value().c_str(); + EXPECT_EQ(0, setxattr(file_path_str, "com.apple.quarantine", + quarantine_str, strlen(quarantine_str), 0, 0)); + EXPECT_EQ(static_cast(strlen(quarantine_str)), + getxattr(file_path_str, "com.apple.quarantine", + NULL, 0, 0, 0)); + EXPECT_TRUE(RemoveQuarantineAttribute(dummy_folder_path)); + EXPECT_EQ(-1, getxattr(file_path_str, "com.apple.quarantine", NULL, 0, 0, 0)); + EXPECT_EQ(ENOATTR, errno); +} + +TEST_F(MacUtilTest, TestRemoveQuarantineAttributeTwice) { + ScopedTempDir temp_dir_; + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + FilePath dummy_folder_path = temp_dir_.path().Append("DummyFolder"); + const char* file_path_str = dummy_folder_path.value().c_str(); + ASSERT_TRUE(file_util::CreateDirectory(dummy_folder_path)); + EXPECT_EQ(-1, getxattr(file_path_str, "com.apple.quarantine", NULL, 0, 0, 0)); + // No quarantine attribute to begin with, but RemoveQuarantineAttribute still + // succeeds because in the end the folder still doesn't have the quarantine + // attribute set. + EXPECT_TRUE(RemoveQuarantineAttribute(dummy_folder_path)); + EXPECT_TRUE(RemoveQuarantineAttribute(dummy_folder_path)); + EXPECT_EQ(ENOATTR, errno); +} + +TEST_F(MacUtilTest, TestRemoveQuarantineAttributeNonExistentPath) { + ScopedTempDir temp_dir_; + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + FilePath non_existent_path = temp_dir_.path().Append("DummyPath"); + ASSERT_FALSE(PathExists(non_existent_path)); + EXPECT_FALSE(RemoveQuarantineAttribute(non_existent_path)); +} + +} // namespace + +} // namespace mac +} // namespace base diff --git a/base/mac/objc_property_releaser.h b/base/mac/objc_property_releaser.h new file mode 100644 index 0000000000..973d793218 --- /dev/null +++ b/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 + +#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/base/mac/objc_property_releaser.mm b/base/mac/objc_property_releaser.mm new file mode 100644 index 0000000000..f7ee88fbcc --- /dev/null +++ b/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 +#include + +#include + +#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/base/mac/objc_property_releaser_unittest.mm b/base/mac/objc_property_releaser_unittest.mm new file mode 100644 index 0000000000..50f81a8762 --- /dev/null +++ b/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 + +#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 + ++ (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 { + @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/base/mac/os_crash_dumps.cc b/base/mac/os_crash_dumps.cc new file mode 100644 index 0000000000..e6b0996ac6 --- /dev/null +++ b/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 +#include + +#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/base/mac/os_crash_dumps.h b/base/mac/os_crash_dumps.h new file mode 100644 index 0000000000..31d90fb24f --- /dev/null +++ b/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/base/mac/scoped_aedesc.h b/base/mac/scoped_aedesc.h new file mode 100644 index 0000000000..a1323c0cb7 --- /dev/null +++ b/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 + +#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 +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/base/mac/scoped_authorizationref.h b/base/mac/scoped_authorizationref.h new file mode 100644 index 0000000000..6413f2e416 --- /dev/null +++ b/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 + +#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/base/mac/scoped_block.h b/base/mac/scoped_block.h new file mode 100644 index 0000000000..509a1c2c19 --- /dev/null +++ b/base/mac/scoped_block.h @@ -0,0 +1,92 @@ +// 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_MAC_SCOPED_BLOCK_H_ +#define BASE_MAC_SCOPED_BLOCK_H_ + +#include + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_policy.h" + +namespace base { +namespace mac { + +// ScopedBlock<> is patterned after ScopedCFTypeRef<>, but uses Block_copy() and +// Block_release() instead of CFRetain() and CFRelease(). + +template +class ScopedBlock { + public: + explicit ScopedBlock( + B block = NULL, + base::scoped_policy::OwnershipPolicy policy = base::scoped_policy::ASSUME) + : block_(block) { + if (block_ && policy == base::scoped_policy::RETAIN) + block_ = Block_copy(block); + } + + ScopedBlock(const ScopedBlock& that) + : block_(that.block_) { + if (block_) + block_ = Block_copy(block_); + } + + ~ScopedBlock() { + if (block_) + Block_release(block_); + } + + ScopedBlock& operator=(const ScopedBlock& that) { + reset(that.get(), base::scoped_policy::RETAIN); + return *this; + } + + void reset(B block = NULL, + base::scoped_policy::OwnershipPolicy policy = + base::scoped_policy::ASSUME) { + if (block && policy == base::scoped_policy::RETAIN) + block = Block_copy(block); + if (block_) + Block_release(block_); + block_ = block; + } + + bool operator==(B that) const { + return block_ == that; + } + + bool operator!=(B that) const { + return block_ != that; + } + + operator B() const { + return block_; + } + + B get() const { + return block_; + } + + void swap(ScopedBlock& that) { + B temp = that.block_; + that.block_ = block_; + block_ = temp; + } + + B release() WARN_UNUSED_RESULT { + B temp = block_; + block_ = NULL; + return temp; + } + + private: + B block_; +}; + +} // namespace mac +} // namespace base + +#endif // BASE_MAC_SCOPED_BLOCK_H_ diff --git a/base/mac/scoped_cffiledescriptorref.h b/base/mac/scoped_cffiledescriptorref.h new file mode 100644 index 0000000000..07196aa921 --- /dev/null +++ b/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 + +#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/base/mac/scoped_cftyperef.h b/base/mac/scoped_cftyperef.h new file mode 100644 index 0000000000..c41de80d80 --- /dev/null +++ b/base/mac/scoped_cftyperef.h @@ -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. + +#ifndef BASE_MAC_SCOPED_CFTYPEREF_H_ +#define BASE_MAC_SCOPED_CFTYPEREF_H_ + +#include + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_policy.h" + +namespace base { + +// 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 +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& that) + : object_(that.object_) { + if (object_) + CFRetain(object_); + } + + ~ScopedCFTypeRef() { + if (object_) + CFRelease(object_); + } + + ScopedCFTypeRef& operator=(const ScopedCFTypeRef& 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 base + +#endif // BASE_MAC_SCOPED_CFTYPEREF_H_ diff --git a/base/mac/scoped_ioobject.h b/base/mac/scoped_ioobject.h new file mode 100644 index 0000000000..854039b553 --- /dev/null +++ b/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 + +#include "base/basictypes.h" +#include "base/compiler_specific.h" + +namespace base { +namespace mac { + +// Just like ScopedCFTypeRef but for io_object_t and subclasses. +template +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/base/mac/scoped_ioplugininterface.h b/base/mac/scoped_ioplugininterface.h new file mode 100644 index 0000000000..503980c3ef --- /dev/null +++ b/base/mac/scoped_ioplugininterface.h @@ -0,0 +1,76 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MAC_SCOPED_IOPLUGININTERFACE_H_ +#define BASE_MAC_SCOPED_IOPLUGININTERFACE_H_ + +#include + +#include "base/basictypes.h" +#include "base/compiler_specific.h" + +namespace base { +namespace mac { + +// Just like ScopedCFTypeRef but for IOCFPlugInInterface and friends +// (IOUSBInterfaceStruct and IOUSBDeviceStruct320 in particular). +template +class ScopedIOPluginInterface { + public: + typedef T** InterfaceT; + typedef InterfaceT element_type; + + explicit ScopedIOPluginInterface(InterfaceT object = NULL) + : object_(object) { + } + + ~ScopedIOPluginInterface() { + if (object_) + (*object_)->Release(object_); + } + + void reset(InterfaceT object = NULL) { + if (object_) + (*object_)->Release(object_); + object_ = object; + } + + bool operator==(InterfaceT that) const { + return object_ == that; + } + + bool operator!=(InterfaceT that) const { + return object_ != that; + } + + operator InterfaceT() const { + return object_; + } + + InterfaceT get() const { + return object_; + } + + void swap(ScopedIOPluginInterface& that) { + InterfaceT temp = that.object_; + that.object_ = object_; + object_ = temp; + } + + InterfaceT release() WARN_UNUSED_RESULT { + InterfaceT temp = object_; + object_ = NULL; + return temp; + } + + private: + InterfaceT object_; + + DISALLOW_COPY_AND_ASSIGN(ScopedIOPluginInterface); +}; + +} // namespace mac +} // namespace base + +#endif // BASE_MAC_SCOPED_IOPLUGININTERFACE_H_ diff --git a/base/mac/scoped_launch_data.h b/base/mac/scoped_launch_data.h new file mode 100644 index 0000000000..e4343b8939 --- /dev/null +++ b/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 + +#include + +#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/base/mac/scoped_mach_port.cc b/base/mac/scoped_mach_port.cc new file mode 100644 index 0000000000..9e45a856a8 --- /dev/null +++ b/base/mac/scoped_mach_port.cc @@ -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. + +#include "base/mac/scoped_mach_port.h" + +namespace base { +namespace mac { + +ScopedMachPort::ScopedMachPort(mach_port_t port) : port_(port) { +} + +ScopedMachPort::~ScopedMachPort() { + reset(); +} + +void ScopedMachPort::reset(mach_port_t port) { + if (port_ != MACH_PORT_NULL) { + mach_port_deallocate(mach_task_self(), port_); + } + port_ = port; +} + +} // namespace mac +} // namespace base diff --git a/base/mac/scoped_mach_port.h b/base/mac/scoped_mach_port.h new file mode 100644 index 0000000000..cc2ef20fe7 --- /dev/null +++ b/base/mac/scoped_mach_port.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_MAC_SCOPED_MACH_PORT_H_ +#define BASE_MAC_SCOPED_MACH_PORT_H_ + +#include + +#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(); + + void reset(mach_port_t port = MACH_PORT_NULL); + + 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/base/mac/scoped_nsautorelease_pool.h b/base/mac/scoped_nsautorelease_pool.h new file mode 100644 index 0000000000..60af71ae05 --- /dev/null +++ b/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/base/mac/scoped_nsautorelease_pool.mm b/base/mac/scoped_nsautorelease_pool.mm new file mode 100644 index 0000000000..e542ca86b2 --- /dev/null +++ b/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 + +#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/base/mac/scoped_nsexception_enabler.h b/base/mac/scoped_nsexception_enabler.h new file mode 100644 index 0000000000..484dd53449 --- /dev/null +++ b/base/mac/scoped_nsexception_enabler.h @@ -0,0 +1,54 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MAC_SCOPED_NSEXCEPTION_ENABLER_H_ +#define BASE_MAC_SCOPED_NSEXCEPTION_ENABLER_H_ + +#import + +#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 |block| with fatal-exceptions turned off, and returns the +// result. If an exception is thrown during the perform, nil is +// returned. +typedef id (^BlockReturningId)(); +BASE_EXPORT id RunBlockIgnoringExceptions(BlockReturningId block); + +} // namespace mac +} // namespace base + +#endif // BASE_MAC_SCOPED_NSEXCEPTION_ENABLER_H_ diff --git a/base/mac/scoped_nsexception_enabler.mm b/base/mac/scoped_nsexception_enabler.mm new file mode 100644 index 0000000000..7b8ad9266b --- /dev/null +++ b/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::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 RunBlockIgnoringExceptions(BlockReturningId block) { + id ret = nil; + @try { + base::mac::ScopedNSExceptionEnabler enable; + ret = block(); + } + @catch(id exception) { + } + return ret; +} + +ScopedNSExceptionEnabler::ScopedNSExceptionEnabler() { + was_enabled_ = GetNSExceptionsAllowed(); + SetNSExceptionsAllowed(true); +} + +ScopedNSExceptionEnabler::~ScopedNSExceptionEnabler() { + SetNSExceptionsAllowed(was_enabled_); +} + +} // namespace mac +} // namespace base diff --git a/base/mac/scoped_nsobject.h b/base/mac/scoped_nsobject.h new file mode 100644 index 0000000000..8d7bd4a27b --- /dev/null +++ b/base/mac/scoped_nsobject.h @@ -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. + +#ifndef BASE_MAC_SCOPED_NSOBJECT_H_ +#define BASE_MAC_SCOPED_NSOBJECT_H_ + +#import +#include "base/basictypes.h" +#include "base/compiler_specific.h" + +namespace base { + +// scoped_nsobject<> is patterned after scoped_ptr<>, but maintains ownership +// of an NSObject subclass object. Style deviations here are solely for +// compatibility with scoped_ptr<>'s interface, with which everyone is already +// familiar. +// +// scoped_nsobject<> takes ownership of an object (in the constructor or in +// reset()) by taking over the caller's existing ownership claim. The caller +// must own the object it gives to scoped_nsobject<>, and relinquishes an +// ownership claim to that object. scoped_nsobject<> does not call -retain, +// callers have to call this manually if appropriate. +// +// scoped_nsprotocol<> has the same behavior as scoped_nsobject, but can be used +// with protocols. +// +// scoped_nsobject<> is not to be used for NSAutoreleasePools. For +// NSAutoreleasePools use ScopedNSAutoreleasePool from +// scoped_nsautorelease_pool.h instead. +// We check for bad uses of scoped_nsobject and NSAutoreleasePool at compile +// time with a template specialization (see below). + +template +class scoped_nsprotocol { + public: + explicit scoped_nsprotocol(NST object = nil) : object_(object) {} + + scoped_nsprotocol(const scoped_nsprotocol& that) + : object_([that.object_ retain]) { + } + + ~scoped_nsprotocol() { + [object_ release]; + } + + scoped_nsprotocol& operator=(const scoped_nsprotocol& that) { + reset([that.get() retain]); + return *this; + } + + void reset(NST object = nil) { + // We intentionally do not check that object != object_ as the caller must + // either already have an ownership claim over whatever it passes to this + // method, or call it with the |RETAIN| policy which will have ensured that + // the object is retained once more when reaching this point. + [object_ release]; + object_ = object; + } + + bool operator==(NST that) const { return object_ == that; } + bool operator!=(NST that) const { return object_ != that; } + + operator NST() const { + return object_; + } + + NST get() const { + return object_; + } + + void swap(scoped_nsprotocol& that) { + NST temp = that.object_; + that.object_ = object_; + object_ = temp; + } + + // scoped_nsprotocol<>::release() is like scoped_ptr<>::release. It is NOT a + // wrapper for [object_ release]. To force a scoped_nsprotocol<> to call + // [object_ release], use scoped_nsprotocol<>::reset(). + NST release() WARN_UNUSED_RESULT { + NST temp = object_; + object_ = nil; + return temp; + } + + // Shift reference to the autorelease pool to be released later. + NST autorelease() { + return [release() autorelease]; + } + + private: + NST object_; +}; + +// Free functions +template +void swap(scoped_nsprotocol& p1, scoped_nsprotocol& p2) { + p1.swap(p2); +} + +template +bool operator==(C p1, const scoped_nsprotocol& p2) { + return p1 == p2.get(); +} + +template +bool operator!=(C p1, const scoped_nsprotocol& p2) { + return p1 != p2.get(); +} + +template +class scoped_nsobject : public scoped_nsprotocol { + public: + explicit scoped_nsobject(NST* object = nil) + : scoped_nsprotocol(object) {} + + scoped_nsobject(const scoped_nsobject& that) + : scoped_nsprotocol(that) { + } + + scoped_nsobject& operator=(const scoped_nsobject& that) { + scoped_nsprotocol::operator=(that); + return *this; + } +}; + +// Specialization to make scoped_nsobject work. +template<> +class scoped_nsobject : public scoped_nsprotocol { + public: + explicit scoped_nsobject(id object = nil) : scoped_nsprotocol(object) {} + + scoped_nsobject(const scoped_nsobject& that) + : scoped_nsprotocol(that) { + } + + scoped_nsobject& operator=(const scoped_nsobject& that) { + scoped_nsprotocol::operator=(that); + return *this; + } +}; + +// Do not use scoped_nsobject for NSAutoreleasePools, use +// ScopedNSAutoreleasePool instead. This is a compile time check. See details +// at top of header. +template<> +class scoped_nsobject { + private: + explicit scoped_nsobject(NSAutoreleasePool* object = nil); + DISALLOW_COPY_AND_ASSIGN(scoped_nsobject); +}; + +} // namespace base + +#endif // BASE_MAC_SCOPED_NSOBJECT_H_ diff --git a/base/mac/scoped_nsobject_unittest.mm b/base/mac/scoped_nsobject_unittest.mm new file mode 100644 index 0000000000..f290c3aef0 --- /dev/null +++ b/base/mac/scoped_nsobject_unittest.mm @@ -0,0 +1,86 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/basictypes.h" +#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/mac/scoped_nsobject.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +TEST(ScopedNSObjectTest, ScopedNSObject) { + base::scoped_nsobject p1([[NSObject alloc] init]); + ASSERT_TRUE(p1.get()); + ASSERT_EQ(1u, [p1 retainCount]); + base::scoped_nsobject p2(p1); + ASSERT_EQ(p1.get(), p2.get()); + ASSERT_EQ(2u, [p1 retainCount]); + p2.reset(); + ASSERT_EQ(nil, p2.get()); + ASSERT_EQ(1u, [p1 retainCount]); + { + base::scoped_nsobject p3 = p1; + ASSERT_EQ(p1.get(), p3.get()); + ASSERT_EQ(2u, [p1 retainCount]); + p3 = p1; + ASSERT_EQ(p1.get(), p3.get()); + ASSERT_EQ(2u, [p1 retainCount]); + } + ASSERT_EQ(1u, [p1 retainCount]); + base::scoped_nsobject p4([p1.get() retain]); + ASSERT_EQ(2u, [p1 retainCount]); + ASSERT_TRUE(p1 == p1.get()); + ASSERT_TRUE(p1 == p1); + ASSERT_FALSE(p1 != p1); + ASSERT_FALSE(p1 != p1.get()); + base::scoped_nsobject p5([[NSObject alloc] init]); + ASSERT_TRUE(p1 != p5); + ASSERT_TRUE(p1 != p5.get()); + ASSERT_FALSE(p1 == p5); + ASSERT_FALSE(p1 == p5.get()); + + base::scoped_nsobject p6 = p1; + ASSERT_EQ(3u, [p6 retainCount]); + { + base::mac::ScopedNSAutoreleasePool pool; + p6.autorelease(); + ASSERT_EQ(nil, p6.get()); + ASSERT_EQ(3u, [p1 retainCount]); + } + ASSERT_EQ(2u, [p1 retainCount]); +} + +TEST(ScopedNSObjectTest, ScopedNSObjectInContainer) { + base::scoped_nsobject p([[NSObject alloc] init]); + ASSERT_TRUE(p.get()); + ASSERT_EQ(1u, [p retainCount]); + { + std::vector> objects; + objects.push_back(p); + ASSERT_EQ(2u, [p retainCount]); + ASSERT_EQ(p.get(), objects[0].get()); + objects.push_back(base::scoped_nsobject([[NSObject alloc] init])); + ASSERT_TRUE(objects[1].get()); + ASSERT_EQ(1u, [objects[1] retainCount]); + } + ASSERT_EQ(1u, [p retainCount]); +} + +TEST(ScopedNSObjectTest, ScopedNSObjectFreeFunctions) { + base::scoped_nsobject p1([[NSObject alloc] init]); + id o1 = p1.get(); + ASSERT_TRUE(o1 == p1); + ASSERT_FALSE(o1 != p1); + base::scoped_nsobject p2([[NSObject alloc] init]); + ASSERT_TRUE(o1 != p2); + ASSERT_FALSE(o1 == p2); + id o2 = p2.get(); + swap(p1, p2); + ASSERT_EQ(o2, p1.get()); + ASSERT_EQ(o1, p2.get()); +} + +} // namespace diff --git a/base/mac/scoped_sending_event.h b/base/mac/scoped_sending_event.h new file mode 100644 index 0000000000..92c2155e15 --- /dev/null +++ b/base/mac/scoped_sending_event.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_MAC_SCOPED_SENDING_EVENT_H_ +#define BASE_MAC_SCOPED_SENDING_EVENT_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/message_loop/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 +- (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* 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/base/mac/scoped_sending_event.mm b/base/mac/scoped_sending_event.mm new file mode 100644 index 0000000000..c3813d8ae6 --- /dev/null +++ b/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*>(NSApp)) { + DCHECK([app_ conformsToProtocol:@protocol(CrAppControlProtocol)]); + handling_ = [app_ isHandlingSendEvent]; + [app_ setHandlingSendEvent:YES]; +} + +ScopedSendingEvent::~ScopedSendingEvent() { + [app_ setHandlingSendEvent:handling_]; +} + +} // namespace mac +} // namespace base diff --git a/base/mac/scoped_sending_event_unittest.mm b/base/mac/scoped_sending_event_unittest.mm new file mode 100644 index 0000000000..9ae99856c9 --- /dev/null +++ b/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 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 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/base/mac/sdk_forward_declarations.h b/base/mac/sdk_forward_declarations.h new file mode 100644 index 0000000000..a2d4013723 --- /dev/null +++ b/base/mac/sdk_forward_declarations.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. + +// This file contains forward declarations for items in later SDKs than the +// default one with which Chromium is built (currently 10.6). +// If you call any function from this header, be sure to check at runtime for +// respondsToSelector: before calling these functions (else your code will crash +// on older OS X versions that chrome still supports). + +#ifndef BASE_MAC_SDK_FORWARD_DECLARATIONS_H_ +#define BASE_MAC_SDK_FORWARD_DECLARATIONS_H_ + +#import + +#if !defined(MAC_OS_X_VERSION_10_7) || \ + MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 +enum { + NSEventPhaseNone = 0, // event not associated with a phase. + NSEventPhaseBegan = 0x1 << 0, + NSEventPhaseStationary = 0x1 << 1, + NSEventPhaseChanged = 0x1 << 2, + NSEventPhaseEnded = 0x1 << 3, + NSEventPhaseCancelled = 0x1 << 4, +}; +typedef NSUInteger NSEventPhase; + +enum { + NSEventSwipeTrackingLockDirection = 0x1 << 0, + NSEventSwipeTrackingClampGestureAmount = 0x1 << 1, +}; +typedef NSUInteger NSEventSwipeTrackingOptions; + +@interface NSEvent (LionSDK) ++ (BOOL)isSwipeTrackingFromScrollEventsEnabled; + +- (NSEventPhase)phase; +- (CGFloat)scrollingDeltaX; +- (CGFloat)scrollingDeltaY; +- (void)trackSwipeEventWithOptions:(NSEventSwipeTrackingOptions)options + dampenAmountThresholdMin:(CGFloat)minDampenThreshold + max:(CGFloat)maxDampenThreshold + usingHandler:(void (^)(CGFloat gestureAmount, + NSEventPhase phase, + BOOL isComplete, + BOOL *stop))trackingHandler; + +- (BOOL)isDirectionInvertedFromDevice; + +@end + +@interface CALayer (LionAPI) +- (CGFloat)contentsScale; +- (void)setContentsScale:(CGFloat)contentsScale; +@end + +@interface NSScreen (LionSDK) +- (CGFloat)backingScaleFactor; +- (NSRect)convertRectToBacking:(NSRect)aRect; +@end + +@interface NSWindow (LionSDK) +- (CGFloat)backingScaleFactor; +@end +#endif // MAC_OS_X_VERSION_10_7 + +#endif // BASE_MAC_SDK_FORWARD_DECLARATIONS_H_ diff --git a/base/md5.cc b/base/md5.cc new file mode 100644 index 0000000000..754994c0e5 --- /dev/null +++ b/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<>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +void MD5Transform(uint32 buf[4], const uint32 in[16]) { + register uint32 a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[ 0]+0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[ 1]+0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[ 2]+0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[ 3]+0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[ 4]+0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[ 5]+0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[ 6]+0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[ 7]+0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[ 8]+0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[ 9]+0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10]+0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11]+0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12]+0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13]+0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14]+0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15]+0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[ 1]+0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[ 6]+0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11]+0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[ 0]+0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[ 5]+0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10]+0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15]+0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[ 4]+0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[ 9]+0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14]+0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[ 3]+0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[ 8]+0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13]+0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[ 2]+0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[ 7]+0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12]+0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[ 5]+0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[ 8]+0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11]+0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14]+0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[ 1]+0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[ 4]+0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[ 7]+0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10]+0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13]+0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[ 0]+0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[ 3]+0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[ 6]+0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[ 9]+0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12]+0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15]+0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[ 2]+0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[ 0]+0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[ 7]+0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14]+0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[ 5]+0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12]+0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[ 3]+0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10]+0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[ 1]+0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[ 8]+0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15]+0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[ 6]+0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13]+0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[ 4]+0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11]+0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[ 2]+0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[ 9]+0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +} // namespace + +namespace base { + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void MD5Init(MD5Context* context) { + struct Context *ctx = (struct Context *)context; + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void MD5Update(MD5Context* context, const StringPiece& data) { + const unsigned char* inbuf = (const unsigned char*)data.data(); + size_t len = data.size(); + struct Context *ctx = (struct Context *)context; + const unsigned char* buf = (const unsigned char*)inbuf; + uint32 t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((uint32)len << 3)) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += static_cast(len >> 29); + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if (t) { + unsigned char *p = (unsigned char *)ctx->in + t; + + t = 64-t; + if (len < t) { + memcpy(p, buf, len); + return; + } + memcpy(p, buf, t); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32 *)ctx->in); + buf += t; + len -= t; + } + + /* Process data in 64-byte chunks */ + + while (len >= 64) { + memcpy(ctx->in, buf, 64); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32 *)ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + + memcpy(ctx->in, buf, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void MD5Final(MD5Digest* digest, MD5Context* context) { + struct Context *ctx = (struct Context *)context; + unsigned count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset(p, 0, count); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32 *)ctx->in); + + /* Now fill the next block with 56 bytes */ + memset(ctx->in, 0, 56); + } else { + /* Pad block to 56 bytes */ + memset(p, 0, count-8); + } + byteReverse(ctx->in, 14); + + /* Append length in bits and transform */ + ((uint32 *)ctx->in)[ 14 ] = ctx->bits[0]; + ((uint32 *)ctx->in)[ 15 ] = ctx->bits[1]; + + MD5Transform(ctx->buf, (uint32 *)ctx->in); + byteReverse((unsigned char *)ctx->buf, 4); + memcpy(digest->a, ctx->buf, 16); + memset(ctx, 0, sizeof(*ctx)); /* In case it's sensitive */ +} + +std::string MD5DigestToBase16(const MD5Digest& digest) { + static char const zEncode[] = "0123456789abcdef"; + + std::string ret; + ret.resize(32); + + int j = 0; + for (int i = 0; i < 16; i ++) { + int a = digest.a[i]; + ret[j++] = zEncode[(a>>4)&0xf]; + ret[j++] = zEncode[a & 0xf]; + } + return ret; +} + +void MD5Sum(const void* data, size_t length, MD5Digest* digest) { + MD5Context ctx; + MD5Init(&ctx); + MD5Update(&ctx, + StringPiece(reinterpret_cast(data), length)); + MD5Final(digest, &ctx); +} + +std::string MD5String(const StringPiece& str) { + MD5Digest digest; + MD5Sum(str.data(), str.length(), &digest); + return MD5DigestToBase16(digest); +} + +} // namespace base diff --git a/base/md5.h b/base/md5.h new file mode 100644 index 0000000000..fba02bd116 --- /dev/null +++ b/base/md5.h @@ -0,0 +1,69 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MD5_H_ +#define BASE_MD5_H_ + +#include "base/base_export.h" +#include "base/strings/string_piece.h" + +namespace base { + +// MD5 stands for Message Digest algorithm 5. +// MD5 is a robust hash function, designed for cyptography, but often used +// for file checksums. The code is complex and slow, but has few +// collisions. +// See Also: +// http://en.wikipedia.org/wiki/MD5 + +// These functions perform MD5 operations. The simplest call is MD5Sum() to +// generate the MD5 sum of the given data. +// +// You can also compute the MD5 sum of data incrementally by making multiple +// calls to MD5Update(): +// MD5Context ctx; // intermediate MD5 data: do not use +// MD5Init(&ctx); +// MD5Update(&ctx, data1, length1); +// MD5Update(&ctx, data2, length2); +// ... +// +// MD5Digest digest; // the result of the computation +// MD5Final(&digest, &ctx); +// +// You can call MD5DigestToBase16() to generate a string of the digest. + +// The output of an MD5 operation. +struct MD5Digest { + unsigned char a[16]; +}; + +// Used for storing intermediate data during an MD5 computation. Callers +// should not access the data. +typedef char MD5Context[88]; + +// Computes the MD5 sum of the given data buffer with the given length. +// The given 'digest' structure will be filled with the result data. +BASE_EXPORT void MD5Sum(const void* data, size_t length, MD5Digest* digest); + +// Initializes the given MD5 context structure for subsequent calls to +// MD5Update(). +BASE_EXPORT void MD5Init(MD5Context* context); + +// For the given buffer of |data| as a StringPiece, updates the given MD5 +// context with the sum of the data. You can call this any number of times +// during the computation, except that MD5Init() must have been called first. +BASE_EXPORT void MD5Update(MD5Context* context, const StringPiece& data); + +// Finalizes the MD5 operation and fills the buffer with the digest. +BASE_EXPORT void MD5Final(MD5Digest* digest, MD5Context* context); + +// Converts a digest into human-readable hexadecimal. +BASE_EXPORT std::string MD5DigestToBase16(const MD5Digest& digest); + +// Returns the MD5 (in hexadecimal) of a string. +BASE_EXPORT std::string MD5String(const StringPiece& str); + +} // namespace base + +#endif // BASE_MD5_H_ diff --git a/base/md5_unittest.cc b/base/md5_unittest.cc new file mode 100644 index 0000000000..1112c4b425 --- /dev/null +++ b/base/md5_unittest.cc @@ -0,0 +1,207 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/md5.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +TEST(MD5, DigestToBase16) { + MD5Digest digest; + + int data[] = { + 0xd4, 0x1d, 0x8c, 0xd9, + 0x8f, 0x00, 0xb2, 0x04, + 0xe9, 0x80, 0x09, 0x98, + 0xec, 0xf8, 0x42, 0x7e + }; + + for (int i = 0; i < 16; ++i) + digest.a[i] = data[i] & 0xff; + + std::string actual = MD5DigestToBase16(digest); + std::string expected = "d41d8cd98f00b204e9800998ecf8427e"; + + EXPECT_EQ(expected, actual); +} + +TEST(MD5, MD5SumEmtpyData) { + MD5Digest digest; + const char* data = ""; + + MD5Sum(data, strlen(data), &digest); + + int expected[] = { + 0xd4, 0x1d, 0x8c, 0xd9, + 0x8f, 0x00, 0xb2, 0x04, + 0xe9, 0x80, 0x09, 0x98, + 0xec, 0xf8, 0x42, 0x7e + }; + + for (int i = 0; i < 16; ++i) + EXPECT_EQ(expected[i], digest.a[i] & 0xFF); +} + +TEST(MD5, MD5SumOneByteData) { + MD5Digest digest; + const char* data = "a"; + + MD5Sum(data, strlen(data), &digest); + + int expected[] = { + 0x0c, 0xc1, 0x75, 0xb9, + 0xc0, 0xf1, 0xb6, 0xa8, + 0x31, 0xc3, 0x99, 0xe2, + 0x69, 0x77, 0x26, 0x61 + }; + + for (int i = 0; i < 16; ++i) + EXPECT_EQ(expected[i], digest.a[i] & 0xFF); +} + +TEST(MD5, MD5SumLongData) { + const int length = 10 * 1024 * 1024 + 1; + scoped_ptr data(new char[length]); + + for (int i = 0; i < length; ++i) + data[i] = i & 0xFF; + + MD5Digest digest; + MD5Sum(data.get(), length, &digest); + + int expected[] = { + 0x90, 0xbd, 0x6a, 0xd9, + 0x0a, 0xce, 0xf5, 0xad, + 0xaa, 0x92, 0x20, 0x3e, + 0x21, 0xc7, 0xa1, 0x3e + }; + + for (int i = 0; i < 16; ++i) + EXPECT_EQ(expected[i], digest.a[i] & 0xFF); +} + +TEST(MD5, ContextWithEmptyData) { + MD5Context ctx; + MD5Init(&ctx); + + MD5Digest digest; + MD5Final(&digest, &ctx); + + int expected[] = { + 0xd4, 0x1d, 0x8c, 0xd9, + 0x8f, 0x00, 0xb2, 0x04, + 0xe9, 0x80, 0x09, 0x98, + 0xec, 0xf8, 0x42, 0x7e + }; + + for (int i = 0; i < 16; ++i) + EXPECT_EQ(expected[i], digest.a[i] & 0xFF); +} + +TEST(MD5, ContextWithLongData) { + MD5Context ctx; + MD5Init(&ctx); + + const int length = 10 * 1024 * 1024 + 1; + scoped_ptr data(new char[length]); + + for (int i = 0; i < length; ++i) + data[i] = i & 0xFF; + + int total = 0; + while (total < length) { + int len = 4097; // intentionally not 2^k. + if (len > length - total) + len = length - total; + + MD5Update(&ctx, + StringPiece(reinterpret_cast(data.get() + total), len)); + total += len; + } + + EXPECT_EQ(length, total); + + MD5Digest digest; + MD5Final(&digest, &ctx); + + int expected[] = { + 0x90, 0xbd, 0x6a, 0xd9, + 0x0a, 0xce, 0xf5, 0xad, + 0xaa, 0x92, 0x20, 0x3e, + 0x21, 0xc7, 0xa1, 0x3e + }; + + for (int i = 0; i < 16; ++i) + EXPECT_EQ(expected[i], digest.a[i] & 0xFF); +} + +// Example data from http://www.ietf.org/rfc/rfc1321.txt A.5 Test Suite +TEST(MD5, MD5StringTestSuite1) { + std::string actual = MD5String(""); + std::string expected = "d41d8cd98f00b204e9800998ecf8427e"; + EXPECT_EQ(expected, actual); +} + +TEST(MD5, MD5StringTestSuite2) { + std::string actual = MD5String("a"); + std::string expected = "0cc175b9c0f1b6a831c399e269772661"; + EXPECT_EQ(expected, actual); +} + +TEST(MD5, MD5StringTestSuite3) { + std::string actual = MD5String("abc"); + std::string expected = "900150983cd24fb0d6963f7d28e17f72"; + EXPECT_EQ(expected, actual); +} + +TEST(MD5, MD5StringTestSuite4) { + std::string actual = MD5String("message digest"); + std::string expected = "f96b697d7cb7938d525a2f31aaf161d0"; + EXPECT_EQ(expected, actual); +} + +TEST(MD5, MD5StringTestSuite5) { + std::string actual = MD5String("abcdefghijklmnopqrstuvwxyz"); + std::string expected = "c3fcd3d76192e4007dfb496cca67e13b"; + EXPECT_EQ(expected, actual); +} + +TEST(MD5, MD5StringTestSuite6) { + std::string actual = MD5String("ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789"); + std::string expected = "d174ab98d277d9f5a5611c2c9f419d9f"; + EXPECT_EQ(expected, actual); +} + +TEST(MD5, MD5StringTestSuite7) { + std::string actual = MD5String("12345678901234567890" + "12345678901234567890" + "12345678901234567890" + "12345678901234567890"); + std::string expected = "57edf4a22be3c955ac49da2e2107b67a"; + EXPECT_EQ(expected, actual); +} + +TEST(MD5, ContextWithStringData) { + MD5Context ctx; + MD5Init(&ctx); + + MD5Update(&ctx, "abc"); + + MD5Digest digest; + MD5Final(&digest, &ctx); + + std::string actual = MD5DigestToBase16(digest); + std::string expected = "900150983cd24fb0d6963f7d28e17f72"; + + EXPECT_EQ(expected, actual); +} + +} // namespace base diff --git a/base/memory/aligned_memory.cc b/base/memory/aligned_memory.cc new file mode 100644 index 0000000000..b6278aba2c --- /dev/null +++ b/base/memory/aligned_memory.cc @@ -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. + +#include "base/memory/aligned_memory.h" + +#include "base/logging.h" + +#if defined(OS_ANDROID) || defined(OS_NACL) +#include +#endif + +namespace base { + +void* AlignedAlloc(size_t size, size_t alignment) { + DCHECK_GT(size, 0U); + DCHECK_EQ(alignment & (alignment - 1), 0U); + DCHECK_EQ(alignment % sizeof(void*), 0U); + void* ptr = NULL; +#if defined(COMPILER_MSVC) + ptr = _aligned_malloc(size, alignment); +// Both Android and NaCl technically support posix_memalign(), but do not expose +// it in the current version of the library headers used by Chrome. Luckily, +// memalign() on both platforms returns pointers which can safely be used with +// free(), so we can use it instead. Issues filed with each project for docs: +// http://code.google.com/p/android/issues/detail?id=35391 +// http://code.google.com/p/chromium/issues/detail?id=138579 +#elif defined(OS_ANDROID) || defined(OS_NACL) + ptr = memalign(alignment, size); +#else + if (posix_memalign(&ptr, alignment, size)) + ptr = NULL; +#endif + // Since aligned allocations may fail for non-memory related reasons, force a + // crash if we encounter a failed allocation; maintaining consistent behavior + // with a normal allocation failure in Chrome. + if (!ptr) { + DLOG(ERROR) << "If you crashed here, your aligned allocation is incorrect: " + << "size=" << size << ", alignment=" << alignment; + CHECK(false); + } + // Sanity check alignment just to be safe. + DCHECK_EQ(reinterpret_cast(ptr) & (alignment - 1), 0U); + return ptr; +} + +} // namespace base diff --git a/base/memory/aligned_memory.h b/base/memory/aligned_memory.h new file mode 100644 index 0000000000..6719599dc5 --- /dev/null +++ b/base/memory/aligned_memory.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. + +// AlignedMemory is a POD type that gives you a portable way to specify static +// or local stack data of a given alignment and size. For example, if you need +// static storage for a class, but you want manual control over when the object +// is constructed and destructed (you don't want static initialization and +// destruction), use AlignedMemory: +// +// static AlignedMemory my_class; +// +// // ... at runtime: +// new(my_class.void_data()) MyClass(); +// +// // ... use it: +// MyClass* mc = my_class.data_as(); +// +// // ... later, to destruct my_class: +// my_class.data_as()->MyClass::~MyClass(); +// +// Alternatively, a runtime sized aligned allocation can be created: +// +// float* my_array = static_cast(AlignedAlloc(size, alignment)); +// +// // ... later, to release the memory: +// AlignedFree(my_array); +// +// Or using scoped_ptr_malloc: +// +// scoped_ptr_malloc my_array( +// static_cast(AlignedAlloc(size, alignment))); + +#ifndef BASE_MEMORY_ALIGNED_MEMORY_H_ +#define BASE_MEMORY_ALIGNED_MEMORY_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/compiler_specific.h" + +#if defined(COMPILER_MSVC) +#include +#else +#include +#endif + +namespace base { + +// AlignedMemory is specialized for all supported alignments. +// Make sure we get a compiler error if someone uses an unsupported alignment. +template +struct AlignedMemory {}; + +#define BASE_DECL_ALIGNED_MEMORY(byte_alignment) \ + template \ + class AlignedMemory { \ + public: \ + ALIGNAS(byte_alignment) uint8 data_[Size]; \ + void* void_data() { return static_cast(data_); } \ + const void* void_data() const { \ + return static_cast(data_); \ + } \ + template \ + Type* data_as() { return static_cast(void_data()); } \ + template \ + const Type* data_as() const { \ + return static_cast(void_data()); \ + } \ + private: \ + void* operator new(size_t); \ + void operator delete(void*); \ + } + +// Specialization for all alignments is required because MSVC (as of VS 2008) +// does not understand ALIGNAS(ALIGNOF(Type)) or ALIGNAS(template_param). +// Greater than 4096 alignment is not supported by some compilers, so 4096 is +// the maximum specified here. +BASE_DECL_ALIGNED_MEMORY(1); +BASE_DECL_ALIGNED_MEMORY(2); +BASE_DECL_ALIGNED_MEMORY(4); +BASE_DECL_ALIGNED_MEMORY(8); +BASE_DECL_ALIGNED_MEMORY(16); +BASE_DECL_ALIGNED_MEMORY(32); +BASE_DECL_ALIGNED_MEMORY(64); +BASE_DECL_ALIGNED_MEMORY(128); +BASE_DECL_ALIGNED_MEMORY(256); +BASE_DECL_ALIGNED_MEMORY(512); +BASE_DECL_ALIGNED_MEMORY(1024); +BASE_DECL_ALIGNED_MEMORY(2048); +BASE_DECL_ALIGNED_MEMORY(4096); + +#undef BASE_DECL_ALIGNED_MEMORY + +BASE_EXPORT void* AlignedAlloc(size_t size, size_t alignment); + +inline void AlignedFree(void* ptr) { +#if defined(COMPILER_MSVC) + _aligned_free(ptr); +#else + free(ptr); +#endif +} + +// Helper class for use with scoped_ptr_malloc. +class BASE_EXPORT ScopedPtrAlignedFree { + public: + inline void operator()(void* ptr) const { + AlignedFree(ptr); + } +}; + +} // namespace base + +#endif // BASE_MEMORY_ALIGNED_MEMORY_H_ diff --git a/base/memory/aligned_memory_unittest.cc b/base/memory/aligned_memory_unittest.cc new file mode 100644 index 0000000000..6942249f5a --- /dev/null +++ b/base/memory/aligned_memory_unittest.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/memory/aligned_memory.h" +#include "base/memory/scoped_ptr.h" +#include "testing/gtest/include/gtest/gtest.h" + +#define EXPECT_ALIGNED(ptr, align) \ + EXPECT_EQ(0u, reinterpret_cast(ptr) & (align - 1)) + +namespace { + +using base::AlignedMemory; + +TEST(AlignedMemoryTest, StaticAlignment) { + static AlignedMemory<8, 8> raw8; + static AlignedMemory<8, 16> raw16; + static AlignedMemory<8, 256> raw256; + static AlignedMemory<8, 4096> raw4096; + + EXPECT_EQ(8u, ALIGNOF(raw8)); + EXPECT_EQ(16u, ALIGNOF(raw16)); + EXPECT_EQ(256u, ALIGNOF(raw256)); + EXPECT_EQ(4096u, ALIGNOF(raw4096)); + + EXPECT_ALIGNED(raw8.void_data(), 8); + EXPECT_ALIGNED(raw16.void_data(), 16); + EXPECT_ALIGNED(raw256.void_data(), 256); + EXPECT_ALIGNED(raw4096.void_data(), 4096); +} + +TEST(AlignedMemoryTest, StackAlignment) { + AlignedMemory<8, 8> raw8; + AlignedMemory<8, 16> raw16; + AlignedMemory<8, 128> raw128; + + EXPECT_EQ(8u, ALIGNOF(raw8)); + EXPECT_EQ(16u, ALIGNOF(raw16)); + EXPECT_EQ(128u, ALIGNOF(raw128)); + + EXPECT_ALIGNED(raw8.void_data(), 8); + EXPECT_ALIGNED(raw16.void_data(), 16); + EXPECT_ALIGNED(raw128.void_data(), 128); + + // NaCl x86-64 compiler emits non-validating instructions for >128 + // bytes alignment. + // http://www.chromium.org/nativeclient/design-documents/nacl-sfi-model-on-x86-64-systems + // TODO(hamaji): Ideally, NaCl compiler for x86-64 should workaround + // this limitation and this #if should be removed. + // https://code.google.com/p/nativeclient/issues/detail?id=3463 +#if !(defined(OS_NACL) && defined(ARCH_CPU_X86_64)) + AlignedMemory<8, 256> raw256; + EXPECT_EQ(256u, ALIGNOF(raw256)); + EXPECT_ALIGNED(raw256.void_data(), 256); + + // TODO(ios): This test hits an armv7 bug in clang. crbug.com/138066 +#if !(defined(OS_IOS) && defined(ARCH_CPU_ARM_FAMILY)) + AlignedMemory<8, 4096> raw4096; + EXPECT_EQ(4096u, ALIGNOF(raw4096)); + EXPECT_ALIGNED(raw4096.void_data(), 4096); +#endif // !(defined(OS_IOS) && defined(ARCH_CPU_ARM_FAMILY)) +#endif +} + +TEST(AlignedMemoryTest, DynamicAllocation) { + void* p = base::AlignedAlloc(8, 8); + EXPECT_TRUE(p); + EXPECT_ALIGNED(p, 8); + base::AlignedFree(p); + + p = base::AlignedAlloc(8, 16); + EXPECT_TRUE(p); + EXPECT_ALIGNED(p, 16); + base::AlignedFree(p); + + p = base::AlignedAlloc(8, 256); + EXPECT_TRUE(p); + EXPECT_ALIGNED(p, 256); + base::AlignedFree(p); + + p = base::AlignedAlloc(8, 4096); + EXPECT_TRUE(p); + EXPECT_ALIGNED(p, 4096); + base::AlignedFree(p); +} + +TEST(AlignedMemoryTest, ScopedDynamicAllocation) { + scoped_ptr_malloc p( + static_cast(base::AlignedAlloc(8, 8))); + EXPECT_TRUE(p.get()); + EXPECT_ALIGNED(p.get(), 8); +} + +} // namespace diff --git a/base/memory/discardable_memory.cc b/base/memory/discardable_memory.cc new file mode 100644 index 0000000000..33f9e19cde --- /dev/null +++ b/base/memory/discardable_memory.cc @@ -0,0 +1,66 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/discardable_memory.h" + +#include "base/logging.h" + +namespace base { + +DiscardableMemory::DiscardableMemory() + : memory_(NULL), + size_(0), + is_locked_(false) +#if defined(OS_ANDROID) + , fd_(-1) +#endif // OS_ANDROID + { + DCHECK(Supported()); +} + +void* DiscardableMemory::Memory() const { + DCHECK(is_locked_); + return memory_; +} + +// Stub implementations for platforms that don't support discardable memory. + +#if !defined(OS_ANDROID) && !defined(OS_MACOSX) + +DiscardableMemory::~DiscardableMemory() { + NOTIMPLEMENTED(); +} + +// static +bool DiscardableMemory::Supported() { + return false; +} + +bool DiscardableMemory::InitializeAndLock(size_t size) { + NOTIMPLEMENTED(); + return false; +} + +LockDiscardableMemoryStatus DiscardableMemory::Lock() { + NOTIMPLEMENTED(); + return DISCARDABLE_MEMORY_FAILED; +} + +void DiscardableMemory::Unlock() { + NOTIMPLEMENTED(); +} + +// static +bool DiscardableMemory::PurgeForTestingSupported() { + return false; +} + +// static +void DiscardableMemory::PurgeForTesting() { + NOTIMPLEMENTED(); +} + +#endif // OS_* + +} // namespace base diff --git a/base/memory/discardable_memory.h b/base/memory/discardable_memory.h new file mode 100644 index 0000000000..d6e91b979d --- /dev/null +++ b/base/memory/discardable_memory.h @@ -0,0 +1,118 @@ +// 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_MEMORY_DISCARDABLE_MEMORY_H_ +#define BASE_MEMORY_DISCARDABLE_MEMORY_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/compiler_specific.h" + +namespace base { + +enum LockDiscardableMemoryStatus { + DISCARDABLE_MEMORY_FAILED = -1, + DISCARDABLE_MEMORY_PURGED = 0, + DISCARDABLE_MEMORY_SUCCESS = 1 +}; + +// Platform abstraction for discardable memory. DiscardableMemory is used to +// cache large objects without worrying about blowing out memory, both on mobile +// devices where there is no swap, and desktop devices where unused free memory +// should be used to help the user experience. This is preferable to releasing +// memory in response to an OOM signal because it is simpler, though it has less +// flexibility as to which objects get discarded. +// +// Discardable memory has two states: locked and unlocked. While the memory is +// locked, it will not be discarded. Unlocking the memory allows the OS to +// reclaim it if needed. Locks do not nest. +// +// Notes: +// - The paging behavior of memory while it is locked is not specified. While +// mobile platforms will not swap it out, it may qualify for swapping +// on desktop platforms. It is not expected that this will matter, as the +// preferred pattern of usage for DiscardableMemory is to lock down the +// memory, use it as quickly as possible, and then unlock it. +// - Because of memory alignment, the amount of memory allocated can be +// larger than the requested memory size. It is not very efficient for +// small allocations. +// +// References: +// - Linux: http://lwn.net/Articles/452035/ +// - Mac: http://trac.webkit.org/browser/trunk/Source/WebCore/platform/mac/PurgeableBufferMac.cpp +// the comment starting with "vm_object_purgable_control" at +// http://www.opensource.apple.com/source/xnu/xnu-792.13.8/osfmk/vm/vm_object.c +class BASE_EXPORT DiscardableMemory { + public: + DiscardableMemory(); + + // If the discardable memory is locked, the destructor will unlock it. + // The opened file will also be closed after this. + ~DiscardableMemory(); + + // Check whether the system supports discardable memory. + static bool Supported(); + + // Initialize the DiscardableMemory object. On success, this function returns + // true and the memory is locked. This should only be called once. + // This call could fail because of platform-specific limitations and the user + // should stop using the DiscardableMemory afterwards. + bool InitializeAndLock(size_t size); + + // Lock the memory so that it will not be purged by the system. Returns + // DISCARDABLE_MEMORY_SUCCESS on success. If the return value is + // DISCARDABLE_MEMORY_FAILED then this object should be discarded and + // a new one should be created. If the return value is + // DISCARDABLE_MEMORY_PURGED then the memory is present but any data that + // was in it is gone. + LockDiscardableMemoryStatus Lock() WARN_UNUSED_RESULT; + + // Unlock the memory so that it can be purged by the system. Must be called + // after every successful lock call. + void Unlock(); + + // Return the memory address held by this object. The object must be locked + // before calling this. Otherwise, this will cause a DCHECK error. + void* Memory() const; + + // Testing utility calls. + + // Check whether a purge of all discardable memory in the system is supported. + // Use only for testing! + static bool PurgeForTestingSupported(); + + // Purge all discardable memory in the system. This call has global effects + // across all running processes, so it should only be used for testing! + static void PurgeForTesting(); + + private: +#if defined(OS_ANDROID) + // Maps the discardable memory into the caller's address space. + // Returns true on success, false otherwise. + bool Map(); + + // Unmaps the discardable memory from the caller's address space. + void Unmap(); + + // Reserve a file descriptor. When reaching the fd limit, this call returns + // false and initialization should fail. + bool ReserveFileDescriptor(); + + // Release a file descriptor so that others can reserve it. + void ReleaseFileDescriptor(); +#endif // OS_ANDROID + + void* memory_; + size_t size_; + bool is_locked_; +#if defined(OS_ANDROID) + int fd_; +#endif // OS_ANDROID + + DISALLOW_COPY_AND_ASSIGN(DiscardableMemory); +}; + +} // namespace base + +#endif // BASE_MEMORY_DISCARDABLE_MEMORY_H_ diff --git a/base/memory/discardable_memory_android.cc b/base/memory/discardable_memory_android.cc new file mode 100644 index 0000000000..73a25ae419 --- /dev/null +++ b/base/memory/discardable_memory_android.cc @@ -0,0 +1,164 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/discardable_memory.h" + +#include +#include + +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/synchronization/lock.h" +#include "third_party/ashmem/ashmem.h" + +namespace { + +base::LazyInstance::Leaky g_discardable_memory_lock = + LAZY_INSTANCE_INITIALIZER; + +// Total number of discardable memory in the process. +int g_num_discardable_memory = 0; + +// Upper limit on the number of discardable memory to avoid hitting file +// descriptor limit. +const int kDiscardableMemoryNumLimit = 128; + +} + +namespace base { + +// static +bool DiscardableMemory::Supported() { + return true; +} + +DiscardableMemory::~DiscardableMemory() { + if (is_locked_) + Unlock(); + // If fd_ is smaller than 0, initialization must have failed and + // g_num_discardable_memory is not incremented by the caller. + if (fd_ < 0) + return; + HANDLE_EINTR(close(fd_)); + fd_ = -1; + ReleaseFileDescriptor(); +} + +bool DiscardableMemory::ReserveFileDescriptor() { + base::AutoLock lock(g_discardable_memory_lock.Get()); + if (g_num_discardable_memory < kDiscardableMemoryNumLimit) { + ++g_num_discardable_memory; + return true; + } + return false; +} + +void DiscardableMemory::ReleaseFileDescriptor() { + base::AutoLock lock(g_discardable_memory_lock.Get()); + --g_num_discardable_memory; + DCHECK_LE(0, g_num_discardable_memory); +} + +bool DiscardableMemory::InitializeAndLock(size_t size) { + // When this function returns true, fd_ should be larger or equal than 0 + // and g_num_discardable_memory is incremented by 1. Otherwise, fd_ + // is less than 0 and g_num_discardable_memory is not incremented by + // the caller. + DCHECK_EQ(fd_, -1); + DCHECK(!memory_); + if (!ReserveFileDescriptor()) + return false; + + size_ = size; + fd_ = ashmem_create_region("", size); + + if (fd_ < 0) { + DLOG(ERROR) << "ashmem_create_region() failed"; + ReleaseFileDescriptor(); + return false; + } + + int err = ashmem_set_prot_region(fd_, PROT_READ | PROT_WRITE); + if (err < 0) { + DLOG(ERROR) << "Error " << err << " when setting protection of ashmem"; + HANDLE_EINTR(close(fd_)); + fd_ = -1; + ReleaseFileDescriptor(); + return false; + } + + if (!Map()) { + // Close the file descriptor in case of any initialization errors. + HANDLE_EINTR(close(fd_)); + fd_ = -1; + ReleaseFileDescriptor(); + return false; + } + + is_locked_ = true; + return true; +} + +LockDiscardableMemoryStatus DiscardableMemory::Lock() { + DCHECK_NE(fd_, -1); + DCHECK(!is_locked_); + + bool purged = false; + if (ashmem_pin_region(fd_, 0, 0) == ASHMEM_WAS_PURGED) + purged = true; + + if (!Map()) + return DISCARDABLE_MEMORY_FAILED; + + is_locked_ = true; + return purged ? DISCARDABLE_MEMORY_PURGED : DISCARDABLE_MEMORY_SUCCESS; +} + +void DiscardableMemory::Unlock() { + DCHECK_GE(fd_, 0); + DCHECK(is_locked_); + + Unmap(); + if (ashmem_unpin_region(fd_, 0, 0)) + DLOG(ERROR) << "Failed to unpin memory."; + is_locked_ = false; +} + +bool DiscardableMemory::Map() { + DCHECK(!memory_); + // There is a problem using MAP_PRIVATE here. As we are constantly calling + // Lock() and Unlock(), data could get lost if they are not written to the + // underlying file when Unlock() gets called. + memory_ = mmap(NULL, size_, PROT_READ | PROT_WRITE, MAP_SHARED, fd_, 0); + if (memory_ == (void*)-1) { + DPLOG(ERROR) << "Failed to map memory."; + memory_ = NULL; + if (ashmem_unpin_region(fd_, 0, 0)) + DLOG(ERROR) << "Failed to unpin memory."; + return false; + } + return true; +} + +void DiscardableMemory::Unmap() { + DCHECK(memory_); + + if (-1 == munmap(memory_, size_)) + DPLOG(ERROR) << "Failed to unmap memory."; + + memory_ = NULL; +} + +// static +bool DiscardableMemory::PurgeForTestingSupported() { + return false; +} + +// static +void DiscardableMemory::PurgeForTesting() { + NOTIMPLEMENTED(); +} + +} // namespace base diff --git a/base/memory/discardable_memory_mac.cc b/base/memory/discardable_memory_mac.cc new file mode 100644 index 0000000000..1dbb6ad1fd --- /dev/null +++ b/base/memory/discardable_memory_mac.cc @@ -0,0 +1,102 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/discardable_memory.h" + +#include + +#include "base/logging.h" + +namespace base { + +namespace { + +// The VM subsystem allows tagging of memory and 240-255 is reserved for +// application use (see mach/vm_statistics.h). Pick 252 (after chromium's atomic +// weight of ~52). +const int kDiscardableMemoryTag = VM_MAKE_TAG(252); + +} // namespace + +// static +bool DiscardableMemory::Supported() { + return true; +} + +DiscardableMemory::~DiscardableMemory() { + if (memory_) { + vm_deallocate(mach_task_self(), + reinterpret_cast(memory_), + size_); + } +} + +bool DiscardableMemory::InitializeAndLock(size_t size) { + DCHECK(!memory_); + size_ = size; + + vm_address_t buffer = 0; + kern_return_t ret = vm_allocate(mach_task_self(), + &buffer, + size, + VM_FLAGS_PURGABLE | + VM_FLAGS_ANYWHERE | + kDiscardableMemoryTag); + + if (ret != KERN_SUCCESS) { + DLOG(ERROR) << "vm_allocate() failed"; + return false; + } + + is_locked_ = true; + memory_ = reinterpret_cast(buffer); + return true; +} + +LockDiscardableMemoryStatus DiscardableMemory::Lock() { + DCHECK(!is_locked_); + + int state = VM_PURGABLE_NONVOLATILE; + kern_return_t ret = vm_purgable_control( + mach_task_self(), + reinterpret_cast(memory_), + VM_PURGABLE_SET_STATE, + &state); + + if (ret != KERN_SUCCESS) + return DISCARDABLE_MEMORY_FAILED; + + is_locked_ = true; + return state & VM_PURGABLE_EMPTY ? DISCARDABLE_MEMORY_PURGED + : DISCARDABLE_MEMORY_SUCCESS; +} + +void DiscardableMemory::Unlock() { + DCHECK(is_locked_); + + int state = VM_PURGABLE_VOLATILE | VM_VOLATILE_GROUP_DEFAULT; + kern_return_t ret = vm_purgable_control( + mach_task_self(), + reinterpret_cast(memory_), + VM_PURGABLE_SET_STATE, + &state); + + if (ret != KERN_SUCCESS) + DLOG(ERROR) << "Failed to unlock memory."; + + is_locked_ = false; +} + +// static +bool DiscardableMemory::PurgeForTestingSupported() { + return true; +} + +// static +void DiscardableMemory::PurgeForTesting() { + int state = 0; + vm_purgable_control(mach_task_self(), 0, VM_PURGABLE_PURGE_ALL, &state); +} + +} // namespace base diff --git a/base/memory/discardable_memory_unittest.cc b/base/memory/discardable_memory_unittest.cc new file mode 100644 index 0000000000..60d35820fb --- /dev/null +++ b/base/memory/discardable_memory_unittest.cc @@ -0,0 +1,61 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/discardable_memory.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +#if defined(OS_ANDROID) || defined(OS_MACOSX) +// Test Lock() and Unlock() functionalities. +TEST(DiscardableMemoryTest, LockAndUnLock) { + ASSERT_TRUE(DiscardableMemory::Supported()); + + const size_t size = 1024; + + DiscardableMemory memory; + ASSERT_TRUE(memory.InitializeAndLock(size)); + void* addr = memory.Memory(); + ASSERT_NE(static_cast(NULL), addr); + + memory.Unlock(); + // The system should have no reason to purge discardable blocks in this brief + // interval, though technically speaking this might flake. + EXPECT_EQ(DISCARDABLE_MEMORY_SUCCESS, memory.Lock()); + addr = memory.Memory(); + ASSERT_NE(static_cast(NULL), addr); + + memory.Unlock(); +} + +// Test delete a discardable memory while it is locked. +TEST(DiscardableMemoryTest, DeleteWhileLocked) { + ASSERT_TRUE(DiscardableMemory::Supported()); + + const size_t size = 1024; + + DiscardableMemory memory; + ASSERT_TRUE(memory.InitializeAndLock(size)); +} + +#if defined(OS_MACOSX) +// Test forced purging. +TEST(DiscardableMemoryTest, Purge) { + ASSERT_TRUE(DiscardableMemory::Supported()); + ASSERT_TRUE(DiscardableMemory::PurgeForTestingSupported()); + + const size_t size = 1024; + + DiscardableMemory memory; + ASSERT_TRUE(memory.InitializeAndLock(size)); + memory.Unlock(); + + DiscardableMemory::PurgeForTesting(); + EXPECT_EQ(DISCARDABLE_MEMORY_PURGED, memory.Lock()); +} +#endif // OS_MACOSX + +#endif // OS_* + +} diff --git a/base/memory/linked_ptr.h b/base/memory/linked_ptr.h new file mode 100644 index 0000000000..80044add81 --- /dev/null +++ b/base/memory/linked_ptr.h @@ -0,0 +1,181 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// A "smart" pointer type with reference tracking. Every pointer to a +// particular object is kept on a circular linked list. When the last pointer +// to an object is destroyed or reassigned, the object is deleted. +// +// Used properly, this deletes the object when the last reference goes away. +// There are several caveats: +// - Like all reference counting schemes, cycles lead to leaks. +// - Each smart pointer is actually two pointers (8 bytes instead of 4). +// - Every time a pointer is released, the entire list of pointers to that +// object is traversed. This class is therefore NOT SUITABLE when there +// will often be more than two or three pointers to a particular object. +// - References are only tracked as long as linked_ptr<> objects are copied. +// If a linked_ptr<> is converted to a raw pointer and back, BAD THINGS +// will happen (double deletion). +// +// A good use of this class is storing object references in STL containers. +// You can safely put linked_ptr<> in a vector<>. +// Other uses may not be as good. +// +// Note: If you use an incomplete type with linked_ptr<>, the class +// *containing* linked_ptr<> must have a constructor and destructor (even +// if they do nothing!). +// +// Thread Safety: +// A linked_ptr is NOT thread safe. Copying a linked_ptr object is +// effectively a read-write operation. +// +// Alternative: to linked_ptr is shared_ptr, which +// - is also two pointers in size (8 bytes for 32 bit addresses) +// - is thread safe for copying and deletion +// - supports weak_ptrs + +#ifndef BASE_MEMORY_LINKED_PTR_H_ +#define BASE_MEMORY_LINKED_PTR_H_ + +#include "base/logging.h" // for CHECK macros + +// This is used internally by all instances of linked_ptr<>. It needs to be +// a non-template class because different types of linked_ptr<> can refer to +// the same object (linked_ptr(obj) vs linked_ptr(obj)). +// So, it needs to be possible for different types of linked_ptr to participate +// in the same circular linked list, so we need a single class type here. +// +// DO NOT USE THIS CLASS DIRECTLY YOURSELF. Use linked_ptr. +class linked_ptr_internal { + public: + // Create a new circle that includes only this instance. + void join_new() { + next_ = this; + } + + // Join an existing circle. + void join(linked_ptr_internal const* ptr) { + next_ = ptr->next_; + ptr->next_ = this; + } + + // Leave whatever circle we're part of. Returns true iff we were the + // last member of the circle. Once this is done, you can join() another. + bool depart() { + if (next_ == this) return true; + linked_ptr_internal const* p = next_; + while (p->next_ != this) p = p->next_; + p->next_ = next_; + return false; + } + + private: + mutable linked_ptr_internal const* next_; +}; + +template +class linked_ptr { + public: + typedef T element_type; + + // Take over ownership of a raw pointer. This should happen as soon as + // possible after the object is created. + explicit linked_ptr(T* ptr = NULL) { capture(ptr); } + ~linked_ptr() { depart(); } + + // Copy an existing linked_ptr<>, adding ourselves to the list of references. + template linked_ptr(linked_ptr const& ptr) { copy(&ptr); } + + linked_ptr(linked_ptr const& ptr) { + DCHECK_NE(&ptr, this); + copy(&ptr); + } + + // Assignment releases the old value and acquires the new. + template linked_ptr& operator=(linked_ptr const& ptr) { + depart(); + copy(&ptr); + return *this; + } + + linked_ptr& operator=(linked_ptr const& ptr) { + if (&ptr != this) { + depart(); + copy(&ptr); + } + return *this; + } + + // Smart pointer members. + void reset(T* ptr = NULL) { + depart(); + capture(ptr); + } + T* get() const { return value_; } + T* operator->() const { return value_; } + T& operator*() const { return *value_; } + // Release ownership of the pointed object and returns it. + // Sole ownership by this linked_ptr object is required. + T* release() { + bool last = link_.depart(); + CHECK(last); + T* v = value_; + value_ = NULL; + return v; + } + + bool operator==(const T* p) const { return value_ == p; } + bool operator!=(const T* p) const { return value_ != p; } + template + bool operator==(linked_ptr const& ptr) const { + return value_ == ptr.get(); + } + template + bool operator!=(linked_ptr const& ptr) const { + return value_ != ptr.get(); + } + + private: + template + friend class linked_ptr; + + T* value_; + linked_ptr_internal link_; + + void depart() { + if (link_.depart()) delete value_; + } + + void capture(T* ptr) { + value_ = ptr; + link_.join_new(); + } + + template void copy(linked_ptr const* ptr) { + value_ = ptr->get(); + if (value_) + link_.join(&ptr->link_); + else + link_.join_new(); + } +}; + +template inline +bool operator==(T* ptr, const linked_ptr& x) { + return ptr == x.get(); +} + +template inline +bool operator!=(T* ptr, const linked_ptr& x) { + return ptr != x.get(); +} + +// A function to convert T* into linked_ptr +// Doing e.g. make_linked_ptr(new FooBarBaz(arg)) is a shorter notation +// for linked_ptr >(new FooBarBaz(arg)) +template +linked_ptr make_linked_ptr(T* ptr) { + return linked_ptr(ptr); +} + +#endif // BASE_MEMORY_LINKED_PTR_H_ diff --git a/base/memory/linked_ptr_unittest.cc b/base/memory/linked_ptr_unittest.cc new file mode 100644 index 0000000000..8b938f2d6c --- /dev/null +++ b/base/memory/linked_ptr_unittest.cc @@ -0,0 +1,110 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/memory/linked_ptr.h" +#include "base/strings/stringprintf.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +int num = 0; + +std::string history; + +// Class which tracks allocation/deallocation +struct A { + A(): mynum(num++) { history += base::StringPrintf("A%d ctor\n", mynum); } + virtual ~A() { history += base::StringPrintf("A%d dtor\n", mynum); } + virtual void Use() { history += base::StringPrintf("A%d use\n", mynum); } + int mynum; +}; + +// Subclass +struct B: public A { + B() { history += base::StringPrintf("B%d ctor\n", mynum); } + virtual ~B() { history += base::StringPrintf("B%d dtor\n", mynum); } + virtual void Use() OVERRIDE { + history += base::StringPrintf("B%d use\n", mynum); + } +}; + +} // namespace + +TEST(LinkedPtrTest, Test) { + { + linked_ptr a0, a1, a2; + a0 = a0; + a1 = a2; + ASSERT_EQ(a0.get(), static_cast(NULL)); + ASSERT_EQ(a1.get(), static_cast(NULL)); + ASSERT_EQ(a2.get(), static_cast(NULL)); + ASSERT_TRUE(a0 == NULL); + ASSERT_TRUE(a1 == NULL); + ASSERT_TRUE(a2 == NULL); + + { + linked_ptr a3(new A); + a0 = a3; + ASSERT_TRUE(a0 == a3); + ASSERT_TRUE(a0 != NULL); + ASSERT_TRUE(a0.get() == a3); + ASSERT_TRUE(a0 == a3.get()); + linked_ptr a4(a0); + a1 = a4; + linked_ptr a5(new A); + ASSERT_TRUE(a5.get() != a3); + ASSERT_TRUE(a5 != a3.get()); + a2 = a5; + linked_ptr b0(new B); + linked_ptr a6(b0); + ASSERT_TRUE(b0 == a6); + ASSERT_TRUE(a6 == b0); + ASSERT_TRUE(b0 != NULL); + a5 = b0; + a5 = b0; + a3->Use(); + a4->Use(); + a5->Use(); + a6->Use(); + b0->Use(); + (*b0).Use(); + b0.get()->Use(); + } + + a0->Use(); + a1->Use(); + a2->Use(); + + a1 = a2; + a2.reset(new A); + a0.reset(); + + linked_ptr a7; + } + + ASSERT_EQ(history, + "A0 ctor\n" + "A1 ctor\n" + "A2 ctor\n" + "B2 ctor\n" + "A0 use\n" + "A0 use\n" + "B2 use\n" + "B2 use\n" + "B2 use\n" + "B2 use\n" + "B2 use\n" + "B2 dtor\n" + "A2 dtor\n" + "A0 use\n" + "A0 use\n" + "A1 use\n" + "A3 ctor\n" + "A0 dtor\n" + "A3 dtor\n" + "A1 dtor\n" + ); +} diff --git a/base/memory/manual_constructor.h b/base/memory/manual_constructor.h new file mode 100644 index 0000000000..9275f73b83 --- /dev/null +++ b/base/memory/manual_constructor.h @@ -0,0 +1,125 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// ManualConstructor statically-allocates space in which to store some +// object, but does not initialize it. You can then call the constructor +// and destructor for the object yourself as you see fit. This is useful +// for memory management optimizations, where you want to initialize and +// destroy an object multiple times but only allocate it once. +// +// (When I say ManualConstructor statically allocates space, I mean that +// the ManualConstructor object itself is forced to be the right size.) +// +// For example usage, check out base/containers/small_map.h. + +#ifndef BASE_MEMORY_MANUAL_CONSTRUCTOR_H_ +#define BASE_MEMORY_MANUAL_CONSTRUCTOR_H_ + +#include + +#include "base/memory/aligned_memory.h" + +namespace base { + +template +class ManualConstructor { + public: + // No constructor or destructor because one of the most useful uses of + // this class is as part of a union, and members of a union cannot have + // constructors or destructors. And, anyway, the whole point of this + // class is to bypass these. + + // Support users creating arrays of ManualConstructor<>s. This ensures that + // the array itself has the correct alignment. + static void* operator new[](size_t size) { +#if defined(COMPILER_MSVC) + return AlignedAlloc(size, __alignof(Type)); +#else + return AlignedAlloc(size, __alignof__(Type)); +#endif + } + static void operator delete[](void* mem) { + AlignedFree(mem); + } + + inline Type* get() { + return space_.template data_as(); + } + inline const Type* get() const { + return space_.template data_as(); + } + + inline Type* operator->() { return get(); } + inline const Type* operator->() const { return get(); } + + inline Type& operator*() { return *get(); } + inline const Type& operator*() const { return *get(); } + + // You can pass up to eight constructor arguments as arguments of Init(). + inline void Init() { + new(space_.void_data()) Type; + } + + template + inline void Init(const T1& p1) { + new(space_.void_data()) Type(p1); + } + + template + inline void Init(const T1& p1, const T2& p2) { + new(space_.void_data()) Type(p1, p2); + } + + template + inline void Init(const T1& p1, const T2& p2, const T3& p3) { + new(space_.void_data()) Type(p1, p2, p3); + } + + template + inline void Init(const T1& p1, const T2& p2, const T3& p3, const T4& p4) { + new(space_.void_data()) Type(p1, p2, p3, p4); + } + + template + inline void Init(const T1& p1, const T2& p2, const T3& p3, const T4& p4, + const T5& p5) { + new(space_.void_data()) Type(p1, p2, p3, p4, p5); + } + + template + inline void Init(const T1& p1, const T2& p2, const T3& p3, const T4& p4, + const T5& p5, const T6& p6) { + new(space_.void_data()) Type(p1, p2, p3, p4, p5, p6); + } + + template + inline void Init(const T1& p1, const T2& p2, const T3& p3, const T4& p4, + const T5& p5, const T6& p6, const T7& p7) { + new(space_.void_data()) Type(p1, p2, p3, p4, p5, p6, p7); + } + + template + inline void Init(const T1& p1, const T2& p2, const T3& p3, const T4& p4, + const T5& p5, const T6& p6, const T7& p7, const T8& p8) { + new(space_.void_data()) Type(p1, p2, p3, p4, p5, p6, p7, p8); + } + + inline void Destroy() { + get()->~Type(); + } + + private: +#if defined(COMPILER_MSVC) + AlignedMemory space_; +#else + AlignedMemory space_; +#endif +}; + +} // namespace base + +#endif // BASE_MEMORY_MANUAL_CONSTRUCTOR_H_ diff --git a/base/memory/memory_pressure_level_list.h b/base/memory/memory_pressure_level_list.h new file mode 100644 index 0000000000..bf3ce60503 --- /dev/null +++ b/base/memory/memory_pressure_level_list.h @@ -0,0 +1,19 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file intentionally does not have header guards, it's included +// inside a macro to generate enum and a java class for the values. + +#ifndef DEFINE_MEMORY_PRESSURE_LEVEL +#error "DEFINE_MEMORY_PRESSURE_LEVEL should be defined." +#endif + +// Modules are advised to free buffers that are cheap to re-allocate and not +// immediately needed. +DEFINE_MEMORY_PRESSURE_LEVEL(MEMORY_PRESSURE_MODERATE, 0) + +// At this level, modules are advised to free all possible memory. +// The alternative is to be killed by the system, which means all memory will +// have to be re-created, plus the cost of a cold start. +DEFINE_MEMORY_PRESSURE_LEVEL(MEMORY_PRESSURE_CRITICAL, 2) diff --git a/base/memory/memory_pressure_listener.cc b/base/memory/memory_pressure_listener.cc new file mode 100644 index 0000000000..e2ea106eaa --- /dev/null +++ b/base/memory/memory_pressure_listener.cc @@ -0,0 +1,57 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/memory_pressure_listener.h" + +#include "base/lazy_instance.h" +#include "base/observer_list_threadsafe.h" + +namespace { + +// ObserverListThreadSafe is RefCountedThreadSafe, this traits is needed +// to ensure the LazyInstance will hold a reference to it. +struct LeakyLazyObserverListTraits : + base::internal::LeakyLazyInstanceTraits< + ObserverListThreadSafe > { + static ObserverListThreadSafe* + New(void* instance) { + ObserverListThreadSafe* ret = + base::internal::LeakyLazyInstanceTraits< + ObserverListThreadSafe >::New( + instance); + // Leaky. + ret->AddRef(); + return ret; + } +}; + +base::LazyInstance< + ObserverListThreadSafe, + LeakyLazyObserverListTraits> g_observers = LAZY_INSTANCE_INITIALIZER; +} // namespace + +namespace base { + +MemoryPressureListener::MemoryPressureListener( + const MemoryPressureListener::MemoryPressureCallback& callback) + : callback_(callback) { + g_observers.Get().AddObserver(this); +} + +MemoryPressureListener::~MemoryPressureListener() { + g_observers.Get().RemoveObserver(this); +} + +void MemoryPressureListener::Notify(MemoryPressureLevel memory_pressure_level) { + callback_.Run(memory_pressure_level); +} + +// static +void MemoryPressureListener::NotifyMemoryPressure( + MemoryPressureLevel memory_pressure_level) { + g_observers.Get().Notify(&MemoryPressureListener::Notify, + memory_pressure_level); +} + +} // namespace base diff --git a/base/memory/memory_pressure_listener.h b/base/memory/memory_pressure_listener.h new file mode 100644 index 0000000000..7b8029dd71 --- /dev/null +++ b/base/memory/memory_pressure_listener.h @@ -0,0 +1,80 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// MemoryPressure provides static APIs for handling memory pressure on +// platforms that have such signals, such as Android. +// The app will try to discard buffers that aren't deemed essential (individual +// modules will implement their own policy). +// +// Refer to memory_pressure_level_list.h for information about what sorts of +// signals can be sent under what conditions. + +#ifndef BASE_MEMORY_PRESSURE_LISTENER_H_ +#define BASE_MEMORY_PRESSURE_LISTENER_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/callback.h" + +namespace base { + +// To start listening, create a new instance, passing a callback to a +// function that takes a MemoryPressureLevel parameter. To stop listening, +// simply delete the listener object. The implementation guarantees +// that the callback will always be called on the thread that created +// the listener. +// If this is the same thread as the system is broadcasting the memory pressure +// event on, then it is guaranteed you're called synchronously within that +// broadcast and hence you should not do long-running garbage collection work. +// But conversely, if there's something that needs to be released before +// control is returned to system code, this is the place to do it. +// Please see notes on memory_pressure_level_list.h: some levels are absolutely +// critical, and if not enough memory is returned to the system, it'll +// potentially kill the app, and then later the app will have to be +// cold-started. +// +// +// Example: +// +// void OnMemoryPressure(MemoryPressureLevel memory_pressure_level) { +// ... +// } +// +// // Start listening. +// MemoryPressureListener* my_listener = +// new MemoryPressureListener(base::Bind(&OnMemoryPressure)); +// +// ... +// +// // Stop listening. +// delete my_listener; +// +class BASE_EXPORT MemoryPressureListener { + public: + enum MemoryPressureLevel { +#define DEFINE_MEMORY_PRESSURE_LEVEL(name, value) name = value, +#include "base/memory/memory_pressure_level_list.h" +#undef DEFINE_MEMORY_PRESSURE_LEVEL + }; + + typedef base::Callback MemoryPressureCallback; + + explicit MemoryPressureListener( + const MemoryPressureCallback& memory_pressure_callback); + ~MemoryPressureListener(); + + // Intended for use by the platform specific implementation. + static void NotifyMemoryPressure(MemoryPressureLevel memory_pressure_level); + + private: + void Notify(MemoryPressureLevel memory_pressure_level); + + MemoryPressureCallback callback_; + + DISALLOW_COPY_AND_ASSIGN(MemoryPressureListener); +}; + +} // namespace base + +#endif // BASE_MEMORY_PRESSURE_LISTENER_H_ diff --git a/base/memory/raw_scoped_refptr_mismatch_checker.h b/base/memory/raw_scoped_refptr_mismatch_checker.h new file mode 100644 index 0000000000..7974f3047b --- /dev/null +++ b/base/memory/raw_scoped_refptr_mismatch_checker.h @@ -0,0 +1,129 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MEMORY_RAW_SCOPED_REFPTR_MISMATCH_CHECKER_H_ +#define BASE_MEMORY_RAW_SCOPED_REFPTR_MISMATCH_CHECKER_H_ + +#include "base/memory/ref_counted.h" +#include "base/template_util.h" +#include "base/tuple.h" +#include "build/build_config.h" + +// It is dangerous to post a task with a T* argument where T is a subtype of +// RefCounted(Base|ThreadSafeBase), since by the time the parameter is used, the +// object may already have been deleted since it was not held with a +// scoped_refptr. Example: http://crbug.com/27191 +// The following set of traits are designed to generate a compile error +// whenever this antipattern is attempted. + +namespace base { + +// This is a base internal implementation file used by task.h and callback.h. +// Not for public consumption, so we wrap it in namespace internal. +namespace internal { + +template +struct NeedsScopedRefptrButGetsRawPtr { +#if defined(OS_WIN) + enum { + value = base::false_type::value + }; +#else + enum { + // Human readable translation: you needed to be a scoped_refptr if you are a + // raw pointer type and are convertible to a RefCounted(Base|ThreadSafeBase) + // type. + value = (is_pointer::value && + (is_convertible::value || + is_convertible::value)) + }; +#endif +}; + +template +struct ParamsUseScopedRefptrCorrectly { + enum { value = 0 }; +}; + +template <> +struct ParamsUseScopedRefptrCorrectly { + enum { value = 1 }; +}; + +template +struct ParamsUseScopedRefptrCorrectly > { + enum { value = !NeedsScopedRefptrButGetsRawPtr::value }; +}; + +template +struct ParamsUseScopedRefptrCorrectly > { + enum { value = !(NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value) }; +}; + +template +struct ParamsUseScopedRefptrCorrectly > { + enum { value = !(NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value) }; +}; + +template +struct ParamsUseScopedRefptrCorrectly > { + enum { value = !(NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value) }; +}; + +template +struct ParamsUseScopedRefptrCorrectly > { + enum { value = !(NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value) }; +}; + +template +struct ParamsUseScopedRefptrCorrectly > { + enum { value = !(NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value) }; +}; + +template +struct ParamsUseScopedRefptrCorrectly > { + enum { value = !(NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value) }; +}; + +template +struct ParamsUseScopedRefptrCorrectly > { + enum { value = !(NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value) }; +}; + +} // namespace internal + +} // namespace base + +#endif // BASE_MEMORY_RAW_SCOPED_REFPTR_MISMATCH_CHECKER_H_ diff --git a/base/memory/ref_counted.cc b/base/memory/ref_counted.cc new file mode 100644 index 0000000000..31ad5098cd --- /dev/null +++ b/base/memory/ref_counted.cc @@ -0,0 +1,95 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/ref_counted.h" + +#include "base/logging.h" +#include "base/threading/thread_collision_warner.h" + +namespace base { + +namespace subtle { + +RefCountedBase::RefCountedBase() + : ref_count_(0) +#ifndef NDEBUG + , in_dtor_(false) +#endif + { +} + +RefCountedBase::~RefCountedBase() { +#ifndef NDEBUG + DCHECK(in_dtor_) << "RefCounted object deleted without calling Release()"; +#endif +} + +void RefCountedBase::AddRef() const { + // TODO(maruel): Add back once it doesn't assert 500 times/sec. + // Current thread books the critical section "AddRelease" without release it. + // DFAKE_SCOPED_LOCK_THREAD_LOCKED(add_release_); +#ifndef NDEBUG + DCHECK(!in_dtor_); +#endif + ++ref_count_; +} + +bool RefCountedBase::Release() const { + // TODO(maruel): Add back once it doesn't assert 500 times/sec. + // Current thread books the critical section "AddRelease" without release it. + // DFAKE_SCOPED_LOCK_THREAD_LOCKED(add_release_); +#ifndef NDEBUG + DCHECK(!in_dtor_); +#endif + if (--ref_count_ == 0) { +#ifndef NDEBUG + in_dtor_ = true; +#endif + return true; + } + return false; +} + +bool RefCountedThreadSafeBase::HasOneRef() const { + return AtomicRefCountIsOne( + &const_cast(this)->ref_count_); +} + +RefCountedThreadSafeBase::RefCountedThreadSafeBase() : ref_count_(0) { +#ifndef NDEBUG + in_dtor_ = false; +#endif +} + +RefCountedThreadSafeBase::~RefCountedThreadSafeBase() { +#ifndef NDEBUG + DCHECK(in_dtor_) << "RefCountedThreadSafe object deleted without " + "calling Release()"; +#endif +} + +void RefCountedThreadSafeBase::AddRef() const { +#ifndef NDEBUG + DCHECK(!in_dtor_); +#endif + AtomicRefCountInc(&ref_count_); +} + +bool RefCountedThreadSafeBase::Release() const { +#ifndef NDEBUG + DCHECK(!in_dtor_); + DCHECK(!AtomicRefCountIsZero(&ref_count_)); +#endif + if (!AtomicRefCountDec(&ref_count_)) { +#ifndef NDEBUG + in_dtor_ = true; +#endif + return true; + } + return false; +} + +} // namespace subtle + +} // namespace base diff --git a/base/memory/ref_counted.h b/base/memory/ref_counted.h new file mode 100644 index 0000000000..0c6a71fdb3 --- /dev/null +++ b/base/memory/ref_counted.h @@ -0,0 +1,301 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MEMORY_REF_COUNTED_H_ +#define BASE_MEMORY_REF_COUNTED_H_ + +#include + +#include "base/atomic_ref_count.h" +#include "base/base_export.h" +#include "base/compiler_specific.h" +#include "base/threading/thread_collision_warner.h" + +namespace base { + +namespace subtle { + +class BASE_EXPORT RefCountedBase { + public: + bool HasOneRef() const { return ref_count_ == 1; } + + protected: + RefCountedBase(); + ~RefCountedBase(); + + void AddRef() const; + + // Returns true if the object should self-delete. + bool Release() const; + + private: + mutable int ref_count_; +#ifndef NDEBUG + mutable bool in_dtor_; +#endif + + DFAKE_MUTEX(add_release_); + + DISALLOW_COPY_AND_ASSIGN(RefCountedBase); +}; + +class BASE_EXPORT RefCountedThreadSafeBase { + public: + bool HasOneRef() const; + + protected: + RefCountedThreadSafeBase(); + ~RefCountedThreadSafeBase(); + + void AddRef() const; + + // Returns true if the object should self-delete. + bool Release() const; + + private: + mutable AtomicRefCount ref_count_; +#ifndef NDEBUG + mutable bool in_dtor_; +#endif + + DISALLOW_COPY_AND_ASSIGN(RefCountedThreadSafeBase); +}; + +} // namespace subtle + +// +// A base class for reference counted classes. Otherwise, known as a cheap +// knock-off of WebKit's RefCounted class. To use this guy just extend your +// class from it like so: +// +// class MyFoo : public base::RefCounted { +// ... +// private: +// friend class base::RefCounted; +// ~MyFoo(); +// }; +// +// You should always make your destructor private, to avoid any code deleting +// the object accidently while there are references to it. +template +class RefCounted : public subtle::RefCountedBase { + public: + RefCounted() {} + + void AddRef() const { + subtle::RefCountedBase::AddRef(); + } + + void Release() const { + if (subtle::RefCountedBase::Release()) { + delete static_cast(this); + } + } + + protected: + ~RefCounted() {} + + private: + DISALLOW_COPY_AND_ASSIGN(RefCounted); +}; + +// Forward declaration. +template class RefCountedThreadSafe; + +// Default traits for RefCountedThreadSafe. Deletes the object when its ref +// count reaches 0. Overload to delete it on a different thread etc. +template +struct DefaultRefCountedThreadSafeTraits { + static void Destruct(const T* x) { + // Delete through RefCountedThreadSafe to make child classes only need to be + // friend with RefCountedThreadSafe instead of this struct, which is an + // implementation detail. + RefCountedThreadSafe::DeleteInternal(x); + } +}; + +// +// A thread-safe variant of RefCounted +// +// class MyFoo : public base::RefCountedThreadSafe { +// ... +// }; +// +// If you're using the default trait, then you should add compile time +// asserts that no one else is deleting your object. i.e. +// private: +// friend class base::RefCountedThreadSafe; +// ~MyFoo(); +template > +class RefCountedThreadSafe : public subtle::RefCountedThreadSafeBase { + public: + RefCountedThreadSafe() {} + + void AddRef() const { + subtle::RefCountedThreadSafeBase::AddRef(); + } + + void Release() const { + if (subtle::RefCountedThreadSafeBase::Release()) { + Traits::Destruct(static_cast(this)); + } + } + + protected: + ~RefCountedThreadSafe() {} + + private: + friend struct DefaultRefCountedThreadSafeTraits; + static void DeleteInternal(const T* x) { delete x; } + + DISALLOW_COPY_AND_ASSIGN(RefCountedThreadSafe); +}; + +// +// A thread-safe wrapper for some piece of data so we can place other +// things in scoped_refptrs<>. +// +template +class RefCountedData + : public base::RefCountedThreadSafe< base::RefCountedData > { + public: + RefCountedData() : data() {} + RefCountedData(const T& in_value) : data(in_value) {} + + T data; + + private: + friend class base::RefCountedThreadSafe >; + ~RefCountedData() {} +}; + +} // namespace base + +// +// A smart pointer class for reference counted objects. Use this class instead +// of calling AddRef and Release manually on a reference counted object to +// avoid common memory leaks caused by forgetting to Release an object +// reference. Sample usage: +// +// class MyFoo : public RefCounted { +// ... +// }; +// +// void some_function() { +// scoped_refptr foo = new MyFoo(); +// foo->Method(param); +// // |foo| is released when this function returns +// } +// +// void some_other_function() { +// scoped_refptr foo = new MyFoo(); +// ... +// foo = NULL; // explicitly releases |foo| +// ... +// if (foo) +// foo->Method(param); +// } +// +// The above examples show how scoped_refptr acts like a pointer to T. +// Given two scoped_refptr classes, it is also possible to exchange +// references between the two objects, like so: +// +// { +// scoped_refptr a = new MyFoo(); +// scoped_refptr b; +// +// b.swap(a); +// // now, |b| references the MyFoo object, and |a| references NULL. +// } +// +// To make both |a| and |b| in the above example reference the same MyFoo +// object, simply use the assignment operator: +// +// { +// scoped_refptr a = new MyFoo(); +// scoped_refptr b; +// +// b = a; +// // now, |a| and |b| each own a reference to the same MyFoo object. +// } +// +template +class scoped_refptr { + public: + typedef T element_type; + + scoped_refptr() : ptr_(NULL) { + } + + scoped_refptr(T* p) : ptr_(p) { + if (ptr_) + ptr_->AddRef(); + } + + scoped_refptr(const scoped_refptr& r) : ptr_(r.ptr_) { + if (ptr_) + ptr_->AddRef(); + } + + template + scoped_refptr(const scoped_refptr& r) : ptr_(r.get()) { + if (ptr_) + ptr_->AddRef(); + } + + ~scoped_refptr() { + if (ptr_) + ptr_->Release(); + } + + T* get() const { return ptr_; } + operator T*() const { return ptr_; } + T* operator->() const { + assert(ptr_ != NULL); + return ptr_; + } + + scoped_refptr& operator=(T* p) { + // AddRef first so that self assignment should work + if (p) + p->AddRef(); + T* old_ptr = ptr_; + ptr_ = p; + if (old_ptr) + old_ptr->Release(); + return *this; + } + + scoped_refptr& operator=(const scoped_refptr& r) { + return *this = r.ptr_; + } + + template + scoped_refptr& operator=(const scoped_refptr& r) { + return *this = r.get(); + } + + void swap(T** pp) { + T* p = ptr_; + ptr_ = *pp; + *pp = p; + } + + void swap(scoped_refptr& r) { + swap(&r.ptr_); + } + + protected: + T* ptr_; +}; + +// Handy utility for creating a scoped_refptr out of a T* explicitly without +// having to retype all the template arguments +template +scoped_refptr make_scoped_refptr(T* t) { + return scoped_refptr(t); +} + +#endif // BASE_MEMORY_REF_COUNTED_H_ diff --git a/base/memory/ref_counted_delete_on_message_loop.h b/base/memory/ref_counted_delete_on_message_loop.h new file mode 100644 index 0000000000..7b898ac00d --- /dev/null +++ b/base/memory/ref_counted_delete_on_message_loop.h @@ -0,0 +1,70 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MEMORY_REF_COUNTED_DELETE_ON_MESSAGE_LOOP_H_ +#define BASE_MEMORY_REF_COUNTED_DELETE_ON_MESSAGE_LOOP_H_ + +#include "base/location.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop/message_loop_proxy.h" + +namespace base { + +// RefCountedDeleteOnMessageLoop is similar to RefCountedThreadSafe, and ensures +// that the object will be deleted on a specified message loop. +// +// Sample usage: +// class Foo : public RefCountedDeleteOnMessageLoop { +// +// Foo(const scoped_refptr& loop) +// : RefCountedDeleteOnMessageLoop(loop) { +// ... +// } +// ... +// private: +// friend class RefCountedDeleteOnMessageLoop; +// friend class DeleteHelper; +// +// ~Foo(); +// }; + +template +class RefCountedDeleteOnMessageLoop : public subtle::RefCountedThreadSafeBase { + public: + RefCountedDeleteOnMessageLoop( + const scoped_refptr& proxy) : proxy_(proxy) { + DCHECK(proxy_.get()); + } + + void AddRef() const { + subtle::RefCountedThreadSafeBase::AddRef(); + } + + void Release() const { + if (subtle::RefCountedThreadSafeBase::Release()) + DestructOnMessageLoop(); + } + + protected: + friend class DeleteHelper; + ~RefCountedDeleteOnMessageLoop() {} + + void DestructOnMessageLoop() const { + const T* t = static_cast(this); + if (proxy_->BelongsToCurrentThread()) + delete t; + else + proxy_->DeleteSoon(FROM_HERE, t); + } + + scoped_refptr proxy_; + + private: + DISALLOW_COPY_AND_ASSIGN(RefCountedDeleteOnMessageLoop); +}; + +} // namespace base + +#endif // BASE_MEMORY_REF_COUNTED_DELETE_ON_MESSAGE_LOOP_H_ diff --git a/base/memory/ref_counted_memory.cc b/base/memory/ref_counted_memory.cc new file mode 100644 index 0000000000..b048a6e0d8 --- /dev/null +++ b/base/memory/ref_counted_memory.cc @@ -0,0 +1,77 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/ref_counted_memory.h" + +#include "base/logging.h" + +namespace base { + +bool RefCountedMemory::Equals( + const scoped_refptr& other) const { + return other.get() && + size() == other->size() && + (memcmp(front(), other->front(), size()) == 0); +} + +RefCountedMemory::RefCountedMemory() {} + +RefCountedMemory::~RefCountedMemory() {} + +const unsigned char* RefCountedStaticMemory::front() const { + return data_; +} + +size_t RefCountedStaticMemory::size() const { + return length_; +} + +RefCountedStaticMemory::~RefCountedStaticMemory() {} + +RefCountedBytes::RefCountedBytes() {} + +RefCountedBytes::RefCountedBytes(const std::vector& initializer) + : data_(initializer) { +} + +RefCountedBytes* RefCountedBytes::TakeVector( + std::vector* to_destroy) { + RefCountedBytes* bytes = new RefCountedBytes; + bytes->data_.swap(*to_destroy); + return bytes; +} + +const unsigned char* RefCountedBytes::front() const { + // STL will assert if we do front() on an empty vector, but calling code + // expects a NULL. + return size() ? &data_.front() : NULL; +} + +size_t RefCountedBytes::size() const { + return data_.size(); +} + +RefCountedBytes::~RefCountedBytes() {} + +RefCountedString::RefCountedString() {} + +RefCountedString::~RefCountedString() {} + +// static +RefCountedString* RefCountedString::TakeString(std::string* to_destroy) { + RefCountedString* self = new RefCountedString; + to_destroy->swap(self->data_); + return self; +} + +const unsigned char* RefCountedString::front() const { + return data_.empty() ? NULL : + reinterpret_cast(data_.data()); +} + +size_t RefCountedString::size() const { + return data_.size(); +} + +} // namespace base diff --git a/base/memory/ref_counted_memory.h b/base/memory/ref_counted_memory.h new file mode 100644 index 0000000000..fd5e8a0b8f --- /dev/null +++ b/base/memory/ref_counted_memory.h @@ -0,0 +1,118 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MEMORY_REF_COUNTED_MEMORY_H_ +#define BASE_MEMORY_REF_COUNTED_MEMORY_H_ + +#include +#include + +#include "base/base_export.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" + +namespace base { + +// A generic interface to memory. This object is reference counted because one +// of its two subclasses own the data they carry, and we need to have +// heterogeneous containers of these two types of memory. +class BASE_EXPORT RefCountedMemory + : public base::RefCountedThreadSafe { + public: + // Retrieves a pointer to the beginning of the data we point to. If the data + // is empty, this will return NULL. + virtual const unsigned char* front() const = 0; + + // Size of the memory pointed to. + virtual size_t size() const = 0; + + // Returns true if |other| is byte for byte equal. + bool Equals(const scoped_refptr& other) const; + + protected: + friend class base::RefCountedThreadSafe; + RefCountedMemory(); + virtual ~RefCountedMemory(); +}; + +// An implementation of RefCountedMemory, where the ref counting does not +// matter. +class BASE_EXPORT RefCountedStaticMemory : public RefCountedMemory { + public: + RefCountedStaticMemory() + : data_(NULL), length_(0) {} + RefCountedStaticMemory(const unsigned char* data, size_t length) + : data_(length ? data : NULL), length_(length) {} + + // Overridden from RefCountedMemory: + virtual const unsigned char* front() const OVERRIDE; + virtual size_t size() const OVERRIDE; + + private: + virtual ~RefCountedStaticMemory(); + + const unsigned char* data_; + size_t length_; + + DISALLOW_COPY_AND_ASSIGN(RefCountedStaticMemory); +}; + +// An implementation of RefCountedMemory, where we own our the data in a +// vector. +class BASE_EXPORT RefCountedBytes : public RefCountedMemory { + public: + RefCountedBytes(); + + // Constructs a RefCountedBytes object by _copying_ from |initializer|. + explicit RefCountedBytes(const std::vector& initializer); + + // Constructs a RefCountedBytes object by performing a swap. (To non + // destructively build a RefCountedBytes, use the constructor that takes a + // vector.) + static RefCountedBytes* TakeVector(std::vector* to_destroy); + + // Overridden from RefCountedMemory: + virtual const unsigned char* front() const OVERRIDE; + virtual size_t size() const OVERRIDE; + + const std::vector& data() const { return data_; } + std::vector& data() { return data_; } + + private: + virtual ~RefCountedBytes(); + + std::vector data_; + + DISALLOW_COPY_AND_ASSIGN(RefCountedBytes); +}; + +// An implementation of RefCountedMemory, where the bytes are stored in an STL +// string. Use this if your data naturally arrives in that format. +class BASE_EXPORT RefCountedString : public RefCountedMemory { + public: + RefCountedString(); + + // Constructs a RefCountedString object by performing a swap. (To non + // destructively build a RefCountedString, use the default constructor and + // copy into object->data()). + static RefCountedString* TakeString(std::string* to_destroy); + + // Overridden from RefCountedMemory: + virtual const unsigned char* front() const OVERRIDE; + virtual size_t size() const OVERRIDE; + + const std::string& data() const { return data_; } + std::string& data() { return data_; } + + private: + virtual ~RefCountedString(); + + std::string data_; + + DISALLOW_COPY_AND_ASSIGN(RefCountedString); +}; + +} // namespace base + +#endif // BASE_MEMORY_REF_COUNTED_MEMORY_H_ diff --git a/base/memory/ref_counted_memory_unittest.cc b/base/memory/ref_counted_memory_unittest.cc new file mode 100644 index 0000000000..c6f2b9c3d1 --- /dev/null +++ b/base/memory/ref_counted_memory_unittest.cc @@ -0,0 +1,71 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/ref_counted_memory.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +TEST(RefCountedMemoryUnitTest, RefCountedStaticMemory) { + scoped_refptr mem = new RefCountedStaticMemory( + reinterpret_cast("static mem00"), 10); + + EXPECT_EQ(10U, mem->size()); + EXPECT_EQ("static mem", + std::string(reinterpret_cast(mem->front()), + mem->size())); +} + +TEST(RefCountedMemoryUnitTest, RefCountedBytes) { + std::vector data; + data.push_back(45); + data.push_back(99); + scoped_refptr mem = RefCountedBytes::TakeVector(&data); + + EXPECT_EQ(0U, data.size()); + + EXPECT_EQ(2U, mem->size()); + EXPECT_EQ(45U, mem->front()[0]); + EXPECT_EQ(99U, mem->front()[1]); +} + +TEST(RefCountedMemoryUnitTest, RefCountedString) { + std::string s("destroy me"); + scoped_refptr mem = RefCountedString::TakeString(&s); + + EXPECT_EQ(0U, s.size()); + + EXPECT_EQ(10U, mem->size()); + EXPECT_EQ('d', mem->front()[0]); + EXPECT_EQ('e', mem->front()[1]); +} + +TEST(RefCountedMemoryUnitTest, Equals) { + std::string s1("same"); + scoped_refptr mem1 = RefCountedString::TakeString(&s1); + + std::vector d2; + d2.push_back('s'); + d2.push_back('a'); + d2.push_back('m'); + d2.push_back('e'); + scoped_refptr mem2 = RefCountedBytes::TakeVector(&d2); + + EXPECT_TRUE(mem1->Equals(mem2)); + + std::string s3("diff"); + scoped_refptr mem3 = RefCountedString::TakeString(&s3); + + EXPECT_FALSE(mem1->Equals(mem3)); + EXPECT_FALSE(mem2->Equals(mem3)); +} + +TEST(RefCountedMemoryUnitTest, EqualsNull) { + std::string s("str"); + scoped_refptr mem = RefCountedString::TakeString(&s); + EXPECT_FALSE(mem->Equals(NULL)); +} + +} // namespace base diff --git a/base/memory/ref_counted_unittest.cc b/base/memory/ref_counted_unittest.cc new file mode 100644 index 0000000000..e8eb0fd90f --- /dev/null +++ b/base/memory/ref_counted_unittest.cc @@ -0,0 +1,62 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/ref_counted.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class SelfAssign : public base::RefCounted { + friend class base::RefCounted; + + ~SelfAssign() {} +}; + +class CheckDerivedMemberAccess : public scoped_refptr { + public: + CheckDerivedMemberAccess() { + // This shouldn't compile if we don't have access to the member variable. + SelfAssign** pptr = &ptr_; + EXPECT_EQ(*pptr, ptr_); + } +}; + +class ScopedRefPtrToSelf : public base::RefCounted { + public: + ScopedRefPtrToSelf() : self_ptr_(this) {} + + static bool was_destroyed() { return was_destroyed_; } + + void SelfDestruct() { self_ptr_ = NULL; } + + private: + friend class base::RefCounted; + ~ScopedRefPtrToSelf() { was_destroyed_ = true; } + + static bool was_destroyed_; + + scoped_refptr self_ptr_; +}; + +bool ScopedRefPtrToSelf::was_destroyed_ = false; + +} // end namespace + +TEST(RefCountedUnitTest, TestSelfAssignment) { + SelfAssign* p = new SelfAssign; + scoped_refptr var(p); + var = var; + EXPECT_EQ(var.get(), p); +} + +TEST(RefCountedUnitTest, ScopedRefPtrMemberAccess) { + CheckDerivedMemberAccess check; +} + +TEST(RefCountedUnitTest, ScopedRefPtrToSelf) { + ScopedRefPtrToSelf* check = new ScopedRefPtrToSelf(); + EXPECT_FALSE(ScopedRefPtrToSelf::was_destroyed()); + check->SelfDestruct(); + EXPECT_TRUE(ScopedRefPtrToSelf::was_destroyed()); +} diff --git a/base/memory/scoped_handle.h b/base/memory/scoped_handle.h new file mode 100644 index 0000000000..b95559dcd8 --- /dev/null +++ b/base/memory/scoped_handle.h @@ -0,0 +1,50 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MEMORY_SCOPED_HANDLE_H_ +#define BASE_MEMORY_SCOPED_HANDLE_H_ + +#include + +#include "base/basictypes.h" + +class ScopedStdioHandle { + public: + ScopedStdioHandle() + : handle_(NULL) { } + + explicit ScopedStdioHandle(FILE* handle) + : handle_(handle) { } + + ~ScopedStdioHandle() { + Close(); + } + + void Close() { + if (handle_) { + fclose(handle_); + handle_ = NULL; + } + } + + FILE* get() const { return handle_; } + + FILE* Take() { + FILE* temp = handle_; + handle_ = NULL; + return temp; + } + + void Set(FILE* newhandle) { + Close(); + handle_ = newhandle; + } + + private: + FILE* handle_; + + DISALLOW_COPY_AND_ASSIGN(ScopedStdioHandle); +}; + +#endif // BASE_MEMORY_SCOPED_HANDLE_H_ diff --git a/base/memory/scoped_open_process.h b/base/memory/scoped_open_process.h new file mode 100644 index 0000000000..8bb19e241e --- /dev/null +++ b/base/memory/scoped_open_process.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_MEMORY_SCOPED_OPEN_PROCESS_H_ +#define BASE_MEMORY_SCOPED_OPEN_PROCESS_H_ + +#include "base/process/process_handle.h" + +namespace base { + +// A class that opens a process from its process id and closes it when the +// instance goes out of scope. +class ScopedOpenProcess { + public: + ScopedOpenProcess() : handle_(kNullProcessHandle) { + } + + // Automatically close the process. + ~ScopedOpenProcess() { + Close(); + } + + // Open a new process by pid. Closes any previously opened process (even if + // opening the new one fails). + bool Open(ProcessId pid) { + Close(); + return OpenProcessHandle(pid, &handle_); + } + + // Close the previously opened process. + void Close() { + if (handle_ == kNullProcessHandle) + return; + + CloseProcessHandle(handle_); + handle_ = kNullProcessHandle; + } + + ProcessHandle handle() const { return handle_; } + + private: + ProcessHandle handle_; + DISALLOW_COPY_AND_ASSIGN(ScopedOpenProcess); +}; +} // namespace base + +#endif // BASE_MEMORY_SCOPED_OPEN_PROCESS_H_ diff --git a/base/memory/scoped_policy.h b/base/memory/scoped_policy.h new file mode 100644 index 0000000000..5dbf2048d6 --- /dev/null +++ b/base/memory/scoped_policy.h @@ -0,0 +1,25 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MEMORY_SCOPED_POLICY_H_ +#define BASE_MEMORY_SCOPED_POLICY_H_ + +namespace base { +namespace scoped_policy { + +// Defines the ownership policy for a scoped object. +enum OwnershipPolicy { + // The scoped object takes ownership of an object by taking over an existing + // ownership claim. + ASSUME, + + // The scoped object will retain the the object and any initial ownership is + // not changed. + RETAIN +}; + +} // namespace scoped_policy +} // namespace base + +#endif // BASE_MEMORY_SCOPED_POLICY_H_ diff --git a/base/memory/scoped_ptr.h b/base/memory/scoped_ptr.h new file mode 100644 index 0000000000..c1fed9ae45 --- /dev/null +++ b/base/memory/scoped_ptr.h @@ -0,0 +1,709 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Scopers help you manage ownership of a pointer, helping you easily manage the +// a pointer within a scope, and automatically destroying the pointer at the +// end of a scope. There are two main classes you will use, which correspond +// to the operators new/delete and new[]/delete[]. +// +// Example usage (scoped_ptr): +// { +// scoped_ptr foo(new Foo("wee")); +// } // foo goes out of scope, releasing the pointer with it. +// +// { +// scoped_ptr foo; // No pointer managed. +// foo.reset(new Foo("wee")); // Now a pointer is managed. +// foo.reset(new Foo("wee2")); // Foo("wee") was destroyed. +// foo.reset(new Foo("wee3")); // Foo("wee2") was destroyed. +// foo->Method(); // Foo::Method() called. +// foo.get()->Method(); // Foo::Method() called. +// SomeFunc(foo.release()); // SomeFunc takes ownership, foo no longer +// // manages a pointer. +// foo.reset(new Foo("wee4")); // foo manages a pointer again. +// foo.reset(); // Foo("wee4") destroyed, foo no longer +// // manages a pointer. +// } // foo wasn't managing a pointer, so nothing was destroyed. +// +// Example usage (scoped_ptr): +// { +// scoped_ptr foo(new Foo[100]); +// foo.get()->Method(); // Foo::Method on the 0th element. +// foo[10].Method(); // Foo::Method on the 10th element. +// } +// +// These scopers also implement part of the functionality of C++11 unique_ptr +// in that they are "movable but not copyable." You can use the scopers in +// the parameter and return types of functions to signify ownership transfer +// in to and out of a function. When calling a function that has a scoper +// as the argument type, it must be called with the result of an analogous +// scoper's Pass() function or another function that generates a temporary; +// passing by copy will NOT work. Here is an example using scoped_ptr: +// +// void TakesOwnership(scoped_ptr arg) { +// // Do something with arg +// } +// scoped_ptr CreateFoo() { +// // No need for calling Pass() because we are constructing a temporary +// // for the return value. +// return scoped_ptr(new Foo("new")); +// } +// scoped_ptr PassThru(scoped_ptr arg) { +// return arg.Pass(); +// } +// +// { +// scoped_ptr ptr(new Foo("yay")); // ptr manages Foo("yay"). +// TakesOwnership(ptr.Pass()); // ptr no longer owns Foo("yay"). +// scoped_ptr ptr2 = CreateFoo(); // ptr2 owns the return Foo. +// scoped_ptr ptr3 = // ptr3 now owns what was in ptr2. +// PassThru(ptr2.Pass()); // ptr2 is correspondingly NULL. +// } +// +// Notice that if you do not call Pass() when returning from PassThru(), or +// when invoking TakesOwnership(), the code will not compile because scopers +// are not copyable; they only implement move semantics which require calling +// the Pass() function to signify a destructive transfer of state. CreateFoo() +// is different though because we are constructing a temporary on the return +// line and thus can avoid needing to call Pass(). +// +// Pass() properly handles upcast in assignment, i.e. you can assign +// scoped_ptr to scoped_ptr: +// +// scoped_ptr foo(new Foo()); +// scoped_ptr parent = foo.Pass(); +// +// PassAs<>() should be used to upcast return value in return statement: +// +// scoped_ptr CreateFoo() { +// scoped_ptr result(new FooChild()); +// return result.PassAs(); +// } +// +// Note that PassAs<>() is implemented only for scoped_ptr, but not for +// scoped_ptr. This is because casting array pointers may not be safe. + +#ifndef BASE_MEMORY_SCOPED_PTR_H_ +#define BASE_MEMORY_SCOPED_PTR_H_ + +// This is an implementation designed to match the anticipated future TR2 +// implementation of the scoped_ptr class and scoped_ptr_malloc (deprecated). + +#include +#include +#include + +#include // For std::swap(). + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/move.h" +#include "base/template_util.h" + +namespace base { + +namespace subtle { +class RefCountedBase; +class RefCountedThreadSafeBase; +} // namespace subtle + +// Function object which deletes its parameter, which must be a pointer. +// If C is an array type, invokes 'delete[]' on the parameter; otherwise, +// invokes 'delete'. The default deleter for scoped_ptr. +template +struct DefaultDeleter { + DefaultDeleter() {} + template DefaultDeleter(const DefaultDeleter& other) { + // IMPLEMENTATION NOTE: C++11 20.7.1.1.2p2 only provides this constructor + // if U* is implicitly convertible to T* and U is not an array type. + // + // Correct implementation should use SFINAE to disable this + // constructor. However, since there are no other 1-argument constructors, + // using a COMPILE_ASSERT() based on is_convertible<> and requiring + // complete types is simpler and will cause compile failures for equivalent + // misuses. + // + // Note, the is_convertible check also ensures that U is not an + // array. T is guaranteed to be a non-array, so any U* where U is an array + // cannot convert to T*. + enum { T_must_be_complete = sizeof(T) }; + enum { U_must_be_complete = sizeof(U) }; + COMPILE_ASSERT((base::is_convertible::value), + U_ptr_must_implicitly_convert_to_T_ptr); + } + inline void operator()(T* ptr) const { + enum { type_must_be_complete = sizeof(T) }; + delete ptr; + } +}; + +// Specialization of DefaultDeleter for array types. +template +struct DefaultDeleter { + inline void operator()(T* ptr) const { + enum { type_must_be_complete = sizeof(T) }; + delete[] ptr; + } + + private: + // Disable this operator for any U != T because it is undefined to execute + // an array delete when the static type of the array mismatches the dynamic + // type. + // + // References: + // C++98 [expr.delete]p3 + // http://cplusplus.github.com/LWG/lwg-defects.html#938 + template void operator()(U* array) const; +}; + +template +struct DefaultDeleter { + // Never allow someone to declare something like scoped_ptr. + COMPILE_ASSERT(sizeof(T) == -1, do_not_use_array_with_size_as_type); +}; + +// Function object which invokes 'free' on its parameter, which must be +// a pointer. Can be used to store malloc-allocated pointers in scoped_ptr: +// +// scoped_ptr foo_ptr( +// static_cast(malloc(sizeof(int)))); +struct FreeDeleter { + inline void operator()(void* ptr) const { + free(ptr); + } +}; + +namespace internal { + +template struct IsNotRefCounted { + enum { + value = !base::is_convertible::value && + !base::is_convertible:: + value + }; +}; + +// Minimal implementation of the core logic of scoped_ptr, suitable for +// reuse in both scoped_ptr and its specializations. +template +class scoped_ptr_impl { + public: + explicit scoped_ptr_impl(T* p) : data_(p) { } + + // Initializer for deleters that have data parameters. + scoped_ptr_impl(T* p, const D& d) : data_(p, d) {} + + // Templated constructor that destructively takes the value from another + // scoped_ptr_impl. + template + scoped_ptr_impl(scoped_ptr_impl* other) + : data_(other->release(), other->get_deleter()) { + // We do not support move-only deleters. We could modify our move + // emulation to have base::subtle::move() and base::subtle::forward() + // functions that are imperfect emulations of their C++11 equivalents, + // but until there's a requirement, just assume deleters are copyable. + } + + template + void TakeState(scoped_ptr_impl* other) { + // See comment in templated constructor above regarding lack of support + // for move-only deleters. + reset(other->release()); + get_deleter() = other->get_deleter(); + } + + ~scoped_ptr_impl() { + if (data_.ptr != NULL) { + // Not using get_deleter() saves one function call in non-optimized + // builds. + static_cast(data_)(data_.ptr); + } + } + + void reset(T* p) { + // This is a self-reset, which is no longer allowed: http://crbug.com/162971 + if (p != NULL && p == data_.ptr) + abort(); + + // Note that running data_.ptr = p can lead to undefined behavior if + // get_deleter()(get()) deletes this. In order to pevent this, reset() + // should update the stored pointer before deleting its old value. + // + // However, changing reset() to use that behavior may cause current code to + // break in unexpected ways. If the destruction of the owned object + // dereferences the scoped_ptr when it is destroyed by a call to reset(), + // then it will incorrectly dispatch calls to |p| rather than the original + // value of |data_.ptr|. + // + // During the transition period, set the stored pointer to NULL while + // deleting the object. Eventually, this safety check will be removed to + // prevent the scenario initially described from occuring and + // http://crbug.com/176091 can be closed. + T* old = data_.ptr; + data_.ptr = NULL; + if (old != NULL) + static_cast(data_)(old); + data_.ptr = p; + } + + T* get() const { return data_.ptr; } + + D& get_deleter() { return data_; } + const D& get_deleter() const { return data_; } + + void swap(scoped_ptr_impl& p2) { + // Standard swap idiom: 'using std::swap' ensures that std::swap is + // present in the overload set, but we call swap unqualified so that + // any more-specific overloads can be used, if available. + using std::swap; + swap(static_cast(data_), static_cast(p2.data_)); + swap(data_.ptr, p2.data_.ptr); + } + + T* release() { + T* old_ptr = data_.ptr; + data_.ptr = NULL; + return old_ptr; + } + + private: + // Needed to allow type-converting constructor. + template friend class scoped_ptr_impl; + + // Use the empty base class optimization to allow us to have a D + // member, while avoiding any space overhead for it when D is an + // empty class. See e.g. http://www.cantrip.org/emptyopt.html for a good + // discussion of this technique. + struct Data : public D { + explicit Data(T* ptr_in) : ptr(ptr_in) {} + Data(T* ptr_in, const D& other) : D(other), ptr(ptr_in) {} + T* ptr; + }; + + Data data_; + + DISALLOW_COPY_AND_ASSIGN(scoped_ptr_impl); +}; + +} // namespace internal + +} // namespace base + +// A scoped_ptr is like a T*, except that the destructor of scoped_ptr +// automatically deletes the pointer it holds (if any). +// That is, scoped_ptr owns the T object that it points to. +// Like a T*, a scoped_ptr may hold either NULL or a pointer to a T object. +// Also like T*, scoped_ptr is thread-compatible, and once you +// dereference it, you get the thread safety guarantees of T. +// +// The size of scoped_ptr is small. On most compilers, when using the +// DefaultDeleter, sizeof(scoped_ptr) == sizeof(T*). Custom deleters will +// increase the size proportional to whatever state they need to have. See +// comments inside scoped_ptr_impl<> for details. +// +// Current implementation targets having a strict subset of C++11's +// unique_ptr<> features. Known deficiencies include not supporting move-only +// deleteres, function pointers as deleters, and deleters with reference +// types. +template > +class scoped_ptr { + MOVE_ONLY_TYPE_FOR_CPP_03(scoped_ptr, RValue) + + COMPILE_ASSERT(base::internal::IsNotRefCounted::value, + T_is_refcounted_type_and_needs_scoped_refptr); + + public: + // The element and deleter types. + typedef T element_type; + typedef D deleter_type; + + // Constructor. Defaults to initializing with NULL. + scoped_ptr() : impl_(NULL) { } + + // Constructor. Takes ownership of p. + explicit scoped_ptr(element_type* p) : impl_(p) { } + + // Constructor. Allows initialization of a stateful deleter. + scoped_ptr(element_type* p, const D& d) : impl_(p, d) { } + + // Constructor. Allows construction from a scoped_ptr rvalue for a + // convertible type and deleter. + // + // IMPLEMENTATION NOTE: C++11 unique_ptr<> keeps this constructor distinct + // from the normal move constructor. By C++11 20.7.1.2.1.21, this constructor + // has different post-conditions if D is a reference type. Since this + // implementation does not support deleters with reference type, + // we do not need a separate move constructor allowing us to avoid one + // use of SFINAE. You only need to care about this if you modify the + // implementation of scoped_ptr. + template + scoped_ptr(scoped_ptr other) : impl_(&other.impl_) { + COMPILE_ASSERT(!base::is_array::value, U_cannot_be_an_array); + } + + // Constructor. Move constructor for C++03 move emulation of this type. + scoped_ptr(RValue rvalue) : impl_(&rvalue.object->impl_) { } + + // operator=. Allows assignment from a scoped_ptr rvalue for a convertible + // type and deleter. + // + // IMPLEMENTATION NOTE: C++11 unique_ptr<> keeps this operator= distinct from + // the normal move assignment operator. By C++11 20.7.1.2.3.4, this templated + // form has different requirements on for move-only Deleters. Since this + // implementation does not support move-only Deleters, we do not need a + // separate move assignment operator allowing us to avoid one use of SFINAE. + // You only need to care about this if you modify the implementation of + // scoped_ptr. + template + scoped_ptr& operator=(scoped_ptr rhs) { + COMPILE_ASSERT(!base::is_array::value, U_cannot_be_an_array); + impl_.TakeState(&rhs.impl_); + return *this; + } + + // Reset. Deletes the currently owned object, if any. + // Then takes ownership of a new object, if given. + void reset(element_type* p = NULL) { impl_.reset(p); } + + // Accessors to get the owned object. + // operator* and operator-> will assert() if there is no current object. + element_type& operator*() const { + assert(impl_.get() != NULL); + return *impl_.get(); + } + element_type* operator->() const { + assert(impl_.get() != NULL); + return impl_.get(); + } + element_type* get() const { return impl_.get(); } + + // Access to the deleter. + deleter_type& get_deleter() { return impl_.get_deleter(); } + const deleter_type& get_deleter() const { return impl_.get_deleter(); } + + // Allow scoped_ptr to be used in boolean expressions, but not + // implicitly convertible to a real bool (which is dangerous). + // + // Note that this trick is only safe when the == and != operators + // are declared explicitly, as otherwise "scoped_ptr1 == + // scoped_ptr2" will compile but do the wrong thing (i.e., convert + // to Testable and then do the comparison). + private: + typedef base::internal::scoped_ptr_impl + scoped_ptr::*Testable; + + public: + operator Testable() const { return impl_.get() ? &scoped_ptr::impl_ : NULL; } + + // Comparison operators. + // These return whether two scoped_ptr refer to the same object, not just to + // two different but equal objects. + bool operator==(const element_type* p) const { return impl_.get() == p; } + bool operator!=(const element_type* p) const { return impl_.get() != p; } + + // Swap two scoped pointers. + void swap(scoped_ptr& p2) { + impl_.swap(p2.impl_); + } + + // Release a pointer. + // The return value is the current pointer held by this object. + // If this object holds a NULL pointer, the return value is NULL. + // After this operation, this object will hold a NULL pointer, + // and will not own the object any more. + element_type* release() WARN_UNUSED_RESULT { + return impl_.release(); + } + + // C++98 doesn't support functions templates with default parameters which + // makes it hard to write a PassAs() that understands converting the deleter + // while preserving simple calling semantics. + // + // Until there is a use case for PassAs() with custom deleters, just ignore + // the custom deleter. + template + scoped_ptr PassAs() { + return scoped_ptr(Pass()); + } + + private: + // Needed to reach into |impl_| in the constructor. + template friend class scoped_ptr; + base::internal::scoped_ptr_impl impl_; + + // Forbidden for API compatibility with std::unique_ptr. + explicit scoped_ptr(int disallow_construction_from_null); + + // Forbid comparison of scoped_ptr types. If U != T, it totally + // doesn't make sense, and if U == T, it still doesn't make sense + // because you should never have the same object owned by two different + // scoped_ptrs. + template bool operator==(scoped_ptr const& p2) const; + template bool operator!=(scoped_ptr const& p2) const; +}; + +template +class scoped_ptr { + MOVE_ONLY_TYPE_FOR_CPP_03(scoped_ptr, RValue) + + public: + // The element and deleter types. + typedef T element_type; + typedef D deleter_type; + + // Constructor. Defaults to initializing with NULL. + scoped_ptr() : impl_(NULL) { } + + // Constructor. Stores the given array. Note that the argument's type + // must exactly match T*. In particular: + // - it cannot be a pointer to a type derived from T, because it is + // inherently unsafe in the general case to access an array through a + // pointer whose dynamic type does not match its static type (eg., if + // T and the derived types had different sizes access would be + // incorrectly calculated). Deletion is also always undefined + // (C++98 [expr.delete]p3). If you're doing this, fix your code. + // - it cannot be NULL, because NULL is an integral expression, not a + // pointer to T. Use the no-argument version instead of explicitly + // passing NULL. + // - it cannot be const-qualified differently from T per unique_ptr spec + // (http://cplusplus.github.com/LWG/lwg-active.html#2118). Users wanting + // to work around this may use implicit_cast(). + // However, because of the first bullet in this comment, users MUST + // NOT use implicit_cast() to upcast the static type of the array. + explicit scoped_ptr(element_type* array) : impl_(array) { } + + // Constructor. Move constructor for C++03 move emulation of this type. + scoped_ptr(RValue rvalue) : impl_(&rvalue.object->impl_) { } + + // operator=. Move operator= for C++03 move emulation of this type. + scoped_ptr& operator=(RValue rhs) { + impl_.TakeState(&rhs.object->impl_); + return *this; + } + + // Reset. Deletes the currently owned array, if any. + // Then takes ownership of a new object, if given. + void reset(element_type* array = NULL) { impl_.reset(array); } + + // Accessors to get the owned array. + element_type& operator[](size_t i) const { + assert(impl_.get() != NULL); + return impl_.get()[i]; + } + element_type* get() const { return impl_.get(); } + + // Access to the deleter. + deleter_type& get_deleter() { return impl_.get_deleter(); } + const deleter_type& get_deleter() const { return impl_.get_deleter(); } + + // Allow scoped_ptr to be used in boolean expressions, but not + // implicitly convertible to a real bool (which is dangerous). + private: + typedef base::internal::scoped_ptr_impl + scoped_ptr::*Testable; + + public: + operator Testable() const { return impl_.get() ? &scoped_ptr::impl_ : NULL; } + + // Comparison operators. + // These return whether two scoped_ptr refer to the same object, not just to + // two different but equal objects. + bool operator==(element_type* array) const { return impl_.get() == array; } + bool operator!=(element_type* array) const { return impl_.get() != array; } + + // Swap two scoped pointers. + void swap(scoped_ptr& p2) { + impl_.swap(p2.impl_); + } + + // Release a pointer. + // The return value is the current pointer held by this object. + // If this object holds a NULL pointer, the return value is NULL. + // After this operation, this object will hold a NULL pointer, + // and will not own the object any more. + element_type* release() WARN_UNUSED_RESULT { + return impl_.release(); + } + + private: + // Force element_type to be a complete type. + enum { type_must_be_complete = sizeof(element_type) }; + + // Actually hold the data. + base::internal::scoped_ptr_impl impl_; + + // Disable initialization from any type other than element_type*, by + // providing a constructor that matches such an initialization, but is + // private and has no definition. This is disabled because it is not safe to + // call delete[] on an array whose static type does not match its dynamic + // type. + template explicit scoped_ptr(U* array); + explicit scoped_ptr(int disallow_construction_from_null); + + // Disable reset() from any type other than element_type*, for the same + // reasons as the constructor above. + template void reset(U* array); + void reset(int disallow_reset_from_null); + + // Forbid comparison of scoped_ptr types. If U != T, it totally + // doesn't make sense, and if U == T, it still doesn't make sense + // because you should never have the same object owned by two different + // scoped_ptrs. + template bool operator==(scoped_ptr const& p2) const; + template bool operator!=(scoped_ptr const& p2) const; +}; + +// Free functions +template +void swap(scoped_ptr& p1, scoped_ptr& p2) { + p1.swap(p2); +} + +template +bool operator==(T* p1, const scoped_ptr& p2) { + return p1 == p2.get(); +} + +template +bool operator!=(T* p1, const scoped_ptr& p2) { + return p1 != p2.get(); +} + +// DEPRECATED: Use scoped_ptr instead. +// +// scoped_ptr_malloc<> is similar to scoped_ptr<>, but it accepts a +// second template argument, the functor used to free the object. + +template +class scoped_ptr_malloc { + MOVE_ONLY_TYPE_FOR_CPP_03(scoped_ptr_malloc, RValue) + + public: + + // The element type + typedef C element_type; + + // Constructor. Defaults to initializing with NULL. + // There is no way to create an uninitialized scoped_ptr. + // The input parameter must be allocated with an allocator that matches the + // Free functor. For the default Free functor, this is malloc, calloc, or + // realloc. + explicit scoped_ptr_malloc(C* p = NULL): ptr_(p) {} + + // Constructor. Move constructor for C++03 move emulation of this type. + scoped_ptr_malloc(RValue rvalue) + : ptr_(rvalue.object->release()) { + } + + // Destructor. If there is a C object, call the Free functor. + ~scoped_ptr_malloc() { + reset(); + } + + // operator=. Move operator= for C++03 move emulation of this type. + scoped_ptr_malloc& operator=(RValue rhs) { + reset(rhs.object->release()); + return *this; + } + + // Reset. Calls the Free functor on the current owned object, if any. + // Then takes ownership of a new object, if given. + // this->reset(this->get()) works. + void reset(C* p = NULL) { + if (ptr_ != p) { + if (ptr_ != NULL) { + FreeProc free_proc; + free_proc(ptr_); + } + ptr_ = p; + } + } + + // Get the current object. + // operator* and operator-> will cause an assert() failure if there is + // no current object. + C& operator*() const { + assert(ptr_ != NULL); + return *ptr_; + } + + C* operator->() const { + assert(ptr_ != NULL); + return ptr_; + } + + C* get() const { + return ptr_; + } + + // Allow scoped_ptr_malloc to be used in boolean expressions, but not + // implicitly convertible to a real bool (which is dangerous). + typedef C* scoped_ptr_malloc::*Testable; + operator Testable() const { return ptr_ ? &scoped_ptr_malloc::ptr_ : NULL; } + + // Comparison operators. + // These return whether a scoped_ptr_malloc and a plain pointer refer + // to the same object, not just to two different but equal objects. + // For compatibility with the boost-derived implementation, these + // take non-const arguments. + bool operator==(C* p) const { + return ptr_ == p; + } + + bool operator!=(C* p) const { + return ptr_ != p; + } + + // Swap two scoped pointers. + void swap(scoped_ptr_malloc & b) { + C* tmp = b.ptr_; + b.ptr_ = ptr_; + ptr_ = tmp; + } + + // Release a pointer. + // The return value is the current pointer held by this object. + // If this object holds a NULL pointer, the return value is NULL. + // After this operation, this object will hold a NULL pointer, + // and will not own the object any more. + C* release() WARN_UNUSED_RESULT { + C* tmp = ptr_; + ptr_ = NULL; + return tmp; + } + + private: + C* ptr_; + + // no reason to use these: each scoped_ptr_malloc should have its own object + template + bool operator==(scoped_ptr_malloc const& p) const; + template + bool operator!=(scoped_ptr_malloc const& p) const; +}; + +template inline +void swap(scoped_ptr_malloc& a, scoped_ptr_malloc& b) { + a.swap(b); +} + +template inline +bool operator==(C* p, const scoped_ptr_malloc& b) { + return p == b.get(); +} + +template inline +bool operator!=(C* p, const scoped_ptr_malloc& b) { + return p != b.get(); +} + +// A function to convert T* into scoped_ptr +// Doing e.g. make_scoped_ptr(new FooBarBaz(arg)) is a shorter notation +// for scoped_ptr >(new FooBarBaz(arg)) +template +scoped_ptr make_scoped_ptr(T* ptr) { + return scoped_ptr(ptr); +} + +#endif // BASE_MEMORY_SCOPED_PTR_H_ diff --git a/base/memory/scoped_ptr_unittest.cc b/base/memory/scoped_ptr_unittest.cc new file mode 100644 index 0000000000..22da53d351 --- /dev/null +++ b/base/memory/scoped_ptr_unittest.cc @@ -0,0 +1,606 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/scoped_ptr.h" + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/callback.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +// Used to test depth subtyping. +class ConDecLoggerParent { + public: + virtual ~ConDecLoggerParent() {} + + virtual void SetPtr(int* ptr) = 0; + + virtual int SomeMeth(int x) const = 0; +}; + +class ConDecLogger : public ConDecLoggerParent { + public: + ConDecLogger() : ptr_(NULL) { } + explicit ConDecLogger(int* ptr) { SetPtr(ptr); } + virtual ~ConDecLogger() { --*ptr_; } + + virtual void SetPtr(int* ptr) OVERRIDE { ptr_ = ptr; ++*ptr_; } + + virtual int SomeMeth(int x) const OVERRIDE { return x; } + + private: + int* ptr_; + + DISALLOW_COPY_AND_ASSIGN(ConDecLogger); +}; + +struct CountingDeleter { + explicit CountingDeleter(int* count) : count_(count) {} + inline void operator()(double* ptr) const { + (*count_)++; + } + int* count_; +}; + +// Used to test assignment of convertible deleters. +struct CountingDeleterChild : public CountingDeleter { + explicit CountingDeleterChild(int* count) : CountingDeleter(count) {} +}; + +class OverloadedNewAndDelete { + public: + void* operator new(size_t size) { + g_new_count++; + return malloc(size); + } + + void operator delete(void* ptr) { + g_delete_count++; + free(ptr); + } + + static void ResetCounters() { + g_new_count = 0; + g_delete_count = 0; + } + + static int new_count() { return g_new_count; } + static int delete_count() { return g_delete_count; } + + private: + static int g_new_count; + static int g_delete_count; +}; + +int OverloadedNewAndDelete::g_new_count = 0; +int OverloadedNewAndDelete::g_delete_count = 0; + +scoped_ptr PassThru(scoped_ptr logger) { + return logger.Pass(); +} + +void GrabAndDrop(scoped_ptr logger) { +} + +// Do not delete this function! It's existence is to test that you can +// return a temporarily constructed version of the scoper. +scoped_ptr TestReturnOfType(int* constructed) { + return scoped_ptr(new ConDecLogger(constructed)); +} + +scoped_ptr UpcastUsingPassAs( + scoped_ptr object) { + return object.PassAs(); +} + +} // namespace + +TEST(ScopedPtrTest, ScopedPtr) { + int constructed = 0; + + // Ensure size of scoped_ptr<> doesn't increase unexpectedly. + COMPILE_ASSERT(sizeof(int*) >= sizeof(scoped_ptr), + scoped_ptr_larger_than_raw_ptr); + + { + scoped_ptr scoper(new ConDecLogger(&constructed)); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper.get()); + + EXPECT_EQ(10, scoper->SomeMeth(10)); + EXPECT_EQ(10, scoper.get()->SomeMeth(10)); + EXPECT_EQ(10, (*scoper).SomeMeth(10)); + } + EXPECT_EQ(0, constructed); + + // Test reset() and release() + { + scoped_ptr scoper(new ConDecLogger(&constructed)); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper.get()); + + scoper.reset(new ConDecLogger(&constructed)); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper.get()); + + scoper.reset(); + EXPECT_EQ(0, constructed); + EXPECT_FALSE(scoper.get()); + + scoper.reset(new ConDecLogger(&constructed)); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper.get()); + + ConDecLogger* take = scoper.release(); + EXPECT_EQ(1, constructed); + EXPECT_FALSE(scoper.get()); + delete take; + EXPECT_EQ(0, constructed); + + scoper.reset(new ConDecLogger(&constructed)); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper.get()); + } + EXPECT_EQ(0, constructed); + + // Test swap(), == and != + { + scoped_ptr scoper1; + scoped_ptr scoper2; + EXPECT_TRUE(scoper1 == scoper2.get()); + EXPECT_FALSE(scoper1 != scoper2.get()); + + ConDecLogger* logger = new ConDecLogger(&constructed); + scoper1.reset(logger); + EXPECT_EQ(logger, scoper1.get()); + EXPECT_FALSE(scoper2.get()); + EXPECT_FALSE(scoper1 == scoper2.get()); + EXPECT_TRUE(scoper1 != scoper2.get()); + + scoper2.swap(scoper1); + EXPECT_EQ(logger, scoper2.get()); + EXPECT_FALSE(scoper1.get()); + EXPECT_FALSE(scoper1 == scoper2.get()); + EXPECT_TRUE(scoper1 != scoper2.get()); + } + EXPECT_EQ(0, constructed); +} + +TEST(ScopedPtrTest, ScopedPtrDepthSubtyping) { + int constructed = 0; + + // Test construction from a scoped_ptr to a derived class. + { + scoped_ptr scoper(new ConDecLogger(&constructed)); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper.get()); + + scoped_ptr scoper_parent(scoper.Pass()); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper_parent.get()); + EXPECT_FALSE(scoper.get()); + + EXPECT_EQ(10, scoper_parent->SomeMeth(10)); + EXPECT_EQ(10, scoper_parent.get()->SomeMeth(10)); + EXPECT_EQ(10, (*scoper_parent).SomeMeth(10)); + } + EXPECT_EQ(0, constructed); + + // Test assignment from a scoped_ptr to a derived class. + { + scoped_ptr scoper(new ConDecLogger(&constructed)); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper.get()); + + scoped_ptr scoper_parent; + scoper_parent = scoper.Pass(); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper_parent.get()); + EXPECT_FALSE(scoper.get()); + } + EXPECT_EQ(0, constructed); + + // Test construction of a scoped_ptr with an additional const annotation. + { + scoped_ptr scoper(new ConDecLogger(&constructed)); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper.get()); + + scoped_ptr scoper_const(scoper.Pass()); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper_const.get()); + EXPECT_FALSE(scoper.get()); + + EXPECT_EQ(10, scoper_const->SomeMeth(10)); + EXPECT_EQ(10, scoper_const.get()->SomeMeth(10)); + EXPECT_EQ(10, (*scoper_const).SomeMeth(10)); + } + EXPECT_EQ(0, constructed); + + // Test assignment to a scoped_ptr with an additional const annotation. + { + scoped_ptr scoper(new ConDecLogger(&constructed)); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper.get()); + + scoped_ptr scoper_const; + scoper_const = scoper.Pass(); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper_const.get()); + EXPECT_FALSE(scoper.get()); + } + EXPECT_EQ(0, constructed); + + // Test assignment to a scoped_ptr deleter of parent type. + { + // Custom deleters never touch these value. + double dummy_value, dummy_value2; + int deletes = 0; + int alternate_deletes = 0; + scoped_ptr scoper(&dummy_value, + CountingDeleter(&deletes)); + scoped_ptr scoper_child( + &dummy_value2, CountingDeleterChild(&alternate_deletes)); + + EXPECT_TRUE(scoper); + EXPECT_TRUE(scoper_child); + EXPECT_EQ(0, deletes); + EXPECT_EQ(0, alternate_deletes); + + // Test this compiles and correctly overwrites the deleter state. + scoper = scoper_child.Pass(); + EXPECT_TRUE(scoper); + EXPECT_FALSE(scoper_child); + EXPECT_EQ(1, deletes); + EXPECT_EQ(0, alternate_deletes); + + scoper.reset(); + EXPECT_FALSE(scoper); + EXPECT_FALSE(scoper_child); + EXPECT_EQ(1, deletes); + EXPECT_EQ(1, alternate_deletes); + + scoper_child.reset(&dummy_value); + EXPECT_TRUE(scoper_child); + EXPECT_EQ(1, deletes); + EXPECT_EQ(1, alternate_deletes); + scoped_ptr scoper_construct(scoper_child.Pass()); + EXPECT_TRUE(scoper_construct); + EXPECT_FALSE(scoper_child); + EXPECT_EQ(1, deletes); + EXPECT_EQ(1, alternate_deletes); + + scoper_construct.reset(); + EXPECT_EQ(1, deletes); + EXPECT_EQ(2, alternate_deletes); + } +} + +TEST(ScopedPtrTest, ScopedPtrWithArray) { + static const int kNumLoggers = 12; + + int constructed = 0; + + { + scoped_ptr scoper(new ConDecLogger[kNumLoggers]); + EXPECT_TRUE(scoper); + EXPECT_EQ(&scoper[0], scoper.get()); + for (int i = 0; i < kNumLoggers; ++i) { + scoper[i].SetPtr(&constructed); + } + EXPECT_EQ(12, constructed); + + EXPECT_EQ(10, scoper.get()->SomeMeth(10)); + EXPECT_EQ(10, scoper[2].SomeMeth(10)); + } + EXPECT_EQ(0, constructed); + + // Test reset() and release() + { + scoped_ptr scoper; + EXPECT_FALSE(scoper.get()); + EXPECT_FALSE(scoper.release()); + EXPECT_FALSE(scoper.get()); + scoper.reset(); + EXPECT_FALSE(scoper.get()); + + scoper.reset(new ConDecLogger[kNumLoggers]); + for (int i = 0; i < kNumLoggers; ++i) { + scoper[i].SetPtr(&constructed); + } + EXPECT_EQ(12, constructed); + scoper.reset(); + EXPECT_EQ(0, constructed); + + scoper.reset(new ConDecLogger[kNumLoggers]); + for (int i = 0; i < kNumLoggers; ++i) { + scoper[i].SetPtr(&constructed); + } + EXPECT_EQ(12, constructed); + ConDecLogger* ptr = scoper.release(); + EXPECT_EQ(12, constructed); + delete[] ptr; + EXPECT_EQ(0, constructed); + } + EXPECT_EQ(0, constructed); + + // Test swap(), ==, !=, and type-safe Boolean. + { + scoped_ptr scoper1; + scoped_ptr scoper2; + EXPECT_TRUE(scoper1 == scoper2.get()); + EXPECT_FALSE(scoper1 != scoper2.get()); + + ConDecLogger* loggers = new ConDecLogger[kNumLoggers]; + for (int i = 0; i < kNumLoggers; ++i) { + loggers[i].SetPtr(&constructed); + } + scoper1.reset(loggers); + EXPECT_TRUE(scoper1); + EXPECT_EQ(loggers, scoper1.get()); + EXPECT_FALSE(scoper2); + EXPECT_FALSE(scoper2.get()); + EXPECT_FALSE(scoper1 == scoper2.get()); + EXPECT_TRUE(scoper1 != scoper2.get()); + + scoper2.swap(scoper1); + EXPECT_EQ(loggers, scoper2.get()); + EXPECT_FALSE(scoper1.get()); + EXPECT_FALSE(scoper1 == scoper2.get()); + EXPECT_TRUE(scoper1 != scoper2.get()); + } + EXPECT_EQ(0, constructed); + + { + ConDecLogger* loggers = new ConDecLogger[kNumLoggers]; + scoped_ptr scoper(loggers); + EXPECT_TRUE(scoper); + for (int i = 0; i < kNumLoggers; ++i) { + scoper[i].SetPtr(&constructed); + } + EXPECT_EQ(kNumLoggers, constructed); + + // Test Pass() with constructor; + scoped_ptr scoper2(scoper.Pass()); + EXPECT_EQ(kNumLoggers, constructed); + + // Test Pass() with assignment; + scoped_ptr scoper3; + scoper3 = scoper2.Pass(); + EXPECT_EQ(kNumLoggers, constructed); + EXPECT_FALSE(scoper); + EXPECT_FALSE(scoper2); + EXPECT_TRUE(scoper3); + } + EXPECT_EQ(0, constructed); +} + +TEST(ScopedPtrTest, PassBehavior) { + int constructed = 0; + { + ConDecLogger* logger = new ConDecLogger(&constructed); + scoped_ptr scoper(logger); + EXPECT_EQ(1, constructed); + + // Test Pass() with constructor; + scoped_ptr scoper2(scoper.Pass()); + EXPECT_EQ(1, constructed); + + // Test Pass() with assignment; + scoped_ptr scoper3; + scoper3 = scoper2.Pass(); + EXPECT_EQ(1, constructed); + EXPECT_FALSE(scoper.get()); + EXPECT_FALSE(scoper2.get()); + EXPECT_TRUE(scoper3.get()); + } + + // Test uncaught Pass() does not leak. + { + ConDecLogger* logger = new ConDecLogger(&constructed); + scoped_ptr scoper(logger); + EXPECT_EQ(1, constructed); + + // Should auto-destruct logger by end of scope. + scoper.Pass(); + EXPECT_FALSE(scoper.get()); + } + EXPECT_EQ(0, constructed); + + // Test that passing to function which does nothing does not leak. + { + ConDecLogger* logger = new ConDecLogger(&constructed); + scoped_ptr scoper(logger); + EXPECT_EQ(1, constructed); + + // Should auto-destruct logger by end of scope. + GrabAndDrop(scoper.Pass()); + EXPECT_FALSE(scoper.get()); + } + EXPECT_EQ(0, constructed); +} + +TEST(ScopedPtrTest, ReturnTypeBehavior) { + int constructed = 0; + + // Test that we can return a scoped_ptr. + { + ConDecLogger* logger = new ConDecLogger(&constructed); + scoped_ptr scoper(logger); + EXPECT_EQ(1, constructed); + + PassThru(scoper.Pass()); + EXPECT_FALSE(scoper.get()); + } + EXPECT_EQ(0, constructed); + + // Test uncaught return type not leak. + { + ConDecLogger* logger = new ConDecLogger(&constructed); + scoped_ptr scoper(logger); + EXPECT_EQ(1, constructed); + + // Should auto-destruct logger by end of scope. + PassThru(scoper.Pass()); + EXPECT_FALSE(scoper.get()); + } + EXPECT_EQ(0, constructed); + + // Call TestReturnOfType() so the compiler doesn't warn for an unused + // function. + { + TestReturnOfType(&constructed); + } + EXPECT_EQ(0, constructed); +} + +TEST(ScopedPtrTest, PassAs) { + int constructed = 0; + { + scoped_ptr scoper(new ConDecLogger(&constructed)); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper.get()); + + scoped_ptr scoper_parent; + scoper_parent = UpcastUsingPassAs(scoper.Pass()); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper_parent.get()); + EXPECT_FALSE(scoper.get()); + } + EXPECT_EQ(0, constructed); +} + +TEST(ScopedPtrTest, CustomDeleter) { + double dummy_value; // Custom deleter never touches this value. + int deletes = 0; + int alternate_deletes = 0; + + // Normal delete support. + { + deletes = 0; + scoped_ptr scoper(&dummy_value, + CountingDeleter(&deletes)); + EXPECT_EQ(0, deletes); + EXPECT_TRUE(scoper.get()); + } + EXPECT_EQ(1, deletes); + + // Test reset() and release(). + deletes = 0; + { + scoped_ptr scoper(NULL, + CountingDeleter(&deletes)); + EXPECT_FALSE(scoper.get()); + EXPECT_FALSE(scoper.release()); + EXPECT_FALSE(scoper.get()); + scoper.reset(); + EXPECT_FALSE(scoper.get()); + EXPECT_EQ(0, deletes); + + scoper.reset(&dummy_value); + scoper.reset(); + EXPECT_EQ(1, deletes); + + scoper.reset(&dummy_value); + EXPECT_EQ(&dummy_value, scoper.release()); + } + EXPECT_EQ(1, deletes); + + // Test get_deleter(). + deletes = 0; + alternate_deletes = 0; + { + scoped_ptr scoper(&dummy_value, + CountingDeleter(&deletes)); + // Call deleter manually. + EXPECT_EQ(0, deletes); + scoper.get_deleter()(&dummy_value); + EXPECT_EQ(1, deletes); + + // Deleter is still there after reset. + scoper.reset(); + EXPECT_EQ(2, deletes); + scoper.get_deleter()(&dummy_value); + EXPECT_EQ(3, deletes); + + // Deleter can be assigned into (matches C++11 unique_ptr<> spec). + scoper.get_deleter() = CountingDeleter(&alternate_deletes); + scoper.reset(&dummy_value); + EXPECT_EQ(0, alternate_deletes); + + } + EXPECT_EQ(3, deletes); + EXPECT_EQ(1, alternate_deletes); + + // Test operator= deleter support. + deletes = 0; + alternate_deletes = 0; + { + double dummy_value2; + scoped_ptr scoper(&dummy_value, + CountingDeleter(&deletes)); + scoped_ptr scoper2( + &dummy_value2, + CountingDeleter(&alternate_deletes)); + EXPECT_EQ(0, deletes); + EXPECT_EQ(0, alternate_deletes); + + // Pass the second deleter through a constructor and an operator=. Then + // reinitialize the empty scopers to ensure that each one is deleting + // properly. + scoped_ptr scoper3(scoper2.Pass()); + scoper = scoper3.Pass(); + EXPECT_EQ(1, deletes); + + scoper2.reset(&dummy_value2); + scoper3.reset(&dummy_value2); + EXPECT_EQ(0, alternate_deletes); + + } + EXPECT_EQ(1, deletes); + EXPECT_EQ(3, alternate_deletes); + + // Test swap(), ==, !=, and type-safe Boolean. + { + scoped_ptr scoper1(NULL, + CountingDeleter(&deletes)); + scoped_ptr scoper2(NULL, + CountingDeleter(&deletes)); + EXPECT_TRUE(scoper1 == scoper2.get()); + EXPECT_FALSE(scoper1 != scoper2.get()); + + scoper1.reset(&dummy_value); + EXPECT_TRUE(scoper1); + EXPECT_EQ(&dummy_value, scoper1.get()); + EXPECT_FALSE(scoper2); + EXPECT_FALSE(scoper2.get()); + EXPECT_FALSE(scoper1 == scoper2.get()); + EXPECT_TRUE(scoper1 != scoper2.get()); + + scoper2.swap(scoper1); + EXPECT_EQ(&dummy_value, scoper2.get()); + EXPECT_FALSE(scoper1.get()); + EXPECT_FALSE(scoper1 == scoper2.get()); + EXPECT_TRUE(scoper1 != scoper2.get()); + } +} + +// Sanity check test for overloaded new and delete operators. Does not do full +// coverage of reset/release/Pass() operations as that is redundant with the +// above. +TEST(ScopedPtrTest, OverloadedNewAndDelete) { + { + OverloadedNewAndDelete::ResetCounters(); + scoped_ptr scoper(new OverloadedNewAndDelete()); + EXPECT_TRUE(scoper.get()); + + scoped_ptr scoper2(scoper.Pass()); + } + EXPECT_EQ(1, OverloadedNewAndDelete::delete_count()); + EXPECT_EQ(1, OverloadedNewAndDelete::new_count()); +} + +// TODO scoped_ptr_malloc diff --git a/base/memory/scoped_ptr_unittest.nc b/base/memory/scoped_ptr_unittest.nc new file mode 100644 index 0000000000..2e2a3e5752 --- /dev/null +++ b/base/memory/scoped_ptr_unittest.nc @@ -0,0 +1,113 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/ref_counted.h" + +namespace { + +class Parent { +}; + +class Child : public Parent { +}; + +class RefCountedClass : public base::RefCountedThreadSafe { +}; + +} // namespace + +#if defined(NCTEST_NO_PASSAS_DOWNCAST) // [r"invalid conversion from"] + +scoped_ptr DowncastUsingPassAs(scoped_ptr object) { + return object.PassAs(); +} + +#elif defined(NCTEST_NO_REF_COUNTED_SCOPED_PTR) // [r"size of array is negative"] + +// scoped_ptr<> should not work for ref-counted objects. +void WontCompile() { + scoped_ptr x; +} + +#elif defined(NCTEST_NO_ARRAY_WITH_SIZE) // [r"size of array is negative"] + +void WontCompile() { + scoped_ptr x; +} + +#elif defined(NCTEST_NO_PASS_FROM_ARRAY) // [r"size of array is negative"] + +void WontCompile() { + scoped_ptr a; + scoped_ptr b; + b = a.Pass(); +} + +#elif defined(NCTEST_NO_PASS_TO_ARRAY) // [r"no match for 'operator='"] + +void WontCompile() { + scoped_ptr a; + scoped_ptr b; + b = a.Pass(); +} + +#elif defined(NCTEST_NO_CONSTRUCT_FROM_ARRAY) // [r"is private"] + +void WontCompile() { + scoped_ptr a; + scoped_ptr b(a.Pass()); +} + +#elif defined(NCTEST_NO_CONSTRUCT_TO_ARRAY) // [r"no matching function for call"] + +void WontCompile() { + scoped_ptr a; + scoped_ptr b(a.Pass()); +} + +#elif defined(NCTEST_NO_CONSTRUCT_SCOPED_PTR_ARRAY_FROM_NULL) // [r"is ambiguous"] + +void WontCompile() { + scoped_ptr x(NULL); +} + +#elif defined(NCTEST_NO_CONSTRUCT_SCOPED_PTR_ARRAY_FROM_DERIVED) // [r"is private"] + +void WontCompile() { + scoped_ptr x(new Child[1]); +} + +#elif defined(NCTEST_NO_RESET_SCOPED_PTR_ARRAY_FROM_NULL) // [r"is ambiguous"] + +void WontCompile() { + scoped_ptr x; + x.reset(NULL); +} + +#elif defined(NCTEST_NO_RESET_SCOPED_PTR_ARRAY_FROM_DERIVED) // [r"is private"] + +void WontCompile() { + scoped_ptr x; + x.reset(new Child[1]); +} + +#elif defined(NCTEST_NO_DELETER_REFERENCE) // [r"fails to be a struct or class type"] + +struct Deleter { + void operator()(int*) {} +}; + +// Current implementation doesn't support Deleter Reference types. Enabling +// support would require changes to the behavior of the constructors to match +// including the use of SFINAE to discard the type-converting constructor +// as per C++11 20.7.1.2.1.19. +void WontCompile() { + Deleter d; + int n; + scoped_ptr a(&n, d); +} + +#endif diff --git a/base/memory/scoped_vector.h b/base/memory/scoped_vector.h new file mode 100644 index 0000000000..59144c0e82 --- /dev/null +++ b/base/memory/scoped_vector.h @@ -0,0 +1,130 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MEMORY_SCOPED_VECTOR_H_ +#define BASE_MEMORY_SCOPED_VECTOR_H_ + +#include + +#include "base/basictypes.h" +#include "base/move.h" +#include "base/stl_util.h" + +// ScopedVector wraps a vector deleting the elements from its +// destructor. +template +class ScopedVector { + MOVE_ONLY_TYPE_FOR_CPP_03(ScopedVector, RValue) + + public: + typedef typename std::vector::allocator_type allocator_type; + typedef typename std::vector::size_type size_type; + typedef typename std::vector::difference_type difference_type; + typedef typename std::vector::pointer pointer; + typedef typename std::vector::const_pointer const_pointer; + typedef typename std::vector::reference reference; + typedef typename std::vector::const_reference const_reference; + typedef typename std::vector::value_type value_type; + typedef typename std::vector::iterator iterator; + typedef typename std::vector::const_iterator const_iterator; + typedef typename std::vector::reverse_iterator reverse_iterator; + typedef typename std::vector::const_reverse_iterator + const_reverse_iterator; + + ScopedVector() {} + ~ScopedVector() { clear(); } + ScopedVector(RValue other) { swap(*other.object); } + + ScopedVector& operator=(RValue rhs) { + swap(*rhs.object); + return *this; + } + + reference operator[](size_t index) { return v_[index]; } + const_reference operator[](size_t index) const { return v_[index]; } + + bool empty() const { return v_.empty(); } + size_t size() const { return v_.size(); } + + reverse_iterator rbegin() { return v_.rbegin(); } + const_reverse_iterator rbegin() const { return v_.rbegin(); } + reverse_iterator rend() { return v_.rend(); } + const_reverse_iterator rend() const { return v_.rend(); } + + iterator begin() { return v_.begin(); } + const_iterator begin() const { return v_.begin(); } + iterator end() { return v_.end(); } + const_iterator end() const { return v_.end(); } + + const_reference front() const { return v_.front(); } + reference front() { return v_.front(); } + const_reference back() const { return v_.back(); } + reference back() { return v_.back(); } + + void push_back(T* elem) { v_.push_back(elem); } + + std::vector& get() { return v_; } + const std::vector& get() const { return v_; } + void swap(std::vector& other) { v_.swap(other); } + void swap(ScopedVector& other) { v_.swap(other.v_); } + void release(std::vector* out) { + out->swap(v_); + v_.clear(); + } + + void reserve(size_t capacity) { v_.reserve(capacity); } + + // Resize, deleting elements in the disappearing range if we are shrinking. + void resize(size_t new_size) { + if (v_.size() > new_size) + STLDeleteContainerPointers(v_.begin() + new_size, v_.end()); + v_.resize(new_size); + } + + template + void assign(InputIterator begin, InputIterator end) { + v_.assign(begin, end); + } + + void clear() { STLDeleteElements(&v_); } + + // Like |clear()|, but doesn't delete any elements. + void weak_clear() { v_.clear(); } + + // Lets the ScopedVector take ownership of |x|. + iterator insert(iterator position, T* x) { + return v_.insert(position, x); + } + + // Lets the ScopedVector take ownership of elements in [first,last). + template + void insert(iterator position, InputIterator first, InputIterator last) { + v_.insert(position, first, last); + } + + iterator erase(iterator position) { + delete *position; + return v_.erase(position); + } + + iterator erase(iterator first, iterator last) { + STLDeleteContainerPointers(first, last); + return v_.erase(first, last); + } + + // Like |erase()|, but doesn't delete the element at |position|. + iterator weak_erase(iterator position) { + return v_.erase(position); + } + + // Like |erase()|, but doesn't delete the elements in [first, last). + iterator weak_erase(iterator first, iterator last) { + return v_.erase(first, last); + } + + private: + std::vector v_; +}; + +#endif // BASE_MEMORY_SCOPED_VECTOR_H_ diff --git a/base/memory/scoped_vector_unittest.cc b/base/memory/scoped_vector_unittest.cc new file mode 100644 index 0000000000..353b52c4e7 --- /dev/null +++ b/base/memory/scoped_vector_unittest.cc @@ -0,0 +1,299 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/scoped_vector.h" + +#include "base/bind.h" +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +// The LifeCycleObject notifies its Observer upon construction & destruction. +class LifeCycleObject { + public: + class Observer { + public: + virtual void OnLifeCycleConstruct(LifeCycleObject* o) = 0; + virtual void OnLifeCycleDestroy(LifeCycleObject* o) = 0; + + protected: + virtual ~Observer() {} + }; + + ~LifeCycleObject() { + observer_->OnLifeCycleDestroy(this); + } + + private: + friend class LifeCycleWatcher; + + explicit LifeCycleObject(Observer* observer) + : observer_(observer) { + observer_->OnLifeCycleConstruct(this); + } + + Observer* observer_; + + DISALLOW_COPY_AND_ASSIGN(LifeCycleObject); +}; + +// The life cycle states we care about for the purposes of testing ScopedVector +// against objects. +enum LifeCycleState { + LC_INITIAL, + LC_CONSTRUCTED, + LC_DESTROYED, +}; + +// Because we wish to watch the life cycle of an object being constructed and +// destroyed, and further wish to test expectations against the state of that +// object, we cannot save state in that object itself. Instead, we use this +// pairing of the watcher, which observes the object and notifies of +// construction & destruction. Since we also may be testing assumptions about +// things not getting freed, this class also acts like a scoping object and +// deletes the |constructed_life_cycle_object_|, if any when the +// LifeCycleWatcher is destroyed. To keep this simple, the only expected state +// changes are: +// INITIAL -> CONSTRUCTED -> DESTROYED. +// Anything more complicated than that should start another test. +class LifeCycleWatcher : public LifeCycleObject::Observer { + public: + LifeCycleWatcher() : life_cycle_state_(LC_INITIAL) {} + virtual ~LifeCycleWatcher() {} + + // Assert INITIAL -> CONSTRUCTED and no LifeCycleObject associated with this + // LifeCycleWatcher. + virtual void OnLifeCycleConstruct(LifeCycleObject* object) OVERRIDE { + ASSERT_EQ(LC_INITIAL, life_cycle_state_); + ASSERT_EQ(NULL, constructed_life_cycle_object_.get()); + life_cycle_state_ = LC_CONSTRUCTED; + constructed_life_cycle_object_.reset(object); + } + + // Assert CONSTRUCTED -> DESTROYED and the |object| being destroyed is the + // same one we saw constructed. + virtual void OnLifeCycleDestroy(LifeCycleObject* object) OVERRIDE { + ASSERT_EQ(LC_CONSTRUCTED, life_cycle_state_); + LifeCycleObject* constructed_life_cycle_object = + constructed_life_cycle_object_.release(); + ASSERT_EQ(constructed_life_cycle_object, object); + life_cycle_state_ = LC_DESTROYED; + } + + LifeCycleState life_cycle_state() const { return life_cycle_state_; } + + // Factory method for creating a new LifeCycleObject tied to this + // LifeCycleWatcher. + LifeCycleObject* NewLifeCycleObject() { + return new LifeCycleObject(this); + } + + // Returns true iff |object| is the same object that this watcher is tracking. + bool IsWatching(LifeCycleObject* object) const { + return object == constructed_life_cycle_object_.get(); + } + + private: + LifeCycleState life_cycle_state_; + scoped_ptr constructed_life_cycle_object_; + + DISALLOW_COPY_AND_ASSIGN(LifeCycleWatcher); +}; + +TEST(ScopedVectorTest, LifeCycleWatcher) { + LifeCycleWatcher watcher; + EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state()); + LifeCycleObject* object = watcher.NewLifeCycleObject(); + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + delete object; + EXPECT_EQ(LC_DESTROYED, watcher.life_cycle_state()); +} + +TEST(ScopedVectorTest, Clear) { + LifeCycleWatcher watcher; + EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state()); + ScopedVector scoped_vector; + scoped_vector.push_back(watcher.NewLifeCycleObject()); + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector.back())); + scoped_vector.clear(); + EXPECT_EQ(LC_DESTROYED, watcher.life_cycle_state()); + EXPECT_TRUE(scoped_vector.empty()); +} + +TEST(ScopedVectorTest, WeakClear) { + LifeCycleWatcher watcher; + EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state()); + ScopedVector scoped_vector; + scoped_vector.push_back(watcher.NewLifeCycleObject()); + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector.back())); + scoped_vector.weak_clear(); + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + EXPECT_TRUE(scoped_vector.empty()); +} + +TEST(ScopedVectorTest, ResizeShrink) { + LifeCycleWatcher first_watcher; + EXPECT_EQ(LC_INITIAL, first_watcher.life_cycle_state()); + LifeCycleWatcher second_watcher; + EXPECT_EQ(LC_INITIAL, second_watcher.life_cycle_state()); + ScopedVector scoped_vector; + + scoped_vector.push_back(first_watcher.NewLifeCycleObject()); + EXPECT_EQ(LC_CONSTRUCTED, first_watcher.life_cycle_state()); + EXPECT_EQ(LC_INITIAL, second_watcher.life_cycle_state()); + EXPECT_TRUE(first_watcher.IsWatching(scoped_vector[0])); + EXPECT_FALSE(second_watcher.IsWatching(scoped_vector[0])); + + scoped_vector.push_back(second_watcher.NewLifeCycleObject()); + EXPECT_EQ(LC_CONSTRUCTED, first_watcher.life_cycle_state()); + EXPECT_EQ(LC_CONSTRUCTED, second_watcher.life_cycle_state()); + EXPECT_FALSE(first_watcher.IsWatching(scoped_vector[1])); + EXPECT_TRUE(second_watcher.IsWatching(scoped_vector[1])); + + // Test that shrinking a vector deletes elements in the disappearing range. + scoped_vector.resize(1); + EXPECT_EQ(LC_CONSTRUCTED, first_watcher.life_cycle_state()); + EXPECT_EQ(LC_DESTROYED, second_watcher.life_cycle_state()); + EXPECT_EQ(1u, scoped_vector.size()); + EXPECT_TRUE(first_watcher.IsWatching(scoped_vector[0])); +} + +TEST(ScopedVectorTest, ResizeGrow) { + LifeCycleWatcher watcher; + EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state()); + ScopedVector scoped_vector; + scoped_vector.push_back(watcher.NewLifeCycleObject()); + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector.back())); + + scoped_vector.resize(5); + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + ASSERT_EQ(5u, scoped_vector.size()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector[0])); + EXPECT_FALSE(watcher.IsWatching(scoped_vector[1])); + EXPECT_FALSE(watcher.IsWatching(scoped_vector[2])); + EXPECT_FALSE(watcher.IsWatching(scoped_vector[3])); + EXPECT_FALSE(watcher.IsWatching(scoped_vector[4])); +} + +TEST(ScopedVectorTest, Scope) { + LifeCycleWatcher watcher; + EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state()); + { + ScopedVector scoped_vector; + scoped_vector.push_back(watcher.NewLifeCycleObject()); + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector.back())); + } + EXPECT_EQ(LC_DESTROYED, watcher.life_cycle_state()); +} + +TEST(ScopedVectorTest, MoveConstruct) { + LifeCycleWatcher watcher; + EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state()); + { + ScopedVector scoped_vector; + scoped_vector.push_back(watcher.NewLifeCycleObject()); + EXPECT_FALSE(scoped_vector.empty()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector.back())); + + ScopedVector scoped_vector_copy(scoped_vector.Pass()); + EXPECT_TRUE(scoped_vector.empty()); + EXPECT_FALSE(scoped_vector_copy.empty()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector_copy.back())); + + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + } + EXPECT_EQ(LC_DESTROYED, watcher.life_cycle_state()); +} + +TEST(ScopedVectorTest, MoveAssign) { + LifeCycleWatcher watcher; + EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state()); + { + ScopedVector scoped_vector; + scoped_vector.push_back(watcher.NewLifeCycleObject()); + ScopedVector scoped_vector_assign; + EXPECT_FALSE(scoped_vector.empty()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector.back())); + + scoped_vector_assign = scoped_vector.Pass(); + EXPECT_TRUE(scoped_vector.empty()); + EXPECT_FALSE(scoped_vector_assign.empty()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector_assign.back())); + + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + } + EXPECT_EQ(LC_DESTROYED, watcher.life_cycle_state()); +} + +class DeleteCounter { + public: + explicit DeleteCounter(int* deletes) + : deletes_(deletes) { + } + + ~DeleteCounter() { + (*deletes_)++; + } + + void VoidMethod0() {} + + private: + int* const deletes_; + + DISALLOW_COPY_AND_ASSIGN(DeleteCounter); +}; + +template +ScopedVector PassThru(ScopedVector scoper) { + return scoper.Pass(); +} + +TEST(ScopedVectorTest, Passed) { + int deletes = 0; + ScopedVector deleter_vector; + deleter_vector.push_back(new DeleteCounter(&deletes)); + EXPECT_EQ(0, deletes); + base::Callback(void)> callback = + base::Bind(&PassThru, base::Passed(&deleter_vector)); + EXPECT_EQ(0, deletes); + ScopedVector result = callback.Run(); + EXPECT_EQ(0, deletes); + result.clear(); + EXPECT_EQ(1, deletes); +}; + +TEST(ScopedVectorTest, InsertRange) { + LifeCycleWatcher watchers[5]; + + std::vector vec; + for(LifeCycleWatcher* it = watchers; it != watchers + arraysize(watchers); + ++it) { + EXPECT_EQ(LC_INITIAL, it->life_cycle_state()); + vec.push_back(it->NewLifeCycleObject()); + EXPECT_EQ(LC_CONSTRUCTED, it->life_cycle_state()); + } + // Start scope for ScopedVector. + { + ScopedVector scoped_vector; + scoped_vector.insert(scoped_vector.end(), vec.begin() + 1, vec.begin() + 3); + for(LifeCycleWatcher* it = watchers; it != watchers + arraysize(watchers); + ++it) + EXPECT_EQ(LC_CONSTRUCTED, it->life_cycle_state()); + } + for(LifeCycleWatcher* it = watchers; it != watchers + 1; ++it) + EXPECT_EQ(LC_CONSTRUCTED, it->life_cycle_state()); + for(LifeCycleWatcher* it = watchers + 1; it != watchers + 3; ++it) + EXPECT_EQ(LC_DESTROYED, it->life_cycle_state()); + for(LifeCycleWatcher* it = watchers + 3; it != watchers + arraysize(watchers); + ++it) + EXPECT_EQ(LC_CONSTRUCTED, it->life_cycle_state()); +} + +} // namespace diff --git a/base/memory/shared_memory.h b/base/memory/shared_memory.h new file mode 100644 index 0000000000..23f6973374 --- /dev/null +++ b/base/memory/shared_memory.h @@ -0,0 +1,281 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MEMORY_SHARED_MEMORY_H_ +#define BASE_MEMORY_SHARED_MEMORY_H_ + +#include "build/build_config.h" + +#include + +#if defined(OS_POSIX) +#include +#include +#include +#endif + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/process/process_handle.h" + +#if defined(OS_POSIX) +#include "base/file_descriptor_posix.h" +#endif + +namespace base { + +class FilePath; + +// SharedMemoryHandle is a platform specific type which represents +// the underlying OS handle to a shared memory segment. +#if defined(OS_WIN) +typedef HANDLE SharedMemoryHandle; +typedef HANDLE SharedMemoryLock; +#elif defined(OS_POSIX) +// A SharedMemoryId is sufficient to identify a given shared memory segment on a +// system, but insufficient to map it. +typedef FileDescriptor SharedMemoryHandle; +typedef ino_t SharedMemoryId; +// On POSIX, the lock is implemented as a lockf() on the mapped file, +// so no additional member (or definition of SharedMemoryLock) is +// needed. +#endif + +// Options for creating a shared memory object. +struct SharedMemoryCreateOptions { + SharedMemoryCreateOptions() : name(NULL), size(0), open_existing(false), + executable(false) {} + + // If NULL, the object is anonymous. This pointer is owned by the caller + // and must live through the call to Create(). + const std::string* name; + + // Size of the shared memory object to be created. + // When opening an existing object, this has no effect. + size_t size; + + // If true, and the shared memory already exists, Create() will open the + // existing shared memory and ignore the size parameter. If false, + // shared memory must not exist. This flag is meaningless unless name is + // non-NULL. + bool open_existing; + + // If true, mappings might need to be made executable later. + bool executable; +}; + +// Platform abstraction for shared memory. Provides a C++ wrapper +// around the OS primitive for a memory mapped file. +class BASE_EXPORT SharedMemory { + public: + SharedMemory(); + +#if defined(OS_WIN) + // Similar to the default constructor, except that this allows for + // calling Lock() to acquire the named mutex before either Create or Open + // are called on Windows. + explicit SharedMemory(const std::wstring& name); +#endif + + // Create a new SharedMemory object from an existing, open + // shared memory file. + SharedMemory(SharedMemoryHandle handle, bool read_only); + + // Create a new SharedMemory object from an existing, open + // shared memory file that was created by a remote process and not shared + // to the current process. + SharedMemory(SharedMemoryHandle handle, bool read_only, + ProcessHandle process); + + // Closes any open files. + ~SharedMemory(); + + // Return true iff the given handle is valid (i.e. not the distingished + // invalid value; NULL for a HANDLE and -1 for a file descriptor) + static bool IsHandleValid(const SharedMemoryHandle& handle); + + // Returns invalid handle (see comment above for exact definition). + static SharedMemoryHandle NULLHandle(); + + // Closes a shared memory handle. + static void CloseHandle(const SharedMemoryHandle& handle); + + // Returns the maximum number of handles that can be open at once per process. + static size_t GetHandleLimit(); + + // Creates a shared memory object as described by the options struct. + // Returns true on success and false on failure. + bool Create(const SharedMemoryCreateOptions& options); + + // Creates and maps an anonymous shared memory segment of size size. + // Returns true on success and false on failure. + bool CreateAndMapAnonymous(size_t size); + + // Creates an anonymous shared memory segment of size size. + // Returns true on success and false on failure. + bool CreateAnonymous(size_t size) { + SharedMemoryCreateOptions options; + options.size = size; + return Create(options); + } + + // Creates or opens a shared memory segment based on a name. + // If open_existing is true, and the shared memory already exists, + // opens the existing shared memory and ignores the size parameter. + // If open_existing is false, shared memory must not exist. + // size is the size of the block to be created. + // Returns true on success, false on failure. + bool CreateNamed(const std::string& name, bool open_existing, size_t size) { + SharedMemoryCreateOptions options; + options.name = &name; + options.open_existing = open_existing; + options.size = size; + return Create(options); + } + + // Deletes resources associated with a shared memory segment based on name. + // Not all platforms require this call. + bool Delete(const std::string& name); + + // Opens a shared memory segment based on a name. + // If read_only is true, opens for read-only access. + // Returns true on success, false on failure. + bool Open(const std::string& name, bool read_only); + + // Maps the shared memory into the caller's address space. + // Returns true on success, false otherwise. The memory address + // is accessed via the memory() accessor. The mapped address is guaranteed to + // have an alignment of at least MAP_MINIMUM_ALIGNMENT. + bool Map(size_t bytes) { + return MapAt(0, bytes); + } + + // Same as above, but with |offset| to specify from begining of the shared + // memory block to map. + // |offset| must be alignent to value of |SysInfo::VMAllocationGranularity()|. + bool MapAt(off_t offset, size_t bytes); + enum { MAP_MINIMUM_ALIGNMENT = 32 }; + + // Unmaps the shared memory from the caller's address space. + // Returns true if successful; returns false on error or if the + // memory is not mapped. + bool Unmap(); + + // The size requested when the map is first created. + size_t requested_size() const { return requested_size_; } + + // The actual size of the mapped memory (may be larger than requested). + size_t mapped_size() const { return mapped_size_; } + + // Gets a pointer to the opened memory space if it has been + // Mapped via Map(). Returns NULL if it is not mapped. + void *memory() const { return memory_; } + + // Returns the underlying OS handle for this segment. + // Use of this handle for anything other than an opaque + // identifier is not portable. + SharedMemoryHandle handle() const; + +#if defined(OS_POSIX) && !defined(OS_NACL) + // Returns a unique identifier for this shared memory segment. Inode numbers + // are technically only unique to a single filesystem. However, we always + // allocate shared memory backing files from the same directory, so will end + // up on the same filesystem. + SharedMemoryId id() const { return inode_; } +#endif + + // Closes the open shared memory segment. + // It is safe to call Close repeatedly. + void Close(); + + // Shares the shared memory to another process. Attempts + // to create a platform-specific new_handle which can be + // used in a remote process to access the shared memory + // file. new_handle is an ouput parameter to receive + // the handle for use in the remote process. + // Returns true on success, false otherwise. + bool ShareToProcess(ProcessHandle process, + SharedMemoryHandle* new_handle) { + return ShareToProcessCommon(process, new_handle, false); + } + + // Logically equivalent to: + // bool ok = ShareToProcess(process, new_handle); + // Close(); + // return ok; + // Note that the memory is unmapped by calling this method, regardless of the + // return value. + bool GiveToProcess(ProcessHandle process, + SharedMemoryHandle* new_handle) { + return ShareToProcessCommon(process, new_handle, true); + } + + // Locks the shared memory. + // + // WARNING: on POSIX the memory locking primitive only works across + // processes, not across threads. The Lock method is not currently + // used in inner loops, so we protect against multiple threads in a + // critical section using a class global lock. + void Lock(); + +#if defined(OS_WIN) + // A Lock() implementation with a timeout that also allows setting + // security attributes on the mutex. sec_attr may be NULL. + // Returns true if the Lock() has been acquired, false if the timeout was + // reached. + bool Lock(uint32 timeout_ms, SECURITY_ATTRIBUTES* sec_attr); +#endif + + // Releases the shared memory lock. + void Unlock(); + + private: +#if defined(OS_POSIX) && !defined(OS_NACL) + bool PrepareMapFile(FILE *fp); + bool FilePathForMemoryName(const std::string& mem_name, FilePath* path); + void LockOrUnlockCommon(int function); +#endif + bool ShareToProcessCommon(ProcessHandle process, + SharedMemoryHandle* new_handle, + bool close_self); + +#if defined(OS_WIN) + std::wstring name_; + HANDLE mapped_file_; +#elif defined(OS_POSIX) + int mapped_file_; + ino_t inode_; +#endif + size_t mapped_size_; + void* memory_; + bool read_only_; + size_t requested_size_; +#if !defined(OS_POSIX) + SharedMemoryLock lock_; +#endif + + DISALLOW_COPY_AND_ASSIGN(SharedMemory); +}; + +// A helper class that acquires the shared memory lock while +// the SharedMemoryAutoLock is in scope. +class SharedMemoryAutoLock { + public: + explicit SharedMemoryAutoLock(SharedMemory* shared_memory) + : shared_memory_(shared_memory) { + shared_memory_->Lock(); + } + + ~SharedMemoryAutoLock() { + shared_memory_->Unlock(); + } + + private: + SharedMemory* shared_memory_; + DISALLOW_COPY_AND_ASSIGN(SharedMemoryAutoLock); +}; + +} // namespace base + +#endif // BASE_MEMORY_SHARED_MEMORY_H_ diff --git a/base/memory/shared_memory_android.cc b/base/memory/shared_memory_android.cc new file mode 100644 index 0000000000..3ca3c8fa36 --- /dev/null +++ b/base/memory/shared_memory_android.cc @@ -0,0 +1,57 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/shared_memory.h" + +#include + +#include "base/logging.h" +#include "third_party/ashmem/ashmem.h" + +namespace base { + +// For Android, we use ashmem to implement SharedMemory. ashmem_create_region +// will automatically pin the region. We never explicitly call pin/unpin. When +// all the file descriptors from different processes associated with the region +// are closed, the memory buffer will go away. + +bool SharedMemory::Create(const SharedMemoryCreateOptions& options) { + DCHECK_EQ(-1, mapped_file_ ); + + if (options.size > static_cast(std::numeric_limits::max())) + return false; + + // "name" is just a label in ashmem. It is visible in /proc/pid/maps. + mapped_file_ = ashmem_create_region( + options.name == NULL ? "" : options.name->c_str(), + options.size); + if (-1 == mapped_file_) { + DLOG(ERROR) << "Shared memory creation failed"; + return false; + } + + int err = ashmem_set_prot_region(mapped_file_, + PROT_READ | PROT_WRITE | PROT_EXEC); + if (err < 0) { + DLOG(ERROR) << "Error " << err << " when setting protection of ashmem"; + return false; + } + requested_size_ = options.size; + + return true; +} + +bool SharedMemory::Delete(const std::string& name) { + // Like on Windows, this is intentionally returning true as ashmem will + // automatically releases the resource when all FDs on it are closed. + return true; +} + +bool SharedMemory::Open(const std::string& name, bool read_only) { + // ashmem doesn't support name mapping + NOTIMPLEMENTED(); + return false; +} + +} // namespace base diff --git a/base/memory/shared_memory_nacl.cc b/base/memory/shared_memory_nacl.cc new file mode 100644 index 0000000000..bc2a98dfdf --- /dev/null +++ b/base/memory/shared_memory_nacl.cc @@ -0,0 +1,158 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/shared_memory.h" + +#include +#include +#include +#include +#include + +#include + +#include "base/logging.h" + +namespace base { + +SharedMemory::SharedMemory() + : mapped_file_(-1), + inode_(0), + mapped_size_(0), + memory_(NULL), + read_only_(false), + requested_size_(0) { +} + +SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only) + : mapped_file_(handle.fd), + inode_(0), + mapped_size_(0), + memory_(NULL), + read_only_(read_only), + requested_size_(0) { +} + +SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only, + ProcessHandle process) + : mapped_file_(handle.fd), + inode_(0), + mapped_size_(0), + memory_(NULL), + read_only_(read_only), + requested_size_(0) { + NOTREACHED(); +} + +SharedMemory::~SharedMemory() { + Close(); +} + +// static +bool SharedMemory::IsHandleValid(const SharedMemoryHandle& handle) { + return handle.fd >= 0; +} + +// static +SharedMemoryHandle SharedMemory::NULLHandle() { + return SharedMemoryHandle(); +} + +// static +void SharedMemory::CloseHandle(const SharedMemoryHandle& handle) { + DCHECK_GE(handle.fd, 0); + if (close(handle.fd) < 0) + DPLOG(ERROR) << "close"; +} + +bool SharedMemory::CreateAndMapAnonymous(size_t size) { + // Untrusted code can't create descriptors or handles. + return false; +} + +bool SharedMemory::Create(const SharedMemoryCreateOptions& options) { + // Untrusted code can't create descriptors or handles. + return false; +} + +bool SharedMemory::Delete(const std::string& name) { + return false; +} + +bool SharedMemory::Open(const std::string& name, bool read_only) { + return false; +} + +bool SharedMemory::MapAt(off_t offset, size_t bytes) { + if (mapped_file_ == -1) + return false; + + if (bytes > static_cast(std::numeric_limits::max())) + return false; + + memory_ = mmap(NULL, bytes, PROT_READ | (read_only_ ? 0 : PROT_WRITE), + MAP_SHARED, mapped_file_, offset); + + bool mmap_succeeded = memory_ != MAP_FAILED && memory_ != NULL; + if (mmap_succeeded) { + mapped_size_ = bytes; + DCHECK_EQ(0U, reinterpret_cast(memory_) & + (SharedMemory::MAP_MINIMUM_ALIGNMENT - 1)); + } else { + memory_ = NULL; + } + + return mmap_succeeded; +} + +bool SharedMemory::Unmap() { + if (memory_ == NULL) + return false; + + if (munmap(memory_, mapped_size_) < 0) + DPLOG(ERROR) << "munmap"; + memory_ = NULL; + mapped_size_ = 0; + return true; +} + +SharedMemoryHandle SharedMemory::handle() const { + return FileDescriptor(mapped_file_, false); +} + +void SharedMemory::Close() { + Unmap(); + if (mapped_file_ > 0) { + if (close(mapped_file_) < 0) + DPLOG(ERROR) << "close"; + mapped_file_ = -1; + } +} + +void SharedMemory::Lock() { + NOTIMPLEMENTED(); +} + +void SharedMemory::Unlock() { + NOTIMPLEMENTED(); +} + +bool SharedMemory::ShareToProcessCommon(ProcessHandle process, + SharedMemoryHandle *new_handle, + bool close_self) { + const int new_fd = dup(mapped_file_); + if (new_fd < 0) { + DPLOG(ERROR) << "dup() failed."; + return false; + } + + new_handle->fd = new_fd; + new_handle->auto_close = true; + + if (close_self) + Close(); + return true; +} + +} // namespace base diff --git a/base/memory/shared_memory_posix.cc b/base/memory/shared_memory_posix.cc new file mode 100644 index 0000000000..e6745ea3a7 --- /dev/null +++ b/base/memory/shared_memory_posix.cc @@ -0,0 +1,423 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/shared_memory.h" + +#include +#include +#include +#include +#include +#include + +#include "base/file_util.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/process/process_metrics.h" +#include "base/safe_strerror_posix.h" +#include "base/strings/utf_string_conversions.h" +#include "base/synchronization/lock.h" +#include "base/threading/platform_thread.h" +#include "base/threading/thread_restrictions.h" + +#if defined(OS_MACOSX) +#include "base/mac/foundation_util.h" +#endif // OS_MACOSX + +#if defined(OS_ANDROID) +#include "base/os_compat_android.h" +#include "third_party/ashmem/ashmem.h" +#endif + +namespace base { + +namespace { + +// Paranoia. Semaphores and shared memory segments should live in different +// namespaces, but who knows what's out there. +const char kSemaphoreSuffix[] = "-sem"; + +LazyInstance::Leaky g_thread_lock_ = LAZY_INSTANCE_INITIALIZER; + +} + +SharedMemory::SharedMemory() + : mapped_file_(-1), + inode_(0), + mapped_size_(0), + memory_(NULL), + read_only_(false), + requested_size_(0) { +} + +SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only) + : mapped_file_(handle.fd), + inode_(0), + mapped_size_(0), + memory_(NULL), + read_only_(read_only), + requested_size_(0) { + struct stat st; + if (fstat(handle.fd, &st) == 0) { + // If fstat fails, then the file descriptor is invalid and we'll learn this + // fact when Map() fails. + inode_ = st.st_ino; + } +} + +SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only, + ProcessHandle process) + : mapped_file_(handle.fd), + inode_(0), + mapped_size_(0), + memory_(NULL), + read_only_(read_only), + requested_size_(0) { + // We don't handle this case yet (note the ignored parameter); let's die if + // someone comes calling. + NOTREACHED(); +} + +SharedMemory::~SharedMemory() { + Close(); +} + +// static +bool SharedMemory::IsHandleValid(const SharedMemoryHandle& handle) { + return handle.fd >= 0; +} + +// static +SharedMemoryHandle SharedMemory::NULLHandle() { + return SharedMemoryHandle(); +} + +// static +void SharedMemory::CloseHandle(const SharedMemoryHandle& handle) { + DCHECK_GE(handle.fd, 0); + if (HANDLE_EINTR(close(handle.fd)) < 0) + DPLOG(ERROR) << "close"; +} + +// static +size_t SharedMemory::GetHandleLimit() { + return base::GetMaxFds(); +} + +bool SharedMemory::CreateAndMapAnonymous(size_t size) { + return CreateAnonymous(size) && Map(size); +} + +#if !defined(OS_ANDROID) +// Chromium mostly only uses the unique/private shmem as specified by +// "name == L"". The exception is in the StatsTable. +// TODO(jrg): there is no way to "clean up" all unused named shmem if +// we restart from a crash. (That isn't a new problem, but it is a problem.) +// In case we want to delete it later, it may be useful to save the value +// of mem_filename after FilePathForMemoryName(). +bool SharedMemory::Create(const SharedMemoryCreateOptions& options) { + DCHECK_EQ(-1, mapped_file_); + if (options.size == 0) return false; + + if (options.size > static_cast(std::numeric_limits::max())) + return false; + + // This function theoretically can block on the disk, but realistically + // the temporary files we create will just go into the buffer cache + // and be deleted before they ever make it out to disk. + base::ThreadRestrictions::ScopedAllowIO allow_io; + + FILE *fp; + bool fix_size = true; + + FilePath path; + if (options.name == NULL || options.name->empty()) { + // It doesn't make sense to have a open-existing private piece of shmem + DCHECK(!options.open_existing); + // Q: Why not use the shm_open() etc. APIs? + // A: Because they're limited to 4mb on OS X. FFFFFFFUUUUUUUUUUU + fp = file_util::CreateAndOpenTemporaryShmemFile(&path, options.executable); + + // Deleting the file prevents anyone else from mapping it in (making it + // private), and prevents the need for cleanup (once the last fd is closed, + // it is truly freed). + if (fp) { + if (unlink(path.value().c_str())) + PLOG(WARNING) << "unlink"; + } + } else { + if (!FilePathForMemoryName(*options.name, &path)) + return false; + + // Make sure that the file is opened without any permission + // to other users on the system. + const mode_t kOwnerOnly = S_IRUSR | S_IWUSR; + + // First, try to create the file. + int fd = HANDLE_EINTR( + open(path.value().c_str(), O_RDWR | O_CREAT | O_EXCL, kOwnerOnly)); + if (fd == -1 && options.open_existing) { + // If this doesn't work, try and open an existing file in append mode. + // Opening an existing file in a world writable directory has two main + // security implications: + // - Attackers could plant a file under their control, so ownership of + // the file is checked below. + // - Attackers could plant a symbolic link so that an unexpected file + // is opened, so O_NOFOLLOW is passed to open(). + fd = HANDLE_EINTR( + open(path.value().c_str(), O_RDWR | O_APPEND | O_NOFOLLOW)); + + // Check that the current user owns the file. + // If uid != euid, then a more complex permission model is used and this + // API is not appropriate. + const uid_t real_uid = getuid(); + const uid_t effective_uid = geteuid(); + struct stat sb; + if (fd >= 0 && + (fstat(fd, &sb) != 0 || sb.st_uid != real_uid || + sb.st_uid != effective_uid)) { + LOG(ERROR) << + "Invalid owner when opening existing shared memory file."; + HANDLE_EINTR(close(fd)); + return false; + } + + // An existing file was opened, so its size should not be fixed. + fix_size = false; + } + fp = NULL; + if (fd >= 0) { + // "a+" is always appropriate: if it's a new file, a+ is similar to w+. + fp = fdopen(fd, "a+"); + } + } + if (fp && fix_size) { + // Get current size. + struct stat stat; + if (fstat(fileno(fp), &stat) != 0) { + file_util::CloseFile(fp); + return false; + } + const size_t current_size = stat.st_size; + if (current_size != options.size) { + if (HANDLE_EINTR(ftruncate(fileno(fp), options.size)) != 0) { + file_util::CloseFile(fp); + return false; + } + } + requested_size_ = options.size; + } + if (fp == NULL) { +#if !defined(OS_MACOSX) + PLOG(ERROR) << "Creating shared memory in " << path.value() << " failed"; + FilePath dir = path.DirName(); + if (access(dir.value().c_str(), W_OK | X_OK) < 0) { + PLOG(ERROR) << "Unable to access(W_OK|X_OK) " << dir.value(); + if (dir.value() == "/dev/shm") { + LOG(FATAL) << "This is frequently caused by incorrect permissions on " + << "/dev/shm. Try 'sudo chmod 1777 /dev/shm' to fix."; + } + } +#else + PLOG(ERROR) << "Creating shared memory in " << path.value() << " failed"; +#endif + return false; + } + + return PrepareMapFile(fp); +} + +// Our current implementation of shmem is with mmap()ing of files. +// These files need to be deleted explicitly. +// In practice this call is only needed for unit tests. +bool SharedMemory::Delete(const std::string& name) { + FilePath path; + if (!FilePathForMemoryName(name, &path)) + return false; + + if (PathExists(path)) + return base::DeleteFile(path, false); + + // Doesn't exist, so success. + return true; +} + +bool SharedMemory::Open(const std::string& name, bool read_only) { + FilePath path; + if (!FilePathForMemoryName(name, &path)) + return false; + + read_only_ = read_only; + + const char *mode = read_only ? "r" : "r+"; + FILE *fp = file_util::OpenFile(path, mode); + return PrepareMapFile(fp); +} + +#endif // !defined(OS_ANDROID) + +bool SharedMemory::MapAt(off_t offset, size_t bytes) { + if (mapped_file_ == -1) + return false; + + if (bytes > static_cast(std::numeric_limits::max())) + return false; + +#if defined(OS_ANDROID) + // On Android, Map can be called with a size and offset of zero to use the + // ashmem-determined size. + if (bytes == 0) { + DCHECK_EQ(0, offset); + int ashmem_bytes = ashmem_get_size_region(mapped_file_); + if (ashmem_bytes < 0) + return false; + bytes = ashmem_bytes; + } +#endif + + memory_ = mmap(NULL, bytes, PROT_READ | (read_only_ ? 0 : PROT_WRITE), + MAP_SHARED, mapped_file_, offset); + + bool mmap_succeeded = memory_ != (void*)-1 && memory_ != NULL; + if (mmap_succeeded) { + mapped_size_ = bytes; + DCHECK_EQ(0U, reinterpret_cast(memory_) & + (SharedMemory::MAP_MINIMUM_ALIGNMENT - 1)); + } else { + memory_ = NULL; + } + + return mmap_succeeded; +} + +bool SharedMemory::Unmap() { + if (memory_ == NULL) + return false; + + munmap(memory_, mapped_size_); + memory_ = NULL; + mapped_size_ = 0; + return true; +} + +SharedMemoryHandle SharedMemory::handle() const { + return FileDescriptor(mapped_file_, false); +} + +void SharedMemory::Close() { + Unmap(); + + if (mapped_file_ > 0) { + if (HANDLE_EINTR(close(mapped_file_)) < 0) + PLOG(ERROR) << "close"; + mapped_file_ = -1; + } +} + +void SharedMemory::Lock() { + g_thread_lock_.Get().Acquire(); + LockOrUnlockCommon(F_LOCK); +} + +void SharedMemory::Unlock() { + LockOrUnlockCommon(F_ULOCK); + g_thread_lock_.Get().Release(); +} + +#if !defined(OS_ANDROID) +bool SharedMemory::PrepareMapFile(FILE *fp) { + DCHECK_EQ(-1, mapped_file_); + if (fp == NULL) return false; + + // This function theoretically can block on the disk, but realistically + // the temporary files we create will just go into the buffer cache + // and be deleted before they ever make it out to disk. + base::ThreadRestrictions::ScopedAllowIO allow_io; + + file_util::ScopedFILE file_closer(fp); + + mapped_file_ = dup(fileno(fp)); + if (mapped_file_ == -1) { + if (errno == EMFILE) { + LOG(WARNING) << "Shared memory creation failed; out of file descriptors"; + return false; + } else { + NOTREACHED() << "Call to dup failed, errno=" << errno; + } + } + + struct stat st; + if (fstat(mapped_file_, &st)) + NOTREACHED(); + inode_ = st.st_ino; + + return true; +} +#endif + +// For the given shmem named |mem_name|, return a filename to mmap() +// (and possibly create). Modifies |filename|. Return false on +// error, or true of we are happy. +bool SharedMemory::FilePathForMemoryName(const std::string& mem_name, + FilePath* path) { + // mem_name will be used for a filename; make sure it doesn't + // contain anything which will confuse us. + DCHECK_EQ(std::string::npos, mem_name.find('/')); + DCHECK_EQ(std::string::npos, mem_name.find('\0')); + + FilePath temp_dir; + if (!file_util::GetShmemTempDir(&temp_dir, false)) + return false; + +#if !defined(OS_MACOSX) +#if defined(GOOGLE_CHROME_BUILD) + std::string name_base = std::string("com.google.Chrome"); +#else + std::string name_base = std::string("org.chromium.Chromium"); +#endif +#else // OS_MACOSX + std::string name_base = std::string(base::mac::BaseBundleID()); +#endif // OS_MACOSX + *path = temp_dir.AppendASCII(name_base + ".shmem." + mem_name); + return true; +} + +void SharedMemory::LockOrUnlockCommon(int function) { + DCHECK_GE(mapped_file_, 0); + while (lockf(mapped_file_, function, 0) < 0) { + if (errno == EINTR) { + continue; + } else if (errno == ENOLCK) { + // temporary kernel resource exaustion + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(500)); + continue; + } else { + NOTREACHED() << "lockf() failed." + << " function:" << function + << " fd:" << mapped_file_ + << " errno:" << errno + << " msg:" << safe_strerror(errno); + } + } +} + +bool SharedMemory::ShareToProcessCommon(ProcessHandle process, + SharedMemoryHandle *new_handle, + bool close_self) { + const int new_fd = dup(mapped_file_); + if (new_fd < 0) { + DPLOG(ERROR) << "dup() failed."; + return false; + } + + new_handle->fd = new_fd; + new_handle->auto_close = true; + + if (close_self) + Close(); + + return true; +} + +} // namespace base diff --git a/base/memory/shared_memory_unittest.cc b/base/memory/shared_memory_unittest.cc new file mode 100644 index 0000000000..892fd7f1a5 --- /dev/null +++ b/base/memory/shared_memory_unittest.cc @@ -0,0 +1,561 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/shared_memory.h" +#include "base/process/kill.h" +#include "base/rand_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/sys_info.h" +#include "base/test/multiprocess_test.h" +#include "base/threading/platform_thread.h" +#include "base/time/time.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/multiprocess_func_list.h" + +#if defined(OS_MACOSX) +#include "base/mac/scoped_nsautorelease_pool.h" +#endif + +#if defined(OS_POSIX) +#include +#include +#include +#include +#endif + +static const int kNumThreads = 5; +static const int kNumTasks = 5; + +namespace base { + +namespace { + +// Each thread will open the shared memory. Each thread will take a different 4 +// byte int pointer, and keep changing it, with some small pauses in between. +// Verify that each thread's value in the shared memory is always correct. +class MultipleThreadMain : public PlatformThread::Delegate { + public: + explicit MultipleThreadMain(int16 id) : id_(id) {} + virtual ~MultipleThreadMain() {} + + static void CleanUp() { + SharedMemory memory; + memory.Delete(s_test_name_); + } + + // PlatformThread::Delegate interface. + virtual void ThreadMain() OVERRIDE { +#if defined(OS_MACOSX) + mac::ScopedNSAutoreleasePool pool; +#endif + const uint32 kDataSize = 1024; + SharedMemory memory; + bool rv = memory.CreateNamed(s_test_name_, true, kDataSize); + EXPECT_TRUE(rv); + rv = memory.Map(kDataSize); + EXPECT_TRUE(rv); + int *ptr = static_cast(memory.memory()) + id_; + EXPECT_EQ(0, *ptr); + + for (int idx = 0; idx < 100; idx++) { + *ptr = idx; + PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(1)); + EXPECT_EQ(*ptr, idx); + } + // Reset back to 0 for the next test that uses the same name. + *ptr = 0; + + memory.Close(); + } + + private: + int16 id_; + + static const char* const s_test_name_; + + DISALLOW_COPY_AND_ASSIGN(MultipleThreadMain); +}; + +const char* const MultipleThreadMain::s_test_name_ = + "SharedMemoryOpenThreadTest"; + +// TODO(port): +// This test requires the ability to pass file descriptors between processes. +// We haven't done that yet in Chrome for POSIX. +#if defined(OS_WIN) +// Each thread will open the shared memory. Each thread will take the memory, +// and keep changing it while trying to lock it, with some small pauses in +// between. Verify that each thread's value in the shared memory is always +// correct. +class MultipleLockThread : public PlatformThread::Delegate { + public: + explicit MultipleLockThread(int id) : id_(id) {} + virtual ~MultipleLockThread() {} + + // PlatformThread::Delegate interface. + virtual void ThreadMain() OVERRIDE { + const uint32 kDataSize = sizeof(int); + SharedMemoryHandle handle = NULL; + { + SharedMemory memory1; + EXPECT_TRUE(memory1.CreateNamed("SharedMemoryMultipleLockThreadTest", + true, kDataSize)); + EXPECT_TRUE(memory1.ShareToProcess(GetCurrentProcess(), &handle)); + // TODO(paulg): Implement this once we have a posix version of + // SharedMemory::ShareToProcess. + EXPECT_TRUE(true); + } + + SharedMemory memory2(handle, false); + EXPECT_TRUE(memory2.Map(kDataSize)); + volatile int* const ptr = static_cast(memory2.memory()); + + for (int idx = 0; idx < 20; idx++) { + memory2.Lock(); + int i = (id_ << 16) + idx; + *ptr = i; + PlatformThread::Sleep(TimeDelta::FromMilliseconds(1)); + EXPECT_EQ(*ptr, i); + memory2.Unlock(); + } + + memory2.Close(); + } + + private: + int id_; + + DISALLOW_COPY_AND_ASSIGN(MultipleLockThread); +}; +#endif + +} // namespace + +// Android doesn't support SharedMemory::Open/Delete/ +// CreateNamed(openExisting=true) +#if !defined(OS_ANDROID) +TEST(SharedMemoryTest, OpenClose) { + const uint32 kDataSize = 1024; + std::string test_name = "SharedMemoryOpenCloseTest"; + + // Open two handles to a memory segment, confirm that they are mapped + // separately yet point to the same space. + SharedMemory memory1; + bool rv = memory1.Delete(test_name); + EXPECT_TRUE(rv); + rv = memory1.Delete(test_name); + EXPECT_TRUE(rv); + rv = memory1.Open(test_name, false); + EXPECT_FALSE(rv); + rv = memory1.CreateNamed(test_name, false, kDataSize); + EXPECT_TRUE(rv); + rv = memory1.Map(kDataSize); + EXPECT_TRUE(rv); + SharedMemory memory2; + rv = memory2.Open(test_name, false); + EXPECT_TRUE(rv); + rv = memory2.Map(kDataSize); + EXPECT_TRUE(rv); + EXPECT_NE(memory1.memory(), memory2.memory()); // Compare the pointers. + + // Make sure we don't segfault. (it actually happened!) + ASSERT_NE(memory1.memory(), static_cast(NULL)); + ASSERT_NE(memory2.memory(), static_cast(NULL)); + + // Write data to the first memory segment, verify contents of second. + memset(memory1.memory(), '1', kDataSize); + EXPECT_EQ(memcmp(memory1.memory(), memory2.memory(), kDataSize), 0); + + // Close the first memory segment, and verify the second has the right data. + memory1.Close(); + char *start_ptr = static_cast(memory2.memory()); + char *end_ptr = start_ptr + kDataSize; + for (char* ptr = start_ptr; ptr < end_ptr; ptr++) + EXPECT_EQ(*ptr, '1'); + + // Close the second memory segment. + memory2.Close(); + + rv = memory1.Delete(test_name); + EXPECT_TRUE(rv); + rv = memory2.Delete(test_name); + EXPECT_TRUE(rv); +} + +TEST(SharedMemoryTest, OpenExclusive) { + const uint32 kDataSize = 1024; + const uint32 kDataSize2 = 2048; + std::ostringstream test_name_stream; + test_name_stream << "SharedMemoryOpenExclusiveTest." + << Time::Now().ToDoubleT(); + std::string test_name = test_name_stream.str(); + + // Open two handles to a memory segment and check that open_existing works + // as expected. + SharedMemory memory1; + bool rv = memory1.CreateNamed(test_name, false, kDataSize); + EXPECT_TRUE(rv); + + // Memory1 knows it's size because it created it. + EXPECT_EQ(memory1.requested_size(), kDataSize); + + rv = memory1.Map(kDataSize); + EXPECT_TRUE(rv); + + // The mapped memory1 must be at least the size we asked for. + EXPECT_GE(memory1.mapped_size(), kDataSize); + + // The mapped memory1 shouldn't exceed rounding for allocation granularity. + EXPECT_LT(memory1.mapped_size(), + kDataSize + base::SysInfo::VMAllocationGranularity()); + + memset(memory1.memory(), 'G', kDataSize); + + SharedMemory memory2; + // Should not be able to create if openExisting is false. + rv = memory2.CreateNamed(test_name, false, kDataSize2); + EXPECT_FALSE(rv); + + // Should be able to create with openExisting true. + rv = memory2.CreateNamed(test_name, true, kDataSize2); + EXPECT_TRUE(rv); + + // Memory2 shouldn't know the size because we didn't create it. + EXPECT_EQ(memory2.requested_size(), 0U); + + // We should be able to map the original size. + rv = memory2.Map(kDataSize); + EXPECT_TRUE(rv); + + // The mapped memory2 must be at least the size of the original. + EXPECT_GE(memory2.mapped_size(), kDataSize); + + // The mapped memory2 shouldn't exceed rounding for allocation granularity. + EXPECT_LT(memory2.mapped_size(), + kDataSize2 + base::SysInfo::VMAllocationGranularity()); + + // Verify that opening memory2 didn't truncate or delete memory 1. + char *start_ptr = static_cast(memory2.memory()); + char *end_ptr = start_ptr + kDataSize; + for (char* ptr = start_ptr; ptr < end_ptr; ptr++) { + EXPECT_EQ(*ptr, 'G'); + } + + memory1.Close(); + memory2.Close(); + + rv = memory1.Delete(test_name); + EXPECT_TRUE(rv); +} +#endif + +// Create a set of N threads to each open a shared memory segment and write to +// it. Verify that they are always reading/writing consistent data. +TEST(SharedMemoryTest, MultipleThreads) { + MultipleThreadMain::CleanUp(); + // On POSIX we have a problem when 2 threads try to create the shmem + // (a file) at exactly the same time, since create both creates the + // file and zerofills it. We solve the problem for this unit test + // (make it not flaky) by starting with 1 thread, then + // intentionally don't clean up its shmem before running with + // kNumThreads. + + int threadcounts[] = { 1, kNumThreads }; + for (size_t i = 0; i < arraysize(threadcounts); i++) { + int numthreads = threadcounts[i]; + scoped_ptr thread_handles; + scoped_ptr thread_delegates; + + thread_handles.reset(new PlatformThreadHandle[numthreads]); + thread_delegates.reset(new MultipleThreadMain*[numthreads]); + + // Spawn the threads. + for (int16 index = 0; index < numthreads; index++) { + PlatformThreadHandle pth; + thread_delegates[index] = new MultipleThreadMain(index); + EXPECT_TRUE(PlatformThread::Create(0, thread_delegates[index], &pth)); + thread_handles[index] = pth; + } + + // Wait for the threads to finish. + for (int index = 0; index < numthreads; index++) { + PlatformThread::Join(thread_handles[index]); + delete thread_delegates[index]; + } + } + MultipleThreadMain::CleanUp(); +} + +// TODO(port): this test requires the MultipleLockThread class +// (defined above), which requires the ability to pass file +// descriptors between processes. We haven't done that yet in Chrome +// for POSIX. +#if defined(OS_WIN) +// Create a set of threads to each open a shared memory segment and write to it +// with the lock held. Verify that they are always reading/writing consistent +// data. +TEST(SharedMemoryTest, Lock) { + PlatformThreadHandle thread_handles[kNumThreads]; + MultipleLockThread* thread_delegates[kNumThreads]; + + // Spawn the threads. + for (int index = 0; index < kNumThreads; ++index) { + PlatformThreadHandle pth; + thread_delegates[index] = new MultipleLockThread(index); + EXPECT_TRUE(PlatformThread::Create(0, thread_delegates[index], &pth)); + thread_handles[index] = pth; + } + + // Wait for the threads to finish. + for (int index = 0; index < kNumThreads; ++index) { + PlatformThread::Join(thread_handles[index]); + delete thread_delegates[index]; + } +} +#endif + +// Allocate private (unique) shared memory with an empty string for a +// name. Make sure several of them don't point to the same thing as +// we might expect if the names are equal. +TEST(SharedMemoryTest, AnonymousPrivate) { + int i, j; + int count = 4; + bool rv; + const uint32 kDataSize = 8192; + + scoped_ptr memories(new SharedMemory[count]); + scoped_ptr pointers(new int*[count]); + ASSERT_TRUE(memories.get()); + ASSERT_TRUE(pointers.get()); + + for (i = 0; i < count; i++) { + rv = memories[i].CreateAndMapAnonymous(kDataSize); + EXPECT_TRUE(rv); + int *ptr = static_cast(memories[i].memory()); + EXPECT_TRUE(ptr); + pointers[i] = ptr; + } + + for (i = 0; i < count; i++) { + // zero out the first int in each except for i; for that one, make it 100. + for (j = 0; j < count; j++) { + if (i == j) + pointers[j][0] = 100; + else + pointers[j][0] = 0; + } + // make sure there is no bleeding of the 100 into the other pointers + for (j = 0; j < count; j++) { + if (i == j) + EXPECT_EQ(100, pointers[j][0]); + else + EXPECT_EQ(0, pointers[j][0]); + } + } + + for (int i = 0; i < count; i++) { + memories[i].Close(); + } +} + +TEST(SharedMemoryTest, MapAt) { + ASSERT_TRUE(SysInfo::VMAllocationGranularity() >= sizeof(uint32)); + const size_t kCount = SysInfo::VMAllocationGranularity(); + const size_t kDataSize = kCount * sizeof(uint32); + + SharedMemory memory; + ASSERT_TRUE(memory.CreateAndMapAnonymous(kDataSize)); + ASSERT_TRUE(memory.Map(kDataSize)); + uint32* ptr = static_cast(memory.memory()); + ASSERT_NE(ptr, static_cast(NULL)); + + for (size_t i = 0; i < kCount; ++i) { + ptr[i] = i; + } + + memory.Unmap(); + + off_t offset = SysInfo::VMAllocationGranularity(); + ASSERT_TRUE(memory.MapAt(offset, kDataSize - offset)); + offset /= sizeof(uint32); + ptr = static_cast(memory.memory()); + ASSERT_NE(ptr, static_cast(NULL)); + for (size_t i = offset; i < kCount; ++i) { + EXPECT_EQ(ptr[i - offset], i); + } +} + +#if defined(OS_POSIX) +// Create a shared memory object, mmap it, and mprotect it to PROT_EXEC. +TEST(SharedMemoryTest, AnonymousExecutable) { + const uint32 kTestSize = 1 << 16; + + SharedMemory shared_memory; + SharedMemoryCreateOptions options; + options.size = kTestSize; + options.executable = true; + + EXPECT_TRUE(shared_memory.Create(options)); + EXPECT_TRUE(shared_memory.Map(shared_memory.requested_size())); + + EXPECT_EQ(0, mprotect(shared_memory.memory(), shared_memory.requested_size(), + PROT_READ | PROT_EXEC)); +} + +// Android supports a different permission model than POSIX for its "ashmem" +// shared memory implementation. So the tests about file permissions are not +// included on Android. +#if !defined(OS_ANDROID) + +// Set a umask and restore the old mask on destruction. +class ScopedUmaskSetter { + public: + explicit ScopedUmaskSetter(mode_t target_mask) { + old_umask_ = umask(target_mask); + } + ~ScopedUmaskSetter() { umask(old_umask_); } + private: + mode_t old_umask_; + DISALLOW_IMPLICIT_CONSTRUCTORS(ScopedUmaskSetter); +}; + +// Create a shared memory object, check its permissions. +TEST(SharedMemoryTest, FilePermissionsAnonymous) { + const uint32 kTestSize = 1 << 8; + + SharedMemory shared_memory; + SharedMemoryCreateOptions options; + options.size = kTestSize; + // Set a file mode creation mask that gives all permissions. + ScopedUmaskSetter permissive_mask(S_IWGRP | S_IWOTH); + + EXPECT_TRUE(shared_memory.Create(options)); + + int shm_fd = shared_memory.handle().fd; + struct stat shm_stat; + EXPECT_EQ(0, fstat(shm_fd, &shm_stat)); + // Neither the group, nor others should be able to read the shared memory + // file. + EXPECT_FALSE(shm_stat.st_mode & S_IRWXO); + EXPECT_FALSE(shm_stat.st_mode & S_IRWXG); +} + +// Create a shared memory object, check its permissions. +TEST(SharedMemoryTest, FilePermissionsNamed) { + const uint32 kTestSize = 1 << 8; + + SharedMemory shared_memory; + SharedMemoryCreateOptions options; + options.size = kTestSize; + std::string shared_mem_name = "shared_perm_test-" + IntToString(getpid()) + + "-" + Uint64ToString(RandUint64()); + options.name = &shared_mem_name; + // Set a file mode creation mask that gives all permissions. + ScopedUmaskSetter permissive_mask(S_IWGRP | S_IWOTH); + + EXPECT_TRUE(shared_memory.Create(options)); + // Clean-up the backing file name immediately, we don't need it. + EXPECT_TRUE(shared_memory.Delete(shared_mem_name)); + + int shm_fd = shared_memory.handle().fd; + struct stat shm_stat; + EXPECT_EQ(0, fstat(shm_fd, &shm_stat)); + // Neither the group, nor others should have been able to open the shared + // memory file while its name existed. + EXPECT_FALSE(shm_stat.st_mode & S_IRWXO); + EXPECT_FALSE(shm_stat.st_mode & S_IRWXG); +} +#endif // !defined(OS_ANDROID) + +#endif // defined(OS_POSIX) + +// Map() will return addresses which are aligned to the platform page size, this +// varies from platform to platform though. Since we'd like to advertise a +// minimum alignment that callers can count on, test for it here. +TEST(SharedMemoryTest, MapMinimumAlignment) { + static const int kDataSize = 8192; + + SharedMemory shared_memory; + ASSERT_TRUE(shared_memory.CreateAndMapAnonymous(kDataSize)); + EXPECT_EQ(0U, reinterpret_cast( + shared_memory.memory()) & (SharedMemory::MAP_MINIMUM_ALIGNMENT - 1)); + shared_memory.Close(); +} + +#if !defined(OS_IOS) // iOS does not allow multiple processes. + +// On POSIX it is especially important we test shmem across processes, +// not just across threads. But the test is enabled on all platforms. +class SharedMemoryProcessTest : public MultiProcessTest { + public: + + static void CleanUp() { + SharedMemory memory; + memory.Delete(s_test_name_); + } + + static int TaskTestMain() { + int errors = 0; +#if defined(OS_MACOSX) + mac::ScopedNSAutoreleasePool pool; +#endif + const uint32 kDataSize = 1024; + SharedMemory memory; + bool rv = memory.CreateNamed(s_test_name_, true, kDataSize); + EXPECT_TRUE(rv); + if (rv != true) + errors++; + rv = memory.Map(kDataSize); + EXPECT_TRUE(rv); + if (rv != true) + errors++; + int *ptr = static_cast(memory.memory()); + + for (int idx = 0; idx < 20; idx++) { + memory.Lock(); + int i = (1 << 16) + idx; + *ptr = i; + PlatformThread::Sleep(TimeDelta::FromMilliseconds(10)); + if (*ptr != i) + errors++; + memory.Unlock(); + } + + memory.Close(); + return errors; + } + + private: + static const char* const s_test_name_; +}; + +const char* const SharedMemoryProcessTest::s_test_name_ = "MPMem"; + +TEST_F(SharedMemoryProcessTest, Tasks) { + SharedMemoryProcessTest::CleanUp(); + + ProcessHandle handles[kNumTasks]; + for (int index = 0; index < kNumTasks; ++index) { + handles[index] = SpawnChild("SharedMemoryTestMain", false); + ASSERT_TRUE(handles[index]); + } + + int exit_code = 0; + for (int index = 0; index < kNumTasks; ++index) { + EXPECT_TRUE(WaitForExitCode(handles[index], &exit_code)); + EXPECT_EQ(0, exit_code); + } + + SharedMemoryProcessTest::CleanUp(); +} + +MULTIPROCESS_TEST_MAIN(SharedMemoryTestMain) { + return SharedMemoryProcessTest::TaskTestMain(); +} + +#endif // !OS_IOS + +} // namespace base diff --git a/base/memory/shared_memory_win.cc b/base/memory/shared_memory_win.cc new file mode 100644 index 0000000000..42e0b046b9 --- /dev/null +++ b/base/memory/shared_memory_win.cc @@ -0,0 +1,260 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/shared_memory.h" + +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" + +namespace { + +// Returns the length of the memory section starting at the supplied address. +size_t GetMemorySectionSize(void* address) { + MEMORY_BASIC_INFORMATION memory_info; + if (!::VirtualQuery(address, &memory_info, sizeof(memory_info))) + return 0; + return memory_info.RegionSize - (static_cast(address) - + static_cast(memory_info.AllocationBase)); +} + +} // namespace. + +namespace base { + +SharedMemory::SharedMemory() + : mapped_file_(NULL), + memory_(NULL), + read_only_(false), + mapped_size_(0), + requested_size_(0), + lock_(NULL) { +} + +SharedMemory::SharedMemory(const std::wstring& name) + : mapped_file_(NULL), + memory_(NULL), + read_only_(false), + requested_size_(0), + mapped_size_(0), + lock_(NULL), + name_(name) { +} + +SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only) + : mapped_file_(handle), + memory_(NULL), + read_only_(read_only), + requested_size_(0), + mapped_size_(0), + lock_(NULL) { +} + +SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only, + ProcessHandle process) + : mapped_file_(NULL), + memory_(NULL), + read_only_(read_only), + requested_size_(0), + mapped_size_(0), + lock_(NULL) { + ::DuplicateHandle(process, handle, + GetCurrentProcess(), &mapped_file_, + STANDARD_RIGHTS_REQUIRED | + (read_only_ ? FILE_MAP_READ : FILE_MAP_ALL_ACCESS), + FALSE, 0); +} + +SharedMemory::~SharedMemory() { + Close(); + if (lock_ != NULL) + CloseHandle(lock_); +} + +// static +bool SharedMemory::IsHandleValid(const SharedMemoryHandle& handle) { + return handle != NULL; +} + +// static +SharedMemoryHandle SharedMemory::NULLHandle() { + return NULL; +} + +// static +void SharedMemory::CloseHandle(const SharedMemoryHandle& handle) { + DCHECK(handle != NULL); + ::CloseHandle(handle); +} + +// static +size_t SharedMemory::GetHandleLimit() { + // Rounded down from value reported here: + // http://blogs.technet.com/b/markrussinovich/archive/2009/09/29/3283844.aspx + return static_cast(1 << 23); +} + +bool SharedMemory::CreateAndMapAnonymous(size_t size) { + return CreateAnonymous(size) && Map(size); +} + +bool SharedMemory::Create(const SharedMemoryCreateOptions& options) { + // TODO(bsy,sehr): crbug.com/210609 NaCl forces us to round up 64k here, + // wasting 32k per mapping on average. + static const size_t kSectionMask = 65536 - 1; + DCHECK(!options.executable); + DCHECK(!mapped_file_); + if (options.size == 0) + return false; + + // Check maximum accounting for overflow. + if (options.size > + static_cast(std::numeric_limits::max()) - kSectionMask) + return false; + + size_t rounded_size = (options.size + kSectionMask) & ~kSectionMask; + name_ = ASCIIToWide(options.name == NULL ? "" : *options.name); + mapped_file_ = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, + PAGE_READWRITE, 0, static_cast(rounded_size), + name_.empty() ? NULL : name_.c_str()); + if (!mapped_file_) + return false; + + requested_size_ = options.size; + + // Check if the shared memory pre-exists. + if (GetLastError() == ERROR_ALREADY_EXISTS) { + // If the file already existed, set requested_size_ to 0 to show that + // we don't know the size. + requested_size_ = 0; + if (!options.open_existing) { + Close(); + return false; + } + } + + return true; +} + +bool SharedMemory::Delete(const std::string& name) { + // intentionally empty -- there is nothing for us to do on Windows. + return true; +} + +bool SharedMemory::Open(const std::string& name, bool read_only) { + DCHECK(!mapped_file_); + + name_ = ASCIIToWide(name); + read_only_ = read_only; + mapped_file_ = OpenFileMapping( + read_only_ ? FILE_MAP_READ : FILE_MAP_ALL_ACCESS, false, + name_.empty() ? NULL : name_.c_str()); + if (mapped_file_ != NULL) { + // Note: size_ is not set in this case. + return true; + } + return false; +} + +bool SharedMemory::MapAt(off_t offset, size_t bytes) { + if (mapped_file_ == NULL) + return false; + + if (bytes > static_cast(std::numeric_limits::max())) + return false; + + memory_ = MapViewOfFile(mapped_file_, + read_only_ ? FILE_MAP_READ : FILE_MAP_ALL_ACCESS, + static_cast(offset) >> 32, + static_cast(offset), + bytes); + if (memory_ != NULL) { + DCHECK_EQ(0U, reinterpret_cast(memory_) & + (SharedMemory::MAP_MINIMUM_ALIGNMENT - 1)); + mapped_size_ = GetMemorySectionSize(memory_); + return true; + } + return false; +} + +bool SharedMemory::Unmap() { + if (memory_ == NULL) + return false; + + UnmapViewOfFile(memory_); + memory_ = NULL; + return true; +} + +bool SharedMemory::ShareToProcessCommon(ProcessHandle process, + SharedMemoryHandle *new_handle, + bool close_self) { + *new_handle = 0; + DWORD access = STANDARD_RIGHTS_REQUIRED | FILE_MAP_READ; + DWORD options = 0; + HANDLE mapped_file = mapped_file_; + HANDLE result; + if (!read_only_) + access |= FILE_MAP_WRITE; + if (close_self) { + // DUPLICATE_CLOSE_SOURCE causes DuplicateHandle to close mapped_file. + options = DUPLICATE_CLOSE_SOURCE; + mapped_file_ = NULL; + Unmap(); + } + + if (process == GetCurrentProcess() && close_self) { + *new_handle = mapped_file; + return true; + } + + if (!DuplicateHandle(GetCurrentProcess(), mapped_file, process, + &result, access, FALSE, options)) + return false; + *new_handle = result; + return true; +} + + +void SharedMemory::Close() { + if (memory_ != NULL) { + UnmapViewOfFile(memory_); + memory_ = NULL; + } + + if (mapped_file_ != NULL) { + CloseHandle(mapped_file_); + mapped_file_ = NULL; + } +} + +void SharedMemory::Lock() { + Lock(INFINITE, NULL); +} + +bool SharedMemory::Lock(uint32 timeout_ms, SECURITY_ATTRIBUTES* sec_attr) { + if (lock_ == NULL) { + std::wstring name = name_; + name.append(L"lock"); + lock_ = CreateMutex(sec_attr, FALSE, name.c_str()); + if (lock_ == NULL) { + DPLOG(ERROR) << "Could not create mutex."; + return false; // there is nothing good we can do here. + } + } + DWORD result = WaitForSingleObject(lock_, timeout_ms); + + // Return false for WAIT_ABANDONED, WAIT_TIMEOUT or WAIT_FAILED. + return (result == WAIT_OBJECT_0); +} + +void SharedMemory::Unlock() { + DCHECK(lock_ != NULL); + ReleaseMutex(lock_); +} + +SharedMemoryHandle SharedMemory::handle() const { + return mapped_file_; +} + +} // namespace base diff --git a/base/memory/singleton.cc b/base/memory/singleton.cc new file mode 100644 index 0000000000..ee5e58d005 --- /dev/null +++ b/base/memory/singleton.cc @@ -0,0 +1,31 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/singleton.h" +#include "base/threading/platform_thread.h" + +namespace base { +namespace internal { + +subtle::AtomicWord WaitForInstance(subtle::AtomicWord* instance) { + // Handle the race. Another thread beat us and either: + // - Has the object in BeingCreated state + // - Already has the object created... + // We know value != NULL. It could be kBeingCreatedMarker, or a valid ptr. + // Unless your constructor can be very time consuming, it is very unlikely + // to hit this race. When it does, we just spin and yield the thread until + // the object has been created. + subtle::AtomicWord value; + while (true) { + value = subtle::NoBarrier_Load(instance); + if (value != kBeingCreatedMarker) + break; + PlatformThread::YieldCurrentThread(); + } + return value; +} + +} // namespace internal +} // namespace base + diff --git a/base/memory/singleton.h b/base/memory/singleton.h new file mode 100644 index 0000000000..0d4fc8990c --- /dev/null +++ b/base/memory/singleton.h @@ -0,0 +1,285 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// PLEASE READ: Do you really need a singleton? +// +// Singletons make it hard to determine the lifetime of an object, which can +// lead to buggy code and spurious crashes. +// +// Instead of adding another singleton into the mix, try to identify either: +// a) An existing singleton that can manage your object's lifetime +// b) Locations where you can deterministically create the object and pass +// into other objects +// +// If you absolutely need a singleton, please keep them as trivial as possible +// and ideally a leaf dependency. Singletons get problematic when they attempt +// to do too much in their destructor or have circular dependencies. + +#ifndef BASE_MEMORY_SINGLETON_H_ +#define BASE_MEMORY_SINGLETON_H_ + +#include "base/at_exit.h" +#include "base/atomicops.h" +#include "base/base_export.h" +#include "base/memory/aligned_memory.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" +#include "base/threading/thread_restrictions.h" + +namespace base { +namespace internal { + +// Our AtomicWord doubles as a spinlock, where a value of +// kBeingCreatedMarker means the spinlock is being held for creation. +static const subtle::AtomicWord kBeingCreatedMarker = 1; + +// We pull out some of the functionality into a non-templated function, so that +// we can implement the more complicated pieces out of line in the .cc file. +BASE_EXPORT subtle::AtomicWord WaitForInstance(subtle::AtomicWord* instance); + +} // namespace internal +} // namespace base + +// TODO(joth): Move more of this file into namespace base + +// Default traits for Singleton. Calls operator new and operator delete on +// the object. Registers automatic deletion at process exit. +// Overload if you need arguments or another memory allocation function. +template +struct DefaultSingletonTraits { + // Allocates the object. + static Type* New() { + // The parenthesis is very important here; it forces POD type + // initialization. + return new Type(); + } + + // Destroys the object. + static void Delete(Type* x) { + delete x; + } + + // Set to true to automatically register deletion of the object on process + // exit. See below for the required call that makes this happen. + static const bool kRegisterAtExit = true; + + // Set to false to disallow access on a non-joinable thread. This is + // different from kRegisterAtExit because StaticMemorySingletonTraits allows + // access on non-joinable threads, and gracefully handles this. + static const bool kAllowedToAccessOnNonjoinableThread = false; +}; + + +// Alternate traits for use with the Singleton. Identical to +// DefaultSingletonTraits except that the Singleton will not be cleaned up +// at exit. +template +struct LeakySingletonTraits : public DefaultSingletonTraits { + static const bool kRegisterAtExit = false; + static const bool kAllowedToAccessOnNonjoinableThread = true; +}; + + +// Alternate traits for use with the Singleton. Allocates memory +// for the singleton instance from a static buffer. The singleton will +// be cleaned up at exit, but can't be revived after destruction unless +// the Resurrect() method is called. +// +// This is useful for a certain category of things, notably logging and +// tracing, where the singleton instance is of a type carefully constructed to +// be safe to access post-destruction. +// In logging and tracing you'll typically get stray calls at odd times, like +// during static destruction, thread teardown and the like, and there's a +// termination race on the heap-based singleton - e.g. if one thread calls +// get(), but then another thread initiates AtExit processing, the first thread +// may call into an object residing in unallocated memory. If the instance is +// allocated from the data segment, then this is survivable. +// +// The destructor is to deallocate system resources, in this case to unregister +// a callback the system will invoke when logging levels change. Note that +// this is also used in e.g. Chrome Frame, where you have to allow for the +// possibility of loading briefly into someone else's process space, and +// so leaking is not an option, as that would sabotage the state of your host +// process once you've unloaded. +template +struct StaticMemorySingletonTraits { + // WARNING: User has to deal with get() in the singleton class + // this is traits for returning NULL. + static Type* New() { + // Only constructs once and returns pointer; otherwise returns NULL. + if (base::subtle::NoBarrier_AtomicExchange(&dead_, 1)) + return NULL; + + return new(buffer_.void_data()) Type(); + } + + static void Delete(Type* p) { + if (p != NULL) + p->Type::~Type(); + } + + static const bool kRegisterAtExit = true; + static const bool kAllowedToAccessOnNonjoinableThread = true; + + // Exposed for unittesting. + static void Resurrect() { + base::subtle::NoBarrier_Store(&dead_, 0); + } + + private: + static base::AlignedMemory buffer_; + // Signal the object was already deleted, so it is not revived. + static base::subtle::Atomic32 dead_; +}; + +template base::AlignedMemory + StaticMemorySingletonTraits::buffer_; +template base::subtle::Atomic32 + StaticMemorySingletonTraits::dead_ = 0; + +// The Singleton class manages a single +// instance of Type which will be created on first use and will be destroyed at +// normal process exit). The Trait::Delete function will not be called on +// abnormal process exit. +// +// DifferentiatingType is used as a key to differentiate two different +// singletons having the same memory allocation functions but serving a +// different purpose. This is mainly used for Locks serving different purposes. +// +// Example usage: +// +// In your header: +// template struct DefaultSingletonTraits; +// class FooClass { +// public: +// static FooClass* GetInstance(); <-- See comment below on this. +// void Bar() { ... } +// private: +// FooClass() { ... } +// friend struct DefaultSingletonTraits; +// +// DISALLOW_COPY_AND_ASSIGN(FooClass); +// }; +// +// In your source file: +// #include "base/memory/singleton.h" +// FooClass* FooClass::GetInstance() { +// return Singleton::get(); +// } +// +// And to call methods on FooClass: +// FooClass::GetInstance()->Bar(); +// +// NOTE: The method accessing Singleton::get() has to be named as GetInstance +// and it is important that FooClass::GetInstance() is not inlined in the +// header. This makes sure that when source files from multiple targets include +// this header they don't end up with different copies of the inlined code +// creating multiple copies of the singleton. +// +// Singleton<> has no non-static members and doesn't need to actually be +// instantiated. +// +// This class is itself thread-safe. The underlying Type must of course be +// thread-safe if you want to use it concurrently. Two parameters may be tuned +// depending on the user's requirements. +// +// Glossary: +// RAE = kRegisterAtExit +// +// On every platform, if Traits::RAE is true, the singleton will be destroyed at +// process exit. More precisely it uses base::AtExitManager which requires an +// object of this type to be instantiated. AtExitManager mimics the semantics +// of atexit() such as LIFO order but under Windows is safer to call. For more +// information see at_exit.h. +// +// If Traits::RAE is false, the singleton will not be freed at process exit, +// thus the singleton will be leaked if it is ever accessed. Traits::RAE +// shouldn't be false unless absolutely necessary. Remember that the heap where +// the object is allocated may be destroyed by the CRT anyway. +// +// Caveats: +// (a) Every call to get(), operator->() and operator*() incurs some overhead +// (16ns on my P4/2.8GHz) to check whether the object has already been +// initialized. You may wish to cache the result of get(); it will not +// change. +// +// (b) Your factory function must never throw an exception. This class is not +// exception-safe. +// +template , + typename DifferentiatingType = Type> +class Singleton { + private: + // Classes using the Singleton pattern should declare a GetInstance() + // method and call Singleton::get() from within that. + friend Type* Type::GetInstance(); + + // Allow TraceLog tests to test tracing after OnExit. + friend class DeleteTraceLogForTesting; + + // This class is safe to be constructed and copy-constructed since it has no + // member. + + // Return a pointer to the one true instance of the class. + static Type* get() { +#ifndef NDEBUG + // Avoid making TLS lookup on release builds. + if (!Traits::kAllowedToAccessOnNonjoinableThread) + base::ThreadRestrictions::AssertSingletonAllowed(); +#endif + + base::subtle::AtomicWord value = base::subtle::NoBarrier_Load(&instance_); + if (value != 0 && value != base::internal::kBeingCreatedMarker) { + // See the corresponding HAPPENS_BEFORE below. + ANNOTATE_HAPPENS_AFTER(&instance_); + return reinterpret_cast(value); + } + + // Object isn't created yet, maybe we will get to create it, let's try... + if (base::subtle::Acquire_CompareAndSwap( + &instance_, 0, base::internal::kBeingCreatedMarker) == 0) { + // instance_ was NULL and is now kBeingCreatedMarker. Only one thread + // will ever get here. Threads might be spinning on us, and they will + // stop right after we do this store. + Type* newval = Traits::New(); + + // This annotation helps race detectors recognize correct lock-less + // synchronization between different threads calling get(). + // See the corresponding HAPPENS_AFTER below and above. + ANNOTATE_HAPPENS_BEFORE(&instance_); + base::subtle::Release_Store( + &instance_, reinterpret_cast(newval)); + + if (newval != NULL && Traits::kRegisterAtExit) + base::AtExitManager::RegisterCallback(OnExit, NULL); + + return newval; + } + + // We hit a race. Wait for the other thread to complete it. + value = base::internal::WaitForInstance(&instance_); + + // See the corresponding HAPPENS_BEFORE above. + ANNOTATE_HAPPENS_AFTER(&instance_); + return reinterpret_cast(value); + } + + // Adapter function for use with AtExit(). This should be called single + // threaded, so don't use atomic operations. + // Calling OnExit while singleton is in use by other threads is a mistake. + static void OnExit(void* /*unused*/) { + // AtExit should only ever be register after the singleton instance was + // created. We should only ever get here with a valid instance_ pointer. + Traits::Delete( + reinterpret_cast(base::subtle::NoBarrier_Load(&instance_))); + instance_ = 0; + } + static base::subtle::AtomicWord instance_; +}; + +template +base::subtle::AtomicWord Singleton:: + instance_ = 0; + +#endif // BASE_MEMORY_SINGLETON_H_ diff --git a/base/memory/singleton_objc.h b/base/memory/singleton_objc.h new file mode 100644 index 0000000000..6df3f7757e --- /dev/null +++ b/base/memory/singleton_objc.h @@ -0,0 +1,60 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Support for using the Singleton pattern with Objective-C objects. A +// SingletonObjC is the same as a Singleton, except the default traits are +// appropriate for Objective-C objects. A typical Objective-C object of type +// NSExampleType can be maintained as a singleton and accessed with: +// +// NSExampleType* exampleSingleton = SingletonObjC::get(); +// +// The first time this is used, it will create exampleSingleton as the result +// of [[NSExampleType alloc] init]. Subsequent calls will return the same +// NSExampleType* object. The object will be released by calling +// -[NSExampleType release] when Singleton's atexit routines run +// (see singleton.h). +// +// For Objective-C objects initialized through means other than the +// no-parameter -init selector, DefaultSingletonObjCTraits may be extended +// as needed: +// +// struct FooSingletonTraits : public DefaultSingletonObjCTraits { +// static Foo* New() { +// return [[Foo alloc] initWithName:@"selecty"]; +// } +// }; +// ... +// Foo* widgetSingleton = SingletonObjC::get(); + +#ifndef BASE_MEMORY_SINGLETON_OBJC_H_ +#define BASE_MEMORY_SINGLETON_OBJC_H_ + +#import +#include "base/memory/singleton.h" + +// Singleton traits usable to manage traditional Objective-C objects, which +// are instantiated by sending |alloc| and |init| messages, and are deallocated +// in a memory-managed environment when their retain counts drop to 0 by +// sending |release| messages. +template +struct DefaultSingletonObjCTraits : public DefaultSingletonTraits { + static Type* New() { + return [[Type alloc] init]; + } + + static void Delete(Type* object) { + [object release]; + } +}; + +// Exactly like Singleton, but without the DefaultSingletonObjCTraits as the +// default trait class. This makes it straightforward for Objective-C++ code +// to hold Objective-C objects as singletons. +template, + typename DifferentiatingType = Type> +class SingletonObjC : public Singleton { +}; + +#endif // BASE_MEMORY_SINGLETON_OBJC_H_ diff --git a/base/memory/singleton_unittest.cc b/base/memory/singleton_unittest.cc new file mode 100644 index 0000000000..5d059043af --- /dev/null +++ b/base/memory/singleton_unittest.cc @@ -0,0 +1,288 @@ +// Copyright (c) 2012 The Chromium Authors. All 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/memory/singleton.h" +#include "base/path_service.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +COMPILE_ASSERT(DefaultSingletonTraits::kRegisterAtExit == true, a); + +typedef void (*CallbackFunc)(); + +class IntSingleton { + public: + static IntSingleton* GetInstance() { + return Singleton::get(); + } + + int value_; +}; + +class Init5Singleton { + public: + struct Trait; + + static Init5Singleton* GetInstance() { + return Singleton::get(); + } + + int value_; +}; + +struct Init5Singleton::Trait : public DefaultSingletonTraits { + static Init5Singleton* New() { + Init5Singleton* instance = new Init5Singleton(); + instance->value_ = 5; + return instance; + } +}; + +int* SingletonInt() { + return &IntSingleton::GetInstance()->value_; +} + +int* SingletonInt5() { + return &Init5Singleton::GetInstance()->value_; +} + +template +struct CallbackTrait : public DefaultSingletonTraits { + static void Delete(Type* instance) { + if (instance->callback_) + (instance->callback_)(); + DefaultSingletonTraits::Delete(instance); + } +}; + +class CallbackSingleton { + public: + CallbackSingleton() : callback_(NULL) { } + CallbackFunc callback_; +}; + +class CallbackSingletonWithNoLeakTrait : public CallbackSingleton { + public: + struct Trait : public CallbackTrait { }; + + CallbackSingletonWithNoLeakTrait() : CallbackSingleton() { } + + static CallbackSingletonWithNoLeakTrait* GetInstance() { + return Singleton::get(); + } +}; + +class CallbackSingletonWithLeakTrait : public CallbackSingleton { + public: + struct Trait : public CallbackTrait { + static const bool kRegisterAtExit = false; + }; + + CallbackSingletonWithLeakTrait() : CallbackSingleton() { } + + static CallbackSingletonWithLeakTrait* GetInstance() { + return Singleton::get(); + } +}; + +class CallbackSingletonWithStaticTrait : public CallbackSingleton { + public: + struct Trait; + + CallbackSingletonWithStaticTrait() : CallbackSingleton() { } + + static CallbackSingletonWithStaticTrait* GetInstance() { + return Singleton::get(); + } +}; + +struct CallbackSingletonWithStaticTrait::Trait + : public StaticMemorySingletonTraits { + static void Delete(CallbackSingletonWithStaticTrait* instance) { + if (instance->callback_) + (instance->callback_)(); + StaticMemorySingletonTraits::Delete( + instance); + } +}; + +template +class AlignedTestSingleton { + public: + AlignedTestSingleton() {} + ~AlignedTestSingleton() {} + static AlignedTestSingleton* GetInstance() { + return Singleton >::get(); + } + + Type type_; +}; + + +void SingletonNoLeak(CallbackFunc CallOnQuit) { + CallbackSingletonWithNoLeakTrait::GetInstance()->callback_ = CallOnQuit; +} + +void SingletonLeak(CallbackFunc CallOnQuit) { + CallbackSingletonWithLeakTrait::GetInstance()->callback_ = CallOnQuit; +} + +CallbackFunc* GetLeakySingleton() { + return &CallbackSingletonWithLeakTrait::GetInstance()->callback_; +} + +void DeleteLeakySingleton() { + DefaultSingletonTraits::Delete( + CallbackSingletonWithLeakTrait::GetInstance()); +} + +void SingletonStatic(CallbackFunc CallOnQuit) { + CallbackSingletonWithStaticTrait::GetInstance()->callback_ = CallOnQuit; +} + +CallbackFunc* GetStaticSingleton() { + return &CallbackSingletonWithStaticTrait::GetInstance()->callback_; +} + +} // namespace + +class SingletonTest : public testing::Test { + public: + SingletonTest() {} + + virtual void SetUp() OVERRIDE { + non_leak_called_ = false; + leaky_called_ = false; + static_called_ = false; + } + + protected: + void VerifiesCallbacks() { + EXPECT_TRUE(non_leak_called_); + EXPECT_FALSE(leaky_called_); + EXPECT_TRUE(static_called_); + non_leak_called_ = false; + leaky_called_ = false; + static_called_ = false; + } + + void VerifiesCallbacksNotCalled() { + EXPECT_FALSE(non_leak_called_); + EXPECT_FALSE(leaky_called_); + EXPECT_FALSE(static_called_); + non_leak_called_ = false; + leaky_called_ = false; + static_called_ = false; + } + + static void CallbackNoLeak() { + non_leak_called_ = true; + } + + static void CallbackLeak() { + leaky_called_ = true; + } + + static void CallbackStatic() { + static_called_ = true; + } + + private: + static bool non_leak_called_; + static bool leaky_called_; + static bool static_called_; +}; + +bool SingletonTest::non_leak_called_ = false; +bool SingletonTest::leaky_called_ = false; +bool SingletonTest::static_called_ = false; + +TEST_F(SingletonTest, Basic) { + int* singleton_int; + int* singleton_int_5; + CallbackFunc* leaky_singleton; + CallbackFunc* static_singleton; + + { + base::ShadowingAtExitManager sem; + { + singleton_int = SingletonInt(); + } + // Ensure POD type initialization. + EXPECT_EQ(*singleton_int, 0); + *singleton_int = 1; + + EXPECT_EQ(singleton_int, SingletonInt()); + EXPECT_EQ(*singleton_int, 1); + + { + singleton_int_5 = SingletonInt5(); + } + // Is default initialized to 5. + EXPECT_EQ(*singleton_int_5, 5); + + SingletonNoLeak(&CallbackNoLeak); + SingletonLeak(&CallbackLeak); + SingletonStatic(&CallbackStatic); + static_singleton = GetStaticSingleton(); + leaky_singleton = GetLeakySingleton(); + EXPECT_TRUE(leaky_singleton); + } + + // Verify that only the expected callback has been called. + VerifiesCallbacks(); + // Delete the leaky singleton. + DeleteLeakySingleton(); + + // The static singleton can't be acquired post-atexit. + EXPECT_EQ(NULL, GetStaticSingleton()); + + { + base::ShadowingAtExitManager sem; + // Verifiy that the variables were reset. + { + singleton_int = SingletonInt(); + EXPECT_EQ(*singleton_int, 0); + } + { + singleton_int_5 = SingletonInt5(); + EXPECT_EQ(*singleton_int_5, 5); + } + { + // Resurrect the static singleton, and assert that it + // still points to the same (static) memory. + CallbackSingletonWithStaticTrait::Trait::Resurrect(); + EXPECT_EQ(GetStaticSingleton(), static_singleton); + } + } + // The leaky singleton shouldn't leak since SingletonLeak has not been called. + VerifiesCallbacksNotCalled(); +} + +#define EXPECT_ALIGNED(ptr, align) \ + EXPECT_EQ(0u, reinterpret_cast(ptr) & (align - 1)) + +TEST_F(SingletonTest, Alignment) { + using base::AlignedMemory; + + // Create some static singletons with increasing sizes and alignment + // requirements. By ordering this way, the linker will need to do some work to + // ensure proper alignment of the static data. + AlignedTestSingleton* align4 = + AlignedTestSingleton::GetInstance(); + AlignedTestSingleton >* align32 = + AlignedTestSingleton >::GetInstance(); + AlignedTestSingleton >* align128 = + AlignedTestSingleton >::GetInstance(); + AlignedTestSingleton >* align4096 = + AlignedTestSingleton >::GetInstance(); + + EXPECT_ALIGNED(align4, 4); + EXPECT_ALIGNED(align32, 32); + EXPECT_ALIGNED(align128, 128); + EXPECT_ALIGNED(align4096, 4096); +} diff --git a/base/memory/weak_ptr.cc b/base/memory/weak_ptr.cc new file mode 100644 index 0000000000..d9ce86ad18 --- /dev/null +++ b/base/memory/weak_ptr.cc @@ -0,0 +1,77 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/weak_ptr.h" + +namespace base { +namespace internal { + +WeakReference::Flag::Flag() : is_valid_(true) { + // Flags only become bound when checked for validity, or invalidated, + // so that we can check that later validity/invalidation operations on + // the same Flag take place on the same sequenced thread. + sequence_checker_.DetachFromSequence(); +} + +void WeakReference::Flag::Invalidate() { + // The flag being invalidated with a single ref implies that there are no + // weak pointers in existence. Allow deletion on other thread in this case. + DCHECK(sequence_checker_.CalledOnValidSequencedThread() || HasOneRef()) + << "WeakPtrs must be invalidated on the same sequenced thread."; + is_valid_ = false; +} + +bool WeakReference::Flag::IsValid() const { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()) + << "WeakPtrs must be checked on the same sequenced thread."; + return is_valid_; +} + +WeakReference::Flag::~Flag() { +} + +WeakReference::WeakReference() { +} + +WeakReference::WeakReference(const Flag* flag) : flag_(flag) { +} + +WeakReference::~WeakReference() { +} + +bool WeakReference::is_valid() const { return flag_.get() && flag_->IsValid(); } + +WeakReferenceOwner::WeakReferenceOwner() { +} + +WeakReferenceOwner::~WeakReferenceOwner() { + Invalidate(); +} + +WeakReference WeakReferenceOwner::GetRef() const { + // If we hold the last reference to the Flag then create a new one. + if (!HasRefs()) + flag_ = new WeakReference::Flag(); + + return WeakReference(flag_.get()); +} + +void WeakReferenceOwner::Invalidate() { + if (flag_.get()) { + flag_->Invalidate(); + flag_ = NULL; + } +} + +WeakPtrBase::WeakPtrBase() { +} + +WeakPtrBase::~WeakPtrBase() { +} + +WeakPtrBase::WeakPtrBase(const WeakReference& ref) : ref_(ref) { +} + +} // namespace internal +} // namespace base diff --git a/base/memory/weak_ptr.h b/base/memory/weak_ptr.h new file mode 100644 index 0000000000..1675889e22 --- /dev/null +++ b/base/memory/weak_ptr.h @@ -0,0 +1,338 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Weak pointers are pointers to an object that do not affect its lifetime, +// and which may be invalidated (i.e. reset to NULL) by the object, or its +// owner, at any time, most commonly when the object is about to be deleted. + +// Weak pointers are useful when an object needs to be accessed safely by one +// or more objects other than its owner, and those callers can cope with the +// object vanishing and e.g. tasks posted to it being silently dropped. +// Reference-counting such an object would complicate the ownership graph and +// make it harder to reason about the object's lifetime. + +// EXAMPLE: +// +// class Controller { +// public: +// void SpawnWorker() { Worker::StartNew(weak_factory_.GetWeakPtr()); } +// void WorkComplete(const Result& result) { ... } +// private: +// // Member variables should appear before the WeakPtrFactory, to ensure +// // that any WeakPtrs to Controller are invalidated before its members +// // variable's destructors are executed, rendering them invalid. +// WeakPtrFactory weak_factory_; +// }; +// +// class Worker { +// public: +// static void StartNew(const WeakPtr& controller) { +// Worker* worker = new Worker(controller); +// // Kick off asynchronous processing... +// } +// private: +// Worker(const WeakPtr& controller) +// : controller_(controller) {} +// void DidCompleteAsynchronousProcessing(const Result& result) { +// if (controller_) +// controller_->WorkComplete(result); +// } +// WeakPtr controller_; +// }; +// +// With this implementation a caller may use SpawnWorker() to dispatch multiple +// Workers and subsequently delete the Controller, without waiting for all +// Workers to have completed. + +// ------------------------- IMPORTANT: Thread-safety ------------------------- + +// Weak pointers may be passed safely between threads, but must always be +// dereferenced and invalidated on the same thread otherwise checking the +// pointer would be racey. +// +// To ensure correct use, the first time a WeakPtr issued by a WeakPtrFactory +// is dereferenced, the factory and its WeakPtrs become bound to the calling +// thread, and cannot be dereferenced or invalidated on any other thread. Bound +// WeakPtrs can still be handed off to other threads, e.g. to use to post tasks +// back to object on the bound thread. +// +// Invalidating the factory's WeakPtrs un-binds it from the thread, allowing it +// to be passed for a different thread to use or delete it. + +#ifndef BASE_MEMORY_WEAK_PTR_H_ +#define BASE_MEMORY_WEAK_PTR_H_ + +#include "base/basictypes.h" +#include "base/base_export.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/sequence_checker.h" +#include "base/template_util.h" + +namespace base { + +template class SupportsWeakPtr; +template class WeakPtr; + +namespace internal { +// These classes are part of the WeakPtr implementation. +// DO NOT USE THESE CLASSES DIRECTLY YOURSELF. + +class BASE_EXPORT WeakReference { + public: + // Although Flag is bound to a specific thread, it may be deleted from another + // via base::WeakPtr::~WeakPtr(). + class Flag : public RefCountedThreadSafe { + public: + Flag(); + + void Invalidate(); + bool IsValid() const; + + private: + friend class base::RefCountedThreadSafe; + + ~Flag(); + + SequenceChecker sequence_checker_; + bool is_valid_; + }; + + WeakReference(); + explicit WeakReference(const Flag* flag); + ~WeakReference(); + + bool is_valid() const; + + private: + scoped_refptr flag_; +}; + +class BASE_EXPORT WeakReferenceOwner { + public: + WeakReferenceOwner(); + ~WeakReferenceOwner(); + + WeakReference GetRef() const; + + bool HasRefs() const { + return flag_.get() && !flag_->HasOneRef(); + } + + void Invalidate(); + + private: + mutable scoped_refptr flag_; +}; + +// This class simplifies the implementation of WeakPtr's type conversion +// constructor by avoiding the need for a public accessor for ref_. A +// WeakPtr cannot access the private members of WeakPtr, so this +// base class gives us a way to access ref_ in a protected fashion. +class BASE_EXPORT WeakPtrBase { + public: + WeakPtrBase(); + ~WeakPtrBase(); + + protected: + explicit WeakPtrBase(const WeakReference& ref); + + WeakReference ref_; +}; + +// This class provides a common implementation of common functions that would +// otherwise get instantiated separately for each distinct instantiation of +// SupportsWeakPtr<>. +class SupportsWeakPtrBase { + public: + // A safe static downcast of a WeakPtr to WeakPtr. This + // conversion will only compile if there is exists a Base which inherits + // from SupportsWeakPtr. See base::AsWeakPtr() below for a helper + // function that makes calling this easier. + template + static WeakPtr StaticAsWeakPtr(Derived* t) { + typedef + is_convertible convertible; + COMPILE_ASSERT(convertible::value, + AsWeakPtr_argument_inherits_from_SupportsWeakPtr); + return AsWeakPtrImpl(t, *t); + } + + private: + // This template function uses type inference to find a Base of Derived + // which is an instance of SupportsWeakPtr. We can then safely + // static_cast the Base* to a Derived*. + template + static WeakPtr AsWeakPtrImpl( + Derived* t, const SupportsWeakPtr&) { + WeakPtr ptr = t->Base::AsWeakPtr(); + return WeakPtr(ptr.ref_, static_cast(ptr.ptr_)); + } +}; + +} // namespace internal + +template class WeakPtrFactory; + +// The WeakPtr class holds a weak reference to |T*|. +// +// This class is designed to be used like a normal pointer. You should always +// null-test an object of this class before using it or invoking a method that +// may result in the underlying object being destroyed. +// +// EXAMPLE: +// +// class Foo { ... }; +// WeakPtr foo; +// if (foo) +// foo->method(); +// +template +class WeakPtr : public internal::WeakPtrBase { + public: + WeakPtr() : ptr_(NULL) { + } + + // Allow conversion from U to T provided U "is a" T. Note that this + // is separate from the (implicit) copy constructor. + template + WeakPtr(const WeakPtr& other) : WeakPtrBase(other), ptr_(other.ptr_) { + } + + T* get() const { return ref_.is_valid() ? ptr_ : NULL; } + + T& operator*() const { + DCHECK(get() != NULL); + return *get(); + } + T* operator->() const { + DCHECK(get() != NULL); + return get(); + } + + // Allow WeakPtr to be used in boolean expressions, but not + // implicitly convertible to a real bool (which is dangerous). + // + // Note that this trick is only safe when the == and != operators + // are declared explicitly, as otherwise "weak_ptr1 == weak_ptr2" + // will compile but do the wrong thing (i.e., convert to Testable + // and then do the comparison). + private: + typedef T* WeakPtr::*Testable; + + public: + operator Testable() const { return get() ? &WeakPtr::ptr_ : NULL; } + + void reset() { + ref_ = internal::WeakReference(); + ptr_ = NULL; + } + + private: + // Explicitly declare comparison operators as required by the bool + // trick, but keep them private. + template bool operator==(WeakPtr const&) const; + template bool operator!=(WeakPtr const&) const; + + friend class internal::SupportsWeakPtrBase; + template friend class WeakPtr; + friend class SupportsWeakPtr; + friend class WeakPtrFactory; + + WeakPtr(const internal::WeakReference& ref, T* ptr) + : WeakPtrBase(ref), + ptr_(ptr) { + } + + // This pointer is only valid when ref_.is_valid() is true. Otherwise, its + // value is undefined (as opposed to NULL). + T* ptr_; +}; + +// A class may be composed of a WeakPtrFactory and thereby +// control how it exposes weak pointers to itself. This is helpful if you only +// need weak pointers within the implementation of a class. This class is also +// useful when working with primitive types. For example, you could have a +// WeakPtrFactory that is used to pass around a weak reference to a bool. +template +class WeakPtrFactory { + public: + explicit WeakPtrFactory(T* ptr) : ptr_(ptr) { + } + + ~WeakPtrFactory() { + ptr_ = NULL; + } + + WeakPtr GetWeakPtr() { + DCHECK(ptr_); + return WeakPtr(weak_reference_owner_.GetRef(), ptr_); + } + + // Call this method to invalidate all existing weak pointers. + void InvalidateWeakPtrs() { + DCHECK(ptr_); + weak_reference_owner_.Invalidate(); + } + + // Call this method to determine if any weak pointers exist. + bool HasWeakPtrs() const { + DCHECK(ptr_); + return weak_reference_owner_.HasRefs(); + } + + private: + internal::WeakReferenceOwner weak_reference_owner_; + T* ptr_; + DISALLOW_IMPLICIT_CONSTRUCTORS(WeakPtrFactory); +}; + +// A class may extend from SupportsWeakPtr to let others take weak pointers to +// it. This avoids the class itself implementing boilerplate to dispense weak +// pointers. However, since SupportsWeakPtr's destructor won't invalidate +// weak pointers to the class until after the derived class' members have been +// destroyed, its use can lead to subtle use-after-destroy issues. +template +class SupportsWeakPtr : public internal::SupportsWeakPtrBase { + public: + SupportsWeakPtr() {} + + WeakPtr AsWeakPtr() { + return WeakPtr(weak_reference_owner_.GetRef(), static_cast(this)); + } + + protected: + ~SupportsWeakPtr() {} + + private: + internal::WeakReferenceOwner weak_reference_owner_; + DISALLOW_COPY_AND_ASSIGN(SupportsWeakPtr); +}; + +// Helper function that uses type deduction to safely return a WeakPtr +// when Derived doesn't directly extend SupportsWeakPtr, instead it +// extends a Base that extends SupportsWeakPtr. +// +// EXAMPLE: +// class Base : public base::SupportsWeakPtr {}; +// class Derived : public Base {}; +// +// Derived derived; +// base::WeakPtr ptr = base::AsWeakPtr(&derived); +// +// Note that the following doesn't work (invalid type conversion) since +// Derived::AsWeakPtr() is WeakPtr SupportsWeakPtr::AsWeakPtr(), +// and there's no way to safely cast WeakPtr to WeakPtr at +// the caller. +// +// base::WeakPtr ptr = derived.AsWeakPtr(); // Fails. + +template +WeakPtr AsWeakPtr(Derived* t) { + return internal::SupportsWeakPtrBase::StaticAsWeakPtr(t); +} + +} // namespace base + +#endif // BASE_MEMORY_WEAK_PTR_H_ diff --git a/base/memory/weak_ptr_unittest.cc b/base/memory/weak_ptr_unittest.cc new file mode 100644 index 0000000000..e7e12a272d --- /dev/null +++ b/base/memory/weak_ptr_unittest.cc @@ -0,0 +1,606 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/weak_ptr.h" + +#include + +#include "base/bind.h" +#include "base/debug/leak_annotations.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace { + +template +class OffThreadObjectCreator { + public: + static T* NewObject() { + T* result; + { + Thread creator_thread("creator_thread"); + creator_thread.Start(); + creator_thread.message_loop()->PostTask( + FROM_HERE, + base::Bind(OffThreadObjectCreator::CreateObject, &result)); + } + DCHECK(result); // We synchronized on thread destruction above. + return result; + } + private: + static void CreateObject(T** result) { + *result = new T; + } +}; + +struct Base { + std::string member; +}; +struct Derived : public Base {}; + +struct TargetBase {}; +struct Target : public TargetBase, public SupportsWeakPtr {}; +struct DerivedTarget : public Target {}; +struct Arrow { + WeakPtr target; +}; +struct TargetWithFactory : public Target { + TargetWithFactory() : factory(this) {} + WeakPtrFactory factory; +}; + +// Helper class to create and destroy weak pointer copies +// and delete objects on a background thread. +class BackgroundThread : public Thread { + public: + BackgroundThread() : Thread("owner_thread") {} + + virtual ~BackgroundThread() { + Stop(); + } + + void CreateArrowFromTarget(Arrow** arrow, Target* target) { + WaitableEvent completion(true, false); + message_loop()->PostTask( + FROM_HERE, + base::Bind(&BackgroundThread::DoCreateArrowFromTarget, + arrow, target, &completion)); + completion.Wait(); + } + + void CreateArrowFromArrow(Arrow** arrow, const Arrow* other) { + WaitableEvent completion(true, false); + message_loop()->PostTask( + FROM_HERE, + base::Bind(&BackgroundThread::DoCreateArrowFromArrow, + arrow, other, &completion)); + completion.Wait(); + } + + void DeleteTarget(Target* object) { + WaitableEvent completion(true, false); + message_loop()->PostTask( + FROM_HERE, + base::Bind(&BackgroundThread::DoDeleteTarget, object, &completion)); + completion.Wait(); + } + + void CopyAndAssignArrow(Arrow* object) { + WaitableEvent completion(true, false); + message_loop()->PostTask( + FROM_HERE, + base::Bind(&BackgroundThread::DoCopyAndAssignArrow, + object, &completion)); + completion.Wait(); + } + + void CopyAndAssignArrowBase(Arrow* object) { + WaitableEvent completion(true, false); + message_loop()->PostTask( + FROM_HERE, + base::Bind(&BackgroundThread::DoCopyAndAssignArrowBase, + object, &completion)); + completion.Wait(); + } + + void DeleteArrow(Arrow* object) { + WaitableEvent completion(true, false); + message_loop()->PostTask( + FROM_HERE, + base::Bind(&BackgroundThread::DoDeleteArrow, object, &completion)); + completion.Wait(); + } + + Target* DeRef(const Arrow* arrow) { + WaitableEvent completion(true, false); + Target* result = NULL; + message_loop()->PostTask( + FROM_HERE, + base::Bind(&BackgroundThread::DoDeRef, arrow, &result, &completion)); + completion.Wait(); + return result; + } + + protected: + static void DoCreateArrowFromArrow(Arrow** arrow, + const Arrow* other, + WaitableEvent* completion) { + *arrow = new Arrow; + **arrow = *other; + completion->Signal(); + } + + static void DoCreateArrowFromTarget(Arrow** arrow, + Target* target, + WaitableEvent* completion) { + *arrow = new Arrow; + (*arrow)->target = target->AsWeakPtr(); + completion->Signal(); + } + + static void DoDeRef(const Arrow* arrow, + Target** result, + WaitableEvent* completion) { + *result = arrow->target.get(); + completion->Signal(); + } + + static void DoDeleteTarget(Target* object, WaitableEvent* completion) { + delete object; + completion->Signal(); + } + + static void DoCopyAndAssignArrow(Arrow* object, WaitableEvent* completion) { + // Copy constructor. + Arrow a = *object; + // Assignment operator. + *object = a; + completion->Signal(); + } + + static void DoCopyAndAssignArrowBase( + Arrow* object, + WaitableEvent* completion) { + // Copy constructor. + WeakPtr b = object->target; + // Assignment operator. + WeakPtr c; + c = object->target; + completion->Signal(); + } + + static void DoDeleteArrow(Arrow* object, WaitableEvent* completion) { + delete object; + completion->Signal(); + } +}; + +} // namespace + +TEST(WeakPtrFactoryTest, Basic) { + int data; + WeakPtrFactory factory(&data); + WeakPtr ptr = factory.GetWeakPtr(); + EXPECT_EQ(&data, ptr.get()); +} + +TEST(WeakPtrFactoryTest, Comparison) { + int data; + WeakPtrFactory factory(&data); + WeakPtr ptr = factory.GetWeakPtr(); + WeakPtr ptr2 = ptr; + EXPECT_EQ(ptr.get(), ptr2.get()); +} + +TEST(WeakPtrFactoryTest, OutOfScope) { + WeakPtr ptr; + EXPECT_EQ(NULL, ptr.get()); + { + int data; + WeakPtrFactory factory(&data); + ptr = factory.GetWeakPtr(); + } + EXPECT_EQ(NULL, ptr.get()); +} + +TEST(WeakPtrFactoryTest, Multiple) { + WeakPtr a, b; + { + int data; + WeakPtrFactory factory(&data); + a = factory.GetWeakPtr(); + b = factory.GetWeakPtr(); + EXPECT_EQ(&data, a.get()); + EXPECT_EQ(&data, b.get()); + } + EXPECT_EQ(NULL, a.get()); + EXPECT_EQ(NULL, b.get()); +} + +TEST(WeakPtrFactoryTest, MultipleStaged) { + WeakPtr a; + { + int data; + WeakPtrFactory factory(&data); + a = factory.GetWeakPtr(); + { + WeakPtr b = factory.GetWeakPtr(); + } + EXPECT_TRUE(NULL != a.get()); + } + EXPECT_EQ(NULL, a.get()); +} + +TEST(WeakPtrFactoryTest, Dereference) { + Base data; + data.member = "123456"; + WeakPtrFactory factory(&data); + WeakPtr ptr = factory.GetWeakPtr(); + EXPECT_EQ(&data, ptr.get()); + EXPECT_EQ(data.member, (*ptr).member); + EXPECT_EQ(data.member, ptr->member); +} + +TEST(WeakPtrFactoryTest, UpCast) { + Derived data; + WeakPtrFactory factory(&data); + WeakPtr ptr = factory.GetWeakPtr(); + ptr = factory.GetWeakPtr(); + EXPECT_EQ(ptr.get(), &data); +} + +TEST(WeakPtrTest, SupportsWeakPtr) { + Target target; + WeakPtr ptr = target.AsWeakPtr(); + EXPECT_EQ(&target, ptr.get()); +} + +TEST(WeakPtrTest, DerivedTarget) { + DerivedTarget target; + WeakPtr ptr = AsWeakPtr(&target); + EXPECT_EQ(&target, ptr.get()); +} + +TEST(WeakPtrTest, InvalidateWeakPtrs) { + int data; + WeakPtrFactory factory(&data); + WeakPtr ptr = factory.GetWeakPtr(); + EXPECT_EQ(&data, ptr.get()); + EXPECT_TRUE(factory.HasWeakPtrs()); + factory.InvalidateWeakPtrs(); + EXPECT_EQ(NULL, ptr.get()); + EXPECT_FALSE(factory.HasWeakPtrs()); +} + +TEST(WeakPtrTest, HasWeakPtrs) { + int data; + WeakPtrFactory factory(&data); + { + WeakPtr ptr = factory.GetWeakPtr(); + EXPECT_TRUE(factory.HasWeakPtrs()); + } + EXPECT_FALSE(factory.HasWeakPtrs()); +} + +TEST(WeakPtrTest, ObjectAndWeakPtrOnDifferentThreads) { + // Test that it is OK to create an object that supports WeakPtr on one thread, + // but use it on another. This tests that we do not trip runtime checks that + // ensure that a WeakPtr is not used by multiple threads. + scoped_ptr target(OffThreadObjectCreator::NewObject()); + WeakPtr weak_ptr = target->AsWeakPtr(); + EXPECT_EQ(target.get(), weak_ptr.get()); +} + +TEST(WeakPtrTest, WeakPtrInitiateAndUseOnDifferentThreads) { + // Test that it is OK to create an object that has a WeakPtr member on one + // thread, but use it on another. This tests that we do not trip runtime + // checks that ensure that a WeakPtr is not used by multiple threads. + scoped_ptr arrow(OffThreadObjectCreator::NewObject()); + Target target; + arrow->target = target.AsWeakPtr(); + EXPECT_EQ(&target, arrow->target.get()); +} + +TEST(WeakPtrTest, MoveOwnershipImplicitly) { + // Move object ownership to another thread by releasing all weak pointers + // on the original thread first, and then establish WeakPtr on a different + // thread. + BackgroundThread background; + background.Start(); + + Target* target = new Target(); + { + WeakPtr weak_ptr = target->AsWeakPtr(); + // Main thread deletes the WeakPtr, then the thread ownership of the + // object can be implicitly moved. + } + Arrow* arrow; + + // Background thread creates WeakPtr(and implicitly owns the object). + background.CreateArrowFromTarget(&arrow, target); + EXPECT_EQ(background.DeRef(arrow), target); + + { + // Main thread creates another WeakPtr, but this does not trigger implicitly + // thread ownership move. + Arrow arrow; + arrow.target = target->AsWeakPtr(); + + // The new WeakPtr is owned by background thread. + EXPECT_EQ(target, background.DeRef(&arrow)); + } + + // Target can only be deleted on background thread. + background.DeleteTarget(target); + background.DeleteArrow(arrow); +} + +TEST(WeakPtrTest, MoveOwnershipOfUnreferencedObject) { + BackgroundThread background; + background.Start(); + + Arrow* arrow; + { + Target target; + // Background thread creates WeakPtr. + background.CreateArrowFromTarget(&arrow, &target); + + // Bind to background thread. + EXPECT_EQ(&target, background.DeRef(arrow)); + + // Release the only WeakPtr. + arrow->target.reset(); + + // Now we should be able to create a new reference from this thread. + arrow->target = target.AsWeakPtr(); + + // Re-bind to main thread. + EXPECT_EQ(&target, arrow->target.get()); + + // And the main thread can now delete the target. + } + + delete arrow; +} + +TEST(WeakPtrTest, MoveOwnershipAfterInvalidate) { + BackgroundThread background; + background.Start(); + + Arrow arrow; + scoped_ptr target(new TargetWithFactory); + + // Bind to main thread. + arrow.target = target->factory.GetWeakPtr(); + EXPECT_EQ(target.get(), arrow.target.get()); + + target->factory.InvalidateWeakPtrs(); + EXPECT_EQ(NULL, arrow.target.get()); + + arrow.target = target->factory.GetWeakPtr(); + // Re-bind to background thread. + EXPECT_EQ(target.get(), background.DeRef(&arrow)); + + // And the background thread can now delete the target. + background.DeleteTarget(target.release()); +} + +TEST(WeakPtrTest, MainThreadRefOutlivesBackgroundThreadRef) { + // Originating thread has a WeakPtr that outlives others. + // - Main thread creates a WeakPtr + // - Background thread creates a WeakPtr copy from the one in main thread + // - Destruct the WeakPtr on background thread + // - Destruct the WeakPtr on main thread + BackgroundThread background; + background.Start(); + + Target target; + Arrow arrow; + arrow.target = target.AsWeakPtr(); + + Arrow* arrow_copy; + background.CreateArrowFromArrow(&arrow_copy, &arrow); + EXPECT_EQ(arrow_copy->target.get(), &target); + background.DeleteArrow(arrow_copy); +} + +TEST(WeakPtrTest, BackgroundThreadRefOutlivesMainThreadRef) { + // Originating thread drops all references before another thread. + // - Main thread creates a WeakPtr and passes copy to background thread + // - Destruct the pointer on main thread + // - Destruct the pointer on background thread + BackgroundThread background; + background.Start(); + + Target target; + Arrow* arrow_copy; + { + Arrow arrow; + arrow.target = target.AsWeakPtr(); + background.CreateArrowFromArrow(&arrow_copy, &arrow); + } + EXPECT_EQ(arrow_copy->target.get(), &target); + background.DeleteArrow(arrow_copy); +} + +TEST(WeakPtrTest, OwnerThreadDeletesObject) { + // Originating thread invalidates WeakPtrs while its held by other thread. + // - Main thread creates WeakPtr and passes Copy to background thread + // - Object gets destroyed on main thread + // (invalidates WeakPtr on background thread) + // - WeakPtr gets destroyed on Thread B + BackgroundThread background; + background.Start(); + Arrow* arrow_copy; + { + Target target; + Arrow arrow; + arrow.target = target.AsWeakPtr(); + background.CreateArrowFromArrow(&arrow_copy, &arrow); + } + EXPECT_EQ(NULL, arrow_copy->target.get()); + background.DeleteArrow(arrow_copy); +} + +TEST(WeakPtrTest, NonOwnerThreadCanCopyAndAssignWeakPtr) { + // Main thread creates a Target object. + Target target; + // Main thread creates an arrow referencing the Target. + Arrow *arrow = new Arrow(); + arrow->target = target.AsWeakPtr(); + + // Background can copy and assign arrow (as well as the WeakPtr inside). + BackgroundThread background; + background.Start(); + background.CopyAndAssignArrow(arrow); + background.DeleteArrow(arrow); +} + +TEST(WeakPtrTest, NonOwnerThreadCanCopyAndAssignWeakPtrBase) { + // Main thread creates a Target object. + Target target; + // Main thread creates an arrow referencing the Target. + Arrow *arrow = new Arrow(); + arrow->target = target.AsWeakPtr(); + + // Background can copy and assign arrow's WeakPtr to a base class WeakPtr. + BackgroundThread background; + background.Start(); + background.CopyAndAssignArrowBase(arrow); + background.DeleteArrow(arrow); +} + +TEST(WeakPtrTest, NonOwnerThreadCanDeleteWeakPtr) { + // Main thread creates a Target object. + Target target; + // Main thread creates an arrow referencing the Target. + Arrow* arrow = new Arrow(); + arrow->target = target.AsWeakPtr(); + + // Background can delete arrow (as well as the WeakPtr inside). + BackgroundThread background; + background.Start(); + background.DeleteArrow(arrow); +} + +#if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) && GTEST_HAS_DEATH_TEST + +TEST(WeakPtrDeathTest, WeakPtrCopyDoesNotChangeThreadBinding) { + // The default style "fast" does not support multi-threaded tests + // (introduces deadlock on Linux). + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; + + BackgroundThread background; + background.Start(); + + // Main thread creates a Target object. + Target target; + // Main thread creates an arrow referencing the Target. + Arrow arrow; + arrow.target = target.AsWeakPtr(); + + // Background copies the WeakPtr. + Arrow* arrow_copy; + background.CreateArrowFromArrow(&arrow_copy, &arrow); + + // The copy is still bound to main thread so I can deref. + EXPECT_EQ(arrow.target.get(), arrow_copy->target.get()); + + // Although background thread created the copy, it can not deref the copied + // WeakPtr. + ASSERT_DEATH(background.DeRef(arrow_copy), ""); + + background.DeleteArrow(arrow_copy); +} + +TEST(WeakPtrDeathTest, NonOwnerThreadDereferencesWeakPtrAfterReference) { + // The default style "fast" does not support multi-threaded tests + // (introduces deadlock on Linux). + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; + + // Main thread creates a Target object. + Target target; + + // Main thread creates an arrow referencing the Target (so target's + // thread ownership can not be implicitly moved). + Arrow arrow; + arrow.target = target.AsWeakPtr(); + arrow.target.get(); + + // Background thread tries to deref target, which violates thread ownership. + BackgroundThread background; + background.Start(); + ASSERT_DEATH(background.DeRef(&arrow), ""); +} + +TEST(WeakPtrDeathTest, NonOwnerThreadDeletesWeakPtrAfterReference) { + // The default style "fast" does not support multi-threaded tests + // (introduces deadlock on Linux). + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; + + scoped_ptr target(new Target()); + + // Main thread creates an arrow referencing the Target. + Arrow arrow; + arrow.target = target->AsWeakPtr(); + + // Background thread tries to deref target, binding it to the thread. + BackgroundThread background; + background.Start(); + background.DeRef(&arrow); + + // Main thread deletes Target, violating thread binding. + ASSERT_DEATH(target.reset(), ""); + + // |target.reset()| died so |target| still holds the object, so we + // must pass it to the background thread to teardown. + background.DeleteTarget(target.release()); +} + +TEST(WeakPtrDeathTest, NonOwnerThreadDeletesObjectAfterReference) { + // The default style "fast" does not support multi-threaded tests + // (introduces deadlock on Linux). + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; + + scoped_ptr target(new Target()); + + // Main thread creates an arrow referencing the Target, and references it, so + // that it becomes bound to the thread. + Arrow arrow; + arrow.target = target->AsWeakPtr(); + arrow.target.get(); + + // Background thread tries to delete target, volating thread binding. + BackgroundThread background; + background.Start(); + ASSERT_DEATH(background.DeleteTarget(target.release()), ""); +} + +TEST(WeakPtrDeathTest, NonOwnerThreadReferencesObjectAfterDeletion) { + // The default style "fast" does not support multi-threaded tests + // (introduces deadlock on Linux). + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; + + scoped_ptr target(new Target()); + + // Main thread creates an arrow referencing the Target. + Arrow arrow; + arrow.target = target->AsWeakPtr(); + + // Background thread tries to delete target, binding the object to the thread. + BackgroundThread background; + background.Start(); + background.DeleteTarget(target.release()); + + // Main thread attempts to dereference the target, violating thread binding. + ASSERT_DEATH(arrow.target.get(), ""); +} + +#endif + +} // namespace base diff --git a/base/memory/weak_ptr_unittest.nc b/base/memory/weak_ptr_unittest.nc new file mode 100644 index 0000000000..a2da8e9779 --- /dev/null +++ b/base/memory/weak_ptr_unittest.nc @@ -0,0 +1,138 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/weak_ptr.h" + +namespace base { + +struct Producer : SupportsWeakPtr {}; +struct DerivedProducer : Producer {}; +struct OtherDerivedProducer : Producer {}; +struct MultiplyDerivedProducer : Producer, + SupportsWeakPtr {}; +struct Unrelated {}; +struct DerivedUnrelated : Unrelated {}; + +#if defined(NCTEST_AUTO_DOWNCAST) // [r"invalid conversion from"] + +void WontCompile() { + Producer f; + WeakPtr ptr = f.AsWeakPtr(); + WeakPtr derived_ptr = ptr; +} + +#elif defined(NCTEST_STATIC_DOWNCAST) // [r"invalid conversion from"] + +void WontCompile() { + Producer f; + WeakPtr ptr = f.AsWeakPtr(); + WeakPtr derived_ptr = + static_cast >(ptr); +} + +#elif defined(NCTEST_AUTO_REF_DOWNCAST) // [r"invalid initialization of reference"] + +void WontCompile() { + Producer f; + WeakPtr ptr = f.AsWeakPtr(); + WeakPtr& derived_ptr = ptr; +} + +#elif defined(NCTEST_STATIC_REF_DOWNCAST) // [r"invalid static_cast"] + +void WontCompile() { + Producer f; + WeakPtr ptr = f.AsWeakPtr(); + WeakPtr& derived_ptr = + static_cast&>(ptr); +} + +#elif defined(NCTEST_STATIC_ASWEAKPTR_DOWNCAST) // [r"no matching function"] + +void WontCompile() { + Producer f; + WeakPtr ptr = + SupportsWeakPtr::StaticAsWeakPtr(&f); +} + +#elif defined(NCTEST_UNSAFE_HELPER_DOWNCAST) // [r"invalid conversion from"] + +void WontCompile() { + Producer f; + WeakPtr ptr = AsWeakPtr(&f); +} + +#elif defined(NCTEST_UNSAFE_INSTANTIATED_HELPER_DOWNCAST) // [r"no matching function"] + +void WontCompile() { + Producer f; + WeakPtr ptr = AsWeakPtr(&f); +} + +#elif defined(NCTEST_UNSAFE_WRONG_INSANTIATED_HELPER_DOWNCAST) // [r"invalid conversion from"] + +void WontCompile() { + Producer f; + WeakPtr ptr = AsWeakPtr(&f); +} + +#elif defined(NCTEST_UNSAFE_HELPER_CAST) // [r"cannot convert"] + +void WontCompile() { + DerivedProducer f; + WeakPtr ptr = AsWeakPtr(&f); +} + +#elif defined(NCTEST_UNSAFE_INSTANTIATED_HELPER_SIDECAST) // [r"no matching function"] + +void WontCompile() { + DerivedProducer f; + WeakPtr ptr = AsWeakPtr(&f); +} + +#elif defined(NCTEST_UNSAFE_WRONG_INSTANTIATED_HELPER_SIDECAST) // [r"cannot convert"] + +void WontCompile() { + DerivedProducer f; + WeakPtr ptr = AsWeakPtr(&f); +} + +#elif defined(NCTEST_UNRELATED_HELPER) // [r"cannot convert"] + +void WontCompile() { + DerivedProducer f; + WeakPtr ptr = AsWeakPtr(&f); +} + +#elif defined(NCTEST_UNRELATED_INSTANTIATED_HELPER) // [r"no matching function"] + +void WontCompile() { + DerivedProducer f; + WeakPtr ptr = AsWeakPtr(&f); +} + +#elif defined(NCTEST_COMPLETELY_UNRELATED_HELPER) // [r"size of array is negative"] + +void WontCompile() { + Unrelated f; + WeakPtr ptr = AsWeakPtr(&f); +} + +#elif defined(NCTEST_DERIVED_COMPLETELY_UNRELATED_HELPER) // [r"size of array is negative"] + +void WontCompile() { + DerivedUnrelated f; + WeakPtr ptr = AsWeakPtr(&f); +} + +#elif defined(NCTEST_AMBIGUOUS_ANCESTORS) // [r"ambiguous base"] + +void WontCompile() { + MultiplyDerivedProducer f; + WeakPtr ptr = AsWeakPtr(&f); +} + +#endif + +} diff --git a/base/message_loop/incoming_task_queue.cc b/base/message_loop/incoming_task_queue.cc new file mode 100644 index 0000000000..db99d8750c --- /dev/null +++ b/base/message_loop/incoming_task_queue.cc @@ -0,0 +1,169 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/message_loop/incoming_task_queue.h" + +#include "base/debug/trace_event.h" +#include "base/location.h" +#include "base/message_loop/message_loop.h" +#include "base/synchronization/waitable_event.h" + +namespace base { +namespace internal { + +IncomingTaskQueue::IncomingTaskQueue(MessageLoop* message_loop) + : message_loop_(message_loop), + next_sequence_num_(0) { +} + +bool IncomingTaskQueue::AddToIncomingQueue( + const tracked_objects::Location& from_here, + const Closure& task, + TimeDelta delay, + bool nestable) { + AutoLock locked(incoming_queue_lock_); + PendingTask pending_task( + from_here, task, CalculateDelayedRuntime(delay), nestable); + return PostPendingTask(&pending_task); +} + +bool IncomingTaskQueue::TryAddToIncomingQueue( + const tracked_objects::Location& from_here, + const Closure& task) { + if (!incoming_queue_lock_.Try()) { + // Reset |task|. + Closure local_task = task; + return false; + } + + AutoLock locked(incoming_queue_lock_, AutoLock::AlreadyAcquired()); + PendingTask pending_task( + from_here, task, CalculateDelayedRuntime(TimeDelta()), true); + return PostPendingTask(&pending_task); +} + +bool IncomingTaskQueue::IsHighResolutionTimerEnabledForTesting() { +#if defined(OS_WIN) + return !high_resolution_timer_expiration_.is_null(); +#else + return true; +#endif +} + +bool IncomingTaskQueue::IsIdleForTesting() { + AutoLock lock(incoming_queue_lock_); + return incoming_queue_.empty(); +} + +void IncomingTaskQueue::LockWaitUnLockForTesting(WaitableEvent* caller_wait, + WaitableEvent* caller_signal) { + AutoLock lock(incoming_queue_lock_); + caller_wait->Signal(); + caller_signal->Wait(); +} + +void IncomingTaskQueue::ReloadWorkQueue(TaskQueue* work_queue) { + // Make sure no tasks are lost. + DCHECK(work_queue->empty()); + + // Acquire all we can from the inter-thread queue with one lock acquisition. + AutoLock lock(incoming_queue_lock_); + if (!incoming_queue_.empty()) + incoming_queue_.Swap(work_queue); // Constant time + + DCHECK(incoming_queue_.empty()); +} + +void IncomingTaskQueue::WillDestroyCurrentMessageLoop() { +#if defined(OS_WIN) + // If we left the high-resolution timer activated, deactivate it now. + // Doing this is not-critical, it is mainly to make sure we track + // the high resolution timer activations properly in our unit tests. + if (!high_resolution_timer_expiration_.is_null()) { + Time::ActivateHighResolutionTimer(false); + high_resolution_timer_expiration_ = TimeTicks(); + } +#endif + + AutoLock lock(incoming_queue_lock_); + message_loop_ = NULL; +} + +IncomingTaskQueue::~IncomingTaskQueue() { + // Verify that WillDestroyCurrentMessageLoop() has been called. + DCHECK(!message_loop_); +} + +TimeTicks IncomingTaskQueue::CalculateDelayedRuntime(TimeDelta delay) { + TimeTicks delayed_run_time; + if (delay > TimeDelta()) { + delayed_run_time = TimeTicks::Now() + delay; + +#if defined(OS_WIN) + if (high_resolution_timer_expiration_.is_null()) { + // Windows timers are granular to 15.6ms. If we only set high-res + // timers for those under 15.6ms, then a 18ms timer ticks at ~32ms, + // which as a percentage is pretty inaccurate. So enable high + // res timers for any timer which is within 2x of the granularity. + // This is a tradeoff between accuracy and power management. + bool needs_high_res_timers = delay.InMilliseconds() < + (2 * Time::kMinLowResolutionThresholdMs); + if (needs_high_res_timers) { + if (Time::ActivateHighResolutionTimer(true)) { + high_resolution_timer_expiration_ = TimeTicks::Now() + + TimeDelta::FromMilliseconds( + MessageLoop::kHighResolutionTimerModeLeaseTimeMs); + } + } + } +#endif + } else { + DCHECK_EQ(delay.InMilliseconds(), 0) << "delay should not be negative"; + } + +#if defined(OS_WIN) + if (!high_resolution_timer_expiration_.is_null()) { + if (TimeTicks::Now() > high_resolution_timer_expiration_) { + Time::ActivateHighResolutionTimer(false); + high_resolution_timer_expiration_ = TimeTicks(); + } + } +#endif + + return delayed_run_time; +} + +bool IncomingTaskQueue::PostPendingTask(PendingTask* pending_task) { + // Warning: Don't try to short-circuit, and handle this thread's tasks more + // directly, as it could starve handling of foreign threads. Put every task + // into this queue. + + // This should only be called while the lock is taken. + incoming_queue_lock_.AssertAcquired(); + + if (!message_loop_) { + pending_task->task.Reset(); + return false; + } + + // Initialize the sequence number. The sequence number is used for delayed + // tasks (to faciliate FIFO sorting when two tasks have the same + // delayed_run_time value) and for identifying the task in about:tracing. + pending_task->sequence_num = next_sequence_num_++; + + TRACE_EVENT_FLOW_BEGIN0("task", "MessageLoop::PostTask", + TRACE_ID_MANGLE(message_loop_->GetTaskTraceID(*pending_task))); + + bool was_empty = incoming_queue_.empty(); + incoming_queue_.push(*pending_task); + pending_task->task.Reset(); + + // Wake up the pump. + message_loop_->ScheduleWork(was_empty); + + return true; +} + +} // namespace internal +} // namespace base diff --git a/base/message_loop/incoming_task_queue.h b/base/message_loop/incoming_task_queue.h new file mode 100644 index 0000000000..d831a71a3f --- /dev/null +++ b/base/message_loop/incoming_task_queue.h @@ -0,0 +1,104 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MESSAGE_LOOP_INCOMING_TASK_QUEUE_H_ +#define BASE_MESSAGE_LOOP_INCOMING_TASK_QUEUE_H_ + +#include "base/base_export.h" +#include "base/memory/ref_counted.h" +#include "base/pending_task.h" +#include "base/synchronization/lock.h" +#include "base/time/time.h" + +namespace base { + +class MessageLoop; +class WaitableEvent; + +namespace internal { + +// Implements a queue of tasks posted to the message loop running on the current +// thread. This class takes care of synchronizing posting tasks from different +// threads and together with MessageLoop ensures clean shutdown. +class BASE_EXPORT IncomingTaskQueue + : public RefCountedThreadSafe { + public: + explicit IncomingTaskQueue(MessageLoop* message_loop); + + // Appends a task to the incoming queue. Posting of all tasks is routed though + // AddToIncomingQueue() or TryAddToIncomingQueue() to make sure that posting + // task is properly synchronized between different threads. + // + // Returns true if the task was successfully added to the queue, otherwise + // returns false. In all cases, the ownership of |task| is transferred to the + // called method. + bool AddToIncomingQueue(const tracked_objects::Location& from_here, + const Closure& task, + TimeDelta delay, + bool nestable); + + // Same as AddToIncomingQueue() except that it will avoid blocking if the lock + // is already held, and will in that case (when the lock is contended) fail to + // add the task, and will return false. + bool TryAddToIncomingQueue(const tracked_objects::Location& from_here, + const Closure& task); + + // Returns true if the message loop has high resolution timers enabled. + // Provided for testing. + bool IsHighResolutionTimerEnabledForTesting(); + + // Returns true if the message loop is "idle". Provided for testing. + bool IsIdleForTesting(); + + // Takes the incoming queue lock, signals |caller_wait| and waits until + // |caller_signal| is signalled. + void LockWaitUnLockForTesting(WaitableEvent* caller_wait, + WaitableEvent* caller_signal); + + // Loads tasks from the |incoming_queue_| into |*work_queue|. Must be called + // from the thread that is running the loop. + void ReloadWorkQueue(TaskQueue* work_queue); + + // Disconnects |this| from the parent message loop. + void WillDestroyCurrentMessageLoop(); + + private: + friend class RefCountedThreadSafe; + virtual ~IncomingTaskQueue(); + + // Calculates the time at which a PendingTask should run. + TimeTicks CalculateDelayedRuntime(TimeDelta delay); + + // Adds a task to |incoming_queue_|. The caller retains ownership of + // |pending_task|, but this function will reset the value of + // |pending_task->task|. This is needed to ensure that the posting call stack + // does not retain |pending_task->task| beyond this function call. + bool PostPendingTask(PendingTask* pending_task); + +#if defined(OS_WIN) + TimeTicks high_resolution_timer_expiration_; +#endif + + // The lock that protects access to |incoming_queue_|, |message_loop_| and + // |next_sequence_num_|. + base::Lock incoming_queue_lock_; + + // An incoming queue of tasks that are acquired under a mutex for processing + // on this instance's thread. These tasks have not yet been been pushed to + // |message_loop_|. + TaskQueue incoming_queue_; + + // Points to the message loop that owns |this|. + MessageLoop* message_loop_; + + // The next sequence number to use for delayed tasks. + int next_sequence_num_; + + DISALLOW_COPY_AND_ASSIGN(IncomingTaskQueue); +}; + +} // namespace internal +} // namespace base + +#endif // BASE_MESSAGE_LOOP_INCOMING_TASK_QUEUE_H_ diff --git a/base/message_loop/message_loop.cc b/base/message_loop/message_loop.cc new file mode 100644 index 0000000000..826c7573ee --- /dev/null +++ b/base/message_loop/message_loop.cc @@ -0,0 +1,754 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/message_loop/message_loop.h" + +#include + +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/debug/alias.h" +#include "base/debug/trace_event.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_pump_default.h" +#include "base/metrics/histogram.h" +#include "base/metrics/statistics_recorder.h" +#include "base/run_loop.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" +#include "base/thread_task_runner_handle.h" +#include "base/threading/thread_local.h" +#include "base/time/time.h" +#include "base/tracked_objects.h" + +#if defined(OS_MACOSX) +#include "base/message_loop/message_pump_mac.h" +#endif +#if defined(OS_POSIX) && !defined(OS_IOS) +#include "base/message_loop/message_pump_libevent.h" +#endif +#if defined(OS_ANDROID) +#include "base/message_loop/message_pump_android.h" +#endif + +#if defined(TOOLKIT_GTK) +#include +#include +#endif + +namespace base { + +namespace { + +// A lazily created thread local storage for quick access to a thread's message +// loop, if one exists. This should be safe and free of static constructors. +LazyInstance > lazy_tls_ptr = + LAZY_INSTANCE_INITIALIZER; + +// Logical events for Histogram profiling. Run with -message-loop-histogrammer +// to get an accounting of messages and actions taken on each thread. +const int kTaskRunEvent = 0x1; +const int kTimerEvent = 0x2; + +// Provide range of message IDs for use in histogramming and debug display. +const int kLeastNonZeroMessageId = 1; +const int kMaxMessageId = 1099; +const int kNumberOfDistinctMessagesDisplayed = 1100; + +// Provide a macro that takes an expression (such as a constant, or macro +// constant) and creates a pair to initalize an array of pairs. In this case, +// our pair consists of the expressions value, and the "stringized" version +// of the expression (i.e., the exrpression put in quotes). For example, if +// we have: +// #define FOO 2 +// #define BAR 5 +// then the following: +// VALUE_TO_NUMBER_AND_NAME(FOO + BAR) +// will expand to: +// {7, "FOO + BAR"} +// We use the resulting array as an argument to our histogram, which reads the +// number as a bucket identifier, and proceeds to use the corresponding name +// in the pair (i.e., the quoted string) when printing out a histogram. +#define VALUE_TO_NUMBER_AND_NAME(name) {name, #name}, + +const LinearHistogram::DescriptionPair event_descriptions_[] = { + // Provide some pretty print capability in our histogram for our internal + // messages. + + // A few events we handle (kindred to messages), and used to profile actions. + VALUE_TO_NUMBER_AND_NAME(kTaskRunEvent) + VALUE_TO_NUMBER_AND_NAME(kTimerEvent) + + {-1, NULL} // The list must be null terminated, per API to histogram. +}; + +bool enable_histogrammer_ = false; + +MessageLoop::MessagePumpFactory* message_pump_for_ui_factory_ = NULL; + +// Returns true if MessagePump::ScheduleWork() must be called one +// time for every task that is added to the MessageLoop incoming queue. +bool AlwaysNotifyPump(MessageLoop::Type type) { +#if defined(OS_ANDROID) + return type == MessageLoop::TYPE_UI || type == MessageLoop::TYPE_JAVA; +#else + return false; +#endif +} + +} // namespace + +//------------------------------------------------------------------------------ + +#if defined(OS_WIN) + +// Upon a SEH exception in this thread, it restores the original unhandled +// exception filter. +static int SEHFilter(LPTOP_LEVEL_EXCEPTION_FILTER old_filter) { + ::SetUnhandledExceptionFilter(old_filter); + return EXCEPTION_CONTINUE_SEARCH; +} + +// Retrieves a pointer to the current unhandled exception filter. There +// is no standalone getter method. +static LPTOP_LEVEL_EXCEPTION_FILTER GetTopSEHFilter() { + LPTOP_LEVEL_EXCEPTION_FILTER top_filter = NULL; + top_filter = ::SetUnhandledExceptionFilter(0); + ::SetUnhandledExceptionFilter(top_filter); + return top_filter; +} + +#endif // defined(OS_WIN) + +//------------------------------------------------------------------------------ + +MessageLoop::TaskObserver::TaskObserver() { +} + +MessageLoop::TaskObserver::~TaskObserver() { +} + +MessageLoop::DestructionObserver::~DestructionObserver() { +} + +//------------------------------------------------------------------------------ + +MessageLoop::MessageLoop(Type type) + : type_(type), + exception_restoration_(false), + nestable_tasks_allowed_(true), +#if defined(OS_WIN) + os_modal_loop_(false), +#endif // OS_WIN + message_histogram_(NULL), + run_loop_(NULL) { + DCHECK(!current()) << "should only have one message loop per thread"; + lazy_tls_ptr.Pointer()->Set(this); + + incoming_task_queue_ = new internal::IncomingTaskQueue(this); + message_loop_proxy_ = + new internal::MessageLoopProxyImpl(incoming_task_queue_); + thread_task_runner_handle_.reset( + new ThreadTaskRunnerHandle(message_loop_proxy_)); + +// TODO(rvargas): Get rid of the OS guards. +#if defined(OS_WIN) +#define MESSAGE_PUMP_UI new MessagePumpForUI() +#define MESSAGE_PUMP_IO new MessagePumpForIO() +#elif defined(OS_IOS) +#define MESSAGE_PUMP_UI MessagePumpMac::Create() +#define MESSAGE_PUMP_IO new MessagePumpIOSForIO() +#elif defined(OS_MACOSX) +#define MESSAGE_PUMP_UI MessagePumpMac::Create() +#define MESSAGE_PUMP_IO new MessagePumpLibevent() +#elif defined(OS_NACL) +// Currently NaCl doesn't have a UI MessageLoop. +// TODO(abarth): Figure out if we need this. +#define MESSAGE_PUMP_UI NULL +// ipc_channel_nacl.cc uses a worker thread to do socket reads currently, and +// doesn't require extra support for watching file descriptors. +#define MESSAGE_PUMP_IO new MessagePumpDefault() +#elif defined(OS_POSIX) // POSIX but not MACOSX. +#define MESSAGE_PUMP_UI new MessagePumpForUI() +#define MESSAGE_PUMP_IO new MessagePumpLibevent() +#else +#error Not implemented +#endif + + if (type_ == TYPE_UI) { + if (message_pump_for_ui_factory_) + pump_.reset(message_pump_for_ui_factory_()); + else + pump_.reset(MESSAGE_PUMP_UI); + } else if (type_ == TYPE_IO) { + pump_.reset(MESSAGE_PUMP_IO); +#if defined(OS_ANDROID) + } else if (type_ == TYPE_JAVA) { + pump_.reset(MESSAGE_PUMP_UI); +#endif + } else { + DCHECK_EQ(TYPE_DEFAULT, type_); + pump_.reset(new MessagePumpDefault()); + } +} + +MessageLoop::~MessageLoop() { + DCHECK_EQ(this, current()); + + DCHECK(!run_loop_); + + // Clean up any unprocessed tasks, but take care: deleting a task could + // result in the addition of more tasks (e.g., via DeleteSoon). We set a + // limit on the number of times we will allow a deleted task to generate more + // tasks. Normally, we should only pass through this loop once or twice. If + // we end up hitting the loop limit, then it is probably due to one task that + // is being stubborn. Inspect the queues to see who is left. + bool did_work; + for (int i = 0; i < 100; ++i) { + DeletePendingTasks(); + ReloadWorkQueue(); + // If we end up with empty queues, then break out of the loop. + did_work = DeletePendingTasks(); + if (!did_work) + break; + } + DCHECK(!did_work); + + // Let interested parties have one last shot at accessing this. + FOR_EACH_OBSERVER(DestructionObserver, destruction_observers_, + WillDestroyCurrentMessageLoop()); + + thread_task_runner_handle_.reset(); + + // Tell the incoming queue that we are dying. + incoming_task_queue_->WillDestroyCurrentMessageLoop(); + incoming_task_queue_ = NULL; + message_loop_proxy_ = NULL; + + // OK, now make it so that no one can find us. + lazy_tls_ptr.Pointer()->Set(NULL); +} + +// static +MessageLoop* MessageLoop::current() { + // TODO(darin): sadly, we cannot enable this yet since people call us even + // when they have no intention of using us. + // DCHECK(loop) << "Ouch, did you forget to initialize me?"; + return lazy_tls_ptr.Pointer()->Get(); +} + +// static +void MessageLoop::EnableHistogrammer(bool enable) { + enable_histogrammer_ = enable; +} + +// static +bool MessageLoop::InitMessagePumpForUIFactory(MessagePumpFactory* factory) { + if (message_pump_for_ui_factory_) + return false; + + message_pump_for_ui_factory_ = factory; + return true; +} + +void MessageLoop::AddDestructionObserver( + DestructionObserver* destruction_observer) { + DCHECK_EQ(this, current()); + destruction_observers_.AddObserver(destruction_observer); +} + +void MessageLoop::RemoveDestructionObserver( + DestructionObserver* destruction_observer) { + DCHECK_EQ(this, current()); + destruction_observers_.RemoveObserver(destruction_observer); +} + +void MessageLoop::PostTask( + const tracked_objects::Location& from_here, + const Closure& task) { + DCHECK(!task.is_null()) << from_here.ToString(); + incoming_task_queue_->AddToIncomingQueue(from_here, task, TimeDelta(), true); +} + +bool MessageLoop::TryPostTask( + const tracked_objects::Location& from_here, + const Closure& task) { + DCHECK(!task.is_null()) << from_here.ToString(); + return incoming_task_queue_->TryAddToIncomingQueue(from_here, task); +} + +void MessageLoop::PostDelayedTask( + const tracked_objects::Location& from_here, + const Closure& task, + TimeDelta delay) { + DCHECK(!task.is_null()) << from_here.ToString(); + incoming_task_queue_->AddToIncomingQueue(from_here, task, delay, true); +} + +void MessageLoop::PostNonNestableTask( + const tracked_objects::Location& from_here, + const Closure& task) { + DCHECK(!task.is_null()) << from_here.ToString(); + incoming_task_queue_->AddToIncomingQueue(from_here, task, TimeDelta(), false); +} + +void MessageLoop::PostNonNestableDelayedTask( + const tracked_objects::Location& from_here, + const Closure& task, + TimeDelta delay) { + DCHECK(!task.is_null()) << from_here.ToString(); + incoming_task_queue_->AddToIncomingQueue(from_here, task, delay, false); +} + +void MessageLoop::Run() { + RunLoop run_loop; + run_loop.Run(); +} + +void MessageLoop::RunUntilIdle() { + RunLoop run_loop; + run_loop.RunUntilIdle(); +} + +void MessageLoop::QuitWhenIdle() { + DCHECK_EQ(this, current()); + if (run_loop_) { + run_loop_->quit_when_idle_received_ = true; + } else { + NOTREACHED() << "Must be inside Run to call Quit"; + } +} + +void MessageLoop::QuitNow() { + DCHECK_EQ(this, current()); + if (run_loop_) { + pump_->Quit(); + } else { + NOTREACHED() << "Must be inside Run to call Quit"; + } +} + +bool MessageLoop::IsType(Type type) const { + return type_ == type; +} + +static void QuitCurrentWhenIdle() { + MessageLoop::current()->QuitWhenIdle(); +} + +// static +Closure MessageLoop::QuitWhenIdleClosure() { + return Bind(&QuitCurrentWhenIdle); +} + +void MessageLoop::SetNestableTasksAllowed(bool allowed) { + if (nestable_tasks_allowed_ != allowed) { + nestable_tasks_allowed_ = allowed; + if (!nestable_tasks_allowed_) + return; + // Start the native pump if we are not already pumping. + pump_->ScheduleWork(); + } +} + +bool MessageLoop::NestableTasksAllowed() const { + return nestable_tasks_allowed_; +} + +bool MessageLoop::IsNested() { + return run_loop_->run_depth_ > 1; +} + +void MessageLoop::AddTaskObserver(TaskObserver* task_observer) { + DCHECK_EQ(this, current()); + task_observers_.AddObserver(task_observer); +} + +void MessageLoop::RemoveTaskObserver(TaskObserver* task_observer) { + DCHECK_EQ(this, current()); + task_observers_.RemoveObserver(task_observer); +} + +bool MessageLoop::is_running() const { + DCHECK_EQ(this, current()); + return run_loop_ != NULL; +} + +bool MessageLoop::IsHighResolutionTimerEnabledForTesting() { + return incoming_task_queue_->IsHighResolutionTimerEnabledForTesting(); +} + +bool MessageLoop::IsIdleForTesting() { + // We only check the imcoming queue|, since we don't want to lock the work + // queue. + return incoming_task_queue_->IsIdleForTesting(); +} + +void MessageLoop::LockWaitUnLockForTesting(WaitableEvent* caller_wait, + WaitableEvent* caller_signal) { + incoming_task_queue_->LockWaitUnLockForTesting(caller_wait, caller_signal); +} + +//------------------------------------------------------------------------------ + +// Runs the loop in two different SEH modes: +// enable_SEH_restoration_ = false : any unhandled exception goes to the last +// one that calls SetUnhandledExceptionFilter(). +// enable_SEH_restoration_ = true : any unhandled exception goes to the filter +// that was existed before the loop was run. +void MessageLoop::RunHandler() { +#if defined(OS_WIN) + if (exception_restoration_) { + RunInternalInSEHFrame(); + return; + } +#endif + + RunInternal(); +} + +#if defined(OS_WIN) +__declspec(noinline) void MessageLoop::RunInternalInSEHFrame() { + LPTOP_LEVEL_EXCEPTION_FILTER current_filter = GetTopSEHFilter(); + __try { + RunInternal(); + } __except(SEHFilter(current_filter)) { + } + return; +} +#endif + +void MessageLoop::RunInternal() { + DCHECK_EQ(this, current()); + + StartHistogrammer(); + +#if !defined(OS_MACOSX) && !defined(OS_ANDROID) + if (run_loop_->dispatcher_ && type() == TYPE_UI) { + static_cast(pump_.get())-> + RunWithDispatcher(this, run_loop_->dispatcher_); + return; + } +#endif + + pump_->Run(this); +} + +bool MessageLoop::ProcessNextDelayedNonNestableTask() { + if (run_loop_->run_depth_ != 1) + return false; + + if (deferred_non_nestable_work_queue_.empty()) + return false; + + PendingTask pending_task = deferred_non_nestable_work_queue_.front(); + deferred_non_nestable_work_queue_.pop(); + + RunTask(pending_task); + return true; +} + +void MessageLoop::RunTask(const PendingTask& pending_task) { + tracked_objects::TrackedTime start_time = + tracked_objects::ThreadData::NowForStartOfRun(pending_task.birth_tally); + + TRACE_EVENT_FLOW_END1("task", "MessageLoop::PostTask", + TRACE_ID_MANGLE(GetTaskTraceID(pending_task)), + "queue_duration", + (start_time - pending_task.EffectiveTimePosted()).InMilliseconds()); + TRACE_EVENT2("task", "MessageLoop::RunTask", + "src_file", pending_task.posted_from.file_name(), + "src_func", pending_task.posted_from.function_name()); + + DCHECK(nestable_tasks_allowed_); + // Execute the task and assume the worst: It is probably not reentrant. + nestable_tasks_allowed_ = false; + + // Before running the task, store the program counter where it was posted + // and deliberately alias it to ensure it is on the stack if the task + // crashes. Be careful not to assume that the variable itself will have the + // expected value when displayed by the optimizer in an optimized build. + // Look at a memory dump of the stack. + const void* program_counter = + pending_task.posted_from.program_counter(); + debug::Alias(&program_counter); + + HistogramEvent(kTaskRunEvent); + + FOR_EACH_OBSERVER(TaskObserver, task_observers_, + WillProcessTask(pending_task)); + pending_task.task.Run(); + FOR_EACH_OBSERVER(TaskObserver, task_observers_, + DidProcessTask(pending_task)); + + tracked_objects::ThreadData::TallyRunOnNamedThreadIfTracking(pending_task, + start_time, tracked_objects::ThreadData::NowForEndOfRun()); + + nestable_tasks_allowed_ = true; +} + +bool MessageLoop::DeferOrRunPendingTask(const PendingTask& pending_task) { + if (pending_task.nestable || run_loop_->run_depth_ == 1) { + RunTask(pending_task); + // Show that we ran a task (Note: a new one might arrive as a + // consequence!). + return true; + } + + // We couldn't run the task now because we're in a nested message loop + // and the task isn't nestable. + deferred_non_nestable_work_queue_.push(pending_task); + return false; +} + +void MessageLoop::AddToDelayedWorkQueue(const PendingTask& pending_task) { + // Move to the delayed work queue. + delayed_work_queue_.push(pending_task); +} + +bool MessageLoop::DeletePendingTasks() { + bool did_work = !work_queue_.empty(); + while (!work_queue_.empty()) { + PendingTask pending_task = work_queue_.front(); + work_queue_.pop(); + if (!pending_task.delayed_run_time.is_null()) { + // We want to delete delayed tasks in the same order in which they would + // normally be deleted in case of any funny dependencies between delayed + // tasks. + AddToDelayedWorkQueue(pending_task); + } + } + did_work |= !deferred_non_nestable_work_queue_.empty(); + while (!deferred_non_nestable_work_queue_.empty()) { + deferred_non_nestable_work_queue_.pop(); + } + did_work |= !delayed_work_queue_.empty(); + + // Historically, we always delete the task regardless of valgrind status. It's + // not completely clear why we want to leak them in the loops above. This + // code is replicating legacy behavior, and should not be considered + // absolutely "correct" behavior. See TODO above about deleting all tasks + // when it's safe. + while (!delayed_work_queue_.empty()) { + delayed_work_queue_.pop(); + } + return did_work; +} + +uint64 MessageLoop::GetTaskTraceID(const PendingTask& task) { + return (static_cast(task.sequence_num) << 32) | + static_cast(reinterpret_cast(this)); +} + +void MessageLoop::ReloadWorkQueue() { + // We can improve performance of our loading tasks from the incoming queue to + // |*work_queue| by waiting until the last minute (|*work_queue| is empty) to + // load. That reduces the number of locks-per-task significantly when our + // queues get large. + if (work_queue_.empty()) + incoming_task_queue_->ReloadWorkQueue(&work_queue_); +} + +void MessageLoop::ScheduleWork(bool was_empty) { + // The Android UI message loop needs to get notified each time + // a task is added to the incoming queue. + if (was_empty || AlwaysNotifyPump(type_)) + pump_->ScheduleWork(); +} + +//------------------------------------------------------------------------------ +// Method and data for histogramming events and actions taken by each instance +// on each thread. + +void MessageLoop::StartHistogrammer() { +#if !defined(OS_NACL) // NaCl build has no metrics code. + if (enable_histogrammer_ && !message_histogram_ + && StatisticsRecorder::IsActive()) { + DCHECK(!thread_name_.empty()); + message_histogram_ = LinearHistogram::FactoryGetWithRangeDescription( + "MsgLoop:" + thread_name_, + kLeastNonZeroMessageId, kMaxMessageId, + kNumberOfDistinctMessagesDisplayed, + message_histogram_->kHexRangePrintingFlag, + event_descriptions_); + } +#endif +} + +void MessageLoop::HistogramEvent(int event) { +#if !defined(OS_NACL) + if (message_histogram_) + message_histogram_->Add(event); +#endif +} + +bool MessageLoop::DoWork() { + if (!nestable_tasks_allowed_) { + // Task can't be executed right now. + return false; + } + + for (;;) { + ReloadWorkQueue(); + if (work_queue_.empty()) + break; + + // Execute oldest task. + do { + PendingTask pending_task = work_queue_.front(); + work_queue_.pop(); + if (!pending_task.delayed_run_time.is_null()) { + AddToDelayedWorkQueue(pending_task); + // If we changed the topmost task, then it is time to reschedule. + if (delayed_work_queue_.top().task.Equals(pending_task.task)) + pump_->ScheduleDelayedWork(pending_task.delayed_run_time); + } else { + if (DeferOrRunPendingTask(pending_task)) + return true; + } + } while (!work_queue_.empty()); + } + + // Nothing happened. + return false; +} + +bool MessageLoop::DoDelayedWork(TimeTicks* next_delayed_work_time) { + if (!nestable_tasks_allowed_ || delayed_work_queue_.empty()) { + recent_time_ = *next_delayed_work_time = TimeTicks(); + return false; + } + + // When we "fall behind," there will be a lot of tasks in the delayed work + // queue that are ready to run. To increase efficiency when we fall behind, + // we will only call Time::Now() intermittently, and then process all tasks + // that are ready to run before calling it again. As a result, the more we + // fall behind (and have a lot of ready-to-run delayed tasks), the more + // efficient we'll be at handling the tasks. + + TimeTicks next_run_time = delayed_work_queue_.top().delayed_run_time; + if (next_run_time > recent_time_) { + recent_time_ = TimeTicks::Now(); // Get a better view of Now(); + if (next_run_time > recent_time_) { + *next_delayed_work_time = next_run_time; + return false; + } + } + + PendingTask pending_task = delayed_work_queue_.top(); + delayed_work_queue_.pop(); + + if (!delayed_work_queue_.empty()) + *next_delayed_work_time = delayed_work_queue_.top().delayed_run_time; + + return DeferOrRunPendingTask(pending_task); +} + +bool MessageLoop::DoIdleWork() { + if (ProcessNextDelayedNonNestableTask()) + return true; + + if (run_loop_->quit_when_idle_received_) + pump_->Quit(); + + return false; +} + +void MessageLoop::DeleteSoonInternal(const tracked_objects::Location& from_here, + void(*deleter)(const void*), + const void* object) { + PostNonNestableTask(from_here, Bind(deleter, object)); +} + +void MessageLoop::ReleaseSoonInternal( + const tracked_objects::Location& from_here, + void(*releaser)(const void*), + const void* object) { + PostNonNestableTask(from_here, Bind(releaser, object)); +} + +//------------------------------------------------------------------------------ +// MessageLoopForUI + +#if defined(OS_WIN) +void MessageLoopForUI::DidProcessMessage(const MSG& message) { + pump_win()->DidProcessMessage(message); +} +#endif // defined(OS_WIN) + +#if defined(OS_ANDROID) +void MessageLoopForUI::Start() { + // No Histogram support for UI message loop as it is managed by Java side + static_cast(pump_.get())->Start(this); +} +#endif + +#if defined(OS_IOS) +void MessageLoopForUI::Attach() { + static_cast(pump_.get())->Attach(this); +} +#endif + +#if !defined(OS_MACOSX) && !defined(OS_NACL) && !defined(OS_ANDROID) +void MessageLoopForUI::AddObserver(Observer* observer) { + pump_ui()->AddObserver(observer); +} + +void MessageLoopForUI::RemoveObserver(Observer* observer) { + pump_ui()->RemoveObserver(observer); +} + +#endif // !defined(OS_MACOSX) && !defined(OS_NACL) && !defined(OS_ANDROID) + +//------------------------------------------------------------------------------ +// MessageLoopForIO + +#if defined(OS_WIN) + +void MessageLoopForIO::RegisterIOHandler(HANDLE file, IOHandler* handler) { + pump_io()->RegisterIOHandler(file, handler); +} + +bool MessageLoopForIO::RegisterJobObject(HANDLE job, IOHandler* handler) { + return pump_io()->RegisterJobObject(job, handler); +} + +bool MessageLoopForIO::WaitForIOCompletion(DWORD timeout, IOHandler* filter) { + return pump_io()->WaitForIOCompletion(timeout, filter); +} + +#elif defined(OS_IOS) + +bool MessageLoopForIO::WatchFileDescriptor(int fd, + bool persistent, + Mode mode, + FileDescriptorWatcher *controller, + Watcher *delegate) { + return pump_io()->WatchFileDescriptor( + fd, + persistent, + mode, + controller, + delegate); +} + +#elif defined(OS_POSIX) && !defined(OS_NACL) + +bool MessageLoopForIO::WatchFileDescriptor(int fd, + bool persistent, + Mode mode, + FileDescriptorWatcher *controller, + Watcher *delegate) { + return pump_libevent()->WatchFileDescriptor( + fd, + persistent, + mode, + controller, + delegate); +} + +#endif + +} // namespace base diff --git a/base/message_loop/message_loop.h b/base/message_loop/message_loop.h new file mode 100644 index 0000000000..f22c9044d1 --- /dev/null +++ b/base/message_loop/message_loop.h @@ -0,0 +1,719 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MESSAGE_LOOP_MESSAGE_LOOP_H_ +#define BASE_MESSAGE_LOOP_MESSAGE_LOOP_H_ + +#include +#include + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/callback_forward.h" +#include "base/location.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/incoming_task_queue.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/message_loop/message_loop_proxy_impl.h" +#include "base/message_loop/message_pump.h" +#include "base/observer_list.h" +#include "base/pending_task.h" +#include "base/sequenced_task_runner_helpers.h" +#include "base/synchronization/lock.h" +#include "base/time/time.h" +#include "base/tracking_info.h" + +#if defined(OS_WIN) +// We need this to declare base::MessagePumpWin::Dispatcher, which we should +// really just eliminate. +#include "base/message_loop/message_pump_win.h" +#elif defined(OS_IOS) +#include "base/message_loop/message_pump_io_ios.h" +#elif defined(OS_POSIX) +#include "base/message_loop/message_pump_libevent.h" +#if !defined(OS_MACOSX) && !defined(OS_ANDROID) + +#if defined(USE_AURA) && defined(USE_X11) && !defined(OS_NACL) +#include "base/message_loop/message_pump_aurax11.h" +#elif defined(USE_OZONE) && !defined(OS_NACL) +#include "base/message_loop/message_pump_ozone.h" +#else +#include "base/message_loop/message_pump_gtk.h" +#endif + +#endif +#endif + +namespace base { + +class HistogramBase; +class RunLoop; +class ThreadTaskRunnerHandle; +#if defined(OS_ANDROID) +class MessagePumpForUI; +#endif +class WaitableEvent; + +// A MessageLoop is used to process events for a particular thread. There is +// at most one MessageLoop instance per thread. +// +// Events include at a minimum Task instances submitted to PostTask and its +// variants. Depending on the type of message pump used by the MessageLoop +// other events such as UI messages may be processed. On Windows APC calls (as +// time permits) and signals sent to a registered set of HANDLEs may also be +// processed. +// +// NOTE: Unless otherwise specified, a MessageLoop's methods may only be called +// on the thread where the MessageLoop's Run method executes. +// +// NOTE: MessageLoop has task reentrancy protection. This means that if a +// task is being processed, a second task cannot start until the first task is +// finished. Reentrancy can happen when processing a task, and an inner +// message pump is created. That inner pump then processes native messages +// which could implicitly start an inner task. Inner message pumps are created +// with dialogs (DialogBox), common dialogs (GetOpenFileName), OLE functions +// (DoDragDrop), printer functions (StartDoc) and *many* others. +// +// Sample workaround when inner task processing is needed: +// HRESULT hr; +// { +// MessageLoop::ScopedNestableTaskAllower allow(MessageLoop::current()); +// hr = DoDragDrop(...); // Implicitly runs a modal message loop. +// } +// // Process |hr| (the result returned by DoDragDrop()). +// +// Please be SURE your task is reentrant (nestable) and all global variables +// are stable and accessible before calling SetNestableTasksAllowed(true). +// +class BASE_EXPORT MessageLoop : public MessagePump::Delegate { + public: + +#if !defined(OS_MACOSX) && !defined(OS_ANDROID) + typedef MessagePumpDispatcher Dispatcher; + typedef MessagePumpObserver Observer; +#endif + + // A MessageLoop has a particular type, which indicates the set of + // asynchronous events it may process in addition to tasks and timers. + // + // TYPE_DEFAULT + // This type of ML only supports tasks and timers. + // + // TYPE_UI + // This type of ML also supports native UI events (e.g., Windows messages). + // See also MessageLoopForUI. + // + // TYPE_IO + // This type of ML also supports asynchronous IO. See also + // MessageLoopForIO. + // + // TYPE_JAVA + // This type of ML is backed by a Java message handler which is responsible + // for running the tasks added to the ML. This is only for use on Android. + // TYPE_JAVA behaves in essence like TYPE_UI, except during construction + // where it does not use the main thread specific pump factory. + // + enum Type { + TYPE_DEFAULT, + TYPE_UI, + TYPE_IO, +#if defined(OS_ANDROID) + TYPE_JAVA, +#endif // defined(OS_ANDROID) + }; + + // Normally, it is not necessary to instantiate a MessageLoop. Instead, it + // is typical to make use of the current thread's MessageLoop instance. + explicit MessageLoop(Type type = TYPE_DEFAULT); + virtual ~MessageLoop(); + + // Returns the MessageLoop object for the current thread, or null if none. + static MessageLoop* current(); + + static void EnableHistogrammer(bool enable_histogrammer); + + typedef MessagePump* (MessagePumpFactory)(); + // Uses the given base::MessagePumpForUIFactory to override the default + // MessagePump implementation for 'TYPE_UI'. Returns true if the factory + // was successfully registered. + static bool InitMessagePumpForUIFactory(MessagePumpFactory* factory); + + // A DestructionObserver is notified when the current MessageLoop is being + // destroyed. These observers are notified prior to MessageLoop::current() + // being changed to return NULL. This gives interested parties the chance to + // do final cleanup that depends on the MessageLoop. + // + // NOTE: Any tasks posted to the MessageLoop during this notification will + // not be run. Instead, they will be deleted. + // + class BASE_EXPORT DestructionObserver { + public: + virtual void WillDestroyCurrentMessageLoop() = 0; + + protected: + virtual ~DestructionObserver(); + }; + + // Add a DestructionObserver, which will start receiving notifications + // immediately. + void AddDestructionObserver(DestructionObserver* destruction_observer); + + // Remove a DestructionObserver. It is safe to call this method while a + // DestructionObserver is receiving a notification callback. + void RemoveDestructionObserver(DestructionObserver* destruction_observer); + + // The "PostTask" family of methods call the task's Run method asynchronously + // from within a message loop at some point in the future. + // + // With the PostTask variant, tasks are invoked in FIFO order, inter-mixed + // with normal UI or IO event processing. With the PostDelayedTask variant, + // tasks are called after at least approximately 'delay_ms' have elapsed. + // + // The NonNestable variants work similarly except that they promise never to + // dispatch the task from a nested invocation of MessageLoop::Run. Instead, + // such tasks get deferred until the top-most MessageLoop::Run is executing. + // + // The MessageLoop takes ownership of the Task, and deletes it after it has + // been Run(). + // + // PostTask(from_here, task) is equivalent to + // PostDelayedTask(from_here, task, 0). + // + // The TryPostTask is meant for the cases where the calling thread cannot + // block. If posting the task will block, the call returns false, the task + // is not posted but the task is consumed anyways. + // + // NOTE: These methods may be called on any thread. The Task will be invoked + // on the thread that executes MessageLoop::Run(). + void PostTask(const tracked_objects::Location& from_here, + const Closure& task); + + bool TryPostTask(const tracked_objects::Location& from_here, + const Closure& task); + + void PostDelayedTask(const tracked_objects::Location& from_here, + const Closure& task, + TimeDelta delay); + + void PostNonNestableTask(const tracked_objects::Location& from_here, + const Closure& task); + + void PostNonNestableDelayedTask(const tracked_objects::Location& from_here, + const Closure& task, + TimeDelta delay); + + // A variant on PostTask that deletes the given object. This is useful + // if the object needs to live until the next run of the MessageLoop (for + // example, deleting a RenderProcessHost from within an IPC callback is not + // good). + // + // NOTE: This method may be called on any thread. The object will be deleted + // on the thread that executes MessageLoop::Run(). If this is not the same + // as the thread that calls PostDelayedTask(FROM_HERE, ), then T MUST inherit + // from RefCountedThreadSafe! + template + void DeleteSoon(const tracked_objects::Location& from_here, const T* object) { + base::subtle::DeleteHelperInternal::DeleteViaSequencedTaskRunner( + this, from_here, object); + } + + // A variant on PostTask that releases the given reference counted object + // (by calling its Release method). This is useful if the object needs to + // live until the next run of the MessageLoop, or if the object needs to be + // released on a particular thread. + // + // NOTE: This method may be called on any thread. The object will be + // released (and thus possibly deleted) on the thread that executes + // MessageLoop::Run(). If this is not the same as the thread that calls + // PostDelayedTask(FROM_HERE, ), then T MUST inherit from + // RefCountedThreadSafe! + template + void ReleaseSoon(const tracked_objects::Location& from_here, + const T* object) { + base::subtle::ReleaseHelperInternal::ReleaseViaSequencedTaskRunner( + this, from_here, object); + } + + // Deprecated: use RunLoop instead. + // Run the message loop. + void Run(); + + // Deprecated: use RunLoop instead. + // Process all pending tasks, windows messages, etc., but don't wait/sleep. + // Return as soon as all items that can be run are taken care of. + void RunUntilIdle(); + + // TODO(jbates) remove this. crbug.com/131220. See QuitWhenIdle(). + void Quit() { QuitWhenIdle(); } + + // Deprecated: use RunLoop instead. + // + // Signals the Run method to return when it becomes idle. It will continue to + // process pending messages and future messages as long as they are enqueued. + // Warning: if the MessageLoop remains busy, it may never quit. Only use this + // Quit method when looping procedures (such as web pages) have been shut + // down. + // + // This method may only be called on the same thread that called Run, and Run + // must still be on the call stack. + // + // Use QuitClosure variants if you need to Quit another thread's MessageLoop, + // but note that doing so is fairly dangerous if the target thread makes + // nested calls to MessageLoop::Run. The problem being that you won't know + // which nested run loop you are quitting, so be careful! + void QuitWhenIdle(); + + // Deprecated: use RunLoop instead. + // + // This method is a variant of Quit, that does not wait for pending messages + // to be processed before returning from Run. + void QuitNow(); + + // TODO(jbates) remove this. crbug.com/131220. See QuitWhenIdleClosure(). + static Closure QuitClosure() { return QuitWhenIdleClosure(); } + + // Deprecated: use RunLoop instead. + // Construct a Closure that will call QuitWhenIdle(). Useful to schedule an + // arbitrary MessageLoop to QuitWhenIdle. + static Closure QuitWhenIdleClosure(); + + // Returns true if this loop is |type|. This allows subclasses (especially + // those in tests) to specialize how they are identified. + virtual bool IsType(Type type) const; + + // Returns the type passed to the constructor. + Type type() const { return type_; } + + // Optional call to connect the thread name with this loop. + void set_thread_name(const std::string& thread_name) { + DCHECK(thread_name_.empty()) << "Should not rename this thread!"; + thread_name_ = thread_name; + } + const std::string& thread_name() const { return thread_name_; } + + // Gets the message loop proxy associated with this message loop. + scoped_refptr message_loop_proxy() { + return message_loop_proxy_; + } + + // Enables or disables the recursive task processing. This happens in the case + // of recursive message loops. Some unwanted message loop may occurs when + // using common controls or printer functions. By default, recursive task + // processing is disabled. + // + // Please utilize |ScopedNestableTaskAllower| instead of calling these methods + // directly. In general nestable message loops are to be avoided. They are + // dangerous and difficult to get right, so please use with extreme caution. + // + // The specific case where tasks get queued is: + // - The thread is running a message loop. + // - It receives a task #1 and execute it. + // - The task #1 implicitly start a message loop, like a MessageBox in the + // unit test. This can also be StartDoc or GetSaveFileName. + // - The thread receives a task #2 before or while in this second message + // loop. + // - With NestableTasksAllowed set to true, the task #2 will run right away. + // Otherwise, it will get executed right after task #1 completes at "thread + // message loop level". + void SetNestableTasksAllowed(bool allowed); + bool NestableTasksAllowed() const; + + // Enables nestable tasks on |loop| while in scope. + class ScopedNestableTaskAllower { + public: + explicit ScopedNestableTaskAllower(MessageLoop* loop) + : loop_(loop), + old_state_(loop_->NestableTasksAllowed()) { + loop_->SetNestableTasksAllowed(true); + } + ~ScopedNestableTaskAllower() { + loop_->SetNestableTasksAllowed(old_state_); + } + + private: + MessageLoop* loop_; + bool old_state_; + }; + + // Enables or disables the restoration during an exception of the unhandled + // exception filter that was active when Run() was called. This can happen + // if some third party code call SetUnhandledExceptionFilter() and never + // restores the previous filter. + void set_exception_restoration(bool restore) { + exception_restoration_ = restore; + } + + // Returns true if we are currently running a nested message loop. + bool IsNested(); + + // A TaskObserver is an object that receives task notifications from the + // MessageLoop. + // + // NOTE: A TaskObserver implementation should be extremely fast! + class BASE_EXPORT TaskObserver { + public: + TaskObserver(); + + // This method is called before processing a task. + virtual void WillProcessTask(const PendingTask& pending_task) = 0; + + // This method is called after processing a task. + virtual void DidProcessTask(const PendingTask& pending_task) = 0; + + protected: + virtual ~TaskObserver(); + }; + + // These functions can only be called on the same thread that |this| is + // running on. + void AddTaskObserver(TaskObserver* task_observer); + void RemoveTaskObserver(TaskObserver* task_observer); + + // When we go into high resolution timer mode, we will stay in hi-res mode + // for at least 1s. + static const int kHighResolutionTimerModeLeaseTimeMs = 1000; + +#if defined(OS_WIN) + void set_os_modal_loop(bool os_modal_loop) { + os_modal_loop_ = os_modal_loop; + } + + bool os_modal_loop() const { + return os_modal_loop_; + } +#endif // OS_WIN + + // Can only be called from the thread that owns the MessageLoop. + bool is_running() const; + + // Returns true if the message loop has high resolution timers enabled. + // Provided for testing. + bool IsHighResolutionTimerEnabledForTesting(); + + // Returns true if the message loop is "idle". Provided for testing. + bool IsIdleForTesting(); + + // Takes the incoming queue lock, signals |caller_wait| and waits until + // |caller_signal| is signalled. + void LockWaitUnLockForTesting(WaitableEvent* caller_wait, + WaitableEvent* caller_signal); + + //---------------------------------------------------------------------------- + protected: + +#if defined(OS_WIN) + MessagePumpWin* pump_win() { + return static_cast(pump_.get()); + } +#elif defined(OS_POSIX) && !defined(OS_IOS) + MessagePumpLibevent* pump_libevent() { + return static_cast(pump_.get()); + } +#endif + + scoped_ptr pump_; + + private: + friend class internal::IncomingTaskQueue; + friend class RunLoop; + + // A function to encapsulate all the exception handling capability in the + // stacks around the running of a main message loop. It will run the message + // loop in a SEH try block or not depending on the set_SEH_restoration() + // flag invoking respectively RunInternalInSEHFrame() or RunInternal(). + void RunHandler(); + +#if defined(OS_WIN) + __declspec(noinline) void RunInternalInSEHFrame(); +#endif + + // A surrounding stack frame around the running of the message loop that + // supports all saving and restoring of state, as is needed for any/all (ugly) + // recursive calls. + void RunInternal(); + + // Called to process any delayed non-nestable tasks. + bool ProcessNextDelayedNonNestableTask(); + + // Runs the specified PendingTask. + void RunTask(const PendingTask& pending_task); + + // Calls RunTask or queues the pending_task on the deferred task list if it + // cannot be run right now. Returns true if the task was run. + bool DeferOrRunPendingTask(const PendingTask& pending_task); + + // Adds the pending task to delayed_work_queue_. + void AddToDelayedWorkQueue(const PendingTask& pending_task); + + // Delete tasks that haven't run yet without running them. Used in the + // destructor to make sure all the task's destructors get called. Returns + // true if some work was done. + bool DeletePendingTasks(); + + // Creates a process-wide unique ID to represent this task in trace events. + // This will be mangled with a Process ID hash to reduce the likelyhood of + // colliding with MessageLoop pointers on other processes. + uint64 GetTaskTraceID(const PendingTask& task); + + // Loads tasks from the incoming queue to |work_queue_| if the latter is + // empty. + void ReloadWorkQueue(); + + // Wakes up the message pump. Can be called on any thread. The caller is + // responsible for synchronizing ScheduleWork() calls. + void ScheduleWork(bool was_empty); + + // Start recording histogram info about events and action IF it was enabled + // and IF the statistics recorder can accept a registration of our histogram. + void StartHistogrammer(); + + // Add occurrence of event to our histogram, so that we can see what is being + // done in a specific MessageLoop instance (i.e., specific thread). + // If message_histogram_ is NULL, this is a no-op. + void HistogramEvent(int event); + + // MessagePump::Delegate methods: + virtual bool DoWork() OVERRIDE; + virtual bool DoDelayedWork(TimeTicks* next_delayed_work_time) OVERRIDE; + virtual bool DoIdleWork() OVERRIDE; + + Type type_; + + // A list of tasks that need to be processed by this instance. Note that + // this queue is only accessed (push/pop) by our current thread. + TaskQueue work_queue_; + + // Contains delayed tasks, sorted by their 'delayed_run_time' property. + DelayedTaskQueue delayed_work_queue_; + + // A recent snapshot of Time::Now(), used to check delayed_work_queue_. + TimeTicks recent_time_; + + // A queue of non-nestable tasks that we had to defer because when it came + // time to execute them we were in a nested message loop. They will execute + // once we're out of nested message loops. + TaskQueue deferred_non_nestable_work_queue_; + + ObserverList destruction_observers_; + + bool exception_restoration_; + + // A recursion block that prevents accidentally running additional tasks when + // insider a (accidentally induced?) nested message pump. + bool nestable_tasks_allowed_; + +#if defined(OS_WIN) + // Should be set to true before calling Windows APIs like TrackPopupMenu, etc + // which enter a modal message loop. + bool os_modal_loop_; +#endif + + std::string thread_name_; + // A profiling histogram showing the counts of various messages and events. + HistogramBase* message_histogram_; + + RunLoop* run_loop_; + + ObserverList task_observers_; + + scoped_refptr incoming_task_queue_; + + // The message loop proxy associated with this message loop. + scoped_refptr message_loop_proxy_; + scoped_ptr thread_task_runner_handle_; + + template friend class base::subtle::DeleteHelperInternal; + template friend class base::subtle::ReleaseHelperInternal; + + void DeleteSoonInternal(const tracked_objects::Location& from_here, + void(*deleter)(const void*), + const void* object); + void ReleaseSoonInternal(const tracked_objects::Location& from_here, + void(*releaser)(const void*), + const void* object); + + DISALLOW_COPY_AND_ASSIGN(MessageLoop); +}; + +//----------------------------------------------------------------------------- +// MessageLoopForUI extends MessageLoop with methods that are particular to a +// MessageLoop instantiated with TYPE_UI. +// +// This class is typically used like so: +// MessageLoopForUI::current()->...call some method... +// +class BASE_EXPORT MessageLoopForUI : public MessageLoop { + public: +#if defined(OS_WIN) + typedef MessagePumpForUI::MessageFilter MessageFilter; +#endif + + MessageLoopForUI() : MessageLoop(TYPE_UI) { + } + + // Returns the MessageLoopForUI of the current thread. + static MessageLoopForUI* current() { + MessageLoop* loop = MessageLoop::current(); + DCHECK(loop); + DCHECK_EQ(MessageLoop::TYPE_UI, loop->type()); + return static_cast(loop); + } + +#if defined(OS_WIN) + void DidProcessMessage(const MSG& message); +#endif // defined(OS_WIN) + +#if defined(OS_IOS) + // On iOS, the main message loop cannot be Run(). Instead call Attach(), + // which connects this MessageLoop to the UI thread's CFRunLoop and allows + // PostTask() to work. + void Attach(); +#endif + +#if defined(OS_ANDROID) + // On Android, the UI message loop is handled by Java side. So Run() should + // never be called. Instead use Start(), which will forward all the native UI + // events to the Java message loop. + void Start(); +#elif !defined(OS_MACOSX) + + // Please see message_pump_win/message_pump_glib for definitions of these + // methods. + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + +#if defined(OS_WIN) + // Plese see MessagePumpForUI for definitions of this method. + void SetMessageFilter(scoped_ptr message_filter) { + pump_ui()->SetMessageFilter(message_filter.Pass()); + } +#endif + + protected: +#if defined(USE_AURA) && defined(USE_X11) && !defined(OS_NACL) + friend class MessagePumpAuraX11; +#endif +#if defined(USE_OZONE) && !defined(OS_NACL) + friend class MessagePumpOzone; +#endif + + // TODO(rvargas): Make this platform independent. + MessagePumpForUI* pump_ui() { + return static_cast(pump_.get()); + } +#endif // !defined(OS_MACOSX) +}; + +// Do not add any member variables to MessageLoopForUI! This is important b/c +// MessageLoopForUI is often allocated via MessageLoop(TYPE_UI). Any extra +// data that you need should be stored on the MessageLoop's pump_ instance. +COMPILE_ASSERT(sizeof(MessageLoop) == sizeof(MessageLoopForUI), + MessageLoopForUI_should_not_have_extra_member_variables); + +//----------------------------------------------------------------------------- +// MessageLoopForIO extends MessageLoop with methods that are particular to a +// MessageLoop instantiated with TYPE_IO. +// +// This class is typically used like so: +// MessageLoopForIO::current()->...call some method... +// +class BASE_EXPORT MessageLoopForIO : public MessageLoop { + public: +#if defined(OS_WIN) + typedef MessagePumpForIO::IOHandler IOHandler; + typedef MessagePumpForIO::IOContext IOContext; + typedef MessagePumpForIO::IOObserver IOObserver; +#elif defined(OS_IOS) + typedef MessagePumpIOSForIO::Watcher Watcher; + typedef MessagePumpIOSForIO::FileDescriptorWatcher + FileDescriptorWatcher; + typedef MessagePumpIOSForIO::IOObserver IOObserver; + + enum Mode { + WATCH_READ = MessagePumpIOSForIO::WATCH_READ, + WATCH_WRITE = MessagePumpIOSForIO::WATCH_WRITE, + WATCH_READ_WRITE = MessagePumpIOSForIO::WATCH_READ_WRITE + }; +#elif defined(OS_POSIX) + typedef MessagePumpLibevent::Watcher Watcher; + typedef MessagePumpLibevent::FileDescriptorWatcher + FileDescriptorWatcher; + typedef MessagePumpLibevent::IOObserver IOObserver; + + enum Mode { + WATCH_READ = MessagePumpLibevent::WATCH_READ, + WATCH_WRITE = MessagePumpLibevent::WATCH_WRITE, + WATCH_READ_WRITE = MessagePumpLibevent::WATCH_READ_WRITE + }; + +#endif + + MessageLoopForIO() : MessageLoop(TYPE_IO) { + } + + // Returns the MessageLoopForIO of the current thread. + static MessageLoopForIO* current() { + MessageLoop* loop = MessageLoop::current(); + DCHECK_EQ(MessageLoop::TYPE_IO, loop->type()); + return static_cast(loop); + } + + void AddIOObserver(IOObserver* io_observer) { + pump_io()->AddIOObserver(io_observer); + } + + void RemoveIOObserver(IOObserver* io_observer) { + pump_io()->RemoveIOObserver(io_observer); + } + +#if defined(OS_WIN) + // Please see MessagePumpWin for definitions of these methods. + void RegisterIOHandler(HANDLE file, IOHandler* handler); + bool RegisterJobObject(HANDLE job, IOHandler* handler); + bool WaitForIOCompletion(DWORD timeout, IOHandler* filter); + + protected: + // TODO(rvargas): Make this platform independent. + MessagePumpForIO* pump_io() { + return static_cast(pump_.get()); + } + +#elif defined(OS_IOS) + // Please see MessagePumpIOSForIO for definition. + bool WatchFileDescriptor(int fd, + bool persistent, + Mode mode, + FileDescriptorWatcher *controller, + Watcher *delegate); + + private: + MessagePumpIOSForIO* pump_io() { + return static_cast(pump_.get()); + } + +#elif defined(OS_POSIX) + // Please see MessagePumpLibevent for definition. + bool WatchFileDescriptor(int fd, + bool persistent, + Mode mode, + FileDescriptorWatcher* controller, + Watcher* delegate); + + private: + MessagePumpLibevent* pump_io() { + return static_cast(pump_.get()); + } +#endif // defined(OS_POSIX) +}; + +// Do not add any member variables to MessageLoopForIO! This is important b/c +// MessageLoopForIO is often allocated via MessageLoop(TYPE_IO). Any extra +// data that you need should be stored on the MessageLoop's pump_ instance. +COMPILE_ASSERT(sizeof(MessageLoop) == sizeof(MessageLoopForIO), + MessageLoopForIO_should_not_have_extra_member_variables); + +} // namespace base + +#endif // BASE_MESSAGE_LOOP_MESSAGE_LOOP_H_ diff --git a/base/message_loop/message_loop_proxy.cc b/base/message_loop/message_loop_proxy.cc new file mode 100644 index 0000000000..e5f0142633 --- /dev/null +++ b/base/message_loop/message_loop_proxy.cc @@ -0,0 +1,17 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/message_loop/message_loop_proxy.h" + +#include "base/bind.h" + +namespace base { + +MessageLoopProxy::MessageLoopProxy() { +} + +MessageLoopProxy::~MessageLoopProxy() { +} + +} // namespace base diff --git a/base/message_loop/message_loop_proxy.h b/base/message_loop/message_loop_proxy.h new file mode 100644 index 0000000000..4ace8026db --- /dev/null +++ b/base/message_loop/message_loop_proxy.h @@ -0,0 +1,38 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MESSAGE_LOOP_MESSAGE_LOOP_PROXY_H_ +#define BASE_MESSAGE_LOOP_MESSAGE_LOOP_PROXY_H_ + +#include "base/base_export.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/single_thread_task_runner.h" + +namespace base { + +// This class provides a thread-safe refcounted interface to the Post* methods +// of a message loop. This class can outlive the target message loop. +// MessageLoopProxy objects are constructed automatically for all MessageLoops. +// So, to access them, you can use any of the following: +// Thread::message_loop_proxy() +// MessageLoop::current()->message_loop_proxy() +// MessageLoopProxy::current() +// +// TODO(akalin): Now that we have the *TaskRunner interfaces, we can +// merge this with MessageLoopProxyImpl. +class BASE_EXPORT MessageLoopProxy : public SingleThreadTaskRunner { + public: + // Gets the MessageLoopProxy for the current message loop, creating one if + // needed. + static scoped_refptr current(); + + protected: + MessageLoopProxy(); + virtual ~MessageLoopProxy(); +}; + +} // namespace base + +#endif // BASE_MESSAGE_LOOP_MESSAGE_LOOP_PROXY_H_ diff --git a/base/message_loop/message_loop_proxy_impl.cc b/base/message_loop/message_loop_proxy_impl.cc new file mode 100644 index 0000000000..b7abca377e --- /dev/null +++ b/base/message_loop/message_loop_proxy_impl.cc @@ -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/message_loop/message_loop_proxy_impl.h" + +#include "base/location.h" +#include "base/logging.h" +#include "base/message_loop/incoming_task_queue.h" +#include "base/message_loop/message_loop.h" + +namespace base { +namespace internal { + +MessageLoopProxyImpl::MessageLoopProxyImpl( + scoped_refptr incoming_queue) + : incoming_queue_(incoming_queue), + valid_thread_id_(PlatformThread::CurrentId()) { +} + +bool MessageLoopProxyImpl::PostDelayedTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta delay) { + DCHECK(!task.is_null()) << from_here.ToString(); + return incoming_queue_->AddToIncomingQueue(from_here, task, delay, true); +} + +bool MessageLoopProxyImpl::PostNonNestableDelayedTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta delay) { + DCHECK(!task.is_null()) << from_here.ToString(); + return incoming_queue_->AddToIncomingQueue(from_here, task, delay, false); +} + +bool MessageLoopProxyImpl::RunsTasksOnCurrentThread() const { + return valid_thread_id_ == PlatformThread::CurrentId(); +} + +MessageLoopProxyImpl::~MessageLoopProxyImpl() { +} + +} // namespace internal + +scoped_refptr +MessageLoopProxy::current() { + MessageLoop* cur_loop = MessageLoop::current(); + if (!cur_loop) + return NULL; + return cur_loop->message_loop_proxy(); +} + +} // namespace base diff --git a/base/message_loop/message_loop_proxy_impl.h b/base/message_loop/message_loop_proxy_impl.h new file mode 100644 index 0000000000..b7f62b9770 --- /dev/null +++ b/base/message_loop/message_loop_proxy_impl.h @@ -0,0 +1,53 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MESSAGE_LOOP_MESSAGE_LOOP_PROXY_IMPL_H_ +#define BASE_MESSAGE_LOOP_MESSAGE_LOOP_PROXY_IMPL_H_ + +#include "base/base_export.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/pending_task.h" +#include "base/threading/platform_thread.h" + +namespace base { +namespace internal { + +class IncomingTaskQueue; + +// A stock implementation of MessageLoopProxy that is created and managed by a +// MessageLoop. For now a MessageLoopProxyImpl can only be created as part of a +// MessageLoop. +class BASE_EXPORT MessageLoopProxyImpl : public MessageLoopProxy { + public: + explicit MessageLoopProxyImpl( + scoped_refptr incoming_queue); + + // MessageLoopProxy implementation + virtual bool PostDelayedTask(const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta delay) OVERRIDE; + virtual bool PostNonNestableDelayedTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta delay) OVERRIDE; + virtual bool RunsTasksOnCurrentThread() const OVERRIDE; + + private: + friend class RefCountedThreadSafe; + virtual ~MessageLoopProxyImpl(); + + // THe incoming queue receiving all posted tasks. + scoped_refptr incoming_queue_; + + // ID of the thread |this| was created on. + PlatformThreadId valid_thread_id_; + + DISALLOW_COPY_AND_ASSIGN(MessageLoopProxyImpl); +}; + +} // namespace internal +} // namespace base + +#endif // BASE_MESSAGE_LOOP_MESSAGE_LOOP_PROXY_IMPL_H_ diff --git a/base/message_loop/message_loop_proxy_impl_unittest.cc b/base/message_loop/message_loop_proxy_impl_unittest.cc new file mode 100644 index 0000000000..81c9b0c51b --- /dev/null +++ b/base/message_loop/message_loop_proxy_impl_unittest.cc @@ -0,0 +1,129 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/message_loop/message_loop_proxy_impl.h" + +#include "base/bind.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/threading/thread.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" + +namespace base { + +class MessageLoopProxyImplTest : public testing::Test { + public: + void Release() const { + AssertOnIOThread(); + Quit(); + } + + void Quit() const { + loop_.PostTask(FROM_HERE, MessageLoop::QuitWhenIdleClosure()); + } + + void AssertOnIOThread() const { + ASSERT_TRUE(io_thread_->message_loop_proxy()->BelongsToCurrentThread()); + ASSERT_EQ(io_thread_->message_loop_proxy(), + MessageLoopProxy::current()); + } + + void AssertOnFileThread() const { + ASSERT_TRUE(file_thread_->message_loop_proxy()->BelongsToCurrentThread()); + ASSERT_EQ(file_thread_->message_loop_proxy(), + MessageLoopProxy::current()); + } + + protected: + virtual void SetUp() OVERRIDE { + io_thread_.reset(new Thread("MessageLoopProxyImplTest_IO")); + file_thread_.reset(new Thread("MessageLoopProxyImplTest_File")); + io_thread_->Start(); + file_thread_->Start(); + } + + virtual void TearDown() OVERRIDE { + io_thread_->Stop(); + file_thread_->Stop(); + } + + static void BasicFunction(MessageLoopProxyImplTest* test) { + test->AssertOnFileThread(); + test->Quit(); + } + + static void AssertNotRun() { + FAIL() << "Callback Should not get executed."; + } + + class DeletedOnFile { + public: + explicit DeletedOnFile(MessageLoopProxyImplTest* test) : test_(test) {} + + ~DeletedOnFile() { + test_->AssertOnFileThread(); + test_->Quit(); + } + + private: + MessageLoopProxyImplTest* test_; + }; + + scoped_ptr io_thread_; + scoped_ptr file_thread_; + + private: + mutable MessageLoop loop_; +}; + +TEST_F(MessageLoopProxyImplTest, Release) { + EXPECT_TRUE(io_thread_->message_loop_proxy()->ReleaseSoon(FROM_HERE, this)); + MessageLoop::current()->Run(); +} + +TEST_F(MessageLoopProxyImplTest, Delete) { + DeletedOnFile* deleted_on_file = new DeletedOnFile(this); + EXPECT_TRUE(file_thread_->message_loop_proxy()->DeleteSoon( + FROM_HERE, deleted_on_file)); + MessageLoop::current()->Run(); +} + +TEST_F(MessageLoopProxyImplTest, PostTask) { + EXPECT_TRUE(file_thread_->message_loop_proxy()->PostTask( + FROM_HERE, Bind(&MessageLoopProxyImplTest::BasicFunction, + Unretained(this)))); + MessageLoop::current()->Run(); +} + +TEST_F(MessageLoopProxyImplTest, PostTaskAfterThreadExits) { + scoped_ptr test_thread( + new Thread("MessageLoopProxyImplTest_Dummy")); + test_thread->Start(); + scoped_refptr message_loop_proxy = + test_thread->message_loop_proxy(); + test_thread->Stop(); + + bool ret = message_loop_proxy->PostTask( + FROM_HERE, + Bind(&MessageLoopProxyImplTest::AssertNotRun)); + EXPECT_FALSE(ret); +} + +TEST_F(MessageLoopProxyImplTest, PostTaskAfterThreadIsDeleted) { + scoped_refptr message_loop_proxy; + { + scoped_ptr test_thread( + new Thread("MessageLoopProxyImplTest_Dummy")); + test_thread->Start(); + message_loop_proxy = test_thread->message_loop_proxy(); + } + bool ret = message_loop_proxy->PostTask( + FROM_HERE, + Bind(&MessageLoopProxyImplTest::AssertNotRun)); + EXPECT_FALSE(ret); +} + +} // namespace base diff --git a/base/message_loop/message_loop_proxy_unittest.cc b/base/message_loop/message_loop_proxy_unittest.cc new file mode 100644 index 0000000000..ada9080811 --- /dev/null +++ b/base/message_loop/message_loop_proxy_unittest.cc @@ -0,0 +1,266 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/message_loop/message_loop_proxy.h" + +#include "base/atomic_sequence_num.h" +#include "base/bind.h" +#include "base/debug/leak_annotations.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace { + +class MessageLoopProxyTest : public testing::Test { + public: + MessageLoopProxyTest() + : current_loop_(new MessageLoop()), + task_thread_("task_thread"), + thread_sync_(true, false) { + } + + void DeleteCurrentMessageLoop() { + current_loop_.reset(); + } + + protected: + virtual void SetUp() OVERRIDE { + // Use SetUp() instead of the constructor to avoid posting a task to a + // partialy constructed object. + task_thread_.Start(); + + // Allow us to pause the |task_thread_|'s MessageLoop. + task_thread_.message_loop()->PostTask( + FROM_HERE, + Bind(&MessageLoopProxyTest::BlockTaskThreadHelper, Unretained(this))); + } + + virtual void TearDown() OVERRIDE { + // Make sure the |task_thread_| is not blocked, and stop the thread + // fully before destuction because its tasks may still depend on the + // |thread_sync_| event. + thread_sync_.Signal(); + task_thread_.Stop(); + DeleteCurrentMessageLoop(); + } + + // Make LoopRecorder threadsafe so that there is defined behavior even if a + // threading mistake sneaks into the PostTaskAndReplyRelay implementation. + class LoopRecorder : public RefCountedThreadSafe { + public: + LoopRecorder(MessageLoop** run_on, MessageLoop** deleted_on, + int* destruct_order) + : run_on_(run_on), + deleted_on_(deleted_on), + destruct_order_(destruct_order) { + } + + void RecordRun() { + *run_on_ = MessageLoop::current(); + } + + private: + friend class RefCountedThreadSafe; + ~LoopRecorder() { + *deleted_on_ = MessageLoop::current(); + *destruct_order_ = g_order.GetNext(); + } + + MessageLoop** run_on_; + MessageLoop** deleted_on_; + int* destruct_order_; + }; + + static void RecordLoop(scoped_refptr recorder) { + recorder->RecordRun(); + } + + static void RecordLoopAndQuit(scoped_refptr recorder) { + recorder->RecordRun(); + MessageLoop::current()->QuitWhenIdle(); + } + + void UnblockTaskThread() { + thread_sync_.Signal(); + } + + void BlockTaskThreadHelper() { + thread_sync_.Wait(); + } + + static StaticAtomicSequenceNumber g_order; + + scoped_ptr current_loop_; + Thread task_thread_; + + private: + base::WaitableEvent thread_sync_; +}; + +StaticAtomicSequenceNumber MessageLoopProxyTest::g_order; + +TEST_F(MessageLoopProxyTest, PostTaskAndReply_Basic) { + MessageLoop* task_run_on = NULL; + MessageLoop* task_deleted_on = NULL; + int task_delete_order = -1; + MessageLoop* reply_run_on = NULL; + MessageLoop* reply_deleted_on = NULL; + int reply_delete_order = -1; + + scoped_refptr task_recoder = + new LoopRecorder(&task_run_on, &task_deleted_on, &task_delete_order); + scoped_refptr reply_recoder = + new LoopRecorder(&reply_run_on, &reply_deleted_on, &reply_delete_order); + + ASSERT_TRUE(task_thread_.message_loop_proxy()->PostTaskAndReply( + FROM_HERE, + Bind(&RecordLoop, task_recoder), + Bind(&RecordLoopAndQuit, reply_recoder))); + + // Die if base::Bind doesn't retain a reference to the recorders. + task_recoder = NULL; + reply_recoder = NULL; + ASSERT_FALSE(task_deleted_on); + ASSERT_FALSE(reply_deleted_on); + + UnblockTaskThread(); + current_loop_->Run(); + + EXPECT_EQ(task_thread_.message_loop(), task_run_on); + EXPECT_EQ(current_loop_.get(), task_deleted_on); + EXPECT_EQ(current_loop_.get(), reply_run_on); + EXPECT_EQ(current_loop_.get(), reply_deleted_on); + EXPECT_LT(task_delete_order, reply_delete_order); +} + +TEST_F(MessageLoopProxyTest, PostTaskAndReplyOnDeletedThreadDoesNotLeak) { + MessageLoop* task_run_on = NULL; + MessageLoop* task_deleted_on = NULL; + int task_delete_order = -1; + MessageLoop* reply_run_on = NULL; + MessageLoop* reply_deleted_on = NULL; + int reply_delete_order = -1; + + scoped_refptr task_recoder = + new LoopRecorder(&task_run_on, &task_deleted_on, &task_delete_order); + scoped_refptr reply_recoder = + new LoopRecorder(&reply_run_on, &reply_deleted_on, &reply_delete_order); + + // Grab a MessageLoopProxy to a dead MessageLoop. + scoped_refptr task_loop_proxy = + task_thread_.message_loop_proxy(); + UnblockTaskThread(); + task_thread_.Stop(); + + ASSERT_FALSE(task_loop_proxy->PostTaskAndReply( + FROM_HERE, + Bind(&RecordLoop, task_recoder), + Bind(&RecordLoopAndQuit, reply_recoder))); + + // The relay should have properly deleted its resources leaving us as the only + // reference. + EXPECT_EQ(task_delete_order, reply_delete_order); + ASSERT_TRUE(task_recoder->HasOneRef()); + ASSERT_TRUE(reply_recoder->HasOneRef()); + + // Nothing should have run though. + EXPECT_FALSE(task_run_on); + EXPECT_FALSE(reply_run_on); +} + +TEST_F(MessageLoopProxyTest, PostTaskAndReply_SameLoop) { + MessageLoop* task_run_on = NULL; + MessageLoop* task_deleted_on = NULL; + int task_delete_order = -1; + MessageLoop* reply_run_on = NULL; + MessageLoop* reply_deleted_on = NULL; + int reply_delete_order = -1; + + scoped_refptr task_recoder = + new LoopRecorder(&task_run_on, &task_deleted_on, &task_delete_order); + scoped_refptr reply_recoder = + new LoopRecorder(&reply_run_on, &reply_deleted_on, &reply_delete_order); + + // Enqueue the relay. + ASSERT_TRUE(current_loop_->message_loop_proxy()->PostTaskAndReply( + FROM_HERE, + Bind(&RecordLoop, task_recoder), + Bind(&RecordLoopAndQuit, reply_recoder))); + + // Die if base::Bind doesn't retain a reference to the recorders. + task_recoder = NULL; + reply_recoder = NULL; + ASSERT_FALSE(task_deleted_on); + ASSERT_FALSE(reply_deleted_on); + + current_loop_->Run(); + + EXPECT_EQ(current_loop_.get(), task_run_on); + EXPECT_EQ(current_loop_.get(), task_deleted_on); + EXPECT_EQ(current_loop_.get(), reply_run_on); + EXPECT_EQ(current_loop_.get(), reply_deleted_on); + EXPECT_LT(task_delete_order, reply_delete_order); +} + +TEST_F(MessageLoopProxyTest, PostTaskAndReply_DeadReplyLoopDoesNotDelete) { + // Annotate the scope as having memory leaks to suppress heapchecker reports. + ANNOTATE_SCOPED_MEMORY_LEAK; + MessageLoop* task_run_on = NULL; + MessageLoop* task_deleted_on = NULL; + int task_delete_order = -1; + MessageLoop* reply_run_on = NULL; + MessageLoop* reply_deleted_on = NULL; + int reply_delete_order = -1; + + scoped_refptr task_recoder = + new LoopRecorder(&task_run_on, &task_deleted_on, &task_delete_order); + scoped_refptr reply_recoder = + new LoopRecorder(&reply_run_on, &reply_deleted_on, &reply_delete_order); + + // Enqueue the relay. + task_thread_.message_loop_proxy()->PostTaskAndReply( + FROM_HERE, + Bind(&RecordLoop, task_recoder), + Bind(&RecordLoopAndQuit, reply_recoder)); + + // Die if base::Bind doesn't retain a reference to the recorders. + task_recoder = NULL; + reply_recoder = NULL; + ASSERT_FALSE(task_deleted_on); + ASSERT_FALSE(reply_deleted_on); + + UnblockTaskThread(); + + // Mercilessly whack the current loop before |reply| gets to run. + current_loop_.reset(); + + // This should ensure the relay has been run. We need to record the + // MessageLoop pointer before stopping the thread because Thread::Stop() will + // NULL out its own pointer. + MessageLoop* task_loop = task_thread_.message_loop(); + task_thread_.Stop(); + + EXPECT_EQ(task_loop, task_run_on); + ASSERT_FALSE(task_deleted_on); + EXPECT_FALSE(reply_run_on); + ASSERT_FALSE(reply_deleted_on); + EXPECT_EQ(task_delete_order, reply_delete_order); + + // The PostTaskAndReplyRelay is leaked here. Even if we had a reference to + // it, we cannot just delete it because PostTaskAndReplyRelay's destructor + // checks that MessageLoop::current() is the the same as when the + // PostTaskAndReplyRelay object was constructed. However, this loop must have + // aleady been deleted in order to perform this test. See + // http://crbug.com/86301. +} + +} // namespace + +} // namespace base diff --git a/base/message_loop/message_loop_unittest.cc b/base/message_loop/message_loop_unittest.cc new file mode 100644 index 0000000000..ab05b3cb5a --- /dev/null +++ b/base/message_loop/message_loop_unittest.cc @@ -0,0 +1,2104 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy_impl.h" +#include "base/pending_task.h" +#include "base/posix/eintr_wrapper.h" +#include "base/run_loop.h" +#include "base/synchronization/waitable_event.h" +#include "base/thread_task_runner_handle.h" +#include "base/threading/platform_thread.h" +#include "base/threading/thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_WIN) +#include "base/message_loop/message_pump_win.h" +#include "base/win/scoped_handle.h" +#endif + +namespace base { + +// TODO(darin): Platform-specific MessageLoop tests should be grouped together +// to avoid chopping this file up with so many #ifdefs. + +namespace { + +class Foo : public RefCounted { + public: + Foo() : test_count_(0) { + } + + void Test0() { + ++test_count_; + } + + void Test1ConstRef(const std::string& a) { + ++test_count_; + result_.append(a); + } + + void Test1Ptr(std::string* a) { + ++test_count_; + result_.append(*a); + } + + void Test1Int(int a) { + test_count_ += a; + } + + void Test2Ptr(std::string* a, std::string* b) { + ++test_count_; + result_.append(*a); + result_.append(*b); + } + + void Test2Mixed(const std::string& a, std::string* b) { + ++test_count_; + result_.append(a); + result_.append(*b); + } + + int test_count() const { return test_count_; } + const std::string& result() const { return result_; } + + private: + friend class RefCounted; + + ~Foo() {} + + int test_count_; + std::string result_; +}; + +void RunTest_PostTask(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + // Add tests to message loop + scoped_refptr foo(new Foo()); + std::string a("a"), b("b"), c("c"), d("d"); + MessageLoop::current()->PostTask(FROM_HERE, Bind( + &Foo::Test0, foo.get())); + MessageLoop::current()->PostTask(FROM_HERE, Bind( + &Foo::Test1ConstRef, foo.get(), a)); + MessageLoop::current()->PostTask(FROM_HERE, Bind( + &Foo::Test1Ptr, foo.get(), &b)); + MessageLoop::current()->PostTask(FROM_HERE, Bind( + &Foo::Test1Int, foo.get(), 100)); + MessageLoop::current()->PostTask(FROM_HERE, Bind( + &Foo::Test2Ptr, foo.get(), &a, &c)); + + // TryPost with no contention. It must succeed. + EXPECT_TRUE(MessageLoop::current()->TryPostTask(FROM_HERE, Bind( + &Foo::Test2Mixed, foo.get(), a, &d))); + + // TryPost with simulated contention. It must fail. We wait for a helper + // thread to lock the queue, we TryPost on this thread and finally we + // signal the helper to unlock and exit. + WaitableEvent wait(true, false); + WaitableEvent signal(true, false); + Thread thread("RunTest_PostTask_helper"); + thread.Start(); + thread.message_loop()->PostTask( + FROM_HERE, + Bind(&MessageLoop::LockWaitUnLockForTesting, + base::Unretained(MessageLoop::current()), + &wait, + &signal)); + + wait.Wait(); + EXPECT_FALSE(MessageLoop::current()->TryPostTask(FROM_HERE, Bind( + &Foo::Test2Mixed, foo.get(), a, &d))); + signal.Signal(); + + // After all tests, post a message that will shut down the message loop + MessageLoop::current()->PostTask(FROM_HERE, Bind( + &MessageLoop::Quit, Unretained(MessageLoop::current()))); + + // Now kick things off + MessageLoop::current()->Run(); + + EXPECT_EQ(foo->test_count(), 105); + EXPECT_EQ(foo->result(), "abacad"); +} + +void RunTest_PostTask_SEH(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + // Add tests to message loop + scoped_refptr foo(new Foo()); + std::string a("a"), b("b"), c("c"), d("d"); + MessageLoop::current()->PostTask(FROM_HERE, Bind( + &Foo::Test0, foo.get())); + MessageLoop::current()->PostTask(FROM_HERE, Bind( + &Foo::Test1ConstRef, foo.get(), a)); + MessageLoop::current()->PostTask(FROM_HERE, Bind( + &Foo::Test1Ptr, foo.get(), &b)); + MessageLoop::current()->PostTask(FROM_HERE, Bind( + &Foo::Test1Int, foo.get(), 100)); + MessageLoop::current()->PostTask(FROM_HERE, Bind( + &Foo::Test2Ptr, foo.get(), &a, &c)); + MessageLoop::current()->PostTask(FROM_HERE, Bind( + &Foo::Test2Mixed, foo.get(), a, &d)); + + // After all tests, post a message that will shut down the message loop + MessageLoop::current()->PostTask(FROM_HERE, Bind( + &MessageLoop::Quit, Unretained(MessageLoop::current()))); + + // Now kick things off with the SEH block active. + MessageLoop::current()->set_exception_restoration(true); + MessageLoop::current()->Run(); + MessageLoop::current()->set_exception_restoration(false); + + EXPECT_EQ(foo->test_count(), 105); + EXPECT_EQ(foo->result(), "abacad"); +} + +// This function runs slowly to simulate a large amount of work being done. +static void SlowFunc(TimeDelta pause, int* quit_counter) { + PlatformThread::Sleep(pause); + if (--(*quit_counter) == 0) + MessageLoop::current()->QuitWhenIdle(); +} + +// This function records the time when Run was called in a Time object, which is +// useful for building a variety of MessageLoop tests. +static void RecordRunTimeFunc(Time* run_time, int* quit_counter) { + *run_time = Time::Now(); + + // Cause our Run function to take some time to execute. As a result we can + // count on subsequent RecordRunTimeFunc()s running at a future time, + // without worry about the resolution of our system clock being an issue. + SlowFunc(TimeDelta::FromMilliseconds(10), quit_counter); +} + +void RunTest_PostDelayedTask_Basic(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + // Test that PostDelayedTask results in a delayed task. + + const TimeDelta kDelay = TimeDelta::FromMilliseconds(100); + + int num_tasks = 1; + Time run_time; + + loop.PostDelayedTask( + FROM_HERE, Bind(&RecordRunTimeFunc, &run_time, &num_tasks), + kDelay); + + Time time_before_run = Time::Now(); + loop.Run(); + Time time_after_run = Time::Now(); + + EXPECT_EQ(0, num_tasks); + EXPECT_LT(kDelay, time_after_run - time_before_run); +} + +void RunTest_PostDelayedTask_InDelayOrder( + MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + // Test that two tasks with different delays run in the right order. + int num_tasks = 2; + Time run_time1, run_time2; + + loop.PostDelayedTask( + FROM_HERE, + Bind(&RecordRunTimeFunc, &run_time1, &num_tasks), + TimeDelta::FromMilliseconds(200)); + // If we get a large pause in execution (due to a context switch) here, this + // test could fail. + loop.PostDelayedTask( + FROM_HERE, + Bind(&RecordRunTimeFunc, &run_time2, &num_tasks), + TimeDelta::FromMilliseconds(10)); + + loop.Run(); + EXPECT_EQ(0, num_tasks); + + EXPECT_TRUE(run_time2 < run_time1); +} + +void RunTest_PostDelayedTask_InPostOrder( + MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + // Test that two tasks with the same delay run in the order in which they + // were posted. + // + // NOTE: This is actually an approximate test since the API only takes a + // "delay" parameter, so we are not exactly simulating two tasks that get + // posted at the exact same time. It would be nice if the API allowed us to + // specify the desired run time. + + const TimeDelta kDelay = TimeDelta::FromMilliseconds(100); + + int num_tasks = 2; + Time run_time1, run_time2; + + loop.PostDelayedTask( + FROM_HERE, + Bind(&RecordRunTimeFunc, &run_time1, &num_tasks), kDelay); + loop.PostDelayedTask( + FROM_HERE, + Bind(&RecordRunTimeFunc, &run_time2, &num_tasks), kDelay); + + loop.Run(); + EXPECT_EQ(0, num_tasks); + + EXPECT_TRUE(run_time1 < run_time2); +} + +void RunTest_PostDelayedTask_InPostOrder_2( + MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + // Test that a delayed task still runs after a normal tasks even if the + // normal tasks take a long time to run. + + const TimeDelta kPause = TimeDelta::FromMilliseconds(50); + + int num_tasks = 2; + Time run_time; + + loop.PostTask(FROM_HERE, Bind(&SlowFunc, kPause, &num_tasks)); + loop.PostDelayedTask( + FROM_HERE, + Bind(&RecordRunTimeFunc, &run_time, &num_tasks), + TimeDelta::FromMilliseconds(10)); + + Time time_before_run = Time::Now(); + loop.Run(); + Time time_after_run = Time::Now(); + + EXPECT_EQ(0, num_tasks); + + EXPECT_LT(kPause, time_after_run - time_before_run); +} + +void RunTest_PostDelayedTask_InPostOrder_3( + MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + // Test that a delayed task still runs after a pile of normal tasks. The key + // difference between this test and the previous one is that here we return + // the MessageLoop a lot so we give the MessageLoop plenty of opportunities + // to maybe run the delayed task. It should know not to do so until the + // delayed task's delay has passed. + + int num_tasks = 11; + Time run_time1, run_time2; + + // Clutter the ML with tasks. + for (int i = 1; i < num_tasks; ++i) + loop.PostTask(FROM_HERE, + Bind(&RecordRunTimeFunc, &run_time1, &num_tasks)); + + loop.PostDelayedTask( + FROM_HERE, Bind(&RecordRunTimeFunc, &run_time2, &num_tasks), + TimeDelta::FromMilliseconds(1)); + + loop.Run(); + EXPECT_EQ(0, num_tasks); + + EXPECT_TRUE(run_time2 > run_time1); +} + +void RunTest_PostDelayedTask_SharedTimer( + MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + // Test that the interval of the timer, used to run the next delayed task, is + // set to a value corresponding to when the next delayed task should run. + + // By setting num_tasks to 1, we ensure that the first task to run causes the + // run loop to exit. + int num_tasks = 1; + Time run_time1, run_time2; + + loop.PostDelayedTask( + FROM_HERE, + Bind(&RecordRunTimeFunc, &run_time1, &num_tasks), + TimeDelta::FromSeconds(1000)); + loop.PostDelayedTask( + FROM_HERE, + Bind(&RecordRunTimeFunc, &run_time2, &num_tasks), + TimeDelta::FromMilliseconds(10)); + + Time start_time = Time::Now(); + + loop.Run(); + EXPECT_EQ(0, num_tasks); + + // Ensure that we ran in far less time than the slower timer. + TimeDelta total_time = Time::Now() - start_time; + EXPECT_GT(5000, total_time.InMilliseconds()); + + // In case both timers somehow run at nearly the same time, sleep a little + // and then run all pending to force them both to have run. This is just + // encouraging flakiness if there is any. + PlatformThread::Sleep(TimeDelta::FromMilliseconds(100)); + RunLoop().RunUntilIdle(); + + EXPECT_TRUE(run_time1.is_null()); + EXPECT_FALSE(run_time2.is_null()); +} + +#if defined(OS_WIN) + +void SubPumpFunc() { + MessageLoop::current()->SetNestableTasksAllowed(true); + MSG msg; + while (GetMessage(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + MessageLoop::current()->QuitWhenIdle(); +} + +void RunTest_PostDelayedTask_SharedTimer_SubPump() { + MessageLoop loop(MessageLoop::TYPE_UI); + + // Test that the interval of the timer, used to run the next delayed task, is + // set to a value corresponding to when the next delayed task should run. + + // By setting num_tasks to 1, we ensure that the first task to run causes the + // run loop to exit. + int num_tasks = 1; + Time run_time; + + loop.PostTask(FROM_HERE, Bind(&SubPumpFunc)); + + // This very delayed task should never run. + loop.PostDelayedTask( + FROM_HERE, + Bind(&RecordRunTimeFunc, &run_time, &num_tasks), + TimeDelta::FromSeconds(1000)); + + // This slightly delayed task should run from within SubPumpFunc). + loop.PostDelayedTask( + FROM_HERE, + Bind(&PostQuitMessage, 0), + TimeDelta::FromMilliseconds(10)); + + Time start_time = Time::Now(); + + loop.Run(); + EXPECT_EQ(1, num_tasks); + + // Ensure that we ran in far less time than the slower timer. + TimeDelta total_time = Time::Now() - start_time; + EXPECT_GT(5000, total_time.InMilliseconds()); + + // In case both timers somehow run at nearly the same time, sleep a little + // and then run all pending to force them both to have run. This is just + // encouraging flakiness if there is any. + PlatformThread::Sleep(TimeDelta::FromMilliseconds(100)); + RunLoop().RunUntilIdle(); + + EXPECT_TRUE(run_time.is_null()); +} + +#endif // defined(OS_WIN) + +// This is used to inject a test point for recording the destructor calls for +// Closure objects send to MessageLoop::PostTask(). It is awkward usage since we +// are trying to hook the actual destruction, which is not a common operation. +class RecordDeletionProbe : public RefCounted { + public: + RecordDeletionProbe(RecordDeletionProbe* post_on_delete, bool* was_deleted) + : post_on_delete_(post_on_delete), was_deleted_(was_deleted) { + } + void Run() {} + + private: + friend class RefCounted; + + ~RecordDeletionProbe() { + *was_deleted_ = true; + if (post_on_delete_.get()) + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&RecordDeletionProbe::Run, post_on_delete_.get())); + } + + scoped_refptr post_on_delete_; + bool* was_deleted_; +}; + +void RunTest_EnsureDeletion(MessageLoop::Type message_loop_type) { + bool a_was_deleted = false; + bool b_was_deleted = false; + { + MessageLoop loop(message_loop_type); + loop.PostTask( + FROM_HERE, Bind(&RecordDeletionProbe::Run, + new RecordDeletionProbe(NULL, &a_was_deleted))); + // TODO(ajwong): Do we really need 1000ms here? + loop.PostDelayedTask( + FROM_HERE, Bind(&RecordDeletionProbe::Run, + new RecordDeletionProbe(NULL, &b_was_deleted)), + TimeDelta::FromMilliseconds(1000)); + } + EXPECT_TRUE(a_was_deleted); + EXPECT_TRUE(b_was_deleted); +} + +void RunTest_EnsureDeletion_Chain(MessageLoop::Type message_loop_type) { + bool a_was_deleted = false; + bool b_was_deleted = false; + bool c_was_deleted = false; + { + MessageLoop loop(message_loop_type); + // The scoped_refptr for each of the below is held either by the chained + // RecordDeletionProbe, or the bound RecordDeletionProbe::Run() callback. + RecordDeletionProbe* a = new RecordDeletionProbe(NULL, &a_was_deleted); + RecordDeletionProbe* b = new RecordDeletionProbe(a, &b_was_deleted); + RecordDeletionProbe* c = new RecordDeletionProbe(b, &c_was_deleted); + loop.PostTask(FROM_HERE, Bind(&RecordDeletionProbe::Run, c)); + } + EXPECT_TRUE(a_was_deleted); + EXPECT_TRUE(b_was_deleted); + EXPECT_TRUE(c_was_deleted); +} + +void NestingFunc(int* depth) { + if (*depth > 0) { + *depth -= 1; + MessageLoop::current()->PostTask(FROM_HERE, + Bind(&NestingFunc, depth)); + + MessageLoop::current()->SetNestableTasksAllowed(true); + MessageLoop::current()->Run(); + } + MessageLoop::current()->QuitWhenIdle(); +} + +#if defined(OS_WIN) + +LONG WINAPI BadExceptionHandler(EXCEPTION_POINTERS *ex_info) { + ADD_FAILURE() << "bad exception handler"; + ::ExitProcess(ex_info->ExceptionRecord->ExceptionCode); + return EXCEPTION_EXECUTE_HANDLER; +} + +// This task throws an SEH exception: initially write to an invalid address. +// If the right SEH filter is installed, it will fix the error. +class Crasher : public RefCounted { + public: + // Ctor. If trash_SEH_handler is true, the task will override the unhandled + // exception handler with one sure to crash this test. + explicit Crasher(bool trash_SEH_handler) + : trash_SEH_handler_(trash_SEH_handler) { + } + + void Run() { + PlatformThread::Sleep(TimeDelta::FromMilliseconds(1)); + if (trash_SEH_handler_) + ::SetUnhandledExceptionFilter(&BadExceptionHandler); + // Generate a SEH fault. We do it in asm to make sure we know how to undo + // the damage. + +#if defined(_M_IX86) + + __asm { + mov eax, dword ptr [Crasher::bad_array_] + mov byte ptr [eax], 66 + } + +#elif defined(_M_X64) + + bad_array_[0] = 66; + +#else +#error "needs architecture support" +#endif + + MessageLoop::current()->QuitWhenIdle(); + } + // Points the bad array to a valid memory location. + static void FixError() { + bad_array_ = &valid_store_; + } + + private: + bool trash_SEH_handler_; + static volatile char* bad_array_; + static char valid_store_; +}; + +volatile char* Crasher::bad_array_ = 0; +char Crasher::valid_store_ = 0; + +// This SEH filter fixes the problem and retries execution. Fixing requires +// that the last instruction: mov eax, [Crasher::bad_array_] to be retried +// so we move the instruction pointer 5 bytes back. +LONG WINAPI HandleCrasherException(EXCEPTION_POINTERS *ex_info) { + if (ex_info->ExceptionRecord->ExceptionCode != EXCEPTION_ACCESS_VIOLATION) + return EXCEPTION_EXECUTE_HANDLER; + + Crasher::FixError(); + +#if defined(_M_IX86) + + ex_info->ContextRecord->Eip -= 5; + +#elif defined(_M_X64) + + ex_info->ContextRecord->Rip -= 5; + +#endif + + return EXCEPTION_CONTINUE_EXECUTION; +} + +void RunTest_Crasher(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + if (::IsDebuggerPresent()) + return; + + LPTOP_LEVEL_EXCEPTION_FILTER old_SEH_filter = + ::SetUnhandledExceptionFilter(&HandleCrasherException); + + MessageLoop::current()->PostTask( + FROM_HERE, + Bind(&Crasher::Run, new Crasher(false))); + MessageLoop::current()->set_exception_restoration(true); + MessageLoop::current()->Run(); + MessageLoop::current()->set_exception_restoration(false); + + ::SetUnhandledExceptionFilter(old_SEH_filter); +} + +void RunTest_CrasherNasty(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + if (::IsDebuggerPresent()) + return; + + LPTOP_LEVEL_EXCEPTION_FILTER old_SEH_filter = + ::SetUnhandledExceptionFilter(&HandleCrasherException); + + MessageLoop::current()->PostTask( + FROM_HERE, + Bind(&Crasher::Run, new Crasher(true))); + MessageLoop::current()->set_exception_restoration(true); + MessageLoop::current()->Run(); + MessageLoop::current()->set_exception_restoration(false); + + ::SetUnhandledExceptionFilter(old_SEH_filter); +} + +#endif // defined(OS_WIN) + +void RunTest_Nesting(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + int depth = 100; + MessageLoop::current()->PostTask(FROM_HERE, + Bind(&NestingFunc, &depth)); + MessageLoop::current()->Run(); + EXPECT_EQ(depth, 0); +} + +const wchar_t* const kMessageBoxTitle = L"MessageLoop Unit Test"; + +enum TaskType { + MESSAGEBOX, + ENDDIALOG, + RECURSIVE, + TIMEDMESSAGELOOP, + QUITMESSAGELOOP, + ORDERED, + PUMPS, + SLEEP, + RUNS, +}; + +// Saves the order in which the tasks executed. +struct TaskItem { + TaskItem(TaskType t, int c, bool s) + : type(t), + cookie(c), + start(s) { + } + + TaskType type; + int cookie; + bool start; + + bool operator == (const TaskItem& other) const { + return type == other.type && cookie == other.cookie && start == other.start; + } +}; + +std::ostream& operator <<(std::ostream& os, TaskType type) { + switch (type) { + case MESSAGEBOX: os << "MESSAGEBOX"; break; + case ENDDIALOG: os << "ENDDIALOG"; break; + case RECURSIVE: os << "RECURSIVE"; break; + case TIMEDMESSAGELOOP: os << "TIMEDMESSAGELOOP"; break; + case QUITMESSAGELOOP: os << "QUITMESSAGELOOP"; break; + case ORDERED: os << "ORDERED"; break; + case PUMPS: os << "PUMPS"; break; + case SLEEP: os << "SLEEP"; break; + default: + NOTREACHED(); + os << "Unknown TaskType"; + break; + } + return os; +} + +std::ostream& operator <<(std::ostream& os, const TaskItem& item) { + if (item.start) + return os << item.type << " " << item.cookie << " starts"; + else + return os << item.type << " " << item.cookie << " ends"; +} + +class TaskList { + public: + void RecordStart(TaskType type, int cookie) { + TaskItem item(type, cookie, true); + DVLOG(1) << item; + task_list_.push_back(item); + } + + void RecordEnd(TaskType type, int cookie) { + TaskItem item(type, cookie, false); + DVLOG(1) << item; + task_list_.push_back(item); + } + + size_t Size() { + return task_list_.size(); + } + + TaskItem Get(int n) { + return task_list_[n]; + } + + private: + std::vector task_list_; +}; + +// Saves the order the tasks ran. +void OrderedFunc(TaskList* order, int cookie) { + order->RecordStart(ORDERED, cookie); + order->RecordEnd(ORDERED, cookie); +} + +#if defined(OS_WIN) + +// MessageLoop implicitly start a "modal message loop". Modal dialog boxes, +// common controls (like OpenFile) and StartDoc printing function can cause +// implicit message loops. +void MessageBoxFunc(TaskList* order, int cookie, bool is_reentrant) { + order->RecordStart(MESSAGEBOX, cookie); + if (is_reentrant) + MessageLoop::current()->SetNestableTasksAllowed(true); + MessageBox(NULL, L"Please wait...", kMessageBoxTitle, MB_OK); + order->RecordEnd(MESSAGEBOX, cookie); +} + +// Will end the MessageBox. +void EndDialogFunc(TaskList* order, int cookie) { + order->RecordStart(ENDDIALOG, cookie); + HWND window = GetActiveWindow(); + if (window != NULL) { + EXPECT_NE(EndDialog(window, IDCONTINUE), 0); + // Cheap way to signal that the window wasn't found if RunEnd() isn't + // called. + order->RecordEnd(ENDDIALOG, cookie); + } +} + +#endif // defined(OS_WIN) + +void RecursiveFunc(TaskList* order, int cookie, int depth, + bool is_reentrant) { + order->RecordStart(RECURSIVE, cookie); + if (depth > 0) { + if (is_reentrant) + MessageLoop::current()->SetNestableTasksAllowed(true); + MessageLoop::current()->PostTask( + FROM_HERE, + Bind(&RecursiveFunc, order, cookie, depth - 1, is_reentrant)); + } + order->RecordEnd(RECURSIVE, cookie); +} + +void RecursiveSlowFunc(TaskList* order, int cookie, int depth, + bool is_reentrant) { + RecursiveFunc(order, cookie, depth, is_reentrant); + PlatformThread::Sleep(TimeDelta::FromMilliseconds(10)); +} + +void QuitFunc(TaskList* order, int cookie) { + order->RecordStart(QUITMESSAGELOOP, cookie); + MessageLoop::current()->QuitWhenIdle(); + order->RecordEnd(QUITMESSAGELOOP, cookie); +} + +void SleepFunc(TaskList* order, int cookie, TimeDelta delay) { + order->RecordStart(SLEEP, cookie); + PlatformThread::Sleep(delay); + order->RecordEnd(SLEEP, cookie); +} + +#if defined(OS_WIN) +void RecursiveFuncWin(MessageLoop* target, + HANDLE event, + bool expect_window, + TaskList* order, + bool is_reentrant) { + target->PostTask(FROM_HERE, + Bind(&RecursiveFunc, order, 1, 2, is_reentrant)); + target->PostTask(FROM_HERE, + Bind(&MessageBoxFunc, order, 2, is_reentrant)); + target->PostTask(FROM_HERE, + Bind(&RecursiveFunc, order, 3, 2, is_reentrant)); + // The trick here is that for recursive task processing, this task will be + // ran _inside_ the MessageBox message loop, dismissing the MessageBox + // without a chance. + // For non-recursive task processing, this will be executed _after_ the + // MessageBox will have been dismissed by the code below, where + // expect_window_ is true. + target->PostTask(FROM_HERE, + Bind(&EndDialogFunc, order, 4)); + target->PostTask(FROM_HERE, + Bind(&QuitFunc, order, 5)); + + // Enforce that every tasks are sent before starting to run the main thread + // message loop. + ASSERT_TRUE(SetEvent(event)); + + // Poll for the MessageBox. Don't do this at home! At the speed we do it, + // you will never realize one MessageBox was shown. + for (; expect_window;) { + HWND window = FindWindow(L"#32770", kMessageBoxTitle); + if (window) { + // Dismiss it. + for (;;) { + HWND button = FindWindowEx(window, NULL, L"Button", NULL); + if (button != NULL) { + EXPECT_EQ(0, SendMessage(button, WM_LBUTTONDOWN, 0, 0)); + EXPECT_EQ(0, SendMessage(button, WM_LBUTTONUP, 0, 0)); + break; + } + } + break; + } + } +} + +#endif // defined(OS_WIN) + +void RunTest_RecursiveDenial1(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + EXPECT_TRUE(MessageLoop::current()->NestableTasksAllowed()); + TaskList order; + MessageLoop::current()->PostTask( + FROM_HERE, + Bind(&RecursiveFunc, &order, 1, 2, false)); + MessageLoop::current()->PostTask( + FROM_HERE, + Bind(&RecursiveFunc, &order, 2, 2, false)); + MessageLoop::current()->PostTask( + FROM_HERE, + Bind(&QuitFunc, &order, 3)); + + MessageLoop::current()->Run(); + + // FIFO order. + ASSERT_EQ(14U, order.Size()); + EXPECT_EQ(order.Get(0), TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order.Get(1), TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order.Get(2), TaskItem(RECURSIVE, 2, true)); + EXPECT_EQ(order.Get(3), TaskItem(RECURSIVE, 2, false)); + EXPECT_EQ(order.Get(4), TaskItem(QUITMESSAGELOOP, 3, true)); + EXPECT_EQ(order.Get(5), TaskItem(QUITMESSAGELOOP, 3, false)); + EXPECT_EQ(order.Get(6), TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order.Get(7), TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order.Get(8), TaskItem(RECURSIVE, 2, true)); + EXPECT_EQ(order.Get(9), TaskItem(RECURSIVE, 2, false)); + EXPECT_EQ(order.Get(10), TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order.Get(11), TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order.Get(12), TaskItem(RECURSIVE, 2, true)); + EXPECT_EQ(order.Get(13), TaskItem(RECURSIVE, 2, false)); +} + +void RunTest_RecursiveDenial3(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + EXPECT_TRUE(MessageLoop::current()->NestableTasksAllowed()); + TaskList order; + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&RecursiveSlowFunc, &order, 1, 2, false)); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&RecursiveSlowFunc, &order, 2, 2, false)); + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + Bind(&OrderedFunc, &order, 3), + TimeDelta::FromMilliseconds(5)); + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + Bind(&QuitFunc, &order, 4), + TimeDelta::FromMilliseconds(5)); + + MessageLoop::current()->Run(); + + // FIFO order. + ASSERT_EQ(16U, order.Size()); + EXPECT_EQ(order.Get(0), TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order.Get(1), TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order.Get(2), TaskItem(RECURSIVE, 2, true)); + EXPECT_EQ(order.Get(3), TaskItem(RECURSIVE, 2, false)); + EXPECT_EQ(order.Get(4), TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order.Get(5), TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order.Get(6), TaskItem(ORDERED, 3, true)); + EXPECT_EQ(order.Get(7), TaskItem(ORDERED, 3, false)); + EXPECT_EQ(order.Get(8), TaskItem(RECURSIVE, 2, true)); + EXPECT_EQ(order.Get(9), TaskItem(RECURSIVE, 2, false)); + EXPECT_EQ(order.Get(10), TaskItem(QUITMESSAGELOOP, 4, true)); + EXPECT_EQ(order.Get(11), TaskItem(QUITMESSAGELOOP, 4, false)); + EXPECT_EQ(order.Get(12), TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order.Get(13), TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order.Get(14), TaskItem(RECURSIVE, 2, true)); + EXPECT_EQ(order.Get(15), TaskItem(RECURSIVE, 2, false)); +} + +void RunTest_RecursiveSupport1(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + TaskList order; + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&RecursiveFunc, &order, 1, 2, true)); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&RecursiveFunc, &order, 2, 2, true)); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&QuitFunc, &order, 3)); + + MessageLoop::current()->Run(); + + // FIFO order. + ASSERT_EQ(14U, order.Size()); + EXPECT_EQ(order.Get(0), TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order.Get(1), TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order.Get(2), TaskItem(RECURSIVE, 2, true)); + EXPECT_EQ(order.Get(3), TaskItem(RECURSIVE, 2, false)); + EXPECT_EQ(order.Get(4), TaskItem(QUITMESSAGELOOP, 3, true)); + EXPECT_EQ(order.Get(5), TaskItem(QUITMESSAGELOOP, 3, false)); + EXPECT_EQ(order.Get(6), TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order.Get(7), TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order.Get(8), TaskItem(RECURSIVE, 2, true)); + EXPECT_EQ(order.Get(9), TaskItem(RECURSIVE, 2, false)); + EXPECT_EQ(order.Get(10), TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order.Get(11), TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order.Get(12), TaskItem(RECURSIVE, 2, true)); + EXPECT_EQ(order.Get(13), TaskItem(RECURSIVE, 2, false)); +} + +#if defined(OS_WIN) +// TODO(darin): These tests need to be ported since they test critical +// message loop functionality. + +// A side effect of this test is the generation a beep. Sorry. +void RunTest_RecursiveDenial2(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + Thread worker("RecursiveDenial2_worker"); + Thread::Options options; + options.message_loop_type = message_loop_type; + ASSERT_EQ(true, worker.StartWithOptions(options)); + TaskList order; + win::ScopedHandle event(CreateEvent(NULL, FALSE, FALSE, NULL)); + worker.message_loop()->PostTask(FROM_HERE, + Bind(&RecursiveFuncWin, + MessageLoop::current(), + event.Get(), + true, + &order, + false)); + // Let the other thread execute. + WaitForSingleObject(event, INFINITE); + MessageLoop::current()->Run(); + + ASSERT_EQ(order.Size(), 17); + EXPECT_EQ(order.Get(0), TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order.Get(1), TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order.Get(2), TaskItem(MESSAGEBOX, 2, true)); + EXPECT_EQ(order.Get(3), TaskItem(MESSAGEBOX, 2, false)); + EXPECT_EQ(order.Get(4), TaskItem(RECURSIVE, 3, true)); + EXPECT_EQ(order.Get(5), TaskItem(RECURSIVE, 3, false)); + // When EndDialogFunc is processed, the window is already dismissed, hence no + // "end" entry. + EXPECT_EQ(order.Get(6), TaskItem(ENDDIALOG, 4, true)); + EXPECT_EQ(order.Get(7), TaskItem(QUITMESSAGELOOP, 5, true)); + EXPECT_EQ(order.Get(8), TaskItem(QUITMESSAGELOOP, 5, false)); + EXPECT_EQ(order.Get(9), TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order.Get(10), TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order.Get(11), TaskItem(RECURSIVE, 3, true)); + EXPECT_EQ(order.Get(12), TaskItem(RECURSIVE, 3, false)); + EXPECT_EQ(order.Get(13), TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order.Get(14), TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order.Get(15), TaskItem(RECURSIVE, 3, true)); + EXPECT_EQ(order.Get(16), TaskItem(RECURSIVE, 3, false)); +} + +// A side effect of this test is the generation a beep. Sorry. This test also +// needs to process windows messages on the current thread. +void RunTest_RecursiveSupport2(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + Thread worker("RecursiveSupport2_worker"); + Thread::Options options; + options.message_loop_type = message_loop_type; + ASSERT_EQ(true, worker.StartWithOptions(options)); + TaskList order; + win::ScopedHandle event(CreateEvent(NULL, FALSE, FALSE, NULL)); + worker.message_loop()->PostTask(FROM_HERE, + Bind(&RecursiveFuncWin, + MessageLoop::current(), + event.Get(), + false, + &order, + true)); + // Let the other thread execute. + WaitForSingleObject(event, INFINITE); + MessageLoop::current()->Run(); + + ASSERT_EQ(order.Size(), 18); + EXPECT_EQ(order.Get(0), TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order.Get(1), TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order.Get(2), TaskItem(MESSAGEBOX, 2, true)); + // Note that this executes in the MessageBox modal loop. + EXPECT_EQ(order.Get(3), TaskItem(RECURSIVE, 3, true)); + EXPECT_EQ(order.Get(4), TaskItem(RECURSIVE, 3, false)); + EXPECT_EQ(order.Get(5), TaskItem(ENDDIALOG, 4, true)); + EXPECT_EQ(order.Get(6), TaskItem(ENDDIALOG, 4, false)); + EXPECT_EQ(order.Get(7), TaskItem(MESSAGEBOX, 2, false)); + /* The order can subtly change here. The reason is that when RecursiveFunc(1) + is called in the main thread, if it is faster than getting to the + PostTask(FROM_HERE, Bind(&QuitFunc) execution, the order of task + execution can change. We don't care anyway that the order isn't correct. + EXPECT_EQ(order.Get(8), TaskItem(QUITMESSAGELOOP, 5, true)); + EXPECT_EQ(order.Get(9), TaskItem(QUITMESSAGELOOP, 5, false)); + EXPECT_EQ(order.Get(10), TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order.Get(11), TaskItem(RECURSIVE, 1, false)); + */ + EXPECT_EQ(order.Get(12), TaskItem(RECURSIVE, 3, true)); + EXPECT_EQ(order.Get(13), TaskItem(RECURSIVE, 3, false)); + EXPECT_EQ(order.Get(14), TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order.Get(15), TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order.Get(16), TaskItem(RECURSIVE, 3, true)); + EXPECT_EQ(order.Get(17), TaskItem(RECURSIVE, 3, false)); +} + +#endif // defined(OS_WIN) + +void FuncThatPumps(TaskList* order, int cookie) { + order->RecordStart(PUMPS, cookie); + { + MessageLoop::ScopedNestableTaskAllower allow(MessageLoop::current()); + RunLoop().RunUntilIdle(); + } + order->RecordEnd(PUMPS, cookie); +} + +void FuncThatRuns(TaskList* order, int cookie, RunLoop* run_loop) { + order->RecordStart(RUNS, cookie); + { + MessageLoop::ScopedNestableTaskAllower allow(MessageLoop::current()); + run_loop->Run(); + } + order->RecordEnd(RUNS, cookie); +} + +void FuncThatQuitsNow() { + MessageLoop::current()->QuitNow(); +} + +// Tests that non nestable tasks run in FIFO if there are no nested loops. +void RunTest_NonNestableWithNoNesting( + MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + TaskList order; + + MessageLoop::current()->PostNonNestableTask( + FROM_HERE, + Bind(&OrderedFunc, &order, 1)); + MessageLoop::current()->PostTask(FROM_HERE, + Bind(&OrderedFunc, &order, 2)); + MessageLoop::current()->PostTask(FROM_HERE, + Bind(&QuitFunc, &order, 3)); + MessageLoop::current()->Run(); + + // FIFO order. + ASSERT_EQ(6U, order.Size()); + EXPECT_EQ(order.Get(0), TaskItem(ORDERED, 1, true)); + EXPECT_EQ(order.Get(1), TaskItem(ORDERED, 1, false)); + EXPECT_EQ(order.Get(2), TaskItem(ORDERED, 2, true)); + EXPECT_EQ(order.Get(3), TaskItem(ORDERED, 2, false)); + EXPECT_EQ(order.Get(4), TaskItem(QUITMESSAGELOOP, 3, true)); + EXPECT_EQ(order.Get(5), TaskItem(QUITMESSAGELOOP, 3, false)); +} + +// Tests that non nestable tasks don't run when there's code in the call stack. +void RunTest_NonNestableInNestedLoop(MessageLoop::Type message_loop_type, + bool use_delayed) { + MessageLoop loop(message_loop_type); + + TaskList order; + + MessageLoop::current()->PostTask( + FROM_HERE, + Bind(&FuncThatPumps, &order, 1)); + if (use_delayed) { + MessageLoop::current()->PostNonNestableDelayedTask( + FROM_HERE, + Bind(&OrderedFunc, &order, 2), + TimeDelta::FromMilliseconds(1)); + } else { + MessageLoop::current()->PostNonNestableTask( + FROM_HERE, + Bind(&OrderedFunc, &order, 2)); + } + MessageLoop::current()->PostTask(FROM_HERE, + Bind(&OrderedFunc, &order, 3)); + MessageLoop::current()->PostTask( + FROM_HERE, + Bind(&SleepFunc, &order, 4, TimeDelta::FromMilliseconds(50))); + MessageLoop::current()->PostTask(FROM_HERE, + Bind(&OrderedFunc, &order, 5)); + if (use_delayed) { + MessageLoop::current()->PostNonNestableDelayedTask( + FROM_HERE, + Bind(&QuitFunc, &order, 6), + TimeDelta::FromMilliseconds(2)); + } else { + MessageLoop::current()->PostNonNestableTask( + FROM_HERE, + Bind(&QuitFunc, &order, 6)); + } + + MessageLoop::current()->Run(); + + // FIFO order. + ASSERT_EQ(12U, order.Size()); + EXPECT_EQ(order.Get(0), TaskItem(PUMPS, 1, true)); + EXPECT_EQ(order.Get(1), TaskItem(ORDERED, 3, true)); + EXPECT_EQ(order.Get(2), TaskItem(ORDERED, 3, false)); + EXPECT_EQ(order.Get(3), TaskItem(SLEEP, 4, true)); + EXPECT_EQ(order.Get(4), TaskItem(SLEEP, 4, false)); + EXPECT_EQ(order.Get(5), TaskItem(ORDERED, 5, true)); + EXPECT_EQ(order.Get(6), TaskItem(ORDERED, 5, false)); + EXPECT_EQ(order.Get(7), TaskItem(PUMPS, 1, false)); + EXPECT_EQ(order.Get(8), TaskItem(ORDERED, 2, true)); + EXPECT_EQ(order.Get(9), TaskItem(ORDERED, 2, false)); + EXPECT_EQ(order.Get(10), TaskItem(QUITMESSAGELOOP, 6, true)); + EXPECT_EQ(order.Get(11), TaskItem(QUITMESSAGELOOP, 6, false)); +} + +// Tests RunLoopQuit only quits the corresponding MessageLoop::Run. +void RunTest_QuitNow(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + TaskList order; + + RunLoop run_loop; + + MessageLoop::current()->PostTask(FROM_HERE, + Bind(&FuncThatRuns, &order, 1, Unretained(&run_loop))); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 2)); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&FuncThatQuitsNow)); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 3)); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&FuncThatQuitsNow)); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 4)); // never runs + + MessageLoop::current()->Run(); + + ASSERT_EQ(6U, order.Size()); + int task_index = 0; + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, false)); + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 3, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 3, false)); + EXPECT_EQ(static_cast(task_index), order.Size()); +} + +// Tests RunLoopQuit works before RunWithID. +void RunTest_RunLoopQuitOrderBefore(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + TaskList order; + + RunLoop run_loop; + + run_loop.Quit(); + + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 1)); // never runs + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&FuncThatQuitsNow)); // never runs + + run_loop.Run(); + + ASSERT_EQ(0U, order.Size()); +} + +// Tests RunLoopQuit works during RunWithID. +void RunTest_RunLoopQuitOrderDuring(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + TaskList order; + + RunLoop run_loop; + + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 1)); + MessageLoop::current()->PostTask( + FROM_HERE, run_loop.QuitClosure()); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 2)); // never runs + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&FuncThatQuitsNow)); // never runs + + run_loop.Run(); + + ASSERT_EQ(2U, order.Size()); + int task_index = 0; + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 1, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 1, false)); + EXPECT_EQ(static_cast(task_index), order.Size()); +} + +// Tests RunLoopQuit works after RunWithID. +void RunTest_RunLoopQuitOrderAfter(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + TaskList order; + + RunLoop run_loop; + + MessageLoop::current()->PostTask(FROM_HERE, + Bind(&FuncThatRuns, &order, 1, Unretained(&run_loop))); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 2)); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&FuncThatQuitsNow)); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 3)); + MessageLoop::current()->PostTask( + FROM_HERE, run_loop.QuitClosure()); // has no affect + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 4)); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&FuncThatQuitsNow)); + + RunLoop outer_run_loop; + outer_run_loop.Run(); + + ASSERT_EQ(8U, order.Size()); + int task_index = 0; + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, false)); + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 3, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 3, false)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 4, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 4, false)); + EXPECT_EQ(static_cast(task_index), order.Size()); +} + +// Tests RunLoopQuit only quits the corresponding MessageLoop::Run. +void RunTest_RunLoopQuitTop(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + TaskList order; + + RunLoop outer_run_loop; + RunLoop nested_run_loop; + + MessageLoop::current()->PostTask(FROM_HERE, + Bind(&FuncThatRuns, &order, 1, Unretained(&nested_run_loop))); + MessageLoop::current()->PostTask( + FROM_HERE, outer_run_loop.QuitClosure()); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 2)); + MessageLoop::current()->PostTask( + FROM_HERE, nested_run_loop.QuitClosure()); + + outer_run_loop.Run(); + + ASSERT_EQ(4U, order.Size()); + int task_index = 0; + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, false)); + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false)); + EXPECT_EQ(static_cast(task_index), order.Size()); +} + +// Tests RunLoopQuit only quits the corresponding MessageLoop::Run. +void RunTest_RunLoopQuitNested(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + TaskList order; + + RunLoop outer_run_loop; + RunLoop nested_run_loop; + + MessageLoop::current()->PostTask(FROM_HERE, + Bind(&FuncThatRuns, &order, 1, Unretained(&nested_run_loop))); + MessageLoop::current()->PostTask( + FROM_HERE, nested_run_loop.QuitClosure()); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 2)); + MessageLoop::current()->PostTask( + FROM_HERE, outer_run_loop.QuitClosure()); + + outer_run_loop.Run(); + + ASSERT_EQ(4U, order.Size()); + int task_index = 0; + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, false)); + EXPECT_EQ(static_cast(task_index), order.Size()); +} + +// Tests RunLoopQuit only quits the corresponding MessageLoop::Run. +void RunTest_RunLoopQuitBogus(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + TaskList order; + + RunLoop outer_run_loop; + RunLoop nested_run_loop; + RunLoop bogus_run_loop; + + MessageLoop::current()->PostTask(FROM_HERE, + Bind(&FuncThatRuns, &order, 1, Unretained(&nested_run_loop))); + MessageLoop::current()->PostTask( + FROM_HERE, bogus_run_loop.QuitClosure()); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 2)); + MessageLoop::current()->PostTask( + FROM_HERE, outer_run_loop.QuitClosure()); + MessageLoop::current()->PostTask( + FROM_HERE, nested_run_loop.QuitClosure()); + + outer_run_loop.Run(); + + ASSERT_EQ(4U, order.Size()); + int task_index = 0; + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, false)); + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false)); + EXPECT_EQ(static_cast(task_index), order.Size()); +} + +// Tests RunLoopQuit only quits the corresponding MessageLoop::Run. +void RunTest_RunLoopQuitDeep(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + TaskList order; + + RunLoop outer_run_loop; + RunLoop nested_loop1; + RunLoop nested_loop2; + RunLoop nested_loop3; + RunLoop nested_loop4; + + MessageLoop::current()->PostTask(FROM_HERE, + Bind(&FuncThatRuns, &order, 1, Unretained(&nested_loop1))); + MessageLoop::current()->PostTask(FROM_HERE, + Bind(&FuncThatRuns, &order, 2, Unretained(&nested_loop2))); + MessageLoop::current()->PostTask(FROM_HERE, + Bind(&FuncThatRuns, &order, 3, Unretained(&nested_loop3))); + MessageLoop::current()->PostTask(FROM_HERE, + Bind(&FuncThatRuns, &order, 4, Unretained(&nested_loop4))); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 5)); + MessageLoop::current()->PostTask( + FROM_HERE, outer_run_loop.QuitClosure()); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 6)); + MessageLoop::current()->PostTask( + FROM_HERE, nested_loop1.QuitClosure()); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 7)); + MessageLoop::current()->PostTask( + FROM_HERE, nested_loop2.QuitClosure()); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 8)); + MessageLoop::current()->PostTask( + FROM_HERE, nested_loop3.QuitClosure()); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 9)); + MessageLoop::current()->PostTask( + FROM_HERE, nested_loop4.QuitClosure()); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 10)); + + outer_run_loop.Run(); + + ASSERT_EQ(18U, order.Size()); + int task_index = 0; + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 2, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 3, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 4, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 5, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 5, false)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 6, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 6, false)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 7, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 7, false)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 8, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 8, false)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 9, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 9, false)); + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 4, false)); + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 3, false)); + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 2, false)); + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false)); + EXPECT_EQ(static_cast(task_index), order.Size()); +} + +void PostNTasksThenQuit(int posts_remaining) { + if (posts_remaining > 1) { + MessageLoop::current()->PostTask( + FROM_HERE, + Bind(&PostNTasksThenQuit, posts_remaining - 1)); + } else { + MessageLoop::current()->QuitWhenIdle(); + } +} + +void RunTest_RecursivePosts(MessageLoop::Type message_loop_type, + int num_times) { + MessageLoop loop(message_loop_type); + loop.PostTask(FROM_HERE, Bind(&PostNTasksThenQuit, num_times)); + loop.Run(); +} + +#if defined(OS_WIN) + +class DispatcherImpl : public MessageLoopForUI::Dispatcher { + public: + DispatcherImpl() : dispatch_count_(0) {} + + virtual bool Dispatch(const NativeEvent& msg) OVERRIDE { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + // Do not count WM_TIMER since it is not what we post and it will cause + // flakiness. + if (msg.message != WM_TIMER) + ++dispatch_count_; + // We treat WM_LBUTTONUP as the last message. + return msg.message != WM_LBUTTONUP; + } + + int dispatch_count_; +}; + +void MouseDownUp() { + PostMessage(NULL, WM_LBUTTONDOWN, 0, 0); + PostMessage(NULL, WM_LBUTTONUP, 'A', 0); +} + +void RunTest_Dispatcher(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + Bind(&MouseDownUp), + TimeDelta::FromMilliseconds(100)); + DispatcherImpl dispatcher; + RunLoop run_loop(&dispatcher); + run_loop.Run(); + ASSERT_EQ(2, dispatcher.dispatch_count_); +} + +LRESULT CALLBACK MsgFilterProc(int code, WPARAM wparam, LPARAM lparam) { + if (code == MessagePumpForUI::kMessageFilterCode) { + MSG* msg = reinterpret_cast(lparam); + if (msg->message == WM_LBUTTONDOWN) + return TRUE; + } + return FALSE; +} + +void RunTest_DispatcherWithMessageHook(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + Bind(&MouseDownUp), + TimeDelta::FromMilliseconds(100)); + HHOOK msg_hook = SetWindowsHookEx(WH_MSGFILTER, + MsgFilterProc, + NULL, + GetCurrentThreadId()); + DispatcherImpl dispatcher; + RunLoop run_loop(&dispatcher); + run_loop.Run(); + ASSERT_EQ(1, dispatcher.dispatch_count_); + UnhookWindowsHookEx(msg_hook); +} + +class TestIOHandler : public MessageLoopForIO::IOHandler { + public: + TestIOHandler(const wchar_t* name, HANDLE signal, bool wait); + + virtual void OnIOCompleted(MessageLoopForIO::IOContext* context, + DWORD bytes_transfered, DWORD error); + + void Init(); + void WaitForIO(); + OVERLAPPED* context() { return &context_.overlapped; } + DWORD size() { return sizeof(buffer_); } + + private: + char buffer_[48]; + MessageLoopForIO::IOContext context_; + HANDLE signal_; + win::ScopedHandle file_; + bool wait_; +}; + +TestIOHandler::TestIOHandler(const wchar_t* name, HANDLE signal, bool wait) + : signal_(signal), wait_(wait) { + memset(buffer_, 0, sizeof(buffer_)); + memset(&context_, 0, sizeof(context_)); + context_.handler = this; + + file_.Set(CreateFile(name, GENERIC_READ, 0, NULL, OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, NULL)); + EXPECT_TRUE(file_.IsValid()); +} + +void TestIOHandler::Init() { + MessageLoopForIO::current()->RegisterIOHandler(file_, this); + + DWORD read; + EXPECT_FALSE(ReadFile(file_, buffer_, size(), &read, context())); + EXPECT_EQ(ERROR_IO_PENDING, GetLastError()); + if (wait_) + WaitForIO(); +} + +void TestIOHandler::OnIOCompleted(MessageLoopForIO::IOContext* context, + DWORD bytes_transfered, DWORD error) { + ASSERT_TRUE(context == &context_); + ASSERT_TRUE(SetEvent(signal_)); +} + +void TestIOHandler::WaitForIO() { + EXPECT_TRUE(MessageLoopForIO::current()->WaitForIOCompletion(300, this)); + EXPECT_TRUE(MessageLoopForIO::current()->WaitForIOCompletion(400, this)); +} + +void RunTest_IOHandler() { + win::ScopedHandle callback_called(CreateEvent(NULL, TRUE, FALSE, NULL)); + ASSERT_TRUE(callback_called.IsValid()); + + const wchar_t* kPipeName = L"\\\\.\\pipe\\iohandler_pipe"; + win::ScopedHandle server( + CreateNamedPipe(kPipeName, PIPE_ACCESS_OUTBOUND, 0, 1, 0, 0, 0, NULL)); + ASSERT_TRUE(server.IsValid()); + + Thread thread("IOHandler test"); + Thread::Options options; + options.message_loop_type = MessageLoop::TYPE_IO; + ASSERT_TRUE(thread.StartWithOptions(options)); + + MessageLoop* thread_loop = thread.message_loop(); + ASSERT_TRUE(NULL != thread_loop); + + TestIOHandler handler(kPipeName, callback_called, false); + thread_loop->PostTask(FROM_HERE, Bind(&TestIOHandler::Init, + Unretained(&handler))); + // Make sure the thread runs and sleeps for lack of work. + PlatformThread::Sleep(TimeDelta::FromMilliseconds(100)); + + const char buffer[] = "Hello there!"; + DWORD written; + EXPECT_TRUE(WriteFile(server, buffer, sizeof(buffer), &written, NULL)); + + DWORD result = WaitForSingleObject(callback_called, 1000); + EXPECT_EQ(WAIT_OBJECT_0, result); + + thread.Stop(); +} + +void RunTest_WaitForIO() { + win::ScopedHandle callback1_called( + CreateEvent(NULL, TRUE, FALSE, NULL)); + win::ScopedHandle callback2_called( + CreateEvent(NULL, TRUE, FALSE, NULL)); + ASSERT_TRUE(callback1_called.IsValid()); + ASSERT_TRUE(callback2_called.IsValid()); + + const wchar_t* kPipeName1 = L"\\\\.\\pipe\\iohandler_pipe1"; + const wchar_t* kPipeName2 = L"\\\\.\\pipe\\iohandler_pipe2"; + win::ScopedHandle server1( + CreateNamedPipe(kPipeName1, PIPE_ACCESS_OUTBOUND, 0, 1, 0, 0, 0, NULL)); + win::ScopedHandle server2( + CreateNamedPipe(kPipeName2, PIPE_ACCESS_OUTBOUND, 0, 1, 0, 0, 0, NULL)); + ASSERT_TRUE(server1.IsValid()); + ASSERT_TRUE(server2.IsValid()); + + Thread thread("IOHandler test"); + Thread::Options options; + options.message_loop_type = MessageLoop::TYPE_IO; + ASSERT_TRUE(thread.StartWithOptions(options)); + + MessageLoop* thread_loop = thread.message_loop(); + ASSERT_TRUE(NULL != thread_loop); + + TestIOHandler handler1(kPipeName1, callback1_called, false); + TestIOHandler handler2(kPipeName2, callback2_called, true); + thread_loop->PostTask(FROM_HERE, Bind(&TestIOHandler::Init, + Unretained(&handler1))); + // TODO(ajwong): Do we really need such long Sleeps in ths function? + // Make sure the thread runs and sleeps for lack of work. + TimeDelta delay = TimeDelta::FromMilliseconds(100); + PlatformThread::Sleep(delay); + thread_loop->PostTask(FROM_HERE, Bind(&TestIOHandler::Init, + Unretained(&handler2))); + PlatformThread::Sleep(delay); + + // At this time handler1 is waiting to be called, and the thread is waiting + // on the Init method of handler2, filtering only handler2 callbacks. + + const char buffer[] = "Hello there!"; + DWORD written; + EXPECT_TRUE(WriteFile(server1, buffer, sizeof(buffer), &written, NULL)); + PlatformThread::Sleep(2 * delay); + EXPECT_EQ(WAIT_TIMEOUT, WaitForSingleObject(callback1_called, 0)) << + "handler1 has not been called"; + + EXPECT_TRUE(WriteFile(server2, buffer, sizeof(buffer), &written, NULL)); + + HANDLE objects[2] = { callback1_called.Get(), callback2_called.Get() }; + DWORD result = WaitForMultipleObjects(2, objects, TRUE, 1000); + EXPECT_EQ(WAIT_OBJECT_0, result); + + thread.Stop(); +} + +#endif // defined(OS_WIN) + +} // namespace + +//----------------------------------------------------------------------------- +// Each test is run against each type of MessageLoop. That way we are sure +// that message loops work properly in all configurations. Of course, in some +// cases, a unit test may only be for a particular type of loop. + +TEST(MessageLoopTest, PostTask) { + RunTest_PostTask(MessageLoop::TYPE_DEFAULT); + RunTest_PostTask(MessageLoop::TYPE_UI); + RunTest_PostTask(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, PostTask_SEH) { + RunTest_PostTask_SEH(MessageLoop::TYPE_DEFAULT); + RunTest_PostTask_SEH(MessageLoop::TYPE_UI); + RunTest_PostTask_SEH(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, PostDelayedTask_Basic) { + RunTest_PostDelayedTask_Basic(MessageLoop::TYPE_DEFAULT); + RunTest_PostDelayedTask_Basic(MessageLoop::TYPE_UI); + RunTest_PostDelayedTask_Basic(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, PostDelayedTask_InDelayOrder) { + RunTest_PostDelayedTask_InDelayOrder(MessageLoop::TYPE_DEFAULT); + RunTest_PostDelayedTask_InDelayOrder(MessageLoop::TYPE_UI); + RunTest_PostDelayedTask_InDelayOrder(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, PostDelayedTask_InPostOrder) { + RunTest_PostDelayedTask_InPostOrder(MessageLoop::TYPE_DEFAULT); + RunTest_PostDelayedTask_InPostOrder(MessageLoop::TYPE_UI); + RunTest_PostDelayedTask_InPostOrder(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, PostDelayedTask_InPostOrder_2) { + RunTest_PostDelayedTask_InPostOrder_2(MessageLoop::TYPE_DEFAULT); + RunTest_PostDelayedTask_InPostOrder_2(MessageLoop::TYPE_UI); + RunTest_PostDelayedTask_InPostOrder_2(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, PostDelayedTask_InPostOrder_3) { + RunTest_PostDelayedTask_InPostOrder_3(MessageLoop::TYPE_DEFAULT); + RunTest_PostDelayedTask_InPostOrder_3(MessageLoop::TYPE_UI); + RunTest_PostDelayedTask_InPostOrder_3(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, PostDelayedTask_SharedTimer) { + RunTest_PostDelayedTask_SharedTimer(MessageLoop::TYPE_DEFAULT); + RunTest_PostDelayedTask_SharedTimer(MessageLoop::TYPE_UI); + RunTest_PostDelayedTask_SharedTimer(MessageLoop::TYPE_IO); +} + +#if defined(OS_WIN) +TEST(MessageLoopTest, PostDelayedTask_SharedTimer_SubPump) { + RunTest_PostDelayedTask_SharedTimer_SubPump(); +} +#endif + +// TODO(darin): MessageLoop does not support deleting all tasks in the +// destructor. +// Fails, http://crbug.com/50272. +TEST(MessageLoopTest, DISABLED_EnsureDeletion) { + RunTest_EnsureDeletion(MessageLoop::TYPE_DEFAULT); + RunTest_EnsureDeletion(MessageLoop::TYPE_UI); + RunTest_EnsureDeletion(MessageLoop::TYPE_IO); +} + +// TODO(darin): MessageLoop does not support deleting all tasks in the +// destructor. +// Fails, http://crbug.com/50272. +TEST(MessageLoopTest, DISABLED_EnsureDeletion_Chain) { + RunTest_EnsureDeletion_Chain(MessageLoop::TYPE_DEFAULT); + RunTest_EnsureDeletion_Chain(MessageLoop::TYPE_UI); + RunTest_EnsureDeletion_Chain(MessageLoop::TYPE_IO); +} + +#if defined(OS_WIN) +TEST(MessageLoopTest, Crasher) { + RunTest_Crasher(MessageLoop::TYPE_DEFAULT); + RunTest_Crasher(MessageLoop::TYPE_UI); + RunTest_Crasher(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, CrasherNasty) { + RunTest_CrasherNasty(MessageLoop::TYPE_DEFAULT); + RunTest_CrasherNasty(MessageLoop::TYPE_UI); + RunTest_CrasherNasty(MessageLoop::TYPE_IO); +} +#endif // defined(OS_WIN) + +TEST(MessageLoopTest, Nesting) { + RunTest_Nesting(MessageLoop::TYPE_DEFAULT); + RunTest_Nesting(MessageLoop::TYPE_UI); + RunTest_Nesting(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, RecursiveDenial1) { + RunTest_RecursiveDenial1(MessageLoop::TYPE_DEFAULT); + RunTest_RecursiveDenial1(MessageLoop::TYPE_UI); + RunTest_RecursiveDenial1(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, RecursiveDenial3) { + RunTest_RecursiveDenial3(MessageLoop::TYPE_DEFAULT); + RunTest_RecursiveDenial3(MessageLoop::TYPE_UI); + RunTest_RecursiveDenial3(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, RecursiveSupport1) { + RunTest_RecursiveSupport1(MessageLoop::TYPE_DEFAULT); + RunTest_RecursiveSupport1(MessageLoop::TYPE_UI); + RunTest_RecursiveSupport1(MessageLoop::TYPE_IO); +} + +#if defined(OS_WIN) +// This test occasionally hangs http://crbug.com/44567 +TEST(MessageLoopTest, DISABLED_RecursiveDenial2) { + RunTest_RecursiveDenial2(MessageLoop::TYPE_DEFAULT); + RunTest_RecursiveDenial2(MessageLoop::TYPE_UI); + RunTest_RecursiveDenial2(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, RecursiveSupport2) { + // This test requires a UI loop + RunTest_RecursiveSupport2(MessageLoop::TYPE_UI); +} +#endif // defined(OS_WIN) + +TEST(MessageLoopTest, NonNestableWithNoNesting) { + RunTest_NonNestableWithNoNesting(MessageLoop::TYPE_DEFAULT); + RunTest_NonNestableWithNoNesting(MessageLoop::TYPE_UI); + RunTest_NonNestableWithNoNesting(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, NonNestableInNestedLoop) { + RunTest_NonNestableInNestedLoop(MessageLoop::TYPE_DEFAULT, false); + RunTest_NonNestableInNestedLoop(MessageLoop::TYPE_UI, false); + RunTest_NonNestableInNestedLoop(MessageLoop::TYPE_IO, false); +} + +TEST(MessageLoopTest, NonNestableDelayedInNestedLoop) { + RunTest_NonNestableInNestedLoop(MessageLoop::TYPE_DEFAULT, true); + RunTest_NonNestableInNestedLoop(MessageLoop::TYPE_UI, true); + RunTest_NonNestableInNestedLoop(MessageLoop::TYPE_IO, true); +} + +TEST(MessageLoopTest, QuitNow) { + RunTest_QuitNow(MessageLoop::TYPE_DEFAULT); + RunTest_QuitNow(MessageLoop::TYPE_UI); + RunTest_QuitNow(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, RunLoopQuitTop) { + RunTest_RunLoopQuitTop(MessageLoop::TYPE_DEFAULT); + RunTest_RunLoopQuitTop(MessageLoop::TYPE_UI); + RunTest_RunLoopQuitTop(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, RunLoopQuitNested) { + RunTest_RunLoopQuitNested(MessageLoop::TYPE_DEFAULT); + RunTest_RunLoopQuitNested(MessageLoop::TYPE_UI); + RunTest_RunLoopQuitNested(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, RunLoopQuitBogus) { + RunTest_RunLoopQuitBogus(MessageLoop::TYPE_DEFAULT); + RunTest_RunLoopQuitBogus(MessageLoop::TYPE_UI); + RunTest_RunLoopQuitBogus(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, RunLoopQuitDeep) { + RunTest_RunLoopQuitDeep(MessageLoop::TYPE_DEFAULT); + RunTest_RunLoopQuitDeep(MessageLoop::TYPE_UI); + RunTest_RunLoopQuitDeep(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, RunLoopQuitOrderBefore) { + RunTest_RunLoopQuitOrderBefore(MessageLoop::TYPE_DEFAULT); + RunTest_RunLoopQuitOrderBefore(MessageLoop::TYPE_UI); + RunTest_RunLoopQuitOrderBefore(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, RunLoopQuitOrderDuring) { + RunTest_RunLoopQuitOrderDuring(MessageLoop::TYPE_DEFAULT); + RunTest_RunLoopQuitOrderDuring(MessageLoop::TYPE_UI); + RunTest_RunLoopQuitOrderDuring(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, RunLoopQuitOrderAfter) { + RunTest_RunLoopQuitOrderAfter(MessageLoop::TYPE_DEFAULT); + RunTest_RunLoopQuitOrderAfter(MessageLoop::TYPE_UI); + RunTest_RunLoopQuitOrderAfter(MessageLoop::TYPE_IO); +} + +class DummyTaskObserver : public MessageLoop::TaskObserver { + public: + explicit DummyTaskObserver(int num_tasks) + : num_tasks_started_(0), + num_tasks_processed_(0), + num_tasks_(num_tasks) {} + + virtual ~DummyTaskObserver() {} + + virtual void WillProcessTask(const PendingTask& pending_task) OVERRIDE { + num_tasks_started_++; + EXPECT_TRUE(pending_task.time_posted != TimeTicks()); + EXPECT_LE(num_tasks_started_, num_tasks_); + EXPECT_EQ(num_tasks_started_, num_tasks_processed_ + 1); + } + + virtual void DidProcessTask(const PendingTask& pending_task) OVERRIDE { + num_tasks_processed_++; + EXPECT_TRUE(pending_task.time_posted != TimeTicks()); + EXPECT_LE(num_tasks_started_, num_tasks_); + EXPECT_EQ(num_tasks_started_, num_tasks_processed_); + } + + int num_tasks_started() const { return num_tasks_started_; } + int num_tasks_processed() const { return num_tasks_processed_; } + + private: + int num_tasks_started_; + int num_tasks_processed_; + const int num_tasks_; + + DISALLOW_COPY_AND_ASSIGN(DummyTaskObserver); +}; + +TEST(MessageLoopTest, TaskObserver) { + const int kNumPosts = 6; + DummyTaskObserver observer(kNumPosts); + + MessageLoop loop; + loop.AddTaskObserver(&observer); + loop.PostTask(FROM_HERE, Bind(&PostNTasksThenQuit, kNumPosts)); + loop.Run(); + loop.RemoveTaskObserver(&observer); + + EXPECT_EQ(kNumPosts, observer.num_tasks_started()); + EXPECT_EQ(kNumPosts, observer.num_tasks_processed()); +} + +#if defined(OS_WIN) +TEST(MessageLoopTest, Dispatcher) { + // This test requires a UI loop + RunTest_Dispatcher(MessageLoop::TYPE_UI); +} + +TEST(MessageLoopTest, DispatcherWithMessageHook) { + // This test requires a UI loop + RunTest_DispatcherWithMessageHook(MessageLoop::TYPE_UI); +} + +TEST(MessageLoopTest, IOHandler) { + RunTest_IOHandler(); +} + +TEST(MessageLoopTest, WaitForIO) { + RunTest_WaitForIO(); +} + +TEST(MessageLoopTest, HighResolutionTimer) { + MessageLoop loop; + + const TimeDelta kFastTimer = TimeDelta::FromMilliseconds(5); + const TimeDelta kSlowTimer = TimeDelta::FromMilliseconds(100); + + EXPECT_FALSE(loop.IsHighResolutionTimerEnabledForTesting()); + + // Post a fast task to enable the high resolution timers. + loop.PostDelayedTask(FROM_HERE, Bind(&PostNTasksThenQuit, 1), + kFastTimer); + loop.Run(); + EXPECT_TRUE(loop.IsHighResolutionTimerEnabledForTesting()); + + // Post a slow task and verify high resolution timers + // are still enabled. + loop.PostDelayedTask(FROM_HERE, Bind(&PostNTasksThenQuit, 1), + kSlowTimer); + loop.Run(); + EXPECT_TRUE(loop.IsHighResolutionTimerEnabledForTesting()); + + // Wait for a while so that high-resolution mode elapses. + PlatformThread::Sleep(TimeDelta::FromMilliseconds( + MessageLoop::kHighResolutionTimerModeLeaseTimeMs)); + + // Post a slow task to disable the high resolution timers. + loop.PostDelayedTask(FROM_HERE, Bind(&PostNTasksThenQuit, 1), + kSlowTimer); + loop.Run(); + EXPECT_FALSE(loop.IsHighResolutionTimerEnabledForTesting()); +} + +#endif // defined(OS_WIN) + +#if defined(OS_POSIX) && !defined(OS_NACL) + +namespace { + +class QuitDelegate : public MessageLoopForIO::Watcher { + public: + virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE { + MessageLoop::current()->QuitWhenIdle(); + } + virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE { + MessageLoop::current()->QuitWhenIdle(); + } +}; + +TEST(MessageLoopTest, FileDescriptorWatcherOutlivesMessageLoop) { + // Simulate a MessageLoop that dies before an FileDescriptorWatcher. + // This could happen when people use the Singleton pattern or atexit. + + // Create a file descriptor. Doesn't need to be readable or writable, + // as we don't need to actually get any notifications. + // pipe() is just the easiest way to do it. + int pipefds[2]; + int err = pipe(pipefds); + ASSERT_EQ(0, err); + int fd = pipefds[1]; + { + // Arrange for controller to live longer than message loop. + MessageLoopForIO::FileDescriptorWatcher controller; + { + MessageLoopForIO message_loop; + + QuitDelegate delegate; + message_loop.WatchFileDescriptor(fd, + true, MessageLoopForIO::WATCH_WRITE, &controller, &delegate); + // and don't run the message loop, just destroy it. + } + } + if (HANDLE_EINTR(close(pipefds[0])) < 0) + PLOG(ERROR) << "close"; + if (HANDLE_EINTR(close(pipefds[1])) < 0) + PLOG(ERROR) << "close"; +} + +TEST(MessageLoopTest, FileDescriptorWatcherDoubleStop) { + // Verify that it's ok to call StopWatchingFileDescriptor(). + // (Errors only showed up in valgrind.) + int pipefds[2]; + int err = pipe(pipefds); + ASSERT_EQ(0, err); + int fd = pipefds[1]; + { + // Arrange for message loop to live longer than controller. + MessageLoopForIO message_loop; + { + MessageLoopForIO::FileDescriptorWatcher controller; + + QuitDelegate delegate; + message_loop.WatchFileDescriptor(fd, + true, MessageLoopForIO::WATCH_WRITE, &controller, &delegate); + controller.StopWatchingFileDescriptor(); + } + } + if (HANDLE_EINTR(close(pipefds[0])) < 0) + PLOG(ERROR) << "close"; + if (HANDLE_EINTR(close(pipefds[1])) < 0) + PLOG(ERROR) << "close"; +} + +} // namespace + +#endif // defined(OS_POSIX) && !defined(OS_NACL) + +namespace { +// Inject a test point for recording the destructor calls for Closure objects +// send to MessageLoop::PostTask(). It is awkward usage since we are trying to +// hook the actual destruction, which is not a common operation. +class DestructionObserverProbe : + public RefCounted { + public: + DestructionObserverProbe(bool* task_destroyed, + bool* destruction_observer_called) + : task_destroyed_(task_destroyed), + destruction_observer_called_(destruction_observer_called) { + } + virtual void Run() { + // This task should never run. + ADD_FAILURE(); + } + private: + friend class RefCounted; + + virtual ~DestructionObserverProbe() { + EXPECT_FALSE(*destruction_observer_called_); + *task_destroyed_ = true; + } + + bool* task_destroyed_; + bool* destruction_observer_called_; +}; + +class MLDestructionObserver : public MessageLoop::DestructionObserver { + public: + MLDestructionObserver(bool* task_destroyed, bool* destruction_observer_called) + : task_destroyed_(task_destroyed), + destruction_observer_called_(destruction_observer_called), + task_destroyed_before_message_loop_(false) { + } + virtual void WillDestroyCurrentMessageLoop() OVERRIDE { + task_destroyed_before_message_loop_ = *task_destroyed_; + *destruction_observer_called_ = true; + } + bool task_destroyed_before_message_loop() const { + return task_destroyed_before_message_loop_; + } + private: + bool* task_destroyed_; + bool* destruction_observer_called_; + bool task_destroyed_before_message_loop_; +}; + +} // namespace + +TEST(MessageLoopTest, DestructionObserverTest) { + // Verify that the destruction observer gets called at the very end (after + // all the pending tasks have been destroyed). + MessageLoop* loop = new MessageLoop; + const TimeDelta kDelay = TimeDelta::FromMilliseconds(100); + + bool task_destroyed = false; + bool destruction_observer_called = false; + + MLDestructionObserver observer(&task_destroyed, &destruction_observer_called); + loop->AddDestructionObserver(&observer); + loop->PostDelayedTask( + FROM_HERE, + Bind(&DestructionObserverProbe::Run, + new DestructionObserverProbe(&task_destroyed, + &destruction_observer_called)), + kDelay); + delete loop; + EXPECT_TRUE(observer.task_destroyed_before_message_loop()); + // The task should have been destroyed when we deleted the loop. + EXPECT_TRUE(task_destroyed); + EXPECT_TRUE(destruction_observer_called); +} + + +// Verify that MessageLoop sets ThreadMainTaskRunner::current() and it +// posts tasks on that message loop. +TEST(MessageLoopTest, ThreadMainTaskRunner) { + MessageLoop loop; + + scoped_refptr foo(new Foo()); + std::string a("a"); + ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, Bind( + &Foo::Test1ConstRef, foo.get(), a)); + + // Post quit task; + MessageLoop::current()->PostTask(FROM_HERE, Bind( + &MessageLoop::Quit, Unretained(MessageLoop::current()))); + + // Now kick things off + MessageLoop::current()->Run(); + + EXPECT_EQ(foo->test_count(), 1); + EXPECT_EQ(foo->result(), "a"); +} + +TEST(MessageLoopTest, IsType) { + MessageLoop loop(MessageLoop::TYPE_UI); + EXPECT_TRUE(loop.IsType(MessageLoop::TYPE_UI)); + EXPECT_FALSE(loop.IsType(MessageLoop::TYPE_IO)); + EXPECT_FALSE(loop.IsType(MessageLoop::TYPE_DEFAULT)); +} + +TEST(MessageLoopTest, RecursivePosts) { + // There was a bug in the MessagePumpGLib where posting tasks recursively + // caused the message loop to hang, due to the buffer of the internal pipe + // becoming full. Test all MessageLoop types to ensure this issue does not + // exist in other MessagePumps. + + // On Linux, the pipe buffer size is 64KiB by default. The bug caused one + // byte accumulated in the pipe per two posts, so we should repeat 128K + // times to reproduce the bug. + const int kNumTimes = 1 << 17; + RunTest_RecursivePosts(MessageLoop::TYPE_DEFAULT, kNumTimes); + RunTest_RecursivePosts(MessageLoop::TYPE_UI, kNumTimes); + RunTest_RecursivePosts(MessageLoop::TYPE_IO, kNumTimes); +} + +} // namespace base diff --git a/base/message_loop/message_pump.cc b/base/message_loop/message_pump.cc new file mode 100644 index 0000000000..7ffc2b170d --- /dev/null +++ b/base/message_loop/message_pump.cc @@ -0,0 +1,15 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/message_loop/message_pump.h" + +namespace base { + +MessagePump::MessagePump() { +} + +MessagePump::~MessagePump() { +} + +} // namespace base diff --git a/base/message_loop/message_pump.h b/base/message_loop/message_pump.h new file mode 100644 index 0000000000..0ebba3a3e6 --- /dev/null +++ b/base/message_loop/message_pump.h @@ -0,0 +1,126 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MESSAGE_LOOP_MESSAGE_PUMP_H_ +#define BASE_MESSAGE_LOOP_MESSAGE_PUMP_H_ + +#include "base/base_export.h" +#include "base/threading/non_thread_safe.h" + +namespace base { + +class TimeTicks; + +class BASE_EXPORT MessagePump : public NonThreadSafe { + public: + // Please see the comments above the Run method for an illustration of how + // these delegate methods are used. + class BASE_EXPORT Delegate { + public: + virtual ~Delegate() {} + + // Called from within Run in response to ScheduleWork or when the message + // pump would otherwise call DoDelayedWork. Returns true to indicate that + // work was done. DoDelayedWork will still be called if DoWork returns + // true, but DoIdleWork will not. + virtual bool DoWork() = 0; + + // Called from within Run in response to ScheduleDelayedWork or when the + // message pump would otherwise sleep waiting for more work. Returns true + // to indicate that delayed work was done. DoIdleWork will not be called + // if DoDelayedWork returns true. Upon return |next_delayed_work_time| + // indicates the time when DoDelayedWork should be called again. If + // |next_delayed_work_time| is null (per Time::is_null), then the queue of + // future delayed work (timer events) is currently empty, and no additional + // calls to this function need to be scheduled. + virtual bool DoDelayedWork(TimeTicks* next_delayed_work_time) = 0; + + // Called from within Run just before the message pump goes to sleep. + // Returns true to indicate that idle work was done. + virtual bool DoIdleWork() = 0; + }; + + MessagePump(); + virtual ~MessagePump(); + + // The Run method is called to enter the message pump's run loop. + // + // Within the method, the message pump is responsible for processing native + // messages as well as for giving cycles to the delegate periodically. The + // message pump should take care to mix delegate callbacks with native + // message processing so neither type of event starves the other of cycles. + // + // The anatomy of a typical run loop: + // + // for (;;) { + // bool did_work = DoInternalWork(); + // if (should_quit_) + // break; + // + // did_work |= delegate_->DoWork(); + // if (should_quit_) + // break; + // + // TimeTicks next_time; + // did_work |= delegate_->DoDelayedWork(&next_time); + // if (should_quit_) + // break; + // + // if (did_work) + // continue; + // + // did_work = delegate_->DoIdleWork(); + // if (should_quit_) + // break; + // + // if (did_work) + // continue; + // + // WaitForWork(); + // } + // + // Here, DoInternalWork is some private method of the message pump that is + // responsible for dispatching the next UI message or notifying the next IO + // completion (for example). WaitForWork is a private method that simply + // blocks until there is more work of any type to do. + // + // Notice that the run loop cycles between calling DoInternalWork, DoWork, + // and DoDelayedWork methods. This helps ensure that none of these work + // queues starve the others. This is important for message pumps that are + // used to drive animations, for example. + // + // Notice also that after each callout to foreign code, the run loop checks + // to see if it should quit. The Quit method is responsible for setting this + // flag. No further work is done once the quit flag is set. + // + // NOTE: Care must be taken to handle Run being called again from within any + // of the callouts to foreign code. Native message pumps may also need to + // deal with other native message pumps being run outside their control + // (e.g., the MessageBox API on Windows pumps UI messages!). To be specific, + // the callouts (DoWork and DoDelayedWork) MUST still be provided even in + // nested sub-loops that are "seemingly" outside the control of this message + // pump. DoWork in particular must never be starved for time slices unless + // it returns false (meaning it has run out of things to do). + // + virtual void Run(Delegate* delegate) = 0; + + // Quit immediately from the most recently entered run loop. This method may + // only be used on the thread that called Run. + virtual void Quit() = 0; + + // Schedule a DoWork callback to happen reasonably soon. Does nothing if a + // DoWork callback is already scheduled. This method may be called from any + // thread. Once this call is made, DoWork should not be "starved" at least + // until it returns a value of false. + virtual void ScheduleWork() = 0; + + // Schedule a DoDelayedWork callback to happen at the specified time, + // cancelling any pending DoDelayedWork callback. This method may only be + // used on the thread that called Run. + virtual void ScheduleDelayedWork(const TimeTicks& delayed_work_time) = 0; +}; + +} // namespace base + +#endif // BASE_MESSAGE_LOOP_MESSAGE_PUMP_H_ diff --git a/base/message_loop/message_pump_android.cc b/base/message_loop/message_pump_android.cc new file mode 100644 index 0000000000..f3f1c9bcb4 --- /dev/null +++ b/base/message_loop/message_pump_android.cc @@ -0,0 +1,133 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/message_loop/message_pump_android.h" + +#include + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/run_loop.h" +#include "base/time/time.h" +#include "jni/SystemMessageHandler_jni.h" + +using base::android::ScopedJavaLocalRef; + +// ---------------------------------------------------------------------------- +// Native JNI methods called by Java. +// ---------------------------------------------------------------------------- +// This method can not move to anonymous namespace as it has been declared as +// 'static' in system_message_handler_jni.h. +static void DoRunLoopOnce(JNIEnv* env, jobject obj, jint native_delegate) { + base::MessagePump::Delegate* delegate = + reinterpret_cast(native_delegate); + DCHECK(delegate); + // This is based on MessagePumpForUI::DoRunLoop() from desktop. + // Note however that our system queue is handled in the java side. + // In desktop we inspect and process a single system message and then + // we call DoWork() / DoDelayedWork(). + // On Android, the java message queue may contain messages for other handlers + // that will be processed before calling here again. + bool did_work = delegate->DoWork(); + + // This is the time when we need to do delayed work. + base::TimeTicks delayed_work_time; + did_work |= delegate->DoDelayedWork(&delayed_work_time); + + // Always call this if there is a delayed message waiting in the queue + // since is at most one delayed message in the Java message handler, and this + // function call may be the result of that message being handled. + if (!delayed_work_time.is_null()) { + Java_SystemMessageHandler_setDelayedTimer(env, obj, + (delayed_work_time - base::TimeTicks::Now()).InMillisecondsRoundedUp()); + } + + // This is a major difference between android and other platforms: since we + // can't inspect it and process just one single message, instead we'll yeld + // the callstack. + if (did_work) + return; + + delegate->DoIdleWork(); +} + +namespace base { + +MessagePumpForUI::MessagePumpForUI() + : run_loop_(NULL) { +} + +MessagePumpForUI::~MessagePumpForUI() { +} + +void MessagePumpForUI::Run(Delegate* delegate) { + NOTREACHED() << "UnitTests should rely on MessagePumpForUIStub in" + " test_stub_android.h"; +} + +void MessagePumpForUI::Start(Delegate* delegate) { + run_loop_ = new RunLoop(); + // Since the RunLoop was just created above, BeforeRun should be guaranteed to + // return true (it only returns false if the RunLoop has been Quit already). + if (!run_loop_->BeforeRun()) + NOTREACHED(); + + DCHECK(system_message_handler_obj_.is_null()); + + JNIEnv* env = base::android::AttachCurrentThread(); + DCHECK(env); + + system_message_handler_obj_.Reset( + Java_SystemMessageHandler_create(env, reinterpret_cast(delegate))); +} + +void MessagePumpForUI::Quit() { + if (!system_message_handler_obj_.is_null()) { + JNIEnv* env = base::android::AttachCurrentThread(); + DCHECK(env); + + Java_SystemMessageHandler_removeTimer(env, + system_message_handler_obj_.obj()); + system_message_handler_obj_.Reset(); + } + + if (run_loop_) { + run_loop_->AfterRun(); + delete run_loop_; + run_loop_ = NULL; + } +} + +void MessagePumpForUI::ScheduleWork() { + DCHECK(!system_message_handler_obj_.is_null()); + + JNIEnv* env = base::android::AttachCurrentThread(); + DCHECK(env); + + Java_SystemMessageHandler_setTimer(env, + system_message_handler_obj_.obj()); +} + +void MessagePumpForUI::ScheduleDelayedWork(const TimeTicks& delayed_work_time) { + DCHECK(!system_message_handler_obj_.is_null()); + + JNIEnv* env = base::android::AttachCurrentThread(); + DCHECK(env); + + jlong millis = + (delayed_work_time - TimeTicks::Now()).InMillisecondsRoundedUp(); + // Note that we're truncating to milliseconds as required by the java side, + // even though delayed_work_time is microseconds resolution. + Java_SystemMessageHandler_setDelayedTimer(env, + system_message_handler_obj_.obj(), millis); +} + +// static +bool MessagePumpForUI::RegisterBindings(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace base diff --git a/base/message_loop/message_pump_android.h b/base/message_loop/message_pump_android.h new file mode 100644 index 0000000000..8a07a0f6e4 --- /dev/null +++ b/base/message_loop/message_pump_android.h @@ -0,0 +1,45 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MESSAGE_LOOP_MESSAGE_PUMP_ANDROID_H_ +#define BASE_MESSAGE_LOOP_MESSAGE_PUMP_ANDROID_H_ + +#include + +#include "base/android/scoped_java_ref.h" +#include "base/base_export.h" +#include "base/compiler_specific.h" +#include "base/message_loop/message_pump.h" + +namespace base { + +class RunLoop; +class TimeTicks; + +// This class implements a MessagePump needed for TYPE_UI MessageLoops on +// OS_ANDROID platform. +class BASE_EXPORT MessagePumpForUI : public MessagePump { + public: + MessagePumpForUI(); + virtual ~MessagePumpForUI(); + + virtual void Run(Delegate* delegate) OVERRIDE; + virtual void Quit() OVERRIDE; + virtual void ScheduleWork() OVERRIDE; + virtual void ScheduleDelayedWork(const TimeTicks& delayed_work_time) OVERRIDE; + + virtual void Start(Delegate* delegate); + + static bool RegisterBindings(JNIEnv* env); + + private: + RunLoop* run_loop_; + base::android::ScopedJavaGlobalRef system_message_handler_obj_; + + DISALLOW_COPY_AND_ASSIGN(MessagePumpForUI); +}; + +} // namespace base + +#endif // BASE_MESSAGE_LOOP_MESSAGE_PUMP_ANDROID_H_ diff --git a/base/message_loop/message_pump_aurax11.cc b/base/message_loop/message_pump_aurax11.cc new file mode 100644 index 0000000000..1f91a0e359 --- /dev/null +++ b/base/message_loop/message_pump_aurax11.cc @@ -0,0 +1,307 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/message_loop/message_pump_aurax11.h" + +#include +#include +#include +#include + +#include "base/basictypes.h" +#include "base/message_loop/message_loop.h" + +namespace base { + +namespace { + +gboolean XSourcePrepare(GSource* source, gint* timeout_ms) { + if (XPending(MessagePumpAuraX11::GetDefaultXDisplay())) + *timeout_ms = 0; + else + *timeout_ms = -1; + return FALSE; +} + +gboolean XSourceCheck(GSource* source) { + return XPending(MessagePumpAuraX11::GetDefaultXDisplay()); +} + +gboolean XSourceDispatch(GSource* source, + GSourceFunc unused_func, + gpointer data) { + MessagePumpAuraX11* pump = static_cast(data); + return pump->DispatchXEvents(); +} + +GSourceFuncs XSourceFuncs = { + XSourcePrepare, + XSourceCheck, + XSourceDispatch, + NULL +}; + +// The connection is essentially a global that's accessed through a static +// method and destroyed whenever ~MessagePumpAuraX11() is called. We do this +// for historical reasons so user code can call +// MessagePumpForUI::GetDefaultXDisplay() where MessagePumpForUI is a typedef +// to whatever type in the current build. +// +// TODO(erg): This can be changed to something more sane like +// MessagePumpAuraX11::Current()->display() once MessagePumpGtk goes away. +Display* g_xdisplay = NULL; +int g_xinput_opcode = -1; + +bool InitializeXInput2Internal() { + Display* display = MessagePumpAuraX11::GetDefaultXDisplay(); + if (!display) + return false; + + int event, err; + + int xiopcode; + if (!XQueryExtension(display, "XInputExtension", &xiopcode, &event, &err)) { + DVLOG(1) << "X Input extension not available."; + return false; + } + g_xinput_opcode = xiopcode; + +#if defined(USE_XI2_MT) + // USE_XI2_MT also defines the required XI2 minor minimum version. + int major = 2, minor = USE_XI2_MT; +#else + int major = 2, minor = 0; +#endif + if (XIQueryVersion(display, &major, &minor) == BadRequest) { + DVLOG(1) << "XInput2 not supported in the server."; + return false; + } +#if defined(USE_XI2_MT) + if (major < 2 || (major == 2 && minor < USE_XI2_MT)) { + DVLOG(1) << "XI version on server is " << major << "." << minor << ". " + << "But 2." << USE_XI2_MT << " is required."; + return false; + } +#endif + + return true; +} + +Window FindEventTarget(const NativeEvent& xev) { + Window target = xev->xany.window; + if (xev->type == GenericEvent && + static_cast(xev->xcookie.data)->extension == g_xinput_opcode) { + target = static_cast(xev->xcookie.data)->event; + } + return target; +} + +bool InitializeXInput2() { + static bool xinput2_supported = InitializeXInput2Internal(); + return xinput2_supported; +} + +bool InitializeXkb() { + Display* display = MessagePumpAuraX11::GetDefaultXDisplay(); + if (!display) + return false; + + int opcode, event, error; + int major = XkbMajorVersion; + int minor = XkbMinorVersion; + if (!XkbQueryExtension(display, &opcode, &event, &error, &major, &minor)) { + DVLOG(1) << "Xkb extension not available."; + return false; + } + + // Ask the server not to send KeyRelease event when the user holds down a key. + // crbug.com/138092 + Bool supported_return; + if (!XkbSetDetectableAutoRepeat(display, True, &supported_return)) { + DVLOG(1) << "XKB not supported in the server."; + return false; + } + + return true; +} + +} // namespace + +MessagePumpAuraX11::MessagePumpAuraX11() : MessagePumpGlib(), + x_source_(NULL) { + InitializeXInput2(); + InitializeXkb(); + InitXSource(); + + // Can't put this in the initializer list because g_xdisplay may not exist + // until after InitXSource(). + x_root_window_ = DefaultRootWindow(g_xdisplay); +} + +MessagePumpAuraX11::~MessagePumpAuraX11() { + g_source_destroy(x_source_); + g_source_unref(x_source_); + XCloseDisplay(g_xdisplay); + g_xdisplay = NULL; +} + +// static +Display* MessagePumpAuraX11::GetDefaultXDisplay() { + if (!g_xdisplay) + g_xdisplay = XOpenDisplay(NULL); + return g_xdisplay; +} + +// static +bool MessagePumpAuraX11::HasXInput2() { + return InitializeXInput2(); +} + +// static +MessagePumpAuraX11* MessagePumpAuraX11::Current() { + MessageLoopForUI* loop = MessageLoopForUI::current(); + return static_cast(loop->pump_ui()); +} + +void MessagePumpAuraX11::AddDispatcherForWindow( + MessagePumpDispatcher* dispatcher, + unsigned long xid) { + dispatchers_.insert(std::make_pair(xid, dispatcher)); +} + +void MessagePumpAuraX11::RemoveDispatcherForWindow(unsigned long xid) { + dispatchers_.erase(xid); +} + +void MessagePumpAuraX11::AddDispatcherForRootWindow( + MessagePumpDispatcher* dispatcher) { + root_window_dispatchers_.AddObserver(dispatcher); +} + +void MessagePumpAuraX11::RemoveDispatcherForRootWindow( + MessagePumpDispatcher* dispatcher) { + root_window_dispatchers_.RemoveObserver(dispatcher); +} + +bool MessagePumpAuraX11::DispatchXEvents() { + Display* display = GetDefaultXDisplay(); + DCHECK(display); + MessagePumpDispatcher* dispatcher = + GetDispatcher() ? GetDispatcher() : this; + + // In the general case, we want to handle all pending events before running + // the tasks. This is what happens in the message_pump_glib case. + while (XPending(display)) { + XEvent xev; + XNextEvent(display, &xev); + if (dispatcher && ProcessXEvent(dispatcher, &xev)) + return TRUE; + } + return TRUE; +} + +void MessagePumpAuraX11::BlockUntilWindowMapped(unsigned long xid) { + XEvent event; + + Display* display = GetDefaultXDisplay(); + DCHECK(display); + + MessagePumpDispatcher* dispatcher = + GetDispatcher() ? GetDispatcher() : this; + + do { + // Block until there's a message of |event_mask| type on |w|. Then remove + // it from the queue and stuff it in |event|. + XWindowEvent(display, xid, StructureNotifyMask, &event); + ProcessXEvent(dispatcher, &event); + } while (event.type != MapNotify); +} + +void MessagePumpAuraX11::InitXSource() { + // CHECKs are to help track down crbug.com/113106. + CHECK(!x_source_); + Display* display = GetDefaultXDisplay(); + CHECK(display) << "Unable to get connection to X server"; + x_poll_.reset(new GPollFD()); + CHECK(x_poll_.get()); + x_poll_->fd = ConnectionNumber(display); + x_poll_->events = G_IO_IN; + + x_source_ = g_source_new(&XSourceFuncs, sizeof(GSource)); + g_source_add_poll(x_source_, x_poll_.get()); + g_source_set_can_recurse(x_source_, TRUE); + g_source_set_callback(x_source_, NULL, this, NULL); + g_source_attach(x_source_, g_main_context_default()); +} + +bool MessagePumpAuraX11::ProcessXEvent(MessagePumpDispatcher* dispatcher, + XEvent* xev) { + bool should_quit = false; + + bool have_cookie = false; + if (xev->type == GenericEvent && + XGetEventData(xev->xgeneric.display, &xev->xcookie)) { + have_cookie = true; + } + + if (!WillProcessXEvent(xev)) { + if (!dispatcher->Dispatch(xev)) { + should_quit = true; + Quit(); + } + DidProcessXEvent(xev); + } + + if (have_cookie) { + XFreeEventData(xev->xgeneric.display, &xev->xcookie); + } + + return should_quit; +} + +bool MessagePumpAuraX11::WillProcessXEvent(XEvent* xevent) { + if (!observers().might_have_observers()) + return false; + ObserverListBase::Iterator it(observers()); + MessagePumpObserver* obs; + while ((obs = it.GetNext()) != NULL) { + if (obs->WillProcessEvent(xevent)) + return true; + } + return false; +} + +void MessagePumpAuraX11::DidProcessXEvent(XEvent* xevent) { + FOR_EACH_OBSERVER(MessagePumpObserver, observers(), DidProcessEvent(xevent)); +} + +MessagePumpDispatcher* MessagePumpAuraX11::GetDispatcherForXEvent( + const NativeEvent& xev) const { + ::Window x_window = FindEventTarget(xev); + DispatchersMap::const_iterator it = dispatchers_.find(x_window); + return it != dispatchers_.end() ? it->second : NULL; +} + +bool MessagePumpAuraX11::Dispatch(const NativeEvent& xev) { + // MappingNotify events (meaning that the keyboard or pointer buttons have + // been remapped) aren't associated with a window; send them to all + // dispatchers. + if (xev->type == MappingNotify) { + for (DispatchersMap::const_iterator it = dispatchers_.begin(); + it != dispatchers_.end(); ++it) { + it->second->Dispatch(xev); + } + return true; + } + + if (FindEventTarget(xev) == x_root_window_) { + FOR_EACH_OBSERVER(MessagePumpDispatcher, root_window_dispatchers_, + Dispatch(xev)); + return true; + } + MessagePumpDispatcher* dispatcher = GetDispatcherForXEvent(xev); + return dispatcher ? dispatcher->Dispatch(xev) : true; +} + +} // namespace base diff --git a/base/message_loop/message_pump_aurax11.h b/base/message_loop/message_pump_aurax11.h new file mode 100644 index 0000000000..89089ad535 --- /dev/null +++ b/base/message_loop/message_pump_aurax11.h @@ -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. + +#ifndef BASE_MESSAGE_LOOP_MESSAGE_PUMP_AURAX11_H +#define BASE_MESSAGE_LOOP_MESSAGE_PUMP_AURAX11_H + +#include +#include + +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_pump.h" +#include "base/message_loop/message_pump_dispatcher.h" +#include "base/message_loop/message_pump_glib.h" +#include "base/message_loop/message_pump_observer.h" +#include "base/observer_list.h" + +// It would be nice to include the X11 headers here so that we use Window +// instead of its typedef of unsigned long, but we can't because everything in +// chrome includes us through base/message_loop/message_loop.h, and X11's crappy +// #define heavy headers muck up half of chrome. + +typedef struct _GPollFD GPollFD; +typedef struct _GSource GSource; +typedef struct _XDisplay Display; + +namespace base { + +// This class implements a message-pump for dispatching X events. +// +// If there's a current dispatcher given through RunWithDispatcher(), that +// dispatcher receives events. Otherwise, we route to messages to dispatchers +// who have subscribed to messages from a specific X11 window. +class BASE_EXPORT MessagePumpAuraX11 : public MessagePumpGlib, + public MessagePumpDispatcher { + public: + MessagePumpAuraX11(); + virtual ~MessagePumpAuraX11(); + + // Returns default X Display. + static Display* GetDefaultXDisplay(); + + // Returns true if the system supports XINPUT2. + static bool HasXInput2(); + + // Returns the UI message pump. + static MessagePumpAuraX11* Current(); + + // Adds/Removes |dispatcher| for the |xid|. This will route all messages from + // the window |xid| to |dispatcher. + void AddDispatcherForWindow(MessagePumpDispatcher* dispatcher, + unsigned long xid); + void RemoveDispatcherForWindow(unsigned long xid); + + // Adds/Removes |dispatcher| to receive all events sent to the X root + // window. A root window can have multiple dispatchers, and events on root + // windows will be dispatched to all. + void AddDispatcherForRootWindow(MessagePumpDispatcher* dispatcher); + void RemoveDispatcherForRootWindow(MessagePumpDispatcher* dispatcher); + + // Internal function. Called by the glib source dispatch function. Processes + // all available X events. + bool DispatchXEvents(); + + // Blocks on the X11 event queue until we receive notification from the + // xserver that |w| has been mapped; StructureNotifyMask events on |w| are + // pulled out from the queue and dispatched out of order. + // + // For those that know X11, this is really a wrapper around XWindowEvent + // which still makes sure the preempted event is dispatched instead of + // dropped on the floor. This method exists because mapping a window is + // asynchronous (and we receive an XEvent when mapped), while there are also + // functions which require a mapped window. + void BlockUntilWindowMapped(unsigned long xid); + + private: + typedef std::map DispatchersMap; + + // Initializes the glib event source for X. + void InitXSource(); + + // Dispatches the XEvent and returns true if we should exit the current loop + // of message processing. + bool ProcessXEvent(MessagePumpDispatcher* dispatcher, XEvent* event); + + // Sends the event to the observers. If an observer returns true, then it does + // not send the event to any other observers and returns true. Returns false + // if no observer returns true. + bool WillProcessXEvent(XEvent* xevent); + void DidProcessXEvent(XEvent* xevent); + + // Returns the Dispatcher based on the event's target window. + MessagePumpDispatcher* GetDispatcherForXEvent(const NativeEvent& xev) const; + + // Overridden from MessagePumpDispatcher: + virtual bool Dispatch(const NativeEvent& event) OVERRIDE; + + // The event source for X events. + GSource* x_source_; + + // The poll attached to |x_source_|. + scoped_ptr x_poll_; + + DispatchersMap dispatchers_; + + // Dispatch calls can cause addition of new dispatchers as we iterate + // through them. Use ObserverList to ensure the iterator remains valid across + // additions. + ObserverList root_window_dispatchers_; + + unsigned long x_root_window_; + + DISALLOW_COPY_AND_ASSIGN(MessagePumpAuraX11); +}; + +typedef MessagePumpAuraX11 MessagePumpForUI; + +} // namespace base + +#endif // BASE_MESSAGE_LOOP_MESSAGE_PUMP_AURAX11_H diff --git a/base/message_loop/message_pump_default.cc b/base/message_loop/message_pump_default.cc new file mode 100644 index 0000000000..27c19e0227 --- /dev/null +++ b/base/message_loop/message_pump_default.cc @@ -0,0 +1,88 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/message_loop/message_pump_default.h" + +#include "base/logging.h" +#include "base/threading/thread_restrictions.h" + +#if defined(OS_MACOSX) +#include "base/mac/scoped_nsautorelease_pool.h" +#endif + +namespace base { + +MessagePumpDefault::MessagePumpDefault() + : keep_running_(true), + event_(false, false) { +} + +MessagePumpDefault::~MessagePumpDefault() { +} + +void MessagePumpDefault::Run(Delegate* delegate) { + DCHECK(keep_running_) << "Quit must have been called outside of Run!"; + + for (;;) { +#if defined(OS_MACOSX) + mac::ScopedNSAutoreleasePool autorelease_pool; +#endif + + bool did_work = delegate->DoWork(); + if (!keep_running_) + break; + + did_work |= delegate->DoDelayedWork(&delayed_work_time_); + if (!keep_running_) + break; + + if (did_work) + continue; + + did_work = delegate->DoIdleWork(); + if (!keep_running_) + break; + + if (did_work) + continue; + + ThreadRestrictions::ScopedAllowWait allow_wait; + if (delayed_work_time_.is_null()) { + event_.Wait(); + } else { + TimeDelta delay = delayed_work_time_ - TimeTicks::Now(); + if (delay > TimeDelta()) { + event_.TimedWait(delay); + } else { + // It looks like delayed_work_time_ indicates a time in the past, so we + // need to call DoDelayedWork now. + delayed_work_time_ = TimeTicks(); + } + } + // Since event_ is auto-reset, we don't need to do anything special here + // other than service each delegate method. + } + + keep_running_ = true; +} + +void MessagePumpDefault::Quit() { + keep_running_ = false; +} + +void MessagePumpDefault::ScheduleWork() { + // Since this can be called on any thread, we need to ensure that our Run + // loop wakes up. + event_.Signal(); +} + +void MessagePumpDefault::ScheduleDelayedWork( + const TimeTicks& delayed_work_time) { + // We know that we can't be blocked on Wait right now since this method can + // only be called on the same thread as Run, so we only need to update our + // record of how long to sleep when we do sleep. + delayed_work_time_ = delayed_work_time; +} + +} // namespace base diff --git a/base/message_loop/message_pump_default.h b/base/message_loop/message_pump_default.h new file mode 100644 index 0000000000..a9b83e8de7 --- /dev/null +++ b/base/message_loop/message_pump_default.h @@ -0,0 +1,40 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MESSAGE_LOOP_MESSAGE_PUMP_DEFAULT_H_ +#define BASE_MESSAGE_LOOP_MESSAGE_PUMP_DEFAULT_H_ + +#include "base/message_loop/message_pump.h" +#include "base/synchronization/waitable_event.h" +#include "base/time/time.h" + +namespace base { + +class MessagePumpDefault : public MessagePump { + public: + MessagePumpDefault(); + virtual ~MessagePumpDefault(); + + // MessagePump methods: + virtual void Run(Delegate* delegate) OVERRIDE; + virtual void Quit() OVERRIDE; + virtual void ScheduleWork() OVERRIDE; + virtual void ScheduleDelayedWork(const TimeTicks& delayed_work_time) OVERRIDE; + + private: + // This flag is set to false when Run should return. + bool keep_running_; + + // Used to sleep until there is more work to do. + WaitableEvent event_; + + // The time at which we should call DoDelayedWork. + TimeTicks delayed_work_time_; + + DISALLOW_COPY_AND_ASSIGN(MessagePumpDefault); +}; + +} // namespace base + +#endif // BASE__MESSAGE_LOOPMESSAGE_PUMP_DEFAULT_H_ diff --git a/base/message_loop/message_pump_dispatcher.h b/base/message_loop/message_pump_dispatcher.h new file mode 100644 index 0000000000..e49fa4f15d --- /dev/null +++ b/base/message_loop/message_pump_dispatcher.h @@ -0,0 +1,32 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MESSAGE_LOOP_MESSAGE_PUMP_DISPATCHER_H +#define BASE_MESSAGE_LOOP_MESSAGE_PUMP_DISPATCHER_H + +#include "base/base_export.h" +#include "base/event_types.h" + +namespace base { + +// Dispatcher is used during a nested invocation of Run to dispatch events when +// |RunLoop(dispatcher).Run()| is used. If |RunLoop().Run()| is invoked, +// MessageLoop does not dispatch events (or invoke TranslateMessage), rather +// every message is passed to Dispatcher's Dispatch method for dispatch. It is +// up to the Dispatcher whether or not to dispatch the event. +// +// The nested loop is exited by either posting a quit, or returning false +// from Dispatch. +class BASE_EXPORT MessagePumpDispatcher { + public: + virtual ~MessagePumpDispatcher() {} + + // Dispatches the event. If true is returned processing continues as + // normal. If false is returned, the nested loop exits immediately. + virtual bool Dispatch(const NativeEvent& event) = 0; +}; + +} // namespace base + +#endif // BASE_MESSAGE_LOOP_MESSAGE_PUMP_DISPATCHER_H diff --git a/base/message_loop/message_pump_glib.cc b/base/message_loop/message_pump_glib.cc new file mode 100644 index 0000000000..cfacb7b09a --- /dev/null +++ b/base/message_loop/message_pump_glib.cc @@ -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/message_loop/message_pump_glib.h" + +#include +#include + +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/threading/platform_thread.h" + +namespace base { + +namespace { + +// Return a timeout suitable for the glib loop, -1 to block forever, +// 0 to return right away, or a timeout in milliseconds from now. +int GetTimeIntervalMilliseconds(const TimeTicks& from) { + if (from.is_null()) + return -1; + + // Be careful here. TimeDelta has a precision of microseconds, but we want a + // value in milliseconds. If there are 5.5ms left, should the delay be 5 or + // 6? It should be 6 to avoid executing delayed work too early. + int delay = static_cast( + ceil((from - TimeTicks::Now()).InMillisecondsF())); + + // If this value is negative, then we need to run delayed work soon. + return delay < 0 ? 0 : delay; +} + +// A brief refresher on GLib: +// GLib sources have four callbacks: Prepare, Check, Dispatch and Finalize. +// On each iteration of the GLib pump, it calls each source's Prepare function. +// This function should return TRUE if it wants GLib to call its Dispatch, and +// FALSE otherwise. It can also set a timeout in this case for the next time +// Prepare should be called again (it may be called sooner). +// After the Prepare calls, GLib does a poll to check for events from the +// system. File descriptors can be attached to the sources. The poll may block +// if none of the Prepare calls returned TRUE. It will block indefinitely, or +// by the minimum time returned by a source in Prepare. +// After the poll, GLib calls Check for each source that returned FALSE +// from Prepare. The return value of Check has the same meaning as for Prepare, +// making Check a second chance to tell GLib we are ready for Dispatch. +// Finally, GLib calls Dispatch for each source that is ready. If Dispatch +// returns FALSE, GLib will destroy the source. Dispatch calls may be recursive +// (i.e., you can call Run from them), but Prepare and Check cannot. +// Finalize is called when the source is destroyed. +// NOTE: It is common for subsytems to want to process pending events while +// doing intensive work, for example the flash plugin. They usually use the +// following pattern (recommended by the GTK docs): +// while (gtk_events_pending()) { +// gtk_main_iteration(); +// } +// +// gtk_events_pending just calls g_main_context_pending, which does the +// following: +// - Call prepare on all the sources. +// - Do the poll with a timeout of 0 (not blocking). +// - Call check on all the sources. +// - *Does not* call dispatch on the sources. +// - Return true if any of prepare() or check() returned true. +// +// gtk_main_iteration just calls g_main_context_iteration, which does the whole +// thing, respecting the timeout for the poll (and block, although it is +// expected not to if gtk_events_pending returned true), and call dispatch. +// +// Thus it is important to only return true from prepare or check if we +// actually have events or work to do. We also need to make sure we keep +// internal state consistent so that if prepare/check return true when called +// from gtk_events_pending, they will still return true when called right +// after, from gtk_main_iteration. +// +// For the GLib pump we try to follow the Windows UI pump model: +// - Whenever we receive a wakeup event or the timer for delayed work expires, +// we run DoWork and/or DoDelayedWork. That part will also run in the other +// event pumps. +// - We also run DoWork, DoDelayedWork, and possibly DoIdleWork in the main +// loop, around event handling. + +struct WorkSource : public GSource { + MessagePumpGlib* pump; +}; + +gboolean WorkSourcePrepare(GSource* source, + gint* timeout_ms) { + *timeout_ms = static_cast(source)->pump->HandlePrepare(); + // We always return FALSE, so that our timeout is honored. If we were + // to return TRUE, the timeout would be considered to be 0 and the poll + // would never block. Once the poll is finished, Check will be called. + return FALSE; +} + +gboolean WorkSourceCheck(GSource* source) { + // Only return TRUE if Dispatch should be called. + return static_cast(source)->pump->HandleCheck(); +} + +gboolean WorkSourceDispatch(GSource* source, + GSourceFunc unused_func, + gpointer unused_data) { + + static_cast(source)->pump->HandleDispatch(); + // Always return TRUE so our source stays registered. + return TRUE; +} + +// I wish these could be const, but g_source_new wants non-const. +GSourceFuncs WorkSourceFuncs = { + WorkSourcePrepare, + WorkSourceCheck, + WorkSourceDispatch, + NULL +}; + +} // namespace + +struct MessagePumpGlib::RunState { + Delegate* delegate; + MessagePumpDispatcher* dispatcher; + + // Used to flag that the current Run() invocation should return ASAP. + bool should_quit; + + // Used to count how many Run() invocations are on the stack. + int run_depth; + + // This keeps the state of whether the pump got signaled that there was new + // work to be done. Since we eat the message on the wake up pipe as soon as + // we get it, we keep that state here to stay consistent. + bool has_work; +}; + +MessagePumpGlib::MessagePumpGlib() + : state_(NULL), + context_(g_main_context_default()), + wakeup_gpollfd_(new GPollFD) { + // Create our wakeup pipe, which is used to flag when work was scheduled. + int fds[2]; + int ret = pipe(fds); + DCHECK_EQ(ret, 0); + (void)ret; // Prevent warning in release mode. + + wakeup_pipe_read_ = fds[0]; + wakeup_pipe_write_ = fds[1]; + wakeup_gpollfd_->fd = wakeup_pipe_read_; + wakeup_gpollfd_->events = G_IO_IN; + + work_source_ = g_source_new(&WorkSourceFuncs, sizeof(WorkSource)); + static_cast(work_source_)->pump = this; + g_source_add_poll(work_source_, wakeup_gpollfd_.get()); + // Use a low priority so that we let other events in the queue go first. + g_source_set_priority(work_source_, G_PRIORITY_DEFAULT_IDLE); + // This is needed to allow Run calls inside Dispatch. + g_source_set_can_recurse(work_source_, TRUE); + g_source_attach(work_source_, context_); +} + +MessagePumpGlib::~MessagePumpGlib() { + g_source_destroy(work_source_); + g_source_unref(work_source_); + close(wakeup_pipe_read_); + close(wakeup_pipe_write_); +} + +void MessagePumpGlib::RunWithDispatcher(Delegate* delegate, + MessagePumpDispatcher* dispatcher) { +#ifndef NDEBUG + // Make sure we only run this on one thread. X/GTK only has one message pump + // so we can only have one UI loop per process. + static PlatformThreadId thread_id = PlatformThread::CurrentId(); + DCHECK(thread_id == PlatformThread::CurrentId()) << + "Running MessagePumpGlib on two different threads; " + "this is unsupported by GLib!"; +#endif + + RunState state; + state.delegate = delegate; + state.dispatcher = dispatcher; + state.should_quit = false; + state.run_depth = state_ ? state_->run_depth + 1 : 1; + state.has_work = false; + + RunState* previous_state = state_; + state_ = &state; + + // We really only do a single task for each iteration of the loop. If we + // have done something, assume there is likely something more to do. This + // will mean that we don't block on the message pump until there was nothing + // more to do. We also set this to true to make sure not to block on the + // first iteration of the loop, so RunUntilIdle() works correctly. + bool more_work_is_plausible = true; + + // We run our own loop instead of using g_main_loop_quit in one of the + // callbacks. This is so we only quit our own loops, and we don't quit + // nested loops run by others. TODO(deanm): Is this what we want? + for (;;) { + // Don't block if we think we have more work to do. + bool block = !more_work_is_plausible; + + more_work_is_plausible = g_main_context_iteration(context_, block); + if (state_->should_quit) + break; + + more_work_is_plausible |= state_->delegate->DoWork(); + if (state_->should_quit) + break; + + more_work_is_plausible |= + state_->delegate->DoDelayedWork(&delayed_work_time_); + if (state_->should_quit) + break; + + if (more_work_is_plausible) + continue; + + more_work_is_plausible = state_->delegate->DoIdleWork(); + if (state_->should_quit) + break; + } + + state_ = previous_state; +} + +// Return the timeout we want passed to poll. +int MessagePumpGlib::HandlePrepare() { + // We know we have work, but we haven't called HandleDispatch yet. Don't let + // the pump block so that we can do some processing. + if (state_ && // state_ may be null during tests. + state_->has_work) + return 0; + + // We don't think we have work to do, but make sure not to block + // longer than the next time we need to run delayed work. + return GetTimeIntervalMilliseconds(delayed_work_time_); +} + +bool MessagePumpGlib::HandleCheck() { + if (!state_) // state_ may be null during tests. + return false; + + // We usually have a single message on the wakeup pipe, since we are only + // signaled when the queue went from empty to non-empty, but there can be + // two messages if a task posted a task, hence we read at most two bytes. + // The glib poll will tell us whether there was data, so this read + // shouldn't block. + if (wakeup_gpollfd_->revents & G_IO_IN) { + char msg[2]; + const int num_bytes = HANDLE_EINTR(read(wakeup_pipe_read_, msg, 2)); + if (num_bytes < 1) { + NOTREACHED() << "Error reading from the wakeup pipe."; + } + DCHECK((num_bytes == 1 && msg[0] == '!') || + (num_bytes == 2 && msg[0] == '!' && msg[1] == '!')); + // Since we ate the message, we need to record that we have more work, + // because HandleCheck() may be called without HandleDispatch being called + // afterwards. + state_->has_work = true; + } + + if (state_->has_work) + return true; + + if (GetTimeIntervalMilliseconds(delayed_work_time_) == 0) { + // The timer has expired. That condition will stay true until we process + // that delayed work, so we don't need to record this differently. + return true; + } + + return false; +} + +void MessagePumpGlib::HandleDispatch() { + state_->has_work = false; + if (state_->delegate->DoWork()) { + // NOTE: on Windows at this point we would call ScheduleWork (see + // MessagePumpGlib::HandleWorkMessage in message_pump_win.cc). But here, + // instead of posting a message on the wakeup pipe, we can avoid the + // syscalls and just signal that we have more work. + state_->has_work = true; + } + + if (state_->should_quit) + return; + + state_->delegate->DoDelayedWork(&delayed_work_time_); +} + +void MessagePumpGlib::AddObserver(MessagePumpObserver* observer) { + observers_.AddObserver(observer); +} + +void MessagePumpGlib::RemoveObserver(MessagePumpObserver* observer) { + observers_.RemoveObserver(observer); +} + +void MessagePumpGlib::Run(Delegate* delegate) { + RunWithDispatcher(delegate, NULL); +} + +void MessagePumpGlib::Quit() { + if (state_) { + state_->should_quit = true; + } else { + NOTREACHED() << "Quit called outside Run!"; + } +} + +void MessagePumpGlib::ScheduleWork() { + // This can be called on any thread, so we don't want to touch any state + // variables as we would then need locks all over. This ensures that if + // we are sleeping in a poll that we will wake up. + char msg = '!'; + if (HANDLE_EINTR(write(wakeup_pipe_write_, &msg, 1)) != 1) { + NOTREACHED() << "Could not write to the UI message loop wakeup pipe!"; + } +} + +void MessagePumpGlib::ScheduleDelayedWork(const TimeTicks& delayed_work_time) { + // We need to wake up the loop in case the poll timeout needs to be + // adjusted. This will cause us to try to do work, but that's ok. + delayed_work_time_ = delayed_work_time; + ScheduleWork(); +} + +MessagePumpDispatcher* MessagePumpGlib::GetDispatcher() { + return state_ ? state_->dispatcher : NULL; +} + +} // namespace base diff --git a/base/message_loop/message_pump_glib.h b/base/message_loop/message_pump_glib.h new file mode 100644 index 0000000000..33690d0858 --- /dev/null +++ b/base/message_loop/message_pump_glib.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_MESSAGE_LOOP_MESSAGE_PUMP_GLIB_H_ +#define BASE_MESSAGE_LOOP_MESSAGE_PUMP_GLIB_H_ + +#include "base/base_export.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_pump.h" +#include "base/observer_list.h" +#include "base/time/time.h" + +typedef struct _GMainContext GMainContext; +typedef struct _GPollFD GPollFD; +typedef struct _GSource GSource; + +namespace base { + +// MessagePumpObserver is notified prior to an event being dispatched. As +// Observers are notified of every change, they have to be FAST! The platform +// specific implementation of the class is in message_pump_gtk/message_pump_x. +class MessagePumpObserver; + +// MessagePumpDispatcher is used during a nested invocation of Run to dispatch +// events. If Run is invoked with a non-NULL MessagePumpDispatcher, MessageLoop +// does not dispatch events (or invoke gtk_main_do_event), rather every event is +// passed to Dispatcher's Dispatch method for dispatch. It is up to the +// Dispatcher to dispatch, or not, the event. The platform specific +// implementation of the class is in message_pump_gtk/message_pump_x. +class MessagePumpDispatcher; + +// This class implements a base MessagePump needed for TYPE_UI MessageLoops on +// platforms using GLib. +class BASE_EXPORT MessagePumpGlib : public MessagePump { + public: + MessagePumpGlib(); + virtual ~MessagePumpGlib(); + + // Like MessagePump::Run, but events are routed through dispatcher. + virtual void RunWithDispatcher(Delegate* delegate, + MessagePumpDispatcher* dispatcher); + + // Internal methods used for processing the pump callbacks. They are + // public for simplicity but should not be used directly. HandlePrepare + // is called during the prepare step of glib, and returns a timeout that + // will be passed to the poll. HandleCheck is called after the poll + // has completed, and returns whether or not HandleDispatch should be called. + // HandleDispatch is called if HandleCheck returned true. + int HandlePrepare(); + bool HandleCheck(); + void HandleDispatch(); + + // Adds an Observer, which will start receiving notifications immediately. + void AddObserver(MessagePumpObserver* observer); + + // Removes an Observer. It is safe to call this method while an Observer is + // receiving a notification callback. + void RemoveObserver(MessagePumpObserver* observer); + + // Overridden from MessagePump: + virtual void Run(Delegate* delegate) OVERRIDE; + virtual void Quit() OVERRIDE; + virtual void ScheduleWork() OVERRIDE; + virtual void ScheduleDelayedWork(const TimeTicks& delayed_work_time) OVERRIDE; + + protected: + // Returns the dispatcher for the current run state (|state_->dispatcher|). + MessagePumpDispatcher* GetDispatcher(); + + ObserverList& observers() { return observers_; } + + private: + // We may make recursive calls to Run, so we save state that needs to be + // separate between them in this structure type. + struct RunState; + + RunState* state_; + + // This is a GLib structure that we can add event sources to. We use the + // default GLib context, which is the one to which all GTK events are + // dispatched. + GMainContext* context_; + + // This is the time when we need to do delayed work. + TimeTicks delayed_work_time_; + + // The work source. It is shared by all calls to Run and destroyed when + // the message pump is destroyed. + GSource* work_source_; + + // We use a wakeup pipe to make sure we'll get out of the glib polling phase + // when another thread has scheduled us to do some work. There is a glib + // mechanism g_main_context_wakeup, but this won't guarantee that our event's + // Dispatch() will be called. + int wakeup_pipe_read_; + int wakeup_pipe_write_; + // Use a scoped_ptr to avoid needing the definition of GPollFD in the header. + scoped_ptr wakeup_gpollfd_; + + // List of observers. + ObserverList observers_; + + DISALLOW_COPY_AND_ASSIGN(MessagePumpGlib); +}; + +} // namespace base + +#endif // BASE_MESSAGE_LOOP_MESSAGE_PUMP_GLIB_H_ diff --git a/base/message_loop/message_pump_glib_unittest.cc b/base/message_loop/message_pump_glib_unittest.cc new file mode 100644 index 0000000000..033e6cde92 --- /dev/null +++ b/base/message_loop/message_pump_glib_unittest.cc @@ -0,0 +1,580 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/message_loop/message_pump_glib.h" + +#include +#include + +#include +#include + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/threading/thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(TOOLKIT_GTK) +#include +#endif + +namespace base { +namespace { + +// This class injects dummy "events" into the GLib loop. When "handled" these +// events can run tasks. This is intended to mock gtk events (the corresponding +// GLib source runs at the same priority). +class EventInjector { + public: + EventInjector() : processed_events_(0) { + source_ = static_cast(g_source_new(&SourceFuncs, sizeof(Source))); + source_->injector = this; + g_source_attach(source_, NULL); + g_source_set_can_recurse(source_, TRUE); + } + + ~EventInjector() { + g_source_destroy(source_); + g_source_unref(source_); + } + + int HandlePrepare() { + // If the queue is empty, block. + if (events_.empty()) + return -1; + TimeDelta delta = events_[0].time - Time::NowFromSystemTime(); + return std::max(0, static_cast(ceil(delta.InMillisecondsF()))); + } + + bool HandleCheck() { + if (events_.empty()) + return false; + return events_[0].time <= Time::NowFromSystemTime(); + } + + void HandleDispatch() { + if (events_.empty()) + return; + Event event = events_[0]; + events_.erase(events_.begin()); + ++processed_events_; + if (!event.callback.is_null()) + event.callback.Run(); + else if (!event.task.is_null()) + event.task.Run(); + } + + // Adds an event to the queue. When "handled", executes |callback|. + // delay_ms is relative to the last event if any, or to Now() otherwise. + void AddEvent(int delay_ms, const Closure& callback) { + AddEventHelper(delay_ms, callback, Closure()); + } + + void AddDummyEvent(int delay_ms) { + AddEventHelper(delay_ms, Closure(), Closure()); + } + + void AddEventAsTask(int delay_ms, const Closure& task) { + AddEventHelper(delay_ms, Closure(), task); + } + + void Reset() { + processed_events_ = 0; + events_.clear(); + } + + int processed_events() const { return processed_events_; } + + private: + struct Event { + Time time; + Closure callback; + Closure task; + }; + + struct Source : public GSource { + EventInjector* injector; + }; + + void AddEventHelper( + int delay_ms, const Closure& callback, const Closure& task) { + Time last_time; + if (!events_.empty()) + last_time = (events_.end()-1)->time; + else + last_time = Time::NowFromSystemTime(); + + Time future = last_time + TimeDelta::FromMilliseconds(delay_ms); + EventInjector::Event event = {future, callback, task}; + events_.push_back(event); + } + + static gboolean Prepare(GSource* source, gint* timeout_ms) { + *timeout_ms = static_cast(source)->injector->HandlePrepare(); + return FALSE; + } + + static gboolean Check(GSource* source) { + return static_cast(source)->injector->HandleCheck(); + } + + static gboolean Dispatch(GSource* source, + GSourceFunc unused_func, + gpointer unused_data) { + static_cast(source)->injector->HandleDispatch(); + return TRUE; + } + + Source* source_; + std::vector events_; + int processed_events_; + static GSourceFuncs SourceFuncs; + DISALLOW_COPY_AND_ASSIGN(EventInjector); +}; + +GSourceFuncs EventInjector::SourceFuncs = { + EventInjector::Prepare, + EventInjector::Check, + EventInjector::Dispatch, + NULL +}; + +void IncrementInt(int *value) { + ++*value; +} + +// Checks how many events have been processed by the injector. +void ExpectProcessedEvents(EventInjector* injector, int count) { + EXPECT_EQ(injector->processed_events(), count); +} + +// Posts a task on the current message loop. +void PostMessageLoopTask(const tracked_objects::Location& from_here, + const Closure& task) { + MessageLoop::current()->PostTask(from_here, task); +} + +// Test fixture. +class MessagePumpGLibTest : public testing::Test { + public: + MessagePumpGLibTest() : loop_(NULL), injector_(NULL) { } + + // Overridden from testing::Test: + virtual void SetUp() OVERRIDE { + loop_ = new MessageLoop(MessageLoop::TYPE_UI); + injector_ = new EventInjector(); + } + virtual void TearDown() OVERRIDE { + delete injector_; + injector_ = NULL; + delete loop_; + loop_ = NULL; + } + + MessageLoop* loop() const { return loop_; } + EventInjector* injector() const { return injector_; } + + private: + MessageLoop* loop_; + EventInjector* injector_; + DISALLOW_COPY_AND_ASSIGN(MessagePumpGLibTest); +}; + +} // namespace + +TEST_F(MessagePumpGLibTest, TestQuit) { + // Checks that Quit works and that the basic infrastructure is working. + + // Quit from a task + RunLoop().RunUntilIdle(); + EXPECT_EQ(0, injector()->processed_events()); + + injector()->Reset(); + // Quit from an event + injector()->AddEvent(0, MessageLoop::QuitWhenIdleClosure()); + loop()->Run(); + EXPECT_EQ(1, injector()->processed_events()); +} + +TEST_F(MessagePumpGLibTest, TestEventTaskInterleave) { + // Checks that tasks posted by events are executed before the next event if + // the posted task queue is empty. + // MessageLoop doesn't make strong guarantees that it is the case, but the + // current implementation ensures it and the tests below rely on it. + // If changes cause this test to fail, it is reasonable to change it, but + // TestWorkWhileWaitingForEvents and TestEventsWhileWaitingForWork have to be + // changed accordingly, otherwise they can become flaky. + injector()->AddEventAsTask(0, Bind(&DoNothing)); + Closure check_task = + Bind(&ExpectProcessedEvents, Unretained(injector()), 2); + Closure posted_task = + Bind(&PostMessageLoopTask, FROM_HERE, check_task); + injector()->AddEventAsTask(0, posted_task); + injector()->AddEventAsTask(0, Bind(&DoNothing)); + injector()->AddEvent(0, MessageLoop::QuitWhenIdleClosure()); + loop()->Run(); + EXPECT_EQ(4, injector()->processed_events()); + + injector()->Reset(); + injector()->AddEventAsTask(0, Bind(&DoNothing)); + check_task = + Bind(&ExpectProcessedEvents, Unretained(injector()), 2); + posted_task = Bind(&PostMessageLoopTask, FROM_HERE, check_task); + injector()->AddEventAsTask(0, posted_task); + injector()->AddEventAsTask(10, Bind(&DoNothing)); + injector()->AddEvent(0, MessageLoop::QuitWhenIdleClosure()); + loop()->Run(); + EXPECT_EQ(4, injector()->processed_events()); +} + +TEST_F(MessagePumpGLibTest, TestWorkWhileWaitingForEvents) { + int task_count = 0; + // Tests that we process tasks while waiting for new events. + // The event queue is empty at first. + for (int i = 0; i < 10; ++i) { + loop()->PostTask(FROM_HERE, Bind(&IncrementInt, &task_count)); + } + // After all the previous tasks have executed, enqueue an event that will + // quit. + loop()->PostTask( + FROM_HERE, + Bind(&EventInjector::AddEvent, Unretained(injector()), 0, + MessageLoop::QuitWhenIdleClosure())); + loop()->Run(); + ASSERT_EQ(10, task_count); + EXPECT_EQ(1, injector()->processed_events()); + + // Tests that we process delayed tasks while waiting for new events. + injector()->Reset(); + task_count = 0; + for (int i = 0; i < 10; ++i) { + loop()->PostDelayedTask( + FROM_HERE, + Bind(&IncrementInt, &task_count), + TimeDelta::FromMilliseconds(10*i)); + } + // After all the previous tasks have executed, enqueue an event that will + // quit. + // This relies on the fact that delayed tasks are executed in delay order. + // That is verified in message_loop_unittest.cc. + loop()->PostDelayedTask( + FROM_HERE, + Bind(&EventInjector::AddEvent, Unretained(injector()), 10, + MessageLoop::QuitWhenIdleClosure()), + TimeDelta::FromMilliseconds(150)); + loop()->Run(); + ASSERT_EQ(10, task_count); + EXPECT_EQ(1, injector()->processed_events()); +} + +TEST_F(MessagePumpGLibTest, TestEventsWhileWaitingForWork) { + // Tests that we process events while waiting for work. + // The event queue is empty at first. + for (int i = 0; i < 10; ++i) { + injector()->AddDummyEvent(0); + } + // After all the events have been processed, post a task that will check that + // the events have been processed (note: the task executes after the event + // that posted it has been handled, so we expect 11 at that point). + Closure check_task = + Bind(&ExpectProcessedEvents, Unretained(injector()), 11); + Closure posted_task = + Bind(&PostMessageLoopTask, FROM_HERE, check_task); + injector()->AddEventAsTask(10, posted_task); + + // And then quit (relies on the condition tested by TestEventTaskInterleave). + injector()->AddEvent(10, MessageLoop::QuitWhenIdleClosure()); + loop()->Run(); + + EXPECT_EQ(12, injector()->processed_events()); +} + +namespace { + +// This class is a helper for the concurrent events / posted tasks test below. +// It will quit the main loop once enough tasks and events have been processed, +// while making sure there is always work to do and events in the queue. +class ConcurrentHelper : public RefCounted { + public: + explicit ConcurrentHelper(EventInjector* injector) + : injector_(injector), + event_count_(kStartingEventCount), + task_count_(kStartingTaskCount) { + } + + void FromTask() { + if (task_count_ > 0) { + --task_count_; + } + if (task_count_ == 0 && event_count_ == 0) { + MessageLoop::current()->QuitWhenIdle(); + } else { + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&ConcurrentHelper::FromTask, this)); + } + } + + void FromEvent() { + if (event_count_ > 0) { + --event_count_; + } + if (task_count_ == 0 && event_count_ == 0) { + MessageLoop::current()->QuitWhenIdle(); + } else { + injector_->AddEventAsTask( + 0, Bind(&ConcurrentHelper::FromEvent, this)); + } + } + + int event_count() const { return event_count_; } + int task_count() const { return task_count_; } + + private: + friend class RefCounted; + + ~ConcurrentHelper() {} + + static const int kStartingEventCount = 20; + static const int kStartingTaskCount = 20; + + EventInjector* injector_; + int event_count_; + int task_count_; +}; + +} // namespace + +TEST_F(MessagePumpGLibTest, TestConcurrentEventPostedTask) { + // Tests that posted tasks don't starve events, nor the opposite. + // We use the helper class above. We keep both event and posted task queues + // full, the helper verifies that both tasks and events get processed. + // If that is not the case, either event_count_ or task_count_ will not get + // to 0, and MessageLoop::QuitWhenIdle() will never be called. + scoped_refptr helper = new ConcurrentHelper(injector()); + + // Add 2 events to the queue to make sure it is always full (when we remove + // the event before processing it). + injector()->AddEventAsTask( + 0, Bind(&ConcurrentHelper::FromEvent, helper.get())); + injector()->AddEventAsTask( + 0, Bind(&ConcurrentHelper::FromEvent, helper.get())); + + // Similarly post 2 tasks. + loop()->PostTask( + FROM_HERE, Bind(&ConcurrentHelper::FromTask, helper.get())); + loop()->PostTask( + FROM_HERE, Bind(&ConcurrentHelper::FromTask, helper.get())); + + loop()->Run(); + EXPECT_EQ(0, helper->event_count()); + EXPECT_EQ(0, helper->task_count()); +} + +namespace { + +void AddEventsAndDrainGLib(EventInjector* injector) { + // Add a couple of dummy events + injector->AddDummyEvent(0); + injector->AddDummyEvent(0); + // Then add an event that will quit the main loop. + injector->AddEvent(0, MessageLoop::QuitWhenIdleClosure()); + + // Post a couple of dummy tasks + MessageLoop::current()->PostTask(FROM_HERE, Bind(&DoNothing)); + MessageLoop::current()->PostTask(FROM_HERE, Bind(&DoNothing)); + + // Drain the events + while (g_main_context_pending(NULL)) { + g_main_context_iteration(NULL, FALSE); + } +} + +} // namespace + +TEST_F(MessagePumpGLibTest, TestDrainingGLib) { + // Tests that draining events using GLib works. + loop()->PostTask( + FROM_HERE, + Bind(&AddEventsAndDrainGLib, Unretained(injector()))); + loop()->Run(); + + EXPECT_EQ(3, injector()->processed_events()); +} + + +namespace { + +#if defined(TOOLKIT_GTK) +void AddEventsAndDrainGtk(EventInjector* injector) { + // Add a couple of dummy events + injector->AddDummyEvent(0); + injector->AddDummyEvent(0); + // Then add an event that will quit the main loop. + injector->AddEvent(0, MessageLoop::QuitWhenIdleClosure()); + + // Post a couple of dummy tasks + MessageLoop::current()->PostTask(FROM_HERE, Bind(&DoNothing)); + MessageLoop::current()->PostTask(FROM_HERE, Bind(&DoNothing)); + + // Drain the events + while (gtk_events_pending()) { + gtk_main_iteration(); + } +} +#endif + +} // namespace + +#if defined(TOOLKIT_GTK) +TEST_F(MessagePumpGLibTest, TestDrainingGtk) { + // Tests that draining events using Gtk works. + loop()->PostTask( + FROM_HERE, + Bind(&AddEventsAndDrainGtk, Unretained(injector()))); + loop()->Run(); + + EXPECT_EQ(3, injector()->processed_events()); +} +#endif + +namespace { + +// Helper class that lets us run the GLib message loop. +class GLibLoopRunner : public RefCounted { + public: + GLibLoopRunner() : quit_(false) { } + + void RunGLib() { + while (!quit_) { + g_main_context_iteration(NULL, TRUE); + } + } + + void RunLoop() { +#if defined(TOOLKIT_GTK) + while (!quit_) { + gtk_main_iteration(); + } +#else + while (!quit_) { + g_main_context_iteration(NULL, TRUE); + } +#endif + } + + void Quit() { + quit_ = true; + } + + void Reset() { + quit_ = false; + } + + private: + friend class RefCounted; + + ~GLibLoopRunner() {} + + bool quit_; +}; + +void TestGLibLoopInternal(EventInjector* injector) { + // Allow tasks to be processed from 'native' event loops. + MessageLoop::current()->SetNestableTasksAllowed(true); + scoped_refptr runner = new GLibLoopRunner(); + + int task_count = 0; + // Add a couple of dummy events + injector->AddDummyEvent(0); + injector->AddDummyEvent(0); + // Post a couple of dummy tasks + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&IncrementInt, &task_count)); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&IncrementInt, &task_count)); + // Delayed events + injector->AddDummyEvent(10); + injector->AddDummyEvent(10); + // Delayed work + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + Bind(&IncrementInt, &task_count), + TimeDelta::FromMilliseconds(30)); + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + Bind(&GLibLoopRunner::Quit, runner.get()), + TimeDelta::FromMilliseconds(40)); + + // Run a nested, straight GLib message loop. + runner->RunGLib(); + + ASSERT_EQ(3, task_count); + EXPECT_EQ(4, injector->processed_events()); + MessageLoop::current()->QuitWhenIdle(); +} + +void TestGtkLoopInternal(EventInjector* injector) { + // Allow tasks to be processed from 'native' event loops. + MessageLoop::current()->SetNestableTasksAllowed(true); + scoped_refptr runner = new GLibLoopRunner(); + + int task_count = 0; + // Add a couple of dummy events + injector->AddDummyEvent(0); + injector->AddDummyEvent(0); + // Post a couple of dummy tasks + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&IncrementInt, &task_count)); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&IncrementInt, &task_count)); + // Delayed events + injector->AddDummyEvent(10); + injector->AddDummyEvent(10); + // Delayed work + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + Bind(&IncrementInt, &task_count), + TimeDelta::FromMilliseconds(30)); + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + Bind(&GLibLoopRunner::Quit, runner.get()), + TimeDelta::FromMilliseconds(40)); + + // Run a nested, straight Gtk message loop. + runner->RunLoop(); + + ASSERT_EQ(3, task_count); + EXPECT_EQ(4, injector->processed_events()); + MessageLoop::current()->QuitWhenIdle(); +} + +} // namespace + +TEST_F(MessagePumpGLibTest, TestGLibLoop) { + // Tests that events and posted tasks are correctly executed if the message + // loop is not run by MessageLoop::Run() but by a straight GLib loop. + // Note that in this case we don't make strong guarantees about niceness + // between events and posted tasks. + loop()->PostTask( + FROM_HERE, + Bind(&TestGLibLoopInternal, Unretained(injector()))); + loop()->Run(); +} + +TEST_F(MessagePumpGLibTest, TestGtkLoop) { + // Tests that events and posted tasks are correctly executed if the message + // loop is not run by MessageLoop::Run() but by a straight Gtk loop. + // Note that in this case we don't make strong guarantees about niceness + // between events and posted tasks. + loop()->PostTask( + FROM_HERE, + Bind(&TestGtkLoopInternal, Unretained(injector()))); + loop()->Run(); +} + +} // namespace base diff --git a/base/message_loop/message_pump_gtk.cc b/base/message_loop/message_pump_gtk.cc new file mode 100644 index 0000000000..ad6511318a --- /dev/null +++ b/base/message_loop/message_pump_gtk.cc @@ -0,0 +1,114 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/message_loop/message_pump_gtk.h" + +#include +#include + +#include "base/debug/trace_event.h" +#include "base/profiler/scoped_profile.h" + +namespace base { + +namespace { + +const char* EventToTypeString(const GdkEvent* event) { + switch (event->type) { + case GDK_NOTHING: return "GDK_NOTHING"; + case GDK_DELETE: return "GDK_DELETE"; + case GDK_DESTROY: return "GDK_DESTROY"; + case GDK_EXPOSE: return "GDK_EXPOSE"; + case GDK_MOTION_NOTIFY: return "GDK_MOTION_NOTIFY"; + case GDK_BUTTON_PRESS: return "GDK_BUTTON_PRESS"; + case GDK_2BUTTON_PRESS: return "GDK_2BUTTON_PRESS"; + case GDK_3BUTTON_PRESS: return "GDK_3BUTTON_PRESS"; + case GDK_BUTTON_RELEASE: return "GDK_BUTTON_RELEASE"; + case GDK_KEY_PRESS: return "GDK_KEY_PRESS"; + case GDK_KEY_RELEASE: return "GDK_KEY_RELEASE"; + case GDK_ENTER_NOTIFY: return "GDK_ENTER_NOTIFY"; + case GDK_LEAVE_NOTIFY: return "GDK_LEAVE_NOTIFY"; + case GDK_FOCUS_CHANGE: return "GDK_FOCUS_CHANGE"; + case GDK_CONFIGURE: return "GDK_CONFIGURE"; + case GDK_MAP: return "GDK_MAP"; + case GDK_UNMAP: return "GDK_UNMAP"; + case GDK_PROPERTY_NOTIFY: return "GDK_PROPERTY_NOTIFY"; + case GDK_SELECTION_CLEAR: return "GDK_SELECTION_CLEAR"; + case GDK_SELECTION_REQUEST: return "GDK_SELECTION_REQUEST"; + case GDK_SELECTION_NOTIFY: return "GDK_SELECTION_NOTIFY"; + case GDK_PROXIMITY_IN: return "GDK_PROXIMITY_IN"; + case GDK_PROXIMITY_OUT: return "GDK_PROXIMITY_OUT"; + case GDK_DRAG_ENTER: return "GDK_DRAG_ENTER"; + case GDK_DRAG_LEAVE: return "GDK_DRAG_LEAVE"; + case GDK_DRAG_MOTION: return "GDK_DRAG_MOTION"; + case GDK_DRAG_STATUS: return "GDK_DRAG_STATUS"; + case GDK_DROP_START: return "GDK_DROP_START"; + case GDK_DROP_FINISHED: return "GDK_DROP_FINISHED"; + case GDK_CLIENT_EVENT: return "GDK_CLIENT_EVENT"; + case GDK_VISIBILITY_NOTIFY: return "GDK_VISIBILITY_NOTIFY"; + case GDK_NO_EXPOSE: return "GDK_NO_EXPOSE"; + case GDK_SCROLL: return "GDK_SCROLL"; + case GDK_WINDOW_STATE: return "GDK_WINDOW_STATE"; + case GDK_SETTING: return "GDK_SETTING"; + case GDK_OWNER_CHANGE: return "GDK_OWNER_CHANGE"; + case GDK_GRAB_BROKEN: return "GDK_GRAB_BROKEN"; + case GDK_DAMAGE: return "GDK_DAMAGE"; + default: + return "Unknown Gdk Event"; + } +} + +} // namespace + +MessagePumpGtk::MessagePumpGtk() : MessagePumpGlib() { + gdk_event_handler_set(&EventDispatcher, this, NULL); +} + +MessagePumpGtk::~MessagePumpGtk() { + gdk_event_handler_set(reinterpret_cast(gtk_main_do_event), + this, NULL); +} + +void MessagePumpGtk::DispatchEvents(GdkEvent* event) { + UNSHIPPED_TRACE_EVENT1("task", "MessagePumpGtk::DispatchEvents", + "type", EventToTypeString(event)); + + WillProcessEvent(event); + + MessagePumpDispatcher* dispatcher = GetDispatcher(); + if (!dispatcher) + gtk_main_do_event(event); + else if (!dispatcher->Dispatch(event)) + Quit(); + + DidProcessEvent(event); +} + +// static +Display* MessagePumpGtk::GetDefaultXDisplay() { + static GdkDisplay* display = gdk_display_get_default(); + if (!display) { + // GTK / GDK has not been initialized, which is a decision we wish to + // support, for example for the GPU process. + static Display* xdisplay = XOpenDisplay(NULL); + return xdisplay; + } + return GDK_DISPLAY_XDISPLAY(display); +} + +void MessagePumpGtk::WillProcessEvent(GdkEvent* event) { + FOR_EACH_OBSERVER(MessagePumpObserver, observers(), WillProcessEvent(event)); +} + +void MessagePumpGtk::DidProcessEvent(GdkEvent* event) { + FOR_EACH_OBSERVER(MessagePumpObserver, observers(), DidProcessEvent(event)); +} + +// static +void MessagePumpGtk::EventDispatcher(GdkEvent* event, gpointer data) { + MessagePumpGtk* message_pump = reinterpret_cast(data); + message_pump->DispatchEvents(event); +} + +} // namespace base diff --git a/base/message_loop/message_pump_gtk.h b/base/message_loop/message_pump_gtk.h new file mode 100644 index 0000000000..947ab885af --- /dev/null +++ b/base/message_loop/message_pump_gtk.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_MESSAGE_LOOP_MESSAGE_PUMP_GTK_H_ +#define BASE_MESSAGE_LOOP_MESSAGE_PUMP_GTK_H_ + +#include "base/message_loop/message_pump_glib.h" + +typedef union _GdkEvent GdkEvent; +typedef struct _XDisplay Display; + +namespace base { + +// The documentation for this class is in message_pump_glib.h +class MessagePumpObserver { + public: + // This method is called before processing a message. + virtual void WillProcessEvent(GdkEvent* event) = 0; + + // This method is called after processing a message. + virtual void DidProcessEvent(GdkEvent* event) = 0; + + protected: + virtual ~MessagePumpObserver() {} +}; + +// The documentation for this class is in message_pump_glib.h +// +// The nested loop is exited by either posting a quit, or returning false +// from Dispatch. +class MessagePumpDispatcher { + public: + // Dispatches the event. If true is returned processing continues as + // normal. If false is returned, the nested loop exits immediately. + virtual bool Dispatch(GdkEvent* event) = 0; + + protected: + virtual ~MessagePumpDispatcher() {} +}; + +// This class implements a message-pump for dispatching GTK events. +class BASE_EXPORT MessagePumpGtk : public MessagePumpGlib { + public: + MessagePumpGtk(); + virtual ~MessagePumpGtk(); + + // Dispatch an available GdkEvent. Essentially this allows a subclass to do + // some task before/after calling the default handler (EventDispatcher). + void DispatchEvents(GdkEvent* event); + + // Returns default X Display. + static Display* GetDefaultXDisplay(); + + private: + // Invoked from EventDispatcher. Notifies all observers we're about to + // process an event. + void WillProcessEvent(GdkEvent* event); + + // Invoked from EventDispatcher. Notifies all observers we processed an + // event. + void DidProcessEvent(GdkEvent* event); + + // Callback prior to gdk dispatching an event. + static void EventDispatcher(GdkEvent* event, void* data); + + DISALLOW_COPY_AND_ASSIGN(MessagePumpGtk); +}; + +typedef MessagePumpGtk MessagePumpForUI; + +} // namespace base + +#endif // BASE_MESSAGE_LOOP_MESSAGE_PUMP_GTK_H_ diff --git a/base/message_loop/message_pump_io_ios.cc b/base/message_loop/message_pump_io_ios.cc new file mode 100644 index 0000000000..cd5ffed4b9 --- /dev/null +++ b/base/message_loop/message_pump_io_ios.cc @@ -0,0 +1,209 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/message_loop/message_pump_io_ios.h" + +namespace base { + +MessagePumpIOSForIO::FileDescriptorWatcher::FileDescriptorWatcher() + : is_persistent_(false), + fdref_(NULL), + callback_types_(0), + fd_source_(NULL), + watcher_(NULL) { +} + +MessagePumpIOSForIO::FileDescriptorWatcher::~FileDescriptorWatcher() { + StopWatchingFileDescriptor(); +} + +bool MessagePumpIOSForIO::FileDescriptorWatcher::StopWatchingFileDescriptor() { + if (fdref_ == NULL) + return true; + + CFFileDescriptorDisableCallBacks(fdref_, callback_types_); + if (pump_) + pump_->RemoveRunLoopSource(fd_source_); + fd_source_.reset(); + fdref_.reset(); + callback_types_ = 0; + pump_.reset(); + watcher_ = NULL; + return true; +} + +void MessagePumpIOSForIO::FileDescriptorWatcher::Init( + CFFileDescriptorRef fdref, + CFOptionFlags callback_types, + CFRunLoopSourceRef fd_source, + bool is_persistent) { + DCHECK(fdref); + DCHECK(!fdref_); + + is_persistent_ = is_persistent; + fdref_.reset(fdref); + callback_types_ = callback_types; + fd_source_.reset(fd_source); +} + +void MessagePumpIOSForIO::FileDescriptorWatcher::OnFileCanReadWithoutBlocking( + int fd, + MessagePumpIOSForIO* pump) { + DCHECK(callback_types_ & kCFFileDescriptorReadCallBack); + pump->WillProcessIOEvent(); + watcher_->OnFileCanReadWithoutBlocking(fd); + pump->DidProcessIOEvent(); +} + +void MessagePumpIOSForIO::FileDescriptorWatcher::OnFileCanWriteWithoutBlocking( + int fd, + MessagePumpIOSForIO* pump) { + DCHECK(callback_types_ & kCFFileDescriptorWriteCallBack); + pump->WillProcessIOEvent(); + watcher_->OnFileCanWriteWithoutBlocking(fd); + pump->DidProcessIOEvent(); +} + +MessagePumpIOSForIO::MessagePumpIOSForIO() : weak_factory_(this) { +} + +MessagePumpIOSForIO::~MessagePumpIOSForIO() { +} + +bool MessagePumpIOSForIO::WatchFileDescriptor( + int fd, + bool persistent, + int mode, + FileDescriptorWatcher *controller, + Watcher *delegate) { + DCHECK_GE(fd, 0); + DCHECK(controller); + DCHECK(delegate); + DCHECK(mode == WATCH_READ || mode == WATCH_WRITE || mode == WATCH_READ_WRITE); + + // WatchFileDescriptor should be called on the pump thread. It is not + // threadsafe, and your watcher may never be registered. + DCHECK(watch_file_descriptor_caller_checker_.CalledOnValidThread()); + + CFFileDescriptorContext source_context = {0}; + source_context.info = controller; + + CFOptionFlags callback_types = 0; + if (mode & WATCH_READ) { + callback_types |= kCFFileDescriptorReadCallBack; + } + if (mode & WATCH_WRITE) { + callback_types |= kCFFileDescriptorWriteCallBack; + } + + CFFileDescriptorRef fdref = controller->fdref_; + if (fdref == NULL) { + base::ScopedCFTypeRef scoped_fdref( + CFFileDescriptorCreate( + kCFAllocatorDefault, fd, false, HandleFdIOEvent, &source_context)); + if (scoped_fdref == NULL) { + NOTREACHED() << "CFFileDescriptorCreate failed"; + return false; + } + + CFFileDescriptorEnableCallBacks(scoped_fdref, callback_types); + + // TODO(wtc): what should the 'order' argument be? + base::ScopedCFTypeRef scoped_fd_source( + CFFileDescriptorCreateRunLoopSource( + kCFAllocatorDefault, scoped_fdref, 0)); + if (scoped_fd_source == NULL) { + NOTREACHED() << "CFFileDescriptorCreateRunLoopSource failed"; + return false; + } + CFRunLoopAddSource(run_loop(), scoped_fd_source, kCFRunLoopCommonModes); + + // Transfer ownership of scoped_fdref and fd_source to controller. + controller->Init(scoped_fdref.release(), callback_types, + scoped_fd_source.release(), persistent); + } else { + // It's illegal to use this function to listen on 2 separate fds with the + // same |controller|. + if (CFFileDescriptorGetNativeDescriptor(fdref) != fd) { + NOTREACHED() << "FDs don't match: " + << CFFileDescriptorGetNativeDescriptor(fdref) + << " != " << fd; + return false; + } + if (persistent != controller->is_persistent_) { + NOTREACHED() << "persistent doesn't match"; + return false; + } + + // Combine old/new event masks. + CFFileDescriptorDisableCallBacks(fdref, controller->callback_types_); + controller->callback_types_ |= callback_types; + CFFileDescriptorEnableCallBacks(fdref, controller->callback_types_); + } + + controller->set_watcher(delegate); + controller->set_pump(weak_factory_.GetWeakPtr()); + + return true; +} + +void MessagePumpIOSForIO::RemoveRunLoopSource(CFRunLoopSourceRef source) { + CFRunLoopRemoveSource(run_loop(), source, kCFRunLoopCommonModes); +} + +void MessagePumpIOSForIO::AddIOObserver(IOObserver *obs) { + io_observers_.AddObserver(obs); +} + +void MessagePumpIOSForIO::RemoveIOObserver(IOObserver *obs) { + io_observers_.RemoveObserver(obs); +} + +void MessagePumpIOSForIO::WillProcessIOEvent() { + FOR_EACH_OBSERVER(IOObserver, io_observers_, WillProcessIOEvent()); +} + +void MessagePumpIOSForIO::DidProcessIOEvent() { + FOR_EACH_OBSERVER(IOObserver, io_observers_, DidProcessIOEvent()); +} + +// static +void MessagePumpIOSForIO::HandleFdIOEvent(CFFileDescriptorRef fdref, + CFOptionFlags callback_types, + void* context) { + FileDescriptorWatcher* controller = + static_cast(context); + DCHECK_EQ(fdref, controller->fdref_); + + // Ensure that |fdref| will remain live for the duration of this function + // call even if |controller| is deleted or |StopWatchingFileDescriptor()| is + // called, either of which will cause |fdref| to be released. + ScopedCFTypeRef scoped_fdref( + fdref, base::scoped_policy::RETAIN); + + int fd = CFFileDescriptorGetNativeDescriptor(fdref); + MessagePumpIOSForIO* pump = controller->pump().get(); + DCHECK(pump); + if (callback_types & kCFFileDescriptorWriteCallBack) + controller->OnFileCanWriteWithoutBlocking(fd, pump); + + // Perform the read callback only if the file descriptor has not been + // invalidated in the write callback. As |FileDescriptorWatcher| invalidates + // its file descriptor on destruction, the file descriptor being valid also + // guarantees that |controller| has not been deleted. + if (callback_types & kCFFileDescriptorReadCallBack && + CFFileDescriptorIsValid(fdref)) { + DCHECK_EQ(fdref, controller->fdref_); + controller->OnFileCanReadWithoutBlocking(fd, pump); + } + + // Re-enable callbacks after the read/write if the file descriptor is still + // valid and the controller is persistent. + if (CFFileDescriptorIsValid(fdref) && controller->is_persistent_) { + DCHECK_EQ(fdref, controller->fdref_); + CFFileDescriptorEnableCallBacks(fdref, callback_types); + } +} + +} // namespace base diff --git a/base/message_loop/message_pump_io_ios.h b/base/message_loop/message_pump_io_ios.h new file mode 100644 index 0000000000..18af4a8fe3 --- /dev/null +++ b/base/message_loop/message_pump_io_ios.h @@ -0,0 +1,144 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MESSAGE_LOOP_MESSAGE_PUMP_IO_IOS_H_ +#define BASE_MESSAGE_LOOP_MESSAGE_PUMP_IO_IOS_H_ + +#include "base/base_export.h" +#include "base/mac/scoped_cffiledescriptorref.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_pump_mac.h" +#include "base/observer_list.h" +#include "base/threading/thread_checker.h" + +namespace base { + +// This file introduces a class to monitor sockets and issue callbacks when +// sockets are ready for I/O on iOS. +class BASE_EXPORT MessagePumpIOSForIO : public MessagePumpNSRunLoop { + public: + class IOObserver { + public: + IOObserver() {} + + // An IOObserver is an object that receives IO notifications from the + // MessagePump. + // + // NOTE: An IOObserver implementation should be extremely fast! + virtual void WillProcessIOEvent() = 0; + virtual void DidProcessIOEvent() = 0; + + protected: + virtual ~IOObserver() {} + }; + + // Used with WatchFileDescriptor to asynchronously monitor the I/O readiness + // of a file descriptor. + class Watcher { + public: + // Called from MessageLoop::Run when an FD can be read from/written to + // without blocking + virtual void OnFileCanReadWithoutBlocking(int fd) = 0; + virtual void OnFileCanWriteWithoutBlocking(int fd) = 0; + + protected: + virtual ~Watcher() {} + }; + + // Object returned by WatchFileDescriptor to manage further watching. + class FileDescriptorWatcher { + public: + FileDescriptorWatcher(); + ~FileDescriptorWatcher(); // Implicitly calls StopWatchingFileDescriptor. + + // NOTE: These methods aren't called StartWatching()/StopWatching() to + // avoid confusion with the win32 ObjectWatcher class. + + // Stop watching the FD, always safe to call. No-op if there's nothing + // to do. + bool StopWatchingFileDescriptor(); + + private: + friend class MessagePumpIOSForIO; + friend class MessagePumpIOSForIOTest; + + // Called by MessagePumpIOSForIO, ownership of |fdref| and |fd_source| + // is transferred to this object. + void Init(CFFileDescriptorRef fdref, + CFOptionFlags callback_types, + CFRunLoopSourceRef fd_source, + bool is_persistent); + + void set_pump(base::WeakPtr pump) { pump_ = pump; } + const base::WeakPtr& pump() const { return pump_; } + + void set_watcher(Watcher* watcher) { watcher_ = watcher; } + + void OnFileCanReadWithoutBlocking(int fd, MessagePumpIOSForIO* pump); + void OnFileCanWriteWithoutBlocking(int fd, MessagePumpIOSForIO* pump); + + bool is_persistent_; // false if this event is one-shot. + base::mac::ScopedCFFileDescriptorRef fdref_; + CFOptionFlags callback_types_; + base::ScopedCFTypeRef fd_source_; + base::WeakPtr pump_; + Watcher* watcher_; + + DISALLOW_COPY_AND_ASSIGN(FileDescriptorWatcher); + }; + + enum Mode { + WATCH_READ = 1 << 0, + WATCH_WRITE = 1 << 1, + WATCH_READ_WRITE = WATCH_READ | WATCH_WRITE + }; + + MessagePumpIOSForIO(); + virtual ~MessagePumpIOSForIO(); + + // Have the current thread's message loop watch for a a situation in which + // reading/writing to the FD can be performed without blocking. + // Callers must provide a preallocated FileDescriptorWatcher object which + // can later be used to manage the lifetime of this event. + // If a FileDescriptorWatcher is passed in which is already attached to + // an event, then the effect is cumulative i.e. after the call |controller| + // will watch both the previous event and the new one. + // If an error occurs while calling this method in a cumulative fashion, the + // event previously attached to |controller| is aborted. + // Returns true on success. + // Must be called on the same thread the message_pump is running on. + bool WatchFileDescriptor(int fd, + bool persistent, + int mode, + FileDescriptorWatcher *controller, + Watcher *delegate); + + void RemoveRunLoopSource(CFRunLoopSourceRef source); + + void AddIOObserver(IOObserver* obs); + void RemoveIOObserver(IOObserver* obs); + + private: + friend class MessagePumpIOSForIOTest; + + void WillProcessIOEvent(); + void DidProcessIOEvent(); + + static void HandleFdIOEvent(CFFileDescriptorRef fdref, + CFOptionFlags callback_types, + void* context); + + ObserverList io_observers_; + ThreadChecker watch_file_descriptor_caller_checker_; + + base::WeakPtrFactory weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(MessagePumpIOSForIO); +}; + +} // namespace base + +#endif // BASE_MESSAGE_LOOP_MESSAGE_PUMP_IO_IOS_H_ diff --git a/base/message_loop/message_pump_io_ios_unittest.cc b/base/message_loop/message_pump_io_ios_unittest.cc new file mode 100644 index 0000000000..9c7a8fbf65 --- /dev/null +++ b/base/message_loop/message_pump_io_ios_unittest.cc @@ -0,0 +1,188 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/message_loop/message_pump_io_ios.h" + +#include + +#include "base/message_loop/message_loop.h" +#include "base/posix/eintr_wrapper.h" +#include "base/threading/thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +class MessagePumpIOSForIOTest : public testing::Test { + protected: + MessagePumpIOSForIOTest() + : ui_loop_(MessageLoop::TYPE_UI), + io_thread_("MessagePumpIOSForIOTestIOThread") {} + virtual ~MessagePumpIOSForIOTest() {} + + virtual void SetUp() OVERRIDE { + Thread::Options options(MessageLoop::TYPE_IO, 0); + ASSERT_TRUE(io_thread_.StartWithOptions(options)); + ASSERT_EQ(MessageLoop::TYPE_IO, io_thread_.message_loop()->type()); + int ret = pipe(pipefds_); + ASSERT_EQ(0, ret); + ret = pipe(alternate_pipefds_); + ASSERT_EQ(0, ret); + } + + virtual void TearDown() OVERRIDE { + if (HANDLE_EINTR(close(pipefds_[0])) < 0) + PLOG(ERROR) << "close"; + if (HANDLE_EINTR(close(pipefds_[1])) < 0) + PLOG(ERROR) << "close"; + } + + MessageLoop* ui_loop() { return &ui_loop_; } + MessageLoopForIO* io_loop() const { + return static_cast(io_thread_.message_loop()); + } + + void HandleFdIOEvent(MessageLoopForIO::FileDescriptorWatcher* watcher) { + MessagePumpIOSForIO::HandleFdIOEvent(watcher->fdref_, + kCFFileDescriptorReadCallBack | kCFFileDescriptorWriteCallBack, + watcher); + } + + int pipefds_[2]; + int alternate_pipefds_[2]; + + private: + MessageLoop ui_loop_; + Thread io_thread_; + + DISALLOW_COPY_AND_ASSIGN(MessagePumpIOSForIOTest); +}; + +namespace { + +// Concrete implementation of MessagePumpIOSForIO::Watcher that does +// nothing useful. +class StupidWatcher : public MessagePumpIOSForIO::Watcher { + public: + virtual ~StupidWatcher() {} + + // base:MessagePumpIOSForIO::Watcher interface + virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE {} + virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE {} +}; + +#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG) + +// Test to make sure that we catch calling WatchFileDescriptor off of the +// wrong thread. +TEST_F(MessagePumpIOSForIOTest, TestWatchingFromBadThread) { + MessagePumpIOSForIO::FileDescriptorWatcher watcher; + StupidWatcher delegate; + + ASSERT_DEBUG_DEATH(io_loop()->WatchFileDescriptor( + STDOUT_FILENO, false, MessageLoopForIO::WATCH_READ, &watcher, &delegate), + "Check failed: " + "watch_file_descriptor_caller_checker_.CalledOnValidThread()"); +} + +#endif // GTEST_HAS_DEATH_TEST && !defined(NDEBUG) + +class BaseWatcher : public MessagePumpIOSForIO::Watcher { + public: + BaseWatcher(MessagePumpIOSForIO::FileDescriptorWatcher* controller) + : controller_(controller) { + DCHECK(controller_); + } + virtual ~BaseWatcher() {} + + // MessagePumpIOSForIO::Watcher interface + virtual void OnFileCanReadWithoutBlocking(int /* fd */) OVERRIDE { + NOTREACHED(); + } + + virtual void OnFileCanWriteWithoutBlocking(int /* fd */) OVERRIDE { + NOTREACHED(); + } + + protected: + MessagePumpIOSForIO::FileDescriptorWatcher* controller_; +}; + +class DeleteWatcher : public BaseWatcher { + public: + explicit DeleteWatcher( + MessagePumpIOSForIO::FileDescriptorWatcher* controller) + : BaseWatcher(controller) {} + + virtual ~DeleteWatcher() { + DCHECK(!controller_); + } + + virtual void OnFileCanWriteWithoutBlocking(int /* fd */) OVERRIDE { + DCHECK(controller_); + delete controller_; + controller_ = NULL; + } +}; + +TEST_F(MessagePumpIOSForIOTest, DeleteWatcher) { + scoped_ptr pump(new MessagePumpIOSForIO); + MessagePumpIOSForIO::FileDescriptorWatcher* watcher = + new MessagePumpIOSForIO::FileDescriptorWatcher; + DeleteWatcher delegate(watcher); + pump->WatchFileDescriptor(pipefds_[1], + false, MessagePumpIOSForIO::WATCH_READ_WRITE, watcher, &delegate); + + // Spoof a callback. + HandleFdIOEvent(watcher); +} + +class StopWatcher : public BaseWatcher { + public: + StopWatcher(MessagePumpIOSForIO::FileDescriptorWatcher* controller, + MessagePumpIOSForIO* pump, + int fd_to_start_watching = -1) + : BaseWatcher(controller), + pump_(pump), + fd_to_start_watching_(fd_to_start_watching) {} + + virtual ~StopWatcher() {} + + virtual void OnFileCanWriteWithoutBlocking(int /* fd */) OVERRIDE { + controller_->StopWatchingFileDescriptor(); + if (fd_to_start_watching_ >= 0) { + pump_->WatchFileDescriptor(fd_to_start_watching_, + false, MessagePumpIOSForIO::WATCH_READ_WRITE, controller_, this); + } + } + + private: + MessagePumpIOSForIO* pump_; + int fd_to_start_watching_; +}; + +TEST_F(MessagePumpIOSForIOTest, StopWatcher) { + scoped_ptr pump(new MessagePumpIOSForIO); + MessagePumpIOSForIO::FileDescriptorWatcher watcher; + StopWatcher delegate(&watcher, pump.get()); + pump->WatchFileDescriptor(pipefds_[1], + false, MessagePumpIOSForIO::WATCH_READ_WRITE, &watcher, &delegate); + + // Spoof a callback. + HandleFdIOEvent(&watcher); +} + +TEST_F(MessagePumpIOSForIOTest, StopWatcherAndWatchSomethingElse) { + scoped_ptr pump(new MessagePumpIOSForIO); + MessagePumpIOSForIO::FileDescriptorWatcher watcher; + StopWatcher delegate(&watcher, pump.get(), alternate_pipefds_[1]); + pump->WatchFileDescriptor(pipefds_[1], + false, MessagePumpIOSForIO::WATCH_READ_WRITE, &watcher, &delegate); + + // Spoof a callback. + HandleFdIOEvent(&watcher); +} + +} // namespace + +} // namespace base diff --git a/base/message_loop/message_pump_libevent.cc b/base/message_loop/message_pump_libevent.cc new file mode 100644 index 0000000000..6d862d1d26 --- /dev/null +++ b/base/message_loop/message_pump_libevent.cc @@ -0,0 +1,375 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/message_loop/message_pump_libevent.h" + +#include +#include +#include + +#include "base/auto_reset.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/observer_list.h" +#include "base/posix/eintr_wrapper.h" +#include "base/time/time.h" +#include "third_party/libevent/event.h" + +#if defined(OS_MACOSX) +#include "base/mac/scoped_nsautorelease_pool.h" +#endif + +// Lifecycle of struct event +// Libevent uses two main data structures: +// struct event_base (of which there is one per message pump), and +// struct event (of which there is roughly one per socket). +// The socket's struct event is created in +// MessagePumpLibevent::WatchFileDescriptor(), +// is owned by the FileDescriptorWatcher, and is destroyed in +// StopWatchingFileDescriptor(). +// It is moved into and out of lists in struct event_base by +// the libevent functions event_add() and event_del(). +// +// TODO(dkegel): +// At the moment bad things happen if a FileDescriptorWatcher +// is active after its MessagePumpLibevent has been destroyed. +// See MessageLoopTest.FileDescriptorWatcherOutlivesMessageLoop +// Not clear yet whether that situation occurs in practice, +// but if it does, we need to fix it. + +namespace base { + +// Return 0 on success +// Too small a function to bother putting in a library? +static int SetNonBlocking(int fd) { + int flags = fcntl(fd, F_GETFL, 0); + if (flags == -1) + flags = 0; + return fcntl(fd, F_SETFL, flags | O_NONBLOCK); +} + +MessagePumpLibevent::FileDescriptorWatcher::FileDescriptorWatcher() + : event_(NULL), + pump_(NULL), + watcher_(NULL), + weak_factory_(this) { +} + +MessagePumpLibevent::FileDescriptorWatcher::~FileDescriptorWatcher() { + if (event_) { + StopWatchingFileDescriptor(); + } +} + +bool MessagePumpLibevent::FileDescriptorWatcher::StopWatchingFileDescriptor() { + event* e = ReleaseEvent(); + if (e == NULL) + return true; + + // event_del() is a no-op if the event isn't active. + int rv = event_del(e); + delete e; + pump_ = NULL; + watcher_ = NULL; + return (rv == 0); +} + +void MessagePumpLibevent::FileDescriptorWatcher::Init(event *e) { + DCHECK(e); + DCHECK(!event_); + + event_ = e; +} + +event *MessagePumpLibevent::FileDescriptorWatcher::ReleaseEvent() { + struct event *e = event_; + event_ = NULL; + return e; +} + +void MessagePumpLibevent::FileDescriptorWatcher::OnFileCanReadWithoutBlocking( + int fd, MessagePumpLibevent* pump) { + // Since OnFileCanWriteWithoutBlocking() gets called first, it can stop + // watching the file descriptor. + if (!watcher_) + return; + pump->WillProcessIOEvent(); + watcher_->OnFileCanReadWithoutBlocking(fd); + pump->DidProcessIOEvent(); +} + +void MessagePumpLibevent::FileDescriptorWatcher::OnFileCanWriteWithoutBlocking( + int fd, MessagePumpLibevent* pump) { + DCHECK(watcher_); + pump->WillProcessIOEvent(); + watcher_->OnFileCanWriteWithoutBlocking(fd); + pump->DidProcessIOEvent(); +} + +MessagePumpLibevent::MessagePumpLibevent() + : keep_running_(true), + in_run_(false), + processed_io_events_(false), + event_base_(event_base_new()), + wakeup_pipe_in_(-1), + wakeup_pipe_out_(-1) { + if (!Init()) + NOTREACHED(); +} + +MessagePumpLibevent::~MessagePumpLibevent() { + DCHECK(wakeup_event_); + DCHECK(event_base_); + event_del(wakeup_event_); + delete wakeup_event_; + if (wakeup_pipe_in_ >= 0) { + if (HANDLE_EINTR(close(wakeup_pipe_in_)) < 0) + DPLOG(ERROR) << "close"; + } + if (wakeup_pipe_out_ >= 0) { + if (HANDLE_EINTR(close(wakeup_pipe_out_)) < 0) + DPLOG(ERROR) << "close"; + } + event_base_free(event_base_); +} + +bool MessagePumpLibevent::WatchFileDescriptor(int fd, + bool persistent, + int mode, + FileDescriptorWatcher *controller, + Watcher *delegate) { + DCHECK_GE(fd, 0); + DCHECK(controller); + DCHECK(delegate); + DCHECK(mode == WATCH_READ || mode == WATCH_WRITE || mode == WATCH_READ_WRITE); + // WatchFileDescriptor should be called on the pump thread. It is not + // threadsafe, and your watcher may never be registered. + DCHECK(watch_file_descriptor_caller_checker_.CalledOnValidThread()); + + int event_mask = persistent ? EV_PERSIST : 0; + if (mode & WATCH_READ) { + event_mask |= EV_READ; + } + if (mode & WATCH_WRITE) { + event_mask |= EV_WRITE; + } + + scoped_ptr evt(controller->ReleaseEvent()); + if (evt.get() == NULL) { + // Ownership is transferred to the controller. + evt.reset(new event); + } else { + // Make sure we don't pick up any funky internal libevent masks. + int old_interest_mask = evt.get()->ev_events & + (EV_READ | EV_WRITE | EV_PERSIST); + + // Combine old/new event masks. + event_mask |= old_interest_mask; + + // Must disarm the event before we can reuse it. + event_del(evt.get()); + + // It's illegal to use this function to listen on 2 separate fds with the + // same |controller|. + if (EVENT_FD(evt.get()) != fd) { + NOTREACHED() << "FDs don't match" << EVENT_FD(evt.get()) << "!=" << fd; + return false; + } + } + + // Set current interest mask and message pump for this event. + event_set(evt.get(), fd, event_mask, OnLibeventNotification, controller); + + // Tell libevent which message pump this socket will belong to when we add it. + if (event_base_set(event_base_, evt.get())) { + return false; + } + + // Add this socket to the list of monitored sockets. + if (event_add(evt.get(), NULL)) { + return false; + } + + // Transfer ownership of evt to controller. + controller->Init(evt.release()); + + controller->set_watcher(delegate); + controller->set_pump(this); + + return true; +} + +void MessagePumpLibevent::AddIOObserver(IOObserver *obs) { + io_observers_.AddObserver(obs); +} + +void MessagePumpLibevent::RemoveIOObserver(IOObserver *obs) { + io_observers_.RemoveObserver(obs); +} + +// Tell libevent to break out of inner loop. +static void timer_callback(int fd, short events, void *context) +{ + event_base_loopbreak((struct event_base *)context); +} + +// Reentrant! +void MessagePumpLibevent::Run(Delegate* delegate) { + DCHECK(keep_running_) << "Quit must have been called outside of Run!"; + AutoReset auto_reset_in_run(&in_run_, true); + + // event_base_loopexit() + EVLOOP_ONCE is leaky, see http://crbug.com/25641. + // Instead, make our own timer and reuse it on each call to event_base_loop(). + scoped_ptr timer_event(new event); + + for (;;) { +#if defined(OS_MACOSX) + mac::ScopedNSAutoreleasePool autorelease_pool; +#endif + + bool did_work = delegate->DoWork(); + if (!keep_running_) + break; + + event_base_loop(event_base_, EVLOOP_NONBLOCK); + did_work |= processed_io_events_; + processed_io_events_ = false; + if (!keep_running_) + break; + + did_work |= delegate->DoDelayedWork(&delayed_work_time_); + if (!keep_running_) + break; + + if (did_work) + continue; + + did_work = delegate->DoIdleWork(); + if (!keep_running_) + break; + + if (did_work) + continue; + + // EVLOOP_ONCE tells libevent to only block once, + // but to service all pending events when it wakes up. + if (delayed_work_time_.is_null()) { + event_base_loop(event_base_, EVLOOP_ONCE); + } else { + TimeDelta delay = delayed_work_time_ - TimeTicks::Now(); + if (delay > TimeDelta()) { + struct timeval poll_tv; + poll_tv.tv_sec = delay.InSeconds(); + poll_tv.tv_usec = delay.InMicroseconds() % Time::kMicrosecondsPerSecond; + event_set(timer_event.get(), -1, 0, timer_callback, event_base_); + event_base_set(event_base_, timer_event.get()); + event_add(timer_event.get(), &poll_tv); + event_base_loop(event_base_, EVLOOP_ONCE); + event_del(timer_event.get()); + } else { + // It looks like delayed_work_time_ indicates a time in the past, so we + // need to call DoDelayedWork now. + delayed_work_time_ = TimeTicks(); + } + } + } + + keep_running_ = true; +} + +void MessagePumpLibevent::Quit() { + DCHECK(in_run_); + // Tell both libevent and Run that they should break out of their loops. + keep_running_ = false; + ScheduleWork(); +} + +void MessagePumpLibevent::ScheduleWork() { + // Tell libevent (in a threadsafe way) that it should break out of its loop. + char buf = 0; + int nwrite = HANDLE_EINTR(write(wakeup_pipe_in_, &buf, 1)); + DCHECK(nwrite == 1 || errno == EAGAIN) + << "[nwrite:" << nwrite << "] [errno:" << errno << "]"; +} + +void MessagePumpLibevent::ScheduleDelayedWork( + const TimeTicks& delayed_work_time) { + // We know that we can't be blocked on Wait right now since this method can + // only be called on the same thread as Run, so we only need to update our + // record of how long to sleep when we do sleep. + delayed_work_time_ = delayed_work_time; +} + +void MessagePumpLibevent::WillProcessIOEvent() { + FOR_EACH_OBSERVER(IOObserver, io_observers_, WillProcessIOEvent()); +} + +void MessagePumpLibevent::DidProcessIOEvent() { + FOR_EACH_OBSERVER(IOObserver, io_observers_, DidProcessIOEvent()); +} + +bool MessagePumpLibevent::Init() { + int fds[2]; + if (pipe(fds)) { + DLOG(ERROR) << "pipe() failed, errno: " << errno; + return false; + } + if (SetNonBlocking(fds[0])) { + DLOG(ERROR) << "SetNonBlocking for pipe fd[0] failed, errno: " << errno; + return false; + } + if (SetNonBlocking(fds[1])) { + DLOG(ERROR) << "SetNonBlocking for pipe fd[1] failed, errno: " << errno; + return false; + } + wakeup_pipe_out_ = fds[0]; + wakeup_pipe_in_ = fds[1]; + + wakeup_event_ = new event; + event_set(wakeup_event_, wakeup_pipe_out_, EV_READ | EV_PERSIST, + OnWakeup, this); + event_base_set(event_base_, wakeup_event_); + + if (event_add(wakeup_event_, 0)) + return false; + return true; +} + +// static +void MessagePumpLibevent::OnLibeventNotification(int fd, short flags, + void* context) { + WeakPtr controller = + static_cast(context)->weak_factory_.GetWeakPtr(); + DCHECK(controller.get()); + + MessagePumpLibevent* pump = controller->pump(); + pump->processed_io_events_ = true; + + if (flags & EV_WRITE) { + controller->OnFileCanWriteWithoutBlocking(fd, pump); + } + // Check |controller| in case it's been deleted in + // controller->OnFileCanWriteWithoutBlocking(). + if (controller.get() && flags & EV_READ) { + controller->OnFileCanReadWithoutBlocking(fd, pump); + } +} + +// Called if a byte is received on the wakeup pipe. +// static +void MessagePumpLibevent::OnWakeup(int socket, short flags, void* context) { + MessagePumpLibevent* that = static_cast(context); + DCHECK(that->wakeup_pipe_out_ == socket); + + // Remove and discard the wakeup byte. + char buf; + int nread = HANDLE_EINTR(read(socket, &buf, 1)); + DCHECK_EQ(nread, 1); + that->processed_io_events_ = true; + // Tell libevent to break out of inner loop. + event_base_loopbreak(that->event_base_); +} + +} // namespace base diff --git a/base/message_loop/message_pump_libevent.h b/base/message_loop/message_pump_libevent.h new file mode 100644 index 0000000000..f3a48a9f62 --- /dev/null +++ b/base/message_loop/message_pump_libevent.h @@ -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. + +#ifndef BASE_MESSAGE_LOOP_MESSAGE_PUMP_LIBEVENT_H_ +#define BASE_MESSAGE_LOOP_MESSAGE_PUMP_LIBEVENT_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_pump.h" +#include "base/observer_list.h" +#include "base/threading/thread_checker.h" +#include "base/time/time.h" + +// Declare structs we need from libevent.h rather than including it +struct event_base; +struct event; + +namespace base { + +// Class to monitor sockets and issue callbacks when sockets are ready for I/O +// TODO(dkegel): add support for background file IO somehow +class BASE_EXPORT MessagePumpLibevent : public MessagePump { + public: + class IOObserver { + public: + IOObserver() {} + + // An IOObserver is an object that receives IO notifications from the + // MessagePump. + // + // NOTE: An IOObserver implementation should be extremely fast! + virtual void WillProcessIOEvent() = 0; + virtual void DidProcessIOEvent() = 0; + + protected: + virtual ~IOObserver() {} + }; + + // Used with WatchFileDescriptor to asynchronously monitor the I/O readiness + // of a file descriptor. + class Watcher { + public: + // Called from MessageLoop::Run when an FD can be read from/written to + // without blocking + virtual void OnFileCanReadWithoutBlocking(int fd) = 0; + virtual void OnFileCanWriteWithoutBlocking(int fd) = 0; + + protected: + virtual ~Watcher() {} + }; + + // Object returned by WatchFileDescriptor to manage further watching. + class FileDescriptorWatcher { + public: + FileDescriptorWatcher(); + ~FileDescriptorWatcher(); // Implicitly calls StopWatchingFileDescriptor. + + // NOTE: These methods aren't called StartWatching()/StopWatching() to + // avoid confusion with the win32 ObjectWatcher class. + + // Stop watching the FD, always safe to call. No-op if there's nothing + // to do. + bool StopWatchingFileDescriptor(); + + private: + friend class MessagePumpLibevent; + friend class MessagePumpLibeventTest; + + // Called by MessagePumpLibevent, ownership of |e| is transferred to this + // object. + void Init(event* e); + + // Used by MessagePumpLibevent to take ownership of event_. + event* ReleaseEvent(); + + void set_pump(MessagePumpLibevent* pump) { pump_ = pump; } + MessagePumpLibevent* pump() const { return pump_; } + + void set_watcher(Watcher* watcher) { watcher_ = watcher; } + + void OnFileCanReadWithoutBlocking(int fd, MessagePumpLibevent* pump); + void OnFileCanWriteWithoutBlocking(int fd, MessagePumpLibevent* pump); + + event* event_; + MessagePumpLibevent* pump_; + Watcher* watcher_; + WeakPtrFactory weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(FileDescriptorWatcher); + }; + + enum Mode { + WATCH_READ = 1 << 0, + WATCH_WRITE = 1 << 1, + WATCH_READ_WRITE = WATCH_READ | WATCH_WRITE + }; + + MessagePumpLibevent(); + virtual ~MessagePumpLibevent(); + + // Have the current thread's message loop watch for a a situation in which + // reading/writing to the FD can be performed without blocking. + // Callers must provide a preallocated FileDescriptorWatcher object which + // can later be used to manage the lifetime of this event. + // If a FileDescriptorWatcher is passed in which is already attached to + // an event, then the effect is cumulative i.e. after the call |controller| + // will watch both the previous event and the new one. + // If an error occurs while calling this method in a cumulative fashion, the + // event previously attached to |controller| is aborted. + // Returns true on success. + // Must be called on the same thread the message_pump is running on. + // TODO(dkegel): switch to edge-triggered readiness notification + bool WatchFileDescriptor(int fd, + bool persistent, + int mode, + FileDescriptorWatcher *controller, + Watcher *delegate); + + void AddIOObserver(IOObserver* obs); + void RemoveIOObserver(IOObserver* obs); + + // MessagePump methods: + virtual void Run(Delegate* delegate) OVERRIDE; + virtual void Quit() OVERRIDE; + virtual void ScheduleWork() OVERRIDE; + virtual void ScheduleDelayedWork(const TimeTicks& delayed_work_time) OVERRIDE; + + private: + friend class MessagePumpLibeventTest; + + void WillProcessIOEvent(); + void DidProcessIOEvent(); + + // Risky part of constructor. Returns true on success. + bool Init(); + + // Called by libevent to tell us a registered FD can be read/written to. + static void OnLibeventNotification(int fd, short flags, + void* context); + + // Unix pipe used to implement ScheduleWork() + // ... callback; called by libevent inside Run() when pipe is ready to read + static void OnWakeup(int socket, short flags, void* context); + + // This flag is set to false when Run should return. + bool keep_running_; + + // This flag is set when inside Run. + bool in_run_; + + // This flag is set if libevent has processed I/O events. + bool processed_io_events_; + + // The time at which we should call DoDelayedWork. + TimeTicks delayed_work_time_; + + // Libevent dispatcher. Watches all sockets registered with it, and sends + // readiness callbacks when a socket is ready for I/O. + event_base* event_base_; + + // ... write end; ScheduleWork() writes a single byte to it + int wakeup_pipe_in_; + // ... read end; OnWakeup reads it and then breaks Run() out of its sleep + int wakeup_pipe_out_; + // ... libevent wrapper for read end + event* wakeup_event_; + + ObserverList io_observers_; + ThreadChecker watch_file_descriptor_caller_checker_; + DISALLOW_COPY_AND_ASSIGN(MessagePumpLibevent); +}; + +} // namespace base + +#endif // BASE_MESSAGE_LOOP_MESSAGE_PUMP_LIBEVENT_H_ diff --git a/base/message_loop/message_pump_libevent_unittest.cc b/base/message_loop/message_pump_libevent_unittest.cc new file mode 100644 index 0000000000..52ca95bebf --- /dev/null +++ b/base/message_loop/message_pump_libevent_unittest.cc @@ -0,0 +1,162 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/message_loop/message_pump_libevent.h" + +#include + +#include "base/message_loop/message_loop.h" +#include "base/posix/eintr_wrapper.h" +#include "base/threading/thread.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/libevent/event.h" + +namespace base { + +class MessagePumpLibeventTest : public testing::Test { + protected: + MessagePumpLibeventTest() + : ui_loop_(MessageLoop::TYPE_UI), + io_thread_("MessagePumpLibeventTestIOThread") {} + virtual ~MessagePumpLibeventTest() {} + + virtual void SetUp() OVERRIDE { + Thread::Options options(MessageLoop::TYPE_IO, 0); + ASSERT_TRUE(io_thread_.StartWithOptions(options)); + ASSERT_EQ(MessageLoop::TYPE_IO, io_thread_.message_loop()->type()); + int ret = pipe(pipefds_); + ASSERT_EQ(0, ret); + } + + virtual void TearDown() OVERRIDE { + if (HANDLE_EINTR(close(pipefds_[0])) < 0) + PLOG(ERROR) << "close"; + if (HANDLE_EINTR(close(pipefds_[1])) < 0) + PLOG(ERROR) << "close"; + } + + MessageLoop* ui_loop() { return &ui_loop_; } + MessageLoopForIO* io_loop() const { + return static_cast(io_thread_.message_loop()); + } + + void OnLibeventNotification( + MessagePumpLibevent* pump, + MessagePumpLibevent::FileDescriptorWatcher* controller) { + pump->OnLibeventNotification(0, EV_WRITE | EV_READ, controller); + } + + int pipefds_[2]; + + private: + MessageLoop ui_loop_; + Thread io_thread_; +}; + +namespace { + +// Concrete implementation of MessagePumpLibevent::Watcher that does +// nothing useful. +class StupidWatcher : public MessagePumpLibevent::Watcher { + public: + virtual ~StupidWatcher() {} + + // base:MessagePumpLibevent::Watcher interface + virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE {} + virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE {} +}; + +#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG) + +// Test to make sure that we catch calling WatchFileDescriptor off of the +// wrong thread. +TEST_F(MessagePumpLibeventTest, TestWatchingFromBadThread) { + MessagePumpLibevent::FileDescriptorWatcher watcher; + StupidWatcher delegate; + + ASSERT_DEATH(io_loop()->WatchFileDescriptor( + STDOUT_FILENO, false, MessageLoopForIO::WATCH_READ, &watcher, &delegate), + "Check failed: " + "watch_file_descriptor_caller_checker_.CalledOnValidThread()"); +} + +#endif // GTEST_HAS_DEATH_TEST && !defined(NDEBUG) + +class BaseWatcher : public MessagePumpLibevent::Watcher { + public: + explicit BaseWatcher(MessagePumpLibevent::FileDescriptorWatcher* controller) + : controller_(controller) { + DCHECK(controller_); + } + virtual ~BaseWatcher() {} + + // base:MessagePumpLibevent::Watcher interface + virtual void OnFileCanReadWithoutBlocking(int /* fd */) OVERRIDE { + NOTREACHED(); + } + + virtual void OnFileCanWriteWithoutBlocking(int /* fd */) OVERRIDE { + NOTREACHED(); + } + + protected: + MessagePumpLibevent::FileDescriptorWatcher* controller_; +}; + +class DeleteWatcher : public BaseWatcher { + public: + explicit DeleteWatcher( + MessagePumpLibevent::FileDescriptorWatcher* controller) + : BaseWatcher(controller) {} + + virtual ~DeleteWatcher() { + DCHECK(!controller_); + } + + virtual void OnFileCanWriteWithoutBlocking(int /* fd */) OVERRIDE { + DCHECK(controller_); + delete controller_; + controller_ = NULL; + } +}; + +TEST_F(MessagePumpLibeventTest, DeleteWatcher) { + scoped_ptr pump(new MessagePumpLibevent); + MessagePumpLibevent::FileDescriptorWatcher* watcher = + new MessagePumpLibevent::FileDescriptorWatcher; + DeleteWatcher delegate(watcher); + pump->WatchFileDescriptor(pipefds_[1], + false, MessagePumpLibevent::WATCH_READ_WRITE, watcher, &delegate); + + // Spoof a libevent notification. + OnLibeventNotification(pump.get(), watcher); +} + +class StopWatcher : public BaseWatcher { + public: + explicit StopWatcher( + MessagePumpLibevent::FileDescriptorWatcher* controller) + : BaseWatcher(controller) {} + + virtual ~StopWatcher() {} + + virtual void OnFileCanWriteWithoutBlocking(int /* fd */) OVERRIDE { + controller_->StopWatchingFileDescriptor(); + } +}; + +TEST_F(MessagePumpLibeventTest, StopWatcher) { + scoped_ptr pump(new MessagePumpLibevent); + MessagePumpLibevent::FileDescriptorWatcher watcher; + StopWatcher delegate(&watcher); + pump->WatchFileDescriptor(pipefds_[1], + false, MessagePumpLibevent::WATCH_READ_WRITE, &watcher, &delegate); + + // Spoof a libevent notification. + OnLibeventNotification(pump.get(), &watcher); +} + +} // namespace + +} // namespace base diff --git a/base/message_loop/message_pump_mac.h b/base/message_loop/message_pump_mac.h new file mode 100644 index 0000000000..748b26591f --- /dev/null +++ b/base/message_loop/message_pump_mac.h @@ -0,0 +1,329 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// The basis for all native run loops on the Mac is the CFRunLoop. It can be +// used directly, it can be used as the driving force behind the similar +// Foundation NSRunLoop, and it can be used to implement higher-level event +// loops such as the NSApplication event loop. +// +// This file introduces a basic CFRunLoop-based implementation of the +// MessagePump interface called CFRunLoopBase. CFRunLoopBase contains all +// of the machinery necessary to dispatch events to a delegate, but does not +// implement the specific run loop. Concrete subclasses must provide their +// own DoRun and Quit implementations. +// +// A concrete subclass that just runs a CFRunLoop loop is provided in +// MessagePumpCFRunLoop. For an NSRunLoop, the similar MessagePumpNSRunLoop +// is provided. +// +// For the application's event loop, an implementation based on AppKit's +// NSApplication event system is provided in MessagePumpNSApplication. +// +// Typically, MessagePumpNSApplication only makes sense on a Cocoa +// application's main thread. If a CFRunLoop-based message pump is needed on +// any other thread, one of the other concrete subclasses is preferrable. +// MessagePumpMac::Create is defined, which returns a new NSApplication-based +// or NSRunLoop-based MessagePump subclass depending on which thread it is +// called on. + +#ifndef BASE_MESSAGE_LOOP_MESSAGE_PUMP_MAC_H_ +#define BASE_MESSAGE_LOOP_MESSAGE_PUMP_MAC_H_ + +#include "base/message_loop/message_pump.h" + +#include "base/basictypes.h" + +#include + +#if !defined(__OBJC__) +class NSAutoreleasePool; +#else // !defined(__OBJC__) +#if defined(OS_IOS) +#import +#else +#import + +// Clients must subclass NSApplication and implement this protocol if they use +// MessagePumpMac. +@protocol CrAppProtocol +// Must return true if -[NSApplication sendEvent:] is currently on the stack. +// See the comment for |CreateAutoreleasePool()| in the cc file for why this is +// necessary. +- (BOOL)isHandlingSendEvent; +@end +#endif // !defined(OS_IOS) +#endif // !defined(__OBJC__) + +namespace base { + +class RunLoop; +class TimeTicks; + +class MessagePumpCFRunLoopBase : public MessagePump { + // Needs access to CreateAutoreleasePool. + friend class MessagePumpScopedAutoreleasePool; + public: + MessagePumpCFRunLoopBase(); + virtual ~MessagePumpCFRunLoopBase(); + + // Subclasses should implement the work they need to do in MessagePump::Run + // in the DoRun method. MessagePumpCFRunLoopBase::Run calls DoRun directly. + // This arrangement is used because MessagePumpCFRunLoopBase needs to set + // up and tear down things before and after the "meat" of DoRun. + virtual void Run(Delegate* delegate) OVERRIDE; + virtual void DoRun(Delegate* delegate) = 0; + + virtual void ScheduleWork() OVERRIDE; + virtual void ScheduleDelayedWork(const TimeTicks& delayed_work_time) OVERRIDE; + + protected: + // Accessors for private data members to be used by subclasses. + CFRunLoopRef run_loop() const { return run_loop_; } + int nesting_level() const { return nesting_level_; } + int run_nesting_level() const { return run_nesting_level_; } + + // Sets this pump's delegate. Signals the appropriate sources if + // |delegateless_work_| is true. |delegate| can be NULL. + void SetDelegate(Delegate* delegate); + + // Return an autorelease pool to wrap around any work being performed. + // In some cases, CreateAutoreleasePool may return nil intentionally to + // preventing an autorelease pool from being created, allowing any + // objects autoreleased by work to fall into the current autorelease pool. + virtual NSAutoreleasePool* CreateAutoreleasePool(); + + private: + // Timer callback scheduled by ScheduleDelayedWork. This does not do any + // work, but it signals work_source_ so that delayed work can be performed + // within the appropriate priority constraints. + static void RunDelayedWorkTimer(CFRunLoopTimerRef timer, void* info); + + // Perform highest-priority work. This is associated with work_source_ + // signalled by ScheduleWork or RunDelayedWorkTimer. The static method calls + // the instance method; the instance method returns true if it resignalled + // work_source_ to be called again from the loop. + static void RunWorkSource(void* info); + bool RunWork(); + + // Perform idle-priority work. This is normally called by PreWaitObserver, + // but is also associated with idle_work_source_. When this function + // actually does perform idle work, it will resignal that source. The + // static method calls the instance method; the instance method returns + // true if idle work was done. + static void RunIdleWorkSource(void* info); + bool RunIdleWork(); + + // Perform work that may have been deferred because it was not runnable + // within a nested run loop. This is associated with + // nesting_deferred_work_source_ and is signalled by + // MaybeScheduleNestingDeferredWork when returning from a nested loop, + // so that an outer loop will be able to perform the necessary tasks if it + // permits nestable tasks. + static void RunNestingDeferredWorkSource(void* info); + bool RunNestingDeferredWork(); + + // Schedules possible nesting-deferred work to be processed before the run + // loop goes to sleep, exits, or begins processing sources at the top of its + // loop. If this function detects that a nested loop had run since the + // previous attempt to schedule nesting-deferred work, it will schedule a + // call to RunNestingDeferredWorkSource. + void MaybeScheduleNestingDeferredWork(); + + // Observer callback responsible for performing idle-priority work, before + // the run loop goes to sleep. Associated with idle_work_observer_. + static void PreWaitObserver(CFRunLoopObserverRef observer, + CFRunLoopActivity activity, void* info); + + // Observer callback called before the run loop processes any sources. + // Associated with pre_source_observer_. + static void PreSourceObserver(CFRunLoopObserverRef observer, + CFRunLoopActivity activity, void* info); + + // Observer callback called when the run loop starts and stops, at the + // beginning and end of calls to CFRunLoopRun. This is used to maintain + // nesting_level_. Associated with enter_exit_observer_. + static void EnterExitObserver(CFRunLoopObserverRef observer, + CFRunLoopActivity activity, void* info); + + // Called by EnterExitObserver after performing maintenance on nesting_level_. + // This allows subclasses an opportunity to perform additional processing on + // the basis of run loops starting and stopping. + virtual void EnterExitRunLoop(CFRunLoopActivity activity); + + // The thread's run loop. + CFRunLoopRef run_loop_; + + // The timer, sources, and observers are described above alongside their + // callbacks. + CFRunLoopTimerRef delayed_work_timer_; + CFRunLoopSourceRef work_source_; + CFRunLoopSourceRef idle_work_source_; + CFRunLoopSourceRef nesting_deferred_work_source_; + CFRunLoopObserverRef pre_wait_observer_; + CFRunLoopObserverRef pre_source_observer_; + CFRunLoopObserverRef enter_exit_observer_; + + // (weak) Delegate passed as an argument to the innermost Run call. + Delegate* delegate_; + + // The time that delayed_work_timer_ is scheduled to fire. This is tracked + // independently of CFRunLoopTimerGetNextFireDate(delayed_work_timer_) + // to be able to reset the timer properly after waking from system sleep. + // See PowerStateNotification. + CFAbsoluteTime delayed_work_fire_time_; + + // The recursion depth of the currently-executing CFRunLoopRun loop on the + // run loop's thread. 0 if no run loops are running inside of whatever scope + // the object was created in. + int nesting_level_; + + // The recursion depth (calculated in the same way as nesting_level_) of the + // innermost executing CFRunLoopRun loop started by a call to Run. + int run_nesting_level_; + + // The deepest (numerically highest) recursion depth encountered since the + // most recent attempt to run nesting-deferred work. + int deepest_nesting_level_; + + // "Delegateless" work flags are set when work is ready to be performed but + // must wait until a delegate is available to process it. This can happen + // when a MessagePumpCFRunLoopBase is instantiated and work arrives without + // any call to Run on the stack. The Run method will check for delegateless + // work on entry and redispatch it as needed once a delegate is available. + bool delegateless_work_; + bool delegateless_idle_work_; + + DISALLOW_COPY_AND_ASSIGN(MessagePumpCFRunLoopBase); +}; + +class MessagePumpCFRunLoop : public MessagePumpCFRunLoopBase { + public: + MessagePumpCFRunLoop(); + virtual ~MessagePumpCFRunLoop(); + + virtual void DoRun(Delegate* delegate) OVERRIDE; + virtual void Quit() OVERRIDE; + + private: + virtual void EnterExitRunLoop(CFRunLoopActivity activity) OVERRIDE; + + // True if Quit is called to stop the innermost MessagePump + // (innermost_quittable_) but some other CFRunLoopRun loop (nesting_level_) + // is running inside the MessagePump's innermost Run call. + bool quit_pending_; + + DISALLOW_COPY_AND_ASSIGN(MessagePumpCFRunLoop); +}; + +class MessagePumpNSRunLoop : public MessagePumpCFRunLoopBase { + public: + BASE_EXPORT MessagePumpNSRunLoop(); + virtual ~MessagePumpNSRunLoop(); + + virtual void DoRun(Delegate* delegate) OVERRIDE; + virtual void Quit() OVERRIDE; + + private: + // A source that doesn't do anything but provide something signalable + // attached to the run loop. This source will be signalled when Quit + // is called, to cause the loop to wake up so that it can stop. + CFRunLoopSourceRef quit_source_; + + // False after Quit is called. + bool keep_running_; + + DISALLOW_COPY_AND_ASSIGN(MessagePumpNSRunLoop); +}; + +#if defined(OS_IOS) +// This is a fake message pump. It attaches sources to the main thread's +// CFRunLoop, so PostTask() will work, but it is unable to drive the loop +// directly, so calling Run() or Quit() are errors. +class MessagePumpUIApplication : public MessagePumpCFRunLoopBase { + public: + MessagePumpUIApplication(); + virtual ~MessagePumpUIApplication(); + virtual void DoRun(Delegate* delegate) OVERRIDE; + virtual void Quit() OVERRIDE; + + // This message pump can not spin the main message loop directly. Instead, + // call |Attach()| to set up a delegate. It is an error to call |Run()|. + virtual void Attach(Delegate* delegate); + + private: + RunLoop* run_loop_; + + DISALLOW_COPY_AND_ASSIGN(MessagePumpUIApplication); +}; + +#else + +class MessagePumpNSApplication : public MessagePumpCFRunLoopBase { + public: + MessagePumpNSApplication(); + virtual ~MessagePumpNSApplication(); + + virtual void DoRun(Delegate* delegate) OVERRIDE; + virtual void Quit() OVERRIDE; + + private: + // False after Quit is called. + bool keep_running_; + + // True if DoRun is managing its own run loop as opposed to letting + // -[NSApplication run] handle it. The outermost run loop in the application + // is managed by -[NSApplication run], inner run loops are handled by a loop + // in DoRun. + bool running_own_loop_; + + DISALLOW_COPY_AND_ASSIGN(MessagePumpNSApplication); +}; + +class MessagePumpCrApplication : public MessagePumpNSApplication { + public: + MessagePumpCrApplication(); + virtual ~MessagePumpCrApplication(); + + protected: + // Returns nil if NSApp is currently in the middle of calling + // -sendEvent. Requires NSApp implementing CrAppProtocol. + virtual NSAutoreleasePool* CreateAutoreleasePool() OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(MessagePumpCrApplication); +}; +#endif // !defined(OS_IOS) + +class MessagePumpMac { + public: + // If not on the main thread, returns a new instance of + // MessagePumpNSRunLoop. + // + // On the main thread, if NSApp exists and conforms to + // CrAppProtocol, creates an instances of MessagePumpCrApplication. + // + // Otherwise creates an instance of MessagePumpNSApplication using a + // default NSApplication. + static MessagePump* Create(); + +#if !defined(OS_IOS) + // If a pump is created before the required CrAppProtocol is + // created, the wrong MessagePump subclass could be used. + // UsingCrApp() returns false if the message pump was created before + // NSApp was initialized, or if NSApp does not implement + // CrAppProtocol. NSApp must be initialized before calling. + BASE_EXPORT static bool UsingCrApp(); + + // Wrapper to query -[NSApp isHandlingSendEvent] from C++ code. + // Requires NSApp to implement CrAppProtocol. + BASE_EXPORT static bool IsHandlingSendEvent(); +#endif // !defined(OS_IOS) + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(MessagePumpMac); +}; + +} // namespace base + +#endif // BASE_MESSAGE_LOOP_MESSAGE_PUMP_MAC_H_ diff --git a/base/message_loop/message_pump_mac.mm b/base/message_loop/message_pump_mac.mm new file mode 100644 index 0000000000..f885fbf5f6 --- /dev/null +++ b/base/message_loop/message_pump_mac.mm @@ -0,0 +1,704 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "base/message_loop/message_pump_mac.h" + +#import + +#include + +#include "base/logging.h" +#include "base/run_loop.h" +#include "base/time/time.h" + +#if !defined(OS_IOS) +#import +#endif // !defined(OS_IOS) + +namespace { + +void NoOp(void* info) { +} + +const CFTimeInterval kCFTimeIntervalMax = + std::numeric_limits::max(); + +#if !defined(OS_IOS) +// Set to true if MessagePumpMac::Create() is called before NSApp is +// initialized. Only accessed from the main thread. +bool g_not_using_cr_app = false; +#endif + +} // namespace + +namespace base { + +// A scoper for autorelease pools created from message pump run loops. +// Avoids dirtying up the ScopedNSAutoreleasePool interface for the rare +// case where an autorelease pool needs to be passed in. +class MessagePumpScopedAutoreleasePool { + public: + explicit MessagePumpScopedAutoreleasePool(MessagePumpCFRunLoopBase* pump) : + pool_(pump->CreateAutoreleasePool()) { + } + ~MessagePumpScopedAutoreleasePool() { + [pool_ drain]; + } + + private: + NSAutoreleasePool* pool_; + DISALLOW_COPY_AND_ASSIGN(MessagePumpScopedAutoreleasePool); +}; + +// Must be called on the run loop thread. +MessagePumpCFRunLoopBase::MessagePumpCFRunLoopBase() + : delegate_(NULL), + delayed_work_fire_time_(kCFTimeIntervalMax), + nesting_level_(0), + run_nesting_level_(0), + deepest_nesting_level_(0), + delegateless_work_(false), + delegateless_idle_work_(false) { + run_loop_ = CFRunLoopGetCurrent(); + CFRetain(run_loop_); + + // Set a repeating timer with a preposterous firing time and interval. The + // timer will effectively never fire as-is. The firing time will be adjusted + // as needed when ScheduleDelayedWork is called. + CFRunLoopTimerContext timer_context = CFRunLoopTimerContext(); + timer_context.info = this; + delayed_work_timer_ = CFRunLoopTimerCreate(NULL, // allocator + kCFTimeIntervalMax, // fire time + kCFTimeIntervalMax, // interval + 0, // flags + 0, // priority + RunDelayedWorkTimer, + &timer_context); + CFRunLoopAddTimer(run_loop_, delayed_work_timer_, kCFRunLoopCommonModes); + + CFRunLoopSourceContext source_context = CFRunLoopSourceContext(); + source_context.info = this; + source_context.perform = RunWorkSource; + work_source_ = CFRunLoopSourceCreate(NULL, // allocator + 1, // priority + &source_context); + CFRunLoopAddSource(run_loop_, work_source_, kCFRunLoopCommonModes); + + source_context.perform = RunIdleWorkSource; + idle_work_source_ = CFRunLoopSourceCreate(NULL, // allocator + 2, // priority + &source_context); + CFRunLoopAddSource(run_loop_, idle_work_source_, kCFRunLoopCommonModes); + + source_context.perform = RunNestingDeferredWorkSource; + nesting_deferred_work_source_ = CFRunLoopSourceCreate(NULL, // allocator + 0, // priority + &source_context); + CFRunLoopAddSource(run_loop_, nesting_deferred_work_source_, + kCFRunLoopCommonModes); + + CFRunLoopObserverContext observer_context = CFRunLoopObserverContext(); + observer_context.info = this; + pre_wait_observer_ = CFRunLoopObserverCreate(NULL, // allocator + kCFRunLoopBeforeWaiting, + true, // repeat + 0, // priority + PreWaitObserver, + &observer_context); + CFRunLoopAddObserver(run_loop_, pre_wait_observer_, kCFRunLoopCommonModes); + + pre_source_observer_ = CFRunLoopObserverCreate(NULL, // allocator + kCFRunLoopBeforeSources, + true, // repeat + 0, // priority + PreSourceObserver, + &observer_context); + CFRunLoopAddObserver(run_loop_, pre_source_observer_, kCFRunLoopCommonModes); + + enter_exit_observer_ = CFRunLoopObserverCreate(NULL, // allocator + kCFRunLoopEntry | + kCFRunLoopExit, + true, // repeat + 0, // priority + EnterExitObserver, + &observer_context); + CFRunLoopAddObserver(run_loop_, enter_exit_observer_, kCFRunLoopCommonModes); +} + +// Ideally called on the run loop thread. If other run loops were running +// lower on the run loop thread's stack when this object was created, the +// same number of run loops must be running when this object is destroyed. +MessagePumpCFRunLoopBase::~MessagePumpCFRunLoopBase() { + CFRunLoopRemoveObserver(run_loop_, enter_exit_observer_, + kCFRunLoopCommonModes); + CFRelease(enter_exit_observer_); + + CFRunLoopRemoveObserver(run_loop_, pre_source_observer_, + kCFRunLoopCommonModes); + CFRelease(pre_source_observer_); + + CFRunLoopRemoveObserver(run_loop_, pre_wait_observer_, + kCFRunLoopCommonModes); + CFRelease(pre_wait_observer_); + + CFRunLoopRemoveSource(run_loop_, nesting_deferred_work_source_, + kCFRunLoopCommonModes); + CFRelease(nesting_deferred_work_source_); + + CFRunLoopRemoveSource(run_loop_, idle_work_source_, kCFRunLoopCommonModes); + CFRelease(idle_work_source_); + + CFRunLoopRemoveSource(run_loop_, work_source_, kCFRunLoopCommonModes); + CFRelease(work_source_); + + CFRunLoopRemoveTimer(run_loop_, delayed_work_timer_, kCFRunLoopCommonModes); + CFRelease(delayed_work_timer_); + + CFRelease(run_loop_); +} + +// Must be called on the run loop thread. +void MessagePumpCFRunLoopBase::Run(Delegate* delegate) { + // nesting_level_ will be incremented in EnterExitRunLoop, so set + // run_nesting_level_ accordingly. + int last_run_nesting_level = run_nesting_level_; + run_nesting_level_ = nesting_level_ + 1; + + Delegate* last_delegate = delegate_; + SetDelegate(delegate); + + DoRun(delegate); + + // Restore the previous state of the object. + SetDelegate(last_delegate); + run_nesting_level_ = last_run_nesting_level; +} + +void MessagePumpCFRunLoopBase::SetDelegate(Delegate* delegate) { + delegate_ = delegate; + + if (delegate) { + // If any work showed up but could not be dispatched for want of a + // delegate, set it up for dispatch again now that a delegate is + // available. + if (delegateless_work_) { + CFRunLoopSourceSignal(work_source_); + delegateless_work_ = false; + } + if (delegateless_idle_work_) { + CFRunLoopSourceSignal(idle_work_source_); + delegateless_idle_work_ = false; + } + } +} + +// May be called on any thread. +void MessagePumpCFRunLoopBase::ScheduleWork() { + CFRunLoopSourceSignal(work_source_); + CFRunLoopWakeUp(run_loop_); +} + +// Must be called on the run loop thread. +void MessagePumpCFRunLoopBase::ScheduleDelayedWork( + const TimeTicks& delayed_work_time) { + TimeDelta delta = delayed_work_time - TimeTicks::Now(); + delayed_work_fire_time_ = CFAbsoluteTimeGetCurrent() + delta.InSecondsF(); + CFRunLoopTimerSetNextFireDate(delayed_work_timer_, delayed_work_fire_time_); +} + +// Called from the run loop. +// static +void MessagePumpCFRunLoopBase::RunDelayedWorkTimer(CFRunLoopTimerRef timer, + void* info) { + MessagePumpCFRunLoopBase* self = static_cast(info); + + // The timer won't fire again until it's reset. + self->delayed_work_fire_time_ = kCFTimeIntervalMax; + + // CFRunLoopTimers fire outside of the priority scheme for CFRunLoopSources. + // In order to establish the proper priority in which work and delayed work + // are processed one for one, the timer used to schedule delayed work must + // signal a CFRunLoopSource used to dispatch both work and delayed work. + CFRunLoopSourceSignal(self->work_source_); +} + +// Called from the run loop. +// static +void MessagePumpCFRunLoopBase::RunWorkSource(void* info) { + MessagePumpCFRunLoopBase* self = static_cast(info); + self->RunWork(); +} + +// Called by MessagePumpCFRunLoopBase::RunWorkSource. +bool MessagePumpCFRunLoopBase::RunWork() { + if (!delegate_) { + // This point can be reached with a NULL delegate_ if Run is not on the + // stack but foreign code is spinning the CFRunLoop. Arrange to come back + // here when a delegate is available. + delegateless_work_ = true; + return false; + } + + // The NSApplication-based run loop only drains the autorelease pool at each + // UI event (NSEvent). The autorelease pool is not drained for each + // CFRunLoopSource target that's run. Use a local pool for any autoreleased + // objects if the app is not currently handling a UI event to ensure they're + // released promptly even in the absence of UI events. + MessagePumpScopedAutoreleasePool autorelease_pool(this); + + // Call DoWork and DoDelayedWork once, and if something was done, arrange to + // come back here again as long as the loop is still running. + bool did_work = delegate_->DoWork(); + bool resignal_work_source = did_work; + + TimeTicks next_time; + delegate_->DoDelayedWork(&next_time); + if (!did_work) { + // Determine whether there's more delayed work, and if so, if it needs to + // be done at some point in the future or if it's already time to do it. + // Only do these checks if did_work is false. If did_work is true, this + // function, and therefore any additional delayed work, will get another + // chance to run before the loop goes to sleep. + bool more_delayed_work = !next_time.is_null(); + if (more_delayed_work) { + TimeDelta delay = next_time - TimeTicks::Now(); + if (delay > TimeDelta()) { + // There's more delayed work to be done in the future. + ScheduleDelayedWork(next_time); + } else { + // There's more delayed work to be done, and its time is in the past. + // Arrange to come back here directly as long as the loop is still + // running. + resignal_work_source = true; + } + } + } + + if (resignal_work_source) { + CFRunLoopSourceSignal(work_source_); + } + + return resignal_work_source; +} + +// Called from the run loop. +// static +void MessagePumpCFRunLoopBase::RunIdleWorkSource(void* info) { + MessagePumpCFRunLoopBase* self = static_cast(info); + self->RunIdleWork(); +} + +// Called by MessagePumpCFRunLoopBase::RunIdleWorkSource. +bool MessagePumpCFRunLoopBase::RunIdleWork() { + if (!delegate_) { + // This point can be reached with a NULL delegate_ if Run is not on the + // stack but foreign code is spinning the CFRunLoop. Arrange to come back + // here when a delegate is available. + delegateless_idle_work_ = true; + return false; + } + + // The NSApplication-based run loop only drains the autorelease pool at each + // UI event (NSEvent). The autorelease pool is not drained for each + // CFRunLoopSource target that's run. Use a local pool for any autoreleased + // objects if the app is not currently handling a UI event to ensure they're + // released promptly even in the absence of UI events. + MessagePumpScopedAutoreleasePool autorelease_pool(this); + + // Call DoIdleWork once, and if something was done, arrange to come back here + // again as long as the loop is still running. + bool did_work = delegate_->DoIdleWork(); + if (did_work) { + CFRunLoopSourceSignal(idle_work_source_); + } + + return did_work; +} + +// Called from the run loop. +// static +void MessagePumpCFRunLoopBase::RunNestingDeferredWorkSource(void* info) { + MessagePumpCFRunLoopBase* self = static_cast(info); + self->RunNestingDeferredWork(); +} + +// Called by MessagePumpCFRunLoopBase::RunNestingDeferredWorkSource. +bool MessagePumpCFRunLoopBase::RunNestingDeferredWork() { + if (!delegate_) { + // This point can be reached with a NULL delegate_ if Run is not on the + // stack but foreign code is spinning the CFRunLoop. There's no sense in + // attempting to do any work or signalling the work sources because + // without a delegate, work is not possible. + return false; + } + + // Immediately try work in priority order. + if (!RunWork()) { + if (!RunIdleWork()) { + return false; + } + } else { + // Work was done. Arrange for the loop to try non-nestable idle work on + // a subsequent pass. + CFRunLoopSourceSignal(idle_work_source_); + } + + return true; +} + +// Called before the run loop goes to sleep or exits, or processes sources. +void MessagePumpCFRunLoopBase::MaybeScheduleNestingDeferredWork() { + // deepest_nesting_level_ is set as run loops are entered. If the deepest + // level encountered is deeper than the current level, a nested loop + // (relative to the current level) ran since the last time nesting-deferred + // work was scheduled. When that situation is encountered, schedule + // nesting-deferred work in case any work was deferred because nested work + // was disallowed. + if (deepest_nesting_level_ > nesting_level_) { + deepest_nesting_level_ = nesting_level_; + CFRunLoopSourceSignal(nesting_deferred_work_source_); + } +} + +// Called from the run loop. +// static +void MessagePumpCFRunLoopBase::PreWaitObserver(CFRunLoopObserverRef observer, + CFRunLoopActivity activity, + void* info) { + MessagePumpCFRunLoopBase* self = static_cast(info); + + // Attempt to do some idle work before going to sleep. + self->RunIdleWork(); + + // The run loop is about to go to sleep. If any of the work done since it + // started or woke up resulted in a nested run loop running, + // nesting-deferred work may have accumulated. Schedule it for processing + // if appropriate. + self->MaybeScheduleNestingDeferredWork(); +} + +// Called from the run loop. +// static +void MessagePumpCFRunLoopBase::PreSourceObserver(CFRunLoopObserverRef observer, + CFRunLoopActivity activity, + void* info) { + MessagePumpCFRunLoopBase* self = static_cast(info); + + // The run loop has reached the top of the loop and is about to begin + // processing sources. If the last iteration of the loop at this nesting + // level did not sleep or exit, nesting-deferred work may have accumulated + // if a nested loop ran. Schedule nesting-deferred work for processing if + // appropriate. + self->MaybeScheduleNestingDeferredWork(); +} + +// Called from the run loop. +// static +void MessagePumpCFRunLoopBase::EnterExitObserver(CFRunLoopObserverRef observer, + CFRunLoopActivity activity, + void* info) { + MessagePumpCFRunLoopBase* self = static_cast(info); + + switch (activity) { + case kCFRunLoopEntry: + ++self->nesting_level_; + if (self->nesting_level_ > self->deepest_nesting_level_) { + self->deepest_nesting_level_ = self->nesting_level_; + } + break; + + case kCFRunLoopExit: + // Not all run loops go to sleep. If a run loop is stopped before it + // goes to sleep due to a CFRunLoopStop call, or if the timeout passed + // to CFRunLoopRunInMode expires, the run loop may proceed directly from + // handling sources to exiting without any sleep. This most commonly + // occurs when CFRunLoopRunInMode is passed a timeout of 0, causing it + // to make a single pass through the loop and exit without sleep. Some + // native loops use CFRunLoop in this way. Because PreWaitObserver will + // not be called in these case, MaybeScheduleNestingDeferredWork needs + // to be called here, as the run loop exits. + // + // MaybeScheduleNestingDeferredWork consults self->nesting_level_ + // to determine whether to schedule nesting-deferred work. It expects + // the nesting level to be set to the depth of the loop that is going + // to sleep or exiting. It must be called before decrementing the + // value so that the value still corresponds to the level of the exiting + // loop. + self->MaybeScheduleNestingDeferredWork(); + --self->nesting_level_; + break; + + default: + break; + } + + self->EnterExitRunLoop(activity); +} + +// Called by MessagePumpCFRunLoopBase::EnterExitRunLoop. The default +// implementation is a no-op. +void MessagePumpCFRunLoopBase::EnterExitRunLoop(CFRunLoopActivity activity) { +} + +// Base version returns a standard NSAutoreleasePool. +NSAutoreleasePool* MessagePumpCFRunLoopBase::CreateAutoreleasePool() { + return [[NSAutoreleasePool alloc] init]; +} + +MessagePumpCFRunLoop::MessagePumpCFRunLoop() + : quit_pending_(false) { +} + +MessagePumpCFRunLoop::~MessagePumpCFRunLoop() {} + +// Called by MessagePumpCFRunLoopBase::DoRun. If other CFRunLoopRun loops were +// running lower on the run loop thread's stack when this object was created, +// the same number of CFRunLoopRun loops must be running for the outermost call +// to Run. Run/DoRun are reentrant after that point. +void MessagePumpCFRunLoop::DoRun(Delegate* delegate) { + // This is completely identical to calling CFRunLoopRun(), except autorelease + // pool management is introduced. + int result; + do { + MessagePumpScopedAutoreleasePool autorelease_pool(this); + result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, + kCFTimeIntervalMax, + false); + } while (result != kCFRunLoopRunStopped && result != kCFRunLoopRunFinished); +} + +// Must be called on the run loop thread. +void MessagePumpCFRunLoop::Quit() { + // Stop the innermost run loop managed by this MessagePumpCFRunLoop object. + if (nesting_level() == run_nesting_level()) { + // This object is running the innermost loop, just stop it. + CFRunLoopStop(run_loop()); + } else { + // There's another loop running inside the loop managed by this object. + // In other words, someone else called CFRunLoopRunInMode on the same + // thread, deeper on the stack than the deepest Run call. Don't preempt + // other run loops, just mark this object to quit the innermost Run as + // soon as the other inner loops not managed by Run are done. + quit_pending_ = true; + } +} + +// Called by MessagePumpCFRunLoopBase::EnterExitObserver. +void MessagePumpCFRunLoop::EnterExitRunLoop(CFRunLoopActivity activity) { + if (activity == kCFRunLoopExit && + nesting_level() == run_nesting_level() && + quit_pending_) { + // Quit was called while loops other than those managed by this object + // were running further inside a run loop managed by this object. Now + // that all unmanaged inner run loops are gone, stop the loop running + // just inside Run. + CFRunLoopStop(run_loop()); + quit_pending_ = false; + } +} + +MessagePumpNSRunLoop::MessagePumpNSRunLoop() + : keep_running_(true) { + CFRunLoopSourceContext source_context = CFRunLoopSourceContext(); + source_context.perform = NoOp; + quit_source_ = CFRunLoopSourceCreate(NULL, // allocator + 0, // priority + &source_context); + CFRunLoopAddSource(run_loop(), quit_source_, kCFRunLoopCommonModes); +} + +MessagePumpNSRunLoop::~MessagePumpNSRunLoop() { + CFRunLoopRemoveSource(run_loop(), quit_source_, kCFRunLoopCommonModes); + CFRelease(quit_source_); +} + +void MessagePumpNSRunLoop::DoRun(Delegate* delegate) { + while (keep_running_) { + // NSRunLoop manages autorelease pools itself. + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode + beforeDate:[NSDate distantFuture]]; + } + + keep_running_ = true; +} + +void MessagePumpNSRunLoop::Quit() { + keep_running_ = false; + CFRunLoopSourceSignal(quit_source_); + CFRunLoopWakeUp(run_loop()); +} + +#if defined(OS_IOS) +MessagePumpUIApplication::MessagePumpUIApplication() + : run_loop_(NULL) { +} + +MessagePumpUIApplication::~MessagePumpUIApplication() {} + +void MessagePumpUIApplication::DoRun(Delegate* delegate) { + NOTREACHED(); +} + +void MessagePumpUIApplication::Quit() { + NOTREACHED(); +} + +void MessagePumpUIApplication::Attach(Delegate* delegate) { + DCHECK(!run_loop_); + run_loop_ = new RunLoop(); + CHECK(run_loop_->BeforeRun()); + SetDelegate(delegate); +} + +#else + +MessagePumpNSApplication::MessagePumpNSApplication() + : keep_running_(true), + running_own_loop_(false) { +} + +MessagePumpNSApplication::~MessagePumpNSApplication() {} + +void MessagePumpNSApplication::DoRun(Delegate* delegate) { + bool last_running_own_loop_ = running_own_loop_; + + // NSApp must be initialized by calling: + // [{some class which implements CrAppProtocol} sharedApplication] + // Most likely candidates are CrApplication or BrowserCrApplication. + // These can be initialized from C++ code by calling + // RegisterCrApp() or RegisterBrowserCrApp(). + CHECK(NSApp); + + if (![NSApp isRunning]) { + running_own_loop_ = false; + // NSApplication manages autorelease pools itself when run this way. + [NSApp run]; + } else { + running_own_loop_ = true; + NSDate* distant_future = [NSDate distantFuture]; + while (keep_running_) { + MessagePumpScopedAutoreleasePool autorelease_pool(this); + NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:distant_future + inMode:NSDefaultRunLoopMode + dequeue:YES]; + if (event) { + [NSApp sendEvent:event]; + } + } + keep_running_ = true; + } + + running_own_loop_ = last_running_own_loop_; +} + +void MessagePumpNSApplication::Quit() { + if (!running_own_loop_) { + [[NSApplication sharedApplication] stop:nil]; + } else { + keep_running_ = false; + } + + // Send a fake event to wake the loop up. + [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined + location:NSZeroPoint + modifierFlags:0 + timestamp:0 + windowNumber:0 + context:NULL + subtype:0 + data1:0 + data2:0] + atStart:NO]; +} + +MessagePumpCrApplication::MessagePumpCrApplication() { +} + +MessagePumpCrApplication::~MessagePumpCrApplication() { +} + +// Prevents an autorelease pool from being created if the app is in the midst of +// handling a UI event because various parts of AppKit depend on objects that +// are created while handling a UI event to be autoreleased in the event loop. +// An example of this is NSWindowController. When a window with a window +// controller is closed it goes through a stack like this: +// (Several stack frames elided for clarity) +// +// #0 [NSWindowController autorelease] +// #1 DoAClose +// #2 MessagePumpCFRunLoopBase::DoWork() +// #3 [NSRunLoop run] +// #4 [NSButton performClick:] +// #5 [NSWindow sendEvent:] +// #6 [NSApp sendEvent:] +// #7 [NSApp run] +// +// -performClick: spins a nested run loop. If the pool created in DoWork was a +// standard NSAutoreleasePool, it would release the objects that were +// autoreleased into it once DoWork released it. This would cause the window +// controller, which autoreleased itself in frame #0, to release itself, and +// possibly free itself. Unfortunately this window controller controls the +// window in frame #5. When the stack is unwound to frame #5, the window would +// no longer exists and crashes may occur. Apple gets around this by never +// releasing the pool it creates in frame #4, and letting frame #7 clean it up +// when it cleans up the pool that wraps frame #7. When an autorelease pool is +// released it releases all other pools that were created after it on the +// autorelease pool stack. +// +// CrApplication is responsible for setting handlingSendEvent to true just +// before it sends the event through the event handling mechanism, and +// returning it to its previous value once the event has been sent. +NSAutoreleasePool* MessagePumpCrApplication::CreateAutoreleasePool() { + if (MessagePumpMac::IsHandlingSendEvent()) + return nil; + return MessagePumpNSApplication::CreateAutoreleasePool(); +} + +// static +bool MessagePumpMac::UsingCrApp() { + DCHECK([NSThread isMainThread]); + + // If NSApp is still not initialized, then the subclass used cannot + // be determined. + DCHECK(NSApp); + + // The pump was created using MessagePumpNSApplication. + if (g_not_using_cr_app) + return false; + + return [NSApp conformsToProtocol:@protocol(CrAppProtocol)]; +} + +// static +bool MessagePumpMac::IsHandlingSendEvent() { + DCHECK([NSApp conformsToProtocol:@protocol(CrAppProtocol)]); + NSObject* app = static_cast*>(NSApp); + return [app isHandlingSendEvent]; +} +#endif // !defined(OS_IOS) + +// static +MessagePump* MessagePumpMac::Create() { + if ([NSThread isMainThread]) { +#if defined(OS_IOS) + return new MessagePumpUIApplication; +#else + if ([NSApp conformsToProtocol:@protocol(CrAppProtocol)]) + return new MessagePumpCrApplication; + + // The main-thread MessagePump implementations REQUIRE an NSApp. + // Executables which have specific requirements for their + // NSApplication subclass should initialize appropriately before + // creating an event loop. + [NSApplication sharedApplication]; + g_not_using_cr_app = true; + return new MessagePumpNSApplication; +#endif + } + + return new MessagePumpNSRunLoop; +} + +} // namespace base diff --git a/base/message_loop/message_pump_observer.h b/base/message_loop/message_pump_observer.h new file mode 100644 index 0000000000..cb46fa35b8 --- /dev/null +++ b/base/message_loop/message_pump_observer.h @@ -0,0 +1,47 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MESSAGE_LOOP_MESSAGE_PUMP_OBSERVER_H +#define BASE_MESSAGE_LOOP_MESSAGE_PUMP_OBSERVER_H + +#include "base/base_export.h" +#include "base/event_types.h" + +namespace base { + +enum EventStatus { + EVENT_CONTINUE, // The event should be dispatched as normal. +#if defined(USE_X11) + EVENT_HANDLED // The event should not be processed any farther. +#endif +}; + +// A MessagePumpObserver is an object that receives global +// notifications from the UI MessageLoop with MessagePumpWin or +// MessagePumpAuraX11. +// +// NOTE: An Observer implementation should be extremely fast! +// +// For use with MessagePumpAuraX11, please see message_pump_glib.h for more +// info about how this is invoked in this environment. +class BASE_EXPORT MessagePumpObserver { + public: + // This method is called before processing a NativeEvent. If the + // method returns EVENT_HANDLED, it indicates the event has already + // been handled, so the event is not processed any farther. If the + // method returns EVENT_CONTINUE, the event dispatching proceeds as + // normal. + virtual EventStatus WillProcessEvent(const NativeEvent& event) = 0; + + // This method is called after processing a message. This method + // will not be called if WillProcessEvent returns EVENT_HANDLED. + virtual void DidProcessEvent(const NativeEvent& event) = 0; + + protected: + virtual ~MessagePumpObserver() {} +}; + +} // namespace base + +#endif // BASE_MESSAGE_LOOP_MESSAGE_PUMP_OBSERVER_H diff --git a/base/message_loop/message_pump_ozone.cc b/base/message_loop/message_pump_ozone.cc new file mode 100644 index 0000000000..f7bff6d3ac --- /dev/null +++ b/base/message_loop/message_pump_ozone.cc @@ -0,0 +1,61 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/message_loop/message_pump_ozone.h" + +#include "base/logging.h" +#include "base/message_loop/message_loop.h" + +namespace base { + +MessagePumpOzone::MessagePumpOzone() + : MessagePumpLibevent() { +} + +MessagePumpOzone::~MessagePumpOzone() { +} + +void MessagePumpOzone::AddObserver(MessagePumpObserver* /* observer */) { + NOTIMPLEMENTED(); +} + +void MessagePumpOzone::RemoveObserver(MessagePumpObserver* /* observer */) { + NOTIMPLEMENTED(); +} + +// static +MessagePumpOzone* MessagePumpOzone::Current() { + MessageLoopForUI* loop = MessageLoopForUI::current(); + return static_cast(loop->pump_ui()); +} + +void MessagePumpOzone::AddDispatcherForRootWindow( + MessagePumpDispatcher* dispatcher) { + // Only one root window is supported. + DCHECK(dispatcher_.size() == 0); + dispatcher_.insert(dispatcher_.begin(),dispatcher); +} + +void MessagePumpOzone::RemoveDispatcherForRootWindow( + MessagePumpDispatcher* dispatcher) { + DCHECK(dispatcher_.size() == 1); + dispatcher_.pop_back(); +} + +bool MessagePumpOzone::Dispatch(const NativeEvent& dev) { + if (dispatcher_.size() > 0) + return dispatcher_[0]->Dispatch(dev); + else + return true; +} + +// This code assumes that the caller tracks the lifetime of the |dispatcher|. +void MessagePumpOzone::RunWithDispatcher( + Delegate* delegate, MessagePumpDispatcher* dispatcher) { + dispatcher_.push_back(dispatcher); + Run(delegate); + dispatcher_.pop_back(); +} + +} // namespace base diff --git a/base/message_loop/message_pump_ozone.h b/base/message_loop/message_pump_ozone.h new file mode 100644 index 0000000000..edcdf2e3a8 --- /dev/null +++ b/base/message_loop/message_pump_ozone.h @@ -0,0 +1,52 @@ +// 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_MESSAGE_LOOP_MESSAGE_PUMP_OZONE_H_ +#define BASE_MESSAGE_LOOP_MESSAGE_PUMP_OZONE_H_ + +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/message_loop/message_pump_dispatcher.h" +#include "base/message_loop/message_pump_libevent.h" +#include "base/message_loop/message_pump_observer.h" +#include "base/observer_list.h" + +namespace base { + +// This class implements a message-pump for processing events from input devices +// Refer to MessagePump for further documentation. +class BASE_EXPORT MessagePumpOzone : public MessagePumpLibevent, + public MessagePumpDispatcher { + public: + MessagePumpOzone(); + virtual ~MessagePumpOzone(); + + // Returns the UI message pump. + static MessagePumpOzone* Current(); + + // Add/Remove the root window dispatcher. + void AddDispatcherForRootWindow(MessagePumpDispatcher* dispatcher); + void RemoveDispatcherForRootWindow(MessagePumpDispatcher* dispatcher); + + void RunWithDispatcher(Delegate* delegate, MessagePumpDispatcher* dispatcher); + + // Add / remove an Observer, which will start receiving notifications + // immediately. + void AddObserver(MessagePumpObserver* observer); + void RemoveObserver(MessagePumpObserver* observer); + + // Overridden from MessagePumpDispatcher. + virtual bool Dispatch(const NativeEvent& event) OVERRIDE; + + private: + std::vector dispatcher_; + + DISALLOW_COPY_AND_ASSIGN(MessagePumpOzone); +}; + +typedef MessagePumpOzone MessagePumpForUI; + +} // namespace base + +#endif // BASE_MESSAGE_LOOP_MESSAGE_PUMP_OZONE_H_ diff --git a/base/message_loop/message_pump_win.cc b/base/message_loop/message_pump_win.cc new file mode 100644 index 0000000000..589d077541 --- /dev/null +++ b/base/message_loop/message_pump_win.cc @@ -0,0 +1,686 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/message_loop/message_pump_win.h" + +#include + +#include "base/debug/trace_event.h" +#include "base/message_loop/message_loop.h" +#include "base/metrics/histogram.h" +#include "base/process/memory.h" +#include "base/strings/stringprintf.h" +#include "base/win/wrapped_window_proc.h" + +namespace base { + +namespace { + +enum MessageLoopProblems { + MESSAGE_POST_ERROR, + COMPLETION_POST_ERROR, + SET_TIMER_ERROR, + MESSAGE_LOOP_PROBLEM_MAX, +}; + +} // namespace + +static const wchar_t kWndClassFormat[] = L"Chrome_MessagePumpWindow_%p"; + +// Message sent to get an additional time slice for pumping (processing) another +// task (a series of such messages creates a continuous task pump). +static const int kMsgHaveWork = WM_USER + 1; + +//----------------------------------------------------------------------------- +// MessagePumpWin public: + +void MessagePumpWin::AddObserver(MessagePumpObserver* observer) { + observers_.AddObserver(observer); +} + +void MessagePumpWin::RemoveObserver(MessagePumpObserver* observer) { + observers_.RemoveObserver(observer); +} + +void MessagePumpWin::WillProcessMessage(const MSG& msg) { + FOR_EACH_OBSERVER(MessagePumpObserver, observers_, WillProcessEvent(msg)); +} + +void MessagePumpWin::DidProcessMessage(const MSG& msg) { + FOR_EACH_OBSERVER(MessagePumpObserver, observers_, DidProcessEvent(msg)); +} + +void MessagePumpWin::RunWithDispatcher( + Delegate* delegate, MessagePumpDispatcher* dispatcher) { + RunState s; + s.delegate = delegate; + s.dispatcher = dispatcher; + s.should_quit = false; + s.run_depth = state_ ? state_->run_depth + 1 : 1; + + RunState* previous_state = state_; + state_ = &s; + + DoRunLoop(); + + state_ = previous_state; +} + +void MessagePumpWin::Quit() { + DCHECK(state_); + state_->should_quit = true; +} + +//----------------------------------------------------------------------------- +// MessagePumpWin protected: + +int MessagePumpWin::GetCurrentDelay() const { + if (delayed_work_time_.is_null()) + return -1; + + // Be careful here. TimeDelta has a precision of microseconds, but we want a + // value in milliseconds. If there are 5.5ms left, should the delay be 5 or + // 6? It should be 6 to avoid executing delayed work too early. + double timeout = + ceil((delayed_work_time_ - TimeTicks::Now()).InMillisecondsF()); + + // If this value is negative, then we need to run delayed work soon. + int delay = static_cast(timeout); + if (delay < 0) + delay = 0; + + return delay; +} + +//----------------------------------------------------------------------------- +// MessagePumpForUI public: + +MessagePumpForUI::MessagePumpForUI() + : atom_(0), + message_filter_(new MessageFilter) { + InitMessageWnd(); +} + +MessagePumpForUI::~MessagePumpForUI() { + DestroyWindow(message_hwnd_); + UnregisterClass(MAKEINTATOM(atom_), + GetModuleFromAddress(&WndProcThunk)); +} + +void MessagePumpForUI::ScheduleWork() { + if (InterlockedExchange(&have_work_, 1)) + return; // Someone else continued the pumping. + + // Make sure the MessagePump does some work for us. + BOOL ret = PostMessage(message_hwnd_, kMsgHaveWork, + reinterpret_cast(this), 0); + if (ret) + return; // There was room in the Window Message queue. + + // We have failed to insert a have-work message, so there is a chance that we + // will starve tasks/timers while sitting in a nested message loop. Nested + // loops only look at Windows Message queues, and don't look at *our* task + // queues, etc., so we might not get a time slice in such. :-( + // We could abort here, but the fear is that this failure mode is plausibly + // common (queue is full, of about 2000 messages), so we'll do a near-graceful + // recovery. Nested loops are pretty transient (we think), so this will + // probably be recoverable. + InterlockedExchange(&have_work_, 0); // Clarify that we didn't really insert. + UMA_HISTOGRAM_ENUMERATION("Chrome.MessageLoopProblem", MESSAGE_POST_ERROR, + MESSAGE_LOOP_PROBLEM_MAX); +} + +void MessagePumpForUI::ScheduleDelayedWork(const TimeTicks& delayed_work_time) { + // + // We would *like* to provide high resolution timers. Windows timers using + // SetTimer() have a 10ms granularity. We have to use WM_TIMER as a wakeup + // mechanism because the application can enter modal windows loops where it + // is not running our MessageLoop; the only way to have our timers fire in + // these cases is to post messages there. + // + // To provide sub-10ms timers, we process timers directly from our run loop. + // For the common case, timers will be processed there as the run loop does + // its normal work. However, we *also* set the system timer so that WM_TIMER + // events fire. This mops up the case of timers not being able to work in + // modal message loops. It is possible for the SetTimer to pop and have no + // pending timers, because they could have already been processed by the + // run loop itself. + // + // We use a single SetTimer corresponding to the timer that will expire + // soonest. As new timers are created and destroyed, we update SetTimer. + // Getting a spurrious SetTimer event firing is benign, as we'll just be + // processing an empty timer queue. + // + delayed_work_time_ = delayed_work_time; + + int delay_msec = GetCurrentDelay(); + DCHECK_GE(delay_msec, 0); + if (delay_msec < USER_TIMER_MINIMUM) + delay_msec = USER_TIMER_MINIMUM; + + // Create a WM_TIMER event that will wake us up to check for any pending + // timers (in case we are running within a nested, external sub-pump). + BOOL ret = SetTimer(message_hwnd_, reinterpret_cast(this), + delay_msec, NULL); + if (ret) + return; + // If we can't set timers, we are in big trouble... but cross our fingers for + // now. + // TODO(jar): If we don't see this error, use a CHECK() here instead. + UMA_HISTOGRAM_ENUMERATION("Chrome.MessageLoopProblem", SET_TIMER_ERROR, + MESSAGE_LOOP_PROBLEM_MAX); +} + +void MessagePumpForUI::PumpOutPendingPaintMessages() { + // If we are being called outside of the context of Run, then don't try to do + // any work. + if (!state_) + return; + + // Create a mini-message-pump to force immediate processing of only Windows + // WM_PAINT messages. Don't provide an infinite loop, but do enough peeking + // to get the job done. Actual common max is 4 peeks, but we'll be a little + // safe here. + const int kMaxPeekCount = 20; + int peek_count; + for (peek_count = 0; peek_count < kMaxPeekCount; ++peek_count) { + MSG msg; + if (!PeekMessage(&msg, NULL, 0, 0, PM_REMOVE | PM_QS_PAINT)) + break; + ProcessMessageHelper(msg); + if (state_->should_quit) // Handle WM_QUIT. + break; + } + // Histogram what was really being used, to help to adjust kMaxPeekCount. + DHISTOGRAM_COUNTS("Loop.PumpOutPendingPaintMessages Peeks", peek_count); +} + +//----------------------------------------------------------------------------- +// MessagePumpForUI private: + +// static +LRESULT CALLBACK MessagePumpForUI::WndProcThunk( + HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { + switch (message) { + case kMsgHaveWork: + reinterpret_cast(wparam)->HandleWorkMessage(); + break; + case WM_TIMER: + reinterpret_cast(wparam)->HandleTimerMessage(); + break; + } + return DefWindowProc(hwnd, message, wparam, lparam); +} + +void MessagePumpForUI::DoRunLoop() { + // IF this was just a simple PeekMessage() loop (servicing all possible work + // queues), then Windows would try to achieve the following order according + // to MSDN documentation about PeekMessage with no filter): + // * Sent messages + // * Posted messages + // * Sent messages (again) + // * WM_PAINT messages + // * WM_TIMER messages + // + // Summary: none of the above classes is starved, and sent messages has twice + // the chance of being processed (i.e., reduced service time). + + for (;;) { + // If we do any work, we may create more messages etc., and more work may + // possibly be waiting in another task group. When we (for example) + // ProcessNextWindowsMessage(), there is a good chance there are still more + // messages waiting. On the other hand, when any of these methods return + // having done no work, then it is pretty unlikely that calling them again + // quickly will find any work to do. Finally, if they all say they had no + // work, then it is a good time to consider sleeping (waiting) for more + // work. + + bool more_work_is_plausible = ProcessNextWindowsMessage(); + if (state_->should_quit) + break; + + more_work_is_plausible |= state_->delegate->DoWork(); + if (state_->should_quit) + break; + + more_work_is_plausible |= + state_->delegate->DoDelayedWork(&delayed_work_time_); + // If we did not process any delayed work, then we can assume that our + // existing WM_TIMER if any will fire when delayed work should run. We + // don't want to disturb that timer if it is already in flight. However, + // if we did do all remaining delayed work, then lets kill the WM_TIMER. + if (more_work_is_plausible && delayed_work_time_.is_null()) + KillTimer(message_hwnd_, reinterpret_cast(this)); + if (state_->should_quit) + break; + + if (more_work_is_plausible) + continue; + + more_work_is_plausible = state_->delegate->DoIdleWork(); + if (state_->should_quit) + break; + + if (more_work_is_plausible) + continue; + + WaitForWork(); // Wait (sleep) until we have work to do again. + } +} + +void MessagePumpForUI::InitMessageWnd() { + // Generate a unique window class name. + string16 class_name = StringPrintf(kWndClassFormat, this); + + HINSTANCE instance = GetModuleFromAddress(&WndProcThunk); + WNDCLASSEX wc = {0}; + wc.cbSize = sizeof(wc); + wc.lpfnWndProc = base::win::WrappedWindowProc; + wc.hInstance = instance; + wc.lpszClassName = class_name.c_str(); + atom_ = RegisterClassEx(&wc); + DCHECK(atom_); + + message_hwnd_ = CreateWindow(MAKEINTATOM(atom_), 0, 0, 0, 0, 0, 0, + HWND_MESSAGE, 0, instance, 0); + DCHECK(message_hwnd_); +} + +void MessagePumpForUI::WaitForWork() { + // Wait until a message is available, up to the time needed by the timer + // manager to fire the next set of timers. + int delay = GetCurrentDelay(); + if (delay < 0) // Negative value means no timers waiting. + delay = INFINITE; + + DWORD result; + result = MsgWaitForMultipleObjectsEx(0, NULL, delay, QS_ALLINPUT, + MWMO_INPUTAVAILABLE); + + if (WAIT_OBJECT_0 == result) { + // A WM_* message is available. + // If a parent child relationship exists between windows across threads + // then their thread inputs are implicitly attached. + // This causes the MsgWaitForMultipleObjectsEx API to return indicating + // that messages are ready for processing (Specifically, mouse messages + // intended for the child window may appear if the child window has + // capture). + // The subsequent PeekMessages call may fail to return any messages thus + // causing us to enter a tight loop at times. + // The WaitMessage call below is a workaround to give the child window + // some time to process its input messages. + MSG msg = {0}; + DWORD queue_status = GetQueueStatus(QS_MOUSE); + if (HIWORD(queue_status) & QS_MOUSE && + !PeekMessage(&msg, NULL, WM_MOUSEFIRST, WM_MOUSELAST, PM_NOREMOVE)) { + WaitMessage(); + } + return; + } + + DCHECK_NE(WAIT_FAILED, result) << GetLastError(); +} + +void MessagePumpForUI::HandleWorkMessage() { + // If we are being called outside of the context of Run, then don't try to do + // any work. This could correspond to a MessageBox call or something of that + // sort. + if (!state_) { + // Since we handled a kMsgHaveWork message, we must still update this flag. + InterlockedExchange(&have_work_, 0); + return; + } + + // Let whatever would have run had we not been putting messages in the queue + // run now. This is an attempt to make our dummy message not starve other + // messages that may be in the Windows message queue. + ProcessPumpReplacementMessage(); + + // Now give the delegate a chance to do some work. He'll let us know if he + // needs to do more work. + if (state_->delegate->DoWork()) + ScheduleWork(); +} + +void MessagePumpForUI::HandleTimerMessage() { + KillTimer(message_hwnd_, reinterpret_cast(this)); + + // If we are being called outside of the context of Run, then don't do + // anything. This could correspond to a MessageBox call or something of + // that sort. + if (!state_) + return; + + state_->delegate->DoDelayedWork(&delayed_work_time_); + if (!delayed_work_time_.is_null()) { + // A bit gratuitous to set delayed_work_time_ again, but oh well. + ScheduleDelayedWork(delayed_work_time_); + } +} + +bool MessagePumpForUI::ProcessNextWindowsMessage() { + // If there are sent messages in the queue then PeekMessage internally + // dispatches the message and returns false. We return true in this + // case to ensure that the message loop peeks again instead of calling + // MsgWaitForMultipleObjectsEx again. + bool sent_messages_in_queue = false; + DWORD queue_status = GetQueueStatus(QS_SENDMESSAGE); + if (HIWORD(queue_status) & QS_SENDMESSAGE) + sent_messages_in_queue = true; + + MSG msg; + if (message_filter_->DoPeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + return ProcessMessageHelper(msg); + + return sent_messages_in_queue; +} + +bool MessagePumpForUI::ProcessMessageHelper(const MSG& msg) { + TRACE_EVENT1("base", "MessagePumpForUI::ProcessMessageHelper", + "message", msg.message); + if (WM_QUIT == msg.message) { + // Repost the QUIT message so that it will be retrieved by the primary + // GetMessage() loop. + state_->should_quit = true; + PostQuitMessage(static_cast(msg.wParam)); + return false; + } + + // While running our main message pump, we discard kMsgHaveWork messages. + if (msg.message == kMsgHaveWork && msg.hwnd == message_hwnd_) + return ProcessPumpReplacementMessage(); + + if (CallMsgFilter(const_cast(&msg), kMessageFilterCode)) + return true; + + WillProcessMessage(msg); + + if (!message_filter_->ProcessMessage(msg)) { + if (state_->dispatcher) { + if (!state_->dispatcher->Dispatch(msg)) + state_->should_quit = true; + } else { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + DidProcessMessage(msg); + return true; +} + +bool MessagePumpForUI::ProcessPumpReplacementMessage() { + // When we encounter a kMsgHaveWork message, this method is called to peek + // and process a replacement message, such as a WM_PAINT or WM_TIMER. The + // goal is to make the kMsgHaveWork as non-intrusive as possible, even though + // a continuous stream of such messages are posted. This method carefully + // peeks a message while there is no chance for a kMsgHaveWork to be pending, + // then resets the have_work_ flag (allowing a replacement kMsgHaveWork to + // possibly be posted), and finally dispatches that peeked replacement. Note + // that the re-post of kMsgHaveWork may be asynchronous to this thread!! + + bool have_message = false; + MSG msg; + // We should not process all window messages if we are in the context of an + // OS modal loop, i.e. in the context of a windows API call like MessageBox. + // This is to ensure that these messages are peeked out by the OS modal loop. + if (MessageLoop::current()->os_modal_loop()) { + // We only peek out WM_PAINT and WM_TIMER here for reasons mentioned above. + have_message = PeekMessage(&msg, NULL, WM_PAINT, WM_PAINT, PM_REMOVE) || + PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, PM_REMOVE); + } else { + have_message = !!message_filter_->DoPeekMessage(&msg, NULL, 0, 0, + PM_REMOVE); + } + + DCHECK(!have_message || kMsgHaveWork != msg.message || + msg.hwnd != message_hwnd_); + + // Since we discarded a kMsgHaveWork message, we must update the flag. + int old_have_work = InterlockedExchange(&have_work_, 0); + DCHECK(old_have_work); + + // We don't need a special time slice if we didn't have_message to process. + if (!have_message) + return false; + + // Guarantee we'll get another time slice in the case where we go into native + // windows code. This ScheduleWork() may hurt performance a tiny bit when + // tasks appear very infrequently, but when the event queue is busy, the + // kMsgHaveWork events get (percentage wise) rarer and rarer. + ScheduleWork(); + return ProcessMessageHelper(msg); +} + +void MessagePumpForUI::SetMessageFilter( + scoped_ptr message_filter) { + message_filter_ = message_filter.Pass(); +} + +//----------------------------------------------------------------------------- +// MessagePumpForIO public: + +MessagePumpForIO::MessagePumpForIO() { + port_.Set(CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, NULL, 1)); + DCHECK(port_.IsValid()); +} + +void MessagePumpForIO::ScheduleWork() { + if (InterlockedExchange(&have_work_, 1)) + return; // Someone else continued the pumping. + + // Make sure the MessagePump does some work for us. + BOOL ret = PostQueuedCompletionStatus(port_, 0, + reinterpret_cast(this), + reinterpret_cast(this)); + if (ret) + return; // Post worked perfectly. + + // See comment in MessagePumpForUI::ScheduleWork() for this error recovery. + InterlockedExchange(&have_work_, 0); // Clarify that we didn't succeed. + UMA_HISTOGRAM_ENUMERATION("Chrome.MessageLoopProblem", COMPLETION_POST_ERROR, + MESSAGE_LOOP_PROBLEM_MAX); +} + +void MessagePumpForIO::ScheduleDelayedWork(const TimeTicks& delayed_work_time) { + // We know that we can't be blocked right now since this method can only be + // called on the same thread as Run, so we only need to update our record of + // how long to sleep when we do sleep. + delayed_work_time_ = delayed_work_time; +} + +void MessagePumpForIO::RegisterIOHandler(HANDLE file_handle, + IOHandler* handler) { + ULONG_PTR key = HandlerToKey(handler, true); + HANDLE port = CreateIoCompletionPort(file_handle, port_, key, 1); + DPCHECK(port); +} + +bool MessagePumpForIO::RegisterJobObject(HANDLE job_handle, + IOHandler* handler) { + // Job object notifications use the OVERLAPPED pointer to carry the message + // data. Mark the completion key correspondingly, so we will not try to + // convert OVERLAPPED* to IOContext*. + ULONG_PTR key = HandlerToKey(handler, false); + JOBOBJECT_ASSOCIATE_COMPLETION_PORT info; + info.CompletionKey = reinterpret_cast(key); + info.CompletionPort = port_; + return SetInformationJobObject(job_handle, + JobObjectAssociateCompletionPortInformation, + &info, + sizeof(info)) != FALSE; +} + +//----------------------------------------------------------------------------- +// MessagePumpForIO private: + +void MessagePumpForIO::DoRunLoop() { + for (;;) { + // If we do any work, we may create more messages etc., and more work may + // possibly be waiting in another task group. When we (for example) + // WaitForIOCompletion(), there is a good chance there are still more + // messages waiting. On the other hand, when any of these methods return + // having done no work, then it is pretty unlikely that calling them + // again quickly will find any work to do. Finally, if they all say they + // had no work, then it is a good time to consider sleeping (waiting) for + // more work. + + bool more_work_is_plausible = state_->delegate->DoWork(); + if (state_->should_quit) + break; + + more_work_is_plausible |= WaitForIOCompletion(0, NULL); + if (state_->should_quit) + break; + + more_work_is_plausible |= + state_->delegate->DoDelayedWork(&delayed_work_time_); + if (state_->should_quit) + break; + + if (more_work_is_plausible) + continue; + + more_work_is_plausible = state_->delegate->DoIdleWork(); + if (state_->should_quit) + break; + + if (more_work_is_plausible) + continue; + + WaitForWork(); // Wait (sleep) until we have work to do again. + } +} + +// Wait until IO completes, up to the time needed by the timer manager to fire +// the next set of timers. +void MessagePumpForIO::WaitForWork() { + // We do not support nested IO message loops. This is to avoid messy + // recursion problems. + DCHECK_EQ(1, state_->run_depth) << "Cannot nest an IO message loop!"; + + int timeout = GetCurrentDelay(); + if (timeout < 0) // Negative value means no timers waiting. + timeout = INFINITE; + + WaitForIOCompletion(timeout, NULL); +} + +bool MessagePumpForIO::WaitForIOCompletion(DWORD timeout, IOHandler* filter) { + IOItem item; + if (completed_io_.empty() || !MatchCompletedIOItem(filter, &item)) { + // We have to ask the system for another IO completion. + if (!GetIOItem(timeout, &item)) + return false; + + if (ProcessInternalIOItem(item)) + return true; + } + + // If |item.has_valid_io_context| is false then |item.context| does not point + // to a context structure, and so should not be dereferenced, although it may + // still hold valid non-pointer data. + if (!item.has_valid_io_context || item.context->handler) { + if (filter && item.handler != filter) { + // Save this item for later + completed_io_.push_back(item); + } else { + DCHECK(!item.has_valid_io_context || + (item.context->handler == item.handler)); + WillProcessIOEvent(); + item.handler->OnIOCompleted(item.context, item.bytes_transfered, + item.error); + DidProcessIOEvent(); + } + } else { + // The handler must be gone by now, just cleanup the mess. + delete item.context; + } + return true; +} + +// Asks the OS for another IO completion result. +bool MessagePumpForIO::GetIOItem(DWORD timeout, IOItem* item) { + memset(item, 0, sizeof(*item)); + ULONG_PTR key = NULL; + OVERLAPPED* overlapped = NULL; + if (!GetQueuedCompletionStatus(port_.Get(), &item->bytes_transfered, &key, + &overlapped, timeout)) { + if (!overlapped) + return false; // Nothing in the queue. + item->error = GetLastError(); + item->bytes_transfered = 0; + } + + item->handler = KeyToHandler(key, &item->has_valid_io_context); + item->context = reinterpret_cast(overlapped); + return true; +} + +bool MessagePumpForIO::ProcessInternalIOItem(const IOItem& item) { + if (this == reinterpret_cast(item.context) && + this == reinterpret_cast(item.handler)) { + // This is our internal completion. + DCHECK(!item.bytes_transfered); + InterlockedExchange(&have_work_, 0); + return true; + } + return false; +} + +// Returns a completion item that was previously received. +bool MessagePumpForIO::MatchCompletedIOItem(IOHandler* filter, IOItem* item) { + DCHECK(!completed_io_.empty()); + for (std::list::iterator it = completed_io_.begin(); + it != completed_io_.end(); ++it) { + if (!filter || it->handler == filter) { + *item = *it; + completed_io_.erase(it); + return true; + } + } + return false; +} + +void MessagePumpForIO::AddIOObserver(IOObserver *obs) { + io_observers_.AddObserver(obs); +} + +void MessagePumpForIO::RemoveIOObserver(IOObserver *obs) { + io_observers_.RemoveObserver(obs); +} + +void MessagePumpForIO::WillProcessIOEvent() { + FOR_EACH_OBSERVER(IOObserver, io_observers_, WillProcessIOEvent()); +} + +void MessagePumpForIO::DidProcessIOEvent() { + FOR_EACH_OBSERVER(IOObserver, io_observers_, DidProcessIOEvent()); +} + +// static +ULONG_PTR MessagePumpForIO::HandlerToKey(IOHandler* handler, + bool has_valid_io_context) { + ULONG_PTR key = reinterpret_cast(handler); + + // |IOHandler| is at least pointer-size aligned, so the lowest two bits are + // always cleared. We use the lowest bit to distinguish completion keys with + // and without the associated |IOContext|. + DCHECK((key & 1) == 0); + + // Mark the completion key as context-less. + if (!has_valid_io_context) + key = key | 1; + return key; +} + +// static +MessagePumpForIO::IOHandler* MessagePumpForIO::KeyToHandler( + ULONG_PTR key, + bool* has_valid_io_context) { + *has_valid_io_context = ((key & 1) == 0); + return reinterpret_cast(key & ~static_cast(1)); +} + +} // namespace base diff --git a/base/message_loop/message_pump_win.h b/base/message_loop/message_pump_win.h new file mode 100644 index 0000000000..2d833540e0 --- /dev/null +++ b/base/message_loop/message_pump_win.h @@ -0,0 +1,396 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MESSAGE_LOOP_MESSAGE_PUMP_WIN_H_ +#define BASE_MESSAGE_LOOP_MESSAGE_PUMP_WIN_H_ + +#include + +#include + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_pump.h" +#include "base/message_loop/message_pump_dispatcher.h" +#include "base/message_loop/message_pump_observer.h" +#include "base/observer_list.h" +#include "base/time/time.h" +#include "base/win/scoped_handle.h" + +namespace base { + +// MessagePumpWin serves as the base for specialized versions of the MessagePump +// for Windows. It provides basic functionality like handling of observers and +// controlling the lifetime of the message pump. +class BASE_EXPORT MessagePumpWin : public MessagePump { + public: + MessagePumpWin() : have_work_(0), state_(NULL) {} + virtual ~MessagePumpWin() {} + + // Add an Observer, which will start receiving notifications immediately. + void AddObserver(MessagePumpObserver* observer); + + // Remove an Observer. It is safe to call this method while an Observer is + // receiving a notification callback. + void RemoveObserver(MessagePumpObserver* observer); + + // Give a chance to code processing additional messages to notify the + // message loop observers that another message has been processed. + void WillProcessMessage(const MSG& msg); + void DidProcessMessage(const MSG& msg); + + // Like MessagePump::Run, but MSG objects are routed through dispatcher. + void RunWithDispatcher(Delegate* delegate, MessagePumpDispatcher* dispatcher); + + // MessagePump methods: + virtual void Run(Delegate* delegate) { RunWithDispatcher(delegate, NULL); } + virtual void Quit(); + + protected: + struct RunState { + Delegate* delegate; + MessagePumpDispatcher* dispatcher; + + // Used to flag that the current Run() invocation should return ASAP. + bool should_quit; + + // Used to count how many Run() invocations are on the stack. + int run_depth; + }; + + virtual void DoRunLoop() = 0; + int GetCurrentDelay() const; + + ObserverList observers_; + + // The time at which delayed work should run. + TimeTicks delayed_work_time_; + + // A boolean value used to indicate if there is a kMsgDoWork message pending + // in the Windows Message queue. There is at most one such message, and it + // can drive execution of tasks when a native message pump is running. + LONG have_work_; + + // State for the current invocation of Run. + RunState* state_; +}; + +//----------------------------------------------------------------------------- +// MessagePumpForUI extends MessagePumpWin with methods that are particular to a +// MessageLoop instantiated with TYPE_UI. +// +// MessagePumpForUI implements a "traditional" Windows message pump. It contains +// a nearly infinite loop that peeks out messages, and then dispatches them. +// Intermixed with those peeks are callouts to DoWork for pending tasks, and +// DoDelayedWork for pending timers. When there are no events to be serviced, +// this pump goes into a wait state. In most cases, this message pump handles +// all processing. +// +// However, when a task, or windows event, invokes on the stack a native dialog +// box or such, that window typically provides a bare bones (native?) message +// pump. That bare-bones message pump generally supports little more than a +// peek of the Windows message queue, followed by a dispatch of the peeked +// message. MessageLoop extends that bare-bones message pump to also service +// Tasks, at the cost of some complexity. +// +// The basic structure of the extension (refered to as a sub-pump) is that a +// special message, kMsgHaveWork, is repeatedly injected into the Windows +// Message queue. Each time the kMsgHaveWork message is peeked, checks are +// made for an extended set of events, including the availability of Tasks to +// run. +// +// After running a task, the special message kMsgHaveWork is again posted to +// the Windows Message queue, ensuring a future time slice for processing a +// future event. To prevent flooding the Windows Message queue, care is taken +// to be sure that at most one kMsgHaveWork message is EVER pending in the +// Window's Message queue. +// +// There are a few additional complexities in this system where, when there are +// no Tasks to run, this otherwise infinite stream of messages which drives the +// sub-pump is halted. The pump is automatically re-started when Tasks are +// queued. +// +// A second complexity is that the presence of this stream of posted tasks may +// prevent a bare-bones message pump from ever peeking a WM_PAINT or WM_TIMER. +// Such paint and timer events always give priority to a posted message, such as +// kMsgHaveWork messages. As a result, care is taken to do some peeking in +// between the posting of each kMsgHaveWork message (i.e., after kMsgHaveWork +// is peeked, and before a replacement kMsgHaveWork is posted). +// +// NOTE: Although it may seem odd that messages are used to start and stop this +// flow (as opposed to signaling objects, etc.), it should be understood that +// the native message pump will *only* respond to messages. As a result, it is +// an excellent choice. It is also helpful that the starter messages that are +// placed in the queue when new task arrive also awakens DoRunLoop. +// +class BASE_EXPORT MessagePumpForUI : public MessagePumpWin { + public: + // A MessageFilter implements the common Peek/Translate/Dispatch code to deal + // with windows messages. + // This abstraction is used to inject TSF message peeking. See + // TextServicesMessageFilter. + class BASE_EXPORT MessageFilter { + public: + virtual ~MessageFilter() {} + // Implements the functionality exposed by the OS through PeekMessage. + virtual BOOL DoPeekMessage(MSG* msg, + HWND window_handle, + UINT msg_filter_min, + UINT msg_filter_max, + UINT remove_msg) { + return PeekMessage(msg, window_handle, msg_filter_min, msg_filter_max, + remove_msg); + } + // Returns true if |message| was consumed by the filter and no extra + // processing is required. If this method returns false, it is the + // responsibility of the caller to ensure that normal processing takes + // place. + // The priority to consume messages is the following: + // - Native Windows' message filter (CallMsgFilter). + // - MessageFilter::ProcessMessage. + // - MessagePumpDispatcher. + // - TranslateMessage / DispatchMessage. + virtual bool ProcessMessage(const MSG& msg) { return false;} + }; + // The application-defined code passed to the hook procedure. + static const int kMessageFilterCode = 0x5001; + + MessagePumpForUI(); + virtual ~MessagePumpForUI(); + + // Sets a new MessageFilter. MessagePumpForUI takes ownership of + // |message_filter|. When SetMessageFilter is called, old MessageFilter is + // deleted. + void SetMessageFilter(scoped_ptr message_filter); + + // MessagePump methods: + virtual void ScheduleWork(); + virtual void ScheduleDelayedWork(const TimeTicks& delayed_work_time); + + // Applications can call this to encourage us to process all pending WM_PAINT + // messages. This method will process all paint messages the Windows Message + // queue can provide, up to some fixed number (to avoid any infinite loops). + void PumpOutPendingPaintMessages(); + + private: + static LRESULT CALLBACK WndProcThunk(HWND window_handle, + UINT message, + WPARAM wparam, + LPARAM lparam); + virtual void DoRunLoop(); + void InitMessageWnd(); + void WaitForWork(); + void HandleWorkMessage(); + void HandleTimerMessage(); + bool ProcessNextWindowsMessage(); + bool ProcessMessageHelper(const MSG& msg); + bool ProcessPumpReplacementMessage(); + + // Atom representing the registered window class. + ATOM atom_; + + // A hidden message-only window. + HWND message_hwnd_; + + scoped_ptr message_filter_; +}; + +//----------------------------------------------------------------------------- +// MessagePumpForIO extends MessagePumpWin with methods that are particular to a +// MessageLoop instantiated with TYPE_IO. This version of MessagePump does not +// deal with Windows mesagges, and instead has a Run loop based on Completion +// Ports so it is better suited for IO operations. +// +class BASE_EXPORT MessagePumpForIO : public MessagePumpWin { + public: + struct IOContext; + + // Clients interested in receiving OS notifications when asynchronous IO + // operations complete should implement this interface and register themselves + // with the message pump. + // + // Typical use #1: + // // Use only when there are no user's buffers involved on the actual IO, + // // so that all the cleanup can be done by the message pump. + // class MyFile : public IOHandler { + // MyFile() { + // ... + // context_ = new IOContext; + // context_->handler = this; + // message_pump->RegisterIOHandler(file_, this); + // } + // ~MyFile() { + // if (pending_) { + // // By setting the handler to NULL, we're asking for this context + // // to be deleted when received, without calling back to us. + // context_->handler = NULL; + // } else { + // delete context_; + // } + // } + // virtual void OnIOCompleted(IOContext* context, DWORD bytes_transfered, + // DWORD error) { + // pending_ = false; + // } + // void DoSomeIo() { + // ... + // // The only buffer required for this operation is the overlapped + // // structure. + // ConnectNamedPipe(file_, &context_->overlapped); + // pending_ = true; + // } + // bool pending_; + // IOContext* context_; + // HANDLE file_; + // }; + // + // Typical use #2: + // class MyFile : public IOHandler { + // MyFile() { + // ... + // message_pump->RegisterIOHandler(file_, this); + // } + // // Plus some code to make sure that this destructor is not called + // // while there are pending IO operations. + // ~MyFile() { + // } + // virtual void OnIOCompleted(IOContext* context, DWORD bytes_transfered, + // DWORD error) { + // ... + // delete context; + // } + // void DoSomeIo() { + // ... + // IOContext* context = new IOContext; + // // This is not used for anything. It just prevents the context from + // // being considered "abandoned". + // context->handler = this; + // ReadFile(file_, buffer, num_bytes, &read, &context->overlapped); + // } + // HANDLE file_; + // }; + // + // Typical use #3: + // Same as the previous example, except that in order to deal with the + // requirement stated for the destructor, the class calls WaitForIOCompletion + // from the destructor to block until all IO finishes. + // ~MyFile() { + // while(pending_) + // message_pump->WaitForIOCompletion(INFINITE, this); + // } + // + class IOHandler { + public: + virtual ~IOHandler() {} + // This will be called once the pending IO operation associated with + // |context| completes. |error| is the Win32 error code of the IO operation + // (ERROR_SUCCESS if there was no error). |bytes_transfered| will be zero + // on error. + virtual void OnIOCompleted(IOContext* context, DWORD bytes_transfered, + DWORD error) = 0; + }; + + // An IOObserver is an object that receives IO notifications from the + // MessagePump. + // + // NOTE: An IOObserver implementation should be extremely fast! + class IOObserver { + public: + IOObserver() {} + + virtual void WillProcessIOEvent() = 0; + virtual void DidProcessIOEvent() = 0; + + protected: + virtual ~IOObserver() {} + }; + + // The extended context that should be used as the base structure on every + // overlapped IO operation. |handler| must be set to the registered IOHandler + // for the given file when the operation is started, and it can be set to NULL + // before the operation completes to indicate that the handler should not be + // called anymore, and instead, the IOContext should be deleted when the OS + // notifies the completion of this operation. Please remember that any buffers + // involved with an IO operation should be around until the callback is + // received, so this technique can only be used for IO that do not involve + // additional buffers (other than the overlapped structure itself). + struct IOContext { + OVERLAPPED overlapped; + IOHandler* handler; + }; + + MessagePumpForIO(); + virtual ~MessagePumpForIO() {} + + // MessagePump methods: + virtual void ScheduleWork(); + virtual void ScheduleDelayedWork(const TimeTicks& delayed_work_time); + + // Register the handler to be used when asynchronous IO for the given file + // completes. The registration persists as long as |file_handle| is valid, so + // |handler| must be valid as long as there is pending IO for the given file. + void RegisterIOHandler(HANDLE file_handle, IOHandler* handler); + + // Register the handler to be used to process job events. The registration + // persists as long as the job object is live, so |handler| must be valid + // until the job object is destroyed. Returns true if the registration + // succeeded, and false otherwise. + bool RegisterJobObject(HANDLE job_handle, IOHandler* handler); + + // Waits for the next IO completion that should be processed by |filter|, for + // up to |timeout| milliseconds. Return true if any IO operation completed, + // regardless of the involved handler, and false if the timeout expired. If + // the completion port received any message and the involved IO handler + // matches |filter|, the callback is called before returning from this code; + // if the handler is not the one that we are looking for, the callback will + // be postponed for another time, so reentrancy problems can be avoided. + // External use of this method should be reserved for the rare case when the + // caller is willing to allow pausing regular task dispatching on this thread. + bool WaitForIOCompletion(DWORD timeout, IOHandler* filter); + + void AddIOObserver(IOObserver* obs); + void RemoveIOObserver(IOObserver* obs); + + private: + struct IOItem { + IOHandler* handler; + IOContext* context; + DWORD bytes_transfered; + DWORD error; + + // In some cases |context| can be a non-pointer value casted to a pointer. + // |has_valid_io_context| is true if |context| is a valid IOContext + // pointer, and false otherwise. + bool has_valid_io_context; + }; + + virtual void DoRunLoop(); + void WaitForWork(); + bool MatchCompletedIOItem(IOHandler* filter, IOItem* item); + bool GetIOItem(DWORD timeout, IOItem* item); + bool ProcessInternalIOItem(const IOItem& item); + void WillProcessIOEvent(); + void DidProcessIOEvent(); + + // Converts an IOHandler pointer to a completion port key. + // |has_valid_io_context| specifies whether completion packets posted to + // |handler| will have valid OVERLAPPED pointers. + static ULONG_PTR HandlerToKey(IOHandler* handler, bool has_valid_io_context); + + // Converts a completion port key to an IOHandler pointer. + static IOHandler* KeyToHandler(ULONG_PTR key, bool* has_valid_io_context); + + // The completion port associated with this thread. + win::ScopedHandle port_; + // This list will be empty almost always. It stores IO completions that have + // not been delivered yet because somebody was doing cleanup. + std::list completed_io_; + + ObserverList io_observers_; +}; + +} // namespace base + +#endif // BASE_MESSAGE_LOOP_MESSAGE_PUMP_WIN_H_ diff --git a/base/metrics/OWNERS b/base/metrics/OWNERS new file mode 100644 index 0000000000..17a19451a2 --- /dev/null +++ b/base/metrics/OWNERS @@ -0,0 +1,8 @@ +# Primary OWNER +jar@chromium.org + +# Secondary OWNER; can review simpler changes +isherman@chromium.org + +# Note that all members of the parent file base/OWNERS can also stamp trivial +# changes, but will probably defer to Jim for meatier changes. diff --git a/base/metrics/bucket_ranges.cc b/base/metrics/bucket_ranges.cc new file mode 100644 index 0000000000..949c813e46 --- /dev/null +++ b/base/metrics/bucket_ranges.cc @@ -0,0 +1,139 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/metrics/bucket_ranges.h" + +#include + +#include "base/logging.h" + +namespace base { + +// Static table of checksums for all possible 8 bit bytes. +const uint32 kCrcTable[256] = { 0x0, 0x77073096L, 0xee0e612cL, +0x990951baL, 0x76dc419L, 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0xedb8832L, +0x79dcb8a4L, 0xe0d5e91eL, 0x97d2d988L, 0x9b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, +0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, 0x1adad47dL, +0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L, 0x646ba8c0L, 0xfd62f97aL, +0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, 0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, +0x4c69105eL, 0xd56041e4L, 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, +0xa50ab56bL, 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, +0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL, 0xc8d75180L, +0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, 0xb8bda50fL, 0x2802b89eL, +0x5f058808L, 0xc60cd9b2L, 0xb10be924L, 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, +0xb6662d3dL, 0x76dc4190L, 0x1db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, +0x6b6b51fL, 0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0xf00f934L, 0x9609a88eL, +0xe10e9818L, 0x7f6a0dbbL, 0x86d3d2dL, 0x91646c97L, 0xe6635c01L, 0x6b6b51f4L, +0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, 0x1b01a57bL, 0x8208f4c1L, +0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L, 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, +0x15da2d49L, 0x8cd37cf3L, 0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, +0xd4bb30e2L, 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL, +0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, 0xaa0a4c5fL, +0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L, 0xc90c2086L, 0x5768b525L, +0x206f85b3L, 0xb966d409L, 0xce61e49fL, 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, +0xc7d7a8b4L, 0x59b33d17L, 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, +0x9abfb3b6L, 0x3b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x4db2615L, +0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0xd6d6a3eL, 0x7a6a5aa8L, 0xe40ecf0bL, +0x9309ff9dL, 0xa00ae27L, 0x7d079eb1L, 0xf00f9344L, 0x8708a3d2L, 0x1e01f268L, +0x6906c2feL, 0xf762575dL, 0x806567cbL, 0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, +0x89d32be0L, 0x10da7a5aL, 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, +0x60b08ed5L, 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, +0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL, 0x36034af6L, +0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, 0x4669be79L, 0xcb61b38cL, +0xbc66831aL, 0x256fd2a0L, 0x5268e236L, 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, +0x5505262fL, 0xc5ba3bbeL, 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, +0xb5d0cf31L, 0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, +0x26d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x5005713L, 0x95bf4a82L, +0xe2b87a14L, 0x7bb12baeL, 0xcb61b38L, 0x92d28e9bL, 0xe5d5be0dL, 0x7cdcefb7L, +0xbdbdf21L, 0x86d3d2d4L, 0xf1d4e242L, 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, +0xf6b9265bL, 0x6fb077e1L, 0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, +0x11010b5cL, 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L, +0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, 0x4969474dL, +0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, 0x37d83bf0L, 0xa9bcae53L, +0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, +0x24b4a3a6L, 0xbad03605L, 0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, +0xc4614ab8L, 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, +0x2d02ef8dL, +}; + +// We generate the CRC-32 using the low order bits to select whether to XOR in +// the reversed polynomial 0xedb88320L. This is nice and simple, and allows us +// to keep the quotient in a uint32. Since we're not concerned about the nature +// of corruptions (i.e., we don't care about bit sequencing, since we are +// handling memory changes, which are more grotesque) so we don't bother to +// get the CRC correct for big-endian vs little-ending calculations. All we +// need is a nice hash, that tends to depend on all the bits of the sample, with +// very little chance of changes in one place impacting changes in another +// place. +static uint32 Crc32(uint32 sum, HistogramBase::Sample value) { + // TODO(jar): Switch to false and watch stats. + const bool kUseRealCrc = true; + + if (kUseRealCrc) { + union { + HistogramBase::Sample range; + unsigned char bytes[sizeof(HistogramBase::Sample)]; + } converter; + converter.range = value; + for (size_t i = 0; i < sizeof(converter); ++i) + sum = kCrcTable[(sum & 0xff) ^ converter.bytes[i]] ^ (sum >> 8); + } else { + // Use hash techniques provided in ReallyFastHash, except we don't care + // about "avalanching" (which would worsten the hash, and add collisions), + // and we don't care about edge cases since we have an even number of bytes. + union { + HistogramBase::Sample range; + uint16 ints[sizeof(HistogramBase::Sample) / 2]; + } converter; + DCHECK_EQ(sizeof(HistogramBase::Sample), sizeof(converter)); + converter.range = value; + sum += converter.ints[0]; + sum = (sum << 16) ^ sum ^ (static_cast(converter.ints[1]) << 11); + sum += sum >> 11; + } + return sum; +} + +BucketRanges::BucketRanges(size_t num_ranges) + : ranges_(num_ranges, 0), + checksum_(0) {} + +BucketRanges::~BucketRanges() {} + +void BucketRanges::set_range(size_t i, HistogramBase::Sample value) { + DCHECK_LT(i, ranges_.size()); + CHECK_GE(value, 0); + ranges_[i] = value; +} + +uint32 BucketRanges::CalculateChecksum() const { + // Seed checksum. + uint32 checksum = static_cast(ranges_.size()); + + for (size_t index = 0; index < ranges_.size(); ++index) + checksum = Crc32(checksum, ranges_[index]); + return checksum; +} + +bool BucketRanges::HasValidChecksum() const { + return CalculateChecksum() == checksum_; +} + +void BucketRanges::ResetChecksum() { + checksum_ = CalculateChecksum(); +} + +bool BucketRanges::Equals(const BucketRanges* other) const { + if (checksum_ != other->checksum_) + return false; + if (ranges_.size() != other->ranges_.size()) + return false; + for (size_t index = 0; index < ranges_.size(); ++index) { + if (ranges_[index] != other->ranges_[index]) + return false; + } + return true; +} + +} // namespace base diff --git a/base/metrics/bucket_ranges.h b/base/metrics/bucket_ranges.h new file mode 100644 index 0000000000..fe1152f5db --- /dev/null +++ b/base/metrics/bucket_ranges.h @@ -0,0 +1,79 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// BucketRanges stores the vector of ranges that delimit what samples are +// tallied in the corresponding buckets of a histogram. Histograms that have +// same ranges for all their corresponding buckets should share the same +// BucketRanges object. +// +// E.g. A 5 buckets LinearHistogram with 1 as minimal value and 4 as maximal +// value will need a BucketRanges with 6 ranges: +// 0, 1, 2, 3, 4, INT_MAX +// +// TODO(kaiwang): Currently we keep all negative values in 0~1 bucket. Consider +// changing 0 to INT_MIN. + +#ifndef BASE_METRICS_BUCKET_RANGES_H_ +#define BASE_METRICS_BUCKET_RANGES_H_ + +#include + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/metrics/histogram_base.h" + +namespace base { + +class BASE_EXPORT BucketRanges { + public: + typedef std::vector Ranges; + + explicit BucketRanges(size_t num_ranges); + ~BucketRanges(); + + size_t size() const { return ranges_.size(); } + HistogramBase::Sample range(size_t i) const { return ranges_[i]; } + void set_range(size_t i, HistogramBase::Sample value); + uint32 checksum() const { return checksum_; } + void set_checksum(uint32 checksum) { checksum_ = checksum; } + + // A bucket is defined by a consecutive pair of entries in |ranges|, so there + // is one fewer bucket than there are ranges. For example, if |ranges| is + // [0, 1, 3, 7, INT_MAX], then the buckets in this histogram are + // [0, 1), [1, 3), [3, 7), and [7, INT_MAX). + size_t bucket_count() const { return ranges_.size() - 1; } + + // Checksum methods to verify whether the ranges are corrupted (e.g. bad + // memory access). + uint32 CalculateChecksum() const; + bool HasValidChecksum() const; + void ResetChecksum(); + + // Return true iff |other| object has same ranges_ as |this| object's ranges_. + bool Equals(const BucketRanges* other) const; + + private: + // A monotonically increasing list of values which determine which bucket to + // put a sample into. For each index, show the smallest sample that can be + // added to the corresponding bucket. + Ranges ranges_; + + // Checksum for the conntents of ranges_. Used to detect random over-writes + // of our data, and to quickly see if some other BucketRanges instance is + // possibly Equal() to this instance. + // TODO(kaiwang): Consider change this to uint64. Because we see a lot of + // noise on UMA dashboard. + uint32 checksum_; + + DISALLOW_COPY_AND_ASSIGN(BucketRanges); +}; + +////////////////////////////////////////////////////////////////////////////// +// Expose only for test. +BASE_EXPORT_PRIVATE extern const uint32 kCrcTable[256]; + +} // namespace base + +#endif // BASE_METRICS_BUCKET_RANGES_H_ diff --git a/base/metrics/bucket_ranges_unittest.cc b/base/metrics/bucket_ranges_unittest.cc new file mode 100644 index 0000000000..fc0699c733 --- /dev/null +++ b/base/metrics/bucket_ranges_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/metrics/bucket_ranges.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace { + +TEST(BucketRangesTest, NormalSetup) { + BucketRanges ranges(5); + ASSERT_EQ(5u, ranges.size()); + ASSERT_EQ(4u, ranges.bucket_count()); + + for (int i = 0; i < 5; ++i) { + EXPECT_EQ(0, ranges.range(i)); + } + EXPECT_EQ(0u, ranges.checksum()); + + ranges.set_range(3, 100); + EXPECT_EQ(100, ranges.range(3)); +} + +TEST(BucketRangesTest, Equals) { + // Compare empty ranges. + BucketRanges ranges1(3); + BucketRanges ranges2(3); + BucketRanges ranges3(5); + + EXPECT_TRUE(ranges1.Equals(&ranges2)); + EXPECT_FALSE(ranges1.Equals(&ranges3)); + EXPECT_FALSE(ranges2.Equals(&ranges3)); + + // Compare full filled ranges. + ranges1.set_range(0, 0); + ranges1.set_range(1, 1); + ranges1.set_range(2, 2); + ranges1.set_checksum(100); + ranges2.set_range(0, 0); + ranges2.set_range(1, 1); + ranges2.set_range(2, 2); + ranges2.set_checksum(100); + + EXPECT_TRUE(ranges1.Equals(&ranges2)); + + // Checksum does not match. + ranges1.set_checksum(99); + EXPECT_FALSE(ranges1.Equals(&ranges2)); + ranges1.set_checksum(100); + + // Range does not match. + ranges1.set_range(1, 3); + EXPECT_FALSE(ranges1.Equals(&ranges2)); +} + +TEST(BucketRangesTest, Checksum) { + BucketRanges ranges(3); + ranges.set_range(0, 0); + ranges.set_range(1, 1); + ranges.set_range(2, 2); + + ranges.ResetChecksum(); + EXPECT_EQ(289217253u, ranges.checksum()); + + ranges.set_range(2, 3); + EXPECT_FALSE(ranges.HasValidChecksum()); + + ranges.ResetChecksum(); + EXPECT_EQ(2843835776u, ranges.checksum()); + EXPECT_TRUE(ranges.HasValidChecksum()); +} + +// Table was generated similarly to sample code for CRC-32 given on: +// http://www.w3.org/TR/PNG/#D-CRCAppendix. +TEST(BucketRangesTest, Crc32TableTest) { + for (int i = 0; i < 256; ++i) { + uint32 checksum = i; + for (int j = 0; j < 8; ++j) { + const uint32 kReversedPolynomial = 0xedb88320L; + if (checksum & 1) + checksum = kReversedPolynomial ^ (checksum >> 1); + else + checksum >>= 1; + } + EXPECT_EQ(kCrcTable[i], checksum); + } +} + +} // namespace +} // namespace base diff --git a/base/metrics/field_trial.cc b/base/metrics/field_trial.cc new file mode 100644 index 0000000000..4456a79716 --- /dev/null +++ b/base/metrics/field_trial.cc @@ -0,0 +1,505 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/metrics/field_trial.h" + +#include "base/build_time.h" +#include "base/logging.h" +#include "base/rand_util.h" +#include "base/sha1.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/sys_byteorder.h" + +namespace base { + +namespace { + +// Created a time value based on |year|, |month| and |day_of_month| parameters. +Time CreateTimeFromParams(int year, int month, int day_of_month) { + DCHECK_GT(year, 1970); + DCHECK_GT(month, 0); + DCHECK_LT(month, 13); + DCHECK_GT(day_of_month, 0); + DCHECK_LT(day_of_month, 32); + + Time::Exploded exploded; + exploded.year = year; + exploded.month = month; + exploded.day_of_week = 0; // Should be unused. + exploded.day_of_month = day_of_month; + exploded.hour = 0; + exploded.minute = 0; + exploded.second = 0; + exploded.millisecond = 0; + + return Time::FromLocalExploded(exploded); +} + +} // namespace + +static const char kHistogramFieldTrialSeparator('_'); + +// statics +const int FieldTrial::kNotFinalized = -1; +const int FieldTrial::kDefaultGroupNumber = 0; +bool FieldTrial::enable_benchmarking_ = false; + +const char FieldTrialList::kPersistentStringSeparator('/'); +int FieldTrialList::kNoExpirationYear = 0; + +//------------------------------------------------------------------------------ +// FieldTrial methods and members. + +FieldTrial::FieldTrial(const std::string& trial_name, + const Probability total_probability, + const std::string& default_group_name, + double entropy_value) + : trial_name_(trial_name), + divisor_(total_probability), + default_group_name_(default_group_name), + random_(static_cast(divisor_ * entropy_value)), + accumulated_group_probability_(0), + next_group_number_(kDefaultGroupNumber + 1), + group_(kNotFinalized), + enable_field_trial_(true), + forced_(false), + group_reported_(false) { + DCHECK_GT(total_probability, 0); + DCHECK(!trial_name_.empty()); + DCHECK(!default_group_name_.empty()); +} + +FieldTrial::EntropyProvider::~EntropyProvider() { +} + +void FieldTrial::Disable() { + DCHECK(!group_reported_); + enable_field_trial_ = false; + + // In case we are disabled after initialization, we need to switch + // the trial to the default group. + if (group_ != kNotFinalized) { + // Only reset when not already the default group, because in case we were + // forced to the default group, the group number may not be + // kDefaultGroupNumber, so we should keep it as is. + if (group_name_ != default_group_name_) + SetGroupChoice(default_group_name_, kDefaultGroupNumber); + } +} + +int FieldTrial::AppendGroup(const std::string& name, + Probability group_probability) { + // When the group choice was previously forced, we only need to return the + // the id of the chosen group, and anything can be returned for the others. + if (forced_) { + DCHECK(!group_name_.empty()); + if (name == group_name_) { + // Note that while |group_| may be equal to |kDefaultGroupNumber| on the + // forced trial, it will not have the same value as the default group + // number returned from the non-forced |FactoryGetFieldTrial()| call, + // which takes care to ensure that this does not happen. + return group_; + } + DCHECK_NE(next_group_number_, group_); + // We still return different numbers each time, in case some caller need + // them to be different. + return next_group_number_++; + } + + DCHECK_LE(group_probability, divisor_); + DCHECK_GE(group_probability, 0); + + if (enable_benchmarking_ || !enable_field_trial_) + group_probability = 0; + + accumulated_group_probability_ += group_probability; + + DCHECK_LE(accumulated_group_probability_, divisor_); + if (group_ == kNotFinalized && accumulated_group_probability_ > random_) { + // This is the group that crossed the random line, so we do the assignment. + SetGroupChoice(name, next_group_number_); + } + return next_group_number_++; +} + +int FieldTrial::group() { + FinalizeGroupChoice(); + FieldTrialList::NotifyFieldTrialGroupSelection(this); + return group_; +} + +const std::string& FieldTrial::group_name() { + // Call |group()| to ensure group gets assigned and observers are notified. + group(); + DCHECK(!group_name_.empty()); + return group_name_; +} + +// static +std::string FieldTrial::MakeName(const std::string& name_prefix, + const std::string& trial_name) { + std::string big_string(name_prefix); + big_string.append(1, kHistogramFieldTrialSeparator); + return big_string.append(FieldTrialList::FindFullName(trial_name)); +} + +// static +void FieldTrial::EnableBenchmarking() { + DCHECK_EQ(0u, FieldTrialList::GetFieldTrialCount()); + enable_benchmarking_ = true; +} + +void FieldTrial::SetForced() { + // We might have been forced before (e.g., by CreateFieldTrial) and it's + // first come first served, e.g., command line switch has precedence. + if (forced_) + return; + + // And we must finalize the group choice before we mark ourselves as forced. + FinalizeGroupChoice(); + forced_ = true; +} + +FieldTrial::~FieldTrial() {} + +void FieldTrial::SetGroupChoice(const std::string& group_name, int number) { + group_ = number; + if (group_name.empty()) + StringAppendF(&group_name_, "%d", group_); + else + group_name_ = group_name; + DVLOG(1) << "Field trial: " << trial_name_ << " Group choice:" << group_name_; +} + +void FieldTrial::FinalizeGroupChoice() { + if (group_ != kNotFinalized) + return; + accumulated_group_probability_ = divisor_; + // Here it's OK to use |kDefaultGroupNumber| since we can't be forced and not + // finalized. + DCHECK(!forced_); + SetGroupChoice(default_group_name_, kDefaultGroupNumber); +} + +bool FieldTrial::GetActiveGroup(ActiveGroup* active_group) const { + if (!group_reported_ || !enable_field_trial_) + return false; + DCHECK_NE(group_, kNotFinalized); + active_group->trial_name = trial_name_; + active_group->group_name = group_name_; + return true; +} + +//------------------------------------------------------------------------------ +// FieldTrialList methods and members. + +// static +FieldTrialList* FieldTrialList::global_ = NULL; + +// static +bool FieldTrialList::used_without_global_ = false; + +FieldTrialList::Observer::~Observer() { +} + +FieldTrialList::FieldTrialList( + const FieldTrial::EntropyProvider* entropy_provider) + : entropy_provider_(entropy_provider), + observer_list_(new ObserverListThreadSafe( + ObserverListBase::NOTIFY_EXISTING_ONLY)) { + DCHECK(!global_); + DCHECK(!used_without_global_); + global_ = this; + + Time two_years_from_build_time = GetBuildTime() + TimeDelta::FromDays(730); + Time::Exploded exploded; + two_years_from_build_time.LocalExplode(&exploded); + kNoExpirationYear = exploded.year; +} + +FieldTrialList::~FieldTrialList() { + AutoLock auto_lock(lock_); + while (!registered_.empty()) { + RegistrationList::iterator it = registered_.begin(); + it->second->Release(); + registered_.erase(it->first); + } + DCHECK_EQ(this, global_); + global_ = NULL; +} + +// static +FieldTrial* FieldTrialList::FactoryGetFieldTrial( + const std::string& trial_name, + FieldTrial::Probability total_probability, + const std::string& default_group_name, + const int year, + const int month, + const int day_of_month, + FieldTrial::RandomizationType randomization_type, + int* default_group_number) { + return FactoryGetFieldTrialWithRandomizationSeed( + trial_name, total_probability, default_group_name, + year, month, day_of_month, randomization_type, 0, default_group_number); +} + +// static +FieldTrial* FieldTrialList::FactoryGetFieldTrialWithRandomizationSeed( + const std::string& trial_name, + FieldTrial::Probability total_probability, + const std::string& default_group_name, + const int year, + const int month, + const int day_of_month, + FieldTrial::RandomizationType randomization_type, + uint32 randomization_seed, + int* default_group_number) { + if (default_group_number) + *default_group_number = FieldTrial::kDefaultGroupNumber; + // Check if the field trial has already been created in some other way. + FieldTrial* existing_trial = Find(trial_name); + if (existing_trial) { + CHECK(existing_trial->forced_); + // If the default group name differs between the existing forced trial + // and this trial, then use a different value for the default group number. + if (default_group_number && + default_group_name != existing_trial->default_group_name()) { + // If the new default group number corresponds to the group that was + // chosen for the forced trial (which has been finalized when it was + // forced), then set the default group number to that. + if (default_group_name == existing_trial->group_name_internal()) { + *default_group_number = existing_trial->group_; + } else { + // Otherwise, use |kNonConflictingGroupNumber| (-2) for the default + // group number, so that it does not conflict with the |AppendGroup()| + // result for the chosen group. + const int kNonConflictingGroupNumber = -2; + COMPILE_ASSERT( + kNonConflictingGroupNumber != FieldTrial::kDefaultGroupNumber, + conflicting_default_group_number); + COMPILE_ASSERT( + kNonConflictingGroupNumber != FieldTrial::kNotFinalized, + conflicting_default_group_number); + *default_group_number = kNonConflictingGroupNumber; + } + } + return existing_trial; + } + + double entropy_value; + if (randomization_type == FieldTrial::ONE_TIME_RANDOMIZED) { + entropy_value = GetEntropyProviderForOneTimeRandomization()-> + GetEntropyForTrial(trial_name, randomization_seed); + } else { + DCHECK_EQ(FieldTrial::SESSION_RANDOMIZED, randomization_type); + DCHECK_EQ(0U, randomization_seed); + entropy_value = RandDouble(); + } + + FieldTrial* field_trial = new FieldTrial(trial_name, total_probability, + default_group_name, entropy_value); + if (GetBuildTime() > CreateTimeFromParams(year, month, day_of_month)) + field_trial->Disable(); + FieldTrialList::Register(field_trial); + return field_trial; +} + +// static +FieldTrial* FieldTrialList::Find(const std::string& name) { + if (!global_) + return NULL; + AutoLock auto_lock(global_->lock_); + return global_->PreLockedFind(name); +} + +// static +int FieldTrialList::FindValue(const std::string& name) { + FieldTrial* field_trial = Find(name); + if (field_trial) + return field_trial->group(); + return FieldTrial::kNotFinalized; +} + +// static +std::string FieldTrialList::FindFullName(const std::string& name) { + FieldTrial* field_trial = Find(name); + if (field_trial) + return field_trial->group_name(); + return std::string(); +} + +// static +bool FieldTrialList::TrialExists(const std::string& name) { + return Find(name) != NULL; +} + +// static +void FieldTrialList::StatesToString(std::string* output) { + FieldTrial::ActiveGroups active_groups; + GetActiveFieldTrialGroups(&active_groups); + for (FieldTrial::ActiveGroups::const_iterator it = active_groups.begin(); + it != active_groups.end(); ++it) { + DCHECK_EQ(std::string::npos, + it->trial_name.find(kPersistentStringSeparator)); + DCHECK_EQ(std::string::npos, + it->group_name.find(kPersistentStringSeparator)); + output->append(it->trial_name); + output->append(1, kPersistentStringSeparator); + output->append(it->group_name); + output->append(1, kPersistentStringSeparator); + } +} + +// static +void FieldTrialList::GetActiveFieldTrialGroups( + FieldTrial::ActiveGroups* active_groups) { + DCHECK(active_groups->empty()); + if (!global_) + return; + AutoLock auto_lock(global_->lock_); + + for (RegistrationList::iterator it = global_->registered_.begin(); + it != global_->registered_.end(); ++it) { + FieldTrial::ActiveGroup active_group; + if (it->second->GetActiveGroup(&active_group)) + active_groups->push_back(active_group); + } +} + +// static +bool FieldTrialList::CreateTrialsFromString(const std::string& trials_string, + FieldTrialActivationMode mode) { + DCHECK(global_); + if (trials_string.empty() || !global_) + return true; + + size_t next_item = 0; + while (next_item < trials_string.length()) { + size_t name_end = trials_string.find(kPersistentStringSeparator, next_item); + if (name_end == trials_string.npos || next_item == name_end) + return false; + size_t group_name_end = trials_string.find(kPersistentStringSeparator, + name_end + 1); + if (group_name_end == trials_string.npos || name_end + 1 == group_name_end) + return false; + std::string name(trials_string, next_item, name_end - next_item); + std::string group_name(trials_string, name_end + 1, + group_name_end - name_end - 1); + next_item = group_name_end + 1; + + FieldTrial* trial = CreateFieldTrial(name, group_name); + if (!trial) + return false; + if (mode == ACTIVATE_TRIALS) { + // Call |group()| to mark the trial as "used" and notify observers, if + // any. This is useful to ensure that field trials created in child + // processes are properly reported in crash reports. + trial->group(); + } + } + return true; +} + +// static +FieldTrial* FieldTrialList::CreateFieldTrial( + const std::string& name, + const std::string& group_name) { + DCHECK(global_); + DCHECK_GE(name.size(), 0u); + DCHECK_GE(group_name.size(), 0u); + if (name.empty() || group_name.empty() || !global_) + return NULL; + + FieldTrial* field_trial = FieldTrialList::Find(name); + if (field_trial) { + // In single process mode, or when we force them from the command line, + // we may have already created the field trial. + if (field_trial->group_name_internal() != group_name) + return NULL; + return field_trial; + } + const int kTotalProbability = 100; + field_trial = new FieldTrial(name, kTotalProbability, group_name, 0); + // Force the trial, which will also finalize the group choice. + field_trial->SetForced(); + FieldTrialList::Register(field_trial); + return field_trial; +} + +// static +void FieldTrialList::AddObserver(Observer* observer) { + if (!global_) + return; + global_->observer_list_->AddObserver(observer); +} + +// static +void FieldTrialList::RemoveObserver(Observer* observer) { + if (!global_) + return; + global_->observer_list_->RemoveObserver(observer); +} + +// static +void FieldTrialList::NotifyFieldTrialGroupSelection(FieldTrial* field_trial) { + if (!global_) + return; + + { + AutoLock auto_lock(global_->lock_); + if (field_trial->group_reported_) + return; + field_trial->group_reported_ = true; + } + + if (!field_trial->enable_field_trial_) + return; + + global_->observer_list_->Notify( + &FieldTrialList::Observer::OnFieldTrialGroupFinalized, + field_trial->trial_name(), + field_trial->group_name_internal()); +} + +// static +size_t FieldTrialList::GetFieldTrialCount() { + if (!global_) + return 0; + AutoLock auto_lock(global_->lock_); + return global_->registered_.size(); +} + +// static +const FieldTrial::EntropyProvider* + FieldTrialList::GetEntropyProviderForOneTimeRandomization() { + if (!global_) { + used_without_global_ = true; + return NULL; + } + + return global_->entropy_provider_.get(); +} + +FieldTrial* FieldTrialList::PreLockedFind(const std::string& name) { + RegistrationList::iterator it = registered_.find(name); + if (registered_.end() == it) + return NULL; + return it->second; +} + +// static +void FieldTrialList::Register(FieldTrial* trial) { + if (!global_) { + used_without_global_ = true; + return; + } + AutoLock auto_lock(global_->lock_); + DCHECK(!global_->PreLockedFind(trial->trial_name())); + trial->AddRef(); + global_->registered_[trial->trial_name()] = trial; +} + +} // namespace base diff --git a/base/metrics/field_trial.h b/base/metrics/field_trial.h new file mode 100644 index 0000000000..08a8406353 --- /dev/null +++ b/base/metrics/field_trial.h @@ -0,0 +1,486 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// FieldTrial is a class for handling details of statistical experiments +// performed by actual users in the field (i.e., in a shipped or beta product). +// All code is called exclusively on the UI thread currently. +// +// The simplest example is an experiment to see whether one of two options +// produces "better" results across our user population. In that scenario, UMA +// data is uploaded to aggregate the test results, and this FieldTrial class +// manages the state of each such experiment (state == which option was +// pseudo-randomly selected). +// +// States are typically generated randomly, either based on a one time +// randomization (which will yield the same results, in terms of selecting +// the client for a field trial or not, for every run of the program on a +// given machine), or by a session randomization (generated each time the +// application starts up, but held constant during the duration of the +// process). + +//------------------------------------------------------------------------------ +// Example: Suppose we have an experiment involving memory, such as determining +// the impact of some pruning algorithm. +// We assume that we already have a histogram of memory usage, such as: + +// HISTOGRAM_COUNTS("Memory.RendererTotal", count); + +// Somewhere in main thread initialization code, we'd probably define an +// instance of a FieldTrial, with code such as: + +// // FieldTrials are reference counted, and persist automagically until +// // process teardown, courtesy of their automatic registration in +// // FieldTrialList. +// // Note: This field trial will run in Chrome instances compiled through +// // 8 July, 2015, and after that all instances will be in "StandardMem". +// scoped_refptr trial( +// base::FieldTrialList::FactoryGetFieldTrial( +// "MemoryExperiment", 1000, "StandardMem", 2015, 7, 8, +// base::FieldTrial::ONE_TIME_RANDOMIZED, NULL)); +// +// const int high_mem_group = +// trial->AppendGroup("HighMem", 20); // 2% in HighMem group. +// const int low_mem_group = +// trial->AppendGroup("LowMem", 20); // 2% in LowMem group. +// // Take action depending of which group we randomly land in. +// if (trial->group() == high_mem_group) +// SetPruningAlgorithm(kType1); // Sample setting of browser state. +// else if (trial->group() == low_mem_group) +// SetPruningAlgorithm(kType2); // Sample alternate setting. + +// We then, in addition to our original histogram, output histograms which have +// slightly different names depending on what group the trial instance happened +// to randomly be assigned: + +// HISTOGRAM_COUNTS("Memory.RendererTotal", count); // The original histogram. +// static const bool memory_renderer_total_trial_exists = +// FieldTrialList::TrialExists("MemoryExperiment"); +// if (memory_renderer_total_trial_exists) { +// HISTOGRAM_COUNTS(FieldTrial::MakeName("Memory.RendererTotal", +// "MemoryExperiment"), count); +// } + +// The above code will create four distinct histograms, with each run of the +// application being assigned to of of the three groups, and for each group, the +// correspondingly named histogram will be populated: + +// Memory.RendererTotal // 100% of users still fill this histogram. +// Memory.RendererTotal_HighMem // 2% of users will fill this histogram. +// Memory.RendererTotal_LowMem // 2% of users will fill this histogram. +// Memory.RendererTotal_StandardMem // 96% of users will fill this histogram. + +//------------------------------------------------------------------------------ + +#ifndef BASE_METRICS_FIELD_TRIAL_H_ +#define BASE_METRICS_FIELD_TRIAL_H_ + +#include +#include +#include + +#include "base/base_export.h" +#include "base/gtest_prod_util.h" +#include "base/memory/ref_counted.h" +#include "base/observer_list_threadsafe.h" +#include "base/synchronization/lock.h" +#include "base/time/time.h" + +namespace base { + +class FieldTrialList; + +class BASE_EXPORT FieldTrial : public RefCounted { + public: + typedef int Probability; // Probability type for being selected in a trial. + + // Specifies the persistence of the field trial group choice. + enum RandomizationType { + // One time randomized trials will persist the group choice between + // restarts, which is recommended for most trials, especially those that + // change user visible behavior. + ONE_TIME_RANDOMIZED, + // Session randomized trials will roll the dice to select a group on every + // process restart. + SESSION_RANDOMIZED, + }; + + // EntropyProvider is an interface for providing entropy for one-time + // randomized (persistent) field trials. + class BASE_EXPORT EntropyProvider { + public: + virtual ~EntropyProvider(); + + // Returns a double in the range of [0, 1) to be used for the dice roll for + // the specified field trial. If |randomization_seed| is not 0, it will be + // used in preference to |trial_name| for generating the entropy by entropy + // providers that support it. A given instance should always return the same + // value given the same input |trial_name| and |randomization_seed| values. + virtual double GetEntropyForTrial(const std::string& trial_name, + uint32 randomization_seed) const = 0; + }; + + // A pair representing a Field Trial and its selected group. + struct ActiveGroup { + std::string trial_name; + std::string group_name; + }; + + typedef std::vector ActiveGroups; + + // A return value to indicate that a given instance has not yet had a group + // assignment (and hence is not yet participating in the trial). + static const int kNotFinalized; + + // Disables this trial, meaning it always determines the default group + // has been selected. May be called immediately after construction, or + // at any time after initialization (should not be interleaved with + // AppendGroup calls). Once disabled, there is no way to re-enable a + // trial. + // TODO(mad): http://code.google.com/p/chromium/issues/detail?id=121446 + // This doesn't properly reset to Default when a group was forced. + void Disable(); + + // Establish the name and probability of the next group in this trial. + // Sometimes, based on construction randomization, this call may cause the + // provided group to be *THE* group selected for use in this instance. + // The return value is the group number of the new group. + int AppendGroup(const std::string& name, Probability group_probability); + + // Return the name of the FieldTrial (excluding the group name). + const std::string& trial_name() const { return trial_name_; } + + // Return the randomly selected group number that was assigned, and notify + // any/all observers that this finalized group number has presumably been used + // (queried), and will never change. Note that this will force an instance to + // participate, and make it illegal to attempt to probabilistically add any + // other groups to the trial. + int group(); + + // If the group's name is empty, a string version containing the group number + // is used as the group name. This causes a winner to be chosen if none was. + const std::string& group_name(); + + // Helper function for the most common use: as an argument to specify the + // name of a HISTOGRAM. Use the original histogram name as the name_prefix. + static std::string MakeName(const std::string& name_prefix, + const std::string& trial_name); + + // Enable benchmarking sets field trials to a common setting. + static void EnableBenchmarking(); + + // Set the field trial as forced, meaning that it was setup earlier than + // the hard coded registration of the field trial to override it. + // This allows the code that was hard coded to register the field trial to + // still succeed even though the field trial has already been registered. + // This must be called after appending all the groups, since we will make + // the group choice here. Note that this is a NOOP for already forced trials. + // And, as the rest of the FieldTrial code, this is not thread safe and must + // be done from the UI thread. + void SetForced(); + + private: + // Allow tests to access our innards for testing purposes. + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, Registration); + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, AbsoluteProbabilities); + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, RemainingProbability); + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, FiftyFiftyProbability); + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, MiddleProbabilities); + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, OneWinner); + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, DisableProbability); + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, ActiveGroups); + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, ActiveGroupsNotFinalized); + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, Save); + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, DuplicateRestore); + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, MakeName); + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, HashClientId); + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, HashClientIdIsUniform); + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, NameGroupIds); + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, SetForcedTurnFeatureOff); + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, SetForcedTurnFeatureOn); + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, SetForcedChangeDefault_Default); + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, SetForcedChangeDefault_NonDefault); + + friend class base::FieldTrialList; + + friend class RefCounted; + + // This is the group number of the 'default' group when a choice wasn't forced + // by a call to FieldTrialList::CreateFieldTrial. It is kept private so that + // consumers don't use it by mistake in cases where the group was forced. + static const int kDefaultGroupNumber; + + // Creates a field trial with the specified parameters. Group assignment will + // be done based on |entropy_value|, which must have a range of [0, 1). + FieldTrial(const std::string& name, + Probability total_probability, + const std::string& default_group_name, + double entropy_value); + virtual ~FieldTrial(); + + // Return the default group name of the FieldTrial. + std::string default_group_name() const { return default_group_name_; } + + // Sets the chosen group name and number. + void SetGroupChoice(const std::string& group_name, int number); + + // Ensures that a group is chosen, if it hasn't yet been. The field trial + // might yet be disabled, so this call will *not* notify observers of the + // status. + void FinalizeGroupChoice(); + + // Returns the trial name and selected group name for this field trial via + // the output parameter |active_group|, but only if the group has already + // been chosen and has been externally observed via |group()| and the trial + // has not been disabled. In that case, true is returned and |active_group| + // is filled in; otherwise, the result is false and |active_group| is left + // untouched. + bool GetActiveGroup(ActiveGroup* active_group) const; + + // Returns the group_name. A winner need not have been chosen. + std::string group_name_internal() const { return group_name_; } + + // The name of the field trial, as can be found via the FieldTrialList. + const std::string trial_name_; + + // The maximum sum of all probabilities supplied, which corresponds to 100%. + // This is the scaling factor used to adjust supplied probabilities. + const Probability divisor_; + + // The name of the default group. + const std::string default_group_name_; + + // The randomly selected probability that is used to select a group (or have + // the instance not participate). It is the product of divisor_ and a random + // number between [0, 1). + Probability random_; + + // Sum of the probabilities of all appended groups. + Probability accumulated_group_probability_; + + int next_group_number_; + + // The pseudo-randomly assigned group number. + // This is kNotFinalized if no group has been assigned. + int group_; + + // A textual name for the randomly selected group. Valid after |group()| + // has been called. + std::string group_name_; + + // When enable_field_trial_ is false, field trial reverts to the 'default' + // group. + bool enable_field_trial_; + + // When forced_ is true, we return the chosen group from AppendGroup when + // appropriate. + bool forced_; + + // Specifies whether the group choice has been reported to observers. + bool group_reported_; + + // When benchmarking is enabled, field trials all revert to the 'default' + // group. + static bool enable_benchmarking_; + + DISALLOW_COPY_AND_ASSIGN(FieldTrial); +}; + +//------------------------------------------------------------------------------ +// Class with a list of all active field trials. A trial is active if it has +// been registered, which includes evaluating its state based on its probaility. +// Only one instance of this class exists. +class BASE_EXPORT FieldTrialList { + public: + // Specifies whether field trials should be activated (marked as "used"), when + // created using |CreateTrialsFromString()|. + enum FieldTrialActivationMode { + DONT_ACTIVATE_TRIALS, + ACTIVATE_TRIALS, + }; + + // Define a separator character to use when creating a persistent form of an + // instance. This is intended for use as a command line argument, passed to a + // second process to mimic our state (i.e., provide the same group name). + static const char kPersistentStringSeparator; // Currently a slash. + + // Year that is guaranteed to not be expired when instantiating a field trial + // via |FactoryGetFieldTrial()|. Set to two years from the build date. + static int kNoExpirationYear; + + // Observer is notified when a FieldTrial's group is selected. + class BASE_EXPORT Observer { + public: + // Notify observers when FieldTrials's group is selected. + virtual void OnFieldTrialGroupFinalized(const std::string& trial_name, + const std::string& group_name) = 0; + + protected: + virtual ~Observer(); + }; + + // This singleton holds the global list of registered FieldTrials. + // + // To support one-time randomized field trials, specify a non-NULL + // |entropy_provider| which should be a source of uniformly distributed + // entropy values. Takes ownership of |entropy_provider|. If one time + // randomization is not desired, pass in NULL for |entropy_provider|. + explicit FieldTrialList(const FieldTrial::EntropyProvider* entropy_provider); + + // Destructor Release()'s references to all registered FieldTrial instances. + ~FieldTrialList(); + + // Get a FieldTrial instance from the factory. + // + // |name| is used to register the instance with the FieldTrialList class, + // and can be used to find the trial (only one trial can be present for each + // name). |default_group_name| is the name of the default group which will + // be chosen if none of the subsequent appended groups get to be chosen. + // |default_group_number| can receive the group number of the default group as + // AppendGroup returns the number of the subsequence groups. |trial_name| and + // |default_group_name| may not be empty but |default_group_number| can be + // NULL if the value is not needed. + // + // Group probabilities that are later supplied must sum to less than or equal + // to the |total_probability|. Arguments |year|, |month| and |day_of_month| + // specify the expiration time. If the build time is after the expiration time + // then the field trial reverts to the 'default' group. + // + // Use this static method to get a startup-randomized FieldTrial or a + // previously created forced FieldTrial. + static FieldTrial* FactoryGetFieldTrial( + const std::string& trial_name, + FieldTrial::Probability total_probability, + const std::string& default_group_name, + const int year, + const int month, + const int day_of_month, + FieldTrial::RandomizationType randomization_type, + int* default_group_number); + + // Same as FactoryGetFieldTrial(), but allows specifying a custom seed to be + // used on one-time randomized field trials (instead of a hash of the trial + // name, which is used otherwise or if |randomization_seed| has value 0). The + // |randomization_seed| value (other than 0) should never be the same for two + // trials, else this would result in correlated group assignments. + // Note: Using a custom randomization seed is only supported by the + // PermutedEntropyProvider (which is used when UMA is not enabled). + static FieldTrial* FactoryGetFieldTrialWithRandomizationSeed( + const std::string& trial_name, + FieldTrial::Probability total_probability, + const std::string& default_group_name, + const int year, + const int month, + const int day_of_month, + FieldTrial::RandomizationType randomization_type, + uint32 randomization_seed, + int* default_group_number); + + // The Find() method can be used to test to see if a named Trial was already + // registered, or to retrieve a pointer to it from the global map. + static FieldTrial* Find(const std::string& name); + + // Returns the group number chosen for the named trial, or + // FieldTrial::kNotFinalized if the trial does not exist. + static int FindValue(const std::string& name); + + // Returns the group name chosen for the named trial, or the + // empty string if the trial does not exist. + static std::string FindFullName(const std::string& name); + + // Returns true if the named trial has been registered. + static bool TrialExists(const std::string& name); + + // Creates a persistent representation of active FieldTrial instances for + // resurrection in another process. This allows randomization to be done in + // one process, and secondary processes can be synchronized on the result. + // The resulting string contains the name and group name pairs of all + // registered FieldTrials for which the group has been chosen and externally + // observed (via |group()|) and which have not been disabled, with "/" used + // to separate all names and to terminate the string. This string is parsed + // by |CreateTrialsFromString()|. + static void StatesToString(std::string* output); + + // Fills in the supplied vector |active_groups| (which must be empty when + // called) with a snapshot of all registered FieldTrials for which the group + // has been chosen and externally observed (via |group()|) and which have + // not been disabled. + static void GetActiveFieldTrialGroups( + FieldTrial::ActiveGroups* active_groups); + + // Use a state string (re: StatesToString()) to augment the current list of + // field trials to include the supplied trials, and using a 100% probability + // for each trial, force them to have the same group string. This is commonly + // used in a non-browser process, to carry randomly selected state in a + // browser process into this non-browser process, but could also be invoked + // through a command line argument to the browser process. The created field + // trials are marked as "used" for the purposes of active trial reporting if + // |mode| is ACTIVATE_TRIALS. + static bool CreateTrialsFromString(const std::string& prior_trials, + FieldTrialActivationMode mode); + + // Create a FieldTrial with the given |name| and using 100% probability for + // the FieldTrial, force FieldTrial to have the same group string as + // |group_name|. This is commonly used in a non-browser process, to carry + // randomly selected state in a browser process into this non-browser process. + // It returns NULL if there is a FieldTrial that is already registered with + // the same |name| but has different finalized group string (|group_name|). + static FieldTrial* CreateFieldTrial(const std::string& name, + const std::string& group_name); + + // Add an observer to be notified when a field trial is irrevocably committed + // to being part of some specific field_group (and hence the group_name is + // also finalized for that field_trial). + static void AddObserver(Observer* observer); + + // Remove an observer. + static void RemoveObserver(Observer* observer); + + // Notify all observers that a group has been finalized for |field_trial|. + static void NotifyFieldTrialGroupSelection(FieldTrial* field_trial); + + // Return the number of active field trials. + static size_t GetFieldTrialCount(); + + private: + // A map from FieldTrial names to the actual instances. + typedef std::map RegistrationList; + + // If one-time randomization is enabled, returns a weak pointer to the + // corresponding EntropyProvider. Otherwise, returns NULL. + static const FieldTrial::EntropyProvider* + GetEntropyProviderForOneTimeRandomization(); + + // Helper function should be called only while holding lock_. + FieldTrial* PreLockedFind(const std::string& name); + + // Register() stores a pointer to the given trial in a global map. + // This method also AddRef's the indicated trial. + // This should always be called after creating a new FieldTrial instance. + static void Register(FieldTrial* trial); + + static FieldTrialList* global_; // The singleton of this class. + + // This will tell us if there is an attempt to register a field + // trial or check if one-time randomization is enabled without + // creating the FieldTrialList. This is not an error, unless a + // FieldTrialList is created after that. + static bool used_without_global_; + + // Lock for access to registered_. + base::Lock lock_; + RegistrationList registered_; + + // Entropy provider to be used for one-time randomized field trials. If NULL, + // one-time randomization is not supported. + scoped_ptr entropy_provider_; + + // List of observers to be notified when a group is selected for a FieldTrial. + scoped_refptr > observer_list_; + + DISALLOW_COPY_AND_ASSIGN(FieldTrialList); +}; + +} // namespace base + +#endif // BASE_METRICS_FIELD_TRIAL_H_ diff --git a/base/metrics/field_trial_unittest.cc b/base/metrics/field_trial_unittest.cc new file mode 100644 index 0000000000..21be9910b8 --- /dev/null +++ b/base/metrics/field_trial_unittest.cc @@ -0,0 +1,859 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/metrics/field_trial.h" + +#include "base/message_loop/message_loop.h" +#include "base/rand_util.h" +#include "base/run_loop.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace { + +// Default group name used by several tests. +const char kDefaultGroupName[] = "DefaultGroup"; + +// Call FieldTrialList::FactoryGetFieldTrial() with a future expiry date. +scoped_refptr CreateFieldTrial( + const std::string& trial_name, + int total_probability, + const std::string& default_group_name, + int* default_group_number) { + return FieldTrialList::FactoryGetFieldTrial( + trial_name, total_probability, default_group_name, + base::FieldTrialList::kNoExpirationYear, 1, 1, + base::FieldTrial::SESSION_RANDOMIZED, default_group_number); +} + +int GetLastYear() { + Time last_year_time = Time::NowFromSystemTime() - TimeDelta::FromDays(365); + Time::Exploded exploded; + last_year_time.LocalExplode(&exploded); + return exploded.year; +} + +// FieldTrialList::Observer implementation for testing. +class TestFieldTrialObserver : public FieldTrialList::Observer { + public: + TestFieldTrialObserver() { + FieldTrialList::AddObserver(this); + } + + virtual ~TestFieldTrialObserver() { + FieldTrialList::RemoveObserver(this); + } + + virtual void OnFieldTrialGroupFinalized(const std::string& trial, + const std::string& group) OVERRIDE { + trial_name_ = trial; + group_name_ = group; + } + + const std::string& trial_name() const { return trial_name_; } + const std::string& group_name() const { return group_name_; } + + private: + std::string trial_name_; + std::string group_name_; + + DISALLOW_COPY_AND_ASSIGN(TestFieldTrialObserver); +}; + +} // namespace + +class FieldTrialTest : public testing::Test { + public: + FieldTrialTest() : trial_list_(NULL) {} + + private: + MessageLoop message_loop_; + FieldTrialList trial_list_; +}; + +// Test registration, and also check that destructors are called for trials +// (and that Valgrind doesn't catch us leaking). +TEST_F(FieldTrialTest, Registration) { + const char* name1 = "name 1 test"; + const char* name2 = "name 2 test"; + EXPECT_FALSE(FieldTrialList::Find(name1)); + EXPECT_FALSE(FieldTrialList::Find(name2)); + + FieldTrial* trial1 = CreateFieldTrial(name1, 10, "default name 1 test", NULL); + EXPECT_EQ(FieldTrial::kNotFinalized, trial1->group_); + EXPECT_EQ(name1, trial1->trial_name()); + EXPECT_EQ("", trial1->group_name_internal()); + + trial1->AppendGroup(std::string(), 7); + + EXPECT_EQ(trial1, FieldTrialList::Find(name1)); + EXPECT_FALSE(FieldTrialList::Find(name2)); + + FieldTrial* trial2 = CreateFieldTrial(name2, 10, "default name 2 test", NULL); + EXPECT_EQ(FieldTrial::kNotFinalized, trial2->group_); + EXPECT_EQ(name2, trial2->trial_name()); + EXPECT_EQ("", trial2->group_name_internal()); + + trial2->AppendGroup("a first group", 7); + + EXPECT_EQ(trial1, FieldTrialList::Find(name1)); + EXPECT_EQ(trial2, FieldTrialList::Find(name2)); + // Note: FieldTrialList should delete the objects at shutdown. +} + +TEST_F(FieldTrialTest, AbsoluteProbabilities) { + char always_true[] = " always true"; + char default_always_true[] = " default always true"; + char always_false[] = " always false"; + char default_always_false[] = " default always false"; + for (int i = 1; i < 250; ++i) { + // Try lots of names, by changing the first character of the name. + always_true[0] = i; + default_always_true[0] = i; + always_false[0] = i; + default_always_false[0] = i; + + FieldTrial* trial_true = + CreateFieldTrial(always_true, 10, default_always_true, NULL); + const std::string winner = "TheWinner"; + int winner_group = trial_true->AppendGroup(winner, 10); + + EXPECT_EQ(winner_group, trial_true->group()); + EXPECT_EQ(winner, trial_true->group_name()); + + FieldTrial* trial_false = + CreateFieldTrial(always_false, 10, default_always_false, NULL); + int loser_group = trial_false->AppendGroup("ALoser", 0); + + EXPECT_NE(loser_group, trial_false->group()); + } +} + +TEST_F(FieldTrialTest, RemainingProbability) { + // First create a test that hasn't had a winner yet. + const std::string winner = "Winner"; + const std::string loser = "Loser"; + scoped_refptr trial; + int counter = 0; + int default_group_number = -1; + do { + std::string name = StringPrintf("trial%d", ++counter); + trial = CreateFieldTrial(name, 10, winner, &default_group_number); + trial->AppendGroup(loser, 5); // 50% chance of not being chosen. + // If a group is not assigned, group_ will be kNotFinalized. + } while (trial->group_ != FieldTrial::kNotFinalized); + + // And that 'default' group (winner) should always win. + EXPECT_EQ(default_group_number, trial->group()); + + // And that winner should ALWAYS win. + EXPECT_EQ(winner, trial->group_name()); +} + +TEST_F(FieldTrialTest, FiftyFiftyProbability) { + // Check that even with small divisors, we have the proper probabilities, and + // all outcomes are possible. Since this is a 50-50 test, it should get both + // outcomes in a few tries, but we'll try no more than 100 times (and be flaky + // with probability around 1 in 2^99). + bool first_winner = false; + bool second_winner = false; + int counter = 0; + do { + std::string name = base::StringPrintf("FiftyFifty%d", ++counter); + std::string default_group_name = base::StringPrintf("Default FiftyFifty%d", + ++counter); + FieldTrial* trial = CreateFieldTrial(name, 2, default_group_name, NULL); + trial->AppendGroup("first", 1); // 50% chance of being chosen. + // If group_ is kNotFinalized, then a group assignement hasn't been done. + if (trial->group_ != FieldTrial::kNotFinalized) { + first_winner = true; + continue; + } + trial->AppendGroup("second", 1); // Always chosen at this point. + EXPECT_NE(FieldTrial::kNotFinalized, trial->group()); + second_winner = true; + } while ((!second_winner || !first_winner) && counter < 100); + EXPECT_TRUE(second_winner); + EXPECT_TRUE(first_winner); +} + +TEST_F(FieldTrialTest, MiddleProbabilities) { + char name[] = " same name"; + char default_group_name[] = " default same name"; + bool false_event_seen = false; + bool true_event_seen = false; + for (int i = 1; i < 250; ++i) { + name[0] = i; + default_group_name[0] = i; + FieldTrial* trial = CreateFieldTrial(name, 10, default_group_name, NULL); + int might_win = trial->AppendGroup("MightWin", 5); + + if (trial->group() == might_win) { + true_event_seen = true; + } else { + false_event_seen = true; + } + if (false_event_seen && true_event_seen) + return; // Successful test!!! + } + // Very surprising to get here. Probability should be around 1 in 2 ** 250. + // One of the following will fail. + EXPECT_TRUE(false_event_seen); + EXPECT_TRUE(true_event_seen); +} + +TEST_F(FieldTrialTest, OneWinner) { + char name[] = "Some name"; + char default_group_name[] = "Default some name"; + int group_count(10); + + int default_group_number = -1; + FieldTrial* trial = + CreateFieldTrial(name, group_count, default_group_name, NULL); + int winner_index(-2); + std::string winner_name; + + for (int i = 1; i <= group_count; ++i) { + int might_win = trial->AppendGroup(std::string(), 1); + + // Because we keep appending groups, we want to see if the last group that + // was added has been assigned or not. + if (trial->group_ == might_win) { + EXPECT_EQ(-2, winner_index); + winner_index = might_win; + StringAppendF(&winner_name, "%d", might_win); + EXPECT_EQ(winner_name, trial->group_name()); + } + } + EXPECT_GE(winner_index, 0); + // Since all groups cover the total probability, we should not have + // chosen the default group. + EXPECT_NE(trial->group(), default_group_number); + EXPECT_EQ(trial->group(), winner_index); + EXPECT_EQ(trial->group_name(), winner_name); +} + +TEST_F(FieldTrialTest, DisableProbability) { + const std::string default_group_name = "Default group"; + const std::string loser = "Loser"; + const std::string name = "Trial"; + + // Create a field trail that has expired. + int default_group_number = -1; + FieldTrial* trial = FieldTrialList::FactoryGetFieldTrial( + name, 1000000000, default_group_name, GetLastYear(), 1, 1, + FieldTrial::SESSION_RANDOMIZED, + &default_group_number); + trial->AppendGroup(loser, 999999999); // 99.9999999% chance of being chosen. + + // Because trial has expired, we should always be in the default group. + EXPECT_EQ(default_group_number, trial->group()); + + // And that default_group_name should ALWAYS win. + EXPECT_EQ(default_group_name, trial->group_name()); +} + +TEST_F(FieldTrialTest, ActiveGroups) { + std::string no_group("No Group"); + FieldTrial* trial = CreateFieldTrial(no_group, 10, "Default", NULL); + + // There is no winner yet, so no NameGroupId should be returned. + FieldTrial::ActiveGroup active_group; + EXPECT_FALSE(trial->GetActiveGroup(&active_group)); + + // Create a single winning group. + std::string one_winner("One Winner"); + trial = CreateFieldTrial(one_winner, 10, "Default", NULL); + std::string winner("Winner"); + trial->AppendGroup(winner, 10); + EXPECT_FALSE(trial->GetActiveGroup(&active_group)); + // Finalize the group selection by accessing the selected group. + trial->group(); + EXPECT_TRUE(trial->GetActiveGroup(&active_group)); + EXPECT_EQ(one_winner, active_group.trial_name); + EXPECT_EQ(winner, active_group.group_name); + + std::string multi_group("MultiGroup"); + FieldTrial* multi_group_trial = + CreateFieldTrial(multi_group, 9, "Default", NULL); + + multi_group_trial->AppendGroup("Me", 3); + multi_group_trial->AppendGroup("You", 3); + multi_group_trial->AppendGroup("Them", 3); + EXPECT_FALSE(multi_group_trial->GetActiveGroup(&active_group)); + // Finalize the group selection by accessing the selected group. + multi_group_trial->group(); + EXPECT_TRUE(multi_group_trial->GetActiveGroup(&active_group)); + EXPECT_EQ(multi_group, active_group.trial_name); + EXPECT_EQ(multi_group_trial->group_name(), active_group.group_name); + + // Now check if the list is built properly... + FieldTrial::ActiveGroups active_groups; + FieldTrialList::GetActiveFieldTrialGroups(&active_groups); + EXPECT_EQ(2U, active_groups.size()); + for (size_t i = 0; i < active_groups.size(); ++i) { + // Order is not guaranteed, so check all values. + EXPECT_NE(no_group, active_groups[i].trial_name); + EXPECT_TRUE(one_winner != active_groups[i].trial_name || + winner == active_groups[i].group_name); + EXPECT_TRUE(multi_group != active_groups[i].trial_name || + multi_group_trial->group_name() == active_groups[i].group_name); + } +} + +TEST_F(FieldTrialTest, ActiveGroupsNotFinalized) { + const char kTrialName[] = "TestTrial"; + const char kSecondaryGroupName[] = "SecondaryGroup"; + + int default_group = -1; + FieldTrial* trial = + CreateFieldTrial(kTrialName, 100, kDefaultGroupName, &default_group); + const int secondary_group = trial->AppendGroup(kSecondaryGroupName, 50); + + // Before |group()| is called, |GetActiveGroup()| should return false. + FieldTrial::ActiveGroup active_group; + EXPECT_FALSE(trial->GetActiveGroup(&active_group)); + + // |GetActiveFieldTrialGroups()| should also not include the trial. + FieldTrial::ActiveGroups active_groups; + FieldTrialList::GetActiveFieldTrialGroups(&active_groups); + EXPECT_TRUE(active_groups.empty()); + + // After |group()| has been called, both APIs should succeed. + const int chosen_group = trial->group(); + EXPECT_TRUE(chosen_group == default_group || chosen_group == secondary_group); + + EXPECT_TRUE(trial->GetActiveGroup(&active_group)); + EXPECT_EQ(kTrialName, active_group.trial_name); + if (chosen_group == default_group) + EXPECT_EQ(kDefaultGroupName, active_group.group_name); + else + EXPECT_EQ(kSecondaryGroupName, active_group.group_name); + + FieldTrialList::GetActiveFieldTrialGroups(&active_groups); + ASSERT_EQ(1U, active_groups.size()); + EXPECT_EQ(kTrialName, active_groups[0].trial_name); + EXPECT_EQ(active_group.group_name, active_groups[0].group_name); +} + +TEST_F(FieldTrialTest, Save) { + std::string save_string; + + FieldTrial* trial = + CreateFieldTrial("Some name", 10, "Default some name", NULL); + // There is no winner yet, so no textual group name is associated with trial. + // In this case, the trial should not be included. + EXPECT_EQ("", trial->group_name_internal()); + FieldTrialList::StatesToString(&save_string); + EXPECT_EQ("", save_string); + save_string.clear(); + + // Create a winning group. + trial->AppendGroup("Winner", 10); + // Finalize the group selection by accessing the selected group. + trial->group(); + FieldTrialList::StatesToString(&save_string); + EXPECT_EQ("Some name/Winner/", save_string); + save_string.clear(); + + // Create a second trial and winning group. + FieldTrial* trial2 = CreateFieldTrial("xxx", 10, "Default xxx", NULL); + trial2->AppendGroup("yyyy", 10); + // Finalize the group selection by accessing the selected group. + trial2->group(); + + FieldTrialList::StatesToString(&save_string); + // We assume names are alphabetized... though this is not critical. + EXPECT_EQ("Some name/Winner/xxx/yyyy/", save_string); + save_string.clear(); + + // Create a third trial with only the default group. + FieldTrial* trial3 = CreateFieldTrial("zzz", 10, "default", NULL); + // Finalize the group selection by accessing the selected group. + trial3->group(); + + FieldTrialList::StatesToString(&save_string); + EXPECT_EQ("Some name/Winner/xxx/yyyy/zzz/default/", save_string); +} + +TEST_F(FieldTrialTest, Restore) { + ASSERT_FALSE(FieldTrialList::TrialExists("Some_name")); + ASSERT_FALSE(FieldTrialList::TrialExists("xxx")); + + FieldTrialList::CreateTrialsFromString("Some_name/Winner/xxx/yyyy/", + FieldTrialList::DONT_ACTIVATE_TRIALS); + + FieldTrial* trial = FieldTrialList::Find("Some_name"); + ASSERT_NE(static_cast(NULL), trial); + EXPECT_EQ("Winner", trial->group_name()); + EXPECT_EQ("Some_name", trial->trial_name()); + + trial = FieldTrialList::Find("xxx"); + ASSERT_NE(static_cast(NULL), trial); + EXPECT_EQ("yyyy", trial->group_name()); + EXPECT_EQ("xxx", trial->trial_name()); +} + +TEST_F(FieldTrialTest, BogusRestore) { + EXPECT_FALSE(FieldTrialList::CreateTrialsFromString( + "MissingSlash", FieldTrialList::DONT_ACTIVATE_TRIALS)); + EXPECT_FALSE(FieldTrialList::CreateTrialsFromString( + "MissingGroupName/", FieldTrialList::DONT_ACTIVATE_TRIALS)); + EXPECT_FALSE(FieldTrialList::CreateTrialsFromString( + "MissingFinalSlash/gname", FieldTrialList::DONT_ACTIVATE_TRIALS)); + EXPECT_FALSE(FieldTrialList::CreateTrialsFromString( + "noname, only group/", FieldTrialList::DONT_ACTIVATE_TRIALS)); +} + +TEST_F(FieldTrialTest, DuplicateRestore) { + FieldTrial* trial = CreateFieldTrial("Some name", 10, "Default", NULL); + trial->AppendGroup("Winner", 10); + // Finalize the group selection by accessing the selected group. + trial->group(); + std::string save_string; + FieldTrialList::StatesToString(&save_string); + EXPECT_EQ("Some name/Winner/", save_string); + + // It is OK if we redundantly specify a winner. + EXPECT_TRUE(FieldTrialList::CreateTrialsFromString( + save_string, FieldTrialList::DONT_ACTIVATE_TRIALS)); + + // But it is an error to try to change to a different winner. + EXPECT_FALSE(FieldTrialList::CreateTrialsFromString( + "Some name/Loser/", FieldTrialList::DONT_ACTIVATE_TRIALS)); +} + +TEST_F(FieldTrialTest, CreateTrialsFromStringActive) { + ASSERT_FALSE(FieldTrialList::TrialExists("Abc")); + ASSERT_FALSE(FieldTrialList::TrialExists("Xyz")); + ASSERT_TRUE(FieldTrialList::CreateTrialsFromString( + "Abc/def/Xyz/zyx/", FieldTrialList::ACTIVATE_TRIALS)); + + FieldTrial::ActiveGroups active_groups; + FieldTrialList::GetActiveFieldTrialGroups(&active_groups); + ASSERT_EQ(2U, active_groups.size()); + EXPECT_EQ("Abc", active_groups[0].trial_name); + EXPECT_EQ("def", active_groups[0].group_name); + EXPECT_EQ("Xyz", active_groups[1].trial_name); + EXPECT_EQ("zyx", active_groups[1].group_name); +} + +TEST_F(FieldTrialTest, CreateTrialsFromStringNotActive) { + ASSERT_FALSE(FieldTrialList::TrialExists("Abc")); + ASSERT_FALSE(FieldTrialList::TrialExists("Xyz")); + ASSERT_TRUE(FieldTrialList::CreateTrialsFromString( + "Abc/def/Xyz/zyx/", FieldTrialList::DONT_ACTIVATE_TRIALS)); + + FieldTrial::ActiveGroups active_groups; + FieldTrialList::GetActiveFieldTrialGroups(&active_groups); + ASSERT_TRUE(active_groups.empty()); + + // Check that the values still get returned and querying them activates them. + EXPECT_EQ("def", FieldTrialList::FindFullName("Abc")); + EXPECT_EQ("zyx", FieldTrialList::FindFullName("Xyz")); + + FieldTrialList::GetActiveFieldTrialGroups(&active_groups); + ASSERT_EQ(2U, active_groups.size()); + EXPECT_EQ("Abc", active_groups[0].trial_name); + EXPECT_EQ("def", active_groups[0].group_name); + EXPECT_EQ("Xyz", active_groups[1].trial_name); + EXPECT_EQ("zyx", active_groups[1].group_name); +} + +TEST_F(FieldTrialTest, CreateTrialsFromStringActiveObserver) { + ASSERT_FALSE(FieldTrialList::TrialExists("Abc")); + + TestFieldTrialObserver observer; + ASSERT_TRUE(FieldTrialList::CreateTrialsFromString( + "Abc/def/", FieldTrialList::ACTIVATE_TRIALS)); + + RunLoop().RunUntilIdle(); + EXPECT_EQ("Abc", observer.trial_name()); + EXPECT_EQ("def", observer.group_name()); +} + +TEST_F(FieldTrialTest, CreateTrialsFromStringNotActiveObserver) { + ASSERT_FALSE(FieldTrialList::TrialExists("Abc")); + + TestFieldTrialObserver observer; + ASSERT_TRUE(FieldTrialList::CreateTrialsFromString( + "Abc/def/", FieldTrialList::DONT_ACTIVATE_TRIALS)); + RunLoop().RunUntilIdle(); + // Observer shouldn't be notified. + EXPECT_TRUE(observer.trial_name().empty()); + + // Check that the values still get returned and querying them activates them. + EXPECT_EQ("def", FieldTrialList::FindFullName("Abc")); + + RunLoop().RunUntilIdle(); + EXPECT_EQ("Abc", observer.trial_name()); + EXPECT_EQ("def", observer.group_name()); +} + +TEST_F(FieldTrialTest, CreateFieldTrial) { + ASSERT_FALSE(FieldTrialList::TrialExists("Some_name")); + + FieldTrialList::CreateFieldTrial("Some_name", "Winner"); + + FieldTrial* trial = FieldTrialList::Find("Some_name"); + ASSERT_NE(static_cast(NULL), trial); + EXPECT_EQ("Winner", trial->group_name()); + EXPECT_EQ("Some_name", trial->trial_name()); +} + +TEST_F(FieldTrialTest, CreateFieldTrialIsNotActive) { + const char kTrialName[] = "CreateFieldTrialIsActiveTrial"; + const char kWinnerGroup[] = "Winner"; + ASSERT_FALSE(FieldTrialList::TrialExists(kTrialName)); + FieldTrialList::CreateFieldTrial(kTrialName, kWinnerGroup); + + FieldTrial::ActiveGroups active_groups; + FieldTrialList::GetActiveFieldTrialGroups(&active_groups); + EXPECT_TRUE(active_groups.empty()); +} + +TEST_F(FieldTrialTest, DuplicateFieldTrial) { + FieldTrial* trial = CreateFieldTrial("Some_name", 10, "Default", NULL); + trial->AppendGroup("Winner", 10); + + // It is OK if we redundantly specify a winner. + FieldTrial* trial1 = FieldTrialList::CreateFieldTrial("Some_name", "Winner"); + EXPECT_TRUE(trial1 != NULL); + + // But it is an error to try to change to a different winner. + FieldTrial* trial2 = FieldTrialList::CreateFieldTrial("Some_name", "Loser"); + EXPECT_TRUE(trial2 == NULL); +} + +TEST_F(FieldTrialTest, MakeName) { + FieldTrial* trial = CreateFieldTrial("Field Trial", 10, "Winner", NULL); + trial->group(); + EXPECT_EQ("Histogram_Winner", + FieldTrial::MakeName("Histogram", "Field Trial")); +} + +TEST_F(FieldTrialTest, DisableImmediately) { + int default_group_number = -1; + FieldTrial* trial = + CreateFieldTrial("trial", 100, "default", &default_group_number); + trial->Disable(); + ASSERT_EQ("default", trial->group_name()); + ASSERT_EQ(default_group_number, trial->group()); +} + +TEST_F(FieldTrialTest, DisableAfterInitialization) { + FieldTrial* trial = CreateFieldTrial("trial", 100, "default", NULL); + trial->AppendGroup("non_default", 100); + trial->Disable(); + ASSERT_EQ("default", trial->group_name()); +} + +TEST_F(FieldTrialTest, ForcedFieldTrials) { + // Validate we keep the forced choice. + FieldTrial* forced_trial = FieldTrialList::CreateFieldTrial("Use the", + "Force"); + EXPECT_STREQ("Force", forced_trial->group_name().c_str()); + + int default_group_number = -1; + FieldTrial* factory_trial = + CreateFieldTrial("Use the", 1000, "default", &default_group_number); + EXPECT_EQ(factory_trial, forced_trial); + + int chosen_group = factory_trial->AppendGroup("Force", 100); + EXPECT_EQ(chosen_group, factory_trial->group()); + int not_chosen_group = factory_trial->AppendGroup("Dark Side", 100); + EXPECT_NE(chosen_group, not_chosen_group); + + // Since we didn't force the default group, we should not be returned the + // chosen group as the default group. + EXPECT_NE(default_group_number, chosen_group); + int new_group = factory_trial->AppendGroup("Duck Tape", 800); + EXPECT_NE(chosen_group, new_group); + // The new group should not be the default group either. + EXPECT_NE(default_group_number, new_group); +} + +TEST_F(FieldTrialTest, ForcedFieldTrialsDefaultGroup) { + // Forcing the default should use the proper group ID. + FieldTrial* forced_trial = FieldTrialList::CreateFieldTrial("Trial Name", + "Default"); + int default_group_number = -1; + FieldTrial* factory_trial = + CreateFieldTrial("Trial Name", 1000, "Default", &default_group_number); + EXPECT_EQ(forced_trial, factory_trial); + + int other_group = factory_trial->AppendGroup("Not Default", 100); + EXPECT_STREQ("Default", factory_trial->group_name().c_str()); + EXPECT_EQ(default_group_number, factory_trial->group()); + EXPECT_NE(other_group, factory_trial->group()); + + int new_other_group = factory_trial->AppendGroup("Not Default Either", 800); + EXPECT_NE(new_other_group, factory_trial->group()); +} + +TEST_F(FieldTrialTest, SetForced) { + // Start by setting a trial for which we ensure a winner... + int default_group_number = -1; + FieldTrial* forced_trial = + CreateFieldTrial("Use the", 1, "default", &default_group_number); + EXPECT_EQ(forced_trial, forced_trial); + + int forced_group = forced_trial->AppendGroup("Force", 1); + EXPECT_EQ(forced_group, forced_trial->group()); + + // Now force it. + forced_trial->SetForced(); + + // Now try to set it up differently as a hard coded registration would. + FieldTrial* hard_coded_trial = + CreateFieldTrial("Use the", 1, "default", &default_group_number); + EXPECT_EQ(hard_coded_trial, forced_trial); + + int would_lose_group = hard_coded_trial->AppendGroup("Force", 0); + EXPECT_EQ(forced_group, hard_coded_trial->group()); + EXPECT_EQ(forced_group, would_lose_group); + + // Same thing if we would have done it to win again. + FieldTrial* other_hard_coded_trial = + CreateFieldTrial("Use the", 1, "default", &default_group_number); + EXPECT_EQ(other_hard_coded_trial, forced_trial); + + int would_win_group = other_hard_coded_trial->AppendGroup("Force", 1); + EXPECT_EQ(forced_group, other_hard_coded_trial->group()); + EXPECT_EQ(forced_group, would_win_group); +} + +TEST_F(FieldTrialTest, SetForcedDefaultOnly) { + const char kTrialName[] = "SetForcedDefaultOnly"; + ASSERT_FALSE(FieldTrialList::TrialExists(kTrialName)); + + int default_group = -1; + FieldTrial* trial = + CreateFieldTrial(kTrialName, 100, kDefaultGroupName, &default_group); + trial->SetForced(); + + trial = CreateFieldTrial(kTrialName, 100, kDefaultGroupName, NULL); + EXPECT_EQ(default_group, trial->group()); + EXPECT_EQ(kDefaultGroupName, trial->group_name()); +} + +TEST_F(FieldTrialTest, SetForcedDefaultWithExtraGroup) { + const char kTrialName[] = "SetForcedDefaultWithExtraGroup"; + ASSERT_FALSE(FieldTrialList::TrialExists(kTrialName)); + + int default_group = -1; + FieldTrial* trial = + CreateFieldTrial(kTrialName, 100, kDefaultGroupName, &default_group); + trial->SetForced(); + + trial = CreateFieldTrial(kTrialName, 100, kDefaultGroupName, NULL); + const int extra_group = trial->AppendGroup("Extra", 100); + EXPECT_EQ(default_group, trial->group()); + EXPECT_NE(extra_group, trial->group()); + EXPECT_EQ(kDefaultGroupName, trial->group_name()); +} + +TEST_F(FieldTrialTest, SetForcedTurnFeatureOn) { + const char kTrialName[] = "SetForcedTurnFeatureOn"; + const char kExtraGroupName[] = "Extra"; + ASSERT_FALSE(FieldTrialList::TrialExists(kTrialName)); + + // Simulate a server-side (forced) config that turns the feature on when the + // original hard-coded config had it disabled. + FieldTrial* forced_trial = + CreateFieldTrial(kTrialName, 100, kDefaultGroupName, NULL); + forced_trial->AppendGroup(kExtraGroupName, 100); + forced_trial->SetForced(); + + int default_group = -1; + FieldTrial* client_trial = + CreateFieldTrial(kTrialName, 100, kDefaultGroupName, &default_group); + const int extra_group = client_trial->AppendGroup(kExtraGroupName, 0); + EXPECT_NE(default_group, extra_group); + + EXPECT_FALSE(client_trial->group_reported_); + EXPECT_EQ(extra_group, client_trial->group()); + EXPECT_TRUE(client_trial->group_reported_); + EXPECT_EQ(kExtraGroupName, client_trial->group_name()); +} + +TEST_F(FieldTrialTest, SetForcedTurnFeatureOff) { + const char kTrialName[] = "SetForcedTurnFeatureOff"; + const char kExtraGroupName[] = "Extra"; + ASSERT_FALSE(FieldTrialList::TrialExists(kTrialName)); + + // Simulate a server-side (forced) config that turns the feature off when the + // original hard-coded config had it enabled. + FieldTrial* forced_trial = + CreateFieldTrial(kTrialName, 100, kDefaultGroupName, NULL); + forced_trial->AppendGroup(kExtraGroupName, 0); + forced_trial->SetForced(); + + int default_group = -1; + FieldTrial* client_trial = + CreateFieldTrial(kTrialName, 100, kDefaultGroupName, &default_group); + const int extra_group = client_trial->AppendGroup(kExtraGroupName, 100); + EXPECT_NE(default_group, extra_group); + + EXPECT_FALSE(client_trial->group_reported_); + EXPECT_EQ(default_group, client_trial->group()); + EXPECT_TRUE(client_trial->group_reported_); + EXPECT_EQ(kDefaultGroupName, client_trial->group_name()); +} + +TEST_F(FieldTrialTest, SetForcedChangeDefault_Default) { + const char kTrialName[] = "SetForcedDefaultGroupChange"; + const char kGroupAName[] = "A"; + const char kGroupBName[] = "B"; + ASSERT_FALSE(FieldTrialList::TrialExists(kTrialName)); + + // Simulate a server-side (forced) config that switches which group is default + // and ensures that the non-forced code receives the correct group numbers. + FieldTrial* forced_trial = + CreateFieldTrial(kTrialName, 100, kGroupAName, NULL); + forced_trial->AppendGroup(kGroupBName, 100); + forced_trial->SetForced(); + + int default_group = -1; + FieldTrial* client_trial = + CreateFieldTrial(kTrialName, 100, kGroupBName, &default_group); + const int extra_group = client_trial->AppendGroup(kGroupAName, 50); + EXPECT_NE(default_group, extra_group); + + EXPECT_FALSE(client_trial->group_reported_); + EXPECT_EQ(default_group, client_trial->group()); + EXPECT_TRUE(client_trial->group_reported_); + EXPECT_EQ(kGroupBName, client_trial->group_name()); +} + +TEST_F(FieldTrialTest, SetForcedChangeDefault_NonDefault) { + const char kTrialName[] = "SetForcedDefaultGroupChange"; + const char kGroupAName[] = "A"; + const char kGroupBName[] = "B"; + ASSERT_FALSE(FieldTrialList::TrialExists(kTrialName)); + + // Simulate a server-side (forced) config that switches which group is default + // and ensures that the non-forced code receives the correct group numbers. + FieldTrial* forced_trial = + CreateFieldTrial(kTrialName, 100, kGroupAName, NULL); + forced_trial->AppendGroup(kGroupBName, 0); + forced_trial->SetForced(); + + int default_group = -1; + FieldTrial* client_trial = + CreateFieldTrial(kTrialName, 100, kGroupBName, &default_group); + const int extra_group = client_trial->AppendGroup(kGroupAName, 50); + EXPECT_NE(default_group, extra_group); + + EXPECT_FALSE(client_trial->group_reported_); + EXPECT_EQ(extra_group, client_trial->group()); + EXPECT_TRUE(client_trial->group_reported_); + EXPECT_EQ(kGroupAName, client_trial->group_name()); +} + +TEST_F(FieldTrialTest, Observe) { + const char kTrialName[] = "TrialToObserve1"; + const char kSecondaryGroupName[] = "SecondaryGroup"; + + TestFieldTrialObserver observer; + int default_group = -1; + FieldTrial* trial = + CreateFieldTrial(kTrialName, 100, kDefaultGroupName, &default_group); + const int secondary_group = trial->AppendGroup(kSecondaryGroupName, 50); + const int chosen_group = trial->group(); + EXPECT_TRUE(chosen_group == default_group || chosen_group == secondary_group); + + RunLoop().RunUntilIdle(); + EXPECT_EQ(kTrialName, observer.trial_name()); + if (chosen_group == default_group) + EXPECT_EQ(kDefaultGroupName, observer.group_name()); + else + EXPECT_EQ(kSecondaryGroupName, observer.group_name()); +} + +TEST_F(FieldTrialTest, ObserveDisabled) { + const char kTrialName[] = "TrialToObserve2"; + + TestFieldTrialObserver observer; + int default_group = -1; + FieldTrial* trial = + CreateFieldTrial(kTrialName, 100, kDefaultGroupName, &default_group); + trial->AppendGroup("A", 25); + trial->AppendGroup("B", 25); + trial->AppendGroup("C", 25); + trial->Disable(); + + // Observer shouldn't be notified of a disabled trial. + RunLoop().RunUntilIdle(); + EXPECT_TRUE(observer.trial_name().empty()); + EXPECT_TRUE(observer.group_name().empty()); + + // Observer shouldn't be notified even after a |group()| call. + EXPECT_EQ(default_group, trial->group()); + RunLoop().RunUntilIdle(); + EXPECT_TRUE(observer.trial_name().empty()); + EXPECT_TRUE(observer.group_name().empty()); +} + +TEST_F(FieldTrialTest, ObserveForcedDisabled) { + const char kTrialName[] = "TrialToObserve3"; + + TestFieldTrialObserver observer; + int default_group = -1; + FieldTrial* trial = + CreateFieldTrial(kTrialName, 100, kDefaultGroupName, &default_group); + trial->AppendGroup("A", 25); + trial->AppendGroup("B", 25); + trial->AppendGroup("C", 25); + trial->SetForced(); + trial->Disable(); + + // Observer shouldn't be notified of a disabled trial, even when forced. + RunLoop().RunUntilIdle(); + EXPECT_TRUE(observer.trial_name().empty()); + EXPECT_TRUE(observer.group_name().empty()); + + // Observer shouldn't be notified even after a |group()| call. + EXPECT_EQ(default_group, trial->group()); + RunLoop().RunUntilIdle(); + EXPECT_TRUE(observer.trial_name().empty()); + EXPECT_TRUE(observer.group_name().empty()); +} + +TEST_F(FieldTrialTest, DisabledTrialNotActive) { + const char kTrialName[] = "DisabledTrial"; + ASSERT_FALSE(FieldTrialList::TrialExists(kTrialName)); + + FieldTrial* trial = + CreateFieldTrial(kTrialName, 100, kDefaultGroupName, NULL); + trial->AppendGroup("X", 50); + trial->Disable(); + + // Ensure the trial is not listed as active. + FieldTrial::ActiveGroups active_groups; + FieldTrialList::GetActiveFieldTrialGroups(&active_groups); + EXPECT_TRUE(active_groups.empty()); + + // Ensure the trial is not listed in the |StatesToString()| result. + std::string states; + FieldTrialList::StatesToString(&states); + EXPECT_TRUE(states.empty()); +} + +TEST_F(FieldTrialTest, ExpirationYearNotExpired) { + const char kTrialName[] = "NotExpired"; + const char kGroupName[] = "Group2"; + const int kProbability = 100; + ASSERT_FALSE(FieldTrialList::TrialExists(kTrialName)); + + FieldTrial* trial = + CreateFieldTrial(kTrialName, kProbability, kDefaultGroupName, NULL); + trial->AppendGroup(kGroupName, kProbability); + EXPECT_EQ(kGroupName, trial->group_name()); +} + +} // namespace base diff --git a/base/metrics/histogram.cc b/base/metrics/histogram.cc new file mode 100644 index 0000000000..fbe66d05d2 --- /dev/null +++ b/base/metrics/histogram.cc @@ -0,0 +1,834 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Histogram is an object that aggregates statistics, and can summarize them in +// various forms, including ASCII graphical, HTML, and numerically (as a +// vector of numbers corresponding to each of the aggregating buckets). +// See header file for details and examples. + +#include "base/metrics/histogram.h" + +#include + +#include +#include + +#include "base/compiler_specific.h" +#include "base/debug/alias.h" +#include "base/logging.h" +#include "base/metrics/sample_vector.h" +#include "base/metrics/statistics_recorder.h" +#include "base/pickle.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/synchronization/lock.h" +#include "base/values.h" + +using std::string; +using std::vector; + +namespace base { + +namespace { + +bool ReadHistogramArguments(PickleIterator* iter, + string* histogram_name, + int* flags, + int* declared_min, + int* declared_max, + uint64* bucket_count, + uint32* range_checksum) { + if (!iter->ReadString(histogram_name) || + !iter->ReadInt(flags) || + !iter->ReadInt(declared_min) || + !iter->ReadInt(declared_max) || + !iter->ReadUInt64(bucket_count) || + !iter->ReadUInt32(range_checksum)) { + DLOG(ERROR) << "Pickle error decoding Histogram: " << *histogram_name; + return false; + } + + // Since these fields may have come from an untrusted renderer, do additional + // checks above and beyond those in Histogram::Initialize() + if (*declared_max <= 0 || + *declared_min <= 0 || + *declared_max < *declared_min || + INT_MAX / sizeof(HistogramBase::Count) <= *bucket_count || + *bucket_count < 2) { + DLOG(ERROR) << "Values error decoding Histogram: " << histogram_name; + return false; + } + + // We use the arguments to find or create the local version of the histogram + // in this process, so we need to clear the IPC flag. + DCHECK(*flags & HistogramBase::kIPCSerializationSourceFlag); + *flags &= ~HistogramBase::kIPCSerializationSourceFlag; + + return true; +} + +bool ValidateRangeChecksum(const HistogramBase& histogram, + uint32 range_checksum) { + const Histogram& casted_histogram = + static_cast(histogram); + + return casted_histogram.bucket_ranges()->checksum() == range_checksum; +} + +} // namespace + +typedef HistogramBase::Count Count; +typedef HistogramBase::Sample Sample; + +// static +const size_t Histogram::kBucketCount_MAX = 16384u; + +HistogramBase* Histogram::FactoryGet(const string& name, + Sample minimum, + Sample maximum, + size_t bucket_count, + int32 flags) { + bool valid_arguments = + InspectConstructionArguments(name, &minimum, &maximum, &bucket_count); + DCHECK(valid_arguments); + + HistogramBase* histogram = StatisticsRecorder::FindHistogram(name); + if (!histogram) { + // To avoid racy destruction at shutdown, the following will be leaked. + BucketRanges* ranges = new BucketRanges(bucket_count + 1); + InitializeBucketRanges(minimum, maximum, ranges); + const BucketRanges* registered_ranges = + StatisticsRecorder::RegisterOrDeleteDuplicateRanges(ranges); + + Histogram* tentative_histogram = + new Histogram(name, minimum, maximum, registered_ranges); + + tentative_histogram->SetFlags(flags); + histogram = + StatisticsRecorder::RegisterOrDeleteDuplicate(tentative_histogram); + } + + DCHECK_EQ(HISTOGRAM, histogram->GetHistogramType()); + CHECK(histogram->HasConstructionArguments(minimum, maximum, bucket_count)); + return histogram; +} + +HistogramBase* Histogram::FactoryTimeGet(const string& name, + TimeDelta minimum, + TimeDelta maximum, + size_t bucket_count, + int32 flags) { + return FactoryGet(name, minimum.InMilliseconds(), maximum.InMilliseconds(), + bucket_count, flags); +} + +TimeTicks Histogram::DebugNow() { +#ifndef NDEBUG + return TimeTicks::Now(); +#else + return TimeTicks(); +#endif +} + +// Calculate what range of values are held in each bucket. +// We have to be careful that we don't pick a ratio between starting points in +// consecutive buckets that is sooo small, that the integer bounds are the same +// (effectively making one bucket get no values). We need to avoid: +// ranges(i) == ranges(i + 1) +// To avoid that, we just do a fine-grained bucket width as far as we need to +// until we get a ratio that moves us along at least 2 units at a time. From +// that bucket onward we do use the exponential growth of buckets. +// +// static +void Histogram::InitializeBucketRanges(Sample minimum, + Sample maximum, + BucketRanges* ranges) { + double log_max = log(static_cast(maximum)); + double log_ratio; + double log_next; + size_t bucket_index = 1; + Sample current = minimum; + ranges->set_range(bucket_index, current); + size_t bucket_count = ranges->bucket_count(); + while (bucket_count > ++bucket_index) { + double log_current; + log_current = log(static_cast(current)); + // Calculate the count'th root of the range. + log_ratio = (log_max - log_current) / (bucket_count - bucket_index); + // See where the next bucket would start. + log_next = log_current + log_ratio; + Sample next; + next = static_cast(floor(exp(log_next) + 0.5)); + if (next > current) + current = next; + else + ++current; // Just do a narrow bucket, and keep trying. + ranges->set_range(bucket_index, current); + } + ranges->set_range(ranges->bucket_count(), HistogramBase::kSampleType_MAX); + ranges->ResetChecksum(); +} + +// static +const int Histogram::kCommonRaceBasedCountMismatch = 5; + +int Histogram::FindCorruption(const HistogramSamples& samples) const { + int inconsistencies = NO_INCONSISTENCIES; + Sample previous_range = -1; // Bottom range is always 0. + for (size_t index = 0; index < bucket_count(); ++index) { + int new_range = ranges(index); + if (previous_range >= new_range) + inconsistencies |= BUCKET_ORDER_ERROR; + previous_range = new_range; + } + + if (!bucket_ranges()->HasValidChecksum()) + inconsistencies |= RANGE_CHECKSUM_ERROR; + + int64 delta64 = samples.redundant_count() - samples.TotalCount(); + if (delta64 != 0) { + int delta = static_cast(delta64); + if (delta != delta64) + delta = INT_MAX; // Flag all giant errors as INT_MAX. + if (delta > 0) { + UMA_HISTOGRAM_COUNTS("Histogram.InconsistentCountHigh", delta); + if (delta > kCommonRaceBasedCountMismatch) + inconsistencies |= COUNT_HIGH_ERROR; + } else { + DCHECK_GT(0, delta); + UMA_HISTOGRAM_COUNTS("Histogram.InconsistentCountLow", -delta); + if (-delta > kCommonRaceBasedCountMismatch) + inconsistencies |= COUNT_LOW_ERROR; + } + } + return inconsistencies; +} + +Sample Histogram::ranges(size_t i) const { + return bucket_ranges_->range(i); +} + +size_t Histogram::bucket_count() const { + return bucket_ranges_->bucket_count(); +} + +// static +bool Histogram::InspectConstructionArguments(const string& name, + Sample* minimum, + Sample* maximum, + size_t* bucket_count) { + // Defensive code for backward compatibility. + if (*minimum < 1) { + DVLOG(1) << "Histogram: " << name << " has bad minimum: " << *minimum; + *minimum = 1; + } + if (*maximum >= kSampleType_MAX) { + DVLOG(1) << "Histogram: " << name << " has bad maximum: " << *maximum; + *maximum = kSampleType_MAX - 1; + } + if (*bucket_count >= kBucketCount_MAX) { + DVLOG(1) << "Histogram: " << name << " has bad bucket_count: " + << *bucket_count; + *bucket_count = kBucketCount_MAX - 1; + } + + if (*minimum >= *maximum) + return false; + if (*bucket_count < 3) + return false; + if (*bucket_count > static_cast(*maximum - *minimum + 2)) + return false; + return true; +} + +HistogramType Histogram::GetHistogramType() const { + return HISTOGRAM; +} + +bool Histogram::HasConstructionArguments(Sample expected_minimum, + Sample expected_maximum, + size_t expected_bucket_count) const { + return ((expected_minimum == declared_min_) && + (expected_maximum == declared_max_) && + (expected_bucket_count == bucket_count())); +} + +void Histogram::Add(int value) { + DCHECK_EQ(0, ranges(0)); + DCHECK_EQ(kSampleType_MAX, ranges(bucket_count())); + + if (value > kSampleType_MAX - 1) + value = kSampleType_MAX - 1; + if (value < 0) + value = 0; + samples_->Accumulate(value, 1); +} + +scoped_ptr Histogram::SnapshotSamples() const { + return SnapshotSampleVector().PassAs(); +} + +void Histogram::AddSamples(const HistogramSamples& samples) { + samples_->Add(samples); +} + +bool Histogram::AddSamplesFromPickle(PickleIterator* iter) { + return samples_->AddFromPickle(iter); +} + +// The following methods provide a graphical histogram display. +void Histogram::WriteHTMLGraph(string* output) const { + // TBD(jar) Write a nice HTML bar chart, with divs an mouse-overs etc. + output->append("
");
+  WriteAsciiImpl(true, "
", output); + output->append("
"); +} + +void Histogram::WriteAscii(string* output) const { + WriteAsciiImpl(true, "\n", output); +} + +bool Histogram::SerializeInfoImpl(Pickle* pickle) const { + DCHECK(bucket_ranges()->HasValidChecksum()); + return pickle->WriteString(histogram_name()) && + pickle->WriteInt(flags()) && + pickle->WriteInt(declared_min()) && + pickle->WriteInt(declared_max()) && + pickle->WriteUInt64(bucket_count()) && + pickle->WriteUInt32(bucket_ranges()->checksum()); +} + +Histogram::Histogram(const string& name, + Sample minimum, + Sample maximum, + const BucketRanges* ranges) + : HistogramBase(name), + bucket_ranges_(ranges), + declared_min_(minimum), + declared_max_(maximum) { + if (ranges) + samples_.reset(new SampleVector(ranges)); +} + +Histogram::~Histogram() { +} + +bool Histogram::PrintEmptyBucket(size_t index) const { + return true; +} + +// Use the actual bucket widths (like a linear histogram) until the widths get +// over some transition value, and then use that transition width. Exponentials +// get so big so fast (and we don't expect to see a lot of entries in the large +// buckets), so we need this to make it possible to see what is going on and +// not have 0-graphical-height buckets. +double Histogram::GetBucketSize(Count current, size_t i) const { + DCHECK_GT(ranges(i + 1), ranges(i)); + static const double kTransitionWidth = 5; + double denominator = ranges(i + 1) - ranges(i); + if (denominator > kTransitionWidth) + denominator = kTransitionWidth; // Stop trying to normalize. + return current/denominator; +} + +const string Histogram::GetAsciiBucketRange(size_t i) const { + return GetSimpleAsciiBucketRange(ranges(i)); +} + +//------------------------------------------------------------------------------ +// Private methods + +// static +HistogramBase* Histogram::DeserializeInfoImpl(PickleIterator* iter) { + string histogram_name; + int flags; + int declared_min; + int declared_max; + uint64 bucket_count; + uint32 range_checksum; + + if (!ReadHistogramArguments(iter, &histogram_name, &flags, &declared_min, + &declared_max, &bucket_count, &range_checksum)) { + return NULL; + } + + // Find or create the local version of the histogram in this process. + HistogramBase* histogram = Histogram::FactoryGet( + histogram_name, declared_min, declared_max, bucket_count, flags); + + if (!ValidateRangeChecksum(*histogram, range_checksum)) { + // The serialized histogram might be corrupted. + return NULL; + } + return histogram; +} + +scoped_ptr Histogram::SnapshotSampleVector() const { + scoped_ptr samples(new SampleVector(bucket_ranges())); + samples->Add(*samples_); + return samples.Pass(); +} + +void Histogram::WriteAsciiImpl(bool graph_it, + const string& newline, + string* output) const { + // Get local (stack) copies of all effectively volatile class data so that we + // are consistent across our output activities. + scoped_ptr snapshot = SnapshotSampleVector(); + Count sample_count = snapshot->TotalCount(); + + WriteAsciiHeader(*snapshot, sample_count, output); + output->append(newline); + + // Prepare to normalize graphical rendering of bucket contents. + double max_size = 0; + if (graph_it) + max_size = GetPeakBucketSize(*snapshot); + + // Calculate space needed to print bucket range numbers. Leave room to print + // nearly the largest bucket range without sliding over the histogram. + size_t largest_non_empty_bucket = bucket_count() - 1; + while (0 == snapshot->GetCountAtIndex(largest_non_empty_bucket)) { + if (0 == largest_non_empty_bucket) + break; // All buckets are empty. + --largest_non_empty_bucket; + } + + // Calculate largest print width needed for any of our bucket range displays. + size_t print_width = 1; + for (size_t i = 0; i < bucket_count(); ++i) { + if (snapshot->GetCountAtIndex(i)) { + size_t width = GetAsciiBucketRange(i).size() + 1; + if (width > print_width) + print_width = width; + } + } + + int64 remaining = sample_count; + int64 past = 0; + // Output the actual histogram graph. + for (size_t i = 0; i < bucket_count(); ++i) { + Count current = snapshot->GetCountAtIndex(i); + if (!current && !PrintEmptyBucket(i)) + continue; + remaining -= current; + string range = GetAsciiBucketRange(i); + output->append(range); + for (size_t j = 0; range.size() + j < print_width + 1; ++j) + output->push_back(' '); + if (0 == current && i < bucket_count() - 1 && + 0 == snapshot->GetCountAtIndex(i + 1)) { + while (i < bucket_count() - 1 && + 0 == snapshot->GetCountAtIndex(i + 1)) { + ++i; + } + output->append("... "); + output->append(newline); + continue; // No reason to plot emptiness. + } + double current_size = GetBucketSize(current, i); + if (graph_it) + WriteAsciiBucketGraph(current_size, max_size, output); + WriteAsciiBucketContext(past, current, remaining, i, output); + output->append(newline); + past += current; + } + DCHECK_EQ(sample_count, past); +} + +double Histogram::GetPeakBucketSize(const SampleVector& samples) const { + double max = 0; + for (size_t i = 0; i < bucket_count() ; ++i) { + double current_size = GetBucketSize(samples.GetCountAtIndex(i), i); + if (current_size > max) + max = current_size; + } + return max; +} + +void Histogram::WriteAsciiHeader(const SampleVector& samples, + Count sample_count, + string* output) const { + StringAppendF(output, + "Histogram: %s recorded %d samples", + histogram_name().c_str(), + sample_count); + if (0 == sample_count) { + DCHECK_EQ(samples.sum(), 0); + } else { + double average = static_cast(samples.sum()) / sample_count; + + StringAppendF(output, ", average = %.1f", average); + } + if (flags() & ~kHexRangePrintingFlag) + StringAppendF(output, " (flags = 0x%x)", flags() & ~kHexRangePrintingFlag); +} + +void Histogram::WriteAsciiBucketContext(const int64 past, + const Count current, + const int64 remaining, + const size_t i, + string* output) const { + double scaled_sum = (past + current + remaining) / 100.0; + WriteAsciiBucketValue(current, scaled_sum, output); + if (0 < i) { + double percentage = past / scaled_sum; + StringAppendF(output, " {%3.1f%%}", percentage); + } +} + +void Histogram::GetParameters(DictionaryValue* params) const { + params->SetString("type", HistogramTypeToString(GetHistogramType())); + params->SetInteger("min", declared_min()); + params->SetInteger("max", declared_max()); + params->SetInteger("bucket_count", static_cast(bucket_count())); +} + +void Histogram::GetCountAndBucketData(Count* count, + int64* sum, + ListValue* buckets) const { + scoped_ptr snapshot = SnapshotSampleVector(); + *count = snapshot->TotalCount(); + *sum = snapshot->sum(); + size_t index = 0; + for (size_t i = 0; i < bucket_count(); ++i) { + Sample count = snapshot->GetCountAtIndex(i); + if (count > 0) { + scoped_ptr bucket_value(new DictionaryValue()); + bucket_value->SetInteger("low", ranges(i)); + if (i != bucket_count() - 1) + bucket_value->SetInteger("high", ranges(i + 1)); + bucket_value->SetInteger("count", count); + buckets->Set(index, bucket_value.release()); + ++index; + } + } +} + +//------------------------------------------------------------------------------ +// LinearHistogram: This histogram uses a traditional set of evenly spaced +// buckets. +//------------------------------------------------------------------------------ + +LinearHistogram::~LinearHistogram() {} + +HistogramBase* LinearHistogram::FactoryGet(const string& name, + Sample minimum, + Sample maximum, + size_t bucket_count, + int32 flags) { + return FactoryGetWithRangeDescription( + name, minimum, maximum, bucket_count, flags, NULL); +} + +HistogramBase* LinearHistogram::FactoryTimeGet(const string& name, + TimeDelta minimum, + TimeDelta maximum, + size_t bucket_count, + int32 flags) { + return FactoryGet(name, minimum.InMilliseconds(), maximum.InMilliseconds(), + bucket_count, flags); +} + +HistogramBase* LinearHistogram::FactoryGetWithRangeDescription( + const std::string& name, + Sample minimum, + Sample maximum, + size_t bucket_count, + int32 flags, + const DescriptionPair descriptions[]) { + bool valid_arguments = Histogram::InspectConstructionArguments( + name, &minimum, &maximum, &bucket_count); + DCHECK(valid_arguments); + + HistogramBase* histogram = StatisticsRecorder::FindHistogram(name); + if (!histogram) { + // To avoid racy destruction at shutdown, the following will be leaked. + BucketRanges* ranges = new BucketRanges(bucket_count + 1); + InitializeBucketRanges(minimum, maximum, ranges); + const BucketRanges* registered_ranges = + StatisticsRecorder::RegisterOrDeleteDuplicateRanges(ranges); + + LinearHistogram* tentative_histogram = + new LinearHistogram(name, minimum, maximum, registered_ranges); + + // Set range descriptions. + if (descriptions) { + for (int i = 0; descriptions[i].description; ++i) { + tentative_histogram->bucket_description_[descriptions[i].sample] = + descriptions[i].description; + } + } + + tentative_histogram->SetFlags(flags); + histogram = + StatisticsRecorder::RegisterOrDeleteDuplicate(tentative_histogram); + } + + DCHECK_EQ(LINEAR_HISTOGRAM, histogram->GetHistogramType()); + CHECK(histogram->HasConstructionArguments(minimum, maximum, bucket_count)); + return histogram; +} + +HistogramType LinearHistogram::GetHistogramType() const { + return LINEAR_HISTOGRAM; +} + +LinearHistogram::LinearHistogram(const string& name, + Sample minimum, + Sample maximum, + const BucketRanges* ranges) + : Histogram(name, minimum, maximum, ranges) { +} + +double LinearHistogram::GetBucketSize(Count current, size_t i) const { + DCHECK_GT(ranges(i + 1), ranges(i)); + // Adjacent buckets with different widths would have "surprisingly" many (few) + // samples in a histogram if we didn't normalize this way. + double denominator = ranges(i + 1) - ranges(i); + return current/denominator; +} + +const string LinearHistogram::GetAsciiBucketRange(size_t i) const { + int range = ranges(i); + BucketDescriptionMap::const_iterator it = bucket_description_.find(range); + if (it == bucket_description_.end()) + return Histogram::GetAsciiBucketRange(i); + return it->second; +} + +bool LinearHistogram::PrintEmptyBucket(size_t index) const { + return bucket_description_.find(ranges(index)) == bucket_description_.end(); +} + +// static +void LinearHistogram::InitializeBucketRanges(Sample minimum, + Sample maximum, + BucketRanges* ranges) { + double min = minimum; + double max = maximum; + size_t bucket_count = ranges->bucket_count(); + for (size_t i = 1; i < bucket_count; ++i) { + double linear_range = + (min * (bucket_count - 1 - i) + max * (i - 1)) / (bucket_count - 2); + ranges->set_range(i, static_cast(linear_range + 0.5)); + } + ranges->set_range(ranges->bucket_count(), HistogramBase::kSampleType_MAX); + ranges->ResetChecksum(); +} + +// static +HistogramBase* LinearHistogram::DeserializeInfoImpl(PickleIterator* iter) { + string histogram_name; + int flags; + int declared_min; + int declared_max; + uint64 bucket_count; + uint32 range_checksum; + + if (!ReadHistogramArguments(iter, &histogram_name, &flags, &declared_min, + &declared_max, &bucket_count, &range_checksum)) { + return NULL; + } + + HistogramBase* histogram = LinearHistogram::FactoryGet( + histogram_name, declared_min, declared_max, bucket_count, flags); + if (!ValidateRangeChecksum(*histogram, range_checksum)) { + // The serialized histogram might be corrupted. + return NULL; + } + return histogram; +} + +//------------------------------------------------------------------------------ +// This section provides implementation for BooleanHistogram. +//------------------------------------------------------------------------------ + +HistogramBase* BooleanHistogram::FactoryGet(const string& name, int32 flags) { + HistogramBase* histogram = StatisticsRecorder::FindHistogram(name); + if (!histogram) { + // To avoid racy destruction at shutdown, the following will be leaked. + BucketRanges* ranges = new BucketRanges(4); + LinearHistogram::InitializeBucketRanges(1, 2, ranges); + const BucketRanges* registered_ranges = + StatisticsRecorder::RegisterOrDeleteDuplicateRanges(ranges); + + BooleanHistogram* tentative_histogram = + new BooleanHistogram(name, registered_ranges); + + tentative_histogram->SetFlags(flags); + histogram = + StatisticsRecorder::RegisterOrDeleteDuplicate(tentative_histogram); + } + + DCHECK_EQ(BOOLEAN_HISTOGRAM, histogram->GetHistogramType()); + return histogram; +} + +HistogramType BooleanHistogram::GetHistogramType() const { + return BOOLEAN_HISTOGRAM; +} + +BooleanHistogram::BooleanHistogram(const string& name, + const BucketRanges* ranges) + : LinearHistogram(name, 1, 2, ranges) {} + +HistogramBase* BooleanHistogram::DeserializeInfoImpl(PickleIterator* iter) { + string histogram_name; + int flags; + int declared_min; + int declared_max; + uint64 bucket_count; + uint32 range_checksum; + + if (!ReadHistogramArguments(iter, &histogram_name, &flags, &declared_min, + &declared_max, &bucket_count, &range_checksum)) { + return NULL; + } + + HistogramBase* histogram = BooleanHistogram::FactoryGet( + histogram_name, flags); + if (!ValidateRangeChecksum(*histogram, range_checksum)) { + // The serialized histogram might be corrupted. + return NULL; + } + return histogram; +} + +//------------------------------------------------------------------------------ +// CustomHistogram: +//------------------------------------------------------------------------------ + +HistogramBase* CustomHistogram::FactoryGet(const string& name, + const vector& custom_ranges, + int32 flags) { + CHECK(ValidateCustomRanges(custom_ranges)); + + HistogramBase* histogram = StatisticsRecorder::FindHistogram(name); + if (!histogram) { + BucketRanges* ranges = CreateBucketRangesFromCustomRanges(custom_ranges); + const BucketRanges* registered_ranges = + StatisticsRecorder::RegisterOrDeleteDuplicateRanges(ranges); + + // To avoid racy destruction at shutdown, the following will be leaked. + CustomHistogram* tentative_histogram = + new CustomHistogram(name, registered_ranges); + + tentative_histogram->SetFlags(flags); + + histogram = + StatisticsRecorder::RegisterOrDeleteDuplicate(tentative_histogram); + } + + DCHECK_EQ(histogram->GetHistogramType(), CUSTOM_HISTOGRAM); + return histogram; +} + +HistogramType CustomHistogram::GetHistogramType() const { + return CUSTOM_HISTOGRAM; +} + +// static +vector CustomHistogram::ArrayToCustomRanges( + const Sample* values, size_t num_values) { + vector all_values; + for (size_t i = 0; i < num_values; ++i) { + Sample value = values[i]; + all_values.push_back(value); + + // Ensure that a guard bucket is added. If we end up with duplicate + // values, FactoryGet will take care of removing them. + all_values.push_back(value + 1); + } + return all_values; +} + +CustomHistogram::CustomHistogram(const string& name, + const BucketRanges* ranges) + : Histogram(name, + ranges->range(1), + ranges->range(ranges->bucket_count() - 1), + ranges) {} + +bool CustomHistogram::SerializeInfoImpl(Pickle* pickle) const { + if (!Histogram::SerializeInfoImpl(pickle)) + return false; + + // Serialize ranges. First and last ranges are alwasy 0 and INT_MAX, so don't + // write them. + for (size_t i = 1; i < bucket_ranges()->bucket_count(); ++i) { + if (!pickle->WriteInt(bucket_ranges()->range(i))) + return false; + } + return true; +} + +double CustomHistogram::GetBucketSize(Count current, size_t i) const { + return 1; +} + +// static +HistogramBase* CustomHistogram::DeserializeInfoImpl(PickleIterator* iter) { + string histogram_name; + int flags; + int declared_min; + int declared_max; + uint64 bucket_count; + uint32 range_checksum; + + if (!ReadHistogramArguments(iter, &histogram_name, &flags, &declared_min, + &declared_max, &bucket_count, &range_checksum)) { + return NULL; + } + + // First and last ranges are not serialized. + vector sample_ranges(bucket_count - 1); + + for (size_t i = 0; i < sample_ranges.size(); ++i) { + if (!iter->ReadInt(&sample_ranges[i])) + return NULL; + } + + HistogramBase* histogram = CustomHistogram::FactoryGet( + histogram_name, sample_ranges, flags); + if (!ValidateRangeChecksum(*histogram, range_checksum)) { + // The serialized histogram might be corrupted. + return NULL; + } + return histogram; +} + +// static +bool CustomHistogram::ValidateCustomRanges( + const vector& custom_ranges) { + bool has_valid_range = false; + for (size_t i = 0; i < custom_ranges.size(); i++) { + Sample sample = custom_ranges[i]; + if (sample < 0 || sample > HistogramBase::kSampleType_MAX - 1) + return false; + if (sample != 0) + has_valid_range = true; + } + return has_valid_range; +} + +// static +BucketRanges* CustomHistogram::CreateBucketRangesFromCustomRanges( + const vector& custom_ranges) { + // Remove the duplicates in the custom ranges array. + vector ranges = custom_ranges; + ranges.push_back(0); // Ensure we have a zero value. + ranges.push_back(HistogramBase::kSampleType_MAX); + std::sort(ranges.begin(), ranges.end()); + ranges.erase(std::unique(ranges.begin(), ranges.end()), ranges.end()); + + BucketRanges* bucket_ranges = new BucketRanges(ranges.size()); + for (size_t i = 0; i < ranges.size(); i++) { + bucket_ranges->set_range(i, ranges[i]); + } + bucket_ranges->ResetChecksum(); + return bucket_ranges; +} + +} // namespace base diff --git a/base/metrics/histogram.h b/base/metrics/histogram.h new file mode 100644 index 0000000000..d82f90aabd --- /dev/null +++ b/base/metrics/histogram.h @@ -0,0 +1,675 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Histogram is an object that aggregates statistics, and can summarize them in +// various forms, including ASCII graphical, HTML, and numerically (as a +// vector of numbers corresponding to each of the aggregating buckets). + +// It supports calls to accumulate either time intervals (which are processed +// as integral number of milliseconds), or arbitrary integral units. + +// For Histogram(exponential histogram), LinearHistogram and CustomHistogram, +// the minimum for a declared range is 1 (instead of 0), while the maximum is +// (HistogramBase::kSampleType_MAX - 1). Currently you can declare histograms +// with ranges exceeding those limits (e.g. 0 as minimal or +// HistogramBase::kSampleType_MAX as maximal), but those excesses will be +// silently clamped to those limits (for backwards compatibility with existing +// code). Best practice is to not exceed the limits. + +// Each use of a histogram with the same name will reference the same underlying +// data, so it is safe to record to the same histogram from multiple locations +// in the code. It is a runtime error if all uses of the same histogram do not +// agree exactly in type, bucket size and range. + +// For Histogram and LinearHistogram, the maximum for a declared range should +// always be larger (not equal) than minmal range. Zero and +// HistogramBase::kSampleType_MAX are implicitly added as first and last ranges, +// so the smallest legal bucket_count is 3. However CustomHistogram can have +// bucket count as 2 (when you give a custom ranges vector containing only 1 +// range). +// For these 3 kinds of histograms, the max bucket count is always +// (Histogram::kBucketCount_MAX - 1). + +// The buckets layout of class Histogram is exponential. For example, buckets +// might contain (sequentially) the count of values in the following intervals: +// [0,1), [1,2), [2,4), [4,8), [8,16), [16,32), [32,64), [64,infinity) +// That bucket allocation would actually result from construction of a histogram +// for values between 1 and 64, with 8 buckets, such as: +// Histogram count("some name", 1, 64, 8); +// Note that the underflow bucket [0,1) and the overflow bucket [64,infinity) +// are also counted by the constructor in the user supplied "bucket_count" +// argument. +// The above example has an exponential ratio of 2 (doubling the bucket width +// in each consecutive bucket. The Histogram class automatically calculates +// the smallest ratio that it can use to construct the number of buckets +// selected in the constructor. An another example, if you had 50 buckets, +// and millisecond time values from 1 to 10000, then the ratio between +// consecutive bucket widths will be approximately somewhere around the 50th +// root of 10000. This approach provides very fine grain (narrow) buckets +// at the low end of the histogram scale, but allows the histogram to cover a +// gigantic range with the addition of very few buckets. + +// Usually we use macros to define and use a histogram. These macros use a +// pattern involving a function static variable, that is a pointer to a +// histogram. This static is explicitly initialized on any thread +// that detects a uninitialized (NULL) pointer. The potentially racy +// initialization is not a problem as it is always set to point to the same +// value (i.e., the FactoryGet always returns the same value). FactoryGet +// is also completely thread safe, which results in a completely thread safe, +// and relatively fast, set of counters. To avoid races at shutdown, the static +// pointer is NOT deleted, and we leak the histograms at process termination. + +#ifndef BASE_METRICS_HISTOGRAM_H_ +#define BASE_METRICS_HISTOGRAM_H_ + +#include +#include +#include + +#include "base/atomicops.h" +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/metrics/bucket_ranges.h" +#include "base/metrics/histogram_base.h" +#include "base/metrics/histogram_samples.h" +#include "base/time/time.h" + +class Pickle; +class PickleIterator; + +namespace base { + +class Lock; +//------------------------------------------------------------------------------ +// Histograms are often put in areas where they are called many many times, and +// performance is critical. As a result, they are designed to have a very low +// recurring cost of executing (adding additional samples). Toward that end, +// the macros declare a static pointer to the histogram in question, and only +// take a "slow path" to construct (or find) the histogram on the first run +// through the macro. We leak the histograms at shutdown time so that we don't +// have to validate using the pointers at any time during the running of the +// process. + +// The following code is generally what a thread-safe static pointer +// initializaion looks like for a histogram (after a macro is expanded). This +// sample is an expansion (with comments) of the code for +// HISTOGRAM_CUSTOM_COUNTS(). + +/* + do { + // The pointer's presence indicates the initialization is complete. + // Initialization is idempotent, so it can safely be atomically repeated. + static base::subtle::AtomicWord atomic_histogram_pointer = 0; + + // Acquire_Load() ensures that we acquire visibility to the pointed-to data + // in the histogrom. + base::Histogram* histogram_pointer(reinterpret_cast( + base::subtle::Acquire_Load(&atomic_histogram_pointer))); + + if (!histogram_pointer) { + // This is the slow path, which will construct OR find the matching + // histogram. FactoryGet includes locks on a global histogram name map + // and is completely thread safe. + histogram_pointer = base::Histogram::FactoryGet( + name, min, max, bucket_count, base::HistogramBase::kNoFlags); + + // Use Release_Store to ensure that the histogram data is made available + // globally before we make the pointer visible. + // Several threads may perform this store, but the same value will be + // stored in all cases (for a given named/spec'ed histogram). + // We could do this without any barrier, since FactoryGet entered and + // exited a lock after construction, but this barrier makes things clear. + base::subtle::Release_Store(&atomic_histogram_pointer, + reinterpret_cast(histogram_pointer)); + } + + // Ensure calling contract is upheld, and the name does NOT vary. + DCHECK(histogram_pointer->histogram_name() == constant_histogram_name); + + histogram_pointer->Add(sample); + } while (0); +*/ + +// The above pattern is repeated in several macros. The only elements that +// vary are the invocation of the Add(sample) vs AddTime(sample), and the choice +// of which FactoryGet method to use. The different FactoryGet methods have +// various argument lists, so the function with its argument list is provided as +// a macro argument here. The name is only used in a DCHECK, to assure that +// callers don't try to vary the name of the histogram (which would tend to be +// ignored by the one-time initialization of the histogtram_pointer). +#define STATIC_HISTOGRAM_POINTER_BLOCK(constant_histogram_name, \ + histogram_add_method_invocation, \ + histogram_factory_get_invocation) \ + do { \ + static base::subtle::AtomicWord atomic_histogram_pointer = 0; \ + base::HistogramBase* histogram_pointer( \ + reinterpret_cast( \ + base::subtle::Acquire_Load(&atomic_histogram_pointer))); \ + if (!histogram_pointer) { \ + histogram_pointer = histogram_factory_get_invocation; \ + base::subtle::Release_Store(&atomic_histogram_pointer, \ + reinterpret_cast(histogram_pointer)); \ + } \ + DCHECK_EQ(histogram_pointer->histogram_name(), \ + std::string(constant_histogram_name)); \ + histogram_pointer->histogram_add_method_invocation; \ + } while (0) + + +//------------------------------------------------------------------------------ +// Provide easy general purpose histogram in a macro, just like stats counters. +// The first four macros use 50 buckets. + +#define HISTOGRAM_TIMES(name, sample) HISTOGRAM_CUSTOM_TIMES( \ + name, sample, base::TimeDelta::FromMilliseconds(1), \ + base::TimeDelta::FromSeconds(10), 50) + +// For folks that need real specific times, use this to select a precise range +// of times you want plotted, and the number of buckets you want used. +#define HISTOGRAM_CUSTOM_TIMES(name, sample, min, max, bucket_count) \ + STATIC_HISTOGRAM_POINTER_BLOCK(name, AddTime(sample), \ + base::Histogram::FactoryTimeGet(name, min, max, bucket_count, \ + base::HistogramBase::kNoFlags)) + +#define HISTOGRAM_COUNTS(name, sample) HISTOGRAM_CUSTOM_COUNTS( \ + name, sample, 1, 1000000, 50) + +#define HISTOGRAM_COUNTS_100(name, sample) HISTOGRAM_CUSTOM_COUNTS( \ + name, sample, 1, 100, 50) + +#define HISTOGRAM_COUNTS_10000(name, sample) HISTOGRAM_CUSTOM_COUNTS( \ + name, sample, 1, 10000, 50) + +#define HISTOGRAM_CUSTOM_COUNTS(name, sample, min, max, bucket_count) \ + STATIC_HISTOGRAM_POINTER_BLOCK(name, Add(sample), \ + base::Histogram::FactoryGet(name, min, max, bucket_count, \ + base::HistogramBase::kNoFlags)) + +#define HISTOGRAM_PERCENTAGE(name, under_one_hundred) \ + HISTOGRAM_ENUMERATION(name, under_one_hundred, 101) + +#define HISTOGRAM_BOOLEAN(name, sample) \ + STATIC_HISTOGRAM_POINTER_BLOCK(name, AddBoolean(sample), \ + base::BooleanHistogram::FactoryGet(name, base::Histogram::kNoFlags)) + +// Support histograming of an enumerated value. The samples should always be +// strictly less than |boundary_value| -- this prevents you from running into +// problems down the line if you add additional buckets to the histogram. Note +// also that, despite explicitly setting the minimum bucket value to |1| below, +// it is fine for enumerated histograms to be 0-indexed -- this is because +// enumerated histograms should never have underflow. +#define HISTOGRAM_ENUMERATION(name, sample, boundary_value) \ + STATIC_HISTOGRAM_POINTER_BLOCK(name, Add(sample), \ + base::LinearHistogram::FactoryGet(name, 1, boundary_value, \ + boundary_value + 1, base::HistogramBase::kNoFlags)) + +// Support histograming of an enumerated value. Samples should be one of the +// std::vector list provided via |custom_ranges|. See comments above +// CustomRanges::FactoryGet about the requirement of |custom_ranges|. +// You can use the helper function CustomHistogram::ArrayToCustomRanges to +// transform a C-style array of valid sample values to a std::vector. +#define HISTOGRAM_CUSTOM_ENUMERATION(name, sample, custom_ranges) \ + STATIC_HISTOGRAM_POINTER_BLOCK(name, Add(sample), \ + base::CustomHistogram::FactoryGet(name, custom_ranges, \ + base::HistogramBase::kNoFlags)) + +#define HISTOGRAM_MEMORY_KB(name, sample) HISTOGRAM_CUSTOM_COUNTS( \ + name, sample, 1000, 500000, 50) + +//------------------------------------------------------------------------------ +// Define Debug vs non-debug flavors of macros. +#ifndef NDEBUG + +#define DHISTOGRAM_TIMES(name, sample) HISTOGRAM_TIMES(name, sample) +#define DHISTOGRAM_COUNTS(name, sample) HISTOGRAM_COUNTS(name, sample) +#define DHISTOGRAM_PERCENTAGE(name, under_one_hundred) HISTOGRAM_PERCENTAGE(\ + name, under_one_hundred) +#define DHISTOGRAM_CUSTOM_TIMES(name, sample, min, max, bucket_count) \ + HISTOGRAM_CUSTOM_TIMES(name, sample, min, max, bucket_count) +#define DHISTOGRAM_CLIPPED_TIMES(name, sample, min, max, bucket_count) \ + HISTOGRAM_CLIPPED_TIMES(name, sample, min, max, bucket_count) +#define DHISTOGRAM_CUSTOM_COUNTS(name, sample, min, max, bucket_count) \ + HISTOGRAM_CUSTOM_COUNTS(name, sample, min, max, bucket_count) +#define DHISTOGRAM_ENUMERATION(name, sample, boundary_value) \ + HISTOGRAM_ENUMERATION(name, sample, boundary_value) +#define DHISTOGRAM_CUSTOM_ENUMERATION(name, sample, custom_ranges) \ + HISTOGRAM_CUSTOM_ENUMERATION(name, sample, custom_ranges) + +#else // NDEBUG +// Keep a mention of passed variables to avoid unused variable warnings in +// release build if these variables are only used in macros. +#define DISCARD_2_ARGUMENTS(a, b) \ + while (0) { \ + static_cast(a); \ + static_cast(b); \ + } +#define DISCARD_3_ARGUMENTS(a, b, c) \ + while (0) { \ + static_cast(a); \ + static_cast(b); \ + static_cast(c); \ + } +#define DISCARD_5_ARGUMENTS(a, b, c, d ,e) \ + while (0) { \ + static_cast(a); \ + static_cast(b); \ + static_cast(c); \ + static_cast(d); \ + static_cast(e); \ + } +#define DHISTOGRAM_TIMES(name, sample) \ + DISCARD_2_ARGUMENTS(name, sample) + +#define DHISTOGRAM_COUNTS(name, sample) \ + DISCARD_2_ARGUMENTS(name, sample) + +#define DHISTOGRAM_PERCENTAGE(name, under_one_hundred) \ + DISCARD_2_ARGUMENTS(name, under_one_hundred) + +#define DHISTOGRAM_CUSTOM_TIMES(name, sample, min, max, bucket_count) \ + DISCARD_5_ARGUMENTS(name, sample, min, max, bucket_count) + +#define DHISTOGRAM_CLIPPED_TIMES(name, sample, min, max, bucket_count) \ + DISCARD_5_ARGUMENTS(name, sample, min, max, bucket_count) + +#define DHISTOGRAM_CUSTOM_COUNTS(name, sample, min, max, bucket_count) \ + DISCARD_5_ARGUMENTS(name, sample, min, max, bucket_count) + +#define DHISTOGRAM_ENUMERATION(name, sample, boundary_value) \ + DISCARD_3_ARGUMENTS(name, sample, boundary_value) + +#define DHISTOGRAM_CUSTOM_ENUMERATION(name, sample, custom_ranges) \ + DISCARD_3_ARGUMENTS(name, sample, custom_ranges) + +#endif // NDEBUG + +//------------------------------------------------------------------------------ +// The following macros provide typical usage scenarios for callers that wish +// to record histogram data, and have the data submitted/uploaded via UMA. +// Not all systems support such UMA, but if they do, the following macros +// should work with the service. + +#define UMA_HISTOGRAM_TIMES(name, sample) UMA_HISTOGRAM_CUSTOM_TIMES( \ + name, sample, base::TimeDelta::FromMilliseconds(1), \ + base::TimeDelta::FromSeconds(10), 50) + +#define UMA_HISTOGRAM_MEDIUM_TIMES(name, sample) UMA_HISTOGRAM_CUSTOM_TIMES( \ + name, sample, base::TimeDelta::FromMilliseconds(10), \ + base::TimeDelta::FromMinutes(3), 50) + +// Use this macro when times can routinely be much longer than 10 seconds. +#define UMA_HISTOGRAM_LONG_TIMES(name, sample) UMA_HISTOGRAM_CUSTOM_TIMES( \ + name, sample, base::TimeDelta::FromMilliseconds(1), \ + base::TimeDelta::FromHours(1), 50) + +#define UMA_HISTOGRAM_CUSTOM_TIMES(name, sample, min, max, bucket_count) \ + STATIC_HISTOGRAM_POINTER_BLOCK(name, AddTime(sample), \ + base::Histogram::FactoryTimeGet(name, min, max, bucket_count, \ + base::HistogramBase::kUmaTargetedHistogramFlag)) + +#define UMA_HISTOGRAM_COUNTS(name, sample) UMA_HISTOGRAM_CUSTOM_COUNTS( \ + name, sample, 1, 1000000, 50) + +#define UMA_HISTOGRAM_COUNTS_100(name, sample) UMA_HISTOGRAM_CUSTOM_COUNTS( \ + name, sample, 1, 100, 50) + +#define UMA_HISTOGRAM_COUNTS_10000(name, sample) UMA_HISTOGRAM_CUSTOM_COUNTS( \ + name, sample, 1, 10000, 50) + +#define UMA_HISTOGRAM_CUSTOM_COUNTS(name, sample, min, max, bucket_count) \ + STATIC_HISTOGRAM_POINTER_BLOCK(name, Add(sample), \ + base::Histogram::FactoryGet(name, min, max, bucket_count, \ + base::HistogramBase::kUmaTargetedHistogramFlag)) + +#define UMA_HISTOGRAM_MEMORY_KB(name, sample) UMA_HISTOGRAM_CUSTOM_COUNTS( \ + name, sample, 1000, 500000, 50) + +#define UMA_HISTOGRAM_MEMORY_MB(name, sample) UMA_HISTOGRAM_CUSTOM_COUNTS( \ + name, sample, 1, 1000, 50) + +#define UMA_HISTOGRAM_PERCENTAGE(name, under_one_hundred) \ + UMA_HISTOGRAM_ENUMERATION(name, under_one_hundred, 101) + +#define UMA_HISTOGRAM_BOOLEAN(name, sample) \ + STATIC_HISTOGRAM_POINTER_BLOCK(name, AddBoolean(sample), \ + base::BooleanHistogram::FactoryGet(name, \ + base::HistogramBase::kUmaTargetedHistogramFlag)) + +// The samples should always be strictly less than |boundary_value|. For more +// details, see the comment for the |HISTOGRAM_ENUMERATION| macro, above. +#define UMA_HISTOGRAM_ENUMERATION(name, sample, boundary_value) \ + STATIC_HISTOGRAM_POINTER_BLOCK(name, Add(sample), \ + base::LinearHistogram::FactoryGet(name, 1, boundary_value, \ + boundary_value + 1, base::HistogramBase::kUmaTargetedHistogramFlag)) + +#define UMA_HISTOGRAM_CUSTOM_ENUMERATION(name, sample, custom_ranges) \ + STATIC_HISTOGRAM_POINTER_BLOCK(name, Add(sample), \ + base::CustomHistogram::FactoryGet(name, custom_ranges, \ + base::HistogramBase::kUmaTargetedHistogramFlag)) + +//------------------------------------------------------------------------------ + +class BucketRanges; +class SampleVector; + +class BooleanHistogram; +class CustomHistogram; +class Histogram; +class LinearHistogram; + +class BASE_EXPORT Histogram : public HistogramBase { + public: + // Initialize maximum number of buckets in histograms as 16,384. + static const size_t kBucketCount_MAX; + + typedef std::vector Counts; + + //---------------------------------------------------------------------------- + // For a valid histogram, input should follow these restrictions: + // minimum > 0 (if a minimum below 1 is specified, it will implicitly be + // normalized up to 1) + // maximum > minimum + // buckets > 2 [minimum buckets needed: underflow, overflow and the range] + // Additionally, + // buckets <= (maximum - minimum + 2) - this is to ensure that we don't have + // more buckets than the range of numbers; having more buckets than 1 per + // value in the range would be nonsensical. + static HistogramBase* FactoryGet(const std::string& name, + Sample minimum, + Sample maximum, + size_t bucket_count, + int32 flags); + static HistogramBase* FactoryTimeGet(const std::string& name, + base::TimeDelta minimum, + base::TimeDelta maximum, + size_t bucket_count, + int32 flags); + + // Time call for use with DHISTOGRAM*. + // Returns TimeTicks::Now() in debug and TimeTicks() in release build. + static TimeTicks DebugNow(); + + static void InitializeBucketRanges(Sample minimum, + Sample maximum, + BucketRanges* ranges); + + // This constant if for FindCorruption. Since snapshots of histograms are + // taken asynchronously relative to sampling, and our counting code currently + // does not prevent race conditions, it is pretty likely that we'll catch a + // redundant count that doesn't match the sample count. We allow for a + // certain amount of slop before flagging this as an inconsistency. Even with + // an inconsistency, we'll snapshot it again (for UMA in about a half hour), + // so we'll eventually get the data, if it was not the result of a corruption. + static const int kCommonRaceBasedCountMismatch; + + // Check to see if bucket ranges, counts and tallies in the snapshot are + // consistent with the bucket ranges and checksums in our histogram. This can + // produce a false-alarm if a race occurred in the reading of the data during + // a SnapShot process, but should otherwise be false at all times (unless we + // have memory over-writes, or DRAM failures). + virtual int FindCorruption(const HistogramSamples& samples) const OVERRIDE; + + //---------------------------------------------------------------------------- + // Accessors for factory constuction, serialization and testing. + //---------------------------------------------------------------------------- + Sample declared_min() const { return declared_min_; } + Sample declared_max() const { return declared_max_; } + virtual Sample ranges(size_t i) const; + virtual size_t bucket_count() const; + const BucketRanges* bucket_ranges() const { return bucket_ranges_; } + + // This function validates histogram construction arguments. It returns false + // if some of the arguments are totally bad. + // Note. Currently it allow some bad input, e.g. 0 as minimum, but silently + // converts it to good input: 1. + // TODO(kaiwang): Be more restrict and return false for any bad input, and + // make this a readonly validating function. + static bool InspectConstructionArguments(const std::string& name, + Sample* minimum, + Sample* maximum, + size_t* bucket_count); + + // HistogramBase implementation: + virtual HistogramType GetHistogramType() const OVERRIDE; + virtual bool HasConstructionArguments( + Sample expected_minimum, + Sample expected_maximum, + size_t expected_bucket_count) const OVERRIDE; + virtual void Add(Sample value) OVERRIDE; + virtual scoped_ptr SnapshotSamples() const OVERRIDE; + virtual void AddSamples(const HistogramSamples& samples) OVERRIDE; + virtual bool AddSamplesFromPickle(PickleIterator* iter) OVERRIDE; + virtual void WriteHTMLGraph(std::string* output) const OVERRIDE; + virtual void WriteAscii(std::string* output) const OVERRIDE; + + protected: + // |ranges| should contain the underflow and overflow buckets. See top + // comments for example. + Histogram(const std::string& name, + Sample minimum, + Sample maximum, + const BucketRanges* ranges); + + virtual ~Histogram(); + + // HistogramBase implementation: + virtual bool SerializeInfoImpl(Pickle* pickle) const OVERRIDE; + + // Method to override to skip the display of the i'th bucket if it's empty. + virtual bool PrintEmptyBucket(size_t index) const; + + // Get normalized size, relative to the ranges(i). + virtual double GetBucketSize(Count current, size_t i) const; + + // Return a string description of what goes in a given bucket. + // Most commonly this is the numeric value, but in derived classes it may + // be a name (or string description) given to the bucket. + virtual const std::string GetAsciiBucketRange(size_t it) const; + + private: + // Allow tests to corrupt our innards for testing purposes. + FRIEND_TEST_ALL_PREFIXES(HistogramTest, BoundsTest); + FRIEND_TEST_ALL_PREFIXES(HistogramTest, BucketPlacementTest); + FRIEND_TEST_ALL_PREFIXES(HistogramTest, CorruptBucketBounds); + FRIEND_TEST_ALL_PREFIXES(HistogramTest, CorruptSampleCounts); + FRIEND_TEST_ALL_PREFIXES(HistogramTest, NameMatchTest); + + friend class StatisticsRecorder; // To allow it to delete duplicates. + friend class StatisticsRecorderTest; + + friend BASE_EXPORT_PRIVATE HistogramBase* DeserializeHistogramInfo( + PickleIterator* iter); + static HistogramBase* DeserializeInfoImpl(PickleIterator* iter); + + // Implementation of SnapshotSamples function. + scoped_ptr SnapshotSampleVector() const; + + //---------------------------------------------------------------------------- + // Helpers for emitting Ascii graphic. Each method appends data to output. + + void WriteAsciiImpl(bool graph_it, + const std::string& newline, + std::string* output) const; + + // Find out how large (graphically) the largest bucket will appear to be. + double GetPeakBucketSize(const SampleVector& samples) const; + + // Write a common header message describing this histogram. + void WriteAsciiHeader(const SampleVector& samples, + Count sample_count, + std::string* output) const; + + // Write information about previous, current, and next buckets. + // Information such as cumulative percentage, etc. + void WriteAsciiBucketContext(const int64 past, const Count current, + const int64 remaining, const size_t i, + std::string* output) const; + + // WriteJSON calls these. + virtual void GetParameters(DictionaryValue* params) const OVERRIDE; + + virtual void GetCountAndBucketData(Count* count, + int64* sum, + ListValue* buckets) const OVERRIDE; + + // Does not own this object. Should get from StatisticsRecorder. + const BucketRanges* bucket_ranges_; + + Sample declared_min_; // Less than this goes into the first bucket. + Sample declared_max_; // Over this goes into the last bucket. + + // Finally, provide the state that changes with the addition of each new + // sample. + scoped_ptr samples_; + + DISALLOW_COPY_AND_ASSIGN(Histogram); +}; + +//------------------------------------------------------------------------------ + +// LinearHistogram is a more traditional histogram, with evenly spaced +// buckets. +class BASE_EXPORT LinearHistogram : public Histogram { + public: + virtual ~LinearHistogram(); + + /* minimum should start from 1. 0 is as minimum is invalid. 0 is an implicit + default underflow bucket. */ + static HistogramBase* FactoryGet(const std::string& name, + Sample minimum, + Sample maximum, + size_t bucket_count, + int32 flags); + static HistogramBase* FactoryTimeGet(const std::string& name, + TimeDelta minimum, + TimeDelta maximum, + size_t bucket_count, + int32 flags); + + struct DescriptionPair { + Sample sample; + const char* description; // Null means end of a list of pairs. + }; + + // Create a LinearHistogram and store a list of number/text values for use in + // writing the histogram graph. + // |descriptions| can be NULL, which means no special descriptions to set. If + // it's not NULL, the last element in the array must has a NULL in its + // "description" field. + static HistogramBase* FactoryGetWithRangeDescription( + const std::string& name, + Sample minimum, + Sample maximum, + size_t bucket_count, + int32 flags, + const DescriptionPair descriptions[]); + + static void InitializeBucketRanges(Sample minimum, + Sample maximum, + BucketRanges* ranges); + + // Overridden from Histogram: + virtual HistogramType GetHistogramType() const OVERRIDE; + + protected: + LinearHistogram(const std::string& name, + Sample minimum, + Sample maximum, + const BucketRanges* ranges); + + virtual double GetBucketSize(Count current, size_t i) const OVERRIDE; + + // If we have a description for a bucket, then return that. Otherwise + // let parent class provide a (numeric) description. + virtual const std::string GetAsciiBucketRange(size_t i) const OVERRIDE; + + // Skip printing of name for numeric range if we have a name (and if this is + // an empty bucket). + virtual bool PrintEmptyBucket(size_t index) const OVERRIDE; + + private: + friend BASE_EXPORT_PRIVATE HistogramBase* DeserializeHistogramInfo( + PickleIterator* iter); + static HistogramBase* DeserializeInfoImpl(PickleIterator* iter); + + // For some ranges, we store a printable description of a bucket range. + // If there is no desciption, then GetAsciiBucketRange() uses parent class + // to provide a description. + typedef std::map BucketDescriptionMap; + BucketDescriptionMap bucket_description_; + + DISALLOW_COPY_AND_ASSIGN(LinearHistogram); +}; + +//------------------------------------------------------------------------------ + +// BooleanHistogram is a histogram for booleans. +class BASE_EXPORT BooleanHistogram : public LinearHistogram { + public: + static HistogramBase* FactoryGet(const std::string& name, int32 flags); + + virtual HistogramType GetHistogramType() const OVERRIDE; + + private: + BooleanHistogram(const std::string& name, const BucketRanges* ranges); + + friend BASE_EXPORT_PRIVATE HistogramBase* DeserializeHistogramInfo( + PickleIterator* iter); + static HistogramBase* DeserializeInfoImpl(PickleIterator* iter); + + DISALLOW_COPY_AND_ASSIGN(BooleanHistogram); +}; + +//------------------------------------------------------------------------------ + +// CustomHistogram is a histogram for a set of custom integers. +class BASE_EXPORT CustomHistogram : public Histogram { + public: + // |custom_ranges| contains a vector of limits on ranges. Each limit should be + // > 0 and < kSampleType_MAX. (Currently 0 is still accepted for backward + // compatibility). The limits can be unordered or contain duplication, but + // client should not depend on this. + static HistogramBase* FactoryGet(const std::string& name, + const std::vector& custom_ranges, + int32 flags); + + // Overridden from Histogram: + virtual HistogramType GetHistogramType() const OVERRIDE; + + // Helper method for transforming an array of valid enumeration values + // to the std::vector expected by HISTOGRAM_CUSTOM_ENUMERATION. + // This function ensures that a guard bucket exists right after any + // valid sample value (unless the next higher sample is also a valid value), + // so that invalid samples never fall into the same bucket as valid samples. + // TODO(kaiwang): Change name to ArrayToCustomEnumRanges. + static std::vector ArrayToCustomRanges(const Sample* values, + size_t num_values); + protected: + CustomHistogram(const std::string& name, + const BucketRanges* ranges); + + // HistogramBase implementation: + virtual bool SerializeInfoImpl(Pickle* pickle) const OVERRIDE; + + virtual double GetBucketSize(Count current, size_t i) const OVERRIDE; + + private: + friend BASE_EXPORT_PRIVATE HistogramBase* DeserializeHistogramInfo( + PickleIterator* iter); + static HistogramBase* DeserializeInfoImpl(PickleIterator* iter); + + static bool ValidateCustomRanges(const std::vector& custom_ranges); + static BucketRanges* CreateBucketRangesFromCustomRanges( + const std::vector& custom_ranges); + + DISALLOW_COPY_AND_ASSIGN(CustomHistogram); +}; + +} // namespace base + +#endif // BASE_METRICS_HISTOGRAM_H_ diff --git a/base/metrics/histogram_base.cc b/base/metrics/histogram_base.cc new file mode 100644 index 0000000000..5b46d2990c --- /dev/null +++ b/base/metrics/histogram_base.cc @@ -0,0 +1,161 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/metrics/histogram_base.h" + +#include + +#include "base/json/json_string_value_serializer.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/metrics/histogram.h" +#include "base/metrics/histogram_samples.h" +#include "base/metrics/sparse_histogram.h" +#include "base/pickle.h" +#include "base/process/process_handle.h" +#include "base/strings/stringprintf.h" +#include "base/values.h" + +namespace base { + +std::string HistogramTypeToString(HistogramType type) { + switch(type) { + case HISTOGRAM: + return "HISTOGRAM"; + case LINEAR_HISTOGRAM: + return "LINEAR_HISTOGRAM"; + case BOOLEAN_HISTOGRAM: + return "BOOLEAN_HISTOGRAM"; + case CUSTOM_HISTOGRAM: + return "CUSTOM_HISTOGRAM"; + case SPARSE_HISTOGRAM: + return "SPARSE_HISTOGRAM"; + default: + NOTREACHED(); + } + return "UNKNOWN"; +} + +HistogramBase* DeserializeHistogramInfo(PickleIterator* iter) { + int type; + if (!iter->ReadInt(&type)) + return NULL; + + switch (type) { + case HISTOGRAM: + return Histogram::DeserializeInfoImpl(iter); + case LINEAR_HISTOGRAM: + return LinearHistogram::DeserializeInfoImpl(iter); + case BOOLEAN_HISTOGRAM: + return BooleanHistogram::DeserializeInfoImpl(iter); + case CUSTOM_HISTOGRAM: + return CustomHistogram::DeserializeInfoImpl(iter); + case SPARSE_HISTOGRAM: + return SparseHistogram::DeserializeInfoImpl(iter); + default: + return NULL; + } +} + +void DeserializeHistogramAndAddSamples(PickleIterator* iter) { + HistogramBase* histogram = DeserializeHistogramInfo(iter); + if (!histogram) + return; + + if (histogram->flags() & base::HistogramBase::kIPCSerializationSourceFlag) { + DVLOG(1) << "Single process mode, histogram observed and not copied: " + << histogram->histogram_name(); + return; + } + histogram->AddSamplesFromPickle(iter); +} + + +const HistogramBase::Sample HistogramBase::kSampleType_MAX = INT_MAX; + +HistogramBase::HistogramBase(const std::string& name) + : histogram_name_(name), + flags_(kNoFlags) {} + +HistogramBase::~HistogramBase() {} + +void HistogramBase::SetFlags(int32 flags) { + flags_ |= flags; +} + +void HistogramBase::ClearFlags(int32 flags) { + flags_ &= ~flags; +} + +void HistogramBase::AddTime(const TimeDelta& time) { + Add(static_cast(time.InMilliseconds())); +} + +void HistogramBase::AddBoolean(bool value) { + Add(value ? 1 : 0); +} + +bool HistogramBase::SerializeInfo(Pickle* pickle) const { + if (!pickle->WriteInt(GetHistogramType())) + return false; + return SerializeInfoImpl(pickle); +} + +int HistogramBase::FindCorruption(const HistogramSamples& samples) const { + // Not supported by default. + return NO_INCONSISTENCIES; +} + +void HistogramBase::WriteJSON(std::string* output) const { + Count count; + int64 sum; + scoped_ptr buckets(new ListValue()); + GetCountAndBucketData(&count, &sum, buckets.get()); + scoped_ptr parameters(new DictionaryValue()); + GetParameters(parameters.get()); + + JSONStringValueSerializer serializer(output); + DictionaryValue root; + root.SetString("name", histogram_name()); + root.SetInteger("count", count); + root.SetDouble("sum", sum); + root.SetInteger("flags", flags()); + root.Set("params", parameters.release()); + root.Set("buckets", buckets.release()); + root.SetInteger("pid", GetCurrentProcId()); + serializer.Serialize(root); +} + +void HistogramBase::WriteAsciiBucketGraph(double current_size, + double max_size, + std::string* output) const { + const int k_line_length = 72; // Maximal horizontal width of graph. + int x_count = static_cast(k_line_length * (current_size / max_size) + + 0.5); + int x_remainder = k_line_length - x_count; + + while (0 < x_count--) + output->append("-"); + output->append("O"); + while (0 < x_remainder--) + output->append(" "); +} + +const std::string HistogramBase::GetSimpleAsciiBucketRange( + Sample sample) const { + std::string result; + if (kHexRangePrintingFlag & flags()) + StringAppendF(&result, "%#x", sample); + else + StringAppendF(&result, "%d", sample); + return result; +} + +void HistogramBase::WriteAsciiBucketValue(Count current, + double scaled_sum, + std::string* output) const { + StringAppendF(output, " (%d = %3.1f%%)", current, current/scaled_sum); +} + +} // namespace base diff --git a/base/metrics/histogram_base.h b/base/metrics/histogram_base.h new file mode 100644 index 0000000000..f5448e78ca --- /dev/null +++ b/base/metrics/histogram_base.h @@ -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. + +#ifndef BASE_METRICS_HISTOGRAM_BASE_H_ +#define BASE_METRICS_HISTOGRAM_BASE_H_ + +#include + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/time/time.h" + +class Pickle; +class PickleIterator; + +namespace base { + +class DictionaryValue; +class HistogramBase; +class HistogramSamples; +class ListValue; + +//////////////////////////////////////////////////////////////////////////////// +// These enums are used to facilitate deserialization of histograms from other +// processes into the browser. If you create another class that inherits from +// HistogramBase, add new histogram types and names below. + +enum BASE_EXPORT HistogramType { + HISTOGRAM, + LINEAR_HISTOGRAM, + BOOLEAN_HISTOGRAM, + CUSTOM_HISTOGRAM, + SPARSE_HISTOGRAM, +}; + +std::string HistogramTypeToString(HistogramType type); + +// Create or find existing histogram that matches the pickled info. +// Returns NULL if the pickled data has problems. +BASE_EXPORT_PRIVATE HistogramBase* DeserializeHistogramInfo( + PickleIterator* iter); + +// Create or find existing histogram and add the samples from pickle. +// Silently returns when seeing any data problem in the pickle. +BASE_EXPORT void DeserializeHistogramAndAddSamples(PickleIterator* iter); + +//////////////////////////////////////////////////////////////////////////////// + +class BASE_EXPORT HistogramBase { + public: + typedef int Sample; // Used for samples. + typedef int Count; // Used to count samples. + + static const Sample kSampleType_MAX; // INT_MAX + + enum Flags { + kNoFlags = 0, + kUmaTargetedHistogramFlag = 0x1, // Histogram should be UMA uploaded. + + // Indicate that the histogram was pickled to be sent across an IPC Channel. + // If we observe this flag on a histogram being aggregated into after IPC, + // then we are running in a single process mode, and the aggregation should + // not take place (as we would be aggregating back into the source + // histogram!). + kIPCSerializationSourceFlag = 0x10, + + // Only for Histogram and its sub classes: fancy bucket-naming support. + kHexRangePrintingFlag = 0x8000, + }; + + // Histogram data inconsistency types. + enum Inconsistency { + NO_INCONSISTENCIES = 0x0, + RANGE_CHECKSUM_ERROR = 0x1, + BUCKET_ORDER_ERROR = 0x2, + COUNT_HIGH_ERROR = 0x4, + COUNT_LOW_ERROR = 0x8, + + NEVER_EXCEEDED_VALUE = 0x10 + }; + + explicit HistogramBase(const std::string& name); + virtual ~HistogramBase(); + + std::string histogram_name() const { return histogram_name_; } + + // Operations with Flags enum. + int32 flags() const { return flags_; } + void SetFlags(int32 flags); + void ClearFlags(int32 flags); + + virtual HistogramType GetHistogramType() const = 0; + + // Whether the histogram has construction arguments as parameters specified. + // For histograms that don't have the concept of minimum, maximum or + // bucket_count, this function always returns false. + virtual bool HasConstructionArguments(Sample expected_minimum, + Sample expected_maximum, + size_t expected_bucket_count) const = 0; + + virtual void Add(Sample value) = 0; + + // 2 convenient functions that call Add(Sample). + void AddTime(const TimeDelta& time); + void AddBoolean(bool value); + + virtual void AddSamples(const HistogramSamples& samples) = 0; + virtual bool AddSamplesFromPickle(PickleIterator* iter) = 0; + + // Serialize the histogram info into |pickle|. + // Note: This only serializes the construction arguments of the histogram, but + // does not serialize the samples. + bool SerializeInfo(Pickle* pickle) const; + + // Try to find out data corruption from histogram and the samples. + // The returned value is a combination of Inconsistency enum. + virtual int FindCorruption(const HistogramSamples& samples) const; + + // Snapshot the current complete set of sample data. + // Override with atomic/locked snapshot if needed. + virtual scoped_ptr SnapshotSamples() const = 0; + + // The following methods provide graphical histogram displays. + virtual void WriteHTMLGraph(std::string* output) const = 0; + virtual void WriteAscii(std::string* output) const = 0; + + // Produce a JSON representation of the histogram. This is implemented with + // the help of GetParameters and GetCountAndBucketData; overwrite them to + // customize the output. + void WriteJSON(std::string* output) const; + +protected: + // Subclasses should implement this function to make SerializeInfo work. + virtual bool SerializeInfoImpl(Pickle* pickle) const = 0; + + // Writes information about the construction parameters in |params|. + virtual void GetParameters(DictionaryValue* params) const = 0; + + // Writes information about the current (non-empty) buckets and their sample + // counts to |buckets|, the total sample count to |count| and the total sum + // to |sum|. + virtual void GetCountAndBucketData(Count* count, + int64* sum, + ListValue* buckets) const = 0; + + //// Produce actual graph (set of blank vs non blank char's) for a bucket. + void WriteAsciiBucketGraph(double current_size, + double max_size, + std::string* output) const; + + // Return a string description of what goes in a given bucket. + const std::string GetSimpleAsciiBucketRange(Sample sample) const; + + // Write textual description of the bucket contents (relative to histogram). + // Output is the count in the buckets, as well as the percentage. + void WriteAsciiBucketValue(Count current, + double scaled_sum, + std::string* output) const; + + private: + const std::string histogram_name_; + int32 flags_; + + DISALLOW_COPY_AND_ASSIGN(HistogramBase); +}; + +} // namespace base + +#endif // BASE_METRICS_HISTOGRAM_BASE_H_ diff --git a/base/metrics/histogram_base_unittest.cc b/base/metrics/histogram_base_unittest.cc new file mode 100644 index 0000000000..0e19d56f4c --- /dev/null +++ b/base/metrics/histogram_base_unittest.cc @@ -0,0 +1,191 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/metrics/histogram.h" +#include "base/metrics/histogram_base.h" +#include "base/metrics/sparse_histogram.h" +#include "base/metrics/statistics_recorder.h" +#include "base/pickle.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +class HistogramBaseTest : public testing::Test { + protected: + HistogramBaseTest() { + // Each test will have a clean state (no Histogram / BucketRanges + // registered). + statistics_recorder_ = NULL; + ResetStatisticsRecorder(); + } + + virtual ~HistogramBaseTest() { + delete statistics_recorder_; + } + + void ResetStatisticsRecorder() { + delete statistics_recorder_; + statistics_recorder_ = new StatisticsRecorder(); + } + + private: + StatisticsRecorder* statistics_recorder_; +}; + +TEST_F(HistogramBaseTest, DeserializeHistogram) { + HistogramBase* histogram = Histogram::FactoryGet( + "TestHistogram", 1, 1000, 10, + (HistogramBase::kUmaTargetedHistogramFlag | + HistogramBase::kIPCSerializationSourceFlag)); + + Pickle pickle; + ASSERT_TRUE(histogram->SerializeInfo(&pickle)); + + PickleIterator iter(pickle); + HistogramBase* deserialized = DeserializeHistogramInfo(&iter); + EXPECT_EQ(histogram, deserialized); + + ResetStatisticsRecorder(); + + PickleIterator iter2(pickle); + deserialized = DeserializeHistogramInfo(&iter2); + EXPECT_TRUE(deserialized); + EXPECT_NE(histogram, deserialized); + EXPECT_EQ("TestHistogram", deserialized->histogram_name()); + EXPECT_TRUE(deserialized->HasConstructionArguments(1, 1000, 10)); + + // kIPCSerializationSourceFlag will be cleared. + EXPECT_EQ(HistogramBase::kUmaTargetedHistogramFlag, deserialized->flags()); +} + +TEST_F(HistogramBaseTest, DeserializeHistogramAndAddSamples) { + HistogramBase* histogram = Histogram::FactoryGet( + "TestHistogram", 1, 1000, 10, HistogramBase::kIPCSerializationSourceFlag); + histogram->Add(1); + histogram->Add(10); + histogram->Add(100); + histogram->Add(1000); + + Pickle pickle; + ASSERT_TRUE(histogram->SerializeInfo(&pickle)); + histogram->SnapshotSamples()->Serialize(&pickle); + + PickleIterator iter(pickle); + DeserializeHistogramAndAddSamples(&iter); + + // The histogram has kIPCSerializationSourceFlag. So samples will be ignored. + scoped_ptr snapshot(histogram->SnapshotSamples()); + EXPECT_EQ(1, snapshot->GetCount(1)); + EXPECT_EQ(1, snapshot->GetCount(10)); + EXPECT_EQ(1, snapshot->GetCount(100)); + EXPECT_EQ(1, snapshot->GetCount(1000)); + + // Clear kIPCSerializationSourceFlag to emulate multi-process usage. + histogram->ClearFlags(HistogramBase::kIPCSerializationSourceFlag); + PickleIterator iter2(pickle); + DeserializeHistogramAndAddSamples(&iter2); + + scoped_ptr snapshot2(histogram->SnapshotSamples()); + EXPECT_EQ(2, snapshot2->GetCount(1)); + EXPECT_EQ(2, snapshot2->GetCount(10)); + EXPECT_EQ(2, snapshot2->GetCount(100)); + EXPECT_EQ(2, snapshot2->GetCount(1000)); +} + +TEST_F(HistogramBaseTest, DeserializeLinearHistogram) { + HistogramBase* histogram = LinearHistogram::FactoryGet( + "TestHistogram", 1, 1000, 10, + HistogramBase::kIPCSerializationSourceFlag); + + Pickle pickle; + ASSERT_TRUE(histogram->SerializeInfo(&pickle)); + + PickleIterator iter(pickle); + HistogramBase* deserialized = DeserializeHistogramInfo(&iter); + EXPECT_EQ(histogram, deserialized); + + ResetStatisticsRecorder(); + + PickleIterator iter2(pickle); + deserialized = DeserializeHistogramInfo(&iter2); + EXPECT_TRUE(deserialized); + EXPECT_NE(histogram, deserialized); + EXPECT_EQ("TestHistogram", deserialized->histogram_name()); + EXPECT_TRUE(deserialized->HasConstructionArguments(1, 1000, 10)); + EXPECT_EQ(0, deserialized->flags()); +} + +TEST_F(HistogramBaseTest, DeserializeBooleanHistogram) { + HistogramBase* histogram = BooleanHistogram::FactoryGet( + "TestHistogram", HistogramBase::kIPCSerializationSourceFlag); + + Pickle pickle; + ASSERT_TRUE(histogram->SerializeInfo(&pickle)); + + PickleIterator iter(pickle); + HistogramBase* deserialized = DeserializeHistogramInfo(&iter); + EXPECT_EQ(histogram, deserialized); + + ResetStatisticsRecorder(); + + PickleIterator iter2(pickle); + deserialized = DeserializeHistogramInfo(&iter2); + EXPECT_TRUE(deserialized); + EXPECT_NE(histogram, deserialized); + EXPECT_EQ("TestHistogram", deserialized->histogram_name()); + EXPECT_TRUE(deserialized->HasConstructionArguments(1, 2, 3)); + EXPECT_EQ(0, deserialized->flags()); +} + +TEST_F(HistogramBaseTest, DeserializeCustomHistogram) { + std::vector ranges; + ranges.push_back(13); + ranges.push_back(5); + ranges.push_back(9); + + HistogramBase* histogram = CustomHistogram::FactoryGet( + "TestHistogram", ranges, HistogramBase::kIPCSerializationSourceFlag); + + Pickle pickle; + ASSERT_TRUE(histogram->SerializeInfo(&pickle)); + + PickleIterator iter(pickle); + HistogramBase* deserialized = DeserializeHistogramInfo(&iter); + EXPECT_EQ(histogram, deserialized); + + ResetStatisticsRecorder(); + + PickleIterator iter2(pickle); + deserialized = DeserializeHistogramInfo(&iter2); + EXPECT_TRUE(deserialized); + EXPECT_NE(histogram, deserialized); + EXPECT_EQ("TestHistogram", deserialized->histogram_name()); + EXPECT_TRUE(deserialized->HasConstructionArguments(5, 13, 4)); + EXPECT_EQ(0, deserialized->flags()); +} + +TEST_F(HistogramBaseTest, DeserializeSparseHistogram) { + HistogramBase* histogram = SparseHistogram::FactoryGet( + "TestHistogram", HistogramBase::kIPCSerializationSourceFlag); + + Pickle pickle; + ASSERT_TRUE(histogram->SerializeInfo(&pickle)); + + PickleIterator iter(pickle); + HistogramBase* deserialized = DeserializeHistogramInfo(&iter); + EXPECT_EQ(histogram, deserialized); + + ResetStatisticsRecorder(); + + PickleIterator iter2(pickle); + deserialized = DeserializeHistogramInfo(&iter2); + EXPECT_TRUE(deserialized); + EXPECT_NE(histogram, deserialized); + EXPECT_EQ("TestHistogram", deserialized->histogram_name()); + EXPECT_EQ(0, deserialized->flags()); +} + +} // namespace base diff --git a/base/metrics/histogram_flattener.h b/base/metrics/histogram_flattener.h new file mode 100644 index 0000000000..ca05a4f421 --- /dev/null +++ b/base/metrics/histogram_flattener.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_METRICS_HISTOGRAM_FLATTENER_H_ +#define BASE_METRICS_HISTOGRAM_FLATTENER_H_ + +#include +#include + +#include "base/basictypes.h" +#include "base/metrics/histogram.h" + +namespace base { + +class HistogramSamples; + +// HistogramFlattener is an interface used by HistogramSnapshotManager, which +// handles the logistics of gathering up available histograms for recording. +// The implementors handle the exact lower level recording mechanism, or +// error report mechanism. +class BASE_EXPORT HistogramFlattener { + public: + virtual void RecordDelta(const HistogramBase& histogram, + const HistogramSamples& snapshot) = 0; + + // Will be called each time a type of Inconsistency is seen on a histogram, + // during inspections done internally in HistogramSnapshotManager class. + virtual void InconsistencyDetected(HistogramBase::Inconsistency problem) = 0; + + // Will be called when a type of Inconsistency is seen for the first time on + // a histogram. + virtual void UniqueInconsistencyDetected( + HistogramBase::Inconsistency problem) = 0; + + // Will be called when the total logged sample count of a histogram + // differs from the sum of logged sample count in all the buckets. The + // argument |amount| is the non-zero discrepancy. + virtual void InconsistencyDetectedInLoggedCount(int amount) = 0; + + protected: + HistogramFlattener() {} + virtual ~HistogramFlattener() {} + + private: + DISALLOW_COPY_AND_ASSIGN(HistogramFlattener); +}; + +} // namespace base + +#endif // BASE_METRICS_HISTOGRAM_FLATTENER_H_ diff --git a/base/metrics/histogram_samples.cc b/base/metrics/histogram_samples.cc new file mode 100644 index 0000000000..0e0eeb4dd3 --- /dev/null +++ b/base/metrics/histogram_samples.cc @@ -0,0 +1,126 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/metrics/histogram_samples.h" + +#include "base/compiler_specific.h" +#include "base/pickle.h" + +namespace base { + +namespace { + +class SampleCountPickleIterator : public SampleCountIterator { + public: + explicit SampleCountPickleIterator(PickleIterator* iter); + + virtual bool Done() const OVERRIDE; + virtual void Next() OVERRIDE; + virtual void Get(HistogramBase::Sample* min, + HistogramBase::Sample* max, + HistogramBase::Count* count) const OVERRIDE; + private: + PickleIterator* const iter_; + + HistogramBase::Sample min_; + HistogramBase::Sample max_; + HistogramBase::Count count_; + bool is_done_; +}; + +SampleCountPickleIterator::SampleCountPickleIterator(PickleIterator* iter) + : iter_(iter), + is_done_(false) { + Next(); +} + +bool SampleCountPickleIterator::Done() const { + return is_done_; +} + +void SampleCountPickleIterator::Next() { + DCHECK(!Done()); + if (!iter_->ReadInt(&min_) || + !iter_->ReadInt(&max_) || + !iter_->ReadInt(&count_)) + is_done_ = true; +} + +void SampleCountPickleIterator::Get(HistogramBase::Sample* min, + HistogramBase::Sample* max, + HistogramBase::Count* count) const { + DCHECK(!Done()); + *min = min_; + *max = max_; + *count = count_; +} + +} // namespace + +HistogramSamples::HistogramSamples() : sum_(0), redundant_count_(0) {} + +HistogramSamples::~HistogramSamples() {} + +void HistogramSamples::Add(const HistogramSamples& other) { + sum_ += other.sum(); + redundant_count_ += other.redundant_count(); + bool success = AddSubtractImpl(other.Iterator().get(), ADD); + DCHECK(success); +} + +bool HistogramSamples::AddFromPickle(PickleIterator* iter) { + int64 sum; + HistogramBase::Count redundant_count; + + if (!iter->ReadInt64(&sum) || !iter->ReadInt(&redundant_count)) + return false; + sum_ += sum; + redundant_count_ += redundant_count; + + SampleCountPickleIterator pickle_iter(iter); + return AddSubtractImpl(&pickle_iter, ADD); +} + +void HistogramSamples::Subtract(const HistogramSamples& other) { + sum_ -= other.sum(); + redundant_count_ -= other.redundant_count(); + bool success = AddSubtractImpl(other.Iterator().get(), SUBTRACT); + DCHECK(success); +} + +bool HistogramSamples::Serialize(Pickle* pickle) const { + if (!pickle->WriteInt64(sum_) || !pickle->WriteInt(redundant_count_)) + return false; + + HistogramBase::Sample min; + HistogramBase::Sample max; + HistogramBase::Count count; + for (scoped_ptr it = Iterator(); + !it->Done(); + it->Next()) { + it->Get(&min, &max, &count); + if (!pickle->WriteInt(min) || + !pickle->WriteInt(max) || + !pickle->WriteInt(count)) + return false; + } + return true; +} + +void HistogramSamples::IncreaseSum(int64 diff) { + sum_ += diff; +} + +void HistogramSamples::IncreaseRedundantCount(HistogramBase::Count diff) { + redundant_count_ += diff; +} + +SampleCountIterator::~SampleCountIterator() {} + +bool SampleCountIterator::GetBucketIndex(size_t* index) const { + DCHECK(!Done()); + return false; +} + +} // namespace base diff --git a/base/metrics/histogram_samples.h b/base/metrics/histogram_samples.h new file mode 100644 index 0000000000..c55b58442c --- /dev/null +++ b/base/metrics/histogram_samples.h @@ -0,0 +1,87 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_METRICS_HISTOGRAM_SAMPLES_H_ +#define BASE_METRICS_HISTOGRAM_SAMPLES_H_ + +#include "base/basictypes.h" +#include "base/metrics/histogram_base.h" +#include "base/memory/scoped_ptr.h" + +class Pickle; +class PickleIterator; + +namespace base { + +class SampleCountIterator; + +// HistogramSamples is a container storing all samples of a histogram. +class BASE_EXPORT HistogramSamples { + public: + HistogramSamples(); + virtual ~HistogramSamples(); + + virtual void Accumulate(HistogramBase::Sample value, + HistogramBase::Count count) = 0; + virtual HistogramBase::Count GetCount(HistogramBase::Sample value) const = 0; + virtual HistogramBase::Count TotalCount() const = 0; + + virtual void Add(const HistogramSamples& other); + + // Add from serialized samples. + virtual bool AddFromPickle(PickleIterator* iter); + + virtual void Subtract(const HistogramSamples& other); + + virtual scoped_ptr Iterator() const = 0; + virtual bool Serialize(Pickle* pickle) const; + + // Accessor fuctions. + int64 sum() const { return sum_; } + HistogramBase::Count redundant_count() const { return redundant_count_; } + + protected: + // Based on |op| type, add or subtract sample counts data from the iterator. + enum Operator { ADD, SUBTRACT }; + virtual bool AddSubtractImpl(SampleCountIterator* iter, Operator op) = 0; + + void IncreaseSum(int64 diff); + void IncreaseRedundantCount(HistogramBase::Count diff); + + private: + int64 sum_; + + // |redundant_count_| helps identify memory corruption. It redundantly stores + // the total number of samples accumulated in the histogram. We can compare + // this count to the sum of the counts (TotalCount() function), and detect + // problems. Note, depending on the implementation of different histogram + // types, there might be races during histogram accumulation and snapshotting + // that we choose to accept. In this case, the tallies might mismatch even + // when no memory corruption has happened. + HistogramBase::Count redundant_count_; +}; + +class BASE_EXPORT SampleCountIterator { + public: + virtual ~SampleCountIterator(); + + virtual bool Done() const = 0; + virtual void Next() = 0; + + // Get the sample and count at current position. + // |min| |max| and |count| can be NULL if the value is not of interest. + // Requires: !Done(); + virtual void Get(HistogramBase::Sample* min, + HistogramBase::Sample* max, + HistogramBase::Count* count) const = 0; + + // Get the index of current histogram bucket. + // For histograms that don't use predefined buckets, it returns false. + // Requires: !Done(); + virtual bool GetBucketIndex(size_t* index) const; +}; + +} // namespace base + +#endif // BASE_METRICS_HISTOGRAM_SAMPLES_H_ diff --git a/base/metrics/histogram_snapshot_manager.cc b/base/metrics/histogram_snapshot_manager.cc new file mode 100644 index 0000000000..2301819ad3 --- /dev/null +++ b/base/metrics/histogram_snapshot_manager.cc @@ -0,0 +1,117 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/metrics/histogram_snapshot_manager.h" + +#include "base/memory/scoped_ptr.h" +#include "base/metrics/histogram_flattener.h" +#include "base/metrics/histogram_samples.h" +#include "base/metrics/statistics_recorder.h" +#include "base/stl_util.h" + +using std::map; +using std::string; + +namespace base { + +HistogramSnapshotManager::HistogramSnapshotManager( + HistogramFlattener* histogram_flattener) + : histogram_flattener_(histogram_flattener) { + DCHECK(histogram_flattener_); +} + +HistogramSnapshotManager::~HistogramSnapshotManager() { + STLDeleteValues(&logged_samples_); +} + +void HistogramSnapshotManager::PrepareDeltas(HistogramBase::Flags flag_to_set, + bool record_only_uma) { + StatisticsRecorder::Histograms histograms; + StatisticsRecorder::GetHistograms(&histograms); + for (StatisticsRecorder::Histograms::const_iterator it = histograms.begin(); + histograms.end() != it; + ++it) { + (*it)->SetFlags(flag_to_set); + if (record_only_uma && + 0 == ((*it)->flags() & Histogram::kUmaTargetedHistogramFlag)) + continue; + PrepareDelta(**it); + } +} + +void HistogramSnapshotManager::PrepareDelta(const HistogramBase& histogram) { + DCHECK(histogram_flattener_); + + // Get up-to-date snapshot of sample stats. + scoped_ptr snapshot(histogram.SnapshotSamples()); + const std::string& histogram_name = histogram.histogram_name(); + + int corruption = histogram.FindCorruption(*snapshot); + + // Crash if we detect that our histograms have been overwritten. This may be + // a fair distance from the memory smasher, but we hope to correlate these + // crashes with other events, such as plugins, or usage patterns, etc. + if (HistogramBase::BUCKET_ORDER_ERROR & corruption) { + // The checksum should have caught this, so crash separately if it didn't. + CHECK_NE(0, HistogramBase::RANGE_CHECKSUM_ERROR & corruption); + CHECK(false); // Crash for the bucket order corruption. + } + // Checksum corruption might not have caused order corruption. + CHECK_EQ(0, HistogramBase::RANGE_CHECKSUM_ERROR & corruption); + + // Note, at this point corruption can only be COUNT_HIGH_ERROR or + // COUNT_LOW_ERROR and they never arise together, so we don't need to extract + // bits from corruption. + if (corruption) { + DLOG(ERROR) << "Histogram: " << histogram_name + << " has data corruption: " << corruption; + histogram_flattener_->InconsistencyDetected( + static_cast(corruption)); + // Don't record corrupt data to metrics services. + int old_corruption = inconsistencies_[histogram_name]; + if (old_corruption == (corruption | old_corruption)) + return; // We've already seen this corruption for this histogram. + inconsistencies_[histogram_name] |= corruption; + histogram_flattener_->UniqueInconsistencyDetected( + static_cast(corruption)); + return; + } + + HistogramSamples* to_log; + map::iterator it = + logged_samples_.find(histogram_name); + if (it == logged_samples_.end()) { + to_log = snapshot.release(); + + // This histogram has not been logged before, add a new entry. + logged_samples_[histogram_name] = to_log; + } else { + HistogramSamples* already_logged = it->second; + InspectLoggedSamplesInconsistency(*snapshot, already_logged); + snapshot->Subtract(*already_logged); + already_logged->Add(*snapshot); + to_log = snapshot.get(); + } + + if (to_log->redundant_count() > 0) + histogram_flattener_->RecordDelta(histogram, *to_log); +} + +void HistogramSnapshotManager::InspectLoggedSamplesInconsistency( + const HistogramSamples& new_snapshot, + HistogramSamples* logged_samples) { + HistogramBase::Count discrepancy = + logged_samples->TotalCount() - logged_samples->redundant_count(); + if (!discrepancy) + return; + + histogram_flattener_->InconsistencyDetectedInLoggedCount(discrepancy); + if (discrepancy > Histogram::kCommonRaceBasedCountMismatch) { + // Fix logged_samples. + logged_samples->Subtract(*logged_samples); + logged_samples->Add(new_snapshot); + } +} + +} // namespace base diff --git a/base/metrics/histogram_snapshot_manager.h b/base/metrics/histogram_snapshot_manager.h new file mode 100644 index 0000000000..3c7050859e --- /dev/null +++ b/base/metrics/histogram_snapshot_manager.h @@ -0,0 +1,61 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_METRICS_HISTOGRAM_SNAPSHOT_MANAGER_H_ +#define BASE_METRICS_HISTOGRAM_SNAPSHOT_MANAGER_H_ + +#include +#include + +#include "base/basictypes.h" +#include "base/metrics/histogram_base.h" + +namespace base { + +class HistogramSamples; +class HistogramFlattener; + +// HistogramSnapshotManager handles the logistics of gathering up available +// histograms for recording either to disk or for transmission (such as from +// renderer to browser, or from browser to UMA upload). Since histograms can sit +// in memory for an extended period of time, and are vulnerable to memory +// corruption, this class also validates as much rendundancy as it can before +// calling for the marginal change (a.k.a., delta) in a histogram to be +// recorded. +class BASE_EXPORT HistogramSnapshotManager { + public: + explicit HistogramSnapshotManager(HistogramFlattener* histogram_flattener); + virtual ~HistogramSnapshotManager(); + + // Snapshot all histograms, and ask |histogram_flattener_| to record the + // delta. The arguments allow selecting only a subset of histograms for + // recording, or to set a flag in each recorded histogram. + void PrepareDeltas(HistogramBase::Flags flags_to_set, bool record_only_uma); + + private: + // Snapshot this histogram, and record the delta. + void PrepareDelta(const HistogramBase& histogram); + + // Try to detect and fix count inconsistency of logged samples. + void InspectLoggedSamplesInconsistency( + const HistogramSamples& new_snapshot, + HistogramSamples* logged_samples); + + // For histograms, track what we've already recorded (as a sample for + // each histogram) so that we can record only the delta with the next log. + std::map logged_samples_; + + // List of histograms found to be corrupt, and their problems. + std::map inconsistencies_; + + // |histogram_flattener_| handles the logistics of recording the histogram + // deltas. + HistogramFlattener* histogram_flattener_; // Weak. + + DISALLOW_COPY_AND_ASSIGN(HistogramSnapshotManager); +}; + +} // namespace base + +#endif // BASE_METRICS_HISTOGRAM_SNAPSHOT_MANAGER_H_ diff --git a/base/metrics/histogram_unittest.cc b/base/metrics/histogram_unittest.cc new file mode 100644 index 0000000000..e7a01aa5af --- /dev/null +++ b/base/metrics/histogram_unittest.cc @@ -0,0 +1,493 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Test of Histogram class + +#include +#include +#include + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/metrics/bucket_ranges.h" +#include "base/metrics/histogram.h" +#include "base/metrics/sample_vector.h" +#include "base/metrics/statistics_recorder.h" +#include "base/pickle.h" +#include "base/time/time.h" +#include "testing/gtest/include/gtest/gtest.h" + +using std::vector; + +namespace base { + +class HistogramTest : public testing::Test { + protected: + virtual void SetUp() { + // Each test will have a clean state (no Histogram / BucketRanges + // registered). + InitializeStatisticsRecorder(); + } + + virtual void TearDown() { + UninitializeStatisticsRecorder(); + } + + void InitializeStatisticsRecorder() { + statistics_recorder_ = new StatisticsRecorder(); + } + + void UninitializeStatisticsRecorder() { + delete statistics_recorder_; + statistics_recorder_ = NULL; + } + + StatisticsRecorder* statistics_recorder_; +}; + +// Check for basic syntax and use. +TEST_F(HistogramTest, BasicTest) { + // Try basic construction + HistogramBase* histogram = Histogram::FactoryGet( + "TestHistogram", 1, 1000, 10, HistogramBase::kNoFlags); + EXPECT_TRUE(histogram); + + HistogramBase* linear_histogram = LinearHistogram::FactoryGet( + "TestLinearHistogram", 1, 1000, 10, HistogramBase::kNoFlags); + EXPECT_TRUE(linear_histogram); + + vector custom_ranges; + custom_ranges.push_back(1); + custom_ranges.push_back(5); + HistogramBase* custom_histogram = CustomHistogram::FactoryGet( + "TestCustomHistogram", custom_ranges, HistogramBase::kNoFlags); + EXPECT_TRUE(custom_histogram); + + // Use standard macros (but with fixed samples) + HISTOGRAM_TIMES("Test2Histogram", TimeDelta::FromDays(1)); + HISTOGRAM_COUNTS("Test3Histogram", 30); + + DHISTOGRAM_TIMES("Test4Histogram", TimeDelta::FromDays(1)); + DHISTOGRAM_COUNTS("Test5Histogram", 30); + + HISTOGRAM_ENUMERATION("Test6Histogram", 129, 130); +} + +// Check that the macro correctly matches histograms by name and records their +// data together. +TEST_F(HistogramTest, NameMatchTest) { + HISTOGRAM_PERCENTAGE("DuplicatedHistogram", 10); + HISTOGRAM_PERCENTAGE("DuplicatedHistogram", 10); + HistogramBase* histogram = LinearHistogram::FactoryGet( + "DuplicatedHistogram", 1, 101, 102, HistogramBase::kNoFlags); + + scoped_ptr samples = histogram->SnapshotSamples(); + EXPECT_EQ(2, samples->TotalCount()); + EXPECT_EQ(2, samples->GetCount(10)); +} + +TEST_F(HistogramTest, ExponentialRangesTest) { + // Check that we got a nice exponential when there was enough rooom. + BucketRanges ranges(9); + Histogram::InitializeBucketRanges(1, 64, &ranges); + EXPECT_EQ(0, ranges.range(0)); + int power_of_2 = 1; + for (int i = 1; i < 8; i++) { + EXPECT_EQ(power_of_2, ranges.range(i)); + power_of_2 *= 2; + } + EXPECT_EQ(HistogramBase::kSampleType_MAX, ranges.range(8)); + + // Check the corresponding Histogram will use the correct ranges. + Histogram* histogram = static_cast( + Histogram::FactoryGet("Histogram", 1, 64, 8, HistogramBase::kNoFlags)); + EXPECT_TRUE(ranges.Equals(histogram->bucket_ranges())); + + // When bucket count is limited, exponential ranges will partially look like + // linear. + BucketRanges ranges2(16); + Histogram::InitializeBucketRanges(1, 32, &ranges2); + + EXPECT_EQ(0, ranges2.range(0)); + EXPECT_EQ(1, ranges2.range(1)); + EXPECT_EQ(2, ranges2.range(2)); + EXPECT_EQ(3, ranges2.range(3)); + EXPECT_EQ(4, ranges2.range(4)); + EXPECT_EQ(5, ranges2.range(5)); + EXPECT_EQ(6, ranges2.range(6)); + EXPECT_EQ(7, ranges2.range(7)); + EXPECT_EQ(9, ranges2.range(8)); + EXPECT_EQ(11, ranges2.range(9)); + EXPECT_EQ(14, ranges2.range(10)); + EXPECT_EQ(17, ranges2.range(11)); + EXPECT_EQ(21, ranges2.range(12)); + EXPECT_EQ(26, ranges2.range(13)); + EXPECT_EQ(32, ranges2.range(14)); + EXPECT_EQ(HistogramBase::kSampleType_MAX, ranges2.range(15)); + + // Check the corresponding Histogram will use the correct ranges. + Histogram* histogram2 = static_cast( + Histogram::FactoryGet("Histogram2", 1, 32, 15, HistogramBase::kNoFlags)); + EXPECT_TRUE(ranges2.Equals(histogram2->bucket_ranges())); +} + +TEST_F(HistogramTest, LinearRangesTest) { + BucketRanges ranges(9); + LinearHistogram::InitializeBucketRanges(1, 7, &ranges); + // Gets a nice linear set of bucket ranges. + for (int i = 0; i < 8; i++) + EXPECT_EQ(i, ranges.range(i)); + EXPECT_EQ(HistogramBase::kSampleType_MAX, ranges.range(8)); + + // The correspoding LinearHistogram should use the correct ranges. + Histogram* histogram = static_cast( + LinearHistogram::FactoryGet("Linear", 1, 7, 8, HistogramBase::kNoFlags)); + EXPECT_TRUE(ranges.Equals(histogram->bucket_ranges())); + + // Linear ranges are not divisible. + BucketRanges ranges2(6); + LinearHistogram::InitializeBucketRanges(1, 6, &ranges2); + EXPECT_EQ(0, ranges2.range(0)); + EXPECT_EQ(1, ranges2.range(1)); + EXPECT_EQ(3, ranges2.range(2)); + EXPECT_EQ(4, ranges2.range(3)); + EXPECT_EQ(6, ranges2.range(4)); + EXPECT_EQ(HistogramBase::kSampleType_MAX, ranges2.range(5)); + // The correspoding LinearHistogram should use the correct ranges. + Histogram* histogram2 = static_cast( + LinearHistogram::FactoryGet("Linear2", 1, 6, 5, HistogramBase::kNoFlags)); + EXPECT_TRUE(ranges2.Equals(histogram2->bucket_ranges())); +} + +TEST_F(HistogramTest, ArrayToCustomRangesTest) { + const HistogramBase::Sample ranges[3] = {5, 10, 20}; + vector ranges_vec = + CustomHistogram::ArrayToCustomRanges(ranges, 3); + ASSERT_EQ(6u, ranges_vec.size()); + EXPECT_EQ(5, ranges_vec[0]); + EXPECT_EQ(6, ranges_vec[1]); + EXPECT_EQ(10, ranges_vec[2]); + EXPECT_EQ(11, ranges_vec[3]); + EXPECT_EQ(20, ranges_vec[4]); + EXPECT_EQ(21, ranges_vec[5]); +} + +TEST_F(HistogramTest, CustomHistogramTest) { + // A well prepared custom ranges. + vector custom_ranges; + custom_ranges.push_back(1); + custom_ranges.push_back(2); + + Histogram* histogram = static_cast( + CustomHistogram::FactoryGet("TestCustomHistogram1", custom_ranges, + HistogramBase::kNoFlags)); + const BucketRanges* ranges = histogram->bucket_ranges(); + ASSERT_EQ(4u, ranges->size()); + EXPECT_EQ(0, ranges->range(0)); // Auto added. + EXPECT_EQ(1, ranges->range(1)); + EXPECT_EQ(2, ranges->range(2)); + EXPECT_EQ(HistogramBase::kSampleType_MAX, ranges->range(3)); // Auto added. + + // A unordered custom ranges. + custom_ranges.clear(); + custom_ranges.push_back(2); + custom_ranges.push_back(1); + histogram = static_cast( + CustomHistogram::FactoryGet("TestCustomHistogram2", custom_ranges, + HistogramBase::kNoFlags)); + ranges = histogram->bucket_ranges(); + ASSERT_EQ(4u, ranges->size()); + EXPECT_EQ(0, ranges->range(0)); + EXPECT_EQ(1, ranges->range(1)); + EXPECT_EQ(2, ranges->range(2)); + EXPECT_EQ(HistogramBase::kSampleType_MAX, ranges->range(3)); + + // A custom ranges with duplicated values. + custom_ranges.clear(); + custom_ranges.push_back(4); + custom_ranges.push_back(1); + custom_ranges.push_back(4); + histogram = static_cast( + CustomHistogram::FactoryGet("TestCustomHistogram3", custom_ranges, + HistogramBase::kNoFlags)); + ranges = histogram->bucket_ranges(); + ASSERT_EQ(4u, ranges->size()); + EXPECT_EQ(0, ranges->range(0)); + EXPECT_EQ(1, ranges->range(1)); + EXPECT_EQ(4, ranges->range(2)); + EXPECT_EQ(HistogramBase::kSampleType_MAX, ranges->range(3)); +} + +TEST_F(HistogramTest, CustomHistogramWithOnly2Buckets) { + // This test exploits the fact that the CustomHistogram can have 2 buckets, + // while the base class Histogram is *supposed* to have at least 3 buckets. + // We should probably change the restriction on the base class (or not inherit + // the base class!). + + vector custom_ranges; + custom_ranges.push_back(4); + + Histogram* histogram = static_cast( + CustomHistogram::FactoryGet("2BucketsCustomHistogram", custom_ranges, + HistogramBase::kNoFlags)); + const BucketRanges* ranges = histogram->bucket_ranges(); + ASSERT_EQ(3u, ranges->size()); + EXPECT_EQ(0, ranges->range(0)); + EXPECT_EQ(4, ranges->range(1)); + EXPECT_EQ(HistogramBase::kSampleType_MAX, ranges->range(2)); +} + +// Make sure histogram handles out-of-bounds data gracefully. +TEST_F(HistogramTest, BoundsTest) { + const size_t kBucketCount = 50; + Histogram* histogram = static_cast( + Histogram::FactoryGet("Bounded", 10, 100, kBucketCount, + HistogramBase::kNoFlags)); + + // Put two samples "out of bounds" above and below. + histogram->Add(5); + histogram->Add(-50); + + histogram->Add(100); + histogram->Add(10000); + + // Verify they landed in the underflow, and overflow buckets. + scoped_ptr samples = histogram->SnapshotSampleVector(); + EXPECT_EQ(2, samples->GetCountAtIndex(0)); + EXPECT_EQ(0, samples->GetCountAtIndex(1)); + size_t array_size = histogram->bucket_count(); + EXPECT_EQ(kBucketCount, array_size); + EXPECT_EQ(0, samples->GetCountAtIndex(array_size - 2)); + EXPECT_EQ(2, samples->GetCountAtIndex(array_size - 1)); + + vector custom_ranges; + custom_ranges.push_back(10); + custom_ranges.push_back(50); + custom_ranges.push_back(100); + Histogram* test_custom_histogram = static_cast( + CustomHistogram::FactoryGet("TestCustomRangeBoundedHistogram", + custom_ranges, HistogramBase::kNoFlags)); + + // Put two samples "out of bounds" above and below. + test_custom_histogram->Add(5); + test_custom_histogram->Add(-50); + test_custom_histogram->Add(100); + test_custom_histogram->Add(1000); + test_custom_histogram->Add(INT_MAX); + + // Verify they landed in the underflow, and overflow buckets. + scoped_ptr custom_samples = + test_custom_histogram->SnapshotSampleVector(); + EXPECT_EQ(2, custom_samples->GetCountAtIndex(0)); + EXPECT_EQ(0, custom_samples->GetCountAtIndex(1)); + size_t bucket_count = test_custom_histogram->bucket_count(); + EXPECT_EQ(0, custom_samples->GetCountAtIndex(bucket_count - 2)); + EXPECT_EQ(3, custom_samples->GetCountAtIndex(bucket_count - 1)); +} + +// Check to be sure samples land as expected is "correct" buckets. +TEST_F(HistogramTest, BucketPlacementTest) { + Histogram* histogram = static_cast( + Histogram::FactoryGet("Histogram", 1, 64, 8, HistogramBase::kNoFlags)); + + // Add i+1 samples to the i'th bucket. + histogram->Add(0); + int power_of_2 = 1; + for (int i = 1; i < 8; i++) { + for (int j = 0; j <= i; j++) + histogram->Add(power_of_2); + power_of_2 *= 2; + } + + // Check to see that the bucket counts reflect our additions. + scoped_ptr samples = histogram->SnapshotSampleVector(); + for (int i = 0; i < 8; i++) + EXPECT_EQ(i + 1, samples->GetCountAtIndex(i)); +} + +TEST_F(HistogramTest, CorruptSampleCounts) { + Histogram* histogram = static_cast( + Histogram::FactoryGet("Histogram", 1, 64, 8, HistogramBase::kNoFlags)); + + // Add some samples. + histogram->Add(20); + histogram->Add(40); + + scoped_ptr snapshot = histogram->SnapshotSampleVector(); + EXPECT_EQ(HistogramBase::NO_INCONSISTENCIES, + histogram->FindCorruption(*snapshot)); + EXPECT_EQ(2, snapshot->redundant_count()); + EXPECT_EQ(2, snapshot->TotalCount()); + + snapshot->counts_[3] += 100; // Sample count won't match redundant count. + EXPECT_EQ(HistogramBase::COUNT_LOW_ERROR, + histogram->FindCorruption(*snapshot)); + snapshot->counts_[2] -= 200; + EXPECT_EQ(HistogramBase::COUNT_HIGH_ERROR, + histogram->FindCorruption(*snapshot)); + + // But we can't spot a corruption if it is compensated for. + snapshot->counts_[1] += 100; + EXPECT_EQ(HistogramBase::NO_INCONSISTENCIES, + histogram->FindCorruption(*snapshot)); +} + +TEST_F(HistogramTest, CorruptBucketBounds) { + Histogram* histogram = static_cast( + Histogram::FactoryGet("Histogram", 1, 64, 8, HistogramBase::kNoFlags)); + + scoped_ptr snapshot = histogram->SnapshotSampleVector(); + EXPECT_EQ(HistogramBase::NO_INCONSISTENCIES, + histogram->FindCorruption(*snapshot)); + + BucketRanges* bucket_ranges = + const_cast(histogram->bucket_ranges()); + HistogramBase::Sample tmp = bucket_ranges->range(1); + bucket_ranges->set_range(1, bucket_ranges->range(2)); + bucket_ranges->set_range(2, tmp); + EXPECT_EQ( + HistogramBase::BUCKET_ORDER_ERROR | HistogramBase::RANGE_CHECKSUM_ERROR, + histogram->FindCorruption(*snapshot)); + + bucket_ranges->set_range(2, bucket_ranges->range(1)); + bucket_ranges->set_range(1, tmp); + EXPECT_EQ(0, histogram->FindCorruption(*snapshot)); + + // Show that two simple changes don't offset each other + bucket_ranges->set_range(3, bucket_ranges->range(3) + 1); + EXPECT_EQ(HistogramBase::RANGE_CHECKSUM_ERROR, + histogram->FindCorruption(*snapshot)); + + bucket_ranges->set_range(4, bucket_ranges->range(4) - 1); + EXPECT_EQ(HistogramBase::RANGE_CHECKSUM_ERROR, + histogram->FindCorruption(*snapshot)); + + // Repair histogram so that destructor won't DCHECK(). + bucket_ranges->set_range(3, bucket_ranges->range(3) - 1); + bucket_ranges->set_range(4, bucket_ranges->range(4) + 1); +} + +TEST_F(HistogramTest, HistogramSerializeInfo) { + Histogram* histogram = static_cast( + Histogram::FactoryGet("Histogram", 1, 64, 8, + HistogramBase::kIPCSerializationSourceFlag)); + Pickle pickle; + histogram->SerializeInfo(&pickle); + + PickleIterator iter(pickle); + + int type; + EXPECT_TRUE(iter.ReadInt(&type)); + EXPECT_EQ(HISTOGRAM, type); + + std::string name; + EXPECT_TRUE(iter.ReadString(&name)); + EXPECT_EQ("Histogram", name); + + int flag; + EXPECT_TRUE(iter.ReadInt(&flag)); + EXPECT_EQ(HistogramBase::kIPCSerializationSourceFlag, flag); + + int min; + EXPECT_TRUE(iter.ReadInt(&min)); + EXPECT_EQ(1, min); + + int max; + EXPECT_TRUE(iter.ReadInt(&max)); + EXPECT_EQ(64, max); + + int64 bucket_count; + EXPECT_TRUE(iter.ReadInt64(&bucket_count)); + EXPECT_EQ(8, bucket_count); + + uint32 checksum; + EXPECT_TRUE(iter.ReadUInt32(&checksum)); + EXPECT_EQ(histogram->bucket_ranges()->checksum(), checksum); + + // No more data in the pickle. + EXPECT_FALSE(iter.SkipBytes(1)); +} + +TEST_F(HistogramTest, CustomHistogramSerializeInfo) { + vector custom_ranges; + custom_ranges.push_back(10); + custom_ranges.push_back(100); + + HistogramBase* custom_histogram = CustomHistogram::FactoryGet( + "TestCustomRangeBoundedHistogram", + custom_ranges, + HistogramBase::kNoFlags); + Pickle pickle; + custom_histogram->SerializeInfo(&pickle); + + // Validate the pickle. + PickleIterator iter(pickle); + + int i; + std::string s; + int64 bucket_count; + uint32 ui32; + EXPECT_TRUE(iter.ReadInt(&i) && iter.ReadString(&s) && iter.ReadInt(&i) && + iter.ReadInt(&i) && iter.ReadInt(&i) && + iter.ReadInt64(&bucket_count) && iter.ReadUInt32(&ui32)); + EXPECT_EQ(3, bucket_count); + + int range; + EXPECT_TRUE(iter.ReadInt(&range)); + EXPECT_EQ(10, range); + EXPECT_TRUE(iter.ReadInt(&range)); + EXPECT_EQ(100, range); + + // No more data in the pickle. + EXPECT_FALSE(iter.SkipBytes(1)); +} + +#if GTEST_HAS_DEATH_TEST +// For Histogram, LinearHistogram and CustomHistogram, the minimum for a +// declared range is 1, while the maximum is (HistogramBase::kSampleType_MAX - +// 1). But we accept ranges exceeding those limits, and silently clamped to +// those limits. This is for backwards compatibility. +TEST(HistogramDeathTest, BadRangesTest) { + HistogramBase* histogram = Histogram::FactoryGet( + "BadRanges", 0, HistogramBase::kSampleType_MAX, 8, + HistogramBase::kNoFlags); + EXPECT_TRUE( + histogram->HasConstructionArguments( + 1, HistogramBase::kSampleType_MAX - 1, 8)); + + HistogramBase* linear_histogram = LinearHistogram::FactoryGet( + "BadRangesLinear", 0, HistogramBase::kSampleType_MAX, 8, + HistogramBase::kNoFlags); + EXPECT_TRUE( + linear_histogram->HasConstructionArguments( + 1, HistogramBase::kSampleType_MAX - 1, 8)); + + vector custom_ranges; + custom_ranges.push_back(0); + custom_ranges.push_back(5); + Histogram* custom_histogram = static_cast( + CustomHistogram::FactoryGet( + "BadRangesCustom", custom_ranges, HistogramBase::kNoFlags)); + const BucketRanges* ranges = custom_histogram->bucket_ranges(); + ASSERT_EQ(3u, ranges->size()); + EXPECT_EQ(0, ranges->range(0)); + EXPECT_EQ(5, ranges->range(1)); + EXPECT_EQ(HistogramBase::kSampleType_MAX, ranges->range(2)); + + // CustomHistogram does not accepts kSampleType_MAX as range. + custom_ranges.push_back(HistogramBase::kSampleType_MAX); + EXPECT_DEATH(CustomHistogram::FactoryGet("BadRangesCustom2", custom_ranges, + HistogramBase::kNoFlags), + ""); + + // CustomHistogram needs at least 1 valid range. + custom_ranges.clear(); + custom_ranges.push_back(0); + EXPECT_DEATH(CustomHistogram::FactoryGet("BadRangesCustom3", custom_ranges, + HistogramBase::kNoFlags), + ""); +} +#endif + +} // namespace base diff --git a/base/metrics/sample_map.cc b/base/metrics/sample_map.cc new file mode 100644 index 0000000000..42468cb57f --- /dev/null +++ b/base/metrics/sample_map.cc @@ -0,0 +1,86 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/metrics/sample_map.h" + +#include "base/logging.h" + +using std::map; + +namespace base { + +typedef HistogramBase::Count Count; +typedef HistogramBase::Sample Sample; + +SampleMap::SampleMap() {} + +SampleMap::~SampleMap() {} + +void SampleMap::Accumulate(Sample value, Count count) { + sample_counts_[value] += count; + IncreaseSum(count * value); + IncreaseRedundantCount(count); +} + +Count SampleMap::GetCount(Sample value) const { + map::const_iterator it = sample_counts_.find(value); + if (it == sample_counts_.end()) + return 0; + return it->second; +} + +Count SampleMap::TotalCount() const { + Count count = 0; + for (map::const_iterator it = sample_counts_.begin(); + it != sample_counts_.end(); + ++it) { + count += it->second; + } + return count; +} + +scoped_ptr SampleMap::Iterator() const { + return scoped_ptr(new SampleMapIterator(sample_counts_)); +} + +bool SampleMap::AddSubtractImpl(SampleCountIterator* iter, + HistogramSamples::Operator op) { + Sample min; + Sample max; + Count count; + for (; !iter->Done(); iter->Next()) { + iter->Get(&min, &max, &count); + if (min + 1 != max) + return false; // SparseHistogram only supports bucket with size 1. + sample_counts_[min] += (op == HistogramSamples::ADD) ? count : -count; + } + return true; +} + +SampleMapIterator::SampleMapIterator(const SampleToCountMap& sample_counts) + : iter_(sample_counts.begin()), + end_(sample_counts.end()) {} + +SampleMapIterator::~SampleMapIterator() {} + +bool SampleMapIterator::Done() const { + return iter_ == end_; +} + +void SampleMapIterator::Next() { + DCHECK(!Done()); + iter_++; +} + +void SampleMapIterator::Get(Sample* min, Sample* max, Count* count) const { + DCHECK(!Done()); + if (min != NULL) + *min = iter_->first; + if (max != NULL) + *max = iter_->first + 1; + if (count != NULL) + *count = iter_->second; +} + +} // namespace base diff --git a/base/metrics/sample_map.h b/base/metrics/sample_map.h new file mode 100644 index 0000000000..cdd1138547 --- /dev/null +++ b/base/metrics/sample_map.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. + +// SampleMap implements HistogramSamples interface. It is used by the +// SparseHistogram class to store samples. + +#ifndef BASE_METRICS_SAMPLE_MAP_H_ +#define BASE_METRICS_SAMPLE_MAP_H_ + +#include + +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/metrics/histogram_base.h" +#include "base/metrics/histogram_samples.h" + +namespace base { + +class BASE_EXPORT_PRIVATE SampleMap : public HistogramSamples { + public: + SampleMap(); + virtual ~SampleMap(); + + // HistogramSamples implementation: + virtual void Accumulate(HistogramBase::Sample value, + HistogramBase::Count count) OVERRIDE; + virtual HistogramBase::Count GetCount( + HistogramBase::Sample value) const OVERRIDE; + virtual HistogramBase::Count TotalCount() const OVERRIDE; + virtual scoped_ptr Iterator() const OVERRIDE; + + protected: + virtual bool AddSubtractImpl( + SampleCountIterator* iter, + HistogramSamples::Operator op) OVERRIDE; // |op| is ADD or SUBTRACT. + + private: + std::map sample_counts_; + + DISALLOW_COPY_AND_ASSIGN(SampleMap); +}; + +class BASE_EXPORT_PRIVATE SampleMapIterator : public SampleCountIterator { + public: + typedef std::map + SampleToCountMap; + + explicit SampleMapIterator(const SampleToCountMap& sample_counts); + virtual ~SampleMapIterator(); + + // SampleCountIterator implementation: + virtual bool Done() const OVERRIDE; + virtual void Next() OVERRIDE; + virtual void Get(HistogramBase::Sample* min, + HistogramBase::Sample* max, + HistogramBase::Count* count) const OVERRIDE; + private: + SampleToCountMap::const_iterator iter_; + const SampleToCountMap::const_iterator end_; +}; + +} // namespace base + +#endif // BASE_METRICS_SAMPLE_MAP_H_ diff --git a/base/metrics/sample_map_unittest.cc b/base/metrics/sample_map_unittest.cc new file mode 100644 index 0000000000..1a53ee7f54 --- /dev/null +++ b/base/metrics/sample_map_unittest.cc @@ -0,0 +1,123 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/scoped_ptr.h" +#include "base/metrics/sample_map.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace { + +TEST(SampleMapTest, AccumulateTest) { + SampleMap samples; + + samples.Accumulate(1, 100); + samples.Accumulate(2, 200); + samples.Accumulate(1, -200); + EXPECT_EQ(-100, samples.GetCount(1)); + EXPECT_EQ(200, samples.GetCount(2)); + + EXPECT_EQ(300, samples.sum()); + EXPECT_EQ(100, samples.TotalCount()); + EXPECT_EQ(samples.redundant_count(), samples.TotalCount()); +} + +TEST(SampleMapTest, AddSubtractTest) { + SampleMap samples1; + SampleMap samples2; + + samples1.Accumulate(1, 100); + samples1.Accumulate(2, 100); + samples1.Accumulate(3, 100); + + samples2.Accumulate(1, 200); + samples2.Accumulate(2, 200); + samples2.Accumulate(4, 200); + + samples1.Add(samples2); + EXPECT_EQ(300, samples1.GetCount(1)); + EXPECT_EQ(300, samples1.GetCount(2)); + EXPECT_EQ(100, samples1.GetCount(3)); + EXPECT_EQ(200, samples1.GetCount(4)); + EXPECT_EQ(2000, samples1.sum()); + EXPECT_EQ(900, samples1.TotalCount()); + EXPECT_EQ(samples1.redundant_count(), samples1.TotalCount()); + + samples1.Subtract(samples2); + EXPECT_EQ(100, samples1.GetCount(1)); + EXPECT_EQ(100, samples1.GetCount(2)); + EXPECT_EQ(100, samples1.GetCount(3)); + EXPECT_EQ(0, samples1.GetCount(4)); + EXPECT_EQ(600, samples1.sum()); + EXPECT_EQ(300, samples1.TotalCount()); + EXPECT_EQ(samples1.redundant_count(), samples1.TotalCount()); +} + +TEST(SampleMapIteratorTest, IterateTest) { + SampleMap samples; + samples.Accumulate(1, 100); + samples.Accumulate(2, 200); + samples.Accumulate(4, -300); + samples.Accumulate(5, 0); + + scoped_ptr it = samples.Iterator(); + + HistogramBase::Sample min; + HistogramBase::Sample max; + HistogramBase::Count count; + + it->Get(&min, &max, &count); + EXPECT_EQ(1, min); + EXPECT_EQ(2, max); + EXPECT_EQ(100, count); + EXPECT_FALSE(it->GetBucketIndex(NULL)); + + it->Next(); + it->Get(&min, &max, &count); + EXPECT_EQ(2, min); + EXPECT_EQ(3, max); + EXPECT_EQ(200, count); + + it->Next(); + it->Get(&min, &max, &count); + EXPECT_EQ(4, min); + EXPECT_EQ(5, max); + EXPECT_EQ(-300, count); + + it->Next(); + it->Get(&min, &max, &count); + EXPECT_EQ(5, min); + EXPECT_EQ(6, max); + EXPECT_EQ(0, count); + + it->Next(); + EXPECT_TRUE(it->Done()); +} + +#if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) && GTEST_HAS_DEATH_TEST + +TEST(SampleMapIteratorDeathTest, IterateDoneTest) { + SampleMap samples; + + scoped_ptr it = samples.Iterator(); + + EXPECT_TRUE(it->Done()); + + HistogramBase::Sample min; + HistogramBase::Sample max; + HistogramBase::Count count; + EXPECT_DEATH(it->Get(&min, &max, &count), ""); + + EXPECT_DEATH(it->Next(), ""); + + samples.Accumulate(1, 100); + it = samples.Iterator(); + EXPECT_FALSE(it->Done()); +} + +#endif +// (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) && GTEST_HAS_DEATH_TEST + +} // namespace +} // namespace base diff --git a/base/metrics/sample_vector.cc b/base/metrics/sample_vector.cc new file mode 100644 index 0000000000..fe602eec80 --- /dev/null +++ b/base/metrics/sample_vector.cc @@ -0,0 +1,161 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/metrics/sample_vector.h" + +#include "base/logging.h" +#include "base/metrics/bucket_ranges.h" + +using std::vector; + +namespace base { + +typedef HistogramBase::Count Count; +typedef HistogramBase::Sample Sample; + +SampleVector::SampleVector(const BucketRanges* bucket_ranges) + : counts_(bucket_ranges->bucket_count()), + bucket_ranges_(bucket_ranges) { + CHECK_GE(bucket_ranges_->bucket_count(), 1u); +} + +SampleVector::~SampleVector() {} + +void SampleVector::Accumulate(Sample value, Count count) { + size_t bucket_index = GetBucketIndex(value); + counts_[bucket_index] += count; + IncreaseSum(count * value); + IncreaseRedundantCount(count); +} + +Count SampleVector::GetCount(Sample value) const { + size_t bucket_index = GetBucketIndex(value); + return counts_[bucket_index]; +} + +Count SampleVector::TotalCount() const { + Count count = 0; + for (size_t i = 0; i < counts_.size(); i++) { + count += counts_[i]; + } + return count; +} + +Count SampleVector::GetCountAtIndex(size_t bucket_index) const { + DCHECK(bucket_index < counts_.size()); + return counts_[bucket_index]; +} + +scoped_ptr SampleVector::Iterator() const { + return scoped_ptr( + new SampleVectorIterator(&counts_, bucket_ranges_)); +} + +bool SampleVector::AddSubtractImpl(SampleCountIterator* iter, + HistogramSamples::Operator op) { + HistogramBase::Sample min; + HistogramBase::Sample max; + HistogramBase::Count count; + + // Go through the iterator and add the counts into correct bucket. + size_t index = 0; + while (index < counts_.size() && !iter->Done()) { + iter->Get(&min, &max, &count); + if (min == bucket_ranges_->range(index) && + max == bucket_ranges_->range(index + 1)) { + // Sample matches this bucket! + counts_[index] += (op == HistogramSamples::ADD) ? count : -count; + iter->Next(); + } else if (min > bucket_ranges_->range(index)) { + // Sample is larger than current bucket range. Try next. + index++; + } else { + // Sample is smaller than current bucket range. We scan buckets from + // smallest to largest, so the sample value must be invalid. + return false; + } + } + + return iter->Done(); +} + +// Use simple binary search. This is very general, but there are better +// approaches if we knew that the buckets were linearly distributed. +size_t SampleVector::GetBucketIndex(Sample value) const { + size_t bucket_count = bucket_ranges_->bucket_count(); + CHECK_GE(bucket_count, 1u); + CHECK_GE(value, bucket_ranges_->range(0)); + CHECK_LT(value, bucket_ranges_->range(bucket_count)); + + size_t under = 0; + size_t over = bucket_count; + size_t mid; + do { + DCHECK_GE(over, under); + mid = under + (over - under)/2; + if (mid == under) + break; + if (bucket_ranges_->range(mid) <= value) + under = mid; + else + over = mid; + } while (true); + + DCHECK_LE(bucket_ranges_->range(mid), value); + CHECK_GT(bucket_ranges_->range(mid + 1), value); + return mid; +} + +SampleVectorIterator::SampleVectorIterator(const vector* counts, + const BucketRanges* bucket_ranges) + : counts_(counts), + bucket_ranges_(bucket_ranges), + index_(0) { + CHECK_GE(bucket_ranges_->bucket_count(), counts_->size()); + SkipEmptyBuckets(); +} + +SampleVectorIterator::~SampleVectorIterator() {} + +bool SampleVectorIterator::Done() const { + return index_ >= counts_->size(); +} + +void SampleVectorIterator::Next() { + DCHECK(!Done()); + index_++; + SkipEmptyBuckets(); +} + +void SampleVectorIterator::Get(HistogramBase::Sample* min, + HistogramBase::Sample* max, + HistogramBase::Count* count) const { + DCHECK(!Done()); + if (min != NULL) + *min = bucket_ranges_->range(index_); + if (max != NULL) + *max = bucket_ranges_->range(index_ + 1); + if (count != NULL) + *count = (*counts_)[index_]; +} + +bool SampleVectorIterator::GetBucketIndex(size_t* index) const { + DCHECK(!Done()); + if (index != NULL) + *index = index_; + return true; +} + +void SampleVectorIterator::SkipEmptyBuckets() { + if (Done()) + return; + + while (index_ < counts_->size()) { + if ((*counts_)[index_] != 0) + return; + index_++; + } +} + +} // namespace base diff --git a/base/metrics/sample_vector.h b/base/metrics/sample_vector.h new file mode 100644 index 0000000000..67c344a97c --- /dev/null +++ b/base/metrics/sample_vector.h @@ -0,0 +1,84 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// SampleVector implements HistogramSamples interface. It is used by all +// Histogram based classes to store samples. + +#ifndef BASE_METRICS_SAMPLE_VECTOR_H_ +#define BASE_METRICS_SAMPLE_VECTOR_H_ + +#include + +#include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/metrics/histogram_base.h" +#include "base/metrics/histogram_samples.h" + +namespace base { + +class BucketRanges; + +class BASE_EXPORT_PRIVATE SampleVector : public HistogramSamples { + public: + explicit SampleVector(const BucketRanges* bucket_ranges); + virtual ~SampleVector(); + + // HistogramSamples implementation: + virtual void Accumulate(HistogramBase::Sample value, + HistogramBase::Count count) OVERRIDE; + virtual HistogramBase::Count GetCount( + HistogramBase::Sample value) const OVERRIDE; + virtual HistogramBase::Count TotalCount() const OVERRIDE; + virtual scoped_ptr Iterator() const OVERRIDE; + + // Get count of a specific bucket. + HistogramBase::Count GetCountAtIndex(size_t bucket_index) const; + + protected: + virtual bool AddSubtractImpl( + SampleCountIterator* iter, + HistogramSamples::Operator op) OVERRIDE; // |op| is ADD or SUBTRACT. + + virtual size_t GetBucketIndex(HistogramBase::Sample value) const; + + private: + FRIEND_TEST_ALL_PREFIXES(HistogramTest, CorruptSampleCounts); + + std::vector counts_; + + // Shares the same BucketRanges with Histogram object. + const BucketRanges* const bucket_ranges_; + + DISALLOW_COPY_AND_ASSIGN(SampleVector); +}; + +class BASE_EXPORT_PRIVATE SampleVectorIterator : public SampleCountIterator { + public: + SampleVectorIterator(const std::vector* counts, + const BucketRanges* bucket_ranges); + virtual ~SampleVectorIterator(); + + // SampleCountIterator implementation: + virtual bool Done() const OVERRIDE; + virtual void Next() OVERRIDE; + virtual void Get(HistogramBase::Sample* min, + HistogramBase::Sample* max, + HistogramBase::Count* count) const OVERRIDE; + + // SampleVector uses predefined buckets, so iterator can return bucket index. + virtual bool GetBucketIndex(size_t* index) const OVERRIDE; + + private: + void SkipEmptyBuckets(); + + const std::vector* counts_; + const BucketRanges* bucket_ranges_; + + size_t index_; +}; + +} // namespace base + +#endif // BASE_METRICS_SAMPLE_VECTOR_H_ diff --git a/base/metrics/sample_vector_unittest.cc b/base/metrics/sample_vector_unittest.cc new file mode 100644 index 0000000000..9c7ba96233 --- /dev/null +++ b/base/metrics/sample_vector_unittest.cc @@ -0,0 +1,265 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/memory/scoped_ptr.h" +#include "base/metrics/bucket_ranges.h" +#include "base/metrics/histogram.h" +#include "base/metrics/sample_vector.h" +#include "testing/gtest/include/gtest/gtest.h" + +using std::vector; + +namespace base { +namespace { + +TEST(SampleVectorTest, AccumulateTest) { + // Custom buckets: [1, 5) [5, 10) + BucketRanges ranges(3); + ranges.set_range(0, 1); + ranges.set_range(1, 5); + ranges.set_range(2, 10); + SampleVector samples(&ranges); + + samples.Accumulate(1, 200); + samples.Accumulate(2, -300); + EXPECT_EQ(-100, samples.GetCountAtIndex(0)); + + samples.Accumulate(5, 200); + EXPECT_EQ(200, samples.GetCountAtIndex(1)); + + EXPECT_EQ(600, samples.sum()); + EXPECT_EQ(100, samples.redundant_count()); + EXPECT_EQ(samples.TotalCount(), samples.redundant_count()); + + samples.Accumulate(5, -100); + EXPECT_EQ(100, samples.GetCountAtIndex(1)); + + EXPECT_EQ(100, samples.sum()); + EXPECT_EQ(0, samples.redundant_count()); + EXPECT_EQ(samples.TotalCount(), samples.redundant_count()); +} + +TEST(SampleVectorTest, AddSubtractTest) { + // Custom buckets: [0, 1) [1, 2) [2, 3) [3, INT_MAX) + BucketRanges ranges(5); + ranges.set_range(0, 0); + ranges.set_range(1, 1); + ranges.set_range(2, 2); + ranges.set_range(3, 3); + ranges.set_range(4, INT_MAX); + + SampleVector samples1(&ranges); + samples1.Accumulate(0, 100); + samples1.Accumulate(2, 100); + samples1.Accumulate(4, 100); + EXPECT_EQ(600, samples1.sum()); + EXPECT_EQ(300, samples1.TotalCount()); + EXPECT_EQ(samples1.redundant_count(), samples1.TotalCount()); + + SampleVector samples2(&ranges); + samples2.Accumulate(1, 200); + samples2.Accumulate(2, 200); + samples2.Accumulate(4, 200); + EXPECT_EQ(1400, samples2.sum()); + EXPECT_EQ(600, samples2.TotalCount()); + EXPECT_EQ(samples2.redundant_count(), samples2.TotalCount()); + + samples1.Add(samples2); + EXPECT_EQ(100, samples1.GetCountAtIndex(0)); + EXPECT_EQ(200, samples1.GetCountAtIndex(1)); + EXPECT_EQ(300, samples1.GetCountAtIndex(2)); + EXPECT_EQ(300, samples1.GetCountAtIndex(3)); + EXPECT_EQ(2000, samples1.sum()); + EXPECT_EQ(900, samples1.TotalCount()); + EXPECT_EQ(samples1.redundant_count(), samples1.TotalCount()); + + samples1.Subtract(samples2); + EXPECT_EQ(100, samples1.GetCountAtIndex(0)); + EXPECT_EQ(0, samples1.GetCountAtIndex(1)); + EXPECT_EQ(100, samples1.GetCountAtIndex(2)); + EXPECT_EQ(100, samples1.GetCountAtIndex(3)); + EXPECT_EQ(600, samples1.sum()); + EXPECT_EQ(300, samples1.TotalCount()); + EXPECT_EQ(samples1.redundant_count(), samples1.TotalCount()); +} + +#if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) && GTEST_HAS_DEATH_TEST +TEST(SampleVectorDeathTest, BucketIndexTest) { + // 8 buckets with exponential layout: + // [0, 1) [1, 2) [2, 4) [4, 8) [8, 16) [16, 32) [32, 64) [64, INT_MAX) + BucketRanges ranges(9); + Histogram::InitializeBucketRanges(1, 64, &ranges); + SampleVector samples(&ranges); + + // Normal case + samples.Accumulate(0, 1); + samples.Accumulate(3, 2); + samples.Accumulate(64, 3); + EXPECT_EQ(1, samples.GetCount(0)); + EXPECT_EQ(2, samples.GetCount(2)); + EXPECT_EQ(3, samples.GetCount(65)); + + // Extreme case. + EXPECT_DEATH(samples.Accumulate(INT_MIN, 100), ""); + EXPECT_DEATH(samples.Accumulate(-1, 100), ""); + EXPECT_DEATH(samples.Accumulate(INT_MAX, 100), ""); + + // Custom buckets: [1, 5) [5, 10) + // Note, this is not a valid BucketRanges for Histogram because it does not + // have overflow buckets. + BucketRanges ranges2(3); + ranges2.set_range(0, 1); + ranges2.set_range(1, 5); + ranges2.set_range(2, 10); + SampleVector samples2(&ranges2); + + // Normal case. + samples2.Accumulate(1, 1); + samples2.Accumulate(4, 1); + samples2.Accumulate(5, 2); + samples2.Accumulate(9, 2); + EXPECT_EQ(2, samples2.GetCount(1)); + EXPECT_EQ(4, samples2.GetCount(5)); + + // Extreme case. + EXPECT_DEATH(samples2.Accumulate(0, 100), ""); + EXPECT_DEATH(samples2.Accumulate(10, 100), ""); +} + +TEST(SampleVectorDeathTest, AddSubtractBucketNotMatchTest) { + // Custom buckets 1: [1, 3) [3, 5) + BucketRanges ranges1(3); + ranges1.set_range(0, 1); + ranges1.set_range(1, 3); + ranges1.set_range(2, 5); + SampleVector samples1(&ranges1); + + // Custom buckets 2: [0, 1) [1, 3) [3, 6) [6, 7) + BucketRanges ranges2(5); + ranges2.set_range(0, 0); + ranges2.set_range(1, 1); + ranges2.set_range(2, 3); + ranges2.set_range(3, 6); + ranges2.set_range(4, 7); + SampleVector samples2(&ranges2); + + samples2.Accumulate(1, 100); + samples1.Add(samples2); + EXPECT_EQ(100, samples1.GetCountAtIndex(0)); + + // Extra bucket in the beginning. + samples2.Accumulate(0, 100); + EXPECT_DEATH(samples1.Add(samples2), ""); + EXPECT_DEATH(samples1.Subtract(samples2), ""); + + // Extra bucket in the end. + samples2.Accumulate(0, -100); + samples2.Accumulate(6, 100); + EXPECT_DEATH(samples1.Add(samples2), ""); + EXPECT_DEATH(samples1.Subtract(samples2), ""); + + // Bucket not match: [3, 5) VS [3, 6) + samples2.Accumulate(6, -100); + samples2.Accumulate(3, 100); + EXPECT_DEATH(samples1.Add(samples2), ""); + EXPECT_DEATH(samples1.Subtract(samples2), ""); +} + +#endif +// (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) && GTEST_HAS_DEATH_TEST + +TEST(SampleVectorIteratorTest, IterateTest) { + BucketRanges ranges(5); + ranges.set_range(0, 0); + ranges.set_range(1, 1); + ranges.set_range(2, 2); + ranges.set_range(3, 3); + ranges.set_range(4, 4); + + vector counts(3); + counts[0] = 1; + counts[1] = 0; // Iterator will bypass this empty bucket. + counts[2] = 2; + + // BucketRanges can have larger size than counts. + SampleVectorIterator it(&counts, &ranges); + size_t index; + + HistogramBase::Sample min; + HistogramBase::Sample max; + HistogramBase::Count count; + it.Get(&min, &max, &count); + EXPECT_EQ(0, min); + EXPECT_EQ(1, max); + EXPECT_EQ(1, count); + EXPECT_TRUE(it.GetBucketIndex(&index)); + EXPECT_EQ(0u, index); + + it.Next(); + it.Get(&min, &max, &count); + EXPECT_EQ(2, min); + EXPECT_EQ(3, max); + EXPECT_EQ(2, count); + EXPECT_TRUE(it.GetBucketIndex(&index)); + EXPECT_EQ(2u, index); + + it.Next(); + EXPECT_TRUE(it.Done()); + + // Create iterator from SampleVector. + SampleVector samples(&ranges); + samples.Accumulate(0, 0); + samples.Accumulate(1, 1); + samples.Accumulate(2, 2); + samples.Accumulate(3, 3); + scoped_ptr it2 = samples.Iterator(); + + int i; + for (i = 1; !it2->Done(); i++, it2->Next()) { + it2->Get(&min, &max, &count); + EXPECT_EQ(i, min); + EXPECT_EQ(i + 1, max); + EXPECT_EQ(i, count); + + size_t index; + EXPECT_TRUE(it2->GetBucketIndex(&index)); + EXPECT_EQ(static_cast(i), index); + } + EXPECT_EQ(4, i); +} + +#if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) && GTEST_HAS_DEATH_TEST + +TEST(SampleVectorIteratorDeathTest, IterateDoneTest) { + BucketRanges ranges(5); + ranges.set_range(0, 0); + ranges.set_range(1, 1); + ranges.set_range(2, 2); + ranges.set_range(3, 3); + ranges.set_range(4, INT_MAX); + SampleVector samples(&ranges); + + scoped_ptr it = samples.Iterator(); + + EXPECT_TRUE(it->Done()); + + HistogramBase::Sample min; + HistogramBase::Sample max; + HistogramBase::Count count; + EXPECT_DEATH(it->Get(&min, &max, &count), ""); + + EXPECT_DEATH(it->Next(), ""); + + samples.Accumulate(2, 100); + it = samples.Iterator(); + EXPECT_FALSE(it->Done()); +} + +#endif +// (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) && GTEST_HAS_DEATH_TEST + +} // namespace +} // namespace base diff --git a/base/metrics/sparse_histogram.cc b/base/metrics/sparse_histogram.cc new file mode 100644 index 0000000000..737ccad72f --- /dev/null +++ b/base/metrics/sparse_histogram.cc @@ -0,0 +1,179 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/metrics/sparse_histogram.h" + +#include "base/metrics/sample_map.h" +#include "base/metrics/statistics_recorder.h" +#include "base/pickle.h" +#include "base/strings/stringprintf.h" +#include "base/synchronization/lock.h" + +using std::map; +using std::string; + +namespace base { + +typedef HistogramBase::Count Count; +typedef HistogramBase::Sample Sample; + +// static +HistogramBase* SparseHistogram::FactoryGet(const string& name, int32 flags) { + HistogramBase* histogram = StatisticsRecorder::FindHistogram(name); + + if (!histogram) { + // To avoid racy destruction at shutdown, the following will be leaked. + HistogramBase* tentative_histogram = new SparseHistogram(name); + tentative_histogram->SetFlags(flags); + histogram = + StatisticsRecorder::RegisterOrDeleteDuplicate(tentative_histogram); + } + DCHECK_EQ(SPARSE_HISTOGRAM, histogram->GetHistogramType()); + return histogram; +} + +SparseHistogram::~SparseHistogram() {} + +HistogramType SparseHistogram::GetHistogramType() const { + return SPARSE_HISTOGRAM; +} + +bool SparseHistogram::HasConstructionArguments( + Sample expected_minimum, + Sample expected_maximum, + size_t expected_bucket_count) const { + // SparseHistogram never has min/max/bucket_count limit. + return false; +} + +void SparseHistogram::Add(Sample value) { + base::AutoLock auto_lock(lock_); + samples_.Accumulate(value, 1); +} + +scoped_ptr SparseHistogram::SnapshotSamples() const { + scoped_ptr snapshot(new SampleMap()); + + base::AutoLock auto_lock(lock_); + snapshot->Add(samples_); + return snapshot.PassAs(); +} + +void SparseHistogram::AddSamples(const HistogramSamples& samples) { + base::AutoLock auto_lock(lock_); + samples_.Add(samples); +} + +bool SparseHistogram::AddSamplesFromPickle(PickleIterator* iter) { + base::AutoLock auto_lock(lock_); + return samples_.AddFromPickle(iter); +} + +void SparseHistogram::WriteHTMLGraph(string* output) const { + output->append("
");
+  WriteAsciiImpl(true, "
", output); + output->append("
"); +} + +void SparseHistogram::WriteAscii(string* output) const { + WriteAsciiImpl(true, "\n", output); +} + +bool SparseHistogram::SerializeInfoImpl(Pickle* pickle) const { + return pickle->WriteString(histogram_name()) && pickle->WriteInt(flags()); +} + +SparseHistogram::SparseHistogram(const string& name) + : HistogramBase(name) {} + +HistogramBase* SparseHistogram::DeserializeInfoImpl(PickleIterator* iter) { + string histogram_name; + int flags; + if (!iter->ReadString(&histogram_name) || !iter->ReadInt(&flags)) { + DLOG(ERROR) << "Pickle error decoding Histogram: " << histogram_name; + return NULL; + } + + DCHECK(flags & HistogramBase::kIPCSerializationSourceFlag); + flags &= ~HistogramBase::kIPCSerializationSourceFlag; + + return SparseHistogram::FactoryGet(histogram_name, flags); +} + +void SparseHistogram::GetParameters(DictionaryValue* params) const { + // TODO(kaiwang): Implement. (See HistogramBase::WriteJSON.) +} + +void SparseHistogram::GetCountAndBucketData(Count* count, + int64* sum, + ListValue* buckets) const { + // TODO(kaiwang): Implement. (See HistogramBase::WriteJSON.) +} + +void SparseHistogram::WriteAsciiImpl(bool graph_it, + const std::string& newline, + std::string* output) const { + // Get a local copy of the data so we are consistent. + scoped_ptr snapshot = SnapshotSamples(); + Count total_count = snapshot->TotalCount(); + double scaled_total_count = total_count / 100.0; + + WriteAsciiHeader(total_count, output); + output->append(newline); + + // Determine how wide the largest bucket range is (how many digits to print), + // so that we'll be able to right-align starts for the graphical bars. + // Determine which bucket has the largest sample count so that we can + // normalize the graphical bar-width relative to that sample count. + Count largest_count = 0; + Sample largest_sample = 0; + scoped_ptr it = snapshot->Iterator(); + while (!it->Done()) + { + Sample min; + Sample max; + Count count; + it->Get(&min, &max, &count); + if (min > largest_sample) + largest_sample = min; + if (count > largest_count) + largest_count = count; + it->Next(); + } + size_t print_width = GetSimpleAsciiBucketRange(largest_sample).size() + 1; + + // iterate over each item and display them + it = snapshot->Iterator(); + while (!it->Done()) + { + Sample min; + Sample max; + Count count; + it->Get(&min, &max, &count); + + // value is min, so display it + string range = GetSimpleAsciiBucketRange(min); + output->append(range); + for (size_t j = 0; range.size() + j < print_width + 1; ++j) + output->push_back(' '); + + if (graph_it) + WriteAsciiBucketGraph(count, largest_count, output); + WriteAsciiBucketValue(count, scaled_total_count, output); + output->append(newline); + it->Next(); + } +} + +void SparseHistogram::WriteAsciiHeader(const Count total_count, + std::string* output) const { + StringAppendF(output, + "Histogram: %s recorded %d samples", + histogram_name().c_str(), + total_count); + if (flags() & ~kHexRangePrintingFlag) + StringAppendF(output, " (flags = 0x%x)", flags() & ~kHexRangePrintingFlag); +} + +} // namespace base diff --git a/base/metrics/sparse_histogram.h b/base/metrics/sparse_histogram.h new file mode 100644 index 0000000000..07d56603a5 --- /dev/null +++ b/base/metrics/sparse_histogram.h @@ -0,0 +1,117 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_METRICS_SPARSE_HISTOGRAM_H_ +#define BASE_METRICS_SPARSE_HISTOGRAM_H_ + +#include +#include + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/metrics/histogram_base.h" +#include "base/metrics/sample_map.h" +#include "base/synchronization/lock.h" + +namespace base { + +// The common code for different SparseHistogram macros. +#define HISTOGRAM_SPARSE_COMMON(name, sample, flag) \ + do { \ + base::HistogramBase* histogram( \ + base::SparseHistogram::FactoryGet(name, flag)); \ + DCHECK_EQ(histogram->histogram_name(), name); \ + histogram->Add(sample); \ + } while (0) + +#define HISTOGRAM_SPARSE_SLOWLY(name, sample) \ + HISTOGRAM_SPARSE_COMMON(name, sample, base::HistogramBase::kNoFlags) + +#define UMA_HISTOGRAM_SPARSE_SLOWLY(name, sample) \ + HISTOGRAM_SPARSE_COMMON(name, sample, \ + base::HistogramBase::kUmaTargetedHistogramFlag) + +//------------------------------------------------------------------------------ +// Define debug only version of macros. +#ifndef NDEBUG + +#define DHISTOGRAM_SPARSE_SLOWLY(name, sample) \ + HISTOGRAM_SPARSE_SLOWLY(name, sample) + +#else // NDEBUG + +#define DHISTOGRAM_SPARSE_SLOWLY(name, sample) \ + while (0) { \ + static_cast(name); \ + static_cast(sample); \ + } + +#endif // NDEBUG + +class HistogramSamples; + +class BASE_EXPORT_PRIVATE SparseHistogram : public HistogramBase { + public: + // If there's one with same name, return the existing one. If not, create a + // new one. + static HistogramBase* FactoryGet(const std::string& name, int32 flags); + + virtual ~SparseHistogram(); + + // HistogramBase implementation: + virtual HistogramType GetHistogramType() const OVERRIDE; + virtual bool HasConstructionArguments( + Sample expected_minimum, + Sample expected_maximum, + size_t expected_bucket_count) const OVERRIDE; + virtual void Add(Sample value) OVERRIDE; + virtual void AddSamples(const HistogramSamples& samples) OVERRIDE; + virtual bool AddSamplesFromPickle(PickleIterator* iter) OVERRIDE; + virtual scoped_ptr SnapshotSamples() const OVERRIDE; + virtual void WriteHTMLGraph(std::string* output) const OVERRIDE; + virtual void WriteAscii(std::string* output) const OVERRIDE; + + protected: + // HistogramBase implementation: + virtual bool SerializeInfoImpl(Pickle* pickle) const OVERRIDE; + + private: + // Clients should always use FactoryGet to create SparseHistogram. + explicit SparseHistogram(const std::string& name); + + friend BASE_EXPORT_PRIVATE HistogramBase* DeserializeHistogramInfo( + PickleIterator* iter); + static HistogramBase* DeserializeInfoImpl(PickleIterator* iter); + + virtual void GetParameters(DictionaryValue* params) const OVERRIDE; + virtual void GetCountAndBucketData(Count* count, + int64* sum, + ListValue* buckets) const OVERRIDE; + + // Helpers for emitting Ascii graphic. Each method appends data to output. + void WriteAsciiImpl(bool graph_it, + const std::string& newline, + std::string* output) const; + + // Write a common header message describing this histogram. + void WriteAsciiHeader(const Count total_count, + std::string* output) const; + + // For constuctor calling. + friend class SparseHistogramTest; + + // Protects access to |samples_|. + mutable base::Lock lock_; + + SampleMap samples_; + + DISALLOW_COPY_AND_ASSIGN(SparseHistogram); +}; + +} // namespace base + +#endif // BASE_METRICS_SPARSE_HISTOGRAM_H_ diff --git a/base/metrics/sparse_histogram_unittest.cc b/base/metrics/sparse_histogram_unittest.cc new file mode 100644 index 0000000000..825381d853 --- /dev/null +++ b/base/metrics/sparse_histogram_unittest.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 + +#include "base/memory/scoped_ptr.h" +#include "base/metrics/histogram_base.h" +#include "base/metrics/histogram_samples.h" +#include "base/metrics/sample_map.h" +#include "base/metrics/sparse_histogram.h" +#include "base/metrics/statistics_recorder.h" +#include "base/pickle.h" +#include "base/strings/stringprintf.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +class SparseHistogramTest : public testing::Test { + protected: + virtual void SetUp() { + // Each test will have a clean state (no Histogram / BucketRanges + // registered). + InitializeStatisticsRecorder(); + } + + virtual void TearDown() { + UninitializeStatisticsRecorder(); + } + + void InitializeStatisticsRecorder() { + statistics_recorder_ = new StatisticsRecorder(); + } + + void UninitializeStatisticsRecorder() { + delete statistics_recorder_; + statistics_recorder_ = NULL; + } + + scoped_ptr NewSparseHistogram(const std::string& name) { + return scoped_ptr(new SparseHistogram(name)); + } + + StatisticsRecorder* statistics_recorder_; +}; + +TEST_F(SparseHistogramTest, BasicTest) { + scoped_ptr histogram(NewSparseHistogram("Sparse")); + scoped_ptr snapshot(histogram->SnapshotSamples()); + EXPECT_EQ(0, snapshot->TotalCount()); + EXPECT_EQ(0, snapshot->sum()); + + histogram->Add(100); + scoped_ptr snapshot1(histogram->SnapshotSamples()); + EXPECT_EQ(1, snapshot1->TotalCount()); + EXPECT_EQ(1, snapshot1->GetCount(100)); + + histogram->Add(100); + histogram->Add(101); + scoped_ptr snapshot2(histogram->SnapshotSamples()); + EXPECT_EQ(3, snapshot2->TotalCount()); + EXPECT_EQ(2, snapshot2->GetCount(100)); + EXPECT_EQ(1, snapshot2->GetCount(101)); +} + +TEST_F(SparseHistogramTest, MacroBasicTest) { + HISTOGRAM_SPARSE_SLOWLY("Sparse", 100); + HISTOGRAM_SPARSE_SLOWLY("Sparse", 200); + HISTOGRAM_SPARSE_SLOWLY("Sparse", 100); + + StatisticsRecorder::Histograms histograms; + StatisticsRecorder::GetHistograms(&histograms); + + ASSERT_EQ(1U, histograms.size()); + HistogramBase* sparse_histogram = histograms[0]; + + EXPECT_EQ(SPARSE_HISTOGRAM, sparse_histogram->GetHistogramType()); + EXPECT_EQ("Sparse", sparse_histogram->histogram_name()); + EXPECT_EQ(HistogramBase::kNoFlags, sparse_histogram->flags()); + + scoped_ptr samples = sparse_histogram->SnapshotSamples(); + EXPECT_EQ(3, samples->TotalCount()); + EXPECT_EQ(2, samples->GetCount(100)); + EXPECT_EQ(1, samples->GetCount(200)); +} + +TEST_F(SparseHistogramTest, MacroUmaTest) { + UMA_HISTOGRAM_SPARSE_SLOWLY("Uma", 100); + + StatisticsRecorder::Histograms histograms; + StatisticsRecorder::GetHistograms(&histograms); + + ASSERT_EQ(1U, histograms.size()); + HistogramBase* sparse_histogram = histograms[0]; + + EXPECT_EQ("Uma", sparse_histogram->histogram_name()); + EXPECT_EQ(HistogramBase::kUmaTargetedHistogramFlag, + sparse_histogram->flags()); +} + +TEST_F(SparseHistogramTest, MacroInLoopTest) { + // Unlike the macros in histogram.h, SparseHistogram macros can have a + // variable as histogram name. + for (int i = 0; i < 2; i++) { + std::string name = StringPrintf("Sparse%d", i + 1); + UMA_HISTOGRAM_SPARSE_SLOWLY(name, 100); + } + + StatisticsRecorder::Histograms histograms; + StatisticsRecorder::GetHistograms(&histograms); + ASSERT_EQ(2U, histograms.size()); + + std::string name1 = histograms[0]->histogram_name(); + std::string name2 = histograms[1]->histogram_name(); + EXPECT_TRUE(("Sparse1" == name1 && "Sparse2" == name2) || + ("Sparse2" == name1 && "Sparse1" == name2)); +} + +TEST_F(SparseHistogramTest, Serialize) { + scoped_ptr histogram(NewSparseHistogram("Sparse")); + histogram->SetFlags(HistogramBase::kIPCSerializationSourceFlag); + + Pickle pickle; + histogram->SerializeInfo(&pickle); + + PickleIterator iter(pickle); + + int type; + EXPECT_TRUE(iter.ReadInt(&type)); + EXPECT_EQ(SPARSE_HISTOGRAM, type); + + std::string name; + EXPECT_TRUE(iter.ReadString(&name)); + EXPECT_EQ("Sparse", name); + + int flag; + EXPECT_TRUE(iter.ReadInt(&flag)); + EXPECT_EQ(HistogramBase::kIPCSerializationSourceFlag, flag); + + // No more data in the pickle. + EXPECT_FALSE(iter.SkipBytes(1)); +} + +} // namespace base diff --git a/base/metrics/statistics_recorder.cc b/base/metrics/statistics_recorder.cc new file mode 100644 index 0000000000..f23c81054c --- /dev/null +++ b/base/metrics/statistics_recorder.cc @@ -0,0 +1,292 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/metrics/statistics_recorder.h" + +#include "base/at_exit.h" +#include "base/debug/leak_annotations.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/metrics/histogram.h" +#include "base/strings/stringprintf.h" +#include "base/synchronization/lock.h" + +using std::list; +using std::string; + +namespace { +// Initialize histogram statistics gathering system. +base::LazyInstance::Leaky g_statistics_recorder_ = + LAZY_INSTANCE_INITIALIZER; +} // namespace + +namespace base { + +// static +void StatisticsRecorder::Initialize() { + // Ensure that an instance of the StatisticsRecorder object is created. + g_statistics_recorder_.Get(); +} + + +// static +bool StatisticsRecorder::IsActive() { + if (lock_ == NULL) + return false; + base::AutoLock auto_lock(*lock_); + return NULL != histograms_; +} + +// static +HistogramBase* StatisticsRecorder::RegisterOrDeleteDuplicate( + HistogramBase* histogram) { + // As per crbug.com/79322 the histograms are intentionally leaked, so we need + // to annotate them. Because ANNOTATE_LEAKING_OBJECT_PTR may be used only once + // for an object, the duplicates should not be annotated. + // Callers are responsible for not calling RegisterOrDeleteDuplicate(ptr) + // twice if (lock_ == NULL) || (!histograms_). + if (lock_ == NULL) { + ANNOTATE_LEAKING_OBJECT_PTR(histogram); // see crbug.com/79322 + return histogram; + } + + HistogramBase* histogram_to_delete = NULL; + HistogramBase* histogram_to_return = NULL; + { + base::AutoLock auto_lock(*lock_); + if (histograms_ == NULL) { + histogram_to_return = histogram; + } else { + const string& name = histogram->histogram_name(); + HistogramMap::iterator it = histograms_->find(name); + if (histograms_->end() == it) { + (*histograms_)[name] = histogram; + ANNOTATE_LEAKING_OBJECT_PTR(histogram); // see crbug.com/79322 + histogram_to_return = histogram; + } else if (histogram == it->second) { + // The histogram was registered before. + histogram_to_return = histogram; + } else { + // We already have one histogram with this name. + histogram_to_return = it->second; + histogram_to_delete = histogram; + } + } + } + delete histogram_to_delete; + return histogram_to_return; +} + +// static +const BucketRanges* StatisticsRecorder::RegisterOrDeleteDuplicateRanges( + const BucketRanges* ranges) { + DCHECK(ranges->HasValidChecksum()); + scoped_ptr ranges_deleter; + + if (lock_ == NULL) { + ANNOTATE_LEAKING_OBJECT_PTR(ranges); + return ranges; + } + + base::AutoLock auto_lock(*lock_); + if (ranges_ == NULL) { + ANNOTATE_LEAKING_OBJECT_PTR(ranges); + return ranges; + } + + list* checksum_matching_list; + RangesMap::iterator ranges_it = ranges_->find(ranges->checksum()); + if (ranges_->end() == ranges_it) { + // Add a new matching list to map. + checksum_matching_list = new list(); + ANNOTATE_LEAKING_OBJECT_PTR(checksum_matching_list); + (*ranges_)[ranges->checksum()] = checksum_matching_list; + } else { + checksum_matching_list = ranges_it->second; + } + + list::iterator checksum_matching_list_it; + for (checksum_matching_list_it = checksum_matching_list->begin(); + checksum_matching_list_it != checksum_matching_list->end(); + ++checksum_matching_list_it) { + const BucketRanges* existing_ranges = *checksum_matching_list_it; + if (existing_ranges->Equals(ranges)) { + if (existing_ranges == ranges) { + return ranges; + } else { + ranges_deleter.reset(ranges); + return existing_ranges; + } + } + } + // We haven't found a BucketRanges which has the same ranges. Register the + // new BucketRanges. + checksum_matching_list->push_front(ranges); + return ranges; +} + +// static +void StatisticsRecorder::WriteHTMLGraph(const std::string& query, + std::string* output) { + if (!IsActive()) + return; + + Histograms snapshot; + GetSnapshot(query, &snapshot); + for (Histograms::iterator it = snapshot.begin(); + it != snapshot.end(); + ++it) { + (*it)->WriteHTMLGraph(output); + output->append("


"); + } +} + +// static +void StatisticsRecorder::WriteGraph(const std::string& query, + std::string* output) { + if (!IsActive()) + return; + if (query.length()) + StringAppendF(output, "Collections of histograms for %s\n", query.c_str()); + else + output->append("Collections of all histograms\n"); + + Histograms snapshot; + GetSnapshot(query, &snapshot); + for (Histograms::iterator it = snapshot.begin(); + it != snapshot.end(); + ++it) { + (*it)->WriteAscii(output); + output->append("\n"); + } +} + +// static +void StatisticsRecorder::GetHistograms(Histograms* output) { + if (lock_ == NULL) + return; + base::AutoLock auto_lock(*lock_); + if (histograms_ == NULL) + return; + + for (HistogramMap::iterator it = histograms_->begin(); + histograms_->end() != it; + ++it) { + DCHECK_EQ(it->first, it->second->histogram_name()); + output->push_back(it->second); + } +} + +// static +void StatisticsRecorder::GetBucketRanges( + std::vector* output) { + if (lock_ == NULL) + return; + base::AutoLock auto_lock(*lock_); + if (ranges_ == NULL) + return; + + for (RangesMap::iterator it = ranges_->begin(); + ranges_->end() != it; + ++it) { + list* ranges_list = it->second; + list::iterator ranges_list_it; + for (ranges_list_it = ranges_list->begin(); + ranges_list_it != ranges_list->end(); + ++ranges_list_it) { + output->push_back(*ranges_list_it); + } + } +} + +// static +HistogramBase* StatisticsRecorder::FindHistogram(const std::string& name) { + if (lock_ == NULL) + return NULL; + base::AutoLock auto_lock(*lock_); + if (histograms_ == NULL) + return NULL; + + HistogramMap::iterator it = histograms_->find(name); + if (histograms_->end() == it) + return NULL; + return it->second; +} + +// private static +void StatisticsRecorder::GetSnapshot(const std::string& query, + Histograms* snapshot) { + if (lock_ == NULL) + return; + base::AutoLock auto_lock(*lock_); + if (histograms_ == NULL) + return; + + for (HistogramMap::iterator it = histograms_->begin(); + histograms_->end() != it; + ++it) { + if (it->first.find(query) != std::string::npos) + snapshot->push_back(it->second); + } +} + +// This singleton instance should be started during the single threaded portion +// of main(), and hence it is not thread safe. It initializes globals to +// provide support for all future calls. +StatisticsRecorder::StatisticsRecorder() { + DCHECK(!histograms_); + if (lock_ == NULL) { + // This will leak on purpose. It's the only way to make sure we won't race + // against the static uninitialization of the module while one of our + // static methods relying on the lock get called at an inappropriate time + // during the termination phase. Since it's a static data member, we will + // leak one per process, which would be similar to the instance allocated + // during static initialization and released only on process termination. + lock_ = new base::Lock; + } + base::AutoLock auto_lock(*lock_); + histograms_ = new HistogramMap; + ranges_ = new RangesMap; + + if (VLOG_IS_ON(1)) + AtExitManager::RegisterCallback(&DumpHistogramsToVlog, this); +} + +// static +void StatisticsRecorder::DumpHistogramsToVlog(void* instance) { + DCHECK(VLOG_IS_ON(1)); + + StatisticsRecorder* me = reinterpret_cast(instance); + string output; + me->WriteGraph(std::string(), &output); + VLOG(1) << output; +} + +StatisticsRecorder::~StatisticsRecorder() { + DCHECK(histograms_ && ranges_ && lock_); + + // Clean up. + scoped_ptr histograms_deleter; + scoped_ptr ranges_deleter; + // We don't delete lock_ on purpose to avoid having to properly protect + // against it going away after we checked for NULL in the static methods. + { + base::AutoLock auto_lock(*lock_); + histograms_deleter.reset(histograms_); + ranges_deleter.reset(ranges_); + histograms_ = NULL; + ranges_ = NULL; + } + // We are going to leak the histograms and the ranges. +} + + +// static +StatisticsRecorder::HistogramMap* StatisticsRecorder::histograms_ = NULL; +// static +StatisticsRecorder::RangesMap* StatisticsRecorder::ranges_ = NULL; +// static +base::Lock* StatisticsRecorder::lock_ = NULL; + +} // namespace base diff --git a/base/metrics/statistics_recorder.h b/base/metrics/statistics_recorder.h new file mode 100644 index 0000000000..9a55225324 --- /dev/null +++ b/base/metrics/statistics_recorder.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. + +// StatisticsRecorder holds all Histograms and BucketRanges that are used by +// Histograms in the system. It provides a general place for +// Histograms/BucketRanges to register, and supports a global API for accessing +// (i.e., dumping, or graphing) the data. + +#ifndef BASE_METRICS_STATISTICS_RECORDER_H_ +#define BASE_METRICS_STATISTICS_RECORDER_H_ + +#include +#include +#include +#include + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/lazy_instance.h" + +namespace base { + +class BucketRanges; +class HistogramBase; +class Lock; + +class BASE_EXPORT StatisticsRecorder { + public: + typedef std::vector Histograms; + + // Initializes the StatisticsRecorder system. + static void Initialize(); + + // Find out if histograms can now be registered into our list. + static bool IsActive(); + + // Register, or add a new histogram to the collection of statistics. If an + // identically named histogram is already registered, then the argument + // |histogram| will deleted. The returned value is always the registered + // histogram (either the argument, or the pre-existing registered histogram). + static HistogramBase* RegisterOrDeleteDuplicate(HistogramBase* histogram); + + // Register, or add a new BucketRanges. If an identically BucketRanges is + // already registered, then the argument |ranges| will deleted. The returned + // value is always the registered BucketRanges (either the argument, or the + // pre-existing one). + static const BucketRanges* RegisterOrDeleteDuplicateRanges( + const BucketRanges* ranges); + + // Methods for printing histograms. Only histograms which have query as + // a substring are written to output (an empty string will process all + // registered histograms). + static void WriteHTMLGraph(const std::string& query, std::string* output); + static void WriteGraph(const std::string& query, std::string* output); + + // Method for extracting histograms which were marked for use by UMA. + static void GetHistograms(Histograms* output); + + // Method for extracting BucketRanges used by all histograms registered. + static void GetBucketRanges(std::vector* output); + + // Find a histogram by name. It matches the exact name. This method is thread + // safe. It returns NULL if a matching histogram is not found. + static HistogramBase* FindHistogram(const std::string& name); + + // GetSnapshot copies some of the pointers to registered histograms into the + // caller supplied vector (Histograms). Only histograms with names matching + // query are returned. The query must be a substring of histogram name for its + // pointer to be copied. + static void GetSnapshot(const std::string& query, Histograms* snapshot); + + private: + // We keep all registered histograms in a map, from name to histogram. + typedef std::map HistogramMap; + + // We keep all |bucket_ranges_| in a map, from checksum to a list of + // |bucket_ranges_|. Checksum is calculated from the |ranges_| in + // |bucket_ranges_|. + typedef std::map*> RangesMap; + + friend struct DefaultLazyInstanceTraits; + friend class HistogramBaseTest; + friend class HistogramTest; + friend class SparseHistogramTest; + friend class StatisticsRecorderTest; + + // The constructor just initializes static members. Usually client code should + // use Initialize to do this. But in test code, you can friend this class and + // call destructor/constructor to get a clean StatisticsRecorder. + StatisticsRecorder(); + ~StatisticsRecorder(); + + static void DumpHistogramsToVlog(void* instance); + + static HistogramMap* histograms_; + static RangesMap* ranges_; + + // Lock protects access to above maps. + static base::Lock* lock_; + + DISALLOW_COPY_AND_ASSIGN(StatisticsRecorder); +}; + +} // namespace base + +#endif // BASE_METRICS_STATISTICS_RECORDER_H_ diff --git a/base/metrics/statistics_recorder_unittest.cc b/base/metrics/statistics_recorder_unittest.cc new file mode 100644 index 0000000000..22504ddfdd --- /dev/null +++ b/base/metrics/statistics_recorder_unittest.cc @@ -0,0 +1,266 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/memory/scoped_ptr.h" +#include "base/metrics/histogram.h" +#include "base/metrics/statistics_recorder.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +class StatisticsRecorderTest : public testing::Test { + protected: + virtual void SetUp() { + // Each test will have a clean state (no Histogram / BucketRanges + // registered). + InitializeStatisticsRecorder(); + } + + virtual void TearDown() { + UninitializeStatisticsRecorder(); + } + + void InitializeStatisticsRecorder() { + statistics_recorder_ = new StatisticsRecorder(); + } + + void UninitializeStatisticsRecorder() { + delete statistics_recorder_; + statistics_recorder_ = NULL; + } + + Histogram* CreateHistogram(const std::string& name, + HistogramBase::Sample min, + HistogramBase::Sample max, + size_t bucket_count) { + BucketRanges* ranges = new BucketRanges(bucket_count + 1); + Histogram::InitializeBucketRanges(min, max, ranges); + const BucketRanges* registered_ranges = + StatisticsRecorder::RegisterOrDeleteDuplicateRanges(ranges); + return new Histogram(name, min, max, registered_ranges); + } + + void DeleteHistogram(HistogramBase* histogram) { + delete histogram; + } + + StatisticsRecorder* statistics_recorder_; +}; + +TEST_F(StatisticsRecorderTest, NotInitialized) { + UninitializeStatisticsRecorder(); + + ASSERT_FALSE(StatisticsRecorder::IsActive()); + + StatisticsRecorder::Histograms registered_histograms; + std::vector registered_ranges; + + StatisticsRecorder::GetHistograms(®istered_histograms); + EXPECT_EQ(0u, registered_histograms.size()); + + Histogram* histogram = CreateHistogram("TestHistogram", 1, 1000, 10); + + // When StatisticsRecorder is not initialized, register is a noop. + EXPECT_EQ(histogram, + StatisticsRecorder::RegisterOrDeleteDuplicate(histogram)); + // Manually delete histogram that was not registered. + DeleteHistogram(histogram); + + // RegisterOrDeleteDuplicateRanges is a no-op. + BucketRanges* ranges = new BucketRanges(3);; + ranges->ResetChecksum(); + EXPECT_EQ(ranges, + StatisticsRecorder::RegisterOrDeleteDuplicateRanges(ranges)); + StatisticsRecorder::GetBucketRanges(®istered_ranges); + EXPECT_EQ(0u, registered_ranges.size()); +} + +TEST_F(StatisticsRecorderTest, RegisterBucketRanges) { + std::vector registered_ranges; + + BucketRanges* ranges1 = new BucketRanges(3);; + ranges1->ResetChecksum(); + BucketRanges* ranges2 = new BucketRanges(4);; + ranges2->ResetChecksum(); + + // Register new ranges. + EXPECT_EQ(ranges1, + StatisticsRecorder::RegisterOrDeleteDuplicateRanges(ranges1)); + EXPECT_EQ(ranges2, + StatisticsRecorder::RegisterOrDeleteDuplicateRanges(ranges2)); + StatisticsRecorder::GetBucketRanges(®istered_ranges); + ASSERT_EQ(2u, registered_ranges.size()); + + // Register some ranges again. + EXPECT_EQ(ranges1, + StatisticsRecorder::RegisterOrDeleteDuplicateRanges(ranges1)); + registered_ranges.clear(); + StatisticsRecorder::GetBucketRanges(®istered_ranges); + ASSERT_EQ(2u, registered_ranges.size()); + // Make sure the ranges is still the one we know. + ASSERT_EQ(3u, ranges1->size()); + EXPECT_EQ(0, ranges1->range(0)); + EXPECT_EQ(0, ranges1->range(1)); + EXPECT_EQ(0, ranges1->range(2)); + + // Register ranges with same values. + BucketRanges* ranges3 = new BucketRanges(3);; + ranges3->ResetChecksum(); + EXPECT_EQ(ranges1, // returning ranges1 + StatisticsRecorder::RegisterOrDeleteDuplicateRanges(ranges3)); + registered_ranges.clear(); + StatisticsRecorder::GetBucketRanges(®istered_ranges); + ASSERT_EQ(2u, registered_ranges.size()); +} + +TEST_F(StatisticsRecorderTest, RegisterHistogram) { + // Create a Histogram that was not registered. + Histogram* histogram = CreateHistogram("TestHistogram", 1, 1000, 10); + + StatisticsRecorder::Histograms registered_histograms; + StatisticsRecorder::GetHistograms(®istered_histograms); + EXPECT_EQ(0u, registered_histograms.size()); + + // Register the Histogram. + EXPECT_EQ(histogram, + StatisticsRecorder::RegisterOrDeleteDuplicate(histogram)); + StatisticsRecorder::GetHistograms(®istered_histograms); + EXPECT_EQ(1u, registered_histograms.size()); + + // Register the same Histogram again. + EXPECT_EQ(histogram, + StatisticsRecorder::RegisterOrDeleteDuplicate(histogram)); + registered_histograms.clear(); + StatisticsRecorder::GetHistograms(®istered_histograms); + EXPECT_EQ(1u, registered_histograms.size()); +} + +TEST_F(StatisticsRecorderTest, FindHistogram) { + HistogramBase* histogram1 = Histogram::FactoryGet( + "TestHistogram1", 1, 1000, 10, HistogramBase::kNoFlags); + HistogramBase* histogram2 = Histogram::FactoryGet( + "TestHistogram2", 1, 1000, 10, HistogramBase::kNoFlags); + + EXPECT_EQ(histogram1, StatisticsRecorder::FindHistogram("TestHistogram1")); + EXPECT_EQ(histogram2, StatisticsRecorder::FindHistogram("TestHistogram2")); + EXPECT_TRUE(StatisticsRecorder::FindHistogram("TestHistogram") == NULL); +} + +TEST_F(StatisticsRecorderTest, GetSnapshot) { + Histogram::FactoryGet("TestHistogram1", 1, 1000, 10, Histogram::kNoFlags); + Histogram::FactoryGet("TestHistogram2", 1, 1000, 10, Histogram::kNoFlags); + Histogram::FactoryGet("TestHistogram3", 1, 1000, 10, Histogram::kNoFlags); + + StatisticsRecorder::Histograms snapshot; + StatisticsRecorder::GetSnapshot("Test", &snapshot); + EXPECT_EQ(3u, snapshot.size()); + + snapshot.clear(); + StatisticsRecorder::GetSnapshot("1", &snapshot); + EXPECT_EQ(1u, snapshot.size()); + + snapshot.clear(); + StatisticsRecorder::GetSnapshot("hello", &snapshot); + EXPECT_EQ(0u, snapshot.size()); +} + +TEST_F(StatisticsRecorderTest, RegisterHistogramWithFactoryGet) { + StatisticsRecorder::Histograms registered_histograms; + + StatisticsRecorder::GetHistograms(®istered_histograms); + ASSERT_EQ(0u, registered_histograms.size()); + + // Create a histogram. + HistogramBase* histogram = Histogram::FactoryGet( + "TestHistogram", 1, 1000, 10, HistogramBase::kNoFlags); + registered_histograms.clear(); + StatisticsRecorder::GetHistograms(®istered_histograms); + EXPECT_EQ(1u, registered_histograms.size()); + + // Get an existing histogram. + HistogramBase* histogram2 = Histogram::FactoryGet( + "TestHistogram", 1, 1000, 10, HistogramBase::kNoFlags); + registered_histograms.clear(); + StatisticsRecorder::GetHistograms(®istered_histograms); + EXPECT_EQ(1u, registered_histograms.size()); + EXPECT_EQ(histogram, histogram2); + + // Create a LinearHistogram. + histogram = LinearHistogram::FactoryGet( + "TestLinearHistogram", 1, 1000, 10, HistogramBase::kNoFlags); + registered_histograms.clear(); + StatisticsRecorder::GetHistograms(®istered_histograms); + EXPECT_EQ(2u, registered_histograms.size()); + + // Create a BooleanHistogram. + histogram = BooleanHistogram::FactoryGet( + "TestBooleanHistogram", HistogramBase::kNoFlags); + registered_histograms.clear(); + StatisticsRecorder::GetHistograms(®istered_histograms); + EXPECT_EQ(3u, registered_histograms.size()); + + // Create a CustomHistogram. + std::vector custom_ranges; + custom_ranges.push_back(1); + custom_ranges.push_back(5); + histogram = CustomHistogram::FactoryGet( + "TestCustomHistogram", custom_ranges, HistogramBase::kNoFlags); + registered_histograms.clear(); + StatisticsRecorder::GetHistograms(®istered_histograms); + EXPECT_EQ(4u, registered_histograms.size()); +} + +TEST_F(StatisticsRecorderTest, RegisterHistogramWithMacros) { + StatisticsRecorder::Histograms registered_histograms; + + HistogramBase* histogram = Histogram::FactoryGet( + "TestHistogramCounts", 1, 1000000, 50, HistogramBase::kNoFlags); + + // The histogram we got from macro is the same as from FactoryGet. + HISTOGRAM_COUNTS("TestHistogramCounts", 30); + registered_histograms.clear(); + StatisticsRecorder::GetHistograms(®istered_histograms); + ASSERT_EQ(1u, registered_histograms.size()); + EXPECT_EQ(histogram, registered_histograms[0]); + + HISTOGRAM_TIMES("TestHistogramTimes", TimeDelta::FromDays(1)); + HISTOGRAM_ENUMERATION("TestHistogramEnumeration", 20, 200); + + registered_histograms.clear(); + StatisticsRecorder::GetHistograms(®istered_histograms); + EXPECT_EQ(3u, registered_histograms.size()); + + // Debugging only macros. + DHISTOGRAM_TIMES("TestHistogramDebugTimes", TimeDelta::FromDays(1)); + DHISTOGRAM_COUNTS("TestHistogramDebugCounts", 30); + registered_histograms.clear(); + StatisticsRecorder::GetHistograms(®istered_histograms); +#ifndef NDEBUG + EXPECT_EQ(5u, registered_histograms.size()); +#else + EXPECT_EQ(3u, registered_histograms.size()); +#endif +} + +TEST_F(StatisticsRecorderTest, BucketRangesSharing) { + std::vector ranges; + StatisticsRecorder::GetBucketRanges(&ranges); + EXPECT_EQ(0u, ranges.size()); + + Histogram::FactoryGet("Histogram", 1, 64, 8, HistogramBase::kNoFlags); + Histogram::FactoryGet("Histogram2", 1, 64, 8, HistogramBase::kNoFlags); + + StatisticsRecorder::GetBucketRanges(&ranges); + EXPECT_EQ(1u, ranges.size()); + + Histogram::FactoryGet("Histogram3", 1, 64, 16, HistogramBase::kNoFlags); + + ranges.clear(); + StatisticsRecorder::GetBucketRanges(&ranges); + EXPECT_EQ(2u, ranges.size()); +} + +} // namespace base diff --git a/base/metrics/stats_counters.cc b/base/metrics/stats_counters.cc new file mode 100644 index 0000000000..12416d9f0f --- /dev/null +++ b/base/metrics/stats_counters.cc @@ -0,0 +1,125 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/metrics/stats_counters.h" + +namespace base { + +StatsCounter::StatsCounter(const std::string& name) + : counter_id_(-1) { + // We prepend the name with 'c:' to indicate that it is a counter. + if (StatsTable::current()) { + // TODO(mbelshe): name_ construction is racy and it may corrupt memory for + // static. + name_ = "c:"; + name_.append(name); + } +} + +StatsCounter::~StatsCounter() { +} + +void StatsCounter::Set(int value) { + int* loc = GetPtr(); + if (loc) + *loc = value; +} + +void StatsCounter::Add(int value) { + int* loc = GetPtr(); + if (loc) + (*loc) += value; +} + +StatsCounter::StatsCounter() + : counter_id_(-1) { +} + +int* StatsCounter::GetPtr() { + StatsTable* table = StatsTable::current(); + if (!table) + return NULL; + + // If counter_id_ is -1, then we haven't looked it up yet. + if (counter_id_ == -1) { + counter_id_ = table->FindCounter(name_); + if (table->GetSlot() == 0) { + if (!table->RegisterThread(std::string())) { + // There is no room for this thread. This thread + // cannot use counters. + counter_id_ = 0; + return NULL; + } + } + } + + // If counter_id_ is > 0, then we have a valid counter. + if (counter_id_ > 0) + return table->GetLocation(counter_id_, table->GetSlot()); + + // counter_id_ was zero, which means the table is full. + return NULL; +} + + +StatsCounterTimer::StatsCounterTimer(const std::string& name) { + // we prepend the name with 't:' to indicate that it is a timer. + if (StatsTable::current()) { + // TODO(mbelshe): name_ construction is racy and it may corrupt memory for + // static. + name_ = "t:"; + name_.append(name); + } +} + +StatsCounterTimer::~StatsCounterTimer() { +} + +void StatsCounterTimer::Start() { + if (!Enabled()) + return; + start_time_ = TimeTicks::Now(); + stop_time_ = TimeTicks(); +} + +// Stop the timer and record the results. +void StatsCounterTimer::Stop() { + if (!Enabled() || !Running()) + return; + stop_time_ = TimeTicks::Now(); + Record(); +} + +// Returns true if the timer is running. +bool StatsCounterTimer::Running() { + return Enabled() && !start_time_.is_null() && stop_time_.is_null(); +} + +// Accept a TimeDelta to increment. +void StatsCounterTimer::AddTime(TimeDelta time) { + Add(static_cast(time.InMilliseconds())); +} + +void StatsCounterTimer::Record() { + AddTime(stop_time_ - start_time_); +} + + +StatsRate::StatsRate(const std::string& name) + : StatsCounterTimer(name), + counter_(name), + largest_add_(std::string(" ").append(name).append("MAX")) { +} + +StatsRate::~StatsRate() { +} + +void StatsRate::Add(int value) { + counter_.Increment(); + StatsCounterTimer::Add(value); + if (value > largest_add_.value()) + largest_add_.Set(value); +} + +} // namespace base diff --git a/base/metrics/stats_counters.h b/base/metrics/stats_counters.h new file mode 100644 index 0000000000..d47bab3e89 --- /dev/null +++ b/base/metrics/stats_counters.h @@ -0,0 +1,197 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_METRICS_STATS_COUNTERS_H_ +#define BASE_METRICS_STATS_COUNTERS_H_ + +#include + +#include "base/base_export.h" +#include "base/compiler_specific.h" +#include "base/metrics/stats_table.h" +#include "base/time/time.h" + +namespace base { + +// StatsCounters are dynamically created values which can be tracked in +// the StatsTable. They are designed to be lightweight to create and +// easy to use. +// +// Since StatsCounters can be created dynamically by name, there is +// a hash table lookup to find the counter in the table. A StatsCounter +// object can be created once and used across multiple threads safely. +// +// Example usage: +// { +// StatsCounter request_count("RequestCount"); +// request_count.Increment(); +// } +// +// Note that creating counters on the stack does work, however creating +// the counter object requires a hash table lookup. For inner loops, it +// may be better to create the counter either as a member of another object +// (or otherwise outside of the loop) for maximum performance. +// +// Internally, a counter represents a value in a row of a StatsTable. +// The row has a 32bit value for each process/thread in the table and also +// a name (stored in the table metadata). +// +// NOTE: In order to make stats_counters usable in lots of different code, +// avoid any dependencies inside this header file. +// + +//------------------------------------------------------------------------------ +// Define macros for ease of use. They also allow us to change definitions +// as the implementation varies, or depending on compile options. +//------------------------------------------------------------------------------ +// First provide generic macros, which exist in production as well as debug. +#define STATS_COUNTER(name, delta) do { \ + base::StatsCounter counter(name); \ + counter.Add(delta); \ +} while (0) + +#define SIMPLE_STATS_COUNTER(name) STATS_COUNTER(name, 1) + +#define RATE_COUNTER(name, duration) do { \ + base::StatsRate hit_count(name); \ + hit_count.AddTime(duration); \ +} while (0) + +// Define Debug vs non-debug flavors of macros. +#ifndef NDEBUG + +#define DSTATS_COUNTER(name, delta) STATS_COUNTER(name, delta) +#define DSIMPLE_STATS_COUNTER(name) SIMPLE_STATS_COUNTER(name) +#define DRATE_COUNTER(name, duration) RATE_COUNTER(name, duration) + +#else // NDEBUG + +#define DSTATS_COUNTER(name, delta) do {} while (0) +#define DSIMPLE_STATS_COUNTER(name) do {} while (0) +#define DRATE_COUNTER(name, duration) do {} while (0) + +#endif // NDEBUG + +//------------------------------------------------------------------------------ +// StatsCounter represents a counter in the StatsTable class. +class BASE_EXPORT StatsCounter { + public: + // Create a StatsCounter object. + explicit StatsCounter(const std::string& name); + virtual ~StatsCounter(); + + // Sets the counter to a specific value. + void Set(int value); + + // Increments the counter. + void Increment() { + Add(1); + } + + virtual void Add(int value); + + // Decrements the counter. + void Decrement() { + Add(-1); + } + + void Subtract(int value) { + Add(-value); + } + + // Is this counter enabled? + // Returns false if table is full. + bool Enabled() { + return GetPtr() != NULL; + } + + int value() { + int* loc = GetPtr(); + if (loc) return *loc; + return 0; + } + + protected: + StatsCounter(); + + // Returns the cached address of this counter location. + int* GetPtr(); + + std::string name_; + // The counter id in the table. We initialize to -1 (an invalid value) + // and then cache it once it has been looked up. The counter_id is + // valid across all threads and processes. + int32 counter_id_; +}; + + +// A StatsCounterTimer is a StatsCounter which keeps a timer during +// the scope of the StatsCounterTimer. On destruction, it will record +// its time measurement. +class BASE_EXPORT StatsCounterTimer : protected StatsCounter { + public: + // Constructs and starts the timer. + explicit StatsCounterTimer(const std::string& name); + virtual ~StatsCounterTimer(); + + // Start the timer. + void Start(); + + // Stop the timer and record the results. + void Stop(); + + // Returns true if the timer is running. + bool Running(); + + // Accept a TimeDelta to increment. + virtual void AddTime(TimeDelta time); + + protected: + // Compute the delta between start and stop, in milliseconds. + void Record(); + + TimeTicks start_time_; + TimeTicks stop_time_; +}; + +// A StatsRate is a timer that keeps a count of the number of intervals added so +// that several statistics can be produced: +// min, max, avg, count, total +class BASE_EXPORT StatsRate : public StatsCounterTimer { + public: + // Constructs and starts the timer. + explicit StatsRate(const std::string& name); + virtual ~StatsRate(); + + virtual void Add(int value) OVERRIDE; + + private: + StatsCounter counter_; + StatsCounter largest_add_; +}; + + +// Helper class for scoping a timer or rate. +template class StatsScope { + public: + explicit StatsScope(T& timer) + : timer_(timer) { + timer_.Start(); + } + + ~StatsScope() { + timer_.Stop(); + } + + void Stop() { + timer_.Stop(); + } + + private: + T& timer_; +}; + +} // namespace base + +#endif // BASE_METRICS_STATS_COUNTERS_H_ diff --git a/base/metrics/stats_table.cc b/base/metrics/stats_table.cc new file mode 100644 index 0000000000..4a3d939cfe --- /dev/null +++ b/base/metrics/stats_table.cc @@ -0,0 +1,568 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/metrics/stats_table.h" + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/shared_memory.h" +#include "base/process/process_handle.h" +#include "base/strings/string_piece.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/platform_thread.h" +#include "base/threading/thread_local_storage.h" + +#if defined(OS_POSIX) +#include "errno.h" +#endif + +namespace base { + +// The StatsTable uses a shared memory segment that is laid out as follows +// +// +-------------------------------------------+ +// | Version | Size | MaxCounters | MaxThreads | +// +-------------------------------------------+ +// | Thread names table | +// +-------------------------------------------+ +// | Thread TID table | +// +-------------------------------------------+ +// | Thread PID table | +// +-------------------------------------------+ +// | Counter names table | +// +-------------------------------------------+ +// | Data | +// +-------------------------------------------+ +// +// The data layout is a grid, where the columns are the thread_ids and the +// rows are the counter_ids. +// +// If the first character of the thread_name is '\0', then that column is +// empty. +// If the first character of the counter_name is '\0', then that row is +// empty. +// +// About Locking: +// This class is designed to be both multi-thread and multi-process safe. +// Aside from initialization, this is done by partitioning the data which +// each thread uses so that no locking is required. However, to allocate +// the rows and columns of the table to particular threads, locking is +// required. +// +// At the shared-memory level, we have a lock. This lock protects the +// shared-memory table only, and is used when we create new counters (e.g. +// use rows) or when we register new threads (e.g. use columns). Reading +// data from the table does not require any locking at the shared memory +// level. +// +// Each process which accesses the table will create a StatsTable object. +// The StatsTable maintains a hash table of the existing counters in the +// table for faster lookup. Since the hash table is process specific, +// each process maintains its own cache. We avoid complexity here by never +// de-allocating from the hash table. (Counters are dynamically added, +// but not dynamically removed). + +// In order for external viewers to be able to read our shared memory, +// we all need to use the same size ints. +COMPILE_ASSERT(sizeof(int)==4, expect_4_byte_ints); + +namespace { + +// An internal version in case we ever change the format of this +// file, and so that we can identify our table. +const int kTableVersion = 0x13131313; + +// The name for un-named counters and threads in the table. +const char kUnknownName[] = ""; + +// Calculates delta to align an offset to the size of an int +inline int AlignOffset(int offset) { + return (sizeof(int) - (offset % sizeof(int))) % sizeof(int); +} + +inline int AlignedSize(int size) { + return size + AlignOffset(size); +} + +} // namespace + +// The StatsTable::Private maintains convenience pointers into the +// shared memory segment. Use this class to keep the data structure +// clean and accessible. +class StatsTable::Private { + public: + // Various header information contained in the memory mapped segment. + struct TableHeader { + int version; + int size; + int max_counters; + int max_threads; + }; + + // Construct a new Private based on expected size parameters, or + // return NULL on failure. + static Private* New(const std::string& name, int size, + int max_threads, int max_counters); + + SharedMemory* shared_memory() { return &shared_memory_; } + + // Accessors for our header pointers + TableHeader* table_header() const { return table_header_; } + int version() const { return table_header_->version; } + int size() const { return table_header_->size; } + int max_counters() const { return table_header_->max_counters; } + int max_threads() const { return table_header_->max_threads; } + + // Accessors for our tables + char* thread_name(int slot_id) const { + return &thread_names_table_[ + (slot_id-1) * (StatsTable::kMaxThreadNameLength)]; + } + PlatformThreadId* thread_tid(int slot_id) const { + return &(thread_tid_table_[slot_id-1]); + } + int* thread_pid(int slot_id) const { + return &(thread_pid_table_[slot_id-1]); + } + char* counter_name(int counter_id) const { + return &counter_names_table_[ + (counter_id-1) * (StatsTable::kMaxCounterNameLength)]; + } + int* row(int counter_id) const { + return &data_table_[(counter_id-1) * max_threads()]; + } + + private: + // Constructor is private because you should use New() instead. + Private() + : table_header_(NULL), + thread_names_table_(NULL), + thread_tid_table_(NULL), + thread_pid_table_(NULL), + counter_names_table_(NULL), + data_table_(NULL) { + } + + // Initializes the table on first access. Sets header values + // appropriately and zeroes all counters. + void InitializeTable(void* memory, int size, int max_counters, + int max_threads); + + // Initializes our in-memory pointers into a pre-created StatsTable. + void ComputeMappedPointers(void* memory); + + SharedMemory shared_memory_; + TableHeader* table_header_; + char* thread_names_table_; + PlatformThreadId* thread_tid_table_; + int* thread_pid_table_; + char* counter_names_table_; + int* data_table_; +}; + +// static +StatsTable::Private* StatsTable::Private::New(const std::string& name, + int size, + int max_threads, + int max_counters) { + scoped_ptr priv(new Private()); + if (!priv->shared_memory_.CreateNamed(name, true, size)) + return NULL; + if (!priv->shared_memory_.Map(size)) + return NULL; + void* memory = priv->shared_memory_.memory(); + + TableHeader* header = static_cast(memory); + + // If the version does not match, then assume the table needs + // to be initialized. + if (header->version != kTableVersion) + priv->InitializeTable(memory, size, max_counters, max_threads); + + // We have a valid table, so compute our pointers. + priv->ComputeMappedPointers(memory); + + return priv.release(); +} + +void StatsTable::Private::InitializeTable(void* memory, int size, + int max_counters, + int max_threads) { + // Zero everything. + memset(memory, 0, size); + + // Initialize the header. + TableHeader* header = static_cast(memory); + header->version = kTableVersion; + header->size = size; + header->max_counters = max_counters; + header->max_threads = max_threads; +} + +void StatsTable::Private::ComputeMappedPointers(void* memory) { + char* data = static_cast(memory); + int offset = 0; + + table_header_ = reinterpret_cast(data); + offset += sizeof(*table_header_); + offset += AlignOffset(offset); + + // Verify we're looking at a valid StatsTable. + DCHECK_EQ(table_header_->version, kTableVersion); + + thread_names_table_ = reinterpret_cast(data + offset); + offset += sizeof(char) * + max_threads() * StatsTable::kMaxThreadNameLength; + offset += AlignOffset(offset); + + thread_tid_table_ = reinterpret_cast(data + offset); + offset += sizeof(int) * max_threads(); + offset += AlignOffset(offset); + + thread_pid_table_ = reinterpret_cast(data + offset); + offset += sizeof(int) * max_threads(); + offset += AlignOffset(offset); + + counter_names_table_ = reinterpret_cast(data + offset); + offset += sizeof(char) * + max_counters() * StatsTable::kMaxCounterNameLength; + offset += AlignOffset(offset); + + data_table_ = reinterpret_cast(data + offset); + offset += sizeof(int) * max_threads() * max_counters(); + + DCHECK_EQ(offset, size()); +} + +// TLSData carries the data stored in the TLS slots for the +// StatsTable. This is used so that we can properly cleanup when the +// thread exits and return the table slot. +// +// Each thread that calls RegisterThread in the StatsTable will have +// a TLSData stored in its TLS. +struct StatsTable::TLSData { + StatsTable* table; + int slot; +}; + +// We keep a singleton table which can be easily accessed. +StatsTable* global_table = NULL; + +StatsTable::StatsTable(const std::string& name, int max_threads, + int max_counters) + : impl_(NULL), + tls_index_(SlotReturnFunction) { + int table_size = + AlignedSize(sizeof(Private::TableHeader)) + + AlignedSize((max_counters * sizeof(char) * kMaxCounterNameLength)) + + AlignedSize((max_threads * sizeof(char) * kMaxThreadNameLength)) + + AlignedSize(max_threads * sizeof(int)) + + AlignedSize(max_threads * sizeof(int)) + + AlignedSize((sizeof(int) * (max_counters * max_threads))); + + impl_ = Private::New(name, table_size, max_threads, max_counters); + + if (!impl_) + DPLOG(ERROR) << "StatsTable did not initialize"; +} + +StatsTable::~StatsTable() { + // Before we tear down our copy of the table, be sure to + // unregister our thread. + UnregisterThread(); + + // Return ThreadLocalStorage. At this point, if any registered threads + // still exist, they cannot Unregister. + tls_index_.Free(); + + // Cleanup our shared memory. + delete impl_; + + // If we are the global table, unregister ourselves. + if (global_table == this) + global_table = NULL; +} + +StatsTable* StatsTable::current() { + return global_table; +} + +void StatsTable::set_current(StatsTable* value) { + global_table = value; +} + +int StatsTable::GetSlot() const { + TLSData* data = GetTLSData(); + if (!data) + return 0; + return data->slot; +} + +int StatsTable::RegisterThread(const std::string& name) { + int slot = 0; + if (!impl_) + return 0; + + // Registering a thread requires that we lock the shared memory + // so that two threads don't grab the same slot. Fortunately, + // thread creation shouldn't happen in inner loops. + { + SharedMemoryAutoLock lock(impl_->shared_memory()); + slot = FindEmptyThread(); + if (!slot) { + return 0; + } + + // We have space, so consume a column in the table. + std::string thread_name = name; + if (name.empty()) + thread_name = kUnknownName; + strlcpy(impl_->thread_name(slot), thread_name.c_str(), + kMaxThreadNameLength); + *(impl_->thread_tid(slot)) = PlatformThread::CurrentId(); + *(impl_->thread_pid(slot)) = GetCurrentProcId(); + } + + // Set our thread local storage. + TLSData* data = new TLSData; + data->table = this; + data->slot = slot; + tls_index_.Set(data); + return slot; +} + +int StatsTable::CountThreadsRegistered() const { + if (!impl_) + return 0; + + // Loop through the shared memory and count the threads that are active. + // We intentionally do not lock the table during the operation. + int count = 0; + for (int index = 1; index <= impl_->max_threads(); index++) { + char* name = impl_->thread_name(index); + if (*name != '\0') + count++; + } + return count; +} + +int StatsTable::FindCounter(const std::string& name) { + // Note: the API returns counters numbered from 1..N, although + // internally, the array is 0..N-1. This is so that we can return + // zero as "not found". + if (!impl_) + return 0; + + // Create a scope for our auto-lock. + { + AutoLock scoped_lock(counters_lock_); + + // Attempt to find the counter. + CountersMap::const_iterator iter; + iter = counters_.find(name); + if (iter != counters_.end()) + return iter->second; + } + + // Counter does not exist, so add it. + return AddCounter(name); +} + +int* StatsTable::GetLocation(int counter_id, int slot_id) const { + if (!impl_) + return NULL; + if (slot_id > impl_->max_threads()) + return NULL; + + int* row = impl_->row(counter_id); + return &(row[slot_id-1]); +} + +const char* StatsTable::GetRowName(int index) const { + if (!impl_) + return NULL; + + return impl_->counter_name(index); +} + +int StatsTable::GetRowValue(int index) const { + return GetRowValue(index, 0); +} + +int StatsTable::GetRowValue(int index, int pid) const { + if (!impl_) + return 0; + + int rv = 0; + int* row = impl_->row(index); + for (int slot_id = 0; slot_id < impl_->max_threads(); slot_id++) { + if (pid == 0 || *impl_->thread_pid(slot_id) == pid) + rv += row[slot_id]; + } + return rv; +} + +int StatsTable::GetCounterValue(const std::string& name) { + return GetCounterValue(name, 0); +} + +int StatsTable::GetCounterValue(const std::string& name, int pid) { + if (!impl_) + return 0; + + int row = FindCounter(name); + if (!row) + return 0; + return GetRowValue(row, pid); +} + +int StatsTable::GetMaxCounters() const { + if (!impl_) + return 0; + return impl_->max_counters(); +} + +int StatsTable::GetMaxThreads() const { + if (!impl_) + return 0; + return impl_->max_threads(); +} + +int* StatsTable::FindLocation(const char* name) { + // Get the static StatsTable + StatsTable *table = StatsTable::current(); + if (!table) + return NULL; + + // Get the slot for this thread. Try to register + // it if none exists. + int slot = table->GetSlot(); + if (!slot && !(slot = table->RegisterThread(std::string()))) + return NULL; + + // Find the counter id for the counter. + std::string str_name(name); + int counter = table->FindCounter(str_name); + + // Now we can find the location in the table. + return table->GetLocation(counter, slot); +} + +void StatsTable::UnregisterThread() { + UnregisterThread(GetTLSData()); +} + +void StatsTable::UnregisterThread(TLSData* data) { + if (!data) + return; + DCHECK(impl_); + + // Mark the slot free by zeroing out the thread name. + char* name = impl_->thread_name(data->slot); + *name = '\0'; + + // Remove the calling thread's TLS so that it cannot use the slot. + tls_index_.Set(NULL); + delete data; +} + +void StatsTable::SlotReturnFunction(void* data) { + // This is called by the TLS destructor, which on some platforms has + // already cleared the TLS info, so use the tls_data argument + // rather than trying to fetch it ourselves. + TLSData* tls_data = static_cast(data); + if (tls_data) { + DCHECK(tls_data->table); + tls_data->table->UnregisterThread(tls_data); + } +} + +int StatsTable::FindEmptyThread() const { + // Note: the API returns slots numbered from 1..N, although + // internally, the array is 0..N-1. This is so that we can return + // zero as "not found". + // + // The reason for doing this is because the thread 'slot' is stored + // in TLS, which is always initialized to zero, not -1. If 0 were + // returned as a valid slot number, it would be confused with the + // uninitialized state. + if (!impl_) + return 0; + + int index = 1; + for (; index <= impl_->max_threads(); index++) { + char* name = impl_->thread_name(index); + if (!*name) + break; + } + if (index > impl_->max_threads()) + return 0; // The table is full. + return index; +} + +int StatsTable::FindCounterOrEmptyRow(const std::string& name) const { + // Note: the API returns slots numbered from 1..N, although + // internally, the array is 0..N-1. This is so that we can return + // zero as "not found". + // + // There isn't much reason for this other than to be consistent + // with the way we track columns for thread slots. (See comments + // in FindEmptyThread for why it is done this way). + if (!impl_) + return 0; + + int free_slot = 0; + for (int index = 1; index <= impl_->max_counters(); index++) { + char* row_name = impl_->counter_name(index); + if (!*row_name && !free_slot) + free_slot = index; // save that we found a free slot + else if (!strncmp(row_name, name.c_str(), kMaxCounterNameLength)) + return index; + } + return free_slot; +} + +int StatsTable::AddCounter(const std::string& name) { + if (!impl_) + return 0; + + int counter_id = 0; + { + // To add a counter to the shared memory, we need the + // shared memory lock. + SharedMemoryAutoLock lock(impl_->shared_memory()); + + // We have space, so create a new counter. + counter_id = FindCounterOrEmptyRow(name); + if (!counter_id) + return 0; + + std::string counter_name = name; + if (name.empty()) + counter_name = kUnknownName; + strlcpy(impl_->counter_name(counter_id), counter_name.c_str(), + kMaxCounterNameLength); + } + + // now add to our in-memory cache + { + AutoLock lock(counters_lock_); + counters_[name] = counter_id; + } + return counter_id; +} + +StatsTable::TLSData* StatsTable::GetTLSData() const { + TLSData* data = + static_cast(tls_index_.Get()); + if (!data) + return NULL; + + DCHECK(data->slot); + DCHECK_EQ(data->table, this); + return data; +} + +} // namespace base diff --git a/base/metrics/stats_table.h b/base/metrics/stats_table.h new file mode 100644 index 0000000000..153af38c92 --- /dev/null +++ b/base/metrics/stats_table.h @@ -0,0 +1,193 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// A StatsTable is a table of statistics. It can be used across multiple +// processes and threads, maintaining cheap statistics counters without +// locking. +// +// The goal is to make it very cheap and easy for developers to add +// counters to code, without having to build one-off utilities or mechanisms +// to track the counters, and also to allow a single "view" to display +// the contents of all counters. +// +// To achieve this, StatsTable creates a shared memory segment to store +// the data for the counters. Upon creation, it has a specific size +// which governs the maximum number of counters and concurrent +// threads/processes which can use it. +// + +#ifndef BASE_METRICS_STATS_TABLE_H_ +#define BASE_METRICS_STATS_TABLE_H_ + +#include + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/containers/hash_tables.h" +#include "base/synchronization/lock.h" +#include "base/threading/thread_local_storage.h" + +namespace base { + +class BASE_EXPORT StatsTable { + public: + // Create a new StatsTable. + // If a StatsTable already exists with the specified name, this StatsTable + // will use the same shared memory segment as the original. Otherwise, + // a new StatsTable is created and all counters are zeroed. + // + // name is the name of the StatsTable to use. + // + // max_threads is the maximum number of threads the table will support. + // If the StatsTable already exists, this number is ignored. + // + // max_counters is the maximum number of counters the table will support. + // If the StatsTable already exists, this number is ignored. + StatsTable(const std::string& name, int max_threads, int max_counters); + + // Destroys the StatsTable. When the last StatsTable is destroyed + // (across all processes), the StatsTable is removed from disk. + ~StatsTable(); + + // For convenience, we create a static table. This is generally + // used automatically by the counters. + static StatsTable* current(); + + // Set the global table for use in this process. + static void set_current(StatsTable* value); + + // Get the slot id for the calling thread. Returns 0 if no + // slot is assigned. + int GetSlot() const; + + // All threads that contribute data to the table must register with the + // table first. This function will set thread local storage for the + // thread containing the location in the table where this thread will + // write its counter data. + // + // name is just a debugging tag to label the thread, and it does not + // need to be unique. It will be truncated to kMaxThreadNameLength-1 + // characters. + // + // On success, returns the slot id for this thread. On failure, + // returns 0. + int RegisterThread(const std::string& name); + + // Returns the number of threads currently registered. This is really not + // useful except for diagnostics and debugging. + int CountThreadsRegistered() const; + + // Find a counter in the StatsTable. + // + // Returns an id for the counter which can be used to call GetLocation(). + // If the counter does not exist, attempts to create a row for the new + // counter. If there is no space in the table for the new counter, + // returns 0. + int FindCounter(const std::string& name); + + // TODO(mbelshe): implement RemoveCounter. + + // Gets the location of a particular value in the table based on + // the counter id and slot id. + int* GetLocation(int counter_id, int slot_id) const; + + // Gets the counter name at a particular row. If the row is empty, + // returns NULL. + const char* GetRowName(int index) const; + + // Gets the sum of the values for a particular row. + int GetRowValue(int index) const; + + // Gets the sum of the values for a particular row for a given pid. + int GetRowValue(int index, int pid) const; + + // Gets the sum of the values for a particular counter. If the counter + // does not exist, creates the counter. + int GetCounterValue(const std::string& name); + + // Gets the sum of the values for a particular counter for a given pid. + // If the counter does not exist, creates the counter. + int GetCounterValue(const std::string& name, int pid); + + // The maxinum number of counters/rows in the table. + int GetMaxCounters() const; + + // The maxinum number of threads/columns in the table. + int GetMaxThreads() const; + + // The maximum length (in characters) of a Thread's name including + // null terminator, as stored in the shared memory. + static const int kMaxThreadNameLength = 32; + + // The maximum length (in characters) of a Counter's name including + // null terminator, as stored in the shared memory. + static const int kMaxCounterNameLength = 64; + + // Convenience function to lookup a counter location for a + // counter by name for the calling thread. Will register + // the thread if it is not already registered. + static int* FindLocation(const char *name); + + private: + class Private; + struct TLSData; + typedef hash_map CountersMap; + + // Returns the space occupied by a thread in the table. Generally used + // if a thread terminates but the process continues. This function + // does not zero out the thread's counters. + // Cannot be used inside a posix tls destructor. + void UnregisterThread(); + + // This variant expects the tls data to be passed in, so it is safe to + // call from inside a posix tls destructor (see doc for pthread_key_create). + void UnregisterThread(TLSData* tls_data); + + // The SlotReturnFunction is called at thread exit for each thread + // which used the StatsTable. + static void SlotReturnFunction(void* data); + + // Locates a free slot in the table. Returns a number > 0 on success, + // or 0 on failure. The caller must hold the shared_memory lock when + // calling this function. + int FindEmptyThread() const; + + // Locates a counter in the table or finds an empty row. Returns a + // number > 0 on success, or 0 on failure. The caller must hold the + // shared_memory_lock when calling this function. + int FindCounterOrEmptyRow(const std::string& name) const; + + // Internal function to add a counter to the StatsTable. Assumes that + // the counter does not already exist in the table. + // + // name is a unique identifier for this counter, and will be truncated + // to kMaxCounterNameLength-1 characters. + // + // On success, returns the counter_id for the newly added counter. + // On failure, returns 0. + int AddCounter(const std::string& name); + + // Get the TLS data for the calling thread. Returns NULL if none is + // initialized. + TLSData* GetTLSData() const; + + Private* impl_; + + // The counters_lock_ protects the counters_ hash table. + base::Lock counters_lock_; + + // The counters_ hash map is an in-memory hash of the counters. + // It is used for quick lookup of counters, but is cannot be used + // as a substitute for what is in the shared memory. Even though + // we don't have a counter in our hash table, another process may + // have created it. + CountersMap counters_; + ThreadLocalStorage::Slot tls_index_; + + DISALLOW_COPY_AND_ASSIGN(StatsTable); +}; + +} // namespace base + +#endif // BASE_METRICS_STATS_TABLE_H_ diff --git a/base/metrics/stats_table_unittest.cc b/base/metrics/stats_table_unittest.cc new file mode 100644 index 0000000000..8fd33971f4 --- /dev/null +++ b/base/metrics/stats_table_unittest.cc @@ -0,0 +1,424 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/shared_memory.h" +#include "base/metrics/stats_counters.h" +#include "base/metrics/stats_table.h" +#include "base/process/kill.h" +#include "base/strings/string_piece.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/test/multiprocess_test.h" +#include "base/threading/platform_thread.h" +#include "base/threading/simple_thread.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/multiprocess_func_list.h" + +namespace base { + +class StatsTableTest : public MultiProcessTest { + public: + void DeleteShmem(const std::string& name) { + SharedMemory mem; + mem.Delete(name); + } +}; + +// Open a StatsTable and verify that we can write to each of the +// locations in the table. +TEST_F(StatsTableTest, VerifySlots) { + const std::string kTableName = "VerifySlotsStatTable"; + const int kMaxThreads = 1; + const int kMaxCounter = 5; + DeleteShmem(kTableName); + StatsTable table(kTableName, kMaxThreads, kMaxCounter); + + // Register a single thread. + std::string thread_name = "mainThread"; + int slot_id = table.RegisterThread(thread_name); + EXPECT_NE(slot_id, 0); + + // Fill up the table with counters. + std::string counter_base_name = "counter"; + for (int index = 0; index < kMaxCounter; index++) { + std::string counter_name = counter_base_name; + base::StringAppendF(&counter_name, "counter.ctr%d", index); + int counter_id = table.FindCounter(counter_name); + EXPECT_GT(counter_id, 0); + } + + // Try to allocate an additional thread. Verify it fails. + slot_id = table.RegisterThread("too many threads"); + EXPECT_EQ(slot_id, 0); + + // Try to allocate an additional counter. Verify it fails. + int counter_id = table.FindCounter(counter_base_name); + EXPECT_EQ(counter_id, 0); + + DeleteShmem(kTableName); +} + +// CounterZero will continually be set to 0. +const std::string kCounterZero = "CounterZero"; +// Counter1313 will continually be set to 1313. +const std::string kCounter1313 = "Counter1313"; +// CounterIncrement will be incremented each time. +const std::string kCounterIncrement = "CounterIncrement"; +// CounterDecrement will be decremented each time. +const std::string kCounterDecrement = "CounterDecrement"; +// CounterMixed will be incremented by odd numbered threads and +// decremented by even threads. +const std::string kCounterMixed = "CounterMixed"; +// The number of thread loops that we will do. +const int kThreadLoops = 100; + +class StatsTableThread : public SimpleThread { + public: + StatsTableThread(std::string name, int id) + : SimpleThread(name), + id_(id) {} + + virtual void Run() OVERRIDE; + + private: + int id_; +}; + +void StatsTableThread::Run() { + // Each thread will open the shared memory and set counters + // concurrently in a loop. We'll use some pauses to + // mixup the thread scheduling. + + StatsCounter zero_counter(kCounterZero); + StatsCounter lucky13_counter(kCounter1313); + StatsCounter increment_counter(kCounterIncrement); + StatsCounter decrement_counter(kCounterDecrement); + for (int index = 0; index < kThreadLoops; index++) { + StatsCounter mixed_counter(kCounterMixed); // create this one in the loop + zero_counter.Set(0); + lucky13_counter.Set(1313); + increment_counter.Increment(); + decrement_counter.Decrement(); + if (id_ % 2) + mixed_counter.Decrement(); + else + mixed_counter.Increment(); + PlatformThread::Sleep(TimeDelta::FromMilliseconds(index % 10)); + } +} + +// Create a few threads and have them poke on their counters. +// See http://crbug.com/10611 for more information. +#if defined(OS_MACOSX) || defined(THREAD_SANITIZER) +#define MAYBE_MultipleThreads DISABLED_MultipleThreads +#else +#define MAYBE_MultipleThreads MultipleThreads +#endif +TEST_F(StatsTableTest, MAYBE_MultipleThreads) { + // Create a stats table. + const std::string kTableName = "MultipleThreadStatTable"; + const int kMaxThreads = 20; + const int kMaxCounter = 5; + DeleteShmem(kTableName); + StatsTable table(kTableName, kMaxThreads, kMaxCounter); + StatsTable::set_current(&table); + + EXPECT_EQ(0, table.CountThreadsRegistered()); + + // Spin up a set of threads to go bang on the various counters. + // After we join the threads, we'll make sure the counters + // contain the values we expected. + StatsTableThread* threads[kMaxThreads]; + + // Spawn the threads. + for (int index = 0; index < kMaxThreads; index++) { + threads[index] = new StatsTableThread("MultipleThreadsTest", index); + threads[index]->Start(); + } + + // Wait for the threads to finish. + for (int index = 0; index < kMaxThreads; index++) { + threads[index]->Join(); + delete threads[index]; + } + + StatsCounter zero_counter(kCounterZero); + StatsCounter lucky13_counter(kCounter1313); + StatsCounter increment_counter(kCounterIncrement); + StatsCounter decrement_counter(kCounterDecrement); + StatsCounter mixed_counter(kCounterMixed); + + // Verify the various counters are correct. + std::string name; + name = "c:" + kCounterZero; + EXPECT_EQ(0, table.GetCounterValue(name)); + name = "c:" + kCounter1313; + EXPECT_EQ(1313 * kMaxThreads, + table.GetCounterValue(name)); + name = "c:" + kCounterIncrement; + EXPECT_EQ(kMaxThreads * kThreadLoops, + table.GetCounterValue(name)); + name = "c:" + kCounterDecrement; + EXPECT_EQ(-kMaxThreads * kThreadLoops, + table.GetCounterValue(name)); + name = "c:" + kCounterMixed; + EXPECT_EQ((kMaxThreads % 2) * kThreadLoops, + table.GetCounterValue(name)); + EXPECT_EQ(0, table.CountThreadsRegistered()); + + DeleteShmem(kTableName); +} + +const std::string kMPTableName = "MultipleProcessStatTable"; + +MULTIPROCESS_TEST_MAIN(StatsTableMultipleProcessMain) { + // Each process will open the shared memory and set counters + // concurrently in a loop. We'll use some pauses to + // mixup the scheduling. + + StatsTable table(kMPTableName, 0, 0); + StatsTable::set_current(&table); + StatsCounter zero_counter(kCounterZero); + StatsCounter lucky13_counter(kCounter1313); + StatsCounter increment_counter(kCounterIncrement); + StatsCounter decrement_counter(kCounterDecrement); + for (int index = 0; index < kThreadLoops; index++) { + zero_counter.Set(0); + lucky13_counter.Set(1313); + increment_counter.Increment(); + decrement_counter.Decrement(); + PlatformThread::Sleep(TimeDelta::FromMilliseconds(index % 10)); + } + return 0; +} + +// Create a few processes and have them poke on their counters. +// This test is slow and flaky http://crbug.com/10611 +TEST_F(StatsTableTest, DISABLED_MultipleProcesses) { + // Create a stats table. + const int kMaxProcs = 20; + const int kMaxCounter = 5; + DeleteShmem(kMPTableName); + StatsTable table(kMPTableName, kMaxProcs, kMaxCounter); + StatsTable::set_current(&table); + EXPECT_EQ(0, table.CountThreadsRegistered()); + + // Spin up a set of processes to go bang on the various counters. + // After we join the processes, we'll make sure the counters + // contain the values we expected. + ProcessHandle procs[kMaxProcs]; + + // Spawn the processes. + for (int16 index = 0; index < kMaxProcs; index++) { + procs[index] = this->SpawnChild("StatsTableMultipleProcessMain", false); + EXPECT_NE(kNullProcessHandle, procs[index]); + } + + // Wait for the processes to finish. + for (int index = 0; index < kMaxProcs; index++) { + EXPECT_TRUE(WaitForSingleProcess( + procs[index], base::TimeDelta::FromMinutes(1))); + CloseProcessHandle(procs[index]); + } + + StatsCounter zero_counter(kCounterZero); + StatsCounter lucky13_counter(kCounter1313); + StatsCounter increment_counter(kCounterIncrement); + StatsCounter decrement_counter(kCounterDecrement); + + // Verify the various counters are correct. + std::string name; + name = "c:" + kCounterZero; + EXPECT_EQ(0, table.GetCounterValue(name)); + name = "c:" + kCounter1313; + EXPECT_EQ(1313 * kMaxProcs, + table.GetCounterValue(name)); + name = "c:" + kCounterIncrement; + EXPECT_EQ(kMaxProcs * kThreadLoops, + table.GetCounterValue(name)); + name = "c:" + kCounterDecrement; + EXPECT_EQ(-kMaxProcs * kThreadLoops, + table.GetCounterValue(name)); + EXPECT_EQ(0, table.CountThreadsRegistered()); + + DeleteShmem(kMPTableName); +} + +class MockStatsCounter : public StatsCounter { + public: + explicit MockStatsCounter(const std::string& name) + : StatsCounter(name) {} + int* Pointer() { return GetPtr(); } +}; + +// Test some basic StatsCounter operations +TEST_F(StatsTableTest, StatsCounter) { + // Create a stats table. + const std::string kTableName = "StatTable"; + const int kMaxThreads = 20; + const int kMaxCounter = 5; + DeleteShmem(kTableName); + StatsTable table(kTableName, kMaxThreads, kMaxCounter); + StatsTable::set_current(&table); + + MockStatsCounter foo("foo"); + + // Test initial state. + EXPECT_TRUE(foo.Enabled()); + ASSERT_NE(foo.Pointer(), static_cast(0)); + EXPECT_EQ(0, *(foo.Pointer())); + EXPECT_EQ(0, table.GetCounterValue("c:foo")); + + // Test Increment. + while (*(foo.Pointer()) < 123) foo.Increment(); + EXPECT_EQ(123, table.GetCounterValue("c:foo")); + foo.Add(0); + EXPECT_EQ(123, table.GetCounterValue("c:foo")); + foo.Add(-1); + EXPECT_EQ(122, table.GetCounterValue("c:foo")); + + // Test Set. + foo.Set(0); + EXPECT_EQ(0, table.GetCounterValue("c:foo")); + foo.Set(100); + EXPECT_EQ(100, table.GetCounterValue("c:foo")); + foo.Set(-1); + EXPECT_EQ(-1, table.GetCounterValue("c:foo")); + foo.Set(0); + EXPECT_EQ(0, table.GetCounterValue("c:foo")); + + // Test Decrement. + foo.Subtract(1); + EXPECT_EQ(-1, table.GetCounterValue("c:foo")); + foo.Subtract(0); + EXPECT_EQ(-1, table.GetCounterValue("c:foo")); + foo.Subtract(-1); + EXPECT_EQ(0, table.GetCounterValue("c:foo")); + + DeleteShmem(kTableName); +} + +class MockStatsCounterTimer : public StatsCounterTimer { + public: + explicit MockStatsCounterTimer(const std::string& name) + : StatsCounterTimer(name) {} + + TimeTicks start_time() { return start_time_; } + TimeTicks stop_time() { return stop_time_; } +}; + +// Test some basic StatsCounterTimer operations +TEST_F(StatsTableTest, StatsCounterTimer) { + // Create a stats table. + const std::string kTableName = "StatTable"; + const int kMaxThreads = 20; + const int kMaxCounter = 5; + DeleteShmem(kTableName); + StatsTable table(kTableName, kMaxThreads, kMaxCounter); + StatsTable::set_current(&table); + + MockStatsCounterTimer bar("bar"); + + // Test initial state. + EXPECT_FALSE(bar.Running()); + EXPECT_TRUE(bar.start_time().is_null()); + EXPECT_TRUE(bar.stop_time().is_null()); + + const TimeDelta kDuration = TimeDelta::FromMilliseconds(100); + + // Do some timing. + bar.Start(); + PlatformThread::Sleep(kDuration); + bar.Stop(); + EXPECT_GT(table.GetCounterValue("t:bar"), 0); + EXPECT_LE(kDuration.InMilliseconds(), table.GetCounterValue("t:bar")); + + // Verify that timing again is additive. + bar.Start(); + PlatformThread::Sleep(kDuration); + bar.Stop(); + EXPECT_GT(table.GetCounterValue("t:bar"), 0); + EXPECT_LE(kDuration.InMilliseconds() * 2, table.GetCounterValue("t:bar")); + DeleteShmem(kTableName); +} + +// Test some basic StatsRate operations +TEST_F(StatsTableTest, StatsRate) { + // Create a stats table. + const std::string kTableName = "StatTable"; + const int kMaxThreads = 20; + const int kMaxCounter = 5; + DeleteShmem(kTableName); + StatsTable table(kTableName, kMaxThreads, kMaxCounter); + StatsTable::set_current(&table); + + StatsRate baz("baz"); + + // Test initial state. + EXPECT_FALSE(baz.Running()); + EXPECT_EQ(0, table.GetCounterValue("c:baz")); + EXPECT_EQ(0, table.GetCounterValue("t:baz")); + + const TimeDelta kDuration = TimeDelta::FromMilliseconds(100); + + // Do some timing. + baz.Start(); + PlatformThread::Sleep(kDuration); + baz.Stop(); + EXPECT_EQ(1, table.GetCounterValue("c:baz")); + EXPECT_LE(kDuration.InMilliseconds(), table.GetCounterValue("t:baz")); + + // Verify that timing again is additive. + baz.Start(); + PlatformThread::Sleep(kDuration); + baz.Stop(); + EXPECT_EQ(2, table.GetCounterValue("c:baz")); + EXPECT_LE(kDuration.InMilliseconds() * 2, table.GetCounterValue("t:baz")); + DeleteShmem(kTableName); +} + +// Test some basic StatsScope operations +TEST_F(StatsTableTest, StatsScope) { + // Create a stats table. + const std::string kTableName = "StatTable"; + const int kMaxThreads = 20; + const int kMaxCounter = 5; + DeleteShmem(kTableName); + StatsTable table(kTableName, kMaxThreads, kMaxCounter); + StatsTable::set_current(&table); + + StatsCounterTimer foo("foo"); + StatsRate bar("bar"); + + // Test initial state. + EXPECT_EQ(0, table.GetCounterValue("t:foo")); + EXPECT_EQ(0, table.GetCounterValue("t:bar")); + EXPECT_EQ(0, table.GetCounterValue("c:bar")); + + const TimeDelta kDuration = TimeDelta::FromMilliseconds(100); + + // Try a scope. + { + StatsScope timer(foo); + StatsScope timer2(bar); + PlatformThread::Sleep(kDuration); + } + EXPECT_LE(kDuration.InMilliseconds(), table.GetCounterValue("t:foo")); + EXPECT_LE(kDuration.InMilliseconds(), table.GetCounterValue("t:bar")); + EXPECT_EQ(1, table.GetCounterValue("c:bar")); + + // Try a second scope. + { + StatsScope timer(foo); + StatsScope timer2(bar); + PlatformThread::Sleep(kDuration); + } + EXPECT_LE(kDuration.InMilliseconds() * 2, table.GetCounterValue("t:foo")); + EXPECT_LE(kDuration.InMilliseconds() * 2, table.GetCounterValue("t:bar")); + EXPECT_EQ(2, table.GetCounterValue("c:bar")); + + DeleteShmem(kTableName); +} + +} // namespace base diff --git a/base/move.h b/base/move.h new file mode 100644 index 0000000000..d2cd3df4f7 --- /dev/null +++ b/base/move.h @@ -0,0 +1,207 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MOVE_H_ +#define BASE_MOVE_H_ + +// Macro with the boilerplate that makes a type move-only in C++03. +// +// USAGE +// +// This macro should be used instead of DISALLOW_COPY_AND_ASSIGN to create +// a "move-only" type. Unlike DISALLOW_COPY_AND_ASSIGN, this macro should be +// the first line in a class declaration. +// +// A class using this macro must call .Pass() (or somehow be an r-value already) +// before it can be: +// +// * Passed as a function argument +// * Used as the right-hand side of an assignment +// * Returned from a function +// +// Each class will still need to define their own "move constructor" and "move +// operator=" to make this useful. Here's an example of the macro, the move +// constructor, and the move operator= from the scoped_ptr class: +// +// template +// class scoped_ptr { +// MOVE_ONLY_TYPE_FOR_CPP_03(scoped_ptr, RValue) +// public: +// scoped_ptr(RValue& other) : ptr_(other.release()) { } +// scoped_ptr& operator=(RValue& other) { +// swap(other); +// return *this; +// } +// }; +// +// Note that the constructor must NOT be marked explicit. +// +// For consistency, the second parameter to the macro should always be RValue +// unless you have a strong reason to do otherwise. It is only exposed as a +// macro parameter so that the move constructor and move operator= don't look +// like they're using a phantom type. +// +// +// HOW THIS WORKS +// +// For a thorough explanation of this technique, see: +// +// http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Move_Constructor +// +// The summary is that we take advantage of 2 properties: +// +// 1) non-const references will not bind to r-values. +// 2) C++ can apply one user-defined conversion when initializing a +// variable. +// +// The first lets us disable the copy constructor and assignment operator +// by declaring private version of them with a non-const reference parameter. +// +// For l-values, direct initialization still fails like in +// DISALLOW_COPY_AND_ASSIGN because the copy constructor and assignment +// operators are private. +// +// For r-values, the situation is different. The copy constructor and +// assignment operator are not viable due to (1), so we are trying to call +// a non-existent constructor and non-existing operator= rather than a private +// one. Since we have not committed an error quite yet, we can provide an +// alternate conversion sequence and a constructor. We add +// +// * a private struct named "RValue" +// * a user-defined conversion "operator RValue()" +// * a "move constructor" and "move operator=" that take the RValue& as +// their sole parameter. +// +// Only r-values will trigger this sequence and execute our "move constructor" +// or "move operator=." L-values will match the private copy constructor and +// operator= first giving a "private in this context" error. This combination +// gives us a move-only type. +// +// For signaling a destructive transfer of data from an l-value, we provide a +// method named Pass() which creates an r-value for the current instance +// triggering the move constructor or move operator=. +// +// Other ways to get r-values is to use the result of an expression like a +// function call. +// +// Here's an example with comments explaining what gets triggered where: +// +// class Foo { +// MOVE_ONLY_TYPE_FOR_CPP_03(Foo, RValue); +// +// public: +// ... API ... +// Foo(RValue other); // Move constructor. +// Foo& operator=(RValue rhs); // Move operator= +// }; +// +// Foo MakeFoo(); // Function that returns a Foo. +// +// Foo f; +// Foo f_copy(f); // ERROR: Foo(Foo&) is private in this context. +// Foo f_assign; +// f_assign = f; // ERROR: operator=(Foo&) is private in this context. +// +// +// Foo f(MakeFoo()); // R-value so alternate conversion executed. +// Foo f_copy(f.Pass()); // R-value so alternate conversion executed. +// f = f_copy.Pass(); // R-value so alternate conversion executed. +// +// +// IMPLEMENTATION SUBTLETIES WITH RValue +// +// The RValue struct is just a container for a pointer back to the original +// object. It should only ever be created as a temporary, and no external +// class should ever declare it or use it in a parameter. +// +// It is tempting to want to use the RValue type in function parameters, but +// excluding the limited usage here for the move constructor and move +// operator=, doing so would mean that the function could take both r-values +// and l-values equially which is unexpected. See COMPARED To Boost.Move for +// more details. +// +// An alternate, and incorrect, implementation of the RValue class used by +// Boost.Move makes RValue a fieldless child of the move-only type. RValue& +// is then used in place of RValue in the various operators. The RValue& is +// "created" by doing *reinterpret_cast(this). This has the appeal +// of never creating a temporary RValue struct even with optimizations +// disabled. Also, by virtue of inheritance you can treat the RValue +// reference as if it were the move-only type itself. Unfortunately, +// using the result of this reinterpret_cast<> is actually undefined behavior +// due to C++98 5.2.10.7. In certain compilers (e.g., NaCl) the optimizer +// will generate non-working code. +// +// In optimized builds, both implementations generate the same assembly so we +// choose the one that adheres to the standard. +// +// +// COMPARED TO C++11 +// +// In C++11, you would implement this functionality using an r-value reference +// and our .Pass() method would be replaced with a call to std::move(). +// +// This emulation also has a deficiency where it uses up the single +// user-defined conversion allowed by C++ during initialization. This can +// cause problems in some API edge cases. For instance, in scoped_ptr, it is +// impossible to make a function "void Foo(scoped_ptr p)" accept a +// value of type scoped_ptr even if you add a constructor to +// scoped_ptr<> that would make it look like it should work. C++11 does not +// have this deficiency. +// +// +// COMPARED TO Boost.Move +// +// Our implementation similar to Boost.Move, but we keep the RValue struct +// private to the move-only type, and we don't use the reinterpret_cast<> hack. +// +// In Boost.Move, RValue is the boost::rv<> template. This type can be used +// when writing APIs like: +// +// void MyFunc(boost::rv& f) +// +// that can take advantage of rv<> to avoid extra copies of a type. However you +// would still be able to call this version of MyFunc with an l-value: +// +// Foo f; +// MyFunc(f); // Uh oh, we probably just destroyed |f| w/o calling Pass(). +// +// unless someone is very careful to also declare a parallel override like: +// +// void MyFunc(const Foo& f) +// +// that would catch the l-values first. This was declared unsafe in C++11 and +// a C++11 compiler will explicitly fail MyFunc(f). Unfortunately, we cannot +// ensure this in C++03. +// +// Since we have no need for writing such APIs yet, our implementation keeps +// RValue private and uses a .Pass() method to do the conversion instead of +// trying to write a version of "std::move()." Writing an API like std::move() +// would require the RValue struct to be public. +// +// +// CAVEATS +// +// If you include a move-only type as a field inside a class that does not +// explicitly declare a copy constructor, the containing class's implicit +// copy constructor will change from Containing(const Containing&) to +// Containing(Containing&). This can cause some unexpected errors. +// +// http://llvm.org/bugs/show_bug.cgi?id=11528 +// +// The workaround is to explicitly declare your copy constructor. +// +#define MOVE_ONLY_TYPE_FOR_CPP_03(type, rvalue_type) \ + private: \ + struct rvalue_type { \ + explicit rvalue_type(type* object) : object(object) {} \ + type* object; \ + }; \ + type(type&); \ + void operator=(type&); \ + public: \ + operator rvalue_type() { return rvalue_type(this); } \ + type Pass() { return type(rvalue_type(this)); } \ + private: + +#endif // BASE_MOVE_H_ diff --git a/base/native_library.h b/base/native_library.h new file mode 100644 index 0000000000..891f35bd54 --- /dev/null +++ b/base/native_library.h @@ -0,0 +1,91 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_NATIVE_LIBRARY_H_ +#define BASE_NATIVE_LIBRARY_H_ + +// This file defines a cross-platform "NativeLibrary" type which represents +// a loadable module. + +#include "base/base_export.h" +#include "build/build_config.h" + +#if defined(OS_WIN) +#include +#elif defined(OS_MACOSX) +#import +#endif // OS_* + +#include "base/strings/string16.h" + +// Macro useful for writing cross-platform function pointers. +#if defined(OS_WIN) && !defined(CDECL) +#define CDECL __cdecl +#else +#define CDECL +#endif + +namespace base { + +class FilePath; + +#if defined(OS_WIN) +typedef HMODULE NativeLibrary; +#elif defined(OS_MACOSX) +enum NativeLibraryType { + BUNDLE, + DYNAMIC_LIB +}; +enum NativeLibraryObjCStatus { + OBJC_UNKNOWN, + OBJC_PRESENT, + OBJC_NOT_PRESENT, +}; +struct NativeLibraryStruct { + NativeLibraryType type; + CFBundleRefNum bundle_resource_ref; + NativeLibraryObjCStatus objc_status; + union { + CFBundleRef bundle; + void* dylib; + }; +}; +typedef NativeLibraryStruct* NativeLibrary; +#elif defined(OS_POSIX) +typedef void* NativeLibrary; +#endif // OS_* + +// Loads a native library from disk. Release it with UnloadNativeLibrary when +// you're done. Returns NULL on failure. +// If |err| is not NULL, it may be filled in with an error message on +// error. +BASE_EXPORT NativeLibrary LoadNativeLibrary(const FilePath& library_path, + std::string* error); + +#if defined(OS_WIN) +// Loads a native library from disk. Release it with UnloadNativeLibrary when +// you're done. +// This function retrieves the LoadLibrary function exported from kernel32.dll +// and calls it instead of directly calling the LoadLibrary function via the +// import table. +BASE_EXPORT NativeLibrary LoadNativeLibraryDynamically( + const FilePath& library_path); +#endif // OS_WIN + +// Unloads a native library. +BASE_EXPORT void UnloadNativeLibrary(NativeLibrary library); + +// Gets a function pointer from a native library. +BASE_EXPORT void* GetFunctionPointerFromNativeLibrary(NativeLibrary library, + const char* name); + +// Returns the full platform specific name for a native library. +// For example: +// "mylib" returns "mylib.dll" on Windows, "libmylib.so" on Linux, +// "mylib.dylib" on Mac. +BASE_EXPORT string16 GetNativeLibraryName(const string16& name); + +} // namespace base + +#endif // BASE_NATIVE_LIBRARY_H_ diff --git a/base/native_library_mac.mm b/base/native_library_mac.mm new file mode 100644 index 0000000000..6544fcaed9 --- /dev/null +++ b/base/native_library_mac.mm @@ -0,0 +1,125 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/native_library.h" + +#include +#include + +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/thread_restrictions.h" + +namespace base { + +static NativeLibraryObjCStatus GetObjCStatusForImage( + const void* function_pointer) { + Dl_info info; + if (!dladdr(function_pointer, &info)) + return OBJC_UNKNOWN; + + // See if the the image contains an "ObjC image info" segment. This method + // of testing is used in _CFBundleGrokObjcImageInfoFromFile in + // CF-744/CFBundle.c, around lines 2447-2474. + // + // In 32-bit images, ObjC can be recognized in __OBJC,__image_info, whereas + // in 64-bit, the data is in __DATA,__objc_imageinfo. +#if __LP64__ + const section_64* section = getsectbynamefromheader_64( + reinterpret_cast(info.dli_fbase), + SEG_DATA, "__objc_imageinfo"); +#else + const section* section = getsectbynamefromheader( + reinterpret_cast(info.dli_fbase), + SEG_OBJC, "__image_info"); +#endif + return section == NULL ? OBJC_NOT_PRESENT : OBJC_PRESENT; +} + +// static +NativeLibrary LoadNativeLibrary(const base::FilePath& library_path, + std::string* error) { + // dlopen() etc. open the file off disk. + if (library_path.Extension() == "dylib" || !DirectoryExists(library_path)) { + void* dylib = dlopen(library_path.value().c_str(), RTLD_LAZY); + if (!dylib) + return NULL; + NativeLibrary native_lib = new NativeLibraryStruct(); + native_lib->type = DYNAMIC_LIB; + native_lib->dylib = dylib; + native_lib->objc_status = OBJC_UNKNOWN; + return native_lib; + } + base::ScopedCFTypeRef url(CFURLCreateFromFileSystemRepresentation( + kCFAllocatorDefault, + (const UInt8*)library_path.value().c_str(), + library_path.value().length(), + true)); + if (!url) + return NULL; + CFBundleRef bundle = CFBundleCreate(kCFAllocatorDefault, url.get()); + if (!bundle) + return NULL; + + NativeLibrary native_lib = new NativeLibraryStruct(); + native_lib->type = BUNDLE; + native_lib->bundle = bundle; + native_lib->bundle_resource_ref = CFBundleOpenBundleResourceMap(bundle); + native_lib->objc_status = OBJC_UNKNOWN; + return native_lib; +} + +// static +void UnloadNativeLibrary(NativeLibrary library) { + if (library->objc_status == OBJC_NOT_PRESENT) { + if (library->type == BUNDLE) { + CFBundleCloseBundleResourceMap(library->bundle, + library->bundle_resource_ref); + CFRelease(library->bundle); + } else { + dlclose(library->dylib); + } + } else { + VLOG(2) << "Not unloading NativeLibrary because it may contain an ObjC " + "segment. library->objc_status = " << library->objc_status; + // Deliberately do not CFRelease the bundle or dlclose the dylib because + // doing so can corrupt the ObjC runtime method caches. See + // http://crbug.com/172319 for details. + } + delete library; +} + +// static +void* GetFunctionPointerFromNativeLibrary(NativeLibrary library, + const char* name) { + void* function_pointer = NULL; + + // Get the function pointer using the right API for the type. + if (library->type == BUNDLE) { + base::ScopedCFTypeRef symbol_name(CFStringCreateWithCString( + kCFAllocatorDefault, name, kCFStringEncodingUTF8)); + function_pointer = CFBundleGetFunctionPointerForName(library->bundle, + symbol_name); + } else { + function_pointer = dlsym(library->dylib, name); + } + + // If this library hasn't been tested for having ObjC, use the function + // pointer to look up the section information for the library. + if (function_pointer && library->objc_status == OBJC_UNKNOWN) + library->objc_status = GetObjCStatusForImage(function_pointer); + + return function_pointer; +} + +// static +string16 GetNativeLibraryName(const string16& name) { + return name + ASCIIToUTF16(".dylib"); +} + +} // namespace base diff --git a/base/native_library_posix.cc b/base/native_library_posix.cc new file mode 100644 index 0000000000..dfa20fc01c --- /dev/null +++ b/base/native_library_posix.cc @@ -0,0 +1,53 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/native_library.h" + +#include + +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/thread_restrictions.h" + +namespace base { + +// static +NativeLibrary LoadNativeLibrary(const FilePath& library_path, + std::string* error) { + // dlopen() opens the file off disk. + base::ThreadRestrictions::AssertIOAllowed(); + + // We deliberately do not use RTLD_DEEPBIND. For the history why, please + // refer to the bug tracker. Some useful bug reports to read include: + // http://crbug.com/17943, http://crbug.com/17557, http://crbug.com/36892, + // and http://crbug.com/40794. + void* dl = dlopen(library_path.value().c_str(), RTLD_LAZY); + if (!dl && error) + *error = dlerror(); + + return dl; +} + +// static +void UnloadNativeLibrary(NativeLibrary library) { + int ret = dlclose(library); + if (ret < 0) { + DLOG(ERROR) << "dlclose failed: " << dlerror(); + NOTREACHED(); + } +} + +// static +void* GetFunctionPointerFromNativeLibrary(NativeLibrary library, + const char* name) { + return dlsym(library, name); +} + +// static +string16 GetNativeLibraryName(const string16& name) { + return ASCIIToUTF16("lib") + name + ASCIIToUTF16(".so"); +} + +} // namespace base diff --git a/base/native_library_win.cc b/base/native_library_win.cc new file mode 100644 index 0000000000..2d437fa2a5 --- /dev/null +++ b/base/native_library_win.cc @@ -0,0 +1,73 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/native_library.h" + +#include + +#include "base/file_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/thread_restrictions.h" + +namespace base { + +typedef HMODULE (WINAPI* LoadLibraryFunction)(const wchar_t* file_name); + +NativeLibrary LoadNativeLibraryHelper(const FilePath& library_path, + LoadLibraryFunction load_library_api) { + // LoadLibrary() opens the file off disk. + base::ThreadRestrictions::AssertIOAllowed(); + + // Switch the current directory to the library directory as the library + // may have dependencies on DLLs in this directory. + bool restore_directory = false; + FilePath current_directory; + if (file_util::GetCurrentDirectory(¤t_directory)) { + FilePath plugin_path = library_path.DirName(); + if (!plugin_path.empty()) { + file_util::SetCurrentDirectory(plugin_path); + restore_directory = true; + } + } + + HMODULE module = (*load_library_api)(library_path.value().c_str()); + if (restore_directory) + file_util::SetCurrentDirectory(current_directory); + + return module; +} + +// static +NativeLibrary LoadNativeLibrary(const FilePath& library_path, + std::string* error) { + return LoadNativeLibraryHelper(library_path, LoadLibraryW); +} + +NativeLibrary LoadNativeLibraryDynamically(const FilePath& library_path) { + typedef HMODULE (WINAPI* LoadLibraryFunction)(const wchar_t* file_name); + + LoadLibraryFunction load_library; + load_library = reinterpret_cast( + GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryW")); + + return LoadNativeLibraryHelper(library_path, load_library); +} + +// static +void UnloadNativeLibrary(NativeLibrary library) { + FreeLibrary(library); +} + +// static +void* GetFunctionPointerFromNativeLibrary(NativeLibrary library, + const char* name) { + return GetProcAddress(library, name); +} + +// static +string16 GetNativeLibraryName(const string16& name) { + return name + ASCIIToUTF16(".dll"); +} + +} // namespace base diff --git a/base/newlib-x86-32.base_untrusted.source_list.gypcmd b/base/newlib-x86-32.base_untrusted.source_list.gypcmd new file mode 100644 index 0000000000..8dbe0ca926 --- /dev/null +++ b/base/newlib-x86-32.base_untrusted.source_list.gypcmd @@ -0,0 +1,353 @@ +../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/prcpucfg.h +third_party/nspr/prcpucfg_freebsd.h +third_party/nspr/prcpucfg_nacl.h +third_party/nspr/prcpucfg_openbsd.h +third_party/nspr/prcpucfg_solaris.h +third_party/nspr/prtime.cc +third_party/nspr/prtime.h +third_party/nspr/prtypes.h +third_party/xdg_mime/xdgmime.h +allocator/allocator_extension.cc +allocator/allocator_extension.h +at_exit.cc +at_exit.h +atomic_ref_count.h +atomic_sequence_num.h +atomicops.h +atomicops_internals_gcc.h +atomicops_internals_tsan.h +atomicops_internals_x86_gcc.h +atomicops_internals_x86_msvc.h +base_export.h +base_paths.h +base_paths_android.h +base_paths_posix.cc +base_paths_posix.h +base_switches.h +base64.cc +base64.h +basictypes.h +bind.h +bind_helpers.cc +bind_helpers.h +bind_internal.h +bits.h +build_time.cc +build_time.h +callback.h +callback_helpers.h +callback_internal.cc +callback_internal.h +cancelable_callback.h +command_line.cc +command_line.h +compiler_specific.h +containers/hash_tables.h +containers/linked_list.h +containers/mru_cache.h +containers/small_map.h +containers/stack_container.h +cpu.h +critical_closure.h +debug/alias.cc +debug/alias.h +debug/crash_logging.cc +debug/crash_logging.h +debug/debugger.cc +debug/debugger.h +debug/debugger_posix.cc +debug/leak_annotations.h +debug/leak_tracker.h +debug/profiler.cc +debug/profiler.h +debug/stack_trace.cc +debug/stack_trace.h +debug/trace_event.h +debug/trace_event_impl.cc +debug/trace_event_impl.h +debug/trace_event_impl_constants.cc +debug/trace_event_memory.cc +debug/trace_event_memory.h +deferred_sequenced_task_runner.cc +deferred_sequenced_task_runner.h +environment.cc +environment.h +file_descriptor_posix.h +file_util.h +file_version_info.h +files/dir_reader_fallback.h +files/dir_reader_posix.h +files/file_enumerator.cc +files/file_enumerator.h +files/file_path.cc +files/file_path.h +files/file_path_constants.cc +files/file_path_watcher.cc +files/file_path_watcher.h +files/file_path_watcher_stub.cc +files/file_util_proxy.h +files/important_file_writer.h +files/important_file_writer.cc +files/memory_mapped_file.cc +files/memory_mapped_file.h +files/memory_mapped_file_posix.cc +files/scoped_platform_file_closer.cc +files/scoped_platform_file_closer.h +files/scoped_temp_dir.h +float_util.h +format_macros.h +gtest_prod_util.h +guid.cc +guid.h +guid_posix.cc +hash.cc +hash.h +id_map.h +ini_parser.cc +ini_parser.h +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 +memory/aligned_memory.cc +memory/aligned_memory.h +memory/discardable_memory.cc +memory/discardable_memory.h +memory/linked_ptr.h +memory/manual_constructor.h +memory/memory_pressure_listener.cc +memory/memory_pressure_listener.h +memory/raw_scoped_refptr_mismatch_checker.h +memory/ref_counted.cc +memory/ref_counted.h +memory/ref_counted_delete_on_message_loop.h +memory/ref_counted_memory.cc +memory/ref_counted_memory.h +memory/scoped_handle.h +memory/scoped_open_process.h +memory/scoped_policy.h +memory/scoped_ptr.h +memory/scoped_vector.h +memory/shared_memory.h +memory/shared_memory_nacl.cc +memory/singleton.cc +memory/singleton.h +memory/weak_ptr.cc +memory/weak_ptr.h +message_loop/incoming_task_queue.cc +message_loop/incoming_task_queue.h +message_loop/message_loop.cc +message_loop/message_loop.h +message_loop/message_loop_proxy.cc +message_loop/message_loop_proxy.h +message_loop/message_loop_proxy_impl.cc +message_loop/message_loop_proxy_impl.h +message_loop/message_pump.cc +message_loop/message_pump.h +message_loop/message_pump_android.h +message_loop/message_pump_default.cc +message_loop/message_pump_default.h +move.h +native_library.h +observer_list.h +observer_list_threadsafe.h +os_compat_android.h +os_compat_nacl.cc +os_compat_nacl.h +path_service.h +pending_task.cc +pending_task.h +pickle.cc +pickle.h +platform_file.cc +platform_file.h +platform_file_posix.cc +port.h +posix/eintr_wrapper.h +posix/global_descriptors.cc +posix/global_descriptors.h +power_monitor/power_monitor.cc +power_monitor/power_monitor.h +power_monitor/power_monitor_device_source_android.h +power_monitor/power_monitor_device_source.cc +power_monitor/power_monitor_device_source.h +power_monitor/power_monitor_device_source_posix.cc +power_monitor/power_monitor_source.cc +power_monitor/power_monitor_source.h +power_monitor/power_observer.h +process/kill.cc +process/kill.h +process/launch.h +process/memory.h +process/process.h +process/process_handle_posix.cc +process/process_info.h +process/process_iterator.cc +process/process_iterator.h +process/process_metrics.h +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 +run_loop.cc +run_loop.h +safe_numerics.h +safe_strerror_posix.cc +safe_strerror_posix.h +scoped_native_library.h +sequence_checker.h +sequence_checker_impl.cc +sequence_checker_impl.h +sequenced_task_runner.cc +sequenced_task_runner.h +sequenced_task_runner_helpers.h +sha1.h +sha1_portable.cc +single_thread_task_runner.h +stl_util.h +strings/latin1_string_conversions.cc +strings/latin1_string_conversions.h +strings/nullable_string16.cc +strings/nullable_string16.h +strings/string16.cc +strings/string16.h +strings/string_number_conversions.cc +strings/string_split.cc +strings/string_split.h +strings/string_number_conversions.h +strings/string_piece.cc +strings/string_piece.h +strings/string_tokenizer.h +strings/string_util.cc +strings/string_util.h +strings/string_util_constants.cc +strings/string_util_posix.h +strings/stringize_macros.h +strings/stringprintf.cc +strings/stringprintf.h +strings/sys_string_conversions.h +strings/sys_string_conversions_posix.cc +strings/utf_offset_string_conversions.cc +strings/utf_offset_string_conversions.h +strings/utf_string_conversion_utils.cc +strings/utf_string_conversion_utils.h +strings/utf_string_conversions.cc +strings/utf_string_conversions.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/lock.cc +synchronization/lock.h +synchronization/lock_impl.h +synchronization/lock_impl_posix.cc +synchronization/spin_wait.h +synchronization/waitable_event.h +synchronization/waitable_event_posix.cc +synchronization/waitable_event_watcher.h +synchronization/waitable_event_watcher_posix.cc +system_monitor/system_monitor.cc +system_monitor/system_monitor.h +sys_byteorder.h +sys_info.cc +sys_info.h +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_linux.cc +threading/platform_thread_posix.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_id_name_manager.cc +threading/thread_id_name_manager.h +threading/thread_local.h +threading/thread_local_posix.cc +threading/thread_local_storage.h +threading/thread_local_storage_posix.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 +time/clock.cc +time/clock.h +time/default_clock.cc +time/default_clock.h +time/default_tick_clock.cc +time/default_tick_clock.h +time/tick_clock.cc +time/tick_clock.h +time/time.cc +time/time.h +time/time_posix.cc +timer/hi_res_timer_manager_posix.cc +timer/hi_res_timer_manager.h +timer/timer.cc +timer/timer.h +tracked_objects.cc +tracked_objects.h +tracking_info.cc +tracking_info.h +tuple.h +values.cc +values.h +value_conversions.cc +value_conversions.h +version.cc +version.h +vlog.cc +vlog.h +base_switches.cc +base_switches.h +strings/string16.cc +sync_socket_nacl.cc +time/time_posix.cc diff --git a/base/newlib-x86-64.base_untrusted.source_list.gypcmd b/base/newlib-x86-64.base_untrusted.source_list.gypcmd new file mode 100644 index 0000000000..8dbe0ca926 --- /dev/null +++ b/base/newlib-x86-64.base_untrusted.source_list.gypcmd @@ -0,0 +1,353 @@ +../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/prcpucfg.h +third_party/nspr/prcpucfg_freebsd.h +third_party/nspr/prcpucfg_nacl.h +third_party/nspr/prcpucfg_openbsd.h +third_party/nspr/prcpucfg_solaris.h +third_party/nspr/prtime.cc +third_party/nspr/prtime.h +third_party/nspr/prtypes.h +third_party/xdg_mime/xdgmime.h +allocator/allocator_extension.cc +allocator/allocator_extension.h +at_exit.cc +at_exit.h +atomic_ref_count.h +atomic_sequence_num.h +atomicops.h +atomicops_internals_gcc.h +atomicops_internals_tsan.h +atomicops_internals_x86_gcc.h +atomicops_internals_x86_msvc.h +base_export.h +base_paths.h +base_paths_android.h +base_paths_posix.cc +base_paths_posix.h +base_switches.h +base64.cc +base64.h +basictypes.h +bind.h +bind_helpers.cc +bind_helpers.h +bind_internal.h +bits.h +build_time.cc +build_time.h +callback.h +callback_helpers.h +callback_internal.cc +callback_internal.h +cancelable_callback.h +command_line.cc +command_line.h +compiler_specific.h +containers/hash_tables.h +containers/linked_list.h +containers/mru_cache.h +containers/small_map.h +containers/stack_container.h +cpu.h +critical_closure.h +debug/alias.cc +debug/alias.h +debug/crash_logging.cc +debug/crash_logging.h +debug/debugger.cc +debug/debugger.h +debug/debugger_posix.cc +debug/leak_annotations.h +debug/leak_tracker.h +debug/profiler.cc +debug/profiler.h +debug/stack_trace.cc +debug/stack_trace.h +debug/trace_event.h +debug/trace_event_impl.cc +debug/trace_event_impl.h +debug/trace_event_impl_constants.cc +debug/trace_event_memory.cc +debug/trace_event_memory.h +deferred_sequenced_task_runner.cc +deferred_sequenced_task_runner.h +environment.cc +environment.h +file_descriptor_posix.h +file_util.h +file_version_info.h +files/dir_reader_fallback.h +files/dir_reader_posix.h +files/file_enumerator.cc +files/file_enumerator.h +files/file_path.cc +files/file_path.h +files/file_path_constants.cc +files/file_path_watcher.cc +files/file_path_watcher.h +files/file_path_watcher_stub.cc +files/file_util_proxy.h +files/important_file_writer.h +files/important_file_writer.cc +files/memory_mapped_file.cc +files/memory_mapped_file.h +files/memory_mapped_file_posix.cc +files/scoped_platform_file_closer.cc +files/scoped_platform_file_closer.h +files/scoped_temp_dir.h +float_util.h +format_macros.h +gtest_prod_util.h +guid.cc +guid.h +guid_posix.cc +hash.cc +hash.h +id_map.h +ini_parser.cc +ini_parser.h +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 +memory/aligned_memory.cc +memory/aligned_memory.h +memory/discardable_memory.cc +memory/discardable_memory.h +memory/linked_ptr.h +memory/manual_constructor.h +memory/memory_pressure_listener.cc +memory/memory_pressure_listener.h +memory/raw_scoped_refptr_mismatch_checker.h +memory/ref_counted.cc +memory/ref_counted.h +memory/ref_counted_delete_on_message_loop.h +memory/ref_counted_memory.cc +memory/ref_counted_memory.h +memory/scoped_handle.h +memory/scoped_open_process.h +memory/scoped_policy.h +memory/scoped_ptr.h +memory/scoped_vector.h +memory/shared_memory.h +memory/shared_memory_nacl.cc +memory/singleton.cc +memory/singleton.h +memory/weak_ptr.cc +memory/weak_ptr.h +message_loop/incoming_task_queue.cc +message_loop/incoming_task_queue.h +message_loop/message_loop.cc +message_loop/message_loop.h +message_loop/message_loop_proxy.cc +message_loop/message_loop_proxy.h +message_loop/message_loop_proxy_impl.cc +message_loop/message_loop_proxy_impl.h +message_loop/message_pump.cc +message_loop/message_pump.h +message_loop/message_pump_android.h +message_loop/message_pump_default.cc +message_loop/message_pump_default.h +move.h +native_library.h +observer_list.h +observer_list_threadsafe.h +os_compat_android.h +os_compat_nacl.cc +os_compat_nacl.h +path_service.h +pending_task.cc +pending_task.h +pickle.cc +pickle.h +platform_file.cc +platform_file.h +platform_file_posix.cc +port.h +posix/eintr_wrapper.h +posix/global_descriptors.cc +posix/global_descriptors.h +power_monitor/power_monitor.cc +power_monitor/power_monitor.h +power_monitor/power_monitor_device_source_android.h +power_monitor/power_monitor_device_source.cc +power_monitor/power_monitor_device_source.h +power_monitor/power_monitor_device_source_posix.cc +power_monitor/power_monitor_source.cc +power_monitor/power_monitor_source.h +power_monitor/power_observer.h +process/kill.cc +process/kill.h +process/launch.h +process/memory.h +process/process.h +process/process_handle_posix.cc +process/process_info.h +process/process_iterator.cc +process/process_iterator.h +process/process_metrics.h +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 +run_loop.cc +run_loop.h +safe_numerics.h +safe_strerror_posix.cc +safe_strerror_posix.h +scoped_native_library.h +sequence_checker.h +sequence_checker_impl.cc +sequence_checker_impl.h +sequenced_task_runner.cc +sequenced_task_runner.h +sequenced_task_runner_helpers.h +sha1.h +sha1_portable.cc +single_thread_task_runner.h +stl_util.h +strings/latin1_string_conversions.cc +strings/latin1_string_conversions.h +strings/nullable_string16.cc +strings/nullable_string16.h +strings/string16.cc +strings/string16.h +strings/string_number_conversions.cc +strings/string_split.cc +strings/string_split.h +strings/string_number_conversions.h +strings/string_piece.cc +strings/string_piece.h +strings/string_tokenizer.h +strings/string_util.cc +strings/string_util.h +strings/string_util_constants.cc +strings/string_util_posix.h +strings/stringize_macros.h +strings/stringprintf.cc +strings/stringprintf.h +strings/sys_string_conversions.h +strings/sys_string_conversions_posix.cc +strings/utf_offset_string_conversions.cc +strings/utf_offset_string_conversions.h +strings/utf_string_conversion_utils.cc +strings/utf_string_conversion_utils.h +strings/utf_string_conversions.cc +strings/utf_string_conversions.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/lock.cc +synchronization/lock.h +synchronization/lock_impl.h +synchronization/lock_impl_posix.cc +synchronization/spin_wait.h +synchronization/waitable_event.h +synchronization/waitable_event_posix.cc +synchronization/waitable_event_watcher.h +synchronization/waitable_event_watcher_posix.cc +system_monitor/system_monitor.cc +system_monitor/system_monitor.h +sys_byteorder.h +sys_info.cc +sys_info.h +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_linux.cc +threading/platform_thread_posix.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_id_name_manager.cc +threading/thread_id_name_manager.h +threading/thread_local.h +threading/thread_local_posix.cc +threading/thread_local_storage.h +threading/thread_local_storage_posix.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 +time/clock.cc +time/clock.h +time/default_clock.cc +time/default_clock.h +time/default_tick_clock.cc +time/default_tick_clock.h +time/tick_clock.cc +time/tick_clock.h +time/time.cc +time/time.h +time/time_posix.cc +timer/hi_res_timer_manager_posix.cc +timer/hi_res_timer_manager.h +timer/timer.cc +timer/timer.h +tracked_objects.cc +tracked_objects.h +tracking_info.cc +tracking_info.h +tuple.h +values.cc +values.h +value_conversions.cc +value_conversions.h +version.cc +version.h +vlog.cc +vlog.h +base_switches.cc +base_switches.h +strings/string16.cc +sync_socket_nacl.cc +time/time_posix.cc diff --git a/base/nix/mime_util_xdg.cc b/base/nix/mime_util_xdg.cc new file mode 100644 index 0000000000..1a41394fc1 --- /dev/null +++ b/base/nix/mime_util_xdg.cc @@ -0,0 +1,655 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/nix/mime_util_xdg.h" + +#include +#include +#include +#include + +#include "base/environment.h" +#include "base/file_util.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/singleton.h" +#include "base/nix/xdg_util.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/synchronization/lock.h" +#include "base/third_party/xdg_mime/xdgmime.h" +#include "base/threading/thread_restrictions.h" +#include "base/time/time.h" + +namespace base { +namespace nix { + +namespace { + +class IconTheme; + +// None of the XDG stuff is thread-safe, so serialize all access under +// this lock. +base::LazyInstance::Leaky + g_mime_util_xdg_lock = LAZY_INSTANCE_INITIALIZER; + +class MimeUtilConstants { + public: + typedef std::map IconThemeMap; + typedef std::map IconDirMtimeMap; + typedef std::vector IconFormats; + + // Specified by XDG icon theme specs. + static const int kUpdateIntervalInSeconds = 5; + + static const size_t kDefaultThemeNum = 4; + + static MimeUtilConstants* GetInstance() { + return Singleton::get(); + } + + // Store icon directories and their mtimes. + IconDirMtimeMap icon_dirs_; + + // Store icon formats. + IconFormats icon_formats_; + + // Store loaded icon_theme. + IconThemeMap icon_themes_; + + // The default theme. + IconTheme* default_themes_[kDefaultThemeNum]; + + base::TimeTicks last_check_time_; + + // The current icon theme, usually set through GTK theme integration. + std::string icon_theme_name_; + + private: + MimeUtilConstants() { + icon_formats_.push_back(".png"); + icon_formats_.push_back(".svg"); + icon_formats_.push_back(".xpm"); + + for (size_t i = 0; i < kDefaultThemeNum; ++i) + default_themes_[i] = NULL; + } + ~MimeUtilConstants(); + + friend struct DefaultSingletonTraits; + + DISALLOW_COPY_AND_ASSIGN(MimeUtilConstants); +}; + +// IconTheme represents an icon theme as defined by the xdg icon theme spec. +// Example themes on GNOME include 'Human' and 'Mist'. +// Example themes on KDE include 'crystalsvg' and 'kdeclassic'. +class IconTheme { + public: + // A theme consists of multiple sub-directories, like '32x32' and 'scalable'. + class SubDirInfo { + public: + // See spec for details. + enum Type { + Fixed, + Scalable, + Threshold + }; + SubDirInfo() + : size(0), + type(Threshold), + max_size(0), + min_size(0), + threshold(2) { + } + size_t size; // Nominal size of the icons in this directory. + Type type; // Type of the icon size. + size_t max_size; // Maximum size that the icons can be scaled to. + size_t min_size; // Minimum size that the icons can be scaled to. + size_t threshold; // Maximum difference from desired size. 2 by default. + }; + + explicit IconTheme(const std::string& name); + + ~IconTheme() {} + + // Returns the path to an icon with the name |icon_name| and a size of |size| + // pixels. If the icon does not exist, but |inherits| is true, then look for + // the icon in the parent theme. + FilePath GetIconPath(const std::string& icon_name, int size, bool inherits); + + // Load a theme with the name |theme_name| into memory. Returns null if theme + // is invalid. + static IconTheme* LoadTheme(const std::string& theme_name); + + private: + // Returns the path to an icon with the name |icon_name| in |subdir|. + FilePath GetIconPathUnderSubdir(const std::string& icon_name, + const std::string& subdir); + + // Whether the theme loaded properly. + bool IsValid() { + return index_theme_loaded_; + } + + // Read and parse |file| which is usually named 'index.theme' per theme spec. + bool LoadIndexTheme(const FilePath& file); + + // Checks to see if the icons in |info| matches |size| (in pixels). Returns + // 0 if they match, or the size difference in pixels. + size_t MatchesSize(SubDirInfo* info, size_t size); + + // Yet another function to read a line. + std::string ReadLine(FILE* fp); + + // Set directories to search for icons to the comma-separated list |dirs|. + bool SetDirectories(const std::string& dirs); + + bool index_theme_loaded_; // True if an instance is properly loaded. + // store the scattered directories of this theme. + std::list dirs_; + + // store the subdirs of this theme and array index of |info_array_|. + std::map subdirs_; + scoped_ptr info_array_; // List of sub-directories. + std::string inherits_; // Name of the theme this one inherits from. +}; + +IconTheme::IconTheme(const std::string& name) + : index_theme_loaded_(false) { + base::ThreadRestrictions::AssertIOAllowed(); + // Iterate on all icon directories to find directories of the specified + // theme and load the first encountered index.theme. + MimeUtilConstants::IconDirMtimeMap::iterator iter; + FilePath theme_path; + MimeUtilConstants::IconDirMtimeMap* icon_dirs = + &MimeUtilConstants::GetInstance()->icon_dirs_; + for (iter = icon_dirs->begin(); iter != icon_dirs->end(); ++iter) { + theme_path = iter->first.Append(name); + if (!DirectoryExists(theme_path)) + continue; + FilePath theme_index = theme_path.Append("index.theme"); + if (!index_theme_loaded_ && PathExists(theme_index)) { + if (!LoadIndexTheme(theme_index)) + return; + index_theme_loaded_ = true; + } + dirs_.push_back(theme_path); + } +} + +FilePath IconTheme::GetIconPath(const std::string& icon_name, int size, + bool inherits) { + std::map::iterator subdir_iter; + FilePath icon_path; + + for (subdir_iter = subdirs_.begin(); + subdir_iter != subdirs_.end(); + ++subdir_iter) { + SubDirInfo* info = &info_array_[subdir_iter->second]; + if (MatchesSize(info, size) == 0) { + icon_path = GetIconPathUnderSubdir(icon_name, subdir_iter->first); + if (!icon_path.empty()) + return icon_path; + } + } + // Now looking for the mostly matched. + size_t min_delta_seen = 9999; + + for (subdir_iter = subdirs_.begin(); + subdir_iter != subdirs_.end(); + ++subdir_iter) { + SubDirInfo* info = &info_array_[subdir_iter->second]; + size_t delta = MatchesSize(info, size); + if (delta < min_delta_seen) { + FilePath path = GetIconPathUnderSubdir(icon_name, subdir_iter->first); + if (!path.empty()) { + min_delta_seen = delta; + icon_path = path; + } + } + } + + if (!icon_path.empty() || !inherits || inherits_ == "") + return icon_path; + + IconTheme* theme = LoadTheme(inherits_); + // Inheriting from itself means the theme is buggy but we shouldn't crash. + if (theme && theme != this) + return theme->GetIconPath(icon_name, size, inherits); + else + return FilePath(); +} + +IconTheme* IconTheme::LoadTheme(const std::string& theme_name) { + scoped_ptr theme; + MimeUtilConstants::IconThemeMap* icon_themes = + &MimeUtilConstants::GetInstance()->icon_themes_; + if (icon_themes->find(theme_name) != icon_themes->end()) { + theme.reset((*icon_themes)[theme_name]); + } else { + theme.reset(new IconTheme(theme_name)); + if (!theme->IsValid()) + theme.reset(); + (*icon_themes)[theme_name] = theme.get(); + } + return theme.release(); +} + +FilePath IconTheme::GetIconPathUnderSubdir(const std::string& icon_name, + const std::string& subdir) { + FilePath icon_path; + std::list::iterator dir_iter; + MimeUtilConstants::IconFormats* icon_formats = + &MimeUtilConstants::GetInstance()->icon_formats_; + for (dir_iter = dirs_.begin(); dir_iter != dirs_.end(); ++dir_iter) { + for (size_t i = 0; i < icon_formats->size(); ++i) { + icon_path = dir_iter->Append(subdir); + icon_path = icon_path.Append(icon_name + (*icon_formats)[i]); + if (PathExists(icon_path)) + return icon_path; + } + } + return FilePath(); +} + +bool IconTheme::LoadIndexTheme(const FilePath& file) { + FILE* fp = file_util::OpenFile(file, "r"); + SubDirInfo* current_info = NULL; + if (!fp) + return false; + + // Read entries. + while (!feof(fp) && !ferror(fp)) { + std::string buf = ReadLine(fp); + if (buf == "") + break; + + std::string entry; + TrimWhitespaceASCII(buf, TRIM_ALL, &entry); + if (entry.length() == 0 || entry[0] == '#') { + // Blank line or Comment. + continue; + } else if (entry[0] == '[' && info_array_.get()) { + current_info = NULL; + std::string subdir = entry.substr(1, entry.length() - 2); + if (subdirs_.find(subdir) != subdirs_.end()) + current_info = &info_array_[subdirs_[subdir]]; + } + + std::string key, value; + std::vector r; + base::SplitStringDontTrim(entry, '=', &r); + if (r.size() < 2) + continue; + + TrimWhitespaceASCII(r[0], TRIM_ALL, &key); + for (size_t i = 1; i < r.size(); i++) + value.append(r[i]); + TrimWhitespaceASCII(value, TRIM_ALL, &value); + + if (current_info) { + if (key == "Size") { + current_info->size = atoi(value.c_str()); + } else if (key == "Type") { + if (value == "Fixed") + current_info->type = SubDirInfo::Fixed; + else if (value == "Scalable") + current_info->type = SubDirInfo::Scalable; + else if (value == "Threshold") + current_info->type = SubDirInfo::Threshold; + } else if (key == "MaxSize") { + current_info->max_size = atoi(value.c_str()); + } else if (key == "MinSize") { + current_info->min_size = atoi(value.c_str()); + } else if (key == "Threshold") { + current_info->threshold = atoi(value.c_str()); + } + } else { + if (key.compare("Directories") == 0 && !info_array_.get()) { + if (!SetDirectories(value)) break; + } else if (key.compare("Inherits") == 0) { + if (value != "hicolor") + inherits_ = value; + } + } + } + + file_util::CloseFile(fp); + return info_array_.get() != NULL; +} + +size_t IconTheme::MatchesSize(SubDirInfo* info, size_t size) { + if (info->type == SubDirInfo::Fixed) { + if (size > info->size) + return size - info->size; + else + return info->size - size; + } else if (info->type == SubDirInfo::Scalable) { + if (size < info->min_size) + return info->min_size - size; + if (size > info->max_size) + return size - info->max_size; + return 0; + } else { + if (size + info->threshold < info->size) + return info->size - size - info->threshold; + if (size > info->size + info->threshold) + return size - info->size - info->threshold; + return 0; + } +} + +std::string IconTheme::ReadLine(FILE* fp) { + if (!fp) + return std::string(); + + std::string result; + const size_t kBufferSize = 100; + char buffer[kBufferSize]; + while ((fgets(buffer, kBufferSize - 1, fp)) != NULL) { + result += buffer; + size_t len = result.length(); + if (len == 0) + break; + char end = result[len - 1]; + if (end == '\n' || end == '\0') + break; + } + + return result; +} + +bool IconTheme::SetDirectories(const std::string& dirs) { + int num = 0; + std::string::size_type pos = 0, epos; + std::string dir; + while ((epos = dirs.find(',', pos)) != std::string::npos) { + TrimWhitespaceASCII(dirs.substr(pos, epos - pos), TRIM_ALL, &dir); + if (dir.length() == 0) { + DLOG(WARNING) << "Invalid index.theme: blank subdir"; + return false; + } + subdirs_[dir] = num++; + pos = epos + 1; + } + TrimWhitespaceASCII(dirs.substr(pos), TRIM_ALL, &dir); + if (dir.length() == 0) { + DLOG(WARNING) << "Invalid index.theme: blank subdir"; + return false; + } + subdirs_[dir] = num++; + info_array_.reset(new SubDirInfo[num]); + return true; +} + +bool CheckDirExistsAndGetMtime(const FilePath& dir, + base::Time* last_modified) { + if (!DirectoryExists(dir)) + return false; + base::PlatformFileInfo file_info; + if (!file_util::GetFileInfo(dir, &file_info)) + return false; + *last_modified = file_info.last_modified; + return true; +} + +// Make sure |dir| exists and add it to the list of icon directories. +void TryAddIconDir(const FilePath& dir) { + base::Time last_modified; + if (!CheckDirExistsAndGetMtime(dir, &last_modified)) + return; + MimeUtilConstants::GetInstance()->icon_dirs_[dir] = last_modified; +} + +// For a xdg directory |dir|, add the appropriate icon sub-directories. +void AddXDGDataDir(const FilePath& dir) { + if (!DirectoryExists(dir)) + return; + TryAddIconDir(dir.Append("icons")); + TryAddIconDir(dir.Append("pixmaps")); +} + +// Add all the xdg icon directories. +void InitIconDir() { + FilePath home = file_util::GetHomeDir(); + if (!home.empty()) { + FilePath legacy_data_dir(home); + legacy_data_dir = legacy_data_dir.AppendASCII(".icons"); + if (DirectoryExists(legacy_data_dir)) + TryAddIconDir(legacy_data_dir); + } + const char* env = getenv("XDG_DATA_HOME"); + if (env) { + AddXDGDataDir(FilePath(env)); + } else if (!home.empty()) { + FilePath local_data_dir(home); + local_data_dir = local_data_dir.AppendASCII(".local"); + local_data_dir = local_data_dir.AppendASCII("share"); + AddXDGDataDir(local_data_dir); + } + + env = getenv("XDG_DATA_DIRS"); + if (!env) { + AddXDGDataDir(FilePath("/usr/local/share")); + AddXDGDataDir(FilePath("/usr/share")); + } else { + std::string xdg_data_dirs = env; + std::string::size_type pos = 0, epos; + while ((epos = xdg_data_dirs.find(':', pos)) != std::string::npos) { + AddXDGDataDir(FilePath(xdg_data_dirs.substr(pos, epos - pos))); + pos = epos + 1; + } + AddXDGDataDir(FilePath(xdg_data_dirs.substr(pos))); + } +} + +void EnsureUpdated() { + MimeUtilConstants* constants = MimeUtilConstants::GetInstance(); + if (constants->last_check_time_.is_null()) { + constants->last_check_time_ = base::TimeTicks::Now(); + InitIconDir(); + return; + } + + // Per xdg theme spec, we should check the icon directories every so often + // for newly added icons. + base::TimeDelta time_since_last_check = + base::TimeTicks::Now() - constants->last_check_time_; + if (time_since_last_check.InSeconds() > constants->kUpdateIntervalInSeconds) { + constants->last_check_time_ += time_since_last_check; + + bool rescan_icon_dirs = false; + MimeUtilConstants::IconDirMtimeMap* icon_dirs = &constants->icon_dirs_; + MimeUtilConstants::IconDirMtimeMap::iterator iter; + for (iter = icon_dirs->begin(); iter != icon_dirs->end(); ++iter) { + base::Time last_modified; + if (!CheckDirExistsAndGetMtime(iter->first, &last_modified) || + last_modified != iter->second) { + rescan_icon_dirs = true; + break; + } + } + + if (rescan_icon_dirs) { + constants->icon_dirs_.clear(); + constants->icon_themes_.clear(); + InitIconDir(); + } + } +} + +// Find a fallback icon if we cannot find it in the default theme. +FilePath LookupFallbackIcon(const std::string& icon_name) { + MimeUtilConstants* constants = MimeUtilConstants::GetInstance(); + MimeUtilConstants::IconDirMtimeMap::iterator iter; + MimeUtilConstants::IconDirMtimeMap* icon_dirs = &constants->icon_dirs_; + MimeUtilConstants::IconFormats* icon_formats = &constants->icon_formats_; + for (iter = icon_dirs->begin(); iter != icon_dirs->end(); ++iter) { + for (size_t i = 0; i < icon_formats->size(); ++i) { + FilePath icon = iter->first.Append(icon_name + (*icon_formats)[i]); + if (PathExists(icon)) + return icon; + } + } + return FilePath(); +} + +// Initialize the list of default themes. +void InitDefaultThemes() { + IconTheme** default_themes = + MimeUtilConstants::GetInstance()->default_themes_; + + scoped_ptr env(base::Environment::Create()); + base::nix::DesktopEnvironment desktop_env = + base::nix::GetDesktopEnvironment(env.get()); + if (desktop_env == base::nix::DESKTOP_ENVIRONMENT_KDE3 || + desktop_env == base::nix::DESKTOP_ENVIRONMENT_KDE4) { + // KDE + std::string kde_default_theme; + std::string kde_fallback_theme; + + // TODO(thestig): Figure out how to get the current icon theme on KDE. + // Setting stored in ~/.kde/share/config/kdeglobals under Icons -> Theme. + default_themes[0] = NULL; + + // Try some reasonable defaults for KDE. + if (desktop_env == base::nix::DESKTOP_ENVIRONMENT_KDE3) { + // KDE 3 + kde_default_theme = "default.kde"; + kde_fallback_theme = "crystalsvg"; + } else { + // KDE 4 + kde_default_theme = "default.kde4"; + kde_fallback_theme = "oxygen"; + } + default_themes[1] = IconTheme::LoadTheme(kde_default_theme); + default_themes[2] = IconTheme::LoadTheme(kde_fallback_theme); + } else { + // Assume it's Gnome and use GTK to figure out the theme. + default_themes[1] = IconTheme::LoadTheme( + MimeUtilConstants::GetInstance()->icon_theme_name_); + default_themes[2] = IconTheme::LoadTheme("gnome"); + } + // hicolor needs to be last per icon theme spec. + default_themes[3] = IconTheme::LoadTheme("hicolor"); + + for (size_t i = 0; i < MimeUtilConstants::kDefaultThemeNum; i++) { + if (default_themes[i] == NULL) + continue; + // NULL out duplicate pointers. + for (size_t j = i + 1; j < MimeUtilConstants::kDefaultThemeNum; j++) { + if (default_themes[j] == default_themes[i]) + default_themes[j] = NULL; + } + } +} + +// Try to find an icon with the name |icon_name| that's |size| pixels. +FilePath LookupIconInDefaultTheme(const std::string& icon_name, int size) { + EnsureUpdated(); + MimeUtilConstants* constants = MimeUtilConstants::GetInstance(); + MimeUtilConstants::IconThemeMap* icon_themes = &constants->icon_themes_; + if (icon_themes->empty()) + InitDefaultThemes(); + + FilePath icon_path; + IconTheme** default_themes = constants->default_themes_; + for (size_t i = 0; i < MimeUtilConstants::kDefaultThemeNum; i++) { + if (default_themes[i]) { + icon_path = default_themes[i]->GetIconPath(icon_name, size, true); + if (!icon_path.empty()) + return icon_path; + } + } + return LookupFallbackIcon(icon_name); +} + +MimeUtilConstants::~MimeUtilConstants() { + for (size_t i = 0; i < kDefaultThemeNum; i++) + delete default_themes_[i]; +} + +} // namespace + +std::string GetFileMimeType(const FilePath& filepath) { + if (filepath.empty()) + return std::string(); + base::ThreadRestrictions::AssertIOAllowed(); + base::AutoLock scoped_lock(g_mime_util_xdg_lock.Get()); + return xdg_mime_get_mime_type_from_file_name(filepath.value().c_str()); +} + +std::string GetDataMimeType(const std::string& data) { + base::ThreadRestrictions::AssertIOAllowed(); + base::AutoLock scoped_lock(g_mime_util_xdg_lock.Get()); + return xdg_mime_get_mime_type_for_data(data.data(), data.length(), NULL); +} + +void SetIconThemeName(const std::string& name) { + // If the theme name is already loaded, do nothing. Chrome doesn't respond + // to changes in the system theme, so we never need to set this more than + // once. + if (!MimeUtilConstants::GetInstance()->icon_theme_name_.empty()) + return; + + MimeUtilConstants::GetInstance()->icon_theme_name_ = name; +} + +FilePath GetMimeIcon(const std::string& mime_type, size_t size) { + base::ThreadRestrictions::AssertIOAllowed(); + std::vector icon_names; + std::string icon_name; + FilePath icon_file; + + if (!mime_type.empty()) { + base::AutoLock scoped_lock(g_mime_util_xdg_lock.Get()); + const char *icon = xdg_mime_get_icon(mime_type.c_str()); + icon_name = std::string(icon ? icon : ""); + } + + if (icon_name.length()) + icon_names.push_back(icon_name); + + // For text/plain, try text-plain. + icon_name = mime_type; + for (size_t i = icon_name.find('/', 0); i != std::string::npos; + i = icon_name.find('/', i + 1)) { + icon_name[i] = '-'; + } + icon_names.push_back(icon_name); + // Also try gnome-mime-text-plain. + icon_names.push_back("gnome-mime-" + icon_name); + + // Try "deb" for "application/x-deb" in KDE 3. + size_t x_substr_pos = mime_type.find("/x-"); + if (x_substr_pos != std::string::npos) { + icon_name = mime_type.substr(x_substr_pos + 3); + icon_names.push_back(icon_name); + } + + // Try generic name like text-x-generic. + icon_name = mime_type.substr(0, mime_type.find('/')) + "-x-generic"; + icon_names.push_back(icon_name); + + // Last resort + icon_names.push_back("unknown"); + + for (size_t i = 0; i < icon_names.size(); i++) { + if (icon_names[i][0] == '/') { + icon_file = FilePath(icon_names[i]); + if (PathExists(icon_file)) + return icon_file; + } else { + icon_file = LookupIconInDefaultTheme(icon_names[i], size); + if (!icon_file.empty()) + return icon_file; + } + } + return FilePath(); +} + +} // namespace nix +} // namespace base diff --git a/base/nix/mime_util_xdg.h b/base/nix/mime_util_xdg.h new file mode 100644 index 0000000000..79eb782e60 --- /dev/null +++ b/base/nix/mime_util_xdg.h @@ -0,0 +1,42 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_NIX_MIME_UTIL_XDG_H_ +#define BASE_NIX_MIME_UTIL_XDG_H_ + +#include + +#include "base/base_export.h" +#include "build/build_config.h" + +namespace base { + +class FilePath; + +namespace nix { + +// Gets the mime type for a file based on its filename. The file path does not +// have to exist. Please note because it doesn't touch the disk, this does not +// work for directories. +// If the mime type is unknown, this will return application/octet-stream. +BASE_EXPORT std::string GetFileMimeType(const FilePath& filepath); + +// Get the mime type for a byte vector. +BASE_EXPORT std::string GetDataMimeType(const std::string& data); + +// Sets the current icon theme that we've detected from the desktop +// environment. Currently only works when we believe we're in a GTK +// environment. +BASE_EXPORT void SetIconThemeName(const std::string& name); + +// Gets the file name for an icon given the mime type and icon pixel size. +// Where an icon is a square image of |size| x |size|. +// This will try to find the closest matching icon. If that's not available, +// then a generic icon, and finally an empty FilePath if all else fails. +BASE_EXPORT FilePath GetMimeIcon(const std::string& mime_type, size_t size); + +} // namespace nix +} // namespace base + +#endif // BASE_NIX_MIME_UTIL_XDG_H_ diff --git a/base/nix/xdg_util.cc b/base/nix/xdg_util.cc new file mode 100644 index 0000000000..b3caf2abe0 --- /dev/null +++ b/base/nix/xdg_util.cc @@ -0,0 +1,115 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/nix/xdg_util.h" + +#include + +#include "base/environment.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/third_party/xdg_user_dirs/xdg_user_dir_lookup.h" + +namespace { + +// The KDE session version environment variable used in KDE 4. +const char kKDE4SessionEnvVar[] = "KDE_SESSION_VERSION"; + +} // namespace + +namespace base { +namespace nix { + +const char kDotConfigDir[] = ".config"; +const char kXdgConfigHomeEnvVar[] = "XDG_CONFIG_HOME"; + +FilePath GetXDGDirectory(Environment* env, const char* env_name, + const char* fallback_dir) { + FilePath path; + std::string env_value; + if (env->GetVar(env_name, &env_value) && !env_value.empty()) + path = FilePath(env_value); + else + path = file_util::GetHomeDir().Append(fallback_dir); + return path.StripTrailingSeparators(); +} + +FilePath GetXDGUserDirectory(const char* dir_name, const char* fallback_dir) { + FilePath path; + char* xdg_dir = xdg_user_dir_lookup(dir_name); + if (xdg_dir) { + path = FilePath(xdg_dir); + free(xdg_dir); + } else { + path = file_util::GetHomeDir().Append(fallback_dir); + } + return path.StripTrailingSeparators(); +} + +DesktopEnvironment GetDesktopEnvironment(Environment* env) { + // XDG_CURRENT_DESKTOP is the newest standard circa 2012. + std::string xdg_current_desktop; + if (env->GetVar("XDG_CURRENT_DESKTOP", &xdg_current_desktop)) { + // Not all desktop environments set this env var as of this writing. + if (xdg_current_desktop == "Unity") + return DESKTOP_ENVIRONMENT_UNITY; + else if (xdg_current_desktop == "GNOME") + return DESKTOP_ENVIRONMENT_GNOME; + } + + // DESKTOP_SESSION was what everyone used in 2010. + std::string desktop_session; + if (env->GetVar("DESKTOP_SESSION", &desktop_session)) { + if (desktop_session == "gnome") { + return DESKTOP_ENVIRONMENT_GNOME; + } else if (desktop_session == "kde4") { + return DESKTOP_ENVIRONMENT_KDE4; + } else if (desktop_session == "kde") { + // This may mean KDE4 on newer systems, so we have to check. + if (env->HasVar(kKDE4SessionEnvVar)) + return DESKTOP_ENVIRONMENT_KDE4; + return DESKTOP_ENVIRONMENT_KDE3; + } else if (desktop_session.find("xfce") != std::string::npos || + desktop_session == "xubuntu") { + return DESKTOP_ENVIRONMENT_XFCE; + } + } + + // Fall back on some older environment variables. + // Useful particularly in the DESKTOP_SESSION=default case. + if (env->HasVar("GNOME_DESKTOP_SESSION_ID")) { + return DESKTOP_ENVIRONMENT_GNOME; + } else if (env->HasVar("KDE_FULL_SESSION")) { + if (env->HasVar(kKDE4SessionEnvVar)) + return DESKTOP_ENVIRONMENT_KDE4; + return DESKTOP_ENVIRONMENT_KDE3; + } + + return DESKTOP_ENVIRONMENT_OTHER; +} + +const char* GetDesktopEnvironmentName(DesktopEnvironment env) { + switch (env) { + case DESKTOP_ENVIRONMENT_OTHER: + return NULL; + case DESKTOP_ENVIRONMENT_GNOME: + return "GNOME"; + case DESKTOP_ENVIRONMENT_KDE3: + return "KDE3"; + case DESKTOP_ENVIRONMENT_KDE4: + return "KDE4"; + case DESKTOP_ENVIRONMENT_UNITY: + return "UNITY"; + case DESKTOP_ENVIRONMENT_XFCE: + return "XFCE"; + } + return NULL; +} + +const char* GetDesktopEnvironmentName(Environment* env) { + return GetDesktopEnvironmentName(GetDesktopEnvironment(env)); +} + +} // namespace nix +} // namespace base diff --git a/base/nix/xdg_util.h b/base/nix/xdg_util.h new file mode 100644 index 0000000000..a8b778405f --- /dev/null +++ b/base/nix/xdg_util.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_NIX_XDG_UTIL_H_ +#define BASE_NIX_XDG_UTIL_H_ + +// XDG refers to http://en.wikipedia.org/wiki/Freedesktop.org . +// This file contains utilities found across free desktop environments. +// +// TODO(brettw) this file should be in app/x11, but is currently used by +// net. We should have a net API to allow the embedder to specify the behavior +// that it uses XDG for, and then move this file. + +#include "base/base_export.h" + +#ifdef nix +#error asdf +#endif + +namespace base { + +class Environment; +class FilePath; + +namespace nix { + +// The default XDG config directory name. +BASE_EXPORT extern const char kDotConfigDir[]; + +// The XDG config directory environment variable. +BASE_EXPORT extern const char kXdgConfigHomeEnvVar[]; + +// Utility function for getting XDG directories. +// |env_name| is the name of an environment variable that we want to use to get +// a directory path. |fallback_dir| is the directory relative to $HOME that we +// use if |env_name| cannot be found or is empty. |fallback_dir| may be NULL. +// Examples of |env_name| are XDG_CONFIG_HOME and XDG_DATA_HOME. +BASE_EXPORT FilePath GetXDGDirectory(Environment* env, const char* env_name, + const char* fallback_dir); + +// Wrapper around xdg_user_dir_lookup() from src/base/third_party/xdg-user-dirs +// This looks up "well known" user directories like the desktop and music +// folder. Examples of |dir_name| are DESKTOP and MUSIC. +BASE_EXPORT FilePath GetXDGUserDirectory(const char* dir_name, + const char* fallback_dir); + +enum DesktopEnvironment { + DESKTOP_ENVIRONMENT_OTHER, + DESKTOP_ENVIRONMENT_GNOME, + // KDE3 and KDE4 are sufficiently different that we count + // them as two different desktop environments here. + DESKTOP_ENVIRONMENT_KDE3, + DESKTOP_ENVIRONMENT_KDE4, + DESKTOP_ENVIRONMENT_UNITY, + DESKTOP_ENVIRONMENT_XFCE, +}; + +// Return an entry from the DesktopEnvironment enum with a best guess +// of which desktop environment we're using. We use this to know when +// to attempt to use preferences from the desktop environment -- +// proxy settings, password manager, etc. +BASE_EXPORT DesktopEnvironment GetDesktopEnvironment(Environment* env); + +// Return a string representation of the given desktop environment. +// May return NULL in the case of DESKTOP_ENVIRONMENT_OTHER. +BASE_EXPORT const char* GetDesktopEnvironmentName(DesktopEnvironment env); +// Convenience wrapper that calls GetDesktopEnvironment() first. +BASE_EXPORT const char* GetDesktopEnvironmentName(Environment* env); + +} // namespace nix +} // namespace base + +#endif // BASE_NIX_XDG_UTIL_H_ diff --git a/base/nix/xdg_util_unittest.cc b/base/nix/xdg_util_unittest.cc new file mode 100644 index 0000000000..2fc9d4c5c4 --- /dev/null +++ b/base/nix/xdg_util_unittest.cc @@ -0,0 +1,76 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/nix/xdg_util.h" + +#include "base/environment.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::_; +using ::testing::Return; +using ::testing::SetArgumentPointee; +using ::testing::StrEq; + +namespace base { +namespace nix { + +namespace { + +class MockEnvironment : public Environment { + public: + MOCK_METHOD2(GetVar, bool(const char*, std::string* result)); + MOCK_METHOD2(SetVar, bool(const char*, const std::string& new_value)); + MOCK_METHOD1(UnSetVar, bool(const char*)); +}; + +const char* kGnome = "gnome"; +const char* kKDE4 = "kde4"; +const char* kKDE = "kde"; +const char* kXFCE = "xfce"; + +} // namespace + +TEST(XDGUtilTest, GetDesktopEnvironmentGnome) { + MockEnvironment getter; + EXPECT_CALL(getter, GetVar(_, _)).WillRepeatedly(Return(false)); + EXPECT_CALL(getter, GetVar(StrEq("DESKTOP_SESSION"), _)) + .WillOnce(DoAll(SetArgumentPointee<1>(kGnome), Return(true))); + + EXPECT_EQ(DESKTOP_ENVIRONMENT_GNOME, + GetDesktopEnvironment(&getter)); +} + +TEST(XDGUtilTest, GetDesktopEnvironmentKDE4) { + MockEnvironment getter; + EXPECT_CALL(getter, GetVar(_, _)).WillRepeatedly(Return(false)); + EXPECT_CALL(getter, GetVar(StrEq("DESKTOP_SESSION"), _)) + .WillOnce(DoAll(SetArgumentPointee<1>(kKDE4), Return(true))); + + EXPECT_EQ(DESKTOP_ENVIRONMENT_KDE4, + GetDesktopEnvironment(&getter)); +} + +TEST(XDGUtilTest, GetDesktopEnvironmentKDE3) { + MockEnvironment getter; + EXPECT_CALL(getter, GetVar(_, _)).WillRepeatedly(Return(false)); + EXPECT_CALL(getter, GetVar(StrEq("DESKTOP_SESSION"), _)) + .WillOnce(DoAll(SetArgumentPointee<1>(kKDE), Return(true))); + + EXPECT_EQ(DESKTOP_ENVIRONMENT_KDE3, + GetDesktopEnvironment(&getter)); +} + +TEST(XDGUtilTest, GetDesktopEnvironmentXFCE) { + MockEnvironment getter; + EXPECT_CALL(getter, GetVar(_, _)).WillRepeatedly(Return(false)); + EXPECT_CALL(getter, GetVar(StrEq("DESKTOP_SESSION"), _)) + .WillOnce(DoAll(SetArgumentPointee<1>(kXFCE), Return(true))); + + EXPECT_EQ(DESKTOP_ENVIRONMENT_XFCE, + GetDesktopEnvironment(&getter)); +} + +} // namespace nix +} // namespace base diff --git a/base/observer_list.h b/base/observer_list.h new file mode 100644 index 0000000000..20f183d9cc --- /dev/null +++ b/base/observer_list.h @@ -0,0 +1,216 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_OBSERVER_LIST_H__ +#define BASE_OBSERVER_LIST_H__ + +#include +#include +#include + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/memory/weak_ptr.h" + +/////////////////////////////////////////////////////////////////////////////// +// +// OVERVIEW: +// +// A container for a list of observers. Unlike a normal STL vector or list, +// this container can be modified during iteration without invalidating the +// iterator. So, it safely handles the case of an observer removing itself +// or other observers from the list while observers are being notified. +// +// TYPICAL USAGE: +// +// class MyWidget { +// public: +// ... +// +// class Observer { +// public: +// virtual void OnFoo(MyWidget* w) = 0; +// virtual void OnBar(MyWidget* w, int x, int y) = 0; +// }; +// +// void AddObserver(Observer* obs) { +// observer_list_.AddObserver(obs); +// } +// +// void RemoveObserver(Observer* obs) { +// observer_list_.RemoveObserver(obs); +// } +// +// void NotifyFoo() { +// FOR_EACH_OBSERVER(Observer, observer_list_, OnFoo(this)); +// } +// +// void NotifyBar(int x, int y) { +// FOR_EACH_OBSERVER(Observer, observer_list_, OnBar(this, x, y)); +// } +// +// private: +// ObserverList observer_list_; +// }; +// +// +/////////////////////////////////////////////////////////////////////////////// + +template +class ObserverListThreadSafe; + +template +class ObserverListBase + : public base::SupportsWeakPtr > { + public: + // Enumeration of which observers are notified. + enum NotificationType { + // Specifies that any observers added during notification are notified. + // This is the default type if non type is provided to the constructor. + NOTIFY_ALL, + + // Specifies that observers added while sending out notification are not + // notified. + NOTIFY_EXISTING_ONLY + }; + + // An iterator class that can be used to access the list of observers. See + // also the FOR_EACH_OBSERVER macro defined below. + class Iterator { + public: + Iterator(ObserverListBase& list) + : list_(list.AsWeakPtr()), + index_(0), + max_index_(list.type_ == NOTIFY_ALL ? + std::numeric_limits::max() : + list.observers_.size()) { + ++list_->notify_depth_; + } + + ~Iterator() { + if (list_.get() && --list_->notify_depth_ == 0) + list_->Compact(); + } + + ObserverType* GetNext() { + if (!list_.get()) + return NULL; + ListType& observers = list_->observers_; + // Advance if the current element is null + size_t max_index = std::min(max_index_, observers.size()); + while (index_ < max_index && !observers[index_]) + ++index_; + return index_ < max_index ? observers[index_++] : NULL; + } + + private: + base::WeakPtr > list_; + size_t index_; + size_t max_index_; + }; + + ObserverListBase() : notify_depth_(0), type_(NOTIFY_ALL) {} + explicit ObserverListBase(NotificationType type) + : notify_depth_(0), type_(type) {} + + // Add an observer to the list. An observer should not be added to + // the same list more than once. + void AddObserver(ObserverType* obs) { + if (std::find(observers_.begin(), observers_.end(), obs) + != observers_.end()) { + NOTREACHED() << "Observers can only be added once!"; + return; + } + observers_.push_back(obs); + } + + // Remove an observer from the list if it is in the list. + void RemoveObserver(ObserverType* obs) { + typename ListType::iterator it = + std::find(observers_.begin(), observers_.end(), obs); + if (it != observers_.end()) { + if (notify_depth_) { + *it = 0; + } else { + observers_.erase(it); + } + } + } + + bool HasObserver(ObserverType* observer) const { + for (size_t i = 0; i < observers_.size(); ++i) { + if (observers_[i] == observer) + return true; + } + return false; + } + + void Clear() { + if (notify_depth_) { + for (typename ListType::iterator it = observers_.begin(); + it != observers_.end(); ++it) { + *it = 0; + } + } else { + observers_.clear(); + } + } + + size_t size() const { return observers_.size(); } + + protected: + void Compact() { + observers_.erase( + std::remove(observers_.begin(), observers_.end(), + static_cast(NULL)), observers_.end()); + } + + private: + friend class ObserverListThreadSafe; + + typedef std::vector ListType; + + ListType observers_; + int notify_depth_; + NotificationType type_; + + friend class ObserverListBase::Iterator; + + DISALLOW_COPY_AND_ASSIGN(ObserverListBase); +}; + +template +class ObserverList : public ObserverListBase { + public: + typedef typename ObserverListBase::NotificationType + NotificationType; + + ObserverList() {} + explicit ObserverList(NotificationType type) + : ObserverListBase(type) {} + + ~ObserverList() { + // When check_empty is true, assert that the list is empty on destruction. + if (check_empty) { + ObserverListBase::Compact(); + DCHECK_EQ(ObserverListBase::size(), 0U); + } + } + + bool might_have_observers() const { + return ObserverListBase::size() != 0; + } +}; + +#define FOR_EACH_OBSERVER(ObserverType, observer_list, func) \ + do { \ + if ((observer_list).might_have_observers()) { \ + ObserverListBase::Iterator it(observer_list); \ + ObserverType* obs; \ + while ((obs = it.GetNext()) != NULL) \ + obs->func; \ + } \ + } while (0) + +#endif // BASE_OBSERVER_LIST_H__ diff --git a/base/observer_list_threadsafe.h b/base/observer_list_threadsafe.h new file mode 100644 index 0000000000..70b4f11ffa --- /dev/null +++ b/base/observer_list_threadsafe.h @@ -0,0 +1,295 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_OBSERVER_LIST_THREADSAFE_H_ +#define BASE_OBSERVER_LIST_THREADSAFE_H_ + +#include +#include + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/observer_list.h" +#include "base/stl_util.h" +#include "base/threading/platform_thread.h" + +/////////////////////////////////////////////////////////////////////////////// +// +// OVERVIEW: +// +// A thread-safe container for a list of observers. +// This is similar to the observer_list (see observer_list.h), but it +// is more robust for multi-threaded situations. +// +// The following use cases are supported: +// * Observers can register for notifications from any thread. +// Callbacks to the observer will occur on the same thread where +// the observer initially called AddObserver() from. +// * Any thread may trigger a notification via Notify(). +// * Observers can remove themselves from the observer list inside +// of a callback. +// * If one thread is notifying observers concurrently with an observer +// removing itself from the observer list, the notifications will +// be silently dropped. +// +// The drawback of the threadsafe observer list is that notifications +// are not as real-time as the non-threadsafe version of this class. +// Notifications will always be done via PostTask() to another thread, +// whereas with the non-thread-safe observer_list, notifications happen +// synchronously and immediately. +// +// IMPLEMENTATION NOTES +// The ObserverListThreadSafe maintains an ObserverList for each thread +// which uses the ThreadSafeObserver. When Notifying the observers, +// we simply call PostTask to each registered thread, and then each thread +// will notify its regular ObserverList. +// +/////////////////////////////////////////////////////////////////////////////// + +// Forward declaration for ObserverListThreadSafeTraits. +template +class ObserverListThreadSafe; + +// An UnboundMethod is a wrapper for a method where the actual object is +// provided at Run dispatch time. +template +class UnboundMethod { + public: + UnboundMethod(Method m, const Params& p) : m_(m), p_(p) { + COMPILE_ASSERT( + (base::internal::ParamsUseScopedRefptrCorrectly::value), + badunboundmethodparams); + } + void Run(T* obj) const { + DispatchToMethod(obj, m_, p_); + } + private: + Method m_; + Params p_; +}; + +// This class is used to work around VS2005 not accepting: +// +// friend class +// base::RefCountedThreadSafe >; +// +// Instead of friending the class, we could friend the actual function +// which calls delete. However, this ends up being +// RefCountedThreadSafe::DeleteInternal(), which is private. So we +// define our own templated traits class so we can friend it. +template +struct ObserverListThreadSafeTraits { + static void Destruct(const ObserverListThreadSafe* x) { + delete x; + } +}; + +template +class ObserverListThreadSafe + : public base::RefCountedThreadSafe< + ObserverListThreadSafe, + ObserverListThreadSafeTraits > { + public: + typedef typename ObserverList::NotificationType + NotificationType; + + ObserverListThreadSafe() + : type_(ObserverListBase::NOTIFY_ALL) {} + explicit ObserverListThreadSafe(NotificationType type) : type_(type) {} + + // Add an observer to the list. An observer should not be added to + // the same list more than once. + void AddObserver(ObserverType* obs) { + // If there is not a current MessageLoop, it is impossible to notify on it, + // so do not add the observer. + if (!base::MessageLoop::current()) + return; + + ObserverList* list = NULL; + base::PlatformThreadId thread_id = base::PlatformThread::CurrentId(); + { + base::AutoLock lock(list_lock_); + if (observer_lists_.find(thread_id) == observer_lists_.end()) + observer_lists_[thread_id] = new ObserverListContext(type_); + list = &(observer_lists_[thread_id]->list); + } + list->AddObserver(obs); + } + + // Remove an observer from the list if it is in the list. + // If there are pending notifications in-transit to the observer, they will + // be aborted. + // If the observer to be removed is in the list, RemoveObserver MUST + // be called from the same thread which called AddObserver. + void RemoveObserver(ObserverType* obs) { + ObserverListContext* context = NULL; + ObserverList* list = NULL; + base::PlatformThreadId thread_id = base::PlatformThread::CurrentId(); + { + base::AutoLock lock(list_lock_); + typename ObserversListMap::iterator it = observer_lists_.find(thread_id); + if (it == observer_lists_.end()) { + // This will happen if we try to remove an observer on a thread + // we never added an observer for. + return; + } + context = it->second; + list = &context->list; + + // If we're about to remove the last observer from the list, + // then we can remove this observer_list entirely. + if (list->HasObserver(obs) && list->size() == 1) + observer_lists_.erase(it); + } + list->RemoveObserver(obs); + + // If RemoveObserver is called from a notification, the size will be + // nonzero. Instead of deleting here, the NotifyWrapper will delete + // when it finishes iterating. + if (list->size() == 0) + delete context; + } + + // Verifies that the list is currently empty (i.e. there are no observers). + void AssertEmpty() const { + base::AutoLock lock(list_lock_); + DCHECK(observer_lists_.empty()); + } + + // Notify methods. + // Make a thread-safe callback to each Observer in the list. + // Note, these calls are effectively asynchronous. You cannot assume + // that at the completion of the Notify call that all Observers have + // been Notified. The notification may still be pending delivery. + template + void Notify(Method m) { + UnboundMethod method(m, MakeTuple()); + Notify(method); + } + + template + void Notify(Method m, const A& a) { + UnboundMethod > method(m, MakeTuple(a)); + Notify >(method); + } + + template + void Notify(Method m, const A& a, const B& b) { + UnboundMethod > method( + m, MakeTuple(a, b)); + Notify >(method); + } + + template + void Notify(Method m, const A& a, const B& b, const C& c) { + UnboundMethod > method( + m, MakeTuple(a, b, c)); + Notify >(method); + } + + template + void Notify(Method m, const A& a, const B& b, const C& c, const D& d) { + UnboundMethod > method( + m, MakeTuple(a, b, c, d)); + Notify >(method); + } + + // TODO(mbelshe): Add more wrappers for Notify() with more arguments. + + private: + // See comment above ObserverListThreadSafeTraits' definition. + friend struct ObserverListThreadSafeTraits; + + struct ObserverListContext { + explicit ObserverListContext(NotificationType type) + : loop(base::MessageLoopProxy::current()), + list(type) { + } + + scoped_refptr loop; + ObserverList list; + + DISALLOW_COPY_AND_ASSIGN(ObserverListContext); + }; + + ~ObserverListThreadSafe() { + STLDeleteValues(&observer_lists_); + } + + template + void Notify(const UnboundMethod& method) { + base::AutoLock lock(list_lock_); + typename ObserversListMap::iterator it; + for (it = observer_lists_.begin(); it != observer_lists_.end(); ++it) { + ObserverListContext* context = (*it).second; + context->loop->PostTask( + FROM_HERE, + base::Bind(&ObserverListThreadSafe:: + template NotifyWrapper, this, context, method)); + } + } + + // Wrapper which is called to fire the notifications for each thread's + // ObserverList. This function MUST be called on the thread which owns + // the unsafe ObserverList. + template + void NotifyWrapper(ObserverListContext* context, + const UnboundMethod& method) { + + // Check that this list still needs notifications. + { + base::AutoLock lock(list_lock_); + typename ObserversListMap::iterator it = + observer_lists_.find(base::PlatformThread::CurrentId()); + + // The ObserverList could have been removed already. In fact, it could + // have been removed and then re-added! If the master list's loop + // does not match this one, then we do not need to finish this + // notification. + if (it == observer_lists_.end() || it->second != context) + return; + } + + { + typename ObserverList::Iterator it(context->list); + ObserverType* obs; + while ((obs = it.GetNext()) != NULL) + method.Run(obs); + } + + // If there are no more observers on the list, we can now delete it. + if (context->list.size() == 0) { + { + base::AutoLock lock(list_lock_); + // Remove |list| if it's not already removed. + // This can happen if multiple observers got removed in a notification. + // See http://crbug.com/55725. + typename ObserversListMap::iterator it = + observer_lists_.find(base::PlatformThread::CurrentId()); + if (it != observer_lists_.end() && it->second == context) + observer_lists_.erase(it); + } + delete context; + } + } + + // Key by PlatformThreadId because in tests, clients can attempt to remove + // observers without a MessageLoop. If this were keyed by MessageLoop, that + // operation would be silently ignored, leaving garbage in the ObserverList. + typedef std::map + ObserversListMap; + + mutable base::Lock list_lock_; // Protects the observer_lists_. + ObserversListMap observer_lists_; + const NotificationType type_; + + DISALLOW_COPY_AND_ASSIGN(ObserverListThreadSafe); +}; + +#endif // BASE_OBSERVER_LIST_THREADSAFE_H_ diff --git a/base/observer_list_unittest.cc b/base/observer_list_unittest.cc new file mode 100644 index 0000000000..57843f401f --- /dev/null +++ b/base/observer_list_unittest.cc @@ -0,0 +1,549 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/observer_list.h" +#include "base/observer_list_threadsafe.h" + +#include + +#include "base/compiler_specific.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/threading/platform_thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace { + +class Foo { + public: + virtual void Observe(int x) = 0; + virtual ~Foo() {} +}; + +class Adder : public Foo { + public: + explicit Adder(int scaler) : total(0), scaler_(scaler) {} + virtual void Observe(int x) OVERRIDE { + total += x * scaler_; + } + virtual ~Adder() {} + int total; + + private: + int scaler_; +}; + +class Disrupter : public Foo { + public: + Disrupter(ObserverList* list, Foo* doomed) + : list_(list), + doomed_(doomed) { + } + virtual ~Disrupter() {} + virtual void Observe(int x) OVERRIDE { + list_->RemoveObserver(doomed_); + } + + private: + ObserverList* list_; + Foo* doomed_; +}; + +class ThreadSafeDisrupter : public Foo { + public: + ThreadSafeDisrupter(ObserverListThreadSafe* list, Foo* doomed) + : list_(list), + doomed_(doomed) { + } + virtual ~ThreadSafeDisrupter() {} + virtual void Observe(int x) OVERRIDE { + list_->RemoveObserver(doomed_); + } + + private: + ObserverListThreadSafe* list_; + Foo* doomed_; +}; + +template +class AddInObserve : public Foo { + public: + explicit AddInObserve(ObserverListType* observer_list) + : added(false), + observer_list(observer_list), + adder(1) { + } + + virtual void Observe(int x) OVERRIDE { + if (!added) { + added = true; + observer_list->AddObserver(&adder); + } + } + + bool added; + ObserverListType* observer_list; + Adder adder; +}; + + +static const int kThreadRunTime = 2000; // ms to run the multi-threaded test. + +// A thread for use in the ThreadSafeObserver test +// which will add and remove itself from the notification +// list repeatedly. +class AddRemoveThread : public PlatformThread::Delegate, + public Foo { + public: + AddRemoveThread(ObserverListThreadSafe* list, bool notify) + : list_(list), + loop_(NULL), + in_list_(false), + start_(Time::Now()), + count_observes_(0), + count_addtask_(0), + do_notifies_(notify), + weak_factory_(this) { + } + + virtual ~AddRemoveThread() { + } + + virtual void ThreadMain() OVERRIDE { + loop_ = new MessageLoop(); // Fire up a message loop. + loop_->PostTask( + FROM_HERE, + base::Bind(&AddRemoveThread::AddTask, weak_factory_.GetWeakPtr())); + loop_->Run(); + //LOG(ERROR) << "Loop 0x" << std::hex << loop_ << " done. " << + // count_observes_ << ", " << count_addtask_; + delete loop_; + loop_ = reinterpret_cast(0xdeadbeef); + delete this; + } + + // This task just keeps posting to itself in an attempt + // to race with the notifier. + void AddTask() { + count_addtask_++; + + if ((Time::Now() - start_).InMilliseconds() > kThreadRunTime) { + VLOG(1) << "DONE!"; + return; + } + + if (!in_list_) { + list_->AddObserver(this); + in_list_ = true; + } + + if (do_notifies_) { + list_->Notify(&Foo::Observe, 10); + } + + loop_->PostTask( + FROM_HERE, + base::Bind(&AddRemoveThread::AddTask, weak_factory_.GetWeakPtr())); + } + + void Quit() { + loop_->PostTask(FROM_HERE, MessageLoop::QuitWhenIdleClosure()); + } + + virtual void Observe(int x) OVERRIDE { + count_observes_++; + + // If we're getting called after we removed ourselves from + // the list, that is very bad! + DCHECK(in_list_); + + // This callback should fire on the appropriate thread + EXPECT_EQ(loop_, MessageLoop::current()); + + list_->RemoveObserver(this); + in_list_ = false; + } + + private: + ObserverListThreadSafe* list_; + MessageLoop* loop_; + bool in_list_; // Are we currently registered for notifications. + // in_list_ is only used on |this| thread. + Time start_; // The time we started the test. + + int count_observes_; // Number of times we observed. + int count_addtask_; // Number of times thread AddTask was called + bool do_notifies_; // Whether these threads should do notifications. + + base::WeakPtrFactory weak_factory_; +}; + +TEST(ObserverListTest, BasicTest) { + ObserverList observer_list; + Adder a(1), b(-1), c(1), d(-1), e(-1); + Disrupter evil(&observer_list, &c); + + observer_list.AddObserver(&a); + observer_list.AddObserver(&b); + + FOR_EACH_OBSERVER(Foo, observer_list, Observe(10)); + + observer_list.AddObserver(&evil); + observer_list.AddObserver(&c); + observer_list.AddObserver(&d); + + // Removing an observer not in the list should do nothing. + observer_list.RemoveObserver(&e); + + FOR_EACH_OBSERVER(Foo, observer_list, Observe(10)); + + EXPECT_EQ(20, a.total); + EXPECT_EQ(-20, b.total); + EXPECT_EQ(0, c.total); + EXPECT_EQ(-10, d.total); + EXPECT_EQ(0, e.total); +} + +TEST(ObserverListThreadSafeTest, BasicTest) { + MessageLoop loop; + + scoped_refptr > observer_list( + new ObserverListThreadSafe); + Adder a(1); + Adder b(-1); + Adder c(1); + Adder d(-1); + ThreadSafeDisrupter evil(observer_list.get(), &c); + + observer_list->AddObserver(&a); + observer_list->AddObserver(&b); + + observer_list->Notify(&Foo::Observe, 10); + RunLoop().RunUntilIdle(); + + observer_list->AddObserver(&evil); + observer_list->AddObserver(&c); + observer_list->AddObserver(&d); + + observer_list->Notify(&Foo::Observe, 10); + RunLoop().RunUntilIdle(); + + EXPECT_EQ(20, a.total); + EXPECT_EQ(-20, b.total); + EXPECT_EQ(0, c.total); + EXPECT_EQ(-10, d.total); +} + +TEST(ObserverListThreadSafeTest, RemoveObserver) { + MessageLoop loop; + + scoped_refptr > observer_list( + new ObserverListThreadSafe); + Adder a(1), b(1); + + // A workaround for the compiler bug. See http://crbug.com/121960. + EXPECT_NE(&a, &b); + + // Should do nothing. + observer_list->RemoveObserver(&a); + observer_list->RemoveObserver(&b); + + observer_list->Notify(&Foo::Observe, 10); + RunLoop().RunUntilIdle(); + + EXPECT_EQ(0, a.total); + EXPECT_EQ(0, b.total); + + observer_list->AddObserver(&a); + + // Should also do nothing. + observer_list->RemoveObserver(&b); + + observer_list->Notify(&Foo::Observe, 10); + RunLoop().RunUntilIdle(); + + EXPECT_EQ(10, a.total); + EXPECT_EQ(0, b.total); +} + +TEST(ObserverListThreadSafeTest, WithoutMessageLoop) { + scoped_refptr > observer_list( + new ObserverListThreadSafe); + + Adder a(1), b(1), c(1); + + // No MessageLoop, so these should not be added. + observer_list->AddObserver(&a); + observer_list->AddObserver(&b); + + { + // Add c when there's a loop. + MessageLoop loop; + observer_list->AddObserver(&c); + + observer_list->Notify(&Foo::Observe, 10); + RunLoop().RunUntilIdle(); + + EXPECT_EQ(0, a.total); + EXPECT_EQ(0, b.total); + EXPECT_EQ(10, c.total); + + // Now add a when there's a loop. + observer_list->AddObserver(&a); + + // Remove c when there's a loop. + observer_list->RemoveObserver(&c); + + // Notify again. + observer_list->Notify(&Foo::Observe, 20); + RunLoop().RunUntilIdle(); + + EXPECT_EQ(20, a.total); + EXPECT_EQ(0, b.total); + EXPECT_EQ(10, c.total); + } + + // Removing should always succeed with or without a loop. + observer_list->RemoveObserver(&a); + + // Notifying should not fail but should also be a no-op. + MessageLoop loop; + observer_list->AddObserver(&b); + observer_list->Notify(&Foo::Observe, 30); + RunLoop().RunUntilIdle(); + + EXPECT_EQ(20, a.total); + EXPECT_EQ(30, b.total); + EXPECT_EQ(10, c.total); +} + +class FooRemover : public Foo { + public: + explicit FooRemover(ObserverListThreadSafe* list) : list_(list) {} + virtual ~FooRemover() {} + + void AddFooToRemove(Foo* foo) { + foos_.push_back(foo); + } + + virtual void Observe(int x) OVERRIDE { + std::vector tmp; + tmp.swap(foos_); + for (std::vector::iterator it = tmp.begin(); + it != tmp.end(); ++it) { + list_->RemoveObserver(*it); + } + } + + private: + const scoped_refptr > list_; + std::vector foos_; +}; + +TEST(ObserverListThreadSafeTest, RemoveMultipleObservers) { + MessageLoop loop; + scoped_refptr > observer_list( + new ObserverListThreadSafe); + + FooRemover a(observer_list.get()); + Adder b(1); + + observer_list->AddObserver(&a); + observer_list->AddObserver(&b); + + a.AddFooToRemove(&a); + a.AddFooToRemove(&b); + + observer_list->Notify(&Foo::Observe, 1); + RunLoop().RunUntilIdle(); +} + +// A test driver for a multi-threaded notification loop. Runs a number +// of observer threads, each of which constantly adds/removes itself +// from the observer list. Optionally, if cross_thread_notifies is set +// to true, the observer threads will also trigger notifications to +// all observers. +static void ThreadSafeObserverHarness(int num_threads, + bool cross_thread_notifies) { + MessageLoop loop; + + const int kMaxThreads = 15; + num_threads = num_threads > kMaxThreads ? kMaxThreads : num_threads; + + scoped_refptr > observer_list( + new ObserverListThreadSafe); + Adder a(1); + Adder b(-1); + Adder c(1); + Adder d(-1); + + observer_list->AddObserver(&a); + observer_list->AddObserver(&b); + + AddRemoveThread* threaded_observer[kMaxThreads]; + base::PlatformThreadHandle threads[kMaxThreads]; + for (int index = 0; index < num_threads; index++) { + threaded_observer[index] = new AddRemoveThread(observer_list.get(), false); + EXPECT_TRUE(PlatformThread::Create(0, + threaded_observer[index], &threads[index])); + } + + Time start = Time::Now(); + while (true) { + if ((Time::Now() - start).InMilliseconds() > kThreadRunTime) + break; + + observer_list->Notify(&Foo::Observe, 10); + + RunLoop().RunUntilIdle(); + } + + for (int index = 0; index < num_threads; index++) { + threaded_observer[index]->Quit(); + PlatformThread::Join(threads[index]); + } +} + +TEST(ObserverListThreadSafeTest, CrossThreadObserver) { + // Use 7 observer threads. Notifications only come from + // the main thread. + ThreadSafeObserverHarness(7, false); +} + +TEST(ObserverListThreadSafeTest, CrossThreadNotifications) { + // Use 3 observer threads. Notifications will fire from + // the main thread and all 3 observer threads. + ThreadSafeObserverHarness(3, true); +} + +TEST(ObserverListThreadSafeTest, OutlivesMessageLoop) { + MessageLoop* loop = new MessageLoop; + scoped_refptr > observer_list( + new ObserverListThreadSafe); + + Adder a(1); + observer_list->AddObserver(&a); + delete loop; + // Test passes if we don't crash here. + observer_list->Notify(&Foo::Observe, 1); +} + +TEST(ObserverListTest, Existing) { + ObserverList observer_list(ObserverList::NOTIFY_EXISTING_ONLY); + Adder a(1); + AddInObserve > b(&observer_list); + + observer_list.AddObserver(&a); + observer_list.AddObserver(&b); + + FOR_EACH_OBSERVER(Foo, observer_list, Observe(1)); + + EXPECT_TRUE(b.added); + // B's adder should not have been notified because it was added during + // notificaiton. + EXPECT_EQ(0, b.adder.total); + + // Notify again to make sure b's adder is notified. + FOR_EACH_OBSERVER(Foo, observer_list, Observe(1)); + EXPECT_EQ(1, b.adder.total); +} + +// Same as above, but for ObserverListThreadSafe +TEST(ObserverListThreadSafeTest, Existing) { + MessageLoop loop; + scoped_refptr > observer_list( + new ObserverListThreadSafe(ObserverList::NOTIFY_EXISTING_ONLY)); + Adder a(1); + AddInObserve > b(observer_list.get()); + + observer_list->AddObserver(&a); + observer_list->AddObserver(&b); + + observer_list->Notify(&Foo::Observe, 1); + RunLoop().RunUntilIdle(); + + EXPECT_TRUE(b.added); + // B's adder should not have been notified because it was added during + // notificaiton. + EXPECT_EQ(0, b.adder.total); + + // Notify again to make sure b's adder is notified. + observer_list->Notify(&Foo::Observe, 1); + RunLoop().RunUntilIdle(); + EXPECT_EQ(1, b.adder.total); +} + +class AddInClearObserve : public Foo { + public: + explicit AddInClearObserve(ObserverList* list) + : list_(list), added_(false), adder_(1) {} + + virtual void Observe(int /* x */) OVERRIDE { + list_->Clear(); + list_->AddObserver(&adder_); + added_ = true; + } + + bool added() const { return added_; } + const Adder& adder() const { return adder_; } + + private: + ObserverList* const list_; + + bool added_; + Adder adder_; +}; + +TEST(ObserverListTest, ClearNotifyAll) { + ObserverList observer_list; + AddInClearObserve a(&observer_list); + + observer_list.AddObserver(&a); + + FOR_EACH_OBSERVER(Foo, observer_list, Observe(1)); + EXPECT_TRUE(a.added()); + EXPECT_EQ(1, a.adder().total) + << "Adder should observe once and have sum of 1."; +} + +TEST(ObserverListTest, ClearNotifyExistingOnly) { + ObserverList observer_list(ObserverList::NOTIFY_EXISTING_ONLY); + AddInClearObserve a(&observer_list); + + observer_list.AddObserver(&a); + + FOR_EACH_OBSERVER(Foo, observer_list, Observe(1)); + EXPECT_TRUE(a.added()); + EXPECT_EQ(0, a.adder().total) + << "Adder should not observe, so sum should still be 0."; +} + +class ListDestructor : public Foo { + public: + explicit ListDestructor(ObserverList* list) : list_(list) {} + virtual ~ListDestructor() {} + + virtual void Observe(int x) OVERRIDE { + delete list_; + } + + private: + ObserverList* list_; +}; + + +TEST(ObserverListTest, IteratorOutlivesList) { + ObserverList* observer_list = new ObserverList; + ListDestructor a(observer_list); + observer_list->AddObserver(&a); + + FOR_EACH_OBSERVER(Foo, *observer_list, Observe(0)); + // If this test fails, there'll be Valgrind errors when this function goes out + // of scope. +} + +} // namespace +} // namespace base diff --git a/base/os_compat_android.cc b/base/os_compat_android.cc new file mode 100644 index 0000000000..2643dc30e1 --- /dev/null +++ b/base/os_compat_android.cc @@ -0,0 +1,170 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/os_compat_android.h" + +#include +#include +#include +#include +#include +#include + +#include "base/rand_util.h" +#include "base/strings/string_piece.h" +#include "base/strings/stringprintf.h" + +extern "C" { +// There is no futimes() avaiable in Bionic, so we provide our own +// implementation until it is there. +int futimes(int fd, const struct timeval tv[2]) { + if (tv == NULL) + return syscall(__NR_utimensat, fd, NULL, NULL, 0); + + if (tv[0].tv_usec < 0 || tv[0].tv_usec >= 1000000 || + tv[1].tv_usec < 0 || tv[1].tv_usec >= 1000000) { + errno = EINVAL; + return -1; + } + + // Convert timeval to timespec. + struct timespec ts[2]; + ts[0].tv_sec = tv[0].tv_sec; + ts[0].tv_nsec = tv[0].tv_usec * 1000; + ts[1].tv_sec = tv[1].tv_sec; + ts[1].tv_nsec = tv[1].tv_usec * 1000; + return syscall(__NR_utimensat, fd, NULL, ts, 0); +} + +// Android has only timegm64() and no timegm(). +// We replicate the behaviour of timegm() when the result overflows time_t. +time_t timegm(struct tm* const t) { + // time_t is signed on Android. + static const time_t kTimeMax = ~(1 << (sizeof(time_t) * CHAR_BIT - 1)); + static const time_t kTimeMin = (1 << (sizeof(time_t) * CHAR_BIT - 1)); + time64_t result = timegm64(t); + if (result < kTimeMin || result > kTimeMax) + return -1; + return result; +} + +// The following is only needed when building with GCC 4.6 or higher +// (i.e. not with Android GCC 4.4.3, nor with Clang). +// +// GCC is now capable of optimizing successive calls to sin() and cos() into +// a single call to sincos(). This means that source code that looks like: +// +// double c, s; +// c = cos(angle); +// s = sin(angle); +// +// Will generate machine code that looks like: +// +// double c, s; +// sincos(angle, &s, &c); +// +// Unfortunately, sincos() and friends are not part of the Android libm.so +// library provided by the NDK for API level 9. When the optimization kicks +// in, it makes the final build fail with a puzzling message (puzzling +// because 'sincos' doesn't appear anywhere in the sources!). +// +// To solve this, we provide our own implementation of the sincos() function +// and related friends. Note that we must also explicitely tell GCC to disable +// optimizations when generating these. Otherwise, the generated machine code +// for each function would simply end up calling itself, resulting in a +// runtime crash due to stack overflow. +// +#if defined(__GNUC__) && !defined(__clang__) + +// For the record, Clang does not support the 'optimize' attribute. +// In the unlikely event that it begins performing this optimization too, +// we'll have to find a different way to achieve this. NOTE: Tested with O1 +// which still performs the optimization. +// +#define GCC_NO_OPTIMIZE __attribute__((optimize("O0"))) + +GCC_NO_OPTIMIZE +void sincos(double angle, double* s, double *c) { + *c = cos(angle); + *s = sin(angle); +} + +GCC_NO_OPTIMIZE +void sincosf(float angle, float* s, float* c) { + *c = cosf(angle); + *s = sinf(angle); +} + +#endif // __GNUC__ && !__clang__ + +// An implementation of mkdtemp, since it is not exposed by the NDK +// for native API level 9 that we target. +// +// For any changes in the mkdtemp function, you should manually run the unittest +// OsCompatAndroidTest.DISABLED_TestMkdTemp in your local machine to check if it +// passes. Please don't enable it, since it creates a directory and may be +// source of flakyness. +char* mkdtemp(char* path) { + if (path == NULL) { + errno = EINVAL; + return NULL; + } + + const int path_len = strlen(path); + + // The last six characters of 'path' must be XXXXXX. + const base::StringPiece kSuffix("XXXXXX"); + const int kSuffixLen = kSuffix.length(); + if (!base::StringPiece(path, path_len).ends_with(kSuffix)) { + errno = EINVAL; + return NULL; + } + + // If the path contains a directory, as in /tmp/foo/XXXXXXXX, make sure + // that /tmp/foo exists, otherwise we're going to loop a really long + // time for nothing below + char* dirsep = strrchr(path, '/'); + if (dirsep != NULL) { + struct stat st; + int ret; + + *dirsep = '\0'; // Terminating directory path temporarily + + ret = stat(path, &st); + + *dirsep = '/'; // Restoring directory separator + if (ret < 0) // Directory probably does not exist + return NULL; + if (!S_ISDIR(st.st_mode)) { // Not a directory + errno = ENOTDIR; + return NULL; + } + } + + // Max number of tries using different random suffixes. + const int kMaxTries = 100; + + // Now loop until we CAN create a directory by that name or we reach the max + // number of tries. + for (int i = 0; i < kMaxTries; ++i) { + // Fill the suffix XXXXXX with a random string composed of a-z chars. + for (int pos = 0; pos < kSuffixLen; ++pos) { + char rand_char = static_cast(base::RandInt('a', 'z')); + path[path_len - kSuffixLen + pos] = rand_char; + } + if (mkdir(path, 0700) == 0) { + // We just created the directory succesfully. + return path; + } + if (errno != EEXIST) { + // The directory doesn't exist, but an error occured + return NULL; + } + } + + // We reached the max number of tries. + return NULL; +} + +} // extern "C" diff --git a/base/os_compat_android.h b/base/os_compat_android.h new file mode 100644 index 0000000000..0f2544496d --- /dev/null +++ b/base/os_compat_android.h @@ -0,0 +1,28 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_OS_COMPAT_ANDROID_H_ +#define BASE_OS_COMPAT_ANDROID_H_ + +#include +#include +#include + +// Not implemented in Bionic. +extern "C" int futimes(int fd, const struct timeval tv[2]); + +// Not exposed or implemented in Bionic. +extern "C" char* mkdtemp(char* path); + +// Android has no timegm(). +extern "C" time_t timegm(struct tm* const t); + +// The lockf() function is not available on Android; we translate to flock(). +#define F_LOCK LOCK_EX +#define F_ULOCK LOCK_UN +inline int lockf(int fd, int cmd, off_t ignored_len) { + return flock(fd, cmd); +} + +#endif // BASE_OS_COMPAT_ANDROID_H_ diff --git a/base/os_compat_android_unittest.cc b/base/os_compat_android_unittest.cc new file mode 100644 index 0000000000..c749b6a223 --- /dev/null +++ b/base/os_compat_android_unittest.cc @@ -0,0 +1,41 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/os_compat_android.h" + +#include "base/file_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +typedef testing::Test OsCompatAndroidTest; + +// Keep this Unittest DISABLED_ , because it actually creates a directory in the +// device and it may be source of flakyness. For any changes in the mkdtemp +// function, you should run this unittest in your local machine to check if it +// passes. +TEST_F(OsCompatAndroidTest, DISABLED_TestMkdTemp) { + FilePath tmp_dir; + EXPECT_TRUE(file_util::GetTempDir(&tmp_dir)); + + // Not six XXXXXX at the suffix of the path. + FilePath sub_dir = tmp_dir.Append("XX"); + std::string sub_dir_string = sub_dir.value(); + // this should be OK since mkdtemp just replaces characters in place + char* buffer = const_cast(sub_dir_string.c_str()); + EXPECT_EQ(NULL, mkdtemp(buffer)); + + // Directory does not exist + char invalid_path2[] = "doesntoexist/foobarXXXXXX"; + EXPECT_EQ(NULL, mkdtemp(invalid_path2)); + + // Successfully create a tmp dir. + FilePath sub_dir2 = tmp_dir.Append("XXXXXX"); + std::string sub_dir2_string = sub_dir2.value(); + // this should be OK since mkdtemp just replaces characters in place + char* buffer2 = const_cast(sub_dir2_string.c_str()); + EXPECT_TRUE(mkdtemp(buffer2) != NULL); +} + +} // namespace base diff --git a/base/os_compat_nacl.cc b/base/os_compat_nacl.cc new file mode 100644 index 0000000000..58fe93e0cb --- /dev/null +++ b/base/os_compat_nacl.cc @@ -0,0 +1,30 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/os_compat_nacl.h" + +#include +#include + +#if !defined (__GLIBC__) + +extern "C" { +// Native Client has no timegm(). +time_t timegm(struct tm* tm) { + time_t ret; + char* tz; + tz = getenv("TZ"); + setenv("TZ", "", 1); + tzset(); + ret = mktime(tm); + if (tz) + setenv("TZ", tz, 1); + else + unsetenv("TZ"); + tzset(); + return ret; +} +} // extern "C" + +#endif // !defined (__GLIBC__) diff --git a/base/os_compat_nacl.h b/base/os_compat_nacl.h new file mode 100644 index 0000000000..13e0e3f8dd --- /dev/null +++ b/base/os_compat_nacl.h @@ -0,0 +1,16 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_OS_COMPAT_NACL_H_ +#define BASE_OS_COMPAT_NACL_H_ + +#include + +#if !defined (__GLIBC__) +// NaCl has no timegm(). +extern "C" time_t timegm(struct tm* const t); +#endif // !defined (__GLIBC__) + +#endif // BASE_OS_COMPAT_NACL_H_ + diff --git a/base/path_service.cc b/base/path_service.cc new file mode 100644 index 0000000000..89e58b2cdb --- /dev/null +++ b/base/path_service.cc @@ -0,0 +1,336 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/path_service.h" + +#if defined(OS_WIN) +#include +#include +#include +#endif + +#include "base/containers/hash_tables.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/synchronization/lock.h" + +using base::FilePath; +using base::MakeAbsoluteFilePath; + +namespace base { + bool PathProvider(int key, FilePath* result); +#if defined(OS_WIN) + bool PathProviderWin(int key, FilePath* result); +#elif defined(OS_MACOSX) + bool PathProviderMac(int key, FilePath* result); +#elif defined(OS_ANDROID) + bool PathProviderAndroid(int key, FilePath* result); +#elif defined(OS_POSIX) + // PathProviderPosix is the default path provider on POSIX OSes other than + // Mac and Android. + bool PathProviderPosix(int key, FilePath* result); +#endif +} + +namespace { + +typedef base::hash_map PathMap; + +// We keep a linked list of providers. In a debug build we ensure that no two +// providers claim overlapping keys. +struct Provider { + PathService::ProviderFunc func; + struct Provider* next; +#ifndef NDEBUG + int key_start; + int key_end; +#endif + bool is_static; +}; + +Provider base_provider = { + base::PathProvider, + NULL, +#ifndef NDEBUG + base::PATH_START, + base::PATH_END, +#endif + true +}; + +#if defined(OS_WIN) +Provider base_provider_win = { + base::PathProviderWin, + &base_provider, +#ifndef NDEBUG + base::PATH_WIN_START, + base::PATH_WIN_END, +#endif + true +}; +#endif + +#if defined(OS_MACOSX) +Provider base_provider_mac = { + base::PathProviderMac, + &base_provider, +#ifndef NDEBUG + base::PATH_MAC_START, + base::PATH_MAC_END, +#endif + true +}; +#endif + +#if defined(OS_ANDROID) +Provider base_provider_android = { + base::PathProviderAndroid, + &base_provider, +#ifndef NDEBUG + base::PATH_ANDROID_START, + base::PATH_ANDROID_END, +#endif + true +}; +#endif + +#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID) +Provider base_provider_posix = { + base::PathProviderPosix, + &base_provider, +#ifndef NDEBUG + base::PATH_POSIX_START, + base::PATH_POSIX_END, +#endif + true +}; +#endif + + +struct PathData { + base::Lock lock; + PathMap cache; // Cache mappings from path key to path value. + PathMap overrides; // Track path overrides. + Provider* providers; // Linked list of path service providers. + bool cache_disabled; // Don't use cache if true; + + PathData() : cache_disabled(false) { +#if defined(OS_WIN) + providers = &base_provider_win; +#elif defined(OS_MACOSX) + providers = &base_provider_mac; +#elif defined(OS_ANDROID) + providers = &base_provider_android; +#elif defined(OS_POSIX) + providers = &base_provider_posix; +#endif + } + + ~PathData() { + Provider* p = providers; + while (p) { + Provider* next = p->next; + if (!p->is_static) + delete p; + p = next; + } + } +}; + +static base::LazyInstance g_path_data = LAZY_INSTANCE_INITIALIZER; + +static PathData* GetPathData() { + return g_path_data.Pointer(); +} + +// Tries to find |key| in the cache. |path_data| should be locked by the caller! +bool LockedGetFromCache(int key, const PathData* path_data, FilePath* result) { + if (path_data->cache_disabled) + return false; + // check for a cached version + PathMap::const_iterator it = path_data->cache.find(key); + if (it != path_data->cache.end()) { + *result = it->second; + return true; + } + return false; +} + +// Tries to find |key| in the overrides map. |path_data| should be locked by the +// caller! +bool LockedGetFromOverrides(int key, PathData* path_data, FilePath* result) { + // check for an overridden version. + PathMap::const_iterator it = path_data->overrides.find(key); + if (it != path_data->overrides.end()) { + if (!path_data->cache_disabled) + path_data->cache[key] = it->second; + *result = it->second; + return true; + } + return false; +} + +} // namespace + +// TODO(brettw): this function does not handle long paths (filename > MAX_PATH) +// characters). This isn't supported very well by Windows right now, so it is +// moot, but we should keep this in mind for the future. +// static +bool PathService::Get(int key, FilePath* result) { + PathData* path_data = GetPathData(); + DCHECK(path_data); + DCHECK(result); + DCHECK_GE(key, base::DIR_CURRENT); + + // special case the current directory because it can never be cached + if (key == base::DIR_CURRENT) + return file_util::GetCurrentDirectory(result); + + Provider* provider = NULL; + { + base::AutoLock scoped_lock(path_data->lock); + if (LockedGetFromCache(key, path_data, result)) + return true; + + if (LockedGetFromOverrides(key, path_data, result)) + return true; + + // Get the beginning of the list while it is still locked. + provider = path_data->providers; + } + + FilePath path; + + // Iterating does not need the lock because only the list head might be + // modified on another thread. + while (provider) { + if (provider->func(key, &path)) + break; + DCHECK(path.empty()) << "provider should not have modified path"; + provider = provider->next; + } + + if (path.empty()) + return false; + + if (path.ReferencesParent()) { + // Make sure path service never returns a path with ".." in it. + path = MakeAbsoluteFilePath(path); + if (path.empty()) + return false; + } + *result = path; + + base::AutoLock scoped_lock(path_data->lock); + if (!path_data->cache_disabled) + path_data->cache[key] = path; + + return true; +} + +// static +bool PathService::Override(int key, const FilePath& path) { + // Just call the full function with true for the value of |create|. + return OverrideAndCreateIfNeeded(key, path, true); +} + +// static +bool PathService::OverrideAndCreateIfNeeded(int key, + const FilePath& path, + bool create) { + PathData* path_data = GetPathData(); + DCHECK(path_data); + DCHECK_GT(key, base::DIR_CURRENT) << "invalid path key"; + + FilePath file_path = path; + + // For some locations this will fail if called from inside the sandbox there- + // fore we protect this call with a flag. + if (create) { + // Make sure the directory exists. We need to do this before we translate + // this to the absolute path because on POSIX, MakeAbsoluteFilePath fails + // if called on a non-existent path. + if (!base::PathExists(file_path) && + !file_util::CreateDirectory(file_path)) + return false; + } + + // We need to have an absolute path. + file_path = MakeAbsoluteFilePath(file_path); + if (file_path.empty()) + return false; + + base::AutoLock scoped_lock(path_data->lock); + + // Clear the cache now. Some of its entries could have depended + // on the value we are overriding, and are now out of sync with reality. + path_data->cache.clear(); + + path_data->overrides[key] = file_path; + + return true; +} + +// static +bool PathService::RemoveOverride(int key) { + PathData* path_data = GetPathData(); + DCHECK(path_data); + + base::AutoLock scoped_lock(path_data->lock); + + if (path_data->overrides.find(key) == path_data->overrides.end()) + return false; + + // Clear the cache now. Some of its entries could have depended on the value + // we are going to remove, and are now out of sync. + path_data->cache.clear(); + + path_data->overrides.erase(key); + + return true; +} + +// static +void PathService::RegisterProvider(ProviderFunc func, int key_start, + int key_end) { + PathData* path_data = GetPathData(); + DCHECK(path_data); + DCHECK_GT(key_end, key_start); + + Provider* p; + + p = new Provider; + p->is_static = false; + p->func = func; +#ifndef NDEBUG + p->key_start = key_start; + p->key_end = key_end; +#endif + + base::AutoLock scoped_lock(path_data->lock); + +#ifndef NDEBUG + Provider *iter = path_data->providers; + while (iter) { + DCHECK(key_start >= iter->key_end || key_end <= iter->key_start) << + "path provider collision"; + iter = iter->next; + } +#endif + + p->next = path_data->providers; + path_data->providers = p; +} + +// static +void PathService::DisableCache() { + PathData* path_data = GetPathData(); + DCHECK(path_data); + + base::AutoLock scoped_lock(path_data->lock); + path_data->cache.clear(); + path_data->cache_disabled = true; +} diff --git a/base/path_service.h b/base/path_service.h new file mode 100644 index 0000000000..832b92b158 --- /dev/null +++ b/base/path_service.h @@ -0,0 +1,82 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_PATH_SERVICE_H_ +#define BASE_PATH_SERVICE_H_ + +#include + +#include "base/base_export.h" +#include "base/base_paths.h" +#include "base/gtest_prod_util.h" +#include "build/build_config.h" + +namespace base { +class FilePath; +class ScopedPathOverride; +} // namespace + +// The path service is a global table mapping keys to file system paths. It is +// OK to use this service from multiple threads. +// +class BASE_EXPORT PathService { + public: + // Retrieves a path to a special directory or file and places it into the + // string pointed to by 'path'. If you ask for a directory it is guaranteed + // to NOT have a path separator at the end. For example, "c:\windows\temp" + // Directories are also guaranteed to exist when this function succeeds. + // + // Returns true if the directory or file was successfully retrieved. On + // failure, 'path' will not be changed. + static bool Get(int key, base::FilePath* path); + + // Overrides the path to a special directory or file. This cannot be used to + // change the value of DIR_CURRENT, but that should be obvious. Also, if the + // path specifies a directory that does not exist, the directory will be + // created by this method. This method returns true if successful. + // + // If the given path is relative, then it will be resolved against + // DIR_CURRENT. + // + // WARNING: Consumers of PathService::Get may expect paths to be constant + // over the lifetime of the app, so this method should be used with caution. + static bool Override(int key, const base::FilePath& path); + + // This function does the same as PathService::Override but it takes an extra + // parameter |create| which guides whether the directory to be overriden must + // be created in case it doesn't exist already. + static bool OverrideAndCreateIfNeeded(int key, + const base::FilePath& path, + bool create); + + // To extend the set of supported keys, you can register a path provider, + // which is just a function mirroring PathService::Get. The ProviderFunc + // returns false if it cannot provide a non-empty path for the given key. + // Otherwise, true is returned. + // + // WARNING: This function could be called on any thread from which the + // PathService is used, so a the ProviderFunc MUST BE THREADSAFE. + // + typedef bool (*ProviderFunc)(int, base::FilePath*); + + // Call to register a path provider. You must specify the range "[key_start, + // key_end)" of supported path keys. + static void RegisterProvider(ProviderFunc provider, + int key_start, + int key_end); + + // Disable internal cache. + static void DisableCache(); + + private: + friend class base::ScopedPathOverride; + FRIEND_TEST_ALL_PREFIXES(PathServiceTest, RemoveOverride); + + // Removes an override for a special directory or file. Returns true if there + // was an override to remove or false if none was present. + // NOTE: This function is intended to be used by tests only! + static bool RemoveOverride(int key); +}; + +#endif // BASE_PATH_SERVICE_H_ diff --git a/base/path_service_unittest.cc b/base/path_service_unittest.cc new file mode 100644 index 0000000000..fbb2d766de --- /dev/null +++ b/base/path_service_unittest.cc @@ -0,0 +1,216 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/path_service.h" + +#include "base/basictypes.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/strings/string_util.h" +#include "build/build_config.h" +#include "testing/gtest/include/gtest/gtest-spi.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" + +#if defined(OS_WIN) +#include +#include "base/win/windows_version.h" +// userenv.dll is required for GetDefaultUserProfileDirectory(). +#pragma comment(lib, "userenv.lib") +#endif + +namespace { + +// Returns true if PathService::Get returns true and sets the path parameter +// to non-empty for the given PathService::DirType enumeration value. +bool ReturnsValidPath(int dir_type) { + base::FilePath path; + bool result = PathService::Get(dir_type, &path); + + // Some paths might not exist on some platforms in which case confirming + // |result| is true and !path.empty() is the best we can do. + bool check_path_exists = true; +#if defined(OS_POSIX) + // If chromium has never been started on this account, the cache path may not + // exist. + if (dir_type == base::DIR_CACHE) + check_path_exists = false; +#endif +#if defined(OS_LINUX) + // On the linux try-bots: a path is returned (e.g. /home/chrome-bot/Desktop), + // but it doesn't exist. + if (dir_type == base::DIR_USER_DESKTOP) + check_path_exists = false; +#endif +#if defined(OS_WIN) + if (dir_type == base::DIR_DEFAULT_USER_QUICK_LAUNCH) { + // On Windows XP, the Quick Launch folder for the "Default User" doesn't + // exist by default. At least confirm that the path returned begins with the + // Default User's profile path. + if (base::win::GetVersion() < base::win::VERSION_VISTA) { + wchar_t default_profile_path[MAX_PATH]; + DWORD size = arraysize(default_profile_path); + return (result && + ::GetDefaultUserProfileDirectory(default_profile_path, &size) && + StartsWith(path.value(), default_profile_path, false)); + } + } else if (dir_type == base::DIR_TASKBAR_PINS) { + // There is no pinned-to-taskbar shortcuts prior to Win7. + if (base::win::GetVersion() < base::win::VERSION_WIN7) + check_path_exists = false; + } +#endif +#if defined(OS_MACOSX) + if (dir_type != base::DIR_EXE && dir_type != base::DIR_MODULE && + dir_type != base::FILE_EXE && dir_type != base::FILE_MODULE) { + if (path.ReferencesParent()) + return false; + } +#else + if (path.ReferencesParent()) + return false; +#endif + return result && !path.empty() && (!check_path_exists || + base::PathExists(path)); +} + +#if defined(OS_WIN) +// Function to test any directory keys that are not supported on some versions +// of Windows. Checks that the function fails and that the returned path is +// empty. +bool ReturnsInvalidPath(int dir_type) { + base::FilePath path; + bool result = PathService::Get(dir_type, &path); + return !result && path.empty(); +} +#endif + +} // namespace + +// On the Mac this winds up using some autoreleased objects, so we need to +// be a PlatformTest. +typedef PlatformTest PathServiceTest; + +// Test that all PathService::Get calls return a value and a true result +// in the development environment. (This test was created because a few +// later changes to Get broke the semantics of the function and yielded the +// correct value while returning false.) +TEST_F(PathServiceTest, Get) { + for (int key = base::PATH_START + 1; key < base::PATH_END; ++key) { +#if defined(OS_ANDROID) + if (key == base::FILE_MODULE || key == base::DIR_USER_DESKTOP) + continue; // Android doesn't implement FILE_MODULE and DIR_USER_DESKTOP; +#elif defined(OS_IOS) + if (key == base::DIR_USER_DESKTOP) + continue; // iOS doesn't implement DIR_USER_DESKTOP; +#endif + EXPECT_PRED1(ReturnsValidPath, key); + } +#if defined(OS_WIN) + for (int key = base::PATH_WIN_START + 1; key < base::PATH_WIN_END; ++key) { + bool valid = true; + switch(key) { + case base::DIR_LOCAL_APP_DATA_LOW: + // DIR_LOCAL_APP_DATA_LOW is not supported prior Vista and is expected + // to fail. + valid = base::win::GetVersion() >= base::win::VERSION_VISTA; + break; + case base::DIR_APP_SHORTCUTS: + // DIR_APP_SHORTCUTS is not supported prior Windows 8 and is expected to + // fail. + valid = base::win::GetVersion() >= base::win::VERSION_WIN8; + break; + } + + if (valid) + EXPECT_TRUE(ReturnsValidPath(key)) << key; + else + EXPECT_TRUE(ReturnsInvalidPath(key)) << key; + } +#elif defined(OS_MACOSX) + for (int key = base::PATH_MAC_START + 1; key < base::PATH_MAC_END; ++key) { + EXPECT_PRED1(ReturnsValidPath, key); + } +#elif defined(OS_ANDROID) + for (int key = base::PATH_ANDROID_START + 1; key < base::PATH_ANDROID_END; + ++key) { + EXPECT_PRED1(ReturnsValidPath, key); + } +#elif defined(OS_POSIX) + for (int key = base::PATH_POSIX_START + 1; key < base::PATH_POSIX_END; + ++key) { + EXPECT_PRED1(ReturnsValidPath, key); + } +#endif +} + +// test that all versions of the Override function of PathService do what they +// are supposed to do. +TEST_F(PathServiceTest, Override) { + int my_special_key = 666; + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath fake_cache_dir(temp_dir.path().AppendASCII("cache")); + // PathService::Override should always create the path provided if it doesn't + // exist. + EXPECT_TRUE(PathService::Override(my_special_key, fake_cache_dir)); + EXPECT_TRUE(base::PathExists(fake_cache_dir)); + + base::FilePath fake_cache_dir2(temp_dir.path().AppendASCII("cache2")); + // PathService::OverrideAndCreateIfNeeded should obey the |create| parameter. + PathService::OverrideAndCreateIfNeeded(my_special_key, + fake_cache_dir2, + false); + EXPECT_FALSE(base::PathExists(fake_cache_dir2)); + EXPECT_TRUE(PathService::OverrideAndCreateIfNeeded(my_special_key, + fake_cache_dir2, + true)); + EXPECT_TRUE(base::PathExists(fake_cache_dir2)); +} + +// Check if multiple overrides can co-exist. +TEST_F(PathServiceTest, OverrideMultiple) { + int my_special_key = 666; + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath fake_cache_dir1(temp_dir.path().AppendASCII("1")); + EXPECT_TRUE(PathService::Override(my_special_key, fake_cache_dir1)); + EXPECT_TRUE(base::PathExists(fake_cache_dir1)); + ASSERT_EQ(1, file_util::WriteFile(fake_cache_dir1.AppendASCII("t1"), ".", 1)); + + base::FilePath fake_cache_dir2(temp_dir.path().AppendASCII("2")); + EXPECT_TRUE(PathService::Override(my_special_key + 1, fake_cache_dir2)); + EXPECT_TRUE(base::PathExists(fake_cache_dir2)); + ASSERT_EQ(1, file_util::WriteFile(fake_cache_dir2.AppendASCII("t2"), ".", 1)); + + base::FilePath result; + EXPECT_TRUE(PathService::Get(my_special_key, &result)); + // Override might have changed the path representation but our test file + // should be still there. + EXPECT_TRUE(base::PathExists(result.AppendASCII("t1"))); + EXPECT_TRUE(PathService::Get(my_special_key + 1, &result)); + EXPECT_TRUE(base::PathExists(result.AppendASCII("t2"))); +} + +TEST_F(PathServiceTest, RemoveOverride) { + // Before we start the test we have to call RemoveOverride at least once to + // clear any overrides that might have been left from other tests. + PathService::RemoveOverride(base::DIR_TEMP); + + base::FilePath original_user_data_dir; + EXPECT_TRUE(PathService::Get(base::DIR_TEMP, &original_user_data_dir)); + EXPECT_FALSE(PathService::RemoveOverride(base::DIR_TEMP)); + + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + EXPECT_TRUE(PathService::Override(base::DIR_TEMP, temp_dir.path())); + base::FilePath new_user_data_dir; + EXPECT_TRUE(PathService::Get(base::DIR_TEMP, &new_user_data_dir)); + EXPECT_NE(original_user_data_dir, new_user_data_dir); + + EXPECT_TRUE(PathService::RemoveOverride(base::DIR_TEMP)); + EXPECT_TRUE(PathService::Get(base::DIR_TEMP, &new_user_data_dir)); + EXPECT_EQ(original_user_data_dir, new_user_data_dir); +} diff --git a/base/pending_task.cc b/base/pending_task.cc new file mode 100644 index 0000000000..b288f28d09 --- /dev/null +++ b/base/pending_task.cc @@ -0,0 +1,60 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/pending_task.h" + +#include "base/tracked_objects.h" + +namespace base { + +#if _MSC_VER >= 1700 +// This a temporary fix for compiling on VS2012. http://crbug.com/154744 +PendingTask::PendingTask() : sequence_num(-1), nestable(false) { +} +#endif + +PendingTask::PendingTask(const tracked_objects::Location& posted_from, + const base::Closure& task) + : base::TrackingInfo(posted_from, TimeTicks()), + task(task), + posted_from(posted_from), + sequence_num(0), + nestable(true) { +} + +PendingTask::PendingTask(const tracked_objects::Location& posted_from, + const base::Closure& task, + TimeTicks delayed_run_time, + bool nestable) + : base::TrackingInfo(posted_from, delayed_run_time), + task(task), + posted_from(posted_from), + sequence_num(0), + nestable(nestable) { +} + +PendingTask::~PendingTask() { +} + +bool PendingTask::operator<(const PendingTask& other) const { + // Since the top of a priority queue is defined as the "greatest" element, we + // need to invert the comparison here. We want the smaller time to be at the + // top of the heap. + + if (delayed_run_time < other.delayed_run_time) + return false; + + if (delayed_run_time > other.delayed_run_time) + return true; + + // If the times happen to match, then we use the sequence number to decide. + // Compare the difference to support integer roll-over. + return (sequence_num - other.sequence_num) > 0; +} + +void TaskQueue::Swap(TaskQueue* queue) { + c.swap(queue->c); // Calls std::deque::swap. +} + +} // namespace base diff --git a/base/pending_task.h b/base/pending_task.h new file mode 100644 index 0000000000..65e17f8d8c --- /dev/null +++ b/base/pending_task.h @@ -0,0 +1,60 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PENDING_TASK_H_ +#define PENDING_TASK_H_ + +#include + +#include "base/base_export.h" +#include "base/callback.h" +#include "base/location.h" +#include "base/time/time.h" +#include "base/tracking_info.h" + +namespace base { + +// Contains data about a pending task. Stored in TaskQueue and DelayedTaskQueue +// for use by classes that queue and execute tasks. +struct BASE_EXPORT PendingTask : public TrackingInfo { +#if _MSC_VER >= 1700 + PendingTask(); +#endif + PendingTask(const tracked_objects::Location& posted_from, + const Closure& task); + PendingTask(const tracked_objects::Location& posted_from, + const Closure& task, + TimeTicks delayed_run_time, + bool nestable); + ~PendingTask(); + + // Used to support sorting. + bool operator<(const PendingTask& other) const; + + // The task to run. + Closure task; + + // The site this PendingTask was posted from. + tracked_objects::Location posted_from; + + // Secondary sort key for run time. + int sequence_num; + + // OK to dispatch from a nested loop. + bool nestable; +}; + +// Wrapper around std::queue specialized for PendingTask which adds a Swap +// helper method. +class BASE_EXPORT TaskQueue : public std::queue { + public: + void Swap(TaskQueue* queue); +}; + +// PendingTasks are sorted by their |delayed_run_time| property. +typedef std::priority_queue DelayedTaskQueue; + +} // namespace base + +#endif // PENDING_TASK_H_ diff --git a/base/perftimer.cc b/base/perftimer.cc new file mode 100644 index 0000000000..9ab7c6b298 --- /dev/null +++ b/base/perftimer.cc @@ -0,0 +1,45 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/perftimer.h" + +#include +#include + +#include "base/basictypes.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/logging.h" + +static FILE* perf_log_file = NULL; + +bool InitPerfLog(const base::FilePath& log_file) { + if (perf_log_file) { + // trying to initialize twice + NOTREACHED(); + return false; + } + + perf_log_file = file_util::OpenFile(log_file, "w"); + return perf_log_file != NULL; +} + +void FinalizePerfLog() { + if (!perf_log_file) { + // trying to cleanup without initializing + NOTREACHED(); + return; + } + file_util::CloseFile(perf_log_file); +} + +void LogPerfResult(const char* test_name, double value, const char* units) { + if (!perf_log_file) { + NOTREACHED(); + return; + } + + fprintf(perf_log_file, "%s\t%g\t%s\n", test_name, value, units); + printf("%s\t%g\t%s\n", test_name, value, units); +} diff --git a/base/perftimer.h b/base/perftimer.h new file mode 100644 index 0000000000..466f81d32b --- /dev/null +++ b/base/perftimer.h @@ -0,0 +1,85 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_PERFTIMER_H_ +#define BASE_PERFTIMER_H_ + +#include + +#include "base/basictypes.h" +#include "base/time/time.h" + +namespace base { +class FilePath; +} + +// ---------------------------------------------------------------------- +// Initializes and finalizes the perf log. These functions should be +// called at the beginning and end (respectively) of running all the +// performance tests. The init function returns true on success. +// ---------------------------------------------------------------------- +bool InitPerfLog(const base::FilePath& log_path); +void FinalizePerfLog(); + +// ---------------------------------------------------------------------- +// LogPerfResult +// Writes to the perf result log the given 'value' resulting from the +// named 'test'. The units are to aid in reading the log by people. +// ---------------------------------------------------------------------- +void LogPerfResult(const char* test_name, double value, const char* units); + +// ---------------------------------------------------------------------- +// PerfTimer +// A simple wrapper around Now() +// ---------------------------------------------------------------------- +class PerfTimer { + public: + PerfTimer() { + begin_ = base::TimeTicks::Now(); + } + + // Returns the time elapsed since object construction + base::TimeDelta Elapsed() const { + return base::TimeTicks::Now() - begin_; + } + + private: + base::TimeTicks begin_; +}; + +// ---------------------------------------------------------------------- +// PerfTimeLogger +// Automates calling LogPerfResult for the common case where you want +// to measure the time that something took. Call Done() when the test +// is complete if you do extra work after the test or there are stack +// objects with potentially expensive constructors. Otherwise, this +// class with automatically log on destruction. +// ---------------------------------------------------------------------- +class PerfTimeLogger { + public: + explicit PerfTimeLogger(const char* test_name) + : logged_(false), + test_name_(test_name) { + } + + ~PerfTimeLogger() { + if (!logged_) + Done(); + } + + void Done() { + // we use a floating-point millisecond value because it is more + // intuitive than microseconds and we want more precision than + // integer milliseconds + LogPerfResult(test_name_.c_str(), timer_.Elapsed().InMillisecondsF(), "ms"); + logged_ = true; + } + + private: + bool logged_; + std::string test_name_; + PerfTimer timer_; +}; + +#endif // BASE_PERFTIMER_H_ diff --git a/base/pickle.cc b/base/pickle.cc new file mode 100644 index 0000000000..af3191b9d8 --- /dev/null +++ b/base/pickle.cc @@ -0,0 +1,361 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/pickle.h" + +#include + +#include // for max() + +//------------------------------------------------------------------------------ + +// static +const int Pickle::kPayloadUnit = 64; + +static const size_t kCapacityReadOnly = static_cast(-1); + +PickleIterator::PickleIterator(const Pickle& pickle) + : read_ptr_(pickle.payload()), + read_end_ptr_(pickle.end_of_payload()) { +} + +template +inline bool PickleIterator::ReadBuiltinType(Type* result) { + const char* read_from = GetReadPointerAndAdvance(); + if (!read_from) + return false; + if (sizeof(Type) > sizeof(uint32)) + memcpy(result, read_from, sizeof(*result)); + else + *result = *reinterpret_cast(read_from); + return true; +} + +template +inline const char* PickleIterator::GetReadPointerAndAdvance() { + const char* current_read_ptr = read_ptr_; + if (read_ptr_ + sizeof(Type) > read_end_ptr_) + return NULL; + if (sizeof(Type) < sizeof(uint32)) + read_ptr_ += AlignInt(sizeof(Type), sizeof(uint32)); + else + read_ptr_ += sizeof(Type); + return current_read_ptr; +} + +const char* PickleIterator::GetReadPointerAndAdvance(int num_bytes) { + if (num_bytes < 0 || read_end_ptr_ - read_ptr_ < num_bytes) + return NULL; + const char* current_read_ptr = read_ptr_; + read_ptr_ += AlignInt(num_bytes, sizeof(uint32)); + return current_read_ptr; +} + +inline const char* PickleIterator::GetReadPointerAndAdvance(int num_elements, + size_t size_element) { + // Check for int32 overflow. + int64 num_bytes = static_cast(num_elements) * size_element; + int num_bytes32 = static_cast(num_bytes); + if (num_bytes != static_cast(num_bytes32)) + return NULL; + return GetReadPointerAndAdvance(num_bytes32); +} + +bool PickleIterator::ReadBool(bool* result) { + return ReadBuiltinType(result); +} + +bool PickleIterator::ReadInt(int* result) { + return ReadBuiltinType(result); +} + +bool PickleIterator::ReadLong(long* result) { + return ReadBuiltinType(result); +} + +bool PickleIterator::ReadUInt16(uint16* result) { + return ReadBuiltinType(result); +} + +bool PickleIterator::ReadUInt32(uint32* result) { + return ReadBuiltinType(result); +} + +bool PickleIterator::ReadInt64(int64* result) { + return ReadBuiltinType(result); +} + +bool PickleIterator::ReadUInt64(uint64* result) { + return ReadBuiltinType(result); +} + +bool PickleIterator::ReadFloat(float* result) { + return ReadBuiltinType(result); +} + +bool PickleIterator::ReadString(std::string* result) { + int len; + if (!ReadInt(&len)) + return false; + const char* read_from = GetReadPointerAndAdvance(len); + if (!read_from) + return false; + + result->assign(read_from, len); + return true; +} + +bool PickleIterator::ReadWString(std::wstring* result) { + int len; + if (!ReadInt(&len)) + return false; + const char* read_from = GetReadPointerAndAdvance(len, sizeof(wchar_t)); + if (!read_from) + return false; + + result->assign(reinterpret_cast(read_from), len); + return true; +} + +bool PickleIterator::ReadString16(string16* result) { + int len; + if (!ReadInt(&len)) + return false; + const char* read_from = GetReadPointerAndAdvance(len, sizeof(char16)); + if (!read_from) + return false; + + result->assign(reinterpret_cast(read_from), len); + return true; +} + +bool PickleIterator::ReadData(const char** data, int* length) { + *length = 0; + *data = 0; + + if (!ReadInt(length)) + return false; + + return ReadBytes(data, *length); +} + +bool PickleIterator::ReadBytes(const char** data, int length) { + const char* read_from = GetReadPointerAndAdvance(length); + if (!read_from) + return false; + *data = read_from; + return true; +} + +// Payload is uint32 aligned. + +Pickle::Pickle() + : header_(NULL), + header_size_(sizeof(Header)), + capacity_(0), + variable_buffer_offset_(0) { + Resize(kPayloadUnit); + header_->payload_size = 0; +} + +Pickle::Pickle(int header_size) + : header_(NULL), + header_size_(AlignInt(header_size, sizeof(uint32))), + capacity_(0), + variable_buffer_offset_(0) { + DCHECK_GE(static_cast(header_size), sizeof(Header)); + DCHECK_LE(header_size, kPayloadUnit); + Resize(kPayloadUnit); + header_->payload_size = 0; +} + +Pickle::Pickle(const char* data, int data_len) + : header_(reinterpret_cast(const_cast(data))), + header_size_(0), + capacity_(kCapacityReadOnly), + variable_buffer_offset_(0) { + if (data_len >= static_cast(sizeof(Header))) + header_size_ = data_len - header_->payload_size; + + if (header_size_ > static_cast(data_len)) + header_size_ = 0; + + if (header_size_ != AlignInt(header_size_, sizeof(uint32))) + header_size_ = 0; + + // If there is anything wrong with the data, we're not going to use it. + if (!header_size_) + header_ = NULL; +} + +Pickle::Pickle(const Pickle& other) + : header_(NULL), + header_size_(other.header_size_), + capacity_(0), + variable_buffer_offset_(other.variable_buffer_offset_) { + size_t payload_size = header_size_ + other.header_->payload_size; + bool resized = Resize(payload_size); + CHECK(resized); // Realloc failed. + memcpy(header_, other.header_, payload_size); +} + +Pickle::~Pickle() { + if (capacity_ != kCapacityReadOnly) + free(header_); +} + +Pickle& Pickle::operator=(const Pickle& other) { + if (this == &other) { + NOTREACHED(); + return *this; + } + if (capacity_ == kCapacityReadOnly) { + header_ = NULL; + capacity_ = 0; + } + if (header_size_ != other.header_size_) { + free(header_); + header_ = NULL; + header_size_ = other.header_size_; + } + bool resized = Resize(other.header_size_ + other.header_->payload_size); + CHECK(resized); // Realloc failed. + memcpy(header_, other.header_, + other.header_size_ + other.header_->payload_size); + variable_buffer_offset_ = other.variable_buffer_offset_; + return *this; +} + +bool Pickle::WriteString(const std::string& value) { + if (!WriteInt(static_cast(value.size()))) + return false; + + return WriteBytes(value.data(), static_cast(value.size())); +} + +bool Pickle::WriteWString(const std::wstring& value) { + if (!WriteInt(static_cast(value.size()))) + return false; + + return WriteBytes(value.data(), + static_cast(value.size() * sizeof(wchar_t))); +} + +bool Pickle::WriteString16(const string16& value) { + if (!WriteInt(static_cast(value.size()))) + return false; + + return WriteBytes(value.data(), + static_cast(value.size()) * sizeof(char16)); +} + +bool Pickle::WriteData(const char* data, int length) { + return length >= 0 && WriteInt(length) && WriteBytes(data, length); +} + +bool Pickle::WriteBytes(const void* data, int data_len) { + DCHECK_NE(kCapacityReadOnly, capacity_) << "oops: pickle is readonly"; + + char* dest = BeginWrite(data_len); + if (!dest) + return false; + + memcpy(dest, data, data_len); + + EndWrite(dest, data_len); + return true; +} + +char* Pickle::BeginWriteData(int length) { + DCHECK_EQ(variable_buffer_offset_, 0U) << + "There can only be one variable buffer in a Pickle"; + + if (length < 0 || !WriteInt(length)) + return NULL; + + char *data_ptr = BeginWrite(length); + if (!data_ptr) + return NULL; + + variable_buffer_offset_ = + data_ptr - reinterpret_cast(header_) - sizeof(int); + + // EndWrite doesn't necessarily have to be called after the write operation, + // so we call it here to pad out what the caller will eventually write. + EndWrite(data_ptr, length); + return data_ptr; +} + +void Pickle::TrimWriteData(int new_length) { + DCHECK_NE(variable_buffer_offset_, 0U); + + // Fetch the the variable buffer size + int* cur_length = reinterpret_cast( + reinterpret_cast(header_) + variable_buffer_offset_); + + if (new_length < 0 || new_length > *cur_length) { + NOTREACHED() << "Invalid length in TrimWriteData."; + return; + } + + // Update the payload size and variable buffer size + header_->payload_size -= (*cur_length - new_length); + *cur_length = new_length; +} + +char* Pickle::BeginWrite(size_t length) { + // write at a uint32-aligned offset from the beginning of the header + size_t offset = AlignInt(header_->payload_size, sizeof(uint32)); + + size_t new_size = offset + length; + size_t needed_size = header_size_ + new_size; + if (needed_size > capacity_ && !Resize(std::max(capacity_ * 2, needed_size))) + return NULL; + +#ifdef ARCH_CPU_64_BITS + DCHECK_LE(length, kuint32max); +#endif + + header_->payload_size = static_cast(new_size); + return mutable_payload() + offset; +} + +void Pickle::EndWrite(char* dest, int length) { + // Zero-pad to keep tools like valgrind from complaining about uninitialized + // memory. + if (length % sizeof(uint32)) + memset(dest + length, 0, sizeof(uint32) - (length % sizeof(uint32))); +} + +bool Pickle::Resize(size_t new_capacity) { + new_capacity = AlignInt(new_capacity, kPayloadUnit); + + CHECK_NE(capacity_, kCapacityReadOnly); + void* p = realloc(header_, new_capacity); + if (!p) + return false; + + header_ = reinterpret_cast(p); + capacity_ = new_capacity; + return true; +} + +// static +const char* Pickle::FindNext(size_t header_size, + const char* start, + const char* end) { + DCHECK_EQ(header_size, AlignInt(header_size, sizeof(uint32))); + DCHECK_LE(header_size, static_cast(kPayloadUnit)); + + if (static_cast(end - start) < sizeof(Header)) + return NULL; + + const Header* hdr = reinterpret_cast(start); + const char* payload_base = start + header_size; + const char* payload_end = payload_base + hdr->payload_size; + if (payload_end < payload_base) + return NULL; + + return (payload_end > end) ? NULL : payload_end; +} diff --git a/base/pickle.h b/base/pickle.h new file mode 100644 index 0000000000..3de2886e5d --- /dev/null +++ b/base/pickle.h @@ -0,0 +1,347 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_PICKLE_H__ +#define BASE_PICKLE_H__ + +#include + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "base/logging.h" +#include "base/strings/string16.h" + +class Pickle; + +// PickleIterator reads data from a Pickle. The Pickle object must remain valid +// while the PickleIterator object is in use. +class BASE_EXPORT PickleIterator { + public: + PickleIterator() : read_ptr_(NULL), read_end_ptr_(NULL) {} + explicit PickleIterator(const Pickle& pickle); + + // Methods for reading the payload of the Pickle. To read from the start of + // the Pickle, create a PickleIterator from a Pickle. If successful, these + // methods return true. Otherwise, false is returned to indicate that the + // result could not be extracted. + bool ReadBool(bool* result) WARN_UNUSED_RESULT; + bool ReadInt(int* result) WARN_UNUSED_RESULT; + bool ReadLong(long* result) WARN_UNUSED_RESULT; + bool ReadUInt16(uint16* result) WARN_UNUSED_RESULT; + bool ReadUInt32(uint32* result) WARN_UNUSED_RESULT; + bool ReadInt64(int64* result) WARN_UNUSED_RESULT; + bool ReadUInt64(uint64* result) WARN_UNUSED_RESULT; + bool ReadFloat(float* result) WARN_UNUSED_RESULT; + bool ReadString(std::string* result) WARN_UNUSED_RESULT; + bool ReadWString(std::wstring* result) WARN_UNUSED_RESULT; + bool ReadString16(string16* result) WARN_UNUSED_RESULT; + bool ReadData(const char** data, int* length) WARN_UNUSED_RESULT; + bool ReadBytes(const char** data, int length) WARN_UNUSED_RESULT; + + // Safer version of ReadInt() checks for the result not being negative. + // Use it for reading the object sizes. + bool ReadLength(int* result) WARN_UNUSED_RESULT { + return ReadInt(result) && *result >= 0; + } + + // Skips bytes in the read buffer and returns true if there are at least + // num_bytes available. Otherwise, does nothing and returns false. + bool SkipBytes(int num_bytes) WARN_UNUSED_RESULT { + return !!GetReadPointerAndAdvance(num_bytes); + } + + private: + // Aligns 'i' by rounding it up to the next multiple of 'alignment' + static size_t AlignInt(size_t i, int alignment) { + return i + (alignment - (i % alignment)) % alignment; + } + + // Read Type from Pickle. + template + inline bool ReadBuiltinType(Type* result); + + // Get read pointer for Type and advance read pointer. + template + inline const char* GetReadPointerAndAdvance(); + + // Get read pointer for |num_bytes| and advance read pointer. This method + // checks num_bytes for negativity and wrapping. + const char* GetReadPointerAndAdvance(int num_bytes); + + // Get read pointer for (num_elements * size_element) bytes and advance read + // pointer. This method checks for int overflow, negativity and wrapping. + inline const char* GetReadPointerAndAdvance(int num_elements, + size_t size_element); + + // Pointers to the Pickle data. + const char* read_ptr_; + const char* read_end_ptr_; + + FRIEND_TEST_ALL_PREFIXES(PickleTest, GetReadPointerAndAdvance); +}; + +// This class provides facilities for basic binary value packing and unpacking. +// +// The Pickle class supports appending primitive values (ints, strings, etc.) +// to a pickle instance. The Pickle instance grows its internal memory buffer +// dynamically to hold the sequence of primitive values. The internal memory +// buffer is exposed as the "data" of the Pickle. This "data" can be passed +// to a Pickle object to initialize it for reading. +// +// When reading from a Pickle object, it is important for the consumer to know +// what value types to read and in what order to read them as the Pickle does +// not keep track of the type of data written to it. +// +// The Pickle's data has a header which contains the size of the Pickle's +// payload. It can optionally support additional space in the header. That +// space is controlled by the header_size parameter passed to the Pickle +// constructor. +// +class BASE_EXPORT Pickle { + public: + // Initialize a Pickle object using the default header size. + Pickle(); + + // Initialize a Pickle object with the specified header size in bytes, which + // must be greater-than-or-equal-to sizeof(Pickle::Header). The header size + // will be rounded up to ensure that the header size is 32bit-aligned. + explicit Pickle(int header_size); + + // Initializes a Pickle from a const block of data. The data is not copied; + // instead the data is merely referenced by this Pickle. Only const methods + // should be used on the Pickle when initialized this way. The header + // padding size is deduced from the data length. + Pickle(const char* data, int data_len); + + // Initializes a Pickle as a deep copy of another Pickle. + Pickle(const Pickle& other); + + // Note: There are no virtual methods in this class. This destructor is + // virtual as an element of defensive coding. Other classes have derived from + // this class, and there is a *chance* that they will cast into this base + // class before destruction. At least one such class does have a virtual + // destructor, suggesting at least some need to call more derived destructors. + virtual ~Pickle(); + + // Performs a deep copy. + Pickle& operator=(const Pickle& other); + + // Returns the size of the Pickle's data. + size_t size() const { return header_size_ + header_->payload_size; } + + // Returns the data for this Pickle. + const void* data() const { return header_; } + + // For compatibility, these older style read methods pass through to the + // PickleIterator methods. + // TODO(jbates) Remove these methods. + bool ReadBool(PickleIterator* iter, bool* result) const { + return iter->ReadBool(result); + } + bool ReadInt(PickleIterator* iter, int* result) const { + return iter->ReadInt(result); + } + bool ReadLong(PickleIterator* iter, long* result) const { + return iter->ReadLong(result); + } + bool ReadUInt16(PickleIterator* iter, uint16* result) const { + return iter->ReadUInt16(result); + } + bool ReadUInt32(PickleIterator* iter, uint32* result) const { + return iter->ReadUInt32(result); + } + bool ReadInt64(PickleIterator* iter, int64* result) const { + return iter->ReadInt64(result); + } + bool ReadUInt64(PickleIterator* iter, uint64* result) const { + return iter->ReadUInt64(result); + } + bool ReadFloat(PickleIterator* iter, float* result) const { + return iter->ReadFloat(result); + } + bool ReadString(PickleIterator* iter, std::string* result) const { + return iter->ReadString(result); + } + bool ReadWString(PickleIterator* iter, std::wstring* result) const { + return iter->ReadWString(result); + } + bool ReadString16(PickleIterator* iter, string16* result) const { + return iter->ReadString16(result); + } + // A pointer to the data will be placed in *data, and the length will be + // placed in *length. This buffer will be into the message's buffer so will + // be scoped to the lifetime of the message (or until the message data is + // mutated). + bool ReadData(PickleIterator* iter, const char** data, int* length) const { + return iter->ReadData(data, length); + } + // A pointer to the data will be placed in *data. The caller specifies the + // number of bytes to read, and ReadBytes will validate this length. The + // returned buffer will be into the message's buffer so will be scoped to the + // lifetime of the message (or until the message data is mutated). + bool ReadBytes(PickleIterator* iter, const char** data, int length) const { + return iter->ReadBytes(data, length); + } + + // Safer version of ReadInt() checks for the result not being negative. + // Use it for reading the object sizes. + bool ReadLength(PickleIterator* iter, int* result) const { + return iter->ReadLength(result); + } + + // Methods for adding to the payload of the Pickle. These values are + // appended to the end of the Pickle's payload. When reading values from a + // Pickle, it is important to read them in the order in which they were added + // to the Pickle. + bool WriteBool(bool value) { + return WriteInt(value ? 1 : 0); + } + bool WriteInt(int value) { + return WriteBytes(&value, sizeof(value)); + } + // WARNING: DO NOT USE THIS METHOD IF PICKLES ARE PERSISTED IN ANY WAY. + // It will write whatever a "long" is on this architecture. On 32-bit + // platforms, it is 32 bits. On 64-bit platforms, it is 64 bits. If persisted + // pickles are still around after upgrading to 64-bit, or if they are copied + // between dissimilar systems, YOUR PICKLES WILL HAVE GONE BAD. + bool WriteLongUsingDangerousNonPortableLessPersistableForm(long value) { + return WriteBytes(&value, sizeof(value)); + } + bool WriteUInt16(uint16 value) { + return WriteBytes(&value, sizeof(value)); + } + bool WriteUInt32(uint32 value) { + return WriteBytes(&value, sizeof(value)); + } + bool WriteInt64(int64 value) { + return WriteBytes(&value, sizeof(value)); + } + bool WriteUInt64(uint64 value) { + return WriteBytes(&value, sizeof(value)); + } + bool WriteFloat(float value) { + return WriteBytes(&value, sizeof(value)); + } + bool WriteString(const std::string& value); + bool WriteWString(const std::wstring& value); + bool WriteString16(const string16& value); + // "Data" is a blob with a length. When you read it out you will be given the + // length. See also WriteBytes. + bool WriteData(const char* data, int length); + // "Bytes" is a blob with no length. The caller must specify the lenght both + // when reading and writing. It is normally used to serialize PoD types of a + // known size. See also WriteData. + bool WriteBytes(const void* data, int data_len); + + // Same as WriteData, but allows the caller to write directly into the + // Pickle. This saves a copy in cases where the data is not already + // available in a buffer. The caller should take care to not write more + // than the length it declares it will. Use ReadData to get the data. + // Returns NULL on failure. + // + // The returned pointer will only be valid until the next write operation + // on this Pickle. + char* BeginWriteData(int length); + + // For Pickles which contain variable length buffers (e.g. those created + // with BeginWriteData), the Pickle can + // be 'trimmed' if the amount of data required is less than originally + // requested. For example, you may have created a buffer with 10K of data, + // but decided to only fill 10 bytes of that data. Use this function + // to trim the buffer so that we don't send 9990 bytes of unused data. + // You cannot increase the size of the variable buffer; only shrink it. + // This function assumes that the length of the variable buffer has + // not been changed. + void TrimWriteData(int length); + + // Payload follows after allocation of Header (header size is customizable). + struct Header { + uint32 payload_size; // Specifies the size of the payload. + }; + + // Returns the header, cast to a user-specified type T. The type T must be a + // subclass of Header and its size must correspond to the header_size passed + // to the Pickle constructor. + template + T* headerT() { + DCHECK_EQ(header_size_, sizeof(T)); + return static_cast(header_); + } + template + const T* headerT() const { + DCHECK_EQ(header_size_, sizeof(T)); + return static_cast(header_); + } + + // The payload is the pickle data immediately following the header. + size_t payload_size() const { return header_->payload_size; } + + const char* payload() const { + return reinterpret_cast(header_) + header_size_; + } + + // Returns the address of the byte immediately following the currently valid + // header + payload. + const char* end_of_payload() const { + // This object may be invalid. + return header_ ? payload() + payload_size() : NULL; + } + + protected: + char* mutable_payload() { + return reinterpret_cast(header_) + header_size_; + } + + size_t capacity() const { + return capacity_; + } + + // Resizes the buffer for use when writing the specified amount of data. The + // location that the data should be written at is returned, or NULL if there + // was an error. Call EndWrite with the returned offset and the given length + // to pad out for the next write. + char* BeginWrite(size_t length); + + // Completes the write operation by padding the data with NULL bytes until it + // is padded. Should be paired with BeginWrite, but it does not necessarily + // have to be called after the data is written. + void EndWrite(char* dest, int length); + + // Resize the capacity, note that the input value should include the size of + // the header: new_capacity = sizeof(Header) + desired_payload_capacity. + // A realloc() failure will cause a Resize failure... and caller should check + // the return result for true (i.e., successful resizing). + bool Resize(size_t new_capacity); + + // Aligns 'i' by rounding it up to the next multiple of 'alignment' + static size_t AlignInt(size_t i, int alignment) { + return i + (alignment - (i % alignment)) % alignment; + } + + // Find the end of the pickled data that starts at range_start. Returns NULL + // if the entire Pickle is not found in the given data range. + static const char* FindNext(size_t header_size, + const char* range_start, + const char* range_end); + + // The allocation granularity of the payload. + static const int kPayloadUnit; + + private: + friend class PickleIterator; + + Header* header_; + size_t header_size_; // Supports extra data between header and payload. + // Allocation size of payload (or -1 if allocation is const). + size_t capacity_; + size_t variable_buffer_offset_; // IF non-zero, then offset to a buffer. + + FRIEND_TEST_ALL_PREFIXES(PickleTest, Resize); + FRIEND_TEST_ALL_PREFIXES(PickleTest, FindNext); + FRIEND_TEST_ALL_PREFIXES(PickleTest, FindNextWithIncompleteHeader); +}; + +#endif // BASE_PICKLE_H__ diff --git a/base/pickle_unittest.cc b/base/pickle_unittest.cc new file mode 100644 index 0000000000..cc384c7061 --- /dev/null +++ b/base/pickle_unittest.cc @@ -0,0 +1,342 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/pickle.h" +#include "base/strings/string16.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const int testint = 2093847192; +const std::string teststr("Hello world"); // note non-aligned string length +const std::wstring testwstr(L"Hello, world"); +const char testdata[] = "AAA\0BBB\0"; +const int testdatalen = arraysize(testdata) - 1; +const bool testbool1 = false; +const bool testbool2 = true; +const uint16 testuint16 = 32123; +const float testfloat = 3.1415926935f; + +// checks that the result +void VerifyResult(const Pickle& pickle) { + PickleIterator iter(pickle); + + int outint; + EXPECT_TRUE(pickle.ReadInt(&iter, &outint)); + EXPECT_EQ(testint, outint); + + std::string outstr; + EXPECT_TRUE(pickle.ReadString(&iter, &outstr)); + EXPECT_EQ(teststr, outstr); + + std::wstring outwstr; + EXPECT_TRUE(pickle.ReadWString(&iter, &outwstr)); + EXPECT_EQ(testwstr, outwstr); + + bool outbool; + EXPECT_TRUE(pickle.ReadBool(&iter, &outbool)); + EXPECT_FALSE(outbool); + EXPECT_TRUE(pickle.ReadBool(&iter, &outbool)); + EXPECT_TRUE(outbool); + + uint16 outuint16; + EXPECT_TRUE(pickle.ReadUInt16(&iter, &outuint16)); + EXPECT_EQ(testuint16, outuint16); + + float outfloat; + EXPECT_TRUE(pickle.ReadFloat(&iter, &outfloat)); + EXPECT_EQ(testfloat, outfloat); + + const char* outdata; + int outdatalen; + EXPECT_TRUE(pickle.ReadData(&iter, &outdata, &outdatalen)); + EXPECT_EQ(testdatalen, outdatalen); + EXPECT_EQ(memcmp(testdata, outdata, outdatalen), 0); + + EXPECT_TRUE(pickle.ReadData(&iter, &outdata, &outdatalen)); + EXPECT_EQ(testdatalen, outdatalen); + EXPECT_EQ(memcmp(testdata, outdata, outdatalen), 0); + + // reads past the end should fail + EXPECT_FALSE(pickle.ReadInt(&iter, &outint)); +} + +} // namespace + +TEST(PickleTest, EncodeDecode) { + Pickle pickle; + + EXPECT_TRUE(pickle.WriteInt(testint)); + EXPECT_TRUE(pickle.WriteString(teststr)); + EXPECT_TRUE(pickle.WriteWString(testwstr)); + EXPECT_TRUE(pickle.WriteBool(testbool1)); + EXPECT_TRUE(pickle.WriteBool(testbool2)); + EXPECT_TRUE(pickle.WriteUInt16(testuint16)); + EXPECT_TRUE(pickle.WriteFloat(testfloat)); + EXPECT_TRUE(pickle.WriteData(testdata, testdatalen)); + + // Over allocate BeginWriteData so we can test TrimWriteData. + char* dest = pickle.BeginWriteData(testdatalen + 100); + EXPECT_TRUE(dest); + memcpy(dest, testdata, testdatalen); + + pickle.TrimWriteData(testdatalen); + + VerifyResult(pickle); + + // test copy constructor + Pickle pickle2(pickle); + VerifyResult(pickle2); + + // test operator= + Pickle pickle3; + pickle3 = pickle; + VerifyResult(pickle3); +} + +// Tests that we can handle really small buffers. +TEST(PickleTest, SmallBuffer) { + scoped_ptr buffer(new char[1]); + + // We should not touch the buffer. + Pickle pickle(buffer.get(), 1); + + PickleIterator iter(pickle); + int data; + EXPECT_FALSE(pickle.ReadInt(&iter, &data)); +} + +// Tests that we can handle improper headers. +TEST(PickleTest, BigSize) { + int buffer[] = { 0x56035200, 25, 40, 50 }; + + Pickle pickle(reinterpret_cast(buffer), sizeof(buffer)); + + PickleIterator iter(pickle); + int data; + EXPECT_FALSE(pickle.ReadInt(&iter, &data)); +} + +TEST(PickleTest, UnalignedSize) { + int buffer[] = { 10, 25, 40, 50 }; + + Pickle pickle(reinterpret_cast(buffer), sizeof(buffer)); + + PickleIterator iter(pickle); + int data; + EXPECT_FALSE(pickle.ReadInt(&iter, &data)); +} + +TEST(PickleTest, ZeroLenStr) { + Pickle pickle; + EXPECT_TRUE(pickle.WriteString(std::string())); + + PickleIterator iter(pickle); + std::string outstr; + EXPECT_TRUE(pickle.ReadString(&iter, &outstr)); + EXPECT_EQ("", outstr); +} + +TEST(PickleTest, ZeroLenWStr) { + Pickle pickle; + EXPECT_TRUE(pickle.WriteWString(std::wstring())); + + PickleIterator iter(pickle); + std::string outstr; + EXPECT_TRUE(pickle.ReadString(&iter, &outstr)); + EXPECT_EQ("", outstr); +} + +TEST(PickleTest, BadLenStr) { + Pickle pickle; + EXPECT_TRUE(pickle.WriteInt(-2)); + + PickleIterator iter(pickle); + std::string outstr; + EXPECT_FALSE(pickle.ReadString(&iter, &outstr)); +} + +TEST(PickleTest, BadLenWStr) { + Pickle pickle; + EXPECT_TRUE(pickle.WriteInt(-1)); + + PickleIterator iter(pickle); + std::wstring woutstr; + EXPECT_FALSE(pickle.ReadWString(&iter, &woutstr)); +} + +TEST(PickleTest, FindNext) { + Pickle pickle; + EXPECT_TRUE(pickle.WriteInt(1)); + EXPECT_TRUE(pickle.WriteString("Domo")); + + const char* start = reinterpret_cast(pickle.data()); + const char* end = start + pickle.size(); + + EXPECT_TRUE(end == Pickle::FindNext(pickle.header_size_, start, end)); + EXPECT_TRUE(NULL == Pickle::FindNext(pickle.header_size_, start, end - 1)); + EXPECT_TRUE(end == Pickle::FindNext(pickle.header_size_, start, end + 1)); +} + +TEST(PickleTest, FindNextWithIncompleteHeader) { + size_t header_size = sizeof(Pickle::Header); + scoped_ptr buffer(new char[header_size - 1]); + memset(buffer.get(), 0x1, header_size - 1); + + const char* start = buffer.get(); + const char* end = start + header_size - 1; + + EXPECT_TRUE(NULL == Pickle::FindNext(header_size, start, end)); +} + +TEST(PickleTest, GetReadPointerAndAdvance) { + Pickle pickle; + + PickleIterator iter(pickle); + EXPECT_FALSE(iter.GetReadPointerAndAdvance(1)); + + EXPECT_TRUE(pickle.WriteInt(1)); + EXPECT_TRUE(pickle.WriteInt(2)); + int bytes = sizeof(int) * 2; + + EXPECT_TRUE(PickleIterator(pickle).GetReadPointerAndAdvance(0)); + EXPECT_TRUE(PickleIterator(pickle).GetReadPointerAndAdvance(1)); + EXPECT_FALSE(PickleIterator(pickle).GetReadPointerAndAdvance(-1)); + EXPECT_TRUE(PickleIterator(pickle).GetReadPointerAndAdvance(bytes)); + EXPECT_FALSE(PickleIterator(pickle).GetReadPointerAndAdvance(bytes + 1)); + EXPECT_FALSE(PickleIterator(pickle).GetReadPointerAndAdvance(INT_MAX)); + EXPECT_FALSE(PickleIterator(pickle).GetReadPointerAndAdvance(INT_MIN)); +} + +TEST(PickleTest, Resize) { + size_t unit = Pickle::kPayloadUnit; + scoped_ptr data(new char[unit]); + char* data_ptr = data.get(); + for (size_t i = 0; i < unit; i++) + data_ptr[i] = 'G'; + + // construct a message that will be exactly the size of one payload unit, + // note that any data will have a 4-byte header indicating the size + const size_t payload_size_after_header = unit - sizeof(uint32); + Pickle pickle; + pickle.WriteData(data_ptr, + static_cast(payload_size_after_header - sizeof(uint32))); + size_t cur_payload = payload_size_after_header; + + // note: we assume 'unit' is a power of 2 + EXPECT_EQ(unit, pickle.capacity()); + EXPECT_EQ(pickle.payload_size(), payload_size_after_header); + + // fill out a full page (noting data header) + pickle.WriteData(data_ptr, static_cast(unit - sizeof(uint32))); + cur_payload += unit; + EXPECT_EQ(unit * 2, pickle.capacity()); + EXPECT_EQ(cur_payload, pickle.payload_size()); + + // one more byte should double the capacity + pickle.WriteData(data_ptr, 1); + cur_payload += 5; + EXPECT_EQ(unit * 4, pickle.capacity()); + EXPECT_EQ(cur_payload, pickle.payload_size()); +} + +namespace { + +struct CustomHeader : Pickle::Header { + int blah; +}; + +} // namespace + +TEST(PickleTest, HeaderPadding) { + const uint32 kMagic = 0x12345678; + + Pickle pickle(sizeof(CustomHeader)); + pickle.WriteInt(kMagic); + + // this should not overwrite the 'int' payload + pickle.headerT()->blah = 10; + + PickleIterator iter(pickle); + int result; + ASSERT_TRUE(pickle.ReadInt(&iter, &result)); + + EXPECT_EQ(static_cast(result), kMagic); +} + +TEST(PickleTest, EqualsOperator) { + Pickle source; + source.WriteInt(1); + + Pickle copy_refs_source_buffer(static_cast(source.data()), + source.size()); + Pickle copy; + copy = copy_refs_source_buffer; + ASSERT_EQ(source.size(), copy.size()); +} + +TEST(PickleTest, EvilLengths) { + Pickle source; + std::string str(100000, 'A'); + EXPECT_TRUE(source.WriteData(str.c_str(), 100000)); + // ReadString16 used to have its read buffer length calculation wrong leading + // to out-of-bounds reading. + PickleIterator iter(source); + string16 str16; + EXPECT_FALSE(source.ReadString16(&iter, &str16)); + + // And check we didn't break ReadString16. + str16 = (wchar_t) 'A'; + Pickle str16_pickle; + EXPECT_TRUE(str16_pickle.WriteString16(str16)); + iter = PickleIterator(str16_pickle); + EXPECT_TRUE(str16_pickle.ReadString16(&iter, &str16)); + EXPECT_EQ(1U, str16.length()); + + // Check we don't fail in a length check with invalid String16 size. + // (1<<31) * sizeof(char16) == 0, so this is particularly evil. + Pickle bad_len; + EXPECT_TRUE(bad_len.WriteInt(1 << 31)); + iter = PickleIterator(bad_len); + EXPECT_FALSE(bad_len.ReadString16(&iter, &str16)); + + // Check we don't fail in a length check with large WStrings. + Pickle big_len; + EXPECT_TRUE(big_len.WriteInt(1 << 30)); + iter = PickleIterator(big_len); + std::wstring wstr; + EXPECT_FALSE(big_len.ReadWString(&iter, &wstr)); +} + +// Check we can write zero bytes of data and 'data' can be NULL. +TEST(PickleTest, ZeroLength) { + Pickle pickle; + EXPECT_TRUE(pickle.WriteData(NULL, 0)); + + PickleIterator iter(pickle); + const char* outdata; + int outdatalen; + EXPECT_TRUE(pickle.ReadData(&iter, &outdata, &outdatalen)); + EXPECT_EQ(0, outdatalen); + // We can't assert that outdata is NULL. +} + +// Check that ReadBytes works properly with an iterator initialized to NULL. +TEST(PickleTest, ReadBytes) { + Pickle pickle; + int data = 0x7abcd; + EXPECT_TRUE(pickle.WriteBytes(&data, sizeof(data))); + + PickleIterator iter(pickle); + const char* outdata_char = NULL; + EXPECT_TRUE(pickle.ReadBytes(&iter, &outdata_char, sizeof(data))); + + int outdata; + memcpy(&outdata, outdata_char, sizeof(outdata)); + EXPECT_EQ(data, outdata); +} diff --git a/base/platform_file.cc b/base/platform_file.cc new file mode 100644 index 0000000000..bb411b893f --- /dev/null +++ b/base/platform_file.cc @@ -0,0 +1,31 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/platform_file.h" + +namespace base { + +PlatformFileInfo::PlatformFileInfo() + : size(0), + is_directory(false), + is_symbolic_link(false) { +} + +PlatformFileInfo::~PlatformFileInfo() {} + +#if !defined(OS_NACL) +PlatformFile CreatePlatformFile(const FilePath& name, + int flags, + bool* created, + PlatformFileError* error) { + if (name.ReferencesParent()) { + if (error) + *error = PLATFORM_FILE_ERROR_ACCESS_DENIED; + return kInvalidPlatformFileValue; + } + return CreatePlatformFileUnsafe(name, flags, created, error); +} +#endif + +} // namespace base diff --git a/base/platform_file.h b/base/platform_file.h new file mode 100644 index 0000000000..e94c7ed0e3 --- /dev/null +++ b/base/platform_file.h @@ -0,0 +1,254 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_PLATFORM_FILE_H_ +#define BASE_PLATFORM_FILE_H_ + +#include "build/build_config.h" +#if defined(OS_WIN) +#include +#endif + +#include + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "base/time/time.h" + +namespace base { + +// PLATFORM_FILE_(OPEN|CREATE).* are mutually exclusive. You should specify +// exactly one of the five (possibly combining with other flags) when opening +// or creating a file. +// PLATFORM_FILE_(WRITE|APPEND) are mutually exclusive. This is so that APPEND +// behavior will be consistent with O_APPEND on POSIX. +enum PlatformFileFlags { + PLATFORM_FILE_OPEN = 1 << 0, // Opens a file, only if it exists. + PLATFORM_FILE_CREATE = 1 << 1, // Creates a new file, only if it + // does not already exist. + PLATFORM_FILE_OPEN_ALWAYS = 1 << 2, // May create a new file. + PLATFORM_FILE_CREATE_ALWAYS = 1 << 3, // May overwrite an old file. + PLATFORM_FILE_OPEN_TRUNCATED = 1 << 4, // Opens a file and truncates it, + // only if it exists. + PLATFORM_FILE_READ = 1 << 5, + PLATFORM_FILE_WRITE = 1 << 6, + PLATFORM_FILE_APPEND = 1 << 7, + PLATFORM_FILE_EXCLUSIVE_READ = 1 << 8, // EXCLUSIVE is opposite of Windows + // SHARE + PLATFORM_FILE_EXCLUSIVE_WRITE = 1 << 9, + PLATFORM_FILE_ASYNC = 1 << 10, + PLATFORM_FILE_TEMPORARY = 1 << 11, // Used on Windows only + PLATFORM_FILE_HIDDEN = 1 << 12, // Used on Windows only + PLATFORM_FILE_DELETE_ON_CLOSE = 1 << 13, + + PLATFORM_FILE_WRITE_ATTRIBUTES = 1 << 14, // Used on Windows only + PLATFORM_FILE_ENUMERATE = 1 << 15, // May enumerate directory + + PLATFORM_FILE_SHARE_DELETE = 1 << 16, // Used on Windows only + + PLATFORM_FILE_TERMINAL_DEVICE = 1 << 17, // Serial port flags + PLATFORM_FILE_BACKUP_SEMANTICS = 1 << 18, // Used on Windows only + + PLATFORM_FILE_EXECUTE = 1 << 19, // Used on Windows only +}; + +// PLATFORM_FILE_ERROR_ACCESS_DENIED is returned when a call fails because of +// a filesystem restriction. PLATFORM_FILE_ERROR_SECURITY is returned when a +// browser policy doesn't allow the operation to be executed. +enum PlatformFileError { + PLATFORM_FILE_OK = 0, + PLATFORM_FILE_ERROR_FAILED = -1, + PLATFORM_FILE_ERROR_IN_USE = -2, + PLATFORM_FILE_ERROR_EXISTS = -3, + PLATFORM_FILE_ERROR_NOT_FOUND = -4, + PLATFORM_FILE_ERROR_ACCESS_DENIED = -5, + PLATFORM_FILE_ERROR_TOO_MANY_OPENED = -6, + PLATFORM_FILE_ERROR_NO_MEMORY = -7, + PLATFORM_FILE_ERROR_NO_SPACE = -8, + PLATFORM_FILE_ERROR_NOT_A_DIRECTORY = -9, + PLATFORM_FILE_ERROR_INVALID_OPERATION = -10, + PLATFORM_FILE_ERROR_SECURITY = -11, + PLATFORM_FILE_ERROR_ABORT = -12, + PLATFORM_FILE_ERROR_NOT_A_FILE = -13, + PLATFORM_FILE_ERROR_NOT_EMPTY = -14, + PLATFORM_FILE_ERROR_INVALID_URL = -15, + PLATFORM_FILE_ERROR_IO = -16, + // Put new entries here and increment PLATFORM_FILE_ERROR_MAX. + PLATFORM_FILE_ERROR_MAX = -17 +}; + +// This explicit mapping matches both FILE_ on Windows and SEEK_ on Linux. +enum PlatformFileWhence { + PLATFORM_FILE_FROM_BEGIN = 0, + PLATFORM_FILE_FROM_CURRENT = 1, + PLATFORM_FILE_FROM_END = 2 +}; + +// Used to hold information about a given file. +// If you add more fields to this structure (platform-specific fields are OK), +// make sure to update all functions that use it in file_util_{win|posix}.cc +// too, and the ParamTraits implementation in +// chrome/common/common_param_traits.cc. +struct BASE_EXPORT PlatformFileInfo { + PlatformFileInfo(); + ~PlatformFileInfo(); + + // The size of the file in bytes. Undefined when is_directory is true. + int64 size; + + // True if the file corresponds to a directory. + bool is_directory; + + // True if the file corresponds to a symbolic link. + bool is_symbolic_link; + + // The last modified time of a file. + base::Time last_modified; + + // The last accessed time of a file. + base::Time last_accessed; + + // The creation time of a file. + base::Time creation_time; +}; + +#if defined(OS_WIN) +typedef HANDLE PlatformFile; +const PlatformFile kInvalidPlatformFileValue = INVALID_HANDLE_VALUE; +PlatformFileError LastErrorToPlatformFileError(DWORD saved_errno); +#elif defined(OS_POSIX) +typedef int PlatformFile; +const PlatformFile kInvalidPlatformFileValue = -1; +PlatformFileError ErrnoToPlatformFileError(int saved_errno); +#endif + +// Creates or opens the given file. If |created| is provided, it will be set to +// true if a new file was created [or an old one truncated to zero length to +// simulate a new file, which can happen with PLATFORM_FILE_CREATE_ALWAYS], and +// false otherwise. |error| can be NULL. +// +// This function fails with 'access denied' if the |name| contains path +// traversal ('..') components. +BASE_EXPORT PlatformFile CreatePlatformFile(const FilePath& name, + int flags, + bool* created, + PlatformFileError* error); + +// Same as CreatePlatformFile but allows paths with traversal (like \..\) +// components. Use only with extreme care. +BASE_EXPORT PlatformFile CreatePlatformFileUnsafe(const FilePath& name, + int flags, + bool* created, + PlatformFileError* error); + +BASE_EXPORT FILE* FdopenPlatformFile(PlatformFile file, const char* mode); + +// Closes a file handle. Returns |true| on success and |false| otherwise. +BASE_EXPORT bool ClosePlatformFile(PlatformFile file); + +// Changes current position in the file to an |offset| relative to an origin +// defined by |whence|. Returns the resultant current position in the file +// (relative to the start) or -1 in case of error. +BASE_EXPORT int64 SeekPlatformFile(PlatformFile file, + PlatformFileWhence whence, + int64 offset); + +// Reads the given number of bytes (or until EOF is reached) starting with the +// given offset. Returns the number of bytes read, or -1 on error. Note that +// this function makes a best effort to read all data on all platforms, so it is +// not intended for stream oriented files but instead for cases when the normal +// expectation is that actually |size| bytes are read unless there is an error. +BASE_EXPORT int ReadPlatformFile(PlatformFile file, int64 offset, + char* data, int size); + +// Same as above but without seek. +BASE_EXPORT int ReadPlatformFileAtCurrentPos(PlatformFile file, + char* data, int size); + +// Reads the given number of bytes (or until EOF is reached) starting with the +// given offset, but does not make any effort to read all data on all platforms. +// Returns the number of bytes read, or -1 on error. +BASE_EXPORT int ReadPlatformFileNoBestEffort(PlatformFile file, int64 offset, + char* data, int size); + +// Same as above but without seek. +BASE_EXPORT int ReadPlatformFileCurPosNoBestEffort(PlatformFile file, + char* data, int size); + +// Writes the given buffer into the file at the given offset, overwritting any +// data that was previously there. Returns the number of bytes written, or -1 +// on error. Note that this function makes a best effort to write all data on +// all platforms. +// Ignores the offset and writes to the end of the file if the file was opened +// with PLATFORM_FILE_APPEND. +BASE_EXPORT int WritePlatformFile(PlatformFile file, int64 offset, + const char* data, int size); + +// Save as above but without seek. +BASE_EXPORT int WritePlatformFileAtCurrentPos(PlatformFile file, + const char* data, int size); + +// Save as above but does not make any effort to write all data on all +// platforms. Returns the number of bytes written, or -1 on error. +BASE_EXPORT int WritePlatformFileCurPosNoBestEffort(PlatformFile file, + const char* data, int size); + +// Truncates the given file to the given length. If |length| is greater than +// the current size of the file, the file is extended with zeros. If the file +// doesn't exist, |false| is returned. +BASE_EXPORT bool TruncatePlatformFile(PlatformFile file, int64 length); + +// Flushes the buffers of the given file. +BASE_EXPORT bool FlushPlatformFile(PlatformFile file); + +// Touches the given file. +BASE_EXPORT bool TouchPlatformFile(PlatformFile file, + const Time& last_access_time, + const Time& last_modified_time); + +// Returns some information for the given file. +BASE_EXPORT bool GetPlatformFileInfo(PlatformFile file, PlatformFileInfo* info); + +// Use this class to pass ownership of a PlatformFile to a receiver that may or +// may not want to accept it. This class does not own the storage for the +// PlatformFile. +// +// EXAMPLE: +// +// void MaybeProcessFile(PassPlatformFile pass_file) { +// if (...) { +// PlatformFile file = pass_file.ReleaseValue(); +// // Now, we are responsible for closing |file|. +// } +// } +// +// void OpenAndMaybeProcessFile(const FilePath& path) { +// PlatformFile file = CreatePlatformFile(path, ...); +// MaybeProcessFile(PassPlatformFile(&file)); +// if (file != kInvalidPlatformFileValue) +// ClosePlatformFile(file); +// } +// +class BASE_EXPORT PassPlatformFile { + public: + explicit PassPlatformFile(PlatformFile* value) : value_(value) { + } + + // Called to retrieve the PlatformFile stored in this object. The caller + // gains ownership of the PlatformFile and is now responsible for closing it. + // Any subsequent calls to this method will return an invalid PlatformFile. + PlatformFile ReleaseValue() { + PlatformFile temp = *value_; + *value_ = kInvalidPlatformFileValue; + return temp; + } + + private: + PlatformFile* value_; +}; + +} // namespace base + +#endif // BASE_PLATFORM_FILE_H_ diff --git a/base/platform_file_posix.cc b/base/platform_file_posix.cc new file mode 100644 index 0000000000..056577c581 --- /dev/null +++ b/base/platform_file_posix.cc @@ -0,0 +1,461 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/platform_file.h" + +#include +#include +#include +#include + +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/metrics/sparse_histogram.h" +#include "base/posix/eintr_wrapper.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/thread_restrictions.h" + +#if defined(OS_ANDROID) +#include "base/os_compat_android.h" +#endif + +namespace base { + +// Make sure our Whence mappings match the system headers. +COMPILE_ASSERT(PLATFORM_FILE_FROM_BEGIN == SEEK_SET && + PLATFORM_FILE_FROM_CURRENT == SEEK_CUR && + PLATFORM_FILE_FROM_END == SEEK_END, whence_matches_system); + +namespace { + +#if defined(OS_BSD) || defined(OS_MACOSX) || defined(OS_NACL) +typedef struct stat stat_wrapper_t; +static int CallFstat(int fd, stat_wrapper_t *sb) { + base::ThreadRestrictions::AssertIOAllowed(); + return fstat(fd, sb); +} +#else +typedef struct stat64 stat_wrapper_t; +static int CallFstat(int fd, stat_wrapper_t *sb) { + base::ThreadRestrictions::AssertIOAllowed(); + return fstat64(fd, sb); +} +#endif + +// NaCl doesn't provide the following system calls, so either simulate them or +// wrap them in order to minimize the number of #ifdef's in this file. +#if !defined(OS_NACL) +static int DoPread(PlatformFile file, char* data, int size, int64 offset) { + return HANDLE_EINTR(pread(file, data, size, offset)); +} + +static int DoPwrite(PlatformFile file, const char* data, int size, + int64 offset) { + return HANDLE_EINTR(pwrite(file, data, size, offset)); +} + +static bool IsOpenAppend(PlatformFile file) { + return (fcntl(file, F_GETFL) & O_APPEND) != 0; +} + +static int CallFtruncate(PlatformFile file, int64 length) { + return HANDLE_EINTR(ftruncate(file, length)); +} + +static int CallFsync(PlatformFile file) { + return HANDLE_EINTR(fsync(file)); +} + +static int CallFutimes(PlatformFile file, const struct timeval times[2]) { +#ifdef __USE_XOPEN2K8 + // futimens should be available, but futimes might not be + // http://pubs.opengroup.org/onlinepubs/9699919799/ + + timespec ts_times[2]; + ts_times[0].tv_sec = times[0].tv_sec; + ts_times[0].tv_nsec = times[0].tv_usec * 1000; + ts_times[1].tv_sec = times[1].tv_sec; + ts_times[1].tv_nsec = times[1].tv_usec * 1000; + + return futimens(file, ts_times); +#else + return futimes(file, times); +#endif +} +#else // defined(OS_NACL) +// TODO(bbudge) Remove DoPread, DoPwrite when NaCl implements pread, pwrite. +static int DoPread(PlatformFile file, char* data, int size, int64 offset) { + lseek(file, static_cast(offset), SEEK_SET); + return HANDLE_EINTR(read(file, data, size)); +} + +static int DoPwrite(PlatformFile file, const char* data, int size, + int64 offset) { + lseek(file, static_cast(offset), SEEK_SET); + return HANDLE_EINTR(write(file, data, size)); +} + +static bool IsOpenAppend(PlatformFile file) { + // NaCl doesn't implement fcntl. Since NaCl's write conforms to the POSIX + // standard and always appends if the file is opened with O_APPEND, just + // return false here. + return false; +} + +static int CallFtruncate(PlatformFile file, int64 length) { + NOTIMPLEMENTED(); // NaCl doesn't implement ftruncate. + return 0; +} + +static int CallFsync(PlatformFile file) { + NOTIMPLEMENTED(); // NaCl doesn't implement fsync. + return 0; +} + +static int CallFutimes(PlatformFile file, const struct timeval times[2]) { + NOTIMPLEMENTED(); // NaCl doesn't implement futimes. + return 0; +} +#endif // defined(OS_NACL) + +} // namespace + +// NaCl doesn't implement system calls to open files directly. +#if !defined(OS_NACL) +// TODO(erikkay): does it make sense to support PLATFORM_FILE_EXCLUSIVE_* here? +PlatformFile CreatePlatformFileUnsafe(const FilePath& name, + int flags, + bool* created, + PlatformFileError* error) { + base::ThreadRestrictions::AssertIOAllowed(); + + int open_flags = 0; + if (flags & PLATFORM_FILE_CREATE) + open_flags = O_CREAT | O_EXCL; + + if (created) + *created = false; + + if (flags & PLATFORM_FILE_CREATE_ALWAYS) { + DCHECK(!open_flags); + open_flags = O_CREAT | O_TRUNC; + } + + if (flags & PLATFORM_FILE_OPEN_TRUNCATED) { + DCHECK(!open_flags); + DCHECK(flags & PLATFORM_FILE_WRITE); + open_flags = O_TRUNC; + } + + if (!open_flags && !(flags & PLATFORM_FILE_OPEN) && + !(flags & PLATFORM_FILE_OPEN_ALWAYS)) { + NOTREACHED(); + errno = EOPNOTSUPP; + if (error) + *error = PLATFORM_FILE_ERROR_FAILED; + return kInvalidPlatformFileValue; + } + + if (flags & PLATFORM_FILE_WRITE && flags & PLATFORM_FILE_READ) { + open_flags |= O_RDWR; + } else if (flags & PLATFORM_FILE_WRITE) { + open_flags |= O_WRONLY; + } else if (!(flags & PLATFORM_FILE_READ) && + !(flags & PLATFORM_FILE_WRITE_ATTRIBUTES) && + !(flags & PLATFORM_FILE_APPEND) && + !(flags & PLATFORM_FILE_OPEN_ALWAYS)) { + NOTREACHED(); + } + + if (flags & PLATFORM_FILE_TERMINAL_DEVICE) + open_flags |= O_NOCTTY | O_NDELAY; + + if (flags & PLATFORM_FILE_APPEND && flags & PLATFORM_FILE_READ) + open_flags |= O_APPEND | O_RDWR; + else if (flags & PLATFORM_FILE_APPEND) + open_flags |= O_APPEND | O_WRONLY; + + COMPILE_ASSERT(O_RDONLY == 0, O_RDONLY_must_equal_zero); + + int mode = S_IRUSR | S_IWUSR; +#if defined(OS_CHROMEOS) + mode |= S_IRGRP | S_IROTH; +#endif + + int descriptor = + HANDLE_EINTR(open(name.value().c_str(), open_flags, mode)); + + if (flags & PLATFORM_FILE_OPEN_ALWAYS) { + if (descriptor < 0) { + open_flags |= O_CREAT; + if (flags & PLATFORM_FILE_EXCLUSIVE_READ || + flags & PLATFORM_FILE_EXCLUSIVE_WRITE) { + open_flags |= O_EXCL; // together with O_CREAT implies O_NOFOLLOW + } + descriptor = HANDLE_EINTR( + open(name.value().c_str(), open_flags, mode)); + if (created && descriptor >= 0) + *created = true; + } + } + + if (created && (descriptor >= 0) && + (flags & (PLATFORM_FILE_CREATE_ALWAYS | PLATFORM_FILE_CREATE))) + *created = true; + + if ((descriptor >= 0) && (flags & PLATFORM_FILE_DELETE_ON_CLOSE)) { + unlink(name.value().c_str()); + } + + if (error) { + if (descriptor >= 0) + *error = PLATFORM_FILE_OK; + else + *error = ErrnoToPlatformFileError(errno); + } + + return descriptor; +} + +FILE* FdopenPlatformFile(PlatformFile file, const char* mode) { + return fdopen(file, mode); +} +#endif // !defined(OS_NACL) + +bool ClosePlatformFile(PlatformFile file) { + base::ThreadRestrictions::AssertIOAllowed(); + return !HANDLE_EINTR(close(file)); +} + +int64 SeekPlatformFile(PlatformFile file, + PlatformFileWhence whence, + int64 offset) { + base::ThreadRestrictions::AssertIOAllowed(); + if (file < 0 || offset < 0) + return -1; + + return lseek(file, static_cast(offset), static_cast(whence)); +} + +int ReadPlatformFile(PlatformFile file, int64 offset, char* data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + if (file < 0 || size < 0) + return -1; + + int bytes_read = 0; + int rv; + do { + rv = DoPread(file, data + bytes_read, + size - bytes_read, offset + bytes_read); + if (rv <= 0) + break; + + bytes_read += rv; + } while (bytes_read < size); + + return bytes_read ? bytes_read : rv; +} + +int ReadPlatformFileAtCurrentPos(PlatformFile file, char* data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + if (file < 0 || size < 0) + return -1; + + int bytes_read = 0; + int rv; + do { + rv = HANDLE_EINTR(read(file, data, size)); + if (rv <= 0) + break; + + bytes_read += rv; + } while (bytes_read < size); + + return bytes_read ? bytes_read : rv; +} + +int ReadPlatformFileNoBestEffort(PlatformFile file, int64 offset, + char* data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + if (file < 0) + return -1; + + return DoPread(file, data, size, offset); +} + +int ReadPlatformFileCurPosNoBestEffort(PlatformFile file, + char* data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + if (file < 0 || size < 0) + return -1; + + return HANDLE_EINTR(read(file, data, size)); +} + +int WritePlatformFile(PlatformFile file, int64 offset, + const char* data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + + if (IsOpenAppend(file)) + return WritePlatformFileAtCurrentPos(file, data, size); + + if (file < 0 || size < 0) + return -1; + + int bytes_written = 0; + int rv; + do { + rv = DoPwrite(file, data + bytes_written, + size - bytes_written, offset + bytes_written); + if (rv <= 0) + break; + + bytes_written += rv; + } while (bytes_written < size); + + return bytes_written ? bytes_written : rv; +} + +int WritePlatformFileAtCurrentPos(PlatformFile file, + const char* data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + if (file < 0 || size < 0) + return -1; + + int bytes_written = 0; + int rv; + do { + rv = HANDLE_EINTR(write(file, data, size)); + if (rv <= 0) + break; + + bytes_written += rv; + } while (bytes_written < size); + + return bytes_written ? bytes_written : rv; +} + +int WritePlatformFileCurPosNoBestEffort(PlatformFile file, + const char* data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + if (file < 0 || size < 0) + return -1; + + return HANDLE_EINTR(write(file, data, size)); +} + +bool TruncatePlatformFile(PlatformFile file, int64 length) { + base::ThreadRestrictions::AssertIOAllowed(); + return ((file >= 0) && !CallFtruncate(file, length)); +} + +bool FlushPlatformFile(PlatformFile file) { + base::ThreadRestrictions::AssertIOAllowed(); + return !CallFsync(file); +} + +bool TouchPlatformFile(PlatformFile file, const base::Time& last_access_time, + const base::Time& last_modified_time) { + base::ThreadRestrictions::AssertIOAllowed(); + if (file < 0) + return false; + + timeval times[2]; + times[0] = last_access_time.ToTimeVal(); + times[1] = last_modified_time.ToTimeVal(); + + return !CallFutimes(file, times); +} + +bool GetPlatformFileInfo(PlatformFile file, PlatformFileInfo* info) { + if (!info) + return false; + + stat_wrapper_t file_info; + if (CallFstat(file, &file_info)) + return false; + + info->is_directory = S_ISDIR(file_info.st_mode); + info->is_symbolic_link = S_ISLNK(file_info.st_mode); + info->size = file_info.st_size; + +#if defined(OS_LINUX) + const time_t last_modified_sec = file_info.st_mtim.tv_sec; + const int64 last_modified_nsec = file_info.st_mtim.tv_nsec; + const time_t last_accessed_sec = file_info.st_atim.tv_sec; + const int64 last_accessed_nsec = file_info.st_atim.tv_nsec; + const time_t creation_time_sec = file_info.st_ctim.tv_sec; + const int64 creation_time_nsec = file_info.st_ctim.tv_nsec; +#elif defined(OS_ANDROID) + const time_t last_modified_sec = file_info.st_mtime; + const int64 last_modified_nsec = file_info.st_mtime_nsec; + const time_t last_accessed_sec = file_info.st_atime; + const int64 last_accessed_nsec = file_info.st_atime_nsec; + const time_t creation_time_sec = file_info.st_ctime; + const int64 creation_time_nsec = file_info.st_ctime_nsec; +#elif defined(OS_MACOSX) || defined(OS_IOS) || defined(OS_BSD) + const time_t last_modified_sec = file_info.st_mtimespec.tv_sec; + const int64 last_modified_nsec = file_info.st_mtimespec.tv_nsec; + const time_t last_accessed_sec = file_info.st_atimespec.tv_sec; + const int64 last_accessed_nsec = file_info.st_atimespec.tv_nsec; + const time_t creation_time_sec = file_info.st_ctimespec.tv_sec; + const int64 creation_time_nsec = file_info.st_ctimespec.tv_nsec; +#else + // TODO(gavinp): Investigate a good high resolution option for OS_NACL. + const time_t last_modified_sec = file_info.st_mtime; + const int64 last_modified_nsec = 0; + const time_t last_accessed_sec = file_info.st_atime; + const int64 last_accessed_nsec = 0; + const time_t creation_time_sec = file_info.st_ctime; + const int64 creation_time_nsec = 0; +#endif + + info->last_modified = + base::Time::FromTimeT(last_modified_sec) + + base::TimeDelta::FromMicroseconds(last_modified_nsec / + base::Time::kNanosecondsPerMicrosecond); + info->last_accessed = + base::Time::FromTimeT(last_accessed_sec) + + base::TimeDelta::FromMicroseconds(last_accessed_nsec / + base::Time::kNanosecondsPerMicrosecond); + info->creation_time = + base::Time::FromTimeT(creation_time_sec) + + base::TimeDelta::FromMicroseconds(creation_time_nsec / + base::Time::kNanosecondsPerMicrosecond); + return true; +} + +PlatformFileError ErrnoToPlatformFileError(int saved_errno) { + switch (saved_errno) { + case EACCES: + case EISDIR: + case EROFS: + case EPERM: + return PLATFORM_FILE_ERROR_ACCESS_DENIED; +#if !defined(OS_NACL) // ETXTBSY not defined by NaCl. + case ETXTBSY: + return PLATFORM_FILE_ERROR_IN_USE; +#endif + case EEXIST: + return PLATFORM_FILE_ERROR_EXISTS; + case ENOENT: + return PLATFORM_FILE_ERROR_NOT_FOUND; + case EMFILE: + return PLATFORM_FILE_ERROR_TOO_MANY_OPENED; + case ENOMEM: + return PLATFORM_FILE_ERROR_NO_MEMORY; + case ENOSPC: + return PLATFORM_FILE_ERROR_NO_SPACE; + case ENOTDIR: + return PLATFORM_FILE_ERROR_NOT_A_DIRECTORY; + default: +#if !defined(OS_NACL) // NaCl build has no metrics code. + UMA_HISTOGRAM_SPARSE_SLOWLY("PlatformFile.UnknownErrors.Posix", + saved_errno); +#endif + return PLATFORM_FILE_ERROR_FAILED; + } +} + +} // namespace base diff --git a/base/platform_file_unittest.cc b/base/platform_file_unittest.cc new file mode 100644 index 0000000000..a1f3927edd --- /dev/null +++ b/base/platform_file_unittest.cc @@ -0,0 +1,400 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/platform_file.h" +#include "base/time/time.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::FilePath; + +namespace { + +// Reads from a file the given number of bytes, or until EOF is reached. +// Returns the number of bytes read. +int ReadFully(base::PlatformFile file, int64 offset, char* data, int size) { + return base::ReadPlatformFile(file, offset, data, size); +} + +// Writes the given number of bytes to a file. +// Returns the number of bytes written. +int WriteFully(base::PlatformFile file, int64 offset, + const char* data, int size) { + return base::WritePlatformFile(file, offset, data, size); +} + +} // namespace + +TEST(PlatformFile, CreatePlatformFile) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + FilePath file_path = temp_dir.path().AppendASCII("create_file_1"); + + // Open a file that doesn't exist. + base::PlatformFileError error_code = base::PLATFORM_FILE_OK; + base::PlatformFile file = base::CreatePlatformFile( + file_path, + base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, + NULL, + &error_code); + EXPECT_EQ(base::kInvalidPlatformFileValue, file); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, error_code); + + // Open or create a file. + bool created = false; + error_code = base::PLATFORM_FILE_OK; + file = base::CreatePlatformFile( + file_path, + base::PLATFORM_FILE_OPEN_ALWAYS | base::PLATFORM_FILE_READ, + &created, + &error_code); + EXPECT_NE(base::kInvalidPlatformFileValue, file); + EXPECT_TRUE(created); + EXPECT_EQ(base::PLATFORM_FILE_OK, error_code); + base::ClosePlatformFile(file); + + // Open an existing file. + created = false; + file = base::CreatePlatformFile( + file_path, + base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, + &created, + &error_code); + EXPECT_NE(base::kInvalidPlatformFileValue, file); + EXPECT_FALSE(created); + EXPECT_EQ(base::PLATFORM_FILE_OK, error_code); + base::ClosePlatformFile(file); + + // Create a file that exists. + file = base::CreatePlatformFile( + file_path, + base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_READ, + &created, + &error_code); + EXPECT_EQ(base::kInvalidPlatformFileValue, file); + EXPECT_FALSE(created); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_EXISTS, error_code); + + // Create or overwrite a file. + error_code = base::PLATFORM_FILE_OK; + file = base::CreatePlatformFile( + file_path, + base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_READ, + &created, + &error_code); + EXPECT_NE(base::kInvalidPlatformFileValue, file); + EXPECT_TRUE(created); + EXPECT_EQ(base::PLATFORM_FILE_OK, error_code); + base::ClosePlatformFile(file); + + // Create a delete-on-close file. + created = false; + file_path = temp_dir.path().AppendASCII("create_file_2"); + file = base::CreatePlatformFile( + file_path, + base::PLATFORM_FILE_OPEN_ALWAYS | base::PLATFORM_FILE_DELETE_ON_CLOSE | + base::PLATFORM_FILE_READ, + &created, + &error_code); + EXPECT_NE(base::kInvalidPlatformFileValue, file); + EXPECT_TRUE(created); + EXPECT_EQ(base::PLATFORM_FILE_OK, error_code); + + EXPECT_TRUE(base::ClosePlatformFile(file)); + EXPECT_FALSE(base::PathExists(file_path)); +} + +TEST(PlatformFile, DeleteOpenFile) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + FilePath file_path = temp_dir.path().AppendASCII("create_file_1"); + + // Create a file. + bool created = false; + base::PlatformFileError error_code = base::PLATFORM_FILE_OK; + base::PlatformFile file = base::CreatePlatformFile( + file_path, + base::PLATFORM_FILE_OPEN_ALWAYS | base::PLATFORM_FILE_READ | + base::PLATFORM_FILE_SHARE_DELETE, + &created, + &error_code); + EXPECT_NE(base::kInvalidPlatformFileValue, file); + EXPECT_TRUE(created); + EXPECT_EQ(base::PLATFORM_FILE_OK, error_code); + + // Open an existing file and mark it as delete on close. + created = false; + base::PlatformFile same_file = base::CreatePlatformFile( + file_path, + base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_DELETE_ON_CLOSE | + base::PLATFORM_FILE_READ, + &created, + &error_code); + EXPECT_NE(base::kInvalidPlatformFileValue, file); + EXPECT_FALSE(created); + EXPECT_EQ(base::PLATFORM_FILE_OK, error_code); + + // Close both handles and check that the file is gone. + base::ClosePlatformFile(file); + base::ClosePlatformFile(same_file); + EXPECT_FALSE(base::PathExists(file_path)); +} + +TEST(PlatformFile, ReadWritePlatformFile) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + FilePath file_path = temp_dir.path().AppendASCII("read_write_file"); + base::PlatformFile file = base::CreatePlatformFile( + file_path, + base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_READ | + base::PLATFORM_FILE_WRITE, + NULL, + NULL); + EXPECT_NE(base::kInvalidPlatformFileValue, file); + + char data_to_write[] = "test"; + const int kTestDataSize = 4; + + // Write 0 bytes to the file. + int bytes_written = WriteFully(file, 0, data_to_write, 0); + EXPECT_EQ(0, bytes_written); + + // Write "test" to the file. + bytes_written = WriteFully(file, 0, data_to_write, kTestDataSize); + EXPECT_EQ(kTestDataSize, bytes_written); + + // Read from EOF. + char data_read_1[32]; + int bytes_read = ReadFully(file, kTestDataSize, data_read_1, kTestDataSize); + EXPECT_EQ(0, bytes_read); + + // Read from somewhere in the middle of the file. + const int kPartialReadOffset = 1; + bytes_read = ReadFully(file, kPartialReadOffset, data_read_1, kTestDataSize); + EXPECT_EQ(kTestDataSize - kPartialReadOffset, bytes_read); + for (int i = 0; i < bytes_read; i++) + EXPECT_EQ(data_to_write[i + kPartialReadOffset], data_read_1[i]); + + // Read 0 bytes. + bytes_read = ReadFully(file, 0, data_read_1, 0); + EXPECT_EQ(0, bytes_read); + + // Read the entire file. + bytes_read = ReadFully(file, 0, data_read_1, kTestDataSize); + EXPECT_EQ(kTestDataSize, bytes_read); + for (int i = 0; i < bytes_read; i++) + EXPECT_EQ(data_to_write[i], data_read_1[i]); + + // Read again, but using the trivial native wrapper. + bytes_read = base::ReadPlatformFileNoBestEffort(file, 0, data_read_1, + kTestDataSize); + EXPECT_LE(bytes_read, kTestDataSize); + for (int i = 0; i < bytes_read; i++) + EXPECT_EQ(data_to_write[i], data_read_1[i]); + + // Write past the end of the file. + const int kOffsetBeyondEndOfFile = 10; + const int kPartialWriteLength = 2; + bytes_written = WriteFully(file, kOffsetBeyondEndOfFile, + data_to_write, kPartialWriteLength); + EXPECT_EQ(kPartialWriteLength, bytes_written); + + // Make sure the file was extended. + int64 file_size = 0; + EXPECT_TRUE(file_util::GetFileSize(file_path, &file_size)); + EXPECT_EQ(kOffsetBeyondEndOfFile + kPartialWriteLength, file_size); + + // Make sure the file was zero-padded. + char data_read_2[32]; + bytes_read = ReadFully(file, 0, data_read_2, static_cast(file_size)); + EXPECT_EQ(file_size, bytes_read); + for (int i = 0; i < kTestDataSize; i++) + EXPECT_EQ(data_to_write[i], data_read_2[i]); + for (int i = kTestDataSize; i < kOffsetBeyondEndOfFile; i++) + EXPECT_EQ(0, data_read_2[i]); + for (int i = kOffsetBeyondEndOfFile; i < file_size; i++) + EXPECT_EQ(data_to_write[i - kOffsetBeyondEndOfFile], data_read_2[i]); + + // Close the file handle to allow the temp directory to be deleted. + base::ClosePlatformFile(file); +} + +TEST(PlatformFile, AppendPlatformFile) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + FilePath file_path = temp_dir.path().AppendASCII("append_file"); + base::PlatformFile file = base::CreatePlatformFile( + file_path, + base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_APPEND, + NULL, + NULL); + EXPECT_NE(base::kInvalidPlatformFileValue, file); + + char data_to_write[] = "test"; + const int kTestDataSize = 4; + + // Write 0 bytes to the file. + int bytes_written = WriteFully(file, 0, data_to_write, 0); + EXPECT_EQ(0, bytes_written); + + // Write "test" to the file. + bytes_written = WriteFully(file, 0, data_to_write, kTestDataSize); + EXPECT_EQ(kTestDataSize, bytes_written); + + base::ClosePlatformFile(file); + file = base::CreatePlatformFile( + file_path, + base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ | + base::PLATFORM_FILE_APPEND, + NULL, + NULL); + EXPECT_NE(base::kInvalidPlatformFileValue, file); + + char append_data_to_write[] = "78"; + const int kAppendDataSize = 2; + + // Append "78" to the file. + bytes_written = WriteFully(file, 0, append_data_to_write, kAppendDataSize); + EXPECT_EQ(kAppendDataSize, bytes_written); + + // Read the entire file. + char data_read_1[32]; + int bytes_read = ReadFully(file, 0, data_read_1, + kTestDataSize + kAppendDataSize); + EXPECT_EQ(kTestDataSize + kAppendDataSize, bytes_read); + for (int i = 0; i < kTestDataSize; i++) + EXPECT_EQ(data_to_write[i], data_read_1[i]); + for (int i = 0; i < kAppendDataSize; i++) + EXPECT_EQ(append_data_to_write[i], data_read_1[kTestDataSize + i]); + + // Close the file handle to allow the temp directory to be deleted. + base::ClosePlatformFile(file); +} + + +TEST(PlatformFile, TruncatePlatformFile) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + FilePath file_path = temp_dir.path().AppendASCII("truncate_file"); + base::PlatformFile file = base::CreatePlatformFile( + file_path, + base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_READ | + base::PLATFORM_FILE_WRITE, + NULL, + NULL); + EXPECT_NE(base::kInvalidPlatformFileValue, file); + + // Write "test" to the file. + char data_to_write[] = "test"; + int kTestDataSize = 4; + int bytes_written = WriteFully(file, 0, data_to_write, kTestDataSize); + EXPECT_EQ(kTestDataSize, bytes_written); + + // Extend the file. + const int kExtendedFileLength = 10; + int64 file_size = 0; + EXPECT_TRUE(base::TruncatePlatformFile(file, kExtendedFileLength)); + EXPECT_TRUE(file_util::GetFileSize(file_path, &file_size)); + EXPECT_EQ(kExtendedFileLength, file_size); + + // Make sure the file was zero-padded. + char data_read[32]; + int bytes_read = ReadFully(file, 0, data_read, static_cast(file_size)); + EXPECT_EQ(file_size, bytes_read); + for (int i = 0; i < kTestDataSize; i++) + EXPECT_EQ(data_to_write[i], data_read[i]); + for (int i = kTestDataSize; i < file_size; i++) + EXPECT_EQ(0, data_read[i]); + + // Truncate the file. + const int kTruncatedFileLength = 2; + EXPECT_TRUE(base::TruncatePlatformFile(file, kTruncatedFileLength)); + EXPECT_TRUE(file_util::GetFileSize(file_path, &file_size)); + EXPECT_EQ(kTruncatedFileLength, file_size); + + // Make sure the file was truncated. + bytes_read = ReadFully(file, 0, data_read, kTestDataSize); + EXPECT_EQ(file_size, bytes_read); + for (int i = 0; i < file_size; i++) + EXPECT_EQ(data_to_write[i], data_read[i]); + + // Close the file handle to allow the temp directory to be deleted. + base::ClosePlatformFile(file); +} + +// Flakily fails: http://crbug.com/86494 +#if defined(OS_ANDROID) +TEST(PlatformFile, TouchGetInfoPlatformFile) { +#else +TEST(PlatformFile, DISABLED_TouchGetInfoPlatformFile) { +#endif + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::PlatformFile file = base::CreatePlatformFile( + temp_dir.path().AppendASCII("touch_get_info_file"), + base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE | + base::PLATFORM_FILE_WRITE_ATTRIBUTES, + NULL, + NULL); + EXPECT_NE(base::kInvalidPlatformFileValue, file); + + // Get info for a newly created file. + base::PlatformFileInfo info; + EXPECT_TRUE(base::GetPlatformFileInfo(file, &info)); + + // Add 2 seconds to account for possible rounding errors on + // filesystems that use a 1s or 2s timestamp granularity. + base::Time now = base::Time::Now() + base::TimeDelta::FromSeconds(2); + EXPECT_EQ(0, info.size); + EXPECT_FALSE(info.is_directory); + EXPECT_FALSE(info.is_symbolic_link); + EXPECT_LE(info.last_accessed.ToInternalValue(), now.ToInternalValue()); + EXPECT_LE(info.last_modified.ToInternalValue(), now.ToInternalValue()); + EXPECT_LE(info.creation_time.ToInternalValue(), now.ToInternalValue()); + base::Time creation_time = info.creation_time; + + // Write "test" to the file. + char data[] = "test"; + const int kTestDataSize = 4; + int bytes_written = WriteFully(file, 0, data, kTestDataSize); + EXPECT_EQ(kTestDataSize, bytes_written); + + // Change the last_accessed and last_modified dates. + // It's best to add values that are multiples of 2 (in seconds) + // to the current last_accessed and last_modified times, because + // FATxx uses a 2s timestamp granularity. + base::Time new_last_accessed = + info.last_accessed + base::TimeDelta::FromSeconds(234); + base::Time new_last_modified = + info.last_modified + base::TimeDelta::FromMinutes(567); + + EXPECT_TRUE(base::TouchPlatformFile(file, new_last_accessed, + new_last_modified)); + + // Make sure the file info was updated accordingly. + EXPECT_TRUE(base::GetPlatformFileInfo(file, &info)); + EXPECT_EQ(info.size, kTestDataSize); + EXPECT_FALSE(info.is_directory); + EXPECT_FALSE(info.is_symbolic_link); + + // ext2/ext3 and HPS/HPS+ seem to have a timestamp granularity of 1s. +#if defined(OS_POSIX) + EXPECT_EQ(info.last_accessed.ToTimeVal().tv_sec, + new_last_accessed.ToTimeVal().tv_sec); + EXPECT_EQ(info.last_modified.ToTimeVal().tv_sec, + new_last_modified.ToTimeVal().tv_sec); +#else + EXPECT_EQ(info.last_accessed.ToInternalValue(), + new_last_accessed.ToInternalValue()); + EXPECT_EQ(info.last_modified.ToInternalValue(), + new_last_modified.ToInternalValue()); +#endif + + EXPECT_EQ(info.creation_time.ToInternalValue(), + creation_time.ToInternalValue()); + + // Close the file handle to allow the temp directory to be deleted. + base::ClosePlatformFile(file); +} diff --git a/base/platform_file_win.cc b/base/platform_file_win.cc new file mode 100644 index 0000000000..c5b49fa7e8 --- /dev/null +++ b/base/platform_file_win.cc @@ -0,0 +1,301 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/platform_file.h" + +#include + +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/metrics/sparse_histogram.h" +#include "base/threading/thread_restrictions.h" + +namespace base { +PlatformFile CreatePlatformFileUnsafe(const FilePath& name, + int flags, + bool* created, + PlatformFileError* error) { + base::ThreadRestrictions::AssertIOAllowed(); + + DWORD disposition = 0; + if (created) + *created = false; + + if (flags & PLATFORM_FILE_OPEN) + disposition = OPEN_EXISTING; + + if (flags & PLATFORM_FILE_CREATE) { + DCHECK(!disposition); + disposition = CREATE_NEW; + } + + if (flags & PLATFORM_FILE_OPEN_ALWAYS) { + DCHECK(!disposition); + disposition = OPEN_ALWAYS; + } + + if (flags & PLATFORM_FILE_CREATE_ALWAYS) { + DCHECK(!disposition); + disposition = CREATE_ALWAYS; + } + + if (flags & PLATFORM_FILE_OPEN_TRUNCATED) { + DCHECK(!disposition); + DCHECK(flags & PLATFORM_FILE_WRITE); + disposition = TRUNCATE_EXISTING; + } + + if (!disposition) { + NOTREACHED(); + return NULL; + } + + DWORD access = 0; + if (flags & PLATFORM_FILE_WRITE) + access = GENERIC_WRITE; + if (flags & PLATFORM_FILE_APPEND) { + DCHECK(!access); + access = FILE_APPEND_DATA; + } + if (flags & PLATFORM_FILE_READ) + access |= GENERIC_READ; + if (flags & PLATFORM_FILE_WRITE_ATTRIBUTES) + access |= FILE_WRITE_ATTRIBUTES; + if (flags & PLATFORM_FILE_EXECUTE) + access |= GENERIC_EXECUTE; + + DWORD sharing = (flags & PLATFORM_FILE_EXCLUSIVE_READ) ? 0 : FILE_SHARE_READ; + if (!(flags & PLATFORM_FILE_EXCLUSIVE_WRITE)) + sharing |= FILE_SHARE_WRITE; + if (flags & PLATFORM_FILE_SHARE_DELETE) + sharing |= FILE_SHARE_DELETE; + + DWORD create_flags = 0; + if (flags & PLATFORM_FILE_ASYNC) + create_flags |= FILE_FLAG_OVERLAPPED; + if (flags & PLATFORM_FILE_TEMPORARY) + create_flags |= FILE_ATTRIBUTE_TEMPORARY; + if (flags & PLATFORM_FILE_HIDDEN) + create_flags |= FILE_ATTRIBUTE_HIDDEN; + if (flags & PLATFORM_FILE_DELETE_ON_CLOSE) + create_flags |= FILE_FLAG_DELETE_ON_CLOSE; + if (flags & PLATFORM_FILE_BACKUP_SEMANTICS) + create_flags |= FILE_FLAG_BACKUP_SEMANTICS; + + HANDLE file = CreateFile(name.value().c_str(), access, sharing, NULL, + disposition, create_flags, NULL); + + if (created && (INVALID_HANDLE_VALUE != file)) { + if (flags & (PLATFORM_FILE_OPEN_ALWAYS)) + *created = (ERROR_ALREADY_EXISTS != GetLastError()); + else if (flags & (PLATFORM_FILE_CREATE_ALWAYS | PLATFORM_FILE_CREATE)) + *created = true; + } + + if (error) { + if (file != kInvalidPlatformFileValue) + *error = PLATFORM_FILE_OK; + else + *error = LastErrorToPlatformFileError(GetLastError()); + } + + return file; +} + +FILE* FdopenPlatformFile(PlatformFile file, const char* mode) { + if (file == kInvalidPlatformFileValue) + return NULL; + int fd = _open_osfhandle(reinterpret_cast(file), 0); + if (fd < 0) + return NULL; + return _fdopen(fd, mode); +} + +bool ClosePlatformFile(PlatformFile file) { + base::ThreadRestrictions::AssertIOAllowed(); + return (CloseHandle(file) != 0); +} + +int64 SeekPlatformFile(PlatformFile file, + PlatformFileWhence whence, + int64 offset) { + base::ThreadRestrictions::AssertIOAllowed(); + if (file == kInvalidPlatformFileValue || offset < 0) + return -1; + + LARGE_INTEGER distance, res; + distance.QuadPart = offset; + DWORD move_method = static_cast(whence); + if (!SetFilePointerEx(file, distance, &res, move_method)) + return -1; + return res.QuadPart; +} + +int ReadPlatformFile(PlatformFile file, int64 offset, char* data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + if (file == kInvalidPlatformFileValue) + return -1; + + LARGE_INTEGER offset_li; + offset_li.QuadPart = offset; + + OVERLAPPED overlapped = {0}; + overlapped.Offset = offset_li.LowPart; + overlapped.OffsetHigh = offset_li.HighPart; + + DWORD bytes_read; + if (::ReadFile(file, data, size, &bytes_read, &overlapped) != 0) + return bytes_read; + else if (ERROR_HANDLE_EOF == GetLastError()) + return 0; + + return -1; +} + +int ReadPlatformFileAtCurrentPos(PlatformFile file, char* data, int size) { + return ReadPlatformFile(file, 0, data, size); +} + +int ReadPlatformFileNoBestEffort(PlatformFile file, int64 offset, char* data, + int size) { + return ReadPlatformFile(file, offset, data, size); +} + +int ReadPlatformFileCurPosNoBestEffort(PlatformFile file, + char* data, int size) { + return ReadPlatformFile(file, 0, data, size); +} + +int WritePlatformFile(PlatformFile file, int64 offset, + const char* data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + if (file == kInvalidPlatformFileValue) + return -1; + + LARGE_INTEGER offset_li; + offset_li.QuadPart = offset; + + OVERLAPPED overlapped = {0}; + overlapped.Offset = offset_li.LowPart; + overlapped.OffsetHigh = offset_li.HighPart; + + DWORD bytes_written; + if (::WriteFile(file, data, size, &bytes_written, &overlapped) != 0) + return bytes_written; + + return -1; +} + +int WritePlatformFileAtCurrentPos(PlatformFile file, const char* data, + int size) { + return WritePlatformFile(file, 0, data, size); +} + +int WritePlatformFileCurPosNoBestEffort(PlatformFile file, + const char* data, int size) { + return WritePlatformFile(file, 0, data, size); +} + +bool TruncatePlatformFile(PlatformFile file, int64 length) { + base::ThreadRestrictions::AssertIOAllowed(); + if (file == kInvalidPlatformFileValue) + return false; + + // Get the current file pointer. + LARGE_INTEGER file_pointer; + LARGE_INTEGER zero; + zero.QuadPart = 0; + if (::SetFilePointerEx(file, zero, &file_pointer, FILE_CURRENT) == 0) + return false; + + LARGE_INTEGER length_li; + length_li.QuadPart = length; + // If length > file size, SetFilePointerEx() should extend the file + // with zeroes on all Windows standard file systems (NTFS, FATxx). + if (!::SetFilePointerEx(file, length_li, NULL, FILE_BEGIN)) + return false; + + // Set the new file length and move the file pointer to its old position. + // This is consistent with ftruncate()'s behavior, even when the file + // pointer points to a location beyond the end of the file. + return ((::SetEndOfFile(file) != 0) && + (::SetFilePointerEx(file, file_pointer, NULL, FILE_BEGIN) != 0)); +} + +bool FlushPlatformFile(PlatformFile file) { + base::ThreadRestrictions::AssertIOAllowed(); + return ((file != kInvalidPlatformFileValue) && ::FlushFileBuffers(file)); +} + +bool TouchPlatformFile(PlatformFile file, const base::Time& last_access_time, + const base::Time& last_modified_time) { + base::ThreadRestrictions::AssertIOAllowed(); + if (file == kInvalidPlatformFileValue) + return false; + + FILETIME last_access_filetime = last_access_time.ToFileTime(); + FILETIME last_modified_filetime = last_modified_time.ToFileTime(); + return (::SetFileTime(file, NULL, &last_access_filetime, + &last_modified_filetime) != 0); +} + +bool GetPlatformFileInfo(PlatformFile file, PlatformFileInfo* info) { + base::ThreadRestrictions::AssertIOAllowed(); + if (!info) + return false; + + BY_HANDLE_FILE_INFORMATION file_info; + if (GetFileInformationByHandle(file, &file_info) == 0) + return false; + + LARGE_INTEGER size; + size.HighPart = file_info.nFileSizeHigh; + size.LowPart = file_info.nFileSizeLow; + info->size = size.QuadPart; + info->is_directory = + (file_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + info->is_symbolic_link = false; // Windows doesn't have symbolic links. + info->last_modified = base::Time::FromFileTime(file_info.ftLastWriteTime); + info->last_accessed = base::Time::FromFileTime(file_info.ftLastAccessTime); + info->creation_time = base::Time::FromFileTime(file_info.ftCreationTime); + return true; +} + +PlatformFileError LastErrorToPlatformFileError(DWORD last_error) { + switch (last_error) { + case ERROR_SHARING_VIOLATION: + return PLATFORM_FILE_ERROR_IN_USE; + case ERROR_FILE_EXISTS: + return PLATFORM_FILE_ERROR_EXISTS; + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + return PLATFORM_FILE_ERROR_NOT_FOUND; + case ERROR_ACCESS_DENIED: + return PLATFORM_FILE_ERROR_ACCESS_DENIED; + case ERROR_TOO_MANY_OPEN_FILES: + return PLATFORM_FILE_ERROR_TOO_MANY_OPENED; + case ERROR_OUTOFMEMORY: + case ERROR_NOT_ENOUGH_MEMORY: + return PLATFORM_FILE_ERROR_NO_MEMORY; + case ERROR_HANDLE_DISK_FULL: + case ERROR_DISK_FULL: + case ERROR_DISK_RESOURCES_EXHAUSTED: + return PLATFORM_FILE_ERROR_NO_SPACE; + case ERROR_USER_MAPPED_FILE: + return PLATFORM_FILE_ERROR_INVALID_OPERATION; + case ERROR_NOT_READY: + case ERROR_SECTOR_NOT_FOUND: + case ERROR_DEV_NOT_EXIST: + case ERROR_IO_DEVICE: + case ERROR_FILE_CORRUPT: + case ERROR_DISK_CORRUPT: + return PLATFORM_FILE_ERROR_IO; + default: + UMA_HISTOGRAM_SPARSE_SLOWLY("PlatformFile.UnknownErrors.Windows", + last_error); + return PLATFORM_FILE_ERROR_FAILED; + } +} + +} // namespace base diff --git a/base/port.h b/base/port.h new file mode 100644 index 0000000000..af4e450afe --- /dev/null +++ b/base/port.h @@ -0,0 +1,54 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_PORT_H_ +#define BASE_PORT_H_ + +#include +#include "build/build_config.h" + +#ifdef COMPILER_MSVC +#define GG_LONGLONG(x) x##I64 +#define GG_ULONGLONG(x) x##UI64 +#else +#define GG_LONGLONG(x) x##LL +#define GG_ULONGLONG(x) x##ULL +#endif + +// Per C99 7.8.14, define __STDC_CONSTANT_MACROS before including +// to get the INTn_C and UINTn_C macros for integer constants. It's difficult +// to guarantee any specific ordering of header includes, so it's difficult to +// guarantee that the INTn_C macros can be defined by including at +// any specific point. Provide GG_INTn_C macros instead. + +#define GG_INT8_C(x) (x) +#define GG_INT16_C(x) (x) +#define GG_INT32_C(x) (x) +#define GG_INT64_C(x) GG_LONGLONG(x) + +#define GG_UINT8_C(x) (x ## U) +#define GG_UINT16_C(x) (x ## U) +#define GG_UINT32_C(x) (x ## U) +#define GG_UINT64_C(x) GG_ULONGLONG(x) + +// It's possible for functions that use a va_list, such as StringPrintf, to +// invalidate the data in it upon use. The fix is to make a copy of the +// structure before using it and use that copy instead. va_copy is provided +// for this purpose. MSVC does not provide va_copy, so define an +// implementation here. It is not guaranteed that assignment is a copy, so the +// StringUtil.VariableArgsFunc unit test tests this capability. +#if defined(COMPILER_GCC) +#define GG_VA_COPY(a, b) (va_copy(a, b)) +#elif defined(COMPILER_MSVC) +#define GG_VA_COPY(a, b) (a = b) +#endif + +// Define an OS-neutral wrapper for shared library entry points +#if defined(OS_WIN) +#define API_CALL __stdcall +#else +#define API_CALL +#endif + +#endif // BASE_PORT_H_ diff --git a/base/posix/eintr_wrapper.h b/base/posix/eintr_wrapper.h new file mode 100644 index 0000000000..8e26752337 --- /dev/null +++ b/base/posix/eintr_wrapper.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. + +// This provides a wrapper around system calls which may be interrupted by a +// signal and return EINTR. See man 7 signal. +// To prevent long-lasting loops (which would likely be a bug, such as a signal +// that should be masked) to go unnoticed, there is a limit after which the +// caller will nonetheless see an EINTR in Debug builds. +// +// On Windows, this wrapper macro does nothing. + +#ifndef BASE_POSIX_EINTR_WRAPPER_H_ +#define BASE_POSIX_EINTR_WRAPPER_H_ + +#include "build/build_config.h" + +#if defined(OS_POSIX) + +#include + +#if defined(NDEBUG) +#define HANDLE_EINTR(x) ({ \ + typeof(x) eintr_wrapper_result; \ + do { \ + eintr_wrapper_result = (x); \ + } while (eintr_wrapper_result == -1 && errno == EINTR); \ + eintr_wrapper_result; \ +}) + +#else + +#define HANDLE_EINTR(x) ({ \ + int eintr_wrapper_counter = 0; \ + typeof(x) eintr_wrapper_result; \ + do { \ + eintr_wrapper_result = (x); \ + } while (eintr_wrapper_result == -1 && errno == EINTR && \ + eintr_wrapper_counter++ < 100); \ + eintr_wrapper_result; \ +}) + +#endif // NDEBUG + +#else + +#define HANDLE_EINTR(x) (x) + +#endif // OS_POSIX + +#endif // BASE_POSIX_EINTR_WRAPPER_H_ diff --git a/base/posix/file_descriptor_shuffle.cc b/base/posix/file_descriptor_shuffle.cc new file mode 100644 index 0000000000..b5b7339bdf --- /dev/null +++ b/base/posix/file_descriptor_shuffle.cc @@ -0,0 +1,96 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/posix/file_descriptor_shuffle.h" + +#include +#include +#include + +#include "base/posix/eintr_wrapper.h" +#include "base/logging.h" + +namespace base { + +bool PerformInjectiveMultimapDestructive( + InjectiveMultimap* m, InjectionDelegate* delegate) { + static const size_t kMaxExtraFDs = 16; + int extra_fds[kMaxExtraFDs]; + unsigned next_extra_fd = 0; + + // DANGER: this function may not allocate. + + for (InjectiveMultimap::iterator i = m->begin(); i != m->end(); ++i) { + int temp_fd = -1; + + // We DCHECK the injectiveness of the mapping. + for (InjectiveMultimap::iterator j = i + 1; j != m->end(); ++j) { + DCHECK(i->dest != j->dest) << "Both fd " << i->source + << " and " << j->source << " map to " << i->dest; + } + + const bool is_identity = i->source == i->dest; + + for (InjectiveMultimap::iterator j = i + 1; j != m->end(); ++j) { + if (!is_identity && i->dest == j->source) { + if (temp_fd == -1) { + if (!delegate->Duplicate(&temp_fd, i->dest)) + return false; + if (next_extra_fd < kMaxExtraFDs) { + extra_fds[next_extra_fd++] = temp_fd; + } else { + RAW_LOG(ERROR, "PerformInjectiveMultimapDestructive overflowed " + "extra_fds. Leaking file descriptors!"); + } + } + + j->source = temp_fd; + j->close = false; + } + + if (i->close && i->source == j->dest) + i->close = false; + + if (i->close && i->source == j->source) { + i->close = false; + j->close = true; + } + } + + if (!is_identity) { + if (!delegate->Move(i->source, i->dest)) + return false; + } + + if (!is_identity && i->close) + delegate->Close(i->source); + } + + for (unsigned i = 0; i < next_extra_fd; i++) + delegate->Close(extra_fds[i]); + + return true; +} + +bool PerformInjectiveMultimap(const InjectiveMultimap& m_in, + InjectionDelegate* delegate) { + InjectiveMultimap m(m_in); + return PerformInjectiveMultimapDestructive(&m, delegate); +} + +bool FileDescriptorTableInjection::Duplicate(int* result, int fd) { + *result = HANDLE_EINTR(dup(fd)); + return *result >= 0; +} + +bool FileDescriptorTableInjection::Move(int src, int dest) { + return HANDLE_EINTR(dup2(src, dest)) != -1; +} + +void FileDescriptorTableInjection::Close(int fd) { + int ret = HANDLE_EINTR(close(fd)); + DPCHECK(ret == 0); +} + +} // namespace base diff --git a/base/posix/file_descriptor_shuffle.h b/base/posix/file_descriptor_shuffle.h new file mode 100644 index 0000000000..9cd918f2e1 --- /dev/null +++ b/base/posix/file_descriptor_shuffle.h @@ -0,0 +1,87 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_POSIX_FILE_DESCRIPTOR_SHUFFLE_H_ +#define BASE_POSIX_FILE_DESCRIPTOR_SHUFFLE_H_ + +// This code exists to perform the shuffling of file descriptors which is +// commonly needed when forking subprocesses. The naive approve is very simple, +// just call dup2 to setup the desired descriptors, but wrong. It's tough to +// handle the edge cases (like mapping 0 -> 1, 1 -> 0) correctly. +// +// In order to unittest this code, it's broken into the abstract action (an +// injective multimap) and the concrete code for dealing with file descriptors. +// Users should use the code like this: +// base::InjectiveMultimap file_descriptor_map; +// file_descriptor_map.push_back(base::InjectionArc(devnull, 0, true)); +// file_descriptor_map.push_back(base::InjectionArc(devnull, 2, true)); +// file_descriptor_map.push_back(base::InjectionArc(pipe[1], 1, true)); +// base::ShuffleFileDescriptors(file_descriptor_map); +// +// and trust the the Right Thing will get done. + +#include + +#include "base/base_export.h" +#include "base/compiler_specific.h" + +namespace base { + +// A Delegate which performs the actions required to perform an injective +// multimapping in place. +class InjectionDelegate { + public: + // Duplicate |fd|, an element of the domain, and write a fresh element of the + // domain into |result|. Returns true iff successful. + virtual bool Duplicate(int* result, int fd) = 0; + // Destructively move |src| to |dest|, overwriting |dest|. Returns true iff + // successful. + virtual bool Move(int src, int dest) = 0; + // Delete an element of the domain. + virtual void Close(int fd) = 0; + + protected: + virtual ~InjectionDelegate() {} +}; + +// An implementation of the InjectionDelegate interface using the file +// descriptor table of the current process as the domain. +class BASE_EXPORT FileDescriptorTableInjection : public InjectionDelegate { + virtual bool Duplicate(int* result, int fd) OVERRIDE; + virtual bool Move(int src, int dest) OVERRIDE; + virtual void Close(int fd) OVERRIDE; +}; + +// A single arc of the directed graph which describes an injective multimapping. +struct InjectionArc { + InjectionArc(int in_source, int in_dest, bool in_close) + : source(in_source), + dest(in_dest), + close(in_close) { + } + + int source; + int dest; + bool close; // if true, delete the source element after performing the + // mapping. +}; + +typedef std::vector InjectiveMultimap; + +BASE_EXPORT bool PerformInjectiveMultimap(const InjectiveMultimap& map, + InjectionDelegate* delegate); + +BASE_EXPORT bool PerformInjectiveMultimapDestructive( + InjectiveMultimap* map, + InjectionDelegate* delegate); + +// This function will not call malloc but will mutate |map| +static inline bool ShuffleFileDescriptors(InjectiveMultimap* map) { + FileDescriptorTableInjection delegate; + return PerformInjectiveMultimapDestructive(map, &delegate); +} + +} // namespace base + +#endif // BASE_POSIX_FILE_DESCRIPTOR_SHUFFLE_H_ diff --git a/base/posix/file_descriptor_shuffle_unittest.cc b/base/posix/file_descriptor_shuffle_unittest.cc new file mode 100644 index 0000000000..9e1b25000e --- /dev/null +++ b/base/posix/file_descriptor_shuffle_unittest.cc @@ -0,0 +1,287 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/posix/file_descriptor_shuffle.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +// 'Duplicated' file descriptors start at this number +const int kDuplicateBase = 1000; + +} // namespace + +namespace base { + +struct Action { + enum Type { + CLOSE, + MOVE, + DUPLICATE, + }; + + Action(Type in_type, int in_fd1, int in_fd2 = -1) + : type(in_type), + fd1(in_fd1), + fd2(in_fd2) { + } + + bool operator==(const Action& other) const { + return other.type == type && + other.fd1 == fd1 && + other.fd2 == fd2; + } + + Type type; + int fd1; + int fd2; +}; + +class InjectionTracer : public InjectionDelegate { + public: + InjectionTracer() + : next_duplicate_(kDuplicateBase) { + } + + virtual bool Duplicate(int* result, int fd) OVERRIDE { + *result = next_duplicate_++; + actions_.push_back(Action(Action::DUPLICATE, *result, fd)); + return true; + } + + virtual bool Move(int src, int dest) OVERRIDE { + actions_.push_back(Action(Action::MOVE, src, dest)); + return true; + } + + virtual void Close(int fd) OVERRIDE { + actions_.push_back(Action(Action::CLOSE, fd)); + } + + const std::vector& actions() const { return actions_; } + + private: + int next_duplicate_; + std::vector actions_; +}; + +TEST(FileDescriptorShuffleTest, Empty) { + InjectiveMultimap map; + InjectionTracer tracer; + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + EXPECT_EQ(0u, tracer.actions().size()); +} + +TEST(FileDescriptorShuffleTest, Noop) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 0, false)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + EXPECT_EQ(0u, tracer.actions().size()); +} + +TEST(FileDescriptorShuffleTest, NoopAndClose) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 0, true)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + EXPECT_EQ(0u, tracer.actions().size()); +} + +TEST(FileDescriptorShuffleTest, Simple1) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 1, false)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(1u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1)); +} + +TEST(FileDescriptorShuffleTest, Simple2) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 1, false)); + map.push_back(InjectionArc(2, 3, false)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(2u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1)); + EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 2, 3)); +} + +TEST(FileDescriptorShuffleTest, Simple3) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 1, true)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(2u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1)); + EXPECT_TRUE(tracer.actions()[1] == Action(Action::CLOSE, 0)); +} + +TEST(FileDescriptorShuffleTest, Simple4) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(10, 0, true)); + map.push_back(InjectionArc(1, 1, true)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(2u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 10, 0)); + EXPECT_TRUE(tracer.actions()[1] == Action(Action::CLOSE, 10)); +} + +TEST(FileDescriptorShuffleTest, Cycle) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 1, false)); + map.push_back(InjectionArc(1, 0, false)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(4u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == + Action(Action::DUPLICATE, kDuplicateBase, 1)); + EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 1)); + EXPECT_TRUE(tracer.actions()[2] == Action(Action::MOVE, kDuplicateBase, 0)); + EXPECT_TRUE(tracer.actions()[3] == Action(Action::CLOSE, kDuplicateBase)); +} + +TEST(FileDescriptorShuffleTest, CycleAndClose1) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 1, true)); + map.push_back(InjectionArc(1, 0, false)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(4u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == + Action(Action::DUPLICATE, kDuplicateBase, 1)); + EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 1)); + EXPECT_TRUE(tracer.actions()[2] == Action(Action::MOVE, kDuplicateBase, 0)); + EXPECT_TRUE(tracer.actions()[3] == Action(Action::CLOSE, kDuplicateBase)); +} + +TEST(FileDescriptorShuffleTest, CycleAndClose2) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 1, false)); + map.push_back(InjectionArc(1, 0, true)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(4u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == + Action(Action::DUPLICATE, kDuplicateBase, 1)); + EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 1)); + EXPECT_TRUE(tracer.actions()[2] == Action(Action::MOVE, kDuplicateBase, 0)); + EXPECT_TRUE(tracer.actions()[3] == Action(Action::CLOSE, kDuplicateBase)); +} + +TEST(FileDescriptorShuffleTest, CycleAndClose3) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 1, true)); + map.push_back(InjectionArc(1, 0, true)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(4u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == + Action(Action::DUPLICATE, kDuplicateBase, 1)); + EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 1)); + EXPECT_TRUE(tracer.actions()[2] == Action(Action::MOVE, kDuplicateBase, 0)); + EXPECT_TRUE(tracer.actions()[3] == Action(Action::CLOSE, kDuplicateBase)); +} + +TEST(FileDescriptorShuffleTest, Fanout) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 1, false)); + map.push_back(InjectionArc(0, 2, false)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(2u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1)); + EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 2)); +} + +TEST(FileDescriptorShuffleTest, FanoutAndClose1) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 1, true)); + map.push_back(InjectionArc(0, 2, false)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(3u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1)); + EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 2)); + EXPECT_TRUE(tracer.actions()[2] == Action(Action::CLOSE, 0)); +} + +TEST(FileDescriptorShuffleTest, FanoutAndClose2) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 1, false)); + map.push_back(InjectionArc(0, 2, true)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(3u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1)); + EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 2)); + EXPECT_TRUE(tracer.actions()[2] == Action(Action::CLOSE, 0)); +} + +TEST(FileDescriptorShuffleTest, FanoutAndClose3) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 1, true)); + map.push_back(InjectionArc(0, 2, true)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(3u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1)); + EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 2)); + EXPECT_TRUE(tracer.actions()[2] == Action(Action::CLOSE, 0)); +} + +class FailingDelegate : public InjectionDelegate { + public: + virtual bool Duplicate(int* result, int fd) OVERRIDE { + return false; + } + + virtual bool Move(int src, int dest) OVERRIDE { + return false; + } + + virtual void Close(int fd) OVERRIDE {} +}; + +TEST(FileDescriptorShuffleTest, EmptyWithFailure) { + InjectiveMultimap map; + FailingDelegate failing; + + EXPECT_TRUE(PerformInjectiveMultimap(map, &failing)); +} + +TEST(FileDescriptorShuffleTest, NoopWithFailure) { + InjectiveMultimap map; + FailingDelegate failing; + map.push_back(InjectionArc(0, 0, false)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &failing)); +} + +TEST(FileDescriptorShuffleTest, Simple1WithFailure) { + InjectiveMultimap map; + FailingDelegate failing; + map.push_back(InjectionArc(0, 1, false)); + + EXPECT_FALSE(PerformInjectiveMultimap(map, &failing)); +} + +} // namespace base diff --git a/base/posix/global_descriptors.cc b/base/posix/global_descriptors.cc new file mode 100644 index 0000000000..bcca443acc --- /dev/null +++ b/base/posix/global_descriptors.cc @@ -0,0 +1,60 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/posix/global_descriptors.h" + +#include +#include + +#include "base/logging.h" + +namespace base { + +// static +GlobalDescriptors* GlobalDescriptors::GetInstance() { + typedef Singleton > + GlobalDescriptorsSingleton; + return GlobalDescriptorsSingleton::get(); +} + +int GlobalDescriptors::Get(Key key) const { + const int ret = MaybeGet(key); + + if (ret == -1) + DLOG(FATAL) << "Unknown global descriptor: " << key; + return ret; +} + +int GlobalDescriptors::MaybeGet(Key key) const { + for (Mapping::const_iterator + i = descriptors_.begin(); i != descriptors_.end(); ++i) { + if (i->first == key) + return i->second; + } + + return -1; +} + +void GlobalDescriptors::Set(Key key, int fd) { + for (Mapping::iterator + i = descriptors_.begin(); i != descriptors_.end(); ++i) { + if (i->first == key) { + i->second = fd; + return; + } + } + + descriptors_.push_back(std::make_pair(key, fd)); +} + +void GlobalDescriptors::Reset(const Mapping& mapping) { + descriptors_ = mapping; +} + +GlobalDescriptors::GlobalDescriptors() {} + +GlobalDescriptors::~GlobalDescriptors() {} + +} // namespace base diff --git a/base/posix/global_descriptors.h b/base/posix/global_descriptors.h new file mode 100644 index 0000000000..c7b9f87fa4 --- /dev/null +++ b/base/posix/global_descriptors.h @@ -0,0 +1,70 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_POSIX_GLOBAL_DESCRIPTORS_H_ +#define BASE_POSIX_GLOBAL_DESCRIPTORS_H_ + +#include "build/build_config.h" + +#include +#include + +#include + +#include "base/memory/singleton.h" + +namespace base { + +// It's common practice to install file descriptors into well known slot +// numbers before execing a child; stdin, stdout and stderr are ubiqutous +// examples. +// +// However, when using a zygote model, this becomes troublesome. Since the +// descriptors which need to be in these slots generally aren't known, any code +// could open a resource and take one of the reserved descriptors. Simply +// overwriting the slot isn't a viable solution. +// +// We could try to fill the reserved slots as soon as possible, but this is a +// fragile solution since global constructors etc are able to open files. +// +// Instead, we retreat from the idea of installing descriptors in specific +// slots and add a layer of indirection in the form of this singleton object. +// It maps from an abstract key to a descriptor. If independent modules each +// need to define keys, then values should be chosen randomly so as not to +// collide. +class BASE_EXPORT GlobalDescriptors { + public: + typedef uint32_t Key; + typedef std::pair KeyFDPair; + typedef std::vector Mapping; + + // Often we want a canonical descriptor for a given Key. In this case, we add + // the following constant to the key value: + static const int kBaseDescriptor = 3; // 0, 1, 2 are already taken. + + // Return the singleton instance of GlobalDescriptors. + static GlobalDescriptors* GetInstance(); + + // Get a descriptor given a key. It is a fatal error if the key is not known. + int Get(Key key) const; + + // Get a descriptor give a key. Returns -1 on error. + int MaybeGet(Key key) const; + + // Set the descriptor for the given key. + void Set(Key key, int fd); + + void Reset(const Mapping& mapping); + + private: + friend struct DefaultSingletonTraits; + GlobalDescriptors(); + ~GlobalDescriptors(); + + Mapping descriptors_; +}; + +} // namespace base + +#endif // BASE_POSIX_GLOBAL_DESCRIPTORS_H_ diff --git a/base/posix/unix_domain_socket_linux.cc b/base/posix/unix_domain_socket_linux.cc new file mode 100644 index 0000000000..757db983cd --- /dev/null +++ b/base/posix/unix_domain_socket_linux.cc @@ -0,0 +1,173 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/posix/unix_domain_socket_linux.h" + +#include +#include +#include +#include + +#include "base/logging.h" +#include "base/pickle.h" +#include "base/posix/eintr_wrapper.h" +#include "base/stl_util.h" + +const size_t UnixDomainSocket::kMaxFileDescriptors = 16; + +// static +bool UnixDomainSocket::SendMsg(int fd, + const void* buf, + size_t length, + const std::vector& fds) { + struct msghdr msg = {}; + struct iovec iov = { const_cast(buf), length }; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + char* control_buffer = NULL; + if (fds.size()) { + const unsigned control_len = CMSG_SPACE(sizeof(int) * fds.size()); + control_buffer = new char[control_len]; + + struct cmsghdr* cmsg; + msg.msg_control = control_buffer; + msg.msg_controllen = control_len; + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int) * fds.size()); + memcpy(CMSG_DATA(cmsg), &fds[0], sizeof(int) * fds.size()); + msg.msg_controllen = cmsg->cmsg_len; + } + + // Avoid a SIGPIPE if the other end breaks the connection. + // Due to a bug in the Linux kernel (net/unix/af_unix.c) MSG_NOSIGNAL isn't + // regarded for SOCK_SEQPACKET in the AF_UNIX domain, but it is mandated by + // POSIX. + const int flags = MSG_NOSIGNAL; + const ssize_t r = HANDLE_EINTR(sendmsg(fd, &msg, flags)); + const bool ret = static_cast(length) == r; + delete[] control_buffer; + return ret; +} + +// static +ssize_t UnixDomainSocket::RecvMsg(int fd, + void* buf, + size_t length, + std::vector* fds) { + return UnixDomainSocket::RecvMsgWithFlags(fd, buf, length, 0, fds); +} + +// static +ssize_t UnixDomainSocket::RecvMsgWithFlags(int fd, + void* buf, + size_t length, + int flags, + std::vector* fds) { + fds->clear(); + + struct msghdr msg = {}; + struct iovec iov = { buf, length }; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + char control_buffer[CMSG_SPACE(sizeof(int) * kMaxFileDescriptors)]; + msg.msg_control = control_buffer; + msg.msg_controllen = sizeof(control_buffer); + + const ssize_t r = HANDLE_EINTR(recvmsg(fd, &msg, flags)); + if (r == -1) + return -1; + + int* wire_fds = NULL; + unsigned wire_fds_len = 0; + + if (msg.msg_controllen > 0) { + struct cmsghdr* cmsg; + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_RIGHTS) { + const unsigned payload_len = cmsg->cmsg_len - CMSG_LEN(0); + DCHECK(payload_len % sizeof(int) == 0); + wire_fds = reinterpret_cast(CMSG_DATA(cmsg)); + wire_fds_len = payload_len / sizeof(int); + break; + } + } + } + + if (msg.msg_flags & MSG_TRUNC || msg.msg_flags & MSG_CTRUNC) { + for (unsigned i = 0; i < wire_fds_len; ++i) + close(wire_fds[i]); + errno = EMSGSIZE; + return -1; + } + + fds->resize(wire_fds_len); + memcpy(vector_as_array(fds), wire_fds, sizeof(int) * wire_fds_len); + + return r; +} + +// static +ssize_t UnixDomainSocket::SendRecvMsg(int fd, + uint8_t* reply, + unsigned max_reply_len, + int* result_fd, + const Pickle& request) { + return UnixDomainSocket::SendRecvMsgWithFlags(fd, reply, max_reply_len, + 0, /* recvmsg_flags */ + result_fd, request); +} + +// static +ssize_t UnixDomainSocket::SendRecvMsgWithFlags(int fd, + uint8_t* reply, + unsigned max_reply_len, + int recvmsg_flags, + int* result_fd, + const Pickle& request) { + int fds[2]; + + // This socketpair is only used for the IPC and is cleaned up before + // returning. + if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds) == -1) + return -1; + + std::vector fd_vector; + fd_vector.push_back(fds[1]); + if (!SendMsg(fd, request.data(), request.size(), fd_vector)) { + close(fds[0]); + close(fds[1]); + return -1; + } + close(fds[1]); + + fd_vector.clear(); + // When porting to OSX keep in mind it doesn't support MSG_NOSIGNAL, so the + // sender might get a SIGPIPE. + const ssize_t reply_len = RecvMsgWithFlags(fds[0], reply, max_reply_len, + recvmsg_flags, &fd_vector); + close(fds[0]); + if (reply_len == -1) + return -1; + + if ((!fd_vector.empty() && result_fd == NULL) || fd_vector.size() > 1) { + for (std::vector::const_iterator + i = fd_vector.begin(); i != fd_vector.end(); ++i) { + close(*i); + } + + NOTREACHED(); + + return -1; + } + + if (result_fd) + *result_fd = fd_vector.empty() ? -1 : fd_vector[0]; + + return reply_len; +} diff --git a/base/posix/unix_domain_socket_linux.h b/base/posix/unix_domain_socket_linux.h new file mode 100644 index 0000000000..66fb8bb1bf --- /dev/null +++ b/base/posix/unix_domain_socket_linux.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_POSIX_UNIX_DOMAIN_SOCKET_LINUX_H_ +#define BASE_POSIX_UNIX_DOMAIN_SOCKET_LINUX_H_ + +#include +#include +#include + +#include "base/base_export.h" + +class Pickle; + +class BASE_EXPORT UnixDomainSocket { + public: + // Maximum number of file descriptors that can be read by RecvMsg(). + static const size_t kMaxFileDescriptors; + + // Use sendmsg to write the given msg and include a vector of file + // descriptors. Returns true if successful. + static bool SendMsg(int fd, + const void* msg, + size_t length, + const std::vector& fds); + + // Use recvmsg to read a message and an array of file descriptors. Returns + // -1 on failure. Note: will read, at most, |kMaxFileDescriptors| descriptors. + static ssize_t RecvMsg(int fd, + void* msg, + size_t length, + std::vector* fds); + + // Perform a sendmsg/recvmsg pair. + // 1. This process creates a UNIX SEQPACKET socketpair. Using + // connection-oriented sockets (SEQPACKET or STREAM) is critical here, + // because if one of the ends closes the other one must be notified. + // 2. This process writes a request to |fd| with an SCM_RIGHTS control + // message containing on end of the fresh socket pair. + // 3. This process blocks reading from the other end of the fresh + // socketpair. + // 4. The target process receives the request, processes it and writes the + // reply to the end of the socketpair contained in the request. + // 5. This process wakes up and continues. + // + // fd: descriptor to send the request on + // reply: buffer for the reply + // reply_len: size of |reply| + // result_fd: (may be NULL) the file descriptor returned in the reply + // (if any) + // request: the bytes to send in the request + static ssize_t SendRecvMsg(int fd, + uint8_t* reply, + unsigned reply_len, + int* result_fd, + const Pickle& request); + + // Similar to SendRecvMsg(), but |recvmsg_flags| allows to control the flags + // of the recvmsg(2) call. + static ssize_t SendRecvMsgWithFlags(int fd, + uint8_t* reply, + unsigned reply_len, + int recvmsg_flags, + int* result_fd, + const Pickle& request); + private: + // Similar to RecvMsg, but allows to specify |flags| for recvmsg(2). + static ssize_t RecvMsgWithFlags(int fd, + void* msg, + size_t length, + int flags, + std::vector* fds); +}; + +#endif // BASE_POSIX_UNIX_DOMAIN_SOCKET_LINUX_H_ diff --git a/base/posix/unix_domain_socket_linux_unittest.cc b/base/posix/unix_domain_socket_linux_unittest.cc new file mode 100644 index 0000000000..1343555b33 --- /dev/null +++ b/base/posix/unix_domain_socket_linux_unittest.cc @@ -0,0 +1,81 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/file_util.h" +#include "base/pickle.h" +#include "base/posix/unix_domain_socket_linux.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace { + +TEST(UnixDomainSocketTest, SendRecvMsgAbortOnReplyFDClose) { + Thread message_thread("UnixDomainSocketTest"); + ASSERT_TRUE(message_thread.Start()); + + int fds[2]; + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds)); + file_util::ScopedFD scoped_fd0(&fds[0]); + file_util::ScopedFD scoped_fd1(&fds[1]); + + // Have the thread send a synchronous message via the socket. + Pickle request; + message_thread.message_loop()->PostTask( + FROM_HERE, + Bind(IgnoreResult(&UnixDomainSocket::SendRecvMsg), + fds[1], static_cast(NULL), 0U, static_cast(NULL), + request)); + + // Receive the message. + std::vector message_fds; + uint8_t buffer[16]; + ASSERT_EQ(static_cast(request.size()), + UnixDomainSocket::RecvMsg(fds[0], buffer, sizeof(buffer), + &message_fds)); + ASSERT_EQ(1U, message_fds.size()); + + // Close the reply FD. + ASSERT_EQ(0, HANDLE_EINTR(close(message_fds.front()))); + + // Check that the thread didn't get blocked. + WaitableEvent event(false, false); + message_thread.message_loop()->PostTask( + FROM_HERE, + Bind(&WaitableEvent::Signal, Unretained(&event))); + ASSERT_TRUE(event.TimedWait(TimeDelta::FromMilliseconds(5000))); +} + +TEST(UnixDomainSocketTest, SendRecvMsgAvoidsSIGPIPE) { + // Make sure SIGPIPE isn't being ignored. + struct sigaction act = {}, oldact; + act.sa_handler = SIG_DFL; + ASSERT_EQ(0, sigaction(SIGPIPE, &act, &oldact)); + int fds[2]; + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds)); + file_util::ScopedFD scoped_fd1(&fds[1]); + ASSERT_EQ(0, HANDLE_EINTR(close(fds[0]))); + + // Have the thread send a synchronous message via the socket. Unless the + // message is sent with MSG_NOSIGNAL, this shall result in SIGPIPE. + Pickle request; + ASSERT_EQ(-1, + UnixDomainSocket::SendRecvMsg(fds[1], static_cast(NULL), + 0U, static_cast(NULL), request)); + ASSERT_EQ(EPIPE, errno); + // Restore the SIGPIPE handler. + ASSERT_EQ(0, sigaction(SIGPIPE, &oldact, NULL)); +} + +} // namespace + +} // namespace base diff --git a/base/power_monitor/power_monitor.cc b/base/power_monitor/power_monitor.cc new file mode 100644 index 0000000000..14dc4b5178 --- /dev/null +++ b/base/power_monitor/power_monitor.cc @@ -0,0 +1,61 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/power_monitor/power_monitor.h" +#include "base/power_monitor/power_monitor_source.h" + +namespace base { + +static PowerMonitor* g_power_monitor = NULL; + +PowerMonitor::PowerMonitor(scoped_ptr source) + : observers_(new ObserverListThreadSafe()), + source_(source.Pass()) { + DCHECK(!g_power_monitor); + g_power_monitor = this; +} + +PowerMonitor::~PowerMonitor() { + DCHECK_EQ(this, g_power_monitor); + g_power_monitor = NULL; +} + +// static +PowerMonitor* PowerMonitor::Get() { + return g_power_monitor; +} + +void PowerMonitor::AddObserver(PowerObserver* obs) { + observers_->AddObserver(obs); +} + +void PowerMonitor::RemoveObserver(PowerObserver* obs) { + observers_->RemoveObserver(obs); +} + +PowerMonitorSource* PowerMonitor::Source() { + return source_.get(); +} + +bool PowerMonitor::IsOnBatteryPower() { + return source_->IsOnBatteryPower(); +} + +void PowerMonitor::NotifyPowerStateChange(bool battery_in_use) { + DVLOG(1) << "PowerStateChange: " << (battery_in_use ? "On" : "Off") + << " battery"; + observers_->Notify(&PowerObserver::OnPowerStateChange, battery_in_use); +} + +void PowerMonitor::NotifySuspend() { + DVLOG(1) << "Power Suspending"; + observers_->Notify(&PowerObserver::OnSuspend); +} + +void PowerMonitor::NotifyResume() { + DVLOG(1) << "Power Resuming"; + observers_->Notify(&PowerObserver::OnResume); +} + +} // namespace base diff --git a/base/power_monitor/power_monitor.h b/base/power_monitor/power_monitor.h new file mode 100644 index 0000000000..4acb3bf122 --- /dev/null +++ b/base/power_monitor/power_monitor.h @@ -0,0 +1,55 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_POWER_MONITOR_POWER_MONITOR_H_ +#define BASE_POWER_MONITOR_POWER_MONITOR_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/observer_list_threadsafe.h" +#include "base/power_monitor/power_observer.h" + +namespace base { + +class PowerMonitorSource; + +// A class used to monitor the power state change and notify the observers about +// the change event. +class BASE_EXPORT PowerMonitor { + public: + // Takes ownership of |source|. + explicit PowerMonitor(scoped_ptr source); + ~PowerMonitor(); + + // Get the process-wide PowerMonitor (if not present, returns NULL). + static PowerMonitor* Get(); + + // Add and remove an observer. + // Can be called from any thread. + // Must not be called from within a notification callback. + void AddObserver(PowerObserver* observer); + void RemoveObserver(PowerObserver* observer); + + // Is the computer currently on battery power. + bool IsOnBatteryPower(); + + private: + friend class PowerMonitorSource; + + PowerMonitorSource* Source(); + + void NotifyPowerStateChange(bool battery_in_use); + void NotifySuspend(); + void NotifyResume(); + + scoped_refptr > observers_; + scoped_ptr source_; + + DISALLOW_COPY_AND_ASSIGN(PowerMonitor); +}; + +} // namespace base + +#endif // BASE_POWER_MONITOR_POWER_MONITOR_H_ diff --git a/base/power_monitor/power_monitor_device_source.cc b/base/power_monitor/power_monitor_device_source.cc new file mode 100644 index 0000000000..0a3997517e --- /dev/null +++ b/base/power_monitor/power_monitor_device_source.cc @@ -0,0 +1,39 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/power_monitor/power_monitor_device_source.h" + +#include "base/time/time.h" + +namespace base { + +#if defined(ENABLE_BATTERY_MONITORING) +// The amount of time (in ms) to wait before running the initial +// battery check. +static int kDelayedBatteryCheckMs = 10 * 1000; +#endif // defined(ENABLE_BATTERY_MONITORING) + +PowerMonitorDeviceSource::PowerMonitorDeviceSource() { + DCHECK(MessageLoop::current()); +#if defined(ENABLE_BATTERY_MONITORING) + delayed_battery_check_.Start(FROM_HERE, + base::TimeDelta::FromMilliseconds(kDelayedBatteryCheckMs), this, + &PowerMonitorDeviceSource::BatteryCheck); +#endif // defined(ENABLE_BATTERY_MONITORING) +#if defined(OS_MACOSX) + PlatformInit(); +#endif +} + +PowerMonitorDeviceSource::~PowerMonitorDeviceSource() { +#if defined(OS_MACOSX) + PlatformDestroy(); +#endif +} + +void PowerMonitorDeviceSource::BatteryCheck() { + ProcessPowerEvent(PowerMonitorSource::POWER_STATE_EVENT); +} + +} // namespace base diff --git a/base/power_monitor/power_monitor_device_source.h b/base/power_monitor/power_monitor_device_source.h new file mode 100644 index 0000000000..993956031c --- /dev/null +++ b/base/power_monitor/power_monitor_device_source.h @@ -0,0 +1,110 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_POWER_MONITOR_POWER_MONITOR_DEVICE_SOURCE_H_ +#define BASE_POWER_MONITOR_POWER_MONITOR_DEVICE_SOURCE_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/observer_list_threadsafe.h" +#include "base/power_monitor/power_monitor_source.h" +#include "base/power_monitor/power_observer.h" + +#if defined(OS_WIN) +#include + +// Windows HiRes timers drain the battery faster so we need to know the battery +// status. This isn't true for other platforms. +#define ENABLE_BATTERY_MONITORING 1 +#else +#undef ENABLE_BATTERY_MONITORING +#endif // !OS_WIN + +#if defined(ENABLE_BATTERY_MONITORING) +#include "base/timer/timer.h" +#endif // defined(ENABLE_BATTERY_MONITORING) + +#if defined(OS_IOS) +#include +#endif // OS_IOS + +namespace base { + +// A class used to monitor the power state change and notify the observers about +// the change event. +class BASE_EXPORT PowerMonitorDeviceSource : public PowerMonitorSource { + public: + PowerMonitorDeviceSource(); + virtual ~PowerMonitorDeviceSource(); + +#if defined(OS_MACOSX) + // Allocate system resources needed by the PowerMonitor class. + // + // This function must be called before instantiating an instance of the class + // and before the Sandbox is initialized. +#if !defined(OS_IOS) + static void AllocateSystemIOPorts(); +#else + static void AllocateSystemIOPorts() {} +#endif // OS_IOS +#endif // OS_MACOSX + + private: +#if defined(OS_WIN) + // Represents a message-only window for power message handling on Windows. + // Only allow PowerMonitor to create it. + class PowerMessageWindow { + public: + PowerMessageWindow(); + ~PowerMessageWindow(); + + private: + void ProcessWmPowerBroadcastMessage(int event_id); + LRESULT CALLBACK WndProc(HWND hwnd, UINT message, + WPARAM wparam, LPARAM lparam); + static LRESULT CALLBACK WndProcThunk(HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam); + // Instance of the module containing the window procedure. + HMODULE instance_; + // A hidden message-only window. + HWND message_hwnd_; + }; +#endif // OS_WIN + +#if defined(OS_MACOSX) + void PlatformInit(); + void PlatformDestroy(); +#endif + + // Platform-specific method to check whether the system is currently + // running on battery power. Returns true if running on batteries, + // false otherwise. + virtual bool IsOnBatteryPowerImpl() OVERRIDE; + + // Checks the battery status and notifies observers if the battery + // status has changed. + void BatteryCheck(); + +#if defined(OS_IOS) + // Holds pointers to system event notification observers. + std::vector notification_observers_; +#endif + +#if defined(ENABLE_BATTERY_MONITORING) + base::OneShotTimer delayed_battery_check_; +#endif + +#if defined(OS_WIN) + PowerMessageWindow power_message_window_; +#endif + + DISALLOW_COPY_AND_ASSIGN(PowerMonitorDeviceSource); +}; + +} // namespace base + +#endif // BASE_POWER_MONITOR_POWER_MONITOR_DEVICE_SOURCE_H_ diff --git a/base/power_monitor/power_monitor_device_source_android.cc b/base/power_monitor/power_monitor_device_source_android.cc new file mode 100644 index 0000000000..4d9eb52780 --- /dev/null +++ b/base/power_monitor/power_monitor_device_source_android.cc @@ -0,0 +1,45 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/power_monitor/power_monitor_device_source_android.h" + +#include "base/power_monitor/power_monitor.h" +#include "base/power_monitor/power_monitor_device_source.h" +#include "base/power_monitor/power_monitor_source.h" +#include "jni/PowerMonitor_jni.h" + +namespace base { + +// A helper function which is a friend of PowerMonitorSource. +void ProcessPowerEventHelper(PowerMonitorSource::PowerEvent event) { + PowerMonitorSource::ProcessPowerEvent(event); +} + +namespace android { + +// Native implementation of PowerMonitor.java. +void OnBatteryChargingChanged(JNIEnv* env, jclass clazz) { + ProcessPowerEventHelper(PowerMonitorSource::POWER_STATE_EVENT); +} + +void OnMainActivityResumed(JNIEnv* env, jclass clazz) { + ProcessPowerEventHelper(PowerMonitorSource::RESUME_EVENT); +} + +void OnMainActivitySuspended(JNIEnv* env, jclass clazz) { + ProcessPowerEventHelper(PowerMonitorSource::SUSPEND_EVENT); +} + +} // namespace android + +bool PowerMonitorDeviceSource::IsOnBatteryPowerImpl() { + JNIEnv* env = base::android::AttachCurrentThread(); + return base::android::Java_PowerMonitor_isBatteryPower(env); +} + +bool RegisterPowerMonitor(JNIEnv* env) { + return base::android::RegisterNativesImpl(env); +} + +} // namespace base diff --git a/base/power_monitor/power_monitor_device_source_android.h b/base/power_monitor/power_monitor_device_source_android.h new file mode 100644 index 0000000000..024f95ab00 --- /dev/null +++ b/base/power_monitor/power_monitor_device_source_android.h @@ -0,0 +1,17 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_POWER_MONITOR_POWER_MONITOR_DEVICE_SOURCE_ANDROID_H_ +#define BASE_POWER_MONITOR_POWER_MONITOR_DEVICE_SOURCE_ANDROID_H_ + +#include + +namespace base { + +// Registers the JNI bindings for PowerMonitorDeviceSource. +bool RegisterPowerMonitor(JNIEnv* env); + +} // namespace base + +#endif // BASE_POWER_MONITOR_POWER_MONITOR_DEVICE_SOURCE_ANDROID_H_ diff --git a/base/power_monitor/power_monitor_device_source_ios.mm b/base/power_monitor/power_monitor_device_source_ios.mm new file mode 100644 index 0000000000..dc12f1c295 --- /dev/null +++ b/base/power_monitor/power_monitor_device_source_ios.mm @@ -0,0 +1,40 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/power_monitor/power_monitor_device_source.h" + +#import + +namespace base { + +void PowerMonitorDeviceSource::PlatformInit() { + NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; + id foreground = + [nc addObserverForName:UIApplicationWillEnterForegroundNotification + object:nil + queue:nil + usingBlock:^(NSNotification* notification) { + ProcessPowerEvent(RESUME_EVENT); + }]; + id background = + [nc addObserverForName:UIApplicationDidEnterBackgroundNotification + object:nil + queue:nil + usingBlock:^(NSNotification* notification) { + ProcessPowerEvent(SUSPEND_EVENT); + }]; + notification_observers_.push_back(foreground); + notification_observers_.push_back(background); +} + +void PowerMonitorDeviceSource::PlatformDestroy() { + NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; + for (std::vector::iterator it = notification_observers_.begin(); + it != notification_observers_.end(); ++it) { + [nc removeObserver:*it]; + } + notification_observers_.clear(); +} + +} // namespace base diff --git a/base/power_monitor/power_monitor_device_source_mac.mm b/base/power_monitor/power_monitor_device_source_mac.mm new file mode 100644 index 0000000000..61e439630e --- /dev/null +++ b/base/power_monitor/power_monitor_device_source_mac.mm @@ -0,0 +1,107 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Implementation based on sample code from +// http://developer.apple.com/library/mac/#qa/qa1340/_index.html. + +#include "base/power_monitor/power_monitor_device_source.h" + +#include "base/power_monitor/power_monitor.h" +#include "base/power_monitor/power_monitor_source.h" + +#include +#include + +namespace base { + +void ProcessPowerEventHelper(PowerMonitorSource::PowerEvent event) { + PowerMonitorSource::ProcessPowerEvent(event); +} + +namespace { + +io_connect_t g_system_power_io_port = 0; +IONotificationPortRef g_notification_port_ref = 0; +io_object_t g_notifier_object = 0; + +void SystemPowerEventCallback(void*, + io_service_t service, + natural_t message_type, + void* message_argument) { + switch (message_type) { + // If this message is not handled the system may delay sleep for 30 seconds. + case kIOMessageCanSystemSleep: + IOAllowPowerChange(g_system_power_io_port, + reinterpret_cast(message_argument)); + break; + case kIOMessageSystemWillSleep: + ProcessPowerEventHelper(base::PowerMonitorSource::SUSPEND_EVENT); + IOAllowPowerChange(g_system_power_io_port, + reinterpret_cast(message_argument)); + break; + + case kIOMessageSystemWillPowerOn: + ProcessPowerEventHelper(PowerMonitorSource::RESUME_EVENT); + break; + } +} + +} // namespace + +// The reason we can't include this code in the constructor is because +// PlatformInit() requires an active runloop and the IO port needs to be +// allocated at sandbox initialization time, before there's a runloop. +// See crbug.com/83783 . + +// static +void PowerMonitorDeviceSource::AllocateSystemIOPorts() { + DCHECK_EQ(g_system_power_io_port, 0u); + + // Notification port allocated by IORegisterForSystemPower. + g_system_power_io_port = IORegisterForSystemPower( + NULL, &g_notification_port_ref, SystemPowerEventCallback, + &g_notifier_object); + + DCHECK_NE(g_system_power_io_port, 0u); +} + +void PowerMonitorDeviceSource::PlatformInit() { + // Need to call AllocateSystemIOPorts() before creating a PowerMonitor + // object. + DCHECK_NE(g_system_power_io_port, 0u); + if (g_system_power_io_port == 0) + return; + + // Add the notification port to the application runloop + CFRunLoopAddSource( + CFRunLoopGetCurrent(), + IONotificationPortGetRunLoopSource(g_notification_port_ref), + kCFRunLoopCommonModes); +} + +void PowerMonitorDeviceSource::PlatformDestroy() { + DCHECK_NE(g_system_power_io_port, 0u); + if (g_system_power_io_port == 0) + return; + + // Remove the sleep notification port from the application runloop + CFRunLoopRemoveSource( + CFRunLoopGetCurrent(), + IONotificationPortGetRunLoopSource(g_notification_port_ref), + kCFRunLoopCommonModes); + + // Deregister for system sleep notifications + IODeregisterForSystemPower(&g_notifier_object); + + // IORegisterForSystemPower implicitly opens the Root Power Domain IOService, + // so we close it here. + IOServiceClose(g_system_power_io_port); + + g_system_power_io_port = 0; + + // Destroy the notification port allocated by IORegisterForSystemPower. + IONotificationPortDestroy(g_notification_port_ref); +} + +} // namespace base diff --git a/base/power_monitor/power_monitor_device_source_posix.cc b/base/power_monitor/power_monitor_device_source_posix.cc new file mode 100644 index 0000000000..f24e5b23f0 --- /dev/null +++ b/base/power_monitor/power_monitor_device_source_posix.cc @@ -0,0 +1,14 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/power_monitor/power_monitor_device_source.h" + +namespace base { + +bool PowerMonitorDeviceSource::IsOnBatteryPowerImpl() { + NOTIMPLEMENTED(); + return false; +} + +} // namespace base diff --git a/base/power_monitor/power_monitor_device_source_win.cc b/base/power_monitor/power_monitor_device_source_win.cc new file mode 100644 index 0000000000..6f4c1319b3 --- /dev/null +++ b/base/power_monitor/power_monitor_device_source_win.cc @@ -0,0 +1,130 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/power_monitor/power_monitor.h" +#include "base/power_monitor/power_monitor_device_source.h" +#include "base/power_monitor/power_monitor_source.h" +#include "base/win/wrapped_window_proc.h" + +namespace base { + +void ProcessPowerEventHelper(PowerMonitorSource::PowerEvent event) { + PowerMonitorSource::ProcessPowerEvent(event); +} + +namespace { + +const wchar_t kWindowClassName[] = L"Base_PowerMessageWindow"; + +} // namespace + +// Function to query the system to see if it is currently running on +// battery power. Returns true if running on battery. +bool PowerMonitorDeviceSource::IsOnBatteryPowerImpl() { + SYSTEM_POWER_STATUS status; + if (!GetSystemPowerStatus(&status)) { + DLOG_GETLASTERROR(ERROR) << "GetSystemPowerStatus failed"; + return false; + } + return (status.ACLineStatus == 0); +} + +PowerMonitorDeviceSource::PowerMessageWindow::PowerMessageWindow() + : instance_(NULL), message_hwnd_(NULL) { + if (MessageLoop::current()->type() != MessageLoop::TYPE_UI) { + // Creating this window in (e.g.) a renderer inhibits shutdown on Windows. + // See http://crbug.com/230122. TODO(vandebo): http://crbug.com/236031 + DLOG(ERROR) + << "Cannot create windows on non-UI thread, power monitor disabled!"; + return; + } + WNDCLASSEX window_class; + base::win::InitializeWindowClass( + kWindowClassName, + &base::win::WrappedWindowProc< + PowerMonitorDeviceSource::PowerMessageWindow::WndProcThunk>, + 0, 0, 0, NULL, NULL, NULL, NULL, NULL, + &window_class); + instance_ = window_class.hInstance; + ATOM clazz = RegisterClassEx(&window_class); + DCHECK(clazz); + + message_hwnd_ = CreateWindowEx(WS_EX_NOACTIVATE, kWindowClassName, + NULL, WS_POPUP, 0, 0, 0, 0, NULL, NULL, instance_, NULL); + SetWindowLongPtr(message_hwnd_, GWLP_USERDATA, + reinterpret_cast(this)); +} + +PowerMonitorDeviceSource::PowerMessageWindow::~PowerMessageWindow() { + if (message_hwnd_) { + DestroyWindow(message_hwnd_); + UnregisterClass(kWindowClassName, instance_); + } +} + +void +PowerMonitorDeviceSource::PowerMessageWindow::ProcessWmPowerBroadcastMessage( + int event_id) { + PowerMonitorSource::PowerEvent power_event; + switch (event_id) { + case PBT_APMPOWERSTATUSCHANGE: // The power status changed. + power_event = PowerMonitorSource::POWER_STATE_EVENT; + break; + case PBT_APMRESUMEAUTOMATIC: // Resume from suspend. + //case PBT_APMRESUMESUSPEND: // User-initiated resume from suspend. + // We don't notify for this latter event + // because if it occurs it is always sent as a + // second event after PBT_APMRESUMEAUTOMATIC. + power_event = PowerMonitorSource::RESUME_EVENT; + break; + case PBT_APMSUSPEND: // System has been suspended. + power_event = PowerMonitorSource::SUSPEND_EVENT; + break; + default: + return; + + // Other Power Events: + // PBT_APMBATTERYLOW - removed in Vista. + // PBT_APMOEMEVENT - removed in Vista. + // PBT_APMQUERYSUSPEND - removed in Vista. + // PBT_APMQUERYSUSPENDFAILED - removed in Vista. + // PBT_APMRESUMECRITICAL - removed in Vista. + // PBT_POWERSETTINGCHANGE - user changed the power settings. + } + + ProcessPowerEventHelper(power_event); +} + +LRESULT CALLBACK PowerMonitorDeviceSource::PowerMessageWindow::WndProc( + HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam) { + switch (message) { + case WM_POWERBROADCAST: { + DWORD power_event = static_cast(message); + ProcessWmPowerBroadcastMessage(power_event); + return TRUE; + } + default: + break; + } + return ::DefWindowProc(hwnd, message, wparam, lparam); +} + +// static +LRESULT CALLBACK PowerMonitorDeviceSource::PowerMessageWindow::WndProcThunk( + HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam) { + PowerMonitorDeviceSource::PowerMessageWindow* message_hwnd = + reinterpret_cast( + GetWindowLongPtr(hwnd, GWLP_USERDATA)); + if (message_hwnd) + return message_hwnd->WndProc(hwnd, message, wparam, lparam); + return ::DefWindowProc(hwnd, message, wparam, lparam); +} + +} // namespace base diff --git a/base/power_monitor/power_monitor_source.cc b/base/power_monitor/power_monitor_source.cc new file mode 100644 index 0000000000..6868cb19e3 --- /dev/null +++ b/base/power_monitor/power_monitor_source.cc @@ -0,0 +1,66 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/power_monitor/power_monitor_source.h" + +#include "base/power_monitor/power_monitor.h" + +namespace base { + +PowerMonitorSource::PowerMonitorSource() + : on_battery_power_(false), + suspended_(false) { +} + +PowerMonitorSource::~PowerMonitorSource() { +} + +bool PowerMonitorSource::IsOnBatteryPower() { + AutoLock auto_lock(battery_lock_); + return on_battery_power_; +} + +void PowerMonitorSource::ProcessPowerEvent(PowerEvent event_id) { + PowerMonitor* monitor = PowerMonitor::Get(); + if (!monitor) + return; + + PowerMonitorSource* source = monitor->Source(); + + // Suppress duplicate notifications. Some platforms may + // send multiple notifications of the same event. + switch (event_id) { + case POWER_STATE_EVENT: + { + bool new_on_battery_power = source->IsOnBatteryPowerImpl(); + bool changed = false; + + { + AutoLock auto_lock(source->battery_lock_); + if (source->on_battery_power_ != new_on_battery_power) { + changed = true; + source->on_battery_power_ = new_on_battery_power; + } + } + + if (changed) + monitor->NotifyPowerStateChange(new_on_battery_power); + } + break; + case RESUME_EVENT: + if (source->suspended_) { + source->suspended_ = false; + monitor->NotifyResume(); + } + break; + case SUSPEND_EVENT: + if (!source->suspended_) { + source->suspended_ = true; + monitor->NotifySuspend(); + } + break; + } +} + +} // namespace base diff --git a/base/power_monitor/power_monitor_source.h b/base/power_monitor/power_monitor_source.h new file mode 100644 index 0000000000..b8f41850ef --- /dev/null +++ b/base/power_monitor/power_monitor_source.h @@ -0,0 +1,65 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_POWER_MONITOR_POWER_MONITOR_SOURCE_H_ +#define BASE_POWER_MONITOR_POWER_MONITOR_SOURCE_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/observer_list_threadsafe.h" +#include "base/synchronization/lock.h" + +namespace base { + +class PowerMonitor; + +// Communicates power state changes to the power monitor. +class BASE_EXPORT PowerMonitorSource { + public: + PowerMonitorSource(); + virtual ~PowerMonitorSource(); + + // Normalized list of power events. + enum PowerEvent { + POWER_STATE_EVENT, // The Power status of the system has changed. + SUSPEND_EVENT, // The system is being suspended. + RESUME_EVENT // The system is being resumed. + }; + + // Is the computer currently on battery power. Can be called on any thread. + bool IsOnBatteryPower(); + + protected: + friend class PowerMonitorTest; + + // Friend function that is allowed to access the protected ProcessPowerEvent. + friend void ProcessPowerEventHelper(PowerEvent); + + // Get the process-wide PowerMonitorSource (if not present, returns NULL). + static PowerMonitorSource* Get(); + + // ProcessPowerEvent should only be called from a single thread, most likely + // the UI thread or, in child processes, the IO thread. + static void ProcessPowerEvent(PowerEvent event_id); + + // Platform-specific method to check whether the system is currently + // running on battery power. Returns true if running on batteries, + // false otherwise. + virtual bool IsOnBatteryPowerImpl() = 0; + + private: + bool on_battery_power_; + bool suspended_; + + // This lock guards access to on_battery_power_, to ensure that + // IsOnBatteryPower can be called from any thread. + Lock battery_lock_; + + DISALLOW_COPY_AND_ASSIGN(PowerMonitorSource); +}; + +} // namespace base + +#endif // BASE_POWER_MONITOR_POWER_MONITOR_SOURCE_H_ diff --git a/base/power_monitor/power_monitor_unittest.cc b/base/power_monitor/power_monitor_unittest.cc new file mode 100644 index 0000000000..5f7b531206 --- /dev/null +++ b/base/power_monitor/power_monitor_unittest.cc @@ -0,0 +1,80 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/power_monitor/power_monitor.h" +#include "base/test/power_monitor_test_base.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +class PowerMonitorTest : public testing::Test { + protected: + PowerMonitorTest() { + power_monitor_source_ = new PowerMonitorTestSource(); + power_monitor_.reset(new PowerMonitor( + scoped_ptr(power_monitor_source_))); + } + virtual ~PowerMonitorTest() {}; + + PowerMonitorTestSource* source() { return power_monitor_source_; } + PowerMonitor* monitor() { return power_monitor_.get(); } + + private: + PowerMonitorTestSource* power_monitor_source_; + scoped_ptr power_monitor_; + + DISALLOW_COPY_AND_ASSIGN(PowerMonitorTest); +}; + +// PowerMonitorSource is tightly coupled with the PowerMonitor, so this test +// Will cover both classes +TEST_F(PowerMonitorTest, PowerNotifications) { + const int kObservers = 5; + + PowerMonitorTestObserver observers[kObservers]; + for (int index = 0; index < kObservers; ++index) + monitor()->AddObserver(&observers[index]); + + // Sending resume when not suspended should have no effect. + source()->GenerateResumeEvent(); + EXPECT_EQ(observers[0].resumes(), 0); + + // Pretend we suspended. + source()->GenerateSuspendEvent(); + // Ensure all observers were notified of the event + for (int index = 0; index < kObservers; ++index) + EXPECT_EQ(observers[index].suspends(), 1); + + // Send a second suspend notification. This should be suppressed. + source()->GenerateSuspendEvent(); + EXPECT_EQ(observers[0].suspends(), 1); + + // Pretend we were awakened. + source()->GenerateResumeEvent(); + EXPECT_EQ(observers[0].resumes(), 1); + + // Send a duplicate resume notification. This should be suppressed. + source()->GenerateResumeEvent(); + EXPECT_EQ(observers[0].resumes(), 1); + + // Pretend the device has gone on battery power + source()->GeneratePowerStateEvent(true); + EXPECT_EQ(observers[0].power_state_changes(), 1); + EXPECT_EQ(observers[0].last_power_state(), true); + + // Repeated indications the device is on battery power should be suppressed. + source()->GeneratePowerStateEvent(true); + EXPECT_EQ(observers[0].power_state_changes(), 1); + + // Pretend the device has gone off battery power + source()->GeneratePowerStateEvent(false); + EXPECT_EQ(observers[0].power_state_changes(), 2); + EXPECT_EQ(observers[0].last_power_state(), false); + + // Repeated indications the device is off battery power should be suppressed. + source()->GeneratePowerStateEvent(false); + EXPECT_EQ(observers[0].power_state_changes(), 2); +} + +} // namespace base diff --git a/base/power_monitor/power_observer.h b/base/power_monitor/power_observer.h new file mode 100644 index 0000000000..6be70bba9d --- /dev/null +++ b/base/power_monitor/power_observer.h @@ -0,0 +1,31 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_POWER_MONITOR_POWER_OBSERVER_H_ +#define BASE_POWER_MONITOR_POWER_OBSERVER_H_ + +#include "base/base_export.h" +#include "base/compiler_specific.h" + +namespace base { + +class BASE_EXPORT PowerObserver { + public: + // Notification of a change in power status of the computer, such + // as from switching between battery and A/C power. + virtual void OnPowerStateChange(bool on_battery_power) {}; + + // Notification that the system is suspending. + virtual void OnSuspend() {} + + // Notification that the system is resuming. + virtual void OnResume() {} + + protected: + virtual ~PowerObserver() {} +}; + +} // namespace base + +#endif // BASE_POWER_MONITOR_POWER_OBSERVER_H_ diff --git a/base/prefs/OWNERS b/base/prefs/OWNERS new file mode 100644 index 0000000000..f3708afe55 --- /dev/null +++ b/base/prefs/OWNERS @@ -0,0 +1,7 @@ +battre@chromium.org +bauerb@chromium.org +mnissler@chromium.org +pam@chromium.org + +# For refactoring changes +joi@chromium.org diff --git a/base/prefs/README b/base/prefs/README new file mode 100644 index 0000000000..52d9c43e2e --- /dev/null +++ b/base/prefs/README @@ -0,0 +1,6 @@ +Prefs is a general-purpose key-value store for application preferences. + +The Prefs code lives in base/prefs but is not part of the +'base/base.gyp:base' library because of a desire to keep its use +optional. If you use Prefs, you should add a GYP dependency on +base/base.gyp:base_prefs. diff --git a/base/prefs/base_prefs_export.h b/base/prefs/base_prefs_export.h new file mode 100644 index 0000000000..3d207db890 --- /dev/null +++ b/base/prefs/base_prefs_export.h @@ -0,0 +1,29 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_PREFS_BASE_PREFS_EXPORT_H_ +#define BASE_PREFS_BASE_PREFS_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(BASE_PREFS_IMPLEMENTATION) +#define BASE_PREFS_EXPORT __declspec(dllexport) +#else +#define BASE_PREFS_EXPORT __declspec(dllimport) +#endif // defined(BASE_PREFS_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(BASE_PREFS_IMPLEMENTATION) +#define BASE_PREFS_EXPORT __attribute__((visibility("default"))) +#else +#define BASE_PREFS_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define BASE_PREFS_EXPORT +#endif + +#endif // BASE_PREFS_BASE_PREFS_EXPORT_H_ diff --git a/base/prefs/default_pref_store.cc b/base/prefs/default_pref_store.cc new file mode 100644 index 0000000000..babb4d5362 --- /dev/null +++ b/base/prefs/default_pref_store.cc @@ -0,0 +1,53 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/prefs/default_pref_store.h" +#include "base/logging.h" + +using base::Value; + +DefaultPrefStore::DefaultPrefStore() {} + +bool DefaultPrefStore::GetValue(const std::string& key, + const Value** result) const { + return prefs_.GetValue(key, result); +} + +void DefaultPrefStore::AddObserver(PrefStore::Observer* observer) { + observers_.AddObserver(observer); +} + +void DefaultPrefStore::RemoveObserver(PrefStore::Observer* observer) { + observers_.RemoveObserver(observer); +} + +size_t DefaultPrefStore::NumberOfObservers() const { + return observers_.size(); +} + +void DefaultPrefStore::SetDefaultValue(const std::string& key, + scoped_ptr value) { + DCHECK(!GetValue(key, NULL)); + prefs_.SetValue(key, value.release()); +} + +void DefaultPrefStore::ReplaceDefaultValue(const std::string& key, + scoped_ptr value) { + const Value* old_value = NULL; + GetValue(key, &old_value); + bool notify = !old_value->Equals(value.get()); + prefs_.SetValue(key, value.release()); + if (notify) + FOR_EACH_OBSERVER(Observer, observers_, OnPrefValueChanged(key)); +} + +DefaultPrefStore::const_iterator DefaultPrefStore::begin() const { + return prefs_.begin(); +} + +DefaultPrefStore::const_iterator DefaultPrefStore::end() const { + return prefs_.end(); +} + +DefaultPrefStore::~DefaultPrefStore() {} diff --git a/base/prefs/default_pref_store.h b/base/prefs/default_pref_store.h new file mode 100644 index 0000000000..97b3960b00 --- /dev/null +++ b/base/prefs/default_pref_store.h @@ -0,0 +1,52 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_PREFS_DEFAULT_PREF_STORE_H_ +#define BASE_PREFS_DEFAULT_PREF_STORE_H_ + +#include + +#include "base/observer_list.h" +#include "base/prefs/base_prefs_export.h" +#include "base/prefs/pref_store.h" +#include "base/prefs/pref_value_map.h" +#include "base/values.h" + +// Used within a PrefRegistry to keep track of default preference values. +class BASE_PREFS_EXPORT DefaultPrefStore : public PrefStore { + public: + typedef PrefValueMap::const_iterator const_iterator; + + DefaultPrefStore(); + + // PrefStore implementation: + virtual bool GetValue(const std::string& key, + const base::Value** result) const OVERRIDE; + virtual void AddObserver(PrefStore::Observer* observer) OVERRIDE; + virtual void RemoveObserver(PrefStore::Observer* observer) OVERRIDE; + virtual size_t NumberOfObservers() const OVERRIDE; + + // Sets a |value| for |key|. Should only be called if a value has not been + // set yet; otherwise call ReplaceDefaultValue(). + void SetDefaultValue(const std::string& key, scoped_ptr value); + + // Replaces the the value for |key| with a new value. Should only be called + // if a value has alreday been set; otherwise call SetDefaultValue(). + void ReplaceDefaultValue(const std::string& key, + scoped_ptr value); + + const_iterator begin() const; + const_iterator end() const; + + private: + virtual ~DefaultPrefStore(); + + PrefValueMap prefs_; + + ObserverList observers_; + + DISALLOW_COPY_AND_ASSIGN(DefaultPrefStore); +}; + +#endif // BASE_PREFS_DEFAULT_PREF_STORE_H_ diff --git a/base/prefs/default_pref_store_unittest.cc b/base/prefs/default_pref_store_unittest.cc new file mode 100644 index 0000000000..7181989173 --- /dev/null +++ b/base/prefs/default_pref_store_unittest.cc @@ -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/prefs/default_pref_store.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::StringValue; +using base::Value; + +namespace { + +class MockPrefStoreObserver : public PrefStore::Observer { + public: + explicit MockPrefStoreObserver(DefaultPrefStore* pref_store); + virtual ~MockPrefStoreObserver(); + + int change_count() { + return change_count_; + } + + // PrefStore::Observer implementation: + virtual void OnPrefValueChanged(const std::string& key) OVERRIDE; + virtual void OnInitializationCompleted(bool succeeded) OVERRIDE {} + + private: + DefaultPrefStore* pref_store_; + + int change_count_; + + DISALLOW_COPY_AND_ASSIGN(MockPrefStoreObserver); +}; + +MockPrefStoreObserver::MockPrefStoreObserver(DefaultPrefStore* pref_store) + : pref_store_(pref_store), change_count_(0) { + pref_store_->AddObserver(this); +} + +MockPrefStoreObserver::~MockPrefStoreObserver() { + pref_store_->RemoveObserver(this); +} + +void MockPrefStoreObserver::OnPrefValueChanged(const std::string& key) { + change_count_++; +} + +} // namespace + +TEST(DefaultPrefStoreTest, NotifyPrefValueChanged) { + scoped_refptr pref_store(new DefaultPrefStore); + MockPrefStoreObserver observer(pref_store.get()); + std::string kPrefKey("pref_key"); + + // Setting a default value shouldn't send a change notification. + pref_store->SetDefaultValue(kPrefKey, + scoped_ptr(new StringValue("foo"))); + EXPECT_EQ(0, observer.change_count()); + + // Replacing the default value should send a change notification... + pref_store->ReplaceDefaultValue(kPrefKey, + scoped_ptr(new StringValue("bar"))); + EXPECT_EQ(1, observer.change_count()); + + // But only if the value actually changed. + pref_store->ReplaceDefaultValue(kPrefKey, + scoped_ptr(new StringValue("bar"))); + EXPECT_EQ(1, observer.change_count()); +} + diff --git a/base/prefs/json_pref_store.cc b/base/prefs/json_pref_store.cc new file mode 100644 index 0000000000..0b93f74394 --- /dev/null +++ b/base/prefs/json_pref_store.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 "base/prefs/json_pref_store.h" + +#include + +#include "base/bind.h" +#include "base/callback.h" +#include "base/file_util.h" +#include "base/json/json_file_value_serializer.h" +#include "base/json/json_string_value_serializer.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/sequenced_task_runner.h" +#include "base/threading/sequenced_worker_pool.h" +#include "base/values.h" + +namespace { + +// Some extensions we'll tack on to copies of the Preferences files. +const base::FilePath::CharType* kBadExtension = FILE_PATH_LITERAL("bad"); + +// Differentiates file loading between origin thread and passed +// (aka file) thread. +class FileThreadDeserializer + : public base::RefCountedThreadSafe { + public: + FileThreadDeserializer(JsonPrefStore* delegate, + base::SequencedTaskRunner* sequenced_task_runner) + : no_dir_(false), + error_(PersistentPrefStore::PREF_READ_ERROR_NONE), + delegate_(delegate), + sequenced_task_runner_(sequenced_task_runner), + origin_loop_proxy_(base::MessageLoopProxy::current()) { + } + + void Start(const base::FilePath& path) { + DCHECK(origin_loop_proxy_->BelongsToCurrentThread()); + sequenced_task_runner_->PostTask( + FROM_HERE, + base::Bind(&FileThreadDeserializer::ReadFileAndReport, + this, path)); + } + + // Deserializes JSON on the sequenced task runner. + void ReadFileAndReport(const base::FilePath& path) { + DCHECK(sequenced_task_runner_->RunsTasksOnCurrentThread()); + + value_.reset(DoReading(path, &error_, &no_dir_)); + + origin_loop_proxy_->PostTask( + FROM_HERE, + base::Bind(&FileThreadDeserializer::ReportOnOriginThread, this)); + } + + // Reports deserialization result on the origin thread. + void ReportOnOriginThread() { + DCHECK(origin_loop_proxy_->BelongsToCurrentThread()); + delegate_->OnFileRead(value_.release(), error_, no_dir_); + } + + static base::Value* DoReading(const base::FilePath& path, + PersistentPrefStore::PrefReadError* error, + bool* no_dir) { + int error_code; + std::string error_msg; + JSONFileValueSerializer serializer(path); + base::Value* value = serializer.Deserialize(&error_code, &error_msg); + HandleErrors(value, path, error_code, error_msg, error); + *no_dir = !base::PathExists(path.DirName()); + return value; + } + + static void HandleErrors(const base::Value* value, + const base::FilePath& path, + int error_code, + const std::string& error_msg, + PersistentPrefStore::PrefReadError* error); + + private: + friend class base::RefCountedThreadSafe; + ~FileThreadDeserializer() {} + + bool no_dir_; + PersistentPrefStore::PrefReadError error_; + scoped_ptr value_; + const scoped_refptr delegate_; + const scoped_refptr sequenced_task_runner_; + const scoped_refptr origin_loop_proxy_; +}; + +// static +void FileThreadDeserializer::HandleErrors( + const base::Value* value, + const base::FilePath& path, + int error_code, + const std::string& error_msg, + PersistentPrefStore::PrefReadError* error) { + *error = PersistentPrefStore::PREF_READ_ERROR_NONE; + if (!value) { + DVLOG(1) << "Error while loading JSON file: " << error_msg + << ", file: " << path.value(); + switch (error_code) { + case JSONFileValueSerializer::JSON_ACCESS_DENIED: + *error = PersistentPrefStore::PREF_READ_ERROR_ACCESS_DENIED; + break; + case JSONFileValueSerializer::JSON_CANNOT_READ_FILE: + *error = PersistentPrefStore::PREF_READ_ERROR_FILE_OTHER; + break; + case JSONFileValueSerializer::JSON_FILE_LOCKED: + *error = PersistentPrefStore::PREF_READ_ERROR_FILE_LOCKED; + break; + case JSONFileValueSerializer::JSON_NO_SUCH_FILE: + *error = PersistentPrefStore::PREF_READ_ERROR_NO_FILE; + break; + default: + *error = PersistentPrefStore::PREF_READ_ERROR_JSON_PARSE; + // JSON errors indicate file corruption of some sort. + // Since the file is corrupt, move it to the side and continue with + // empty preferences. This will result in them losing their settings. + // We keep the old file for possible support and debugging assistance + // as well as to detect if they're seeing these errors repeatedly. + // TODO(erikkay) Instead, use the last known good file. + base::FilePath bad = path.ReplaceExtension(kBadExtension); + + // If they've ever had a parse error before, put them in another bucket. + // TODO(erikkay) if we keep this error checking for very long, we may + // want to differentiate between recent and long ago errors. + if (base::PathExists(bad)) + *error = PersistentPrefStore::PREF_READ_ERROR_JSON_REPEAT; + base::Move(path, bad); + break; + } + } else if (!value->IsType(base::Value::TYPE_DICTIONARY)) { + *error = PersistentPrefStore::PREF_READ_ERROR_JSON_TYPE; + } +} + +} // namespace + +scoped_refptr JsonPrefStore::GetTaskRunnerForFile( + const base::FilePath& filename, + base::SequencedWorkerPool* worker_pool) { + std::string token("json_pref_store-"); + token.append(filename.AsUTF8Unsafe()); + return worker_pool->GetSequencedTaskRunnerWithShutdownBehavior( + worker_pool->GetNamedSequenceToken(token), + base::SequencedWorkerPool::BLOCK_SHUTDOWN); +} + +JsonPrefStore::JsonPrefStore(const base::FilePath& filename, + base::SequencedTaskRunner* sequenced_task_runner) + : path_(filename), + sequenced_task_runner_(sequenced_task_runner), + prefs_(new base::DictionaryValue()), + read_only_(false), + writer_(filename, sequenced_task_runner), + initialized_(false), + read_error_(PREF_READ_ERROR_OTHER) {} + +bool JsonPrefStore::GetValue(const std::string& key, + const base::Value** result) const { + base::Value* tmp = NULL; + if (!prefs_->Get(key, &tmp)) + return false; + + if (result) + *result = tmp; + return true; +} + +void JsonPrefStore::AddObserver(PrefStore::Observer* observer) { + observers_.AddObserver(observer); +} + +void JsonPrefStore::RemoveObserver(PrefStore::Observer* observer) { + observers_.RemoveObserver(observer); +} + +size_t JsonPrefStore::NumberOfObservers() const { + return observers_.size(); +} + +bool JsonPrefStore::IsInitializationComplete() const { + return initialized_; +} + +bool JsonPrefStore::GetMutableValue(const std::string& key, + base::Value** result) { + return prefs_->Get(key, result); +} + +void JsonPrefStore::SetValue(const std::string& key, base::Value* value) { + DCHECK(value); + scoped_ptr new_value(value); + base::Value* old_value = NULL; + prefs_->Get(key, &old_value); + if (!old_value || !value->Equals(old_value)) { + prefs_->Set(key, new_value.release()); + ReportValueChanged(key); + } +} + +void JsonPrefStore::SetValueSilently(const std::string& key, + base::Value* value) { + DCHECK(value); + scoped_ptr new_value(value); + base::Value* old_value = NULL; + prefs_->Get(key, &old_value); + if (!old_value || !value->Equals(old_value)) { + prefs_->Set(key, new_value.release()); + if (!read_only_) + writer_.ScheduleWrite(this); + } +} + +void JsonPrefStore::RemoveValue(const std::string& key) { + if (prefs_->Remove(key, NULL)) + ReportValueChanged(key); +} + +void JsonPrefStore::MarkNeedsEmptyValue(const std::string& key) { + keys_need_empty_value_.insert(key); +} + +bool JsonPrefStore::ReadOnly() const { + return read_only_; +} + +PersistentPrefStore::PrefReadError JsonPrefStore::GetReadError() const { + return read_error_; +} + +PersistentPrefStore::PrefReadError JsonPrefStore::ReadPrefs() { + if (path_.empty()) { + OnFileRead(NULL, PREF_READ_ERROR_FILE_NOT_SPECIFIED, false); + return PREF_READ_ERROR_FILE_NOT_SPECIFIED; + } + + PrefReadError error; + bool no_dir; + base::Value* value = + FileThreadDeserializer::DoReading(path_, &error, &no_dir); + OnFileRead(value, error, no_dir); + return error; +} + +void JsonPrefStore::ReadPrefsAsync(ReadErrorDelegate *error_delegate) { + initialized_ = false; + error_delegate_.reset(error_delegate); + if (path_.empty()) { + OnFileRead(NULL, PREF_READ_ERROR_FILE_NOT_SPECIFIED, false); + return; + } + + // Start async reading of the preferences file. It will delete itself + // in the end. + scoped_refptr deserializer( + new FileThreadDeserializer(this, sequenced_task_runner_.get())); + deserializer->Start(path_); +} + +void JsonPrefStore::CommitPendingWrite() { + if (writer_.HasPendingWrite() && !read_only_) + writer_.DoScheduledWrite(); +} + +void JsonPrefStore::ReportValueChanged(const std::string& key) { + FOR_EACH_OBSERVER(PrefStore::Observer, observers_, OnPrefValueChanged(key)); + if (!read_only_) + writer_.ScheduleWrite(this); +} + +void JsonPrefStore::OnFileRead(base::Value* value_owned, + PersistentPrefStore::PrefReadError error, + bool no_dir) { + scoped_ptr value(value_owned); + read_error_ = error; + + if (no_dir) { + FOR_EACH_OBSERVER(PrefStore::Observer, + observers_, + OnInitializationCompleted(false)); + return; + } + + initialized_ = true; + + switch (error) { + case PREF_READ_ERROR_ACCESS_DENIED: + case PREF_READ_ERROR_FILE_OTHER: + case PREF_READ_ERROR_FILE_LOCKED: + case PREF_READ_ERROR_JSON_TYPE: + case PREF_READ_ERROR_FILE_NOT_SPECIFIED: + read_only_ = true; + break; + case PREF_READ_ERROR_NONE: + DCHECK(value.get()); + prefs_.reset(static_cast(value.release())); + break; + case PREF_READ_ERROR_NO_FILE: + // If the file just doesn't exist, maybe this is first run. In any case + // there's no harm in writing out default prefs in this case. + break; + case PREF_READ_ERROR_JSON_PARSE: + case PREF_READ_ERROR_JSON_REPEAT: + break; + default: + NOTREACHED() << "Unknown error: " << error; + } + + if (error_delegate_.get() && error != PREF_READ_ERROR_NONE) + error_delegate_->OnError(error); + + FOR_EACH_OBSERVER(PrefStore::Observer, + observers_, + OnInitializationCompleted(true)); +} + +JsonPrefStore::~JsonPrefStore() { + CommitPendingWrite(); +} + +bool JsonPrefStore::SerializeData(std::string* output) { + // TODO(tc): Do we want to prune webkit preferences that match the default + // value? + JSONStringValueSerializer serializer(output); + serializer.set_pretty_print(true); + scoped_ptr copy( + prefs_->DeepCopyWithoutEmptyChildren()); + + // Iterates |keys_need_empty_value_| and if the key exists in |prefs_|, + // ensure its empty ListValue or DictonaryValue is preserved. + for (std::set::const_iterator + it = keys_need_empty_value_.begin(); + it != keys_need_empty_value_.end(); + ++it) { + const std::string& key = *it; + + base::Value* value = NULL; + if (!prefs_->Get(key, &value)) + continue; + + if (value->IsType(base::Value::TYPE_LIST)) { + const base::ListValue* list = NULL; + if (value->GetAsList(&list) && list->empty()) + copy->Set(key, new base::ListValue); + } else if (value->IsType(base::Value::TYPE_DICTIONARY)) { + const base::DictionaryValue* dict = NULL; + if (value->GetAsDictionary(&dict) && dict->empty()) + copy->Set(key, new base::DictionaryValue); + } + } + + return serializer.Serialize(*(copy.get())); +} diff --git a/base/prefs/json_pref_store.h b/base/prefs/json_pref_store.h new file mode 100644 index 0000000000..738b917a59 --- /dev/null +++ b/base/prefs/json_pref_store.h @@ -0,0 +1,103 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_PREFS_JSON_PREF_STORE_H_ +#define BASE_PREFS_JSON_PREF_STORE_H_ + +#include +#include + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "base/files/important_file_writer.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/observer_list.h" +#include "base/prefs/base_prefs_export.h" +#include "base/prefs/persistent_pref_store.h" + +namespace base { +class DictionaryValue; +class FilePath; +class SequencedWorkerPool; +class SequencedTaskRunner; +class Value; +} + + +// A writable PrefStore implementation that is used for user preferences. +class BASE_PREFS_EXPORT JsonPrefStore + : public PersistentPrefStore, + public base::ImportantFileWriter::DataSerializer { + public: + // Returns instance of SequencedTaskRunner which guarantees that file + // operations on the same file will be executed in sequenced order. + static scoped_refptr GetTaskRunnerForFile( + const base::FilePath& pref_filename, + base::SequencedWorkerPool* worker_pool); + + // |sequenced_task_runner| is must be a shutdown-blocking task runner, ideally + // created by GetTaskRunnerForFile() method above. + JsonPrefStore(const base::FilePath& pref_filename, + base::SequencedTaskRunner* sequenced_task_runner); + + // PrefStore overrides: + virtual bool GetValue(const std::string& key, + const base::Value** result) const OVERRIDE; + virtual void AddObserver(PrefStore::Observer* observer) OVERRIDE; + virtual void RemoveObserver(PrefStore::Observer* observer) OVERRIDE; + virtual size_t NumberOfObservers() const OVERRIDE; + virtual bool IsInitializationComplete() const OVERRIDE; + + // PersistentPrefStore overrides: + virtual bool GetMutableValue(const std::string& key, + base::Value** result) OVERRIDE; + virtual void SetValue(const std::string& key, base::Value* value) OVERRIDE; + virtual void SetValueSilently(const std::string& key, + base::Value* value) OVERRIDE; + virtual void RemoveValue(const std::string& key) OVERRIDE; + virtual void MarkNeedsEmptyValue(const std::string& key) OVERRIDE; + virtual bool ReadOnly() const OVERRIDE; + virtual PrefReadError GetReadError() const OVERRIDE; + virtual PrefReadError ReadPrefs() OVERRIDE; + virtual void ReadPrefsAsync(ReadErrorDelegate* error_delegate) OVERRIDE; + virtual void CommitPendingWrite() OVERRIDE; + virtual void ReportValueChanged(const std::string& key) OVERRIDE; + + // This method is called after JSON file has been read. Method takes + // ownership of the |value| pointer. Note, this method is used with + // asynchronous file reading, so class exposes it only for the internal needs. + // (read: do not call it manually). + void OnFileRead(base::Value* value_owned, PrefReadError error, bool no_dir); + + private: + virtual ~JsonPrefStore(); + + // ImportantFileWriter::DataSerializer overrides: + virtual bool SerializeData(std::string* output) OVERRIDE; + + base::FilePath path_; + const scoped_refptr sequenced_task_runner_; + + scoped_ptr prefs_; + + bool read_only_; + + // Helper for safely writing pref data. + base::ImportantFileWriter writer_; + + ObserverList observers_; + + scoped_ptr error_delegate_; + + bool initialized_; + PrefReadError read_error_; + + std::set keys_need_empty_value_; + + DISALLOW_COPY_AND_ASSIGN(JsonPrefStore); +}; + +#endif // BASE_PREFS_JSON_PREF_STORE_H_ diff --git a/base/prefs/json_pref_store_unittest.cc b/base/prefs/json_pref_store_unittest.cc new file mode 100644 index 0000000000..34e1b8a9ed --- /dev/null +++ b/base/prefs/json_pref_store_unittest.cc @@ -0,0 +1,287 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/prefs/json_pref_store.h" + +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/path_service.h" +#include "base/run_loop.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/sequenced_worker_pool.h" +#include "base/threading/thread.h" +#include "base/values.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace { + +const char kHomePage[] = "homepage"; + +class MockPrefStoreObserver : public PrefStore::Observer { + public: + MOCK_METHOD1(OnPrefValueChanged, void (const std::string&)); + MOCK_METHOD1(OnInitializationCompleted, void (bool)); +}; + +class MockReadErrorDelegate : public PersistentPrefStore::ReadErrorDelegate { + public: + MOCK_METHOD1(OnError, void(PersistentPrefStore::PrefReadError)); +}; + +} // namespace + +class JsonPrefStoreTest : public testing::Test { + protected: + virtual void SetUp() OVERRIDE { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + + ASSERT_TRUE(PathService::Get(base::DIR_TEST_DATA, &data_dir_)); + data_dir_ = data_dir_.AppendASCII("prefs"); + ASSERT_TRUE(PathExists(data_dir_)); + } + + // The path to temporary directory used to contain the test operations. + base::ScopedTempDir temp_dir_; + // The path to the directory where the test data is stored. + base::FilePath data_dir_; + // A message loop that we can use as the file thread message loop. + MessageLoop message_loop_; +}; + +// Test fallback behavior for a nonexistent file. +TEST_F(JsonPrefStoreTest, NonExistentFile) { + base::FilePath bogus_input_file = data_dir_.AppendASCII("read.txt"); + ASSERT_FALSE(PathExists(bogus_input_file)); + scoped_refptr pref_store = new JsonPrefStore( + bogus_input_file, message_loop_.message_loop_proxy().get()); + EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NO_FILE, + pref_store->ReadPrefs()); + EXPECT_FALSE(pref_store->ReadOnly()); +} + +// Test fallback behavior for an invalid file. +TEST_F(JsonPrefStoreTest, InvalidFile) { + base::FilePath invalid_file_original = data_dir_.AppendASCII("invalid.json"); + base::FilePath invalid_file = temp_dir_.path().AppendASCII("invalid.json"); + ASSERT_TRUE(base::CopyFile(invalid_file_original, invalid_file)); + scoped_refptr pref_store = + new JsonPrefStore(invalid_file, message_loop_.message_loop_proxy().get()); + EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_JSON_PARSE, + pref_store->ReadPrefs()); + EXPECT_FALSE(pref_store->ReadOnly()); + + // The file should have been moved aside. + EXPECT_FALSE(PathExists(invalid_file)); + base::FilePath moved_aside = temp_dir_.path().AppendASCII("invalid.bad"); + EXPECT_TRUE(PathExists(moved_aside)); + EXPECT_TRUE(TextContentsEqual(invalid_file_original, moved_aside)); +} + +// This function is used to avoid code duplication while testing synchronous and +// asynchronous version of the JsonPrefStore loading. +void RunBasicJsonPrefStoreTest(JsonPrefStore* pref_store, + const base::FilePath& output_file, + const base::FilePath& golden_output_file) { + const char kNewWindowsInTabs[] = "tabs.new_windows_in_tabs"; + const char kMaxTabs[] = "tabs.max_tabs"; + const char kLongIntPref[] = "long_int.pref"; + + std::string cnn("http://www.cnn.com"); + + const Value* actual; + EXPECT_TRUE(pref_store->GetValue(kHomePage, &actual)); + std::string string_value; + EXPECT_TRUE(actual->GetAsString(&string_value)); + EXPECT_EQ(cnn, string_value); + + const char kSomeDirectory[] = "some_directory"; + + EXPECT_TRUE(pref_store->GetValue(kSomeDirectory, &actual)); + base::FilePath::StringType path; + EXPECT_TRUE(actual->GetAsString(&path)); + EXPECT_EQ(base::FilePath::StringType(FILE_PATH_LITERAL("/usr/local/")), path); + base::FilePath some_path(FILE_PATH_LITERAL("/usr/sbin/")); + + pref_store->SetValue(kSomeDirectory, new StringValue(some_path.value())); + EXPECT_TRUE(pref_store->GetValue(kSomeDirectory, &actual)); + EXPECT_TRUE(actual->GetAsString(&path)); + EXPECT_EQ(some_path.value(), path); + + // Test reading some other data types from sub-dictionaries. + EXPECT_TRUE(pref_store->GetValue(kNewWindowsInTabs, &actual)); + bool boolean = false; + EXPECT_TRUE(actual->GetAsBoolean(&boolean)); + EXPECT_TRUE(boolean); + + pref_store->SetValue(kNewWindowsInTabs, new FundamentalValue(false)); + EXPECT_TRUE(pref_store->GetValue(kNewWindowsInTabs, &actual)); + EXPECT_TRUE(actual->GetAsBoolean(&boolean)); + EXPECT_FALSE(boolean); + + EXPECT_TRUE(pref_store->GetValue(kMaxTabs, &actual)); + int integer = 0; + EXPECT_TRUE(actual->GetAsInteger(&integer)); + EXPECT_EQ(20, integer); + pref_store->SetValue(kMaxTabs, new FundamentalValue(10)); + EXPECT_TRUE(pref_store->GetValue(kMaxTabs, &actual)); + EXPECT_TRUE(actual->GetAsInteger(&integer)); + EXPECT_EQ(10, integer); + + pref_store->SetValue(kLongIntPref, + new StringValue(base::Int64ToString(214748364842LL))); + EXPECT_TRUE(pref_store->GetValue(kLongIntPref, &actual)); + EXPECT_TRUE(actual->GetAsString(&string_value)); + int64 value; + base::StringToInt64(string_value, &value); + EXPECT_EQ(214748364842LL, value); + + // Serialize and compare to expected output. + ASSERT_TRUE(PathExists(golden_output_file)); + pref_store->CommitPendingWrite(); + RunLoop().RunUntilIdle(); + EXPECT_TRUE(TextContentsEqual(golden_output_file, output_file)); + ASSERT_TRUE(base::DeleteFile(output_file, false)); +} + +TEST_F(JsonPrefStoreTest, Basic) { + ASSERT_TRUE(base::CopyFile(data_dir_.AppendASCII("read.json"), + temp_dir_.path().AppendASCII("write.json"))); + + // Test that the persistent value can be loaded. + base::FilePath input_file = temp_dir_.path().AppendASCII("write.json"); + ASSERT_TRUE(PathExists(input_file)); + scoped_refptr pref_store = + new JsonPrefStore(input_file, message_loop_.message_loop_proxy().get()); + ASSERT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE, pref_store->ReadPrefs()); + ASSERT_FALSE(pref_store->ReadOnly()); + + // The JSON file looks like this: + // { + // "homepage": "http://www.cnn.com", + // "some_directory": "/usr/local/", + // "tabs": { + // "new_windows_in_tabs": true, + // "max_tabs": 20 + // } + // } + + RunBasicJsonPrefStoreTest( + pref_store.get(), input_file, data_dir_.AppendASCII("write.golden.json")); +} + +TEST_F(JsonPrefStoreTest, BasicAsync) { + ASSERT_TRUE(base::CopyFile(data_dir_.AppendASCII("read.json"), + temp_dir_.path().AppendASCII("write.json"))); + + // Test that the persistent value can be loaded. + base::FilePath input_file = temp_dir_.path().AppendASCII("write.json"); + ASSERT_TRUE(PathExists(input_file)); + scoped_refptr pref_store = + new JsonPrefStore(input_file, message_loop_.message_loop_proxy().get()); + + { + MockPrefStoreObserver mock_observer; + pref_store->AddObserver(&mock_observer); + + MockReadErrorDelegate* mock_error_delegate = new MockReadErrorDelegate; + pref_store->ReadPrefsAsync(mock_error_delegate); + + EXPECT_CALL(mock_observer, OnInitializationCompleted(true)).Times(1); + EXPECT_CALL(*mock_error_delegate, + OnError(PersistentPrefStore::PREF_READ_ERROR_NONE)).Times(0); + RunLoop().RunUntilIdle(); + pref_store->RemoveObserver(&mock_observer); + + ASSERT_FALSE(pref_store->ReadOnly()); + } + + // The JSON file looks like this: + // { + // "homepage": "http://www.cnn.com", + // "some_directory": "/usr/local/", + // "tabs": { + // "new_windows_in_tabs": true, + // "max_tabs": 20 + // } + // } + + RunBasicJsonPrefStoreTest( + pref_store.get(), input_file, data_dir_.AppendASCII("write.golden.json")); +} + +// Tests asynchronous reading of the file when there is no file. +TEST_F(JsonPrefStoreTest, AsyncNonExistingFile) { + base::FilePath bogus_input_file = data_dir_.AppendASCII("read.txt"); + ASSERT_FALSE(PathExists(bogus_input_file)); + scoped_refptr pref_store = new JsonPrefStore( + bogus_input_file, message_loop_.message_loop_proxy().get()); + MockPrefStoreObserver mock_observer; + pref_store->AddObserver(&mock_observer); + + MockReadErrorDelegate *mock_error_delegate = new MockReadErrorDelegate; + pref_store->ReadPrefsAsync(mock_error_delegate); + + EXPECT_CALL(mock_observer, OnInitializationCompleted(true)).Times(1); + EXPECT_CALL(*mock_error_delegate, + OnError(PersistentPrefStore::PREF_READ_ERROR_NO_FILE)).Times(1); + RunLoop().RunUntilIdle(); + pref_store->RemoveObserver(&mock_observer); + + EXPECT_FALSE(pref_store->ReadOnly()); +} + +TEST_F(JsonPrefStoreTest, NeedsEmptyValue) { + base::FilePath pref_file = temp_dir_.path().AppendASCII("write.json"); + + ASSERT_TRUE(base::CopyFile( + data_dir_.AppendASCII("read.need_empty_value.json"), + pref_file)); + + // Test that the persistent value can be loaded. + ASSERT_TRUE(PathExists(pref_file)); + scoped_refptr pref_store = + new JsonPrefStore(pref_file, message_loop_.message_loop_proxy().get()); + ASSERT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE, pref_store->ReadPrefs()); + ASSERT_FALSE(pref_store->ReadOnly()); + + // The JSON file looks like this: + // { + // "list": [ 1 ], + // "list_needs_empty_value": [ 2 ], + // "dict": { + // "dummy": true, + // }, + // "dict_needs_empty_value": { + // "dummy": true, + // }, + // } + + // Set flag to preserve empty values for the following keys. + pref_store->MarkNeedsEmptyValue("list_needs_empty_value"); + pref_store->MarkNeedsEmptyValue("dict_needs_empty_value"); + + // Set all keys to empty values. + pref_store->SetValue("list", new base::ListValue); + pref_store->SetValue("list_needs_empty_value", new base::ListValue); + pref_store->SetValue("dict", new base::DictionaryValue); + pref_store->SetValue("dict_needs_empty_value", new base::DictionaryValue); + + // Write to file. + pref_store->CommitPendingWrite(); + RunLoop().RunUntilIdle(); + + // Compare to expected output. + base::FilePath golden_output_file = + data_dir_.AppendASCII("write.golden.need_empty_value.json"); + ASSERT_TRUE(PathExists(golden_output_file)); + EXPECT_TRUE(TextContentsEqual(golden_output_file, pref_file)); +} + +} // namespace base diff --git a/base/prefs/mock_pref_change_callback.cc b/base/prefs/mock_pref_change_callback.cc new file mode 100644 index 0000000000..96b71974eb --- /dev/null +++ b/base/prefs/mock_pref_change_callback.cc @@ -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. + +#include "base/prefs/mock_pref_change_callback.h" + +#include "base/bind.h" + +MockPrefChangeCallback::MockPrefChangeCallback(PrefService* prefs) + : prefs_(prefs) { +} + +MockPrefChangeCallback::~MockPrefChangeCallback() {} + +PrefChangeRegistrar::NamedChangeCallback MockPrefChangeCallback::GetCallback() { + return base::Bind(&MockPrefChangeCallback::OnPreferenceChanged, + base::Unretained(this)); +} + +void MockPrefChangeCallback::Expect(const std::string& pref_name, + const base::Value* value) { + EXPECT_CALL(*this, OnPreferenceChanged(pref_name)) + .With(PrefValueMatches(prefs_, pref_name, value)); +} diff --git a/base/prefs/mock_pref_change_callback.h b/base/prefs/mock_pref_change_callback.h new file mode 100644 index 0000000000..422754afd6 --- /dev/null +++ b/base/prefs/mock_pref_change_callback.h @@ -0,0 +1,52 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_PREFS_MOCK_PREF_CHANGE_CALLBACK_H_ +#define BASE_PREFS_MOCK_PREF_CHANGE_CALLBACK_H_ + +#include + +#include "base/prefs/pref_change_registrar.h" +#include "base/prefs/pref_service.h" +#include "testing/gmock/include/gmock/gmock.h" + +using testing::Pointee; +using testing::Property; +using testing::Truly; + +// Matcher that checks whether the current value of the preference named +// |pref_name| in |prefs| matches |value|. If |value| is NULL, the matcher +// checks that the value is not set. +MATCHER_P3(PrefValueMatches, prefs, pref_name, value, "") { + const PrefService::Preference* pref = + prefs->FindPreference(pref_name.c_str()); + if (!pref) + return false; + + const base::Value* actual_value = pref->GetValue(); + if (!actual_value) + return value == NULL; + if (!value) + return actual_value == NULL; + return value->Equals(actual_value); +} + +// A mock for testing preference notifications and easy setup of expectations. +class MockPrefChangeCallback { + public: + explicit MockPrefChangeCallback(PrefService* prefs); + virtual ~MockPrefChangeCallback(); + + PrefChangeRegistrar::NamedChangeCallback GetCallback(); + + MOCK_METHOD1(OnPreferenceChanged, void(const std::string&)); + + void Expect(const std::string& pref_name, + const base::Value* value); + + private: + PrefService* prefs_; +}; + +#endif // BASE_PREFS_MOCK_PREF_CHANGE_CALLBACK_H_ diff --git a/base/prefs/overlay_user_pref_store.cc b/base/prefs/overlay_user_pref_store.cc new file mode 100644 index 0000000000..628d3b4e33 --- /dev/null +++ b/base/prefs/overlay_user_pref_store.cc @@ -0,0 +1,182 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/prefs/overlay_user_pref_store.h" + +#include "base/memory/scoped_ptr.h" +#include "base/values.h" + +OverlayUserPrefStore::OverlayUserPrefStore( + PersistentPrefStore* underlay) + : underlay_(underlay) { + underlay_->AddObserver(this); +} + +bool OverlayUserPrefStore::IsSetInOverlay(const std::string& key) const { + return overlay_.GetValue(key, NULL); +} + +void OverlayUserPrefStore::AddObserver(PrefStore::Observer* observer) { + observers_.AddObserver(observer); +} + +void OverlayUserPrefStore::RemoveObserver(PrefStore::Observer* observer) { + observers_.RemoveObserver(observer); +} + +size_t OverlayUserPrefStore::NumberOfObservers() const { + return observers_.size(); +} + +bool OverlayUserPrefStore::IsInitializationComplete() const { + return underlay_->IsInitializationComplete(); +} + +bool OverlayUserPrefStore::GetValue(const std::string& key, + const base::Value** result) const { + // If the |key| shall NOT be stored in the overlay store, there must not + // be an entry. + DCHECK(ShallBeStoredInOverlay(key) || !overlay_.GetValue(key, NULL)); + + if (overlay_.GetValue(key, result)) + return true; + return underlay_->GetValue(GetUnderlayKey(key), result); +} + +bool OverlayUserPrefStore::GetMutableValue(const std::string& key, + base::Value** result) { + if (!ShallBeStoredInOverlay(key)) + return underlay_->GetMutableValue(GetUnderlayKey(key), result); + + if (overlay_.GetValue(key, result)) + return true; + + // Try to create copy of underlay if the overlay does not contain a value. + base::Value* underlay_value = NULL; + if (!underlay_->GetMutableValue(GetUnderlayKey(key), &underlay_value)) + return false; + + *result = underlay_value->DeepCopy(); + overlay_.SetValue(key, *result); + return true; +} + +void OverlayUserPrefStore::SetValue(const std::string& key, + base::Value* value) { + if (!ShallBeStoredInOverlay(key)) { + underlay_->SetValue(GetUnderlayKey(key), value); + return; + } + + if (overlay_.SetValue(key, value)) + ReportValueChanged(key); +} + +void OverlayUserPrefStore::SetValueSilently(const std::string& key, + base::Value* value) { + if (!ShallBeStoredInOverlay(key)) { + underlay_->SetValueSilently(GetUnderlayKey(key), value); + return; + } + + overlay_.SetValue(key, value); +} + +void OverlayUserPrefStore::RemoveValue(const std::string& key) { + if (!ShallBeStoredInOverlay(key)) { + underlay_->RemoveValue(GetUnderlayKey(key)); + return; + } + + if (overlay_.RemoveValue(key)) + ReportValueChanged(key); +} + +void OverlayUserPrefStore::MarkNeedsEmptyValue(const std::string& key) { + if (!ShallBeStoredInOverlay(key)) + underlay_->MarkNeedsEmptyValue(key); +} + +bool OverlayUserPrefStore::ReadOnly() const { + return false; +} + +PersistentPrefStore::PrefReadError OverlayUserPrefStore::GetReadError() const { + return PersistentPrefStore::PREF_READ_ERROR_NONE; +} + +PersistentPrefStore::PrefReadError OverlayUserPrefStore::ReadPrefs() { + // We do not read intentionally. + OnInitializationCompleted(true); + return PersistentPrefStore::PREF_READ_ERROR_NONE; +} + +void OverlayUserPrefStore::ReadPrefsAsync( + ReadErrorDelegate* error_delegate_raw) { + scoped_ptr error_delegate(error_delegate_raw); + // We do not read intentionally. + OnInitializationCompleted(true); +} + +void OverlayUserPrefStore::CommitPendingWrite() { + underlay_->CommitPendingWrite(); + // We do not write our content intentionally. +} + +void OverlayUserPrefStore::ReportValueChanged(const std::string& key) { + FOR_EACH_OBSERVER(PrefStore::Observer, observers_, OnPrefValueChanged(key)); +} + +void OverlayUserPrefStore::OnPrefValueChanged(const std::string& key) { + if (!overlay_.GetValue(GetOverlayKey(key), NULL)) + ReportValueChanged(GetOverlayKey(key)); +} + +void OverlayUserPrefStore::OnInitializationCompleted(bool succeeded) { + FOR_EACH_OBSERVER(PrefStore::Observer, observers_, + OnInitializationCompleted(succeeded)); +} + +void OverlayUserPrefStore::RegisterOverlayPref(const std::string& key) { + RegisterOverlayPref(key, key); +} + +void OverlayUserPrefStore::RegisterOverlayPref( + const std::string& overlay_key, + const std::string& underlay_key) { + DCHECK(!overlay_key.empty()) << "Overlay key is empty"; + DCHECK(overlay_to_underlay_names_map_.find(overlay_key) == + overlay_to_underlay_names_map_.end()) << + "Overlay key already registered"; + DCHECK(!underlay_key.empty()) << "Underlay key is empty"; + DCHECK(underlay_to_overlay_names_map_.find(underlay_key) == + underlay_to_overlay_names_map_.end()) << + "Underlay key already registered"; + overlay_to_underlay_names_map_[overlay_key] = underlay_key; + underlay_to_overlay_names_map_[underlay_key] = overlay_key; +} + +OverlayUserPrefStore::~OverlayUserPrefStore() { + underlay_->RemoveObserver(this); +} + +const std::string& OverlayUserPrefStore::GetOverlayKey( + const std::string& underlay_key) const { + NamesMap::const_iterator i = + underlay_to_overlay_names_map_.find(underlay_key); + return i != underlay_to_overlay_names_map_.end() ? i->second : underlay_key; +} + +const std::string& OverlayUserPrefStore::GetUnderlayKey( + const std::string& overlay_key) const { + NamesMap::const_iterator i = + overlay_to_underlay_names_map_.find(overlay_key); + return i != overlay_to_underlay_names_map_.end() ? i->second : overlay_key; +} + +bool OverlayUserPrefStore::ShallBeStoredInOverlay( + const std::string& key) const { + return overlay_to_underlay_names_map_.find(key) != + overlay_to_underlay_names_map_.end(); +} diff --git a/base/prefs/overlay_user_pref_store.h b/base/prefs/overlay_user_pref_store.h new file mode 100644 index 0000000000..120d405246 --- /dev/null +++ b/base/prefs/overlay_user_pref_store.h @@ -0,0 +1,85 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_PREFS_OVERLAY_USER_PREF_STORE_H_ +#define BASE_PREFS_OVERLAY_USER_PREF_STORE_H_ + +#include +#include + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/observer_list.h" +#include "base/prefs/base_prefs_export.h" +#include "base/prefs/persistent_pref_store.h" +#include "base/prefs/pref_value_map.h" + +// PersistentPrefStore that directs all write operations into an in-memory +// PrefValueMap. Read operations are first answered by the PrefValueMap. +// If the PrefValueMap does not contain a value for the requested key, +// the look-up is passed on to an underlying PersistentPrefStore |underlay_|. +class BASE_PREFS_EXPORT OverlayUserPrefStore : public PersistentPrefStore, + public PrefStore::Observer { + public: + explicit OverlayUserPrefStore(PersistentPrefStore* underlay); + + // Returns true if a value has been set for the |key| in this + // OverlayUserPrefStore, i.e. if it potentially overrides a value + // from the |underlay_|. + virtual bool IsSetInOverlay(const std::string& key) const; + + // Methods of PrefStore. + virtual void AddObserver(PrefStore::Observer* observer) OVERRIDE; + virtual void RemoveObserver(PrefStore::Observer* observer) OVERRIDE; + virtual size_t NumberOfObservers() const OVERRIDE; + virtual bool IsInitializationComplete() const OVERRIDE; + virtual bool GetValue(const std::string& key, + const base::Value** result) const OVERRIDE; + + // Methods of PersistentPrefStore. + virtual bool GetMutableValue(const std::string& key, + base::Value** result) OVERRIDE; + virtual void SetValue(const std::string& key, base::Value* value) OVERRIDE; + virtual void SetValueSilently(const std::string& key, + base::Value* value) OVERRIDE; + virtual void RemoveValue(const std::string& key) OVERRIDE; + virtual void MarkNeedsEmptyValue(const std::string& key) OVERRIDE; + virtual bool ReadOnly() const OVERRIDE; + virtual PrefReadError GetReadError() const OVERRIDE; + virtual PrefReadError ReadPrefs() OVERRIDE; + virtual void ReadPrefsAsync(ReadErrorDelegate* delegate) OVERRIDE; + virtual void CommitPendingWrite() OVERRIDE; + virtual void ReportValueChanged(const std::string& key) OVERRIDE; + + // Methods of PrefStore::Observer. + virtual void OnPrefValueChanged(const std::string& key) OVERRIDE; + virtual void OnInitializationCompleted(bool succeeded) OVERRIDE; + + void RegisterOverlayPref(const std::string& key); + void RegisterOverlayPref(const std::string& overlay_key, + const std::string& underlay_key); + + protected: + virtual ~OverlayUserPrefStore(); + + private: + typedef std::map NamesMap; + + const std::string& GetOverlayKey(const std::string& underlay_key) const; + const std::string& GetUnderlayKey(const std::string& overlay_key) const; + + // Returns true if |key| corresponds to a preference that shall be stored in + // an in-memory PrefStore that is not persisted to disk. + bool ShallBeStoredInOverlay(const std::string& key) const; + + ObserverList observers_; + PrefValueMap overlay_; + scoped_refptr underlay_; + NamesMap overlay_to_underlay_names_map_; + NamesMap underlay_to_overlay_names_map_; + + DISALLOW_COPY_AND_ASSIGN(OverlayUserPrefStore); +}; + +#endif // BASE_PREFS_OVERLAY_USER_PREF_STORE_H_ diff --git a/base/prefs/overlay_user_pref_store_unittest.cc b/base/prefs/overlay_user_pref_store_unittest.cc new file mode 100644 index 0000000000..c4e980bbae --- /dev/null +++ b/base/prefs/overlay_user_pref_store_unittest.cc @@ -0,0 +1,278 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/prefs/overlay_user_pref_store.h" + +#include "base/prefs/pref_store_observer_mock.h" +#include "base/prefs/testing_pref_store.h" +#include "base/values.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::Mock; +using ::testing::StrEq; + +namespace base { +namespace { + +const char kBrowserWindowPlacement[] = "browser.window_placement"; +const char kShowBookmarkBar[] = "bookmark_bar.show_on_all_tabs"; + +const char* overlay_key = kBrowserWindowPlacement; +const char* regular_key = kShowBookmarkBar; +// With the removal of the kWebKitGlobalXXX prefs, we'll no longer have real +// prefs using the overlay pref store, so make up keys here. +const char* mapped_overlay_key = "test.per_tab.javascript_enabled"; +const char* mapped_underlay_key = "test.per_profile.javascript_enabled"; + +} // namespace + +class OverlayUserPrefStoreTest : public testing::Test { + protected: + OverlayUserPrefStoreTest() + : underlay_(new TestingPrefStore()), + overlay_(new OverlayUserPrefStore(underlay_.get())) { + overlay_->RegisterOverlayPref(overlay_key); + overlay_->RegisterOverlayPref(mapped_overlay_key, mapped_underlay_key); + } + + virtual ~OverlayUserPrefStoreTest() {} + + scoped_refptr underlay_; + scoped_refptr overlay_; +}; + +TEST_F(OverlayUserPrefStoreTest, Observer) { + PrefStoreObserverMock obs; + overlay_->AddObserver(&obs); + + // Check that underlay first value is reported. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(overlay_key))).Times(1); + underlay_->SetValue(overlay_key, new FundamentalValue(42)); + Mock::VerifyAndClearExpectations(&obs); + + // Check that underlay overwriting is reported. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(overlay_key))).Times(1); + underlay_->SetValue(overlay_key, new FundamentalValue(43)); + Mock::VerifyAndClearExpectations(&obs); + + // Check that overwriting change in overlay is reported. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(overlay_key))).Times(1); + overlay_->SetValue(overlay_key, new FundamentalValue(44)); + Mock::VerifyAndClearExpectations(&obs); + + // Check that hidden underlay change is not reported. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(overlay_key))).Times(0); + underlay_->SetValue(overlay_key, new FundamentalValue(45)); + Mock::VerifyAndClearExpectations(&obs); + + // Check that overlay remove is reported. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(overlay_key))).Times(1); + overlay_->RemoveValue(overlay_key); + Mock::VerifyAndClearExpectations(&obs); + + // Check that underlay remove is reported. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(overlay_key))).Times(1); + underlay_->RemoveValue(overlay_key); + Mock::VerifyAndClearExpectations(&obs); + + // Check respecting of silence. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(overlay_key))).Times(0); + overlay_->SetValueSilently(overlay_key, new FundamentalValue(46)); + Mock::VerifyAndClearExpectations(&obs); + + overlay_->RemoveObserver(&obs); + + // Check successful unsubscription. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(overlay_key))).Times(0); + underlay_->SetValue(overlay_key, new FundamentalValue(47)); + overlay_->SetValue(overlay_key, new FundamentalValue(48)); + Mock::VerifyAndClearExpectations(&obs); +} + +TEST_F(OverlayUserPrefStoreTest, GetAndSet) { + const Value* value = NULL; + EXPECT_FALSE(overlay_->GetValue(overlay_key, &value)); + EXPECT_FALSE(underlay_->GetValue(overlay_key, &value)); + + underlay_->SetValue(overlay_key, new FundamentalValue(42)); + + // Value shines through: + EXPECT_TRUE(overlay_->GetValue(overlay_key, &value)); + EXPECT_TRUE(base::FundamentalValue(42).Equals(value)); + + EXPECT_TRUE(underlay_->GetValue(overlay_key, &value)); + EXPECT_TRUE(base::FundamentalValue(42).Equals(value)); + + overlay_->SetValue(overlay_key, new FundamentalValue(43)); + + EXPECT_TRUE(overlay_->GetValue(overlay_key, &value)); + EXPECT_TRUE(base::FundamentalValue(43).Equals(value)); + + EXPECT_TRUE(underlay_->GetValue(overlay_key, &value)); + EXPECT_TRUE(base::FundamentalValue(42).Equals(value)); + + overlay_->RemoveValue(overlay_key); + + // Value shines through: + EXPECT_TRUE(overlay_->GetValue(overlay_key, &value)); + EXPECT_TRUE(base::FundamentalValue(42).Equals(value)); + + EXPECT_TRUE(underlay_->GetValue(overlay_key, &value)); + EXPECT_TRUE(base::FundamentalValue(42).Equals(value)); +} + +// Check that GetMutableValue does not return the dictionary of the underlay. +TEST_F(OverlayUserPrefStoreTest, ModifyDictionaries) { + underlay_->SetValue(overlay_key, new DictionaryValue); + + Value* modify = NULL; + EXPECT_TRUE(overlay_->GetMutableValue(overlay_key, &modify)); + ASSERT_TRUE(modify); + ASSERT_TRUE(modify->IsType(Value::TYPE_DICTIONARY)); + static_cast(modify)->SetInteger(overlay_key, 42); + + Value* original_in_underlay = NULL; + EXPECT_TRUE(underlay_->GetMutableValue(overlay_key, &original_in_underlay)); + ASSERT_TRUE(original_in_underlay); + ASSERT_TRUE(original_in_underlay->IsType(Value::TYPE_DICTIONARY)); + EXPECT_TRUE(static_cast(original_in_underlay)->empty()); + + Value* modified = NULL; + EXPECT_TRUE(overlay_->GetMutableValue(overlay_key, &modified)); + ASSERT_TRUE(modified); + ASSERT_TRUE(modified->IsType(Value::TYPE_DICTIONARY)); + EXPECT_TRUE(Value::Equals(modify, static_cast(modified))); +} + +// Here we consider a global preference that is not overlayed. +TEST_F(OverlayUserPrefStoreTest, GlobalPref) { + PrefStoreObserverMock obs; + overlay_->AddObserver(&obs); + + const Value* value = NULL; + + // Check that underlay first value is reported. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(regular_key))).Times(1); + underlay_->SetValue(regular_key, new FundamentalValue(42)); + Mock::VerifyAndClearExpectations(&obs); + + // Check that underlay overwriting is reported. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(regular_key))).Times(1); + underlay_->SetValue(regular_key, new FundamentalValue(43)); + Mock::VerifyAndClearExpectations(&obs); + + // Check that we get this value from the overlay + EXPECT_TRUE(overlay_->GetValue(regular_key, &value)); + EXPECT_TRUE(base::FundamentalValue(43).Equals(value)); + + // Check that overwriting change in overlay is reported. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(regular_key))).Times(1); + overlay_->SetValue(regular_key, new FundamentalValue(44)); + Mock::VerifyAndClearExpectations(&obs); + + // Check that we get this value from the overlay and the underlay. + EXPECT_TRUE(overlay_->GetValue(regular_key, &value)); + EXPECT_TRUE(base::FundamentalValue(44).Equals(value)); + EXPECT_TRUE(underlay_->GetValue(regular_key, &value)); + EXPECT_TRUE(base::FundamentalValue(44).Equals(value)); + + // Check that overlay remove is reported. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(regular_key))).Times(1); + overlay_->RemoveValue(regular_key); + Mock::VerifyAndClearExpectations(&obs); + + // Check that value was removed from overlay and underlay + EXPECT_FALSE(overlay_->GetValue(regular_key, &value)); + EXPECT_FALSE(underlay_->GetValue(regular_key, &value)); + + // Check respecting of silence. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(regular_key))).Times(0); + overlay_->SetValueSilently(regular_key, new FundamentalValue(46)); + Mock::VerifyAndClearExpectations(&obs); + + overlay_->RemoveObserver(&obs); + + // Check successful unsubscription. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(regular_key))).Times(0); + underlay_->SetValue(regular_key, new FundamentalValue(47)); + overlay_->SetValue(regular_key, new FundamentalValue(48)); + Mock::VerifyAndClearExpectations(&obs); +} + +// Check that names mapping works correctly. +TEST_F(OverlayUserPrefStoreTest, NamesMapping) { + PrefStoreObserverMock obs; + overlay_->AddObserver(&obs); + + const Value* value = NULL; + + // Check that if there is no override in the overlay, changing underlay value + // is reported as changing an overlay value. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(mapped_overlay_key))).Times(1); + underlay_->SetValue(mapped_underlay_key, new FundamentalValue(42)); + Mock::VerifyAndClearExpectations(&obs); + + // Check that underlay overwriting is reported. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(mapped_overlay_key))).Times(1); + underlay_->SetValue(mapped_underlay_key, new FundamentalValue(43)); + Mock::VerifyAndClearExpectations(&obs); + + // Check that we get this value from the overlay with both keys + EXPECT_TRUE(overlay_->GetValue(mapped_overlay_key, &value)); + EXPECT_TRUE(base::FundamentalValue(43).Equals(value)); + // In this case, overlay reads directly from the underlay. + EXPECT_TRUE(overlay_->GetValue(mapped_underlay_key, &value)); + EXPECT_TRUE(base::FundamentalValue(43).Equals(value)); + + // Check that overwriting change in overlay is reported. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(mapped_overlay_key))).Times(1); + overlay_->SetValue(mapped_overlay_key, new FundamentalValue(44)); + Mock::VerifyAndClearExpectations(&obs); + + // Check that we get an overriden value from overlay, while reading the + // value from underlay still holds an old value. + EXPECT_TRUE(overlay_->GetValue(mapped_overlay_key, &value)); + EXPECT_TRUE(base::FundamentalValue(44).Equals(value)); + EXPECT_TRUE(overlay_->GetValue(mapped_underlay_key, &value)); + EXPECT_TRUE(base::FundamentalValue(43).Equals(value)); + EXPECT_TRUE(underlay_->GetValue(mapped_underlay_key, &value)); + EXPECT_TRUE(base::FundamentalValue(43).Equals(value)); + + // Check that hidden underlay change is not reported. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(mapped_overlay_key))).Times(0); + underlay_->SetValue(mapped_underlay_key, new FundamentalValue(45)); + Mock::VerifyAndClearExpectations(&obs); + + // Check that overlay remove is reported. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(mapped_overlay_key))).Times(1); + overlay_->RemoveValue(mapped_overlay_key); + Mock::VerifyAndClearExpectations(&obs); + + // Check that underlay remove is reported. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(mapped_overlay_key))).Times(1); + underlay_->RemoveValue(mapped_underlay_key); + Mock::VerifyAndClearExpectations(&obs); + + // Check that value was removed. + EXPECT_FALSE(overlay_->GetValue(mapped_overlay_key, &value)); + EXPECT_FALSE(overlay_->GetValue(mapped_underlay_key, &value)); + + // Check respecting of silence. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(mapped_overlay_key))).Times(0); + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(mapped_underlay_key))).Times(0); + overlay_->SetValueSilently(mapped_overlay_key, new FundamentalValue(46)); + Mock::VerifyAndClearExpectations(&obs); + + overlay_->RemoveObserver(&obs); + + // Check successful unsubscription. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(mapped_overlay_key))).Times(0); + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(mapped_underlay_key))).Times(0); + underlay_->SetValue(mapped_underlay_key, new FundamentalValue(47)); + overlay_->SetValue(mapped_overlay_key, new FundamentalValue(48)); + Mock::VerifyAndClearExpectations(&obs); +} + +} // namespace base diff --git a/base/prefs/persistent_pref_store.h b/base/prefs/persistent_pref_store.h new file mode 100644 index 0000000000..0baf02ac89 --- /dev/null +++ b/base/prefs/persistent_pref_store.h @@ -0,0 +1,95 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_PREFS_PERSISTENT_PREF_STORE_H_ +#define BASE_PREFS_PERSISTENT_PREF_STORE_H_ + +#include + +#include "base/prefs/base_prefs_export.h" +#include "base/prefs/pref_store.h" + +// This interface is complementary to the PrefStore interface, declaring +// additional functionality that adds support for setting values and persisting +// the data to some backing store. +class BASE_PREFS_EXPORT PersistentPrefStore : public PrefStore { + public: + // Unique integer code for each type of error so we can report them + // distinctly in a histogram. + // NOTE: Don't change the order here as it will change the server's meaning + // of the histogram. + enum PrefReadError { + PREF_READ_ERROR_NONE = 0, + PREF_READ_ERROR_JSON_PARSE, + PREF_READ_ERROR_JSON_TYPE, + PREF_READ_ERROR_ACCESS_DENIED, + PREF_READ_ERROR_FILE_OTHER, + PREF_READ_ERROR_FILE_LOCKED, + PREF_READ_ERROR_NO_FILE, + PREF_READ_ERROR_JSON_REPEAT, + PREF_READ_ERROR_OTHER, + PREF_READ_ERROR_FILE_NOT_SPECIFIED, + PREF_READ_ERROR_MAX_ENUM + }; + + class ReadErrorDelegate { + public: + virtual ~ReadErrorDelegate() {} + + virtual void OnError(PrefReadError error) = 0; + }; + + // Equivalent to PrefStore::GetValue but returns a mutable value. + virtual bool GetMutableValue(const std::string& key, + base::Value** result) = 0; + + // Triggers a value changed notification. This function needs to be called + // if one retrieves a list or dictionary with GetMutableValue and change its + // value. SetValue takes care of notifications itself. Note that + // ReportValueChanged will trigger notifications even if nothing has changed. + virtual void ReportValueChanged(const std::string& key) = 0; + + // Sets a |value| for |key| in the store. Assumes ownership of |value|, which + // must be non-NULL. + virtual void SetValue(const std::string& key, base::Value* value) = 0; + + // Same as SetValue, but doesn't generate notifications. This is used by + // PrefService::GetMutableUserPref() in order to put empty entries + // into the user pref store. Using SetValue is not an option since existing + // tests rely on the number of notifications generated. + virtual void SetValueSilently(const std::string& key, base::Value* value) = 0; + + // Removes the value for |key|. + virtual void RemoveValue(const std::string& key) = 0; + + // Marks that the |key| with empty ListValue/DictionaryValue needs to be + // persisted. + virtual void MarkNeedsEmptyValue(const std::string& key) = 0; + + // Whether the store is in a pseudo-read-only mode where changes are not + // actually persisted to disk. This happens in some cases when there are + // read errors during startup. + virtual bool ReadOnly() const = 0; + + // Gets the read error. Only valid if IsInitializationComplete() returns true. + virtual PrefReadError GetReadError() const = 0; + + // Reads the preferences from disk. Notifies observers via + // "PrefStore::OnInitializationCompleted" when done. + virtual PrefReadError ReadPrefs() = 0; + + // Reads the preferences from disk asynchronously. Notifies observers via + // "PrefStore::OnInitializationCompleted" when done. Also it fires + // |error_delegate| if it is not NULL and reading error has occurred. + // Owns |error_delegate|. + virtual void ReadPrefsAsync(ReadErrorDelegate* error_delegate) = 0; + + // Lands any pending writes to disk. + virtual void CommitPendingWrite() = 0; + + protected: + virtual ~PersistentPrefStore() {} +}; + +#endif // BASE_PREFS_PERSISTENT_PREF_STORE_H_ diff --git a/base/prefs/pref_change_registrar.cc b/base/prefs/pref_change_registrar.cc new file mode 100644 index 0000000000..28ac374034 --- /dev/null +++ b/base/prefs/pref_change_registrar.cc @@ -0,0 +1,95 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/prefs/pref_change_registrar.h" + +#include "base/bind.h" +#include "base/logging.h" +#include "base/prefs/pref_service.h" + +PrefChangeRegistrar::PrefChangeRegistrar() : service_(NULL) {} + +PrefChangeRegistrar::~PrefChangeRegistrar() { + // If you see an invalid memory access in this destructor, this + // PrefChangeRegistrar might be subscribed to an OffTheRecordProfileImpl that + // has been destroyed. This should not happen any more but be warned. + // Feel free to contact battre@chromium.org in case this happens. + RemoveAll(); +} + +void PrefChangeRegistrar::Init(PrefService* service) { + DCHECK(IsEmpty() || service_ == service); + service_ = service; +} + +void PrefChangeRegistrar::Add(const char* path, + const base::Closure& obs) { + Add(path, base::Bind(&PrefChangeRegistrar::InvokeUnnamedCallback, obs)); +} + +void PrefChangeRegistrar::Add(const char* path, + const NamedChangeCallback& obs) { + if (!service_) { + NOTREACHED(); + return; + } + DCHECK(!IsObserved(path)) << "Already had this pref registered."; + + service_->AddPrefObserver(path, this); + observers_[path] = obs; +} + +void PrefChangeRegistrar::Remove(const char* path) { + DCHECK(IsObserved(path)); + + observers_.erase(path); + service_->RemovePrefObserver(path, this); +} + +void PrefChangeRegistrar::RemoveAll() { + for (ObserverMap::const_iterator it = observers_.begin(); + it != observers_.end(); ++it) { + service_->RemovePrefObserver(it->first.c_str(), this); + } + + observers_.clear(); +} + +bool PrefChangeRegistrar::IsEmpty() const { + return observers_.empty(); +} + +bool PrefChangeRegistrar::IsObserved(const std::string& pref) { + return observers_.find(pref) != observers_.end(); +} + +bool PrefChangeRegistrar::IsManaged() { + for (ObserverMap::const_iterator it = observers_.begin(); + it != observers_.end(); ++it) { + const PrefService::Preference* pref = + service_->FindPreference(it->first.c_str()); + if (pref && pref->IsManaged()) + return true; + } + return false; +} + +void PrefChangeRegistrar::OnPreferenceChanged(PrefService* service, + const std::string& pref) { + if (IsObserved(pref)) + observers_[pref].Run(pref); +} + +void PrefChangeRegistrar::InvokeUnnamedCallback(const base::Closure& callback, + const std::string& pref_name) { + callback.Run(); +} + +PrefService* PrefChangeRegistrar::prefs() { + return service_; +} + +const PrefService* PrefChangeRegistrar::prefs() const { + return service_; +} diff --git a/base/prefs/pref_change_registrar.h b/base/prefs/pref_change_registrar.h new file mode 100644 index 0000000000..a914bea7d4 --- /dev/null +++ b/base/prefs/pref_change_registrar.h @@ -0,0 +1,81 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_PREFS_PREF_CHANGE_REGISTRAR_H_ +#define BASE_PREFS_PREF_CHANGE_REGISTRAR_H_ + +#include +#include + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/prefs/base_prefs_export.h" +#include "base/prefs/pref_observer.h" + +class PrefService; + +// Automatically manages the registration of one or more pref change observers +// with a PrefStore. Functions much like NotificationRegistrar, but specifically +// manages observers of preference changes. When the Registrar is destroyed, +// all registered observers are automatically unregistered with the PrefStore. +class BASE_PREFS_EXPORT PrefChangeRegistrar : public PrefObserver { + public: + // You can register this type of callback if you need to know the + // path of the preference that is changing. + typedef base::Callback NamedChangeCallback; + + PrefChangeRegistrar(); + virtual ~PrefChangeRegistrar(); + + // Must be called before adding or removing observers. Can be called more + // than once as long as the value of |service| doesn't change. + void Init(PrefService* service); + + // Adds a pref observer for the specified pref |path| and |obs| observer + // object. All registered observers will be automatically unregistered + // when the registrar's destructor is called. + // + // The second version binds a callback that will receive the path of + // the preference that is changing as its parameter. + // + // Only one observer may be registered per path. + void Add(const char* path, const base::Closure& obs); + void Add(const char* path, const NamedChangeCallback& obs); + + // Removes the pref observer registered for |path|. + void Remove(const char* path); + + // Removes all observers that have been previously added with a call to Add. + void RemoveAll(); + + // Returns true if no pref observers are registered. + bool IsEmpty() const; + + // Check whether |pref| is in the set of preferences being observed. + bool IsObserved(const std::string& pref); + + // Check whether any of the observed preferences has the managed bit set. + bool IsManaged(); + + // Return the PrefService for this registrar. + PrefService* prefs(); + const PrefService* prefs() const; + + private: + // PrefObserver: + virtual void OnPreferenceChanged(PrefService* service, + const std::string& pref_name) OVERRIDE; + + static void InvokeUnnamedCallback(const base::Closure& callback, + const std::string& pref_name); + + typedef std::map ObserverMap; + + ObserverMap observers_; + PrefService* service_; + + DISALLOW_COPY_AND_ASSIGN(PrefChangeRegistrar); +}; + +#endif // BASE_PREFS_PREF_CHANGE_REGISTRAR_H_ diff --git a/base/prefs/pref_change_registrar_unittest.cc b/base/prefs/pref_change_registrar_unittest.cc new file mode 100644 index 0000000000..f353a8fb3c --- /dev/null +++ b/base/prefs/pref_change_registrar_unittest.cc @@ -0,0 +1,200 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/prefs/pref_change_registrar.h" +#include "base/prefs/pref_observer.h" +#include "base/prefs/pref_registry_simple.h" +#include "base/prefs/testing_pref_service.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::Mock; +using testing::Eq; + +namespace base { +namespace { + +const char kHomePage[] = "homepage"; +const char kHomePageIsNewTabPage[] = "homepage_is_newtabpage"; +const char kApplicationLocale[] = "intl.app_locale"; + +// A mock provider that allows us to capture pref observer changes. +class MockPrefService : public TestingPrefServiceSimple { + public: + MockPrefService() {} + virtual ~MockPrefService() {} + + MOCK_METHOD2(AddPrefObserver, + void(const char*, PrefObserver*)); + MOCK_METHOD2(RemovePrefObserver, + void(const char*, PrefObserver*)); +}; + +} // namespace + +class PrefChangeRegistrarTest : public testing::Test { + public: + PrefChangeRegistrarTest() {} + virtual ~PrefChangeRegistrarTest() {} + + protected: + virtual void SetUp() OVERRIDE; + + base::Closure observer() const { + return base::Bind(&base::DoNothing); + } + + MockPrefService* service() const { return service_.get(); } + + private: + scoped_ptr service_; +}; + +void PrefChangeRegistrarTest::SetUp() { + service_.reset(new MockPrefService()); +} + +TEST_F(PrefChangeRegistrarTest, AddAndRemove) { + PrefChangeRegistrar registrar; + registrar.Init(service()); + + // Test adding. + EXPECT_CALL(*service(), + AddPrefObserver(Eq(std::string("test.pref.1")), ®istrar)); + EXPECT_CALL(*service(), + AddPrefObserver(Eq(std::string("test.pref.2")), ®istrar)); + registrar.Add("test.pref.1", observer()); + registrar.Add("test.pref.2", observer()); + EXPECT_FALSE(registrar.IsEmpty()); + + // Test removing. + Mock::VerifyAndClearExpectations(service()); + EXPECT_CALL(*service(), + RemovePrefObserver(Eq(std::string("test.pref.1")), ®istrar)); + EXPECT_CALL(*service(), + RemovePrefObserver(Eq(std::string("test.pref.2")), ®istrar)); + registrar.Remove("test.pref.1"); + registrar.Remove("test.pref.2"); + EXPECT_TRUE(registrar.IsEmpty()); + + // Explicitly check the expectations now to make sure that the Removes + // worked (rather than the registrar destructor doing the work). + Mock::VerifyAndClearExpectations(service()); +} + +TEST_F(PrefChangeRegistrarTest, AutoRemove) { + PrefChangeRegistrar registrar; + registrar.Init(service()); + + // Setup of auto-remove. + EXPECT_CALL(*service(), + AddPrefObserver(Eq(std::string("test.pref.1")), ®istrar)); + registrar.Add("test.pref.1", observer()); + Mock::VerifyAndClearExpectations(service()); + EXPECT_FALSE(registrar.IsEmpty()); + + // Test auto-removing. + EXPECT_CALL(*service(), + RemovePrefObserver(Eq(std::string("test.pref.1")), ®istrar)); +} + +TEST_F(PrefChangeRegistrarTest, RemoveAll) { + PrefChangeRegistrar registrar; + registrar.Init(service()); + + EXPECT_CALL(*service(), + AddPrefObserver(Eq(std::string("test.pref.1")), ®istrar)); + EXPECT_CALL(*service(), + AddPrefObserver(Eq(std::string("test.pref.2")), ®istrar)); + registrar.Add("test.pref.1", observer()); + registrar.Add("test.pref.2", observer()); + Mock::VerifyAndClearExpectations(service()); + + EXPECT_CALL(*service(), + RemovePrefObserver(Eq(std::string("test.pref.1")), ®istrar)); + EXPECT_CALL(*service(), + RemovePrefObserver(Eq(std::string("test.pref.2")), ®istrar)); + registrar.RemoveAll(); + EXPECT_TRUE(registrar.IsEmpty()); + + // Explicitly check the expectations now to make sure that the RemoveAll + // worked (rather than the registrar destructor doing the work). + Mock::VerifyAndClearExpectations(service()); +} + +class ObserveSetOfPreferencesTest : public testing::Test { + public: + virtual void SetUp() { + pref_service_.reset(new TestingPrefServiceSimple); + PrefRegistrySimple* registry = pref_service_->registry(); + registry->RegisterStringPref(kHomePage, "http://google.com"); + registry->RegisterBooleanPref(kHomePageIsNewTabPage, false); + registry->RegisterStringPref(kApplicationLocale, std::string()); + } + + PrefChangeRegistrar* CreatePrefChangeRegistrar() { + PrefChangeRegistrar* pref_set = new PrefChangeRegistrar(); + base::Closure callback = base::Bind(&base::DoNothing); + pref_set->Init(pref_service_.get()); + pref_set->Add(kHomePage, callback); + pref_set->Add(kHomePageIsNewTabPage, callback); + return pref_set; + } + + MOCK_METHOD1(OnPreferenceChanged, void(const std::string&)); + + scoped_ptr pref_service_; +}; + +TEST_F(ObserveSetOfPreferencesTest, IsObserved) { + scoped_ptr pref_set(CreatePrefChangeRegistrar()); + EXPECT_TRUE(pref_set->IsObserved(kHomePage)); + EXPECT_TRUE(pref_set->IsObserved(kHomePageIsNewTabPage)); + EXPECT_FALSE(pref_set->IsObserved(kApplicationLocale)); +} + +TEST_F(ObserveSetOfPreferencesTest, IsManaged) { + scoped_ptr pref_set(CreatePrefChangeRegistrar()); + EXPECT_FALSE(pref_set->IsManaged()); + pref_service_->SetManagedPref(kHomePage, + new StringValue("http://crbug.com")); + EXPECT_TRUE(pref_set->IsManaged()); + pref_service_->SetManagedPref(kHomePageIsNewTabPage, + new FundamentalValue(true)); + EXPECT_TRUE(pref_set->IsManaged()); + pref_service_->RemoveManagedPref(kHomePage); + EXPECT_TRUE(pref_set->IsManaged()); + pref_service_->RemoveManagedPref(kHomePageIsNewTabPage); + EXPECT_FALSE(pref_set->IsManaged()); +} + +TEST_F(ObserveSetOfPreferencesTest, Observe) { + using testing::_; + using testing::Mock; + + PrefChangeRegistrar pref_set; + PrefChangeRegistrar::NamedChangeCallback callback = base::Bind( + &ObserveSetOfPreferencesTest::OnPreferenceChanged, + base::Unretained(this)); + pref_set.Init(pref_service_.get()); + pref_set.Add(kHomePage, callback); + pref_set.Add(kHomePageIsNewTabPage, callback); + + EXPECT_CALL(*this, OnPreferenceChanged(kHomePage)); + pref_service_->SetUserPref(kHomePage, new StringValue("http://crbug.com")); + Mock::VerifyAndClearExpectations(this); + + EXPECT_CALL(*this, OnPreferenceChanged(kHomePageIsNewTabPage)); + pref_service_->SetUserPref(kHomePageIsNewTabPage, + new FundamentalValue(true)); + Mock::VerifyAndClearExpectations(this); + + EXPECT_CALL(*this, OnPreferenceChanged(_)).Times(0); + pref_service_->SetUserPref(kApplicationLocale, new StringValue("en_US.utf8")); + Mock::VerifyAndClearExpectations(this); +} + +} // namespace base diff --git a/base/prefs/pref_member.cc b/base/prefs/pref_member.cc new file mode 100644 index 0000000000..ca6e3bed44 --- /dev/null +++ b/base/prefs/pref_member.cc @@ -0,0 +1,225 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/prefs/pref_member.h" + +#include "base/bind_helpers.h" +#include "base/callback.h" +#include "base/location.h" +#include "base/prefs/pref_service.h" +#include "base/value_conversions.h" + +using base::MessageLoopProxy; + +namespace subtle { + +PrefMemberBase::PrefMemberBase() + : prefs_(NULL), + setting_value_(false) { +} + +PrefMemberBase::~PrefMemberBase() { + Destroy(); +} + +void PrefMemberBase::Init(const char* pref_name, + PrefService* prefs, + const NamedChangeCallback& observer) { + observer_ = observer; + Init(pref_name, prefs); +} + +void PrefMemberBase::Init(const char* pref_name, + PrefService* prefs) { + DCHECK(pref_name); + DCHECK(prefs); + DCHECK(pref_name_.empty()); // Check that Init is only called once. + prefs_ = prefs; + pref_name_ = pref_name; + // Check that the preference is registered. + DCHECK(prefs_->FindPreference(pref_name_.c_str())) + << pref_name << " not registered."; + + // Add ourselves as a pref observer so we can keep our local value in sync. + prefs_->AddPrefObserver(pref_name, this); +} + +void PrefMemberBase::Destroy() { + if (prefs_ && !pref_name_.empty()) { + prefs_->RemovePrefObserver(pref_name_.c_str(), this); + prefs_ = NULL; + } +} + +void PrefMemberBase::MoveToThread( + const scoped_refptr& message_loop) { + VerifyValuePrefName(); + // Load the value from preferences if it hasn't been loaded so far. + if (!internal()) + UpdateValueFromPref(base::Closure()); + internal()->MoveToThread(message_loop); +} + +void PrefMemberBase::OnPreferenceChanged(PrefService* service, + const std::string& pref_name) { + VerifyValuePrefName(); + UpdateValueFromPref((!setting_value_ && !observer_.is_null()) ? + base::Bind(observer_, pref_name) : base::Closure()); +} + +void PrefMemberBase::UpdateValueFromPref(const base::Closure& callback) const { + VerifyValuePrefName(); + const PrefService::Preference* pref = + prefs_->FindPreference(pref_name_.c_str()); + DCHECK(pref); + if (!internal()) + CreateInternal(); + internal()->UpdateValue(pref->GetValue()->DeepCopy(), + pref->IsManaged(), + pref->IsUserModifiable(), + callback); +} + +void PrefMemberBase::VerifyPref() const { + VerifyValuePrefName(); + if (!internal()) + UpdateValueFromPref(base::Closure()); +} + +void PrefMemberBase::InvokeUnnamedCallback(const base::Closure& callback, + const std::string& pref_name) { + callback.Run(); +} + +PrefMemberBase::Internal::Internal() + : thread_loop_(MessageLoopProxy::current()), + is_managed_(false) { +} +PrefMemberBase::Internal::~Internal() { } + +bool PrefMemberBase::Internal::IsOnCorrectThread() const { + // In unit tests, there may not be a message loop. + return thread_loop_.get() == NULL || thread_loop_->BelongsToCurrentThread(); +} + +void PrefMemberBase::Internal::UpdateValue( + base::Value* v, + bool is_managed, + bool is_user_modifiable, + const base::Closure& callback) const { + scoped_ptr value(v); + base::ScopedClosureRunner closure_runner(callback); + if (IsOnCorrectThread()) { + bool rv = UpdateValueInternal(*value); + DCHECK(rv); + is_managed_ = is_managed; + is_user_modifiable_ = is_user_modifiable; + } else { + bool may_run = thread_loop_->PostTask( + FROM_HERE, + base::Bind(&PrefMemberBase::Internal::UpdateValue, this, + value.release(), is_managed, is_user_modifiable, + closure_runner.Release())); + DCHECK(may_run); + } +} + +void PrefMemberBase::Internal::MoveToThread( + const scoped_refptr& message_loop) { + CheckOnCorrectThread(); + thread_loop_ = message_loop; +} + +bool PrefMemberVectorStringUpdate(const base::Value& value, + std::vector* string_vector) { + if (!value.IsType(base::Value::TYPE_LIST)) + return false; + const base::ListValue* list = static_cast(&value); + + std::vector local_vector; + for (base::ListValue::const_iterator it = list->begin(); + it != list->end(); ++it) { + std::string string_value; + if (!(*it)->GetAsString(&string_value)) + return false; + + local_vector.push_back(string_value); + } + + string_vector->swap(local_vector); + return true; +} + +} // namespace subtle + +template <> +void PrefMember::UpdatePref(const bool& value) { + prefs()->SetBoolean(pref_name().c_str(), value); +} + +template <> +bool PrefMember::Internal::UpdateValueInternal( + const base::Value& value) const { + return value.GetAsBoolean(&value_); +} + +template <> +void PrefMember::UpdatePref(const int& value) { + prefs()->SetInteger(pref_name().c_str(), value); +} + +template <> +bool PrefMember::Internal::UpdateValueInternal( + const base::Value& value) const { + return value.GetAsInteger(&value_); +} + +template <> +void PrefMember::UpdatePref(const double& value) { + prefs()->SetDouble(pref_name().c_str(), value); +} + +template <> +bool PrefMember::Internal::UpdateValueInternal(const base::Value& value) + const { + return value.GetAsDouble(&value_); +} + +template <> +void PrefMember::UpdatePref(const std::string& value) { + prefs()->SetString(pref_name().c_str(), value); +} + +template <> +bool PrefMember::Internal::UpdateValueInternal( + const base::Value& value) + const { + return value.GetAsString(&value_); +} + +template <> +void PrefMember::UpdatePref(const base::FilePath& value) { + prefs()->SetFilePath(pref_name().c_str(), value); +} + +template <> +bool PrefMember::Internal::UpdateValueInternal( + const base::Value& value) + const { + return base::GetValueAsFilePath(value, &value_); +} + +template <> +void PrefMember >::UpdatePref( + const std::vector& value) { + base::ListValue list_value; + list_value.AppendStrings(value); + prefs()->Set(pref_name().c_str(), list_value); +} + +template <> +bool PrefMember >::Internal::UpdateValueInternal( + const base::Value& value) const { + return subtle::PrefMemberVectorStringUpdate(value, &value_); +} diff --git a/base/prefs/pref_member.h b/base/prefs/pref_member.h new file mode 100644 index 0000000000..17f5b447eb --- /dev/null +++ b/base/prefs/pref_member.h @@ -0,0 +1,352 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// A helper class that stays in sync with a preference (bool, int, real, +// string or filepath). For example: +// +// class MyClass { +// public: +// MyClass(PrefService* prefs) { +// my_string_.Init(prefs::kHomePage, prefs); +// } +// private: +// StringPrefMember my_string_; +// }; +// +// my_string_ should stay in sync with the prefs::kHomePage pref and will +// update if either the pref changes or if my_string_.SetValue is called. +// +// An optional observer can be passed into the Init method which can be used to +// notify MyClass of changes. Note that if you use SetValue(), the observer +// will not be notified. + +#ifndef BASE_PREFS_PREF_MEMBER_H_ +#define BASE_PREFS_PREF_MEMBER_H_ + +#include +#include + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/callback_forward.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/prefs/base_prefs_export.h" +#include "base/prefs/pref_observer.h" +#include "base/values.h" + +class PrefService; + +namespace subtle { + +class BASE_PREFS_EXPORT PrefMemberBase : public PrefObserver { + public: + // Type of callback you can register if you need to know the name of + // the pref that is changing. + typedef base::Callback NamedChangeCallback; + + PrefService* prefs() { return prefs_; } + const PrefService* prefs() const { return prefs_; } + + protected: + class BASE_PREFS_EXPORT Internal + : public base::RefCountedThreadSafe { + public: + Internal(); + + // Update the value, either by calling |UpdateValueInternal| directly + // or by dispatching to the right thread. + // Takes ownership of |value|. + void UpdateValue(base::Value* value, + bool is_managed, + bool is_user_modifiable, + const base::Closure& callback) const; + + void MoveToThread( + const scoped_refptr& message_loop); + + // See PrefMember<> for description. + bool IsManaged() const { + return is_managed_; + } + + bool IsUserModifiable() const { + return is_user_modifiable_; + } + + protected: + friend class base::RefCountedThreadSafe; + virtual ~Internal(); + + void CheckOnCorrectThread() const { + DCHECK(IsOnCorrectThread()); + } + + private: + // This method actually updates the value. It should only be called from + // the thread the PrefMember is on. + virtual bool UpdateValueInternal(const base::Value& value) const = 0; + + bool IsOnCorrectThread() const; + + scoped_refptr thread_loop_; + mutable bool is_managed_; + mutable bool is_user_modifiable_; + + DISALLOW_COPY_AND_ASSIGN(Internal); + }; + + PrefMemberBase(); + virtual ~PrefMemberBase(); + + // See PrefMember<> for description. + void Init(const char* pref_name, PrefService* prefs, + const NamedChangeCallback& observer); + void Init(const char* pref_name, PrefService* prefs); + + virtual void CreateInternal() const = 0; + + // See PrefMember<> for description. + void Destroy(); + + void MoveToThread(const scoped_refptr& message_loop); + + // PrefObserver + virtual void OnPreferenceChanged(PrefService* service, + const std::string& pref_name) OVERRIDE; + + void VerifyValuePrefName() const { + DCHECK(!pref_name_.empty()); + } + + // This method is used to do the actual sync with the preference. + // Note: it is logically const, because it doesn't modify the state + // seen by the outside world. It is just doing a lazy load behind the scenes. + void UpdateValueFromPref(const base::Closure& callback) const; + + // Verifies the preference name, and lazily loads the preference value if + // it hasn't been loaded yet. + void VerifyPref() const; + + const std::string& pref_name() const { return pref_name_; } + + virtual Internal* internal() const = 0; + + // Used to allow registering plain base::Closure callbacks. + static void InvokeUnnamedCallback(const base::Closure& callback, + const std::string& pref_name); + + private: + // Ordered the members to compact the class instance. + std::string pref_name_; + NamedChangeCallback observer_; + PrefService* prefs_; + + protected: + bool setting_value_; +}; + +// This function implements StringListPrefMember::UpdateValue(). +// It is exposed here for testing purposes. +bool BASE_PREFS_EXPORT PrefMemberVectorStringUpdate( + const base::Value& value, + std::vector* string_vector); + +} // namespace subtle + +template +class PrefMember : public subtle::PrefMemberBase { + public: + // Defer initialization to an Init method so it's easy to make this class be + // a member variable. + PrefMember() {} + virtual ~PrefMember() {} + + // Do the actual initialization of the class. Use the two-parameter + // version if you don't want any notifications of changes. This + // method should only be called on the UI thread. + void Init(const char* pref_name, PrefService* prefs, + const NamedChangeCallback& observer) { + subtle::PrefMemberBase::Init(pref_name, prefs, observer); + } + void Init(const char* pref_name, PrefService* prefs, + const base::Closure& observer) { + subtle::PrefMemberBase::Init( + pref_name, prefs, + base::Bind(&PrefMemberBase::InvokeUnnamedCallback, observer)); + } + void Init(const char* pref_name, PrefService* prefs) { + subtle::PrefMemberBase::Init(pref_name, prefs); + } + + // Unsubscribes the PrefMember from the PrefService. After calling this + // function, the PrefMember may not be used any more on the UI thread. + // Assuming |MoveToThread| was previously called, |GetValue|, |IsManaged|, + // and |IsUserModifiable| can still be called from the other thread but + // the results will no longer update from the PrefService. + // This method should only be called on the UI thread. + void Destroy() { + subtle::PrefMemberBase::Destroy(); + } + + // Moves the PrefMember to another thread, allowing read accesses from there. + // Changes from the PrefService will be propagated asynchronously + // via PostTask. + // This method should only be used from the thread the PrefMember is currently + // on, which is the UI thread by default. + void MoveToThread(const scoped_refptr& message_loop) { + subtle::PrefMemberBase::MoveToThread(message_loop); + } + + // Check whether the pref is managed, i.e. controlled externally through + // enterprise configuration management (e.g. windows group policy). Returns + // false for unknown prefs. + // This method should only be used from the thread the PrefMember is currently + // on, which is the UI thread unless changed by |MoveToThread|. + bool IsManaged() const { + VerifyPref(); + return internal_->IsManaged(); + } + + // Checks whether the pref can be modified by the user. This returns false + // when the pref is managed by a policy or an extension, and when a command + // line flag overrides the pref. + // This method should only be used from the thread the PrefMember is currently + // on, which is the UI thread unless changed by |MoveToThread|. + bool IsUserModifiable() const { + VerifyPref(); + return internal_->IsUserModifiable(); + } + + // Retrieve the value of the member variable. + // This method should only be used from the thread the PrefMember is currently + // on, which is the UI thread unless changed by |MoveToThread|. + ValueType GetValue() const { + VerifyPref(); + return internal_->value(); + } + + // Provided as a convenience. + ValueType operator*() const { + return GetValue(); + } + + // Set the value of the member variable. + // This method should only be called on the UI thread. + void SetValue(const ValueType& value) { + VerifyValuePrefName(); + setting_value_ = true; + UpdatePref(value); + setting_value_ = false; + } + + // Returns the pref name. + const std::string& GetPrefName() const { + return pref_name(); + } + + private: + class Internal : public subtle::PrefMemberBase::Internal { + public: + Internal() : value_(ValueType()) {} + + ValueType value() { + CheckOnCorrectThread(); + return value_; + } + + protected: + virtual ~Internal() {} + + virtual BASE_PREFS_EXPORT bool UpdateValueInternal( + const base::Value& value) const OVERRIDE; + + // We cache the value of the pref so we don't have to keep walking the pref + // tree. + mutable ValueType value_; + + DISALLOW_COPY_AND_ASSIGN(Internal); + }; + + virtual Internal* internal() const OVERRIDE { return internal_.get(); } + virtual void CreateInternal() const OVERRIDE { internal_ = new Internal(); } + + // This method is used to do the actual sync with pref of the specified type. + void BASE_PREFS_EXPORT UpdatePref(const ValueType& value); + + mutable scoped_refptr internal_; + + DISALLOW_COPY_AND_ASSIGN(PrefMember); +}; + +// Declaration of template specialization need to be repeated here +// specifically for each specialization (rather than just once above) +// or at least one of our compilers won't be happy in all cases. +// Specifically, it was failing on ChromeOS with a complaint about +// PrefMember::UpdateValueInternal not being defined when +// built in a chroot with the following parameters: +// +// FEATURES="noclean nostrip" USE="-chrome_debug -chrome_remoting +// -chrome_internal -chrome_pdf component_build" +// ~/trunk/goma/goma-wrapper cros_chrome_make --board=${BOARD} +// --install --runhooks + +template <> +BASE_PREFS_EXPORT void PrefMember::UpdatePref(const bool& value); + +template <> +BASE_PREFS_EXPORT bool PrefMember::Internal::UpdateValueInternal( + const base::Value& value) const; + +template <> +BASE_PREFS_EXPORT void PrefMember::UpdatePref(const int& value); + +template <> +BASE_PREFS_EXPORT bool PrefMember::Internal::UpdateValueInternal( + const base::Value& value) const; + +template <> +BASE_PREFS_EXPORT void PrefMember::UpdatePref(const double& value); + +template <> +BASE_PREFS_EXPORT bool PrefMember::Internal::UpdateValueInternal( + const base::Value& value) const; + +template <> +BASE_PREFS_EXPORT void PrefMember::UpdatePref( + const std::string& value); + +template <> +BASE_PREFS_EXPORT bool PrefMember::Internal::UpdateValueInternal( + const base::Value& value) const; + +template <> +BASE_PREFS_EXPORT void PrefMember::UpdatePref( + const base::FilePath& value); + +template <> +BASE_PREFS_EXPORT bool +PrefMember::Internal::UpdateValueInternal( + const base::Value& value) const; + +template <> +BASE_PREFS_EXPORT void PrefMember >::UpdatePref( + const std::vector& value); + +template <> +BASE_PREFS_EXPORT bool +PrefMember >::Internal::UpdateValueInternal( + const base::Value& value) const; + +typedef PrefMember BooleanPrefMember; +typedef PrefMember IntegerPrefMember; +typedef PrefMember DoublePrefMember; +typedef PrefMember StringPrefMember; +typedef PrefMember FilePathPrefMember; +// This preference member is expensive for large string arrays. +typedef PrefMember > StringListPrefMember; + +#endif // BASE_PREFS_PREF_MEMBER_H_ diff --git a/base/prefs/pref_member_unittest.cc b/base/prefs/pref_member_unittest.cc new file mode 100644 index 0000000000..d4d7e02c97 --- /dev/null +++ b/base/prefs/pref_member_unittest.cc @@ -0,0 +1,321 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/prefs/pref_member.h" + +#include "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "base/prefs/pref_registry_simple.h" +#include "base/prefs/testing_pref_service.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const char kBoolPref[] = "bool"; +const char kIntPref[] = "int"; +const char kDoublePref[] = "double"; +const char kStringPref[] = "string"; +const char kStringListPref[] = "string_list"; + +void RegisterTestPrefs(PrefRegistrySimple* registry) { + registry->RegisterBooleanPref(kBoolPref, false); + registry->RegisterIntegerPref(kIntPref, 0); + registry->RegisterDoublePref(kDoublePref, 0.0); + registry->RegisterStringPref(kStringPref, "default"); + registry->RegisterListPref(kStringListPref, new base::ListValue()); +} + +class GetPrefValueHelper + : public base::RefCountedThreadSafe { + public: + GetPrefValueHelper() : value_(false), pref_thread_("pref thread") { + pref_thread_.Start(); + } + + void Init(const char* pref_name, PrefService* prefs) { + pref_.Init(pref_name, prefs); + pref_.MoveToThread(pref_thread_.message_loop_proxy()); + } + + void Destroy() { + pref_.Destroy(); + } + + void FetchValue() { + base::WaitableEvent event(true, false); + ASSERT_TRUE( + pref_thread_.message_loop_proxy()->PostTask( + FROM_HERE, + base::Bind(&GetPrefValueHelper::GetPrefValue, this, &event))); + event.Wait(); + } + + // The thread must be stopped on the main thread. GetPrefValueHelper being + // ref-counted, the destructor can be called from any thread. + void StopThread() { + pref_thread_.Stop(); + } + + bool value() { return value_; } + + private: + friend class base::RefCountedThreadSafe; + ~GetPrefValueHelper() {} + + void GetPrefValue(base::WaitableEvent* event) { + value_ = pref_.GetValue(); + event->Signal(); + } + + BooleanPrefMember pref_; + bool value_; + + base::Thread pref_thread_; // The thread |pref_| runs on. +}; + +class PrefMemberTestClass { + public: + explicit PrefMemberTestClass(PrefService* prefs) + : observe_cnt_(0), prefs_(prefs) { + str_.Init(kStringPref, prefs, + base::Bind(&PrefMemberTestClass::OnPreferenceChanged, + base::Unretained(this))); + } + + void OnPreferenceChanged(const std::string& pref_name) { + EXPECT_EQ(pref_name, kStringPref); + EXPECT_EQ(str_.GetValue(), prefs_->GetString(kStringPref)); + ++observe_cnt_; + } + + StringPrefMember str_; + int observe_cnt_; + + private: + PrefService* prefs_; +}; + +} // anonymous namespace + +TEST(PrefMemberTest, BasicGetAndSet) { + TestingPrefServiceSimple prefs; + RegisterTestPrefs(prefs.registry()); + + // Test bool + BooleanPrefMember boolean; + boolean.Init(kBoolPref, &prefs); + + // Check the defaults + EXPECT_FALSE(prefs.GetBoolean(kBoolPref)); + EXPECT_FALSE(boolean.GetValue()); + EXPECT_FALSE(*boolean); + + // Try changing through the member variable. + boolean.SetValue(true); + EXPECT_TRUE(boolean.GetValue()); + EXPECT_TRUE(prefs.GetBoolean(kBoolPref)); + EXPECT_TRUE(*boolean); + + // Try changing back through the pref. + prefs.SetBoolean(kBoolPref, false); + EXPECT_FALSE(prefs.GetBoolean(kBoolPref)); + EXPECT_FALSE(boolean.GetValue()); + EXPECT_FALSE(*boolean); + + // Test int + IntegerPrefMember integer; + integer.Init(kIntPref, &prefs); + + // Check the defaults + EXPECT_EQ(0, prefs.GetInteger(kIntPref)); + EXPECT_EQ(0, integer.GetValue()); + EXPECT_EQ(0, *integer); + + // Try changing through the member variable. + integer.SetValue(5); + EXPECT_EQ(5, integer.GetValue()); + EXPECT_EQ(5, prefs.GetInteger(kIntPref)); + EXPECT_EQ(5, *integer); + + // Try changing back through the pref. + prefs.SetInteger(kIntPref, 2); + EXPECT_EQ(2, prefs.GetInteger(kIntPref)); + EXPECT_EQ(2, integer.GetValue()); + EXPECT_EQ(2, *integer); + + // Test double + DoublePrefMember double_member; + double_member.Init(kDoublePref, &prefs); + + // Check the defaults + EXPECT_EQ(0.0, prefs.GetDouble(kDoublePref)); + EXPECT_EQ(0.0, double_member.GetValue()); + EXPECT_EQ(0.0, *double_member); + + // Try changing through the member variable. + double_member.SetValue(1.0); + EXPECT_EQ(1.0, double_member.GetValue()); + EXPECT_EQ(1.0, prefs.GetDouble(kDoublePref)); + EXPECT_EQ(1.0, *double_member); + + // Try changing back through the pref. + prefs.SetDouble(kDoublePref, 3.0); + EXPECT_EQ(3.0, prefs.GetDouble(kDoublePref)); + EXPECT_EQ(3.0, double_member.GetValue()); + EXPECT_EQ(3.0, *double_member); + + // Test string + StringPrefMember string; + string.Init(kStringPref, &prefs); + + // Check the defaults + EXPECT_EQ("default", prefs.GetString(kStringPref)); + EXPECT_EQ("default", string.GetValue()); + EXPECT_EQ("default", *string); + + // Try changing through the member variable. + string.SetValue("foo"); + EXPECT_EQ("foo", string.GetValue()); + EXPECT_EQ("foo", prefs.GetString(kStringPref)); + EXPECT_EQ("foo", *string); + + // Try changing back through the pref. + prefs.SetString(kStringPref, "bar"); + EXPECT_EQ("bar", prefs.GetString(kStringPref)); + EXPECT_EQ("bar", string.GetValue()); + EXPECT_EQ("bar", *string); + + // Test string list + base::ListValue expected_list; + std::vector expected_vector; + StringListPrefMember string_list; + string_list.Init(kStringListPref, &prefs); + + // Check the defaults + EXPECT_TRUE(expected_list.Equals(prefs.GetList(kStringListPref))); + EXPECT_EQ(expected_vector, string_list.GetValue()); + EXPECT_EQ(expected_vector, *string_list); + + // Try changing through the pref member. + expected_list.AppendString("foo"); + expected_vector.push_back("foo"); + string_list.SetValue(expected_vector); + + EXPECT_TRUE(expected_list.Equals(prefs.GetList(kStringListPref))); + EXPECT_EQ(expected_vector, string_list.GetValue()); + EXPECT_EQ(expected_vector, *string_list); + + // Try adding through the pref. + expected_list.AppendString("bar"); + expected_vector.push_back("bar"); + prefs.Set(kStringListPref, expected_list); + + EXPECT_TRUE(expected_list.Equals(prefs.GetList(kStringListPref))); + EXPECT_EQ(expected_vector, string_list.GetValue()); + EXPECT_EQ(expected_vector, *string_list); + + // Try removing through the pref. + expected_list.Remove(0, NULL); + expected_vector.erase(expected_vector.begin()); + prefs.Set(kStringListPref, expected_list); + + EXPECT_TRUE(expected_list.Equals(prefs.GetList(kStringListPref))); + EXPECT_EQ(expected_vector, string_list.GetValue()); + EXPECT_EQ(expected_vector, *string_list); +} + +TEST(PrefMemberTest, InvalidList) { + // Set the vector to an initial good value. + std::vector expected_vector; + expected_vector.push_back("foo"); + + // Try to add a valid list first. + base::ListValue list; + list.AppendString("foo"); + std::vector vector; + EXPECT_TRUE(subtle::PrefMemberVectorStringUpdate(list, &vector)); + EXPECT_EQ(expected_vector, vector); + + // Now try to add an invalid list. |vector| should not be changed. + list.AppendInteger(0); + EXPECT_FALSE(subtle::PrefMemberVectorStringUpdate(list, &vector)); + EXPECT_EQ(expected_vector, vector); +} + +TEST(PrefMemberTest, TwoPrefs) { + // Make sure two DoublePrefMembers stay in sync. + TestingPrefServiceSimple prefs; + RegisterTestPrefs(prefs.registry()); + + DoublePrefMember pref1; + pref1.Init(kDoublePref, &prefs); + DoublePrefMember pref2; + pref2.Init(kDoublePref, &prefs); + + pref1.SetValue(2.3); + EXPECT_EQ(2.3, *pref2); + + pref2.SetValue(3.5); + EXPECT_EQ(3.5, *pref1); + + prefs.SetDouble(kDoublePref, 4.2); + EXPECT_EQ(4.2, *pref1); + EXPECT_EQ(4.2, *pref2); +} + +TEST(PrefMemberTest, Observer) { + TestingPrefServiceSimple prefs; + RegisterTestPrefs(prefs.registry()); + + PrefMemberTestClass test_obj(&prefs); + EXPECT_EQ("default", *test_obj.str_); + + // Calling SetValue should not fire the observer. + test_obj.str_.SetValue("hello"); + EXPECT_EQ(0, test_obj.observe_cnt_); + EXPECT_EQ("hello", prefs.GetString(kStringPref)); + + // Changing the pref does fire the observer. + prefs.SetString(kStringPref, "world"); + EXPECT_EQ(1, test_obj.observe_cnt_); + EXPECT_EQ("world", *(test_obj.str_)); + + // Not changing the value should not fire the observer. + prefs.SetString(kStringPref, "world"); + EXPECT_EQ(1, test_obj.observe_cnt_); + EXPECT_EQ("world", *(test_obj.str_)); + + prefs.SetString(kStringPref, "hello"); + EXPECT_EQ(2, test_obj.observe_cnt_); + EXPECT_EQ("hello", prefs.GetString(kStringPref)); +} + +TEST(PrefMemberTest, NoInit) { + // Make sure not calling Init on a PrefMember doesn't cause problems. + IntegerPrefMember pref; +} + +TEST(PrefMemberTest, MoveToThread) { + TestingPrefServiceSimple prefs; + scoped_refptr helper(new GetPrefValueHelper()); + RegisterTestPrefs(prefs.registry()); + helper->Init(kBoolPref, &prefs); + + helper->FetchValue(); + EXPECT_FALSE(helper->value()); + + prefs.SetBoolean(kBoolPref, true); + + helper->FetchValue(); + EXPECT_TRUE(helper->value()); + + helper->Destroy(); + + helper->FetchValue(); + EXPECT_TRUE(helper->value()); + + helper->StopThread(); +} diff --git a/base/prefs/pref_notifier.h b/base/prefs/pref_notifier.h new file mode 100644 index 0000000000..e0df260c51 --- /dev/null +++ b/base/prefs/pref_notifier.h @@ -0,0 +1,26 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_PREFS_PREF_NOTIFIER_H_ +#define BASE_PREFS_PREF_NOTIFIER_H_ + +#include + +// Delegate interface used by PrefValueStore to notify its owner about changes +// to the preference values. +// TODO(mnissler, danno): Move this declaration to pref_value_store.h once we've +// cleaned up all public uses of this interface. +class PrefNotifier { + public: + virtual ~PrefNotifier() {} + + // Sends out a change notification for the preference identified by + // |pref_name|. + virtual void OnPreferenceChanged(const std::string& pref_name) = 0; + + // Broadcasts the intialization completed notification. + virtual void OnInitializationCompleted(bool succeeded) = 0; +}; + +#endif // BASE_PREFS_PREF_NOTIFIER_H_ diff --git a/base/prefs/pref_notifier_impl.cc b/base/prefs/pref_notifier_impl.cc new file mode 100644 index 0000000000..c02a7b3f5a --- /dev/null +++ b/base/prefs/pref_notifier_impl.cc @@ -0,0 +1,117 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/prefs/pref_notifier_impl.h" + +#include "base/logging.h" +#include "base/prefs/pref_service.h" +#include "base/stl_util.h" + +PrefNotifierImpl::PrefNotifierImpl() + : pref_service_(NULL) { +} + +PrefNotifierImpl::PrefNotifierImpl(PrefService* service) + : pref_service_(service) { +} + +PrefNotifierImpl::~PrefNotifierImpl() { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Verify that there are no pref observers when we shut down. + for (PrefObserverMap::iterator it = pref_observers_.begin(); + it != pref_observers_.end(); ++it) { + PrefObserverList::Iterator obs_iterator(*(it->second)); + if (obs_iterator.GetNext()) { + LOG(WARNING) << "pref observer found at shutdown " << it->first; + } + } + + // Same for initialization observers. + if (!init_observers_.empty()) + LOG(WARNING) << "Init observer found at shutdown."; + + STLDeleteContainerPairSecondPointers(pref_observers_.begin(), + pref_observers_.end()); + pref_observers_.clear(); + init_observers_.clear(); +} + +void PrefNotifierImpl::AddPrefObserver(const char* path, + PrefObserver* obs) { + // Get the pref observer list associated with the path. + PrefObserverList* observer_list = NULL; + const PrefObserverMap::iterator observer_iterator = + pref_observers_.find(path); + if (observer_iterator == pref_observers_.end()) { + observer_list = new PrefObserverList; + pref_observers_[path] = observer_list; + } else { + observer_list = observer_iterator->second; + } + + // Add the pref observer. ObserverList will DCHECK if it already is + // in the list. + observer_list->AddObserver(obs); +} + +void PrefNotifierImpl::RemovePrefObserver(const char* path, + PrefObserver* obs) { + DCHECK(thread_checker_.CalledOnValidThread()); + + const PrefObserverMap::iterator observer_iterator = + pref_observers_.find(path); + if (observer_iterator == pref_observers_.end()) { + return; + } + + PrefObserverList* observer_list = observer_iterator->second; + observer_list->RemoveObserver(obs); +} + +void PrefNotifierImpl::AddInitObserver(base::Callback obs) { + init_observers_.push_back(obs); +} + +void PrefNotifierImpl::OnPreferenceChanged(const std::string& path) { + FireObservers(path); +} + +void PrefNotifierImpl::OnInitializationCompleted(bool succeeded) { + DCHECK(thread_checker_.CalledOnValidThread()); + + // We must make a copy of init_observers_ and clear it before we run + // observers, or we can end up in this method re-entrantly before + // clearing the observers list. + PrefInitObserverList observers(init_observers_); + init_observers_.clear(); + + for (PrefInitObserverList::iterator it = observers.begin(); + it != observers.end(); + ++it) { + it->Run(succeeded); + } +} + +void PrefNotifierImpl::FireObservers(const std::string& path) { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Only send notifications for registered preferences. + if (!pref_service_->FindPreference(path.c_str())) + return; + + const PrefObserverMap::iterator observer_iterator = + pref_observers_.find(path); + if (observer_iterator == pref_observers_.end()) + return; + + FOR_EACH_OBSERVER(PrefObserver, + *(observer_iterator->second), + OnPreferenceChanged(pref_service_, path)); +} + +void PrefNotifierImpl::SetPrefService(PrefService* pref_service) { + DCHECK(pref_service_ == NULL); + pref_service_ = pref_service; +} diff --git a/base/prefs/pref_notifier_impl.h b/base/prefs/pref_notifier_impl.h new file mode 100644 index 0000000000..655203d06d --- /dev/null +++ b/base/prefs/pref_notifier_impl.h @@ -0,0 +1,73 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_PREFS_PREF_NOTIFIER_IMPL_H_ +#define BASE_PREFS_PREF_NOTIFIER_IMPL_H_ + +#include +#include + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/containers/hash_tables.h" +#include "base/observer_list.h" +#include "base/prefs/base_prefs_export.h" +#include "base/prefs/pref_notifier.h" +#include "base/prefs/pref_observer.h" +#include "base/threading/thread_checker.h" + +class PrefService; + +// The PrefNotifier implementation used by the PrefService. +class BASE_PREFS_EXPORT PrefNotifierImpl + : public NON_EXPORTED_BASE(PrefNotifier) { + public: + PrefNotifierImpl(); + explicit PrefNotifierImpl(PrefService* pref_service); + virtual ~PrefNotifierImpl(); + + // If the pref at the given path changes, we call the observer's + // OnPreferenceChanged method. + void AddPrefObserver(const char* path, PrefObserver* observer); + void RemovePrefObserver(const char* path, PrefObserver* observer); + + // We run the callback once, when initialization completes. The bool + // parameter will be set to true for successful initialization, + // false for unsuccessful. + void AddInitObserver(base::Callback observer); + + void SetPrefService(PrefService* pref_service); + + protected: + // PrefNotifier overrides. + virtual void OnPreferenceChanged(const std::string& pref_name) OVERRIDE; + virtual void OnInitializationCompleted(bool succeeded) OVERRIDE; + + // A map from pref names to a list of observers. Observers get fired in the + // order they are added. These should only be accessed externally for unit + // testing. + typedef ObserverList PrefObserverList; + typedef base::hash_map PrefObserverMap; + + typedef std::list > PrefInitObserverList; + + const PrefObserverMap* pref_observers() const { return &pref_observers_; } + + private: + // For the given pref_name, fire any observer of the pref. Virtual so it can + // be mocked for unit testing. + virtual void FireObservers(const std::string& path); + + // Weak reference; the notifier is owned by the PrefService. + PrefService* pref_service_; + + PrefObserverMap pref_observers_; + PrefInitObserverList init_observers_; + + base::ThreadChecker thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(PrefNotifierImpl); +}; + +#endif // BASE_PREFS_PREF_NOTIFIER_IMPL_H_ diff --git a/base/prefs/pref_notifier_impl_unittest.cc b/base/prefs/pref_notifier_impl_unittest.cc new file mode 100644 index 0000000000..29ea322e7e --- /dev/null +++ b/base/prefs/pref_notifier_impl_unittest.cc @@ -0,0 +1,227 @@ +// Copyright (c) 2011 The Chromium Authors. All 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/prefs/mock_pref_change_callback.h" +#include "base/prefs/pref_notifier_impl.h" +#include "base/prefs/pref_observer.h" +#include "base/prefs/pref_registry_simple.h" +#include "base/prefs/pref_service.h" +#include "base/prefs/pref_value_store.h" +#include "base/prefs/testing_pref_service.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::_; +using testing::Field; +using testing::Invoke; +using testing::Mock; +using testing::Truly; + +namespace { + +const char kChangedPref[] = "changed_pref"; +const char kUnchangedPref[] = "unchanged_pref"; + +class MockPrefInitObserver { + public: + MOCK_METHOD1(OnInitializationCompleted, void(bool)); +}; + +// This is an unmodified PrefNotifierImpl, except we make +// OnPreferenceChanged public for tests. +class TestingPrefNotifierImpl : public PrefNotifierImpl { + public: + explicit TestingPrefNotifierImpl(PrefService* service) + : PrefNotifierImpl(service) { + } + + // Make public for tests. + using PrefNotifierImpl::OnPreferenceChanged; +}; + +// Mock PrefNotifier that allows tracking of observers and notifications. +class MockPrefNotifier : public PrefNotifierImpl { + public: + explicit MockPrefNotifier(PrefService* pref_service) + : PrefNotifierImpl(pref_service) {} + virtual ~MockPrefNotifier() {} + + MOCK_METHOD1(FireObservers, void(const std::string& path)); + + size_t CountObserver(const char* path, PrefObserver* obs) { + PrefObserverMap::const_iterator observer_iterator = + pref_observers()->find(path); + if (observer_iterator == pref_observers()->end()) + return false; + + PrefObserverList* observer_list = observer_iterator->second; + PrefObserverList::Iterator it(*observer_list); + PrefObserver* existing_obs; + size_t count = 0; + while ((existing_obs = it.GetNext()) != NULL) { + if (existing_obs == obs) + count++; + } + + return count; + } + + // Make public for tests below. + using PrefNotifierImpl::OnPreferenceChanged; + using PrefNotifierImpl::OnInitializationCompleted; +}; + +class PrefObserverMock : public PrefObserver { + public: + PrefObserverMock() {} + virtual ~PrefObserverMock() {} + + MOCK_METHOD2(OnPreferenceChanged, void(PrefService*, const std::string&)); + + void Expect(PrefService* prefs, + const std::string& pref_name, + const base::Value* value) { + EXPECT_CALL(*this, OnPreferenceChanged(prefs, pref_name)) + .With(PrefValueMatches(prefs, pref_name, value)); + } +}; + +// Test fixture class. +class PrefNotifierTest : public testing::Test { + protected: + virtual void SetUp() { + pref_service_.registry()->RegisterBooleanPref(kChangedPref, true); + pref_service_.registry()->RegisterBooleanPref(kUnchangedPref, true); + } + + TestingPrefServiceSimple pref_service_; + + PrefObserverMock obs1_; + PrefObserverMock obs2_; +}; + +TEST_F(PrefNotifierTest, OnPreferenceChanged) { + MockPrefNotifier notifier(&pref_service_); + EXPECT_CALL(notifier, FireObservers(kChangedPref)).Times(1); + notifier.OnPreferenceChanged(kChangedPref); +} + +TEST_F(PrefNotifierTest, OnInitializationCompleted) { + MockPrefNotifier notifier(&pref_service_); + MockPrefInitObserver observer; + notifier.AddInitObserver( + base::Bind(&MockPrefInitObserver::OnInitializationCompleted, + base::Unretained(&observer))); + EXPECT_CALL(observer, OnInitializationCompleted(true)); + notifier.OnInitializationCompleted(true); +} + +TEST_F(PrefNotifierTest, AddAndRemovePrefObservers) { + const char pref_name[] = "homepage"; + const char pref_name2[] = "proxy"; + + MockPrefNotifier notifier(&pref_service_); + notifier.AddPrefObserver(pref_name, &obs1_); + ASSERT_EQ(1u, notifier.CountObserver(pref_name, &obs1_)); + ASSERT_EQ(0u, notifier.CountObserver(pref_name2, &obs1_)); + ASSERT_EQ(0u, notifier.CountObserver(pref_name, &obs2_)); + ASSERT_EQ(0u, notifier.CountObserver(pref_name2, &obs2_)); + + // Re-adding the same observer for the same pref doesn't change anything. + // Skip this in debug mode, since it hits a DCHECK and death tests aren't + // thread-safe. +#if defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON) + notifier.AddPrefObserver(pref_name, &obs1_); + ASSERT_EQ(1u, notifier.CountObserver(pref_name, &obs1_)); + ASSERT_EQ(0u, notifier.CountObserver(pref_name2, &obs1_)); + ASSERT_EQ(0u, notifier.CountObserver(pref_name, &obs2_)); + ASSERT_EQ(0u, notifier.CountObserver(pref_name2, &obs2_)); +#endif + + // Ensure that we can add the same observer to a different pref. + notifier.AddPrefObserver(pref_name2, &obs1_); + ASSERT_EQ(1u, notifier.CountObserver(pref_name, &obs1_)); + ASSERT_EQ(1u, notifier.CountObserver(pref_name2, &obs1_)); + ASSERT_EQ(0u, notifier.CountObserver(pref_name, &obs2_)); + ASSERT_EQ(0u, notifier.CountObserver(pref_name2, &obs2_)); + + // Ensure that we can add another observer to the same pref. + notifier.AddPrefObserver(pref_name, &obs2_); + ASSERT_EQ(1u, notifier.CountObserver(pref_name, &obs1_)); + ASSERT_EQ(1u, notifier.CountObserver(pref_name2, &obs1_)); + ASSERT_EQ(1u, notifier.CountObserver(pref_name, &obs2_)); + ASSERT_EQ(0u, notifier.CountObserver(pref_name2, &obs2_)); + + // Ensure that we can remove all observers, and that removing a non-existent + // observer is harmless. + notifier.RemovePrefObserver(pref_name, &obs1_); + ASSERT_EQ(0u, notifier.CountObserver(pref_name, &obs1_)); + ASSERT_EQ(1u, notifier.CountObserver(pref_name2, &obs1_)); + ASSERT_EQ(1u, notifier.CountObserver(pref_name, &obs2_)); + ASSERT_EQ(0u, notifier.CountObserver(pref_name2, &obs2_)); + + notifier.RemovePrefObserver(pref_name, &obs2_); + ASSERT_EQ(0u, notifier.CountObserver(pref_name, &obs1_)); + ASSERT_EQ(1u, notifier.CountObserver(pref_name2, &obs1_)); + ASSERT_EQ(0u, notifier.CountObserver(pref_name, &obs2_)); + ASSERT_EQ(0u, notifier.CountObserver(pref_name2, &obs2_)); + + notifier.RemovePrefObserver(pref_name, &obs1_); + ASSERT_EQ(0u, notifier.CountObserver(pref_name, &obs1_)); + ASSERT_EQ(1u, notifier.CountObserver(pref_name2, &obs1_)); + ASSERT_EQ(0u, notifier.CountObserver(pref_name, &obs2_)); + ASSERT_EQ(0u, notifier.CountObserver(pref_name2, &obs2_)); + + notifier.RemovePrefObserver(pref_name2, &obs1_); + ASSERT_EQ(0u, notifier.CountObserver(pref_name, &obs1_)); + ASSERT_EQ(0u, notifier.CountObserver(pref_name2, &obs1_)); + ASSERT_EQ(0u, notifier.CountObserver(pref_name, &obs2_)); + ASSERT_EQ(0u, notifier.CountObserver(pref_name2, &obs2_)); +} + +TEST_F(PrefNotifierTest, FireObservers) { + TestingPrefNotifierImpl notifier(&pref_service_); + notifier.AddPrefObserver(kChangedPref, &obs1_); + notifier.AddPrefObserver(kUnchangedPref, &obs1_); + + EXPECT_CALL(obs1_, OnPreferenceChanged(&pref_service_, kChangedPref)); + EXPECT_CALL(obs2_, OnPreferenceChanged(_, _)).Times(0); + notifier.OnPreferenceChanged(kChangedPref); + Mock::VerifyAndClearExpectations(&obs1_); + Mock::VerifyAndClearExpectations(&obs2_); + + notifier.AddPrefObserver(kChangedPref, &obs2_); + notifier.AddPrefObserver(kUnchangedPref, &obs2_); + + EXPECT_CALL(obs1_, OnPreferenceChanged(&pref_service_, kChangedPref)); + EXPECT_CALL(obs2_, OnPreferenceChanged(&pref_service_, kChangedPref)); + notifier.OnPreferenceChanged(kChangedPref); + Mock::VerifyAndClearExpectations(&obs1_); + Mock::VerifyAndClearExpectations(&obs2_); + + // Make sure removing an observer from one pref doesn't affect anything else. + notifier.RemovePrefObserver(kChangedPref, &obs1_); + + EXPECT_CALL(obs1_, OnPreferenceChanged(_, _)).Times(0); + EXPECT_CALL(obs2_, OnPreferenceChanged(&pref_service_, kChangedPref)); + notifier.OnPreferenceChanged(kChangedPref); + Mock::VerifyAndClearExpectations(&obs1_); + Mock::VerifyAndClearExpectations(&obs2_); + + // Make sure removing an observer entirely doesn't affect anything else. + notifier.RemovePrefObserver(kUnchangedPref, &obs1_); + + EXPECT_CALL(obs1_, OnPreferenceChanged(_, _)).Times(0); + EXPECT_CALL(obs2_, OnPreferenceChanged(&pref_service_, kChangedPref)); + notifier.OnPreferenceChanged(kChangedPref); + Mock::VerifyAndClearExpectations(&obs1_); + Mock::VerifyAndClearExpectations(&obs2_); + + notifier.RemovePrefObserver(kChangedPref, &obs2_); + notifier.RemovePrefObserver(kUnchangedPref, &obs2_); +} + +} // namespace diff --git a/base/prefs/pref_observer.h b/base/prefs/pref_observer.h new file mode 100644 index 0000000000..5d8f5b63dc --- /dev/null +++ b/base/prefs/pref_observer.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_PREFS_PREF_OBSERVER_H_ +#define BASE_PREFS_PREF_OBSERVER_H_ + +#include + +class PrefService; + +// Used internally to the Prefs subsystem to pass preference change +// notifications between PrefService, PrefNotifierImpl and +// PrefChangeRegistrar. +class PrefObserver { + public: + virtual void OnPreferenceChanged(PrefService* service, + const std::string& pref_name) = 0; +}; + +#endif // BASE_PREFS_PREF_OBSERVER_H_ diff --git a/base/prefs/pref_registry.cc b/base/prefs/pref_registry.cc new file mode 100644 index 0000000000..31d788d2f3 --- /dev/null +++ b/base/prefs/pref_registry.cc @@ -0,0 +1,63 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/prefs/pref_registry.h" + +#include "base/logging.h" +#include "base/prefs/default_pref_store.h" +#include "base/prefs/pref_store.h" +#include "base/values.h" + +PrefRegistry::PrefRegistry() + : defaults_(new DefaultPrefStore()) { +} + +PrefRegistry::~PrefRegistry() { +} + +scoped_refptr PrefRegistry::defaults() { + return defaults_.get(); +} + +PrefRegistry::const_iterator PrefRegistry::begin() const { + return defaults_->begin(); +} + +PrefRegistry::const_iterator PrefRegistry::end() const { + return defaults_->end(); +} + +void PrefRegistry::SetDefaultPrefValue(const char* pref_name, + base::Value* value) { + DCHECK(value); + if (DCHECK_IS_ON()) { + const base::Value* current_value = NULL; + DCHECK(defaults_->GetValue(pref_name, ¤t_value)) + << "Setting default for unregistered pref: " << pref_name; + DCHECK(value->IsType(current_value->GetType())) + << "Wrong type for new default: " << pref_name; + } + + defaults_->ReplaceDefaultValue(pref_name, make_scoped_ptr(value)); +} + +void PrefRegistry::SetRegistrationCallback( + const RegistrationCallback& callback) { + registration_callback_ = callback; +} + +void PrefRegistry::RegisterPreference(const char* path, + base::Value* default_value) { + base::Value::Type orig_type = default_value->GetType(); + DCHECK(orig_type != base::Value::TYPE_NULL && + orig_type != base::Value::TYPE_BINARY) << + "invalid preference type: " << orig_type; + DCHECK(!defaults_->GetValue(path, NULL)) << + "Trying to register a previously registered pref: " << path; + + defaults_->SetDefaultValue(path, make_scoped_ptr(default_value)); + + if (!registration_callback_.is_null()) + registration_callback_.Run(path, default_value); +} diff --git a/base/prefs/pref_registry.h b/base/prefs/pref_registry.h new file mode 100644 index 0000000000..6c7eac9f59 --- /dev/null +++ b/base/prefs/pref_registry.h @@ -0,0 +1,72 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_PREFS_PREF_REGISTRY_H_ +#define BASE_PREFS_PREF_REGISTRY_H_ + +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/prefs/base_prefs_export.h" +#include "base/prefs/pref_value_map.h" + +namespace base { +class Value; +} + +class DefaultPrefStore; +class PrefStore; + +// Preferences need to be registered with a type and default value +// before they are used. +// +// The way you use a PrefRegistry is that you register all required +// preferences on it (via one of its subclasses), then pass it as a +// construction parameter to PrefService. +// +// Currently, registrations after constructing the PrefService will +// also work, but this is being deprecated. +class BASE_PREFS_EXPORT PrefRegistry : public base::RefCounted { + public: + typedef PrefValueMap::const_iterator const_iterator; + typedef base::Callback RegistrationCallback; + + PrefRegistry(); + + // Gets the registered defaults. + scoped_refptr defaults(); + + // Allows iteration over defaults. + const_iterator begin() const; + const_iterator end() const; + + // Changes the default value for a preference. Takes ownership of |value|. + // + // |pref_name| must be a previously registered preference. + void SetDefaultPrefValue(const char* pref_name, base::Value* value); + + // Exactly one callback can be set for registration. The callback + // will be invoked each time registration has been performed on this + // object. + // + // Calling this method after a callback has already been set will + // make the object forget the previous callback and use the new one + // instead. + void SetRegistrationCallback(const RegistrationCallback& callback); + + protected: + friend class base::RefCounted; + virtual ~PrefRegistry(); + + // Used by subclasses to register a default value for a preference. + void RegisterPreference(const char* path, base::Value* default_value); + + scoped_refptr defaults_; + + private: + RegistrationCallback registration_callback_; + + DISALLOW_COPY_AND_ASSIGN(PrefRegistry); +}; + +#endif // BASE_PREFS_PREF_REGISTRY_H_ diff --git a/base/prefs/pref_registry_simple.cc b/base/prefs/pref_registry_simple.cc new file mode 100644 index 0000000000..c068b4ba43 --- /dev/null +++ b/base/prefs/pref_registry_simple.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/prefs/pref_registry_simple.h" + +#include "base/files/file_path.h" +#include "base/strings/string_number_conversions.h" +#include "base/values.h" + +PrefRegistrySimple::PrefRegistrySimple() { +} + +PrefRegistrySimple::~PrefRegistrySimple() { +} + +void PrefRegistrySimple::RegisterBooleanPref(const char* path, + bool default_value) { + RegisterPreference(path, base::Value::CreateBooleanValue(default_value)); +} + +void PrefRegistrySimple::RegisterIntegerPref(const char* path, + int default_value) { + RegisterPreference(path, base::Value::CreateIntegerValue(default_value)); +} + +void PrefRegistrySimple::RegisterDoublePref(const char* path, + double default_value) { + RegisterPreference(path, base::Value::CreateDoubleValue(default_value)); +} + +void PrefRegistrySimple::RegisterStringPref(const char* path, + const std::string& default_value) { + RegisterPreference(path, base::Value::CreateStringValue(default_value)); +} + +void PrefRegistrySimple::RegisterFilePathPref( + const char* path, + const base::FilePath& default_value) { + RegisterPreference(path, + base::Value::CreateStringValue(default_value.value())); +} + +void PrefRegistrySimple::RegisterListPref(const char* path) { + RegisterPreference(path, new base::ListValue()); +} + +void PrefRegistrySimple::RegisterListPref(const char* path, + base::ListValue* default_value) { + RegisterPreference(path, default_value); +} + +void PrefRegistrySimple::RegisterDictionaryPref(const char* path) { + RegisterPreference(path, new base::DictionaryValue()); +} + +void PrefRegistrySimple::RegisterDictionaryPref( + const char* path, + base::DictionaryValue* default_value) { + RegisterPreference(path, default_value); +} + +void PrefRegistrySimple::RegisterInt64Pref(const char* path, + int64 default_value) { + RegisterPreference( + path, base::Value::CreateStringValue(base::Int64ToString(default_value))); +} diff --git a/base/prefs/pref_registry_simple.h b/base/prefs/pref_registry_simple.h new file mode 100644 index 0000000000..50b646711f --- /dev/null +++ b/base/prefs/pref_registry_simple.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_PREFS_PREF_REGISTRY_SIMPLE_H_ +#define BASE_PREFS_PREF_REGISTRY_SIMPLE_H_ + +#include + +#include "base/prefs/base_prefs_export.h" +#include "base/prefs/pref_registry.h" + +namespace base { +class DictionaryValue; +class FilePath; +class ListValue; +} + +// A simple implementation of PrefRegistry. +class BASE_PREFS_EXPORT PrefRegistrySimple : public PrefRegistry { + public: + PrefRegistrySimple(); + + void RegisterBooleanPref(const char* path, bool default_value); + void RegisterIntegerPref(const char* path, int default_value); + void RegisterDoublePref(const char* path, double default_value); + void RegisterStringPref(const char* path, const std::string& default_value); + void RegisterFilePathPref(const char* path, + const base::FilePath& default_value); + void RegisterListPref(const char* path); + void RegisterDictionaryPref(const char* path); + void RegisterListPref(const char* path, base::ListValue* default_value); + void RegisterDictionaryPref(const char* path, + base::DictionaryValue* default_value); + void RegisterInt64Pref(const char* path, + int64 default_value); + + private: + virtual ~PrefRegistrySimple(); + + DISALLOW_COPY_AND_ASSIGN(PrefRegistrySimple); +}; + +#endif // BASE_PREFS_PREF_REGISTRY_SIMPLE_H_ diff --git a/base/prefs/pref_service.cc b/base/prefs/pref_service.cc new file mode 100644 index 0000000000..046af915b3 --- /dev/null +++ b/base/prefs/pref_service.cc @@ -0,0 +1,591 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/prefs/pref_service.h" + +#include + +#include "base/bind.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/metrics/histogram.h" +#include "base/prefs/default_pref_store.h" +#include "base/prefs/pref_notifier_impl.h" +#include "base/prefs/pref_registry.h" +#include "base/prefs/pref_value_store.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/value_conversions.h" +#include "build/build_config.h" + +namespace { + +class ReadErrorHandler : public PersistentPrefStore::ReadErrorDelegate { + public: + ReadErrorHandler(base::Callback cb) + : callback_(cb) {} + + virtual void OnError(PersistentPrefStore::PrefReadError error) OVERRIDE { + callback_.Run(error); + } + + private: + base::Callback callback_; +}; + +} // namespace + +PrefService::PrefService( + PrefNotifierImpl* pref_notifier, + PrefValueStore* pref_value_store, + PersistentPrefStore* user_prefs, + PrefRegistry* pref_registry, + base::Callback + read_error_callback, + bool async) + : pref_notifier_(pref_notifier), + pref_value_store_(pref_value_store), + pref_registry_(pref_registry), + user_pref_store_(user_prefs), + read_error_callback_(read_error_callback) { + pref_notifier_->SetPrefService(this); + + pref_registry_->SetRegistrationCallback( + base::Bind(&PrefService::AddRegisteredPreference, + base::Unretained(this))); + AddInitialPreferences(); + + InitFromStorage(async); +} + +PrefService::~PrefService() { + DCHECK(CalledOnValidThread()); + + // Remove our callback, setting a NULL one. + pref_registry_->SetRegistrationCallback(PrefRegistry::RegistrationCallback()); + + // Reset pointers so accesses after destruction reliably crash. + pref_value_store_.reset(); + pref_registry_ = NULL; + user_pref_store_ = NULL; + pref_notifier_.reset(); +} + +void PrefService::InitFromStorage(bool async) { + if (!async) { + read_error_callback_.Run(user_pref_store_->ReadPrefs()); + } else { + // Guarantee that initialization happens after this function returned. + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&PersistentPrefStore::ReadPrefsAsync, + user_pref_store_.get(), + new ReadErrorHandler(read_error_callback_))); + } +} + +bool PrefService::ReloadPersistentPrefs() { + return user_pref_store_->ReadPrefs() == + PersistentPrefStore::PREF_READ_ERROR_NONE; +} + +void PrefService::CommitPendingWrite() { + DCHECK(CalledOnValidThread()); + user_pref_store_->CommitPendingWrite(); +} + +bool PrefService::GetBoolean(const char* path) const { + DCHECK(CalledOnValidThread()); + + bool result = false; + + const base::Value* value = GetPreferenceValue(path); + if (!value) { + NOTREACHED() << "Trying to read an unregistered pref: " << path; + return result; + } + bool rv = value->GetAsBoolean(&result); + DCHECK(rv); + return result; +} + +int PrefService::GetInteger(const char* path) const { + DCHECK(CalledOnValidThread()); + + int result = 0; + + const base::Value* value = GetPreferenceValue(path); + if (!value) { + NOTREACHED() << "Trying to read an unregistered pref: " << path; + return result; + } + bool rv = value->GetAsInteger(&result); + DCHECK(rv); + return result; +} + +double PrefService::GetDouble(const char* path) const { + DCHECK(CalledOnValidThread()); + + double result = 0.0; + + const base::Value* value = GetPreferenceValue(path); + if (!value) { + NOTREACHED() << "Trying to read an unregistered pref: " << path; + return result; + } + bool rv = value->GetAsDouble(&result); + DCHECK(rv); + return result; +} + +std::string PrefService::GetString(const char* path) const { + DCHECK(CalledOnValidThread()); + + std::string result; + + const base::Value* value = GetPreferenceValue(path); + if (!value) { + NOTREACHED() << "Trying to read an unregistered pref: " << path; + return result; + } + bool rv = value->GetAsString(&result); + DCHECK(rv); + return result; +} + +base::FilePath PrefService::GetFilePath(const char* path) const { + DCHECK(CalledOnValidThread()); + + base::FilePath result; + + const base::Value* value = GetPreferenceValue(path); + if (!value) { + NOTREACHED() << "Trying to read an unregistered pref: " << path; + return base::FilePath(result); + } + bool rv = base::GetValueAsFilePath(*value, &result); + DCHECK(rv); + return result; +} + +bool PrefService::HasPrefPath(const char* path) const { + const Preference* pref = FindPreference(path); + return pref && !pref->IsDefaultValue(); +} + +base::DictionaryValue* PrefService::GetPreferenceValues() const { + DCHECK(CalledOnValidThread()); + base::DictionaryValue* out = new base::DictionaryValue; + PrefRegistry::const_iterator i = pref_registry_->begin(); + for (; i != pref_registry_->end(); ++i) { + const base::Value* value = GetPreferenceValue(i->first); + DCHECK(value); + out->Set(i->first, value->DeepCopy()); + } + return out; +} + +const PrefService::Preference* PrefService::FindPreference( + const char* pref_name) const { + DCHECK(CalledOnValidThread()); + PreferenceMap::iterator it = prefs_map_.find(pref_name); + if (it != prefs_map_.end()) + return &(it->second); + const base::Value* default_value = NULL; + if (!pref_registry_->defaults()->GetValue(pref_name, &default_value)) + return NULL; + it = prefs_map_.insert( + std::make_pair(pref_name, Preference( + this, pref_name, default_value->GetType()))).first; + return &(it->second); +} + +bool PrefService::ReadOnly() const { + return user_pref_store_->ReadOnly(); +} + +PrefService::PrefInitializationStatus PrefService::GetInitializationStatus() + const { + if (!user_pref_store_->IsInitializationComplete()) + return INITIALIZATION_STATUS_WAITING; + + switch (user_pref_store_->GetReadError()) { + case PersistentPrefStore::PREF_READ_ERROR_NONE: + return INITIALIZATION_STATUS_SUCCESS; + case PersistentPrefStore::PREF_READ_ERROR_NO_FILE: + return INITIALIZATION_STATUS_CREATED_NEW_PREF_STORE; + default: + return INITIALIZATION_STATUS_ERROR; + } +} + +bool PrefService::IsManagedPreference(const char* pref_name) const { + const Preference* pref = FindPreference(pref_name); + return pref && pref->IsManaged(); +} + +bool PrefService::IsUserModifiablePreference(const char* pref_name) const { + const Preference* pref = FindPreference(pref_name); + return pref && pref->IsUserModifiable(); +} + +const base::DictionaryValue* PrefService::GetDictionary( + const char* path) const { + DCHECK(CalledOnValidThread()); + + const base::Value* value = GetPreferenceValue(path); + if (!value) { + NOTREACHED() << "Trying to read an unregistered pref: " << path; + return NULL; + } + if (value->GetType() != base::Value::TYPE_DICTIONARY) { + NOTREACHED(); + return NULL; + } + return static_cast(value); +} + +const base::Value* PrefService::GetUserPrefValue(const char* path) const { + DCHECK(CalledOnValidThread()); + + const Preference* pref = FindPreference(path); + if (!pref) { + NOTREACHED() << "Trying to get an unregistered pref: " << path; + return NULL; + } + + // Look for an existing preference in the user store. If it doesn't + // exist, return NULL. + base::Value* value = NULL; + if (!user_pref_store_->GetMutableValue(path, &value)) + return NULL; + + if (!value->IsType(pref->GetType())) { + NOTREACHED() << "Pref value type doesn't match registered type."; + return NULL; + } + + return value; +} + +void PrefService::SetDefaultPrefValue(const char* path, + base::Value* value) { + DCHECK(CalledOnValidThread()); + pref_registry_->SetDefaultPrefValue(path, value); +} + +const base::Value* PrefService::GetDefaultPrefValue(const char* path) const { + DCHECK(CalledOnValidThread()); + // Lookup the preference in the default store. + const base::Value* value = NULL; + if (!pref_registry_->defaults()->GetValue(path, &value)) { + NOTREACHED() << "Default value missing for pref: " << path; + return NULL; + } + return value; +} + +const base::ListValue* PrefService::GetList(const char* path) const { + DCHECK(CalledOnValidThread()); + + const base::Value* value = GetPreferenceValue(path); + if (!value) { + NOTREACHED() << "Trying to read an unregistered pref: " << path; + return NULL; + } + if (value->GetType() != base::Value::TYPE_LIST) { + NOTREACHED(); + return NULL; + } + return static_cast(value); +} + +void PrefService::AddPrefObserver(const char* path, PrefObserver* obs) { + pref_notifier_->AddPrefObserver(path, obs); +} + +void PrefService::RemovePrefObserver(const char* path, PrefObserver* obs) { + pref_notifier_->RemovePrefObserver(path, obs); +} + +void PrefService::AddPrefInitObserver(base::Callback obs) { + pref_notifier_->AddInitObserver(obs); +} + +PrefRegistry* PrefService::DeprecatedGetPrefRegistry() { + return pref_registry_.get(); +} + +void PrefService::AddInitialPreferences() { + for (PrefRegistry::const_iterator it = pref_registry_->begin(); + it != pref_registry_->end(); + ++it) { + AddRegisteredPreference(it->first.c_str(), it->second); + } +} + +// TODO(joi): Once MarkNeedsEmptyValue is gone, we can probably +// completely get rid of this method. There will be one difference in +// semantics; currently all registered preferences are stored right +// away in the prefs_map_, if we remove this they would be stored only +// opportunistically. +void PrefService::AddRegisteredPreference(const char* path, + base::Value* default_value) { + DCHECK(CalledOnValidThread()); + + // For ListValue and DictionaryValue with non empty default, empty value + // for |path| needs to be persisted in |user_pref_store_|. So that + // non empty default is not used when user sets an empty ListValue or + // DictionaryValue. + bool needs_empty_value = false; + base::Value::Type orig_type = default_value->GetType(); + if (orig_type == base::Value::TYPE_LIST) { + const base::ListValue* list = NULL; + if (default_value->GetAsList(&list) && !list->empty()) + needs_empty_value = true; + } else if (orig_type == base::Value::TYPE_DICTIONARY) { + const base::DictionaryValue* dict = NULL; + if (default_value->GetAsDictionary(&dict) && !dict->empty()) + needs_empty_value = true; + } + if (needs_empty_value) + user_pref_store_->MarkNeedsEmptyValue(path); +} + +void PrefService::ClearPref(const char* path) { + DCHECK(CalledOnValidThread()); + + const Preference* pref = FindPreference(path); + if (!pref) { + NOTREACHED() << "Trying to clear an unregistered pref: " << path; + return; + } + user_pref_store_->RemoveValue(path); +} + +void PrefService::Set(const char* path, const base::Value& value) { + SetUserPrefValue(path, value.DeepCopy()); +} + +void PrefService::SetBoolean(const char* path, bool value) { + SetUserPrefValue(path, base::Value::CreateBooleanValue(value)); +} + +void PrefService::SetInteger(const char* path, int value) { + SetUserPrefValue(path, base::Value::CreateIntegerValue(value)); +} + +void PrefService::SetDouble(const char* path, double value) { + SetUserPrefValue(path, base::Value::CreateDoubleValue(value)); +} + +void PrefService::SetString(const char* path, const std::string& value) { + SetUserPrefValue(path, base::Value::CreateStringValue(value)); +} + +void PrefService::SetFilePath(const char* path, const base::FilePath& value) { + SetUserPrefValue(path, base::CreateFilePathValue(value)); +} + +void PrefService::SetInt64(const char* path, int64 value) { + SetUserPrefValue(path, + base::Value::CreateStringValue(base::Int64ToString(value))); +} + +int64 PrefService::GetInt64(const char* path) const { + DCHECK(CalledOnValidThread()); + + const base::Value* value = GetPreferenceValue(path); + if (!value) { + NOTREACHED() << "Trying to read an unregistered pref: " << path; + return 0; + } + std::string result("0"); + bool rv = value->GetAsString(&result); + DCHECK(rv); + + int64 val; + base::StringToInt64(result, &val); + return val; +} + +void PrefService::SetUint64(const char* path, uint64 value) { + SetUserPrefValue(path, + base::Value::CreateStringValue(base::Uint64ToString(value))); +} + +uint64 PrefService::GetUint64(const char* path) const { + DCHECK(CalledOnValidThread()); + + const base::Value* value = GetPreferenceValue(path); + if (!value) { + NOTREACHED() << "Trying to read an unregistered pref: " << path; + return 0; + } + std::string result("0"); + bool rv = value->GetAsString(&result); + DCHECK(rv); + + uint64 val; + base::StringToUint64(result, &val); + return val; +} + +base::Value* PrefService::GetMutableUserPref(const char* path, + base::Value::Type type) { + CHECK(type == base::Value::TYPE_DICTIONARY || type == base::Value::TYPE_LIST); + DCHECK(CalledOnValidThread()); + + const Preference* pref = FindPreference(path); + if (!pref) { + NOTREACHED() << "Trying to get an unregistered pref: " << path; + return NULL; + } + if (pref->GetType() != type) { + NOTREACHED() << "Wrong type for GetMutableValue: " << path; + return NULL; + } + + // Look for an existing preference in the user store. If it doesn't + // exist or isn't the correct type, create a new user preference. + base::Value* value = NULL; + if (!user_pref_store_->GetMutableValue(path, &value) || + !value->IsType(type)) { + if (type == base::Value::TYPE_DICTIONARY) { + value = new base::DictionaryValue; + } else if (type == base::Value::TYPE_LIST) { + value = new base::ListValue; + } else { + NOTREACHED(); + } + user_pref_store_->SetValueSilently(path, value); + } + return value; +} + +void PrefService::ReportUserPrefChanged(const std::string& key) { + user_pref_store_->ReportValueChanged(key); +} + +void PrefService::SetUserPrefValue(const char* path, base::Value* new_value) { + scoped_ptr owned_value(new_value); + DCHECK(CalledOnValidThread()); + + const Preference* pref = FindPreference(path); + if (!pref) { + NOTREACHED() << "Trying to write an unregistered pref: " << path; + return; + } + if (pref->GetType() != new_value->GetType()) { + NOTREACHED() << "Trying to set pref " << path + << " of type " << pref->GetType() + << " to value of type " << new_value->GetType(); + return; + } + + user_pref_store_->SetValue(path, owned_value.release()); +} + +void PrefService::UpdateCommandLinePrefStore(PrefStore* command_line_store) { + pref_value_store_->UpdateCommandLinePrefStore(command_line_store); +} + +/////////////////////////////////////////////////////////////////////////////// +// PrefService::Preference + +PrefService::Preference::Preference(const PrefService* service, + const char* name, + base::Value::Type type) + : name_(name), + type_(type), + pref_service_(service) { + DCHECK(name); + DCHECK(service); +} + +const std::string PrefService::Preference::name() const { + return name_; +} + +base::Value::Type PrefService::Preference::GetType() const { + return type_; +} + +const base::Value* PrefService::Preference::GetValue() const { + const base::Value* result= pref_service_->GetPreferenceValue(name_); + DCHECK(result) << "Must register pref before getting its value"; + return result; +} + +const base::Value* PrefService::Preference::GetRecommendedValue() const { + DCHECK(pref_service_->FindPreference(name_.c_str())) << + "Must register pref before getting its value"; + + const base::Value* found_value = NULL; + if (pref_value_store()->GetRecommendedValue(name_, type_, &found_value)) { + DCHECK(found_value->IsType(type_)); + return found_value; + } + + // The pref has no recommended value. + return NULL; +} + +bool PrefService::Preference::IsManaged() const { + return pref_value_store()->PrefValueInManagedStore(name_.c_str()); +} + +bool PrefService::Preference::IsRecommended() const { + return pref_value_store()->PrefValueFromRecommendedStore(name_.c_str()); +} + +bool PrefService::Preference::HasExtensionSetting() const { + return pref_value_store()->PrefValueInExtensionStore(name_.c_str()); +} + +bool PrefService::Preference::HasUserSetting() const { + return pref_value_store()->PrefValueInUserStore(name_.c_str()); +} + +bool PrefService::Preference::IsExtensionControlled() const { + return pref_value_store()->PrefValueFromExtensionStore(name_.c_str()); +} + +bool PrefService::Preference::IsUserControlled() const { + return pref_value_store()->PrefValueFromUserStore(name_.c_str()); +} + +bool PrefService::Preference::IsDefaultValue() const { + return pref_value_store()->PrefValueFromDefaultStore(name_.c_str()); +} + +bool PrefService::Preference::IsUserModifiable() const { + return pref_value_store()->PrefValueUserModifiable(name_.c_str()); +} + +bool PrefService::Preference::IsExtensionModifiable() const { + return pref_value_store()->PrefValueExtensionModifiable(name_.c_str()); +} + +const base::Value* PrefService::GetPreferenceValue( + const std::string& path) const { + DCHECK(CalledOnValidThread()); + const base::Value* default_value = NULL; + if (pref_registry_->defaults()->GetValue(path, &default_value)) { + const base::Value* found_value = NULL; + base::Value::Type default_type = default_value->GetType(); + if (pref_value_store_->GetValue(path, default_type, &found_value)) { + DCHECK(found_value->IsType(default_type)); + return found_value; + } else { + // Every registered preference has at least a default value. + NOTREACHED() << "no valid value found for registered pref " << path; + } + } + + return NULL; +} diff --git a/base/prefs/pref_service.h b/base/prefs/pref_service.h new file mode 100644 index 0000000000..8af042f40b --- /dev/null +++ b/base/prefs/pref_service.h @@ -0,0 +1,354 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This provides a way to access the application's current preferences. + +// Chromium settings and storage represent user-selected preferences and +// information and MUST not be extracted, overwritten or modified except +// through Chromium defined APIs. + +#ifndef BASE_PREFS_PREF_SERVICE_H_ +#define BASE_PREFS_PREF_SERVICE_H_ + +#include +#include + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/containers/hash_tables.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/observer_list.h" +#include "base/prefs/base_prefs_export.h" +#include "base/prefs/persistent_pref_store.h" +#include "base/threading/non_thread_safe.h" +#include "base/values.h" + +class PrefNotifier; +class PrefNotifierImpl; +class PrefObserver; +class PrefRegistry; +class PrefValueStore; +class PrefStore; + +namespace base { +class FilePath; +} + +namespace subtle { +class PrefMemberBase; +class ScopedUserPrefUpdateBase; +} + +// Base class for PrefServices. You can use the base class to read and +// interact with preferences, but not to register new preferences; for +// that see e.g. PrefRegistrySimple. +// +// Settings and storage accessed through this class represent +// user-selected preferences and information and MUST not be +// extracted, overwritten or modified except through the defined APIs. +class BASE_PREFS_EXPORT PrefService : public base::NonThreadSafe { + public: + enum PrefInitializationStatus { + INITIALIZATION_STATUS_WAITING, + INITIALIZATION_STATUS_SUCCESS, + INITIALIZATION_STATUS_CREATED_NEW_PREF_STORE, + INITIALIZATION_STATUS_ERROR + }; + + // A helper class to store all the information associated with a preference. + class BASE_PREFS_EXPORT Preference { + public: + // The type of the preference is determined by the type with which it is + // registered. This type needs to be a boolean, integer, double, string, + // dictionary (a branch), or list. You shouldn't need to construct this on + // your own; use the PrefService::Register*Pref methods instead. + Preference(const PrefService* service, + const char* name, + base::Value::Type type); + ~Preference() {} + + // Returns the name of the Preference (i.e., the key, e.g., + // browser.window_placement). + const std::string name() const; + + // Returns the registered type of the preference. + base::Value::Type GetType() const; + + // Returns the value of the Preference, falling back to the registered + // default value if no other has been set. + const base::Value* GetValue() const; + + // Returns the value recommended by the admin, if any. + const base::Value* GetRecommendedValue() const; + + // Returns true if the Preference is managed, i.e. set by an admin policy. + // Since managed prefs have the highest priority, this also indicates + // whether the pref is actually being controlled by the policy setting. + bool IsManaged() const; + + // Returns true if the Preference is recommended, i.e. set by an admin + // policy but the user is allowed to change it. + bool IsRecommended() const; + + // Returns true if the Preference has a value set by an extension, even if + // that value is being overridden by a higher-priority source. + bool HasExtensionSetting() const; + + // Returns true if the Preference has a user setting, even if that value is + // being overridden by a higher-priority source. + bool HasUserSetting() const; + + // Returns true if the Preference value is currently being controlled by an + // extension, and not by any higher-priority source. + bool IsExtensionControlled() const; + + // Returns true if the Preference value is currently being controlled by a + // user setting, and not by any higher-priority source. + bool IsUserControlled() const; + + // Returns true if the Preference is currently using its default value, + // and has not been set by any higher-priority source (even with the same + // value). + bool IsDefaultValue() const; + + // Returns true if the user can change the Preference value, which is the + // case if no higher-priority source than the user store controls the + // Preference. + bool IsUserModifiable() const; + + // Returns true if an extension can change the Preference value, which is + // the case if no higher-priority source than the extension store controls + // the Preference. + bool IsExtensionModifiable() const; + + private: + friend class PrefService; + + PrefValueStore* pref_value_store() const { + return pref_service_->pref_value_store_.get(); + } + + const std::string name_; + + const base::Value::Type type_; + + // Reference to the PrefService in which this pref was created. + const PrefService* pref_service_; + }; + + // You may wish to use PrefServiceBuilder or one of its subclasses + // for simplified construction. + PrefService( + PrefNotifierImpl* pref_notifier, + PrefValueStore* pref_value_store, + PersistentPrefStore* user_prefs, + PrefRegistry* pref_registry, + base::Callback + read_error_callback, + bool async); + virtual ~PrefService(); + + // Reloads the data from file. This should only be called when the importer + // is running during first run, and the main process may not change pref + // values while the importer process is running. Returns true on success. + bool ReloadPersistentPrefs(); + + // Lands pending writes to disk. This should only be used if we need to save + // immediately (basically, during shutdown). + void CommitPendingWrite(); + + // Returns true if the preference for the given preference name is available + // and is managed. + bool IsManagedPreference(const char* pref_name) const; + + // Returns |true| if a preference with the given name is available and its + // value can be changed by the user. + bool IsUserModifiablePreference(const char* pref_name) const; + + // Look up a preference. Returns NULL if the preference is not + // registered. + const PrefService::Preference* FindPreference(const char* path) const; + + // If the path is valid and the value at the end of the path matches the type + // specified, it will return the specified value. Otherwise, the default + // value (set when the pref was registered) will be returned. + bool GetBoolean(const char* path) const; + int GetInteger(const char* path) const; + double GetDouble(const char* path) const; + std::string GetString(const char* path) const; + base::FilePath GetFilePath(const char* path) const; + + // Returns the branch if it exists, or the registered default value otherwise. + // Note that |path| must point to a registered preference. In that case, these + // functions will never return NULL. + const base::DictionaryValue* GetDictionary( + const char* path) const; + const base::ListValue* GetList(const char* path) const; + + // Removes a user pref and restores the pref to its default value. + void ClearPref(const char* path); + + // If the path is valid (i.e., registered), update the pref value in the user + // prefs. + // To set the value of dictionary or list values in the pref tree use + // Set(), but to modify the value of a dictionary or list use either + // ListPrefUpdate or DictionaryPrefUpdate from scoped_user_pref_update.h. + void Set(const char* path, const base::Value& value); + void SetBoolean(const char* path, bool value); + void SetInteger(const char* path, int value); + void SetDouble(const char* path, double value); + void SetString(const char* path, const std::string& value); + void SetFilePath(const char* path, const base::FilePath& value); + + // Int64 helper methods that actually store the given value as a string. + // Note that if obtaining the named value via GetDictionary or GetList, the + // Value type will be TYPE_STRING. + void SetInt64(const char* path, int64 value); + int64 GetInt64(const char* path) const; + + // As above, but for unsigned values. + void SetUint64(const char* path, uint64 value); + uint64 GetUint64(const char* path) const; + + // Returns the value of the given preference, from the user pref store. If + // the preference is not set in the user pref store, returns NULL. + const base::Value* GetUserPrefValue(const char* path) const; + + // Changes the default value for a preference. Takes ownership of |value|. + // + // Will cause a pref change notification to be fired if this causes + // the effective value to change. + void SetDefaultPrefValue(const char* path, base::Value* value); + + // Returns the default value of the given preference. |path| must point to a + // registered preference. In that case, will never return NULL. + const base::Value* GetDefaultPrefValue(const char* path) const; + + // Returns true if a value has been set for the specified path. + // NOTE: this is NOT the same as FindPreference. In particular + // FindPreference returns whether RegisterXXX has been invoked, where as + // this checks if a value exists for the path. + bool HasPrefPath(const char* path) const; + + // Returns a dictionary with effective preference values. The ownership + // is passed to the caller. + base::DictionaryValue* GetPreferenceValues() const; + + bool ReadOnly() const; + + PrefInitializationStatus GetInitializationStatus() const; + + // Tell our PrefValueStore to update itself to |command_line_store|. + // Takes ownership of the store. + virtual void UpdateCommandLinePrefStore(PrefStore* command_line_store); + + // We run the callback once, when initialization completes. The bool + // parameter will be set to true for successful initialization, + // false for unsuccessful. + void AddPrefInitObserver(base::Callback callback); + + // Returns the PrefRegistry object for this service. You should not + // use this; the intent is for no registrations to take place after + // PrefService has been constructed. + PrefRegistry* DeprecatedGetPrefRegistry(); + + protected: + // Adds the registered preferences from the PrefRegistry instance + // passed to us at construction time. + void AddInitialPreferences(); + + // Updates local caches for a preference registered at |path|. The + // |default_value| must not be NULL as it determines the preference + // value's type. AddRegisteredPreference must not be called twice + // for the same path. + void AddRegisteredPreference(const char* path, + base::Value* default_value); + + // The PrefNotifier handles registering and notifying preference observers. + // It is created and owned by this PrefService. Subclasses may access it for + // unit testing. + scoped_ptr pref_notifier_; + + // The PrefValueStore provides prioritized preference values. It is owned by + // this PrefService. Subclasses may access it for unit testing. + scoped_ptr pref_value_store_; + + scoped_refptr pref_registry_; + + // Pref Stores and profile that we passed to the PrefValueStore. + scoped_refptr user_pref_store_; + + // Callback to call when a read error occurs. + base::Callback read_error_callback_; + + private: + // Hash map expected to be fastest here since it minimises expensive + // string comparisons. Order is unimportant, and deletions are rare. + // Confirmed on Android where this speeded Chrome startup by roughly 50ms + // vs. std::map, and by roughly 180ms vs. std::set of Preference pointers. + typedef base::hash_map PreferenceMap; + + // Give access to ReportUserPrefChanged() and GetMutableUserPref(). + friend class subtle::ScopedUserPrefUpdateBase; + + // Registration of pref change observers must be done using the + // PrefChangeRegistrar, which is declared as a friend here to grant it + // access to the otherwise protected members Add/RemovePrefObserver. + // PrefMember registers for preferences changes notification directly to + // avoid the storage overhead of the registrar, so its base class must be + // declared as a friend, too. + friend class PrefChangeRegistrar; + friend class subtle::PrefMemberBase; + + // These are protected so they can only be accessed by the friend + // classes listed above. + // + // If the pref at the given path changes, we call the observer's + // OnPreferenceChanged method. Note that observers should not call + // these methods directly but rather use a PrefChangeRegistrar to + // make sure the observer gets cleaned up properly. + // + // Virtual for testing. + virtual void AddPrefObserver(const char* path, PrefObserver* obs); + virtual void RemovePrefObserver(const char* path, PrefObserver* obs); + + // Sends notification of a changed preference. This needs to be called by + // a ScopedUserPrefUpdate if a DictionaryValue or ListValue is changed. + void ReportUserPrefChanged(const std::string& key); + + // Sets the value for this pref path in the user pref store and informs the + // PrefNotifier of the change. + void SetUserPrefValue(const char* path, base::Value* new_value); + + // Load preferences from storage, attempting to diagnose and handle errors. + // This should only be called from the constructor. + void InitFromStorage(bool async); + + // Used to set the value of dictionary or list values in the user pref store. + // This will create a dictionary or list if one does not exist in the user + // pref store. This method returns NULL only if you're requesting an + // unregistered pref or a non-dict/non-list pref. + // |type| may only be Values::TYPE_DICTIONARY or Values::TYPE_LIST and + // |path| must point to a registered preference of type |type|. + // Ownership of the returned value remains at the user pref store. + base::Value* GetMutableUserPref(const char* path, + base::Value::Type type); + + // GetPreferenceValue is the equivalent of FindPreference(path)->GetValue(), + // it has been added for performance. If is faster because it does + // not need to find or create a Preference object to get the + // value (GetValue() calls back though the preference service to + // actually get the value.). + const base::Value* GetPreferenceValue(const std::string& path) const; + + // Local cache of registered Preference objects. The pref_registry_ + // is authoritative with respect to what the types and default values + // of registered preferences are. + mutable PreferenceMap prefs_map_; + + DISALLOW_COPY_AND_ASSIGN(PrefService); +}; + +#endif // BASE_PREFS_PREF_SERVICE_H_ diff --git a/base/prefs/pref_service_builder.cc b/base/prefs/pref_service_builder.cc new file mode 100644 index 0000000000..b3d3533603 --- /dev/null +++ b/base/prefs/pref_service_builder.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 "base/prefs/pref_service_builder.h" + +#include "base/bind.h" +#include "base/prefs/default_pref_store.h" +#include "base/prefs/json_pref_store.h" +#include "base/prefs/pref_notifier_impl.h" +#include "base/prefs/pref_service.h" + +#include "base/prefs/pref_value_store.h" + +namespace { + +// Do-nothing default implementation. +void DoNothingHandleReadError(PersistentPrefStore::PrefReadError error) { +} + +} // namespace + +PrefServiceBuilder::PrefServiceBuilder() { + ResetDefaultState(); +} + +PrefServiceBuilder::~PrefServiceBuilder() { +} + +PrefServiceBuilder& PrefServiceBuilder::WithManagedPrefs(PrefStore* store) { + managed_prefs_ = store; + return *this; +} + +PrefServiceBuilder& PrefServiceBuilder::WithExtensionPrefs(PrefStore* store) { + extension_prefs_ = store; + return *this; +} + +PrefServiceBuilder& PrefServiceBuilder::WithCommandLinePrefs(PrefStore* store) { + command_line_prefs_ = store; + return *this; +} + +PrefServiceBuilder& PrefServiceBuilder::WithUserPrefs( + PersistentPrefStore* store) { + user_prefs_ = store; + return *this; +} + +PrefServiceBuilder& PrefServiceBuilder::WithRecommendedPrefs(PrefStore* store) { + recommended_prefs_ = store; + return *this; +} + +PrefServiceBuilder& PrefServiceBuilder::WithReadErrorCallback( + const base::Callback& + read_error_callback) { + read_error_callback_ = read_error_callback; + return *this; +} + +PrefServiceBuilder& PrefServiceBuilder::WithUserFilePrefs( + const base::FilePath& prefs_file, + base::SequencedTaskRunner* task_runner) { + user_prefs_ = new JsonPrefStore(prefs_file, task_runner); + return *this; +} + +PrefServiceBuilder& PrefServiceBuilder::WithAsync(bool async) { + async_ = async; + return *this; +} + +PrefService* PrefServiceBuilder::Create(PrefRegistry* pref_registry) { + PrefNotifierImpl* pref_notifier = new PrefNotifierImpl(); + PrefService* pref_service = + new PrefService(pref_notifier, + new PrefValueStore(managed_prefs_.get(), + extension_prefs_.get(), + command_line_prefs_.get(), + user_prefs_.get(), + recommended_prefs_.get(), + pref_registry->defaults().get(), + pref_notifier), + user_prefs_.get(), + pref_registry, + read_error_callback_, + async_); + ResetDefaultState(); + return pref_service; +} + +void PrefServiceBuilder::ResetDefaultState() { + managed_prefs_ = NULL; + extension_prefs_ = NULL; + command_line_prefs_ = NULL; + user_prefs_ = NULL; + recommended_prefs_ = NULL; + read_error_callback_ = base::Bind(&DoNothingHandleReadError); + async_ = false; +} diff --git a/base/prefs/pref_service_builder.h b/base/prefs/pref_service_builder.h new file mode 100644 index 0000000000..4bd4fde7dd --- /dev/null +++ b/base/prefs/pref_service_builder.h @@ -0,0 +1,72 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_PREFS_PREF_SERVICE_BUILDER_H_ +#define BASE_PREFS_PREF_SERVICE_BUILDER_H_ + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/prefs/base_prefs_export.h" +#include "base/prefs/persistent_pref_store.h" +#include "base/prefs/pref_registry.h" +#include "base/prefs/pref_store.h" + +class PrefService; + +namespace base { +class FilePath; +class SequencedTaskRunner; +} + +// A class that allows convenient building of PrefService. +class BASE_PREFS_EXPORT PrefServiceBuilder { + public: + PrefServiceBuilder(); + virtual ~PrefServiceBuilder(); + + // Functions for setting the various parameters of the PrefService to build. + // These take ownership of the |store| parameter. + PrefServiceBuilder& WithManagedPrefs(PrefStore* store); + PrefServiceBuilder& WithExtensionPrefs(PrefStore* store); + PrefServiceBuilder& WithCommandLinePrefs(PrefStore* store); + PrefServiceBuilder& WithUserPrefs(PersistentPrefStore* store); + PrefServiceBuilder& WithRecommendedPrefs(PrefStore* store); + + // Sets up error callback for the PrefService. A do-nothing default + // is provided if this is not called. + PrefServiceBuilder& WithReadErrorCallback( + const base::Callback& + read_error_callback); + + // Specifies to use an actual file-backed user pref store. + PrefServiceBuilder& WithUserFilePrefs( + const base::FilePath& prefs_file, + base::SequencedTaskRunner* task_runner); + + PrefServiceBuilder& WithAsync(bool async); + + // Creates a PrefService object initialized with the parameters from + // this builder. + virtual PrefService* Create(PrefRegistry* registry); + + protected: + virtual void ResetDefaultState(); + + scoped_refptr managed_prefs_; + scoped_refptr extension_prefs_; + scoped_refptr command_line_prefs_; + scoped_refptr user_prefs_; + scoped_refptr recommended_prefs_; + + base::Callback read_error_callback_; + + // Defaults to false. + bool async_; + + private: + DISALLOW_COPY_AND_ASSIGN(PrefServiceBuilder); +}; + +#endif // BASE_PREFS_PREF_SERVICE_BUILDER_H_ diff --git a/base/prefs/pref_service_unittest.cc b/base/prefs/pref_service_unittest.cc new file mode 100644 index 0000000000..ff6bb2f2de --- /dev/null +++ b/base/prefs/pref_service_unittest.cc @@ -0,0 +1,318 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/prefs/json_pref_store.h" +#include "base/prefs/mock_pref_change_callback.h" +#include "base/prefs/pref_change_registrar.h" +#include "base/prefs/pref_registry_simple.h" +#include "base/prefs/pref_value_store.h" +#include "base/prefs/testing_pref_service.h" +#include "base/prefs/testing_pref_store.h" +#include "base/values.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::_; +using testing::Mock; + +const char kPrefName[] = "pref.name"; + +TEST(PrefServiceTest, NoObserverFire) { + TestingPrefServiceSimple prefs; + + const char pref_name[] = "homepage"; + prefs.registry()->RegisterStringPref(pref_name, std::string()); + + const char new_pref_value[] = "http://www.google.com/"; + MockPrefChangeCallback obs(&prefs); + PrefChangeRegistrar registrar; + registrar.Init(&prefs); + registrar.Add(pref_name, obs.GetCallback()); + + // This should fire the checks in MockPrefChangeCallback::OnPreferenceChanged. + const base::StringValue expected_value(new_pref_value); + obs.Expect(pref_name, &expected_value); + prefs.SetString(pref_name, new_pref_value); + Mock::VerifyAndClearExpectations(&obs); + + // Setting the pref to the same value should not set the pref value a second + // time. + EXPECT_CALL(obs, OnPreferenceChanged(_)).Times(0); + prefs.SetString(pref_name, new_pref_value); + Mock::VerifyAndClearExpectations(&obs); + + // Clearing the pref should cause the pref to fire. + const base::StringValue expected_default_value((std::string())); + obs.Expect(pref_name, &expected_default_value); + prefs.ClearPref(pref_name); + Mock::VerifyAndClearExpectations(&obs); + + // Clearing the pref again should not cause the pref to fire. + EXPECT_CALL(obs, OnPreferenceChanged(_)).Times(0); + prefs.ClearPref(pref_name); + Mock::VerifyAndClearExpectations(&obs); +} + +TEST(PrefServiceTest, HasPrefPath) { + TestingPrefServiceSimple prefs; + + const char path[] = "fake.path"; + + // Shouldn't initially have a path. + EXPECT_FALSE(prefs.HasPrefPath(path)); + + // Register the path. This doesn't set a value, so the path still shouldn't + // exist. + prefs.registry()->RegisterStringPref(path, std::string()); + EXPECT_FALSE(prefs.HasPrefPath(path)); + + // Set a value and make sure we have a path. + prefs.SetString(path, "blah"); + EXPECT_TRUE(prefs.HasPrefPath(path)); +} + +TEST(PrefServiceTest, Observers) { + const char pref_name[] = "homepage"; + + TestingPrefServiceSimple prefs; + prefs.SetUserPref(pref_name, + base::Value::CreateStringValue("http://www.cnn.com")); + prefs.registry()->RegisterStringPref(pref_name, std::string()); + + const char new_pref_value[] = "http://www.google.com/"; + const base::StringValue expected_new_pref_value(new_pref_value); + MockPrefChangeCallback obs(&prefs); + PrefChangeRegistrar registrar; + registrar.Init(&prefs); + registrar.Add(pref_name, obs.GetCallback()); + + PrefChangeRegistrar registrar_two; + registrar_two.Init(&prefs); + + // This should fire the checks in MockPrefChangeCallback::OnPreferenceChanged. + obs.Expect(pref_name, &expected_new_pref_value); + prefs.SetString(pref_name, new_pref_value); + Mock::VerifyAndClearExpectations(&obs); + + // Now try adding a second pref observer. + const char new_pref_value2[] = "http://www.youtube.com/"; + const base::StringValue expected_new_pref_value2(new_pref_value2); + MockPrefChangeCallback obs2(&prefs); + obs.Expect(pref_name, &expected_new_pref_value2); + obs2.Expect(pref_name, &expected_new_pref_value2); + registrar_two.Add(pref_name, obs2.GetCallback()); + // This should fire the checks in obs and obs2. + prefs.SetString(pref_name, new_pref_value2); + Mock::VerifyAndClearExpectations(&obs); + Mock::VerifyAndClearExpectations(&obs2); + + // Set a recommended value. + const base::StringValue recommended_pref_value("http://www.gmail.com/"); + obs.Expect(pref_name, &expected_new_pref_value2); + obs2.Expect(pref_name, &expected_new_pref_value2); + // This should fire the checks in obs and obs2 but with an unchanged value + // as the recommended value is being overridden by the user-set value. + prefs.SetRecommendedPref(pref_name, recommended_pref_value.DeepCopy()); + Mock::VerifyAndClearExpectations(&obs); + Mock::VerifyAndClearExpectations(&obs2); + + // Make sure obs2 still works after removing obs. + registrar.Remove(pref_name); + EXPECT_CALL(obs, OnPreferenceChanged(_)).Times(0); + obs2.Expect(pref_name, &expected_new_pref_value); + // This should only fire the observer in obs2. + prefs.SetString(pref_name, new_pref_value); + Mock::VerifyAndClearExpectations(&obs); + Mock::VerifyAndClearExpectations(&obs2); +} + +// Make sure that if a preference changes type, so the wrong type is stored in +// the user pref file, it uses the correct fallback value instead. +TEST(PrefServiceTest, GetValueChangedType) { + const int kTestValue = 10; + TestingPrefServiceSimple prefs; + prefs.registry()->RegisterIntegerPref(kPrefName, kTestValue); + + // Check falling back to a recommended value. + prefs.SetUserPref(kPrefName, + base::Value::CreateStringValue("not an integer")); + const PrefService::Preference* pref = prefs.FindPreference(kPrefName); + ASSERT_TRUE(pref); + const base::Value* value = pref->GetValue(); + ASSERT_TRUE(value); + EXPECT_EQ(base::Value::TYPE_INTEGER, value->GetType()); + int actual_int_value = -1; + EXPECT_TRUE(value->GetAsInteger(&actual_int_value)); + EXPECT_EQ(kTestValue, actual_int_value); +} + +TEST(PrefServiceTest, GetValueAndGetRecommendedValue) { + const int kDefaultValue = 5; + const int kUserValue = 10; + const int kRecommendedValue = 15; + TestingPrefServiceSimple prefs; + prefs.registry()->RegisterIntegerPref(kPrefName, kDefaultValue); + + // Create pref with a default value only. + const PrefService::Preference* pref = prefs.FindPreference(kPrefName); + ASSERT_TRUE(pref); + + // Check that GetValue() returns the default value. + const base::Value* value = pref->GetValue(); + ASSERT_TRUE(value); + EXPECT_EQ(base::Value::TYPE_INTEGER, value->GetType()); + int actual_int_value = -1; + EXPECT_TRUE(value->GetAsInteger(&actual_int_value)); + EXPECT_EQ(kDefaultValue, actual_int_value); + + // Check that GetRecommendedValue() returns no value. + value = pref->GetRecommendedValue(); + ASSERT_FALSE(value); + + // Set a user-set value. + prefs.SetUserPref(kPrefName, base::Value::CreateIntegerValue(kUserValue)); + + // Check that GetValue() returns the user-set value. + value = pref->GetValue(); + ASSERT_TRUE(value); + EXPECT_EQ(base::Value::TYPE_INTEGER, value->GetType()); + actual_int_value = -1; + EXPECT_TRUE(value->GetAsInteger(&actual_int_value)); + EXPECT_EQ(kUserValue, actual_int_value); + + // Check that GetRecommendedValue() returns no value. + value = pref->GetRecommendedValue(); + ASSERT_FALSE(value); + + // Set a recommended value. + prefs.SetRecommendedPref(kPrefName, + base::Value::CreateIntegerValue(kRecommendedValue)); + + // Check that GetValue() returns the user-set value. + value = pref->GetValue(); + ASSERT_TRUE(value); + EXPECT_EQ(base::Value::TYPE_INTEGER, value->GetType()); + actual_int_value = -1; + EXPECT_TRUE(value->GetAsInteger(&actual_int_value)); + EXPECT_EQ(kUserValue, actual_int_value); + + // Check that GetRecommendedValue() returns the recommended value. + value = pref->GetRecommendedValue(); + ASSERT_TRUE(value); + EXPECT_EQ(base::Value::TYPE_INTEGER, value->GetType()); + actual_int_value = -1; + EXPECT_TRUE(value->GetAsInteger(&actual_int_value)); + EXPECT_EQ(kRecommendedValue, actual_int_value); + + // Remove the user-set value. + prefs.RemoveUserPref(kPrefName); + + // Check that GetValue() returns the recommended value. + value = pref->GetValue(); + ASSERT_TRUE(value); + EXPECT_EQ(base::Value::TYPE_INTEGER, value->GetType()); + actual_int_value = -1; + EXPECT_TRUE(value->GetAsInteger(&actual_int_value)); + EXPECT_EQ(kRecommendedValue, actual_int_value); + + // Check that GetRecommendedValue() returns the recommended value. + value = pref->GetRecommendedValue(); + ASSERT_TRUE(value); + EXPECT_EQ(base::Value::TYPE_INTEGER, value->GetType()); + actual_int_value = -1; + EXPECT_TRUE(value->GetAsInteger(&actual_int_value)); + EXPECT_EQ(kRecommendedValue, actual_int_value); +} + +class PrefServiceSetValueTest : public testing::Test { + protected: + static const char kName[]; + static const char kValue[]; + + PrefServiceSetValueTest() : observer_(&prefs_) {} + + TestingPrefServiceSimple prefs_; + MockPrefChangeCallback observer_; +}; + +const char PrefServiceSetValueTest::kName[] = "name"; +const char PrefServiceSetValueTest::kValue[] = "value"; + +TEST_F(PrefServiceSetValueTest, SetStringValue) { + const char default_string[] = "default"; + const base::StringValue default_value(default_string); + prefs_.registry()->RegisterStringPref(kName, default_string); + + PrefChangeRegistrar registrar; + registrar.Init(&prefs_); + registrar.Add(kName, observer_.GetCallback()); + + // Changing the controlling store from default to user triggers notification. + observer_.Expect(kName, &default_value); + prefs_.Set(kName, default_value); + Mock::VerifyAndClearExpectations(&observer_); + + EXPECT_CALL(observer_, OnPreferenceChanged(_)).Times(0); + prefs_.Set(kName, default_value); + Mock::VerifyAndClearExpectations(&observer_); + + base::StringValue new_value(kValue); + observer_.Expect(kName, &new_value); + prefs_.Set(kName, new_value); + Mock::VerifyAndClearExpectations(&observer_); +} + +TEST_F(PrefServiceSetValueTest, SetDictionaryValue) { + prefs_.registry()->RegisterDictionaryPref(kName); + PrefChangeRegistrar registrar; + registrar.Init(&prefs_); + registrar.Add(kName, observer_.GetCallback()); + + EXPECT_CALL(observer_, OnPreferenceChanged(_)).Times(0); + prefs_.RemoveUserPref(kName); + Mock::VerifyAndClearExpectations(&observer_); + + base::DictionaryValue new_value; + new_value.SetString(kName, kValue); + observer_.Expect(kName, &new_value); + prefs_.Set(kName, new_value); + Mock::VerifyAndClearExpectations(&observer_); + + EXPECT_CALL(observer_, OnPreferenceChanged(_)).Times(0); + prefs_.Set(kName, new_value); + Mock::VerifyAndClearExpectations(&observer_); + + base::DictionaryValue empty; + observer_.Expect(kName, &empty); + prefs_.Set(kName, empty); + Mock::VerifyAndClearExpectations(&observer_); +} + +TEST_F(PrefServiceSetValueTest, SetListValue) { + prefs_.registry()->RegisterListPref(kName); + PrefChangeRegistrar registrar; + registrar.Init(&prefs_); + registrar.Add(kName, observer_.GetCallback()); + + EXPECT_CALL(observer_, OnPreferenceChanged(_)).Times(0); + prefs_.RemoveUserPref(kName); + Mock::VerifyAndClearExpectations(&observer_); + + base::ListValue new_value; + new_value.Append(base::Value::CreateStringValue(kValue)); + observer_.Expect(kName, &new_value); + prefs_.Set(kName, new_value); + Mock::VerifyAndClearExpectations(&observer_); + + EXPECT_CALL(observer_, OnPreferenceChanged(_)).Times(0); + prefs_.Set(kName, new_value); + Mock::VerifyAndClearExpectations(&observer_); + + base::ListValue empty; + observer_.Expect(kName, &empty); + prefs_.Set(kName, empty); + Mock::VerifyAndClearExpectations(&observer_); +} diff --git a/base/prefs/pref_store.cc b/base/prefs/pref_store.cc new file mode 100644 index 0000000000..0521654129 --- /dev/null +++ b/base/prefs/pref_store.cc @@ -0,0 +1,13 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/prefs/pref_store.h" + +size_t PrefStore::NumberOfObservers() const { + return 0; +} + +bool PrefStore::IsInitializationComplete() const { + return true; +} diff --git a/base/prefs/pref_store.h b/base/prefs/pref_store.h new file mode 100644 index 0000000000..22395288d1 --- /dev/null +++ b/base/prefs/pref_store.h @@ -0,0 +1,63 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_PREFS_PREF_STORE_H_ +#define BASE_PREFS_PREF_STORE_H_ + +#include + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/prefs/base_prefs_export.h" + +namespace base { +class Value; +} + +// This is an abstract interface for reading and writing from/to a persistent +// preference store, used by PrefService. An implementation using a JSON file +// can be found in JsonPrefStore, while an implementation without any backing +// store for testing can be found in TestingPrefStore. Furthermore, there is +// CommandLinePrefStore, which bridges command line options to preferences and +// ConfigurationPolicyPrefStore, which is used for hooking up configuration +// policy with the preference subsystem. +class BASE_PREFS_EXPORT PrefStore : public base::RefCounted { + public: + // Observer interface for monitoring PrefStore. + class BASE_PREFS_EXPORT Observer { + public: + // Called when the value for the given |key| in the store changes. + virtual void OnPrefValueChanged(const std::string& key) = 0; + // Notification about the PrefStore being fully initialized. + virtual void OnInitializationCompleted(bool succeeded) = 0; + + protected: + virtual ~Observer() {} + }; + + PrefStore() {} + + // Add and remove observers. + virtual void AddObserver(Observer* observer) {} + virtual void RemoveObserver(Observer* observer) {} + virtual size_t NumberOfObservers() const; + + // Whether the store has completed all asynchronous initialization. + virtual bool IsInitializationComplete() const; + + // Get the value for a given preference |key| and stores it in |*result|. + // |*result| is only modified if the return value is true and if |result| + // is not NULL. Ownership of the |*result| value remains with the PrefStore. + virtual bool GetValue(const std::string& key, + const base::Value** result) const = 0; + + protected: + friend class base::RefCounted; + virtual ~PrefStore() {} + + private: + DISALLOW_COPY_AND_ASSIGN(PrefStore); +}; + +#endif // BASE_PREFS_PREF_STORE_H_ diff --git a/base/prefs/pref_store_observer_mock.cc b/base/prefs/pref_store_observer_mock.cc new file mode 100644 index 0000000000..0970e6310d --- /dev/null +++ b/base/prefs/pref_store_observer_mock.cc @@ -0,0 +1,9 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/prefs/pref_store_observer_mock.h" + +PrefStoreObserverMock::PrefStoreObserverMock() {} + +PrefStoreObserverMock::~PrefStoreObserverMock() {} diff --git a/base/prefs/pref_store_observer_mock.h b/base/prefs/pref_store_observer_mock.h new file mode 100644 index 0000000000..8252c3b359 --- /dev/null +++ b/base/prefs/pref_store_observer_mock.h @@ -0,0 +1,25 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_PREFS_PREF_STORE_OBSERVER_MOCK_H_ +#define BASE_PREFS_PREF_STORE_OBSERVER_MOCK_H_ + +#include "base/basictypes.h" +#include "base/prefs/pref_store.h" +#include "testing/gmock/include/gmock/gmock.h" + +// A gmock-ified implementation of PrefStore::Observer. +class PrefStoreObserverMock : public PrefStore::Observer { + public: + PrefStoreObserverMock(); + virtual ~PrefStoreObserverMock(); + + MOCK_METHOD1(OnPrefValueChanged, void(const std::string&)); + MOCK_METHOD1(OnInitializationCompleted, void(bool)); + + private: + DISALLOW_COPY_AND_ASSIGN(PrefStoreObserverMock); +}; + +#endif // BASE_PREFS_PREF_STORE_OBSERVER_MOCK_H_ diff --git a/base/prefs/pref_value_map.cc b/base/prefs/pref_value_map.cc new file mode 100644 index 0000000000..eeda272217 --- /dev/null +++ b/base/prefs/pref_value_map.cc @@ -0,0 +1,152 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/prefs/pref_value_map.h" + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/stl_util.h" +#include "base/values.h" + +PrefValueMap::PrefValueMap() {} + +PrefValueMap::~PrefValueMap() { + Clear(); +} + +bool PrefValueMap::GetValue(const std::string& key, + const base::Value** value) const { + const Map::const_iterator entry = prefs_.find(key); + if (entry != prefs_.end()) { + if (value) + *value = entry->second; + return true; + } + + return false; +} + +bool PrefValueMap::GetValue(const std::string& key, base::Value** value) { + const Map::const_iterator entry = prefs_.find(key); + if (entry != prefs_.end()) { + if (value) + *value = entry->second; + return true; + } + + return false; +} + +bool PrefValueMap::SetValue(const std::string& key, base::Value* value) { + DCHECK(value); + scoped_ptr value_ptr(value); + const Map::iterator entry = prefs_.find(key); + if (entry != prefs_.end()) { + if (base::Value::Equals(entry->second, value)) + return false; + delete entry->second; + entry->second = value_ptr.release(); + } else { + prefs_[key] = value_ptr.release(); + } + + return true; +} + +bool PrefValueMap::RemoveValue(const std::string& key) { + const Map::iterator entry = prefs_.find(key); + if (entry != prefs_.end()) { + delete entry->second; + prefs_.erase(entry); + return true; + } + + return false; +} + +void PrefValueMap::Clear() { + STLDeleteValues(&prefs_); + prefs_.clear(); +} + +void PrefValueMap::Swap(PrefValueMap* other) { + prefs_.swap(other->prefs_); +} + +PrefValueMap::iterator PrefValueMap::begin() { + return prefs_.begin(); +} + +PrefValueMap::iterator PrefValueMap::end() { + return prefs_.end(); +} + +PrefValueMap::const_iterator PrefValueMap::begin() const { + return prefs_.begin(); +} + +PrefValueMap::const_iterator PrefValueMap::end() const { + return prefs_.end(); +} + +bool PrefValueMap::GetBoolean(const std::string& key, + bool* value) const { + const base::Value* stored_value = NULL; + return GetValue(key, &stored_value) && stored_value->GetAsBoolean(value); +} + +void PrefValueMap::SetBoolean(const std::string& key, bool value) { + SetValue(key, new base::FundamentalValue(value)); +} + +bool PrefValueMap::GetString(const std::string& key, + std::string* value) const { + const base::Value* stored_value = NULL; + return GetValue(key, &stored_value) && stored_value->GetAsString(value); +} + +void PrefValueMap::SetString(const std::string& key, + const std::string& value) { + SetValue(key, new base::StringValue(value)); +} + +bool PrefValueMap::GetInteger(const std::string& key, int* value) const { + const base::Value* stored_value = NULL; + return GetValue(key, &stored_value) && stored_value->GetAsInteger(value); +} + +void PrefValueMap::SetInteger(const std::string& key, const int value) { + SetValue(key, new base::FundamentalValue(value)); +} + +void PrefValueMap::GetDifferingKeys( + const PrefValueMap* other, + std::vector* differing_keys) const { + differing_keys->clear(); + + // Walk over the maps in lockstep, adding everything that is different. + Map::const_iterator this_pref(prefs_.begin()); + Map::const_iterator other_pref(other->prefs_.begin()); + while (this_pref != prefs_.end() && other_pref != other->prefs_.end()) { + const int diff = this_pref->first.compare(other_pref->first); + if (diff == 0) { + if (!this_pref->second->Equals(other_pref->second)) + differing_keys->push_back(this_pref->first); + ++this_pref; + ++other_pref; + } else if (diff < 0) { + differing_keys->push_back(this_pref->first); + ++this_pref; + } else if (diff > 0) { + differing_keys->push_back(other_pref->first); + ++other_pref; + } + } + + // Add the remaining entries. + for ( ; this_pref != prefs_.end(); ++this_pref) + differing_keys->push_back(this_pref->first); + for ( ; other_pref != other->prefs_.end(); ++other_pref) + differing_keys->push_back(other_pref->first); +} diff --git a/base/prefs/pref_value_map.h b/base/prefs/pref_value_map.h new file mode 100644 index 0000000000..1d79127719 --- /dev/null +++ b/base/prefs/pref_value_map.h @@ -0,0 +1,88 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_PREFS_PREF_VALUE_MAP_H_ +#define BASE_PREFS_PREF_VALUE_MAP_H_ + +#include +#include +#include + +#include "base/basictypes.h" +#include "base/prefs/base_prefs_export.h" + +namespace base { +class Value; +} + +// A generic string to value map used by the PrefStore implementations. +class BASE_PREFS_EXPORT PrefValueMap { + public: + typedef std::map::iterator iterator; + typedef std::map::const_iterator const_iterator; + + PrefValueMap(); + virtual ~PrefValueMap(); + + // Gets the value for |key| and stores it in |value|. Ownership remains with + // the map. Returns true if a value is present. If not, |value| is not + // touched. + bool GetValue(const std::string& key, const base::Value** value) const; + bool GetValue(const std::string& key, base::Value** value); + + // Sets a new |value| for |key|. Takes ownership of |value|, which must be + // non-NULL. Returns true if the value changed. + bool SetValue(const std::string& key, base::Value* value); + + // Removes the value for |key| from the map. Returns true if a value was + // removed. + bool RemoveValue(const std::string& key); + + // Clears the map. + void Clear(); + + // Swaps the contents of two maps. + void Swap(PrefValueMap* other); + + iterator begin(); + iterator end(); + const_iterator begin() const; + const_iterator end() const; + + // Gets a boolean value for |key| and stores it in |value|. Returns true if + // the value was found and of the proper type. + bool GetBoolean(const std::string& key, bool* value) const; + + // Sets the value for |key| to the boolean |value|. + void SetBoolean(const std::string& key, bool value); + + // Gets a string value for |key| and stores it in |value|. Returns true if + // the value was found and of the proper type. + bool GetString(const std::string& key, std::string* value) const; + + // Sets the value for |key| to the string |value|. + void SetString(const std::string& key, const std::string& value); + + // Gets an int value for |key| and stores it in |value|. Returns true if + // the value was found and of the proper type. + bool GetInteger(const std::string& key, int* value) const; + + // Sets the value for |key| to the int |value|. + void SetInteger(const std::string& key, const int value); + + // Compares this value map against |other| and stores all key names that have + // different values in |differing_keys|. This includes keys that are present + // only in one of the maps. + void GetDifferingKeys(const PrefValueMap* other, + std::vector* differing_keys) const; + + private: + typedef std::map Map; + + Map prefs_; + + DISALLOW_COPY_AND_ASSIGN(PrefValueMap); +}; + +#endif // BASE_PREFS_PREF_VALUE_MAP_H_ diff --git a/base/prefs/pref_value_map_unittest.cc b/base/prefs/pref_value_map_unittest.cc new file mode 100644 index 0000000000..8cc51ad02c --- /dev/null +++ b/base/prefs/pref_value_map_unittest.cc @@ -0,0 +1,115 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/prefs/pref_value_map.h" + +#include "base/values.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace { + +TEST(PrefValueMapTest, SetValue) { + PrefValueMap map; + const Value* result = NULL; + EXPECT_FALSE(map.GetValue("key", &result)); + EXPECT_FALSE(result); + + EXPECT_TRUE(map.SetValue("key", new StringValue("test"))); + EXPECT_FALSE(map.SetValue("key", new StringValue("test"))); + EXPECT_TRUE(map.SetValue("key", new StringValue("hi mom!"))); + + EXPECT_TRUE(map.GetValue("key", &result)); + EXPECT_TRUE(StringValue("hi mom!").Equals(result)); +} + +TEST(PrefValueMapTest, GetAndSetIntegerValue) { + PrefValueMap map; + ASSERT_TRUE(map.SetValue("key", new FundamentalValue(5))); + + int int_value = 0; + EXPECT_TRUE(map.GetInteger("key", &int_value)); + EXPECT_EQ(5, int_value); + + map.SetInteger("key", -14); + EXPECT_TRUE(map.GetInteger("key", &int_value)); + EXPECT_EQ(-14, int_value); +} + +TEST(PrefValueMapTest, RemoveValue) { + PrefValueMap map; + EXPECT_FALSE(map.RemoveValue("key")); + + EXPECT_TRUE(map.SetValue("key", new StringValue("test"))); + EXPECT_TRUE(map.GetValue("key", NULL)); + + EXPECT_TRUE(map.RemoveValue("key")); + EXPECT_FALSE(map.GetValue("key", NULL)); + + EXPECT_FALSE(map.RemoveValue("key")); +} + +TEST(PrefValueMapTest, Clear) { + PrefValueMap map; + EXPECT_TRUE(map.SetValue("key", new StringValue("test"))); + EXPECT_TRUE(map.GetValue("key", NULL)); + + map.Clear(); + + EXPECT_FALSE(map.GetValue("key", NULL)); +} + +TEST(PrefValueMapTest, GetDifferingKeys) { + PrefValueMap reference; + EXPECT_TRUE(reference.SetValue("b", new StringValue("test"))); + EXPECT_TRUE(reference.SetValue("c", new StringValue("test"))); + EXPECT_TRUE(reference.SetValue("e", new StringValue("test"))); + + PrefValueMap check; + std::vector differing_paths; + std::vector expected_differing_paths; + + reference.GetDifferingKeys(&check, &differing_paths); + expected_differing_paths.push_back("b"); + expected_differing_paths.push_back("c"); + expected_differing_paths.push_back("e"); + EXPECT_EQ(expected_differing_paths, differing_paths); + + EXPECT_TRUE(check.SetValue("a", new StringValue("test"))); + EXPECT_TRUE(check.SetValue("c", new StringValue("test"))); + EXPECT_TRUE(check.SetValue("d", new StringValue("test"))); + + reference.GetDifferingKeys(&check, &differing_paths); + expected_differing_paths.clear(); + expected_differing_paths.push_back("a"); + expected_differing_paths.push_back("b"); + expected_differing_paths.push_back("d"); + expected_differing_paths.push_back("e"); + EXPECT_EQ(expected_differing_paths, differing_paths); +} + +TEST(PrefValueMapTest, SwapTwoMaps) { + PrefValueMap first_map; + EXPECT_TRUE(first_map.SetValue("a", new StringValue("test"))); + EXPECT_TRUE(first_map.SetValue("b", new StringValue("test"))); + EXPECT_TRUE(first_map.SetValue("c", new StringValue("test"))); + + PrefValueMap second_map; + EXPECT_TRUE(second_map.SetValue("d", new StringValue("test"))); + EXPECT_TRUE(second_map.SetValue("e", new StringValue("test"))); + EXPECT_TRUE(second_map.SetValue("f", new StringValue("test"))); + + first_map.Swap(&second_map); + + EXPECT_TRUE(first_map.GetValue("d", NULL)); + EXPECT_TRUE(first_map.GetValue("e", NULL)); + EXPECT_TRUE(first_map.GetValue("f", NULL)); + + EXPECT_TRUE(second_map.GetValue("a", NULL)); + EXPECT_TRUE(second_map.GetValue("b", NULL)); + EXPECT_TRUE(second_map.GetValue("c", NULL)); +} + +} // namespace +} // namespace base diff --git a/base/prefs/pref_value_store.cc b/base/prefs/pref_value_store.cc new file mode 100644 index 0000000000..7d54f09f35 --- /dev/null +++ b/base/prefs/pref_value_store.cc @@ -0,0 +1,277 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/prefs/pref_value_store.h" + +#include "base/logging.h" +#include "base/prefs/pref_notifier.h" +#include "base/prefs/pref_observer.h" + +PrefValueStore::PrefStoreKeeper::PrefStoreKeeper() + : pref_value_store_(NULL), + type_(PrefValueStore::INVALID_STORE) { +} + +PrefValueStore::PrefStoreKeeper::~PrefStoreKeeper() { + if (pref_store_.get()) { + pref_store_->RemoveObserver(this); + pref_store_ = NULL; + } + pref_value_store_ = NULL; +} + +void PrefValueStore::PrefStoreKeeper::Initialize( + PrefValueStore* store, + PrefStore* pref_store, + PrefValueStore::PrefStoreType type) { + if (pref_store_.get()) { + pref_store_->RemoveObserver(this); + DCHECK_EQ(0U, pref_store_->NumberOfObservers()); + } + type_ = type; + pref_value_store_ = store; + pref_store_ = pref_store; + if (pref_store_.get()) + pref_store_->AddObserver(this); +} + +void PrefValueStore::PrefStoreKeeper::OnPrefValueChanged( + const std::string& key) { + pref_value_store_->OnPrefValueChanged(type_, key); +} + +void PrefValueStore::PrefStoreKeeper::OnInitializationCompleted( + bool succeeded) { + pref_value_store_->OnInitializationCompleted(type_, succeeded); +} + +PrefValueStore::PrefValueStore(PrefStore* managed_prefs, + PrefStore* extension_prefs, + PrefStore* command_line_prefs, + PrefStore* user_prefs, + PrefStore* recommended_prefs, + PrefStore* default_prefs, + PrefNotifier* pref_notifier) + : pref_notifier_(pref_notifier), + initialization_failed_(false) { + InitPrefStore(MANAGED_STORE, managed_prefs); + InitPrefStore(EXTENSION_STORE, extension_prefs); + InitPrefStore(COMMAND_LINE_STORE, command_line_prefs); + InitPrefStore(USER_STORE, user_prefs); + InitPrefStore(RECOMMENDED_STORE, recommended_prefs); + InitPrefStore(DEFAULT_STORE, default_prefs); + + CheckInitializationCompleted(); +} + +PrefValueStore::~PrefValueStore() {} + +PrefValueStore* PrefValueStore::CloneAndSpecialize( + PrefStore* managed_prefs, + PrefStore* extension_prefs, + PrefStore* command_line_prefs, + PrefStore* user_prefs, + PrefStore* recommended_prefs, + PrefStore* default_prefs, + PrefNotifier* pref_notifier) { + DCHECK(pref_notifier); + if (!managed_prefs) + managed_prefs = GetPrefStore(MANAGED_STORE); + if (!extension_prefs) + extension_prefs = GetPrefStore(EXTENSION_STORE); + if (!command_line_prefs) + command_line_prefs = GetPrefStore(COMMAND_LINE_STORE); + if (!user_prefs) + user_prefs = GetPrefStore(USER_STORE); + if (!recommended_prefs) + recommended_prefs = GetPrefStore(RECOMMENDED_STORE); + if (!default_prefs) + default_prefs = GetPrefStore(DEFAULT_STORE); + + return new PrefValueStore( + managed_prefs, extension_prefs, command_line_prefs, user_prefs, + recommended_prefs, default_prefs, pref_notifier); +} + +void PrefValueStore::set_callback(const PrefChangedCallback& callback) { + pref_changed_callback_ = callback; +} + +bool PrefValueStore::GetValue(const std::string& name, + base::Value::Type type, + const base::Value** out_value) const { + // Check the |PrefStore|s in order of their priority from highest to lowest, + // looking for the first preference value with the given |name| and |type|. + for (size_t i = 0; i <= PREF_STORE_TYPE_MAX; ++i) { + if (GetValueFromStoreWithType(name.c_str(), type, + static_cast(i), out_value)) + return true; + } + return false; +} + +bool PrefValueStore::GetRecommendedValue(const std::string& name, + base::Value::Type type, + const base::Value** out_value) const { + return GetValueFromStoreWithType(name.c_str(), type, RECOMMENDED_STORE, + out_value); +} + +void PrefValueStore::NotifyPrefChanged( + const char* path, + PrefValueStore::PrefStoreType new_store) { + DCHECK(new_store != INVALID_STORE); + // A notification is sent when the pref value in any store changes. If this + // store is currently being overridden by a higher-priority store, the + // effective value of the pref will not have changed. + pref_notifier_->OnPreferenceChanged(path); + if (!pref_changed_callback_.is_null()) + pref_changed_callback_.Run(path); +} + +bool PrefValueStore::PrefValueInManagedStore(const char* name) const { + return PrefValueInStore(name, MANAGED_STORE); +} + +bool PrefValueStore::PrefValueInExtensionStore(const char* name) const { + return PrefValueInStore(name, EXTENSION_STORE); +} + +bool PrefValueStore::PrefValueInUserStore(const char* name) const { + return PrefValueInStore(name, USER_STORE); +} + +bool PrefValueStore::PrefValueFromExtensionStore(const char* name) const { + return ControllingPrefStoreForPref(name) == EXTENSION_STORE; +} + +bool PrefValueStore::PrefValueFromUserStore(const char* name) const { + return ControllingPrefStoreForPref(name) == USER_STORE; +} + +bool PrefValueStore::PrefValueFromRecommendedStore(const char* name) const { + return ControllingPrefStoreForPref(name) == RECOMMENDED_STORE; +} + +bool PrefValueStore::PrefValueFromDefaultStore(const char* name) const { + return ControllingPrefStoreForPref(name) == DEFAULT_STORE; +} + +bool PrefValueStore::PrefValueUserModifiable(const char* name) const { + PrefStoreType effective_store = ControllingPrefStoreForPref(name); + return effective_store >= USER_STORE || + effective_store == INVALID_STORE; +} + +bool PrefValueStore::PrefValueExtensionModifiable(const char* name) const { + PrefStoreType effective_store = ControllingPrefStoreForPref(name); + return effective_store >= EXTENSION_STORE || + effective_store == INVALID_STORE; +} + +void PrefValueStore::UpdateCommandLinePrefStore(PrefStore* command_line_prefs) { + InitPrefStore(COMMAND_LINE_STORE, command_line_prefs); +} + +bool PrefValueStore::PrefValueInStore( + const char* name, + PrefValueStore::PrefStoreType store) const { + // Declare a temp Value* and call GetValueFromStore, + // ignoring the output value. + const base::Value* tmp_value = NULL; + return GetValueFromStore(name, store, &tmp_value); +} + +bool PrefValueStore::PrefValueInStoreRange( + const char* name, + PrefValueStore::PrefStoreType first_checked_store, + PrefValueStore::PrefStoreType last_checked_store) const { + if (first_checked_store > last_checked_store) { + NOTREACHED(); + return false; + } + + for (size_t i = first_checked_store; + i <= static_cast(last_checked_store); ++i) { + if (PrefValueInStore(name, static_cast(i))) + return true; + } + return false; +} + +PrefValueStore::PrefStoreType PrefValueStore::ControllingPrefStoreForPref( + const char* name) const { + for (size_t i = 0; i <= PREF_STORE_TYPE_MAX; ++i) { + if (PrefValueInStore(name, static_cast(i))) + return static_cast(i); + } + return INVALID_STORE; +} + +bool PrefValueStore::GetValueFromStore(const char* name, + PrefValueStore::PrefStoreType store_type, + const base::Value** out_value) const { + // Only return true if we find a value and it is the correct type, so stale + // values with the incorrect type will be ignored. + const PrefStore* store = GetPrefStore(static_cast(store_type)); + if (store && store->GetValue(name, out_value)) + return true; + + // No valid value found for the given preference name: set the return value + // to false. + *out_value = NULL; + return false; +} + +bool PrefValueStore::GetValueFromStoreWithType( + const char* name, + base::Value::Type type, + PrefStoreType store, + const base::Value** out_value) const { + if (GetValueFromStore(name, store, out_value)) { + if ((*out_value)->IsType(type)) + return true; + + LOG(WARNING) << "Expected type for " << name << " is " << type + << " but got " << (*out_value)->GetType() + << " in store " << store; + } + + *out_value = NULL; + return false; +} + +void PrefValueStore::OnPrefValueChanged(PrefValueStore::PrefStoreType type, + const std::string& key) { + NotifyPrefChanged(key.c_str(), type); +} + +void PrefValueStore::OnInitializationCompleted( + PrefValueStore::PrefStoreType type, bool succeeded) { + if (initialization_failed_) + return; + if (!succeeded) { + initialization_failed_ = true; + pref_notifier_->OnInitializationCompleted(false); + return; + } + CheckInitializationCompleted(); +} + +void PrefValueStore::InitPrefStore(PrefValueStore::PrefStoreType type, + PrefStore* pref_store) { + pref_stores_[type].Initialize(this, pref_store, type); +} + +void PrefValueStore::CheckInitializationCompleted() { + if (initialization_failed_) + return; + for (size_t i = 0; i <= PREF_STORE_TYPE_MAX; ++i) { + scoped_refptr store = + GetPrefStore(static_cast(i)); + if (store.get() && !store->IsInitializationComplete()) + return; + } + pref_notifier_->OnInitializationCompleted(true); +} diff --git a/base/prefs/pref_value_store.h b/base/prefs/pref_value_store.h new file mode 100644 index 0000000000..4036e40ac8 --- /dev/null +++ b/base/prefs/pref_value_store.h @@ -0,0 +1,260 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_PREFS_PREF_VALUE_STORE_H_ +#define BASE_PREFS_PREF_VALUE_STORE_H_ + +#include +#include +#include + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/gtest_prod_util.h" +#include "base/memory/ref_counted.h" +#include "base/prefs/base_prefs_export.h" +#include "base/prefs/pref_store.h" +#include "base/values.h" + +class PrefNotifier; +class PrefStore; + +// The PrefValueStore manages various sources of values for Preferences +// (e.g., configuration policies, extensions, and user settings). It returns +// the value of a Preference from the source with the highest priority, and +// allows setting user-defined values for preferences that are not managed. +// +// Unless otherwise explicitly noted, all of the methods of this class must +// be called on the UI thread. +class BASE_PREFS_EXPORT PrefValueStore { + public: + typedef base::Callback PrefChangedCallback; + + // In decreasing order of precedence: + // |managed_prefs| contains all preferences from mandatory policies. + // |extension_prefs| contains preference values set by extensions. + // |command_line_prefs| contains preference values set by command-line + // switches. + // |user_prefs| contains all user-set preference values. + // |recommended_prefs| contains all preferences from recommended policies. + // |default_prefs| contains application-default preference values. It must + // be non-null if any preferences are to be registered. + // + // |pref_notifier| facilitates broadcasting preference change notifications + // to the world. + PrefValueStore(PrefStore* managed_prefs, + PrefStore* extension_prefs, + PrefStore* command_line_prefs, + PrefStore* user_prefs, + PrefStore* recommended_prefs, + PrefStore* default_prefs, + PrefNotifier* pref_notifier); + virtual ~PrefValueStore(); + + // Creates a clone of this PrefValueStore with PrefStores overwritten + // by the parameters passed, if unequal NULL. + PrefValueStore* CloneAndSpecialize(PrefStore* managed_prefs, + PrefStore* extension_prefs, + PrefStore* command_line_prefs, + PrefStore* user_prefs, + PrefStore* recommended_prefs, + PrefStore* default_prefs, + PrefNotifier* pref_notifier); + + // A PrefValueStore can have exactly one callback that is directly + // notified of preferences changing in the store. This does not + // filter through the PrefNotifier mechanism, which may not forward + // certain changes (e.g. unregistered prefs). + void set_callback(const PrefChangedCallback& callback); + + // Gets the value for the given preference name that has the specified value + // type. Values stored in a PrefStore that have the matching |name| but + // a non-matching |type| are silently skipped. Returns true if a valid value + // was found in any of the available PrefStores. Most callers should use + // Preference::GetValue() instead of calling this method directly. + bool GetValue(const std::string& name, + base::Value::Type type, + const base::Value** out_value) const; + + // Gets the recommended value for the given preference name that has the + // specified value type. A value stored in the recommended PrefStore that has + // the matching |name| but a non-matching |type| is silently ignored. Returns + // true if a valid value was found. Most callers should use + // Preference::GetRecommendedValue() instead of calling this method directly. + bool GetRecommendedValue(const std::string& name, + base::Value::Type type, + const base::Value** out_value) const; + + // These methods return true if a preference with the given name is in the + // indicated pref store, even if that value is currently being overridden by + // a higher-priority source. + bool PrefValueInManagedStore(const char* name) const; + bool PrefValueInExtensionStore(const char* name) const; + bool PrefValueInUserStore(const char* name) const; + + // These methods return true if a preference with the given name is actually + // being controlled by the indicated pref store and not being overridden by + // a higher-priority source. + bool PrefValueFromExtensionStore(const char* name) const; + bool PrefValueFromUserStore(const char* name) const; + bool PrefValueFromRecommendedStore(const char* name) const; + bool PrefValueFromDefaultStore(const char* name) const; + + // Check whether a Preference value is modifiable by the user, i.e. whether + // there is no higher-priority source controlling it. + bool PrefValueUserModifiable(const char* name) const; + + // Check whether a Preference value is modifiable by an extension, i.e. + // whether there is no higher-priority source controlling it. + bool PrefValueExtensionModifiable(const char* name) const; + + // Update the command line PrefStore with |command_line_prefs|. + void UpdateCommandLinePrefStore(PrefStore* command_line_prefs); + + private: + // PrefStores must be listed here in order from highest to lowest priority. + // MANAGED contains all managed preference values that are provided by + // mandatory policies (e.g. Windows Group Policy or cloud policy). + // EXTENSION contains preference values set by extensions. + // COMMAND_LINE contains preference values set by command-line switches. + // USER contains all user-set preference values. + // RECOMMENDED contains all preferences that are provided by recommended + // policies. + // DEFAULT contains all application default preference values. + enum PrefStoreType { + // INVALID_STORE is not associated with an actual PrefStore but used as + // an invalid marker, e.g. as a return value. + INVALID_STORE = -1, + MANAGED_STORE = 0, + EXTENSION_STORE, + COMMAND_LINE_STORE, + USER_STORE, + RECOMMENDED_STORE, + DEFAULT_STORE, + PREF_STORE_TYPE_MAX = DEFAULT_STORE + }; + + // Keeps a PrefStore reference on behalf of the PrefValueStore and monitors + // the PrefStore for changes, forwarding notifications to PrefValueStore. This + // indirection is here for the sake of disambiguating notifications from the + // individual PrefStores. + class PrefStoreKeeper : public PrefStore::Observer { + public: + PrefStoreKeeper(); + virtual ~PrefStoreKeeper(); + + // Takes ownership of |pref_store|. + void Initialize(PrefValueStore* store, + PrefStore* pref_store, + PrefStoreType type); + + PrefStore* store() { return pref_store_.get(); } + const PrefStore* store() const { return pref_store_.get(); } + + private: + // PrefStore::Observer implementation. + virtual void OnPrefValueChanged(const std::string& key) OVERRIDE; + virtual void OnInitializationCompleted(bool succeeded) OVERRIDE; + + // PrefValueStore this keeper is part of. + PrefValueStore* pref_value_store_; + + // The PrefStore managed by this keeper. + scoped_refptr pref_store_; + + // Type of the pref store. + PrefStoreType type_; + + DISALLOW_COPY_AND_ASSIGN(PrefStoreKeeper); + }; + + typedef std::map PrefTypeMap; + + friend class PrefValueStorePolicyRefreshTest; + FRIEND_TEST_ALL_PREFIXES(PrefValueStorePolicyRefreshTest, TestPolicyRefresh); + FRIEND_TEST_ALL_PREFIXES(PrefValueStorePolicyRefreshTest, + TestRefreshPolicyPrefsCompletion); + FRIEND_TEST_ALL_PREFIXES(PrefValueStorePolicyRefreshTest, + TestConcurrentPolicyRefresh); + + // Returns true if the preference with the given name has a value in the + // given PrefStoreType, of the same value type as the preference was + // registered with. + bool PrefValueInStore(const char* name, PrefStoreType store) const; + + // Returns true if a preference has an explicit value in any of the + // stores in the range specified by |first_checked_store| and + // |last_checked_store|, even if that value is currently being + // overridden by a higher-priority store. + bool PrefValueInStoreRange(const char* name, + PrefStoreType first_checked_store, + PrefStoreType last_checked_store) const; + + // Returns the pref store type identifying the source that controls the + // Preference identified by |name|. If none of the sources has a value, + // INVALID_STORE is returned. In practice, the default PrefStore + // should always have a value for any registered preferencem, so INVALID_STORE + // indicates an error. + PrefStoreType ControllingPrefStoreForPref(const char* name) const; + + // Get a value from the specified |store|. + bool GetValueFromStore(const char* name, + PrefStoreType store, + const base::Value** out_value) const; + + // Get a value from the specified |store| if its |type| matches. + bool GetValueFromStoreWithType(const char* name, + base::Value::Type type, + PrefStoreType store, + const base::Value** out_value) const; + + // Called upon changes in individual pref stores in order to determine whether + // the user-visible pref value has changed. Triggers the change notification + // if the effective value of the preference has changed, or if the store + // controlling the pref has changed. + void NotifyPrefChanged(const char* path, PrefStoreType new_store); + + // Called from the PrefStoreKeeper implementation when a pref value for |key| + // changed in the pref store for |type|. + void OnPrefValueChanged(PrefStoreType type, const std::string& key); + + // Handle the event that the store for |type| has completed initialization. + void OnInitializationCompleted(PrefStoreType type, bool succeeded); + + // Initializes a pref store keeper. Sets up a PrefStoreKeeper that will take + // ownership of the passed |pref_store|. + void InitPrefStore(PrefStoreType type, PrefStore* pref_store); + + // Checks whether initialization is completed and tells the notifier if that + // is the case. + void CheckInitializationCompleted(); + + // Get the PrefStore pointer for the given type. May return NULL if there is + // no PrefStore for that type. + PrefStore* GetPrefStore(PrefStoreType type) { + return pref_stores_[type].store(); + } + const PrefStore* GetPrefStore(PrefStoreType type) const { + return pref_stores_[type].store(); + } + + // Keeps the PrefStore references in order of precedence. + PrefStoreKeeper pref_stores_[PREF_STORE_TYPE_MAX + 1]; + + PrefChangedCallback pref_changed_callback_; + + // Used for generating notifications. This is a weak reference, + // since the notifier is owned by the corresponding PrefService. + PrefNotifier* pref_notifier_; + + // A mapping of preference names to their registered types. + PrefTypeMap pref_types_; + + // True if not all of the PrefStores were initialized successfully. + bool initialization_failed_; + + DISALLOW_COPY_AND_ASSIGN(PrefValueStore); +}; + +#endif // BASE_PREFS_PREF_VALUE_STORE_H_ diff --git a/base/prefs/pref_value_store_unittest.cc b/base/prefs/pref_value_store_unittest.cc new file mode 100644 index 0000000000..b38c4acef1 --- /dev/null +++ b/base/prefs/pref_value_store_unittest.cc @@ -0,0 +1,592 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/bind.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/prefs/pref_notifier.h" +#include "base/prefs/pref_value_store.h" +#include "base/prefs/testing_pref_store.h" +#include "base/values.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::Mock; +using testing::_; + +namespace { + +// Allows to capture pref notifications through gmock. +class MockPrefNotifier : public PrefNotifier { + public: + MOCK_METHOD1(OnPreferenceChanged, void(const std::string&)); + MOCK_METHOD1(OnInitializationCompleted, void(bool)); +}; + +// Allows to capture sync model associator interaction. +class MockPrefModelAssociator { + public: + MOCK_METHOD1(ProcessPrefChange, void(const std::string&)); +}; + +} // namespace + +// Names of the preferences used in this test. +namespace prefs { +const char kManagedPref[] = "this.pref.managed"; +const char kCommandLinePref[] = "this.pref.command_line"; +const char kExtensionPref[] = "this.pref.extension"; +const char kUserPref[] = "this.pref.user"; +const char kRecommendedPref[] = "this.pref.recommended"; +const char kDefaultPref[] = "this.pref.default"; +const char kMissingPref[] = "this.pref.does_not_exist"; +} + +// Potentially expected values of all preferences used in this test program. +namespace managed_pref { +const char kManagedValue[] = "managed:managed"; +} + +namespace extension_pref { +const char kManagedValue[] = "extension:managed"; +const char kExtensionValue[] = "extension:extension"; +} + +namespace command_line_pref { +const char kManagedValue[] = "command_line:managed"; +const char kExtensionValue[] = "command_line:extension"; +const char kCommandLineValue[] = "command_line:command_line"; +} + +namespace user_pref { +const char kManagedValue[] = "user:managed"; +const char kExtensionValue[] = "user:extension"; +const char kCommandLineValue[] = "user:command_line"; +const char kUserValue[] = "user:user"; +} + +namespace recommended_pref { +const char kManagedValue[] = "recommended:managed"; +const char kExtensionValue[] = "recommended:extension"; +const char kCommandLineValue[] = "recommended:command_line"; +const char kUserValue[] = "recommended:user"; +const char kRecommendedValue[] = "recommended:recommended"; +} + +namespace default_pref { +const char kManagedValue[] = "default:managed"; +const char kExtensionValue[] = "default:extension"; +const char kCommandLineValue[] = "default:command_line"; +const char kUserValue[] = "default:user"; +const char kRecommendedValue[] = "default:recommended"; +const char kDefaultValue[] = "default:default"; +} + +class PrefValueStoreTest : public testing::Test { + protected: + virtual void SetUp() { + // Create TestingPrefStores. + CreateManagedPrefs(); + CreateExtensionPrefs(); + CreateCommandLinePrefs(); + CreateUserPrefs(); + CreateRecommendedPrefs(); + CreateDefaultPrefs(); + sync_associator_.reset(new MockPrefModelAssociator()); + + // Create a fresh PrefValueStore. + pref_value_store_.reset(new PrefValueStore(managed_pref_store_.get(), + extension_pref_store_.get(), + command_line_pref_store_.get(), + user_pref_store_.get(), + recommended_pref_store_.get(), + default_pref_store_.get(), + &pref_notifier_)); + + pref_value_store_->set_callback( + base::Bind(&MockPrefModelAssociator::ProcessPrefChange, + base::Unretained(sync_associator_.get()))); + } + + void CreateManagedPrefs() { + managed_pref_store_ = new TestingPrefStore; + managed_pref_store_->SetString( + prefs::kManagedPref, + managed_pref::kManagedValue); + } + + void CreateExtensionPrefs() { + extension_pref_store_ = new TestingPrefStore; + extension_pref_store_->SetString( + prefs::kManagedPref, + extension_pref::kManagedValue); + extension_pref_store_->SetString( + prefs::kExtensionPref, + extension_pref::kExtensionValue); + } + + void CreateCommandLinePrefs() { + command_line_pref_store_ = new TestingPrefStore; + command_line_pref_store_->SetString( + prefs::kManagedPref, + command_line_pref::kManagedValue); + command_line_pref_store_->SetString( + prefs::kExtensionPref, + command_line_pref::kExtensionValue); + command_line_pref_store_->SetString( + prefs::kCommandLinePref, + command_line_pref::kCommandLineValue); + } + + void CreateUserPrefs() { + user_pref_store_ = new TestingPrefStore; + user_pref_store_->SetString( + prefs::kManagedPref, + user_pref::kManagedValue); + user_pref_store_->SetString( + prefs::kCommandLinePref, + user_pref::kCommandLineValue); + user_pref_store_->SetString( + prefs::kExtensionPref, + user_pref::kExtensionValue); + user_pref_store_->SetString( + prefs::kUserPref, + user_pref::kUserValue); + } + + void CreateRecommendedPrefs() { + recommended_pref_store_ = new TestingPrefStore; + recommended_pref_store_->SetString( + prefs::kManagedPref, + recommended_pref::kManagedValue); + recommended_pref_store_->SetString( + prefs::kCommandLinePref, + recommended_pref::kCommandLineValue); + recommended_pref_store_->SetString( + prefs::kExtensionPref, + recommended_pref::kExtensionValue); + recommended_pref_store_->SetString( + prefs::kUserPref, + recommended_pref::kUserValue); + recommended_pref_store_->SetString( + prefs::kRecommendedPref, + recommended_pref::kRecommendedValue); + } + + void CreateDefaultPrefs() { + default_pref_store_ = new TestingPrefStore; + default_pref_store_->SetString( + prefs::kManagedPref, + default_pref::kManagedValue); + default_pref_store_->SetString( + prefs::kCommandLinePref, + default_pref::kCommandLineValue); + default_pref_store_->SetString( + prefs::kExtensionPref, + default_pref::kExtensionValue); + default_pref_store_->SetString( + prefs::kUserPref, + default_pref::kUserValue); + default_pref_store_->SetString( + prefs::kRecommendedPref, + default_pref::kRecommendedValue); + default_pref_store_->SetString( + prefs::kDefaultPref, + default_pref::kDefaultValue); + } + + void ExpectValueChangeNotifications(const char* name) { + EXPECT_CALL(pref_notifier_, OnPreferenceChanged(name)); + EXPECT_CALL(*sync_associator_, ProcessPrefChange(name)); + } + + void CheckAndClearValueChangeNotifications() { + Mock::VerifyAndClearExpectations(&pref_notifier_); + Mock::VerifyAndClearExpectations(sync_associator_.get()); + } + + MockPrefNotifier pref_notifier_; + scoped_ptr sync_associator_; + scoped_ptr pref_value_store_; + + scoped_refptr managed_pref_store_; + scoped_refptr extension_pref_store_; + scoped_refptr command_line_pref_store_; + scoped_refptr user_pref_store_; + scoped_refptr recommended_pref_store_; + scoped_refptr default_pref_store_; +}; + +TEST_F(PrefValueStoreTest, GetValue) { + const base::Value* value; + + // The following tests read a value from the PrefService. The preferences are + // set in a way such that all lower-priority stores have a value and we can + // test whether overrides work correctly. + + // Test getting a managed value. + value = NULL; + ASSERT_TRUE(pref_value_store_->GetValue(prefs::kManagedPref, + base::Value::TYPE_STRING, &value)); + std::string actual_str_value; + EXPECT_TRUE(value->GetAsString(&actual_str_value)); + EXPECT_EQ(managed_pref::kManagedValue, actual_str_value); + + // Test getting an extension value. + value = NULL; + ASSERT_TRUE(pref_value_store_->GetValue(prefs::kExtensionPref, + base::Value::TYPE_STRING, &value)); + EXPECT_TRUE(value->GetAsString(&actual_str_value)); + EXPECT_EQ(extension_pref::kExtensionValue, actual_str_value); + + // Test getting a command-line value. + value = NULL; + ASSERT_TRUE(pref_value_store_->GetValue(prefs::kCommandLinePref, + base::Value::TYPE_STRING, &value)); + EXPECT_TRUE(value->GetAsString(&actual_str_value)); + EXPECT_EQ(command_line_pref::kCommandLineValue, actual_str_value); + + // Test getting a user-set value. + value = NULL; + ASSERT_TRUE(pref_value_store_->GetValue(prefs::kUserPref, + base::Value::TYPE_STRING, &value)); + EXPECT_TRUE(value->GetAsString(&actual_str_value)); + EXPECT_EQ(user_pref::kUserValue, actual_str_value); + + // Test getting a user set value overwriting a recommended value. + value = NULL; + ASSERT_TRUE(pref_value_store_->GetValue(prefs::kRecommendedPref, + base::Value::TYPE_STRING, &value)); + EXPECT_TRUE(value->GetAsString(&actual_str_value)); + EXPECT_EQ(recommended_pref::kRecommendedValue, + actual_str_value); + + // Test getting a default value. + value = NULL; + ASSERT_TRUE(pref_value_store_->GetValue(prefs::kDefaultPref, + base::Value::TYPE_STRING, &value)); + EXPECT_TRUE(value->GetAsString(&actual_str_value)); + EXPECT_EQ(default_pref::kDefaultValue, actual_str_value); + + // Test getting a preference value that the |PrefValueStore| + // does not contain. + base::FundamentalValue tmp_dummy_value(true); + value = &tmp_dummy_value; + ASSERT_FALSE(pref_value_store_->GetValue(prefs::kMissingPref, + base::Value::TYPE_STRING, &value)); + ASSERT_FALSE(value); +} + +TEST_F(PrefValueStoreTest, GetRecommendedValue) { + const base::Value* value; + + // The following tests read a value from the PrefService. The preferences are + // set in a way such that all lower-priority stores have a value and we can + // test whether overrides do not clutter the recommended value. + + // Test getting recommended value when a managed value is present. + value = NULL; + ASSERT_TRUE(pref_value_store_->GetRecommendedValue( + prefs::kManagedPref, + base::Value::TYPE_STRING, &value)); + std::string actual_str_value; + EXPECT_TRUE(value->GetAsString(&actual_str_value)); + EXPECT_EQ(recommended_pref::kManagedValue, actual_str_value); + + // Test getting recommended value when an extension value is present. + value = NULL; + ASSERT_TRUE(pref_value_store_->GetRecommendedValue( + prefs::kExtensionPref, + base::Value::TYPE_STRING, &value)); + EXPECT_TRUE(value->GetAsString(&actual_str_value)); + EXPECT_EQ(recommended_pref::kExtensionValue, actual_str_value); + + // Test getting recommended value when a command-line value is present. + value = NULL; + ASSERT_TRUE(pref_value_store_->GetRecommendedValue( + prefs::kCommandLinePref, + base::Value::TYPE_STRING, &value)); + EXPECT_TRUE(value->GetAsString(&actual_str_value)); + EXPECT_EQ(recommended_pref::kCommandLineValue, actual_str_value); + + // Test getting recommended value when a user-set value is present. + value = NULL; + ASSERT_TRUE(pref_value_store_->GetRecommendedValue( + prefs::kUserPref, + base::Value::TYPE_STRING, &value)); + EXPECT_TRUE(value->GetAsString(&actual_str_value)); + EXPECT_EQ(recommended_pref::kUserValue, actual_str_value); + + // Test getting recommended value when no higher-priority value is present. + value = NULL; + ASSERT_TRUE(pref_value_store_->GetRecommendedValue( + prefs::kRecommendedPref, + base::Value::TYPE_STRING, &value)); + EXPECT_TRUE(value->GetAsString(&actual_str_value)); + EXPECT_EQ(recommended_pref::kRecommendedValue, + actual_str_value); + + // Test getting recommended value when no recommended value is present. + base::FundamentalValue tmp_dummy_value(true); + value = &tmp_dummy_value; + ASSERT_FALSE(pref_value_store_->GetRecommendedValue( + prefs::kDefaultPref, + base::Value::TYPE_STRING, &value)); + ASSERT_FALSE(value); + + // Test getting a preference value that the |PrefValueStore| + // does not contain. + value = &tmp_dummy_value; + ASSERT_FALSE(pref_value_store_->GetRecommendedValue( + prefs::kMissingPref, + base::Value::TYPE_STRING, &value)); + ASSERT_FALSE(value); +} + +TEST_F(PrefValueStoreTest, PrefChanges) { + // Check pref controlled by highest-priority store. + ExpectValueChangeNotifications(prefs::kManagedPref); + managed_pref_store_->NotifyPrefValueChanged(prefs::kManagedPref); + CheckAndClearValueChangeNotifications(); + + ExpectValueChangeNotifications(prefs::kManagedPref); + extension_pref_store_->NotifyPrefValueChanged(prefs::kManagedPref); + CheckAndClearValueChangeNotifications(); + + ExpectValueChangeNotifications(prefs::kManagedPref); + command_line_pref_store_->NotifyPrefValueChanged(prefs::kManagedPref); + CheckAndClearValueChangeNotifications(); + + ExpectValueChangeNotifications(prefs::kManagedPref); + user_pref_store_->NotifyPrefValueChanged(prefs::kManagedPref); + CheckAndClearValueChangeNotifications(); + + ExpectValueChangeNotifications(prefs::kManagedPref); + recommended_pref_store_->NotifyPrefValueChanged(prefs::kManagedPref); + CheckAndClearValueChangeNotifications(); + + ExpectValueChangeNotifications(prefs::kManagedPref); + default_pref_store_->NotifyPrefValueChanged(prefs::kManagedPref); + CheckAndClearValueChangeNotifications(); + + // Check pref controlled by user store. + ExpectValueChangeNotifications(prefs::kUserPref); + managed_pref_store_->NotifyPrefValueChanged(prefs::kUserPref); + CheckAndClearValueChangeNotifications(); + + ExpectValueChangeNotifications(prefs::kUserPref); + extension_pref_store_->NotifyPrefValueChanged(prefs::kUserPref); + CheckAndClearValueChangeNotifications(); + + ExpectValueChangeNotifications(prefs::kUserPref); + command_line_pref_store_->NotifyPrefValueChanged(prefs::kUserPref); + CheckAndClearValueChangeNotifications(); + + ExpectValueChangeNotifications(prefs::kUserPref); + user_pref_store_->NotifyPrefValueChanged(prefs::kUserPref); + CheckAndClearValueChangeNotifications(); + + ExpectValueChangeNotifications(prefs::kUserPref); + recommended_pref_store_->NotifyPrefValueChanged(prefs::kUserPref); + CheckAndClearValueChangeNotifications(); + + ExpectValueChangeNotifications(prefs::kUserPref); + default_pref_store_->NotifyPrefValueChanged(prefs::kUserPref); + CheckAndClearValueChangeNotifications(); + + // Check pref controlled by default-pref store. + ExpectValueChangeNotifications(prefs::kDefaultPref); + managed_pref_store_->NotifyPrefValueChanged(prefs::kDefaultPref); + CheckAndClearValueChangeNotifications(); + + ExpectValueChangeNotifications(prefs::kDefaultPref); + extension_pref_store_->NotifyPrefValueChanged(prefs::kDefaultPref); + CheckAndClearValueChangeNotifications(); + + ExpectValueChangeNotifications(prefs::kDefaultPref); + command_line_pref_store_->NotifyPrefValueChanged(prefs::kDefaultPref); + CheckAndClearValueChangeNotifications(); + + ExpectValueChangeNotifications(prefs::kDefaultPref); + user_pref_store_->NotifyPrefValueChanged(prefs::kDefaultPref); + CheckAndClearValueChangeNotifications(); + + ExpectValueChangeNotifications(prefs::kDefaultPref); + recommended_pref_store_->NotifyPrefValueChanged(prefs::kDefaultPref); + CheckAndClearValueChangeNotifications(); + + ExpectValueChangeNotifications(prefs::kDefaultPref); + default_pref_store_->NotifyPrefValueChanged(prefs::kDefaultPref); + CheckAndClearValueChangeNotifications(); +} + +TEST_F(PrefValueStoreTest, OnInitializationCompleted) { + EXPECT_CALL(pref_notifier_, OnInitializationCompleted(true)).Times(0); + managed_pref_store_->SetInitializationCompleted(); + extension_pref_store_->SetInitializationCompleted(); + command_line_pref_store_->SetInitializationCompleted(); + recommended_pref_store_->SetInitializationCompleted(); + default_pref_store_->SetInitializationCompleted(); + Mock::VerifyAndClearExpectations(&pref_notifier_); + + // The notification should only be triggered after the last store is done. + EXPECT_CALL(pref_notifier_, OnInitializationCompleted(true)).Times(1); + user_pref_store_->SetInitializationCompleted(); + Mock::VerifyAndClearExpectations(&pref_notifier_); +} + +TEST_F(PrefValueStoreTest, PrefValueInManagedStore) { + EXPECT_TRUE(pref_value_store_->PrefValueInManagedStore( + prefs::kManagedPref)); + EXPECT_FALSE(pref_value_store_->PrefValueInManagedStore( + prefs::kExtensionPref)); + EXPECT_FALSE(pref_value_store_->PrefValueInManagedStore( + prefs::kCommandLinePref)); + EXPECT_FALSE(pref_value_store_->PrefValueInManagedStore( + prefs::kUserPref)); + EXPECT_FALSE(pref_value_store_->PrefValueInManagedStore( + prefs::kRecommendedPref)); + EXPECT_FALSE(pref_value_store_->PrefValueInManagedStore( + prefs::kDefaultPref)); + EXPECT_FALSE(pref_value_store_->PrefValueInManagedStore( + prefs::kMissingPref)); +} + +TEST_F(PrefValueStoreTest, PrefValueInExtensionStore) { + EXPECT_TRUE(pref_value_store_->PrefValueInExtensionStore( + prefs::kManagedPref)); + EXPECT_TRUE(pref_value_store_->PrefValueInExtensionStore( + prefs::kExtensionPref)); + EXPECT_FALSE(pref_value_store_->PrefValueInExtensionStore( + prefs::kCommandLinePref)); + EXPECT_FALSE(pref_value_store_->PrefValueInExtensionStore( + prefs::kUserPref)); + EXPECT_FALSE(pref_value_store_->PrefValueInExtensionStore( + prefs::kRecommendedPref)); + EXPECT_FALSE(pref_value_store_->PrefValueInExtensionStore( + prefs::kDefaultPref)); + EXPECT_FALSE(pref_value_store_->PrefValueInExtensionStore( + prefs::kMissingPref)); +} + +TEST_F(PrefValueStoreTest, PrefValueInUserStore) { + EXPECT_TRUE(pref_value_store_->PrefValueInUserStore( + prefs::kManagedPref)); + EXPECT_TRUE(pref_value_store_->PrefValueInUserStore( + prefs::kExtensionPref)); + EXPECT_TRUE(pref_value_store_->PrefValueInUserStore( + prefs::kCommandLinePref)); + EXPECT_TRUE(pref_value_store_->PrefValueInUserStore( + prefs::kUserPref)); + EXPECT_FALSE(pref_value_store_->PrefValueInUserStore( + prefs::kRecommendedPref)); + EXPECT_FALSE(pref_value_store_->PrefValueInUserStore( + prefs::kDefaultPref)); + EXPECT_FALSE(pref_value_store_->PrefValueInUserStore( + prefs::kMissingPref)); +} + +TEST_F(PrefValueStoreTest, PrefValueFromExtensionStore) { + EXPECT_FALSE(pref_value_store_->PrefValueFromExtensionStore( + prefs::kManagedPref)); + EXPECT_TRUE(pref_value_store_->PrefValueFromExtensionStore( + prefs::kExtensionPref)); + EXPECT_FALSE(pref_value_store_->PrefValueFromExtensionStore( + prefs::kCommandLinePref)); + EXPECT_FALSE(pref_value_store_->PrefValueFromExtensionStore( + prefs::kUserPref)); + EXPECT_FALSE(pref_value_store_->PrefValueFromExtensionStore( + prefs::kRecommendedPref)); + EXPECT_FALSE(pref_value_store_->PrefValueFromExtensionStore( + prefs::kDefaultPref)); + EXPECT_FALSE(pref_value_store_->PrefValueFromExtensionStore( + prefs::kMissingPref)); +} + +TEST_F(PrefValueStoreTest, PrefValueFromUserStore) { + EXPECT_FALSE(pref_value_store_->PrefValueFromUserStore( + prefs::kManagedPref)); + EXPECT_FALSE(pref_value_store_->PrefValueFromUserStore( + prefs::kExtensionPref)); + EXPECT_FALSE(pref_value_store_->PrefValueFromUserStore( + prefs::kCommandLinePref)); + EXPECT_TRUE(pref_value_store_->PrefValueFromUserStore( + prefs::kUserPref)); + EXPECT_FALSE(pref_value_store_->PrefValueFromUserStore( + prefs::kRecommendedPref)); + EXPECT_FALSE(pref_value_store_->PrefValueFromUserStore( + prefs::kDefaultPref)); + EXPECT_FALSE(pref_value_store_->PrefValueFromUserStore( + prefs::kMissingPref)); +} + +TEST_F(PrefValueStoreTest, PrefValueFromRecommendedStore) { + EXPECT_FALSE(pref_value_store_->PrefValueFromRecommendedStore( + prefs::kManagedPref)); + EXPECT_FALSE(pref_value_store_->PrefValueFromRecommendedStore( + prefs::kExtensionPref)); + EXPECT_FALSE(pref_value_store_->PrefValueFromRecommendedStore( + prefs::kCommandLinePref)); + EXPECT_FALSE(pref_value_store_->PrefValueFromRecommendedStore( + prefs::kUserPref)); + EXPECT_TRUE(pref_value_store_->PrefValueFromRecommendedStore( + prefs::kRecommendedPref)); + EXPECT_FALSE(pref_value_store_->PrefValueFromRecommendedStore( + prefs::kDefaultPref)); + EXPECT_FALSE(pref_value_store_->PrefValueFromRecommendedStore( + prefs::kMissingPref)); +} + +TEST_F(PrefValueStoreTest, PrefValueFromDefaultStore) { + EXPECT_FALSE(pref_value_store_->PrefValueFromDefaultStore( + prefs::kManagedPref)); + EXPECT_FALSE(pref_value_store_->PrefValueFromDefaultStore( + prefs::kExtensionPref)); + EXPECT_FALSE(pref_value_store_->PrefValueFromDefaultStore( + prefs::kCommandLinePref)); + EXPECT_FALSE(pref_value_store_->PrefValueFromDefaultStore( + prefs::kUserPref)); + EXPECT_FALSE(pref_value_store_->PrefValueFromDefaultStore( + prefs::kRecommendedPref)); + EXPECT_TRUE(pref_value_store_->PrefValueFromDefaultStore( + prefs::kDefaultPref)); + EXPECT_FALSE(pref_value_store_->PrefValueFromDefaultStore( + prefs::kMissingPref)); +} + +TEST_F(PrefValueStoreTest, PrefValueUserModifiable) { + EXPECT_FALSE(pref_value_store_->PrefValueUserModifiable( + prefs::kManagedPref)); + EXPECT_FALSE(pref_value_store_->PrefValueUserModifiable( + prefs::kExtensionPref)); + EXPECT_FALSE(pref_value_store_->PrefValueUserModifiable( + prefs::kCommandLinePref)); + EXPECT_TRUE(pref_value_store_->PrefValueUserModifiable( + prefs::kUserPref)); + EXPECT_TRUE(pref_value_store_->PrefValueUserModifiable( + prefs::kRecommendedPref)); + EXPECT_TRUE(pref_value_store_->PrefValueUserModifiable( + prefs::kDefaultPref)); + EXPECT_TRUE(pref_value_store_->PrefValueUserModifiable( + prefs::kMissingPref)); +} + +TEST_F(PrefValueStoreTest, PrefValueExtensionModifiable) { + EXPECT_FALSE(pref_value_store_->PrefValueExtensionModifiable( + prefs::kManagedPref)); + EXPECT_TRUE(pref_value_store_->PrefValueExtensionModifiable( + prefs::kExtensionPref)); + EXPECT_TRUE(pref_value_store_->PrefValueExtensionModifiable( + prefs::kCommandLinePref)); + EXPECT_TRUE(pref_value_store_->PrefValueExtensionModifiable( + prefs::kUserPref)); + EXPECT_TRUE(pref_value_store_->PrefValueExtensionModifiable( + prefs::kRecommendedPref)); + EXPECT_TRUE(pref_value_store_->PrefValueExtensionModifiable( + prefs::kDefaultPref)); + EXPECT_TRUE(pref_value_store_->PrefValueExtensionModifiable( + prefs::kMissingPref)); +} diff --git a/base/prefs/testing_pref_service.cc b/base/prefs/testing_pref_service.cc new file mode 100644 index 0000000000..b96268a1ce --- /dev/null +++ b/base/prefs/testing_pref_service.cc @@ -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/prefs/testing_pref_service.h" + +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/prefs/default_pref_store.h" +#include "base/prefs/pref_notifier_impl.h" +#include "base/prefs/pref_registry_simple.h" +#include "base/prefs/pref_value_store.h" +#include "testing/gtest/include/gtest/gtest.h" + +template <> +TestingPrefServiceBase::TestingPrefServiceBase( + TestingPrefStore* managed_prefs, + TestingPrefStore* user_prefs, + TestingPrefStore* recommended_prefs, + PrefRegistry* pref_registry, + PrefNotifierImpl* pref_notifier) + : PrefService( + pref_notifier, + new PrefValueStore(managed_prefs, + NULL, + NULL, + user_prefs, + recommended_prefs, + pref_registry->defaults().get(), + pref_notifier), + user_prefs, + pref_registry, + base::Bind(&TestingPrefServiceBase::HandleReadError), + false), + managed_prefs_(managed_prefs), + user_prefs_(user_prefs), + recommended_prefs_(recommended_prefs) {} + +TestingPrefServiceSimple::TestingPrefServiceSimple() + : TestingPrefServiceBase( + new TestingPrefStore(), + new TestingPrefStore(), + new TestingPrefStore(), + new PrefRegistrySimple(), + new PrefNotifierImpl()) { +} + +TestingPrefServiceSimple::~TestingPrefServiceSimple() { +} + +PrefRegistrySimple* TestingPrefServiceSimple::registry() { + return static_cast(DeprecatedGetPrefRegistry()); +} diff --git a/base/prefs/testing_pref_service.h b/base/prefs/testing_pref_service.h new file mode 100644 index 0000000000..1af4ba6d1e --- /dev/null +++ b/base/prefs/testing_pref_service.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_PREFS_TESTING_PREF_SERVICE_H_ +#define BASE_PREFS_TESTING_PREF_SERVICE_H_ + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/prefs/pref_registry.h" +#include "base/prefs/pref_service.h" +#include "base/prefs/testing_pref_store.h" + +class PrefNotifierImpl; +class PrefRegistrySimple; +class TestingPrefStore; + +// A PrefService subclass for testing. It operates totally in memory and +// provides additional API for manipulating preferences at the different levels +// (managed, extension, user) conveniently. +// +// Use this via its specializations, e.g. TestingPrefServiceSimple. +template +class TestingPrefServiceBase : public SuperPrefService { + public: + virtual ~TestingPrefServiceBase(); + + // Read the value of a preference from the managed layer. Returns NULL if the + // preference is not defined at the managed layer. + const base::Value* GetManagedPref(const char* path) const; + + // Set a preference on the managed layer and fire observers if the preference + // changed. Assumes ownership of |value|. + void SetManagedPref(const char* path, base::Value* value); + + // Clear the preference on the managed layer and fire observers if the + // preference has been defined previously. + void RemoveManagedPref(const char* path); + + // Similar to the above, but for user preferences. + const base::Value* GetUserPref(const char* path) const; + void SetUserPref(const char* path, base::Value* value); + void RemoveUserPref(const char* path); + + // Similar to the above, but for recommended policy preferences. + const base::Value* GetRecommendedPref(const char* path) const; + void SetRecommendedPref(const char* path, base::Value* value); + void RemoveRecommendedPref(const char* path); + + // Do-nothing implementation for TestingPrefService. + static void HandleReadError(PersistentPrefStore::PrefReadError error) {} + + protected: + TestingPrefServiceBase( + TestingPrefStore* managed_prefs, + TestingPrefStore* user_prefs, + TestingPrefStore* recommended_prefs, + ConstructionPrefRegistry* pref_registry, + PrefNotifierImpl* pref_notifier); + + private: + // Reads the value of the preference indicated by |path| from |pref_store|. + // Returns NULL if the preference was not found. + const base::Value* GetPref(TestingPrefStore* pref_store, + const char* path) const; + + // Sets the value for |path| in |pref_store|. + void SetPref(TestingPrefStore* pref_store, const char* path, + base::Value* value); + + // Removes the preference identified by |path| from |pref_store|. + void RemovePref(TestingPrefStore* pref_store, const char* path); + + // Pointers to the pref stores our value store uses. + scoped_refptr managed_prefs_; + scoped_refptr user_prefs_; + scoped_refptr recommended_prefs_; + + DISALLOW_COPY_AND_ASSIGN(TestingPrefServiceBase); +}; + +// Test version of PrefService. +class TestingPrefServiceSimple + : public TestingPrefServiceBase { + public: + TestingPrefServiceSimple(); + virtual ~TestingPrefServiceSimple(); + + // This is provided as a convenience for registering preferences on + // an existing TestingPrefServiceSimple instance. On a production + // PrefService you would do all registrations before constructing + // it, passing it a PrefRegistry via its constructor (or via + // e.g. PrefServiceBuilder). + PrefRegistrySimple* registry(); + + private: + DISALLOW_COPY_AND_ASSIGN(TestingPrefServiceSimple); +}; + +template<> +TestingPrefServiceBase::TestingPrefServiceBase( + TestingPrefStore* managed_prefs, + TestingPrefStore* user_prefs, + TestingPrefStore* recommended_prefs, + PrefRegistry* pref_registry, + PrefNotifierImpl* pref_notifier); + +template +TestingPrefServiceBase< + SuperPrefService, ConstructionPrefRegistry>::~TestingPrefServiceBase() { +} + +template +const base::Value* TestingPrefServiceBase< + SuperPrefService, ConstructionPrefRegistry>::GetManagedPref( + const char* path) const { + return GetPref(managed_prefs_.get(), path); +} + +template +void TestingPrefServiceBase< + SuperPrefService, ConstructionPrefRegistry>::SetManagedPref( + const char* path, base::Value* value) { + SetPref(managed_prefs_.get(), path, value); +} + +template +void TestingPrefServiceBase< + SuperPrefService, ConstructionPrefRegistry>::RemoveManagedPref( + const char* path) { + RemovePref(managed_prefs_.get(), path); +} + +template +const base::Value* TestingPrefServiceBase< + SuperPrefService, ConstructionPrefRegistry>::GetUserPref( + const char* path) const { + return GetPref(user_prefs_.get(), path); +} + +template +void TestingPrefServiceBase< + SuperPrefService, ConstructionPrefRegistry>::SetUserPref( + const char* path, base::Value* value) { + SetPref(user_prefs_.get(), path, value); +} + +template +void TestingPrefServiceBase< + SuperPrefService, ConstructionPrefRegistry>::RemoveUserPref( + const char* path) { + RemovePref(user_prefs_.get(), path); +} + +template +const base::Value* TestingPrefServiceBase< + SuperPrefService, ConstructionPrefRegistry>::GetRecommendedPref( + const char* path) const { + return GetPref(recommended_prefs_, path); +} + +template +void TestingPrefServiceBase< + SuperPrefService, ConstructionPrefRegistry>::SetRecommendedPref( + const char* path, base::Value* value) { + SetPref(recommended_prefs_.get(), path, value); +} + +template +void TestingPrefServiceBase< + SuperPrefService, ConstructionPrefRegistry>::RemoveRecommendedPref( + const char* path) { + RemovePref(recommended_prefs_.get(), path); +} + +template +const base::Value* TestingPrefServiceBase< + SuperPrefService, ConstructionPrefRegistry>::GetPref( + TestingPrefStore* pref_store, const char* path) const { + const base::Value* res; + return pref_store->GetValue(path, &res) ? res : NULL; +} + +template +void TestingPrefServiceBase< + SuperPrefService, ConstructionPrefRegistry>::SetPref( + TestingPrefStore* pref_store, const char* path, base::Value* value) { + pref_store->SetValue(path, value); +} + +template +void TestingPrefServiceBase< + SuperPrefService, ConstructionPrefRegistry>::RemovePref( + TestingPrefStore* pref_store, const char* path) { + pref_store->RemoveValue(path); +} + +#endif // BASE_PREFS_TESTING_PREF_SERVICE_H_ diff --git a/base/prefs/testing_pref_store.cc b/base/prefs/testing_pref_store.cc new file mode 100644 index 0000000000..fcb48cee2e --- /dev/null +++ b/base/prefs/testing_pref_store.cc @@ -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. + +#include "base/prefs/testing_pref_store.h" + +#include "base/memory/scoped_ptr.h" +#include "base/values.h" + +TestingPrefStore::TestingPrefStore() + : read_only_(true), + init_complete_(false) { +} + +bool TestingPrefStore::GetValue(const std::string& key, + const base::Value** value) const { + return prefs_.GetValue(key, value); +} + +bool TestingPrefStore::GetMutableValue(const std::string& key, + base::Value** value) { + return prefs_.GetValue(key, value); +} + +void TestingPrefStore::AddObserver(PrefStore::Observer* observer) { + observers_.AddObserver(observer); +} + +void TestingPrefStore::RemoveObserver(PrefStore::Observer* observer) { + observers_.RemoveObserver(observer); +} + +size_t TestingPrefStore::NumberOfObservers() const { + return observers_.size(); +} + +bool TestingPrefStore::IsInitializationComplete() const { + return init_complete_; +} + +void TestingPrefStore::SetValue(const std::string& key, base::Value* value) { + if (prefs_.SetValue(key, value)) + NotifyPrefValueChanged(key); +} + +void TestingPrefStore::SetValueSilently(const std::string& key, + base::Value* value) { + prefs_.SetValue(key, value); +} + +void TestingPrefStore::RemoveValue(const std::string& key) { + if (prefs_.RemoveValue(key)) + NotifyPrefValueChanged(key); +} + +void TestingPrefStore::MarkNeedsEmptyValue(const std::string& key) { +} + +bool TestingPrefStore::ReadOnly() const { + return read_only_; +} + +PersistentPrefStore::PrefReadError TestingPrefStore::GetReadError() const { + return PersistentPrefStore::PREF_READ_ERROR_NONE; +} + +PersistentPrefStore::PrefReadError TestingPrefStore::ReadPrefs() { + NotifyInitializationCompleted(); + return PersistentPrefStore::PREF_READ_ERROR_NONE; +} + +void TestingPrefStore::ReadPrefsAsync(ReadErrorDelegate* error_delegate_raw) { + scoped_ptr error_delegate(error_delegate_raw); + NotifyInitializationCompleted(); +} + +void TestingPrefStore::SetInitializationCompleted() { + init_complete_ = true; + NotifyInitializationCompleted(); +} + +void TestingPrefStore::NotifyPrefValueChanged(const std::string& key) { + FOR_EACH_OBSERVER(Observer, observers_, OnPrefValueChanged(key)); +} + +void TestingPrefStore::NotifyInitializationCompleted() { + FOR_EACH_OBSERVER(Observer, observers_, OnInitializationCompleted(true)); +} + +void TestingPrefStore::ReportValueChanged(const std::string& key) { + FOR_EACH_OBSERVER(Observer, observers_, OnPrefValueChanged(key)); +} + +void TestingPrefStore::SetString(const std::string& key, + const std::string& value) { + SetValue(key, new base::StringValue(value)); +} + +void TestingPrefStore::SetInteger(const std::string& key, int value) { + SetValue(key, new base::FundamentalValue(value)); +} + +void TestingPrefStore::SetBoolean(const std::string& key, bool value) { + SetValue(key, new base::FundamentalValue(value)); +} + +bool TestingPrefStore::GetString(const std::string& key, + std::string* value) const { + const base::Value* stored_value; + if (!prefs_.GetValue(key, &stored_value) || !stored_value) + return false; + + return stored_value->GetAsString(value); +} + +bool TestingPrefStore::GetInteger(const std::string& key, int* value) const { + const base::Value* stored_value; + if (!prefs_.GetValue(key, &stored_value) || !stored_value) + return false; + + return stored_value->GetAsInteger(value); +} + +bool TestingPrefStore::GetBoolean(const std::string& key, bool* value) const { + const base::Value* stored_value; + if (!prefs_.GetValue(key, &stored_value) || !stored_value) + return false; + + return stored_value->GetAsBoolean(value); +} + +void TestingPrefStore::set_read_only(bool read_only) { + read_only_ = read_only; +} + +TestingPrefStore::~TestingPrefStore() {} diff --git a/base/prefs/testing_pref_store.h b/base/prefs/testing_pref_store.h new file mode 100644 index 0000000000..a9f1e92253 --- /dev/null +++ b/base/prefs/testing_pref_store.h @@ -0,0 +1,84 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_PREFS_TESTING_PREF_STORE_H_ +#define BASE_PREFS_TESTING_PREF_STORE_H_ + +#include + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/observer_list.h" +#include "base/prefs/persistent_pref_store.h" +#include "base/prefs/pref_value_map.h" + +// |TestingPrefStore| is a preference store implementation that allows tests to +// explicitly manipulate the contents of the store, triggering notifications +// where appropriate. +class TestingPrefStore : public PersistentPrefStore { + public: + TestingPrefStore(); + + // Overriden from PrefStore. + virtual bool GetValue(const std::string& key, + const base::Value** result) const OVERRIDE; + virtual void AddObserver(PrefStore::Observer* observer) OVERRIDE; + virtual void RemoveObserver(PrefStore::Observer* observer) OVERRIDE; + virtual size_t NumberOfObservers() const OVERRIDE; + virtual bool IsInitializationComplete() const OVERRIDE; + + // PersistentPrefStore overrides: + virtual bool GetMutableValue(const std::string& key, + base::Value** result) OVERRIDE; + virtual void ReportValueChanged(const std::string& key) OVERRIDE; + virtual void SetValue(const std::string& key, base::Value* value) OVERRIDE; + virtual void SetValueSilently(const std::string& key, + base::Value* value) OVERRIDE; + virtual void RemoveValue(const std::string& key) OVERRIDE; + virtual void MarkNeedsEmptyValue(const std::string& key) OVERRIDE; + virtual bool ReadOnly() const OVERRIDE; + virtual PrefReadError GetReadError() const OVERRIDE; + virtual PersistentPrefStore::PrefReadError ReadPrefs() OVERRIDE; + virtual void ReadPrefsAsync(ReadErrorDelegate* error_delegate) OVERRIDE; + virtual void CommitPendingWrite() OVERRIDE {} + + // Marks the store as having completed initialization. + void SetInitializationCompleted(); + + // Used for tests to trigger notifications explicitly. + void NotifyPrefValueChanged(const std::string& key); + void NotifyInitializationCompleted(); + + // Some convenience getters/setters. + void SetString(const std::string& key, const std::string& value); + void SetInteger(const std::string& key, int value); + void SetBoolean(const std::string& key, bool value); + + bool GetString(const std::string& key, std::string* value) const; + bool GetInteger(const std::string& key, int* value) const; + bool GetBoolean(const std::string& key, bool* value) const; + + // Getter and Setter methods for setting and getting the state of the + // |TestingPrefStore|. + virtual void set_read_only(bool read_only); + + protected: + virtual ~TestingPrefStore(); + + private: + // Stores the preference values. + PrefValueMap prefs_; + + // Flag that indicates if the PrefStore is read-only + bool read_only_; + + // Whether initialization has been completed. + bool init_complete_; + + ObserverList observers_; + + DISALLOW_COPY_AND_ASSIGN(TestingPrefStore); +}; + +#endif // BASE_PREFS_TESTING_PREF_STORE_H_ diff --git a/base/prefs/value_map_pref_store.cc b/base/prefs/value_map_pref_store.cc new file mode 100644 index 0000000000..b4b5751111 --- /dev/null +++ b/base/prefs/value_map_pref_store.cc @@ -0,0 +1,45 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/prefs/value_map_pref_store.h" + +#include + +#include "base/stl_util.h" +#include "base/values.h" + +ValueMapPrefStore::ValueMapPrefStore() {} + +bool ValueMapPrefStore::GetValue(const std::string& key, + const base::Value** value) const { + return prefs_.GetValue(key, value); +} + +void ValueMapPrefStore::AddObserver(PrefStore::Observer* observer) { + observers_.AddObserver(observer); +} + +void ValueMapPrefStore::RemoveObserver(PrefStore::Observer* observer) { + observers_.RemoveObserver(observer); +} + +size_t ValueMapPrefStore::NumberOfObservers() const { + return observers_.size(); +} + +ValueMapPrefStore::~ValueMapPrefStore() {} + +void ValueMapPrefStore::SetValue(const std::string& key, base::Value* value) { + if (prefs_.SetValue(key, value)) + FOR_EACH_OBSERVER(Observer, observers_, OnPrefValueChanged(key)); +} + +void ValueMapPrefStore::RemoveValue(const std::string& key) { + if (prefs_.RemoveValue(key)) + FOR_EACH_OBSERVER(Observer, observers_, OnPrefValueChanged(key)); +} + +void ValueMapPrefStore::NotifyInitializationCompleted() { + FOR_EACH_OBSERVER(Observer, observers_, OnInitializationCompleted(true)); +} diff --git a/base/prefs/value_map_pref_store.h b/base/prefs/value_map_pref_store.h new file mode 100644 index 0000000000..c9c9b1c905 --- /dev/null +++ b/base/prefs/value_map_pref_store.h @@ -0,0 +1,52 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_PREFS_VALUE_MAP_PREF_STORE_H_ +#define BASE_PREFS_VALUE_MAP_PREF_STORE_H_ + +#include +#include + +#include "base/basictypes.h" +#include "base/observer_list.h" +#include "base/prefs/base_prefs_export.h" +#include "base/prefs/pref_store.h" +#include "base/prefs/pref_value_map.h" + +// A basic PrefStore implementation that uses a simple name-value map for +// storing the preference values. +class BASE_PREFS_EXPORT ValueMapPrefStore : public PrefStore { + public: + ValueMapPrefStore(); + + // PrefStore overrides: + virtual bool GetValue(const std::string& key, + const base::Value** value) const OVERRIDE; + virtual void AddObserver(PrefStore::Observer* observer) OVERRIDE; + virtual void RemoveObserver(PrefStore::Observer* observer) OVERRIDE; + virtual size_t NumberOfObservers() const OVERRIDE; + + protected: + virtual ~ValueMapPrefStore(); + + // Store a |value| for |key| in the store. Also generates an notification if + // the value changed. Assumes ownership of |value|, which must be non-NULL. + void SetValue(const std::string& key, base::Value* value); + + // Remove the value for |key| from the store. Sends a notification if there + // was a value to be removed. + void RemoveValue(const std::string& key); + + // Notify observers about the initialization completed event. + void NotifyInitializationCompleted(); + + private: + PrefValueMap prefs_; + + ObserverList observers_; + + DISALLOW_COPY_AND_ASSIGN(ValueMapPrefStore); +}; + +#endif // BASE_PREFS_VALUE_MAP_PREF_STORE_H_ diff --git a/base/process/internal_linux.cc b/base/process/internal_linux.cc new file mode 100644 index 0000000000..ee1107c179 --- /dev/null +++ b/base/process/internal_linux.cc @@ -0,0 +1,190 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/internal_linux.h" + +#include + +#include +#include +#include + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/threading/thread_restrictions.h" +#include "base/time/time.h" + +namespace base { +namespace internal { + +const char kProcDir[] = "/proc"; + +const char kStatFile[] = "stat"; + +base::FilePath GetProcPidDir(pid_t pid) { + return base::FilePath(kProcDir).Append(IntToString(pid)); +} + +pid_t ProcDirSlotToPid(const char* d_name) { + int i; + for (i = 0; i < NAME_MAX && d_name[i]; ++i) { + if (!IsAsciiDigit(d_name[i])) { + return 0; + } + } + if (i == NAME_MAX) + return 0; + + // Read the process's command line. + pid_t pid; + std::string pid_string(d_name); + if (!StringToInt(pid_string, &pid)) { + NOTREACHED(); + return 0; + } + return pid; +} + +bool ReadProcFile(const FilePath& file, std::string* buffer) { + buffer->clear(); + // Synchronously reading files in /proc is safe. + ThreadRestrictions::ScopedAllowIO allow_io; + + if (!file_util::ReadFileToString(file, buffer)) { + DLOG(WARNING) << "Failed to read " << file.MaybeAsASCII(); + return false; + } + return !buffer->empty(); +} + +bool ReadProcStats(pid_t pid, std::string* buffer) { + FilePath stat_file = internal::GetProcPidDir(pid).Append(kStatFile); + return ReadProcFile(stat_file, buffer); +} + +bool ParseProcStats(const std::string& stats_data, + std::vector* proc_stats) { + // |stats_data| may be empty if the process disappeared somehow. + // e.g. http://crbug.com/145811 + if (stats_data.empty()) + return false; + + // The stat file is formatted as: + // pid (process name) data1 data2 .... dataN + // Look for the closing paren by scanning backwards, to avoid being fooled by + // processes with ')' in the name. + size_t open_parens_idx = stats_data.find(" ("); + size_t close_parens_idx = stats_data.rfind(") "); + if (open_parens_idx == std::string::npos || + close_parens_idx == std::string::npos || + open_parens_idx > close_parens_idx) { + DLOG(WARNING) << "Failed to find matched parens in '" << stats_data << "'"; + NOTREACHED(); + return false; + } + open_parens_idx++; + + proc_stats->clear(); + // PID. + proc_stats->push_back(stats_data.substr(0, open_parens_idx)); + // Process name without parentheses. + proc_stats->push_back( + stats_data.substr(open_parens_idx + 1, + close_parens_idx - (open_parens_idx + 1))); + + // Split the rest. + std::vector other_stats; + SplitString(stats_data.substr(close_parens_idx + 2), ' ', &other_stats); + for (size_t i = 0; i < other_stats.size(); ++i) + proc_stats->push_back(other_stats[i]); + return true; +} + +typedef std::map ProcStatMap; +void ParseProcStat(const std::string& contents, ProcStatMap* output) { + typedef std::pair StringPair; + std::vector key_value_pairs; + SplitStringIntoKeyValuePairs(contents, ' ', '\n', &key_value_pairs); + for (size_t i = 0; i < key_value_pairs.size(); ++i) { + const StringPair& key_value_pair = key_value_pairs[i]; + output->insert(key_value_pair); + } +} + +int GetProcStatsFieldAsInt(const std::vector& proc_stats, + ProcStatsFields field_num) { + DCHECK_GE(field_num, VM_PPID); + CHECK_LT(static_cast(field_num), proc_stats.size()); + + int value; + return StringToInt(proc_stats[field_num], &value) ? value : 0; +} + +size_t GetProcStatsFieldAsSizeT(const std::vector& proc_stats, + ProcStatsFields field_num) { + DCHECK_GE(field_num, VM_PPID); + CHECK_LT(static_cast(field_num), proc_stats.size()); + + size_t value; + return StringToSizeT(proc_stats[field_num], &value) ? value : 0; +} + +int ReadProcStatsAndGetFieldAsInt(pid_t pid, + ProcStatsFields field_num) { + std::string stats_data; + if (!ReadProcStats(pid, &stats_data)) + return 0; + std::vector proc_stats; + if (!ParseProcStats(stats_data, &proc_stats)) + return 0; + return GetProcStatsFieldAsInt(proc_stats, field_num); +} + +size_t ReadProcStatsAndGetFieldAsSizeT(pid_t pid, + ProcStatsFields field_num) { + std::string stats_data; + if (!ReadProcStats(pid, &stats_data)) + return 0; + std::vector proc_stats; + if (!ParseProcStats(stats_data, &proc_stats)) + return 0; + return GetProcStatsFieldAsSizeT(proc_stats, field_num); +} + +Time GetBootTime() { + FilePath path("/proc/stat"); + std::string contents; + if (!ReadProcFile(path, &contents)) + return Time(); + ProcStatMap proc_stat; + ParseProcStat(contents, &proc_stat); + ProcStatMap::const_iterator btime_it = proc_stat.find("btime"); + if (btime_it == proc_stat.end()) + return Time(); + int btime; + if (!StringToInt(btime_it->second, &btime)) + return Time(); + return Time::FromTimeT(btime); +} + +TimeDelta ClockTicksToTimeDelta(int clock_ticks) { + // This queries the /proc-specific scaling factor which is + // conceptually the system hertz. To dump this value on another + // system, try + // od -t dL /proc/self/auxv + // and look for the number after 17 in the output; mine is + // 0000040 17 100 3 134512692 + // which means the answer is 100. + // It may be the case that this value is always 100. + static const int kHertz = sysconf(_SC_CLK_TCK); + + return TimeDelta::FromMicroseconds( + Time::kMicrosecondsPerSecond * clock_ticks / kHertz); +} + +} // namespace internal +} // namespace base diff --git a/base/process/internal_linux.h b/base/process/internal_linux.h new file mode 100644 index 0000000000..a10cee36e5 --- /dev/null +++ b/base/process/internal_linux.h @@ -0,0 +1,89 @@ +// 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 file contains internal routines that are called by other files in +// base/process/. + +#ifndef BASE_PROCESS_LINUX_INTERNAL_H_ +#define BASE_PROCESS_LINUX_INTERNAL_H_ + +#include "base/files/file_path.h" + +namespace base { + +class Time; +class TimeDelta; + +namespace internal { + +// "/proc" +extern const char kProcDir[]; + +// "stat" +extern const char kStatFile[]; + +// Returns a FilePath to "/proc/pid". +base::FilePath GetProcPidDir(pid_t pid); + +// Take a /proc directory entry named |d_name|, and if it is the directory for +// a process, convert it to a pid_t. +// Returns 0 on failure. +// e.g. /proc/self/ will return 0, whereas /proc/1234 will return 1234. +pid_t ProcDirSlotToPid(const char* d_name); + +// Reads /proc//stat into |buffer|. Returns true if the file can be read +// and is non-empty. +bool ReadProcStats(pid_t pid, std::string* buffer); + +// Takes |stats_data| and populates |proc_stats| with the values split by +// spaces. Taking into account the 2nd field may, in itself, contain spaces. +// Returns true if successful. +bool ParseProcStats(const std::string& stats_data, + std::vector* proc_stats); + +// Fields from /proc//stat, 0-based. See man 5 proc. +// If the ordering ever changes, carefully review functions that use these +// values. +enum ProcStatsFields { + VM_COMM = 1, // Filename of executable, without parentheses. + VM_STATE = 2, // Letter indicating the state of the process. + VM_PPID = 3, // PID of the parent. + VM_PGRP = 4, // Process group id. + VM_UTIME = 13, // Time scheduled in user mode in clock ticks. + VM_STIME = 14, // Time scheduled in kernel mode in clock ticks. + VM_NUMTHREADS = 19, // Number of threads. + VM_STARTTIME = 21, // The time the process started in clock ticks. + VM_VSIZE = 22, // Virtual memory size in bytes. + VM_RSS = 23, // Resident Set Size in pages. +}; + +// Reads the |field_num|th field from |proc_stats|. Returns 0 on failure. +// This version does not handle the first 3 values, since the first value is +// simply |pid|, and the next two values are strings. +int GetProcStatsFieldAsInt(const std::vector& proc_stats, + ProcStatsFields field_num); + +// Same as GetProcStatsFieldAsInt(), but for size_t values. +size_t GetProcStatsFieldAsSizeT(const std::vector& proc_stats, + ProcStatsFields field_num); + +// Convenience wrapper around GetProcStatsFieldAsInt(), ParseProcStats() and +// ReadProcStats(). See GetProcStatsFieldAsInt() for details. +int ReadProcStatsAndGetFieldAsInt(pid_t pid, + ProcStatsFields field_num); + +// Same as ReadProcStatsAndGetFieldAsInt() but for size_t values. +size_t ReadProcStatsAndGetFieldAsSizeT(pid_t pid, + ProcStatsFields field_num); + +// Returns the time that the OS started. Clock ticks are relative to this. +Time GetBootTime(); + +// Converts Linux clock ticks to a wall time delta. +TimeDelta ClockTicksToTimeDelta(int clock_ticks); + +} // namespace internal +} // namespace base + +#endif // BASE_PROCESS_LINUX_INTERNAL_H_ diff --git a/base/process/kill.cc b/base/process/kill.cc new file mode 100644 index 0000000000..caca3484a1 --- /dev/null +++ b/base/process/kill.cc @@ -0,0 +1,26 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/kill.h" + +#include "base/process/process_iterator.h" + +namespace base { + +bool KillProcesses(const FilePath::StringType& executable_name, + int exit_code, + const ProcessFilter* filter) { + bool result = true; + NamedProcessIterator iter(executable_name, filter); + while (const ProcessEntry* entry = iter.NextProcessEntry()) { +#if defined(OS_WIN) + result &= KillProcessById(entry->pid(), exit_code, true); +#else + result &= KillProcess(entry->pid(), exit_code, true); +#endif + } + return result; +} + +} // namespace base diff --git a/base/process/kill.h b/base/process/kill.h new file mode 100644 index 0000000000..f81ea9090d --- /dev/null +++ b/base/process/kill.h @@ -0,0 +1,144 @@ +// 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 file contains routines to kill processes and get the exit code and +// termination status. + +#ifndef BASE_PROCESS_KILL_H_ +#define BASE_PROCESS_KILL_H_ + +#include "base/files/file_path.h" +#include "base/process/process_handle.h" +#include "base/time/time.h" + +namespace base { + +class ProcessFilter; + +// Return status values from GetTerminationStatus. Don't use these as +// exit code arguments to KillProcess*(), use platform/application +// specific values instead. +enum TerminationStatus { + TERMINATION_STATUS_NORMAL_TERMINATION, // zero exit status + TERMINATION_STATUS_ABNORMAL_TERMINATION, // non-zero exit status + TERMINATION_STATUS_PROCESS_WAS_KILLED, // e.g. SIGKILL or task manager kill + TERMINATION_STATUS_PROCESS_CRASHED, // e.g. Segmentation fault + TERMINATION_STATUS_STILL_RUNNING, // child hasn't exited yet + TERMINATION_STATUS_MAX_ENUM +}; + +// Attempts to kill all the processes on the current machine that were launched +// from the given executable name, ending them with the given exit code. If +// filter is non-null, then only processes selected by the filter are killed. +// Returns true if all processes were able to be killed off, false if at least +// one couldn't be killed. +BASE_EXPORT bool KillProcesses(const FilePath::StringType& executable_name, + int exit_code, + const ProcessFilter* filter); + +// Attempts to kill the process identified by the given process +// entry structure, giving it the specified exit code. If |wait| is true, wait +// for the process to be actually terminated before returning. +// Returns true if this is successful, false otherwise. +BASE_EXPORT bool KillProcess(ProcessHandle process, int exit_code, bool wait); + +#if defined(OS_POSIX) +// Attempts to kill the process group identified by |process_group_id|. Returns +// true on success. +BASE_EXPORT bool KillProcessGroup(ProcessHandle process_group_id); +#endif // defined(OS_POSIX) + +#if defined(OS_WIN) +BASE_EXPORT bool KillProcessById(ProcessId process_id, + int exit_code, + bool wait); +#endif // defined(OS_WIN) + +// Get the termination status of the process by interpreting the +// circumstances of the child process' death. |exit_code| is set to +// the status returned by waitpid() on POSIX, and from +// GetExitCodeProcess() on Windows. |exit_code| may be NULL if the +// caller is not interested in it. Note that on Linux, this function +// will only return a useful result the first time it is called after +// the child exits (because it will reap the child and the information +// will no longer be available). +BASE_EXPORT TerminationStatus GetTerminationStatus(ProcessHandle handle, + int* exit_code); + +#if defined(OS_POSIX) +// Wait for the process to exit and get the termination status. See +// GetTerminationStatus for more information. On POSIX systems, we can't call +// WaitForExitCode and then GetTerminationStatus as the child will be reaped +// when WaitForExitCode return and this information will be lost. +BASE_EXPORT TerminationStatus WaitForTerminationStatus(ProcessHandle handle, + int* exit_code); +#endif // defined(OS_POSIX) + +// Waits for process to exit. On POSIX systems, if the process hasn't been +// signaled then puts the exit code in |exit_code|; otherwise it's considered +// a failure. On Windows |exit_code| is always filled. Returns true on success, +// and closes |handle| in any case. +BASE_EXPORT bool WaitForExitCode(ProcessHandle handle, int* exit_code); + +// Waits for process to exit. If it did exit within |timeout_milliseconds|, +// then puts the exit code in |exit_code|, and returns true. +// In POSIX systems, if the process has been signaled then |exit_code| is set +// to -1. Returns false on failure (the caller is then responsible for closing +// |handle|). +// The caller is always responsible for closing the |handle|. +BASE_EXPORT bool WaitForExitCodeWithTimeout(ProcessHandle handle, + int* exit_code, + base::TimeDelta timeout); + +// Wait for all the processes based on the named executable to exit. If filter +// is non-null, then only processes selected by the filter are waited on. +// Returns after all processes have exited or wait_milliseconds have expired. +// Returns true if all the processes exited, false otherwise. +BASE_EXPORT bool WaitForProcessesToExit( + const FilePath::StringType& executable_name, + base::TimeDelta wait, + const ProcessFilter* filter); + +// Wait for a single process to exit. Return true if it exited cleanly within +// the given time limit. On Linux |handle| must be a child process, however +// on Mac and Windows it can be any process. +BASE_EXPORT bool WaitForSingleProcess(ProcessHandle handle, + base::TimeDelta wait); + +// Waits a certain amount of time (can be 0) for all the processes with a given +// executable name to exit, then kills off any of them that are still around. +// If filter is non-null, then only processes selected by the filter are waited +// on. Killed processes are ended with the given exit code. Returns false if +// any processes needed to be killed, true if they all exited cleanly within +// the wait_milliseconds delay. +BASE_EXPORT bool CleanupProcesses(const FilePath::StringType& executable_name, + base::TimeDelta wait, + int exit_code, + const ProcessFilter* filter); + +// This method ensures that the specified process eventually terminates, and +// then it closes the given process handle. +// +// It assumes that the process has already been signalled to exit, and it +// begins by waiting a small amount of time for it to exit. If the process +// does not appear to have exited, then this function starts to become +// aggressive about ensuring that the process terminates. +// +// On Linux this method does not block the calling thread. +// On OS X this method may block for up to 2 seconds. +// +// NOTE: The process handle must have been opened with the PROCESS_TERMINATE +// and SYNCHRONIZE permissions. +// +BASE_EXPORT void EnsureProcessTerminated(ProcessHandle process_handle); + +#if defined(OS_POSIX) && !defined(OS_MACOSX) +// The nicer version of EnsureProcessTerminated() that is patient and will +// wait for |process_handle| to finish and then reap it. +BASE_EXPORT void EnsureProcessGetsReaped(ProcessHandle process_handle); +#endif + +} // namespace base + +#endif // BASE_PROCESS_KILL_H_ diff --git a/base/process/kill_mac.cc b/base/process/kill_mac.cc new file mode 100644 index 0000000000..5ebca5d007 --- /dev/null +++ b/base/process/kill_mac.cc @@ -0,0 +1,173 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/kill.h" + +#include +#include +#include +#include + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" + +namespace base { + +namespace { + +const int kWaitBeforeKillSeconds = 2; + +// Reap |child| process. This call blocks until completion. +void BlockingReap(pid_t child) { + const pid_t result = HANDLE_EINTR(waitpid(child, NULL, 0)); + if (result == -1) { + DPLOG(ERROR) << "waitpid(" << child << ", NULL, 0)"; + } +} + +// Waits for |timeout| seconds for the given |child| to exit and reap it. If +// the child doesn't exit within the time specified, kills it. +// +// This function takes two approaches: first, it tries to use kqueue to +// observe when the process exits. kevent can monitor a kqueue with a +// timeout, so this method is preferred to wait for a specified period of +// time. Once the kqueue indicates the process has exited, waitpid will reap +// the exited child. If the kqueue doesn't provide an exit event notification, +// before the timeout expires, or if the kqueue fails or misbehaves, the +// process will be mercilessly killed and reaped. +// +// A child process passed to this function may be in one of several states: +// running, terminated and not yet reaped, and (apparently, and unfortunately) +// terminated and already reaped. Normally, a process will at least have been +// asked to exit before this function is called, but this is not required. +// If a process is terminating and unreaped, there may be a window between the +// time that kqueue will no longer recognize it and when it becomes an actual +// zombie that a non-blocking (WNOHANG) waitpid can reap. This condition is +// detected when kqueue indicates that the process is not running and a +// non-blocking waitpid fails to reap the process but indicates that it is +// still running. In this event, a blocking attempt to reap the process +// collects the known-dying child, preventing zombies from congregating. +// +// In the event that the kqueue misbehaves entirely, as it might under a +// EMFILE condition ("too many open files", or out of file descriptors), this +// function will forcibly kill and reap the child without delay. This +// eliminates another potential zombie vector. (If you're out of file +// descriptors, you're probably deep into something else, but that doesn't +// mean that zombies be allowed to kick you while you're down.) +// +// The fact that this function seemingly can be called to wait on a child +// that's not only already terminated but already reaped is a bit of a +// problem: a reaped child's pid can be reclaimed and may refer to a distinct +// process in that case. The fact that this function can seemingly be called +// to wait on a process that's not even a child is also a problem: kqueue will +// work in that case, but waitpid won't, and killing a non-child might not be +// the best approach. +void WaitForChildToDie(pid_t child, int timeout) { + DCHECK(child > 0); + DCHECK(timeout > 0); + + // DON'T ADD ANY EARLY RETURNS TO THIS FUNCTION without ensuring that + // |child| has been reaped. Specifically, even if a kqueue, kevent, or other + // call fails, this function should fall back to the last resort of trying + // to kill and reap the process. Not observing this rule will resurrect + // zombies. + + int result; + + int kq = HANDLE_EINTR(kqueue()); + if (kq == -1) { + DPLOG(ERROR) << "kqueue()"; + } else { + file_util::ScopedFD auto_close_kq(&kq); + + struct kevent change = {0}; + EV_SET(&change, child, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL); + result = HANDLE_EINTR(kevent(kq, &change, 1, NULL, 0, NULL)); + + if (result == -1) { + if (errno != ESRCH) { + DPLOG(ERROR) << "kevent (setup " << child << ")"; + } else { + // At this point, one of the following has occurred: + // 1. The process has died but has not yet been reaped. + // 2. The process has died and has already been reaped. + // 3. The process is in the process of dying. It's no longer + // kqueueable, but it may not be waitable yet either. Mark calls + // this case the "zombie death race". + + result = HANDLE_EINTR(waitpid(child, NULL, WNOHANG)); + + if (result != 0) { + // A positive result indicates case 1. waitpid succeeded and reaped + // the child. A result of -1 indicates case 2. The child has already + // been reaped. In both of these cases, no further action is + // necessary. + return; + } + + // |result| is 0, indicating case 3. The process will be waitable in + // short order. Fall back out of the kqueue code to kill it (for good + // measure) and reap it. + } + } else { + // Keep track of the elapsed time to be able to restart kevent if it's + // interrupted. + TimeDelta remaining_delta = TimeDelta::FromSeconds(timeout); + TimeTicks deadline = TimeTicks::Now() + remaining_delta; + result = -1; + struct kevent event = {0}; + while (remaining_delta.InMilliseconds() > 0) { + const struct timespec remaining_timespec = remaining_delta.ToTimeSpec(); + result = kevent(kq, NULL, 0, &event, 1, &remaining_timespec); + if (result == -1 && errno == EINTR) { + remaining_delta = deadline - TimeTicks::Now(); + result = 0; + } else { + break; + } + } + + if (result == -1) { + DPLOG(ERROR) << "kevent (wait " << child << ")"; + } else if (result > 1) { + DLOG(ERROR) << "kevent (wait " << child << "): unexpected result " + << result; + } else if (result == 1) { + if ((event.fflags & NOTE_EXIT) && + (event.ident == static_cast(child))) { + // The process is dead or dying. This won't block for long, if at + // all. + BlockingReap(child); + return; + } else { + DLOG(ERROR) << "kevent (wait " << child + << "): unexpected event: fflags=" << event.fflags + << ", ident=" << event.ident; + } + } + } + } + + // The child is still alive, or is very freshly dead. Be sure by sending it + // a signal. This is safe even if it's freshly dead, because it will be a + // zombie (or on the way to zombiedom) and kill will return 0 even if the + // signal is not delivered to a live process. + result = kill(child, SIGKILL); + if (result == -1) { + DPLOG(ERROR) << "kill(" << child << ", SIGKILL)"; + } else { + // The child is definitely on the way out now. BlockingReap won't need to + // wait for long, if at all. + BlockingReap(child); + } +} + +} // namespace + +void EnsureProcessTerminated(ProcessHandle process) { + WaitForChildToDie(process, kWaitBeforeKillSeconds); +} + +} // namespace base diff --git a/base/process/kill_posix.cc b/base/process/kill_posix.cc new file mode 100644 index 0000000000..5938fa5323 --- /dev/null +++ b/base/process/kill_posix.cc @@ -0,0 +1,492 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/kill.h" + +#include +#include +#include +#include + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/process/process_iterator.h" +#include "base/synchronization/waitable_event.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" +#include "base/threading/platform_thread.h" + +namespace base { + +namespace { + +int WaitpidWithTimeout(ProcessHandle handle, + int64 wait_milliseconds, + bool* success) { + // This POSIX version of this function only guarantees that we wait no less + // than |wait_milliseconds| for the process to exit. The child process may + // exit sometime before the timeout has ended but we may still block for up + // to 256 milliseconds after the fact. + // + // waitpid() has no direct support on POSIX for specifying a timeout, you can + // either ask it to block indefinitely or return immediately (WNOHANG). + // When a child process terminates a SIGCHLD signal is sent to the parent. + // Catching this signal would involve installing a signal handler which may + // affect other parts of the application and would be difficult to debug. + // + // Our strategy is to call waitpid() once up front to check if the process + // has already exited, otherwise to loop for wait_milliseconds, sleeping for + // at most 256 milliseconds each time using usleep() and then calling + // waitpid(). The amount of time we sleep starts out at 1 milliseconds, and + // we double it every 4 sleep cycles. + // + // usleep() is speced to exit if a signal is received for which a handler + // has been installed. This means that when a SIGCHLD is sent, it will exit + // depending on behavior external to this function. + // + // This function is used primarily for unit tests, if we want to use it in + // the application itself it would probably be best to examine other routes. + int status = -1; + pid_t ret_pid = HANDLE_EINTR(waitpid(handle, &status, WNOHANG)); + static const int64 kMaxSleepInMicroseconds = 1 << 18; // ~256 milliseconds. + int64 max_sleep_time_usecs = 1 << 10; // ~1 milliseconds. + int64 double_sleep_time = 0; + + // If the process hasn't exited yet, then sleep and try again. + TimeTicks wakeup_time = TimeTicks::Now() + + TimeDelta::FromMilliseconds(wait_milliseconds); + while (ret_pid == 0) { + TimeTicks now = TimeTicks::Now(); + if (now > wakeup_time) + break; + // Guaranteed to be non-negative! + int64 sleep_time_usecs = (wakeup_time - now).InMicroseconds(); + // Sleep for a bit while we wait for the process to finish. + if (sleep_time_usecs > max_sleep_time_usecs) + sleep_time_usecs = max_sleep_time_usecs; + + // usleep() will return 0 and set errno to EINTR on receipt of a signal + // such as SIGCHLD. + usleep(sleep_time_usecs); + ret_pid = HANDLE_EINTR(waitpid(handle, &status, WNOHANG)); + + if ((max_sleep_time_usecs < kMaxSleepInMicroseconds) && + (double_sleep_time++ % 4 == 0)) { + max_sleep_time_usecs *= 2; + } + } + + if (success) + *success = (ret_pid != -1); + + return status; +} + +TerminationStatus GetTerminationStatusImpl(ProcessHandle handle, + bool can_block, + int* exit_code) { + int status = 0; + const pid_t result = HANDLE_EINTR(waitpid(handle, &status, + can_block ? 0 : WNOHANG)); + if (result == -1) { + DPLOG(ERROR) << "waitpid(" << handle << ")"; + if (exit_code) + *exit_code = 0; + return TERMINATION_STATUS_NORMAL_TERMINATION; + } else if (result == 0) { + // the child hasn't exited yet. + if (exit_code) + *exit_code = 0; + return TERMINATION_STATUS_STILL_RUNNING; + } + + if (exit_code) + *exit_code = status; + + if (WIFSIGNALED(status)) { + switch (WTERMSIG(status)) { + case SIGABRT: + case SIGBUS: + case SIGFPE: + case SIGILL: + case SIGSEGV: + return TERMINATION_STATUS_PROCESS_CRASHED; + case SIGINT: + case SIGKILL: + case SIGTERM: + return TERMINATION_STATUS_PROCESS_WAS_KILLED; + default: + break; + } + } + + if (WIFEXITED(status) && WEXITSTATUS(status) != 0) + return TERMINATION_STATUS_ABNORMAL_TERMINATION; + + return TERMINATION_STATUS_NORMAL_TERMINATION; +} + +} // namespace + +// Attempts to kill the process identified by the given process +// entry structure. Ignores specified exit_code; posix can't force that. +// Returns true if this is successful, false otherwise. +bool KillProcess(ProcessHandle process_id, int exit_code, bool wait) { + DCHECK_GT(process_id, 1) << " tried to kill invalid process_id"; + if (process_id <= 1) + return false; + bool result = kill(process_id, SIGTERM) == 0; + if (result && wait) { + int tries = 60; + + if (RunningOnValgrind()) { + // Wait for some extra time when running under Valgrind since the child + // processes may take some time doing leak checking. + tries *= 2; + } + + unsigned sleep_ms = 4; + + // The process may not end immediately due to pending I/O + bool exited = false; + while (tries-- > 0) { + pid_t pid = HANDLE_EINTR(waitpid(process_id, NULL, WNOHANG)); + if (pid == process_id) { + exited = true; + break; + } + if (pid == -1) { + if (errno == ECHILD) { + // The wait may fail with ECHILD if another process also waited for + // the same pid, causing the process state to get cleaned up. + exited = true; + break; + } + DPLOG(ERROR) << "Error waiting for process " << process_id; + } + + usleep(sleep_ms * 1000); + const unsigned kMaxSleepMs = 1000; + if (sleep_ms < kMaxSleepMs) + sleep_ms *= 2; + } + + // If we're waiting and the child hasn't died by now, force it + // with a SIGKILL. + if (!exited) + result = kill(process_id, SIGKILL) == 0; + } + + if (!result) + DPLOG(ERROR) << "Unable to terminate process " << process_id; + + return result; +} + +bool KillProcessGroup(ProcessHandle process_group_id) { + bool result = kill(-1 * process_group_id, SIGKILL) == 0; + if (!result) + DPLOG(ERROR) << "Unable to terminate process group " << process_group_id; + return result; +} + +TerminationStatus GetTerminationStatus(ProcessHandle handle, int* exit_code) { + return GetTerminationStatusImpl(handle, false /* can_block */, exit_code); +} + +TerminationStatus WaitForTerminationStatus(ProcessHandle handle, + int* exit_code) { + return GetTerminationStatusImpl(handle, true /* can_block */, exit_code); +} + +bool WaitForExitCode(ProcessHandle handle, int* exit_code) { + int status; + if (HANDLE_EINTR(waitpid(handle, &status, 0)) == -1) { + NOTREACHED(); + return false; + } + + if (WIFEXITED(status)) { + *exit_code = WEXITSTATUS(status); + return true; + } + + // If it didn't exit cleanly, it must have been signaled. + DCHECK(WIFSIGNALED(status)); + return false; +} + +bool WaitForExitCodeWithTimeout(ProcessHandle handle, + int* exit_code, + base::TimeDelta timeout) { + bool waitpid_success = false; + int status = WaitpidWithTimeout(handle, timeout.InMilliseconds(), + &waitpid_success); + if (status == -1) + return false; + if (!waitpid_success) + return false; + if (WIFSIGNALED(status)) { + *exit_code = -1; + return true; + } + if (WIFEXITED(status)) { + *exit_code = WEXITSTATUS(status); + return true; + } + return false; +} + +bool WaitForProcessesToExit(const FilePath::StringType& executable_name, + base::TimeDelta wait, + const ProcessFilter* filter) { + bool result = false; + + // TODO(port): This is inefficient, but works if there are multiple procs. + // TODO(port): use waitpid to avoid leaving zombies around + + base::TimeTicks end_time = base::TimeTicks::Now() + wait; + do { + NamedProcessIterator iter(executable_name, filter); + if (!iter.NextProcessEntry()) { + result = true; + break; + } + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); + } while ((end_time - base::TimeTicks::Now()) > base::TimeDelta()); + + return result; +} + +#if defined(OS_MACOSX) +// Using kqueue on Mac so that we can wait on non-child processes. +// We can't use kqueues on child processes because we need to reap +// our own children using wait. +static bool WaitForSingleNonChildProcess(ProcessHandle handle, + base::TimeDelta wait) { + DCHECK_GT(handle, 0); + DCHECK(wait.InMilliseconds() == base::kNoTimeout || wait > base::TimeDelta()); + + int kq = kqueue(); + if (kq == -1) { + DPLOG(ERROR) << "kqueue"; + return false; + } + file_util::ScopedFD kq_closer(&kq); + + struct kevent change = {0}; + EV_SET(&change, handle, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL); + int result = HANDLE_EINTR(kevent(kq, &change, 1, NULL, 0, NULL)); + if (result == -1) { + if (errno == ESRCH) { + // If the process wasn't found, it must be dead. + return true; + } + + DPLOG(ERROR) << "kevent (setup " << handle << ")"; + return false; + } + + // Keep track of the elapsed time to be able to restart kevent if it's + // interrupted. + bool wait_forever = wait.InMilliseconds() == base::kNoTimeout; + base::TimeDelta remaining_delta; + base::TimeTicks deadline; + if (!wait_forever) { + remaining_delta = wait; + deadline = base::TimeTicks::Now() + remaining_delta; + } + + result = -1; + struct kevent event = {0}; + + while (wait_forever || remaining_delta > base::TimeDelta()) { + struct timespec remaining_timespec; + struct timespec* remaining_timespec_ptr; + if (wait_forever) { + remaining_timespec_ptr = NULL; + } else { + remaining_timespec = remaining_delta.ToTimeSpec(); + remaining_timespec_ptr = &remaining_timespec; + } + + result = kevent(kq, NULL, 0, &event, 1, remaining_timespec_ptr); + + if (result == -1 && errno == EINTR) { + if (!wait_forever) { + remaining_delta = deadline - base::TimeTicks::Now(); + } + result = 0; + } else { + break; + } + } + + if (result < 0) { + DPLOG(ERROR) << "kevent (wait " << handle << ")"; + return false; + } else if (result > 1) { + DLOG(ERROR) << "kevent (wait " << handle << "): unexpected result " + << result; + return false; + } else if (result == 0) { + // Timed out. + return false; + } + + DCHECK_EQ(result, 1); + + if (event.filter != EVFILT_PROC || + (event.fflags & NOTE_EXIT) == 0 || + event.ident != static_cast(handle)) { + DLOG(ERROR) << "kevent (wait " << handle + << "): unexpected event: filter=" << event.filter + << ", fflags=" << event.fflags + << ", ident=" << event.ident; + return false; + } + + return true; +} +#endif // OS_MACOSX + +bool WaitForSingleProcess(ProcessHandle handle, base::TimeDelta wait) { + ProcessHandle parent_pid = GetParentProcessId(handle); + ProcessHandle our_pid = Process::Current().handle(); + if (parent_pid != our_pid) { +#if defined(OS_MACOSX) + // On Mac we can wait on non child processes. + return WaitForSingleNonChildProcess(handle, wait); +#else + // Currently on Linux we can't handle non child processes. + NOTIMPLEMENTED(); +#endif // OS_MACOSX + } + + bool waitpid_success; + int status = -1; + if (wait.InMilliseconds() == base::kNoTimeout) { + waitpid_success = (HANDLE_EINTR(waitpid(handle, &status, 0)) != -1); + } else { + status = WaitpidWithTimeout( + handle, wait.InMilliseconds(), &waitpid_success); + } + + if (status != -1) { + DCHECK(waitpid_success); + return WIFEXITED(status); + } else { + return false; + } +} + +bool CleanupProcesses(const FilePath::StringType& executable_name, + base::TimeDelta wait, + int exit_code, + const ProcessFilter* filter) { + bool exited_cleanly = WaitForProcessesToExit(executable_name, wait, filter); + if (!exited_cleanly) + KillProcesses(executable_name, exit_code, filter); + return exited_cleanly; +} + +#if !defined(OS_MACOSX) + +namespace { + +// Return true if the given child is dead. This will also reap the process. +// Doesn't block. +static bool IsChildDead(pid_t child) { + const pid_t result = HANDLE_EINTR(waitpid(child, NULL, WNOHANG)); + if (result == -1) { + DPLOG(ERROR) << "waitpid(" << child << ")"; + NOTREACHED(); + } else if (result > 0) { + // The child has died. + return true; + } + + return false; +} + +// A thread class which waits for the given child to exit and reaps it. +// If the child doesn't exit within a couple of seconds, kill it. +class BackgroundReaper : public PlatformThread::Delegate { + public: + BackgroundReaper(pid_t child, unsigned timeout) + : child_(child), + timeout_(timeout) { + } + + // Overridden from PlatformThread::Delegate: + virtual void ThreadMain() OVERRIDE { + WaitForChildToDie(); + delete this; + } + + void WaitForChildToDie() { + // Wait forever case. + if (timeout_ == 0) { + pid_t r = HANDLE_EINTR(waitpid(child_, NULL, 0)); + if (r != child_) { + DPLOG(ERROR) << "While waiting for " << child_ + << " to terminate, we got the following result: " << r; + } + return; + } + + // There's no good way to wait for a specific child to exit in a timed + // fashion. (No kqueue on Linux), so we just loop and sleep. + + // Wait for 2 * timeout_ 500 milliseconds intervals. + for (unsigned i = 0; i < 2 * timeout_; ++i) { + PlatformThread::Sleep(TimeDelta::FromMilliseconds(500)); + if (IsChildDead(child_)) + return; + } + + if (kill(child_, SIGKILL) == 0) { + // SIGKILL is uncatchable. Since the signal was delivered, we can + // just wait for the process to die now in a blocking manner. + if (HANDLE_EINTR(waitpid(child_, NULL, 0)) < 0) + DPLOG(WARNING) << "waitpid"; + } else { + DLOG(ERROR) << "While waiting for " << child_ << " to terminate we" + << " failed to deliver a SIGKILL signal (" << errno << ")."; + } + } + + private: + const pid_t child_; + // Number of seconds to wait, if 0 then wait forever and do not attempt to + // kill |child_|. + const unsigned timeout_; + + DISALLOW_COPY_AND_ASSIGN(BackgroundReaper); +}; + +} // namespace + +void EnsureProcessTerminated(ProcessHandle process) { + // If the child is already dead, then there's nothing to do. + if (IsChildDead(process)) + return; + + const unsigned timeout = 2; // seconds + BackgroundReaper* reaper = new BackgroundReaper(process, timeout); + PlatformThread::CreateNonJoinable(0, reaper); +} + +void EnsureProcessGetsReaped(ProcessHandle process) { + // If the child is already dead, then there's nothing to do. + if (IsChildDead(process)) + return; + + BackgroundReaper* reaper = new BackgroundReaper(process, 0); + PlatformThread::CreateNonJoinable(0, reaper); +} + +#endif // !defined(OS_MACOSX) + +} // namespace base diff --git a/base/process/kill_win.cc b/base/process/kill_win.cc new file mode 100644 index 0000000000..99a7c66185 --- /dev/null +++ b/base/process/kill_win.cc @@ -0,0 +1,255 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/kill.h" + +#include +#include + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/process/process_iterator.h" +#include "base/win/object_watcher.h" + +namespace base { + +namespace { + +// Exit codes with special meanings on Windows. +const DWORD kNormalTerminationExitCode = 0; +const DWORD kDebuggerInactiveExitCode = 0xC0000354; +const DWORD kKeyboardInterruptExitCode = 0xC000013A; +const DWORD kDebuggerTerminatedExitCode = 0x40010004; + +// This exit code is used by the Windows task manager when it kills a +// process. It's value is obviously not that unique, and it's +// surprising to me that the task manager uses this value, but it +// seems to be common practice on Windows to test for it as an +// indication that the task manager has killed something if the +// process goes away. +const DWORD kProcessKilledExitCode = 1; + +// Maximum amount of time (in milliseconds) to wait for the process to exit. +static const int kWaitInterval = 2000; + +class TimerExpiredTask : public win::ObjectWatcher::Delegate { + public: + explicit TimerExpiredTask(ProcessHandle process); + ~TimerExpiredTask(); + + void TimedOut(); + + // MessageLoop::Watcher ----------------------------------------------------- + virtual void OnObjectSignaled(HANDLE object); + + private: + void KillProcess(); + + // The process that we are watching. + ProcessHandle process_; + + win::ObjectWatcher watcher_; + + DISALLOW_COPY_AND_ASSIGN(TimerExpiredTask); +}; + +TimerExpiredTask::TimerExpiredTask(ProcessHandle process) : process_(process) { + watcher_.StartWatching(process_, this); +} + +TimerExpiredTask::~TimerExpiredTask() { + TimedOut(); + DCHECK(!process_) << "Make sure to close the handle."; +} + +void TimerExpiredTask::TimedOut() { + if (process_) + KillProcess(); +} + +void TimerExpiredTask::OnObjectSignaled(HANDLE object) { + CloseHandle(process_); + process_ = NULL; +} + +void TimerExpiredTask::KillProcess() { + // Stop watching the process handle since we're killing it. + watcher_.StopWatching(); + + // OK, time to get frisky. We don't actually care when the process + // terminates. We just care that it eventually terminates, and that's what + // TerminateProcess should do for us. Don't check for the result code since + // it fails quite often. This should be investigated eventually. + base::KillProcess(process_, kProcessKilledExitCode, false); + + // Now, just cleanup as if the process exited normally. + OnObjectSignaled(process_); +} + +} // namespace + +bool KillProcess(ProcessHandle process, int exit_code, bool wait) { + bool result = (TerminateProcess(process, exit_code) != FALSE); + if (result && wait) { + // The process may not end immediately due to pending I/O + if (WAIT_OBJECT_0 != WaitForSingleObject(process, 60 * 1000)) + DLOG_GETLASTERROR(ERROR) << "Error waiting for process exit"; + } else if (!result) { + DLOG_GETLASTERROR(ERROR) << "Unable to terminate process"; + } + return result; +} + +// Attempts to kill the process identified by the given process +// entry structure, giving it the specified exit code. +// Returns true if this is successful, false otherwise. +bool KillProcessById(ProcessId process_id, int exit_code, bool wait) { + HANDLE process = OpenProcess(PROCESS_TERMINATE | SYNCHRONIZE, + FALSE, // Don't inherit handle + process_id); + if (!process) { + DLOG_GETLASTERROR(ERROR) << "Unable to open process " << process_id; + return false; + } + bool ret = KillProcess(process, exit_code, wait); + CloseHandle(process); + return ret; +} + +TerminationStatus GetTerminationStatus(ProcessHandle handle, int* exit_code) { + DWORD tmp_exit_code = 0; + + if (!::GetExitCodeProcess(handle, &tmp_exit_code)) { + DLOG_GETLASTERROR(FATAL) << "GetExitCodeProcess() failed"; + if (exit_code) { + // This really is a random number. We haven't received any + // information about the exit code, presumably because this + // process doesn't have permission to get the exit code, or + // because of some other cause for GetExitCodeProcess to fail + // (MSDN docs don't give the possible failure error codes for + // this function, so it could be anything). But we don't want + // to leave exit_code uninitialized, since that could cause + // random interpretations of the exit code. So we assume it + // terminated "normally" in this case. + *exit_code = kNormalTerminationExitCode; + } + // Assume the child has exited normally if we can't get the exit + // code. + return TERMINATION_STATUS_NORMAL_TERMINATION; + } + if (tmp_exit_code == STILL_ACTIVE) { + DWORD wait_result = WaitForSingleObject(handle, 0); + if (wait_result == WAIT_TIMEOUT) { + if (exit_code) + *exit_code = wait_result; + return TERMINATION_STATUS_STILL_RUNNING; + } + + if (wait_result == WAIT_FAILED) { + DLOG_GETLASTERROR(ERROR) << "WaitForSingleObject() failed"; + } else { + DCHECK_EQ(WAIT_OBJECT_0, wait_result); + + // Strange, the process used 0x103 (STILL_ACTIVE) as exit code. + NOTREACHED(); + } + + return TERMINATION_STATUS_ABNORMAL_TERMINATION; + } + + if (exit_code) + *exit_code = tmp_exit_code; + + switch (tmp_exit_code) { + case kNormalTerminationExitCode: + return TERMINATION_STATUS_NORMAL_TERMINATION; + case kDebuggerInactiveExitCode: // STATUS_DEBUGGER_INACTIVE. + case kKeyboardInterruptExitCode: // Control-C/end session. + case kDebuggerTerminatedExitCode: // Debugger terminated process. + case kProcessKilledExitCode: // Task manager kill. + return TERMINATION_STATUS_PROCESS_WAS_KILLED; + default: + // All other exit codes indicate crashes. + return TERMINATION_STATUS_PROCESS_CRASHED; + } +} + +bool WaitForExitCode(ProcessHandle handle, int* exit_code) { + bool success = WaitForExitCodeWithTimeout( + handle, exit_code, base::TimeDelta::FromMilliseconds(INFINITE)); + CloseProcessHandle(handle); + return success; +} + +bool WaitForExitCodeWithTimeout(ProcessHandle handle, + int* exit_code, + base::TimeDelta timeout) { + if (::WaitForSingleObject(handle, timeout.InMilliseconds()) != WAIT_OBJECT_0) + return false; + DWORD temp_code; // Don't clobber out-parameters in case of failure. + if (!::GetExitCodeProcess(handle, &temp_code)) + return false; + + *exit_code = temp_code; + return true; +} + +bool WaitForProcessesToExit(const FilePath::StringType& executable_name, + base::TimeDelta wait, + const ProcessFilter* filter) { + const ProcessEntry* entry; + bool result = true; + DWORD start_time = GetTickCount(); + + NamedProcessIterator iter(executable_name, filter); + while ((entry = iter.NextProcessEntry())) { + DWORD remaining_wait = std::max( + 0, wait.InMilliseconds() - (GetTickCount() - start_time)); + HANDLE process = OpenProcess(SYNCHRONIZE, + FALSE, + entry->th32ProcessID); + DWORD wait_result = WaitForSingleObject(process, remaining_wait); + CloseHandle(process); + result = result && (wait_result == WAIT_OBJECT_0); + } + + return result; +} + +bool WaitForSingleProcess(ProcessHandle handle, base::TimeDelta wait) { + int exit_code; + if (!WaitForExitCodeWithTimeout(handle, &exit_code, wait)) + return false; + return exit_code == 0; +} + +bool CleanupProcesses(const FilePath::StringType& executable_name, + base::TimeDelta wait, + int exit_code, + const ProcessFilter* filter) { + bool exited_cleanly = WaitForProcessesToExit(executable_name, wait, filter); + if (!exited_cleanly) + KillProcesses(executable_name, exit_code, filter); + return exited_cleanly; +} + +void EnsureProcessTerminated(ProcessHandle process) { + DCHECK(process != GetCurrentProcess()); + + // If already signaled, then we are done! + if (WaitForSingleObject(process, 0) == WAIT_OBJECT_0) { + CloseHandle(process); + return; + } + + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&TimerExpiredTask::TimedOut, + base::Owned(new TimerExpiredTask(process))), + base::TimeDelta::FromMilliseconds(kWaitInterval)); +} + +} // namespace base diff --git a/base/process/launch.h b/base/process/launch.h new file mode 100644 index 0000000000..45b10537b8 --- /dev/null +++ b/base/process/launch.h @@ -0,0 +1,258 @@ +// 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 file contains functions for launching subprocesses. + +#ifndef BASE_PROCESS_LAUNCH_H_ +#define BASE_PROCESS_LAUNCH_H_ + +#include +#include +#include +#include + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/process/process_handle.h" + +#if defined(OS_POSIX) +#include "base/posix/file_descriptor_shuffle.h" +#elif defined(OS_WIN) +#include +#endif + +class CommandLine; + +namespace base { + +typedef std::vector > EnvironmentVector; +typedef std::vector > FileHandleMappingVector; + +// Options for launching a subprocess that are passed to LaunchProcess(). +// The default constructor constructs the object with default options. +struct LaunchOptions { + LaunchOptions() + : wait(false), +#if defined(OS_WIN) + start_hidden(false), + inherit_handles(false), + as_user(NULL), + empty_desktop_name(false), + job_handle(NULL), + stdin_handle(NULL), + stdout_handle(NULL), + stderr_handle(NULL), + force_breakaway_from_job_(false) +#else + environ(NULL), + fds_to_remap(NULL), + maximize_rlimits(NULL), + new_process_group(false) +#if defined(OS_LINUX) + , clone_flags(0) +#endif // OS_LINUX +#if defined(OS_CHROMEOS) + , ctrl_terminal_fd(-1) +#endif // OS_CHROMEOS +#endif // !defined(OS_WIN) + {} + + // If true, wait for the process to complete. + bool wait; + +#if defined(OS_WIN) + bool start_hidden; + + // If true, the new process inherits handles from the parent. In production + // code this flag should be used only when running short-lived, trusted + // binaries, because open handles from other libraries and subsystems will + // leak to the child process, causing errors such as open socket hangs. + bool inherit_handles; + + // If non-NULL, runs as if the user represented by the token had launched it. + // Whether the application is visible on the interactive desktop depends on + // the token belonging to an interactive logon session. + // + // To avoid hard to diagnose problems, when specified this loads the + // environment variables associated with the user and if this operation fails + // the entire call fails as well. + UserTokenHandle as_user; + + // If true, use an empty string for the desktop name. + bool empty_desktop_name; + + // If non-NULL, launches the application in that job object. The process will + // be terminated immediately and LaunchProcess() will fail if assignment to + // the job object fails. + HANDLE job_handle; + + // Handles for the redirection of stdin, stdout and stderr. The handles must + // be inheritable. Caller should either set all three of them or none (i.e. + // there is no way to redirect stderr without redirecting stdin). The + // |inherit_handles| flag must be set to true when redirecting stdio stream. + HANDLE stdin_handle; + HANDLE stdout_handle; + HANDLE stderr_handle; + + // If set to true, ensures that the child process is launched with the + // CREATE_BREAKAWAY_FROM_JOB flag which allows it to breakout of the parent + // job if any. + bool force_breakaway_from_job_; +#else + // If non-NULL, set/unset environment variables. + // See documentation of AlterEnvironment(). + // This pointer is owned by the caller and must live through the + // call to LaunchProcess(). + const EnvironmentVector* environ; + + // If non-NULL, remap file descriptors according to the mapping of + // src fd->dest fd to propagate FDs into the child process. + // This pointer is owned by the caller and must live through the + // call to LaunchProcess(). + const FileHandleMappingVector* fds_to_remap; + + // Each element is an RLIMIT_* constant that should be raised to its + // rlim_max. This pointer is owned by the caller and must live through + // the call to LaunchProcess(). + const std::set* maximize_rlimits; + + // If true, start the process in a new process group, instead of + // inheriting the parent's process group. The pgid of the child process + // will be the same as its pid. + bool new_process_group; + +#if defined(OS_LINUX) + // If non-zero, start the process using clone(), using flags as provided. + int clone_flags; +#endif // defined(OS_LINUX) + +#if defined(OS_CHROMEOS) + // If non-negative, the specified file descriptor will be set as the launched + // process' controlling terminal. + int ctrl_terminal_fd; +#endif // defined(OS_CHROMEOS) + +#endif // !defined(OS_WIN) +}; + +// Launch a process via the command line |cmdline|. +// See the documentation of LaunchOptions for details on |options|. +// +// Returns true upon success. +// +// Upon success, if |process_handle| is non-NULL, it will be filled in with the +// handle of the launched process. NOTE: In this case, the caller is +// responsible for closing the handle so that it doesn't leak! +// Otherwise, the process handle will be implicitly closed. +// +// Unix-specific notes: +// - All file descriptors open in the parent process will be closed in the +// child process except for any preserved by options::fds_to_remap, and +// stdin, stdout, and stderr. If not remapped by options::fds_to_remap, +// stdin is reopened as /dev/null, and the child is allowed to inherit its +// parent's stdout and stderr. +// - If the first argument on the command line does not contain a slash, +// PATH will be searched. (See man execvp.) +BASE_EXPORT bool LaunchProcess(const CommandLine& cmdline, + const LaunchOptions& options, + ProcessHandle* process_handle); + +#if defined(OS_WIN) +// Windows-specific LaunchProcess that takes the command line as a +// string. Useful for situations where you need to control the +// command line arguments directly, but prefer the CommandLine version +// if launching Chrome itself. +// +// The first command line argument should be the path to the process, +// and don't forget to quote it. +// +// Example (including literal quotes) +// cmdline = "c:\windows\explorer.exe" -foo "c:\bar\" +BASE_EXPORT bool LaunchProcess(const string16& cmdline, + const LaunchOptions& options, + ProcessHandle* process_handle); + +#elif defined(OS_POSIX) +// A POSIX-specific version of LaunchProcess that takes an argv array +// instead of a CommandLine. Useful for situations where you need to +// control the command line arguments directly, but prefer the +// CommandLine version if launching Chrome itself. +BASE_EXPORT bool LaunchProcess(const std::vector& argv, + const LaunchOptions& options, + ProcessHandle* process_handle); + +// AlterEnvironment returns a modified environment vector, constructed from the +// given environment and the list of changes given in |changes|. Each key in +// the environment is matched against the first element of the pairs. In the +// event of a match, the value is replaced by the second of the pair, unless +// the second is empty, in which case the key-value is removed. +// +// The returned array is allocated using new[] and must be freed by the caller. +BASE_EXPORT char** AlterEnvironment(const EnvironmentVector& changes, + const char* const* const env); + +// Close all file descriptors, except those which are a destination in the +// given multimap. Only call this function in a child process where you know +// that there aren't any other threads. +BASE_EXPORT void CloseSuperfluousFds(const InjectiveMultimap& saved_map); +#endif // defined(OS_POSIX) + +#if defined(OS_WIN) +// Set JOBOBJECT_EXTENDED_LIMIT_INFORMATION to JobObject |job_object|. +// As its limit_info.BasicLimitInformation.LimitFlags has +// JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE. +// When the provide JobObject |job_object| is closed, the binded process will +// be terminated. +BASE_EXPORT bool SetJobObjectAsKillOnJobClose(HANDLE job_object); + +// Output multi-process printf, cout, cerr, etc to the cmd.exe console that ran +// chrome. This is not thread-safe: only call from main thread. +BASE_EXPORT void RouteStdioToConsole(); +#endif // defined(OS_WIN) + +// Executes the application specified by |cl| and wait for it to exit. Stores +// the output (stdout) in |output|. Redirects stderr to /dev/null. Returns true +// on success (application launched and exited cleanly, with exit code +// indicating success). +BASE_EXPORT bool GetAppOutput(const CommandLine& cl, std::string* output); + +#if defined(OS_POSIX) +// A POSIX-specific version of GetAppOutput that takes an argv array +// instead of a CommandLine. Useful for situations where you need to +// control the command line arguments directly. +BASE_EXPORT bool GetAppOutput(const std::vector& argv, + std::string* output); + +// A restricted version of |GetAppOutput()| which (a) clears the environment, +// and (b) stores at most |max_output| bytes; also, it doesn't search the path +// for the command. +BASE_EXPORT bool GetAppOutputRestricted(const CommandLine& cl, + std::string* output, size_t max_output); + +// A version of |GetAppOutput()| which also returns the exit code of the +// executed command. Returns true if the application runs and exits cleanly. If +// this is the case the exit code of the application is available in +// |*exit_code|. +BASE_EXPORT bool GetAppOutputWithExitCode(const CommandLine& cl, + std::string* output, int* exit_code); +#endif // defined(OS_POSIX) + +// If supported on the platform, and the user has sufficent rights, increase +// the current process's scheduling priority to a high priority. +BASE_EXPORT void RaiseProcessToHighPriority(); + +#if defined(OS_MACOSX) +// Restore the default exception handler, setting it to Apple Crash Reporter +// (ReportCrash). When forking and execing a new process, the child will +// inherit the parent's exception ports, which may be set to the Breakpad +// instance running inside the parent. The parent's Breakpad instance should +// not handle the child's exceptions. Calling RestoreDefaultExceptionHandler +// in the child after forking will restore the standard exception handler. +// See http://crbug.com/20371/ for more details. +void RestoreDefaultExceptionHandler(); +#endif // defined(OS_MACOSX) + +} // namespace base + +#endif // BASE_PROCESS_LAUNCH_H_ diff --git a/base/process/launch_ios.cc b/base/process/launch_ios.cc new file mode 100644 index 0000000000..3c700f8f0a --- /dev/null +++ b/base/process/launch_ios.cc @@ -0,0 +1,13 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/launch.h" + +namespace base { + +void RaiseProcessToHighPriority() { + // Impossible on iOS. Do nothing. +} + +} // namespace base diff --git a/base/process/launch_mac.cc b/base/process/launch_mac.cc new file mode 100644 index 0000000000..176edca72e --- /dev/null +++ b/base/process/launch_mac.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/process/launch.h" + +#include + +namespace base { + +void RestoreDefaultExceptionHandler() { + // This function is tailored to remove the Breakpad exception handler. + // exception_mask matches s_exception_mask in + // breakpad/src/client/mac/handler/exception_handler.cc + const exception_mask_t exception_mask = EXC_MASK_BAD_ACCESS | + EXC_MASK_BAD_INSTRUCTION | + EXC_MASK_ARITHMETIC | + EXC_MASK_BREAKPOINT; + + // Setting the exception port to MACH_PORT_NULL may not be entirely + // kosher to restore the default exception handler, but in practice, + // it results in the exception port being set to Apple Crash Reporter, + // the desired behavior. + task_set_exception_ports(mach_task_self(), exception_mask, MACH_PORT_NULL, + EXCEPTION_DEFAULT, THREAD_STATE_NONE); +} + +} // namespace base diff --git a/base/process/launch_posix.cc b/base/process/launch_posix.cc new file mode 100644 index 0000000000..52e149cd8d --- /dev/null +++ b/base/process/launch_posix.cc @@ -0,0 +1,760 @@ +// Copyright (c) 2012 The Chromium Authors. All 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/launch.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "base/allocator/type_profiler_control.h" +#include "base/command_line.h" +#include "base/compiler_specific.h" +#include "base/debug/debugger.h" +#include "base/debug/stack_trace.h" +#include "base/file_util.h" +#include "base/files/dir_reader_posix.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/posix/eintr_wrapper.h" +#include "base/process/kill.h" +#include "base/process/process_metrics.h" +#include "base/strings/stringprintf.h" +#include "base/synchronization/waitable_event.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" +#include "base/threading/platform_thread.h" +#include "base/threading/thread_restrictions.h" + +#if defined(OS_CHROMEOS) +#include +#endif + +#if defined(OS_FREEBSD) +#include +#include +#endif + +#if defined(OS_MACOSX) +#include +#include +#else +extern char** environ; +#endif + +namespace base { + +namespace { + +// Get the process's "environment" (i.e. the thing that setenv/getenv +// work with). +char** GetEnvironment() { +#if defined(OS_MACOSX) + return *_NSGetEnviron(); +#else + return environ; +#endif +} + +// Set the process's "environment" (i.e. the thing that setenv/getenv +// work with). +void SetEnvironment(char** env) { +#if defined(OS_MACOSX) + *_NSGetEnviron() = env; +#else + environ = env; +#endif +} + +// Set the calling thread's signal mask to new_sigmask and return +// the previous signal mask. +sigset_t SetSignalMask(const sigset_t& new_sigmask) { + sigset_t old_sigmask; +#if defined(OS_ANDROID) + // POSIX says pthread_sigmask() must be used in multi-threaded processes, + // but Android's pthread_sigmask() was broken until 4.1: + // https://code.google.com/p/android/issues/detail?id=15337 + // http://stackoverflow.com/questions/13777109/pthread-sigmask-on-android-not-working + RAW_CHECK(sigprocmask(SIG_SETMASK, &new_sigmask, &old_sigmask) == 0); +#else + RAW_CHECK(pthread_sigmask(SIG_SETMASK, &new_sigmask, &old_sigmask) == 0); +#endif + return old_sigmask; +} + +#if !defined(OS_LINUX) || \ + (!defined(__i386__) && !defined(__x86_64__) && !defined(__arm__)) +void ResetChildSignalHandlersToDefaults() { + // The previous signal handlers are likely to be meaningless in the child's + // context so we reset them to the defaults for now. http://crbug.com/44953 + // These signal handlers are set up at least in browser_main_posix.cc: + // BrowserMainPartsPosix::PreEarlyInitialization and stack_trace_posix.cc: + // EnableInProcessStackDumping. + signal(SIGHUP, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGILL, SIG_DFL); + signal(SIGABRT, SIG_DFL); + signal(SIGFPE, SIG_DFL); + signal(SIGBUS, SIG_DFL); + signal(SIGSEGV, SIG_DFL); + signal(SIGSYS, SIG_DFL); + signal(SIGTERM, SIG_DFL); +} + +#else + +// TODO(jln): remove the Linux special case once kernels are fixed. + +// Internally the kernel makes sigset_t an array of long large enough to have +// one bit per signal. +typedef uint64_t kernel_sigset_t; + +// This is what struct sigaction looks like to the kernel at least on X86 and +// ARM. MIPS, for instance, is very different. +struct kernel_sigaction { + void* k_sa_handler; // For this usage it only needs to be a generic pointer. + unsigned long k_sa_flags; + void* k_sa_restorer; // For this usage it only needs to be a generic pointer. + kernel_sigset_t k_sa_mask; +}; + +// glibc's sigaction() will prevent access to sa_restorer, so we need to roll +// our own. +int sys_rt_sigaction(int sig, const struct kernel_sigaction* act, + struct kernel_sigaction* oact) { + return syscall(SYS_rt_sigaction, sig, act, oact, sizeof(kernel_sigset_t)); +} + +// This function is intended to be used in between fork() and execve() and will +// reset all signal handlers to the default. +// The motivation for going through all of them is that sa_restorer can leak +// from parents and help defeat ASLR on buggy kernels. We reset it to NULL. +// See crbug.com/177956. +void ResetChildSignalHandlersToDefaults(void) { + for (int signum = 1; ; ++signum) { + struct kernel_sigaction act = {0}; + int sigaction_get_ret = sys_rt_sigaction(signum, NULL, &act); + if (sigaction_get_ret && errno == EINVAL) { +#if !defined(NDEBUG) + // Linux supports 32 real-time signals from 33 to 64. + // If the number of signals in the Linux kernel changes, someone should + // look at this code. + const int kNumberOfSignals = 64; + RAW_CHECK(signum == kNumberOfSignals + 1); +#endif // !defined(NDEBUG) + break; + } + // All other failures are fatal. + if (sigaction_get_ret) { + RAW_LOG(FATAL, "sigaction (get) failed."); + } + + // The kernel won't allow to re-set SIGKILL or SIGSTOP. + if (signum != SIGSTOP && signum != SIGKILL) { + act.k_sa_handler = reinterpret_cast(SIG_DFL); + act.k_sa_restorer = NULL; + if (sys_rt_sigaction(signum, &act, NULL)) { + RAW_LOG(FATAL, "sigaction (set) failed."); + } + } +#if !defined(NDEBUG) + // Now ask the kernel again and check that no restorer will leak. + if (sys_rt_sigaction(signum, NULL, &act) || act.k_sa_restorer) { + RAW_LOG(FATAL, "Cound not fix sa_restorer."); + } +#endif // !defined(NDEBUG) + } +} +#endif // !defined(OS_LINUX) || + // (!defined(__i386__) && !defined(__x86_64__) && !defined(__arm__)) + +} // anonymous namespace + +// A class to handle auto-closing of DIR*'s. +class ScopedDIRClose { + public: + inline void operator()(DIR* x) const { + if (x) { + closedir(x); + } + } +}; +typedef scoped_ptr_malloc ScopedDIR; + +#if defined(OS_LINUX) +static const char kFDDir[] = "/proc/self/fd"; +#elif defined(OS_MACOSX) +static const char kFDDir[] = "/dev/fd"; +#elif defined(OS_SOLARIS) +static const char kFDDir[] = "/dev/fd"; +#elif defined(OS_FREEBSD) +static const char kFDDir[] = "/dev/fd"; +#elif defined(OS_OPENBSD) +static const char kFDDir[] = "/dev/fd"; +#elif defined(OS_ANDROID) +static const char kFDDir[] = "/proc/self/fd"; +#endif + +void CloseSuperfluousFds(const base::InjectiveMultimap& saved_mapping) { + // DANGER: no calls to malloc are allowed from now on: + // http://crbug.com/36678 + + // Get the maximum number of FDs possible. + size_t max_fds = GetMaxFds(); + + DirReaderPosix fd_dir(kFDDir); + if (!fd_dir.IsValid()) { + // Fallback case: Try every possible fd. + for (size_t i = 0; i < max_fds; ++i) { + const int fd = static_cast(i); + if (fd == STDIN_FILENO || fd == STDOUT_FILENO || fd == STDERR_FILENO) + continue; + InjectiveMultimap::const_iterator j; + for (j = saved_mapping.begin(); j != saved_mapping.end(); j++) { + if (fd == j->dest) + break; + } + if (j != saved_mapping.end()) + continue; + + // Since we're just trying to close anything we can find, + // ignore any error return values of close(). + ignore_result(HANDLE_EINTR(close(fd))); + } + return; + } + + const int dir_fd = fd_dir.fd(); + + for ( ; fd_dir.Next(); ) { + // Skip . and .. entries. + if (fd_dir.name()[0] == '.') + continue; + + char *endptr; + errno = 0; + const long int fd = strtol(fd_dir.name(), &endptr, 10); + if (fd_dir.name()[0] == 0 || *endptr || fd < 0 || errno) + continue; + if (fd == STDIN_FILENO || fd == STDOUT_FILENO || fd == STDERR_FILENO) + continue; + InjectiveMultimap::const_iterator i; + for (i = saved_mapping.begin(); i != saved_mapping.end(); i++) { + if (fd == i->dest) + break; + } + if (i != saved_mapping.end()) + continue; + if (fd == dir_fd) + continue; + + // When running under Valgrind, Valgrind opens several FDs for its + // own use and will complain if we try to close them. All of + // these FDs are >= |max_fds|, so we can check against that here + // before closing. See https://bugs.kde.org/show_bug.cgi?id=191758 + if (fd < static_cast(max_fds)) { + int ret = HANDLE_EINTR(close(fd)); + DPCHECK(ret == 0); + } + } +} + +char** AlterEnvironment(const EnvironmentVector& changes, + const char* const* const env) { + unsigned count = 0; + unsigned size = 0; + + // First assume that all of the current environment will be included. + for (unsigned i = 0; env[i]; i++) { + const char *const pair = env[i]; + count++; + size += strlen(pair) + 1 /* terminating NUL */; + } + + for (EnvironmentVector::const_iterator j = changes.begin(); + j != changes.end(); + ++j) { + bool found = false; + const char *pair; + + for (unsigned i = 0; env[i]; i++) { + pair = env[i]; + const char *const equals = strchr(pair, '='); + if (!equals) + continue; + const unsigned keylen = equals - pair; + if (keylen == j->first.size() && + memcmp(pair, j->first.data(), keylen) == 0) { + found = true; + break; + } + } + + // if found, we'll either be deleting or replacing this element. + if (found) { + count--; + size -= strlen(pair) + 1; + if (j->second.size()) + found = false; + } + + // if !found, then we have a new element to add. + if (!found && !j->second.empty()) { + count++; + size += j->first.size() + 1 /* '=' */ + j->second.size() + 1 /* NUL */; + } + } + + count++; // for the final NULL + uint8_t *buffer = new uint8_t[sizeof(char*) * count + size]; + char **const ret = reinterpret_cast(buffer); + unsigned k = 0; + char *scratch = reinterpret_cast(buffer + sizeof(char*) * count); + + for (unsigned i = 0; env[i]; i++) { + const char *const pair = env[i]; + const char *const equals = strchr(pair, '='); + if (!equals) { + const unsigned len = strlen(pair); + ret[k++] = scratch; + memcpy(scratch, pair, len + 1); + scratch += len + 1; + continue; + } + const unsigned keylen = equals - pair; + bool handled = false; + for (EnvironmentVector::const_iterator + j = changes.begin(); j != changes.end(); j++) { + if (j->first.size() == keylen && + memcmp(j->first.data(), pair, keylen) == 0) { + if (!j->second.empty()) { + ret[k++] = scratch; + memcpy(scratch, pair, keylen + 1); + scratch += keylen + 1; + memcpy(scratch, j->second.c_str(), j->second.size() + 1); + scratch += j->second.size() + 1; + } + handled = true; + break; + } + } + + if (!handled) { + const unsigned len = strlen(pair); + ret[k++] = scratch; + memcpy(scratch, pair, len + 1); + scratch += len + 1; + } + } + + // Now handle new elements + for (EnvironmentVector::const_iterator + j = changes.begin(); j != changes.end(); j++) { + if (j->second.empty()) + continue; + + bool found = false; + for (unsigned i = 0; env[i]; i++) { + const char *const pair = env[i]; + const char *const equals = strchr(pair, '='); + if (!equals) + continue; + const unsigned keylen = equals - pair; + if (keylen == j->first.size() && + memcmp(pair, j->first.data(), keylen) == 0) { + found = true; + break; + } + } + + if (!found) { + ret[k++] = scratch; + memcpy(scratch, j->first.data(), j->first.size()); + scratch += j->first.size(); + *scratch++ = '='; + memcpy(scratch, j->second.c_str(), j->second.size() + 1); + scratch += j->second.size() + 1; + } + } + + ret[k] = NULL; + return ret; +} + +bool LaunchProcess(const std::vector& argv, + const LaunchOptions& options, + ProcessHandle* process_handle) { + size_t fd_shuffle_size = 0; + if (options.fds_to_remap) { + fd_shuffle_size = options.fds_to_remap->size(); + } + + InjectiveMultimap fd_shuffle1; + InjectiveMultimap fd_shuffle2; + fd_shuffle1.reserve(fd_shuffle_size); + fd_shuffle2.reserve(fd_shuffle_size); + + scoped_ptr argv_cstr(new char*[argv.size() + 1]); + scoped_ptr new_environ; + if (options.environ) + new_environ.reset(AlterEnvironment(*options.environ, GetEnvironment())); + + sigset_t full_sigset; + sigfillset(&full_sigset); + const sigset_t orig_sigmask = SetSignalMask(full_sigset); + + pid_t pid; +#if defined(OS_LINUX) + if (options.clone_flags) { + // Signal handling in this function assumes the creation of a new + // process, so we check that a thread is not being created by mistake + // and that signal handling follows the process-creation rules. + RAW_CHECK( + !(options.clone_flags & (CLONE_SIGHAND | CLONE_THREAD | CLONE_VM))); + pid = syscall(__NR_clone, options.clone_flags, 0, 0, 0); + } else +#endif + { + pid = fork(); + } + + // Always restore the original signal mask in the parent. + if (pid != 0) { + SetSignalMask(orig_sigmask); + } + + if (pid < 0) { + DPLOG(ERROR) << "fork"; + return false; + } else if (pid == 0) { + // Child process + + // DANGER: fork() rule: in the child, if you don't end up doing exec*(), + // you call _exit() instead of exit(). This is because _exit() does not + // call any previously-registered (in the parent) exit handlers, which + // might do things like block waiting for threads that don't even exist + // in the child. + + // If a child process uses the readline library, the process block forever. + // In BSD like OSes including OS X it is safe to assign /dev/null as stdin. + // See http://crbug.com/56596. + int null_fd = HANDLE_EINTR(open("/dev/null", O_RDONLY)); + if (null_fd < 0) { + RAW_LOG(ERROR, "Failed to open /dev/null"); + _exit(127); + } + + file_util::ScopedFD null_fd_closer(&null_fd); + int new_fd = HANDLE_EINTR(dup2(null_fd, STDIN_FILENO)); + if (new_fd != STDIN_FILENO) { + RAW_LOG(ERROR, "Failed to dup /dev/null for stdin"); + _exit(127); + } + + if (options.new_process_group) { + // Instead of inheriting the process group ID of the parent, the child + // starts off a new process group with pgid equal to its process ID. + if (setpgid(0, 0) < 0) { + RAW_LOG(ERROR, "setpgid failed"); + _exit(127); + } + } + + // Stop type-profiler. + // The profiler should be stopped between fork and exec since it inserts + // locks at new/delete expressions. See http://crbug.com/36678. + base::type_profiler::Controller::Stop(); + + if (options.maximize_rlimits) { + // Some resource limits need to be maximal in this child. + std::set::const_iterator resource; + for (resource = options.maximize_rlimits->begin(); + resource != options.maximize_rlimits->end(); + ++resource) { + struct rlimit limit; + if (getrlimit(*resource, &limit) < 0) { + RAW_LOG(WARNING, "getrlimit failed"); + } else if (limit.rlim_cur < limit.rlim_max) { + limit.rlim_cur = limit.rlim_max; + if (setrlimit(*resource, &limit) < 0) { + RAW_LOG(WARNING, "setrlimit failed"); + } + } + } + } + +#if defined(OS_MACOSX) + RestoreDefaultExceptionHandler(); +#endif // defined(OS_MACOSX) + + ResetChildSignalHandlersToDefaults(); + SetSignalMask(orig_sigmask); + +#if 0 + // When debugging it can be helpful to check that we really aren't making + // any hidden calls to malloc. + void *malloc_thunk = + reinterpret_cast(reinterpret_cast(malloc) & ~4095); + mprotect(malloc_thunk, 4096, PROT_READ | PROT_WRITE | PROT_EXEC); + memset(reinterpret_cast(malloc), 0xff, 8); +#endif // 0 + + // DANGER: no calls to malloc are allowed from now on: + // http://crbug.com/36678 + +#if defined(OS_CHROMEOS) + if (options.ctrl_terminal_fd >= 0) { + // Set process' controlling terminal. + if (HANDLE_EINTR(setsid()) != -1) { + if (HANDLE_EINTR( + ioctl(options.ctrl_terminal_fd, TIOCSCTTY, NULL)) == -1) { + RAW_LOG(WARNING, "ioctl(TIOCSCTTY), ctrl terminal not set"); + } + } else { + RAW_LOG(WARNING, "setsid failed, ctrl terminal not set"); + } + } +#endif // defined(OS_CHROMEOS) + + if (options.fds_to_remap) { + for (FileHandleMappingVector::const_iterator + it = options.fds_to_remap->begin(); + it != options.fds_to_remap->end(); ++it) { + fd_shuffle1.push_back(InjectionArc(it->first, it->second, false)); + fd_shuffle2.push_back(InjectionArc(it->first, it->second, false)); + } + } + + if (options.environ) + SetEnvironment(new_environ.get()); + + // fd_shuffle1 is mutated by this call because it cannot malloc. + if (!ShuffleFileDescriptors(&fd_shuffle1)) + _exit(127); + + CloseSuperfluousFds(fd_shuffle2); + + for (size_t i = 0; i < argv.size(); i++) + argv_cstr[i] = const_cast(argv[i].c_str()); + argv_cstr[argv.size()] = NULL; + execvp(argv_cstr[0], argv_cstr.get()); + + RAW_LOG(ERROR, "LaunchProcess: failed to execvp:"); + RAW_LOG(ERROR, argv_cstr[0]); + _exit(127); + } else { + // Parent process + if (options.wait) { + // While this isn't strictly disk IO, waiting for another process to + // finish is the sort of thing ThreadRestrictions is trying to prevent. + base::ThreadRestrictions::AssertIOAllowed(); + pid_t ret = HANDLE_EINTR(waitpid(pid, 0, 0)); + DPCHECK(ret > 0); + } + + if (process_handle) + *process_handle = pid; + } + + return true; +} + + +bool LaunchProcess(const CommandLine& cmdline, + const LaunchOptions& options, + ProcessHandle* process_handle) { + return LaunchProcess(cmdline.argv(), options, process_handle); +} + +void RaiseProcessToHighPriority() { + // On POSIX, we don't actually do anything here. We could try to nice() or + // setpriority() or sched_getscheduler, but these all require extra rights. +} + +// Return value used by GetAppOutputInternal to encapsulate the various exit +// scenarios from the function. +enum GetAppOutputInternalResult { + EXECUTE_FAILURE, + EXECUTE_SUCCESS, + GOT_MAX_OUTPUT, +}; + +// Executes the application specified by |argv| and wait for it to exit. Stores +// the output (stdout) in |output|. If |do_search_path| is set, it searches the +// path for the application; in that case, |envp| must be null, and it will use +// the current environment. If |do_search_path| is false, |argv[0]| should fully +// specify the path of the application, and |envp| will be used as the +// environment. Redirects stderr to /dev/null. +// If we successfully start the application and get all requested output, we +// return GOT_MAX_OUTPUT, or if there is a problem starting or exiting +// the application we return RUN_FAILURE. Otherwise we return EXECUTE_SUCCESS. +// The GOT_MAX_OUTPUT return value exists so a caller that asks for limited +// output can treat this as a success, despite having an exit code of SIG_PIPE +// due to us closing the output pipe. +// In the case of EXECUTE_SUCCESS, the application exit code will be returned +// in |*exit_code|, which should be checked to determine if the application +// ran successfully. +static GetAppOutputInternalResult GetAppOutputInternal( + const std::vector& argv, + char* const envp[], + std::string* output, + size_t max_output, + bool do_search_path, + int* exit_code) { + // Doing a blocking wait for another command to finish counts as IO. + base::ThreadRestrictions::AssertIOAllowed(); + // exit_code must be supplied so calling function can determine success. + DCHECK(exit_code); + *exit_code = EXIT_FAILURE; + + int pipe_fd[2]; + pid_t pid; + InjectiveMultimap fd_shuffle1, fd_shuffle2; + scoped_ptr argv_cstr(new char*[argv.size() + 1]); + + fd_shuffle1.reserve(3); + fd_shuffle2.reserve(3); + + // Either |do_search_path| should be false or |envp| should be null, but not + // both. + DCHECK(!do_search_path ^ !envp); + + if (pipe(pipe_fd) < 0) + return EXECUTE_FAILURE; + + switch (pid = fork()) { + case -1: // error + close(pipe_fd[0]); + close(pipe_fd[1]); + return EXECUTE_FAILURE; + case 0: // child + { +#if defined(OS_MACOSX) + RestoreDefaultExceptionHandler(); +#endif + // DANGER: no calls to malloc are allowed from now on: + // http://crbug.com/36678 + + // Obscure fork() rule: in the child, if you don't end up doing exec*(), + // you call _exit() instead of exit(). This is because _exit() does not + // call any previously-registered (in the parent) exit handlers, which + // might do things like block waiting for threads that don't even exist + // in the child. + int dev_null = open("/dev/null", O_WRONLY); + if (dev_null < 0) + _exit(127); + + // Stop type-profiler. + // The profiler should be stopped between fork and exec since it inserts + // locks at new/delete expressions. See http://crbug.com/36678. + base::type_profiler::Controller::Stop(); + + fd_shuffle1.push_back(InjectionArc(pipe_fd[1], STDOUT_FILENO, true)); + fd_shuffle1.push_back(InjectionArc(dev_null, STDERR_FILENO, true)); + fd_shuffle1.push_back(InjectionArc(dev_null, STDIN_FILENO, true)); + // Adding another element here? Remeber to increase the argument to + // reserve(), above. + + std::copy(fd_shuffle1.begin(), fd_shuffle1.end(), + std::back_inserter(fd_shuffle2)); + + if (!ShuffleFileDescriptors(&fd_shuffle1)) + _exit(127); + + CloseSuperfluousFds(fd_shuffle2); + + for (size_t i = 0; i < argv.size(); i++) + argv_cstr[i] = const_cast(argv[i].c_str()); + argv_cstr[argv.size()] = NULL; + if (do_search_path) + execvp(argv_cstr[0], argv_cstr.get()); + else + execve(argv_cstr[0], argv_cstr.get(), envp); + _exit(127); + } + default: // parent + { + // Close our writing end of pipe now. Otherwise later read would not + // be able to detect end of child's output (in theory we could still + // write to the pipe). + close(pipe_fd[1]); + + output->clear(); + char buffer[256]; + size_t output_buf_left = max_output; + ssize_t bytes_read = 1; // A lie to properly handle |max_output == 0| + // case in the logic below. + + while (output_buf_left > 0) { + bytes_read = HANDLE_EINTR(read(pipe_fd[0], buffer, + std::min(output_buf_left, sizeof(buffer)))); + if (bytes_read <= 0) + break; + output->append(buffer, bytes_read); + output_buf_left -= static_cast(bytes_read); + } + close(pipe_fd[0]); + + // Always wait for exit code (even if we know we'll declare + // GOT_MAX_OUTPUT). + bool success = WaitForExitCode(pid, exit_code); + + // If we stopped because we read as much as we wanted, we return + // GOT_MAX_OUTPUT (because the child may exit due to |SIGPIPE|). + if (!output_buf_left && bytes_read > 0) + return GOT_MAX_OUTPUT; + else if (success) + return EXECUTE_SUCCESS; + return EXECUTE_FAILURE; + } + } +} + +bool GetAppOutput(const CommandLine& cl, std::string* output) { + return GetAppOutput(cl.argv(), output); +} + +bool GetAppOutput(const std::vector& argv, std::string* output) { + // Run |execve()| with the current environment and store "unlimited" data. + int exit_code; + GetAppOutputInternalResult result = GetAppOutputInternal( + argv, NULL, output, std::numeric_limits::max(), true, + &exit_code); + return result == EXECUTE_SUCCESS && exit_code == EXIT_SUCCESS; +} + +// TODO(viettrungluu): Conceivably, we should have a timeout as well, so we +// don't hang if what we're calling hangs. +bool GetAppOutputRestricted(const CommandLine& cl, + std::string* output, size_t max_output) { + // Run |execve()| with the empty environment. + char* const empty_environ = NULL; + int exit_code; + GetAppOutputInternalResult result = GetAppOutputInternal( + cl.argv(), &empty_environ, output, max_output, false, &exit_code); + return result == GOT_MAX_OUTPUT || (result == EXECUTE_SUCCESS && + exit_code == EXIT_SUCCESS); +} + +bool GetAppOutputWithExitCode(const CommandLine& cl, + std::string* output, + int* exit_code) { + // Run |execve()| with the current environment and store "unlimited" data. + GetAppOutputInternalResult result = GetAppOutputInternal( + cl.argv(), NULL, output, std::numeric_limits::max(), true, + exit_code); + return result == EXECUTE_SUCCESS; +} + +} // namespace base diff --git a/base/process/launch_win.cc b/base/process/launch_win.cc new file mode 100644 index 0000000000..c5126c56fb --- /dev/null +++ b/base/process/launch_win.cc @@ -0,0 +1,283 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/launch.h" + +#include +#include +#include +#include +#include + +#include + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/command_line.h" +#include "base/debug/stack_trace.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/metrics/histogram.h" +#include "base/process/kill.h" +#include "base/sys_info.h" +#include "base/win/object_watcher.h" +#include "base/win/scoped_handle.h" +#include "base/win/scoped_process_information.h" +#include "base/win/windows_version.h" + +// userenv.dll is required for CreateEnvironmentBlock(). +#pragma comment(lib, "userenv.lib") + +namespace base { + +namespace { + +// This exit code is used by the Windows task manager when it kills a +// process. It's value is obviously not that unique, and it's +// surprising to me that the task manager uses this value, but it +// seems to be common practice on Windows to test for it as an +// indication that the task manager has killed something if the +// process goes away. +const DWORD kProcessKilledExitCode = 1; + +} // namespace + +void RouteStdioToConsole() { + // Don't change anything if stdout or stderr already point to a + // valid stream. + // + // If we are running under Buildbot or under Cygwin's default + // terminal (mintty), stderr and stderr will be pipe handles. In + // that case, we don't want to open CONOUT$, because its output + // likely does not go anywhere. + // + // We don't use GetStdHandle() to check stdout/stderr here because + // it can return dangling IDs of handles that were never inherited + // by this process. These IDs could have been reused by the time + // this function is called. The CRT checks the validity of + // stdout/stderr on startup (before the handle IDs can be reused). + // _fileno(stdout) will return -2 (_NO_CONSOLE_FILENO) if stdout was + // invalid. + if (_fileno(stdout) >= 0 || _fileno(stderr) >= 0) + return; + + if (!AttachConsole(ATTACH_PARENT_PROCESS)) { + unsigned int result = GetLastError(); + // Was probably already attached. + if (result == ERROR_ACCESS_DENIED) + return; + // Don't bother creating a new console for each child process if the + // parent process is invalid (eg: crashed). + if (result == ERROR_GEN_FAILURE) + return; + // Make a new console if attaching to parent fails with any other error. + // It should be ERROR_INVALID_HANDLE at this point, which means the browser + // was likely not started from a console. + AllocConsole(); + } + + // Arbitrary byte count to use when buffering output lines. More + // means potential waste, less means more risk of interleaved + // log-lines in output. + enum { kOutputBufferSize = 64 * 1024 }; + + if (freopen("CONOUT$", "w", stdout)) { + setvbuf(stdout, NULL, _IOLBF, kOutputBufferSize); + // Overwrite FD 1 for the benefit of any code that uses this FD + // directly. This is safe because the CRT allocates FDs 0, 1 and + // 2 at startup even if they don't have valid underlying Windows + // handles. This means we won't be overwriting an FD created by + // _open() after startup. + _dup2(_fileno(stdout), 1); + } + if (freopen("CONOUT$", "w", stderr)) { + setvbuf(stderr, NULL, _IOLBF, kOutputBufferSize); + _dup2(_fileno(stderr), 2); + } + + // Fix all cout, wcout, cin, wcin, cerr, wcerr, clog and wclog. + std::ios::sync_with_stdio(); +} + +bool LaunchProcess(const string16& cmdline, + const LaunchOptions& options, + ProcessHandle* process_handle) { + STARTUPINFO startup_info = {}; + startup_info.cb = sizeof(startup_info); + if (options.empty_desktop_name) + startup_info.lpDesktop = L""; + startup_info.dwFlags = STARTF_USESHOWWINDOW; + startup_info.wShowWindow = options.start_hidden ? SW_HIDE : SW_SHOW; + + if (options.stdin_handle || options.stdout_handle || options.stderr_handle) { + DCHECK(options.inherit_handles); + DCHECK(options.stdin_handle); + DCHECK(options.stdout_handle); + DCHECK(options.stderr_handle); + startup_info.dwFlags |= STARTF_USESTDHANDLES; + startup_info.hStdInput = options.stdin_handle; + startup_info.hStdOutput = options.stdout_handle; + startup_info.hStdError = options.stderr_handle; + } + + DWORD flags = 0; + + if (options.job_handle) { + flags |= CREATE_SUSPENDED; + + // If this code is run under a debugger, the launched process is + // automatically associated with a job object created by the debugger. + // The CREATE_BREAKAWAY_FROM_JOB flag is used to prevent this. + flags |= CREATE_BREAKAWAY_FROM_JOB; + } + + if (options.force_breakaway_from_job_) + flags |= CREATE_BREAKAWAY_FROM_JOB; + + base::win::ScopedProcessInformation process_info; + + if (options.as_user) { + flags |= CREATE_UNICODE_ENVIRONMENT; + void* enviroment_block = NULL; + + if (!CreateEnvironmentBlock(&enviroment_block, options.as_user, FALSE)) { + DPLOG(ERROR); + return false; + } + + BOOL launched = + CreateProcessAsUser(options.as_user, NULL, + const_cast(cmdline.c_str()), + NULL, NULL, options.inherit_handles, flags, + enviroment_block, NULL, &startup_info, + process_info.Receive()); + DestroyEnvironmentBlock(enviroment_block); + if (!launched) { + DPLOG(ERROR); + return false; + } + } else { + if (!CreateProcess(NULL, + const_cast(cmdline.c_str()), NULL, NULL, + options.inherit_handles, flags, NULL, NULL, + &startup_info, process_info.Receive())) { + DPLOG(ERROR); + return false; + } + } + + if (options.job_handle) { + if (0 == AssignProcessToJobObject(options.job_handle, + process_info.process_handle())) { + DLOG(ERROR) << "Could not AssignProcessToObject."; + KillProcess(process_info.process_handle(), kProcessKilledExitCode, true); + return false; + } + + ResumeThread(process_info.thread_handle()); + } + + if (options.wait) + WaitForSingleObject(process_info.process_handle(), INFINITE); + + // If the caller wants the process handle, we won't close it. + if (process_handle) + *process_handle = process_info.TakeProcessHandle(); + + return true; +} + +bool LaunchProcess(const CommandLine& cmdline, + const LaunchOptions& options, + ProcessHandle* process_handle) { + return LaunchProcess(cmdline.GetCommandLineString(), options, process_handle); +} + +bool SetJobObjectAsKillOnJobClose(HANDLE job_object) { + JOBOBJECT_EXTENDED_LIMIT_INFORMATION limit_info = {0}; + limit_info.BasicLimitInformation.LimitFlags = + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + return 0 != SetInformationJobObject( + job_object, + JobObjectExtendedLimitInformation, + &limit_info, + sizeof(limit_info)); +} + +bool GetAppOutput(const CommandLine& cl, std::string* output) { + HANDLE out_read = NULL; + HANDLE out_write = NULL; + + SECURITY_ATTRIBUTES sa_attr; + // Set the bInheritHandle flag so pipe handles are inherited. + sa_attr.nLength = sizeof(SECURITY_ATTRIBUTES); + sa_attr.bInheritHandle = TRUE; + sa_attr.lpSecurityDescriptor = NULL; + + // Create the pipe for the child process's STDOUT. + if (!CreatePipe(&out_read, &out_write, &sa_attr, 0)) { + NOTREACHED() << "Failed to create pipe"; + return false; + } + + // Ensure we don't leak the handles. + win::ScopedHandle scoped_out_read(out_read); + win::ScopedHandle scoped_out_write(out_write); + + // Ensure the read handle to the pipe for STDOUT is not inherited. + if (!SetHandleInformation(out_read, HANDLE_FLAG_INHERIT, 0)) { + NOTREACHED() << "Failed to disabled pipe inheritance"; + return false; + } + + FilePath::StringType writable_command_line_string(cl.GetCommandLineString()); + + base::win::ScopedProcessInformation proc_info; + STARTUPINFO start_info = { 0 }; + + start_info.cb = sizeof(STARTUPINFO); + start_info.hStdOutput = out_write; + // Keep the normal stdin and stderr. + start_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + start_info.hStdError = GetStdHandle(STD_ERROR_HANDLE); + start_info.dwFlags |= STARTF_USESTDHANDLES; + + // Create the child process. + if (!CreateProcess(NULL, + &writable_command_line_string[0], + NULL, NULL, + TRUE, // Handles are inherited. + 0, NULL, NULL, &start_info, proc_info.Receive())) { + NOTREACHED() << "Failed to start process"; + return false; + } + + // Close our writing end of pipe now. Otherwise later read would not be able + // to detect end of child's output. + scoped_out_write.Close(); + + // Read output from the child process's pipe for STDOUT + const int kBufferSize = 1024; + char buffer[kBufferSize]; + + for (;;) { + DWORD bytes_read = 0; + BOOL success = ReadFile(out_read, buffer, kBufferSize, &bytes_read, NULL); + if (!success || bytes_read == 0) + break; + output->append(buffer, bytes_read); + } + + // Let's wait for the process to finish. + WaitForSingleObject(proc_info.process_handle(), INFINITE); + + return true; +} + +void RaiseProcessToHighPriority() { + SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); +} + +} // namespace base diff --git a/base/process/memory.h b/base/process/memory.h new file mode 100644 index 0000000000..de79477e95 --- /dev/null +++ b/base/process/memory.h @@ -0,0 +1,69 @@ +// 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_PROCESS_MEMORY_H_ +#define BASE_PROCESS_MEMORY_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/process/process_handle.h" +#include "build/build_config.h" + +#if defined(OS_WIN) +#include +#endif + +namespace base { + +// Enables low fragmentation heap (LFH) for every heaps of this process. This +// won't have any effect on heaps created after this function call. It will not +// modify data allocated in the heaps before calling this function. So it is +// better to call this function early in initialization and again before +// entering the main loop. +// Note: Returns true on Windows 2000 without doing anything. +BASE_EXPORT bool EnableLowFragmentationHeap(); + +// Enables 'terminate on heap corruption' flag. Helps protect against heap +// overflow. Has no effect if the OS doesn't provide the necessary facility. +BASE_EXPORT void EnableTerminationOnHeapCorruption(); + +// Turns on process termination if memory runs out. +BASE_EXPORT void EnableTerminationOnOutOfMemory(); + +#if defined(OS_WIN) +// Returns the module handle to which an address belongs. The reference count +// of the module is not incremented. +BASE_EXPORT HMODULE GetModuleFromAddress(void* address); +#endif + +#if defined(OS_LINUX) || defined(OS_ANDROID) +BASE_EXPORT extern size_t g_oom_size; + +// The maximum allowed value for the OOM score. +const int kMaxOomScore = 1000; + +// This adjusts /proc//oom_score_adj so the Linux OOM killer will +// prefer to kill certain process types over others. The range for the +// adjustment is [-1000, 1000], with [0, 1000] being user accessible. +// If the Linux system doesn't support the newer oom_score_adj range +// of [0, 1000], then we revert to using the older oom_adj, and +// translate the given value into [0, 15]. Some aliasing of values +// may occur in that case, of course. +BASE_EXPORT bool AdjustOOMScore(ProcessId process, int score); +#endif + +#if defined(OS_MACOSX) +// Very large images or svg canvases can cause huge mallocs. Skia +// does tricks on tcmalloc-based systems to allow malloc to fail with +// a NULL rather than hit the oom crasher. This replicates that for +// OSX. +// +// IF YOU USE THIS WITHOUT CONSULTING YOUR FRIENDLY OSX DEVELOPER, +// YOUR CODE IS LIKELY TO BE REVERTED. THANK YOU. +BASE_EXPORT void* UncheckedMalloc(size_t size); +#endif // defined(OS_MACOSX) + +} // namespace base + +#endif // BASE_PROCESS_MEMORY_H_ diff --git a/base/process/memory_linux.cc b/base/process/memory_linux.cc new file mode 100644 index 0000000000..f81429b2ac --- /dev/null +++ b/base/process/memory_linux.cc @@ -0,0 +1,183 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/memory.h" + +#include + +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/process/internal_linux.h" +#include "base/strings/string_number_conversions.h" + +namespace base { + +size_t g_oom_size = 0U; + +namespace { + +void OnNoMemorySize(size_t size) { + g_oom_size = size; + + if (size != 0) + LOG(FATAL) << "Out of memory, size = " << size; + LOG(FATAL) << "Out of memory."; +} + +void OnNoMemory() { + OnNoMemorySize(0); +} + +} // namespace + +#if !defined(ADDRESS_SANITIZER) && !defined(MEMORY_SANITIZER) && \ + !defined(THREAD_SANITIZER) && !defined(LEAK_SANITIZER) + +#if defined(LIBC_GLIBC) && !defined(USE_TCMALLOC) + +extern "C" { +void* __libc_malloc(size_t size); +void* __libc_realloc(void* ptr, size_t size); +void* __libc_calloc(size_t nmemb, size_t size); +void* __libc_valloc(size_t size); +void* __libc_pvalloc(size_t size); +void* __libc_memalign(size_t alignment, size_t size); + +// Overriding the system memory allocation functions: +// +// For security reasons, we want malloc failures to be fatal. Too much code +// doesn't check for a NULL return value from malloc and unconditionally uses +// the resulting pointer. If the first offset that they try to access is +// attacker controlled, then the attacker can direct the code to access any +// part of memory. +// +// Thus, we define all the standard malloc functions here and mark them as +// visibility 'default'. This means that they replace the malloc functions for +// all Chromium code and also for all code in shared libraries. There are tests +// for this in process_util_unittest.cc. +// +// If we are using tcmalloc, then the problem is moot since tcmalloc handles +// this for us. Thus this code is in a !defined(USE_TCMALLOC) block. +// +// If we are testing the binary with AddressSanitizer, we should not +// redefine malloc and let AddressSanitizer do it instead. +// +// We call the real libc functions in this code by using __libc_malloc etc. +// Previously we tried using dlsym(RTLD_NEXT, ...) but that failed depending on +// the link order. Since ld.so needs calloc during symbol resolution, it +// defines its own versions of several of these functions in dl-minimal.c. +// Depending on the runtime library order, dlsym ended up giving us those +// functions and bad things happened. See crbug.com/31809 +// +// This means that any code which calls __libc_* gets the raw libc versions of +// these functions. + +#define DIE_ON_OOM_1(function_name) \ + void* function_name(size_t) __attribute__ ((visibility("default"))); \ + \ + void* function_name(size_t size) { \ + void* ret = __libc_##function_name(size); \ + if (ret == NULL && size != 0) \ + OnNoMemorySize(size); \ + return ret; \ + } + +#define DIE_ON_OOM_2(function_name, arg1_type) \ + void* function_name(arg1_type, size_t) \ + __attribute__ ((visibility("default"))); \ + \ + void* function_name(arg1_type arg1, size_t size) { \ + void* ret = __libc_##function_name(arg1, size); \ + if (ret == NULL && size != 0) \ + OnNoMemorySize(size); \ + return ret; \ + } + +DIE_ON_OOM_1(malloc) +DIE_ON_OOM_1(valloc) +DIE_ON_OOM_1(pvalloc) + +DIE_ON_OOM_2(calloc, size_t) +DIE_ON_OOM_2(realloc, void*) +DIE_ON_OOM_2(memalign, size_t) + +// posix_memalign has a unique signature and doesn't have a __libc_ variant. +int posix_memalign(void** ptr, size_t alignment, size_t size) + __attribute__ ((visibility("default"))); + +int posix_memalign(void** ptr, size_t alignment, size_t size) { + // This will use the safe version of memalign, above. + *ptr = memalign(alignment, size); + return 0; +} + +} // extern C + +#else + +// TODO(mostynb@opera.com): dlsym dance + +#endif // LIBC_GLIBC && !USE_TCMALLOC + +#endif // !*_SANITIZER + +void EnableTerminationOnHeapCorruption() { + // On Linux, there nothing to do AFAIK. +} + +void EnableTerminationOnOutOfMemory() { +#if defined(OS_ANDROID) + // Android doesn't support setting a new handler. + DLOG(WARNING) << "Not feasible."; +#else + // Set the new-out of memory handler. + std::set_new_handler(&OnNoMemory); + // If we're using glibc's allocator, the above functions will override + // malloc and friends and make them die on out of memory. +#endif +} + +// NOTE: This is not the only version of this function in the source: +// the setuid sandbox (in process_util_linux.c, in the sandbox source) +// also has its own C version. +bool AdjustOOMScore(ProcessId process, int score) { + if (score < 0 || score > kMaxOomScore) + return false; + + FilePath oom_path(internal::GetProcPidDir(process)); + + // Attempt to write the newer oom_score_adj file first. + FilePath oom_file = oom_path.AppendASCII("oom_score_adj"); + if (PathExists(oom_file)) { + std::string score_str = IntToString(score); + DVLOG(1) << "Adjusting oom_score_adj of " << process << " to " + << score_str; + int score_len = static_cast(score_str.length()); + return (score_len == file_util::WriteFile(oom_file, + score_str.c_str(), + score_len)); + } + + // If the oom_score_adj file doesn't exist, then we write the old + // style file and translate the oom_adj score to the range 0-15. + oom_file = oom_path.AppendASCII("oom_adj"); + if (PathExists(oom_file)) { + // Max score for the old oom_adj range. Used for conversion of new + // values to old values. + const int kMaxOldOomScore = 15; + + int converted_score = score * kMaxOldOomScore / kMaxOomScore; + std::string score_str = IntToString(converted_score); + DVLOG(1) << "Adjusting oom_adj of " << process << " to " << score_str; + int score_len = static_cast(score_str.length()); + return (score_len == file_util::WriteFile(oom_file, + score_str.c_str(), + score_len)); + } + + return false; +} + +} // namespace base diff --git a/base/process/memory_mac.mm b/base/process/memory_mac.mm new file mode 100644 index 0000000000..dd30e704c8 --- /dev/null +++ b/base/process/memory_mac.mm @@ -0,0 +1,704 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/memory.h" + +#include +#include +#include +#include +#include +#import + +#include + +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/mac/mac_util.h" +#include "base/scoped_clear_errno.h" +#include "third_party/apple_apsl/CFBase.h" +#include "third_party/apple_apsl/malloc.h" + +#if ARCH_CPU_32_BITS +#include +#include + +#include "base/threading/thread_local.h" +#include "third_party/mach_override/mach_override.h" +#endif // ARCH_CPU_32_BITS + +namespace base { + +// These are helpers for EnableTerminationOnHeapCorruption, which is a no-op +// on 64 bit Macs. +#if ARCH_CPU_32_BITS +namespace { + +// Finds the library path for malloc() and thus the libC part of libSystem, +// which in Lion is in a separate image. +const char* LookUpLibCPath() { + const void* addr = reinterpret_cast(&malloc); + + Dl_info info; + if (dladdr(addr, &info)) + return info.dli_fname; + + DLOG(WARNING) << "Could not find image path for malloc()"; + return NULL; +} + +typedef void(*malloc_error_break_t)(void); +malloc_error_break_t g_original_malloc_error_break = NULL; + +// Returns the function pointer for malloc_error_break. This symbol is declared +// as __private_extern__ and cannot be dlsym()ed. Instead, use nlist() to +// get it. +malloc_error_break_t LookUpMallocErrorBreak() { + const char* lib_c_path = LookUpLibCPath(); + if (!lib_c_path) + return NULL; + + // Only need to look up two symbols, but nlist() requires a NULL-terminated + // array and takes no count. + struct nlist nl[3]; + bzero(&nl, sizeof(nl)); + + // The symbol to find. + nl[0].n_un.n_name = const_cast("_malloc_error_break"); + + // A reference symbol by which the address of the desired symbol will be + // calculated. + nl[1].n_un.n_name = const_cast("_malloc"); + + int rv = nlist(lib_c_path, nl); + if (rv != 0 || nl[0].n_type == N_UNDF || nl[1].n_type == N_UNDF) { + return NULL; + } + + // nlist() returns addresses as offsets in the image, not the instruction + // pointer in memory. Use the known in-memory address of malloc() + // to compute the offset for malloc_error_break(). + uintptr_t reference_addr = reinterpret_cast(&malloc); + reference_addr -= nl[1].n_value; + reference_addr += nl[0].n_value; + + return reinterpret_cast(reference_addr); +} + +// Combines ThreadLocalBoolean with AutoReset. It would be convenient +// to compose ThreadLocalPointer with base::AutoReset, but that +// would require allocating some storage for the bool. +class ThreadLocalBooleanAutoReset { + public: + ThreadLocalBooleanAutoReset(ThreadLocalBoolean* tlb, bool new_value) + : scoped_tlb_(tlb), + original_value_(tlb->Get()) { + scoped_tlb_->Set(new_value); + } + ~ThreadLocalBooleanAutoReset() { + scoped_tlb_->Set(original_value_); + } + + private: + ThreadLocalBoolean* scoped_tlb_; + bool original_value_; + + DISALLOW_COPY_AND_ASSIGN(ThreadLocalBooleanAutoReset); +}; + +base::LazyInstance::Leaky + g_unchecked_malloc = LAZY_INSTANCE_INITIALIZER; + +// NOTE(shess): This is called when the malloc library noticed that the heap +// is fubar. Avoid calls which will re-enter the malloc library. +void CrMallocErrorBreak() { + g_original_malloc_error_break(); + + // Out of memory is certainly not heap corruption, and not necessarily + // something for which the process should be terminated. Leave that decision + // to the OOM killer. The EBADF case comes up because the malloc library + // attempts to log to ASL (syslog) before calling this code, which fails + // accessing a Unix-domain socket because of sandboxing. + if (errno == ENOMEM || (errno == EBADF && g_unchecked_malloc.Get().Get())) + return; + + // A unit test checks this error message, so it needs to be in release builds. + char buf[1024] = + "Terminating process due to a potential for future heap corruption: " + "errno="; + char errnobuf[] = { + '0' + ((errno / 100) % 10), + '0' + ((errno / 10) % 10), + '0' + (errno % 10), + '\000' + }; + COMPILE_ASSERT(ELAST <= 999, errno_too_large_to_encode); + strlcat(buf, errnobuf, sizeof(buf)); + RAW_LOG(ERROR, buf); + + // Crash by writing to NULL+errno to allow analyzing errno from + // crash dump info (setting a breakpad key would re-enter the malloc + // library). Max documented errno in intro(2) is actually 102, but + // it really just needs to be "small" to stay on the right vm page. + const int kMaxErrno = 256; + char* volatile death_ptr = NULL; + death_ptr += std::min(errno, kMaxErrno); + *death_ptr = '!'; +} + +} // namespace +#endif // ARCH_CPU_32_BITS + +void EnableTerminationOnHeapCorruption() { +#if defined(ADDRESS_SANITIZER) || ARCH_CPU_64_BITS + // AddressSanitizer handles heap corruption, and on 64 bit Macs, the malloc + // system automatically abort()s on heap corruption. + return; +#else + // Only override once, otherwise CrMallocErrorBreak() will recurse + // to itself. + if (g_original_malloc_error_break) + return; + + malloc_error_break_t malloc_error_break = LookUpMallocErrorBreak(); + if (!malloc_error_break) { + DLOG(WARNING) << "Could not find malloc_error_break"; + return; + } + + mach_error_t err = mach_override_ptr( + (void*)malloc_error_break, + (void*)&CrMallocErrorBreak, + (void**)&g_original_malloc_error_break); + + if (err != err_none) + DLOG(WARNING) << "Could not override malloc_error_break; error = " << err; +#endif // defined(ADDRESS_SANITIZER) || ARCH_CPU_64_BITS +} + +// ------------------------------------------------------------------------ + +namespace { + +bool g_oom_killer_enabled; + +// Starting with Mac OS X 10.7, the zone allocators set up by the system are +// read-only, to prevent them from being overwritten in an attack. However, +// blindly unprotecting and reprotecting the zone allocators fails with +// GuardMalloc because GuardMalloc sets up its zone allocator using a block of +// memory in its bss. Explicit saving/restoring of the protection is required. +// +// This function takes a pointer to a malloc zone, de-protects it if necessary, +// and returns (in the out parameters) a region of memory (if any) to be +// re-protected when modifications are complete. This approach assumes that +// there is no contention for the protection of this memory. +void DeprotectMallocZone(ChromeMallocZone* default_zone, + mach_vm_address_t* reprotection_start, + mach_vm_size_t* reprotection_length, + vm_prot_t* reprotection_value) { + mach_port_t unused; + *reprotection_start = reinterpret_cast(default_zone); + struct vm_region_basic_info_64 info; + mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64; + kern_return_t result = + mach_vm_region(mach_task_self(), + reprotection_start, + reprotection_length, + VM_REGION_BASIC_INFO_64, + reinterpret_cast(&info), + &count, + &unused); + CHECK(result == KERN_SUCCESS); + + result = mach_port_deallocate(mach_task_self(), unused); + CHECK(result == KERN_SUCCESS); + + // Does the region fully enclose the zone pointers? Possibly unwarranted + // simplification used: using the size of a full version 8 malloc zone rather + // than the actual smaller size if the passed-in zone is not version 8. + CHECK(*reprotection_start <= + reinterpret_cast(default_zone)); + mach_vm_size_t zone_offset = reinterpret_cast(default_zone) - + reinterpret_cast(*reprotection_start); + CHECK(zone_offset + sizeof(ChromeMallocZone) <= *reprotection_length); + + if (info.protection & VM_PROT_WRITE) { + // No change needed; the zone is already writable. + *reprotection_start = 0; + *reprotection_length = 0; + *reprotection_value = VM_PROT_NONE; + } else { + *reprotection_value = info.protection; + result = mach_vm_protect(mach_task_self(), + *reprotection_start, + *reprotection_length, + false, + info.protection | VM_PROT_WRITE); + CHECK(result == KERN_SUCCESS); + } +} + +// === C malloc/calloc/valloc/realloc/posix_memalign === + +typedef void* (*malloc_type)(struct _malloc_zone_t* zone, + size_t size); +typedef void* (*calloc_type)(struct _malloc_zone_t* zone, + size_t num_items, + size_t size); +typedef void* (*valloc_type)(struct _malloc_zone_t* zone, + size_t size); +typedef void (*free_type)(struct _malloc_zone_t* zone, + void* ptr); +typedef void* (*realloc_type)(struct _malloc_zone_t* zone, + void* ptr, + size_t size); +typedef void* (*memalign_type)(struct _malloc_zone_t* zone, + size_t alignment, + size_t size); + +malloc_type g_old_malloc; +calloc_type g_old_calloc; +valloc_type g_old_valloc; +free_type g_old_free; +realloc_type g_old_realloc; +memalign_type g_old_memalign; + +malloc_type g_old_malloc_purgeable; +calloc_type g_old_calloc_purgeable; +valloc_type g_old_valloc_purgeable; +free_type g_old_free_purgeable; +realloc_type g_old_realloc_purgeable; +memalign_type g_old_memalign_purgeable; + +void* oom_killer_malloc(struct _malloc_zone_t* zone, + size_t size) { +#if ARCH_CPU_32_BITS + ScopedClearErrno clear_errno; +#endif // ARCH_CPU_32_BITS + void* result = g_old_malloc(zone, size); + if (!result && size) + debug::BreakDebugger(); + return result; +} + +void* oom_killer_calloc(struct _malloc_zone_t* zone, + size_t num_items, + size_t size) { +#if ARCH_CPU_32_BITS + ScopedClearErrno clear_errno; +#endif // ARCH_CPU_32_BITS + void* result = g_old_calloc(zone, num_items, size); + if (!result && num_items && size) + debug::BreakDebugger(); + return result; +} + +void* oom_killer_valloc(struct _malloc_zone_t* zone, + size_t size) { +#if ARCH_CPU_32_BITS + ScopedClearErrno clear_errno; +#endif // ARCH_CPU_32_BITS + void* result = g_old_valloc(zone, size); + if (!result && size) + debug::BreakDebugger(); + return result; +} + +void oom_killer_free(struct _malloc_zone_t* zone, + void* ptr) { +#if ARCH_CPU_32_BITS + ScopedClearErrno clear_errno; +#endif // ARCH_CPU_32_BITS + g_old_free(zone, ptr); +} + +void* oom_killer_realloc(struct _malloc_zone_t* zone, + void* ptr, + size_t size) { +#if ARCH_CPU_32_BITS + ScopedClearErrno clear_errno; +#endif // ARCH_CPU_32_BITS + void* result = g_old_realloc(zone, ptr, size); + if (!result && size) + debug::BreakDebugger(); + return result; +} + +void* oom_killer_memalign(struct _malloc_zone_t* zone, + size_t alignment, + size_t size) { +#if ARCH_CPU_32_BITS + ScopedClearErrno clear_errno; +#endif // ARCH_CPU_32_BITS + void* result = g_old_memalign(zone, alignment, size); + // Only die if posix_memalign would have returned ENOMEM, since there are + // other reasons why NULL might be returned (see + // http://opensource.apple.com/source/Libc/Libc-583/gen/malloc.c ). + if (!result && size && alignment >= sizeof(void*) + && (alignment & (alignment - 1)) == 0) { + debug::BreakDebugger(); + } + return result; +} + +void* oom_killer_malloc_purgeable(struct _malloc_zone_t* zone, + size_t size) { +#if ARCH_CPU_32_BITS + ScopedClearErrno clear_errno; +#endif // ARCH_CPU_32_BITS + void* result = g_old_malloc_purgeable(zone, size); + if (!result && size) + debug::BreakDebugger(); + return result; +} + +void* oom_killer_calloc_purgeable(struct _malloc_zone_t* zone, + size_t num_items, + size_t size) { +#if ARCH_CPU_32_BITS + ScopedClearErrno clear_errno; +#endif // ARCH_CPU_32_BITS + void* result = g_old_calloc_purgeable(zone, num_items, size); + if (!result && num_items && size) + debug::BreakDebugger(); + return result; +} + +void* oom_killer_valloc_purgeable(struct _malloc_zone_t* zone, + size_t size) { +#if ARCH_CPU_32_BITS + ScopedClearErrno clear_errno; +#endif // ARCH_CPU_32_BITS + void* result = g_old_valloc_purgeable(zone, size); + if (!result && size) + debug::BreakDebugger(); + return result; +} + +void oom_killer_free_purgeable(struct _malloc_zone_t* zone, + void* ptr) { +#if ARCH_CPU_32_BITS + ScopedClearErrno clear_errno; +#endif // ARCH_CPU_32_BITS + g_old_free_purgeable(zone, ptr); +} + +void* oom_killer_realloc_purgeable(struct _malloc_zone_t* zone, + void* ptr, + size_t size) { +#if ARCH_CPU_32_BITS + ScopedClearErrno clear_errno; +#endif // ARCH_CPU_32_BITS + void* result = g_old_realloc_purgeable(zone, ptr, size); + if (!result && size) + debug::BreakDebugger(); + return result; +} + +void* oom_killer_memalign_purgeable(struct _malloc_zone_t* zone, + size_t alignment, + size_t size) { +#if ARCH_CPU_32_BITS + ScopedClearErrno clear_errno; +#endif // ARCH_CPU_32_BITS + void* result = g_old_memalign_purgeable(zone, alignment, size); + // Only die if posix_memalign would have returned ENOMEM, since there are + // other reasons why NULL might be returned (see + // http://opensource.apple.com/source/Libc/Libc-583/gen/malloc.c ). + if (!result && size && alignment >= sizeof(void*) + && (alignment & (alignment - 1)) == 0) { + debug::BreakDebugger(); + } + return result; +} + +// === C++ operator new === + +void oom_killer_new() { + debug::BreakDebugger(); +} + +// === Core Foundation CFAllocators === + +bool CanGetContextForCFAllocator() { + return !base::mac::IsOSLaterThanMountainLion_DontCallThis(); +} + +CFAllocatorContext* ContextForCFAllocator(CFAllocatorRef allocator) { + if (base::mac::IsOSSnowLeopard()) { + ChromeCFAllocatorLeopards* our_allocator = + const_cast( + reinterpret_cast(allocator)); + return &our_allocator->_context; + } else if (base::mac::IsOSLion() || base::mac::IsOSMountainLion()) { + ChromeCFAllocatorLions* our_allocator = + const_cast( + reinterpret_cast(allocator)); + return &our_allocator->_context; + } else { + return NULL; + } +} + +CFAllocatorAllocateCallBack g_old_cfallocator_system_default; +CFAllocatorAllocateCallBack g_old_cfallocator_malloc; +CFAllocatorAllocateCallBack g_old_cfallocator_malloc_zone; + +void* oom_killer_cfallocator_system_default(CFIndex alloc_size, + CFOptionFlags hint, + void* info) { + void* result = g_old_cfallocator_system_default(alloc_size, hint, info); + if (!result) + debug::BreakDebugger(); + return result; +} + +void* oom_killer_cfallocator_malloc(CFIndex alloc_size, + CFOptionFlags hint, + void* info) { + void* result = g_old_cfallocator_malloc(alloc_size, hint, info); + if (!result) + debug::BreakDebugger(); + return result; +} + +void* oom_killer_cfallocator_malloc_zone(CFIndex alloc_size, + CFOptionFlags hint, + void* info) { + void* result = g_old_cfallocator_malloc_zone(alloc_size, hint, info); + if (!result) + debug::BreakDebugger(); + return result; +} + +// === Cocoa NSObject allocation === + +typedef id (*allocWithZone_t)(id, SEL, NSZone*); +allocWithZone_t g_old_allocWithZone; + +id oom_killer_allocWithZone(id self, SEL _cmd, NSZone* zone) +{ + id result = g_old_allocWithZone(self, _cmd, zone); + if (!result) + debug::BreakDebugger(); + return result; +} + +} // namespace + +void* UncheckedMalloc(size_t size) { + if (g_old_malloc) { +#if ARCH_CPU_32_BITS + ScopedClearErrno clear_errno; + ThreadLocalBooleanAutoReset flag(g_unchecked_malloc.Pointer(), true); +#endif // ARCH_CPU_32_BITS + return g_old_malloc(malloc_default_zone(), size); + } + return malloc(size); +} + +void EnableTerminationOnOutOfMemory() { + if (g_oom_killer_enabled) + return; + + g_oom_killer_enabled = true; + + // === C malloc/calloc/valloc/realloc/posix_memalign === + + // This approach is not perfect, as requests for amounts of memory larger than + // MALLOC_ABSOLUTE_MAX_SIZE (currently SIZE_T_MAX - (2 * PAGE_SIZE)) will + // still fail with a NULL rather than dying (see + // http://opensource.apple.com/source/Libc/Libc-583/gen/malloc.c for details). + // Unfortunately, it's the best we can do. Also note that this does not affect + // allocations from non-default zones. + + CHECK(!g_old_malloc && !g_old_calloc && !g_old_valloc && !g_old_realloc && + !g_old_memalign) << "Old allocators unexpectedly non-null"; + + CHECK(!g_old_malloc_purgeable && !g_old_calloc_purgeable && + !g_old_valloc_purgeable && !g_old_realloc_purgeable && + !g_old_memalign_purgeable) << "Old allocators unexpectedly non-null"; + +#if !defined(ADDRESS_SANITIZER) + // Don't do anything special on OOM for the malloc zones replaced by + // AddressSanitizer, as modifying or protecting them may not work correctly. + + ChromeMallocZone* default_zone = + reinterpret_cast(malloc_default_zone()); + ChromeMallocZone* purgeable_zone = + reinterpret_cast(malloc_default_purgeable_zone()); + + mach_vm_address_t default_reprotection_start = 0; + mach_vm_size_t default_reprotection_length = 0; + vm_prot_t default_reprotection_value = VM_PROT_NONE; + DeprotectMallocZone(default_zone, + &default_reprotection_start, + &default_reprotection_length, + &default_reprotection_value); + + mach_vm_address_t purgeable_reprotection_start = 0; + mach_vm_size_t purgeable_reprotection_length = 0; + vm_prot_t purgeable_reprotection_value = VM_PROT_NONE; + if (purgeable_zone) { + DeprotectMallocZone(purgeable_zone, + &purgeable_reprotection_start, + &purgeable_reprotection_length, + &purgeable_reprotection_value); + } + + // Default zone + + g_old_malloc = default_zone->malloc; + g_old_calloc = default_zone->calloc; + g_old_valloc = default_zone->valloc; + g_old_free = default_zone->free; + g_old_realloc = default_zone->realloc; + CHECK(g_old_malloc && g_old_calloc && g_old_valloc && g_old_free && + g_old_realloc) + << "Failed to get system allocation functions."; + + default_zone->malloc = oom_killer_malloc; + default_zone->calloc = oom_killer_calloc; + default_zone->valloc = oom_killer_valloc; + default_zone->free = oom_killer_free; + default_zone->realloc = oom_killer_realloc; + + if (default_zone->version >= 5) { + g_old_memalign = default_zone->memalign; + if (g_old_memalign) + default_zone->memalign = oom_killer_memalign; + } + + // Purgeable zone (if it exists) + + if (purgeable_zone) { + g_old_malloc_purgeable = purgeable_zone->malloc; + g_old_calloc_purgeable = purgeable_zone->calloc; + g_old_valloc_purgeable = purgeable_zone->valloc; + g_old_free_purgeable = purgeable_zone->free; + g_old_realloc_purgeable = purgeable_zone->realloc; + CHECK(g_old_malloc_purgeable && g_old_calloc_purgeable && + g_old_valloc_purgeable && g_old_free_purgeable && + g_old_realloc_purgeable) + << "Failed to get system allocation functions."; + + purgeable_zone->malloc = oom_killer_malloc_purgeable; + purgeable_zone->calloc = oom_killer_calloc_purgeable; + purgeable_zone->valloc = oom_killer_valloc_purgeable; + purgeable_zone->free = oom_killer_free_purgeable; + purgeable_zone->realloc = oom_killer_realloc_purgeable; + + if (purgeable_zone->version >= 5) { + g_old_memalign_purgeable = purgeable_zone->memalign; + if (g_old_memalign_purgeable) + purgeable_zone->memalign = oom_killer_memalign_purgeable; + } + } + + // Restore protection if it was active. + + if (default_reprotection_start) { + kern_return_t result = mach_vm_protect(mach_task_self(), + default_reprotection_start, + default_reprotection_length, + false, + default_reprotection_value); + CHECK(result == KERN_SUCCESS); + } + + if (purgeable_reprotection_start) { + kern_return_t result = mach_vm_protect(mach_task_self(), + purgeable_reprotection_start, + purgeable_reprotection_length, + false, + purgeable_reprotection_value); + CHECK(result == KERN_SUCCESS); + } +#endif + + // === C malloc_zone_batch_malloc === + + // batch_malloc is omitted because the default malloc zone's implementation + // only supports batch_malloc for "tiny" allocations from the free list. It + // will fail for allocations larger than "tiny", and will only allocate as + // many blocks as it's able to from the free list. These factors mean that it + // can return less than the requested memory even in a non-out-of-memory + // situation. There's no good way to detect whether a batch_malloc failure is + // due to these other factors, or due to genuine memory or address space + // exhaustion. The fact that it only allocates space from the "tiny" free list + // means that it's likely that a failure will not be due to memory exhaustion. + // Similarly, these constraints on batch_malloc mean that callers must always + // be expecting to receive less memory than was requested, even in situations + // where memory pressure is not a concern. Finally, the only public interface + // to batch_malloc is malloc_zone_batch_malloc, which is specific to the + // system's malloc implementation. It's unlikely that anyone's even heard of + // it. + + // === C++ operator new === + + // Yes, operator new does call through to malloc, but this will catch failures + // that our imperfect handling of malloc cannot. + + std::set_new_handler(oom_killer_new); + +#ifndef ADDRESS_SANITIZER + // === Core Foundation CFAllocators === + + // This will not catch allocation done by custom allocators, but will catch + // all allocation done by system-provided ones. + + CHECK(!g_old_cfallocator_system_default && !g_old_cfallocator_malloc && + !g_old_cfallocator_malloc_zone) + << "Old allocators unexpectedly non-null"; + + bool cf_allocator_internals_known = CanGetContextForCFAllocator(); + + if (cf_allocator_internals_known) { + CFAllocatorContext* context = + ContextForCFAllocator(kCFAllocatorSystemDefault); + CHECK(context) << "Failed to get context for kCFAllocatorSystemDefault."; + g_old_cfallocator_system_default = context->allocate; + CHECK(g_old_cfallocator_system_default) + << "Failed to get kCFAllocatorSystemDefault allocation function."; + context->allocate = oom_killer_cfallocator_system_default; + + context = ContextForCFAllocator(kCFAllocatorMalloc); + CHECK(context) << "Failed to get context for kCFAllocatorMalloc."; + g_old_cfallocator_malloc = context->allocate; + CHECK(g_old_cfallocator_malloc) + << "Failed to get kCFAllocatorMalloc allocation function."; + context->allocate = oom_killer_cfallocator_malloc; + + context = ContextForCFAllocator(kCFAllocatorMallocZone); + CHECK(context) << "Failed to get context for kCFAllocatorMallocZone."; + g_old_cfallocator_malloc_zone = context->allocate; + CHECK(g_old_cfallocator_malloc_zone) + << "Failed to get kCFAllocatorMallocZone allocation function."; + context->allocate = oom_killer_cfallocator_malloc_zone; + } else { + NSLog(@"Internals of CFAllocator not known; out-of-memory failures via " + "CFAllocator will not result in termination. http://crbug.com/45650"); + } +#endif + + // === Cocoa NSObject allocation === + + // Note that both +[NSObject new] and +[NSObject alloc] call through to + // +[NSObject allocWithZone:]. + + CHECK(!g_old_allocWithZone) + << "Old allocator unexpectedly non-null"; + + Class nsobject_class = [NSObject class]; + Method orig_method = class_getClassMethod(nsobject_class, + @selector(allocWithZone:)); + g_old_allocWithZone = reinterpret_cast( + method_getImplementation(orig_method)); + CHECK(g_old_allocWithZone) + << "Failed to get allocWithZone allocation function."; + method_setImplementation(orig_method, + reinterpret_cast(oom_killer_allocWithZone)); +} + +} // namespace base diff --git a/base/process/memory_stubs.cc b/base/process/memory_stubs.cc new file mode 100644 index 0000000000..b06c7d5f2b --- /dev/null +++ b/base/process/memory_stubs.cc @@ -0,0 +1,19 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/memory.h" + +namespace base { + +void EnableTerminationOnOutOfMemory() { +} + +void EnableTerminationOnHeapCorruption() { +} + +bool AdjustOOMScore(ProcessId process, int score) { + return false; +} + +} // namespace base diff --git a/base/process/memory_unittest.cc b/base/process/memory_unittest.cc new file mode 100644 index 0000000000..a1f30526aa --- /dev/null +++ b/base/process/memory_unittest.cc @@ -0,0 +1,379 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#define _CRT_SECURE_NO_WARNINGS + +#include "base/process/memory.h" + +#include + +#include "base/compiler_specific.h" +#include "base/debug/alias.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_WIN) +#include +#endif +#if defined(OS_POSIX) +#include +#endif +#if defined(OS_MACOSX) +#include +#include "base/process/memory_unittest_mac.h" +#endif +#if defined(OS_LINUX) +#include +#include +#endif + +#if defined(OS_WIN) +// HeapQueryInformation function pointer. +typedef BOOL (WINAPI* HeapQueryFn) \ + (HANDLE, HEAP_INFORMATION_CLASS, PVOID, SIZE_T, PSIZE_T); + +const int kConstantInModule = 42; + +TEST(ProcessMemoryTest, GetModuleFromAddress) { + // Since the unit tests are their own EXE, this should be + // equivalent to the EXE's HINSTANCE. + // + // kConstantInModule is a constant in this file and + // therefore within the unit test EXE. + EXPECT_EQ(::GetModuleHandle(NULL), + base::GetModuleFromAddress( + const_cast(&kConstantInModule))); + + // Any address within the kernel32 module should return + // kernel32's HMODULE. Our only assumption here is that + // kernel32 is larger than 4 bytes. + HMODULE kernel32 = ::GetModuleHandle(L"kernel32.dll"); + HMODULE kernel32_from_address = + base::GetModuleFromAddress(reinterpret_cast(kernel32) + 1); + EXPECT_EQ(kernel32, kernel32_from_address); +} + +TEST(ProcessMemoryTest, EnableLFH) { + ASSERT_TRUE(base::EnableLowFragmentationHeap()); + if (IsDebuggerPresent()) { + // Under these conditions, LFH can't be enabled. There's no point to test + // anything. + const char* no_debug_env = getenv("_NO_DEBUG_HEAP"); + if (!no_debug_env || strcmp(no_debug_env, "1")) + return; + } + HMODULE kernel32 = GetModuleHandle(L"kernel32.dll"); + ASSERT_TRUE(kernel32 != NULL); + HeapQueryFn heap_query = reinterpret_cast(GetProcAddress( + kernel32, + "HeapQueryInformation")); + + // On Windows 2000, the function is not exported. This is not a reason to + // fail but we won't be able to retrieves information about the heap, so we + // should stop here. + if (heap_query == NULL) + return; + + HANDLE heaps[1024] = { 0 }; + unsigned number_heaps = GetProcessHeaps(1024, heaps); + EXPECT_GT(number_heaps, 0u); + for (unsigned i = 0; i < number_heaps; ++i) { + ULONG flag = 0; + SIZE_T length; + ASSERT_NE(0, heap_query(heaps[i], + HeapCompatibilityInformation, + &flag, + sizeof(flag), + &length)); + // If flag is 0, the heap is a standard heap that does not support + // look-asides. If flag is 1, the heap supports look-asides. If flag is 2, + // the heap is a low-fragmentation heap (LFH). Note that look-asides are not + // supported on the LFH. + + // We don't have any documented way of querying the HEAP_NO_SERIALIZE flag. + EXPECT_LE(flag, 2u); + EXPECT_NE(flag, 1u); + } +} +#endif // defined(OS_WIN) + +#if defined(OS_MACOSX) + +// For the following Mac tests: +// Note that base::EnableTerminationOnHeapCorruption() is called as part of +// test suite setup and does not need to be done again, else mach_override +// will fail. + +#if !defined(ADDRESS_SANITIZER) +// The following code tests the system implementation of malloc() thus no need +// to test it under AddressSanitizer. +TEST(ProcessMemoryTest, MacMallocFailureDoesNotTerminate) { + // Test that ENOMEM doesn't crash via CrMallocErrorBreak two ways: the exit + // code and lack of the error string. The number of bytes is one less than + // MALLOC_ABSOLUTE_MAX_SIZE, more than which the system early-returns NULL and + // does not call through malloc_error_break(). See the comment at + // EnableTerminationOnOutOfMemory() for more information. + void* buf = NULL; + ASSERT_EXIT( + { + base::EnableTerminationOnOutOfMemory(); + + buf = malloc(std::numeric_limits::max() - (2 * PAGE_SIZE) - 1); + }, + testing::KilledBySignal(SIGTRAP), + "\\*\\*\\* error: can't allocate region.*" + "(Terminating process due to a potential for future heap " + "corruption){0}"); + + base::debug::Alias(buf); +} +#endif // !defined(ADDRESS_SANITIZER) + +TEST(ProcessMemoryTest, MacTerminateOnHeapCorruption) { + // Assert that freeing an unallocated pointer will crash the process. + char buf[9]; + asm("" : "=r" (buf)); // Prevent clang from being too smart. +#if ARCH_CPU_64_BITS + // On 64 bit Macs, the malloc system automatically abort()s on heap corruption + // but does not output anything. + ASSERT_DEATH(free(buf), ""); +#elif defined(ADDRESS_SANITIZER) + // AddressSanitizer replaces malloc() and prints a different error message on + // heap corruption. + ASSERT_DEATH(free(buf), "attempting free on address which " + "was not malloc\\(\\)-ed"); +#else + ASSERT_DEATH(free(buf), "being freed.*" + "\\*\\*\\* set a breakpoint in malloc_error_break to debug.*" + "Terminating process due to a potential for future heap corruption"); +#endif // ARCH_CPU_64_BITS || defined(ADDRESS_SANITIZER) +} + +#endif // defined(OS_MACOSX) + +// Android doesn't implement set_new_handler, so we can't use the +// OutOfMemoryTest cases. +// OpenBSD does not support these tests either. +// AddressSanitizer and ThreadSanitizer define the malloc()/free()/etc. +// functions so that they don't crash if the program is out of memory, so the +// OOM tests aren't supposed to work. +// TODO(vandebo) make this work on Windows too. +#if !defined(OS_ANDROID) && !defined(OS_OPENBSD) && \ + !defined(OS_WIN) && \ + !defined(ADDRESS_SANITIZER) && !defined(THREAD_SANITIZER) + +#if defined(USE_TCMALLOC) +extern "C" { +int tc_set_new_mode(int mode); +} +#endif // defined(USE_TCMALLOC) + +class OutOfMemoryDeathTest : public testing::Test { + public: + OutOfMemoryDeathTest() + : value_(NULL), + // Make test size as large as possible minus a few pages so + // that alignment or other rounding doesn't make it wrap. + test_size_(std::numeric_limits::max() - 12 * 1024), + signed_test_size_(std::numeric_limits::max()) { + } + +#if defined(USE_TCMALLOC) + virtual void SetUp() OVERRIDE { + tc_set_new_mode(1); + } + + virtual void TearDown() OVERRIDE { + tc_set_new_mode(0); + } +#endif // defined(USE_TCMALLOC) + + void SetUpInDeathAssert() { + // Must call EnableTerminationOnOutOfMemory() because that is called from + // chrome's main function and therefore hasn't been called yet. + // Since this call may result in another thread being created and death + // tests shouldn't be started in a multithread environment, this call + // should be done inside of the ASSERT_DEATH. + base::EnableTerminationOnOutOfMemory(); + } + + void* value_; + size_t test_size_; + ssize_t signed_test_size_; +}; + +TEST_F(OutOfMemoryDeathTest, New) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = operator new(test_size_); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, NewArray) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = new char[test_size_]; + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, Malloc) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = malloc(test_size_); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, Realloc) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = realloc(NULL, test_size_); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, Calloc) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = calloc(1024, test_size_ / 1024L); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, Valloc) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = valloc(test_size_); + }, ""); +} + +#if defined(OS_LINUX) +TEST_F(OutOfMemoryDeathTest, Pvalloc) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = pvalloc(test_size_); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, Memalign) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = memalign(4, test_size_); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, ViaSharedLibraries) { + // g_try_malloc is documented to return NULL on failure. (g_malloc is the + // 'safe' default that crashes if allocation fails). However, since we have + // hopefully overridden malloc, even g_try_malloc should fail. This tests + // that the run-time symbol resolution is overriding malloc for shared + // libraries as well as for our code. + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = g_try_malloc(test_size_); + }, ""); +} +#endif // OS_LINUX + +// Android doesn't implement posix_memalign(). +#if defined(OS_POSIX) && !defined(OS_ANDROID) +TEST_F(OutOfMemoryDeathTest, Posix_memalign) { + // Grab the return value of posix_memalign to silence a compiler warning + // about unused return values. We don't actually care about the return + // value, since we're asserting death. + ASSERT_DEATH({ + SetUpInDeathAssert(); + EXPECT_EQ(ENOMEM, posix_memalign(&value_, 8, test_size_)); + }, ""); +} +#endif // defined(OS_POSIX) && !defined(OS_ANDROID) + +#if defined(OS_MACOSX) + +// Purgeable zone tests + +TEST_F(OutOfMemoryDeathTest, MallocPurgeable) { + malloc_zone_t* zone = malloc_default_purgeable_zone(); + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = malloc_zone_malloc(zone, test_size_); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, ReallocPurgeable) { + malloc_zone_t* zone = malloc_default_purgeable_zone(); + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = malloc_zone_realloc(zone, NULL, test_size_); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, CallocPurgeable) { + malloc_zone_t* zone = malloc_default_purgeable_zone(); + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = malloc_zone_calloc(zone, 1024, test_size_ / 1024L); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, VallocPurgeable) { + malloc_zone_t* zone = malloc_default_purgeable_zone(); + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = malloc_zone_valloc(zone, test_size_); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, PosixMemalignPurgeable) { + malloc_zone_t* zone = malloc_default_purgeable_zone(); + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = malloc_zone_memalign(zone, 8, test_size_); + }, ""); +} + +// Since these allocation functions take a signed size, it's possible that +// calling them just once won't be enough to exhaust memory. In the 32-bit +// environment, it's likely that these allocation attempts will fail because +// not enough contiguous address space is available. In the 64-bit environment, +// it's likely that they'll fail because they would require a preposterous +// amount of (virtual) memory. + +TEST_F(OutOfMemoryDeathTest, CFAllocatorSystemDefault) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + while ((value_ = + base::AllocateViaCFAllocatorSystemDefault(signed_test_size_))) {} + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, CFAllocatorMalloc) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + while ((value_ = + base::AllocateViaCFAllocatorMalloc(signed_test_size_))) {} + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, CFAllocatorMallocZone) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + while ((value_ = + base::AllocateViaCFAllocatorMallocZone(signed_test_size_))) {} + }, ""); +} + +#if !defined(ARCH_CPU_64_BITS) + +// See process_util_unittest_mac.mm for an explanation of why this test isn't +// run in the 64-bit environment. + +TEST_F(OutOfMemoryDeathTest, PsychoticallyBigObjCObject) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + while ((value_ = base::AllocatePsychoticallyBigObjCObject())) {} + }, ""); +} + +#endif // !ARCH_CPU_64_BITS +#endif // OS_MACOSX + +#endif // !defined(OS_ANDROID) && !defined(OS_OPENBSD) && + // !defined(OS_WIN) && !defined(ADDRESS_SANITIZER) diff --git a/base/process/memory_unittest_mac.h b/base/process/memory_unittest_mac.h new file mode 100644 index 0000000000..472d2c5962 --- /dev/null +++ b/base/process/memory_unittest_mac.h @@ -0,0 +1,32 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains helpers for the process_util_unittest to allow it to fully +// test the Mac code. + +#ifndef BASE_PROCESS_MEMORY_UNITTEST_MAC_H_ +#define BASE_PROCESS_MEMORY_UNITTEST_MAC_H_ + +#include "base/basictypes.h" + +namespace base { + +// Allocates memory via system allocators. Alas, they take a _signed_ size for +// allocation. +void* AllocateViaCFAllocatorSystemDefault(ssize_t size); +void* AllocateViaCFAllocatorMalloc(ssize_t size); +void* AllocateViaCFAllocatorMallocZone(ssize_t size); + +#if !defined(ARCH_CPU_64_BITS) +// See process_util_unittest_mac.mm for an explanation of why this function +// isn't implemented for the 64-bit environment. + +// Allocates a huge Objective C object. +void* AllocatePsychoticallyBigObjCObject(); + +#endif // !ARCH_CPU_64_BITS + +} // namespace base + +#endif // BASE_PROCESS_MEMORY_UNITTEST_MAC_H_ diff --git a/base/process/memory_unittest_mac.mm b/base/process/memory_unittest_mac.mm new file mode 100644 index 0000000000..bc4bf65347 --- /dev/null +++ b/base/process/memory_unittest_mac.mm @@ -0,0 +1,59 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/memory_unittest_mac.h" + +#import +#include + +#if !defined(ARCH_CPU_64_BITS) + +// In the 64-bit environment, the Objective-C 2.0 Runtime Reference states +// that sizeof(anInstance) is constrained to 32 bits. That's not necessarily +// "psychotically big" and in fact a 64-bit program is expected to be able to +// successfully allocate an object that large, likely reserving a good deal of +// swap space. The only way to test the behavior of memory exhaustion for +// Objective-C allocation in this environment would be to loop over allocation +// of these large objects, but that would slowly consume all available memory +// and cause swap file proliferation. That's bad, so this behavior isn't +// tested in the 64-bit environment. + +@interface PsychoticallyBigObjCObject : NSObject +{ + // In the 32-bit environment, the compiler limits Objective-C objects to + // < 2GB in size. + int justUnder2Gigs_[(2U * 1024 * 1024 * 1024 - 1) / sizeof(int)]; +} + +@end + +@implementation PsychoticallyBigObjCObject + +@end + +namespace base { + +void* AllocatePsychoticallyBigObjCObject() { + return [[PsychoticallyBigObjCObject alloc] init]; +} + +} // namespace base + +#endif // ARCH_CPU_64_BITS + +namespace base { + +void* AllocateViaCFAllocatorSystemDefault(ssize_t size) { + return CFAllocatorAllocate(kCFAllocatorSystemDefault, size, 0); +} + +void* AllocateViaCFAllocatorMalloc(ssize_t size) { + return CFAllocatorAllocate(kCFAllocatorMalloc, size, 0); +} + +void* AllocateViaCFAllocatorMallocZone(ssize_t size) { + return CFAllocatorAllocate(kCFAllocatorMallocZone, size, 0); +} + +} // namespace base diff --git a/base/process/memory_win.cc b/base/process/memory_win.cc new file mode 100644 index 0000000000..c53a1be539 --- /dev/null +++ b/base/process/memory_win.cc @@ -0,0 +1,85 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/memory.h" + +#include + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" + +namespace base { + +namespace { + +void OnNoMemory() { + // Kill the process. This is important for security, since WebKit doesn't + // NULL-check many memory allocations. If a malloc fails, returns NULL, and + // the buffer is then used, it provides a handy mapping of memory starting at + // address 0 for an attacker to utilize. + __debugbreak(); + _exit(1); +} + +// HeapSetInformation function pointer. +typedef BOOL (WINAPI* HeapSetFn)(HANDLE, HEAP_INFORMATION_CLASS, PVOID, SIZE_T); + +} // namespace + +bool EnableLowFragmentationHeap() { + HMODULE kernel32 = GetModuleHandle(L"kernel32.dll"); + HeapSetFn heap_set = reinterpret_cast(GetProcAddress( + kernel32, + "HeapSetInformation")); + + // On Windows 2000, the function is not exported. This is not a reason to + // fail. + if (!heap_set) + return true; + + unsigned number_heaps = GetProcessHeaps(0, NULL); + if (!number_heaps) + return false; + + // Gives us some extra space in the array in case a thread is creating heaps + // at the same time we're querying them. + static const int MARGIN = 8; + scoped_ptr heaps(new HANDLE[number_heaps + MARGIN]); + number_heaps = GetProcessHeaps(number_heaps + MARGIN, heaps.get()); + if (!number_heaps) + return false; + + for (unsigned i = 0; i < number_heaps; ++i) { + ULONG lfh_flag = 2; + // Don't bother with the result code. It may fails on heaps that have the + // HEAP_NO_SERIALIZE flag. This is expected and not a problem at all. + heap_set(heaps[i], + HeapCompatibilityInformation, + &lfh_flag, + sizeof(lfh_flag)); + } + return true; +} + +void EnableTerminationOnHeapCorruption() { + // Ignore the result code. Supported on XP SP3 and Vista. + HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0); +} + +void EnableTerminationOnOutOfMemory() { + std::set_new_handler(&OnNoMemory); +} + +HMODULE GetModuleFromAddress(void* address) { + HMODULE instance = NULL; + if (!::GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + static_cast(address), + &instance)) { + NOTREACHED(); + } + return instance; +} + +} // namespace base diff --git a/base/process/process.h b/base/process/process.h new file mode 100644 index 0000000000..20e8884b97 --- /dev/null +++ b/base/process/process.h @@ -0,0 +1,70 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_PROCESS_PROCESS_PROCESS_H_ +#define BASE_PROCESS_PROCESS_PROCESS_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/process/process_handle.h" +#include "build/build_config.h" + +namespace base { + +class BASE_EXPORT Process { + public: + Process() : process_(kNullProcessHandle) { + } + + explicit Process(ProcessHandle handle) : process_(handle) { + } + + // A handle to the current process. + static Process Current(); + + static bool CanBackgroundProcesses(); + + // Get/Set the handle for this process. The handle will be 0 if the process + // is no longer running. + ProcessHandle handle() const { return process_; } + void set_handle(ProcessHandle handle) { + process_ = handle; + } + + // Get the PID for this process. + ProcessId pid() const; + + // Is the this process the current process. + bool is_current() const; + + // Close the process handle. This will not terminate the process. + void Close(); + + // Terminates the process with extreme prejudice. The given result code will + // be the exit code of the process. If the process has already exited, this + // will do nothing. + void Terminate(int result_code); + + // A process is backgrounded when it's priority is lower than normal. + // Return true if this process is backgrounded, false otherwise. + bool IsProcessBackgrounded() const; + + // Set a process as backgrounded. If value is true, the priority + // of the process will be lowered. If value is false, the priority + // of the process will be made "normal" - equivalent to default + // process priority. + // Returns true if the priority was changed, false otherwise. + bool SetProcessBackgrounded(bool value); + + // Returns an integer representing the priority of a process. The meaning + // of this value is OS dependent. + int GetPriority() const; + + private: + ProcessHandle process_; +}; + +} // namespace base + +#endif // BASE_PROCESS_PROCESS_PROCESS_H_ diff --git a/base/process/process_handle.h b/base/process/process_handle.h new file mode 100644 index 0000000000..a37784275e --- /dev/null +++ b/base/process/process_handle.h @@ -0,0 +1,96 @@ +// 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_PROCESS_PROCESS_HANDLE_H_ +#define BASE_PROCESS_PROCESS_HANDLE_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "build/build_config.h" + +#include +#if defined(OS_WIN) +#include +#endif + +namespace base { + +// ProcessHandle is a platform specific type which represents the underlying OS +// handle to a process. +// ProcessId is a number which identifies the process in the OS. +#if defined(OS_WIN) +typedef HANDLE ProcessHandle; +typedef DWORD ProcessId; +typedef HANDLE UserTokenHandle; +const ProcessHandle kNullProcessHandle = NULL; +const ProcessId kNullProcessId = 0; +#elif defined(OS_POSIX) +// On POSIX, our ProcessHandle will just be the PID. +typedef pid_t ProcessHandle; +typedef pid_t ProcessId; +const ProcessHandle kNullProcessHandle = 0; +const ProcessId kNullProcessId = 0; +#endif // defined(OS_WIN) + +// Returns the id of the current process. +BASE_EXPORT ProcessId GetCurrentProcId(); + +// Returns the ProcessHandle of the current process. +BASE_EXPORT ProcessHandle GetCurrentProcessHandle(); + +// Converts a PID to a process handle. This handle must be closed by +// CloseProcessHandle when you are done with it. Returns true on success. +BASE_EXPORT bool OpenProcessHandle(ProcessId pid, ProcessHandle* handle); + +// Converts a PID to a process handle. On Windows the handle is opened +// with more access rights and must only be used by trusted code. +// You have to close returned handle using CloseProcessHandle. Returns true +// on success. +// TODO(sanjeevr): Replace all calls to OpenPrivilegedProcessHandle with the +// more specific OpenProcessHandleWithAccess method and delete this. +BASE_EXPORT bool OpenPrivilegedProcessHandle(ProcessId pid, + ProcessHandle* handle); + +// Converts a PID to a process handle using the desired access flags. Use a +// combination of the kProcessAccess* flags defined above for |access_flags|. +BASE_EXPORT bool OpenProcessHandleWithAccess(ProcessId pid, + uint32 access_flags, + ProcessHandle* handle); + +// Closes the process handle opened by OpenProcessHandle. +BASE_EXPORT void CloseProcessHandle(ProcessHandle process); + +// Returns the unique ID for the specified process. This is functionally the +// same as Windows' GetProcessId(), but works on versions of Windows before +// Win XP SP1 as well. +BASE_EXPORT ProcessId GetProcId(ProcessHandle process); + +#if defined(OS_WIN) +enum IntegrityLevel { + INTEGRITY_UNKNOWN, + LOW_INTEGRITY, + MEDIUM_INTEGRITY, + HIGH_INTEGRITY, +}; +// Determine the integrity level of the specified process. Returns false +// if the system does not support integrity levels (pre-Vista) or in the case +// of an underlying system failure. +BASE_EXPORT bool GetProcessIntegrityLevel(ProcessHandle process, + IntegrityLevel* level); +#endif + +#if defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_BSD) +// Returns the path to the executable of the given process. +BASE_EXPORT FilePath GetProcessExecutablePath(ProcessHandle process); +#endif + +#if defined(OS_POSIX) +// Returns the ID for the parent of the given process. +BASE_EXPORT ProcessId GetParentProcessId(ProcessHandle process); +#endif + +} // namespace base + +#endif // BASE_PROCESS_PROCESS_HANDLE_H_ diff --git a/base/process/process_handle_freebsd.cc b/base/process/process_handle_freebsd.cc new file mode 100644 index 0000000000..8a0e9de8f2 --- /dev/null +++ b/base/process/process_handle_freebsd.cc @@ -0,0 +1,39 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/process_handle.h" + +#include +#include +#include + +namespace base { + +ProcessId GetParentProcessId(ProcessHandle process) { + struct kinfo_proc info; + size_t length; + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, process }; + + if (sysctl(mib, arraysize(mib), &info, &length, NULL, 0) < 0) + return -1; + + return info.ki_ppid; +} + +FilePath GetProcessExecutablePath(ProcessHandle process) { + char pathname[PATH_MAX]; + size_t length; + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, process }; + + length = sizeof(pathname); + + if (sysctl(mib, arraysize(mib), pathname, &length, NULL, 0) < 0 || + length == 0) { + return FilePath(); + } + + return FilePath(std::string(pathname)); +} + +} // namespace base diff --git a/base/process/process_handle_linux.cc b/base/process/process_handle_linux.cc new file mode 100644 index 0000000000..91441f7b38 --- /dev/null +++ b/base/process/process_handle_linux.cc @@ -0,0 +1,30 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/process_handle.h" + +#include "base/file_util.h" +#include "base/process/internal_linux.h" + +namespace base { + +ProcessId GetParentProcessId(ProcessHandle process) { + ProcessId pid = + internal::ReadProcStatsAndGetFieldAsInt(process, internal::VM_PPID); + if (pid) + return pid; + return -1; +} + +FilePath GetProcessExecutablePath(ProcessHandle process) { + FilePath stat_file = internal::GetProcPidDir(process).Append("exe"); + FilePath exe_name; + if (!file_util::ReadSymbolicLink(stat_file, &exe_name)) { + // No such process. Happens frequently in e.g. TerminateAllChromeProcesses + return FilePath(); + } + return exe_name; +} + +} // namespace base diff --git a/base/process/process_handle_mac.cc b/base/process/process_handle_mac.cc new file mode 100644 index 0000000000..6cb8d686e4 --- /dev/null +++ b/base/process/process_handle_mac.cc @@ -0,0 +1,27 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/process_handle.h" + +#include +#include + +#include "base/logging.h" + +namespace base { + +ProcessId GetParentProcessId(ProcessHandle process) { + struct kinfo_proc info; + size_t length = sizeof(struct kinfo_proc); + int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, process }; + if (sysctl(mib, 4, &info, &length, NULL, 0) < 0) { + DPLOG(ERROR) << "sysctl"; + return -1; + } + if (length == 0) + return -1; + return info.kp_eproc.e_ppid; +} + +} // namespace base diff --git a/base/process/process_handle_openbsd.cc b/base/process/process_handle_openbsd.cc new file mode 100644 index 0000000000..3508ccb04c --- /dev/null +++ b/base/process/process_handle_openbsd.cc @@ -0,0 +1,49 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/process_handle.h" + +#include +#include +#include + +namespace base { + +ProcessId GetParentProcessId(ProcessHandle process) { + struct kinfo_proc info; + size_t length; + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, process, + sizeof(struct kinfo_proc), 0 }; + + if (sysctl(mib, arraysize(mib), NULL, &length, NULL, 0) < 0) + return -1; + + mib[5] = (length / sizeof(struct kinfo_proc)); + + if (sysctl(mib, arraysize(mib), &info, &length, NULL, 0) < 0) + return -1; + + return info.p_ppid; +} + +FilePath GetProcessExecutablePath(ProcessHandle process) { + struct kinfo_proc kp; + size_t len; + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, process, + sizeof(struct kinfo_proc), 0 }; + + if (sysctl(mib, arraysize(mib), NULL, &len, NULL, 0) == -1) + return FilePath(); + mib[5] = (len / sizeof(struct kinfo_proc)); + if (sysctl(mib, arraysize(mib), &kp, &len, NULL, 0) < 0) + return FilePath(); + if ((kp.p_flag & P_SYSTEM) != 0) + return FilePath(); + if (strcmp(kp.p_comm, "chrome") == 0) + return FilePath(kp.p_comm); + + return FilePath(); +} + +} // namespace base diff --git a/base/process/process_handle_posix.cc b/base/process/process_handle_posix.cc new file mode 100644 index 0000000000..4013254a5c --- /dev/null +++ b/base/process/process_handle_posix.cc @@ -0,0 +1,49 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/process_handle.h" + +#include + +namespace base { + +ProcessId GetCurrentProcId() { + return getpid(); +} + +ProcessHandle GetCurrentProcessHandle() { + return GetCurrentProcId(); +} + +bool OpenProcessHandle(ProcessId pid, ProcessHandle* handle) { + // On Posix platforms, process handles are the same as PIDs, so we + // don't need to do anything. + *handle = pid; + return true; +} + +bool OpenPrivilegedProcessHandle(ProcessId pid, ProcessHandle* handle) { + // On POSIX permissions are checked for each operation on process, + // not when opening a "handle". + return OpenProcessHandle(pid, handle); +} + +bool OpenProcessHandleWithAccess(ProcessId pid, + uint32 access_flags, + ProcessHandle* handle) { + // On POSIX permissions are checked for each operation on process, + // not when opening a "handle". + return OpenProcessHandle(pid, handle); +} + +void CloseProcessHandle(ProcessHandle process) { + // See OpenProcessHandle, nothing to do. + return; +} + +ProcessId GetProcId(ProcessHandle process) { + return process; +} + +} // namespace base diff --git a/base/process/process_handle_win.cc b/base/process/process_handle_win.cc new file mode 100644 index 0000000000..3bc3a125e0 --- /dev/null +++ b/base/process/process_handle_win.cc @@ -0,0 +1,126 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/process_handle.h" + +#include + +#include "base/memory/scoped_ptr.h" +#include "base/win/scoped_handle.h" +#include "base/win/windows_version.h" + +namespace base { + +ProcessId GetCurrentProcId() { + return ::GetCurrentProcessId(); +} + +ProcessHandle GetCurrentProcessHandle() { + return ::GetCurrentProcess(); +} + +bool OpenProcessHandle(ProcessId pid, ProcessHandle* handle) { + // We try to limit privileges granted to the handle. If you need this + // for test code, consider using OpenPrivilegedProcessHandle instead of + // adding more privileges here. + ProcessHandle result = OpenProcess(PROCESS_TERMINATE | + PROCESS_QUERY_INFORMATION | + SYNCHRONIZE, + FALSE, pid); + + if (result == NULL) + return false; + + *handle = result; + return true; +} + +bool OpenPrivilegedProcessHandle(ProcessId pid, ProcessHandle* handle) { + ProcessHandle result = OpenProcess(PROCESS_DUP_HANDLE | + PROCESS_TERMINATE | + PROCESS_QUERY_INFORMATION | + PROCESS_VM_READ | + SYNCHRONIZE, + FALSE, pid); + + if (result == NULL) + return false; + + *handle = result; + return true; +} + +bool OpenProcessHandleWithAccess(ProcessId pid, + uint32 access_flags, + ProcessHandle* handle) { + ProcessHandle result = OpenProcess(access_flags, FALSE, pid); + + if (result == NULL) + return false; + + *handle = result; + return true; +} + +void CloseProcessHandle(ProcessHandle process) { + CloseHandle(process); +} + +ProcessId GetProcId(ProcessHandle process) { + // This returns 0 if we have insufficient rights to query the process handle. + return GetProcessId(process); +} + +bool GetProcessIntegrityLevel(ProcessHandle process, IntegrityLevel *level) { + if (!level) + return false; + + if (win::GetVersion() < base::win::VERSION_VISTA) + return false; + + HANDLE process_token; + if (!OpenProcessToken(process, TOKEN_QUERY | TOKEN_QUERY_SOURCE, + &process_token)) + return false; + + win::ScopedHandle scoped_process_token(process_token); + + DWORD token_info_length = 0; + if (GetTokenInformation(process_token, TokenIntegrityLevel, NULL, 0, + &token_info_length) || + GetLastError() != ERROR_INSUFFICIENT_BUFFER) + return false; + + scoped_ptr token_label_bytes(new char[token_info_length]); + if (!token_label_bytes.get()) + return false; + + TOKEN_MANDATORY_LABEL* token_label = + reinterpret_cast(token_label_bytes.get()); + if (!token_label) + return false; + + if (!GetTokenInformation(process_token, TokenIntegrityLevel, token_label, + token_info_length, &token_info_length)) + return false; + + DWORD integrity_level = *GetSidSubAuthority(token_label->Label.Sid, + (DWORD)(UCHAR)(*GetSidSubAuthorityCount(token_label->Label.Sid)-1)); + + if (integrity_level < SECURITY_MANDATORY_MEDIUM_RID) { + *level = LOW_INTEGRITY; + } else if (integrity_level >= SECURITY_MANDATORY_MEDIUM_RID && + integrity_level < SECURITY_MANDATORY_HIGH_RID) { + *level = MEDIUM_INTEGRITY; + } else if (integrity_level >= SECURITY_MANDATORY_HIGH_RID) { + *level = HIGH_INTEGRITY; + } else { + NOTREACHED(); + return false; + } + + return true; +} + +} // namespace base diff --git a/base/process/process_info.h b/base/process/process_info.h new file mode 100644 index 0000000000..e9e7b4e815 --- /dev/null +++ b/base/process/process_info.h @@ -0,0 +1,25 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_PROCESS_PROCESS_PROCESS_INFO_H_ +#define BASE_PROCESS_PROCESS_PROCESS_INFO_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace base { + +class Time; + +// Vends information about the current process. +class BASE_EXPORT CurrentProcessInfo { + public: + // Returns the time at which the process was launched. May be empty if an + // error occurred retrieving the information. + static const Time CreationTime(); +}; + +} // namespace base + +#endif // BASE_PROCESS_PROCESS_PROCESS_INFO_H_ diff --git a/base/process/process_info_linux.cc b/base/process/process_info_linux.cc new file mode 100644 index 0000000000..da34c180e8 --- /dev/null +++ b/base/process/process_info_linux.cc @@ -0,0 +1,27 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/process_info.h" + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/process/internal_linux.h" +#include "base/process/process_handle.h" +#include "base/time/time.h" + +namespace base { + +//static +const Time CurrentProcessInfo::CreationTime() { + ProcessHandle pid = GetCurrentProcessHandle(); + int start_ticks = internal::ReadProcStatsAndGetFieldAsInt( + pid, internal::VM_STARTTIME); + DCHECK(start_ticks); + TimeDelta start_offset = internal::ClockTicksToTimeDelta(start_ticks); + Time boot_time = internal::GetBootTime(); + DCHECK(!boot_time.is_null()); + return Time(boot_time + start_offset); +} + +} // namespace base diff --git a/base/process/process_info_mac.cc b/base/process/process_info_mac.cc new file mode 100644 index 0000000000..ab8394b891 --- /dev/null +++ b/base/process/process_info_mac.cc @@ -0,0 +1,31 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/process_info.h" + +#include +#include +#include + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/time/time.h" + +namespace base { + +//static +const Time CurrentProcessInfo::CreationTime() { + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid() }; + size_t len = 0; + if (sysctl(mib, arraysize(mib), NULL, &len, NULL, 0) < 0) + return Time(); + + scoped_ptr_malloc + proc(static_cast(malloc(len))); + if (sysctl(mib, arraysize(mib), proc.get(), &len, NULL, 0) < 0) + return Time(); + return Time::FromTimeVal(proc->kp_proc.p_un.__p_starttime); +} + +} // namespace base diff --git a/base/process/process_info_win.cc b/base/process/process_info_win.cc new file mode 100644 index 0000000000..b930ae6dd8 --- /dev/null +++ b/base/process/process_info_win.cc @@ -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. + +#include "base/process/process_info.h" + +#include + +#include "base/basictypes.h" +#include "base/time/time.h" + +namespace base { + +//static +const Time CurrentProcessInfo::CreationTime() { + FILETIME creation_time = {}; + FILETIME ignore = {}; + if (::GetProcessTimes(::GetCurrentProcess(), &creation_time, &ignore, + &ignore, &ignore) == false) + return Time(); + + return Time::FromFileTime(creation_time); +} + +} // namespace base diff --git a/base/process/process_iterator.cc b/base/process/process_iterator.cc new file mode 100644 index 0000000000..b9ef047732 --- /dev/null +++ b/base/process/process_iterator.cc @@ -0,0 +1,65 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/process_iterator.h" + +namespace base { + +#if defined(OS_POSIX) +ProcessEntry::ProcessEntry() : pid_(0), ppid_(0), gid_(0) {} +ProcessEntry::~ProcessEntry() {} +#endif + +const ProcessEntry* ProcessIterator::NextProcessEntry() { + bool result = false; + do { + result = CheckForNextProcess(); + } while (result && !IncludeEntry()); + if (result) + return &entry_; + return NULL; +} + +ProcessIterator::ProcessEntries ProcessIterator::Snapshot() { + ProcessEntries found; + while (const ProcessEntry* process_entry = NextProcessEntry()) { + found.push_back(*process_entry); + } + return found; +} + +bool ProcessIterator::IncludeEntry() { + return !filter_ || filter_->Includes(entry_); +} + +NamedProcessIterator::NamedProcessIterator( + const FilePath::StringType& executable_name, + const ProcessFilter* filter) : ProcessIterator(filter), + executable_name_(executable_name) { +#if defined(OS_ANDROID) + // On Android, the process name contains only the last 15 characters, which + // is in file /proc//stat, the string between open parenthesis and close + // parenthesis. Please See ProcessIterator::CheckForNextProcess for details. + // Now if the length of input process name is greater than 15, only save the + // last 15 characters. + if (executable_name_.size() > 15) { + executable_name_ = FilePath::StringType(executable_name_, + executable_name_.size() - 15, 15); + } +#endif +} + +NamedProcessIterator::~NamedProcessIterator() { +} + +int GetProcessCount(const FilePath::StringType& executable_name, + const ProcessFilter* filter) { + int count = 0; + NamedProcessIterator iter(executable_name, filter); + while (iter.NextProcessEntry()) + ++count; + return count; +} + +} // namespace base diff --git a/base/process/process_iterator.h b/base/process/process_iterator.h new file mode 100644 index 0000000000..aa8ba74aba --- /dev/null +++ b/base/process/process_iterator.h @@ -0,0 +1,183 @@ +// 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 file contains methods to iterate over processes on the system. + +#ifndef BASE_PROCESS_PROCESS_ITERATOR_H_ +#define BASE_PROCESS_PROCESS_ITERATOR_H_ + +#include +#include +#include + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "base/process/process.h" +#include "build/build_config.h" + +#if defined(OS_WIN) +#include +#include +#elif defined(OS_MACOSX) || defined(OS_BSD) +#include +#elif defined(OS_POSIX) +#include +#endif + +namespace base { + +#if defined(OS_WIN) +struct ProcessEntry : public PROCESSENTRY32 { + ProcessId pid() const { return th32ProcessID; } + ProcessId parent_pid() const { return th32ParentProcessID; } + const wchar_t* exe_file() const { return szExeFile; } +}; + +// Process access masks. These constants provide platform-independent +// definitions for the standard Windows access masks. +// See http://msdn.microsoft.com/en-us/library/ms684880(VS.85).aspx for +// the specific semantics of each mask value. +const uint32 kProcessAccessTerminate = PROCESS_TERMINATE; +const uint32 kProcessAccessCreateThread = PROCESS_CREATE_THREAD; +const uint32 kProcessAccessSetSessionId = PROCESS_SET_SESSIONID; +const uint32 kProcessAccessVMOperation = PROCESS_VM_OPERATION; +const uint32 kProcessAccessVMRead = PROCESS_VM_READ; +const uint32 kProcessAccessVMWrite = PROCESS_VM_WRITE; +const uint32 kProcessAccessDuplicateHandle = PROCESS_DUP_HANDLE; +const uint32 kProcessAccessCreateProcess = PROCESS_CREATE_PROCESS; +const uint32 kProcessAccessSetQuota = PROCESS_SET_QUOTA; +const uint32 kProcessAccessSetInformation = PROCESS_SET_INFORMATION; +const uint32 kProcessAccessQueryInformation = PROCESS_QUERY_INFORMATION; +const uint32 kProcessAccessSuspendResume = PROCESS_SUSPEND_RESUME; +const uint32 kProcessAccessQueryLimitedInfomation = + PROCESS_QUERY_LIMITED_INFORMATION; +const uint32 kProcessAccessWaitForTermination = SYNCHRONIZE; +#elif defined(OS_POSIX) +struct BASE_EXPORT ProcessEntry { + ProcessEntry(); + ~ProcessEntry(); + + ProcessId pid() const { return pid_; } + ProcessId parent_pid() const { return ppid_; } + ProcessId gid() const { return gid_; } + const char* exe_file() const { return exe_file_.c_str(); } + const std::vector& cmd_line_args() const { + return cmd_line_args_; + } + + ProcessId pid_; + ProcessId ppid_; + ProcessId gid_; + std::string exe_file_; + std::vector cmd_line_args_; +}; + +// Process access masks. They are not used on Posix because access checking +// does not happen during handle creation. +const uint32 kProcessAccessTerminate = 0; +const uint32 kProcessAccessCreateThread = 0; +const uint32 kProcessAccessSetSessionId = 0; +const uint32 kProcessAccessVMOperation = 0; +const uint32 kProcessAccessVMRead = 0; +const uint32 kProcessAccessVMWrite = 0; +const uint32 kProcessAccessDuplicateHandle = 0; +const uint32 kProcessAccessCreateProcess = 0; +const uint32 kProcessAccessSetQuota = 0; +const uint32 kProcessAccessSetInformation = 0; +const uint32 kProcessAccessQueryInformation = 0; +const uint32 kProcessAccessSuspendResume = 0; +const uint32 kProcessAccessQueryLimitedInfomation = 0; +const uint32 kProcessAccessWaitForTermination = 0; +#endif // defined(OS_POSIX) + +// Used to filter processes by process ID. +class ProcessFilter { + public: + // Returns true to indicate set-inclusion and false otherwise. This method + // should not have side-effects and should be idempotent. + virtual bool Includes(const ProcessEntry& entry) const = 0; + + protected: + virtual ~ProcessFilter() {} +}; + +// This class provides a way to iterate through a list of processes on the +// current machine with a specified filter. +// To use, create an instance and then call NextProcessEntry() until it returns +// false. +class BASE_EXPORT ProcessIterator { + public: + typedef std::list ProcessEntries; + + explicit ProcessIterator(const ProcessFilter* filter); + virtual ~ProcessIterator(); + + // If there's another process that matches the given executable name, + // returns a const pointer to the corresponding PROCESSENTRY32. + // If there are no more matching processes, returns NULL. + // The returned pointer will remain valid until NextProcessEntry() + // is called again or this NamedProcessIterator goes out of scope. + const ProcessEntry* NextProcessEntry(); + + // Takes a snapshot of all the ProcessEntry found. + ProcessEntries Snapshot(); + + protected: + virtual bool IncludeEntry(); + const ProcessEntry& entry() { return entry_; } + + private: + // Determines whether there's another process (regardless of executable) + // left in the list of all processes. Returns true and sets entry_ to + // that process's info if there is one, false otherwise. + bool CheckForNextProcess(); + + // Initializes a PROCESSENTRY32 data structure so that it's ready for + // use with Process32First/Process32Next. + void InitProcessEntry(ProcessEntry* entry); + +#if defined(OS_WIN) + HANDLE snapshot_; + bool started_iteration_; +#elif defined(OS_MACOSX) || defined(OS_BSD) + std::vector kinfo_procs_; + size_t index_of_kinfo_proc_; +#elif defined(OS_POSIX) + DIR* procfs_dir_; +#endif + ProcessEntry entry_; + const ProcessFilter* filter_; + + DISALLOW_COPY_AND_ASSIGN(ProcessIterator); +}; + +// This class provides a way to iterate through the list of processes +// on the current machine that were started from the given executable +// name. To use, create an instance and then call NextProcessEntry() +// until it returns false. +class BASE_EXPORT NamedProcessIterator : public ProcessIterator { + public: + NamedProcessIterator(const FilePath::StringType& executable_name, + const ProcessFilter* filter); + virtual ~NamedProcessIterator(); + + protected: + virtual bool IncludeEntry() OVERRIDE; + + private: + FilePath::StringType executable_name_; + + DISALLOW_COPY_AND_ASSIGN(NamedProcessIterator); +}; + +// Returns the number of processes on the machine that are running from the +// given executable name. If filter is non-null, then only processes selected +// by the filter will be counted. +BASE_EXPORT int GetProcessCount(const FilePath::StringType& executable_name, + const ProcessFilter* filter); + +} // namespace base + +#endif // BASE_PROCESS_PROCESS_ITERATOR_H_ diff --git a/base/process/process_iterator_freebsd.cc b/base/process/process_iterator_freebsd.cc new file mode 100644 index 0000000000..e8225b0054 --- /dev/null +++ b/base/process/process_iterator_freebsd.cc @@ -0,0 +1,124 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/process_iterator.h" + +#include + +#include "base/logging.h" +#include "base/strings/string_util.h" + +namespace base { + +ProcessIterator::ProcessIterator(const ProcessFilter* filter) + : index_of_kinfo_proc_(), + filter_(filter) { + + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_UID, getuid() }; + + bool done = false; + int try_num = 1; + const int max_tries = 10; + + do { + size_t len = 0; + if (sysctl(mib, arraysize(mib), NULL, &len, NULL, 0) < 0) { + LOG(ERROR) << "failed to get the size needed for the process list"; + kinfo_procs_.resize(0); + done = true; + } else { + size_t num_of_kinfo_proc = len / sizeof(struct kinfo_proc); + // Leave some spare room for process table growth (more could show up + // between when we check and now) + num_of_kinfo_proc += 16; + kinfo_procs_.resize(num_of_kinfo_proc); + len = num_of_kinfo_proc * sizeof(struct kinfo_proc); + if (sysctl(mib, arraysize(mib), &kinfo_procs_[0], &len, NULL, 0) <0) { + // If we get a mem error, it just means we need a bigger buffer, so + // loop around again. Anything else is a real error and give up. + if (errno != ENOMEM) { + LOG(ERROR) << "failed to get the process list"; + kinfo_procs_.resize(0); + done = true; + } + } else { + // Got the list, just make sure we're sized exactly right + size_t num_of_kinfo_proc = len / sizeof(struct kinfo_proc); + kinfo_procs_.resize(num_of_kinfo_proc); + done = true; + } + } + } while (!done && (try_num++ < max_tries)); + + if (!done) { + LOG(ERROR) << "failed to collect the process list in a few tries"; + kinfo_procs_.resize(0); + } +} + +ProcessIterator::~ProcessIterator() { +} + +bool ProcessIterator::CheckForNextProcess() { + std::string data; + + for (; index_of_kinfo_proc_ < kinfo_procs_.size(); ++index_of_kinfo_proc_) { + size_t length; + struct kinfo_proc kinfo = kinfo_procs_[index_of_kinfo_proc_]; + int mib[] = { CTL_KERN, KERN_PROC_ARGS, kinfo.ki_pid }; + + if ((kinfo.ki_pid > 0) && (kinfo.ki_stat == SZOMB)) + continue; + + length = 0; + if (sysctl(mib, arraysize(mib), NULL, &length, NULL, 0) < 0) { + LOG(ERROR) << "failed to figure out the buffer size for a command line"; + continue; + } + + data.resize(length); + + if (sysctl(mib, arraysize(mib), &data[0], &length, NULL, 0) < 0) { + LOG(ERROR) << "failed to fetch a commandline"; + continue; + } + + std::string delimiters; + delimiters.push_back('\0'); + Tokenize(data, delimiters, &entry_.cmd_line_args_); + + size_t exec_name_end = data.find('\0'); + if (exec_name_end == std::string::npos) { + LOG(ERROR) << "command line data didn't match expected format"; + continue; + } + + entry_.pid_ = kinfo.ki_pid; + entry_.ppid_ = kinfo.ki_ppid; + entry_.gid_ = kinfo.ki_pgid; + + size_t last_slash = data.rfind('/', exec_name_end); + if (last_slash == std::string::npos) { + entry_.exe_file_.assign(data, 0, exec_name_end); + } else { + entry_.exe_file_.assign(data, last_slash + 1, + exec_name_end - last_slash - 1); + } + + // Start w/ the next entry next time through + ++index_of_kinfo_proc_; + + return true; + } + return false; +} + +bool NamedProcessIterator::IncludeEntry() { + if (executable_name_ != entry().exe_file()) + return false; + + return ProcessIterator::IncludeEntry(); +} + +} // namespace base diff --git a/base/process/process_iterator_linux.cc b/base/process/process_iterator_linux.cc new file mode 100644 index 0000000000..6da51ca3b5 --- /dev/null +++ b/base/process/process_iterator_linux.cc @@ -0,0 +1,137 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/process_iterator.h" + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/process/internal_linux.h" +#include "base/strings/string_util.h" +#include "base/threading/thread_restrictions.h" + +namespace base { + +namespace { + +// Reads the |field_num|th field from |proc_stats|. +// Returns an empty string on failure. +// This version only handles VM_COMM and VM_STATE, which are the only fields +// that are strings. +std::string GetProcStatsFieldAsString( + const std::vector& proc_stats, + internal::ProcStatsFields field_num) { + if (field_num < internal::VM_COMM || field_num > internal::VM_STATE) { + NOTREACHED(); + return std::string(); + } + + if (proc_stats.size() > static_cast(field_num)) + return proc_stats[field_num]; + + NOTREACHED(); + return 0; +} + +// Reads /proc//cmdline and populates |proc_cmd_line_args| with the command +// line arguments. Returns true if successful. +// Note: /proc//cmdline contains command line arguments separated by single +// null characters. We tokenize it into a vector of strings using '\0' as a +// delimiter. +bool GetProcCmdline(pid_t pid, std::vector* proc_cmd_line_args) { + // Synchronously reading files in /proc is safe. + ThreadRestrictions::ScopedAllowIO allow_io; + + FilePath cmd_line_file = internal::GetProcPidDir(pid).Append("cmdline"); + std::string cmd_line; + if (!file_util::ReadFileToString(cmd_line_file, &cmd_line)) + return false; + std::string delimiters; + delimiters.push_back('\0'); + Tokenize(cmd_line, delimiters, proc_cmd_line_args); + return true; +} + +} // namespace + +ProcessIterator::ProcessIterator(const ProcessFilter* filter) + : filter_(filter) { + procfs_dir_ = opendir(internal::kProcDir); +} + +ProcessIterator::~ProcessIterator() { + if (procfs_dir_) { + closedir(procfs_dir_); + procfs_dir_ = NULL; + } +} + +bool ProcessIterator::CheckForNextProcess() { + // TODO(port): skip processes owned by different UID + + pid_t pid = kNullProcessId; + std::vector cmd_line_args; + std::string stats_data; + std::vector proc_stats; + + // Arbitrarily guess that there will never be more than 200 non-process + // files in /proc. Hardy has 53 and Lucid has 61. + int skipped = 0; + const int kSkipLimit = 200; + while (skipped < kSkipLimit) { + dirent* slot = readdir(procfs_dir_); + // all done looking through /proc? + if (!slot) + return false; + + // If not a process, keep looking for one. + pid = internal::ProcDirSlotToPid(slot->d_name); + if (!pid) { + skipped++; + continue; + } + + if (!GetProcCmdline(pid, &cmd_line_args)) + continue; + + if (!internal::ReadProcStats(pid, &stats_data)) + continue; + if (!internal::ParseProcStats(stats_data, &proc_stats)) + continue; + + std::string runstate = + GetProcStatsFieldAsString(proc_stats, internal::VM_STATE); + if (runstate.size() != 1) { + NOTREACHED(); + continue; + } + + // Is the process in 'Zombie' state, i.e. dead but waiting to be reaped? + // Allowed values: D R S T Z + if (runstate[0] != 'Z') + break; + + // Nope, it's a zombie; somebody isn't cleaning up after their children. + // (e.g. WaitForProcessesToExit doesn't clean up after dead children yet.) + // There could be a lot of zombies, can't really decrement i here. + } + if (skipped >= kSkipLimit) { + NOTREACHED(); + return false; + } + + entry_.pid_ = pid; + entry_.ppid_ = GetProcStatsFieldAsInt(proc_stats, internal::VM_PPID); + entry_.gid_ = GetProcStatsFieldAsInt(proc_stats, internal::VM_PGRP); + entry_.cmd_line_args_.assign(cmd_line_args.begin(), cmd_line_args.end()); + entry_.exe_file_ = GetProcessExecutablePath(pid).BaseName().value(); + return true; +} + +bool NamedProcessIterator::IncludeEntry() { + if (executable_name_ != entry().exe_file()) + return false; + return ProcessIterator::IncludeEntry(); +} + +} // namespace base diff --git a/base/process/process_iterator_mac.cc b/base/process/process_iterator_mac.cc new file mode 100644 index 0000000000..29daa2d489 --- /dev/null +++ b/base/process/process_iterator_mac.cc @@ -0,0 +1,134 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/process_iterator.h" + +#include +#include +#include + +#include "base/logging.h" +#include "base/strings/string_util.h" + +namespace base { + +ProcessIterator::ProcessIterator(const ProcessFilter* filter) + : index_of_kinfo_proc_(0), + filter_(filter) { + // Get a snapshot of all of my processes (yes, as we loop it can go stale, but + // but trying to find where we were in a constantly changing list is basically + // impossible. + + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_UID, geteuid() }; + + // Since more processes could start between when we get the size and when + // we get the list, we do a loop to keep trying until we get it. + bool done = false; + int try_num = 1; + const int max_tries = 10; + do { + // Get the size of the buffer + size_t len = 0; + if (sysctl(mib, arraysize(mib), NULL, &len, NULL, 0) < 0) { + DLOG(ERROR) << "failed to get the size needed for the process list"; + kinfo_procs_.resize(0); + done = true; + } else { + size_t num_of_kinfo_proc = len / sizeof(struct kinfo_proc); + // Leave some spare room for process table growth (more could show up + // between when we check and now) + num_of_kinfo_proc += 16; + kinfo_procs_.resize(num_of_kinfo_proc); + len = num_of_kinfo_proc * sizeof(struct kinfo_proc); + // Load the list of processes + if (sysctl(mib, arraysize(mib), &kinfo_procs_[0], &len, NULL, 0) < 0) { + // If we get a mem error, it just means we need a bigger buffer, so + // loop around again. Anything else is a real error and give up. + if (errno != ENOMEM) { + DLOG(ERROR) << "failed to get the process list"; + kinfo_procs_.resize(0); + done = true; + } + } else { + // Got the list, just make sure we're sized exactly right + size_t num_of_kinfo_proc = len / sizeof(struct kinfo_proc); + kinfo_procs_.resize(num_of_kinfo_proc); + done = true; + } + } + } while (!done && (try_num++ < max_tries)); + + if (!done) { + DLOG(ERROR) << "failed to collect the process list in a few tries"; + kinfo_procs_.resize(0); + } +} + +ProcessIterator::~ProcessIterator() { +} + +bool ProcessIterator::CheckForNextProcess() { + std::string data; + for (; index_of_kinfo_proc_ < kinfo_procs_.size(); ++index_of_kinfo_proc_) { + kinfo_proc& kinfo = kinfo_procs_[index_of_kinfo_proc_]; + + // Skip processes just awaiting collection + if ((kinfo.kp_proc.p_pid > 0) && (kinfo.kp_proc.p_stat == SZOMB)) + continue; + + int mib[] = { CTL_KERN, KERN_PROCARGS, kinfo.kp_proc.p_pid }; + + // Find out what size buffer we need. + size_t data_len = 0; + if (sysctl(mib, arraysize(mib), NULL, &data_len, NULL, 0) < 0) { + DVPLOG(1) << "failed to figure out the buffer size for a commandline"; + continue; + } + + data.resize(data_len); + if (sysctl(mib, arraysize(mib), &data[0], &data_len, NULL, 0) < 0) { + DVPLOG(1) << "failed to fetch a commandline"; + continue; + } + + // |data| contains all the command line parameters of the process, separated + // by blocks of one or more null characters. We tokenize |data| into a + // vector of strings using '\0' as a delimiter and populate + // |entry_.cmd_line_args_|. + std::string delimiters; + delimiters.push_back('\0'); + Tokenize(data, delimiters, &entry_.cmd_line_args_); + + // |data| starts with the full executable path followed by a null character. + // We search for the first instance of '\0' and extract everything before it + // to populate |entry_.exe_file_|. + size_t exec_name_end = data.find('\0'); + if (exec_name_end == std::string::npos) { + DLOG(ERROR) << "command line data didn't match expected format"; + continue; + } + + entry_.pid_ = kinfo.kp_proc.p_pid; + entry_.ppid_ = kinfo.kp_eproc.e_ppid; + entry_.gid_ = kinfo.kp_eproc.e_pgid; + size_t last_slash = data.rfind('/', exec_name_end); + if (last_slash == std::string::npos) + entry_.exe_file_.assign(data, 0, exec_name_end); + else + entry_.exe_file_.assign(data, last_slash + 1, + exec_name_end - last_slash - 1); + // Start w/ the next entry next time through + ++index_of_kinfo_proc_; + // Done + return true; + } + return false; +} + +bool NamedProcessIterator::IncludeEntry() { + return (executable_name_ == entry().exe_file() && + ProcessIterator::IncludeEntry()); +} + +} // namespace base diff --git a/base/process/process_iterator_openbsd.cc b/base/process/process_iterator_openbsd.cc new file mode 100644 index 0000000000..7c44eb118c --- /dev/null +++ b/base/process/process_iterator_openbsd.cc @@ -0,0 +1,128 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/process_iterator.h" + +#include +#include + +#include "base/logging.h" +#include "base/strings/string_util.h" + +namespace base { + +ProcessIterator::ProcessIterator(const ProcessFilter* filter) + : index_of_kinfo_proc_(), + filter_(filter) { + + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_UID, getuid(), + sizeof(struct kinfo_proc), 0 }; + + bool done = false; + int try_num = 1; + const int max_tries = 10; + + do { + size_t len = 0; + if (sysctl(mib, arraysize(mib), NULL, &len, NULL, 0) < 0) { + DLOG(ERROR) << "failed to get the size needed for the process list"; + kinfo_procs_.resize(0); + done = true; + } else { + size_t num_of_kinfo_proc = len / sizeof(struct kinfo_proc); + // Leave some spare room for process table growth (more could show up + // between when we check and now) + num_of_kinfo_proc += 16; + kinfo_procs_.resize(num_of_kinfo_proc); + len = num_of_kinfo_proc * sizeof(struct kinfo_proc); + if (sysctl(mib, arraysize(mib), &kinfo_procs_[0], &len, NULL, 0) < 0) { + // If we get a mem error, it just means we need a bigger buffer, so + // loop around again. Anything else is a real error and give up. + if (errno != ENOMEM) { + DLOG(ERROR) << "failed to get the process list"; + kinfo_procs_.resize(0); + done = true; + } + } else { + // Got the list, just make sure we're sized exactly right + size_t num_of_kinfo_proc = len / sizeof(struct kinfo_proc); + kinfo_procs_.resize(num_of_kinfo_proc); + done = true; + } + } + } while (!done && (try_num++ < max_tries)); + + if (!done) { + DLOG(ERROR) << "failed to collect the process list in a few tries"; + kinfo_procs_.resize(0); + } +} + +ProcessIterator::~ProcessIterator() { +} + +bool ProcessIterator::CheckForNextProcess() { + std::string data; + for (; index_of_kinfo_proc_ < kinfo_procs_.size(); ++index_of_kinfo_proc_) { + kinfo_proc& kinfo = kinfo_procs_[index_of_kinfo_proc_]; + + // Skip processes just awaiting collection + if ((kinfo.p_pid > 0) && (kinfo.p_stat == SZOMB)) + continue; + + int mib[] = { CTL_KERN, KERN_PROC_ARGS, kinfo.p_pid }; + + // Find out what size buffer we need. + size_t data_len = 0; + if (sysctl(mib, arraysize(mib), NULL, &data_len, NULL, 0) < 0) { + DVPLOG(1) << "failed to figure out the buffer size for a commandline"; + continue; + } + + data.resize(data_len); + if (sysctl(mib, arraysize(mib), &data[0], &data_len, NULL, 0) < 0) { + DVPLOG(1) << "failed to fetch a commandline"; + continue; + } + + // |data| contains all the command line parameters of the process, separated + // by blocks of one or more null characters. We tokenize |data| into a + // vector of strings using '\0' as a delimiter and populate + // |entry_.cmd_line_args_|. + std::string delimiters; + delimiters.push_back('\0'); + Tokenize(data, delimiters, &entry_.cmd_line_args_); + + // |data| starts with the full executable path followed by a null character. + // We search for the first instance of '\0' and extract everything before it + // to populate |entry_.exe_file_|. + size_t exec_name_end = data.find('\0'); + if (exec_name_end == std::string::npos) { + DLOG(ERROR) << "command line data didn't match expected format"; + continue; + } + + entry_.pid_ = kinfo.p_pid; + entry_.ppid_ = kinfo.p_ppid; + entry_.gid_ = kinfo.p__pgid; + size_t last_slash = data.rfind('/', exec_name_end); + if (last_slash == std::string::npos) + entry_.exe_file_.assign(data, 0, exec_name_end); + else + entry_.exe_file_.assign(data, last_slash + 1, + exec_name_end - last_slash - 1); + // Start w/ the next entry next time through + ++index_of_kinfo_proc_; + // Done + return true; + } + return false; +} + +bool NamedProcessIterator::IncludeEntry() { + return (executable_name_ == entry().exe_file() && + ProcessIterator::IncludeEntry()); +} + +} // namespace base diff --git a/base/process/process_iterator_win.cc b/base/process/process_iterator_win.cc new file mode 100644 index 0000000000..9d5a970ef4 --- /dev/null +++ b/base/process/process_iterator_win.cc @@ -0,0 +1,41 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/process_iterator.h" + +namespace base { + +ProcessIterator::ProcessIterator(const ProcessFilter* filter) + : started_iteration_(false), + filter_(filter) { + snapshot_ = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); +} + +ProcessIterator::~ProcessIterator() { + CloseHandle(snapshot_); +} + +bool ProcessIterator::CheckForNextProcess() { + InitProcessEntry(&entry_); + + if (!started_iteration_) { + started_iteration_ = true; + return !!Process32First(snapshot_, &entry_); + } + + return !!Process32Next(snapshot_, &entry_); +} + +void ProcessIterator::InitProcessEntry(ProcessEntry* entry) { + memset(entry, 0, sizeof(*entry)); + entry->dwSize = sizeof(*entry); +} + +bool NamedProcessIterator::IncludeEntry() { + // Case insensitive. + return _wcsicmp(executable_name_.c_str(), entry().exe_file()) == 0 && + ProcessIterator::IncludeEntry(); +} + +} // namespace base diff --git a/base/process/process_linux.cc b/base/process/process_linux.cc new file mode 100644 index 0000000000..93006aafe5 --- /dev/null +++ b/base/process/process_linux.cc @@ -0,0 +1,137 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/process.h" + +#include +#include + +#include "base/file_util.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/strings/string_split.h" +#include "base/strings/stringprintf.h" +#include "base/synchronization/lock.h" + +namespace { +const int kForegroundPriority = 0; + +#if defined(OS_CHROMEOS) +// We are more aggressive in our lowering of background process priority +// for chromeos as we have much more control over other processes running +// on the machine. +// +// TODO(davemoore) Refactor this by adding support for higher levels to set +// the foregrounding / backgrounding process so we don't have to keep +// chrome / chromeos specific logic here. +const int kBackgroundPriority = 19; +const char kControlPath[] = "/sys/fs/cgroup/cpu%s/cgroup.procs"; +const char kForeground[] = "/chrome_renderers/foreground"; +const char kBackground[] = "/chrome_renderers/background"; +const char kProcPath[] = "/proc/%d/cgroup"; + +struct CGroups { + // Check for cgroups files. ChromeOS supports these by default. It creates + // a cgroup mount in /sys/fs/cgroup and then configures two cpu task groups, + // one contains at most a single foreground renderer and the other contains + // all background renderers. This allows us to limit the impact of background + // renderers on foreground ones to a greater level than simple renicing. + bool enabled; + base::FilePath foreground_file; + base::FilePath background_file; + + CGroups() { + foreground_file = + base::FilePath(base::StringPrintf(kControlPath, kForeground)); + background_file = + base::FilePath(base::StringPrintf(kControlPath, kBackground)); + file_util::FileSystemType foreground_type; + file_util::FileSystemType background_type; + enabled = + file_util::GetFileSystemType(foreground_file, &foreground_type) && + file_util::GetFileSystemType(background_file, &background_type) && + foreground_type == file_util::FILE_SYSTEM_CGROUP && + background_type == file_util::FILE_SYSTEM_CGROUP; + } +}; + +base::LazyInstance cgroups = LAZY_INSTANCE_INITIALIZER; +#else +const int kBackgroundPriority = 5; +#endif +} + +namespace base { + +bool Process::IsProcessBackgrounded() const { + DCHECK(process_); + +#if defined(OS_CHROMEOS) + if (cgroups.Get().enabled) { + std::string proc; + if (file_util::ReadFileToString( + base::FilePath(StringPrintf(kProcPath, process_)), + &proc)) { + std::vector proc_parts; + base::SplitString(proc, ':', &proc_parts); + DCHECK(proc_parts.size() == 3); + bool ret = proc_parts[2] == std::string(kBackground); + return ret; + } else { + return false; + } + } +#endif + return GetPriority() == kBackgroundPriority; +} + +bool Process::SetProcessBackgrounded(bool background) { + DCHECK(process_); + +#if defined(OS_CHROMEOS) + if (cgroups.Get().enabled) { + std::string pid = StringPrintf("%d", process_); + const base::FilePath file = + background ? + cgroups.Get().background_file : cgroups.Get().foreground_file; + return file_util::WriteFile(file, pid.c_str(), pid.size()) > 0; + } +#endif // OS_CHROMEOS + + if (!CanBackgroundProcesses()) + return false; + + int priority = background ? kBackgroundPriority : kForegroundPriority; + int result = setpriority(PRIO_PROCESS, process_, priority); + DPCHECK(result == 0); + return result == 0; +} + +struct CheckForNicePermission { + CheckForNicePermission() : can_reraise_priority(false) { + // We won't be able to raise the priority if we don't have the right rlimit. + // The limit may be adjusted in /etc/security/limits.conf for PAM systems. + struct rlimit rlim; + if ((getrlimit(RLIMIT_NICE, &rlim) == 0) && + (20 - kForegroundPriority) <= static_cast(rlim.rlim_cur)) { + can_reraise_priority = true; + } + }; + + bool can_reraise_priority; +}; + +// static +bool Process::CanBackgroundProcesses() { +#if defined(OS_CHROMEOS) + if (cgroups.Get().enabled) + return true; +#endif + + static LazyInstance check_for_nice_permission = + LAZY_INSTANCE_INITIALIZER; + return check_for_nice_permission.Get().can_reraise_priority; +} + +} // namespace base diff --git a/base/process/process_metrics.h b/base/process/process_metrics.h new file mode 100644 index 0000000000..e329b4ef73 --- /dev/null +++ b/base/process/process_metrics.h @@ -0,0 +1,269 @@ +// 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 file contains routines for gathering resource statistics for processes +// running on the system. + +#ifndef BASE_PROCESS_PROCESS_METRICS_H_ +#define BASE_PROCESS_PROCESS_METRICS_H_ + +#include + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/process/process_handle.h" +#include "base/time/time.h" + +#if defined(OS_MACOSX) +#include +#endif + +namespace base { + +#if defined(OS_WIN) +struct IoCounters : public IO_COUNTERS { +}; +#elif defined(OS_POSIX) +struct IoCounters { + uint64_t ReadOperationCount; + uint64_t WriteOperationCount; + uint64_t OtherOperationCount; + uint64_t ReadTransferCount; + uint64_t WriteTransferCount; + uint64_t OtherTransferCount; +}; +#endif + +// Working Set (resident) memory usage broken down by +// +// On Windows: +// priv (private): These pages (kbytes) cannot be shared with any other process. +// shareable: These pages (kbytes) can be shared with other processes under +// the right circumstances. +// shared : These pages (kbytes) are currently shared with at least one +// other process. +// +// On Linux: +// priv: Pages mapped only by this process. +// shared: PSS or 0 if the kernel doesn't support this. +// shareable: 0 + +// On ChromeOS: +// priv: Pages mapped only by this process. +// shared: PSS or 0 if the kernel doesn't support this. +// shareable: 0 +// swapped Pages swapped out to zram. +// +// On OS X: TODO(thakis): Revise. +// priv: Memory. +// shared: 0 +// shareable: 0 +// +struct WorkingSetKBytes { + WorkingSetKBytes() : priv(0), shareable(0), shared(0) {} + size_t priv; + size_t shareable; + size_t shared; +#if defined(OS_CHROMEOS) + size_t swapped; +#endif +}; + +// Committed (resident + paged) memory usage broken down by +// private: These pages cannot be shared with any other process. +// mapped: These pages are mapped into the view of a section (backed by +// pagefile.sys) +// image: These pages are mapped into the view of an image section (backed by +// file system) +struct CommittedKBytes { + CommittedKBytes() : priv(0), mapped(0), image(0) {} + size_t priv; + size_t mapped; + size_t image; +}; + +// Free memory (Megabytes marked as free) in the 2G process address space. +// total : total amount in megabytes marked as free. Maximum value is 2048. +// largest : size of the largest contiguous amount of memory found. It is +// always smaller or equal to FreeMBytes::total. +// largest_ptr: starting address of the largest memory block. +struct FreeMBytes { + size_t total; + size_t largest; + void* largest_ptr; +}; + +// Convert a POSIX timeval to microseconds. +BASE_EXPORT int64 TimeValToMicroseconds(const struct timeval& tv); + +// Provides performance metrics for a specified process (CPU usage, memory and +// IO counters). To use it, invoke CreateProcessMetrics() to get an instance +// for a specific process, then access the information with the different get +// methods. +class BASE_EXPORT ProcessMetrics { + public: + ~ProcessMetrics(); + + // Creates a ProcessMetrics for the specified process. + // The caller owns the returned object. +#if !defined(OS_MACOSX) || defined(OS_IOS) + static ProcessMetrics* CreateProcessMetrics(ProcessHandle process); +#else + class PortProvider { + public: + virtual ~PortProvider() {} + + // Should return the mach task for |process| if possible, or else + // |MACH_PORT_NULL|. Only processes that this returns tasks for will have + // metrics on OS X (except for the current process, which always gets + // metrics). + virtual mach_port_t TaskForPid(ProcessHandle process) const = 0; + }; + + // The port provider needs to outlive the ProcessMetrics object returned by + // this function. If NULL is passed as provider, the returned object + // only returns valid metrics if |process| is the current process. + static ProcessMetrics* CreateProcessMetrics(ProcessHandle process, + PortProvider* port_provider); +#endif // !defined(OS_MACOSX) || defined(OS_IOS) + + // Returns the current space allocated for the pagefile, in bytes (these pages + // may or may not be in memory). On Linux, this returns the total virtual + // memory size. + size_t GetPagefileUsage() const; + // Returns the peak space allocated for the pagefile, in bytes. + size_t GetPeakPagefileUsage() const; + // Returns the current working set size, in bytes. On Linux, this returns + // the resident set size. + size_t GetWorkingSetSize() const; + // Returns the peak working set size, in bytes. + size_t GetPeakWorkingSetSize() const; + // Returns private and sharedusage, in bytes. Private bytes is the amount of + // memory currently allocated to a process that cannot be shared. Returns + // false on platform specific error conditions. Note: |private_bytes| + // returns 0 on unsupported OSes: prior to XP SP2. + bool GetMemoryBytes(size_t* private_bytes, + size_t* shared_bytes); + // Fills a CommittedKBytes with both resident and paged + // memory usage as per definition of CommittedBytes. + void GetCommittedKBytes(CommittedKBytes* usage) const; + // Fills a WorkingSetKBytes containing resident private and shared memory + // usage in bytes, as per definition of WorkingSetBytes. + bool GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const; + + // Computes the current process available memory for allocation. + // It does a linear scan of the address space querying each memory region + // for its free (unallocated) status. It is useful for estimating the memory + // load and fragmentation. + bool CalculateFreeMemory(FreeMBytes* free) const; + + // Returns the CPU usage in percent since the last time this method was + // called. The first time this method is called it returns 0 and will return + // the actual CPU info on subsequent calls. + // On Windows, the CPU usage value is for all CPUs. So if you have 2 CPUs and + // your process is using all the cycles of 1 CPU and not the other CPU, this + // method returns 50. + double GetCPUUsage(); + + // Retrieves accounting information for all I/O operations performed by the + // process. + // If IO information is retrieved successfully, the function returns true + // and fills in the IO_COUNTERS passed in. The function returns false + // otherwise. + bool GetIOCounters(IoCounters* io_counters) const; + + private: +#if !defined(OS_MACOSX) || defined(OS_IOS) + explicit ProcessMetrics(ProcessHandle process); +#else + ProcessMetrics(ProcessHandle process, PortProvider* port_provider); +#endif // !defined(OS_MACOSX) || defined(OS_IOS) + +#if defined(OS_LINUX) || defined(OS_ANDROID) + bool GetWorkingSetKBytesStatm(WorkingSetKBytes* ws_usage) const; +#endif + +#if defined(OS_CHROMEOS) + bool GetWorkingSetKBytesTotmaps(WorkingSetKBytes *ws_usage) const; +#endif + + ProcessHandle process_; + + int processor_count_; + + // Used to store the previous times and CPU usage counts so we can + // compute the CPU usage between calls. + int64 last_time_; + int64 last_system_time_; + +#if !defined(OS_IOS) +#if defined(OS_MACOSX) + // Queries the port provider if it's set. + mach_port_t TaskForPid(ProcessHandle process) const; + + PortProvider* port_provider_; +#elif defined(OS_POSIX) + // Jiffie count at the last_time_ we updated. + int last_cpu_; +#endif // defined(OS_POSIX) +#endif // !defined(OS_IOS) + + DISALLOW_COPY_AND_ASSIGN(ProcessMetrics); +}; + +// Returns the memory committed by the system in KBytes. +// Returns 0 if it can't compute the commit charge. +BASE_EXPORT size_t GetSystemCommitCharge(); + +#if defined(OS_LINUX) || defined(OS_ANDROID) +// Parse the data found in /proc//stat and return the sum of the +// CPU-related ticks. Returns -1 on parse error. +// Exposed for testing. +BASE_EXPORT int ParseProcStatCPU(const std::string& input); + +// Data from /proc/meminfo about system-wide memory consumption. +// Values are in KB. +struct BASE_EXPORT SystemMemoryInfoKB { + SystemMemoryInfoKB(); + + int total; + int free; + int buffers; + int cached; + int active_anon; + int inactive_anon; + int active_file; + int inactive_file; + int shmem; + + // Gem data will be -1 if not supported. + int gem_objects; + long long gem_size; +}; +// Retrieves data from /proc/meminfo about system-wide memory consumption. +// Fills in the provided |meminfo| structure. Returns true on success. +// Exposed for memory debugging widget. +BASE_EXPORT bool GetSystemMemoryInfo(SystemMemoryInfoKB* meminfo); +#endif // defined(OS_LINUX) || defined(OS_ANDROID) + +#if defined(OS_LINUX) || defined(OS_ANDROID) +// Get the number of threads of |process| as available in /proc//stat. +// This should be used with care as no synchronization with running threads is +// done. This is mostly useful to guarantee being single-threaded. +// Returns 0 on failure. +BASE_EXPORT int GetNumberOfThreads(ProcessHandle process); + +// /proc/self/exe refers to the current executable. +BASE_EXPORT extern const char kProcSelfExe[]; +#endif // defined(OS_LINUX) || defined(OS_ANDROID) + +#if defined(OS_POSIX) +// Returns the maximum number of file descriptors that can be open by a process +// at once. If the number is unavailable, a conservative best guess is returned. +size_t GetMaxFds(); +#endif // defined(OS_POSIX) + +} // namespace base + +#endif // BASE_PROCESS_PROCESS_METRICS_H_ diff --git a/base/process/process_metrics_freebsd.cc b/base/process/process_metrics_freebsd.cc new file mode 100644 index 0000000000..019454cd81 --- /dev/null +++ b/base/process/process_metrics_freebsd.cc @@ -0,0 +1,122 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/process_metrics.h" + +namespace base { + +ProcessMetrics::ProcessMetrics(ProcessHandle process) + : process_(process), + last_time_(0), + last_system_time_(0), + last_cpu_(0) { + processor_count_ = base::SysInfo::NumberOfProcessors(); +} + +// static +ProcessMetrics* ProcessMetrics::CreateProcessMetrics(ProcessHandle process) { + return new ProcessMetrics(process); +} + +size_t ProcessMetrics::GetPagefileUsage() const { + struct kinfo_proc info; + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, process_ }; + size_t length = sizeof(info); + + if (sysctl(mib, arraysize(mib), &info, &length, NULL, 0) < 0) + return 0; + + return info.ki_size; +} + +size_t ProcessMetrics::GetPeakPagefileUsage() const { + return 0; +} + +size_t ProcessMetrics::GetWorkingSetSize() const { + struct kinfo_proc info; + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, process_ }; + size_t length = sizeof(info); + + if (sysctl(mib, arraysize(mib), &info, &length, NULL, 0) < 0) + return 0; + + return info.ki_rssize * getpagesize(); +} + +size_t ProcessMetrics::GetPeakWorkingSetSize() const { + return 0; +} + +bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes, + size_t* shared_bytes) { + WorkingSetKBytes ws_usage; + if (!GetWorkingSetKBytes(&ws_usage)) + return false; + + if (private_bytes) + *private_bytes = ws_usage.priv << 10; + + if (shared_bytes) + *shared_bytes = ws_usage.shared * 1024; + + return true; +} + +bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const { +// TODO(bapt) be sure we can't be precise + size_t priv = GetWorkingSetSize(); + if (!priv) + return false; + ws_usage->priv = priv / 1024; + ws_usage->shareable = 0; + ws_usage->shared = 0; + + return true; +} + +double ProcessMetrics::GetCPUUsage() { + struct kinfo_proc info; + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, process_ }; + size_t length = sizeof(info); + + struct timeval now; + int retval = gettimeofday(&now, NULL); + if (retval) + return 0; + + if (sysctl(mib, arraysize(mib), &info, &length, NULL, 0) < 0) + return 0; + + return (info.ki_pctcpu / FSCALE) * 100.0; +} + +bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const { + return false; +} + +size_t GetSystemCommitCharge() { + int mib[2], pagesize; + unsigned long mem_total, mem_free, mem_inactive; + size_t length = sizeof(mem_total); + + if (sysctl(mib, arraysize(mib), &mem_total, &length, NULL, 0) < 0) + return 0; + + length = sizeof(mem_free); + if (sysctlbyname("vm.stats.vm.v_free_count", &mem_free, &length, NULL, 0) < 0) + return 0; + + length = sizeof(mem_inactive); + if (sysctlbyname("vm.stats.vm.v_inactive_count", &mem_inactive, &length, + NULL, 0) < 0) { + return 0; + } + + pagesize = getpagesize(); + + return mem_total - (mem_free*pagesize) - (mem_inactive*pagesize); +} + +} // namespace base diff --git a/base/process/process_metrics_ios.cc b/base/process/process_metrics_ios.cc new file mode 100644 index 0000000000..94c671901b --- /dev/null +++ b/base/process/process_metrics_ios.cc @@ -0,0 +1,64 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/process_metrics.h" + +#include + +namespace base { + +namespace { + +bool GetTaskInfo(task_basic_info_64* task_info_data) { + mach_msg_type_number_t count = TASK_BASIC_INFO_64_COUNT; + kern_return_t kr = task_info(mach_task_self(), + TASK_BASIC_INFO_64, + reinterpret_cast(task_info_data), + &count); + return kr == KERN_SUCCESS; +} + +} // namespace + +ProcessMetrics::ProcessMetrics(ProcessHandle process) {} + +ProcessMetrics::~ProcessMetrics() {} + +// static +ProcessMetrics* ProcessMetrics::CreateProcessMetrics(ProcessHandle process) { + return new ProcessMetrics(process); +} + +size_t ProcessMetrics::GetPagefileUsage() const { + task_basic_info_64 task_info_data; + if (!GetTaskInfo(&task_info_data)) + return 0; + return task_info_data.virtual_size; +} + +size_t ProcessMetrics::GetWorkingSetSize() const { + task_basic_info_64 task_info_data; + if (!GetTaskInfo(&task_info_data)) + return 0; + return task_info_data.resident_size; +} + +size_t GetMaxFds() { + static const rlim_t kSystemDefaultMaxFds = 256; + rlim_t max_fds; + struct rlimit nofile; + if (getrlimit(RLIMIT_NOFILE, &nofile)) { + // Error case: Take a best guess. + max_fds = kSystemDefaultMaxFds; + } else { + max_fds = nofile.rlim_cur; + } + + if (max_fds > INT_MAX) + max_fds = INT_MAX; + + return static_cast(max_fds); +} + +} // namespace base diff --git a/base/process/process_metrics_linux.cc b/base/process/process_metrics_linux.cc new file mode 100644 index 0000000000..1c86ee467c --- /dev/null +++ b/base/process/process_metrics_linux.cc @@ -0,0 +1,516 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/process_metrics.h" + +#include +#include +#include +#include +#include +#include + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/process/internal_linux.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_tokenizer.h" +#include "base/strings/string_util.h" +#include "base/sys_info.h" +#include "base/threading/thread_restrictions.h" + +namespace base { + +namespace { + +enum ParsingState { + KEY_NAME, + KEY_VALUE +}; + +// Read /proc//status and returns the value for |field|, or 0 on failure. +// Only works for fields in the form of "Field: value kB". +size_t ReadProcStatusAndGetFieldAsSizeT(pid_t pid, const std::string& field) { + FilePath stat_file = internal::GetProcPidDir(pid).Append("status"); + std::string status; + { + // Synchronously reading files in /proc is safe. + ThreadRestrictions::ScopedAllowIO allow_io; + if (!file_util::ReadFileToString(stat_file, &status)) + return 0; + } + + StringTokenizer tokenizer(status, ":\n"); + ParsingState state = KEY_NAME; + StringPiece last_key_name; + while (tokenizer.GetNext()) { + switch (state) { + case KEY_NAME: + last_key_name = tokenizer.token_piece(); + state = KEY_VALUE; + break; + case KEY_VALUE: + DCHECK(!last_key_name.empty()); + if (last_key_name == field) { + std::string value_str; + tokenizer.token_piece().CopyToString(&value_str); + std::string value_str_trimmed; + TrimWhitespaceASCII(value_str, TRIM_ALL, &value_str_trimmed); + std::vector split_value_str; + SplitString(value_str_trimmed, ' ', &split_value_str); + if (split_value_str.size() != 2 || split_value_str[1] != "kB") { + NOTREACHED(); + return 0; + } + size_t value; + if (!StringToSizeT(split_value_str[0], &value)) { + NOTREACHED(); + return 0; + } + return value; + } + state = KEY_NAME; + break; + } + } + NOTREACHED(); + return 0; +} + +// Get the total CPU of a single process. Return value is number of jiffies +// on success or -1 on error. +int GetProcessCPU(pid_t pid) { + // Use /proc//task to find all threads and parse their /stat file. + FilePath task_path = internal::GetProcPidDir(pid).Append("task"); + + DIR* dir = opendir(task_path.value().c_str()); + if (!dir) { + DPLOG(ERROR) << "opendir(" << task_path.value() << ")"; + return -1; + } + + int total_cpu = 0; + while (struct dirent* ent = readdir(dir)) { + pid_t tid = internal::ProcDirSlotToPid(ent->d_name); + if (!tid) + continue; + + // Synchronously reading files in /proc is safe. + ThreadRestrictions::ScopedAllowIO allow_io; + + std::string stat; + FilePath stat_path = + task_path.Append(ent->d_name).Append(internal::kStatFile); + if (file_util::ReadFileToString(stat_path, &stat)) { + int cpu = ParseProcStatCPU(stat); + if (cpu > 0) + total_cpu += cpu; + } + } + closedir(dir); + + return total_cpu; +} + +} // namespace + +// static +ProcessMetrics* ProcessMetrics::CreateProcessMetrics(ProcessHandle process) { + return new ProcessMetrics(process); +} + +// On linux, we return vsize. +size_t ProcessMetrics::GetPagefileUsage() const { + return internal::ReadProcStatsAndGetFieldAsSizeT(process_, + internal::VM_VSIZE); +} + +// On linux, we return the high water mark of vsize. +size_t ProcessMetrics::GetPeakPagefileUsage() const { + return ReadProcStatusAndGetFieldAsSizeT(process_, "VmPeak") * 1024; +} + +// On linux, we return RSS. +size_t ProcessMetrics::GetWorkingSetSize() const { + return internal::ReadProcStatsAndGetFieldAsSizeT(process_, internal::VM_RSS) * + getpagesize(); +} + +// On linux, we return the high water mark of RSS. +size_t ProcessMetrics::GetPeakWorkingSetSize() const { + return ReadProcStatusAndGetFieldAsSizeT(process_, "VmHWM") * 1024; +} + +bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes, + size_t* shared_bytes) { + WorkingSetKBytes ws_usage; + if (!GetWorkingSetKBytes(&ws_usage)) + return false; + + if (private_bytes) + *private_bytes = ws_usage.priv * 1024; + + if (shared_bytes) + *shared_bytes = ws_usage.shared * 1024; + + return true; +} + +bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const { +#if defined(OS_CHROMEOS) + if (GetWorkingSetKBytesTotmaps(ws_usage)) + return true; +#endif + return GetWorkingSetKBytesStatm(ws_usage); +} + +double ProcessMetrics::GetCPUUsage() { + struct timeval now; + int retval = gettimeofday(&now, NULL); + if (retval) + return 0; + int64 time = TimeValToMicroseconds(now); + + if (last_time_ == 0) { + // First call, just set the last values. + last_time_ = time; + last_cpu_ = GetProcessCPU(process_); + return 0; + } + + int64 time_delta = time - last_time_; + DCHECK_NE(time_delta, 0); + if (time_delta == 0) + return 0; + + int cpu = GetProcessCPU(process_); + + // We have the number of jiffies in the time period. Convert to percentage. + // Note this means we will go *over* 100 in the case where multiple threads + // are together adding to more than one CPU's worth. + TimeDelta cpu_time = internal::ClockTicksToTimeDelta(cpu); + TimeDelta last_cpu_time = internal::ClockTicksToTimeDelta(last_cpu_); + int percentage = 100 * (cpu_time - last_cpu_time).InSecondsF() / + TimeDelta::FromMicroseconds(time_delta).InSecondsF(); + + last_time_ = time; + last_cpu_ = cpu; + + return percentage; +} + +// To have /proc/self/io file you must enable CONFIG_TASK_IO_ACCOUNTING +// in your kernel configuration. +bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const { + // Synchronously reading files in /proc is safe. + ThreadRestrictions::ScopedAllowIO allow_io; + + std::string proc_io_contents; + FilePath io_file = internal::GetProcPidDir(process_).Append("io"); + if (!file_util::ReadFileToString(io_file, &proc_io_contents)) + return false; + + (*io_counters).OtherOperationCount = 0; + (*io_counters).OtherTransferCount = 0; + + StringTokenizer tokenizer(proc_io_contents, ": \n"); + ParsingState state = KEY_NAME; + StringPiece last_key_name; + while (tokenizer.GetNext()) { + switch (state) { + case KEY_NAME: + last_key_name = tokenizer.token_piece(); + state = KEY_VALUE; + break; + case KEY_VALUE: + DCHECK(!last_key_name.empty()); + if (last_key_name == "syscr") { + StringToInt64(tokenizer.token_piece(), + reinterpret_cast(&(*io_counters).ReadOperationCount)); + } else if (last_key_name == "syscw") { + StringToInt64(tokenizer.token_piece(), + reinterpret_cast(&(*io_counters).WriteOperationCount)); + } else if (last_key_name == "rchar") { + StringToInt64(tokenizer.token_piece(), + reinterpret_cast(&(*io_counters).ReadTransferCount)); + } else if (last_key_name == "wchar") { + StringToInt64(tokenizer.token_piece(), + reinterpret_cast(&(*io_counters).WriteTransferCount)); + } + state = KEY_NAME; + break; + } + } + return true; +} + +ProcessMetrics::ProcessMetrics(ProcessHandle process) + : process_(process), + last_time_(0), + last_system_time_(0), + last_cpu_(0) { + processor_count_ = base::SysInfo::NumberOfProcessors(); +} + +#if defined(OS_CHROMEOS) +// Private, Shared and Proportional working set sizes are obtained from +// /proc//totmaps +bool ProcessMetrics::GetWorkingSetKBytesTotmaps(WorkingSetKBytes *ws_usage) + const { + // The format of /proc//totmaps is: + // + // Rss: 6120 kB + // Pss: 3335 kB + // Shared_Clean: 1008 kB + // Shared_Dirty: 4012 kB + // Private_Clean: 4 kB + // Private_Dirty: 1096 kB + // Referenced: XXX kB + // Anonymous: XXX kB + // AnonHugePages: XXX kB + // Swap: XXX kB + // Locked: XXX kB + const size_t kPssIndex = (1 * 3) + 1; + const size_t kPrivate_CleanIndex = (4 * 3) + 1; + const size_t kPrivate_DirtyIndex = (5 * 3) + 1; + const size_t kSwapIndex = (9 * 3) + 1; + + std::string totmaps_data; + { + FilePath totmaps_file = internal::GetProcPidDir(process_).Append("totmaps"); + ThreadRestrictions::ScopedAllowIO allow_io; + bool ret = file_util::ReadFileToString(totmaps_file, &totmaps_data); + if (!ret || totmaps_data.length() == 0) + return false; + } + + std::vector totmaps_fields; + SplitStringAlongWhitespace(totmaps_data, &totmaps_fields); + + DCHECK_EQ("Pss:", totmaps_fields[kPssIndex-1]); + DCHECK_EQ("Private_Clean:", totmaps_fields[kPrivate_CleanIndex - 1]); + DCHECK_EQ("Private_Dirty:", totmaps_fields[kPrivate_DirtyIndex - 1]); + DCHECK_EQ("Swap:", totmaps_fields[kSwapIndex-1]); + + int pss = 0; + int private_clean = 0; + int private_dirty = 0; + int swap = 0; + bool ret = true; + ret &= StringToInt(totmaps_fields[kPssIndex], &pss); + ret &= StringToInt(totmaps_fields[kPrivate_CleanIndex], &private_clean); + ret &= StringToInt(totmaps_fields[kPrivate_DirtyIndex], &private_dirty); + ret &= StringToInt(totmaps_fields[kSwapIndex], &swap); + + // On ChromeOS swap is to zram. We count this as private / shared, as + // increased swap decreases available RAM to user processes, which would + // otherwise create surprising results. + ws_usage->priv = private_clean + private_dirty + swap; + ws_usage->shared = pss + swap; + ws_usage->shareable = 0; + ws_usage->swapped = swap; + return ret; +} +#endif + +// Private and Shared working set sizes are obtained from /proc//statm. +bool ProcessMetrics::GetWorkingSetKBytesStatm(WorkingSetKBytes* ws_usage) + const { + // Use statm instead of smaps because smaps is: + // a) Large and slow to parse. + // b) Unavailable in the SUID sandbox. + + // First we need to get the page size, since everything is measured in pages. + // For details, see: man 5 proc. + const int page_size_kb = getpagesize() / 1024; + if (page_size_kb <= 0) + return false; + + std::string statm; + { + FilePath statm_file = internal::GetProcPidDir(process_).Append("statm"); + // Synchronously reading files in /proc is safe. + ThreadRestrictions::ScopedAllowIO allow_io; + bool ret = file_util::ReadFileToString(statm_file, &statm); + if (!ret || statm.length() == 0) + return false; + } + + std::vector statm_vec; + SplitString(statm, ' ', &statm_vec); + if (statm_vec.size() != 7) + return false; // Not the format we expect. + + int statm_rss, statm_shared; + bool ret = true; + ret &= StringToInt(statm_vec[1], &statm_rss); + ret &= StringToInt(statm_vec[2], &statm_shared); + + ws_usage->priv = (statm_rss - statm_shared) * page_size_kb; + ws_usage->shared = statm_shared * page_size_kb; + + // Sharable is not calculated, as it does not provide interesting data. + ws_usage->shareable = 0; + +#if defined(OS_CHROMEOS) + // Can't get swapped memory from statm. + ws_usage->swapped = 0; +#endif + + return ret; +} + +size_t GetSystemCommitCharge() { + SystemMemoryInfoKB meminfo; + if (!GetSystemMemoryInfo(&meminfo)) + return 0; + return meminfo.total - meminfo.free - meminfo.buffers - meminfo.cached; +} + +// Exposed for testing. +int ParseProcStatCPU(const std::string& input) { + std::vector proc_stats; + if (!internal::ParseProcStats(input, &proc_stats)) + return -1; + + if (proc_stats.size() <= internal::VM_STIME) + return -1; + int utime = GetProcStatsFieldAsInt(proc_stats, internal::VM_UTIME); + int stime = GetProcStatsFieldAsInt(proc_stats, internal::VM_STIME); + return utime + stime; +} + +namespace { + +// The format of /proc/meminfo is: +// +// MemTotal: 8235324 kB +// MemFree: 1628304 kB +// Buffers: 429596 kB +// Cached: 4728232 kB +// ... +const size_t kMemTotalIndex = 1; +const size_t kMemFreeIndex = 4; +const size_t kMemBuffersIndex = 7; +const size_t kMemCachedIndex = 10; +const size_t kMemActiveAnonIndex = 22; +const size_t kMemInactiveAnonIndex = 25; +const size_t kMemActiveFileIndex = 28; +const size_t kMemInactiveFileIndex = 31; + +} // namespace + +SystemMemoryInfoKB::SystemMemoryInfoKB() + : total(0), + free(0), + buffers(0), + cached(0), + active_anon(0), + inactive_anon(0), + active_file(0), + inactive_file(0), + shmem(0), + gem_objects(-1), + gem_size(-1) { +} + +bool GetSystemMemoryInfo(SystemMemoryInfoKB* meminfo) { + // Synchronously reading files in /proc is safe. + ThreadRestrictions::ScopedAllowIO allow_io; + + // Used memory is: total - free - buffers - caches + FilePath meminfo_file("/proc/meminfo"); + std::string meminfo_data; + if (!file_util::ReadFileToString(meminfo_file, &meminfo_data)) { + DLOG(WARNING) << "Failed to open " << meminfo_file.value(); + return false; + } + std::vector meminfo_fields; + SplitStringAlongWhitespace(meminfo_data, &meminfo_fields); + + if (meminfo_fields.size() < kMemCachedIndex) { + DLOG(WARNING) << "Failed to parse " << meminfo_file.value() + << ". Only found " << meminfo_fields.size() << " fields."; + return false; + } + + DCHECK_EQ(meminfo_fields[kMemTotalIndex-1], "MemTotal:"); + DCHECK_EQ(meminfo_fields[kMemFreeIndex-1], "MemFree:"); + DCHECK_EQ(meminfo_fields[kMemBuffersIndex-1], "Buffers:"); + DCHECK_EQ(meminfo_fields[kMemCachedIndex-1], "Cached:"); + DCHECK_EQ(meminfo_fields[kMemActiveAnonIndex-1], "Active(anon):"); + DCHECK_EQ(meminfo_fields[kMemInactiveAnonIndex-1], "Inactive(anon):"); + DCHECK_EQ(meminfo_fields[kMemActiveFileIndex-1], "Active(file):"); + DCHECK_EQ(meminfo_fields[kMemInactiveFileIndex-1], "Inactive(file):"); + + StringToInt(meminfo_fields[kMemTotalIndex], &meminfo->total); + StringToInt(meminfo_fields[kMemFreeIndex], &meminfo->free); + StringToInt(meminfo_fields[kMemBuffersIndex], &meminfo->buffers); + StringToInt(meminfo_fields[kMemCachedIndex], &meminfo->cached); + StringToInt(meminfo_fields[kMemActiveAnonIndex], &meminfo->active_anon); + StringToInt(meminfo_fields[kMemInactiveAnonIndex], + &meminfo->inactive_anon); + StringToInt(meminfo_fields[kMemActiveFileIndex], &meminfo->active_file); + StringToInt(meminfo_fields[kMemInactiveFileIndex], + &meminfo->inactive_file); +#if defined(OS_CHROMEOS) + // Chrome OS has a tweaked kernel that allows us to query Shmem, which is + // usually video memory otherwise invisible to the OS. Unfortunately, the + // meminfo format varies on different hardware so we have to search for the + // string. It always appears after "Cached:". + for (size_t i = kMemCachedIndex+2; i < meminfo_fields.size(); i += 3) { + if (meminfo_fields[i] == "Shmem:") { + StringToInt(meminfo_fields[i+1], &meminfo->shmem); + break; + } + } + + // Report on Chrome OS GEM object graphics memory. /var/run/debugfs_gpu is a + // bind mount into /sys/kernel/debug and synchronously reading the in-memory + // files in /sys is fast. +#if defined(ARCH_CPU_ARM_FAMILY) + FilePath geminfo_file("/var/run/debugfs_gpu/exynos_gem_objects"); +#else + FilePath geminfo_file("/var/run/debugfs_gpu/i915_gem_objects"); +#endif + std::string geminfo_data; + meminfo->gem_objects = -1; + meminfo->gem_size = -1; + if (file_util::ReadFileToString(geminfo_file, &geminfo_data)) { + int gem_objects = -1; + long long gem_size = -1; + int num_res = sscanf(geminfo_data.c_str(), + "%d objects, %lld bytes", + &gem_objects, &gem_size); + if (num_res == 2) { + meminfo->gem_objects = gem_objects; + meminfo->gem_size = gem_size; + } + } + +#if defined(ARCH_CPU_ARM_FAMILY) + // Incorporate Mali graphics memory if present. + FilePath mali_memory_file("/sys/devices/platform/mali.0/memory"); + std::string mali_memory_data; + if (file_util::ReadFileToString(mali_memory_file, &mali_memory_data)) { + long long mali_size = -1; + int num_res = sscanf(mali_memory_data.c_str(), "%lld bytes", &mali_size); + if (num_res == 1) + meminfo->gem_size += mali_size; + } +#endif // defined(ARCH_CPU_ARM_FAMILY) +#endif // defined(OS_CHROMEOS) + + return true; +} + +const char kProcSelfExe[] = "/proc/self/exe"; + +int GetNumberOfThreads(ProcessHandle process) { + return internal::ReadProcStatsAndGetFieldAsInt(process, + internal::VM_NUMTHREADS); +} + +} // namespace base diff --git a/base/process/process_metrics_mac.cc b/base/process/process_metrics_mac.cc new file mode 100644 index 0000000000..048735ed36 --- /dev/null +++ b/base/process/process_metrics_mac.cc @@ -0,0 +1,325 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/process_metrics.h" + +#include +#include +#include +#include + +#include "base/containers/hash_tables.h" +#include "base/logging.h" +#include "base/mac/scoped_mach_port.h" +#include "base/sys_info.h" + +namespace base { + +namespace { + +bool GetTaskInfo(mach_port_t task, task_basic_info_64* task_info_data) { + if (task == MACH_PORT_NULL) + return false; + mach_msg_type_number_t count = TASK_BASIC_INFO_64_COUNT; + kern_return_t kr = task_info(task, + TASK_BASIC_INFO_64, + reinterpret_cast(task_info_data), + &count); + // Most likely cause for failure: |task| is a zombie. + return kr == KERN_SUCCESS; +} + +bool GetCPUTypeForProcess(pid_t pid, cpu_type_t* cpu_type) { + size_t len = sizeof(*cpu_type); + int result = sysctlbyname("sysctl.proc_cputype", + cpu_type, + &len, + NULL, + 0); + if (result != 0) { + DPLOG(ERROR) << "sysctlbyname(""sysctl.proc_cputype"")"; + return false; + } + + return true; +} + +bool IsAddressInSharedRegion(mach_vm_address_t addr, cpu_type_t type) { + if (type == CPU_TYPE_I386) { + return addr >= SHARED_REGION_BASE_I386 && + addr < (SHARED_REGION_BASE_I386 + SHARED_REGION_SIZE_I386); + } else if (type == CPU_TYPE_X86_64) { + return addr >= SHARED_REGION_BASE_X86_64 && + addr < (SHARED_REGION_BASE_X86_64 + SHARED_REGION_SIZE_X86_64); + } else { + return false; + } +} + +} // namespace + +// Getting a mach task from a pid for another process requires permissions in +// general, so there doesn't really seem to be a way to do these (and spinning +// up ps to fetch each stats seems dangerous to put in a base api for anyone to +// call). Child processes ipc their port, so return something if available, +// otherwise return 0. + +// static +ProcessMetrics* ProcessMetrics::CreateProcessMetrics( + ProcessHandle process, + ProcessMetrics::PortProvider* port_provider) { + return new ProcessMetrics(process, port_provider); +} + +size_t ProcessMetrics::GetPagefileUsage() const { + task_basic_info_64 task_info_data; + if (!GetTaskInfo(TaskForPid(process_), &task_info_data)) + return 0; + return task_info_data.virtual_size; +} + +size_t ProcessMetrics::GetPeakPagefileUsage() const { + return 0; +} + +size_t ProcessMetrics::GetWorkingSetSize() const { + task_basic_info_64 task_info_data; + if (!GetTaskInfo(TaskForPid(process_), &task_info_data)) + return 0; + return task_info_data.resident_size; +} + +size_t ProcessMetrics::GetPeakWorkingSetSize() const { + return 0; +} + +// This is a rough approximation of the algorithm that libtop uses. +// private_bytes is the size of private resident memory. +// shared_bytes is the size of shared resident memory. +bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes, + size_t* shared_bytes) { + kern_return_t kr; + size_t private_pages_count = 0; + size_t shared_pages_count = 0; + + if (!private_bytes && !shared_bytes) + return true; + + mach_port_t task = TaskForPid(process_); + if (task == MACH_PORT_NULL) { + DLOG(ERROR) << "Invalid process"; + return false; + } + + cpu_type_t cpu_type; + if (!GetCPUTypeForProcess(process_, &cpu_type)) + return false; + + // The same region can be referenced multiple times. To avoid double counting + // we need to keep track of which regions we've already counted. + base::hash_set seen_objects; + + // We iterate through each VM region in the task's address map. For shared + // memory we add up all the pages that are marked as shared. Like libtop we + // try to avoid counting pages that are also referenced by other tasks. Since + // we don't have access to the VM regions of other tasks the only hint we have + // is if the address is in the shared region area. + // + // Private memory is much simpler. We simply count the pages that are marked + // as private or copy on write (COW). + // + // See libtop_update_vm_regions in + // http://www.opensource.apple.com/source/top/top-67/libtop.c + mach_vm_size_t size = 0; + for (mach_vm_address_t address = MACH_VM_MIN_ADDRESS;; address += size) { + vm_region_top_info_data_t info; + mach_msg_type_number_t info_count = VM_REGION_TOP_INFO_COUNT; + mach_port_t object_name; + kr = mach_vm_region(task, + &address, + &size, + VM_REGION_TOP_INFO, + (vm_region_info_t)&info, + &info_count, + &object_name); + if (kr == KERN_INVALID_ADDRESS) { + // We're at the end of the address space. + break; + } else if (kr != KERN_SUCCESS) { + DLOG(ERROR) << "Calling mach_vm_region failed with error: " + << mach_error_string(kr); + return false; + } + + if (IsAddressInSharedRegion(address, cpu_type) && + info.share_mode != SM_PRIVATE) + continue; + + if (info.share_mode == SM_COW && info.ref_count == 1) + info.share_mode = SM_PRIVATE; + + switch (info.share_mode) { + case SM_PRIVATE: + private_pages_count += info.private_pages_resident; + private_pages_count += info.shared_pages_resident; + break; + case SM_COW: + private_pages_count += info.private_pages_resident; + // Fall through + case SM_SHARED: + if (seen_objects.count(info.obj_id) == 0) { + // Only count the first reference to this region. + seen_objects.insert(info.obj_id); + shared_pages_count += info.shared_pages_resident; + } + break; + default: + break; + } + } + + vm_size_t page_size; + kr = host_page_size(task, &page_size); + if (kr != KERN_SUCCESS) { + DLOG(ERROR) << "Failed to fetch host page size, error: " + << mach_error_string(kr); + return false; + } + + if (private_bytes) + *private_bytes = private_pages_count * page_size; + if (shared_bytes) + *shared_bytes = shared_pages_count * page_size; + + return true; +} + +void ProcessMetrics::GetCommittedKBytes(CommittedKBytes* usage) const { +} + +bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const { + size_t priv = GetWorkingSetSize(); + if (!priv) + return false; + ws_usage->priv = priv / 1024; + ws_usage->shareable = 0; + ws_usage->shared = 0; + return true; +} + +#define TIME_VALUE_TO_TIMEVAL(a, r) do { \ + (r)->tv_sec = (a)->seconds; \ + (r)->tv_usec = (a)->microseconds; \ +} while (0) + +double ProcessMetrics::GetCPUUsage() { + mach_port_t task = TaskForPid(process_); + if (task == MACH_PORT_NULL) + return 0; + + kern_return_t kr; + + // Libtop explicitly loops over the threads (libtop_pinfo_update_cpu_usage() + // in libtop.c), but this is more concise and gives the same results: + task_thread_times_info thread_info_data; + mach_msg_type_number_t thread_info_count = TASK_THREAD_TIMES_INFO_COUNT; + kr = task_info(task, + TASK_THREAD_TIMES_INFO, + reinterpret_cast(&thread_info_data), + &thread_info_count); + if (kr != KERN_SUCCESS) { + // Most likely cause: |task| is a zombie. + return 0; + } + + task_basic_info_64 task_info_data; + if (!GetTaskInfo(task, &task_info_data)) + return 0; + + /* Set total_time. */ + // thread info contains live time... + struct timeval user_timeval, system_timeval, task_timeval; + TIME_VALUE_TO_TIMEVAL(&thread_info_data.user_time, &user_timeval); + TIME_VALUE_TO_TIMEVAL(&thread_info_data.system_time, &system_timeval); + timeradd(&user_timeval, &system_timeval, &task_timeval); + + // ... task info contains terminated time. + TIME_VALUE_TO_TIMEVAL(&task_info_data.user_time, &user_timeval); + TIME_VALUE_TO_TIMEVAL(&task_info_data.system_time, &system_timeval); + timeradd(&user_timeval, &task_timeval, &task_timeval); + timeradd(&system_timeval, &task_timeval, &task_timeval); + + struct timeval now; + int retval = gettimeofday(&now, NULL); + if (retval) + return 0; + + int64 time = TimeValToMicroseconds(now); + int64 task_time = TimeValToMicroseconds(task_timeval); + + if ((last_system_time_ == 0) || (last_time_ == 0)) { + // First call, just set the last values. + last_system_time_ = task_time; + last_time_ = time; + return 0; + } + + int64 system_time_delta = task_time - last_system_time_; + int64 time_delta = time - last_time_; + DCHECK_NE(0U, time_delta); + if (time_delta == 0) + return 0; + + last_system_time_ = task_time; + last_time_ = time; + + return static_cast(system_time_delta * 100.0) / time_delta; +} + +bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const { + return false; +} + +ProcessMetrics::ProcessMetrics(ProcessHandle process, + ProcessMetrics::PortProvider* port_provider) + : process_(process), + last_time_(0), + last_system_time_(0), + port_provider_(port_provider) { + processor_count_ = SysInfo::NumberOfProcessors(); +} + +mach_port_t ProcessMetrics::TaskForPid(ProcessHandle process) const { + mach_port_t task = MACH_PORT_NULL; + if (port_provider_) + task = port_provider_->TaskForPid(process_); + if (task == MACH_PORT_NULL && process_ == getpid()) + task = mach_task_self(); + return task; +} + +// Bytes committed by the system. +size_t GetSystemCommitCharge() { + base::mac::ScopedMachPort host(mach_host_self()); + mach_msg_type_number_t count = HOST_VM_INFO_COUNT; + vm_statistics_data_t data; + kern_return_t kr = host_statistics(host, HOST_VM_INFO, + reinterpret_cast(&data), + &count); + if (kr) { + DLOG(WARNING) << "Failed to fetch host statistics."; + return 0; + } + + vm_size_t page_size; + kr = host_page_size(host, &page_size); + if (kr) { + DLOG(ERROR) << "Failed to fetch host page size."; + return 0; + } + + return (data.active_count * page_size) / 1024; +} + +} // namespace base diff --git a/base/process/process_metrics_openbsd.cc b/base/process/process_metrics_openbsd.cc new file mode 100644 index 0000000000..36f607c0a9 --- /dev/null +++ b/base/process/process_metrics_openbsd.cc @@ -0,0 +1,168 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/process_metrics.h" + +#include +#include + +namespace base { + +// static +ProcessMetrics* ProcessMetrics::CreateProcessMetrics(ProcessHandle process) { + return new ProcessMetrics(process); +} + +size_t ProcessMetrics::GetPagefileUsage() const { + struct kinfo_proc info; + size_t length; + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, process_, + sizeof(struct kinfo_proc), 0 }; + + if (sysctl(mib, arraysize(mib), NULL, &length, NULL, 0) < 0) + return -1; + + mib[5] = (length / sizeof(struct kinfo_proc)); + + if (sysctl(mib, arraysize(mib), &info, &length, NULL, 0) < 0) + return -1; + + return (info.p_vm_tsize + info.p_vm_dsize + info.p_vm_ssize); +} + +size_t ProcessMetrics::GetPeakPagefileUsage() const { + return 0; +} + +size_t ProcessMetrics::GetWorkingSetSize() const { + struct kinfo_proc info; + size_t length; + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, process_, + sizeof(struct kinfo_proc), 0 }; + + if (sysctl(mib, arraysize(mib), NULL, &length, NULL, 0) < 0) + return -1; + + mib[5] = (length / sizeof(struct kinfo_proc)); + + if (sysctl(mib, arraysize(mib), &info, &length, NULL, 0) < 0) + return -1; + + return info.p_vm_rssize * getpagesize(); +} + +size_t ProcessMetrics::GetPeakWorkingSetSize() const { + return 0; +} + +bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes, + size_t* shared_bytes) { + WorkingSetKBytes ws_usage; + + if (!GetWorkingSetKBytes(&ws_usage)) + return false; + + if (private_bytes) + *private_bytes = ws_usage.priv << 10; + + if (shared_bytes) + *shared_bytes = ws_usage.shared * 1024; + + return true; +} + +bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const { + // TODO(bapt): be sure we can't be precise + size_t priv = GetWorkingSetSize(); + if (!priv) + return false; + ws_usage->priv = priv / 1024; + ws_usage->shareable = 0; + ws_usage->shared = 0; + + return true; +} + +bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const { + return false; +} + +static int GetProcessCPU(pid_t pid) { + struct kinfo_proc info; + size_t length; + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid, + sizeof(struct kinfo_proc), 0 }; + + if (sysctl(mib, arraysize(mib), NULL, &length, NULL, 0) < 0) + return -1; + + mib[5] = (length / sizeof(struct kinfo_proc)); + + if (sysctl(mib, arraysize(mib), &info, &length, NULL, 0) < 0) + return 0; + + return info.p_pctcpu; +} + +double ProcessMetrics::GetCPUUsage() { + struct timeval now; + + int retval = gettimeofday(&now, NULL); + if (retval) + return 0; + + int64 time = TimeValToMicroseconds(now); + + if (last_time_ == 0) { + // First call, just set the last values. + last_time_ = time; + last_cpu_ = GetProcessCPU(process_); + return 0; + } + + int64 time_delta = time - last_time_; + DCHECK_NE(time_delta, 0); + + if (time_delta == 0) + return 0; + + int cpu = GetProcessCPU(process_); + + last_time_ = time; + last_cpu_ = cpu; + + double percentage = static_cast((cpu * 100.0) / FSCALE); + + return percentage; +} + +ProcessMetrics::ProcessMetrics(ProcessHandle process) + : process_(process), + last_time_(0), + last_system_time_(0), + last_cpu_(0) { + + processor_count_ = base::SysInfo::NumberOfProcessors(); +} + +size_t GetSystemCommitCharge() { + int mib[] = { CTL_VM, VM_METER }; + int pagesize; + struct vmtotal vmtotal; + unsigned long mem_total, mem_free, mem_inactive; + size_t len = sizeof(vmtotal); + + if (sysctl(mib, arraysize(mib), &vmtotal, &len, NULL, 0) < 0) + return 0; + + mem_total = vmtotal.t_vm; + mem_free = vmtotal.t_free; + mem_inactive = vmtotal.t_vm - vmtotal.t_avm; + + pagesize = getpagesize(); + + return mem_total - (mem_free*pagesize) - (mem_inactive*pagesize); +} + +} // namespace base diff --git a/base/process/process_metrics_posix.cc b/base/process/process_metrics_posix.cc new file mode 100644 index 0000000000..531f6a40d7 --- /dev/null +++ b/base/process/process_metrics_posix.cc @@ -0,0 +1,55 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/process_metrics.h" + +#include +#include + +#include "base/logging.h" + +namespace base { + +int64 TimeValToMicroseconds(const struct timeval& tv) { + static const int kMicrosecondsPerSecond = 1000000; + int64 ret = tv.tv_sec; // Avoid (int * int) integer overflow. + ret *= kMicrosecondsPerSecond; + ret += tv.tv_usec; + return ret; +} + +ProcessMetrics::~ProcessMetrics() { } + +#if defined(OS_LINUX) +static const rlim_t kSystemDefaultMaxFds = 8192; +#elif defined(OS_MACOSX) +static const rlim_t kSystemDefaultMaxFds = 256; +#elif defined(OS_SOLARIS) +static const rlim_t kSystemDefaultMaxFds = 8192; +#elif defined(OS_FREEBSD) +static const rlim_t kSystemDefaultMaxFds = 8192; +#elif defined(OS_OPENBSD) +static const rlim_t kSystemDefaultMaxFds = 256; +#elif defined(OS_ANDROID) +static const rlim_t kSystemDefaultMaxFds = 1024; +#endif + +size_t GetMaxFds() { + rlim_t max_fds; + struct rlimit nofile; + if (getrlimit(RLIMIT_NOFILE, &nofile)) { + // getrlimit failed. Take a best guess. + max_fds = kSystemDefaultMaxFds; + RAW_LOG(ERROR, "getrlimit(RLIMIT_NOFILE) failed"); + } else { + max_fds = nofile.rlim_cur; + } + + if (max_fds > INT_MAX) + max_fds = INT_MAX; + + return static_cast(max_fds); +} + +} // namespace base diff --git a/base/process/process_metrics_win.cc b/base/process/process_metrics_win.cc new file mode 100644 index 0000000000..f42ea86feb --- /dev/null +++ b/base/process/process_metrics_win.cc @@ -0,0 +1,315 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/process_metrics.h" + +#include +#include + +#include "base/logging.h" +#include "base/sys_info.h" + +namespace base { + +// System pagesize. This value remains constant on x86/64 architectures. +const int PAGESIZE_KB = 4; + +ProcessMetrics::~ProcessMetrics() { } + +// static +ProcessMetrics* ProcessMetrics::CreateProcessMetrics(ProcessHandle process) { + return new ProcessMetrics(process); +} + +size_t ProcessMetrics::GetPagefileUsage() const { + PROCESS_MEMORY_COUNTERS pmc; + if (GetProcessMemoryInfo(process_, &pmc, sizeof(pmc))) { + return pmc.PagefileUsage; + } + return 0; +} + +// Returns the peak space allocated for the pagefile, in bytes. +size_t ProcessMetrics::GetPeakPagefileUsage() const { + PROCESS_MEMORY_COUNTERS pmc; + if (GetProcessMemoryInfo(process_, &pmc, sizeof(pmc))) { + return pmc.PeakPagefileUsage; + } + return 0; +} + +// Returns the current working set size, in bytes. +size_t ProcessMetrics::GetWorkingSetSize() const { + PROCESS_MEMORY_COUNTERS pmc; + if (GetProcessMemoryInfo(process_, &pmc, sizeof(pmc))) { + return pmc.WorkingSetSize; + } + return 0; +} + +// Returns the peak working set size, in bytes. +size_t ProcessMetrics::GetPeakWorkingSetSize() const { + PROCESS_MEMORY_COUNTERS pmc; + if (GetProcessMemoryInfo(process_, &pmc, sizeof(pmc))) { + return pmc.PeakWorkingSetSize; + } + return 0; +} + +bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes, + size_t* shared_bytes) { + // PROCESS_MEMORY_COUNTERS_EX is not supported until XP SP2. + // GetProcessMemoryInfo() will simply fail on prior OS. So the requested + // information is simply not available. Hence, we will return 0 on unsupported + // OSes. Unlike most Win32 API, we don't need to initialize the "cb" member. + PROCESS_MEMORY_COUNTERS_EX pmcx; + if (private_bytes && + GetProcessMemoryInfo(process_, + reinterpret_cast(&pmcx), + sizeof(pmcx))) { + *private_bytes = pmcx.PrivateUsage; + } + + if (shared_bytes) { + WorkingSetKBytes ws_usage; + if (!GetWorkingSetKBytes(&ws_usage)) + return false; + + *shared_bytes = ws_usage.shared * 1024; + } + + return true; +} + +void ProcessMetrics::GetCommittedKBytes(CommittedKBytes* usage) const { + MEMORY_BASIC_INFORMATION mbi = {0}; + size_t committed_private = 0; + size_t committed_mapped = 0; + size_t committed_image = 0; + void* base_address = NULL; + while (VirtualQueryEx(process_, base_address, &mbi, sizeof(mbi)) == + sizeof(mbi)) { + if (mbi.State == MEM_COMMIT) { + if (mbi.Type == MEM_PRIVATE) { + committed_private += mbi.RegionSize; + } else if (mbi.Type == MEM_MAPPED) { + committed_mapped += mbi.RegionSize; + } else if (mbi.Type == MEM_IMAGE) { + committed_image += mbi.RegionSize; + } else { + NOTREACHED(); + } + } + void* new_base = (static_cast(mbi.BaseAddress)) + mbi.RegionSize; + // Avoid infinite loop by weird MEMORY_BASIC_INFORMATION. + // If we query 64bit processes in a 32bit process, VirtualQueryEx() + // returns such data. + if (new_base <= base_address) { + usage->image = 0; + usage->mapped = 0; + usage->priv = 0; + return; + } + base_address = new_base; + } + usage->image = committed_image / 1024; + usage->mapped = committed_mapped / 1024; + usage->priv = committed_private / 1024; +} + +bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const { + size_t ws_private = 0; + size_t ws_shareable = 0; + size_t ws_shared = 0; + + DCHECK(ws_usage); + memset(ws_usage, 0, sizeof(*ws_usage)); + + DWORD number_of_entries = 4096; // Just a guess. + PSAPI_WORKING_SET_INFORMATION* buffer = NULL; + int retries = 5; + for (;;) { + DWORD buffer_size = sizeof(PSAPI_WORKING_SET_INFORMATION) + + (number_of_entries * sizeof(PSAPI_WORKING_SET_BLOCK)); + + // if we can't expand the buffer, don't leak the previous + // contents or pass a NULL pointer to QueryWorkingSet + PSAPI_WORKING_SET_INFORMATION* new_buffer = + reinterpret_cast( + realloc(buffer, buffer_size)); + if (!new_buffer) { + free(buffer); + return false; + } + buffer = new_buffer; + + // Call the function once to get number of items + if (QueryWorkingSet(process_, buffer, buffer_size)) + break; // Success + + if (GetLastError() != ERROR_BAD_LENGTH) { + free(buffer); + return false; + } + + number_of_entries = static_cast(buffer->NumberOfEntries); + + // Maybe some entries are being added right now. Increase the buffer to + // take that into account. + number_of_entries = static_cast(number_of_entries * 1.25); + + if (--retries == 0) { + free(buffer); // If we're looping, eventually fail. + return false; + } + } + + // On windows 2000 the function returns 1 even when the buffer is too small. + // The number of entries that we are going to parse is the minimum between the + // size we allocated and the real number of entries. + number_of_entries = + std::min(number_of_entries, static_cast(buffer->NumberOfEntries)); + for (unsigned int i = 0; i < number_of_entries; i++) { + if (buffer->WorkingSetInfo[i].Shared) { + ws_shareable++; + if (buffer->WorkingSetInfo[i].ShareCount > 1) + ws_shared++; + } else { + ws_private++; + } + } + + ws_usage->priv = ws_private * PAGESIZE_KB; + ws_usage->shareable = ws_shareable * PAGESIZE_KB; + ws_usage->shared = ws_shared * PAGESIZE_KB; + free(buffer); + return true; +} + +static uint64 FileTimeToUTC(const FILETIME& ftime) { + LARGE_INTEGER li; + li.LowPart = ftime.dwLowDateTime; + li.HighPart = ftime.dwHighDateTime; + return li.QuadPart; +} + +double ProcessMetrics::GetCPUUsage() { + FILETIME now; + FILETIME creation_time; + FILETIME exit_time; + FILETIME kernel_time; + FILETIME user_time; + + GetSystemTimeAsFileTime(&now); + + if (!GetProcessTimes(process_, &creation_time, &exit_time, + &kernel_time, &user_time)) { + // We don't assert here because in some cases (such as in the Task Manager) + // we may call this function on a process that has just exited but we have + // not yet received the notification. + return 0; + } + int64 system_time = (FileTimeToUTC(kernel_time) + FileTimeToUTC(user_time)) / + processor_count_; + int64 time = FileTimeToUTC(now); + + if ((last_system_time_ == 0) || (last_time_ == 0)) { + // First call, just set the last values. + last_system_time_ = system_time; + last_time_ = time; + return 0; + } + + int64 system_time_delta = system_time - last_system_time_; + int64 time_delta = time - last_time_; + DCHECK_NE(0U, time_delta); + if (time_delta == 0) + return 0; + + // We add time_delta / 2 so the result is rounded. + int cpu = static_cast((system_time_delta * 100 + time_delta / 2) / + time_delta); + + last_system_time_ = system_time; + last_time_ = time; + + return cpu; +} + +bool ProcessMetrics::CalculateFreeMemory(FreeMBytes* free) const { + const SIZE_T kTopAddress = 0x7F000000; + const SIZE_T kMegabyte = 1024 * 1024; + SIZE_T accumulated = 0; + + MEMORY_BASIC_INFORMATION largest = {0}; + UINT_PTR scan = 0; + while (scan < kTopAddress) { + MEMORY_BASIC_INFORMATION info; + if (!::VirtualQueryEx(process_, reinterpret_cast(scan), + &info, sizeof(info))) + return false; + if (info.State == MEM_FREE) { + accumulated += info.RegionSize; + if (info.RegionSize > largest.RegionSize) + largest = info; + } + scan += info.RegionSize; + } + free->largest = largest.RegionSize / kMegabyte; + free->largest_ptr = largest.BaseAddress; + free->total = accumulated / kMegabyte; + return true; +} + +bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const { + return GetProcessIoCounters(process_, io_counters) != FALSE; +} + +ProcessMetrics::ProcessMetrics(ProcessHandle process) + : process_(process), + processor_count_(base::SysInfo::NumberOfProcessors()), + last_time_(0), + last_system_time_(0) { +} + +// GetPerformanceInfo is not available on WIN2K. So we'll +// load it on-the-fly. +const wchar_t kPsapiDllName[] = L"psapi.dll"; +typedef BOOL (WINAPI *GetPerformanceInfoFunction) ( + PPERFORMANCE_INFORMATION pPerformanceInformation, + DWORD cb); + +// Beware of races if called concurrently from multiple threads. +static BOOL InternalGetPerformanceInfo( + PPERFORMANCE_INFORMATION pPerformanceInformation, DWORD cb) { + static GetPerformanceInfoFunction GetPerformanceInfo_func = NULL; + if (!GetPerformanceInfo_func) { + HMODULE psapi_dll = ::GetModuleHandle(kPsapiDllName); + if (psapi_dll) + GetPerformanceInfo_func = reinterpret_cast( + GetProcAddress(psapi_dll, "GetPerformanceInfo")); + + if (!GetPerformanceInfo_func) { + // The function could be loaded! + memset(pPerformanceInformation, 0, cb); + return FALSE; + } + } + return GetPerformanceInfo_func(pPerformanceInformation, cb); +} + +size_t GetSystemCommitCharge() { + // Get the System Page Size. + SYSTEM_INFO system_info; + GetSystemInfo(&system_info); + + PERFORMANCE_INFORMATION info; + if (!InternalGetPerformanceInfo(&info, sizeof(info))) { + DLOG(ERROR) << "Failed to fetch internal performance info."; + return 0; + } + return (info.CommitTotal * system_info.dwPageSize) / 1024; +} + +} // namespace base diff --git a/base/process/process_posix.cc b/base/process/process_posix.cc new file mode 100644 index 0000000000..3d8b31d035 --- /dev/null +++ b/base/process/process_posix.cc @@ -0,0 +1,73 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/process.h" + +#include +#include +#include + +#include "base/logging.h" +#include "base/process/kill.h" + +namespace base { + +// static +Process Process::Current() { + return Process(GetCurrentProcessHandle()); +} + +ProcessId Process::pid() const { + if (process_ == 0) + return 0; + + return GetProcId(process_); +} + +bool Process::is_current() const { + return process_ == GetCurrentProcessHandle(); +} + +void Process::Close() { + process_ = 0; + // if the process wasn't terminated (so we waited) or the state + // wasn't already collected w/ a wait from process_utils, we're gonna + // end up w/ a zombie when it does finally exit. +} + +void Process::Terminate(int result_code) { + // result_code isn't supportable. + if (!process_) + return; + // We don't wait here. It's the responsibility of other code to reap the + // child. + KillProcess(process_, result_code, false); +} + +#if !defined(OS_LINUX) +bool Process::IsProcessBackgrounded() const { + // See SetProcessBackgrounded(). + return false; +} + +bool Process::SetProcessBackgrounded(bool value) { + // POSIX only allows lowering the priority of a process, so if we + // were to lower it we wouldn't be able to raise it back to its initial + // priority. + return false; +} + +// static +bool Process::CanBackgroundProcesses() { + return false; +} + +#endif + +int Process::GetPriority() const { + DCHECK(process_); + return getpriority(PRIO_PROCESS, process_); +} + +} // namspace base diff --git a/base/process/process_util_unittest.cc b/base/process/process_util_unittest.cc new file mode 100644 index 0000000000..77f058c8f2 --- /dev/null +++ b/base/process/process_util_unittest.cc @@ -0,0 +1,961 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#define _CRT_SECURE_NO_WARNINGS + +#include + +#include "base/command_line.h" +#include "base/debug/alias.h" +#include "base/debug/stack_trace.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/path_service.h" +#include "base/posix/eintr_wrapper.h" +#include "base/process/kill.h" +#include "base/process/launch.h" +#include "base/process/memory.h" +#include "base/process/process.h" +#include "base/process/process_metrics.h" +#include "base/strings/utf_string_conversions.h" +#include "base/test/multiprocess_test.h" +#include "base/test/test_timeouts.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" +#include "base/threading/platform_thread.h" +#include "base/threading/thread.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/multiprocess_func_list.h" + +#if defined(OS_LINUX) +#include +#include +#include +#endif +#if defined(OS_POSIX) +#include +#include +#include +#include +#include +#include +#include +#endif +#if defined(OS_WIN) +#include +#endif +#if defined(OS_MACOSX) +#include +#include +#endif + +using base::FilePath; + +namespace { + +#if defined(OS_WIN) +const wchar_t kProcessName[] = L"base_unittests.exe"; +#else +const wchar_t kProcessName[] = L"base_unittests"; +#endif // defined(OS_WIN) + +#if defined(OS_ANDROID) +const char kShellPath[] = "/system/bin/sh"; +const char kPosixShell[] = "sh"; +#else +const char kShellPath[] = "/bin/sh"; +const char kPosixShell[] = "bash"; +#endif + +const char kSignalFileSlow[] = "SlowChildProcess.die"; +const char kSignalFileCrash[] = "CrashingChildProcess.die"; +const char kSignalFileKill[] = "KilledChildProcess.die"; + +#if defined(OS_WIN) +const int kExpectedStillRunningExitCode = 0x102; +const int kExpectedKilledExitCode = 1; +#else +const int kExpectedStillRunningExitCode = 0; +#endif + +// Sleeps until file filename is created. +void WaitToDie(const char* filename) { + FILE* fp; + do { + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10)); + fp = fopen(filename, "r"); + } while (!fp); + fclose(fp); +} + +// Signals children they should die now. +void SignalChildren(const char* filename) { + FILE* fp = fopen(filename, "w"); + fclose(fp); +} + +// Using a pipe to the child to wait for an event was considered, but +// there were cases in the past where pipes caused problems (other +// libraries closing the fds, child deadlocking). This is a simple +// case, so it's not worth the risk. Using wait loops is discouraged +// in most instances. +base::TerminationStatus WaitForChildTermination(base::ProcessHandle handle, + int* exit_code) { + // Now we wait until the result is something other than STILL_RUNNING. + base::TerminationStatus status = base::TERMINATION_STATUS_STILL_RUNNING; + const base::TimeDelta kInterval = base::TimeDelta::FromMilliseconds(20); + base::TimeDelta waited; + do { + status = base::GetTerminationStatus(handle, exit_code); + base::PlatformThread::Sleep(kInterval); + waited += kInterval; + } while (status == base::TERMINATION_STATUS_STILL_RUNNING && +// Waiting for more time for process termination on android devices. +#if defined(OS_ANDROID) + waited < TestTimeouts::large_test_timeout()); +#else + waited < TestTimeouts::action_max_timeout()); +#endif + + return status; +} + +} // namespace + +class ProcessUtilTest : public base::MultiProcessTest { + public: +#if defined(OS_POSIX) + // Spawn a child process that counts how many file descriptors are open. + int CountOpenFDsInChild(); +#endif + // Converts the filename to a platform specific filepath. + // On Android files can not be created in arbitrary directories. + static std::string GetSignalFilePath(const char* filename); +}; + +std::string ProcessUtilTest::GetSignalFilePath(const char* filename) { +#if !defined(OS_ANDROID) + return filename; +#else + FilePath tmp_dir; + PathService::Get(base::DIR_CACHE, &tmp_dir); + tmp_dir = tmp_dir.Append(filename); + return tmp_dir.value(); +#endif +} + +MULTIPROCESS_TEST_MAIN(SimpleChildProcess) { + return 0; +} + +TEST_F(ProcessUtilTest, SpawnChild) { + base::ProcessHandle handle = this->SpawnChild("SimpleChildProcess", false); + ASSERT_NE(base::kNullProcessHandle, handle); + EXPECT_TRUE(base::WaitForSingleProcess( + handle, TestTimeouts::action_max_timeout())); + base::CloseProcessHandle(handle); +} + +MULTIPROCESS_TEST_MAIN(SlowChildProcess) { + WaitToDie(ProcessUtilTest::GetSignalFilePath(kSignalFileSlow).c_str()); + return 0; +} + +TEST_F(ProcessUtilTest, KillSlowChild) { + const std::string signal_file = + ProcessUtilTest::GetSignalFilePath(kSignalFileSlow); + remove(signal_file.c_str()); + base::ProcessHandle handle = this->SpawnChild("SlowChildProcess", false); + ASSERT_NE(base::kNullProcessHandle, handle); + SignalChildren(signal_file.c_str()); + EXPECT_TRUE(base::WaitForSingleProcess( + handle, TestTimeouts::action_max_timeout())); + base::CloseProcessHandle(handle); + remove(signal_file.c_str()); +} + +// Times out on Linux and Win, flakes on other platforms, http://crbug.com/95058 +TEST_F(ProcessUtilTest, DISABLED_GetTerminationStatusExit) { + const std::string signal_file = + ProcessUtilTest::GetSignalFilePath(kSignalFileSlow); + remove(signal_file.c_str()); + base::ProcessHandle handle = this->SpawnChild("SlowChildProcess", false); + ASSERT_NE(base::kNullProcessHandle, handle); + + int exit_code = 42; + EXPECT_EQ(base::TERMINATION_STATUS_STILL_RUNNING, + base::GetTerminationStatus(handle, &exit_code)); + EXPECT_EQ(kExpectedStillRunningExitCode, exit_code); + + SignalChildren(signal_file.c_str()); + exit_code = 42; + base::TerminationStatus status = + WaitForChildTermination(handle, &exit_code); + EXPECT_EQ(base::TERMINATION_STATUS_NORMAL_TERMINATION, status); + EXPECT_EQ(0, exit_code); + base::CloseProcessHandle(handle); + remove(signal_file.c_str()); +} + +#if defined(OS_WIN) +// TODO(cpu): figure out how to test this in other platforms. +TEST_F(ProcessUtilTest, GetProcId) { + base::ProcessId id1 = base::GetProcId(GetCurrentProcess()); + EXPECT_NE(0ul, id1); + base::ProcessHandle handle = this->SpawnChild("SimpleChildProcess", false); + ASSERT_NE(base::kNullProcessHandle, handle); + base::ProcessId id2 = base::GetProcId(handle); + EXPECT_NE(0ul, id2); + EXPECT_NE(id1, id2); + base::CloseProcessHandle(handle); +} +#endif + +#if !defined(OS_MACOSX) +// This test is disabled on Mac, since it's flaky due to ReportCrash +// taking a variable amount of time to parse and load the debug and +// symbol data for this unit test's executable before firing the +// signal handler. +// +// TODO(gspencer): turn this test process into a very small program +// with no symbols (instead of using the multiprocess testing +// framework) to reduce the ReportCrash overhead. + +MULTIPROCESS_TEST_MAIN(CrashingChildProcess) { + WaitToDie(ProcessUtilTest::GetSignalFilePath(kSignalFileCrash).c_str()); +#if defined(OS_POSIX) + // Have to disable to signal handler for segv so we can get a crash + // instead of an abnormal termination through the crash dump handler. + ::signal(SIGSEGV, SIG_DFL); +#endif + // Make this process have a segmentation fault. + volatile int* oops = NULL; + *oops = 0xDEAD; + return 1; +} + +// This test intentionally crashes, so we don't need to run it under +// AddressSanitizer. +// TODO(jschuh): crbug.com/175753 Fix this in Win64 bots. +#if defined(ADDRESS_SANITIZER) || (defined(OS_WIN) && defined(ARCH_CPU_X86_64)) +#define MAYBE_GetTerminationStatusCrash DISABLED_GetTerminationStatusCrash +#else +#define MAYBE_GetTerminationStatusCrash GetTerminationStatusCrash +#endif +TEST_F(ProcessUtilTest, MAYBE_GetTerminationStatusCrash) { + const std::string signal_file = + ProcessUtilTest::GetSignalFilePath(kSignalFileCrash); + remove(signal_file.c_str()); + base::ProcessHandle handle = this->SpawnChild("CrashingChildProcess", + false); + ASSERT_NE(base::kNullProcessHandle, handle); + + int exit_code = 42; + EXPECT_EQ(base::TERMINATION_STATUS_STILL_RUNNING, + base::GetTerminationStatus(handle, &exit_code)); + EXPECT_EQ(kExpectedStillRunningExitCode, exit_code); + + SignalChildren(signal_file.c_str()); + exit_code = 42; + base::TerminationStatus status = + WaitForChildTermination(handle, &exit_code); + EXPECT_EQ(base::TERMINATION_STATUS_PROCESS_CRASHED, status); + +#if defined(OS_WIN) + EXPECT_EQ(0xc0000005, exit_code); +#elif defined(OS_POSIX) + int signaled = WIFSIGNALED(exit_code); + EXPECT_NE(0, signaled); + int signal = WTERMSIG(exit_code); + EXPECT_EQ(SIGSEGV, signal); +#endif + base::CloseProcessHandle(handle); + + // Reset signal handlers back to "normal". + base::debug::EnableInProcessStackDumping(); + remove(signal_file.c_str()); +} +#endif // !defined(OS_MACOSX) + +MULTIPROCESS_TEST_MAIN(KilledChildProcess) { + WaitToDie(ProcessUtilTest::GetSignalFilePath(kSignalFileKill).c_str()); +#if defined(OS_WIN) + // Kill ourselves. + HANDLE handle = ::OpenProcess(PROCESS_ALL_ACCESS, 0, ::GetCurrentProcessId()); + ::TerminateProcess(handle, kExpectedKilledExitCode); +#elif defined(OS_POSIX) + // Send a SIGKILL to this process, just like the OOM killer would. + ::kill(getpid(), SIGKILL); +#endif + return 1; +} + +TEST_F(ProcessUtilTest, GetTerminationStatusKill) { + const std::string signal_file = + ProcessUtilTest::GetSignalFilePath(kSignalFileKill); + remove(signal_file.c_str()); + base::ProcessHandle handle = this->SpawnChild("KilledChildProcess", + false); + ASSERT_NE(base::kNullProcessHandle, handle); + + int exit_code = 42; + EXPECT_EQ(base::TERMINATION_STATUS_STILL_RUNNING, + base::GetTerminationStatus(handle, &exit_code)); + EXPECT_EQ(kExpectedStillRunningExitCode, exit_code); + + SignalChildren(signal_file.c_str()); + exit_code = 42; + base::TerminationStatus status = + WaitForChildTermination(handle, &exit_code); + EXPECT_EQ(base::TERMINATION_STATUS_PROCESS_WAS_KILLED, status); +#if defined(OS_WIN) + EXPECT_EQ(kExpectedKilledExitCode, exit_code); +#elif defined(OS_POSIX) + int signaled = WIFSIGNALED(exit_code); + EXPECT_NE(0, signaled); + int signal = WTERMSIG(exit_code); + EXPECT_EQ(SIGKILL, signal); +#endif + base::CloseProcessHandle(handle); + remove(signal_file.c_str()); +} + +// Ensure that the priority of a process is restored correctly after +// backgrounding and restoring. +// Note: a platform may not be willing or able to lower the priority of +// a process. The calls to SetProcessBackground should be noops then. +TEST_F(ProcessUtilTest, SetProcessBackgrounded) { + base::ProcessHandle handle = this->SpawnChild("SimpleChildProcess", false); + base::Process process(handle); + int old_priority = process.GetPriority(); +#if defined(OS_WIN) + EXPECT_TRUE(process.SetProcessBackgrounded(true)); + EXPECT_TRUE(process.IsProcessBackgrounded()); + EXPECT_TRUE(process.SetProcessBackgrounded(false)); + EXPECT_FALSE(process.IsProcessBackgrounded()); +#else + process.SetProcessBackgrounded(true); + process.SetProcessBackgrounded(false); +#endif + int new_priority = process.GetPriority(); + EXPECT_EQ(old_priority, new_priority); +} + +// Same as SetProcessBackgrounded but to this very process. It uses +// a different code path at least for Windows. +TEST_F(ProcessUtilTest, SetProcessBackgroundedSelf) { + base::Process process(base::Process::Current().handle()); + int old_priority = process.GetPriority(); +#if defined(OS_WIN) + EXPECT_TRUE(process.SetProcessBackgrounded(true)); + EXPECT_TRUE(process.IsProcessBackgrounded()); + EXPECT_TRUE(process.SetProcessBackgrounded(false)); + EXPECT_FALSE(process.IsProcessBackgrounded()); +#else + process.SetProcessBackgrounded(true); + process.SetProcessBackgrounded(false); +#endif + int new_priority = process.GetPriority(); + EXPECT_EQ(old_priority, new_priority); +} + +#if defined(OS_LINUX) || defined(OS_ANDROID) +TEST_F(ProcessUtilTest, GetSystemMemoryInfo) { + base::SystemMemoryInfoKB info; + EXPECT_TRUE(base::GetSystemMemoryInfo(&info)); + + // Ensure each field received a value. + EXPECT_GT(info.total, 0); + EXPECT_GT(info.free, 0); + EXPECT_GT(info.buffers, 0); + EXPECT_GT(info.cached, 0); + EXPECT_GT(info.active_anon, 0); + EXPECT_GT(info.inactive_anon, 0); + EXPECT_GT(info.active_file, 0); + EXPECT_GT(info.inactive_file, 0); + + // All the values should be less than the total amount of memory. + EXPECT_LT(info.free, info.total); + EXPECT_LT(info.buffers, info.total); + EXPECT_LT(info.cached, info.total); + EXPECT_LT(info.active_anon, info.total); + EXPECT_LT(info.inactive_anon, info.total); + EXPECT_LT(info.active_file, info.total); + EXPECT_LT(info.inactive_file, info.total); + +#if defined(OS_CHROMEOS) + // Chrome OS exposes shmem. + EXPECT_GT(info.shmem, 0); + EXPECT_LT(info.shmem, info.total); + // Chrome unit tests are not run on actual Chrome OS hardware, so gem_objects + // and gem_size cannot be tested here. +#endif +} +#endif // defined(OS_LINUX) || defined(OS_ANDROID) + +// TODO(estade): if possible, port these 2 tests. +#if defined(OS_WIN) +TEST_F(ProcessUtilTest, CalcFreeMemory) { + scoped_ptr metrics( + base::ProcessMetrics::CreateProcessMetrics(::GetCurrentProcess())); + ASSERT_TRUE(NULL != metrics.get()); + + bool using_tcmalloc = false; + + // Detect if we are using tcmalloc +#if !defined(NO_TCMALLOC) + const char* chrome_allocator = getenv("CHROME_ALLOCATOR"); + if (!chrome_allocator || _stricmp(chrome_allocator, "tcmalloc") == 0) + using_tcmalloc = true; +#endif + + // Typical values here is ~1900 for total and ~1000 for largest. Obviously + // it depends in what other tests have done to this process. + base::FreeMBytes free_mem1 = {0}; + EXPECT_TRUE(metrics->CalculateFreeMemory(&free_mem1)); + EXPECT_LT(10u, free_mem1.total); + EXPECT_LT(10u, free_mem1.largest); + EXPECT_GT(2048u, free_mem1.total); + EXPECT_GT(2048u, free_mem1.largest); + EXPECT_GE(free_mem1.total, free_mem1.largest); + EXPECT_TRUE(NULL != free_mem1.largest_ptr); + + // Allocate 20M and check again. It should have gone down. + const int kAllocMB = 20; + scoped_ptr alloc(new char[kAllocMB * 1024 * 1024]); + size_t expected_total = free_mem1.total - kAllocMB; + size_t expected_largest = free_mem1.largest; + + base::FreeMBytes free_mem2 = {0}; + EXPECT_TRUE(metrics->CalculateFreeMemory(&free_mem2)); + EXPECT_GE(free_mem2.total, free_mem2.largest); + // This test is flaky when using tcmalloc, because tcmalloc + // allocation strategy sometimes results in less than the + // full drop of 20Mb of free memory. + if (!using_tcmalloc) + EXPECT_GE(expected_total, free_mem2.total); + EXPECT_GE(expected_largest, free_mem2.largest); + EXPECT_TRUE(NULL != free_mem2.largest_ptr); +} + +TEST_F(ProcessUtilTest, GetAppOutput) { + // Let's create a decently long message. + std::string message; + for (int i = 0; i < 1025; i++) { // 1025 so it does not end on a kilo-byte + // boundary. + message += "Hello!"; + } + // cmd.exe's echo always adds a \r\n to its output. + std::string expected(message); + expected += "\r\n"; + + FilePath cmd(L"cmd.exe"); + CommandLine cmd_line(cmd); + cmd_line.AppendArg("/c"); + cmd_line.AppendArg("echo " + message + ""); + std::string output; + ASSERT_TRUE(base::GetAppOutput(cmd_line, &output)); + EXPECT_EQ(expected, output); + + // Let's make sure stderr is ignored. + CommandLine other_cmd_line(cmd); + other_cmd_line.AppendArg("/c"); + // http://msdn.microsoft.com/library/cc772622.aspx + cmd_line.AppendArg("echo " + message + " >&2"); + output.clear(); + ASSERT_TRUE(base::GetAppOutput(other_cmd_line, &output)); + EXPECT_EQ("", output); +} + +TEST_F(ProcessUtilTest, LaunchAsUser) { + base::UserTokenHandle token; + ASSERT_TRUE(OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &token)); + std::wstring cmdline = + this->MakeCmdLine("SimpleChildProcess", false).GetCommandLineString(); + base::LaunchOptions options; + options.as_user = token; + EXPECT_TRUE(base::LaunchProcess(cmdline, options, NULL)); +} + +#endif // defined(OS_WIN) + +#if defined(OS_POSIX) + +namespace { + +// Returns the maximum number of files that a process can have open. +// Returns 0 on error. +int GetMaxFilesOpenInProcess() { + struct rlimit rlim; + if (getrlimit(RLIMIT_NOFILE, &rlim) != 0) { + return 0; + } + + // rlim_t is a uint64 - clip to maxint. We do this since FD #s are ints + // which are all 32 bits on the supported platforms. + rlim_t max_int = static_cast(std::numeric_limits::max()); + if (rlim.rlim_cur > max_int) { + return max_int; + } + + return rlim.rlim_cur; +} + +const int kChildPipe = 20; // FD # for write end of pipe in child process. + +} // namespace + +MULTIPROCESS_TEST_MAIN(ProcessUtilsLeakFDChildProcess) { + // This child process counts the number of open FDs, it then writes that + // number out to a pipe connected to the parent. + int num_open_files = 0; + int write_pipe = kChildPipe; + int max_files = GetMaxFilesOpenInProcess(); + for (int i = STDERR_FILENO + 1; i < max_files; i++) { + if (i != kChildPipe) { + int fd; + if ((fd = HANDLE_EINTR(dup(i))) != -1) { + close(fd); + num_open_files += 1; + } + } + } + + int written = HANDLE_EINTR(write(write_pipe, &num_open_files, + sizeof(num_open_files))); + DCHECK_EQ(static_cast(written), sizeof(num_open_files)); + int ret = HANDLE_EINTR(close(write_pipe)); + DPCHECK(ret == 0); + + return 0; +} + +int ProcessUtilTest::CountOpenFDsInChild() { + int fds[2]; + if (pipe(fds) < 0) + NOTREACHED(); + + base::FileHandleMappingVector fd_mapping_vec; + fd_mapping_vec.push_back(std::pair(fds[1], kChildPipe)); + base::ProcessHandle handle = this->SpawnChild( + "ProcessUtilsLeakFDChildProcess", fd_mapping_vec, false); + CHECK(handle); + int ret = HANDLE_EINTR(close(fds[1])); + DPCHECK(ret == 0); + + // Read number of open files in client process from pipe; + int num_open_files = -1; + ssize_t bytes_read = + HANDLE_EINTR(read(fds[0], &num_open_files, sizeof(num_open_files))); + CHECK_EQ(bytes_read, static_cast(sizeof(num_open_files))); + +#if defined(THREAD_SANITIZER) || defined(USE_HEAPCHECKER) + // Compiler-based ThreadSanitizer makes this test slow. + CHECK(base::WaitForSingleProcess(handle, base::TimeDelta::FromSeconds(3))); +#else + CHECK(base::WaitForSingleProcess(handle, base::TimeDelta::FromSeconds(1))); +#endif + base::CloseProcessHandle(handle); + ret = HANDLE_EINTR(close(fds[0])); + DPCHECK(ret == 0); + + return num_open_files; +} + +#if defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER) +// ProcessUtilTest.FDRemapping is flaky when ran under xvfb-run on Precise. +// The problem is 100% reproducible with both ASan and TSan. +// See http://crbug.com/136720. +#define MAYBE_FDRemapping DISABLED_FDRemapping +#else +#define MAYBE_FDRemapping FDRemapping +#endif +TEST_F(ProcessUtilTest, MAYBE_FDRemapping) { + int fds_before = CountOpenFDsInChild(); + + // open some dummy fds to make sure they don't propagate over to the + // child process. + int dev_null = open("/dev/null", O_RDONLY); + int sockets[2]; + socketpair(AF_UNIX, SOCK_STREAM, 0, sockets); + + int fds_after = CountOpenFDsInChild(); + + ASSERT_EQ(fds_after, fds_before); + + int ret; + ret = HANDLE_EINTR(close(sockets[0])); + DPCHECK(ret == 0); + ret = HANDLE_EINTR(close(sockets[1])); + DPCHECK(ret == 0); + ret = HANDLE_EINTR(close(dev_null)); + DPCHECK(ret == 0); +} + +namespace { + +std::string TestLaunchProcess(const base::EnvironmentVector& env_changes, + const int clone_flags) { + std::vector args; + base::FileHandleMappingVector fds_to_remap; + + args.push_back(kPosixShell); + args.push_back("-c"); + args.push_back("echo $BASE_TEST"); + + int fds[2]; + PCHECK(pipe(fds) == 0); + + fds_to_remap.push_back(std::make_pair(fds[1], 1)); + base::LaunchOptions options; + options.wait = true; + options.environ = &env_changes; + options.fds_to_remap = &fds_to_remap; +#if defined(OS_LINUX) + options.clone_flags = clone_flags; +#else + CHECK_EQ(0, clone_flags); +#endif // OS_LINUX + EXPECT_TRUE(base::LaunchProcess(args, options, NULL)); + PCHECK(HANDLE_EINTR(close(fds[1])) == 0); + + char buf[512]; + const ssize_t n = HANDLE_EINTR(read(fds[0], buf, sizeof(buf))); + PCHECK(n > 0); + + PCHECK(HANDLE_EINTR(close(fds[0])) == 0); + + return std::string(buf, n); +} + +const char kLargeString[] = + "0123456789012345678901234567890123456789012345678901234567890123456789" + "0123456789012345678901234567890123456789012345678901234567890123456789" + "0123456789012345678901234567890123456789012345678901234567890123456789" + "0123456789012345678901234567890123456789012345678901234567890123456789" + "0123456789012345678901234567890123456789012345678901234567890123456789" + "0123456789012345678901234567890123456789012345678901234567890123456789" + "0123456789012345678901234567890123456789012345678901234567890123456789"; + +} // namespace + +TEST_F(ProcessUtilTest, LaunchProcess) { + base::EnvironmentVector env_changes; + const int no_clone_flags = 0; + + env_changes.push_back(std::make_pair(std::string("BASE_TEST"), + std::string("bar"))); + EXPECT_EQ("bar\n", TestLaunchProcess(env_changes, no_clone_flags)); + env_changes.clear(); + + EXPECT_EQ(0, setenv("BASE_TEST", "testing", 1 /* override */)); + EXPECT_EQ("testing\n", TestLaunchProcess(env_changes, no_clone_flags)); + + env_changes.push_back( + std::make_pair(std::string("BASE_TEST"), std::string())); + EXPECT_EQ("\n", TestLaunchProcess(env_changes, no_clone_flags)); + + env_changes[0].second = "foo"; + EXPECT_EQ("foo\n", TestLaunchProcess(env_changes, no_clone_flags)); + + env_changes.clear(); + EXPECT_EQ(0, setenv("BASE_TEST", kLargeString, 1 /* override */)); + EXPECT_EQ(std::string(kLargeString) + "\n", + TestLaunchProcess(env_changes, no_clone_flags)); + + env_changes.push_back(std::make_pair(std::string("BASE_TEST"), + std::string("wibble"))); + EXPECT_EQ("wibble\n", TestLaunchProcess(env_changes, no_clone_flags)); + +#if defined(OS_LINUX) + // Test a non-trival value for clone_flags. + // Don't test on Valgrind as it has limited support for clone(). + if (!RunningOnValgrind()) { + EXPECT_EQ("wibble\n", TestLaunchProcess(env_changes, CLONE_FS | SIGCHLD)); + } +#endif +} + +TEST_F(ProcessUtilTest, AlterEnvironment) { + const char* const empty[] = { NULL }; + const char* const a2[] = { "A=2", NULL }; + base::EnvironmentVector changes; + char** e; + + e = base::AlterEnvironment(changes, empty); + EXPECT_TRUE(e[0] == NULL); + delete[] e; + + changes.push_back(std::make_pair(std::string("A"), std::string("1"))); + e = base::AlterEnvironment(changes, empty); + EXPECT_EQ(std::string("A=1"), e[0]); + EXPECT_TRUE(e[1] == NULL); + delete[] e; + + changes.clear(); + changes.push_back(std::make_pair(std::string("A"), std::string())); + e = base::AlterEnvironment(changes, empty); + EXPECT_TRUE(e[0] == NULL); + delete[] e; + + changes.clear(); + e = base::AlterEnvironment(changes, a2); + EXPECT_EQ(std::string("A=2"), e[0]); + EXPECT_TRUE(e[1] == NULL); + delete[] e; + + changes.clear(); + changes.push_back(std::make_pair(std::string("A"), std::string("1"))); + e = base::AlterEnvironment(changes, a2); + EXPECT_EQ(std::string("A=1"), e[0]); + EXPECT_TRUE(e[1] == NULL); + delete[] e; + + changes.clear(); + changes.push_back(std::make_pair(std::string("A"), std::string())); + e = base::AlterEnvironment(changes, a2); + EXPECT_TRUE(e[0] == NULL); + delete[] e; +} + +TEST_F(ProcessUtilTest, GetAppOutput) { + std::string output; + +#if defined(OS_ANDROID) + std::vector argv; + argv.push_back("sh"); // Instead of /bin/sh, force path search to find it. + argv.push_back("-c"); + + argv.push_back("exit 0"); + EXPECT_TRUE(base::GetAppOutput(CommandLine(argv), &output)); + EXPECT_STREQ("", output.c_str()); + + argv[2] = "exit 1"; + EXPECT_FALSE(base::GetAppOutput(CommandLine(argv), &output)); + EXPECT_STREQ("", output.c_str()); + + argv[2] = "echo foobar42"; + EXPECT_TRUE(base::GetAppOutput(CommandLine(argv), &output)); + EXPECT_STREQ("foobar42\n", output.c_str()); +#else + EXPECT_TRUE(base::GetAppOutput(CommandLine(FilePath("true")), &output)); + EXPECT_STREQ("", output.c_str()); + + EXPECT_FALSE(base::GetAppOutput(CommandLine(FilePath("false")), &output)); + + std::vector argv; + argv.push_back("/bin/echo"); + argv.push_back("-n"); + argv.push_back("foobar42"); + EXPECT_TRUE(base::GetAppOutput(CommandLine(argv), &output)); + EXPECT_STREQ("foobar42", output.c_str()); +#endif // defined(OS_ANDROID) +} + +TEST_F(ProcessUtilTest, GetAppOutputRestricted) { + // Unfortunately, since we can't rely on the path, we need to know where + // everything is. So let's use /bin/sh, which is on every POSIX system, and + // its built-ins. + std::vector argv; + argv.push_back(std::string(kShellPath)); // argv[0] + argv.push_back("-c"); // argv[1] + + // On success, should set |output|. We use |/bin/sh -c 'exit 0'| instead of + // |true| since the location of the latter may be |/bin| or |/usr/bin| (and we + // need absolute paths). + argv.push_back("exit 0"); // argv[2]; equivalent to "true" + std::string output = "abc"; + EXPECT_TRUE(base::GetAppOutputRestricted(CommandLine(argv), &output, 100)); + EXPECT_STREQ("", output.c_str()); + + argv[2] = "exit 1"; // equivalent to "false" + output = "before"; + EXPECT_FALSE(base::GetAppOutputRestricted(CommandLine(argv), + &output, 100)); + EXPECT_STREQ("", output.c_str()); + + // Amount of output exactly equal to space allowed. + argv[2] = "echo 123456789"; // (the sh built-in doesn't take "-n") + output.clear(); + EXPECT_TRUE(base::GetAppOutputRestricted(CommandLine(argv), &output, 10)); + EXPECT_STREQ("123456789\n", output.c_str()); + + // Amount of output greater than space allowed. + output.clear(); + EXPECT_TRUE(base::GetAppOutputRestricted(CommandLine(argv), &output, 5)); + EXPECT_STREQ("12345", output.c_str()); + + // Amount of output less than space allowed. + output.clear(); + EXPECT_TRUE(base::GetAppOutputRestricted(CommandLine(argv), &output, 15)); + EXPECT_STREQ("123456789\n", output.c_str()); + + // Zero space allowed. + output = "abc"; + EXPECT_TRUE(base::GetAppOutputRestricted(CommandLine(argv), &output, 0)); + EXPECT_STREQ("", output.c_str()); +} + +#if !defined(OS_MACOSX) && !defined(OS_OPENBSD) +// TODO(benwells): GetAppOutputRestricted should terminate applications +// with SIGPIPE when we have enough output. http://crbug.com/88502 +TEST_F(ProcessUtilTest, GetAppOutputRestrictedSIGPIPE) { + std::vector argv; + std::string output; + + argv.push_back(std::string(kShellPath)); // argv[0] + argv.push_back("-c"); +#if defined(OS_ANDROID) + argv.push_back("while echo 12345678901234567890; do :; done"); + EXPECT_TRUE(base::GetAppOutputRestricted(CommandLine(argv), &output, 10)); + EXPECT_STREQ("1234567890", output.c_str()); +#else + argv.push_back("yes"); + EXPECT_TRUE(base::GetAppOutputRestricted(CommandLine(argv), &output, 10)); + EXPECT_STREQ("y\ny\ny\ny\ny\n", output.c_str()); +#endif +} +#endif + +TEST_F(ProcessUtilTest, GetAppOutputRestrictedNoZombies) { + std::vector argv; + + argv.push_back(std::string(kShellPath)); // argv[0] + argv.push_back("-c"); // argv[1] + argv.push_back("echo 123456789012345678901234567890"); // argv[2] + + // Run |GetAppOutputRestricted()| 300 (> default per-user processes on Mac OS + // 10.5) times with an output buffer big enough to capture all output. + for (int i = 0; i < 300; i++) { + std::string output; + EXPECT_TRUE(base::GetAppOutputRestricted(CommandLine(argv), &output, 100)); + EXPECT_STREQ("123456789012345678901234567890\n", output.c_str()); + } + + // Ditto, but with an output buffer too small to capture all output. + for (int i = 0; i < 300; i++) { + std::string output; + EXPECT_TRUE(base::GetAppOutputRestricted(CommandLine(argv), &output, 10)); + EXPECT_STREQ("1234567890", output.c_str()); + } +} + +TEST_F(ProcessUtilTest, GetAppOutputWithExitCode) { + // Test getting output from a successful application. + std::vector argv; + std::string output; + int exit_code; + argv.push_back(std::string(kShellPath)); // argv[0] + argv.push_back("-c"); // argv[1] + argv.push_back("echo foo"); // argv[2]; + EXPECT_TRUE(base::GetAppOutputWithExitCode(CommandLine(argv), &output, + &exit_code)); + EXPECT_STREQ("foo\n", output.c_str()); + EXPECT_EQ(exit_code, 0); + + // Test getting output from an application which fails with a specific exit + // code. + output.clear(); + argv[2] = "echo foo; exit 2"; + EXPECT_TRUE(base::GetAppOutputWithExitCode(CommandLine(argv), &output, + &exit_code)); + EXPECT_STREQ("foo\n", output.c_str()); + EXPECT_EQ(exit_code, 2); +} + +TEST_F(ProcessUtilTest, GetParentProcessId) { + base::ProcessId ppid = base::GetParentProcessId(base::GetCurrentProcId()); + EXPECT_EQ(ppid, getppid()); +} + +#if defined(OS_LINUX) || defined(OS_ANDROID) +TEST_F(ProcessUtilTest, ParseProcStatCPU) { + // /proc/self/stat for a process running "top". + const char kTopStat[] = "960 (top) S 16230 960 16230 34818 960 " + "4202496 471 0 0 0 " + "12 16 0 0 " // <- These are the goods. + "20 0 1 0 121946157 15077376 314 18446744073709551615 4194304 " + "4246868 140733983044336 18446744073709551615 140244213071219 " + "0 0 0 138047495 0 0 0 17 1 0 0 0 0 0"; + EXPECT_EQ(12 + 16, base::ParseProcStatCPU(kTopStat)); + + // cat /proc/self/stat on a random other machine I have. + const char kSelfStat[] = "5364 (cat) R 5354 5364 5354 34819 5364 " + "0 142 0 0 0 " + "0 0 0 0 " // <- No CPU, apparently. + "16 0 1 0 1676099790 2957312 114 4294967295 134512640 134528148 " + "3221224832 3221224344 3086339742 0 0 0 0 0 0 0 17 0 0 0"; + + EXPECT_EQ(0, base::ParseProcStatCPU(kSelfStat)); +} + +// Disable on Android because base_unittests runs inside a Dalvik VM that +// starts and stop threads (crbug.com/175563). +#if !defined(OS_ANDROID) +TEST_F(ProcessUtilTest, GetNumberOfThreads) { + const base::ProcessHandle current = base::GetCurrentProcessHandle(); + const int initial_threads = base::GetNumberOfThreads(current); + ASSERT_GT(initial_threads, 0); + const int kNumAdditionalThreads = 10; + { + scoped_ptr my_threads[kNumAdditionalThreads]; + for (int i = 0; i < kNumAdditionalThreads; ++i) { + my_threads[i].reset(new base::Thread("GetNumberOfThreadsTest")); + my_threads[i]->Start(); + ASSERT_EQ(base::GetNumberOfThreads(current), initial_threads + 1 + i); + } + } + // The Thread destructor will stop them. + ASSERT_EQ(initial_threads, base::GetNumberOfThreads(current)); +} +#endif // !defined(OS_ANDROID) + +#endif // defined(OS_LINUX) || defined(OS_ANDROID) + +// TODO(port): port those unit tests. +bool IsProcessDead(base::ProcessHandle child) { + // waitpid() will actually reap the process which is exactly NOT what we + // want to test for. The good thing is that if it can't find the process + // we'll get a nice value for errno which we can test for. + const pid_t result = HANDLE_EINTR(waitpid(child, NULL, WNOHANG)); + return result == -1 && errno == ECHILD; +} + +TEST_F(ProcessUtilTest, DelayedTermination) { + base::ProcessHandle child_process = + SpawnChild("process_util_test_never_die", false); + ASSERT_TRUE(child_process); + base::EnsureProcessTerminated(child_process); + base::WaitForSingleProcess(child_process, base::TimeDelta::FromSeconds(5)); + + // Check that process was really killed. + EXPECT_TRUE(IsProcessDead(child_process)); + base::CloseProcessHandle(child_process); +} + +MULTIPROCESS_TEST_MAIN(process_util_test_never_die) { + while (1) { + sleep(500); + } + return 0; +} + +TEST_F(ProcessUtilTest, ImmediateTermination) { + base::ProcessHandle child_process = + SpawnChild("process_util_test_die_immediately", false); + ASSERT_TRUE(child_process); + // Give it time to die. + sleep(2); + base::EnsureProcessTerminated(child_process); + + // Check that process was really killed. + EXPECT_TRUE(IsProcessDead(child_process)); + base::CloseProcessHandle(child_process); +} + +MULTIPROCESS_TEST_MAIN(process_util_test_die_immediately) { + return 0; +} + +#endif // defined(OS_POSIX) diff --git a/base/process/process_util_unittest_ios.cc b/base/process/process_util_unittest_ios.cc new file mode 100644 index 0000000000..cad0f1b09f --- /dev/null +++ b/base/process/process_util_unittest_ios.cc @@ -0,0 +1,15 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/scoped_ptr.h" +#include "base/process/process_metrics.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(ProcessUtilTestIos, Memory) { + scoped_ptr process_metrics( + base::ProcessMetrics::CreateProcessMetrics( + base::GetCurrentProcessHandle())); + + ASSERT_NE(0u, process_metrics->GetWorkingSetSize()); +} diff --git a/base/process/process_win.cc b/base/process/process_win.cc new file mode 100644 index 0000000000..1217b50989 --- /dev/null +++ b/base/process/process_win.cc @@ -0,0 +1,92 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/process.h" + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/win/windows_version.h" + +namespace base { + +void Process::Close() { + if (!process_) + return; + + // Don't call CloseHandle on a pseudo-handle. + if (process_ != ::GetCurrentProcess()) + ::CloseHandle(process_); + + process_ = NULL; +} + +void Process::Terminate(int result_code) { + if (!process_) + return; + + // Call NtTerminateProcess directly, without going through the import table, + // which might have been hooked with a buggy replacement by third party + // software. http://crbug.com/81449. + HMODULE module = GetModuleHandle(L"ntdll.dll"); + typedef UINT (WINAPI *TerminateProcessPtr)(HANDLE handle, UINT code); + TerminateProcessPtr terminate_process = reinterpret_cast( + GetProcAddress(module, "NtTerminateProcess")); + terminate_process(process_, result_code); +} + +bool Process::IsProcessBackgrounded() const { + if (!process_) + return false; // Failure case. + DWORD priority = GetPriority(); + if (priority == 0) + return false; // Failure case. + return ((priority == BELOW_NORMAL_PRIORITY_CLASS) || + (priority == IDLE_PRIORITY_CLASS)); +} + +bool Process::SetProcessBackgrounded(bool value) { + if (!process_) + return false; + // Vista and above introduce a real background mode, which not only + // sets the priority class on the threads but also on the IO generated + // by it. Unfortunately it can only be set for the calling process. + DWORD priority; + if ((base::win::GetVersion() >= base::win::VERSION_VISTA) && + (process_ == ::GetCurrentProcess())) { + priority = value ? PROCESS_MODE_BACKGROUND_BEGIN : + PROCESS_MODE_BACKGROUND_END; + } else { + priority = value ? BELOW_NORMAL_PRIORITY_CLASS : NORMAL_PRIORITY_CLASS; + } + + return (::SetPriorityClass(process_, priority) != 0); +} + +ProcessId Process::pid() const { + if (process_ == 0) + return 0; + + return GetProcId(process_); +} + +bool Process::is_current() const { + return process_ == GetCurrentProcess(); +} + +// static +Process Process::Current() { + return Process(::GetCurrentProcess()); +} + +// static +bool Process::CanBackgroundProcesses() { + return true; +} + +int Process::GetPriority() const { + DCHECK(process_); + return ::GetPriorityClass(process_); +} + +} // namespace base diff --git a/base/profiler/alternate_timer.cc b/base/profiler/alternate_timer.cc new file mode 100644 index 0000000000..4eba89c255 --- /dev/null +++ b/base/profiler/alternate_timer.cc @@ -0,0 +1,37 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/profiler/alternate_timer.h" + +#include "base/logging.h" + +namespace { + +tracked_objects::NowFunction* g_time_function = NULL; +tracked_objects::TimeSourceType g_time_source_type = + tracked_objects::TIME_SOURCE_TYPE_WALL_TIME; + +} // anonymous namespace + +namespace tracked_objects { + +const char kAlternateProfilerTime[] = "CHROME_PROFILER_TIME"; + +// Set an alternate timer function to replace the OS time function when +// profiling. +void SetAlternateTimeSource(NowFunction* now_function, TimeSourceType type) { + DCHECK_EQ(reinterpret_cast(NULL), g_time_function); + g_time_function = now_function; + g_time_source_type = type; +} + +NowFunction* GetAlternateTimeSource() { + return g_time_function; +} + +TimeSourceType GetTimeSourceType() { + return g_time_source_type; +} + +} // namespace tracked_objects diff --git a/base/profiler/alternate_timer.h b/base/profiler/alternate_timer.h new file mode 100644 index 0000000000..fdc75dc6f0 --- /dev/null +++ b/base/profiler/alternate_timer.h @@ -0,0 +1,44 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This is a glue file, which allows third party code to call into our profiler +// without having to include most any functions from base. + +#ifndef BASE_PROFILER_ALTERNATE_TIMER_H_ +#define BASE_PROFILER_ALTERNATE_TIMER_H_ + +#include "base/base_export.h" + +namespace tracked_objects { + +enum TimeSourceType { + TIME_SOURCE_TYPE_WALL_TIME, + TIME_SOURCE_TYPE_TCMALLOC +}; + +// Provide type for an alternate timer function. +typedef unsigned int NowFunction(); + +// Environment variable name that is used to activate alternate timer profiling +// (such as using TCMalloc allocations to provide a pseudo-timer) for tasks +// instead of wall clock profiling. +BASE_EXPORT extern const char kAlternateProfilerTime[]; + +// Set an alternate timer function to replace the OS time function when +// profiling. Typically this is called by an allocator that is providing a +// function that indicates how much memory has been allocated on any given +// thread. +BASE_EXPORT void SetAlternateTimeSource(NowFunction* now_function, + TimeSourceType type); + +// Gets the pointer to a function that was set via SetAlternateTimeSource(). +// Returns NULL if no set was done prior to calling GetAlternateTimeSource. +NowFunction* GetAlternateTimeSource(); + +// Returns the type of the currently set time source. +BASE_EXPORT TimeSourceType GetTimeSourceType(); + +} // namespace tracked_objects + +#endif // BASE_PROFILER_ALTERNATE_TIMER_H_ diff --git a/base/profiler/scoped_profile.cc b/base/profiler/scoped_profile.cc new file mode 100644 index 0000000000..93c86e92cc --- /dev/null +++ b/base/profiler/scoped_profile.cc @@ -0,0 +1,31 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/profiler/scoped_profile.h" + +#include "base/location.h" +#include "base/tracked_objects.h" + + +namespace tracked_objects { + + +ScopedProfile::ScopedProfile(const Location& location) + : birth_(ThreadData::TallyABirthIfActive(location)), + start_of_run_(ThreadData::NowForStartOfRun(birth_)) { +} + +ScopedProfile::~ScopedProfile() { + StopClockAndTally(); +} + +void ScopedProfile::StopClockAndTally() { + if (!birth_) + return; + ThreadData::TallyRunInAScopedRegionIfTracking(birth_, start_of_run_, + ThreadData::NowForEndOfRun()); + birth_ = NULL; +} + +} // namespace tracked_objects diff --git a/base/profiler/scoped_profile.h b/base/profiler/scoped_profile.h new file mode 100644 index 0000000000..8b77d6d381 --- /dev/null +++ b/base/profiler/scoped_profile.h @@ -0,0 +1,67 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + + +#ifndef BASE_PROFILER_SCOPED_PROFILE_H_ +#define BASE_PROFILER_SCOPED_PROFILE_H_ + +//------------------------------------------------------------------------------ +// ScopedProfile provides basic helper functions for profiling a short +// region of code within a scope. It is separate from the related ThreadData +// class so that it can be included without much other cruft, and provide the +// macros listed below. + +#include "base/base_export.h" +#include "base/location.h" +#include "base/profiler/tracked_time.h" + +#if defined(GOOGLE_CHROME_BUILD) + +// We don't ship these profiled regions. This is for developer builds only. +// It allows developers to do some profiling of their code, and see results on +// their about:profiler page. +#define TRACK_RUN_IN_THIS_SCOPED_REGION_FOR_DEVELOPER_BUILDS(scope_name) \ + ((void)0) + +#else + +#define TRACK_RUN_IN_THIS_SCOPED_REGION_FOR_DEVELOPER_BUILDS(scope_name) \ + ::tracked_objects::ScopedProfile LINE_BASED_VARIABLE_NAME_FOR_PROFILING( \ + FROM_HERE_WITH_EXPLICIT_FUNCTION(#scope_name)) + +#endif + + + +#define PASTE_LINE_NUMBER_ON_NAME(name, line) name##line + +#define LINE_BASED_VARIABLE_NAME_FOR_PROFILING \ + PASTE_LINE_NUMBER_ON_NAME(some_profiler_variable_, __LINE__) + +#define TRACK_RUN_IN_IPC_HANDLER(dispatch_function_name) \ + ::tracked_objects::ScopedProfile some_tracking_variable_name( \ + FROM_HERE_WITH_EXPLICIT_FUNCTION(#dispatch_function_name)) + + +namespace tracked_objects { +class Births; + +class BASE_EXPORT ScopedProfile { + public: + explicit ScopedProfile(const Location& location); + ~ScopedProfile(); + + // Stop tracing prior to the end destruction of the instance. + void StopClockAndTally(); + + private: + Births* birth_; // Place in code where tracking started. + const TrackedTime start_of_run_; + + DISALLOW_COPY_AND_ASSIGN(ScopedProfile); +}; + +} // namespace tracked_objects + +#endif // BASE_PROFILER_SCOPED_PROFILE_H_ diff --git a/base/profiler/tracked_time.cc b/base/profiler/tracked_time.cc new file mode 100644 index 0000000000..27d3358be3 --- /dev/null +++ b/base/profiler/tracked_time.cc @@ -0,0 +1,76 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/profiler/tracked_time.h" + +#include "build/build_config.h" + +#if defined(OS_WIN) +#include // Declare timeGetTime()... after including build_config. +#endif + +namespace tracked_objects { + +Duration::Duration() : ms_(0) {} +Duration::Duration(int32 duration) : ms_(duration) {} + +Duration& Duration::operator+=(const Duration& other) { + ms_ += other.ms_; + return *this; +} + +Duration Duration::operator+(const Duration& other) const { + return Duration(ms_ + other.ms_); +} + +bool Duration::operator==(const Duration& other) const { + return ms_ == other.ms_; +} + +bool Duration::operator!=(const Duration& other) const { + return ms_ != other.ms_; +} + +bool Duration::operator>(const Duration& other) const { + return ms_ > other.ms_; +} + +// static +Duration Duration::FromMilliseconds(int ms) { return Duration(ms); } + +int32 Duration::InMilliseconds() const { return ms_; } + +//------------------------------------------------------------------------------ + +TrackedTime::TrackedTime() : ms_(0) {} +TrackedTime::TrackedTime(int32 ms) : ms_(ms) {} +TrackedTime::TrackedTime(const base::TimeTicks& time) + : ms_((time - base::TimeTicks()).InMilliseconds()) { +} + +// static +TrackedTime TrackedTime::Now() { +#if defined(OS_WIN) + // Use lock-free accessor to 32 bit time. + // Note that TimeTicks::Now() is built on this, so we have "compatible" + // times when we down-convert a TimeTicks sample. + // TODO(jar): Surface this interface via something in base/time/time.h. + return TrackedTime(static_cast(timeGetTime())); +#else + // Posix has nice cheap 64 bit times, so we just down-convert it. + return TrackedTime(base::TimeTicks::Now()); +#endif // OS_WIN +} + +Duration TrackedTime::operator-(const TrackedTime& other) const { + return Duration(ms_ - other.ms_); +} + +TrackedTime TrackedTime::operator+(const Duration& other) const { + return TrackedTime(ms_ + other.ms_); +} + +bool TrackedTime::is_null() const { return ms_ == 0; } + +} // namespace tracked_objects diff --git a/base/profiler/tracked_time.h b/base/profiler/tracked_time.h new file mode 100644 index 0000000000..23632749a0 --- /dev/null +++ b/base/profiler/tracked_time.h @@ -0,0 +1,71 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_PROFILER_TRACKED_TIME_H_ +#define BASE_PROFILER_TRACKED_TIME_H_ + + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/time/time.h" + +namespace tracked_objects { + +//------------------------------------------------------------------------------ + +// TimeTicks maintains a wasteful 64 bits of data (we need less than 32), and on +// windows, a 64 bit timer is expensive to even obtain. We use a simple +// millisecond counter for most of our time values, as well as millisecond units +// of duration between those values. This means we can only handle durations +// up to 49 days (range), or 24 days (non-negative time durations). +// We only define enough methods to service the needs of the tracking classes, +// and our interfaces are modeled after what TimeTicks and TimeDelta use (so we +// can swap them into place if we want to use the "real" classes). + +class BASE_EXPORT Duration { // Similar to base::TimeDelta. + public: + Duration(); + + Duration& operator+=(const Duration& other); + Duration operator+(const Duration& other) const; + + bool operator==(const Duration& other) const; + bool operator!=(const Duration& other) const; + bool operator>(const Duration& other) const; + + static Duration FromMilliseconds(int ms); + + int32 InMilliseconds() const; + + private: + friend class TrackedTime; + explicit Duration(int32 duration); + + // Internal time is stored directly in milliseconds. + int32 ms_; +}; + +class BASE_EXPORT TrackedTime { // Similar to base::TimeTicks. + public: + TrackedTime(); + explicit TrackedTime(const base::TimeTicks& time); + + static TrackedTime Now(); + Duration operator-(const TrackedTime& other) const; + TrackedTime operator+(const Duration& other) const; + bool is_null() const; + + static TrackedTime FromMilliseconds(int32 ms) { return TrackedTime(ms); } + + private: + friend class Duration; + explicit TrackedTime(int32 ms); + + // Internal duration is stored directly in milliseconds. + uint32 ms_; +}; + +} // namespace tracked_objects + +#endif // BASE_PROFILER_TRACKED_TIME_H_ diff --git a/base/profiler/tracked_time_unittest.cc b/base/profiler/tracked_time_unittest.cc new file mode 100644 index 0000000000..ef84f153a6 --- /dev/null +++ b/base/profiler/tracked_time_unittest.cc @@ -0,0 +1,110 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Test of classes in tracked_time.cc + +#include "base/profiler/tracked_time.h" +#include "base/time/time.h" +#include "base/tracked_objects.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace tracked_objects { + +TEST(TrackedTimeTest, TrackedTimerMilliseconds) { + // First make sure we basicallly transfer simple milliseconds values as + // expected. Most critically, things should not become null. + int32 kSomeMilliseconds = 243; // Some example times. + int64 kReallyBigMilliseconds = (1LL << 35) + kSomeMilliseconds; + + TrackedTime some = TrackedTime() + + Duration::FromMilliseconds(kSomeMilliseconds); + EXPECT_EQ(kSomeMilliseconds, (some - TrackedTime()).InMilliseconds()); + EXPECT_FALSE(some.is_null()); + + // Now create a big time, to check that it is wrapped modulo 2^32. + base::TimeTicks big = base::TimeTicks() + + base::TimeDelta::FromMilliseconds(kReallyBigMilliseconds); + EXPECT_EQ(kReallyBigMilliseconds, (big - base::TimeTicks()).InMilliseconds()); + + TrackedTime wrapped_big(big); + // Expect wrapping at 32 bits. + EXPECT_EQ(kSomeMilliseconds, (wrapped_big - TrackedTime()).InMilliseconds()); +} + +TEST(TrackedTimeTest, TrackedTimerDuration) { + int kFirstMilliseconds = 793; + int kSecondMilliseconds = 14889; + + Duration first = Duration::FromMilliseconds(kFirstMilliseconds); + Duration second = Duration::FromMilliseconds(kSecondMilliseconds); + + EXPECT_EQ(kFirstMilliseconds, first.InMilliseconds()); + EXPECT_EQ(kSecondMilliseconds, second.InMilliseconds()); + + Duration sum = first + second; + EXPECT_EQ(kFirstMilliseconds + kSecondMilliseconds, sum.InMilliseconds()); +} + +TEST(TrackedTimeTest, TrackedTimerVsTimeTicks) { + // Make sure that our 32 bit timer is aligned with the TimeTicks() timer. + + // First get a 64 bit timer (which should not be null). + base::TimeTicks ticks_before = base::TimeTicks::Now(); + EXPECT_FALSE(ticks_before.is_null()); + + // Then get a 32 bit timer that can be be null when it wraps. + TrackedTime now = TrackedTime::Now(); + + // Then get a bracketing time. + base::TimeTicks ticks_after = base::TimeTicks::Now(); + EXPECT_FALSE(ticks_after.is_null()); + + // Now make sure that we bracketed our tracked time nicely. + Duration before = now - TrackedTime(ticks_before); + EXPECT_LE(0, before.InMilliseconds()); + Duration after = now - TrackedTime(ticks_after); + EXPECT_GE(0, after.InMilliseconds()); +} + +TEST(TrackedTimeTest, TrackedTimerDisabled) { + // Check to be sure disabling the collection of data induces a null time + // (which we know will return much faster). + if (!ThreadData::InitializeAndSetTrackingStatus(ThreadData::DEACTIVATED)) + return; + // Since we disabled tracking, we should get a null response. + TrackedTime track_now = ThreadData::Now(); + EXPECT_TRUE(track_now.is_null()); + track_now = ThreadData::NowForStartOfRun(NULL); + EXPECT_TRUE(track_now.is_null()); + track_now = ThreadData::NowForEndOfRun(); + EXPECT_TRUE(track_now.is_null()); +} + +TEST(TrackedTimeTest, TrackedTimerEnabled) { + if (!ThreadData::InitializeAndSetTrackingStatus( + ThreadData::PROFILING_CHILDREN_ACTIVE)) + return; + // Make sure that when we enable tracking, we get a real timer result. + + // First get a 64 bit timer (which should not be null). + base::TimeTicks ticks_before = base::TimeTicks::Now(); + EXPECT_FALSE(ticks_before.is_null()); + + // Then get a 32 bit timer that can be null when it wraps. + // Crtical difference from the TrackedTimerVsTimeTicks test, is that we use + // ThreadData::Now(). It can sometimes return the null time. + TrackedTime now = ThreadData::Now(); + + // Then get a bracketing time. + base::TimeTicks ticks_after = base::TimeTicks::Now(); + EXPECT_FALSE(ticks_after.is_null()); + + // Now make sure that we bracketed our tracked time nicely. + Duration before = now - TrackedTime(ticks_before); + EXPECT_LE(0, before.InMilliseconds()); + Duration after = now - TrackedTime(ticks_after); + EXPECT_GE(0, after.InMilliseconds()); +} + +} // namespace tracked_objects diff --git a/base/rand_util.cc b/base/rand_util.cc new file mode 100644 index 0000000000..da6de87dc3 --- /dev/null +++ b/base/rand_util.cc @@ -0,0 +1,80 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/rand_util.h" + +#include + +#include + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/strings/string_util.h" + +namespace base { + +int RandInt(int min, int max) { + DCHECK_LE(min, max); + + uint64 range = static_cast(max) - min + 1; + int result = min + static_cast(base::RandGenerator(range)); + DCHECK_GE(result, min); + DCHECK_LE(result, max); + return result; +} + +double RandDouble() { + return BitsToOpenEndedUnitInterval(base::RandUint64()); +} + +double BitsToOpenEndedUnitInterval(uint64 bits) { + // We try to get maximum precision by masking out as many bits as will fit + // in the target type's mantissa, and raising it to an appropriate power to + // produce output in the range [0, 1). For IEEE 754 doubles, the mantissa + // is expected to accommodate 53 bits. + + COMPILE_ASSERT(std::numeric_limits::radix == 2, otherwise_use_scalbn); + static const int kBits = std::numeric_limits::digits; + uint64 random_bits = bits & ((GG_UINT64_C(1) << kBits) - 1); + double result = ldexp(static_cast(random_bits), -1 * kBits); + DCHECK_GE(result, 0.0); + DCHECK_LT(result, 1.0); + return result; +} + +uint64 RandGenerator(uint64 range) { + DCHECK_GT(range, 0u); + // We must discard random results above this number, as they would + // make the random generator non-uniform (consider e.g. if + // MAX_UINT64 was 7 and |range| was 5, then a result of 1 would be twice + // as likely as a result of 3 or 4). + uint64 max_acceptable_value = + (std::numeric_limits::max() / range) * range - 1; + + uint64 value; + do { + value = base::RandUint64(); + } while (value > max_acceptable_value); + + return value % range; +} + +void RandBytes(void* output, size_t output_length) { + uint64 random_int; + size_t random_int_size = sizeof(random_int); + for (size_t i = 0; i < output_length; i += random_int_size) { + random_int = base::RandUint64(); + size_t copy_count = std::min(output_length - i, random_int_size); + memcpy(((uint8*)output) + i, &random_int, copy_count); + } +} + +std::string RandBytesAsString(size_t length) { + DCHECK_GT(length, 0u); + std::string result; + RandBytes(WriteInto(&result, length + 1), length); + return result; +} + +} // namespace base diff --git a/base/rand_util.h b/base/rand_util.h new file mode 100644 index 0000000000..bae8c31178 --- /dev/null +++ b/base/rand_util.h @@ -0,0 +1,58 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_RAND_UTIL_H_ +#define BASE_RAND_UTIL_H_ + +#include + +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace base { + +// Returns a random number in range [0, kuint64max]. Thread-safe. +BASE_EXPORT uint64 RandUint64(); + +// Returns a random number between min and max (inclusive). Thread-safe. +BASE_EXPORT int RandInt(int min, int max); + +// Returns a random number in range [0, range). Thread-safe. +// +// Note that this can be used as an adapter for std::random_shuffle(): +// Given a pre-populated |std::vector myvector|, shuffle it as +// std::random_shuffle(myvector.begin(), myvector.end(), base::RandGenerator); +BASE_EXPORT uint64 RandGenerator(uint64 range); + +// Returns a random double in range [0, 1). Thread-safe. +BASE_EXPORT double RandDouble(); + +// Given input |bits|, convert with maximum precision to a double in +// the range [0, 1). Thread-safe. +BASE_EXPORT double BitsToOpenEndedUnitInterval(uint64 bits); + +// Fills |output_length| bytes of |output| with random data. +// +// WARNING: +// Do not use for security-sensitive purposes. +// See crypto/ for cryptographically secure random number generation APIs. +BASE_EXPORT void RandBytes(void* output, size_t output_length); + +// Fills a string of length |length| with with random data and returns it. +// |length| should be nonzero. +// +// Note that this is a variation of |RandBytes| with a different return type. +// +// WARNING: +// Do not use for security-sensitive purposes. +// See crypto/ for cryptographically secure random number generation APIs. +BASE_EXPORT std::string RandBytesAsString(size_t length); + +#if defined(OS_POSIX) +BASE_EXPORT int GetUrandomFD(); +#endif + +} // namespace base + +#endif // BASE_RAND_UTIL_H_ diff --git a/base/rand_util_nacl.cc b/base/rand_util_nacl.cc new file mode 100644 index 0000000000..47450aaba6 --- /dev/null +++ b/base/rand_util_nacl.cc @@ -0,0 +1,53 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/rand_util.h" + +#include "base/basictypes.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "native_client/src/untrusted/irt/irt.h" + +namespace { + +class NaclRandom { + public: + NaclRandom() { + size_t result = nacl_interface_query(NACL_IRT_RANDOM_v0_1, + &random_, sizeof(random_)); + CHECK_EQ(result, sizeof(random_)); + } + + ~NaclRandom() { + } + + void GetRandomBytes(char* buffer, uint32_t num_bytes) { + while (num_bytes > 0) { + size_t nread; + int error = random_.get_random_bytes(buffer, num_bytes, &nread); + CHECK_EQ(error, 0); + CHECK_LE(nread, num_bytes); + buffer += nread; + num_bytes -= nread; + } + } + + private: + nacl_irt_random random_; +}; + +base::LazyInstance::Leaky g_nacl_random = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +namespace base { + +uint64 RandUint64() { + uint64 result; + g_nacl_random.Pointer()->GetRandomBytes( + reinterpret_cast(&result), sizeof(result)); + return result; +} + +} // namespace base diff --git a/base/rand_util_posix.cc b/base/rand_util_posix.cc new file mode 100644 index 0000000000..9b18777b99 --- /dev/null +++ b/base/rand_util_posix.cc @@ -0,0 +1,61 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/rand_util.h" + +#include +#include +#include + +#include "base/file_util.h" +#include "base/lazy_instance.h" +#include "base/logging.h" + +namespace { + +// We keep the file descriptor for /dev/urandom around so we don't need to +// reopen it (which is expensive), and since we may not even be able to reopen +// it if we are later put in a sandbox. This class wraps the file descriptor so +// we can use LazyInstance to handle opening it on the first access. +class URandomFd { + public: + URandomFd() { + fd_ = open("/dev/urandom", O_RDONLY); + DCHECK_GE(fd_, 0) << "Cannot open /dev/urandom: " << errno; + } + + ~URandomFd() { + close(fd_); + } + + int fd() const { return fd_; } + + private: + int fd_; +}; + +base::LazyInstance::Leaky g_urandom_fd = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +namespace base { + +// NOTE: This function must be cryptographically secure. http://crbug.com/140076 +uint64 RandUint64() { + uint64 number; + + int urandom_fd = g_urandom_fd.Pointer()->fd(); + bool success = file_util::ReadFromFD(urandom_fd, + reinterpret_cast(&number), + sizeof(number)); + CHECK(success); + + return number; +} + +int GetUrandomFD(void) { + return g_urandom_fd.Pointer()->fd(); +} + +} // namespace base diff --git a/base/rand_util_unittest.cc b/base/rand_util_unittest.cc new file mode 100644 index 0000000000..e0e85ecaa9 --- /dev/null +++ b/base/rand_util_unittest.cc @@ -0,0 +1,122 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/rand_util.h" + +#include +#include + +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const int kIntMin = std::numeric_limits::min(); +const int kIntMax = std::numeric_limits::max(); + +} // namespace + +TEST(RandUtilTest, SameMinAndMax) { + EXPECT_EQ(base::RandInt(0, 0), 0); + EXPECT_EQ(base::RandInt(kIntMin, kIntMin), kIntMin); + EXPECT_EQ(base::RandInt(kIntMax, kIntMax), kIntMax); +} + +TEST(RandUtilTest, RandDouble) { + // Force 64-bit precision, making sure we're not in a 80-bit FPU register. + volatile double number = base::RandDouble(); + EXPECT_GT(1.0, number); + EXPECT_LE(0.0, number); +} + +TEST(RandUtilTest, RandBytes) { + const size_t buffer_size = 50; + char buffer[buffer_size]; + memset(buffer, 0, buffer_size); + base::RandBytes(buffer, buffer_size); + std::sort(buffer, buffer + buffer_size); + // Probability of occurrence of less than 25 unique bytes in 50 random bytes + // is below 10^-25. + EXPECT_GT(std::unique(buffer, buffer + buffer_size) - buffer, 25); +} + +TEST(RandUtilTest, RandBytesAsString) { + std::string random_string = base::RandBytesAsString(1); + EXPECT_EQ(1U, random_string.size()); + random_string = base::RandBytesAsString(145); + EXPECT_EQ(145U, random_string.size()); + char accumulator = 0; + for (size_t i = 0; i < random_string.size(); ++i) + accumulator |= random_string[i]; + // In theory this test can fail, but it won't before the universe dies of + // heat death. + EXPECT_NE(0, accumulator); +} + +// Make sure that it is still appropriate to use RandGenerator in conjunction +// with std::random_shuffle(). +TEST(RandUtilTest, RandGeneratorForRandomShuffle) { + EXPECT_EQ(base::RandGenerator(1), 0U); + EXPECT_LE(std::numeric_limits::max(), + std::numeric_limits::max()); +} + +TEST(RandUtilTest, RandGeneratorIsUniform) { + // Verify that RandGenerator has a uniform distribution. This is a + // regression test that consistently failed when RandGenerator was + // implemented this way: + // + // return base::RandUint64() % max; + // + // A degenerate case for such an implementation is e.g. a top of + // range that is 2/3rds of the way to MAX_UINT64, in which case the + // bottom half of the range would be twice as likely to occur as the + // top half. A bit of calculus care of jar@ shows that the largest + // measurable delta is when the top of the range is 3/4ths of the + // way, so that's what we use in the test. + const uint64 kTopOfRange = (std::numeric_limits::max() / 4ULL) * 3ULL; + const uint64 kExpectedAverage = kTopOfRange / 2ULL; + const uint64 kAllowedVariance = kExpectedAverage / 50ULL; // +/- 2% + const int kMinAttempts = 1000; + const int kMaxAttempts = 1000000; + + double cumulative_average = 0.0; + int count = 0; + while (count < kMaxAttempts) { + uint64 value = base::RandGenerator(kTopOfRange); + cumulative_average = (count * cumulative_average + value) / (count + 1); + + // Don't quit too quickly for things to start converging, or we may have + // a false positive. + if (count > kMinAttempts && + kExpectedAverage - kAllowedVariance < cumulative_average && + cumulative_average < kExpectedAverage + kAllowedVariance) { + break; + } + + ++count; + } + + ASSERT_LT(count, kMaxAttempts) << "Expected average was " << + kExpectedAverage << ", average ended at " << cumulative_average; +} + +TEST(RandUtilTest, RandUint64ProducesBothValuesOfAllBits) { + // This tests to see that our underlying random generator is good + // enough, for some value of good enough. + uint64 kAllZeros = 0ULL; + uint64 kAllOnes = ~kAllZeros; + uint64 found_ones = kAllZeros; + uint64 found_zeros = kAllOnes; + + for (size_t i = 0; i < 1000; ++i) { + uint64 value = base::RandUint64(); + found_ones |= value; + found_zeros &= value; + + if (found_zeros == kAllZeros && found_ones == kAllOnes) + return; + } + + FAIL() << "Didn't achieve all bit values in maximum number of tries."; +} diff --git a/base/rand_util_win.cc b/base/rand_util_win.cc new file mode 100644 index 0000000000..391fe5b9e6 --- /dev/null +++ b/base/rand_util_win.cc @@ -0,0 +1,31 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/rand_util.h" + +#include + +#include "base/basictypes.h" +#include "base/logging.h" + +namespace { + +uint32 RandUint32() { + uint32 number; + CHECK_EQ(rand_s(&number), 0); + return number; +} + +} // namespace + +namespace base { + +// NOTE: This function must be cryptographically secure. http://crbug.com/140076 +uint64 RandUint64() { + uint32 first_half = RandUint32(); + uint32 second_half = RandUint32(); + return (static_cast(first_half) << 32) + second_half; +} + +} // namespace base diff --git a/base/run_loop.cc b/base/run_loop.cc new file mode 100644 index 0000000000..991571eacd --- /dev/null +++ b/base/run_loop.cc @@ -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. + +#include "base/run_loop.h" + +#include "base/bind.h" + +namespace base { + +RunLoop::RunLoop() + : loop_(MessageLoop::current()), + weak_factory_(this), + previous_run_loop_(NULL), + run_depth_(0), + run_called_(false), + quit_called_(false), + running_(false), + quit_when_idle_received_(false) { +#if !defined(OS_MACOSX) && !defined(OS_ANDROID) + dispatcher_ = NULL; +#endif +} + +#if !defined(OS_MACOSX) && !defined(OS_ANDROID) +RunLoop::RunLoop(MessageLoop::Dispatcher* dispatcher) + : loop_(MessageLoop::current()), + weak_factory_(this), + previous_run_loop_(NULL), + dispatcher_(dispatcher), + run_depth_(0), + run_called_(false), + quit_called_(false), + running_(false), + quit_when_idle_received_(false) { +} +#endif + +RunLoop::~RunLoop() { +} + +void RunLoop::Run() { + if (!BeforeRun()) + return; + loop_->RunHandler(); + AfterRun(); +} + +void RunLoop::RunUntilIdle() { + quit_when_idle_received_ = true; + Run(); +} + +void RunLoop::Quit() { + quit_called_ = true; + if (running_ && loop_->run_loop_ == this) { + // This is the inner-most RunLoop, so quit now. + loop_->QuitNow(); + } +} + +base::Closure RunLoop::QuitClosure() { + return base::Bind(&RunLoop::Quit, weak_factory_.GetWeakPtr()); +} + +bool RunLoop::BeforeRun() { + DCHECK(!run_called_); + run_called_ = true; + + // Allow Quit to be called before Run. + if (quit_called_) + return false; + + // Push RunLoop stack: + previous_run_loop_ = loop_->run_loop_; + run_depth_ = previous_run_loop_? previous_run_loop_->run_depth_ + 1 : 1; + loop_->run_loop_ = this; + + running_ = true; + return true; +} + +void RunLoop::AfterRun() { + running_ = false; + + // Pop RunLoop stack: + loop_->run_loop_ = previous_run_loop_; + + // Execute deferred QuitNow, if any: + if (previous_run_loop_ && previous_run_loop_->quit_called_) + loop_->QuitNow(); +} + +} // namespace base diff --git a/base/run_loop.h b/base/run_loop.h new file mode 100644 index 0000000000..42a516000e --- /dev/null +++ b/base/run_loop.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_RUN_LOOP_H_ +#define BASE_RUN_LOOP_H_ + +#include "base/base_export.h" +#include "base/callback.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop.h" + +namespace base { +#if defined(OS_ANDROID) +class MessagePumpForUI; +#endif + +#if defined(OS_IOS) +class MessagePumpUIApplication; +#endif + +// Helper class to Run a nested MessageLoop. Please do not use nested +// MessageLoops in production code! If you must, use this class instead of +// calling MessageLoop::Run/Quit directly. RunLoop::Run can only be called once +// per RunLoop lifetime. Create a RunLoop on the stack and call Run/Quit to run +// a nested MessageLoop. +class BASE_EXPORT RunLoop { + public: + RunLoop(); +#if !defined(OS_MACOSX) && !defined(OS_ANDROID) + explicit RunLoop(MessageLoop::Dispatcher* dispatcher); +#endif + ~RunLoop(); + +#if !defined(OS_MACOSX) && !defined(OS_ANDROID) + void set_dispatcher(MessageLoop::Dispatcher* dispatcher) { + dispatcher_ = dispatcher; + } +#endif + + // Run the current MessageLoop. This blocks until Quit is called. Before + // calling Run, be sure to grab an AsWeakPtr or the QuitClosure in order to + // stop the MessageLoop asynchronously. MessageLoop::Quit and QuitNow will + // also trigger a return from Run, but those are deprecated. + void Run(); + + // Run the current MessageLoop until it doesn't find any tasks or messages in + // the queue (it goes idle). WARNING: This may never return! Only use this + // when repeating tasks such as animated web pages have been shut down. + void RunUntilIdle(); + + bool running() const { return running_; } + + // Quit an earlier call to Run(). There can be other nested RunLoops servicing + // the same task queue (MessageLoop); Quitting one RunLoop has no bearing on + // the others. Quit can be called before, during or after Run. If called + // before Run, Run will return immediately when called. Calling Quit after the + // RunLoop has already finished running has no effect. + // + // WARNING: You must NEVER assume that a call to Quit will terminate the + // targetted message loop. If a nested message loop continues running, the + // target may NEVER terminate. It is very easy to livelock (run forever) in + // such a case. + void Quit(); + + // Convenience method to get a closure that safely calls Quit (has no effect + // if the RunLoop instance is gone). + // + // Example: + // RunLoop run_loop; + // PostTask(run_loop.QuitClosure()); + // run_loop.Run(); + base::Closure QuitClosure(); + + private: + friend class MessageLoop; +#if defined(OS_ANDROID) + // Android doesn't support the blocking MessageLoop::Run, so it calls + // BeforeRun and AfterRun directly. + friend class base::MessagePumpForUI; +#endif + +#if defined(OS_IOS) + // iOS doesn't support the blocking MessageLoop::Run, so it calls + // BeforeRun directly. + friend class base::MessagePumpUIApplication; +#endif + + // Return false to abort the Run. + bool BeforeRun(); + void AfterRun(); + + MessageLoop* loop_; + + // WeakPtrFactory for QuitClosure safety. + base::WeakPtrFactory weak_factory_; + + // Parent RunLoop or NULL if this is the top-most RunLoop. + RunLoop* previous_run_loop_; + +#if !defined(OS_MACOSX) && !defined(OS_ANDROID) + MessageLoop::Dispatcher* dispatcher_; +#endif + + // Used to count how many nested Run() invocations are on the stack. + int run_depth_; + + bool run_called_; + bool quit_called_; + bool running_; + + // Used to record that QuitWhenIdle() was called on the MessageLoop, meaning + // that we should quit Run once it becomes idle. + bool quit_when_idle_received_; + + DISALLOW_COPY_AND_ASSIGN(RunLoop); +}; + +} // namespace base + +#endif // BASE_RUN_LOOP_H_ diff --git a/base/safe_numerics.h b/base/safe_numerics.h new file mode 100644 index 0000000000..ce5c72fcc7 --- /dev/null +++ b/base/safe_numerics.h @@ -0,0 +1,135 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_SAFE_NUMERICS_H_ +#define BASE_SAFE_NUMERICS_H_ + +#include + +#include "base/logging.h" + +namespace base { +namespace internal { + +template +struct IsValidNumericCastImpl; + +#define BASE_NUMERIC_CAST_CASE_SPECIALIZATION(A, B, C, D, Code) \ +template <> struct IsValidNumericCastImpl { \ + template static inline bool Test( \ + Source source, DestBounds min, DestBounds max) { \ + return Code; \ + } \ +} + +#define BASE_NUMERIC_CAST_CASE_SAME_SIZE(DestSigned, SourceSigned, Code) \ + BASE_NUMERIC_CAST_CASE_SPECIALIZATION( \ + true, true, DestSigned, SourceSigned, Code); \ + BASE_NUMERIC_CAST_CASE_SPECIALIZATION( \ + true, false, DestSigned, SourceSigned, Code) + +#define BASE_NUMERIC_CAST_CASE_SOURCE_LARGER(DestSigned, SourceSigned, Code) \ + BASE_NUMERIC_CAST_CASE_SPECIALIZATION( \ + false, false, DestSigned, SourceSigned, Code); \ + +#define BASE_NUMERIC_CAST_CASE_DEST_LARGER(DestSigned, SourceSigned, Code) \ + BASE_NUMERIC_CAST_CASE_SPECIALIZATION( \ + false, true, DestSigned, SourceSigned, Code); \ + +// The three top level cases are: +// - Same size +// - Source larger +// - Dest larger +// And for each of those three cases, we handle the 4 different possibilities +// of signed and unsigned. This gives 12 cases to handle, which we enumerate +// below. +// +// The last argument in each of the macros is the actual comparison code. It +// has three arguments available, source (the value), and min/max which are +// the ranges of the destination. + + +// These are the cases where both types have the same size. + +// Both signed. +BASE_NUMERIC_CAST_CASE_SAME_SIZE(true, true, true); +// Both unsigned. +BASE_NUMERIC_CAST_CASE_SAME_SIZE(false, false, true); +// Dest unsigned, Source signed. +BASE_NUMERIC_CAST_CASE_SAME_SIZE(false, true, source >= 0); +// Dest signed, Source unsigned. +// This cast is OK because Dest's max must be less than Source's. +BASE_NUMERIC_CAST_CASE_SAME_SIZE(true, false, + source <= static_cast(max)); + + +// These are the cases where Source is larger. + +// Both unsigned. +BASE_NUMERIC_CAST_CASE_SOURCE_LARGER(false, false, source <= max); +// Both signed. +BASE_NUMERIC_CAST_CASE_SOURCE_LARGER(true, true, + source >= min && source <= max); +// Dest is unsigned, Source is signed. +BASE_NUMERIC_CAST_CASE_SOURCE_LARGER(false, true, + source >= 0 && source <= max); +// Dest is signed, Source is unsigned. +// This cast is OK because Dest's max must be less than Source's. +BASE_NUMERIC_CAST_CASE_SOURCE_LARGER(true, false, + source <= static_cast(max)); + + +// These are the cases where Dest is larger. + +// Both unsigned. +BASE_NUMERIC_CAST_CASE_DEST_LARGER(false, false, true); +// Both signed. +BASE_NUMERIC_CAST_CASE_DEST_LARGER(true, true, true); +// Dest is unsigned, Source is signed. +BASE_NUMERIC_CAST_CASE_DEST_LARGER(false, true, source >= 0); +// Dest is signed, Source is unsigned. +BASE_NUMERIC_CAST_CASE_DEST_LARGER(true, false, true); + +#undef BASE_NUMERIC_CAST_CASE_SPECIALIZATION +#undef BASE_NUMERIC_CAST_CASE_SAME_SIZE +#undef BASE_NUMERIC_CAST_CASE_SOURCE_LARGER +#undef BASE_NUMERIC_CAST_CASE_DEST_LARGER + + +// The main test for whether the conversion will under or overflow. +template +inline bool IsValidNumericCast(Source source) { + typedef std::numeric_limits SourceLimits; + typedef std::numeric_limits DestLimits; + COMPILE_ASSERT(SourceLimits::is_specialized, argument_must_be_numeric); + COMPILE_ASSERT(SourceLimits::is_integer, argument_must_be_integral); + COMPILE_ASSERT(DestLimits::is_specialized, result_must_be_numeric); + COMPILE_ASSERT(DestLimits::is_integer, result_must_be_integral); + + return IsValidNumericCastImpl< + sizeof(Dest) == sizeof(Source), + (sizeof(Dest) > sizeof(Source)), + DestLimits::is_signed, + SourceLimits::is_signed>::Test( + source, + DestLimits::min(), + DestLimits::max()); +} + +} // namespace internal + +// checked_numeric_cast<> is analogous to static_cast<> for numeric types, +// except that it CHECKs that the specified numeric conversion will not +// overflow or underflow. Floating point arguments are not currently allowed +// (this is COMPILE_ASSERTd), though this could be supported if necessary. +template +inline Dest checked_numeric_cast(Source source) { + CHECK(internal::IsValidNumericCast(source)); + return static_cast(source); +} + +} // namespace base + +#endif // BASE_SAFE_NUMERICS_H_ diff --git a/base/safe_numerics_unittest.cc b/base/safe_numerics_unittest.cc new file mode 100644 index 0000000000..c66e56f3cf --- /dev/null +++ b/base/safe_numerics_unittest.cc @@ -0,0 +1,151 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include + +#include "base/safe_numerics.h" + +namespace base { +namespace internal { + +// This is far (far, far) too slow to run normally, but if you're refactoring +// it might be useful. +// #define RUN_EXHAUSTIVE_TEST + +#ifdef RUN_EXHAUSTIVE_TEST + +template void ExhaustiveCheckFromTo() { + fprintf(stderr, "."); + From i = std::numeric_limits::min(); + for (;;) { + std::ostringstream str_from, str_to; + str_from << i; + To to = static_cast(i); + str_to << to; + bool strings_equal = str_from.str() == str_to.str(); + EXPECT_EQ(IsValidNumericCast(i), strings_equal); + fprintf(stderr, "\r%s vs %s\x1B[K", + str_from.str().c_str(), str_to.str().c_str()); + ++i; + // If we wrap, then we've tested everything. + if (i == std::numeric_limits::min()) + break; + } +} + +template void ExhaustiveCheckFrom() { + ExhaustiveCheckFromTo(); + ExhaustiveCheckFromTo(); + ExhaustiveCheckFromTo(); + ExhaustiveCheckFromTo(); + ExhaustiveCheckFromTo(); + ExhaustiveCheckFromTo(); + ExhaustiveCheckFromTo(); + fprintf(stderr, "\n"); +} + +#endif + + +TEST(SafeNumerics, NumericCast) { + int small_positive = 1; + int small_negative = -1; + int large_positive = INT_MAX; + int large_negative = INT_MIN; + size_t size_t_small = 1; + size_t size_t_large = UINT_MAX; + + // Narrow signed destination. + EXPECT_TRUE(IsValidNumericCast(small_positive)); + EXPECT_TRUE(IsValidNumericCast(small_negative)); + EXPECT_FALSE(IsValidNumericCast(large_positive)); + EXPECT_FALSE(IsValidNumericCast(large_negative)); + EXPECT_TRUE(IsValidNumericCast(small_positive)); + EXPECT_TRUE(IsValidNumericCast(small_negative)); + + // Narrow unsigned destination. + EXPECT_TRUE(IsValidNumericCast(small_positive)); + EXPECT_FALSE(IsValidNumericCast(small_negative)); + EXPECT_FALSE(IsValidNumericCast(large_positive)); + EXPECT_FALSE(IsValidNumericCast(large_negative)); + EXPECT_FALSE(IsValidNumericCast(small_negative)); + EXPECT_FALSE(IsValidNumericCast(large_negative)); + + // Same width signed destination. + EXPECT_TRUE(IsValidNumericCast(small_positive)); + EXPECT_TRUE(IsValidNumericCast(small_negative)); + EXPECT_TRUE(IsValidNumericCast(large_positive)); + EXPECT_TRUE(IsValidNumericCast(large_negative)); + + // Same width unsigned destination. + EXPECT_TRUE(IsValidNumericCast(small_positive)); + EXPECT_FALSE(IsValidNumericCast(small_negative)); + EXPECT_TRUE(IsValidNumericCast(large_positive)); + EXPECT_FALSE(IsValidNumericCast(large_negative)); + + // Wider signed destination. + EXPECT_TRUE(IsValidNumericCast(small_positive)); + EXPECT_TRUE(IsValidNumericCast(large_negative)); + EXPECT_TRUE(IsValidNumericCast(small_positive)); + EXPECT_TRUE(IsValidNumericCast(large_negative)); + + // Wider unsigned destination. + EXPECT_TRUE(IsValidNumericCast(small_positive)); + EXPECT_FALSE(IsValidNumericCast(small_negative)); + EXPECT_TRUE(IsValidNumericCast(large_positive)); + EXPECT_FALSE(IsValidNumericCast(large_negative)); + + // Negative to size_t. + EXPECT_FALSE(IsValidNumericCast(small_negative)); + EXPECT_FALSE(IsValidNumericCast(large_negative)); + + // From unsigned. + // Small. + EXPECT_TRUE(IsValidNumericCast(size_t_small)); + EXPECT_TRUE(IsValidNumericCast(size_t_small)); + EXPECT_TRUE(IsValidNumericCast(size_t_small)); + EXPECT_TRUE(IsValidNumericCast(size_t_small)); + EXPECT_TRUE(IsValidNumericCast(size_t_small)); + EXPECT_TRUE(IsValidNumericCast(size_t_small)); + EXPECT_TRUE(IsValidNumericCast(size_t_small)); + EXPECT_TRUE(IsValidNumericCast(size_t_small)); + + // Large. + EXPECT_FALSE(IsValidNumericCast(size_t_large)); + EXPECT_FALSE(IsValidNumericCast(size_t_large)); + EXPECT_FALSE(IsValidNumericCast(size_t_large)); + EXPECT_FALSE(IsValidNumericCast(size_t_large)); + EXPECT_FALSE(IsValidNumericCast(size_t_large)); + EXPECT_TRUE(IsValidNumericCast(size_t_large)); + EXPECT_TRUE(IsValidNumericCast(size_t_large)); + EXPECT_TRUE(IsValidNumericCast(size_t_large)); + + // Various edge cases. + EXPECT_TRUE(IsValidNumericCast(static_cast(SHRT_MIN))); + EXPECT_FALSE( + IsValidNumericCast(static_cast(SHRT_MIN))); + EXPECT_FALSE(IsValidNumericCast(SHRT_MIN)); + + // Confirm that checked_numeric_cast<> actually compiles. + std::vector v; + unsigned int checked_size = + base::checked_numeric_cast(v.size()); + EXPECT_EQ(0u, checked_size); + +#ifdef RUN_EXHAUSTIVE_TEST + ExhaustiveCheckFrom(); + ExhaustiveCheckFrom(); + ExhaustiveCheckFrom(); + ExhaustiveCheckFrom(); + ExhaustiveCheckFrom(); + ExhaustiveCheckFrom(); + ExhaustiveCheckFrom(); +#endif +} + +} // namespace internal +} // namespace base diff --git a/base/safe_numerics_unittest.nc b/base/safe_numerics_unittest.nc new file mode 100644 index 0000000000..4219cd56a4 --- /dev/null +++ b/base/safe_numerics_unittest.nc @@ -0,0 +1,29 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/safe_numerics.h" + +using base::internal::IsValidNumericCast; + +#if defined(NCTEST_NO_FLOATING_POINT_1) // [r"size of array is negative"] + +void WontCompile() { + IsValidNumericCast(0.0); +} + +#elif defined(NCTEST_NO_FLOATING_POINT_2) // [r"size of array is negative"] + +void WontCompile() { + IsValidNumericCast(0.0f); +} + +#elif defined(NCTEST_NO_FLOATING_POINT_3) // [r"size of array is negative"] + +void WontCompile() { + IsValidNumericCast(DBL_MAX); +} + +#endif diff --git a/base/safe_strerror_posix.cc b/base/safe_strerror_posix.cc new file mode 100644 index 0000000000..a91bb8de0c --- /dev/null +++ b/base/safe_strerror_posix.cc @@ -0,0 +1,111 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "build/build_config.h" +#include "base/safe_strerror_posix.h" + +#include +#include +#include + +#define USE_HISTORICAL_STRERRO_R (defined(__GLIBC__) || defined(OS_NACL)) + +#if USE_HISTORICAL_STRERRO_R && defined(__GNUC__) +// GCC will complain about the unused second wrap function unless we tell it +// that we meant for them to be potentially unused, which is exactly what this +// attribute is for. +#define POSSIBLY_UNUSED __attribute__((unused)) +#else +#define POSSIBLY_UNUSED +#endif + +#if USE_HISTORICAL_STRERRO_R +// glibc has two strerror_r functions: a historical GNU-specific one that +// returns type char *, and a POSIX.1-2001 compliant one available since 2.3.4 +// that returns int. This wraps the GNU-specific one. +static void POSSIBLY_UNUSED wrap_posix_strerror_r( + char *(*strerror_r_ptr)(int, char *, size_t), + int err, + char *buf, + size_t len) { + // GNU version. + char *rc = (*strerror_r_ptr)(err, buf, len); + if (rc != buf) { + // glibc did not use buf and returned a static string instead. Copy it + // into buf. + buf[0] = '\0'; + strncat(buf, rc, len - 1); + } + // The GNU version never fails. Unknown errors get an "unknown error" message. + // The result is always null terminated. +} +#endif // USE_HISTORICAL_STRERRO_R + +// Wrapper for strerror_r functions that implement the POSIX interface. POSIX +// does not define the behaviour for some of the edge cases, so we wrap it to +// guarantee that they are handled. This is compiled on all POSIX platforms, but +// it will only be used on Linux if the POSIX strerror_r implementation is +// being used (see below). +static void POSSIBLY_UNUSED wrap_posix_strerror_r( + int (*strerror_r_ptr)(int, char *, size_t), + int err, + char *buf, + size_t len) { + int old_errno = errno; + // Have to cast since otherwise we get an error if this is the GNU version + // (but in such a scenario this function is never called). Sadly we can't use + // C++-style casts because the appropriate one is reinterpret_cast but it's + // considered illegal to reinterpret_cast a type to itself, so we get an + // error in the opposite case. + int result = (*strerror_r_ptr)(err, buf, len); + if (result == 0) { + // POSIX is vague about whether the string will be terminated, although + // it indirectly implies that typically ERANGE will be returned, instead + // of truncating the string. We play it safe by always terminating the + // string explicitly. + buf[len - 1] = '\0'; + } else { + // Error. POSIX is vague about whether the return value is itself a system + // error code or something else. On Linux currently it is -1 and errno is + // set. On BSD-derived systems it is a system error and errno is unchanged. + // We try and detect which case it is so as to put as much useful info as + // we can into our message. + int strerror_error; // The error encountered in strerror + int new_errno = errno; + if (new_errno != old_errno) { + // errno was changed, so probably the return value is just -1 or something + // else that doesn't provide any info, and errno is the error. + strerror_error = new_errno; + } else { + // Either the error from strerror_r was the same as the previous value, or + // errno wasn't used. Assume the latter. + strerror_error = result; + } + // snprintf truncates and always null-terminates. + snprintf(buf, + len, + "Error %d while retrieving error %d", + strerror_error, + err); + } + errno = old_errno; +} + +void safe_strerror_r(int err, char *buf, size_t len) { + if (buf == NULL || len <= 0) { + return; + } + // If using glibc (i.e., Linux), the compiler will automatically select the + // appropriate overloaded function based on the function type of strerror_r. + // The other one will be elided from the translation unit since both are + // static. + wrap_posix_strerror_r(&strerror_r, err, buf, len); +} + +std::string safe_strerror(int err) { + const int buffer_size = 256; + char buf[buffer_size]; + safe_strerror_r(err, buf, sizeof(buf)); + return std::string(buf); +} diff --git a/base/safe_strerror_posix.h b/base/safe_strerror_posix.h new file mode 100644 index 0000000000..2f77d84a1b --- /dev/null +++ b/base/safe_strerror_posix.h @@ -0,0 +1,38 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_SAFE_STRERROR_POSIX_H_ +#define BASE_SAFE_STRERROR_POSIX_H_ + +#include + +#include "base/base_export.h" + +// BEFORE using anything from this file, first look at PLOG and friends in +// logging.h and use them instead if applicable. +// +// This file declares safe, portable alternatives to the POSIX strerror() +// function. strerror() is inherently unsafe in multi-threaded apps and should +// never be used. Doing so can cause crashes. Additionally, the thread-safe +// alternative strerror_r varies in semantics across platforms. Use these +// functions instead. + +// Thread-safe strerror function with dependable semantics that never fails. +// It will write the string form of error "err" to buffer buf of length len. +// If there is an error calling the OS's strerror_r() function then a message to +// that effect will be printed into buf, truncating if necessary. The final +// result is always null-terminated. The value of errno is never changed. +// +// Use this instead of strerror_r(). +BASE_EXPORT void safe_strerror_r(int err, char *buf, size_t len); + +// Calls safe_strerror_r with a buffer of suitable size and returns the result +// in a C++ string. +// +// Use this instead of strerror(). Note though that safe_strerror_r will be +// more robust in the case of heap corruption errors, since it doesn't need to +// allocate a string. +BASE_EXPORT std::string safe_strerror(int err); + +#endif // BASE_SAFE_STRERROR_POSIX_H_ diff --git a/base/scoped_clear_errno.h b/base/scoped_clear_errno.h new file mode 100644 index 0000000000..7b972fc85a --- /dev/null +++ b/base/scoped_clear_errno.h @@ -0,0 +1,34 @@ +// 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_SCOPED_CLEAR_ERRNO_H_ +#define BASE_SCOPED_CLEAR_ERRNO_H_ + +#include + +#include "base/basictypes.h" + +namespace base { + +// Simple scoper that saves the current value of errno, resets it to 0, and on +// destruction puts the old value back. +class ScopedClearErrno { + public: + ScopedClearErrno() : old_errno_(errno) { + errno = 0; + } + ~ScopedClearErrno() { + if (errno == 0) + errno = old_errno_; + } + + private: + const int old_errno_; + + DISALLOW_COPY_AND_ASSIGN(ScopedClearErrno); +}; + +} // namespace base + +#endif // BASE_SCOPED_CLEAR_ERRNO_H_ diff --git a/base/scoped_clear_errno_unittest.cc b/base/scoped_clear_errno_unittest.cc new file mode 100644 index 0000000000..8afb33e033 --- /dev/null +++ b/base/scoped_clear_errno_unittest.cc @@ -0,0 +1,30 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/scoped_clear_errno.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +TEST(ScopedClearErrno, TestNoError) { + errno = 1; + { + ScopedClearErrno clear_error; + EXPECT_EQ(0, errno); + } + EXPECT_EQ(1, errno); +} + +TEST(ScopedClearErrno, TestError) { + errno = 1; + { + ScopedClearErrno clear_error; + errno = 2; + } + EXPECT_EQ(2, errno); +} + +} // namespace base diff --git a/base/scoped_native_library.cc b/base/scoped_native_library.cc new file mode 100644 index 0000000000..7290d29595 --- /dev/null +++ b/base/scoped_native_library.cc @@ -0,0 +1,44 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/scoped_native_library.h" + +namespace base { + +ScopedNativeLibrary::ScopedNativeLibrary() : library_(NULL) { +} + +ScopedNativeLibrary::ScopedNativeLibrary(NativeLibrary library) + : library_(library) { +} + +ScopedNativeLibrary::ScopedNativeLibrary(const FilePath& library_path) { + library_ = base::LoadNativeLibrary(library_path, NULL); +} + +ScopedNativeLibrary::~ScopedNativeLibrary() { + if (library_) + base::UnloadNativeLibrary(library_); +} + +void* ScopedNativeLibrary::GetFunctionPointer( + const char* function_name) const { + if (!library_) + return NULL; + return base::GetFunctionPointerFromNativeLibrary(library_, function_name); +} + +void ScopedNativeLibrary::Reset(NativeLibrary library) { + if (library_) + base::UnloadNativeLibrary(library_); + library_ = library; +} + +NativeLibrary ScopedNativeLibrary::Release() { + NativeLibrary result = library_; + library_ = NULL; + return result; +} + +} // namespace base diff --git a/base/scoped_native_library.h b/base/scoped_native_library.h new file mode 100644 index 0000000000..e9923f4342 --- /dev/null +++ b/base/scoped_native_library.h @@ -0,0 +1,52 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_SCOPED_NATIVE_LIBRARY_H_ +#define BASE_SCOPED_NATIVE_LIBRARY_H_ + +#include "base/base_export.h" +#include "base/native_library.h" + +namespace base { + +class FilePath; + +// A class which encapsulates a base::NativeLibrary object available only in a +// scope. +// This class automatically unloads the loaded library in its destructor. +class BASE_EXPORT ScopedNativeLibrary { + public: + // Initializes with a NULL library. + ScopedNativeLibrary(); + + // Takes ownership of the given library handle. + explicit ScopedNativeLibrary(NativeLibrary library); + + // Opens the given library and manages its lifetime. + explicit ScopedNativeLibrary(const FilePath& library_path); + + ~ScopedNativeLibrary(); + + // Returns true if there's a valid library loaded. + bool is_valid() const { return !!library_; } + + void* GetFunctionPointer(const char* function_name) const; + + // Takes ownership of the given library handle. Any existing handle will + // be freed. + void Reset(NativeLibrary library); + + // Returns the native library handle and removes it from this object. The + // caller must manage the lifetime of the handle. + NativeLibrary Release(); + + private: + NativeLibrary library_; + + DISALLOW_COPY_AND_ASSIGN(ScopedNativeLibrary); +}; + +} // namespace base + +#endif // BASE_MEMORY_NATIVE_LIBRARY_H_ diff --git a/base/scoped_native_library_unittest.cc b/base/scoped_native_library_unittest.cc new file mode 100644 index 0000000000..c120555ec6 --- /dev/null +++ b/base/scoped_native_library_unittest.cc @@ -0,0 +1,37 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/scoped_native_library.h" +#if defined(OS_WIN) +#include "base/files/file_path.h" +#endif + +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +// Tests whether or not a function pointer retrieved via ScopedNativeLibrary +// is available only in a scope. +TEST(ScopedNativeLibrary, Basic) { +#if defined(OS_WIN) + // Get the pointer to DirectDrawCreate() from "ddraw.dll" and verify it + // is valid only in this scope. + // FreeLibrary() doesn't actually unload a DLL until its reference count + // becomes zero, i.e. this function pointer is still valid if the DLL used + // in this test is also used by another part of this executable. + // So, this test uses "ddraw.dll", which is not used by Chrome at all but + // installed on all versions of Windows. + FARPROC test_function; + { + FilePath path(GetNativeLibraryName(L"ddraw")); + ScopedNativeLibrary library(path); + test_function = reinterpret_cast( + library.GetFunctionPointer("DirectDrawCreate")); + EXPECT_EQ(0, IsBadCodePtr(test_function)); + } + EXPECT_NE(0, IsBadCodePtr(test_function)); +#endif +} + +} // namespace base diff --git a/base/scoped_observer.h b/base/scoped_observer.h new file mode 100644 index 0000000000..eae6367204 --- /dev/null +++ b/base/scoped_observer.h @@ -0,0 +1,54 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_SCOPED_OBSERVER_H_ +#define BASE_SCOPED_OBSERVER_H_ + +#include +#include + +#include "base/basictypes.h" + +// ScopedObserver is used to keep track of the set of sources an object has +// attached itself to as an observer. When ScopedObserver is destroyed it +// removes the object as an observer from all sources it has been added to. +template +class ScopedObserver { + public: + explicit ScopedObserver(Observer* observer) : observer_(observer) {} + + ~ScopedObserver() { + for (size_t i = 0; i < sources_.size(); ++i) + sources_[i]->RemoveObserver(observer_); + } + + // Adds the object passed to the constructor as an observer on |source|. + void Add(Source* source) { + sources_.push_back(source); + source->AddObserver(observer_); + } + + // Remove the object passed to the constructor as an observer from |source|. + void Remove(Source* source) { + sources_.erase(std::find(sources_.begin(), sources_.end(), source)); + source->RemoveObserver(observer_); + } + + bool IsObserving(Source* source) const { + for (size_t i = 0; i < sources_.size(); ++i) { + if (sources_[i] == source) + return true; + } + return false; + } + + private: + Observer* observer_; + + std::vector sources_; + + DISALLOW_COPY_AND_ASSIGN(ScopedObserver); +}; + +#endif // BASE_SCOPED_OBSERVER_H_ diff --git a/base/security_unittest.cc b/base/security_unittest.cc new file mode 100644 index 0000000000..5b266b0a11 --- /dev/null +++ b/base/security_unittest.cc @@ -0,0 +1,306 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "build/build_config.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_POSIX) +#include +#include +#endif + +using std::nothrow; +using std::numeric_limits; + +namespace { + +// This function acts as a compiler optimization barrier. We use it to +// prevent the compiler from making an expression a compile-time constant. +// We also use it so that the compiler doesn't discard certain return values +// as something we don't need (see the comment with calloc below). +template +Type HideValueFromCompiler(volatile Type value) { +#if defined(__GNUC__) + // In a GCC compatible compiler (GCC or Clang), make this compiler barrier + // more robust than merely using "volatile". + __asm__ volatile ("" : "+r" (value)); +#endif // __GNUC__ + return value; +} + +// - NO_TCMALLOC (should be defined if we compile with linux_use_tcmalloc=0) +// - ADDRESS_SANITIZER because it has its own memory allocator +// - IOS does not use tcmalloc +// - OS_MACOSX does not use tcmalloc +#if !defined(NO_TCMALLOC) && !defined(ADDRESS_SANITIZER) && \ + !defined(OS_IOS) && !defined(OS_MACOSX) + #define TCMALLOC_TEST(function) function +#else + #define TCMALLOC_TEST(function) DISABLED_##function +#endif + +// TODO(jln): switch to std::numeric_limits::max() when we switch to +// C++11. +const size_t kTooBigAllocSize = INT_MAX; + +// Detect runtime TCMalloc bypasses. +bool IsTcMallocBypassed() { +#if defined(OS_LINUX) || defined(OS_CHROMEOS) + // This should detect a TCMalloc bypass from Valgrind. + char* g_slice = getenv("G_SLICE"); + if (g_slice && !strcmp(g_slice, "always-malloc")) + return true; +#elif defined(OS_WIN) + // This should detect a TCMalloc bypass from setting + // the CHROME_ALLOCATOR environment variable. + char* allocator = getenv("CHROME_ALLOCATOR"); + if (allocator && strcmp(allocator, "tcmalloc")) + return true; +#endif + return false; +} + +bool CallocDiesOnOOM() { +// The wrapper function in base/process_util_linux.cc that is used when we +// compile without TCMalloc will just die on OOM instead of returning NULL. +// This function is explicitly disabled if we compile with AddressSanitizer, +// MemorySanitizer or ThreadSanitizer. +#if defined(OS_LINUX) && defined(NO_TCMALLOC) && \ + (!defined(ADDRESS_SANITIZER) && \ + !defined(MEMORY_SANITIZER) && \ + !defined(THREAD_SANITIZER)) + return true; +#else + return false; +#endif +} + +// Fake test that allow to know the state of TCMalloc by looking at bots. +TEST(SecurityTest, TCMALLOC_TEST(IsTCMallocDynamicallyBypassed)) { + printf("Malloc is dynamically bypassed: %s\n", + IsTcMallocBypassed() ? "yes." : "no."); +} + +// The MemoryAllocationRestrictions* tests test that we can not allocate a +// memory range that cannot be indexed via an int. This is used to mitigate +// vulnerabilities in libraries that use int instead of size_t. See +// crbug.com/169327. + +TEST(SecurityTest, TCMALLOC_TEST(MemoryAllocationRestrictionsMalloc)) { + if (!IsTcMallocBypassed()) { + scoped_ptr ptr(static_cast( + HideValueFromCompiler(malloc(kTooBigAllocSize)))); + ASSERT_TRUE(!ptr); + } +} + +TEST(SecurityTest, TCMALLOC_TEST(MemoryAllocationRestrictionsCalloc)) { + if (!IsTcMallocBypassed()) { + scoped_ptr ptr(static_cast( + HideValueFromCompiler(calloc(kTooBigAllocSize, 1)))); + ASSERT_TRUE(!ptr); + } +} + +TEST(SecurityTest, TCMALLOC_TEST(MemoryAllocationRestrictionsRealloc)) { + if (!IsTcMallocBypassed()) { + char* orig_ptr = static_cast(malloc(1)); + ASSERT_TRUE(orig_ptr); + scoped_ptr ptr(static_cast( + HideValueFromCompiler(realloc(orig_ptr, kTooBigAllocSize)))); + ASSERT_TRUE(!ptr); + // If realloc() did not succeed, we need to free orig_ptr. + free(orig_ptr); + } +} + +typedef struct { + char large_array[kTooBigAllocSize]; +} VeryLargeStruct; + +TEST(SecurityTest, TCMALLOC_TEST(MemoryAllocationRestrictionsNew)) { + if (!IsTcMallocBypassed()) { + scoped_ptr ptr( + HideValueFromCompiler(new (nothrow) VeryLargeStruct)); + ASSERT_TRUE(!ptr); + } +} + +TEST(SecurityTest, TCMALLOC_TEST(MemoryAllocationRestrictionsNewArray)) { + if (!IsTcMallocBypassed()) { + scoped_ptr ptr( + HideValueFromCompiler(new (nothrow) char[kTooBigAllocSize])); + ASSERT_TRUE(!ptr); + } +} + +// The tests bellow check for overflows in new[] and calloc(). + +#if defined(OS_IOS) || defined(OS_WIN) + #define DISABLE_ON_IOS_AND_WIN(function) DISABLED_##function +#else + #define DISABLE_ON_IOS_AND_WIN(function) function +#endif + +// There are platforms where these tests are known to fail. We would like to +// be able to easily check the status on the bots, but marking tests as +// FAILS_ is too clunky. +void OverflowTestsSoftExpectTrue(bool overflow_detected) { + if (!overflow_detected) { +#if defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_MACOSX) + // Sadly, on Linux, Android, and OSX we don't have a good story yet. Don't + // fail the test, but report. + printf("Platform has overflow: %s\n", + !overflow_detected ? "yes." : "no."); +#else + // Otherwise, fail the test. (Note: EXPECT are ok in subfunctions, ASSERT + // aren't). + EXPECT_TRUE(overflow_detected); +#endif + } +} + +// Test array[TooBig][X] and array[X][TooBig] allocations for int overflows. +// IOS doesn't honor nothrow, so disable the test there. +// Crashes on Windows Dbg builds, disable there as well. +TEST(SecurityTest, DISABLE_ON_IOS_AND_WIN(NewOverflow)) { + const size_t kArraySize = 4096; + // We want something "dynamic" here, so that the compiler doesn't + // immediately reject crazy arrays. + const size_t kDynamicArraySize = HideValueFromCompiler(kArraySize); + // numeric_limits are still not constexpr until we switch to C++11, so we + // use an ugly cast. + const size_t kMaxSizeT = ~static_cast(0); + ASSERT_EQ(numeric_limits::max(), kMaxSizeT); + const size_t kArraySize2 = kMaxSizeT / kArraySize + 10; + const size_t kDynamicArraySize2 = HideValueFromCompiler(kArraySize2); + { + scoped_ptr array_pointer(new (nothrow) + char[kDynamicArraySize2][kArraySize]); + OverflowTestsSoftExpectTrue(!array_pointer); + } + // On windows, the compiler prevents static array sizes of more than + // 0x7fffffff (error C2148). +#if !defined(OS_WIN) || !defined(ARCH_CPU_64_BITS) + { + scoped_ptr array_pointer(new (nothrow) + char[kDynamicArraySize][kArraySize2]); + OverflowTestsSoftExpectTrue(!array_pointer); + } +#endif // !defined(OS_WIN) || !defined(ARCH_CPU_64_BITS) +} + +// Call calloc(), eventually free the memory and return whether or not +// calloc() did succeed. +bool CallocReturnsNull(size_t nmemb, size_t size) { + scoped_ptr array_pointer( + static_cast(calloc(nmemb, size))); + // We need the call to HideValueFromCompiler(): we have seen LLVM + // optimize away the call to calloc() entirely and assume + // the pointer to not be NULL. + return HideValueFromCompiler(array_pointer.get()) == NULL; +} + +// Test if calloc() can overflow. +TEST(SecurityTest, CallocOverflow) { + const size_t kArraySize = 4096; + const size_t kMaxSizeT = numeric_limits::max(); + const size_t kArraySize2 = kMaxSizeT / kArraySize + 10; + if (!CallocDiesOnOOM()) { + EXPECT_TRUE(CallocReturnsNull(kArraySize, kArraySize2)); + EXPECT_TRUE(CallocReturnsNull(kArraySize2, kArraySize)); + } else { + // It's also ok for calloc to just terminate the process. +#if defined(GTEST_HAS_DEATH_TEST) + EXPECT_DEATH(CallocReturnsNull(kArraySize, kArraySize2), ""); + EXPECT_DEATH(CallocReturnsNull(kArraySize2, kArraySize), ""); +#endif // GTEST_HAS_DEATH_TEST + } +} + +#if (defined(OS_LINUX) || defined(OS_CHROMEOS)) && defined(__x86_64__) +// Useful for debugging. +void PrintProcSelfMaps() { + int fd = open("/proc/self/maps", O_RDONLY); + file_util::ScopedFD fd_closer(&fd); + ASSERT_GE(fd, 0); + char buffer[1<<13]; + int ret; + ret = read(fd, buffer, sizeof(buffer) - 1); + ASSERT_GT(ret, 0); + buffer[ret - 1] = 0; + fprintf(stdout, "%s\n", buffer); +} + +// Check if ptr1 and ptr2 are separated by less than size chars. +bool ArePointersToSameArea(void* ptr1, void* ptr2, size_t size) { + ptrdiff_t ptr_diff = reinterpret_cast(std::max(ptr1, ptr2)) - + reinterpret_cast(std::min(ptr1, ptr2)); + return static_cast(ptr_diff) <= size; +} + +// Check if TCMalloc uses an underlying random memory allocator. +TEST(SecurityTest, TCMALLOC_TEST(RandomMemoryAllocations)) { + if (IsTcMallocBypassed()) + return; + size_t kPageSize = 4096; // We support x86_64 only. + // Check that malloc() returns an address that is neither the kernel's + // un-hinted mmap area, nor the current brk() area. The first malloc() may + // not be at a random address because TCMalloc will first exhaust any memory + // that it has allocated early on, before starting the sophisticated + // allocators. + void* default_mmap_heap_address = + mmap(0, kPageSize, PROT_READ|PROT_WRITE, + MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); + ASSERT_NE(default_mmap_heap_address, + static_cast(MAP_FAILED)); + ASSERT_EQ(munmap(default_mmap_heap_address, kPageSize), 0); + void* brk_heap_address = sbrk(0); + ASSERT_NE(brk_heap_address, reinterpret_cast(-1)); + ASSERT_TRUE(brk_heap_address != NULL); + // 1 MB should get us past what TCMalloc pre-allocated before initializing + // the sophisticated allocators. + size_t kAllocSize = 1<<20; + scoped_ptr ptr( + static_cast(malloc(kAllocSize))); + ASSERT_TRUE(ptr != NULL); + // If two pointers are separated by less than 512MB, they are considered + // to be in the same area. + // Our random pointer could be anywhere within 0x3fffffffffff (46bits), + // and we are checking that it's not withing 1GB (30 bits) from two + // addresses (brk and mmap heap). We have roughly one chance out of + // 2^15 to flake. + const size_t kAreaRadius = 1<<29; + bool in_default_mmap_heap = ArePointersToSameArea( + ptr.get(), default_mmap_heap_address, kAreaRadius); + EXPECT_FALSE(in_default_mmap_heap); + + bool in_default_brk_heap = ArePointersToSameArea( + ptr.get(), brk_heap_address, kAreaRadius); + EXPECT_FALSE(in_default_brk_heap); + + // In the implementation, we always mask our random addresses with + // kRandomMask, so we use it as an additional detection mechanism. + const uintptr_t kRandomMask = 0x3fffffffffffULL; + bool impossible_random_address = + reinterpret_cast(ptr.get()) & ~kRandomMask; + EXPECT_FALSE(impossible_random_address); +} + +#endif // (defined(OS_LINUX) || defined(OS_CHROMEOS)) && defined(__x86_64__) + +} // namespace diff --git a/base/sequence_checker.h b/base/sequence_checker.h new file mode 100644 index 0000000000..89bbd7eca9 --- /dev/null +++ b/base/sequence_checker.h @@ -0,0 +1,68 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_SEQUENCE_CHECKER_H_ +#define BASE_SEQUENCE_CHECKER_H_ + +#include "base/memory/ref_counted.h" + +// See comments for the similar block in thread_checker.h. +#if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) +#define ENABLE_SEQUENCE_CHECKER 1 +#else +#define ENABLE_SEQUENCE_CHECKER 0 +#endif + +#if ENABLE_SEQUENCE_CHECKER +#include "base/sequence_checker_impl.h" +#endif + +namespace base { + +class SequencedTaskRunner; + +// Do nothing implementation, for use in release mode. +// +// Note: You should almost always use the SequenceChecker class to get +// the right version for your build configuration. +class SequenceCheckerDoNothing { + public: + bool CalledOnValidSequencedThread() const { + return true; + } + + void DetachFromSequence() {} +}; + +// SequenceChecker is a helper class used to help verify that some +// methods of a class are called in sequence -- that is, called from +// the same SequencedTaskRunner. It is a generalization of +// ThreadChecker; see comments in sequence_checker_impl.h for details. +// +// Example: +// class MyClass { +// public: +// void Foo() { +// DCHECK(sequence_checker_.CalledOnValidSequence()); +// ... (do stuff) ... +// } +// +// private: +// SequenceChecker sequence_checker_; +// } +// +// In Release mode, CalledOnValidSequence will always return true. +#if ENABLE_SEQUENCE_CHECKER +class SequenceChecker : public SequenceCheckerImpl { +}; +#else +class SequenceChecker : public SequenceCheckerDoNothing { +}; +#endif // ENABLE_SEQUENCE_CHECKER + +#undef ENABLE_SEQUENCE_CHECKER + +} // namespace base + +#endif // BASE_SEQUENCE_CHECKER_H_ diff --git a/base/sequence_checker_impl.cc b/base/sequence_checker_impl.cc new file mode 100644 index 0000000000..e95b8ee5f3 --- /dev/null +++ b/base/sequence_checker_impl.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/sequence_checker_impl.h" + +namespace base { + +SequenceCheckerImpl::SequenceCheckerImpl() + : sequence_token_assigned_(false) { + AutoLock auto_lock(lock_); + EnsureSequenceTokenAssigned(); +} + +SequenceCheckerImpl::~SequenceCheckerImpl() {} + +bool SequenceCheckerImpl::CalledOnValidSequencedThread() const { + AutoLock auto_lock(lock_); + EnsureSequenceTokenAssigned(); + + // If this thread is not associated with a SequencedWorkerPool, + // SequenceChecker behaves as a ThreadChecker. See header for details. + if (!sequence_token_.IsValid()) + return thread_checker_.CalledOnValidThread(); + + return sequence_token_.Equals( + SequencedWorkerPool::GetSequenceTokenForCurrentThread()); +} + +void SequenceCheckerImpl::DetachFromSequence() { + AutoLock auto_lock(lock_); + thread_checker_.DetachFromThread(); + sequence_token_assigned_ = false; + sequence_token_ = SequencedWorkerPool::SequenceToken(); +} + +void SequenceCheckerImpl::EnsureSequenceTokenAssigned() const { + lock_.AssertAcquired(); + if (sequence_token_assigned_) + return; + + sequence_token_assigned_ = true; + sequence_token_ = SequencedWorkerPool::GetSequenceTokenForCurrentThread(); +} + +} // namespace base diff --git a/base/sequence_checker_impl.h b/base/sequence_checker_impl.h new file mode 100644 index 0000000000..741aafee64 --- /dev/null +++ b/base/sequence_checker_impl.h @@ -0,0 +1,52 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_SEQUENCE_CHECKER_IMPL_H_ +#define BASE_SEQUENCE_CHECKER_IMPL_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/synchronization/lock.h" +#include "base/threading/sequenced_worker_pool.h" +#include "base/threading/thread_checker_impl.h" + +namespace base { + +// SequenceCheckerImpl is used to help verify that some methods of a +// class are called in sequence -- that is, called from the same +// SequencedTaskRunner. It is a generalization of ThreadChecker; in +// particular, it behaves exactly like ThreadChecker if constructed +// on a thread that is not part of a SequencedWorkerPool. +class BASE_EXPORT SequenceCheckerImpl { + public: + SequenceCheckerImpl(); + ~SequenceCheckerImpl(); + + // Returns whether the we are being called on the same sequence token + // as previous calls. If there is no associated sequence, then returns + // whether we are being called on the underlying ThreadChecker's thread. + bool CalledOnValidSequencedThread() const; + + // Unbinds the checker from the currently associated sequence. The + // checker will be re-bound on the next call to CalledOnValidSequence(). + void DetachFromSequence(); + + private: + void EnsureSequenceTokenAssigned() const; + + // Guards all variables below. + mutable Lock lock_; + + // Used if |sequence_token_| is not valid. + ThreadCheckerImpl thread_checker_; + mutable bool sequence_token_assigned_; + + mutable SequencedWorkerPool::SequenceToken sequence_token_; + + DISALLOW_COPY_AND_ASSIGN(SequenceCheckerImpl); +}; + +} // namespace base + +#endif // BASE_SEQUENCE_CHECKER_IMPL_H_ diff --git a/base/sequence_checker_unittest.cc b/base/sequence_checker_unittest.cc new file mode 100644 index 0000000000..7df7614926 --- /dev/null +++ b/base/sequence_checker_unittest.cc @@ -0,0 +1,339 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/sequence_checker.h" +#include "base/test/sequenced_worker_pool_owner.h" +#include "base/threading/thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +// Duplicated from base/sequence_checker.h so that we can be good citizens +// there and undef the macro. +#if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) +#define ENABLE_SEQUENCE_CHECKER 1 +#else +#define ENABLE_SEQUENCE_CHECKER 0 +#endif + +namespace base { + +namespace { + +const size_t kNumWorkerThreads = 3; + +// Simple class to exercise the basics of SequenceChecker. +// DoStuff should verify that it's called on a valid sequenced thread. +// SequenceCheckedObject can be destroyed on any thread (like WeakPtr). +class SequenceCheckedObject { + public: + SequenceCheckedObject() {} + ~SequenceCheckedObject() {} + + // Verifies that it was called on the same thread as the constructor. + void DoStuff() { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + } + + void DetachFromSequence() { + sequence_checker_.DetachFromSequence(); + } + + private: + SequenceChecker sequence_checker_; + + DISALLOW_COPY_AND_ASSIGN(SequenceCheckedObject); +}; + +class SequenceCheckerTest : public testing::Test { + public: + SequenceCheckerTest() : other_thread_("sequence_checker_test_other_thread") {} + + virtual ~SequenceCheckerTest() {} + + virtual void SetUp() OVERRIDE { + other_thread_.Start(); + ResetPool(); + } + + virtual void TearDown() OVERRIDE { + other_thread_.Stop(); + pool()->Shutdown(); + } + + protected: + base::Thread* other_thread() { return &other_thread_; } + + const scoped_refptr& pool() { + return pool_owner_->pool(); + } + + void PostDoStuffToWorkerPool(SequenceCheckedObject* sequence_checked_object, + const std::string& token_name) { + pool()->PostNamedSequencedWorkerTask( + token_name, + FROM_HERE, + base::Bind(&SequenceCheckedObject::DoStuff, + base::Unretained(sequence_checked_object))); + } + + void PostDoStuffToOtherThread( + SequenceCheckedObject* sequence_checked_object) { + other_thread()->message_loop()->PostTask( + FROM_HERE, + base::Bind(&SequenceCheckedObject::DoStuff, + base::Unretained(sequence_checked_object))); + } + + void PostDeleteToOtherThread( + scoped_ptr sequence_checked_object) { + other_thread()->message_loop()->DeleteSoon( + FROM_HERE, + sequence_checked_object.release()); + } + + // Destroys the SequencedWorkerPool instance, blocking until it is fully shut + // down, and creates a new instance. + void ResetPool() { + pool_owner_.reset(new SequencedWorkerPoolOwner(kNumWorkerThreads, "test")); + } + + void MethodOnDifferentThreadDeathTest(); + void DetachThenCallFromDifferentThreadDeathTest(); + void DifferentSequenceTokensDeathTest(); + void WorkerPoolAndSimpleThreadDeathTest(); + void TwoDifferentWorkerPoolsDeathTest(); + + private: + MessageLoop message_loop_; // Needed by SequencedWorkerPool to function. + base::Thread other_thread_; + scoped_ptr pool_owner_; +}; + +TEST_F(SequenceCheckerTest, CallsAllowedOnSameThread) { + scoped_ptr sequence_checked_object( + new SequenceCheckedObject); + + // Verify that DoStuff doesn't assert. + sequence_checked_object->DoStuff(); + + // Verify that the destructor doesn't assert. + sequence_checked_object.reset(); +} + +TEST_F(SequenceCheckerTest, DestructorAllowedOnDifferentThread) { + scoped_ptr sequence_checked_object( + new SequenceCheckedObject); + + // Verify the destructor doesn't assert when called on a different thread. + PostDeleteToOtherThread(sequence_checked_object.Pass()); + other_thread()->Stop(); +} + +TEST_F(SequenceCheckerTest, DetachFromSequence) { + scoped_ptr sequence_checked_object( + new SequenceCheckedObject); + + // Verify that DoStuff doesn't assert when called on a different thread after + // a call to DetachFromSequence. + sequence_checked_object->DetachFromSequence(); + + PostDoStuffToOtherThread(sequence_checked_object.get()); + other_thread()->Stop(); +} + +TEST_F(SequenceCheckerTest, SameSequenceTokenValid) { + scoped_ptr sequence_checked_object( + new SequenceCheckedObject); + + sequence_checked_object->DetachFromSequence(); + PostDoStuffToWorkerPool(sequence_checked_object.get(), "A"); + PostDoStuffToWorkerPool(sequence_checked_object.get(), "A"); + PostDoStuffToWorkerPool(sequence_checked_object.get(), "A"); + PostDoStuffToWorkerPool(sequence_checked_object.get(), "A"); + pool()->FlushForTesting(); + + PostDeleteToOtherThread(sequence_checked_object.Pass()); + other_thread()->Stop(); +} + +TEST_F(SequenceCheckerTest, DetachSequenceTokenValid) { + scoped_ptr sequence_checked_object( + new SequenceCheckedObject); + + sequence_checked_object->DetachFromSequence(); + PostDoStuffToWorkerPool(sequence_checked_object.get(), "A"); + PostDoStuffToWorkerPool(sequence_checked_object.get(), "A"); + pool()->FlushForTesting(); + + sequence_checked_object->DetachFromSequence(); + PostDoStuffToWorkerPool(sequence_checked_object.get(), "B"); + PostDoStuffToWorkerPool(sequence_checked_object.get(), "B"); + pool()->FlushForTesting(); + + PostDeleteToOtherThread(sequence_checked_object.Pass()); + other_thread()->Stop(); +} + +#if GTEST_HAS_DEATH_TEST || !ENABLE_SEQUENCE_CHECKER + +void SequenceCheckerTest::MethodOnDifferentThreadDeathTest() { + scoped_ptr sequence_checked_object( + new SequenceCheckedObject); + + // DoStuff should assert in debug builds only when called on a + // different thread. + PostDoStuffToOtherThread(sequence_checked_object.get()); + other_thread()->Stop(); +} + +#if ENABLE_SEQUENCE_CHECKER +TEST_F(SequenceCheckerTest, MethodNotAllowedOnDifferentThreadDeathTestInDebug) { + // The default style "fast" does not support multi-threaded tests. + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; + ASSERT_DEATH({ + MethodOnDifferentThreadDeathTest(); + }, ""); +} +#else +TEST_F(SequenceCheckerTest, MethodAllowedOnDifferentThreadDeathTestInRelease) { + MethodOnDifferentThreadDeathTest(); +} +#endif // ENABLE_SEQUENCE_CHECKER + +void SequenceCheckerTest::DetachThenCallFromDifferentThreadDeathTest() { + scoped_ptr sequence_checked_object( + new SequenceCheckedObject); + + // DoStuff doesn't assert when called on a different thread + // after a call to DetachFromSequence. + sequence_checked_object->DetachFromSequence(); + PostDoStuffToOtherThread(sequence_checked_object.get()); + other_thread()->Stop(); + + // DoStuff should assert in debug builds only after moving to + // another thread. + sequence_checked_object->DoStuff(); +} + +#if ENABLE_SEQUENCE_CHECKER +TEST_F(SequenceCheckerTest, DetachFromSequenceDeathTestInDebug) { + // The default style "fast" does not support multi-threaded tests. + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; + ASSERT_DEATH({ + DetachThenCallFromDifferentThreadDeathTest(); + }, ""); +} +#else +TEST_F(SequenceCheckerTest, DetachFromThreadDeathTestInRelease) { + DetachThenCallFromDifferentThreadDeathTest(); +} +#endif // ENABLE_SEQUENCE_CHECKER + +void SequenceCheckerTest::DifferentSequenceTokensDeathTest() { + scoped_ptr sequence_checked_object( + new SequenceCheckedObject); + + sequence_checked_object->DetachFromSequence(); + PostDoStuffToWorkerPool(sequence_checked_object.get(), "A"); + PostDoStuffToWorkerPool(sequence_checked_object.get(), "A"); + PostDoStuffToWorkerPool(sequence_checked_object.get(), "B"); + PostDoStuffToWorkerPool(sequence_checked_object.get(), "B"); + pool()->FlushForTesting(); + + PostDeleteToOtherThread(sequence_checked_object.Pass()); + other_thread()->Stop(); +} + +#if ENABLE_SEQUENCE_CHECKER +TEST_F(SequenceCheckerTest, DifferentSequenceTokensDeathTestInDebug) { + // The default style "fast" does not support multi-threaded tests. + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; + ASSERT_DEATH({ + DifferentSequenceTokensDeathTest(); + }, ""); +} +#else +TEST_F(SequenceCheckerTest, + DifferentSequenceTokensDeathTestInRelease) { + DifferentSequenceTokensDeathTest(); +} +#endif // ENABLE_SEQUENCE_CHECKER + +void SequenceCheckerTest::WorkerPoolAndSimpleThreadDeathTest() { + scoped_ptr sequence_checked_object( + new SequenceCheckedObject); + + sequence_checked_object->DetachFromSequence(); + PostDoStuffToWorkerPool(sequence_checked_object.get(), "A"); + PostDoStuffToWorkerPool(sequence_checked_object.get(), "A"); + pool()->FlushForTesting(); + + PostDoStuffToOtherThread(sequence_checked_object.get()); + other_thread()->Stop(); +} + +#if ENABLE_SEQUENCE_CHECKER +TEST_F(SequenceCheckerTest, WorkerPoolAndSimpleThreadDeathTestInDebug) { + // The default style "fast" does not support multi-threaded tests. + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; + ASSERT_DEATH({ + WorkerPoolAndSimpleThreadDeathTest(); + }, ""); +} +#else +TEST_F(SequenceCheckerTest, + WorkerPoolAndSimpleThreadDeathTestInRelease) { + WorkerPoolAndSimpleThreadDeathTest(); +} +#endif // ENABLE_SEQUENCE_CHECKER + +void SequenceCheckerTest::TwoDifferentWorkerPoolsDeathTest() { + scoped_ptr sequence_checked_object( + new SequenceCheckedObject); + + sequence_checked_object->DetachFromSequence(); + PostDoStuffToWorkerPool(sequence_checked_object.get(), "A"); + PostDoStuffToWorkerPool(sequence_checked_object.get(), "A"); + pool()->FlushForTesting(); + + SequencedWorkerPoolOwner second_pool_owner(kNumWorkerThreads, "test2"); + second_pool_owner.pool()->PostNamedSequencedWorkerTask( + "A", + FROM_HERE, + base::Bind(&SequenceCheckedObject::DoStuff, + base::Unretained(sequence_checked_object.get()))); + second_pool_owner.pool()->FlushForTesting(); + second_pool_owner.pool()->Shutdown(); +} + +#if ENABLE_SEQUENCE_CHECKER +TEST_F(SequenceCheckerTest, TwoDifferentWorkerPoolsDeathTestInDebug) { + // The default style "fast" does not support multi-threaded tests. + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; + ASSERT_DEATH({ + TwoDifferentWorkerPoolsDeathTest(); + }, ""); +} +#else +TEST_F(SequenceCheckerTest, + TwoDifferentWorkerPoolsDeathTestInRelease) { + TwoDifferentWorkerPoolsDeathTest(); +} +#endif // ENABLE_SEQUENCE_CHECKER + +#endif // GTEST_HAS_DEATH_TEST || !ENABLE_SEQUENCE_CHECKER + +} // namespace + +} // namespace base + +// Just in case we ever get lumped together with other compilation units. +#undef ENABLE_SEQUENCE_CHECKER diff --git a/base/sequenced_task_runner.cc b/base/sequenced_task_runner.cc new file mode 100644 index 0000000000..00d4048815 --- /dev/null +++ b/base/sequenced_task_runner.cc @@ -0,0 +1,31 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/sequenced_task_runner.h" + +#include "base/bind.h" + +namespace base { + +bool SequencedTaskRunner::PostNonNestableTask( + const tracked_objects::Location& from_here, + const Closure& task) { + return PostNonNestableDelayedTask(from_here, task, base::TimeDelta()); +} + +bool SequencedTaskRunner::DeleteSoonInternal( + const tracked_objects::Location& from_here, + void(*deleter)(const void*), + const void* object) { + return PostNonNestableTask(from_here, Bind(deleter, object)); +} + +bool SequencedTaskRunner::ReleaseSoonInternal( + const tracked_objects::Location& from_here, + void(*releaser)(const void*), + const void* object) { + return PostNonNestableTask(from_here, Bind(releaser, object)); +} + +} // namespace base diff --git a/base/sequenced_task_runner.h b/base/sequenced_task_runner.h new file mode 100644 index 0000000000..f6ca646781 --- /dev/null +++ b/base/sequenced_task_runner.h @@ -0,0 +1,159 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_SEQUENCED_TASKRUNNER_H_ +#define BASE_SEQUENCED_TASKRUNNER_H_ + +#include "base/base_export.h" +#include "base/sequenced_task_runner_helpers.h" +#include "base/task_runner.h" + +namespace base { + +// A SequencedTaskRunner is a subclass of TaskRunner that provides +// additional guarantees on the order that tasks are started, as well +// as guarantees on when tasks are in sequence, i.e. one task finishes +// before the other one starts. +// +// Summary +// ------- +// Non-nested tasks with the same delay will run one by one in FIFO +// order. +// +// Detailed guarantees +// ------------------- +// +// SequencedTaskRunner also adds additional methods for posting +// non-nestable tasks. In general, an implementation of TaskRunner +// may expose task-running methods which are themselves callable from +// within tasks. A non-nestable task is one that is guaranteed to not +// be run from within an already-running task. Conversely, a nestable +// task (the default) is a task that can be run from within an +// already-running task. +// +// The guarantees of SequencedTaskRunner are as follows: +// +// - Given two tasks T2 and T1, T2 will start after T1 starts if: +// +// * T2 is posted after T1; and +// * T2 has equal or higher delay than T1; and +// * T2 is non-nestable or T1 is nestable. +// +// - If T2 will start after T1 starts by the above guarantee, then +// T2 will start after T1 finishes and is destroyed if: +// +// * T2 is non-nestable, or +// * T1 doesn't call any task-running methods. +// +// - If T2 will start after T1 finishes by the above guarantee, then +// all memory changes in T1 and T1's destruction will be visible +// to T2. +// +// - If T2 runs nested within T1 via a call to the task-running +// method M, then all memory changes in T1 up to the call to M +// will be visible to T2, and all memory changes in T2 will be +// visible to T1 from the return from M. +// +// Note that SequencedTaskRunner does not guarantee that tasks are run +// on a single dedicated thread, although the above guarantees provide +// most (but not all) of the same guarantees. If you do need to +// guarantee that tasks are run on a single dedicated thread, see +// SingleThreadTaskRunner (in single_thread_task_runner.h). +// +// Some corollaries to the above guarantees, assuming the tasks in +// question don't call any task-running methods: +// +// - Tasks posted via PostTask are run in FIFO order. +// +// - Tasks posted via PostNonNestableTask are run in FIFO order. +// +// - Tasks posted with the same delay and the same nestable state +// are run in FIFO order. +// +// - A list of tasks with the same nestable state posted in order of +// non-decreasing delay is run in FIFO order. +// +// - A list of tasks posted in order of non-decreasing delay with at +// most a single change in nestable state from nestable to +// non-nestable is run in FIFO order. (This is equivalent to the +// statement of the first guarantee above.) +// +// Some theoretical implementations of SequencedTaskRunner: +// +// - A SequencedTaskRunner that wraps a regular TaskRunner but makes +// sure that only one task at a time is posted to the TaskRunner, +// with appropriate memory barriers in between tasks. +// +// - A SequencedTaskRunner that, for each task, spawns a joinable +// thread to run that task and immediately quit, and then +// immediately joins that thread. +// +// - A SequencedTaskRunner that stores the list of posted tasks and +// has a method Run() that runs each runnable task in FIFO order +// that can be called from any thread, but only if another +// (non-nested) Run() call isn't already happening. +class BASE_EXPORT SequencedTaskRunner : public TaskRunner { + public: + // The two PostNonNestable*Task methods below are like their + // nestable equivalents in TaskRunner, but they guarantee that the + // posted task will not run nested within an already-running task. + // + // A simple corollary is that posting a task as non-nestable can + // only delay when the task gets run. That is, posting a task as + // non-nestable may not affect when the task gets run, or it could + // make it run later than it normally would, but it won't make it + // run earlier than it normally would. + + // TODO(akalin): Get rid of the boolean return value for the methods + // below. + + bool PostNonNestableTask(const tracked_objects::Location& from_here, + const Closure& task); + + virtual bool PostNonNestableDelayedTask( + const tracked_objects::Location& from_here, + const Closure& task, + base::TimeDelta delay) = 0; + + // Submits a non-nestable task to delete the given object. Returns + // true if the object may be deleted at some point in the future, + // and false if the object definitely will not be deleted. + template + bool DeleteSoon(const tracked_objects::Location& from_here, + const T* object) { + return + subtle::DeleteHelperInternal::DeleteViaSequencedTaskRunner( + this, from_here, object); + } + + // Submits a non-nestable task to release the given object. Returns + // true if the object may be released at some point in the future, + // and false if the object definitely will not be released. + template + bool ReleaseSoon(const tracked_objects::Location& from_here, + T* object) { + return + subtle::ReleaseHelperInternal::ReleaseViaSequencedTaskRunner( + this, from_here, object); + } + + protected: + virtual ~SequencedTaskRunner() {} + + private: + template friend class subtle::DeleteHelperInternal; + template friend class subtle::ReleaseHelperInternal; + + bool DeleteSoonInternal(const tracked_objects::Location& from_here, + void(*deleter)(const void*), + const void* object); + + bool ReleaseSoonInternal(const tracked_objects::Location& from_here, + void(*releaser)(const void*), + const void* object); +}; + +} // namespace base + +#endif // BASE_SEQUENCED_TASKRUNNER_H_ diff --git a/base/sequenced_task_runner_helpers.h b/base/sequenced_task_runner_helpers.h new file mode 100644 index 0000000000..2d0d493290 --- /dev/null +++ b/base/sequenced_task_runner_helpers.h @@ -0,0 +1,112 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_SEQUENCED_TASK_RUNNER_HELPERS_H_ +#define BASE_SEQUENCED_TASK_RUNNER_HELPERS_H_ + +#include "base/basictypes.h" + +// TODO(akalin): Investigate whether it's possible to just have +// SequencedTaskRunner use these helpers (instead of MessageLoop). +// Then we can just move these to sequenced_task_runner.h. + +namespace tracked_objects { +class Location; +} + +namespace base { + +namespace subtle { +template class DeleteHelperInternal; +template class ReleaseHelperInternal; +} + +// Template helpers which use function indirection to erase T from the +// function signature while still remembering it so we can call the +// correct destructor/release function. +// +// We use this trick so we don't need to include bind.h in a header +// file like sequenced_task_runner.h. We also wrap the helpers in a +// templated class to make it easier for users of DeleteSoon to +// declare the helper as a friend. +template +class DeleteHelper { + private: + template friend class subtle::DeleteHelperInternal; + + static void DoDelete(const void* object) { + delete reinterpret_cast(object); + } + + DISALLOW_COPY_AND_ASSIGN(DeleteHelper); +}; + +template +class ReleaseHelper { + private: + template friend class subtle::ReleaseHelperInternal; + + static void DoRelease(const void* object) { + reinterpret_cast(object)->Release(); + } + + DISALLOW_COPY_AND_ASSIGN(ReleaseHelper); +}; + +namespace subtle { + +// An internal SequencedTaskRunner-like class helper for DeleteHelper +// and ReleaseHelper. We don't want to expose the Do*() functions +// directly directly since the void* argument makes it possible to +// pass/ an object of the wrong type to delete. Instead, we force +// callers to go through these internal helpers for type +// safety. SequencedTaskRunner-like classes which expose DeleteSoon or +// ReleaseSoon methods should friend the appropriate helper and +// implement a corresponding *Internal method with the following +// signature: +// +// bool(const tracked_objects::Location&, +// void(*function)(const void*), +// void* object) +// +// An implementation of this function should simply create a +// base::Closure from (function, object) and return the result of +// posting the task. +template +class DeleteHelperInternal { + public: + template + static ReturnType DeleteViaSequencedTaskRunner( + SequencedTaskRunnerType* sequenced_task_runner, + const tracked_objects::Location& from_here, + const T* object) { + return sequenced_task_runner->DeleteSoonInternal( + from_here, &DeleteHelper::DoDelete, object); + } + + private: + DISALLOW_COPY_AND_ASSIGN(DeleteHelperInternal); +}; + +template +class ReleaseHelperInternal { + public: + template + static ReturnType ReleaseViaSequencedTaskRunner( + SequencedTaskRunnerType* sequenced_task_runner, + const tracked_objects::Location& from_here, + const T* object) { + return sequenced_task_runner->ReleaseSoonInternal( + from_here, &ReleaseHelper::DoRelease, object); + } + + private: + DISALLOW_COPY_AND_ASSIGN(ReleaseHelperInternal); +}; + +} // namespace subtle + +} // namespace base + +#endif // BASE_SEQUENCED_TASK_RUNNER_HELPERS_H_ diff --git a/base/sha1.h b/base/sha1.h new file mode 100644 index 0000000000..998cccba8d --- /dev/null +++ b/base/sha1.h @@ -0,0 +1,29 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_SHA1_H_ +#define BASE_SHA1_H_ + +#include + +#include "base/base_export.h" + +namespace base { + +// These functions perform SHA-1 operations. + +static const size_t kSHA1Length = 20; // Length in bytes of a SHA-1 hash. + +// Computes the SHA-1 hash of the input string |str| and returns the full +// hash. +BASE_EXPORT std::string SHA1HashString(const std::string& str); + +// Computes the SHA-1 hash of the |len| bytes in |data| and puts the hash +// in |hash|. |hash| must be kSHA1Length bytes long. +BASE_EXPORT void SHA1HashBytes(const unsigned char* data, size_t len, + unsigned char* hash); + +} // namespace base + +#endif // BASE_SHA1_H_ diff --git a/base/sha1_portable.cc b/base/sha1_portable.cc new file mode 100644 index 0000000000..529fc905b7 --- /dev/null +++ b/base/sha1_portable.cc @@ -0,0 +1,215 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/sha1.h" + +#include + +#include "base/basictypes.h" + +namespace base { + +// Implementation of SHA-1. Only handles data in byte-sized blocks, +// which simplifies the code a fair bit. + +// Identifier names follow notation in FIPS PUB 180-3, where you'll +// also find a description of the algorithm: +// http://csrc.nist.gov/publications/fips/fips180-3/fips180-3_final.pdf + +// Usage example: +// +// SecureHashAlgorithm sha; +// while(there is data to hash) +// sha.Update(moredata, size of data); +// sha.Final(); +// memcpy(somewhere, sha.Digest(), 20); +// +// to reuse the instance of sha, call sha.Init(); + +// TODO(jhawkins): Replace this implementation with a per-platform +// implementation using each platform's crypto library. See +// http://crbug.com/47218 + +class SecureHashAlgorithm { + public: + SecureHashAlgorithm() { Init(); } + + static const int kDigestSizeBytes; + + void Init(); + void Update(const void* data, size_t nbytes); + void Final(); + + // 20 bytes of message digest. + const unsigned char* Digest() const { + return reinterpret_cast(H); + } + + private: + void Pad(); + void Process(); + + uint32 A, B, C, D, E; + + uint32 H[5]; + + union { + uint32 W[80]; + uint8 M[64]; + }; + + uint32 cursor; + uint32 l; +}; + +static inline uint32 f(uint32 t, uint32 B, uint32 C, uint32 D) { + if (t < 20) { + return (B & C) | ((~B) & D); + } else if (t < 40) { + return B ^ C ^ D; + } else if (t < 60) { + return (B & C) | (B & D) | (C & D); + } else { + return B ^ C ^ D; + } +} + +static inline uint32 S(uint32 n, uint32 X) { + return (X << n) | (X >> (32-n)); +} + +static inline uint32 K(uint32 t) { + if (t < 20) { + return 0x5a827999; + } else if (t < 40) { + return 0x6ed9eba1; + } else if (t < 60) { + return 0x8f1bbcdc; + } else { + return 0xca62c1d6; + } +} + +static inline void swapends(uint32* t) { + *t = ((*t & 0xff000000) >> 24) | + ((*t & 0xff0000) >> 8) | + ((*t & 0xff00) << 8) | + ((*t & 0xff) << 24); +} + +const int SecureHashAlgorithm::kDigestSizeBytes = 20; + +void SecureHashAlgorithm::Init() { + A = 0; + B = 0; + C = 0; + D = 0; + E = 0; + cursor = 0; + l = 0; + H[0] = 0x67452301; + H[1] = 0xefcdab89; + H[2] = 0x98badcfe; + H[3] = 0x10325476; + H[4] = 0xc3d2e1f0; +} + +void SecureHashAlgorithm::Final() { + Pad(); + Process(); + + for (int t = 0; t < 5; ++t) + swapends(&H[t]); +} + +void SecureHashAlgorithm::Update(const void* data, size_t nbytes) { + const uint8* d = reinterpret_cast(data); + while (nbytes--) { + M[cursor++] = *d++; + if (cursor >= 64) + Process(); + l += 8; + } +} + +void SecureHashAlgorithm::Pad() { + M[cursor++] = 0x80; + + if (cursor > 64-8) { + // pad out to next block + while (cursor < 64) + M[cursor++] = 0; + + Process(); + } + + while (cursor < 64-4) + M[cursor++] = 0; + + M[64-4] = (l & 0xff000000) >> 24; + M[64-3] = (l & 0xff0000) >> 16; + M[64-2] = (l & 0xff00) >> 8; + M[64-1] = (l & 0xff); +} + +void SecureHashAlgorithm::Process() { + uint32 t; + + // Each a...e corresponds to a section in the FIPS 180-3 algorithm. + + // a. + // + // W and M are in a union, so no need to memcpy. + // memcpy(W, M, sizeof(M)); + for (t = 0; t < 16; ++t) + swapends(&W[t]); + + // b. + for (t = 16; t < 80; ++t) + W[t] = S(1, W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16]); + + // c. + A = H[0]; + B = H[1]; + C = H[2]; + D = H[3]; + E = H[4]; + + // d. + for (t = 0; t < 80; ++t) { + uint32 TEMP = S(5, A) + f(t, B, C, D) + E + W[t] + K(t); + E = D; + D = C; + C = S(30, B); + B = A; + A = TEMP; + } + + // e. + H[0] += A; + H[1] += B; + H[2] += C; + H[3] += D; + H[4] += E; + + cursor = 0; +} + +std::string SHA1HashString(const std::string& str) { + char hash[SecureHashAlgorithm::kDigestSizeBytes]; + SHA1HashBytes(reinterpret_cast(str.c_str()), + str.length(), reinterpret_cast(hash)); + return std::string(hash, SecureHashAlgorithm::kDigestSizeBytes); +} + +void SHA1HashBytes(const unsigned char* data, size_t len, + unsigned char* hash) { + SecureHashAlgorithm sha; + sha.Update(data, len); + sha.Final(); + + memcpy(hash, sha.Digest(), SecureHashAlgorithm::kDigestSizeBytes); +} + +} // namespace base diff --git a/base/sha1_unittest.cc b/base/sha1_unittest.cc new file mode 100644 index 0000000000..b29fe4662a --- /dev/null +++ b/base/sha1_unittest.cc @@ -0,0 +1,108 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/sha1.h" + +#include + +#include "base/basictypes.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(SHA1Test, Test1) { + // Example A.1 from FIPS 180-2: one-block message. + std::string input = "abc"; + + int expected[] = { 0xa9, 0x99, 0x3e, 0x36, + 0x47, 0x06, 0x81, 0x6a, + 0xba, 0x3e, 0x25, 0x71, + 0x78, 0x50, 0xc2, 0x6c, + 0x9c, 0xd0, 0xd8, 0x9d }; + + std::string output = base::SHA1HashString(input); + for (size_t i = 0; i < base::kSHA1Length; i++) + EXPECT_EQ(expected[i], output[i] & 0xFF); +} + +TEST(SHA1Test, Test2) { + // Example A.2 from FIPS 180-2: multi-block message. + std::string input = + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"; + + int expected[] = { 0x84, 0x98, 0x3e, 0x44, + 0x1c, 0x3b, 0xd2, 0x6e, + 0xba, 0xae, 0x4a, 0xa1, + 0xf9, 0x51, 0x29, 0xe5, + 0xe5, 0x46, 0x70, 0xf1 }; + + std::string output = base::SHA1HashString(input); + for (size_t i = 0; i < base::kSHA1Length; i++) + EXPECT_EQ(expected[i], output[i] & 0xFF); +} + +TEST(SHA1Test, Test3) { + // Example A.3 from FIPS 180-2: long message. + std::string input(1000000, 'a'); + + int expected[] = { 0x34, 0xaa, 0x97, 0x3c, + 0xd4, 0xc4, 0xda, 0xa4, + 0xf6, 0x1e, 0xeb, 0x2b, + 0xdb, 0xad, 0x27, 0x31, + 0x65, 0x34, 0x01, 0x6f }; + + std::string output = base::SHA1HashString(input); + for (size_t i = 0; i < base::kSHA1Length; i++) + EXPECT_EQ(expected[i], output[i] & 0xFF); +} + +TEST(SHA1Test, Test1Bytes) { + // Example A.1 from FIPS 180-2: one-block message. + std::string input = "abc"; + unsigned char output[base::kSHA1Length]; + + unsigned char expected[] = { 0xa9, 0x99, 0x3e, 0x36, + 0x47, 0x06, 0x81, 0x6a, + 0xba, 0x3e, 0x25, 0x71, + 0x78, 0x50, 0xc2, 0x6c, + 0x9c, 0xd0, 0xd8, 0x9d }; + + base::SHA1HashBytes(reinterpret_cast(input.c_str()), + input.length(), output); + for (size_t i = 0; i < base::kSHA1Length; i++) + EXPECT_EQ(expected[i], output[i]); +} + +TEST(SHA1Test, Test2Bytes) { + // Example A.2 from FIPS 180-2: multi-block message. + std::string input = + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"; + unsigned char output[base::kSHA1Length]; + + unsigned char expected[] = { 0x84, 0x98, 0x3e, 0x44, + 0x1c, 0x3b, 0xd2, 0x6e, + 0xba, 0xae, 0x4a, 0xa1, + 0xf9, 0x51, 0x29, 0xe5, + 0xe5, 0x46, 0x70, 0xf1 }; + + base::SHA1HashBytes(reinterpret_cast(input.c_str()), + input.length(), output); + for (size_t i = 0; i < base::kSHA1Length; i++) + EXPECT_EQ(expected[i], output[i]); +} + +TEST(SHA1Test, Test3Bytes) { + // Example A.3 from FIPS 180-2: long message. + std::string input(1000000, 'a'); + unsigned char output[base::kSHA1Length]; + + unsigned char expected[] = { 0x34, 0xaa, 0x97, 0x3c, + 0xd4, 0xc4, 0xda, 0xa4, + 0xf6, 0x1e, 0xeb, 0x2b, + 0xdb, 0xad, 0x27, 0x31, + 0x65, 0x34, 0x01, 0x6f }; + + base::SHA1HashBytes(reinterpret_cast(input.c_str()), + input.length(), output); + for (size_t i = 0; i < base::kSHA1Length; i++) + EXPECT_EQ(expected[i], output[i]); +} diff --git a/base/sha1_win.cc b/base/sha1_win.cc new file mode 100644 index 0000000000..98c3840f0e --- /dev/null +++ b/base/sha1_win.cc @@ -0,0 +1,67 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/sha1.h" + +#include +#include + +// This file is not being compiled at the moment (see bug 47218). If we keep +// sha1 inside base, we cannot depend on src/crypto. +// #include "crypto/scoped_capi_types.h" +#include "base/logging.h" + +namespace base { + +std::string SHA1HashString(const std::string& str) { + ScopedHCRYPTPROV provider; + if (!CryptAcquireContext(provider.receive(), NULL, NULL, PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT)) { + DLOG_GETLASTERROR(ERROR) << "CryptAcquireContext failed"; + return std::string(kSHA1Length, '\0'); + } + + { + ScopedHCRYPTHASH hash; + if (!CryptCreateHash(provider, CALG_SHA1, 0, 0, hash.receive())) { + DLOG_GETLASTERROR(ERROR) << "CryptCreateHash failed"; + return std::string(kSHA1Length, '\0'); + } + + if (!CryptHashData(hash, reinterpret_cast(str.data()), + static_cast(str.length()), 0)) { + DLOG_GETLASTERROR(ERROR) << "CryptHashData failed"; + return std::string(kSHA1Length, '\0'); + } + + DWORD hash_len = 0; + DWORD buffer_size = sizeof hash_len; + if (!CryptGetHashParam(hash, HP_HASHSIZE, + reinterpret_cast(&hash_len), + &buffer_size, 0)) { + DLOG_GETLASTERROR(ERROR) << "CryptGetHashParam(HP_HASHSIZE) failed"; + return std::string(kSHA1Length, '\0'); + } + + std::string result; + if (!CryptGetHashParam(hash, HP_HASHVAL, + // We need the + 1 here not because the call will write a trailing \0, + // but so that result.length() is correctly set to |hash_len|. + reinterpret_cast(WriteInto(&result, hash_len + 1)), &hash_len, + 0))) { + DLOG_GETLASTERROR(ERROR) << "CryptGetHashParam(HP_HASHVAL) failed"; + return std::string(kSHA1Length, '\0'); + } + + if (hash_len != kSHA1Length) { + DLOG(ERROR) << "Returned hash value is wrong length: " << hash_len + << " should be " << kSHA1Length; + return std::string(kSHA1Length, '\0'); + } + + return result; + } +} + +} // namespace base diff --git a/base/single_thread_task_runner.h b/base/single_thread_task_runner.h new file mode 100644 index 0000000000..e82941a348 --- /dev/null +++ b/base/single_thread_task_runner.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_SINGLE_THREAD_TASK_RUNNER_H_ +#define BASE_SINGLE_THREAD_TASK_RUNNER_H_ + +#include "base/base_export.h" +#include "base/sequenced_task_runner.h" + +namespace base { + +// A SingleThreadTaskRunner is a SequencedTaskRunner with one more +// guarantee; namely, that all tasks are run on a single dedicated +// thread. Most use cases require only a SequencedTaskRunner, unless +// there is a specific need to run tasks on only a single thread. +// +// SingleThreadTaskRunner implementations might: +// - Post tasks to an existing thread's MessageLoop (see MessageLoopProxy). +// - Create their own worker thread and MessageLoop to post tasks to. +// - Add tasks to a FIFO and signal to a non-MessageLoop thread for them to +// be processed. This allows TaskRunner-oriented code run on threads +// running other kinds of message loop, e.g. Jingle threads. +class BASE_EXPORT SingleThreadTaskRunner : public SequencedTaskRunner { + public: + // A more explicit alias to RunsTasksOnCurrentThread(). + bool BelongsToCurrentThread() const { + return RunsTasksOnCurrentThread(); + } + + protected: + virtual ~SingleThreadTaskRunner() {} +}; + +} // namespace base + +#endif // BASE_SINGLE_THREAD_TASK_RUNNER_H_ diff --git a/base/stl_util.h b/base/stl_util.h new file mode 100644 index 0000000000..edd8803f63 --- /dev/null +++ b/base/stl_util.h @@ -0,0 +1,222 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Derived from google3/util/gtl/stl_util.h + +#ifndef BASE_STL_UTIL_H_ +#define BASE_STL_UTIL_H_ + +#include +#include +#include +#include + +#include "base/logging.h" + +// Clears internal memory of an STL object. +// STL clear()/reserve(0) does not always free internal memory allocated +// This function uses swap/destructor to ensure the internal memory is freed. +template +void STLClearObject(T* obj) { + T tmp; + tmp.swap(*obj); + // Sometimes "T tmp" allocates objects with memory (arena implementation?). + // Hence using additional reserve(0) even if it doesn't always work. + obj->reserve(0); +} + +// For a range within a container of pointers, calls delete (non-array version) +// on these pointers. +// NOTE: for these three functions, we could just implement a DeleteObject +// functor and then call for_each() on the range and functor, but this +// requires us to pull in all of algorithm.h, which seems expensive. +// For hash_[multi]set, it is important that this deletes behind the iterator +// because the hash_set may call the hash function on the iterator when it is +// advanced, which could result in the hash function trying to deference a +// stale pointer. +template +void STLDeleteContainerPointers(ForwardIterator begin, ForwardIterator end) { + while (begin != end) { + ForwardIterator temp = begin; + ++begin; + delete *temp; + } +} + +// For a range within a container of pairs, calls delete (non-array version) on +// BOTH items in the pairs. +// NOTE: Like STLDeleteContainerPointers, it is important that this deletes +// behind the iterator because if both the key and value are deleted, the +// container may call the hash function on the iterator when it is advanced, +// which could result in the hash function trying to dereference a stale +// pointer. +template +void STLDeleteContainerPairPointers(ForwardIterator begin, + ForwardIterator end) { + while (begin != end) { + ForwardIterator temp = begin; + ++begin; + delete temp->first; + delete temp->second; + } +} + +// For a range within a container of pairs, calls delete (non-array version) on +// the FIRST item in the pairs. +// NOTE: Like STLDeleteContainerPointers, deleting behind the iterator. +template +void STLDeleteContainerPairFirstPointers(ForwardIterator begin, + ForwardIterator end) { + while (begin != end) { + ForwardIterator temp = begin; + ++begin; + delete temp->first; + } +} + +// For a range within a container of pairs, calls delete. +// NOTE: Like STLDeleteContainerPointers, deleting behind the iterator. +// Deleting the value does not always invalidate the iterator, but it may +// do so if the key is a pointer into the value object. +template +void STLDeleteContainerPairSecondPointers(ForwardIterator begin, + ForwardIterator end) { + while (begin != end) { + ForwardIterator temp = begin; + ++begin; + delete temp->second; + } +} + +// To treat a possibly-empty vector as an array, use these functions. +// If you know the array will never be empty, you can use &*v.begin() +// directly, but that is undefined behaviour if |v| is empty. +template +inline T* vector_as_array(std::vector* v) { + return v->empty() ? NULL : &*v->begin(); +} + +template +inline const T* vector_as_array(const std::vector* v) { + return v->empty() ? NULL : &*v->begin(); +} + +// Return a mutable char* pointing to a string's internal buffer, +// which may not be null-terminated. Writing through this pointer will +// modify the string. +// +// string_as_array(&str)[i] is valid for 0 <= i < str.size() until the +// next call to a string method that invalidates iterators. +// +// As of 2006-04, there is no standard-blessed way of getting a +// mutable reference to a string's internal buffer. However, issue 530 +// (http://www.open-std.org/JTC1/SC22/WG21/docs/lwg-active.html#530) +// proposes this as the method. According to Matt Austern, this should +// already work on all current implementations. +inline char* string_as_array(std::string* str) { + // DO NOT USE const_cast(str->data()) + return str->empty() ? NULL : &*str->begin(); +} + +// The following functions are useful for cleaning up STL containers whose +// elements point to allocated memory. + +// STLDeleteElements() deletes all the elements in an STL container and clears +// the container. This function is suitable for use with a vector, set, +// hash_set, or any other STL container which defines sensible begin(), end(), +// and clear() methods. +// +// If container is NULL, this function is a no-op. +// +// As an alternative to calling STLDeleteElements() directly, consider +// STLElementDeleter (defined below), which ensures that your container's +// elements are deleted when the STLElementDeleter goes out of scope. +template +void STLDeleteElements(T* container) { + if (!container) + return; + STLDeleteContainerPointers(container->begin(), container->end()); + container->clear(); +} + +// Given an STL container consisting of (key, value) pairs, STLDeleteValues +// deletes all the "value" components and clears the container. Does nothing +// in the case it's given a NULL pointer. +template +void STLDeleteValues(T* container) { + if (!container) + return; + for (typename T::iterator i(container->begin()); i != container->end(); ++i) + delete i->second; + container->clear(); +} + + +// The following classes provide a convenient way to delete all elements or +// values from STL containers when they goes out of scope. This greatly +// simplifies code that creates temporary objects and has multiple return +// statements. Example: +// +// vector tmp_proto; +// STLElementDeleter > d(&tmp_proto); +// if (...) return false; +// ... +// return success; + +// Given a pointer to an STL container this class will delete all the element +// pointers when it goes out of scope. +template +class STLElementDeleter { + public: + STLElementDeleter(T* container) : container_(container) {} + ~STLElementDeleter() { STLDeleteElements(container_); } + + private: + T* container_; +}; + +// Given a pointer to an STL container this class will delete all the value +// pointers when it goes out of scope. +template +class STLValueDeleter { + public: + STLValueDeleter(T* container) : container_(container) {} + ~STLValueDeleter() { STLDeleteValues(container_); } + + private: + T* container_; +}; + +// Test to see if a set, map, hash_set or hash_map contains a particular key. +// Returns true if the key is in the collection. +template +bool ContainsKey(const Collection& collection, const Key& key) { + return collection.find(key) != collection.end(); +} + +namespace base { + +// Returns true if the container is sorted. +template +bool STLIsSorted(const Container& cont) { + return std::adjacent_find(cont.begin(), cont.end(), + std::greater()) + == cont.end(); +} + +// Returns a new ResultType containing the difference of two sorted containers. +template +ResultType STLSetDifference(const Arg1& a1, const Arg2& a2) { + DCHECK(STLIsSorted(a1)); + DCHECK(STLIsSorted(a2)); + ResultType difference; + std::set_difference(a1.begin(), a1.end(), + a2.begin(), a2.end(), + std::inserter(difference, difference.end())); + return difference; +} + +} // namespace base + +#endif // BASE_STL_UTIL_H_ diff --git a/base/stl_util_unittest.cc b/base/stl_util_unittest.cc new file mode 100644 index 0000000000..63d5c5c1ee --- /dev/null +++ b/base/stl_util_unittest.cc @@ -0,0 +1,82 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/stl_util.h" + +#include + +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace { + +TEST(STLUtilTest, STLIsSorted) { + { + std::set set; + set.insert(24); + set.insert(1); + set.insert(12); + EXPECT_TRUE(STLIsSorted(set)); + } + + { + std::vector vector; + vector.push_back(1); + vector.push_back(1); + vector.push_back(4); + vector.push_back(64); + vector.push_back(12432); + EXPECT_TRUE(STLIsSorted(vector)); + vector.back() = 1; + EXPECT_FALSE(STLIsSorted(vector)); + } +} + +TEST(STLUtilTest, STLSetDifference) { + std::set a1; + a1.insert(1); + a1.insert(2); + a1.insert(3); + a1.insert(4); + + std::set a2; + a2.insert(3); + a2.insert(4); + a2.insert(5); + a2.insert(6); + a2.insert(7); + + { + std::set difference; + difference.insert(1); + difference.insert(2); + EXPECT_EQ(difference, STLSetDifference >(a1, a2)); + } + + { + std::set difference; + difference.insert(5); + difference.insert(6); + difference.insert(7); + EXPECT_EQ(difference, STLSetDifference >(a2, a1)); + } + + { + std::vector difference; + difference.push_back(1); + difference.push_back(2); + EXPECT_EQ(difference, STLSetDifference >(a1, a2)); + } + + { + std::vector difference; + difference.push_back(5); + difference.push_back(6); + difference.push_back(7); + EXPECT_EQ(difference, STLSetDifference >(a2, a1)); + } +} + +} // namespace +} // namespace base diff --git a/base/strings/latin1_string_conversions.cc b/base/strings/latin1_string_conversions.cc new file mode 100644 index 0000000000..dca62ced53 --- /dev/null +++ b/base/strings/latin1_string_conversions.cc @@ -0,0 +1,19 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/strings/latin1_string_conversions.h" + +namespace base { + +string16 Latin1OrUTF16ToUTF16(size_t length, + const Latin1Char* latin1, + const char16* utf16) { + if (!length) + return string16(); + if (latin1) + return string16(latin1, latin1 + length); + return string16(utf16, utf16 + length); +} + +} // namespace base diff --git a/base/strings/latin1_string_conversions.h b/base/strings/latin1_string_conversions.h new file mode 100644 index 0000000000..387cb65a52 --- /dev/null +++ b/base/strings/latin1_string_conversions.h @@ -0,0 +1,32 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_STRINGS_LATIN1_STRING_CONVERSIONS_H_ +#define BASE_STRINGS_LATIN1_STRING_CONVERSIONS_H_ + +#include + +#include "base/base_export.h" +#include "base/strings/string16.h" + +namespace base { + +// This definition of Latin1Char matches the definition of LChar in Blink. We +// use unsigned char rather than char to make less tempting to mix and match +// Latin-1 and UTF-8 characters.. +typedef unsigned char Latin1Char; + +// This somewhat odd function is designed to help us convert from Blink Strings +// to string16. A Blink string is either backed by an array of Latin-1 +// characters or an array of UTF-16 characters. This function is called by +// WebString::operator string16() to convert one or the other character array +// to string16. This function is defined here rather than in WebString.h to +// avoid binary bloat in all the callers of the conversion operator. +BASE_EXPORT string16 Latin1OrUTF16ToUTF16(size_t length, + const Latin1Char* latin1, + const char16* utf16); + +} // namespace base + +#endif // BASE_STRINGS_LATIN1_STRING_CONVERSIONS_H_ diff --git a/base/strings/nullable_string16.cc b/base/strings/nullable_string16.cc new file mode 100644 index 0000000000..07f81d4339 --- /dev/null +++ b/base/strings/nullable_string16.cc @@ -0,0 +1,17 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/strings/nullable_string16.h" + +#include + +#include "base/strings/utf_string_conversions.h" + +namespace base { + +std::ostream& operator<<(std::ostream& out, const NullableString16& value) { + return value.is_null() ? out << "(null)" : out << UTF16ToUTF8(value.string()); +} + +} // namespace base diff --git a/base/strings/nullable_string16.h b/base/strings/nullable_string16.h new file mode 100644 index 0000000000..5997d17441 --- /dev/null +++ b/base/strings/nullable_string16.h @@ -0,0 +1,46 @@ +// 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_STRINGS_NULLABLE_STRING16_H_ +#define BASE_STRINGS_NULLABLE_STRING16_H_ + +#include + +#include "base/base_export.h" +#include "base/strings/string16.h" + +namespace base { + +// This class is a simple wrapper for string16 which also contains a null +// state. This should be used only where the difference between null and +// empty is meaningful. +class NullableString16 { + public: + NullableString16() : is_null_(true) { } + NullableString16(const string16& string, bool is_null) + : string_(string), is_null_(is_null) { + } + + const string16& string() const { return string_; } + bool is_null() const { return is_null_; } + + private: + string16 string_; + bool is_null_; +}; + +inline bool operator==(const NullableString16& a, const NullableString16& b) { + return a.is_null() == b.is_null() && a.string() == b.string(); +} + +inline bool operator!=(const NullableString16& a, const NullableString16& b) { + return !(a == b); +} + +BASE_EXPORT std::ostream& operator<<(std::ostream& out, + const NullableString16& value); + +} // namespace + +#endif // BASE_STRINGS_NULLABLE_STRING16_H_ diff --git a/base/strings/nullable_string16_unittest.cc b/base/strings/nullable_string16_unittest.cc new file mode 100644 index 0000000000..f02fdcebfe --- /dev/null +++ b/base/strings/nullable_string16_unittest.cc @@ -0,0 +1,35 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/strings/nullable_string16.h" +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +TEST(NullableString16Test, DefaultConstructor) { + NullableString16 s; + EXPECT_TRUE(s.is_null()); + EXPECT_EQ(string16(), s.string()); +} + +TEST(NullableString16Test, Equals) { + NullableString16 a(ASCIIToUTF16("hello"), false); + NullableString16 b(ASCIIToUTF16("hello"), false); + EXPECT_EQ(a, b); +} + +TEST(NullableString16Test, NotEquals) { + NullableString16 a(ASCIIToUTF16("hello"), false); + NullableString16 b(ASCIIToUTF16("world"), false); + EXPECT_NE(a, b); +} + +TEST(NullableString16Test, NotEqualsNull) { + NullableString16 a(ASCIIToUTF16("hello"), false); + NullableString16 b; + EXPECT_NE(a, b); +} + +} // namespace base diff --git a/base/strings/string16.cc b/base/strings/string16.cc new file mode 100644 index 0000000000..c802eef128 --- /dev/null +++ b/base/strings/string16.cc @@ -0,0 +1,82 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/strings/string16.h" + +#if defined(WCHAR_T_IS_UTF16) + +#error This file should not be used on 2-byte wchar_t systems +// If this winds up being needed on 2-byte wchar_t systems, either the +// definitions below can be used, or the host system's wide character +// functions like wmemcmp can be wrapped. + +#elif defined(WCHAR_T_IS_UTF32) + +#include + +#include "base/strings/utf_string_conversions.h" + +namespace base { + +int c16memcmp(const char16* s1, const char16* s2, size_t n) { + // We cannot call memcmp because that changes the semantics. + while (n-- > 0) { + if (*s1 != *s2) { + // We cannot use (*s1 - *s2) because char16 is unsigned. + return ((*s1 < *s2) ? -1 : 1); + } + ++s1; + ++s2; + } + return 0; +} + +size_t c16len(const char16* s) { + const char16 *s_orig = s; + while (*s) { + ++s; + } + return s - s_orig; +} + +const char16* c16memchr(const char16* s, char16 c, size_t n) { + while (n-- > 0) { + if (*s == c) { + return s; + } + ++s; + } + return 0; +} + +char16* c16memmove(char16* s1, const char16* s2, size_t n) { + return static_cast(memmove(s1, s2, n * sizeof(char16))); +} + +char16* c16memcpy(char16* s1, const char16* s2, size_t n) { + return static_cast(memcpy(s1, s2, n * sizeof(char16))); +} + +char16* c16memset(char16* s, char16 c, size_t n) { + char16 *s_orig = s; + while (n-- > 0) { + *s = c; + ++s; + } + return s_orig; +} + +std::ostream& operator<<(std::ostream& out, const string16& str) { + return out << UTF16ToUTF8(str); +} + +void PrintTo(const string16& str, std::ostream* out) { + *out << str; +} + +} // namespace base + +template class std::basic_string; + +#endif // WCHAR_T_IS_UTF32 diff --git a/base/strings/string16.h b/base/strings/string16.h new file mode 100644 index 0000000000..fd98f1b5be --- /dev/null +++ b/base/strings/string16.h @@ -0,0 +1,189 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_STRINGS_STRING16_H_ +#define BASE_STRINGS_STRING16_H_ + +// WHAT: +// A version of std::basic_string that provides 2-byte characters even when +// wchar_t is not implemented as a 2-byte type. You can access this class as +// string16. We also define char16, which string16 is based upon. +// +// WHY: +// On Windows, wchar_t is 2 bytes, and it can conveniently handle UTF-16/UCS-2 +// data. Plenty of existing code operates on strings encoded as UTF-16. +// +// On many other platforms, sizeof(wchar_t) is 4 bytes by default. We can make +// it 2 bytes by using the GCC flag -fshort-wchar. But then std::wstring fails +// at run time, because it calls some functions (like wcslen) that come from +// the system's native C library -- which was built with a 4-byte wchar_t! +// It's wasteful to use 4-byte wchar_t strings to carry UTF-16 data, and it's +// entirely improper on those systems where the encoding of wchar_t is defined +// as UTF-32. +// +// Here, we define string16, which is similar to std::wstring but replaces all +// libc functions with custom, 2-byte-char compatible routines. It is capable +// of carrying UTF-16-encoded data. + +#include +#include + +#include "base/base_export.h" +#include "base/basictypes.h" + +#if defined(WCHAR_T_IS_UTF16) + +namespace base { + +typedef wchar_t char16; +typedef std::wstring string16; +typedef std::char_traits string16_char_traits; + +} // namespace base + +#elif defined(WCHAR_T_IS_UTF32) + +namespace base { + +typedef uint16 char16; + +// char16 versions of the functions required by string16_char_traits; these +// are based on the wide character functions of similar names ("w" or "wcs" +// instead of "c16"). +BASE_EXPORT int c16memcmp(const char16* s1, const char16* s2, size_t n); +BASE_EXPORT size_t c16len(const char16* s); +BASE_EXPORT const char16* c16memchr(const char16* s, char16 c, size_t n); +BASE_EXPORT char16* c16memmove(char16* s1, const char16* s2, size_t n); +BASE_EXPORT char16* c16memcpy(char16* s1, const char16* s2, size_t n); +BASE_EXPORT char16* c16memset(char16* s, char16 c, size_t n); + +struct string16_char_traits { + typedef char16 char_type; + typedef int int_type; + + // int_type needs to be able to hold each possible value of char_type, and in + // addition, the distinct value of eof(). + COMPILE_ASSERT(sizeof(int_type) > sizeof(char_type), unexpected_type_width); + + typedef std::streamoff off_type; + typedef mbstate_t state_type; + typedef std::fpos pos_type; + + static void assign(char_type& c1, const char_type& c2) { + c1 = c2; + } + + static bool eq(const char_type& c1, const char_type& c2) { + return c1 == c2; + } + static bool lt(const char_type& c1, const char_type& c2) { + return c1 < c2; + } + + static int compare(const char_type* s1, const char_type* s2, size_t n) { + return c16memcmp(s1, s2, n); + } + + static size_t length(const char_type* s) { + return c16len(s); + } + + static const char_type* find(const char_type* s, size_t n, + const char_type& a) { + return c16memchr(s, a, n); + } + + static char_type* move(char_type* s1, const char_type* s2, int_type n) { + return c16memmove(s1, s2, n); + } + + static char_type* copy(char_type* s1, const char_type* s2, size_t n) { + return c16memcpy(s1, s2, n); + } + + static char_type* assign(char_type* s, size_t n, char_type a) { + return c16memset(s, a, n); + } + + static int_type not_eof(const int_type& c) { + return eq_int_type(c, eof()) ? 0 : c; + } + + static char_type to_char_type(const int_type& c) { + return char_type(c); + } + + static int_type to_int_type(const char_type& c) { + return int_type(c); + } + + static bool eq_int_type(const int_type& c1, const int_type& c2) { + return c1 == c2; + } + + static int_type eof() { + return static_cast(EOF); + } +}; + +typedef std::basic_string string16; + +BASE_EXPORT extern std::ostream& operator<<(std::ostream& out, + const string16& str); + +// This is required by googletest to print a readable output on test failures. +BASE_EXPORT extern void PrintTo(const string16& str, std::ostream* out); + +} // namespace base + +// The string class will be explicitly instantiated only once, in string16.cc. +// +// std::basic_string<> in GNU libstdc++ contains a static data member, +// _S_empty_rep_storage, to represent empty strings. When an operation such +// as assignment or destruction is performed on a string, causing its existing +// data member to be invalidated, it must not be freed if this static data +// member is being used. Otherwise, it counts as an attempt to free static +// (and not allocated) data, which is a memory error. +// +// Generally, due to C++ template magic, _S_empty_rep_storage will be marked +// as a coalesced symbol, meaning that the linker will combine multiple +// instances into a single one when generating output. +// +// If a string class is used by multiple shared libraries, a problem occurs. +// Each library will get its own copy of _S_empty_rep_storage. When strings +// are passed across a library boundary for alteration or destruction, memory +// errors will result. GNU libstdc++ contains a configuration option, +// --enable-fully-dynamic-string (_GLIBCXX_FULLY_DYNAMIC_STRING), which +// disables the static data member optimization, but it's a good optimization +// and non-STL code is generally at the mercy of the system's STL +// configuration. Fully-dynamic strings are not the default for GNU libstdc++ +// libstdc++ itself or for the libstdc++ installations on the systems we care +// about, such as Mac OS X and relevant flavors of Linux. +// +// See also http://gcc.gnu.org/bugzilla/show_bug.cgi?id=24196 . +// +// To avoid problems, string classes need to be explicitly instantiated only +// once, in exactly one library. All other string users see it via an "extern" +// declaration. This is precisely how GNU libstdc++ handles +// std::basic_string (string) and std::basic_string (wstring). +// +// This also works around a Mac OS X linker bug in ld64-85.2.1 (Xcode 3.1.2), +// in which the linker does not fully coalesce symbols when dead code +// stripping is enabled. This bug causes the memory errors described above +// to occur even when a std::basic_string<> does not cross shared library +// boundaries, such as in statically-linked executables. +// +// TODO(mark): File this bug with Apple and update this note with a bug number. + +extern template +class BASE_EXPORT std::basic_string; + +#endif // WCHAR_T_IS_UTF32 + +// TODO(brettw) update users of string16 to use the namespace and remove +// this "using". +using base::char16; +using base::string16; + +#endif // BASE_STRINGS_STRING16_H_ diff --git a/base/strings/string16_unittest.cc b/base/strings/string16_unittest.cc new file mode 100644 index 0000000000..d98b2a9ec5 --- /dev/null +++ b/base/strings/string16_unittest.cc @@ -0,0 +1,54 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/strings/string16.h" + +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(WCHAR_T_IS_UTF32) + +// We define a custom operator<< for string16 so we can use it with logging. +// This tests that conversion. +TEST(String16Test, OutputStream) { + // Basic stream test. + { + std::ostringstream stream; + stream << "Empty '" << string16() << "' standard '" + << string16(ASCIIToUTF16("Hello, world")) << "'"; + EXPECT_STREQ("Empty '' standard 'Hello, world'", + stream.str().c_str()); + } + + // Interesting edge cases. + { + // These should each get converted to the invalid character: EF BF BD. + string16 initial_surrogate; + initial_surrogate.push_back(0xd800); + string16 final_surrogate; + final_surrogate.push_back(0xdc00); + + // Old italic A = U+10300, will get converted to: F0 90 8C 80 'z'. + string16 surrogate_pair; + surrogate_pair.push_back(0xd800); + surrogate_pair.push_back(0xdf00); + surrogate_pair.push_back('z'); + + // Will get converted to the invalid char + 's': EF BF BD 's'. + string16 unterminated_surrogate; + unterminated_surrogate.push_back(0xd800); + unterminated_surrogate.push_back('s'); + + std::ostringstream stream; + stream << initial_surrogate << "," << final_surrogate << "," + << surrogate_pair << "," << unterminated_surrogate; + + EXPECT_STREQ("\xef\xbf\xbd,\xef\xbf\xbd,\xf0\x90\x8c\x80z,\xef\xbf\xbds", + stream.str().c_str()); + } +} + +#endif diff --git a/base/strings/string_number_conversions.cc b/base/strings/string_number_conversions.cc new file mode 100644 index 0000000000..b9412d9313 --- /dev/null +++ b/base/strings/string_number_conversions.cc @@ -0,0 +1,516 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/strings/string_number_conversions.h" + +#include +#include +#include +#include + +#include + +#include "base/logging.h" +#include "base/scoped_clear_errno.h" +#include "base/strings/utf_string_conversions.h" +#include "base/third_party/dmg_fp/dmg_fp.h" + +namespace base { + +namespace { + +template +struct IntToStringT { + // This is to avoid a compiler warning about unary minus on unsigned type. + // For example, say you had the following code: + // template + // INT abs(INT value) { return value < 0 ? -value : value; } + // Even though if INT is unsigned, it's impossible for value < 0, so the + // unary minus will never be taken, the compiler will still generate a + // warning. We do a little specialization dance... + template + struct ToUnsignedT {}; + + template + struct ToUnsignedT { + static UINT2 ToUnsigned(INT2 value) { + return static_cast(value); + } + }; + + template + struct ToUnsignedT { + static UINT2 ToUnsigned(INT2 value) { + return static_cast(value < 0 ? -value : value); + } + }; + + // This set of templates is very similar to the above templates, but + // for testing whether an integer is negative. + template + struct TestNegT {}; + template + struct TestNegT { + static bool TestNeg(INT2 value) { + // value is unsigned, and can never be negative. + return false; + } + }; + template + struct TestNegT { + static bool TestNeg(INT2 value) { + return value < 0; + } + }; + + static STR IntToString(INT value) { + // log10(2) ~= 0.3 bytes needed per bit or per byte log10(2**8) ~= 2.4. + // So round up to allocate 3 output characters per byte, plus 1 for '-'. + const int kOutputBufSize = 3 * sizeof(INT) + 1; + + // Allocate the whole string right away, we will right back to front, and + // then return the substr of what we ended up using. + STR outbuf(kOutputBufSize, 0); + + bool is_neg = TestNegT::TestNeg(value); + // Even though is_neg will never be true when INT is parameterized as + // unsigned, even the presence of the unary operation causes a warning. + UINT res = ToUnsignedT::ToUnsigned(value); + + for (typename STR::iterator it = outbuf.end();;) { + --it; + DCHECK(it != outbuf.begin()); + *it = static_cast((res % 10) + '0'); + res /= 10; + + // We're done.. + if (res == 0) { + if (is_neg) { + --it; + DCHECK(it != outbuf.begin()); + *it = static_cast('-'); + } + return STR(it, outbuf.end()); + } + } + NOTREACHED(); + return STR(); + } +}; + +// Utility to convert a character to a digit in a given base +template class BaseCharToDigit { +}; + +// Faster specialization for bases <= 10 +template class BaseCharToDigit { + public: + static bool Convert(CHAR c, uint8* digit) { + if (c >= '0' && c < '0' + BASE) { + *digit = c - '0'; + return true; + } + return false; + } +}; + +// Specialization for bases where 10 < base <= 36 +template class BaseCharToDigit { + public: + static bool Convert(CHAR c, uint8* digit) { + if (c >= '0' && c <= '9') { + *digit = c - '0'; + } else if (c >= 'a' && c < 'a' + BASE - 10) { + *digit = c - 'a' + 10; + } else if (c >= 'A' && c < 'A' + BASE - 10) { + *digit = c - 'A' + 10; + } else { + return false; + } + return true; + } +}; + +template bool CharToDigit(CHAR c, uint8* digit) { + return BaseCharToDigit::Convert(c, digit); +} + +// There is an IsWhitespace for wchars defined in string_util.h, but it is +// locale independent, whereas the functions we are replacing were +// locale-dependent. TBD what is desired, but for the moment let's not introduce +// a change in behaviour. +template class WhitespaceHelper { +}; + +template<> class WhitespaceHelper { + public: + static bool Invoke(char c) { + return 0 != isspace(static_cast(c)); + } +}; + +template<> class WhitespaceHelper { + public: + static bool Invoke(char16 c) { + return 0 != iswspace(c); + } +}; + +template bool LocalIsWhitespace(CHAR c) { + return WhitespaceHelper::Invoke(c); +} + +// IteratorRangeToNumberTraits should provide: +// - a typedef for iterator_type, the iterator type used as input. +// - a typedef for value_type, the target numeric type. +// - static functions min, max (returning the minimum and maximum permitted +// values) +// - constant kBase, the base in which to interpret the input +template +class IteratorRangeToNumber { + public: + typedef IteratorRangeToNumberTraits traits; + typedef typename traits::iterator_type const_iterator; + typedef typename traits::value_type value_type; + + // Generalized iterator-range-to-number conversion. + // + static bool Invoke(const_iterator begin, + const_iterator end, + value_type* output) { + bool valid = true; + + while (begin != end && LocalIsWhitespace(*begin)) { + valid = false; + ++begin; + } + + if (begin != end && *begin == '-') { + if (!std::numeric_limits::is_signed) { + valid = false; + } else if (!Negative::Invoke(begin + 1, end, output)) { + valid = false; + } + } else { + if (begin != end && *begin == '+') { + ++begin; + } + if (!Positive::Invoke(begin, end, output)) { + valid = false; + } + } + + return valid; + } + + private: + // Sign provides: + // - a static function, CheckBounds, that determines whether the next digit + // causes an overflow/underflow + // - a static function, Increment, that appends the next digit appropriately + // according to the sign of the number being parsed. + template + class Base { + public: + static bool Invoke(const_iterator begin, const_iterator end, + typename traits::value_type* output) { + *output = 0; + + if (begin == end) { + return false; + } + + // Note: no performance difference was found when using template + // specialization to remove this check in bases other than 16 + if (traits::kBase == 16 && end - begin > 2 && *begin == '0' && + (*(begin + 1) == 'x' || *(begin + 1) == 'X')) { + begin += 2; + } + + for (const_iterator current = begin; current != end; ++current) { + uint8 new_digit = 0; + + if (!CharToDigit(*current, &new_digit)) { + return false; + } + + if (current != begin) { + if (!Sign::CheckBounds(output, new_digit)) { + return false; + } + *output *= traits::kBase; + } + + Sign::Increment(new_digit, output); + } + return true; + } + }; + + class Positive : public Base { + public: + static bool CheckBounds(value_type* output, uint8 new_digit) { + if (*output > static_cast(traits::max() / traits::kBase) || + (*output == static_cast(traits::max() / traits::kBase) && + new_digit > traits::max() % traits::kBase)) { + *output = traits::max(); + return false; + } + return true; + } + static void Increment(uint8 increment, value_type* output) { + *output += increment; + } + }; + + class Negative : public Base { + public: + static bool CheckBounds(value_type* output, uint8 new_digit) { + if (*output < traits::min() / traits::kBase || + (*output == traits::min() / traits::kBase && + new_digit > 0 - traits::min() % traits::kBase)) { + *output = traits::min(); + return false; + } + return true; + } + static void Increment(uint8 increment, value_type* output) { + *output -= increment; + } + }; +}; + +template +class BaseIteratorRangeToNumberTraits { + public: + typedef ITERATOR iterator_type; + typedef VALUE value_type; + static value_type min() { + return std::numeric_limits::min(); + } + static value_type max() { + return std::numeric_limits::max(); + } + static const int kBase = BASE; +}; + +template +class BaseHexIteratorRangeToIntTraits + : public BaseIteratorRangeToNumberTraits { +}; + +template +class BaseHexIteratorRangeToInt64Traits + : public BaseIteratorRangeToNumberTraits { +}; + +template +class BaseHexIteratorRangeToUInt64Traits + : public BaseIteratorRangeToNumberTraits { +}; + +typedef BaseHexIteratorRangeToIntTraits + HexIteratorRangeToIntTraits; + +typedef BaseHexIteratorRangeToInt64Traits + HexIteratorRangeToInt64Traits; + +typedef BaseHexIteratorRangeToUInt64Traits + HexIteratorRangeToUInt64Traits; + +template +bool HexStringToBytesT(const STR& input, std::vector* output) { + DCHECK_EQ(output->size(), 0u); + size_t count = input.size(); + if (count == 0 || (count % 2) != 0) + return false; + for (uintptr_t i = 0; i < count / 2; ++i) { + uint8 msb = 0; // most significant 4 bits + uint8 lsb = 0; // least significant 4 bits + if (!CharToDigit<16>(input[i * 2], &msb) || + !CharToDigit<16>(input[i * 2 + 1], &lsb)) + return false; + output->push_back((msb << 4) | lsb); + } + return true; +} + +template +class StringPieceToNumberTraits + : public BaseIteratorRangeToNumberTraits { +}; + +template +bool StringToIntImpl(const StringPiece& input, VALUE* output) { + return IteratorRangeToNumber >::Invoke( + input.begin(), input.end(), output); +} + +template +class StringPiece16ToNumberTraits + : public BaseIteratorRangeToNumberTraits { +}; + +template +bool String16ToIntImpl(const StringPiece16& input, VALUE* output) { + return IteratorRangeToNumber >::Invoke( + input.begin(), input.end(), output); +} + +} // namespace + +std::string IntToString(int value) { + return IntToStringT:: + IntToString(value); +} + +string16 IntToString16(int value) { + return IntToStringT:: + IntToString(value); +} + +std::string UintToString(unsigned int value) { + return IntToStringT:: + IntToString(value); +} + +string16 UintToString16(unsigned int value) { + return IntToStringT:: + IntToString(value); +} + +std::string Int64ToString(int64 value) { + return IntToStringT:: + IntToString(value); +} + +string16 Int64ToString16(int64 value) { + return IntToStringT::IntToString(value); +} + +std::string Uint64ToString(uint64 value) { + return IntToStringT:: + IntToString(value); +} + +string16 Uint64ToString16(uint64 value) { + return IntToStringT:: + IntToString(value); +} + +std::string DoubleToString(double value) { + // According to g_fmt.cc, it is sufficient to declare a buffer of size 32. + char buffer[32]; + dmg_fp::g_fmt(buffer, value); + return std::string(buffer); +} + +bool StringToInt(const StringPiece& input, int* output) { + return StringToIntImpl(input, output); +} + +bool StringToInt(const StringPiece16& input, int* output) { + return String16ToIntImpl(input, output); +} + +bool StringToUint(const StringPiece& input, unsigned* output) { + return StringToIntImpl(input, output); +} + +bool StringToUint(const StringPiece16& input, unsigned* output) { + return String16ToIntImpl(input, output); +} + +bool StringToInt64(const StringPiece& input, int64* output) { + return StringToIntImpl(input, output); +} + +bool StringToInt64(const StringPiece16& input, int64* output) { + return String16ToIntImpl(input, output); +} + +bool StringToUint64(const StringPiece& input, uint64* output) { + return StringToIntImpl(input, output); +} + +bool StringToUint64(const StringPiece16& input, uint64* output) { + return String16ToIntImpl(input, output); +} + +bool StringToSizeT(const StringPiece& input, size_t* output) { + return StringToIntImpl(input, output); +} + +bool StringToSizeT(const StringPiece16& input, size_t* output) { + return String16ToIntImpl(input, output); +} + +bool StringToDouble(const std::string& input, double* output) { + // Thread-safe? It is on at least Mac, Linux, and Windows. + ScopedClearErrno clear_errno; + + char* endptr = NULL; + *output = dmg_fp::strtod(input.c_str(), &endptr); + + // Cases to return false: + // - If errno is ERANGE, there was an overflow or underflow. + // - If the input string is empty, there was nothing to parse. + // - If endptr does not point to the end of the string, there are either + // characters remaining in the string after a parsed number, or the string + // does not begin with a parseable number. endptr is compared to the + // expected end given the string's stated length to correctly catch cases + // where the string contains embedded NUL characters. + // - If the first character is a space, there was leading whitespace + return errno == 0 && + !input.empty() && + input.c_str() + input.length() == endptr && + !isspace(input[0]); +} + +// Note: if you need to add String16ToDouble, first ask yourself if it's +// really necessary. If it is, probably the best implementation here is to +// convert to 8-bit and then use the 8-bit version. + +// Note: if you need to add an iterator range version of StringToDouble, first +// ask yourself if it's really necessary. If it is, probably the best +// implementation here is to instantiate a string and use the string version. + +std::string HexEncode(const void* bytes, size_t size) { + static const char kHexChars[] = "0123456789ABCDEF"; + + // Each input byte creates two output hex characters. + std::string ret(size * 2, '\0'); + + for (size_t i = 0; i < size; ++i) { + char b = reinterpret_cast(bytes)[i]; + ret[(i * 2)] = kHexChars[(b >> 4) & 0xf]; + ret[(i * 2) + 1] = kHexChars[b & 0xf]; + } + return ret; +} + +bool HexStringToInt(const StringPiece& input, int* output) { + return IteratorRangeToNumber::Invoke( + input.begin(), input.end(), output); +} + +bool HexStringToInt64(const StringPiece& input, int64* output) { + return IteratorRangeToNumber::Invoke( + input.begin(), input.end(), output); +} + +bool HexStringToUInt64(const StringPiece& input, uint64* output) { + return IteratorRangeToNumber::Invoke( + input.begin(), input.end(), output); +} + +bool HexStringToBytes(const std::string& input, std::vector* output) { + return HexStringToBytesT(input, output); +} + +} // namespace base diff --git a/base/strings/string_number_conversions.h b/base/strings/string_number_conversions.h new file mode 100644 index 0000000000..b199bb5f7a --- /dev/null +++ b/base/strings/string_number_conversions.h @@ -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. + +#ifndef BASE_STRINGS_STRING_NUMBER_CONVERSIONS_H_ +#define BASE_STRINGS_STRING_NUMBER_CONVERSIONS_H_ + +#include +#include + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/strings/string16.h" +#include "base/strings/string_piece.h" + +// ---------------------------------------------------------------------------- +// IMPORTANT MESSAGE FROM YOUR SPONSOR +// +// This file contains no "wstring" variants. New code should use string16. If +// you need to make old code work, use the UTF8 version and convert. Please do +// not add wstring variants. +// +// Please do not add "convenience" functions for converting strings to integers +// that return the value and ignore success/failure. That encourages people to +// write code that doesn't properly handle the error conditions. +// ---------------------------------------------------------------------------- + +namespace base { + +// Number -> string conversions ------------------------------------------------ + +BASE_EXPORT std::string IntToString(int value); +BASE_EXPORT string16 IntToString16(int value); + +BASE_EXPORT std::string UintToString(unsigned value); +BASE_EXPORT string16 UintToString16(unsigned value); + +BASE_EXPORT std::string Int64ToString(int64 value); +BASE_EXPORT string16 Int64ToString16(int64 value); + +BASE_EXPORT std::string Uint64ToString(uint64 value); +BASE_EXPORT string16 Uint64ToString16(uint64 value); + +// DoubleToString converts the double to a string format that ignores the +// locale. If you want to use locale specific formatting, use ICU. +BASE_EXPORT std::string DoubleToString(double value); + +// String -> number conversions ------------------------------------------------ + +// Perform a best-effort conversion of the input string to a numeric type, +// setting |*output| to the result of the conversion. Returns true for +// "perfect" conversions; returns false in the following cases: +// - Overflow. |*output| will be set to the maximum value supported +// by the data type. +// - Underflow. |*output| will be set to the minimum value supported +// by the data type. +// - Trailing characters in the string after parsing the number. |*output| +// will be set to the value of the number that was parsed. +// - Leading whitespace in the string before parsing the number. |*output| will +// be set to the value of the number that was parsed. +// - No characters parseable as a number at the beginning of the string. +// |*output| will be set to 0. +// - Empty string. |*output| will be set to 0. +BASE_EXPORT bool StringToInt(const StringPiece& input, int* output); +BASE_EXPORT bool StringToInt(const StringPiece16& input, int* output); + +BASE_EXPORT bool StringToUint(const StringPiece& input, unsigned* output); +BASE_EXPORT bool StringToUint(const StringPiece16& input, unsigned* output); + +BASE_EXPORT bool StringToInt64(const StringPiece& input, int64* output); +BASE_EXPORT bool StringToInt64(const StringPiece16& input, int64* output); + +BASE_EXPORT bool StringToUint64(const StringPiece& input, uint64* output); +BASE_EXPORT bool StringToUint64(const StringPiece16& input, uint64* output); + +BASE_EXPORT bool StringToSizeT(const StringPiece& input, size_t* output); +BASE_EXPORT bool StringToSizeT(const StringPiece16& input, size_t* output); + +// For floating-point conversions, only conversions of input strings in decimal +// form are defined to work. Behavior with strings representing floating-point +// numbers in hexadecimal, and strings representing non-fininte values (such as +// NaN and inf) is undefined. Otherwise, these behave the same as the integral +// variants. This expects the input string to NOT be specific to the locale. +// If your input is locale specific, use ICU to read the number. +BASE_EXPORT bool StringToDouble(const std::string& input, double* output); + +// Hex encoding ---------------------------------------------------------------- + +// Returns a hex string representation of a binary buffer. The returned hex +// string will be in upper case. This function does not check if |size| is +// within reasonable limits since it's written with trusted data in mind. If +// you suspect that the data you want to format might be large, the absolute +// max size for |size| should be is +// std::numeric_limits::max() / 2 +BASE_EXPORT std::string HexEncode(const void* bytes, size_t size); + +// Best effort conversion, see StringToInt above for restrictions. +// Will only successful parse hex values that will fit into |output|, i.e. +// -0x80000000 < |input| < 0x7FFFFFFF. +BASE_EXPORT bool HexStringToInt(const StringPiece& input, int* output); + +// Best effort conversion, see StringToInt above for restrictions. +// Will only successful parse hex values that will fit into |output|, i.e. +// -0x8000000000000000 < |input| < 0x7FFFFFFFFFFFFFFF. +BASE_EXPORT bool HexStringToInt64(const StringPiece& input, int64* output); + +// Best effort conversion, see StringToInt above for restrictions. +// Will only successful parse hex values that will fit into |output|, i.e. +// 0x0000000000000000 < |input| < 0xFFFFFFFFFFFFFFFF. +// The string is not required to start with 0x. +BASE_EXPORT bool HexStringToUInt64(const StringPiece& input, uint64* output); + +// Similar to the previous functions, except that output is a vector of bytes. +// |*output| will contain as many bytes as were successfully parsed prior to the +// error. There is no overflow, but input.size() must be evenly divisible by 2. +// Leading 0x or +/- are not allowed. +BASE_EXPORT bool HexStringToBytes(const std::string& input, + std::vector* output); + +} // namespace base + +#endif // BASE_STRINGS_STRING_NUMBER_CONVERSIONS_H_ diff --git a/base/strings/string_number_conversions_unittest.cc b/base/strings/string_number_conversions_unittest.cc new file mode 100644 index 0000000000..b8619647de --- /dev/null +++ b/base/strings/string_number_conversions_unittest.cc @@ -0,0 +1,713 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include + +#include +#include + +#include "base/format_macros.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace { + +template +struct IntToStringTest { + INT num; + const char* sexpected; + const char* uexpected; +}; + +} // namespace + +TEST(StringNumberConversionsTest, IntToString) { + static const IntToStringTest int_tests[] = { + { 0, "0", "0" }, + { -1, "-1", "4294967295" }, + { std::numeric_limits::max(), "2147483647", "2147483647" }, + { std::numeric_limits::min(), "-2147483648", "2147483648" }, + }; + static const IntToStringTest int64_tests[] = { + { 0, "0", "0" }, + { -1, "-1", "18446744073709551615" }, + { std::numeric_limits::max(), + "9223372036854775807", + "9223372036854775807", }, + { std::numeric_limits::min(), + "-9223372036854775808", + "9223372036854775808" }, + }; + + for (size_t i = 0; i < arraysize(int_tests); ++i) { + const IntToStringTest* test = &int_tests[i]; + EXPECT_EQ(IntToString(test->num), test->sexpected); + EXPECT_EQ(IntToString16(test->num), UTF8ToUTF16(test->sexpected)); + EXPECT_EQ(UintToString(test->num), test->uexpected); + EXPECT_EQ(UintToString16(test->num), UTF8ToUTF16(test->uexpected)); + } + for (size_t i = 0; i < arraysize(int64_tests); ++i) { + const IntToStringTest* test = &int64_tests[i]; + EXPECT_EQ(Int64ToString(test->num), test->sexpected); + EXPECT_EQ(Int64ToString16(test->num), UTF8ToUTF16(test->sexpected)); + EXPECT_EQ(Uint64ToString(test->num), test->uexpected); + EXPECT_EQ(Uint64ToString16(test->num), UTF8ToUTF16(test->uexpected)); + } +} + +TEST(StringNumberConversionsTest, Uint64ToString) { + static const struct { + uint64 input; + std::string output; + } cases[] = { + {0, "0"}, + {42, "42"}, + {INT_MAX, "2147483647"}, + {kuint64max, "18446744073709551615"}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) + EXPECT_EQ(cases[i].output, Uint64ToString(cases[i].input)); +} + +TEST(StringNumberConversionsTest, StringToInt) { + static const struct { + std::string input; + int output; + bool success; + } cases[] = { + {"0", 0, true}, + {"42", 42, true}, + {"42\x99", 42, false}, + {"\x99" "42\x99", 0, false}, + {"-2147483648", INT_MIN, true}, + {"2147483647", INT_MAX, true}, + {"", 0, false}, + {" 42", 42, false}, + {"42 ", 42, false}, + {"\t\n\v\f\r 42", 42, false}, + {"blah42", 0, false}, + {"42blah", 42, false}, + {"blah42blah", 0, false}, + {"-273.15", -273, false}, + {"+98.6", 98, false}, + {"--123", 0, false}, + {"++123", 0, false}, + {"-+123", 0, false}, + {"+-123", 0, false}, + {"-", 0, false}, + {"-2147483649", INT_MIN, false}, + {"-99999999999", INT_MIN, false}, + {"2147483648", INT_MAX, false}, + {"99999999999", INT_MAX, false}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + int output = 0; + EXPECT_EQ(cases[i].success, StringToInt(cases[i].input, &output)); + EXPECT_EQ(cases[i].output, output); + + string16 utf16_input = UTF8ToUTF16(cases[i].input); + output = 0; + EXPECT_EQ(cases[i].success, StringToInt(utf16_input, &output)); + EXPECT_EQ(cases[i].output, output); + } + + // One additional test to verify that conversion of numbers in strings with + // embedded NUL characters. The NUL and extra data after it should be + // interpreted as junk after the number. + const char input[] = "6\06"; + std::string input_string(input, arraysize(input) - 1); + int output; + EXPECT_FALSE(StringToInt(input_string, &output)); + EXPECT_EQ(6, output); + + string16 utf16_input = UTF8ToUTF16(input_string); + output = 0; + EXPECT_FALSE(StringToInt(utf16_input, &output)); + EXPECT_EQ(6, output); + + output = 0; + const char16 negative_wide_input[] = { 0xFF4D, '4', '2', 0}; + EXPECT_FALSE(StringToInt(string16(negative_wide_input), &output)); + EXPECT_EQ(0, output); +} + +TEST(StringNumberConversionsTest, StringToUint) { + static const struct { + std::string input; + unsigned output; + bool success; + } cases[] = { + {"0", 0, true}, + {"42", 42, true}, + {"42\x99", 42, false}, + {"\x99" "42\x99", 0, false}, + {"-2147483648", 0, false}, + {"2147483647", INT_MAX, true}, + {"", 0, false}, + {" 42", 42, false}, + {"42 ", 42, false}, + {"\t\n\v\f\r 42", 42, false}, + {"blah42", 0, false}, + {"42blah", 42, false}, + {"blah42blah", 0, false}, + {"-273.15", 0, false}, + {"+98.6", 98, false}, + {"--123", 0, false}, + {"++123", 0, false}, + {"-+123", 0, false}, + {"+-123", 0, false}, + {"-", 0, false}, + {"-2147483649", 0, false}, + {"-99999999999", 0, false}, + {"4294967295", UINT_MAX, true}, + {"4294967296", UINT_MAX, false}, + {"99999999999", UINT_MAX, false}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + unsigned output = 0; + EXPECT_EQ(cases[i].success, StringToUint(cases[i].input, &output)); + EXPECT_EQ(cases[i].output, output); + + string16 utf16_input = UTF8ToUTF16(cases[i].input); + output = 0; + EXPECT_EQ(cases[i].success, StringToUint(utf16_input, &output)); + EXPECT_EQ(cases[i].output, output); + } + + // One additional test to verify that conversion of numbers in strings with + // embedded NUL characters. The NUL and extra data after it should be + // interpreted as junk after the number. + const char input[] = "6\06"; + std::string input_string(input, arraysize(input) - 1); + unsigned output; + EXPECT_FALSE(StringToUint(input_string, &output)); + EXPECT_EQ(6U, output); + + string16 utf16_input = UTF8ToUTF16(input_string); + output = 0; + EXPECT_FALSE(StringToUint(utf16_input, &output)); + EXPECT_EQ(6U, output); + + output = 0; + const char16 negative_wide_input[] = { 0xFF4D, '4', '2', 0}; + EXPECT_FALSE(StringToUint(string16(negative_wide_input), &output)); + EXPECT_EQ(0U, output); +} + +TEST(StringNumberConversionsTest, StringToInt64) { + static const struct { + std::string input; + int64 output; + bool success; + } cases[] = { + {"0", 0, true}, + {"42", 42, true}, + {"-2147483648", INT_MIN, true}, + {"2147483647", INT_MAX, true}, + {"-2147483649", GG_INT64_C(-2147483649), true}, + {"-99999999999", GG_INT64_C(-99999999999), true}, + {"2147483648", GG_INT64_C(2147483648), true}, + {"99999999999", GG_INT64_C(99999999999), true}, + {"9223372036854775807", kint64max, true}, + {"-9223372036854775808", kint64min, true}, + {"09", 9, true}, + {"-09", -9, true}, + {"", 0, false}, + {" 42", 42, false}, + {"42 ", 42, false}, + {"0x42", 0, false}, + {"\t\n\v\f\r 42", 42, false}, + {"blah42", 0, false}, + {"42blah", 42, false}, + {"blah42blah", 0, false}, + {"-273.15", -273, false}, + {"+98.6", 98, false}, + {"--123", 0, false}, + {"++123", 0, false}, + {"-+123", 0, false}, + {"+-123", 0, false}, + {"-", 0, false}, + {"-9223372036854775809", kint64min, false}, + {"-99999999999999999999", kint64min, false}, + {"9223372036854775808", kint64max, false}, + {"99999999999999999999", kint64max, false}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + int64 output = 0; + EXPECT_EQ(cases[i].success, StringToInt64(cases[i].input, &output)); + EXPECT_EQ(cases[i].output, output); + + string16 utf16_input = UTF8ToUTF16(cases[i].input); + output = 0; + EXPECT_EQ(cases[i].success, StringToInt64(utf16_input, &output)); + EXPECT_EQ(cases[i].output, output); + } + + // One additional test to verify that conversion of numbers in strings with + // embedded NUL characters. The NUL and extra data after it should be + // interpreted as junk after the number. + const char input[] = "6\06"; + std::string input_string(input, arraysize(input) - 1); + int64 output; + EXPECT_FALSE(StringToInt64(input_string, &output)); + EXPECT_EQ(6, output); + + string16 utf16_input = UTF8ToUTF16(input_string); + output = 0; + EXPECT_FALSE(StringToInt64(utf16_input, &output)); + EXPECT_EQ(6, output); +} + +TEST(StringNumberConversionsTest, StringToUint64) { + static const struct { + std::string input; + uint64 output; + bool success; + } cases[] = { + {"0", 0, true}, + {"42", 42, true}, + {"-2147483648", 0, false}, + {"2147483647", INT_MAX, true}, + {"-2147483649", 0, false}, + {"-99999999999", 0, false}, + {"2147483648", GG_UINT64_C(2147483648), true}, + {"99999999999", GG_UINT64_C(99999999999), true}, + {"9223372036854775807", kint64max, true}, + {"-9223372036854775808", 0, false}, + {"09", 9, true}, + {"-09", 0, false}, + {"", 0, false}, + {" 42", 42, false}, + {"42 ", 42, false}, + {"0x42", 0, false}, + {"\t\n\v\f\r 42", 42, false}, + {"blah42", 0, false}, + {"42blah", 42, false}, + {"blah42blah", 0, false}, + {"-273.15", 0, false}, + {"+98.6", 98, false}, + {"--123", 0, false}, + {"++123", 0, false}, + {"-+123", 0, false}, + {"+-123", 0, false}, + {"-", 0, false}, + {"-9223372036854775809", 0, false}, + {"-99999999999999999999", 0, false}, + {"9223372036854775808", GG_UINT64_C(9223372036854775808), true}, + {"99999999999999999999", kuint64max, false}, + {"18446744073709551615", kuint64max, true}, + {"18446744073709551616", kuint64max, false}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + uint64 output = 0; + EXPECT_EQ(cases[i].success, StringToUint64(cases[i].input, &output)); + EXPECT_EQ(cases[i].output, output); + + string16 utf16_input = UTF8ToUTF16(cases[i].input); + output = 0; + EXPECT_EQ(cases[i].success, StringToUint64(utf16_input, &output)); + EXPECT_EQ(cases[i].output, output); + } + + // One additional test to verify that conversion of numbers in strings with + // embedded NUL characters. The NUL and extra data after it should be + // interpreted as junk after the number. + const char input[] = "6\06"; + std::string input_string(input, arraysize(input) - 1); + uint64 output; + EXPECT_FALSE(StringToUint64(input_string, &output)); + EXPECT_EQ(6U, output); + + string16 utf16_input = UTF8ToUTF16(input_string); + output = 0; + EXPECT_FALSE(StringToUint64(utf16_input, &output)); + EXPECT_EQ(6U, output); +} + +TEST(StringNumberConversionsTest, StringToSizeT) { + + size_t size_t_max = std::numeric_limits::max(); + std::string size_t_max_string = StringPrintf("%" PRIuS, size_t_max); + + static const struct { + std::string input; + size_t output; + bool success; + } cases[] = { + {"0", 0, true}, + {"42", 42, true}, + {"-2147483648", 0, false}, + {"2147483647", INT_MAX, true}, + {"-2147483649", 0, false}, + {"-99999999999", 0, false}, + {"2147483648", 2147483648U, true}, +#if SIZE_MAX > 4294967295U + {"99999999999", 99999999999U, true}, +#endif + {"-9223372036854775808", 0, false}, + {"09", 9, true}, + {"-09", 0, false}, + {"", 0, false}, + {" 42", 42, false}, + {"42 ", 42, false}, + {"0x42", 0, false}, + {"\t\n\v\f\r 42", 42, false}, + {"blah42", 0, false}, + {"42blah", 42, false}, + {"blah42blah", 0, false}, + {"-273.15", 0, false}, + {"+98.6", 98, false}, + {"--123", 0, false}, + {"++123", 0, false}, + {"-+123", 0, false}, + {"+-123", 0, false}, + {"-", 0, false}, + {"-9223372036854775809", 0, false}, + {"-99999999999999999999", 0, false}, + {"999999999999999999999999", size_t_max, false}, + {size_t_max_string, size_t_max, true}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + size_t output = 0; + EXPECT_EQ(cases[i].success, StringToSizeT(cases[i].input, &output)); + EXPECT_EQ(cases[i].output, output); + + string16 utf16_input = UTF8ToUTF16(cases[i].input); + output = 0; + EXPECT_EQ(cases[i].success, StringToSizeT(utf16_input, &output)); + EXPECT_EQ(cases[i].output, output); + } + + // One additional test to verify that conversion of numbers in strings with + // embedded NUL characters. The NUL and extra data after it should be + // interpreted as junk after the number. + const char input[] = "6\06"; + std::string input_string(input, arraysize(input) - 1); + size_t output; + EXPECT_FALSE(StringToSizeT(input_string, &output)); + EXPECT_EQ(6U, output); + + string16 utf16_input = UTF8ToUTF16(input_string); + output = 0; + EXPECT_FALSE(StringToSizeT(utf16_input, &output)); + EXPECT_EQ(6U, output); +} + +TEST(StringNumberConversionsTest, HexStringToInt) { + static const struct { + std::string input; + int64 output; + bool success; + } cases[] = { + {"0", 0, true}, + {"42", 66, true}, + {"-42", -66, true}, + {"+42", 66, true}, + {"7fffffff", INT_MAX, true}, + {"-80000000", INT_MIN, true}, + {"80000000", INT_MAX, false}, // Overflow test. + {"-80000001", INT_MIN, false}, // Underflow test. + {"0x42", 66, true}, + {"-0x42", -66, true}, + {"+0x42", 66, true}, + {"0x7fffffff", INT_MAX, true}, + {"-0x80000000", INT_MIN, true}, + {"-80000000", INT_MIN, true}, + {"80000000", INT_MAX, false}, // Overflow test. + {"-80000001", INT_MIN, false}, // Underflow test. + {"0x0f", 15, true}, + {"0f", 15, true}, + {" 45", 0x45, false}, + {"\t\n\v\f\r 0x45", 0x45, false}, + {" 45", 0x45, false}, + {"45 ", 0x45, false}, + {"45:", 0x45, false}, + {"efgh", 0xef, false}, + {"0xefgh", 0xef, false}, + {"hgfe", 0, false}, + {"-", 0, false}, + {"", 0, false}, + {"0x", 0, false}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + int output = 0; + EXPECT_EQ(cases[i].success, HexStringToInt(cases[i].input, &output)); + EXPECT_EQ(cases[i].output, output); + } + // One additional test to verify that conversion of numbers in strings with + // embedded NUL characters. The NUL and extra data after it should be + // interpreted as junk after the number. + const char input[] = "0xc0ffee\09"; + std::string input_string(input, arraysize(input) - 1); + int output; + EXPECT_FALSE(HexStringToInt(input_string, &output)); + EXPECT_EQ(0xc0ffee, output); +} + +TEST(StringNumberConversionsTest, HexStringToInt64) { + static const struct { + std::string input; + int64 output; + bool success; + } cases[] = { + {"0", 0, true}, + {"42", 66, true}, + {"-42", -66, true}, + {"+42", 66, true}, + {"40acd88557b", GG_INT64_C(4444444448123), true}, + {"7fffffff", INT_MAX, true}, + {"-80000000", INT_MIN, true}, + {"ffffffff", 0xffffffff, true}, + {"DeadBeef", 0xdeadbeef, true}, + {"0x42", 66, true}, + {"-0x42", -66, true}, + {"+0x42", 66, true}, + {"0x40acd88557b", GG_INT64_C(4444444448123), true}, + {"0x7fffffff", INT_MAX, true}, + {"-0x80000000", INT_MIN, true}, + {"0xffffffff", 0xffffffff, true}, + {"0XDeadBeef", 0xdeadbeef, true}, + {"0x7fffffffffffffff", kint64max, true}, + {"-0x8000000000000000", kint64min, true}, + {"0x8000000000000000", kint64max, false}, // Overflow test. + {"-0x8000000000000001", kint64min, false}, // Underflow test. + {"0x0f", 15, true}, + {"0f", 15, true}, + {" 45", 0x45, false}, + {"\t\n\v\f\r 0x45", 0x45, false}, + {" 45", 0x45, false}, + {"45 ", 0x45, false}, + {"45:", 0x45, false}, + {"efgh", 0xef, false}, + {"0xefgh", 0xef, false}, + {"hgfe", 0, false}, + {"-", 0, false}, + {"", 0, false}, + {"0x", 0, false}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + int64 output = 0; + EXPECT_EQ(cases[i].success, HexStringToInt64(cases[i].input, &output)); + EXPECT_EQ(cases[i].output, output); + } + // One additional test to verify that conversion of numbers in strings with + // embedded NUL characters. The NUL and extra data after it should be + // interpreted as junk after the number. + const char input[] = "0xc0ffee\09"; + std::string input_string(input, arraysize(input) - 1); + int64 output; + EXPECT_FALSE(HexStringToInt64(input_string, &output)); + EXPECT_EQ(0xc0ffee, output); +} + +TEST(StringNumberConversionsTest, HexStringToUInt64) { + static const struct { + std::string input; + uint64 output; + bool success; + } cases[] = { + {"0", 0, true}, + {"42", 66, true}, + {"-42", 0, false}, + {"+42", 66, true}, + {"40acd88557b", GG_INT64_C(4444444448123), true}, + {"7fffffff", INT_MAX, true}, + {"-80000000", 0, false}, + {"ffffffff", 0xffffffff, true}, + {"DeadBeef", 0xdeadbeef, true}, + {"0x42", 66, true}, + {"-0x42", 0, false}, + {"+0x42", 66, true}, + {"0x40acd88557b", GG_INT64_C(4444444448123), true}, + {"0x7fffffff", INT_MAX, true}, + {"-0x80000000", 0, false}, + {"0xffffffff", 0xffffffff, true}, + {"0XDeadBeef", 0xdeadbeef, true}, + {"0x7fffffffffffffff", kint64max, true}, + {"-0x8000000000000000", 0, false}, + {"0x8000000000000000", GG_UINT64_C(0x8000000000000000), true}, + {"-0x8000000000000001", 0, false}, + {"0xFFFFFFFFFFFFFFFF", kuint64max, true}, + {"FFFFFFFFFFFFFFFF", kuint64max, true}, + {"0x0000000000000000", 0, true}, + {"0000000000000000", 0, true}, + {"1FFFFFFFFFFFFFFFF", kuint64max, false}, // Overflow test. + {"0x0f", 15, true}, + {"0f", 15, true}, + {" 45", 0x45, false}, + {"\t\n\v\f\r 0x45", 0x45, false}, + {" 45", 0x45, false}, + {"45 ", 0x45, false}, + {"45:", 0x45, false}, + {"efgh", 0xef, false}, + {"0xefgh", 0xef, false}, + {"hgfe", 0, false}, + {"-", 0, false}, + {"", 0, false}, + {"0x", 0, false}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + uint64 output = 0; + EXPECT_EQ(cases[i].success, HexStringToUInt64(cases[i].input, &output)); + EXPECT_EQ(cases[i].output, output); + } + // One additional test to verify that conversion of numbers in strings with + // embedded NUL characters. The NUL and extra data after it should be + // interpreted as junk after the number. + const char input[] = "0xc0ffee\09"; + std::string input_string(input, arraysize(input) - 1); + uint64 output; + EXPECT_FALSE(HexStringToUInt64(input_string, &output)); + EXPECT_EQ(0xc0ffeeU, output); +} + +TEST(StringNumberConversionsTest, HexStringToBytes) { + static const struct { + const std::string input; + const char* output; + size_t output_len; + bool success; + } cases[] = { + {"0", "", 0, false}, // odd number of characters fails + {"00", "\0", 1, true}, + {"42", "\x42", 1, true}, + {"-42", "", 0, false}, // any non-hex value fails + {"+42", "", 0, false}, + {"7fffffff", "\x7f\xff\xff\xff", 4, true}, + {"80000000", "\x80\0\0\0", 4, true}, + {"deadbeef", "\xde\xad\xbe\xef", 4, true}, + {"DeadBeef", "\xde\xad\xbe\xef", 4, true}, + {"0x42", "", 0, false}, // leading 0x fails (x is not hex) + {"0f", "\xf", 1, true}, + {"45 ", "\x45", 1, false}, + {"efgh", "\xef", 1, false}, + {"", "", 0, false}, + {"0123456789ABCDEF", "\x01\x23\x45\x67\x89\xAB\xCD\xEF", 8, true}, + {"0123456789ABCDEF012345", + "\x01\x23\x45\x67\x89\xAB\xCD\xEF\x01\x23\x45", 11, true}, + }; + + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + std::vector output; + std::vector compare; + EXPECT_EQ(cases[i].success, HexStringToBytes(cases[i].input, &output)) << + i << ": " << cases[i].input; + for (size_t j = 0; j < cases[i].output_len; ++j) + compare.push_back(static_cast(cases[i].output[j])); + ASSERT_EQ(output.size(), compare.size()) << i << ": " << cases[i].input; + EXPECT_TRUE(std::equal(output.begin(), output.end(), compare.begin())) << + i << ": " << cases[i].input; + } +} + +TEST(StringNumberConversionsTest, StringToDouble) { + static const struct { + std::string input; + double output; + bool success; + } cases[] = { + {"0", 0.0, true}, + {"42", 42.0, true}, + {"-42", -42.0, true}, + {"123.45", 123.45, true}, + {"-123.45", -123.45, true}, + {"+123.45", 123.45, true}, + {"2.99792458e8", 299792458.0, true}, + {"149597870.691E+3", 149597870691.0, true}, + {"6.", 6.0, true}, + {"9e99999999999999999999", HUGE_VAL, false}, + {"-9e99999999999999999999", -HUGE_VAL, false}, + {"1e-2", 0.01, true}, + {"42 ", 42.0, false}, + {" 1e-2", 0.01, false}, + {"1e-2 ", 0.01, false}, + {"-1E-7", -0.0000001, true}, + {"01e02", 100, true}, + {"2.3e15", 2.3e15, true}, + {"\t\n\v\f\r -123.45e2", -12345.0, false}, + {"+123 e4", 123.0, false}, + {"123e ", 123.0, false}, + {"123e", 123.0, false}, + {" 2.99", 2.99, false}, + {"1e3.4", 1000.0, false}, + {"nothing", 0.0, false}, + {"-", 0.0, false}, + {"+", 0.0, false}, + {"", 0.0, false}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + double output; + errno = 1; + EXPECT_EQ(cases[i].success, StringToDouble(cases[i].input, &output)); + if (cases[i].success) + EXPECT_EQ(1, errno) << i; // confirm that errno is unchanged. + EXPECT_DOUBLE_EQ(cases[i].output, output); + } + + // One additional test to verify that conversion of numbers in strings with + // embedded NUL characters. The NUL and extra data after it should be + // interpreted as junk after the number. + const char input[] = "3.14\0159"; + std::string input_string(input, arraysize(input) - 1); + double output; + EXPECT_FALSE(StringToDouble(input_string, &output)); + EXPECT_DOUBLE_EQ(3.14, output); +} + +TEST(StringNumberConversionsTest, DoubleToString) { + static const struct { + double input; + const char* expected; + } cases[] = { + {0.0, "0"}, + {1.25, "1.25"}, + {1.33518e+012, "1.33518e+12"}, + {1.33489e+012, "1.33489e+12"}, + {1.33505e+012, "1.33505e+12"}, + {1.33545e+009, "1335450000"}, + {1.33503e+009, "1335030000"}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + EXPECT_EQ(cases[i].expected, DoubleToString(cases[i].input)); + } + + // The following two values were seen in crashes in the wild. + const char input_bytes[8] = {0, 0, 0, 0, '\xee', '\x6d', '\x73', '\x42'}; + double input = 0; + memcpy(&input, input_bytes, arraysize(input_bytes)); + EXPECT_EQ("1335179083776", DoubleToString(input)); + const char input_bytes2[8] = + {0, 0, 0, '\xa0', '\xda', '\x6c', '\x73', '\x42'}; + input = 0; + memcpy(&input, input_bytes2, arraysize(input_bytes2)); + EXPECT_EQ("1334890332160", DoubleToString(input)); +} + +TEST(StringNumberConversionsTest, HexEncode) { + std::string hex(HexEncode(NULL, 0)); + EXPECT_EQ(hex.length(), 0U); + unsigned char bytes[] = {0x01, 0xff, 0x02, 0xfe, 0x03, 0x80, 0x81}; + hex = HexEncode(bytes, sizeof(bytes)); + EXPECT_EQ(hex.compare("01FF02FE038081"), 0); +} + +} // namespace base diff --git a/base/strings/string_piece.cc b/base/strings/string_piece.cc new file mode 100644 index 0000000000..79a42d7842 --- /dev/null +++ b/base/strings/string_piece.cc @@ -0,0 +1,255 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// Copied from strings/stringpiece.cc with modifications + +#include "base/strings/string_piece.h" + +#include +#include + +namespace base { + +// MSVC doesn't like complex extern templates and DLLs. +#if !defined(COMPILER_MSVC) +namespace internal { +template class StringPieceDetail; +template class StringPieceDetail; +} // namespace internal + +template class BasicStringPiece; +#endif + +bool operator==(const StringPiece& x, const StringPiece& y) { + if (x.size() != y.size()) + return false; + + return StringPiece::wordmemcmp(x.data(), y.data(), x.size()) == 0; +} + +std::ostream& operator<<(std::ostream& o, const StringPiece& piece) { + o.write(piece.data(), static_cast(piece.size())); + return o; +} + +namespace internal { +void CopyToString(const StringPiece& self, std::string* target) { + target->assign(!self.empty() ? self.data() : "", self.size()); +} + +void AppendToString(const StringPiece& self, std::string* target) { + if (!self.empty()) + target->append(self.data(), self.size()); +} + +StringPiece::size_type copy(const StringPiece& self, + char* buf, + StringPiece::size_type n, + StringPiece::size_type pos) { + StringPiece::size_type ret = std::min(self.size() - pos, n); + memcpy(buf, self.data() + pos, ret); + return ret; +} + +StringPiece::size_type find(const StringPiece& self, + const StringPiece& s, + StringPiece::size_type pos) { + if (pos > self.size()) + return StringPiece::npos; + + StringPiece::const_iterator result = + std::search(self.begin() + pos, self.end(), s.begin(), s.end()); + const StringPiece::size_type xpos = + static_cast(result - self.begin()); + return xpos + s.size() <= self.size() ? xpos : StringPiece::npos; +} + +StringPiece::size_type find(const StringPiece& self, + char c, + StringPiece::size_type pos) { + if (pos >= self.size()) + return StringPiece::npos; + + StringPiece::const_iterator result = + std::find(self.begin() + pos, self.end(), c); + return result != self.end() ? + static_cast(result - self.begin()) : StringPiece::npos; +} + +StringPiece::size_type rfind(const StringPiece& self, + const StringPiece& s, + StringPiece::size_type pos) { + if (self.size() < s.size()) + return StringPiece::npos; + + if (s.empty()) + return std::min(self.size(), pos); + + StringPiece::const_iterator last = + self.begin() + std::min(self.size() - s.size(), pos) + s.size(); + StringPiece::const_iterator result = + std::find_end(self.begin(), last, s.begin(), s.end()); + return result != last ? + static_cast(result - self.begin()) : StringPiece::npos; +} + +StringPiece::size_type rfind(const StringPiece& self, + char c, + StringPiece::size_type pos) { + if (self.size() == 0) + return StringPiece::npos; + + for (StringPiece::size_type i = std::min(pos, self.size() - 1); ; --i) { + if (self.data()[i] == c) + return i; + if (i == 0) + break; + } + return StringPiece::npos; +} + +// For each character in characters_wanted, sets the index corresponding +// to the ASCII code of that character to 1 in table. This is used by +// the find_.*_of methods below to tell whether or not a character is in +// the lookup table in constant time. +// The argument `table' must be an array that is large enough to hold all +// the possible values of an unsigned char. Thus it should be be declared +// as follows: +// bool table[UCHAR_MAX + 1] +static inline void BuildLookupTable(const StringPiece& characters_wanted, + bool* table) { + const StringPiece::size_type length = characters_wanted.length(); + const char* const data = characters_wanted.data(); + for (StringPiece::size_type i = 0; i < length; ++i) { + table[static_cast(data[i])] = true; + } +} + +StringPiece::size_type find_first_of(const StringPiece& self, + const StringPiece& s, + StringPiece::size_type pos) { + if (self.size() == 0 || s.size() == 0) + return StringPiece::npos; + + // Avoid the cost of BuildLookupTable() for a single-character search. + if (s.size() == 1) + return find(self, s.data()[0], pos); + + bool lookup[UCHAR_MAX + 1] = { false }; + BuildLookupTable(s, lookup); + for (StringPiece::size_type i = pos; i < self.size(); ++i) { + if (lookup[static_cast(self.data()[i])]) { + return i; + } + } + return StringPiece::npos; +} + +StringPiece::size_type find_first_not_of(const StringPiece& self, + const StringPiece& s, + StringPiece::size_type pos) { + if (self.size() == 0) + return StringPiece::npos; + + if (s.size() == 0) + return 0; + + // Avoid the cost of BuildLookupTable() for a single-character search. + if (s.size() == 1) + return find_first_not_of(self, s.data()[0], pos); + + bool lookup[UCHAR_MAX + 1] = { false }; + BuildLookupTable(s, lookup); + for (StringPiece::size_type i = pos; i < self.size(); ++i) { + if (!lookup[static_cast(self.data()[i])]) { + return i; + } + } + return StringPiece::npos; +} + +StringPiece::size_type find_first_not_of(const StringPiece& self, + char c, + StringPiece::size_type pos) { + if (self.size() == 0) + return StringPiece::npos; + + for (; pos < self.size(); ++pos) { + if (self.data()[pos] != c) { + return pos; + } + } + return StringPiece::npos; +} + +StringPiece::size_type find_last_of(const StringPiece& self, + const StringPiece& s, + StringPiece::size_type pos) { + if (self.size() == 0 || s.size() == 0) + return StringPiece::npos; + + // Avoid the cost of BuildLookupTable() for a single-character search. + if (s.size() == 1) + return rfind(self, s.data()[0], pos); + + bool lookup[UCHAR_MAX + 1] = { false }; + BuildLookupTable(s, lookup); + for (StringPiece::size_type i = std::min(pos, self.size() - 1); ; --i) { + if (lookup[static_cast(self.data()[i])]) + return i; + if (i == 0) + break; + } + return StringPiece::npos; +} + +StringPiece::size_type find_last_not_of(const StringPiece& self, + const StringPiece& s, + StringPiece::size_type pos) { + if (self.size() == 0) + return StringPiece::npos; + + StringPiece::size_type i = std::min(pos, self.size() - 1); + if (s.size() == 0) + return i; + + // Avoid the cost of BuildLookupTable() for a single-character search. + if (s.size() == 1) + return find_last_not_of(self, s.data()[0], pos); + + bool lookup[UCHAR_MAX + 1] = { false }; + BuildLookupTable(s, lookup); + for (; ; --i) { + if (!lookup[static_cast(self.data()[i])]) + return i; + if (i == 0) + break; + } + return StringPiece::npos; +} + +StringPiece::size_type find_last_not_of(const StringPiece& self, + char c, + StringPiece::size_type pos) { + if (self.size() == 0) + return StringPiece::npos; + + for (StringPiece::size_type i = std::min(pos, self.size() - 1); ; --i) { + if (self.data()[i] != c) + return i; + if (i == 0) + break; + } + return StringPiece::npos; +} + +StringPiece substr(const StringPiece& self, + StringPiece::size_type pos, + StringPiece::size_type n) { + if (pos > self.size()) pos = self.size(); + if (n > self.size() - pos) n = self.size() - pos; + return StringPiece(self.data() + pos, n); +} + +} // namespace internal +} // namespace base diff --git a/base/strings/string_piece.h b/base/strings/string_piece.h new file mode 100644 index 0000000000..818d6ca598 --- /dev/null +++ b/base/strings/string_piece.h @@ -0,0 +1,451 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// Copied from strings/stringpiece.h with modifications +// +// A string-like object that points to a sized piece of memory. +// +// Functions or methods may use const StringPiece& parameters to accept either +// a "const char*" or a "string" value that will be implicitly converted to +// a StringPiece. The implicit conversion means that it is often appropriate +// to include this .h file in other files rather than forward-declaring +// StringPiece as would be appropriate for most other Google classes. +// +// Systematic usage of StringPiece is encouraged as it will reduce unnecessary +// conversions from "const char*" to "string" and back again. +// +// StringPiece16 is similar to StringPiece but for base::string16 instead of +// std::string. We do not define as large of a subset of the STL functions +// from basic_string as in StringPiece, but this can be changed if these +// functions (find, find_first_of, etc.) are found to be useful in this context. +// + +#ifndef BASE_STRINGS_STRING_PIECE_H_ +#define BASE_STRINGS_STRING_PIECE_H_ + +#include + +#include +#include + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/containers/hash_tables.h" +#include "base/strings/string16.h" + +namespace base { + +template class BasicStringPiece; +typedef BasicStringPiece StringPiece; +typedef BasicStringPiece StringPiece16; + +namespace internal { + +// Defines the types, methods, operators, and data members common to both +// StringPiece and StringPiece16. Do not refer to this class directly, but +// rather to BasicStringPiece, StringPiece, or StringPiece16. +template class StringPieceDetail { + public: + // standard STL container boilerplate + typedef size_t size_type; + typedef typename STRING_TYPE::value_type value_type; + typedef const value_type* pointer; + typedef const value_type& reference; + typedef const value_type& const_reference; + typedef ptrdiff_t difference_type; + typedef const value_type* const_iterator; + typedef std::reverse_iterator const_reverse_iterator; + + static const size_type npos; + + public: + // We provide non-explicit singleton constructors so users can pass + // in a "const char*" or a "string" wherever a "StringPiece" is + // expected (likewise for char16, string16, StringPiece16). + StringPieceDetail() : ptr_(NULL), length_(0) {} + StringPieceDetail(const value_type* str) + : ptr_(str), + length_((str == NULL) ? 0 : STRING_TYPE::traits_type::length(str)) {} + StringPieceDetail(const STRING_TYPE& str) + : ptr_(str.data()), length_(str.size()) {} + StringPieceDetail(const value_type* offset, size_type len) + : ptr_(offset), length_(len) {} + StringPieceDetail(const typename STRING_TYPE::const_iterator& begin, + const typename STRING_TYPE::const_iterator& end) + : ptr_((end > begin) ? &(*begin) : NULL), + length_((end > begin) ? (size_type)(end - begin) : 0) {} + + // data() may return a pointer to a buffer with embedded NULs, and the + // returned buffer may or may not be null terminated. Therefore it is + // typically a mistake to pass data() to a routine that expects a NUL + // terminated string. + const value_type* data() const { return ptr_; } + size_type size() const { return length_; } + size_type length() const { return length_; } + bool empty() const { return length_ == 0; } + + void clear() { + ptr_ = NULL; + length_ = 0; + } + void set(const value_type* data, size_type len) { + ptr_ = data; + length_ = len; + } + void set(const value_type* str) { + ptr_ = str; + length_ = str ? STRING_TYPE::traits_type::length(str) : 0; + } + + value_type operator[](size_type i) const { return ptr_[i]; } + + void remove_prefix(size_type n) { + ptr_ += n; + length_ -= n; + } + + void remove_suffix(size_type n) { + length_ -= n; + } + + int compare(const BasicStringPiece& x) const { + int r = wordmemcmp( + ptr_, x.ptr_, (length_ < x.length_ ? length_ : x.length_)); + if (r == 0) { + if (length_ < x.length_) r = -1; + else if (length_ > x.length_) r = +1; + } + return r; + } + + STRING_TYPE as_string() const { + // std::string doesn't like to take a NULL pointer even with a 0 size. + return empty() ? STRING_TYPE() : STRING_TYPE(data(), size()); + } + + const_iterator begin() const { return ptr_; } + const_iterator end() const { return ptr_ + length_; } + const_reverse_iterator rbegin() const { + return const_reverse_iterator(ptr_ + length_); + } + const_reverse_iterator rend() const { + return const_reverse_iterator(ptr_); + } + + size_type max_size() const { return length_; } + size_type capacity() const { return length_; } + + static int wordmemcmp(const value_type* p, + const value_type* p2, + size_type N) { + return STRING_TYPE::traits_type::compare(p, p2, N); + } + + protected: + const value_type* ptr_; + size_type length_; +}; + +template +const typename StringPieceDetail::size_type +StringPieceDetail::npos = + typename StringPieceDetail::size_type(-1); + +// MSVC doesn't like complex extern templates and DLLs. +#if !defined(COMPILER_MSVC) +extern template class BASE_EXPORT StringPieceDetail; +extern template class BASE_EXPORT StringPieceDetail; +#endif + +BASE_EXPORT void CopyToString(const StringPiece& self, std::string* target); +BASE_EXPORT void AppendToString(const StringPiece& self, std::string* target); +BASE_EXPORT StringPieceDetail::size_type copy( + const StringPiece& self, + char* buf, + StringPieceDetail::size_type n, + StringPieceDetail::size_type pos); +BASE_EXPORT StringPieceDetail::size_type find( + const StringPiece& self, + const StringPiece& s, + StringPieceDetail::size_type pos); +BASE_EXPORT StringPieceDetail::size_type find( + const StringPiece& self, + char c, + StringPieceDetail::size_type pos); +BASE_EXPORT StringPieceDetail::size_type rfind( + const StringPiece& self, + const StringPiece& s, + StringPieceDetail::size_type pos); +BASE_EXPORT StringPieceDetail::size_type rfind( + const StringPiece& self, + char c, + StringPieceDetail::size_type pos); +BASE_EXPORT StringPieceDetail::size_type find_first_of( + const StringPiece& self, + const StringPiece& s, + StringPieceDetail::size_type pos); +BASE_EXPORT StringPieceDetail::size_type find_first_not_of( + const StringPiece& self, + const StringPiece& s, + StringPieceDetail::size_type pos); +BASE_EXPORT StringPieceDetail::size_type find_first_not_of( + const StringPiece& self, + char c, + StringPieceDetail::size_type pos); +BASE_EXPORT StringPieceDetail::size_type find_last_of( + const StringPiece& self, + const StringPiece& s, + StringPieceDetail::size_type pos); +BASE_EXPORT StringPieceDetail::size_type find_last_of( + const StringPiece& self, + char c, + StringPieceDetail::size_type pos); +BASE_EXPORT StringPieceDetail::size_type find_last_not_of( + const StringPiece& self, + const StringPiece& s, + StringPieceDetail::size_type pos); +BASE_EXPORT StringPieceDetail::size_type find_last_not_of( + const StringPiece& self, + char c, + StringPieceDetail::size_type pos); +BASE_EXPORT StringPiece substr(const StringPiece& self, + StringPieceDetail::size_type pos, + StringPieceDetail::size_type n); +} // namespace internal + +// Defines the template type that is instantiated as either StringPiece or +// StringPiece16. +template class BasicStringPiece : + public internal::StringPieceDetail { + public: + typedef typename internal::StringPieceDetail::value_type + value_type; + typedef typename internal::StringPieceDetail::size_type + size_type; + + BasicStringPiece() {} + BasicStringPiece(const value_type*str) + : internal::StringPieceDetail(str) {} + BasicStringPiece(const STRING_TYPE& str) + : internal::StringPieceDetail(str) {} + BasicStringPiece(const value_type* offset, size_type len) + : internal::StringPieceDetail(offset, len) {} + BasicStringPiece(const typename STRING_TYPE::const_iterator& begin, + const typename STRING_TYPE::const_iterator& end) + : internal::StringPieceDetail(begin, end) {} +}; + +// Specializes BasicStringPiece for std::string to add a few operations that +// are not needed for string16. +template <> class BasicStringPiece : + public internal::StringPieceDetail { + public: + BasicStringPiece() {} + BasicStringPiece(const char* str) + : internal::StringPieceDetail(str) {} + BasicStringPiece(const std::string& str) + : internal::StringPieceDetail(str) {} + BasicStringPiece(const char* offset, size_type len) + : internal::StringPieceDetail(offset, len) {} + BasicStringPiece(const std::string::const_iterator& begin, + const std::string::const_iterator& end) + : internal::StringPieceDetail(begin, end) {} + + // Prevent the following overload of set() from hiding the definitions in the + // base class. + using internal::StringPieceDetail::set; + + void set(const void* data, size_type len) { + ptr_ = reinterpret_cast(data); + length_ = len; + } + + void CopyToString(std::string* target) const { + internal::CopyToString(*this, target); + } + + void AppendToString(std::string* target) const { + internal::AppendToString(*this, target); + } + + // Does "this" start with "x" + bool starts_with(const BasicStringPiece& x) const { + return ((length_ >= x.length_) && + (wordmemcmp(ptr_, x.ptr_, x.length_) == 0)); + } + + // Does "this" end with "x" + bool ends_with(const BasicStringPiece& x) const { + return ((length_ >= x.length_) && + (wordmemcmp(ptr_ + (length_-x.length_), x.ptr_, x.length_) == 0)); + } + + size_type copy(char* buf, size_type n, size_type pos = 0) const { + return internal::copy(*this, buf, n, pos); + } + + size_type find(const BasicStringPiece& s, size_type pos = 0) const { + return internal::find(*this, s, pos); + } + + size_type find(char c, size_type pos = 0) const { + return internal::find(*this, c, pos); + } + + size_type rfind(const BasicStringPiece& s, size_type pos = npos) const { + return internal::rfind(*this, s, pos); + } + + size_type rfind(char c, size_type pos = npos) const { + return internal::rfind(*this, c, pos); + } + + size_type find_first_of(const BasicStringPiece& s, size_type pos = 0) const { + return internal::find_first_of(*this, s, pos); + } + + size_type find_first_of(char c, size_type pos = 0) const { + return find(c, pos); + } + + size_type find_first_not_of(const BasicStringPiece& s, + size_type pos = 0) const { + return internal::find_first_not_of(*this, s, pos); + } + + size_type find_first_not_of(char c, size_type pos = 0) const { + return internal::find_first_not_of(*this, c, pos); + } + + size_type find_last_of(const BasicStringPiece& s, + size_type pos = npos) const { + return internal::find_last_of(*this, s, pos); + } + + size_type find_last_of(char c, size_type pos = npos) const { + return rfind(c, pos); + } + + size_type find_last_not_of(const BasicStringPiece& s, + size_type pos = npos) const { + return internal::find_last_not_of(*this, s, pos); + } + + size_type find_last_not_of(char c, size_type pos = npos) const { + return internal::find_last_not_of(*this, c, pos); + } + + BasicStringPiece substr(size_type pos, size_type n = npos) const { + return internal::substr(*this, pos, n); + } +}; + +// MSVC doesn't like complex extern templates and DLLs. +#if !defined(COMPILER_MSVC) +// We can't explicitly declare the std::string instantiation here because it was +// already instantiated when specialized, above. Not only is it a no-op, but +// currently it also crashes Clang (see http://crbug.com/107412). +extern template class BASE_EXPORT BasicStringPiece; +#endif + +BASE_EXPORT bool operator==(const StringPiece& x, const StringPiece& y); + +inline bool operator!=(const StringPiece& x, const StringPiece& y) { + return !(x == y); +} + +inline bool operator<(const StringPiece& x, const StringPiece& y) { + const int r = StringPiece::wordmemcmp( + x.data(), y.data(), (x.size() < y.size() ? x.size() : y.size())); + return ((r < 0) || ((r == 0) && (x.size() < y.size()))); +} + +inline bool operator>(const StringPiece& x, const StringPiece& y) { + return y < x; +} + +inline bool operator<=(const StringPiece& x, const StringPiece& y) { + return !(x > y); +} + +inline bool operator>=(const StringPiece& x, const StringPiece& y) { + return !(x < y); +} + +inline bool operator==(const StringPiece16& x, const StringPiece16& y) { + if (x.size() != y.size()) + return false; + + return StringPiece16::wordmemcmp(x.data(), y.data(), x.size()) == 0; +} + +inline bool operator!=(const StringPiece16& x, const StringPiece16& y) { + return !(x == y); +} + +inline bool operator<(const StringPiece16& x, const StringPiece16& y) { + const int r = StringPiece16::wordmemcmp( + x.data(), y.data(), (x.size() < y.size() ? x.size() : y.size())); + return ((r < 0) || ((r == 0) && (x.size() < y.size()))); +} + +inline bool operator>(const StringPiece16& x, const StringPiece16& y) { + return y < x; +} + +inline bool operator<=(const StringPiece16& x, const StringPiece16& y) { + return !(x > y); +} + +inline bool operator>=(const StringPiece16& x, const StringPiece16& y) { + return !(x < y); +} + +BASE_EXPORT std::ostream& operator<<(std::ostream& o, + const StringPiece& piece); + +} // namespace base + +// We provide appropriate hash functions so StringPiece and StringPiece16 can +// be used as keys in hash sets and maps. + +// This hash function is copied from base/containers/hash_tables.h. We don't +// use the ones already defined for string and string16 directly because it +// would require the string constructors to be called, which we don't want. +#define HASH_STRING_PIECE(StringPieceType, string_piece) \ + std::size_t result = 0; \ + for (StringPieceType::const_iterator i = string_piece.begin(); \ + i != string_piece.end(); ++i) \ + result = (result * 131) + *i; \ + return result; \ + +namespace BASE_HASH_NAMESPACE { +#if defined(COMPILER_GCC) + +template<> +struct hash { + std::size_t operator()(const base::StringPiece& sp) const { + HASH_STRING_PIECE(base::StringPiece, sp); + } +}; +template<> +struct hash { + std::size_t operator()(const base::StringPiece16& sp16) const { + HASH_STRING_PIECE(base::StringPiece16, sp16); + } +}; + +#elif defined(COMPILER_MSVC) + +inline size_t hash_value(const base::StringPiece& sp) { + HASH_STRING_PIECE(base::StringPiece, sp); +} +inline size_t hash_value(const base::StringPiece16& sp16) { + HASH_STRING_PIECE(base::StringPiece16, sp16); +} + +#endif // COMPILER + +} // namespace BASE_HASH_NAMESPACE + +#endif // BASE_STRINGS_STRING_PIECE_H_ diff --git a/base/strings/string_piece_unittest.cc b/base/strings/string_piece_unittest.cc new file mode 100644 index 0000000000..84ed9efca8 --- /dev/null +++ b/base/strings/string_piece_unittest.cc @@ -0,0 +1,677 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/strings/string16.h" +#include "base/strings/string_piece.h" +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +template +class CommonStringPieceTest : public ::testing::Test { + public: + static const T as_string(const char* input) { + return T(input); + } + static const T& as_string(const T& input) { + return input; + } +}; + +template <> +class CommonStringPieceTest : public ::testing::Test { + public: + static const string16 as_string(const char* input) { + return ASCIIToUTF16(input); + } + static const string16 as_string(const std::string& input) { + return ASCIIToUTF16(input); + } +}; + +typedef ::testing::Types SupportedStringTypes; + +TYPED_TEST_CASE(CommonStringPieceTest, SupportedStringTypes); + +TYPED_TEST(CommonStringPieceTest, CheckComparisonOperators) { +#define CMP_Y(op, x, y) \ + { \ + TypeParam lhs(TestFixture::as_string(x)); \ + TypeParam rhs(TestFixture::as_string(y)); \ + ASSERT_TRUE( (BasicStringPiece((lhs.c_str())) op \ + BasicStringPiece((rhs.c_str())))); \ + ASSERT_TRUE( (BasicStringPiece((lhs.c_str())).compare( \ + BasicStringPiece((rhs.c_str()))) op 0)); \ + } + +#define CMP_N(op, x, y) \ + { \ + TypeParam lhs(TestFixture::as_string(x)); \ + TypeParam rhs(TestFixture::as_string(y)); \ + ASSERT_FALSE( (BasicStringPiece((lhs.c_str())) op \ + BasicStringPiece((rhs.c_str())))); \ + ASSERT_FALSE( (BasicStringPiece((lhs.c_str())).compare( \ + BasicStringPiece((rhs.c_str()))) op 0)); \ + } + + CMP_Y(==, "", ""); + CMP_Y(==, "a", "a"); + CMP_Y(==, "aa", "aa"); + CMP_N(==, "a", ""); + CMP_N(==, "", "a"); + CMP_N(==, "a", "b"); + CMP_N(==, "a", "aa"); + CMP_N(==, "aa", "a"); + + CMP_N(!=, "", ""); + CMP_N(!=, "a", "a"); + CMP_N(!=, "aa", "aa"); + CMP_Y(!=, "a", ""); + CMP_Y(!=, "", "a"); + CMP_Y(!=, "a", "b"); + CMP_Y(!=, "a", "aa"); + CMP_Y(!=, "aa", "a"); + + CMP_Y(<, "a", "b"); + CMP_Y(<, "a", "aa"); + CMP_Y(<, "aa", "b"); + CMP_Y(<, "aa", "bb"); + CMP_N(<, "a", "a"); + CMP_N(<, "b", "a"); + CMP_N(<, "aa", "a"); + CMP_N(<, "b", "aa"); + CMP_N(<, "bb", "aa"); + + CMP_Y(<=, "a", "a"); + CMP_Y(<=, "a", "b"); + CMP_Y(<=, "a", "aa"); + CMP_Y(<=, "aa", "b"); + CMP_Y(<=, "aa", "bb"); + CMP_N(<=, "b", "a"); + CMP_N(<=, "aa", "a"); + CMP_N(<=, "b", "aa"); + CMP_N(<=, "bb", "aa"); + + CMP_N(>=, "a", "b"); + CMP_N(>=, "a", "aa"); + CMP_N(>=, "aa", "b"); + CMP_N(>=, "aa", "bb"); + CMP_Y(>=, "a", "a"); + CMP_Y(>=, "b", "a"); + CMP_Y(>=, "aa", "a"); + CMP_Y(>=, "b", "aa"); + CMP_Y(>=, "bb", "aa"); + + CMP_N(>, "a", "a"); + CMP_N(>, "a", "b"); + CMP_N(>, "a", "aa"); + CMP_N(>, "aa", "b"); + CMP_N(>, "aa", "bb"); + CMP_Y(>, "b", "a"); + CMP_Y(>, "aa", "a"); + CMP_Y(>, "b", "aa"); + CMP_Y(>, "bb", "aa"); + + std::string x; + for (int i = 0; i < 256; i++) { + x += 'a'; + std::string y = x; + CMP_Y(==, x, y); + for (int j = 0; j < i; j++) { + std::string z = x; + z[j] = 'b'; // Differs in position 'j' + CMP_N(==, x, z); + } + } + +#undef CMP_Y +#undef CMP_N +} + +TYPED_TEST(CommonStringPieceTest, CheckSTL) { + TypeParam alphabet(TestFixture::as_string("abcdefghijklmnopqrstuvwxyz")); + TypeParam abc(TestFixture::as_string("abc")); + TypeParam xyz(TestFixture::as_string("xyz")); + TypeParam foobar(TestFixture::as_string("foobar")); + + BasicStringPiece a(alphabet); + BasicStringPiece b(abc); + BasicStringPiece c(xyz); + BasicStringPiece d(foobar); + BasicStringPiece e; + TypeParam temp(TestFixture::as_string("123")); + temp += static_cast(0); + temp += TestFixture::as_string("456"); + BasicStringPiece f(temp); + + ASSERT_EQ(a[6], static_cast('g')); + ASSERT_EQ(b[0], static_cast('a')); + ASSERT_EQ(c[2], static_cast('z')); + ASSERT_EQ(f[3], static_cast('\0')); + ASSERT_EQ(f[5], static_cast('5')); + + ASSERT_EQ(*d.data(), static_cast('f')); + ASSERT_EQ(d.data()[5], static_cast('r')); + ASSERT_TRUE(e.data() == NULL); + + ASSERT_EQ(*a.begin(), static_cast('a')); + ASSERT_EQ(*(b.begin() + 2), static_cast('c')); + ASSERT_EQ(*(c.end() - 1), static_cast('z')); + + ASSERT_EQ(*a.rbegin(), static_cast('z')); + ASSERT_EQ(*(b.rbegin() + 2), + static_cast('a')); + ASSERT_EQ(*(c.rend() - 1), static_cast('x')); + ASSERT_TRUE(a.rbegin() + 26 == a.rend()); + + ASSERT_EQ(a.size(), 26U); + ASSERT_EQ(b.size(), 3U); + ASSERT_EQ(c.size(), 3U); + ASSERT_EQ(d.size(), 6U); + ASSERT_EQ(e.size(), 0U); + ASSERT_EQ(f.size(), 7U); + + ASSERT_TRUE(!d.empty()); + ASSERT_TRUE(d.begin() != d.end()); + ASSERT_TRUE(d.begin() + 6 == d.end()); + + ASSERT_TRUE(e.empty()); + ASSERT_TRUE(e.begin() == e.end()); + + d.clear(); + ASSERT_EQ(d.size(), 0U); + ASSERT_TRUE(d.empty()); + ASSERT_TRUE(d.data() == NULL); + ASSERT_TRUE(d.begin() == d.end()); + + ASSERT_GE(a.max_size(), a.capacity()); + ASSERT_GE(a.capacity(), a.size()); +} + +// STL stuff only supported by the std::string version +TEST(StringPieceTest, CheckSTL) { + StringPiece a("abcdefghijklmnopqrstuvwxyz"); + StringPiece b("abc"); + StringPiece c("xyz"); + StringPiece d("foobar"); + d.clear(); + StringPiece e; + std::string temp("123"); + temp += '\0'; + temp += "456"; + StringPiece f(temp); + + char buf[4] = { '%', '%', '%', '%' }; + ASSERT_EQ(a.copy(buf, 4), 4U); + ASSERT_EQ(buf[0], a[0]); + ASSERT_EQ(buf[1], a[1]); + ASSERT_EQ(buf[2], a[2]); + ASSERT_EQ(buf[3], a[3]); + ASSERT_EQ(a.copy(buf, 3, 7), 3U); + ASSERT_EQ(buf[0], a[7]); + ASSERT_EQ(buf[1], a[8]); + ASSERT_EQ(buf[2], a[9]); + ASSERT_EQ(buf[3], a[3]); + ASSERT_EQ(c.copy(buf, 99), 3U); + ASSERT_EQ(buf[0], c[0]); + ASSERT_EQ(buf[1], c[1]); + ASSERT_EQ(buf[2], c[2]); + ASSERT_EQ(buf[3], a[3]); + + ASSERT_EQ(StringPiece::npos, std::string::npos); + + ASSERT_EQ(a.find(b), 0U); + ASSERT_EQ(a.find(b, 1), StringPiece::npos); + ASSERT_EQ(a.find(c), 23U); + ASSERT_EQ(a.find(c, 9), 23U); + ASSERT_EQ(a.find(c, StringPiece::npos), StringPiece::npos); + ASSERT_EQ(b.find(c), StringPiece::npos); + ASSERT_EQ(b.find(c, StringPiece::npos), StringPiece::npos); + ASSERT_EQ(a.find(d), 0U); + ASSERT_EQ(a.find(e), 0U); + ASSERT_EQ(a.find(d, 12), 12U); + ASSERT_EQ(a.find(e, 17), 17U); + StringPiece g("xx not found bb"); + ASSERT_EQ(a.find(g), StringPiece::npos); + // empty string nonsense + ASSERT_EQ(d.find(b), StringPiece::npos); + ASSERT_EQ(e.find(b), StringPiece::npos); + ASSERT_EQ(d.find(b, 4), StringPiece::npos); + ASSERT_EQ(e.find(b, 7), StringPiece::npos); + + size_t empty_search_pos = std::string().find(std::string()); + ASSERT_EQ(d.find(d), empty_search_pos); + ASSERT_EQ(d.find(e), empty_search_pos); + ASSERT_EQ(e.find(d), empty_search_pos); + ASSERT_EQ(e.find(e), empty_search_pos); + ASSERT_EQ(d.find(d, 4), std::string().find(std::string(), 4)); + ASSERT_EQ(d.find(e, 4), std::string().find(std::string(), 4)); + ASSERT_EQ(e.find(d, 4), std::string().find(std::string(), 4)); + ASSERT_EQ(e.find(e, 4), std::string().find(std::string(), 4)); + + ASSERT_EQ(a.find('a'), 0U); + ASSERT_EQ(a.find('c'), 2U); + ASSERT_EQ(a.find('z'), 25U); + ASSERT_EQ(a.find('$'), StringPiece::npos); + ASSERT_EQ(a.find('\0'), StringPiece::npos); + ASSERT_EQ(f.find('\0'), 3U); + ASSERT_EQ(f.find('3'), 2U); + ASSERT_EQ(f.find('5'), 5U); + ASSERT_EQ(g.find('o'), 4U); + ASSERT_EQ(g.find('o', 4), 4U); + ASSERT_EQ(g.find('o', 5), 8U); + ASSERT_EQ(a.find('b', 5), StringPiece::npos); + // empty string nonsense + ASSERT_EQ(d.find('\0'), StringPiece::npos); + ASSERT_EQ(e.find('\0'), StringPiece::npos); + ASSERT_EQ(d.find('\0', 4), StringPiece::npos); + ASSERT_EQ(e.find('\0', 7), StringPiece::npos); + ASSERT_EQ(d.find('x'), StringPiece::npos); + ASSERT_EQ(e.find('x'), StringPiece::npos); + ASSERT_EQ(d.find('x', 4), StringPiece::npos); + ASSERT_EQ(e.find('x', 7), StringPiece::npos); + + ASSERT_EQ(a.rfind(b), 0U); + ASSERT_EQ(a.rfind(b, 1), 0U); + ASSERT_EQ(a.rfind(c), 23U); + ASSERT_EQ(a.rfind(c, 22U), StringPiece::npos); + ASSERT_EQ(a.rfind(c, 1U), StringPiece::npos); + ASSERT_EQ(a.rfind(c, 0U), StringPiece::npos); + ASSERT_EQ(b.rfind(c), StringPiece::npos); + ASSERT_EQ(b.rfind(c, 0U), StringPiece::npos); + ASSERT_EQ(a.rfind(d), (size_t) a.as_string().rfind(std::string())); + ASSERT_EQ(a.rfind(e), a.as_string().rfind(std::string())); + ASSERT_EQ(a.rfind(d, 12), 12U); + ASSERT_EQ(a.rfind(e, 17), 17U); + ASSERT_EQ(a.rfind(g), StringPiece::npos); + ASSERT_EQ(d.rfind(b), StringPiece::npos); + ASSERT_EQ(e.rfind(b), StringPiece::npos); + ASSERT_EQ(d.rfind(b, 4), StringPiece::npos); + ASSERT_EQ(e.rfind(b, 7), StringPiece::npos); + // empty string nonsense + ASSERT_EQ(d.rfind(d, 4), std::string().rfind(std::string())); + ASSERT_EQ(e.rfind(d, 7), std::string().rfind(std::string())); + ASSERT_EQ(d.rfind(e, 4), std::string().rfind(std::string())); + ASSERT_EQ(e.rfind(e, 7), std::string().rfind(std::string())); + ASSERT_EQ(d.rfind(d), std::string().rfind(std::string())); + ASSERT_EQ(e.rfind(d), std::string().rfind(std::string())); + ASSERT_EQ(d.rfind(e), std::string().rfind(std::string())); + ASSERT_EQ(e.rfind(e), std::string().rfind(std::string())); + + ASSERT_EQ(g.rfind('o'), 8U); + ASSERT_EQ(g.rfind('q'), StringPiece::npos); + ASSERT_EQ(g.rfind('o', 8), 8U); + ASSERT_EQ(g.rfind('o', 7), 4U); + ASSERT_EQ(g.rfind('o', 3), StringPiece::npos); + ASSERT_EQ(f.rfind('\0'), 3U); + ASSERT_EQ(f.rfind('\0', 12), 3U); + ASSERT_EQ(f.rfind('3'), 2U); + ASSERT_EQ(f.rfind('5'), 5U); + // empty string nonsense + ASSERT_EQ(d.rfind('o'), StringPiece::npos); + ASSERT_EQ(e.rfind('o'), StringPiece::npos); + ASSERT_EQ(d.rfind('o', 4), StringPiece::npos); + ASSERT_EQ(e.rfind('o', 7), StringPiece::npos); + + ASSERT_EQ( + StringPiece("one,two:three;four").find_first_of(StringPiece(",:"), 1), + 3U); + ASSERT_EQ(a.find_first_of(b), 0U); + ASSERT_EQ(a.find_first_of(b, 0), 0U); + ASSERT_EQ(a.find_first_of(b, 1), 1U); + ASSERT_EQ(a.find_first_of(b, 2), 2U); + ASSERT_EQ(a.find_first_of(b, 3), StringPiece::npos); + ASSERT_EQ(a.find_first_of(c), 23U); + ASSERT_EQ(a.find_first_of(c, 23), 23U); + ASSERT_EQ(a.find_first_of(c, 24), 24U); + ASSERT_EQ(a.find_first_of(c, 25), 25U); + ASSERT_EQ(a.find_first_of(c, 26), StringPiece::npos); + ASSERT_EQ(g.find_first_of(b), 13U); + ASSERT_EQ(g.find_first_of(c), 0U); + ASSERT_EQ(a.find_first_of(f), StringPiece::npos); + ASSERT_EQ(f.find_first_of(a), StringPiece::npos); + // empty string nonsense + ASSERT_EQ(a.find_first_of(d), StringPiece::npos); + ASSERT_EQ(a.find_first_of(e), StringPiece::npos); + ASSERT_EQ(d.find_first_of(b), StringPiece::npos); + ASSERT_EQ(e.find_first_of(b), StringPiece::npos); + ASSERT_EQ(d.find_first_of(d), StringPiece::npos); + ASSERT_EQ(e.find_first_of(d), StringPiece::npos); + ASSERT_EQ(d.find_first_of(e), StringPiece::npos); + ASSERT_EQ(e.find_first_of(e), StringPiece::npos); + + ASSERT_EQ(a.find_first_not_of(b), 3U); + ASSERT_EQ(a.find_first_not_of(c), 0U); + ASSERT_EQ(b.find_first_not_of(a), StringPiece::npos); + ASSERT_EQ(c.find_first_not_of(a), StringPiece::npos); + ASSERT_EQ(f.find_first_not_of(a), 0U); + ASSERT_EQ(a.find_first_not_of(f), 0U); + ASSERT_EQ(a.find_first_not_of(d), 0U); + ASSERT_EQ(a.find_first_not_of(e), 0U); + // empty string nonsense + ASSERT_EQ(d.find_first_not_of(a), StringPiece::npos); + ASSERT_EQ(e.find_first_not_of(a), StringPiece::npos); + ASSERT_EQ(d.find_first_not_of(d), StringPiece::npos); + ASSERT_EQ(e.find_first_not_of(d), StringPiece::npos); + ASSERT_EQ(d.find_first_not_of(e), StringPiece::npos); + ASSERT_EQ(e.find_first_not_of(e), StringPiece::npos); + + StringPiece h("===="); + ASSERT_EQ(h.find_first_not_of('='), StringPiece::npos); + ASSERT_EQ(h.find_first_not_of('=', 3), StringPiece::npos); + ASSERT_EQ(h.find_first_not_of('\0'), 0U); + ASSERT_EQ(g.find_first_not_of('x'), 2U); + ASSERT_EQ(f.find_first_not_of('\0'), 0U); + ASSERT_EQ(f.find_first_not_of('\0', 3), 4U); + ASSERT_EQ(f.find_first_not_of('\0', 2), 2U); + // empty string nonsense + ASSERT_EQ(d.find_first_not_of('x'), StringPiece::npos); + ASSERT_EQ(e.find_first_not_of('x'), StringPiece::npos); + ASSERT_EQ(d.find_first_not_of('\0'), StringPiece::npos); + ASSERT_EQ(e.find_first_not_of('\0'), StringPiece::npos); + + // StringPiece g("xx not found bb"); + StringPiece i("56"); + ASSERT_EQ(h.find_last_of(a), StringPiece::npos); + ASSERT_EQ(g.find_last_of(a), g.size()-1); + ASSERT_EQ(a.find_last_of(b), 2U); + ASSERT_EQ(a.find_last_of(c), a.size()-1); + ASSERT_EQ(f.find_last_of(i), 6U); + ASSERT_EQ(a.find_last_of('a'), 0U); + ASSERT_EQ(a.find_last_of('b'), 1U); + ASSERT_EQ(a.find_last_of('z'), 25U); + ASSERT_EQ(a.find_last_of('a', 5), 0U); + ASSERT_EQ(a.find_last_of('b', 5), 1U); + ASSERT_EQ(a.find_last_of('b', 0), StringPiece::npos); + ASSERT_EQ(a.find_last_of('z', 25), 25U); + ASSERT_EQ(a.find_last_of('z', 24), StringPiece::npos); + ASSERT_EQ(f.find_last_of(i, 5), 5U); + ASSERT_EQ(f.find_last_of(i, 6), 6U); + ASSERT_EQ(f.find_last_of(a, 4), StringPiece::npos); + // empty string nonsense + ASSERT_EQ(f.find_last_of(d), StringPiece::npos); + ASSERT_EQ(f.find_last_of(e), StringPiece::npos); + ASSERT_EQ(f.find_last_of(d, 4), StringPiece::npos); + ASSERT_EQ(f.find_last_of(e, 4), StringPiece::npos); + ASSERT_EQ(d.find_last_of(d), StringPiece::npos); + ASSERT_EQ(d.find_last_of(e), StringPiece::npos); + ASSERT_EQ(e.find_last_of(d), StringPiece::npos); + ASSERT_EQ(e.find_last_of(e), StringPiece::npos); + ASSERT_EQ(d.find_last_of(f), StringPiece::npos); + ASSERT_EQ(e.find_last_of(f), StringPiece::npos); + ASSERT_EQ(d.find_last_of(d, 4), StringPiece::npos); + ASSERT_EQ(d.find_last_of(e, 4), StringPiece::npos); + ASSERT_EQ(e.find_last_of(d, 4), StringPiece::npos); + ASSERT_EQ(e.find_last_of(e, 4), StringPiece::npos); + ASSERT_EQ(d.find_last_of(f, 4), StringPiece::npos); + ASSERT_EQ(e.find_last_of(f, 4), StringPiece::npos); + + ASSERT_EQ(a.find_last_not_of(b), a.size()-1); + ASSERT_EQ(a.find_last_not_of(c), 22U); + ASSERT_EQ(b.find_last_not_of(a), StringPiece::npos); + ASSERT_EQ(b.find_last_not_of(b), StringPiece::npos); + ASSERT_EQ(f.find_last_not_of(i), 4U); + ASSERT_EQ(a.find_last_not_of(c, 24), 22U); + ASSERT_EQ(a.find_last_not_of(b, 3), 3U); + ASSERT_EQ(a.find_last_not_of(b, 2), StringPiece::npos); + // empty string nonsense + ASSERT_EQ(f.find_last_not_of(d), f.size()-1); + ASSERT_EQ(f.find_last_not_of(e), f.size()-1); + ASSERT_EQ(f.find_last_not_of(d, 4), 4U); + ASSERT_EQ(f.find_last_not_of(e, 4), 4U); + ASSERT_EQ(d.find_last_not_of(d), StringPiece::npos); + ASSERT_EQ(d.find_last_not_of(e), StringPiece::npos); + ASSERT_EQ(e.find_last_not_of(d), StringPiece::npos); + ASSERT_EQ(e.find_last_not_of(e), StringPiece::npos); + ASSERT_EQ(d.find_last_not_of(f), StringPiece::npos); + ASSERT_EQ(e.find_last_not_of(f), StringPiece::npos); + ASSERT_EQ(d.find_last_not_of(d, 4), StringPiece::npos); + ASSERT_EQ(d.find_last_not_of(e, 4), StringPiece::npos); + ASSERT_EQ(e.find_last_not_of(d, 4), StringPiece::npos); + ASSERT_EQ(e.find_last_not_of(e, 4), StringPiece::npos); + ASSERT_EQ(d.find_last_not_of(f, 4), StringPiece::npos); + ASSERT_EQ(e.find_last_not_of(f, 4), StringPiece::npos); + + ASSERT_EQ(h.find_last_not_of('x'), h.size() - 1); + ASSERT_EQ(h.find_last_not_of('='), StringPiece::npos); + ASSERT_EQ(b.find_last_not_of('c'), 1U); + ASSERT_EQ(h.find_last_not_of('x', 2), 2U); + ASSERT_EQ(h.find_last_not_of('=', 2), StringPiece::npos); + ASSERT_EQ(b.find_last_not_of('b', 1), 0U); + // empty string nonsense + ASSERT_EQ(d.find_last_not_of('x'), StringPiece::npos); + ASSERT_EQ(e.find_last_not_of('x'), StringPiece::npos); + ASSERT_EQ(d.find_last_not_of('\0'), StringPiece::npos); + ASSERT_EQ(e.find_last_not_of('\0'), StringPiece::npos); + + ASSERT_EQ(a.substr(0, 3), b); + ASSERT_EQ(a.substr(23), c); + ASSERT_EQ(a.substr(23, 3), c); + ASSERT_EQ(a.substr(23, 99), c); + ASSERT_EQ(a.substr(0), a); + ASSERT_EQ(a.substr(3, 2), "de"); + // empty string nonsense + ASSERT_EQ(a.substr(99, 2), e); + ASSERT_EQ(d.substr(99), e); + ASSERT_EQ(d.substr(0, 99), e); + ASSERT_EQ(d.substr(99, 99), e); +} + +TYPED_TEST(CommonStringPieceTest, CheckCustom) { + TypeParam foobar(TestFixture::as_string("foobar")); + BasicStringPiece a(foobar); + TypeParam s1(TestFixture::as_string("123")); + s1 += static_cast('\0'); + s1 += TestFixture::as_string("456"); + BasicStringPiece b(s1); + BasicStringPiece e; + TypeParam s2; + + // remove_prefix + BasicStringPiece c(a); + c.remove_prefix(3); + ASSERT_EQ(c, TestFixture::as_string("bar")); + c = a; + c.remove_prefix(0); + ASSERT_EQ(c, a); + c.remove_prefix(c.size()); + ASSERT_EQ(c, e); + + // remove_suffix + c = a; + c.remove_suffix(3); + ASSERT_EQ(c, TestFixture::as_string("foo")); + c = a; + c.remove_suffix(0); + ASSERT_EQ(c, a); + c.remove_suffix(c.size()); + ASSERT_EQ(c, e); + + // set + c.set(foobar.c_str()); + ASSERT_EQ(c, a); + c.set(foobar.c_str(), 6); + ASSERT_EQ(c, a); + c.set(foobar.c_str(), 0); + ASSERT_EQ(c, e); + c.set(foobar.c_str(), 7); // Note, has an embedded NULL + ASSERT_NE(c, a); + + // as_string + TypeParam s3(a.as_string().c_str(), 7); // Note, has an embedded NULL + ASSERT_TRUE(c == s3); + TypeParam s4(e.as_string()); + ASSERT_TRUE(s4.empty()); +} + +TEST(StringPieceTest, CheckCustom) { + StringPiece a("foobar"); + std::string s1("123"); + s1 += '\0'; + s1 += "456"; + StringPiece b(s1); + StringPiece e; + std::string s2; + + // CopyToString + a.CopyToString(&s2); + ASSERT_EQ(s2.size(), 6U); + ASSERT_EQ(s2, "foobar"); + b.CopyToString(&s2); + ASSERT_EQ(s2.size(), 7U); + ASSERT_EQ(s1, s2); + e.CopyToString(&s2); + ASSERT_TRUE(s2.empty()); + + // AppendToString + s2.erase(); + a.AppendToString(&s2); + ASSERT_EQ(s2.size(), 6U); + ASSERT_EQ(s2, "foobar"); + a.AppendToString(&s2); + ASSERT_EQ(s2.size(), 12U); + ASSERT_EQ(s2, "foobarfoobar"); + + // starts_with + ASSERT_TRUE(a.starts_with(a)); + ASSERT_TRUE(a.starts_with("foo")); + ASSERT_TRUE(a.starts_with(e)); + ASSERT_TRUE(b.starts_with(s1)); + ASSERT_TRUE(b.starts_with(b)); + ASSERT_TRUE(b.starts_with(e)); + ASSERT_TRUE(e.starts_with("")); + ASSERT_TRUE(!a.starts_with(b)); + ASSERT_TRUE(!b.starts_with(a)); + ASSERT_TRUE(!e.starts_with(a)); + + // ends with + ASSERT_TRUE(a.ends_with(a)); + ASSERT_TRUE(a.ends_with("bar")); + ASSERT_TRUE(a.ends_with(e)); + ASSERT_TRUE(b.ends_with(s1)); + ASSERT_TRUE(b.ends_with(b)); + ASSERT_TRUE(b.ends_with(e)); + ASSERT_TRUE(e.ends_with("")); + ASSERT_TRUE(!a.ends_with(b)); + ASSERT_TRUE(!b.ends_with(a)); + ASSERT_TRUE(!e.ends_with(a)); + + StringPiece c; + c.set(static_cast("foobar"), 6); + ASSERT_EQ(c, a); + c.set(static_cast("foobar"), 0); + ASSERT_EQ(c, e); + c.set(static_cast("foobar"), 7); + ASSERT_NE(c, a); +} + +TYPED_TEST(CommonStringPieceTest, CheckNULL) { + // we used to crash here, but now we don't. + BasicStringPiece s(NULL); + ASSERT_EQ(s.data(), (const typename TypeParam::value_type*)NULL); + ASSERT_EQ(s.size(), 0U); + + s.set(NULL); + ASSERT_EQ(s.data(), (const typename TypeParam::value_type*)NULL); + ASSERT_EQ(s.size(), 0U); + + TypeParam str = s.as_string(); + ASSERT_EQ(str.length(), 0U); + ASSERT_EQ(str, TypeParam()); +} + +TYPED_TEST(CommonStringPieceTest, CheckComparisons2) { + TypeParam alphabet(TestFixture::as_string("abcdefghijklmnopqrstuvwxyz")); + TypeParam alphabet_z(TestFixture::as_string("abcdefghijklmnopqrstuvwxyzz")); + TypeParam alphabet_y(TestFixture::as_string("abcdefghijklmnopqrstuvwxyy")); + BasicStringPiece abc(alphabet); + + // check comparison operations on strings longer than 4 bytes. + ASSERT_TRUE(abc == BasicStringPiece(alphabet)); + ASSERT_TRUE(abc.compare(BasicStringPiece(alphabet)) == 0); + + ASSERT_TRUE(abc < BasicStringPiece(alphabet_z)); + ASSERT_TRUE(abc.compare(BasicStringPiece(alphabet_z)) < 0); + + ASSERT_TRUE(abc > BasicStringPiece(alphabet_y)); + ASSERT_TRUE(abc.compare(BasicStringPiece(alphabet_y)) > 0); +} + +// Test operations only supported by std::string version. +TEST(StringPieceTest, CheckComparisons2) { + StringPiece abc("abcdefghijklmnopqrstuvwxyz"); + + // starts_with + ASSERT_TRUE(abc.starts_with(abc)); + ASSERT_TRUE(abc.starts_with("abcdefghijklm")); + ASSERT_TRUE(!abc.starts_with("abcdefguvwxyz")); + + // ends_with + ASSERT_TRUE(abc.ends_with(abc)); + ASSERT_TRUE(!abc.ends_with("abcdefguvwxyz")); + ASSERT_TRUE(abc.ends_with("nopqrstuvwxyz")); +} + +TYPED_TEST(CommonStringPieceTest, StringCompareNotAmbiguous) { + ASSERT_TRUE(TestFixture::as_string("hello").c_str() == + TestFixture::as_string("hello")); + ASSERT_TRUE(TestFixture::as_string("hello").c_str() < + TestFixture::as_string("world")); +} + +TYPED_TEST(CommonStringPieceTest, HeterogenousStringPieceEquals) { + TypeParam hello(TestFixture::as_string("hello")); + + ASSERT_TRUE(BasicStringPiece(hello) == hello); + ASSERT_TRUE(hello.c_str() == BasicStringPiece(hello)); +} + +// string16-specific stuff +TEST(StringPiece16Test, CheckSTL) { + // Check some non-ascii characters. + string16 fifth(ASCIIToUTF16("123")); + fifth.push_back(0x0000); + fifth.push_back(0xd8c5); + fifth.push_back(0xdffe); + StringPiece16 f(fifth); + + ASSERT_EQ(f[3], '\0'); + ASSERT_EQ(f[5], static_cast(0xdffe)); + + ASSERT_EQ(f.size(), 6U); +} + + + +TEST(StringPiece16Test, CheckConversion) { + // Make sure that we can convert from UTF8 to UTF16 and back. We use a two + // byte character (G clef) to test this. + ASSERT_EQ( + UTF16ToUTF8( + StringPiece16(UTF8ToUTF16("\xf0\x9d\x84\x9e")).as_string()), + "\xf0\x9d\x84\x9e"); +} + +TYPED_TEST(CommonStringPieceTest, CheckConstructors) { + TypeParam str(TestFixture::as_string("hello world")); + TypeParam empty; + + ASSERT_TRUE(str == BasicStringPiece(str)); + ASSERT_TRUE(str == BasicStringPiece(str.c_str())); + ASSERT_TRUE(TestFixture::as_string("hello") == + BasicStringPiece(str.c_str(), 5)); + ASSERT_TRUE(empty == BasicStringPiece(str.c_str(), 0U)); + ASSERT_TRUE(empty == BasicStringPiece(NULL)); + ASSERT_TRUE(empty == BasicStringPiece(NULL, 0U)); + ASSERT_TRUE(empty == BasicStringPiece()); + ASSERT_TRUE(str == BasicStringPiece(str.begin(), str.end())); + ASSERT_TRUE(empty == BasicStringPiece(str.begin(), str.begin())); + ASSERT_TRUE(empty == BasicStringPiece(empty)); + ASSERT_TRUE(empty == BasicStringPiece(empty.begin(), empty.end())); +} + +} // namespace base diff --git a/base/strings/string_split.cc b/base/strings/string_split.cc new file mode 100644 index 0000000000..210789cd22 --- /dev/null +++ b/base/strings/string_split.cc @@ -0,0 +1,219 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/strings/string_split.h" + +#include "base/logging.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/third_party/icu/icu_utf.h" + +namespace base { + +template +static void SplitStringT(const STR& str, + const typename STR::value_type s, + bool trim_whitespace, + std::vector* r) { + r->clear(); + size_t last = 0; + size_t c = str.size(); + for (size_t i = 0; i <= c; ++i) { + if (i == c || str[i] == s) { + STR tmp(str, last, i - last); + if (trim_whitespace) + TrimWhitespace(tmp, TRIM_ALL, &tmp); + // Avoid converting an empty or all-whitespace source string into a vector + // of one empty string. + if (i != c || !r->empty() || !tmp.empty()) + r->push_back(tmp); + last = i + 1; + } + } +} + +void SplitString(const string16& str, + char16 c, + std::vector* r) { + DCHECK(CBU16_IS_SINGLE(c)); + SplitStringT(str, c, true, r); +} + +void SplitString(const std::string& str, + char c, + std::vector* r) { +#if CHAR_MIN < 0 + DCHECK(c >= 0); +#endif + DCHECK(c < 0x7F); + SplitStringT(str, c, true, r); +} + +bool SplitStringIntoKeyValues( + const std::string& line, + char key_value_delimiter, + std::string* key, std::vector* values) { + key->clear(); + values->clear(); + + // Find the key string. + size_t end_key_pos = line.find_first_of(key_value_delimiter); + if (end_key_pos == std::string::npos) { + DVLOG(1) << "cannot parse key from line: " << line; + return false; // no key + } + key->assign(line, 0, end_key_pos); + + // Find the values string. + std::string remains(line, end_key_pos, line.size() - end_key_pos); + size_t begin_values_pos = remains.find_first_not_of(key_value_delimiter); + if (begin_values_pos == std::string::npos) { + DVLOG(1) << "cannot parse value from line: " << line; + return false; // no value + } + std::string values_string(remains, begin_values_pos, + remains.size() - begin_values_pos); + + // Construct the values vector. + values->push_back(values_string); + return true; +} + +bool SplitStringIntoKeyValuePairs(const std::string& line, + char key_value_delimiter, + char key_value_pair_delimiter, + StringPairs* key_value_pairs) { + key_value_pairs->clear(); + + std::vector pairs; + SplitString(line, key_value_pair_delimiter, &pairs); + + bool success = true; + for (size_t i = 0; i < pairs.size(); ++i) { + // Empty pair. SplitStringIntoKeyValues is more strict about an empty pair + // line, so continue with the next pair. + if (pairs[i].empty()) + continue; + + std::string key; + std::vector value; + if (!SplitStringIntoKeyValues(pairs[i], + key_value_delimiter, + &key, &value)) { + // Don't return here, to allow for keys without associated + // values; just record that our split failed. + success = false; + } + DCHECK_LE(value.size(), 1U); + key_value_pairs->push_back( + make_pair(key, value.empty() ? std::string() : value[0])); + } + return success; +} + +template +static void SplitStringUsingSubstrT(const STR& str, + const STR& s, + std::vector* r) { + r->clear(); + typename STR::size_type begin_index = 0; + while (true) { + const typename STR::size_type end_index = str.find(s, begin_index); + if (end_index == STR::npos) { + const STR term = str.substr(begin_index); + STR tmp; + TrimWhitespace(term, TRIM_ALL, &tmp); + r->push_back(tmp); + return; + } + const STR term = str.substr(begin_index, end_index - begin_index); + STR tmp; + TrimWhitespace(term, TRIM_ALL, &tmp); + r->push_back(tmp); + begin_index = end_index + s.size(); + } +} + +void SplitStringUsingSubstr(const string16& str, + const string16& s, + std::vector* r) { + SplitStringUsingSubstrT(str, s, r); +} + +void SplitStringUsingSubstr(const std::string& str, + const std::string& s, + std::vector* r) { + SplitStringUsingSubstrT(str, s, r); +} + +void SplitStringDontTrim(const string16& str, + char16 c, + std::vector* r) { + DCHECK(CBU16_IS_SINGLE(c)); + SplitStringT(str, c, false, r); +} + +void SplitStringDontTrim(const std::string& str, + char c, + std::vector* r) { + DCHECK(IsStringUTF8(str)); +#if CHAR_MIN < 0 + DCHECK(c >= 0); +#endif + DCHECK(c < 0x7F); + SplitStringT(str, c, false, r); +} + +template +void SplitStringAlongWhitespaceT(const STR& str, std::vector* result) { + result->clear(); + const size_t length = str.length(); + if (!length) + return; + + bool last_was_ws = false; + size_t last_non_ws_start = 0; + for (size_t i = 0; i < length; ++i) { + switch (str[i]) { + // HTML 5 defines whitespace as: space, tab, LF, line tab, FF, or CR. + case L' ': + case L'\t': + case L'\xA': + case L'\xB': + case L'\xC': + case L'\xD': + if (!last_was_ws) { + if (i > 0) { + result->push_back( + str.substr(last_non_ws_start, i - last_non_ws_start)); + } + last_was_ws = true; + } + break; + + default: // Not a space character. + if (last_was_ws) { + last_was_ws = false; + last_non_ws_start = i; + } + break; + } + } + if (!last_was_ws) { + result->push_back( + str.substr(last_non_ws_start, length - last_non_ws_start)); + } +} + +void SplitStringAlongWhitespace(const string16& str, + std::vector* result) { + SplitStringAlongWhitespaceT(str, result); +} + +void SplitStringAlongWhitespace(const std::string& str, + std::vector* result) { + SplitStringAlongWhitespaceT(str, result); +} + +} // namespace base diff --git a/base/strings/string_split.h b/base/strings/string_split.h new file mode 100644 index 0000000000..faf08d6985 --- /dev/null +++ b/base/strings/string_split.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_STRINGS_STRING_SPLIT_H_ +#define BASE_STRINGS_STRING_SPLIT_H_ + +#include +#include +#include + +#include "base/base_export.h" +#include "base/strings/string16.h" + +namespace base { + +// Splits |str| into a vector of strings delimited by |s|, placing the results +// in |r|. If several instances of |s| are contiguous, or if |str| begins with +// or ends with |s|, then an empty string is inserted. +// +// Every substring is trimmed of any leading or trailing white space. +// NOTE: |c| must be in BMP (Basic Multilingual Plane) +BASE_EXPORT void SplitString(const string16& str, + char16 c, + std::vector* r); +// |str| should not be in a multi-byte encoding like Shift-JIS or GBK in which +// the trailing byte of a multi-byte character can be in the ASCII range. +// UTF-8, and other single/multi-byte ASCII-compatible encodings are OK. +// Note: |c| must be in the ASCII range. +BASE_EXPORT void SplitString(const std::string& str, + char c, + std::vector* r); + +BASE_EXPORT bool SplitStringIntoKeyValues(const std::string& line, + char key_value_delimiter, + std::string* key, + std::vector* values); + +typedef std::vector > StringPairs;; + +BASE_EXPORT bool SplitStringIntoKeyValuePairs( + const std::string& line, + char key_value_delimiter, + char key_value_pair_delimiter, + StringPairs* key_value_pairs); + +// The same as SplitString, but use a substring delimiter instead of a char. +BASE_EXPORT void SplitStringUsingSubstr(const string16& str, + const string16& s, + std::vector* r); +BASE_EXPORT void SplitStringUsingSubstr(const std::string& str, + const std::string& s, + std::vector* r); + +// The same as SplitString, but don't trim white space. +// NOTE: |c| must be in BMP (Basic Multilingual Plane) +BASE_EXPORT void SplitStringDontTrim(const string16& str, + char16 c, + std::vector* r); +// |str| should not be in a multi-byte encoding like Shift-JIS or GBK in which +// the trailing byte of a multi-byte character can be in the ASCII range. +// UTF-8, and other single/multi-byte ASCII-compatible encodings are OK. +// Note: |c| must be in the ASCII range. +BASE_EXPORT void SplitStringDontTrim(const std::string& str, + char c, + std::vector* r); + +// WARNING: this uses whitespace as defined by the HTML5 spec. If you need +// a function similar to this but want to trim all types of whitespace, then +// factor this out into a function that takes a string containing the characters +// that are treated as whitespace. +// +// Splits the string along whitespace (where whitespace is the five space +// characters defined by HTML 5). Each contiguous block of non-whitespace +// characters is added to result. +BASE_EXPORT void SplitStringAlongWhitespace(const string16& str, + std::vector* result); +BASE_EXPORT void SplitStringAlongWhitespace(const std::string& str, + std::vector* result); + +} // namespace base + +#endif // BASE_STRINGS_STRING_SPLIT_H_ diff --git a/base/strings/string_split_unittest.cc b/base/strings/string_split_unittest.cc new file mode 100644 index 0000000000..eb69b68fcb --- /dev/null +++ b/base/strings/string_split_unittest.cc @@ -0,0 +1,318 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/strings/string_split.h" + +#include "base/strings/utf_string_conversions.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::ElementsAre; + +namespace base { + +namespace { + +#if !defined(WCHAR_T_IS_UTF16) +// Overload SplitString with a wide-char version to make it easier to +// test the string16 version with wide character literals. +void SplitString(const std::wstring& str, + wchar_t c, + std::vector* result) { + std::vector result16; + SplitString(WideToUTF16(str), c, &result16); + for (size_t i = 0; i < result16.size(); ++i) + result->push_back(UTF16ToWide(result16[i])); +} +#endif + +} // anonymous namespace + +class SplitStringIntoKeyValuesTest : public testing::Test { + protected: + std::string key; + std::vector values; +}; + +TEST_F(SplitStringIntoKeyValuesTest, EmptyInputMultipleValues) { + EXPECT_FALSE(SplitStringIntoKeyValues(std::string(), // Empty input + '\t', // Key separators + &key, + &values)); + EXPECT_TRUE(key.empty()); + EXPECT_TRUE(values.empty()); +} + +TEST_F(SplitStringIntoKeyValuesTest, EmptyValueInputMultipleValues) { + EXPECT_FALSE(SplitStringIntoKeyValues("key_with_no_value\t", + '\t', // Key separators + &key, &values)); + EXPECT_EQ("key_with_no_value", key); + EXPECT_TRUE(values.empty()); +} + +TEST_F(SplitStringIntoKeyValuesTest, EmptyKeyInputMultipleValues) { + EXPECT_TRUE(SplitStringIntoKeyValues("\tvalue for empty key", + '\t', // Key separators + &key, &values)); + EXPECT_TRUE(key.empty()); + ASSERT_EQ(1U, values.size()); +} + +TEST_F(SplitStringIntoKeyValuesTest, KeyWithMultipleValues) { + EXPECT_TRUE(SplitStringIntoKeyValues("key1\tvalue1, value2 value3", + '\t', // Key separators + &key, &values)); + EXPECT_EQ("key1", key); + ASSERT_EQ(1U, values.size()); + EXPECT_EQ("value1, value2 value3", values[0]); +} + +TEST_F(SplitStringIntoKeyValuesTest, EmptyInputSingleValue) { + EXPECT_FALSE(SplitStringIntoKeyValues(std::string(), // Empty input + '\t', // Key separators + &key, + &values)); + EXPECT_TRUE(key.empty()); + EXPECT_TRUE(values.empty()); +} + +TEST_F(SplitStringIntoKeyValuesTest, EmptyValueInputSingleValue) { + EXPECT_FALSE(SplitStringIntoKeyValues("key_with_no_value\t", + '\t', // Key separators + &key, &values)); + EXPECT_EQ("key_with_no_value", key); + EXPECT_TRUE(values.empty()); +} + +TEST_F(SplitStringIntoKeyValuesTest, EmptyKeyInputSingleValue) { + EXPECT_TRUE(SplitStringIntoKeyValues("\tvalue for empty key", + '\t', // Key separators + &key, &values)); + EXPECT_TRUE(key.empty()); + ASSERT_EQ(1U, values.size()); + EXPECT_EQ("value for empty key", values[0]); +} + +TEST_F(SplitStringIntoKeyValuesTest, KeyWithSingleValue) { + EXPECT_TRUE(SplitStringIntoKeyValues("key1\tvalue1, value2 value3", + '\t', // Key separators + &key, &values)); + EXPECT_EQ("key1", key); + ASSERT_EQ(1U, values.size()); + EXPECT_EQ("value1, value2 value3", values[0]); +} + +class SplitStringIntoKeyValuePairsTest : public testing::Test { + protected: + std::vector > kv_pairs; +}; + +TEST_F(SplitStringIntoKeyValuePairsTest, EmptyString) { + EXPECT_TRUE(SplitStringIntoKeyValuePairs(std::string(), + ':', // Key-value delimiters + ',', // Key-value pair delims + &kv_pairs)); + EXPECT_TRUE(kv_pairs.empty()); +} + +TEST_F(SplitStringIntoKeyValuePairsTest, EmptySecondPair) { + EXPECT_TRUE(SplitStringIntoKeyValuePairs("key1:value1,,key3:value3", + ':', // Key-value delimiters + ',', // Key-value pair delims + &kv_pairs)); + ASSERT_EQ(2U, kv_pairs.size()); + EXPECT_EQ("key1", kv_pairs[0].first); + EXPECT_EQ("value1", kv_pairs[0].second); + EXPECT_EQ("key3", kv_pairs[1].first); + EXPECT_EQ("value3", kv_pairs[1].second); +} + +TEST_F(SplitStringIntoKeyValuePairsTest, EmptySecondValue) { + EXPECT_FALSE(SplitStringIntoKeyValuePairs("key1:value1 , key2:", + ':', // Key-value delimiters + ',', // Key-value pair delims + &kv_pairs)); + ASSERT_EQ(2U, kv_pairs.size()); + EXPECT_EQ("key1", kv_pairs[0].first); + EXPECT_EQ("value1", kv_pairs[0].second); + EXPECT_EQ("key2", kv_pairs[1].first); + EXPECT_EQ("", kv_pairs[1].second); +} + +TEST_F(SplitStringIntoKeyValuePairsTest, DelimiterInValue) { + EXPECT_TRUE(SplitStringIntoKeyValuePairs("key1:va:ue1 , key2:value2", + ':', // Key-value delimiters + ',', // Key-value pair delims + &kv_pairs)); + ASSERT_EQ(2U, kv_pairs.size()); + EXPECT_EQ("key1", kv_pairs[0].first); + EXPECT_EQ("va:ue1", kv_pairs[0].second); + EXPECT_EQ("key2", kv_pairs[1].first); + EXPECT_EQ("value2", kv_pairs[1].second); +} + +TEST(SplitStringUsingSubstrTest, EmptyString) { + std::vector results; + SplitStringUsingSubstr(std::string(), "DELIMITER", &results); + ASSERT_EQ(1u, results.size()); + EXPECT_THAT(results, ElementsAre("")); +} + +// Test for SplitString +TEST(StringUtilTest, SplitString) { + std::vector r; + + SplitString(std::wstring(), L',', &r); + EXPECT_EQ(0U, r.size()); + r.clear(); + + SplitString(L"a,b,c", L',', &r); + ASSERT_EQ(3U, r.size()); + EXPECT_EQ(r[0], L"a"); + EXPECT_EQ(r[1], L"b"); + EXPECT_EQ(r[2], L"c"); + r.clear(); + + SplitString(L"a, b, c", L',', &r); + ASSERT_EQ(3U, r.size()); + EXPECT_EQ(r[0], L"a"); + EXPECT_EQ(r[1], L"b"); + EXPECT_EQ(r[2], L"c"); + r.clear(); + + SplitString(L"a,,c", L',', &r); + ASSERT_EQ(3U, r.size()); + EXPECT_EQ(r[0], L"a"); + EXPECT_EQ(r[1], L""); + EXPECT_EQ(r[2], L"c"); + r.clear(); + + SplitString(L" ", L'*', &r); + EXPECT_EQ(0U, r.size()); + r.clear(); + + SplitString(L"foo", L'*', &r); + ASSERT_EQ(1U, r.size()); + EXPECT_EQ(r[0], L"foo"); + r.clear(); + + SplitString(L"foo ,", L',', &r); + ASSERT_EQ(2U, r.size()); + EXPECT_EQ(r[0], L"foo"); + EXPECT_EQ(r[1], L""); + r.clear(); + + SplitString(L",", L',', &r); + ASSERT_EQ(2U, r.size()); + EXPECT_EQ(r[0], L""); + EXPECT_EQ(r[1], L""); + r.clear(); + + SplitString(L"\t\ta\t", L'\t', &r); + ASSERT_EQ(4U, r.size()); + EXPECT_EQ(r[0], L""); + EXPECT_EQ(r[1], L""); + EXPECT_EQ(r[2], L"a"); + EXPECT_EQ(r[3], L""); + r.clear(); + + SplitString(L"\ta\t\nb\tcc", L'\n', &r); + ASSERT_EQ(2U, r.size()); + EXPECT_EQ(r[0], L"a"); + EXPECT_EQ(r[1], L"b\tcc"); + r.clear(); +} + +TEST(SplitStringUsingSubstrTest, StringWithNoDelimiter) { + std::vector results; + SplitStringUsingSubstr("alongwordwithnodelimiter", "DELIMITER", &results); + ASSERT_EQ(1u, results.size()); + EXPECT_THAT(results, ElementsAre("alongwordwithnodelimiter")); +} + +TEST(SplitStringUsingSubstrTest, LeadingDelimitersSkipped) { + std::vector results; + SplitStringUsingSubstr( + "DELIMITERDELIMITERDELIMITERoneDELIMITERtwoDELIMITERthree", + "DELIMITER", + &results); + ASSERT_EQ(6u, results.size()); + EXPECT_THAT(results, ElementsAre("", "", "", "one", "two", "three")); +} + +TEST(SplitStringUsingSubstrTest, ConsecutiveDelimitersSkipped) { + std::vector results; + SplitStringUsingSubstr( + "unoDELIMITERDELIMITERDELIMITERdosDELIMITERtresDELIMITERDELIMITERcuatro", + "DELIMITER", + &results); + ASSERT_EQ(7u, results.size()); + EXPECT_THAT(results, ElementsAre("uno", "", "", "dos", "tres", "", "cuatro")); +} + +TEST(SplitStringUsingSubstrTest, TrailingDelimitersSkipped) { + std::vector results; + SplitStringUsingSubstr( + "unDELIMITERdeuxDELIMITERtroisDELIMITERquatreDELIMITERDELIMITERDELIMITER", + "DELIMITER", + &results); + ASSERT_EQ(7u, results.size()); + EXPECT_THAT( + results, ElementsAre("un", "deux", "trois", "quatre", "", "", "")); +} + +TEST(StringSplitTest, StringSplitDontTrim) { + std::vector r; + + SplitStringDontTrim(" ", '*', &r); + ASSERT_EQ(1U, r.size()); + EXPECT_EQ(r[0], " "); + + SplitStringDontTrim("\t \ta\t ", '\t', &r); + ASSERT_EQ(4U, r.size()); + EXPECT_EQ(r[0], ""); + EXPECT_EQ(r[1], " "); + EXPECT_EQ(r[2], "a"); + EXPECT_EQ(r[3], " "); + + SplitStringDontTrim("\ta\t\nb\tcc", '\n', &r); + ASSERT_EQ(2U, r.size()); + EXPECT_EQ(r[0], "\ta\t"); + EXPECT_EQ(r[1], "b\tcc"); +} + +TEST(StringSplitTest, SplitStringAlongWhitespace) { + struct TestData { + const char* input; + const size_t expected_result_count; + const char* output1; + const char* output2; + } data[] = { + { "a", 1, "a", "" }, + { " ", 0, "", "" }, + { " a", 1, "a", "" }, + { " ab ", 1, "ab", "" }, + { " ab c", 2, "ab", "c" }, + { " ab c ", 2, "ab", "c" }, + { " ab cd", 2, "ab", "cd" }, + { " ab cd ", 2, "ab", "cd" }, + { " \ta\t", 1, "a", "" }, + { " b\ta\t", 2, "b", "a" }, + { " b\tat", 2, "b", "at" }, + { "b\tat", 2, "b", "at" }, + { "b\t at", 2, "b", "at" }, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(data); ++i) { + std::vector results; + SplitStringAlongWhitespace(data[i].input, &results); + ASSERT_EQ(data[i].expected_result_count, results.size()); + if (data[i].expected_result_count > 0) + ASSERT_EQ(data[i].output1, results[0]); + if (data[i].expected_result_count > 1) + ASSERT_EQ(data[i].output2, results[1]); + } +} + +} // namespace base diff --git a/base/strings/string_tokenizer.h b/base/strings/string_tokenizer.h new file mode 100644 index 0000000000..8defbac3b8 --- /dev/null +++ b/base/strings/string_tokenizer.h @@ -0,0 +1,260 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_STRINGS_STRING_TOKENIZER_H_ +#define BASE_STRINGS_STRING_TOKENIZER_H_ + +#include +#include + +#include "base/strings/string_piece.h" + +namespace base { + +// StringTokenizerT is a simple string tokenizer class. It works like an +// iterator that with each step (see the Advance method) updates members that +// refer to the next token in the input string. The user may optionally +// configure the tokenizer to return delimiters. +// +// Warning: be careful not to pass a C string into the 2-arg constructor: +// StringTokenizer t("this is a test", " "); // WRONG +// This will create a temporary std::string, save the begin() and end() +// iterators, and then the string will be freed before we actually start +// tokenizing it. +// Instead, use a std::string or use the 3 arg constructor of CStringTokenizer. +// +// +// EXAMPLE 1: +// +// char input[] = "this is a test"; +// CStringTokenizer t(input, input + strlen(input), " "); +// while (t.GetNext()) { +// printf("%s\n", t.token().c_str()); +// } +// +// Output: +// +// this +// is +// a +// test +// +// +// EXAMPLE 2: +// +// std::string input = "no-cache=\"foo, bar\", private"; +// StringTokenizer t(input, ", "); +// t.set_quote_chars("\""); +// while (t.GetNext()) { +// printf("%s\n", t.token().c_str()); +// } +// +// Output: +// +// no-cache="foo, bar" +// private +// +// +// EXAMPLE 3: +// +// bool next_is_option = false, next_is_value = false; +// std::string input = "text/html; charset=UTF-8; foo=bar"; +// StringTokenizer t(input, "; ="); +// t.set_options(StringTokenizer::RETURN_DELIMS); +// while (t.GetNext()) { +// if (t.token_is_delim()) { +// switch (*t.token_begin()) { +// case ';': +// next_is_option = true; +// break; +// case '=': +// next_is_value = true; +// break; +// } +// } else { +// const char* label; +// if (next_is_option) { +// label = "option-name"; +// next_is_option = false; +// } else if (next_is_value) { +// label = "option-value"; +// next_is_value = false; +// } else { +// label = "mime-type"; +// } +// printf("%s: %s\n", label, t.token().c_str()); +// } +// } +// +// +template +class StringTokenizerT { + public: + typedef typename str::value_type char_type; + + // Options that may be pass to set_options() + enum { + // Specifies the delimiters should be returned as tokens + RETURN_DELIMS = 1 << 0, + }; + + // The string object must live longer than the tokenizer. (In particular this + // should not be constructed with a temporary.) + StringTokenizerT(const str& string, + const str& delims) { + Init(string.begin(), string.end(), delims); + } + + StringTokenizerT(const_iterator string_begin, + const_iterator string_end, + const str& delims) { + Init(string_begin, string_end, delims); + } + + // Set the options for this tokenizer. By default, this is 0. + void set_options(int options) { options_ = options; } + + // Set the characters to regard as quotes. By default, this is empty. When + // a quote char is encountered, the tokenizer will switch into a mode where + // it ignores delimiters that it finds. It switches out of this mode once it + // finds another instance of the quote char. If a backslash is encountered + // within a quoted string, then the next character is skipped. + void set_quote_chars(const str& quotes) { quotes_ = quotes; } + + // Call this method to advance the tokenizer to the next delimiter. This + // returns false if the tokenizer is complete. This method must be called + // before calling any of the token* methods. + bool GetNext() { + if (quotes_.empty() && options_ == 0) + return QuickGetNext(); + else + return FullGetNext(); + } + + // Start iterating through tokens from the beginning of the string. + void Reset() { + token_end_ = start_pos_; + } + + // Returns true if token is a delimiter. When the tokenizer is constructed + // with the RETURN_DELIMS option, this method can be used to check if the + // returned token is actually a delimiter. + bool token_is_delim() const { return token_is_delim_; } + + // If GetNext() returned true, then these methods may be used to read the + // value of the token. + const_iterator token_begin() const { return token_begin_; } + const_iterator token_end() const { return token_end_; } + str token() const { return str(token_begin_, token_end_); } + base::StringPiece token_piece() const { + return base::StringPiece(&*token_begin_, + std::distance(token_begin_, token_end_)); + } + + private: + void Init(const_iterator string_begin, + const_iterator string_end, + const str& delims) { + start_pos_ = string_begin; + token_begin_ = string_begin; + token_end_ = string_begin; + end_ = string_end; + delims_ = delims; + options_ = 0; + token_is_delim_ = false; + } + + // Implementation of GetNext() for when we have no quote characters. We have + // two separate implementations because AdvanceOne() is a hot spot in large + // text files with large tokens. + bool QuickGetNext() { + token_is_delim_ = false; + for (;;) { + token_begin_ = token_end_; + if (token_end_ == end_) + return false; + ++token_end_; + if (delims_.find(*token_begin_) == str::npos) + break; + // else skip over delimiter. + } + while (token_end_ != end_ && delims_.find(*token_end_) == str::npos) + ++token_end_; + return true; + } + + // Implementation of GetNext() for when we have to take quotes into account. + bool FullGetNext() { + AdvanceState state; + token_is_delim_ = false; + for (;;) { + token_begin_ = token_end_; + if (token_end_ == end_) + return false; + ++token_end_; + if (AdvanceOne(&state, *token_begin_)) + break; + if (options_ & RETURN_DELIMS) { + token_is_delim_ = true; + return true; + } + // else skip over delimiter. + } + while (token_end_ != end_ && AdvanceOne(&state, *token_end_)) + ++token_end_; + return true; + } + + bool IsDelim(char_type c) const { + return delims_.find(c) != str::npos; + } + + bool IsQuote(char_type c) const { + return quotes_.find(c) != str::npos; + } + + struct AdvanceState { + bool in_quote; + bool in_escape; + char_type quote_char; + AdvanceState() : in_quote(false), in_escape(false), quote_char('\0') {} + }; + + // Returns true if a delimiter was not hit. + bool AdvanceOne(AdvanceState* state, char_type c) { + if (state->in_quote) { + if (state->in_escape) { + state->in_escape = false; + } else if (c == '\\') { + state->in_escape = true; + } else if (c == state->quote_char) { + state->in_quote = false; + } + } else { + if (IsDelim(c)) + return false; + state->in_quote = IsQuote(state->quote_char = c); + } + return true; + } + + const_iterator start_pos_; + const_iterator token_begin_; + const_iterator token_end_; + const_iterator end_; + str delims_; + str quotes_; + int options_; + bool token_is_delim_; +}; + +typedef StringTokenizerT + StringTokenizer; +typedef StringTokenizerT + WStringTokenizer; +typedef StringTokenizerT CStringTokenizer; + +} // namespace base + +#endif // BASE_STRINGS_STRING_TOKENIZER_H_ diff --git a/base/strings/string_tokenizer_unittest.cc b/base/strings/string_tokenizer_unittest.cc new file mode 100644 index 0000000000..d391845328 --- /dev/null +++ b/base/strings/string_tokenizer_unittest.cc @@ -0,0 +1,234 @@ +// 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/strings/string_tokenizer.h" + +#include "testing/gtest/include/gtest/gtest.h" + +using std::string; + +namespace base { + +namespace { + +TEST(StringTokenizerTest, Simple) { + string input = "this is a test"; + StringTokenizer t(input, " "); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("this"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("is"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("a"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("test"), t.token()); + + EXPECT_FALSE(t.GetNext()); +} + +TEST(StringTokenizerTest, Reset) { + string input = "this is a test"; + StringTokenizer t(input, " "); + + for (int i = 0; i < 2; ++i) { + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("this"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("is"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("a"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("test"), t.token()); + + EXPECT_FALSE(t.GetNext()); + t.Reset(); + } +} + +TEST(StringTokenizerTest, RetDelims) { + string input = "this is a test"; + StringTokenizer t(input, " "); + t.set_options(StringTokenizer::RETURN_DELIMS); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("this"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string(" "), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("is"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string(" "), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("a"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string(" "), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("test"), t.token()); + + EXPECT_FALSE(t.GetNext()); +} + +TEST(StringTokenizerTest, ManyDelims) { + string input = "this: is, a-test"; + StringTokenizer t(input, ": ,-"); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("this"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("is"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("a"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("test"), t.token()); + + EXPECT_FALSE(t.GetNext()); +} + +TEST(StringTokenizerTest, ParseHeader) { + string input = "Content-Type: text/html ; charset=UTF-8"; + StringTokenizer t(input, ": ;="); + t.set_options(StringTokenizer::RETURN_DELIMS); + + EXPECT_TRUE(t.GetNext()); + EXPECT_FALSE(t.token_is_delim()); + EXPECT_EQ(string("Content-Type"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_TRUE(t.token_is_delim()); + EXPECT_EQ(string(":"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_TRUE(t.token_is_delim()); + EXPECT_EQ(string(" "), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_FALSE(t.token_is_delim()); + EXPECT_EQ(string("text/html"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_TRUE(t.token_is_delim()); + EXPECT_EQ(string(" "), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_TRUE(t.token_is_delim()); + EXPECT_EQ(string(";"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_TRUE(t.token_is_delim()); + EXPECT_EQ(string(" "), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_FALSE(t.token_is_delim()); + EXPECT_EQ(string("charset"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_TRUE(t.token_is_delim()); + EXPECT_EQ(string("="), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_FALSE(t.token_is_delim()); + EXPECT_EQ(string("UTF-8"), t.token()); + + EXPECT_FALSE(t.GetNext()); + EXPECT_FALSE(t.token_is_delim()); +} + +TEST(StringTokenizerTest, ParseQuotedString) { + string input = "foo bar 'hello world' baz"; + StringTokenizer t(input, " "); + t.set_quote_chars("'"); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("foo"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("bar"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("'hello world'"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("baz"), t.token()); + + EXPECT_FALSE(t.GetNext()); +} + +TEST(StringTokenizerTest, ParseQuotedString_Malformed) { + string input = "bar 'hello wo"; + StringTokenizer t(input, " "); + t.set_quote_chars("'"); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("bar"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("'hello wo"), t.token()); + + EXPECT_FALSE(t.GetNext()); +} + +TEST(StringTokenizerTest, ParseQuotedString_Multiple) { + string input = "bar 'hel\"lo\" wo' baz\""; + StringTokenizer t(input, " "); + t.set_quote_chars("'\""); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("bar"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("'hel\"lo\" wo'"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("baz\""), t.token()); + + EXPECT_FALSE(t.GetNext()); +} + +TEST(StringTokenizerTest, ParseQuotedString_EscapedQuotes) { + string input = "foo 'don\\'t do that'"; + StringTokenizer t(input, " "); + t.set_quote_chars("'"); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("foo"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("'don\\'t do that'"), t.token()); + + EXPECT_FALSE(t.GetNext()); +} + +TEST(StringTokenizerTest, ParseQuotedString_EscapedQuotes2) { + string input = "foo='a, b', bar"; + StringTokenizer t(input, ", "); + t.set_quote_chars("'"); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("foo='a, b'"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("bar"), t.token()); + + EXPECT_FALSE(t.GetNext()); +} + +} // namespace + +} // namespace base diff --git a/base/strings/string_util.cc b/base/strings/string_util.cc new file mode 100644 index 0000000000..3ed706978e --- /dev/null +++ b/base/strings/string_util.cc @@ -0,0 +1,1011 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/strings/string_util.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/memory/singleton.h" +#include "base/strings/utf_string_conversion_utils.h" +#include "base/strings/utf_string_conversions.h" +#include "base/third_party/icu/icu_utf.h" +#include "build/build_config.h" + +namespace { + +// Force the singleton used by Empty[W]String[16] to be a unique type. This +// prevents other code that might accidentally use Singleton from +// getting our internal one. +struct EmptyStrings { + EmptyStrings() {} + const std::string s; + const std::wstring ws; + const string16 s16; + + static EmptyStrings* GetInstance() { + return Singleton::get(); + } +}; + +// Used by ReplaceStringPlaceholders to track the position in the string of +// replaced parameters. +struct ReplacementOffset { + ReplacementOffset(uintptr_t parameter, size_t offset) + : parameter(parameter), + offset(offset) {} + + // Index of the parameter. + uintptr_t parameter; + + // Starting position in the string. + size_t offset; +}; + +static bool CompareParameter(const ReplacementOffset& elem1, + const ReplacementOffset& elem2) { + return elem1.parameter < elem2.parameter; +} + +} // namespace + +namespace base { + +bool IsWprintfFormatPortable(const wchar_t* format) { + for (const wchar_t* position = format; *position != '\0'; ++position) { + if (*position == '%') { + bool in_specification = true; + bool modifier_l = false; + while (in_specification) { + // Eat up characters until reaching a known specifier. + if (*++position == '\0') { + // The format string ended in the middle of a specification. Call + // it portable because no unportable specifications were found. The + // string is equally broken on all platforms. + return true; + } + + if (*position == 'l') { + // 'l' is the only thing that can save the 's' and 'c' specifiers. + modifier_l = true; + } else if (((*position == 's' || *position == 'c') && !modifier_l) || + *position == 'S' || *position == 'C' || *position == 'F' || + *position == 'D' || *position == 'O' || *position == 'U') { + // Not portable. + return false; + } + + if (wcschr(L"diouxXeEfgGaAcspn%", *position)) { + // Portable, keep scanning the rest of the format string. + in_specification = false; + } + } + } + } + + return true; +} + +} // namespace base + + +const std::string& EmptyString() { + return EmptyStrings::GetInstance()->s; +} + +const std::wstring& EmptyWString() { + return EmptyStrings::GetInstance()->ws; +} + +const string16& EmptyString16() { + return EmptyStrings::GetInstance()->s16; +} + +template +bool ReplaceCharsT(const STR& input, + const typename STR::value_type replace_chars[], + const STR& replace_with, + STR* output) { + bool removed = false; + size_t replace_length = replace_with.length(); + + *output = input; + + size_t found = output->find_first_of(replace_chars); + while (found != STR::npos) { + removed = true; + output->replace(found, 1, replace_with); + found = output->find_first_of(replace_chars, found + replace_length); + } + + return removed; +} + +bool ReplaceChars(const string16& input, + const char16 replace_chars[], + const string16& replace_with, + string16* output) { + return ReplaceCharsT(input, replace_chars, replace_with, output); +} + +bool ReplaceChars(const std::string& input, + const char replace_chars[], + const std::string& replace_with, + std::string* output) { + return ReplaceCharsT(input, replace_chars, replace_with, output); +} + +bool RemoveChars(const string16& input, + const char16 remove_chars[], + string16* output) { + return ReplaceChars(input, remove_chars, string16(), output); +} + +bool RemoveChars(const std::string& input, + const char remove_chars[], + std::string* output) { + return ReplaceChars(input, remove_chars, std::string(), output); +} + +template +TrimPositions TrimStringT(const STR& input, + const typename STR::value_type trim_chars[], + TrimPositions positions, + STR* output) { + // Find the edges of leading/trailing whitespace as desired. + const typename STR::size_type last_char = input.length() - 1; + const typename STR::size_type first_good_char = (positions & TRIM_LEADING) ? + input.find_first_not_of(trim_chars) : 0; + const typename STR::size_type last_good_char = (positions & TRIM_TRAILING) ? + input.find_last_not_of(trim_chars) : last_char; + + // When the string was all whitespace, report that we stripped off whitespace + // from whichever position the caller was interested in. For empty input, we + // stripped no whitespace, but we still need to clear |output|. + if (input.empty() || + (first_good_char == STR::npos) || (last_good_char == STR::npos)) { + bool input_was_empty = input.empty(); // in case output == &input + output->clear(); + return input_was_empty ? TRIM_NONE : positions; + } + + // Trim the whitespace. + *output = + input.substr(first_good_char, last_good_char - first_good_char + 1); + + // Return where we trimmed from. + return static_cast( + ((first_good_char == 0) ? TRIM_NONE : TRIM_LEADING) | + ((last_good_char == last_char) ? TRIM_NONE : TRIM_TRAILING)); +} + +bool TrimString(const std::wstring& input, + const wchar_t trim_chars[], + std::wstring* output) { + return TrimStringT(input, trim_chars, TRIM_ALL, output) != TRIM_NONE; +} + +#if !defined(WCHAR_T_IS_UTF16) +bool TrimString(const string16& input, + const char16 trim_chars[], + string16* output) { + return TrimStringT(input, trim_chars, TRIM_ALL, output) != TRIM_NONE; +} +#endif + +bool TrimString(const std::string& input, + const char trim_chars[], + std::string* output) { + return TrimStringT(input, trim_chars, TRIM_ALL, output) != TRIM_NONE; +} + +void TruncateUTF8ToByteSize(const std::string& input, + const size_t byte_size, + std::string* output) { + DCHECK(output); + if (byte_size > input.length()) { + *output = input; + return; + } + DCHECK_LE(byte_size, static_cast(kint32max)); + // Note: This cast is necessary because CBU8_NEXT uses int32s. + int32 truncation_length = static_cast(byte_size); + int32 char_index = truncation_length - 1; + const char* data = input.data(); + + // Using CBU8, we will move backwards from the truncation point + // to the beginning of the string looking for a valid UTF8 + // character. Once a full UTF8 character is found, we will + // truncate the string to the end of that character. + while (char_index >= 0) { + int32 prev = char_index; + uint32 code_point = 0; + CBU8_NEXT(data, char_index, truncation_length, code_point); + if (!base::IsValidCharacter(code_point) || + !base::IsValidCodepoint(code_point)) { + char_index = prev - 1; + } else { + break; + } + } + + if (char_index >= 0 ) + *output = input.substr(0, char_index); + else + output->clear(); +} + +TrimPositions TrimWhitespace(const string16& input, + TrimPositions positions, + string16* output) { + return TrimStringT(input, kWhitespaceUTF16, positions, output); +} + +TrimPositions TrimWhitespaceASCII(const std::string& input, + TrimPositions positions, + std::string* output) { + return TrimStringT(input, kWhitespaceASCII, positions, output); +} + +// This function is only for backward-compatibility. +// To be removed when all callers are updated. +TrimPositions TrimWhitespace(const std::string& input, + TrimPositions positions, + std::string* output) { + return TrimWhitespaceASCII(input, positions, output); +} + +template +STR CollapseWhitespaceT(const STR& text, + bool trim_sequences_with_line_breaks) { + STR result; + result.resize(text.size()); + + // Set flags to pretend we're already in a trimmed whitespace sequence, so we + // will trim any leading whitespace. + bool in_whitespace = true; + bool already_trimmed = true; + + int chars_written = 0; + for (typename STR::const_iterator i(text.begin()); i != text.end(); ++i) { + if (IsWhitespace(*i)) { + if (!in_whitespace) { + // Reduce all whitespace sequences to a single space. + in_whitespace = true; + result[chars_written++] = L' '; + } + if (trim_sequences_with_line_breaks && !already_trimmed && + ((*i == '\n') || (*i == '\r'))) { + // Whitespace sequences containing CR or LF are eliminated entirely. + already_trimmed = true; + --chars_written; + } + } else { + // Non-whitespace chracters are copied straight across. + in_whitespace = false; + already_trimmed = false; + result[chars_written++] = *i; + } + } + + if (in_whitespace && !already_trimmed) { + // Any trailing whitespace is eliminated. + --chars_written; + } + + result.resize(chars_written); + return result; +} + +std::wstring CollapseWhitespace(const std::wstring& text, + bool trim_sequences_with_line_breaks) { + return CollapseWhitespaceT(text, trim_sequences_with_line_breaks); +} + +#if !defined(WCHAR_T_IS_UTF16) +string16 CollapseWhitespace(const string16& text, + bool trim_sequences_with_line_breaks) { + return CollapseWhitespaceT(text, trim_sequences_with_line_breaks); +} +#endif + +std::string CollapseWhitespaceASCII(const std::string& text, + bool trim_sequences_with_line_breaks) { + return CollapseWhitespaceT(text, trim_sequences_with_line_breaks); +} + +bool ContainsOnlyWhitespaceASCII(const std::string& str) { + for (std::string::const_iterator i(str.begin()); i != str.end(); ++i) { + if (!IsAsciiWhitespace(*i)) + return false; + } + return true; +} + +bool ContainsOnlyWhitespace(const string16& str) { + return str.find_first_not_of(kWhitespaceUTF16) == string16::npos; +} + +template +static bool ContainsOnlyCharsT(const STR& input, const STR& characters) { + for (typename STR::const_iterator iter = input.begin(); + iter != input.end(); ++iter) { + if (characters.find(*iter) == STR::npos) + return false; + } + return true; +} + +bool ContainsOnlyChars(const std::wstring& input, + const std::wstring& characters) { + return ContainsOnlyCharsT(input, characters); +} + +#if !defined(WCHAR_T_IS_UTF16) +bool ContainsOnlyChars(const string16& input, const string16& characters) { + return ContainsOnlyCharsT(input, characters); +} +#endif + +bool ContainsOnlyChars(const std::string& input, + const std::string& characters) { + return ContainsOnlyCharsT(input, characters); +} + +std::string WideToASCII(const std::wstring& wide) { + DCHECK(IsStringASCII(wide)) << wide; + return std::string(wide.begin(), wide.end()); +} + +std::string UTF16ToASCII(const string16& utf16) { + DCHECK(IsStringASCII(utf16)) << utf16; + return std::string(utf16.begin(), utf16.end()); +} + +// Latin1 is just the low range of Unicode, so we can copy directly to convert. +bool WideToLatin1(const std::wstring& wide, std::string* latin1) { + std::string output; + output.resize(wide.size()); + latin1->clear(); + for (size_t i = 0; i < wide.size(); i++) { + if (wide[i] > 255) + return false; + output[i] = static_cast(wide[i]); + } + latin1->swap(output); + return true; +} + +template +static bool DoIsStringASCII(const STR& str) { + for (size_t i = 0; i < str.length(); i++) { + typename ToUnsigned::Unsigned c = str[i]; + if (c > 0x7F) + return false; + } + return true; +} + +bool IsStringASCII(const std::wstring& str) { + return DoIsStringASCII(str); +} + +#if !defined(WCHAR_T_IS_UTF16) +bool IsStringASCII(const string16& str) { + return DoIsStringASCII(str); +} +#endif + +bool IsStringASCII(const base::StringPiece& str) { + return DoIsStringASCII(str); +} + +bool IsStringUTF8(const std::string& str) { + const char *src = str.data(); + int32 src_len = static_cast(str.length()); + int32 char_index = 0; + + while (char_index < src_len) { + int32 code_point; + CBU8_NEXT(src, char_index, src_len, code_point); + if (!base::IsValidCharacter(code_point)) + return false; + } + return true; +} + +template +static inline bool DoLowerCaseEqualsASCII(Iter a_begin, + Iter a_end, + const char* b) { + for (Iter it = a_begin; it != a_end; ++it, ++b) { + if (!*b || base::ToLowerASCII(*it) != *b) + return false; + } + return *b == 0; +} + +// Front-ends for LowerCaseEqualsASCII. +bool LowerCaseEqualsASCII(const std::string& a, const char* b) { + return DoLowerCaseEqualsASCII(a.begin(), a.end(), b); +} + +bool LowerCaseEqualsASCII(const std::wstring& a, const char* b) { + return DoLowerCaseEqualsASCII(a.begin(), a.end(), b); +} + +#if !defined(WCHAR_T_IS_UTF16) +bool LowerCaseEqualsASCII(const string16& a, const char* b) { + return DoLowerCaseEqualsASCII(a.begin(), a.end(), b); +} +#endif + +bool LowerCaseEqualsASCII(std::string::const_iterator a_begin, + std::string::const_iterator a_end, + const char* b) { + return DoLowerCaseEqualsASCII(a_begin, a_end, b); +} + +bool LowerCaseEqualsASCII(std::wstring::const_iterator a_begin, + std::wstring::const_iterator a_end, + const char* b) { + return DoLowerCaseEqualsASCII(a_begin, a_end, b); +} + +#if !defined(WCHAR_T_IS_UTF16) +bool LowerCaseEqualsASCII(string16::const_iterator a_begin, + string16::const_iterator a_end, + const char* b) { + return DoLowerCaseEqualsASCII(a_begin, a_end, b); +} +#endif + +// TODO(port): Resolve wchar_t/iterator issues that require OS_ANDROID here. +#if !defined(OS_ANDROID) +bool LowerCaseEqualsASCII(const char* a_begin, + const char* a_end, + const char* b) { + return DoLowerCaseEqualsASCII(a_begin, a_end, b); +} + +bool LowerCaseEqualsASCII(const wchar_t* a_begin, + const wchar_t* a_end, + const char* b) { + return DoLowerCaseEqualsASCII(a_begin, a_end, b); +} + +#if !defined(WCHAR_T_IS_UTF16) +bool LowerCaseEqualsASCII(const char16* a_begin, + const char16* a_end, + const char* b) { + return DoLowerCaseEqualsASCII(a_begin, a_end, b); +} +#endif + +#endif // !defined(OS_ANDROID) + +bool EqualsASCII(const string16& a, const base::StringPiece& b) { + if (a.length() != b.length()) + return false; + return std::equal(b.begin(), b.end(), a.begin()); +} + +bool StartsWithASCII(const std::string& str, + const std::string& search, + bool case_sensitive) { + if (case_sensitive) + return str.compare(0, search.length(), search) == 0; + else + return base::strncasecmp(str.c_str(), search.c_str(), search.length()) == 0; +} + +template +bool StartsWithT(const STR& str, const STR& search, bool case_sensitive) { + if (case_sensitive) { + return str.compare(0, search.length(), search) == 0; + } else { + if (search.size() > str.size()) + return false; + return std::equal(search.begin(), search.end(), str.begin(), + base::CaseInsensitiveCompare()); + } +} + +bool StartsWith(const std::wstring& str, const std::wstring& search, + bool case_sensitive) { + return StartsWithT(str, search, case_sensitive); +} + +#if !defined(WCHAR_T_IS_UTF16) +bool StartsWith(const string16& str, const string16& search, + bool case_sensitive) { + return StartsWithT(str, search, case_sensitive); +} +#endif + +template +bool EndsWithT(const STR& str, const STR& search, bool case_sensitive) { + typename STR::size_type str_length = str.length(); + typename STR::size_type search_length = search.length(); + if (search_length > str_length) + return false; + if (case_sensitive) { + return str.compare(str_length - search_length, search_length, search) == 0; + } else { + return std::equal(search.begin(), search.end(), + str.begin() + (str_length - search_length), + base::CaseInsensitiveCompare()); + } +} + +bool EndsWith(const std::string& str, const std::string& search, + bool case_sensitive) { + return EndsWithT(str, search, case_sensitive); +} + +bool EndsWith(const std::wstring& str, const std::wstring& search, + bool case_sensitive) { + return EndsWithT(str, search, case_sensitive); +} + +#if !defined(WCHAR_T_IS_UTF16) +bool EndsWith(const string16& str, const string16& search, + bool case_sensitive) { + return EndsWithT(str, search, case_sensitive); +} +#endif + +static const char* const kByteStringsUnlocalized[] = { + " B", + " kB", + " MB", + " GB", + " TB", + " PB" +}; + +string16 FormatBytesUnlocalized(int64 bytes) { + double unit_amount = static_cast(bytes); + size_t dimension = 0; + const int kKilo = 1024; + while (unit_amount >= kKilo && + dimension < arraysize(kByteStringsUnlocalized) - 1) { + unit_amount /= kKilo; + dimension++; + } + + char buf[64]; + if (bytes != 0 && dimension > 0 && unit_amount < 100) { + base::snprintf(buf, arraysize(buf), "%.1lf%s", unit_amount, + kByteStringsUnlocalized[dimension]); + } else { + base::snprintf(buf, arraysize(buf), "%.0lf%s", unit_amount, + kByteStringsUnlocalized[dimension]); + } + + return ASCIIToUTF16(buf); +} + +template +void DoReplaceSubstringsAfterOffset(StringType* str, + typename StringType::size_type start_offset, + const StringType& find_this, + const StringType& replace_with, + bool replace_all) { + if ((start_offset == StringType::npos) || (start_offset >= str->length())) + return; + + DCHECK(!find_this.empty()); + for (typename StringType::size_type offs(str->find(find_this, start_offset)); + offs != StringType::npos; offs = str->find(find_this, offs)) { + str->replace(offs, find_this.length(), replace_with); + offs += replace_with.length(); + + if (!replace_all) + break; + } +} + +void ReplaceFirstSubstringAfterOffset(string16* str, + string16::size_type start_offset, + const string16& find_this, + const string16& replace_with) { + DoReplaceSubstringsAfterOffset(str, start_offset, find_this, replace_with, + false); // replace first instance +} + +void ReplaceFirstSubstringAfterOffset(std::string* str, + std::string::size_type start_offset, + const std::string& find_this, + const std::string& replace_with) { + DoReplaceSubstringsAfterOffset(str, start_offset, find_this, replace_with, + false); // replace first instance +} + +void ReplaceSubstringsAfterOffset(string16* str, + string16::size_type start_offset, + const string16& find_this, + const string16& replace_with) { + DoReplaceSubstringsAfterOffset(str, start_offset, find_this, replace_with, + true); // replace all instances +} + +void ReplaceSubstringsAfterOffset(std::string* str, + std::string::size_type start_offset, + const std::string& find_this, + const std::string& replace_with) { + DoReplaceSubstringsAfterOffset(str, start_offset, find_this, replace_with, + true); // replace all instances +} + + +template +static size_t TokenizeT(const STR& str, + const STR& delimiters, + std::vector* tokens) { + tokens->clear(); + + typename STR::size_type start = str.find_first_not_of(delimiters); + while (start != STR::npos) { + typename STR::size_type end = str.find_first_of(delimiters, start + 1); + if (end == STR::npos) { + tokens->push_back(str.substr(start)); + break; + } else { + tokens->push_back(str.substr(start, end - start)); + start = str.find_first_not_of(delimiters, end + 1); + } + } + + return tokens->size(); +} + +size_t Tokenize(const std::wstring& str, + const std::wstring& delimiters, + std::vector* tokens) { + return TokenizeT(str, delimiters, tokens); +} + +#if !defined(WCHAR_T_IS_UTF16) +size_t Tokenize(const string16& str, + const string16& delimiters, + std::vector* tokens) { + return TokenizeT(str, delimiters, tokens); +} +#endif + +size_t Tokenize(const std::string& str, + const std::string& delimiters, + std::vector* tokens) { + return TokenizeT(str, delimiters, tokens); +} + +size_t Tokenize(const base::StringPiece& str, + const base::StringPiece& delimiters, + std::vector* tokens) { + return TokenizeT(str, delimiters, tokens); +} + +template +static STR JoinStringT(const std::vector& parts, const STR& sep) { + if (parts.empty()) + return STR(); + + STR result(parts[0]); + typename std::vector::const_iterator iter = parts.begin(); + ++iter; + + for (; iter != parts.end(); ++iter) { + result += sep; + result += *iter; + } + + return result; +} + +std::string JoinString(const std::vector& parts, char sep) { + return JoinStringT(parts, std::string(1, sep)); +} + +string16 JoinString(const std::vector& parts, char16 sep) { + return JoinStringT(parts, string16(1, sep)); +} + +std::string JoinString(const std::vector& parts, + const std::string& separator) { + return JoinStringT(parts, separator); +} + +string16 JoinString(const std::vector& parts, + const string16& separator) { + return JoinStringT(parts, separator); +} + +template +OutStringType DoReplaceStringPlaceholders(const FormatStringType& format_string, + const std::vector& subst, std::vector* offsets) { + size_t substitutions = subst.size(); + + size_t sub_length = 0; + for (typename std::vector::const_iterator iter = subst.begin(); + iter != subst.end(); ++iter) { + sub_length += iter->length(); + } + + OutStringType formatted; + formatted.reserve(format_string.length() + sub_length); + + std::vector r_offsets; + for (typename FormatStringType::const_iterator i = format_string.begin(); + i != format_string.end(); ++i) { + if ('$' == *i) { + if (i + 1 != format_string.end()) { + ++i; + DCHECK('$' == *i || '1' <= *i) << "Invalid placeholder: " << *i; + if ('$' == *i) { + while (i != format_string.end() && '$' == *i) { + formatted.push_back('$'); + ++i; + } + --i; + } else { + uintptr_t index = 0; + while (i != format_string.end() && '0' <= *i && *i <= '9') { + index *= 10; + index += *i - '0'; + ++i; + } + --i; + index -= 1; + if (offsets) { + ReplacementOffset r_offset(index, + static_cast(formatted.size())); + r_offsets.insert(std::lower_bound(r_offsets.begin(), + r_offsets.end(), + r_offset, + &CompareParameter), + r_offset); + } + if (index < substitutions) + formatted.append(subst.at(index)); + } + } + } else { + formatted.push_back(*i); + } + } + if (offsets) { + for (std::vector::const_iterator i = r_offsets.begin(); + i != r_offsets.end(); ++i) { + offsets->push_back(i->offset); + } + } + return formatted; +} + +string16 ReplaceStringPlaceholders(const string16& format_string, + const std::vector& subst, + std::vector* offsets) { + return DoReplaceStringPlaceholders(format_string, subst, offsets); +} + +std::string ReplaceStringPlaceholders(const base::StringPiece& format_string, + const std::vector& subst, + std::vector* offsets) { + return DoReplaceStringPlaceholders(format_string, subst, offsets); +} + +string16 ReplaceStringPlaceholders(const string16& format_string, + const string16& a, + size_t* offset) { + std::vector offsets; + std::vector subst; + subst.push_back(a); + string16 result = ReplaceStringPlaceholders(format_string, subst, &offsets); + + DCHECK(offsets.size() == 1); + if (offset) { + *offset = offsets[0]; + } + return result; +} + +static bool IsWildcard(base_icu::UChar32 character) { + return character == '*' || character == '?'; +} + +// Move the strings pointers to the point where they start to differ. +template +static void EatSameChars(const CHAR** pattern, const CHAR* pattern_end, + const CHAR** string, const CHAR* string_end, + NEXT next) { + const CHAR* escape = NULL; + while (*pattern != pattern_end && *string != string_end) { + if (!escape && IsWildcard(**pattern)) { + // We don't want to match wildcard here, except if it's escaped. + return; + } + + // Check if the escapement char is found. If so, skip it and move to the + // next character. + if (!escape && **pattern == '\\') { + escape = *pattern; + next(pattern, pattern_end); + continue; + } + + // Check if the chars match, if so, increment the ptrs. + const CHAR* pattern_next = *pattern; + const CHAR* string_next = *string; + base_icu::UChar32 pattern_char = next(&pattern_next, pattern_end); + if (pattern_char == next(&string_next, string_end) && + pattern_char != (base_icu::UChar32) CBU_SENTINEL) { + *pattern = pattern_next; + *string = string_next; + } else { + // Uh ho, it did not match, we are done. If the last char was an + // escapement, that means that it was an error to advance the ptr here, + // let's put it back where it was. This also mean that the MatchPattern + // function will return false because if we can't match an escape char + // here, then no one will. + if (escape) { + *pattern = escape; + } + return; + } + + escape = NULL; + } +} + +template +static void EatWildcard(const CHAR** pattern, const CHAR* end, NEXT next) { + while (*pattern != end) { + if (!IsWildcard(**pattern)) + return; + next(pattern, end); + } +} + +template +static bool MatchPatternT(const CHAR* eval, const CHAR* eval_end, + const CHAR* pattern, const CHAR* pattern_end, + int depth, + NEXT next) { + const int kMaxDepth = 16; + if (depth > kMaxDepth) + return false; + + // Eat all the matching chars. + EatSameChars(&pattern, pattern_end, &eval, eval_end, next); + + // If the string is empty, then the pattern must be empty too, or contains + // only wildcards. + if (eval == eval_end) { + EatWildcard(&pattern, pattern_end, next); + return pattern == pattern_end; + } + + // Pattern is empty but not string, this is not a match. + if (pattern == pattern_end) + return false; + + // If this is a question mark, then we need to compare the rest with + // the current string or the string with one character eaten. + const CHAR* next_pattern = pattern; + next(&next_pattern, pattern_end); + if (pattern[0] == '?') { + if (MatchPatternT(eval, eval_end, next_pattern, pattern_end, + depth + 1, next)) + return true; + const CHAR* next_eval = eval; + next(&next_eval, eval_end); + if (MatchPatternT(next_eval, eval_end, next_pattern, pattern_end, + depth + 1, next)) + return true; + } + + // This is a *, try to match all the possible substrings with the remainder + // of the pattern. + if (pattern[0] == '*') { + // Collapse duplicate wild cards (********** into *) so that the + // method does not recurse unnecessarily. http://crbug.com/52839 + EatWildcard(&next_pattern, pattern_end, next); + + while (eval != eval_end) { + if (MatchPatternT(eval, eval_end, next_pattern, pattern_end, + depth + 1, next)) + return true; + eval++; + } + + // We reached the end of the string, let see if the pattern contains only + // wildcards. + if (eval == eval_end) { + EatWildcard(&pattern, pattern_end, next); + if (pattern != pattern_end) + return false; + return true; + } + } + + return false; +} + +struct NextCharUTF8 { + base_icu::UChar32 operator()(const char** p, const char* end) { + base_icu::UChar32 c; + int offset = 0; + CBU8_NEXT(*p, offset, end - *p, c); + *p += offset; + return c; + } +}; + +struct NextCharUTF16 { + base_icu::UChar32 operator()(const char16** p, const char16* end) { + base_icu::UChar32 c; + int offset = 0; + CBU16_NEXT(*p, offset, end - *p, c); + *p += offset; + return c; + } +}; + +bool MatchPattern(const base::StringPiece& eval, + const base::StringPiece& pattern) { + return MatchPatternT(eval.data(), eval.data() + eval.size(), + pattern.data(), pattern.data() + pattern.size(), + 0, NextCharUTF8()); +} + +bool MatchPattern(const string16& eval, const string16& pattern) { + return MatchPatternT(eval.c_str(), eval.c_str() + eval.size(), + pattern.c_str(), pattern.c_str() + pattern.size(), + 0, NextCharUTF16()); +} + +// The following code is compatible with the OpenBSD lcpy interface. See: +// http://www.gratisoft.us/todd/papers/strlcpy.html +// ftp://ftp.openbsd.org/pub/OpenBSD/src/lib/libc/string/{wcs,str}lcpy.c + +namespace { + +template +size_t lcpyT(CHAR* dst, const CHAR* src, size_t dst_size) { + for (size_t i = 0; i < dst_size; ++i) { + if ((dst[i] = src[i]) == 0) // We hit and copied the terminating NULL. + return i; + } + + // We were left off at dst_size. We over copied 1 byte. Null terminate. + if (dst_size != 0) + dst[dst_size - 1] = 0; + + // Count the rest of the |src|, and return it's length in characters. + while (src[dst_size]) ++dst_size; + return dst_size; +} + +} // namespace + +size_t base::strlcpy(char* dst, const char* src, size_t dst_size) { + return lcpyT(dst, src, dst_size); +} +size_t base::wcslcpy(wchar_t* dst, const wchar_t* src, size_t dst_size) { + return lcpyT(dst, src, dst_size); +} diff --git a/base/strings/string_util.h b/base/strings/string_util.h new file mode 100644 index 0000000000..d2a216efaf --- /dev/null +++ b/base/strings/string_util.h @@ -0,0 +1,576 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// This file defines utility functions for working with strings. + +#ifndef BASE_STRINGS_STRING_UTIL_H_ +#define BASE_STRINGS_STRING_UTIL_H_ + +#include +#include // va_list + +#include +#include + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/strings/string16.h" +#include "base/strings/string_piece.h" // For implicit conversions. + +// Safe standard library wrappers for all platforms. + +namespace base { + +// C standard-library functions like "strncasecmp" and "snprintf" that aren't +// cross-platform are provided as "base::strncasecmp", and their prototypes +// are listed below. These functions are then implemented as inline calls +// to the platform-specific equivalents in the platform-specific headers. + +// Compares the two strings s1 and s2 without regard to case using +// the current locale; returns 0 if they are equal, 1 if s1 > s2, and -1 if +// s2 > s1 according to a lexicographic comparison. +int strcasecmp(const char* s1, const char* s2); + +// Compares up to count characters of s1 and s2 without regard to case using +// the current locale; returns 0 if they are equal, 1 if s1 > s2, and -1 if +// s2 > s1 according to a lexicographic comparison. +int strncasecmp(const char* s1, const char* s2, size_t count); + +// Same as strncmp but for char16 strings. +int strncmp16(const char16* s1, const char16* s2, size_t count); + +// Wrapper for vsnprintf that always null-terminates and always returns the +// number of characters that would be in an untruncated formatted +// string, even when truncation occurs. +int vsnprintf(char* buffer, size_t size, const char* format, va_list arguments) + PRINTF_FORMAT(3, 0); + +// vswprintf always null-terminates, but when truncation occurs, it will either +// return -1 or the number of characters that would be in an untruncated +// formatted string. The actual return value depends on the underlying +// C library's vswprintf implementation. +int vswprintf(wchar_t* buffer, size_t size, + const wchar_t* format, va_list arguments) + WPRINTF_FORMAT(3, 0); + +// Some of these implementations need to be inlined. + +// We separate the declaration from the implementation of this inline +// function just so the PRINTF_FORMAT works. +inline int snprintf(char* buffer, size_t size, const char* format, ...) + PRINTF_FORMAT(3, 4); +inline int snprintf(char* buffer, size_t size, const char* format, ...) { + va_list arguments; + va_start(arguments, format); + int result = vsnprintf(buffer, size, format, arguments); + va_end(arguments); + return result; +} + +// We separate the declaration from the implementation of this inline +// function just so the WPRINTF_FORMAT works. +inline int swprintf(wchar_t* buffer, size_t size, const wchar_t* format, ...) + WPRINTF_FORMAT(3, 4); +inline int swprintf(wchar_t* buffer, size_t size, const wchar_t* format, ...) { + va_list arguments; + va_start(arguments, format); + int result = vswprintf(buffer, size, format, arguments); + va_end(arguments); + return result; +} + +// BSD-style safe and consistent string copy functions. +// Copies |src| to |dst|, where |dst_size| is the total allocated size of |dst|. +// Copies at most |dst_size|-1 characters, and always NULL terminates |dst|, as +// long as |dst_size| is not 0. Returns the length of |src| in characters. +// If the return value is >= dst_size, then the output was truncated. +// NOTE: All sizes are in number of characters, NOT in bytes. +BASE_EXPORT size_t strlcpy(char* dst, const char* src, size_t dst_size); +BASE_EXPORT size_t wcslcpy(wchar_t* dst, const wchar_t* src, size_t dst_size); + +// Scan a wprintf format string to determine whether it's portable across a +// variety of systems. This function only checks that the conversion +// specifiers used by the format string are supported and have the same meaning +// on a variety of systems. It doesn't check for other errors that might occur +// within a format string. +// +// Nonportable conversion specifiers for wprintf are: +// - 's' and 'c' without an 'l' length modifier. %s and %c operate on char +// data on all systems except Windows, which treat them as wchar_t data. +// Use %ls and %lc for wchar_t data instead. +// - 'S' and 'C', which operate on wchar_t data on all systems except Windows, +// which treat them as char data. Use %ls and %lc for wchar_t data +// instead. +// - 'F', which is not identified by Windows wprintf documentation. +// - 'D', 'O', and 'U', which are deprecated and not available on all systems. +// Use %ld, %lo, and %lu instead. +// +// Note that there is no portable conversion specifier for char data when +// working with wprintf. +// +// This function is intended to be called from base::vswprintf. +BASE_EXPORT bool IsWprintfFormatPortable(const wchar_t* format); + +// ASCII-specific tolower. The standard library's tolower is locale sensitive, +// so we don't want to use it here. +template inline Char ToLowerASCII(Char c) { + return (c >= 'A' && c <= 'Z') ? (c + ('a' - 'A')) : c; +} + +// ASCII-specific toupper. The standard library's toupper is locale sensitive, +// so we don't want to use it here. +template inline Char ToUpperASCII(Char c) { + return (c >= 'a' && c <= 'z') ? (c + ('A' - 'a')) : c; +} + +// Function objects to aid in comparing/searching strings. + +template struct CaseInsensitiveCompare { + public: + bool operator()(Char x, Char y) const { + // TODO(darin): Do we really want to do locale sensitive comparisons here? + // See http://crbug.com/24917 + return tolower(x) == tolower(y); + } +}; + +template struct CaseInsensitiveCompareASCII { + public: + bool operator()(Char x, Char y) const { + return ToLowerASCII(x) == ToLowerASCII(y); + } +}; + +} // namespace base + +#if defined(OS_WIN) +#include "base/strings/string_util_win.h" +#elif defined(OS_POSIX) +#include "base/strings/string_util_posix.h" +#else +#error Define string operations appropriately for your platform +#endif + +// These threadsafe functions return references to globally unique empty +// strings. +// +// DO NOT USE THESE AS A GENERAL-PURPOSE SUBSTITUTE FOR DEFAULT CONSTRUCTORS. +// There is only one case where you should use these: functions which need to +// return a string by reference (e.g. as a class member accessor), and don't +// have an empty string to use (e.g. in an error case). These should not be +// used as initializers, function arguments, or return values for functions +// which return by value or outparam. +BASE_EXPORT const std::string& EmptyString(); +BASE_EXPORT const std::wstring& EmptyWString(); +BASE_EXPORT const string16& EmptyString16(); + +BASE_EXPORT extern const wchar_t kWhitespaceWide[]; +BASE_EXPORT extern const char16 kWhitespaceUTF16[]; +BASE_EXPORT extern const char kWhitespaceASCII[]; + +BASE_EXPORT extern const char kUtf8ByteOrderMark[]; + +// Removes characters in |remove_chars| from anywhere in |input|. Returns true +// if any characters were removed. |remove_chars| must be null-terminated. +// NOTE: Safe to use the same variable for both |input| and |output|. +BASE_EXPORT bool RemoveChars(const string16& input, + const char16 remove_chars[], + string16* output); +BASE_EXPORT bool RemoveChars(const std::string& input, + const char remove_chars[], + std::string* output); + +// Replaces characters in |replace_chars| from anywhere in |input| with +// |replace_with|. Each character in |replace_chars| will be replaced with +// the |replace_with| string. Returns true if any characters were replaced. +// |replace_chars| must be null-terminated. +// NOTE: Safe to use the same variable for both |input| and |output|. +BASE_EXPORT bool ReplaceChars(const string16& input, + const char16 replace_chars[], + const string16& replace_with, + string16* output); +BASE_EXPORT bool ReplaceChars(const std::string& input, + const char replace_chars[], + const std::string& replace_with, + std::string* output); + +// Removes characters in |trim_chars| from the beginning and end of |input|. +// |trim_chars| must be null-terminated. +// NOTE: Safe to use the same variable for both |input| and |output|. +BASE_EXPORT bool TrimString(const std::wstring& input, + const wchar_t trim_chars[], + std::wstring* output); +BASE_EXPORT bool TrimString(const string16& input, + const char16 trim_chars[], + string16* output); +BASE_EXPORT bool TrimString(const std::string& input, + const char trim_chars[], + std::string* output); + +// Truncates a string to the nearest UTF-8 character that will leave +// the string less than or equal to the specified byte size. +BASE_EXPORT void TruncateUTF8ToByteSize(const std::string& input, + const size_t byte_size, + std::string* output); + +// Trims any whitespace from either end of the input string. Returns where +// whitespace was found. +// The non-wide version has two functions: +// * TrimWhitespaceASCII() +// This function is for ASCII strings and only looks for ASCII whitespace; +// Please choose the best one according to your usage. +// NOTE: Safe to use the same variable for both input and output. +enum TrimPositions { + TRIM_NONE = 0, + TRIM_LEADING = 1 << 0, + TRIM_TRAILING = 1 << 1, + TRIM_ALL = TRIM_LEADING | TRIM_TRAILING, +}; +BASE_EXPORT TrimPositions TrimWhitespace(const string16& input, + TrimPositions positions, + string16* output); +BASE_EXPORT TrimPositions TrimWhitespaceASCII(const std::string& input, + TrimPositions positions, + std::string* output); + +// Deprecated. This function is only for backward compatibility and calls +// TrimWhitespaceASCII(). +BASE_EXPORT TrimPositions TrimWhitespace(const std::string& input, + TrimPositions positions, + std::string* output); + +// Searches for CR or LF characters. Removes all contiguous whitespace +// strings that contain them. This is useful when trying to deal with text +// copied from terminals. +// Returns |text|, with the following three transformations: +// (1) Leading and trailing whitespace is trimmed. +// (2) If |trim_sequences_with_line_breaks| is true, any other whitespace +// sequences containing a CR or LF are trimmed. +// (3) All other whitespace sequences are converted to single spaces. +BASE_EXPORT std::wstring CollapseWhitespace( + const std::wstring& text, + bool trim_sequences_with_line_breaks); +BASE_EXPORT string16 CollapseWhitespace( + const string16& text, + bool trim_sequences_with_line_breaks); +BASE_EXPORT std::string CollapseWhitespaceASCII( + const std::string& text, + bool trim_sequences_with_line_breaks); + +// Returns true if the passed string is empty or contains only white-space +// characters. +BASE_EXPORT bool ContainsOnlyWhitespaceASCII(const std::string& str); +BASE_EXPORT bool ContainsOnlyWhitespace(const string16& str); + +// Returns true if |input| is empty or contains only characters found in +// |characters|. +BASE_EXPORT bool ContainsOnlyChars(const std::wstring& input, + const std::wstring& characters); +BASE_EXPORT bool ContainsOnlyChars(const string16& input, + const string16& characters); +BASE_EXPORT bool ContainsOnlyChars(const std::string& input, + const std::string& characters); + +// Converts to 7-bit ASCII by truncating. The result must be known to be ASCII +// beforehand. +BASE_EXPORT std::string WideToASCII(const std::wstring& wide); +BASE_EXPORT std::string UTF16ToASCII(const string16& utf16); + +// Converts the given wide string to the corresponding Latin1. This will fail +// (return false) if any characters are more than 255. +BASE_EXPORT bool WideToLatin1(const std::wstring& wide, std::string* latin1); + +// Returns true if the specified string matches the criteria. How can a wide +// string be 8-bit or UTF8? It contains only characters that are < 256 (in the +// first case) or characters that use only 8-bits and whose 8-bit +// representation looks like a UTF-8 string (the second case). +// +// Note that IsStringUTF8 checks not only if the input is structurally +// valid but also if it doesn't contain any non-character codepoint +// (e.g. U+FFFE). It's done on purpose because all the existing callers want +// to have the maximum 'discriminating' power from other encodings. If +// there's a use case for just checking the structural validity, we have to +// add a new function for that. +BASE_EXPORT bool IsStringUTF8(const std::string& str); +BASE_EXPORT bool IsStringASCII(const std::wstring& str); +BASE_EXPORT bool IsStringASCII(const base::StringPiece& str); +BASE_EXPORT bool IsStringASCII(const string16& str); + +// Converts the elements of the given string. This version uses a pointer to +// clearly differentiate it from the non-pointer variant. +template inline void StringToLowerASCII(str* s) { + for (typename str::iterator i = s->begin(); i != s->end(); ++i) + *i = base::ToLowerASCII(*i); +} + +template inline str StringToLowerASCII(const str& s) { + // for std::string and std::wstring + str output(s); + StringToLowerASCII(&output); + return output; +} + +// Converts the elements of the given string. This version uses a pointer to +// clearly differentiate it from the non-pointer variant. +template inline void StringToUpperASCII(str* s) { + for (typename str::iterator i = s->begin(); i != s->end(); ++i) + *i = base::ToUpperASCII(*i); +} + +template inline str StringToUpperASCII(const str& s) { + // for std::string and std::wstring + str output(s); + StringToUpperASCII(&output); + return output; +} + +// Compare the lower-case form of the given string against the given ASCII +// string. This is useful for doing checking if an input string matches some +// token, and it is optimized to avoid intermediate string copies. This API is +// borrowed from the equivalent APIs in Mozilla. +BASE_EXPORT bool LowerCaseEqualsASCII(const std::string& a, const char* b); +BASE_EXPORT bool LowerCaseEqualsASCII(const std::wstring& a, const char* b); +BASE_EXPORT bool LowerCaseEqualsASCII(const string16& a, const char* b); + +// Same thing, but with string iterators instead. +BASE_EXPORT bool LowerCaseEqualsASCII(std::string::const_iterator a_begin, + std::string::const_iterator a_end, + const char* b); +BASE_EXPORT bool LowerCaseEqualsASCII(std::wstring::const_iterator a_begin, + std::wstring::const_iterator a_end, + const char* b); +BASE_EXPORT bool LowerCaseEqualsASCII(string16::const_iterator a_begin, + string16::const_iterator a_end, + const char* b); +BASE_EXPORT bool LowerCaseEqualsASCII(const char* a_begin, + const char* a_end, + const char* b); +BASE_EXPORT bool LowerCaseEqualsASCII(const wchar_t* a_begin, + const wchar_t* a_end, + const char* b); +BASE_EXPORT bool LowerCaseEqualsASCII(const char16* a_begin, + const char16* a_end, + const char* b); + +// Performs a case-sensitive string compare. The behavior is undefined if both +// strings are not ASCII. +BASE_EXPORT bool EqualsASCII(const string16& a, const base::StringPiece& b); + +// Returns true if str starts with search, or false otherwise. +BASE_EXPORT bool StartsWithASCII(const std::string& str, + const std::string& search, + bool case_sensitive); +BASE_EXPORT bool StartsWith(const std::wstring& str, + const std::wstring& search, + bool case_sensitive); +BASE_EXPORT bool StartsWith(const string16& str, + const string16& search, + bool case_sensitive); + +// Returns true if str ends with search, or false otherwise. +BASE_EXPORT bool EndsWith(const std::string& str, + const std::string& search, + bool case_sensitive); +BASE_EXPORT bool EndsWith(const std::wstring& str, + const std::wstring& search, + bool case_sensitive); +BASE_EXPORT bool EndsWith(const string16& str, + const string16& search, + bool case_sensitive); + + +// Determines the type of ASCII character, independent of locale (the C +// library versions will change based on locale). +template +inline bool IsAsciiWhitespace(Char c) { + return c == ' ' || c == '\r' || c == '\n' || c == '\t'; +} +template +inline bool IsAsciiAlpha(Char c) { + return ((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')); +} +template +inline bool IsAsciiDigit(Char c) { + return c >= '0' && c <= '9'; +} + +template +inline bool IsHexDigit(Char c) { + return (c >= '0' && c <= '9') || + (c >= 'A' && c <= 'F') || + (c >= 'a' && c <= 'f'); +} + +template +inline Char HexDigitToInt(Char c) { + DCHECK(IsHexDigit(c)); + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + return 0; +} + +// Returns true if it's a whitespace character. +inline bool IsWhitespace(wchar_t c) { + return wcschr(kWhitespaceWide, c) != NULL; +} + +// Return a byte string in human-readable format with a unit suffix. Not +// appropriate for use in any UI; use of FormatBytes and friends in ui/base is +// highly recommended instead. TODO(avi): Figure out how to get callers to use +// FormatBytes instead; remove this. +BASE_EXPORT string16 FormatBytesUnlocalized(int64 bytes); + +// Starting at |start_offset| (usually 0), replace the first instance of +// |find_this| with |replace_with|. +BASE_EXPORT void ReplaceFirstSubstringAfterOffset( + string16* str, + string16::size_type start_offset, + const string16& find_this, + const string16& replace_with); +BASE_EXPORT void ReplaceFirstSubstringAfterOffset( + std::string* str, + std::string::size_type start_offset, + const std::string& find_this, + const std::string& replace_with); + +// Starting at |start_offset| (usually 0), look through |str| and replace all +// instances of |find_this| with |replace_with|. +// +// This does entire substrings; use std::replace in for single +// characters, for example: +// std::replace(str.begin(), str.end(), 'a', 'b'); +BASE_EXPORT void ReplaceSubstringsAfterOffset( + string16* str, + string16::size_type start_offset, + const string16& find_this, + const string16& replace_with); +BASE_EXPORT void ReplaceSubstringsAfterOffset( + std::string* str, + std::string::size_type start_offset, + const std::string& find_this, + const std::string& replace_with); + +// Reserves enough memory in |str| to accommodate |length_with_null| characters, +// sets the size of |str| to |length_with_null - 1| characters, and returns a +// pointer to the underlying contiguous array of characters. This is typically +// used when calling a function that writes results into a character array, but +// the caller wants the data to be managed by a string-like object. It is +// convenient in that is can be used inline in the call, and fast in that it +// avoids copying the results of the call from a char* into a string. +// +// |length_with_null| must be at least 2, since otherwise the underlying string +// would have size 0, and trying to access &((*str)[0]) in that case can result +// in a number of problems. +// +// Internally, this takes linear time because the resize() call 0-fills the +// underlying array for potentially all +// (|length_with_null - 1| * sizeof(string_type::value_type)) bytes. Ideally we +// could avoid this aspect of the resize() call, as we expect the caller to +// immediately write over this memory, but there is no other way to set the size +// of the string, and not doing that will mean people who access |str| rather +// than str.c_str() will get back a string of whatever size |str| had on entry +// to this function (probably 0). +template +inline typename string_type::value_type* WriteInto(string_type* str, + size_t length_with_null) { + DCHECK_GT(length_with_null, 1u); + str->reserve(length_with_null); + str->resize(length_with_null - 1); + return &((*str)[0]); +} + +//----------------------------------------------------------------------------- + +// Splits a string into its fields delimited by any of the characters in +// |delimiters|. Each field is added to the |tokens| vector. Returns the +// number of tokens found. +BASE_EXPORT size_t Tokenize(const std::wstring& str, + const std::wstring& delimiters, + std::vector* tokens); +BASE_EXPORT size_t Tokenize(const string16& str, + const string16& delimiters, + std::vector* tokens); +BASE_EXPORT size_t Tokenize(const std::string& str, + const std::string& delimiters, + std::vector* tokens); +BASE_EXPORT size_t Tokenize(const base::StringPiece& str, + const base::StringPiece& delimiters, + std::vector* tokens); + +// Does the opposite of SplitString(). +BASE_EXPORT string16 JoinString(const std::vector& parts, char16 s); +BASE_EXPORT std::string JoinString( + const std::vector& parts, char s); + +// Join |parts| using |separator|. +BASE_EXPORT std::string JoinString( + const std::vector& parts, + const std::string& separator); +BASE_EXPORT string16 JoinString( + const std::vector& parts, + const string16& separator); + +// Replace $1-$2-$3..$9 in the format string with |a|-|b|-|c|..|i| respectively. +// Additionally, any number of consecutive '$' characters is replaced by that +// number less one. Eg $$->$, $$$->$$, etc. The offsets parameter here can be +// NULL. This only allows you to use up to nine replacements. +BASE_EXPORT string16 ReplaceStringPlaceholders( + const string16& format_string, + const std::vector& subst, + std::vector* offsets); + +BASE_EXPORT std::string ReplaceStringPlaceholders( + const base::StringPiece& format_string, + const std::vector& subst, + std::vector* offsets); + +// Single-string shortcut for ReplaceStringHolders. |offset| may be NULL. +BASE_EXPORT string16 ReplaceStringPlaceholders(const string16& format_string, + const string16& a, + size_t* offset); + +// Returns true if the string passed in matches the pattern. The pattern +// string can contain wildcards like * and ? +// The backslash character (\) is an escape character for * and ? +// We limit the patterns to having a max of 16 * or ? characters. +// ? matches 0 or 1 character, while * matches 0 or more characters. +BASE_EXPORT bool MatchPattern(const base::StringPiece& string, + const base::StringPiece& pattern); +BASE_EXPORT bool MatchPattern(const string16& string, const string16& pattern); + +// Hack to convert any char-like type to its unsigned counterpart. +// For example, it will convert char, signed char and unsigned char to unsigned +// char. +template +struct ToUnsigned { + typedef T Unsigned; +}; + +template<> +struct ToUnsigned { + typedef unsigned char Unsigned; +}; +template<> +struct ToUnsigned { + typedef unsigned char Unsigned; +}; +template<> +struct ToUnsigned { +#if defined(WCHAR_T_IS_UTF16) + typedef unsigned short Unsigned; +#elif defined(WCHAR_T_IS_UTF32) + typedef uint32 Unsigned; +#endif +}; +template<> +struct ToUnsigned { + typedef unsigned short Unsigned; +}; + +#endif // BASE_STRINGS_STRING_UTIL_H_ diff --git a/base/strings/string_util_constants.cc b/base/strings/string_util_constants.cc new file mode 100644 index 0000000000..d92e40cf37 --- /dev/null +++ b/base/strings/string_util_constants.cc @@ -0,0 +1,55 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/strings/string_util.h" + +#define WHITESPACE_UNICODE \ + 0x0009, /* to */ \ + 0x000A, \ + 0x000B, \ + 0x000C, \ + 0x000D, \ + 0x0020, /* Space */ \ + 0x0085, /* */ \ + 0x00A0, /* No-Break Space */ \ + 0x1680, /* Ogham Space Mark */ \ + 0x180E, /* Mongolian Vowel Separator */ \ + 0x2000, /* En Quad to Hair Space */ \ + 0x2001, \ + 0x2002, \ + 0x2003, \ + 0x2004, \ + 0x2005, \ + 0x2006, \ + 0x2007, \ + 0x2008, \ + 0x2009, \ + 0x200A, \ + 0x200C, /* Zero Width Non-Joiner */ \ + 0x2028, /* Line Separator */ \ + 0x2029, /* Paragraph Separator */ \ + 0x202F, /* Narrow No-Break Space */ \ + 0x205F, /* Medium Mathematical Space */ \ + 0x3000, /* Ideographic Space */ \ + 0 + +const wchar_t kWhitespaceWide[] = { + WHITESPACE_UNICODE +}; + +const char16 kWhitespaceUTF16[] = { + WHITESPACE_UNICODE +}; + +const char kWhitespaceASCII[] = { + 0x09, // to + 0x0A, + 0x0B, + 0x0C, + 0x0D, + 0x20, // Space + 0 +}; + +const char kUtf8ByteOrderMark[] = "\xEF\xBB\xBF"; diff --git a/base/strings/string_util_posix.h b/base/strings/string_util_posix.h new file mode 100644 index 0000000000..34b14f178f --- /dev/null +++ b/base/strings/string_util_posix.h @@ -0,0 +1,53 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_STRINGS_STRING_UTIL_POSIX_H_ +#define BASE_STRINGS_STRING_UTIL_POSIX_H_ + +#include +#include +#include +#include + +#include "base/logging.h" +#include "base/strings/string_util.h" + +namespace base { + +// Chromium code style is to not use malloc'd strings; this is only for use +// for interaction with APIs that require it. +inline char* strdup(const char* str) { + return ::strdup(str); +} + +inline int strcasecmp(const char* string1, const char* string2) { + return ::strcasecmp(string1, string2); +} + +inline int strncasecmp(const char* string1, const char* string2, size_t count) { + return ::strncasecmp(string1, string2, count); +} + +inline int vsnprintf(char* buffer, size_t size, + const char* format, va_list arguments) { + return ::vsnprintf(buffer, size, format, arguments); +} + +inline int strncmp16(const char16* s1, const char16* s2, size_t count) { +#if defined(WCHAR_T_IS_UTF16) + return ::wcsncmp(s1, s2, count); +#elif defined(WCHAR_T_IS_UTF32) + return c16memcmp(s1, s2, count); +#endif +} + +inline int vswprintf(wchar_t* buffer, size_t size, + const wchar_t* format, va_list arguments) { + DCHECK(IsWprintfFormatPortable(format)); + return ::vswprintf(buffer, size, format, arguments); +} + +} // namespace base + +#endif // BASE_STRINGS_STRING_UTIL_POSIX_H_ diff --git a/base/strings/string_util_unittest.cc b/base/strings/string_util_unittest.cc new file mode 100644 index 0000000000..58b7620b35 --- /dev/null +++ b/base/strings/string_util_unittest.cc @@ -0,0 +1,1191 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/strings/string_util.h" + +#include +#include + +#include +#include + +#include "base/basictypes.h" +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::ElementsAre; + +namespace base { + +static const struct trim_case { + const wchar_t* input; + const TrimPositions positions; + const wchar_t* output; + const TrimPositions return_value; +} trim_cases[] = { + {L" Google Video ", TRIM_LEADING, L"Google Video ", TRIM_LEADING}, + {L" Google Video ", TRIM_TRAILING, L" Google Video", TRIM_TRAILING}, + {L" Google Video ", TRIM_ALL, L"Google Video", TRIM_ALL}, + {L"Google Video", TRIM_ALL, L"Google Video", TRIM_NONE}, + {L"", TRIM_ALL, L"", TRIM_NONE}, + {L" ", TRIM_LEADING, L"", TRIM_LEADING}, + {L" ", TRIM_TRAILING, L"", TRIM_TRAILING}, + {L" ", TRIM_ALL, L"", TRIM_ALL}, + {L"\t\rTest String\n", TRIM_ALL, L"Test String", TRIM_ALL}, + {L"\x2002Test String\x00A0\x3000", TRIM_ALL, L"Test String", TRIM_ALL}, +}; + +static const struct trim_case_ascii { + const char* input; + const TrimPositions positions; + const char* output; + const TrimPositions return_value; +} trim_cases_ascii[] = { + {" Google Video ", TRIM_LEADING, "Google Video ", TRIM_LEADING}, + {" Google Video ", TRIM_TRAILING, " Google Video", TRIM_TRAILING}, + {" Google Video ", TRIM_ALL, "Google Video", TRIM_ALL}, + {"Google Video", TRIM_ALL, "Google Video", TRIM_NONE}, + {"", TRIM_ALL, "", TRIM_NONE}, + {" ", TRIM_LEADING, "", TRIM_LEADING}, + {" ", TRIM_TRAILING, "", TRIM_TRAILING}, + {" ", TRIM_ALL, "", TRIM_ALL}, + {"\t\rTest String\n", TRIM_ALL, "Test String", TRIM_ALL}, +}; + +namespace { + +// Helper used to test TruncateUTF8ToByteSize. +bool Truncated(const std::string& input, const size_t byte_size, + std::string* output) { + size_t prev = input.length(); + TruncateUTF8ToByteSize(input, byte_size, output); + return prev != output->length(); +} + +} // namespace + +TEST(StringUtilTest, TruncateUTF8ToByteSize) { + std::string output; + + // Empty strings and invalid byte_size arguments + EXPECT_FALSE(Truncated(std::string(), 0, &output)); + EXPECT_EQ(output, ""); + EXPECT_TRUE(Truncated("\xe1\x80\xbf", 0, &output)); + EXPECT_EQ(output, ""); + EXPECT_FALSE(Truncated("\xe1\x80\xbf", -1, &output)); + EXPECT_FALSE(Truncated("\xe1\x80\xbf", 4, &output)); + + // Testing the truncation of valid UTF8 correctly + EXPECT_TRUE(Truncated("abc", 2, &output)); + EXPECT_EQ(output, "ab"); + EXPECT_TRUE(Truncated("\xc2\x81\xc2\x81", 2, &output)); + EXPECT_EQ(output.compare("\xc2\x81"), 0); + EXPECT_TRUE(Truncated("\xc2\x81\xc2\x81", 3, &output)); + EXPECT_EQ(output.compare("\xc2\x81"), 0); + EXPECT_FALSE(Truncated("\xc2\x81\xc2\x81", 4, &output)); + EXPECT_EQ(output.compare("\xc2\x81\xc2\x81"), 0); + + { + const char array[] = "\x00\x00\xc2\x81\xc2\x81"; + const std::string array_string(array, arraysize(array)); + EXPECT_TRUE(Truncated(array_string, 4, &output)); + EXPECT_EQ(output.compare(std::string("\x00\x00\xc2\x81", 4)), 0); + } + + { + const char array[] = "\x00\xc2\x81\xc2\x81"; + const std::string array_string(array, arraysize(array)); + EXPECT_TRUE(Truncated(array_string, 4, &output)); + EXPECT_EQ(output.compare(std::string("\x00\xc2\x81", 3)), 0); + } + + // Testing invalid UTF8 + EXPECT_TRUE(Truncated("\xed\xa0\x80\xed\xbf\xbf", 6, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xed\xa0\x8f", 3, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xed\xbf\xbf", 3, &output)); + EXPECT_EQ(output.compare(""), 0); + + // Testing invalid UTF8 mixed with valid UTF8 + EXPECT_FALSE(Truncated("\xe1\x80\xbf", 3, &output)); + EXPECT_EQ(output.compare("\xe1\x80\xbf"), 0); + EXPECT_FALSE(Truncated("\xf1\x80\xa0\xbf", 4, &output)); + EXPECT_EQ(output.compare("\xf1\x80\xa0\xbf"), 0); + EXPECT_FALSE(Truncated("a\xc2\x81\xe1\x80\xbf\xf1\x80\xa0\xbf", + 10, &output)); + EXPECT_EQ(output.compare("a\xc2\x81\xe1\x80\xbf\xf1\x80\xa0\xbf"), 0); + EXPECT_TRUE(Truncated("a\xc2\x81\xe1\x80\xbf\xf1""a""\x80\xa0", + 10, &output)); + EXPECT_EQ(output.compare("a\xc2\x81\xe1\x80\xbf\xf1""a"), 0); + EXPECT_FALSE(Truncated("\xef\xbb\xbf" "abc", 6, &output)); + EXPECT_EQ(output.compare("\xef\xbb\xbf" "abc"), 0); + + // Overlong sequences + EXPECT_TRUE(Truncated("\xc0\x80", 2, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xc1\x80\xc1\x81", 4, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xe0\x80\x80", 3, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xe0\x82\x80", 3, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xe0\x9f\xbf", 3, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xf0\x80\x80\x8D", 4, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xf0\x80\x82\x91", 4, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xf0\x80\xa0\x80", 4, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xf0\x8f\xbb\xbf", 4, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xf8\x80\x80\x80\xbf", 5, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xfc\x80\x80\x80\xa0\xa5", 6, &output)); + EXPECT_EQ(output.compare(""), 0); + + // Beyond U+10FFFF (the upper limit of Unicode codespace) + EXPECT_TRUE(Truncated("\xf4\x90\x80\x80", 4, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xf8\xa0\xbf\x80\xbf", 5, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xfc\x9c\xbf\x80\xbf\x80", 6, &output)); + EXPECT_EQ(output.compare(""), 0); + + // BOMs in UTF-16(BE|LE) and UTF-32(BE|LE) + EXPECT_TRUE(Truncated("\xfe\xff", 2, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xff\xfe", 2, &output)); + EXPECT_EQ(output.compare(""), 0); + + { + const char array[] = "\x00\x00\xfe\xff"; + const std::string array_string(array, arraysize(array)); + EXPECT_TRUE(Truncated(array_string, 4, &output)); + EXPECT_EQ(output.compare(std::string("\x00\x00", 2)), 0); + } + + // Variants on the previous test + { + const char array[] = "\xff\xfe\x00\x00"; + const std::string array_string(array, 4); + EXPECT_FALSE(Truncated(array_string, 4, &output)); + EXPECT_EQ(output.compare(std::string("\xff\xfe\x00\x00", 4)), 0); + } + { + const char array[] = "\xff\x00\x00\xfe"; + const std::string array_string(array, arraysize(array)); + EXPECT_TRUE(Truncated(array_string, 4, &output)); + EXPECT_EQ(output.compare(std::string("\xff\x00\x00", 3)), 0); + } + + // Non-characters : U+xxFFF[EF] where xx is 0x00 through 0x10 and + EXPECT_TRUE(Truncated("\xef\xbf\xbe", 3, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xf0\x8f\xbf\xbe", 4, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xf3\xbf\xbf\xbf", 4, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xef\xb7\x90", 3, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xef\xb7\xaf", 3, &output)); + EXPECT_EQ(output.compare(""), 0); + + // Strings in legacy encodings that are valid in UTF-8, but + // are invalid as UTF-8 in real data. + EXPECT_TRUE(Truncated("caf\xe9", 4, &output)); + EXPECT_EQ(output.compare("caf"), 0); + EXPECT_TRUE(Truncated("\xb0\xa1\xb0\xa2", 4, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_FALSE(Truncated("\xa7\x41\xa6\x6e", 4, &output)); + EXPECT_EQ(output.compare("\xa7\x41\xa6\x6e"), 0); + EXPECT_TRUE(Truncated("\xa7\x41\xa6\x6e\xd9\xee\xe4\xee", 7, + &output)); + EXPECT_EQ(output.compare("\xa7\x41\xa6\x6e"), 0); + + // Testing using the same string as input and output. + EXPECT_FALSE(Truncated(output, 4, &output)); + EXPECT_EQ(output.compare("\xa7\x41\xa6\x6e"), 0); + EXPECT_TRUE(Truncated(output, 3, &output)); + EXPECT_EQ(output.compare("\xa7\x41"), 0); + + // "abc" with U+201[CD] in windows-125[0-8] + EXPECT_TRUE(Truncated("\x93" "abc\x94", 5, &output)); + EXPECT_EQ(output.compare("\x93" "abc"), 0); + + // U+0639 U+064E U+0644 U+064E in ISO-8859-6 + EXPECT_TRUE(Truncated("\xd9\xee\xe4\xee", 4, &output)); + EXPECT_EQ(output.compare(""), 0); + + // U+03B3 U+03B5 U+03B9 U+03AC in ISO-8859-7 + EXPECT_TRUE(Truncated("\xe3\xe5\xe9\xdC", 4, &output)); + EXPECT_EQ(output.compare(""), 0); +} + +TEST(StringUtilTest, TrimWhitespace) { + string16 output; // Allow contents to carry over to next testcase + for (size_t i = 0; i < arraysize(trim_cases); ++i) { + const trim_case& value = trim_cases[i]; + EXPECT_EQ(value.return_value, + TrimWhitespace(WideToUTF16(value.input), value.positions, + &output)); + EXPECT_EQ(WideToUTF16(value.output), output); + } + + // Test that TrimWhitespace() can take the same string for input and output + output = ASCIIToUTF16(" This is a test \r\n"); + EXPECT_EQ(TRIM_ALL, TrimWhitespace(output, TRIM_ALL, &output)); + EXPECT_EQ(ASCIIToUTF16("This is a test"), output); + + // Once more, but with a string of whitespace + output = ASCIIToUTF16(" \r\n"); + EXPECT_EQ(TRIM_ALL, TrimWhitespace(output, TRIM_ALL, &output)); + EXPECT_EQ(string16(), output); + + std::string output_ascii; + for (size_t i = 0; i < arraysize(trim_cases_ascii); ++i) { + const trim_case_ascii& value = trim_cases_ascii[i]; + EXPECT_EQ(value.return_value, + TrimWhitespace(value.input, value.positions, &output_ascii)); + EXPECT_EQ(value.output, output_ascii); + } +} + +static const struct collapse_case { + const wchar_t* input; + const bool trim; + const wchar_t* output; +} collapse_cases[] = { + {L" Google Video ", false, L"Google Video"}, + {L"Google Video", false, L"Google Video"}, + {L"", false, L""}, + {L" ", false, L""}, + {L"\t\rTest String\n", false, L"Test String"}, + {L"\x2002Test String\x00A0\x3000", false, L"Test String"}, + {L" Test \n \t String ", false, L"Test String"}, + {L"\x2002Test\x1680 \x2028 \tString\x00A0\x3000", false, L"Test String"}, + {L" Test String", false, L"Test String"}, + {L"Test String ", false, L"Test String"}, + {L"Test String", false, L"Test String"}, + {L"", true, L""}, + {L"\n", true, L""}, + {L" \r ", true, L""}, + {L"\nFoo", true, L"Foo"}, + {L"\r Foo ", true, L"Foo"}, + {L" Foo bar ", true, L"Foo bar"}, + {L" \tFoo bar \n", true, L"Foo bar"}, + {L" a \r b\n c \r\n d \t\re \t f \n ", true, L"abcde f"}, +}; + +TEST(StringUtilTest, CollapseWhitespace) { + for (size_t i = 0; i < arraysize(collapse_cases); ++i) { + const collapse_case& value = collapse_cases[i]; + EXPECT_EQ(value.output, CollapseWhitespace(value.input, value.trim)); + } +} + +static const struct collapse_case_ascii { + const char* input; + const bool trim; + const char* output; +} collapse_cases_ascii[] = { + {" Google Video ", false, "Google Video"}, + {"Google Video", false, "Google Video"}, + {"", false, ""}, + {" ", false, ""}, + {"\t\rTest String\n", false, "Test String"}, + {" Test \n \t String ", false, "Test String"}, + {" Test String", false, "Test String"}, + {"Test String ", false, "Test String"}, + {"Test String", false, "Test String"}, + {"", true, ""}, + {"\n", true, ""}, + {" \r ", true, ""}, + {"\nFoo", true, "Foo"}, + {"\r Foo ", true, "Foo"}, + {" Foo bar ", true, "Foo bar"}, + {" \tFoo bar \n", true, "Foo bar"}, + {" a \r b\n c \r\n d \t\re \t f \n ", true, "abcde f"}, +}; + +TEST(StringUtilTest, CollapseWhitespaceASCII) { + for (size_t i = 0; i < arraysize(collapse_cases_ascii); ++i) { + const collapse_case_ascii& value = collapse_cases_ascii[i]; + EXPECT_EQ(value.output, CollapseWhitespaceASCII(value.input, value.trim)); + } +} + +TEST(StringUtilTest, ContainsOnlyWhitespaceASCII) { + EXPECT_TRUE(ContainsOnlyWhitespaceASCII(std::string())); + EXPECT_TRUE(ContainsOnlyWhitespaceASCII(" ")); + EXPECT_TRUE(ContainsOnlyWhitespaceASCII("\t")); + EXPECT_TRUE(ContainsOnlyWhitespaceASCII("\t \r \n ")); + EXPECT_FALSE(ContainsOnlyWhitespaceASCII("a")); + EXPECT_FALSE(ContainsOnlyWhitespaceASCII("\thello\r \n ")); +} + +TEST(StringUtilTest, ContainsOnlyWhitespace) { + EXPECT_TRUE(ContainsOnlyWhitespace(string16())); + EXPECT_TRUE(ContainsOnlyWhitespace(ASCIIToUTF16(" "))); + EXPECT_TRUE(ContainsOnlyWhitespace(ASCIIToUTF16("\t"))); + EXPECT_TRUE(ContainsOnlyWhitespace(ASCIIToUTF16("\t \r \n "))); + EXPECT_FALSE(ContainsOnlyWhitespace(ASCIIToUTF16("a"))); + EXPECT_FALSE(ContainsOnlyWhitespace(ASCIIToUTF16("\thello\r \n "))); +} + +TEST(StringUtilTest, IsStringUTF8) { + EXPECT_TRUE(IsStringUTF8("abc")); + EXPECT_TRUE(IsStringUTF8("\xc2\x81")); + EXPECT_TRUE(IsStringUTF8("\xe1\x80\xbf")); + EXPECT_TRUE(IsStringUTF8("\xf1\x80\xa0\xbf")); + EXPECT_TRUE(IsStringUTF8("a\xc2\x81\xe1\x80\xbf\xf1\x80\xa0\xbf")); + EXPECT_TRUE(IsStringUTF8("\xef\xbb\xbf" "abc")); // UTF-8 BOM + + // surrogate code points + EXPECT_FALSE(IsStringUTF8("\xed\xa0\x80\xed\xbf\xbf")); + EXPECT_FALSE(IsStringUTF8("\xed\xa0\x8f")); + EXPECT_FALSE(IsStringUTF8("\xed\xbf\xbf")); + + // overlong sequences + EXPECT_FALSE(IsStringUTF8("\xc0\x80")); // U+0000 + EXPECT_FALSE(IsStringUTF8("\xc1\x80\xc1\x81")); // "AB" + EXPECT_FALSE(IsStringUTF8("\xe0\x80\x80")); // U+0000 + EXPECT_FALSE(IsStringUTF8("\xe0\x82\x80")); // U+0080 + EXPECT_FALSE(IsStringUTF8("\xe0\x9f\xbf")); // U+07ff + EXPECT_FALSE(IsStringUTF8("\xf0\x80\x80\x8D")); // U+000D + EXPECT_FALSE(IsStringUTF8("\xf0\x80\x82\x91")); // U+0091 + EXPECT_FALSE(IsStringUTF8("\xf0\x80\xa0\x80")); // U+0800 + EXPECT_FALSE(IsStringUTF8("\xf0\x8f\xbb\xbf")); // U+FEFF (BOM) + EXPECT_FALSE(IsStringUTF8("\xf8\x80\x80\x80\xbf")); // U+003F + EXPECT_FALSE(IsStringUTF8("\xfc\x80\x80\x80\xa0\xa5")); // U+00A5 + + // Beyond U+10FFFF (the upper limit of Unicode codespace) + EXPECT_FALSE(IsStringUTF8("\xf4\x90\x80\x80")); // U+110000 + EXPECT_FALSE(IsStringUTF8("\xf8\xa0\xbf\x80\xbf")); // 5 bytes + EXPECT_FALSE(IsStringUTF8("\xfc\x9c\xbf\x80\xbf\x80")); // 6 bytes + + // BOMs in UTF-16(BE|LE) and UTF-32(BE|LE) + EXPECT_FALSE(IsStringUTF8("\xfe\xff")); + EXPECT_FALSE(IsStringUTF8("\xff\xfe")); + EXPECT_FALSE(IsStringUTF8(std::string("\x00\x00\xfe\xff", 4))); + EXPECT_FALSE(IsStringUTF8("\xff\xfe\x00\x00")); + + // Non-characters : U+xxFFF[EF] where xx is 0x00 through 0x10 and + EXPECT_FALSE(IsStringUTF8("\xef\xbf\xbe")); // U+FFFE) + EXPECT_FALSE(IsStringUTF8("\xf0\x8f\xbf\xbe")); // U+1FFFE + EXPECT_FALSE(IsStringUTF8("\xf3\xbf\xbf\xbf")); // U+10FFFF + EXPECT_FALSE(IsStringUTF8("\xef\xb7\x90")); // U+FDD0 + EXPECT_FALSE(IsStringUTF8("\xef\xb7\xaf")); // U+FDEF + // Strings in legacy encodings. We can certainly make up strings + // in a legacy encoding that are valid in UTF-8, but in real data, + // most of them are invalid as UTF-8. + EXPECT_FALSE(IsStringUTF8("caf\xe9")); // cafe with U+00E9 in ISO-8859-1 + EXPECT_FALSE(IsStringUTF8("\xb0\xa1\xb0\xa2")); // U+AC00, U+AC001 in EUC-KR + EXPECT_FALSE(IsStringUTF8("\xa7\x41\xa6\x6e")); // U+4F60 U+597D in Big5 + // "abc" with U+201[CD] in windows-125[0-8] + EXPECT_FALSE(IsStringUTF8("\x93" "abc\x94")); + // U+0639 U+064E U+0644 U+064E in ISO-8859-6 + EXPECT_FALSE(IsStringUTF8("\xd9\xee\xe4\xee")); + // U+03B3 U+03B5 U+03B9 U+03AC in ISO-8859-7 + EXPECT_FALSE(IsStringUTF8("\xe3\xe5\xe9\xdC")); + + // Check that we support Embedded Nulls. The first uses the canonical UTF-8 + // representation, and the second uses a 2-byte sequence. The second version + // is invalid UTF-8 since UTF-8 states that the shortest encoding for a + // given codepoint must be used. + static const char kEmbeddedNull[] = "embedded\0null"; + EXPECT_TRUE(IsStringUTF8( + std::string(kEmbeddedNull, sizeof(kEmbeddedNull)))); + EXPECT_FALSE(IsStringUTF8("embedded\xc0\x80U+0000")); +} + +TEST(StringUtilTest, ConvertASCII) { + static const char* char_cases[] = { + "Google Video", + "Hello, world\n", + "0123ABCDwxyz \a\b\t\r\n!+,.~" + }; + + static const wchar_t* const wchar_cases[] = { + L"Google Video", + L"Hello, world\n", + L"0123ABCDwxyz \a\b\t\r\n!+,.~" + }; + + for (size_t i = 0; i < arraysize(char_cases); ++i) { + EXPECT_TRUE(IsStringASCII(char_cases[i])); + std::wstring wide = ASCIIToWide(char_cases[i]); + EXPECT_EQ(wchar_cases[i], wide); + + EXPECT_TRUE(IsStringASCII(wchar_cases[i])); + std::string ascii = WideToASCII(wchar_cases[i]); + EXPECT_EQ(char_cases[i], ascii); + } + + EXPECT_FALSE(IsStringASCII("Google \x80Video")); + EXPECT_FALSE(IsStringASCII(L"Google \x80Video")); + + // Convert empty strings. + std::wstring wempty; + std::string empty; + EXPECT_EQ(empty, WideToASCII(wempty)); + EXPECT_EQ(wempty, ASCIIToWide(empty)); + + // Convert strings with an embedded NUL character. + const char chars_with_nul[] = "test\0string"; + const int length_with_nul = arraysize(chars_with_nul) - 1; + std::string string_with_nul(chars_with_nul, length_with_nul); + std::wstring wide_with_nul = ASCIIToWide(string_with_nul); + EXPECT_EQ(static_cast(length_with_nul), + wide_with_nul.length()); + std::string narrow_with_nul = WideToASCII(wide_with_nul); + EXPECT_EQ(static_cast(length_with_nul), + narrow_with_nul.length()); + EXPECT_EQ(0, string_with_nul.compare(narrow_with_nul)); +} + +TEST(StringUtilTest, ToUpperASCII) { + EXPECT_EQ('C', ToUpperASCII('C')); + EXPECT_EQ('C', ToUpperASCII('c')); + EXPECT_EQ('2', ToUpperASCII('2')); + + EXPECT_EQ(L'C', ToUpperASCII(L'C')); + EXPECT_EQ(L'C', ToUpperASCII(L'c')); + EXPECT_EQ(L'2', ToUpperASCII(L'2')); + + std::string in_place_a("Cc2"); + StringToUpperASCII(&in_place_a); + EXPECT_EQ("CC2", in_place_a); + + std::wstring in_place_w(L"Cc2"); + StringToUpperASCII(&in_place_w); + EXPECT_EQ(L"CC2", in_place_w); + + std::string original_a("Cc2"); + std::string upper_a = StringToUpperASCII(original_a); + EXPECT_EQ("CC2", upper_a); + + std::wstring original_w(L"Cc2"); + std::wstring upper_w = StringToUpperASCII(original_w); + EXPECT_EQ(L"CC2", upper_w); +} + +TEST(StringUtilTest, LowerCaseEqualsASCII) { + static const struct { + const wchar_t* src_w; + const char* src_a; + const char* dst; + } lowercase_cases[] = { + { L"FoO", "FoO", "foo" }, + { L"foo", "foo", "foo" }, + { L"FOO", "FOO", "foo" }, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(lowercase_cases); ++i) { + EXPECT_TRUE(LowerCaseEqualsASCII(lowercase_cases[i].src_w, + lowercase_cases[i].dst)); + EXPECT_TRUE(LowerCaseEqualsASCII(lowercase_cases[i].src_a, + lowercase_cases[i].dst)); + } +} + +TEST(StringUtilTest, FormatBytesUnlocalized) { + static const struct { + int64 bytes; + const char* expected; + } cases[] = { + // Expected behavior: we show one post-decimal digit when we have + // under two pre-decimal digits, except in cases where it makes no + // sense (zero or bytes). + // Since we switch units once we cross the 1000 mark, this keeps + // the display of file sizes or bytes consistently around three + // digits. + {0, "0 B"}, + {512, "512 B"}, + {1024*1024, "1.0 MB"}, + {1024*1024*1024, "1.0 GB"}, + {10LL*1024*1024*1024, "10.0 GB"}, + {99LL*1024*1024*1024, "99.0 GB"}, + {105LL*1024*1024*1024, "105 GB"}, + {105LL*1024*1024*1024 + 500LL*1024*1024, "105 GB"}, + {~(1LL<<63), "8192 PB"}, + + {99*1024 + 103, "99.1 kB"}, + {1024*1024 + 103, "1.0 MB"}, + {1024*1024 + 205 * 1024, "1.2 MB"}, + {1024*1024*1024 + (927 * 1024*1024), "1.9 GB"}, + {10LL*1024*1024*1024, "10.0 GB"}, + {100LL*1024*1024*1024, "100 GB"}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + EXPECT_EQ(ASCIIToUTF16(cases[i].expected), + FormatBytesUnlocalized(cases[i].bytes)); + } +} +TEST(StringUtilTest, ReplaceSubstringsAfterOffset) { + static const struct { + const char* str; + string16::size_type start_offset; + const char* find_this; + const char* replace_with; + const char* expected; + } cases[] = { + {"aaa", 0, "a", "b", "bbb"}, + {"abb", 0, "ab", "a", "ab"}, + {"Removing some substrings inging", 0, "ing", "", "Remov some substrs "}, + {"Not found", 0, "x", "0", "Not found"}, + {"Not found again", 5, "x", "0", "Not found again"}, + {" Making it much longer ", 0, " ", "Four score and seven years ago", + "Four score and seven years agoMakingFour score and seven years agoit" + "Four score and seven years agomuchFour score and seven years agolonger" + "Four score and seven years ago"}, + {"Invalid offset", 9999, "t", "foobar", "Invalid offset"}, + {"Replace me only me once", 9, "me ", "", "Replace me only once"}, + {"abababab", 2, "ab", "c", "abccc"}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); i++) { + string16 str = ASCIIToUTF16(cases[i].str); + ReplaceSubstringsAfterOffset(&str, cases[i].start_offset, + ASCIIToUTF16(cases[i].find_this), + ASCIIToUTF16(cases[i].replace_with)); + EXPECT_EQ(ASCIIToUTF16(cases[i].expected), str); + } +} + +TEST(StringUtilTest, ReplaceFirstSubstringAfterOffset) { + static const struct { + const char* str; + string16::size_type start_offset; + const char* find_this; + const char* replace_with; + const char* expected; + } cases[] = { + {"aaa", 0, "a", "b", "baa"}, + {"abb", 0, "ab", "a", "ab"}, + {"Removing some substrings inging", 0, "ing", "", + "Remov some substrings inging"}, + {"Not found", 0, "x", "0", "Not found"}, + {"Not found again", 5, "x", "0", "Not found again"}, + {" Making it much longer ", 0, " ", "Four score and seven years ago", + "Four score and seven years agoMaking it much longer "}, + {"Invalid offset", 9999, "t", "foobar", "Invalid offset"}, + {"Replace me only me once", 4, "me ", "", "Replace only me once"}, + {"abababab", 2, "ab", "c", "abcabab"}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); i++) { + string16 str = ASCIIToUTF16(cases[i].str); + ReplaceFirstSubstringAfterOffset(&str, cases[i].start_offset, + ASCIIToUTF16(cases[i].find_this), + ASCIIToUTF16(cases[i].replace_with)); + EXPECT_EQ(ASCIIToUTF16(cases[i].expected), str); + } +} + +TEST(StringUtilTest, HexDigitToInt) { + EXPECT_EQ(0, HexDigitToInt('0')); + EXPECT_EQ(1, HexDigitToInt('1')); + EXPECT_EQ(2, HexDigitToInt('2')); + EXPECT_EQ(3, HexDigitToInt('3')); + EXPECT_EQ(4, HexDigitToInt('4')); + EXPECT_EQ(5, HexDigitToInt('5')); + EXPECT_EQ(6, HexDigitToInt('6')); + EXPECT_EQ(7, HexDigitToInt('7')); + EXPECT_EQ(8, HexDigitToInt('8')); + EXPECT_EQ(9, HexDigitToInt('9')); + EXPECT_EQ(10, HexDigitToInt('A')); + EXPECT_EQ(11, HexDigitToInt('B')); + EXPECT_EQ(12, HexDigitToInt('C')); + EXPECT_EQ(13, HexDigitToInt('D')); + EXPECT_EQ(14, HexDigitToInt('E')); + EXPECT_EQ(15, HexDigitToInt('F')); + + // Verify the lower case as well. + EXPECT_EQ(10, HexDigitToInt('a')); + EXPECT_EQ(11, HexDigitToInt('b')); + EXPECT_EQ(12, HexDigitToInt('c')); + EXPECT_EQ(13, HexDigitToInt('d')); + EXPECT_EQ(14, HexDigitToInt('e')); + EXPECT_EQ(15, HexDigitToInt('f')); +} + +// This checks where we can use the assignment operator for a va_list. We need +// a way to do this since Visual C doesn't support va_copy, but assignment on +// va_list is not guaranteed to be a copy. See StringAppendVT which uses this +// capability. +static void VariableArgsFunc(const char* format, ...) { + va_list org; + va_start(org, format); + + va_list dup; + GG_VA_COPY(dup, org); + int i1 = va_arg(org, int); + int j1 = va_arg(org, int); + char* s1 = va_arg(org, char*); + double d1 = va_arg(org, double); + va_end(org); + + int i2 = va_arg(dup, int); + int j2 = va_arg(dup, int); + char* s2 = va_arg(dup, char*); + double d2 = va_arg(dup, double); + + EXPECT_EQ(i1, i2); + EXPECT_EQ(j1, j2); + EXPECT_STREQ(s1, s2); + EXPECT_EQ(d1, d2); + + va_end(dup); +} + +TEST(StringUtilTest, VAList) { + VariableArgsFunc("%d %d %s %lf", 45, 92, "This is interesting", 9.21); +} + +// Test for Tokenize +template +void TokenizeTest() { + std::vector r; + size_t size; + + size = Tokenize(STR("This is a string"), STR(" "), &r); + EXPECT_EQ(4U, size); + ASSERT_EQ(4U, r.size()); + EXPECT_EQ(r[0], STR("This")); + EXPECT_EQ(r[1], STR("is")); + EXPECT_EQ(r[2], STR("a")); + EXPECT_EQ(r[3], STR("string")); + r.clear(); + + size = Tokenize(STR("one,two,three"), STR(","), &r); + EXPECT_EQ(3U, size); + ASSERT_EQ(3U, r.size()); + EXPECT_EQ(r[0], STR("one")); + EXPECT_EQ(r[1], STR("two")); + EXPECT_EQ(r[2], STR("three")); + r.clear(); + + size = Tokenize(STR("one,two:three;four"), STR(",:"), &r); + EXPECT_EQ(3U, size); + ASSERT_EQ(3U, r.size()); + EXPECT_EQ(r[0], STR("one")); + EXPECT_EQ(r[1], STR("two")); + EXPECT_EQ(r[2], STR("three;four")); + r.clear(); + + size = Tokenize(STR("one,two:three;four"), STR(";,:"), &r); + EXPECT_EQ(4U, size); + ASSERT_EQ(4U, r.size()); + EXPECT_EQ(r[0], STR("one")); + EXPECT_EQ(r[1], STR("two")); + EXPECT_EQ(r[2], STR("three")); + EXPECT_EQ(r[3], STR("four")); + r.clear(); + + size = Tokenize(STR("one, two, three"), STR(","), &r); + EXPECT_EQ(3U, size); + ASSERT_EQ(3U, r.size()); + EXPECT_EQ(r[0], STR("one")); + EXPECT_EQ(r[1], STR(" two")); + EXPECT_EQ(r[2], STR(" three")); + r.clear(); + + size = Tokenize(STR("one, two, three, "), STR(","), &r); + EXPECT_EQ(4U, size); + ASSERT_EQ(4U, r.size()); + EXPECT_EQ(r[0], STR("one")); + EXPECT_EQ(r[1], STR(" two")); + EXPECT_EQ(r[2], STR(" three")); + EXPECT_EQ(r[3], STR(" ")); + r.clear(); + + size = Tokenize(STR("one, two, three,"), STR(","), &r); + EXPECT_EQ(3U, size); + ASSERT_EQ(3U, r.size()); + EXPECT_EQ(r[0], STR("one")); + EXPECT_EQ(r[1], STR(" two")); + EXPECT_EQ(r[2], STR(" three")); + r.clear(); + + size = Tokenize(STR(), STR(","), &r); + EXPECT_EQ(0U, size); + ASSERT_EQ(0U, r.size()); + r.clear(); + + size = Tokenize(STR(","), STR(","), &r); + EXPECT_EQ(0U, size); + ASSERT_EQ(0U, r.size()); + r.clear(); + + size = Tokenize(STR(",;:."), STR(".:;,"), &r); + EXPECT_EQ(0U, size); + ASSERT_EQ(0U, r.size()); + r.clear(); + + size = Tokenize(STR("\t\ta\t"), STR("\t"), &r); + EXPECT_EQ(1U, size); + ASSERT_EQ(1U, r.size()); + EXPECT_EQ(r[0], STR("a")); + r.clear(); + + size = Tokenize(STR("\ta\t\nb\tcc"), STR("\n"), &r); + EXPECT_EQ(2U, size); + ASSERT_EQ(2U, r.size()); + EXPECT_EQ(r[0], STR("\ta\t")); + EXPECT_EQ(r[1], STR("b\tcc")); + r.clear(); +} + +TEST(StringUtilTest, TokenizeStdString) { + TokenizeTest(); +} + +TEST(StringUtilTest, TokenizeStringPiece) { + TokenizeTest(); +} + +// Test for JoinString +TEST(StringUtilTest, JoinString) { + std::vector in; + EXPECT_EQ("", JoinString(in, ',')); + + in.push_back("a"); + EXPECT_EQ("a", JoinString(in, ',')); + + in.push_back("b"); + in.push_back("c"); + EXPECT_EQ("a,b,c", JoinString(in, ',')); + + in.push_back(std::string()); + EXPECT_EQ("a,b,c,", JoinString(in, ',')); + in.push_back(" "); + EXPECT_EQ("a|b|c|| ", JoinString(in, '|')); +} + +// Test for JoinString overloaded with std::string separator +TEST(StringUtilTest, JoinStringWithString) { + std::string separator(", "); + std::vector parts; + EXPECT_EQ(std::string(), JoinString(parts, separator)); + + parts.push_back("a"); + EXPECT_EQ("a", JoinString(parts, separator)); + + parts.push_back("b"); + parts.push_back("c"); + EXPECT_EQ("a, b, c", JoinString(parts, separator)); + + parts.push_back(std::string()); + EXPECT_EQ("a, b, c, ", JoinString(parts, separator)); + parts.push_back(" "); + EXPECT_EQ("a|b|c|| ", JoinString(parts, "|")); +} + +// Test for JoinString overloaded with string16 separator +TEST(StringUtilTest, JoinStringWithString16) { + string16 separator = ASCIIToUTF16(", "); + std::vector parts; + EXPECT_EQ(string16(), JoinString(parts, separator)); + + parts.push_back(ASCIIToUTF16("a")); + EXPECT_EQ(ASCIIToUTF16("a"), JoinString(parts, separator)); + + parts.push_back(ASCIIToUTF16("b")); + parts.push_back(ASCIIToUTF16("c")); + EXPECT_EQ(ASCIIToUTF16("a, b, c"), JoinString(parts, separator)); + + parts.push_back(ASCIIToUTF16("")); + EXPECT_EQ(ASCIIToUTF16("a, b, c, "), JoinString(parts, separator)); + parts.push_back(ASCIIToUTF16(" ")); + EXPECT_EQ(ASCIIToUTF16("a|b|c|| "), JoinString(parts, ASCIIToUTF16("|"))); +} + +TEST(StringUtilTest, StartsWith) { + EXPECT_TRUE(StartsWithASCII("javascript:url", "javascript", true)); + EXPECT_FALSE(StartsWithASCII("JavaScript:url", "javascript", true)); + EXPECT_TRUE(StartsWithASCII("javascript:url", "javascript", false)); + EXPECT_TRUE(StartsWithASCII("JavaScript:url", "javascript", false)); + EXPECT_FALSE(StartsWithASCII("java", "javascript", true)); + EXPECT_FALSE(StartsWithASCII("java", "javascript", false)); + EXPECT_FALSE(StartsWithASCII(std::string(), "javascript", false)); + EXPECT_FALSE(StartsWithASCII(std::string(), "javascript", true)); + EXPECT_TRUE(StartsWithASCII("java", std::string(), false)); + EXPECT_TRUE(StartsWithASCII("java", std::string(), true)); + + EXPECT_TRUE(StartsWith(L"javascript:url", L"javascript", true)); + EXPECT_FALSE(StartsWith(L"JavaScript:url", L"javascript", true)); + EXPECT_TRUE(StartsWith(L"javascript:url", L"javascript", false)); + EXPECT_TRUE(StartsWith(L"JavaScript:url", L"javascript", false)); + EXPECT_FALSE(StartsWith(L"java", L"javascript", true)); + EXPECT_FALSE(StartsWith(L"java", L"javascript", false)); + EXPECT_FALSE(StartsWith(std::wstring(), L"javascript", false)); + EXPECT_FALSE(StartsWith(std::wstring(), L"javascript", true)); + EXPECT_TRUE(StartsWith(L"java", std::wstring(), false)); + EXPECT_TRUE(StartsWith(L"java", std::wstring(), true)); +} + +TEST(StringUtilTest, EndsWith) { + EXPECT_TRUE(EndsWith(L"Foo.plugin", L".plugin", true)); + EXPECT_FALSE(EndsWith(L"Foo.Plugin", L".plugin", true)); + EXPECT_TRUE(EndsWith(L"Foo.plugin", L".plugin", false)); + EXPECT_TRUE(EndsWith(L"Foo.Plugin", L".plugin", false)); + EXPECT_FALSE(EndsWith(L".plug", L".plugin", true)); + EXPECT_FALSE(EndsWith(L".plug", L".plugin", false)); + EXPECT_FALSE(EndsWith(L"Foo.plugin Bar", L".plugin", true)); + EXPECT_FALSE(EndsWith(L"Foo.plugin Bar", L".plugin", false)); + EXPECT_FALSE(EndsWith(std::wstring(), L".plugin", false)); + EXPECT_FALSE(EndsWith(std::wstring(), L".plugin", true)); + EXPECT_TRUE(EndsWith(L"Foo.plugin", std::wstring(), false)); + EXPECT_TRUE(EndsWith(L"Foo.plugin", std::wstring(), true)); + EXPECT_TRUE(EndsWith(L".plugin", L".plugin", false)); + EXPECT_TRUE(EndsWith(L".plugin", L".plugin", true)); + EXPECT_TRUE(EndsWith(std::wstring(), std::wstring(), false)); + EXPECT_TRUE(EndsWith(std::wstring(), std::wstring(), true)); +} + +TEST(StringUtilTest, GetStringFWithOffsets) { + std::vector subst; + subst.push_back(ASCIIToUTF16("1")); + subst.push_back(ASCIIToUTF16("2")); + std::vector offsets; + + ReplaceStringPlaceholders(ASCIIToUTF16("Hello, $1. Your number is $2."), + subst, + &offsets); + EXPECT_EQ(2U, offsets.size()); + EXPECT_EQ(7U, offsets[0]); + EXPECT_EQ(25U, offsets[1]); + offsets.clear(); + + ReplaceStringPlaceholders(ASCIIToUTF16("Hello, $2. Your number is $1."), + subst, + &offsets); + EXPECT_EQ(2U, offsets.size()); + EXPECT_EQ(25U, offsets[0]); + EXPECT_EQ(7U, offsets[1]); + offsets.clear(); +} + +TEST(StringUtilTest, ReplaceStringPlaceholdersTooFew) { + // Test whether replacestringplaceholders works as expected when there + // are fewer inputs than outputs. + std::vector subst; + subst.push_back(ASCIIToUTF16("9a")); + subst.push_back(ASCIIToUTF16("8b")); + subst.push_back(ASCIIToUTF16("7c")); + + string16 formatted = + ReplaceStringPlaceholders( + ASCIIToUTF16("$1a,$2b,$3c,$4d,$5e,$6f,$1g,$2h,$3i"), subst, NULL); + + EXPECT_EQ(formatted, ASCIIToUTF16("9aa,8bb,7cc,d,e,f,9ag,8bh,7ci")); +} + +TEST(StringUtilTest, ReplaceStringPlaceholders) { + std::vector subst; + subst.push_back(ASCIIToUTF16("9a")); + subst.push_back(ASCIIToUTF16("8b")); + subst.push_back(ASCIIToUTF16("7c")); + subst.push_back(ASCIIToUTF16("6d")); + subst.push_back(ASCIIToUTF16("5e")); + subst.push_back(ASCIIToUTF16("4f")); + subst.push_back(ASCIIToUTF16("3g")); + subst.push_back(ASCIIToUTF16("2h")); + subst.push_back(ASCIIToUTF16("1i")); + + string16 formatted = + ReplaceStringPlaceholders( + ASCIIToUTF16("$1a,$2b,$3c,$4d,$5e,$6f,$7g,$8h,$9i"), subst, NULL); + + EXPECT_EQ(formatted, ASCIIToUTF16("9aa,8bb,7cc,6dd,5ee,4ff,3gg,2hh,1ii")); +} + +TEST(StringUtilTest, ReplaceStringPlaceholdersMoreThan9Replacements) { + std::vector subst; + subst.push_back(ASCIIToUTF16("9a")); + subst.push_back(ASCIIToUTF16("8b")); + subst.push_back(ASCIIToUTF16("7c")); + subst.push_back(ASCIIToUTF16("6d")); + subst.push_back(ASCIIToUTF16("5e")); + subst.push_back(ASCIIToUTF16("4f")); + subst.push_back(ASCIIToUTF16("3g")); + subst.push_back(ASCIIToUTF16("2h")); + subst.push_back(ASCIIToUTF16("1i")); + subst.push_back(ASCIIToUTF16("0j")); + subst.push_back(ASCIIToUTF16("-1k")); + subst.push_back(ASCIIToUTF16("-2l")); + subst.push_back(ASCIIToUTF16("-3m")); + subst.push_back(ASCIIToUTF16("-4n")); + + string16 formatted = + ReplaceStringPlaceholders( + ASCIIToUTF16("$1a,$2b,$3c,$4d,$5e,$6f,$7g,$8h,$9i," + "$10j,$11k,$12l,$13m,$14n,$1"), subst, NULL); + + EXPECT_EQ(formatted, ASCIIToUTF16("9aa,8bb,7cc,6dd,5ee,4ff,3gg,2hh," + "1ii,0jj,-1kk,-2ll,-3mm,-4nn,9a")); +} + +TEST(StringUtilTest, StdStringReplaceStringPlaceholders) { + std::vector subst; + subst.push_back("9a"); + subst.push_back("8b"); + subst.push_back("7c"); + subst.push_back("6d"); + subst.push_back("5e"); + subst.push_back("4f"); + subst.push_back("3g"); + subst.push_back("2h"); + subst.push_back("1i"); + + std::string formatted = + ReplaceStringPlaceholders( + "$1a,$2b,$3c,$4d,$5e,$6f,$7g,$8h,$9i", subst, NULL); + + EXPECT_EQ(formatted, "9aa,8bb,7cc,6dd,5ee,4ff,3gg,2hh,1ii"); +} + +TEST(StringUtilTest, ReplaceStringPlaceholdersConsecutiveDollarSigns) { + std::vector subst; + subst.push_back("a"); + subst.push_back("b"); + subst.push_back("c"); + EXPECT_EQ(ReplaceStringPlaceholders("$$1 $$$2 $$$$3", subst, NULL), + "$1 $$2 $$$3"); +} + +TEST(StringUtilTest, MatchPatternTest) { + EXPECT_TRUE(MatchPattern("www.google.com", "*.com")); + EXPECT_TRUE(MatchPattern("www.google.com", "*")); + EXPECT_FALSE(MatchPattern("www.google.com", "www*.g*.org")); + EXPECT_TRUE(MatchPattern("Hello", "H?l?o")); + EXPECT_FALSE(MatchPattern("www.google.com", "http://*)")); + EXPECT_FALSE(MatchPattern("www.msn.com", "*.COM")); + EXPECT_TRUE(MatchPattern("Hello*1234", "He??o\\*1*")); + EXPECT_FALSE(MatchPattern("", "*.*")); + EXPECT_TRUE(MatchPattern("", "*")); + EXPECT_TRUE(MatchPattern("", "?")); + EXPECT_TRUE(MatchPattern("", "")); + EXPECT_FALSE(MatchPattern("Hello", "")); + EXPECT_TRUE(MatchPattern("Hello*", "Hello*")); + // Stop after a certain recursion depth. + EXPECT_FALSE(MatchPattern("123456789012345678", "?????????????????*")); + + // Test UTF8 matching. + EXPECT_TRUE(MatchPattern("heart: \xe2\x99\xa0", "*\xe2\x99\xa0")); + EXPECT_TRUE(MatchPattern("heart: \xe2\x99\xa0.", "heart: ?.")); + EXPECT_TRUE(MatchPattern("hearts: \xe2\x99\xa0\xe2\x99\xa0", "*")); + // Invalid sequences should be handled as a single invalid character. + EXPECT_TRUE(MatchPattern("invalid: \xef\xbf\xbe", "invalid: ?")); + // If the pattern has invalid characters, it shouldn't match anything. + EXPECT_FALSE(MatchPattern("\xf4\x90\x80\x80", "\xf4\x90\x80\x80")); + + // Test UTF16 character matching. + EXPECT_TRUE(MatchPattern(UTF8ToUTF16("www.google.com"), + UTF8ToUTF16("*.com"))); + EXPECT_TRUE(MatchPattern(UTF8ToUTF16("Hello*1234"), + UTF8ToUTF16("He??o\\*1*"))); + + // This test verifies that consecutive wild cards are collapsed into 1 + // wildcard (when this doesn't occur, MatchPattern reaches it's maximum + // recursion depth). + EXPECT_TRUE(MatchPattern(UTF8ToUTF16("Hello"), + UTF8ToUTF16("He********************************o"))); +} + +TEST(StringUtilTest, LcpyTest) { + // Test the normal case where we fit in our buffer. + { + char dst[10]; + wchar_t wdst[10]; + EXPECT_EQ(7U, base::strlcpy(dst, "abcdefg", arraysize(dst))); + EXPECT_EQ(0, memcmp(dst, "abcdefg", 8)); + EXPECT_EQ(7U, base::wcslcpy(wdst, L"abcdefg", arraysize(wdst))); + EXPECT_EQ(0, memcmp(wdst, L"abcdefg", sizeof(wchar_t) * 8)); + } + + // Test dst_size == 0, nothing should be written to |dst| and we should + // have the equivalent of strlen(src). + { + char dst[2] = {1, 2}; + wchar_t wdst[2] = {1, 2}; + EXPECT_EQ(7U, base::strlcpy(dst, "abcdefg", 0)); + EXPECT_EQ(1, dst[0]); + EXPECT_EQ(2, dst[1]); + EXPECT_EQ(7U, base::wcslcpy(wdst, L"abcdefg", 0)); +#if defined(WCHAR_T_IS_UNSIGNED) + EXPECT_EQ(1U, wdst[0]); + EXPECT_EQ(2U, wdst[1]); +#else + EXPECT_EQ(1, wdst[0]); + EXPECT_EQ(2, wdst[1]); +#endif + } + + // Test the case were we _just_ competely fit including the null. + { + char dst[8]; + wchar_t wdst[8]; + EXPECT_EQ(7U, base::strlcpy(dst, "abcdefg", arraysize(dst))); + EXPECT_EQ(0, memcmp(dst, "abcdefg", 8)); + EXPECT_EQ(7U, base::wcslcpy(wdst, L"abcdefg", arraysize(wdst))); + EXPECT_EQ(0, memcmp(wdst, L"abcdefg", sizeof(wchar_t) * 8)); + } + + // Test the case were we we are one smaller, so we can't fit the null. + { + char dst[7]; + wchar_t wdst[7]; + EXPECT_EQ(7U, base::strlcpy(dst, "abcdefg", arraysize(dst))); + EXPECT_EQ(0, memcmp(dst, "abcdef", 7)); + EXPECT_EQ(7U, base::wcslcpy(wdst, L"abcdefg", arraysize(wdst))); + EXPECT_EQ(0, memcmp(wdst, L"abcdef", sizeof(wchar_t) * 7)); + } + + // Test the case were we are just too small. + { + char dst[3]; + wchar_t wdst[3]; + EXPECT_EQ(7U, base::strlcpy(dst, "abcdefg", arraysize(dst))); + EXPECT_EQ(0, memcmp(dst, "ab", 3)); + EXPECT_EQ(7U, base::wcslcpy(wdst, L"abcdefg", arraysize(wdst))); + EXPECT_EQ(0, memcmp(wdst, L"ab", sizeof(wchar_t) * 3)); + } +} + +TEST(StringUtilTest, WprintfFormatPortabilityTest) { + static const struct { + const wchar_t* input; + bool portable; + } cases[] = { + { L"%ls", true }, + { L"%s", false }, + { L"%S", false }, + { L"%lS", false }, + { L"Hello, %s", false }, + { L"%lc", true }, + { L"%c", false }, + { L"%C", false }, + { L"%lC", false }, + { L"%ls %s", false }, + { L"%s %ls", false }, + { L"%s %ls %s", false }, + { L"%f", true }, + { L"%f %F", false }, + { L"%d %D", false }, + { L"%o %O", false }, + { L"%u %U", false }, + { L"%f %d %o %u", true }, + { L"%-8d (%02.1f%)", true }, + { L"% 10s", false }, + { L"% 10ls", true } + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) + EXPECT_EQ(cases[i].portable, base::IsWprintfFormatPortable(cases[i].input)); +} + +TEST(StringUtilTest, RemoveChars) { + const char* kRemoveChars = "-/+*"; + std::string input = "A-+bc/d!*"; + EXPECT_TRUE(RemoveChars(input, kRemoveChars, &input)); + EXPECT_EQ("Abcd!", input); + + // No characters match kRemoveChars. + EXPECT_FALSE(RemoveChars(input, kRemoveChars, &input)); + EXPECT_EQ("Abcd!", input); + + // Empty string. + input.clear(); + EXPECT_FALSE(RemoveChars(input, kRemoveChars, &input)); + EXPECT_EQ(std::string(), input); +} + +TEST(StringUtilTest, ReplaceChars) { + struct TestData { + const char* input; + const char* replace_chars; + const char* replace_with; + const char* output; + bool result; + } cases[] = { + { "", "", "", "", false }, + { "test", "", "", "test", false }, + { "test", "", "!", "test", false }, + { "test", "z", "!", "test", false }, + { "test", "e", "!", "t!st", true }, + { "test", "e", "!?", "t!?st", true }, + { "test", "ez", "!", "t!st", true }, + { "test", "zed", "!?", "t!?st", true }, + { "test", "t", "!?", "!?es!?", true }, + { "test", "et", "!>", "!>!>s!>", true }, + { "test", "zest", "!", "!!!!", true }, + { "test", "szt", "!", "!e!!", true }, + { "test", "t", "test", "testestest", true }, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + std::string output; + bool result = ReplaceChars(cases[i].input, + cases[i].replace_chars, + cases[i].replace_with, + &output); + EXPECT_EQ(cases[i].result, result); + EXPECT_EQ(cases[i].output, output); + } +} + +TEST(StringUtilTest, ContainsOnlyChars) { + // Providing an empty list of characters should return false but for the empty + // string. + EXPECT_TRUE(ContainsOnlyChars(std::string(), std::string())); + EXPECT_FALSE(ContainsOnlyChars("Hello", std::string())); + + EXPECT_TRUE(ContainsOnlyChars(std::string(), "1234")); + EXPECT_TRUE(ContainsOnlyChars("1", "1234")); + EXPECT_TRUE(ContainsOnlyChars("1", "4321")); + EXPECT_TRUE(ContainsOnlyChars("123", "4321")); + EXPECT_FALSE(ContainsOnlyChars("123a", "4321")); +} + +class WriteIntoTest : public testing::Test { + protected: + static void WritesCorrectly(size_t num_chars) { + std::string buffer; + char kOriginal[] = "supercali"; + strncpy(WriteInto(&buffer, num_chars + 1), kOriginal, num_chars); + // Using std::string(buffer.c_str()) instead of |buffer| truncates the + // string at the first \0. + EXPECT_EQ(std::string(kOriginal, + std::min(num_chars, arraysize(kOriginal) - 1)), + std::string(buffer.c_str())); + EXPECT_EQ(num_chars, buffer.size()); + } +}; + +TEST_F(WriteIntoTest, WriteInto) { + // Validate that WriteInto reserves enough space and + // sizes a string correctly. + WritesCorrectly(1); + WritesCorrectly(2); + WritesCorrectly(5000); + + // Validate that WriteInto doesn't modify other strings + // when using a Copy-on-Write implementation. + const char kLive[] = "live"; + const char kDead[] = "dead"; + const std::string live = kLive; + std::string dead = live; + strncpy(WriteInto(&dead, 5), kDead, 4); + EXPECT_EQ(kDead, dead); + EXPECT_EQ(4u, dead.size()); + EXPECT_EQ(kLive, live); + EXPECT_EQ(4u, live.size()); +} + +} // namespace base diff --git a/base/strings/string_util_win.h b/base/strings/string_util_win.h new file mode 100644 index 0000000000..602ba27378 --- /dev/null +++ b/base/strings/string_util_win.h @@ -0,0 +1,61 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_STRINGS_STRING_UTIL_WIN_H_ +#define BASE_STRINGS_STRING_UTIL_WIN_H_ + +#include +#include +#include +#include + +#include "base/logging.h" + +namespace base { + +// Chromium code style is to not use malloc'd strings; this is only for use +// for interaction with APIs that require it. +inline char* strdup(const char* str) { + return _strdup(str); +} + +inline int strcasecmp(const char* s1, const char* s2) { + return _stricmp(s1, s2); +} + +inline int strncasecmp(const char* s1, const char* s2, size_t count) { + return _strnicmp(s1, s2, count); +} + +inline int strncmp16(const char16* s1, const char16* s2, size_t count) { + return ::wcsncmp(s1, s2, count); +} + +inline int vsnprintf(char* buffer, size_t size, + const char* format, va_list arguments) { + int length = _vsprintf_p(buffer, size, format, arguments); + if (length < 0) { + if (size > 0) + buffer[0] = 0; + return _vscprintf_p(format, arguments); + } + return length; +} + +inline int vswprintf(wchar_t* buffer, size_t size, + const wchar_t* format, va_list arguments) { + DCHECK(IsWprintfFormatPortable(format)); + + int length = _vswprintf_p(buffer, size, format, arguments); + if (length < 0) { + if (size > 0) + buffer[0] = 0; + return _vscwprintf_p(format, arguments); + } + return length; +} + +} // namespace base + +#endif // BASE_STRINGS_STRING_UTIL_WIN_H_ diff --git a/base/strings/stringize_macros.h b/base/strings/stringize_macros.h new file mode 100644 index 0000000000..d4e27071e4 --- /dev/null +++ b/base/strings/stringize_macros.h @@ -0,0 +1,31 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// This file defines preprocessor macros for stringizing preprocessor +// symbols (or their output) and manipulating preprocessor symbols +// that define strings. + +#ifndef BASE_STRINGS_STRINGIZE_MACROS_H_ +#define BASE_STRINGS_STRINGIZE_MACROS_H_ + +#include "build/build_config.h" + +// This is not very useful as it does not expand defined symbols if +// called directly. Use its counterpart without the _NO_EXPANSION +// suffix, below. +#define STRINGIZE_NO_EXPANSION(x) #x + +// Use this to quote the provided parameter, first expanding it if it +// is a preprocessor symbol. +// +// For example, if: +// #define A FOO +// #define B(x) myobj->FunctionCall(x) +// +// Then: +// STRINGIZE(A) produces "FOO" +// STRINGIZE(B(y)) produces "myobj->FunctionCall(y)" +#define STRINGIZE(x) STRINGIZE_NO_EXPANSION(x) + +#endif // BASE_STRINGS_STRINGIZE_MACROS_H_ diff --git a/base/strings/stringize_macros_unittest.cc b/base/strings/stringize_macros_unittest.cc new file mode 100644 index 0000000000..d7f9e560ae --- /dev/null +++ b/base/strings/stringize_macros_unittest.cc @@ -0,0 +1,29 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/strings/stringize_macros.h" + +#include "testing/gtest/include/gtest/gtest.h" + +// Macros as per documentation in header file. +#define PREPROCESSOR_UTIL_UNITTEST_A FOO +#define PREPROCESSOR_UTIL_UNITTEST_B(x) myobj->FunctionCall(x) +#define PREPROCESSOR_UTIL_UNITTEST_C "foo" + +TEST(StringizeTest, Ansi) { + EXPECT_STREQ( + "PREPROCESSOR_UTIL_UNITTEST_A", + STRINGIZE_NO_EXPANSION(PREPROCESSOR_UTIL_UNITTEST_A)); + EXPECT_STREQ( + "PREPROCESSOR_UTIL_UNITTEST_B(y)", + STRINGIZE_NO_EXPANSION(PREPROCESSOR_UTIL_UNITTEST_B(y))); + EXPECT_STREQ( + "PREPROCESSOR_UTIL_UNITTEST_C", + STRINGIZE_NO_EXPANSION(PREPROCESSOR_UTIL_UNITTEST_C)); + + EXPECT_STREQ("FOO", STRINGIZE(PREPROCESSOR_UTIL_UNITTEST_A)); + EXPECT_STREQ("myobj->FunctionCall(y)", + STRINGIZE(PREPROCESSOR_UTIL_UNITTEST_B(y))); + EXPECT_STREQ("\"foo\"", STRINGIZE(PREPROCESSOR_UTIL_UNITTEST_C)); +} diff --git a/base/strings/stringprintf.cc b/base/strings/stringprintf.cc new file mode 100644 index 0000000000..fe23daaa35 --- /dev/null +++ b/base/strings/stringprintf.cc @@ -0,0 +1,186 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/strings/stringprintf.h" + +#include + +#include "base/scoped_clear_errno.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" + +namespace base { + +namespace { + +// Overloaded wrappers around vsnprintf and vswprintf. The buf_size parameter +// is the size of the buffer. These return the number of characters in the +// formatted string excluding the NUL terminator. If the buffer is not +// large enough to accommodate the formatted string without truncation, they +// return the number of characters that would be in the fully-formatted string +// (vsnprintf, and vswprintf on Windows), or -1 (vswprintf on POSIX platforms). +inline int vsnprintfT(char* buffer, + size_t buf_size, + const char* format, + va_list argptr) { + return base::vsnprintf(buffer, buf_size, format, argptr); +} + +#if !defined(OS_ANDROID) +inline int vsnprintfT(wchar_t* buffer, + size_t buf_size, + const wchar_t* format, + va_list argptr) { + return base::vswprintf(buffer, buf_size, format, argptr); +} +#endif + +// Templatized backend for StringPrintF/StringAppendF. This does not finalize +// the va_list, the caller is expected to do that. +template +static void StringAppendVT(StringType* dst, + const typename StringType::value_type* format, + va_list ap) { + // First try with a small fixed size buffer. + // This buffer size should be kept in sync with StringUtilTest.GrowBoundary + // and StringUtilTest.StringPrintfBounds. + typename StringType::value_type stack_buf[1024]; + + va_list ap_copy; + GG_VA_COPY(ap_copy, ap); + +#if !defined(OS_WIN) + ScopedClearErrno clear_errno; +#endif + int result = vsnprintfT(stack_buf, arraysize(stack_buf), format, ap_copy); + va_end(ap_copy); + + if (result >= 0 && result < static_cast(arraysize(stack_buf))) { + // It fit. + dst->append(stack_buf, result); + return; + } + + // Repeatedly increase buffer size until it fits. + int mem_length = arraysize(stack_buf); + while (true) { + if (result < 0) { +#if !defined(OS_WIN) + // On Windows, vsnprintfT always returns the number of characters in a + // fully-formatted string, so if we reach this point, something else is + // wrong and no amount of buffer-doubling is going to fix it. + if (errno != 0 && errno != EOVERFLOW) +#endif + { + // If an error other than overflow occurred, it's never going to work. + DLOG(WARNING) << "Unable to printf the requested string due to error."; + return; + } + // Try doubling the buffer size. + mem_length *= 2; + } else { + // We need exactly "result + 1" characters. + mem_length = result + 1; + } + + if (mem_length > 32 * 1024 * 1024) { + // That should be plenty, don't try anything larger. This protects + // against huge allocations when using vsnprintfT implementations that + // return -1 for reasons other than overflow without setting errno. + DLOG(WARNING) << "Unable to printf the requested string due to size."; + return; + } + + std::vector mem_buf(mem_length); + + // NOTE: You can only use a va_list once. Since we're in a while loop, we + // need to make a new copy each time so we don't use up the original. + GG_VA_COPY(ap_copy, ap); + result = vsnprintfT(&mem_buf[0], mem_length, format, ap_copy); + va_end(ap_copy); + + if ((result >= 0) && (result < mem_length)) { + // It fit. + dst->append(&mem_buf[0], result); + return; + } + } +} + +} // namespace + +std::string StringPrintf(const char* format, ...) { + va_list ap; + va_start(ap, format); + std::string result; + StringAppendV(&result, format, ap); + va_end(ap); + return result; +} + +#if !defined(OS_ANDROID) +std::wstring StringPrintf(const wchar_t* format, ...) { + va_list ap; + va_start(ap, format); + std::wstring result; + StringAppendV(&result, format, ap); + va_end(ap); + return result; +} +#endif + +std::string StringPrintV(const char* format, va_list ap) { + std::string result; + StringAppendV(&result, format, ap); + return result; +} + +const std::string& SStringPrintf(std::string* dst, const char* format, ...) { + va_list ap; + va_start(ap, format); + dst->clear(); + StringAppendV(dst, format, ap); + va_end(ap); + return *dst; +} + +#if !defined(OS_ANDROID) +const std::wstring& SStringPrintf(std::wstring* dst, + const wchar_t* format, ...) { + va_list ap; + va_start(ap, format); + dst->clear(); + StringAppendV(dst, format, ap); + va_end(ap); + return *dst; +} +#endif + +void StringAppendF(std::string* dst, const char* format, ...) { + va_list ap; + va_start(ap, format); + StringAppendV(dst, format, ap); + va_end(ap); +} + +#if !defined(OS_ANDROID) +void StringAppendF(std::wstring* dst, const wchar_t* format, ...) { + va_list ap; + va_start(ap, format); + StringAppendV(dst, format, ap); + va_end(ap); +} +#endif + +void StringAppendV(std::string* dst, const char* format, va_list ap) { + StringAppendVT(dst, format, ap); +} + +#if !defined(OS_ANDROID) +void StringAppendV(std::wstring* dst, const wchar_t* format, va_list ap) { + StringAppendVT(dst, format, ap); +} +#endif + +} // namespace base diff --git a/base/strings/stringprintf.h b/base/strings/stringprintf.h new file mode 100644 index 0000000000..3c0e399c0a --- /dev/null +++ b/base/strings/stringprintf.h @@ -0,0 +1,62 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_STRINGS_STRINGPRINTF_H_ +#define BASE_STRINGS_STRINGPRINTF_H_ + +#include // va_list + +#include + +#include "base/base_export.h" +#include "base/compiler_specific.h" + +namespace base { + +// Return a C++ string given printf-like input. +BASE_EXPORT std::string StringPrintf(const char* format, ...) + PRINTF_FORMAT(1, 2); +// OS_ANDROID's libc does not support wchar_t, so several overloads are omitted. +#if !defined(OS_ANDROID) +BASE_EXPORT std::wstring StringPrintf(const wchar_t* format, ...) + WPRINTF_FORMAT(1, 2); +#endif + +// Return a C++ string given vprintf-like input. +BASE_EXPORT std::string StringPrintV(const char* format, va_list ap) + PRINTF_FORMAT(1, 0); + +// Store result into a supplied string and return it. +BASE_EXPORT const std::string& SStringPrintf(std::string* dst, + const char* format, ...) + PRINTF_FORMAT(2, 3); +#if !defined(OS_ANDROID) +BASE_EXPORT const std::wstring& SStringPrintf(std::wstring* dst, + const wchar_t* format, ...) + WPRINTF_FORMAT(2, 3); +#endif + +// Append result to a supplied string. +BASE_EXPORT void StringAppendF(std::string* dst, const char* format, ...) + PRINTF_FORMAT(2, 3); +#if !defined(OS_ANDROID) +// TODO(evanm): this is only used in a few places in the code; +// replace with string16 version. +BASE_EXPORT void StringAppendF(std::wstring* dst, const wchar_t* format, ...) + WPRINTF_FORMAT(2, 3); +#endif + +// Lower-level routine that takes a va_list and appends to a specified +// string. All other routines are just convenience wrappers around it. +BASE_EXPORT void StringAppendV(std::string* dst, const char* format, va_list ap) + PRINTF_FORMAT(2, 0); +#if !defined(OS_ANDROID) +BASE_EXPORT void StringAppendV(std::wstring* dst, + const wchar_t* format, va_list ap) + WPRINTF_FORMAT(2, 0); +#endif + +} // namespace base + +#endif // BASE_STRINGS_STRINGPRINTF_H_ diff --git a/base/strings/stringprintf_unittest.cc b/base/strings/stringprintf_unittest.cc new file mode 100644 index 0000000000..a1bf2da426 --- /dev/null +++ b/base/strings/stringprintf_unittest.cc @@ -0,0 +1,188 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/strings/stringprintf.h" + +#include + +#include "base/basictypes.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace { + +// A helper for the StringAppendV test that follows. +// +// Just forwards its args to StringAppendV. +static void StringAppendVTestHelper(std::string* out, const char* format, ...) { + va_list ap; + va_start(ap, format); + StringAppendV(out, format, ap); + va_end(ap); +} + +} // namespace + +TEST(StringPrintfTest, StringPrintfEmpty) { + EXPECT_EQ("", StringPrintf("%s", "")); +} + +TEST(StringPrintfTest, StringPrintfMisc) { + EXPECT_EQ("123hello w", StringPrintf("%3d%2s %1c", 123, "hello", 'w')); +#if !defined(OS_ANDROID) + EXPECT_EQ(L"123hello w", StringPrintf(L"%3d%2ls %1lc", 123, L"hello", 'w')); +#endif +} + +TEST(StringPrintfTest, StringAppendfEmptyString) { + std::string value("Hello"); + StringAppendF(&value, "%s", ""); + EXPECT_EQ("Hello", value); + +#if !defined(OS_ANDROID) + std::wstring valuew(L"Hello"); + StringAppendF(&valuew, L"%ls", L""); + EXPECT_EQ(L"Hello", valuew); +#endif +} + +TEST(StringPrintfTest, StringAppendfString) { + std::string value("Hello"); + StringAppendF(&value, " %s", "World"); + EXPECT_EQ("Hello World", value); + +#if !defined(OS_ANDROID) + std::wstring valuew(L"Hello"); + StringAppendF(&valuew, L" %ls", L"World"); + EXPECT_EQ(L"Hello World", valuew); +#endif +} + +TEST(StringPrintfTest, StringAppendfInt) { + std::string value("Hello"); + StringAppendF(&value, " %d", 123); + EXPECT_EQ("Hello 123", value); + +#if !defined(OS_ANDROID) + std::wstring valuew(L"Hello"); + StringAppendF(&valuew, L" %d", 123); + EXPECT_EQ(L"Hello 123", valuew); +#endif +} + +// Make sure that lengths exactly around the initial buffer size are handled +// correctly. +TEST(StringPrintfTest, StringPrintfBounds) { + const int kSrcLen = 1026; + char src[kSrcLen]; + for (size_t i = 0; i < arraysize(src); i++) + src[i] = 'A'; + + wchar_t srcw[kSrcLen]; + for (size_t i = 0; i < arraysize(srcw); i++) + srcw[i] = 'A'; + + for (int i = 1; i < 3; i++) { + src[kSrcLen - i] = 0; + std::string out; + SStringPrintf(&out, "%s", src); + EXPECT_STREQ(src, out.c_str()); + +#if !defined(OS_ANDROID) + srcw[kSrcLen - i] = 0; + std::wstring outw; + SStringPrintf(&outw, L"%ls", srcw); + EXPECT_STREQ(srcw, outw.c_str()); +#endif + } +} + +// Test very large sprintfs that will cause the buffer to grow. +TEST(StringPrintfTest, Grow) { + char src[1026]; + for (size_t i = 0; i < arraysize(src); i++) + src[i] = 'A'; + src[1025] = 0; + + const char* fmt = "%sB%sB%sB%sB%sB%sB%s"; + + std::string out; + SStringPrintf(&out, fmt, src, src, src, src, src, src, src); + + const int kRefSize = 320000; + char* ref = new char[kRefSize]; +#if defined(OS_WIN) + sprintf_s(ref, kRefSize, fmt, src, src, src, src, src, src, src); +#elif defined(OS_POSIX) + snprintf(ref, kRefSize, fmt, src, src, src, src, src, src, src); +#endif + + EXPECT_STREQ(ref, out.c_str()); + delete[] ref; +} + +TEST(StringPrintfTest, StringAppendV) { + std::string out; + StringAppendVTestHelper(&out, "%d foo %s", 1, "bar"); + EXPECT_EQ("1 foo bar", out); +} + +// Test the boundary condition for the size of the string_util's +// internal buffer. +TEST(StringPrintfTest, GrowBoundary) { + const int string_util_buf_len = 1024; + // Our buffer should be one larger than the size of StringAppendVT's stack + // buffer. + const int buf_len = string_util_buf_len + 1; + char src[buf_len + 1]; // Need extra one for NULL-terminator. + for (int i = 0; i < buf_len; ++i) + src[i] = 'a'; + src[buf_len] = 0; + + std::string out; + SStringPrintf(&out, "%s", src); + + EXPECT_STREQ(src, out.c_str()); +} + +// TODO(evanm): what's the proper cross-platform test here? +#if defined(OS_WIN) +// sprintf in Visual Studio fails when given U+FFFF. This tests that the +// failure case is gracefuly handled. +TEST(StringPrintfTest, Invalid) { + wchar_t invalid[2]; + invalid[0] = 0xffff; + invalid[1] = 0; + + std::wstring out; + SStringPrintf(&out, L"%ls", invalid); + EXPECT_STREQ(L"", out.c_str()); +} +#endif + +// Test that the positional parameters work. +TEST(StringPrintfTest, PositionalParameters) { + std::string out; + SStringPrintf(&out, "%1$s %1$s", "test"); + EXPECT_STREQ("test test", out.c_str()); + +#if defined(OS_WIN) + std::wstring wout; + SStringPrintf(&wout, L"%1$ls %1$ls", L"test"); + EXPECT_STREQ(L"test test", wout.c_str()); +#endif +} + +// Test that StringPrintf and StringAppendV do not change errno. +TEST(StringPrintfTest, StringPrintfErrno) { + errno = 1; + EXPECT_EQ("", StringPrintf("%s", "")); + EXPECT_EQ(1, errno); + std::string out; + StringAppendVTestHelper(&out, "%d foo %s", 1, "bar"); + EXPECT_EQ(1, errno); +} + +} // namespace base diff --git a/base/strings/sys_string_conversions.h b/base/strings/sys_string_conversions.h new file mode 100644 index 0000000000..42f2389a8b --- /dev/null +++ b/base/strings/sys_string_conversions.h @@ -0,0 +1,83 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_STRINGS_SYS_STRING_CONVERSIONS_H_ +#define BASE_STRINGS_SYS_STRING_CONVERSIONS_H_ + +// Provides system-dependent string type conversions for cases where it's +// necessary to not use ICU. Generally, you should not need this in Chrome, +// but it is used in some shared code. Dependencies should be minimal. + +#include + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/strings/string16.h" +#include "base/strings/string_piece.h" + +#if defined(OS_MACOSX) +#include +#ifdef __OBJC__ +@class NSString; +#else +class NSString; +#endif +#endif // OS_MACOSX + +namespace base { + +// Converts between wide and UTF-8 representations of a string. On error, the +// result is system-dependent. +BASE_EXPORT std::string SysWideToUTF8(const std::wstring& wide); +BASE_EXPORT std::wstring SysUTF8ToWide(const StringPiece& utf8); + +// Converts between wide and the system multi-byte representations of a string. +// DANGER: This will lose information and can change (on Windows, this can +// change between reboots). +BASE_EXPORT std::string SysWideToNativeMB(const std::wstring& wide); +BASE_EXPORT std::wstring SysNativeMBToWide(const StringPiece& native_mb); + +// Windows-specific ------------------------------------------------------------ + +#if defined(OS_WIN) + +// Converts between 8-bit and wide strings, using the given code page. The +// code page identifier is one accepted by the Windows function +// MultiByteToWideChar(). +BASE_EXPORT std::wstring SysMultiByteToWide(const StringPiece& mb, + uint32 code_page); +BASE_EXPORT std::string SysWideToMultiByte(const std::wstring& wide, + uint32 code_page); + +#endif // defined(OS_WIN) + +// Mac-specific ---------------------------------------------------------------- + +#if defined(OS_MACOSX) + +// Converts between STL strings and CFStringRefs/NSStrings. + +// Creates a string, and returns it with a refcount of 1. You are responsible +// for releasing it. Returns NULL on failure. +BASE_EXPORT CFStringRef SysUTF8ToCFStringRef(const std::string& utf8); +BASE_EXPORT CFStringRef SysUTF16ToCFStringRef(const string16& utf16); + +// Same, but returns an autoreleased NSString. +BASE_EXPORT NSString* SysUTF8ToNSString(const std::string& utf8); +BASE_EXPORT NSString* SysUTF16ToNSString(const string16& utf16); + +// Converts a CFStringRef to an STL string. Returns an empty string on failure. +BASE_EXPORT std::string SysCFStringRefToUTF8(CFStringRef ref); +BASE_EXPORT string16 SysCFStringRefToUTF16(CFStringRef ref); + +// Same, but accepts NSString input. Converts nil NSString* to the appropriate +// string type of length 0. +BASE_EXPORT std::string SysNSStringToUTF8(NSString* ref); +BASE_EXPORT string16 SysNSStringToUTF16(NSString* ref); + +#endif // defined(OS_MACOSX) + +} // namespace base + +#endif // BASE_STRINGS_SYS_STRING_CONVERSIONS_H_ diff --git a/base/strings/sys_string_conversions_mac.mm b/base/strings/sys_string_conversions_mac.mm new file mode 100644 index 0000000000..9479e787c0 --- /dev/null +++ b/base/strings/sys_string_conversions_mac.mm @@ -0,0 +1,186 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/strings/sys_string_conversions.h" + +#import + +#include + +#include "base/mac/foundation_util.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/strings/string_piece.h" + +namespace base { + +namespace { + +// Convert the supplied CFString into the specified encoding, and return it as +// an STL string of the template type. Returns an empty string on failure. +// +// Do not assert in this function since it is used by the asssertion code! +template +static StringType CFStringToSTLStringWithEncodingT(CFStringRef cfstring, + CFStringEncoding encoding) { + CFIndex length = CFStringGetLength(cfstring); + if (length == 0) + return StringType(); + + CFRange whole_string = CFRangeMake(0, length); + CFIndex out_size; + CFIndex converted = CFStringGetBytes(cfstring, + whole_string, + encoding, + 0, // lossByte + false, // isExternalRepresentation + NULL, // buffer + 0, // maxBufLen + &out_size); + if (converted == 0 || out_size == 0) + return StringType(); + + // out_size is the number of UInt8-sized units needed in the destination. + // A buffer allocated as UInt8 units might not be properly aligned to + // contain elements of StringType::value_type. Use a container for the + // proper value_type, and convert out_size by figuring the number of + // value_type elements per UInt8. Leave room for a NUL terminator. + typename StringType::size_type elements = + out_size * sizeof(UInt8) / sizeof(typename StringType::value_type) + 1; + + std::vector out_buffer(elements); + converted = CFStringGetBytes(cfstring, + whole_string, + encoding, + 0, // lossByte + false, // isExternalRepresentation + reinterpret_cast(&out_buffer[0]), + out_size, + NULL); // usedBufLen + if (converted == 0) + return StringType(); + + out_buffer[elements - 1] = '\0'; + return StringType(&out_buffer[0], elements - 1); +} + +// Given an STL string |in| with an encoding specified by |in_encoding|, +// convert it to |out_encoding| and return it as an STL string of the +// |OutStringType| template type. Returns an empty string on failure. +// +// Do not assert in this function since it is used by the asssertion code! +template +static OutStringType STLStringToSTLStringWithEncodingsT( + const InStringType& in, + CFStringEncoding in_encoding, + CFStringEncoding out_encoding) { + typename InStringType::size_type in_length = in.length(); + if (in_length == 0) + return OutStringType(); + + base::ScopedCFTypeRef cfstring(CFStringCreateWithBytesNoCopy( + NULL, + reinterpret_cast(in.data()), + in_length * sizeof(typename InStringType::value_type), + in_encoding, + false, + kCFAllocatorNull)); + if (!cfstring) + return OutStringType(); + + return CFStringToSTLStringWithEncodingT(cfstring, + out_encoding); +} + +// Given an STL string |in| with an encoding specified by |in_encoding|, +// return it as a CFStringRef. Returns NULL on failure. +template +static CFStringRef STLStringToCFStringWithEncodingsT( + const StringType& in, + CFStringEncoding in_encoding) { + typename StringType::size_type in_length = in.length(); + if (in_length == 0) + return CFSTR(""); + + return CFStringCreateWithBytes(kCFAllocatorDefault, + reinterpret_cast(in.data()), + in_length * + sizeof(typename StringType::value_type), + in_encoding, + false); +} + +// Specify the byte ordering explicitly, otherwise CFString will be confused +// when strings don't carry BOMs, as they typically won't. +static const CFStringEncoding kNarrowStringEncoding = kCFStringEncodingUTF8; +#ifdef __BIG_ENDIAN__ +static const CFStringEncoding kMediumStringEncoding = kCFStringEncodingUTF16BE; +static const CFStringEncoding kWideStringEncoding = kCFStringEncodingUTF32BE; +#elif defined(__LITTLE_ENDIAN__) +static const CFStringEncoding kMediumStringEncoding = kCFStringEncodingUTF16LE; +static const CFStringEncoding kWideStringEncoding = kCFStringEncodingUTF32LE; +#endif // __LITTLE_ENDIAN__ + +} // namespace + +// Do not assert in this function since it is used by the asssertion code! +std::string SysWideToUTF8(const std::wstring& wide) { + return STLStringToSTLStringWithEncodingsT( + wide, kWideStringEncoding, kNarrowStringEncoding); +} + +// Do not assert in this function since it is used by the asssertion code! +std::wstring SysUTF8ToWide(const StringPiece& utf8) { + return STLStringToSTLStringWithEncodingsT( + utf8, kNarrowStringEncoding, kWideStringEncoding); +} + +std::string SysWideToNativeMB(const std::wstring& wide) { + return SysWideToUTF8(wide); +} + +std::wstring SysNativeMBToWide(const StringPiece& native_mb) { + return SysUTF8ToWide(native_mb); +} + +CFStringRef SysUTF8ToCFStringRef(const std::string& utf8) { + return STLStringToCFStringWithEncodingsT(utf8, kNarrowStringEncoding); +} + +CFStringRef SysUTF16ToCFStringRef(const string16& utf16) { + return STLStringToCFStringWithEncodingsT(utf16, kMediumStringEncoding); +} + +NSString* SysUTF8ToNSString(const std::string& utf8) { + return (NSString*)base::mac::CFTypeRefToNSObjectAutorelease( + SysUTF8ToCFStringRef(utf8)); +} + +NSString* SysUTF16ToNSString(const string16& utf16) { + return (NSString*)base::mac::CFTypeRefToNSObjectAutorelease( + SysUTF16ToCFStringRef(utf16)); +} + +std::string SysCFStringRefToUTF8(CFStringRef ref) { + return CFStringToSTLStringWithEncodingT(ref, + kNarrowStringEncoding); +} + +string16 SysCFStringRefToUTF16(CFStringRef ref) { + return CFStringToSTLStringWithEncodingT(ref, + kMediumStringEncoding); +} + +std::string SysNSStringToUTF8(NSString* nsstring) { + if (!nsstring) + return std::string(); + return SysCFStringRefToUTF8(reinterpret_cast(nsstring)); +} + +string16 SysNSStringToUTF16(NSString* nsstring) { + if (!nsstring) + return string16(); + return SysCFStringRefToUTF16(reinterpret_cast(nsstring)); +} + +} // namespace base diff --git a/base/strings/sys_string_conversions_mac_unittest.mm b/base/strings/sys_string_conversions_mac_unittest.mm new file mode 100644 index 0000000000..4750a9a8b5 --- /dev/null +++ b/base/strings/sys_string_conversions_mac_unittest.mm @@ -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. + +#import + +#include "base/strings/string16.h" +#include "base/strings/sys_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +TEST(SysStrings, ConversionsFromNSString) { + EXPECT_STREQ("Hello, world!", SysNSStringToUTF8(@"Hello, world!").c_str()); + + // Conversions should be able to handle a NULL value without crashing. + EXPECT_STREQ("", SysNSStringToUTF8(nil).c_str()); + EXPECT_EQ(string16(), SysNSStringToUTF16(nil)); +} + +} // namespace base diff --git a/base/strings/sys_string_conversions_posix.cc b/base/strings/sys_string_conversions_posix.cc new file mode 100644 index 0000000000..1e926bb549 --- /dev/null +++ b/base/strings/sys_string_conversions_posix.cc @@ -0,0 +1,161 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/strings/sys_string_conversions.h" + +#include + +#include "base/strings/string_piece.h" +#include "base/strings/utf_string_conversions.h" + +namespace base { + +std::string SysWideToUTF8(const std::wstring& wide) { + // In theory this should be using the system-provided conversion rather + // than our ICU, but this will do for now. + return WideToUTF8(wide); +} +std::wstring SysUTF8ToWide(const StringPiece& utf8) { + // In theory this should be using the system-provided conversion rather + // than our ICU, but this will do for now. + std::wstring out; + UTF8ToWide(utf8.data(), utf8.size(), &out); + return out; +} + +#if defined(OS_CHROMEOS) || defined(OS_ANDROID) +// TODO(port): Consider reverting the OS_ANDROID when we have wcrtomb() +// support and a better understanding of what calls these routines. + +// ChromeOS always runs in UTF-8 locale. +std::string SysWideToNativeMB(const std::wstring& wide) { + return WideToUTF8(wide); +} + +std::wstring SysNativeMBToWide(const StringPiece& native_mb) { + return SysUTF8ToWide(native_mb); +} + +#else + +std::string SysWideToNativeMB(const std::wstring& wide) { + mbstate_t ps; + + // Calculate the number of multi-byte characters. We walk through the string + // without writing the output, counting the number of multi-byte characters. + size_t num_out_chars = 0; + memset(&ps, 0, sizeof(ps)); + for (size_t i = 0; i < wide.size(); ++i) { + const wchar_t src = wide[i]; + // Use a temp buffer since calling wcrtomb with an output of NULL does not + // calculate the output length. + char buf[16]; + // Skip NULLs to avoid wcrtomb's special handling of them. + size_t res = src ? wcrtomb(buf, src, &ps) : 0; + switch (res) { + // Handle any errors and return an empty string. + case static_cast(-1): + return std::string(); + break; + case 0: + // We hit an embedded null byte, keep going. + ++num_out_chars; + break; + default: + num_out_chars += res; + break; + } + } + + if (num_out_chars == 0) + return std::string(); + + std::string out; + out.resize(num_out_chars); + + // We walk the input string again, with |i| tracking the index of the + // wide input, and |j| tracking the multi-byte output. + memset(&ps, 0, sizeof(ps)); + for (size_t i = 0, j = 0; i < wide.size(); ++i) { + const wchar_t src = wide[i]; + // We don't want wcrtomb to do its funkiness for embedded NULLs. + size_t res = src ? wcrtomb(&out[j], src, &ps) : 0; + switch (res) { + // Handle any errors and return an empty string. + case static_cast(-1): + return std::string(); + break; + case 0: + // We hit an embedded null byte, keep going. + ++j; // Output is already zeroed. + break; + default: + j += res; + break; + } + } + + return out; +} + +std::wstring SysNativeMBToWide(const StringPiece& native_mb) { + mbstate_t ps; + + // Calculate the number of wide characters. We walk through the string + // without writing the output, counting the number of wide characters. + size_t num_out_chars = 0; + memset(&ps, 0, sizeof(ps)); + for (size_t i = 0; i < native_mb.size(); ) { + const char* src = native_mb.data() + i; + size_t res = mbrtowc(NULL, src, native_mb.size() - i, &ps); + switch (res) { + // Handle any errors and return an empty string. + case static_cast(-2): + case static_cast(-1): + return std::wstring(); + break; + case 0: + // We hit an embedded null byte, keep going. + i += 1; // Fall through. + default: + i += res; + ++num_out_chars; + break; + } + } + + if (num_out_chars == 0) + return std::wstring(); + + std::wstring out; + out.resize(num_out_chars); + + memset(&ps, 0, sizeof(ps)); // Clear the shift state. + // We walk the input string again, with |i| tracking the index of the + // multi-byte input, and |j| tracking the wide output. + for (size_t i = 0, j = 0; i < native_mb.size(); ++j) { + const char* src = native_mb.data() + i; + wchar_t* dst = &out[j]; + size_t res = mbrtowc(dst, src, native_mb.size() - i, &ps); + switch (res) { + // Handle any errors and return an empty string. + case static_cast(-2): + case static_cast(-1): + return std::wstring(); + break; + case 0: + i += 1; // Skip null byte. + break; + default: + i += res; + break; + } + } + + return out; +} + +#endif // OS_CHROMEOS + +} // namespace base diff --git a/base/strings/sys_string_conversions_unittest.cc b/base/strings/sys_string_conversions_unittest.cc new file mode 100644 index 0000000000..d2d38e40cc --- /dev/null +++ b/base/strings/sys_string_conversions_unittest.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 + +#include "base/basictypes.h" +#include "base/strings/string_piece.h" +#include "base/strings/sys_string_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/test/scoped_locale.h" +#include "testing/gtest/include/gtest/gtest.h" + +#ifdef WCHAR_T_IS_UTF32 +static const std::wstring kSysWideOldItalicLetterA = L"\x10300"; +#else +static const std::wstring kSysWideOldItalicLetterA = L"\xd800\xdf00"; +#endif + +namespace base { + +TEST(SysStrings, SysWideToUTF8) { + EXPECT_EQ("Hello, world", SysWideToUTF8(L"Hello, world")); + EXPECT_EQ("\xe4\xbd\xa0\xe5\xa5\xbd", SysWideToUTF8(L"\x4f60\x597d")); + + // >16 bits + EXPECT_EQ("\xF0\x90\x8C\x80", SysWideToUTF8(kSysWideOldItalicLetterA)); + + // Error case. When Windows finds a UTF-16 character going off the end of + // a string, it just converts that literal value to UTF-8, even though this + // is invalid. + // + // This is what XP does, but Vista has different behavior, so we don't bother + // verifying it: + // EXPECT_EQ("\xE4\xBD\xA0\xED\xA0\x80zyxw", + // SysWideToUTF8(L"\x4f60\xd800zyxw")); + + // Test embedded NULLs. + std::wstring wide_null(L"a"); + wide_null.push_back(0); + wide_null.push_back('b'); + + std::string expected_null("a"); + expected_null.push_back(0); + expected_null.push_back('b'); + + EXPECT_EQ(expected_null, SysWideToUTF8(wide_null)); +} + +TEST(SysStrings, SysUTF8ToWide) { + EXPECT_EQ(L"Hello, world", SysUTF8ToWide("Hello, world")); + EXPECT_EQ(L"\x4f60\x597d", SysUTF8ToWide("\xe4\xbd\xa0\xe5\xa5\xbd")); + // >16 bits + EXPECT_EQ(kSysWideOldItalicLetterA, SysUTF8ToWide("\xF0\x90\x8C\x80")); + + // Error case. When Windows finds an invalid UTF-8 character, it just skips + // it. This seems weird because it's inconsistent with the reverse conversion. + // + // This is what XP does, but Vista has different behavior, so we don't bother + // verifying it: + // EXPECT_EQ(L"\x4f60zyxw", SysUTF8ToWide("\xe4\xbd\xa0\xe5\xa5zyxw")); + + // Test embedded NULLs. + std::string utf8_null("a"); + utf8_null.push_back(0); + utf8_null.push_back('b'); + + std::wstring expected_null(L"a"); + expected_null.push_back(0); + expected_null.push_back('b'); + + EXPECT_EQ(expected_null, SysUTF8ToWide(utf8_null)); +} + +#if defined(OS_LINUX) // Tests depend on setting a specific Linux locale. + +TEST(SysStrings, SysWideToNativeMB) { + ScopedLocale locale("en_US.utf-8"); + EXPECT_EQ("Hello, world", SysWideToNativeMB(L"Hello, world")); + EXPECT_EQ("\xe4\xbd\xa0\xe5\xa5\xbd", SysWideToNativeMB(L"\x4f60\x597d")); + + // >16 bits + EXPECT_EQ("\xF0\x90\x8C\x80", SysWideToNativeMB(kSysWideOldItalicLetterA)); + + // Error case. When Windows finds a UTF-16 character going off the end of + // a string, it just converts that literal value to UTF-8, even though this + // is invalid. + // + // This is what XP does, but Vista has different behavior, so we don't bother + // verifying it: + // EXPECT_EQ("\xE4\xBD\xA0\xED\xA0\x80zyxw", + // SysWideToNativeMB(L"\x4f60\xd800zyxw")); + + // Test embedded NULLs. + std::wstring wide_null(L"a"); + wide_null.push_back(0); + wide_null.push_back('b'); + + std::string expected_null("a"); + expected_null.push_back(0); + expected_null.push_back('b'); + + EXPECT_EQ(expected_null, SysWideToNativeMB(wide_null)); +} + +// We assume the test is running in a UTF8 locale. +TEST(SysStrings, SysNativeMBToWide) { + ScopedLocale locale("en_US.utf-8"); + EXPECT_EQ(L"Hello, world", SysNativeMBToWide("Hello, world")); + EXPECT_EQ(L"\x4f60\x597d", SysNativeMBToWide("\xe4\xbd\xa0\xe5\xa5\xbd")); + // >16 bits + EXPECT_EQ(kSysWideOldItalicLetterA, SysNativeMBToWide("\xF0\x90\x8C\x80")); + + // Error case. When Windows finds an invalid UTF-8 character, it just skips + // it. This seems weird because it's inconsistent with the reverse conversion. + // + // This is what XP does, but Vista has different behavior, so we don't bother + // verifying it: + // EXPECT_EQ(L"\x4f60zyxw", SysNativeMBToWide("\xe4\xbd\xa0\xe5\xa5zyxw")); + + // Test embedded NULLs. + std::string utf8_null("a"); + utf8_null.push_back(0); + utf8_null.push_back('b'); + + std::wstring expected_null(L"a"); + expected_null.push_back(0); + expected_null.push_back('b'); + + EXPECT_EQ(expected_null, SysNativeMBToWide(utf8_null)); +} + +static const wchar_t* const kConvertRoundtripCases[] = { + L"Google Video", + // "网页 图片 资讯更多 »" + L"\x7f51\x9875\x0020\x56fe\x7247\x0020\x8d44\x8baf\x66f4\x591a\x0020\x00bb", + // "Παγκόσμιος Ιστός" + L"\x03a0\x03b1\x03b3\x03ba\x03cc\x03c3\x03bc\x03b9" + L"\x03bf\x03c2\x0020\x0399\x03c3\x03c4\x03cc\x03c2", + // "ПоиÑк Ñтраниц на руÑÑком" + L"\x041f\x043e\x0438\x0441\x043a\x0020\x0441\x0442" + L"\x0440\x0430\x043d\x0438\x0446\x0020\x043d\x0430" + L"\x0020\x0440\x0443\x0441\x0441\x043a\x043e\x043c", + // "전체서비스" + L"\xc804\xccb4\xc11c\xbe44\xc2a4", + + // Test characters that take more than 16 bits. This will depend on whether + // wchar_t is 16 or 32 bits. +#if defined(WCHAR_T_IS_UTF16) + L"\xd800\xdf00", + // ????? (Mathematical Alphanumeric Symbols (U+011d40 - U+011d44 : A,B,C,D,E) + L"\xd807\xdd40\xd807\xdd41\xd807\xdd42\xd807\xdd43\xd807\xdd44", +#elif defined(WCHAR_T_IS_UTF32) + L"\x10300", + // ????? (Mathematical Alphanumeric Symbols (U+011d40 - U+011d44 : A,B,C,D,E) + L"\x11d40\x11d41\x11d42\x11d43\x11d44", +#endif +}; + + +TEST(SysStrings, SysNativeMBAndWide) { + ScopedLocale locale("en_US.utf-8"); + for (size_t i = 0; i < arraysize(kConvertRoundtripCases); ++i) { + std::wstring wide = kConvertRoundtripCases[i]; + std::wstring trip = SysNativeMBToWide(SysWideToNativeMB(wide)); + EXPECT_EQ(wide.size(), trip.size()); + EXPECT_EQ(wide, trip); + } + + // We assume our test is running in UTF-8, so double check through ICU. + for (size_t i = 0; i < arraysize(kConvertRoundtripCases); ++i) { + std::wstring wide = kConvertRoundtripCases[i]; + std::wstring trip = SysNativeMBToWide(WideToUTF8(wide)); + EXPECT_EQ(wide.size(), trip.size()); + EXPECT_EQ(wide, trip); + } + + for (size_t i = 0; i < arraysize(kConvertRoundtripCases); ++i) { + std::wstring wide = kConvertRoundtripCases[i]; + std::wstring trip = UTF8ToWide(SysWideToNativeMB(wide)); + EXPECT_EQ(wide.size(), trip.size()); + EXPECT_EQ(wide, trip); + } +} +#endif // OS_LINUX + +} // namespace base diff --git a/base/strings/sys_string_conversions_win.cc b/base/strings/sys_string_conversions_win.cc new file mode 100644 index 0000000000..94d4466223 --- /dev/null +++ b/base/strings/sys_string_conversions_win.cc @@ -0,0 +1,70 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/strings/sys_string_conversions.h" + +#include + +#include "base/strings/string_piece.h" + +namespace base { + +// Do not assert in this function since it is used by the asssertion code! +std::string SysWideToUTF8(const std::wstring& wide) { + return SysWideToMultiByte(wide, CP_UTF8); +} + +// Do not assert in this function since it is used by the asssertion code! +std::wstring SysUTF8ToWide(const StringPiece& utf8) { + return SysMultiByteToWide(utf8, CP_UTF8); +} + +std::string SysWideToNativeMB(const std::wstring& wide) { + return SysWideToMultiByte(wide, CP_ACP); +} + +std::wstring SysNativeMBToWide(const StringPiece& native_mb) { + return SysMultiByteToWide(native_mb, CP_ACP); +} + +// Do not assert in this function since it is used by the asssertion code! +std::wstring SysMultiByteToWide(const StringPiece& mb, uint32 code_page) { + if (mb.empty()) + return std::wstring(); + + int mb_length = static_cast(mb.length()); + // Compute the length of the buffer. + int charcount = MultiByteToWideChar(code_page, 0, + mb.data(), mb_length, NULL, 0); + if (charcount == 0) + return std::wstring(); + + std::wstring wide; + wide.resize(charcount); + MultiByteToWideChar(code_page, 0, mb.data(), mb_length, &wide[0], charcount); + + return wide; +} + +// Do not assert in this function since it is used by the asssertion code! +std::string SysWideToMultiByte(const std::wstring& wide, uint32 code_page) { + int wide_length = static_cast(wide.length()); + if (wide_length == 0) + return std::string(); + + // Compute the length of the buffer we'll need. + int charcount = WideCharToMultiByte(code_page, 0, wide.data(), wide_length, + NULL, 0, NULL, NULL); + if (charcount == 0) + return std::string(); + + std::string mb; + mb.resize(charcount); + WideCharToMultiByte(code_page, 0, wide.data(), wide_length, + &mb[0], charcount, NULL, NULL); + + return mb; +} + +} // namespace base diff --git a/base/strings/utf_offset_string_conversions.cc b/base/strings/utf_offset_string_conversions.cc new file mode 100644 index 0000000000..bb402e4d24 --- /dev/null +++ b/base/strings/utf_offset_string_conversions.cc @@ -0,0 +1,166 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/strings/utf_offset_string_conversions.h" + +#include + +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_piece.h" +#include "base/strings/utf_string_conversion_utils.h" + +namespace base { + +// Converts the given source Unicode character type to the given destination +// Unicode character type as a STL string. The given input buffer and size +// determine the source, and the given output STL string will be replaced by +// the result. +template +bool ConvertUnicode(const SrcChar* src, + size_t src_len, + DestStdString* output, + std::vector* offsets_for_adjustment) { + if (offsets_for_adjustment) { + std::for_each(offsets_for_adjustment->begin(), + offsets_for_adjustment->end(), + LimitOffset(src_len)); + } + + // ICU requires 32-bit numbers. + bool success = true; + OffsetAdjuster offset_adjuster(offsets_for_adjustment); + int32 src_len32 = static_cast(src_len); + for (int32 i = 0; i < src_len32; i++) { + uint32 code_point; + size_t original_i = i; + size_t chars_written = 0; + if (ReadUnicodeCharacter(src, src_len32, &i, &code_point)) { + chars_written = WriteUnicodeCharacter(code_point, output); + } else { + chars_written = WriteUnicodeCharacter(0xFFFD, output); + success = false; + } + if (offsets_for_adjustment) { + // NOTE: ReadUnicodeCharacter() adjusts |i| to point _at_ the last + // character read, not after it (so that incrementing it in the loop + // increment will place it at the right location), so we need to account + // for that in determining the amount that was read. + offset_adjuster.Add(OffsetAdjuster::Adjustment(original_i, + i - original_i + 1, chars_written)); + } + } + return success; +} + +bool UTF8ToUTF16AndAdjustOffset(const char* src, + size_t src_len, + string16* output, + size_t* offset_for_adjustment) { + std::vector offsets; + if (offset_for_adjustment) + offsets.push_back(*offset_for_adjustment); + PrepareForUTF16Or32Output(src, src_len, output); + bool ret = ConvertUnicode(src, src_len, output, &offsets); + if (offset_for_adjustment) + *offset_for_adjustment = offsets[0]; + return ret; +} + +bool UTF8ToUTF16AndAdjustOffsets(const char* src, + size_t src_len, + string16* output, + std::vector* offsets_for_adjustment) { + PrepareForUTF16Or32Output(src, src_len, output); + return ConvertUnicode(src, src_len, output, offsets_for_adjustment); +} + +string16 UTF8ToUTF16AndAdjustOffset(const base::StringPiece& utf8, + size_t* offset_for_adjustment) { + std::vector offsets; + if (offset_for_adjustment) + offsets.push_back(*offset_for_adjustment); + string16 result; + UTF8ToUTF16AndAdjustOffsets(utf8.data(), utf8.length(), &result, + &offsets); + if (offset_for_adjustment) + *offset_for_adjustment = offsets[0]; + return result; +} + +string16 UTF8ToUTF16AndAdjustOffsets( + const base::StringPiece& utf8, + std::vector* offsets_for_adjustment) { + string16 result; + UTF8ToUTF16AndAdjustOffsets(utf8.data(), utf8.length(), &result, + offsets_for_adjustment); + return result; +} + +std::string UTF16ToUTF8AndAdjustOffset( + const base::StringPiece16& utf16, + size_t* offset_for_adjustment) { + std::vector offsets; + if (offset_for_adjustment) + offsets.push_back(*offset_for_adjustment); + std::string result = UTF16ToUTF8AndAdjustOffsets(utf16, &offsets); + if (offset_for_adjustment) + *offset_for_adjustment = offsets[0]; + return result; +} + +std::string UTF16ToUTF8AndAdjustOffsets( + const base::StringPiece16& utf16, + std::vector* offsets_for_adjustment) { + std::string result; + PrepareForUTF8Output(utf16.data(), utf16.length(), &result); + ConvertUnicode(utf16.data(), utf16.length(), &result, offsets_for_adjustment); + return result; +} + +OffsetAdjuster::Adjustment::Adjustment(size_t original_offset, + size_t original_length, + size_t output_length) + : original_offset(original_offset), + original_length(original_length), + output_length(output_length) { +} + +OffsetAdjuster::OffsetAdjuster(std::vector* offsets_for_adjustment) + : offsets_for_adjustment_(offsets_for_adjustment) { +} + +OffsetAdjuster::~OffsetAdjuster() { + if (!offsets_for_adjustment_ || adjustments_.empty()) + return; + for (std::vector::iterator i(offsets_for_adjustment_->begin()); + i != offsets_for_adjustment_->end(); ++i) + AdjustOffset(i); +} + +void OffsetAdjuster::Add(const Adjustment& adjustment) { + adjustments_.push_back(adjustment); +} + +void OffsetAdjuster::AdjustOffset(std::vector::iterator offset) { + if (*offset == string16::npos) + return; + size_t adjustment = 0; + for (std::vector::const_iterator i = adjustments_.begin(); + i != adjustments_.end(); ++i) { + if (*offset == i->original_offset && i->output_length == 0) { + *offset = string16::npos; + return; + } + if (*offset <= i->original_offset) + break; + if (*offset < (i->original_offset + i->original_length)) { + *offset = string16::npos; + return; + } + adjustment += (i->original_length - i->output_length); + } + *offset -= adjustment; +} + +} // namespace base diff --git a/base/strings/utf_offset_string_conversions.h b/base/strings/utf_offset_string_conversions.h new file mode 100644 index 0000000000..1b615f4449 --- /dev/null +++ b/base/strings/utf_offset_string_conversions.h @@ -0,0 +1,93 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_STRINGS_UTF_OFFSET_STRING_CONVERSIONS_H_ +#define BASE_STRINGS_UTF_OFFSET_STRING_CONVERSIONS_H_ + +#include +#include + +#include "base/base_export.h" +#include "base/strings/string16.h" +#include "base/strings/string_piece.h" + +namespace base { + +// Like the conversions in utf_string_conversions.h, but also takes one or more +// offsets (|offset[s]_for_adjustment|) into the source strings, each offset +// will be adjusted to point at the same logical place in the result strings. +// If this isn't possible because an offset points past the end of the source +// strings or into the middle of a multibyte sequence, the offending offset will +// be set to string16::npos. |offset[s]_for_adjustment| may be NULL. +BASE_EXPORT bool UTF8ToUTF16AndAdjustOffset(const char* src, + size_t src_len, + string16* output, + size_t* offset_for_adjustment); +BASE_EXPORT bool UTF8ToUTF16AndAdjustOffsets( + const char* src, + size_t src_len, + string16* output, + std::vector* offsets_for_adjustment); + +BASE_EXPORT string16 UTF8ToUTF16AndAdjustOffset(const base::StringPiece& utf8, + size_t* offset_for_adjustment); +BASE_EXPORT string16 UTF8ToUTF16AndAdjustOffsets( + const base::StringPiece& utf8, + std::vector* offsets_for_adjustment); + +BASE_EXPORT std::string UTF16ToUTF8AndAdjustOffset( + const base::StringPiece16& utf16, + size_t* offset_for_adjustment); +BASE_EXPORT std::string UTF16ToUTF8AndAdjustOffsets( + const base::StringPiece16& utf16, + std::vector* offsets_for_adjustment); + +// Limiting function callable by std::for_each which will replace any value +// which is equal to or greater than |limit| with npos. +template +struct LimitOffset { + explicit LimitOffset(size_t limit) + : limit_(limit) {} + + void operator()(size_t& offset) { + if (offset >= limit_) + offset = T::npos; + } + + size_t limit_; +}; + +// Stack object which, on destruction, will update a vector of offsets based on +// any supplied adjustments. To use, declare one of these, providing the +// address of the offset vector to adjust. Then Add() any number of Adjustments +// (each Adjustment gives the |original_offset| of a substring and the lengths +// of the substring before and after transforming). When the OffsetAdjuster +// goes out of scope, all the offsets in the provided vector will be updated. +class BASE_EXPORT OffsetAdjuster { + public: + struct BASE_EXPORT Adjustment { + Adjustment(size_t original_offset, + size_t original_length, + size_t output_length); + + size_t original_offset; + size_t original_length; + size_t output_length; + }; + + explicit OffsetAdjuster(std::vector* offsets_for_adjustment); + ~OffsetAdjuster(); + + void Add(const Adjustment& adjustment); + + private: + void AdjustOffset(std::vector::iterator offset); + + std::vector* offsets_for_adjustment_; + std::vector adjustments_; +}; + +} // namespace base + +#endif // BASE_STRINGS_UTF_OFFSET_STRING_CONVERSIONS_H_ diff --git a/base/strings/utf_offset_string_conversions_unittest.cc b/base/strings/utf_offset_string_conversions_unittest.cc new file mode 100644 index 0000000000..5545c0d22e --- /dev/null +++ b/base/strings/utf_offset_string_conversions_unittest.cc @@ -0,0 +1,154 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/logging.h" +#include "base/strings/string_piece.h" +#include "base/strings/utf_offset_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace { + +static const size_t kNpos = string16::npos; + +} // namespace + +TEST(UTFOffsetStringConversionsTest, AdjustOffset) { + struct UTF8ToUTF16Case { + const char* utf8; + size_t input_offset; + size_t output_offset; + } utf8_to_utf16_cases[] = { + {"", 0, kNpos}, + {"\xe4\xbd\xa0\xe5\xa5\xbd", 1, kNpos}, + {"\xe4\xbd\xa0\xe5\xa5\xbd", 3, 1}, + {"\xed\xb0\x80z", 3, 1}, + {"A\xF0\x90\x8C\x80z", 1, 1}, + {"A\xF0\x90\x8C\x80z", 2, kNpos}, + {"A\xF0\x90\x8C\x80z", 5, 3}, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(utf8_to_utf16_cases); ++i) { + size_t offset = utf8_to_utf16_cases[i].input_offset; + UTF8ToUTF16AndAdjustOffset(utf8_to_utf16_cases[i].utf8, &offset); + EXPECT_EQ(utf8_to_utf16_cases[i].output_offset, offset); + } + + struct UTF16ToUTF8Case { + char16 utf16[10]; + size_t input_offset; + size_t output_offset; + } utf16_to_utf8_cases[] = { + {{}, 0, kNpos}, + // Converted to 3-byte utf-8 sequences + {{0x5909, 0x63DB}, 2, kNpos}, + {{0x5909, 0x63DB}, 1, 3}, + // Converted to 2-byte utf-8 sequences + {{'A', 0x00bc, 0x00be, 'z'}, 1, 1}, + {{'A', 0x00bc, 0x00be, 'z'}, 2, 3}, + {{'A', 0x00bc, 0x00be, 'z'}, 3, 5}, + // Surrogate pair + {{'A', 0xd800, 0xdf00, 'z'}, 1, 1}, + {{'A', 0xd800, 0xdf00, 'z'}, 2, kNpos}, + {{'A', 0xd800, 0xdf00, 'z'}, 3, 5}, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(utf16_to_utf8_cases); ++i) { + size_t offset = utf16_to_utf8_cases[i].input_offset; + UTF16ToUTF8AndAdjustOffset(utf16_to_utf8_cases[i].utf16, &offset); + EXPECT_EQ(utf16_to_utf8_cases[i].output_offset, offset); + } +} + +TEST(UTFOffsetStringConversionsTest, LimitOffsets) { + const size_t kLimit = 10; + const size_t kItems = 20; + std::vector size_ts; + for (size_t t = 0; t < kItems; ++t) + size_ts.push_back(t); + std::for_each(size_ts.begin(), size_ts.end(), + LimitOffset(kLimit)); + size_t unlimited_count = 0; + for (std::vector::iterator ti = size_ts.begin(); ti != size_ts.end(); + ++ti) { + if (*ti < kLimit && *ti != kNpos) + ++unlimited_count; + } + EXPECT_EQ(10U, unlimited_count); + + // Reverse the values in the vector and try again. + size_ts.clear(); + for (size_t t = kItems; t > 0; --t) + size_ts.push_back(t - 1); + std::for_each(size_ts.begin(), size_ts.end(), + LimitOffset(kLimit)); + unlimited_count = 0; + for (std::vector::iterator ti = size_ts.begin(); ti != size_ts.end(); + ++ti) { + if (*ti < kLimit && *ti != kNpos) + ++unlimited_count; + } + EXPECT_EQ(10U, unlimited_count); +} + +TEST(UTFOffsetStringConversionsTest, AdjustOffsets) { + // Imagine we have strings as shown in the following cases where the + // X's represent encoded characters. + // 1: abcXXXdef ==> abcXdef + { + std::vector offsets; + for (size_t t = 0; t < 9; ++t) + offsets.push_back(t); + { + OffsetAdjuster offset_adjuster(&offsets); + offset_adjuster.Add(OffsetAdjuster::Adjustment(3, 3, 1)); + } + size_t expected_1[] = {0, 1, 2, 3, kNpos, kNpos, 4, 5, 6}; + EXPECT_EQ(offsets.size(), arraysize(expected_1)); + for (size_t i = 0; i < arraysize(expected_1); ++i) + EXPECT_EQ(expected_1[i], offsets[i]); + } + + // 2: XXXaXXXXbcXXXXXXXdefXXX ==> XaXXbcXXXXdefX + { + std::vector offsets; + for (size_t t = 0; t < 23; ++t) + offsets.push_back(t); + { + OffsetAdjuster offset_adjuster(&offsets); + offset_adjuster.Add(OffsetAdjuster::Adjustment(0, 3, 1)); + offset_adjuster.Add(OffsetAdjuster::Adjustment(4, 4, 2)); + offset_adjuster.Add(OffsetAdjuster::Adjustment(10, 7, 4)); + offset_adjuster.Add(OffsetAdjuster::Adjustment(20, 3, 1)); + } + size_t expected_2[] = {0, kNpos, kNpos, 1, 2, kNpos, kNpos, kNpos, 4, 5, 6, + kNpos, kNpos, kNpos, kNpos, kNpos, kNpos, 10, 11, 12, + 13, kNpos, kNpos}; + EXPECT_EQ(offsets.size(), arraysize(expected_2)); + for (size_t i = 0; i < arraysize(expected_2); ++i) + EXPECT_EQ(expected_2[i], offsets[i]); + } + + // 3: XXXaXXXXbcdXXXeXX ==> aXXXXbcdXXXe + { + std::vector offsets; + for (size_t t = 0; t < 17; ++t) + offsets.push_back(t); + { + OffsetAdjuster offset_adjuster(&offsets); + offset_adjuster.Add(OffsetAdjuster::Adjustment(0, 3, 0)); + offset_adjuster.Add(OffsetAdjuster::Adjustment(4, 4, 4)); + offset_adjuster.Add(OffsetAdjuster::Adjustment(11, 3, 3)); + offset_adjuster.Add(OffsetAdjuster::Adjustment(15, 2, 0)); + } + size_t expected_3[] = {kNpos, kNpos, kNpos, 0, 1, kNpos, kNpos, kNpos, 5, 6, + 7, 8, kNpos, kNpos, 11, kNpos, kNpos}; + EXPECT_EQ(offsets.size(), arraysize(expected_3)); + for (size_t i = 0; i < arraysize(expected_3); ++i) + EXPECT_EQ(expected_3[i], offsets[i]); + } +} + +} // namaspace base diff --git a/base/strings/utf_string_conversion_utils.cc b/base/strings/utf_string_conversion_utils.cc new file mode 100644 index 0000000000..09a003d1b9 --- /dev/null +++ b/base/strings/utf_string_conversion_utils.cc @@ -0,0 +1,148 @@ +// 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/strings/utf_string_conversion_utils.h" + +#include "base/third_party/icu/icu_utf.h" + +namespace base { + +// ReadUnicodeCharacter -------------------------------------------------------- + +bool ReadUnicodeCharacter(const char* src, + int32 src_len, + int32* char_index, + uint32* code_point_out) { + // U8_NEXT expects to be able to use -1 to signal an error, so we must + // use a signed type for code_point. But this function returns false + // on error anyway, so code_point_out is unsigned. + int32 code_point; + CBU8_NEXT(src, *char_index, src_len, code_point); + *code_point_out = static_cast(code_point); + + // The ICU macro above moves to the next char, we want to point to the last + // char consumed. + (*char_index)--; + + // Validate the decoded value. + return IsValidCodepoint(code_point); +} + +bool ReadUnicodeCharacter(const char16* src, + int32 src_len, + int32* char_index, + uint32* code_point) { + if (CBU16_IS_SURROGATE(src[*char_index])) { + if (!CBU16_IS_SURROGATE_LEAD(src[*char_index]) || + *char_index + 1 >= src_len || + !CBU16_IS_TRAIL(src[*char_index + 1])) { + // Invalid surrogate pair. + return false; + } + + // Valid surrogate pair. + *code_point = CBU16_GET_SUPPLEMENTARY(src[*char_index], + src[*char_index + 1]); + (*char_index)++; + } else { + // Not a surrogate, just one 16-bit word. + *code_point = src[*char_index]; + } + + return IsValidCodepoint(*code_point); +} + +#if defined(WCHAR_T_IS_UTF32) +bool ReadUnicodeCharacter(const wchar_t* src, + int32 src_len, + int32* char_index, + uint32* code_point) { + // Conversion is easy since the source is 32-bit. + *code_point = src[*char_index]; + + // Validate the value. + return IsValidCodepoint(*code_point); +} +#endif // defined(WCHAR_T_IS_UTF32) + +// WriteUnicodeCharacter ------------------------------------------------------- + +size_t WriteUnicodeCharacter(uint32 code_point, std::string* output) { + if (code_point <= 0x7f) { + // Fast path the common case of one byte. + output->push_back(code_point); + return 1; + } + + + // CBU8_APPEND_UNSAFE can append up to 4 bytes. + size_t char_offset = output->length(); + size_t original_char_offset = char_offset; + output->resize(char_offset + CBU8_MAX_LENGTH); + + CBU8_APPEND_UNSAFE(&(*output)[0], char_offset, code_point); + + // CBU8_APPEND_UNSAFE will advance our pointer past the inserted character, so + // it will represent the new length of the string. + output->resize(char_offset); + return char_offset - original_char_offset; +} + +size_t WriteUnicodeCharacter(uint32 code_point, string16* output) { + if (CBU16_LENGTH(code_point) == 1) { + // Thie code point is in the Basic Multilingual Plane (BMP). + output->push_back(static_cast(code_point)); + return 1; + } + // Non-BMP characters use a double-character encoding. + size_t char_offset = output->length(); + output->resize(char_offset + CBU16_MAX_LENGTH); + CBU16_APPEND_UNSAFE(&(*output)[0], char_offset, code_point); + return CBU16_MAX_LENGTH; +} + +// Generalized Unicode converter ----------------------------------------------- + +template +void PrepareForUTF8Output(const CHAR* src, + size_t src_len, + std::string* output) { + output->clear(); + if (src_len == 0) + return; + if (src[0] < 0x80) { + // Assume that the entire input will be ASCII. + output->reserve(src_len); + } else { + // Assume that the entire input is non-ASCII and will have 3 bytes per char. + output->reserve(src_len * 3); + } +} + +// Instantiate versions we know callers will need. +template void PrepareForUTF8Output(const wchar_t*, size_t, std::string*); +template void PrepareForUTF8Output(const char16*, size_t, std::string*); + +template +void PrepareForUTF16Or32Output(const char* src, + size_t src_len, + STRING* output) { + output->clear(); + if (src_len == 0) + return; + if (static_cast(src[0]) < 0x80) { + // Assume the input is all ASCII, which means 1:1 correspondence. + output->reserve(src_len); + } else { + // Otherwise assume that the UTF-8 sequences will have 2 bytes for each + // character. + output->reserve(src_len / 2); + } +} + +// Instantiate versions we know callers will need. +template void PrepareForUTF16Or32Output(const char*, size_t, std::wstring*); +template void PrepareForUTF16Or32Output(const char*, size_t, string16*); + +} // namespace base diff --git a/base/strings/utf_string_conversion_utils.h b/base/strings/utf_string_conversion_utils.h new file mode 100644 index 0000000000..22abbbc9e7 --- /dev/null +++ b/base/strings/utf_string_conversion_utils.h @@ -0,0 +1,97 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_STRINGS_UTF_STRING_CONVERSION_UTILS_H_ +#define BASE_STRINGS_UTF_STRING_CONVERSION_UTILS_H_ + +// This should only be used by the various UTF string conversion files. + +#include "base/base_export.h" +#include "base/strings/string16.h" + +namespace base { + +inline bool IsValidCodepoint(uint32 code_point) { + // Excludes the surrogate code points ([0xD800, 0xDFFF]) and + // codepoints larger than 0x10FFFF (the highest codepoint allowed). + // Non-characters and unassigned codepoints are allowed. + return code_point < 0xD800u || + (code_point >= 0xE000u && code_point <= 0x10FFFFu); +} + +inline bool IsValidCharacter(uint32 code_point) { + // Excludes non-characters (U+FDD0..U+FDEF, and all codepoints ending in + // 0xFFFE or 0xFFFF) from the set of valid code points. + return code_point < 0xD800u || (code_point >= 0xE000u && + code_point < 0xFDD0u) || (code_point > 0xFDEFu && + code_point <= 0x10FFFFu && (code_point & 0xFFFEu) != 0xFFFEu); +} + +// ReadUnicodeCharacter -------------------------------------------------------- + +// Reads a UTF-8 stream, placing the next code point into the given output +// |*code_point|. |src| represents the entire string to read, and |*char_index| +// is the character offset within the string to start reading at. |*char_index| +// will be updated to index the last character read, such that incrementing it +// (as in a for loop) will take the reader to the next character. +// +// Returns true on success. On false, |*code_point| will be invalid. +BASE_EXPORT bool ReadUnicodeCharacter(const char* src, + int32 src_len, + int32* char_index, + uint32* code_point_out); + +// Reads a UTF-16 character. The usage is the same as the 8-bit version above. +BASE_EXPORT bool ReadUnicodeCharacter(const char16* src, + int32 src_len, + int32* char_index, + uint32* code_point); + +#if defined(WCHAR_T_IS_UTF32) +// Reads UTF-32 character. The usage is the same as the 8-bit version above. +BASE_EXPORT bool ReadUnicodeCharacter(const wchar_t* src, + int32 src_len, + int32* char_index, + uint32* code_point); +#endif // defined(WCHAR_T_IS_UTF32) + +// WriteUnicodeCharacter ------------------------------------------------------- + +// Appends a UTF-8 character to the given 8-bit string. Returns the number of +// bytes written. +// TODO(brettw) Bug 79631: This function should not be exposed. +BASE_EXPORT size_t WriteUnicodeCharacter(uint32 code_point, + std::string* output); + +// Appends the given code point as a UTF-16 character to the given 16-bit +// string. Returns the number of 16-bit values written. +BASE_EXPORT size_t WriteUnicodeCharacter(uint32 code_point, string16* output); + +#if defined(WCHAR_T_IS_UTF32) +// Appends the given UTF-32 character to the given 32-bit string. Returns the +// number of 32-bit values written. +inline size_t WriteUnicodeCharacter(uint32 code_point, std::wstring* output) { + // This is the easy case, just append the character. + output->push_back(code_point); + return 1; +} +#endif // defined(WCHAR_T_IS_UTF32) + +// Generalized Unicode converter ----------------------------------------------- + +// Guesses the length of the output in UTF-8 in bytes, clears that output +// string, and reserves that amount of space. We assume that the input +// character types are unsigned, which will be true for UTF-16 and -32 on our +// systems. +template +void PrepareForUTF8Output(const CHAR* src, size_t src_len, std::string* output); + +// Prepares an output buffer (containing either UTF-16 or -32 data) given some +// UTF-8 input that will be converted to it. See PrepareForUTF8Output(). +template +void PrepareForUTF16Or32Output(const char* src, size_t src_len, STRING* output); + +} // namespace base + +#endif // BASE_STRINGS_UTF_STRING_CONVERSION_UTILS_H_ diff --git a/base/strings/utf_string_conversions.cc b/base/strings/utf_string_conversions.cc new file mode 100644 index 0000000000..c3ea4f253e --- /dev/null +++ b/base/strings/utf_string_conversions.cc @@ -0,0 +1,185 @@ +// 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/strings/utf_string_conversions.h" + +#include "base/strings/string_piece.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversion_utils.h" + +namespace base { + +namespace { + +// Generalized Unicode converter ----------------------------------------------- + +// Converts the given source Unicode character type to the given destination +// Unicode character type as a STL string. The given input buffer and size +// determine the source, and the given output STL string will be replaced by +// the result. +template +bool ConvertUnicode(const SRC_CHAR* src, + size_t src_len, + DEST_STRING* output) { + // ICU requires 32-bit numbers. + bool success = true; + int32 src_len32 = static_cast(src_len); + for (int32 i = 0; i < src_len32; i++) { + uint32 code_point; + if (ReadUnicodeCharacter(src, src_len32, &i, &code_point)) { + WriteUnicodeCharacter(code_point, output); + } else { + WriteUnicodeCharacter(0xFFFD, output); + success = false; + } + } + + return success; +} + +} // namespace + +// UTF-8 <-> Wide -------------------------------------------------------------- + +bool WideToUTF8(const wchar_t* src, size_t src_len, std::string* output) { + PrepareForUTF8Output(src, src_len, output); + return ConvertUnicode(src, src_len, output); +} + +std::string WideToUTF8(const std::wstring& wide) { + std::string ret; + // Ignore the success flag of this call, it will do the best it can for + // invalid input, which is what we want here. + WideToUTF8(wide.data(), wide.length(), &ret); + return ret; +} + +bool UTF8ToWide(const char* src, size_t src_len, std::wstring* output) { + PrepareForUTF16Or32Output(src, src_len, output); + return ConvertUnicode(src, src_len, output); +} + +std::wstring UTF8ToWide(const StringPiece& utf8) { + std::wstring ret; + UTF8ToWide(utf8.data(), utf8.length(), &ret); + return ret; +} + +// UTF-16 <-> Wide ------------------------------------------------------------- + +#if defined(WCHAR_T_IS_UTF16) + +// When wide == UTF-16, then conversions are a NOP. +bool WideToUTF16(const wchar_t* src, size_t src_len, string16* output) { + output->assign(src, src_len); + return true; +} + +string16 WideToUTF16(const std::wstring& wide) { + return wide; +} + +bool UTF16ToWide(const char16* src, size_t src_len, std::wstring* output) { + output->assign(src, src_len); + return true; +} + +std::wstring UTF16ToWide(const string16& utf16) { + return utf16; +} + +#elif defined(WCHAR_T_IS_UTF32) + +bool WideToUTF16(const wchar_t* src, size_t src_len, string16* output) { + output->clear(); + // Assume that normally we won't have any non-BMP characters so the counts + // will be the same. + output->reserve(src_len); + return ConvertUnicode(src, src_len, output); +} + +string16 WideToUTF16(const std::wstring& wide) { + string16 ret; + WideToUTF16(wide.data(), wide.length(), &ret); + return ret; +} + +bool UTF16ToWide(const char16* src, size_t src_len, std::wstring* output) { + output->clear(); + // Assume that normally we won't have any non-BMP characters so the counts + // will be the same. + output->reserve(src_len); + return ConvertUnicode(src, src_len, output); +} + +std::wstring UTF16ToWide(const string16& utf16) { + std::wstring ret; + UTF16ToWide(utf16.data(), utf16.length(), &ret); + return ret; +} + +#endif // defined(WCHAR_T_IS_UTF32) + +// UTF16 <-> UTF8 -------------------------------------------------------------- + +#if defined(WCHAR_T_IS_UTF32) + +bool UTF8ToUTF16(const char* src, size_t src_len, string16* output) { + PrepareForUTF16Or32Output(src, src_len, output); + return ConvertUnicode(src, src_len, output); +} + +string16 UTF8ToUTF16(const StringPiece& utf8) { + string16 ret; + // Ignore the success flag of this call, it will do the best it can for + // invalid input, which is what we want here. + UTF8ToUTF16(utf8.data(), utf8.length(), &ret); + return ret; +} + +bool UTF16ToUTF8(const char16* src, size_t src_len, std::string* output) { + PrepareForUTF8Output(src, src_len, output); + return ConvertUnicode(src, src_len, output); +} + +std::string UTF16ToUTF8(const string16& utf16) { + std::string ret; + // Ignore the success flag of this call, it will do the best it can for + // invalid input, which is what we want here. + UTF16ToUTF8(utf16.data(), utf16.length(), &ret); + return ret; +} + +#elif defined(WCHAR_T_IS_UTF16) +// Easy case since we can use the "wide" versions we already wrote above. + +bool UTF8ToUTF16(const char* src, size_t src_len, string16* output) { + return UTF8ToWide(src, src_len, output); +} + +string16 UTF8ToUTF16(const StringPiece& utf8) { + return UTF8ToWide(utf8); +} + +bool UTF16ToUTF8(const char16* src, size_t src_len, std::string* output) { + return WideToUTF8(src, src_len, output); +} + +std::string UTF16ToUTF8(const string16& utf16) { + return WideToUTF8(utf16); +} + +#endif + +std::wstring ASCIIToWide(const StringPiece& ascii) { + DCHECK(IsStringASCII(ascii)) << ascii; + return std::wstring(ascii.begin(), ascii.end()); +} + +string16 ASCIIToUTF16(const StringPiece& ascii) { + DCHECK(IsStringASCII(ascii)) << ascii; + return string16(ascii.begin(), ascii.end()); +} + +} // namespace base diff --git a/base/strings/utf_string_conversions.h b/base/strings/utf_string_conversions.h new file mode 100644 index 0000000000..3461aa491d --- /dev/null +++ b/base/strings/utf_string_conversions.h @@ -0,0 +1,72 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_STRINGS_UTF_STRING_CONVERSIONS_H_ +#define BASE_STRINGS_UTF_STRING_CONVERSIONS_H_ + +#include + +#include "base/base_export.h" +#include "base/strings/string16.h" +#include "base/strings/string_piece.h" + +namespace base { + +// These convert between UTF-8, -16, and -32 strings. They are potentially slow, +// so avoid unnecessary conversions. The low-level versions return a boolean +// indicating whether the conversion was 100% valid. In this case, it will still +// do the best it can and put the result in the output buffer. The versions that +// return strings ignore this error and just return the best conversion +// possible. +BASE_EXPORT bool WideToUTF8(const wchar_t* src, size_t src_len, + std::string* output); +BASE_EXPORT std::string WideToUTF8(const std::wstring& wide); +BASE_EXPORT bool UTF8ToWide(const char* src, size_t src_len, + std::wstring* output); +BASE_EXPORT std::wstring UTF8ToWide(const StringPiece& utf8); + +BASE_EXPORT bool WideToUTF16(const wchar_t* src, size_t src_len, + string16* output); +BASE_EXPORT string16 WideToUTF16(const std::wstring& wide); +BASE_EXPORT bool UTF16ToWide(const char16* src, size_t src_len, + std::wstring* output); +BASE_EXPORT std::wstring UTF16ToWide(const string16& utf16); + +BASE_EXPORT bool UTF8ToUTF16(const char* src, size_t src_len, string16* output); +BASE_EXPORT string16 UTF8ToUTF16(const StringPiece& utf8); +BASE_EXPORT bool UTF16ToUTF8(const char16* src, size_t src_len, + std::string* output); +BASE_EXPORT std::string UTF16ToUTF8(const string16& utf16); + +// We are trying to get rid of wstring as much as possible, but it's too big +// a mess to do it all at once. These conversions should be used when we +// really should just be passing a string16 around, but we haven't finished +// porting whatever module uses wstring and the conversion is being used as a +// stopcock. This makes it easy to grep for the ones that should be removed. +#if defined(OS_WIN) +# define WideToUTF16Hack +# define UTF16ToWideHack +#else +# define WideToUTF16Hack WideToUTF16 +# define UTF16ToWideHack UTF16ToWide +#endif + +// These convert an ASCII string, typically a hardcoded constant, to a +// UTF16/Wide string. +BASE_EXPORT std::wstring ASCIIToWide(const StringPiece& ascii); +BASE_EXPORT string16 ASCIIToUTF16(const StringPiece& ascii); + +} // namespace base + +// TODO(brettw) remove these when callers are fixed up. +using base::WideToUTF8; +using base::UTF8ToWide; +using base::WideToUTF16; +using base::UTF16ToWide; +using base::UTF8ToUTF16; +using base::UTF16ToUTF8; +using base::ASCIIToWide; +using base::ASCIIToUTF16; + +#endif // BASE_STRINGS_UTF_STRING_CONVERSIONS_H_ diff --git a/base/strings/utf_string_conversions_unittest.cc b/base/strings/utf_string_conversions_unittest.cc new file mode 100644 index 0000000000..08009c4f38 --- /dev/null +++ b/base/strings/utf_string_conversions_unittest.cc @@ -0,0 +1,211 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/strings/string_piece.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace { + +const wchar_t* const kConvertRoundtripCases[] = { + L"Google Video", + // "网页 图片 资讯更多 »" + L"\x7f51\x9875\x0020\x56fe\x7247\x0020\x8d44\x8baf\x66f4\x591a\x0020\x00bb", + // "Παγκόσμιος Ιστός" + L"\x03a0\x03b1\x03b3\x03ba\x03cc\x03c3\x03bc\x03b9" + L"\x03bf\x03c2\x0020\x0399\x03c3\x03c4\x03cc\x03c2", + // "ПоиÑк Ñтраниц на руÑÑком" + L"\x041f\x043e\x0438\x0441\x043a\x0020\x0441\x0442" + L"\x0440\x0430\x043d\x0438\x0446\x0020\x043d\x0430" + L"\x0020\x0440\x0443\x0441\x0441\x043a\x043e\x043c", + // "전체서비스" + L"\xc804\xccb4\xc11c\xbe44\xc2a4", + + // Test characters that take more than 16 bits. This will depend on whether + // wchar_t is 16 or 32 bits. +#if defined(WCHAR_T_IS_UTF16) + L"\xd800\xdf00", + // ????? (Mathematical Alphanumeric Symbols (U+011d40 - U+011d44 : A,B,C,D,E) + L"\xd807\xdd40\xd807\xdd41\xd807\xdd42\xd807\xdd43\xd807\xdd44", +#elif defined(WCHAR_T_IS_UTF32) + L"\x10300", + // ????? (Mathematical Alphanumeric Symbols (U+011d40 - U+011d44 : A,B,C,D,E) + L"\x11d40\x11d41\x11d42\x11d43\x11d44", +#endif +}; + +} // namespace + +TEST(UTFStringConversionsTest, ConvertUTF8AndWide) { + // we round-trip all the wide strings through UTF-8 to make sure everything + // agrees on the conversion. This uses the stream operators to test them + // simultaneously. + for (size_t i = 0; i < arraysize(kConvertRoundtripCases); ++i) { + std::ostringstream utf8; + utf8 << WideToUTF8(kConvertRoundtripCases[i]); + std::wostringstream wide; + wide << UTF8ToWide(utf8.str()); + + EXPECT_EQ(kConvertRoundtripCases[i], wide.str()); + } +} + +TEST(UTFStringConversionsTest, ConvertUTF8AndWideEmptyString) { + // An empty std::wstring should be converted to an empty std::string, + // and vice versa. + std::wstring wempty; + std::string empty; + EXPECT_EQ(empty, WideToUTF8(wempty)); + EXPECT_EQ(wempty, UTF8ToWide(empty)); +} + +TEST(UTFStringConversionsTest, ConvertUTF8ToWide) { + struct UTF8ToWideCase { + const char* utf8; + const wchar_t* wide; + bool success; + } convert_cases[] = { + // Regular UTF-8 input. + {"\xe4\xbd\xa0\xe5\xa5\xbd", L"\x4f60\x597d", true}, + // Non-character is passed through. + {"\xef\xbf\xbfHello", L"\xffffHello", true}, + // Truncated UTF-8 sequence. + {"\xe4\xa0\xe5\xa5\xbd", L"\xfffd\x597d", false}, + // Truncated off the end. + {"\xe5\xa5\xbd\xe4\xa0", L"\x597d\xfffd", false}, + // Non-shortest-form UTF-8. + {"\xf0\x84\xbd\xa0\xe5\xa5\xbd", L"\xfffd\x597d", false}, + // This UTF-8 character decodes to a UTF-16 surrogate, which is illegal. + {"\xed\xb0\x80", L"\xfffd", false}, + // Non-BMP characters. The second is a non-character regarded as valid. + // The result will either be in UTF-16 or UTF-32. +#if defined(WCHAR_T_IS_UTF16) + {"A\xF0\x90\x8C\x80z", L"A\xd800\xdf00z", true}, + {"A\xF4\x8F\xBF\xBEz", L"A\xdbff\xdffez", true}, +#elif defined(WCHAR_T_IS_UTF32) + {"A\xF0\x90\x8C\x80z", L"A\x10300z", true}, + {"A\xF4\x8F\xBF\xBEz", L"A\x10fffez", true}, +#endif + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(convert_cases); i++) { + std::wstring converted; + EXPECT_EQ(convert_cases[i].success, + UTF8ToWide(convert_cases[i].utf8, + strlen(convert_cases[i].utf8), + &converted)); + std::wstring expected(convert_cases[i].wide); + EXPECT_EQ(expected, converted); + } + + // Manually test an embedded NULL. + std::wstring converted; + EXPECT_TRUE(UTF8ToWide("\00Z\t", 3, &converted)); + ASSERT_EQ(3U, converted.length()); + EXPECT_EQ(static_cast(0), converted[0]); + EXPECT_EQ('Z', converted[1]); + EXPECT_EQ('\t', converted[2]); + + // Make sure that conversion replaces, not appends. + EXPECT_TRUE(UTF8ToWide("B", 1, &converted)); + ASSERT_EQ(1U, converted.length()); + EXPECT_EQ('B', converted[0]); +} + +#if defined(WCHAR_T_IS_UTF16) +// This test is only valid when wchar_t == UTF-16. +TEST(UTFStringConversionsTest, ConvertUTF16ToUTF8) { + struct WideToUTF8Case { + const wchar_t* utf16; + const char* utf8; + bool success; + } convert_cases[] = { + // Regular UTF-16 input. + {L"\x4f60\x597d", "\xe4\xbd\xa0\xe5\xa5\xbd", true}, + // Test a non-BMP character. + {L"\xd800\xdf00", "\xF0\x90\x8C\x80", true}, + // Non-characters are passed through. + {L"\xffffHello", "\xEF\xBF\xBFHello", true}, + {L"\xdbff\xdffeHello", "\xF4\x8F\xBF\xBEHello", true}, + // The first character is a truncated UTF-16 character. + {L"\xd800\x597d", "\xef\xbf\xbd\xe5\xa5\xbd", false}, + // Truncated at the end. + {L"\x597d\xd800", "\xe5\xa5\xbd\xef\xbf\xbd", false}, + }; + + for (int i = 0; i < arraysize(convert_cases); i++) { + std::string converted; + EXPECT_EQ(convert_cases[i].success, + WideToUTF8(convert_cases[i].utf16, + wcslen(convert_cases[i].utf16), + &converted)); + std::string expected(convert_cases[i].utf8); + EXPECT_EQ(expected, converted); + } +} + +#elif defined(WCHAR_T_IS_UTF32) +// This test is only valid when wchar_t == UTF-32. +TEST(UTFStringConversionsTest, ConvertUTF32ToUTF8) { + struct WideToUTF8Case { + const wchar_t* utf32; + const char* utf8; + bool success; + } convert_cases[] = { + // Regular 16-bit input. + {L"\x4f60\x597d", "\xe4\xbd\xa0\xe5\xa5\xbd", true}, + // Test a non-BMP character. + {L"A\x10300z", "A\xF0\x90\x8C\x80z", true}, + // Non-characters are passed through. + {L"\xffffHello", "\xEF\xBF\xBFHello", true}, + {L"\x10fffeHello", "\xF4\x8F\xBF\xBEHello", true}, + // Invalid Unicode code points. + {L"\xfffffffHello", "\xEF\xBF\xBDHello", false}, + // The first character is a truncated UTF-16 character. + {L"\xd800\x597d", "\xef\xbf\xbd\xe5\xa5\xbd", false}, + {L"\xdc01Hello", "\xef\xbf\xbdHello", false}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(convert_cases); i++) { + std::string converted; + EXPECT_EQ(convert_cases[i].success, + WideToUTF8(convert_cases[i].utf32, + wcslen(convert_cases[i].utf32), + &converted)); + std::string expected(convert_cases[i].utf8); + EXPECT_EQ(expected, converted); + } +} +#endif // defined(WCHAR_T_IS_UTF32) + +TEST(UTFStringConversionsTest, ConvertMultiString) { + static wchar_t wmulti[] = { + L'f', L'o', L'o', L'\0', + L'b', L'a', L'r', L'\0', + L'b', L'a', L'z', L'\0', + L'\0' + }; + static char multi[] = { + 'f', 'o', 'o', '\0', + 'b', 'a', 'r', '\0', + 'b', 'a', 'z', '\0', + '\0' + }; + std::wstring wmultistring; + memcpy(WriteInto(&wmultistring, arraysize(wmulti)), wmulti, sizeof(wmulti)); + EXPECT_EQ(arraysize(wmulti) - 1, wmultistring.length()); + std::string expected; + memcpy(WriteInto(&expected, arraysize(multi)), multi, sizeof(multi)); + EXPECT_EQ(arraysize(multi) - 1, expected.length()); + const std::string& converted = WideToUTF8(wmultistring); + EXPECT_EQ(arraysize(multi) - 1, converted.length()); + EXPECT_EQ(expected, converted); +} + +} // base diff --git a/base/supports_user_data.cc b/base/supports_user_data.cc new file mode 100644 index 0000000000..2a0263ed0d --- /dev/null +++ b/base/supports_user_data.cc @@ -0,0 +1,40 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/supports_user_data.h" + +namespace base { + +SupportsUserData::SupportsUserData() { + // Harmless to construct on a different thread to subsequent usage. + thread_checker_.DetachFromThread(); +} + +SupportsUserData::Data* SupportsUserData::GetUserData(const void* key) const { + DCHECK(thread_checker_.CalledOnValidThread()); + DataMap::const_iterator found = user_data_.find(key); + if (found != user_data_.end()) + return found->second.get(); + return NULL; +} + +void SupportsUserData::SetUserData(const void* key, Data* data) { + DCHECK(thread_checker_.CalledOnValidThread()); + user_data_[key] = linked_ptr(data); +} + +void SupportsUserData::RemoveUserData(const void* key) { + DCHECK(thread_checker_.CalledOnValidThread()); + user_data_.erase(key); +} + +void SupportsUserData::DetachUserDataThread() { + thread_checker_.DetachFromThread(); +} + +SupportsUserData::~SupportsUserData() { + DCHECK(thread_checker_.CalledOnValidThread() || user_data_.empty()); +} + +} // namespace base diff --git a/base/supports_user_data.h b/base/supports_user_data.h new file mode 100644 index 0000000000..77367553da --- /dev/null +++ b/base/supports_user_data.h @@ -0,0 +1,81 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_SUPPORTS_USER_DATA_H_ +#define BASE_SUPPORTS_USER_DATA_H_ + +#include + +#include "base/base_export.h" +#include "base/memory/linked_ptr.h" +#include "base/memory/ref_counted.h" +#include "base/threading/thread_checker.h" + +namespace base { + +// This is a helper for classes that want to allow users to stash random data by +// key. At destruction all the objects will be destructed. +class BASE_EXPORT SupportsUserData { + public: + SupportsUserData(); + + // Derive from this class and add your own data members to associate extra + // information with this object. Alternatively, add this as a public base + // class to any class with a virtual destructor. + class BASE_EXPORT Data { + public: + virtual ~Data() {} + }; + + // The user data allows the clients to associate data with this object. + // Multiple user data values can be stored under different keys. + // This object will TAKE OWNERSHIP of the given data pointer, and will + // delete the object if it is changed or the object is destroyed. + Data* GetUserData(const void* key) const; + void SetUserData(const void* key, Data* data); + void RemoveUserData(const void* key); + + // SupportsUserData is not thread-safe, and on debug build will assert it is + // only used on one thread. Calling this method allows the caller to hand + // the SupportsUserData instance across threads. Use only if you are taking + // full control of the synchronization of that hand over. + void DetachUserDataThread(); + + protected: + virtual ~SupportsUserData(); + + private: + typedef std::map > DataMap; + + // Externally-defined data accessible by key. + DataMap user_data_; + // Guards usage of |user_data_| + ThreadChecker thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(SupportsUserData); +}; + +// Adapter class that releases a refcounted object when the +// SupportsUserData::Data object is deleted. +template +class UserDataAdapter : public base::SupportsUserData::Data { + public: + static T* Get(SupportsUserData* supports_user_data, const char* key) { + UserDataAdapter* data = + static_cast(supports_user_data->GetUserData(key)); + return data ? static_cast(data->object_.get()) : NULL; + } + + UserDataAdapter(T* object) : object_(object) {} + T* release() { return object_.release(); } + + private: + scoped_refptr object_; + + DISALLOW_COPY_AND_ASSIGN(UserDataAdapter); +}; + +} // namespace base + +#endif // BASE_SUPPORTS_USER_DATA_H_ diff --git a/base/sync_socket.h b/base/sync_socket.h new file mode 100644 index 0000000000..8ba3f6c265 --- /dev/null +++ b/base/sync_socket.h @@ -0,0 +1,130 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_SYNC_SOCKET_H_ +#define BASE_SYNC_SOCKET_H_ + +// A socket abstraction used for sending and receiving plain +// data. Because the receiving is blocking, they can be used to perform +// rudimentary cross-process synchronization with low latency. + +#include "base/basictypes.h" +#if defined(OS_WIN) +#include +#endif +#include + +#include "base/base_export.h" +#include "base/compiler_specific.h" +#include "base/synchronization/waitable_event.h" + +namespace base { + +class BASE_EXPORT SyncSocket { + public: +#if defined(OS_WIN) + typedef HANDLE Handle; +#else + typedef int Handle; +#endif + static const Handle kInvalidHandle; + + SyncSocket(); + + // Creates a SyncSocket from a Handle. Used in transport. + explicit SyncSocket(Handle handle) : handle_(handle) {} + virtual ~SyncSocket(); + + // Initializes and connects a pair of sockets. + // |socket_a| and |socket_b| must not hold a valid handle. Upon successful + // return, the sockets will both be valid and connected. + static bool CreatePair(SyncSocket* socket_a, SyncSocket* socket_b); + + // Closes the SyncSocket. Returns true on success, false on failure. + virtual bool Close(); + + // Sends the message to the remote peer of the SyncSocket. + // Note it is not safe to send messages from the same socket handle by + // multiple threads simultaneously. + // buffer is a pointer to the data to send. + // length is the length of the data to send (must be non-zero). + // Returns the number of bytes sent, or 0 upon failure. + virtual size_t Send(const void* buffer, size_t length); + + // Receives a message from an SyncSocket. + // buffer is a pointer to the buffer to receive data. + // length is the number of bytes of data to receive (must be non-zero). + // Returns the number of bytes received, or 0 upon failure. + virtual size_t Receive(void* buffer, size_t length); + + // Returns the number of bytes available. If non-zero, Receive() will not + // not block when called. NOTE: Some implementations cannot reliably + // determine the number of bytes available so avoid using the returned + // size as a promise and simply test against zero. + size_t Peek(); + + // Extracts the contained handle. Used for transferring between + // processes. + Handle handle() const { return handle_; } + + protected: + Handle handle_; + + private: + DISALLOW_COPY_AND_ASSIGN(SyncSocket); +}; + +// Derives from SyncSocket and adds support for shutting down the socket from +// another thread while a blocking Receive or Send is being done from the +// thread that owns the socket. +class BASE_EXPORT CancelableSyncSocket : public SyncSocket { + public: + CancelableSyncSocket(); + explicit CancelableSyncSocket(Handle handle); + virtual ~CancelableSyncSocket() {} + + // Initializes a pair of cancelable sockets. See documentation for + // SyncSocket::CreatePair for more details. + static bool CreatePair(CancelableSyncSocket* socket_a, + CancelableSyncSocket* socket_b); + + // A way to shut down a socket even if another thread is currently performing + // a blocking Receive or Send. + bool Shutdown(); + +#if defined(OS_WIN) + // Since the Linux and Mac implementations actually use a socket, shutting + // them down from another thread is pretty simple - we can just call + // shutdown(). However, the Windows implementation relies on named pipes + // and there isn't a way to cancel a blocking synchronous Read that is + // supported on +#include +#include +#include + +#include "base/logging.h" + + +namespace base { + +const SyncSocket::Handle SyncSocket::kInvalidHandle = -1; + +SyncSocket::SyncSocket() : handle_(kInvalidHandle) { +} + +SyncSocket::~SyncSocket() { +} + +// static +bool SyncSocket::CreatePair(SyncSocket* socket_a, SyncSocket* socket_b) { + return false; +} + +bool SyncSocket::Close() { + if (handle_ != kInvalidHandle) { + if (close(handle_) < 0) + DPLOG(ERROR) << "close"; + handle_ = -1; + } + return true; +} + +size_t SyncSocket::Send(const void* buffer, size_t length) { + // Not implemented since it's not needed by any client code yet. + return -1; +} + +size_t SyncSocket::Receive(void* buffer, size_t length) { + return read(handle_, buffer, length); +} + +size_t SyncSocket::Peek() { + return -1; +} + +CancelableSyncSocket::CancelableSyncSocket() { +} + +CancelableSyncSocket::CancelableSyncSocket(Handle handle) + : SyncSocket(handle) { +} + +size_t CancelableSyncSocket::Send(const void* buffer, size_t length) { + return -1; +} + +bool CancelableSyncSocket::Shutdown() { + return false; +} + +// static +bool CancelableSyncSocket::CreatePair(CancelableSyncSocket* socket_a, + CancelableSyncSocket* socket_b) { + return SyncSocket::CreatePair(socket_a, socket_b); +} + +} // namespace base diff --git a/base/sync_socket_posix.cc b/base/sync_socket_posix.cc new file mode 100644 index 0000000000..257916df33 --- /dev/null +++ b/base/sync_socket_posix.cc @@ -0,0 +1,154 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/sync_socket.h" + +#include +#include +#include +#include +#include +#include +#include + +#if defined(OS_SOLARIS) +#include +#endif + +#include "base/file_util.h" +#include "base/logging.h" + + +namespace base { + +namespace { +// To avoid users sending negative message lengths to Send/Receive +// we clamp message lengths, which are size_t, to no more than INT_MAX. +const size_t kMaxMessageLength = static_cast(INT_MAX); + +} // namespace + +const SyncSocket::Handle SyncSocket::kInvalidHandle = -1; + +SyncSocket::SyncSocket() : handle_(kInvalidHandle) {} + +SyncSocket::~SyncSocket() { + Close(); +} + +// static +bool SyncSocket::CreatePair(SyncSocket* socket_a, SyncSocket* socket_b) { + DCHECK(socket_a != socket_b); + DCHECK(socket_a->handle_ == kInvalidHandle); + DCHECK(socket_b->handle_ == kInvalidHandle); + +#if defined(OS_MACOSX) + int nosigpipe = 1; +#endif // defined(OS_MACOSX) + + Handle handles[2] = { kInvalidHandle, kInvalidHandle }; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, handles) != 0) + goto cleanup; + +#if defined(OS_MACOSX) + // On OSX an attempt to read or write to a closed socket may generate a + // SIGPIPE rather than returning -1. setsockopt will shut this off. + if (0 != setsockopt(handles[0], SOL_SOCKET, SO_NOSIGPIPE, + &nosigpipe, sizeof nosigpipe) || + 0 != setsockopt(handles[1], SOL_SOCKET, SO_NOSIGPIPE, + &nosigpipe, sizeof nosigpipe)) { + goto cleanup; + } +#endif + + // Copy the handles out for successful return. + socket_a->handle_ = handles[0]; + socket_b->handle_ = handles[1]; + + return true; + + cleanup: + if (handles[0] != kInvalidHandle) { + if (HANDLE_EINTR(close(handles[0])) < 0) + DPLOG(ERROR) << "close"; + } + if (handles[1] != kInvalidHandle) { + if (HANDLE_EINTR(close(handles[1])) < 0) + DPLOG(ERROR) << "close"; + } + + return false; +} + +bool SyncSocket::Close() { + if (handle_ == kInvalidHandle) { + return false; + } + int retval = HANDLE_EINTR(close(handle_)); + if (retval < 0) + DPLOG(ERROR) << "close"; + handle_ = kInvalidHandle; + return (retval == 0); +} + +size_t SyncSocket::Send(const void* buffer, size_t length) { + DCHECK_LE(length, kMaxMessageLength); + const char* charbuffer = static_cast(buffer); + int len = file_util::WriteFileDescriptor(handle_, charbuffer, length); + + return (len == -1) ? 0 : static_cast(len); +} + +size_t SyncSocket::Receive(void* buffer, size_t length) { + DCHECK_LE(length, kMaxMessageLength); + char* charbuffer = static_cast(buffer); + if (file_util::ReadFromFD(handle_, charbuffer, length)) + return length; + return 0; +} + +size_t SyncSocket::Peek() { + int number_chars; + if (-1 == ioctl(handle_, FIONREAD, &number_chars)) { + // If there is an error in ioctl, signal that the channel would block. + return 0; + } + return (size_t) number_chars; +} + +CancelableSyncSocket::CancelableSyncSocket() {} +CancelableSyncSocket::CancelableSyncSocket(Handle handle) + : SyncSocket(handle) { +} + +bool CancelableSyncSocket::Shutdown() { + return HANDLE_EINTR(shutdown(handle(), SHUT_RDWR)) >= 0; +} + +size_t CancelableSyncSocket::Send(const void* buffer, size_t length) { + long flags = 0; + flags = fcntl(handle_, F_GETFL, NULL); + if (flags != -1 && (flags & O_NONBLOCK) == 0) { + // Set the socket to non-blocking mode for sending if its original mode + // is blocking. + fcntl(handle_, F_SETFL, flags | O_NONBLOCK); + } + + size_t len = SyncSocket::Send(buffer, length); + + if (flags != -1 && (flags & O_NONBLOCK) == 0) { + // Restore the original flags. + fcntl(handle_, F_SETFL, flags); + } + + return len; +} + +// static +bool CancelableSyncSocket::CreatePair(CancelableSyncSocket* socket_a, + CancelableSyncSocket* socket_b) { + return SyncSocket::CreatePair(socket_a, socket_b); +} + +} // namespace base diff --git a/base/sync_socket_win.cc b/base/sync_socket_win.cc new file mode 100644 index 0000000000..99a6afea3e --- /dev/null +++ b/base/sync_socket_win.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/sync_socket.h" + +#include "base/logging.h" +#include "base/win/scoped_handle.h" + +namespace base { + +using win::ScopedHandle; + +namespace { +// IMPORTANT: do not change how this name is generated because it will break +// in sandboxed scenarios as we might have by-name policies that allow pipe +// creation. Also keep the secure random number generation. +const wchar_t kPipeNameFormat[] = L"\\\\.\\pipe\\chrome.sync.%u.%u.%lu"; +const size_t kPipePathMax = arraysize(kPipeNameFormat) + (3 * 10) + 1; + +// To avoid users sending negative message lengths to Send/Receive +// we clamp message lengths, which are size_t, to no more than INT_MAX. +const size_t kMaxMessageLength = static_cast(INT_MAX); + +const int kOutBufferSize = 4096; +const int kInBufferSize = 4096; +const int kDefaultTimeoutMilliSeconds = 1000; + +bool CreatePairImpl(HANDLE* socket_a, HANDLE* socket_b, bool overlapped) { + DCHECK(socket_a != socket_b); + DCHECK(*socket_a == SyncSocket::kInvalidHandle); + DCHECK(*socket_b == SyncSocket::kInvalidHandle); + + wchar_t name[kPipePathMax]; + ScopedHandle handle_a; + DWORD flags = PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE; + if (overlapped) + flags |= FILE_FLAG_OVERLAPPED; + + do { + unsigned int rnd_name; + if (rand_s(&rnd_name) != 0) + return false; + + swprintf(name, kPipePathMax, + kPipeNameFormat, + GetCurrentProcessId(), + GetCurrentThreadId(), + rnd_name); + + handle_a.Set(CreateNamedPipeW( + name, + flags, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, + 1, + kOutBufferSize, + kInBufferSize, + kDefaultTimeoutMilliSeconds, + NULL)); + } while (!handle_a.IsValid() && + (GetLastError() == ERROR_PIPE_BUSY)); + + if (!handle_a.IsValid()) { + NOTREACHED(); + return false; + } + + // The SECURITY_ANONYMOUS flag means that the server side (handle_a) cannot + // impersonate the client (handle_b). This allows us not to care which side + // ends up in which side of a privilege boundary. + flags = SECURITY_SQOS_PRESENT | SECURITY_ANONYMOUS; + if (overlapped) + flags |= FILE_FLAG_OVERLAPPED; + + ScopedHandle handle_b(CreateFileW(name, + GENERIC_READ | GENERIC_WRITE, + 0, // no sharing. + NULL, // default security attributes. + OPEN_EXISTING, // opens existing pipe. + flags, + NULL)); // no template file. + if (!handle_b.IsValid()) { + DPLOG(ERROR) << "CreateFileW failed"; + return false; + } + + if (!ConnectNamedPipe(handle_a, NULL)) { + DWORD error = GetLastError(); + if (error != ERROR_PIPE_CONNECTED) { + DPLOG(ERROR) << "ConnectNamedPipe failed"; + return false; + } + } + + *socket_a = handle_a.Take(); + *socket_b = handle_b.Take(); + + return true; +} + +// Inline helper to avoid having the cast everywhere. +DWORD GetNextChunkSize(size_t current_pos, size_t max_size) { + // The following statement is for 64 bit portability. + return static_cast(((max_size - current_pos) <= UINT_MAX) ? + (max_size - current_pos) : UINT_MAX); +} + +// Template function that supports calling ReadFile or WriteFile in an +// overlapped fashion and waits for IO completion. The function also waits +// on an event that can be used to cancel the operation. If the operation +// is cancelled, the function returns and closes the relevant socket object. +template +size_t CancelableFileOperation(Function operation, HANDLE file, + BufferType* buffer, size_t length, + base::WaitableEvent* io_event, + base::WaitableEvent* cancel_event, + CancelableSyncSocket* socket, + DWORD timeout_in_ms) { + // The buffer must be byte size or the length check won't make much sense. + COMPILE_ASSERT(sizeof(buffer[0]) == sizeof(char), incorrect_buffer_type); + DCHECK_LE(length, kMaxMessageLength); + + OVERLAPPED ol = {0}; + ol.hEvent = io_event->handle(); + size_t count = 0; + while (count < length) { + DWORD chunk = GetNextChunkSize(count, length); + // This is either the ReadFile or WriteFile call depending on whether + // we're receiving or sending data. + DWORD len = 0; + BOOL ok = operation(file, static_cast(buffer) + count, chunk, + &len, &ol); + if (!ok) { + if (::GetLastError() == ERROR_IO_PENDING) { + HANDLE events[] = { io_event->handle(), cancel_event->handle() }; + int wait_result = WaitForMultipleObjects( + arraysize(events), events, FALSE, timeout_in_ms); + if (wait_result == (WAIT_OBJECT_0 + 0)) { + GetOverlappedResult(file, &ol, &len, TRUE); + } else if (wait_result == (WAIT_OBJECT_0 + 1)) { + VLOG(1) << "Shutdown was signaled. Closing socket."; + CancelIo(file); + socket->Close(); + count = 0; + break; + } else { + // Timeout happened. + DCHECK_EQ(WAIT_TIMEOUT, wait_result); + if (!CancelIo(file)){ + DLOG(WARNING) << "CancelIo() failed"; + } + break; + } + } else { + break; + } + } + + count += len; + + // Quit the operation if we can't write/read anymore. + if (len != chunk) + break; + } + + return (count > 0) ? count : 0; +} + +} // namespace + +#if defined(COMPONENT_BUILD) +const SyncSocket::Handle SyncSocket::kInvalidHandle = INVALID_HANDLE_VALUE; +#endif + +SyncSocket::SyncSocket() : handle_(kInvalidHandle) {} + +SyncSocket::~SyncSocket() { + Close(); +} + +// static +bool SyncSocket::CreatePair(SyncSocket* socket_a, SyncSocket* socket_b) { + return CreatePairImpl(&socket_a->handle_, &socket_b->handle_, false); +} + +bool SyncSocket::Close() { + if (handle_ == kInvalidHandle) + return false; + + BOOL retval = CloseHandle(handle_); + handle_ = kInvalidHandle; + return retval ? true : false; +} + +size_t SyncSocket::Send(const void* buffer, size_t length) { + DCHECK_LE(length, kMaxMessageLength); + size_t count = 0; + while (count < length) { + DWORD len; + DWORD chunk = GetNextChunkSize(count, length); + if (WriteFile(handle_, static_cast(buffer) + count, + chunk, &len, NULL) == FALSE) { + return (0 < count) ? count : 0; + } + count += len; + } + return count; +} + +size_t SyncSocket::Receive(void* buffer, size_t length) { + DCHECK_LE(length, kMaxMessageLength); + size_t count = 0; + while (count < length) { + DWORD len; + DWORD chunk = GetNextChunkSize(count, length); + if (ReadFile(handle_, static_cast(buffer) + count, + chunk, &len, NULL) == FALSE) { + return (0 < count) ? count : 0; + } + count += len; + } + return count; +} + +size_t SyncSocket::Peek() { + DWORD available = 0; + PeekNamedPipe(handle_, NULL, 0, NULL, &available, NULL); + return available; +} + +CancelableSyncSocket::CancelableSyncSocket() + : shutdown_event_(true, false), file_operation_(true, false) { +} + +CancelableSyncSocket::CancelableSyncSocket(Handle handle) + : SyncSocket(handle), shutdown_event_(true, false), + file_operation_(true, false) { +} + +bool CancelableSyncSocket::Shutdown() { + // This doesn't shut down the pipe immediately, but subsequent Receive or Send + // methods will fail straight away. + shutdown_event_.Signal(); + return true; +} + +bool CancelableSyncSocket::Close() { + bool ret = SyncSocket::Close(); + shutdown_event_.Reset(); + return ret; +} + +size_t CancelableSyncSocket::Send(const void* buffer, size_t length) { + static const DWORD kWaitTimeOutInMs = 500; + return CancelableFileOperation( + &WriteFile, handle_, reinterpret_cast(buffer), + length, &file_operation_, &shutdown_event_, this, kWaitTimeOutInMs); +} + +size_t CancelableSyncSocket::Receive(void* buffer, size_t length) { + return CancelableFileOperation(&ReadFile, handle_, + reinterpret_cast(buffer), length, &file_operation_, + &shutdown_event_, this, INFINITE); +} + +// static +bool CancelableSyncSocket::CreatePair(CancelableSyncSocket* socket_a, + CancelableSyncSocket* socket_b) { + return CreatePairImpl(&socket_a->handle_, &socket_b->handle_, true); +} + + +} // namespace base diff --git a/base/synchronization/cancellation_flag.cc b/base/synchronization/cancellation_flag.cc new file mode 100644 index 0000000000..ad3b551169 --- /dev/null +++ b/base/synchronization/cancellation_flag.cc @@ -0,0 +1,22 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/synchronization/cancellation_flag.h" + +#include "base/logging.h" + +namespace base { + +void CancellationFlag::Set() { +#if !defined(NDEBUG) + DCHECK_EQ(set_on_, PlatformThread::CurrentId()); +#endif + base::subtle::Release_Store(&flag_, 1); +} + +bool CancellationFlag::IsSet() const { + return base::subtle::Acquire_Load(&flag_) != 0; +} + +} // namespace base diff --git a/base/synchronization/cancellation_flag.h b/base/synchronization/cancellation_flag.h new file mode 100644 index 0000000000..51a4def1eb --- /dev/null +++ b/base/synchronization/cancellation_flag.h @@ -0,0 +1,43 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_SYNCHRONIZATION_CANCELLATION_FLAG_H_ +#define BASE_SYNCHRONIZATION_CANCELLATION_FLAG_H_ + +#include "base/base_export.h" +#include "base/atomicops.h" +#include "base/threading/platform_thread.h" + +namespace base { + +// CancellationFlag allows one thread to cancel jobs executed on some worker +// thread. Calling Set() from one thread and IsSet() from a number of threads +// is thread-safe. +// +// This class IS NOT intended for synchronization between threads. +class BASE_EXPORT CancellationFlag { + public: + CancellationFlag() : flag_(false) { +#if !defined(NDEBUG) + set_on_ = PlatformThread::CurrentId(); +#endif + } + ~CancellationFlag() {} + + // Set the flag. May only be called on the thread which owns the object. + void Set(); + bool IsSet() const; // Returns true iff the flag was set. + + private: + base::subtle::Atomic32 flag_; +#if !defined(NDEBUG) + PlatformThreadId set_on_; +#endif + + DISALLOW_COPY_AND_ASSIGN(CancellationFlag); +}; + +} // namespace base + +#endif // BASE_SYNCHRONIZATION_CANCELLATION_FLAG_H_ diff --git a/base/synchronization/cancellation_flag_unittest.cc b/base/synchronization/cancellation_flag_unittest.cc new file mode 100644 index 0000000000..02b08b6a9d --- /dev/null +++ b/base/synchronization/cancellation_flag_unittest.cc @@ -0,0 +1,64 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Tests of CancellationFlag class. + +#include "base/synchronization/cancellation_flag.h" + +#include "base/bind.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/synchronization/spin_wait.h" +#include "base/threading/thread.h" +#include "base/time/time.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" + +namespace base { + +namespace { + +//------------------------------------------------------------------------------ +// Define our test class. +//------------------------------------------------------------------------------ + +void CancelHelper(CancellationFlag* flag) { +#if GTEST_HAS_DEATH_TEST + ASSERT_DEBUG_DEATH(flag->Set(), ""); +#endif +} + +TEST(CancellationFlagTest, SimpleSingleThreadedTest) { + CancellationFlag flag; + ASSERT_FALSE(flag.IsSet()); + flag.Set(); + ASSERT_TRUE(flag.IsSet()); +} + +TEST(CancellationFlagTest, DoubleSetTest) { + CancellationFlag flag; + ASSERT_FALSE(flag.IsSet()); + flag.Set(); + ASSERT_TRUE(flag.IsSet()); + flag.Set(); + ASSERT_TRUE(flag.IsSet()); +} + +TEST(CancellationFlagTest, SetOnDifferentThreadDeathTest) { + // Checks that Set() can't be called from any other thread. + // CancellationFlag should die on a DCHECK if Set() is called from + // other thread. + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; + Thread t("CancellationFlagTest.SetOnDifferentThreadDeathTest"); + ASSERT_TRUE(t.Start()); + ASSERT_TRUE(t.message_loop()); + ASSERT_TRUE(t.IsRunning()); + + CancellationFlag flag; + t.message_loop()->PostTask(FROM_HERE, base::Bind(&CancelHelper, &flag)); +} + +} // namespace + +} // namespace base diff --git a/base/synchronization/condition_variable.h b/base/synchronization/condition_variable.h new file mode 100644 index 0000000000..4f744f80b2 --- /dev/null +++ b/base/synchronization/condition_variable.h @@ -0,0 +1,118 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// ConditionVariable wraps pthreads condition variable synchronization or, on +// Windows, simulates it. This functionality is very helpful for having +// several threads wait for an event, as is common with a thread pool managed +// by a master. The meaning of such an event in the (worker) thread pool +// scenario is that additional tasks are now available for processing. It is +// used in Chrome in the DNS prefetching system to notify worker threads that +// a queue now has items (tasks) which need to be tended to. A related use +// would have a pool manager waiting on a ConditionVariable, waiting for a +// thread in the pool to announce (signal) that there is now more room in a +// (bounded size) communications queue for the manager to deposit tasks, or, +// as a second example, that the queue of tasks is completely empty and all +// workers are waiting. +// +// USAGE NOTE 1: spurious signal events are possible with this and +// most implementations of condition variables. As a result, be +// *sure* to retest your condition before proceeding. The following +// is a good example of doing this correctly: +// +// while (!work_to_be_done()) Wait(...); +// +// In contrast do NOT do the following: +// +// if (!work_to_be_done()) Wait(...); // Don't do this. +// +// Especially avoid the above if you are relying on some other thread only +// issuing a signal up *if* there is work-to-do. There can/will +// be spurious signals. Recheck state on waiting thread before +// assuming the signal was intentional. Caveat caller ;-). +// +// USAGE NOTE 2: Broadcast() frees up all waiting threads at once, +// which leads to contention for the locks they all held when they +// called Wait(). This results in POOR performance. A much better +// approach to getting a lot of threads out of Wait() is to have each +// thread (upon exiting Wait()) call Signal() to free up another +// Wait'ing thread. Look at condition_variable_unittest.cc for +// both examples. +// +// Broadcast() can be used nicely during teardown, as it gets the job +// done, and leaves no sleeping threads... and performance is less +// critical at that point. +// +// The semantics of Broadcast() are carefully crafted so that *all* +// threads that were waiting when the request was made will indeed +// get signaled. Some implementations mess up, and don't signal them +// all, while others allow the wait to be effectively turned off (for +// a while while waiting threads come around). This implementation +// appears correct, as it will not "lose" any signals, and will guarantee +// that all threads get signaled by Broadcast(). +// +// This implementation offers support for "performance" in its selection of +// which thread to revive. Performance, in direct contrast with "fairness," +// assures that the thread that most recently began to Wait() is selected by +// Signal to revive. Fairness would (if publicly supported) assure that the +// thread that has Wait()ed the longest is selected. The default policy +// may improve performance, as the selected thread may have a greater chance of +// having some of its stack data in various CPU caches. +// +// For a discussion of the many very subtle implementation details, see the FAQ +// at the end of condition_variable_win.cc. + +#ifndef BASE_SYNCHRONIZATION_CONDITION_VARIABLE_H_ +#define BASE_SYNCHRONIZATION_CONDITION_VARIABLE_H_ + +#include "build/build_config.h" + +#if defined(OS_POSIX) +#include +#endif + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/synchronization/lock.h" + +namespace base { + +class ConditionVarImpl; +class TimeDelta; + +class BASE_EXPORT ConditionVariable { + public: + // Construct a cv for use with ONLY one user lock. + explicit ConditionVariable(Lock* user_lock); + + ~ConditionVariable(); + + // Wait() releases the caller's critical section atomically as it starts to + // sleep, and the reacquires it when it is signaled. + void Wait(); + void TimedWait(const TimeDelta& max_time); + + // Broadcast() revives all waiting threads. + void Broadcast(); + // Signal() revives one waiting thread. + void Signal(); + + private: + +#if defined(OS_WIN) + ConditionVarImpl* impl_; +#elif defined(OS_POSIX) + pthread_cond_t condition_; + pthread_mutex_t* user_mutex_; +#if !defined(NDEBUG) + base::Lock* user_lock_; // Needed to adjust shadow lock state on wait. +#endif + +#endif + + DISALLOW_COPY_AND_ASSIGN(ConditionVariable); +}; + +} // namespace base + +#endif // BASE_SYNCHRONIZATION_CONDITION_VARIABLE_H_ diff --git a/base/synchronization/condition_variable_posix.cc b/base/synchronization/condition_variable_posix.cc new file mode 100644 index 0000000000..93b35ed354 --- /dev/null +++ b/base/synchronization/condition_variable_posix.cc @@ -0,0 +1,80 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/synchronization/condition_variable.h" + +#include +#include + +#include "base/logging.h" +#include "base/synchronization/lock.h" +#include "base/threading/thread_restrictions.h" +#include "base/time/time.h" + +namespace base { + +ConditionVariable::ConditionVariable(Lock* user_lock) + : user_mutex_(user_lock->lock_.os_lock()) +#if !defined(NDEBUG) + , user_lock_(user_lock) +#endif +{ + int rv = pthread_cond_init(&condition_, NULL); + DCHECK_EQ(0, rv); +} + +ConditionVariable::~ConditionVariable() { + int rv = pthread_cond_destroy(&condition_); + DCHECK_EQ(0, rv); +} + +void ConditionVariable::Wait() { + base::ThreadRestrictions::AssertWaitAllowed(); +#if !defined(NDEBUG) + user_lock_->CheckHeldAndUnmark(); +#endif + int rv = pthread_cond_wait(&condition_, user_mutex_); + DCHECK_EQ(0, rv); +#if !defined(NDEBUG) + user_lock_->CheckUnheldAndMark(); +#endif +} + +void ConditionVariable::TimedWait(const TimeDelta& max_time) { + base::ThreadRestrictions::AssertWaitAllowed(); + int64 usecs = max_time.InMicroseconds(); + + // The timeout argument to pthread_cond_timedwait is in absolute time. + struct timeval now; + gettimeofday(&now, NULL); + + struct timespec abstime; + abstime.tv_sec = now.tv_sec + (usecs / Time::kMicrosecondsPerSecond); + abstime.tv_nsec = (now.tv_usec + (usecs % Time::kMicrosecondsPerSecond)) * + Time::kNanosecondsPerMicrosecond; + abstime.tv_sec += abstime.tv_nsec / Time::kNanosecondsPerSecond; + abstime.tv_nsec %= Time::kNanosecondsPerSecond; + DCHECK_GE(abstime.tv_sec, now.tv_sec); // Overflow paranoia + +#if !defined(NDEBUG) + user_lock_->CheckHeldAndUnmark(); +#endif + int rv = pthread_cond_timedwait(&condition_, user_mutex_, &abstime); + DCHECK(rv == 0 || rv == ETIMEDOUT); +#if !defined(NDEBUG) + user_lock_->CheckUnheldAndMark(); +#endif +} + +void ConditionVariable::Broadcast() { + int rv = pthread_cond_broadcast(&condition_); + DCHECK_EQ(0, rv); +} + +void ConditionVariable::Signal() { + int rv = pthread_cond_signal(&condition_); + DCHECK_EQ(0, rv); +} + +} // namespace base diff --git a/base/synchronization/condition_variable_unittest.cc b/base/synchronization/condition_variable_unittest.cc new file mode 100644 index 0000000000..4230e63549 --- /dev/null +++ b/base/synchronization/condition_variable_unittest.cc @@ -0,0 +1,715 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Multi-threaded tests of ConditionVariable class. + +#include +#include +#include + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/synchronization/condition_variable.h" +#include "base/synchronization/lock.h" +#include "base/synchronization/spin_wait.h" +#include "base/threading/platform_thread.h" +#include "base/threading/thread_collision_warner.h" +#include "base/time/time.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" + +namespace base { + +namespace { +//------------------------------------------------------------------------------ +// Define our test class, with several common variables. +//------------------------------------------------------------------------------ + +class ConditionVariableTest : public PlatformTest { + public: + const TimeDelta kZeroMs; + const TimeDelta kTenMs; + const TimeDelta kThirtyMs; + const TimeDelta kFortyFiveMs; + const TimeDelta kSixtyMs; + const TimeDelta kOneHundredMs; + + ConditionVariableTest() + : kZeroMs(TimeDelta::FromMilliseconds(0)), + kTenMs(TimeDelta::FromMilliseconds(10)), + kThirtyMs(TimeDelta::FromMilliseconds(30)), + kFortyFiveMs(TimeDelta::FromMilliseconds(45)), + kSixtyMs(TimeDelta::FromMilliseconds(60)), + kOneHundredMs(TimeDelta::FromMilliseconds(100)) { + } +}; + +//------------------------------------------------------------------------------ +// Define a class that will control activities an several multi-threaded tests. +// The general structure of multi-threaded tests is that a test case will +// construct an instance of a WorkQueue. The WorkQueue will spin up some +// threads and control them throughout their lifetime, as well as maintaining +// a central repository of the work thread's activity. Finally, the WorkQueue +// will command the the worker threads to terminate. At that point, the test +// cases will validate that the WorkQueue has records showing that the desired +// activities were performed. +//------------------------------------------------------------------------------ + +// Callers are responsible for synchronizing access to the following class. +// The WorkQueue::lock_, as accessed via WorkQueue::lock(), should be used for +// all synchronized access. +class WorkQueue : public PlatformThread::Delegate { + public: + explicit WorkQueue(int thread_count); + virtual ~WorkQueue(); + + // PlatformThread::Delegate interface. + virtual void ThreadMain() OVERRIDE; + + //---------------------------------------------------------------------------- + // Worker threads only call the following methods. + // They should use the lock to get exclusive access. + int GetThreadId(); // Get an ID assigned to a thread.. + bool EveryIdWasAllocated() const; // Indicates that all IDs were handed out. + TimeDelta GetAnAssignment(int thread_id); // Get a work task duration. + void WorkIsCompleted(int thread_id); + + int task_count() const; + bool allow_help_requests() const; // Workers can signal more workers. + bool shutdown() const; // Check if shutdown has been requested. + + void thread_shutting_down(); + + + //---------------------------------------------------------------------------- + // Worker threads can call them but not needed to acquire a lock. + Lock* lock(); + + ConditionVariable* work_is_available(); + ConditionVariable* all_threads_have_ids(); + ConditionVariable* no_more_tasks(); + + //---------------------------------------------------------------------------- + // The rest of the methods are for use by the controlling master thread (the + // test case code). + void ResetHistory(); + int GetMinCompletionsByWorkerThread() const; + int GetMaxCompletionsByWorkerThread() const; + int GetNumThreadsTakingAssignments() const; + int GetNumThreadsCompletingTasks() const; + int GetNumberOfCompletedTasks() const; + TimeDelta GetWorkTime() const; + + void SetWorkTime(TimeDelta delay); + void SetTaskCount(int count); + void SetAllowHelp(bool allow); + + // The following must be called without locking, and will spin wait until the + // threads are all in a wait state. + void SpinUntilAllThreadsAreWaiting(); + void SpinUntilTaskCountLessThan(int task_count); + + // Caller must acquire lock before calling. + void SetShutdown(); + + // Compares the |shutdown_task_count_| to the |thread_count| and returns true + // if they are equal. This check will acquire the |lock_| so the caller + // should not hold the lock when calling this method. + bool ThreadSafeCheckShutdown(int thread_count); + + private: + // Both worker threads and controller use the following to synchronize. + Lock lock_; + ConditionVariable work_is_available_; // To tell threads there is work. + + // Conditions to notify the controlling process (if it is interested). + ConditionVariable all_threads_have_ids_; // All threads are running. + ConditionVariable no_more_tasks_; // Task count is zero. + + const int thread_count_; + int waiting_thread_count_; + scoped_ptr thread_handles_; + std::vector assignment_history_; // Number of assignment per worker. + std::vector completion_history_; // Number of completions per worker. + int thread_started_counter_; // Used to issue unique id to workers. + int shutdown_task_count_; // Number of tasks told to shutdown + int task_count_; // Number of assignment tasks waiting to be processed. + TimeDelta worker_delay_; // Time each task takes to complete. + bool allow_help_requests_; // Workers can signal more workers. + bool shutdown_; // Set when threads need to terminate. + + DFAKE_MUTEX(locked_methods_); +}; + +//------------------------------------------------------------------------------ +// The next section contains the actual tests. +//------------------------------------------------------------------------------ + +TEST_F(ConditionVariableTest, StartupShutdownTest) { + Lock lock; + + // First try trivial startup/shutdown. + { + ConditionVariable cv1(&lock); + } // Call for cv1 destruction. + + // Exercise with at least a few waits. + ConditionVariable cv(&lock); + + lock.Acquire(); + cv.TimedWait(kTenMs); // Wait for 10 ms. + cv.TimedWait(kTenMs); // Wait for 10 ms. + lock.Release(); + + lock.Acquire(); + cv.TimedWait(kTenMs); // Wait for 10 ms. + cv.TimedWait(kTenMs); // Wait for 10 ms. + cv.TimedWait(kTenMs); // Wait for 10 ms. + lock.Release(); +} // Call for cv destruction. + +TEST_F(ConditionVariableTest, TimeoutTest) { + Lock lock; + ConditionVariable cv(&lock); + lock.Acquire(); + + TimeTicks start = TimeTicks::Now(); + const TimeDelta WAIT_TIME = TimeDelta::FromMilliseconds(300); + // Allow for clocking rate granularity. + const TimeDelta FUDGE_TIME = TimeDelta::FromMilliseconds(50); + + cv.TimedWait(WAIT_TIME + FUDGE_TIME); + TimeDelta duration = TimeTicks::Now() - start; + // We can't use EXPECT_GE here as the TimeDelta class does not support the + // required stream conversion. + EXPECT_TRUE(duration >= WAIT_TIME); + + lock.Release(); +} + + +// Suddenly got flaky on Win, see http://crbug.com/10607 (starting at +// comment #15) +#if defined(OS_WIN) +#define MAYBE_MultiThreadConsumerTest DISABLED_MultiThreadConsumerTest +#else +#define MAYBE_MultiThreadConsumerTest MultiThreadConsumerTest +#endif +// Test serial task servicing, as well as two parallel task servicing methods. +TEST_F(ConditionVariableTest, MAYBE_MultiThreadConsumerTest) { + const int kThreadCount = 10; + WorkQueue queue(kThreadCount); // Start the threads. + + const int kTaskCount = 10; // Number of tasks in each mini-test here. + + Time start_time; // Used to time task processing. + + { + base::AutoLock auto_lock(*queue.lock()); + while (!queue.EveryIdWasAllocated()) + queue.all_threads_have_ids()->Wait(); + } + + // If threads aren't in a wait state, they may start to gobble up tasks in + // parallel, short-circuiting (breaking) this test. + queue.SpinUntilAllThreadsAreWaiting(); + + { + // Since we have no tasks yet, all threads should be waiting by now. + base::AutoLock auto_lock(*queue.lock()); + EXPECT_EQ(0, queue.GetNumThreadsTakingAssignments()); + EXPECT_EQ(0, queue.GetNumThreadsCompletingTasks()); + EXPECT_EQ(0, queue.task_count()); + EXPECT_EQ(0, queue.GetMaxCompletionsByWorkerThread()); + EXPECT_EQ(0, queue.GetMinCompletionsByWorkerThread()); + EXPECT_EQ(0, queue.GetNumberOfCompletedTasks()); + + // Set up to make each task include getting help from another worker, so + // so that the work gets done in paralell. + queue.ResetHistory(); + queue.SetTaskCount(kTaskCount); + queue.SetWorkTime(kThirtyMs); + queue.SetAllowHelp(true); + + start_time = Time::Now(); + } + + queue.work_is_available()->Signal(); // But each worker can signal another. + // Wait till we at least start to handle tasks (and we're not all waiting). + queue.SpinUntilTaskCountLessThan(kTaskCount); + // Wait to allow the all workers to get done. + queue.SpinUntilAllThreadsAreWaiting(); + + { + // Wait until all work tasks have at least been assigned. + base::AutoLock auto_lock(*queue.lock()); + while (queue.task_count()) + queue.no_more_tasks()->Wait(); + + // To avoid racy assumptions, we'll just assert that at least 2 threads + // did work. We know that the first worker should have gone to sleep, and + // hence a second worker should have gotten an assignment. + EXPECT_LE(2, queue.GetNumThreadsTakingAssignments()); + EXPECT_EQ(kTaskCount, queue.GetNumberOfCompletedTasks()); + + // Try to ask all workers to help, and only a few will do the work. + queue.ResetHistory(); + queue.SetTaskCount(3); + queue.SetWorkTime(kThirtyMs); + queue.SetAllowHelp(false); + } + queue.work_is_available()->Broadcast(); // Make them all try. + // Wait till we at least start to handle tasks (and we're not all waiting). + queue.SpinUntilTaskCountLessThan(3); + // Wait to allow the 3 workers to get done. + queue.SpinUntilAllThreadsAreWaiting(); + + { + base::AutoLock auto_lock(*queue.lock()); + EXPECT_EQ(3, queue.GetNumThreadsTakingAssignments()); + EXPECT_EQ(3, queue.GetNumThreadsCompletingTasks()); + EXPECT_EQ(0, queue.task_count()); + EXPECT_EQ(1, queue.GetMaxCompletionsByWorkerThread()); + EXPECT_EQ(0, queue.GetMinCompletionsByWorkerThread()); + EXPECT_EQ(3, queue.GetNumberOfCompletedTasks()); + + // Set up to make each task get help from another worker. + queue.ResetHistory(); + queue.SetTaskCount(3); + queue.SetWorkTime(kThirtyMs); + queue.SetAllowHelp(true); // Allow (unnecessary) help requests. + } + queue.work_is_available()->Broadcast(); // Signal all threads. + // Wait till we at least start to handle tasks (and we're not all waiting). + queue.SpinUntilTaskCountLessThan(3); + // Wait to allow the 3 workers to get done. + queue.SpinUntilAllThreadsAreWaiting(); + + { + base::AutoLock auto_lock(*queue.lock()); + EXPECT_EQ(3, queue.GetNumThreadsTakingAssignments()); + EXPECT_EQ(3, queue.GetNumThreadsCompletingTasks()); + EXPECT_EQ(0, queue.task_count()); + EXPECT_EQ(1, queue.GetMaxCompletionsByWorkerThread()); + EXPECT_EQ(0, queue.GetMinCompletionsByWorkerThread()); + EXPECT_EQ(3, queue.GetNumberOfCompletedTasks()); + + // Set up to make each task get help from another worker. + queue.ResetHistory(); + queue.SetTaskCount(20); // 2 tasks per thread. + queue.SetWorkTime(kThirtyMs); + queue.SetAllowHelp(true); + } + queue.work_is_available()->Signal(); // But each worker can signal another. + // Wait till we at least start to handle tasks (and we're not all waiting). + queue.SpinUntilTaskCountLessThan(20); + // Wait to allow the 10 workers to get done. + queue.SpinUntilAllThreadsAreWaiting(); // Should take about 60 ms. + + { + base::AutoLock auto_lock(*queue.lock()); + EXPECT_EQ(10, queue.GetNumThreadsTakingAssignments()); + EXPECT_EQ(10, queue.GetNumThreadsCompletingTasks()); + EXPECT_EQ(0, queue.task_count()); + EXPECT_EQ(20, queue.GetNumberOfCompletedTasks()); + + // Same as last test, but with Broadcast(). + queue.ResetHistory(); + queue.SetTaskCount(20); // 2 tasks per thread. + queue.SetWorkTime(kThirtyMs); + queue.SetAllowHelp(true); + } + queue.work_is_available()->Broadcast(); + // Wait till we at least start to handle tasks (and we're not all waiting). + queue.SpinUntilTaskCountLessThan(20); + // Wait to allow the 10 workers to get done. + queue.SpinUntilAllThreadsAreWaiting(); // Should take about 60 ms. + + { + base::AutoLock auto_lock(*queue.lock()); + EXPECT_EQ(10, queue.GetNumThreadsTakingAssignments()); + EXPECT_EQ(10, queue.GetNumThreadsCompletingTasks()); + EXPECT_EQ(0, queue.task_count()); + EXPECT_EQ(20, queue.GetNumberOfCompletedTasks()); + + queue.SetShutdown(); + } + queue.work_is_available()->Broadcast(); // Force check for shutdown. + + SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(TimeDelta::FromMinutes(1), + queue.ThreadSafeCheckShutdown(kThreadCount)); +} + +TEST_F(ConditionVariableTest, LargeFastTaskTest) { + const int kThreadCount = 200; + WorkQueue queue(kThreadCount); // Start the threads. + + Lock private_lock; // Used locally for master to wait. + base::AutoLock private_held_lock(private_lock); + ConditionVariable private_cv(&private_lock); + + { + base::AutoLock auto_lock(*queue.lock()); + while (!queue.EveryIdWasAllocated()) + queue.all_threads_have_ids()->Wait(); + } + + // Wait a bit more to allow threads to reach their wait state. + queue.SpinUntilAllThreadsAreWaiting(); + + { + // Since we have no tasks, all threads should be waiting by now. + base::AutoLock auto_lock(*queue.lock()); + EXPECT_EQ(0, queue.GetNumThreadsTakingAssignments()); + EXPECT_EQ(0, queue.GetNumThreadsCompletingTasks()); + EXPECT_EQ(0, queue.task_count()); + EXPECT_EQ(0, queue.GetMaxCompletionsByWorkerThread()); + EXPECT_EQ(0, queue.GetMinCompletionsByWorkerThread()); + EXPECT_EQ(0, queue.GetNumberOfCompletedTasks()); + + // Set up to make all workers do (an average of) 20 tasks. + queue.ResetHistory(); + queue.SetTaskCount(20 * kThreadCount); + queue.SetWorkTime(kFortyFiveMs); + queue.SetAllowHelp(false); + } + queue.work_is_available()->Broadcast(); // Start up all threads. + // Wait until we've handed out all tasks. + { + base::AutoLock auto_lock(*queue.lock()); + while (queue.task_count() != 0) + queue.no_more_tasks()->Wait(); + } + + // Wait till the last of the tasks complete. + queue.SpinUntilAllThreadsAreWaiting(); + + { + // With Broadcast(), every thread should have participated. + // but with racing.. they may not all have done equal numbers of tasks. + base::AutoLock auto_lock(*queue.lock()); + EXPECT_EQ(kThreadCount, queue.GetNumThreadsTakingAssignments()); + EXPECT_EQ(kThreadCount, queue.GetNumThreadsCompletingTasks()); + EXPECT_EQ(0, queue.task_count()); + EXPECT_LE(20, queue.GetMaxCompletionsByWorkerThread()); + EXPECT_EQ(20 * kThreadCount, queue.GetNumberOfCompletedTasks()); + + // Set up to make all workers do (an average of) 4 tasks. + queue.ResetHistory(); + queue.SetTaskCount(kThreadCount * 4); + queue.SetWorkTime(kFortyFiveMs); + queue.SetAllowHelp(true); // Might outperform Broadcast(). + } + queue.work_is_available()->Signal(); // Start up one thread. + + // Wait until we've handed out all tasks + { + base::AutoLock auto_lock(*queue.lock()); + while (queue.task_count() != 0) + queue.no_more_tasks()->Wait(); + } + + // Wait till the last of the tasks complete. + queue.SpinUntilAllThreadsAreWaiting(); + + { + // With Signal(), every thread should have participated. + // but with racing.. they may not all have done four tasks. + base::AutoLock auto_lock(*queue.lock()); + EXPECT_EQ(kThreadCount, queue.GetNumThreadsTakingAssignments()); + EXPECT_EQ(kThreadCount, queue.GetNumThreadsCompletingTasks()); + EXPECT_EQ(0, queue.task_count()); + EXPECT_LE(4, queue.GetMaxCompletionsByWorkerThread()); + EXPECT_EQ(4 * kThreadCount, queue.GetNumberOfCompletedTasks()); + + queue.SetShutdown(); + } + queue.work_is_available()->Broadcast(); // Force check for shutdown. + + // Wait for shutdowns to complete. + SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(TimeDelta::FromMinutes(1), + queue.ThreadSafeCheckShutdown(kThreadCount)); +} + +//------------------------------------------------------------------------------ +// Finally we provide the implementation for the methods in the WorkQueue class. +//------------------------------------------------------------------------------ + +WorkQueue::WorkQueue(int thread_count) + : lock_(), + work_is_available_(&lock_), + all_threads_have_ids_(&lock_), + no_more_tasks_(&lock_), + thread_count_(thread_count), + waiting_thread_count_(0), + thread_handles_(new PlatformThreadHandle[thread_count]), + assignment_history_(thread_count), + completion_history_(thread_count), + thread_started_counter_(0), + shutdown_task_count_(0), + task_count_(0), + allow_help_requests_(false), + shutdown_(false) { + EXPECT_GE(thread_count_, 1); + ResetHistory(); + SetTaskCount(0); + SetWorkTime(TimeDelta::FromMilliseconds(30)); + + for (int i = 0; i < thread_count_; ++i) { + PlatformThreadHandle pth; + EXPECT_TRUE(PlatformThread::Create(0, this, &pth)); + thread_handles_[i] = pth; + } +} + +WorkQueue::~WorkQueue() { + { + base::AutoLock auto_lock(lock_); + SetShutdown(); + } + work_is_available_.Broadcast(); // Tell them all to terminate. + + for (int i = 0; i < thread_count_; ++i) { + PlatformThread::Join(thread_handles_[i]); + } + EXPECT_EQ(0, waiting_thread_count_); +} + +int WorkQueue::GetThreadId() { + DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_); + DCHECK(!EveryIdWasAllocated()); + return thread_started_counter_++; // Give out Unique IDs. +} + +bool WorkQueue::EveryIdWasAllocated() const { + DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_); + return thread_count_ == thread_started_counter_; +} + +TimeDelta WorkQueue::GetAnAssignment(int thread_id) { + DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_); + DCHECK_LT(0, task_count_); + assignment_history_[thread_id]++; + if (0 == --task_count_) { + no_more_tasks_.Signal(); + } + return worker_delay_; +} + +void WorkQueue::WorkIsCompleted(int thread_id) { + DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_); + completion_history_[thread_id]++; +} + +int WorkQueue::task_count() const { + DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_); + return task_count_; +} + +bool WorkQueue::allow_help_requests() const { + DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_); + return allow_help_requests_; +} + +bool WorkQueue::shutdown() const { + lock_.AssertAcquired(); + DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_); + return shutdown_; +} + +// Because this method is called from the test's main thread we need to actually +// take the lock. Threads will call the thread_shutting_down() method with the +// lock already acquired. +bool WorkQueue::ThreadSafeCheckShutdown(int thread_count) { + bool all_shutdown; + base::AutoLock auto_lock(lock_); + { + // Declare in scope so DFAKE is guranteed to be destroyed before AutoLock. + DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_); + all_shutdown = (shutdown_task_count_ == thread_count); + } + return all_shutdown; +} + +void WorkQueue::thread_shutting_down() { + lock_.AssertAcquired(); + DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_); + shutdown_task_count_++; +} + +Lock* WorkQueue::lock() { + return &lock_; +} + +ConditionVariable* WorkQueue::work_is_available() { + return &work_is_available_; +} + +ConditionVariable* WorkQueue::all_threads_have_ids() { + return &all_threads_have_ids_; +} + +ConditionVariable* WorkQueue::no_more_tasks() { + return &no_more_tasks_; +} + +void WorkQueue::ResetHistory() { + for (int i = 0; i < thread_count_; ++i) { + assignment_history_[i] = 0; + completion_history_[i] = 0; + } +} + +int WorkQueue::GetMinCompletionsByWorkerThread() const { + int minumum = completion_history_[0]; + for (int i = 0; i < thread_count_; ++i) + minumum = std::min(minumum, completion_history_[i]); + return minumum; +} + +int WorkQueue::GetMaxCompletionsByWorkerThread() const { + int maximum = completion_history_[0]; + for (int i = 0; i < thread_count_; ++i) + maximum = std::max(maximum, completion_history_[i]); + return maximum; +} + +int WorkQueue::GetNumThreadsTakingAssignments() const { + int count = 0; + for (int i = 0; i < thread_count_; ++i) + if (assignment_history_[i]) + count++; + return count; +} + +int WorkQueue::GetNumThreadsCompletingTasks() const { + int count = 0; + for (int i = 0; i < thread_count_; ++i) + if (completion_history_[i]) + count++; + return count; +} + +int WorkQueue::GetNumberOfCompletedTasks() const { + int total = 0; + for (int i = 0; i < thread_count_; ++i) + total += completion_history_[i]; + return total; +} + +TimeDelta WorkQueue::GetWorkTime() const { + return worker_delay_; +} + +void WorkQueue::SetWorkTime(TimeDelta delay) { + worker_delay_ = delay; +} + +void WorkQueue::SetTaskCount(int count) { + task_count_ = count; +} + +void WorkQueue::SetAllowHelp(bool allow) { + allow_help_requests_ = allow; +} + +void WorkQueue::SetShutdown() { + lock_.AssertAcquired(); + shutdown_ = true; +} + +void WorkQueue::SpinUntilAllThreadsAreWaiting() { + while (true) { + { + base::AutoLock auto_lock(lock_); + if (waiting_thread_count_ == thread_count_) + break; + } + PlatformThread::Sleep(TimeDelta::FromMilliseconds(30)); + } +} + +void WorkQueue::SpinUntilTaskCountLessThan(int task_count) { + while (true) { + { + base::AutoLock auto_lock(lock_); + if (task_count_ < task_count) + break; + } + PlatformThread::Sleep(TimeDelta::FromMilliseconds(30)); + } +} + + +//------------------------------------------------------------------------------ +// Define the standard worker task. Several tests will spin out many of these +// threads. +//------------------------------------------------------------------------------ + +// The multithread tests involve several threads with a task to perform as +// directed by an instance of the class WorkQueue. +// The task is to: +// a) Check to see if there are more tasks (there is a task counter). +// a1) Wait on condition variable if there are no tasks currently. +// b) Call a function to see what should be done. +// c) Do some computation based on the number of milliseconds returned in (b). +// d) go back to (a). + +// WorkQueue::ThreadMain() implements the above task for all threads. +// It calls the controlling object to tell the creator about progress, and to +// ask about tasks. + +void WorkQueue::ThreadMain() { + int thread_id; + { + base::AutoLock auto_lock(lock_); + thread_id = GetThreadId(); + if (EveryIdWasAllocated()) + all_threads_have_ids()->Signal(); // Tell creator we're ready. + } + + Lock private_lock; // Used to waste time on "our work". + while (1) { // This is the main consumer loop. + TimeDelta work_time; + bool could_use_help; + { + base::AutoLock auto_lock(lock_); + while (0 == task_count() && !shutdown()) { + ++waiting_thread_count_; + work_is_available()->Wait(); + --waiting_thread_count_; + } + if (shutdown()) { + // Ack the notification of a shutdown message back to the controller. + thread_shutting_down(); + return; // Terminate. + } + // Get our task duration from the queue. + work_time = GetAnAssignment(thread_id); + could_use_help = (task_count() > 0) && allow_help_requests(); + } // Release lock + + // Do work (outside of locked region. + if (could_use_help) + work_is_available()->Signal(); // Get help from other threads. + + if (work_time > TimeDelta::FromMilliseconds(0)) { + // We could just sleep(), but we'll instead further exercise the + // condition variable class, and do a timed wait. + base::AutoLock auto_lock(private_lock); + ConditionVariable private_cv(&private_lock); + private_cv.TimedWait(work_time); // Unsynchronized waiting. + } + + { + base::AutoLock auto_lock(lock_); + // Send notification that we completed our "work." + WorkIsCompleted(thread_id); + } + } +} + +} // namespace + +} // namespace base diff --git a/base/synchronization/condition_variable_win.cc b/base/synchronization/condition_variable_win.cc new file mode 100644 index 0000000000..bf0d5f3de7 --- /dev/null +++ b/base/synchronization/condition_variable_win.cc @@ -0,0 +1,669 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/synchronization/condition_variable.h" + +#include +#include + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/synchronization/lock.h" +#include "base/threading/thread_restrictions.h" +#include "base/time/time.h" + +namespace { +// We can't use the linker supported delay-load for kernel32 so all this +// cruft here is to manually late-bind the needed functions. +typedef void (WINAPI *InitializeConditionVariableFn)(PCONDITION_VARIABLE); +typedef BOOL (WINAPI *SleepConditionVariableCSFn)(PCONDITION_VARIABLE, + PCRITICAL_SECTION, DWORD); +typedef void (WINAPI *WakeConditionVariableFn)(PCONDITION_VARIABLE); +typedef void (WINAPI *WakeAllConditionVariableFn)(PCONDITION_VARIABLE); + +InitializeConditionVariableFn initialize_condition_variable_fn; +SleepConditionVariableCSFn sleep_condition_variable_fn; +WakeConditionVariableFn wake_condition_variable_fn; +WakeAllConditionVariableFn wake_all_condition_variable_fn; + +bool BindVistaCondVarFunctions() { + HMODULE kernel32 = GetModuleHandleA("kernel32.dll"); + initialize_condition_variable_fn = + reinterpret_cast( + GetProcAddress(kernel32, "InitializeConditionVariable")); + if (!initialize_condition_variable_fn) + return false; + sleep_condition_variable_fn = + reinterpret_cast( + GetProcAddress(kernel32, "SleepConditionVariableCS")); + if (!sleep_condition_variable_fn) + return false; + wake_condition_variable_fn = + reinterpret_cast( + GetProcAddress(kernel32, "WakeConditionVariable")); + if (!wake_condition_variable_fn) + return false; + wake_all_condition_variable_fn = + reinterpret_cast( + GetProcAddress(kernel32, "WakeAllConditionVariable")); + if (!wake_all_condition_variable_fn) + return false; + return true; +} + +} // namespace. + +namespace base { +// Abstract base class of the pimpl idiom. +class ConditionVarImpl { + public: + virtual ~ConditionVarImpl() {}; + virtual void Wait() = 0; + virtual void TimedWait(const TimeDelta& max_time) = 0; + virtual void Broadcast() = 0; + virtual void Signal() = 0; +}; + +/////////////////////////////////////////////////////////////////////////////// +// Windows Vista and Win7 implementation. +/////////////////////////////////////////////////////////////////////////////// + +class WinVistaCondVar: public ConditionVarImpl { + public: + WinVistaCondVar(Lock* user_lock); + ~WinVistaCondVar() {}; + // Overridden from ConditionVarImpl. + virtual void Wait() OVERRIDE; + virtual void TimedWait(const TimeDelta& max_time) OVERRIDE; + virtual void Broadcast() OVERRIDE; + virtual void Signal() OVERRIDE; + + private: + base::Lock& user_lock_; + CONDITION_VARIABLE cv_; +}; + +WinVistaCondVar::WinVistaCondVar(Lock* user_lock) + : user_lock_(*user_lock) { + initialize_condition_variable_fn(&cv_); + DCHECK(user_lock); +} + +void WinVistaCondVar::Wait() { + TimedWait(TimeDelta::FromMilliseconds(INFINITE)); +} + +void WinVistaCondVar::TimedWait(const TimeDelta& max_time) { + base::ThreadRestrictions::AssertWaitAllowed(); + DWORD timeout = static_cast(max_time.InMilliseconds()); + CRITICAL_SECTION* cs = user_lock_.lock_.os_lock(); + +#if !defined(NDEBUG) + user_lock_.CheckHeldAndUnmark(); +#endif + + if (FALSE == sleep_condition_variable_fn(&cv_, cs, timeout)) { + DCHECK(GetLastError() != WAIT_TIMEOUT); + } + +#if !defined(NDEBUG) + user_lock_.CheckUnheldAndMark(); +#endif +} + +void WinVistaCondVar::Broadcast() { + wake_all_condition_variable_fn(&cv_); +} + +void WinVistaCondVar::Signal() { + wake_condition_variable_fn(&cv_); +} + +/////////////////////////////////////////////////////////////////////////////// +// Windows XP implementation. +/////////////////////////////////////////////////////////////////////////////// + +class WinXPCondVar : public ConditionVarImpl { + public: + WinXPCondVar(Lock* user_lock); + ~WinXPCondVar(); + // Overridden from ConditionVarImpl. + virtual void Wait() OVERRIDE; + virtual void TimedWait(const TimeDelta& max_time) OVERRIDE; + virtual void Broadcast() OVERRIDE; + virtual void Signal() OVERRIDE; + + // Define Event class that is used to form circularly linked lists. + // The list container is an element with NULL as its handle_ value. + // The actual list elements have a non-zero handle_ value. + // All calls to methods MUST be done under protection of a lock so that links + // can be validated. Without the lock, some links might asynchronously + // change, and the assertions would fail (as would list change operations). + class Event { + public: + // Default constructor with no arguments creates a list container. + Event(); + ~Event(); + + // InitListElement transitions an instance from a container, to an element. + void InitListElement(); + + // Methods for use on lists. + bool IsEmpty() const; + void PushBack(Event* other); + Event* PopFront(); + Event* PopBack(); + + // Methods for use on list elements. + // Accessor method. + HANDLE handle() const; + // Pull an element from a list (if it's in one). + Event* Extract(); + + // Method for use on a list element or on a list. + bool IsSingleton() const; + + private: + // Provide pre/post conditions to validate correct manipulations. + bool ValidateAsDistinct(Event* other) const; + bool ValidateAsItem() const; + bool ValidateAsList() const; + bool ValidateLinks() const; + + HANDLE handle_; + Event* next_; + Event* prev_; + DISALLOW_COPY_AND_ASSIGN(Event); + }; + + // Note that RUNNING is an unlikely number to have in RAM by accident. + // This helps with defensive destructor coding in the face of user error. + enum RunState { SHUTDOWN = 0, RUNNING = 64213 }; + + // Internal implementation methods supporting Wait(). + Event* GetEventForWaiting(); + void RecycleEvent(Event* used_event); + + RunState run_state_; + + // Private critical section for access to member data. + base::Lock internal_lock_; + + // Lock that is acquired before calling Wait(). + base::Lock& user_lock_; + + // Events that threads are blocked on. + Event waiting_list_; + + // Free list for old events. + Event recycling_list_; + int recycling_list_size_; + + // The number of allocated, but not yet deleted events. + int allocation_counter_; +}; + +WinXPCondVar::WinXPCondVar(Lock* user_lock) + : user_lock_(*user_lock), + run_state_(RUNNING), + allocation_counter_(0), + recycling_list_size_(0) { + DCHECK(user_lock); +} + +WinXPCondVar::~WinXPCondVar() { + AutoLock auto_lock(internal_lock_); + run_state_ = SHUTDOWN; // Prevent any more waiting. + + DCHECK_EQ(recycling_list_size_, allocation_counter_); + if (recycling_list_size_ != allocation_counter_) { // Rare shutdown problem. + // There are threads of execution still in this->TimedWait() and yet the + // caller has instigated the destruction of this instance :-/. + // A common reason for such "overly hasty" destruction is that the caller + // was not willing to wait for all the threads to terminate. Such hasty + // actions are a violation of our usage contract, but we'll give the + // waiting thread(s) one last chance to exit gracefully (prior to our + // destruction). + // Note: waiting_list_ *might* be empty, but recycling is still pending. + AutoUnlock auto_unlock(internal_lock_); + Broadcast(); // Make sure all waiting threads have been signaled. + Sleep(10); // Give threads a chance to grab internal_lock_. + // All contained threads should be blocked on user_lock_ by now :-). + } // Reacquire internal_lock_. + + DCHECK_EQ(recycling_list_size_, allocation_counter_); +} + +void WinXPCondVar::Wait() { + // Default to "wait forever" timing, which means have to get a Signal() + // or Broadcast() to come out of this wait state. + TimedWait(TimeDelta::FromMilliseconds(INFINITE)); +} + +void WinXPCondVar::TimedWait(const TimeDelta& max_time) { + base::ThreadRestrictions::AssertWaitAllowed(); + Event* waiting_event; + HANDLE handle; + { + AutoLock auto_lock(internal_lock_); + if (RUNNING != run_state_) return; // Destruction in progress. + waiting_event = GetEventForWaiting(); + handle = waiting_event->handle(); + DCHECK(handle); + } // Release internal_lock. + + { + AutoUnlock unlock(user_lock_); // Release caller's lock + WaitForSingleObject(handle, static_cast(max_time.InMilliseconds())); + // Minimize spurious signal creation window by recycling asap. + AutoLock auto_lock(internal_lock_); + RecycleEvent(waiting_event); + // Release internal_lock_ + } // Reacquire callers lock to depth at entry. +} + +// Broadcast() is guaranteed to signal all threads that were waiting (i.e., had +// a cv_event internally allocated for them) before Broadcast() was called. +void WinXPCondVar::Broadcast() { + std::stack handles; // See FAQ-question-10. + { + AutoLock auto_lock(internal_lock_); + if (waiting_list_.IsEmpty()) + return; + while (!waiting_list_.IsEmpty()) + // This is not a leak from waiting_list_. See FAQ-question 12. + handles.push(waiting_list_.PopBack()->handle()); + } // Release internal_lock_. + while (!handles.empty()) { + SetEvent(handles.top()); + handles.pop(); + } +} + +// Signal() will select one of the waiting threads, and signal it (signal its +// cv_event). For better performance we signal the thread that went to sleep +// most recently (LIFO). If we want fairness, then we wake the thread that has +// been sleeping the longest (FIFO). +void WinXPCondVar::Signal() { + HANDLE handle; + { + AutoLock auto_lock(internal_lock_); + if (waiting_list_.IsEmpty()) + return; // No one to signal. + // Only performance option should be used. + // This is not a leak from waiting_list. See FAQ-question 12. + handle = waiting_list_.PopBack()->handle(); // LIFO. + } // Release internal_lock_. + SetEvent(handle); +} + +// GetEventForWaiting() provides a unique cv_event for any caller that needs to +// wait. This means that (worst case) we may over time create as many cv_event +// objects as there are threads simultaneously using this instance's Wait() +// functionality. +WinXPCondVar::Event* WinXPCondVar::GetEventForWaiting() { + // We hold internal_lock, courtesy of Wait(). + Event* cv_event; + if (0 == recycling_list_size_) { + DCHECK(recycling_list_.IsEmpty()); + cv_event = new Event(); + cv_event->InitListElement(); + allocation_counter_++; + DCHECK(cv_event->handle()); + } else { + cv_event = recycling_list_.PopFront(); + recycling_list_size_--; + } + waiting_list_.PushBack(cv_event); + return cv_event; +} + +// RecycleEvent() takes a cv_event that was previously used for Wait()ing, and +// recycles it for use in future Wait() calls for this or other threads. +// Note that there is a tiny chance that the cv_event is still signaled when we +// obtain it, and that can cause spurious signals (if/when we re-use the +// cv_event), but such is quite rare (see FAQ-question-5). +void WinXPCondVar::RecycleEvent(Event* used_event) { + // We hold internal_lock, courtesy of Wait(). + // If the cv_event timed out, then it is necessary to remove it from + // waiting_list_. If it was selected by Broadcast() or Signal(), then it is + // already gone. + used_event->Extract(); // Possibly redundant + recycling_list_.PushBack(used_event); + recycling_list_size_++; +} +//------------------------------------------------------------------------------ +// The next section provides the implementation for the private Event class. +//------------------------------------------------------------------------------ + +// Event provides a doubly-linked-list of events for use exclusively by the +// ConditionVariable class. + +// This custom container was crafted because no simple combination of STL +// classes appeared to support the functionality required. The specific +// unusual requirement for a linked-list-class is support for the Extract() +// method, which can remove an element from a list, potentially for insertion +// into a second list. Most critically, the Extract() method is idempotent, +// turning the indicated element into an extracted singleton whether it was +// contained in a list or not. This functionality allows one (or more) of +// threads to do the extraction. The iterator that identifies this extractable +// element (in this case, a pointer to the list element) can be used after +// arbitrary manipulation of the (possibly) enclosing list container. In +// general, STL containers do not provide iterators that can be used across +// modifications (insertions/extractions) of the enclosing containers, and +// certainly don't provide iterators that can be used if the identified +// element is *deleted* (removed) from the container. + +// It is possible to use multiple redundant containers, such as an STL list, +// and an STL map, to achieve similar container semantics. This container has +// only O(1) methods, while the corresponding (multiple) STL container approach +// would have more complex O(log(N)) methods (yeah... N isn't that large). +// Multiple containers also makes correctness more difficult to assert, as +// data is redundantly stored and maintained, which is generally evil. + +WinXPCondVar::Event::Event() : handle_(0) { + next_ = prev_ = this; // Self referencing circular. +} + +WinXPCondVar::Event::~Event() { + if (0 == handle_) { + // This is the list holder + while (!IsEmpty()) { + Event* cv_event = PopFront(); + DCHECK(cv_event->ValidateAsItem()); + delete cv_event; + } + } + DCHECK(IsSingleton()); + if (0 != handle_) { + int ret_val = CloseHandle(handle_); + DCHECK(ret_val); + } +} + +// Change a container instance permanently into an element of a list. +void WinXPCondVar::Event::InitListElement() { + DCHECK(!handle_); + handle_ = CreateEvent(NULL, false, false, NULL); + DCHECK(handle_); +} + +// Methods for use on lists. +bool WinXPCondVar::Event::IsEmpty() const { + DCHECK(ValidateAsList()); + return IsSingleton(); +} + +void WinXPCondVar::Event::PushBack(Event* other) { + DCHECK(ValidateAsList()); + DCHECK(other->ValidateAsItem()); + DCHECK(other->IsSingleton()); + // Prepare other for insertion. + other->prev_ = prev_; + other->next_ = this; + // Cut into list. + prev_->next_ = other; + prev_ = other; + DCHECK(ValidateAsDistinct(other)); +} + +WinXPCondVar::Event* WinXPCondVar::Event::PopFront() { + DCHECK(ValidateAsList()); + DCHECK(!IsSingleton()); + return next_->Extract(); +} + +WinXPCondVar::Event* WinXPCondVar::Event::PopBack() { + DCHECK(ValidateAsList()); + DCHECK(!IsSingleton()); + return prev_->Extract(); +} + +// Methods for use on list elements. +// Accessor method. +HANDLE WinXPCondVar::Event::handle() const { + DCHECK(ValidateAsItem()); + return handle_; +} + +// Pull an element from a list (if it's in one). +WinXPCondVar::Event* WinXPCondVar::Event::Extract() { + DCHECK(ValidateAsItem()); + if (!IsSingleton()) { + // Stitch neighbors together. + next_->prev_ = prev_; + prev_->next_ = next_; + // Make extractee into a singleton. + prev_ = next_ = this; + } + DCHECK(IsSingleton()); + return this; +} + +// Method for use on a list element or on a list. +bool WinXPCondVar::Event::IsSingleton() const { + DCHECK(ValidateLinks()); + return next_ == this; +} + +// Provide pre/post conditions to validate correct manipulations. +bool WinXPCondVar::Event::ValidateAsDistinct(Event* other) const { + return ValidateLinks() && other->ValidateLinks() && (this != other); +} + +bool WinXPCondVar::Event::ValidateAsItem() const { + return (0 != handle_) && ValidateLinks(); +} + +bool WinXPCondVar::Event::ValidateAsList() const { + return (0 == handle_) && ValidateLinks(); +} + +bool WinXPCondVar::Event::ValidateLinks() const { + // Make sure both of our neighbors have links that point back to us. + // We don't do the O(n) check and traverse the whole loop, and instead only + // do a local check to (and returning from) our immediate neighbors. + return (next_->prev_ == this) && (prev_->next_ == this); +} + + +/* +FAQ On WinXPCondVar subtle implementation details: + +1) What makes this problem subtle? Please take a look at "Strategies +for Implementing POSIX Condition Variables on Win32" by Douglas +C. Schmidt and Irfan Pyarali. +http://www.cs.wustl.edu/~schmidt/win32-cv-1.html It includes +discussions of numerous flawed strategies for implementing this +functionality. I'm not convinced that even the final proposed +implementation has semantics that are as nice as this implementation +(especially with regard to Broadcast() and the impact on threads that +try to Wait() after a Broadcast() has been called, but before all the +original waiting threads have been signaled). + +2) Why can't you use a single wait_event for all threads that call +Wait()? See FAQ-question-1, or consider the following: If a single +event were used, then numerous threads calling Wait() could release +their cs locks, and be preempted just before calling +WaitForSingleObject(). If a call to Broadcast() was then presented on +a second thread, it would be impossible to actually signal all +waiting(?) threads. Some number of SetEvent() calls *could* be made, +but there could be no guarantee that those led to to more than one +signaled thread (SetEvent()'s may be discarded after the first!), and +there could be no guarantee that the SetEvent() calls didn't just +awaken "other" threads that hadn't even started waiting yet (oops). +Without any limit on the number of requisite SetEvent() calls, the +system would be forced to do many such calls, allowing many new waits +to receive spurious signals. + +3) How does this implementation cause spurious signal events? The +cause in this implementation involves a race between a signal via +time-out and a signal via Signal() or Broadcast(). The series of +actions leading to this are: + +a) Timer fires, and a waiting thread exits the line of code: + + WaitForSingleObject(waiting_event, max_time.InMilliseconds()); + +b) That thread (in (a)) is randomly pre-empted after the above line, +leaving the waiting_event reset (unsignaled) and still in the +waiting_list_. + +c) A call to Signal() (or Broadcast()) on a second thread proceeds, and +selects the waiting cv_event (identified in step (b)) as the event to revive +via a call to SetEvent(). + +d) The Signal() method (step c) calls SetEvent() on waiting_event (step b). + +e) The waiting cv_event (step b) is now signaled, but no thread is +waiting on it. + +f) When that waiting_event (step b) is reused, it will immediately +be signaled (spuriously). + + +4) Why do you recycle events, and cause spurious signals? First off, +the spurious events are very rare. They can only (I think) appear +when the race described in FAQ-question-3 takes place. This should be +very rare. Most(?) uses will involve only timer expiration, or only +Signal/Broadcast() actions. When both are used, it will be rare that +the race will appear, and it would require MANY Wait() and signaling +activities. If this implementation did not recycle events, then it +would have to create and destroy events for every call to Wait(). +That allocation/deallocation and associated construction/destruction +would be costly (per wait), and would only be a rare benefit (when the +race was "lost" and a spurious signal took place). That would be bad +(IMO) optimization trade-off. Finally, such spurious events are +allowed by the specification of condition variables (such as +implemented in Vista), and hence it is better if any user accommodates +such spurious events (see usage note in condition_variable.h). + +5) Why don't you reset events when you are about to recycle them, or +about to reuse them, so that the spurious signals don't take place? +The thread described in FAQ-question-3 step c may be pre-empted for an +arbitrary length of time before proceeding to step d. As a result, +the wait_event may actually be re-used *before* step (e) is reached. +As a result, calling reset would not help significantly. + +6) How is it that the callers lock is released atomically with the +entry into a wait state? We commit to the wait activity when we +allocate the wait_event for use in a given call to Wait(). This +allocation takes place before the caller's lock is released (and +actually before our internal_lock_ is released). That allocation is +the defining moment when "the wait state has been entered," as that +thread *can* now be signaled by a call to Broadcast() or Signal(). +Hence we actually "commit to wait" before releasing the lock, making +the pair effectively atomic. + +8) Why do you need to lock your data structures during waiting, as the +caller is already in possession of a lock? We need to Acquire() and +Release() our internal lock during Signal() and Broadcast(). If we tried +to use a callers lock for this purpose, we might conflict with their +external use of the lock. For example, the caller may use to consistently +hold a lock on one thread while calling Signal() on another, and that would +block Signal(). + +9) Couldn't a more efficient implementation be provided if you +preclude using more than one external lock in conjunction with a +single ConditionVariable instance? Yes, at least it could be viewed +as a simpler API (since you don't have to reiterate the lock argument +in each Wait() call). One of the constructors now takes a specific +lock as an argument, and a there are corresponding Wait() calls that +don't specify a lock now. It turns that the resulting implmentation +can't be made more efficient, as the internal lock needs to be used by +Signal() and Broadcast(), to access internal data structures. As a +result, I was not able to utilize the user supplied lock (which is +being used by the user elsewhere presumably) to protect the private +member access. + +9) Since you have a second lock, how can be be sure that there is no +possible deadlock scenario? Our internal_lock_ is always the last +lock acquired, and the first one released, and hence a deadlock (due +to critical section problems) is impossible as a consequence of our +lock. + +10) When doing a Broadcast(), why did you copy all the events into +an STL queue, rather than making a linked-loop, and iterating over it? +The iterating during Broadcast() is done so outside the protection +of the internal lock. As a result, other threads, such as the thread +wherein a related event is waiting, could asynchronously manipulate +the links around a cv_event. As a result, the link structure cannot +be used outside a lock. Broadcast() could iterate over waiting +events by cycling in-and-out of the protection of the internal_lock, +but that appears more expensive than copying the list into an STL +stack. + +11) Why did the lock.h file need to be modified so much for this +change? Central to a Condition Variable is the atomic release of a +lock during a Wait(). This places Wait() functionality exactly +mid-way between the two classes, Lock and Condition Variable. Given +that there can be nested Acquire()'s of locks, and Wait() had to +Release() completely a held lock, it was necessary to augment the Lock +class with a recursion counter. Even more subtle is the fact that the +recursion counter (in a Lock) must be protected, as many threads can +access it asynchronously. As a positive fallout of this, there are +now some DCHECKS to be sure no one Release()s a Lock more than they +Acquire()ed it, and there is ifdef'ed functionality that can detect +nested locks (legal under windows, but not under Posix). + +12) Why is it that the cv_events removed from list in Broadcast() and Signal() +are not leaked? How are they recovered?? The cv_events that appear to leak are +taken from the waiting_list_. For each element in that list, there is currently +a thread in or around the WaitForSingleObject() call of Wait(), and those +threads have references to these otherwise leaked events. They are passed as +arguments to be recycled just aftre returning from WaitForSingleObject(). + +13) Why did you use a custom container class (the linked list), when STL has +perfectly good containers, such as an STL list? The STL list, as with any +container, does not guarantee the utility of an iterator across manipulation +(such as insertions and deletions) of the underlying container. The custom +double-linked-list container provided that assurance. I don't believe any +combination of STL containers provided the services that were needed at the same +O(1) efficiency as the custom linked list. The unusual requirement +for the container class is that a reference to an item within a container (an +iterator) needed to be maintained across an arbitrary manipulation of the +container. This requirement exposes itself in the Wait() method, where a +waiting_event must be selected prior to the WaitForSingleObject(), and then it +must be used as part of recycling to remove the related instance from the +waiting_list. A hash table (STL map) could be used, but I was embarrased to +use a complex and relatively low efficiency container when a doubly linked list +provided O(1) performance in all required operations. Since other operations +to provide performance-and/or-fairness required queue (FIFO) and list (LIFO) +containers, I would also have needed to use an STL list/queue as well as an STL +map. In the end I decided it would be "fun" to just do it right, and I +put so many assertions (DCHECKs) into the container class that it is trivial to +code review and validate its correctness. + +*/ + +ConditionVariable::ConditionVariable(Lock* user_lock) + : impl_(NULL) { + static bool use_vista_native_cv = BindVistaCondVarFunctions(); + if (use_vista_native_cv) + impl_= new WinVistaCondVar(user_lock); + else + impl_ = new WinXPCondVar(user_lock); +} + +ConditionVariable::~ConditionVariable() { + delete impl_; +} + +void ConditionVariable::Wait() { + impl_->Wait(); +} + +void ConditionVariable::TimedWait(const TimeDelta& max_time) { + impl_->TimedWait(max_time); +} + +void ConditionVariable::Broadcast() { + impl_->Broadcast(); +} + +void ConditionVariable::Signal() { + impl_->Signal(); +} + +} // namespace base diff --git a/base/synchronization/lock.cc b/base/synchronization/lock.cc new file mode 100644 index 0000000000..49efbe9265 --- /dev/null +++ b/base/synchronization/lock.cc @@ -0,0 +1,48 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file is used for debugging assertion support. The Lock class +// is functionally a wrapper around the LockImpl class, so the only +// real intelligence in the class is in the debugging logic. + +#if !defined(NDEBUG) + +#include "base/synchronization/lock.h" +#include "base/logging.h" + +namespace base { + +const PlatformThreadId kNoThreadId = static_cast(0); + +Lock::Lock() : lock_() { + owned_by_thread_ = false; + owning_thread_id_ = kNoThreadId; +} + +Lock::~Lock() { + DCHECK(!owned_by_thread_); + DCHECK_EQ(kNoThreadId, owning_thread_id_); +} + +void Lock::AssertAcquired() const { + DCHECK(owned_by_thread_); + DCHECK_EQ(owning_thread_id_, PlatformThread::CurrentId()); +} + +void Lock::CheckHeldAndUnmark() { + DCHECK(owned_by_thread_); + DCHECK_EQ(owning_thread_id_, PlatformThread::CurrentId()); + owned_by_thread_ = false; + owning_thread_id_ = kNoThreadId; +} + +void Lock::CheckUnheldAndMark() { + DCHECK(!owned_by_thread_); + owned_by_thread_ = true; + owning_thread_id_ = PlatformThread::CurrentId(); +} + +} // namespace base + +#endif // NDEBUG diff --git a/base/synchronization/lock.h b/base/synchronization/lock.h new file mode 100644 index 0000000000..7e8ffe7b74 --- /dev/null +++ b/base/synchronization/lock.h @@ -0,0 +1,140 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_SYNCHRONIZATION_LOCK_H_ +#define BASE_SYNCHRONIZATION_LOCK_H_ + +#include "base/base_export.h" +#include "base/synchronization/lock_impl.h" +#include "base/threading/platform_thread.h" + +namespace base { + +// A convenient wrapper for an OS specific critical section. The only real +// intelligence in this class is in debug mode for the support for the +// AssertAcquired() method. +class BASE_EXPORT Lock { + public: +#if defined(NDEBUG) // Optimized wrapper implementation + Lock() : lock_() {} + ~Lock() {} + void Acquire() { lock_.Lock(); } + void Release() { lock_.Unlock(); } + + // If the lock is not held, take it and return true. If the lock is already + // held by another thread, immediately return false. This must not be called + // by a thread already holding the lock (what happens is undefined and an + // assertion may fail). + bool Try() { return lock_.Try(); } + + // Null implementation if not debug. + void AssertAcquired() const {} +#else + Lock(); + ~Lock(); + + // NOTE: Although windows critical sections support recursive locks, we do not + // allow this, and we will commonly fire a DCHECK() if a thread attempts to + // acquire the lock a second time (while already holding it). + void Acquire() { + lock_.Lock(); + CheckUnheldAndMark(); + } + void Release() { + CheckHeldAndUnmark(); + lock_.Unlock(); + } + + bool Try() { + bool rv = lock_.Try(); + if (rv) { + CheckUnheldAndMark(); + } + return rv; + } + + void AssertAcquired() const; +#endif // NDEBUG + +#if defined(OS_POSIX) + // The posix implementation of ConditionVariable needs to be able + // to see our lock and tweak our debugging counters, as it releases + // and acquires locks inside of pthread_cond_{timed,}wait. + friend class ConditionVariable; +#elif defined(OS_WIN) + // The Windows Vista implementation of ConditionVariable needs the + // native handle of the critical section. + friend class WinVistaCondVar; +#endif + + private: +#if !defined(NDEBUG) + // Members and routines taking care of locks assertions. + // Note that this checks for recursive locks and allows them + // if the variable is set. This is allowed by the underlying implementation + // on windows but not on Posix, so we're doing unneeded checks on Posix. + // It's worth it to share the code. + void CheckHeldAndUnmark(); + void CheckUnheldAndMark(); + + // All private data is implicitly protected by lock_. + // Be VERY careful to only access members under that lock. + + // Determines validity of owning_thread_id_. Needed as we don't have + // a null owning_thread_id_ value. + bool owned_by_thread_; + base::PlatformThreadId owning_thread_id_; +#endif // NDEBUG + + // Platform specific underlying lock implementation. + internal::LockImpl lock_; + + DISALLOW_COPY_AND_ASSIGN(Lock); +}; + +// A helper class that acquires the given Lock while the AutoLock is in scope. +class AutoLock { + public: + struct AlreadyAcquired {}; + + explicit AutoLock(Lock& lock) : lock_(lock) { + lock_.Acquire(); + } + + AutoLock(Lock& lock, const AlreadyAcquired&) : lock_(lock) { + lock_.AssertAcquired(); + } + + ~AutoLock() { + lock_.AssertAcquired(); + lock_.Release(); + } + + private: + Lock& lock_; + DISALLOW_COPY_AND_ASSIGN(AutoLock); +}; + +// AutoUnlock is a helper that will Release() the |lock| argument in the +// constructor, and re-Acquire() it in the destructor. +class AutoUnlock { + public: + explicit AutoUnlock(Lock& lock) : lock_(lock) { + // We require our caller to have the lock. + lock_.AssertAcquired(); + lock_.Release(); + } + + ~AutoUnlock() { + lock_.Acquire(); + } + + private: + Lock& lock_; + DISALLOW_COPY_AND_ASSIGN(AutoUnlock); +}; + +} // namespace base + +#endif // BASE_SYNCHRONIZATION_LOCK_H_ diff --git a/base/synchronization/lock_impl.h b/base/synchronization/lock_impl.h new file mode 100644 index 0000000000..0b04167b9d --- /dev/null +++ b/base/synchronization/lock_impl.h @@ -0,0 +1,61 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_SYNCHRONIZATION_LOCK_IMPL_H_ +#define BASE_SYNCHRONIZATION_LOCK_IMPL_H_ + +#include "build/build_config.h" + +#if defined(OS_WIN) +#include +#elif defined(OS_POSIX) +#include +#endif + +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace base { +namespace internal { + +// This class implements the underlying platform-specific spin-lock mechanism +// used for the Lock class. Most users should not use LockImpl directly, but +// should instead use Lock. +class BASE_EXPORT LockImpl { + public: +#if defined(OS_WIN) + typedef CRITICAL_SECTION OSLockType; +#elif defined(OS_POSIX) + typedef pthread_mutex_t OSLockType; +#endif + + LockImpl(); + ~LockImpl(); + + // If the lock is not held, take it and return true. If the lock is already + // held by something else, immediately return false. + bool Try(); + + // Take the lock, blocking until it is available if necessary. + void Lock(); + + // Release the lock. This must only be called by the lock's holder: after + // a successful call to Try, or a call to Lock. + void Unlock(); + + // Return the native underlying lock. + // TODO(awalker): refactor lock and condition variables so that this is + // unnecessary. + OSLockType* os_lock() { return &os_lock_; } + + private: + OSLockType os_lock_; + + DISALLOW_COPY_AND_ASSIGN(LockImpl); +}; + +} // namespace internal +} // namespace base + +#endif // BASE_SYNCHRONIZATION_LOCK_IMPL_H_ diff --git a/base/synchronization/lock_impl_posix.cc b/base/synchronization/lock_impl_posix.cc new file mode 100644 index 0000000000..86158767ea --- /dev/null +++ b/base/synchronization/lock_impl_posix.cc @@ -0,0 +1,55 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/synchronization/lock_impl.h" + +#include +#include + +#include "base/logging.h" + +namespace base { +namespace internal { + +LockImpl::LockImpl() { +#ifndef NDEBUG + // In debug, setup attributes for lock error checking. + pthread_mutexattr_t mta; + int rv = pthread_mutexattr_init(&mta); + DCHECK_EQ(rv, 0) << ". " << strerror(rv); + rv = pthread_mutexattr_settype(&mta, PTHREAD_MUTEX_ERRORCHECK); + DCHECK_EQ(rv, 0) << ". " << strerror(rv); + rv = pthread_mutex_init(&os_lock_, &mta); + DCHECK_EQ(rv, 0) << ". " << strerror(rv); + rv = pthread_mutexattr_destroy(&mta); + DCHECK_EQ(rv, 0) << ". " << strerror(rv); +#else + // In release, go with the default lock attributes. + pthread_mutex_init(&os_lock_, NULL); +#endif +} + +LockImpl::~LockImpl() { + int rv = pthread_mutex_destroy(&os_lock_); + DCHECK_EQ(rv, 0) << ". " << strerror(rv); +} + +bool LockImpl::Try() { + int rv = pthread_mutex_trylock(&os_lock_); + DCHECK(rv == 0 || rv == EBUSY) << ". " << strerror(rv); + return rv == 0; +} + +void LockImpl::Lock() { + int rv = pthread_mutex_lock(&os_lock_); + DCHECK_EQ(rv, 0) << ". " << strerror(rv); +} + +void LockImpl::Unlock() { + int rv = pthread_mutex_unlock(&os_lock_); + DCHECK_EQ(rv, 0) << ". " << strerror(rv); +} + +} // namespace internal +} // namespace base diff --git a/base/synchronization/lock_impl_win.cc b/base/synchronization/lock_impl_win.cc new file mode 100644 index 0000000000..bb8a23d898 --- /dev/null +++ b/base/synchronization/lock_impl_win.cc @@ -0,0 +1,36 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/synchronization/lock_impl.h" + +namespace base { +namespace internal { + +LockImpl::LockImpl() { + // The second parameter is the spin count, for short-held locks it avoid the + // contending thread from going to sleep which helps performance greatly. + ::InitializeCriticalSectionAndSpinCount(&os_lock_, 2000); +} + +LockImpl::~LockImpl() { + ::DeleteCriticalSection(&os_lock_); +} + +bool LockImpl::Try() { + if (::TryEnterCriticalSection(&os_lock_) != FALSE) { + return true; + } + return false; +} + +void LockImpl::Lock() { + ::EnterCriticalSection(&os_lock_); +} + +void LockImpl::Unlock() { + ::LeaveCriticalSection(&os_lock_); +} + +} // namespace internal +} // namespace base diff --git a/base/synchronization/lock_unittest.cc b/base/synchronization/lock_unittest.cc new file mode 100644 index 0000000000..161447557f --- /dev/null +++ b/base/synchronization/lock_unittest.cc @@ -0,0 +1,216 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/synchronization/lock.h" + +#include + +#include "base/compiler_specific.h" +#include "base/threading/platform_thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +// Basic test to make sure that Acquire()/Release()/Try() don't crash ---------- + +class BasicLockTestThread : public PlatformThread::Delegate { + public: + explicit BasicLockTestThread(Lock* lock) : lock_(lock), acquired_(0) {} + + virtual void ThreadMain() OVERRIDE { + for (int i = 0; i < 10; i++) { + lock_->Acquire(); + acquired_++; + lock_->Release(); + } + for (int i = 0; i < 10; i++) { + lock_->Acquire(); + acquired_++; + PlatformThread::Sleep(TimeDelta::FromMilliseconds(rand() % 20)); + lock_->Release(); + } + for (int i = 0; i < 10; i++) { + if (lock_->Try()) { + acquired_++; + PlatformThread::Sleep(TimeDelta::FromMilliseconds(rand() % 20)); + lock_->Release(); + } + } + } + + int acquired() const { return acquired_; } + + private: + Lock* lock_; + int acquired_; + + DISALLOW_COPY_AND_ASSIGN(BasicLockTestThread); +}; + +TEST(LockTest, Basic) { + Lock lock; + BasicLockTestThread thread(&lock); + PlatformThreadHandle handle; + + ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle)); + + int acquired = 0; + for (int i = 0; i < 5; i++) { + lock.Acquire(); + acquired++; + lock.Release(); + } + for (int i = 0; i < 10; i++) { + lock.Acquire(); + acquired++; + PlatformThread::Sleep(TimeDelta::FromMilliseconds(rand() % 20)); + lock.Release(); + } + for (int i = 0; i < 10; i++) { + if (lock.Try()) { + acquired++; + PlatformThread::Sleep(TimeDelta::FromMilliseconds(rand() % 20)); + lock.Release(); + } + } + for (int i = 0; i < 5; i++) { + lock.Acquire(); + acquired++; + PlatformThread::Sleep(TimeDelta::FromMilliseconds(rand() % 20)); + lock.Release(); + } + + PlatformThread::Join(handle); + + EXPECT_GE(acquired, 20); + EXPECT_GE(thread.acquired(), 20); +} + +// Test that Try() works as expected ------------------------------------------- + +class TryLockTestThread : public PlatformThread::Delegate { + public: + explicit TryLockTestThread(Lock* lock) : lock_(lock), got_lock_(false) {} + + virtual void ThreadMain() OVERRIDE { + got_lock_ = lock_->Try(); + if (got_lock_) + lock_->Release(); + } + + bool got_lock() const { return got_lock_; } + + private: + Lock* lock_; + bool got_lock_; + + DISALLOW_COPY_AND_ASSIGN(TryLockTestThread); +}; + +TEST(LockTest, TryLock) { + Lock lock; + + ASSERT_TRUE(lock.Try()); + // We now have the lock.... + + // This thread will not be able to get the lock. + { + TryLockTestThread thread(&lock); + PlatformThreadHandle handle; + + ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle)); + + PlatformThread::Join(handle); + + ASSERT_FALSE(thread.got_lock()); + } + + lock.Release(); + + // This thread will.... + { + TryLockTestThread thread(&lock); + PlatformThreadHandle handle; + + ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle)); + + PlatformThread::Join(handle); + + ASSERT_TRUE(thread.got_lock()); + // But it released it.... + ASSERT_TRUE(lock.Try()); + } + + lock.Release(); +} + +// Tests that locks actually exclude ------------------------------------------- + +class MutexLockTestThread : public PlatformThread::Delegate { + public: + MutexLockTestThread(Lock* lock, int* value) : lock_(lock), value_(value) {} + + // Static helper which can also be called from the main thread. + static void DoStuff(Lock* lock, int* value) { + for (int i = 0; i < 40; i++) { + lock->Acquire(); + int v = *value; + PlatformThread::Sleep(TimeDelta::FromMilliseconds(rand() % 10)); + *value = v + 1; + lock->Release(); + } + } + + virtual void ThreadMain() OVERRIDE { + DoStuff(lock_, value_); + } + + private: + Lock* lock_; + int* value_; + + DISALLOW_COPY_AND_ASSIGN(MutexLockTestThread); +}; + +TEST(LockTest, MutexTwoThreads) { + Lock lock; + int value = 0; + + MutexLockTestThread thread(&lock, &value); + PlatformThreadHandle handle; + + ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle)); + + MutexLockTestThread::DoStuff(&lock, &value); + + PlatformThread::Join(handle); + + EXPECT_EQ(2 * 40, value); +} + +TEST(LockTest, MutexFourThreads) { + Lock lock; + int value = 0; + + MutexLockTestThread thread1(&lock, &value); + MutexLockTestThread thread2(&lock, &value); + MutexLockTestThread thread3(&lock, &value); + PlatformThreadHandle handle1; + PlatformThreadHandle handle2; + PlatformThreadHandle handle3; + + ASSERT_TRUE(PlatformThread::Create(0, &thread1, &handle1)); + ASSERT_TRUE(PlatformThread::Create(0, &thread2, &handle2)); + ASSERT_TRUE(PlatformThread::Create(0, &thread3, &handle3)); + + MutexLockTestThread::DoStuff(&lock, &value); + + PlatformThread::Join(handle1); + PlatformThread::Join(handle2); + PlatformThread::Join(handle3); + + EXPECT_EQ(4 * 40, value); +} + +} // namespace base diff --git a/base/synchronization/spin_wait.h b/base/synchronization/spin_wait.h new file mode 100644 index 0000000000..9b147cda96 --- /dev/null +++ b/base/synchronization/spin_wait.h @@ -0,0 +1,50 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file provides a macro ONLY for use in testing. +// DO NOT USE IN PRODUCTION CODE. There are much better ways to wait. + +// This code is very helpful in testing multi-threaded code, without depending +// on almost any primitives. This is especially helpful if you are testing +// those primitive multi-threaded constructs. + +// We provide a simple one argument spin wait (for 1 second), and a generic +// spin wait (for longer periods of time). + +#ifndef BASE_SYNCHRONIZATION_SPIN_WAIT_H_ +#define BASE_SYNCHRONIZATION_SPIN_WAIT_H_ + +#include "base/threading/platform_thread.h" +#include "base/time/time.h" + +// Provide a macro that will wait no longer than 1 second for an asynchronous +// change is the value of an expression. +// A typical use would be: +// +// SPIN_FOR_1_SECOND_OR_UNTIL_TRUE(0 == f(x)); +// +// The expression will be evaluated repeatedly until it is true, or until +// the time (1 second) expires. +// Since tests generally have a 5 second watch dog timer, this spin loop is +// typically used to get the padding needed on a given test platform to assure +// that the test passes, even if load varies, and external events vary. + +#define SPIN_FOR_1_SECOND_OR_UNTIL_TRUE(expression) \ + SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(base::TimeDelta::FromSeconds(1), \ + (expression)) + +#define SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(delta, expression) do { \ + base::TimeTicks start = base::TimeTicks::Now(); \ + const base::TimeDelta kTimeout = delta; \ + while (!(expression)) { \ + if (kTimeout < base::TimeTicks::Now() - start) { \ + EXPECT_LE((base::TimeTicks::Now() - start).InMilliseconds(), \ + kTimeout.InMilliseconds()) << "Timed out"; \ + break; \ + } \ + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50)); \ + } \ + } while (0) + +#endif // BASE_SYNCHRONIZATION_SPIN_WAIT_H_ diff --git a/base/synchronization/waitable_event.h b/base/synchronization/waitable_event.h new file mode 100644 index 0000000000..26e27791b6 --- /dev/null +++ b/base/synchronization/waitable_event.h @@ -0,0 +1,182 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_SYNCHRONIZATION_WAITABLE_EVENT_H_ +#define BASE_SYNCHRONIZATION_WAITABLE_EVENT_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" + +#if defined(OS_WIN) +#include +#endif + +#if defined(OS_POSIX) +#include +#include +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#endif + +namespace base { + +// This replaces INFINITE from Win32 +static const int kNoTimeout = -1; + +class TimeDelta; + +// A WaitableEvent can be a useful thread synchronization tool when you want to +// allow one thread to wait for another thread to finish some work. For +// non-Windows systems, this can only be used from within a single address +// space. +// +// Use a WaitableEvent when you would otherwise use a Lock+ConditionVariable to +// protect a simple boolean value. However, if you find yourself using a +// WaitableEvent in conjunction with a Lock to wait for a more complex state +// change (e.g., for an item to be added to a queue), then you should probably +// be using a ConditionVariable instead of a WaitableEvent. +// +// NOTE: On Windows, this class provides a subset of the functionality afforded +// by a Windows event object. This is intentional. If you are writing Windows +// specific code and you need other features of a Windows event, then you might +// be better off just using an Windows event directly. +class BASE_EXPORT WaitableEvent { + public: + // If manual_reset is true, then to set the event state to non-signaled, a + // consumer must call the Reset method. If this parameter is false, then the + // system automatically resets the event state to non-signaled after a single + // waiting thread has been released. + WaitableEvent(bool manual_reset, bool initially_signaled); + +#if defined(OS_WIN) + // Create a WaitableEvent from an Event HANDLE which has already been + // created. This objects takes ownership of the HANDLE and will close it when + // deleted. + explicit WaitableEvent(HANDLE event_handle); + + // Releases ownership of the handle from this object. + HANDLE Release(); +#endif + + ~WaitableEvent(); + + // Put the event in the un-signaled state. + void Reset(); + + // Put the event in the signaled state. Causing any thread blocked on Wait + // to be woken up. + void Signal(); + + // Returns true if the event is in the signaled state, else false. If this + // is not a manual reset event, then this test will cause a reset. + bool IsSignaled(); + + // Wait indefinitely for the event to be signaled. + void Wait(); + + // Wait up until max_time has passed for the event to be signaled. Returns + // true if the event was signaled. If this method returns false, then it + // does not necessarily mean that max_time was exceeded. + bool TimedWait(const TimeDelta& max_time); + +#if defined(OS_WIN) + HANDLE handle() const { return handle_; } +#endif + + // Wait, synchronously, on multiple events. + // waitables: an array of WaitableEvent pointers + // count: the number of elements in @waitables + // + // returns: the index of a WaitableEvent which has been signaled. + // + // You MUST NOT delete any of the WaitableEvent objects while this wait is + // happening. + static size_t WaitMany(WaitableEvent** waitables, size_t count); + + // For asynchronous waiting, see WaitableEventWatcher + + // This is a private helper class. It's here because it's used by friends of + // this class (such as WaitableEventWatcher) to be able to enqueue elements + // of the wait-list + class Waiter { + public: + // Signal the waiter to wake up. + // + // Consider the case of a Waiter which is in multiple WaitableEvent's + // wait-lists. Each WaitableEvent is automatic-reset and two of them are + // signaled at the same time. Now, each will wake only the first waiter in + // the wake-list before resetting. However, if those two waiters happen to + // be the same object (as can happen if another thread didn't have a chance + // to dequeue the waiter from the other wait-list in time), two auto-resets + // will have happened, but only one waiter has been signaled! + // + // Because of this, a Waiter may "reject" a wake by returning false. In + // this case, the auto-reset WaitableEvent shouldn't act as if anything has + // been notified. + virtual bool Fire(WaitableEvent* signaling_event) = 0; + + // Waiters may implement this in order to provide an extra condition for + // two Waiters to be considered equal. In WaitableEvent::Dequeue, if the + // pointers match then this function is called as a final check. See the + // comments in ~Handle for why. + virtual bool Compare(void* tag) = 0; + + protected: + virtual ~Waiter() {} + }; + + private: + friend class WaitableEventWatcher; + +#if defined(OS_WIN) + HANDLE handle_; +#else + // On Windows, one can close a HANDLE which is currently being waited on. The + // MSDN documentation says that the resulting behaviour is 'undefined', but + // it doesn't crash. However, if we were to include the following members + // directly then, on POSIX, one couldn't use WaitableEventWatcher to watch an + // event which gets deleted. This mismatch has bitten us several times now, + // so we have a kernel of the WaitableEvent, which is reference counted. + // WaitableEventWatchers may then take a reference and thus match the Windows + // behaviour. + struct WaitableEventKernel : + public RefCountedThreadSafe { + public: + WaitableEventKernel(bool manual_reset, bool initially_signaled); + + bool Dequeue(Waiter* waiter, void* tag); + + base::Lock lock_; + const bool manual_reset_; + bool signaled_; + std::list waiters_; + + private: + friend class RefCountedThreadSafe; + ~WaitableEventKernel(); + }; + + typedef std::pair WaiterAndIndex; + + // When dealing with arrays of WaitableEvent*, we want to sort by the address + // of the WaitableEvent in order to have a globally consistent locking order. + // In that case we keep them, in sorted order, in an array of pairs where the + // second element is the index of the WaitableEvent in the original, + // unsorted, array. + static size_t EnqueueMany(WaiterAndIndex* waitables, + size_t count, Waiter* waiter); + + bool SignalAll(); + bool SignalOne(); + void Enqueue(Waiter* waiter); + + scoped_refptr kernel_; +#endif + + DISALLOW_COPY_AND_ASSIGN(WaitableEvent); +}; + +} // namespace base + +#endif // BASE_SYNCHRONIZATION_WAITABLE_EVENT_H_ diff --git a/base/synchronization/waitable_event_posix.cc b/base/synchronization/waitable_event_posix.cc new file mode 100644 index 0000000000..714c111e19 --- /dev/null +++ b/base/synchronization/waitable_event_posix.cc @@ -0,0 +1,407 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include "base/logging.h" +#include "base/synchronization/waitable_event.h" +#include "base/synchronization/condition_variable.h" +#include "base/synchronization/lock.h" +#include "base/threading/thread_restrictions.h" + +// ----------------------------------------------------------------------------- +// A WaitableEvent on POSIX is implemented as a wait-list. Currently we don't +// support cross-process events (where one process can signal an event which +// others are waiting on). Because of this, we can avoid having one thread per +// listener in several cases. +// +// The WaitableEvent maintains a list of waiters, protected by a lock. Each +// waiter is either an async wait, in which case we have a Task and the +// MessageLoop to run it on, or a blocking wait, in which case we have the +// condition variable to signal. +// +// Waiting involves grabbing the lock and adding oneself to the wait list. Async +// waits can be canceled, which means grabbing the lock and removing oneself +// from the list. +// +// Waiting on multiple events is handled by adding a single, synchronous wait to +// the wait-list of many events. An event passes a pointer to itself when +// firing a waiter and so we can store that pointer to find out which event +// triggered. +// ----------------------------------------------------------------------------- + +namespace base { + +// ----------------------------------------------------------------------------- +// This is just an abstract base class for waking the two types of waiters +// ----------------------------------------------------------------------------- +WaitableEvent::WaitableEvent(bool manual_reset, bool initially_signaled) + : kernel_(new WaitableEventKernel(manual_reset, initially_signaled)) { +} + +WaitableEvent::~WaitableEvent() { +} + +void WaitableEvent::Reset() { + base::AutoLock locked(kernel_->lock_); + kernel_->signaled_ = false; +} + +void WaitableEvent::Signal() { + base::AutoLock locked(kernel_->lock_); + + if (kernel_->signaled_) + return; + + if (kernel_->manual_reset_) { + SignalAll(); + kernel_->signaled_ = true; + } else { + // In the case of auto reset, if no waiters were woken, we remain + // signaled. + if (!SignalOne()) + kernel_->signaled_ = true; + } +} + +bool WaitableEvent::IsSignaled() { + base::AutoLock locked(kernel_->lock_); + + const bool result = kernel_->signaled_; + if (result && !kernel_->manual_reset_) + kernel_->signaled_ = false; + return result; +} + +// ----------------------------------------------------------------------------- +// Synchronous waits + +// ----------------------------------------------------------------------------- +// This is a synchronous waiter. The thread is waiting on the given condition +// variable and the fired flag in this object. +// ----------------------------------------------------------------------------- +class SyncWaiter : public WaitableEvent::Waiter { + public: + SyncWaiter() + : fired_(false), + signaling_event_(NULL), + lock_(), + cv_(&lock_) { + } + + virtual bool Fire(WaitableEvent* signaling_event) OVERRIDE { + base::AutoLock locked(lock_); + + if (fired_) + return false; + + fired_ = true; + signaling_event_ = signaling_event; + + cv_.Broadcast(); + + // Unlike AsyncWaiter objects, SyncWaiter objects are stack-allocated on + // the blocking thread's stack. There is no |delete this;| in Fire. The + // SyncWaiter object is destroyed when it goes out of scope. + + return true; + } + + WaitableEvent* signaling_event() const { + return signaling_event_; + } + + // --------------------------------------------------------------------------- + // These waiters are always stack allocated and don't delete themselves. Thus + // there's no problem and the ABA tag is the same as the object pointer. + // --------------------------------------------------------------------------- + virtual bool Compare(void* tag) OVERRIDE { + return this == tag; + } + + // --------------------------------------------------------------------------- + // Called with lock held. + // --------------------------------------------------------------------------- + bool fired() const { + return fired_; + } + + // --------------------------------------------------------------------------- + // During a TimedWait, we need a way to make sure that an auto-reset + // WaitableEvent doesn't think that this event has been signaled between + // unlocking it and removing it from the wait-list. Called with lock held. + // --------------------------------------------------------------------------- + void Disable() { + fired_ = true; + } + + base::Lock* lock() { + return &lock_; + } + + base::ConditionVariable* cv() { + return &cv_; + } + + private: + bool fired_; + WaitableEvent* signaling_event_; // The WaitableEvent which woke us + base::Lock lock_; + base::ConditionVariable cv_; +}; + +void WaitableEvent::Wait() { + bool result = TimedWait(TimeDelta::FromSeconds(-1)); + DCHECK(result) << "TimedWait() should never fail with infinite timeout"; +} + +bool WaitableEvent::TimedWait(const TimeDelta& max_time) { + base::ThreadRestrictions::AssertWaitAllowed(); + const Time end_time(Time::Now() + max_time); + const bool finite_time = max_time.ToInternalValue() >= 0; + + kernel_->lock_.Acquire(); + if (kernel_->signaled_) { + if (!kernel_->manual_reset_) { + // In this case we were signaled when we had no waiters. Now that + // someone has waited upon us, we can automatically reset. + kernel_->signaled_ = false; + } + + kernel_->lock_.Release(); + return true; + } + + SyncWaiter sw; + sw.lock()->Acquire(); + + Enqueue(&sw); + kernel_->lock_.Release(); + // We are violating locking order here by holding the SyncWaiter lock but not + // the WaitableEvent lock. However, this is safe because we don't lock @lock_ + // again before unlocking it. + + for (;;) { + const Time current_time(Time::Now()); + + if (sw.fired() || (finite_time && current_time >= end_time)) { + const bool return_value = sw.fired(); + + // We can't acquire @lock_ before releasing the SyncWaiter lock (because + // of locking order), however, in between the two a signal could be fired + // and @sw would accept it, however we will still return false, so the + // signal would be lost on an auto-reset WaitableEvent. Thus we call + // Disable which makes sw::Fire return false. + sw.Disable(); + sw.lock()->Release(); + + kernel_->lock_.Acquire(); + kernel_->Dequeue(&sw, &sw); + kernel_->lock_.Release(); + + return return_value; + } + + if (finite_time) { + const TimeDelta max_wait(end_time - current_time); + sw.cv()->TimedWait(max_wait); + } else { + sw.cv()->Wait(); + } + } +} + +// ----------------------------------------------------------------------------- +// Synchronous waiting on multiple objects. + +static bool // StrictWeakOrdering +cmp_fst_addr(const std::pair &a, + const std::pair &b) { + return a.first < b.first; +} + +// static +size_t WaitableEvent::WaitMany(WaitableEvent** raw_waitables, + size_t count) { + base::ThreadRestrictions::AssertWaitAllowed(); + DCHECK(count) << "Cannot wait on no events"; + + // We need to acquire the locks in a globally consistent order. Thus we sort + // the array of waitables by address. We actually sort a pairs so that we can + // map back to the original index values later. + std::vector > waitables; + waitables.reserve(count); + for (size_t i = 0; i < count; ++i) + waitables.push_back(std::make_pair(raw_waitables[i], i)); + + DCHECK_EQ(count, waitables.size()); + + sort(waitables.begin(), waitables.end(), cmp_fst_addr); + + // The set of waitables must be distinct. Since we have just sorted by + // address, we can check this cheaply by comparing pairs of consecutive + // elements. + for (size_t i = 0; i < waitables.size() - 1; ++i) { + DCHECK(waitables[i].first != waitables[i+1].first); + } + + SyncWaiter sw; + + const size_t r = EnqueueMany(&waitables[0], count, &sw); + if (r) { + // One of the events is already signaled. The SyncWaiter has not been + // enqueued anywhere. EnqueueMany returns the count of remaining waitables + // when the signaled one was seen, so the index of the signaled event is + // @count - @r. + return waitables[count - r].second; + } + + // At this point, we hold the locks on all the WaitableEvents and we have + // enqueued our waiter in them all. + sw.lock()->Acquire(); + // Release the WaitableEvent locks in the reverse order + for (size_t i = 0; i < count; ++i) { + waitables[count - (1 + i)].first->kernel_->lock_.Release(); + } + + for (;;) { + if (sw.fired()) + break; + + sw.cv()->Wait(); + } + sw.lock()->Release(); + + // The address of the WaitableEvent which fired is stored in the SyncWaiter. + WaitableEvent *const signaled_event = sw.signaling_event(); + // This will store the index of the raw_waitables which fired. + size_t signaled_index = 0; + + // Take the locks of each WaitableEvent in turn (except the signaled one) and + // remove our SyncWaiter from the wait-list + for (size_t i = 0; i < count; ++i) { + if (raw_waitables[i] != signaled_event) { + raw_waitables[i]->kernel_->lock_.Acquire(); + // There's no possible ABA issue with the address of the SyncWaiter here + // because it lives on the stack. Thus the tag value is just the pointer + // value again. + raw_waitables[i]->kernel_->Dequeue(&sw, &sw); + raw_waitables[i]->kernel_->lock_.Release(); + } else { + signaled_index = i; + } + } + + return signaled_index; +} + +// ----------------------------------------------------------------------------- +// If return value == 0: +// The locks of the WaitableEvents have been taken in order and the Waiter has +// been enqueued in the wait-list of each. None of the WaitableEvents are +// currently signaled +// else: +// None of the WaitableEvent locks are held. The Waiter has not been enqueued +// in any of them and the return value is the index of the first WaitableEvent +// which was signaled, from the end of the array. +// ----------------------------------------------------------------------------- +// static +size_t WaitableEvent::EnqueueMany + (std::pair* waitables, + size_t count, Waiter* waiter) { + if (!count) + return 0; + + waitables[0].first->kernel_->lock_.Acquire(); + if (waitables[0].first->kernel_->signaled_) { + if (!waitables[0].first->kernel_->manual_reset_) + waitables[0].first->kernel_->signaled_ = false; + waitables[0].first->kernel_->lock_.Release(); + return count; + } + + const size_t r = EnqueueMany(waitables + 1, count - 1, waiter); + if (r) { + waitables[0].first->kernel_->lock_.Release(); + } else { + waitables[0].first->Enqueue(waiter); + } + + return r; +} + +// ----------------------------------------------------------------------------- + + +// ----------------------------------------------------------------------------- +// Private functions... + +WaitableEvent::WaitableEventKernel::WaitableEventKernel(bool manual_reset, + bool initially_signaled) + : manual_reset_(manual_reset), + signaled_(initially_signaled) { +} + +WaitableEvent::WaitableEventKernel::~WaitableEventKernel() { +} + +// ----------------------------------------------------------------------------- +// Wake all waiting waiters. Called with lock held. +// ----------------------------------------------------------------------------- +bool WaitableEvent::SignalAll() { + bool signaled_at_least_one = false; + + for (std::list::iterator + i = kernel_->waiters_.begin(); i != kernel_->waiters_.end(); ++i) { + if ((*i)->Fire(this)) + signaled_at_least_one = true; + } + + kernel_->waiters_.clear(); + return signaled_at_least_one; +} + +// --------------------------------------------------------------------------- +// Try to wake a single waiter. Return true if one was woken. Called with lock +// held. +// --------------------------------------------------------------------------- +bool WaitableEvent::SignalOne() { + for (;;) { + if (kernel_->waiters_.empty()) + return false; + + const bool r = (*kernel_->waiters_.begin())->Fire(this); + kernel_->waiters_.pop_front(); + if (r) + return true; + } +} + +// ----------------------------------------------------------------------------- +// Add a waiter to the list of those waiting. Called with lock held. +// ----------------------------------------------------------------------------- +void WaitableEvent::Enqueue(Waiter* waiter) { + kernel_->waiters_.push_back(waiter); +} + +// ----------------------------------------------------------------------------- +// Remove a waiter from the list of those waiting. Return true if the waiter was +// actually removed. Called with lock held. +// ----------------------------------------------------------------------------- +bool WaitableEvent::WaitableEventKernel::Dequeue(Waiter* waiter, void* tag) { + for (std::list::iterator + i = waiters_.begin(); i != waiters_.end(); ++i) { + if (*i == waiter && (*i)->Compare(tag)) { + waiters_.erase(i); + return true; + } + } + + return false; +} + +// ----------------------------------------------------------------------------- + +} // namespace base diff --git a/base/synchronization/waitable_event_unittest.cc b/base/synchronization/waitable_event_unittest.cc new file mode 100644 index 0000000000..b66f6e8720 --- /dev/null +++ b/base/synchronization/waitable_event_unittest.cc @@ -0,0 +1,108 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/synchronization/waitable_event.h" + +#include "base/compiler_specific.h" +#include "base/threading/platform_thread.h" +#include "base/time/time.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +TEST(WaitableEventTest, ManualBasics) { + WaitableEvent event(true, false); + + EXPECT_FALSE(event.IsSignaled()); + + event.Signal(); + EXPECT_TRUE(event.IsSignaled()); + EXPECT_TRUE(event.IsSignaled()); + + event.Reset(); + EXPECT_FALSE(event.IsSignaled()); + EXPECT_FALSE(event.TimedWait(TimeDelta::FromMilliseconds(10))); + + event.Signal(); + event.Wait(); + EXPECT_TRUE(event.TimedWait(TimeDelta::FromMilliseconds(10))); +} + +TEST(WaitableEventTest, AutoBasics) { + WaitableEvent event(false, false); + + EXPECT_FALSE(event.IsSignaled()); + + event.Signal(); + EXPECT_TRUE(event.IsSignaled()); + EXPECT_FALSE(event.IsSignaled()); + + event.Reset(); + EXPECT_FALSE(event.IsSignaled()); + EXPECT_FALSE(event.TimedWait(TimeDelta::FromMilliseconds(10))); + + event.Signal(); + event.Wait(); + EXPECT_FALSE(event.TimedWait(TimeDelta::FromMilliseconds(10))); + + event.Signal(); + EXPECT_TRUE(event.TimedWait(TimeDelta::FromMilliseconds(10))); +} + +TEST(WaitableEventTest, WaitManyShortcut) { + WaitableEvent* ev[5]; + for (unsigned i = 0; i < 5; ++i) + ev[i] = new WaitableEvent(false, false); + + ev[3]->Signal(); + EXPECT_EQ(WaitableEvent::WaitMany(ev, 5), 3u); + + ev[3]->Signal(); + EXPECT_EQ(WaitableEvent::WaitMany(ev, 5), 3u); + + ev[4]->Signal(); + EXPECT_EQ(WaitableEvent::WaitMany(ev, 5), 4u); + + ev[0]->Signal(); + EXPECT_EQ(WaitableEvent::WaitMany(ev, 5), 0u); + + for (unsigned i = 0; i < 5; ++i) + delete ev[i]; +} + +class WaitableEventSignaler : public PlatformThread::Delegate { + public: + WaitableEventSignaler(double seconds, WaitableEvent* ev) + : seconds_(seconds), + ev_(ev) { + } + + virtual void ThreadMain() OVERRIDE { + PlatformThread::Sleep(TimeDelta::FromSeconds(static_cast(seconds_))); + ev_->Signal(); + } + + private: + const double seconds_; + WaitableEvent *const ev_; +}; + +TEST(WaitableEventTest, WaitMany) { + WaitableEvent* ev[5]; + for (unsigned i = 0; i < 5; ++i) + ev[i] = new WaitableEvent(false, false); + + WaitableEventSignaler signaler(0.1, ev[2]); + PlatformThreadHandle thread; + PlatformThread::Create(0, &signaler, &thread); + + EXPECT_EQ(WaitableEvent::WaitMany(ev, 5), 2u); + + PlatformThread::Join(thread); + + for (unsigned i = 0; i < 5; ++i) + delete ev[i]; +} + +} // namespace base diff --git a/base/synchronization/waitable_event_watcher.h b/base/synchronization/waitable_event_watcher.h new file mode 100644 index 0000000000..ede2835490 --- /dev/null +++ b/base/synchronization/waitable_event_watcher.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_SYNCHRONIZATION_WAITABLE_EVENT_WATCHER_H_ +#define BASE_SYNCHRONIZATION_WAITABLE_EVENT_WATCHER_H_ + +#include "base/base_export.h" +#include "build/build_config.h" + +#if defined(OS_WIN) +#include "base/win/object_watcher.h" +#else +#include "base/callback.h" +#include "base/message_loop/message_loop.h" +#include "base/synchronization/waitable_event.h" +#endif + +namespace base { + +class Flag; +class AsyncWaiter; +class AsyncCallbackTask; +class WaitableEvent; + +// This class provides a way to wait on a WaitableEvent asynchronously. +// +// Each instance of this object can be waiting on a single WaitableEvent. When +// the waitable event is signaled, a callback is made in the thread of a given +// MessageLoop. This callback can be deleted by deleting the waiter. +// +// Typical usage: +// +// class MyClass { +// public: +// void DoStuffWhenSignaled(WaitableEvent *waitable_event) { +// watcher_.StartWatching(waitable_event, +// base::Bind(&MyClass::OnWaitableEventSignaled, this); +// } +// private: +// void OnWaitableEventSignaled(WaitableEvent* waitable_event) { +// // OK, time to do stuff! +// } +// base::WaitableEventWatcher watcher_; +// }; +// +// In the above example, MyClass wants to "do stuff" when waitable_event +// becomes signaled. WaitableEventWatcher makes this task easy. When MyClass +// goes out of scope, the watcher_ will be destroyed, and there is no need to +// worry about OnWaitableEventSignaled being called on a deleted MyClass +// pointer. +// +// BEWARE: With automatically reset WaitableEvents, a signal may be lost if it +// occurs just before a WaitableEventWatcher is deleted. There is currently no +// safe way to stop watching an automatic reset WaitableEvent without possibly +// missing a signal. +// +// NOTE: you /are/ allowed to delete the WaitableEvent while still waiting on +// it with a Watcher. It will act as if the event was never signaled. + +class BASE_EXPORT WaitableEventWatcher +#if defined(OS_WIN) + : public win::ObjectWatcher::Delegate { +#else + : public MessageLoop::DestructionObserver { +#endif + public: + typedef Callback EventCallback; + WaitableEventWatcher(); + virtual ~WaitableEventWatcher(); + + // When @event is signaled, the given callback is called on the thread of the + // current message loop when StartWatching is called. + bool StartWatching(WaitableEvent* event, const EventCallback& callback); + + // Cancel the current watch. Must be called from the same thread which + // started the watch. + // + // Does nothing if no event is being watched, nor if the watch has completed. + // The callback will *not* be called for the current watch after this + // function returns. Since the callback runs on the same thread as this + // function, it cannot be called during this function either. + void StopWatching(); + + // Return the currently watched event, or NULL if no object is currently being + // watched. + WaitableEvent* GetWatchedEvent(); + + // Return the callback that will be invoked when the event is + // signaled. + const EventCallback& callback() const { return callback_; } + + private: +#if defined(OS_WIN) + virtual void OnObjectSignaled(HANDLE h) OVERRIDE; + win::ObjectWatcher watcher_; +#else + // Implementation of MessageLoop::DestructionObserver + virtual void WillDestroyCurrentMessageLoop() OVERRIDE; + + MessageLoop* message_loop_; + scoped_refptr cancel_flag_; + AsyncWaiter* waiter_; + base::Closure internal_callback_; + scoped_refptr kernel_; +#endif + + WaitableEvent* event_; + EventCallback callback_; +}; + +} // namespace base + +#endif // BASE_SYNCHRONIZATION_WAITABLE_EVENT_WATCHER_H_ diff --git a/base/synchronization/waitable_event_watcher_posix.cc b/base/synchronization/waitable_event_watcher_posix.cc new file mode 100644 index 0000000000..54e01f8864 --- /dev/null +++ b/base/synchronization/waitable_event_watcher_posix.cc @@ -0,0 +1,271 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/synchronization/waitable_event_watcher.h" + +#include "base/bind.h" +#include "base/location.h" +#include "base/message_loop/message_loop.h" +#include "base/synchronization/lock.h" +#include "base/synchronization/waitable_event.h" + +namespace base { + +// ----------------------------------------------------------------------------- +// WaitableEventWatcher (async waits). +// +// The basic design is that we add an AsyncWaiter to the wait-list of the event. +// That AsyncWaiter has a pointer to MessageLoop, and a Task to be posted to it. +// The MessageLoop ends up running the task, which calls the delegate. +// +// Since the wait can be canceled, we have a thread-safe Flag object which is +// set when the wait has been canceled. At each stage in the above, we check the +// flag before going onto the next stage. Since the wait may only be canceled in +// the MessageLoop which runs the Task, we are assured that the delegate cannot +// be called after canceling... + +// ----------------------------------------------------------------------------- +// A thread-safe, reference-counted, write-once flag. +// ----------------------------------------------------------------------------- +class Flag : public RefCountedThreadSafe { + public: + Flag() { flag_ = false; } + + void Set() { + AutoLock locked(lock_); + flag_ = true; + } + + bool value() const { + AutoLock locked(lock_); + return flag_; + } + + private: + friend class RefCountedThreadSafe; + ~Flag() {} + + mutable Lock lock_; + bool flag_; + + DISALLOW_COPY_AND_ASSIGN(Flag); +}; + +// ----------------------------------------------------------------------------- +// This is an asynchronous waiter which posts a task to a MessageLoop when +// fired. An AsyncWaiter may only be in a single wait-list. +// ----------------------------------------------------------------------------- +class AsyncWaiter : public WaitableEvent::Waiter { + public: + AsyncWaiter(MessageLoop* message_loop, + const base::Closure& callback, + Flag* flag) + : message_loop_(message_loop), + callback_(callback), + flag_(flag) { } + + virtual bool Fire(WaitableEvent* event) OVERRIDE { + // Post the callback if we haven't been cancelled. + if (!flag_->value()) { + message_loop_->PostTask(FROM_HERE, callback_); + } + + // We are removed from the wait-list by the WaitableEvent itself. It only + // remains to delete ourselves. + delete this; + + // We can always return true because an AsyncWaiter is never in two + // different wait-lists at the same time. + return true; + } + + // See StopWatching for discussion + virtual bool Compare(void* tag) OVERRIDE { + return tag == flag_.get(); + } + + private: + MessageLoop *const message_loop_; + base::Closure callback_; + scoped_refptr flag_; +}; + +// ----------------------------------------------------------------------------- +// For async waits we need to make a callback in a MessageLoop thread. We do +// this by posting a callback, which calls the delegate and keeps track of when +// the event is canceled. +// ----------------------------------------------------------------------------- +void AsyncCallbackHelper(Flag* flag, + const WaitableEventWatcher::EventCallback& callback, + WaitableEvent* event) { + // Runs in MessageLoop thread. + if (!flag->value()) { + // This is to let the WaitableEventWatcher know that the event has occured + // because it needs to be able to return NULL from GetWatchedObject + flag->Set(); + callback.Run(event); + } +} + +WaitableEventWatcher::WaitableEventWatcher() + : message_loop_(NULL), + cancel_flag_(NULL), + waiter_(NULL), + event_(NULL) { +} + +WaitableEventWatcher::~WaitableEventWatcher() { + StopWatching(); +} + +// ----------------------------------------------------------------------------- +// The Handle is how the user cancels a wait. After deleting the Handle we +// insure that the delegate cannot be called. +// ----------------------------------------------------------------------------- +bool WaitableEventWatcher::StartWatching( + WaitableEvent* event, + const EventCallback& callback) { + MessageLoop *const current_ml = MessageLoop::current(); + DCHECK(current_ml) << "Cannot create WaitableEventWatcher without a " + "current MessageLoop"; + + // A user may call StartWatching from within the callback function. In this + // case, we won't know that we have finished watching, expect that the Flag + // will have been set in AsyncCallbackHelper(). + if (cancel_flag_.get() && cancel_flag_->value()) { + if (message_loop_) { + message_loop_->RemoveDestructionObserver(this); + message_loop_ = NULL; + } + + cancel_flag_ = NULL; + } + + DCHECK(!cancel_flag_.get()) << "StartWatching called while still watching"; + + cancel_flag_ = new Flag; + callback_ = callback; + internal_callback_ = + base::Bind(&AsyncCallbackHelper, cancel_flag_, callback_, event); + WaitableEvent::WaitableEventKernel* kernel = event->kernel_.get(); + + AutoLock locked(kernel->lock_); + + event_ = event; + + if (kernel->signaled_) { + if (!kernel->manual_reset_) + kernel->signaled_ = false; + + // No hairpinning - we can't call the delegate directly here. We have to + // enqueue a task on the MessageLoop as normal. + current_ml->PostTask(FROM_HERE, internal_callback_); + return true; + } + + message_loop_ = current_ml; + current_ml->AddDestructionObserver(this); + + kernel_ = kernel; + waiter_ = new AsyncWaiter(current_ml, internal_callback_, cancel_flag_.get()); + event->Enqueue(waiter_); + + return true; +} + +void WaitableEventWatcher::StopWatching() { + callback_.Reset(); + + if (message_loop_) { + message_loop_->RemoveDestructionObserver(this); + message_loop_ = NULL; + } + + if (!cancel_flag_.get()) // if not currently watching... + return; + + if (cancel_flag_->value()) { + // In this case, the event has fired, but we haven't figured that out yet. + // The WaitableEvent may have been deleted too. + cancel_flag_ = NULL; + return; + } + + if (!kernel_.get()) { + // We have no kernel. This means that we never enqueued a Waiter on an + // event because the event was already signaled when StartWatching was + // called. + // + // In this case, a task was enqueued on the MessageLoop and will run. + // We set the flag in case the task hasn't yet run. The flag will stop the + // delegate getting called. If the task has run then we have the last + // reference to the flag and it will be deleted immedately after. + cancel_flag_->Set(); + cancel_flag_ = NULL; + return; + } + + AutoLock locked(kernel_->lock_); + // We have a lock on the kernel. No one else can signal the event while we + // have it. + + // We have a possible ABA issue here. If Dequeue was to compare only the + // pointer values then it's possible that the AsyncWaiter could have been + // fired, freed and the memory reused for a different Waiter which was + // enqueued in the same wait-list. We would think that that waiter was our + // AsyncWaiter and remove it. + // + // To stop this, Dequeue also takes a tag argument which is passed to the + // virtual Compare function before the two are considered a match. So we need + // a tag which is good for the lifetime of this handle: the Flag. Since we + // have a reference to the Flag, its memory cannot be reused while this object + // still exists. So if we find a waiter with the correct pointer value, and + // which shares a Flag pointer, we have a real match. + if (kernel_->Dequeue(waiter_, cancel_flag_.get())) { + // Case 2: the waiter hasn't been signaled yet; it was still on the wait + // list. We've removed it, thus we can delete it and the task (which cannot + // have been enqueued with the MessageLoop because the waiter was never + // signaled) + delete waiter_; + internal_callback_.Reset(); + cancel_flag_ = NULL; + return; + } + + // Case 3: the waiter isn't on the wait-list, thus it was signaled. It may + // not have run yet, so we set the flag to tell it not to bother enqueuing the + // task on the MessageLoop, but to delete it instead. The Waiter deletes + // itself once run. + cancel_flag_->Set(); + cancel_flag_ = NULL; + + // If the waiter has already run then the task has been enqueued. If the Task + // hasn't yet run, the flag will stop the delegate from getting called. (This + // is thread safe because one may only delete a Handle from the MessageLoop + // thread.) + // + // If the delegate has already been called then we have nothing to do. The + // task has been deleted by the MessageLoop. +} + +WaitableEvent* WaitableEventWatcher::GetWatchedEvent() { + if (!cancel_flag_.get()) + return NULL; + + if (cancel_flag_->value()) + return NULL; + + return event_; +} + +// ----------------------------------------------------------------------------- +// This is called when the MessageLoop which the callback will be run it is +// deleted. We need to cancel the callback as if we had been deleted, but we +// will still be deleted at some point in the future. +// ----------------------------------------------------------------------------- +void WaitableEventWatcher::WillDestroyCurrentMessageLoop() { + StopWatching(); +} + +} // namespace base diff --git a/base/synchronization/waitable_event_watcher_unittest.cc b/base/synchronization/waitable_event_watcher_unittest.cc new file mode 100644 index 0000000000..5319d1efba --- /dev/null +++ b/base/synchronization/waitable_event_watcher_unittest.cc @@ -0,0 +1,176 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/synchronization/waitable_event_watcher.h" + +#include "base/bind.h" +#include "base/callback.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/platform_thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace { + +// The message loops on which each waitable event timer should be tested. +const MessageLoop::Type testing_message_loops[] = { + MessageLoop::TYPE_DEFAULT, + MessageLoop::TYPE_IO, +#if !defined(OS_IOS) // iOS does not allow direct running of the UI loop. + MessageLoop::TYPE_UI, +#endif +}; + +const int kNumTestingMessageLoops = arraysize(testing_message_loops); + +void QuitWhenSignaled(WaitableEvent* event) { + MessageLoop::current()->QuitWhenIdle(); +} + +class DecrementCountContainer { + public: + explicit DecrementCountContainer(int* counter) : counter_(counter) { + } + void OnWaitableEventSignaled(WaitableEvent* object) { + --(*counter_); + } + private: + int* counter_; +}; + +void RunTest_BasicSignal(MessageLoop::Type message_loop_type) { + MessageLoop message_loop(message_loop_type); + + // A manual-reset event that is not yet signaled. + WaitableEvent event(true, false); + + WaitableEventWatcher watcher; + EXPECT_TRUE(watcher.GetWatchedEvent() == NULL); + + watcher.StartWatching(&event, Bind(&QuitWhenSignaled)); + EXPECT_EQ(&event, watcher.GetWatchedEvent()); + + event.Signal(); + + MessageLoop::current()->Run(); + + EXPECT_TRUE(watcher.GetWatchedEvent() == NULL); +} + +void RunTest_BasicCancel(MessageLoop::Type message_loop_type) { + MessageLoop message_loop(message_loop_type); + + // A manual-reset event that is not yet signaled. + WaitableEvent event(true, false); + + WaitableEventWatcher watcher; + + watcher.StartWatching(&event, Bind(&QuitWhenSignaled)); + + watcher.StopWatching(); +} + +void RunTest_CancelAfterSet(MessageLoop::Type message_loop_type) { + MessageLoop message_loop(message_loop_type); + + // A manual-reset event that is not yet signaled. + WaitableEvent event(true, false); + + WaitableEventWatcher watcher; + + int counter = 1; + DecrementCountContainer delegate(&counter); + WaitableEventWatcher::EventCallback callback = + Bind(&DecrementCountContainer::OnWaitableEventSignaled, + Unretained(&delegate)); + watcher.StartWatching(&event, callback); + + event.Signal(); + + // Let the background thread do its business + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(30)); + + watcher.StopWatching(); + + RunLoop().RunUntilIdle(); + + // Our delegate should not have fired. + EXPECT_EQ(1, counter); +} + +void RunTest_OutlivesMessageLoop(MessageLoop::Type message_loop_type) { + // Simulate a MessageLoop that dies before an WaitableEventWatcher. This + // ordinarily doesn't happen when people use the Thread class, but it can + // happen when people use the Singleton pattern or atexit. + WaitableEvent event(true, false); + { + WaitableEventWatcher watcher; + { + MessageLoop message_loop(message_loop_type); + + watcher.StartWatching(&event, Bind(&QuitWhenSignaled)); + } + } +} + +void RunTest_DeleteUnder(MessageLoop::Type message_loop_type) { + // Delete the WaitableEvent out from under the Watcher. This is explictly + // allowed by the interface. + + MessageLoop message_loop(message_loop_type); + + { + WaitableEventWatcher watcher; + + WaitableEvent* event = new WaitableEvent(false, false); + + watcher.StartWatching(event, Bind(&QuitWhenSignaled)); + delete event; + } +} + +} // namespace + +//----------------------------------------------------------------------------- + +TEST(WaitableEventWatcherTest, BasicSignal) { + for (int i = 0; i < kNumTestingMessageLoops; i++) { + RunTest_BasicSignal(testing_message_loops[i]); + } +} + +TEST(WaitableEventWatcherTest, BasicCancel) { + for (int i = 0; i < kNumTestingMessageLoops; i++) { + RunTest_BasicCancel(testing_message_loops[i]); + } +} + +TEST(WaitableEventWatcherTest, CancelAfterSet) { + for (int i = 0; i < kNumTestingMessageLoops; i++) { + RunTest_CancelAfterSet(testing_message_loops[i]); + } +} + +TEST(WaitableEventWatcherTest, OutlivesMessageLoop) { + for (int i = 0; i < kNumTestingMessageLoops; i++) { + RunTest_OutlivesMessageLoop(testing_message_loops[i]); + } +} + +#if defined(OS_WIN) +// Crashes sometimes on vista. http://crbug.com/62119 +#define MAYBE_DeleteUnder DISABLED_DeleteUnder +#else +#define MAYBE_DeleteUnder DeleteUnder +#endif +TEST(WaitableEventWatcherTest, MAYBE_DeleteUnder) { + for (int i = 0; i < kNumTestingMessageLoops; i++) { + RunTest_DeleteUnder(testing_message_loops[i]); + } +} + +} // namespace base diff --git a/base/synchronization/waitable_event_watcher_win.cc b/base/synchronization/waitable_event_watcher_win.cc new file mode 100644 index 0000000000..46d47ac581 --- /dev/null +++ b/base/synchronization/waitable_event_watcher_win.cc @@ -0,0 +1,48 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/synchronization/waitable_event_watcher.h" + +#include "base/compiler_specific.h" +#include "base/synchronization/waitable_event.h" +#include "base/win/object_watcher.h" + +namespace base { + +WaitableEventWatcher::WaitableEventWatcher() + : event_(NULL) { +} + +WaitableEventWatcher::~WaitableEventWatcher() { +} + +bool WaitableEventWatcher::StartWatching( + WaitableEvent* event, + const EventCallback& callback) { + callback_ = callback; + event_ = event; + return watcher_.StartWatching(event->handle(), this); +} + +void WaitableEventWatcher::StopWatching() { + callback_.Reset(); + event_ = NULL; + watcher_.StopWatching(); +} + +WaitableEvent* WaitableEventWatcher::GetWatchedEvent() { + return event_; +} + +void WaitableEventWatcher::OnObjectSignaled(HANDLE h) { + WaitableEvent* event = event_; + EventCallback callback = callback_; + event_ = NULL; + callback_.Reset(); + DCHECK(event); + + callback.Run(event); +} + +} // namespace base diff --git a/base/synchronization/waitable_event_win.cc b/base/synchronization/waitable_event_win.cc new file mode 100644 index 0000000000..8259cb74cf --- /dev/null +++ b/base/synchronization/waitable_event_win.cc @@ -0,0 +1,102 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/synchronization/waitable_event.h" + +#include +#include + +#include "base/logging.h" +#include "base/threading/thread_restrictions.h" +#include "base/time/time.h" + +namespace base { + +WaitableEvent::WaitableEvent(bool manual_reset, bool signaled) + : handle_(CreateEvent(NULL, manual_reset, signaled, NULL)) { + // We're probably going to crash anyways if this is ever NULL, so we might as + // well make our stack reports more informative by crashing here. + CHECK(handle_); +} + +WaitableEvent::WaitableEvent(HANDLE handle) + : handle_(handle) { + CHECK(handle) << "Tried to create WaitableEvent from NULL handle"; +} + +WaitableEvent::~WaitableEvent() { + CloseHandle(handle_); +} + +HANDLE WaitableEvent::Release() { + HANDLE rv = handle_; + handle_ = INVALID_HANDLE_VALUE; + return rv; +} + +void WaitableEvent::Reset() { + ResetEvent(handle_); +} + +void WaitableEvent::Signal() { + SetEvent(handle_); +} + +bool WaitableEvent::IsSignaled() { + return TimedWait(TimeDelta::FromMilliseconds(0)); +} + +void WaitableEvent::Wait() { + base::ThreadRestrictions::AssertWaitAllowed(); + DWORD result = WaitForSingleObject(handle_, INFINITE); + // It is most unexpected that this should ever fail. Help consumers learn + // about it if it should ever fail. + DCHECK_EQ(WAIT_OBJECT_0, result) << "WaitForSingleObject failed"; +} + +bool WaitableEvent::TimedWait(const TimeDelta& max_time) { + base::ThreadRestrictions::AssertWaitAllowed(); + DCHECK(max_time >= TimeDelta::FromMicroseconds(0)); + // Be careful here. TimeDelta has a precision of microseconds, but this API + // is in milliseconds. If there are 5.5ms left, should the delay be 5 or 6? + // It should be 6 to avoid returning too early. + double timeout = ceil(max_time.InMillisecondsF()); + DWORD result = WaitForSingleObject(handle_, static_cast(timeout)); + switch (result) { + case WAIT_OBJECT_0: + return true; + case WAIT_TIMEOUT: + return false; + } + // It is most unexpected that this should ever fail. Help consumers learn + // about it if it should ever fail. + NOTREACHED() << "WaitForSingleObject failed"; + return false; +} + +// static +size_t WaitableEvent::WaitMany(WaitableEvent** events, size_t count) { + base::ThreadRestrictions::AssertWaitAllowed(); + HANDLE handles[MAXIMUM_WAIT_OBJECTS]; + CHECK_LE(count, MAXIMUM_WAIT_OBJECTS) + << "Can only wait on " << MAXIMUM_WAIT_OBJECTS << " with WaitMany"; + + for (size_t i = 0; i < count; ++i) + handles[i] = events[i]->handle(); + + // The cast is safe because count is small - see the CHECK above. + DWORD result = + WaitForMultipleObjects(static_cast(count), + handles, + FALSE, // don't wait for all the objects + INFINITE); // no timeout + if (result >= WAIT_OBJECT_0 + count) { + DLOG_GETLASTERROR(FATAL) << "WaitForMultipleObjects failed"; + return 0; + } + + return result - WAIT_OBJECT_0; +} + +} // namespace base diff --git a/base/sys_byteorder.h b/base/sys_byteorder.h new file mode 100644 index 0000000000..97e33acfda --- /dev/null +++ b/base/sys_byteorder.h @@ -0,0 +1,147 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This header defines cross-platform ByteSwap() implementations for 16, 32 and +// 64-bit values, and NetToHostXX() / HostToNextXX() functions equivalent to +// the traditional ntohX() and htonX() functions. +// Use the functions defined here rather than using the platform-specific +// functions directly. + +#ifndef BASE_SYS_BYTEORDER_H_ +#define BASE_SYS_BYTEORDER_H_ + +#include "base/basictypes.h" +#include "build/build_config.h" + +#if defined(OS_WIN) +#include +#else +#include +#endif + +// Include headers to provide byteswap for all platforms. +#if defined(COMPILER_MSVC) +#include +#elif defined(OS_MACOSX) +#include +#elif defined(OS_OPENBSD) +#include +#else +#include +#endif + + +namespace base { + +// Returns a value with all bytes in |x| swapped, i.e. reverses the endianness. +inline uint16 ByteSwap(uint16 x) { +#if defined(COMPILER_MSVC) + return _byteswap_ushort(x); +#elif defined(OS_MACOSX) + return OSSwapInt16(x); +#elif defined(OS_OPENBSD) + return swap16(x); +#else + return bswap_16(x); +#endif +} +inline uint32 ByteSwap(uint32 x) { +#if defined(COMPILER_MSVC) + return _byteswap_ulong(x); +#elif defined(OS_MACOSX) + return OSSwapInt32(x); +#elif defined(OS_OPENBSD) + return swap32(x); +#else + return bswap_32(x); +#endif +} +inline uint64 ByteSwap(uint64 x) { +#if defined(COMPILER_MSVC) + return _byteswap_uint64(x); +#elif defined(OS_MACOSX) + return OSSwapInt64(x); +#elif defined(OS_OPENBSD) + return swap64(x); +#else + return bswap_64(x); +#endif +} + +// Converts the bytes in |x| from host order (endianness) to little endian, and +// returns the result. +inline uint16 ByteSwapToLE16(uint16 x) { +#if defined(ARCH_CPU_LITTLE_ENDIAN) + return x; +#else + return ByteSwap(x); +#endif +} +inline uint32 ByteSwapToLE32(uint32 x) { +#if defined(ARCH_CPU_LITTLE_ENDIAN) + return x; +#else + return ByteSwap(x); +#endif +} +inline uint64 ByteSwapToLE64(uint64 x) { +#if defined(ARCH_CPU_LITTLE_ENDIAN) + return x; +#else + return ByteSwap(x); +#endif +} + +// Converts the bytes in |x| from network to host order (endianness), and +// returns the result. +inline uint16 NetToHost16(uint16 x) { +#if defined(ARCH_CPU_LITTLE_ENDIAN) + return ByteSwap(x); +#else + return x; +#endif +} +inline uint32 NetToHost32(uint32 x) { +#if defined(ARCH_CPU_LITTLE_ENDIAN) + return ByteSwap(x); +#else + return x; +#endif +} +inline uint64 NetToHost64(uint64 x) { +#if defined(ARCH_CPU_LITTLE_ENDIAN) + return ByteSwap(x); +#else + return x; +#endif +} + +// Converts the bytes in |x| from host to network order (endianness), and +// returns the result. +inline uint16 HostToNet16(uint16 x) { +#if defined(ARCH_CPU_LITTLE_ENDIAN) + return ByteSwap(x); +#else + return x; +#endif +} +inline uint32 HostToNet32(uint32 x) { +#if defined(ARCH_CPU_LITTLE_ENDIAN) + return ByteSwap(x); +#else + return x; +#endif +} +inline uint64 HostToNet64(uint64 x) { +#if defined(ARCH_CPU_LITTLE_ENDIAN) + return ByteSwap(x); +#else + return x; +#endif +} + +} // namespace base + + +#endif // BASE_SYS_BYTEORDER_H_ diff --git a/base/sys_info.cc b/base/sys_info.cc new file mode 100644 index 0000000000..d43e9320bc --- /dev/null +++ b/base/sys_info.cc @@ -0,0 +1,20 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/sys_info.h" + +#include "base/time/time.h" + +namespace base { + +// static +int64 SysInfo::Uptime() { + // This code relies on an implementation detail of TimeTicks::Now() - that + // its return value happens to coincide with the system uptime value in + // microseconds, on Win/Mac/iOS/Linux/ChromeOS and Android. + int64 uptime_in_microseconds = TimeTicks::Now().ToInternalValue(); + return uptime_in_microseconds / 1000; +} + +} // namespace base diff --git a/base/sys_info.h b/base/sys_info.h new file mode 100644 index 0000000000..38462ed111 --- /dev/null +++ b/base/sys_info.h @@ -0,0 +1,113 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_SYS_INFO_H_ +#define BASE_SYS_INFO_H_ + +#include + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "build/build_config.h" + +namespace base { + +class BASE_EXPORT SysInfo { + public: + // Return the number of logical processors/cores on the current machine. + static int NumberOfProcessors(); + + // Return the number of bytes of physical memory on the current machine. + static int64 AmountOfPhysicalMemory(); + + // Return the number of bytes of current available physical memory on the + // machine. + static int64 AmountOfAvailablePhysicalMemory(); + + // Return the number of megabytes of physical memory on the current machine. + static int AmountOfPhysicalMemoryMB() { + return static_cast(AmountOfPhysicalMemory() / 1024 / 1024); + } + + // Return the available disk space in bytes on the volume containing |path|, + // or -1 on failure. + static int64 AmountOfFreeDiskSpace(const FilePath& path); + + // Returns system uptime in milliseconds. + static int64 Uptime(); + + // Returns the name of the host operating system. + static std::string OperatingSystemName(); + + // Returns the version of the host operating system. + static std::string OperatingSystemVersion(); + + // Retrieves detailed numeric values for the OS version. + // TODO(port): Implement a Linux version of this method and enable the + // corresponding unit test. + // DON'T USE THIS ON THE MAC OR WINDOWS to determine the current OS release + // for OS version-specific feature checks and workarounds. If you must use + // an OS version check instead of a feature check, use the base::mac::IsOS* + // family from base/mac/mac_util.h, or base::win::GetVersion from + // base/win/windows_version.h. + static void OperatingSystemVersionNumbers(int32* major_version, + int32* minor_version, + int32* bugfix_version); + + // Returns the architecture of the running operating system. + // Exact return value may differ across platforms. + // e.g. a 32-bit x86 kernel on a 64-bit capable CPU will return "x86", + // whereas a x86-64 kernel on the same CPU will return "x86_64" + static std::string OperatingSystemArchitecture(); + + // Avoid using this. Use base/cpu.h to get information about the CPU instead. + // http://crbug.com/148884 + // Returns the CPU model name of the system. If it can not be figured out, + // an empty string is returned. + static std::string CPUModelName(); + + // Return the smallest amount of memory (in bytes) which the VM system will + // allocate. + static size_t VMAllocationGranularity(); + +#if defined(OS_POSIX) && !defined(OS_MACOSX) + // Returns the maximum SysV shared memory segment size. + static size_t MaxSharedMemorySize(); +#endif // defined(OS_POSIX) && !defined(OS_MACOSX) + +#if defined(OS_CHROMEOS) + // Returns the name of the version entry we wish to look up in the + // Linux Standard Base release information file. + static std::string GetLinuxStandardBaseVersionKey(); + + // Parses /etc/lsb-release to get version information for Google Chrome OS. + // Declared here so it can be exposed for unit testing. + static void ParseLsbRelease(const std::string& lsb_release, + int32* major_version, + int32* minor_version, + int32* bugfix_version); + + // Returns the path to the lsb-release file. + static FilePath GetLsbReleaseFilePath(); +#endif // defined(OS_CHROMEOS) + +#if defined(OS_ANDROID) + // Returns the Android build's codename. + static std::string GetAndroidBuildCodename(); + + // Returns the Android build ID. + static std::string GetAndroidBuildID(); + + // Returns the device's name. + static std::string GetDeviceName(); + + static int DalvikHeapSizeMB(); + static int DalvikHeapGrowthLimitMB(); +#endif // defined(OS_ANDROID) +}; + +} // namespace base + +#endif // BASE_SYS_INFO_H_ diff --git a/base/sys_info_android.cc b/base/sys_info_android.cc new file mode 100644 index 0000000000..577ca0e6bd --- /dev/null +++ b/base/sys_info_android.cc @@ -0,0 +1,167 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/sys_info.h" + +#include + +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_piece.h" +#include "base/strings/stringprintf.h" + +namespace { + +// Default version of Android to fall back to when actual version numbers +// cannot be acquired. +// TODO(dfalcantara): Keep this reasonably up to date with the latest publicly +// available version of Android. +const int kDefaultAndroidMajorVersion = 4; +const int kDefaultAndroidMinorVersion = 3; +const int kDefaultAndroidBugfixVersion = 0; + +// Parse out the OS version numbers from the system properties. +void ParseOSVersionNumbers(const char* os_version_str, + int32 *major_version, + int32 *minor_version, + int32 *bugfix_version) { + if (os_version_str[0]) { + // Try to parse out the version numbers from the string. + int num_read = sscanf(os_version_str, "%d.%d.%d", major_version, + minor_version, bugfix_version); + + if (num_read > 0) { + // If we don't have a full set of version numbers, make the extras 0. + if (num_read < 2) *minor_version = 0; + if (num_read < 3) *bugfix_version = 0; + return; + } + } + + // For some reason, we couldn't parse the version number string. + *major_version = kDefaultAndroidMajorVersion; + *minor_version = kDefaultAndroidMinorVersion; + *bugfix_version = kDefaultAndroidBugfixVersion; +} + +// Parses a system property (specified with unit 'k','m' or 'g'). +// Returns a value in bytes. +// Returns -1 if the string could not be parsed. +int64 ParseSystemPropertyBytes(const base::StringPiece& str) { + const int64 KB = 1024; + const int64 MB = 1024 * KB; + const int64 GB = 1024 * MB; + if (str.size() == 0u) + return -1; + int64 unit_multiplier = 1; + size_t length = str.size(); + if (str[length - 1] == 'k') { + unit_multiplier = KB; + length--; + } else if (str[length - 1] == 'm') { + unit_multiplier = MB; + length--; + } else if (str[length - 1] == 'g') { + unit_multiplier = GB; + length--; + } + int64 result = 0; + bool parsed = base::StringToInt64(str.substr(0, length), &result); + bool negative = result <= 0; + bool overflow = result >= std::numeric_limits::max() / unit_multiplier; + if (!parsed || negative || overflow) + return -1; + return result * unit_multiplier; +} + +int GetDalvikHeapSizeMB() { + char heap_size_str[PROP_VALUE_MAX]; + __system_property_get("dalvik.vm.heapsize", heap_size_str); + // dalvik.vm.heapsize property is writable by a root user. + // Clamp it to reasonable range as a sanity check, + // a typical android device will never have less than 48MB. + const int64 MB = 1024 * 1024; + int64 result = ParseSystemPropertyBytes(heap_size_str); + if (result == -1) { + // We should consider not exposing these values if they are not reliable. + LOG(ERROR) << "Can't parse dalvik.vm.heapsize: " << heap_size_str; + result = base::SysInfo::AmountOfPhysicalMemoryMB() / 3; + } + result = std::min(std::max(32 * MB, result), 1024 * MB) / MB; + return static_cast(result); +} + +int GetDalvikHeapGrowthLimitMB() { + char heap_size_str[PROP_VALUE_MAX]; + __system_property_get("dalvik.vm.heapgrowthlimit", heap_size_str); + // dalvik.vm.heapgrowthlimit property is writable by a root user. + // Clamp it to reasonable range as a sanity check, + // a typical android device will never have less than 24MB. + const int64 MB = 1024 * 1024; + int64 result = ParseSystemPropertyBytes(heap_size_str); + if (result == -1) { + // We should consider not exposing these values if they are not reliable. + LOG(ERROR) << "Can't parse dalvik.vm.heapgrowthlimit: " << heap_size_str; + result = base::SysInfo::AmountOfPhysicalMemoryMB() / 6; + } + result = std::min(std::max(16 * MB, result), 512 * MB) / MB; + return static_cast(result); +} + +} // anonymous namespace + +namespace base { + +std::string SysInfo::OperatingSystemName() { + return "Android"; +} + +std::string SysInfo::GetAndroidBuildCodename() { + char os_version_codename_str[PROP_VALUE_MAX]; + __system_property_get("ro.build.version.codename", os_version_codename_str); + return std::string(os_version_codename_str); +} + +std::string SysInfo::GetAndroidBuildID() { + char os_build_id_str[PROP_VALUE_MAX]; + __system_property_get("ro.build.id", os_build_id_str); + return std::string(os_build_id_str); +} + +std::string SysInfo::GetDeviceName() { + char device_model_str[PROP_VALUE_MAX]; + __system_property_get("ro.product.model", device_model_str); + return std::string(device_model_str); +} + +std::string SysInfo::OperatingSystemVersion() { + int32 major, minor, bugfix; + OperatingSystemVersionNumbers(&major, &minor, &bugfix); + return StringPrintf("%d.%d.%d", major, minor, bugfix); +} + +void SysInfo::OperatingSystemVersionNumbers(int32* major_version, + int32* minor_version, + int32* bugfix_version) { + // Read the version number string out from the properties. + char os_version_str[PROP_VALUE_MAX]; + __system_property_get("ro.build.version.release", os_version_str); + + // Parse out the numbers. + ParseOSVersionNumbers(os_version_str, major_version, minor_version, + bugfix_version); +} + +int SysInfo::DalvikHeapSizeMB() { + static int heap_size = GetDalvikHeapSizeMB(); + return heap_size; +} + +int SysInfo::DalvikHeapGrowthLimitMB() { + static int heap_growth_limit = GetDalvikHeapGrowthLimitMB(); + return heap_growth_limit; +} + + +} // namespace base diff --git a/base/sys_info_chromeos.cc b/base/sys_info_chromeos.cc new file mode 100644 index 0000000000..1335760d03 --- /dev/null +++ b/base/sys_info_chromeos.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. + +#include "base/sys_info.h" + +#include "base/basictypes.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/lazy_instance.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_piece.h" +#include "base/strings/string_tokenizer.h" +#include "base/threading/thread_restrictions.h" + +namespace base { + +static const char* kLinuxStandardBaseVersionKeys[] = { + "CHROMEOS_RELEASE_VERSION", + "GOOGLE_RELEASE", + "DISTRIB_RELEASE", + NULL +}; + +const char kLinuxStandardBaseReleaseFile[] = "/etc/lsb-release"; + +struct ChromeOSVersionNumbers { + ChromeOSVersionNumbers() + : major_version(0), + minor_version(0), + bugfix_version(0), + parsed(false) { + } + + int32 major_version; + int32 minor_version; + int32 bugfix_version; + bool parsed; +}; + +static LazyInstance + g_chrome_os_version_numbers = LAZY_INSTANCE_INITIALIZER; + +// static +void SysInfo::OperatingSystemVersionNumbers(int32* major_version, + int32* minor_version, + int32* bugfix_version) { + if (!g_chrome_os_version_numbers.Get().parsed) { + // The other implementations of SysInfo don't block on the disk. + // See http://code.google.com/p/chromium/issues/detail?id=60394 + // Perhaps the caller ought to cache this? + // Temporary allowing while we work the bug out. + ThreadRestrictions::ScopedAllowIO allow_io; + + FilePath path(kLinuxStandardBaseReleaseFile); + std::string contents; + if (file_util::ReadFileToString(path, &contents)) { + g_chrome_os_version_numbers.Get().parsed = true; + ParseLsbRelease(contents, + &(g_chrome_os_version_numbers.Get().major_version), + &(g_chrome_os_version_numbers.Get().minor_version), + &(g_chrome_os_version_numbers.Get().bugfix_version)); + } + } + *major_version = g_chrome_os_version_numbers.Get().major_version; + *minor_version = g_chrome_os_version_numbers.Get().minor_version; + *bugfix_version = g_chrome_os_version_numbers.Get().bugfix_version; +} + +// static +std::string SysInfo::GetLinuxStandardBaseVersionKey() { + return std::string(kLinuxStandardBaseVersionKeys[0]); +} + +// static +void SysInfo::ParseLsbRelease(const std::string& lsb_release, + int32* major_version, + int32* minor_version, + int32* bugfix_version) { + size_t version_key_index = std::string::npos; + for (int i = 0; kLinuxStandardBaseVersionKeys[i] != NULL; ++i) { + version_key_index = lsb_release.find(kLinuxStandardBaseVersionKeys[i]); + if (std::string::npos != version_key_index) { + break; + } + } + if (std::string::npos == version_key_index) { + return; + } + + size_t start_index = lsb_release.find_first_of('=', version_key_index); + start_index++; // Move past '='. + size_t length = lsb_release.find_first_of('\n', start_index) - start_index; + std::string version = lsb_release.substr(start_index, length); + StringTokenizer tokenizer(version, "."); + for (int i = 0; i < 3 && tokenizer.GetNext(); ++i) { + if (0 == i) { + StringToInt(StringPiece(tokenizer.token_begin(), + tokenizer.token_end()), + major_version); + *minor_version = *bugfix_version = 0; + } else if (1 == i) { + StringToInt(StringPiece(tokenizer.token_begin(), + tokenizer.token_end()), + minor_version); + } else { // 2 == i + StringToInt(StringPiece(tokenizer.token_begin(), + tokenizer.token_end()), + bugfix_version); + } + } +} + +// static +FilePath SysInfo::GetLsbReleaseFilePath() { + return FilePath(kLinuxStandardBaseReleaseFile); +} + +} // namespace base diff --git a/base/sys_info_freebsd.cc b/base/sys_info_freebsd.cc new file mode 100644 index 0000000000..832b35985b --- /dev/null +++ b/base/sys_info_freebsd.cc @@ -0,0 +1,36 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/sys_info.h" + +#include + +#include "base/logging.h" + +namespace base { + +int64 SysInfo::AmountOfPhysicalMemory() { + int pages, page_size; + size_t size = sizeof(pages); + sysctlbyname("vm.stats.vm.v_page_count", &pages, &size, NULL, 0); + sysctlbyname("vm.stats.vm.v_page_size", &page_size, &size, NULL, 0); + if (pages == -1 || page_size == -1) { + NOTREACHED(); + return 0; + } + return static_cast(pages) * page_size; +} + +// static +size_t SysInfo::MaxSharedMemorySize() { + size_t limit; + size_t size = sizeof(limit); + if (sysctlbyname("kern.ipc.shmmax", &limit, &size, NULL, 0) < 0) { + NOTREACHED(); + return 0; + } + return limit; +} + +} // namespace base diff --git a/base/sys_info_ios.mm b/base/sys_info_ios.mm new file mode 100644 index 0000000000..de68fcf37c --- /dev/null +++ b/base/sys_info_ios.mm @@ -0,0 +1,90 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/sys_info.h" + +#import +#include +#include +#include + +#include "base/logging.h" +#include "base/mac/scoped_mach_port.h" +#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/strings/sys_string_conversions.h" + +namespace base { + +// static +std::string SysInfo::OperatingSystemName() { + static dispatch_once_t get_system_name_once; + static std::string* system_name; + dispatch_once(&get_system_name_once, ^{ + base::mac::ScopedNSAutoreleasePool pool; + system_name = new std::string( + SysNSStringToUTF8([[UIDevice currentDevice] systemName])); + }); + // Examples of returned value: 'iPhone OS' on iPad 5.1.1 + // and iPhone 5.1.1. + return *system_name; +} + +// static +std::string SysInfo::OperatingSystemVersion() { + static dispatch_once_t get_system_version_once; + static std::string* system_version; + dispatch_once(&get_system_version_once, ^{ + base::mac::ScopedNSAutoreleasePool pool; + system_version = new std::string( + SysNSStringToUTF8([[UIDevice currentDevice] systemVersion])); + }); + return *system_version; +} + +// static +void SysInfo::OperatingSystemVersionNumbers(int32* major_version, + int32* minor_version, + int32* bugfix_version) { + base::mac::ScopedNSAutoreleasePool pool; + std::string system_version = OperatingSystemVersion(); + if (!system_version.empty()) { + // Try to parse out the version numbers from the string. + int num_read = sscanf(system_version.c_str(), "%d.%d.%d", major_version, + minor_version, bugfix_version); + if (num_read < 1) + *major_version = 0; + if (num_read < 2) + *minor_version = 0; + if (num_read < 3) + *bugfix_version = 0; + } +} + +// static +int64 SysInfo::AmountOfPhysicalMemory() { + struct host_basic_info hostinfo; + mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT; + base::mac::ScopedMachPort host(mach_host_self()); + int result = host_info(host, + HOST_BASIC_INFO, + reinterpret_cast(&hostinfo), + &count); + if (result != KERN_SUCCESS) { + NOTREACHED(); + return 0; + } + DCHECK_EQ(HOST_BASIC_INFO_COUNT, count); + return static_cast(hostinfo.max_mem); +} + +// static +std::string SysInfo::CPUModelName() { + char name[256]; + size_t len = arraysize(name); + if (sysctlbyname("machdep.cpu.brand_string", &name, &len, NULL, 0) == 0) + return name; + return std::string(); +} + +} // namespace base diff --git a/base/sys_info_linux.cc b/base/sys_info_linux.cc new file mode 100644 index 0000000000..58a0aefee6 --- /dev/null +++ b/base/sys_info_linux.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/sys_info.h" + +#include + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" + +namespace { + +int64 AmountOfMemory(int pages_name) { + long pages = sysconf(pages_name); + long page_size = sysconf(_SC_PAGESIZE); + if (pages == -1 || page_size == -1) { + NOTREACHED(); + return 0; + } + return static_cast(pages) * page_size; +} + +} // namespace + +namespace base { + +// static +int64 SysInfo::AmountOfPhysicalMemory() { + return AmountOfMemory(_SC_PHYS_PAGES); +} + +// static +int64 SysInfo::AmountOfAvailablePhysicalMemory() { + return AmountOfMemory(_SC_AVPHYS_PAGES); +} + +// static +size_t SysInfo::MaxSharedMemorySize() { + static int64 limit; + static bool limit_valid = false; + if (!limit_valid) { + std::string contents; + file_util::ReadFileToString(FilePath("/proc/sys/kernel/shmmax"), &contents); + DCHECK(!contents.empty()); + if (!contents.empty() && contents[contents.length() - 1] == '\n') { + contents.erase(contents.length() - 1); + } + if (base::StringToInt64(contents, &limit)) { + DCHECK(limit >= 0); + DCHECK(static_cast(limit) <= std::numeric_limits::max()); + limit_valid = true; + } else { + NOTREACHED(); + return 0; + } + } + return static_cast(limit); +} + +// static +std::string SysInfo::CPUModelName() { +#if defined(OS_CHROMEOS) && defined(ARCH_CPU_ARMEL) + const char kCpuModelPrefix[] = "Hardware"; +#else + const char kCpuModelPrefix[] = "model name"; +#endif + std::string contents; + file_util::ReadFileToString(FilePath("/proc/cpuinfo"), &contents); + DCHECK(!contents.empty()); + if (!contents.empty()) { + std::istringstream iss(contents); + std::string line; + while (std::getline(iss, line)) { + if (line.compare(0, strlen(kCpuModelPrefix), kCpuModelPrefix) == 0) { + size_t pos = line.find(": "); + return line.substr(pos + 2); + } + } + } + return std::string(); +} + +} // namespace base diff --git a/base/sys_info_mac.cc b/base/sys_info_mac.cc new file mode 100644 index 0000000000..2911c7c4ce --- /dev/null +++ b/base/sys_info_mac.cc @@ -0,0 +1,88 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/sys_info.h" + +#include +#include +#include +#include +#include +#include + +#include "base/logging.h" +#include "base/mac/scoped_mach_port.h" +#include "base/strings/stringprintf.h" + +namespace base { + +// static +std::string SysInfo::OperatingSystemName() { + return "Mac OS X"; +} + +// static +std::string SysInfo::OperatingSystemVersion() { + int32 major, minor, bugfix; + OperatingSystemVersionNumbers(&major, &minor, &bugfix); + return base::StringPrintf("%d.%d.%d", major, minor, bugfix); +} + +// static +void SysInfo::OperatingSystemVersionNumbers(int32* major_version, + int32* minor_version, + int32* bugfix_version) { + Gestalt(gestaltSystemVersionMajor, + reinterpret_cast(major_version)); + Gestalt(gestaltSystemVersionMinor, + reinterpret_cast(minor_version)); + Gestalt(gestaltSystemVersionBugFix, + reinterpret_cast(bugfix_version)); +} + +// static +int64 SysInfo::AmountOfPhysicalMemory() { + struct host_basic_info hostinfo; + mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT; + base::mac::ScopedMachPort host(mach_host_self()); + int result = host_info(host, + HOST_BASIC_INFO, + reinterpret_cast(&hostinfo), + &count); + if (result != KERN_SUCCESS) { + NOTREACHED(); + return 0; + } + DCHECK_EQ(HOST_BASIC_INFO_COUNT, count); + return static_cast(hostinfo.max_mem); +} + +// static +int64 SysInfo::AmountOfAvailablePhysicalMemory() { + base::mac::ScopedMachPort host(mach_host_self()); + vm_statistics_data_t vm_info; + mach_msg_type_number_t count = HOST_VM_INFO_COUNT; + + if (host_statistics(host.get(), + HOST_VM_INFO, + reinterpret_cast(&vm_info), + &count) != KERN_SUCCESS) { + NOTREACHED(); + return 0; + } + + return static_cast( + vm_info.free_count - vm_info.speculative_count) * PAGE_SIZE; +} + +// static +std::string SysInfo::CPUModelName() { + char name[256]; + size_t len = arraysize(name); + if (sysctlbyname("machdep.cpu.brand_string", &name, &len, NULL, 0) == 0) + return name; + return std::string(); +} + +} // namespace base diff --git a/base/sys_info_openbsd.cc b/base/sys_info_openbsd.cc new file mode 100644 index 0000000000..edbb2c9d86 --- /dev/null +++ b/base/sys_info_openbsd.cc @@ -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. + +#include "base/sys_info.h" + +#include +#include +#include + +#include "base/logging.h" + +namespace { + +int64 AmountOfMemory(int pages_name) { + long pages = sysconf(pages_name); + long page_size = sysconf(_SC_PAGESIZE); + if (pages == -1 || page_size == -1) { + NOTREACHED(); + return 0; + } + return static_cast(pages) * page_size; +} + +} // namespace + +namespace base { + +// static +int SysInfo::NumberOfProcessors() { + int mib[] = { CTL_HW, HW_NCPU }; + int ncpu; + size_t size = sizeof(ncpu); + if (sysctl(mib, arraysize(mib), &ncpu, &size, NULL, 0) < 0) { + NOTREACHED(); + return 1; + } + return ncpu; +} + +// static +int64 SysInfo::AmountOfPhysicalMemory() { + return AmountOfMemory(_SC_PHYS_PAGES); +} + +// static +int64 SysInfo::AmountOfAvailablePhysicalMemory() { + return AmountOfMemory(_SC_AVPHYS_PAGES); +} + +// static +size_t SysInfo::MaxSharedMemorySize() { + int mib[] = { CTL_KERN, KERN_SHMINFO, KERN_SHMINFO_SHMMAX }; + size_t limit; + size_t size = sizeof(limit); + if (sysctl(mib, arraysize(mib), &limit, &size, NULL, 0) < 0) { + NOTREACHED(); + return 0; + } + return limit; +} + +// static +std::string SysInfo::CPUModelName() { + int mib[] = { CTL_HW, HW_MODEL }; + char name[256]; + size_t len = arraysize(name); + if (sysctl(mib, arraysize(mib), name, &len, NULL, 0) < 0) { + NOTREACHED(); + return std::string(); + } + return name; +} + +} // namespace base diff --git a/base/sys_info_posix.cc b/base/sys_info_posix.cc new file mode 100644 index 0000000000..bbb1662559 --- /dev/null +++ b/base/sys_info_posix.cc @@ -0,0 +1,97 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/sys_info.h" + +#include +#include +#include +#include +#include + +#include "base/basictypes.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/thread_restrictions.h" + +#if defined(OS_ANDROID) +#include +#define statvfs statfs // Android uses a statvfs-like statfs struct and call. +#else +#include +#endif + +namespace base { + +#if !defined(OS_OPENBSD) +int SysInfo::NumberOfProcessors() { + // It seems that sysconf returns the number of "logical" processors on both + // Mac and Linux. So we get the number of "online logical" processors. + long res = sysconf(_SC_NPROCESSORS_ONLN); + if (res == -1) { + NOTREACHED(); + return 1; + } + + return static_cast(res); +} +#endif + +// static +int64 SysInfo::AmountOfFreeDiskSpace(const FilePath& path) { + base::ThreadRestrictions::AssertIOAllowed(); + + struct statvfs stats; + if (HANDLE_EINTR(statvfs(path.value().c_str(), &stats)) != 0) + return -1; + return static_cast(stats.f_bavail) * stats.f_frsize; +} + +#if !defined(OS_MACOSX) && !defined(OS_ANDROID) +// static +std::string SysInfo::OperatingSystemName() { + struct utsname info; + if (uname(&info) < 0) { + NOTREACHED(); + return std::string(); + } + return std::string(info.sysname); +} +#endif + +#if !defined(OS_MACOSX) && !defined(OS_ANDROID) +// static +std::string SysInfo::OperatingSystemVersion() { + struct utsname info; + if (uname(&info) < 0) { + NOTREACHED(); + return std::string(); + } + return std::string(info.release); +} +#endif + +// static +std::string SysInfo::OperatingSystemArchitecture() { + struct utsname info; + if (uname(&info) < 0) { + NOTREACHED(); + return std::string(); + } + std::string arch(info.machine); + if (arch == "i386" || arch == "i486" || arch == "i586" || arch == "i686") { + arch = "x86"; + } else if (arch == "amd64") { + arch = "x86_64"; + } + return arch; +} + +// static +size_t SysInfo::VMAllocationGranularity() { + return getpagesize(); +} + +} // namespace base diff --git a/base/sys_info_unittest.cc b/base/sys_info_unittest.cc new file mode 100644 index 0000000000..8153f2b357 --- /dev/null +++ b/base/sys_info_unittest.cc @@ -0,0 +1,111 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/file_util.h" +#include "base/sys_info.h" +#include "base/threading/platform_thread.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" + +typedef PlatformTest SysInfoTest; +using base::FilePath; + +#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID) +TEST_F(SysInfoTest, MaxSharedMemorySize) { + // We aren't actually testing that it's correct, just that it's sane. + EXPECT_GT(base::SysInfo::MaxSharedMemorySize(), 0u); +} +#endif + +TEST_F(SysInfoTest, NumProcs) { + // We aren't actually testing that it's correct, just that it's sane. + EXPECT_GE(base::SysInfo::NumberOfProcessors(), 1); +} + +TEST_F(SysInfoTest, AmountOfMem) { + // We aren't actually testing that it's correct, just that it's sane. + EXPECT_GT(base::SysInfo::AmountOfPhysicalMemory(), 0); + EXPECT_GT(base::SysInfo::AmountOfPhysicalMemoryMB(), 0); +} + +TEST_F(SysInfoTest, AmountOfFreeDiskSpace) { + // We aren't actually testing that it's correct, just that it's sane. + FilePath tmp_path; + ASSERT_TRUE(file_util::GetTempDir(&tmp_path)); + EXPECT_GT(base::SysInfo::AmountOfFreeDiskSpace(tmp_path), 0) + << tmp_path.value(); +} + +#if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_CHROMEOS) +TEST_F(SysInfoTest, OperatingSystemVersionNumbers) { + int32 os_major_version = -1; + int32 os_minor_version = -1; + int32 os_bugfix_version = -1; + base::SysInfo::OperatingSystemVersionNumbers(&os_major_version, + &os_minor_version, + &os_bugfix_version); + EXPECT_GT(os_major_version, -1); + EXPECT_GT(os_minor_version, -1); + EXPECT_GT(os_bugfix_version, -1); +} +#endif + +TEST_F(SysInfoTest, Uptime) { + int64 up_time_1 = base::SysInfo::Uptime(); + // UpTime() is implemented internally using TimeTicks::Now(), which documents + // system resolution as being 1-15ms. Sleep a little longer than that. + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(20)); + int64 up_time_2 = base::SysInfo::Uptime(); + EXPECT_GT(up_time_1, 0); + EXPECT_GT(up_time_2, up_time_1); +} + +#if defined(OS_CHROMEOS) +TEST_F(SysInfoTest, GoogleChromeOSVersionNumbers) { + int32 os_major_version = -1; + int32 os_minor_version = -1; + int32 os_bugfix_version = -1; + std::string lsb_release("FOO=1234123.34.5\n"); + lsb_release.append(base::SysInfo::GetLinuxStandardBaseVersionKey()); + lsb_release.append("=1.2.3.4\n"); + base::SysInfo::ParseLsbRelease(lsb_release, + &os_major_version, + &os_minor_version, + &os_bugfix_version); + EXPECT_EQ(1, os_major_version); + EXPECT_EQ(2, os_minor_version); + EXPECT_EQ(3, os_bugfix_version); +} + +TEST_F(SysInfoTest, GoogleChromeOSVersionNumbersFirst) { + int32 os_major_version = -1; + int32 os_minor_version = -1; + int32 os_bugfix_version = -1; + std::string lsb_release(base::SysInfo::GetLinuxStandardBaseVersionKey()); + lsb_release.append("=1.2.3.4\n"); + lsb_release.append("FOO=1234123.34.5\n"); + base::SysInfo::ParseLsbRelease(lsb_release, + &os_major_version, + &os_minor_version, + &os_bugfix_version); + EXPECT_EQ(1, os_major_version); + EXPECT_EQ(2, os_minor_version); + EXPECT_EQ(3, os_bugfix_version); +} + +TEST_F(SysInfoTest, GoogleChromeOSNoVersionNumbers) { + int32 os_major_version = -1; + int32 os_minor_version = -1; + int32 os_bugfix_version = -1; + std::string lsb_release("FOO=1234123.34.5\n"); + base::SysInfo::ParseLsbRelease(lsb_release, + &os_major_version, + &os_minor_version, + &os_bugfix_version); + EXPECT_EQ(-1, os_major_version); + EXPECT_EQ(-1, os_minor_version); + EXPECT_EQ(-1, os_bugfix_version); +} + +#endif // OS_CHROMEOS diff --git a/base/sys_info_win.cc b/base/sys_info_win.cc new file mode 100644 index 0000000000..191fd94ff5 --- /dev/null +++ b/base/sys_info_win.cc @@ -0,0 +1,125 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/sys_info.h" + +#include + +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/stringprintf.h" +#include "base/threading/thread_restrictions.h" +#include "base/win/windows_version.h" + +namespace { + +int64 AmountOfMemory(DWORDLONG MEMORYSTATUSEX::* memory_field) { + MEMORYSTATUSEX memory_info; + memory_info.dwLength = sizeof(memory_info); + if (!GlobalMemoryStatusEx(&memory_info)) { + NOTREACHED(); + return 0; + } + + int64 rv = static_cast(memory_info.*memory_field); + if (rv < 0) + rv = kint64max; + return rv; +} + +} // namespace + +namespace base { + +// static +int SysInfo::NumberOfProcessors() { + return win::OSInfo::GetInstance()->processors(); +} + +// static +int64 SysInfo::AmountOfPhysicalMemory() { + return AmountOfMemory(&MEMORYSTATUSEX::ullTotalPhys); +} + +// static +int64 SysInfo::AmountOfAvailablePhysicalMemory() { + return AmountOfMemory(&MEMORYSTATUSEX::ullAvailPhys); +} + +// static +int64 SysInfo::AmountOfFreeDiskSpace(const FilePath& path) { + base::ThreadRestrictions::AssertIOAllowed(); + + ULARGE_INTEGER available, total, free; + if (!GetDiskFreeSpaceExW(path.value().c_str(), &available, &total, &free)) { + return -1; + } + int64 rv = static_cast(available.QuadPart); + if (rv < 0) + rv = kint64max; + return rv; +} + +// static +std::string SysInfo::OperatingSystemName() { + return "Windows NT"; +} + +// static +std::string SysInfo::OperatingSystemVersion() { + win::OSInfo* os_info = win::OSInfo::GetInstance(); + win::OSInfo::VersionNumber version_number = os_info->version_number(); + std::string version(StringPrintf("%d.%d", version_number.major, + version_number.minor)); + win::OSInfo::ServicePack service_pack = os_info->service_pack(); + if (service_pack.major != 0) { + version += StringPrintf(" SP%d", service_pack.major); + if (service_pack.minor != 0) + version += StringPrintf(".%d", service_pack.minor); + } + return version; +} + +// TODO: Implement OperatingSystemVersionComplete, which would include +// patchlevel/service pack number. +// See chrome/browser/feedback/feedback_util.h, FeedbackUtil::SetOSVersion. + +// static +std::string SysInfo::OperatingSystemArchitecture() { + win::OSInfo::WindowsArchitecture arch = + win::OSInfo::GetInstance()->architecture(); + switch (arch) { + case win::OSInfo::X86_ARCHITECTURE: + return "x86"; + case win::OSInfo::X64_ARCHITECTURE: + return "x86_64"; + case win::OSInfo::IA64_ARCHITECTURE: + return "ia64"; + default: + return ""; + } +} + +// static +std::string SysInfo::CPUModelName() { + return win::OSInfo::GetInstance()->processor_model_name(); +} + +// static +size_t SysInfo::VMAllocationGranularity() { + return win::OSInfo::GetInstance()->allocation_granularity(); +} + +// static +void SysInfo::OperatingSystemVersionNumbers(int32* major_version, + int32* minor_version, + int32* bugfix_version) { + win::OSInfo* os_info = win::OSInfo::GetInstance(); + *major_version = os_info->version_number().major; + *minor_version = os_info->version_number().minor; + *bugfix_version = 0; +} + +} // namespace base diff --git a/base/system_monitor/system_monitor.cc b/base/system_monitor/system_monitor.cc new file mode 100644 index 0000000000..11dd000a16 --- /dev/null +++ b/base/system_monitor/system_monitor.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/system_monitor/system_monitor.h" + +#include + +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/time/time.h" + +namespace base { + +static SystemMonitor* g_system_monitor = NULL; + +SystemMonitor::SystemMonitor() + : devices_changed_observer_list_( + new ObserverListThreadSafe()) { + DCHECK(!g_system_monitor); + g_system_monitor = this; +} + +SystemMonitor::~SystemMonitor() { + DCHECK_EQ(this, g_system_monitor); + g_system_monitor = NULL; +} + +// static +SystemMonitor* SystemMonitor::Get() { + return g_system_monitor; +} + +void SystemMonitor::ProcessDevicesChanged(DeviceType device_type) { + NotifyDevicesChanged(device_type); +} + +void SystemMonitor::AddDevicesChangedObserver(DevicesChangedObserver* obs) { + devices_changed_observer_list_->AddObserver(obs); +} + +void SystemMonitor::RemoveDevicesChangedObserver(DevicesChangedObserver* obs) { + devices_changed_observer_list_->RemoveObserver(obs); +} + +void SystemMonitor::NotifyDevicesChanged(DeviceType device_type) { + DVLOG(1) << "DevicesChanged with device type " << device_type; + devices_changed_observer_list_->Notify( + &DevicesChangedObserver::OnDevicesChanged, device_type); +} + +} // namespace base diff --git a/base/system_monitor/system_monitor.h b/base/system_monitor/system_monitor.h new file mode 100644 index 0000000000..5dd849f5e3 --- /dev/null +++ b/base/system_monitor/system_monitor.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_SYSTEM_MONITOR_SYSTEM_MONITOR_H_ +#define BASE_SYSTEM_MONITOR_SYSTEM_MONITOR_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/observer_list_threadsafe.h" +#include "build/build_config.h" + +namespace base { + +// Class for monitoring various system-related subsystems +// such as power management, network status, etc. +// TODO(mbelshe): Add support beyond just power management. +class BASE_EXPORT SystemMonitor { + public: + // Type of devices whose change need to be monitored, such as add/remove. + enum DeviceType { + DEVTYPE_AUDIO_CAPTURE, // Audio capture device, e.g., microphone. + DEVTYPE_VIDEO_CAPTURE, // Video capture device, e.g., webcam. + DEVTYPE_UNKNOWN, // Other devices. + }; + + // Create SystemMonitor. Only one SystemMonitor instance per application + // is allowed. + SystemMonitor(); + ~SystemMonitor(); + + // Get the application-wide SystemMonitor (if not present, returns NULL). + static SystemMonitor* Get(); + + class BASE_EXPORT DevicesChangedObserver { + public: + // Notification that the devices connected to the system have changed. + // This is only implemented on Windows currently. + virtual void OnDevicesChanged(DeviceType device_type) {} + + protected: + virtual ~DevicesChangedObserver() {} + }; + + // Add a new observer. + // Can be called from any thread. + // Must not be called from within a notification callback. + void AddDevicesChangedObserver(DevicesChangedObserver* obs); + + // Remove an existing observer. + // Can be called from any thread. + // Must not be called from within a notification callback. + void RemoveDevicesChangedObserver(DevicesChangedObserver* obs); + + // The ProcessFoo() style methods are a broken pattern and should not + // be copied. Any significant addition to this class is blocked on + // refactoring to improve the state of affairs. See http://crbug.com/149059 + + // Cross-platform handling of a device change event. + void ProcessDevicesChanged(DeviceType device_type); + + private: + // Functions to trigger notifications. + void NotifyDevicesChanged(DeviceType device_type); + + scoped_refptr > + devices_changed_observer_list_; + + DISALLOW_COPY_AND_ASSIGN(SystemMonitor); +}; + +} // namespace base + +#endif // BASE_SYSTEM_MONITOR_SYSTEM_MONITOR_H_ diff --git a/base/system_monitor/system_monitor_unittest.cc b/base/system_monitor/system_monitor_unittest.cc new file mode 100644 index 0000000000..e49405ec45 --- /dev/null +++ b/base/system_monitor/system_monitor_unittest.cc @@ -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/system_monitor/system_monitor.h" + +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/test/mock_devices_changed_observer.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace { + +class SystemMonitorTest : public testing::Test { + protected: + SystemMonitorTest() { + system_monitor_.reset(new SystemMonitor); + } + virtual ~SystemMonitorTest() {} + + MessageLoop message_loop_; + scoped_ptr system_monitor_; + + DISALLOW_COPY_AND_ASSIGN(SystemMonitorTest); +}; + +TEST_F(SystemMonitorTest, DeviceChangeNotifications) { + const int kObservers = 5; + + testing::Sequence mock_sequencer[kObservers]; + MockDevicesChangedObserver observers[kObservers]; + for (int index = 0; index < kObservers; ++index) { + system_monitor_->AddDevicesChangedObserver(&observers[index]); + + EXPECT_CALL(observers[index], + OnDevicesChanged(SystemMonitor::DEVTYPE_UNKNOWN)) + .Times(3) + .InSequence(mock_sequencer[index]); + } + + system_monitor_->ProcessDevicesChanged(SystemMonitor::DEVTYPE_UNKNOWN); + RunLoop().RunUntilIdle(); + + system_monitor_->ProcessDevicesChanged(SystemMonitor::DEVTYPE_UNKNOWN); + system_monitor_->ProcessDevicesChanged(SystemMonitor::DEVTYPE_UNKNOWN); + RunLoop().RunUntilIdle(); +} + +} // namespace + +} // namespace base diff --git a/base/task_runner.cc b/base/task_runner.cc new file mode 100644 index 0000000000..5860f2820f --- /dev/null +++ b/base/task_runner.cc @@ -0,0 +1,68 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/task_runner.h" + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/threading/post_task_and_reply_impl.h" + +namespace base { + +namespace { + +// TODO(akalin): There's only one other implementation of +// PostTaskAndReplyImpl in WorkerPool. Investigate whether it'll be +// possible to merge the two. +class PostTaskAndReplyTaskRunner : public internal::PostTaskAndReplyImpl { + public: + explicit PostTaskAndReplyTaskRunner(TaskRunner* destination); + + private: + virtual bool PostTask(const tracked_objects::Location& from_here, + const Closure& task) OVERRIDE; + + // Non-owning. + TaskRunner* destination_; +}; + +PostTaskAndReplyTaskRunner::PostTaskAndReplyTaskRunner( + TaskRunner* destination) : destination_(destination) { + DCHECK(destination_); +} + +bool PostTaskAndReplyTaskRunner::PostTask( + const tracked_objects::Location& from_here, + const Closure& task) { + return destination_->PostTask(from_here, task); +} + +} // namespace + +bool TaskRunner::PostTask(const tracked_objects::Location& from_here, + const Closure& task) { + return PostDelayedTask(from_here, task, base::TimeDelta()); +} + +bool TaskRunner::PostTaskAndReply( + const tracked_objects::Location& from_here, + const Closure& task, + const Closure& reply) { + return PostTaskAndReplyTaskRunner(this).PostTaskAndReply( + from_here, task, reply); +} + +TaskRunner::TaskRunner() {} + +TaskRunner::~TaskRunner() {} + +void TaskRunner::OnDestruct() const { + delete this; +} + +void TaskRunnerTraits::Destruct(const TaskRunner* task_runner) { + task_runner->OnDestruct(); +} + +} // namespace base diff --git a/base/task_runner.h b/base/task_runner.h new file mode 100644 index 0000000000..7d07b8ceb0 --- /dev/null +++ b/base/task_runner.h @@ -0,0 +1,153 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TASK_RUNNER_H_ +#define BASE_TASK_RUNNER_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/callback_forward.h" +#include "base/memory/ref_counted.h" +#include "base/time/time.h" + +namespace tracked_objects { +class Location; +} // namespace tracked_objects + +namespace base { + +struct TaskRunnerTraits; + +// A TaskRunner is an object that runs posted tasks (in the form of +// Closure objects). The TaskRunner interface provides a way of +// decoupling task posting from the mechanics of how each task will be +// run. TaskRunner provides very weak guarantees as to how posted +// tasks are run (or if they're run at all). In particular, it only +// guarantees: +// +// - Posting a task will not run it synchronously. That is, no +// Post*Task method will call task.Run() directly. +// +// - Increasing the delay can only delay when the task gets run. +// That is, increasing the delay may not affect when the task gets +// run, or it could make it run later than it normally would, but +// it won't make it run earlier than it normally would. +// +// TaskRunner does not guarantee the order in which posted tasks are +// run, whether tasks overlap, or whether they're run on a particular +// thread. Also it does not guarantee a memory model for shared data +// between tasks. (In other words, you should use your own +// synchronization/locking primitives if you need to share data +// between tasks.) +// +// Implementations of TaskRunner should be thread-safe in that all +// methods must be safe to call on any thread. Ownership semantics +// for TaskRunners are in general not clear, which is why the +// interface itself is RefCountedThreadSafe. +// +// Some theoretical implementations of TaskRunner: +// +// - A TaskRunner that uses a thread pool to run posted tasks. +// +// - A TaskRunner that, for each task, spawns a non-joinable thread +// to run that task and immediately quit. +// +// - A TaskRunner that stores the list of posted tasks and has a +// method Run() that runs each runnable task in random order. +class BASE_EXPORT TaskRunner + : public RefCountedThreadSafe { + public: + // Posts the given task to be run. Returns true if the task may be + // run at some point in the future, and false if the task definitely + // will not be run. + // + // Equivalent to PostDelayedTask(from_here, task, 0). + bool PostTask(const tracked_objects::Location& from_here, + const Closure& task); + + // Like PostTask, but tries to run the posted task only after + // |delay_ms| has passed. + // + // It is valid for an implementation to ignore |delay_ms|; that is, + // to have PostDelayedTask behave the same as PostTask. + virtual bool PostDelayedTask(const tracked_objects::Location& from_here, + const Closure& task, + base::TimeDelta delay) = 0; + + // Returns true if the current thread is a thread on which a task + // may be run, and false if no task will be run on the current + // thread. + // + // It is valid for an implementation to always return true, or in + // general to use 'true' as a default value. + virtual bool RunsTasksOnCurrentThread() const = 0; + + // Posts |task| on the current TaskRunner. On completion, |reply| + // is posted to the thread that called PostTaskAndReply(). Both + // |task| and |reply| are guaranteed to be deleted on the thread + // from which PostTaskAndReply() is invoked. This allows objects + // that must be deleted on the originating thread to be bound into + // the |task| and |reply| Closures. In particular, it can be useful + // to use WeakPtr<> in the |reply| Closure so that the reply + // operation can be canceled. See the following pseudo-code: + // + // class DataBuffer : public RefCountedThreadSafe { + // public: + // // Called to add data into a buffer. + // void AddData(void* buf, size_t length); + // ... + // }; + // + // + // class DataLoader : public SupportsWeakPtr { + // public: + // void GetData() { + // scoped_refptr buffer = new DataBuffer(); + // target_thread_.message_loop_proxy()->PostTaskAndReply( + // FROM_HERE, + // base::Bind(&DataBuffer::AddData, buffer), + // base::Bind(&DataLoader::OnDataReceived, AsWeakPtr(), buffer)); + // } + // + // private: + // void OnDataReceived(scoped_refptr buffer) { + // // Do something with buffer. + // } + // }; + // + // + // Things to notice: + // * Results of |task| are shared with |reply| by binding a shared argument + // (a DataBuffer instance). + // * The DataLoader object has no special thread safety. + // * The DataLoader object can be deleted while |task| is still running, + // and the reply will cancel itself safely because it is bound to a + // WeakPtr<>. + bool PostTaskAndReply(const tracked_objects::Location& from_here, + const Closure& task, + const Closure& reply); + + protected: + friend struct TaskRunnerTraits; + + // Only the Windows debug build seems to need this: see + // http://crbug.com/112250. + friend class RefCountedThreadSafe; + + TaskRunner(); + virtual ~TaskRunner(); + + // Called when this object should be destroyed. By default simply + // deletes |this|, but can be overridden to do something else, like + // delete on a certain thread. + virtual void OnDestruct() const; +}; + +struct BASE_EXPORT TaskRunnerTraits { + static void Destruct(const TaskRunner* task_runner); +}; + +} // namespace base + +#endif // BASE_TASK_RUNNER_H_ diff --git a/base/task_runner_util.h b/base/task_runner_util.h new file mode 100644 index 0000000000..b6dd0f36d6 --- /dev/null +++ b/base/task_runner_util.h @@ -0,0 +1,71 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TASK_RUNNER_UTIL_H_ +#define BASE_TASK_RUNNER_UTIL_H_ + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/callback_internal.h" +#include "base/logging.h" +#include "base/task_runner.h" + +namespace base { + +namespace internal { + +// Adapts a function that produces a result via a return value to +// one that returns via an output parameter. +template +void ReturnAsParamAdapter(const Callback& func, + ReturnType* result) { + *result = func.Run(); +} + +// Adapts a T* result to a callblack that expects a T. +template +void ReplyAdapter(const Callback& callback, + TaskReturnType* result) { + // TODO(ajwong): Remove this conditional and add a DCHECK to enforce that + // |reply| must be non-null in PostTaskAndReplyWithResult() below after + // current code that relies on this API softness has been removed. + // http://crbug.com/162712 + if (!callback.is_null()) + callback.Run(CallbackForward(*result)); +} + +} // namespace internal + +// When you have these methods +// +// R DoWorkAndReturn(); +// void Callback(const R& result); +// +// and want to call them in a PostTaskAndReply kind of fashion where the +// result of DoWorkAndReturn is passed to the Callback, you can use +// PostTaskAndReplyWithResult as in this example: +// +// PostTaskAndReplyWithResult( +// target_thread_.message_loop_proxy(), +// FROM_HERE, +// Bind(&DoWorkAndReturn), +// Bind(&Callback)); +template +bool PostTaskAndReplyWithResult( + TaskRunner* task_runner, + const tracked_objects::Location& from_here, + const Callback& task, + const Callback& reply) { + TaskReturnType* result = new TaskReturnType(); + return task_runner->PostTaskAndReply( + from_here, + base::Bind(&internal::ReturnAsParamAdapter, task, + result), + base::Bind(&internal::ReplyAdapter, reply, + base::Owned(result))); +} + +} // namespace base + +#endif // BASE_TASK_RUNNER_UTIL_H_ diff --git a/base/task_runner_util_unittest.cc b/base/task_runner_util_unittest.cc new file mode 100644 index 0000000000..04743944d6 --- /dev/null +++ b/base/task_runner_util_unittest.cc @@ -0,0 +1,128 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/task_runner_util.h" + +#include "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace { + +int ReturnFourtyTwo() { + return 42; +} + +void StoreValue(int* destination, int value) { + *destination = value; +} + +void StoreDoubleValue(double* destination, double value) { + *destination = value; +} + +int g_foo_destruct_count = 0; +int g_foo_free_count = 0; + +struct Foo { + ~Foo() { + ++g_foo_destruct_count; + } +}; + +scoped_ptr CreateFoo() { + return scoped_ptr(new Foo); +} + +void ExpectFoo(scoped_ptr foo) { + EXPECT_TRUE(foo.get()); + scoped_ptr local_foo(foo.Pass()); + EXPECT_TRUE(local_foo.get()); + EXPECT_FALSE(foo.get()); +} + +struct FreeFooFunctor { + void operator()(Foo* foo) const { + ++g_foo_free_count; + delete foo; + }; +}; + +scoped_ptr_malloc CreateScopedFoo() { + return scoped_ptr_malloc(new Foo); +} + +void ExpectScopedFoo(scoped_ptr_malloc foo) { + EXPECT_TRUE(foo.get()); + scoped_ptr_malloc local_foo(foo.Pass()); + EXPECT_TRUE(local_foo.get()); + EXPECT_FALSE(foo.get()); +} + +} // namespace + +TEST(TaskRunnerHelpersTest, PostTaskAndReplyWithResult) { + int result = 0; + + MessageLoop message_loop; + PostTaskAndReplyWithResult(message_loop.message_loop_proxy().get(), + FROM_HERE, + Bind(&ReturnFourtyTwo), + Bind(&StoreValue, &result)); + + RunLoop().RunUntilIdle(); + + EXPECT_EQ(42, result); +} + +TEST(TaskRunnerHelpersTest, PostTaskAndReplyWithResultImplicitConvert) { + double result = 0; + + MessageLoop message_loop; + PostTaskAndReplyWithResult(message_loop.message_loop_proxy().get(), + FROM_HERE, + Bind(&ReturnFourtyTwo), + Bind(&StoreDoubleValue, &result)); + + RunLoop().RunUntilIdle(); + + EXPECT_DOUBLE_EQ(42.0, result); +} + +TEST(TaskRunnerHelpersTest, PostTaskAndReplyWithResultPassed) { + g_foo_destruct_count = 0; + g_foo_free_count = 0; + + MessageLoop message_loop; + PostTaskAndReplyWithResult(message_loop.message_loop_proxy().get(), + FROM_HERE, + Bind(&CreateFoo), + Bind(&ExpectFoo)); + + RunLoop().RunUntilIdle(); + + EXPECT_EQ(1, g_foo_destruct_count); + EXPECT_EQ(0, g_foo_free_count); +} + +TEST(TaskRunnerHelpersTest, PostTaskAndReplyWithResultPassedFreeProc) { + g_foo_destruct_count = 0; + g_foo_free_count = 0; + + MessageLoop message_loop; + PostTaskAndReplyWithResult(message_loop.message_loop_proxy().get(), + FROM_HERE, + Bind(&CreateScopedFoo), + Bind(&ExpectScopedFoo)); + + RunLoop().RunUntilIdle(); + + EXPECT_EQ(1, g_foo_destruct_count); + EXPECT_EQ(1, g_foo_free_count); +} + +} // namespace base diff --git a/base/template_util.h b/base/template_util.h new file mode 100644 index 0000000000..e21d4dc773 --- /dev/null +++ b/base/template_util.h @@ -0,0 +1,108 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEMPLATE_UTIL_H_ +#define BASE_TEMPLATE_UTIL_H_ + +#include // For size_t. + +#include "build/build_config.h" + +namespace base { + +// template definitions from tr1 + +template +struct integral_constant { + static const T value = v; + typedef T value_type; + typedef integral_constant type; +}; + +template const T integral_constant::value; + +typedef integral_constant true_type; +typedef integral_constant false_type; + +template struct is_pointer : false_type {}; +template struct is_pointer : true_type {}; + +template struct is_same : public false_type {}; +template struct is_same : true_type {}; + +template struct is_array : public false_type {}; +template struct is_array : public true_type {}; +template struct is_array : public true_type {}; + +template struct is_non_const_reference : false_type {}; +template struct is_non_const_reference : true_type {}; +template struct is_non_const_reference : false_type {}; + +template struct is_void : false_type {}; +template <> struct is_void : true_type {}; + +namespace internal { + +// Types YesType and NoType are guaranteed such that sizeof(YesType) < +// sizeof(NoType). +typedef char YesType; + +struct NoType { + YesType dummy[2]; +}; + +// This class is an implementation detail for is_convertible, and you +// don't need to know how it works to use is_convertible. For those +// who care: we declare two different functions, one whose argument is +// of type To and one with a variadic argument list. We give them +// return types of different size, so we can use sizeof to trick the +// compiler into telling us which function it would have chosen if we +// had called it with an argument of type From. See Alexandrescu's +// _Modern C++ Design_ for more details on this sort of trick. + +struct ConvertHelper { + template + static YesType Test(To); + + template + static NoType Test(...); + + template + static From& Create(); +}; + +// Used to determine if a type is a struct/union/class. Inspired by Boost's +// is_class type_trait implementation. +struct IsClassHelper { + template + static YesType Test(void(C::*)(void)); + + template + static NoType Test(...); +}; + +} // namespace internal + +// Inherits from true_type if From is convertible to To, false_type otherwise. +// +// Note that if the type is convertible, this will be a true_type REGARDLESS +// of whether or not the conversion would emit a warning. +template +struct is_convertible + : integral_constant( + internal::ConvertHelper::Create())) == + sizeof(internal::YesType)> { +}; + +template +struct is_class + : integral_constant(0)) == + sizeof(internal::YesType)> { +}; + +} // namespace base + +#endif // BASE_TEMPLATE_UTIL_H_ diff --git a/base/template_util_unittest.cc b/base/template_util_unittest.cc new file mode 100644 index 0000000000..4cfa3e4fb5 --- /dev/null +++ b/base/template_util_unittest.cc @@ -0,0 +1,80 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/template_util.h" + +#include "base/basictypes.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace { + +struct AStruct {}; +class AClass {}; +enum AnEnum {}; + +class Parent {}; +class Child : public Parent {}; + +// is_pointer +COMPILE_ASSERT(!is_pointer::value, IsPointer); +COMPILE_ASSERT(!is_pointer::value, IsPointer); +COMPILE_ASSERT(is_pointer::value, IsPointer); +COMPILE_ASSERT(is_pointer::value, IsPointer); + +// is_array +COMPILE_ASSERT(!is_array::value, IsArray); +COMPILE_ASSERT(!is_array::value, IsArray); +COMPILE_ASSERT(!is_array::value, IsArray); +COMPILE_ASSERT(is_array::value, IsArray); +COMPILE_ASSERT(is_array::value, IsArray); +COMPILE_ASSERT(is_array::value, IsArray); + +// is_non_const_reference +COMPILE_ASSERT(!is_non_const_reference::value, IsNonConstReference); +COMPILE_ASSERT(!is_non_const_reference::value, IsNonConstReference); +COMPILE_ASSERT(is_non_const_reference::value, IsNonConstReference); + +// is_convertible + +// Extra parens needed to make preprocessor macro parsing happy. Otherwise, +// it sees the equivalent of: +// +// (is_convertible < Child), (Parent > ::value) +// +// Silly C++. +COMPILE_ASSERT( (is_convertible::value), IsConvertible); +COMPILE_ASSERT(!(is_convertible::value), IsConvertible); +COMPILE_ASSERT(!(is_convertible::value), IsConvertible); +COMPILE_ASSERT( (is_convertible::value), IsConvertible); +COMPILE_ASSERT( (is_convertible::value), IsConvertible); +COMPILE_ASSERT(!(is_convertible::value), IsConvertible); + +// Array types are an easy corner case. Make sure to test that +// it does indeed compile. +COMPILE_ASSERT(!(is_convertible::value), IsConvertible); +COMPILE_ASSERT(!(is_convertible::value), IsConvertible); +COMPILE_ASSERT( (is_convertible::value), IsConvertible); + +// is_same +COMPILE_ASSERT(!(is_same::value), IsSame); +COMPILE_ASSERT(!(is_same::value), IsSame); +COMPILE_ASSERT( (is_same::value), IsSame); +COMPILE_ASSERT( (is_same::value), IsSame); +COMPILE_ASSERT( (is_same::value), IsSame); +COMPILE_ASSERT( (is_same::value), IsSame); +COMPILE_ASSERT(!(is_same::value), IsSame); + + +// is_class +COMPILE_ASSERT(is_class::value, IsClass); +COMPILE_ASSERT(is_class::value, IsClass); +COMPILE_ASSERT(!is_class::value, IsClass); +COMPILE_ASSERT(!is_class::value, IsClass); +COMPILE_ASSERT(!is_class::value, IsClass); +COMPILE_ASSERT(!is_class::value, IsClass); +COMPILE_ASSERT(!is_class::value, IsClass); + +} // namespace +} // namespace base diff --git a/base/test/OWNERS b/base/test/OWNERS new file mode 100644 index 0000000000..92ecc889fd --- /dev/null +++ b/base/test/OWNERS @@ -0,0 +1 @@ +phajdan.jr@chromium.org diff --git a/base/test/android/OWNERS b/base/test/android/OWNERS new file mode 100644 index 0000000000..ebc6e2624f --- /dev/null +++ b/base/test/android/OWNERS @@ -0,0 +1,3 @@ +bulach@chromium.org +joth@chromium.org +yfriedman@chromium.org diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/AdvancedMockContext.java b/base/test/android/javatests/src/org/chromium/base/test/util/AdvancedMockContext.java new file mode 100644 index 0000000000..a57a8b0319 --- /dev/null +++ b/base/test/android/javatests/src/org/chromium/base/test/util/AdvancedMockContext.java @@ -0,0 +1,103 @@ +// 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. + +package org.chromium.base.test.util; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.SharedPreferences; +import android.test.mock.MockContentResolver; +import android.test.mock.MockContext; + +import java.util.HashMap; +import java.util.Map; + +/** + * ContextWrapper that adds functionality for SharedPreferences and a way to set and retrieve flags. + */ +public class AdvancedMockContext extends ContextWrapper { + + private final MockContentResolver mMockContentResolver = new MockContentResolver(); + + private final Map mSharedPreferences = + new HashMap(); + + private final Map flags = new HashMap(); + + public AdvancedMockContext(Context base) { + super(base); + } + + public AdvancedMockContext() { + super(new MockContext()); + } + + @Override + public String getPackageName() { + return getBaseContext().getPackageName(); + } + + @Override + public Context getApplicationContext() { + return this; + } + + @Override + public ContentResolver getContentResolver() { + return mMockContentResolver; + } + + public MockContentResolver getMockContentResolver() { + return mMockContentResolver; + } + + @Override + public SharedPreferences getSharedPreferences(String name, int mode) { + synchronized (mSharedPreferences) { + if (!mSharedPreferences.containsKey(name)) { + // Auto-create shared preferences to mimic Android Context behavior + mSharedPreferences.put(name, new InMemorySharedPreferences()); + } + return mSharedPreferences.get(name); + } + } + + public void addSharedPreferences(String name, Map data) { + synchronized (mSharedPreferences) { + mSharedPreferences.put(name, new InMemorySharedPreferences(data)); + } + } + + public void setFlag(String key) { + flags.put(key, true); + } + + public void clearFlag(String key) { + flags.remove(key); + } + + public boolean isFlagSet(String key) { + return flags.containsKey(key) && flags.get(key); + } + + public static class MapBuilder { + + private final Map mData = new HashMap(); + + public static MapBuilder create() { + return new MapBuilder(); + } + + public MapBuilder add(String key, Object value) { + mData.put(key, value); + return this; + } + + public Map build() { + return mData; + } + + } +} diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/DisabledTest.java b/base/test/android/javatests/src/org/chromium/base/test/util/DisabledTest.java new file mode 100644 index 0000000000..c2b2ecddcd --- /dev/null +++ b/base/test/android/javatests/src/org/chromium/base/test/util/DisabledTest.java @@ -0,0 +1,21 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.base.test.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation is for disabled tests. + *

+ * Tests with this annotation will not be run on any of the normal bots. + * Please note that they might eventually run on a special bot. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface DisabledTest { +} diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/EnormousTest.java b/base/test/android/javatests/src/org/chromium/base/test/util/EnormousTest.java new file mode 100644 index 0000000000..9f8fa939e8 --- /dev/null +++ b/base/test/android/javatests/src/org/chromium/base/test/util/EnormousTest.java @@ -0,0 +1,24 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.base.test.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation is for enormous tests. + *

+ * Examples of enormous tests are tests that depend on external web sites or + * tests that are long running. + *

+ * Such tests are likely NOT reliable enough to run on tree closing bots and + * should only be run on FYI bots. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface EnormousTest { +} diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/Feature.java b/base/test/android/javatests/src/org/chromium/base/test/util/Feature.java new file mode 100644 index 0000000000..aa120ea8f0 --- /dev/null +++ b/base/test/android/javatests/src/org/chromium/base/test/util/Feature.java @@ -0,0 +1,29 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.base.test.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The java instrumentation tests are normally fairly large (in terms of + * dependencies), and the test suite ends up containing a large amount of + * tests that are not trivial to filter / group just by their names. + * Instead, we use this annotation: each test should be annotated as: + * @Feature({"Foo", "Bar"}) + * in order for the test runner scripts to be able to filter and group + * them accordingly (for instance, this enable us to run all tests that exercise + * feature Foo). + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Feature { + /** + * @return A list of feature names. + */ + public String[] value(); +} diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/InMemorySharedPreferences.java b/base/test/android/javatests/src/org/chromium/base/test/util/InMemorySharedPreferences.java new file mode 100644 index 0000000000..c127732769 --- /dev/null +++ b/base/test/android/javatests/src/org/chromium/base/test/util/InMemorySharedPreferences.java @@ -0,0 +1,238 @@ +// 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. + +package org.chromium.base.test.util; + +import android.content.SharedPreferences; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * An implementation of SharedPreferences that can be used in tests. + *

[^:]+):$' % re.escape(path)) + path_dir = os.path.dirname(path) + + current_dir = '' + files = {} + for line in ls_output: + directory_match = re_directory.match(line) + if directory_match: + current_dir = directory_match.group('dir') + continue + file_match = re_file.match(line) + if file_match: + filename = os.path.join(current_dir, file_match.group('filename')) + if filename.startswith(path_dir): + filename = filename[len(path_dir) + 1:] + lastmod = datetime.datetime.strptime( + file_match.group('date') + ' ' + file_match.group('time')[:5], + '%Y-%m-%d %H:%M') + if not utc_offset and 'timezone' in re_file.groupindex: + utc_offset = file_match.group('timezone') + if isinstance(utc_offset, str) and len(utc_offset) == 5: + utc_delta = datetime.timedelta(hours=int(utc_offset[1:3]), + minutes=int(utc_offset[3:5])) + if utc_offset[0:1] == '-': + utc_delta = -utc_delta + lastmod -= utc_delta + files[filename] = (int(file_match.group('size')), lastmod) + return files + + +def _ParseMd5SumOutput(md5sum_output): + """Returns a list of tuples from the provided md5sum output. + + Args: + md5sum_output: output directly from md5sum binary. + + Returns: + List of namedtuples with attributes |hash| and |path|, where |path| is the + absolute path to the file with an Md5Sum of |hash|. + """ + HashAndPath = collections.namedtuple('HashAndPath', ['hash', 'path']) + split_lines = [line.split(' ') for line in md5sum_output] + return [HashAndPath._make(s) for s in split_lines if len(s) == 2] + + +def _HasAdbPushSucceeded(command_output): + """Returns whether adb push has succeeded from the provided output.""" + # TODO(frankf): We should look at the return code instead of the command + # output for many of the commands in this file. + if not command_output: + return True + # Success looks like this: "3035 KB/s (12512056 bytes in 4.025s)" + # Errors look like this: "failed to copy ... " + if not re.search('^[0-9]', command_output.splitlines()[-1]): + logging.critical('PUSH FAILED: ' + command_output) + return False + return True + + +def GetLogTimestamp(log_line, year): + """Returns the timestamp of the given |log_line| in the given year.""" + try: + return datetime.datetime.strptime('%s-%s' % (year, log_line[:18]), + '%Y-%m-%d %H:%M:%S.%f') + except (ValueError, IndexError): + logging.critical('Error reading timestamp from ' + log_line) + return None + + +class AndroidCommands(object): + """Helper class for communicating with Android device via adb. + + Args: + device: If given, adb commands are only send to the device of this ID. + Otherwise commands are sent to all attached devices. + """ + + def __init__(self, device=None): + adb_dir = os.path.dirname(constants.ADB_PATH) + if adb_dir and adb_dir not in os.environ['PATH'].split(os.pathsep): + # Required by third_party/android_testrunner to call directly 'adb'. + os.environ['PATH'] += os.pathsep + adb_dir + self._adb = adb_interface.AdbInterface() + if device: + self._adb.SetTargetSerial(device) + self._device = device + self._logcat = None + self.logcat_process = None + self._logcat_tmpoutfile = None + self._pushed_files = [] + self._device_utc_offset = None + self._potential_push_size = 0 + self._actual_push_size = 0 + self._md5sum_build_dir = '' + self._external_storage = '' + self._util_wrapper = '' + + def _LogShell(self, cmd): + """Logs the adb shell command.""" + if self._device: + device_repr = self._device[-4:] + else: + device_repr = '????' + logging.info('[%s]> %s', device_repr, cmd) + + def Adb(self): + """Returns our AdbInterface to avoid us wrapping all its methods.""" + return self._adb + + def GetDevice(self): + """Returns the device serial.""" + return self._device + + def IsOnline(self): + """Checks whether the device is online. + + Returns: + True if device is in 'device' mode, False otherwise. + """ + out = self._adb.SendCommand('get-state') + return out.strip() == 'device' + + def IsRootEnabled(self): + """Checks if root is enabled on the device.""" + root_test_output = self.RunShellCommand('ls /root') or [''] + return not 'Permission denied' in root_test_output[0] + + def EnableAdbRoot(self): + """Enables adb root on the device. + + Returns: + True: if output from executing adb root was as expected. + False: otherwise. + """ + if self.GetBuildType() == 'user': + logging.warning("Can't enable root in production builds with type user") + return False + else: + return_value = self._adb.EnableAdbRoot() + # EnableAdbRoot inserts a call for wait-for-device only when adb logcat + # output matches what is expected. Just to be safe add a call to + # wait-for-device. + self._adb.SendCommand('wait-for-device') + return return_value + + def GetDeviceYear(self): + """Returns the year information of the date on device.""" + return self.RunShellCommand('date +%Y')[0] + + def GetExternalStorage(self): + if not self._external_storage: + self._external_storage = self.RunShellCommand('echo $EXTERNAL_STORAGE')[0] + assert self._external_storage, 'Unable to find $EXTERNAL_STORAGE' + return self._external_storage + + def WaitForDevicePm(self): + """Blocks until the device's package manager is available. + + To workaround http://b/5201039, we restart the shell and retry if the + package manager isn't back after 120 seconds. + + Raises: + errors.WaitForResponseTimedOutError after max retries reached. + """ + last_err = None + retries = 3 + while retries: + try: + self._adb.WaitForDevicePm() + return # Success + except errors.WaitForResponseTimedOutError as e: + last_err = e + logging.warning('Restarting and retrying after timeout: %s', e) + retries -= 1 + self.RestartShell() + raise last_err # Only reached after max retries, re-raise the last error. + + def RestartShell(self): + """Restarts the shell on the device. Does not block for it to return.""" + self.RunShellCommand('stop') + self.RunShellCommand('start') + + def Reboot(self, full_reboot=True): + """Reboots the device and waits for the package manager to return. + + Args: + full_reboot: Whether to fully reboot the device or just restart the shell. + """ + # TODO(torne): hive can't reboot the device either way without breaking the + # connection; work out if we can handle this better + if os.environ.get('USING_HIVE'): + logging.warning('Ignoring reboot request as we are on hive') + return + if full_reboot or not self.IsRootEnabled(): + self._adb.SendCommand('reboot') + timeout = 300 + retries = 1 + # Wait for the device to disappear. + while retries < 10 and self.IsOnline(): + time.sleep(1) + retries += 1 + else: + self.RestartShell() + timeout = 120 + # To run tests we need at least the package manager and the sd card (or + # other external storage) to be ready. + self.WaitForDevicePm() + self.WaitForSdCardReady(timeout) + + def Shutdown(self): + """Shuts down the device.""" + self._adb.SendCommand('reboot -p') + + def Uninstall(self, package): + """Uninstalls the specified package from the device. + + Args: + package: Name of the package to remove. + + Returns: + A status string returned by adb uninstall + """ + uninstall_command = 'uninstall %s' % package + + self._LogShell(uninstall_command) + return self._adb.SendCommand(uninstall_command, timeout_time=60) + + def Install(self, package_file_path, reinstall=False): + """Installs the specified package to the device. + + Args: + package_file_path: Path to .apk file to install. + reinstall: Reinstall an existing apk, keeping the data. + + Returns: + A status string returned by adb install + """ + assert os.path.isfile(package_file_path), ('<%s> is not file' % + package_file_path) + + install_cmd = ['install'] + + if reinstall: + install_cmd.append('-r') + + install_cmd.append(package_file_path) + install_cmd = ' '.join(install_cmd) + + self._LogShell(install_cmd) + return self._adb.SendCommand(install_cmd, + timeout_time=2 * 60, + retry_count=0) + + def ManagedInstall(self, apk_path, keep_data=False, package_name=None, + reboots_on_failure=2): + """Installs specified package and reboots device on timeouts. + + If package_name is supplied, checks if the package is already installed and + doesn't reinstall if the apk md5sums match. + + Args: + apk_path: Path to .apk file to install. + keep_data: Reinstalls instead of uninstalling first, preserving the + application data. + package_name: Package name (only needed if keep_data=False). + reboots_on_failure: number of time to reboot if package manager is frozen. + """ + # Check if package is already installed and up to date. + if package_name: + installed_apk_path = self.GetApplicationPath(package_name) + if (installed_apk_path and + not self.GetFilesChanged(apk_path, installed_apk_path)): + logging.info('Skipped install: identical %s APK already installed' % + package_name) + return + # Install. + reboots_left = reboots_on_failure + while True: + try: + if not keep_data: + assert package_name + self.Uninstall(package_name) + install_status = self.Install(apk_path, reinstall=keep_data) + if 'Success' in install_status: + return + except errors.WaitForResponseTimedOutError: + print '@@@STEP_WARNINGS@@@' + logging.info('Timeout on installing %s on device %s', apk_path, + self._device) + + if reboots_left <= 0: + raise Exception('Install failure') + + # Force a hard reboot on last attempt + self.Reboot(full_reboot=(reboots_left == 1)) + reboots_left -= 1 + + def MakeSystemFolderWritable(self): + """Remounts the /system folder rw.""" + out = self._adb.SendCommand('remount') + if out.strip() != 'remount succeeded': + raise errors.MsgException('Remount failed: %s' % out) + + def RestartAdbServer(self): + """Restart the adb server.""" + self.KillAdbServer() + self.StartAdbServer() + + def KillAdbServer(self): + """Kill adb server.""" + adb_cmd = [constants.ADB_PATH, 'kill-server'] + return cmd_helper.RunCmd(adb_cmd) + + def StartAdbServer(self): + """Start adb server.""" + adb_cmd = [constants.ADB_PATH, 'start-server'] + return cmd_helper.RunCmd(adb_cmd) + + def WaitForSystemBootCompleted(self, wait_time): + """Waits for targeted system's boot_completed flag to be set. + + Args: + wait_time: time in seconds to wait + + Raises: + WaitForResponseTimedOutError if wait_time elapses and flag still not + set. + """ + logging.info('Waiting for system boot completed...') + self._adb.SendCommand('wait-for-device') + # Now the device is there, but system not boot completed. + # Query the sys.boot_completed flag with a basic command + boot_completed = False + attempts = 0 + wait_period = 5 + while not boot_completed and (attempts * wait_period) < wait_time: + output = self._adb.SendShellCommand('getprop sys.boot_completed', + retry_count=1) + output = output.strip() + if output == '1': + boot_completed = True + else: + # If 'error: xxx' returned when querying the flag, it means + # adb server lost the connection to the emulator, so restart the adb + # server. + if 'error:' in output: + self.RestartAdbServer() + time.sleep(wait_period) + attempts += 1 + if not boot_completed: + raise errors.WaitForResponseTimedOutError( + 'sys.boot_completed flag was not set after %s seconds' % wait_time) + + def WaitForSdCardReady(self, timeout_time): + """Wait for the SD card ready before pushing data into it.""" + logging.info('Waiting for SD card ready...') + sdcard_ready = False + attempts = 0 + wait_period = 5 + external_storage = self.GetExternalStorage() + while not sdcard_ready and attempts * wait_period < timeout_time: + output = self.RunShellCommand('ls ' + external_storage) + if output: + sdcard_ready = True + else: + time.sleep(wait_period) + attempts += 1 + if not sdcard_ready: + raise errors.WaitForResponseTimedOutError( + 'SD card not ready after %s seconds' % timeout_time) + + # It is tempting to turn this function into a generator, however this is not + # possible without using a private (local) adb_shell instance (to ensure no + # other command interleaves usage of it), which would defeat the main aim of + # being able to reuse the adb shell instance across commands. + def RunShellCommand(self, command, timeout_time=20, log_result=False): + """Send a command to the adb shell and return the result. + + Args: + command: String containing the shell command to send. Must not include + the single quotes as we use them to escape the whole command. + timeout_time: Number of seconds to wait for command to respond before + retrying, used by AdbInterface.SendShellCommand. + log_result: Boolean to indicate whether we should log the result of the + shell command. + + Returns: + list containing the lines of output received from running the command + """ + self._LogShell(command) + if "'" in command: logging.warning(command + " contains ' quotes") + result = self._adb.SendShellCommand( + "'%s'" % command, timeout_time).splitlines() + if ['error: device not found'] == result: + raise errors.DeviceUnresponsiveError('device not found') + if log_result: + self._LogShell('\n'.join(result)) + return result + + def GetShellCommandStatusAndOutput(self, command, timeout_time=20, + log_result=False): + """See RunShellCommand() above. + + Returns: + The tuple (exit code, list of output lines). + """ + lines = self.RunShellCommand( + command + '; echo %$?', timeout_time, log_result) + last_line = lines[-1] + status_pos = last_line.rfind('%') + assert status_pos >= 0 + status = int(last_line[status_pos + 1:]) + if status_pos == 0: + lines = lines[:-1] + else: + lines = lines[:-1] + [last_line[:status_pos]] + return (status, lines) + + def KillAll(self, process): + """Android version of killall, connected via adb. + + Args: + process: name of the process to kill off + + Returns: + the number of processes killed + """ + pids = self.ExtractPid(process) + if pids: + self.RunShellCommand('kill -9 ' + ' '.join(pids)) + return len(pids) + + def KillAllBlocking(self, process, timeout_sec): + """Blocking version of killall, connected via adb. + + This waits until no process matching the corresponding name appears in ps' + output anymore. + + Args: + process: name of the process to kill off + timeout_sec: the timeout in seconds + + Returns: + the number of processes killed + """ + processes_killed = self.KillAll(process) + if processes_killed: + elapsed = 0 + wait_period = 0.1 + # Note that this doesn't take into account the time spent in ExtractPid(). + while self.ExtractPid(process) and elapsed < timeout_sec: + time.sleep(wait_period) + elapsed += wait_period + if elapsed >= timeout_sec: + return 0 + return processes_killed + + def _GetActivityCommand(self, package, activity, wait_for_completion, action, + category, data, extras, trace_file_name, force_stop): + """Creates command to start |package|'s activity on the device. + + Args - as for StartActivity + + Returns: + the command to run on the target to start the activity + """ + cmd = 'am start -a %s' % action + if force_stop: + cmd += ' -S' + if wait_for_completion: + cmd += ' -W' + if category: + cmd += ' -c %s' % category + if package and activity: + cmd += ' -n %s/%s' % (package, activity) + if data: + cmd += ' -d "%s"' % data + if extras: + for key in extras: + value = extras[key] + if isinstance(value, str): + cmd += ' --es' + elif isinstance(value, bool): + cmd += ' --ez' + elif isinstance(value, int): + cmd += ' --ei' + else: + raise NotImplementedError( + 'Need to teach StartActivity how to pass %s extras' % type(value)) + cmd += ' %s %s' % (key, value) + if trace_file_name: + cmd += ' --start-profiler ' + trace_file_name + return cmd + + def StartActivity(self, package, activity, wait_for_completion=False, + action='android.intent.action.VIEW', + category=None, data=None, + extras=None, trace_file_name=None, + force_stop=False): + """Starts |package|'s activity on the device. + + Args: + package: Name of package to start (e.g. 'com.google.android.apps.chrome'). + activity: Name of activity (e.g. '.Main' or + 'com.google.android.apps.chrome.Main'). + wait_for_completion: wait for the activity to finish launching (-W flag). + action: string (e.g. "android.intent.action.MAIN"). Default is VIEW. + category: string (e.g. "android.intent.category.HOME") + data: Data string to pass to activity (e.g. 'http://www.example.com/'). + extras: Dict of extras to pass to activity. Values are significant. + trace_file_name: If used, turns on and saves the trace to this file name. + force_stop: force stop the target app before starting the activity (-S + flag). + """ + cmd = self._GetActivityCommand(package, activity, wait_for_completion, + action, category, data, extras, + trace_file_name, force_stop) + self.RunShellCommand(cmd) + + def StartActivityTimed(self, package, activity, wait_for_completion=False, + action='android.intent.action.VIEW', + category=None, data=None, + extras=None, trace_file_name=None, + force_stop=False): + """Starts |package|'s activity on the device, returning the start time + + Args - as for StartActivity + + Returns: + a timestamp string for the time at which the activity started + """ + cmd = self._GetActivityCommand(package, activity, wait_for_completion, + action, category, data, extras, + trace_file_name, force_stop) + self.StartMonitoringLogcat() + self.RunShellCommand('log starting activity; ' + cmd) + activity_started_re = re.compile('.*starting activity.*') + m = self.WaitForLogMatch(activity_started_re, None) + assert m + start_line = m.group(0) + return GetLogTimestamp(start_line, self.GetDeviceYear()) + + def GoHome(self): + """Tell the device to return to the home screen. Blocks until completion.""" + self.RunShellCommand('am start -W ' + '-a android.intent.action.MAIN -c android.intent.category.HOME') + + def CloseApplication(self, package): + """Attempt to close down the application, using increasing violence. + + Args: + package: Name of the process to kill off, e.g. + com.google.android.apps.chrome + """ + self.RunShellCommand('am force-stop ' + package) + + def GetApplicationPath(self, package): + """Get the installed apk path on the device for the given package. + + Args: + package: Name of the package. + + Returns: + Path to the apk on the device if it exists, None otherwise. + """ + pm_path_output = self.RunShellCommand('pm path ' + package) + # The path output contains anything if and only if the package + # exists. + if pm_path_output: + # pm_path_output is of the form: "package:/path/to/foo.apk" + return pm_path_output[0].split(':')[1] + else: + return None + + def ClearApplicationState(self, package): + """Closes and clears all state for the given |package|.""" + # Check that the package exists before clearing it. Necessary because + # calling pm clear on a package that doesn't exist may never return. + pm_path_output = self.RunShellCommand('pm path ' + package) + # The path output only contains anything if and only if the package exists. + if pm_path_output: + self.RunShellCommand('pm clear ' + package) + + def SendKeyEvent(self, keycode): + """Sends keycode to the device. + + Args: + keycode: Numeric keycode to send (see "enum" at top of file). + """ + self.RunShellCommand('input keyevent %d' % keycode) + + def _RunMd5Sum(self, host_path, device_path): + """Gets the md5sum of a host path and device path. + + Args: + host_path: Path (file or directory) on the host. + device_path: Path on the device. + + Returns: + A tuple containing lists of the host and device md5sum results as + created by _ParseMd5SumOutput(). + """ + if not self._md5sum_build_dir: + default_build_type = os.environ.get('BUILD_TYPE', 'Debug') + build_dir = '%s/%s/' % ( + cmd_helper.OutDirectory().get(), default_build_type) + md5sum_dist_path = '%s/md5sum_dist' % build_dir + if not os.path.exists(md5sum_dist_path): + build_dir = '%s/Release/' % cmd_helper.OutDirectory().get() + md5sum_dist_path = '%s/md5sum_dist' % build_dir + assert os.path.exists(md5sum_dist_path), 'Please build md5sum.' + command = 'push %s %s' % (md5sum_dist_path, MD5SUM_DEVICE_FOLDER) + assert _HasAdbPushSucceeded(self._adb.SendCommand(command)) + self._md5sum_build_dir = build_dir + + cmd = (MD5SUM_LD_LIBRARY_PATH + ' ' + self._util_wrapper + ' ' + + MD5SUM_DEVICE_PATH + ' ' + device_path) + device_hash_tuples = _ParseMd5SumOutput( + self.RunShellCommand(cmd, timeout_time=2 * 60)) + assert os.path.exists(host_path), 'Local path not found %s' % host_path + md5sum_output = cmd_helper.GetCmdOutput( + ['%s/md5sum_bin_host' % self._md5sum_build_dir, host_path]) + host_hash_tuples = _ParseMd5SumOutput(md5sum_output.splitlines()) + return (host_hash_tuples, device_hash_tuples) + + def GetFilesChanged(self, host_path, device_path): + """Compares the md5sum of a host path against a device path. + + Note: Ignores extra files on the device. + + Args: + host_path: Path (file or directory) on the host. + device_path: Path on the device. + + Returns: + A list of tuples of the form (host_path, device_path) for files whose + md5sums do not match. + """ + host_hash_tuples, device_hash_tuples = self._RunMd5Sum( + host_path, device_path) + + # Ignore extra files on the device. + if len(device_hash_tuples) > len(host_hash_tuples): + host_files = [os.path.relpath(os.path.normpath(p.path), + os.path.normpath(host_path)) for p in host_hash_tuples] + + def HostHas(fname): + return any(path in fname for path in host_files) + + device_hash_tuples = [h for h in device_hash_tuples if HostHas(h.path)] + + # Constructs the target device path from a given host path. Don't use when + # only a single file is given as the base name given in device_path may + # differ from that in host_path. + def HostToDevicePath(host_file_path): + return os.path.join(os.path.dirname(device_path), os.path.relpath( + host_file_path, os.path.dirname(os.path.normpath(host_path)))) + + device_hashes = [h.hash for h in device_hash_tuples] + return [(t.path, HostToDevicePath(t.path) if os.path.isdir(host_path) else + device_path) + for t in host_hash_tuples if t.hash not in device_hashes] + + def PushIfNeeded(self, host_path, device_path): + """Pushes |host_path| to |device_path|. + + Works for files and directories. This method skips copying any paths in + |test_data_paths| that already exist on the device with the same hash. + + All pushed files can be removed by calling RemovePushedFiles(). + """ + MAX_INDIVIDUAL_PUSHES = 50 + assert os.path.exists(host_path), 'Local path not found %s' % host_path + + def GetHostSize(path): + return int(cmd_helper.GetCmdOutput(['du', '-sb', path]).split()[0]) + + size = GetHostSize(host_path) + self._pushed_files.append(device_path) + self._potential_push_size += size + + changed_files = self.GetFilesChanged(host_path, device_path) + if not changed_files: + return + + def Push(host, device): + # NOTE: We can't use adb_interface.Push() because it hardcodes a timeout + # of 60 seconds which isn't sufficient for a lot of users of this method. + push_command = 'push %s %s' % (host, device) + self._LogShell(push_command) + + # Retry push with increasing backoff if the device is busy. + retry = 0 + while True: + output = self._adb.SendCommand(push_command, timeout_time=30 * 60) + if _HasAdbPushSucceeded(output): + return + if retry < 3: + retry += 1 + wait_time = 5 * retry + logging.error('Push failed, retrying in %d seconds: %s' % + (wait_time, output)) + time.sleep(wait_time) + else: + raise Exception('Push failed: %s' % output) + + diff_size = 0 + if len(changed_files) <= MAX_INDIVIDUAL_PUSHES: + diff_size = sum(GetHostSize(f[0]) for f in changed_files) + + # TODO(craigdh): Replace this educated guess with a heuristic that + # approximates the push time for each method. + if len(changed_files) > MAX_INDIVIDUAL_PUSHES or diff_size > 0.5 * size: + # We're pushing everything, remove everything first and then create it. + self._actual_push_size += size + if os.path.isdir(host_path): + self.RunShellCommand('rm -r %s' % device_path, timeout_time=2 * 60) + self.RunShellCommand('mkdir -p %s' % device_path) + Push(host_path, device_path) + else: + for f in changed_files: + Push(f[0], f[1]) + self._actual_push_size += diff_size + + def GetPushSizeInfo(self): + """Get total size of pushes to the device done via PushIfNeeded() + + Returns: + A tuple: + 1. Total size of push requests to PushIfNeeded (MB) + 2. Total size that was actually pushed (MB) + """ + return (self._potential_push_size, self._actual_push_size) + + def GetFileContents(self, filename, log_result=False): + """Gets contents from the file specified by |filename|.""" + return self.RunShellCommand('cat "%s" 2>/dev/null' % filename, + log_result=log_result) + + def SetFileContents(self, filename, contents): + """Writes |contents| to the file specified by |filename|.""" + with tempfile.NamedTemporaryFile() as f: + f.write(contents) + f.flush() + self._adb.Push(f.name, filename) + + _TEMP_FILE_BASE_FMT = 'temp_file_%d' + _TEMP_SCRIPT_FILE_BASE_FMT = 'temp_script_file_%d.sh' + + def _GetDeviceTempFileName(self, base_name): + i = 0 + while self.FileExistsOnDevice( + self.GetExternalStorage() + '/' + base_name % i): + i += 1 + return self.GetExternalStorage() + '/' + base_name % i + + def CanAccessProtectedFileContents(self): + """Returns True if Get/SetProtectedFileContents would work via "su". + + Devices running user builds don't have adb root, but may provide "su" which + can be used for accessing protected files. + """ + r = self.RunShellCommand('su -c cat /dev/null') + return r == [] or r[0].strip() == '' + + def GetProtectedFileContents(self, filename, log_result=False): + """Gets contents from the protected file specified by |filename|. + + This is less efficient than GetFileContents, but will work for protected + files and device files. + """ + # Run the script as root + return self.RunShellCommand('su -c cat "%s" 2> /dev/null' % filename) + + def SetProtectedFileContents(self, filename, contents): + """Writes |contents| to the protected file specified by |filename|. + + This is less efficient than SetFileContents, but will work for protected + files and device files. + """ + temp_file = self._GetDeviceTempFileName(AndroidCommands._TEMP_FILE_BASE_FMT) + temp_script = self._GetDeviceTempFileName( + AndroidCommands._TEMP_SCRIPT_FILE_BASE_FMT) + + # Put the contents in a temporary file + self.SetFileContents(temp_file, contents) + # Create a script to copy the file contents to its final destination + self.SetFileContents(temp_script, 'cat %s > %s' % (temp_file, filename)) + # Run the script as root + self.RunShellCommand('su -c sh %s' % temp_script) + # And remove the temporary files + self.RunShellCommand('rm ' + temp_file) + self.RunShellCommand('rm ' + temp_script) + + def RemovePushedFiles(self): + """Removes all files pushed with PushIfNeeded() from the device.""" + for p in self._pushed_files: + self.RunShellCommand('rm -r %s' % p, timeout_time=2 * 60) + + def ListPathContents(self, path): + """Lists files in all subdirectories of |path|. + + Args: + path: The path to list. + + Returns: + A dict of {"name": (size, lastmod), ...}. + """ + # Example output: + # /foo/bar: + # -rw-r----- 1 user group 102 2011-05-12 12:29:54.131623387 +0100 baz.txt + re_file = re.compile('^-(?P[^\s]+)\s+' + '(?P[^\s]+)\s+' + '(?P[^\s]+)\s+' + '(?P[^\s]+)\s+' + '(?P[^\s]+)\s+' + '(?P::flags(). + +This script sets the MH_NO_HEAP_EXECUTION bit on Mach-O executables. It is +intended for use with executables produced by a linker that predates Apple's +modifications to set this bit itself. It is also useful for setting this bit +for non-i386 executables, including x86_64 executables. Apple's linker only +sets it for 32-bit i386 executables, presumably under the assumption that +the value of vm.allow_data_exec is set in stone. However, if someone were to +change vm.allow_data_exec to 2 or 3, 64-bit x86_64 executables would run +without hardware protection against code execution on data pages. This +script can set the bit for x86_64 executables, guaranteeing that they run +with appropriate protection even when vm.allow_data_exec has been tampered +with. + +POSITION-INDEPENDENT EXECUTABLES/ADDRESS SPACE LAYOUT RANDOMIZATION + +This script sets or clears the MH_PIE bit in an executable's Mach-O header, +enabling or disabling position independence on Mac OS X 10.5 and later. +Processes running position-independent executables have varying levels of +ASLR protection depending on the OS release. The main executable's load +address, shared library load addresess, and the heap and stack base +addresses may be randomized. Position-independent executables are produced +by supplying the -pie flag to the linker (or defeated by supplying -no_pie). +Executables linked with a deployment target of 10.7 or higher have PIE on +by default. + +This script is never strictly needed during the build to enable PIE, as all +linkers used are recent enough to support -pie. However, it's used to +disable the PIE bit as needed on already-linked executables. +""" + +import optparse +import os +import struct +import sys + + +# +FAT_MAGIC = 0xcafebabe +FAT_CIGAM = 0xbebafeca + +# +MH_MAGIC = 0xfeedface +MH_CIGAM = 0xcefaedfe +MH_MAGIC_64 = 0xfeedfacf +MH_CIGAM_64 = 0xcffaedfe +MH_EXECUTE = 0x2 +MH_PIE = 0x00200000 +MH_NO_HEAP_EXECUTION = 0x01000000 + + +class MachOError(Exception): + """A class for exceptions thrown by this module.""" + + pass + + +def CheckedSeek(file, offset): + """Seeks the file-like object at |file| to offset |offset| and raises a + MachOError if anything funny happens.""" + + file.seek(offset, os.SEEK_SET) + new_offset = file.tell() + if new_offset != offset: + raise MachOError, \ + 'seek: expected offset %d, observed %d' % (offset, new_offset) + + +def CheckedRead(file, count): + """Reads |count| bytes from the file-like |file| object, raising a + MachOError if any other number of bytes is read.""" + + bytes = file.read(count) + if len(bytes) != count: + raise MachOError, \ + 'read: expected length %d, observed %d' % (count, len(bytes)) + + return bytes + + +def ReadUInt32(file, endian): + """Reads an unsinged 32-bit integer from the file-like |file| object, + treating it as having endianness specified by |endian| (per the |struct| + module), and returns it as a number. Raises a MachOError if the proper + length of data can't be read from |file|.""" + + bytes = CheckedRead(file, 4) + + (uint32,) = struct.unpack(endian + 'I', bytes) + return uint32 + + +def ReadMachHeader(file, endian): + """Reads an entire |mach_header| structure () from the + file-like |file| object, treating it as having endianness specified by + |endian| (per the |struct| module), and returns a 7-tuple of its members + as numbers. Raises a MachOError if the proper length of data can't be read + from |file|.""" + + bytes = CheckedRead(file, 28) + + magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags = \ + struct.unpack(endian + '7I', bytes) + return magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags + + +def ReadFatArch(file): + """Reads an entire |fat_arch| structure () from the file-like + |file| object, treating it as having endianness specified by |endian| + (per the |struct| module), and returns a 5-tuple of its members as numbers. + Raises a MachOError if the proper length of data can't be read from + |file|.""" + + bytes = CheckedRead(file, 20) + + cputype, cpusubtype, offset, size, align = struct.unpack('>5I', bytes) + return cputype, cpusubtype, offset, size, align + + +def WriteUInt32(file, uint32, endian): + """Writes |uint32| as an unsinged 32-bit integer to the file-like |file| + object, treating it as having endianness specified by |endian| (per the + |struct| module).""" + + bytes = struct.pack(endian + 'I', uint32) + assert len(bytes) == 4 + + file.write(bytes) + + +def HandleMachOFile(file, options, offset=0): + """Seeks the file-like |file| object to |offset|, reads its |mach_header|, + and rewrites the header's |flags| field if appropriate. The header's + endianness is detected. Both 32-bit and 64-bit Mach-O headers are supported + (mach_header and mach_header_64). Raises MachOError if used on a header that + does not have a known magic number or is not of type MH_EXECUTE. The + MH_PIE and MH_NO_HEAP_EXECUTION bits are set or cleared in the |flags| field + according to |options| and written to |file| if any changes need to be made. + If already set or clear as specified by |options|, nothing is written.""" + + CheckedSeek(file, offset) + magic = ReadUInt32(file, '<') + if magic == MH_MAGIC or magic == MH_MAGIC_64: + endian = '<' + elif magic == MH_CIGAM or magic == MH_CIGAM_64: + endian = '>' + else: + raise MachOError, \ + 'Mach-O file at offset %d has illusion of magic' % offset + + CheckedSeek(file, offset) + magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags = \ + ReadMachHeader(file, endian) + assert magic == MH_MAGIC or magic == MH_MAGIC_64 + if filetype != MH_EXECUTE: + raise MachOError, \ + 'Mach-O file at offset %d is type 0x%x, expected MH_EXECUTE' % \ + (offset, filetype) + + original_flags = flags + + if options.no_heap_execution: + flags |= MH_NO_HEAP_EXECUTION + else: + flags &= ~MH_NO_HEAP_EXECUTION + + if options.pie: + flags |= MH_PIE + else: + flags &= ~MH_PIE + + if flags != original_flags: + CheckedSeek(file, offset + 24) + WriteUInt32(file, flags, endian) + + +def HandleFatFile(file, options, fat_offset=0): + """Seeks the file-like |file| object to |offset| and loops over its + |fat_header| entries, calling HandleMachOFile for each.""" + + CheckedSeek(file, fat_offset) + magic = ReadUInt32(file, '>') + assert magic == FAT_MAGIC + + nfat_arch = ReadUInt32(file, '>') + + for index in xrange(0, nfat_arch): + cputype, cpusubtype, offset, size, align = ReadFatArch(file) + assert size >= 28 + + # HandleMachOFile will seek around. Come back here after calling it, in + # case it sought. + fat_arch_offset = file.tell() + HandleMachOFile(file, options, offset) + CheckedSeek(file, fat_arch_offset) + + +def main(me, args): + parser = optparse.OptionParser('%prog [options] ') + parser.add_option('--executable-heap', action='store_false', + dest='no_heap_execution', default=True, + help='Clear the MH_NO_HEAP_EXECUTION bit') + parser.add_option('--no-pie', action='store_false', + dest='pie', default=True, + help='Clear the MH_PIE bit') + (options, loose_args) = parser.parse_args(args) + if len(loose_args) != 1: + parser.print_usage() + return 1 + + executable_path = loose_args[0] + executable_file = open(executable_path, 'rb+') + + magic = ReadUInt32(executable_file, '<') + if magic == FAT_CIGAM: + # Check FAT_CIGAM and not FAT_MAGIC because the read was little-endian. + HandleFatFile(executable_file, options) + elif magic == MH_MAGIC or magic == MH_CIGAM or \ + magic == MH_MAGIC_64 or magic == MH_CIGAM_64: + HandleMachOFile(executable_file, options) + else: + raise MachOError, '%s is not a Mach-O or fat file' % executable_file + + executable_file.close() + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv[0], sys.argv[1:])) diff --git a/build/mac/change_mach_o_flags_from_xcode.sh b/build/mac/change_mach_o_flags_from_xcode.sh new file mode 100755 index 0000000000..1824f8db52 --- /dev/null +++ b/build/mac/change_mach_o_flags_from_xcode.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +# Copyright (c) 2011 The Chromium 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 small wrapper script around change_mach_o_flags.py allowing it to +# be invoked easily from Xcode. change_mach_o_flags.py expects its arguments +# on the command line, but Xcode puts its parameters in the environment. + +set -e + +exec "$(dirname "${0}")/change_mach_o_flags.py" \ + "${@}" \ + "${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}" diff --git a/build/mac/chrome_mac.croc b/build/mac/chrome_mac.croc new file mode 100644 index 0000000000..8cde00ce20 --- /dev/null +++ b/build/mac/chrome_mac.croc @@ -0,0 +1,36 @@ +# -*- python -*- +# Crocodile config file for Chromium mac + +{ + # List of rules, applied in order + 'rules' : [ + # Specify inclusions before exclusions, since rules are in order. + + # Don't include chromeos, linux, or windows specific files + { + 'regexp' : '.*(_|/)(chromeos|linux|win|views)(\\.|_)', + 'include' : 0, + }, + # Don't include ChromeOS dirs + { + 'regexp' : '.*/chromeos/', + 'include' : 0, + }, + + # Groups + { + 'regexp' : '.*_test_mac\\.', + 'group' : 'test', + }, + + # Languages + { + 'regexp' : '.*\\.m$', + 'language' : 'ObjC', + }, + { + 'regexp' : '.*\\.mm$', + 'language' : 'ObjC++', + }, + ], +} diff --git a/build/mac/copy_asan_runtime_dylib.sh b/build/mac/copy_asan_runtime_dylib.sh new file mode 100755 index 0000000000..b67b4346cd --- /dev/null +++ b/build/mac/copy_asan_runtime_dylib.sh @@ -0,0 +1,63 @@ +#!/bin/bash + +# 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. + +# For app bundles built with ASan, copies the runtime lib +# (libclang_rt.asan_osx_dynamic.dylib), on which their executables depend, from +# the compiler installation path into the bundle and fixes the dylib's install +# name in the binary to be relative to @executable_path. + +set -e + +BINARY="${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}" +BINARY_DIR="$(dirname "${BINARY}")" +ASAN_DYLIB_NAME=libclang_rt.asan_osx_dynamic.dylib +ASAN_DYLIB=$(find \ + "${BUILT_PRODUCTS_DIR}/../../third_party/llvm-build/Release+Asserts/lib/clang/" \ + -type f -path "*${ASAN_DYLIB_NAME}") + +# Find the link to the ASan runtime encoded in the binary. +BUILTIN_DYLIB_PATH=$(otool -L "${BINARY}" | \ + sed -Ene 's/^[[:blank:]]+(.*libclang_rt\.asan_osx_dynamic\.dylib).*$/\1/p') + +if [[ -z "${BUILTIN_DYLIB_PATH}" ]]; then + echo "${BINARY} does not depend on the ASan runtime library!" >&2 + # TODO(glider): make this return 1 when we fully switch to the dynamic + # runtime in ASan. + exit 0 +fi + +DYLIB_BASENAME=$(basename "${ASAN_DYLIB}") +if [[ "${DYLIB_BASENAME}" != "${ASAN_DYLIB_NAME}" ]]; then + echo "basename(${ASAN_DYLIB}) != ${ASAN_DYLIB_NAME}" >&2 + exit 1 +fi + +# Check whether the directory containing the executable binary is named +# "MacOS". In this case we're building a full-fledged OSX app and will put +# the runtime into appname.app/Contents/Libraries/. Otherwise this is probably +# an iOS gtest app, and the ASan runtime is put next to the executable. +UPPER_DIR=$(dirname "${BINARY_DIR}") +if [ "${UPPER_DIR}" == "MacOS" ]; then + LIBRARIES_DIR="${UPPER_DIR}/Libraries" + mkdir -p "${LIBRARIES_DIR}" + NEW_LC_ID_DYLIB="@executable_path/../Libraries/${ASAN_DYLIB_NAME}" +else + LIBRARIES_DIR="${BINARY_DIR}" + NEW_LC_ID_DYLIB="@executable_path/${ASAN_DYLIB_NAME}" +fi + +cp "${ASAN_DYLIB}" "${LIBRARIES_DIR}" + +# Make LC_ID_DYLIB of the runtime copy point to its location. +install_name_tool \ + -id "${NEW_LC_ID_DYLIB}" \ + "${LIBRARIES_DIR}/${ASAN_DYLIB_NAME}" + +# Fix the rpath to the runtime library recorded in the binary. +install_name_tool \ + -change "${BUILTIN_DYLIB_PATH}" \ + "${NEW_LC_ID_DYLIB}" \ + "${BINARY}" diff --git a/build/mac/copy_framework_unversioned.sh b/build/mac/copy_framework_unversioned.sh new file mode 100755 index 0000000000..380cc90840 --- /dev/null +++ b/build/mac/copy_framework_unversioned.sh @@ -0,0 +1,118 @@ +#!/bin/bash + +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Copies a framework to its new home, "unversioning" it. +# +# Normally, frameworks are versioned bundles. The contents of a framework are +# stored in a versioned directory within the bundle, and symbolic links +# provide access to the actual code and resources. See +# http://developer.apple.com/mac/library/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/FrameworkAnatomy.html +# +# The symbolic links usually found in frameworks create problems. Symbolic +# links are excluded from code signatures. That means that it's possible to +# remove or retarget a symbolic link within a framework without affecting the +# seal. In Chrome's case, the outer .app bundle contains a framework where +# all application code and resources live. In order for the signature on the +# .app to be meaningful, it encompasses the framework. Because framework +# resources are accessed through the framework's symbolic links, this +# arrangement results in a case where the resources can be altered without +# affecting the .app signature's validity. +# +# Indirection through symbolic links also carries a runtime performance +# penalty on open() operations, although open() typically completes so quickly +# that this is not considered a major performance problem. +# +# To resolve these problems, the frameworks that ship within Chrome's .app +# bundle are unversioned. Unversioning is simple: instead of using the +# original outer .framework directory as the framework that ships within the +# .app, the inner versioned directory is used. Instead of accessing bundled +# resources through symbolic links, they are accessed directly. In normal +# situations, the only hard-coded use of the versioned directory is by dyld, +# when loading the framework's code, but this is handled through a normal +# Mach-O load command, and it is easy to adjust the load command to point to +# the unversioned framework code rather than the versioned counterpart. +# +# The resulting framework bundles aren't strictly conforming, but they work +# as well as normal versioned framework bundles. +# +# An option to skip running install_name_tool is available. By passing -I as +# the first argument to this script, install_name_tool will be skipped. This +# is only suitable for copied frameworks that will not be linked against, or +# when install_name_tool will be run on any linker output when something is +# linked against the copied framework. This option exists to allow signed +# frameworks to pass through without subjecting them to any modifications that +# would break their signatures. + +set -e + +RUN_INSTALL_NAME_TOOL=1 +if [ $# -eq 3 ] && [ "${1}" = "-I" ] ; then + shift + RUN_INSTALL_NAME_TOOL= +fi + +if [ $# -ne 2 ] ; then + echo "usage: ${0} [-I] FRAMEWORK DESTINATION_DIR" >& 2 + exit 1 +fi + +# FRAMEWORK should be a path to a versioned framework bundle, ending in +# .framework. DESTINATION_DIR is the directory that the unversioned framework +# bundle will be copied to. + +FRAMEWORK="${1}" +DESTINATION_DIR="${2}" + +FRAMEWORK_NAME="$(basename "${FRAMEWORK}")" +if [ "${FRAMEWORK_NAME: -10}" != ".framework" ] ; then + echo "${0}: ${FRAMEWORK_NAME} does not end in .framework" >& 2 + exit 1 +fi +FRAMEWORK_NAME_NOEXT="${FRAMEWORK_NAME:0:$((${#FRAMEWORK_NAME} - 10))}" + +# Find the current version. +VERSIONS="${FRAMEWORK}/Versions" +CURRENT_VERSION_LINK="${VERSIONS}/Current" +CURRENT_VERSION_ID="$(readlink "${VERSIONS}/Current")" +CURRENT_VERSION="${VERSIONS}/${CURRENT_VERSION_ID}" + +# Make sure that the framework's structure makes sense as a versioned bundle. +if [ ! -e "${CURRENT_VERSION}/${FRAMEWORK_NAME_NOEXT}" ] ; then + echo "${0}: ${FRAMEWORK_NAME} does not contain a dylib" >& 2 + exit 1 +fi + +DESTINATION="${DESTINATION_DIR}/${FRAMEWORK_NAME}" + +# Copy the versioned directory within the versioned framework to its +# destination location. +mkdir -p "${DESTINATION_DIR}" +rsync -acC --delete --exclude Headers --exclude PrivateHeaders \ + --include '*.so' "${CURRENT_VERSION}/" "${DESTINATION}" + +if [[ -n "${RUN_INSTALL_NAME_TOOL}" ]]; then + # Adjust the Mach-O LC_ID_DYLIB load command in the framework. This does not + # change the LC_LOAD_DYLIB load commands in anything that may have already + # linked against the framework. Not all frameworks will actually need this + # to be changed. Some frameworks may already be built with the proper + # LC_ID_DYLIB for use as an unversioned framework. Xcode users can do this + # by setting LD_DYLIB_INSTALL_NAME to + # $(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(WRAPPER_NAME)/$(PRODUCT_NAME) + # If invoking ld via gcc or g++, pass the desired path to -Wl,-install_name + # at link time. + FRAMEWORK_DYLIB="${DESTINATION}/${FRAMEWORK_NAME_NOEXT}" + LC_ID_DYLIB_OLD="$(otool -l "${FRAMEWORK_DYLIB}" | + grep -A10 "^ *cmd LC_ID_DYLIB$" | + grep -m1 "^ *name" | + sed -Ee 's/^ *name (.*) \(offset [0-9]+\)$/\1/')" + VERSION_PATH="/Versions/${CURRENT_VERSION_ID}/${FRAMEWORK_NAME_NOEXT}" + LC_ID_DYLIB_NEW="$(echo "${LC_ID_DYLIB_OLD}" | + sed -Ee "s%${VERSION_PATH}$%/${FRAMEWORK_NAME_NOEXT}%")" + + if [ "${LC_ID_DYLIB_NEW}" != "${LC_ID_DYLIB_OLD}" ] ; then + install_name_tool -id "${LC_ID_DYLIB_NEW}" "${FRAMEWORK_DYLIB}" + fi +fi diff --git a/build/mac/edit_xibs.sh b/build/mac/edit_xibs.sh new file mode 100755 index 0000000000..a3054557b3 --- /dev/null +++ b/build/mac/edit_xibs.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +# Copyright (c) 2012 The Chromium 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 is a convenience to run GYP for /src/chrome/chrome_nibs.gyp +# with the Xcode generator (as you likely use ninja). Documentation: +# http://dev.chromium.org/developers/design-documents/mac-xib-files + +set -e + +RELSRC=$(dirname "$0")/../.. +SRC=$(cd "$RELSRC" && pwd) +GYP_GENERATORS=xcode python "$SRC/tools/gyp/gyp" "$SRC/chrome/chrome_nibs.gyp" +echo "You can now edit XIB files in Xcode using:" +echo " $SRC/chrome/chrome_nibs.xcodeproj" diff --git a/build/mac/find_sdk.py b/build/mac/find_sdk.py new file mode 100755 index 0000000000..067be638d2 --- /dev/null +++ b/build/mac/find_sdk.py @@ -0,0 +1,82 @@ +#!/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. + +import os +import re +import subprocess +import sys + +"""Prints the lowest locally available SDK version greater than or equal to a +given minimum sdk version to standard output. + +Usage: + python find_sdk.py 10.6 # Ignores SDKs < 10.6 +""" + +from optparse import OptionParser + + +def parse_version(version_str): + """'10.6' => [10, 6]""" + return map(int, re.findall(r'(\d+)', version_str)) + + +def main(): + parser = OptionParser() + parser.add_option("--verify", + action="store_true", dest="verify", default=False, + help="return the sdk argument and warn if it doesn't exist") + parser.add_option("--sdk_path", + action="store", type="string", dest="sdk_path", default="", + help="user-specified SDK path; bypasses verification") + (options, args) = parser.parse_args() + min_sdk_version = args[0] + + job = subprocess.Popen(['xcode-select', '-print-path'], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + out, err = job.communicate() + if job.returncode != 0: + print >>sys.stderr, out + print >>sys.stderr, err + raise Exception(('Error %d running xcode-select, you might have to run ' + '|sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer| ' + 'if you are using Xcode 4.') % job.returncode) + # The Developer folder moved in Xcode 4.3. + xcode43_sdk_path = os.path.join( + out.rstrip(), 'Platforms/MacOSX.platform/Developer/SDKs') + if os.path.isdir(xcode43_sdk_path): + sdk_dir = xcode43_sdk_path + else: + sdk_dir = os.path.join(out.rstrip(), 'SDKs') + sdks = [re.findall('^MacOSX(10\.\d+)\.sdk$', s) for s in os.listdir(sdk_dir)] + sdks = [s[0] for s in sdks if s] # [['10.5'], ['10.6']] => ['10.5', '10.6'] + sdks = [s for s in sdks # ['10.5', '10.6'] => ['10.6'] + if parse_version(s) >= parse_version(min_sdk_version)] + if not sdks: + raise Exception('No %s+ SDK found' % min_sdk_version) + best_sdk = sorted(sdks, key=parse_version)[0] + + if options.verify and best_sdk != min_sdk_version and not options.sdk_path: + print >>sys.stderr, '' + print >>sys.stderr, ' vvvvvvv' + print >>sys.stderr, '' + print >>sys.stderr, \ + 'This build requires the %s SDK, but it was not found on your system.' \ + % min_sdk_version + print >>sys.stderr, \ + 'Either install it, or explicitly set mac_sdk in your GYP_DEFINES.' + print >>sys.stderr, '' + print >>sys.stderr, ' ^^^^^^^' + print >>sys.stderr, '' + return min_sdk_version + + return best_sdk + + +if __name__ == '__main__': + if sys.platform != 'darwin': + raise Exception("This script only runs on Mac") + print main() diff --git a/build/mac/make_more_helpers.sh b/build/mac/make_more_helpers.sh new file mode 100755 index 0000000000..6f5c4749eb --- /dev/null +++ b/build/mac/make_more_helpers.sh @@ -0,0 +1,91 @@ +#!/bin/bash + +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Usage: make_more_helpers.sh +# +# This script creates additional helper .app bundles for Chromium, based on +# the existing helper .app bundle, changing their Mach-O header's flags to +# enable and disable various features. Based on Chromium Helper.app, it will +# create Chromium Helper EH.app, which has the MH_NO_HEAP_EXECUTION bit +# cleared to support Chromium child processes that require an executable heap, +# and Chromium Helper NP.app, which has the MH_PIE bit cleared to support +# Chromium child processes that cannot tolerate ASLR. +# +# This script expects to be called from the chrome_exe target as a postbuild, +# and operates directly within the built-up browser app's versioned directory. +# +# Each helper is adjusted by giving it the proper bundle name, renaming the +# executable, adjusting several Info.plist keys, and changing the executable's +# Mach-O flags. + +set -eu + +make_helper() { + local containing_dir="${1}" + local app_name="${2}" + local feature="${3}" + local flags="${4}" + + local helper_name="${app_name} Helper" + local helper_stem="${containing_dir}/${helper_name}" + local original_helper="${helper_stem}.app" + if [[ ! -d "${original_helper}" ]]; then + echo "${0}: error: ${original_helper} is a required directory" >& 2 + exit 1 + fi + local original_helper_exe="${original_helper}/Contents/MacOS/${helper_name}" + if [[ ! -f "${original_helper_exe}" ]]; then + echo "${0}: error: ${original_helper_exe} is a required file" >& 2 + exit 1 + fi + + local feature_helper="${helper_stem} ${feature}.app" + + rsync -acC --delete --include '*.so' "${original_helper}/" "${feature_helper}" + + local helper_feature="${helper_name} ${feature}" + local helper_feature_exe="${feature_helper}/Contents/MacOS/${helper_feature}" + mv "${feature_helper}/Contents/MacOS/${helper_name}" "${helper_feature_exe}" + + local change_flags="$(dirname "${0}")/change_mach_o_flags.py" + "${change_flags}" ${flags} "${helper_feature_exe}" + + local feature_info="${feature_helper}/Contents/Info" + local feature_info_plist="${feature_info}.plist" + + defaults write "${feature_info}" "CFBundleDisplayName" "${helper_feature}" + defaults write "${feature_info}" "CFBundleExecutable" "${helper_feature}" + + cfbundleid="$(defaults read "${feature_info}" "CFBundleIdentifier")" + feature_cfbundleid="${cfbundleid}.${feature}" + defaults write "${feature_info}" "CFBundleIdentifier" "${feature_cfbundleid}" + + cfbundlename="$(defaults read "${feature_info}" "CFBundleName")" + feature_cfbundlename="${cfbundlename} ${feature}" + defaults write "${feature_info}" "CFBundleName" "${feature_cfbundlename}" + + # As usual, defaults might have put the plist into whatever format excites + # it, but Info.plists get converted back to the expected XML format. + plutil -convert xml1 "${feature_info_plist}" + + # `defaults` also changes the file permissions, so make the file + # world-readable again. + chmod a+r "${feature_info_plist}" +} + +if [[ ${#} -ne 2 ]]; then + echo "usage: ${0} " >& 2 + exit 1 +fi + +DIRECTORY_WITHIN_CONTENTS="${1}" +APP_NAME="${2}" + +CONTENTS_DIR="${BUILT_PRODUCTS_DIR}/${CONTENTS_FOLDER_PATH}" +CONTAINING_DIR="${CONTENTS_DIR}/${DIRECTORY_WITHIN_CONTENTS}" + +make_helper "${CONTAINING_DIR}" "${APP_NAME}" "EH" "--executable-heap" +make_helper "${CONTAINING_DIR}" "${APP_NAME}" "NP" "--no-pie" diff --git a/build/mac/strip_from_xcode b/build/mac/strip_from_xcode new file mode 100755 index 0000000000..c26b9fb492 --- /dev/null +++ b/build/mac/strip_from_xcode @@ -0,0 +1,62 @@ +#!/bin/bash + +# Copyright (c) 2008 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# This is a handy wrapper script that figures out how to call the strip +# utility (strip_save_dsym in this case), if it even needs to be called at all, +# and then does it. This script should be called by a post-link phase in +# targets that might generate Mach-O executables, dynamic libraries, or +# loadable bundles. +# +# An example "Strip If Needed" build phase placed after "Link Binary With +# Libraries" would do: +# exec "${XCODEPROJ_DEPTH}/build/mac/strip_from_xcode" + +if [ "${CONFIGURATION}" != "Release" ] ; then + # Only strip in release mode. + exit 0 +fi + +declare -a FLAGS + +# MACH_O_TYPE is not set for a command-line tool, so check PRODUCT_TYPE too. +# Weird. +if [ "${MACH_O_TYPE}" = "mh_execute" ] || \ + [ "${PRODUCT_TYPE}" = "com.apple.product-type.tool" ] ; then + # Strip everything (no special flags). No-op. + true +elif [ "${MACH_O_TYPE}" = "mh_dylib" ] || \ + [ "${MACH_O_TYPE}" = "mh_bundle" ]; then + # Strip debugging symbols and local symbols + FLAGS[${#FLAGS[@]}]=-S + FLAGS[${#FLAGS[@]}]=-x +elif [ "${MACH_O_TYPE}" = "staticlib" ] ; then + # Don't strip static libraries. + exit 0 +else + # Warn, but don't treat this as an error. + echo $0: warning: unrecognized MACH_O_TYPE ${MACH_O_TYPE} + exit 0 +fi + +if [ -n "${STRIPFLAGS}" ] ; then + # Pick up the standard STRIPFLAGS Xcode setting, used for "Additional Strip + # Flags". + for stripflag in "${STRIPFLAGS}" ; do + FLAGS[${#FLAGS[@]}]="${stripflag}" + done +fi + +if [ -n "${CHROMIUM_STRIP_SAVE_FILE}" ] ; then + # An Xcode project can communicate a file listing symbols to saved in this + # environment variable by setting it as a build setting. This isn't a + # standard Xcode setting. It's used in preference to STRIPFLAGS to + # eliminate quoting ambiguity concerns. + FLAGS[${#FLAGS[@]}]=-s + FLAGS[${#FLAGS[@]}]="${CHROMIUM_STRIP_SAVE_FILE}" +fi + +exec "$(dirname ${0})/strip_save_dsym" "${FLAGS[@]}" \ + "${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}" diff --git a/build/mac/strip_save_dsym b/build/mac/strip_save_dsym new file mode 100755 index 0000000000..ef08d831f1 --- /dev/null +++ b/build/mac/strip_save_dsym @@ -0,0 +1,341 @@ +#!/usr/bin/python + +# Copyright (c) 2011 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Usage: strip_save_dsym +# +# strip_save_dsym is a wrapper around the standard strip utility. Given an +# input Mach-O file, strip_save_dsym will save a copy of the file in a "fake" +# .dSYM bundle for debugging, and then call strip to strip the Mach-O file. +# Note that the .dSYM file is a "fake" in that it's not a self-contained +# .dSYM bundle, it just contains a copy of the original (unstripped) Mach-O +# file, and therefore contains references to object files on the filesystem. +# The generated .dSYM bundle is therefore unsuitable for debugging in the +# absence of these .o files. +# +# If a .dSYM already exists and has a newer timestamp than the Mach-O file, +# this utility does nothing. That allows strip_save_dsym to be run on a file +# that has already been stripped without trashing the .dSYM. +# +# Rationale: the "right" way to generate dSYM bundles, dsymutil, is incredibly +# slow. On the other hand, doing a file copy (which is really all that +# dsymutil does) is comparatively fast. Since we usually just want to strip +# a release-mode executable but still be able to debug it, and we don't care +# so much about generating a hermetic dSYM bundle, we'll prefer the file copy. +# If a real dSYM is ever needed, it's still possible to create one by running +# dsymutil and pointing it at the original Mach-O file inside the "fake" +# bundle, provided that the object files are available. + +import errno +import os +import re +import shutil +import subprocess +import sys +import time + +# Returns a list of architectures contained in a Mach-O file. The file can be +# a universal (fat) file, in which case there will be one list element for +# each contained architecture, or it can be a thin single-architecture Mach-O +# file, in which case the list will contain a single element identifying the +# architecture. On error, returns an empty list. Determines the architecture +# list by calling file. +def macho_archs(macho): + macho_types = ["executable", + "dynamically linked shared library", + "bundle"] + macho_types_re = "Mach-O (?:64-bit )?(?:" + "|".join(macho_types) + ")" + + file_cmd = subprocess.Popen(["/usr/bin/file", "-b", "--", macho], + stdout=subprocess.PIPE) + + archs = [] + + type_line = file_cmd.stdout.readline() + type_match = re.match("^%s (.*)$" % macho_types_re, type_line) + if type_match: + archs.append(type_match.group(1)) + return [type_match.group(1)] + else: + type_match = re.match("^Mach-O universal binary with (.*) architectures$", + type_line) + if type_match: + for i in range(0, int(type_match.group(1))): + arch_line = file_cmd.stdout.readline() + arch_match = re.match( + "^.* \(for architecture (.*)\):\t%s .*$" % macho_types_re, + arch_line) + if arch_match: + archs.append(arch_match.group(1)) + + if file_cmd.wait() != 0: + archs = [] + + if len(archs) == 0: + print >> sys.stderr, "No architectures in %s" % macho + + return archs + +# Returns a dictionary mapping architectures contained in the file as returned +# by macho_archs to the LC_UUID load command for that architecture. +# Architectures with no LC_UUID load command are omitted from the dictionary. +# Determines the UUID value by calling otool. +def macho_uuids(macho): + uuids = {} + + archs = macho_archs(macho) + if len(archs) == 0: + return uuids + + for arch in archs: + if arch == "": + continue + + otool_cmd = subprocess.Popen(["/usr/bin/otool", "-arch", arch, "-l", "-", + macho], + stdout=subprocess.PIPE) + # state 0 is when nothing UUID-related has been seen yet. State 1 is + # entered after a load command begins, but it may not be an LC_UUID load + # command. States 2, 3, and 4 are intermediate states while reading an + # LC_UUID command. State 5 is the terminal state for a successful LC_UUID + # read. State 6 is the error state. + state = 0 + uuid = "" + for otool_line in otool_cmd.stdout: + if state == 0: + if re.match("^Load command .*$", otool_line): + state = 1 + elif state == 1: + if re.match("^ cmd LC_UUID$", otool_line): + state = 2 + else: + state = 0 + elif state == 2: + if re.match("^ cmdsize 24$", otool_line): + state = 3 + else: + state = 6 + elif state == 3: + # The UUID display format changed in the version of otool shipping + # with the Xcode 3.2.2 prerelease. The new format is traditional: + # uuid 4D7135B2-9C56-C5F5-5F49-A994258E0955 + # and with Xcode 3.2.6, then line is indented one more space: + # uuid 4D7135B2-9C56-C5F5-5F49-A994258E0955 + # The old format, from cctools-750 and older's otool, breaks the UUID + # up into a sequence of bytes: + # uuid 0x4d 0x71 0x35 0xb2 0x9c 0x56 0xc5 0xf5 + # 0x5f 0x49 0xa9 0x94 0x25 0x8e 0x09 0x55 + new_uuid_match = re.match("^ {3,4}uuid (.{8}-.{4}-.{4}-.{4}-.{12})$", + otool_line) + if new_uuid_match: + uuid = new_uuid_match.group(1) + + # Skip state 4, there is no second line to read. + state = 5 + else: + old_uuid_match = re.match("^ uuid 0x(..) 0x(..) 0x(..) 0x(..) " + "0x(..) 0x(..) 0x(..) 0x(..)$", + otool_line) + if old_uuid_match: + state = 4 + uuid = old_uuid_match.group(1) + old_uuid_match.group(2) + \ + old_uuid_match.group(3) + old_uuid_match.group(4) + "-" + \ + old_uuid_match.group(5) + old_uuid_match.group(6) + "-" + \ + old_uuid_match.group(7) + old_uuid_match.group(8) + "-" + else: + state = 6 + elif state == 4: + old_uuid_match = re.match("^ 0x(..) 0x(..) 0x(..) 0x(..) " + "0x(..) 0x(..) 0x(..) 0x(..)$", + otool_line) + if old_uuid_match: + state = 5 + uuid += old_uuid_match.group(1) + old_uuid_match.group(2) + "-" + \ + old_uuid_match.group(3) + old_uuid_match.group(4) + \ + old_uuid_match.group(5) + old_uuid_match.group(6) + \ + old_uuid_match.group(7) + old_uuid_match.group(8) + else: + state = 6 + + if otool_cmd.wait() != 0: + state = 6 + + if state == 5: + uuids[arch] = uuid.upper() + + if len(uuids) == 0: + print >> sys.stderr, "No UUIDs in %s" % macho + + return uuids + +# Given a path to a Mach-O file and possible information from the environment, +# determines the desired path to the .dSYM. +def dsym_path(macho): + # If building a bundle, the .dSYM should be placed next to the bundle. Use + # WRAPPER_NAME to make this determination. If called from xcodebuild, + # WRAPPER_NAME will be set to the name of the bundle. + dsym = "" + if "WRAPPER_NAME" in os.environ: + if "BUILT_PRODUCTS_DIR" in os.environ: + dsym = os.path.join(os.environ["BUILT_PRODUCTS_DIR"], + os.environ["WRAPPER_NAME"]) + else: + dsym = os.environ["WRAPPER_NAME"] + else: + dsym = macho + + dsym += ".dSYM" + + return dsym + +# Creates a fake .dSYM bundle at dsym for macho, a Mach-O image with the +# architectures and UUIDs specified by the uuids map. +def make_fake_dsym(macho, dsym): + uuids = macho_uuids(macho) + if len(uuids) == 0: + return False + + dwarf_dir = os.path.join(dsym, "Contents", "Resources", "DWARF") + dwarf_file = os.path.join(dwarf_dir, os.path.basename(macho)) + try: + os.makedirs(dwarf_dir) + except OSError, (err, error_string): + if err != errno.EEXIST: + raise + shutil.copyfile(macho, dwarf_file) + + # info_template is the same as what dsymutil would have written, with the + # addition of the fake_dsym key. + info_template = \ +''' + + + + CFBundleDevelopmentRegion + English + CFBundleIdentifier + com.apple.xcode.dsym.%(root_name)s + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + dSYM + CFBundleSignature + ???? + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + dSYM_UUID + +%(uuid_dict)s + fake_dsym + + + +''' + + root_name = os.path.basename(dsym)[:-5] # whatever.dSYM without .dSYM + uuid_dict = "" + for arch in sorted(uuids): + uuid_dict += "\t\t\t" + arch + "\n"\ + "\t\t\t" + uuids[arch] + "\n" + info_dict = { + "root_name": root_name, + "uuid_dict": uuid_dict, + } + info_contents = info_template % info_dict + info_file = os.path.join(dsym, "Contents", "Info.plist") + info_fd = open(info_file, "w") + info_fd.write(info_contents) + info_fd.close() + + return True + +# For a Mach-O file, determines where the .dSYM bundle should be located. If +# the bundle does not exist or has a modification time older than the Mach-O +# file, calls make_fake_dsym to create a fake .dSYM bundle there, then strips +# the Mach-O file and sets the modification time on the .dSYM bundle and Mach-O +# file to be identical. +def strip_and_make_fake_dsym(macho): + dsym = dsym_path(macho) + macho_stat = os.stat(macho) + dsym_stat = None + try: + dsym_stat = os.stat(dsym) + except OSError, (err, error_string): + if err != errno.ENOENT: + raise + + if dsym_stat is None or dsym_stat.st_mtime < macho_stat.st_mtime: + # Make a .dSYM bundle + if not make_fake_dsym(macho, dsym): + return False + + # Strip the Mach-O file + remove_dsym = True + try: + strip_path = "" + if "SYSTEM_DEVELOPER_BIN_DIR" in os.environ: + strip_path = os.environ["SYSTEM_DEVELOPER_BIN_DIR"] + else: + strip_path = "/usr/bin" + strip_path = os.path.join(strip_path, "strip") + strip_cmdline = [strip_path] + sys.argv[1:] + strip_cmd = subprocess.Popen(strip_cmdline) + if strip_cmd.wait() == 0: + remove_dsym = False + finally: + if remove_dsym: + shutil.rmtree(dsym) + + # Update modification time on the Mach-O file and .dSYM bundle + now = time.time() + os.utime(macho, (now, now)) + os.utime(dsym, (now, now)) + + return True + +def main(argv=None): + if argv is None: + argv = sys.argv + + # This only supports operating on one file at a time. Look at the arguments + # to strip to figure out what the source to be stripped is. Arguments are + # processed in the same way that strip does, although to reduce complexity, + # this doesn't do all of the same checking as strip. For example, strip + # has no -Z switch and would treat -Z on the command line as an error. For + # the purposes this is needed for, that's fine. + macho = None + process_switches = True + ignore_argument = False + for arg in argv[1:]: + if ignore_argument: + ignore_argument = False + continue + if process_switches: + if arg == "-": + process_switches = False + # strip has these switches accept an argument: + if arg in ["-s", "-R", "-d", "-o", "-arch"]: + ignore_argument = True + if arg[0] == "-": + continue + if macho is None: + macho = arg + else: + print >> sys.stderr, "Too many things to strip" + return 1 + + if macho is None: + print >> sys.stderr, "Nothing to strip" + return 1 + + if not strip_and_make_fake_dsym(macho): + return 1 + + return 0 + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/build/mac/tweak_info_plist.py b/build/mac/tweak_info_plist.py new file mode 100755 index 0000000000..eb38acea4e --- /dev/null +++ b/build/mac/tweak_info_plist.py @@ -0,0 +1,305 @@ +#!/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. + +# +# Xcode supports build variable substitutions and CPP; sadly, that doesn't work +# because: +# +# 1. Xcode wants to do the Info.plist work before it runs any build phases, +# this means if we were to generate a .h file for INFOPLIST_PREFIX_HEADER +# we'd have to put it in another target so it runs in time. +# 2. Xcode also doesn't check to see if the header being used as a prefix for +# the Info.plist has changed. So even if we updated it, it's only looking +# at the modtime of the info.plist to see if that's changed. +# +# So, we work around all of this by making a script build phase that will run +# during the app build, and simply update the info.plist in place. This way +# by the time the app target is done, the info.plist is correct. +# + +import optparse +import os +from os import environ as env +import plistlib +import re +import subprocess +import sys +import tempfile + +TOP = os.path.join(env['SRCROOT'], '..') + + +def _GetOutput(args): + """Runs a subprocess and waits for termination. Returns (stdout, returncode) + of the process. stderr is attached to the parent.""" + proc = subprocess.Popen(args, stdout=subprocess.PIPE) + (stdout, stderr) = proc.communicate() + return (stdout, proc.returncode) + + +def _GetOutputNoError(args): + """Similar to _GetOutput() but ignores stderr. If there's an error launching + the child (like file not found), the exception will be caught and (None, 1) + will be returned to mimic quiet failure.""" + try: + proc = subprocess.Popen(args, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + except OSError: + return (None, 1) + (stdout, stderr) = proc.communicate() + return (stdout, proc.returncode) + + +def _RemoveKeys(plist, *keys): + """Removes a varargs of keys from the plist.""" + for key in keys: + try: + del plist[key] + except KeyError: + pass + + +def _AddVersionKeys(plist, version=None): + """Adds the product version number into the plist. Returns True on success and + False on error. The error will be printed to stderr.""" + if version: + match = re.match('\d+\.\d+\.(\d+\.\d+)$', version) + if not match: + print >>sys.stderr, 'Invalid version string specified: "%s"' % version + return False + + full_version = match.group(0) + bundle_version = match.group(1) + + else: + # Pull in the Chrome version number. + VERSION_TOOL = os.path.join(TOP, 'chrome/tools/build/version.py') + VERSION_FILE = os.path.join(TOP, 'chrome/VERSION') + + (stdout, retval1) = _GetOutput([VERSION_TOOL, '-f', VERSION_FILE, '-t', + '@MAJOR@.@MINOR@.@BUILD@.@PATCH@']) + full_version = stdout.rstrip() + + (stdout, retval2) = _GetOutput([VERSION_TOOL, '-f', VERSION_FILE, '-t', + '@BUILD@.@PATCH@']) + bundle_version = stdout.rstrip() + + # If either of the two version commands finished with non-zero returncode, + # report the error up. + if retval1 or retval2: + return False + + # Add public version info so "Get Info" works. + plist['CFBundleShortVersionString'] = full_version + + # Honor the 429496.72.95 limit. The maximum comes from splitting 2^32 - 1 + # into 6, 2, 2 digits. The limitation was present in Tiger, but it could + # have been fixed in later OS release, but hasn't been tested (it's easy + # enough to find out with "lsregister -dump). + # http://lists.apple.com/archives/carbon-dev/2006/Jun/msg00139.html + # BUILD will always be an increasing value, so BUILD_PATH gives us something + # unique that meetings what LS wants. + plist['CFBundleVersion'] = bundle_version + + # Return with no error. + return True + + +def _DoSCMKeys(plist, add_keys): + """Adds the SCM information, visible in about:version, to property list. If + |add_keys| is True, it will insert the keys, otherwise it will remove them.""" + scm_revision = None + if add_keys: + # Pull in the Chrome revision number. + VERSION_TOOL = os.path.join(TOP, 'chrome/tools/build/version.py') + LASTCHANGE_FILE = os.path.join(TOP, 'build/util/LASTCHANGE') + (stdout, retval) = _GetOutput([VERSION_TOOL, '-f', LASTCHANGE_FILE, '-t', + '@LASTCHANGE@']) + if retval: + return False + scm_revision = stdout.rstrip() + + # See if the operation failed. + _RemoveKeys(plist, 'SCMRevision') + if scm_revision != None: + plist['SCMRevision'] = scm_revision + elif add_keys: + print >>sys.stderr, 'Could not determine SCM revision. This may be OK.' + + return True + + +def _DoPDFKeys(plist, add_keys): + """Adds PDF support to the document types list. If add_keys is True, it will + add the type information dictionary. If it is False, it will remove it if + present.""" + + PDF_FILE_EXTENSION = 'pdf' + + def __AddPDFKeys(sub_plist): + """Writes the keys into a sub-dictionary of the plist.""" + sub_plist['CFBundleTypeExtensions'] = [PDF_FILE_EXTENSION] + sub_plist['CFBundleTypeIconFile'] = 'document.icns' + sub_plist['CFBundleTypeMIMETypes'] = 'application/pdf' + sub_plist['CFBundleTypeName'] = 'PDF Document' + sub_plist['CFBundleTypeRole'] = 'Viewer' + + DOCUMENT_TYPES_KEY = 'CFBundleDocumentTypes' + + # First get the list of document types, creating it if necessary. + try: + extensions = plist[DOCUMENT_TYPES_KEY] + except KeyError: + # If this plist doesn't have a type dictionary, create one if set to add the + # keys. If not, bail. + if not add_keys: + return + extensions = plist[DOCUMENT_TYPES_KEY] = [] + + # Loop over each entry in the list, looking for one that handles PDF types. + for i, ext in enumerate(extensions): + # If an entry for .pdf files is found... + if 'CFBundleTypeExtensions' not in ext: + continue + if PDF_FILE_EXTENSION in ext['CFBundleTypeExtensions']: + if add_keys: + # Overwrite the existing keys with new ones. + __AddPDFKeys(ext) + else: + # Otherwise, delete the entry entirely. + del extensions[i] + return + + # No PDF entry exists. If one needs to be added, do so now. + if add_keys: + pdf_entry = {} + __AddPDFKeys(pdf_entry) + extensions.append(pdf_entry) + + +def _AddBreakpadKeys(plist, branding): + """Adds the Breakpad keys. This must be called AFTER _AddVersionKeys() and + also requires the |branding| argument.""" + plist['BreakpadReportInterval'] = '3600' # Deliberately a string. + plist['BreakpadProduct'] = '%s_Mac' % branding + plist['BreakpadProductDisplay'] = branding + plist['BreakpadVersion'] = plist['CFBundleShortVersionString'] + # These are both deliberately strings and not boolean. + plist['BreakpadSendAndExit'] = 'YES' + plist['BreakpadSkipConfirm'] = 'YES' + + +def _RemoveBreakpadKeys(plist): + """Removes any set Breakpad keys.""" + _RemoveKeys(plist, + 'BreakpadURL', + 'BreakpadReportInterval', + 'BreakpadProduct', + 'BreakpadProductDisplay', + 'BreakpadVersion', + 'BreakpadSendAndExit', + 'BreakpadSkipConfirm') + + +def _AddKeystoneKeys(plist, bundle_identifier): + """Adds the Keystone keys. This must be called AFTER _AddVersionKeys() and + also requires the |bundle_identifier| argument (com.example.product).""" + plist['KSVersion'] = plist['CFBundleShortVersionString'] + plist['KSProductID'] = bundle_identifier + plist['KSUpdateURL'] = 'https://tools.google.com/service/update2' + + +def _RemoveKeystoneKeys(plist): + """Removes any set Keystone keys.""" + _RemoveKeys(plist, + 'KSVersion', + 'KSProductID', + 'KSUpdateURL') + + +def Main(argv): + parser = optparse.OptionParser('%prog [options]') + parser.add_option('--breakpad', dest='use_breakpad', action='store', + type='int', default=False, help='Enable Breakpad [1 or 0]') + parser.add_option('--breakpad_uploads', dest='breakpad_uploads', + action='store', type='int', default=False, + help='Enable Breakpad\'s uploading of crash dumps [1 or 0]') + parser.add_option('--keystone', dest='use_keystone', action='store', + type='int', default=False, help='Enable Keystone [1 or 0]') + parser.add_option('--scm', dest='add_scm_info', action='store', type='int', + default=True, help='Add SCM metadata [1 or 0]') + parser.add_option('--pdf', dest='add_pdf_support', action='store', type='int', + default=False, help='Add PDF file handler support [1 or 0]') + parser.add_option('--branding', dest='branding', action='store', + type='string', default=None, help='The branding of the binary') + parser.add_option('--bundle_id', dest='bundle_identifier', + action='store', type='string', default=None, + help='The bundle id of the binary') + parser.add_option('--version', dest='version', action='store', type='string', + default=None, help='The version string [major.minor.build.patch]') + (options, args) = parser.parse_args(argv) + + if len(args) > 0: + print >>sys.stderr, parser.get_usage() + return 1 + + # Read the plist into its parsed format. + DEST_INFO_PLIST = os.path.join(env['TARGET_BUILD_DIR'], env['INFOPLIST_PATH']) + plist = plistlib.readPlist(DEST_INFO_PLIST) + + # Insert the product version. + if not _AddVersionKeys(plist, version=options.version): + return 2 + + # Add Breakpad if configured to do so. + if options.use_breakpad: + if options.branding is None: + print >>sys.stderr, 'Use of Breakpad requires branding.' + return 1 + _AddBreakpadKeys(plist, options.branding) + if options.breakpad_uploads: + plist['BreakpadURL'] = 'https://clients2.google.com/cr/report' + else: + # This allows crash dumping to a file without uploading the + # dump, for testing purposes. Breakpad does not recognise + # "none" as a special value, but this does stop crash dump + # uploading from happening. We need to specify something + # because if "BreakpadURL" is not present, Breakpad will not + # register its crash handler and no crash dumping will occur. + plist['BreakpadURL'] = 'none' + else: + _RemoveBreakpadKeys(plist) + + # Only add Keystone in Release builds. + if options.use_keystone and env['CONFIGURATION'] == 'Release': + if options.bundle_identifier is None: + print >>sys.stderr, 'Use of Keystone requires the bundle id.' + return 1 + _AddKeystoneKeys(plist, options.bundle_identifier) + else: + _RemoveKeystoneKeys(plist) + + # Adds or removes any SCM keys. + if not _DoSCMKeys(plist, options.add_scm_info): + return 3 + + # Adds or removes the PDF file handler entry. + _DoPDFKeys(plist, options.add_pdf_support) + + # Now that all keys have been mutated, rewrite the file. + temp_info_plist = tempfile.NamedTemporaryFile() + plistlib.writePlist(plist, temp_info_plist.name) + + # Info.plist will work perfectly well in any plist format, but traditionally + # applications use xml1 for this, so convert it to ensure that it's valid. + proc = subprocess.Popen(['plutil', '-convert', 'xml1', '-o', DEST_INFO_PLIST, + temp_info_plist.name]) + proc.wait() + return proc.returncode + + +if __name__ == '__main__': + sys.exit(Main(sys.argv[1:])) diff --git a/build/mac/verify_no_objc.sh b/build/mac/verify_no_objc.sh new file mode 100755 index 0000000000..955f9befff --- /dev/null +++ b/build/mac/verify_no_objc.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +# Copyright (c) 2011 The Chromium 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 makes sure that no __OBJC,__image_info section appears in the +# executable file built by the Xcode target that runs the script. If such a +# section appears, the script prints an error message and exits nonzero. +# +# Why is this important? +# +# On 10.5, there's a bug in CFBundlePreflightExecutable that causes it to +# crash when operating in an executable that has not loaded at its default +# address (that is, when it's a position-independent executable with the +# MH_PIE bit set in its mach_header) and the executable has an +# __OBJC,__image_info section. See http://crbug.com/88697. +# +# Chrome's main executables don't use any Objective-C at all, and don't need +# to carry this section around. Not linking them as Objective-C when they +# don't need it anyway saves about 4kB in the linked executable, although most +# of that 4kB is just filled with zeroes. +# +# This script makes sure that nobody goofs and accidentally introduces these +# sections into the main executables. + +set -eu + +otool="${DEVELOPER_BIN_DIR:-/usr/bin}/otool" +executable="${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}" + +if "${otool}" -arch i386 -o "${executable}" | grep -q '^Contents.*section$'; \ +then + echo "${0}: ${executable} has an __OBJC,__image_info section" 2>&1 + exit 1 +fi + +if [[ ${PIPESTATUS[0]} -ne 0 ]]; then + echo "${0}: otool failed" 2>&1 + exit 1 +fi + +exit 0 diff --git a/build/nocompile.gypi b/build/nocompile.gypi new file mode 100644 index 0000000000..f9021ae379 --- /dev/null +++ b/build/nocompile.gypi @@ -0,0 +1,96 @@ +# Copyright (c) 2011 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# This file is meant to be included into an target to create a unittest that +# invokes a set of no-compile tests. A no-compile test is a test that asserts +# a particular construct will not compile. +# +# Also see: +# http://dev.chromium.org/developers/testing/no-compile-tests +# +# To use this, create a gyp target with the following form: +# { +# 'target_name': 'my_module_nc_unittests', +# 'type': 'executable', +# 'sources': [ +# 'nc_testset_1.nc', +# 'nc_testset_2.nc', +# ], +# 'includes': ['path/to/this/gypi/file'], +# } +# +# The .nc files are C++ files that contain code we wish to assert will not +# compile. Each individual test case in the file should be put in its own +# #ifdef section. The expected output should be appended with a C++-style +# comment that has a python list of regular expressions. This will likely +# be greater than 80-characters. Giving a solid expected output test is +# important so that random compile failures do not cause the test to pass. +# +# Example .nc file: +# +# #if defined(TEST_NEEDS_SEMICOLON) // [r"expected ',' or ';' at end of input"] +# +# int a = 1 +# +# #elif defined(TEST_NEEDS_CAST) // [r"invalid conversion from 'void*' to 'char*'"] +# +# void* a = NULL; +# char* b = a; +# +# #endif +# +# If we needed disable TEST_NEEDS_SEMICOLON, then change the define to: +# +# DISABLE_TEST_NEEDS_SEMICOLON +# TEST_NEEDS_CAST +# +# The lines above are parsed by a regexp so avoid getting creative with the +# formatting or ifdef logic; it will likely just not work. +# +# Implementation notes: +# The .nc files are actually processed by a python script which executes the +# compiler and generates a .cc file that is empty on success, or will have a +# series of #error lines on failure, and a set of trivially passing gunit +# TEST() functions on success. This allows us to fail at the compile step when +# something goes wrong, and know during the unittest run that the test was at +# least processed when things go right. + +{ + # TODO(awong): Disabled until http://crbug.com/105388 is resolved. + 'sources/': [['exclude', '\\.nc$']], + 'conditions': [ + [ 'OS=="linux" and clang==0', { + 'rules': [ + { + 'variables': { + 'nocompile_driver': '<(DEPTH)/tools/nocompile_driver.py', + 'nc_result_path': ('<(INTERMEDIATE_DIR)/<(module_dir)/' + '<(RULE_INPUT_ROOT)_nc.cc'), + }, + 'rule_name': 'run_nocompile', + 'extension': 'nc', + 'inputs': [ + '<(nocompile_driver)', + ], + 'outputs': [ + '<(nc_result_path)' + ], + 'action': [ + 'python', + '<(nocompile_driver)', + '4', # number of compilers to invoke in parallel. + '<(RULE_INPUT_PATH)', + '-Wall -Werror -Wfatal-errors -I<(DEPTH)', + '<(nc_result_path)', + ], + 'message': 'Generating no compile results for <(RULE_INPUT_PATH)', + 'process_outputs_as_sources': 1, + }, + ], + }, { + 'sources/': [['exclude', '\\.nc$']] + }], # 'OS=="linux" and clang=="0"' + ], +} + diff --git a/build/output_dll_copy.rules b/build/output_dll_copy.rules new file mode 100644 index 0000000000..c6e905131d --- /dev/null +++ b/build/output_dll_copy.rules @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/build/precompile.cc b/build/precompile.cc new file mode 100644 index 0000000000..db1ef6dfe5 --- /dev/null +++ b/build/precompile.cc @@ -0,0 +1,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. + +// Precompiled header generator for Windows builds. No include is needed +// in this file as the PCH include is forced via the "Forced Include File" +// flag in the projects generated by GYP. diff --git a/build/precompile.h b/build/precompile.h new file mode 100644 index 0000000000..ab678cab27 --- /dev/null +++ b/build/precompile.h @@ -0,0 +1,110 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Precompiled header for Chromium project on Windows, not used by +// other build configurations. Using precompiled headers speeds the +// build up significantly, around 1/4th on VS 2010 on an HP Z600 with 12 +// GB of memory. +// +// Numeric comments beside includes are the number of times they were +// included under src/chrome/browser on 2011/8/20, which was used as a +// baseline for deciding what to include in the PCH. Includes without +// a numeric comment are generally included at least 5 times. It may +// be possible to tweak the speed of the build by commenting out or +// removing some of the less frequently used headers. + +#if defined(BUILD_PRECOMPILE_H_) +#error You shouldn't include the precompiled header file more than once. +#endif + +#define BUILD_PRECOMPILE_H_ + +// The Windows header needs to come before almost all the other +// Windows-specific headers. +#include +#include +#include +#include // 4 +#include // 2 + +// Defines in atlbase.h cause conflicts; if we could figure out how +// this family of headers can be included in the PCH, it might speed +// up the build as several of them are used frequently. +/* +#include +#include +#include +#include // 2 +#include // 2 +#include // 2 +#include // 1 +#include // 1 +#include // 2 +*/ + +// Objbase.h and other files that rely on it bring in [ #define +// interface struct ] which can cause problems in a multi-platform +// build like Chrome's. #undef-ing it does not work as there are +// currently 118 targets that break if we do this, so leaving out of +// the precompiled header for now. +//#include // 2 +//#include // 3 +//#include // 2 +//#include // 2 +//#include // 1 +//#include // 1 +//#include // 2 +//#include // 1 +//#include // 1 +//#include // 2 +//#include // 2 +//#include // 2 +//#include // 1 +//#include // 1 +//#include // 4 +//#include // 2 + +// Caused other conflicts in addition to the 'interface' issue above. +// #include + +#include +#include +#include // 4 +#include +#include // 1 +#include +#include // 1 +#include +#include +#include +#include +#include // 4 + +#include +#include // 3 +#include +#include +#include // 3 +#include // 2 +#include +#include +#include // 3 +#include +#include // 2 +#include // 2 +#include +#include +#include +#include +#include // 2 +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intsafe_workaround.h" diff --git a/build/protoc.gypi b/build/protoc.gypi new file mode 100644 index 0000000000..52fb8a2b60 --- /dev/null +++ b/build/protoc.gypi @@ -0,0 +1,124 @@ +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# This file is meant to be included into a target to provide a rule +# to invoke protoc in a consistent manner. For Java-targets, see +# protoc_java.gypi. +# +# To use this, create a gyp target with the following form: +# { +# 'target_name': 'my_proto_lib', +# 'type': 'static_library', +# 'sources': [ +# 'foo.proto', +# 'bar.proto', +# ], +# 'variables': { +# # Optional, see below: 'proto_in_dir': '.' +# 'proto_out_dir': 'dir/for/my_proto_lib' +# }, +# 'includes': ['path/to/this/gypi/file'], +# } +# If necessary, you may add normal .cc files to the sources list or other gyp +# dependencies. The proto headers are guaranteed to be generated before any +# source files, even within this target, are compiled. +# +# The 'proto_in_dir' variable must be the relative path to the +# directory containing the .proto files. If left out, it defaults to '.'. +# +# The 'proto_out_dir' variable specifies the path suffix that output +# files are generated under. Targets that gyp-depend on my_proto_lib +# will be able to include the resulting proto headers with an include +# like: +# #include "dir/for/my_proto_lib/foo.pb.h" +# +# If you need to add an EXPORT macro to a protobuf's c++ header, set the +# 'cc_generator_options' variable with the value: 'dllexport_decl=FOO_EXPORT:' +# e.g. 'dllexport_decl=BASE_EXPORT:' +# +# It is likely you also need to #include a file for the above EXPORT macro to +# work. You can do so with the 'cc_include' variable. +# e.g. 'base/base_export.h' +# +# Implementation notes: +# A proto_out_dir of foo/bar produces +# <(SHARED_INTERMEDIATE_DIR)/protoc_out/foo/bar/{file1,file2}.pb.{cc,h} +# <(SHARED_INTERMEDIATE_DIR)/pyproto/foo/bar/{file1,file2}_pb2.py + +{ + 'variables': { + 'protoc_wrapper': '<(DEPTH)/tools/protoc_wrapper/protoc_wrapper.py', + 'cc_dir': '<(SHARED_INTERMEDIATE_DIR)/protoc_out/<(proto_out_dir)', + 'py_dir': '<(PRODUCT_DIR)/pyproto/<(proto_out_dir)', + 'cc_generator_options%': '', + 'cc_include%': '', + 'proto_in_dir%': '.', + 'conditions': [ + ['use_system_protobuf==0', { + 'protoc': '<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)protoc<(EXECUTABLE_SUFFIX)', + }, { # use_system_protobuf==1 + 'protoc': '(java_out_dir), since that +# is the root directory of all the output. +# +# Implementation notes: +# A target_name of foo and proto-specified 'package' java.package.path produces: +# <(PRODUCT_DIR)/java_proto/foo/{java/package/path/}{Foo,Bar}.java +# where Foo and Bar are taken from 'java_outer_classname' of the protos. +# +# How the .jar-file is created is different than how protoc is used for other +# targets, and as such, this lives in its own file. + +{ + 'variables': { + 'protoc': '<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)protoc<(EXECUTABLE_SUFFIX)', + 'java_out_dir': '<(PRODUCT_DIR)/java_proto/<(_target_name)/src', + 'proto_in_dir%': '.', + 'stamp_file': '<(java_out_dir).stamp', + 'script': '<(DEPTH)/build/protoc_java.py', + + # The rest of the variables here are for the java.gypi include. + 'java_in_dir': '<(DEPTH)/build/android/empty', + 'generated_src_dirs': ['<(java_out_dir)'], + # Adding the |stamp_file| to |additional_input_paths| makes the actions in + # the include of java.gypi depend on the genproto_java action. + 'additional_input_paths': ['<(stamp_file)'], + }, + 'actions': [ + { + 'action_name': 'genproto_java', + 'inputs': [ + '<(script)', + '<(protoc)', + '<@(_sources)', + ], + # We do not know the names of the generated files, so we use a stamp. + 'outputs': [ + '<(stamp_file)', + ], + 'action': [ + '<(script)', + '<(protoc)', + '<(proto_in_dir)', + '<(java_out_dir)', + '<(stamp_file)', + '<@(_sources)', + ], + 'message': 'Generating Java code from <(proto_in_dir)', + }, + ], + 'dependencies': [ + '<(DEPTH)/third_party/protobuf/protobuf.gyp:protoc#host', + '<(DEPTH)/third_party/protobuf/protobuf.gyp:protobuf_lite_javalib', + ], + 'includes': [ 'java.gypi' ], +} diff --git a/build/protoc_java.py b/build/protoc_java.py new file mode 100755 index 0000000000..42e204464a --- /dev/null +++ b/build/protoc_java.py @@ -0,0 +1,56 @@ +#!/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. + +"""Generate java source files from protobufs + +Usage: + protoc_java.py {protoc} {proto_path} {java_out} {stamp_file} {proto_files} + +This is a helper file for the genproto_java action in protoc_java.gypi. + +It performs the following steps: +1. Deletes all old sources (ensures deleted classes are not part of new jars). +2. Creates source directory. +3. Generates Java files using protoc. +4. Creates a new stamp file. +""" + +import os +import shutil +import subprocess +import sys + +def main(argv): + if len(argv) < 5: + usage() + return 1 + + protoc_path, proto_path, java_out, stamp_file = argv[1:5] + proto_files = argv[5:] + + # Delete all old sources + if os.path.exists(java_out): + shutil.rmtree(java_out) + + # Create source directory + os.makedirs(java_out) + + # Generate Java files using protoc + ret = subprocess.call( + [protoc_path, '--proto_path', proto_path, '--java_out', java_out] + + proto_files) + + if ret == 0: + # Create a new stamp file + with file(stamp_file, 'a'): + os.utime(stamp_file, None) + + return ret + +def usage(): + print(__doc__); + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/build/release.gypi b/build/release.gypi new file mode 100644 index 0000000000..7595ef5a29 --- /dev/null +++ b/build/release.gypi @@ -0,0 +1,17 @@ +{ + 'conditions': [ + # Handle build types. + ['buildtype=="Dev"', { + 'includes': ['internal/release_impl.gypi'], + }], + ['buildtype=="Official"', { + 'includes': ['internal/release_impl_official.gypi'], + }], + # TODO(bradnelson): may also need: + # checksenabled + # coverage + # dom_stats + # pgo_instrument + # pgo_optimize + ], +} diff --git a/build/sanitize-mac-build-log.sed b/build/sanitize-mac-build-log.sed new file mode 100755 index 0000000000..3312eac5a8 --- /dev/null +++ b/build/sanitize-mac-build-log.sed @@ -0,0 +1,35 @@ +#!/bin/echo Use sanitize-mac-build-log.sh or sed -f + +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Use this sed script to reduce a Mac build log into something readable. + +# Drop uninformative lines. +/^distcc/d +/^Check dependencies/d +/^ setenv /d +/^ cd /d +/^make: Nothing to be done/d +/^$/d + +# Xcode prints a short "compiling foobar.o" line followed by the lengthy +# full command line. These deletions drop the command line. +\|^ /Developer/usr/bin/|d +\|^ /Developer/Library/PrivateFrameworks/DevToolsCore.framework/|d +\|^ /Developer/Library/Xcode/Plug-ins/CoreBuildTasks.xcplugin/|d + +# Drop any goma command lines as well. +\|^ .*/gomacc |d + +# And, if you've overridden something from your own bin directory, remove those +# full command lines, too. +\|^ /Users/[^/]*/bin/|d + +# There's already a nice note for bindings, don't need the command line. +\|^python scripts/rule_binding.py|d + +# Shorten the "compiling foobar.o" line. +s|^Distributed-CompileC \(.*\) normal i386 c++ com.apple.compilers.gcc.4_2| CC \1| +s|^CompileC \(.*\) normal i386 c++ com.apple.compilers.gcc.4_2| CC \1| diff --git a/build/sanitize-mac-build-log.sh b/build/sanitize-mac-build-log.sh new file mode 100755 index 0000000000..dc743fabb5 --- /dev/null +++ b/build/sanitize-mac-build-log.sh @@ -0,0 +1,6 @@ +#!/bin/sh +# 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. +sed -f `dirname "${0}"`/`basename "${0}" sh`sed + diff --git a/build/sanitize-png-files.sh b/build/sanitize-png-files.sh new file mode 100755 index 0000000000..e47508e470 --- /dev/null +++ b/build/sanitize-png-files.sh @@ -0,0 +1,445 @@ +#!/bin/bash +# 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. + +# The optimization code is based on pngslim (http://goo.gl/a0XHg) +# and executes a similar pipleline to optimize the png file size. +# The steps that require pngoptimizercl/pngrewrite/deflopt are omitted, +# but this runs all other processes, including: +# 1) various color-dependent optimizations using optipng. +# 2) optimize the number of huffman blocks. +# 3) randomize the huffman table. +# 4) Further optimize using optipng and advdef (zlib stream). +# Due to the step 3), each run may produce slightly different results. +# +# Note(oshima): In my experiment, advdef didn't reduce much. I'm keeping it +# for now as it does not take much time to run. + +readonly ALL_DIRS=" +ash/resources +ui/resources +chrome/app/theme +chrome/browser/resources +chrome/renderer/resources +webkit/glue/resources +remoting/resources +remoting/webapp +" + +# Files larger than this file size (in bytes) will +# use the optimization parameters tailored for large files. +LARGE_FILE_THRESHOLD=3000 + +# Constants used for optimization +readonly DEFAULT_MIN_BLOCK_SIZE=128 +readonly DEFAULT_LIMIT_BLOCKS=256 +readonly DEFAULT_RANDOM_TRIALS=100 +# Taken from the recommendation in the pngslim's readme.txt. +readonly LARGE_MIN_BLOCK_SIZE=1 +readonly LARGE_LIMIT_BLOCKS=2 +readonly LARGE_RANDOM_TRIALS=1 + +# Global variables for stats +TOTAL_OLD_BYTES=0 +TOTAL_NEW_BYTES=0 +TOTAL_FILE=0 +PROCESSED_FILE=0 + +declare -a THROBBER_STR=('-' '\\' '|' '/') +THROBBER_COUNT=0 + +# Show throbber character at current cursor position. +function throbber { + echo -ne "${THROBBER_STR[$THROBBER_COUNT]}\b" + let THROBBER_COUNT=($THROBBER_COUNT+1)%4 +} + +# Usage: pngout_loop ... +# Optimize the png file using pngout with the given options +# using various block split thresholds and filter types. +function pngout_loop { + local file=$1 + shift + local opts=$* + if [ $OPTIMIZE_LEVEL == 1 ]; then + for j in $(seq 0 5); do + throbber + pngout -q -k1 -s1 -f$j $opts $file + done + else + for i in 0 128 256 512; do + for j in $(seq 0 5); do + throbber + pngout -q -k1 -s1 -b$i -f$j $opts $file + done + done + fi +} + +# Usage: get_color_depth_list +# Returns the list of color depth options for current optimization level. +function get_color_depth_list { + if [ $OPTIMIZE_LEVEL == 1 ]; then + echo "-d0" + else + echo "-d1 -d2 -d4 -d8" + fi +} + +# Usage: process_grayscale +# Optimize grayscale images for all color bit depths. +# +# TODO(oshima): Experiment with -d0 w/o -c0. +function process_grayscale { + echo -n "|gray" + for opt in $(get_color_depth_list); do + pngout_loop $file -c0 $opt + done +} + +# Usage: process_grayscale_alpha +# Optimize grayscale images with alpha for all color bit depths. +function process_grayscale_alpha { + echo -n "|gray-a" + pngout_loop $file -c4 + for opt in $(get_color_depth_list); do + pngout_loop $file -c3 $opt + done +} + +# Usage: process_rgb +# Optimize rgb images with or without alpha for all color bit depths. +function process_rgb { + echo -n "|rgb" + for opt in $(get_color_depth_list); do + pngout_loop $file -c3 $opt + done + pngout_loop $file -c2 + pngout_loop $file -c6 +} + +# Usage: huffman_blocks +# Optimize the huffman blocks. +function huffman_blocks { + local file=$1 + echo -n "|huffman" + local size=$(stat -c%s $file) + local min_block_size=$DEFAULT_MIN_BLOCK_SIZE + local limit_blocks=$DEFAULT_LIMIT_BLOCKS + + if [ $size -gt $LARGE_FILE_THRESHOLD ]; then + min_block_size=$LARGE_MIN_BLOCK_SIZE + limit_blocks=$LARGE_LIMIT_BLOCKS + fi + let max_blocks=$size/$min_block_size + if [ $max_blocks -gt $limit_blocks ]; then + max_blocks=$limit_blocks + fi + + for i in $(seq 2 $max_blocks); do + throbber + pngout -q -k1 -ks -s1 -n$i $file + done +} + +# Usage: random_huffman_table_trial +# Try compressing by randomizing the initial huffman table. +# +# TODO(oshima): Try adjusting different parameters for large files to +# reduce runtime. +function random_huffman_table_trial { + echo -n "|random" + local file=$1 + local old_size=$(stat -c%s $file) + local trials_count=$DEFAULT_RANDOM_TRIALS + + if [ $old_size -gt $LARGE_FILE_THRESHOLD ]; then + trials_count=$LARGE_RANDOM_TRIALS + fi + for i in $(seq 1 $trials_count); do + throbber + pngout -q -k1 -ks -s0 -r $file + done + local new_size=$(stat -c%s $file) + if [ $new_size -lt $old_size ]; then + random_huffman_table_trial $file + fi +} + +# Usage: final_comprssion +# Further compress using optipng and advdef. +# TODO(oshima): Experiment with 256. +function final_compression { + echo -n "|final" + local file=$1 + if [ $OPTIMIZE_LEVEL == 2 ]; then + for i in 32k 16k 8k 4k 2k 1k 512; do + throbber + optipng -q -nb -nc -zw$i -zc1-9 -zm1-9 -zs0-3 -f0-5 $file + done + fi + for i in $(seq 1 4); do + throbber + advdef -q -z -$i $file + done + echo -ne "\r" +} + +# Usage: get_color_type +# Returns the color type name of the png file. Here is the list of names +# for each color type codes. +# 0 : grayscale +# 2 : RGB +# 3 : colormap +# 4 : gray+alpha +# 6 : RGBA +# See http://en.wikipedia.org/wiki/Portable_Network_Graphics#Color_depth +# for details about the color type code. +function get_color_type { + local file=$1 + echo $(file $file | awk -F, '{print $3}' | awk '{print $2}') +} + +# Usage: optimize_size +# Performs png file optimization. +function optimize_size { + tput el + local file=$1 + echo -n "$file " + + advdef -q -z -4 $file + + pngout -q -s4 -c0 -force $file $file.tmp.png + if [ -f $file.tmp.png ]; then + rm $file.tmp.png + process_grayscale $file + process_grayscale_alpha $file + else + pngout -q -s4 -c4 -force $file $file.tmp.png + if [ -f $file.tmp.png ]; then + rm $file.tmp.png + process_grayscale_alpha $file + else + process_rgb $file + fi + fi + + echo -n "|filter" + local old_color_type=$(get_color_type $file) + optipng -q -zc9 -zm8 -zs0-3 -f0-5 $file -out $file.tmp.png + local new_color_type=$(get_color_type $file.tmp.png) + # optipng may corrupt a png file when reducing the color type + # to grayscale/grayscale+alpha. Just skip such cases until + # the bug is fixed. See crbug.com/174505, crbug.com/174084. + # The issue is reported in + # https://sourceforge.net/tracker/?func=detail&aid=3603630&group_id=151404&atid=780913 + if [[ $old_color_type == "RGBA" && $new_color_type =~ gray.* ]] ; then + rm $file.tmp.png + echo -n "[skip opting]" + else + mv $file.tmp.png $file + fi + pngout -q -k1 -s1 $file + + huffman_blocks $file + + # TODO(oshima): Experiment with strategy 1. + echo -n "|strategy" + if [ $OPTIMIZE_LEVEL == 2 ]; then + for i in 3 2 0; do + pngout -q -k1 -ks -s$i $file + done + else + pngout -q -k1 -ks -s1 $file + fi + + if [ $OPTIMIZE_LEVEL == 2 ]; then + random_huffman_table_trial $file + fi + + final_compression $file +} + +# Usage: process_file +function process_file { + local file=$1 + local name=$(basename $file) + # -rem alla removes all ancillary chunks except for tRNS + pngcrush -d $TMP_DIR -brute -reduce -rem alla $file > /dev/null + + if [ $OPTIMIZE_LEVEL != 0 ]; then + optimize_size $TMP_DIR/$name + fi +} + +# Usage: sanitize_file +function sanitize_file { + local file=$1 + local name=$(basename $file) + local old=$(stat -c%s $file) + local tmp_file=$TMP_DIR/$name + + process_file $file + + local new=$(stat -c%s $tmp_file) + let diff=$old-$new + let percent=($diff*100)/$old + let TOTAL_FILE+=1 + + tput el + if [ $new -lt $old ]; then + echo -ne "$file : $old => $new ($diff bytes : $percent %)\n" + mv "$tmp_file" "$file" + let TOTAL_OLD_BYTES+=$old + let TOTAL_NEW_BYTES+=$new + let PROCESSED_FILE+=1 + else + if [ $OPTIMIZE_LEVEL == 0 ]; then + echo -ne "$file : skipped\r" + fi + rm $tmp_file + fi +} + +function sanitize_dir { + local dir=$1 + for f in $(find $dir -name "*.png"); do + if $using_cygwin ; then + sanitize_file $(cygpath -w $f) + else + sanitize_file $f + fi + done +} + +function install_if_not_installed { + local program=$1 + local package=$2 + which $program > /dev/null 2>&1 + if [ "$?" != "0" ]; then + if $using_cygwin ; then + echo "Couldn't find $program. Please run setup.exe and install the $package package." + exit 1 + else + read -p "Couldn't find $program. Do you want to install? (y/n)" + [ "$REPLY" == "y" ] && sudo apt-get install $package + [ "$REPLY" == "y" ] || exit + fi + fi +} + +function fail_if_not_installed { + local program=$1 + local url=$2 + which $program > /dev/null 2>&1 + if [ $? != 0 ]; then + echo "Couldn't find $program. Please download and install it from $url ." + exit 1 + fi +} + +function show_help { + local program=$(basename $0) + echo \ +"Usage: $program [options] dir ... + +$program is a utility to reduce the size of png files by removing +unnecessary chunks and compressing the image. + +Options: + -o Specify optimization level: (default is 1) + 0 Just run pngcrush. It removes unnecessary chunks and perform basic + optimization on the encoded data. + 1 Optimize png files using pngout/optipng and advdef. This can further + reduce addtional 5~30%. This is the default level. + 2 Aggressively optimize the size of png files. This may produce + addtional 1%~5% reduction. Warning: this is *VERY* + slow and can take hours to process all files. + -h Print this help text." + exit 1 +} + +if [ ! -e ../.gclient ]; then + echo "$0 must be run in src directory" + exit 1 +fi + +if [ "$(expr substr $(uname -s) 1 6)" == "CYGWIN" ]; then + using_cygwin=true +else + using_cygwin=false +fi + +OPTIMIZE_LEVEL=1 +# Parse options +while getopts o:h opts +do + case $opts in + o) + if [[ ! "$OPTARG" =~ [012] ]]; then + show_help + fi + OPTIMIZE_LEVEL=$OPTARG + [ "$1" == "-o" ] && shift + shift;; + [h?]) + show_help;; + esac +done + +# Make sure we have all necessary commands installed. +install_if_not_installed pngcrush pngcrush +if [ $OPTIMIZE_LEVEL == 2 ]; then + install_if_not_installed optipng optipng + + if $using_cygwin ; then + fail_if_not_installed advdef "http://advancemame.sourceforge.net/comp-readme.html" + else + install_if_not_installed advdef advancecomp + fi + + if $using_cygwin ; then + pngout_url="http://www.advsys.net/ken/utils.htm" + else + pngout_url="http://www.jonof.id.au/kenutils" + fi + fail_if_not_installed pngout $pngout_url +fi + +# Create tmp directory for crushed png file. +TMP_DIR=$(mktemp -d) +if $using_cygwin ; then + TMP_DIR=$(cygpath -w $TMP_DIR) +fi + +# Make sure we cleanup temp dir +trap "rm -rf $TMP_DIR" EXIT + +# If no directories are specified, sanitize all directories. +DIRS=$@ +set ${DIRS:=$ALL_DIRS} + +echo "Optimize level=$OPTIMIZE_LEVEL" +for d in $DIRS; do + if $using_cygwin ; then + d=$(cygpath -w $d) + fi + echo "Sanitizing png files in $d" + sanitize_dir $d + echo +done + +# Print the results. +if [ $PROCESSED_FILE == 0 ]; then + echo "Did not find any files (out of $TOTAL_FILE files)" \ + "that could be optimized" \ + "in $(date -u -d @$SECONDS +%T)s" +else + let diff=$TOTAL_OLD_BYTES-$TOTAL_NEW_BYTES + let percent=$diff*100/$TOTAL_OLD_BYTES + echo "Processed $PROCESSED_FILE files (out of $TOTAL_FILE files)" \ + "in $(date -u -d @$SECONDS +%T)s" + echo "Result : $TOTAL_OLD_BYTES => $TOTAL_NEW_BYTES bytes" \ + "($diff bytes : $percent %)" +fi diff --git a/build/sanitize-win-build-log.sed b/build/sanitize-win-build-log.sed new file mode 100755 index 0000000000..ce5165422d --- /dev/null +++ b/build/sanitize-win-build-log.sed @@ -0,0 +1,18 @@ +#!/bin/echo Use sanitize-win-build-log.sh or sed -f + +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Use this sed script to reduce a Windows build log into something +# machine-parsable. + +# Drop uninformative lines. +/The operation completed successfully\./d + +# Drop parallelization indicators on lines. +s/^[0-9]\+>// + +# Shorten bindings generation lines +s/^.*"\(perl\|[^"]\+perl\.exe\)".*deprecated_generate_bindings\.pl".*\("[^"]\+\.idl"\).*$/ deprecated_generate_bindings \2/ +s/^.*"python".*idl_compiler\.py".*\("[^"]\+\.idl"\).*$/ idl_compiler \1/ diff --git a/build/sanitize-win-build-log.sh b/build/sanitize-win-build-log.sh new file mode 100755 index 0000000000..dc743fabb5 --- /dev/null +++ b/build/sanitize-win-build-log.sh @@ -0,0 +1,6 @@ +#!/bin/sh +# 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. +sed -f `dirname "${0}"`/`basename "${0}" sh`sed + diff --git a/build/shim_headers.gypi b/build/shim_headers.gypi new file mode 100644 index 0000000000..4291468de1 --- /dev/null +++ b/build/shim_headers.gypi @@ -0,0 +1,53 @@ +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# This file is meant to be included into a target to handle shim headers +# in a consistent manner. To use this the following variables need to be +# defined: +# headers_root_path: string: path to directory containing headers +# header_filenames: list: list of header file names + +{ + 'variables': { + 'shim_headers_path': '<(SHARED_INTERMEDIATE_DIR)/shim_headers/<(_target_name)/<(_toolset)', + 'shim_generator_additional_args%': [], + }, + 'include_dirs++': [ + '<(shim_headers_path)', + ], + 'all_dependent_settings': { + 'include_dirs+++': [ + '<(shim_headers_path)', + ], + 'include_dirs++++': [ + '<(shim_headers_path)', + ], + }, + 'actions': [ + { + 'variables': { + 'generator_path': '<(DEPTH)/tools/generate_shim_headers/generate_shim_headers.py', + 'generator_args': [ + '--headers-root', '<(headers_root_path)', + '--output-directory', '<(shim_headers_path)', + '<@(shim_generator_additional_args)', + '<@(header_filenames)', + ], + }, + 'action_name': 'generate_<(_target_name)_shim_headers', + 'inputs': [ + '<(generator_path)', + ], + 'outputs': [ + '@(library_dexed_jars_paths)', + ], + 'outputs': [ + '<(output_dex_path)', + ], + 'action': [ + 'python', '<(DEPTH)/build/android/gyp/dex.py', + '--dex-path=<(output_dex_path)', + '--android-sdk-tools=<(android_sdk_tools)', + + # TODO(newt): remove this once http://crbug.com/177552 is fixed in ninja. + '--ignore=>!(echo \'>(_inputs)\' | md5sum)', + + '>@(library_dexed_jars_paths)', + ], + }, + ], +} diff --git a/build/update-linux-sandbox.sh b/build/update-linux-sandbox.sh new file mode 100755 index 0000000000..ebf8c105a5 --- /dev/null +++ b/build/update-linux-sandbox.sh @@ -0,0 +1,75 @@ +#!/bin/sh + +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +BUILDTYPE="${BUILDTYPE:-Debug}" +CHROME_SRC_DIR="${CHROME_SRC_DIR:-$(dirname -- $(readlink -fn -- "$0"))/..}" +CHROME_OUT_DIR="${CHROME_SRC_DIR}/out/${BUILDTYPE}" +CHROME_SANDBOX_BUILD_PATH="${CHROME_OUT_DIR}/chrome_sandbox" +CHROME_SANDBOX_INST_PATH="/usr/local/sbin/chrome-devel-sandbox" +CHROME_SANDBOX_INST_DIR=$(dirname -- "$CHROME_SANDBOX_INST_PATH") + +TARGET_DIR_TYPE=$(stat -f -c %t -- "${CHROME_SANDBOX_INST_DIR}" 2>/dev/null) +if [ $? -ne 0 ]; then + echo "Could not get status of ${CHROME_SANDBOX_INST_DIR}" + exit 1 +fi + +# Make sure the path is not on NFS. +if [ "${TARGET_DIR_TYPE}" = "6969" ]; then + echo "Please make sure ${CHROME_SANDBOX_INST_PATH} is not on NFS!" + exit 1 +fi + +installsandbox() { + echo "(using sudo so you may be asked for your password)" + sudo -- cp "${CHROME_SANDBOX_BUILD_PATH}" \ + "${CHROME_SANDBOX_INST_PATH}" && + sudo -- chown root:root "${CHROME_SANDBOX_INST_PATH}" && + sudo -- chmod 4755 "${CHROME_SANDBOX_INST_PATH}" + return $? +} + +if [ ! -d "${CHROME_OUT_DIR}" ]; then + echo -n "${CHROME_OUT_DIR} does not exist. Use \"BUILDTYPE=Release ${0}\" " + echo "If you are building in Release mode" + exit 1 +fi + +if [ ! -f "${CHROME_SANDBOX_BUILD_PATH}" ]; then + echo -n "Could not find ${CHROME_SANDBOX_BUILD_PATH}, " + echo "please make sure you build the chrome_sandbox target" + exit 1 +fi + +if [ ! -f "${CHROME_SANDBOX_INST_PATH}" ]; then + echo -n "Could not find ${CHROME_SANDBOX_INST_PATH}, " + echo "installing it now." + installsandbox +fi + +if [ ! -f "${CHROME_SANDBOX_INST_PATH}" ]; then + echo "Failed to install ${CHROME_SANDBOX_INST_PATH}" + exit 1 +fi + +CURRENT_API=$("${CHROME_SANDBOX_BUILD_PATH}" --get-api) +INSTALLED_API=$("${CHROME_SANDBOX_INST_PATH}" --get-api) + +if [ "${CURRENT_API}" != "${INSTALLED_API}" ]; then + echo "Your installed setuid sandbox is too old, installing it now." + if ! installsandbox; then + echo "Failed to install ${CHROME_SANDBOX_INST_PATH}" + exit 1 + fi +else + echo "Your setuid sandbox is up to date" + if [ "${CHROME_DEVEL_SANDBOX}" != "${CHROME_SANDBOX_INST_PATH}" ]; then + echo -n "Make sure you have \"export " + echo -n "CHROME_DEVEL_SANDBOX=${CHROME_SANDBOX_INST_PATH}\" " + echo "somewhere in your .bashrc" + echo "This variable is currently: ${CHROME_DEVEL_SANDBOX:-empty}" + fi +fi diff --git a/build/util/LASTCHANGE b/build/util/LASTCHANGE new file mode 100644 index 0000000000..3442f10ff2 --- /dev/null +++ b/build/util/LASTCHANGE @@ -0,0 +1 @@ +LASTCHANGE=217677 diff --git a/build/util/LASTCHANGE.blink b/build/util/LASTCHANGE.blink new file mode 100644 index 0000000000..dbf0c3a783 --- /dev/null +++ b/build/util/LASTCHANGE.blink @@ -0,0 +1 @@ +LASTCHANGE=156106 diff --git a/build/util/lastchange.py b/build/util/lastchange.py new file mode 100755 index 0000000000..3c1ce28e28 --- /dev/null +++ b/build/util/lastchange.py @@ -0,0 +1,236 @@ +#!/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. + +""" +lastchange.py -- Chromium revision fetching utility. +""" + +import re +import optparse +import os +import subprocess +import sys + +_GIT_SVN_ID_REGEX = re.compile(r'.*git-svn-id:\s*([^@]*)@([0-9]+)', re.DOTALL) + +class VersionInfo(object): + def __init__(self, url, revision): + self.url = url + self.revision = revision + + +def FetchSVNRevision(directory, svn_url_regex): + """ + Fetch the Subversion branch and revision for a given directory. + + Errors are swallowed. + + Returns: + A VersionInfo object or None on error. + """ + try: + proc = subprocess.Popen(['svn', 'info'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=directory, + shell=(sys.platform=='win32')) + except OSError: + # command is apparently either not installed or not executable. + return None + if not proc: + return None + + attrs = {} + for line in proc.stdout: + line = line.strip() + if not line: + continue + key, val = line.split(': ', 1) + attrs[key] = val + + try: + match = svn_url_regex.search(attrs['URL']) + if match: + url = match.group(2) + else: + url = '' + revision = attrs['Revision'] + except KeyError: + return None + + return VersionInfo(url, revision) + + +def RunGitCommand(directory, command): + """ + Launches git subcommand. + + Errors are swallowed. + + Returns: + A process object or None. + """ + command = ['git'] + command + # Force shell usage under cygwin. This is a workaround for + # mysterious loss of cwd while invoking cygwin's git. + # We can't just pass shell=True to Popen, as under win32 this will + # cause CMD to be used, while we explicitly want a cygwin shell. + if sys.platform == 'cygwin': + command = ['sh', '-c', ' '.join(command)] + try: + proc = subprocess.Popen(command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=directory, + shell=(sys.platform=='win32')) + return proc + except OSError: + return None + + +def FetchGitRevision(directory): + """ + Fetch the Git hash for a given directory. + + Errors are swallowed. + + Returns: + A VersionInfo object or None on error. + """ + proc = RunGitCommand(directory, ['rev-parse', 'HEAD']) + if proc: + output = proc.communicate()[0].strip() + if proc.returncode == 0 and output: + return VersionInfo('git', output[:7]) + return None + + +def FetchGitSVNURLAndRevision(directory, svn_url_regex): + """ + Fetch the Subversion URL and revision through Git. + + Errors are swallowed. + + Returns: + A tuple containing the Subversion URL and revision. + """ + proc = RunGitCommand(directory, ['log', '-1', + '--grep=git-svn-id', '--format=%b']) + if proc: + output = proc.communicate()[0].strip() + if proc.returncode == 0 and output: + # Extract the latest SVN revision and the SVN URL. + # The target line is the last "git-svn-id: ..." line like this: + # git-svn-id: svn://svn.chromium.org/chrome/trunk/src@85528 0039d316.... + match = _GIT_SVN_ID_REGEX.search(output) + if match: + revision = match.group(2) + url_match = svn_url_regex.search(match.group(1)) + if url_match: + url = url_match.group(2) + else: + url = '' + return url, revision + return None, None + + +def FetchGitSVNRevision(directory, svn_url_regex): + """ + Fetch the Git-SVN identifier for the local tree. + + Errors are swallowed. + """ + url, revision = FetchGitSVNURLAndRevision(directory, svn_url_regex) + if url and revision: + return VersionInfo(url, revision) + return None + + +def FetchVersionInfo(default_lastchange, directory=None, + directory_regex_prior_to_src_url='chrome|blink|svn'): + """ + Returns the last change (in the form of a branch, revision tuple), + from some appropriate revision control system. + """ + svn_url_regex = re.compile( + r'.*/(' + directory_regex_prior_to_src_url + r')(/.*)') + + version_info = (FetchSVNRevision(directory, svn_url_regex) or + FetchGitSVNRevision(directory, svn_url_regex) or + FetchGitRevision(directory)) + if not version_info: + if default_lastchange and os.path.exists(default_lastchange): + revision = open(default_lastchange, 'r').read().strip() + version_info = VersionInfo(None, revision) + else: + version_info = VersionInfo(None, None) + return version_info + + +def WriteIfChanged(file_name, contents): + """ + Writes the specified contents to the specified file_name + iff the contents are different than the current contents. + """ + try: + old_contents = open(file_name, 'r').read() + except EnvironmentError: + pass + else: + if contents == old_contents: + return + os.unlink(file_name) + open(file_name, 'w').write(contents) + + +def main(argv=None): + if argv is None: + argv = sys.argv + + parser = optparse.OptionParser(usage="lastchange.py [options]") + parser.add_option("-d", "--default-lastchange", metavar="FILE", + help="default last change input FILE") + parser.add_option("-o", "--output", metavar="FILE", + help="write last change to FILE") + parser.add_option("--revision-only", action='store_true', + help="just print the SVN revision number") + parser.add_option("-s", "--source-dir", metavar="DIR", + help="use repository in the given directory") + opts, args = parser.parse_args(argv[1:]) + + out_file = opts.output + + while len(args) and out_file is None: + if out_file is None: + out_file = args.pop(0) + if args: + sys.stderr.write('Unexpected arguments: %r\n\n' % args) + parser.print_help() + sys.exit(2) + + if opts.source_dir: + src_dir = opts.source_dir + else: + src_dir = os.path.dirname(os.path.abspath(__file__)) + + version_info = FetchVersionInfo(opts.default_lastchange, src_dir) + + if version_info.revision == None: + version_info.revision = '0' + + if opts.revision_only: + print version_info.revision + else: + contents = "LASTCHANGE=%s\n" % version_info.revision + if out_file: + WriteIfChanged(out_file, contents) + else: + sys.stdout.write(contents) + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/build/util/lib/common/__init__.py b/build/util/lib/common/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/build/util/lib/common/unittest_util.py b/build/util/lib/common/unittest_util.py new file mode 100644 index 0000000000..e586224aac --- /dev/null +++ b/build/util/lib/common/unittest_util.py @@ -0,0 +1,151 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Utilities for dealing with the python unittest module.""" + +import fnmatch +import sys +import unittest + + +class _TextTestResult(unittest._TextTestResult): + """A test result class that can print formatted text results to a stream. + + Results printed in conformance with gtest output format, like: + [ RUN ] autofill.AutofillTest.testAutofillInvalid: "test desc." + [ OK ] autofill.AutofillTest.testAutofillInvalid + [ RUN ] autofill.AutofillTest.testFillProfile: "test desc." + [ OK ] autofill.AutofillTest.testFillProfile + [ RUN ] autofill.AutofillTest.testFillProfileCrazyCharacters: "Test." + [ OK ] autofill.AutofillTest.testFillProfileCrazyCharacters + """ + def __init__(self, stream, descriptions, verbosity): + unittest._TextTestResult.__init__(self, stream, descriptions, verbosity) + self._fails = set() + + def _GetTestURI(self, test): + return '%s.%s.%s' % (test.__class__.__module__, + test.__class__.__name__, + test._testMethodName) + + def getDescription(self, test): + return '%s: "%s"' % (self._GetTestURI(test), test.shortDescription()) + + def startTest(self, test): + unittest.TestResult.startTest(self, test) + self.stream.writeln('[ RUN ] %s' % self.getDescription(test)) + + def addSuccess(self, test): + unittest.TestResult.addSuccess(self, test) + self.stream.writeln('[ OK ] %s' % self._GetTestURI(test)) + + def addError(self, test, err): + unittest.TestResult.addError(self, test, err) + self.stream.writeln('[ ERROR ] %s' % self._GetTestURI(test)) + self._fails.add(self._GetTestURI(test)) + + def addFailure(self, test, err): + unittest.TestResult.addFailure(self, test, err) + self.stream.writeln('[ FAILED ] %s' % self._GetTestURI(test)) + self._fails.add(self._GetTestURI(test)) + + def getRetestFilter(self): + return ':'.join(self._fails) + + +class TextTestRunner(unittest.TextTestRunner): + """Test Runner for displaying test results in textual format. + + Results are displayed in conformance with google test output. + """ + + def __init__(self, verbosity=1): + unittest.TextTestRunner.__init__(self, stream=sys.stderr, + verbosity=verbosity) + + def _makeResult(self): + return _TextTestResult(self.stream, self.descriptions, self.verbosity) + + +def GetTestsFromSuite(suite): + """Returns all the tests from a given test suite.""" + tests = [] + for x in suite: + if isinstance(x, unittest.TestSuite): + tests += GetTestsFromSuite(x) + else: + tests += [x] + return tests + + +def GetTestNamesFromSuite(suite): + """Returns a list of every test name in the given suite.""" + return map(lambda x: GetTestName(x), GetTestsFromSuite(suite)) + + +def GetTestName(test): + """Gets the test name of the given unittest test.""" + return '.'.join([test.__class__.__module__, + test.__class__.__name__, + test._testMethodName]) + + +def FilterTestSuite(suite, gtest_filter): + """Returns a new filtered tests suite based on the given gtest filter. + + See http://code.google.com/p/googletest/wiki/AdvancedGuide + for gtest_filter specification. + """ + return unittest.TestSuite(FilterTests(GetTestsFromSuite(suite), gtest_filter)) + + +def FilterTests(all_tests, gtest_filter): + """Filter a list of tests based on the given gtest filter. + + Args: + all_tests: List of tests (unittest.TestSuite) + gtest_filter: Filter to apply. + + Returns: + Filtered subset of the given list of tests. + """ + test_names = [GetTestName(test) for test in all_tests] + filtered_names = FilterTestNames(test_names, gtest_filter) + return [test for test in all_tests if GetTestName(test) in filtered_names] + + +def FilterTestNames(all_tests, gtest_filter): + """Filter a list of test names based on the given gtest filter. + + See http://code.google.com/p/googletest/wiki/AdvancedGuide + for gtest_filter specification. + + Args: + all_tests: List of test names. + gtest_filter: Filter to apply. + + Returns: + Filtered subset of the given list of test names. + """ + pattern_groups = gtest_filter.split('-') + positive_patterns = pattern_groups[0].split(':') + negative_patterns = None + if len(pattern_groups) > 1: + negative_patterns = pattern_groups[1].split(':') + + tests = [] + for test in all_tests: + # Test name must by matched by one positive pattern. + for pattern in positive_patterns: + if fnmatch.fnmatch(test, pattern): + break + else: + continue + # Test name must not be matched by any negative patterns. + for pattern in negative_patterns or []: + if fnmatch.fnmatch(test, pattern): + break + else: + tests += [test] + return tests diff --git a/build/util/lib/common/util.py b/build/util/lib/common/util.py new file mode 100644 index 0000000000..a415b1f534 --- /dev/null +++ b/build/util/lib/common/util.py @@ -0,0 +1,151 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Generic utilities for all python scripts.""" + +import atexit +import httplib +import os +import signal +import stat +import subprocess +import sys +import tempfile +import urlparse + + +def GetPlatformName(): + """Return a string to be used in paths for the platform.""" + if IsWindows(): + return 'win' + if IsMac(): + return 'mac' + if IsLinux(): + return 'linux' + raise NotImplementedError('Unknown platform "%s".' % sys.platform) + + +def IsWindows(): + return sys.platform == 'cygwin' or sys.platform.startswith('win') + + +def IsLinux(): + return sys.platform.startswith('linux') + + +def IsMac(): + return sys.platform.startswith('darwin') + + +def _DeleteDir(path): + """Deletes a directory recursively, which must exist.""" + # Don't use shutil.rmtree because it can't delete read-only files on Win. + for root, dirs, files in os.walk(path, topdown=False): + for name in files: + filename = os.path.join(root, name) + os.chmod(filename, stat.S_IWRITE) + os.remove(filename) + for name in dirs: + os.rmdir(os.path.join(root, name)) + os.rmdir(path) + + +def Delete(path): + """Deletes the given file or directory (recursively), which must exist.""" + if os.path.isdir(path): + _DeleteDir(path) + else: + os.remove(path) + + +def MaybeDelete(path): + """Deletes the given file or directory (recurisvely), if it exists.""" + if os.path.exists(path): + Delete(path) + + +def MakeTempDir(parent_dir=None): + """Creates a temporary directory and returns an absolute path to it. + + The temporary directory is automatically deleted when the python interpreter + exits normally. + + Args: + parent_dir: the directory to create the temp dir in. If None, the system + temp dir is used. + + Returns: + The absolute path to the temporary directory. + """ + path = tempfile.mkdtemp(dir=parent_dir) + atexit.register(MaybeDelete, path) + return path + + +def Unzip(zip_path, output_dir): + """Unzips the given zip file using a system installed unzip tool. + + Args: + zip_path: zip file to unzip. + output_dir: directory to unzip the contents of the zip file. The directory + must exist. + + Raises: + RuntimeError if the unzip operation fails. + """ + if IsWindows(): + unzip_cmd = ['C:\\Program Files\\7-Zip\\7z.exe', 'x', '-y'] + else: + unzip_cmd = ['unzip', '-o'] + unzip_cmd += [zip_path] + if RunCommand(unzip_cmd, output_dir) != 0: + raise RuntimeError('Unable to unzip %s to %s' % (zip_path, output_dir)) + + +def Kill(pid): + """Terminate the given pid.""" + if IsWindows(): + subprocess.call(['taskkill.exe', '/T', '/F', '/PID', str(pid)]) + else: + os.kill(pid, signal.SIGTERM) + + +def RunCommand(cmd, cwd=None): + """Runs the given command and returns the exit code. + + Args: + cmd: list of command arguments. + cwd: working directory to execute the command, or None if the current + working directory should be used. + + Returns: + The exit code of the command. + """ + process = subprocess.Popen(cmd, cwd=cwd) + process.wait() + return process.returncode + + +def DoesUrlExist(url): + """Determines whether a resource exists at the given URL. + + Args: + url: URL to be verified. + + Returns: + True if url exists, otherwise False. + """ + parsed = urlparse.urlparse(url) + try: + conn = httplib.HTTPConnection(parsed.netloc) + conn.request('HEAD', parsed.path) + response = conn.getresponse() + except (socket.gaierror, socket.error): + return False + finally: + conn.close() + # Follow both permanent (301) and temporary (302) redirects. + if response.status == 302 or response.status == 301: + return DoesUrlExist(response.getheader('location')) + return response.status == 200 diff --git a/build/whitespace_file.txt b/build/whitespace_file.txt new file mode 100644 index 0000000000..405a489430 --- /dev/null +++ b/build/whitespace_file.txt @@ -0,0 +1,73 @@ +Copyright (c) 2013 The Chromium Authors. All rights reserved. +Use of this useless file is governed by a BSD-style license that can be +found in the LICENSE file. + + +This file is used for making non-code changes to trigger buildbot cycles. Make +any modification below this line. + +====================================================================== + +Let's make a story. Add one sentence for every commit: + +CHAPTER 1: +It was a dark and blinky night; the rain fell in torrents -- except at +occasional intervals, when it was checked by a violent gust of wind which +swept up the streets (for it is in London that our scene lies), rattling along +the housetops, and fiercely agitating the scanty flame of the lamps that +struggled against the elements. A hooded figure emerged. + +It was a Domo-Kun. + +"What took you so long?", inquired his wife. + +Silence. Oblivious to his silence, she continued, "Did Mr. Usagi enjoy the +waffles you brought him?""You know him, he's not one to forego a waffle, +no matter how burnt," he snickered. + +The pause was filled with the sound of thunder. + +CHAPTER 2: +The jelly was as dark as night, and just as runny. +The Domo-Kun shuddered, remembering the way Mr. Usagi had speared his waffles +with his fork, watching the runny jelly spread and pool across his plate, +like the blood of a dying fawn. "It reminds me of that time --" he started, as +his wife cut in quickly: "-- please. I can't bear to hear it.". A flury of +images coming from the past flowed through his mind. + +"You recall what happened on Mulholland drive?" The ceiling fan rotated slowly +overhead, barely disturbing the thick cigarette smoke. No doubt was left about +when the fan was last cleaned. + +There was a poignant pause. + +CHAPTER 3: +Mr. Usagi felt that something wasn't right. Shortly after the Domo-Kun left he +began feeling sick. He thought out loud to himself, "No, he wouldn't have done +that to me." He considered that perhaps he shouldn't have pushed so hard. +Perhaps he shouldn't have been so cold and sarcastic, after the unimaginable +horror that had occurred just the week before. + +Next time, there won't be any sushi. Why sushi with waffles anyway? It's like +adorning breakfast cereal with halibut -- shameful. + +CHAPTER 4: +The taste of stable sushi in his mouth the next morning was unbearable. He +wondered where the sushi came from as he attempted to wash the taste away with +a bottle of 3000Â¥ sake. He tries to recall the cook's face. Purple? + +CHAPTER 5: +Many years later, Mr. Usagi would laugh at the memory of the earnest, +well-intentioned Domo-Kun. Another day in the life. + +TRUISMS (1978-1983) +JENNY HOLZER +A LITTLE KNOWLEDGE CAN GO A LONG WAY +A LOT OF PROFESSIONALS ARE CRACKPOTS +A MAN CAN'T KNOW WHAT IT IS TO BE A MOTHER +A NAME MEANS A LOT JUST BY ITSELF +A POSITIVE ATTITUDE MEANS ALL THE DIFFERENCE IN THE WORLD +A RELAXED MAN IS NOT NECESSARILY A BETTER MAN +NO ONE SHOULD EVER USE SVN +AN INFLEXIBLE POSITION SOMETIMES IS A SIGN OF PARALYSIS +IT IS MANS FATE TO OUTSMART HIMSELF diff --git a/build/win/chrome_win.croc b/build/win/chrome_win.croc new file mode 100644 index 0000000000..e1e3bb76d6 --- /dev/null +++ b/build/win/chrome_win.croc @@ -0,0 +1,26 @@ +# -*- python -*- +# Crocodile config file for Chromium windows + +{ + # List of rules, applied in order + 'rules' : [ + # Specify inclusions before exclusions, since rules are in order. + + # Don't include chromeos, posix, or linux specific files + { + 'regexp' : '.*(_|/)(chromeos|linux|posix)(\\.|_)', + 'include' : 0, + }, + # Don't include ChromeOS dirs + { + 'regexp' : '.*/chromeos/', + 'include' : 0, + }, + + # Groups + { + 'regexp' : '.*_test_win\\.', + 'group' : 'test', + }, + ], +} diff --git a/build/win/compatibility.manifest b/build/win/compatibility.manifest new file mode 100644 index 0000000000..f7bc13e593 --- /dev/null +++ b/build/win/compatibility.manifest @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/build/win/importlibs/create_import_lib.gypi b/build/win/importlibs/create_import_lib.gypi new file mode 100644 index 0000000000..c809eab84f --- /dev/null +++ b/build/win/importlibs/create_import_lib.gypi @@ -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. + +# This file is meant to be included into a target to provide a rule +# to create import libraries from an import description file in a consistent +# manner. +# +# To use this, create a gyp target with the following form: +# { +# 'target_name': 'my_proto_lib', +# 'type': 'none', +# 'sources': [ +# 'foo.imports', +# 'bar.imports', +# ], +# 'variables': { +# # Optional, see below: 'proto_in_dir': '.' +# 'create_importlib': 'path-to-script', +# 'lib_dir': 'path-to-output-directory', +# }, +# 'includes': ['path/to/this/gypi/file'], +# } +# +# This will generate import libraries named 'foo.lib' and 'bar.lib' in the +# specified lib directory. + +{ + 'variables': { + 'create_importlib': '<(DEPTH)/build/win/importlibs/create_importlib_win.py', + 'lib_dir': '<(PRODUCT_DIR)/lib', + }, + 'rules': [ + { + 'rule_name': 'create_import_lib', + 'extension': 'imports', + 'inputs': [ + '<(create_importlib)', + ], + 'outputs': [ + '<(lib_dir)/<(RULE_INPUT_ROOT).lib', + ], + 'action': [ + 'python', + '<(create_importlib)', + '--output-file', '<@(_outputs)', + '<(RULE_INPUT_PATH)', + ], + 'msvs_cygwin_shell': 0, + 'message': 'Generating import library from <(RULE_INPUT_PATH)', + 'process_outputs_as_sources': 0, + }, + ], +} diff --git a/build/win/importlibs/create_importlib_win.py b/build/win/importlibs/create_importlib_win.py new file mode 100755 index 0000000000..bb6a2f0263 --- /dev/null +++ b/build/win/importlibs/create_importlib_win.py @@ -0,0 +1,217 @@ +#!/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. +# +"""Creates an import library from an import description file.""" +import ast +import logging +import optparse +import os +import os.path +import shutil +import subprocess +import sys +import tempfile + + +_USAGE = """\ +Usage: %prog [options] [imports-file] + +Creates an import library from imports-file. + +Note: this script uses the microsoft assembler (ml.exe) and the library tool + (lib.exe), both of which must be in path. +""" + + +_ASM_STUB_HEADER = """\ +; This file is autogenerated by create_importlib_win.py, do not edit. +.386 +.MODEL FLAT, C +.CODE + +; Stubs to provide mangled names to lib.exe for the +; correct generation of import libs. +""" + + +_DEF_STUB_HEADER = """\ +; This file is autogenerated by create_importlib_win.py, do not edit. + +; Export declarations for generating import libs. +""" + + +_LOGGER = logging.getLogger() + + + +class _Error(Exception): + pass + + +class _ImportLibraryGenerator(object): + def __init__(self, temp_dir): + self._temp_dir = temp_dir + + def _Shell(self, cmd, **kw): + ret = subprocess.call(cmd, **kw) + _LOGGER.info('Running "%s" returned %d.', cmd, ret) + if ret != 0: + raise _Error('Command "%s" returned %d.' % (cmd, ret)) + + def _ReadImportsFile(self, imports_file): + # Slurp the imports file. + return ast.literal_eval(open(imports_file).read()) + + def _WriteStubsFile(self, import_names, output_file): + output_file.write(_ASM_STUB_HEADER) + + for name in import_names: + output_file.write('%s PROC\n' % name) + output_file.write('%s ENDP\n' % name) + + output_file.write('END\n') + + def _WriteDefFile(self, dll_name, import_names, output_file): + output_file.write(_DEF_STUB_HEADER) + output_file.write('NAME %s\n' % dll_name) + output_file.write('EXPORTS\n') + for name in import_names: + name = name.split('@')[0] + output_file.write(' %s\n' % name) + + def _CreateObj(self, dll_name, imports): + """Writes an assembly file containing empty declarations. + + For each imported function of the form: + + AddClipboardFormatListener@4 PROC + AddClipboardFormatListener@4 ENDP + + The resulting object file is then supplied to lib.exe with a .def file + declaring the corresponding non-adorned exports as they appear on the + exporting DLL, e.g. + + EXPORTS + AddClipboardFormatListener + + In combination, the .def file and the .obj file cause lib.exe to generate + an x86 import lib with public symbols named like + "__imp__AddClipboardFormatListener@4", binding to exports named like + "AddClipboardFormatListener". + + All of this is perpetrated in a temporary directory, as the intermediate + artifacts are quick and easy to produce, and of no interest to anyone + after the fact.""" + + # Create an .asm file to provide stdcall-like stub names to lib.exe. + asm_name = dll_name + '.asm' + _LOGGER.info('Writing asm file "%s".', asm_name) + with open(os.path.join(self._temp_dir, asm_name), 'wb') as stubs_file: + self._WriteStubsFile(imports, stubs_file) + + # Invoke on the assembler to compile it to .obj. + obj_name = dll_name + '.obj' + cmdline = ['ml.exe', '/nologo', '/c', asm_name, '/Fo', obj_name] + self._Shell(cmdline, cwd=self._temp_dir, stdout=open(os.devnull)) + + return obj_name + + def _CreateImportLib(self, dll_name, imports, architecture, output_file): + """Creates an import lib binding imports to dll_name for architecture. + + On success, writes the import library to output file. + """ + obj_file = None + + # For x86 architecture we have to provide an object file for correct + # name mangling between the import stubs and the exported functions. + if architecture == 'x86': + obj_file = self._CreateObj(dll_name, imports) + + # Create the corresponding .def file. This file has the non stdcall-adorned + # names, as exported by the destination DLL. + def_name = dll_name + '.def' + _LOGGER.info('Writing def file "%s".', def_name) + with open(os.path.join(self._temp_dir, def_name), 'wb') as def_file: + self._WriteDefFile(dll_name, imports, def_file) + + # Invoke on lib.exe to create the import library. + # We generate everything into the temporary directory, as the .exp export + # files will be generated at the same path as the import library, and we + # don't want those files potentially gunking the works. + dll_base_name, ext = os.path.splitext(dll_name) + lib_name = dll_base_name + '.lib' + cmdline = ['lib.exe', + '/machine:%s' % architecture, + '/def:%s' % def_name, + '/out:%s' % lib_name] + if obj_file: + cmdline.append(obj_file) + + self._Shell(cmdline, cwd=self._temp_dir, stdout=open(os.devnull)) + + # Copy the .lib file to the output directory. + shutil.copyfile(os.path.join(self._temp_dir, lib_name), output_file) + _LOGGER.info('Created "%s".', output_file) + + def CreateImportLib(self, imports_file, output_file): + # Read the imports file. + imports = self._ReadImportsFile(imports_file) + + # Creates the requested import library in the output directory. + self._CreateImportLib(imports['dll_name'], + imports['imports'], + imports.get('architecture', 'x86'), + output_file) + + +def main(): + parser = optparse.OptionParser(usage=_USAGE) + parser.add_option('-o', '--output-file', + help='Specifies the output file path.') + parser.add_option('-k', '--keep-temp-dir', + action='store_true', + help='Keep the temporary directory.') + parser.add_option('-v', '--verbose', + action='store_true', + help='Verbose logging.') + + options, args = parser.parse_args() + + if len(args) != 1: + parser.error('You must provide an imports file.') + + if not options.output_file: + parser.error('You must provide an output file.') + + options.output_file = os.path.abspath(options.output_file) + + if options.verbose: + logging.basicConfig(level=logging.INFO) + else: + logging.basicConfig(level=logging.WARN) + + + temp_dir = tempfile.mkdtemp() + _LOGGER.info('Created temporary directory "%s."', temp_dir) + try: + # Create a generator and create the import lib. + generator = _ImportLibraryGenerator(temp_dir) + + ret = generator.CreateImportLib(args[0], options.output_file) + except Exception, e: + _LOGGER.exception('Failed to create import lib.') + ret = 1 + finally: + if not options.keep_temp_dir: + shutil.rmtree(temp_dir) + _LOGGER.info('Deleted temporary directory "%s."', temp_dir) + + return ret + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/build/win/importlibs/filter_export_list.py b/build/win/importlibs/filter_export_list.py new file mode 100755 index 0000000000..c2489a9da7 --- /dev/null +++ b/build/win/importlibs/filter_export_list.py @@ -0,0 +1,85 @@ +#!/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. +# +"""Help maintaining DLL import lists.""" +import ast +import optparse +import re +import sys + + +_EXPORT_RE = re.compile(r""" + ^\s*(?P[0-9]+) # The ordinal field. + \s+(?P[0-9A-F]+) # The hint field. + \s(?P........) # The RVA field. + \s+(?P[^ ]+) # And finally the name we're really after. +""", re.VERBOSE) + + +_USAGE = r"""\ +Usage: %prog [options] [master-file] + +This script filters a list of exports from a DLL, generated from something +like the following command line: + +C:\> dumpbin /exports user32.dll + +against a master list of imports built from e.g. + +C:\> dumpbin /exports user32.lib + +The point of this is to trim non-public exports from the list, and to +normalize the names to their stdcall-mangled form for the generation of +import libraries. +Note that the export names from the latter incanatation are stdcall-mangled, +e.g. they are suffixed with "@" and the number of argument bytes to the +function. +""" + +def _ReadMasterFile(master_file): + # Slurp the master file. + with open(master_file) as f: + master_exports = ast.literal_eval(f.read()) + + master_mapping = {} + for export in master_exports: + name = export.split('@')[0] + master_mapping[name] = export + + return master_mapping + + +def main(): + parser = optparse.OptionParser(usage=_USAGE) + parser.add_option('-r', '--reverse', + action='store_true', + help='Reverse the matching, e.g. return the functions ' + 'in the master list that aren\'t in the input.') + + options, args = parser.parse_args() + if len(args) != 1: + parser.error('Must provide a master file.') + + master_mapping = _ReadMasterFile(args[0]) + + found_exports = [] + for line in sys.stdin: + match = _EXPORT_RE.match(line) + if match: + export_name = master_mapping.get(match.group('name'), None) + if export_name: + found_exports.append(export_name) + + if options.reverse: + # Invert the found_exports list. + found_exports = set(master_mapping.values()) - set(found_exports) + + # Sort the found exports for tidy output. + print '\n'.join(sorted(found_exports)) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/build/win/importlibs/x86/user32.winxp.imports b/build/win/importlibs/x86/user32.winxp.imports new file mode 100644 index 0000000000..24403a8aee --- /dev/null +++ b/build/win/importlibs/x86/user32.winxp.imports @@ -0,0 +1,670 @@ +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# +# This file is used to create a custom import library for Chrome's use of +# user32.dll exports. The set of exports defined below +{ + 'architecture': 'x86', + + # The DLL to bind to. + 'dll_name': 'user32.dll', + + # Name of the generated import library. + 'importlib_name': 'user32.winxp.lib', + + # This is the set of exports observed on a user32.dll from Windows XP SP2. + # The version of the DLL where these were observed is 5.1.2600.2180. + # Incidentally this set of exports also coincides with Windows XP SP3, where + # the version of the DLL is 5.1.2600.5512. + # Don't add new imports here unless and until the minimal supported + # Windows version has been bumped past Windows XP SP2+. + 'imports': [ + 'ActivateKeyboardLayout@8', + 'AdjustWindowRect@12', + 'AdjustWindowRectEx@16', + 'AllowSetForegroundWindow@4', + 'AnimateWindow@12', + 'AnyPopup@0', + 'AppendMenuA@16', + 'AppendMenuW@16', + 'ArrangeIconicWindows@4', + 'AttachThreadInput@12', + 'BeginDeferWindowPos@4', + 'BeginPaint@8', + 'BlockInput@4', + 'BringWindowToTop@4', + 'BroadcastSystemMessage@20', + 'BroadcastSystemMessageA@20', + 'BroadcastSystemMessageExA@24', + 'BroadcastSystemMessageExW@24', + 'BroadcastSystemMessageW@20', + 'CallMsgFilter@8', + 'CallMsgFilterA@8', + 'CallMsgFilterW@8', + 'CallNextHookEx@16', + 'CallWindowProcA@20', + 'CallWindowProcW@20', + 'CascadeChildWindows@8', + 'CascadeWindows@20', + 'ChangeClipboardChain@8', + 'ChangeDisplaySettingsA@8', + 'ChangeDisplaySettingsExA@20', + 'ChangeDisplaySettingsExW@20', + 'ChangeDisplaySettingsW@8', + 'ChangeMenuA@20', + 'ChangeMenuW@20', + 'CharLowerA@4', + 'CharLowerBuffA@8', + 'CharLowerBuffW@8', + 'CharLowerW@4', + 'CharNextA@4', + 'CharNextExA@12', + 'CharNextW@4', + 'CharPrevA@8', + 'CharPrevExA@16', + 'CharPrevW@8', + 'CharToOemA@8', + 'CharToOemBuffA@12', + 'CharToOemBuffW@12', + 'CharToOemW@8', + 'CharUpperA@4', + 'CharUpperBuffA@8', + 'CharUpperBuffW@8', + 'CharUpperW@4', + 'CheckDlgButton@12', + 'CheckMenuItem@12', + 'CheckMenuRadioItem@20', + 'CheckRadioButton@16', + 'ChildWindowFromPoint@12', + 'ChildWindowFromPointEx@16', + 'ClientToScreen@8', + 'ClipCursor@4', + 'CloseClipboard@0', + 'CloseDesktop@4', + 'CloseWindow@4', + 'CloseWindowStation@4', + 'CopyAcceleratorTableA@12', + 'CopyAcceleratorTableW@12', + 'CopyIcon@4', + 'CopyImage@20', + 'CopyRect@8', + 'CountClipboardFormats@0', + 'CreateAcceleratorTableA@8', + 'CreateAcceleratorTableW@8', + 'CreateCaret@16', + 'CreateCursor@28', + 'CreateDesktopA@24', + 'CreateDesktopW@24', + 'CreateDialogIndirectParamA@20', + 'CreateDialogIndirectParamW@20', + 'CreateDialogParamA@20', + 'CreateDialogParamW@20', + 'CreateIcon@28', + 'CreateIconFromResource@16', + 'CreateIconFromResourceEx@28', + 'CreateIconIndirect@4', + 'CreateMDIWindowA@40', + 'CreateMDIWindowW@40', + 'CreateMenu@0', + 'CreatePopupMenu@0', + 'CreateWindowExA@48', + 'CreateWindowExW@48', + 'CreateWindowStationA@16', + 'CreateWindowStationW@16', + 'DdeAbandonTransaction@12', + 'DdeAccessData@8', + 'DdeAddData@16', + 'DdeClientTransaction@32', + 'DdeCmpStringHandles@8', + 'DdeConnect@16', + 'DdeConnectList@20', + 'DdeCreateDataHandle@28', + 'DdeCreateStringHandleA@12', + 'DdeCreateStringHandleW@12', + 'DdeDisconnect@4', + 'DdeDisconnectList@4', + 'DdeEnableCallback@12', + 'DdeFreeDataHandle@4', + 'DdeFreeStringHandle@8', + 'DdeGetData@16', + 'DdeGetLastError@4', + 'DdeImpersonateClient@4', + 'DdeInitializeA@16', + 'DdeInitializeW@16', + 'DdeKeepStringHandle@8', + 'DdeNameService@16', + 'DdePostAdvise@12', + 'DdeQueryConvInfo@12', + 'DdeQueryNextServer@8', + 'DdeQueryStringA@20', + 'DdeQueryStringW@20', + 'DdeReconnect@4', + 'DdeSetQualityOfService@12', + 'DdeSetUserHandle@12', + 'DdeUnaccessData@4', + 'DdeUninitialize@4', + 'DefDlgProcA@16', + 'DefDlgProcW@16', + 'DefFrameProcA@20', + 'DefFrameProcW@20', + 'DefMDIChildProcA@16', + 'DefMDIChildProcW@16', + 'DefRawInputProc@12', + 'DefWindowProcA@16', + 'DefWindowProcW@16', + 'DeferWindowPos@32', + 'DeleteMenu@12', + 'DeregisterShellHookWindow@4', + 'DestroyAcceleratorTable@4', + 'DestroyCaret@0', + 'DestroyCursor@4', + 'DestroyIcon@4', + 'DestroyMenu@4', + 'DestroyWindow@4', + 'DialogBoxIndirectParamA@20', + 'DialogBoxIndirectParamW@20', + 'DialogBoxParamA@20', + 'DialogBoxParamW@20', + 'DisableProcessWindowsGhosting@0', + 'DispatchMessageA@4', + 'DispatchMessageW@4', + 'DlgDirListA@20', + 'DlgDirListComboBoxA@20', + 'DlgDirListComboBoxW@20', + 'DlgDirListW@20', + 'DlgDirSelectComboBoxExA@16', + 'DlgDirSelectComboBoxExW@16', + 'DlgDirSelectExA@16', + 'DlgDirSelectExW@16', + 'DragDetect@12', + 'DragObject@20', + 'DrawAnimatedRects@16', + 'DrawCaption@16', + 'DrawEdge@16', + 'DrawFocusRect@8', + 'DrawFrame@16', + 'DrawFrameControl@16', + 'DrawIcon@16', + 'DrawIconEx@36', + 'DrawMenuBar@4', + 'DrawStateA@40', + 'DrawStateW@40', + 'DrawTextA@20', + 'DrawTextExA@24', + 'DrawTextExW@24', + 'DrawTextW@20', + 'EditWndProc@16', + 'EmptyClipboard@0', + 'EnableMenuItem@12', + 'EnableScrollBar@12', + 'EnableWindow@8', + 'EndDeferWindowPos@4', + 'EndDialog@8', + 'EndMenu@0', + 'EndPaint@8', + 'EndTask@12', + 'EnumChildWindows@12', + 'EnumClipboardFormats@4', + 'EnumDesktopWindows@12', + 'EnumDesktopsA@12', + 'EnumDesktopsW@12', + 'EnumDisplayDevicesA@16', + 'EnumDisplayDevicesW@16', + 'EnumDisplayMonitors@16', + 'EnumDisplaySettingsA@12', + 'EnumDisplaySettingsExA@16', + 'EnumDisplaySettingsExW@16', + 'EnumDisplaySettingsW@12', + 'EnumPropsA@8', + 'EnumPropsExA@12', + 'EnumPropsExW@12', + 'EnumPropsW@8', + 'EnumThreadWindows@12', + 'EnumWindowStationsA@8', + 'EnumWindowStationsW@8', + 'EnumWindows@8', + 'EqualRect@8', + 'ExcludeUpdateRgn@8', + 'ExitWindowsEx@8', + 'FillRect@12', + 'FindWindowA@8', + 'FindWindowExA@16', + 'FindWindowExW@16', + 'FindWindowW@8', + 'FlashWindow@8', + 'FlashWindowEx@4', + 'FrameRect@12', + 'FreeDDElParam@8', + 'GetActiveWindow@0', + 'GetAltTabInfo@20', + 'GetAltTabInfoA@20', + 'GetAltTabInfoW@20', + 'GetAncestor@8', + 'GetAsyncKeyState@4', + 'GetCapture@0', + 'GetCaretBlinkTime@0', + 'GetCaretPos@4', + 'GetClassInfoA@12', + 'GetClassInfoExA@12', + 'GetClassInfoExW@12', + 'GetClassInfoW@12', + 'GetClassLongA@8', + 'GetClassLongW@8', + 'GetClassNameA@12', + 'GetClassNameW@12', + 'GetClassWord@8', + 'GetClientRect@8', + 'GetClipCursor@4', + 'GetClipboardData@4', + 'GetClipboardFormatNameA@12', + 'GetClipboardFormatNameW@12', + 'GetClipboardOwner@0', + 'GetClipboardSequenceNumber@0', + 'GetClipboardViewer@0', + 'GetComboBoxInfo@8', + 'GetCursor@0', + 'GetCursorInfo@4', + 'GetCursorPos@4', + 'GetDC@4', + 'GetDCEx@12', + 'GetDesktopWindow@0', + 'GetDialogBaseUnits@0', + 'GetDlgCtrlID@4', + 'GetDlgItem@8', + 'GetDlgItemInt@16', + 'GetDlgItemTextA@16', + 'GetDlgItemTextW@16', + 'GetDoubleClickTime@0', + 'GetFocus@0', + 'GetForegroundWindow@0', + 'GetGUIThreadInfo@8', + 'GetGuiResources@8', + 'GetIconInfo@8', + 'GetInputDesktop@0', + 'GetInputState@0', + 'GetKBCodePage@0', + 'GetKeyNameTextA@12', + 'GetKeyNameTextW@12', + 'GetKeyState@4', + 'GetKeyboardLayout@4', + 'GetKeyboardLayoutList@8', + 'GetKeyboardLayoutNameA@4', + 'GetKeyboardLayoutNameW@4', + 'GetKeyboardState@4', + 'GetKeyboardType@4', + 'GetLastActivePopup@4', + 'GetLastInputInfo@4', + 'GetLayeredWindowAttributes@16', + 'GetListBoxInfo@4', + 'GetMenu@4', + 'GetMenuBarInfo@16', + 'GetMenuCheckMarkDimensions@0', + 'GetMenuContextHelpId@4', + 'GetMenuDefaultItem@12', + 'GetMenuInfo@8', + 'GetMenuItemCount@4', + 'GetMenuItemID@8', + 'GetMenuItemInfoA@16', + 'GetMenuItemInfoW@16', + 'GetMenuItemRect@16', + 'GetMenuState@12', + 'GetMenuStringA@20', + 'GetMenuStringW@20', + 'GetMessageA@16', + 'GetMessageExtraInfo@0', + 'GetMessagePos@0', + 'GetMessageTime@0', + 'GetMessageW@16', + 'GetMonitorInfoA@8', + 'GetMonitorInfoW@8', + 'GetMouseMovePointsEx@20', + 'GetNextDlgGroupItem@12', + 'GetNextDlgTabItem@12', + 'GetOpenClipboardWindow@0', + 'GetParent@4', + 'GetPriorityClipboardFormat@8', + 'GetProcessDefaultLayout@4', + 'GetProcessWindowStation@0', + 'GetPropA@8', + 'GetPropW@8', + 'GetQueueStatus@4', + 'GetRawInputBuffer@12', + 'GetRawInputData@20', + 'GetRawInputDeviceInfoA@16', + 'GetRawInputDeviceInfoW@16', + 'GetRawInputDeviceList@12', + 'GetRegisteredRawInputDevices@12', + 'GetScrollBarInfo@12', + 'GetScrollInfo@12', + 'GetScrollPos@8', + 'GetScrollRange@16', + 'GetShellWindow@0', + 'GetSubMenu@8', + 'GetSysColor@4', + 'GetSysColorBrush@4', + 'GetSystemMenu@8', + 'GetSystemMetrics@4', + 'GetTabbedTextExtentA@20', + 'GetTabbedTextExtentW@20', + 'GetThreadDesktop@4', + 'GetTitleBarInfo@8', + 'GetTopWindow@4', + 'GetUpdateRect@12', + 'GetUpdateRgn@12', + 'GetUserObjectInformationA@20', + 'GetUserObjectInformationW@20', + 'GetUserObjectSecurity@20', + 'GetWindow@8', + 'GetWindowContextHelpId@4', + 'GetWindowDC@4', + 'GetWindowInfo@8', + 'GetWindowLongA@8', + 'GetWindowLongW@8', + 'GetWindowModuleFileName@12', + 'GetWindowModuleFileNameA@12', + 'GetWindowModuleFileNameW@12', + 'GetWindowPlacement@8', + 'GetWindowRect@8', + 'GetWindowRgn@8', + 'GetWindowRgnBox@8', + 'GetWindowTextA@12', + 'GetWindowTextLengthA@4', + 'GetWindowTextLengthW@4', + 'GetWindowTextW@12', + 'GetWindowThreadProcessId@8', + 'GetWindowWord@8', + 'GrayStringA@36', + 'GrayStringW@36', + 'HideCaret@4', + 'HiliteMenuItem@16', + 'IMPGetIMEA@8', + 'IMPGetIMEW@8', + 'IMPQueryIMEA@4', + 'IMPQueryIMEW@4', + 'IMPSetIMEA@8', + 'IMPSetIMEW@8', + 'ImpersonateDdeClientWindow@8', + 'InSendMessage@0', + 'InSendMessageEx@4', + 'InflateRect@12', + 'InsertMenuA@20', + 'InsertMenuItemA@16', + 'InsertMenuItemW@16', + 'InsertMenuW@20', + 'InternalGetWindowText@12', + 'IntersectRect@12', + 'InvalidateRect@12', + 'InvalidateRgn@12', + 'InvertRect@8', + 'IsCharAlphaA@4', + 'IsCharAlphaNumericA@4', + 'IsCharAlphaNumericW@4', + 'IsCharAlphaW@4', + 'IsCharLowerA@4', + 'IsCharLowerW@4', + 'IsCharUpperA@4', + 'IsCharUpperW@4', + 'IsChild@8', + 'IsClipboardFormatAvailable@4', + 'IsDialogMessage@8', + 'IsDialogMessageA@8', + 'IsDialogMessageW@8', + 'IsDlgButtonChecked@8', + 'IsGUIThread@4', + 'IsHungAppWindow@4', + 'IsIconic@4', + 'IsMenu@4', + 'IsRectEmpty@4', + 'IsWinEventHookInstalled@4', + 'IsWindow@4', + 'IsWindowEnabled@4', + 'IsWindowUnicode@4', + 'IsWindowVisible@4', + 'IsZoomed@4', + 'KillTimer@8', + 'LoadAcceleratorsA@8', + 'LoadAcceleratorsW@8', + 'LoadBitmapA@8', + 'LoadBitmapW@8', + 'LoadCursorA@8', + 'LoadCursorFromFileA@4', + 'LoadCursorFromFileW@4', + 'LoadCursorW@8', + 'LoadIconA@8', + 'LoadIconW@8', + 'LoadImageA@24', + 'LoadImageW@24', + 'LoadKeyboardLayoutA@8', + 'LoadKeyboardLayoutW@8', + 'LoadMenuA@8', + 'LoadMenuIndirectA@4', + 'LoadMenuIndirectW@4', + 'LoadMenuW@8', + 'LoadStringA@16', + 'LoadStringW@16', + 'LockSetForegroundWindow@4', + 'LockWindowUpdate@4', + 'LockWorkStation@0', + 'LookupIconIdFromDirectory@8', + 'LookupIconIdFromDirectoryEx@20', + 'MapDialogRect@8', + 'MapVirtualKeyA@8', + 'MapVirtualKeyExA@12', + 'MapVirtualKeyExW@12', + 'MapVirtualKeyW@8', + 'MapWindowPoints@16', + 'MenuItemFromPoint@16', + 'MessageBeep@4', + 'MessageBoxA@16', + 'MessageBoxExA@20', + 'MessageBoxExW@20', + 'MessageBoxIndirectA@4', + 'MessageBoxIndirectW@4', + 'MessageBoxTimeoutA@24', + 'MessageBoxTimeoutW@24', + 'MessageBoxW@16', + 'ModifyMenuA@20', + 'ModifyMenuW@20', + 'MonitorFromPoint@12', + 'MonitorFromRect@8', + 'MonitorFromWindow@8', + 'MoveWindow@24', + 'MsgWaitForMultipleObjects@20', + 'MsgWaitForMultipleObjectsEx@20', + 'NotifyWinEvent@16', + 'OemKeyScan@4', + 'OemToCharA@8', + 'OemToCharBuffA@12', + 'OemToCharBuffW@12', + 'OemToCharW@8', + 'OffsetRect@12', + 'OpenClipboard@4', + 'OpenDesktopA@16', + 'OpenDesktopW@16', + 'OpenIcon@4', + 'OpenInputDesktop@12', + 'OpenWindowStationA@12', + 'OpenWindowStationW@12', + 'PackDDElParam@12', + 'PaintDesktop@4', + 'PeekMessageA@20', + 'PeekMessageW@20', + 'PostMessageA@16', + 'PostMessageW@16', + 'PostQuitMessage@4', + 'PostThreadMessageA@16', + 'PostThreadMessageW@16', + 'PrintWindow@12', + 'PrivateExtractIconsA@32', + 'PrivateExtractIconsW@32', + 'PtInRect@12', + 'RealChildWindowFromPoint@12', + 'RealGetWindowClass@12', + 'RealGetWindowClassA@12', + 'RealGetWindowClassW@12', + 'RedrawWindow@16', + 'RegisterClassA@4', + 'RegisterClassExA@4', + 'RegisterClassExW@4', + 'RegisterClassW@4', + 'RegisterClipboardFormatA@4', + 'RegisterClipboardFormatW@4', + 'RegisterDeviceNotificationA@12', + 'RegisterDeviceNotificationW@12', + 'RegisterHotKey@16', + 'RegisterRawInputDevices@12', + 'RegisterShellHookWindow@4', + 'RegisterWindowMessageA@4', + 'RegisterWindowMessageW@4', + 'ReleaseCapture@0', + 'ReleaseDC@8', + 'RemoveMenu@12', + 'RemovePropA@8', + 'RemovePropW@8', + 'ReplyMessage@4', + 'ReuseDDElParam@20', + 'ScreenToClient@8', + 'ScrollDC@28', + 'ScrollWindow@20', + 'ScrollWindowEx@32', + 'SendDlgItemMessageA@20', + 'SendDlgItemMessageW@20', + 'SendIMEMessageExA@8', + 'SendIMEMessageExW@8', + 'SendInput@12', + 'SendMessageA@16', + 'SendMessageCallbackA@24', + 'SendMessageCallbackW@24', + 'SendMessageTimeoutA@28', + 'SendMessageTimeoutW@28', + 'SendMessageW@16', + 'SendNotifyMessageA@16', + 'SendNotifyMessageW@16', + 'SetActiveWindow@4', + 'SetCapture@4', + 'SetCaretBlinkTime@4', + 'SetCaretPos@8', + 'SetClassLongA@12', + 'SetClassLongW@12', + 'SetClassWord@12', + 'SetClipboardData@8', + 'SetClipboardViewer@4', + 'SetCursor@4', + 'SetCursorPos@8', + 'SetDebugErrorLevel@4', + 'SetDeskWallpaper@4', + 'SetDlgItemInt@16', + 'SetDlgItemTextA@12', + 'SetDlgItemTextW@12', + 'SetDoubleClickTime@4', + 'SetFocus@4', + 'SetForegroundWindow@4', + 'SetKeyboardState@4', + 'SetLastErrorEx@8', + 'SetLayeredWindowAttributes@16', + 'SetMenu@8', + 'SetMenuContextHelpId@8', + 'SetMenuDefaultItem@12', + 'SetMenuInfo@8', + 'SetMenuItemBitmaps@20', + 'SetMenuItemInfoA@16', + 'SetMenuItemInfoW@16', + 'SetMessageExtraInfo@4', + 'SetMessageQueue@4', + 'SetParent@8', + 'SetProcessDefaultLayout@4', + 'SetProcessWindowStation@4', + 'SetPropA@12', + 'SetPropW@12', + 'SetRect@20', + 'SetRectEmpty@4', + 'SetScrollInfo@16', + 'SetScrollPos@16', + 'SetScrollRange@20', + 'SetShellWindow@4', + 'SetSysColors@12', + 'SetSystemCursor@8', + 'SetThreadDesktop@4', + 'SetTimer@16', + 'SetUserObjectInformationA@16', + 'SetUserObjectInformationW@16', + 'SetUserObjectSecurity@12', + 'SetWinEventHook@28', + 'SetWindowContextHelpId@8', + 'SetWindowLongA@12', + 'SetWindowLongW@12', + 'SetWindowPlacement@8', + 'SetWindowPos@28', + 'SetWindowRgn@12', + 'SetWindowTextA@8', + 'SetWindowTextW@8', + 'SetWindowWord@12', + 'SetWindowsHookA@8', + 'SetWindowsHookExA@16', + 'SetWindowsHookExW@16', + 'SetWindowsHookW@8', + 'ShowCaret@4', + 'ShowCursor@4', + 'ShowOwnedPopups@8', + 'ShowScrollBar@12', + 'ShowWindow@8', + 'ShowWindowAsync@8', + 'SubtractRect@12', + 'SwapMouseButton@4', + 'SwitchDesktop@4', + 'SwitchToThisWindow@8', + 'SystemParametersInfoA@16', + 'SystemParametersInfoW@16', + 'TabbedTextOutA@32', + 'TabbedTextOutW@32', + 'TileChildWindows@8', + 'TileWindows@20', + 'ToAscii@20', + 'ToAsciiEx@24', + 'ToUnicode@24', + 'ToUnicodeEx@28', + 'TrackMouseEvent@4', + 'TrackPopupMenu@28', + 'TrackPopupMenuEx@24', + 'TranslateAccelerator@12', + 'TranslateAcceleratorA@12', + 'TranslateAcceleratorW@12', + 'TranslateMDISysAccel@8', + 'TranslateMessage@4', + 'UnhookWinEvent@4', + 'UnhookWindowsHook@8', + 'UnhookWindowsHookEx@4', + 'UnionRect@12', + 'UnloadKeyboardLayout@4', + 'UnpackDDElParam@16', + 'UnregisterClassA@8', + 'UnregisterClassW@8', + 'UnregisterDeviceNotification@4', + 'UnregisterHotKey@8', + 'UpdateLayeredWindow@36', + 'UpdateWindow@4', + 'UserHandleGrantAccess@12', + 'ValidateRect@8', + 'ValidateRgn@8', + 'VkKeyScanA@4', + 'VkKeyScanExA@8', + 'VkKeyScanExW@8', + 'VkKeyScanW@4', + 'WINNLSEnableIME@8', + 'WINNLSGetEnableStatus@4', + 'WINNLSGetIMEHotkey@4', + 'WaitForInputIdle@8', + 'WaitMessage@0', + 'WinHelpA@16', + 'WinHelpW@16', + 'WindowFromDC@4', + 'WindowFromPoint@8', + 'keybd_event@16', + 'mouse_event@20', + 'wsprintfA', + 'wsprintfW', + 'wvsprintfA@12', + 'wvsprintfW@12', + ] +} diff --git a/build/win/importlibs/x86/user32.winxp.lib b/build/win/importlibs/x86/user32.winxp.lib new file mode 100644 index 0000000000..deb55778d5 Binary files /dev/null and b/build/win/importlibs/x86/user32.winxp.lib differ diff --git a/build/win/install-build-deps.py b/build/win/install-build-deps.py new file mode 100755 index 0000000000..d9e50b6e7e --- /dev/null +++ b/build/win/install-build-deps.py @@ -0,0 +1,47 @@ +#!/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. + +import shutil +import sys +import os + +def patch_msbuild(): + """VS2010 MSBuild has a ULDI bug that we patch here. See http://goo.gl/Pn8tj. + """ + source_path = os.path.join(os.environ['ProgramFiles(x86)'], + "MSBuild", + "Microsoft.Cpp", + "v4.0", + "Microsoft.CppBuild.targets") + backup_path = source_path + ".backup" + if not os.path.exists(backup_path): + try: + print "Backing up %s..." % source_path + shutil.copyfile(source_path, backup_path) + except IOError: + print "Could not back up %s to %s. Run as Administrator?" % ( + source_path, backup_path) + return 1 + + source = open(source_path).read() + base = ('''' + replace = base + ''' Condition="'$(ConfigurationType)'=='StaticLibrary'">''' + result = source.replace(find, replace) + + if result != source: + open(source_path, "w").write(result) + print "Patched %s." % source_path + return 0 + + +def main(): + return patch_msbuild() + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/build/win/setup_cygwin_mount.py b/build/win/setup_cygwin_mount.py new file mode 100644 index 0000000000..d68a3af41d --- /dev/null +++ b/build/win/setup_cygwin_mount.py @@ -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. + +import os +import sys + +def main(): + if len(sys.argv) != 2 or sys.argv[1] != '--win-only': + return 1 + if sys.platform in ('win32', 'cygwin'): + self_dir = os.path.dirname(sys.argv[0]) + mount_path = os.path.join(self_dir, "../../third_party/cygwin") + batch_path = os.path.join(mount_path, "setup_mount.bat") + return os.system(os.path.normpath(batch_path) + ">nul") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/build/win_precompile.gypi b/build/win_precompile.gypi new file mode 100644 index 0000000000..fb86076666 --- /dev/null +++ b/build/win_precompile.gypi @@ -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 this file to make targets in your .gyp use the default +# precompiled header on Windows, in debug builds only as the official +# builders blow up (out of memory) if precompiled headers are used for +# release builds. + +{ + 'conditions': [ + ['OS=="win" and chromium_win_pch==1', { + 'target_defaults': { + 'msvs_precompiled_header': '<(DEPTH)/build/precompile.h', + 'msvs_precompiled_source': '<(DEPTH)/build/precompile.cc', + 'sources': ['<(DEPTH)/build/precompile.cc'], + } + }], + ], +} diff --git a/media/base/bit_reader.cc b/media/base/bit_reader.cc new file mode 100644 index 0000000000..9f6f4098a1 --- /dev/null +++ b/media/base/bit_reader.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 "media/base/bit_reader.h" + +namespace media { + +BitReader::BitReader(const uint8* data, off_t size) + : data_(data), bytes_left_(size), num_remaining_bits_in_curr_byte_(0) { + DCHECK(data_ != NULL && bytes_left_ > 0); + + UpdateCurrByte(); +} + +BitReader::~BitReader() {} + +bool BitReader::SkipBits(int num_bits) { + DCHECK_GE(num_bits, 0); + DLOG_IF(INFO, num_bits > 100) + << "BitReader::SkipBits inefficient for large skips"; + + // Skip any bits in the current byte waiting to be processed, then + // process full bytes until less than 8 bits remaining. + while (num_bits > 0 && num_bits > num_remaining_bits_in_curr_byte_) { + num_bits -= num_remaining_bits_in_curr_byte_; + num_remaining_bits_in_curr_byte_ = 0; + UpdateCurrByte(); + + // If there is no more data remaining, only return true if we + // skipped all that were requested. + if (num_remaining_bits_in_curr_byte_ == 0) + return (num_bits == 0); + } + + // Less than 8 bits remaining to skip. Use ReadBitsInternal to verify + // that the remaining bits we need exist, and adjust them as necessary + // for subsequent operations. + uint64 not_needed; + return ReadBitsInternal(num_bits, ¬_needed); +} + +int BitReader::bits_available() const { + return 8 * bytes_left_ + num_remaining_bits_in_curr_byte_; +} + +bool BitReader::ReadBitsInternal(int num_bits, uint64* out) { + DCHECK_LE(num_bits, 64); + + *out = 0; + + while (num_remaining_bits_in_curr_byte_ != 0 && num_bits != 0) { + int bits_to_take = std::min(num_remaining_bits_in_curr_byte_, num_bits); + + *out <<= bits_to_take; + *out += curr_byte_ >> (num_remaining_bits_in_curr_byte_ - bits_to_take); + num_bits -= bits_to_take; + num_remaining_bits_in_curr_byte_ -= bits_to_take; + curr_byte_ &= (1 << num_remaining_bits_in_curr_byte_) - 1; + + if (num_remaining_bits_in_curr_byte_ == 0) + UpdateCurrByte(); + } + + return num_bits == 0; +} + +void BitReader::UpdateCurrByte() { + DCHECK_EQ(num_remaining_bits_in_curr_byte_, 0); + + if (bytes_left_ == 0) + return; + + // Load a new byte and advance pointers. + curr_byte_ = *data_; + ++data_; + --bytes_left_; + num_remaining_bits_in_curr_byte_ = 8; +} + +} // namespace media diff --git a/media/base/bit_reader.h b/media/base/bit_reader.h new file mode 100644 index 0000000000..8c15891c91 --- /dev/null +++ b/media/base/bit_reader.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 MEDIA_BASE_BIT_READER_H_ +#define MEDIA_BASE_BIT_READER_H_ + +#include + +#include "base/basictypes.h" +#include "base/logging.h" +#include "media/base/media_export.h" + +namespace media { + +// A class to read bit streams. +class MEDIA_EXPORT BitReader { + public: + // Initialize the reader to start reading at |data|, |size| being size + // of |data| in bytes. + BitReader(const uint8* data, off_t size); + ~BitReader(); + + // Read |num_bits| next bits from stream and return in |*out|, first bit + // from the stream starting at |num_bits| position in |*out|. + // |num_bits| cannot be larger than the bits the type can hold. + // Return false if the given number of bits cannot be read (not enough + // bits in the stream), true otherwise. When return false, the stream will + // enter a state where further ReadBits/SkipBits operations will always + // return false unless |num_bits| is 0. The type |T| has to be a primitive + // integer type. + template bool ReadBits(int num_bits, T *out) { + DCHECK_LE(num_bits, static_cast(sizeof(T) * 8)); + uint64 temp; + bool ret = ReadBitsInternal(num_bits, &temp); + *out = static_cast(temp); + return ret; + } + + // Skip |num_bits| next bits from stream. Return false if the given number of + // bits cannot be skipped (not enough bits in the stream), true otherwise. + // When return false, the stream will enter a state where further ReadBits/ + // SkipBits operations will always return false unless |num_bits| is 0. + bool SkipBits(int num_bits); + + // Returns the number of bits available for reading. + int bits_available() const; + + private: + // Help function used by ReadBits to avoid inlining the bit reading logic. + bool ReadBitsInternal(int num_bits, uint64* out); + + // Advance to the next byte, loading it into curr_byte_. + // If the num_remaining_bits_in_curr_byte_ is 0 after this function returns, + // the stream has reached the end. + void UpdateCurrByte(); + + // Pointer to the next unread (not in curr_byte_) byte in the stream. + const uint8* data_; + + // Bytes left in the stream (without the curr_byte_). + off_t bytes_left_; + + // Contents of the current byte; first unread bit starting at position + // 8 - num_remaining_bits_in_curr_byte_ from MSB. + uint8 curr_byte_; + + // Number of bits remaining in curr_byte_ + int num_remaining_bits_in_curr_byte_; + + private: + DISALLOW_COPY_AND_ASSIGN(BitReader); +}; + +} // namespace media + +#endif // MEDIA_BASE_BIT_READER_H_ diff --git a/media/base/bit_reader_unittest.cc b/media/base/bit_reader_unittest.cc new file mode 100644 index 0000000000..3dca9c632d --- /dev/null +++ b/media/base/bit_reader_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 "media/base/bit_reader.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +TEST(BitReaderTest, NormalOperationTest) { + uint8 value8; + uint64 value64; + // 0101 0101 1001 1001 repeats 4 times + uint8 buffer[] = {0x55, 0x99, 0x55, 0x99, 0x55, 0x99, 0x55, 0x99}; + BitReader reader1(buffer, 6); // Initialize with 6 bytes only + + EXPECT_TRUE(reader1.ReadBits(1, &value8)); + EXPECT_EQ(value8, 0); + EXPECT_TRUE(reader1.ReadBits(8, &value8)); + EXPECT_EQ(value8, 0xab); // 1010 1011 + EXPECT_TRUE(reader1.ReadBits(7, &value64)); + EXPECT_TRUE(reader1.ReadBits(32, &value64)); + EXPECT_EQ(value64, 0x55995599u); + EXPECT_FALSE(reader1.ReadBits(1, &value8)); + value8 = 0xff; + EXPECT_TRUE(reader1.ReadBits(0, &value8)); + EXPECT_EQ(value8, 0); + + BitReader reader2(buffer, 8); + EXPECT_TRUE(reader2.ReadBits(64, &value64)); + EXPECT_EQ(value64, 0x5599559955995599ull); + EXPECT_FALSE(reader2.ReadBits(1, &value8)); + EXPECT_TRUE(reader2.ReadBits(0, &value8)); +} + +TEST(BitReaderTest, ReadBeyondEndTest) { + uint8 value8; + uint8 buffer[] = {0x12}; + BitReader reader1(buffer, sizeof(buffer)); + + EXPECT_TRUE(reader1.ReadBits(4, &value8)); + EXPECT_FALSE(reader1.ReadBits(5, &value8)); + EXPECT_FALSE(reader1.ReadBits(1, &value8)); + EXPECT_TRUE(reader1.ReadBits(0, &value8)); +} + +TEST(BitReaderTest, SkipBitsTest) { + uint8 value8; + uint8 buffer[] = { 0x0a, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; + BitReader reader1(buffer, sizeof(buffer)); + + EXPECT_TRUE(reader1.SkipBits(2)); + EXPECT_TRUE(reader1.ReadBits(3, &value8)); + EXPECT_EQ(value8, 1); + EXPECT_TRUE(reader1.SkipBits(11)); + EXPECT_TRUE(reader1.ReadBits(8, &value8)); + EXPECT_EQ(value8, 3); + EXPECT_TRUE(reader1.SkipBits(76)); + EXPECT_TRUE(reader1.ReadBits(4, &value8)); + EXPECT_EQ(value8, 13); + EXPECT_FALSE(reader1.SkipBits(100)); + EXPECT_TRUE(reader1.SkipBits(0)); + EXPECT_FALSE(reader1.SkipBits(1)); +} + +} // namespace media diff --git a/media/base/buffers.h b/media/base/buffers.h new file mode 100644 index 0000000000..6a6c7303d1 --- /dev/null +++ b/media/base/buffers.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. + +// Timestamps are derived directly from the encoded media file and are commonly +// known as the presentation timestamp (PTS). Durations are a best-guess and +// are usually derived from the sample/frame rate of the media file. +// +// Due to encoding and transmission errors, it is not guaranteed that timestamps +// arrive in a monotonically increasing order nor that the next timestamp will +// be equal to the previous timestamp plus the duration. +// +// In the ideal scenario for a 25fps movie, buffers are timestamped as followed: +// +// Buffer0 Buffer1 Buffer2 ... BufferN +// Timestamp: 0us 40000us 80000us ... (N*40000)us +// Duration*: 40000us 40000us 40000us ... 40000us +// +// *25fps = 0.04s per frame = 40000us per frame + +#ifndef MEDIA_BASE_BUFFERS_H_ +#define MEDIA_BASE_BUFFERS_H_ + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/time/time.h" +#include "media/base/media_export.h" + +namespace media { + +// TODO(scherkus): Move the contents of this file elsewhere. + +// Indicates an invalid or missing timestamp. +MEDIA_EXPORT extern inline base::TimeDelta kNoTimestamp() { + return base::TimeDelta::FromMicroseconds(kint64min); +} + +// Represents an infinite stream duration. +MEDIA_EXPORT extern inline base::TimeDelta kInfiniteDuration() { + return base::TimeDelta::FromMicroseconds(kint64max); +} + +} // namespace media + +#endif // MEDIA_BASE_BUFFERS_H_ diff --git a/media/base/byte_queue.cc b/media/base/byte_queue.cc new file mode 100644 index 0000000000..534b55225e --- /dev/null +++ b/media/base/byte_queue.cc @@ -0,0 +1,84 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/base/byte_queue.h" + +#include "base/logging.h" + +namespace media { + +// Default starting size for the queue. +enum { kDefaultQueueSize = 1024 }; + +ByteQueue::ByteQueue() + : buffer_(new uint8[kDefaultQueueSize]), + size_(kDefaultQueueSize), + offset_(0), + used_(0) { +} + +ByteQueue::~ByteQueue() {} + +void ByteQueue::Reset() { + offset_ = 0; + used_ = 0; +} + +void ByteQueue::Push(const uint8* data, int size) { + DCHECK(data); + DCHECK_GT(size, 0); + + size_t size_needed = used_ + size; + + // Check to see if we need a bigger buffer. + if (size_needed > size_) { + size_t new_size = 2 * size_; + while (size_needed > new_size && new_size > size_) + new_size *= 2; + + // Sanity check to make sure we didn't overflow. + CHECK_GT(new_size, size_); + + scoped_ptr new_buffer(new uint8[new_size]); + + // Copy the data from the old buffer to the start of the new one. + if (used_ > 0) + memcpy(new_buffer.get(), front(), used_); + + buffer_.reset(new_buffer.release()); + size_ = new_size; + offset_ = 0; + } else if ((offset_ + used_ + size) > size_) { + // The buffer is big enough, but we need to move the data in the queue. + memmove(buffer_.get(), front(), used_); + offset_ = 0; + } + + memcpy(front() + used_, data, size); + used_ += size; +} + +void ByteQueue::Peek(const uint8** data, int* size) const { + DCHECK(data); + DCHECK(size); + *data = front(); + *size = used_; +} + +void ByteQueue::Pop(int count) { + DCHECK_LE(count, used_); + + offset_ += count; + used_ -= count; + + // Move the offset back to 0 if we have reached the end of the buffer. + if (offset_ == size_) { + DCHECK_EQ(used_, 0); + offset_ = 0; + } +} + +uint8* ByteQueue::front() const { return buffer_.get() + offset_; } + +} // namespace media diff --git a/media/base/byte_queue.h b/media/base/byte_queue.h new file mode 100644 index 0000000000..f25328d362 --- /dev/null +++ b/media/base/byte_queue.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 MEDIA_BASE_BYTE_QUEUE_H_ +#define MEDIA_BASE_BYTE_QUEUE_H_ + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "media/base/media_export.h" + +namespace media { + +// Represents a queue of bytes. +// Data is added to the end of the queue via an Push() call and removed via +// Pop(). The contents of the queue can be observed via the Peek() method. +// This class manages the underlying storage of the queue and tries to minimize +// the number of buffer copies when data is appended and removed. +class MEDIA_EXPORT ByteQueue { + public: + ByteQueue(); + ~ByteQueue(); + + // Reset the queue to the empty state. + void Reset(); + + // Appends new bytes onto the end of the queue. + void Push(const uint8* data, int size); + + // Get a pointer to the front of the queue and the queue size. + // These values are only valid until the next Push() or + // Pop() call. + void Peek(const uint8** data, int* size) const; + + // Remove |count| bytes from the front of the queue. + void Pop(int count); + + private: + // Returns a pointer to the front of the queue. + uint8* front() const; + + scoped_ptr buffer_; + + // Size of |buffer_|. + size_t size_; + + // Offset from the start of |buffer_| that marks the front of the queue. + size_t offset_; + + // Number of bytes stored in the queue. + int used_; + + DISALLOW_COPY_AND_ASSIGN(ByteQueue); +}; + +} // namespace media + +#endif // MEDIA_BASE_BYTE_QUEUE_H_ diff --git a/media/base/container_names.cc b/media/base/container_names.cc new file mode 100644 index 0000000000..f062929d54 --- /dev/null +++ b/media/base/container_names.cc @@ -0,0 +1,1671 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/base/container_names.h" + +#include +#include + +#include "base/basictypes.h" +#include "base/logging.h" +#include "media/base/bit_reader.h" + +namespace media { + +namespace container_names { + +#define TAG(a, b, c, d) \ + ((static_cast(a) << 24) | (static_cast(b) << 16) | \ + (static_cast(c) << 8) | (static_cast(d))) + +#define RCHECK(x) \ + do { \ + if (!(x)) \ + return false; \ + } while (0) + +#define UTF8_BYTE_ORDER_MARK "\xef\xbb\xbf" + +// Helper function to read 2 bytes (16 bits, big endian) from a buffer. +static int Read16(const uint8* p) { + return p[0] << 8 | p[1]; +} + +// Helper function to read 3 bytes (24 bits, big endian) from a buffer. +static uint32 Read24(const uint8* p) { + return p[0] << 16 | p[1] << 8 | p[2]; +} + +// Helper function to read 4 bytes (32 bits, big endian) from a buffer. +static uint32 Read32(const uint8* p) { + return p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3]; +} + +// Helper function to read 4 bytes (32 bits, little endian) from a buffer. +static uint32 Read32LE(const uint8* p) { + return p[3] << 24 | p[2] << 16 | p[1] << 8 | p[0]; +} + +// Helper function to do buffer comparisons with a string without going off the +// end of the buffer. +static bool StartsWith(const uint8* buffer, + size_t buffer_size, + const char* prefix) { + size_t prefix_size = strlen(prefix); + return (prefix_size <= buffer_size && + memcmp(buffer, prefix, prefix_size) == 0); +} + +// Helper function to do buffer comparisons with another buffer (to allow for +// embedded \0 in the comparison) without going off the end of the buffer. +static bool StartsWith(const uint8* buffer, + size_t buffer_size, + const uint8* prefix, + size_t prefix_size) { + return (prefix_size <= buffer_size && + memcmp(buffer, prefix, prefix_size) == 0); +} + +// Helper function to read up to 64 bits from a bit stream. +static uint64 ReadBits(BitReader* reader, int num_bits) { + DCHECK_GE(reader->bits_available(), num_bits); + DCHECK((num_bits > 0) && (num_bits <= 64)); + uint64 value; + reader->ReadBits(num_bits, &value); + return value; +} + +const int kAc3FrameSizeTable[38][3] = { + { 128, 138, 192 }, { 128, 140, 192 }, { 160, 174, 240 }, { 160, 176, 240 }, + { 192, 208, 288 }, { 192, 210, 288 }, { 224, 242, 336 }, { 224, 244, 336 }, + { 256, 278, 384 }, { 256, 280, 384 }, { 320, 348, 480 }, { 320, 350, 480 }, + { 384, 416, 576 }, { 384, 418, 576 }, { 448, 486, 672 }, { 448, 488, 672 }, + { 512, 556, 768 }, { 512, 558, 768 }, { 640, 696, 960 }, { 640, 698, 960 }, + { 768, 834, 1152 }, { 768, 836, 1152 }, { 896, 974, 1344 }, + { 896, 976, 1344 }, { 1024, 1114, 1536 }, { 1024, 1116, 1536 }, + { 1280, 1392, 1920 }, { 1280, 1394, 1920 }, { 1536, 1670, 2304 }, + { 1536, 1672, 2304 }, { 1792, 1950, 2688 }, { 1792, 1952, 2688 }, + { 2048, 2228, 3072 }, { 2048, 2230, 3072 }, { 2304, 2506, 3456 }, + { 2304, 2508, 3456 }, { 2560, 2768, 3840 }, { 2560, 2770, 3840 } +}; + +// Checks for an ADTS AAC container. +static bool CheckAac(const uint8* buffer, int buffer_size) { + // Audio Data Transport Stream (ADTS) header is 7 or 9 bytes + // (from http://wiki.multimedia.cx/index.php?title=ADTS) + RCHECK(buffer_size > 6); + + int offset = 0; + while (offset + 6 < buffer_size) { + BitReader reader(buffer + offset, 6); + + // Syncword must be 0xfff. + RCHECK(ReadBits(&reader, 12) == 0xfff); + + // Skip MPEG version. + reader.SkipBits(1); + + // Layer is always 0. + RCHECK(ReadBits(&reader, 2) == 0); + + // Skip protection + profile. + reader.SkipBits(1 + 2); + + // Check sampling frequency index. + RCHECK(ReadBits(&reader, 4) != 15); // Forbidden. + + // Skip private stream, channel configuration, originality, home, + // copyrighted stream, and copyright_start. + reader.SkipBits(1 + 3 + 1 + 1 + 1 + 1); + + // Get frame length (includes header). + int size = ReadBits(&reader, 13); + RCHECK(size > 0); + offset += size; + } + return true; +} + +const uint16 kAc3SyncWord = 0x0b77; + +// Checks for an AC3 container. +static bool CheckAc3(const uint8* buffer, int buffer_size) { + // Reference: ATSC Standard: Digital Audio Compression (AC-3, E-AC-3) + // Doc. A/52:2012 + // (http://www.atsc.org/cms/standards/A52-2012(12-17).pdf) + + // AC3 container looks like syncinfo | bsi | audblk * 6 | aux | check. + RCHECK(buffer_size > 6); + + int offset = 0; + while (offset + 6 < buffer_size) { + BitReader reader(buffer + offset, 6); + + // Check syncinfo. + RCHECK(ReadBits(&reader, 16) == kAc3SyncWord); + + // Skip crc1. + reader.SkipBits(16); + + // Verify fscod. + int sample_rate_code = ReadBits(&reader, 2); + RCHECK(sample_rate_code != 3); // Reserved. + + // Verify frmsizecod. + int frame_size_code = ReadBits(&reader, 6); + RCHECK(frame_size_code < 38); // Undefined. + + // Verify bsid. + RCHECK(ReadBits(&reader, 5) < 10); // Normally 8 or 6, 16 used by EAC3. + + offset += kAc3FrameSizeTable[frame_size_code][sample_rate_code]; + } + return true; +} + +// Checks for an EAC3 container (very similar to AC3) +static bool CheckEac3(const uint8* buffer, int buffer_size) { + // Reference: ATSC Standard: Digital Audio Compression (AC-3, E-AC-3) + // Doc. A/52:2012 + // (http://www.atsc.org/cms/standards/A52-2012(12-17).pdf) + + // EAC3 container looks like syncinfo | bsi | audfrm | audblk* | aux | check. + RCHECK(buffer_size > 6); + + int offset = 0; + while (offset + 6 < buffer_size) { + BitReader reader(buffer + offset, 6); + + // Check syncinfo. + RCHECK(ReadBits(&reader, 16) == kAc3SyncWord); + + // Verify strmtyp. + RCHECK(ReadBits(&reader, 2) != 3); + + // Skip substreamid. + reader.SkipBits(3); + + // Get frmsize. Include syncinfo size and convert to bytes. + int frame_size = (ReadBits(&reader, 11) + 1) * 2; + RCHECK(frame_size >= 7); + + // Skip fscod, fscod2, acmod, and lfeon. + reader.SkipBits(2 + 2 + 3 + 1); + + // Verify bsid. + int bit_stream_id = ReadBits(&reader, 5); + RCHECK(bit_stream_id >= 11 && bit_stream_id <= 16); + + offset += frame_size; + } + return true; +} + +// Additional checks for a BINK container. +static bool CheckBink(const uint8* buffer, int buffer_size) { + // Reference: http://wiki.multimedia.cx/index.php?title=Bink_Container + RCHECK(buffer_size >= 44); + + // Verify number of frames specified. + RCHECK(Read32LE(buffer + 8) > 0); + + // Verify width in range. + int width = Read32LE(buffer + 20); + RCHECK(width > 0 && width <= 32767); + + // Verify height in range. + int height = Read32LE(buffer + 24); + RCHECK(height > 0 && height <= 32767); + + // Verify frames per second specified. + RCHECK(Read32LE(buffer + 28) > 0); + + // Verify video frames per second specified. + RCHECK(Read32LE(buffer + 32) > 0); + + // Number of audio tracks must be 256 or less. + return (Read32LE(buffer + 40) <= 256); +} + +// Additional checks for a CAF container. +static bool CheckCaf(const uint8* buffer, int buffer_size) { + // Reference: Apple Core Audio Format Specification 1.0 + // (https://developer.apple.com/library/mac/#documentation/MusicAudio/Reference/CAFSpec/CAF_spec/CAF_spec.html) + RCHECK(buffer_size >= 52); + BitReader reader(buffer, buffer_size); + + // mFileType should be "caff". + RCHECK(ReadBits(&reader, 32) == TAG('c', 'a', 'f', 'f')); + + // mFileVersion should be 1. + RCHECK(ReadBits(&reader, 16) == 1); + + // Skip mFileFlags. + reader.SkipBits(16); + + // First chunk should be Audio Description chunk, size 32l. + RCHECK(ReadBits(&reader, 32) == TAG('d', 'e', 's', 'c')); + RCHECK(ReadBits(&reader, 64) == 32); + + // CAFAudioFormat.mSampleRate(float64) not 0 + RCHECK(ReadBits(&reader, 64) != 0); + + // CAFAudioFormat.mFormatID not 0 + RCHECK(ReadBits(&reader, 32) != 0); + + // Skip CAFAudioFormat.mBytesPerPacket and mFramesPerPacket. + reader.SkipBits(32 + 32); + + // CAFAudioFormat.mChannelsPerFrame not 0 + RCHECK(ReadBits(&reader, 32) != 0); + return true; +} + +static bool kSamplingFrequencyValid[16] = { false, true, true, true, false, + false, true, true, true, false, + false, true, true, true, false, + false }; +static bool kExtAudioIdValid[8] = { true, false, true, false, false, false, + true, false }; + +// Additional checks for a DTS container. +static bool CheckDts(const uint8* buffer, int buffer_size) { + // Reference: ETSI TS 102 114 V1.3.1 (2011-08) + // (http://www.etsi.org/deliver/etsi_ts/102100_102199/102114/01.03.01_60/ts_102114v010301p.pdf) + RCHECK(buffer_size > 11); + + int offset = 0; + while (offset + 11 < buffer_size) { + BitReader reader(buffer + offset, 11); + + // Verify sync word. + RCHECK(ReadBits(&reader, 32) == 0x7ffe8001); + + // Skip frame type and deficit sample count. + reader.SkipBits(1 + 5); + + // Verify CRC present flag. + RCHECK(ReadBits(&reader, 1) == 0); // CPF must be 0. + + // Verify number of PCM sample blocks. + RCHECK(ReadBits(&reader, 7) >= 5); + + // Verify primary frame byte size. + int frame_size = ReadBits(&reader, 14); + RCHECK(frame_size >= 95); + + // Skip audio channel arrangement. + reader.SkipBits(6); + + // Verify core audio sampling frequency is an allowed value. + RCHECK(kSamplingFrequencyValid[ReadBits(&reader, 4)]); + + // Verify transmission bit rate is valid. + RCHECK(ReadBits(&reader, 5) <= 25); + + // Verify reserved field is 0. + RCHECK(ReadBits(&reader, 1) == 0); + + // Skip dynamic range flag, time stamp flag, auxiliary data flag, and HDCD. + reader.SkipBits(1 + 1 + 1 + 1); + + // Verify extension audio descriptor flag is an allowed value. + RCHECK(kExtAudioIdValid[ReadBits(&reader, 3)]); + + // Skip extended coding flag and audio sync word insertion flag. + reader.SkipBits(1 + 1); + + // Verify low frequency effects flag is an allowed value. + RCHECK(ReadBits(&reader, 2) != 3); + + offset += frame_size + 1; + } + return true; +} + +// Checks for a DV container. +static bool CheckDV(const uint8* buffer, int buffer_size) { + // Reference: SMPTE 314M (Annex A has differences with IEC 61834). + // (http://standards.smpte.org/content/978-1-61482-454-1/st-314-2005/SEC1.body.pdf) + RCHECK(buffer_size > 11); + + int offset = 0; + int current_sequence_number = -1; + int last_block_number[6]; + while (offset + 11 < buffer_size) { + BitReader reader(buffer + offset, 11); + + // Decode ID data. Sections 5, 6, and 7 are reserved. + int section = ReadBits(&reader, 3); + RCHECK(section < 5); + + // Next bit must be 1. + RCHECK(ReadBits(&reader, 1) == 1); + + // Skip arbitrary bits. + reader.SkipBits(4); + + int sequence_number = ReadBits(&reader, 4); + + // Skip FSC. + reader.SkipBits(1); + + // Next 3 bits must be 1. + RCHECK(ReadBits(&reader, 3) == 7); + + int block_number = ReadBits(&reader, 8); + + if (section == 0) { // Header. + // Validate the reserved bits in the next 8 bytes. + reader.SkipBits(1); + RCHECK(ReadBits(&reader, 1) == 0); + RCHECK(ReadBits(&reader, 11) == 0x7ff); + reader.SkipBits(4); + RCHECK(ReadBits(&reader, 4) == 0xf); + reader.SkipBits(4); + RCHECK(ReadBits(&reader, 4) == 0xf); + reader.SkipBits(4); + RCHECK(ReadBits(&reader, 4) == 0xf); + reader.SkipBits(3); + RCHECK(ReadBits(&reader, 24) == 0xffffff); + current_sequence_number = sequence_number; + for (size_t i = 0; i < arraysize(last_block_number); ++i) + last_block_number[i] = -1; + } else { + // Sequence number must match (this will also fail if no header seen). + RCHECK(sequence_number == current_sequence_number); + // Block number should be increasing. + RCHECK(block_number > last_block_number[section]); + last_block_number[section] = block_number; + } + + // Move to next block. + offset += 80; + } + return true; +} + + +// Checks for a GSM container. +static bool CheckGsm(const uint8* buffer, int buffer_size) { + // Reference: ETSI EN 300 961 V8.1.1 + // (http://www.etsi.org/deliver/etsi_en/300900_300999/300961/08.01.01_60/en_300961v080101p.pdf) + // also http://tools.ietf.org/html/rfc3551#page-24 + // GSM files have a 33 byte block, only first 4 bits are fixed. + RCHECK(buffer_size >= 1024); // Need enough data to do a decent check. + + int offset = 0; + while (offset < buffer_size) { + // First 4 bits of each block are xD. + RCHECK((buffer[offset] & 0xf0) == 0xd0); + offset += 33; + } + return true; +} + +// Advance to the first set of |num_bits| bits that match |start_code|. |offset| +// is the current location in the buffer, and is updated. |bytes_needed| is the +// number of bytes that must remain in the buffer when |start_code| is found. +// Returns true if start_code found (and enough space in the buffer after it), +// false otherwise. +static bool AdvanceToStartCode(const uint8* buffer, + int buffer_size, + int* offset, + int bytes_needed, + int num_bits, + uint32 start_code) { + DCHECK_GE(bytes_needed, 3); + DCHECK_LE(num_bits, 24); // Only supports up to 24 bits. + + // Create a mask to isolate |num_bits| bits, once shifted over. + uint32 bits_to_shift = 24 - num_bits; + uint32 mask = (1 << num_bits) - 1; + while (*offset + bytes_needed < buffer_size) { + uint32 next = Read24(buffer + *offset); + if (((next >> bits_to_shift) & mask) == start_code) + return true; + ++(*offset); + } + return false; +} + +// Checks for an H.261 container. +static bool CheckH261(const uint8* buffer, int buffer_size) { + // Reference: ITU-T Recommendation H.261 (03/1993) + // (http://www.itu.int/rec/T-REC-H.261-199303-I/en) + RCHECK(buffer_size > 16); + + int offset = 0; + bool seen_start_code = false; + while (true) { + // Advance to picture_start_code, if there is one. + if (!AdvanceToStartCode(buffer, buffer_size, &offset, 4, 20, 0x10)) { + // No start code found (or off end of buffer), so success if + // there was at least one valid header. + return seen_start_code; + } + + // Now verify the block. AdvanceToStartCode() made sure that there are + // at least 4 bytes remaining in the buffer. + BitReader reader(buffer + offset, buffer_size - offset); + RCHECK(ReadBits(&reader, 20) == 0x10); + + // Skip the temporal reference and PTYPE. + reader.SkipBits(5 + 6); + + // Skip any extra insertion information. Since this is open-ended, if we run + // out of bits assume that the buffer is correctly formatted. + int extra = ReadBits(&reader, 1); + while (extra == 1) { + if (!reader.SkipBits(8)) + return seen_start_code; + if (!reader.ReadBits(1, &extra)) + return seen_start_code; + } + + // Next should be a Group of Blocks start code. Again, if we run out of + // bits, then assume that the buffer up to here is correct, and the buffer + // just happened to end in the middle of a header. + int next; + if (!reader.ReadBits(16, &next)) + return seen_start_code; + RCHECK(next == 1); + + // Move to the next block. + seen_start_code = true; + offset += 4; + } +} + +// Checks for an H.263 container. +static bool CheckH263(const uint8* buffer, int buffer_size) { + // Reference: ITU-T Recommendation H.263 (01/2005) + // (http://www.itu.int/rec/T-REC-H.263-200501-I/en) + // header is PSC(22b) + TR(8b) + PTYPE(8+b). + RCHECK(buffer_size > 16); + + int offset = 0; + bool seen_start_code = false; + while (true) { + // Advance to picture_start_code, if there is one. + if (!AdvanceToStartCode(buffer, buffer_size, &offset, 9, 22, 0x20)) { + // No start code found (or off end of buffer), so success if + // there was at least one valid header. + return seen_start_code; + } + + // Now verify the block. AdvanceToStartCode() made sure that there are + // at least 9 bytes remaining in the buffer. + BitReader reader(buffer + offset, 9); + RCHECK(ReadBits(&reader, 22) == 0x20); + + // Skip the temporal reference. + reader.SkipBits(8); + + // Verify that the first 2 bits of PTYPE are 10b. + RCHECK(ReadBits(&reader, 2) == 2); + + // Skip the split screen indicator, document camera indicator, and full + // picture freeze release. + reader.SkipBits(1 + 1 + 1); + + // Verify Source Format. + int format = ReadBits(&reader, 3); + RCHECK(format != 0 && format != 6); // Forbidden or reserved. + + if (format == 7) { + // Verify full extended PTYPE. + int ufep = ReadBits(&reader, 3); + if (ufep == 1) { + // Verify the optional part of PLUSPTYPE. + format = ReadBits(&reader, 3); + RCHECK(format != 0 && format != 7); // Reserved. + reader.SkipBits(11); + // Next 4 bits should be b1000. + RCHECK(ReadBits(&reader, 4) == 8); // Not allowed. + } else { + RCHECK(ufep == 0); // Only 0 and 1 allowed. + } + + // Verify picture type code is not a reserved value. + int picture_type_code = ReadBits(&reader, 3); + RCHECK(picture_type_code != 6 && picture_type_code != 7); // Reserved. + + // Skip picture resampling mode, reduced resolution mode, + // and rounding type. + reader.SkipBits(1 + 1 + 1); + + // Next 3 bits should be b001. + RCHECK(ReadBits(&reader, 3) == 1); // Not allowed. + } + + // Move to the next block. + seen_start_code = true; + offset += 9; + } +} + +// Checks for an H.264 container. +static bool CheckH264(const uint8* buffer, int buffer_size) { + // Reference: ITU-T Recommendation H.264 (01/2012) + // (http://www.itu.int/rec/T-REC-H.264) + // Section B.1: Byte stream NAL unit syntax and semantics. + RCHECK(buffer_size > 4); + + int offset = 0; + int parameter_count = 0; + while (true) { + // Advance to picture_start_code, if there is one. + if (!AdvanceToStartCode(buffer, buffer_size, &offset, 4, 24, 1)) { + // No start code found (or off end of buffer), so success if + // there was at least one valid header. + return parameter_count > 0; + } + + // Now verify the block. AdvanceToStartCode() made sure that there are + // at least 4 bytes remaining in the buffer. + BitReader reader(buffer + offset, 4); + RCHECK(ReadBits(&reader, 24) == 1); + + // Verify forbidden_zero_bit. + RCHECK(ReadBits(&reader, 1) == 0); + + // Extract nal_ref_idc and nal_unit_type. + int nal_ref_idc = ReadBits(&reader, 2); + int nal_unit_type = ReadBits(&reader, 5); + + switch (nal_unit_type) { + case 5: // Coded slice of an IDR picture. + RCHECK(nal_ref_idc != 0); + break; + case 6: // Supplemental enhancement information (SEI). + case 9: // Access unit delimiter. + case 10: // End of sequence. + case 11: // End of stream. + case 12: // Filler data. + RCHECK(nal_ref_idc == 0); + break; + case 7: // Sequence parameter set. + case 8: // Picture parameter set. + ++parameter_count; + break; + } + + // Skip the current start_code_prefix and move to the next. + offset += 4; + } +} + +static const char kHlsSignature[] = "#EXTM3U"; +static const char kHls1[] = "#EXT-X-STREAM-INF:"; +static const char kHls2[] = "#EXT-X-TARGETDURATION:"; +static const char kHls3[] = "#EXT-X-MEDIA-SEQUENCE:"; + +// Additional checks for a HLS container. +static bool CheckHls(const uint8* buffer, int buffer_size) { + // HLS is simply a play list used for Apple HTTP Live Streaming. + // Reference: Apple HTTP Live Streaming Overview + // (http://goo.gl/MIwxj) + + if (StartsWith(buffer, buffer_size, kHlsSignature)) { + // Need to find "#EXT-X-STREAM-INF:", "#EXT-X-TARGETDURATION:", or + // "#EXT-X-MEDIA-SEQUENCE:" somewhere in the buffer. Other playlists (like + // WinAmp) only have additional lines with #EXTINF + // (http://en.wikipedia.org/wiki/M3U). + int offset = strlen(kHlsSignature); + while (offset < buffer_size) { + if (buffer[offset] == '#') { + if (StartsWith(buffer + offset, buffer_size - offset, kHls1) || + StartsWith(buffer + offset, buffer_size - offset, kHls2) || + StartsWith(buffer + offset, buffer_size - offset, kHls3)) { + return true; + } + } + ++offset; + } + } + return false; +} + +// Checks for a MJPEG stream. +static bool CheckMJpeg(const uint8* buffer, int buffer_size) { + // Reference: ISO/IEC 10918-1 : 1993(E), Annex B + // (http://www.w3.org/Graphics/JPEG/itu-t81.pdf) + RCHECK(buffer_size >= 16); + + int offset = 0; + int last_restart = -1; + int num_codes = 0; + while (offset + 5 < buffer_size) { + // Marker codes are always a two byte code with the first byte xFF. + RCHECK(buffer[offset] == 0xff); + uint8 code = buffer[offset + 1]; + RCHECK(code >= 0xc0 || code == 1); + + // Skip sequences of xFF. + if (code == 0xff) { + ++offset; + continue; + } + + // Success if the next marker code is EOI (end of image) + if (code == 0xd9) + return true; + + // Check remaining codes. + if (code == 0xd8 || code == 1) { + // SOI (start of image) / TEM (private use). No other data with header. + offset += 2; + } else if (code >= 0xd0 && code <= 0xd7) { + // RST (restart) codes must be in sequence. No other data with header. + int restart = code & 0x07; + if (last_restart >= 0) + RCHECK(restart == (last_restart + 1) % 8); + last_restart = restart; + offset += 2; + } else { + // All remaining marker codes are followed by a length of the header. + int length = Read16(buffer + offset + 2) + 2; + + // Special handling of SOS (start of scan) marker since the entropy + // coded data follows the SOS. Any xFF byte in the data block must be + // followed by x00 in the data. + if (code == 0xda) { + int number_components = buffer[offset + 4]; + RCHECK(length == 8 + 2 * number_components); + + // Advance to the next marker. + offset += length; + while (offset + 2 < buffer_size) { + if (buffer[offset] == 0xff && buffer[offset + 1] != 0) + break; + ++offset; + } + } else { + // Skip over the marker data for the other marker codes. + offset += length; + } + } + ++num_codes; + } + return (num_codes > 1); +} + +enum Mpeg2StartCodes { + PROGRAM_END_CODE = 0xb9, + PACK_START_CODE = 0xba +}; + +// Checks for a MPEG2 Program Stream. +static bool CheckMpeg2ProgramStream(const uint8* buffer, int buffer_size) { + // Reference: ISO/IEC 13818-1 : 2000 (E) / ITU-T Rec. H.222.0 (2000 E). + RCHECK(buffer_size > 14); + + int offset = 0; + while (offset + 14 < buffer_size) { + BitReader reader(buffer + offset, 14); + + // Must start with pack_start_code. + RCHECK(ReadBits(&reader, 24) == 1); + RCHECK(ReadBits(&reader, 8) == PACK_START_CODE); + + // Determine MPEG version (MPEG1 has b0010, while MPEG2 has b01). + int mpeg_version = ReadBits(&reader, 2); + if (mpeg_version == 0) { + // MPEG1, 10 byte header + // Validate rest of version code + RCHECK(ReadBits(&reader, 2) == 2); + } else { + RCHECK(mpeg_version == 1); + } + + // Skip system_clock_reference_base [32..30]. + reader.SkipBits(3); + + // Verify marker bit. + RCHECK(ReadBits(&reader, 1) == 1); + + // Skip system_clock_reference_base [29..15]. + reader.SkipBits(15); + + // Verify next marker bit. + RCHECK(ReadBits(&reader, 1) == 1); + + // Skip system_clock_reference_base [14..0]. + reader.SkipBits(15); + + // Verify next marker bit. + RCHECK(ReadBits(&reader, 1) == 1); + + if (mpeg_version == 0) { + // Verify second marker bit. + RCHECK(ReadBits(&reader, 1) == 1); + + // Skip mux_rate. + reader.SkipBits(22); + + // Verify next marker bit. + RCHECK(ReadBits(&reader, 1) == 1); + + // Update offset to be after this header. + offset += 12; + } else { + // Must be MPEG2. + // Skip program_mux_rate. + reader.SkipBits(22); + + // Verify pair of marker bits. + RCHECK(ReadBits(&reader, 2) == 3); + + // Skip reserved. + reader.SkipBits(5); + + // Update offset to be after this header. + int pack_stuffing_length = ReadBits(&reader, 3); + offset += 14 + pack_stuffing_length; + } + + // Check for system headers and PES_packets. + while (offset + 6 < buffer_size && Read24(buffer + offset) == 1) { + // Next 8 bits determine stream type. + int stream_id = buffer[offset + 3]; + + // Some stream types are reserved and shouldn't occur. + if (mpeg_version == 0) + RCHECK(stream_id != 0xbc && stream_id < 0xf0); + else + RCHECK(stream_id != 0xfc && stream_id != 0xfd && stream_id != 0xfe); + + // Some stream types are used for pack headers. + if (stream_id == PACK_START_CODE) // back to outer loop. + break; + if (stream_id == PROGRAM_END_CODE) // end of stream. + return true; + + int pes_length = Read16(buffer + offset + 4); + RCHECK(pes_length > 0); + offset = offset + 6 + pes_length; + } + } + // Success as we are off the end of the buffer and liked everything + // in the buffer. + return true; +} + +const uint8 kMpeg2SyncWord = 0x47; + +// Checks for a MPEG2 Transport Stream. +static bool CheckMpeg2TransportStream(const uint8* buffer, int buffer_size) { + // Spec: ISO/IEC 13818-1 : 2000 (E) / ITU-T Rec. H.222.0 (2000 E). + // Normal packet size is 188 bytes. However, some systems add various error + // correction data at the end, resulting in packet of length 192/204/208 + // (https://en.wikipedia.org/wiki/MPEG_transport_stream). Determine the + // length with the first packet. + RCHECK(buffer_size >= 250); // Want more than 1 packet to check. + + int offset = 0; + int packet_length = -1; + while (buffer[offset] != kMpeg2SyncWord && offset < 20) { + // Skip over any header in the first 20 bytes. + ++offset; + } + + while (offset + 6 < buffer_size) { + BitReader reader(buffer + offset, 6); + + // Must start with sync byte. + RCHECK(ReadBits(&reader, 8) == kMpeg2SyncWord); + + // Skip transport_error_indicator, payload_unit_start_indicator, and + // transport_priority. + reader.SkipBits(1 + 1 + 1); + + // Verify the pid is not a reserved value. + int pid = ReadBits(&reader, 13); + RCHECK(pid < 3 || pid > 15); + + // Skip transport_scrambling_control. + reader.SkipBits(2); + + // Adaptation_field_control can not be 0. + int adaptation_field_control = ReadBits(&reader, 2); + RCHECK(adaptation_field_control != 0); + + // If there is an adaptation_field, verify it. + if (adaptation_field_control >= 2) { + // Skip continuity_counter. + reader.SkipBits(4); + + // Get adaptation_field_length and verify it. + int adaptation_field_length = ReadBits(&reader, 8); + if (adaptation_field_control == 2) + RCHECK(adaptation_field_length == 183); + else + RCHECK(adaptation_field_length <= 182); + } + + // Attempt to determine the packet length on the first packet. + if (packet_length < 0) { + if (buffer[offset + 188] == kMpeg2SyncWord) + packet_length = 188; + else if (buffer[offset + 192] == kMpeg2SyncWord) + packet_length = 192; + else if (buffer[offset + 204] == kMpeg2SyncWord) + packet_length = 204; + else + packet_length = 208; + } + offset += packet_length; + } + return true; +} + +enum Mpeg4StartCodes { + VISUAL_OBJECT_SEQUENCE_START_CODE = 0xb0, + VISUAL_OBJECT_SEQUENCE_END_CODE = 0xb1, + VISUAL_OBJECT_START_CODE = 0xb5, + VOP_START_CODE = 0xb6 +}; + +// Checks for a raw MPEG4 bitstream container. +static bool CheckMpeg4BitStream(const uint8* buffer, int buffer_size) { + // Defined in ISO/IEC 14496-2:2001. + // However, no length ... simply scan for start code values. + // Note tags are very similar to H.264. + RCHECK(buffer_size > 4); + + int offset = 0; + int sequence_start_count = 0; + int sequence_end_count = 0; + int visual_object_count = 0; + int vop_count = 0; + while (true) { + // Advance to start_code, if there is one. + if (!AdvanceToStartCode(buffer, buffer_size, &offset, 6, 24, 1)) { + // Not a complete sequence in memory, so return true if we've seen a + // visual_object_sequence_start_code and a visual_object_start_code. + return (sequence_start_count > 0 && visual_object_count > 0); + } + + // Now verify the block. AdvanceToStartCode() made sure that there are + // at least 6 bytes remaining in the buffer. + BitReader reader(buffer + offset, 6); + RCHECK(ReadBits(&reader, 24) == 1); + + int start_code = ReadBits(&reader, 8); + RCHECK(start_code < 0x30 || start_code > 0xaf); // 30..AF and + RCHECK(start_code < 0xb7 || start_code > 0xb9); // B7..B9 reserved + + switch (start_code) { + case VISUAL_OBJECT_SEQUENCE_START_CODE: { + ++sequence_start_count; + // Verify profile in not one of many reserved values. + int profile = ReadBits(&reader, 8); + RCHECK(profile > 0); + RCHECK(profile < 0x04 || profile > 0x10); + RCHECK(profile < 0x13 || profile > 0x20); + RCHECK(profile < 0x23 || profile > 0x31); + RCHECK(profile < 0x35 || profile > 0x41); + RCHECK(profile < 0x43 || profile > 0x60); + RCHECK(profile < 0x65 || profile > 0x70); + RCHECK(profile < 0x73 || profile > 0x80); + RCHECK(profile < 0x83 || profile > 0x90); + RCHECK(profile < 0x95 || profile > 0xa0); + RCHECK(profile < 0xa4 || profile > 0xb0); + RCHECK(profile < 0xb5 || profile > 0xc0); + RCHECK(profile < 0xc3 || profile > 0xd0); + RCHECK(profile < 0xe4); + break; + } + + case VISUAL_OBJECT_SEQUENCE_END_CODE: + RCHECK(++sequence_end_count == sequence_start_count); + break; + + case VISUAL_OBJECT_START_CODE: { + ++visual_object_count; + if (ReadBits(&reader, 1) == 1) { + int visual_object_verid = ReadBits(&reader, 4); + RCHECK(visual_object_verid > 0 && visual_object_verid < 3); + RCHECK(ReadBits(&reader, 3) != 0); + } + int visual_object_type = ReadBits(&reader, 4); + RCHECK(visual_object_type > 0 && visual_object_type < 6); + break; + } + + case VOP_START_CODE: + RCHECK(++vop_count <= visual_object_count); + break; + } + // Skip this block. + offset += 6; + } +} + +// Additional checks for a MOV/QuickTime/MPEG4 container. +static bool CheckMov(const uint8* buffer, int buffer_size) { + // Reference: ISO/IEC 14496-12:2005(E). + // (http://standards.iso.org/ittf/PubliclyAvailableStandards/c061988_ISO_IEC_14496-12_2012.zip) + RCHECK(buffer_size > 8); + + int offset = 0; + while (offset + 8 < buffer_size) { + int atomsize = Read32(buffer + offset); + uint32 atomtype = Read32(buffer + offset + 4); + // Only need to check for ones that are valid at the top level. + switch (atomtype) { + case TAG('f','t','y','p'): + case TAG('p','d','i','n'): + case TAG('m','o','o','v'): + case TAG('m','o','o','f'): + case TAG('m','f','r','a'): + case TAG('m','d','a','t'): + case TAG('f','r','e','e'): + case TAG('s','k','i','p'): + case TAG('m','e','t','a'): + case TAG('m','e','c','o'): + case TAG('s','t','y','p'): + case TAG('s','i','d','x'): + case TAG('s','s','i','x'): + case TAG('p','r','f','t'): + case TAG('b','l','o','c'): + break; + default: + return false; + } + if (atomsize == 1) { + // Indicates that the length is the next 64bits. + if (offset + 16 > buffer_size) + break; + if (Read32(buffer + offset + 8) != 0) + break; // Offset is way past buffer size. + atomsize = Read32(buffer + offset + 12); + } + if (atomsize <= 0) + break; // Indicates the last atom or length too big. + offset += atomsize; + } + return true; +} + +enum MPEGVersion { + VERSION_25 = 0, + VERSION_RESERVED, + VERSION_2, + VERSION_1 +}; +enum MPEGLayer { + L_RESERVED = 0, + LAYER_3, + LAYER_2, + LAYER_1 +}; + +static int kSampleRateTable[4][4] = { { 11025, 12000, 8000, 0 }, // v2.5 + { 0, 0, 0, 0 }, // not used + { 22050, 24000, 16000, 0 }, // v2 + { 44100, 48000, 32000, 0 } // v1 +}; + +static int kBitRateTableV1L1[16] = { 0, 32, 64, 96, 128, 160, 192, 224, 256, + 288, 320, 352, 384, 416, 448, 0 }; +static int kBitRateTableV1L2[16] = { 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, + 192, 224, 256, 320, 384, 0 }; +static int kBitRateTableV1L3[16] = { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, + 160, 192, 224, 256, 320, 0 }; +static int kBitRateTableV2L1[16] = { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, + 160, 176, 192, 224, 256, 0 }; +static int kBitRateTableV2L23[16] = { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, + 112, 128, 144, 160, 0 }; + +static bool ValidMpegAudioFrameHeader(const uint8* header, + int header_size, + int* framesize) { + // Reference: http://mpgedit.org/mpgedit/mpeg_format/mpeghdr.htm. + DCHECK_GE(header_size, 4); + *framesize = 0; + BitReader reader(header, 4); // Header can only be 4 bytes long. + + // Verify frame sync (11 bits) are all set. + RCHECK(ReadBits(&reader, 11) == 0x7ff); + + // Verify MPEG audio version id. + int version = ReadBits(&reader, 2); + RCHECK(version != 1); // Reserved. + + // Verify layer. + int layer = ReadBits(&reader, 2); + RCHECK(layer != 0); + + // Skip protection bit. + reader.SkipBits(1); + + // Verify bitrate index. + int bitrate_index = ReadBits(&reader, 4); + RCHECK(bitrate_index != 0xf); + + // Verify sampling rate frequency index. + int sampling_index = ReadBits(&reader, 2); + RCHECK(sampling_index != 3); + + // Get padding bit. + int padding = ReadBits(&reader, 1); + + // Frame size: + // For Layer I files = (12 * BitRate / SampleRate + Padding) * 4 + // For others = 144 * BitRate / SampleRate + Padding + // Unfortunately, BitRate and SampleRate are coded. + int sampling_rate = kSampleRateTable[version][sampling_index]; + int bitrate; + if (version == VERSION_1) { + if (layer == LAYER_1) + bitrate = kBitRateTableV1L1[bitrate_index]; + else if (layer == LAYER_2) + bitrate = kBitRateTableV1L2[bitrate_index]; + else + bitrate = kBitRateTableV1L3[bitrate_index]; + } else { + if (layer == LAYER_1) + bitrate = kBitRateTableV2L1[bitrate_index]; + else + bitrate = kBitRateTableV2L23[bitrate_index]; + } + if (layer == LAYER_1) + *framesize = ((12000 * bitrate) / sampling_rate + padding) * 4; + else + *framesize = (144000 * bitrate) / sampling_rate + padding; + return (bitrate > 0 && sampling_rate > 0); +} + +// Extract a size encoded the MP3 way. +static int GetMp3HeaderSize(const uint8* buffer, int buffer_size) { + DCHECK_GE(buffer_size, 9); + int size = ((buffer[6] & 0x7f) << 21) + ((buffer[7] & 0x7f) << 14) + + ((buffer[8] & 0x7f) << 7) + (buffer[9] & 0x7f) + 10; + if (buffer[5] & 0x10) // Footer added? + size += 10; + return size; +} + +// Additional checks for a MP3 container. +static bool CheckMp3(const uint8* buffer, int buffer_size, bool seenHeader) { + RCHECK(buffer_size >= 10); // Must be enough to read the initial header. + + int framesize; + int numSeen = 0; + int offset = 0; + if (seenHeader) { + offset = GetMp3HeaderSize(buffer, buffer_size); + } else { + // Skip over leading 0's. + while (offset < buffer_size && buffer[offset] == 0) + ++offset; + } + + while (offset + 3 < buffer_size) { + RCHECK(ValidMpegAudioFrameHeader( + buffer + offset, buffer_size - offset, &framesize)); + + // Have we seen enough valid headers? + if (++numSeen > 10) + return true; + offset += framesize; + } + // Off the end of the buffer, return success if a few valid headers seen. + return numSeen > 2; +} + +// Check that the next characters in |buffer| represent a number. The format +// accepted is optional whitespace followed by 1 or more digits. |max_digits| +// specifies the maximum number of digits to process. Returns true if a valid +// number is found, false otherwise. +static bool VerifyNumber(const uint8* buffer, + int buffer_size, + int* offset, + int max_digits) { + RCHECK(*offset < buffer_size); + + // Skip over any leading space. + while (isspace(buffer[*offset])) { + ++(*offset); + RCHECK(*offset < buffer_size); + } + + // Need to process up to max_digits digits. + int numSeen = 0; + while (--max_digits >= 0 && isdigit(buffer[*offset])) { + ++numSeen; + ++(*offset); + if (*offset >= buffer_size) + return true; // Out of space but seen a digit. + } + + // Success if at least one digit seen. + return (numSeen > 0); +} + +// Check that the next character in |buffer| is one of |c1| or |c2|. |c2| is +// optional. Returns true if there is a match, false if no match or out of +// space. +static inline bool VerifyCharacters(const uint8* buffer, + int buffer_size, + int* offset, + char c1, + char c2) { + RCHECK(*offset < buffer_size); + char c = static_cast(buffer[(*offset)++]); + return (c == c1 || (c == c2 && c2 != 0)); +} + +// Checks for a SRT container. +static bool CheckSrt(const uint8* buffer, int buffer_size) { + // Reference: http://en.wikipedia.org/wiki/SubRip + RCHECK(buffer_size > 20); + + // First line should just be the subtitle sequence number. + int offset = StartsWith(buffer, buffer_size, UTF8_BYTE_ORDER_MARK) ? 3 : 0; + RCHECK(VerifyNumber(buffer, buffer_size, &offset, 100)); + RCHECK(VerifyCharacters(buffer, buffer_size, &offset, '\n', '\r')); + + // Skip any additional \n\r. + while (VerifyCharacters(buffer, buffer_size, &offset, '\n', '\r')) {} + --offset; // Since VerifyCharacters() gobbled up the next non-CR/LF. + + // Second line should look like the following: + // 00:00:10,500 --> 00:00:13,000 + // Units separator can be , or . + RCHECK(VerifyNumber(buffer, buffer_size, &offset, 100)); + RCHECK(VerifyCharacters(buffer, buffer_size, &offset, ':', 0)); + RCHECK(VerifyNumber(buffer, buffer_size, &offset, 2)); + RCHECK(VerifyCharacters(buffer, buffer_size, &offset, ':', 0)); + RCHECK(VerifyNumber(buffer, buffer_size, &offset, 2)); + RCHECK(VerifyCharacters(buffer, buffer_size, &offset, ',', '.')); + RCHECK(VerifyNumber(buffer, buffer_size, &offset, 3)); + RCHECK(VerifyCharacters(buffer, buffer_size, &offset, ' ', 0)); + RCHECK(VerifyCharacters(buffer, buffer_size, &offset, '-', 0)); + RCHECK(VerifyCharacters(buffer, buffer_size, &offset, '-', 0)); + RCHECK(VerifyCharacters(buffer, buffer_size, &offset, '>', 0)); + RCHECK(VerifyCharacters(buffer, buffer_size, &offset, ' ', 0)); + RCHECK(VerifyNumber(buffer, buffer_size, &offset, 100)); + RCHECK(VerifyCharacters(buffer, buffer_size, &offset, ':', 0)); + RCHECK(VerifyNumber(buffer, buffer_size, &offset, 2)); + RCHECK(VerifyCharacters(buffer, buffer_size, &offset, ':', 0)); + RCHECK(VerifyNumber(buffer, buffer_size, &offset, 2)); + RCHECK(VerifyCharacters(buffer, buffer_size, &offset, ',', '.')); + RCHECK(VerifyNumber(buffer, buffer_size, &offset, 3)); + return true; +} + +// Read a Matroska Element Id. +static int GetElementId(BitReader* reader) { + // Element ID is coded with the leading zero bits (max 3) determining size. + // If it is an invalid encoding or the end of the buffer is reached, + // return -1 as a tag that won't be expected. + if (reader->bits_available() >= 8) { + int num_bits_to_read = 0; + static int prefix[] = { 0x80, 0x4000, 0x200000, 0x10000000 }; + for (int i = 0; i < 4; ++i) { + num_bits_to_read += 7; + if (ReadBits(reader, 1) == 1) { + if (reader->bits_available() < num_bits_to_read) + break; + // prefix[] adds back the bits read individually. + return ReadBits(reader, num_bits_to_read) | prefix[i]; + } + } + } + // Invalid encoding, return something not expected. + return -1; +} + +// Read a Matroska Unsigned Integer (VINT). +static uint64 GetVint(BitReader* reader) { + // Values are coded with the leading zero bits (max 7) determining size. + // If it is an invalid coding or the end of the buffer is reached, + // return something that will go off the end of the buffer. + if (reader->bits_available() >= 8) { + int num_bits_to_read = 0; + for (int i = 0; i < 8; ++i) { + num_bits_to_read += 7; + if (ReadBits(reader, 1) == 1) { + if (reader->bits_available() < num_bits_to_read) + break; + return ReadBits(reader, num_bits_to_read); + } + } + } + // Incorrect format (more than 7 leading 0's) or off the end of the buffer. + // Since the return value is used as a byte size, return a value that will + // cause a failure when used. + return (reader->bits_available() / 8) + 2; +} + +// Additional checks for a WEBM container. +static bool CheckWebm(const uint8* buffer, int buffer_size) { + // Reference: http://www.matroska.org/technical/specs/index.html + RCHECK(buffer_size > 12); + + BitReader reader(buffer, buffer_size); + + // Verify starting Element Id. + RCHECK(GetElementId(&reader) == 0x1a45dfa3); + + // Get the header size, and ensure there are enough bits to check. + int header_size = GetVint(&reader); + RCHECK(reader.bits_available() / 8 >= header_size); + + // Loop through the header. + while (reader.bits_available() > 0) { + int tag = GetElementId(&reader); + int tagsize = GetVint(&reader); + switch (tag) { + case 0x4286: // EBMLVersion + case 0x42f7: // EBMLReadVersion + case 0x42f2: // EBMLMaxIdLength + case 0x42f3: // EBMLMaxSizeLength + case 0x4287: // DocTypeVersion + case 0x4285: // DocTypeReadVersion + case 0xec: // void + case 0xbf: // CRC32 + RCHECK(reader.SkipBits(tagsize * 8)); + break; + + case 0x4282: // EBMLDocType + // Need to see "webm" or "matroska" next. + switch (ReadBits(&reader, 32)) { + case TAG('w', 'e', 'b', 'm') : + return true; + case TAG('m', 'a', 't', 'r') : + return (ReadBits(&reader, 32) == TAG('o', 's', 'k', 'a')); + } + return false; + + default: // Unrecognized tag + return false; + } + } + return false; +} + +enum VC1StartCodes { + VC1_FRAME_START_CODE = 0x0d, + VC1_ENTRY_POINT_START_CODE = 0x0e, + VC1_SEQUENCE_START_CODE = 0x0f +}; + +// Checks for a VC1 bitstream container. +static bool CheckVC1(const uint8* buffer, int buffer_size) { + // Reference: SMPTE 421M + // (http://standards.smpte.org/content/978-1-61482-555-5/st-421-2006/SEC1.body.pdf) + // However, no length ... simply scan for start code values. + // Expect to see SEQ | [ [ ENTRY ] PIC* ]* + // Note tags are very similar to H.264. + + RCHECK(buffer_size >= 24); + + // First check for Bitstream Metadata Serialization (Annex L) + if (buffer[0] == 0xc5 && + Read32(buffer + 4) == 0x04 && + Read32(buffer + 20) == 0x0c) { + // Verify settings in STRUCT_C and STRUCT_A + BitReader reader(buffer + 8, 12); + + int profile = ReadBits(&reader, 4); + if (profile == 0 || profile == 4) { // simple or main + // Skip FRMRTQ_POSTPROC, BITRTQ_POSTPROC, and LOOPFILTER. + reader.SkipBits(3 + 5 + 1); + + // Next bit must be 0. + RCHECK(ReadBits(&reader, 1) == 0); + + // Skip MULTIRES. + reader.SkipBits(1); + + // Next bit must be 1. + RCHECK(ReadBits(&reader, 1) == 1); + + // Skip FASTUVMC, EXTENDED_MV, DQUANT, and VSTRANSFORM. + reader.SkipBits(1 + 1 + 2 + 1); + + // Next bit must be 0. + RCHECK(ReadBits(&reader, 1) == 0); + + // Skip OVERLAP, SYNCMARKER, RANGERED, MAXBFRAMES, QUANTIZER, and + // FINTERPFLAG. + reader.SkipBits(1 + 1 + 1 + 3 + 2 + 1); + + // Next bit must be 1. + RCHECK(ReadBits(&reader, 1) == 1); + + } else { + RCHECK(profile == 12); // Other profile values not allowed. + RCHECK(ReadBits(&reader, 28) == 0); + } + + // Now check HORIZ_SIZE and VERT_SIZE, which must be 8192 or less. + RCHECK(ReadBits(&reader, 32) <= 8192); + RCHECK(ReadBits(&reader, 32) <= 8192); + return true; + } + + // Buffer isn't Bitstream Metadata, so scan for start codes. + int offset = 0; + int sequence_start_code = 0; + int frame_start_code = 0; + while (true) { + // Advance to start_code, if there is one. + if (!AdvanceToStartCode(buffer, buffer_size, &offset, 5, 24, 1)) { + // Not a complete sequence in memory, so return true if we've seen a + // sequence start and a frame start (not checking entry points since + // they only occur in advanced profiles). + return (sequence_start_code > 0 && frame_start_code > 0); + } + + // Now verify the block. AdvanceToStartCode() made sure that there are + // at least 5 bytes remaining in the buffer. + BitReader reader(buffer + offset, 5); + RCHECK(ReadBits(&reader, 24) == 1); + + // Keep track of the number of certain types received. + switch (ReadBits(&reader, 8)) { + case VC1_SEQUENCE_START_CODE: { + ++sequence_start_code; + switch (ReadBits(&reader, 2)) { + case 0: // simple + case 1: // main + RCHECK(ReadBits(&reader, 2) == 0); + break; + case 2: // complex + return false; + case 3: // advanced + RCHECK(ReadBits(&reader, 3) <= 4); // Verify level = 0..4 + RCHECK(ReadBits(&reader, 2) == 1); // Verify colordiff_format = 1 + break; + } + break; + } + + case VC1_ENTRY_POINT_START_CODE: + // No fields in entry data to check. However, it must occur after + // sequence header. + RCHECK(sequence_start_code > 0); + break; + + case VC1_FRAME_START_CODE: + ++frame_start_code; + break; + } + offset += 5; + } +} + +// For some formats the signature is a bunch of characters. They are defined +// below. Note that the first 4 characters of the string may be used as a TAG +// in LookupContainerByFirst4. For signatures that contain embedded \0, use +// uint8[]. +static const char kAmrSignature[] = "#!AMR"; +static const uint8 kAsfSignature[] = { 0x30, 0x26, 0xb2, 0x75, 0x8e, 0x66, 0xcf, + 0x11, 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, + 0xce, 0x6c }; +static const char kAssSignature[] = "[Script Info]"; +static const char kAssBomSignature[] = UTF8_BYTE_ORDER_MARK "[Script Info]"; +static const uint8 kWtvSignature[] = { 0xb7, 0xd8, 0x00, 0x20, 0x37, 0x49, 0xda, + 0x11, 0xa6, 0x4e, 0x00, 0x07, 0xe9, 0x5e, + 0xad, 0x8d }; + +// Attempt to determine the container type from the buffer provided. This is +// a simple pass, that uses the first 4 bytes of the buffer as an index to get +// a rough idea of the container format. +static MediaContainerName LookupContainerByFirst4(const uint8* buffer, + int buffer_size) { + // Minimum size that the code expects to exist without checking size. + if (buffer_size < 12) + return CONTAINER_UNKNOWN; + + uint32 first4 = Read32(buffer); + switch (first4) { + case 0x1a45dfa3: + if (CheckWebm(buffer, buffer_size)) + return CONTAINER_WEBM; + break; + + case 0x3026b275: + if (StartsWith(buffer, + buffer_size, + kAsfSignature, + sizeof(kAsfSignature))) { + return CONTAINER_ASF; + } + break; + + case TAG('#','!','A','M'): + if (StartsWith(buffer, buffer_size, kAmrSignature)) + return CONTAINER_AMR; + break; + + case TAG('#','E','X','T'): + if (CheckHls(buffer, buffer_size)) + return CONTAINER_HLS; + break; + + case TAG('.','R','M','F'): + if (buffer[4] == 0 && buffer[5] == 0) + return CONTAINER_RM; + break; + + case TAG('.','r','a','\xfd'): + return CONTAINER_RM; + + case TAG('B','I','K','b'): + case TAG('B','I','K','d'): + case TAG('B','I','K','f'): + case TAG('B','I','K','g'): + case TAG('B','I','K','h'): + case TAG('B','I','K','i'): + if (CheckBink(buffer, buffer_size)) + return CONTAINER_BINK; + break; + + case TAG('c','a','f','f'): + if (CheckCaf(buffer, buffer_size)) + return CONTAINER_CAF; + break; + + case TAG('D','E','X','A'): + if (buffer_size > 15 && + Read16(buffer + 11) <= 2048 && + Read16(buffer + 13) <= 2048) { + return CONTAINER_DXA; + } + break; + + case TAG('D','T','S','H'): + if (Read32(buffer + 4) == TAG('D','H','D','R')) + return CONTAINER_DTSHD; + break; + + case 0x64a30100: + case 0x64a30200: + case 0x64a30300: + case 0x64a30400: + case 0x0001a364: + case 0x0002a364: + case 0x0003a364: + if (Read32(buffer + 4) != 0 && Read32(buffer + 8) != 0) + return CONTAINER_IRCAM; + break; + + case TAG('f','L','a','C'): + return CONTAINER_FLAC; + + case TAG('F','L','V',0): + case TAG('F','L','V',1): + case TAG('F','L','V',2): + case TAG('F','L','V',3): + case TAG('F','L','V',4): + if (buffer[5] == 0 && Read32(buffer + 5) > 8) + return CONTAINER_FLV; + break; + + case TAG('F','O','R','M'): + switch (Read32(buffer + 8)) { + case TAG('A','I','F','F'): + case TAG('A','I','F','C'): + return CONTAINER_AIFF; + } + break; + + case TAG('M','A','C',' '): + return CONTAINER_APE; + + case TAG('O','N','2',' '): + if (Read32(buffer + 8) == TAG('O','N','2','f')) + return CONTAINER_AVI; + break; + + case TAG('O','g','g','S'): + if (buffer[5] <= 7) + return CONTAINER_OGG; + break; + + case TAG('R','F','6','4'): + if (buffer_size > 16 && Read32(buffer + 12) == TAG('d','s','6','4')) + return CONTAINER_WAV; + break; + + case TAG('R','I','F','F'): + switch (Read32(buffer + 8)) { + case TAG('A','V','I',' '): + case TAG('A','V','I','X'): + case TAG('A','V','I','\x19'): + case TAG('A','M','V',' '): + return CONTAINER_AVI; + case TAG('W','A','V','E'): + return CONTAINER_WAV; + } + break; + + case TAG('[','S','c','r'): + if (StartsWith(buffer, buffer_size, kAssSignature)) + return CONTAINER_ASS; + break; + + case TAG('\xef','\xbb','\xbf','['): + if (StartsWith(buffer, buffer_size, kAssBomSignature)) + return CONTAINER_ASS; + break; + + case 0x7ffe8001: + case 0xfe7f0180: + case 0x1fffe800: + case 0xff1f00e8: + if (CheckDts(buffer, buffer_size)) + return CONTAINER_DTS; + break; + + case 0xb7d80020: + if (StartsWith(buffer, + buffer_size, + kWtvSignature, + sizeof(kWtvSignature))) { + return CONTAINER_WTV; + } + break; + } + + // Now try a few different ones that look at something other + // than the first 4 bytes. + uint32 first3 = first4 & 0xffffff00; + switch (first3) { + case TAG('C','W','S',0): + case TAG('F','W','S',0): + return CONTAINER_SWF; + + case TAG('I','D','3',0): + if (CheckMp3(buffer, buffer_size, true)) + return CONTAINER_MP3; + break; + } + + // Maybe the first 2 characters are something we can use. + uint32 first2 = Read16(buffer); + switch (first2) { + case kAc3SyncWord: + if (CheckAc3(buffer, buffer_size)) + return CONTAINER_AC3; + if (CheckEac3(buffer, buffer_size)) + return CONTAINER_EAC3; + break; + + case 0xfff0: + case 0xfff1: + case 0xfff8: + case 0xfff9: + if (CheckAac(buffer, buffer_size)) + return CONTAINER_AAC; + break; + } + + // Check if the file is in MP3 format without the header. + if (CheckMp3(buffer, buffer_size, false)) + return CONTAINER_MP3; + + return CONTAINER_UNKNOWN; +} + +// Attempt to determine the container name from the buffer provided. +MediaContainerName DetermineContainer(const uint8* buffer, int buffer_size) { + DCHECK(buffer); + + // Since MOV/QuickTime/MPEG4 streams are common, check for them first. + if (CheckMov(buffer, buffer_size)) + return CONTAINER_MOV; + + // Next attempt the simple checks, that typically look at just the + // first few bytes of the file. + MediaContainerName result = LookupContainerByFirst4(buffer, buffer_size); + if (result != CONTAINER_UNKNOWN) + return result; + + // Additional checks that may scan a portion of the buffer. + if (CheckMpeg2ProgramStream(buffer, buffer_size)) + return CONTAINER_MPEG2PS; + if (CheckMpeg2TransportStream(buffer, buffer_size)) + return CONTAINER_MPEG2TS; + if (CheckMJpeg(buffer, buffer_size)) + return CONTAINER_MJPEG; + if (CheckDV(buffer, buffer_size)) + return CONTAINER_DV; + if (CheckH261(buffer, buffer_size)) + return CONTAINER_H261; + if (CheckH263(buffer, buffer_size)) + return CONTAINER_H263; + if (CheckH264(buffer, buffer_size)) + return CONTAINER_H264; + if (CheckMpeg4BitStream(buffer, buffer_size)) + return CONTAINER_MPEG4BS; + if (CheckVC1(buffer, buffer_size)) + return CONTAINER_VC1; + if (CheckSrt(buffer, buffer_size)) + return CONTAINER_SRT; + if (CheckGsm(buffer, buffer_size)) + return CONTAINER_GSM; + + // AC3/EAC3 might not start at the beginning of the stream, + // so scan for a start code. + int offset = 1; // No need to start at byte 0 due to First4 check. + if (AdvanceToStartCode(buffer, buffer_size, &offset, 4, 16, kAc3SyncWord)) { + if (CheckAc3(buffer + offset, buffer_size - offset)) + return CONTAINER_AC3; + if (CheckEac3(buffer + offset, buffer_size - offset)) + return CONTAINER_EAC3; + } + + return CONTAINER_UNKNOWN; +} + +} // namespace container_names + +} // namespace media diff --git a/media/base/container_names.h b/media/base/container_names.h new file mode 100644 index 0000000000..7b7b099a00 --- /dev/null +++ b/media/base/container_names.h @@ -0,0 +1,70 @@ +// 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 MEDIA_BASE_CONTAINER_NAMES_H_ +#define MEDIA_BASE_CONTAINER_NAMES_H_ + +#include "base/basictypes.h" +#include "media/base/media_export.h" + +namespace media { + +namespace container_names { + +// This is the set of input container formats detected for logging purposes. Not +// all of these are enabled (and it varies by product). Any additions need to be +// done at the end of the list (before CONTAINER_MAX). This list must be kept in +// sync with the enum definition "MediaContainers" in +// tools/metrics/histograms/histograms.xml. +enum MediaContainerName { + CONTAINER_UNKNOWN, // Unknown + CONTAINER_AAC, // AAC (Advanced Audio Coding) + CONTAINER_AC3, // AC-3 + CONTAINER_AIFF, // AIFF (Audio Interchange File Format) + CONTAINER_AMR, // AMR (Adaptive Multi-Rate Audio) + CONTAINER_APE, // APE (Monkey's Audio) + CONTAINER_ASF, // ASF (Advanced / Active Streaming Format) + CONTAINER_ASS, // SSA (SubStation Alpha) subtitle + CONTAINER_AVI, // AVI (Audio Video Interleaved) + CONTAINER_BINK, // Bink + CONTAINER_CAF, // CAF (Apple Core Audio Format) + CONTAINER_DTS, // DTS + CONTAINER_DTSHD, // DTS-HD + CONTAINER_DV, // DV (Digital Video) + CONTAINER_DXA, // DXA + CONTAINER_EAC3, // Enhanced AC-3 + CONTAINER_FLAC, // FLAC (Free Lossless Audio Codec) + CONTAINER_FLV, // FLV (Flash Video) + CONTAINER_GSM, // GSM (Global System for Mobile Audio) + CONTAINER_H261, // H.261 + CONTAINER_H263, // H.263 + CONTAINER_H264, // H.264 + CONTAINER_HLS, // HLS (Apple HTTP Live Streaming PlayList) + CONTAINER_IRCAM, // Berkeley/IRCAM/CARL Sound Format + CONTAINER_MJPEG, // MJPEG video + CONTAINER_MOV, // QuickTime / MOV / MPEG4 + CONTAINER_MP3, // MP3 (MPEG audio layer 2/3) + CONTAINER_MPEG2PS, // MPEG-2 Program Stream + CONTAINER_MPEG2TS, // MPEG-2 Transport Stream + CONTAINER_MPEG4BS, // MPEG-4 Bitstream + CONTAINER_OGG, // Ogg + CONTAINER_RM, // RM (RealMedia) + CONTAINER_SRT, // SRT (SubRip subtitle) + CONTAINER_SWF, // SWF (ShockWave Flash) + CONTAINER_VC1, // VC-1 + CONTAINER_WAV, // WAV / WAVE (Waveform Audio) + CONTAINER_WEBM, // Matroska / WebM + CONTAINER_WTV, // WTV (Windows Television) + CONTAINER_MAX // Must be last +}; + +// Determine the container type. +MEDIA_EXPORT MediaContainerName DetermineContainer(const uint8* buffer, + int buffer_size); + +} // namespace container_names + +} // namespace media + +#endif // MEDIA_BASE_CONTAINER_NAMES_H_ diff --git a/media/base/container_names_unittest.cc b/media/base/container_names_unittest.cc new file mode 100644 index 0000000000..21f80af6d9 --- /dev/null +++ b/media/base/container_names_unittest.cc @@ -0,0 +1,220 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/file_util.h" +#include "media/base/container_names.h" +#include "media/base/test_data_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +namespace container_names { + +// Using a macros to simplify tests. Since EXPECT_EQ outputs the second argument +// as a string when it fails, this lets the output identify what item actually +// failed. +#define VERIFY(buffer, name) \ + EXPECT_EQ(name, \ + DetermineContainer(reinterpret_cast(buffer), \ + sizeof(buffer))) + +// Test that small buffers are handled correctly. +TEST(ContainerNamesTest, CheckSmallBuffer) { + // Empty buffer. + char buffer[1]; // ([0] not allowed on win) + VERIFY(buffer, CONTAINER_UNKNOWN); + + // Try a simple SRT file. + char buffer1[] = + "1\n" + "00:03:23,550 --> 00:03:24,375\n" + "You always had a hard time finding your place in this world.\n" + "\n" + "2\n" + "00:03:24,476 --> 00:03:25,175\n" + "What are you talking about?\n"; + VERIFY(buffer1, CONTAINER_SRT); + + // HLS has it's own loop. + char buffer2[] = "#EXTM3U" + "some other random stuff" + "#EXT-X-MEDIA-SEQUENCE:"; + VERIFY(buffer2, CONTAINER_HLS); + + // Try a large buffer all zeros. + char buffer3[4096]; + memset(buffer3, 0, sizeof(buffer3)); + VERIFY(buffer3, CONTAINER_UNKNOWN); + + // Reuse buffer, but all \n this time. + memset(buffer3, '\n', sizeof(buffer3)); + VERIFY(buffer3, CONTAINER_UNKNOWN); +} + +#define BYTE_ORDER_MARK "\xef\xbb\xbf" + +// Note that the comparisons need at least 12 bytes, so make sure the buffer is +// at least that size. +const char kAmrBuffer[12] = "#!AMR"; +uint8 kAsfBuffer[] = { 0x30, 0x26, 0xb2, 0x75, 0x8e, 0x66, 0xcf, 0x11, 0xa6, + 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c }; +const char kAss1Buffer[] = "[Script Info]"; +const char kAss2Buffer[] = BYTE_ORDER_MARK "[Script Info]"; +uint8 kCafBuffer[] = { 'c', 'a', 'f', 'f', 0, 1, 0, 0, 'd', 'e', 's', 'c', 0, 0, + 0, 0, 0, 0, 0, 32, 64, 229, 136, 128, 0, 0, 0, 0, 'a', + 'a', 'c', ' ', 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, + 0, 2, 0, 0, 0, 0 }; +const char kDtshdBuffer[12] = "DTSHDHDR"; +const char kDxaBuffer[16] = "DEXA"; +const char kFlacBuffer[12] = "fLaC"; +uint8 kFlvBuffer[12] = { 'F', 'L', 'V', 0, 0, 0, 0, 1, 0, 0, 0, 0 }; +uint8 kIrcamBuffer[] = { 0x64, 0xa3, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1 }; +const char kRm1Buffer[12] = ".RMF\0\0"; +const char kRm2Buffer[12] = ".ra\xfd"; +uint8 kWtvBuffer[] = { 0xb7, 0xd8, 0x00, 0x20, 0x37, 0x49, 0xda, 0x11, 0xa6, + 0x4e, 0x00, 0x07, 0xe9, 0x5e, 0xad, 0x8d }; +uint8 kBug263073Buffer[] = { + 0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32, + 0x00, 0x00, 0x00, 0x00, 0x69, 0x73, 0x6f, 0x6d, 0x6d, 0x70, 0x34, 0x32, + 0x00, 0x00, 0x00, 0x01, 0x6d, 0x64, 0x61, 0x74, 0x00, 0x00, 0x00, 0x00, + 0xaa, 0x2e, 0x22, 0xcf, 0x00, 0x00, 0x00, 0x37, 0x67, 0x64, 0x00, 0x28, + 0xac, 0x2c, 0xa4, 0x01, 0xe0, 0x08, 0x9f, 0x97, 0x01, 0x52, 0x02, 0x02, + 0x02, 0x80, 0x00, 0x01}; + +// Test that containers that start with fixed strings are handled correctly. +// This is to verify that the TAG matches the first 4 characters of the string. +TEST(ContainerNamesTest, CheckFixedStrings) { + VERIFY(kAmrBuffer, CONTAINER_AMR); + VERIFY(kAsfBuffer, CONTAINER_ASF); + VERIFY(kAss1Buffer, CONTAINER_ASS); + VERIFY(kAss2Buffer, CONTAINER_ASS); + VERIFY(kCafBuffer, CONTAINER_CAF); + VERIFY(kDtshdBuffer, CONTAINER_DTSHD); + VERIFY(kDxaBuffer, CONTAINER_DXA); + VERIFY(kFlacBuffer, CONTAINER_FLAC); + VERIFY(kFlvBuffer, CONTAINER_FLV); + VERIFY(kIrcamBuffer, CONTAINER_IRCAM); + VERIFY(kRm1Buffer, CONTAINER_RM); + VERIFY(kRm2Buffer, CONTAINER_RM); + VERIFY(kWtvBuffer, CONTAINER_WTV); + VERIFY(kBug263073Buffer, CONTAINER_MOV); +} + +// Determine the container type of a specified file. +void TestFile(MediaContainerName expected, const base::FilePath& filename) { + char buffer[8192]; + + // Windows implementation of ReadFile fails if file smaller than desired size, + // so use file length if file less than 8192 bytes (http://crbug.com/243885). + int read_size = sizeof(buffer); + int64 actual_size; + if (file_util::GetFileSize(filename, &actual_size) && actual_size < read_size) + read_size = actual_size; + int read = file_util::ReadFile(filename, buffer, read_size); + + // Now verify the type. + EXPECT_EQ(expected, + DetermineContainer(reinterpret_cast(buffer), read)) + << "Failure with file " << filename.value(); +} + +TEST(ContainerNamesTest, FileCheckOGG) { + TestFile(CONTAINER_OGG, GetTestDataFilePath("bear.ogv")); + TestFile(CONTAINER_OGG, GetTestDataFilePath("9ch.ogg")); +} + +TEST(ContainerNamesTest, FileCheckWAV) { + TestFile(CONTAINER_WAV, GetTestDataFilePath("4ch.wav")); + TestFile(CONTAINER_WAV, GetTestDataFilePath("sfx_f32le.wav")); + TestFile(CONTAINER_WAV, GetTestDataFilePath("sfx_s16le.wav")); +} + +TEST(ContainerNamesTest, FileCheckMOV) { + TestFile(CONTAINER_MOV, GetTestDataFilePath("bear-1280x720.mp4")); + TestFile(CONTAINER_MOV, GetTestDataFilePath("sfx.m4a")); +} + +TEST(ContainerNamesTest, FileCheckWEBM) { + TestFile(CONTAINER_WEBM, GetTestDataFilePath("bear-320x240.webm")); + TestFile(CONTAINER_WEBM, GetTestDataFilePath("no_streams.webm")); + TestFile(CONTAINER_WEBM, GetTestDataFilePath("webm_ebml_element")); +} + +TEST(ContainerNamesTest, FileCheckMP3) { + TestFile(CONTAINER_MP3, GetTestDataFilePath("id3_test.mp3")); + TestFile(CONTAINER_MP3, GetTestDataFilePath("sfx.mp3")); +} + +TEST(ContainerNamesTest, FileCheckAC3) { + TestFile(CONTAINER_AC3, GetTestDataFilePath("bear.ac3")); +} + +TEST(ContainerNamesTest, FileCheckAAC) { + TestFile(CONTAINER_AAC, GetTestDataFilePath("bear.adts")); +} + +TEST(ContainerNamesTest, FileCheckAIFF) { + TestFile(CONTAINER_AIFF, GetTestDataFilePath("bear.aiff")); +} + +TEST(ContainerNamesTest, FileCheckASF) { + TestFile(CONTAINER_ASF, GetTestDataFilePath("bear.asf")); +} + +TEST(ContainerNamesTest, FileCheckAVI) { + TestFile(CONTAINER_AVI, GetTestDataFilePath("bear.avi")); +} + +TEST(ContainerNamesTest, FileCheckEAC3) { + TestFile(CONTAINER_EAC3, GetTestDataFilePath("bear.eac3")); +} + +TEST(ContainerNamesTest, FileCheckFLAC) { + TestFile(CONTAINER_FLAC, GetTestDataFilePath("bear.flac")); +} + +TEST(ContainerNamesTest, FileCheckFLV) { + TestFile(CONTAINER_FLV, GetTestDataFilePath("bear.flv")); +} + +TEST(ContainerNamesTest, FileCheckH261) { + TestFile(CONTAINER_H261, GetTestDataFilePath("bear.h261")); +} + +TEST(ContainerNamesTest, FileCheckH263) { + TestFile(CONTAINER_H263, GetTestDataFilePath("bear.h263")); +} + +TEST(ContainerNamesTest, FileCheckMJPEG) { + TestFile(CONTAINER_MJPEG, GetTestDataFilePath("bear.mjpeg")); +} + +TEST(ContainerNamesTest, FileCheckMPEG2PS) { + TestFile(CONTAINER_MPEG2PS, GetTestDataFilePath("bear.mpeg")); +} + +TEST(ContainerNamesTest, FileCheckMPEG2TS) { + TestFile(CONTAINER_MPEG2TS, GetTestDataFilePath("bear.m2ts")); +} + +TEST(ContainerNamesTest, FileCheckRM) { + TestFile(CONTAINER_RM, GetTestDataFilePath("bear.rm")); +} + +TEST(ContainerNamesTest, FileCheckSWF) { + TestFile(CONTAINER_SWF, GetTestDataFilePath("bear.swf")); +} + +// Try a few non containers. +TEST(ContainerNamesTest, FileCheckUNKNOWN) { + TestFile(CONTAINER_UNKNOWN, GetTestDataFilePath("ten_byte_file")); + TestFile(CONTAINER_UNKNOWN, GetTestDataFilePath("README")); + TestFile(CONTAINER_UNKNOWN, GetTestDataFilePath("bali_640x360_P422.yuv")); + TestFile(CONTAINER_UNKNOWN, GetTestDataFilePath("bali_640x360_RGB24.rgb")); + TestFile(CONTAINER_UNKNOWN, GetTestDataFilePath("webm_vp8_track_entry")); +} + +} // namespace container_names + +} // namespace media diff --git a/media/base/decrypt_config.cc b/media/base/decrypt_config.cc new file mode 100644 index 0000000000..53e20143e1 --- /dev/null +++ b/media/base/decrypt_config.cc @@ -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. + +#include "media/base/decrypt_config.h" + +#include "base/logging.h" + +namespace media { + +DecryptConfig::DecryptConfig(const std::string& key_id, + const std::string& iv, + const int data_offset, + const std::vector& subsamples) + : key_id_(key_id), + iv_(iv), + data_offset_(data_offset), + subsamples_(subsamples) { + CHECK_GT(key_id.size(), 0u); + CHECK(iv.size() == static_cast(DecryptConfig::kDecryptionKeySize) || + iv.empty()); + CHECK_GE(data_offset, 0); +} + +DecryptConfig::~DecryptConfig() {} + +} // namespace media diff --git a/media/base/decrypt_config.h b/media/base/decrypt_config.h new file mode 100644 index 0000000000..be0bb4d61b --- /dev/null +++ b/media/base/decrypt_config.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 MEDIA_BASE_DECRYPT_CONFIG_H_ +#define MEDIA_BASE_DECRYPT_CONFIG_H_ + +#include +#include + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "media/base/media_export.h" + +namespace media { + +// The Common Encryption spec provides for subsample encryption, where portions +// of a sample are set in cleartext. A SubsampleEntry specifies the number of +// clear and encrypted bytes in each subsample. For decryption, all of the +// encrypted bytes in a sample should be considered a single logical stream, +// regardless of how they are divided into subsamples, and the clear bytes +// should not be considered as part of decryption. This is logically equivalent +// to concatenating all 'cypher_bytes' portions of subsamples, decrypting that +// result, and then copying each byte from the decrypted block over the +// position of the corresponding encrypted byte. +struct SubsampleEntry { + uint32 clear_bytes; + uint32 cypher_bytes; +}; + +// Contains all information that a decryptor needs to decrypt a media sample. +class MEDIA_EXPORT DecryptConfig { + public: + // Keys are always 128 bits. + static const int kDecryptionKeySize = 16; + + // |key_id| is the ID that references the decryption key for this sample. + // |iv| is the initialization vector defined by the encrypted format. + // Currently |iv| must be 16 bytes as defined by WebM and ISO. Or must be + // empty which signals an unencrypted frame. + // |data_offset| is the amount of data that should be discarded from the + // head of the sample buffer before applying subsample information. A + // decrypted buffer will be shorter than an encrypted buffer by this amount. + // |subsamples| defines the clear and encrypted portions of the sample as + // described above. A decrypted buffer will be equal in size to the sum + // of the subsample sizes. + // + // |data_offset| is applied before |subsamples|. + DecryptConfig(const std::string& key_id, + const std::string& iv, + const int data_offset, + const std::vector& subsamples); + ~DecryptConfig(); + + const std::string& key_id() const { return key_id_; } + const std::string& iv() const { return iv_; } + int data_offset() const { return data_offset_; } + const std::vector& subsamples() const { return subsamples_; } + + private: + const std::string key_id_; + + // Initialization vector. + const std::string iv_; + + // TODO(fgalligan): Remove |data_offset_| if there is no plan to use it in + // the future. + // Amount of data to be discarded before applying subsample information. + const int data_offset_; + + // Subsample information. May be empty for some formats, meaning entire frame + // (less data ignored by data_offset_) is encrypted. + const std::vector subsamples_; + + DISALLOW_COPY_AND_ASSIGN(DecryptConfig); +}; + +} // namespace media + +#endif // MEDIA_BASE_DECRYPT_CONFIG_H_ diff --git a/media/base/limits.h b/media/base/limits.h new file mode 100644 index 0000000000..ed7ac513c7 --- /dev/null +++ b/media/base/limits.h @@ -0,0 +1,51 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Contains limit definition constants for the media subsystem. + +#ifndef MEDIA_BASE_LIMITS_H_ +#define MEDIA_BASE_LIMITS_H_ + +#include "base/basictypes.h" + +namespace media { + +namespace limits { + +enum { + // Maximum possible dimension (width or height) for any video. + kMaxDimension = (1 << 15) - 1, // 32767 + + // Maximum possible canvas size (width multiplied by height) for any video. + kMaxCanvas = (1 << (14 * 2)), // 16384 x 16384 + + // Total number of video frames which are populating in the pipeline. + kMaxVideoFrames = 4, + + // The following limits are used by AudioParameters::IsValid(). + // + // A few notes on sample rates of common formats: + // - AAC files are limited to 96 kHz. + // - MP3 files are limited to 48 kHz. + // - Vorbis used to be limited to 96 KHz, but no longer has that + // restriction. + // - Most PC audio hardware is limited to 192 KHz. + kMaxSampleRate = 192000, + kMinSampleRate = 3000, + kMaxChannels = 32, + kMaxBytesPerSample = 4, + kMaxBitsPerSample = kMaxBytesPerSample * 8, + kMaxSamplesPerPacket = kMaxSampleRate, + kMaxPacketSizeInBytes = + kMaxBytesPerSample * kMaxChannels * kMaxSamplesPerPacket, + + // This limit is used by ParamTraits. + kMaxFramesPerSecond = 1000, +}; + +} // namespace limits + +} // namespace media + +#endif // MEDIA_BASE_LIMITS_H_ diff --git a/media/base/test_data_util.cc b/media/base/test_data_util.cc new file mode 100644 index 0000000000..55e82fc863 --- /dev/null +++ b/media/base/test_data_util.cc @@ -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. + +#include "media/base/test_data_util.h" + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "media/base/decoder_buffer.h" + +namespace media { + +base::FilePath GetTestDataFilePath(const std::string& name) { + base::FilePath file_path; + CHECK(PathService::Get(base::DIR_SOURCE_ROOT, &file_path)); + + file_path = file_path.Append(FILE_PATH_LITERAL("media")) + .Append(FILE_PATH_LITERAL("test")).Append(FILE_PATH_LITERAL("data")) + .AppendASCII(name); + return file_path; +} + +scoped_refptr ReadTestDataFile(const std::string& name) { + base::FilePath file_path; + CHECK(PathService::Get(base::DIR_SOURCE_ROOT, &file_path)); + + file_path = file_path.Append(FILE_PATH_LITERAL("media")) + .Append(FILE_PATH_LITERAL("test")).Append(FILE_PATH_LITERAL("data")) + .AppendASCII(name); + + int64 tmp = 0; + CHECK(file_util::GetFileSize(file_path, &tmp)) + << "Failed to get file size for '" << name << "'"; + + int file_size = static_cast(tmp); + + scoped_refptr buffer(new DecoderBuffer(file_size)); + CHECK_EQ(file_size, + file_util::ReadFile( + file_path, reinterpret_cast(buffer->writable_data()), + file_size)) << "Failed to read '" << name << "'"; + + return buffer; +} + +} // namespace media diff --git a/media/base/test_data_util.h b/media/base/test_data_util.h new file mode 100644 index 0000000000..8d51e96c73 --- /dev/null +++ b/media/base/test_data_util.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 MEDIA_BASE_TEST_DATA_UTIL_H_ +#define MEDIA_BASE_TEST_DATA_UTIL_H_ + +#include + +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" + +namespace media { + +class DecoderBuffer; + +// Returns a file path for a file in the media/test/data directory. +base::FilePath GetTestDataFilePath(const std::string& name); + +// Reads a test file from media/test/data directory and stores it in +// a DecoderBuffer. Use DecoderBuffer vs DataBuffer to ensure no matter +// what a test does, it's safe to use FFmpeg methods. +// +// |name| - The name of the file. +// |buffer| - The contents of the file. +scoped_refptr ReadTestDataFile(const std::string& name); + +} // namespace media + +#endif // MEDIA_BASE_TEST_DATA_UTIL_H_ diff --git a/media/base/text_track.h b/media/base/text_track.h new file mode 100644 index 0000000000..01a2ed727f --- /dev/null +++ b/media/base/text_track.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 MEDIA_BASE_TEXT_TRACK_H_ +#define MEDIA_BASE_TEXT_TRACK_H_ + +#include + +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "base/time/time.h" + +namespace media { + +// Specifies the varieties of text tracks. +enum TextKind { + kTextSubtitles, + kTextCaptions, + kTextDescriptions, + kTextMetadata, + kTextNone +}; + +class TextTrack { + public: + virtual ~TextTrack() {} + virtual void addWebVTTCue(const base::TimeDelta& start, + const base::TimeDelta& end, + const std::string& id, + const std::string& content, + const std::string& settings) = 0; +}; + +typedef base::Callback + (TextKind kind, + const std::string& label, + const std::string& language)> AddTextTrackCB; + +} // namespace media + +#endif // MEDIA_BASE_TEXT_TRACK_H_ diff --git a/media/mp4/aac.cc b/media/mp4/aac.cc new file mode 100644 index 0000000000..6604c505a0 --- /dev/null +++ b/media/mp4/aac.cc @@ -0,0 +1,275 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/mp4/aac.h" + +#include + +#include "base/logging.h" +#include "media/base/bit_reader.h" +#include "media/mp4/rcheck.h" + +// The following conversion table is extracted from ISO 14496 Part 3 - +// Table 1.16 - Sampling Frequency Index. +static const int kFrequencyMap[] = { + 96000, 88200, 64000, 48000, 44100, 32000, 24000, + 22050, 16000, 12000, 11025, 8000, 7350 +}; + +namespace media { + +static ChannelLayout ConvertChannelConfigToLayout(uint8 channel_config) { + switch (channel_config) { + case 1: + return CHANNEL_LAYOUT_MONO; + case 2: + return CHANNEL_LAYOUT_STEREO; + case 3: + return CHANNEL_LAYOUT_SURROUND; + case 4: + return CHANNEL_LAYOUT_4_0; + case 5: + return CHANNEL_LAYOUT_5_0; + case 6: + return CHANNEL_LAYOUT_5_1; + case 8: + return CHANNEL_LAYOUT_7_1; + default: + break; + } + + return CHANNEL_LAYOUT_UNSUPPORTED; +} + +namespace mp4 { + +AAC::AAC() + : profile_(0), frequency_index_(0), channel_config_(0), frequency_(0), + extension_frequency_(0), channel_layout_(CHANNEL_LAYOUT_UNSUPPORTED) { +} + +AAC::~AAC() { +} + +bool AAC::Parse(const std::vector& data) { +#if defined(OS_ANDROID) + codec_specific_data_ = data; +#endif + if (data.empty()) + return false; + + BitReader reader(&data[0], data.size()); + uint8 extension_type = 0; + bool ps_present = false; + uint8 extension_frequency_index = 0xff; + + frequency_ = 0; + extension_frequency_ = 0; + + // The following code is written according to ISO 14496 Part 3 Table 1.13 - + // Syntax of AudioSpecificConfig. + + // Read base configuration + RCHECK(reader.ReadBits(5, &profile_)); + RCHECK(reader.ReadBits(4, &frequency_index_)); + if (frequency_index_ == 0xf) + RCHECK(reader.ReadBits(24, &frequency_)); + RCHECK(reader.ReadBits(4, &channel_config_)); + + // Read extension configuration. + if (profile_ == 5 || profile_ == 29) { + ps_present = (profile_ == 29); + extension_type = 5; + RCHECK(reader.ReadBits(4, &extension_frequency_index)); + if (extension_frequency_index == 0xf) + RCHECK(reader.ReadBits(24, &extension_frequency_)); + RCHECK(reader.ReadBits(5, &profile_)); + } + + RCHECK(SkipDecoderGASpecificConfig(&reader)); + RCHECK(SkipErrorSpecificConfig()); + + // Read extension configuration again + // Note: The check for 16 available bits comes from the AAC spec. + if (extension_type != 5 && reader.bits_available() >= 16) { + uint16 sync_extension_type; + uint8 sbr_present_flag; + uint8 ps_present_flag; + + if (reader.ReadBits(11, &sync_extension_type) && + sync_extension_type == 0x2b7) { + if (reader.ReadBits(5, &extension_type) && extension_type == 5) { + RCHECK(reader.ReadBits(1, &sbr_present_flag)); + + if (sbr_present_flag) { + RCHECK(reader.ReadBits(4, &extension_frequency_index)); + + if (extension_frequency_index == 0xf) + RCHECK(reader.ReadBits(24, &extension_frequency_)); + + // Note: The check for 12 available bits comes from the AAC spec. + if (reader.bits_available() >= 12) { + RCHECK(reader.ReadBits(11, &sync_extension_type)); + if (sync_extension_type == 0x548) { + RCHECK(reader.ReadBits(1, &ps_present_flag)); + ps_present = ps_present_flag != 0; + } + } + } + } + } + } + + if (frequency_ == 0) { + RCHECK(frequency_index_ < arraysize(kFrequencyMap)); + frequency_ = kFrequencyMap[frequency_index_]; + } + + if (extension_frequency_ == 0 && extension_frequency_index != 0xff) { + RCHECK(extension_frequency_index < arraysize(kFrequencyMap)); + extension_frequency_ = kFrequencyMap[extension_frequency_index]; + } + + // When Parametric Stereo is on, mono will be played as stereo. + if (ps_present && channel_config_ == 1) + channel_layout_ = CHANNEL_LAYOUT_STEREO; + else + channel_layout_ = ConvertChannelConfigToLayout(channel_config_); + + return frequency_ != 0 && channel_layout_ != CHANNEL_LAYOUT_UNSUPPORTED && + profile_ >= 1 && profile_ <= 4 && frequency_index_ != 0xf && + channel_config_ <= 7; +} + +int AAC::GetOutputSamplesPerSecond(bool sbr_in_mimetype) const { + if (extension_frequency_ > 0) + return extension_frequency_; + + if (!sbr_in_mimetype) + return frequency_; + + // The following code is written according to ISO 14496 Part 3 Table 1.11 and + // Table 1.22. (Table 1.11 refers to the capping to 48000, Table 1.22 refers + // to SBR doubling the AAC sample rate.) + // TODO(acolwell) : Extend sample rate cap to 96kHz for Level 5 content. + DCHECK_GT(frequency_, 0); + return std::min(2 * frequency_, 48000); +} + +ChannelLayout AAC::GetChannelLayout(bool sbr_in_mimetype) const { + // Check for implicit signalling of HE-AAC and indicate stereo output + // if the mono channel configuration is signalled. + // See ISO-14496-3 Section 1.6.6.1.2 for details about this special casing. + if (sbr_in_mimetype && channel_config_ == 1) + return CHANNEL_LAYOUT_STEREO; + + return channel_layout_; +} + +bool AAC::ConvertEsdsToADTS(std::vector* buffer) const { + size_t size = buffer->size() + kADTSHeaderSize; + + DCHECK(profile_ >= 1 && profile_ <= 4 && frequency_index_ != 0xf && + channel_config_ <= 7); + + // ADTS header uses 13 bits for packet size. + if (size >= (1 << 13)) + return false; + + std::vector& adts = *buffer; + + adts.insert(buffer->begin(), kADTSHeaderSize, 0); + adts[0] = 0xff; + adts[1] = 0xf1; + adts[2] = ((profile_ - 1) << 6) + (frequency_index_ << 2) + + (channel_config_ >> 2); + adts[3] = ((channel_config_ & 0x3) << 6) + (size >> 11); + adts[4] = (size & 0x7ff) >> 3; + adts[5] = ((size & 7) << 5) + 0x1f; + adts[6] = 0xfc; + + return true; +} + +// Currently this function only support GASpecificConfig defined in +// ISO 14496 Part 3 Table 4.1 - Syntax of GASpecificConfig() +bool AAC::SkipDecoderGASpecificConfig(BitReader* bit_reader) const { + switch (profile_) { + case 1: + case 2: + case 3: + case 4: + case 6: + case 7: + case 17: + case 19: + case 20: + case 21: + case 22: + case 23: + return SkipGASpecificConfig(bit_reader); + default: + break; + } + + return false; +} + +bool AAC::SkipErrorSpecificConfig() const { + switch (profile_) { + case 17: + case 19: + case 20: + case 21: + case 22: + case 23: + case 24: + case 25: + case 26: + case 27: + return false; + default: + break; + } + + return true; +} + +// The following code is written according to ISO 14496 part 3 Table 4.1 - +// GASpecificConfig. +bool AAC::SkipGASpecificConfig(BitReader* bit_reader) const { + uint8 extension_flag = 0; + uint8 depends_on_core_coder; + uint16 dummy; + + RCHECK(bit_reader->ReadBits(1, &dummy)); // frameLengthFlag + RCHECK(bit_reader->ReadBits(1, &depends_on_core_coder)); + if (depends_on_core_coder == 1) + RCHECK(bit_reader->ReadBits(14, &dummy)); // coreCoderDelay + + RCHECK(bit_reader->ReadBits(1, &extension_flag)); + RCHECK(channel_config_ != 0); + + if (profile_ == 6 || profile_ == 20) + RCHECK(bit_reader->ReadBits(3, &dummy)); // layerNr + + if (extension_flag) { + if (profile_ == 22) { + RCHECK(bit_reader->ReadBits(5, &dummy)); // numOfSubFrame + RCHECK(bit_reader->ReadBits(11, &dummy)); // layer_length + } + + if (profile_ == 17 || profile_ == 19 || profile_ == 20 || profile_ == 23) { + RCHECK(bit_reader->ReadBits(3, &dummy)); // resilience flags + } + + RCHECK(bit_reader->ReadBits(1, &dummy)); // extensionFlag3 + } + + return true; +} + +} // namespace mp4 + +} // namespace media diff --git a/media/mp4/aac.h b/media/mp4/aac.h new file mode 100644 index 0000000000..1a546b743f --- /dev/null +++ b/media/mp4/aac.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 MEDIA_MP4_AAC_H_ +#define MEDIA_MP4_AAC_H_ + +#include + +#include "base/basictypes.h" +#include "media/base/channel_layout.h" +#include "media/base/media_export.h" + +namespace media { + +class BitReader; + +namespace mp4 { + +// This class parses the AAC information from decoder specific information +// embedded in the esds box in an ISO BMFF file. +// Please refer to ISO 14496 Part 3 Table 1.13 - Syntax of AudioSpecificConfig +// for more details. +class MEDIA_EXPORT AAC { + public: + AAC(); + ~AAC(); + + // Parse the AAC config from the raw binary data embedded in esds box. + // The function will parse the data and get the ElementaryStreamDescriptor, + // then it will parse the ElementaryStreamDescriptor to get audio stream + // configurations. + bool Parse(const std::vector& data); + + // Gets the output sample rate for the AAC stream. + // |sbr_in_mimetype| should be set to true if the SBR mode is + // signalled in the mimetype. (ie mp4a.40.5 in the codecs parameter). + // Returns the samples_per_second value that should used in an + // AudioDecoderConfig. + int GetOutputSamplesPerSecond(bool sbr_in_mimetype) const; + + // Gets the channel layout for the AAC stream. + // |sbr_in_mimetype| should be set to true if the SBR mode is + // signalled in the mimetype. (ie mp4a.40.5 in the codecs parameter). + // Returns the channel_layout value that should used in an + // AudioDecoderConfig. + ChannelLayout GetChannelLayout(bool sbr_in_mimetype) const; + + // This function converts a raw AAC frame into an AAC frame with an ADTS + // header. On success, the function returns true and stores the converted data + // in the buffer. The function returns false on failure and leaves the buffer + // unchanged. + bool ConvertEsdsToADTS(std::vector* buffer) const; + +#if defined(OS_ANDROID) + // Returns the codec specific data needed by android MediaCodec. + std::vector codec_specific_data() const { + return codec_specific_data_; + } +#endif + + // Size in bytes of the ADTS header added by ConvertEsdsToADTS(). + static const size_t kADTSHeaderSize = 7; + + private: + bool SkipDecoderGASpecificConfig(BitReader* bit_reader) const; + bool SkipErrorSpecificConfig() const; + bool SkipGASpecificConfig(BitReader* bit_reader) const; + + // The following variables store the AAC specific configuration information + // that are used to generate the ADTS header. + uint8 profile_; + uint8 frequency_index_; + uint8 channel_config_; + +#if defined(OS_ANDROID) + // The codec specific data needed by the android MediaCodec. + std::vector codec_specific_data_; +#endif + + // The following variables store audio configuration information that + // can be used by Chromium. They are based on the AAC specific + // configuration but can be overridden by extensions in elementary + // stream descriptor. + int frequency_; + int extension_frequency_; + ChannelLayout channel_layout_; +}; + +} // namespace mp4 + +} // namespace media + +#endif // MEDIA_MP4_AAC_H_ diff --git a/media/mp4/aac_unittest.cc b/media/mp4/aac_unittest.cc new file mode 100644 index 0000000000..d9ce22db3f --- /dev/null +++ b/media/mp4/aac_unittest.cc @@ -0,0 +1,146 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/mp4/aac.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +namespace mp4 { + +TEST(AACTest, BasicProfileTest) { + AAC aac; + uint8 buffer[] = {0x12, 0x10}; + std::vector data; + + data.assign(buffer, buffer + sizeof(buffer)); + + EXPECT_TRUE(aac.Parse(data)); + EXPECT_EQ(aac.GetOutputSamplesPerSecond(false), 44100); + EXPECT_EQ(aac.GetChannelLayout(false), CHANNEL_LAYOUT_STEREO); +} + +TEST(AACTest, ExtensionTest) { + AAC aac; + uint8 buffer[] = {0x13, 0x08, 0x56, 0xe5, 0x9d, 0x48, 0x80}; + std::vector data; + + data.assign(buffer, buffer + sizeof(buffer)); + + EXPECT_TRUE(aac.Parse(data)); + EXPECT_EQ(aac.GetOutputSamplesPerSecond(false), 48000); + EXPECT_EQ(aac.GetOutputSamplesPerSecond(true), 48000); + EXPECT_EQ(aac.GetChannelLayout(false), CHANNEL_LAYOUT_STEREO); +} + +// Test implicit SBR with mono channel config. +// Mono channel layout should only be reported if SBR is not +// specified. Otherwise stereo should be reported. +// See ISO-14496-3 Section 1.6.6.1.2 for details about this special casing. +TEST(AACTest, ImplicitSBR_ChannelConfig0) { + AAC aac; + uint8 buffer[] = {0x13, 0x08}; + std::vector data; + + data.assign(buffer, buffer + sizeof(buffer)); + + EXPECT_TRUE(aac.Parse(data)); + + // Test w/o implict SBR. + EXPECT_EQ(aac.GetOutputSamplesPerSecond(false), 24000); + EXPECT_EQ(aac.GetChannelLayout(false), CHANNEL_LAYOUT_MONO); + + // Test implicit SBR. + EXPECT_EQ(aac.GetOutputSamplesPerSecond(true), 48000); + EXPECT_EQ(aac.GetChannelLayout(true), CHANNEL_LAYOUT_STEREO); +} + +// Tests implicit SBR with a stereo channel config. +TEST(AACTest, ImplicitSBR_ChannelConfig1) { + AAC aac; + uint8 buffer[] = {0x13, 0x10}; + std::vector data; + + data.assign(buffer, buffer + sizeof(buffer)); + + EXPECT_TRUE(aac.Parse(data)); + + // Test w/o implict SBR. + EXPECT_EQ(aac.GetOutputSamplesPerSecond(false), 24000); + EXPECT_EQ(aac.GetChannelLayout(false), CHANNEL_LAYOUT_STEREO); + + // Test implicit SBR. + EXPECT_EQ(aac.GetOutputSamplesPerSecond(true), 48000); + EXPECT_EQ(aac.GetChannelLayout(true), CHANNEL_LAYOUT_STEREO); +} + +TEST(AACTest, SixChannelTest) { + AAC aac; + uint8 buffer[] = {0x11, 0xb0}; + std::vector data; + + data.assign(buffer, buffer + sizeof(buffer)); + + EXPECT_TRUE(aac.Parse(data)); + EXPECT_EQ(aac.GetOutputSamplesPerSecond(false), 48000); + EXPECT_EQ(aac.GetChannelLayout(false), CHANNEL_LAYOUT_5_1); +} + +TEST(AACTest, DataTooShortTest) { + AAC aac; + std::vector data; + + EXPECT_FALSE(aac.Parse(data)); + + data.push_back(0x12); + EXPECT_FALSE(aac.Parse(data)); +} + +TEST(AACTest, IncorrectProfileTest) { + AAC aac; + uint8 buffer[] = {0x0, 0x08}; + std::vector data; + + data.assign(buffer, buffer + sizeof(buffer)); + + EXPECT_FALSE(aac.Parse(data)); + + data[0] = 0x08; + EXPECT_TRUE(aac.Parse(data)); + + data[0] = 0x28; + EXPECT_FALSE(aac.Parse(data)); +} + +TEST(AACTest, IncorrectFrequencyTest) { + AAC aac; + uint8 buffer[] = {0x0f, 0x88}; + std::vector data; + + data.assign(buffer, buffer + sizeof(buffer)); + + EXPECT_FALSE(aac.Parse(data)); + + data[0] = 0x0e; + data[1] = 0x08; + EXPECT_TRUE(aac.Parse(data)); +} + +TEST(AACTest, IncorrectChannelTest) { + AAC aac; + uint8 buffer[] = {0x0e, 0x00}; + std::vector data; + + data.assign(buffer, buffer + sizeof(buffer)); + + EXPECT_FALSE(aac.Parse(data)); + + data[1] = 0x08; + EXPECT_TRUE(aac.Parse(data)); +} + +} // namespace mp4 + +} // namespace media diff --git a/media/mp4/avc.cc b/media/mp4/avc.cc new file mode 100644 index 0000000000..ae28ffd256 --- /dev/null +++ b/media/mp4/avc.cc @@ -0,0 +1,91 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/mp4/avc.h" + +#include +#include + +#include "media/mp4/box_definitions.h" +#include "media/mp4/box_reader.h" + +namespace media { +namespace mp4 { + +static const uint8 kAnnexBStartCode[] = {0, 0, 0, 1}; +static const int kAnnexBStartCodeSize = 4; + +static bool ConvertAVCToAnnexBInPlaceForLengthSize4(std::vector* buf) { + const int kLengthSize = 4; + size_t pos = 0; + while (pos + kLengthSize < buf->size()) { + int nal_size = (*buf)[pos]; + nal_size = (nal_size << 8) + (*buf)[pos+1]; + nal_size = (nal_size << 8) + (*buf)[pos+2]; + nal_size = (nal_size << 8) + (*buf)[pos+3]; + std::copy(kAnnexBStartCode, kAnnexBStartCode + kAnnexBStartCodeSize, + buf->begin() + pos); + pos += kLengthSize + nal_size; + } + return pos == buf->size(); +} + +// static +bool AVC::ConvertFrameToAnnexB(int length_size, std::vector* buffer) { + RCHECK(length_size == 1 || length_size == 2 || length_size == 4); + + if (length_size == 4) + return ConvertAVCToAnnexBInPlaceForLengthSize4(buffer); + + std::vector temp; + temp.swap(*buffer); + buffer->reserve(temp.size() + 32); + + size_t pos = 0; + while (pos + length_size < temp.size()) { + int nal_size = temp[pos]; + if (length_size == 2) nal_size = (nal_size << 8) + temp[pos+1]; + pos += length_size; + + RCHECK(pos + nal_size <= temp.size()); + buffer->insert(buffer->end(), kAnnexBStartCode, + kAnnexBStartCode + kAnnexBStartCodeSize); + buffer->insert(buffer->end(), temp.begin() + pos, + temp.begin() + pos + nal_size); + pos += nal_size; + } + return pos == temp.size(); +} + +// static +bool AVC::ConvertConfigToAnnexB( + const AVCDecoderConfigurationRecord& avc_config, + std::vector* buffer) { + DCHECK(buffer->empty()); + buffer->clear(); + int total_size = 0; + for (size_t i = 0; i < avc_config.sps_list.size(); i++) + total_size += avc_config.sps_list[i].size() + kAnnexBStartCodeSize; + for (size_t i = 0; i < avc_config.pps_list.size(); i++) + total_size += avc_config.pps_list[i].size() + kAnnexBStartCodeSize; + buffer->reserve(total_size); + + for (size_t i = 0; i < avc_config.sps_list.size(); i++) { + buffer->insert(buffer->end(), kAnnexBStartCode, + kAnnexBStartCode + kAnnexBStartCodeSize); + buffer->insert(buffer->end(), avc_config.sps_list[i].begin(), + avc_config.sps_list[i].end()); + } + + for (size_t i = 0; i < avc_config.pps_list.size(); i++) { + buffer->insert(buffer->end(), kAnnexBStartCode, + kAnnexBStartCode + kAnnexBStartCodeSize); + buffer->insert(buffer->end(), avc_config.pps_list[i].begin(), + avc_config.pps_list[i].end()); + } + return true; +} + +} // namespace mp4 +} // namespace media diff --git a/media/mp4/avc.h b/media/mp4/avc.h new file mode 100644 index 0000000000..3d815a1739 --- /dev/null +++ b/media/mp4/avc.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. + +#ifndef MEDIA_MP4_AVC_H_ +#define MEDIA_MP4_AVC_H_ + +#include + +#include "base/basictypes.h" +#include "media/base/media_export.h" + +namespace media { +namespace mp4 { + +struct AVCDecoderConfigurationRecord; + +class MEDIA_EXPORT AVC { + public: + static bool ConvertFrameToAnnexB(int length_size, std::vector* buffer); + + static bool ConvertConfigToAnnexB( + const AVCDecoderConfigurationRecord& avc_config, + std::vector* buffer); +}; + +} // namespace mp4 +} // namespace media + +#endif // MEDIA_MP4_AVC_H_ diff --git a/media/mp4/avc_unittest.cc b/media/mp4/avc_unittest.cc new file mode 100644 index 0000000000..766a979196 --- /dev/null +++ b/media/mp4/avc_unittest.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 + +#include "base/basictypes.h" +#include "media/base/stream_parser_buffer.h" +#include "media/mp4/avc.h" +#include "media/mp4/box_definitions.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/gtest/include/gtest/gtest-param-test.h" + +namespace media { +namespace mp4 { + +static const uint8 kNALU1[] = { 0x01, 0x02, 0x03 }; +static const uint8 kNALU2[] = { 0x04, 0x05, 0x06, 0x07 }; +static const uint8 kExpected[] = { + 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x03, + 0x00, 0x00, 0x00, 0x01, 0x04, 0x05, 0x06, 0x07 }; + +static const uint8 kExpectedParamSets[] = { + 0x00, 0x00, 0x00, 0x01, 0x67, 0x12, + 0x00, 0x00, 0x00, 0x01, 0x67, 0x34, + 0x00, 0x00, 0x00, 0x01, 0x68, 0x56, 0x78}; + +class AVCConversionTest : public testing::TestWithParam { + protected: + void MakeInputForLength(int length_size, std::vector* buf) { + buf->clear(); + for (int i = 1; i < length_size; i++) + buf->push_back(0); + buf->push_back(sizeof(kNALU1)); + buf->insert(buf->end(), kNALU1, kNALU1 + sizeof(kNALU1)); + + for (int i = 1; i < length_size; i++) + buf->push_back(0); + buf->push_back(sizeof(kNALU2)); + buf->insert(buf->end(), kNALU2, kNALU2 + sizeof(kNALU2)); + } +}; + +TEST_P(AVCConversionTest, ParseCorrectly) { + std::vector buf; + MakeInputForLength(GetParam(), &buf); + EXPECT_TRUE(AVC::ConvertFrameToAnnexB(GetParam(), &buf)); + EXPECT_EQ(buf.size(), sizeof(kExpected)); + EXPECT_EQ(0, memcmp(kExpected, &buf[0], sizeof(kExpected))); +} + +TEST_P(AVCConversionTest, ParsePartial) { + std::vector buf; + MakeInputForLength(GetParam(), &buf); + buf.pop_back(); + EXPECT_FALSE(AVC::ConvertFrameToAnnexB(GetParam(), &buf)); + // This tests a buffer ending in the middle of a NAL length. For length size + // of one, this can't happen, so we skip that case. + if (GetParam() != 1) { + MakeInputForLength(GetParam(), &buf); + buf.erase(buf.end() - (sizeof(kNALU2) + 1), buf.end()); + EXPECT_FALSE(AVC::ConvertFrameToAnnexB(GetParam(), &buf)); + } +} + +TEST_P(AVCConversionTest, ParseEmpty) { + std::vector buf; + EXPECT_TRUE(AVC::ConvertFrameToAnnexB(GetParam(), &buf)); + EXPECT_EQ(0u, buf.size()); +} + +INSTANTIATE_TEST_CASE_P(AVCConversionTestValues, + AVCConversionTest, + ::testing::Values(1, 2, 4)); + +TEST_F(AVCConversionTest, ConvertConfigToAnnexB) { + AVCDecoderConfigurationRecord avc_config; + avc_config.sps_list.resize(2); + avc_config.sps_list[0].push_back(0x67); + avc_config.sps_list[0].push_back(0x12); + avc_config.sps_list[1].push_back(0x67); + avc_config.sps_list[1].push_back(0x34); + avc_config.pps_list.resize(1); + avc_config.pps_list[0].push_back(0x68); + avc_config.pps_list[0].push_back(0x56); + avc_config.pps_list[0].push_back(0x78); + + std::vector buf; + EXPECT_TRUE(AVC::ConvertConfigToAnnexB(avc_config, &buf)); + EXPECT_EQ(0, memcmp(kExpectedParamSets, &buf[0], + sizeof(kExpectedParamSets))); +} + +} // namespace mp4 +} // namespace media diff --git a/media/mp4/box_definitions.cc b/media/mp4/box_definitions.cc new file mode 100644 index 0000000000..e7f169323b --- /dev/null +++ b/media/mp4/box_definitions.cc @@ -0,0 +1,757 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/mp4/box_definitions.h" + +#include "base/logging.h" +#include "media/mp4/es_descriptor.h" +#include "media/mp4/rcheck.h" + +namespace media { +namespace mp4 { + +FileType::FileType() {} +FileType::~FileType() {} +FourCC FileType::BoxType() const { return FOURCC_FTYP; } + +bool FileType::Parse(BoxReader* reader) { + RCHECK(reader->ReadFourCC(&major_brand) && reader->Read4(&minor_version)); + size_t num_brands = (reader->size() - reader->pos()) / sizeof(FourCC); + return reader->SkipBytes(sizeof(FourCC) * num_brands); // compatible_brands +} + +ProtectionSystemSpecificHeader::ProtectionSystemSpecificHeader() {} +ProtectionSystemSpecificHeader::~ProtectionSystemSpecificHeader() {} +FourCC ProtectionSystemSpecificHeader::BoxType() const { return FOURCC_PSSH; } + +bool ProtectionSystemSpecificHeader::Parse(BoxReader* reader) { + // Validate the box's contents and hang on to the system ID. + uint32 size; + RCHECK(reader->ReadFullBoxHeader() && + reader->ReadVec(&system_id, 16) && + reader->Read4(&size) && + reader->HasBytes(size)); + + // Copy the entire box, including the header, for passing to EME as initData. + DCHECK(raw_box.empty()); + raw_box.assign(reader->data(), reader->data() + reader->size()); + return true; +} + +SampleAuxiliaryInformationOffset::SampleAuxiliaryInformationOffset() {} +SampleAuxiliaryInformationOffset::~SampleAuxiliaryInformationOffset() {} +FourCC SampleAuxiliaryInformationOffset::BoxType() const { return FOURCC_SAIO; } + +bool SampleAuxiliaryInformationOffset::Parse(BoxReader* reader) { + RCHECK(reader->ReadFullBoxHeader()); + if (reader->flags() & 1) + RCHECK(reader->SkipBytes(8)); + + uint32 count; + RCHECK(reader->Read4(&count) && + reader->HasBytes(count * (reader->version() == 1 ? 8 : 4))); + offsets.resize(count); + + for (uint32 i = 0; i < count; i++) { + if (reader->version() == 1) { + RCHECK(reader->Read8(&offsets[i])); + } else { + RCHECK(reader->Read4Into8(&offsets[i])); + } + } + return true; +} + +SampleAuxiliaryInformationSize::SampleAuxiliaryInformationSize() + : default_sample_info_size(0), sample_count(0) { +} +SampleAuxiliaryInformationSize::~SampleAuxiliaryInformationSize() {} +FourCC SampleAuxiliaryInformationSize::BoxType() const { return FOURCC_SAIZ; } + +bool SampleAuxiliaryInformationSize::Parse(BoxReader* reader) { + RCHECK(reader->ReadFullBoxHeader()); + if (reader->flags() & 1) + RCHECK(reader->SkipBytes(8)); + + RCHECK(reader->Read1(&default_sample_info_size) && + reader->Read4(&sample_count)); + if (default_sample_info_size == 0) + return reader->ReadVec(&sample_info_sizes, sample_count); + return true; +} + +OriginalFormat::OriginalFormat() : format(FOURCC_NULL) {} +OriginalFormat::~OriginalFormat() {} +FourCC OriginalFormat::BoxType() const { return FOURCC_FRMA; } + +bool OriginalFormat::Parse(BoxReader* reader) { + return reader->ReadFourCC(&format); +} + +SchemeType::SchemeType() : type(FOURCC_NULL), version(0) {} +SchemeType::~SchemeType() {} +FourCC SchemeType::BoxType() const { return FOURCC_SCHM; } + +bool SchemeType::Parse(BoxReader* reader) { + RCHECK(reader->ReadFullBoxHeader() && + reader->ReadFourCC(&type) && + reader->Read4(&version)); + return true; +} + +TrackEncryption::TrackEncryption() + : is_encrypted(false), default_iv_size(0) { +} +TrackEncryption::~TrackEncryption() {} +FourCC TrackEncryption::BoxType() const { return FOURCC_TENC; } + +bool TrackEncryption::Parse(BoxReader* reader) { + uint8 flag; + RCHECK(reader->ReadFullBoxHeader() && + reader->SkipBytes(2) && + reader->Read1(&flag) && + reader->Read1(&default_iv_size) && + reader->ReadVec(&default_kid, 16)); + is_encrypted = (flag != 0); + if (is_encrypted) { + RCHECK(default_iv_size == 8 || default_iv_size == 16); + } else { + RCHECK(default_iv_size == 0); + } + return true; +} + +SchemeInfo::SchemeInfo() {} +SchemeInfo::~SchemeInfo() {} +FourCC SchemeInfo::BoxType() const { return FOURCC_SCHI; } + +bool SchemeInfo::Parse(BoxReader* reader) { + return reader->ScanChildren() && reader->ReadChild(&track_encryption); +} + +ProtectionSchemeInfo::ProtectionSchemeInfo() {} +ProtectionSchemeInfo::~ProtectionSchemeInfo() {} +FourCC ProtectionSchemeInfo::BoxType() const { return FOURCC_SINF; } + +bool ProtectionSchemeInfo::Parse(BoxReader* reader) { + RCHECK(reader->ScanChildren() && + reader->ReadChild(&format) && + reader->ReadChild(&type)); + if (type.type == FOURCC_CENC) + RCHECK(reader->ReadChild(&info)); + // Other protection schemes are silently ignored. Since the protection scheme + // type can't be determined until this box is opened, we return 'true' for + // non-CENC protection scheme types. It is the parent box's responsibility to + // ensure that this scheme type is a supported one. + return true; +} + +MovieHeader::MovieHeader() + : creation_time(0), + modification_time(0), + timescale(0), + duration(0), + rate(-1), + volume(-1), + next_track_id(0) {} +MovieHeader::~MovieHeader() {} +FourCC MovieHeader::BoxType() const { return FOURCC_MVHD; } + +bool MovieHeader::Parse(BoxReader* reader) { + RCHECK(reader->ReadFullBoxHeader()); + + if (reader->version() == 1) { + RCHECK(reader->Read8(&creation_time) && + reader->Read8(&modification_time) && + reader->Read4(×cale) && + reader->Read8(&duration)); + } else { + RCHECK(reader->Read4Into8(&creation_time) && + reader->Read4Into8(&modification_time) && + reader->Read4(×cale) && + reader->Read4Into8(&duration)); + } + + RCHECK(reader->Read4s(&rate) && + reader->Read2s(&volume) && + reader->SkipBytes(10) && // reserved + reader->SkipBytes(36) && // matrix + reader->SkipBytes(24) && // predefined zero + reader->Read4(&next_track_id)); + return true; +} + +TrackHeader::TrackHeader() + : creation_time(0), + modification_time(0), + track_id(0), + duration(0), + layer(-1), + alternate_group(-1), + volume(-1), + width(0), + height(0) {} +TrackHeader::~TrackHeader() {} +FourCC TrackHeader::BoxType() const { return FOURCC_TKHD; } + +bool TrackHeader::Parse(BoxReader* reader) { + RCHECK(reader->ReadFullBoxHeader()); + if (reader->version() == 1) { + RCHECK(reader->Read8(&creation_time) && + reader->Read8(&modification_time) && + reader->Read4(&track_id) && + reader->SkipBytes(4) && // reserved + reader->Read8(&duration)); + } else { + RCHECK(reader->Read4Into8(&creation_time) && + reader->Read4Into8(&modification_time) && + reader->Read4(&track_id) && + reader->SkipBytes(4) && // reserved + reader->Read4Into8(&duration)); + } + + RCHECK(reader->SkipBytes(8) && // reserved + reader->Read2s(&layer) && + reader->Read2s(&alternate_group) && + reader->Read2s(&volume) && + reader->SkipBytes(2) && // reserved + reader->SkipBytes(36) && // matrix + reader->Read4(&width) && + reader->Read4(&height)); + width >>= 16; + height >>= 16; + return true; +} + +SampleDescription::SampleDescription() : type(kInvalid) {} +SampleDescription::~SampleDescription() {} +FourCC SampleDescription::BoxType() const { return FOURCC_STSD; } + +bool SampleDescription::Parse(BoxReader* reader) { + uint32 count; + RCHECK(reader->SkipBytes(4) && + reader->Read4(&count)); + video_entries.clear(); + audio_entries.clear(); + + // Note: this value is preset before scanning begins. See comments in the + // Parse(Media*) function. + if (type == kVideo) { + RCHECK(reader->ReadAllChildren(&video_entries)); + } else if (type == kAudio) { + RCHECK(reader->ReadAllChildren(&audio_entries)); + } + return true; +} + +SampleTable::SampleTable() {} +SampleTable::~SampleTable() {} +FourCC SampleTable::BoxType() const { return FOURCC_STBL; } + +bool SampleTable::Parse(BoxReader* reader) { + return reader->ScanChildren() && + reader->ReadChild(&description); +} + +EditList::EditList() {} +EditList::~EditList() {} +FourCC EditList::BoxType() const { return FOURCC_ELST; } + +bool EditList::Parse(BoxReader* reader) { + uint32 count; + RCHECK(reader->ReadFullBoxHeader() && reader->Read4(&count)); + + if (reader->version() == 1) { + RCHECK(reader->HasBytes(count * 20)); + } else { + RCHECK(reader->HasBytes(count * 12)); + } + edits.resize(count); + + for (std::vector::iterator edit = edits.begin(); + edit != edits.end(); ++edit) { + if (reader->version() == 1) { + RCHECK(reader->Read8(&edit->segment_duration) && + reader->Read8s(&edit->media_time)); + } else { + RCHECK(reader->Read4Into8(&edit->segment_duration) && + reader->Read4sInto8s(&edit->media_time)); + } + RCHECK(reader->Read2s(&edit->media_rate_integer) && + reader->Read2s(&edit->media_rate_fraction)); + } + return true; +} + +Edit::Edit() {} +Edit::~Edit() {} +FourCC Edit::BoxType() const { return FOURCC_EDTS; } + +bool Edit::Parse(BoxReader* reader) { + return reader->ScanChildren() && reader->ReadChild(&list); +} + +HandlerReference::HandlerReference() : type(kInvalid) {} +HandlerReference::~HandlerReference() {} +FourCC HandlerReference::BoxType() const { return FOURCC_HDLR; } + +bool HandlerReference::Parse(BoxReader* reader) { + FourCC hdlr_type; + RCHECK(reader->SkipBytes(8) && reader->ReadFourCC(&hdlr_type)); + // Note: remaining fields in box ignored + if (hdlr_type == FOURCC_VIDE) { + type = kVideo; + } else if (hdlr_type == FOURCC_SOUN) { + type = kAudio; + } else { + type = kInvalid; + } + return true; +} + +AVCDecoderConfigurationRecord::AVCDecoderConfigurationRecord() + : version(0), + profile_indication(0), + profile_compatibility(0), + avc_level(0), + length_size(0) {} + +AVCDecoderConfigurationRecord::~AVCDecoderConfigurationRecord() {} +FourCC AVCDecoderConfigurationRecord::BoxType() const { return FOURCC_AVCC; } + +bool AVCDecoderConfigurationRecord::Parse(BoxReader* reader) { + RCHECK(reader->Read1(&version) && version == 1 && + reader->Read1(&profile_indication) && + reader->Read1(&profile_compatibility) && + reader->Read1(&avc_level)); + + uint8 length_size_minus_one; + RCHECK(reader->Read1(&length_size_minus_one) && + (length_size_minus_one & 0xfc) == 0xfc); + length_size = (length_size_minus_one & 0x3) + 1; + + uint8 num_sps; + RCHECK(reader->Read1(&num_sps) && (num_sps & 0xe0) == 0xe0); + num_sps &= 0x1f; + + sps_list.resize(num_sps); + for (int i = 0; i < num_sps; i++) { + uint16 sps_length; + RCHECK(reader->Read2(&sps_length) && + reader->ReadVec(&sps_list[i], sps_length)); + } + + uint8 num_pps; + RCHECK(reader->Read1(&num_pps)); + + pps_list.resize(num_pps); + for (int i = 0; i < num_pps; i++) { + uint16 pps_length; + RCHECK(reader->Read2(&pps_length) && + reader->ReadVec(&pps_list[i], pps_length)); + } + + return true; +} + +PixelAspectRatioBox::PixelAspectRatioBox() : h_spacing(1), v_spacing(1) {} +PixelAspectRatioBox::~PixelAspectRatioBox() {} +FourCC PixelAspectRatioBox::BoxType() const { return FOURCC_PASP; } + +bool PixelAspectRatioBox::Parse(BoxReader* reader) { + RCHECK(reader->Read4(&h_spacing) && + reader->Read4(&v_spacing)); + return true; +} + +VideoSampleEntry::VideoSampleEntry() + : format(FOURCC_NULL), + data_reference_index(0), + width(0), + height(0) {} + +VideoSampleEntry::~VideoSampleEntry() {} +FourCC VideoSampleEntry::BoxType() const { + DCHECK(false) << "VideoSampleEntry should be parsed according to the " + << "handler type recovered in its Media ancestor."; + return FOURCC_NULL; +} + +bool VideoSampleEntry::Parse(BoxReader* reader) { + format = reader->type(); + RCHECK(reader->SkipBytes(6) && + reader->Read2(&data_reference_index) && + reader->SkipBytes(16) && + reader->Read2(&width) && + reader->Read2(&height) && + reader->SkipBytes(50)); + + RCHECK(reader->ScanChildren() && + reader->MaybeReadChild(&pixel_aspect)); + + if (format == FOURCC_ENCV) { + // Continue scanning until a recognized protection scheme is found, or until + // we run out of protection schemes. + while (sinf.type.type != FOURCC_CENC) { + if (!reader->ReadChild(&sinf)) + return false; + } + } + + if (format == FOURCC_AVC1 || + (format == FOURCC_ENCV && sinf.format.format == FOURCC_AVC1)) { + RCHECK(reader->ReadChild(&avcc)); + } + return true; +} + +ElementaryStreamDescriptor::ElementaryStreamDescriptor() + : object_type(kForbidden) {} + +ElementaryStreamDescriptor::~ElementaryStreamDescriptor() {} + +FourCC ElementaryStreamDescriptor::BoxType() const { + return FOURCC_ESDS; +} + +bool ElementaryStreamDescriptor::Parse(BoxReader* reader) { + std::vector data; + ESDescriptor es_desc; + + RCHECK(reader->ReadFullBoxHeader()); + RCHECK(reader->ReadVec(&data, reader->size() - reader->pos())); + RCHECK(es_desc.Parse(data)); + + object_type = es_desc.object_type(); + + RCHECK(aac.Parse(es_desc.decoder_specific_info())); + + return true; +} + +AudioSampleEntry::AudioSampleEntry() + : format(FOURCC_NULL), + data_reference_index(0), + channelcount(0), + samplesize(0), + samplerate(0) {} + +AudioSampleEntry::~AudioSampleEntry() {} + +FourCC AudioSampleEntry::BoxType() const { + DCHECK(false) << "AudioSampleEntry should be parsed according to the " + << "handler type recovered in its Media ancestor."; + return FOURCC_NULL; +} + +bool AudioSampleEntry::Parse(BoxReader* reader) { + format = reader->type(); + RCHECK(reader->SkipBytes(6) && + reader->Read2(&data_reference_index) && + reader->SkipBytes(8) && + reader->Read2(&channelcount) && + reader->Read2(&samplesize) && + reader->SkipBytes(4) && + reader->Read4(&samplerate)); + // Convert from 16.16 fixed point to integer + samplerate >>= 16; + + RCHECK(reader->ScanChildren()); + if (format == FOURCC_ENCA) { + // Continue scanning until a recognized protection scheme is found, or until + // we run out of protection schemes. + while (sinf.type.type != FOURCC_CENC) { + if (!reader->ReadChild(&sinf)) + return false; + } + } + + // ESDS is not valid in case of EAC3. + RCHECK(reader->MaybeReadChild(&esds)); + return true; +} + +MediaHeader::MediaHeader() + : creation_time(0), + modification_time(0), + timescale(0), + duration(0) {} +MediaHeader::~MediaHeader() {} +FourCC MediaHeader::BoxType() const { return FOURCC_MDHD; } + +bool MediaHeader::Parse(BoxReader* reader) { + RCHECK(reader->ReadFullBoxHeader()); + + if (reader->version() == 1) { + RCHECK(reader->Read8(&creation_time) && + reader->Read8(&modification_time) && + reader->Read4(×cale) && + reader->Read8(&duration)); + } else { + RCHECK(reader->Read4Into8(&creation_time) && + reader->Read4Into8(&modification_time) && + reader->Read4(×cale) && + reader->Read4Into8(&duration)); + } + // Skip language information + return reader->SkipBytes(4); +} + +MediaInformation::MediaInformation() {} +MediaInformation::~MediaInformation() {} +FourCC MediaInformation::BoxType() const { return FOURCC_MINF; } + +bool MediaInformation::Parse(BoxReader* reader) { + return reader->ScanChildren() && + reader->ReadChild(&sample_table); +} + +Media::Media() {} +Media::~Media() {} +FourCC Media::BoxType() const { return FOURCC_MDIA; } + +bool Media::Parse(BoxReader* reader) { + RCHECK(reader->ScanChildren() && + reader->ReadChild(&header) && + reader->ReadChild(&handler)); + + // Maddeningly, the HandlerReference box specifies how to parse the + // SampleDescription box, making the latter the only box (of those that we + // support) which cannot be parsed correctly on its own (or even with + // information from its strict ancestor tree). We thus copy the handler type + // to the sample description box *before* parsing it to provide this + // information while parsing. + information.sample_table.description.type = handler.type; + RCHECK(reader->ReadChild(&information)); + return true; +} + +Track::Track() {} +Track::~Track() {} +FourCC Track::BoxType() const { return FOURCC_TRAK; } + +bool Track::Parse(BoxReader* reader) { + RCHECK(reader->ScanChildren() && + reader->ReadChild(&header) && + reader->ReadChild(&media) && + reader->MaybeReadChild(&edit)); + return true; +} + +MovieExtendsHeader::MovieExtendsHeader() : fragment_duration(0) {} +MovieExtendsHeader::~MovieExtendsHeader() {} +FourCC MovieExtendsHeader::BoxType() const { return FOURCC_MEHD; } + +bool MovieExtendsHeader::Parse(BoxReader* reader) { + RCHECK(reader->ReadFullBoxHeader()); + if (reader->version() == 1) { + RCHECK(reader->Read8(&fragment_duration)); + } else { + RCHECK(reader->Read4Into8(&fragment_duration)); + } + return true; +} + +TrackExtends::TrackExtends() + : track_id(0), + default_sample_description_index(0), + default_sample_duration(0), + default_sample_size(0), + default_sample_flags(0) {} +TrackExtends::~TrackExtends() {} +FourCC TrackExtends::BoxType() const { return FOURCC_TREX; } + +bool TrackExtends::Parse(BoxReader* reader) { + RCHECK(reader->ReadFullBoxHeader() && + reader->Read4(&track_id) && + reader->Read4(&default_sample_description_index) && + reader->Read4(&default_sample_duration) && + reader->Read4(&default_sample_size) && + reader->Read4(&default_sample_flags)); + return true; +} + +MovieExtends::MovieExtends() {} +MovieExtends::~MovieExtends() {} +FourCC MovieExtends::BoxType() const { return FOURCC_MVEX; } + +bool MovieExtends::Parse(BoxReader* reader) { + header.fragment_duration = 0; + return reader->ScanChildren() && + reader->MaybeReadChild(&header) && + reader->ReadChildren(&tracks); +} + +Movie::Movie() : fragmented(false) {} +Movie::~Movie() {} +FourCC Movie::BoxType() const { return FOURCC_MOOV; } + +bool Movie::Parse(BoxReader* reader) { + return reader->ScanChildren() && + reader->ReadChild(&header) && + reader->ReadChildren(&tracks) && + // Media Source specific: 'mvex' required + reader->ReadChild(&extends) && + reader->MaybeReadChildren(&pssh); +} + +TrackFragmentDecodeTime::TrackFragmentDecodeTime() : decode_time(0) {} +TrackFragmentDecodeTime::~TrackFragmentDecodeTime() {} +FourCC TrackFragmentDecodeTime::BoxType() const { return FOURCC_TFDT; } + +bool TrackFragmentDecodeTime::Parse(BoxReader* reader) { + RCHECK(reader->ReadFullBoxHeader()); + if (reader->version() == 1) + return reader->Read8(&decode_time); + else + return reader->Read4Into8(&decode_time); +} + +MovieFragmentHeader::MovieFragmentHeader() : sequence_number(0) {} +MovieFragmentHeader::~MovieFragmentHeader() {} +FourCC MovieFragmentHeader::BoxType() const { return FOURCC_MFHD; } + +bool MovieFragmentHeader::Parse(BoxReader* reader) { + return reader->SkipBytes(4) && reader->Read4(&sequence_number); +} + +TrackFragmentHeader::TrackFragmentHeader() + : track_id(0), + sample_description_index(0), + default_sample_duration(0), + default_sample_size(0), + default_sample_flags(0), + has_default_sample_flags(false) {} + +TrackFragmentHeader::~TrackFragmentHeader() {} +FourCC TrackFragmentHeader::BoxType() const { return FOURCC_TFHD; } + +bool TrackFragmentHeader::Parse(BoxReader* reader) { + RCHECK(reader->ReadFullBoxHeader() && reader->Read4(&track_id)); + + // Media Source specific: reject tracks that set 'base-data-offset-present'. + // Although the Media Source requires that 'default-base-is-moof' (14496-12 + // Amendment 2) be set, we omit this check as many otherwise-valid files in + // the wild don't set it. + // + // RCHECK((flags & 0x020000) && !(flags & 0x1)); + RCHECK(!(reader->flags() & 0x1)); + + if (reader->flags() & 0x2) { + RCHECK(reader->Read4(&sample_description_index)); + } else { + sample_description_index = 0; + } + + if (reader->flags() & 0x8) { + RCHECK(reader->Read4(&default_sample_duration)); + } else { + default_sample_duration = 0; + } + + if (reader->flags() & 0x10) { + RCHECK(reader->Read4(&default_sample_size)); + } else { + default_sample_size = 0; + } + + if (reader->flags() & 0x20) { + RCHECK(reader->Read4(&default_sample_flags)); + has_default_sample_flags = true; + } else { + has_default_sample_flags = false; + } + + return true; +} + +TrackFragmentRun::TrackFragmentRun() + : sample_count(0), data_offset(0) {} +TrackFragmentRun::~TrackFragmentRun() {} +FourCC TrackFragmentRun::BoxType() const { return FOURCC_TRUN; } + +bool TrackFragmentRun::Parse(BoxReader* reader) { + RCHECK(reader->ReadFullBoxHeader() && + reader->Read4(&sample_count)); + const uint32 flags = reader->flags(); + + bool data_offset_present = (flags & 0x1) != 0; + bool first_sample_flags_present = (flags & 0x4) != 0; + bool sample_duration_present = (flags & 0x100) != 0; + bool sample_size_present = (flags & 0x200) != 0; + bool sample_flags_present = (flags & 0x400) != 0; + bool sample_composition_time_offsets_present = (flags & 0x800) != 0; + + if (data_offset_present) { + RCHECK(reader->Read4(&data_offset)); + } else { + data_offset = 0; + } + + uint32 first_sample_flags; + if (first_sample_flags_present) + RCHECK(reader->Read4(&first_sample_flags)); + + int fields = sample_duration_present + sample_size_present + + sample_flags_present + sample_composition_time_offsets_present; + RCHECK(reader->HasBytes(fields * sample_count)); + + if (sample_duration_present) + sample_durations.resize(sample_count); + if (sample_size_present) + sample_sizes.resize(sample_count); + if (sample_flags_present) + sample_flags.resize(sample_count); + if (sample_composition_time_offsets_present) + sample_composition_time_offsets.resize(sample_count); + + for (uint32 i = 0; i < sample_count; ++i) { + if (sample_duration_present) + RCHECK(reader->Read4(&sample_durations[i])); + if (sample_size_present) + RCHECK(reader->Read4(&sample_sizes[i])); + if (sample_flags_present) + RCHECK(reader->Read4(&sample_flags[i])); + if (sample_composition_time_offsets_present) + RCHECK(reader->Read4s(&sample_composition_time_offsets[i])); + } + + if (first_sample_flags_present) { + if (sample_flags.size() == 0) { + sample_flags.push_back(first_sample_flags); + } else { + sample_flags[0] = first_sample_flags; + } + } + return true; +} + +TrackFragment::TrackFragment() {} +TrackFragment::~TrackFragment() {} +FourCC TrackFragment::BoxType() const { return FOURCC_TRAF; } + +bool TrackFragment::Parse(BoxReader* reader) { + return reader->ScanChildren() && + reader->ReadChild(&header) && + // Media Source specific: 'tfdt' required + reader->ReadChild(&decode_time) && + reader->MaybeReadChildren(&runs) && + reader->MaybeReadChild(&auxiliary_offset) && + reader->MaybeReadChild(&auxiliary_size); +} + +MovieFragment::MovieFragment() {} +MovieFragment::~MovieFragment() {} +FourCC MovieFragment::BoxType() const { return FOURCC_MOOF; } + +bool MovieFragment::Parse(BoxReader* reader) { + RCHECK(reader->ScanChildren() && + reader->ReadChild(&header) && + reader->ReadChildren(&tracks) && + reader->MaybeReadChildren(&pssh)); + return true; +} + +} // namespace mp4 +} // namespace media diff --git a/media/mp4/box_definitions.h b/media/mp4/box_definitions.h new file mode 100644 index 0000000000..eab8c4f410 --- /dev/null +++ b/media/mp4/box_definitions.h @@ -0,0 +1,351 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_MP4_BOX_DEFINITIONS_H_ +#define MEDIA_MP4_BOX_DEFINITIONS_H_ + +#include +#include + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "media/base/media_export.h" +#include "media/mp4/aac.h" +#include "media/mp4/avc.h" +#include "media/mp4/box_reader.h" +#include "media/mp4/fourccs.h" + +namespace media { +namespace mp4 { + +enum TrackType { + kInvalid = 0, + kVideo, + kAudio, + kHint +}; + +#define DECLARE_BOX_METHODS(T) \ + T(); \ + virtual ~T(); \ + virtual bool Parse(BoxReader* reader) OVERRIDE; \ + virtual FourCC BoxType() const OVERRIDE; \ + +struct MEDIA_EXPORT FileType : Box { + DECLARE_BOX_METHODS(FileType); + + FourCC major_brand; + uint32 minor_version; +}; + +struct MEDIA_EXPORT ProtectionSystemSpecificHeader : Box { + DECLARE_BOX_METHODS(ProtectionSystemSpecificHeader); + + std::vector system_id; + std::vector raw_box; +}; + +struct MEDIA_EXPORT SampleAuxiliaryInformationOffset : Box { + DECLARE_BOX_METHODS(SampleAuxiliaryInformationOffset); + + std::vector offsets; +}; + +struct MEDIA_EXPORT SampleAuxiliaryInformationSize : Box { + DECLARE_BOX_METHODS(SampleAuxiliaryInformationSize); + + uint8 default_sample_info_size; + uint32 sample_count; + std::vector sample_info_sizes; +}; + +struct MEDIA_EXPORT OriginalFormat : Box { + DECLARE_BOX_METHODS(OriginalFormat); + + FourCC format; +}; + +struct MEDIA_EXPORT SchemeType : Box { + DECLARE_BOX_METHODS(SchemeType); + + FourCC type; + uint32 version; +}; + +struct MEDIA_EXPORT TrackEncryption : Box { + DECLARE_BOX_METHODS(TrackEncryption); + + // Note: this definition is specific to the CENC protection type. + bool is_encrypted; + uint8 default_iv_size; + std::vector default_kid; +}; + +struct MEDIA_EXPORT SchemeInfo : Box { + DECLARE_BOX_METHODS(SchemeInfo); + + TrackEncryption track_encryption; +}; + +struct MEDIA_EXPORT ProtectionSchemeInfo : Box { + DECLARE_BOX_METHODS(ProtectionSchemeInfo); + + OriginalFormat format; + SchemeType type; + SchemeInfo info; +}; + +struct MEDIA_EXPORT MovieHeader : Box { + DECLARE_BOX_METHODS(MovieHeader); + + uint64 creation_time; + uint64 modification_time; + uint32 timescale; + uint64 duration; + int32 rate; + int16 volume; + uint32 next_track_id; +}; + +struct MEDIA_EXPORT TrackHeader : Box { + DECLARE_BOX_METHODS(TrackHeader); + + uint64 creation_time; + uint64 modification_time; + uint32 track_id; + uint64 duration; + int16 layer; + int16 alternate_group; + int16 volume; + uint32 width; + uint32 height; +}; + +struct MEDIA_EXPORT EditListEntry { + uint64 segment_duration; + int64 media_time; + int16 media_rate_integer; + int16 media_rate_fraction; +}; + +struct MEDIA_EXPORT EditList : Box { + DECLARE_BOX_METHODS(EditList); + + std::vector edits; +}; + +struct MEDIA_EXPORT Edit : Box { + DECLARE_BOX_METHODS(Edit); + + EditList list; +}; + +struct MEDIA_EXPORT HandlerReference : Box { + DECLARE_BOX_METHODS(HandlerReference); + + TrackType type; +}; + +struct MEDIA_EXPORT AVCDecoderConfigurationRecord : Box { + DECLARE_BOX_METHODS(AVCDecoderConfigurationRecord); + + uint8 version; + uint8 profile_indication; + uint8 profile_compatibility; + uint8 avc_level; + uint8 length_size; + + typedef std::vector SPS; + typedef std::vector PPS; + + std::vector sps_list; + std::vector pps_list; +}; + +struct MEDIA_EXPORT PixelAspectRatioBox : Box { + DECLARE_BOX_METHODS(PixelAspectRatioBox); + + uint32 h_spacing; + uint32 v_spacing; +}; + +struct MEDIA_EXPORT VideoSampleEntry : Box { + DECLARE_BOX_METHODS(VideoSampleEntry); + + FourCC format; + uint16 data_reference_index; + uint16 width; + uint16 height; + + PixelAspectRatioBox pixel_aspect; + ProtectionSchemeInfo sinf; + + // Currently expected to be present regardless of format. + AVCDecoderConfigurationRecord avcc; +}; + +struct MEDIA_EXPORT ElementaryStreamDescriptor : Box { + DECLARE_BOX_METHODS(ElementaryStreamDescriptor); + + uint8 object_type; + AAC aac; +}; + +struct MEDIA_EXPORT AudioSampleEntry : Box { + DECLARE_BOX_METHODS(AudioSampleEntry); + + FourCC format; + uint16 data_reference_index; + uint16 channelcount; + uint16 samplesize; + uint32 samplerate; + + ProtectionSchemeInfo sinf; + ElementaryStreamDescriptor esds; +}; + +struct MEDIA_EXPORT SampleDescription : Box { + DECLARE_BOX_METHODS(SampleDescription); + + TrackType type; + std::vector video_entries; + std::vector audio_entries; +}; + +struct MEDIA_EXPORT SampleTable : Box { + DECLARE_BOX_METHODS(SampleTable); + + // Media Source specific: we ignore many of the sub-boxes in this box, + // including some that are required to be present in the BMFF spec. This + // includes the 'stts', 'stsc', and 'stco' boxes, which must contain no + // samples in order to be compliant files. + SampleDescription description; +}; + +struct MEDIA_EXPORT MediaHeader : Box { + DECLARE_BOX_METHODS(MediaHeader); + + uint64 creation_time; + uint64 modification_time; + uint32 timescale; + uint64 duration; +}; + +struct MEDIA_EXPORT MediaInformation : Box { + DECLARE_BOX_METHODS(MediaInformation); + + SampleTable sample_table; +}; + +struct MEDIA_EXPORT Media : Box { + DECLARE_BOX_METHODS(Media); + + MediaHeader header; + HandlerReference handler; + MediaInformation information; +}; + +struct MEDIA_EXPORT Track : Box { + DECLARE_BOX_METHODS(Track); + + TrackHeader header; + Media media; + Edit edit; +}; + +struct MEDIA_EXPORT MovieExtendsHeader : Box { + DECLARE_BOX_METHODS(MovieExtendsHeader); + + uint64 fragment_duration; +}; + +struct MEDIA_EXPORT TrackExtends : Box { + DECLARE_BOX_METHODS(TrackExtends); + + uint32 track_id; + uint32 default_sample_description_index; + uint32 default_sample_duration; + uint32 default_sample_size; + uint32 default_sample_flags; +}; + +struct MEDIA_EXPORT MovieExtends : Box { + DECLARE_BOX_METHODS(MovieExtends); + + MovieExtendsHeader header; + std::vector tracks; +}; + +struct MEDIA_EXPORT Movie : Box { + DECLARE_BOX_METHODS(Movie); + + bool fragmented; + MovieHeader header; + MovieExtends extends; + std::vector tracks; + std::vector pssh; +}; + +struct MEDIA_EXPORT TrackFragmentDecodeTime : Box { + DECLARE_BOX_METHODS(TrackFragmentDecodeTime); + + uint64 decode_time; +}; + +struct MEDIA_EXPORT MovieFragmentHeader : Box { + DECLARE_BOX_METHODS(MovieFragmentHeader); + + uint32 sequence_number; +}; + +struct MEDIA_EXPORT TrackFragmentHeader : Box { + DECLARE_BOX_METHODS(TrackFragmentHeader); + + uint32 track_id; + + uint32 sample_description_index; + uint32 default_sample_duration; + uint32 default_sample_size; + uint32 default_sample_flags; + + // As 'flags' might be all zero, we cannot use zeroness alone to identify + // when default_sample_flags wasn't specified, unlike the other values. + bool has_default_sample_flags; +}; + +struct MEDIA_EXPORT TrackFragmentRun : Box { + DECLARE_BOX_METHODS(TrackFragmentRun); + + uint32 sample_count; + uint32 data_offset; + std::vector sample_flags; + std::vector sample_sizes; + std::vector sample_durations; + std::vector sample_composition_time_offsets; +}; + +struct MEDIA_EXPORT TrackFragment : Box { + DECLARE_BOX_METHODS(TrackFragment); + + TrackFragmentHeader header; + std::vector runs; + TrackFragmentDecodeTime decode_time; + SampleAuxiliaryInformationOffset auxiliary_offset; + SampleAuxiliaryInformationSize auxiliary_size; +}; + +struct MEDIA_EXPORT MovieFragment : Box { + DECLARE_BOX_METHODS(MovieFragment); + + MovieFragmentHeader header; + std::vector tracks; + std::vector pssh; +}; + +#undef DECLARE_BOX + +} // namespace mp4 +} // namespace media + +#endif // MEDIA_MP4_BOX_DEFINITIONS_H_ diff --git a/media/mp4/box_reader.cc b/media/mp4/box_reader.cc new file mode 100644 index 0000000000..c788772035 --- /dev/null +++ b/media/mp4/box_reader.cc @@ -0,0 +1,240 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/mp4/box_reader.h" + +#include +#include +#include +#include + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "media/mp4/box_definitions.h" +#include "media/mp4/rcheck.h" + +namespace media { +namespace mp4 { + +Box::~Box() {} + +bool BufferReader::Read1(uint8* v) { + RCHECK(HasBytes(1)); + *v = buf_[pos_++]; + return true; +} + +// Internal implementation of multi-byte reads +template bool BufferReader::Read(T* v) { + RCHECK(HasBytes(sizeof(T))); + + T tmp = 0; + for (size_t i = 0; i < sizeof(T); i++) { + tmp <<= 8; + tmp += buf_[pos_++]; + } + *v = tmp; + return true; +} + +bool BufferReader::Read2(uint16* v) { return Read(v); } +bool BufferReader::Read2s(int16* v) { return Read(v); } +bool BufferReader::Read4(uint32* v) { return Read(v); } +bool BufferReader::Read4s(int32* v) { return Read(v); } +bool BufferReader::Read8(uint64* v) { return Read(v); } +bool BufferReader::Read8s(int64* v) { return Read(v); } + +bool BufferReader::ReadFourCC(FourCC* v) { + return Read4(reinterpret_cast(v)); +} + +bool BufferReader::ReadVec(std::vector* vec, int count) { + RCHECK(HasBytes(count)); + vec->clear(); + vec->insert(vec->end(), buf_ + pos_, buf_ + pos_ + count); + pos_ += count; + return true; +} + +bool BufferReader::SkipBytes(int bytes) { + RCHECK(HasBytes(bytes)); + pos_ += bytes; + return true; +} + +bool BufferReader::Read4Into8(uint64* v) { + uint32 tmp; + RCHECK(Read4(&tmp)); + *v = tmp; + return true; +} + +bool BufferReader::Read4sInto8s(int64* v) { + // Beware of the need for sign extension. + int32 tmp; + RCHECK(Read4s(&tmp)); + *v = tmp; + return true; +} + + +BoxReader::BoxReader(const uint8* buf, const int size, + const LogCB& log_cb) + : BufferReader(buf, size), + log_cb_(log_cb), + type_(FOURCC_NULL), + version_(0), + flags_(0), + scanned_(false) { +} + +BoxReader::~BoxReader() { + if (scanned_ && !children_.empty()) { + for (ChildMap::iterator itr = children_.begin(); + itr != children_.end(); ++itr) { + DVLOG(1) << "Skipping unknown box: " << FourCCToString(itr->first); + } + } +} + +// static +BoxReader* BoxReader::ReadTopLevelBox(const uint8* buf, + const int buf_size, + const LogCB& log_cb, + bool* err) { + scoped_ptr reader(new BoxReader(buf, buf_size, log_cb)); + if (!reader->ReadHeader(err)) + return NULL; + + if (!IsValidTopLevelBox(reader->type(), log_cb)) { + *err = true; + return NULL; + } + + if (reader->size() <= buf_size) + return reader.release(); + + return NULL; +} + +// static +bool BoxReader::StartTopLevelBox(const uint8* buf, + const int buf_size, + const LogCB& log_cb, + FourCC* type, + int* box_size, + bool* err) { + BoxReader reader(buf, buf_size, log_cb); + if (!reader.ReadHeader(err)) return false; + if (!IsValidTopLevelBox(reader.type(), log_cb)) { + *err = true; + return false; + } + *type = reader.type(); + *box_size = reader.size(); + return true; +} + +// static +bool BoxReader::IsValidTopLevelBox(const FourCC& type, + const LogCB& log_cb) { + switch (type) { + case FOURCC_FTYP: + case FOURCC_PDIN: + case FOURCC_BLOC: + case FOURCC_MOOV: + case FOURCC_MOOF: + case FOURCC_MFRA: + case FOURCC_MDAT: + case FOURCC_FREE: + case FOURCC_SKIP: + case FOURCC_META: + case FOURCC_MECO: + case FOURCC_STYP: + case FOURCC_SIDX: + case FOURCC_SSIX: + case FOURCC_PRFT: + return true; + default: + // Hex is used to show nonprintable characters and aid in debugging + MEDIA_LOG(log_cb) << "Unrecognized top-level box type 0x" + << std::hex << type; + return false; + } +} + +bool BoxReader::ScanChildren() { + DCHECK(!scanned_); + scanned_ = true; + + bool err = false; + while (pos() < size()) { + BoxReader child(&buf_[pos_], size_ - pos_, log_cb_); + if (!child.ReadHeader(&err)) break; + + children_.insert(std::pair(child.type(), child)); + pos_ += child.size(); + } + + DCHECK(!err); + return !err && pos() == size(); +} + +bool BoxReader::ReadChild(Box* child) { + DCHECK(scanned_); + FourCC child_type = child->BoxType(); + + ChildMap::iterator itr = children_.find(child_type); + RCHECK(itr != children_.end()); + DVLOG(2) << "Found a " << FourCCToString(child_type) << " box."; + RCHECK(child->Parse(&itr->second)); + children_.erase(itr); + return true; +} + +bool BoxReader::MaybeReadChild(Box* child) { + if (!children_.count(child->BoxType())) return true; + return ReadChild(child); +} + +bool BoxReader::ReadFullBoxHeader() { + uint32 vflags; + RCHECK(Read4(&vflags)); + version_ = vflags >> 24; + flags_ = vflags & 0xffffff; + return true; +} + +bool BoxReader::ReadHeader(bool* err) { + uint64 size = 0; + *err = false; + + if (!HasBytes(8)) return false; + CHECK(Read4Into8(&size) && ReadFourCC(&type_)); + + if (size == 0) { + // Media Source specific: we do not support boxes that run to EOS. + *err = true; + return false; + } else if (size == 1) { + if (!HasBytes(8)) return false; + CHECK(Read8(&size)); + } + + // Implementation-specific: support for boxes larger than 2^31 has been + // removed. + if (size < static_cast(pos_) || + size > static_cast(kint32max)) { + *err = true; + return false; + } + + // Note that the pos_ head has advanced to the byte immediately after the + // header, which is where we want it. + size_ = size; + return true; +} + +} // namespace mp4 +} // namespace media diff --git a/media/mp4/box_reader.h b/media/mp4/box_reader.h new file mode 100644 index 0000000000..43f11d56fe --- /dev/null +++ b/media/mp4/box_reader.h @@ -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. + +#ifndef MEDIA_MP4_BOX_READER_H_ +#define MEDIA_MP4_BOX_READER_H_ + +#include +#include + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "media/base/media_export.h" +#include "media/base/media_log.h" +#include "media/mp4/fourccs.h" +#include "media/mp4/rcheck.h" + +namespace media { +namespace mp4 { + +class BoxReader; + +struct MEDIA_EXPORT Box { + virtual ~Box(); + virtual bool Parse(BoxReader* reader) = 0; + virtual FourCC BoxType() const = 0; +}; + +class MEDIA_EXPORT BufferReader { + public: + BufferReader(const uint8* buf, const int size) + : buf_(buf), size_(size), pos_(0) {} + + bool HasBytes(int count) { return (pos() + count <= size()); } + + // Read a value from the stream, perfoming endian correction, and advance the + // stream pointer. + bool Read1(uint8* v) WARN_UNUSED_RESULT; + bool Read2(uint16* v) WARN_UNUSED_RESULT; + bool Read2s(int16* v) WARN_UNUSED_RESULT; + bool Read4(uint32* v) WARN_UNUSED_RESULT; + bool Read4s(int32* v) WARN_UNUSED_RESULT; + bool Read8(uint64* v) WARN_UNUSED_RESULT; + bool Read8s(int64* v) WARN_UNUSED_RESULT; + + bool ReadFourCC(FourCC* v) WARN_UNUSED_RESULT; + + bool ReadVec(std::vector* t, int count) WARN_UNUSED_RESULT; + + // These variants read a 4-byte integer of the corresponding signedness and + // store it in the 8-byte return type. + bool Read4Into8(uint64* v) WARN_UNUSED_RESULT; + bool Read4sInto8s(int64* v) WARN_UNUSED_RESULT; + + // Advance the stream by this many bytes. + bool SkipBytes(int nbytes) WARN_UNUSED_RESULT; + + const uint8* data() const { return buf_; } + int size() const { return size_; } + int pos() const { return pos_; } + + protected: + const uint8* buf_; + int size_; + int pos_; + + template bool Read(T* t) WARN_UNUSED_RESULT; +}; + +class MEDIA_EXPORT BoxReader : public BufferReader { + public: + ~BoxReader(); + + // Create a BoxReader from a buffer. Note that this function may return NULL + // if an intact, complete box was not available in the buffer. If |*err| is + // set, there was a stream-level error when creating the box; otherwise, NULL + // values are only expected when insufficient data is available. + // + // |buf| is retained but not owned, and must outlive the BoxReader instance. + static BoxReader* ReadTopLevelBox(const uint8* buf, + const int buf_size, + const LogCB& log_cb, + bool* err); + + // Read the box header from the current buffer. This function returns true if + // there is enough data to read the header and the header is sane; that is, it + // does not check to ensure the entire box is in the buffer before returning + // true. The semantics of |*err| are the same as above. + // + // |buf| is not retained. + static bool StartTopLevelBox(const uint8* buf, + const int buf_size, + const LogCB& log_cb, + FourCC* type, + int* box_size, + bool* err) WARN_UNUSED_RESULT; + + // Returns true if |type| is recognized to be a top-level box, false + // otherwise. This returns true for some boxes which we do not parse. + // Helpful in debugging misaligned appends. + static bool IsValidTopLevelBox(const FourCC& type, + const LogCB& log_cb); + + // Scan through all boxes within the current box, starting at the current + // buffer position. Must be called before any of the *Child functions work. + bool ScanChildren() WARN_UNUSED_RESULT; + + // Read exactly one child box from the set of children. The type of the child + // will be determined by the BoxType() method of |child|. + bool ReadChild(Box* child) WARN_UNUSED_RESULT; + + // Read one child if available. Returns false on error, true on successful + // read or on child absent. + bool MaybeReadChild(Box* child) WARN_UNUSED_RESULT; + + // Read at least one child. False means error or no such child present. + template bool ReadChildren( + std::vector* children) WARN_UNUSED_RESULT; + + // Read any number of children. False means error. + template bool MaybeReadChildren( + std::vector* children) WARN_UNUSED_RESULT; + + // Read all children, regardless of FourCC. This is used from exactly one box, + // corresponding to a rather significant inconsistency in the BMFF spec. + // Note that this method is mutually exclusive with ScanChildren(). + template bool ReadAllChildren( + std::vector* children) WARN_UNUSED_RESULT; + + // Populate the values of 'version()' and 'flags()' from a full box header. + // Many boxes, but not all, use these values. This call should happen after + // the box has been initialized, and does not re-read the main box header. + bool ReadFullBoxHeader() WARN_UNUSED_RESULT; + + FourCC type() const { return type_; } + uint8 version() const { return version_; } + uint32 flags() const { return flags_; } + + private: + BoxReader(const uint8* buf, const int size, const LogCB& log_cb); + + // Must be called immediately after init. If the return is false, this + // indicates that the box header and its contents were not available in the + // stream or were nonsensical, and that the box must not be used further. In + // this case, if |*err| is false, the problem was simply a lack of data, and + // should only be an error condition if some higher-level component knows that + // no more data is coming (i.e. EOS or end of containing box). If |*err| is + // true, the error is unrecoverable and the stream should be aborted. + bool ReadHeader(bool* err); + + LogCB log_cb_; + FourCC type_; + uint8 version_; + uint32 flags_; + + typedef std::multimap ChildMap; + + // The set of child box FourCCs and their corresponding buffer readers. Only + // valid if scanned_ is true. + ChildMap children_; + bool scanned_; +}; + +// Template definitions +template bool BoxReader::ReadChildren(std::vector* children) { + RCHECK(MaybeReadChildren(children) && !children->empty()); + return true; +} + +template +bool BoxReader::MaybeReadChildren(std::vector* children) { + DCHECK(scanned_); + DCHECK(children->empty()); + + children->resize(1); + FourCC child_type = (*children)[0].BoxType(); + + ChildMap::iterator start_itr = children_.lower_bound(child_type); + ChildMap::iterator end_itr = children_.upper_bound(child_type); + children->resize(std::distance(start_itr, end_itr)); + typename std::vector::iterator child_itr = children->begin(); + for (ChildMap::iterator itr = start_itr; itr != end_itr; ++itr) { + RCHECK(child_itr->Parse(&itr->second)); + ++child_itr; + } + children_.erase(start_itr, end_itr); + + DVLOG(2) << "Found " << children->size() << " " + << FourCCToString(child_type) << " boxes."; + return true; +} + +template +bool BoxReader::ReadAllChildren(std::vector* children) { + DCHECK(!scanned_); + scanned_ = true; + + bool err = false; + while (pos() < size()) { + BoxReader child_reader(&buf_[pos_], size_ - pos_, log_cb_); + if (!child_reader.ReadHeader(&err)) break; + T child; + RCHECK(child.Parse(&child_reader)); + children->push_back(child); + pos_ += child_reader.size(); + } + + return !err; +} + +} // namespace mp4 +} // namespace media + +#endif // MEDIA_MP4_BOX_READER_H_ diff --git a/media/mp4/box_reader_unittest.cc b/media/mp4/box_reader_unittest.cc new file mode 100644 index 0000000000..99d9975fd2 --- /dev/null +++ b/media/mp4/box_reader_unittest.cc @@ -0,0 +1,201 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "media/mp4/box_reader.h" +#include "media/mp4/rcheck.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { +namespace mp4 { + +static const uint8 kSkipBox[] = { + // Top-level test box containing three children + 0x00, 0x00, 0x00, 0x40, 's', 'k', 'i', 'p', + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0xf9, 0x0a, 0x0b, 0x0c, 0xfd, 0x0e, 0x0f, 0x10, + // Ordinary (8-byte header) child box + 0x00, 0x00, 0x00, 0x0c, 'p', 's', 's', 'h', 0xde, 0xad, 0xbe, 0xef, + // Extended-size header child box + 0x00, 0x00, 0x00, 0x01, 'p', 's', 's', 'h', + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, + 0xfa, 0xce, 0xca, 0xfe, + // Empty free box + 0x00, 0x00, 0x00, 0x08, 'f', 'r', 'e', 'e', + // Trailing garbage + 0x00 }; + +struct FreeBox : Box { + virtual bool Parse(BoxReader* reader) OVERRIDE { + return true; + } + virtual FourCC BoxType() const OVERRIDE { return FOURCC_FREE; } +}; + +struct PsshBox : Box { + uint32 val; + + virtual bool Parse(BoxReader* reader) OVERRIDE { + return reader->Read4(&val); + } + virtual FourCC BoxType() const OVERRIDE { return FOURCC_PSSH; } +}; + +struct SkipBox : Box { + uint8 a, b; + uint16 c; + int32 d; + int64 e; + + std::vector kids; + FreeBox mpty; + + virtual bool Parse(BoxReader* reader) OVERRIDE { + RCHECK(reader->ReadFullBoxHeader() && + reader->Read1(&a) && + reader->Read1(&b) && + reader->Read2(&c) && + reader->Read4s(&d) && + reader->Read4sInto8s(&e)); + return reader->ScanChildren() && + reader->ReadChildren(&kids) && + reader->MaybeReadChild(&mpty); + } + virtual FourCC BoxType() const OVERRIDE { return FOURCC_SKIP; } + + SkipBox(); + virtual ~SkipBox(); +}; + +SkipBox::SkipBox() {} +SkipBox::~SkipBox() {} + +class BoxReaderTest : public testing::Test { + protected: + std::vector GetBuf() { + return std::vector(kSkipBox, kSkipBox + sizeof(kSkipBox)); + } +}; + +TEST_F(BoxReaderTest, ExpectedOperationTest) { + std::vector buf = GetBuf(); + bool err; + scoped_ptr reader( + BoxReader::ReadTopLevelBox(&buf[0], buf.size(), LogCB(), &err)); + EXPECT_FALSE(err); + EXPECT_TRUE(reader.get()); + + SkipBox box; + EXPECT_TRUE(box.Parse(reader.get())); + EXPECT_EQ(0x01, reader->version()); + EXPECT_EQ(0x020304u, reader->flags()); + EXPECT_EQ(0x05, box.a); + EXPECT_EQ(0x06, box.b); + EXPECT_EQ(0x0708, box.c); + EXPECT_EQ(static_cast(0xf90a0b0c), box.d); + EXPECT_EQ(static_cast(0xfd0e0f10), box.e); + + EXPECT_EQ(2u, box.kids.size()); + EXPECT_EQ(0xdeadbeef, box.kids[0].val); + EXPECT_EQ(0xfacecafe, box.kids[1].val); + + // Accounting for the extra byte outside of the box above + EXPECT_EQ(buf.size(), static_cast(reader->size() + 1)); +} + +TEST_F(BoxReaderTest, OuterTooShortTest) { + std::vector buf = GetBuf(); + bool err; + + // Create a soft failure by truncating the outer box. + scoped_ptr r( + BoxReader::ReadTopLevelBox(&buf[0], buf.size() - 2, LogCB(), &err)); + + EXPECT_FALSE(err); + EXPECT_FALSE(r.get()); +} + +TEST_F(BoxReaderTest, InnerTooLongTest) { + std::vector buf = GetBuf(); + bool err; + + // Make an inner box too big for its outer box. + buf[25] = 1; + scoped_ptr reader( + BoxReader::ReadTopLevelBox(&buf[0], buf.size(), LogCB(), &err)); + + SkipBox box; + EXPECT_FALSE(box.Parse(reader.get())); +} + +TEST_F(BoxReaderTest, WrongFourCCTest) { + std::vector buf = GetBuf(); + bool err; + + // Set an unrecognized top-level FourCC. + buf[5] = 1; + scoped_ptr reader( + BoxReader::ReadTopLevelBox(&buf[0], buf.size(), LogCB(), &err)); + EXPECT_FALSE(reader.get()); + EXPECT_TRUE(err); +} + +TEST_F(BoxReaderTest, ScanChildrenTest) { + std::vector buf = GetBuf(); + bool err; + scoped_ptr reader( + BoxReader::ReadTopLevelBox(&buf[0], buf.size(), LogCB(), &err)); + + EXPECT_TRUE(reader->SkipBytes(16) && reader->ScanChildren()); + + FreeBox free; + EXPECT_TRUE(reader->ReadChild(&free)); + EXPECT_FALSE(reader->ReadChild(&free)); + EXPECT_TRUE(reader->MaybeReadChild(&free)); + + std::vector kids; + + EXPECT_TRUE(reader->ReadChildren(&kids)); + EXPECT_EQ(2u, kids.size()); + kids.clear(); + EXPECT_FALSE(reader->ReadChildren(&kids)); + EXPECT_TRUE(reader->MaybeReadChildren(&kids)); +} + +TEST_F(BoxReaderTest, ReadAllChildrenTest) { + std::vector buf = GetBuf(); + // Modify buffer to exclude its last 'free' box + buf[3] = 0x38; + bool err; + scoped_ptr reader( + BoxReader::ReadTopLevelBox(&buf[0], buf.size(), LogCB(), &err)); + + std::vector kids; + EXPECT_TRUE(reader->SkipBytes(16) && reader->ReadAllChildren(&kids)); + EXPECT_EQ(2u, kids.size()); + EXPECT_EQ(kids[0].val, 0xdeadbeef); // Ensure order is preserved +} + +TEST_F(BoxReaderTest, SkippingBloc) { + static const uint8 kData[] = { + 0x00, 0x00, 0x00, 0x09, 'b', 'l', 'o', 'c', 0x00 + }; + + std::vector buf(kData, kData + sizeof(kData)); + + bool err; + scoped_ptr reader( + BoxReader::ReadTopLevelBox(&buf[0], buf.size(), LogCB(), &err)); + + EXPECT_FALSE(err); + EXPECT_TRUE(reader); + EXPECT_EQ(FOURCC_BLOC, reader->type()); +} + +} // namespace mp4 +} // namespace media diff --git a/media/mp4/cenc.cc b/media/mp4/cenc.cc new file mode 100644 index 0000000000..104948dd4f --- /dev/null +++ b/media/mp4/cenc.cc @@ -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 "media/mp4/cenc.h" + +#include + +#include "media/mp4/box_reader.h" +#include "media/mp4/rcheck.h" + +namespace media { +namespace mp4 { + +FrameCENCInfo::FrameCENCInfo() {} +FrameCENCInfo::~FrameCENCInfo() {} + +bool FrameCENCInfo::Parse(int iv_size, BufferReader* reader) { + const int kEntrySize = 6; + // Mandated by CENC spec + RCHECK(iv_size == 8 || iv_size == 16); + + memset(iv, 0, sizeof(iv)); + for (int i = 0; i < iv_size; i++) + RCHECK(reader->Read1(&iv[i])); + + if (!reader->HasBytes(1)) return true; + + uint16 subsample_count; + RCHECK(reader->Read2(&subsample_count) && + reader->HasBytes(subsample_count * kEntrySize)); + + subsamples.resize(subsample_count); + for (int i = 0; i < subsample_count; i++) { + uint16 clear_bytes; + uint32 cypher_bytes; + RCHECK(reader->Read2(&clear_bytes) && + reader->Read4(&cypher_bytes)); + subsamples[i].clear_bytes = clear_bytes; + subsamples[i].cypher_bytes = cypher_bytes; + } + return true; +} + +size_t FrameCENCInfo::GetTotalSizeOfSubsamples() const { + size_t size = 0; + for (size_t i = 0; i < subsamples.size(); i++) { + size += subsamples[i].clear_bytes + subsamples[i].cypher_bytes; + } + return size; +} + +} // namespace mp4 +} // namespace media diff --git a/media/mp4/cenc.h b/media/mp4/cenc.h new file mode 100644 index 0000000000..e558559a93 --- /dev/null +++ b/media/mp4/cenc.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 MEDIA_MP4_CENC_H_ +#define MEDIA_MP4_CENC_H_ + +#include + +#include "base/basictypes.h" +#include "media/base/decrypt_config.h" + +namespace media { +namespace mp4 { + +class BufferReader; + +struct FrameCENCInfo { + uint8 iv[16]; + std::vector subsamples; + + FrameCENCInfo(); + ~FrameCENCInfo(); + bool Parse(int iv_size, BufferReader* r); + size_t GetTotalSizeOfSubsamples() const; +}; + + +} // namespace mp4 +} // namespace media + +#endif // MEDIA_MP4_CENC_H_ diff --git a/media/mp4/es_descriptor.cc b/media/mp4/es_descriptor.cc new file mode 100644 index 0000000000..8517b82cbd --- /dev/null +++ b/media/mp4/es_descriptor.cc @@ -0,0 +1,117 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/mp4/es_descriptor.h" + +#include "media/base/bit_reader.h" +#include "media/mp4/rcheck.h" + +// The elementary stream size is specific by up to 4 bytes. +// The MSB of a byte indicates if there are more bytes for the size. +static bool ReadESSize(media::BitReader* reader, uint32* size) { + uint8 msb; + uint8 byte; + + *size = 0; + + for (size_t i = 0; i < 4; ++i) { + RCHECK(reader->ReadBits(1, &msb)); + RCHECK(reader->ReadBits(7, &byte)); + *size = (*size << 7) + byte; + + if (msb == 0) + break; + } + + return true; +} + +namespace media { + +namespace mp4 { + +// static +bool ESDescriptor::IsAAC(uint8 object_type) { + return object_type == kISO_14496_3 || object_type == kISO_13818_7_AAC_LC; +} + +ESDescriptor::ESDescriptor() + : object_type_(kForbidden) { +} + +ESDescriptor::~ESDescriptor() {} + +bool ESDescriptor::Parse(const std::vector& data) { + BitReader reader(&data[0], data.size()); + uint8 tag; + uint32 size; + uint8 stream_dependency_flag; + uint8 url_flag; + uint8 ocr_stream_flag; + uint16 dummy; + + RCHECK(reader.ReadBits(8, &tag)); + RCHECK(tag == kESDescrTag); + RCHECK(ReadESSize(&reader, &size)); + + RCHECK(reader.ReadBits(16, &dummy)); // ES_ID + RCHECK(reader.ReadBits(1, &stream_dependency_flag)); + RCHECK(reader.ReadBits(1, &url_flag)); + RCHECK(!url_flag); // We don't support url flag + RCHECK(reader.ReadBits(1, &ocr_stream_flag)); + RCHECK(reader.ReadBits(5, &dummy)); // streamPriority + + if (stream_dependency_flag) + RCHECK(reader.ReadBits(16, &dummy)); // dependsOn_ES_ID + if (ocr_stream_flag) + RCHECK(reader.ReadBits(16, &dummy)); // OCR_ES_Id + + RCHECK(ParseDecoderConfigDescriptor(&reader)); + + return true; +} + +uint8 ESDescriptor::object_type() const { + return object_type_; +} + +const std::vector& ESDescriptor::decoder_specific_info() const { + return decoder_specific_info_; +} + +bool ESDescriptor::ParseDecoderConfigDescriptor(BitReader* reader) { + uint8 tag; + uint32 size; + uint64 dummy; + + RCHECK(reader->ReadBits(8, &tag)); + RCHECK(tag == kDecoderConfigDescrTag); + RCHECK(ReadESSize(reader, &size)); + + RCHECK(reader->ReadBits(8, &object_type_)); + RCHECK(reader->ReadBits(64, &dummy)); + RCHECK(reader->ReadBits(32, &dummy)); + RCHECK(ParseDecoderSpecificInfo(reader)); + + return true; +} + +bool ESDescriptor::ParseDecoderSpecificInfo(BitReader* reader) { + uint8 tag; + uint32 size; + + RCHECK(reader->ReadBits(8, &tag)); + RCHECK(tag == kDecoderSpecificInfoTag); + RCHECK(ReadESSize(reader, &size)); + + decoder_specific_info_.resize(size); + for (uint32 i = 0; i < size; ++i) + RCHECK(reader->ReadBits(8, &decoder_specific_info_[i])); + + return true; +} + +} // namespace mp4 + +} // namespace media diff --git a/media/mp4/es_descriptor.h b/media/mp4/es_descriptor.h new file mode 100644 index 0000000000..36e1bf2a3b --- /dev/null +++ b/media/mp4/es_descriptor.h @@ -0,0 +1,62 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_MP4_ES_DESCRIPTOR_H_ +#define MEDIA_MP4_ES_DESCRIPTOR_H_ + +#include + +#include "base/basictypes.h" +#include "media/base/media_export.h" + +namespace media { + +class BitReader; + +namespace mp4 { + +// The following values are extracted from ISO 14496 Part 1 Table 5 - +// objectTypeIndication Values. Only values currently in use are included. +enum ObjectType { + kForbidden = 0, + kISO_14496_3 = 0x40, // MPEG4 AAC + kISO_13818_7_AAC_LC = 0x67, // MPEG2 AAC-LC + kEAC3 = 0xa6 // Dolby Digital Plus +}; + +// This class parse object type and decoder specific information from an +// elementary stream descriptor, which is usually contained in an esds box. +// Please refer to ISO 14496 Part 1 7.2.6.5 for more details. +class MEDIA_EXPORT ESDescriptor { + public: + // Utility function to check if the given object type is AAC. + static bool IsAAC(uint8 object_type); + + ESDescriptor(); + ~ESDescriptor(); + + bool Parse(const std::vector& data); + + uint8 object_type() const; + const std::vector& decoder_specific_info() const; + + private: + enum Tag { + kESDescrTag = 0x03, + kDecoderConfigDescrTag = 0x04, + kDecoderSpecificInfoTag = 0x05 + }; + + bool ParseDecoderConfigDescriptor(BitReader* reader); + bool ParseDecoderSpecificInfo(BitReader* reader); + + uint8 object_type_; + std::vector decoder_specific_info_; +}; + +} // namespace mp4 + +} // namespace media + +#endif // MEDIA_MP4_ES_DESCRIPTOR_H_ diff --git a/media/mp4/es_descriptor_unittest.cc b/media/mp4/es_descriptor_unittest.cc new file mode 100644 index 0000000000..c3a39fbefc --- /dev/null +++ b/media/mp4/es_descriptor_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 "media/mp4/es_descriptor.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +namespace mp4 { + +TEST(ESDescriptorTest, SingleByteLengthTest) { + ESDescriptor es_desc; + uint8 buffer[] = { + 0x03, 0x19, 0x00, 0x01, 0x00, 0x04, 0x11, 0x40, + 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x05, 0x02, 0x12, 0x10, + 0x06, 0x01, 0x02 + }; + std::vector data; + + data.assign(buffer, buffer + sizeof(buffer)); + + EXPECT_EQ(es_desc.object_type(), kForbidden); + EXPECT_TRUE(es_desc.Parse(data)); + EXPECT_EQ(es_desc.object_type(), kISO_14496_3); + EXPECT_EQ(es_desc.decoder_specific_info().size(), 2u); + EXPECT_EQ(es_desc.decoder_specific_info()[0], 0x12); + EXPECT_EQ(es_desc.decoder_specific_info()[1], 0x10); +} + +TEST(ESDescriptorTest, NonAACTest) { + ESDescriptor es_desc; + uint8 buffer[] = { + 0x03, 0x19, 0x00, 0x01, 0x00, 0x04, 0x11, 0x66, + 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x05, 0x02, 0x12, 0x10, + 0x06, 0x01, 0x02 + }; + std::vector data; + + data.assign(buffer, buffer + sizeof(buffer)); + + EXPECT_TRUE(es_desc.Parse(data)); + EXPECT_NE(es_desc.object_type(), kISO_14496_3); + EXPECT_EQ(es_desc.decoder_specific_info().size(), 2u); + EXPECT_EQ(es_desc.decoder_specific_info()[0], 0x12); + EXPECT_EQ(es_desc.decoder_specific_info()[1], 0x10); +} + +TEST(ESDescriptorTest, MultiByteLengthTest) { + ESDescriptor es_desc; + uint8 buffer[] = { + 0x03, 0x80, 0x19, 0x00, 0x01, 0x00, 0x04, 0x80, + 0x80, 0x11, 0x40, 0x15, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, + 0x80, 0x80, 0x80, 0x02, 0x12, 0x10, 0x06, 0x01, + 0x02 + }; + std::vector data; + + data.assign(buffer, buffer + sizeof(buffer)); + + EXPECT_TRUE(es_desc.Parse(data)); + EXPECT_EQ(es_desc.object_type(), kISO_14496_3); + EXPECT_EQ(es_desc.decoder_specific_info().size(), 2u); + EXPECT_EQ(es_desc.decoder_specific_info()[0], 0x12); + EXPECT_EQ(es_desc.decoder_specific_info()[1], 0x10); +} + +TEST(ESDescriptorTest, FiveByteLengthTest) { + ESDescriptor es_desc; + uint8 buffer[] = { + 0x03, 0x80, 0x19, 0x00, 0x01, 0x00, 0x04, 0x80, + 0x80, 0x11, 0x40, 0x15, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, + 0x80, 0x80, 0x80, 0x80, 0x02, 0x12, 0x10, 0x06, + 0x01, 0x02 + }; + std::vector data; + + data.assign(buffer, buffer + sizeof(buffer)); + + EXPECT_TRUE(es_desc.Parse(data)); + EXPECT_EQ(es_desc.object_type(), kISO_14496_3); + EXPECT_EQ(es_desc.decoder_specific_info().size(), 0u); +} + +} // namespace mp4 + +} // namespace media diff --git a/media/mp4/fourccs.h b/media/mp4/fourccs.h new file mode 100644 index 0000000000..b71d2ff3a5 --- /dev/null +++ b/media/mp4/fourccs.h @@ -0,0 +1,100 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_MP4_FOURCCS_H_ +#define MEDIA_MP4_FOURCCS_H_ + +#include + +namespace media { +namespace mp4 { + +enum FourCC { + FOURCC_NULL = 0, + FOURCC_AVC1 = 0x61766331, + FOURCC_AVCC = 0x61766343, + FOURCC_BLOC = 0x626C6F63, + FOURCC_CENC = 0x63656e63, + FOURCC_CO64 = 0x636f3634, + FOURCC_CTTS = 0x63747473, + FOURCC_DINF = 0x64696e66, + FOURCC_EAC3 = 0x65632d33, + FOURCC_EDTS = 0x65647473, + FOURCC_ELST = 0x656c7374, + FOURCC_ENCA = 0x656e6361, + FOURCC_ENCV = 0x656e6376, + FOURCC_ESDS = 0x65736473, + FOURCC_FREE = 0x66726565, + FOURCC_FRMA = 0x66726d61, + FOURCC_FTYP = 0x66747970, + FOURCC_HDLR = 0x68646c72, + FOURCC_HINT = 0x68696e74, + FOURCC_IODS = 0x696f6473, + FOURCC_MDAT = 0x6d646174, + FOURCC_MDHD = 0x6d646864, + FOURCC_MDIA = 0x6d646961, + FOURCC_MECO = 0x6d65636f, + FOURCC_MEHD = 0x6d656864, + FOURCC_META = 0x6d657461, + FOURCC_MFHD = 0x6d666864, + FOURCC_MFRA = 0x6d667261, + FOURCC_MINF = 0x6d696e66, + FOURCC_MOOF = 0x6d6f6f66, + FOURCC_MOOV = 0x6d6f6f76, + FOURCC_MP4A = 0x6d703461, + FOURCC_MP4V = 0x6d703476, + FOURCC_MVEX = 0x6d766578, + FOURCC_MVHD = 0x6d766864, + FOURCC_PASP = 0x70617370, + FOURCC_PDIN = 0x7064696e, + FOURCC_PRFT = 0x70726674, + FOURCC_PSSH = 0x70737368, + FOURCC_SAIO = 0x7361696f, + FOURCC_SAIZ = 0x7361697a, + FOURCC_SCHI = 0x73636869, + FOURCC_SCHM = 0x7363686d, + FOURCC_SDTP = 0x73647470, + FOURCC_SIDX = 0x73696478, + FOURCC_SINF = 0x73696e66, + FOURCC_SKIP = 0x736b6970, + FOURCC_SMHD = 0x736d6864, + FOURCC_SOUN = 0x736f756e, + FOURCC_SSIX = 0x73736978, + FOURCC_STBL = 0x7374626c, + FOURCC_STCO = 0x7374636f, + FOURCC_STSC = 0x73747363, + FOURCC_STSD = 0x73747364, + FOURCC_STSS = 0x73747373, + FOURCC_STSZ = 0x7374737a, + FOURCC_STTS = 0x73747473, + FOURCC_STYP = 0x73747970, + FOURCC_TENC = 0x74656e63, + FOURCC_TFDT = 0x74666474, + FOURCC_TFHD = 0x74666864, + FOURCC_TKHD = 0x746b6864, + FOURCC_TRAF = 0x74726166, + FOURCC_TRAK = 0x7472616b, + FOURCC_TREX = 0x74726578, + FOURCC_TRUN = 0x7472756e, + FOURCC_UDTA = 0x75647461, + FOURCC_UUID = 0x75756964, + FOURCC_VIDE = 0x76696465, + FOURCC_VMHD = 0x766d6864, + FOURCC_WIDE = 0x77696465, +}; + +const inline std::string FourCCToString(FourCC fourcc) { + char buf[5]; + buf[0] = (fourcc >> 24) & 0xff; + buf[1] = (fourcc >> 16) & 0xff; + buf[2] = (fourcc >> 8) & 0xff; + buf[3] = (fourcc) & 0xff; + buf[4] = 0; + return std::string(buf); +} + +} // namespace mp4 +} // namespace media + +#endif // MEDIA_MP4_FOURCCS_H_ diff --git a/media/mp4/mp4_stream_parser.cc b/media/mp4/mp4_stream_parser.cc new file mode 100644 index 0000000000..fc4ee8abd1 --- /dev/null +++ b/media/mp4/mp4_stream_parser.cc @@ -0,0 +1,577 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/mp4/mp4_stream_parser.h" + +#include "base/callback.h" +#include "base/callback_helpers.h" +#include "base/logging.h" +#include "base/time/time.h" +#include "media/base/audio_decoder_config.h" +#include "media/base/stream_parser_buffer.h" +#include "media/base/video_decoder_config.h" +#include "media/base/video_util.h" +#include "media/mp4/box_definitions.h" +#include "media/mp4/box_reader.h" +#include "media/mp4/es_descriptor.h" +#include "media/mp4/rcheck.h" + +namespace media { +namespace mp4 { + +// TODO(xhwang): Figure out the init data type appropriately once it's spec'ed. +static const char kMp4InitDataType[] = "video/mp4"; + +MP4StreamParser::MP4StreamParser(const std::set& audio_object_types, + bool has_sbr) + : state_(kWaitingForInit), + moof_head_(0), + mdat_tail_(0), + has_audio_(false), + has_video_(false), + audio_track_id_(0), + video_track_id_(0), + audio_object_types_(audio_object_types), + has_sbr_(has_sbr), + is_audio_track_encrypted_(false), + is_video_track_encrypted_(false) { +} + +MP4StreamParser::~MP4StreamParser() {} + +void MP4StreamParser::Init(const InitCB& init_cb, + const NewConfigCB& config_cb, + const NewBuffersCB& new_buffers_cb, + const NewTextBuffersCB& /* text_cb */ , + const NeedKeyCB& need_key_cb, + const AddTextTrackCB& /* add_text_track_cb */ , + const NewMediaSegmentCB& new_segment_cb, + const base::Closure& end_of_segment_cb, + const LogCB& log_cb) { + DCHECK_EQ(state_, kWaitingForInit); + DCHECK(init_cb_.is_null()); + DCHECK(!init_cb.is_null()); + DCHECK(!config_cb.is_null()); + DCHECK(!new_buffers_cb.is_null()); + DCHECK(!need_key_cb.is_null()); + DCHECK(!end_of_segment_cb.is_null()); + + ChangeState(kParsingBoxes); + init_cb_ = init_cb; + config_cb_ = config_cb; + new_buffers_cb_ = new_buffers_cb; + need_key_cb_ = need_key_cb; + new_segment_cb_ = new_segment_cb; + end_of_segment_cb_ = end_of_segment_cb; + log_cb_ = log_cb; +} + +void MP4StreamParser::Reset() { + queue_.Reset(); + runs_.reset(); + moof_head_ = 0; + mdat_tail_ = 0; +} + +void MP4StreamParser::Flush() { + DCHECK_NE(state_, kWaitingForInit); + Reset(); + ChangeState(kParsingBoxes); +} + +bool MP4StreamParser::Parse(const uint8* buf, int size) { + DCHECK_NE(state_, kWaitingForInit); + + if (state_ == kError) + return false; + + queue_.Push(buf, size); + + BufferQueue audio_buffers; + BufferQueue video_buffers; + + bool result, err = false; + + do { + if (state_ == kParsingBoxes) { + result = ParseBox(&err); + } else { + DCHECK_EQ(kEmittingSamples, state_); + result = EnqueueSample(&audio_buffers, &video_buffers, &err); + if (result) { + int64 max_clear = runs_->GetMaxClearOffset() + moof_head_; + err = !ReadAndDiscardMDATsUntil(max_clear); + } + } + } while (result && !err); + + if (!err) + err = !SendAndFlushSamples(&audio_buffers, &video_buffers); + + if (err) { + DLOG(ERROR) << "Error while parsing MP4"; + moov_.reset(); + Reset(); + ChangeState(kError); + return false; + } + + return true; +} + +bool MP4StreamParser::ParseBox(bool* err) { + const uint8* buf; + int size; + queue_.Peek(&buf, &size); + if (!size) return false; + + scoped_ptr reader( + BoxReader::ReadTopLevelBox(buf, size, log_cb_, err)); + if (reader.get() == NULL) return false; + + if (reader->type() == FOURCC_MOOV) { + *err = !ParseMoov(reader.get()); + } else if (reader->type() == FOURCC_MOOF) { + moof_head_ = queue_.head(); + *err = !ParseMoof(reader.get()); + + // Set up first mdat offset for ReadMDATsUntil(). + mdat_tail_ = queue_.head() + reader->size(); + + // Return early to avoid evicting 'moof' data from queue. Auxiliary info may + // be located anywhere in the file, including inside the 'moof' itself. + // (Since 'default-base-is-moof' is mandated, no data references can come + // before the head of the 'moof', so keeping this box around is sufficient.) + return !(*err); + } else { + MEDIA_LOG(log_cb_) << "Skipping unrecognized top-level box: " + << FourCCToString(reader->type()); + } + + queue_.Pop(reader->size()); + return !(*err); +} + + +bool MP4StreamParser::ParseMoov(BoxReader* reader) { + moov_.reset(new Movie); + RCHECK(moov_->Parse(reader)); + runs_.reset(); + + has_audio_ = false; + has_video_ = false; + + AudioDecoderConfig audio_config; + VideoDecoderConfig video_config; + + for (std::vector::const_iterator track = moov_->tracks.begin(); + track != moov_->tracks.end(); ++track) { + // TODO(strobe): Only the first audio and video track present in a file are + // used. (Track selection is better accomplished via Source IDs, though, so + // adding support for track selection within a stream is low-priority.) + const SampleDescription& samp_descr = + track->media.information.sample_table.description; + + // TODO(strobe): When codec reconfigurations are supported, detect and send + // a codec reconfiguration for fragments using a sample description index + // different from the previous one + size_t desc_idx = 0; + for (size_t t = 0; t < moov_->extends.tracks.size(); t++) { + const TrackExtends& trex = moov_->extends.tracks[t]; + if (trex.track_id == track->header.track_id) { + desc_idx = trex.default_sample_description_index; + break; + } + } + RCHECK(desc_idx > 0); + desc_idx -= 1; // BMFF descriptor index is one-based + + if (track->media.handler.type == kAudio && !audio_config.IsValidConfig()) { + RCHECK(!samp_descr.audio_entries.empty()); + + // It is not uncommon to find otherwise-valid files with incorrect sample + // description indices, so we fail gracefully in that case. + if (desc_idx >= samp_descr.audio_entries.size()) + desc_idx = 0; + const AudioSampleEntry& entry = samp_descr.audio_entries[desc_idx]; + const AAC& aac = entry.esds.aac; + + if (!(entry.format == FOURCC_MP4A || entry.format == FOURCC_EAC3 || + (entry.format == FOURCC_ENCA && + entry.sinf.format.format == FOURCC_MP4A))) { + MEDIA_LOG(log_cb_) << "Unsupported audio format 0x" + << std::hex << entry.format << " in stsd box."; + return false; + } + + uint8 audio_type = entry.esds.object_type; + DVLOG(1) << "audio_type " << std::hex << audio_type; + if (audio_type == kForbidden && entry.format == FOURCC_EAC3) { + audio_type = kEAC3; + } + if (audio_object_types_.find(audio_type) == audio_object_types_.end()) { + MEDIA_LOG(log_cb_) << "audio object type 0x" << std::hex << audio_type + << " does not match what is specified in the" + << " mimetype."; + return false; + } + + AudioCodec codec = kUnknownAudioCodec; + ChannelLayout channel_layout = CHANNEL_LAYOUT_NONE; + int sample_per_second = 0; + std::vector extra_data; + // Check if it is MPEG4 AAC defined in ISO 14496 Part 3 or + // supported MPEG2 AAC varients. + if (ESDescriptor::IsAAC(audio_type)) { + codec = kCodecAAC; + channel_layout = aac.GetChannelLayout(has_sbr_); + sample_per_second = aac.GetOutputSamplesPerSecond(has_sbr_); +#if defined(OS_ANDROID) + extra_data = aac.codec_specific_data(); +#endif + } else if (audio_type == kEAC3) { + codec = kCodecEAC3; + channel_layout = GuessChannelLayout(entry.channelcount); + sample_per_second = entry.samplerate; + } else { + MEDIA_LOG(log_cb_) << "Unsupported audio object type 0x" << std::hex + << audio_type << " in esds."; + return false; + } + + SampleFormat sample_format; + if (entry.samplesize == 8) { + sample_format = kSampleFormatU8; + } else if (entry.samplesize == 16) { + sample_format = kSampleFormatS16; + } else if (entry.samplesize == 32) { + sample_format = kSampleFormatS32; + } else { + LOG(ERROR) << "Unsupported sample size."; + return false; + } + + is_audio_track_encrypted_ = entry.sinf.info.track_encryption.is_encrypted; + DVLOG(1) << "is_audio_track_encrypted_: " << is_audio_track_encrypted_; + audio_config.Initialize( + codec, sample_format, channel_layout, sample_per_second, + extra_data.size() ? &extra_data[0] : NULL, extra_data.size(), + is_audio_track_encrypted_, false); + has_audio_ = true; + audio_track_id_ = track->header.track_id; + } + if (track->media.handler.type == kVideo && !video_config.IsValidConfig()) { + RCHECK(!samp_descr.video_entries.empty()); + if (desc_idx >= samp_descr.video_entries.size()) + desc_idx = 0; + const VideoSampleEntry& entry = samp_descr.video_entries[desc_idx]; + + if (!(entry.format == FOURCC_AVC1 || + (entry.format == FOURCC_ENCV && + entry.sinf.format.format == FOURCC_AVC1))) { + MEDIA_LOG(log_cb_) << "Unsupported video format 0x" + << std::hex << entry.format << " in stsd box."; + return false; + } + + // TODO(strobe): Recover correct crop box + gfx::Size coded_size(entry.width, entry.height); + gfx::Rect visible_rect(coded_size); + gfx::Size natural_size = GetNaturalSize(visible_rect.size(), + entry.pixel_aspect.h_spacing, + entry.pixel_aspect.v_spacing); + is_video_track_encrypted_ = entry.sinf.info.track_encryption.is_encrypted; + DVLOG(1) << "is_video_track_encrypted_: " << is_video_track_encrypted_; + video_config.Initialize(kCodecH264, H264PROFILE_MAIN, VideoFrame::YV12, + coded_size, visible_rect, natural_size, + // No decoder-specific buffer needed for AVC; + // SPS/PPS are embedded in the video stream + NULL, 0, is_video_track_encrypted_, true); + has_video_ = true; + video_track_id_ = track->header.track_id; + } + } + + RCHECK(config_cb_.Run(audio_config, video_config)); + + base::TimeDelta duration; + if (moov_->extends.header.fragment_duration > 0) { + duration = TimeDeltaFromRational(moov_->extends.header.fragment_duration, + moov_->header.timescale); + } else if (moov_->header.duration > 0 && + moov_->header.duration != kuint64max) { + duration = TimeDeltaFromRational(moov_->header.duration, + moov_->header.timescale); + } else { + duration = kInfiniteDuration(); + } + + if (!init_cb_.is_null()) + base::ResetAndReturn(&init_cb_).Run(true, duration); + + EmitNeedKeyIfNecessary(moov_->pssh); + return true; +} + +bool MP4StreamParser::ParseMoof(BoxReader* reader) { + RCHECK(moov_.get()); // Must already have initialization segment + MovieFragment moof; + RCHECK(moof.Parse(reader)); + if (!runs_) + runs_.reset(new TrackRunIterator(moov_.get(), log_cb_)); + RCHECK(runs_->Init(moof)); + EmitNeedKeyIfNecessary(moof.pssh); + new_segment_cb_.Run(); + ChangeState(kEmittingSamples); + return true; +} + +void MP4StreamParser::EmitNeedKeyIfNecessary( + const std::vector& headers) { + // TODO(strobe): ensure that the value of init_data (all PSSH headers + // concatenated in arbitrary order) matches the EME spec. + // See https://www.w3.org/Bugs/Public/show_bug.cgi?id=17673. + if (headers.empty()) + return; + + size_t total_size = 0; + for (size_t i = 0; i < headers.size(); i++) + total_size += headers[i].raw_box.size(); + + scoped_ptr init_data(new uint8[total_size]); + size_t pos = 0; + for (size_t i = 0; i < headers.size(); i++) { + memcpy(&init_data.get()[pos], &headers[i].raw_box[0], + headers[i].raw_box.size()); + pos += headers[i].raw_box.size(); + } + need_key_cb_.Run(kMp4InitDataType, init_data.Pass(), total_size); +} + +bool MP4StreamParser::PrepareAVCBuffer( + const AVCDecoderConfigurationRecord& avc_config, + std::vector* frame_buf, + std::vector* subsamples) const { + // Convert the AVC NALU length fields to Annex B headers, as expected by + // decoding libraries. Since this may enlarge the size of the buffer, we also + // update the clear byte count for each subsample if encryption is used to + // account for the difference in size between the length prefix and Annex B + // start code. + RCHECK(AVC::ConvertFrameToAnnexB(avc_config.length_size, frame_buf)); + if (!subsamples->empty()) { + const int nalu_size_diff = 4 - avc_config.length_size; + size_t expected_size = runs_->sample_size() + + subsamples->size() * nalu_size_diff; + RCHECK(frame_buf->size() == expected_size); + for (size_t i = 0; i < subsamples->size(); i++) + (*subsamples)[i].clear_bytes += nalu_size_diff; + } + + if (runs_->is_keyframe()) { + // If this is a keyframe, we (re-)inject SPS and PPS headers at the start of + // a frame. If subsample info is present, we also update the clear byte + // count for that first subsample. + std::vector param_sets; + RCHECK(AVC::ConvertConfigToAnnexB(avc_config, ¶m_sets)); + frame_buf->insert(frame_buf->begin(), + param_sets.begin(), param_sets.end()); + if (!subsamples->empty()) + (*subsamples)[0].clear_bytes += param_sets.size(); + } + return true; +} + +bool MP4StreamParser::PrepareAACBuffer( + const AAC& aac_config, std::vector* frame_buf, + std::vector* subsamples) const { + // Append an ADTS header to every audio sample. + RCHECK(aac_config.ConvertEsdsToADTS(frame_buf)); + + // As above, adjust subsample information to account for the headers. AAC is + // not required to use subsample encryption, so we may need to add an entry. + if (subsamples->empty()) { + SubsampleEntry entry; + entry.clear_bytes = AAC::kADTSHeaderSize; + entry.cypher_bytes = frame_buf->size() - AAC::kADTSHeaderSize; + subsamples->push_back(entry); + } else { + (*subsamples)[0].clear_bytes += AAC::kADTSHeaderSize; + } + return true; +} + +bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers, + BufferQueue* video_buffers, + bool* err) { + if (!runs_->IsRunValid()) { + // Flush any buffers we've gotten in this chunk so that buffers don't + // cross NewSegment() calls + *err = !SendAndFlushSamples(audio_buffers, video_buffers); + if (*err) + return false; + + // Remain in kEnqueueingSamples state, discarding data, until the end of + // the current 'mdat' box has been appended to the queue. + if (!queue_.Trim(mdat_tail_)) + return false; + + ChangeState(kParsingBoxes); + end_of_segment_cb_.Run(); + return true; + } + + if (!runs_->IsSampleValid()) { + runs_->AdvanceRun(); + return true; + } + + DCHECK(!(*err)); + + const uint8* buf; + int buf_size; + queue_.Peek(&buf, &buf_size); + if (!buf_size) return false; + + bool audio = has_audio_ && audio_track_id_ == runs_->track_id(); + bool video = has_video_ && video_track_id_ == runs_->track_id(); + + // Skip this entire track if it's not one we're interested in + if (!audio && !video) + runs_->AdvanceRun(); + + // Attempt to cache the auxiliary information first. Aux info is usually + // placed in a contiguous block before the sample data, rather than being + // interleaved. If we didn't cache it, this would require that we retain the + // start of the segment buffer while reading samples. Aux info is typically + // quite small compared to sample data, so this pattern is useful on + // memory-constrained devices where the source buffer consumes a substantial + // portion of the total system memory. + if (runs_->AuxInfoNeedsToBeCached()) { + queue_.PeekAt(runs_->aux_info_offset() + moof_head_, &buf, &buf_size); + if (buf_size < runs_->aux_info_size()) return false; + *err = !runs_->CacheAuxInfo(buf, buf_size); + return !*err; + } + + queue_.PeekAt(runs_->sample_offset() + moof_head_, &buf, &buf_size); + if (buf_size < runs_->sample_size()) return false; + + scoped_ptr decrypt_config; + std::vector subsamples; + if (runs_->is_encrypted()) { + decrypt_config = runs_->GetDecryptConfig(); + if (!decrypt_config) { + *err = true; + return false; + } + subsamples = decrypt_config->subsamples(); + } + + std::vector frame_buf(buf, buf + runs_->sample_size()); + if (video) { + if (!PrepareAVCBuffer(runs_->video_description().avcc, + &frame_buf, &subsamples)) { + MEDIA_LOG(log_cb_) << "Failed to prepare AVC sample for decode"; + *err = true; + return false; + } + } + + if (audio) { + if (ESDescriptor::IsAAC(runs_->audio_description().esds.object_type) && + !PrepareAACBuffer(runs_->audio_description().esds.aac, + &frame_buf, &subsamples)) { + MEDIA_LOG(log_cb_) << "Failed to prepare AAC sample for decode"; + *err = true; + return false; + } + } + + if (decrypt_config) { + if (!subsamples.empty()) { + // Create a new config with the updated subsamples. + decrypt_config.reset(new DecryptConfig( + decrypt_config->key_id(), + decrypt_config->iv(), + decrypt_config->data_offset(), + subsamples)); + } + // else, use the existing config. + } else if ((audio && is_audio_track_encrypted_) || + (video && is_video_track_encrypted_)) { + // The media pipeline requires a DecryptConfig with an empty |iv|. + // TODO(ddorwin): Refactor so we do not need a fake key ID ("1"); + decrypt_config.reset( + new DecryptConfig("1", "", 0, std::vector())); + } + + scoped_refptr stream_buf = + StreamParserBuffer::CopyFrom(&frame_buf[0], frame_buf.size(), + runs_->is_keyframe()); + + if (decrypt_config) + stream_buf->set_decrypt_config(decrypt_config.Pass()); + + stream_buf->set_duration(runs_->duration()); + stream_buf->set_timestamp(runs_->cts()); + stream_buf->SetDecodeTimestamp(runs_->dts()); + + DVLOG(3) << "Pushing frame: aud=" << audio + << ", key=" << runs_->is_keyframe() + << ", dur=" << runs_->duration().InMilliseconds() + << ", dts=" << runs_->dts().InMilliseconds() + << ", cts=" << runs_->cts().InMilliseconds() + << ", size=" << runs_->sample_size(); + + if (audio) { + audio_buffers->push_back(stream_buf); + } else { + video_buffers->push_back(stream_buf); + } + + runs_->AdvanceSample(); + return true; +} + +bool MP4StreamParser::SendAndFlushSamples(BufferQueue* audio_buffers, + BufferQueue* video_buffers) { + if (audio_buffers->empty() && video_buffers->empty()) + return true; + + bool success = new_buffers_cb_.Run(*audio_buffers, *video_buffers); + audio_buffers->clear(); + video_buffers->clear(); + return success; +} + +bool MP4StreamParser::ReadAndDiscardMDATsUntil(const int64 offset) { + bool err = false; + while (mdat_tail_ < offset) { + const uint8* buf; + int size; + queue_.PeekAt(mdat_tail_, &buf, &size); + + FourCC type; + int box_sz; + if (!BoxReader::StartTopLevelBox(buf, size, log_cb_, + &type, &box_sz, &err)) + break; + + if (type != FOURCC_MDAT) { + MEDIA_LOG(log_cb_) << "Unexpected box type while parsing MDATs: " + << FourCCToString(type); + } + mdat_tail_ += box_sz; + } + queue_.Trim(std::min(mdat_tail_, offset)); + return !err; +} + +void MP4StreamParser::ChangeState(State new_state) { + DVLOG(2) << "Changing state: " << new_state; + state_ = new_state; +} + +} // namespace mp4 +} // namespace media diff --git a/media/mp4/mp4_stream_parser.h b/media/mp4/mp4_stream_parser.h new file mode 100644 index 0000000000..81139d5270 --- /dev/null +++ b/media/mp4/mp4_stream_parser.h @@ -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. + +#ifndef MEDIA_MP4_MP4_STREAM_PARSER_H_ +#define MEDIA_MP4_MP4_STREAM_PARSER_H_ + +#include +#include + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "media/base/media_export.h" +#include "media/base/stream_parser.h" +#include "media/mp4/offset_byte_queue.h" +#include "media/mp4/track_run_iterator.h" + +namespace media { +namespace mp4 { + +struct Movie; +class BoxReader; + +class MEDIA_EXPORT MP4StreamParser : public StreamParser { + public: + MP4StreamParser(const std::set& audio_object_types, bool has_sbr); + virtual ~MP4StreamParser(); + + virtual void Init(const InitCB& init_cb, const NewConfigCB& config_cb, + const NewBuffersCB& new_buffers_cb, + const NewTextBuffersCB& text_cb, + const NeedKeyCB& need_key_cb, + const AddTextTrackCB& add_text_track_cb, + const NewMediaSegmentCB& new_segment_cb, + const base::Closure& end_of_segment_cb, + const LogCB& log_cb) OVERRIDE; + virtual void Flush() OVERRIDE; + virtual bool Parse(const uint8* buf, int size) OVERRIDE; + + private: + enum State { + kWaitingForInit, + kParsingBoxes, + kEmittingSamples, + kError + }; + + bool ParseBox(bool* err); + bool ParseMoov(mp4::BoxReader* reader); + bool ParseMoof(mp4::BoxReader* reader); + + void EmitNeedKeyIfNecessary( + const std::vector& headers); + + // To retain proper framing, each 'mdat' atom must be read; to limit memory + // usage, the atom's data needs to be discarded incrementally as frames are + // extracted from the stream. This function discards data from the stream up + // to |offset|, updating the |mdat_tail_| value so that framing can be + // retained after all 'mdat' information has been read. + // Returns 'true' on success, 'false' if there was an error. + bool ReadAndDiscardMDATsUntil(const int64 offset); + + void ChangeState(State new_state); + + bool EmitConfigs(); + bool PrepareAVCBuffer(const AVCDecoderConfigurationRecord& avc_config, + std::vector* frame_buf, + std::vector* subsamples) const; + bool PrepareAACBuffer(const AAC& aac_config, + std::vector* frame_buf, + std::vector* subsamples) const; + bool EnqueueSample(BufferQueue* audio_buffers, + BufferQueue* video_buffers, + bool* err); + bool SendAndFlushSamples(BufferQueue* audio_buffers, + BufferQueue* video_buffers); + + void Reset(); + + State state_; + InitCB init_cb_; + NewConfigCB config_cb_; + NewBuffersCB new_buffers_cb_; + NeedKeyCB need_key_cb_; + NewMediaSegmentCB new_segment_cb_; + base::Closure end_of_segment_cb_; + LogCB log_cb_; + + OffsetByteQueue queue_; + + // These two parameters are only valid in the |kEmittingSegments| state. + // + // |moof_head_| is the offset of the start of the most recently parsed moof + // block. All byte offsets in sample information are relative to this offset, + // as mandated by the Media Source spec. + int64 moof_head_; + // |mdat_tail_| is the stream offset of the end of the current 'mdat' box. + // Valid iff it is greater than the head of the queue. + int64 mdat_tail_; + + scoped_ptr moov_; + scoped_ptr runs_; + + bool has_audio_; + bool has_video_; + uint32 audio_track_id_; + uint32 video_track_id_; + // The object types allowed for audio tracks. + std::set audio_object_types_; + bool has_sbr_; + bool is_audio_track_encrypted_; + bool is_video_track_encrypted_; + + DISALLOW_COPY_AND_ASSIGN(MP4StreamParser); +}; + +} // namespace mp4 +} // namespace media + +#endif // MEDIA_MP4_MP4_STREAM_PARSER_H_ diff --git a/media/mp4/mp4_stream_parser_unittest.cc b/media/mp4/mp4_stream_parser_unittest.cc new file mode 100644 index 0000000000..fa880ac38c --- /dev/null +++ b/media/mp4/mp4_stream_parser_unittest.cc @@ -0,0 +1,216 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/time/time.h" +#include "media/base/audio_decoder_config.h" +#include "media/base/decoder_buffer.h" +#include "media/base/stream_parser_buffer.h" +#include "media/base/test_data_util.h" +#include "media/base/video_decoder_config.h" +#include "media/mp4/es_descriptor.h" +#include "media/mp4/mp4_stream_parser.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::TimeDelta; + +namespace media { +namespace mp4 { + +// TODO(xhwang): Figure out the init data type appropriately once it's spec'ed. +static const char kMp4InitDataType[] = "video/mp4"; + +class MP4StreamParserTest : public testing::Test { + public: + MP4StreamParserTest() + : configs_received_(false) { + std::set audio_object_types; + audio_object_types.insert(kISO_14496_3); + parser_.reset(new MP4StreamParser(audio_object_types, false)); + } + + protected: + scoped_ptr parser_; + bool configs_received_; + + bool AppendData(const uint8* data, size_t length) { + return parser_->Parse(data, length); + } + + bool AppendDataInPieces(const uint8* data, size_t length, size_t piece_size) { + const uint8* start = data; + const uint8* end = data + length; + while (start < end) { + size_t append_size = std::min(piece_size, + static_cast(end - start)); + if (!AppendData(start, append_size)) + return false; + start += append_size; + } + return true; + } + + void InitF(bool init_ok, base::TimeDelta duration) { + DVLOG(1) << "InitF: ok=" << init_ok + << ", dur=" << duration.InMilliseconds(); + } + + bool NewConfigF(const AudioDecoderConfig& ac, const VideoDecoderConfig& vc) { + DVLOG(1) << "NewConfigF: audio=" << ac.IsValidConfig() + << ", video=" << vc.IsValidConfig(); + configs_received_ = true; + return true; + } + + + void DumpBuffers(const std::string& label, + const StreamParser::BufferQueue& buffers) { + DVLOG(2) << "DumpBuffers: " << label << " size " << buffers.size(); + for (StreamParser::BufferQueue::const_iterator buf = buffers.begin(); + buf != buffers.end(); buf++) { + DVLOG(3) << " n=" << buf - buffers.begin() + << ", size=" << (*buf)->data_size() + << ", dur=" << (*buf)->duration().InMilliseconds(); + } + } + + bool NewBuffersF(const StreamParser::BufferQueue& audio_buffers, + const StreamParser::BufferQueue& video_buffers) { + DumpBuffers("audio_buffers", audio_buffers); + DumpBuffers("video_buffers", video_buffers); + return true; + } + + bool NewTextBuffersF(TextTrack* text_track, + const StreamParser::BufferQueue& buffers) { + return true; + } + + void KeyNeededF(const std::string& type, + scoped_ptr init_data, int init_data_size) { + DVLOG(1) << "KeyNeededF: " << init_data_size; + EXPECT_EQ(kMp4InitDataType, type); + EXPECT_TRUE(init_data.get()); + EXPECT_GT(init_data_size, 0); + } + + scoped_ptr AddTextTrackF( + TextKind kind, + const std::string& label, + const std::string& language) { + return scoped_ptr(); + } + + void NewSegmentF() { + DVLOG(1) << "NewSegmentF"; + } + + void EndOfSegmentF() { + DVLOG(1) << "EndOfSegmentF()"; + } + + void InitializeParser() { + parser_->Init( + base::Bind(&MP4StreamParserTest::InitF, base::Unretained(this)), + base::Bind(&MP4StreamParserTest::NewConfigF, base::Unretained(this)), + base::Bind(&MP4StreamParserTest::NewBuffersF, base::Unretained(this)), + base::Bind(&MP4StreamParserTest::NewTextBuffersF, + base::Unretained(this)), + base::Bind(&MP4StreamParserTest::KeyNeededF, base::Unretained(this)), + base::Bind(&MP4StreamParserTest::AddTextTrackF, base::Unretained(this)), + base::Bind(&MP4StreamParserTest::NewSegmentF, base::Unretained(this)), + base::Bind(&MP4StreamParserTest::EndOfSegmentF, + base::Unretained(this)), + LogCB()); + } + + bool ParseMP4File(const std::string& filename, int append_bytes) { + InitializeParser(); + + scoped_refptr buffer = ReadTestDataFile(filename); + EXPECT_TRUE(AppendDataInPieces(buffer->data(), + buffer->data_size(), + append_bytes)); + return true; + } +}; + +TEST_F(MP4StreamParserTest, UnalignedAppend) { + // Test small, non-segment-aligned appends (small enough to exercise + // incremental append system) + ParseMP4File("bear-1280x720-av_frag.mp4", 512); +} + +TEST_F(MP4StreamParserTest, BytewiseAppend) { + // Ensure no incremental errors occur when parsing + ParseMP4File("bear-1280x720-av_frag.mp4", 1); +} + +TEST_F(MP4StreamParserTest, MultiFragmentAppend) { + // Large size ensures multiple fragments are appended in one call (size is + // larger than this particular test file) + ParseMP4File("bear-1280x720-av_frag.mp4", 768432); +} + +TEST_F(MP4StreamParserTest, Flush) { + // Flush while reading sample data, then start a new stream. + InitializeParser(); + + scoped_refptr buffer = + ReadTestDataFile("bear-1280x720-av_frag.mp4"); + EXPECT_TRUE(AppendDataInPieces(buffer->data(), 65536, 512)); + parser_->Flush(); + EXPECT_TRUE(AppendDataInPieces(buffer->data(), + buffer->data_size(), + 512)); +} + +TEST_F(MP4StreamParserTest, Reinitialization) { + InitializeParser(); + + scoped_refptr buffer = + ReadTestDataFile("bear-1280x720-av_frag.mp4"); + EXPECT_TRUE(AppendDataInPieces(buffer->data(), + buffer->data_size(), + 512)); + EXPECT_TRUE(AppendDataInPieces(buffer->data(), + buffer->data_size(), + 512)); +} + +TEST_F(MP4StreamParserTest, MPEG2_AAC_LC) { + std::set audio_object_types; + audio_object_types.insert(kISO_13818_7_AAC_LC); + parser_.reset(new MP4StreamParser(audio_object_types, false)); + ParseMP4File("bear-mpeg2-aac-only_frag.mp4", 512); +} + +// Test that a moov box is not always required after Flush() is called. +TEST_F(MP4StreamParserTest, NoMoovAfterFlush) { + InitializeParser(); + + scoped_refptr buffer = + ReadTestDataFile("bear-1280x720-av_frag.mp4"); + EXPECT_TRUE(AppendDataInPieces(buffer->data(), + buffer->data_size(), + 512)); + parser_->Flush(); + + const int kFirstMoofOffset = 1307; + EXPECT_TRUE(AppendDataInPieces(buffer->data() + kFirstMoofOffset, + buffer->data_size() - kFirstMoofOffset, + 512)); +} + +// TODO(strobe): Create and test media which uses CENC auxiliary info stored +// inside a private box + +} // namespace mp4 +} // namespace media diff --git a/media/mp4/offset_byte_queue.cc b/media/mp4/offset_byte_queue.cc new file mode 100644 index 0000000000..a530150899 --- /dev/null +++ b/media/mp4/offset_byte_queue.cc @@ -0,0 +1,64 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/mp4/offset_byte_queue.h" + +#include "base/basictypes.h" +#include "base/logging.h" + +namespace media { + +OffsetByteQueue::OffsetByteQueue() : buf_(NULL), size_(0), head_(0) {} +OffsetByteQueue::~OffsetByteQueue() {} + +void OffsetByteQueue::Reset() { + queue_.Reset(); + buf_ = NULL; + size_ = 0; + head_ = 0; +} + +void OffsetByteQueue::Push(const uint8* buf, int size) { + queue_.Push(buf, size); + Sync(); + DVLOG(4) << "Buffer pushed. head=" << head() << " tail=" << tail(); +} + +void OffsetByteQueue::Peek(const uint8** buf, int* size) { + *buf = size_ > 0 ? buf_ : NULL; + *size = size_; +} + +void OffsetByteQueue::Pop(int count) { + queue_.Pop(count); + head_ += count; + Sync(); +} + +void OffsetByteQueue::PeekAt(int64 offset, const uint8** buf, int* size) { + DCHECK(offset >= head()); + if (offset < head() || offset >= tail()) { + *buf = NULL; + *size = 0; + return; + } + *buf = &buf_[offset - head()]; + *size = tail() - offset; +} + +bool OffsetByteQueue::Trim(int64 max_offset) { + if (max_offset < head_) return true; + if (max_offset > tail()) { + Pop(size_); + return false; + } + Pop(max_offset - head_); + return true; +} + +void OffsetByteQueue::Sync() { + queue_.Peek(&buf_, &size_); +} + +} // namespace media diff --git a/media/mp4/offset_byte_queue.h b/media/mp4/offset_byte_queue.h new file mode 100644 index 0000000000..9349b96088 --- /dev/null +++ b/media/mp4/offset_byte_queue.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 MEDIA_MP4_OFFSET_BYTE_QUEUE_H_ +#define MEDIA_MP4_OFFSET_BYTE_QUEUE_H_ + +#include "base/basictypes.h" +#include "media/base/byte_queue.h" +#include "media/base/media_export.h" + +namespace media { + +// A wrapper around a ByteQueue which maintains a notion of a +// monotonically-increasing offset. All buffer access is done by passing these +// offsets into this class, going some way towards preventing the proliferation +// of many different meanings of "offset", "head", etc. +class MEDIA_EXPORT OffsetByteQueue { + public: + OffsetByteQueue(); + ~OffsetByteQueue(); + + // These work like their underlying ByteQueue counterparts. + void Reset(); + void Push(const uint8* buf, int size); + void Peek(const uint8** buf, int* size); + void Pop(int count); + + // Sets |buf| to point at the first buffered byte corresponding to |offset|, + // and |size| to the number of bytes available starting from that offset. + // + // It is an error if the offset is before the current head. It's not an error + // if the current offset is beyond tail(), but you will of course get back + // a null |buf| and a |size| of zero. + void PeekAt(int64 offset, const uint8** buf, int* size); + + // Marks the bytes up to (but not including) |max_offset| as ready for + // deletion. This is relatively inexpensive, but will not necessarily reduce + // the resident buffer size right away (or ever). + // + // Returns true if the full range of bytes were successfully trimmed, + // including the case where |max_offset| is less than the current head. + // Returns false if |max_offset| > tail() (although all bytes currently + // buffered are still cleared). + bool Trim(int64 max_offset); + + // The head and tail positions, in terms of the file's absolute offsets. + // tail() is an exclusive bound. + int64 head() { return head_; } + int64 tail() { return head_ + size_; } + + private: + // Synchronize |buf_| and |size_| with |queue_|. + void Sync(); + + ByteQueue queue_; + const uint8* buf_; + int size_; + int64 head_; + + DISALLOW_COPY_AND_ASSIGN(OffsetByteQueue); +}; + +} // namespace media + +#endif // MEDIA_MP4_MP4_STREAM_PARSER_H_ diff --git a/media/mp4/offset_byte_queue_unittest.cc b/media/mp4/offset_byte_queue_unittest.cc new file mode 100644 index 0000000000..b9afbc8e1b --- /dev/null +++ b/media/mp4/offset_byte_queue_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 + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "media/mp4/offset_byte_queue.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +class OffsetByteQueueTest : public testing::Test { + public: + virtual void SetUp() OVERRIDE { + uint8 buf[256]; + for (int i = 0; i < 256; i++) { + buf[i] = i; + } + queue_.reset(new OffsetByteQueue); + queue_->Push(buf, sizeof(buf)); + queue_->Push(buf, sizeof(buf)); + queue_->Pop(384); + + // Queue will start with 128 bytes of data and an offset of 384 bytes. + // These values are used throughout the test. + } + + protected: + scoped_ptr queue_; +}; + +TEST_F(OffsetByteQueueTest, SetUp) { + EXPECT_EQ(384, queue_->head()); + EXPECT_EQ(512, queue_->tail()); + + const uint8* buf; + int size; + + queue_->Peek(&buf, &size); + EXPECT_EQ(128, size); + EXPECT_EQ(128, buf[0]); + EXPECT_EQ(255, buf[size-1]); +} + +TEST_F(OffsetByteQueueTest, PeekAt) { + const uint8* buf; + int size; + + queue_->PeekAt(400, &buf, &size); + EXPECT_EQ(queue_->tail() - 400, size); + EXPECT_EQ(400 - 256, buf[0]); + + queue_->PeekAt(512, &buf, &size); + EXPECT_EQ(NULL, buf); + EXPECT_EQ(0, size); +} + +TEST_F(OffsetByteQueueTest, Trim) { + EXPECT_TRUE(queue_->Trim(128)); + EXPECT_TRUE(queue_->Trim(384)); + EXPECT_EQ(384, queue_->head()); + EXPECT_EQ(512, queue_->tail()); + + EXPECT_TRUE(queue_->Trim(400)); + EXPECT_EQ(400, queue_->head()); + EXPECT_EQ(512, queue_->tail()); + + const uint8* buf; + int size; + queue_->PeekAt(400, &buf, &size); + EXPECT_EQ(queue_->tail() - 400, size); + EXPECT_EQ(400 - 256, buf[0]); + + // Trimming to the exact end of the buffer should return 'true'. This + // accomodates EOS cases. + EXPECT_TRUE(queue_->Trim(512)); + EXPECT_EQ(512, queue_->head()); + queue_->Peek(&buf, &size); + EXPECT_EQ(NULL, buf); + + // Trimming past the end of the buffer should return 'false'; we haven't seen + // the preceeding bytes. + EXPECT_FALSE(queue_->Trim(513)); + + // However, doing that shouldn't affect the EOS case. Only adding new data + // should alter this behavior. + EXPECT_TRUE(queue_->Trim(512)); +} + +} // namespace media diff --git a/media/mp4/rcheck.h b/media/mp4/rcheck.h new file mode 100644 index 0000000000..8165056067 --- /dev/null +++ b/media/mp4/rcheck.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 MEDIA_MP4_RCHECK_H_ +#define MEDIA_MP4_RCHECK_H_ + +#include "base/logging.h" + +#define RCHECK(x) \ + do { \ + if (!(x)) { \ + DLOG(ERROR) << "Failure while parsing MP4: " << #x; \ + return false; \ + } \ + } while (0) + +#endif // MEDIA_MP4_RCHECK_H_ diff --git a/media/mp4/track_run_iterator.cc b/media/mp4/track_run_iterator.cc new file mode 100644 index 0000000000..f16a8bffd1 --- /dev/null +++ b/media/mp4/track_run_iterator.cc @@ -0,0 +1,441 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/mp4/track_run_iterator.h" + +#include + +#include "media/base/buffers.h" +#include "media/base/stream_parser_buffer.h" +#include "media/mp4/rcheck.h" + +namespace { +static const uint32 kSampleIsDifferenceSampleFlagMask = 0x10000; +} + +namespace media { +namespace mp4 { + +struct SampleInfo { + int size; + int duration; + int cts_offset; + bool is_keyframe; +}; + +struct TrackRunInfo { + uint32 track_id; + std::vector samples; + int64 timescale; + int64 start_dts; + int64 sample_start_offset; + + bool is_audio; + const AudioSampleEntry* audio_description; + const VideoSampleEntry* video_description; + + int64 aux_info_start_offset; // Only valid if aux_info_total_size > 0. + int aux_info_default_size; + std::vector aux_info_sizes; // Populated if default_size == 0. + int aux_info_total_size; + + TrackRunInfo(); + ~TrackRunInfo(); +}; + +TrackRunInfo::TrackRunInfo() + : track_id(0), + timescale(-1), + start_dts(-1), + sample_start_offset(-1), + is_audio(false), + aux_info_start_offset(-1), + aux_info_default_size(-1), + aux_info_total_size(-1) { +} +TrackRunInfo::~TrackRunInfo() {} + +TimeDelta TimeDeltaFromRational(int64 numer, int64 denom) { + DCHECK_LT((numer > 0 ? numer : -numer), + kint64max / base::Time::kMicrosecondsPerSecond); + return TimeDelta::FromMicroseconds( + base::Time::kMicrosecondsPerSecond * numer / denom); +} + +TrackRunIterator::TrackRunIterator(const Movie* moov, + const LogCB& log_cb) + : moov_(moov), log_cb_(log_cb), sample_offset_(0) { + CHECK(moov); +} + +TrackRunIterator::~TrackRunIterator() {} + +static void PopulateSampleInfo(const TrackExtends& trex, + const TrackFragmentHeader& tfhd, + const TrackFragmentRun& trun, + const int64 edit_list_offset, + const uint32 i, + SampleInfo* sample_info) { + if (i < trun.sample_sizes.size()) { + sample_info->size = trun.sample_sizes[i]; + } else if (tfhd.default_sample_size > 0) { + sample_info->size = tfhd.default_sample_size; + } else { + sample_info->size = trex.default_sample_size; + } + + if (i < trun.sample_durations.size()) { + sample_info->duration = trun.sample_durations[i]; + } else if (tfhd.default_sample_duration > 0) { + sample_info->duration = tfhd.default_sample_duration; + } else { + sample_info->duration = trex.default_sample_duration; + } + + if (i < trun.sample_composition_time_offsets.size()) { + sample_info->cts_offset = trun.sample_composition_time_offsets[i]; + } else { + sample_info->cts_offset = 0; + } + sample_info->cts_offset += edit_list_offset; + + uint32 flags; + if (i < trun.sample_flags.size()) { + flags = trun.sample_flags[i]; + } else if (tfhd.has_default_sample_flags) { + flags = tfhd.default_sample_flags; + } else { + flags = trex.default_sample_flags; + } + sample_info->is_keyframe = !(flags & kSampleIsDifferenceSampleFlagMask); +} + +// In well-structured encrypted media, each track run will be immediately +// preceded by its auxiliary information; this is the only optimal storage +// pattern in terms of minimum number of bytes from a serial stream needed to +// begin playback. It also allows us to optimize caching on memory-constrained +// architectures, because we can cache the relatively small auxiliary +// information for an entire run and then discard data from the input stream, +// instead of retaining the entire 'mdat' box. +// +// We optimize for this situation (with no loss of generality) by sorting track +// runs during iteration in order of their first data offset (either sample data +// or auxiliary data). +class CompareMinTrackRunDataOffset { + public: + bool operator()(const TrackRunInfo& a, const TrackRunInfo& b) { + int64 a_aux = a.aux_info_total_size ? a.aux_info_start_offset : kint64max; + int64 b_aux = b.aux_info_total_size ? b.aux_info_start_offset : kint64max; + + int64 a_lesser = std::min(a_aux, a.sample_start_offset); + int64 a_greater = std::max(a_aux, a.sample_start_offset); + int64 b_lesser = std::min(b_aux, b.sample_start_offset); + int64 b_greater = std::max(b_aux, b.sample_start_offset); + + if (a_lesser == b_lesser) return a_greater < b_greater; + return a_lesser < b_lesser; + } +}; + +bool TrackRunIterator::Init(const MovieFragment& moof) { + runs_.clear(); + + for (size_t i = 0; i < moof.tracks.size(); i++) { + const TrackFragment& traf = moof.tracks[i]; + + const Track* trak = NULL; + for (size_t t = 0; t < moov_->tracks.size(); t++) { + if (moov_->tracks[t].header.track_id == traf.header.track_id) + trak = &moov_->tracks[t]; + } + RCHECK(trak); + + const TrackExtends* trex = NULL; + for (size_t t = 0; t < moov_->extends.tracks.size(); t++) { + if (moov_->extends.tracks[t].track_id == traf.header.track_id) + trex = &moov_->extends.tracks[t]; + } + RCHECK(trex); + + const SampleDescription& stsd = + trak->media.information.sample_table.description; + if (stsd.type != kAudio && stsd.type != kVideo) { + DVLOG(1) << "Skipping unhandled track type"; + continue; + } + size_t desc_idx = traf.header.sample_description_index; + if (!desc_idx) desc_idx = trex->default_sample_description_index; + RCHECK(desc_idx > 0); // Descriptions are one-indexed in the file + desc_idx -= 1; + + // Process edit list to remove CTS offset introduced in the presence of + // B-frames (those that contain a single edit with a nonnegative media + // time). Other uses of edit lists are not supported, as they are + // both uncommon and better served by higher-level protocols. + int64 edit_list_offset = 0; + const std::vector& edits = trak->edit.list.edits; + if (!edits.empty()) { + if (edits.size() > 1) + DVLOG(1) << "Multi-entry edit box detected; some components ignored."; + + if (edits[0].media_time < 0) { + DVLOG(1) << "Empty edit list entry ignored."; + } else { + edit_list_offset = -edits[0].media_time; + } + } + + int64 run_start_dts = traf.decode_time.decode_time; + int sample_count_sum = 0; + + for (size_t j = 0; j < traf.runs.size(); j++) { + const TrackFragmentRun& trun = traf.runs[j]; + TrackRunInfo tri; + tri.track_id = traf.header.track_id; + tri.timescale = trak->media.header.timescale; + tri.start_dts = run_start_dts; + tri.sample_start_offset = trun.data_offset; + + tri.is_audio = (stsd.type == kAudio); + if (tri.is_audio) { + RCHECK(!stsd.audio_entries.empty()); + if (desc_idx > stsd.audio_entries.size()) + desc_idx = 0; + tri.audio_description = &stsd.audio_entries[desc_idx]; + } else { + RCHECK(!stsd.video_entries.empty()); + if (desc_idx > stsd.video_entries.size()) + desc_idx = 0; + tri.video_description = &stsd.video_entries[desc_idx]; + } + + // Collect information from the auxiliary_offset entry with the same index + // in the 'saiz' container as the current run's index in the 'trun' + // container, if it is present. + if (traf.auxiliary_offset.offsets.size() > j) { + // There should be an auxiliary info entry corresponding to each sample + // in the auxiliary offset entry's corresponding track run. + RCHECK(traf.auxiliary_size.sample_count >= + sample_count_sum + trun.sample_count); + tri.aux_info_start_offset = traf.auxiliary_offset.offsets[j]; + tri.aux_info_default_size = + traf.auxiliary_size.default_sample_info_size; + if (tri.aux_info_default_size == 0) { + const std::vector& sizes = + traf.auxiliary_size.sample_info_sizes; + tri.aux_info_sizes.insert(tri.aux_info_sizes.begin(), + sizes.begin() + sample_count_sum, + sizes.begin() + sample_count_sum + trun.sample_count); + } + + // If the default info size is positive, find the total size of the aux + // info block from it, otherwise sum over the individual sizes of each + // aux info entry in the aux_offset entry. + if (tri.aux_info_default_size) { + tri.aux_info_total_size = + tri.aux_info_default_size * trun.sample_count; + } else { + tri.aux_info_total_size = 0; + for (size_t k = 0; k < trun.sample_count; k++) { + tri.aux_info_total_size += tri.aux_info_sizes[k]; + } + } + } else { + tri.aux_info_start_offset = -1; + tri.aux_info_total_size = 0; + } + + tri.samples.resize(trun.sample_count); + for (size_t k = 0; k < trun.sample_count; k++) { + PopulateSampleInfo(*trex, traf.header, trun, edit_list_offset, + k, &tri.samples[k]); + run_start_dts += tri.samples[k].duration; + } + runs_.push_back(tri); + sample_count_sum += trun.sample_count; + } + } + + std::sort(runs_.begin(), runs_.end(), CompareMinTrackRunDataOffset()); + run_itr_ = runs_.begin(); + ResetRun(); + return true; +} + +void TrackRunIterator::AdvanceRun() { + ++run_itr_; + ResetRun(); +} + +void TrackRunIterator::ResetRun() { + if (!IsRunValid()) return; + sample_dts_ = run_itr_->start_dts; + sample_offset_ = run_itr_->sample_start_offset; + sample_itr_ = run_itr_->samples.begin(); + cenc_info_.clear(); +} + +void TrackRunIterator::AdvanceSample() { + DCHECK(IsSampleValid()); + sample_dts_ += sample_itr_->duration; + sample_offset_ += sample_itr_->size; + ++sample_itr_; +} + +// This implementation only indicates a need for caching if CENC auxiliary +// info is available in the stream. +bool TrackRunIterator::AuxInfoNeedsToBeCached() { + DCHECK(IsRunValid()); + return is_encrypted() && aux_info_size() > 0 && cenc_info_.size() == 0; +} + +// This implementation currently only caches CENC auxiliary info. +bool TrackRunIterator::CacheAuxInfo(const uint8* buf, int buf_size) { + RCHECK(AuxInfoNeedsToBeCached() && buf_size >= aux_info_size()); + + cenc_info_.resize(run_itr_->samples.size()); + int64 pos = 0; + for (size_t i = 0; i < run_itr_->samples.size(); i++) { + int info_size = run_itr_->aux_info_default_size; + if (!info_size) + info_size = run_itr_->aux_info_sizes[i]; + + BufferReader reader(buf + pos, info_size); + RCHECK(cenc_info_[i].Parse(track_encryption().default_iv_size, &reader)); + pos += info_size; + } + + return true; +} + +bool TrackRunIterator::IsRunValid() const { + return run_itr_ != runs_.end(); +} + +bool TrackRunIterator::IsSampleValid() const { + return IsRunValid() && (sample_itr_ != run_itr_->samples.end()); +} + +// Because tracks are in sorted order and auxiliary information is cached when +// returning samples, it is guaranteed that no data will be required before the +// lesser of the minimum data offset of this track and the next in sequence. +// (The stronger condition - that no data is required before the minimum data +// offset of this track alone - is not guaranteed, because the BMFF spec does +// not have any inter-run ordering restrictions.) +int64 TrackRunIterator::GetMaxClearOffset() { + int64 offset = kint64max; + + if (IsSampleValid()) { + offset = std::min(offset, sample_offset_); + if (AuxInfoNeedsToBeCached()) + offset = std::min(offset, aux_info_offset()); + } + if (run_itr_ != runs_.end()) { + std::vector::const_iterator next_run = run_itr_ + 1; + if (next_run != runs_.end()) { + offset = std::min(offset, next_run->sample_start_offset); + if (next_run->aux_info_total_size) + offset = std::min(offset, next_run->aux_info_start_offset); + } + } + if (offset == kint64max) return 0; + return offset; +} + +uint32 TrackRunIterator::track_id() const { + DCHECK(IsRunValid()); + return run_itr_->track_id; +} + +bool TrackRunIterator::is_encrypted() const { + DCHECK(IsRunValid()); + return track_encryption().is_encrypted; +} + +int64 TrackRunIterator::aux_info_offset() const { + return run_itr_->aux_info_start_offset; +} + +int TrackRunIterator::aux_info_size() const { + return run_itr_->aux_info_total_size; +} + +bool TrackRunIterator::is_audio() const { + DCHECK(IsRunValid()); + return run_itr_->is_audio; +} + +const AudioSampleEntry& TrackRunIterator::audio_description() const { + DCHECK(is_audio()); + DCHECK(run_itr_->audio_description); + return *run_itr_->audio_description; +} + +const VideoSampleEntry& TrackRunIterator::video_description() const { + DCHECK(!is_audio()); + DCHECK(run_itr_->video_description); + return *run_itr_->video_description; +} + +int64 TrackRunIterator::sample_offset() const { + DCHECK(IsSampleValid()); + return sample_offset_; +} + +int TrackRunIterator::sample_size() const { + DCHECK(IsSampleValid()); + return sample_itr_->size; +} + +TimeDelta TrackRunIterator::dts() const { + DCHECK(IsSampleValid()); + return TimeDeltaFromRational(sample_dts_, run_itr_->timescale); +} + +TimeDelta TrackRunIterator::cts() const { + DCHECK(IsSampleValid()); + return TimeDeltaFromRational(sample_dts_ + sample_itr_->cts_offset, + run_itr_->timescale); +} + +TimeDelta TrackRunIterator::duration() const { + DCHECK(IsSampleValid()); + return TimeDeltaFromRational(sample_itr_->duration, run_itr_->timescale); +} + +bool TrackRunIterator::is_keyframe() const { + DCHECK(IsSampleValid()); + return sample_itr_->is_keyframe; +} + +const TrackEncryption& TrackRunIterator::track_encryption() const { + if (is_audio()) + return audio_description().sinf.info.track_encryption; + return video_description().sinf.info.track_encryption; +} + +scoped_ptr TrackRunIterator::GetDecryptConfig() { + size_t sample_idx = sample_itr_ - run_itr_->samples.begin(); + DCHECK(sample_idx < cenc_info_.size()); + const FrameCENCInfo& cenc_info = cenc_info_[sample_idx]; + DCHECK(is_encrypted() && !AuxInfoNeedsToBeCached()); + + if (!cenc_info.subsamples.empty() && + (cenc_info.GetTotalSizeOfSubsamples() != + static_cast(sample_size()))) { + MEDIA_LOG(log_cb_) << "Incorrect CENC subsample size."; + return scoped_ptr(); + } + + const std::vector& kid = track_encryption().default_kid; + return scoped_ptr(new DecryptConfig( + std::string(reinterpret_cast(&kid[0]), kid.size()), + std::string(reinterpret_cast(cenc_info.iv), + arraysize(cenc_info.iv)), + 0, // No offset to start of media data in MP4 using CENC. + cenc_info.subsamples)); +} + +} // namespace mp4 +} // namespace media diff --git a/media/mp4/track_run_iterator.h b/media/mp4/track_run_iterator.h new file mode 100644 index 0000000000..a21c5ba0c2 --- /dev/null +++ b/media/mp4/track_run_iterator.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 MEDIA_MP4_TRACK_RUN_ITERATOR_H_ +#define MEDIA_MP4_TRACK_RUN_ITERATOR_H_ + +#include + +#include "base/memory/scoped_ptr.h" +#include "base/time/time.h" +#include "media/base/media_export.h" +#include "media/base/media_log.h" +#include "media/mp4/box_definitions.h" +#include "media/mp4/cenc.h" + +namespace media { + +class DecryptConfig; + +namespace mp4 { + +using base::TimeDelta; +base::TimeDelta MEDIA_EXPORT TimeDeltaFromRational(int64 numer, int64 denom); + +struct SampleInfo; +struct TrackRunInfo; + +class MEDIA_EXPORT TrackRunIterator { + public: + // Create a new TrackRunIterator. A reference to |moov| will be retained for + // the lifetime of this object. + TrackRunIterator(const Movie* moov, const LogCB& log_cb); + ~TrackRunIterator(); + + // Sets up the iterator to handle all the runs from the current fragment. + bool Init(const MovieFragment& moof); + + // Returns true if the properties of the current run or sample are valid. + bool IsRunValid() const; + bool IsSampleValid() const; + + // Advance the properties to refer to the next run or sample. Requires that + // the current sample be valid. + void AdvanceRun(); + void AdvanceSample(); + + // Returns true if this track run has auxiliary information and has not yet + // been cached. Only valid if IsRunValid(). + bool AuxInfoNeedsToBeCached(); + + // Caches the CENC data from the given buffer. |buf| must be a buffer starting + // at the offset given by cenc_offset(), with a |size| of at least + // cenc_size(). Returns true on success, false on error. + bool CacheAuxInfo(const uint8* buf, int size); + + // Returns the maximum buffer location at which no data earlier in the stream + // will be required in order to read the current or any subsequent sample. You + // may clear all data up to this offset before reading the current sample + // safely. Result is in the same units as offset() (for Media Source this is + // in bytes past the the head of the MOOF box). + int64 GetMaxClearOffset(); + + // Property of the current run. Only valid if IsRunValid(). + uint32 track_id() const; + int64 aux_info_offset() const; + int aux_info_size() const; + bool is_encrypted() const; + bool is_audio() const; + // Only one is valid, based on the value of is_audio(). + const AudioSampleEntry& audio_description() const; + const VideoSampleEntry& video_description() const; + + // Properties of the current sample. Only valid if IsSampleValid(). + int64 sample_offset() const; + int sample_size() const; + TimeDelta dts() const; + TimeDelta cts() const; + TimeDelta duration() const; + bool is_keyframe() const; + + // Only call when is_encrypted() is true and AuxInfoNeedsToBeCached() is + // false. Result is owned by caller. + scoped_ptr GetDecryptConfig(); + + private: + void ResetRun(); + const TrackEncryption& track_encryption() const; + + const Movie* moov_; + LogCB log_cb_; + + std::vector runs_; + std::vector::const_iterator run_itr_; + std::vector::const_iterator sample_itr_; + + std::vector cenc_info_; + + int64 sample_dts_; + int64 sample_offset_; + + DISALLOW_COPY_AND_ASSIGN(TrackRunIterator); +}; + +} // namespace mp4 +} // namespace media + +#endif // MEDIA_MP4_TRACK_RUN_ITERATOR_H_ diff --git a/media/mp4/track_run_iterator_unittest.cc b/media/mp4/track_run_iterator_unittest.cc new file mode 100644 index 0000000000..499a2e1a69 --- /dev/null +++ b/media/mp4/track_run_iterator_unittest.cc @@ -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. + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "media/mp4/box_definitions.h" +#include "media/mp4/rcheck.h" +#include "media/mp4/track_run_iterator.h" +#include "testing/gtest/include/gtest/gtest.h" + +// The sum of the elements in a vector initialized with SumAscending, +// less the value of the last element. +static const int kSumAscending1 = 45; + +static const int kAudioScale = 48000; +static const int kVideoScale = 25; + +static const uint32 kSampleIsDifferenceSampleFlagMask = 0x10000; + +static const uint8 kAuxInfo[] = { + 0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x31, + 0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x32, + 0x00, 0x02, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x04 +}; + +static const char kIv1[] = { + 0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x31, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static const uint8 kKeyId[] = { + 0x41, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x54, + 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x44 +}; + +namespace media { +namespace mp4 { + +class TrackRunIteratorTest : public testing::Test { + public: + TrackRunIteratorTest() { + CreateMovie(); + } + + protected: + Movie moov_; + LogCB log_cb_; + scoped_ptr iter_; + + void CreateMovie() { + moov_.header.timescale = 1000; + moov_.tracks.resize(3); + moov_.extends.tracks.resize(2); + moov_.tracks[0].header.track_id = 1; + moov_.tracks[0].media.header.timescale = kAudioScale; + SampleDescription& desc1 = + moov_.tracks[0].media.information.sample_table.description; + AudioSampleEntry aud_desc; + aud_desc.format = FOURCC_MP4A; + aud_desc.sinf.info.track_encryption.is_encrypted = false; + desc1.type = kAudio; + desc1.audio_entries.push_back(aud_desc); + moov_.extends.tracks[0].track_id = 1; + moov_.extends.tracks[0].default_sample_description_index = 1; + + moov_.tracks[1].header.track_id = 2; + moov_.tracks[1].media.header.timescale = kVideoScale; + SampleDescription& desc2 = + moov_.tracks[1].media.information.sample_table.description; + VideoSampleEntry vid_desc; + vid_desc.format = FOURCC_AVC1; + vid_desc.sinf.info.track_encryption.is_encrypted = false; + desc2.type = kVideo; + desc2.video_entries.push_back(vid_desc); + moov_.extends.tracks[1].track_id = 2; + moov_.extends.tracks[1].default_sample_description_index = 1; + + moov_.tracks[2].header.track_id = 3; + moov_.tracks[2].media.information.sample_table.description.type = kHint; + } + + MovieFragment CreateFragment() { + MovieFragment moof; + moof.tracks.resize(2); + moof.tracks[0].decode_time.decode_time = 0; + moof.tracks[0].header.track_id = 1; + moof.tracks[0].header.has_default_sample_flags = true; + moof.tracks[0].header.default_sample_duration = 1024; + moof.tracks[0].header.default_sample_size = 4; + moof.tracks[0].runs.resize(2); + moof.tracks[0].runs[0].sample_count = 10; + moof.tracks[0].runs[0].data_offset = 100; + SetAscending(&moof.tracks[0].runs[0].sample_sizes); + + moof.tracks[0].runs[1].sample_count = 10; + moof.tracks[0].runs[1].data_offset = 10000; + + moof.tracks[1].header.track_id = 2; + moof.tracks[1].header.has_default_sample_flags = false; + moof.tracks[1].decode_time.decode_time = 10; + moof.tracks[1].runs.resize(1); + moof.tracks[1].runs[0].sample_count = 10; + moof.tracks[1].runs[0].data_offset = 200; + SetAscending(&moof.tracks[1].runs[0].sample_sizes); + SetAscending(&moof.tracks[1].runs[0].sample_durations); + moof.tracks[1].runs[0].sample_flags.resize(10); + for (size_t i = 1; i < moof.tracks[1].runs[0].sample_flags.size(); i++) { + moof.tracks[1].runs[0].sample_flags[i] = + kSampleIsDifferenceSampleFlagMask; + } + + return moof; + } + + // Update the first sample description of a Track to indicate encryption + void AddEncryption(Track* track) { + SampleDescription* stsd = + &track->media.information.sample_table.description; + ProtectionSchemeInfo* sinf; + if (!stsd->video_entries.empty()) { + sinf = &stsd->video_entries[0].sinf; + } else { + sinf = &stsd->audio_entries[0].sinf; + } + + sinf->type.type = FOURCC_CENC; + sinf->info.track_encryption.is_encrypted = true; + sinf->info.track_encryption.default_iv_size = 8; + sinf->info.track_encryption.default_kid.insert( + sinf->info.track_encryption.default_kid.begin(), + kKeyId, kKeyId + arraysize(kKeyId)); + } + + // Add aux info covering the first track run to a TrackFragment, and update + // the run to ensure it matches length and subsample information. + void AddAuxInfoHeaders(int offset, TrackFragment* frag) { + frag->auxiliary_offset.offsets.push_back(offset); + frag->auxiliary_size.sample_count = 2; + frag->auxiliary_size.sample_info_sizes.push_back(8); + frag->auxiliary_size.sample_info_sizes.push_back(22); + frag->runs[0].sample_count = 2; + frag->runs[0].sample_sizes[1] = 10; + } + + void SetAscending(std::vector* vec) { + vec->resize(10); + for (size_t i = 0; i < vec->size(); i++) + (*vec)[i] = i+1; + } +}; + +TEST_F(TrackRunIteratorTest, NoRunsTest) { + iter_.reset(new TrackRunIterator(&moov_, log_cb_)); + ASSERT_TRUE(iter_->Init(MovieFragment())); + EXPECT_FALSE(iter_->IsRunValid()); + EXPECT_FALSE(iter_->IsSampleValid()); +} + +TEST_F(TrackRunIteratorTest, BasicOperationTest) { + iter_.reset(new TrackRunIterator(&moov_, log_cb_)); + MovieFragment moof = CreateFragment(); + + // Test that runs are sorted correctly, and that properties of the initial + // sample of the first run are correct + ASSERT_TRUE(iter_->Init(moof)); + EXPECT_TRUE(iter_->IsRunValid()); + EXPECT_FALSE(iter_->is_encrypted()); + EXPECT_EQ(iter_->track_id(), 1u); + EXPECT_EQ(iter_->sample_offset(), 100); + EXPECT_EQ(iter_->sample_size(), 1); + EXPECT_EQ(iter_->dts(), TimeDeltaFromRational(0, kAudioScale)); + EXPECT_EQ(iter_->cts(), TimeDeltaFromRational(0, kAudioScale)); + EXPECT_EQ(iter_->duration(), TimeDeltaFromRational(1024, kAudioScale)); + EXPECT_TRUE(iter_->is_keyframe()); + + // Advance to the last sample in the current run, and test its properties + for (int i = 0; i < 9; i++) iter_->AdvanceSample(); + EXPECT_EQ(iter_->track_id(), 1u); + EXPECT_EQ(iter_->sample_offset(), 100 + kSumAscending1); + EXPECT_EQ(iter_->sample_size(), 10); + EXPECT_EQ(iter_->dts(), TimeDeltaFromRational(1024 * 9, kAudioScale)); + EXPECT_EQ(iter_->duration(), TimeDeltaFromRational(1024, kAudioScale)); + EXPECT_TRUE(iter_->is_keyframe()); + + // Test end-of-run + iter_->AdvanceSample(); + EXPECT_FALSE(iter_->IsSampleValid()); + + // Test last sample of next run + iter_->AdvanceRun(); + EXPECT_TRUE(iter_->is_keyframe()); + for (int i = 0; i < 9; i++) iter_->AdvanceSample(); + EXPECT_EQ(iter_->track_id(), 2u); + EXPECT_EQ(iter_->sample_offset(), 200 + kSumAscending1); + EXPECT_EQ(iter_->sample_size(), 10); + int64 base_dts = kSumAscending1 + moof.tracks[1].decode_time.decode_time; + EXPECT_EQ(iter_->dts(), TimeDeltaFromRational(base_dts, kVideoScale)); + EXPECT_EQ(iter_->duration(), TimeDeltaFromRational(10, kVideoScale)); + EXPECT_FALSE(iter_->is_keyframe()); + + // Test final run + iter_->AdvanceRun(); + EXPECT_EQ(iter_->track_id(), 1u); + EXPECT_EQ(iter_->dts(), TimeDeltaFromRational(1024 * 10, kAudioScale)); + iter_->AdvanceSample(); + EXPECT_EQ(moof.tracks[0].runs[1].data_offset + + moof.tracks[0].header.default_sample_size, + iter_->sample_offset()); + iter_->AdvanceRun(); + EXPECT_FALSE(iter_->IsRunValid()); +} + +TEST_F(TrackRunIteratorTest, TrackExtendsDefaultsTest) { + moov_.extends.tracks[0].default_sample_duration = 50; + moov_.extends.tracks[0].default_sample_size = 3; + moov_.extends.tracks[0].default_sample_flags = + kSampleIsDifferenceSampleFlagMask; + iter_.reset(new TrackRunIterator(&moov_, log_cb_)); + MovieFragment moof = CreateFragment(); + moof.tracks[0].header.has_default_sample_flags = false; + moof.tracks[0].header.default_sample_size = 0; + moof.tracks[0].header.default_sample_duration = 0; + moof.tracks[0].runs[0].sample_sizes.clear(); + ASSERT_TRUE(iter_->Init(moof)); + iter_->AdvanceSample(); + EXPECT_FALSE(iter_->is_keyframe()); + EXPECT_EQ(iter_->sample_size(), 3); + EXPECT_EQ(iter_->sample_offset(), moof.tracks[0].runs[0].data_offset + 3); + EXPECT_EQ(iter_->duration(), TimeDeltaFromRational(50, kAudioScale)); + EXPECT_EQ(iter_->dts(), TimeDeltaFromRational(50, kAudioScale)); +} + +TEST_F(TrackRunIteratorTest, FirstSampleFlagTest) { + // Ensure that keyframes are flagged correctly in the face of BMFF boxes which + // explicitly specify the flags for the first sample in a run and rely on + // defaults for all subsequent samples + iter_.reset(new TrackRunIterator(&moov_, log_cb_)); + MovieFragment moof = CreateFragment(); + moof.tracks[1].header.has_default_sample_flags = true; + moof.tracks[1].header.default_sample_flags = + kSampleIsDifferenceSampleFlagMask; + moof.tracks[1].runs[0].sample_flags.resize(1); + ASSERT_TRUE(iter_->Init(moof)); + iter_->AdvanceRun(); + EXPECT_TRUE(iter_->is_keyframe()); + iter_->AdvanceSample(); + EXPECT_FALSE(iter_->is_keyframe()); +} + +TEST_F(TrackRunIteratorTest, ReorderingTest) { + // Test frame reordering and edit list support. The frames have the following + // decode timestamps: + // + // 0ms 40ms 120ms 240ms + // | 0 | 1 - | 2 - - | + // + // ...and these composition timestamps, after edit list adjustment: + // + // 0ms 40ms 160ms 240ms + // | 0 | 2 - - | 1 - | + + // Create an edit list with one entry, with an initial start time of 80ms + // (that is, 2 / kVideoTimescale) and a duration of zero (which is treated as + // infinite according to 14496-12:2012). This will cause the first 80ms of the + // media timeline - which will be empty, due to CTS biasing - to be discarded. + iter_.reset(new TrackRunIterator(&moov_, log_cb_)); + EditListEntry entry; + entry.segment_duration = 0; + entry.media_time = 2; + entry.media_rate_integer = 1; + entry.media_rate_fraction = 0; + moov_.tracks[1].edit.list.edits.push_back(entry); + + // Add CTS offsets. Without bias, the CTS offsets for the first three frames + // would simply be [0, 3, -2]. Since CTS offsets should be non-negative for + // maximum compatibility, these values are biased up to [2, 5, 0], and the + // extra 80ms is removed via the edit list. + MovieFragment moof = CreateFragment(); + std::vector& cts_offsets = + moof.tracks[1].runs[0].sample_composition_time_offsets; + cts_offsets.resize(10); + cts_offsets[0] = 2; + cts_offsets[1] = 5; + cts_offsets[2] = 0; + moof.tracks[1].decode_time.decode_time = 0; + + ASSERT_TRUE(iter_->Init(moof)); + iter_->AdvanceRun(); + EXPECT_EQ(iter_->dts(), TimeDeltaFromRational(0, kVideoScale)); + EXPECT_EQ(iter_->cts(), TimeDeltaFromRational(0, kVideoScale)); + EXPECT_EQ(iter_->duration(), TimeDeltaFromRational(1, kVideoScale)); + iter_->AdvanceSample(); + EXPECT_EQ(iter_->dts(), TimeDeltaFromRational(1, kVideoScale)); + EXPECT_EQ(iter_->cts(), TimeDeltaFromRational(4, kVideoScale)); + EXPECT_EQ(iter_->duration(), TimeDeltaFromRational(2, kVideoScale)); + iter_->AdvanceSample(); + EXPECT_EQ(iter_->dts(), TimeDeltaFromRational(3, kVideoScale)); + EXPECT_EQ(iter_->cts(), TimeDeltaFromRational(1, kVideoScale)); + EXPECT_EQ(iter_->duration(), TimeDeltaFromRational(3, kVideoScale)); +} + +TEST_F(TrackRunIteratorTest, IgnoreUnknownAuxInfoTest) { + iter_.reset(new TrackRunIterator(&moov_, log_cb_)); + MovieFragment moof = CreateFragment(); + moof.tracks[1].auxiliary_offset.offsets.push_back(50); + moof.tracks[1].auxiliary_size.default_sample_info_size = 2; + moof.tracks[1].auxiliary_size.sample_count = 2; + moof.tracks[1].runs[0].sample_count = 2; + ASSERT_TRUE(iter_->Init(moof)); + iter_->AdvanceRun(); + EXPECT_FALSE(iter_->AuxInfoNeedsToBeCached()); +} + +TEST_F(TrackRunIteratorTest, DecryptConfigTest) { + AddEncryption(&moov_.tracks[1]); + iter_.reset(new TrackRunIterator(&moov_, log_cb_)); + + MovieFragment moof = CreateFragment(); + AddAuxInfoHeaders(50, &moof.tracks[1]); + + ASSERT_TRUE(iter_->Init(moof)); + + // The run for track 2 will be first, since its aux info offset is the first + // element in the file. + EXPECT_EQ(iter_->track_id(), 2u); + EXPECT_TRUE(iter_->is_encrypted()); + EXPECT_TRUE(iter_->AuxInfoNeedsToBeCached()); + EXPECT_EQ(static_cast(iter_->aux_info_size()), arraysize(kAuxInfo)); + EXPECT_EQ(iter_->aux_info_offset(), 50); + EXPECT_EQ(iter_->GetMaxClearOffset(), 50); + EXPECT_FALSE(iter_->CacheAuxInfo(NULL, 0)); + EXPECT_FALSE(iter_->CacheAuxInfo(kAuxInfo, 3)); + EXPECT_TRUE(iter_->AuxInfoNeedsToBeCached()); + EXPECT_TRUE(iter_->CacheAuxInfo(kAuxInfo, arraysize(kAuxInfo))); + EXPECT_FALSE(iter_->AuxInfoNeedsToBeCached()); + EXPECT_EQ(iter_->sample_offset(), 200); + EXPECT_EQ(iter_->GetMaxClearOffset(), moof.tracks[0].runs[0].data_offset); + scoped_ptr config = iter_->GetDecryptConfig(); + ASSERT_EQ(arraysize(kKeyId), config->key_id().size()); + EXPECT_TRUE(!memcmp(kKeyId, config->key_id().data(), + config->key_id().size())); + ASSERT_EQ(arraysize(kIv1), config->iv().size()); + EXPECT_TRUE(!memcmp(kIv1, config->iv().data(), config->iv().size())); + EXPECT_TRUE(config->subsamples().empty()); + iter_->AdvanceSample(); + config = iter_->GetDecryptConfig(); + EXPECT_EQ(config->subsamples().size(), 2u); + EXPECT_EQ(config->subsamples()[0].clear_bytes, 1u); + EXPECT_EQ(config->subsamples()[1].cypher_bytes, 4u); +} + +// It is legal for aux info blocks to be shared among multiple formats. +TEST_F(TrackRunIteratorTest, SharedAuxInfoTest) { + AddEncryption(&moov_.tracks[0]); + AddEncryption(&moov_.tracks[1]); + iter_.reset(new TrackRunIterator(&moov_, log_cb_)); + + MovieFragment moof = CreateFragment(); + moof.tracks[0].runs.resize(1); + AddAuxInfoHeaders(50, &moof.tracks[0]); + AddAuxInfoHeaders(50, &moof.tracks[1]); + moof.tracks[0].auxiliary_size.default_sample_info_size = 8; + + ASSERT_TRUE(iter_->Init(moof)); + EXPECT_EQ(iter_->track_id(), 1u); + EXPECT_EQ(iter_->aux_info_offset(), 50); + EXPECT_TRUE(iter_->CacheAuxInfo(kAuxInfo, arraysize(kAuxInfo))); + scoped_ptr config = iter_->GetDecryptConfig(); + ASSERT_EQ(arraysize(kIv1), config->iv().size()); + EXPECT_TRUE(!memcmp(kIv1, config->iv().data(), config->iv().size())); + iter_->AdvanceSample(); + EXPECT_EQ(iter_->GetMaxClearOffset(), 50); + iter_->AdvanceRun(); + EXPECT_EQ(iter_->GetMaxClearOffset(), 50); + EXPECT_EQ(iter_->aux_info_offset(), 50); + EXPECT_TRUE(iter_->CacheAuxInfo(kAuxInfo, arraysize(kAuxInfo))); + EXPECT_EQ(iter_->GetMaxClearOffset(), 200); + ASSERT_EQ(arraysize(kIv1), config->iv().size()); + EXPECT_TRUE(!memcmp(kIv1, config->iv().data(), config->iv().size())); + iter_->AdvanceSample(); + EXPECT_EQ(iter_->GetMaxClearOffset(), 201); +} + +// Sensible files are expected to place auxiliary information for a run +// immediately before the main data for that run. Alternative schemes are +// possible, however, including the somewhat reasonable behavior of placing all +// aux info at the head of the 'mdat' box together, and the completely +// unreasonable behavior demonstrated here: +// byte 50: track 2, run 1 aux info +// byte 100: track 1, run 1 data +// byte 200: track 2, run 1 data +// byte 201: track 1, run 2 aux info (*inside* track 2, run 1 data) +// byte 10000: track 1, run 2 data +// byte 20000: track 1, run 1 aux info +TEST_F(TrackRunIteratorTest, UnexpectedOrderingTest) { + AddEncryption(&moov_.tracks[0]); + AddEncryption(&moov_.tracks[1]); + iter_.reset(new TrackRunIterator(&moov_, log_cb_)); + + MovieFragment moof = CreateFragment(); + AddAuxInfoHeaders(20000, &moof.tracks[0]); + moof.tracks[0].auxiliary_offset.offsets.push_back(201); + moof.tracks[0].auxiliary_size.sample_count += 2; + moof.tracks[0].auxiliary_size.default_sample_info_size = 8; + moof.tracks[0].runs[1].sample_count = 2; + AddAuxInfoHeaders(50, &moof.tracks[1]); + moof.tracks[1].runs[0].sample_sizes[0] = 5; + + ASSERT_TRUE(iter_->Init(moof)); + EXPECT_EQ(iter_->track_id(), 2u); + EXPECT_EQ(iter_->aux_info_offset(), 50); + EXPECT_EQ(iter_->sample_offset(), 200); + EXPECT_TRUE(iter_->CacheAuxInfo(kAuxInfo, arraysize(kAuxInfo))); + EXPECT_EQ(iter_->GetMaxClearOffset(), 100); + iter_->AdvanceRun(); + EXPECT_EQ(iter_->track_id(), 1u); + EXPECT_EQ(iter_->aux_info_offset(), 20000); + EXPECT_EQ(iter_->sample_offset(), 100); + EXPECT_TRUE(iter_->CacheAuxInfo(kAuxInfo, arraysize(kAuxInfo))); + EXPECT_EQ(iter_->GetMaxClearOffset(), 100); + iter_->AdvanceSample(); + EXPECT_EQ(iter_->GetMaxClearOffset(), 101); + iter_->AdvanceRun(); + EXPECT_EQ(iter_->track_id(), 1u); + EXPECT_EQ(iter_->aux_info_offset(), 201); + EXPECT_EQ(iter_->sample_offset(), 10000); + EXPECT_EQ(iter_->GetMaxClearOffset(), 201); + EXPECT_TRUE(iter_->CacheAuxInfo(kAuxInfo, arraysize(kAuxInfo))); + EXPECT_EQ(iter_->GetMaxClearOffset(), 10000); +} + +} // namespace mp4 +} // namespace media diff --git a/media/test/data/48_aac_infinite_loop.m4a b/media/test/data/48_aac_infinite_loop.m4a new file mode 100644 index 0000000000..27a6184a8d Binary files /dev/null and b/media/test/data/48_aac_infinite_loop.m4a differ diff --git a/media/test/data/4ch.wav b/media/test/data/4ch.wav new file mode 100644 index 0000000000..44ffd75e02 Binary files /dev/null and b/media/test/data/4ch.wav differ diff --git a/media/test/data/9ch.ogg b/media/test/data/9ch.ogg new file mode 100644 index 0000000000..97a1d1208d Binary files /dev/null and b/media/test/data/9ch.ogg differ diff --git a/media/test/data/README b/media/test/data/README new file mode 100644 index 0000000000..b28b749f7f --- /dev/null +++ b/media/test/data/README @@ -0,0 +1,58 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +bear-320x240.webm - WebM encode of bear.1280x720.mp4 resized to 320x240. +no_streams.webm - Header, Info, & Tracks element from bear-320x240.webm slightly corrupted so it looks + like there are no tracks. +nonzero-start-time.webm - Has the same headers as bear-320x240.webm but the first cluster of this file + is the second cluster of bear-320x240.webm. This creates the situation where + the media data doesn't start at time 0. +bear-320x240-live.webm - bear-320x240.webm remuxed w/o a duration and using clusters with unknown sizes. + ffmpeg -i bear-320x240.webm -acodec copy -vcodec copy -f webm pipe:1 > bear-320x240-live.webm +vp8-I-frame-160x240 - The first I frame of a 160x240 reencode of bear-320x240.webm. +vp8-I-frame-320x120 - The first I frame of a 320x120 reencode of bear-320x240.webm. +vp8-I-frame-320x240 - The first I frame of bear-320x240.webm. +vp8-I-frame-320x480 - The first I frame of a 320x480 reencode of bear-320x240.webm. +vp8-I-frame-640x240 - The first I frame of a 640x240 reencode of bear-320x240.webm. +vp8-corrupt-I-frame - A copy of vp8-I-frame-320x240 w/ all bytes XORed w/ 0xA5. + +Vorbis test data from bear.ogv (44100 Hz, 16 bits, stereo) +vorbis-extradata - Vorbis extradata section +vorbis-packet-0 - timestamp: 0ms, duration: 0ms +vorbis-packet-1 - timestamp: 0ms, duration: 0ms +vorbis-packet-2 - timestamp: 0ms, duration: 0ms +vorbis-packet-3 - timestamp: 2902ms, duration: 0ms + +// Encrypted Files +bear-1280x720-a_frag-cenc.mp4 - A fragmented MP4 version of the audio track of bear-1280x720.mp4 encrypted (ISO CENC) using key ID [1] and key [2]. +bear-1280x720-a_frag-cenc_clear-all.mp4 - Same as bear-1280x720-a_frag-cenc.mp4 but no fragments are encrypted. +bear-1280x720-v_frag-cenc.mp4 - A fragmented MP4 version of the video track of bear-1280x720.mp4 encrypted (ISO CENC) using key ID [1] and key [2]. +bear-1280x720-v_frag-cenc_clear-all.mp4 - Same as bear-1280x720-v_frag-cenc.mp4 but no fragments are encrypted. +bear-320x240-16x9-aspect-av_enc-av.webm - bear-320x240-16x9-aspect.webm with audio & video encrypted using key ID [1] and key [2] +bear-320x240-av_enc-av.webm - bear-320x240.webm with audio & video encrypted using key ID [1] and key [2]. +bear-320x240-av_enc-av_clear-1s.webm - Same as bear-320x240-av_enc-av.webm but with no frames in the first second encrypted. +bear-320x240-av_enc-av_clear-all.webm - Same as bear-320x240-av_enc-av.webm but with no frames encrypted. +bear-640x360-av_enc-av.webm - bear-640x360.webm with audio & video encrypted using key ID [1] and key [2]. +bear-640x360-a_frag-cenc.mp4 - A fragmented MP4 version of the audio track of bear-640x360.mp4 encrypted (ISO CENC) using key ID [1] and key [2]. +bear-640x360-v_frag-cenc.mp4 - A fragmented MP4 version of the video track of bear-640x360.mp4 encrypted (ISO CENC) using key ID [1] and key [2]. + +[1] 30313233343536373839303132333435 +[2] ebdd62f16814d27b68ef122afce4ae3c + +// Container Tests (additional containers derived from bear.ogv) +bear.ac3 -- created using "avconv -i bear.ogv -f ac3 -b 192k bear.ac3". +bear.adts -- created using "avconv -i bear.ogv -f adts -strict experimental bear.adts". +bear.aiff -- created using "avconv -i bear.ogv -f aiff bear.aiff". +bear.asf -- created using "avconv -i bear.ogv -f asf bear.asf". +bear.avi -- created using "avconv -i bear.ogv -f avi -b 192k bear.avi". +bear.eac3 -- created using "avconv -i bear.ogv -f eac3 bear.eac3". +bear.flac -- created using "avconv -i bear.ogv -f flac bear.flac". +bear.flv -- created using "avconv -i bear.ogv -f flv bear.flv". +bear.h261 -- created using "avconv -i bear.ogv -f h261 -s:0 cif bear.h261". +bear.h263 -- created using "avconv -i bear.ogv -f h263 -s:0 cif bear.h263". +bear.m2ts -- created using "avconv -i bear.ogv -f mpegts bear.m2ts". +bear.mjpeg -- created using "avconv -i bear.ogv -f mjpeg bear.mjpeg". +bear.mpeg -- created using "avconv -i bear.ogv -f mpeg bear.mpeg". +bear.rm -- created using "avconv -i bear.ogv -f rm -b 192k bear.rm". +bear.swf -- created using "avconv -i bear.ogv -f swf -an bear.swf". diff --git a/media/test/data/bali_640x360_P420.yuv b/media/test/data/bali_640x360_P420.yuv new file mode 100644 index 0000000000..40d471b493 --- /dev/null +++ b/media/test/data/bali_640x360_P420.yuv @@ -0,0 +1 @@ +DEHKLLMNNPPPPQSQSRRRSSRRQSQQPONMLJKJGGFCAA>>:;:;;88888899989::988:9767755544211100..--++)'))(())((((''(((('())))**++,,**++,,,,,,,,++++*)))))))))))((''&&&&&&&&&&&&&&&&&&''&&&&&&&&&&'''''''(()+,,-/0224557778899999999::9999::8876677:>EJQYahlquwxxwuqmjhhghjlnnoooopopoqstwy|~~~~|xrldYNA4.,)++++,*+++.4:BOZenx~ƒ†…ƒ‚}wmgXH;/)%$$#$%%'*.178>CLS^emt{€€|zuoiaYSORQTTQSLD=60'%#$$""#"""!!#!"! #%&(+07=DKPW^cglrwxyxwuvvuuuok_WKFA??@@@@@@????==:99975543100/039?CFFJNU\bkptxz{}‚‚‚ƒƒ€€~}{yzvttx{€‚ƒ†ˆ‡‡…ƒ~|zxutty€„†…ƒƒƒ€|xpfXJ=4/.-,038=DNZbksx|€‚€ƒƒ‚‚~}{yvwwzz|„ˆŠ‹‹‹ŠŠŠŠˆ‡‡‡ˆˆˆ†€ypdXOHEA><;FIILLLMNNPPPPQRQTSRRSSRRRRQQPONMLJJHHHEAA@=<;<<:8:::::89999:::98888876665544211100..--++*())(())((((''(((('())))**++,,,,++,,,,,,,,++++*)))))))))))((''&&&&&&&&&&&&&&&&&&''&&&&&&&&&&'''''''(()+,-/01124557888899999999::9999::999878;=DKQY_gosyz|||ztqljkklmooppoomlkjjjkkorwz~€~~~wsk`UH<2+*++++++,,+,17?JVamw}…†…„{tk^P?3*&$$$%&(,/258=AEJRZcjr|€€zundZSOMTbpfVNE=6)#$###""#"""!!#""!!"$&+-4=CJPV\belquyz{zwuvvuuuskaVKEB??@@@@@@????>><:::755431000116<;HHIKLLNOOOPPQQQRQSRRRRRRQPPPNMLKJJHGEDA@@>=<;:8:9899899:9999::999988776655332100//..-,,++*(((((((((((((())))))))**,,,,,,,,--,,,,,,,,,,+*++**))))))((''''''''&&&&(''('%'&''&&&&&&''&&''''(((())*,..013345558888999988999999999:99778;>CGOU^bjqv{}}~{xvpnllklmnoommkigeca^adjmrw{€‚‚‚|vqi^QB6.*+***++++++05=EQ_jr{€…ˆ††‚}xncTE6-&$$%&',/148=>BDHLU^eow€„„ƒzsh`VPN]euvo`fys@$!######""""!"""##$$*.39AGMS[`ejpsx{}|xvvuvvvsoj`TMD@????@@????@>==<;::755421/0..28;BMT\hnrux|~}|{vqicVJ>4.+)))((''(((())*+,-01134589=>@AEJMSZ_fkrv{‚ƒƒƒ…†‡‡ˆ†……„†}|zwtrppquvy}ƒƒ………~~{yzz{†‡‡†‚„}yrj\N>51-,,.037;:IJJKMLNOOOPPQQQRSRRRQQPPPPOOMLKKIIGFDCA@?><;;:9977999:::998:::999988776643332100......-,+*))(())(((((((())))))))**,,,,,,,,--,,,,,,,,,,+*++***)))))((('''''''&&&&(((('&'''&&&&&&&''&&''''(((()))+-/01223555468899998899999999;:9977::?DJRY^flsx|~}|yvsnljjihhiffefd`^ZXUUX\aeluz€‚‚‚~|vmbWI;4,*,******++.28ANXcoy€†ˆ‡‡ƒ~}tj[M=0)$$$%+.168;?BDCFKOXdmu~ƒ††‚}wncWOWl`ilpkdw^I+!$####""""!"""#"%(.3;@FMRY_cgnquy}|{yvvvwwwuph`TKDB????@@????>===<;::755411./..27;BJT\fnruxwz~}|wslcZN?81+())((''))(())*+-.013334679>>DINSY_aimtx}€ƒ„††‡ˆ‰ˆˆˆ……ƒ€~|yvtqoooppsuxƒƒ„„ƒ~~}{||~†††…„‚€{vofXI;3//0..025:BMU^gpw|€~~~~|{xvtqmllnrv{‡‰Š‹‹ŒŒŠŠŠŠŠŠ‹‹Š…€wj]RKGEB?<;HIJJLLMNOOOOQQRRRQRRQQPPPOPOLJJJHGGECA@?>=<;:9::888889::::::::99888865544322210000//----,+)))(((((((((((()))))))****,,,,,,,,,,,,,,,,,,,+**++*())*())))((''''''''''''''''''''&&''''''''((((((***+--.1112455678888999999::9899998769:;@ELSY`fmsvy{yvwspkhedd__^^^^_]ZWTRONOSX_fpz€„ƒ„„}xql\OB8/+*****+++,-28?IWamw€‡ŠŠˆ‡ƒ{vm_RB4*%%%(.249<@CDEDFINU_iqz‚†‡„€yri]U\ijqYIRiqcRWG! !$##"""#"""""&(-4;?EKSW]bdintyz||{xwwwwxwsoj_TJD???>>??????>>==<:997543211///06:@KS\ekrvwy{}{zzwqg\OB81-+))((''((')(**+,-/0232479:?DHNRY`dgmrv{‚…†ˆˆŠŠ‰‰ˆˆ†…ƒ€~}xvtplkjjkmot{‚‚ƒƒ€~}}}~‚…††„ƒ‚€zslbRC82/..../26;BMWaiqwz~€~}}{wusrmiiimqv|„ŠŠŠŠ‹‹‰‰‰‰‹‹‹ŠŠ…vgZNHGEC@>=<;;:::99889:::::::::99888876543322100000////--,+*))((((())(((((())))++**++,,,,,,,,,,--,,,,,,,+++++,*))))))))((''''((((''''''''''''''((''''''(((())**++--.0103333567888999999::9899887778:;@EJPYaiouvvvtqolfc_ZYYXXZZXXYYXUOMJGEHPYcjs{‚ƒƒ€}vmcWI<2.-****+++,,07>>>>>>>>>=<;:9975432311../38?IR\fmptwz{{}|zvqi_RG:2.)))((('((*)*++,,-./13369;@FKPUZ_dhmqvz}‚„…‡‡ˆŠŠŠŠ‰‡…ƒ€zxvtokhgeegjms{‚‚‚€~~~€ƒ†‡†„ƒ„€}xri^O@71.-../226;CMXaiquz}~~~~~}{zywsmmkgggjnqx„‡ŠŠŠŠŠ‰‰ˆˆŠ‹‹‹ˆ…~uh[NIGDC@?@KKKMMMMOOOPPPPRSQQPPPPOOPOMMKKIIHFEDBA?>==<;;;998899::::::::::9988887655321111111100/.--,,++******))**(())******++,,-------,,,--,,--,,,,--++,,**++))))(((((((((((('''((((('&&&''('''((((((()**++,,,-/01233466799888888::88986655678:>BGNW_gmqoomkhgb^XWTSRUUWWXXVVUPKFB>1++/479<>BCEFFGGILPX`jr{„…ƒ|ulb^`S[NUTEOSJFDcjdZ%!$$$$$$#$&)038?ELQV[^`eimqstwzzzywvvwxxuqk`RJC?<<==>>>>>>==;;<:7786432210./.37>HQ[clqtvxz}~|zxtkaRG=40,)((((())+++,,,-../146:?EIORW\afinsux~‚…‡‡‡‡ˆ‹‹Š‰ˆ…‚}{wspnkihdbcgint{€‚‚€€€~€€‚…ˆˆ†…„ƒ€}wqeYK=61-..-.148=FOX`iqv|}}}|}|}ywwsnkjgfccejqw~„‡ŠŠŠ‰‰‰‰ˆˆ‹‹Œ‹‰ƒ}teZRJHGDB@?JILLMLPNOOPPPPOPQQPPPPOOOMMMKJJHFDCB@?>====<;;::8899::::::::::9988887655321111111100/.--,,++******))**))))******++---------,----..---,,,--++,,++++))))(((((((((((('''((((('&%&''('''((((((()**++,,,-/1112335568888888899888755455669=AEKS^ehhffedb]ZURSRQRRRVVWWVSPMGA=879AJWaks}€‚…‚‚€|voeYG:2.,*+++*+++,17?KXdq|ƒˆŠˆ‡†‚|umbRA4//269=?BDGFGGHHHKPV[emu~‚„‚|xnc`UNG:C@59:>@CKT]S##$%%$$$&&*.5;?GLQXZ]`cfgkoqsuyyzyvuvuvvuph^QHA=<<<<======<<;:98777543220//..28=FRZckqvuw{}}||{tlbWK<41-)(*****)+++,,,.//258=BEKPTY^chmpsv|~‚„‡‡†‡ˆˆŠŠŠ‰†ƒ€}yuqoljgda_`cgjnty}€€€€€€ƒ‚‚…ˆ‰ˆ†…„€}vncWJ<73.//./158>FO[cjpw{}~}||{zxurojgfcb``dipv~„‡ŠŠŠ‰‰‰‰ˆŠˆ‹Œ‹ˆ…~tgYQKHGDBA@KKNNMMOOOOPPPQQQQOPPPPOOLLMLMJHFECBA???=>=<<<<;;;;::::::;;;;::::8877744432221122220000..-,,,**))******(*)))***++**,,,-----..........----,,,+++++++)))))())((((''))((((((((((((((''((((((((()**++,,+,--021122445677887777776655443568:>CJRY_bdb``^\ZWTRPPQSTTUUVVSPMGC;5124;EP\hqy~ƒ‚ƒ€€}wpj^PA6/+****(**++/7DHNSX^cikosu|}ƒ„„†††ˆ‰‰‰‰‰†„|wtpjhfca^]_cgimou{€~€„ƒ…‡‰Šˆ††ƒƒ€|ulaTF;740/0.026=BHR\dipx|}}~}|{wuqolgd_\\\\aiov}…‰‰Š‰ˆˆ‡‡ˆŠ‹‹Œ‰„|sg\QJIFECA@LLNNMMOOOOPPPQQQRQPPPPOMLLKLJHHFDB@@??>?>=<<<<;;;;::::::;;;;::::8877744432221122110000....,,**********(**))***++**,,,,----............--,,,+++++++***)))))(((())))((((((((((((((''((((((((()**++,,+,--..00112445778877776655443333469=BINUWVWZ[[[ZZVUSSSSTUUWXVUROIC=60--27@KVcmv}‚€ƒ€zsmcVI;2-***+*)*++.29DQ^gt}ƒ‡ˆ‡†ƒ|sk_QC97:=?CEEGGGGIIIIIIINW`jsyvhkpd]][MEBWY(+4<;7=DK'%%%%&(*05:@GLRW\]`bddgeefilpsvvvssutuurmf\PG@<999:;;:;;;;;:9::765342220//./27FLU]emsw|}|}|zwurmjeb_[YYXZ`jmv~…ˆ‰Š‰ˆˆ‡‡‡‰Œ‹ŒŒˆ‚|sg]RLJFEC@>MMNNNNOOOOPPPQQQQQQQOONNMMKHIGDBA@@@????>=<;<<<<<<;;;;::::;;::98997765432233221110.011////-,,,,,++******++++****++++,,----//-.//.-....---,-,++++****)((())))(((()))((((((((((((((((((()))))))***,,++,,--///33454556666664455443211257;?EKOUVUY[[\[ZWWUVVVVWWXWVSOKF=71,*+.3;FP]gqxƒ€~{wpi]PA3.**)**))**,29@KWdny€‡ˆ‡†„|tmeWJB?>@CFGGIIJJJJHHFFEGMWcmw|}skei`ed`VA98541147=DJSYafnuz}~||{{xspjga_ZXSRSV^hmw€†‰Š‰ˆˆ††‡‡‰Š‹‹‰†‚}sh\RMHGDCA@MMNNPPOOOOPPQRQQQQNNMMMMLLJHGEBA@???@@>=>=;<<<<<:9;;;;::::99::98777765432233221110.0111///-,,,++**++****++++****++++,,--..---.//.-....-----,++++++**))))))))(((()))((((((((((((())(((()))))))*****++,,--/0100143556666664444332100146:>DINQUVZ[[\\\YYXXXXWWZXWTRMGA94.*)+-07@KWalu}‚~}ytmdUG:0+*********07=ES`jw~ƒ††…„|woh`TJEBDEGHHIIJJKKHGBBBBEP[gttoiflgegeXJC@G9123,9DHLQUX\^^]^][YYYYXWYXYTSOIB=6.+(()*.4;FR^hqy€~}{vph\N@4-*)**)****.6:BP\hq|‚………ƒ}{undZQKFFFHHIIIJJJIGD><;=@JVctihigcfd`^B3LSX<@RSD<:CHXfU<$,08=BEKQW[]dfeee`\\WUVY]djqtvtsstrplibWJ@;6666788887777666654331//10//-/14:CMV_gotwwy{{{{|yslaVJ=740/////./025:@EKQW\`ekmrty{~~~~~€€‚ƒƒ„ƒ„„ƒ„ƒ‚€zvqh_YVVY\_cfikmnoqtw{{{{{}‚„ˆŠ‹ŒŒ‹‰††‡‡ƒ{tj^RIA<;:888;>ELSY`fmtz|~}}|{wtolc]VSOMIHLQZemx‚‡ŠŠ‰ˆ††‡†ˆ‰‰‰ˆ‡„~yqj_TNHECA?>MMOOPPPOOOOOPPOONNNMLKKJIGFDBA@?@@????@@?>=<====<<;;:::::::99:877766654444333223222211000/.-,,--,,++++,,,+****++++,,--.........///....-,---,,,++++*+**))))))))(((())))))(((((((((((((()))')))***++++++,,..-./0101222333322321110./2569?EJNTXZ\__``^\[[[[YXY[XVQLE=6/*))))),07ALXbnu}€€~~}ysmcUI90*)))()****07>IWdnw~ƒ„„ƒ|wqkaXPIGGHHIJJJJKJE@<969;ES^pookie`]YSMIJLS``__XKA8+;Rnn5(7=;;>@CJQX]ejqv{~~}}}{vrmf`YRMJECCHQ\eq|„ˆ‡ˆ‰ˆ†††‡ˆ‰‰‰†…‚~wsi_TMGDB@@>NNOOOONNOOONPPQPNNLLJJIHGFCA@@@@?>?????>??>===<<<<<<::;;::;;:9887776565233443344332221110//--,..--,,,,,+******++++,,--........00//....,---,,,,,,*,+*))))))))))))))(())(((((((((())(((())**))))*)****+++++,,-/////0111122122110/.01149<@GLRVZ^^_`aa`^]]][YY[ZXSNGB:3+()))))*.3;FS^itz~{wog[N>3*)))******/5ILcmhcmwJOKTZ[YWJIKNV`iquwuwtssqkh^SF<643344554444555553221200/.,.././4:CLU^gnswwzxyz{||todZQC;663222327:@FMUY`ekotwz|€€~~}}~€€‚‚‚€€}}{xtphd[YZZ]afilooqrrtvy|}|||{ƒ…‰Š‹‰‡‡††ƒ{rj\PIDBAA?AAFIQV]bhouy}~~}{xtpjc[SLFC?=?DO[gs~„ˆˆˆ‰‡……†‡ˆ‰ˆ‡„‚}yskaUMEBA?>>NNOOOONNOOOOPPONLLKKJJHGDB@@@>>>>>?????>???===<<<<<<::;;::9988887776565565334455332221110///.-....--,,,+++****++++,,--........00//...-,---,,,,,,,,,*))))))))))))))(())(((((((((())(((())**)))))())))++++*+,---.//011111101100//.0126:=CGOTY\^`abcca___\[[ZZZUNKC=5.*'(((((),07AMXdnv}€}~}yslaTE7-*))))****-39BM[fq{€‚„‚€}zupi`WPMJJIKKKJIFA=84/-,1;QirsmM]aWUONPMGBNVb`XUX]T=9BT^qZLO[m~w€jh[Ybmpwtd\P@AJT_ksuwvvtssqkh^SF<6422332222333333432210/-..,--,,-38@HS\hoswxxyyz{{{tni]SG>8765556:?DJOW^diotxz€‚€~}||}}~~}}~zywsqngb\YY]acglmprrrsux{}}zz{|ƒˆŠŽŒ‡‡‡††ƒ{rj\RNHFFFEGIKOW^cflrx|~~~}{{ytmf^XOGA=9:>>OONNOOOOPPOPNMNLKJIHFFFDBB@@??>>==>>??@@>=>>====<<<<;:<<;;;;888777665555555554554432222210000./0.--,,-,,,,,,+***++,,------//0001....-,--..---,,,,,++****))(((((((((())**))(())(())(((())))))))))**++++*))*---....//0111100///0./0137=AFLPVZ^`bdeddabaa_\[ZYYTOF?9/*('('&&''*.5:41+**.9Pq~xe`\GYYUQRNLMHQVYLL\_KGA*FL]TO[nto€smhmlepz‚qj\F=KUalswwxxvtsqng]PD:310011112211100111/0/----,-,+,,028>GR\enrvwwy{{{zzwrk`VH@<96689;BGLS[ahmru}‚ƒƒƒ~|}}|}~}€~}}zzywurpmgb]\]_beinoqrrstvy||}zz{}ƒˆ‹Ž‘Šˆˆˆ††ƒ|rh]TPMKLLKLNRX^beknsx}~|}||ywrle]QH@:777>GSbny‡‰Šˆ‡†††††‰ˆ…„ƒ{wslbVLFA?>:9OONNOOOOOOMNMLKJJIIHFCCA??====>>>>>>??@@>=>>==>>==<<<;<<;;;;988766665555555565554444442211100/0/..--.,-,,,,,+***++,,-----.//0000....----...---,,,,++****))))(((((((())))))(())(())(((())))))))))**((((**)*,,-....//00000////--/01459?CINTZ^`cefffeda``_[ZZZWQLB<3-*('('&&&')-18CMYcmw|~€}||xql_SD6-**(())***.49DR_jr{~€‚€~{yupjaXQMLLLLKGC>:5/+('+/5Ts||{o_VHFVVQU[RKT\[jRIZdeZGBFKOJXf_lmqo€{lgl_[pfgKU@HWdnswwxxxwvsng]PD:20//000000//000000/..-,,,,++*+-.17>GQ[entxxxxyz|zzwqkbXNE?=98<@DHOW^eipty~‚ƒ…‚‚€€|{{z{}|~~~€~|zyxvvutsrpnhc__`adgjnpqrsvvxz}|{zz{}ƒˆ‹ŽŽ‰‡ˆˆˆ†ƒ{sj_XTSPPPQRVZ\bfjoqvz~~}|||yvnh`YND<8447>IVdpz‚‡ˆˆˆ‡…††ˆ‰‰‡„„‚}yxqlbVLE@>=:9ONONOONNMLKMLKJHIHEDCB@?;;<<:<<<==>>>>?@>>>>====??==<<<<<;;::877777766667754576655554443211100100/.-,-----,,,+++++++,,--.//////....---.....-,,-,++********(*((''(((())))(((('((())))))))(((((())))))**)))))*++,,.///0/00..//.../166=AGLRX]`cefgijgfca_[ZZXWVPF=8/*('(('&&&''+.5=IT^iqy|~{||}xpe[J;0+)(())**)+28BLXdow}|{{zvsne[TNLJLJGD@;50)($"'+6Ybtwxo`SLJZ[TUa_STZ\^]ST^ldTJM^NQ`ikurfko[IGTV]RXSQRMKVdntwwwvwwvskh\PC92.----.-..////.....-..,,,,,,*+-/17>GPZenquxxxxy|{{xsng\OGB?<=BHMRWaglrw|€„ƒƒ‚ƒ€}}zzyyyz{{{{|{zwrsssrsrpnifb``deiloqrsuvwz|}|{zz{}€„‰ŒŽŠˆ‰‰‡†ƒ{slb\XXWUUVX[^agloruy|~~}|{{ytnf^SJ@83137@LXgs}„‡‰ˆˆ‡†…†ˆˆˆ‡ƒƒ}|xurkbVLC?<:99ONNNMMMMLKKKJIGFFEECA>>=;;:::;<<==>>>>?@>>>>====>>==<<<<<<;::99977776666777788665555444322211110//..------,,,+++++++,,--.../////...---.....-,,-,++********))(((((((())))((((''(())))))))((((((**))))**)))))*++,,+-/./...--//./.137;BGLRV]_cfgikkjgfca_][[YWRIC;4-(''(&'('&'((,19BNXcmvz|~{||{wrj`PB5,)(())**)*/5GPZdmquxxxxwzzzwtoh^RJE@CFKPW\ahmtx}„…ƒ‚‚€~~}}zzxxwyzzzzzxwusqqqqrrpokfbbcdfiloqrsvvwz}}|{zz{}…‰Œ‹‰‡‡‡†„‚{slb^ZYXXXZ[]`gkprsx}~~~}|{{ytle[QE;52239BP_jv†ˆ‰ˆˆ‡††‡ˆˆˆ‡ƒ‚{xxvslcVLC?;999OOONMMNLKKJIHGFFDCB@=<<;77998:<<>=>>>>??>>??>>>>==>><<>>=;;;9887777766665678666646555554322102220/.//,-,------++,,++----../0....///.....-/-,--,+****))))**))))**(())))))))(*))(())))))))(((((())))))++++++**+++,----....../000227;?FLQU\`acgjjmmigfca^[[ZWSOG=70+((('&&'&&())*/4FPZclqwwxxxxz{{xvpi`VNGFHNRY]dipuzƒ†…„€€€€}}{yvvuttuuwvvvwurqqpqqrsqokhdccdgiloqrsvvxz|}}{zz{~†Œ‘‹‰‰ˆŠˆ‡†}vkda^\[\\_abeinsww{}}}}|{{zwrleYNB94224;:::978999;;;;=>>????>>==>>>>==>>====;<;;:9887777666666778966675555543331121100010---------,,--,,----....-...///.......-,,,,+****))(())**))**))))))))))))))(())))))))(((((())))))++++,,**++++++--....../00237;@EKQVZadeikkllijifc`^[YXVPI@91-*)(('&&'&'&&()-27ANWbnuz{|{{}{wqi^N?4+*())))))*05=JXalt{|}~}{{|zuof_UNJFC=72,'#!$""#>`^RV_^\URRUZ\]T`\LKZ_^gSJ[c`\VFWKRg_dcgeeedQ4:HECB=/9CP[hqwxxxxxxvtnj_PA70****)*+**++,***))))))))(()**+,.27=EQ[cjruwxxxxx{{yupjbXQMLRV\`flsw}ƒ„ƒƒ‚€€|zywtrpoprtsrrtvutppopqrrrmjebbcdgiloqrswwxz}|{{zz|}†ŒŽ‹‰‰ˆ‡ˆ‰‡ƒ}vkdb_][^^acfjorvy|}€}{{{{yvpkaUJ=73128>KXds}„‡ŠŠˆ‡†…†‡ˆˆ‡…~zyxwvtneXLD=:89:OOMMMMKIHGGGEDDA>=;:9887678:;;;;;<====>=>>>>>>>><>=====<<<;;::9877776677777777888876545543332211011//..-----,,------------...-./../.----..-,++,+**))(())(&(&)(**))))))))))))))(())(((())(((((((()))***++++++++++++----....0022894+)$!!#! /eg[FLd]VSQUURDN_iRBNXTW^VY[[XUUSPBHW]h^fcfk^<2EC=DE71:CQ\hqvxxxxvvvtoi^O@6.)''''())))(*)***)((((%%'()**+--16>ENWbkqtvxxxyz{{xvrnc[VSUX^dhms{~„ƒƒ€~~}ywwurnmllmklmpstrqonoqrrqrliec`befhlnoruwwxz}{{zzy{}‚‡ŒŽ‹‰ˆ‰‰ˆŠˆƒ}vleb_^^_`cfhlotvy|€}}yzzyvtmg^OD;6433:@O]iv€‡‰‹‰ˆ‡†‡‡‡ˆˆ…ƒ{wywvvtogYMC=989>OOMMLKIHGFEECBB?;:998787678:;;;;;<=====>>>>>>>====<<==>>==<<:;:9888877777777778866787655554333331110//..--++,,------------....//../.----..-,++,+**))(())()**)&))))))*)))))))))(())(((())(((((((()))***++++++++++++,,--.../0158EQWbkqtvxwwwy{{{wunia]\]`glpsw}‚ƒƒ„€~}|zyutpokjiihjkmpstrqonoqrrrpkhca`bbdgjlmotwwy{|{zzzy}~ƒˆŒŽ‹‰ˆˆ‰ŠŠˆƒ}vledbaaabehkosvz{}~~}|yzzyvsleYME;6435>>>>>==>>==>><<====;;:9999788889999886788766665443333332100..--,,,,,,,,------........///....--.-,,,,++))))))))*/28-')))))**))))))))(((())))(((((((((()))*+*+,,*++,,++++,,,-//00159>DIKQW[_ceikmoppprmhea_\ZXWSLC<5/)('''''''&''&&(''*/56/,&# !! !#Ohmhcab`_SPSX\ZdTEC=DLP[`VPV?BEBEFHPQPOZW\_TIOU\O,.49CP\hquvvuuuuvtnh]N@4,('&((%%''&&))(()&%%%%&&&'(*,.1148?FQWaiosvxuuwxyzzyvsmgdcdhlrv{ƒƒ‚€€€}}{zxuuqomkgdcfhjmorttrqmopqrqpmkgeaabbcfiimoquyyzzzzzyy|ƒˆŒŽŠˆ‡‡‰ŠŠ‰…}vkffdccdfjkmouwy{}}}}|z{{zwqkcXMA95237?IVdo{…‹‹‹‰‡‡†‡‰‰ˆ‡…€|xuuuwvtph[OB<7:=EMLKJJHGFECCB@=9966655444666678:::9:<<<<<==>>>>>>>>>>>>>>=<====;;::999888889999998777776665544333332100/---,,--++,,,,,,--........///....----,,,,+)))))))))*6CC4'())))**))))))))))(())((''(((((((())*++++,,*++,,++++,,,-//0258>CGMRV[`dfikmoqqppokfc`][XUTOF?81-*('''''''&''&&''')-28CR\fpuy||{||yuodVF6,((('''''),3;CQ]juy|zum_QF<3-*%"!!! !5Wmy\^ba^YXJKPZb[]YMBHFGKYWPPYC4:KORJHHFJLPPPLN\hVO)-5:DO\grvttuutvurmi]N@4+'&%%%%%&&&&()(('&%%&&&&()*,.03459?FQXajosvxuuwxy{{zxvrmjiiorw|€ƒ„‚‚~}}zxwuspplieaabfhknqrttqpnopqqpomiebaabccefhjnruyxz{{{zyy|ƒˆŽŽŠ‰‰‡ˆ‰‰‡ƒ}vkffddddfilnsuwy{{{zzyyz{yvpjbWI>85348@M\iu‡‹‹‹‰‡‡†‡‰‰‰ˆ„yvtttuvtog\QE<9=CJONLIGFFEDA?=<9666654434665557788::;<<<==>>====>>??>>>>??>>==<<<;::::::::99::::998899757776555555332200..--++++*+++++,,--------../..////.---,,,,+**))))**))2:9-)((((((((())))))(((()'(((((((())((+++,,+,-,,+,,,,,,,,.,..0237TJXdUA=O@LTSPNQ:;HMKHHNJUY\^behhaJBKFDBMNLYfVW+,2:EQ]gqssrrrstusni^OA4,(%%$$$$$$&&&'(('&%%'''()+-./1247;@FOV`hosuwuwxx{}}|yxvsqqrwy~„ƒƒ€€‚€~|{{xwuqpnkgc_acfhjlpsstsqpoooopnmkgca_``_acefhlqtx{{{|zwxy}€„‰ŽŽŠˆˆ‰ŠŠ‰ˆ…|tlgfeeeefjknqrtyz{{zyyyxxxsmg`UG=9767;DR`lx†‹‹‹ˆ‡‡ˆ‰ŠŠ‰ˆƒxussttttqg]QE=>BGNLKJGGFDCB@>;97553333323455667988::9:;;==>>==>>??????@>??>>==<<<<;:;;<;::::::::998876778876666655332210..--++++*+++,,,,--------../..////.---,,,++**))))))))**++)((((((((()))))))(&)()))(((((())))+++,,+,-,,+,----,,,././047:@EIPV[`dgilnqrrropnlhda][YVRLE<6/+*))('&&''&''''+5=ICEB^NCP[\J(+3CIOXaiosvxxwvxy{}~}{zwwwy}€ƒ„„ƒƒ€~|{zxusoolgb_^_acgjlorssutqpoooopnokeba_^]_``ddglqtwxy{|zwxy~†ŠŽŽŽŒ‹ˆˆˆˆˆŠŠˆ‚{rlgeeeefgghmpqsvxzzyxyyxxvslf_QG=:99:=FTbmyƒŠ‹‹‹ˆˆ‡ˆ‰ŠŠ‰ˆƒxussttttqg]QD>AHLPLLIGFECA@><965432212223344557888999:;;<=>>>>??????@@?????>?>====<:;;<<:::::9::99998677777568774433211/---,,+++,,,,----------.......////.--,,,+***)))((((((+5B<,'''(((((((((((()((((((((((((())++++++,++,,,,,------./..0159>DHNRY_behklopssoommkgb_\YXSOI@81,*))*''('''''''&+4-'''+.7COZhpxz|zz||~yqfWE7,('&'&'(,/49BKUaksz}€„}vl_O;-$$###$#%>>>??????@@????@@?>====;<<;;;;::::9::99997679887877775432210.---+++++++++,,,,------......./---,--,,++**))))(((())'61**'((((((((((((((()(((((((((()))*+++++++++,,,,,----..////137:AFMSX\`dijmnprqqpnllhd`][XWQKC;5.+)(()))'&''''&&'%$%'('*.5?LVcowz|{zz||ztl\K6;58AQbioqhP=F>46CGFPNMNVY[^`abb`]XTR5EGGBNaMJU]K.16=FR]iqsttssttttlh]OA5-)%%%%$$%%%$%%%%%%%&'*+.002457:<<;;?FQ^iv€ˆ‹‹Š‰ˆˆˆ‰Š‹‰Š‡€zwtsssttsqiaUKFJPVZKIGEEC@;9865422100////11333356897889;;=>=>????@@@@A@@@AA@@?>=====<<<:;<:;<;:::99:97788888877775533210.--,,++++****,,,,,,----....,./.---,--,,****))))((((**'&(((())((((''''(((((()'((')))******+++++++++,,,----....000036:>DJLT[_aeijmonooonkjjfb^\[XTOG?70+*)))*2/((''''''&&&%$&#(-1;HUamuy||zz|{zun`P?2*'%&()*/39=AIS]fov}‚‚ƒƒ‚{tiZF6,&$##%(([kX<6BACDKZTPLL9117>HR^jrtttusttsrli_QB5-)%'''%%&&%%%%&&%&''(,..01356:<>@CHMR\bhosvxyxxy{~„‡ˆˆˆ‰‰‰ˆ‡……„ƒ„€}|yvtpliidb`aabcefilnqstutrqooooponlieb_][[Z[[]]^bglrvz{|}zwz}†‰‹ŽŽŒ‰‡‡‡ˆˆˆ…‚~vnhdcb_`\\`ehknsuuuuuusuuusoj`YOD?>>?EKVbmvˆŒŠ‰‰‰‡‡‰ŠŠ‹ˆ„yvsrrttvurleVOJMT[_HHFCA?<98655222100////01213356777889;;=>=>????@@@@A@@@AAAA?>>>====<<<;;;<<;:::99999999888877775544320.--,,+++++**+,,,,,,----....,./.--.---,,****))))(((((())''((((''(('''''((((()'((')))******+++++++++,,,--..//..000136:AFLSW\`cgikmnlljjiihhc_\[[VSLC;4-**)))**)((''''''%&&&$&%(+.7CQ]hry|}{{||{vofWD3)'%&(*.26:>CJR\elu{€‚ƒƒ|ypaO>0'###$%)DKF8??=>JZdoswr[TNA>ABMKEPTYZ[^``a_\XTMGB:8MB@GUZZVE,247>JT^jrtttussssrli^PB7/+('''%%&&&&%%&&&'(*++.03457:;>@BEKQT\bhosvxwwvw}€„‡ŠŒ‹‰‡†…„ƒ‚€|{vsokhfb_`bbaceehklpqstutrqoonnomnlieb^\ZYYYXYZ\`glrvz{}{xwz}†‰‹ŽŒŠ‰‡‡‡ˆˆˆ…€zslgdb`^^\\`eiloqttssssstvsrnj`XNGC@?BIO[dpzƒ‰‹Š‰‰‰‰‰Š‹‹‹ˆ„yurrsttuusmd[SOQY`dHEDB@=984553101100..../11123566699::;;==>>????@@AAAA?A@@A@???>==<<======<<;:::::::999977997766555310..-,++++++**++++++---.--+--/--,,--..,,++**))))((('''((((''''''''''''''''''''(((((())**+++,,,,,,,--,----.0300../00047799FFPRW[]^_a`^[WRLF@852TC;GPYX\@0269@KVaksuuttstttrlh_QA60,)('''%''&&&$&&'(*,-.0357:;==BDFHOSX^djosvwyyy{„†Š‘‘ŽŒ‰ˆ‡…‚~}ytqnjhc_^`_aeefghilkoqrtttsqoonmnmlieb^[[YXYXWWY\`flqvy|}{{{z~†ˆ‹ŽŒŠˆ†‡‡‰‰…‚}wqjfc_^^\]^afinprtsssrrstttrog`VNHECCFKU_gr{…‹‹‹Š‰‰‰‰Š‹‰‹‰…~zvtstuuutrke_WVX^diGEC@=9864332200000..../11123566699::;;==>>????@@AAAAABBBA@???>==<<======<<;;::::9999::77887766553210..-,++++++**++++++,,-.---./---------,,++**))))((('''''((''''''''''''''''''''(((((())**,++,,,,,,,--,---../0..../11469?DKPTX[_cghiihffeedca`^^\[XSNE=4.*)))))((((('&''&&&&&&&&&'+.2@EKOWahpx~‚‚‚ƒƒ~wn_N=/'##$%&BuwD&)3:HTcpvxunK;./27.;VYZ]^`aa][VRLFA93.-7?EQOOL_M955:AJUbluwvuuuututlh_QA60,)(''''''''&'&'(*,-.03579<=?ADFHJQUZ`djosuvxz{€…ˆŒŽ’““’‘‰ˆ‡„€~~yvrliea`]^`_accfghillopqsusqpppnmnmkgea]ZYWVWTSTTY\djqw{||zyyz~†ˆ‹ŽŒŠˆ†‡ˆˆˆ…€zupfc`^]\]]^afjlprsrppooprrrpme]TMJGFFJNXblv€ˆ‹‹Š‰‰‰‰‹Š‹ŠŠˆ„}yvtstuvuusnja[Y\`fkFCA=:86743111./0//.....011344568::99;;==>>????@BAA@@BBAA@@????>>========<<;;;;99:998998877666644111/---,++**+++++++++++++-----,.--------,*)**)*)))((('((((''&&&&''''&&&&&&&&&&&&''(('(**+*++,,,,,,-----.--..--...//0368;@HNSTY\_behecbaabb``__`^^[WRKA:2,*****))((((''''''&&&&&&%%).3:DR`lswzzzyy{ztmcSC4+**-257>???@@BAABBBBAA@@??????========<<;;;;;;:998998877666644111/---,++**+++++++++++++,----.---------,*)**)*)))((('((((''&&&&''''&&&&&&&&&&&&''(('(**+*++,,,,,,-----..../..///001269>DINSW[^^bedb`_][\\]]_```][VOF>6.******)))(((''''''&&&&&&%%(+28BO\iqvy{zxxxzvogXF8/--/38;>@DGJMT\dlv}€‚ƒ}vl]J8+&#"" 3L="&/48DRantwxZ=$21?MUZ[[_`a`]YVQLE?80,-/20.?MQRTMII65:AMZcmuwwwvuwwtspi`RB91-**))(((((())),,-013468:;?ACEJMORW[_cglquvwy{~ƒŠŽ“–—˜—–“’Š‡…}ytnjgc`\[\^``acfffgjklnppsvusqqooolljfd`][WVUSRQPQRS[aiouy|ywxxz~‚‡‰ŒŒŠ‰‰ˆ‰‡†ƒ}vpga_^]\ZZ\_dhjmopppnnnorsttqkf^VPNLKMRXbkt}„ˆ‹‹‰ˆ‰‰‰Š‰ˆ‡‡…}xvtuuvvwwurngaabgmr@=;:8865432200..--..../111225677::::<<==<>??AAAAAAAABB@@@@??????>>>>====<;;;;;99:9988788666655554110-,-,****++++++,,++,,,,,,--,,,,,,,,,,,,))()))((((''&'((((&&&&&&&&%%&&&&&&''''''((()**++++,,,,,,----.///00..////2048;AGKOUW\]_bba^\\[[Z[]]`aa`^YTLD;3,******)))('&''''((''''&&&&'*/5?NXeovxy{zwxwtoi[M>411269>@AEGJMSZbhsz€‚‚ƒƒ„„xreS?0(#$.+10&$&226CQ`mty|iIJ[[YXXY\_`__]YUNJD=70--,.--.^SRONORI47???AAAABBAABBA@@@??????>>>>====;;;;;;:::9987888877755435322/.-,****++++++,,++--,,++,,,,,,,,,,++++)))+**((((''&&'''''&&&&&&&%%%%%%%%&&&&''(((*****+,--,,,,----.///00//00/0336:?DIMSVZ\]_aa^\ZZYYXZ]_bbb`]XRI?61,******))*))'((('((''''&&&&'*-3;HT_msxyyxwwwurjaQD7337:@DGJKNRUW[_cgknstvx{~„‰”™™š—–”“‘ŒŠˆ†}uoid^[YYZZ[^__acdefgiknoqqttrqqpoonnjhfb]XWTSRONNMLNQWahpwzzzzz{{‚‡‹‰‰‰‰‰ˆ…€ztlb^[ZYYZZ]afjjmqpqonnmoqrssold]XSPPSUZaks{‚‰ŠŠŠ‰‰‰‰‰ˆ†…„ƒ€|yvuuuvvwwwvqjhgikos;:876644322000.-....../12233566699:;<<==>>?@BBBBBBBBBB@@???>>>??==>>==<<;:;:::9999888989::99:8997975520+*)**+-+-,,,-..-,,,,)++,+,,,,,,,,+*)**))(((((''&&&&&&&&%%%%%%%%%%%%%%&&&&(((())*****+,,,,,,--...///////001235:>CGKOUXZ[_`_^]ZYYYYZ^accdb`\VPE<6/+******))**))))('((''&&'''''(+09CP]iryzywwvwxvmeWH=679;>ACCDFJMMS^flu|€€€€‚ƒ€zo^M<0'&#"$%$$$*/5AQ^mv{||{vnhc]Z^_`a]YUOID=6-+*+-....DP[fouwuvwwvvwuoh`QE:3.-,++,,,,-,+,-.123569;>@DGIKNQTW[^beimnrsvx{„‰‘—šš˜—“‘’‹‡…‚ytmd_ZXWWWYZZ]^_adefgjmopprsssqpoponmhhea[VTRQPNMLIKMOV_hptyxxwxxz„ˆŽŒŒŠŠ‰ˆˆ„€yria]ZXXXYZ]afimproonnonquvtsojc]XTRSUY]gmu~…‰Š‰‰‰‰‰‰‰†„ƒ‚~~|yxuwuvvwwwtroiginqu:9876654321111.-....../12233566699:;<<==>>?@BBBBBBBBBB@@???>>>??==<<===;:;;:::9999889:=>@@??@ABBBA>><83.+***./011121110/...--+,+,,,,,,,,+*))*))(((((''&&&&&&&&%%%%%%%%%%%%%%&&&&(((())*****+,,,,,---...///////001358LXgpuxxwvuvwtqhZPB::;=?ADCEFJJLPZbjrz~€€€€‚‚zqgVC4(&##$25!#)-5AN^luz}{xplf`^_`_]YRNIB:5/,+*+,--..2QbSSSObgB7ADHJMPSVY\_cdhmqstww{…Š–——•”‘ŽŠ‡†ƒ€|wmg`[WUUVWYZZ\]_adeggkmopprsssrpoponmigc^YURPNMLKKIGIMT]fmsvyxwwz|€„ˆŒŽŽŒŠŠŠˆˆ„xof^ZYVWWXY]ahjmmqponoopquvtsojb\ZXVUW]biqy†Š‰‰‰‰‰‰‰‰†ƒ‚~}}|yxusuvvwwwtrojhioqu98766544321110.-.....//123345677:::;==>>>>@ABBCCCCBBBAA@@@?>??????<===<<:99:::9998889;@DFHHHHIIKJJHIFA:3.,-/145679:::8854110/-+*---,,,,+***)((((((((&&&&&&%%&&%%%%%%$$%%%%%%%%&&((''))*****+,,,,--..-.....//0011368:>EKOTWX[[\^__\[ZXWYX\`cdefc^[TKA92,*+++++++++*))))))))''((((((&&),2:GUbmtvxxuuuxvskbSH=;=?ACDEEFFJMPW]fnw~‚€€€‚ƒ~wm[I7+##!#*-##'.5@O\itz}~{yumfc`_][WQLIB92-+,++,,-//115FWQKNWV<8=GT^hrxwxuwwwwvsnh_PF:42...-,,,,++-/12335::<>@CFILORTV[^acgkprvwxy|€„‰”–•”’Œ‹ˆ„‚|yrkdZTUUTTUVXZZ[^_ceghlmnooqssrqpoqpqmjea[XTQPMKHGEDEFKQZflswxxywy|€ƒ‡‹ŒŽŒ‹ŠŠŠ‰ˆ„~wnf\XWWVWX[^bgjlmqromnnoprtsrojb\YYWW[`flt|‚‡‰‰Šˆ‰ˆˆ‰‡ƒ€~}|{||yvsuwvvwxwvromklpsw8766553332110..-.....//1234556779:;<==>>??@ABBCCCCBBBA@@@@?>>>========<;:;;::999879:=AFJMOOOPRSSRRSROIC931258;>@ADDDECB?<:9730/,--,,,,,+*****)))((((&&&&&&%%&&%%%%%%$$%%%%%%%%&&''''))*****+,,,,--....../////01357<@EKOSV[\\]_^]`^\[ZZZ]`acdedc]YPG>70+++++++++++*)))))))))'((((((&&'+07BP\iquwxvttvvsmgYNB=?@BDDEFGFHKOSYahr{€€€€‚ƒƒ€{scP?0'$"!#""#'-2@MZitx~~{yungb_][WRMFA:2-+***+,-/00242LWKII[N68@JU`jtxxwwwwwwwtoj^OE<50....-,,,..-/12335:;=>ADHJMPRVX[^beimquvx{|}„‰Ž““‘‹ˆ†‚€|wtng`WSURSSRTVYZ[^_adgjlmooprssrqpopomkgb_YUQOMKHFDCAACHOYclrwxwwx{}„ˆ‹ŒŒ‹ŠŠŠŠ‰ˆ„~wnf]YYXVWY\_bhknpoopnoorstutspib\YYY[]cipw…ˆ‰ˆ‰ˆˆ‰‰ˆ…€~|{x{}}ywuvwvwwxxwtqmkknsw6666553332110/-,..////0234676778:::<<<==>??@CCCCCCBBB@@>>??>==========<<;:::9989::;=@EINRVWWXY[[ZZZ\YTKC<:;>ACIKMPSTRONJJIE@=81/-,-+++*,**))*))(('''''&&&%%%%%%%$$$$$$$$%%%%%%%%''''()**++++-----..//////1/112248<??@BBBBAAAAAA@?==?>========<<;;:::99979::<=AGKPSXZZ[\^^___a_\UMECDIMPVX[^_`b__]YVRNGB=62/.-,,,+++))*))(''''''''&%%%%%%%$$$$$$$$%%%%%%%%'''''(**++++----./////0/00011258=ADINRW[]^abdgffdb_^^^_accddec_\RJA91+++**+++++,++********((''''''(((),2;FS`kruwuuuuwvqngYNGCBDFGFGFFFHIILT^hqy~‚‚ƒ‚€xo`M<.'#%#$$$(/9AN\grwz{{xtle_\UQKC?82,*****+,,-.//..2LdXJLLX=9AKWbmvyyxwuvxxvsmg]OC<622/110/.,../1023568:<@CFIKNRSVY]_dfkoquy|€ƒƒˆ‹ŽŠ‰‡†‚|xtpic]WSQOOOPPRUVY[\^`chjlnppqrrqqqqqpomie_ZTNMKIFC@?;::@AB@CCDDBBA@?>>=>>====>>>=;;:::99987889:;=BGKORVZZ[\]^adbab`]WPNOSX\_acejjhgffc_]XVQIA<52-++-++***)(((''''&&''&%%%$$$$####$$$$$$%%%%%%%&''''))++,,--../////000/001269=BFJNTVZ]]acehjkieec`__`acefdca[WMD>7/+,,++,,,,++,,++****++)())((((((&'*/7DQ]iruuuustxwtpk_TJCDDGEGFGGFDFEFMWcnx~‚‚‚‚‚‚ƒƒ{sdSA1'$$%&&',58DNZgrvwyyurme]WQJE?80+)))++*+--..//001CfVRNPV8=CMXcnuxzxxwwxxvtmg]PD<62322222/-./02213579:=@CGJMORUXY^acglprvz~€„ƒ…‰ŒŒŠˆ‡„‚|xtpkc\WSPOOONPQSTVW[^_cdegmooopqrqqqqrpomhc^YSNKHCB@=;977:@KT_ipuwwuwz}ƒ†‰ŠŒ‹Šˆ‡†‡‡††ƒzrid]ZXZ]\_aehlooqqqqprsuutsplgb\\\^_fmszˆ‰Š‰ˆˆ‰ŠŠ‰‡}yvstx|zxyxwyxxzzxwurmkmqtu775433432111110//022333568997788::;;:=>===<<<<===<;;::9998778779<>@DILORUXWX[\_bbdfdc`[WZ^^ceggikkmikkigec_YUOH@92-,,**)**)(((''''&&%%%%%%$$$$##$$$$$$$$%%%%%%%&''''))++,,--..//////0011248;?DHLPTV^_bdeijkmllifeca`bbddedd^YSJA92.+,,++,,,,++,,++****+++)))(((((('&)-4@MYdnsvuutuwxwsofYOHDDFFFFGFDA?>BFP]jt|‚ƒ‚‚‚‚ƒƒ|vjZF6+%%&&).17>FN[grvwxxurmcXRKD>80))*))+++,--..//000?baQLOLEJU`ipuwwuxy€„†‰‹‹Šˆˆ‡…†‡††ƒ€ypfb^\\\]^_bfinpqrrqrrtuvvvtqkea]\]_bhow~„‰ŠŠ‰ˆˆ‰ŠŠ‰‡€|xsqvy{zzxwwvxxxxxwurommnqs6654443210121121113334446577899:99::<;=???@A@AAA@@>@@@?>=<=>==<<==<<;:9999887666569;?BFJMPSUUTSTX]aceegec``bcfegiiiillkliigdcb`[ULE=5.,**)**)(((&&''&%%%%%%%$$######$$###$%%%%%%$%''''()*+,,--..///////01567<@EIMSWY\_dhjmnpprqojihdcabccbca`[TMC<5/.,,,,+,,,,,-,,++++++++****(())((((*-2;;<@LXdrz‚ƒƒ‚‚‚‚‚€wp_L;.&&')-05;@IP\gqvwwwspk_UMF?5.+())**++,,,-..//1114NeRKOU^M?Q[gqwzzxxwwwwusmh\OE=854332220//////12479;>ACHKLNSUWZ^bcgmqtx~‚ƒ†‡‰Š‹‹Š‰†„ƒ}ytqkf_XTROOORRUXXZZ]^_bcegjkmnopqrtssrrspnnid]VQKDA>;:762248?JV`kqvyxvx{„‡Š‹‹ˆ‡‡†…†‡‰‡„xogb`^]_``cehjnqrrrssuvuxvvsric^]\^bflsy€‡‰‹‹ŠŠ‰ŠŠ‹Š†{vtrtx||xxxwwxxyyxwtqmkmort6654443210122221114455556577989:::;;<=>????@A@AA@@A@??>>=<<=<<==<<;;::99989966654589ACHKMORTXY]^cglptx~‚…ˆˆŠ‹Œ‰‰ˆ…ƒ‚€}ytpjd]XVTUSUXXZ\]_``bcdhiikmoopqpqsrsrrsrpomf`ZTMC=:97531028@KWakrvwxvx{„‡Š‹‹‰ˆ‡†‡ˆ‡ˆˆ…xogb``___adfiloqrssttvvwxvvrpic^\[^bgow}„ˆ‹Œ‹ŠŠ‰ŠŠŠ‰„|xsppsy{{yxxyyxxyyxwtqomnprt55554442232233333455556666679;;;;;<<<>>>????AB@@@@@?>>====>=;;;;::::::998877555544557:9<>=>>:99;CIQW_dhihgfeeba\WTTX\\]]]`adfhjkmj`VLA4-*)**(()('&&&&&%%%%%%$$$$#"####"$##$%&&&&&'''((***+,.----//0001247CJQX_hqxxyvqoh\OD90*)))))****+,--...//103;RdTTGNNRQZgtz|zzyvvvvtqng\OF>:7665553110000013567:=ACGJMORSVXY`bhjnsx}„†Š‹‰Š‰ˆ„ƒ‚|yvphc\XVWWVY[]abceegjklnnpqrssqqrssrrrqtrrqnje_XQJB<65532027@KValsvwwvz}…ˆ‹‹‹Š‰ˆˆˆ‰ŠŠ†…€yohdb`_^_bgfjnrqssstuxxxzwurlfa]]]`ejry…‰‹Œ‹ŠŠ‹ŠŒŠˆ‚|wqmpuy{{xwvvvxxwwyxutpoprsu55554444334344445555556666679;;;;<<<=?>>????@A@@@@?=====<;<<::;;::::::998776655433331456653300029@HQY]begefb_YTOHFFFHKLPQTY\afkmpnleYL>1,+++*))(('&&&&%%%%%%$$"""#####"$$$$%&&&&&''(((***+,.----//001249KYensuutuvwyyxtj_UMHFGFEC@:51024=JZgv~„†„€€‚‚€yqaN>30139>BHNSZ_hqvxyxsnh\L>4-()*)))**+++,--...//014=TNQOKQLBM]jv||{yxvvvvtqnh_RF>:7665533111100002467:=@BEHJMPSUY[^`eilrx~‚‡Š‹‹Š‡‡…ƒ‚€~{wtpjd^]XZ[[]`bdeiillnpqqrrvvvvttstsrrrtuttusokf^XPF@;8653238@KValsvwvx{~‚†ˆ‹‹‹Š‰ˆˆˆ‰ŠŠ‡„xohec`__`cefjnrsuttuvwyyywuqle^]]^`fnv|‚ˆ‹ŒŒŠ‹ŠŠ‹‹‰‡|wplosy{{xxwvvxxwwxxutqpoqsu66554444444455555555665566899:::;;;=====??????????>><;<<=;=;;;::::::99887654543211/..0110--.,+,.58AJQY\]aaa\UMC:556878:;AFKQYcjlrvtmcUJ:1-.,+*))'&&&&&&&%%%$$$##""########$%%%&&'''(((**+,,----./001348?BHNQY]acfjmrsvwz||yzzvrnljeddbba]YRMIA:3-+,,,,------........//./012210--*)))))),/3;IWcmrutttvvx{wuoeWMIGFEC@=72-,.28EUbq|‚…ƒ€€‚‚{vjYH:778>BGKOSZbjqw{yxtnh]M>3,))))****,,*,....0/0023ORY\RPRTKA[kuz|zywvwwvurmg^QF>97665432221100111345:=>ADGILPSUWY^_cfjqw~ƒ‡‰‹‹Šˆ…„‚€€}{xtojea__]^abdehijknprtuvwvxxwvvvttrrrstuvvwutrmi`\SHA:743126@LWbltxwvxz‚†ŠŒ‹‰ˆˆˆ‰ŠŠŠˆƒ~woib`_^`bdghlprttuuwyzzyzwtnic^]]_chqx„ˆŒŒŒŠŒ‹‹Œ‹‰…yrmkotxzzwvuvvxxxvxyvurpqutt6655444444445555555566668899::::;;;===>>??????>>>>==<;;;<<<;::::::::99886644442211/.....,,+,++,.04:DINRVYYXRI>40..-,+,.027?GPZcjquwskaUH<751/+)(&&&&&&&&%%%$$$##""########$%%%%&'''(()**+,----.//02358=DKNS[^cfhlnotwxz|{}yxwsplihecb`]ZXRKF?92-++,,,,------..//....///0124443.-,+**))),-2:FSaksutttuuwyzxoe]QKGFDB>:50,**06AR_my……‚€€€‚„‚~woaSC<;>BGKNSW\djqw{zyvqj^O?3,*())****,,,-,,...01123?bocRJKHPSaluz|zywvwwvurnh^QF>976654322211001113457:=>CEGKMPSWW[\_djpxƒˆ‰ŠŠˆ†„‚€~}zxtoieaa_`aacdfiikmmnqtwvxxzzyxxxutrrrsuvyyzzyvsphbZQJA;86538@LYdntwyxz{€ƒ‡‹ŽŽŒŠ‰Š‰ŠŠŠ‰ˆƒ~wngbba`_adgimqtttuuwy{{yxvrmfa^]]`cjt|†‹ŒŒ‹ŠŒŒŒ‹‰…zqllotxzzwvuvwxxxvxyxurprvvu65554444555555555555557788:::;;;<<==><=???>>>>>>>===;;;;::;::9::9:;988976554321100//..-,++++++**+/5;AGLLONJC93-*))******+.4AEEHJMPTVW\]bhow}ƒ†ˆ‰ˆ…ƒ‚€~~}{yvrmhdbbcbacddfghklnoqsvxxyz{}yyxusrrrsuw{~||z}|xqkcZRIA:6448AMXdosvzyy~…ˆŽŽŒ‹Š‹‹‹‹‹Šˆ…wmfb^]\]_cfinrstuuuvxyyzwtpld_]^^cgow|…ŠŒŒŠŠŒŒŒ‰„wpkipuwyywwwwxwwxvxzywsopruu65554433555555444466668889:::;;;<<<<>=>???>>=====<<<::::<8::;999899988765544220/....--,+******))**-4:=@CA@:4,+*)****))))),.2;GP[eluvtqiaZROJE@:3,(''*++*(&%$$$#""######$$$$$$$%%&&''())*+,--./000148;BGMTZ`deknqqtwwxy{yvtspmkhhdb_]XURMGB;51---,,,,----....///.0000//1135456520.-,,,**,-18BMZensuttttvxywrj_VLGEB>951-)(),3:FVdr}„ƒ€€„„{qbUMHIKMOQSX\cgptvwwwsmbSE>;,*++**++--...../..0246VcJBJGKIHW`mv||{zwwxxwvtoh_RH@:8555544442201111333568;>ADFIKNQSUX\bhpw}ƒˆˆˆ†…‚€~}{|ytplgcbcccc`cdefhijmopqrtwy{||zzxusrrrsvy{~}~€|yrkdZRH?7248AMZfovxyy{…ˆŽŽŒ‹Š‹‹‹‹‹Šˆ…wngb^\[]_bejorsvvuvxz|{xwsnha^\\^bhpw€†‹Œ‹Š‹Œ‹ŠŒ‰ƒ~tljjotwyywwwvwvvwxz{ywtstuvw556677555556766677777788:::::;;;==<===>>>>==<<<<<<;;;;;;99<;::888777655555223///.,----++))))*)(()(),1569840,*(((****))))))+-39EP[equvurlgb^YQMF>80,-23322-'%$$##########$$%%%%%''&(())*+,---.//0147:?FMSZ^dgjlnpqrvwwwusromkifb_\[XTRNHD?730----,,----../-.0/../111122224467641/----++++.15>JUdlrttsssvwxwslaWLFBA<83/,)''*28CRaoz€ƒ‚‚€€„…‚}wl_UONONQRUX[`fjrwwwwtmbTINJ-)))**++---...////016QlG7<9?IIJVamw{|{yxwyxwvuqlaRG@<96655433333111133443689<>AEGJMNPRT[`hqy~ƒ‡ˆˆ‡†„€}|zyvrnieba`aaaaab`abdegghjnrsuxzzzxvtrrrrtvwy{z~‚„„‚~{qkbZQE<549DP[fpwyyyz~‚†‰ŒŒ‹ŠŠŒŒ‹ŠŠ‡ypje\[ZZ\`djnsuuvvxyz{{xvqkd_[YYZ`hs{‚ˆŒŽ‹‹‹Œ‹‹ŒŒ‰ƒ}skhjnsxyxwwuuvwxvx{{zwstuuvx666677556666766677777788:::::;<<==<===>>>>==<<<<;;:::::::999998887776555543210/..,,,,,++*)))))(())(*,-00-*))(())))))(())))*+-28CP\ipuwutpllfa[TKE?:;?@A??71,%$##########$$%%%%%'''((***+,-...//037:>CJPW^cfklmopstttsrpnkifda]\ZXSPMID@;62/.----,,----.../0////011112222335652//..--,,,,-03;GS`jrtsrsstvwwslbXNF@?950,,)'').6?L[iu}‚‚‚€~ƒ„ƒ€}sg]UOOPUUWZ\^cglrvvvsk_RPGB/*****++----../////25a†]98IQOOKUclx}~}yxxxxzxuqlbTG@<966554333332246887755679<>ADFHJLNPX^gov~ƒ‡ˆˆ…„‚€~~}{ywvsmgda`^]^]\Z[Z[Z\]`cbdfimquvwwtsqqrrrtvvxzx|~ƒ„‚~zrjaYOE<9;FQ]hqxzzz}€„‡ŠŒŒ‹ŠŠŒŒ‹Šˆ…‚ztlf^XWY[bhmrtttuvxyz{yutnh`[ZYX[bkt|ƒˆŒŒ‹ŠŠŠ‰‹Œ‹ˆ‚{rliknsyyxwvwvutuvx{{zwtsuvuw77757755776676558867889989::9<==;>>?<<====<<==;;:999999999988898965555332322/..,-,,++*****))(('(('&'(())''(('())('))((((((((*.4=GS^hrwyvuuspnhb]VQNOPQSSOHA7,%#!""$$###$%%%%&&'''''()*,,,-..011258=BIOV\afikmoprutrqniiffc]ZYVTPLJGEB>820...-------------.001111334422211344220000.-,.---/39EP_gqtssrstvvusndYNEA=84.++)'').3:GWgpz‚ƒƒ€}~ƒ„ƒzoe\VTSUVXZY[\ciosvutmaXS7>7+,,++,,+,--../000136UwgIJITTQOYdpx}}|yyxzyzyyslcUG@<96644432213469=<=<<;86579<=@CDFIJPU]gryƒ‡‡‡…ƒ€~}{zxvvpleb^\YYUVUTSSSSRTTUXZ]afknnqqqrrrrrrrsssuxxz}‚„‚€xribVKB>?JS_jqy{{z~€…‰ŒŽŒ‹‹‹ŒŒŒŒŒˆƒ}wpg`]Y\^cjpsuttuwxz{zwsqjd]YWWW\dmw~†‹ŒŒ‹‹ŠŠŠ‹‹Œ‹ˆ‚{pkikmsyyxwuuvvwwvw{{{vtsvutt776777667777666677778899:9;;:;;;:;;<>>====<<;;;;999999998888888877664422120/..-+,,+++**)**))(('(('&'(('''''''())('))((((''(((+.6=HR]hrvyxxwutqnhfa`acdffd[SH:+$$""#####$%%%%&&''''())*,,,-./11147:AFMSY`dgjlnpqssolhfc`\\YURNKHEEA><750/.-..------------.0111121334443322452221110/-....-/28CP\gorssrrrtttrmdYNE@<72.+)('''+17AQ`lw€ƒƒ€}~ƒ„…ƒ~uh`YWVWWWWUWX^fmtuurhZXL7@:,,,++,,+,,-../00022>X_\TWC@NUUXfpx}}|yyxz{zyyslcUI@:866444333389:7778;<=@CGJPY`iry…ˆ‡†ƒ€€~{zzxvtnic_YTSPNMLJIJKLLLLMPQTX]ehknqpqqqprrrrrqqqruvz~€‚‚|xpg[TIABKT`krwyy{}€…‰ŒŽŒ‹‹‹ŒŒŒ‹‹‰…{tme`^^afkpsutuwxzyzxurnf`ZVWUX]eowˆŠŒ‹‹ŠŠ‹ŒŒ‹Š†wohgjmsyyxwvuvvvvwy{{zvrrttss7768988869887777667878::::;;;;<<<<<<<<<=<<<<<<::9988998888887777555521111110---,+***))))))((''''('''''''&&''''(((())))''''(('(,24>GQ[cjosvvuttssopoqruvvrjeWI9)"!!""$$$%$$%%&&&&'()*+,,,,-/012369=CIOV\afilnopsqlkfb\ZVRPLJHGCA=:9842/....------....----00012233555555445544322210////-..028BMXeosrqqqqstsqmeZOE>;60,''((')+.5?LZft|ƒ‚€}~ƒ……†€ynf]YXXWURRPV\bjrvuraXY@6<2,+,,-------../01003;[f^Z`[;HTXVhpz}}{yxxyyyyztmcUJ@96545543246;?EJNPPROKD>967779<>AFKRZclsy…‡††„€}||zxuqmg_YSPMGFFEDEDDDEFGGIJMRY^dgjloqrrsrrronkklmmoqv|€{tlb[PJFNWblsy{{}‚†ŠŽŽŒ‹‹Œ‹‹‹ŒŠ…‚}vqjfeefmpstuuwxyyyzwsnhb]WRSSX]goy‚‰‹Œ‹‹‹‹‹‹ŒŒ‰†wlefiouyywvuuvvvvuwzzzvtrqqqp888899997:889977668878::::;;;;<<<<<<<<<<<<<<::::88888877778757745555111100.--,,,+**))(('((''&&&&'&''''''&&''''((((((''''''(('(*+05=GNT]cfilnqrtuwxz|~……zsfVF5(!!""$$$%$$%%&&&&()*),----/002358;AFLSY`ehjlmnoqnhc\YTPNHDA?><;8764200///..------....0000011123345555555566444322000/0/-/.028@LVcorrqropqrqqmeZOE=84/+(()(()+-3:EVdpz€‚€}|}‚†‡†€{qi`[XWUQQOLOT^irutvbQK?882.,,--------.//000125Mk_WdV8;PWWdqz}}{yxxyyy{ytmcUJ@:7755554459@GMSX[\[YUNB;85667:>@FOV]fnv}‚†‡†„ƒ€~~~||zxuqke^TLGCABBAABAAAABCCDFKPW]`cjmqppqqrttplieebbfhntz€}yog_WPOQYbltz{{}„ˆ‰ŽŽŒ‹‹ŒŒ‹‹‰ˆƒ~zuqkkjkpsuuvvxxyzzyurlf^URPRRW]hr|„‰ŒŒ‹‹‹‹‹‹ŒŒŠ…~wmecjpvyywuuuvvvvuvxxxvsrpqpn9999999999888877877789::::::;<<<<<<<==<;<<;:::887776777777667546545311200/.-,+++**))''''&&(((())&%%&''''&&''''''''''''''''&('''')+4;BHNRXZ]bdhnuw}€„†ˆ‹ˆ†‚|reUG5&#!"$$$$$$%%%%'(****,-../011358;=BGNV\beiklooomia\TNHEA?=<96534321////----..........//00112333455566775566554433220011../137>JT`kpqroonpqppme[PC;51-*''()()*,/7BP_lw‚~}}ƒƒ……~umc^ZUQMIGGHP[jqvtkRBKNF>60,,--,,----/0//10/25H[[[i[76PTVdrz~zyyyyzzxxsmdUJ@:75564446;@HPX^bghfb_VKB964358@FKSZdkry€„‡ˆ…„‚€}|~}zxvrjbZMGC>?>>@?@@@???A@CCGNT[afimoqrqqssqpje]YUVZ]ckswz}{rld[XUY^gouz}}~„‡ŠŽ‘‘ŒŒŒŽ‹Š‡…|{{xxtpqqtuvvvwwy{zywtoic[TPOOPU\gq~…‹ŒŒ‹‹‹‹ŒŒ‰„~skffkqwzywtuvvvuuuvwwwsqnnnpn9999999999888877887789::::::;<<<<<<<==<;::::997766666566765555655431110//..,,****)(''''&&&''''((&%%&''''&&''''''''''''''''('''&&%(*059D\\M?60,,--,,----.///00.2=RY^cdTIQQOVgqz~~zyyyyzzywsmfXKA:75543448=ENU^flqrple]PE<6538=?<>==><=?>?CGNT[aeioppqpqsqqoic[QJHMTZckquzyvoia]\]bgov{{{„†ˆ‹‘ŽŒŒŒŒ‹ˆ‡„zyzz{{zxvxwwwwwxyx{zywsmf_WPPNLOU^it~†Œ‹‹‹‹ŒŒ‰zriffjqwzxwuuvvvuuuvwwvrpmnmkj888899999987887788889:::::::;<<<<<<<<<;:::::987766665455556566555211000/.-++**)*)))('&&%&%%%&&%%&%%&''''&&&&''''''''''&&&&(&%%&%%%%'*/11238@IPYcjsz€‚„……ˆ††‚xo]L8'##$$$$$$$%(((*))*+--/.0013578=AGOUZaehknolljc\TIC=96654444312200000/-----...//....//1112334455667777777766554333021100//48;ER^iprqnmkmnrpmh]QE:3.+)(&())*+*.2;FVeqz€‚~|z{~ƒ„ƒzrj\VPKB?==DMXfphI7B^i`QD80-.--,,..---./0./13A]]baUSYXSPYdq{~}{yxyzzzywtneWKA;8444446;AJT]fotyyzsj`UG=747;BJPV_fosz€ƒ††…ƒƒ~~}}||}{wqh_UJB?><<<=<>=;;<==?@DGNV[agjlnnoqqrrplg_WKCDJRX]bgjmnmmjd]ULB9874433333311100000/-----...//..../011233355557788777777765554322110110048;CNZioqqnllompqog^SG;2-*)(()))++*,39BP_lv}|zz{~‚‚{vj[SLE?:9:AKWbpkN9LjocRE90-...--....-./00123FbibSMjbRUSWfr{~}{yxxyyyzwtneWKA:7443346AIRV\bfhknnnmg`XPF;7444443333212100000/....-.--//..001111334555579999999988776655442211111148;@LWdmpponkmnnqold\NA5-))*)*))**+,18=KZht{~~}zyy|~€€}wj_RHA<557?IT`jqJ?auqgVG<4/.,,....--./00003;Zz{eHN\\HIRYdqz|yxvxxyyxuoeXJ@84332137AHPW_emrx}ƒ…‡‡†‚~~}}~~~~|wqj_SI@;<;:999<<;;;;<9=AGOV]cilnoooononmgb[OG:304;ENV`fnqsojjkloru{}~‚…ˆŠŽŽŒŠ‹ŠŠˆƒ|vrniltx}~€|{{{{{{}{xqkd\TLHFFIMWdp|…‹ŽŽ‹‹‹‹ŒŒŒˆ„~ukcaglrwyywuuuuuvtvwvvuqnkjheb::::;;:99987777788889:99::;;;;::;;;;;;:9:8776676555444544444432221/0/.,,,*(((('''&'&'&$$%%%%%%%%$$%%&&&&&&&&&&&'&&''''''&&%'&&&&$$%%%%%$%$&&&*09@EJMNSZbny‚†„ym\D-$#####$%&'())***,---/0/1369NYcer{~}{yxvxyzyxuodWJ@84311248@HP\gs|‚„ƒ~ul`THAAFIOW[cjpx}ƒ†‰‰‡„‚~~}{|~~~~{wqh^PH@;:99999::99999:=AHPY^dilnooonnomjdaWOB61./3;FQ[cjnqroonprtw{~~€ƒ‡ŒŽŽŠŠ‹Š‰†zrkebemrx|~€€||{{{{{}{vniaXOICDEGMXfr}‡ŒŽŽ‹‹ŠŒŒŒŒ‰ƒ|qjbbgltyyywuuuuuuuwwvtspmkifeb;;;;;;;;::9887888888:8999::::9:::999;:888877765454445444234441220//0.-++**)((((('&'&%&&%&&%%%%%%$$$%&&''''&&%%&'%%&&&&&&&&&&&&&&%%%%$$%%$$$$$$',158;=CKWiv€…„zn[C,##$$$$$%&'())*,,,---/0/136:>DKQX\afjjjnmid`XOF>96545444444333321000///.-----...0001133445567789988999998877665443322322136=BIT_iqrpnkmooprpkcWI<0+++))*++,+-/6;GVbnw}~{xxz}~€€~yobSD82036)##$$$$$%&'())*,,,--.001369?DJNU[_ffgknnlhcZSJC<665554444443333210000//.-----...0001133355556789999999998767665543322231247HUbmy€„…‚~uk`TLHMSXY\dmt{…‰‰Š‡„€€~~~||}{~{vpg\PD=;76667777767767>CJSY_fjmomnmmnmkf_YPE:2.++-18ALVajmtttuxy{}~€‚„ˆŒŒŒ‹Š‹Š‡{sg[SPX`kqvz~~}}}}}||zwrkcZQGB??CHQ]jw‚‰‹Œ‹‹‹ŒŒŒŠ†€xnf_bgltxyxvtttuuuuuuuspnjhfda];;;:::;;:9987788889998999;;;::::99988877655554555544333323554100....,+++**)'''''&&&%&%%$$%%%$$$$$%&%%&')'&&&''''''''&&''''&&%%%%#%$$$$####""#"$$#%&',4>N`q}„‚ykW<'##$$$$%&'()**+++,,.12249=BFKQT[adghimnlif`WNG?8766544444442222110000///.------.0001123455557779999::9999888866554433332458=BIQZckpqommooqrvqjbQC6.))+*+**,+.3:?GS`js|€}yyx{|~€€{sgWF:3149:BNYeTK^ztqk`PC70,,,+,,,----.0/3Rqzw_FEL>>LUUeiq|~{yxwwxxyxuofYJ@9412114;AIWcoz„‡‡„xm`UQQSVX[agpw}…ˆ‰‰ˆ…ƒ~~~}{{}~~}|upf[OB<864456766554457;BKSZ`gjllmmlkmlic]VMA8/,**,06?JT^imsvwwz{~~€€€‚…ˆ‹ŒŒŒ‹‹ŠŠ‡€zpeXKJP\dkrw{}~~~}}}{{xuog^XMD?==@GS_l{„‹Ž‹‹‹ŠŒŒŒ‹‰…uld`chntxxwtssssssuuttqpnjfec`[;;:;::;;:98777888888899989;;::99::8988664455544444333322222221//--,,+****)('''''&%&%&%$$$%%%$$$$$%%'(++--+('('''''''%%&&&&$$$%%%%$$$$$####""#"###$%&+1>M_p}„‚ykW>'##%%$$%&'()**+,,-./0259?DHMQV[]deijkjjjic[SJA97566544444442222221/000.--------.0001123456657778999::999999776655443333346:>CIQ[dkpqonnoqstvtnfZJ<0****+*+,/26EQQTffq|}{yxwwxxyxtneXJ@9401/15;CMYfr~†‡‡†‚|pdYVVXZ\]dirx~…ˆ‰‰‡ƒ~~~|||}~~|ytof[OC:644444555444457BKWco|†‹ŒŒ‹Œ‹ŒŒŠˆƒ|ujb`chntxwuttsrrrsttsqromjfc`][;::;;;;;9:877788877788889:::::989987776655433333443333221100//.-,+**+++*((''''&&&%%&%%%$$$$$#$$$$%&',03442-+)()))&&&%%$$$$$$#%$$&$####$#$##""#""! $')/:J\my…zkXA)"$#$%%&'()))*+*,.-0259AEJNSV]_cdfhkkkkjf_VMD>86565544433332222110../0.------...0001112346667889989::::9999776665553333567:?DIQZcjoqponqrstvuqkaQB4**++,+.036;AEMValu}‚€~z{{|€~yoaOA71148ALWdLLjvvqleVF;2-++*+,,--,-,00IzrUK^]TPWLMQPiir~}{zywyyyyxtnfXJ?8411/27?FP[hv…‡‹‰†rg]XXY\]`elrz€„ˆ‰‰†ƒ€~}~|~~}~€|yuoeYMA9522232233443348=ENV]ejkmmmnllkie^ZPE:2+))*,/38EO[cjrwy}~€‚ƒ„„„„†‰ŒŒŒŠ‰‰‰‰†…~uj`VKGMT^irx{}~}}{zwqld[OD=::;BLWgs‡‹ŒŒ‹‹‹‹‹‹‹ŠŠˆ}sga^bgotvwurnqqrrssssrqnljfa^[Y;::;;;;;:9877788787788889::::9778877776544333333222211111100//.-,+*****)((''''&&&%%&%$%$$$$$#$$$%$&'+05:=9722100.,(&$$##$$$$#%$$%#####$###"#"#"""!"%(/9GVjy„|n]J1$%#$%%&'()))*+,-.036:@FKOSX\_acfjkkkjjic\SJB;64655544433333333110/.//-------...0001112345677889989:::::988877665553355668<@EIQXaiprrpoqrstvvtneWF8.-.,+.157;@DJQX`js}ƒƒ|{{z}‚‚€{reUD82148AITcKFivvrlfWH=2-,,*+,,-----1DtwmZ8OOMSWQRROcjs}€}{zyxwyyyxuneVI>843125;AIS^lx‚‰Š‹Š†th`[[\]]bflrv|…††„‚~}}|||}~}zxtndZNA9521112211111149>GPY`gjmmmmmllkhc]WMB7/*)))+/3:FO[cipv{~€ƒ†…†ƒ„„†‡ŠŒŒŒŠ‰‰‰‰†…}vl`SKJMT^hpv{}~}}|{zvpjbVKC=::;BNZiu‰‹Œ‹Œ‹‹‹‹‹‹‹‰†€xpe`^bhntvvtrpqqqqrrrrqpmjgca^ZX;9;::;;;::89878788877899999888887766665522221111222210000/00//..,+**))))))'&%%''%%&&%%%$$$##%%$$##$&+.37;;:6777765/*%$$$$$$$####"#####""""""""!!!!#$)-6BRcs~‚~scP8'&$$'''(((**++,./25=CHMQTY]_bdhkkjmkhie^VLD?97665566553333332211///..-------../10011123455678899899999987899764434345676:>@FKPX`iqstrppqsuvxvpg\K;2----169=BEJMT[ajs|„……€~{z}~‚€xk[J;3347>ISaJJjvtrofXI<4-,,*+,,-----.KxzqJ7UNLIMSMRWchr{€~{zyxxyyywtmeYK=752548=ELXcp|†ŒŠ†}tib^\]]_aejpvyƒ…„„ƒ}}}}}~}~}yxumcYK?7321000000000/6:@GQYahmlmmmmlkie`ZUK?5-+++**.4?IS\cjqu{„††„ƒ„„†‡ŠŒ‹‹Š‰‰ˆ‰‰ˆ…~vl_RJKQVajqy|~~~|||{xsne\QG?:89=DO_lyƒŠ‹‹‹‹‹‹ŒŒŒ‹Š‰„}vlc^^bgnuutsrqppppqqqqomkgfc`\WV;9;:9:;;99998777888778889998886677665543222211112111100/0///..--+,**))))('%%%%&&%%&&%%%$$$########$%(*.166456699872-'$$$$$$$####"#####""""""""!! !#&+25347>GR^IHlvtsofXJ=4-,,*+,,------;`pjQ8[WNLPYUMTc`fv}~}zyxxyyywtofXK@96689>ELS]ht~‡ŒŽŠ„|rhc`_^]]^`djov}ƒ„‚~}}}~~~}}yvrmcYK?731000....../04:BKSZcjmlllmmlkic]WPE<3-+),+-09BLU]cjrv{ƒ…††„ƒ„„†‡ŠŒŠŠ‰ˆˆ†ˆ‡‡„}vl_RNNSZbksy|~~}~}||{wrkf\QG<888=FSco{…Š‹‹‹‹‹‹ŒŒŒ‹Š‰…|sic^^biovvtspopqqqqqqqomjgdb^ZWW::<<::::;9::9887888888889977765555445543222211222200//////./.--,++*)('''''%%%%%##%%%$$$$%%#"######$$%'(*++-///26661.(%##""""####$$#""""""""!!!"""""#%)0:JZkx€wi[F/&%*/23,)))*+,-28;AGKQUZ^`cdfhiiiigfdaYPHA<764555555554433322200//.-.-------..0011112325666788888899889888887775445567:<=AEJMQX_gouuusrrtvtvwsndUG:1027<@EHMPTX[_diq{‚‰†ƒ~s^j|€yz€|seTD83365.,,)*,,,,,,-+2LenaPn|iQO^^NLa^Vk‚|zyyyyyxxvofYJ?;;=ACHLTZbmx†‹ŒŒ‰ƒznga`_]ZXY\bipw}„‚~|}}|}~~~~}{ztqkcWI=630/...---,,-/5FXdq~‡ŠŠŠ‹‹ŒŒŒŒŒŒ‹ˆ‚{qf`\^cjrvwtsqpqrrrssqoljlge^\ZYY::<<::;;;99988878888888888777655443344332111112222/////.///..--,++)*('''''''''%##%%$$$$$%%#"######$$$$$&&&')**+///+)&%##""""####$$#"""""""! !!"""""#%)/8EWftznaP6)).59;3*))*+.16;@FLQVZ\adcfihhiihgc_YRKB<7655555555554433322200//...---..--..0011112325666688889977899888777765655668:=ADHILQW_emtwusssrttvvtphYJ>8595.,,+,,,,,,,,,-4FSkkuteb`aZNBSfkz}{zyyyyyxxsmf\PDCACGJNRZajr|„‰‹Œ‰ˆuic_\ZURSTX`fnwƒƒ~|}{{}~~~~~|yvuqkbVI=63/.---,,,+++/4Qboy€|sgWB-)1;?A7,**,-169=CJQVX^bfeggjhiihfa]VOLD<97754545566444443221100//...-------../01111233456667766889989887766665666676:=@CEIJMQW\emsvwuspssssuuqj_PC<<>AEJNRX[^aegkpvz€†…xwujfqf[hszyo^M=4249CP[PO_qsrkeZK>6/,*++++++,,--+++8YrlgdpdWVTS]hu{}{{zyzyxxxvoi_UMJJLMPTYahov~†Š‹Š‡‚zre]ZVRMJMOS]fr|€ƒ~|||}~~~~}~{xxupibTI>52.----,++++*05>GOYahknlmkkkljgcZULB8/+++04=GNV]binswy|‚„‡‡…‚ƒ„†Š‹Œ‹‹Š‰‡†‡ˆˆ‡ƒ|ulaVRU\biqx|~~~~}~~}xume^RE:6648AM\ly‚Š‹‹‹ŠŠ‹ŒŒŒ‹ˆƒ~vl`YY^elsvwusqrqqrrrrrolhgc`\\^`b;;::;;;;:98777878877777787775433111111110000001100.-////--.+,,,,)()'''&&&&&&%%$$$$$$$$$$$$##########$$%%%%$$%%$##%&%$#""""""""""""""!! !! !"#$)/9K]lw~tk\J3),4955655545544444443221100//.-.-------../0111123344566776677887788776666655688:=@BEEILMQVZdlswwvtrrrrtssqlbWNEBDJMQU[_dfikpsvz}€ƒ‚vut|dPD?@D^^XohJ8049?KZNM^lrrmeZK>6/,*++++++,,,,,,+5M]chkcDHMLS`ir{}{yywxyyyxuqkc\XRQPTV[`fnu|‚ˆ‹‹Š†xmcYSLHDBEMU^iv}‚ƒ~||}}~~}|{xwtpibTI>52----,,++++,19@HP[bhlmkllkkkieaXSJ@5/*+-3;EPU]ciorwz||‚„††…‚ƒ„†Š‹Œ‹‹‰ˆ‡†‡ˆˆ‡ƒ|ui^WUX_fmty}~~|}}}||xukf\PB84459DP_o{…Š‹‹‹‹‹‹‹ŒŒŒŠˆƒ}uh^YY^elswwusrrqqssssrolheb^^^_bd<;;;::::98988677887766776566421111110000/11100////00/.--,,-,,+,,))('&&%%&&%%%%%$"#########$$""""######$$$$$$$$$"""$$$#""""""""""""""!!  !"##'-5CVhs}{raR>.+/75/*+,.05;>DJPW\`cfiijjjiec_ZTOGB<765456544554445443222110/..---,--,,--...011112333555566777788898866666665679<=@CEFILKMOTYcltvwvtrrrrrrsqoh^TLHJOSY^bhknstvyz}€ƒ€mYZqhU[bi^PxVf`_T<:?YnbKGN_prleZK>5.-+,,,,,,,,,++,2;PVWbfQO]IELVeqz~~|xwxyz{{{yvphc^[YWZ^cgmry‡‰‹‹‰ƒvk_QLD=8;@MYeq{„„~~}€~}|{zxuupibTI=30--,,++**++-3:BJT\dhkjljjkjigc_XOF<3-)-2;FOV]djprvz{}~‚„††„„‚ƒ„†‰‹‹‹‹‰ˆ‡‡‡ˆ‰‡ƒ}vmbWSZaipx{}~~~}}}|}wuicWJ?62229DTbp}†Š‹‹‰‹Œ‹‹‹‹Š‡†zqh]WX_fmtvtutqrsrsstsqnkfca_`_adf<;;;::::988877768877666655443111111100000000//0///0/.---,,,++*))))(''%%%&&%%%%%$"######"##$$""""######$$$$$$$$"$#"$$###"""""""""""""!! !!"##%)4@N`mz}ufYI5,+-,+*+-047>CGNUZ^bdgiijjjfa\VQJD@;7665663344554445442222110/..---,--,,--../01111233355456766778878876666666789==?BEHIKLMOPS[bkpuzyusqqqqsutsne[TPQUZ_ejqux€ƒ‡†„vX@PZRP@X`^dlNJA@GKh˜“rICLcsqkf[K>5.-+,,,,,,,,-/Hn}„†„tcfjV[JN\Xeqz|}{xwzzy{{{zxvoga_][_chmsx}„‡Š‹Š‡ƒ|uiZL@7339CP\hv}ƒ†„~~€€~}zyxxwspibTI=30--,,++**++-39CKT\djkkjjjihhea[VMB81,+07BKS]fjoty}~}}~„†‡…„‚ƒ…†‰‹‹‹‹ˆ†††‡ˆ‰‡ƒ}vj_XW]emqy{}~~~|}xrh^UH;3122;FXgs€‰Š‹Œ‹‹Œ‹‹‹‰ˆ†ƒ|tkc[WX_fmtvurqqqrrqrrqpmjeca`aadef;;;:::999877776655665556422221001100////..-----//...--+++++)))*)(('&&&&%%%$$$$$$$$$$$###########$$""####$$""##""########""""""""!!!!   !!""""$(.8GWgsz|xnaR>-*((**,-058>ELRY\`cdhijihd_YTNHB>976335444455553344332211000/....,,--.---../001202344555555556666887666447679:<>ABFHJJLNLNQQYbhptz{vtqqpprtvvqkd]XW[]djqw|ƒ‡‡‡‡ˆˆˆŠ‹Œ~jVQZVZ\cfib\EDLQD@LhwmYGZwsole[K=6/--,,,,,,++-/[~€vx}xiiwkRIV_Xdq|~~|yxwwy{{|{zwojeb`bdimry}†ˆ‰‹‰…‚{qeVF;404=HUamx……„€€}|{xxwwvrkbVJ=3.,,---,,+**/3:DNXaeiiiiiijieb\WQH>5.-/5>HT\dkov{~}|}~„„††„‚„‚…ˆ‹Œ‹Šˆ‡‡‡‡ˆ‰ˆ…~uj^Z\`hosy{}~~~|xph\SE80-,09554454444455553344332211000/..--,---..--.0/0012023335555555566667775656679;=>@ABEGIKLMMMOPSV^gnuzzxusqsssuwwtpjd_^`diou~ƒ‰‘ŒpVIIW_mrklnYGQCFGIIKTb^O[tnmic[L>6/,,,,,,,,+++,.>Yhpkfd_nkPO^^Tdpy~~|yxxxzz{|{yvojeccfimqvz}…‡ˆ‰‰…‚{qeTD8218BM[fr}„„„‚€€}|{xxwwvqlbVI<3.,,---,,+**.5O_ly„‰Š‹ŠŠŠ‹‹ŠŠŠˆ…€yrk^VSXaipuvsrqrrrrssssolieddeffghg::;;;;99877766656655444200111110/0./..//..--------,,,,,+**(*)'&&((('%%&%&&%%$$##$$$$$$$$####"""###""##""""""###"""!!!!!!!!!!!!!!!!!! !  !! !#$'.8GWfpz|wocTC2,--0136:@EKRUY]aeijjjhaYSLGB=:674445555444555433333321100//..--,,----..//./11111112445555554455555445568<<>@ABFIIJLMMNONOSX[ckrwxxtsrrqsvxxxuojfdehlqw€‡Š’‘’’‘””•“Š^;BX]Wgag}pVd\KDCGOU[VOUijnlcZL>70,,,,,,++,,,,6SKNVWaPIWQNXR[Xepz}}{xwxyzz{|{{xsjfcffjotwz}‚„‡‰‡…€zqdSC725IVaoyƒ…‚€€€€€€~~}|yvvxxvqldWI;2-++,,-,+++-29AJT\bfhihhiiigd_YQJ@725=BJS\bhnsw{|}{zz{…††…ƒƒ„…†‰‹‹Š‰ˆ‡‡‡‰Š‹Š…~tia^`hlsy|~}~~{wodYJ;4-,/6CXdt€ˆ‹‹ŠŠŠŠŠŠ‰ˆ†‡…€xmeYURXajptrrqpqqstssrpmkjijjhhhfd^<<;;::998766665533331011021146674310//..--,,,+,,,,++**++)))'(())&&%%%%%#%%%%################""""$$""""""""####""!!!!!!!!!!!! !! ! ! "!!""#&,9EUcnx}zreYK;669;>>CHLRTX]adfhigc]UPJB=8766654344445544554433333211//......-------..../11001243334455554555443688:<>@BDEGGIJKLMNNOONPOQSYbjqyvttsrqsvy|}ywrmijinuz…‹Ž‘’˜™˜˜™š˜“ˆ„kSy€yob]LEV^YUQOMOPRYa\QC71--,,,,,+,,-*-5ZgWTaVcT:GLSX^hu|}}zzywwxz{{{xslgcccfiloruyy}……ƒ€zrdTE:58AN[gr{ƒƒ‚€~€€~{zyvvwxwslcVH;2-++++****+.4;FNV\cfghgghhhhd_WOG<59Pdmx~}zzyxwyzzzxuojdbbbbgkkosuxz}‚‚ypeTE96:CP^jv„…ƒ‚€~~~}{zyvwyxwslcVH;1+*********06=GOW_fhiggghhgfb]WOG?<@DIQX\bhllruvvsrswz}‚…‡†ƒ„„„‡ˆ‹Œ‹ŠŠŠ‹‹ŒŒ‹Š„~uk``flrxz|}}~~€~zskbSD81,+1;M_n{…Š‹Œ‹‹‹ŠŠ‰ˆˆ‡†|si^SPR[bjrttrrrrrrsttsqonlllljhf`[V;;99999876665343221///.//2278<>BB@=9554211/....---++*)))))((''('&&%%%%$$%%%%&&%%$$%%$$##########""""""""""""##""""!!!!!!   !!!!""#%&*3BN^ht}zsjbUGABCHLPTWZ]_bdefgeb\WOGB;85455556655445566555544442200000/..----..----///011011212222222423435678<=>ABDEEEHIHIKLLMOOMNONNMOPU^grvvtststtwy|}zuoljllmpsz„Š”•–˜š™—•‘‹„€|{{}~€{tk^RNUOPUVVMGKOSSTWRG;4+..-,,,---,..05:=QNQEDOPC>Pkgw|{xwwyyyzyxutlib[]^aeigmpquwy{~}wlbRF89=HTbny‚†„ƒ~~~}|{yxvxyxwslbVG90+))))**))+18@IRZafgihhhhggeaZUNFBAEJPY[_dhkmprqomorvz~€ƒ„…„„„„‡‰ŒŽŽ‹Š‹‹ŒŒŒŠ„~rgbbgmtyz{}~€}xri]NB7.+,3?Obrˆ‹‹Œ‹‹‹‹‹Š‰ˆ‡†yqg[QPR[clquvtsrssttttrrppolkkkfc\UP;;:9998876554333221///./.16:;@CFIFDA><;855431120..,,+***))((''('&&%%%%$$%%%%&&%%$$$$$$##########""""""""""""##""""!!!!!!   !!"#"""#%'/:GVcnx|wrjaSHHKNRVZ]`abdefedc`\UNF>966666666655555566665555443310000/..----..----///0//01011233545555555789<=AACCEGHHIIIJLLMNNNMLMLKHIJOWblsvusqrttwy{|zuolihfgglpy|‡“•––˜–’‰‚yz|~~~yqj]UURRNNLNIDGOPNJMPJD9----,,,---,--.3=PWPLCISQCAOmiw|zxwwxyyzxwtslg_YXZ`fknolnnorvy|}{ukaPD89@JVdq|ƒ‚€~~~}|yvvvxyxwslbVG90+)))))))),29AJS[bfghihhhgfb_YUMIDGJRX^_bejllmlkiilpuy|ƒ„††„„„‡‰ŒŽŽ‹‹‹‹ŒŒŒŠ…~rhcfjpuz{|}~€|wqh]N@6.+*6CTdt‡ŒŒŒ‹‹‹‹‹Š‰ˆ‡„wndVOOT[dlttsssrrrrsttssqpoonmhc_WNI<<::99888554544121/..../036:=AFJMMKHGEB??<<;87765411.,))))(''&&&%%%%&&%%&&%%%%%%%%$$##$$########""#####$""##$$""!!!! !!   !$$##!!$&+2?M\ht~|vrkdYTRVX\`ccefffifda_XQKC<6687667777776656668976553333100000/-./....-/./-/01224455566866786688;>???ACDFFHHIIJJKKKLJKKKMLKJHFCDIS^iqtvtsssuwxy{zuqkfd`_behpw{…ŒŽ‘’–•••“Ž‡~yyxz{xsnha^[YVTMMJIJFCGKGCHPMLH/).-,,+-,----,06FXQQIQUGA=>Jky|{zwvuwzzwuurldZWWY^gmcF@Dcknquz~{uj_PC9=DO]hu}‚‚€~~~~~~~~}~{zwwvvwxxvrkaSF80,(('''(()-4;BKS\cfghhiiiifc`[XRLKLQV\_ceiiijihdddintx{‚ƒ„„„……‡ŠŽŽŒŒŽŽŒˆ…tkggmsvy{|}}}~~€xofXK?5-+.8FXjw„ŠŒŒŒ‹‹‹‹‹Š‰‡„~ulbWPNS^gmttssqqrrtttuuutspoolf`ZQIH<<;:98875554332210...../036:?DHLRRPNMKIHFDDC?@@><;9952-+*))'&&&&%$%%&&%%&&%%%%%%%%%%$$$$#########"#####%$$#$$$$"!!!!  #"##"!$&'.8FXepz}{wrle`^^]adffijiiigc_[TMF>999886677777777888:89976643301000000//0/0//00.144456668::<=>=>@@ABBDDFFFFHIJIIIKKKKLLLLKKKKLKLJHGCB@?CLYdqtvtssttwyyyxvpib\XWZ\`krx€‡Š‘••–”’Žˆ|yywvtnecaZYWVYWMKLJE>??>>:64221/110/21132134577;<<;:9=IUcotusssstwwyzwtpe\SPNPS\cmqy‚ˆ‘‘““’‘Œ†|xwwwpf]YVRQMNVQFGFC;;?CGHKMOMOQ]@#*,-,-,,,-,--0<N_o}‰‹ŒŒ‹‹‹‹‹Š‰‡†„{qf[QOQY`iqttqqqqrssttuwwutrolg`XNHEJ::8877775442220/..------/247=BGKQRRRSRPONMNPPPQROPLGDA=940+)('''&&&&&&&&&&&&%&&&%%%%%%%%%%#$$$$###$$%%'%%%%%%%$#""!!    !!!!!! !!!"%').4>JZfq{€|ywtussqpooppoojgd^UNHA<;999988889989;;=BFIJMNKIC@853223333467:;=`k__K:96Maqy}ywwwuvwp_V_hiea\aS=EH?KX`YW[fryxuneXL?@EN\it|‚€€}}}}}}|~}|zyutrrsutqoh^RE80+((''&'),19CKS[cghhhhhhiihhfea][[[\_acec`]YWTRW^ekqtxz~‚…ˆŒŽŽŽŽŠ†€xpmntxz{|{|}~~~€€}wqgZK=4//3?Rcr€ˆ‹Œ‹Œ‹‹‹‹‹Š‰‡†zod[QNSY`jqssssrprstuuvwwwwspme^ULEDP888877766431111/.---,,,,,.059?CGLPQQQQQPPOQSTTVVSUSSPKGA<52-)('(''%%&&&&&(''%&&&%%&&&&%%%%%%%%&&$$%&&&&'&%%%$$$$##""  !!! !!"""$$&+/4;DQ`js|~|zzzxvxvvurtrrnid]UNF?;:;;::9999::::=AGPTY\_`^[UMA654549;=@CEFIMMOSVVZZ\^_bedghiknmorssttuvuvvwwvvtuttrpooligd_[VOKGD@=87434GR[enu~…Œ‘“‘‘Š…zxvuwvolf^XRVWTQKHDBACBDDILNTSLMJDl\9+---,/2///1@\rm_[C98>Xf]p|zxvvvuvo]]b^`[XZU[I^F@D]VMQblqurspgYKBBHS_lv~}}}}}}}~}{xvurqqrsrpmh]QE82+)((&'()-45229DVgv‚ŠŒŒ‹Œ‹‹‹‹Š‰ˆ†xnbYQNS[bkqrrqqqqqrrtvwvvwwuqoe]TJEHU8887776534311/00.---,,,,++.269>BGHJJLLMLNPPRSSWYZXXXVSPLGB:4/+)(''&%&&&&&('''(((''&&''&&&&%%&&&&&&''&&'''&%%$$$$$$$"!  !!! !"""#%%(+/27>JWblu}~||||z|zzywwuusmg]UNE?=<;;<<;;;:9;;@EMV^eknprqoj`QC;:=@CHIMQTVXZ[]cdgikmnprrqttuwyzyx{|~}~~~~}|}}}|zzzywtpjf_XPJB=94348>FR^jsuussstwxwxxtmcP@228BNXaju}†Œ‘’‘‹…|xvvwxxuqjha^XSPPKGFFGEEDFHIOYSDBBJ`h6)+--,..11;OhhVWO999J`eS]{{yuuutoh^c_VWUPOLOb\]]EOR_gsmtp}uqdWJACJVcox€ƒ~}||}}}}}{{zyvrqqqqrrpmh]QD70,)(()'(+.6>IQX`fhiihhiihjjjihfa]]]_aa_]YTQMJLPW`hmrwz{~‚…‡‹ŽŽŽŽŽŽŽŽŽŒˆ…~uttx{{|zz{}~~~€€~yrh[MA833;GXhxƒŠŒŒ‹‹Š‹‹‹‹‹Šˆ†xnbWRRW_fkqrroopqqrrtvwzzyxwtpi`TKGJ\99976553331///....,+++,,+++./379=ABEGGHIKNNNPPUWXYZZWWVSQIC>84.+*'''&&&'((('((((''''''''')%%&'''()(''&''''%%$$$$%%$$"!  !!!!!!""!"$&(*,/26>><<=<<;=>AGQV_isz}ƒ„ƒ{qcUHEHKOSX[_chhlmnqrstuwwy|z{{{|~~€‚ƒƒ‚ƒ„…„„„‚‚‚‚ƒ€}zuoibZQLF@<>ACIR^hquutsssvvxyyxy{{|^/-?IS^jt|…’’’ŽŠ…€|ywxvxyzxxricWVQRNMIKHFHEFGIMVLCADB?UK$*/,//1<`SKQP\SN9;;Q_jketzztswqeb^ZRPSQJJFDRSdaZJNOWbjih_WlbUICEOZhp{€~}|||||||{zywtponnqrqolg]PC70+)(((')+18BJT[bgiiihhiiijjiigd___^^_^YWPLIGGKQXbjpuvz|}ƒˆ‹ŽŽŽŽˆ…xuux{||z{}}}~€€~|xqh^QD;44>K]m|‡‹Œ‹ŠŠ‹‹‹ŠŠ‰‰ˆ†€xmaWTT[agmqsspopqrsrsuwz|{zwuohaULIPb8876544200////..--,+++++***++-/1488:==@CCEFHJJPSUWYZ[XYXXRMIB;62.+('''')))(())))((''''''')''''(())))('((''&%$$$$%%##"!  !!!!!!""!"%'(*+.25:@ENXdjqsuy{~}~~|{yyuld\TMFC>>=<<<==>?BHOX`kt~‰”—••”†|reacgkptuwz|}}||}}€€~~}}}}{z{{{{}~€‚€‚‚€€‚ƒ„†‡‡„†‡…„€|zvsolgd_\\_fksuvuqsrstwƒš£¡¢¥«®¨h,>OZiu€Š‘•“‘ŽŒˆ„€|{ywwxzzzwnkcODT`XYQLMKLQRUUPJBOFE<=:BcXJVF0^ysrnW>QUQ:;JY_VivrpvxvvriURW]THJJJLRTMT[L[UV\U@`g^_nbRGHKValv~€~|{{yy{{{{|{zwsponllooonje[OA80+*)((().6>HQXbgijiiiiiijligida^ZYWXUOHEA=>CIPW]fkqtxz}~~‚…‡ŒŽ‘Ž‘‘‘‘‹‰…|}||{{{{||~~€}{{rh^SG=89BSds€ˆ‹‹ŠŠ‹‹‹‹‹‹ŠŠ‰‡}vj_VRW_enrutspopqrqvuvx{}~~|zwqjcYXbr7766433110//..----+++*))**))))'*))),+-.10258<=@CEIMQUVWX[Z[YXSLD?:61.-,+++*)++)))))))((((())****))****()(('&&&%%&&$$$#"""  !!""""$%%&'+-.0469<@HNUZ`ddhnpsxz||~}€‚€€|ztnha[UMGEC@@DINV`hp|†”—˜––—‘Šwmknrsuw|~~~~~}}}}}}||||||zzz{{{}~~‚‚‚‚‚‚‚‚€€‚ƒƒ„†„…†‡‡‡„ƒ}zwwtpnmrtwwwspnnx„—¡ŸŸ¡¨¬°±®’48IZiu‚Œ’•“‘ŽŒˆ„‚~|yxxxzz{{yrbNCPSS^XQQIMUVWQN>:;?DLSZagmsvy{}~~~~‚…ˆŽ‘‘‘ŽŒˆ†€}}}}{{{{||}}€}|zwri_UJ@:;EWfv‰‹‹ŠŠ‹‹‹‹‹‹ŠŠ‰…ui^VR[bhosttspopqqsvuwz{}~€~yunh`bmy775543120/..--,,,,++++***)))'''())))**))*-..0359:?CGMSVY[]__]ZVPJC?:631,,,+**++,+++++****)+,*,,,++**++))**('&&&'''%%$$##"!  !!!!""#%$%((,/0479:>@DJPQTW\cginqvx{~€ƒƒ}zvohaXVQKFGKRXagpz„Š“—————’Œxssvwyzz{}}~}~}{||{{||zzzzzxyzzz}}~~‚‚‚‚‚‚€‚€€€‚‚‚ƒ‚ƒ…„„„††…‡‡„‚‚€€}}}}~|xsv…—¢ŸŸš¦«®¯®«šA5GYft‚”•”‘Ž‰†ƒ||zwxwz{|{si`SSR\hbWTLNWXTRK85IC=;:7:>Thf^D,`UUoVRQZI<>KUH;V}zZTabfd_]ZasiGGJJILLTXXPMINQdc`SR_cnmNIP^hs{€~|{zzyz{{{{{ywsqnllklooomjd[PB70+*)*))/4CHOUW\^`^^\ZUOID@=731/-++,+,,,,,++++++,,,---,,,,,,+***)'''''''%%$$##"!  !!!!"""#$%((*,/257:>BDDDEHLSY^bfimovyz€‚…‰‡„‚|yvpid`ZVUWZahny‰‹“––””“‡ywwxxywz{{z{{yxyyyxxuuttrrrstuvvwwzz}}}~~~~||~~€‚‚€€‚ƒ†‡ˆˆˆ‰ˆ†……………‡…ƒ€“ž ›ž¡¦ªª¨¦¥ž–T1HXguƒŒ’•”‘Œ‹ˆƒ||xyyz|~}|xrd_VWadb[RPSPRVPK7.38;;88>CLbqkDVvkYTZm_P@>?QJA@B:9<=<<<==GOQe\BEJKKKHOPUSPQRVWP=MOTTabWJVakv~~zzzzy{{{{{{ywspmllkloopnjd[MA60++*)+,27AJRYagjkkjigfffdbb^ZVSOLGB=77337>??@DGLQW\`cgimpuz~†‡†‡„€}yuroieddfkpx‚ˆŠ‹Ž””“‘‹…}xtuwuvvuuvuuwtrromkjjhgggeffdfiklmpqrstuuuutuuttuvvwxy|}~‚‚€……†ˆ‡‡‰‰‰‰ŠŠŒ‹Œ‹‰“Ÿ›™› £¤¤¢œš–”‘s3DXguƒ””““ŽŒˆƒ}yzyyyz|}}wsic][ddecXXXYVVSC62/7=<:7>KSxx]HPhpjgXUQQ@A?<7>BDFGGFEGFFMTF?BBCMPNKKJIIOVWYSEEO68FUUGCXMXdmx€}zyyyy{z{{{zxvsomkkklmonlhbYK?40,+,,.18@GNU\djljjhhhgffc_^ZUQLHB>:4/136:@DJQY`hmswz}~~~|~‚‡‰Ž‘‘‘‹ˆ„~}{{z{}}~~}}zyvrngbZPGBEPao}†‹‹‹ŠŠ‹‹‹‹‹‹‹Šˆ…}rh_Z[ckswwvspmnqqqrtvwwy~ƒ‡ˆ‡…‚||ˆ334211//.,,++,++**((((((''''((''''''&&''')))******,.19?DLOVZ]_aa_ZXXUQKGB>730..-...-,,----..........---,+++*''''''&&%%$$#"    !!""###$$%'')*-0377;<<<:CA=GDB=<;;88599@TNF@?EJJLKIFHNYb[UMFJUWNEKJEWNU\eqz€~{yyyyxyz{{{zxtqmmkkklopnlhbXJ?4///0259=DJSZ`gkkjjigfffea_ZVRKDA;60.,/25=;8776789>ACEHKLPSUWYWWVUVTSRQQPPOOOOQTX[aejovz}„……„„…………‡pŒ”™›—‹}~~|n|ŠŒ‹sLYgv„‹“‘‹‰‡…‚iamcz…‡ˆ‚wjb\VTa\FCHQRHRb]L::;6565649E@6Xh_UNTYOF;?<@AEI=4c{nWdjMKLHJIKMJFCBBAAHLMMKE=?@@KPEWX\_blu|}}zxwwxyyzzzyxzzrkkhgkklookf`UJ@::;?BDHNRVY]bgkmmiihhffd`]XSNH>5/,)))*.27::>BEJQSY_dipw}€ƒ‡†…ƒ‚ƒ„‡ŠŽ‹Ž—œœ”ŒŠ„wsƒ{r|{{‚ZZguƒ‰’Ž‹Šˆ„†‹wpqro‹””‘Œxkoh]Xfc][FGOZcmf;MG:78678659BDEZ`TOHIJ@@=?BNZcH<2Wzm]MPXRNJMNJGFLMFAA;;?DGIIFIJLJOO?XXRbhox~~}|ywvvwwvyxzwvohoz‚~sljorjig\SLHCEGJORTWY[`fhjklkkjhgfda_[VQJA8/+(()((,38;BLSZagmrwz{€€€„†Š‘ŽŽ‹ˆ…ƒ}}{}}~~~~}|{xuqmjfaYSMQ\lz„‹ŒŠ‹‹‹‹Œ‹‹Œ‹Šˆ|rfcgisy{zvvsspooopqrronquz…ˆŠŠŒŒ‘‘1//.--+*+***((''''&&&&&&%&&&&%&&%&''''(())**++********+,,,059?EIMSY^bdfiif^TH<3101/0111122110222111100//..-,++*)))(('&&&%$""!!  !! "$',/00/-+'&%$&&'(*+/359=@CFIKOPPQRRTVXZ\]]^aejrx€†ŒŽŒ‡……†ˆˆˆ‡‡‡‡ˆŠŠŠŠ‹‘”““‰|l\QNLF?;9531./0....-/2336<>BEHKMPRTWWWWXXWVTSRNJFD>:41/..28=BIOU[`gnvz~}€zvy‚Š‘™›™•‘ŽŽŽ‰{nsmly€Ž›–v~v€Ž‚zƒ‹‘“tp}px’”‚cagjcU\[\PPOIZdX9MK=8878;76=?GcTN7<68=FDAACKW]CF;8=AGIIJLLNOQXZXYXZepy}}|{xwuvtyxssvpfJL[p†™`X\SGK[^YQLHJLQVXZ[^cfkmnmnljihgecb`\TMG=4-*(()((,27=ELS[ahntxy|€€€„†Š‘ŽŽ‹ˆ…ƒ|{zz}}~~~~}}{xsolifa[URU_m}‡‹ŽŽ‹‹‹‹Œ‹‹Œ‹Šˆ{sifimtz{zwussrpoopqppnkmqv{†ˆŠŒŽ‘‘‘0/..--+)*)(()('&&&&&%%%%&&&&&&%%&&''(())))))********++,,---.06>>960,)(')))*.169=BFGJLOPRTTTTVZ\`deeefiqu{€…Œ‰‡…†‰Š‰‰ˆˆ‡ˆ‡†ˆˆŠŒ’“’ŒˆzhREA=9731000../----..048<;9;@E<98:FC@G?:987L@ACEJUSFI_]SZeRRX9BhrhUIFDABB?:7;@CGHIKMPRRUX[[X`foz~}|zwvwuuyt_`whbkrnna`JGFINNEYb\UPPQVY[]`deimopomkijhhgee`]ZTLD;1*)))())-49?FNU\cjpuw{}~€€€„ˆ’Ž‹‹‹ˆ…ƒ€~{yyz}}}~~~}{yvspliea\WUXbp~ˆŒ‹Š‹‹‹‹‹‹ŒŒŒ‹‡€{slkmrw{|zxtssoonnpqqnkhhlpv~„ˆˆ‘’/...-+*)()('((&&&&%%%%%%%%%%%%%%%&''(())))))********++,,,,,-,.29>CKQY`hmrqkcUH:32222223344444333333321/.....--+***(('&&&%$"!!   "%*/39@GKKKGC>70+)*,-/157=AFJLOORTUVVXX\`ehlmnnnprx}†‹ŒŒˆ†…†ˆ‰ŠŠŠ‰‡‡†„††ˆ‹Ž‘”Œ‡{iQD<77353//....----0036:>BDHJNQQRVWXYYZZZXWUURNJF@951-,,,++,,-05;DLT[beuŽ”—’”•™žŸ˜ykomŽ’‘’’’¡¶»¹»½½½¼»»´–‡ƒˆvpihjk_]`vwy{kUVrƒnUZjmjll`]TQTNJSvu<413;<<:@KG847?FDLE;<9;J=>A<8:<@CDGIJKLNQV[[X]gqu|}xxtsvvmUsmld]^inf^CDNPQJDH]le`ZUTWZ\^adfiloqqomkijhhgee`[XQJB90)()))*,08;AIOW^ejpvx{}€€‚…ŠŒ’ŽŽŒŒ‹‹‰†ƒ}{yz{{{|}}}zyvsqnjgd`\WW]esŠ‹‹‹‹‹‹‹‹ŒŒŒ‹‡€{spnquy}}{wsssponnnnnkihijmqz€…ˆ‘’--,,++('('''&&&%%%%%%%%%%%%%&&&&&&''(())))**********,+,,--..--.27:?JR]hmqsodXK=42222123444443333443212311/00..,,++))('''&%%#"!!!  !"#$(/5=GMRVY[VRJC<60./0449=CFJLORRUUWZZ[\`bfmrtsqqsuy~‚…Š‹‰†……†‰‹‹‹Š‡…„ƒ‚†‰Œ’’Ž‹€o]RLD<84541--//---.2268>BEIJLOPSSVWYYZ[ZZYWUTPLHB=61.++,,,,,,-148?INTYm‘™––ŒŽš”‘}]5c…„‰‘””Ÿ¸¾¼»¿¿¾½¼¾¶³º¤„ˆ†‰|_Rgcspgwz{zlW\dXL[cfb]agWHGNMLZmhJ:9ID><8DA9673EYSA39<><;:>BDCDFGFHJPSWY[]gqwxzyvtslrnmorfG6@daHKKLLHFL\kpkhc]YWYZ_achikopqpnljhhhgfdc`ZWOG?5-)'''+,04:ADKRZ^emrvy{}~ƒ†‰Œ‘ŽŽŽŒŒ‹‰‡…‚€~|zyxy{{{|}}yvsplihfc`\[Y^gu‰Œ‹‹‹‹‹‹‹ŒŒ‹Š‡{uqruy{~}zxtsrppoooopmjgfflpw}‚ˆŒ‘’--,,+)''''&&&%$$$$$$$$$$$$$$%%&&&&''(())))********,,,,----../1/.24;DLXbjsvqiYL=53323234444556666444323331/000.--,,))))('&%%#"!!!  !"$(-5>HRW^degfaYQGA;5457:?CGJLNQTVYYZ[[\`ejnrvvvttvyy~‚†ˆˆˆ„„…†‡‰ŠŠ‡…~zyyzƒŠ’‘…ylc\WPID:71./..---0168:@DHIMNOPSSVWYY[[ZZXXURPJE?94/-,,+,+,-.0368FEA<5COQ987<GT`jstqj[M<54433245544666644555434331/011/.---*)**))'&$""""" !#&+3;GPXahnoqqlg]TLF?<:=ADHJKPQSVX\[\\\`chnruvvvuvvx{~‚„‡‡†„„†„„…„‚‚~yvvslou|‡Œ’’ŽŠwqmia[VOH?93/-.-/038=@BFIJLNPQSTVWYZ[[ZXVUTRMIB<72/-,----./028=@FKOTqœœ ¤“rYghu€w”Œ‰Švvo»¾¾¾²°¼¾¿½¸“v§º¯…˜ƒQ6OVO@KCFC^YLQAE:nyM0=Bprwxteh^JGGDC@:78:<>ACEFHHJPUUUXQXmvzxxsHZspgZafeqo_TLJNNLOflkllmnnkhd_\]]_`ccgiikmkkjjhgfedc`]XRKC:1,)'&(,04;>BGMV^bhptwz|}~~~}~ƒ†Š‘ŽŽŽŽ‹‹Šˆ‡…}{ywxy{{{zyxurnjgefdc`^\\dnyƒŠŒ‹‹‹‹‹‹‹‹‹‹‹Š‡{usuy|}~}ywttsppoopoolifceintx~„Œ‘‹++**)'''$%%%$$$#########$$$$$$$$&&''(())))****++,,-------...1311238>GT`jrusiZJ=4343335665566665555543433211010.---,++*))'&%###""!#(-5@IT^fouyzxrlf_UQMGEGJIJNOPTUX[^]___aeknotvvusstuy|€‚„…„††ƒ€~~~{wsplgc^agq‡Ž’‘Œˆ~xxuqnkd^VL@91./158EITRMKKKTMKEAB?>zV12?b_fvvsk^YQFFFCB>;98;=>BDGHKLOW][YYdmvywuq;Qsd>[okvwNCSOJEQbihgjjlnoomgc_\\\\^\\^adfhjjjiggfecb`\WPIA80+('(+.38GU_jtyrh[H;33356556666666666666655554432110.,.-,+*+*'&%$##"!!!!#%*18BMW`hry}{ysoic^ZWTSNNPTSSRTWY[_``aacfikorrtsrrrtvy}~‚„„ƒ~{xtplgc_XQORYeq|†ŽŽ‰€|}||yxtnibYNB;65:=@@DGJLLMNOPRTVWXZXVWSROMID>:630..//..1358<<>@BEFIJOPRU[^Y\bnvxwttqusdQdovmK?GKJGWljihiklknnmjhb^\ZZWTRSUY\_fhjjiggfeca^\WNG?5.(&&*,059<@DHOV_fmquz|{|}}||~ƒ†‹‘ŽŽŒŒŽŽŒŠ‡…ƒ{yyxxyyyxxvspmhebcecda`aelt€‰ŒŒ‹ŠŠ‹‹‹‹‹‹ŠŠ‰‡…‚{vwxz}‚{yvsrqoooopolkgebdglpv}…Š‘‹‰*)(('&%%%%%%$#!!"""""####"""##$$&&&''()))*)*++,,++----....../321238>FS^itxsh[J;54456556666666666666655554432110110-++**('&&%$$"!!!##$'*19CMWbjrvyxvpqlhed^\[YWZ\ZZY[\_aeffffhijlpqrsrrqqqty}€€~}yvrmhd_[YTGABDMYgv„Ž‹„}{}~~}zvskdYNC??@CDFIKLLMNOPQTVWYXURSNMMID?:842/////1258:>@CFHJN^•ššžŸ—ŒwW\‰¤§–„tbYadIZ¾®†²µ©®nq‹¡š}e™Š™‹gagf]cdYTWf\Teiic\bR;<>D=E=8;:H[fLKPCCBG`RUXAF=W=>CGPXKNqupg_WNJCACC@=<>@ACFMLNVYWZZ[]`hrxyvupoqpnnryfJBJJCAJbefhhhhklmlkie_[YXTOMJLKPY_cgijigfedba]ZUNG>4+'&(+.059>@EKRXbgnswz|{|}}||~€„‡Œ‘ŽŽŒŒŽŽŒŠ‡„‚€~{yxwwxxxyyvspjfb`adddaabgoxƒ‰Œ‹‹‹‹‹‹‹‹‹‹ŠŠ‰‡…€xvuw{~zwusrqoooopolkfcbdfkpv}…Œ‘‹‰)(''%$$$##""""!!""""""##"!!!!"$$&&&''()))*++++,,----....----/133128>HT^juxrh]L=56665576666666666666666665444221000/.,+*)'&'&%$#"""""#&+2:CMW`gnopoomljjjhgcb`^^__a_cefhlkjiijjlnqtsrqonpqvz~€|zxvqke_[WRNJG@>@BFP`qŒŽ‰zuy{~€~}yuoeYPIHGGGJJJJLNNNQSUVVSQNMJIGD@:6440012357:<>@BEGIJPS„ž˜œŸ˜’vYQx–˜~wf‡_( }½»©„}—¬®w[e˜‰>Z“ ˜zUY\]twm_\]ZSVmhghVHE9=MSF@>Td\`u[FLNNGCUA=@=CA779DWS?5CBDC;@O\O>D?;9@JMUSLYjnqjZOMHB@BB?<:=AACFSUS]bba`_^bjsxxwsprpkHTxvFFMNFLgjfdfeegijkkklf`\VUQOGC@CDNV]dhiihgfcdca]ZUME<3,(')+/25:?AFLR[dipux{{||||{}}„ˆŽŽ‹‹ŒŒŽŽŒŠ‡ƒ€~}{ywxwxxxwvtqlhc_^^cdcbaejqy‚ŠŒŒ‹‹‹‹‹‹ŒŒ‹Š‰†ƒxsuw{}}|xutrrqnnooqpljgcbeglsx†Œ‘Œ‹‰(&&&%$$$""""""!!"""""""""!!!!"$$&&&''())**++++,,--......----.022128>DO[epvtkaP@77765556666666666666666665444223200/.,+*)'&'&%$#"""""#&+2;AKU\ceddcacfijjjhgecdceffhklmopnmmmnnnqstsrrppqvz|zzzwupkf]WQJHDDA@>>=>BJZl}‰Œ‹†|rgkqv{~‚‚ƒ€}{tle[SNKIIIJJMMNNPRSROKKIFFDC@;863322579;<>ACDFJLMQQdŸœ œ‘ƒ‚…cQf„ƒ†ƒƒŸ›Z*#x¼»´¥‚]|œ›ˆbVkŽ{lp–¤fC\_cz’’vmdb\haV\X=9?DKWYD9BBc\WbYKFC@:LT38>IJ<435>FJHDAG@E7@GT=@<=:H]X[]?EOM^^_ZO??@ABBB@=>AACFTYW_cecbabgltxwtsl`\rlf_TMCL[dsmhedcbcehjkkkhc]YROJC:76;7557879<>?BDFHKLMOPQTƒ£Ÿ¢Ÿ™‰pˆ‹uPW|{vs{ —@*#hº¹¶²¦…`dvƒƒi`}‡}pc„‹ODfrŽˆ’Š}|cacSRX\R3>KS^kF/05MSMQJIFDDDW;88=@732548DD;>?BAS@AF@=BJRHVgQQW@F@0OOMP@;?DFHB?>?@BBDHQYZ_fgddefinuyxuqpc]jgJHSNMYesnieccbcceeiihgd_ZRLG@9215:CLV]dfhhfeedca^[XQIB7/*('),046CKQSSOJIOTZaeghghhgijjmnpqqqrrqqquuw{{{zwttuvvwutrolga[SKD@>>?>=><<;<=AGRcx†Š‡‚ucOJOXbkrz…†‡…ƒ€yvod[SOKKLLMMMMHFCA@ABCBA=;<:9;>AABCDFHJLNOSUQScš¡ž—‘qY~‡t^Md}{vqu‚2**Mµ´±°«ˆrijmohu|mdtvb>>Xƒ¡™–•Œuf[UKMX_^GKBFZUD329>5:NIIC>;FS:589CFIFA??AACGHU\YZchceagmr{wsj^gg^^RGF::KS_rmiecbadddhgggf`]UMD=5-,.3DHKKHGFIOV[^`adcegijjkmopqqsrstvwxz{{||ywvvtrppolie^VLD?@?=>====;;;;G43BKK?3A>?713059DC>IQY`eggeddcb`_\XUPG>4-)((*,058:=BIRZbjpvvxy{{|{zz{}†‰ŒŒ‹‹Œ‹‹ŒŒŒ‹ˆ„}|zwutuvvvutroje_ZUWY]`adhptz„ŽŒŒŒŒŒŒŠŠ‹‹Š‰‡ƒ}tppptvxzxurqqonnnmmnlkkiifghms~‡ŽŒ‹‰‰$!##"!   !"!"$$%%''())))*++,,--......-.////....////029AKW`ekpnh`ULF>;:86644445555444455466665323321/-,,+*((((&%%$"""##%&+039>@CBBCGINSWYX[\\]_aadgikmpqtstuxyy|||||yzxuqnlifc_XPGB?>>==><=<<::9:;=CM^p€ˆ‰†|jS715=HO^ju}„‡†‡‡…‚~{vpi[SOMLKHB?;;;>AEFGGIIJJJJJJJJLMNOQRRRRSWVv£ž›•Œn:Ic‰ˆxXNYrxzx„`854.z¤Ÿ¢¤–‰~mdXNB?D]jhcJ@;:;>@A@>??CFGHITahk\FNN`kZOT]T4/@HCAFGGTrrlheddeccdeihe`[UK@5-())/6>IQ[bfhgedcbba_\XTOE=3-)((*,058:=AJS[bjpvvxy{{|{zz{~†‰ŒŒŒ‹‹‰‰Š‹ŒŒŒ‹ˆ„~|{yxvtuvvxwvtnic\XTTX\`aejqx~ˆŽŒŒŒ‹‹‹‹‹‹Š‰…€yollnpvwzxurqonnnnmmmmmkjjjhjov€ŠŽŽŒŠ‰‰"!!! ! !!!"!""#%%''())))*++,,--..//......00......,//15;DNYchopkd^UNGC@=:85331444444445566666544332//-,++)()''&&&$$##""$&'-15;:<;=?CEJOQTSSQQRTY]`adgjmprruwy{}€€€~{xtqmid`_YRLE><<<<<<<;::999999;>IYm~ˆŠ†€q\>*+/7?KVdnv}ƒ†‡‡…„‚yri]SLKGDB=<;>CGHJMNNPRSTSPOOOOOPQRSRSUVVV|£ž˜‹j6Ua{Œ}dRLfu}‚‰V9788I”˜ž£”…}wl`ULFI\c\ZGAFJr™•’”žžzZs‰yli\ZV]lqyvjb[TUVUU\hPC?;FNI;019G7--/48Kf`URP=>CJA=>JMFDMNI>ALVVVSWF:=A=:>AACEGEAMRQ^SOY\IERZZdY[TC:>9EG<0CHLMMKKLLMOQW[adgjmprrxyz|„‚~{xsokf^ZWSMF?::;;;;;;::99887777:=DRey…‰†‚yhL0*),09CMZfqy~‚„„„‚€vmbUKFA@CCCEHKNQTUUUVWXWUTSSQQRPTSQUTUTZ‹žœ—s,>A=8:;?AABDCCFJJIMRPHCPQBEJITS@?B@AKQfktyxunftxwdX]T^^cfea]UF;1((')07@KU\dfggfdbaa`_\XRMD;2,)))+-1578ADFHHGJLNQUZ[_cfknrrsw{|€~€}|ywqngc]XPMGB=;:::9::99998876666679>K_r€‰ˆ‚}nW;))')-4=IS]hoxƒ„„‚{uvxpaTIEDGIJMRUY[]^]YWXZ]ZXVVUTSRSTRSSV]b•˜˜•Œ}+9N`{ycNKDIPRIA=>>=<9n„Šnmjb_WPPSQJMPIJGEJvˆ„‚€ˆ’‰~[AIOMLJE>5?[ajte[NRWHJL=?:52892550-*'*1;?>TL=74774395358;;:98==;:BQDA?EMGCA=??BGIR^Z]dTV^\J=RZQ:@LGEPapvsswtssmpieJSAH]cegea[SG;1+&'*19BKT]dfggfcbaa_^[WRJC92-*+),.0468=CLW`gmrvxzzz{z{{z}ƒˆŒ‹‹Š‰‰‰ˆˆ‰‰ŠŠŠŠ‰†„€~|xvvuuuuuvuuoje]VQOQW^bfkqy}„‹ŽŽŒŠŠŠŠ‰‰‹‹Š‰†‚~vmeeejnswwtqoqponmmnmnpppnmnlpu~‡Ž‹Š‰‰‰!!!!   !"#$&&'(()**+,,,,.../////.....------,,.,,--047@FR[^cefeb`[XUSMHE=643222344445544433344100/-,++**('((&&%#%$$##$$$'+,-3789=??@CEFMRU[^`dgiklnquwzz}||zwvsojd]WPICA?>;;88889988887765555548:FUj{…‰†‚vdG-(&()),5>HU]gpv}|pe_f{xpeZONPVZbilnooplf_ZY[_^ZVVVTTRPOPPOOY™ —–”Œ318VnpSJPHA<;=@AAA@>7Er~sedaXZRKNNJKMLFGGJLYu‡…z{”ŽwF:ABCAA@>;6/2V€Œ‡vb`UQSI=D98849?=2.1/.-)8AECBIIYQjuMPipraIA@HIH=ThnpqrjZpfl^UR@@LU]chedb[SH:1+'(,2:CLU]dfggfbbaa_^\XRJB:3,++),.0158=ENW`iptvxz{{{zzzz}ƒ†Š‹‹‰‰ˆˆˆˆ‰‰ŠŠŠŠ‰†ƒzxvvvuuuuuvuuoje]VQOQW^bglrz‡ŽŽŒŠŠŠŠ‰‰‰Š‰‰†zrha`bgnsvvsqoooonoonmnpoonmnntzƒŠŽŽ‹Š‰‰‰!###!  "##$%%()()+++,--,..../.././/-----,,,,,,,-,,,.18@HQVZ^`abaa`]ZVPJE@:53322333344334333320/00.-+++*(''''%&$%##$$$##%'*,/37;>@?@CIMVZ]aiknnoopsvxwywvwutqnjc^XQJC<98;<::876567776666542333356=L`s‡ˆ…|pW9'%%'((,/7@KVajojdonSj€}th_]clv}€„‚~}}unghhie_YVSRPNLKLJIGL” š”ˆƒE.6Og\GIRJEB?@@@?>=26FQqnc]ZWVPOMIHFKLIIPRWZXix|†•ŽmC;<;899:9763/;f~††saSNICEKJ=26J;4,34/+*3?=DE953496JVI60,38:HSZckquxy{|||zyyy|€ƒ‰‹‹‹‰‰‡‡‡‡ˆˆŠŠŠŠˆ…ƒzxwvuuvvvvuusmgc\SONSY_cgmt|ƒ‰Ž‹‰‰‰‰ŠŠŠ‹Šˆ…€ymd]\`gotuusqonponnnnnnnmmnnqqv}…ŽŒ‹Š‰‰‰!#%%" ! !#$%%()()+++,--,......././/----,,,,**++,+,,,,16?FLOSX[]^`_`_\WSNHA:532233334433433321///0/-,,-+))'''&&$$#$%$$%%%'+059=ADFJKOU[^cfjnrrstvuwwwtsqomkif`\VPKB:756889::9867566655554331111237BVjy„ˆ…‚yeF,%'%')*06;DSbmhk]dxqxyxyrsrwƒ……ƒ†‚‚……ƒ}~xpg[QOLLGGECCCAzž•Š„ƒY.5A`VCHMJHEBCCBA@?^n^BXlg_XSRNOMKGHMMKMLTVWVXZo˜•„oUB8;:576421.--Dj|‰Œ€eTNJDAIJ/),/32<:,*19,'4?94343413400334331000149<:=CLD/).375BM;?UXQSV`mZ@DPb]KD@B[njnpmpvweEb¨’Kd]LBJQ`hhigb\SH:1+)*.3=GOY`dfggfdba`_^ZWQJB:322337;;<<@EMT\emswyy{||}{xxx{€ƒ‰‹ŒŒ‰‰‡‡‡‡ˆˆŠŠŠŠˆ…‚~{ywvtsvvvvuusmhbZQNOSY`dipw†‹ŽŒŠ‰‰‰‰ŠŠ‹‹Šˆƒ{rh^ZZ^ekrvusqopnonnnnnmmmmnnrrzƒ‰Œ‹‹‹ŠŠ#$&'%  !"#$%%&'()))***,..-.--..-.///.,,--,,+++++*,+,**+-/4;?DIMQVY\]_`b^]UNLB>7312233343333221111//..,,-**)''&&&%$#&%%%%)+/39;@EHNQVWX_fjmnquxzzyxwurpkkgeb^YVQMFA;74446578788665544444443211000023;Lbu‚ˆ‰‡rY=)%$%%(.1:GS[^Yacfoqqkiopu~‚„€~||xy}‡ŒŽ‘”•“ŽŠ‡{l[LA>>B><9;X˜‘Š‡ƒ€q4/=X[IKMKKTFDCE@>:_•jg`ecZSPLPOMGHJLMRMM[VKQTd–ƒl]QE;2247641.,*-He|ŒzeV]WQSOD::249?<007:+(+44644438:EOD=7542/--0.038:?>E>5/1:?@?85EURYW_mj[LNLZmS]etuls€twtyvUcODS[TAHWfgghhc[RG92-+,27@JSZaegggfecba_^[XTOJD@?BGHMOPPPSSX]ciqvzz{{{{zxyyy}ƒ†ŠŒ‹Š‰ˆ‡‡‡‡‰‰ŠŠŠŠ‡„‚}ywwuuuwwvvvvrlg_XNLOTY`elsx€‡‹ŽŒŠ‰‰‰‰ŠŠŠŠ‹†ypdYRU[cltwtspnmoonnnnlkkkknoot{„ŒŒŒŒŒ#$&'&" !"#$&&''()))***,..-------.///-,,--,,))++))**+***+,.36<@DGMPVX\_`a`_ZRKE=512233333333221111//..,,+**)''''&%$%$%(*+/49?DHLRVY\`deiptvwxyzyyxtpnigd`[WQNJHC>84311124455766543333333332111//-/015CXl~ˆŒŠ†|jQ;&%)45.0889759~–ˆ‚€Q=?Q_PMNLIJFCDB@>7Uš’‚vgVZ`YPMRNHEEJHKNNNYYSHJS’ˆq]IC43789952-***0J`|•–nadcsk\XSMD738DHI@7+/355326GLIKF67:864:A=8/+-7>AM]A2?CHBHG=GMQRU^gd]UVU`egIWorpmo„~hib[\[YMDI[`H;FOffijfa]SJ<2-,/38BKSZaeggggecdcddeeb_^YUXX[`ehillnnppstwz||{zzzxwxxz~„ŠŒ‹Š‰ˆ‡‡‡‡‰‰ŠŠŠŠ‡„~{ywuvvwwvvvvrlg_WMKNTYafnu{‚ˆŒŽŒŠ‰‰‰‰ŠŠŠŠŠ‡uk^SMQYdmsvsrpnoqonnnnlkkkkkmns|…‘ŒŒŒŒŒ!#&'&#  "#$%&''('(*+***,-/----....00.,,,*++*))**)))***)++++,.259=@EJQV[]__`a\UNG;52222223322210000....---+**(((''&&')-/38;BHMQT[_bfikpstwy{{zyxurolid`[XRLHD>;95211100000033333322222422222210.----.4C>;720)()**5Ld‚”“ˆ}wturcbXLKLB7;:75427<<:>??;:758DEK;.112>JOiqO/=RNLTQR_sZLRZgQ]R;=\fYOT`\^ZYb^VPQID@LJ@ENAEINUX]bhjmpstvxzz{yxutqojfa^XROIC=:623210/////00/02222112222243200000/.-,,-.16EZp‚Ž‹‰€yiO:CXWQRK]mpbeh]e`YdcRKE>DDEFGDI\mŽ•––‘ŒƒŠ„”™œš•ŽzYA751139AHPX_cfijlnpqsvz}€ƒ†‡…†††‡‰ŠŠ‹‹ŠŠŠŠŠŠ‹‹ŠŠŠŠŠ‡†{z|€‚‡‹‹‹ŠŠˆ‡‡ˆˆˆ‰ŠŠ‹‰…‚|zxwwwwxxwwutqke^SKINS\dkry€‡ŽŒŠ‰‰‹‹‹‹‹‹‡„|rcQIGPZeotutrpnnnnnnnmkigbdfhnv‹‘‹‹‹‹"$$" !!"#$&'''()*))++--------.....--,,,*++***))(()))*****))**++//48;511000..////////./1111112211132111/...-,++,./2BFDB=3&! !$'*+>Q`qƒ„tniu~tvpebcZ\G;<;961345369585D;;?;7;65-/9^pqeV]UTOODLSV]WSN\KEPHBZL^m]_`VM+.953674=MQcl_UGDOecihikkgc^VI?8354;FKTYbgnprvvz}ƒ‡‰ŽŒ‹‹‹Œ‹‹‹‹ŠŠ‹‹ŒŽŽŽŽŽ‹‰Š„€€ƒ„‰Œ‹ŠŠ‰ˆ‡‡‡‡ˆ‰ŠŠŠ‰…‚|{yxxwwwwwwutrkd\RIHMS\elrzƒˆŽ‘ŽŽŒŠˆ‰ŠŠ‹‹‹Š‡ƒym^MEGS]hqtusrpnnnnnnomkheccdhoxƒŒŽŒŒŽŽŒ  !!"#$&'''()*))++--------.....-,,,,*++***))))))**))**++****++-04:@GOU[beif]VJB8322122100011//....--,++*)*,148?CGNT[\bhkoruwyyxvusqplkigb^YTOKEA:62/--./--.-...../.../00000011000221110..,-,++++-/7E[rƒŒŠtq‚’•‘‹ƒ~{qXMQdZ`QJPele~|\QHEDDGFCAEFEGIV_’•š’’‘”—žœŽ”—ˆŒ‘‘’•…~|~…XhXJLKGC@A==Co“¡§›Œˆ}ukgdcaaMH]f`WIFEEC929H\hx_`XGDCEB?>8-"!#'*+,/:M\jw„‡‚xw}~‚zuj^OUH?CFZU<>31547Uid\V\[Q@;Cajh^\ZUFDL?AMXHVh^\NEK?G;?6?CH\ehxoNL=Aeffijkkgc^VLA:679?FOW`hotw|‚…ˆ‹’’’ŽŒ‹‹Š‹‹‹ŠŠ‹‹‹‹ŠŒŒŒŽŽŒ‹‰ˆ†ˆ‰ŒŒ‹Šˆ‡‡‡‡‡ˆ‰ŠŠŠ‰…‚|{yxxwwwwwwutpjaYPHGLS\elt{ƒˆŽŽŒŠŠ‹ŠŠ‹‹‹Š‡xiVIEJT^jrtusrpnnnnnnomkhebbchqy…ŒŒŽŽŒ!!!"#$%''()()**,,,-..,........-,+***++***))))((((((()(())*'))++-29?GNU_dgjd\RKB:55431200010..//.---.-,,.168?FJOT[_dilmpsuwvvtromihea`]YUPLFB;710.----.---.-----.-..//./0000//00121122/.++,+**))),0:Ndxƒ„z|‹‘Šƒ…Š†ƒ‚‚}zlX]nbVOe„‚w|n`OEAFFEHEFHJLJA?Sn‡•““˜šŽŠŒˆqŠŽ””Ž„{y{‘q…]HJKEA?;::J}—£§œekƒ~unlkjii•–’Œƒx\F>713Kekrnea]MHDDA??=2(%'),,,,-:L]gpv{†‘•„‡uoaQbVNWUXUX>5ECEBD16A:FOKFC0.;9,4JZSNP\UF=FKTLQILQUGEGJMVWHIXZM@EOIG8?C89DXny}ysR4=Qhgggijlhb^WNE=8;EPY_gkic[SMEA=876521010/.//----+-/1376/8Selsnga^UKA?A?@>81*.///-+*+:P[gjl“¡˜–˜†{kceebXTDVM<>B?IA0=;AF?GN:.?VD-5IT[JAJ<<>BMV_is}‚‡ŒŽ‘“”””””“‹ˆ„~{yy{~€‚ƒ†‡†‡‡†‡‡‡‡‰‰ŒŒŽ’““‘ŒŠˆ‡††‡‡‰Š‹‹Šˆ…‚|{zxwwwwwwwusog_UJFEKR[clw…ŠŒŽ‹Š‰Š‹‹‹‹‹Š†€ufSIJR[emrssqppommllonmjgd``cis~ˆŒŒŽŽŒ!!""$$%&&')*(**,,,,-,-........,+++++**))))))))''((''''''))())()))),19@IR\dikib\WQKGC>=;9533100...-,-//47:?GMTZ`cgmprrtsqqljhea\XTOLHEA=9510.-.++,+++++++++,,--,,,,------.........//.//..,+**)))))(*+5FZf|~†ƒqbphioWXeX\ggd__qƒ’„tlgbZMDFJPJEECDGIKLLOx˜’‘’—ž—ŠˆŽŽ…u}ˆy‹Œ{wv‰ŽqDLKIIC?>7ZŸ¥ª¥Š+4Mr‚}y{‹›¡ ”Ž†t]H;4/9MYbghc[VTOA>??AB>813310/-+*+5L_hdk˜¢¤¤¤¢˜~`VhqfUOaVE2<:DORK>/&4GWE2;GUcVCED5;F[S<737:EDHB1:AFab8CHPV]agjmptrpnljfb^[USNKFA=9311-,,--,++*++++++++++,,,,++++,,,,,,--.......//...--,+**))))))(+5Mpzyuqmii^\vso}wtfZ]bTKTbuŒ†uimmgVDAIHJGDEEFFHILMu–Ž‹—ž•‹Œ‹ŽŽ‹Œ’‡Ž‰zvv„‹‹‹zKGHFFEA>5c“¡§§¥šW2=?FI>Aj‡š™’…€ufZS;11>LXZ]^`XTPMC=>BDGB?966431.-)*+5Rb_]n€Š“ŸŸ›‹xixsh`[WZKGV`=94;AGFH89>H9DHNTY_ehkmnonmjfa^XUPLJE>;730/.-,,++,-++**++++++++++++++++++++,,,,,,,,--------,,,,,+**))))((+0a}oeivodi^f{zym]f`^|ucdlt|‹Œ“Œ…yudUKLLGDEDFFJMMJi”ŒŒ“›‰[r…‰‡Œ‘”‘†{yutŠ‘‘‹\DIHHCA<:rš£¨©¦št0<;974/P~‡‚yqeXPG5:BLRRTVXUOKHD<9AJKFD@;97651.+.17@V^[bkt~„ˆˆˆ‰…ƒ‚~uiUPUTUddLB7522;>7gZ3-0>NA6;<;;:<>BJOWagosvxzz{}€€€ƒ…‡ˆˆ‰Œˆ‡‡ˆ‰Š‹‹Š‹ˆ†~}{ywwxxwwwvvqkdZQFABHR\hqy‰ŽŒ‹Š‰Š‹‹‹ŒŒ‹Š‡€vj^YZ_gnuvtrpppnnnnnnnmjfdbabn{†’ŽŽŽŒ‹ !""$%&&'&'()**,,,--.--,-......-,++******)(((((((''((''&&&&((((&&&(**+/3:731//-++,,,,++-,++**++++++++++++++++++++,,,,,,,,,,------,,,,,++*))))*-3G€zqzwtwiikgju`MK[\egebd_g{}‡€“›££Š†xUCHHEGHHIHIIG`•ŠŒ™ˆ/9OcqˆŒŽŒ„ywuw‘šš˜•h@GFEA@=Ey™¢¥ª¦19;;;949fy{zvqg_XPLGGHIJNPNPRNJIF=4@NNKECA?:7642247:7CW]_dlpqsusvwrjhjfVIMUZQVXN12,9E?=;<<<>?BFMV]fmsvwwvwy||~€‚…†††‹ŒŒ‹ˆˆˆˆŠ‹‹ŠŠ‡†~}{ywxxxwwvutnjc[PE@CHQ\hq{ƒ‰ŽŒ‹Š‰Š‹‹‹ŒŒ‹Š‡€vj_[]dkquwupppnnnnnnnnmjfdbaep~ˆ‘’ŽŽŽŽŒ!##$$$&%&('()++,,,,,-........---,,+****))))))((((''(((('&((''((''''(*)+05=ELRX^acdcedaa^][[XTQLHEECDIMPV[_bdgihgda^[SOJE@:61--,,+++++,,,,,,,,,,++****++**************++,,,,--,,----,,++**++,,)+**.7Qdxdf‚…€ppffibOMLNMZgm_TOL^hpj€‰‘¥¨¡œ’xU@IFHIGIJHHHTŽŠ‹‹”Œ@;;>V~˜Ÿ£¦¦ž}.9=<<:54Jgkljf_VTPNKJKJLNONNNNMKGA5APRPIFFE?<96:;;8799LX]`ejjptvxpZGFHJIDDGQJFHG>;C26?ACIRYbhptwvvvwyzz{|~ƒ„„„ˆ‰‹‹‹Œ‹‹‰‰‰‹ŠŠŠ‰ˆ…~|zxxxxxvvwvsojcXNE?BHR]kt}ƒŠŽŽŒŠ‰ŠŠ‹Œ‹‹Š‰†wh__bjqvwwtrqpnnnonnoomjfeacfr€‰‘ŽŽŽŽ !"#$$$&'(('()+,,,,,,-........---,,+****))))))((((''((((((((''((''''''('*,4;@GNRVY\^^_aa`a__^\ZVSQPPPTWZ^_acgffb^[WSLGA;50-++**)+*+,++,,,,,,,,,,++****++**************++,,,,--,,,,,,++++****++)+,+0Nsysly…ˆŠ„}|vsyydUP[[K`gdb][V`cd~œž¯³­«¤Ÿ—‚G@FILMMLIDBG‰†‹‘ŽD4>>?E\y††„ztwy{—œ›œ™ŠN?EGD@?h“››Ÿ£ž}.68;:8617S__\][TMIIHFJNNNMMMLKLLLF@JRSVGDHIFD@<=BFB@>>DTXZZ[__ehnpne[TLB;<B01;CSL8@769?@@EKV^dlsyxyywxyyyyz{}€‚‚ƒ„ƒ…†‰ŠŒ‰‰‰‰ŠŠ‹Š‰ˆ…~|zxwwxxvvvusniaWMD?AHT_lv€‡ŒŽŽŒ‹Š‰ŠŠ‹Œ‹‹Š‰„}rg``hmuwyxvrqpnnnonnnnlihgdejv‚‹‘ŽŽŽ!"!"#$%'&'())**+,,,,,-......--..-,,+****))((((((((''((((((((''''''''(())'',07@@@Srƒ‚uowx|“˜˜—–“aCHHEADy•›—˜ ›q-6899:973>QVUWVRNJIIGGJMMNLMNJJHIKN\e`UEEEGLLJFDSbSCCB=OWUTPKGIOSY[Z\XK::ILROKIRXK=:.5?9:8>B:86.(*,44(9757?H;<<==>?@BEOW^hpv{}|{xxzyyyz|~ƒ„ƒ‚†‡ˆ‰ˆŒŒ‹‰‰ŒŒŠ‰‡„~{zxwwxxvvvuqmg^ULC?AIUamxˆŽ‹Š‰‰‰Š‹ŒŠŠŠˆƒyoeacjouxzxsqqponnoomnmkjihefmy†Œ‘ŽŽ "#"##$%'&'())*+,,-,,,-......--..-,,+****))((((((((''((((((((''''''''''((''().26:@CGILMSUWX\_aadedcaaa`adeca`]YRMIC;2-*)()'****++++++,,,,,,,,,,,,,,+++++++,++**********+,------,,++++++++*))))))+,?y‰j|{|y€Š€ur|}ypvy~vj\QXkgSGdwyzy‚¨­ª³¹´·¹±›h7IHGFFCCC=WŽ‰}„‹’S4@?=@BNpƒtqxz~””——–xHHLFBG˜“Œ–Ÿœb,699::85219?DE95535/$2<=CA6;CD>:878ENMEMho}|xupnkhgghiorquuuspnlllnopsv{€ƒ‡‘’“”’’ŽŒ‰„|ungc`a_^^^_^\[YTMG?<:;<<=>@DIQ[ajv|€~{yyzzz{{€ƒ‡…ƒ‚…††ˆ‰ŠŠŠ‹‹‹‹‰‡„€}{yxwwxxvvusplf]TJA>BJUcp{‚‰ŽŠŠ‰‰‰Š‹ŒŠŠ‰‡‚xmc^bipuxyvsppoonlmnnomkjhgfgp}‡ŽŽŽ‹‹‹ !!#$$$%%&'(())*+++,--......//-----,,+****))('''((((''((((((((((((''''&&&(''((()-036:=AAGKLORVW\^bbcccccaaa_[WQMHA;3.)'(())((*+*++**++,---------------,+++,,********+++++,----,,--++++++***((()))*7k‘qXowz‚††~€€„|nvspwphcVc}oG4Xv|z…œ£®·º»»»¹­ŠBBELJDBCD>F~Œ‚}…‘ˆ=8?>>ECa‚wsy~…ˆˆ“––‰ONIFAJƒšž›š›š^+677999730-8GNOOMKKIGGFKKMKMMJHFCFSMKNRSIIIJQVW[aiYLJB9EVSJB>@EH@:2//--07CE7223:;>C>DMK?=9BH96864479AC943@BJOXcnmliggiililtvuttqrrtvvx|€ƒˆŠ’”“•”‘Œ‰„~umgb^[[]]^^^]\\ZXRMH@;:::;==@EIS[foy€……‚€~}{yzz{{}ƒ…ˆ†…‚‚ƒƒ†ˆ‹‹Œ‹ŠŒ‹‰‡„€{ywvwwvwvuuupke\RG@?CKYgs}„ŠŽŽŒŠŠ‰‰‰ŠŠŠŠŠ‰‡ui]]ajouxxusqppnmlmmoonlljigjs~ˆ‘ŒŽŒŒŽŽŽ !!#$$$%%&'(())*++,,-.......//-----,,+****))('''((((''(((((((())((''''&&&''''''(+*,.0156;?@CGLPTZ]^_````]\ZVQLGA:3.+)'()((())*++**++,,,---------------,+++,,++******+++++,----,,--,,,,++***())))*,GohWYqu|‰†‡ƒ‹Œƒ}‚€{qulpmXEdsjRG]ft‚˜¨µ»»¼»ºµž]7>AEBCC?@Cd‘ˆ~~Š—f1@@>CHq€xxz~{zŒ•—[@EGHJ‚›œžšš”Q07:9:9863/.0;EMONMKIGGGEKMKKJHEFCDXQLNSTHIIKMU_rwod]KB=M[RHGMQRPH?30/00022//0325;;F;;EB938>A=7979:>>82363BO=6589:<;9=CB9:BEHR]aagihjootyzyyxvxz|~€ƒ‡‰ŒŽ’“•““ˆƒ{skc^]\[XZ\\\]]\\\YVRKE=;:9:;<<>EJR_gq|‚‡‡…ƒ€|{z{||ƒ…‰Šˆ†ƒƒ†‰‹ŒŒ‹ˆ†‚~zxvvwwvvvuttoid[OF@?EMZft~…‹ŽŠ‰‰‰‰‰ŠŠŠŠŠˆ„|re\Y^gnswvrqpppnmlmmonnmljihmvŒ‘Œ‹’“”’‹ !""$$%%&&''(****++*-............---,,+**))))((((((((((((((((''''''((''&&''''''&&((((++*-1468>BGLOQUVVYYWURNKE@83))''((')(**)**,,,+--,-......----------,,++,,,,+*++++++,,--------,-,,..06+)++**)*,5Y^cw^k~ˆ€xˆ‹||{{qmvoxu\AVp}ucY]b{Š‘“£¯º¼»»º¸§p06=BADEDDCNƒ†~}‚’ŽH1?BKm~|{€€†‰‰‡‰Ž”•e?CCEP—œœŸ›™’@3;;:::9863/.0=JLMLLLJJGIKMIDCAEDEEX[MOTSBHIKMN[mmlgfI?D[^RPVWWVK@82.*)-.../../147984852.1;A>;;69:;<981::144728BIG>75>CDFC>9?PX[Y\dmqtvy}~}}}~ƒ„‡ŠŒŒŒŽŽ’”’Œ‡‚{pg]WXY[\\]]\\\\\\[[WUPJD<:99:;<<@GKS]kv…‰‰‰†ƒ}|z{|{€„†Š‹Š†ƒ€…„‡ŒŒ‹Œ‰…‚}zwuuvvvvvvtrmgaYOE?=EQ^kv‚†‹ŽŽŠ‰‰‡‡ˆ‰ŠŠŠŠˆ‚xn_VU[blsvvtqponmkklmoppomkjjoz…Ž‘‘Ž‘‹‰ !""$$%%&&''())**++*-............---,,+**))))((((((((((((((((((''''((''&&''''''&&(((())((+,.257:>BDIJJMNKHGC@:2,)((''(('))*+***,,----,-......----------,,,,,,,,,+,,++++,,------------..4S@(++**)*2HRnŠ‡Šqim~“……ˆƒ{{€vprmM?UBbxx|nX_j€Œ˜Ÿ¨²»»»ºº¦t47<Lut€‡rX_oƒž¥®´¹»º¹£i17:<>AA@@FCDm‘†}{}€Š‰;2gƒ€}€…‡‡‰ŠŽŽƒVKDDK}’–˜›šš“P59:;<;;:941.,/@KPPLJHHLLMPQQQPMG:01aVMQM4=CFGJV]ba^^bbddddc`ZRE<977.'')**+++,,.0019=AHKFFOOKEBFORF?961433453538CF=@A9:@GKIG?7QaV^tƒ†Š‹ŠŒŒŒŒ‹Š‰ˆ‡‰‰‰‡†…wndVMFFLSVYYYZZZ[[[[[[ZZWTOIA::989;<=AJX^alw€†Š‹‹‰„€}zzy{}„‡Š‹‹ˆ†‚‚ƒ‡‰ŒŒ‹†„|xvttuuuuuutqlf]UI@:=EQ_mz…‹ŽŽŒŠˆˆ‡‡‡ˆŠŠŠ‰…~wi\QSZenuwvtrqonlkklmnoppnmkmu‚ˆ’•’Ž‹‰†‡‡…ˆˆ !!!""$$%$%'((*++**++........//------,+++**)())''''((((((((((''''''''((((''''''''''(((())))***)*,-..13677762/,)((((''(())))*+++--,,--..//../0//////-----------,,,,,,,,,,,,,------..---.-,-,,,**++-8OQy‡€Tb[OJMi›™‰†‘Ž|ty|z€yoH?^xxu„†nScq˜¦©¬°·ºµšN/9:>>=@A>ABBK{Œƒ}zx‹@wƒ}{‡‡‚†‹Ž‘‘‘’mNLEI|—–——–’U59;;<;;9743.-,9IOSVWWWWVSQY[XOB3,(,]^OPJ,/49?T[`e_`jlmhiiij`TF?::992)(,*++***)*-..2=IKMPNXOILHAFMQVNB/264//1460/8K@;85NTIA99@DGA9=Q[Tk‹‘Ž‹Š‰††„€~}}}|yxoj`VLFDGKQTXXWXXXYYYYZZZZVRLFA::989;FRan{†‹ŽŽ‹‰ˆˆ‡‡‡ˆŠŠŠ‰…}sfXPS[gqvwusrponlklmmnpqppnmqyŒ”—”‹‹‡†‡‡ˆŠŠ !!!"#$#$%&('()++++,,..////////----,,-,+***))))((((((((((((''''&&''''((((''''''''''(((((()))*))******,,----+*)('&&((((()))**+,,,-....//./001111/////-..//..--------,,,,,,,,----..........--++++,-0JKP…bMHEJSh®©¡›— ˜Œ„vvopkoO:;Hb„Š„~iV\yŸ¥§§©³©|729<=<?BQƒŠ‚{{v€wƒ|w|ƒ†|„ŠŒŒ‘’“‘{ROGIx•“””“`7<;;<==:841/+-8Xmvuoja]YV_eb\K4&%%'TcLLH-..0T`fbaexqpigjkml\IB?<===6/12210.)(%'+../9ALOIV`QJF?GKLEEJF1#093-./0,/?Mx_CDIPYboy‚†‰ŒŠˆƒ€}zxzz}ƒ…‰Œ‹Š‰ˆ„ƒ€‚‚…ˆ‹‹‹†ƒ}xxvstuuutttspjd[SG?=>HTcp‡ŒŽ‹‰ˆ†††‡ˆ‰ŠŠŠˆƒ{qcWQW`jrvvurrponmmmmmnqqqmmot~Ž–”ŽŒ‰‰ŠŠŒŒŒŒ‹Š !!!!"#$$$%%&'())))++,,--..////..----,,,+****))))((((((((((((((''&')(''((((''''''''''(((((((()*))**))))))*****))(()(((((()))*+,,,-.//////./0011111/00//..//..--------,,......--....//......--,,++-.5VIh‰KGEXr†—¡§­­¬§¢›€†‹}okmsr]??83Gmw‚~Y]p€’ž¤¡ŸŠO4;;:::8;:BMOd‰…{ywy„Ž‰|x~…€~ˆˆŒ’‹‰ƒSIHOp‹‘””’‘Œj:<;=<=<85400..[€„}soi[WUTZ]]XE,&)(*HlULJ/,/Tkkkgl{yojhegkme\RJEA@B?<9768630*##$(,-./8AFQ[`RKLTQZc_WQI5!+<<1.00.+*CMYer‡ŒŽ‹‰ˆ‡‡†‡ˆ‰ŠŠŠˆƒzobVSX`jsywurqponmmmmmnpqpomm}”˜“‰†ˆ‰ŒŒŽ‹‹ŒŒ !!""""#$%%%&&'((')***+,,,,....//.-----,+*)**(())))))))))))(((((()*,)&(''(((((('''(''''((((((()()))))****))())))((*))(())(())*+,....//.//1111111111221111/////.------..----,-...-............------./?]nnnG>aŽšŸ¨¦¡££«ªª©Žwx|zb[KHCDJqy}o^jwˆ“œ™’ŠlD:=:<=ACC?=<>BHJRmˆxuv|…Šzv{y…t}‘“‘’hEIQg„“ŽˆŽ‹u<9:<=;:944/.+K{~zvrpe_XQJHCDGB400-.AlZML01Ejmrox‚|vnifchlomh^XTPOIA=<9<986,%"#$&*..-.2D^eh\PVXdlinlidZG8846/--49=DB8=WoŠ‡{xsokhgfghjllmoic\SKGFIMRSXXXXXYZZZZ[Y[XUPJD>:989:;==BIJOJDB??HQRq‰{wuw|‡‚sqy{wkn‡wbs˜šš•{ECM^†‘Ž„wŒŒz@68;;962110,8q}ywuuk`VSTMEA:;;<9644>hZPP39fjqtˆƒ}zroqslcftxofe_XQJE@>@=<7*$$$$&--.,),Gdeg^[[dqy…†‚}{~wa<04.+6FFD?CLL^`\s|}{[FGLFG@7?@=A[s†ypnjhgggghjlnmmkf]UNGHKORUVVXXXXYZZZZZZXUPJD>:989:;D>?@?@@IEDM>:AFOIx‡}vsyz‚ƒuttpWa}wm{”•˜˜ŽUBEdš—‹r…~A77998432/0.G{‹yptpom`ps[:F@:;;;:8=]\PSA^jnx‰Šƒ~zz}|n__^iwtmeaYXUQKGDB>2'#"$#*1..+*+K^_aT\Ya`m{usyzzrjT504>?=978>Qlkb]]csi[NJFOPI?=@>A[x‚rlkihgffhjlmmnkc\VNLIKOSTVVWWXXYYYYZZYWUPHB=:87:::=CIPXakv~‚†ŠŠ‡ƒ{vvwy}…‰‹‹Š‰‡ˆˆ†‡……‚€ƒ‚ƒƒ„ƒzwsrrsrrrqppme^XNC?CJValy„‹ŽŽŒ‰‡†††‡ˆ‰ŠŠŠ†~tg\TW^gqvvtromlmmmmmmmomo‚“•ŽŒ‹ŠŠ‰ˆˆ‹‹‹‹ŽŽ !!""####"#%%&&'((())**+,,---....//.---,,,+****))))))))))))))((((((((''''''(((((((())((''(((())))))))**))))))))))))))()++**++,,..-///00111122222212422222110/////........-----.......////.......---,,./=TGCDTt›³²£¦¨¢¤¥£ª§¥ž—˜¢¡•“Šqdcdimgfkuyt|`kv}z|j<,29?>?@???BFHJA<>AJMLy‡}uvz†ˆxl[V\w€†‘Œ‹“cCCOŒ ¡–^‚?78977744566M”‰~qŸ—•†~€P;DD<;:<<;S\NWeqs‰ˆ„€ƒƒ…xg`_^`bb_ab]XUSROMHF<)$#%$%.2/.++2N\_WGQ[]]amzywwurhijZJF>.6;5+*4T‚Š‹‰~g]SNPVNLM>8=>EZƒ~sigefhfijkmmmjf_XPMKMQSTVVVVXXYYYYZZXVSNGA=::9::;>DJQYcmw~„ˆ‰‰‡ƒ}yvvwy}…‰‹‹Š‰‡ˆˆ††„…€ƒƒ‚{utssttssrqpme^VLC?DKXcn{…ŒŽŽŒ‰‡†††‡ˆ‰ŠŠˆ„|rfZSY`kswvtpponlmnnllmmrŠ–’ŒŒŠ‰ˆŠ‹‰‡‡‡‰ŽŽŽŒ !"""""##$$$$''('(((***++,,--......--..,,++,+++**))))**))))))))))(((((((((('&((((''(((((('''(('(())))))))))))))******)(****,-,--../00/.002011333322243333331100000/..//..--.----..-................---+//>FFCNq„¦²±˜š­®±²¶·¯¬£™’˜–~vi[ojdehnvz‚‚{jfr{{x_1,01:CDDA>=<9CJH::=HMCR†„|y||ƒŽ‰kHQTk€’˜“‹ˆ~mJCDv™ ¡Œs“v?:;;<>?@>CB2V  ‡}“—˜„opkU27?D?;<=>DLQZeoy~„‡‡†…|wvvx{}†Š‹‹Š‰‡ˆ††…†…‚€€€‚ƒ‚|wstrrrsssppmd]TJCAENZer}ˆŒŠ‰ˆ‡‡ˆˆ‰‹‹‡ƒ|qcXSZbksuuspnnmnlmmkmlv˜’‰ŠŠ‹Š‹ˆ†…†ŠŒŽŽŽŒ !"""""##%%%%''(((((***++,,--......----,,++,+++**))))**))))))))))(((((((((('&((((((()(((('''(('(())))))))))))))******+*****----..//00/0012322333355334433331100000/////.......-...-................---+.09AEOp†°µµžŒª¯³µ¹¸³­ ˜’•š—”{zjbi`acst|€†‘~RbpxuX,(+17<@AB@>><;AA<=;AKH9ZŠ†}{{€†‹EIV_xŠ“™š—•‰tqQDEWˆ™–v‚JBBCDFDC@==92n©¥¢›~ˆ…|yg[IA7548?@;<:Cahkqv„…‡ˆŒ’•€e`ghjjhdd^M;=LRPPQPMI>*""%'*056425EQPRF@FHP[\_[bnhfcXQRYciK301./2/,-8o„€€rv„††…xM=FB?CELbsxmjgighjlnnojg`YROMNQTUVVVVVXZZ[\\ZYVSMF@<:;;:<@CHMT\gpz„‡‡†ƒ|wwwx{}†Š‹‹‰ˆ‡ˆ††‡†…‚€€€‚‚€}xtsssrttsqpmd]SGAAGP^ju€‰ŽŒŠ‰‰ˆ‡ˆˆ‰ŠŠ‡ƒzn`UV\dmsuuspnmlmllkngw—‘ŒŠŠ‹‹ŠŠ‡„‡ŠŽŽŽŽ‹"!""!!""######$$$$''''(((***++,,--......--,,,,,,,+++*)))))**))))****))))(((((((('&''''(())((((''''''(())))))))))))))******+*++,.,--..//01111112332443344444433332211000000//..........................----,,,/C?LRQRSRJ7&""'+,3659:@KROD7CHEEGEPPS^cgghkllmolga]TOMNRUVVVUWYYYZ\]\\ZURLFA>===<>BGLPW`irz€„‡‡†ƒ~yvxxy|}†‰‹‹ˆ‡†‡†††‡…€€€}xtqsrsttrqljd]QE?BHT`kx‚ŠŽ‹Š‰ˆˆˆ‰‰ˆ‰‰†‚xm^VU]fotusqpnnmjikjh~–—‘Š‹ŒŒ‰‰‰‰‰‹ŽŒ‰"!!!""""######$$$$''''(((***++,,--......--,,,,,,,+++*)))))**))))****))))(((((((('&''''''''((((''''''(())))))))))))))******+*++,---..//001111123234444444554433332211000000//....................--..------,,,/?Qo†‰˜®¦¥²p~¦Ÿš¤¬© ™Ž…“Šˆzsljckqut}…ŒŸ©­nJejhL*+&,.>MKJBAA?<;>;;=>DHFFwˆ~{{}ƒuJRv‡…„€‡ŽyrNFCKsrLE@?=>=<::5GdX©›—ƒ~‚€f[PMMC6643126>IHBE`osx€ˆ–“sqkkjklpspjX9./3BOQRRPA-%$'+-0;?BABFGHLB89>HOQNVYYXOC3,.1334BOO:7618;6N=LE>HHKMNQUV_dilnnnkgb_VQOOSVVVVUWYYYZ\]]]YWRLFA@?>?@BEJNR[bkt{€‚……„}yvwwy|ƒ‡ŠŠŠˆ‡†‡††‡‡…„€~€€€~yssrstsssrnhaZQE?BKXcny„ŠŽ‹‰ˆˆˆˆ‰Š‰ˆˆ…wl\SW_hptusqpnlkmjil‚––‹‹‰‰‰Š‹‘Œ‰""####"#%%$$$$$%%%''''(((***++,,----....--,,--,,,+++*)))******))**)))))((((('''''&(('''''((((('''&''(())))))))***********+***,---..//0010011132455665444665444332222111000//...........-.---......--,,,,----,0Ce€Ž‹¡©š«—pr‘¥®®¬¬§ž˜„‡“Ž‚€ƒ}pifnrs{ƒ„“©®ŠG_^]G..3.-6G:>FCB@<>=<<<=BKLHzrzŠ‡€|{€…ŠlMc{€yndk}‚_EDBLcpTFEA>===;:814Zj¥“€yvmeWLCBA74334:CKSSSEGOUX\s…‡|€€zqmlopvwqiS4-005HRTQH8-*+,.2>IMNOONGEF:8:>BJOOTSI@-+A`z›£¦¥¥£”~k]WF2=@5?Yosyzj\ayŒŽ‡†JCGDMZeq}…‹ŽŒŠ‰‰‡ˆˆ‰‰ˆ‡„~tgZSW_irtuspollkljj„—•‘ŽŒŽŒŒ‰‰‰‘‘Œ‰#######$%%$$%%%&&&''''(((***++,,----------,,,,,,,+++*)))******)))))))))((((('''''&(('''''((((('')'''(())))))))***********+***,--..//00111133333355666655665444332222111000////../......-.-,,------,,++,,----,0Jx‘™“’‘™£œˆˆ©²¶ª˜˜Ž„„Šs{xtvsiqw|„ŒŒ—©°£RUWV@*+AC20037DA@@=?<==;;?DJG{vjŽ†|z†ˆ`Rfmc^^^k‚‰~H?CAABGHDB?=>;95225Td‚¥Ÿ€|vi_UTLD?834;?GRVY[XYYMIGIOU]izŠŒ†xihry~zmF.,0317IOI<;;9;@EKH|~sj„Šƒ}wzˆƒZ_d[\]\ew‚‡o?@A@?BFDB>=;98400;Ub|›|pskXUPE997ZzŽ¤»¾¾¿¾¾¾¾½º·´°ª}9(024?Mdtw‚ƒ„||‰Š‡LHHOSPZtkjie`]YYZ]^]YWUUUUUUUVWWXZZ[\\[YUSMIEDDEEGJNRV[bhpu}€ƒ„‚{wuvvx{~‚†‹Œ‹Šˆ‡‡ˆ††‡‡††„}{{}}~{zvssttssqojd^UKCBIT_jw‚‡ŒŒˆ‰‡‡ˆˆˆˆˆ‡‚{reWS[bkrvvqolkihk‰—“‹ŠŒŽ‹Š‰‰†ˆ‹‘‘’‘ŒŽŒˆ&$#$%%$%&&%%%&%&''((((())+++,,,,.......---,,,,++++++**********))****))))((((''''&&&&&&&&''''''''&&(((((()))))))*****++++++,,,,--////1122334334444566666656665544332210100/0//./.8/,,------,,,,,,,,,,,,,,--,++2g„ œ™™‘‘Œš£•“¦¤¥¡£’Ž“Œ‰‹”‚yy|~ywtv|{a3›ˆLPKE/'(,.,,+.16;FA?C=?<<;64@MONH6.,=HWvŒ¦¹¾¾¿¾¾¾¾¾¼º·³°®¥„8.4348Uhg~z}~‚‡Œ{hGDNNNOyxkhjhgojd_][\YWXYWUVVUVXYZ[\]]\ZXURNIGGGHHJNRVZ^dlrw}€„ƒ}zuuvxz|ƒ‡‹Œ‹Šˆ‡‡ˆ‡‡‡‡†††‚€}|}~~}}{wssttssqojd]TKCCJVbkyƒŠŒŽŽ‹Š‰‡‡‡ˆˆˆˆˆ‡‚zpcYT[dmtuspnmkijŒ™’Š‹‹‹‹‹Šˆ„„ˆ““‘ŽŒˆ(&$%%%%%&&&&''''(())))*+**++,,,,,,,,---,++,,,,++++************))))))(())((((''''&&&&&&&&''''''''&&(((((()))))))***++++,,,,,,----//0002223333344455565566665555553322110//...../MK0----,,,,,,++++++++++++,+,*+5h{Œ ¢¡Ÿ›š›‰Ž¢… §¦¢œ‰’Œ‰Ž„xv{}|yvokojW;]dNMPN@+')+-/;1.027JD==>@>?;;?@Djxsnu‹€||‚…^XWTT[jz}xx‡{M>@ABCGH=:8431.0XVou{|‚„{c†n>HGOPo~tmlhdedbb_[WUVWXXVVVUUWXY^`][ZZYXSNJIHHJKMQRV\`fmuz„{vtuvwy}…‰‹ŒŠ‰‡†‡‡‡†‡††……„ƒ€}}{}~}zwusssssqnicZPIDGO[cq{„ŠŒŽŽ‹‰‰ˆˆˆˆˆ‡ˆ‡†€xmbVV]fnuwsqolml‹—“Ž‹‰ŠŠŠŠ‡†„‚~‹““‘ŒŒŽ‘Ž‹‡'&$%%%''&&&&''''(()))*++**++,,,,,,,,---,++,,,,++++************))))))(())((((''''&&%%%%%%&&&&&'''&&(((((()))))))***++++,,,,,,----//0002223333344455565566665566553322110//.....,),.------,,,,++++++++++++,++++6f{‘££›™š›–‘…¢™œ§žšš•“‹‡Œƒzvuyyxrme\WQJPNNMPK<+'*),0.+,344459>>@@?=;=A?_‰vnln…“‡~{}ƒ‹pQPU^dq|xsl}‰kB?CBCFD=:7521/0;MZtz~xsh\YSF>?:5;PWY_dfqslf]XWVLLQYao‡™ —›žž›Œa130146674421036I\eow|ŠŒl:7:;<<>@=70/7?Ef—¶¾½¾¾¾¾¾¾¾»»º¶³­§ž›–U0LW@0CBaYm€}€xFM~[?EKNV`^URJIIJLMRQMKJOSWWUVTWYVT[dee^Y[_]VPKIJIKLORUY^chov|ƒ~ztsuvx{}…‰ŒŒŠ‰‡†‡‡‡†‡††…†…ƒƒ€~~~}zxutssssqnicZPFFHP\es}†ŒŒŽŽ‹‰‰ˆ‡‡ˆˆ‡‡†„vl_VX_gpuvsrpknˆ–“Ž‹‹‰Š‹‡…„ƒ~t~Š‘‘ŒŒŒŒŽ‘ŽŠ†''&&''''''''((''(((()***)*++,,,,,,,,,,,,++++***,++**********))))))(((())((((''&&&&&&&&&&%%%%&'''&&''(((()))))))*))**+++,,,,,----.0/102223433444555665566665555543311110///.-..-..----,,,,,,,++*)**********)*)6j†—Ÿš—•š’‘‰›—™›˜–‹ˆŽ“ŒŒ‹„yz{}wpbgaPEKQONOQLJ<'&()+-*)*,+/31619>>A>=?>?P‡uonlrŒƒ|~|„‚UASdpyzreY`xy\CDB?DH>:74310/:L`ƒŽ…maVTSFBB>8<9;Bz‚wwuwƒ’Š~€ˆqXahv{wn\G>HZeWECEC@@9731/0-;I`Š›ŒcGSTVHDGCCHRX_hjn€‘‰ƒwf^elgmrutw‚‡ŒŽ‰„~wpV=:9898543258:Yttuxƒ‡‰b675443020..266EqŸ´»»½¾¼¼¸´¶ºº¹³®¤™‘Œ†ZRLUR14MO=h€€||xnTKLLMNPMONLDLXUMECCEGVRQ[YSYWceemkhhfmoh^QMLNNORUX\bhnsz~€‚‚~zvtstwx|ƒ‡‹Œ‹‰ˆ‡†‡‡†††††‡†‡†„‚~~~}yusrrpnomgaZPIHMW`kx†ŠŽŽŠ‰‡†††‡‡††„{si\W\ckrwwqplƒ–’Ž‹Š‹Š‰‡„…|ddtƒ‘ŒŒŒŒŽ’Œ‰†((((((((''(((((()))))****)*,,,,,+*++++*****)(+*)))**))))))))))))((((''''''''&&&&%%%%%%%%%%%%&&&'&&&'((''))))))))))**++++,-,,..//./1011112344554555666666555555432211000/...---------,,,,++++***************0@>h˜“–š‘–•Œ—›š–‹†‡‰ƒ„……Š}yqqpmlgcTJKKMNQNJH7'((())+(*.,+,7P=83<:??=<9;l‚‚ˆ’„~‚„‚YitxzxjWD97:KaTB?CCCA841/..LP^c]_`VRHZhhlke[SUgRGX_Zcia[gmlc`Xkl^SOOOPPRUVZ^bgnuz}‚}yvusvxx|ƒ‡Š‹Š‰ˆ‡‡‡‡††††††‡ˆ‡…ƒ€€~}yusrrpomjg_YPIJQ[epy‚‡ŠŽ‹Š‰ˆ‡‡†‡‡‡‡„€zqf\X^fnuvwrp~–“‹ŠŠ‹‰…†ƒƒ{d^iwƒŽŽŽ‘‘Œ‰ˆ((((((((''(((((()))))****)*,,,,,*+++++*****)(+*)))))))))))))))))((((''''''''&&&&%%%%%%%%%%%%&&&&&&&'((''(())()))))**++++,-,-,,--./1011112333555555666666555555332211000/..---,--,,,,,,,,++++***************,.0a†’”–Ž•••—››˜–ˆ‡€Žƒƒ‹ˆ‚‹…wywvrme^UNLIKNNNKI:)((())**+---,/3+0VDB=?@>;=\“Œ‰††‹—€}‚‡qrwwytgTC8548LZODAFDD=51//.=:97779:Mv„€€„‡…€g<75631/.-/39;CU_fks{‹••Š}w€‘š›—‹‡‡†~ZDDIKQP<BGMMKHH>-(')*(()*-.,+(),.:CP;?=><>J‘Š‡†Œ“˜•ˆ~‚…duxvpcO>777:EV\O?>B?@:40./:Kg„’—h.6QdTHB>>AE[ad{‘—‚vx{p~”–‹€š¤¢¢ “~rmhgd_ZZTOIB@>=;98:4[‰ˆw{…‚|{jD6643/../15:?Ph}~ywma_becdlx€ƒ‡ˆƒ|{ncY?@7<7EZBAbvxu|‚€dL`OMijgg`^[_hjmlnnkha^__WYZXUW\]ajhB08WoWQQPQQQSVY\`djpw|€‚‚}yttvxy|ƒˆŠŒŒ‹‰ˆ‡‡††‡‡‡†‡‡‡ˆˆ‡„‚‚‚}xutttqomje^VMIOV`iu~„ˆŒŽŠˆ‡††‡‡‡‡††ƒxpd]]dksvuou–Ž‹ˆ‡‡ˆ‡†„ƒ~g__enz„‰ŠŒŒŽŽŽŽ‘’ŽŠ‰‘((((((((((()))))********+++++++++++*********))(())))))))))))))))((('&&&%%%&&&&&&%%%%$$%%%%%%%%%%&&&'''&''''((())))**++,,,----.//001110111233445556565565554444322100/.--------,,,,******))**))**/2))))(())))),Pm}ŽŒŽ‹ˆ‰››šœ˜‡‡Ž‰Š‡ƒ‰‘ƒxwwtoe_UD@CJNIKHF=-(')*'(((*,++.-3+).8<>?<:<@“ŒŠŽ‘•–—Š€…Šinwup`LB88??>;Z…~sw~€{xnQ8753300158?BVvŒŽ‡}iZVROMT[^`d`]\ZbtYB?=B4=AK7Kegnz‚…h&bqSZ]dklfhcbhopppkgd__\ZXYYYZTT\^X6(1OaURRPQQRTXZ\`fkrx}€ƒƒ}zvsuxy|„‡Œ‹‹Š‰ˆ‡‡††††‡†‡‡‡ˆ‰ˆ††„„}xtssspnlie^VMLQXakv~„ˆŒ‰‡†††‡‡‡‡††ƒwod^`gnswttŠ•‘‹‡…†‡ˆ†ƒ…iccbfq~‰ŒŽŽŽŽŽŽ‘ˆŽ”))(((())))))))****)*********++,,++******++))))((((((((((((((''''&&''&&&&&&&%%%%%%%$$$$%%$$$$$%%%&%&&&&&'''''(((()))*)+,,,,,,-...////01/11222445555654444433333201///..----,,,,++**++++***********)))'''''''()+Ik~†„‡‰…‚‹˜’‹ŽŒ}‡‹Œ‰ˆˆ„€€…’qtyslhaSLHGF<@IGENUe|…‹’ ­µ²²²¬¥ œ®³®¤ŸŽ}slec^YWXTMHB@@BCB@Cb‚†‚xw|}yvo[@:;;<7257<@E[xŠŒ‘‘‘‚qjc\VSMGFHHLXgsoLGI>G8>EDG?HWay€8E’’€IXVQVckb]lmqtqmhd`][ZYXXYZWZ]\VE?LWYUQPQPRRUY[_chltx|€‚‚€}zxwvz{}€„‡‹‹Š‰‰ˆ‡‡††††‡†††‡ˆŠˆ‡…ƒƒ~zwssssonlgd\TMNS[cmx€…‰ŒŽ‹‰‰ˆ‡‡‡‡††††ƒ~wme`bhovuq†“‹‰ˆ‡ˆ†…ˆkeebbhv‚ŒŽŒŽŒ••***)))))**))******)***********++))***)))**))))((((((((((((((''''&&''&&&&&&&%%%%%%%$$$$%%%%%%%%%%%$$$&&&&''''''(()))))+,,,,,,-...////0010122233555554444433333310////..----,,++++))****))))))))))*))&'''''''()+0'()*)('+**+,*)*)++-.17:@Kkœ™‘‘–˜—‡’Žƒ‚„‡ˆurtm[ICCCJWeok`UPPIAA@:5/2C[qƒyNc‡‚zzeG=E>NVP]yŠ–¥±³µ³°­«¤›”ª­¦¡¢–‡tnjda\bpg\QHEDDDDDP|’ƒ{z~wui_\Y]\_P48;>ABWo‚…Š‘Œywupcca]\[^afmpfBA=@BC;LAIG?Rgv€peŠ“„PPWKJVOKWqrrtqniea^\[[YY[\\\\\XVZ[XUUSQPPRRUX\_cinsw|€‚‚|yvwx{|}€„‡‹‹Š‰‰ˆ‡‡†††††…††‡ˆˆˆ††ƒƒƒzwssqqonkfbZSNPU]eq|‚‡‰ŒŽ‰ˆˆ‡††††…………ƒ~wmdcgirwr|’‘‰ŠŠŠ‡‡„‡…rhifbbjx…ŽŽŽŽŽŽ‘Œ‘˜•))****(****())++**))******++******))))((((((((((((((((((((''((''&&&&&&%%&&'%%%%%$$$$$$$$$$$$$%%%&%%&&&&&&&''''''''))(*,,++,,-...//00111012222244555544332233220./..-----,+++**))))))))('(((())))))''''''''&(),2k‹ŠŒŠƒz„ŒŽ•Š}}†‚sgd~‰†‡ŠŽq^_^`gdQQSUMLNNJFE?1'')))))(+(),*+*+,-,,047Uz‘•’”•—–›zŠ–ˆ„††‰ƒ|{vqssu}ˆ‹‚jY[ZUNGFGA72?Njxl\„•—‰„‹‘tL@>JULRmŒž¨±³°¬¨££¤¢Ÿ£œ•’‘Œ€smkhmmjsjcZSKHHICBN}”˜Ž‚€}wriiotzzve=68:>BNaz†‹„zx{{zsfdjg`\_fgjnZ[[X96:>MKCFDO`y|[rŒ’„TGRK6GMelprtsqoke_][[ZZZ\\[\][Z\ZWWUSQPPNRRUY]_cjnrv|€‚‚€~zxxy{|~„ˆ‹‹‰ˆˆ‡‡‡††…………†††‡ˆˆ‡†…ƒ€~yvrqqqonlgc\RPTY_hr|ƒ‡ŠŠ‰ˆ‡‡‡‡‡††……„~wmfegmuv{‘Œ‰‰ŠŠˆ‡†ˆ„oiiidbeozˆ‘’ŽŒŒŒŒŽ––•))))))(****+++++**))********))**)))))))(((((((((((((((((((''''''&&&&&&%%&&%%%%%%$$$$$$$$$$$$$%%%%%%%%%%&&&''''''''))(*,,++,,-...//00//1012222244444444222233310//.------++++**))((((((''&&&&''((''''''''''&()/3N‰Œˆ…€|‚†‹’Œ‹ˆwinnft‚‹‡Š‹ŠaOOPTV_]OMONKJKHGE?1''))*#*+**'&(+++*+,++.An’šš”’“•—–•z•Žˆƒ†ˆ‰‡†’”–•’”——’‰uRPRXRMGEJC64Jmˆw{”™›‘’Ÿ£Ÿ…hiZO@Mf«°°²­¥  ¡¬¤Ž £¤¤¢ž“i[do€vqnf_SJHKKGB@]Œ˜Ž‚€xuxxqv€‰„sP46;??DSiy}qlqusqmgcgf_[_cbgb\`seB7;=>FJQG;Nx{d{‹Š‚\IO>+@T]dqqrssolfa]\\[[[[Z[[\\\\ZXUUQPPPOPRUY]_chlrvy~‚€~|yy{{|~†‰‹‹ˆˆ‡‡‡‡††…………†††‡ˆˆ‡†…ƒ€~zvsqqqonlgb[RPTY`is~ƒ‡‹Ž‰ˆ‡†††††††……ƒ}wmfgjqu{‘Š‹Š‹Š‡††‡tjnliecgq~‰’‘ŽŽŽŽŽ‘’—””**))))*++***++++**))******))))))))))))((''((((((((((((((((''&&&&&&&&&&&%%%%%%%%%$$$$$$$$#$$$$%%%%%%%%%&&%%''''''''(())**)+,-,--.000011101222223333333322112322///.-----,++****))(('''&''''''''&&''&&''''''&(,1;Ek†‡…„ƒ†ƒ…‹ˆ‚ƒ„‚‡„qv‰ŒŽŽ‚[NLTXTGWb[ONNKJKFC=/%'(*,Im8#(,,))+,,././2T€˜ ˜’’•š––~{‘•‰„„ˆ‹ˆz’”˜™—–———–…`VXXZ\UJFGB:>a‰“•—–š¤¥¤ “†“š†_@OŒ¦®°¯­¢œž ¢Š¤¥¦¥ Ÿ”[257<@CJRTWXWUTSPNQSTWYP:+?8;>:?CC=>=AJGIX[J^kŠ†ƒuXFIRDBMI@LRT_bt†…xKAJZaPZopqssqnje_][ZZZZZZZZZZZXWTPNMLLMMNRUX\acdgjquz~‚ƒƒ€}~‚„ˆˆ‡‡‡†…„……ƒƒƒ„……††‡‡ˆ‡ˆ…ƒ~{vsqqppnlhd]XSV\`hry‚†‰‹Œ‹‰Š‰‡†††††††……€yrnkwŒŒŒŠŠŠŠˆ‡‹‰ulnmnpppmot|ŠŽŽŒ‹’”—”“’++++++****++++******)))))(((((((((('&&'''''(((((((''''''&&%%%%%%%%%%%%%%%%%%%%$$$$$$$$##$$$$$$$$$$$$$$$$$$$%&&&'%%'''((()+++,,-.../01111000211111021//.---..-,,,,++++**)(()(((&&%%%%%%##########%%%%%&'',1@Vu|uf^WYfdWQLEIVU\dx{|ŠŠŽŽ†lZ]`kgiknlbhopkopshmr|d<),,)())(''''((**-;NZbx“ŒŽ•‘Ž‰|…†…†”‹„‚€…Œ„|‹‘’‘”š¡—œŸ˜~h]\YPH=uœ•ŠˆnZ`ppknp‚•¦¡ˆŽŽ~s‚‰„‚†ˆ„~~phƒ¥œ™•‰yocRDCHnŽyš›‘Œ‰yskf^[ZV@,+('))+/4796;?EIK?BQNJ:=0')))-;NZiŒ‰†‰‹‡†„†Š†ƒ‡‹‘˜—‹{zvzˆ`5OYfqx’¥°°±©¡˜†nc^\PIc“™“•‰„v]u’”‹Œˆ„ˆ”œ •zx~‚}xtq’”‚mt‡˜¢ª¦¡–~ˆ‹ƒ|vqldXEg…‰Š…|“”…€wngb]UOIC:.(&'&&&&&'((((')****,.16:=CDLHG<59NG<;@G:3AIHTdw‚ve\VkhQV]fqrsrqmid^[ZZZZZZZZZYYWURONKJJJKLOSVY^_bbdhlqx}ƒƒ‚€€‚ƒ„‡ŠŠ‡††……„ƒ‚‚ƒ…†‡ˆˆˆ‰ˆ‡†„}yurppoomjfb\WUY_fmu|‚ˆŒŒ‹‹‰‡‡†††……†††„{vp‚‹‡…‰ˆˆˆˆ‡‰Š{yƒ†‡‰Š‹ŒŽŽŽŒŒŒŽ‘‘‘‘‘,,++++++++++**++****))))''''''''''''''''''''(((((())((''&&&&%$%&%%%%%%%%%%%%$$$$$$##$$$$$$$$$$$$%%##$$$$$$$$$$&&&&&&&()))*,,,,--......//./0000////..-,++,,,,,,,+*))))(((''&&%%%$##""""""""""""##$$%%%(+5Fdyxsw||zz{vtj_UE3+1?P[cb^jjVWird\[`lqsqpmid^[ZZZYYZZYYYYVTQMKJHHJKMPSVZ\^`bdgkqv{€ƒƒƒ„†‰‹Šˆ‡†…„„ƒƒ€ƒ………‡‰ˆˆ‹†‡…„€~ysrqponliea\XV\`fmt{‡ŠŒ‹‹‰†††††……†‡…ƒ€{p~Ž‹ˆ††‡‡†‡ˆŒ…„ˆŒŽŽŽŽŽŽŒŒŒŒ‹‹ŒŽŽ,,++++++++++********))))''''''''''''''''''''(((((())((''&&&&&&&&%%%%%%%%%%%%$$$$$$##$$$$$$%%##$$%%$#$$$$$$$$$$&&&&&&&()))*,,,,--......//./00//////..-,+++++++++*)))(((('&&&%%%$$##""##!!!!!!!!""$$%%%*0Dbxwqv|~|zxtph\PHQaj}~jwŠ‚ƒ‚zWQWqymr{qqquytwu€op~yto_urbmŠŒŽrN3((()**+++4GT^xŠ…€{ptx|}€„„Ž—™•Œ€vpmqz„W:O]s‡Ÿ­°¬®§Š]IMNQNN_š˜š€jP\…‰ŒŽŠˆ…Ž“{jkox€•§«§£—{SJJGYƒ˜„Šš—“ˆ}sonnofPA]‚ŽŽ…{‚€…€tbG:4342.(%%#%%%$$$%$%%%&%'(*,/015?53/*+.2>^R?@QJWsxqtz||z{zzrjf[NIWivˆ}p€‚€‚iKNPcnnhikptuqfuxyposvkeX`plW—’‘mdg?2')))(*5ALYcˆ‚}}|vnpstvrv}„Ž’Œ†{urppz…WQ`rŠš®²°©C=DEDKPP[Š›šžšT:=ds{‚…{xy‚|h_aixˆ–¢Ÿœ›“~UFHOk‹–•’š”‹trskhkoaHFSlx€{rnyy~}cB40.,*(('&$#$$$%%$%&&%$%'',/1022473-(&&)*.E_PXYZYVI,+-9QPTL\y}}‚‡zSQOXcppqpmhc]ZXWWWWXVXXWWUQOKJGFFHJLORUY[^_`aeinty‚‚‚‚‚ƒƒ„ˆ‹‹Šˆˆ†…„„ƒƒƒƒ„„‡ˆ‰‰Š‰‰‰‡†ƒ‚|vsrqonmkhe_[XX^cjry~ƒˆ‹Š‰‰‡………………„……„‚||‘‘ˆˆ†…†ˆ‰‘’’“’ŒŽŽŽŽŽŽŽŠŒŒŽŽ‹‹‹‹Š‹‹,,++++++++********))))((((''''''''''''''((''(((())))((''&&''&&''&&&&&&%%%%%%%%%%$$$$$%%%$$%%##$%%%%%%%$$$$$$$$&&&%&&(())*+,,,,------..////..---------,++****)))((((''''&&&%$$$#"##!!!!!!!!!"""###%'',8Lktrrw{|zz|yxqlbVNJ[v‡ˆxuƒ}}€yYGGSinokekfjsmhl~rosohigGjnTn”†qhft?MD)(,*)2N{••—•Œ~cS\`befUHBFWswqi`jpuuS401/,*(('%$#$$$%%$%%$&(+-.12463220.(&%%')+co]€‰dWg]hfYVI9/3>JVZ~„}zvuvrpoliecba^_agecby}qlnmlu‚}sˆ“RS[Q>:Jk„Ž…rc‰{‚pLQkvŠynifbejioy—¦žŽzvqqkM=:FCD<+'(&&&()))07?IY]U]cP@ELFMWk\SWS^kyK@]Lfkfqomgc]YWVVVVWWWWVURPKFEBABDGKNRUXZ\]^^afjou{‚‚ƒ„†‡Š‹‹Šˆ‡…„……ƒƒ„„…‡†ˆŠŠŠ‰‹Š†…}ytprqommjhc^WY_diqw}€…‹ŒŠˆˆ††……………………‚…‹‰ŠŠŒŽŽŽŽ‘‘ŽŒ‰‡……‡ŒŒ‹‰‰‹Œ’“‘Ž‘’++++++******)))))))(''''(('((((((((((((('((((())))((((((((('&&((''''&&&&''%%%%&&&&&&&&&&&&&&%%%%%%%%&&%%%%&&&&&&&'((()()**++----..----.---------,+*****)))((((((((&&&&%$###"""!! !"!!""#$$'0B\nlmq{{tqrvxxywof\RDBn…xk€xxyqVFHQbhjmfbUTNDKHHEFJE>887;BAF^ZJˆŒpa~tpnaTKHA;>JPVyƒ}ytqqqssohe_[ZWYZaaWHIqupoifkw‚u|‡[OVN?)*,,**('&%%$#$$#"$&%&,:BEEDGGB=5)&&&&&()'*/3:DDJWd`ZVF@;@LWXZ\>B[T28fQN]irpmhb\XVTTUWWWWWUTQNJEA@@ACFJMQTXYZ[\]`chmrz|~€ƒ……Š‹‹ˆ‡ˆ‡„…„„„„ƒƒ„††‡ŠŠ‹ŠŠˆ‡„}xsqpqpmljga]Z[`djqx}†‹‹‹‹ˆ††……………………‚Ž’‹‡‹ŽŽŽŒ‹ŽŒˆ†ƒ€‚ˆŒ‹Œ‰ŠŠ‘”“ŽŒŒ‘Ž++++++******)))))))(''''(('(((((((((((((((((())))))((((((((())((((''&&&&'''%&&''''''''''&&&&&&%%%%%%&&%%%%&&&&&&&'((()()**++,,,,..----.---------,+**)))))(''''''''&&%%$###""""!  !"!!!"#$%,;;@F?O]:k{…ˆ‡„~tcTMIEA@FJQr‚zvsronprnie_[YONT`^RF2Gjmnjhhnv€|c@KNNEHo¢žj\o…‹†y51Lw“‰€wkd^[^c\f™Ÿ–‡|o\al\FB@DMWakqwvunU8+-;Rht~wd^dx††„„{i_S4&)-,*)''&%%$$$$#""#$(/:BDC?:8760'''&'((''*16:=8GQX[YVOIKCLYZRPF6Q\MVjYJYrspnhb\XVTTTSVVUUTSPLJEA@?@BEJMQTXYXZZ\^aejqw}~€ƒ…†ˆ‹‹Š‰‡†„…„„„„ƒƒ„††‡Š‹‹Š‰‡‡„€|vsqqqpmljga]Z]afks{€ƒ‰‹‹‹‹ˆ††……………†††Š‹‹ŠŒŽŽŽŒŒŽ‹‰…‚€|y{~‚‰ŽŽŒ‹ŠŠŠ‹““’Ž‹’‘‘Ž,,+++++*******)))))('()(''((''))))(())(((((())))))**)(((()*((()))('''&''''''''((''''''''''''&&''&&&&&&&&%%%%%%''''(((())*+++++,,------------,,+++***)))))(''''&&&&&%##$#"""!""!!!! !!!!!!!!###&)7OmrikqvslijkoqvwtkcVK?;j‹tyrpxwbKHPag_`hWJFDICHNKLKMECB@>?>E@AJD:==;81('''(('(&*1;=79BGI[^[\GFADKKIV`R@YRcn^J\stpmgb\WUTSTTTTTTSRNKGC@??@AEILPTWXYZZZ\^bgntx}~€ƒ†ˆ‰‹‹Š‰‡†„…„„ƒƒƒƒ„…ˆˆ‹‹‹ŠŠˆ‡„€|vrqpppoljga]Z\`hnu{€…ˆ‹‹Š‰††………………„†ŠŒŒŽŽŒŒ‹‹ŒŒŒŠŠˆ†‚~|yyxy|€…‹ŽŒŠŠŒŽ““’‹‹‘Ž,,,,,,,+******)))))('()(''(((())))(())((((()))))))****))()*((()))''''&''''''''((''((((((''''&&''&&&&&&&&%%%%%%''''(((())*+++++,,------------,,+++***)))))(''''&&&&&%####""!!!!! !!!!!!!!!!!!#"#)0FftmkptsmkhijmpsurkcVJ>6f—’~quxz\CHQek_]_PDFILGMQOMJJDBA@A?@BDCCF@n„‰‡ƒzkYNKECBBADG_…xvtqnjikjga^ZVPG>63;=867VmggffgowqF:7;_Œ£¦¤¦ž‡u`eyŒ†‰K1@„—‡ƒzsbZXVVNTˆ›’„yjONYP;19BKQWY\_cfe\J60=Qcikku|~™œ’‚z|wdT>*&'))*('&%$%%$###"""#*3:=?A>>;630+&''((((&)-2319<:DU[RW\D?DQTU^i]Lbwx~gDXktomfb\WUSRSSTTTTSRNKEC@??@AEILPTVWXYYYY\`dkqx}~€ƒ†ˆ‰‹‹ˆ‡‡…„…„„ƒƒƒƒ„…ˆ‰‹‹‹ŠŠˆ„{urqpqonkhe`\[^cimv}‡‰‹‹Š‡††…………………‡ŽŽŽŒ‹‰‰‰ŠŠ‹‹‹ˆ„|yxxwwx{}ˆŽ‹‹“’Œ‹‹‘’ŽŒ‹**************)())))(((())))))))))))))))))******++**,,,+****))))))((((((''''))))))))((''((''''((&&''''&&&&&&&&'''')))***)*,,,,,,,,,,..--,,,,,+**))))))((('&&&&&&%%%$##"""!!! !! !!! "$#$(/B^qmjottlhfdadintsskbUH81U”‘„vnszm[HKSda\]ZLDJONNORSRKGGEFILA<>@?CEDW‡‡†€rgUJGCBAB@>@\‡ysopokhhhea]YTPH91155556w‘‡}shVRUTTMq›–…yn^OZaT54;GNTX\^abc`YF9?KZdgfglph“™‚ysi^T?,())())'%%$#####""!#%09<=>?@<831/*''((((&&(-.1347:=>@BFIMNRUVWXXWWX]bhnuz|‚…‡‰‰Šˆ‡‡……„„ƒƒƒƒƒ…†ˆ‰Š‹‹‰‰‡‡„€{urqppoljfc_\]_ciqx}‚‡ŠŒŒŠ‰‡‡…………………ŽŽŽ‹‰ˆˆ†‰ŠŠ‰‰ˆƒ}yvvuvvwy{{ƒŠŽŽŽŒ‹’’‘Ž‹‹‹Š’“‘ŽŒ‹**************)((())(((())))))))))))))))))***+**++--,,,+**++*)))))(((((((((())))))))((((((((''''''''''&&&&&&&&''''((*****+,,,,,,,,,,--,,,+,,,+**))))))))('&&&&&&%%%$$$"""!!! !! !! "(5%&-?Wrtlntunieb_\_inswqjaTG6-J’„trrvaOJLO]YSROCFMONKLPY[PHHIFHLPNBA@@CDA€ˆ…}n_QICA>;=<9;T…}qopnhfgec_\VUPD533113356Mplhfecltt>,O~˜ £¢˜Š}cZw`v|r:6oskXNORRQW†–‡wmcXTVZUB?DKRW[]_`caZRHELR\bdgkpy€™Ž}sng_ZU@,*'(())'%%$####!""!#,7<=>?@?<8530+('((((''&+.135557;CMOBFUZ>AO\TVY\bMS\JRivwple_WRPPQPQSSSRQNHDA>=>@AEIMOSUVWWWVVW[_fmrw}ƒ†‡‰‰Šˆ‡††…„„ƒƒƒƒƒ…†‡ˆŠŠ‹‰‰‡‡„ytrqppoljfc_\]`fkry~‚‡Š‹‹‰‡‡‡…………„‚‹’‘‘ŽŒ‹‰ˆˆ†ˆŠ‰†ƒ}xwtuvwwwxz{{€…ŠŽŽŽ‹Œ‘‘Œ‰ŠŠ‹”“Œ‹****++******))(''()))())))))))))(())**++*+++++,,++,,,,,,,,,,*)))))(()))))))))))))))))))))))))(''''''''&&&&&&&&''''((()++**,,,,,,,,,,,,,,,+--++*)))))(((('&%%%%%%$$$#%%"""!  !! !! "%(,>Vounqsvojea]WV]iqvvrk`SF58[‰|prsl\QFGPTLDACDGMKKJLN]\OFDBA@@RVD=A>D>>i‡vmd[TD;888779B~~rlljhdc_XVWVRL<..1/./0275drja`cgis_9^|™›”ŠnNR^CavsG7rxlaGGGMNNdƒˆwh^ORTPLSUPSVY\^__bdb`RJNW]`dlolp™Ž|oieb[YS=+(&''(&&%%$###$"!!"&1;==?@A@>;864-&$&&''%$&(,/0256777=>@DGJMOSUVVVUTUVY]cjpv|€‡ˆŠŠ‰ˆ‡†……„…„ƒƒƒƒ…†ˆˆ‹ŠŠŠŒŠ‡ƒ~xrpqqpnljeb^]]ahntz„ˆŠŠ‰ˆ†††††„„„‡’’‘ŽŒ‹Š‰‰‰†ˆ…‚}zwwvuwwwxyyz{|€†‹ŽŽŽŽ‘ŒŠŠ‰‹’•“Œ****++******))(''())))))))))))))(())**++++++++,,++,,,,,,,,,,*)))))))))))))))))))))))))))))))))((''''''&&&&&&&&''''((()**)+,,,,,,,,,,,,,,,+++++*)))))((((&&%%%%%%$$$#$$"""!  !! ""'-=;9D??>=A@J€tlgdTA;6764468b~smjgec_\QLNMIE6**/.,./018Oojedcabfp[c{ŠŒ„zmRFFCBW~upU:vŠtk\BEGLKRkvui^TSTVXC>?@A@?=<85+$"##$$##%&*.10379<;:9889CMLLQ[UX_aWEI_dKOqtmd]XSQPOQQQQQPOLHC@>=?ACFINOSTUVVUTTUW\bkpw|€ƒ‡‰ŠŠ‰ˆ‡‡†…„„„ƒƒƒƒ…†ˆˆ‰‹ŠŠ‹Šˆ€}wrpqqpnljda^]^biqu{€„ˆŠŠ‰ˆ††………„„…‘’Ž‹Šˆˆˆ‰ˆ‚|wwvvuvvwwwxxz{}ƒ‡ŒŽŽŽŒ‘ŽŠ‰‰ŠŒ‘“•“Œ********))*))))'()))))))))))****))**))**+,*,++,,,,----..,,,-+*++**++****))**++****))))))))))))))''''''''''''''(())(())***+++++--,,,,,,,,++*)***)))((('((&%%%%%%%$$$#"""""!! !! #')9TovpnswskeaZTMJO]iotsme[NC75I]kfhqSEGC=BEGCBDEHFGIIIKQQNF@BBCED>;B==>?@:m{rkfcM@D=411235;rrieeeb`ZQKNOKE6-**--.//.7@inifb`\`jiiw„†xlT>FBAKX}xjbBz†tfW@BHJLVdge\TSTVVE4.8Vg``_ceddggg\_imlr{r^`‚ˆ|le_]\WQPN>+%&''''&%%%$$$!"!"&1:=?@@@CB@?>;4,$"""$$""$%'*./15:===71.+.6HVXPRYYiiLIbbZLmukc\XXZROPPPQQQNJFA>=>@CCFJMPRSRUWTSSUW[bjpv|‚‡‰‹‰ˆˆ‡‡†…„……ƒƒƒ„†‡Š‹Œ‹Š‹‰…€{trppppnljc`]]_eipu|ƒ†ˆŠ‹‰ˆ†„„……„„Š‘“’‘ŽŽ‹‰‰‰ˆ‡‰„zyywwwwvwwwwvxyz{„‰ŽŽŽ‹‹‰‰†‰Ž•–”’ŽŒ‹))******)))))))())))))))))))******++**+++,++++,---------,,-,,+++++++++****++++++++**))**))))))))((((''''''''''(())(())))*+++,,----,,,,,,++*)))))**((('&&&%%%%%&$$$##"""""!   !!#%+7Vpyolqvvoga\UMDEO]gnqskbWJ>424`~hgmoJFC>:@CGEDDEDGHHIIFKOQTXdnnqokcURU_P79Wxqjg_OB@93212312Hrjcba_^YQLIMNMG;.*.1/./237Joni_\[]bmsw{|uk\A?BDMS`rn__Mu„seSAGIJMZ]ZXVTSUR@0.4C_jmnkggfgiieckortzg[W`z{lc]YXYRPMJ;*&%&(''&%%%$#"""!#-9<>AA@@B@@?>;5("####$##$%%%(*.1379862,++.=>@CFILNQQQQSTSQQTX\ckqx~€ƒ‡ŠŠŠ‰ˆ‡†……„„„ƒƒƒ„†‡Š‹‹Œ‹ŠŠ‰„€ztqppppnkic`]]`flsv|ƒ‡ŠŒ‹Šˆ†…………ƒ‡ŒŽ’’Ž‹‡‡‡ˆ‰‡€}yxyxxwwvwwwxxxzz{„ˆŽŽŽ‘ŒŠ‰ˆ‡‰Œ“––“ŽŒ‹‹(())))))(((())))))))))))))))****+++++++++++,,,,--...------..,,,,++++++**++++**,,,,+***++*)**))))((((((''''''''(())**))****++,,,,--,,,,,,**+***)))*)))&%%&%$$##%%$"!"##""!!! !! !#'5Qnxrnpuuslc]VLA=DP]forpi^WJ<411f|khokEE?99=BEFFEEDFIECFCCLi‚•š†xvwiU_qyY7?snke[M?621110./13Xkc`^]\XRMJIJMNNG9+*-/-/343RrmaWXY\amzvpjbM<@?B]`gbYZXRmm`NHJMLORRTTUSQM=303@Sgqtxzvokilljmry}rZIZPWffc^[XYTMJHD5'$&&')('%%%$#"# #)7=>=@@@@AA@@?<3& ##"#%$$#$$%'(),-.*+*+*)+-7P\RZcdba[\i[K[soi_c[@V_SOOOMLHGC@==>ABDGLOQQQRTRQPQSX\cksy}€ƒ‡ŠŠ‰ˆ‡…„„„ƒ‚‚‚‚ƒƒ…‡Š‹‹‹ŠŠŠ‰…~ysppppomjhca]^bgnsy}ƒˆŠ‹‰ˆ†††„„†Œ“‘‹‰‡……ŠŠ{{xxxxyyxvwwwxxwwz|†‰ŽŽ’‘ŒŠ‰ˆˆ‹Ž“—•’ŽŒ‹Š(())))))(((())))))))))))))))****++++++++++,,,,,.......------,,,,++++++++,,,,++,,++*+++++*)**))))((((((''''''''(())**))****++,,,,,,,,,,,,**))*)))(((((&&&&%$$##%%$$""##""!!! !!#$(7LkwmmsuvuqiaZOA:=DO^hmnlf^UH<43>tvhgncAC@:9?ECDEDCAEHKFBBHh£ªª¡•‹|wx{hWg‚{T/`ole[K=62231/./12?@@@@AA@@?<2% ##"#&&##$$&.))&%&***+*++,.3FJ[__X`dZ_YLVqrbZE)4NNLOOOMLHEB?==>ABFILOQQRSRQPPQSX_dlsy}„‡ŠŠ‰ˆ‡…„…„ƒ‚‚‚‚ƒƒ†ˆŠ‹‹‹ŠŠ‹ˆƒ~ztppppomigb`]^bhnty~ƒ‡‰ŒŠ‰‡…………†Š“‘’‹‹ŒŠ‡‹„ywyzyxxyxwwwwvwwwxz|ƒ‡ŽŽŽ‘“ŽŒ‰ˆ‡‹‘”–’‘ŽŒ‹Š))**))))(())))))))))*)))))******++++,,+++,---.-.//......-.-,-,--,,,,,,,,--,,++,,,,+++++++*++****))((((''(((())*******(******,,,,+,,,,,,,*)()))**('&&&%%%%%%%%%&'**%$#""!""! "# !!%(0Ijwmhntxtsnf\SC76?HQ^hoplg\QF:46P~rabl_ABA:6?EBBBCC@CHQJ?H^Šž¥­¥”Ž’”ŠqZb}‰w;FrkeZO@622233/.155>^`WVUTSRNLMMKLLKKH??@AAAA@@?:/#! !"""#&#!"%)+))++**++++,*,//1DVmigf_fjXRVngh`N+1FOPNNNLKGDA>==>ABDGMOPPRRQPPPORY`gou{~‚…ˆ‰‰‰ˆ‡…„„‚‚‚ƒ„†‰Š‹‹‹‹Š‰…‚~ysoooonlhda`_adjpv|€„ˆ‰‹‰‡‡…„…†‹‘”’ŽŽŽ‹ŒŒ…|{yzxyxxwwxxxxxxxwx{~„‹ŽŽ’‘Œ‹ŠŠ‰Œ”—–“‘ŽŒŒŠŠ(())(((((())))))))))*)))))**++**++++,,,,+,---..///......-.,,-,--,,----,,++,,+,,,,,,,,,,++*++****))((((''(((())*******(******,,,,+,,,,,,,*()+))))(&&&&%%%%%%%%%&*00&#"!"!""! !!!!#%&1FdyogkrxvvrjaWJ807AJT`iprme[OD81==>ABGJMNPPRRPPOONSZ`hpu{~‚…ˆ‰‰‰ˆ‡…„„‚‚‚‚…‡ŠŠ‹‹‹‹Š‰…}uqoooonlhda``bflrx~‚†‹ŒŠŠˆ‡…„†ŠŽ’’“’ŽŽŽ~y{yyxxxxwwwwwwwwwwy|‚‡‘‘ŽŽ•Œ‹ŠŠ‹•—•’ŽŒŒŠŠ(((())(()))))*****))+*++**++++,,,,++,,,,,,..--//..........--....,,..------,,,,,,,,,,,,,,,,++**++(((((((())))))))**++**+++++++++,,,,,,,,+**))))(('&%%%%%%$$$$$$&&)(%#""!!""!!!!!! !"$)-@auskkrvxxtoe\M>01:DPZbjqricXLC71Eu‚lgjqVAF>99?CDCBBCACEDIa—£¦¨ª¬ª¡œ™–‡vyz’ŽŽ|Edlf`WOGGFEEFEFJLWZTHLRSRONMLKKKKKHGDDD>73.*'#%>YYW[\]]^]bUIRYRTWVUSMJG@4;EADIMLLQTW\\VI<701=P`lptvtnkhgfiige_[NURGHMZaa^[USMGD@=1($##%%%&&&&&$""+59:<>>??@BBAA@@?:/##$#""!###""%((**++++***+,.148=BDGTfggmiNL^jjd`ZTLLNMNNNMKGC@>=>?ADFIMMPPPPPPNOOUZbiqx}ƒ…ˆˆŠˆ‡…ƒƒ‚€ƒ†ˆŠŠŠŠ‰ˆˆˆ„ztpoooomkgecbbchnuz~‚ˆŒŒŠˆ‡‡††Š’’‘Ž‘ŽŽyyxywxxxxwxxwwwwwwxz}„‰’’ŽŽŽ““ŒŒ‹‰ˆ‹‘–—•‘‹‹‹Š(((())(())))*+******+*+++*++,,,,,,++,,----..-.//..........--....,-,,,,----,,,,--.-,,,,,,,,+++*++*((((((())))))****++**+++++++++,,,,,,,,+**))))((&&%%%%%%%#$$$$##%$"#""!!""!!!!""!!#%(1?[vskkqvvvvof\QB4/5>HU^fnqng_UI=67V{‚lfiqT?E>99BEEDCCCBCEHYp‹—š¤¬«ª®£¡žœ”r†‡€ˆ’†iQd_]]\YYVTUTSTTRWWY[SNMNKLKLKJIIJHEEBA>:73-(&'&CWPPW\YWZ`bUUWYUTRPKGBB?43>HCEIJLOQUXWQF=722>N]hpxxqkiifffghgXD8FMJHR^`a\XUPHDA=9,%#"#$$%&&&&%##&08;<>@???@BBAA@@=;/# ""!!###""%'**********+/069?<=?BFHKMMPPPPOONOQV\biqz~ƒ…ˆ‰‰ˆ‡…ƒƒ‚€ƒ†‰‹‹‹Š‰ˆˆ„€|uqnooooljgebcefjqw{€„ŠŒŠ‰ˆ……‰’“”‘‘ŽŽŽŽ†wy|yyzxxwwxxxwwvvwwxz~…‹’“’‘’•‘Œ‹Š‡ŠŒ’•••‘ŒŒ‹Š''(((((())))))++++,,+++++*++,,,,,,,,----..--//////..................----..----,,------++,,++**,,+***))))**))))**********++++++++,,,,+***))((''''&%%%%%$$$$$$$$$$&$####"!""!!!!""#"%)1=XsqjlprrsrmdYNC5/07BMVbiopke[OC75Be~lhflO@C@87BGECCEFC@@Nk––ž§ªª¬¢££¤¢Œ}ƒ‰‹”šŠzRYXWUTOOPPOOOOPPRV[[UPIIFEEFEEFHHHHEA?>:853,'&(.PZUSUTSUY]bWRRNPNKFA=?A7.3=AADFKMNRWYQF:834>KWelrslhhgfffghicN6@FEIX^^\XUSKEB?;5)######$%%$$###)29=>?@@??@A@@@??>:.#!!""!!"""""$&+++)******,04666689>:9630*%'*2R_XPRURPTa[DICEFEB><>>5/,-:@AEJLMNNPOC9743:HU^glmlihfffe_XVLOULCFOZ^\ZVSOHC><93'$#####$%%$$###)3:=>?@@@?@@@@@??>:.#!!""!!""""!"$(**)******,/3444569@GIIIMC@AUhonfbcgSLMMNMLJFC@??@ADGHKMMNNOPRTTTWY_fktz}ƒ‡‰Šˆˆ…„‚‚€€€~€…ˆŒŠ‰ˆ†ƒytpooonnljgedehiov{ƒ‡‹‹‰‰‡†‹”‘’‘‘ŽŽŽŽŽsx{{yyyxxxxwwwwwwwxxy|‚ˆŽ’“’‘Ž”‘ŒŠŠˆ‰Œ’•—•”Œ‹Š''((((()))**++++++,,,,,,+-----------......////00//..............................------,,,,,,,,,,+***))********++****++**++++++++,+******))''&&&&&&$$$$$$$%$$$#$$$%#"""#""""!!!##%(*7Kjypilmppme\QD91++.8@KVajpple\QG;35Y{ylgknQ@EC<7?DEEFHGB?No…•˜‰¡¯¹º»¸ª§’|“˜¢žŸsMUXXVWVSRSSRPRRUUUUWPNJHFFEECBBAAAA@>><;:63.)(+*5T][XSQOR[aJ;??CB?;8873/--9DIKLNPOOQO?652.5DQWY]dihkkfgi\OW8LYKGNY]\ZVSQKD@<:91&####$"#$$####")4=????@A@AA@@@@@?<-#! """"""""!"#'')*+*+))++-102479669=@ED?CFAKfg`_\ZUNMNNMLJFCA??ABEHIJLLNNPRVWZZ]aaemt{~€„‡‰Š‰†„ƒƒ‚€~‚‡‹Ž‹Š‰ˆ†|wronmmnmjhgdefjlpw}€ƒˆ‹ŽŒŠ‡‡Š“‘ŽŽ‹ˆzux|}zyxxxxwwwwwvuuxxy~ƒŠ”“‘Ž“–Œ‹‹ˆˆŠŽ’•–”“Ž‹Š''((((()))**++++++,,,,,,,...--------.....///////................................--...-,,,,,,,,,,+*************++********++++++++,+******)(''&&&&&&%%$$$$$%$$$#$$$$#"""#"!!"!""##'.7EfyrkknmlkcXK?4,))-3:GP[fmqpkbXMA516^{y~mein`?CC>8>EEEFIHDHaw‰Œ”’ƒ‹¤±¹»¼º²¯©ª¬®  ¥—\XXXVTTTPQQQPPQSUXYYTPMFFEEDDEDCC@@A@>>=;;950-+,.7UUWWRQPTZUB;;>>=;8751//2>HLMMNPPOQUG(/0.2A@AAAAABA@@@@@@>/#! """"""""!"##$$()()**++,/135653668=?>CCC=I_ff\ZTUOLNNMLJFCA?@BCEHJLLLOORUYZ_aegglpw{~€„‡‰‡‡†„ƒƒ‚€~„ˆŽŒŠ‰†ƒytpnnmmnmjhfefgjmty~‚†‰ŽŽŒŠ‡ˆŽ“‘‘ŽŒ‹‹‹Š€utz|{{zxxxxwwwwwvvvxxy~…Œ’”“‘“‘‹ŒŠˆˆ‹”•–”’‹Š(())**)))******++++---------......--/......0////.../............///////.////////-./.....-,,,,,++,,,,++++++++*********+,,,,++***+++++**))('''&&%%$$%%%%%%%%$$$$$$$$########"""#$'+1=Zxrjjqpph]TH8-(%&)-5?IT^fnome]RF;20>gwvkbelvK?D@::EDDFGFL`tˆ†Ž‘„‘£±µ¶º»¶¹»º»º®¦¢žŠjZVRRQSSTPPNMMRTRTUUSQNKHFEDCDCCCBBBA@@>===9841...AWVQPQPOMUV;5;<=:8432019GMNMNNNNNQSSG=)(/5;CJMV[_bdfgfcabUVTKPY[[YUQMEA<;:60'$$$$$$##"#""""#'2>ACCB@??@BAAAA@?=3%! "!!!!!!!!!!"""&))**++*+/1324468;;:9==BGMR[fba\VPTQNNMJFB@@BBCFIKMLMMQUZ\`ekoppptw}€ƒ‡‰‰ˆ†„„‚€€„‡ŠŒŒ‹‡„{wronmmmlkjgffgilqw{€‚…ŠŽŽŠ‰ˆŒ’““’ŽŽŽŽŒ‰‡‹ˆywx{}}zyxxwwwwwwwwwwwxz€‡’“’‘““‹ˆ‹Šˆ‰Œ‘“•–“ŽŒ‹‹‰(())**))****++++,----.------......../...//./////../-............///////.........-./.....-,,,,,++,,,,++++++++*********+,,,,++**++++++**))('''&&%%$$%%%%%%%%$$$$$$$$########""""%(-8Mrxpmqtri_SF5)'%%&)09CNWbkpmh`YO@2/0Cmxxmccnzr=A?:8ACBEAG_vƒ€Ž‡Ž”“™ ¤¯®°¹¼¼¹³°µ´µ©©¡wUTYesyyyyum^NLLNNORRPNKHFEDECCCCBBA@@@?>==:9863..,@SPONNKKMQR:69:8652202=KPONNNNNNPUUTT929:;=AFRWZ[_b`a_\_YSNKOXZYXSPJ?<9750*%$$$$$$######""#&/;@ACB@?@AAABBA@?<5)!!!!!!!!!!!!!!"""$&(**(()*,/0124547755699;BJSblbZRPOSPMMJFCACCCDHJLMLNORU[afkrvwxxxy}€€„‡‰‰‡†„‚€€€‚„‡ŠŽŒŠ‡„{ysommmmmlkifgggimrx|…ˆ‹Šˆ‰’’ŽŽŽ‹‡‡Œyy{|||zyxxwwwwwwwwwwwx{‚‡“”’‘‘“‘Œ‹‹Šˆˆ‹“•••’ŽŒ‹Šˆ))))******+++++,,----.-............././0//00//////..........................................-,,,,,,,+++++++*****++++,,,,,,++*++++++*****)'&&&&%%$$%%%%%%%%$$$$$$###########"#$'+4GivnjqvvncVF5'$$$%&)1:EPZdlpkf]RG:.-4Usxxlcahw~U:@76?ACDAQu†Œ†’•“––“—š›¥¦¬¹¾¼³³²³±¬¤¨¦”€^bt~~}|{xxutk`YXWURROLJGDEFFDDDBCA@??@>=<;:98754.-/?OLMLJIGIPL66965310/5CMRRNOMMLLMPSW\VF;9<;=@IPVWZZZY[ZYUPLLSXZVRNLC9840,(%$$$$$$$$$$###""#$+8@ABCBAAA@@AA>??<7*!""!!!!!""""""##""$&+*)**)****/2244010/57<@EJ[fjaYTOPPNLKGCABAEFIKLMMMNQU]aipx|}}{zz~‚††ˆˆ‡‡„ƒ€€€ƒ†ˆ‹ŽŒ‹‰†‚~ytommmlmmkjihghhjnty~†ŠŒŠ‰’‘Ž‹‹ˆ†‰ˆyyy|}{{zyxxvvwwwwwwvvvy}ƒ‡““‘‘“ŒŠŠˆˆ”•“•‘ŒŒ‰ˆ))))****+++++,,,-..................../00//00//////......................//..................-,,,,,,,+++++++*****++++,,,,,,+++++++++*****)'&&&&%%$$%%%%%%%%$$$$$$#########$#"#%(0=^ysinvvqgZL6($#$$%&+3;FQ[ennjaZMA3--8\xws~pacht€u@=:5<@ACLf†”––˜™’„Œ‘‘š ¤²¶·´±¯¬­Ÿ™––”nt{zvwvwtssstqomjhdb]YTRNLEDDBABA@AA@?==<;:9875430--@MGGFFFFKQF686530//8IQSSQOMMKKLLPRYWXL;69<=CJMRTVTTTUVQMMSXYVSOLIB81/+%#$$$$$$%%$$%#"$""##&2=?@AACCA?=@BB@?>8,""""!!!!""""""##""$$+)*+,*(((+0//0-**1403879>OWaWQZYQPMKIFC@@CEEHLMKMMLOT[cjsz‚€~~~„†ˆ‰††ƒƒ€€€ƒ†‡‰ŠŽ‹‹ˆ…‚{wrnmmmlllkjhhghhjouz}†ŠŒ‹‰Š’‘ŽŽ‹ˆ††Œ†zxz{}{zzxwwvvvvwwwwvvvy}ƒˆ“‘““ŒŒ‰‡‡Œ’””•“‘ŽŒ‹‰ˆ))))***++++,,,,,-.......////....--//000000//////................//--00////////....///.......-,----,,++*,++++,,,,,,,,,,,,,,+,,,,,++++***)*)&&&&%%$$$$$$%%$$$$$$##########$$##%),6Nswljruqk_O:*$!""#$'+3=HR^dkje]RH=1,5Hk}}€sbcht€e5968?CB[|“‘™ž˜”œ”‡Š‘˜¤£©«¯³´©£‘Œ†tgtuutrqroonnnllifb`__^\\\XUTQMJHEA==>===<:98765210..6/.+&%$$$$$$$$$$$$$$####$+8>@@@@BA>>?>?=<;5+"!!""!""""""!!####$$'((*))(+./,+)*))*-17<=CGNW[aotbUONMKGDB@AEHHIKMMMMLORZbkv|€ƒ‚€~|…‡ˆ††„ƒƒ€€€€„‡ˆŠŒŒ‹ˆ†ƒysomkkklkkkigfghimqv{†Š‹ˆ’Ž‹Œ‰…„Š€|yy{|{{yxxwvvuuvvvvwwxz~„ŠŽ’‘“’ŽŽŽŽ‰‰ŠŽ“•“”’‘Œ‹Šˆ‡))))**+*,,,,,,---...../0////////..//000000//////....////........//--//////////....///.......-,--,,,,,,,,++++,,,,,,,,,,,,,,,,,,,,++++***)('&&&&%%$$$$$$%%$$$$$$##$$$$$$$$$$#$(+2Ek|okpttncUB-#""""#$'-5=HR]fjjbXMB705Gi|~}v}wbdfp€‚~H776=AHjŠ—š–˜£¤›ž›‹ŒŽ˜¢¦­¬®±­¦œ“‹ˆ‹‹„ˆ€kooqqpmmkjkjiihdbe`__ZYXXXWTQSRSRRPG?==;::98776422...=DBACBBDIT>664024BOSVWSSPOLJJLLMQSMNTSLB?@BDIMNMMJDDEGHFDDCEFC@91,+)'&%%$$%%$$$$%%%%%%%%#%,6<:83*#""!!"!""""""""####$$&'''&*,09<3(('&')1=CEMPQUVY_p€y^XNKLIA@BBCFGIKMNNNMNOU^gr{€ƒ‚€~…‡‡†…‚‚ƒ€€‚‚†‰Š‹Œ‹ˆ‡ƒ€{wrnlkkklkkkigfghimqv{†Œ‹‹ŠŠ‘‘ŒŒ‹‡ƒ†Œ†|zyz|||{yxwvvvuuvvvvwwxz~„ŠŽŽ‘•ŽŒˆ‰Œ‘“•“”’‘Œ‹Š‰‡†))))++,,,,,-----....../0////////0000000000//00//..////..........00////////....//......////...---,,,,,,-+++++,,,,,,,,--,,,-,,,,,,++++**))('''&&%%%%%%$$$$$$$$$$!#$$$$$$$$#$$$(.<`tlqvyqhZN5$!!##!#%'.5?=?@?<84-&#!"#!"" """""""""""#&&%&'''(')4:/(&%''+1;BEJNQSSWXYj~]^oPHGC@@@BEFIKLLNNMNMQWcmv}€~}}~‚‡‰‰‡…„‚‚ƒ„ˆŠ‹Œ‹ˆ‡†‚}wtqlkkkkkkkjhfffgimqv{‚‡‹‹‹‹Ž‘ŽŽŽŒŒ‰„‚ŠŒ|yy||}{zxwwuuuuuuvuvuuxz…‰’”ŽŽŽŒ‰ŠŽ‘”””•’ŽŒŠ‰ˆ‡)))))*,,,-----......../1////////00000000000/000/..////..........//////////..-///......////...---,,,,,,-+++++,,,,,,,,--,,--,,,,,,++++**))('''&&%%%%%%$$$$$$$$$$$$$$$$$$$$%%$&)3Oyqpw}zpdTC-#!""##$&(+3Uz›¥¦¥§¢™ Ÿ”ŽŽ—Ÿ¢Ÿœ”Šuiba\n‡v_gghhfecbdeb___]ZXZXVQOMIIIKKKLKKMNMGC?<:99875554110.9HEBEDFFJOF22437HRUTTSSSQOMKJKLKKMMKKJ?0%',29;CGFD@@A@BFJIHFC@6/*'''&''''&&&%$%'('''&$%%%%%&*-38:=>;61*$""%$"#$%!""""""""##"#$$%%&&'(&'(&'('&&%'*26>DGMNPSZcnaN^\ICA??@BFHIKLLNNMNJOS]iqy~}|}„†‡ˆ†…„‚‚‚…‰ŠŒŒ‹‰Š‡„€|wsnmlkkkkkkjhffffhlqv{‚‡‹‹ŒŒ’‘ŽŒŠ‡ƒ„Œˆ{vxyzz{ywvwuuuuuuuuvuuy{†ŒŽŽŽŽ””ŽŽ‹‰‹Ž‘””•”’Ž‹‰ˆ‡†****,-,,,-......./////00//11////0000000000000011//////.0////..//..//////........////////.....-......,,,,,,,,,,,,----,,-,--..--,,,,++**))((&&&&%%%%%%%%$$%$$$$$%$$$$$$$%%%%%'+;fwqr{yk^M:(#"!"#"#&(*1:EOZ`cc`VOQ\m}ˆŒ‡xxv|}k`^dz„ˆŽC+2<>AABFIECA:4,(('*++*()(''&&&&&&'&''&$&%$###"%+.231-'"""#$%""###"""""#"$%%&&)+'&&$&&%%''&&''(()),.18=EJPW`jvu\Y\j^G@AA?AFHIKKKLNMKJLPWemv|~~~}z~…ˆˆ‡†„ƒ€ƒˆŠ‹‹Š‹†…{wsnkkkkjjkjhggggghlqv|„‰ŠŠ‘‘ŽŽŽŽŒ‰…„‡Œ„~zyxy{{zxvvutusuuvvvuwvx}€…‹ŽŽŒ”’ŽŽŠ‰‘“”•”“’Œ‹‹‹Šˆ‡†****,-,,,,....////////00////////000000000001001100//////////..//..//////........////////.....-......,,,,,,,,,,,,----,,,-....--,,,,++**)(''&&&&%%%%%%%%$$$$$$$$!$$$$$$$%%%%&),?q„try€‚ufYF1$#"""#"#%'*19DOZ`cdaZ]gq~†‰Š„y~€j`\^v‡ŒŒz6/5Ow‹— §©©¨§¢œœ—‹‡|‚Œ’‘Šyuvsfagkr}u~lV_cb`\\_]\[\XVSSSQPMKHFDAABAACDDDEGIIJGB<8665666533227KOGGGGJGHL=028FPTTUSRPOPNMLKKKKJIFEB8*###%&'&)+/:<<@DDEFB5-*+---*++*)*)((''&&&&&&&%%&&%$$$#"##"$$""!""!##"""###"""""#$$),15975.)'((%%''&&''(((*+*,/7DMTX[\[^hiWPJC?>?ACFHIKKKKLMKJJOVbitz~~~}zƒ‡‡‡…„ƒ‚‡ˆ‹ŒŽ‹Š‰„„zvpliklljjjigfffgijnrw{†ŠŠŠ‹ŽŽŽŒŽ‹‡„…‹ˆ}yxxy{{{yvvutstuuvvvuuwy~ƒˆ‹Ž’”ŽŽŽŒ‰‰‘“”•”’ŽŒ‹‹‹Šˆ‡†**+++,--+,..//0000001111111100110011001111111111111100//////.../..////////....//..00////.....----..-,,,,,,,,------..,,,.....----,,+***)(((&&&&&&%%%%%%$$%$$$####$$$$$$$$%%%+.@outy€€p_Q=+$###"#"#%(,08CNX^ac__ku}…‰‹‹ƒ|ƒ…|m_[\t…{nv~b21Dp…”¥¨¨¨¨£œ™“„wu„ugelqj`eZpp]qyUSWYYYZZVWVTSSNOPLLKIFDA?=<;jusz€zeWI3'$#####"#$'*-6ALV]`ba_n|…ˆˆŒƒxx{}}o]Z[gf_i{ƒ|H)>d}Ž–Ÿ¤¦¨¦¢—“€u|vprpoleahhdgpeN^Ic…YMQSTUSUUSRRPNHIKJJGEB@=;98999;=IEDCDEDFHTC6BOTUTSQQOONNMLKKKIIGE?2$%#$$%&''(*)4@BB?7.+-./,..--,,++(''''''&&((('%%%%$####""""""""##"""#!#""##$$##"%*4?JSWWVRLA3'$#%&&&&)),/-,.36;eusv|paP=+$#%$######&)-6@KV\adebn€…‰ŠŒ‡uwx{~o\YZfcgpzx|=-Vs‡“š ¥§¥£Ÿš–†z{wqjihjegjhqw{rTHQW~bIOONKPMSRNLJHDEHEDDCA?<6544366779>BBBAAA@@<963465654468=FEDDEECELWG@MSUSSQPOOOMMMLKKJJHF>1$$#$%%((''(*+4960+-..--..-.-.-**($#&'*'&'())*'&&$$$##""!######"""##%$"!"!!""""#(2@LVaihgaZPB3*$$%%&&(*05:=A=8L\lvuvvsngaZQMHD@?ABDGIIJJJKKIIIIJP[grzƒ‚€„†‡‡…„„ƒ€‚…ˆŠ‹ŒŒ‹‹‡ƒ~xrnjjiijjjjihhgfhjjmrvz~ƒ‡Š““ŽŒŽŠ„ƒ…Š…}yvvyz{zxwuutsrrtuvvvvvw~ˆ‹Œ’˜‘ŽŒŠŒ‘’”””’ŽŒŠˆ‡†„„‚**+++,....-.00112222220011110011111111111110000000000000////////....//////............--..--..--,---,,,,,,,,------..--......----,,++**)(((('%%&&&&&&%$%%$$%$######$$%%$$%&)/6Aa~usuxkZJ5&"$%$$$####&)-5?IS\adgnz„†ˆ†}kpwvxzr`]^dguz{y}Žx/>hzŠ“™ž£¢¡ ›‡€zvpggfhgegox|xwob\Wg]CKJFFIJTUMGFD@?BA?@?@>:63310034459>@@???>>>>843556665467@HFDDFEEHOXKHPUURQPOOOMMLLLLKKIG?3$$#$%%''&'(*('*'(,/.///.0.-/./-'%##&'*(''(((&''&%$%$#"""######""##)52%!!!!""""$,8DS_ktxtoh\N?0*%%%').0/4;=<;<=:853211/./11159<=>>>>===;8556776888:1)'),04<<=;998778641//./-,-///269:;>>>====;877777997:<=DGFDDFEEHLPNHNPPONMLMMMMLJKJIGC9&$$%%%&''())(&%$%+../..22/0355/' !#',*(((((''''&&&&%%$$""""""#$##!""#"#!!!!""&0?LYj{‡Šˆzqf\MA8436ASalk[]bgy}‚€wttuusnibWNEBBABDFGIIIJKIIHHGGKR]is~…‡‡‡‡‡‡‡‡…ƒ‚€…‡‰‰ŒŒŠ‰ˆ†ƒ|vqljhhiihhjjihgggiknquz}„ˆŽ”•‘ŽŒŽ†‚‚†Š…€}yxvwyzyxwvsssssstuuuvwy|‚†ŠŽ’•’Ž‹ŠŽ’‘“ŽŒŒŠˆ‡†„ƒ„‚---.....//01112222222221121122111211222233333333330000//////////................//--.-,,,,,,,,,,------------------....--./....--,,+))))))(''&&''''&&%%%%%%%%%%%%"$$$%%%%'+05<867:::867553266510/-,,,(*-,,/1467:<<<;;<=977899:?EGGEDEEFGIOEAKNKLMLKKKKKKJHGGE=+#%%&&'')*,)(&%$%,.///111688:80& "'-.,*&)()'''''&%%%&$$$$$############""!!""%.=L]m‰Š‰‚}umh^TPIFHTbfekeelsvyx|zvuwtrqkcYPHCAADEGHHIIJJJJGGFGLS^it…Š‰ˆ‰ˆˆˆ†„ƒ‚‚„‡ˆŒ‹‹ˆ‰†…‚~xrnjihhhhhhhhhggghjkprv{~ˆ”•‘‘ŽŽŽŽŠƒƒ…ˆˆƒ€}zwwyzyzvvutsttttttttsvx|ƒ‡ŒŽŒŽ””Ž‹ˆ‹Ž‘‘ŽŒŠŠ‡‡†…ƒ€,-./..../001122222223321121122111211222233333322000000//////////................//,,,-,,,,,,,,,,------------------....--./....----+))))))('''&'''''&&%%%%%%%%%%%%$$$%&%%)-33=JYgrngfid@$%%$%%%%%&&%%(*.6ANat|yumaJ4.6Yxsry{laagir†ywwzzdK]pxy€ƒŠŽ’Š‚vlhddgaY\binv…|rllnqsv>1475455564322210231/..,,,,('))+,/02389:<<<<<;:89<=?YbHB@>BIIFFGFFHGHJBCHJKLKJJJJKKJHGGD@5&%%&&(***+*'%%$%,.//.048=>;=;3' "*012/-,++)''))&%%%%&$$$$$$##########""!!""'.FHJLIFGIJKJHFEC@9)$$&$(++++*(%%%&+.00.1:=@B@=:4(%-24785/-.*&&%&''%%%%$$$#%#%$%%##""""##"""#%*4GYj}†‹ŒŠ‡†ƒ~{zwmlgY[W\f^F@F5<`v|wssntog\SJDCCBEGHIIIIIIIHHEHMT_mxƒ‰ŒŒŒ‹ˆ††„‚‚„‡ˆ‹‹‹Šˆˆ†ƒ}ytoljhhhiiiihhhgggiknrtx{€Š’••’‘ŒŽ‰ƒ„„ˆ„~~zxvuwxxwvuutsssssssuuvy{€†‹ŒŽ””ŽŽŽ‹‰ˆ‹ŽŽŽŽŽ‹‰‡†††„‚‚ƒ„........00011122223322222333332233333333333333334411000000///////////.............-,,,,,,,,,,,,,,,----------------....--/.////--,,+)*****)(())))((('&%&&%%%%%%%%$'+)'%&(+,/5>LZfoqkgcfcA"$#%$%%%%%&))+3DTl…ˆ„{tj_VPIDFRabf~zncahk~‰‡{ssuvw|yOQgljkoustkgjklot|}€†‡„„yq^MD;1224---.0011.///,*++++-,,,++++(%&&'(*+.135787567779::>BDFHJFFFFHJLMKJIHKKJG@@DHJIDDGIJIHGEB?9+$$%&(+,,,+)&%%%*.00/6>@DCA?>3( !"'07<<99852.(('%&&%%%%$$$#"#$%%%%#####$$$$$#$(2GYj|‡ŒŒŒ‹ˆ„…ƒ‚{un]\cieZPVO?Kj}~vthC`qhaUMFEEEGHHIJJIIGGFFEHMT`nxƒ‰ŒŒŒ‹ˆ†„ƒ‚‚„…ˆ‰‹‹Š‰†…†ƒ€|wsmkihhhhhiihhhgggiknruy{‚””’‘ŽŒŒŠ…€ƒˆˆƒ€}zxvuwxxwvuutsssssssuuvy|‚‡ŒŒ‹–“ŒŒŠˆ‰ŽŽŽ‘‹‹‰‡††ƒ€‚ƒ†‡--..///-/011112222222222233333223333334222333333332211111000/.//////..--..........-+,,,,,,,,,,,,,,------------------............,,,+++++****))))(()('&%%%%%%&&&'*8C7'&'&)-06?L[fotpigbeZ/$##$&$$&'(,3=Ocx…‹‹…wld`YWRNOXadn{wngfhp‡Œƒyknrprw{WAWbfacegwnalos{ƒˆˆ†ŠŒŒ‰~kWD86444471*()/.----,,*)))*****(''''%#$&&''*,/13455333337889448?;697.'(*,--,,+*('''((''&'&%%%#$$#$&%'),./10222222478>CIKLLLMONNQTWXXXVROKJIGBAA?=?BCEFGFD@<5*&&'()*,,.-*('&%(-.0040031:AMmyuvvsokc[QIFFHJJJJJJJJHHFFEGKUamw‚‰ŒŒŠ‡„‚‚ƒ„‡Š‹ŒŠ‰‡…„‚€€|vpkihhhhggiihhhggijlosvz–‘’ŽŒŒ‹†€€„‰ƒ€~{wvuvxxwvtttsrrrrsssuuw{~ƒŠŒŽ’”‹ˆˆ‹ŽŽŒ‹‰†ƒƒƒƒ…‡‡‰--////0011111133333333333333333344443333443333333322441000000000................,,,,,,,,,,,,,,,,,,++,,--------------......//..--++++++**++**))))((()((((''((((()+?UP<;-)*.2:EP[dmorlhdbegC#"%&$%+.;Oi|‡†‰‰ˆxcXX^a`_]ZWWe{{ozsggfmtxyvpomoruz\36KVVXXR\ntxrbk~zˆ†}iP@98865889;?5('''*,*)(('%%%$&%%%%$$$$$$"!$%$&(*.,-///0111499?EGMNOOOQTUVWYY]ZYXURNJIIGDB><=ABABCB>8/*''&')**+,,*(((%'+/03@BGJJH@2' &5:<=>=93/--.--*(&$$#!!!#"!! !"$$%())*.-,.04EZp~}„ŒŽŒŽŽ‹Š‡}uj`WOE7.(&#&*6:@NYiottnf^SLHGIIJJJJJIIIHFFEFLWalx€ˆŒŒŒŠ†ƒ‚„†ˆŠ‹‹Š‡‡†…ƒ€{voljihhhgghhhhhghjknotw{„’”’‘ŽŒ‹Œ‹‰ƒ€€…‡„}|xwtvxwvvtssrrrrrrsstuy}‡‹Œ””‹ŒŠˆˆŒ‘‘ŠŠ‰†…ƒ‚ƒ…ˆ‡ˆ..//0000111111333333333333333355444433334433333333334410000000..................,,,,,,,,,,,,,,,,,,++,,--------------.....///..----++++**++**))))(((((((((()))))**2IXK:-*+/6>GR^gkkflmc`cif;"$$%),5Ih‚…„…†…|oXJNPX^bb``\Zc}~{‚ujjhkmouxrmnnruw^1.7KQPQMNX^ZRFBi}{uzvkP;56:987768;<;.&%%&''%%%$$$$#$##$##"""""!!##$%&)+,,----.0147;>FJKNPQQSVWZ[\Y[]\ZYUSONNKIGDB>>@@AA?<5/*''&'((*+++*(((''+..4BBGIHF?-#  )5;=?>72.+)&$(,,*&$#"#""!#"!! !!#$%'*)*,,05;9ESd{x|‰ŽŒŒ‹Š†yskfZRB9.((+09DFLONQ\kkicWOJIIJLJJJJIIHGEEEFLWanyˆŒŒŒ‹‰†ƒ‚……‡Š‹Œ‹Š‡†„ƒƒzumjhhggggghhhhhghjmorux~‹•’’Ž‹‹‹‹‹…‚€€……‚}zxwuvxwvutssrrrrrrsssux}„‰ŒŽ•“ŽŒ‹ŠŠˆˆŒŽŽ‘‘‘‘‹Š‡…„„„…ˆ‰‰Š////000022221133333333333333444455553333444444443344432011000000..........----------+++++,,,++,,++,,,,,,,,,,,,------.....///..--++,,,,**++**))**))(())'')+**,,+,-09LT<--/3:BLU`hjga`khbbcoc/$(+/>^~‡„‡…w]B5=<94.*))(((((*++****(')*.4AFHHEB7) "#,6=><940,*('$$'(*($###$$"!"""! !$%'(+--/3665IF53Vd]H<55689;;::979:;=-%%#$%%$$##"""""" !!!! #$&&%&(*+***,.49;ENT]fjjbUKHakecdnq>!(*6Qy‚}~sW9&(+,153,131*1DC843247:::;;;78:;<6(('&%&''%$""##""!!!!!! ! "#$%(***,,,.367;:4/*)&%#""!"$!%&)))&$#"$%%%&&*++,./-.;KXctxws|{„‡ŠŠŠŠ‡„y|‚…ƒ|xrib\VOB:>:=@CEGHLNUQVZJHVS_i\MIJJKIGECDHQXdoy€ˆ‰‹ˆˆ…‚‚ƒ„‡ˆŠ‹‰ˆ‰‰‡†„{vqmhgfeeeggghghhimmpqtvŽ’‘Ž‹‹Š‰ˆˆŠ‡…‚€€„†‚}{yxyyyutssrrrrrrqqssuv{‚Š‘”’ŽŒŠŠˆˆ‰‘‘‘‘ŽŽŒŠ‡††„ˆ‰ŠŠ‹ˆ‡001122333322222233432344444444445555554444444455554433323333200000......------,,,,,,+++++++,,+++*,,,,,------,,------......--..--....--,+,,,+****))+++--/0122334444444>JC9BLT]fklj`UE8Hiidbita*#+:Z|}xwc7%'*),,059@EMS[frx„’…yxse``aipofgkmnor],(*-129FA@<6**)('(,/023598::9:;;9779<;-(*+,())*)('%#$$""""!!  "#$%&)))*,,,/368905VpiccjqH$)8Z~{xs\.&&)((+,039?FKXiwyƒŒzud^\]clphggilorZ-'*-5;9RH=:92+%'$&&)-.27@=989:998668<<7-+-/,,,...,)%#$##""" "##$&'())),,/373139>AHMPQOQSUVXZ]][[adcbb_^_ab\RTRG;753320-.-+++,,++,,./003;@CCA;1$!!%'(-26:93-)(&%$""$%#$%()),-(%$$$&''()**)(+/8ETU=5*'/BXk}ˆŠ‹‰ˆ„}ukhqx‚~xsleaYRKCBFFFIPQSWTLIPNORYeV]]MKJJGEDEJS]fqyƒ†‡†…„ƒƒ…‡Š‹‹Š‰‰‰‰‰‰‡†‚~xsokggedffggghghikmprtvxˆ“ŽŒŠˆ‰ˆ‡„…ƒƒ„~ƒ†ƒ€€|xxzxwtsssrrqqrqqqstsy€†Œ’‘’•‘Œ‹Šˆ‡ŠŒŽŽ‘‘Œˆˆ‡ˆ‰‰‹‹ŠŠ‰†„222222332222333333433444444455555555555566555555556655554422111100/.....------,,,,,,,+++++,-++++,,,-,,--,,,,,,------....--------------,+++**,,++,-/03367668899888:;;;;>JURT\cjrmcWJ8.,?fmebelf;$5Qw|vq^6&'))'()*.38>GWlyu|†Œƒzvh^\[^chggegmprZ+'*-5AGNRTPRSSUUWXX[\]a_`__[[YY[VSNG@674210---++,,,,--.1333;>AA?9/$!"(+,/193+'&$&&#$#&))(*..-,)%$%'*((((((+264<1*''*5CZn~‰Š‹Š‡ƒ|qjeiqx‚‚}xsokeYRLJIOOQUY\\VQWRKNTWZUIPXKDEEEDFNT]grx‚……„ƒƒ……†‰Š‹‹‰ˆˆ‰‰‰‡‡†}xsokffedffggghiijlnpruv}Ž“Ž‹ˆ‡‡††††…„„€€……ƒ€~|yxzuwtrssrrqqqqqqrst{ˆ‘““‹‹Šˆ‡‰‘‘‘‘‹‹‰ˆˆŠŠŠ‹Œ‹‰ˆ†‚333333331344333322444555443345555555556677555555556666554432221111/...----,,,,----,,,,,,++++++++,,--,,--------------....----..--....--,+,,,,---.1222559;::;;;<;;<<===@CFOVZ\fnvnaSF5.,3Mpifcem]/*Dj{upeA&%')'&))*/48AZryos‚ˆzvna][^efffcfjlkZ-%&,17Jg[N8//,++((*++*,.13224577685596582*)+,,++,,+,+)(''&$  ""#$&''''''((*(),/28>AKQQRTSRSSSTTRUXZ\Y[^]]dc`ZRQQMI887211/0-------//34548=>><6+##$&)/19M`lpk_RA3+*&"""&-64688830-,*++*))))&%*+'%%'''(2C\p‹‹Œ‹‡„|pc\`hry€„‚}ztpkhbbca`_b_^][bmhY[^]YWP@8OVeZGFFGLU`isz€ƒ„„‚ƒ„„†‰ŠŠ‹‰‡‡ˆˆˆˆ‡ƒ}wrnjgfedfffghhiijmorstyƒŒˆ‡†‡††††…„ƒ€„„€~}|zyzvvsqrrrrrqppqqrsu{„‰Ž”‘ŒŒ‹ˆ‡ˆŽ‘ŽŒŠˆ‡‰‰‹ŒŒ‹Š‡†ƒ333333333444333322444555444456555555556677655555556666554432221111/...------,,----,,,,,,++++++++,,--,,--------------....----..--....--,+,,,,-../24758;>?=>>>@@@AAABBCDFJLSVYZjvn_OD5/,-9ZtiedjlZ.2YwvpiO*')'(&)*)+/5D`wsjn~ƒztoqf^\\`bffcgmlm\.&&++0MgYRQ9**++,,-,,-.---//14587556875@P+*-,,+++*+++,*('&$""""##$$%%''((*/98?ABACDCDCDDDEEEGHKQY_^\cpkZL?3/.-0AipldfmsY-GlwumY5&()'))*,-1;Sqxljr||tomqm`][_acfgijlm_.%*,.1Pe]WUV:+'(((()*-./-,,-.0445314433KxR!//.---,++*++*('&%" ! !!#$$#"&&&(,37=EJOSWSSPROQNQOPRTRZ`efghddZN>QNY@0;=7220000100168862452*&#%&(4J^p‚‰‹‹ŒŽŽ‹„zkT1):EMIA?EFFEA;:8:::<<:863,&! """%'7NdwˆŒ‹‹‹‹†}ncUSYckt{€†…ƒ€zzyxxwmlnmuyupklqosrikjkno]NTOGCMUZbirx{~€€‚…†‡‡‰ŠŠˆ‡ˆ††‡‡„ƒ‚}yupnjggfffeggfgijkmprvv‘ŽŠˆ‡††‡‡†ƒƒ…‚€€„‡ƒ€€~|zyxvvsqqqqqrrqqppqrx€‡‘”’ŽŒ‹‹ˆˆˆ‹‘’’ŽŠ†ˆˆŠ‹ŠŒŒ‹‡†„„ƒƒ2222333344465544444444445555554455556666556666666666666543443222110/..----,,,,----,,++,,+,,,-,+,-----,,-..--------....------....//../.,-..//113689<>?ABDEFGGGGGGHHIIIKJNU]dggadg[L?2/.-04TpkidhruJ<@LJID?967:;<<9998-$ !$$$*?Si}‰ŽŠŠŒŒŠtdSKPZeov|„††…ƒ€€{zwuqsxzyxtufadkjpqong_TWgbfTLT\ejsvz}€‚†‡ˆˆ‰Šˆ‡‡‡…††…„~|xtomifeeefeggfgijlnqqtw„’‘Œˆ‡†‡ˆ‡†„„…€‚†ˆ€€~|zzyvtsqqqqqqqooppqry€‰Ž’•’Ž‹Œ‰ˆˆŒ‘‘‘Šˆˆ‰Š‹‹‹‹Š†„ƒƒƒƒ3333444444454455554444555555555544456666455677665666666654443322110/..------------,,,,,,,,,,,,,,------,,--,,----------------....////010...013559:;?ACDDEGIIJJLKKLLLKLNOSY`gmpmd\UK?62////;_phedjtk@UwysiF(''))()*-3Fi{rkn|zqmkdavk\]\`cdcchjj]0'*+-3XhZYYVRJ>,&&'''+23/..,,-../24>FPaxŠ”w+13234321/-+((%#"####"   !! !#$&)-3:@DEFHHGJLNONNMMPOQU\`feijgd[RMJZeXF?<:7320112366876332+'')Ai‰Ž‘’‘Ž‘‘ŒŒ’†lB?FLEDMITYUOMIB==>AA>>?<2($#%%&'.BXmŒ‹‹‹‰ƒyeNBHR_ipx€†ˆˆ……€~yvutuqtwwkfc]Z`^bfb[a\Z=AQd]^[_emrvzz|‚…†‡‰Š‰ˆ‡……„„‚‚}zvsnlieeeefffffgijnosst{Ž””ŒŠˆˆ‰‰ˆ‡…„…€€‚‡‡‚‚~|yxyusrpqqqpppoopppu{‚‰‘”‘Œˆ†‡ˆŒ‘‘‘‹ˆ‰‰Š‹ŒŒŒ‰ˆ…„‚‚„†333344444454445555444455555555554445666645567766566666665444332211//..------------,,,,,,,,,,,,,,------,,--,,----..----------......0335400023378<>?CEFEHJMNNNOPPOPPPOQRRUZahnospbRC>7543//1Cjpfeely^Nt|voP+&(**((*-2Jmyolrxnlgbdopb`^^acabeih\0'+,06_e[]][POL@.('&'(.2/-,,*+,,-7BNYix„Œ–˜[-4566321/-+*)(&#"""!  !#$$&(-269>ABDEEINKOPPOPMOSV\l_aoljc[QLYe_UOIA98852212567798662,(9b‚‡Œ“——”“•–’‹Š’Ž{R@ABFGKMMOOQQSUUTTTTSSSSUV\ciqqqoe[PC<8542105Pqieffru]q€ysY0&&)*)),.5Rqvmmu~qeggiiith_^\_``cdghZ0&,-1:ee^`^[OMNQD-)('%'//+)+**+/8GWbn{†Šˆ–›“6$/3444400--.-,)&$!! !"""#%)-.16:>@BCEMSRQNMIHMQU[ejoplmj`SNK`cUPPNI?8743334478:<<<949Z‹Š’—–“•šš—’ŒŠŠŒ‘…^>IIJHLMRTZ^_bcd]Z^]ZXTE0%##%%&&-ASi}ŒŽŒŠŠ„|pbE/1>JXelw‡‹Œ‰‡†…‚€xjaZY^\WWW\^^_jeXiƒˆ}LZa^ZYforsvz|‚…‡‰‰‡„……ƒ‚ƒ~~~~}zxtqkkgedeeedeeghilnpsss‚“““‹‡‡‡ˆ†…„ƒ€€ƒ‰†€€~|zxvuqqpppoooooonopw}…–“ŒŒ‹‰ˆˆ‹‘ŽŒˆˆ‰ŠŠŠ‰‰‡‡†…„„„†44444444554444445555444444444455555566666677776666667634554433331100/.----,,---,----,,,,--,,,,,,----..,,,,,,..--......--------//134468;<<<<<;;=@DDHJLNOQRSUWXYXYXXXXXXZ[`fkpsqoeZQPKA8433208^qgdhnvuxƒ€xa3(*(**+,.enhfhqy~yi>%'*())*0Bdyolkq€ukhgiigflc_]^bbdgfgV31114Gfc]__YFFNSTRC+&$##'6=69?EOX`jx…Š“–›™U*,.10/...-,++*'$"""%'*+++,/37:@JKQQNJOLNPSW^^^acaaZVYSUc^QONNNLHE>72.+//02345Os‡ŠŒŠŠ‘€ps€Ž’Œ‡‡Š‹rHAHIJLNQSSSVWYWW\^^\XUE,!! "$%$'4Jbw„ŠŠ‰†~uiR3(56>DO^o|‚‰ŽŒŒŠ…‚€ztneZWVX^djdedRL[{‡‰Ž}of[Valonw}„†‡‡††…„ƒ‚€{|{{{yxtokheddccddeeeghloqrty–”‘Š‰‰‰‡…„ƒ„ƒ€‚ˆ„‚‚€€|yvvusqpqppoonnnnlot{„‹‘“–’ŽŒ‹‰ˆ†‡ŒŽŒ‹‡†ˆˆ‰Š‰ˆˆ†††„…†…ˆ44444444554444444434444444444455555566666677777777778DWN96544432310000....-,--------------..------..--------......------....--//23579;?DDEFEBACEIKNQSTVX[]^_````_`````bdgkpsvtrk`WMHDHMKA4101Gkkdchp}…€zpV0(*(+-17Jiumkhozojhhiieceha`_^bdeefU31112Pga_c`WEFKPQRP?)###%1DNDCMSZbiy„ˆ‰‘— –;/.00//./.-++*'$"! !#%&&*,,-/37?DEKMNKIJMOQSWZ^^a`b^YUSUc]QPNNNLHEDC>830.,+//Diƒ„„ˆŠŒŽˆwU=AYx†…ŠŒŒuPDHIIILMNORSURRTWYXVRPC.!! "$%$&2E]r‚ŠŠ‰†ƒ}sfR/&7;?AMany…‰ŒŒ‹†‚yskcYSQPKOVX^_^_v‰Ž‘•™œ›ŸŸ—}WU[TU[`cp~ƒ„ƒƒ€~|{zz{|ywsnjgdddccddeeegimpstu~’—”’ŽŒ‰ŠŠŠ†ƒƒ„ƒƒ‰ƒ€|yxvtrqqpoooonnnnnpu|…Œ‘’”•ŽŒ‹‰ˆ†‡ŒŒŒ‹‹‰ˆ‡ˆˆ‰Š‰ˆˆ‡†††††…ˆ44443455444444444444555544334455665566776677776666667:GN<555444331//00...---..-...,,--..------..---...........------------....//1469;>BHJMMLIGGJLNPSWY[^_bbcddddccddeeffjnquvwrlcZQJCBBFIID:10Pofcbfr~„~ykWC536:BJYoqjgenumkijjheecmda`_bdddeS0,*,2[idaghWIKMOOOOL9&$%(0AVXKHRXagr{‡‰‡Œ”šž„%(.320//.+)***(&# !""$&'*-/24:ADGPTYYZ[]^emvustpkf`_ZPWf`SPOOMKIFDBBC=:421+2\~‚ƒƒ…‡Š‰€mK*()=cƒŽ‡„‡ŒvPAIJKKKMOONNNKKPSSONMKC.!!""$%%(2H]rƒ‰‡†ƒ€{obM-)+/6?Malu|‚…ˆŒŠ‰ˆ…yskfYUQPR]_]\bgo{’•› Ÿ¢¥¤œWLNSQJG@;ALVl~‚ƒ~zyyz{zyvsmhedddddddeeegjmqstu†–•”‘Œ‹‹‹‹‡‚‚ƒ‚€†‰‚€€‚€€}xwuspoppppnnnnmnopu~‡‘‘•”ŽŒ‰‰††ˆŒŽŠ‹Œ‹‰‡‡‡ˆˆˆ‡†……†‡‡……„Š‘44443455444444444444555544444455555566776677776666666657:3554443310000....--..-..-..-.//------..---...........------------....//357:<@DIMORRPMJLNQSUXZ_abeegffhhiihhiiiinqtuvurlcZTMFB@>=DMNE?>\ldccis}yrf\UV[`dlrnigdlxoijihfbdbmlfecdfddcR0)**<:=CEEOghddfoy||wysppprropnhefbk{pgfffeccbmvjfeadcdbQ,**,IlgfhgfKKONNONLLKH82599=SZTOSZ_iq|„‡‰‹‘•š›™C*,,***''&&&&&%#  !"!&'*/9Nahjjijpyyvustsqooponprrtsqm`UQOONLIGECBBEJQ``P[{€{||€„~m8&(%'/NwŠ†ƒ†ˆ‚hCDIJKKJKNMMLFFJKLLKJIG>.! "#$$%(3K`w†‰…‚~{tl\G.&1)(:P[elqw{~ƒ………„ƒ€{smijkhaZ^cgkpx‚‹‚B.0;M]`nqtfVXchimmh^RVgy{xyxzzyxupkfdbbddddddeegknqss{’—•Ž‹‹ŒŒŽ‡‚ƒ€€ˆ‡‚‚ƒ~|yvtrqpoooomlllmmosy‚ŠŽ“’Œ‹‰‰ˆ‰ŒŽŠŠ‰‰ˆˆ‡‡‡ˆˆˆ‡‡‡‡‡ˆ…„„†4433444444445544555544445455445545666667776666665566555553554333332211110/..-../00//////..//..........////..--------....------/0/28;@DHLQVY[[ZUSTUZ\^begjjlmmnnnnnmnpprsuvvvtsoib]UPMHEB@=9856;CRfeceisyuxvttuxvsolfbbebi}pijhjhiijo{nihcceb^N,*+/XlhfjnaELPPOPNLKIGFB?<=;=E9.#!"""%(8Pfz‡‡‚~~{tiX>+,0,.EQZ^gkquz|}~‚€|yutspngefhkqxƒŠŒ‹\2DL\ba]nsm†Ÿ£©¦“ˆ€‚ogmvwxzywxsojedbbdddddefgimooru~—™“Œ‹ŠŒŽ‡ƒ€ˆ‡‚‚‚€}{yvtrqpoooollllmmoszƒ‘‘‘ŒŒ‹‰‰‡ˆŒŽŠŠ‰‰ˆˆ‡‰‡‡ˆ‡††‡‡ˆ†ƒ‚ƒ‰5544443333444444444555444444445555775556666666666666665555542244332111000//../....//////////////......////..../---..--..------/0146:@DHLQX\^a`ZXYZ\``dhijlmnooooooqqrrstwwvutqmfa\WQMJHDA?;96213?9;54BQZbglu€ƒ‡‰‘•—˜šs $'&'%%$$""!! "7[lica`acirzwuuwvtsrpnnlllkkloptz€wZLONLKGECCCCJT`kt€„}y{{xuoJ)&&%&&.Ntˆƒ……zO7BFJLLMLLLKHEIKKMKJHFD>.# !!!%,AHLPV\`cda^\]_abfhilnppqoooooqqrsuwxxvtsokfa\VSPMJFD@=:74111GjeacilXKRUTTNJKWhfa_\Zdxpjidgjjgdexxkhfchg[J,'.PnihlojEELPSTRQOMIEC><@E>;9=9?TV[`gmv~„ˆŠ’•—˜X&&$$$$%%""!!  4Zkje``bcclvyuvuxxxxusqpnnlljjjklnsz‚y\ONLKGECBBEJRant€}ywwsp`9,,(&'(/Os‡‚€„„vI7BFJLLMLJJICEIJIIIHIGE?-$##!"#%,@Xm€ˆ‡‚€zrdP5""&2BMPVY\aeikmqtxz|||{{|{wtttrptx~ˆŒ‹ˆ‚71EB=8@ARdQYafku}‚†‰‹Ž’–—‘=&((%&%$##""!! "Eloe^\`ddfjxzwvwxy{zyxvurqpqmkkkiijlr{„z[PLJFECDCDITant~zvsstqrZ7/0,(''2Qu„€~‚ƒp=3AHKJLMLJIABGIIIJIHIHF>,"#%#$%'/C[qƒ‰…ƒ€}ypaM2 !%9KPTVUX\^cegklsvxy{{{}~zzxxuvy|ƒ‡‰…„33:74213Nj`]`gg5'*++,.9Re_WVPMVxshggcekejkn‚tiffmv`C&BdjhilpqNBKPUVVTROLGEC?=9:@JJ?9>E[XPX`bju{„‰ŠŽ’’•†- ()'''%%%""!!  /]nk_^`cegjqzwuvwxy{}}}}ywvtrqonmkjfhgksyqULJFECCCDJTblr|zrppqnnmaE1//++5Uw…~~€i2#5EJKNMLG?=CIKJJJIHIHF<*"##"%%&0G_wˆ‰…ƒ€}wo`H-#%,BPTWWWYZZ]\`dfkqtvxyy{}}}}|xz}~€ƒ……‚y77;AMRJDo¦·¾¶ˆ| ´º¸¶‘~Š—–’˜‚uvutokgecbaabbbbcegjmost|“™—ŒŒ‹Œ‰ƒ‚ƒƒ‚€€…‰…‚‚‚€}zvtrrqpnnkkkklllnpwŠŽŽŽŒ‹††ŠŒ‹Œ‹‰Š‰‡††………ˆ‡‡ƒ‚‰’445543555544444444333353444455556655555656665566667777664444443344343311111100////////0000//./00////00.///0000/..-....----..../134679=;859FI?:9BIPPV^fmu~„‡ŠŒ’“w%"(((&%%$! !""Bjnb^`cefjpw{wtvxw{|~}~~}zxuvwtrqojgfeffmsz|iPJGFCECGLUbrxyupmnpoopomaG2-,7Vy‚~€~d0#$*;FHJH?53<:95328CG>15=CFQ[^enu{‚†ˆŠŒ‘”k%&&""#" ! #Nnl`_eddirwz|wtsuxz{{|}}||xtuxvtttrpkigefhmszx`KHDCDDHMXfrxwrqnmonnqromk`TB?\z|~€y[(""#!*8AA7/9@BFGIKLJIHD6%""""$$(:Qi{ˆŠ…„‚xmZ?&%1DRVY[^__ZYWTSY]ekmoompsx|}}‚‚|‚ƒmVCFEIE@a“¨²·¸ªŽ|†–•›•š”’’“›¦•ƒronhfdbbbbaacddfglnqrs„™™”Œ‰ŠŠŒ‘Ž‰ƒ‚‚€„‡‚‚‚‚€~zwusqooonmlkkklmknry„ŒŽŒŒŒ‹Œ‰‡‡ŠŽŽŒŠ‰ˆˆ††††††„ƒ…‹–•’Ž65554345555555444433443333445544555566666655445566756666545544443333211111110000//////0000//0000..//00//////00/..-....----..000135689XdWSQRTXirfabcaailrpq€ujffhq{zyuonprnNDJNORRROKJEA><:75331/2@C:37;9653321/2<=437AGRYYaemuy‚†…ˆ‘M  "%(+-% 7amhdgimmrxunprrqposvvwyzphcehmnopnmkkrxwoihjlmrtsdMHDCGKUcotvtnmmonkntrpljhimr{ƒ€{~|sK$!$$"$$$'*+).:AEHIKMLE<0&$$##&'0DYq…‹ˆ„‚whT4%0BLRYZ[^``_\WUSTY^afhiknpuquz|}€‚‚‚€„€n[ZZTQHDqœ¦¥£ž•—˜Š‹™—–“–š˜•šœ“‰‚jedabbabbbcceghkppsv‹š˜•ŠŠŠŒ†„ƒƒ€~€ƒ‚}zvsqnnmlllkkjjkjlnt~ˆŒŽŽŠŠ‰‹‹ˆ†‡Œ‘ŒŠˆ‡††…„„……‚‚‚ˆ“•””‘55665566645555444455443333345544555555556666666666666666665554443333332211001111/////0//00//0000///////////////..-....----..//123579:=?CGLSX_flqwurokighiot{†‡‡‡ˆ‰ˆ‡…}zwrolhdb^[[YUSOLJGC?;8621./4SdZW[cS&%(+2C]aXRQVYX`ricjjnlkmkllcgie`]cinquzzn^OMNMMNNMLKJIEB?=96522/.../6A?7>GNWaU]dlt{‚‚†‹‘•A !!!! !%(/4-Aemhehjknvwpiknrponoqttuvkcaaagjmnqnlifju{slljjlnpqmWFCCHNXeptxqnopqnjmrroljjhkt}‚|}}rC#$$"##"%%',.**4=BGHGHE;762100//,-7FE?CHNidU_glqx}~ƒ†‹‘•‰8  """""""" !#"'/8HZhhhkmmqwvligillmmopqqtr][[[_`ekopnoqmjditwqokkmlnnreIDBIQ[hqwunnpppmfirrpnkljiq{„€z|{k;$&%$%%%#%',12+$(6ADGFB7+#$%$$&*6Mcz‰‡„ƒ|reL.&7EKOSYY\^]][WUUUX\^abddccikjorx|€‚ƒ|rnkge`\\|ž¤¢œ™“‰Š‡z’“—˜••™¢Ÿž“†}kcbbbabaaccegilqrt{’š–’Š‰Š‘‹…„‚~‚ˆ†€€‚{xupnmllljjkkjkjjmqx‚‹Œ‹Œ‹‰‡ˆ‡‡…††’‹Š‹‰ˆ†…„„……ƒ‚…‹‘–•””’Œ9899998777665554444444333333444444446666556666666666666665664444433322221111111111//////--//0000110000//00/////..-....----.../013579;>@BFJOTY_eiptwsplggmwz}€ƒƒƒƒ…„‚‚~zvusplifca^[YVROMJHFC>:86330//5Y`WX\aC&),0A\]WVVYYY_sqijkmqljjlnjjdXYYR=:>BFRSWYYYVSOOKJGFDB@?<;9740/.----/8FKFFCSrcY`iisz}ƒ‡Œ”‹2! !!"####"$$%$$""(%5HMRZdjootxwiceddgggknpnojRQXW[\_cfhlpsutnjdkyyspllmlorpUDCJQ]muxrnoppnibhqspmmmmlu}„€|{yh7%''%%'(&'*./41*$%-:DC@3*$$$$'',9Pg|‡†„ƒyn_A)(@BDHLRV\agmrvvspljnuy{~€€€€|zyuroljgeb`^[ZWROMJHFC>:86330..1Ib\Y[`T((+0B[]WUUWWX\ovlnquyqoqrkike]\ZL==@HNTXY[[YVSQOMIDCBA>>=<:8520...//159CLIB2F^g]^djsy‚„†Œ•Š2! "$#$$$$$$$%%%$%5SlbNJWblqx{obaacaaccbgklnaAELRXY]bcdgmquuwsmiit~yqnkmmmprcHEJUarvxropppnb_gqupmkjhlr|ƒ~}}ze1&&'%%&(())&%)24,&%%.;>1&$%%('(-?Voˆ†„ƒ€€ynZ8(0?GJNQTY\][]][[[[]^^_`_^]^bcbcgjovy}}}€€{uttrsqop„”š–’šš”v{ƒ‰‹–™›š˜––—˜•ƒ|o`ababaabcegjmqst˜™•’ŠŠ‹‘“Š„ƒ‚€ƒˆ…€}zuspmmkkkkjkkijilns|‡‹ŒŠˆ†………„„‡‹‘ŒŒ‰‰ˆ‡‡…††……„„ˆ”•”””““ŽŒ<;::::::98887766444444333444333344556655555555666655666655444444333322221111000000////....//////00//0//////.....--....-------.012479:=?AEGKNRW]bjpuwvtmlovx{{~~|z|{{zxvqolkiecb_\YWTROMJEEA=977321-./9]aYY_a6&+0C[^YYYZ[[_kwqnmpy{xtwohef^WTG<>BHMRVZ\[ZWUSPLHDA@><<;:8764311/015;?;;JMFAB]g_`dkry€ƒ†Œ‘—ˆ/"&#$$%&##!$$%#*FhuqdLAJ`t|yg_b^_acccaaeikT?CJHOTWZaa`dintyzwtqloy{rmmmmkmmiPILWfvxuqqrqpm\^fptqmkkiks}ƒ{}xa,'('(('(*)'&'(-00'(()*//*)'&(%&0F\sƒˆˆƒ€€yjU1'5EKJLPRTXZ\\\\[\]]\]``_`]^^[Y\`elotwyz}~xrstuwutv…Œ‘“ ¬¬¢•z{ƒ‡Œ’•šž¡•”˜™’‚xdb``aabacehknrtt…——“ŒŒŠŒŽ‘’‰ƒƒƒ‚€€„ˆ…€€€{wvuqnmlkjjjkkkkjkowˆ‹‹Šˆ…„ƒƒƒ…ˆŒŽŒŒŠŠ‰ˆ†…††……ƒ…’••”””’’’ŽŒ=<;;;;::98887766544444333444333344556655555555666655666655444433333322221111000000////....//////00/////////.....--....-------.012479:=?ADEILOSY^dkrvwwrprsvwz{z{{zzzywuroljheba^[YUROMKHEB?;976411../2Ma[Y[bO'+/=X`\\\^^`agwuoqw‚‚wvxrkheUJOA:=@CKQUXYYYWVRPMJEC@>=;::::86532334<=;;=<>S]Zcijcddmsy~€ƒˆ——ƒ+('&%&&$#%$!(Onxi`c^K=<<<;;;99887765444444334433445555665555666666665566665544333333321111111111////....---.//00//00/////...........//..//.-....012468:=?ACEGJNPV[_fjntwurqrtvxyxxyyxxwutonlihfb_]ZWSPNLIFC?>:97422/.--.=^^YY`a;(-=Xe`_^_ciigs}ut|Š…ywwunj_QJPE:=@CGNQVVWVUURPMIFDA@><:::::7544579=<<;=6,3L^fginndbjqw{€„†”—…&!)')'&%$#"5qyrke_]XN?>QRVYYY\^bbeeeefWD=IKKMPPQRUWXYZZ\]_^]]]]\\]^ZWXY[[`ippquqghmosvxwz‹®³«žŽ…„‚‚ƒ‚‚Š—œœ›Ÿ¡œ–’˜‚~f`_``a`adhmprtwŠ˜•’ŒŒŒ’Žƒ€‚‚€„ˆ…€}{wsqnmlkkkkkkkllkmqx‚‰‹‹‹‡………ƒƒƒ‡‹ŽŒ‹‰ˆˆ‡†††……„‡”–•”””“‘‘‘Ž‹@@?===<<;;99887777555544334433445555665555666666665566665544333333321111111100////....---.////--00/////...........//..//.-.....02468:=?ACEILMNRV[`glpvxxtrtvvvwwuuvvutsnmkgfe`_]XUQNLJGDB?=;:8641/.---2Q`[Z^bR*,9Udcddelppir~z}‡Š|v|unf`UK=:9=;:;:7544456;>=<;;3/+*<<;:::88776666554455444444445566665566555566665554443333222222011110//////....---.--...........---............//.......02458:;>@BDFILMQUX]dhlquxtrttuuusssrtrroljhfdb_[YVSPLJHFCB?>:87641/-,,,-A_]YZ^b=)7Qffiknqutpt|‚ŠŠpmuslj_XH789:<=AEKPQPPQPONOKFB@?>=;;988543237=>><<81...+0Jchhqqogfmsx}ƒŠ“–(!'+('"3suokkmmloo[C8Rfz‡‰…‚}yo]:+9EIMOOOPOOPRRVWYZ[^^_`]YXXW][TSUYed_dehk]V^fmtz{‡Œ”¡ ž—“‹‹‹††ŠŠŠ‡‡‰›š˜ž˜–’ƒyda`aaadgiloswz’˜’‘ŽŒ‹‰‰Œ’‹„ƒ‚€€„Š…|ywtqpomkjkkjjjjjjkns}…‹Š‹Š‡…„„…ƒ†‹ŽŽŒŠ‰ŠŠ‡†††…ƒ…‹’”•••”’‘’‘ŒBAA@?@?>>;;:::88776666554455444444445566666666555566666644443333222222011110//////....------...........---............//....../123468;=@CEEGJLPSVZ]cglquuqqqpprrppqpmmljheca^ZXVTQMKHFEBA><:76420.,+++-3O`VUY`W,2Jaklmqvxvqqs€mtlnvieh^XWB8::99:=AHNPMKLNOONLHC?<;:8778854325:?@><<71//0/.+5Sjjjurhejr{€ƒ‰‹’v$('(&jwmhjprttxrZG<7GIESWYZZXE657:81:DB@BDHIQ]dgfghginnprvxwvyzuojhea_`a_dswvsqrrpiYNOU`ilijljjq||€m<%'()))*+*+**++*++/471&'()),18>@CEGINQTWY^chnnoommnnoommmkkjhgeba^[YTRQNKIHECA>>;86631/.,,++,.<\XST\_A)=Tflos{zvolgin[bq†Œxg[OSSB::;9977;CJMKHFJKMLKEB<::88887643237>BB@><5111100/,'@]flmpmjmvy~ƒˆŒŒ‘O#(&Qzqkjpuw{}`VG;3;B;?IUZO5)6;9=@=;;>BDGIQZchifabc`_eikoqstsrqqjd`_^`_chwxtturqodRMNT_fhjmkikr|„}€m@2.*++++****+,--,--08><<:988777655433344443344665566666655555555555544333322211111111000////....--,,,,--....------........--...........//0123578:;=?BEGHKORVXY]begikkkiijjiiihhfedb_^[WUSPNLJHFCA?=<886420,+++++,,2M_VQW[W,0=P^houztmh_WQJZr…Ž†€sfe`I=9;99778>CIHEEFHIHGC@<:85545653357=CDBB=94222010../,+Dcjmpqmpsv~€…ˆ‰w $"B{qhjovy|‚w\VVI9257876;8,05679:>>>EHLQTZ[aijfa^cf_Z\_abfoqmlnqjd`_^^]alvtqqrqoo`LKOU]cekmjjnu}†‚‚„rRIE<0,,+*++*+..,+--.2:92)-8>7=MbtƒŒ‰…„ƒ‚€ueJ/'0>FJKNQPPOONMNNORX]]^_^[WTSW_fhdcbejklnjcipv|‚‡‹ŠŠ‹‹“š¢£ š˜™š—•––ŽŠŽ‘’““•‡„‚vpnmooqrswxy…—–’ŒŒ“‰ƒ‚ƒ„ˆƒ~~{wurpmlljiihiiiijjmpxˆ‹‰Šˆ……„…‡‹“’ŽŒŠ‰ˆ‡…„„‰”••”“’’’‰MMIHGFDBCA@?=<;;8986556666554444444455555555556644444445544333332222222221//////......,,,,,,--,.---...------------..--..........0023668;<>@BEHHMORVXZ\`bdeddbcegfeefecba^[ZXURPOLJHHEAA>;;86421.++++++,-,<[YRU[bE&19EW`ksoha\XUIH`|‰…{uujN<9<:87658=@A@BCCCCDA?<96430//146;?BEECB=6323311210320(3Rgmmjt}wtvz‚€j7 4uujjnvw~u[TUVN>423565432788::99CJNPT\_`^ejhe`cghec_]YPP`omjlqkb]\]^\^ovqopqrqlRLLOSX\ellgimv€†……ˆ†ylc[SD4-+++++++,++++,.4;=033:>DZnŒ‰ˆˆ†„{uj][WLEADFHNONONLJD@ILTZZYX\\[WY^flooprsstqsrqsw{‚ƒ†‹Œ‹ŠˆˆŠŒ‘“—š™™”“–˜˜–“‘ŽŽŽ’”†…Š„}wustvwwxwz|Š•”’‹‹‹‹Ž‘ˆ‚‚‚‚€~~„ˆ~|ywtqpnlljjjiiihhkklqyƒŠ‰ˆŠ‡………‡‰Œ‘•’‹Šˆ†„ƒ…‰Ž”•””“’‘’Ž‹NNLKIHEDECCA?=<<:996556666553333444455555555554444444445533333332222222221//////......,,,,,,--,.--,---------------..--..........00234779:<>?ACGJMQSUUVX\\^]]^_acba``_`^]ZWVTROOKIGFCA?><;98521.,++++++-,-1G_WQZd_0(0:MX[ced^XYZRJNlztpoorbQA<54421/14554678;<><:;964332158?BEFFEB>9311222255555450/=Zinny}{ueRT8'/ltihlow~|gWWZ[XRA3468<3/1666:>?BJMOTWW[__bfgfedeehea[YRE@Qfjjkpi`[[\\\eyzppqsqpbJHKNPU\eolikp{†ˆ‰Š‰‰…ypd[I6,+++*++,++++*,/5>735=BQizˆŽ‰ˆˆ†ƒ}tj\]affeWJFGLLJKGA.*>BFVXXY\`_]^emrsstvyyxxwvxz}‚ƒ†ˆŒŒ‹‹‰‰ˆ‰Œ“•––’‘’“““‹’–•”“”‘Œ†{wyzzz||‹““ŽŠŠŠŠŒ‘…‚‚‚‚€~~€‚ˆ~{ywrqpnlkiiiiiihhjklq{…‹Œ‰‰‰†…†‡‹Œ‘““’‹Šˆ†„ƒ†Ž”••”“’‘“‘Ž‹ONNMKJHFEDCB@?>=;;977677555555444444444444444444333334544333333322222211110///....----,,,,,,,,,,--,---------------------.....-.-001445789:;=?AFGKLOPQQSVWXXXXZ[]]\]\\\ZXURRPOLJJGECAA?;9764320,,,,**,,,,,-7U[SU]eO&,4@MSUYXTRVYXUR]lmkfhibXK8-.-+*(('''''*-1489=?9#/3.GXZ\_cdcfkrvwyyyyz|~}|}€ƒ…††ŠŒŽŒŽ‘‘’’‘‘‘ŽŒŒ‘‘Œ‹Š‹Š„€€€€€ƒ„Œ’ŽŒŠ‰‰‰ŒŽ„‚‚~~‚ˆ~zxvrppnlkiihfhiiiikms}‡‹Œ‰‰ˆ†…†‰‹’”’‘Ž‹Šˆ‡…‡ˆ’–•“’’‘‘ŽŽ‘Ž‹PONNMKJHGEDB@>>=<;987677555555444444444444444444333334544333333322222211110///....----,,,,,,,,,,,,,,,,,,----------------.....-..//03345789:;=@DEIJLNMOOPQRRRUWYZYXYXXWVTQONMLJIHFDB@>;977531/.,,,,++,,,,,,/C_WSXch>*-3@LPLLLMNRVYVTgkkhcbb^[J+('&%##!!""#$&)09ENQRPOONMJIIHGGFCA<7100001567666445554105I^owyye0*izmnmnrxw\XZ[\]a__G65531-.04FJILelSSSRSW]]acb_\\\]_^ZYQNC:7=O`g`ij`\\\[ax~urpsqqlLBEIMQW^djnuy†‹’’““ŽŒˆ€vk[D0-.----,,++++,/4;;=@Nbvˆ’ŒŠˆˆ‡‚{pe_DFciea^Scmg_VL=@;9<@KY_acefhnrvyz||||}~}~€‚†††‰Ž““•––———————•••“‘Œ‹ŒŒŒŽŽŒŒ‰‡…„ƒ„…„ƒ„„„‚‚„„ƒ‡Ž‹ŠŠ‰‰‰ŒŒ‚€€€€~~‚‡~|zwurppnlkiihfffhhijovˆŒ‰‰‡…†…ŠŒ‘“”’ŽŒ‹ŠŠˆ‡ˆ”–”“‘‘‘‘ŽŽ’Ž‹TSRPNNMKIGFDCB>==;9:8677466655444444444444444443333345442222331133222211000///..--,,,,,,,,,,,+++++++,,--------------------........013355778:=@DDFHIKMMMMNNOOPSTSTTTSSRROMMKIIGGFD@?<<:75421.-,,,--,,,,,,,-.5TaVX_fa0+/6ALJFGHGHMVUSXiopcZ\daWC'%%#!!""##$')/:KUTRPOOONJJIIGDDCA=7300001446777;895653560.07?EB5,jxnnrqpzz]XZ\_aa`aO@74430/--26>ABJNQWXUTUZ]__ZZ\[]]YTQMGA<66=O\^V`lb]]]\lƒ~uqqrsp_DBFHLOV_cgr|…‰Ž‘’““”“ŽŒ†tgV=//......++,+-.07<@ERj€ŽŒ‹ŠŠ‰ƒ|pdcP=^gbeehhhhlolbODKQQYafgknrsx{€~~~€~|}~ƒ„‡”˜šž   ¡¢£¡£¢ ™—”ŒŒŒŒŒ‹‡†‚‚ƒƒƒƒƒ‚ƒ……‡ˆˆ†……‰Œ‹‹Š‰ˆˆˆŒŽ‰‚~~~~~~}€†~{yvtqonllkihgfeeggijpw€ˆŒŒˆ‰‡…„‡‹Ž“–”’‹‰ŠŠŠŠ‹‘––”’“‘Ž‹“Ž‹UTSSPOOMKIFFCB@>=;:8977765665544444444443344443233333332222211111122221100/////.--,,,,,,,,++++++****++++,,++,,------------......//01115577:<<>ACDFFHJJLMLNNNNPQPQPPONLLJJHFFEDCB@?<::86420/.--,+----,,,,,,..?a`WX_gO(*.6BMJGIEDHKMTW^mqf_`de^T9!$"! """$',1?P[YXYVRUQNJGFFDA@?<9400001244668<><9557754410/-.+1evmkoqpw{cY[^^dffP>8;84422101347:BKPX[YZYY]]]]ZZ[\\YTPMI=8656=KSTQYhe[]]awƒ}vtutsqR?BFGKOV_houˆŽ‘”–—••“’ŽŒŒ‡}sbM5---....,,,++-,27K\dtˆ’‘ŽŒ‹‹ˆ‚ylcbcZ^eccefiiggjooppokdgmrsuuyz€‚‚„„‚€€€~|{zxy{|}…œ£¦ª««««¬­®°­ªª©£Ÿš–‹‹‹‹‰‡„„€€€€€€‚ƒ‡Š‰†„…‰‹Œ‹‹‰‰‰‰‰‹ˆƒ€~~|~||}€ƒ}{yvtqomllkihffefggijqz„Œ‹ŠŠ…„„‡Œ‘••’‘ŽŒŠ‰‰Š‰‹‘”•““”‘Ž‹’ŠXWUSRRQOMKHIEC@>=;;9977765554444333333333322333344222211111100110120110011//...---,,++++++******))**++**++*+++,,,,--,,----..-.////01333356799;=?ACEEFHIIKKLMNNMMMKKJHGFDDBCCB@A@><;967410/.---,+----,,-,++--4PeYWX`g=#,/6CKJFDCBEFHQWerjihb]X\S2($!! !"%)/=S^^``\[[TQKFBAA?>778821001233478;@A=8875544320-,-6lwljonnwzh\\[]`aW:(.7;9520036435:?GPUXWTVYY^^]\Z[]^^ZTRQG;7524:FMQSVdf]^[f€ƒzwvtstvkK=BEJQU\ky~ˆ’•–˜™—•’‘Œ‹ƒym]C2.,//00...++-..4CawŽ”’ŽŒŒŒ…€ug`_aabacddggeehnrrvy~}||~~†††ˆ‡ƒ€~|{xwtrsttt~¦¬¯±´´´¶·º»»º´³²®©¥•Ž‹ˆ†‚€~zzy{{{|}ˆŠˆ……‡‰Š‹ŠŠ‰ŠŠ‰ŠŒˆƒ€€€~~}}~‚~zxurqnmkjiihffffggijs|†ŒŒ‰†„„†Œ“••’ŒŠ‰ˆˆ‰‰‘–•’’’‘‘Œ‹Ž‘ŒŠYXWWUTSPNMIHECA?><<:877765554444333322222211222233222211111100110120000000//...---,,++**********))****))+++*++,,,,--,,--....-.//001122223668::<=??ACDEGGIIIJJJLLIGFFDCDA@A@@>=><<9864520//..--,,----,,-,+,,-/;ZaZUYed3&,/9CKHEECAAAHPZkuwp^PU`bJ( !!!!#%+6N`bedb_YOHA8500277569842111233567:>=:8875544533,-Bmumjmqqvyga\`^aX:))-07:952028755:?HORX[WOOVZaa^\Z[^]^ZXVWNB<655149;;<862-*+022C_y‹““’ŽŒ‹ŠŠ…|ob_baabbdeeeggjkprtx}€„ƒƒ……„……ˆˆˆ‡‡ƒ€zxwttpqonnqu«±²·¹¹¹»¼¾½½¼»º¹·°¬¡–”•ˆ…„ƒ‚~|ywvuvvvwy{~…ˆ„„ˆ‰ŠŠ‰ŠŠ‰Š‹‡„‚€€€€}}~|yvsrnmlkkjihffhhhhimt}‡Œ‹Šˆƒƒ…ˆ””’‘Ž‹Šˆˆˆ‹’”’‘ŽŽ‹Œ‹Ž‘‹[[ZXXXTQONLIGDB@?=<;88875555323333332222221122000111111111//00001110//0/-/..--.---+++*))))**)((((()()*+++++++,,,,,,,----..--..0000000012445788:<<=>ABDDEEFGGGGFEGDCCCAAA?===:<=;9864332///..------,,---------3Hg`WU\g_)&&/5@HGFDA@@BIT_nro`PT]^W;!!""$'.D]ccdc\ND7,(#!#'(+.15:<943347679;==@:897565445307b}xmgkqsuw`Z\_abS-&(-/36477418;899>FOTUWWTPPRW^___\\^\[ZZ\\XK?<89DNTUVV[daW_v€}yvtprrjjwxXEIMVcw‚Š’”••–—™”‰„‡‹‡|qcSEGLPPPMHC@81214Ed|”“Œ‹‹‹ˆ†xk`_cbcaabcfghjossu{ƒ…†‡ˆ‰‰ˆˆ††ˆ†…„ƒ€~yvspohfimpmn˜­²³¸¼¼½½¾¾¾½¼»»»»¶°§žž–‹€€€{yxvsrorpprrvw„€{~…‹‹Š‰ŠŠŠ‹Š†„ƒ€€€~~€ƒ{xwsplllljiiihfiihhilu€‰Œ‹ŒŠ†„„†Š’•’Ž‹‹‰‡ˆŠŒ’’“‘ŽŽŽŒ‹‹‹Ž‹‰[[[[YYVTRNMJGEB@@>=<998755553333333322222211220001111111110000000000100//..-,,,++++++)))))**)((((()(')*****+++++,,,,----..--..00110000124446889:=>=>?ABBCDEEDDDCCB@>>===<<;;;:::77522221///.----..--.-...-,,--3ShZWY^iQ#%*,3>FHDCB@;>>GMVXTSSSPQSQUZ^^]ZZYYYX^d`OCB>?FRUUUVXa`[i€€|wususj`bkuxXFKXo}†‘’‘‘’’’‡€~zz‰ŒŠƒxsid_bfffb]VQJA846Ml„‘’’‹Š‰‰‡|rdb`bcbb`abdglptw{|…‡ˆ‰ŠŠŠŠˆˆ†…‚~}}}|xsnjff_hmpkoŸ¯´¸»¾¾¾½¾¾½¾½¼»»»ºµ¬¦‰zz}~zxvtromkhhjjljoy}v{‚‹Š‰ŠŠŠ‹Š†…„ƒ~~€„‚zwupnkjiijiiihfhhhhjpw‰ŒŠŒŠ†„„ˆŒ”—“‘ŒŠ‰ˆˆ‰’“’’’’“—˜“ŽŽŒŒ‹‹‹Ž‹‰[[\\[YWVSQNLJHFD@>=<:9765542223322221102222222111100000011000000//0/....---,,,+*++**))(())))(())((()()))*****++++,,,----------/1..0011223333667799<>>@@AAAAAA@??=>><;;::::999866555221110000----.....--------./8Wc\XYcd@!%)+1;FIB@@>=@EUjsrmkihieD!""#'.?^iedbL3*+)%#!#""#&(+/79666679=@BCFHGB?;8766543I~yrmilqsyud[^]aaD$)++-.047:=9>JF<9=ELPTXURRRNMONPSV[ZXZZ[VTZa]NHFCCDLRUUVV_`ar|xwvtvtd]`glssVO^sƒŽ‘‘ŽŒŒˆ…}qotvyŒŽ‹†€}zxy{}|zwrkf\VKFK[q‡”’Œˆ‰‡†…|xkaadbaccehiknquy}}‚…‡ŠŠŠ‰Šˆ‡ˆ†…ƒ~zvvwvvusmhb\_dacgm‚¡«³¸»½½½¾½¶¾¿¾½½½½¼´ª …styyyz{zxurpkgffijhdemnw{ww|‚‡‹ˆˆŠ‹‰†‡„ƒ„‚€€€„‚{wtrpllkkjjiighhhiikq|„ŠŠŠ…„ƒˆ•–”’ŽŒŠ‰ˆ‹‹‘“”“’““˜¡¡›’Ž‹‹‹ŒŒ‘ŒŠ^^\\[YWVTROLJHEECA=<:976654222331111111111111100//000000110/////./..--..,,,,--**++**))((((((''(('''()))))))******+++**,-----..--//001122445566779:;<>>>@?????>>><;:9998888778866553221110000/-..-,---,-------./0?_cXVYba.#''+.7CGFBB>;>?A>=FD?=@FMNLMLKNQMLNOOQRSVWW[\VTUWUNKKGEEHNUUVV\dj}|vusuuuua]bbekso_i{‰‹ˆ††‡„€xkhmtrx…‹ŒŠ‡„„†ŠŽŒ‹‡{tlf\\_m{‹’‘Š‡‡……„}tf]^_bdcfgklquw{„…‡ŠŒŒ‹‹Š‹Š†„€…wnjjkljd[WTQRUSW`‚Ÿ¡ª¶³µ»¸¸¼¾¼½½¿½½½½¼¹¦}imqtuvvwvuronkidipopkfltsjekpuvˆˆ‹‹‡††…………ƒ‚‚€ƒ‚}wurqllkkjiiighiiiikq{†‹Œ‹‹Š…„‰“–”“‘‹ŠŠ‰‘’““’‘‘“œ£¤œŒŠŒ‹ŒŽŽŒŠ]]\\[YXWTRPNLIGDB?<;:987643322111100111100000000////....//..........----,,,,--++**)((()(&'((&&''&&'((((())))()****++++,,,,--,.--0000112222223456778;<=====<<<<<;::::9988776888655533210000000///..--,,.---...---1Fd^UV]hV%&'&(+3?IGA??<:C^qjjbapsqM##'3NmpqnP--,)$""!!"#""$),0456678:=@DHJHGA>=875110^€xtqtuz|~gY_]bgU&,)-4=GEHLOIGDEIEFEDEKLIFCEJKIIKNRTQQRUYY\\URTQOPOKHFELSVWUYds{wrsvvvwr_[__agkqtt‚ŒŒŒˆ„ƒ‚ƒ…yecholm{…‹‹‹‹Ž“–•–“ˆƒ|wolqx„Ž“‹‰†„„„{o`Z_`^bgjnqsv|}€†ˆ‰‰ŠŒŽ‹‹‰ˆ‡‡ƒ~wz}|{sfZZXTPMWPGQ^jl‰£¨­µ¹»²Œ˜œ¬¾¾¿¾¾½½¼½·¢rYcilppqttttrqpnkgjonoppmqvm\[`emzˆŠ‹ˆ…………………‚‚‚‚„ƒ~xurolkjlhhiiihhhhjou}‡Œ‹Š‹ˆ„‚‡”•’Ž‹‹Ž“””’‘’™£¢—ŽŒ‹Š‹ŒŠ^^]][YYWVSQOLJHDB?<;:987643322221100////../000//....//////------/.--,,,,,,,,,,****((((('&'(('&''&&&'((((((()**))**++++,,,,,-,,.-..00112222223456779:;<<===<<;;97777777776654446533332111000000////..//..--...--,.3PgYRXbeF#%$#$(.;EIF@?>>Hcpncekhkf8!$(1PopokG.,)&#" !!"#"!#(+-14679:<>?AEIJIA=>>:50/[yxsqtx|{~pV\^`h^,),,0:CDEOQSXSMQOCINMGFGEDCCDIIHGMNTUTUVXZ^_YTSQOOQOLHDHQVWX[fuztrsstuwr_[\``ago|‹ŒŒŠ‡„ƒ„€wdbdljbq‰ŒŽŒ’–™š™—”Ž‹‡ƒ}…Š‘Šˆ…„„ƒ{m^[^^^einrwz|ƒ‡ŠŒŒŒŒŒŒŠ‰ˆ…‚{ytquy|vvld[T^aFDl’š™§²·¸¸¹¾¦w™Œ‘»¾¾¿¾¾¾½²]RW^cgkloqtssrqqroloromkkloooaORV`t†ˆ‹ˆ……„………„‚‚‚ƒ„ƒ€}xtpmlkihhhiiihhhkpw€‰Œ‹Œ‹ˆ„ƒ‹’–“’ŽŒ‹ŒŒ“”•’‘‘’™ Ÿ—Œ‹‹‹ŒŠ^^]][ZYWTSQONJHEC@<;:976644310110000//.....0////..........----,,--++,-,,+++***))))('')''('''&&&&&&''''''((((()))(***,,++,,,,---.111122113333335556798:::::8:::888777666666544443333222221111000////.//.-..--.----/6[bRQWab9"##""',6BHGCBB?PgbXZ]gknZ("%-Ffmjf@+,'&#! !$%##"$(+-/336:<==>AFIKC??A<25`wrlstxz||u[Z]]dg:++,-.6=AGOTXfbWICDMLIGEB@ABBCGIGHJNSVWWYZZ]a]YUSRRSNMHDEMUXZ[fsvqswxvuvq`[^]`dgsˆ‘‰†„ƒƒ„ƒtbccii^j{‡ŒŽŽ’•™ššš—–’‘Œ‰‡†ŠŒ‹Šˆ‡‡……whZTX^bhkos{„†‰ŠŽŽŒ‹Œ‹Š‡‡ƒ‚~}zzzug\armnge^_qsIZ™¬±´¹»»½¾¼»µ¬¸°³¼¾¾¾¿¾º§uHEORX\`dkoppprrqsvvrrxtlgginrriRAMWoƒ‡‹ˆ…ƒ„„„„ƒƒ‚‚„ƒ†‚~zwrnllihhhhhhhhimqz„‹‹ŠŒ‹ˆ„…Ž”•“’‘‘ŽŒ‹ŒŽ•—•‘‘“› œ–ŽŠŠŠŒŒŒŠ^]]][ZYWUTQNLIGEDB=<:9766433211100..//.....0//..,,........----,,,,+++,++***)))))(('&'(''('&&&&&&&&'''''''''((((()***++++,,,,--./111122113333335556778899999888777666666666444443333222221111000/////00/...--.-,,+--=b\TUZ_W-!"!#"(2BJJHCBAX_OLOW_hkC#)9YhibA,*&$" !$$#!!#$'+-0369<==>ADHIGCA?8Lpvmmlpvvyzs_Y]\^eI)-,11059@HOWakhWJGJNNJEA@>>??BDFHILMOSVXYXZ[]^\XXWWTONJEDGQWYZervsvzyxuwr_XY\_dkxƒŽ‘Œ‰†ƒƒ‚„‚~qbacfg]h{‡ŽŽ’“˜™šš˜–•’‹ŒŒ‰‰‡‡†„~vh^WUajghov}„‰‹ŽŒŒ‹‰†„„}}ytonj]SIennpuhU\jLo¦´¸»½¿¿¾»´³·º»½¾¾¿¿½»°—dFKSQOQUZ^bgjmprqrruyyrswqppoqwvshKEYp„ˆ‹ƒ‚„ƒ„ƒƒƒƒ„…‡‚€|yupnlkhhhhhhhhimr{…‹Š‰ŒŠ‡„‰’••’‘‘‘ŒŒŒŽ‘––“‘‘’˜œ™Œ‹‹ŒŒŽŒŽŽŒŒŠ^^^^[ZZXVSQOMJJGEB@>;96664331111//.-----....--..,,--..---,,,-,,,++++++++***)**))))(''&&&&&%%%%%%%%$%'&''(('&''))((******+,,,.../00112111223334446655777787777777666666777766664333333322222211100000//..----,,,,,,+-GdWRU[cV,!"! "'0?ABAB>;H_tqljiputvvq`Y\]^f_)+/0325888?HXil_UMMPSUSME==<<;;;;=BFLNNMPPUZZYY[\YUQOOLKJHDCACKRW`gghjmompmdcb\ZdsŽ’’’Ž‹‹‰ˆ‹ŒŒŠ‰ƒsb`bhru}Œ‘Ž‘’“”“‘Œˆ„„‡‘’‘‘‘‹Š‰ˆ…{yxsrx„†‰‹ŽŽ‹‡ƒ€||zywsqpomppkjknpqmiejc^c_v¥µº¼¾¼º¹µ£¬­¸»¼¹±“eJLSRQQQPPOOOMMQTZ^agipqrtssusv{|tq}ƒˆ‰py€ƒ†}y||}~‚ƒƒ…†ˆ…„‚€}{wrpnjihfgggkoxˆŠ‰ˆˆˆˆŒ““Ž“––‘Ž—œ—ˆˆ‹Š‹Œ‰„€ŽŽŽ‹\\\\ZYXVUTQONKIFC@?=;:9764322000//------,,,,,,,,,,,,,,,,,,++***())**)()))))(((((((&&&&%%$$$$$$$$$$#$&&&&&&&&''(())))****++,,-./000112222233333565566666666666666777666667766554333333333221110///////../----,,++,,))'):d`USV_id4!"##$%+3>MSPSgnZNNSatX#(>afYMB*""""!"$%&'%&%%(0/*))**+++,.,NrqkhgkooopviXea\ZZYUX4..366679;78@?B;19EORPRUSQJD=999;=BFLNPRTTUWYZ[\\YRLKIIGHEDCAAFKT]ddehklkmj_`hd`hw‰’’‘ŽŠŠ‰ŠŒŒ‹ŒŠ†ub_dkv{‡‘ŽŽŽ‘‘Ž‹ˆ„~~ƒ‹‘’‘ŽŽ‹Š‹Šˆ…‚€~}€„‡‰‹ŒŽŽŽ‡~xuqmmrsppqonmlklonnncY`ed`ZU`ž´¹»º¸¸¸´®°©²º¸¬QLMRRRPOQPPNMMNLNQUY^chlnrqprvx†‡…zŠ‘’‰y…ƒzx{{{|}}€‚ƒ…†‡†…„€}{zwsomjhggghkpzƒˆŠˆˆˆ‡‰”“ŽŽ’••’ŽŽ“›œš‘‹‹‹‹‹Œ†}ŒŒŠYYYYYXWUSRPNLKIFFD?==:8764332000/.---,,,,,,,--,,--++,,++******((**))))))))(((((('&$&%%%%%%%%$$$$$$"#%%%%&&''&&(())))***+,,,--//00000123323444456666666555766666676666677666655543333333322221100//../...------++,++('''BkbTRW_hc8!#$$$()*6FQTXliYPTXhsA)3FOJ:88:>AELNQTWXVVXZ\]\XPMJLJFEGDEEDAFMW`abehiikfZ]ghjp}Ž‘‰‰‰‰ŒŒŠˆŠ‹…wd_fpz‘“’ŒŒ‹ŽŽ‹‰…‚}„Š’”’ŒŒŒŒ‹Šˆ„ƒ‚„ˆŠŒŽŽŠ„{voga\_chklpqroonllnmlU@MX_[H@YŒ°¶¹¶¯µ·«¯±­¶¶ª€NOQOQQSRRSQPNNLOONNSUX\bfimoqt|…Ž–•’‡’˜šˆ„ƒvvyzzzz{~€‚ƒ†‡…„ƒ‚€~zuqolkjihjls|…ˆŠ††‰ŠŒ“’Ž’••’“™žœ–ŽŠŠŠ‹‹‹Š„}|Œ‹‹‰YYYYYXWUSRPNLKIFFDA?<<8764332000.----,++++****++,,++**))))))))(())))(()(((''''&&&&&%%%%%%%%%$$$$$$#$%%%%&&''''(())))***+,,..-//00000122233454456666666556666666665666677666655543333333322221100//......----,,++,+++(%#)HmaYWW_ggC!$%&%%$'/;JP\teWUW_rj2,,()%# !! !!#$')./,,(&+)('%$""$%WtmollnokmrrF>hle\XZPXW0+159:<<;>;55463.39DIHHFFEDBA>:88:3Np¥´¶¯‹˜°—¦²°± ˜oMOT]WSTXWURQQPOOOOOPQUX[`hq}ˆ“š¡¢ž™™—•ŽŠ…wuyzzzz{|~€……ƒ‚ƒ‚~~}xvtpnkihilt€‡Šˆ……†‰”•’Ž“•”‘Ž•žŸš‘‰ŠŒŒˆ‚w}‹ŽŒŠˆWWXXWWUTTSPNMKHEEA?=<;98532220//-,---,+*++++++++****)))))*))))))(((('''((('''&&&%%%%$$$$$$%%$$$$$$%%%%%%&&''(((((()))*++,,,--/////00122244455566666666666555555556566556666666433333332221/111//00......,,,,++,,+,*,,(%%*KmbXVW]diK##$$$$"! &2?FkwcXWUevP#''%#"! !!! !##%.9@<:/(++)&%#"#!=spklonmposk:+`kid\[UK[A)/49::=@@=86542-149@DDA=?ABAB?<:98;=BEJLQVVTTVXYZVTOJKLKHHHHJKJHDDIMV^ceefheWXdmt€‘ŽŒŠ‰‰Š‹ŽŽ‰‡‡Š†}h_jvŒ’ŽŠ‰‡…ƒƒ‚…ŠŒ‡„|‚ˆ”‘Š‰‰ˆ‹‹‹ŠŒŠ‰ŠŠŽŽŽŽŽŒ‰†zm[N\a\]^^ZZXY`ejmmmeWG=GJKNB?M^•¯²”R‡¤}š³·ª…v\MNRXQTYa]TUVVTQPRRPNOPR]n~œ¢£¢ ›Ÿ£Ÿšš˜’Œ‰Šˆ‡ƒ~xvxxzz{{z|~…„€~}{wutrollknyˆ‹ˆ†…‡Š‘””ŽŽŒŒŽ‘••‘Ž’šž•‹ŠŠ‹Œ†}rx†‹‰†WWWWUUTTRQPNLJHEDA?=<;98652220//-,,--+*+************))))))))))))(''''''(((((&&&&%%%%$$$$$$$$$$$$$$%%%%%%&&''(((((()))*++,,,--///11001222444555666666666655555555565665566666664333333322210011////....--,,,,+++++++,,)&#(/Mm`VUX^fl[&"$%#" !)3HlqbWVZhp2&#$#!! !!! !$"(.3782/++-*'%#"#-hnkjjmpllta.5_kigc`bJ[Q++<>;:=?D>7559830258<@=:7:>>>?>=:97:<:87543320//-,++++**********))))))))((((((((''''''''''''&&&%%%$$$###""$#$$$$$$%%%%&'''''((((()))**++,--.-.000022223433555666666666665555556666666666666666443322111111000/0.//.-..-,++++++)))*,--+)%$'-MobUTW]dl_4!$#"! !%*Epn`YY`rU#%"""! "$(-/2761,-./,)%$""XvmggllmukO3Cahjjhd^L\[5.:@?@@BB>835:@B<4379976567::<><<::78;>CIOQQQONOQTUSQLLKKLLKKMNNNLGDABFOT\acec\[iv‚‰‹ŽŒŠ‡…††ˆŠ‰†ˆˆˆˆ„~jbp{‡‹ˆ„‚€|wqosy|€ƒ†‡„‡‰Œ‹ˆ‡ˆ‡ˆŠŠŠŠŠ‹’’Œˆˆ…‚€xsssrqigjhiicWPQW[ZVTUUOCCTSVY]get¤€™‰…£¯¯¦|WJHIMLLRUQOPPRUWWUQRTOTfˆ¦²¹¼º¸°©¥››ž  ¡Ÿœ™™•‡{v{zzz{{{zy|~‚}}~~}}||ywrqppu}„Š‰…ƒ‡ŠŽ•”’Ž’“Ž‘–š›˜“‰ŠˆŠŒŒ†{rnw…‰Šˆ…UUTTSSSPPNLKIGEDBA@=<;7574331///-,++++**********))))))))((((((((''''''''''&&%%%%%%$$$###""##$$$$$$%%%%&'''''((((())**+++,-....00002222344445666666666666555555666666666666664444332211111100///.//.-..,,++++++*)()+++*(&(+./PndXTWZ^ffC""  !Gwm^VZeq:!##"! #&-2111,+,-12.*'$#FEA955564332599;;<<;:99<;:866532100/-,,+++*)****))))((((((((((((((((''''''&&%%%%%%$$$$$%$#########$$$$%%$%%&'''''()))))**++,,-.-./0101223345554555557766666656665566666655555555444433221111100/..--/.-,--,+++****)((**+-,)'+,.0/HkcVSTX]_fQ/!!#Uzg][^j`%"# "! !! #(*,*++),,-461,(%2lnffjijp]=;:<@DJMPSVURPONNMLKKKLLMNNPQPOOMHFCA@@CHRZaa`fxƒŠ‹‰‰†„„‚„‚|y{„‡…„€{jes‚ŽŒ‡ƒ‚€~vl`_dkosw|‚…‡Š‰‰ŠŠ‰‰‡††ˆ‰‰Š‹ŒŠŒŒŒŒŠŒŒ‡„}|zwtqqqpqqn\9489>=?DHKMPRTSSQPNMKKKKKLLNPPQTSPOLIFCB@>?@GQU[bm|†ŠŠ‰‡„ƒ‚‚‚}vqu‡…„€{jiv…Šƒ‚€~}xma]`eimqv}„„†‰‰‡‡ˆ‡††††††‡‰Š‹‰ˆ††‡ˆˆ‡‰ˆ„~|{zwvtpqqpqnM)5688@<84364107AFDB:430.013454679<>AABEFHKNOOQSRPNLJIIKKMMOPSSUSPNLIFDD@AA>AEKTar„‰‰‰ˆ†„€€€znkv€ˆˆ„€yjjw…Œ‹‡‚€}zvkd_^_ehmsy€…††ˆˆ‡‡„„‡…………………‡†ƒ|}~~‚‚„††~}zywvuutuvkR/.:<>CGKGGHOPQXXV\`aeidm‹—“œ§©¦ mAFJILKJIJIJJJJJJIKJSl–®·¼½¾¾¾¾¾¾¾¾¹µ®¥£¡š™™˜˜££Ÿ•„z~}{{{{yzxxz~sptuww|{|~~~}}{yv{ƒ‰‰…‚‡Œ‘•”‘Ž‹ŠŠ‹Œ’‘‘“–™™•‘Š‰ˆ‰Š‹Œˆuoon{…ˆˆ‡†OONNMMLLLJGFDB@@@=;::76643220///-,++,,****))))((((((((((((((''''((''&&&&%%%%%%$$$$$$##########$$$$%%%%%''''''()))**++,,,,-..001132223455555555555566666666665555555555555544333333111100/.....--,,,+++**))))))(()))-/21/-.147:=>>UjeXSRVX_ihN%  %((;qr]_akb "!!!$(*,,.---./0110,UrfcdhkpnjhgefdaabcdfY51333459AGC94451104>EFA:51/.013566779<>BBCEFJJLLLNOQSOMLIIJJLMPRTTUSOMJHFDCAAA@>?BO[pƒŠŠˆ…‚€~zqhlt‚‡‡†ymlx…‰‰„|{|vkb_\[_`fmu~ˆ‡„††‡‡„„…„……„„……ƒ€{vqpqou|~‚…‚~|{|{{{yxy{wgJ@AFP[^XKMX\]^\Zadefkkc{”‹›§§¥™cCHJIJKJIHHIIJJHHIIOfª·º¾¾½¾¾¾¾¾¾¾¾º¶«§£¡›™™š–—Ÿ¢¢œŽ€{{|{{{zxxy{}nlnqttxy{|}|||{zy{ƒŠˆ„ƒŠŽ••“ŽŽŽŠ‰Š‹Œ‘’“——˜–‘ŒŠ‰‡Š‰Š‹Š„}uoon}†‰‰‡†LLLKKKIIJGDBCA@>>;:87755421000/...,+,,****))))(((((((((())((''''''''&&'&&&&%%%%%$$$$########$$$$$$%%%&''((''()*****++,,---//0012433334455555665555555555666655555555555444333333221110///..--,--**++**)(((''''(((()-1442/236:Idm\SQQUZfma9"%',,Htjc``pF! "!%&+-/20..000221LrhbcejppigdddddcefdjbE1255348:DX]93332103:EKF;53.,/13567879;>BCDFFIGJJKLNORRRNKIHHJJMOSTURMKIGDCB@@@A@@?FSiz„‡…‚~}|ujcivƒŠˆ†‚yomy…ˆ†‚~||zufefed^^ait€ˆ‡………††„„„„„…ƒ…ƒ‚€zvmbaees{|}‚€|}€‚€~~yphdehiidW[`a``bcfgjkohbl‹–Ÿ§©¤SCFKKIJIHFGGGHJGGHI]„£²¹¼½¾¾¾¾¾¾¾¾¾¾½¸³­¥Ÿ›š˜–•‘“—™™”…zyzz{zyxxyy~pjllqrtvy|~|z|||z}ƒˆ‡„„Š–”ŽŠ‰Š‹Ž’’”˜š™˜“Œ‹Š‰ˆ‰Œ‹‹‹‡|tnon|ˆ‰‰ˆ‡KKKIJJHHGHFDBA?=<::877663210000.--,+,,****))))(((((((((())((''''''''&&'&&&&%%%%%$$##########$$$$$$%%%&''(())))****++,,,---/00112433334455555665555555555666655555555555444333333221110///.---,,+**))**)(((''''''(()-167524:<>BFJBC@?VkbWQPS[_gn^4$+,-.Oxj__`l4 "$&&)-1232244441IsmfffinphedddddeegddYC7534437;?CLN723322128CHF=830../0156669;<@BDFFGFGHHIMPPQNLKIHHHHJNPSTPMKIGDCA?@@CDCAAHYjt|€€~}|~|wnbbkw„Š‰ˆ„{omy„‡ƒ}z||yulhjhe`^^iv…‹Šˆ„„„„„„„„„„†…ƒ€}woeWNQXmvy{}€~ƒ†‡†ˆˆ‡}wqssqnjdW_a`__`eeghlogcg{•ž¥¥£‡JEKJIHFHGGGGGGGIHFLnš¯¸¼¿¾¾¾¾¾¾¾¾¾¾¾½»´¬¦¡™•‘‰…„‹…{yzyyzzyyz{€qjggnqpsvxz|z{{}{~…ˆ‡ƒ…’•’ŒŠŠ‹Œ”—˜š™—’ŒŠ‰‰ˆ‰‹‹‹‹Š†ytnno}‡‰‰‰ˆIIIIHHGEDDCBB@?><;98866543000//-.--,,+*)))**))((((((''))))))))))((''''&&%%%%%%%%$$$$$$$$$$%%%%$$%%&&%&'())**))*,++,,....///011223344446456666666666666664455545564444433422222221100//...-,,++*)))))''&&''(('&%%&')+16:976>?BHLMJIC?ABCEEEEEDGGHHIIHHFFGGGJOQROLJHFEDC@@@@CEEEEGHP[dmty{wqfb^_ky†‹‰‹…{oo{€€|{{{{}}ypoljiffq‰…‚€€‚ƒƒƒ…„‚}ztgZTKHPctvvy{}~‚„‡‹ŽŽŽ‹‰wponj[_`a``cacefgc^efhnq†šŸF@FGHGHEEEEEFEEDFOtž²º½¾¾¾¾¾¾¾¾¾¾¾¾¾½¹²¬§£Ÿ˜“Š…„…†‹‹„{yyyyyyyywy}rgeggjnnsvyz|{{{z…ˆ‡…”•“ŽŒŒŒŒ‹‹Ž“–˜—•‘ŠŠŠ‰‰ˆ‰ŠŒŒ‰†|xpmls‚ŠŽŽŽŽEEEEDDBA@@A@><;::876745422100/...-,,,,+*******))))))))******))))((''((&&&&%%&&%%%%$$%%%%%%%%%%%%&&''''()))*****,,,+,././/111111233443444555555556655554345555334443333332333111111//....-,++**)()(('''&%'%&&&&%%%%&*19?BB==FKKLJJHEB??A;I`jdYYYWY]`kgA&&&(bvdbdj:!(+*,--145688?jkgiigltkdfghhiill_H:BE>3153016A[R520/..01348<:966642-,---/04798;==?AABBCCDCCCCCCCDEDEFFGGJKNQLIGFEECA@?@CDGGFFIGMV_gosqgSX_bn{ˆŠ‰‰„|qp{~|{yxyy}}tonnkilv…’‡ƒ~€€‚ƒƒ‚‚€~ysha[WQQZhsuwxz}€‚„‡ŠŽŽ‰wpljb]`abbbacdfgd^Ydfemrz‘šžšx@BFEDHGGECDFDDCEJd’¬¸º½¾¾¾¾¾¾¾¾¾¾¾¾½¼¶²¬§¢›–‡„‚ƒ…‰‡}{{zzyxxwvw}sddgefkloswx{{{{z‡ˆ†ˆ””’‹‹‹‹”–—”“‰‰ˆˆ†‡†ŠŒŒ‹†€{yutw~ˆŽ‘‘BBBBBBAA@@@?<;::9765554322210/...-**+++*******))))))))******))))))((((&&&&%%&&%%%%$$%%%%%%%%%%%%&&''''))))*****,,,,,//.//111111134443455555555556655554445554334443333222233211110/..-.-,,+**)((((('&&%%%$%%%%%%&'(+/:BGHC=FNOPNJJEB?=<:69Mhi]XVTUY]ciV2!$,`vccbg1"*,0321378<8=iqjhjhiolfgjjgjnn[B9:?DI;3442016?PI30////276769<<9853/++,,--/159:;==>@@@@>?A@??=>>@?AACDDDDEHJJIHFDEECA@?@CDEFGFHGGJOX`fcVIO^en|‰Š‹Š…|pqz}~}yyzzz|€wtqonlnyˆ’†~~€ƒ„ƒ‚€~{xriea[WW]isuwwz}„…ˆŒŽŽŽ‰‚xpgc``abccccdfffa]]hhhkru€•ž›u><::998744343322110/..++,+,++)))******++****++++++******('(('&&&&&&&&&&&%%%&&&%%%%%%&&&&&&''')******++,---.///0011202224445566666655555555555545543333333333222122111000..----,+**)))((('&&%%%&%$$$$$%$&&*.8ELPIDGRTVRMIDA><<992.8VebWUSVX\^ggK+'craeh]'*,27=CKLI@IjsmjmiipnfhiikngZL;9:=?@;642200467;71/11/037:97:@A=:53/++,----/2669;;<;;=>>??@ACCBADFFGGFDCBB@?@?@BCCDDEEEFEGJQUQJEGWfs~ˆˆˆ‡ƒympw{}{zyyyz|€„~wqqoor}‹“Œ…€z{z~ƒ…ƒ‚~|}}}|uqmida`cbisvww{|ƒ„†ˆ‹ŽŽŽŠ‚ypebbcdccccccfdcc]^ijjmrstŒ›˜r>AEDECCEDDDEDBDKm™®¶º½¾¾¾¾¾¾¾¾¾¾¾¾¾¾º¸µ®« •‘ŽŒŠ…ƒ~zy~~{zzzyxxwvu|zhehgdejnnqtvy|}|ˆ††Ž’‘“ŽŒŒ‹‹ŒŒ“–”‘Œˆ‹‹Œ‹‡ˆŠ‹‰…„††‡‰‹‘‘”““@@@@????<<;:99887666533322110/..,+++++*)))******++****++++++****))*((('&&&&&&&&&&&&&&&&&%%%%&&'''''''()*))++++,,,--..//0001111222344556666665555555555553454333333333322122211100/.--,-,++*))(((('&&&%$$&&$$%&$#$$&)-8EOTRMKSXVSOKGC?;:732/.,=XgaYTSVX[`gbB&$^oaeeV:85=@ILQXcrrlmljkppjiklojSEFGHCABA>84420//36763.-.11149=;7;BD@=71/+,,----/14678:;=:889<<<;;;:::===>?ABCCDDEDCA@@@AA??@@AABBBBDCDDCDEFDEEShuƒ‡†‡…€tmqxxzzzyyyz{|‰ŒxurqqwŒ’‹…€{zy{~‚„ƒ‚~ywxzyzwxpnlmmlhmtwwx{}‚…††‡ˆ‹Š‡unjfbfeddcccccdb`_floljpxyŽ“oCBGDEDACDDDEDBCW…¦³·¼¾¾¾¾¾¾¾¾¾¾¾¾¾¾½½¹¸²« ™•–•”‘ŽŠ…~vuyyyzzyxwwuvyzgeedefiklnrvy}}|„ˆ†ˆ“’‘‘ŒŒ‹‹ŒŒ‘’‘ŒˆˆŠ‘––’Ž‹Š‹ŠˆŒ’’’‘’‘“‘‘’’????>>==>;;;9788767643321100.-..++++*+++********++****,,,,,,,,+**,+(**((&&''''''''''''((''&%&'''(()))))***+,,,....../0/00010022223445555666655665566554444443333332122221101000/..--,,+++*))((('''&&&%$$$&$#####$#$&+6ALVZVQSXYXTNKHD?<8310./,,Dah^VTTTX_chcE,^i]chP+7>????@@AAAAABBBCCCBBBETiv€‚‚{oiovvwxxxyzuuv‡’€yvtrx‚ŒŠƒ{xxx|‚„…ƒxpptwuwvusrsutqlktwxx{…†…†‡ˆˆ…}uspkfcefffdccbc`[]cgkqnoru{‰ˆpFCCEEEBABCDEFCJj—¬µ»¼¾¿¾¾¾¾¾¾¾¾¾¾¾¾¾¾½»¹³¨£¢  ¡œ–“Œvuwxyzzywwwvxz}heecfdghknrvy}{}…††‰”“’ŽŒŒ‹‹‹’‘ŽŒŠˆˆ‹–›š—•‘Ž‘“•–”““’’’‘‘’‘’<<<<<<;;<9888755656532211100.---++++************++***+,,,,,,,,,,+**)((((''''''''''''''((''''''''(()))))*+++,,,.../../0000010022223445555666655665566554444443333332122111100000/.--,,++***)(('''''&%%%$$$$$$#####$$%)0:FQYXQQXYXXSOLHD?;752/.-/*1HeiYSSVXY\dibVjqcdbJ8K_eggntlhjlmppkml]=-5I[]TOMEGG=74230/,0:=621.--03668::89>AEB@93/--,,-.0222455567778989:988888889;;=?@ABBBB?>??>>>>>>?@@AAA???@AAB@BAJ[ky{{|}yrkipttvwwwyxrgl€“zxvsy‚ŒŽˆƒ|yyy|ƒ…†„xoimmopsvvuuwvutpmrwz{„……††‡‡„ytpnijgffecccca]Z]cgmuuuomp†ˆpLDDDEDACCCDDEFV€£²¸»½¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¼»¸¶µ±ª¦£›”Žƒzuwxyzzywwwwwz}hedcdeghimqtz|{}„…†‰“”“’ŽŽŒŒ‹‹‹ŠŠˆˆ‹˜œ™˜–•–––———•”““““’’’‘Œ<<;;;;::998876776655211100..-,++**************++**++--,,------,,,+****))((('''((((((((((((''(((())))))*+,,+,----..////00011102222344445556665555555555444444333311110000000////.--,-,++***))(('''&&%$#$$##"#""""###$&-6CMSTPNUZ[[YTMIGB?<732/+,,,+3Ui_ZWWWYYZbjqutponligeblrjhgjnqrnj\>")3G]_ZRMB8EE;5220/./7HL<31//..3789;867?@@???>?>=<====>?@AAA@@>>>@@?BIWcrzwvzyqj^blqsuuvwwwqcWn˜‚}yx{„Œ‹†‚}zz{|€„…ˆ‡ƒ|sb_dghlnpsuxxvtstttuz~ƒ„………‡‡‡ƒ{wxusoigfedcbb_^Z^fmtwvupliv†pQGFFFDCBCDEEAJd’ª´¹½½½¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾»°«¦Ž„zuwyzyyywwwvwz}lccdddfhjmrs{z|~…††‹“–’‘ŽŒ‹‹‹‘Ž‹ŠŠ‹Ž”—œššš˜š™™—˜—–•”““”••’“‡::::;;::99776666554431110/..-,++,,************++**++----......,,++++++)))((((())))))))))))))(((())))))*+,,,,------////000111022223444455566655555555555444443333111100000////....-,+++***))(('''&&%%$$####""!!""""!#$)3>GLIFIOY\^]XQNLGC?:74/+*)+(*+===<<====>?AAAA??>?>>?AIT_mwvtvupeXOUafkoqtuywn[;2m–‹}{~„Œ‹†‚}zz|‚†‡ˆ‡†‚xiYY]`dfglrvwwvvuutwz|‚ƒƒ„……†ƒ~zyzuromgddcb__^]ajsywvupmko~YDGFEBBBCDEEANs›¬¶¹¼½¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾½¾¾¾¾½µ¬ xnpsuwwyyyywwwvwz}kccbddfhjmqtz||~„…‡““’‘ŽŒ‹‹‹Ž‘‹Š‹Ž’“•–šš›™ššš™™˜—–••““’‘‘ŽŠ…€{99:::9::8888666644442011/..--,++****)))***+++,,+++,,--........-,+,+*++))**)))))()'((((((**))))(((()*++++,,--..--..//0000001101222233334555666655554455333444222211110.//00//--...-,++**)))((''&&&%%$#"##""""!!!!!!""#'09CEA?BMWZ__^WUPKGC?90('('&&&'#*C[_YWSRUWY\`bccdcclwqiglptyh?!#((9Neidcc[L42A9420//.,-/1/011/15412579888=EGFD@;50,++../1344433333344456677667755557788:;>>=<<<<<<==<==AES_isvrrqldUB>FPX\bgmstthYVLfŽ†€‚‡ŒŠ†‚~~}„‡ˆ‰‡‰ˆq_QSUZ]`fjrttuuwvvyyz}‚„„„„ƒƒ~||zuupkhfda_][\eipsussnjigr|cGFFFDBCCDCDBR~ ­¶º½½¾¿¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾½½¾¾¾¾½µ¥Žscdeinsxyzzyxwvwwy~ncbbdfegjlou{|}‚…ˆ•”“ŽŒ‹‰‰Ž‹Š‹Ž’•—™™šœ›šš™šš˜˜––”’’Œˆ…€yumd99::99998888655532222000...--,++))++**)*+++++,,+,,,,---........,--,+,,+*++***)))*(**))))**))**))))*+++++,,--..--..//0000001112332222333455444455554444333444222211110.//....----++++**)))((''&&&%%$$##""!!!!!!!!!!""#'-8BGD>BHPW]`_^\ZTNKE?.'&&%((('&$$(@]bTOTWWZ^_acedluspkorvxe;%&(&'=]iiec_UI,+86310/.-*+,./00002672236::89>BDEEA<61,++...0133333222244444456466655555677:;<==9::;;;<===?GRZgrtqtrkcU>029?DIQZ`fmnh_ehr‚‘‹„‚‚ŠŽŠ†„‚€€„‡ˆˆŠˆˆŠ…zmVMOPTW]dkpqsttvwxyz|}}~€€‚„„„‚}|yttnjgc`^^\ajpppqqqmihfiroPLJJEBBBBABEU…£°·»½½¾¿¾¾¾¾¾¾¾¾¾¾¾¾¾¾½¼¼¼¾½¼¹©ˆhY_dehhkqxyyyywvvvy~sfccgiijlnqv|}~€‚…‰Ž’‘’ŽŒ‹‰‰Ž‹ŠŠŽ‘”˜™›››™™˜˜–————–•“‰ƒ~ztldYQP8899997788875555322220/....--,++++++***++++++,----............--..-+,,,++++*))****))****+)***+*(*++,++++,,--..--..//0000/1111222223322333345445555334433453222220011/././....-,,,++**))((((&&&%%&%$##"""!!!!!!!!! !"#&+4AFFDGIOUZ_bca_\WSM>.))%$$$&(*((($*G]]UY]`acchhmsplkmqswkN4,+&'*C^ibWRYSJ-(76000/--*,0/11121389757:<><;;@CEEB?;50,+,,-.0112332200223344444444554234679;<;978888:;<>==??BBBA?<>CP[fqussng_R@2//0369@FOW_b`[^V]tŽ†„„‡‹‰……„„„„„†‡ˆˆ‰ˆˆˆŠ†ylVJHJLR[gnnoprtwyxx{}~~~€ƒ‚ƒ€~{xvrnjd`^[]ehjmpqonligddfkWTPICBABBBAEX‡¤°¶º¼¼¾¾¾¾¾¾¾¾¾¾¾¾¾¾½¼»ºº½½»·­…\PW]`dghijrwxxxvvwx|€yqprssssttw}~‚†Š‘’‘ŽŒŒ‹ŠŠ‹Š‹“—˜˜š™š™˜•”’‘‘ŽŒˆ€wpg`XNGDHN8888887788755555322220/....--,++++++***++++++,----......//////....-.,,,,,+++**++++**++**+,***+++*++,++++,,--..--..//000000111222223322333333443333554433330002220000../....---,+++**))(')&'&&%%%$$##""!! !! !#&+4?HJJKNPSZ_eeffb^XM4)+(&$%'&&+-..+&%*:KWgljkmnkurmmosuwlSI52<=12LcdZSLMMG0%65/00.---/201212369=;988;<==>@?@B@?=92.,,,-.0112332210113344443333444233568:;989888879:;;;<=@ABA?=@NYcopqsoh\N<.-,-/00047:CINPWYRZi‹†……‡ˆ†…ƒƒ‚‚ƒƒ†‡ŠŠŠ‰ˆ‡‰Š‡~o[HDEHQcikkmprxywwy{{{|}}~~‚ƒ‚}{yurnje[PT\efgnonnligddb`XRPFDBBAAABDXˆ¥±·¹¼¾½¾¾¾¾¾¾¾¾¾¾¾¾¾¼º¸·»»½¹±•\EPY\^bfgihltxyyzz|‚…‚‚‚‚€~~~~€‚‚†ŒŽ‘ŒŒ‹ŠŠŒŒ’–——˜˜™™—•“ŒŠˆ†„ysg_VNEEFJLPS88777777765555553222200///.--,,,,,++++*,,,,,,,..--//....00////......--..,,,,+++*++++****,,++++,,**,,,,+-,,--..--..//000000011112222222343333333333333333320.0111000/......--,,+++**))(&&''&&%%$$$##""!!! !! !! !"%+3=EILOQQT[`fkjfdc[H1,/,()))')*,,-/.01128AQX`cfrroortuteQPO;;HOJO[`XURKKOF1$3500/-.-16855324469?>=;;<@@BA>0.-,--//011248DY‰¦°·¹»½½¾¾¾¾¾¾¾¾¾¾¾¾¼»¹¶µ·º»¹­‚FEOV\^`ddgiiow{{|…‡‡ŠŠ‹‹ŠŠ‰‡†„„††…„ƒƒ‡ŒŽ‘ŽŒŒŒŒŠ‰‹‘”•—–•–——–”Ž‰…}xuqncVLFBA@CHMMPT77777768765555553222200///.---,,,,++*)*,++,,--....--..//00////..----....----,+++,,**++++,,++++,,,,,,,,+--,--..--..//00000001110122222233333333332233333333210011//......----++++))))(''&'&&%%$$$##""!! !! !! !#(.6>EIKPSTW_djnliebV>.-,***--(**,,,**-.0110..((drllkhim`ILNPDCNRQY^ZY][UUTR<'/6830.--17=<:63335;@AACA?@BBA<:<>==<:51.---/11112222222222331111113321//112443345555667799::<=>?>FP]krnolf\N=0.,,,,-////00101359;>AK`f`dhkpqsvz}ƒ……‡‰‰‹‰‰†„‡ŠŒŒ„vcG?HWcfdekpzyvwyz{yxxxz{|}~}|€|zvsrooplknkkoooljhfbab`\KLOGCBBBBABV„£®¶¹¼½¾¾¾¾¾¾¾¾¾¾¾¾¾¼º¸³¯µ»»·§pHLQY\^bdgjjjms{„‡‹ŽŽ‹ˆ‡‡‡‡††…„ˆŒŽŽŽŽŒŒŒŒŠ‰‹‘“”“•““”“ŽŒ†uld`ZOE@<=>A@CHLNOR77777777665544333222201000/...-,------............11110000000000--..-.0...--..----,,,,,,,,--,,,,,,,,--,.....-...000000000.11110022221111345433222222221111110.00--------,,+,++****))((((&%&%%%$$##! !!  "$*08@EHKPSVZbfkonie^O0*)))**-1,,,,--*%#$',0016.Zvjjkolh\UJIJIEKROX]a`aaZX_^ZK/)4A@5.-0:ACB<64469;?CDEHDBABC<78999:8840--,.00111122112222221000002211//01001122444466668989;===@JVdnlnje[M;3/,+,,--..//00//00221236@EEIMSU[achotyz€†…ˆ‰†„……‡Š‡yeIGVdeacisyyvvyyzywtqvwz|}}}~}}{yvsrnnoonmlmnjggebaab]OKSPDBDCBCDRw›®¶º¼½¼½½½¾¾¾¾¾¾¿¿¼»»µ«­´¸·®šo_cdddgjjkmnnry}…‡‰‘‘ŽŒ‹†‡†……†„…‰‹Œ‹‹Œ‹ŠŒŽŒ‘‘“Œ‰…€vkaUME>98:;<>@@DFILLL77777777665544333222201000/.....------....,,...../11110000000000/...-.0.......----,,,,,,,,--,,,,,,------....-...000000000.//00//11111111233133222222221111111/00....----,,+,+**)**))((((''%%$$$###!!!!  !$',4=CFGHKQUZafhlkieZB-(*++**((++++,,,'#"%%),2;Zulikjke[MLEGE95JPQW`_[__ZZ[_VS:+/DM9//3@DEE@99:88<>?BDHID??::7555589983.-,.00111122112200000000000011///1001111233444566879899:ANYhmmje\N>520.+,,--..//00//00221220/04468<@GHNV\diouy}€‚‚ƒƒ„‡‹Œ‡|hSX_dbdowxwvvyywvpnnotwz{}}~~~}|zyvtrqqpnnmljfdbaa`a`VMMPHCCCCBDKo—¬¶¹¼½¼¼¼¼½½½½¼¼»»¸·±­¦¦«¯®¤—€xzwwvvwwwvyy}}€ƒ…ˆ‹‘’‘’’‘‘ŽŒ‰‡†…„„„„„ˆŒŒŒŒŒŒ‹‹Œ‹‰ŠŒŒŽ’‘ŽŽŒ†…}xngZOE>;:7879<=?@ADFGJJJ77777766665544333222310/00/...----..-------/..//0000000000111100000000//00////..------,,,,,,----....../-..--..//00//////////00//00001122211212222211100000000000//..----,+++**)))))('''''&%$$#""!!""!!  !#)/6>EFFFINUY`ehjiigW7.-,-)**))((((*++)(''%&*(IpjjfgecPAKMIDB@@FNXbd\Zb_WQR\YOD:36=2./08?EFB;>@:889:;?HKF?:75654467;;852/--//0011112222////0000////00//./0011112222123466777759CP^hjhf]P?6421.,,,,,./....//00110222000/0//0026DGGHGKPX]`ddeebI-++)+'))))(((((***))'&&'>hfgfgdcN@DMML><830-//00001122220///////////..//./0011111101111233444448DQ^ggd_UC54410/-,,,,--,,..../011022211100.-..,../37<@GMSY^dhlpsx€…ˆ‡€sghqy|{xwwvqlgd\TV`gmrwxz}~}~}|}{yxvutsrqomjfba___`]PHJIHDACCGQj£­°±±±±­¬ª©§¤¤¡Ÿžœ›šœœšš™˜•’‘‘ŽŒŠˆ†„ƒ…„ƒ……‡‰‹Ž‘’’’’‘Šˆ‡ƒ‚‚‚‚‚…ˆˆ‰ŠŒŒŒŒ‰ˆ‰†‡‡…‚‚}ypjcZOGB;9;;;<==<<:9:=?A@CDDCCAA887766776666553332222210110/////------........////000000111100000000//////////..------,,----,+--......./////................////00001122221121110000//000///..--....---,++*****)))('''(&%$$#$##"""!!!!  #%)/6?FHHHHKOTY[^_b`W9'*)'((((()(((()+++*(),$Cl`bbdceP;B<:KG>=EDSda]]ab_c^ab`bTHDHVJ>BG9,05;?BGB:766336AJMGB:53355679>?A>:51..//0011235510////00..////////0000000000000011222238CQ^cb`XJ;4430///-,,,,,,,--//0111111121000/.-----..--.13;?DHLRW^dipuwwrlmsxwwyvspja]XMHQZ]dmrrw}~~~~~}~}|{ywvutusplhfa_``__[NLJEEHPV^jv ¤¦¦¦¤£  Ÿžœ››™—–˜˜˜˜˜˜——––•“’’ŽŒŠˆ†ƒ„‚ƒ††‡‰Ž’““’’‘ŽŠ‡ƒ‚‚‚‚„††ˆˆ‰‰ŠŠ‡~|zvrnia\ULF>;:9:89;<<==<<;:<>>BBABC@@@?777777776666553332222210000/////------......//////000000111100000000000000////..------,,--------.......///////..............////00001111221111110000//000///..--..--,,,*+*,+**))))''''&%$$####""""!!  !#%)/4@;76@F=556Pcccddda_`debcXLDGJA;@A:1.028ADE?634012:BEFB;7445579;=?AA>:51/0011112344420/////...////////////0////00////////16AMZa]ZRE9121////..,,,,,,--//0111111121120/..-,-----./../0036:>DIMSV[^^^afjklljgd]UQMMOIEQ^fjmpu{€||||}}{yyvvttqmjhecaa```TLNQX_ks|‚”›žžŸŸœ›œ›™™™˜™˜——–––••”““‘ŽŽŒ‹ŠŠ‰‡†ƒƒ‚‚‚ƒƒ„‡‰Ž’’’’‘Šˆ…ƒ‚‚‚‚‚„„†‡‡‡„‚}wmfdaYQKHA<;;;;;:;<<;<<==<<:;<=??BBBB@@?@776678776655442211223310////////--....//..////..00000011111111111111111100////....--------------....////////................/////0//0000221111000000//00/....-..--,,++++++**)(*(''''&&%$###"#""!!!! !"%()-3;ADFHJLORSUUSM?4)''''))(((())))++-++)*0ll`_adfV8AD<239@DBKEI_baffbc_cddgeguK@>?=>@A90/.7EED@821.-/29@BB?;6543368:IW[VSLA70///...--,,,,,-.../1000001111100/.-.-,---..0..//0/00/1568<>ACEHMPTVVXUSNECCFKF?DR[aflov}€||}||}|{yxwwsqomjgebcacb\_kq{ƒŠ’˜›››œœ›››››™™˜—••••””’ŽŽ‹‹‹‰ˆ†…‚€‚‚…‡‡‹ŒŽ‘’’‘ŽŒŠ†„ƒ‚‚‚ƒ„…‚ƒ‚wl_UJE@?A?B@:<<;<;<<<<<<=<<=:9<<==?BCCB@BD776678776655443322223100//00////......////////..00000011111111111111111100////....--------------....////////................/////0//0000111111000000//00/....-,,,,+++++++***((*'''''&%%$#"""""""!!!  "#&),04D?Peegjecddb_aebfdGBBDEEEE>3/.279AE>3/.-./29>A?976645578;>@@@>:611100123444422210/....--....----.................1?BAA>;;;>BB>?ENSZbenu}~}}|}~|{yxyyvtqonkjgilrvx|‚‹’–š™šœœœ›››››››š™™šš˜—””””””Œ‰‰ˆŠŽŽŒŠˆ‡„‚€€€€~‚„ˆŠ‹Œ‘‘Ž‹‡…„‚ƒ„„€zri^TJC?@@?BA=<<;<<<<;=<<<;;;<;<<==@CDDEEFF66776866775554332222210/..///////0////....////////0000111111111111111111//.////...--,,-------------.00//////..........------....///////////000000000/////....-,,,,++++++**)())((''&&&%%%$##""!""!!!!  !$&+.27?BHIIJLNOPQQMH:,'&&&'()((((**))*))*,-]legdbfV,?@=97321011244554444410...--...-------..//..--..----,09BMUPLF?5-,,,,,,,,,,++,-...///00000011110///......////0000000000........./0/001111367:;;==AGKPW_hry~€}|}~}|{{{zvvtsqonotz‚ˆ•——™™››››››š››ššš˜š—˜——˜˜–‹’’’‘‰…ƒ…’”””•”“’ŽŒˆ†ƒ€€}†ˆŠŒŽŽŽ‹Š†„ƒƒ‚ƒ‚‚~{tmia\PB@@?B@><<<==;;;;;;<;<:<=???@BEFFGGHG77776866776554332222210/////////-/////....////////0000111111111111111110///./.....--,,-------------.00//////..........------....///////////000000000/////....-,,,,++++++**('))((''&&%%%$$##""!""!!!!  !"#&(,039AFIIIJLNOPPNID4('&&&'((((')))*(((()-[ndge`h[-2>GTYXQF8:<98?^ltrlhje^`aaff\SROLNONLLH?3/237=CF?4/---,-/477765554679<=>@A?<9621011234555577522///.-...-------......--..-----.7AJROJF?5-******++++++,-...///00000011110000////..////0000000000..--........../////13366778;<;;<<<<;;;;;;;=>??@BBBCEGGGHHHG88777766665544322222211000///000//////..////....////0001111111110/./1100///.--.,--,,,,----,,,,,,....////////........--...--.........///.//000000000000///....---,,++,,+**)''''(('&&%%%$$#""!"!!! ""  #%(),15@A>=:7520/1213455689643100.-...,,,,,++,,-----,,,----,,5@FQNIE@7.(())))****,,--..--..////0000111000//////////00000000000/...-..--.../////0001222344556;IWcmx„€}€~zyxz{{{{|€‡‹Ž’“•••–—™™šš™š˜”‘Š†|uolhffkmpqs{‚„‚thnz‹˜Ÿž ž  š™”‘Œ‰…€|z{{}€…†ˆ‰ˆˆ‰‰‰‰††ƒ‚ƒƒ‚‚€€€€€}wsh]M@?<;::;;<<;;:;;;>@@AABCDFFFHGGGFFF6677996666554432222221100////010//////..//////....//0000111111110/./00//.-..---,--,,,-----,,,,,,....////..///.....------.--.............//000000000000///....---,,+++++*)'''''&&&&&%%$$$#""!"!!! !!   !#%(++.37>EJJHEDFGIKJGC;+&&&''())'')('((()+%Oibde`ea0,38=GNUYXP4/9AEHlttto`X`[Z_chg`YVTTQPPQPLGA:53127=@:2,***)**-012357888779<>??>=:752112133467876542000/..-,,,,,++++,,,,-,,,++,,--1>ACCDDEFGGGGGFFEEEE7777666655554432222210000000//0///////-///////...../0000111111000///.......-------,,,,,,+,,,,,,,--..////00////....,.....----..........,-..//////0000/////....---,,++++*(((''''%%&&%%$$$##""!!!!!  %'*+,049@FIIHEBBFGGFE?5)&&&''((((''(()****Tm`bea`c1*566=DIPWXT>==>81/138<<3,+*)))*,/13589:9:7778:>@AA@=:642012334567535422110..---,,++++++,,++,,++**)).8>FKGDA:5.''(''&((())))()++--./../0010001221111//////////00//11//..--.......///1/00/1112344442249CS`ksyƒ€}|wqotx}ƒƒ…†ˆ‰‰ŽŽŽŽŒ†~woh`XSNLFCDDDEM]XROTZ_gn”ž ŸŸžŸž›–‘Œ†€|toiddeimsw|~€„„„„„ƒ‚‚‚€}~~~}~~€„€~zrgZF><;;<<><>>?ABCEFGGFFFFFFFFEEECCD6677776655554432220000000000///.//////-///0000......///001////00////.....---,,,++++++++++,,,,,,,--..////00////.....-....----........,,-...//////0000/////....---,,++**)(((''''&%&%%%$$###""!!!!!  "$',-/39<@FIIGB??BEFEA=/(&&&&&''''''())*+._kaab_dd5&29;>>BJOPPI>;>BCECB:,'%%&&''((()')(*)3el^`_]`j?"/4:<@B@CHJJ?8;A@<;:752111224567797553442213330/.,,,----++**+)))*-6>ABDB?7)'%%&&''((((**+':ki^__]boG -238:DGKFEHG=>Qd[DOjlmgjiegkmpqnief`\ZWTQOMLLKGF>7114363.-+))(()*+-13379:766677:=>>><<;975211123435579:76753333330/....--,,++*****)(+19@FC>:53.(''''%%%%%&%%%&')))*)*,,-..011112222210/....////..//..----------.156884320235677883333246?JYcmux~€}{yvrmjkorv{||zxwusqniida\XRPNKKKKJIFCCDFECCB@ADOZ[YYYYYVQNFA=98779?EJRV\___aceimpqvz}~|{zzzz{{{|||{{|~€yskaWKCDEEEDDEEEEEEFFEFFFFFFFEEGFGE6654556644333322102211000///////000.......-.//..----/.........----,,------,,+*******))*+,,,,----+,--..//00..........--,-----------..............///...///.------++++)))(''&&&&&&$$%%$#$$#"!!   #$(+-18===;97543002222568997995442101100///.++-*****))()-5;@C>;73/,)%$&&%%%$%%%%%&''''()**,---./012322220000.0///.--..--,,,,,,**--/1468534422345896666521134=IV`irv}€~zyvrljfkotx|{}{zzwusqmie_[XTQMLJGEB@?=@>><<;;==?????><<9:::89::9:>>=;97532111333335888876421/0000000/.-,,**))))())-6;@>:641.*&$&&$$%$%%%%%&''''''))++,,../02222221111////..------,,--,+,,,-/11344123434456866666411344><9;;99;?EJOS[^`_`_\ZY[`ejqvz{{yyxxzz|{y{{||||}~}ytmc[NEFFEDCDCBBBCCCCDEFGGIIHHFGF54224322322211110000////////......//..----++,--,+-,,,,,,-+***+++,,++,,,,++++++**)))))))*++++++++,,,,..----..........---------------,------....//--...0--------,,+*****('''&&&&&&&&%$####"""" !"%()-/4;AHMPPLIFEB@BBDJG8**'$$&('''&'$7fmc]_chhlZ&+CKJNMOTSKING@9NaVZWR]dilkkqnlnpob_eggebb_WSPLHCBACB?:30254.,,++++,,+**+-/3578652113688;<=>>>>;955312331024425775422001123431/--***))))(((18;=:6640.+)%%%%%%%%&&&&&&&&''(()***,,-.01000011111/00////.-,,,+**+*+++-.0111112333223455566766644559ANYbkqx~}xurniefgjmqvy{{{zzzxxvtpligfca]XSMIFDA>::6777677899<<==<<=<:9889<@FJPUZ]```][USTZ]dhpv{{zzxyyy{{{|}|}{{|}}|xtkb[OEDEDCCBAAACCCDEFFHHJJIHFFD33222222221110010000//......----....--,,,,++*,,+,++++++++++++,,,++**++++********)))))))*++,,++++,,,,..--,-..........----------------..----......--....--------,,,+****('''&&&&&&&&%$$$$#""  "#&)),.28>DGJKKIFEBCBCBFB0(+'&'&'''(*%@pmbb`ffhoL#*0EQJGF@BLQFKJ?>=<93/011,++++++,,+,+,,/.3565531/057789;<>@@?=:76334300011114445332222234320--,**)))(&&'*38:<865430,*(&%%%%%%%%%&&&&&&''()**,,-//0000011111000////.-,,++++**+++,-...../01221233355666555334437?KT]emty~|vrpnlgbegjoux{{zzzzyywtrnlkkihd`[XUPMHFA;877666777::;;;;::887779A@?<:8697531//.//122444433355431/.-,,*)(*)'%''-2499856720-+'%%%%$$$$$%%%%&$%%'(**,,........---/0000..0..-,,++++++**++,,,,----01111233444433332232126:BNWbjqx{zurmmkfbbekqtvzxyzyxxvttolmmmljiggb^ZVQLE?986656777789::998866569>BFMSX\^^^]YUNIHJS[ckqwz{xuvvwxwwyz{}}z{{{zvpi`XPFDCB@@ABCDDDGHHJLKJHGECA1110111100/.//-..-------,+++,,,,-,++++*+)*++***)))******++**))++))**))))))))()((''(**)****++,,,,++++,,------------..--------------,,--,,,,--,,,,.,-.-,------,,,,++++))((''''&&&&&&&%%%$$#""" !$%)*,-.16;?CEGJJIHEBBA@9.*++('()(((*>kmfcfkmog5&.10=XXKNKJLMRSNDFFPQhieafltolknnknl`bdffefghf_ZVRJB=;:866:;83/-,*(()**,-./...../124642223568999=><:;9753221/0132334433344321/.-,,+*)))('&&),15::997661/,'$%$$$$##$$$$$$&&'(**,,--......---/.....///.---,,++++**++,,,,--..////0112222211112210/0235>JV^gotyyvrpmlhcaejlptwxyyxvsqomjiijjkjmmkie`ZSKD<87666667777776666555768>DINTZ\\^^]UQJFBGOXaiotxxxvttuvvuuy{{zxyyzxvqibYPECABBCDDEGHIJIKKJJHFCAB000000//0/////0.----+,,,,*,,,,,,,+**))*)())))))*))*)****++**))))(('(((('(()))((((((())********++++++,,,,,,,,,,------..--,,------,,++++**+++++++++++-----,,+*,,+,,,****(('''''%''&&&&%%$#$#""  "%(),+.147>BEFHIKJIHDDC?0*)*(&(*&)(*)dpgeejjoW*(0/4DV\LKHILKBIRKE?IY_YX[`jpskjjelnlg_aegc_afhe_]YUNGB=:879=D?51-+)((((+,--//..--.//3345521136779;<<><9866533334444542300000.-..-,+*)))('&'(,27::;;9841.*$#####""""##$%$&()*,,,,-....--,,,---.././/....--,,,,++,,,,,,,,----..//01000000//11/.////15;FO\ckry{wtpmiheaagjoruwwtqqnkihfdddhjijljgd_YQH>987665566775544445555569?ELQWY[^_]ZSMIC@IPX`gntyywurrstrrruy{yyyz|xvpjd\SJAACDFEHJJJJIIKJIFFCA?////....//.././---,,+,,,,*,,++))(*))(()('((('())***)))))**))))))(('((((&''(('&'''''())**********++++,,,,,,,,,,--------,,,,------,,++++**+++++++++++++,,+,,+*,,++++****((''''&&''&&&&&&$#$#""  "&)*,.148>CEFIJKKJJIFFF9++**)'&&'((#TojijnpoO,43.-4CJNOQMSSSOLKDBGG[^UK\biqommjgmomifdba``_cfb^ZYVPKD?<<=<988665566775544445544568>CJNTZ\]_][WPJEDDJQZbhpswxvtrpqqqrruz{zyyz{zwsog_XMFECGHIJJJJIIIIIFDA=<....--...--,-.--,,))****+((((((('''''''''''(((''''(((())**(((())(((('&&&&&&&&&&&''&'))****++))))))+++++++,,*++,-,,,,+*++*,,+,,,,****++++**++++++******+*+,++++++******))))('&'((''%%&&%%$$!!  $(+,/137;AGLMNLJKJLKJJC.&)'%'''(('&Mqhciopi?"300/14=GRZ^XVXYUMIB6@@@?>>=;;8542/-,---,-.,,+**)))('&&&%&+/24688441.,'"#######$$$$%&'(++,,--..,,++,,--......////..,,,,------..--------..//000011//..//..--....017?IS\emswwvpjhhd_^^flnpppnlgba^[WTSSX[`ea_ZWME=986665566775566556666669@@@@AABBBAAA?=;87531//.--,,,+++**)))''&&&%$&(+.025442/-)%#######$$%%%&'(++,,,,--,,++,,--......////.-,,,,------......----.///0000111/0000/.--..//0122;EP[ainvyupmjie_Z]bhknqrplfb^YUQNLOOSXYWUPJB9786666677887777889999:::;AFKQVY[\]^ZUQKGFHMRXahlpuwyywrmmoonorv{zzz{||yxtolc\RKIIIIHHGEEDC?=;76....,+-,,,--,,,,+****(''(&&&&&''''&&''''''&&%%''''(((())(())((('''''%%%%%%%%%%%%&&&&&&''(''''())))*)*+++*++++++,++++****))+++*++**********))))**************++++******)))))((((((('%%%%%$$""!!  $&)-037;>@CHMPQONNLLMK?-)(('*('&%&TvjefjdgC#.210.-/9HORXYY^dZTMG>1??A=>?@@BBA=::8664210//-,+**+*))(('&%%$#$$$%),.1234/.+&#"$##$#%%&&&''(****++++,,,,----.-......//.-,,,+---------.-------.///00011001100/.00//00230149AJV]fkrxwqpjec]ZZ^cglmonlgb[VSPIGGHJMMKFC=88:9887788888888:<<<;;;;<;?CHMRXZZ\^[WRJIGILQUZagmqtxyxvrmonpqrsv{{{{|}€€{xsohaVNIHGGDDCDA?;:752,,,,--,*++++******((*)))&%%%&&&&&&%%&&&&''&&%%''''&&(())((((('((''&&%%%%%%%%%%%%&&&&&&'''&&&'())))*)))++*******+***++***))****))))))))))))(()))*))))))))**))**********)))))(((((((((((&%$$""!!  $(*-037?CDEHIJLMOMMLJB2''''(*)%%$StkafhiiM&.10..-+/>MWPUVY__][TH>7CQ^^UWc_[htstwyzqffki`[Z[\ZRKIHJJJKJJFDA<875654-(%&'())))+-,,-....*''&$%(*+-/25568988;<<<===;::;>>><9::::8732100.,+***+))((&&%%##$$##%&*,.11/.-*%%$##$$%%&&&''(****++++,,,,--...-......//.-,,,+,,,,-----.-------.////00000011100/1111222223236>GQYajpuxrnhc^[VY^`ekmnnnhc\VSNHDA@CCB@==:;<;::::;;;;;;;;<=======>==BDIPSWXZ\YWTNJKJJNSW\dgkqtxyxvromqrrrty|{{|€ƒ~}wqlaULIHEBCB?=;97633++,,,,,+****))))(((&&&&&&%%%%%%%%%%%%%%%%%%%%%&&''&&&'((((((((''&&%%%#%%%%%%%%%%%%&&&&%&&&&&'))(''*())()))))))****++*)))))))(((())))(()))((())))))(((())))))))))))**)))))))((())((''((&$$%$#"!!! %'*/27:@EGFGEFIKNNMJD7,*((()*+$#A{m_^edj_%+1038/++-8DLORSUZ[^]QJB=HRWYQRZRTbrstx}|wusoh`\YVUTPHHIHGGEFJGDA>976543.('''(((')*++++-//.,*'%"$'(+.046776679=<<<=<:976798877:;<<;76320/.,*****))(('&&&#$$$$$#$'*-.10/.,)&$$$%%%%&&''(())**++,,----..///,---...//.-,,++**++++,----,----....//00001112221133334455434467>?>>=>>???<<=>????@@????>>>=<>BHLPTVXYZWUPLKHINRUY_dhnrvyzyvrqqqrssvy~€€‚ƒ…„‚~yri_QGFC?==;9644330+++,,+******))(((((&&&&&&%$$$$%%%%%%%%%%%%%%%%&&''&&%&((''((((''&&%%$%%%%%%%%%%%%%&&&&%%&&&&'''''')((('(()))))))(())))))))))((((((''''(((((())))))(((((())))))))))**)))))))((())(('''''%%%$###!! %),/4:=BGIIHFEGJNONNG>3))))*)((-qwi`dijb5%.1122-**,5BLNNSW[]\ZNF<8HVY[[S`dY[bmptswwwunjaZTQPPLFGHIFDDEIJHCB?;98851+((()))*)')*++,...,-,(%$$'*.26887668:;=<<<;:9664666577;9897521//.-,,,,,**))('''&$$$$$#$%(*.01//0-)(&%%%%%&&''(()))*++,,----..-------......-,,++**++++,---..........//001122123333344455777877778;@IQYdhpttmha[ZSRV]`dkmmlid^XUOLHC?@@@AAA@@@AAA@@?@AAAA@@??==>><;;:852+)))+*+,,((')+,-.-,.,*)'&&(+.59854799<<=<::9755545555653666210///.-,/..,++,****'%$$$#$$$%),.21121.+(%#%''&&&'(((()*++++,,,,....----....,-..,+++****++,,--/.....//..//00223221243345556677888888779>EMU]emsrmib][WSV\_cgkijiec[XTOLGEDBCCDCBCCBBAAAABBBB@@?=<;;;;99:;@EKPRWYZZXSPOLOQSXY\achlqsvy{{xusuuuwz|ƒ„‚‚ƒ……ƒzqfYHA<98755211.,,**))((**))(())((''&&&&%%$$###$$$$$%%%%%%&&%%%%%%&&&&&%''(&''''&&''&&%%%%%%%%%%%%%%%%%%$$%&''''''''((((((((('''''''''((((''''''''&&&&&&''''((((((''((((''(((((())****)))))))(()))((''(('%&&&%$#""! $&(,.37;EHNRUW[ZYVSQQRTVX[]`bbhmpswx||zxusvx||~‚…„‚€‚‚~yri[J>8754220/.-+,**))(())))(()('''&&&%%$$$$$$$$%%$$$$%%%%%%%%&&&&%%'''''''&''&&&&''&%%%&%%%%%$$$$$$%%%%%%%&&&'&''(('''%'('&&&&&&&''&(''''''&&&%%%%%%&&&&&%%&&'''''''''''''((((()(((**))(()))(())))'(((((('''&$###!  $%*,-1467:<=;>@BIJLJJF>2*'()(%Syocagso]$'//.--++***,/BKMRSSQPU[ZLDNbkooerj_[heaaMWglomkjgc^]XNGB?@ACEFDDEHIIHFDB@=7/,,//03682.*'(())+,+,--..++--000011236676544111012222100.//////..--..//11/.,*))*)('&&#%$$%)-1323330/,)&%%%&'''(((**++,,,,,-...../..////..-+**++*))++++++,.//01111222222233333444556666666887799888777?FOZciqusnje`^[YX\`eghklid`\WUPKHHGFEECCBA??@@BBAAA@?=<;:987889<@DJOTWY[[YVTVVXY[\]_ddghlprtx||{xuvxz}‚„…€~|yriZI;5531/.-,++++))))(())))(()('''&&&%%$$$$$$$$%%$$$$%%%%%%%%%%&&%%'''''''&''&&&&''&%%%&%%%%%$$$$$$%%%%%%%&&&'&''(('''%%%%%&&&&&&&&&'''''%%&&%$$$$%%%%%%%&&%%&&&&''&&&&&&''''((&'((**))(()))))))))'(((((('''&#$##!  "%(+-023347:;>BDFIKKJFB6*'))&G|o`_fqqe7 ,0.---++***-=GDGQTWROQYYRJSbjq^ovbeoha`bRP\dhjhd_`b^YQGB@?@BCCDDEFFFFEDB@=7/-/13425420+)((((***,/12200..--//../02241111011012222100///////...-..//11/..,,,*)('&&$%$$$'+-2555410-+(&%%&''''((**++,,,,-....../00110000/-+++++**+,,,,+,.//011112222222222223434566655557877998887666?@CFFEDCEECBA@<72.04872-121-,*))()(),/255442..,..--///.///////00133332111..//0000..--00000/.-,,*)('&$%%$$$$(+.2455211.+)'%%''((()++++----........001111200/,,----,,--,,,,..//1111111122111101223444554444566688888888659BLV_jsx|zwrkgc`ZX]adghhjhd`\YVQLIGFEDC@??BDCDECBA@?;;;:89:::?CCBAAEFB@?ABA90.1550-/211/.,)))++/-0358830.,,,--...-.--..-0/013333332200001111../000000/.,++*)(''&%%$$$$%),0244221/-,('%%%((()++++------......000000001011/---..--,,----./00000011110011012223444455444466777777774448@LWaju|~}zvpie_YW[]cffijifb\ZVQLIGGFEBBDFGFGGECB@@?=<;9:::;=ADHNSWZ\__``aa``acegggfc``bdjosx}||zz{~€€€}xvtrqmh_VF6---+++,,,++''(((((())('&&&&&'&%%%$$$$##$$%%&&&&&&&&''&&&&&&&&&&''%%%&((''(('''''&&&$&&&%%%%%%%%%%%%&&''&&&&%%&&%%&&$$%%%%$$%%%%%$$$$#####""""""!"""""""####$$##$%%%%%%&&&&&'(((''))))))))))(())))''&((&&'%%$"" !##')*+.039<@BFJMNNOOJFA1()*[wlmvsoP #,,,,,,,,,**+15:@HORONKHKKUadXQZeigackh[Xda___\__\ZYSQORRNHB@><;;=<<=?====?DKA1**.0.//33201/,*-0110214883/+*)),+,,,,,-....///02123234433221100//..00110/.,,++*(''&&%$%#$#&'++/0111//-+)'''((())*+++,,,+++,...-0000//01001100..--..--,,----,..../00////000011222233443333466666777654447@LWclv||vrie\USX\aeiijifb_[VRMIIHGGFGHJJJJGEDCC@>=<;;==??AEHMRVZ^`accbbabedeffffc`][\djnsuy{{zz{~~~ywuronlicYM=.***+++,+))(((((((())('''&&&&&%%%$$$$##$$%%&&&&&&&&''&&&&&&&&&&&&%%%&''''((''''&%&&&%&&%%%%%%%%%%%%&&''&&&&&&&&%%%%###%%%$$$$$$$####"##"""!!!"""!!!!!!!""!!""""#$$$%%%%%&&&('''''))))))))))))))))****''''&$$#!!!!"$')*+-/16>BFIMPRRTRJF?.()9zrinwwqT! "&,,++,++,,***/05>FILLIJLMLXbcYEHST_fldda_`bike^^_^]ZRPQSRLEBA><87788989999;BPI1*),-022421/0//023445222330-*)**)+++----.////0/0100023443333221100..//00/./--,+*)()(&&%%$#$$$'(*-./0100/.,)'(()(()**)+,,....//.-////..,-////....-...--,,,,,,,-..-.//////0000001100002322222333334554444469@LYcpv{{}}wqh_VPQW\aehjjlgda\VRMLKJJJKLLMMLIGFDC@?=<<<==??CGHMRVY]__cddebddeeffffc]]ZX[aflpsyz{{yz~~}xvrpmnlje^TC1+)*)**+*(('''())('))('''''&&%%%%%%%%$$$$%%&&&&&&''''''&&&&&&%%''&&''''''''&&&&''%%&%%%$$%&%%&&%%%%%&&&%%''&&%%%%$$$$"###$$$$##""""""!!!!! !!!!!! ! !!!!!##""!"##$$$%%%%%&'&&(((((())))))))))))****((((&%$$""!!!$()*++./5=AEJSUTTVQJE;*)(Tuqmsyte* "!%*,,***+,.,+++*3=BHKGHHFDFP_SFKSQTbgeijf^_\bjdZX[\\ZSPTVSQLCB?;:7544545688:AE@8,)+,.334422145665676310//,+*)(((()+----.0/011////0001331223333110/,-/../-.,-,+++*))(&$$$$$#$$$'(+,,/0221/-+*)))******+,..00/0/-./////....////......-,,,++++,,--....//////00////////00122211111123334434578ANYenwz{|yuoeXPPQTZbfgjkjgdb]XRPOMOMNOONNLIHFED@@?>=<>>>>AEINQU[]]acefffgffggffdb_[URQV^dhkrvz|z{|zzxuqrllkif_XJ:,)+*))))(('''())('))('''''&&%%%%%%$#$$$%%%&&&&&&&&&&''&&&&&&%%''&&''''''''&&&&&&&&&%%%%%%&%%&&%%%%%&&&%%%%&&%%%%$$$$#"""####""!!!!!!  !!! !!! !$$!!!"##$%$$%%&&&'))(((((())))))))********(((('%$$##""#&())*+..48>HKRVUURKHD9)(,ksjqxsi7$"#'*++**)+,./,*,+5AGGIIF@?;=FRJ?IRYkhfhhceg\P]gaXTWXZZVQUSMMJCC@==:755534688<>>><0))+-0144434688875431/---,+*)((((()*,../0/011///////01113234445320/,+../----,++**++(''%$&$$$$#&&(*+.133220//,**++++,,,.0000/0110.////....////....//-,,,----..--....//111100//////////002211222223334454576:CNYcjsyyyurj_VOSRW\bgmklkieb\XWRQPPOPPNNJHGEDDB@@??=??AACEGNQU[]^bdgihghggggfeda]XRONORYaglptx{zyzzxxwsmmligd\SC4-*)((''((€€€€~~~~~~}}||}}~€€€€€€€€€€~~~~~~~~~}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~}|{zzzzyyyyyyyyyz}€‚…†‰ˆ‰ˆˆˆ‰‰ˆˆ†…ƒ‚‚ƒƒ‚€|{zzzzz|}ƒƒ……„‚€~‚€ƒ†‡‡„‚ƒˆ„„‚€~~~~~~~~~~€ƒƒ‚€~}{||{}~~~~~~~}}}€ƒ„„…†‡‡ƒ~}}}}||{{{{||{}||}~€€€€€€€~||{|~~~€€€ƒƒƒ€}{xwyz~€€€€~}|}~~~~‚„}|€€€€~~~~~~}}||}}€€€€€€€€€€€~~~~~~~~~}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~}|{zzzzyyyyyyyyyz}€‚‡ˆˆˆˆ‡„„„„„„ƒ„ƒ€~~~‚ƒƒ‚€|{zzzzz{}~€‚ƒ„„ƒ€€‚ƒƒƒ‚‚ƒ………~x{‚…„‚~~~~~‚ƒƒƒ‚€}{||{}}}~~~~~}}}~ƒ„„…†‡‡ƒ~}}}}||{{{{||}~~~}~€€€€€~|yy{|~~~~~€€€‚~}{zyz{~€€€€~}|}~~~~‚„}{y€€€€€~~~~}}{{}~€€€€€€€€€€€~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~}||zyyzzyyyyyyyy{}€ƒ…ˆ‡†ƒ‚€ƒƒ„……‚~~}}‚„„ƒ‚|{z{{zz{~€‚„„ƒ€€ƒƒƒƒ‚‚‚‚ƒƒ…„€yssu€†ˆ~~~~~€‚ƒƒƒ‚€}|}}}}|||}~~~}}}}~€‚ƒ……††‡„‚~~}}}|{zz{|}}€€€€€€€€~}|{yz{|~~~}}€€‚‚ƒ‚}}zzyz|}€€€€‚‚‚€~~|~~~~~~€‚ƒ~yy€€€€€~~||}}}}~€€€€€€€€€€€~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}||z{{zzyyyyyyyyy|‚„„‚„…††‡„}|{}‚„„ƒ‚€}{z{{{{{}~€‚„„ƒ‚‚ƒƒƒƒƒƒƒ„ƒyvuvzƒ€~~€ƒ‚ƒƒ€‚}|}}}}|||}~~~~}|}}‚‚„…††‡…ƒ~~}}}|{{{|}€€€€€€|{xwxy{|~~}|}€€‚‚„ƒ~|{zy{|}€€‚‚€}}|~~~~~~ƒ„€{yy€€€~~}}|{}}}€€€€€€€€€€€€€€€€€}}~~~~~}}}||}}}}}}}}}}~~~~}}~~~~~~~~~~~~~~~~~~}}||{{yyyyyyxxxxyz|€€‚ƒƒƒƒ†‡‡…„‚}|{{|ƒƒƒƒ|{zzzz|}€‚„„„ƒƒ„„ƒƒƒƒƒ~€~}|z{‚€€€‚„ƒƒ‚‚€€€€€~|{}}}}}}|}~~}}}}|~€ƒƒƒ„…†„‚€}~~}|||}€€€‚ƒƒ‚€€€~|yxwwxz|}}}|{|‚‚……ƒ‚€~|{{{|~‚‚ƒƒ‚}{z}~~~~~‚„{yx€€€}}||{z||}€€€€€€€€€€€€€€€€€~~~~}}}}||}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~}}||{{zzyyyyxxxxyz{~‚ƒ„…………†‡‡…‚€|{{{|ƒƒƒƒ‚€}{zzzzz|~€€‚„„„„„„ƒƒƒƒ}|}~}}}~}~ƒ…ƒ€ƒ„ƒƒ‚~~€€€~|{}}}}}}}~~~~}}||}ƒƒƒ„…†„‚€}~~~~~‚ƒƒ‚‚‚€€€~|}}zywvvxz|}}}|{|‚‚……ƒ‚€~}{||}‚‚‚}{yz|~~~~~~‚ƒ‚}{y€€~~{|{{{{|~€€€‚‚€€€€€€€€€€€€€€€€€€}}}}}|{}}}}}}}}}}}}}}}~~~~~~}}~~}}}}}}~~~~~~}}||||{{zyyyxxxxy{|~‚„„†††††…††„}{z{{}~€‚‚ƒ‚{{{zz{{}~€€€ƒ„„„ƒƒƒƒƒ‚€|}~~~~}}„ˆ‰ƒ}}‚„„‚„‡‡ƒ‚‚~|||‚‚€~}|||}}}}}}~~~|{{}~ƒƒƒ„…†…ƒ‚ƒƒ„„ƒ€€€€€€~}~~~}zyvuuw{|}}||z|€€‚‚……ƒƒ€~}~~‚ƒ€~|zwxz}~„‚|z€~}}{{{zz{}~€€€€€€€€€€€€€€€€€€€€€€}}}}||{}}}}}}}}}}}}}}}~~~~~~}}~~}}}}}}~~~~~~}}}}||}|{{zyyyxxxxy{}‚„„†††††…†…‚|{|{{{}€‚ƒƒ‚€|{{zz{{|}€€ƒ„„ƒƒƒƒ‚~}}€€~~€€~}{{ƒ…††…„„‚€~}|‚‚€~}|||}}}}}}~~~~}{{|‚ƒƒƒ„…†…ƒ€‚ƒƒ„ƒƒ€€~}~~~}zxuttw{~}||z|€€‚‚……ƒƒ‚€€€‚‚‚~|wvw{~ƒƒ€}{€€€€~~}{{zzz{~~~~€€€€€€€€€€€€€~}}}|{{{{|||||||}}}}}}}}}}~~}}}}}}~~}}}}}}}}}}}}}}}}|||zzyxxxxwxy}€ƒ……††„………„ƒ||||||||~€‚ƒ„ƒ~|{zz{{|}}~€€‚„„ƒƒ‚€~~€~~€~}||y~‚€{vvz|}|ƒƒƒ~|{{{|||}|~}}~~}}{|~‚ƒƒƒƒ†…ƒ€€‚ƒƒ„„ƒ‚‚~~~~~~}~~~~{xusstw{|}}|{{}€ƒ††„„ƒƒƒƒ€‚ƒ„ƒzwvy|~~~~„„~z€€€€~}|{{{{{|~~~€€€€€€€€€€€€€~}~}|{{{{|||||||}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}|||zzyxxywxz{‚‚ƒ…††„………„~||||||||}~~€ƒ„ƒ‚€}zzz{{{|}€€‚„„ƒƒ‚}}~}|}~€~}|zxyzx|‚~xtqqrtw|ƒƒƒ~}|}}||}}|~}}}}||{|~‚ƒƒƒƒ„„‚€€€‚ƒƒƒƒ‚€~}||}}~}~~}}yvsrrsw{}~}|{{}€ƒ……„„„„ƒƒ‚‚„…ƒ}yvwy~~~~‚…ƒ{€€€€~}||{{|}}}~~~€€€€€€€€€€€€€€€~~~}|{zz{{||||||||}}}}}}}}}}}}|||||}||||}}}}}}}}}}||{|{|zzyxxwy|~ƒƒƒ„…„„„……„~||||||||}}~€€‚‚ƒƒ~|{{||{}~€‚„…„ƒ‚€~}}~€}|}~|{{zzywvv|‚‚~xtvxz}~€€‚ƒƒ~}|||||}}}}}}}}|{{{}€ƒƒƒƒƒƒ‚€€‚‚‚‚€€}|{y{|~~~}}}zxusqqrvz}~}|z|~€€€‚ƒ……„„„„ƒƒƒ‚ƒƒ„…ƒ€{xvxy~~~~€ƒ…„€|€€}}|{|||}}}~~~€€€€€€€€€€€€€€~||{zzz||{{||||||}}}}}}}}}}~~~~||||||||}}}}}}}}}}||{{{{zzyxyx{~€‚„„ƒ„„„„„„„„}{||||||||}}~€‚‚ƒƒ|{{||{|}~€‚‚„„ƒ€~~}€€~~|zz{{zxxvw{~zz{||||}€€‚ƒƒ~}||||}}}}}}}}|{{yz}€€‚ƒƒƒƒƒ‚€€€~|yyxy{~~~}}|ywtqppsvz}~|{{}~€€€‚ƒ……„„„„ƒƒ‚‚‚ƒ„‚{xvx|~~~€ƒ…„€|~~}}|}}~~~~}~~~€€€€€€‚~}|{{{{{{{{{{{{||||}}~~~~}~~~€||||||||}}}}}}}}}}{{{{{{zzyyy{~€‚ƒ‚ƒ„„„„„ƒƒƒ}||||||}}|}|}}~€‚‚ƒƒ„€}y{||{||}€‚ƒƒ„‚}~~~|yz~}}~~||}}|{wvyxuy€~{z{~ƒƒ~~}}}}}}}}}||zyzyyz}‚ƒƒƒƒ‚‚‚‚}|{ywvwx|}~}~~}|xuspnpruy}|{|~€‚ƒ…………„‚‚‚‚‚‚ƒ‚}zwvz}€€~~‚††~~~}}|}}~~~~~~~€€€€€€€€‚~}|{{{{{{{{{{{||||||}}||||{}~~~|||||||||}}}}}}}}||{{{{zzzzyyz|€‚ƒ„‚‚‚‚‚‚‚€}||||||}}|||}}~€‚‚ƒƒ„~}}||}~€€‚ƒ„‚}~~}ztsw}~}|}~€€‚„„‚|tswwy~€{z{~ƒƒ~~}~~}}}}}}{{yyxxwx{}‚ƒƒƒ‚‚€€€€~{zywvvwz}~~|zwurpnnpuy}|{|~€‚ƒ……ƒƒ‚€€€€‚|zwx{€€€‚„„~~~}~~~~€€€€€€‚‚‚‚€~~}|{{{zz{{{{{{||}}}}}}}}}}}}||||||}}}}}}}}}}||||{{{{{{zzzzxz|}€€‚‚‚€€€€‚‚~~}||||||||}}}}}~€‚‚ƒƒ„ƒ€~||}~€ƒƒƒ‚€}~~||yy|}„‡…€~~€€ƒ„„ƒ~wsuwz}}|z|‚‚‚}|}}}}~~}|zyyxwutvy}€‚ƒƒ€€~}|zxwvuuvy~€|zusqpnnpuz}}|{|~€€€‚„„ƒ€€€€€€}zvvy|€€€€„„~~~}~~~€€€€€‚‚‚‚€~}|{z{{zz{{{{{{||||}}}}}}}}}}||||||}}}}}}}}}}||||{{{{{{zzzzxz|~€€€€€}{z|||||||{|}}}}~€‚‚ƒ‚ƒƒ€~}~€€‚€€ƒƒ‚~~~}}{}}€ƒ…‚€€‚‚‚‚€}ysprw{|z{|‚‚‚€~}|}}}}}}|zyxwwutrtx}€‚‚‚€€€€€€€}|zywvusuvz€€€~|yutrpnnpuz}}|{|~€€€‚„„ƒ€€€€~zwuuy|€€€€„„~~~~~€€€€€€€€€€‚‚€€€~}|{{{{{{{{{{z|||}}}}||||}}}}}}}}}}~~~~~~}}}}|||z{{{{zzzyyyy|~€‚~}}~€€}||{{||||||}}}}}~€‚‚ƒ‚€‚‚ƒ€€€€‚„ƒ~|||~}ƒ„‚€}{xtolu|~{{|€‚‚ƒƒ}|||||||{yxwwvtsprv{~€‚‚€€}||}~~}|yxvvusstw|~€€€|yvtsrompty}}||}€€€‚‚ƒƒ‚€€~~~~}zxusvz}€€„ƒ}~~€€€€€€€€‚‚€€€~}|{{{{{{{{{{z|||}}}}||||}}}}}}}}}}~~~~~~}}}}|||z{{{{zzzyyyz}‚€~}}~€€}{||{{||||||}}}}}~€‚‚ƒ‚€€€‚‚ƒ€€€‚„ƒ~{xy||~‚ƒ‚ƒ„ƒ‚‚€}{ywuoks|}{{|€‚‚ƒƒ€}{z||||{{zywvsrponouz}~~|zz{|}|zxxvtsrrtw}€€€€~|xttrqonpty}}||}€€€‚‚ƒƒ‚€€~~}}{xtrsv{~€€„‚~€€€€€€€€€€€€‚‚‚‚‚‚‚€€€€~}|{{zz{{{{{{||}}}}}}}}}}}}~~}}||~~~~~~}}}}||{{zzzzyyyxxz{‚„ƒ€}}~€‚‚‚|{{{{{||||||}}}}~~€‚‚‚ƒ‚€€ƒ‚€€€‚ƒƒƒ‚€~|{||~‚†‡‡…„ƒƒ€}{zyzxtmqz|z{}€‚‚ƒƒ~{z{{|{{{zxwvsqnmlmry}~|zyxxz{}|yxwtrqpqsx}€€€€~|wtsrqonpty}}||~€€€‚‚ƒƒ€€€}|yxtpqrw}€€}}~€€‚}€€€€€€€€€€€€€€€‚‚‚‚‚‚„……††„‚~}}}}~~||}}}}}}}}}}}}~~}}}}~~~~~~}}}}||{{zzzzyyyxy{~‚ƒƒ‚€€€€‚‚|{{{||||||||}}}}~~€‚ƒƒ‚ƒ‚€€€‚ƒƒ‚€~~}}}~„„……„……‚~|zyyxxvpnw{z{}€‚‚ƒƒ~{z{{{{{zyxutqnkjikpv|~}zxwvvvx{||zywtrqpquy~€€€~|vtsrrpnpv{}}||~€€€‚‚ƒƒ€€€}|zxtqnorw}€€}||}~€€}€€€€€€€€€€€€‚‚€ƒ…ˆ‰‰‹‹ˆ‡ƒ€~€‚ƒƒ††…‚~}|||}}}}}}}~~}}~~~~~~~~~~~|||||{{zzzzyyyy{~ƒƒ„„‚‚€ƒ‚}|{{{||||||||}}}}}}€€€€„„ƒƒ‚‚‚„ƒ‚}~€€‚‚ƒ‚~~€‚‚„„ƒ~{{zzyxwwpnuyy|}‚‚‚‚€}{z{{yyzxxvtqomkifiov|~{xvtsstxz{zzyvtrqpqty€€€€€zvtssrqqrx||||}~‚ƒ‚€€€€€€~|zxvrokouy~€€~z|~€€€€~€€€€€€‚‚€ƒ„…ˆ‰‰‰‰‰ˆ‡…ƒ…‡ˆ‰Š‰‡‡†ƒ€~~}}}}}}}}~~}}~~~~~~~~~~~|||||{{yyyyyyy{‚‚„„……ƒ‚€‚€€|{{{{||||||||}}}}}}}~€‚‚ƒƒƒƒ‚‚‚ƒ‚}|}€‚‚‚ƒƒ~}‚‚ƒƒ‚~|{{zzyxwwvsuyy}~‚‚‚‚€}{z{{{{{zxvuroljgfiov|~{xvtutuxz{zyxvtrqpqu{€€€€€{wutusrsux||||}~‚ƒ‚€€€€€~|zwtqnkou{€€€{y{}€€€€~€€€€€€€€€€€€€€„‡ˆ‡ˆˆˆˆ‡‡††ˆ‡‰‰‡‡ˆ‰ˆ„‚~~}}}}}}~~~~~~~~}}~~~~}}||{{zzyyxzy{}€„„………„„‚€‚€}{{{{{||||||||}}}}}}~€‚‚‚‚ƒƒƒƒƒƒ‚€}|~€€‚ƒƒƒ€~€€‚‚‚‚‚‚~|{zzyxxwwwxtsxz|€€‚‚€}{z{{{{zzywurolifeflu|~€|yxuvvwyz|{zyxvsrstx}€€€€€zxvwvsuuvz}{{{|€€€‚ƒ‚€€€€€}yvqollpv}€€€€€}zx{~€€€€€€~}€€€€€€€€€€€€‚ƒ‚‚‚ƒ…††……†………………†ˆˆ†‚€~}}}}}}~~~~~~~~~~~~~~}}||{{zzyyxz|‚„…†††††„‚‚‚~{z{|||||||||||}}}}}}~€‚‚‚‚ƒƒƒƒƒƒ‚~{{~€€‚ƒƒƒ‚€€‚‚ƒ‚‚‚‚‚‚|{zzzyxxwwwwssy{|~€€‚‚€}{z{{{{zzywurolifeflu|~~}{zxxz{}~€€~|{yxxy|~€€€€€~{yxwvutvy{{{{|€€€‚ƒ‚€€€€€}yvqnkkry~€€€€}xwz~€€€€€€~}‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€€€~|}}‚„…ƒ~{{~~€ƒ†‡ˆ†ƒ€~~}}}}}}~~~~~~~~~~}}|{{{zzyz{}„‡†…………††‚ƒ€€€€~||{{{{zz{{{{{{||}}||}}|}€ƒƒƒƒƒƒ‚€}||}€‚‚‚‚ƒƒ‚‚ƒ…„…ƒƒ‚‚€|{zzzyyxxvxwtrvz~€ƒƒ|{yz{||{zyxutoljgfhlu|~~~}|||~€€‚‚‚‚€~}~~€€€€~{yywutvz|||}~~€€‚ƒ‚€€€€€}yupljltz~€€€{wuz~€€€€€€‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~|}}~€}xwxxyy|€‚†……ƒ‚€€~~~~~~~~}}|{{{zzz|}€‚…†…†………ƒƒ|zzz{{{{zz{{{{{{||}}|||||}€ƒƒƒƒƒ‚~}{{|€‚‚‚‚ƒƒƒ„…†……ƒƒ‚‚ƒ€}{zzzyyxwxxxtqry|€|{yz{||{zyxutqnkhgimu|~~~~}~€ƒƒƒƒƒ‚‚€€€€€€€€€~{xvuwz|||}~~€€‚ƒ‚€€€€~}yupllpu|€€}yvwz~€€€€€€‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~}}||}}~zxxyzz{{|~€ƒƒ„†…„ƒƒ€€€€€€~~~~~~}}|{{{zzz}€ƒ……„„………„‚€€|zxxxzz{{zz{{{{||||}}||{{{||€€‚‚‚ƒƒ€~}||{|€‚‚ƒƒƒ…„……†††„ƒƒƒ‚‚€}|{zzyyxxxyvsnpw|€€€|{zzz}}}{zywtrokhhinv|~}}~€‚„ƒƒƒƒ~€€€~~~€‚€~zwuwy|||}}€€€ƒ„}~{wtojkpw}€€€}yux{€€€€€€~‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~}}||||{{xxy{{{||}}~€‚ƒ„……†††††††…~~~~~~~~}}|{{{zz{~‚„„„„…„ƒ‚~~||{zxwyzzzzzzzz{{{{zz{{zzzz{{{|}€€€‚‚‚‚‚€}|{{z{~‚‚ƒƒƒ„…‡‡‡†„‚‚‚€}|{zzyywwx{}ytrw|€ƒƒ€}}{||}}|~~}ywspoljkov{}~}~€‚€€~|zyxyyz|€€€~}}|~€‚ƒ|wuwy|||}}~~€ƒ„|~~~~~}{wrmkmtz~€€}yux{€€€€€€~ƒƒƒƒ‚‚‚‚‚‚€€€€€€€€€€~~~~}}}}}}}|~~}}||{{{{{{}}}}~~€‚ƒ„ƒ†‡ˆ‰‰Š‰…€}~~~~}}||{{{{{|}€„„„„…†„~}|yyyywwwyzzzzzz||{{{zyzzyzzzz{{z{~€‚‚€~}}{{{{|~€‚‚ƒ„„…††…‚€€€~~{zyyxxwwxx{{utx|}‚‚~}|||}~€€|zvsonnnrz}~~€€|zxwvtrqruy~€€€~~|||}‚„„~xwxz|||~~~~~€‚ƒ‚~€~}{urmjnv}€~|xux|€€€€€€~ƒƒƒƒ‚‚‚‚‚‚€€€€€€~~~~}}}}}}}|}}||}}{{{{||}}}}}}}~~€‚ƒ…‡ˆˆ‰ˆ†~}}~~~~}}||{{{{{|~‚‚„„„‚~{zxwvwwwwwwwyzzzzzz{{{{yyyy{yyyzz{{z{|~€‚‚‚~}|{{{{{}€‚‚‚ƒ„…†…ƒ€|{}}{yyyxxwwvvwvwvwz}‚‚‚‚~}|||}ƒ„„}xvtrstx{~~zyxwvusqpqty~€€€€~{vxz~ƒƒ‚|xyz|||~~~~~~~‚‚~~~}yupnmqy~€~€|wux|€€€€€€~ƒƒƒƒ‚‚‚‚‚‚€€€€€€€€~~~~}}||||||||||||||||||||||||||}|{{}}€ƒƒƒ„„……„€}}~~~}}}||{{zz{}~ƒ„„ƒƒ{xvvwwvvxxwxyyzzzzzzzy{{zyzzzzyyyyyz{{|}€‚‚‚€~}{||zz||‚„„ƒ~~~~|v|€~|zyyxxxwvvuvywvy}‚‚‚‚~}||~ƒ…„ƒ€|zyy{{{~€€€€~yxxwuutrrsuz~€€€‚ƒ}yutw{ƒ‚}{yz{{{|}}}~~€‚‚~~|zuqoot{€€€|wux|€€€€€}ƒƒƒƒ‚‚‚‚‚‚€€€€€€€~~~~}}||||||||||||||||||||||||||}}|{||}€€ƒ„€||~~~}}}||{{zz{}‚ƒƒƒ‚€|ywvvvvvvxxwxyyzzzzzzzyyyyyyyyyxxyyyz{{|}~€‚‚‚€~|{||{{||‚ƒ‚€||}{xy€‚}{yyxxxwxzxvvtvy}‚‚‚‚~}|{||}€ƒƒ‚€~~}~€€€~€~{yxwuutsstw{€€€€€|vsty~ƒ€|zz{{{|}}}~}|}€~yuqnou|€€|vux|€€€€€€€}„„„„‚‚‚‚‚‚€€€€€€}}}}||||||||||}}}}||||||||||~~~~~~}}{}~}|{|€„…„}yz}~}}}}{zzz{{}€‚ƒ„„ƒ~zxwwwwwwwxxyxyyzzzzzzzz{{yyxxyyxxyyyyzz|}€€€‚‚‚‚~|{|||}|~€€‚‚‚€}|z|{x{ƒ€}{yxxxwx{zussv{}‚‚ƒƒ€}|zzz||~€€€€€€€€€~~€€ƒ€~{yxxxvvsuuz~€€€€‚€}yutw|€‚~z{{{{z{}}~~}{yz}~|xtpnpw}~~xttz}€€€€€~|„„„„‚‚‚‚‚‚€€€€}||||||||||||||||}}}}}||||}}~~~~~~~~~~}|~ƒ‡‡‚|yz}~}}}}|{zz{~ƒ„„„„ƒ€|yxwwwwwwwxxyxyyzzzzzzzzyyyyxxyyxxyyyy{{}~€‚‚ƒƒ€|{|}}~~€€}|z|{x{ƒ€}{yxxxwy}€zuttv{}‚‚ƒƒ€}|zz{|||~€€€€€~€€ƒ|yxxxvwwxw{~€€€€‚€}zvtw{~€€|{{{{z{}}}~~{wvy||wtposx~~~|wrtz}€€€€€~|ƒƒ„„‚‚‚‚€~}}||{{||||||||||}}~~~|}}|{||}}}}~~~~~~…ˆ‰ƒ~{{}~}}}|||||~‚„„……ƒƒ‚|wwwwwwwwwxxyxzz{{{{{{zyzyyyxxxxxxyyyyz|~€€€ƒƒƒƒ}|}|~€€€€‚‚ƒ|z{zv~‚ƒ€}{yxxxyz}ytrrtz~€€ƒƒ|zzz{{|}€ƒƒƒ‚‚‚€€€~€€ƒ|zyyyxxwxz~€€€€€€}{xuvz}€}|{{{z{}}}}yvx{}}~~~{vsopty~€€{uruz}€€€€€~{ƒƒƒƒ‚‚‚‚€~~~||||{{{||||||||||~€€€~~~~|||}}}}~~~~~~~~„‡‡…{{}~}}}||}~„……„…„ƒƒ~zxwwwwwwwwxxyxzz{{{{{{zyzzyyxxxxxxyyyy|}~€ƒƒƒƒ‚€~|~€ƒ……ƒ‚‚€||yxx~‚ƒ€}{yxxxy||wuurtz~€€‚‚}{{{{{|}€ƒƒƒ‚€€€€€€€~€€ƒ€}|{{{zyxy{€€€€}{xwwx{|{}||{{{|}}}}€~zwx{}}~~~{wsppu{~~~~zuruz}€€€€€{xƒƒ‚‚‚€€€~~~~~~}}{{{{{{||}}}}}}}}||~}|||~~~~~~~~~~~~~ƒ†‰…‚|z|}}|}}‚†‡‡†…„…‚~|yxxxwwwwxxxxyyzz{{{{{{zzzzyyyyyyyyyyyz|~€€‚‚‚‚‚€€„…††‚€€€|{xvv}‚„€}{zzyyyz}~yuuqtz~€€‚€~}~}|z{|ƒƒƒ‚€~}~€€~~€‚{z{{{zyyz}€‚‚~|{yyyyyz{{{{{||||{}~€€~{wy{}~~€€~{vsqrx|}~}}~~|yssu{€€€€€€}zuƒƒ‚‚‚‚€€~~~~~~~~~~}}}}||||||}}}}}}}}||}~~~~}|||~~~~~~~~~~~~~ƒ†‡‡„€~~€}}~…‡‡ˆ‡†…ƒ‚{ywvwwwwwwxxxyyyzz{{{{{{zzzzyyyywwwwyyz{|‚‚‚‚‚‚€€ƒ†‡††„‚€€‚‚€}|yvv|ƒ}{zzyyxxx|~{xsu{~€€ƒ‚‚€}|{|~‚ƒƒ}z|~€€~~€‚}{{{{zyyz}€€€}{{{{{{{yy{{{||||{}~€€~|zz|~~€€~{wsqsx|}~~~~~}ytsw|€€€€€€}ywƒƒ‚‚‚‚‚€€€€€~~~~~~~~~}}}{||}}}}}}}}}}||||||}}}}||}}}}}}~~~~~~~~‚…†‡…}~~€ƒ†‡ˆˆ‡‡…‚~zxwwvvvwwxxxxzzz{zz{{{{{{zzzzyyxxyyyyyy{}€‚‚‚‚‚‚ƒ„…†……ƒ„€€~€ƒ‚~~vu{‚}{zzzz{zwx}}xtu{~€€‚‚„ƒ„ƒ‚‚}||}‚||{{€€~€‚‚€}{{{{zyy{}€€€€€€€}|{|}~~}{zz{{{zz{{z|~€€||||}}~~~{wsruy}}~~~}xttw|€€€€{xwƒƒ‚‚‚‚€€€€~~~~~~~~~}}}{||}}}}}}}}}}||||||}}}}}}}}}}}}~~~~~~~~€ƒ…††‚}}}}~‚…†‡ˆˆ†ƒ€}zwxxvvvvwwxxyyzz{{{{{{{{{{zzzzyyyyyyyyz{}€‚‚‚‚‚‚‚‚„†…††…ƒ‚€€~}€€zvz‚}{zzzz|€‚ƒ~ysu{~€€‚‚„…„„„ƒ~||~~|z{|~€€~€‚‚€}{{{{zyy|}€€€€€€}|{|}~~}{xzz{{zz{{z|~€€||||}~~€€~{wsrwz~}~~~}xttx|€€€€€|zx„„‚€~~~~~~~~}}~~}|||}}}}}}}}}}||||}}}}}}}}~~}}~~~~~~}~~~~€‚…‡…‚~~~~€‚„††††„|yxwwwwwwwwwxxyyzz{{{{{{{{{{zzzzzzzzyyzz|€‚‚‚‚€‚‚ƒƒ„†††…„€€~‚‚€|{y{€}|{zzzxxww{zvrv{€€€€€€‚……„„„‚€}}€€}{{|€€€€€€€‚‚€~|}}{yyz{~€€€€€~{{}}~~~}{xyz{{zz{{z|~€{{|}}|~~€}wstx|}~||||wuty~€€€€~~ƒƒ‚€€~~~~}}~~}|||}}}}}}}}}}}}}}}}}}}}}}~~}}~~}}~~~~~~}~~~~ƒ…†ƒ€€‚„…††…ƒ|zxwvwwwwwwwwxxyyzz{{{{{{{{{{zzzzzzzzz{|~€‚‚‚‚‚‚‚€€‚‚ƒƒƒ„…†…ƒ€€€€€ƒ‚‚ƒƒ‚€}~€€}|{zzz{{}}{xttv{}~€€€€€‚„„ƒƒƒ€}}€€}{{|€€€€€€€‚‚€}{{{{zyz{~€€€€€~}}~~~~~}{xyz{{zz{{z|~€}{{|}~|~~€}wttx|}~||||wuuy~€€€€€‚‚‚€€€€€~€‚‚~~}}}}||~~~~~~~~}}~~}}}}}}}}}}~~}}}}~~~~~~~~ƒ…††„‚ƒ„„…†…‡‡„|yxuvvvvwwwwwwxxyyzz{{||{{{{{{{{{{zzz||~€ƒƒƒƒ‚‚‚€‚ƒƒƒ„„„ƒ€€~€€‚„‚‚€€~|{{{zy|}|xvux{~€€€€€€‚‚‚‚€€€~}||{{}~€€€€€‚ƒƒ}|{{zzyz}~€€€€€€~~~~}yxxy{{{{zz||€~{{|~~}}‚‚|wtvz|~}}}}~|wtv{€€€€€€‚‚‚€€€€€~€€††„ƒ‚‚‚‚€€}}~~~~~~~~~~~~}}}}}}}}}}}}~~}}}}~~~~~~~~ƒ††…„…†‡‡ˆ†……‚€~zwwvvvvwwwwwwxxyyzz{{||||||~~~~~~~~}€‚‚‚ƒ‚ƒƒƒ‚‚~~€€‚ƒƒ‚€~~€€€€~€€|{{{zyzz{usv{~~€€€€€€€€}{{}}{{{}~€€€€‚‚€}||{zzy{}~€€€€€€€€€€€~~|{yxxyzz{{||||~€}{{|~~}}€‚‚|vtuz|~}}}}~|xuw{€€€€€€€€€€€€€€„ˆ‰ˆ‡……ƒƒƒƒ€€~~~~~~~~~~~~}}}}}}}}}}||}}}~~|~~~~~~~~~~~~~~€ƒ††††‡ˆˆˆ‡†„‚€|zwuuuuuvvvxxyzyyyy{|{|~~}~~€€€€‚‚„„ƒƒ„„„„ƒ‚~}{}€‚‚‚‚‚‚€€~~~~€€€€€€€~~‚€{z{zzyy{vvz{}~€€€€€€‚}zzy}~~}{{}~€€€€€}|{{zz{|~€€€€€€€‚||{{yyyyz{|||{|€}|||~~}}€ƒƒ{wuuz}}}}~~€~yvx|~~~~€€‚}€€€€€€€€„ˆ‰ˆ‡ˆˆˆˆˆˆ††ƒ~}~~~~~~~~~}}}}}}}}}}||}}|}}~~~~~~~~~~~~~~~€€€ƒ††††‡ˆˆˆ‡†„‚yvtttttuvvz~€€~}{{{|~ƒƒ„……†‡ˆˆˆˆˆˆˆˆˆˆˆˆ††††„ƒ€~|}~€€€‚‚‚‚‚~~‚‚€~~~€€€€€€~ƒ‚~{{{{zzyz||}~€€‚„„‚|zz~€~|zz}~€€€€€}|{{zzz|~€€€€€‚ƒ‚‚ƒƒ‚~{{z{yyyyz{|||{|€€~|||~~}}€ƒƒ{utx|}}}}~~€~yxy|~~~~~€‚ƒƒ|€€€€€€€€€„…†‡ˆˆ‰‰‰ŠŠŠ‡„€~~}}~~~}~~~~~~}}}}}}||||}}}}~~~~~~~~~~~~€€€‚‚„………†‡‡‡…„‚}xtstsrrsw|ƒˆ‰ˆ†‚€€€„…„‡ˆ‰Š‹ŒŒ‹‹‹‹‰‰ˆˆ‰‰‰‰ˆˆˆˆˆˆ…„‚€~~€€€€‚‚‚}‚€~~€€€‚ƒ€€~€‚ƒ~z{{|zz{{}}~‚ƒ†…ƒ€€€{zz}~€€€€€€€}||z{y{}~€€€€€‚„ƒƒƒƒ‚{z{zyyxzxz||||{}~€~||}}~}}€‚ƒ€zuuy{}}}}}~‚{y{|}~€„…„|€€€€€€€€€‚‚……††‡‰‰‰Š‰†‚€}~~~}~~~~~~}}}}}}||||}}}}~~~~~~~~~~~~€€€‚ƒƒƒ„………†…„‚~zwtsttx|ƒ‡‡‰ˆ††„…‡ˆ‰ˆ‰‰‰‰‰‰‰‰‰‡‡ˆˆ‡‡‡‡‡‡††‡‡‡‡††††„ƒƒ€€‚ƒ…‰‹Š„€€€~€€‚€€€€€€~€€}~€€{x{}}ƒ‚€‚‚€€~{zz}~€€€€€€€}||z{z{}~€€€€‚„ƒƒƒ‚€}{z{zyyxzxz||||z{|~||}}~}}€ƒ„€ztvz|}}}}}~‚{y{|}~€‚ƒ„|€€€€ƒ„†‡ˆ‰‰ˆ‡ƒ‚€€~~}|~~|~~~|||||||||}}}~~~~~~~~~~~~~~€€€‚‚„†††…†„€zvvw{…‡ˆˆ††……‡‰ˆˆˆˆˆ‡††……„„ƒƒ„„„„…………„„……‡‡‡‡‡‡‡‡††ƒ€€‚…‡ˆ†‡†‡‚€~~~~~€€€€€€~~~€€~{|}~€ƒƒ‚€€ƒ‚ƒ€€€€‚{z|~~~€€€€€€€€}|zz{{}€€€€ƒ„„„€€~}zzzyyyyz{{{{zz|}}{||{|{~ƒytvz|}}}}}€‚‚€}z|}~~~}}~€ƒ‚|€€~~~€‚ƒ…ˆŠŠˆ‡„ƒ}}}||||~~~|||||||||}}}~~~~~~~~~~~~~~€€~~}}‚ƒ„„…………‚€~~…†‡††……ƒ„…†††„„…ƒƒƒƒƒƒƒ„„ƒƒƒƒƒƒƒƒ„„„„……„„„„„„††…ƒ„†‡†…‚ƒ„ƒ€€~~~~~€€€€€~~€€~}|~~€}}}}}€~|}€‚ƒ€€€€€€€€~{z|~~~€€€€€€€€}|zyz{}€€€€‚‚€~}zzzyyyyz{{{{zz|}~~}{||{|{~ƒytvz|}}}}~ƒƒ€~{}}~~~}}}~€€}€€~~†‡ˆˆ‰‰‡…„€~}}}}}}}}||||||||||}}~~~~~~~~~~~~~~~~||}}€‚ƒ……‡‡†„„……†…„„„‚‚ƒƒƒƒ‚‚‚€€~~€€€€€€‚‚‚ƒƒ‚„„„„„„„ƒ„…†ƒ{}„‚€~~}€~€‚‚‚€€~~€€~}|{}}||‚€€€€‚‚€€€€~}z{~~~~~~€€€€€€‚€}}~}|}~€€€€€€€€~}~~~~}zzzzyyzz{{{{{{z{~~||||{||}ƒ€ztvz|||||~€„„€~}}~~}{{|~€€~~~~‚„†ˆˆ‰‰ˆ„}}}}}}}}||||||||||}}~~~~~~€€~~~~~~~~€€€~~~}‚„…†……†……„„„„‚‚€~||{{|||~€‚‚‚‚ƒ€€}}}ƒƒ‚‚‚‚€€€}z}„„€~}}}€}z{‚……ƒ€€€€€€~|||}~€~}~€€€ƒ……„ƒ‚€€€}|z{~~~~€€€€€€€€€€~€€€€€€||{}~~~}zzzzyyzz{{{{{{z{}}||||{||}ƒ€ztvz|||||~€‚‚€~~~~}{z{|~~~€€€€~~~~}}€ƒ„†‰‰‰…~{|||||{{{{{zz||||||}}~~~~‚€~}}~~~€€€€€€€ƒƒƒ„„……††„„ƒƒ„‚~{zyyyyyz|~€ƒƒ„„„„„„ƒƒ€}{{{{}}~‚ƒƒƒ‚‚€~}€„†€~}|~„ƒ~…ƒ€€€€~~~~}zz|}~~€€‚‚‚‚‚€€€€€€~{{|€€€€€€€€‚‚‚‚€€€€€€€~}{{}|~~|{zyzzyyy{{{{{{{yy{{||||{||}€‚zuw{|||{}~€‚~~~~~|zxz{}~€€~~~~}}}~€„‡‰‰†|z{{||{{{zzz{{{{{{|||}~~~~€€ƒ„†††ƒ~~€‚‚‚‚ƒƒ…„„„„„ƒƒ„„„„ƒƒƒƒƒƒƒ~|{zyy{{|~~€ƒƒ„„„…………„„‚~}}}|}~‚ƒ…„„‚~~‚‚‚…~~~{yz}€€€~}~~~€~€‚‚€~~~~~}}{||}|‚ƒƒƒ„ƒ‚~€€|{|~€€}~~~€‚‚‚€€€€€€€~}{{~}}}|{zyxxyyy{{{zzyyyyzz{{{{{||}€‚zvx{|||{}~€‚€~~~}zxxz|}~~~~~~~~~~~}}}}€…ˆˆ†zyzz||{{{zzzzz{{{{{|||}}~~~„†Š‰‰ˆƒ‚‚‚ƒƒ„„„„„„†…††…‡„ƒ„„ƒ‚€€€ƒ‚‚‚~}|}~€ƒ‚ƒƒ„„„„„ƒƒ‚~~~~€‚„†…‚~|€~~~}„„‚}|~~~}~~}}}~€‚€}~~~}}~~~€‚‚‚€€€€€}z{|}€‚~}}€ƒƒ‚€€€€€€€€€€~}||}|{zxwwwyzz{{zzzzyyzz{{{{|{{|~€~zwy{}||{}~€‚€~~}{xvx{|~~~~~~~}}||}}}}€…‰‰ˆ{yzz||{{{zzzzzzzzz{{||}}~~€‚…ˆŠŠ‰ˆ†…„„„„„„………†††††…„„…„ƒƒƒ€~{zz~‚ƒ……‡†„€~€ƒƒƒƒƒ„„„„„ƒ‚~~~~‚ƒ„„‚€~}€~~~~}wz~~€}|~||}~||{z{{{}~}}}|{zz{}}}~~‚~~~~€}z{~~~€€~~€€€‚ƒƒ‚€€€€€€€€€€€€~}~~€€€~|{zxwwwyzz{{zzzzyyzz{{{{|{{|~€~zxz{}||{}~€‚€~}{xwy|}~~}~~~~~~~~}}||||}}„‰‹‰‚{yz{{{{{{{{{yyzzyyz{|}||~~€ƒ††‰ˆˆ‡†ˆ‰‰‡‡ˆˆ††……„„†…„…………„ƒ€}|zwvuz‚ƒƒƒ…‡ˆˆ†„ƒ‚‚„„„„ƒƒ…„‚‚€€~~€‚ƒƒ„„}zwy|}}||~~~€|vtw|~€€|{~}|z{zzz{{{|~~}~{{{zyyxwz|}}}~€ƒƒ€~~~€€z{}‚€‚ƒ„„ƒ€}}}€€€€€€€€}||~€€}{yxxxxxz{||zzzxyyyy{{z|||||~|yx{||{{{}~€€}zwx{~~~~~~~~~~~~}}||||}}„‰‹ˆ{yz{{{{{zzzzyyzzyyz{z{||~~€ƒ…………ƒ…†ˆ‰‰ˆˆ‡‡‡‡††‡‡†…„………‚zwtsrruz‚ƒ‚ƒ„†††††…„„„„„„ƒƒ‚€€€€€€‚„………„€{trtxxwxyyzuxzy{}||{zzz|{||||zzzyz|||~}{z|{yyy{{zyy|}}~~}~‚‚€~~~€€zz}~€ƒ„…„€€€€‚ƒ‚€~{{|~€€€€€€€€}||~€€}{yxxxxxz{{{zzzxyyyy{{|}}}||}}|yy||{{{{}€}zwx{}~~~~~~~~}}||||}}€…‰‰„|{|{zzzyyyyzzzzzzzyz{{||}~€€ƒ„„‚ƒ„‡‡ˆˆˆ‰ˆˆ‡†ˆ†‡ˆ‡†‡†…ƒ‚~wtppqqquz€ƒ„€}„†……††„„………„ƒ‚€€‚‚ƒƒ„……………‚|vtsuvustuy{xx„€{xxwyz|{{zxyzxwyyxxz|||}{{{{}~}}~~~}~€€€€€€€~{{~‚ƒƒƒ‚€€€€€€‚„ƒ€}|||}€€€€~|{}~€€}{yyyyyyyzzzyyzzyyxz{||}}{zz}}{zz{|z{{{}€‚€€€~~}zwx{~~~~~~~~~~}}||||}}}„‡ˆ…ƒ}||{zzyyyyzzzzzzyxyz{||}~€€‚ƒ‚‚ƒ„…………„†ˆˆ‡ˆ‡†‡‡†…„ƒ€|vsrqqrrsvxƒ„}{}€ƒ„………………„‚€‚ƒ„„……………†…„†„~ywwtuvvustw|}||{}}}{zwvxz|{zxwz|~}}|{zz|||}||{|}~~~~~}}€€€~~~~€~€ƒ„„„ƒ~~€~|‚ƒ‚||{|}€€€€€~~}}~}}}{yyyyyyyzzzyyxxyyxz{||}}{{{||{z{||{{{{}€~~~~}zwxz~~~~~~~~~~~}}}}}}}|||||~€…ˆ††ƒ||yyyyyyyyyyyyyyzzz|}}~~€€€€€ƒƒ„„ƒƒ…†‡‡‡‡‡†††„ƒƒ~{wsssssrstux~‚„‚{z|~€€‚ƒ……†ƒ‚‚‚ƒ†‡††††…„………†…‚~zzywtuvussuyz|~‚‚~{{zyvvz{zxvsu{~~zz|zyzz{{}}~~~~}}|{}~~~~~€‚€‚‚‚€€|xvx€‚‚‚{zz{~€€€€€€€€~~~~}}~~|zxxxxxxzzzzyyxxwxyzz{{~~}|{|||{z{|z||{}}}~€~}}~~yxyz~~~~~~~~}}}}}}}}}}|||||}~………†…„‚|{zzyyyyyyyyyyzzz|}}~~€€€€‚‚„„†‡‡‡‡‡…„„„‚€~{xvttttttstuvx|„„‚||}}~‚‚ƒ…„„„‡ˆ‰Š‰‡‡ˆˆ‡†…„„„ƒ~{{ywtturqruvy|€ƒƒ€|yxwvtwywusqpqsux{|}}|{zz|||}}}|{|||~~~}}~€€€‚‚‚€€€€€€{sry‚‚‚{z{}~€€€€€€€€~~~~|||||zyyyyzz{{zzyyxxwxyzz{{~~}zy|{{{{||z|{|~{{}~}}}~~{yz|~~~~~~~~}}}}}||||||||||}}~€ƒƒ…ˆˆ†„€~{zyyyyyyyyyyyz{{|~}~~~€€€ƒ„„‡ˆˆ‰ˆ‡†…ƒ„ƒ€}zxvvwxvvvuuuuvvw|„„ƒ}€€‚ƒ„†…‡‹ŒŒŒŠ‰‰ˆ‰‰ˆ…ƒ‚€€€€|{zzwtuvuttwxz~€„‚{ywvtttsrrrssuuuuuy{}}~~}~€€€~||~~~}}}|~||~~~~~€‚€€€~}€€|st|‚„„‚{z|}~€€€€€€€€~~|{|||{yyyxyy{{zyyyyxwxyzz{{~~|{z{{{{{|{{{|~‚{{}~~~~}}~~|z{}~~~~~~~~~~}}}}|||||||||||}}|}€€‚…†‡‡„~{yyyyyyyyyyyz{{{|}~‚…†ˆ‰‰Š‰‰ˆ†…„‚€~{zxwuvwyyywwvvvvwwx{‚„„ƒ€~~€}{{~€„†††……‡‡††††ƒ~~}}|{|}zxwwtutv{|xx{}|zxtsuusolmrwwvvwvtvz~ƒ„‚€~€€~}||~€€~}~€~~~}~€€€€|y{}zvx‚„„‚}||}~€€€€€‚ƒƒ‚‚‚‚€~~}|zzyxyy{{zyyyyxwxyzz{{~~|zyzz{z{|{{{}€~xy|}~~}}}}zy}~~~~~~~~~~~~~~~|||||||{||||||||}}}}}}‚ƒ…‡‡‡ƒ}zyxxxxwwyyyyzz{|€ƒ…‡ŠŠŠ‰ˆ‡‡†…„‚~}|{yxxxxxxxxxwwwwwwwxy{~‚ƒ„ƒƒ‚}vuwxxwz|}„……ƒ~‚†‡„€€}ƒ‚}xvvvtwxwrpuxyzwustwwphhpvyxyyyxwy{~~ƒƒ‚€}{|~}€€€~‚‚}~{zz|~}€|{z{}||}~|z|€‚ƒ…ƒ~}}~€‚ƒƒƒ……„„„‚‚‚€}|zyyz{{yxxxxwwxz{zz|~{yyzzy{{{{{{|€zvy|~~}|wwy}~~~~~~~~~~~~~}}~~|||||||{||||||||}}}}}}}}}‚„‡ˆ‡}zzzzzyyyyyyzz|~‚ƒ…‡ˆˆŠ‰ˆ†…„ƒƒ€}|{zxyyyyyyyyyyyxxxxxxxyy{}€ƒƒ„ƒ‚}|xusrsuvvxz|||}}}‚‚ƒƒ„††‡…ƒ€€„‚}yuvvtrqrnknsw{zwtttuoecjswxz{{zyvx{{z~}{zz}‚ƒ€€€}}}~~}{zz{{|~~{zz{|||||€}~ƒ„…„ƒ€~~~‚‚ƒƒƒƒ‚‚€€€|{z{{yxxyxwwxyzzz|~{yxxyyz{{{{{|€~yvy|~~}{wvy}~~~~~~~~~~~~~~~~~||||||{{{{||||||}}}}~~}}}}€‚†ˆ‡…‚~|{{zzzyyyz|}ƒ†‡ˆˆ‡†‡†„‚€€|{zz{zzzzzyyzzzzzzyyyyyy{{z{{‚ƒ}wolotuqsyy{{z}~‚„ƒƒ„ƒƒ‚‚‚€~ƒ‚zwwuupklnlmpw}}xuttsoihlswy{||{{xwwwtx‚€|z{~‚€€€~{||}{xuvxz{}{xxx{|~}€‚€~€€„††ƒ€€€€‚„„‚ƒ‚€€€€€€ƒ‚ƒ}{zyyywwxxwxyzyz|~|xwwwy{{{zz|}€‚~zvx|~€~{vux}~~~~~~~~~~~~~~~~~||||||{{{{||}}}}}}}}~~~~~~€„‡ˆ‡…ƒƒ‚}}|||}~€ƒ†ˆ‡‡†…„‚€~}||||{{||||{{{{{zz{{zzyyyy{{{{{|€ƒƒ€~€€yrlinrrnpux|||~‚„ƒƒƒƒ‚‚‚€~}ywwunmiopnnrvsmmptusomptwy{||{{yxwvstxƒƒ}‚ƒ‚€€}|{{}}|}zyz{{x{~~€€‚€‚ƒ…‡„‚‚‚‚‚€€€}|~~~€€€~|zyyyyxwwxyzyz||xwvwyz{{zz|}€‚€|x{|~€€~yuvy}~~~~~}}~~~~~~}}||||||{{{{||}}}}}}~~~~‚†ˆ‡†‡‡††ƒ‚~~€ƒ„†‡†…„ƒ€~|}{{{{||}}|}||||{{{{{{{{{{zzzz{{{{|~€‚„ƒ€ƒ‚|smjlmrvux{}}}‚ƒ€€€}|ƒ‚zxvrmiglqttromlmquvtqoruwxz{}{zyyxvtppty}€€}ƒ…ƒ€€}}~}}~~}yx{{||€€€€€~‚‚‚ƒ„„„‚€€€€€€‚ƒƒ„„„„‚€~~}|{{}~~€|yyxxvvwzyy{|}€€|xwvvxzzzzz{}€‚{|}~~~|yvw{}}~~~~~~}}||||||{{{{||}}}}~~~~~~€‚„†††ˆˆˆ‡…ƒ‚ƒ„…†††„‚|zz||}}}}}}}|}||||||||||||||{{{{|||||~ƒ……‡„€~|ztlhikr|€~~€€€€~~}€||…ƒ€|yvohdelrvvtroooqruutstuwwwz{~|zwvxxwuuvwxww|€‚‚‚€€€€€€€~}}}{z~€€~€€€€€~~‚‚‚‚‚‚‚€€€€€€‚ƒ„††‡‡†„‚~}|zy{{|~€€~zvwwvvvxxyz|}€€|xuvvxzzzzz{}€‚€|}}~~~|yvx}}}}}~~~~~~}}}}||||{{z|||}}}}~~€€‚ƒ……‡ˆ‰‰‡………„„„‚~{{zz{|~~~~~}}}}|||||||||||||||||||}}||~ƒ…„‡ˆ†„ƒƒ‚€|unlmv€‚~}}€~}~€}z|…„‚|ytnf`emrtwwtrrssrtuusstustyz}}xtsuwxwstwwwx|€€€€€€€€€~~}||yz}}}€€~€€€~}||‚‚‚‚€‚‚€€€‚‚‚……†‡‡†„‚~~~|{zzyz|€|ywvvvvxxy{z}€€}xttwyzzzzz{€~}~~~~~~~}{ywy|}~~~~~~}}}}||||{{z|||}}}}~~~~~€€ƒ„†††………ƒ€}|{{|{}}}~~~~~}}}}|||||||||||||||||||}}}}€„„†‰ŠŠˆ†…„„€~{vtw}€€‚~}{{{|~€€~|z||y{€‚„…€ztnfcfmsvxxwwvuttuvwvwutqruvyywrqsuxvttvvwwz|}~€€€€€€€€€€~}}}zz|zwvtw{~}|~€‚‚‚ƒ€‚‚‚‚„……„„…ƒ~{{|}zyyxwy{~‚|yvvvvxxyz{~€}xttwyzzzzz{€}{|~~~~~~~}{ywy|€‚‚~~~~~~}}||||||||||||||~~~~~~~~~~}}~‚ƒƒ‚~|{{{}}}~~~~~~~}}|||{||||||||||||||{|~~~~|‚†‰ˆ‰‹‹‹‡……ƒ€‚„€zy{~~~‚~zyyzz}€€~{xz€†‡†€ztojegnruvvxxxutttwxyz{yusuwwvrporssrqrtuvxzyz|}€€€€€€€€}}}~~€~|xtns|€~‚„„„ƒ€€€‚‚‚ƒ„ƒƒ‚„ƒ|yyyyzxxxvvz}yvttwxxxy{~€€zustwyzyyxz}~{xz}}|||zwwy}ƒ…~~~~~~}}||||||||||||||~~~~~~~~~~~~}|||}~~}{{z|||}}~~~~~~~~}}|||{||||||||||||||{|}}}}~€‡‹†~„‹‹Š…„„ƒƒ…‡‰…}z{€€€|zyyyxz}€€€~|wtx‚†‡†ztojfgnruvwwxxuttvwxy|}ywuwxwtpnnooonorsuwyzyz{}€€€€€€~~~€€}zvtx€‚‚‚‚ƒ€€€€€€‚‚‚ƒ„ƒƒ‚‚€~zwwvwxwxxuvy{}€‚}xuuwxxxx{~€ytqtwyzyyxz}€}xwz|~}}|yvz‚……ƒ‚~~~~~~~~}}||||||||||||~~~~~~~~~~~~~~~~~|}}||||||}}}}~~~~~~~~~~~~}}|{||||||||}}|||||||||}‚‰‡z|€„…ƒ€ƒƒ‡Šƒ}|ƒƒ~|||zyvwy~}~|zurw€†ˆ‡ƒ}unkhhnsstuxywsqsuwyy||zxxyxvsommljgkmqtuxz{|{z{|}€€€€~}}ƒ„‚€~zvuy€‚‚‚‚‚€€‚„‚€€‚ƒ„‚ƒ‚‚|yuuuvvvvuvwwyz|~|ywwxxxx{€}xsrtwxyyyx{~}uu{}~€~}|{y|‚ƒ‚‚‚ƒƒ~~~~~~~~}}||||||||||||~~~~~~~~~~~~~~~~~|}}||||||}}}}~~~~~~~~~~~~}}|{||||||||{{|||||||||}…‚}z}xwz|yssx{xttx‚†ƒ~}|~~}~~{ywvyz{zwtqw€ƒ……ƒ}umkhjnstuxxwpjfgijmrsvwvvwwtqokgddhgkruxz{|}|{{z{€€€„…†…ƒ~}zwyz|}~}}}{wz‚ƒ€€‚ƒ„‚ƒ‚{wtsstvvvuuvwyy{~€€|wwwwwx{~€|wrqtwyzyyx{ysu|~€~}||„ƒ‚€€€€~~}}}}}|}}||||||}}}}~~~~~~~~~~~~~~~~~~}}}}||||||||~~~~~~~~~~~~}}}|||||||{{zz{{{{|||||}{z}zrttuvtuvspoont|‚|zxw{~€~}zwvvxyzwrry€ƒ„†‚}wolihlruxxwrg_]]\`joqsqrrtsrqmhdddijmrx|~~~}{z{}€|z|~€„†}ywwwywvsuz}}yw|€‚ƒ€€€€‚‚ƒ‚‚|yvssrsuuvutuwwxz}€~zxwwwy|~~zuqqtwz{yxx{€}vty}~~}{|€„„‚ƒ~~~~~~}}}}}}}|||||||}}}}~~~~~~~~~~~~~~~~~~~~}}}}||||||||~~~~~~~~~~~~~~}}}|||||||{{{{{{{{{{{{z{|zwxzwruz||zrnoonnt}}xvvz}€~}{xwwxyyxvvz€„……ƒ}xqnjimsvxvvpcTOOMWdorsqponllikjiggjmnosvz}~|zyz}€~{{|}„‡ƒ{ywx}~||~~|wvx|€€€ƒ€€€€€€‚‚ƒ‚‚|wtrrrsuuuttuwwxx|}€|ywvww{~~zuqqtwz{yxx{~€{vty}~~}{}‚ƒƒ‚~~~~€€~~~~}}}}}}}|||||||}}|~~~~~~~~~~~~~~~~~~~~~~~~~~~~|}}}{||||||}}}}~~~~~~~~~~}|}|||||||{{zz{{{{{{{{{|}yvuzvnqwwz~~tqoopos|~vuv{}~}{zxwxxwwwxz‚‚ƒƒƒ~unjkpsuuuvqiTLNV_irttsqoifeefhigefmqrsuwz{{yxxz|~}|}|{}€‚†‰Š…|zwz}€|urrv||}~ƒ€€€€‚ƒƒƒ‚€|vtqqrstttstvvvwwz||}€~ywuvx{~yrpquxyyyyx{{uty}}~}|‚„„ƒƒ~}}}}~~~~~~}}}}}}}|||||||}}|~~~~~~~~~~~~~~~~~~~~~~~~~~~~|}}}{||||||}}}}~~~~~~~~~~}|}|||||||{z{{{{{{{{{{{||xpnqplmrssw{ztqqqooq{€yvw{|}}~}|{yxxxvvuwy|~z}€ztpqtuuvttpka\[bhmqrpnmnnkigeccccgntttuxyzywutwz}}}~€…ˆ…„„‡ˆ‡ƒ{y{|~€€|yxtorv{€€€€€€‚ƒ„ƒ‚~{urqqrstttstvvvvvyz|}€zwwwx{~xrpquxyyyyx{~ysuz}}}|‚„ƒƒ‚€}}}}}}~~~~~~~~~~~~}}}}}}}}}}~~~~}}~~~~~~~~~~~~~~~}}}||||||||}}}}}}}}~~}}||||{|}}||||||||{{{{|}|wohhklosqory{uprtsqu||wx||||~~||{zwvuttvww{{{}€zwvvututrnifbchopoljgfimqpmhe_Z[dpvxxxxxvsnmprw|‚‚~~}…Œ…~z{|}~€€€~€}vqpuz}€€~~€‚……ƒ‚}yurpqssssstvvvuuuvx{}€€|wvvx}}vqqrwxxxwxx{zttz~~}€‚„„ƒƒ~}~~||}}~~~~~~~~~~~~}}}}}}}}}}}}~~}}~~~~~~~~~~~~~~~}}}||||||||}}}}}}}}~~}}||||{|}}||||||||}}}}|}}xpkkjkmpmmpv|ytsvvtw|~|yz||||{|}|zywvtstuvvx||}~~|yxwvwwwtoljhkopolgb``chnpnjgc_`hrvxxxxurmhhnsvz~~‚ƒ~~~~~‚‡‡ƒ€}|~~€€€‚‚€ztqotwz{|~}€„…ƒ‚|xtqpqssssstvvutttuwz|€€|wvvx}~{uposwxxxwwy{~~xtvz|}€ƒƒƒƒ‚~{y{||||}}~~~~}}}}}}~~}}}}}}}}}}~~~~~~~~~~~~~~~~}~~~~~~}}}|}}||||}}}}}}}}~~~~}}}}}}{{{||||||||||||||||}zrljklmlghks{zxvuuvy}~}{{|~|z|{{{zzwvvsrruux|€}}}|{yxvvwtpnlnoqrpkd]XX]cjomidb^_fptuvxxtnhdfovwxyyz{~~~€€„‹””Šƒ~}~€€€‚€€}{sopqqty}‚ƒƒƒ|wtrqrrrrrstvvutsstwy|{wuvy|ztppswyxxxwy{~|urvx{‚ƒƒ‚‚{vy{{{{{||~~~~}}}}}}}}}}}}}}}}}}~~~~~~~~~~~~~~~~}~~~~~~}}}|}}||||}}}}}}}}~~~~}}}}||{{{||||||||||||||||}zrljlllgdfjqy}zywxxz}}|||{||z{zzz{zyxvussstty€}}}|{yyyyyvnjmoqqqokcZVW_fhecbaaacgnqrtuurlfadkruwzz{}{xxz}‚ƒ…‰‹‰„€~}~€‚‚€€€€zwvy|~€€‚ƒƒ€{wrpoqrrrrstvvutsstw{~zusuy|~ytppswyxxwwy|~zvtux‚ƒ‚{vuy{{{{{zz}}~~}}}}||}}}}}}}}||}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}{{||}}}}}}~~}~~~}}||||zz{{|||}}}||||~~}}~~~{rkhkmkedhnr{€~{zzzz|}|{{||||{zzyzzzzywtsrrsv~€€~}|||yxyyxuoklopppmib[XZdc^VXY\^cgjmpqrstqkgcciouwz{|}|zwvy~€‚†††‡‡†ƒ€~}€‚ƒ€€€€~}~~~‚ƒ‚zupoopqqssstvvusssrv{~€€{vsuz}|wrnpsyyyyvvy{~ystw}‚ƒ}vrtz{{{zzz}}}}}}}}}||}}}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}||||}}}}}}}}}}||||{{{{|||}}}}}}}~~}}~~}skijkieflsv|~|}}z|}|{{||||{{zz{{zyywsqqrsv{€€|vuvwxupmljjlmnkf__iok[KNNOWagjkloqrqoiebcimrvz{|}}€€‚ƒ„‡†‡‡‡‚~ƒ‚~}}~~~~~‚}ytpoopqqssstuuussstx|€ytsuy||wqpqtwwwwvvy{|wsty‚‚yrrw{}{{zzz}}}}}}}}||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}|||||}}}}}}}}}}}||||||||||}}}}}}}}~~~~~}ukhhijgipuy|~~}~}||{{{{|}}{{{zz{||zxrpnorsx~‚}wrstvwtpiecfjkkhdhjlh\RKHHPZefbagopokgc``cgmv{|{{}„…†ˆ‰‰‰‰Šˆƒ€€€€~}~ƒ‚~}}}~€~~~~~~€€~|wsonoppqssstuuussstw}~xttvy{{vopruwwvvvvy{{wsw~‚‚zurrw{{zyz{|€}}}}}}}}||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}|||||}}}}}}}}}}}||||||||||}}}}~~~~~~~~ƒƒ|pjjkkoquy}~}||}€€|{{{}~||{{{yzzwtpnmnqqtz€~xrqqtvsohcdghg^_cjlmhaWQNMOYfaWW`imnkfb_]_bmu|}{{}€ƒ…†††‡ˆˆŠ‰Š†ƒ‚€€ƒ‚||}~€~~~~~~~~€~|xspmlmpqssstuuussstw}~wstvy{ztopsvwwvvvvy{{wu|‚ƒ‚‚~{wurrw{{zyz{~‚||||||||||||}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}||||||||||||}}}}}}{{}}}}~~}}~~~~}}~~‚‡‡vpnmovxz|~}{|}~~ƒ„‚}{{|}}}}|{{zyvtrqmjkoqqu{|zuqnorrqoheehlh]UXckig_WTVY^bi`WPYekjga_^\`dmv||{|„…†……†‡ˆ‰Š‰‡„ƒ„„‚€€ƒƒ}}}~~~~~~~~~€‚€}{wsollmprrrssttssrrvy~€|vssuz{xsopruwvuuuvx{yw{„„‚zvvrotyzzzzz}€„||||||||||||}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}||||||||||||}}||}}~~}}}}~~~~~~~~}}~‚†‰ƒ}xvvtux{}~}|}~€„‡‡…}}}}}}}|{zyxvsqnmlkmopqtwwxvuqpnnppqogbempm`VWYacd^YZ``_`d^ZPS`hcUSV]]`dnty~~|{}~‚ƒ‚ƒƒ…ˆ‰Š‰‰†„„„ƒ€€‚ƒ€}}}~~~}}}}~~€}{wsolkloqrrssttssrrvz€{vssuyzvqnoruvvutuvxzw{€‚‚€{xxzxx|~ƒ„||||||||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}{{||||}}||||||||}}}}}}}}~~~~~~~~}||€†ˆ†€~~zzz|~~}{|~€‚ƒ†ŠŠ‡„€}}|~}||zyxvspnnonklorsrrqpqtrnkmopogcfkigdYQV\bca`bc_\XYVRQU^fcSJNY\`fnsx~~}}|}|~€‚„†ˆŠ‰‡††„ƒ‚ƒ‚€}{}~}||||}~€~{yvsojklopqrtttrssqrvzzsrsuyyupnpruuttstvxxz~‚‚‚€~||€€€‚ƒ„ƒƒƒƒ„„„„||||||||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}||||||||||||||}}}}}}}}~~~~~~~~~~€„‰ˆ†„‚‚€€~z}~}|~€€‚…ŠŠŠ‡…ƒ€~~}||zyxvromkmmkjnprrttqnoqqlklnmiegic]Z[UW[a`^\_fga\XTOTX]baWMOX_ekrvz}}~~}~€€€‚ƒ……‡‡††‡„‚‚…„€~~~~}~}||||}~€€}zxvsojjknoqrtttrssrtx|€€~ysrsuwwtonpruuttsuvvz‚‚‚‚‚„„„„„„„…„„„„„„„„||}}||||}}}}~~~~~~}}~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}{{||{{{{zz||||}}}}}}}}~~~~~~~~~~~~‚‡‡†…‚‚€€{z~~}~€€‚ƒ„ƒ„‰‰Šˆ‡…‚~~|{zxwtrnjklmjjlorrturmonjinoonlhiicYY\Z]aa^TOZiojbZXZ__`de[PT\dkqvz|}}~~~~€€€€‚‚‚‚‚‚…‡ˆ††ƒ„††‚~}~~}|||||~€€|{xurniijnpqrtttrrrruy}€€~yrqsuwvqnnpsutsstsvy‚‚‚‚‚ƒ………„†„ƒ„„ƒ‚ƒƒƒƒƒƒƒ}}}}||||}}}}~~~~~~}}}}}}}}}}~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~}}~~||{{{{}}||||}}}}}}}}~~~~~~~~~~€ƒ†ˆ…„ƒ‚‚€€zz€~}}~~€€‚‚ƒ…††ˆ‰†‚€}|zxvsqmjhjjkkkoqrtsqnmklprqppljjlf^[]abc`VMUalojb]^enmhig`Z]ckrwyz|}}}}€‚‚ƒƒ„‚‚„„†ˆ‡„„†ˆ…€€~~~}|||||~€€~{zwurniijnpqrtttrrrsvz~€}wrqsuvtpmnpsutsrstv|€„„……………ƒƒ‚€~}}‚‚‚ƒƒ}}}}|||||}}}~~~~~~}}}}}}}}}}~~}}}}~~~~~~~~~~~~~~~~}}~~~~}}||{{|{{||||||||}~~}}~~~~~~~~~~~„‡ˆ…ƒ‚ƒ‚€}yy||{|yyy|}}€‚…‡†ƒƒ‚~~|{wqnmighjnonnqrttsomnrrppopljlke]]aefaYNS]gnnjgdbfooiiiaY[dnuxzz|}}}}~€„ƒ……ƒ‚‚‚ƒ…Š‹‰…†ˆ‰„€}~}||{{{~~|zywvspljknoqrtttrrrsw|€€|uqqsuvromnrsutsssvz€ƒƒ„„„„„„„zxz{|y{€‚‚€}}}}|||||}}}~~~~~~}}}}}}}}}}}}||||~~~~~~~~~~~~~~~~~~~~~~}}||{{|{{||||||||}~~~~~~~~~~~~~~†‰‡„ƒ‚ƒ‚€|{}|zyxwwwwy{||…†„„‚~}}{xqnmkiikoqsrsssstsrssqrrrppool_Y^ehe]UT^gloligecipkcbfc\]epvy{z|}}}}~~~~~~€€€‚ƒ„‡‰ˆ†‡ˆ†„‚€~}||{{{~~}{yxvvspljknoqrtttrrrty|€zuqqsuupmlnrsttsrry‚ƒƒƒƒƒ„„}{vqrtwz{y|€ƒ‚~~||}}}}||~~~~~~~~~~~~~~}}}}||||}}}}~~~~~~~~~~~~~~~~~~~~~}}}||||||||}}}}}}~~~~~~~~~~~~~~€‚…ˆ‡…ƒƒ€ƒ‚€€~|~|xxxxvvuw{||‚„…ƒ‚}|||zrnmlllmprvvvtttttuvtqrttrrqpi`[bkne[V\ejmnkgddgmmidegdbekruz|}}~~~~~}||{|~€€€€‚ƒ……†††‡‡…‚€~~}{zzz{}~}{zxxwvtqnllmoqsssrrrruz}yspqrttplloqtssrqu|ƒƒ‚‚‚‚}vsqomorxyz|„}{}}}}}}}}~~~~~~~~~~~~~~}}}}||||}}}}~~~~~~~~~~~~~~~~~~~~}}}}||||||||}}}}}}~~~~~~~~~~~~~~€€„‡‡…ƒ‚€€ƒƒ€‚‚€}{|zxxxxvvuvz|}~ƒ…„‚}|}|zrnmopoprtxxxwvvttvtsrsuusrqqj`_gnph__ejmnmjfefikkhiihffjmquz|}}~~~~~||{z|~€€€€€€€€ƒ„……ˆ‰‡„€~€~|{zz{}~~~|zxxwvuqnlmnpqsssrrrrvz~€~wrpqrtsommpstssrtz‚ƒƒƒ‚‚}wsponmlmsyz{~€ƒ‚~{z~~~~}}|~~~~~~~~~~~~~~~}}}}||||}}}}}}}}}}~~~~~~~~~~~~}}}}}}}}||}}}}}}}}~~~~~~~~~~~€„ˆˆ…‚‚‚‚€‚~|z{yxxxxvvuvxy{|}‚„ƒƒ€}z{|yuomprtuvx{{{ywuttvurruuttrqrngekoolhilopnhgfeghffimlihkmprvz||}~}~~}}{{z{}~~~€€€€€€‚‚„†‡†„}~€}{{{{|~}|{zxxwvupnmmoqqrssrrruy}€~wrpqstqnlmpsssrsyƒƒƒ€~xsrponnmloswy{‚~{zy~~~~~}|~~~~~~~~~~~~~~~}}}}||||}}}}}}}}}}}}}}~~~~~~~~~~~~}}}}}}}}||}}}}}}}}~~~~~~~~~~€…‡ˆ†ƒ‚‚€‚ƒ|ywxxwwxxvwwwtnlmx‚…„ƒ€}zyzzxroqsuwz}~~}|yxvvvuvvssqppoqokkoppmmnqqogaacbaaadjmmklnpqux{||}|}}}zzzzyz}~~~}}€€‚ƒ„„„„ƒ~€}{{{|}|{{zxxwvuqnmnpqqrssrrruz}€{tqpqstpllmpssssv|ƒƒƒyrppooonkjmqwz{}€‚‚€}zxx~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}~~~}}}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~€„ˆ‰†ƒ~~‚„‚~ƒ…}ywwvvvwyyyxxqmjjr€†„‚~y{{{ywtrtwz|~€yxwvvutsssponloqpprrommprqmd__aba``glonmnpprvy|}}}}}|{{{zz{{}~~~~}~€€€€‚…††~€}{zz{||{zzzxxwvuromnqqrssrqqruz}€€~ysqpqqqnkkmqssuv|ƒ„„ƒspoooomlkjnuwy|€~yxww~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~~~€„ˆˆ†„„€~~ƒƒ‚€€„„€{ywwvvvwxyxwurnmmp|……‚€€€~|zxuvwz|~€|{zxvvutsqnmnnpstuutpoprsrngcccdcdhloplloqrtwz|~}}}}|{{{zz{{}~~~~}~€€€‚„…‚}~|zzz{||{zzzxxwvuromnqqrssrqqsvz}~~|wtqpqqqnkloqssw{„„ƒ„‚wponnnnmlkjnuyz}€€~yxww~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~}}}}}}}}}}}}}}}}|~~~~~~~~~}}~~~~ƒ‡‡†…ƒ‚}}€‚ƒ…ƒ€„{ywwvvuwxxutttrqmn{…†ƒ€€€€~|zxxxy{|~€~{yyyxvuutrooqsttvvwvtrqruuqjedeeefhnponoprsuz|}}}}|||{zzzzz{}~~}~~€€€€€€€~~~€~~{zxx{||{zzyyxwwtqonnpqssrqpqty|}|||vrppppomllortv{€‚ƒ…ƒ„~qnlnommlkkkntxz€€{yxvv~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~}}}}}}}}}}}}}}}}|~~~~~~~~~}}~~~~†‰‡…ƒ}|~€‚ƒ„„„€}ywwvvwvvurrry{vtu}…‡„‚€€~{zzzzz{{|}~}|{zyxwvwvtsstuwwwxzzywstvvsokihfegipqppqrtuw{|}}}}|||{zzzzz{}~~}~~~€€€€€€€€€€€}~|zz{||{zzyyxwutqonnpqssrqpqty|}||ytpopponmlmpsux}‚ƒ„„ƒ~vllmmnmmlkkkovz}€€~zxvvv~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~}}}}}}||}}~~~~~~}}~~~~‚†ˆˆ…„€}|~€‚„„„‚~‚ƒ€}ywwvxwvuropsy|{}~…‡†‚‚‚€~}|{zzzz|||}~~~~|{zzyxxvvvvvwwyy{}}|xvvwwupnkihjlrqrrtwwx{||}}}}}|{z{zzzz{|~~~€€€€€€}}{zzz}{{z{{zxvtttsrpopqrrrrorw|}}|zuroooonmlmnprw|‚ƒ„‚zollmmmlkkjilrx{€€}yuutv~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~}}}}}}||}}~~~~~~~~}}~~ƒ‡ˆ‡…ƒ{{~€‚„…ƒ~}€ƒ€|xxxxxvsspmosw}€€††ƒ€€€€~|{{{|||||}~~~~|{zzyxyyxxwxxxz{|}}|zyvwwwurqomnosttvwzzy|||}}}}}|{z{zz{z{|~~~~~€€€€€€}{xy{}}{|||{{{zxvtsssrpopqrrrrrux|}|zwrpoooonmlmnptz‚ƒƒƒ{rmllmmmlkkjjmsy|€‚~{vuutv~~~~~~~~~~||~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~}}}}}}}}}}}}}}}}~~~~~~~~}}~~€…ˆ‡†ƒ€~||‚‚„ƒ€€‚ƒ€}yyzzyxurpmmrw|}{{ƒ†„€€€€€€€~~~|}{zyzyzyzxxwwxy{}~~~}{xwvvyywutrtvuwxz|||}~~}}}}}|||{zzz{{}~}}€||{xuuy~}{{zzzzxvtrrssqpprrrqqrvz|{{ytqoooooomkmoru{ƒ‚€yqmkllmlkkjiinuy}‚‚|xvvvvv~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~}}}}}}}}}}}}~~}}~~~~~~~~~~~ƒ‡†…„}|}}‚‚ƒƒ„‚~€|y{{|zxuqonqv{}~{x}‚‚€€€€€€€€€€€~|{zzyyxyyzyxwwxz{}~~~}{|z{zz{zyxxxyz|}}}}z|||}}}||||||{||{|~~€~||}|zxz}‚}{zzzzxvtrrssqpprrrqqsx{}zywsonoooomlkkns}‚ƒ‚€|tnkkllmlkkjiintx|€‚€{wuuuuu~~~~~~~~~~~~}}}}~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}||}}~~~~~~~~~~~~~~~~†‡……ƒ||}}€€€ƒ„ƒƒ„ˆ„~€‚~zz}}{zxuqorvz{|{|‚ƒ‚€€€€€€€€€~|{zzywwyy{{yxwx|}~€~~~}}}}}||}|{z{~|z{yyz|}}||}}}}}}}}~~~~~~~€}}{{|{|€‚‚~zyyyxxwusqrtrqqsrrssvz||zxtrpnnnommkikov}€ƒƒ‚€€yrmklmmmkkkjjkou{}xuttttt~~~~~~~~~~~~}}}}~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~~~~~„†„ƒ~||}~€ƒ„ƒ…ˆˆ„~}€}ywx|}|zwsqrtwxx{}ƒ…„€€€€€€€€€€~|{zzyyyyy{{zyxxx{}~~~~~~~~~}}}||}}{yxyyzz|}}||}}}}}}}}~~~~€‚‚‚€~~}}{{|}}€}{zyyyxxwusqrtrqqsrrstw{||zwsqpnnnommkkmsz€‚ƒ‚€}vqlklmmmkkkjjlpvz|vtsssss~~~~~~~~}}~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||}}}}~~~~~~~~~~~~~~€ƒ……‚€~|}}~€ƒ†‡ˆ„‚|{~€{vsty€~{yvsrquyz}€„„€}}{{{{zzxxxz{{xwwy}~~~~}}}}~{xxz{}~|~}|||}}}}}~}}~~~~‚ƒ„ƒ‚~}}{|{{}~€}|yzzxxywusqstrqqsrssu{{||zvroooooomlkjnxƒ‚ƒ‚zsnljkllljjjiimrv|€‚{vsrrrrr~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||}}}}~~~~~~~~~~~~~~€ƒ……‚~||~}~€ƒ…†‡†‚~~}~€|wuvy~€€}{xurruz{}€~}€„€~~}}||{{zzzzz{{{xwvw{~~~€€~}}}}}||yxy{}€€~}|||}}}}}}}}~~~~€‚ƒ„„ƒ€}}|{|€€~|yzzxxywurpqrrqqsrstx{{{{xuqoooooomlknt{ƒƒƒ„~xpmkjkllljjjjjmry~€‚~yurrrrrr~~~~~~~~~~~~~~~~~~~~}}}{~~~~}}}}}}}~~~}}}}}}}{||||||||||}}~~~~€€~~~~~~€~|~€„„ƒ‚{||~~~ƒ†ˆ‡ƒ~~~|~€}xvwy}€~}{wuvy{y{}{xz‚‚€~~~~~~~~~~~~~~~~|{{z{zx{|{zyxxwy|}}~~}}~{zz|€‚ƒ‚€~}}|~}}}}}}}~~~~~~€„„„„ƒ}€€~€|{zzyxxwuqooprrrrrsw{||zyvspooooonlkknw~ƒ„„„|upllkkllmkkkijptz~€€€{xusrqqqs~~~~~~~~~~~~~~~~~~~~}}}{}}}}}}}}}}}~~~}}}}}}}{||||||||||}}~~~~~~~~~~~~€~~~€„…„ƒ‚||~}~~ƒ†‰‰…ƒ‚€€~|~€}usu{|~€|yyz|{xyyywx~€~~~~~~~~~~~~~~~~~}}}}|{y|}}|zyxwxz|~~}||}}~|{zz~‚„„ƒ€€~}}}}}}}}~~~~~~~~€„„„„ƒ‚€}|~~€|{zzyxxwuqooprrrrsuy{|{ywtrpooooonlknr{„ƒƒƒ~yspllllmmmkkkjkpv}€zvsrqqqsu~~~~~~~~~~~~~~~~~~~~~~~~~~||~~~~~~~~~~~~~~}}}}}}||||||||||||}}}}~~~~~~~~€~~…‡………|||~}ƒ…ˆ‰…„……ƒ~z}€|vsx{}}ƒƒ~}}}}zxxxxy}~~~~~~~~~~~~~~~~€~~€~~}{|}}||zzzzyy{}~€€€}}~~|zzƒ…„‚€€‚„ƒ€~~}~~~~~~~~~}~~|~€‚ƒƒƒƒƒƒƒƒƒ‚€€€|zyzzyxvtonoprrrstw|||zvvsqpooooonmlnvƒƒƒƒ‚€|uqomllmmmmkjjjnqw|€ytsrrpqvy€€~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~}}}}}}||||||||||||}}}}~~~~~~~~~~€ƒƒ~„††…†ƒ{{~†‰ˆ†…‚‚††…‚y|~zvx|~|ƒƒ€}zyxyz{|~~~~~~~~~~~~~~~~€€€}}|}~~}~}|{z{{z{{}~}}}}}~~}{{€ƒ…ƒ€€‚‚€€~~}~~~~~~~~~}}}~€‚ƒƒƒƒ…‡„„‚ƒ………„ƒ‚€}|zzzyxvtonoprrrsw{}}{yussqpooooomknrz€‚‚ƒ‚xsqomllmmmmkjjkosz~€|wsrrrrtx{~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}||{{||||||||}}}}~~~~~~~~~~~~}……††……‚}|„ˆ‰…‚€‚†‡†„xx€~yy||z|€‚ƒƒ€~~zwxyz{}}}}}}~~~~~~~~~~~~€€~||||~}}~~||||||z{||}|||{}~}}||}……ƒ‚‚‚€€~~~~}}~~~~}||‚‚‚„ƒƒƒƒ„„„„‡ˆŒ‹‰„||zyxwtpopqqrrvy|~|{xurrqonnnmmklov~‚‚‚}urpnllmmmmmmiimqx}€{vsrrtvx|~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}||||||||||||}}}}}}~~~~~~~~~~~}€ƒ††ƒ‚„…†‚|…‡ƒ||„…†„uv}‚{{}}zy~€‚‚€~~|zxyz{}}~~||}}}}}}}}~~~~€€~}}}}~}}~~~~}}||{{{{{{{{z|||}||}„„‚}|}€€~~~}}~~~~}‚†…„ƒ€‚ƒƒƒƒ‚‚‚…ˆŠ‹ŒŠ‡††ƒ€|yywtpopqrrtx{}~{ywtrqppnnnmmklqz€‚‚‚~ztromllmmmmmmiimry~yutssvy}€€€€€~~~~~~~~~}}}}~~~~||}}}}}}}}||||||||||||}}}}}}~~}}~~~~~~~~~~€‚„…†€‚…†„~ƒ„€zz}~€€ƒ‡†~usy€€}{~|{z{‚€{zyxz{}}~~|z||||||}}~~~~~€€€~}}}~~~~~}}}~}|||||z|{{zz{{{{}~„„‚}|}€€€€€~~}~ƒƒ€~~„„„„ƒ‚‚„ˆ‰‹ŒŠ‡……ˆ‰…~{xwspooqqswy}}|ywusrpooooonmlms|‚‚‚€~xsqolllllmmmmkjns{€€}yvtrsw|€€€€~~~~~~~~~}}}}~~~~~~}}}}}}}}||||||||||||}}}}}}~~}}~~~~~~~~~~€‚„…„~„……ƒƒ‚}z{}}}††~usy~~{z{€€€~~|{{zz{}}}~~}||||||||~~}}~~~€€€~}}|}~~~~~~}~}}}|}}}}|{zzzz{{}~‚‚€~|}~‚‚ƒ‚€€€€€€€€€€€}||ƒƒ„„„‚‚„‡ˆŠŒ‹‰…‡‡‡‡‰ˆ…ƒ|wtqpqrsuz|~}{xvsrpqonnmmmklov~‚€~xqomlllllmmmmjjou|€€}wttsv{~€‚~}~~}}~~~~~~~~~~}}}}}}||||||||||}}}}~}}~~~~~~}}}}}}~~~€‚„„„€€„†…ƒƒƒ„{{{|~‚‡†}svy‚€€~|z}~€€~}}~}}{z{{|}€ƒ‚|zz||}}~~~~~~~~}~~~~~~~~}}}}}~~}|||~~|{{yzz{{|~~~~ƒ„„ƒƒ‚€€€€€€€€€€}}}€……„‚‚‚„………ƒ‚‚‡‰ˆ‡ˆ‹Š‡€ysrrsqty}~}{zvsrqqppnnmkkjkqz€€€€€}wqonlmmmmlllkkkou~€{trtux|~}~~}}}}~~~~~~~~~~}}}}}}||||||||||}}}}~~~~~~~~~}}}}}}~~~€‚„„„€€ƒ…†………†ƒ}z{|ƒˆ†tqu|€€~|z{}~~|{z|}~|{{|}ƒ‡ˆ‚{zz||||}}~~~~~~}~~~~~~~~}~~~~~}||||}~~}|{{{{|~}}}€‚‚„„„ƒƒ‚~}€‚ƒ‚€‚‚‚€€}}~€‚ƒ„††„‚‚‚ƒ‚‚‚„„††ƒ‡ˆ‰ˆ‰ˆ‰‚yusqqv{~|ywsrpqqppnnmkkjmt|€€€}wponlmmmmlllkkkrx€zutvy{}€~€€€€~~~~~}}}}~~~~~~~||||||||||||||||||}~~€~~~~~}}}}}|}}}}~‚…„‚€€„††……ˆƒ~z{}„‡…xxy~‚€€}{}}}{zxxx|~}}}~ƒ†‰‹ˆ{zzz{{{{}~~~~~~~~~~~~~~~}}~~~|~}~~€€€~|||}}|~~€ƒƒ‚ƒ„}{zƒ†‡†††‡„€€~|}~€€‚††„‚‚ƒƒƒ…†…„ƒ……‡ŠŠ‡xtrrz}€{yvqppqqpponnllknw€€€~woommmmmmmmljimsz€€~ytuxz~‚~{€€€€~~~~~}}}}~~~~~~~||||||||||||||||||}~~€~~}}}}}|}}}}~€ƒ„‚€€„„†††}xy}†ˆ‡ƒ~~~€€€€~}}}{zxvtuy|}‚…ˆ‰ŠŒ‹…|{zz{{{{{}~~~~}}}}~~~~~~}}||}~~~}~}}|}}€€€~||||}~‚ƒ‚‚‚‚„|z|~€‚ƒƒ„†‚€|{}~€„……‚€€‚‚ƒƒ„…„ƒ€€||Œ—Œˆ{z{€~zwtpppqqpponnljinw€‚‚€€{tonlmmmmmmmljinuz€~}xux|€€{x~~~~~~~~}}}}~~}}}|||||||||||||{{|||~~~‚‚ƒ~}}}}}||}}~~}‚ƒƒ€€€‚ƒ………„€~|}…†‡…~~}~~}|}|{ywtttvx~‚„†ˆ‰‰ŠŒŒ‹‚{zzzz{||}~~~~~~~~~~~~~~}{{y||}~~~~~}}~€€~|{{{|~‚ƒ‚€€‚ƒƒ~}}|~ƒ~~{|~~€‚…ƒ€€‚‚‚ƒƒƒƒƒƒ‚‚€}€‹ŽŒ‘Ž…}{|zyvrooqqqooonmkhjpzƒ€|unnlmmmnnnmkikow}€€{vvy}€€~wv€€~~~~~~}}}}}}}}||||||||||||||}}|||~~~€ƒ„ƒ‚~}||}}}}}}~~€‚ƒƒ€€€ƒ„„„…††‡‡ˆˆ‡…ƒ}}}~~€~|{{{yvursrtw|€‚„‡‰‰‰‰‹ŒŽ‰{{{||||}~~~~~~~~~~~~~}|{|}~~€€~~~~€€~|{zz|~ƒƒ€~~€‚ƒƒ}|||}}}‚‚€~}~{|~‚…ƒ€‚ƒƒƒƒƒƒƒƒƒ„„ƒ‚„Š‘‹†ƒ~zyyyxyxwsqoqqqooonmkklt}€‚€{rmmlmmmnnnmjhkrx}€€{xy}~€€€xtu~~~~~~}}}}}}|}||||||||||||||||||~~}}„………}|||}~€€ƒ‚ƒ‚€€€€€‚„„……‡ˆˆˆˆ‡†„~|~~~~}{zzzwvtsqqqtx|‚‚…‰‹‹ˆ}{|}|}}~~~~~~~~~~~~~}}}~€~~~~~€~}}{xz{€‚|}~€„„~|{|}{{|€€}||~‚…†ƒ€ƒ…………ƒ‚ƒ……………„†ŠŽ‰‚}{vs|€€|tv}{tqpqpomnmmkjms€€zqmmlmmmnnmliiltz}€~zy{~€}tsu~~~~~~}}}}}}|}||||||||||||||||||~~}}ƒ„……ƒ||}€‚‚ƒƒ‚‚ƒ‚ƒ‚€€€~~€ƒ„ƒ€„‡ˆ‡…„~|}~}~}|zyxxuutrsrqsv{‚‚†ˆ‰‹Œ„|{||}}~~~~~~~~~~~~~~€€~~~}}}}||}~€~}}{yy}€~}{|}€ƒ‚€~}~}}~€€€~~}||~‚…†ƒ€€‚†‡‡……ƒƒ…„„„„…†ˆ‹Œ†~vqq|„ƒ}wy|wsqqpomnmmkinw‚‚€€xpmmlmmmnnmliinu{~€~||~€€€€~xssw~~}}~~}}}{||||||||||||||||}}}}~~~~~€‚…†…}|~€ƒ„„„††………ƒƒ‚„ƒ}}‡ˆ…„„~|||}}|zwvxvtuusssrsvx|€‚ƒ„†‡‰‹ŒŽ‹|{|}}}~~~~~~~~}}~}}}}|}|{zxz{|}{zz|€€~{||{|~ƒ†ƒ€€€~€€~}{}‚ƒ…„‚„ˆ‰ˆ‡…„†††……„„…‡ˆŠ‡}uos}ƒ€~~vv|zsrpppnmkkjpy€€‚€€~wpmmllkmmmkjhinv|~}€€€€€€zqru|~~}}~~}}}{||||||||||||||||}}}}~~~~~€‚…†‡ƒ~‚„…„„……„„„‚‚€€€€€‚ƒ~z}‡ˆ…„„}{}}}}|zzyvtsuvutttuvxz|€‚ƒˆ‰‹‹ŽŽ‰~}}~}~~~~~~~}}}€€~}||{|{{{{|{yzz}~}{zz|€€~{|||~„†„€€~~€€~~}~€‚ƒ…„‚„ˆ‰ˆ‡…„†††…………††ˆŠ‡€vqqy}|z~|||~|yspppnmkkkqz€‚€{uommllkmmmmlgipx}~€€~~€€€}vqty}~~~~~~~~}}||||||||||||}}}}}}}}}}~~~~~~„…†„„„†…ƒ……„„ƒ‚~€€€€€ƒƒ€|}‡Šˆ‡†‚~~~}{{zzxuvvwwvxuvyy{{}€…‰Š‹Žˆ~}~~~~~~~~}~€€|{zz{z{{}~~~}|{|}€~{xz}€|z{|}~€„†ƒ~~€‚‚‚€~}|~€‚„…„‚„‡ˆˆ‡†…†‡‡‡††………†‰†„}tuy|€€‚ƒ€~vpppnmljkr}€€|rlmmllllmmljhkty~€}vrtx|}~~~~~~}}||||||||||||}}}}}}}}}}~~~~~~~‚„……„‚„…„‚‚‚€~~€€€€€€€‚ƒ‚~|€‡Šˆ‡‡…€}|{{|||yxvuuvxz{{{||}„‡‰ŠŒŽ‡€}~~~~~€}{zyyzz{|~€€€~}~€€€~{xz}€|yy{|~€…†‚~~~€€~~}|}‚„…„ƒ†‡ˆ‡ˆ†„†‡††††††‡‡‡…‚||~‚€€‚„ƒƒ‚€~uppnmljkr}€€|tnmmllllmmlihltz~€zutvz{}€€~~~~~~~~~~~~}}}}|}{{||||||||}}}}}}}}~~~~~~~~}€‚†……†ƒ„ƒ‚€€€€€~~~€€€€€€€€‚ƒ~}€‡Š‰ˆ†„ƒ€|{zz|}}zxuttuyz||~}~€€€€‚…‡ˆŠŠŒŒŒ‡~~~€~~~€~|{yyxy{}}€€ƒ‚‚~€€€€zw{}~zwyy{‚„„~}}~€~||}~‚„ƒ‚€„†‡ˆˆ†ƒƒ††††…‡‡‡ˆˆ…‚€}|}‚€€‚…†„€zroonkkms~€‚€€}uollllkmmmmjjmw}€~}~€~wtuxy{~~~~~~~~~~~~~~~~}}|}}||||||||}}}}}}}}}}~~~~~~}€‚„…†„†ƒ‚€€€€€~€€€€€€€€ƒ€‡Š‰ˆ……„ƒ€}zxxxz||{xusrsvz||~€€€€‚†ŠŠ‰‹ŒŒŒŒ†€~~~€€~~}|{zzyz|}}~~‚…‡†…ƒ€€~~|{}~€~zwyy{‚„„~}}}}~~}||}ƒ…„‚€ƒ„‡ˆ‡††…††††…‡ˆˆ††…‚€ƒƒ‚…………ƒ‚}uponkkmv~€‚€€}tnllllkmmmmjiow}€~}~€ysuvxy{~~~~~~~~~}~~~~~~~~~~}|||||||||||}}~~~~}}}}~~~~~~~~€„†††„ƒ€~~~€€~~ƒƒ€†Š‰‡†…„{wvuuvy{{{wusssuy{}~€€~|€…ŠŒ‹‹ŒŒŒ‰„~~}}~}||{{{|}~~~‚‡‰Š‰‡…ƒ€~~||}€}xvwy|‚ƒ„~}|||~~}}{{}ƒ……„‚‚‚ƒ„†‡††„……………†ˆˆ†…„‚‚‚ƒƒ„„‚ƒ††……„‚€zspnkjnx€‚€€|umllkkmmnljijqy|~}}~€‚‚}xuxyyy{~~~~~~~~~}~~~~~~~~~~}|||||||||||}}~~~~}}}}~~~~~~~~€ƒ…††…„‚~~~€€~}~€‚‚‚ƒ‰Š†„€{xrooruy{{{yvsssuy{}}}~€€|{|†Š‹‹‹‹‹‹Š…€€€‚~~~}}|||{{||}}ƒ†ˆˆŠŠˆ†„‚€~~|}€€~yxyy|~€‚„„~~}}~~}}||}‚ƒƒ„ƒ‚‚‚‚‚ƒƒ„†‡‡…ƒƒ………†‡‡…„„ƒƒƒ„„……†‡ˆˆ‡…ƒƒƒ‚{wtqpsz€‚€€|slllkkmmlkjhks{}~}}~€‚zwvyzyy{~~~~~~~~~€€~~~~~~~~~~~~}}}|||||||||||}}}}}}~~~~~~}}~~~~ƒ„……‚€~~~~~€€}|…†‚|}|xqnoty|}}zzvwvwyz||}€|{|}„ˆ‰Š‰‰ˆ…€€‚€~}}}}||{{{{}ƒ…‰‰ŠŠŠ‰Š…„~}}}|}|zwxx{~‚ƒ‚‚~~~~€€~|}~€‚ƒ‚‚‚ƒƒ‚‚‚„‡…ƒ„„„……††……„ƒ„„……††††††††„„ƒƒƒ‚€}{yy~‚€~{rnkkklmmlkjimv|~~~‚‚{wuxzzxxz~~~~~~~~~~~~~~~~~~~~~}}}|||||||||||}}}}}}~~~~~~}}~~~~‚ƒƒ€~~~~~~~€‚‚|€‚€ƒƒ~xrorvz~~~~|||||}~~€}|{z|}~€„ˆ‹ŒŒˆ…€~~}}}||}}}~€‚„…ˆŠŠ‰‹‹‰ˆƒ‚€~||}}€€|zwxz}‚‚ƒƒƒƒ~~€€~}‚„‚ƒ………„„„‚‚‚~‚„…ƒ„…„……†††…„„………………††††††……„„„ƒ„„‚€‚‚‚‚‚€~{rnkkklmmlkihmw}~|}‚‚{wwyyywwz~}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}||||~~~~~~~~~~~~}}~~~~~~~~€€€€~~~~~~€€~}}}}}~~|}€ƒ‚„„„ƒ}vpqvy|}~~~}~€‚‚‚~}|{{||{|€‚…ŠŠ‡„€‚€~}||{{{||}€„„…†ˆ‰ŠŠŠŠ‰†ƒ€€~||}€€}|zxy{~€ƒƒƒ„„ƒ€~~~|}€ƒƒƒƒ„„ƒƒƒ…„ƒ†ƒƒ€~ƒ…ƒ„„……†…………ƒ‚ƒƒ……„……†‡‡‡‡‡‡††††„„„ƒƒƒ„„„„‚€€yrnkkklnnlkgipx~}}€}zwyyyxvvz~}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}~~}}~~~~~~~~~~~~}}}}~~~~~~~~~~~~~~~~~~~~~}}}}}~~ƒ‚~{~€ƒ„„ƒƒ‚}usux{|~~~~€€€€€~}|z{{||{|}€€‚‚€€€~}|{|{{{{{„†‡‡‰‹ŠŠŠŠ‡„€~}zz|}€€~||zxyz}}}€‚‚‚ƒƒƒ€~~~~€ƒƒƒƒ„„„„ƒ……††††……‚‚ƒƒ„„……†ˆˆ†~€€€ƒ†………………††††„„„ƒƒƒ„„„„„‚€€yqlkkkllllkikqy}~}}€€|yxyyxwvvz~€€}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}~~}}~~~~}~~~~~}}}}}}~~~~}}~~~~~~}~~~~~~~~~~}}}}}}}}}}~‚€}z}„„ƒƒƒƒzsrvyz}~~~}|{{{{{zzz|~}~‚‚€€}|z{{{z{{|€‚†ˆˆ‰‰‹ŠŠ‹Šˆ…~|zyz|~€€~|z{yzzz{}€ƒƒƒ‚€€€€~~|}€‚‚ƒƒ„„ƒƒ………………„„„„„„ƒƒ„††‡†…€yux|}~€ƒ„…‡„………„……„…„„„‚„„……„„ƒƒyqmkkllllkjilt{}}}€€‚|yyzzxvvvy}€€€~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}~~}}~~~~}}~~~~}}}}}}~~~~}}~~~~~~}~~~~~~~~~~}}}}}}}}}||~‚‚{z}‚„ƒ‚„„xtuxy{}{|}}~}||{{{zzz|~}~€‚‚‚€~}|{zzz{|}~„‡ˆˆˆˆ‰ŠŠŠ‰‰‡ƒ}zyz|~€€}}{|{zyy}€‚‚‚€€‚‚€~€€‚‚ƒƒ„„ƒƒ„„……„„„„„„ƒƒ„„†………‚}tnpx}~~}~~€ˆ‹Š†…†„„‚‚ƒ„„……„ƒƒƒƒ„„„„ƒ}tnllllllkjimu|}€€€|yy|€xtty}€~~~~~}}}~~~~~~~~~~|}}}}}}~~~~||}|}}}}}}}}~~}}}}}}|}~~}}}}}}}}}}~~~~}}~~~~~~~~~~~~~~||}}||||}}}~€‚~xy~ƒ„„„„ƒ~ww{yy}€zy||}~€~}}}||{{zyz||€~}|{zz{z~€‚‚ƒ„††††ˆ‰Š‰‰‰ˆ†ƒ|zy}~~~~~}|zy{~€€‚‚‚‚ƒƒ‚‚‚‚‚ƒ„ƒƒƒƒ„„ƒƒƒƒ„„„„„„„†„ƒ€~zrnntz~~}}~~‡ˆ„„„„„ƒ€ƒ„†ˆ†ƒ€ƒ„„……ƒ€ysonmmmmkihnw~~}€€€{yy}„ƒytty~€~~~~~}}}~~~~~~~~~~~~|}}}}}}~~~~~~}|}}}}}}}}~~}}}}}}{}}}}}}}}}}}}}~~~~}}~~~~~~}}~~~~~~~~~~}}||||}}}~~‚€|xz~‚ƒ„„ƒ‚~~{z|~|z|}~~€~}}}||{{zyz|}€~}|||{~€‚‚‚‚„………†‡‰‰ŠŠ‰‰†‚}zy|}||~~~~~€€‚ƒƒ‚‚ƒƒ‚‚ƒ„ƒƒ‚‚ƒ‚‚‚ƒƒ„„……„„ƒ„‡ˆ…~usu{{}}y{~~Œƒ~‚ƒƒ„‚€ƒ„†‡ˆ‡‚€ƒƒƒƒ„ƒzsponmmkiipx~~}€‚€~zzz~„ytty~‚€€~}}}}~~~~~~~~~~~~~~|}}}}}}~~~~~~~~~~~~}}}}~~~~}}~~}}}}}}}}}}}}|||~~~~~~~}}~~~~}}}}}}}}~~}}}}||}}~‚‚€{xz~„………ƒ~{}}}~~}}|||||{zy{~€€€~~~}}}|~‚ƒ„…ƒ‚‚€€‚„††ˆ‰‰‰‰Š‰ˆ„}{||}}~~€~~~€€€€€‚ƒƒ‚‚‚‚ƒƒƒƒ‚€€‚‚‚‚„„…………„…ˆˆ†}sw}~~||~ˆŠ€zzz}€‚‚~~ƒ†‡‡ˆˆ}~‚‚ƒ„„ƒ‚|vronmjhhqz~}€zwxzƒƒ{wttx|‚€€~}}}}~~~~~~~~~~~~~~|}}}}}}~~~~~~~~~~~~}}}}~~~~}}~~}}}}}}}}}}}}|||~~~~~~~}}}}}}~~~~}}||}}~~}}}}}}}}}}‚|xx}‚„„„ƒ|~‚‚}~~}||||{{{|€€~€~}~~}}}|~~„†‡…„‚€€‚……‡ˆˆˆˆ‰‰ˆ…|||}}~~€€€}}~€€€€€€‚‚‚‚‚ƒƒƒƒ‚€€‚‚ƒƒ„„……†††‡ˆˆ…€uw|~~yy~ƒ‹‡zyyyyz{~‚‚€‚‚‚„ˆ„€}}€‚‚ƒ„„ƒ‚~xtqnkglv{€}{x{‚}wustx|‚‚€€~~~~~~~~}}~~~~~~}}}}}}}}~~~~~~~~~~~~}}}}}}}}~~}}~~}}}}}}~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}~}‚€|xz}~„…„‚€€~€‚€~€€~~}}{zyz~€€~~~~}|}}}}}€‚…‡…„€€€„…††ˆˆˆ‡‡‡…„‚}|}}~~}€€€€€‚‚‚~||€€€€€€€‚‚‚‚ƒ€€€€€‚‚‚„„„„††‡ˆ‡‡†‚yw{{{xz…„zwvwyyyyy|~}{}~€ƒ…‡‡}|‚‚„„„„‚~ztolkmu}€€€€}zx|‚xrqssw|‚‚€€~~~~~~~~}}~~~~~~}}}}}}}}~~~~~~~~~~~~}}}}}}}}~~}}~~}}}}}}}}}}~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}‚}zy{}€‚„„€|{}~~€€~~}}{z|€€€}}|{|||{}}}||€‚ƒ‚€€€ƒ„……†‡‡†„……‡†ƒ€~~}}~€€€‚‚‚~|}€€€‚€€€‚‚€€€€€€‚‚‚‚‚ƒƒ„…„„‚}wzzwu{‚‚|vuvwvvvvuqqszƒ„†††‡†ƒ~{~€€ƒƒƒ‚ƒƒƒ€{umjpx~€€€€|y{}vttspty‚‚}}~~~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}~~}}}}}}}}~~~~~~~~~~~~~~}}}}}}}|}}}}}}}}}}}}}}}}}}~zxy}€€ƒƒ€€}{|}~~~~~~}|{{€€~~~~{zy{||~~~~~~€€€€„„„„…„„„…‡ˆˆ†„ƒ~~~€€€ƒ‚€|}€‚„ƒ~~€€‚€€€€€€€‚ƒƒƒƒ‚€€‚~ywtrrz€}vtttutttuokrz„†………‡ˆ‡†‚~~~ƒ„‚‚ƒ‚‚€zqnty}€€€€€||y}‚ysrrqoty‚‚~~~~~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~}}}}}~~}}}}}}}}~~~~~~~~~~~~~~}}}}}}}|}}}}}}}}}}}}}}}}|||{vx|~‚‚€}|}~~~~}}~~~||}€}~~~~}{||{|{}~~~€€~~~€€‚‚ƒ‚‚ƒƒƒ„…†‡‡…„ƒ€~€€ƒ‚€~€ƒ††…‚€~€€‚€€€€€€€ƒƒ„„„ƒy{|~~{uqqryzutttuuvtnlt|~~€…†……‡ˆˆ‡…€€~€‚ƒƒ‚|vsuz}€€€€~||€{vsssnnuz‚€€~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~}}||}}}}}}~~~~~~~~~~~~~~}}~~}}}}}}}}}}}}||||||€€€€€}yxz|‚‚}||~}}~~~|}~}}~~}}|z{zz{||~~~~~}~~€‚‚‚€‚ƒƒ„†‡††„„ƒ€€‚€‚‚„†‰ˆ…‚€€‚‚ƒ„€€‚ƒ{wy|}~€€‚€zposz{xuttuuttojq{~~~~€††††‡‡…ƒ‚€€€|y~€€‚‚zuw{~€€~}~„€{vssurlksx‚€€~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~}}}}}}}}~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}~~||||||}}|~~{zz{~€‚}{{|~~~~~}~~}}~}}|{{{zz|~~‚‚~}~~€‚‚‚€€€€ƒ„†‡……„„ƒ„„„‚€€‚~‚„…‡‡†‚€€‚ƒ„ƒ€€€€‚„ƒ€~€€ƒ‚„„sprz{wtttuuuqkr{}~~~~}€†††‡‡…„„€yty}€‚‚€|xy|~€~‚‚|vssssqkmv{€€€€~~~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~}zzzxyz|~~€zvy}‚‚€{z{|}~}~~€|}}~{z{~~|||}~|}~€~~~~€€€€ƒ„„„‚ƒƒƒ„††„‚‚€€‚€~€‚ƒ††…ƒ€€€€„…„‚‚ƒ‚ƒ…„„ƒƒƒƒ‚ƒ††xssz{wuutturlly~~~~~~~‚‡……††„€€€zpsx}€‚}zz|€€€€~€~|xusqrrmjmw}€€€€~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~}zyywwxy|~€}zz|€€{z|}}}||~~~~{{{zz{|||||~~}}|}~~€~~~~€€€€€€€€€€‚‚‚€ƒ…………ƒƒ‚‚€€€‚€~€‚ƒ††…ƒ€€€€€ƒ„ƒ‚ƒƒƒ‚ƒ…„„„„ƒƒ‚ƒ……‚}wuyyvtttttojr}~~~~~~~‚…„‚„„‚€€€{qpt{|{{}€€€€~€|xtqqqqrqsx}€€€€€€€~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~€€€€~~~~~~~~}}}}~~~~}}~~~~~~}~~}yxxvwwxy|€€€zx|€|y{{zzy}~}}zzxyzz{{||}}€}|}€€~}|||}€€€€€~~~~~‚ƒ„„ƒƒƒƒ~€ƒ‚€€‚€‚†……ƒ€~€€€‚„ƒƒ‚‚‚‚„…„„……„ƒƒƒ‚‚ƒ€zxyyustutrjlx}~~~~~~~„€€€€€‚€€|sqrx}}|}€€€€~€|xxxwustyxz‚‚€€€€~~~~~~~~}}~}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~€€€€~~~~~~~~}}}}~~~~}}~~~~~~}~~}yxxusstvx{|~€€€}||}}zxxz|~~~|||{zyxxz{||}~~}}}~€€~}||||}~~~~~}~~~~€€€‚‚‚€€€‚ƒ‚€€‚ƒ†…ƒ€~„‚‚„ƒƒ‚‚‚‚‚„„„……„ƒƒƒywyxtsssrnirz}~~~~~~~€€„„‚€€{qpquz~~}|}€€€€~|xwy}€~~€‚„„……~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~€€~~~~~~~~~~~~~~~~~~~~~~~~~~~~|zzvtqrrvwy}~€€~||{zz}€~~~||{xxxy{{{{|}}~}}~~~€~}|||}|||}~~||}}}}}}~~~~~~€€€€€€€€ƒ„„ƒ‚€‚‚€ƒ„ƒ~~}~~}‚ƒ‚ƒƒ‚‚„„‚‚ƒƒ„„„„„„‚€€~xvxwusssqkku}~~~~~~~~}~~€„†ˆ„ƒƒ€€uoquw|}}}€€€€€{xuw{~‚„ƒ‚‚„„……†~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~€€€€~~~~~~~~~~~~~~~~~~~~~~~~~~~~{zzwtqqqrvy||€€~|{z|~€€}}}}{yxwwx{{{{||~~}}}~~}~}||{|{{{|||||||}}||}~~~~~~~~~~€‚……„„„ƒ‚‚‚~~„ƒ€~}~~|}ƒƒ„„„„„„„ƒƒ„„ƒƒ„„„„„„‚€€ytrxwvtssqjnz~~~~~~~~~~…‹ˆ‡…ƒƒ‚~vrswy|}}}€€€€€~zxyz€‚‚ƒƒ……‡‰Š‹~~}}~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}~~~~~~~~~~~~~~~~~~}}}}}~~~~~~~~~~~~~~|xvusrqqsv{}~~|~~€€€{{|~€€}zyvwyyxxyz{{{||}~~~~|}}~~~~}{{{{{{zz{{{{||||{|}}}}~~||}}~~~€‚„…„…ƒ‚‚ƒ„„‚€~ƒ‚€€€}}€ƒƒ‚‚ƒƒƒƒ‚„„ƒ„„„„„„ƒƒ‚€{xuuuwwuusojoz~~~~~~~~~…‹……††ƒ‚€~yy{|€€€€~{y|~€‚ƒ„„‡ˆ‰Š‹~~}}~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}~~~~~~~~~~~~~~|xvttsrrtx{|{{z{{|~~€}|yxwwwxzyyyz{{{||}~~|||~~~~}{{{{{{zz{{{{{{{{{{||}}}}}}}}}}}„††…ƒ€€€‚ƒ„‚ƒ†…ƒƒ€€‚€€€€‚ƒ„„„„„ƒƒƒƒ‚~{vvuutvvvutpkqz}~~~~~~~~‡‰ƒ€‚…†ƒ‚€€€€€€€€€€€€~{{~€‚ƒƒ…„†††‡Š‹~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~~~€€€€~~}}~~~~~~~~}~~~~~~~~~~~~~~~~|zwwwtsstz|||z{{||}|{}€~zxvwxyzyzzyz{}}||}}~€~}}}}}||zzzzzzzzzz{{{{{{{{{{{{{{{|||~€‚……„~~~~ƒ„‡‡†…ƒ‚‚€€€‚‚€}{|}~~‚„„ƒƒƒƒ‚‚€}{xuutuvwwvsqnqz}~}}ƒ€‚…ƒ€„„ƒƒƒƒ‚‚ƒƒ„„ƒƒƒƒ‚€}|}€„…‡‡„„„„†ˆŠ~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~~~~€€€€~~~~~~~~~~~~}~~~~~~~~~~~~~~~~|zwxwuttvy|||z{{|||~~€€}{yvwxyzzyxyz{{|}}}}~€€}}|{{{{zzzzzzzzzz{{{{{{{{{{{{{{{|||~€„…ƒ~~~~~~~€€€ƒ„„„ƒ‚ƒ‚ƒƒ„„„„ƒ‚}~}}}‚„„„„ƒƒƒƒƒ€}{yutttuvvsolnx‚„…‡‡†…ƒ…†…‚‚‚ƒ„„„„ƒƒ‚‚ƒƒ„„ƒƒƒƒ‚€‚„†‡ˆˆ‡…ƒƒ†ˆŠ~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~~~~~~€€€€~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~|{xyyxvwy|||||||||{|~€}||zwyzzyyxyyz{|~}}}}~}~€€}}{{{{{||zzzzzzzz{{{{{{||||||||||||}~€‚€~}}}~~~~~~€€€€€€€„†‡‡†‡‡„‚€€~~‚ƒƒƒƒƒƒƒƒƒƒ€~zwtsttopoov„…††††††…„…………„„„„……††„ƒ‚‚‚‚‚‚‚‚ƒ„ƒƒ‚‚…„„‡ˆˆ‰†„‚ƒƒƒ„ƒ†‡ˆŠŠ~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}~~~~~€€€€~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~|{xxxxxy{{||||||}|~€€zxxyyzzz{{{|zzz{}}{{{{{{|}}}{{{||||{{zzzzzz{{{{{{||||||||||||}}€~}|}}~~~~~~€€€€€€‚ƒ†‡‡‡†…„„‚‚‚€€‚‚‚ƒƒƒƒƒ‚‚€|zwusvwz~‚„…†…………„ƒƒ…………„„„„„„„„†„„„‚‚ƒƒ‚…†‰ˆˆˆ†„ƒ‚€€ƒƒ„„…†‡‡‡~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~}}}}~~~~~~~~~~~}~~~~}}~~~~~~~~~~€€~~~~~~~~~}~~}}~~{zyxxwyz{{||{||||}€€}yttvw{{{|}~}|zzzz|{|zz{||}~}}{{z{{{||{{{{z{{{{{{{|}|||}}}~~}}}}}~~}}}}}}~~~~~~~~~~~~‚‚ƒƒƒƒƒƒƒƒ‚ƒ‚€€€‚ƒƒƒƒƒƒƒ„„‚‚€€}}~€€‚„ƒ……………„„ƒƒ„„‚€‚ƒƒ„††……‚€€ƒƒ…„„‡ˆˆ†„……ƒƒƒƒƒƒƒ„„…………~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}~~~~~~~~~~~~}~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~}}~~}|zyyyyy{{||{||{}‚‚€{vttw{||~~~}{zzzz{yzz{z||{}}}|{z{{{||{{{{z{{{||||}}|||}}}}}~~}}}~~}}}}}}}~~~~~~~~~~~~‚‚ƒƒƒƒƒƒ€€€~~€‚‚ƒƒƒƒƒ„„ƒƒ„‚‚ƒ‚‚ƒƒ„…‡ˆ‡‡‡‡„‚€}|z~~€‚‚„…††……ƒ‚€€€€‚ƒƒƒ…‡ŠŠ†……ƒƒƒ‚‚‚‚ƒƒ„„……~~~~}}}}}~~~~~}~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~}}}~}{yyzyyyzz|||{{|~‚‚}zwuuvz|}{}}|{xxxyyzxy{{||||||zzz{{{||{{{{{||}}}}}}}||}}}}~~~~~~~~~~}}}}}}}}~~~~~~~~~~~~€€ƒ‚ƒƒƒ€~~€‚ƒ„„„„„„„……ƒƒ„…„…††‡ˆ‡†…„€~{zzyy|€‚ƒ„„‡‡‡‡‡††„ƒ€‚‚‚ƒƒƒ„ˆŠ‰…„‚€€€€ƒƒ‚‚~~~~}}}}}~~~~~~~~~}~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~}}}~}{yyzyyzzz{{{{|~‚‚€~|zwuuux{{{{{{zxxxyyzz{yyzz{{{{zzz{{{||{{{{{||}}}}}}}|}}}}}~~~~~~~~~~~~}}}}}}||~~~~~~~~~~€€€€€€€€‚ƒƒ„„„„„„…………„……†‡‡ˆ‰†ƒ‚€}{|…†ˆˆ‰‰‡††††††…„‚‚‚‚ƒƒ‚ƒ†‡‰…€€€€€€~~~~}}~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|z{{zzzzz{{zz|‚‚€~{zz|zwuvvvy|||}|yyvvwxzz{zzzz{{{{{z{{zz||||{{{|}~}}~~}}}}}}~~~~~~~~€€€~}}}}~~~~}}}}}}~~~~~~€€€€€€€€€€€€€€€€‚‚ƒ……„„………†„„ƒƒ„„„„„……„„„„ƒƒƒƒ„…†‡‡†……‚ƒ…†……‡†„~‚‚ƒƒƒ„……‡†ƒ€€€€€€€~~~~}}~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~€|z{{|{zzz{||~‚ƒƒ||{{yxvqrv|}|||{yywwwwzz|{zzz{{{{{{zyzz{{||{{{|{|||~~~~}}}}}}~~~~~~~~€€€€~~~~~~~~~}}}}}}~~~~~~€€€€€€€€€€€€€€€€€‚‚ƒ…„……………††……„ƒ„„……††…………„„„„„„„…„„„ƒƒ€„†††‡ˆ‡…‚‚‚ƒƒƒƒ„…‡‡…ƒ€€}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~}}~€€}|{{~}|{z{{~‚ƒƒƒ|zyzzywvtqw|~||zxwywwuwxy{{zzy{{{{{{{zz{zz{{}}}~|{}}}}}}}}}}~~~~~~~~~~€€‚€~~~~}}}}}}}}}}~~~~~€€‚‚ƒ„……‡‡…………†‡…ƒ………†‡†‡‡‡‡††††„…„„ƒƒ„„‚€€‚„†‡ˆˆ†…ƒ€€‚‚ƒƒ‚‚ƒ„…†‡…ƒ€~~~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}~~}|{{{z||{|ƒ„„€|z{{zyyyxvqpv|}{yvvwyxxuuuxzzzzyzz{{{{{zz{{{{{{{|}|{||||||}}~~~~~~~~~~~~€€‚‚‚‚~~~~}}}}}}}}}}~~~~~~~~~~€€‚‚ƒƒƒƒ„…………†‡‡†……ƒ…†‡ˆˆˆˆˆ‰‰ˆ‡…ƒƒ‚‚ƒƒ€€€€ƒ„…‡‰‡…ƒ‚‚‚‚ƒƒƒƒ‚ƒ„…†……‚€}}}}}}~~~~~~~~~~~~~~~~€€€€~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|~~|||{{{{{z}ƒ…ƒ}|}|{zxwspotx{zyvvvwwxyyxwxxyzzy{{{{{{{||}}}|||}}}}}}||}}}}~~~~~~~~~~~~€ƒ‚‚€~~~|}}|}}}~~}}}}}}||~~~~~~}}€€€€€€‚‚‚‚ƒ……††‡‡‡†…„„„†ˆ‰‰‰‰‰‹Š†…ƒ‚€€€€€~~~‚…†ˆ‰‡…ƒ‚‚‚‚ƒƒ‚‚‚ƒƒ„„……„‚~~€€€€}}}}}}~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~€€€~|~}{{{{|}‚‚€~||~€|ywuqnnrvyyyvvvuwy{{{ywwyzzy{{{{{{{||}}}|||{{{{||}}~~}}~~~~~~~~~~~~€€‚‚€~~~}}|}}}||}}}}}}~~~~~~~~€€€€€€‚„……†‡‡‡†„„„…†‡‡‡ˆˆ‡…‚€}~~~„…††ˆ†„ƒƒ„„ƒƒ‚‚‚‚‚ƒƒ„………„‚€€€€}}}}}}~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~||}~~~~~~~~~~~~~~~~~~}}~~~€€€€~~~~~~€€}|{z{z}€}{||~‚‚€}yxwtrpquyyxwuuwy{}}||zyyyzxzzz||{|}}}}||||{{{{zz{{}}}}|~~~}}}}}}~~€‚€~~~}}}}}}}}|}}}}}}}~~~~~~~€€€€€€€‚ƒ…†…‡‡‡……„„„…‡†…„€~~~~~}}~~~~‚ƒ…†ˆ†…„„„„ƒ‚‚‚‚‚‚„„……‡…„‚€€€€€}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|}~~~~~~~~~~~~~~~~~~~~€€€€~~~~~~€€€€~|{z{~€€~}|{||~‚‚~zyxusppsuxxwwvwy{|}||z{yyywxyyzz{|}}}}{{{|||||{{{{{{{{{|||}}}}}}~~€‚‚€~~}}}}}}}}|}}}}}}}}}~~~~~€€€€€€€€€‚„……††……„„ƒ„††„‚€~}~~}}}}~~~~ƒ„…‡†…„„„„ƒ‚‚‚€ƒƒ„†ˆˆƒ€€€€}}}}}}~~~~~~~~~~~~~~~~~~~~~~~}~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~€€€€€€€€~~~~~~€€|{{~€‚ƒ‚|{{{|}€‚€~}ywwsooqsuwxvwxxyy||{||zyyxwwxyyyz{{}}{{z|{{{{zzzzzzyyzz{{{|||}}~~~~~~‚ƒ‚‚€~}}}}}}}}}}}}}}}||}}~~~~€€€€€€€€€€€€„„…‡†…‡††††………‚€}}~~}}|}}~€ƒ„……††„ƒƒƒƒ„‚‚€€‚„„†‰ˆ†ƒ€€}}}}}}~~~~~~~~~~~~~~~~~~~~~~~}~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~€€€€€€€€~~~~~~€€€€€|{|ƒ‚€€{{{|}~€‚ƒ‚€~zyxtpopoqsuvyzzyy{{{||zzzxwwxyyyz{{|||||{{{{{zzzyyyzzzz{{{|||}}}}~~~~~€‚‚ƒ‚€~}}}}}}}}}}}}}}~~~~~~~~~~€€€€€€€€€€~‚ƒ…„…††‡††………„ƒ}}}}|||}}~€ƒ…„†††…„„„€€‚„†‡Š‡„€~~~~~~~~~~~~~~~~~}~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~€€€€€€€€€€~~~~~~}}€€€€~|}ƒƒ€~|{||}|~€‚„ƒƒ~{xurqponoqsvwzyzyz}}}}{{zyyyyyzzyz||||||zz{zyyzyxxzzzzzz||||||||}}~~~~€‚‚€~}}}}}||||||}}}}}}~~~~€€€€~~~~ƒ„ƒ…†‡‡‡……………ƒ€~}{z{{|||~~~~‚‚„………………ƒ‚€~~‚€‚ƒƒ…ˆˆ…‚~~~~~~~~~~~~~~}~~}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~€€€€€€€€€€€€~~}}€‚‚‚€~}‚ƒ‚~|{{||}|~€‚‚‚€~{vtrononnprsvyz|zxyy{{{{zzzyyyyyyz||||||||{zyyyxyyyyzz||||||||||{|||}~~~€‚€€~}}}}||||||}}}}}}~~~~~~~~~~~~~~‚‚ƒ…‡‡‡……………„‚€~|{{{|||~~~~€‚‚ƒ………………ƒ‚€}|~€€‚ƒƒ…ˆˆ†ƒ‚‚klmnooonooortuvvwxzzzzwurpqqppppqqrssuxyzzyyyy||}}~€€€€€€€€€€}~~}}}}}}}}{{||||||||||||}}~~~€‚ƒ„„…………††……††„ƒ‚|sjd^[]_ada_][^cehhe``bglsz€„……„„{qf]YX]bjtz~{urppmifaadgknpquyz€€€€€€|wpmjjjjjjjkknqqtuuuuuvvuwvwyxupf[XTV[^choy€‚‚ƒ€€~~|xvtomjggcdfgghfhjjmprtttttsrrqpoqutuuz|zvplhbbdca\Z]dknttsrrsuwz~‚ƒ…‚mooppporssttuvwwxzzzzxvtrpqqppppqqrsuvvxzzzzzz{{||{}~~€€€€}~~}}}}}}}}}}||}|~}||||||}}~~~‚‚ƒ„„…………††‡‡††„ƒ}wpgc_^^`bb]ZYX[_dghfc`acfmu~‚……„„‚{si_\Z[bhpw}}{wtrpmnifcbbejmppqsv{€€€|zupkihhhhhhiklnortuuuuuvvuwvwyxupg]VUW\^cgpw€€€€~~{wtoljihgeegiiijjkjmprttttsrppqpoqsrpsw{||ytqmhdccb`\Z]dlsuusrrsuwz~„†ˆ…moqqssrstutuvwxxy{{{ywusqpppppqqsssstuuwyyy{}}||||}}}~}~~~~||~~~~}}||}}~~~}|}}~~~~~~‚ƒƒ„„…………‡‡†††„ƒ‚}vmg`]^`cda^[XVY]fmojea``emt{‚ƒ………ƒ€vmfcaadhot{yusqnmnjiihedehpy†„zwz~€€}{wrnjhhhjjiiijlnosvwvvvvwwwwwxxyupg]WUVZ^dhpw}€€€€€€€€|ztrojghhgghhjjjkklnnpstsuuropppoprrqrtxz||wsqkgecca_[]aelsutsstuvxy}„ˆ‰‡nonprrqrtuvwxyzz{|{{yvvsqppppprrssuuuwwxyyy{{{||||}}}~}~~~~~~~~~~}}~~~}|}}~~~~€‚ƒ„„……††‡‡†††„ƒ‚wnc^__dddb`YTT[epuvneaabfkqwƒ………„{smgbbeilostpnmllllmmkigglwƒ…‚ynqy€‚€zvqkihjiikkjjjkmoqrtvwwwwwwwwwxyzxsj`XTW[`cgnu|€€~|yurnlihhhhffgkmmmnonllpsvtssroooomoqportxz{yvrlhca`adaacdhnstrqqtuuwz†‰‰ˆnnmnpqstuuuvwx{||{ywvurqqqppqrrttvxxxxxxzz{{y{}}}}}~~~~~~~~}}~~~~€€€€~~~~~€€€€‚‚ƒƒ„„‡‡‡‡……………‚~yohfbeffc^\WUWamv|{rkca`chnu{„„…„‚}vrjeefikopoomllkknonnjijov}‚xqqw~|wrnkhhiikklkiiknoqruvxxxxxxxvxxyyyukbZWVZ`chms|~€~|xsnjihgheefgijnoooonljkpsssuuroomnppommptxxzxspmida_aebaejnsuurpqtuwwy}†ŠŠŠnnnopqtuuvwxz{}~}}{zxvutrqqqrrrsuvwxxxxxyyzzzzy{}}}}}}~~~~~~~~~~~€€€€€€€~€€€‚‚‚ƒƒ„……††……………‚€zslhdcdec`^ZX^iv|~wpic``dkry‚„……„€yrkdbehjlnonlllllppppnorsxzwtonv||vrpkhjlmoopnkijlpptuxyyyxxxxyxyyz{ytlcZWY]_bglsy~}ywsonliggfddefiklnoooolijmpsssssromlnppommruxxuspmkfbabeefimpuxvsqpqtvvtuy…ŠŠŠjlnopqsuvxxzz}~~}{zxvutssqqqqrrsuuwxxyyyz{{yyz|}}|||}~~~~~~€~€€€€€€€€ƒƒ‚ƒƒ„„„…………†„…ƒztnhecca`^]\]fr{ƒ€zukeb^bgms{€ƒ„…ƒ|unlgdfjkjiikkkmorttrtuvx{|xqmmppijtusnjjklmoprspkhikmoswxyxxxxyyyyzzz{zunf_ZZ\_bglqwz{}|zwrokigeecbbdegimopqponmljhlortsrsronlnpqnmnqtutrqnnkgdabffkpsvxzwrpqsuvtstx„‰‹‹jlnopqsuvyz|~~~~}|{zxvutssqqqqrruvwxxxyyyz{{{{z|}}|||}}~~~~~€€€€€€€€€€€€€€€€‚‚‚‚ƒƒ„„…………„ƒƒ‚~xrlfb``ba`^_dpy}ƒ€{vrlda`fkqyƒ„…ƒ~xqnigikjhiklllnqtvuttxyyyzvonprokjjnniiloqonrttpkhikmoswyzzzyyyyyyzzz{zvnfa^[]_bgjowz{zvsqmiggecbceeefilnoqsponmjhhkpstsrsspnlnpromnoppnlkkkhdcdehkrw{~~{usrstuwtsrv€‡‡ˆmmopopqwx{}}~}}{yvutsqqqqppqstuvxyzzz{{zzzz{{}}yz|}|}~~}}~~€€€€€€€€€€€‚‚€‚‚ƒƒ„„„„„ƒƒƒ{tnie`\]``^^ckv}‚‚ƒƒ€|umga`bgpv|„„„‚}|wqljgklihknmnpruvvwv|~zwwwwxzvkhiinsooqvytmnqrpkiijmprwyzyyyxyyyyzzzzyxohd`^^acfkpsxytrnlhgddb_abdfgimmqqrrpmmnjiilrvuuttspnlmopmkkkkjijhfecbbegkou{~€~xtsrtvxwtqpu|ƒ††mmmooprwy|~}}{yvutsssrrrrsttuvxyzz{zz{{||{{yyz||}}~~~€€€€€€€€€€€€€‚‚€‚‚ƒƒƒ„„„ƒƒƒ}xqje_]]^__`cir{€‚‚ƒƒ‚xqkebagmtz‚„„ƒ~yrmjgghkjkkmnrtwyzz{€ysuz~}vqoieeiosu{‚{tqrrpkiijlorw{|||zzzz{{{{zzzxrkd`^^acfhlpsspkjfdcbbdedfhikmnnnnppnlkllkjnrvuuttspnlnqpkhgfffededccbbehlrx}}{wsprtvwurqprz……llnooruw{~~~~~||{yvvusrrqqpqqsuvvwwzzzzzzzzzzz{{{|}~}~~~~€€€€€€€€€‚‚‚‚‚‚‚‚‚‚‚‚‚‚€€€‚‚ƒƒƒƒƒƒ„‚€}xrmgc``_```afnx‚ƒƒƒƒƒ€|vnh``chou|‚ƒƒƒysnjfehjjklpsuxzz|z|}~vqx~yvtolkjijnpx{wqpvwupkghkmprw|}||{{{z||{{|{{xrkda``bcegjnpoigegdbabbcgihjnoonmnoomnnomkkosuuuuuspmmopoicbeea`adcdeeffhmry}|{zuqqssuurqqppx‚‚lllnqtvx{{}}~~{{zwvutrrrqqqrstuvwxxzzzzzzz{{zz{zz{|}}~}€€€€€€€€€€€€€‚‚‚‚‚‚‚‚‚‚‚€€€‚‚ƒƒƒƒƒ…„{vrmhdb_\Z``bekr{‚ƒƒƒƒƒ€~xrlc_bflrz€ƒƒƒ~xtnjfehjjklqty{|{zxvvvvw}‚€|xsqpoonpt{xtsx}}ytlhhklorw|}||||{{zz{{yyxvplgc``adghikjjhbacddcegfgijknoonnoqqpnnonlmqtvvvwvurpoprmgaabb`_`abcccdghmry}|zxtqrssuttsrqqt{kmmoqswxyz{|~~~|zyxwvutrqqrrstuvuvxxwxxx{{z|zz{{{{||}}~~€€€€€€€€€€€€‚‚‚‚‚‚‚‚‚‚‚‚€€‚‚‚‚‚‚ƒƒƒƒ€{uplgec`^]]__bgqz€ƒƒƒƒ‚ƒƒ‚{vof`bekqw}‚‚€xvqjfdfhjkmtx}|}~‚{vvuv|‚|zyy{vtvstw|wqt|~}xrlhhjloqv|}}}}{|||zywwwvtsojea``cijjgggecacddihijkkklnonmmmpmmprronnruvttvvspnopplf`aa^^__abccehhjnsx|{xvrprstvtqrrqqsy~lnmoruwxyz{|}}{zyxxwuttqqqqqqrssvwxxyyzzyyyzzz||||||}}~~€€€€€€€€€€€‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚ƒƒƒ{vojfeec`__`ceinv|€ƒƒƒƒƒƒ‚~|voga`dhnu{‚‚}{uqnhfghkkpvzz~€‚‡Šƒ~~~~|xvssqopqruxzxuv}~}xrlhhjnpty|}}}~}}}|zywvutspmidbaadefedggebceggjjkjjjiknonmlllllprrpoosvxuuvvtqnoppkebba^^\_adefijjkosy{zxvrprstvtqooppqwzymmmotuvxxz{{||}{xwwvvusrppqqrsstxxxxyyzzzzzzzz{{{{||~~}€€€€€€€€ƒƒ‚‚‚‚‚‚‚‚‚‚‚‚ƒƒƒƒƒƒ‚‚‚}wqlieeecbabefhks{€‚ƒƒƒƒƒƒ‚‚€}vohbaaglrz~|wtpmifegklqvz{‚†…ytruvwtrqmknoosuuxzyvw~~xqokiklnqv{}~~~~~|{ywussttqnhebbbeffgggghhggjkjlmjijkknmmllnmnopoqrortvttruvtqmnqokc``ccbaacdefijlmqtxzywtqqruuvsqpppppuutmnoqsttuwxzzzzzywwuutsrqqqqqrrstwwxxyyyzzz{{zz{{{{}}~~}€€€€€€€‚‚‚‚‚‚‚‚‚‚‚‚‚‚ƒƒƒƒƒƒ‚‚‚|wplifeeeeefghlqx~€‚ƒƒƒƒƒƒ‚‚ƒzsmhbcfkpw}|ytrpnkhehlpptx{‚„‚}wsqmnpqppnmmmpuy{zyzwvz~xqmiiknprw{}~~~~|{zxvstssrolhgcaadgghhhhiijjmnmnnkjijjkkkjlmnmlprssstwwutuvwtqopqokfbbdddbbefhjjijnrvyyxurqqruwtpnppppoqqpnoqqrqrswxyyyxxxvvutsrqppppqssttuvwwwxyz{{{{}}~~~~~~~~€€€€€€€€€‚‚‚‚‚‚‚‚‚‚ƒƒƒƒƒƒƒƒƒƒƒƒ}wpjhhghhhiiklpw€‚ƒƒƒƒƒƒƒƒ‚~{uojfceipt{}|zvppppmifgjnoswz~€~zuppnmnoonnnmpux{€{xuu|~xpkiikntuy|~~~~}|zxvvtutsrmjijfbbdhklmljllnoqrrpnljeggijkknmmnnprtttwyzwtuvvtpopqolfddeddcbdhjlllkosxxxvrqppsvxwspnponnnoonoqqrrstuwxxyxwwuussrqqppppqssttuvvwxyyz{{{{{{}}~~~~~~~~€€€€€€€€€‚‚‚‚‚‚‚‚ƒƒƒƒƒƒƒƒƒƒƒƒ~ysnjiiklllmmmotz€‚ƒƒƒƒƒƒƒƒ‚|vpkfceimswzywtqppomkgdgloquz~ƒ~xsomoqpmmmooqw{ƒ…{wvz€~xpljklorwz|~~~}|{yxvvtutspolkhgcdhmptutqpqrsrttqmkjhhhijiigiklnprsuvyzxutuvusoopqolfffdcbbbdhjmmnlpsvxvurqppsvxwtqonmlkknnppqqrssuvwwvwwwwvuttsrrqqqqrrsttuvwxyzyz||z{{z{{|||}~~}~}€€€€€€‚‚ƒƒƒƒ‚‚‚‚ƒƒƒ‚‚€€{tnjijknopoponrv|‚ƒƒƒƒƒƒƒƒƒƒƒ‚‚~yrmhddglouwwvsqoomlkiehknosw}€€|vrplkjfgklnqu{€ƒ„†ƒ{vuz~wpkklmoqv{}~€€~}{zzywutrqonkjhgghotw{zxvtrsvwwvsnlijjjhiiiggjlnqssvvxyyustvuqoorrpledbcbabcehknnmmosvwutrqoqsvwwtqomkjjjlmppooqrrtvwwvvvssttsssrrqqqqrrsttuuvwxyyzyyzyxuuussux||{zzyzz|}~€€€€€€€€€€€€‚‚ƒƒƒƒ‚‚‚‚ƒƒƒ‚ƒ~wpjihiloprqqpnrw|‚ƒƒƒƒƒƒƒƒƒƒƒ‚‚€zuoiecekorstsqpoomnljfeinpqw}~}|{xspljhhiknosy‚…„†…}vrw}~wpkklmoqw|~~~~}}|zyxvtspooljihinu|€}zvtvxzzvspmlhgghiiikjjmnqsstvz{yustvwrporrpgddb```acehknonmpswutspprstuvvtqomkjhhklnoooopqsuvwuttuuuuttssrrqqrrssttuvvwxyy{|ywtrokjhilpwwusrpqrrux|~€€€€~€€€‚‚‚ƒƒƒƒƒƒƒƒƒƒƒƒƒzslhghjkooooooqu|€‚‚ƒƒƒƒƒƒƒƒƒƒ‚‚€|vqjdadimopqqppppnpokhginpptz~}~|xuqmjiiimnrw}‚ƒ…††‡ƒzvw‚wpkkjkoqv}€€€~}|zyxvuqpnoljhhkt{€‚„€{wvw{|zxtpnljihiigiiillnoqsuxy|yutuwvrrrssme`bbaa_`cfhknnnnquvtrqqqstuvxwtomljihhjknoppprstuvutttuuuussqqrrrrsssttuvvwwxyy{{xuqljihfefjnmifcbdehloswz|~~~€€€€‚‚‚ƒƒƒƒƒƒƒƒ„ƒƒ‚}yqjgeccdimpppoprw|€‚‚ƒƒƒƒƒƒƒƒƒƒ‚‚~xrkecceilmpppqqqqrrmhfilpqsx|}}{wtqmljjjnrw|€‚…†††‡…}uv}|vplllmoqv}€€€~~}{zxwvrqopmkjjnv~‚„„ƒ}yy{||ywurqnlihfffghhjlpqqqrv{{yuwxwvrrrssme`a````adgilnnklquvtqppqsttuvvqnomljhhjkklnopqqqssssttssrrrrrrrrssssststuvvvxyz{yxvrmljigecbccbbbb`adeehou{}~}}~~€€€€‚‚‚‚‚‚ƒƒƒƒƒ‚€|xsmfc````foqrqpqw|ƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚xrnfacfkjjkmoprsstspjghknpqtz|{ytpqlkklmqw{~‚„…††††{tu|€~wpllklnrx}~~~}||zxvusqoqomllqw}ƒ„€}{z{zyxwtrpmjigffiihgijnpqrsw{|zvuxwurrrrqia^_]^`bdfhkmnmlmrsuttrprtttuwwrnmmkifehkklmnpqqqssssrrssrrrrrrrrrsssttvvvvvwxyz{yxvrnmkjhfdcbcdbaabdddccflqw{}}~~~€€€€€€‚‚‚‚‚‚ƒƒ‚‚~zupkhd_]\`ekpsrqrtz‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚{uojeacehjkmopsuxxvslihjnoqtxyxsnlljkkmqvz~€‚„„…††††wsuy{uokkklosw|~€€€|{zxvtrpqomlkqwz}€|yuuustsrppolhgfhjiiihhkmopqswzywstvwuqqrroh`]_]^`cegilonmlmqrrqqqqsvwuwyxtollkiedfhmnqqppqqrsttttssrrsrrrrrrsttuuuuwwyyxyz{{{xurrrrnjgggfcfffffdeba`cjtxz||}~~~€€€€€€‚‚ƒƒƒƒƒƒƒ}wqleb_][\^blnqsrtwz€ƒƒ„„„„„„„ƒƒƒƒƒƒƒ‚~wqkeceggijmpqsx{{yvojhhoppsuurmkkjkllnqx‚ƒƒƒ…………‡†„|srwxupljklnrw|€€€~}}}|zyvtsrqonnqsvyz|zwppqmkkmkmmkifdceiiihgggjoppswz|zvtuutrrsrne^\\[`bcehkoqnkilpqsrpppsuttuxxqmnnlhgdefmnppppqqrsssssrrssrqrrrrsstuuuuuxxwxxyz{{{zzxxyyurmlijmqsuttqmhbbbcksvwxz|}}~~€€€€€€‚‚ƒƒƒƒ‚‚|vojdb_^]`aeimprsuy|~€‚ƒƒ„„„„„„„ƒƒƒƒƒƒƒ„‚xrleacdhjjmqtw{}}{xslhhloppplifhhjlllnqx‚ƒƒƒ……„…‡„€ysrsvtoljjknrx}€€€€~}}}}|zzxvusrpnpoqtvwvqmhfecbadgjnkhddceijjhjlljjjotxz|wstuutrrsrne^\\[_befloopnkjmopppppqsutstzysnmmkgeccelmmmmonpqqqqppqqppqqqqrsstuuvvvvwxxxyz{|{{{{||||wuonnquxy{{{ytmecbcehkoruxyz{{~}~€€‚‚ƒƒƒƒƒƒ‚~xokgfcaaabdglorsuz€ƒƒƒƒƒƒƒƒ„„„„ƒƒ‚‚ƒƒƒƒ‚~unhb`dghikrvy~~~{vohgknqlieebdehkkmnrx‚ƒ„„„„…………ƒywxxupmkijotx}~~}}}||}||{zxvtqnpnnnpsusof```^]^cfjllkgdcgjklmpponmknqw{{xuuvvussrqog]Z[_bgiikopomjjmpqssqqsuvvtsxxqnmlihebefklmmlmnpqqqqppqqrrqqqqqrttuvvvwwxxxxyz{|{{||||{{zxvvvxyz|{|||ytokhdb`bdgijijlqv|}~€€‚‚ƒƒƒƒƒƒ~ysnigdbabfglnpquy|‚ƒ„ƒƒƒƒƒƒƒƒ„„……ƒƒƒƒ„„ƒƒ~vrlfcchjknswz~€{yrjfhlnjebbceehlmnpru|‚ƒ„„„„……ƒ€€}xuzzupkiijnsx}~~~~{xxy||}|zwusqnnnmnpqqlda`aaabgknppmhdcfikopsuronlkou{{wtuvvusstsme\Y[afgkkloqplhijnprsqqsuvvruyvqnljjiebdbkjjklmoqrrqqppqqrrrrsstuuuttvvwwxxxxyz{{||{|||||||{{||{{||||{zvsqmieabd``]_bdgnwz~€€€€‚‚ƒƒƒƒƒƒƒ‚€{vqljhebaejlqsvw|~ƒƒƒƒƒƒƒƒƒƒƒƒ„……†……………………ƒ{slfdegjlpv{~‚‚}yslfehlkc`acfhmoonoty~‚ƒƒƒƒ„ƒƒ„ƒ|srwzupllmnnqx}€€€}xtpqwy{|}|zvspnonnnqrnkfggjmnrtuvupkfbbeilosw{xrnjint{{vtuvwtrstrlc[YV_hlmmoprnjggjmqtspqtvxwstxupolkjhedefjijkmnnpppqqppqqrrrrsttuuuuuvvxxxxxxyz{||||~}}}}||||{{||||||{zyyurmkhgda^\\]^`gpx}€€‚‚‚‚ƒƒƒƒƒƒƒ‚}xrmkhgghkptxyz}‚‚ƒƒƒƒƒƒƒƒƒƒ„„……††††††…………ƒ}umfcdgjosw}‚‚‚~xtoigilkhcbfknrsqmou|€‚ƒƒƒƒ„……‚yroovyupllklnqx}€€€~xrieemty||zwspoommkmoppooqvxyyzzzzwrlfbcegjmqx{|vofejszzvtuvvsrstsnf_]\ahlnooppmighjostrstvwwutvxuomlkjhfefhjjjkmnppqqqqqqrrrrrrsttuuuuuvvxxxxzzyz{{}}||}}||||{{{{{{||zz||zyxusomigea`````enw}‚‚ƒƒ„„„„ƒƒ‚|wqmkkiimsy|~€€ƒƒƒƒƒƒƒƒƒƒ„„„„……††‡‡‡‡††……„ƒ|wphefhlosz‚‚‚€zvqkijknifdiotvxsmmqw}€ƒ‚‚„„……ƒ~vooqwyvokkjknsy~‚|rh`\]ent{{wsqponlmnopqppqv|}||~~~|zskfcdfhjlsx~|vnfadnwxvuvwwtuutroiba^agkoqssqmigikqsststwxxuuxyvolllkidfhijjjkmnppqqqqqqrrrrrrstuuwwwwvvxxyyxyzz||||||}}||||{{{{{{{{zz|||z{yxwtqnkjhfda^bkt}‚‚ƒƒ„„„„ƒƒƒ~ytnmjjkqvz~€‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„„„„…††‡‡‡‡‡††……„ƒ}yskffgjosx~ƒƒƒ‚~{vojhjmljjqw{|{uommr|„„„„„„‚|usrquxvokkjknsy~zpha]_fntxwsommonlmoppqrruz}~~~~~|xqjgdehikmqv{{vld`bltxvuvwwtuuvtpifebejnpsssqnigkmrvurstwxxuruxuoljjkigilmijjjlmoqppppppppqrsstuvwxxxxwwxxzzxxxy{}||{{zz{{zy|zyyyy{{{{||{{zz|||{wusqmga^agr|€‚‚‚ƒƒ„„‚ƒƒ‚€~xrkjklqw|ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„„„†‡‡ˆˆˆ‡‡†††…„ƒ|vpheehmrx~‚„„ƒ‚€€|wsliimnmnu}~ytomnrxƒ„„„„ƒ‚{uuwuvvvnlkmlotz‚‚|riccdhorssolllkklmnoppqquz}€~~}vngcdefgjmrvyyulc_bkuxvuvwvutvvsnigihhloprtrnjgfjnquuttuxxwsquwrnkkjhfgjkmijkklmnoppppppqqqrssttuvxxyywwxxzz{zy{{}||||}}{{zyxyxxyyzz{{||||||{{{{zzxvqha^`jt|€‚‚‚ƒƒ„„……‚€|wupmklmrw~€‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„„„†‡‡ˆˆˆ‡‡‡††…„ƒ~zupgedgjov|‚ƒƒ‚~yuvtmjimopqxztollrxƒ„„„„ƒ‚|wtz}zvvwplkknnsx}‚‚€zsjggklnpppnnmmllnnpqqqqqsw}~zsmebcehijnswyytmf`ajsvtuvxvutvurnjijkknprsutnjiimrtussuvxxvrswxsnkkjhfgjoqijjjklnppqpqqqrqqrssuuwxyyyyxxyyyzzz{{{{||||}}{{yyyxyyyz{y{{{{||||{{{{{{|zukb^ajt|€‚ƒƒ„„ƒ„ƒ‚€}wpnlllnqtz~€ƒƒƒ„„ƒƒƒƒƒƒƒƒƒƒƒƒ„„……†‡‡ˆˆˆˆˆ‡‡††…„|wpjfdeimr{ƒƒ€|ustuokkmopry}zvqkjov„„„„„„€zvw{|xtttqnlmmosy‚€|uqmmnnmoppmmomomnoqqqqppsx}~~~~}ysmhcdegjlpsy|ysmfbckrttuwxwvuuuqnklmmnpqsttqmlkmqvwvstvvxxtptyysnlkkiegilqijkklklmnppqqqqqrsvvwxwxyyyyyyyyxyzz{{{{||||zzzzyxwvuuuwwxzz{{||||||||{{|yvnd`ags{~ƒƒ„„ƒ„‚€ztplkkllmqw}ƒƒƒ„„ƒƒƒƒƒƒƒƒƒƒƒƒ„„……†‡‡ˆˆˆˆˆ‡‡‡†ƒ‚}xurlfdeimrw}~zumlorokjjlorw}€|vqnmqw~ƒ„„„„‚}z|}xusrrsrnlllnry‚€{vrsssnmoppnnommnoppqqqqqtx|~~~~~|xqkgceghkosu{|ytoieenqrtuwxwvvvvqlijmpqsttuusomnotwxvtuvvwwsrtyxrnlkkihjlothijllmmnmoqqrrrrsuwwvvxxyyxy{{{{{{{{||||}}|{{{yyxxwwwwuutuwy{|{{{{||||||{{xpfaaeqz~‚ƒ„„ƒ‚}wqnkhhiijotz‚‚ƒƒƒƒ„„„„ƒƒƒƒƒƒƒƒƒƒƒ„…††‡‡ˆˆˆˆˆ‡†‡…‚€}xsrmgddfiptz}}ysmefmqnjhllnsv{}vromrx~‚ƒ„ƒƒƒz{{vsopqtpmmmlmqw‚{xssvvtommpppqqomnoopprrssuz|~~}}zvoiecdghlosz}|wrlighorttvxwwvwwwrnlmoqsututvspoorvywsstuuwwttvxvqonljjjknqwhijllnppqrrrrrsstvwwwwyyzzyz||||{{||}}|z{{{z{{yyyyyywwuuvxwyy{}}}}||||{{{{ysja_clu{€ƒƒ}wrkgggfgilrw}€‚‚ƒƒƒƒ„„„„ƒƒƒƒƒƒƒƒƒƒƒ„…††‡‡‡ˆˆˆˆ‡†‡…‚zuqoniffhhnsxxuoidcegnnnnpoqstz}xsolou}„…„„ƒƒ~|zxurqrsqmjlklnuz|yvsuwzzvppprrrtqomnpppqrrssty|€~}|zvojeddfhkpty{zvpmkjnsvuuwxxwvwwvsnlmoqstvvuvsqpsuxzvutuxxxxttxzwolkljjjmprrijjllmopqqqrrsttuuxxy{{zzz{{||{{||||||||{{y{yyzzzzzzzzyywwxxxy{{||}}|||zz{ztmd`cjtz~€€‚‚wsmgccbdgiqu}€‚ƒƒƒƒ„„„„ƒƒƒƒƒƒƒƒƒƒƒ„…†…†‡‡‡‡‡‡††…‚zwtpnnkihiilprqlgdba`cioqrv{{xwz~€|xrmnqwƒ…†……„„‚{urqstpljkjkmpsutppty{zurqttuvupnmnnoqqsqqruy}~{xuojedceimquzzxronnprvyxvxzzyyxxxrljmprtwwuvwurswy}{wuuvxyzyuuxytnkkkkkkllnmijlmlmopppqstuvvvwxxy{{{||||||||||||||{{yyyzyyyyyyzzzzyyyyyyy{{{||||{{|zz{zwqkbaenw}€€|smga`aceiow|€‚ƒƒƒƒ„„„„ƒƒƒƒƒƒƒƒƒƒƒ„„…††‡‡‡‡‡‡…„ƒ|xsqpnolhgiijlkjgda_ceimquz}~€}|‚{wspnrxƒ…†……‚||}xrquwvpljkjiilpqpmlqvz{vsvwzxvrpnnprrrrrsrtvy|~~{wrnieefiknrxzyvqonnpuxzwxz{{yyyyxrklnprtuvvwwutvy|}{wuwxxyywuw{yrmkjjjkkkljjjklmnopppqrtvvvvwxyyz||||||||||||||||{zzxxwwxyyyyyzzzzyyyyzz{|||{|{{{{{{{{{ztoheektz~€~ysje``_cjqv}€€€‚ƒƒƒƒƒ„„„„ƒƒƒ‚‚‚‚‚ƒƒ„„„„…………†††…„‚€|tqpponmkhghhiieadcdcjlnptx}yw|†Š‹‰€xqpsyƒ…………„ƒƒƒ~ysrw{yqkjjkjknnmlilru{{ywx{|xxspopqrrrprttuwy|~~{ywqlhddfgjnrvvtqqqppruzzxwy{zyyyzzsnnqrsuuvuvvtsvy{}zwwyyxzyxuvz{smjiiijjjjhjjklmnopppqstvvvvwxyyz|||||}}||||||||{{zyxxyyxyyyyyzzzzzzyyzzyzzz{|{{{{{{{{{zwrmgcfnuy{{zwqhc`aciqx{~€€€‚ƒƒƒƒ„„„„ƒƒƒ‚‚‚‚‚ƒƒ„„„„……………„„‚}{vtqppnnpqlhgghhfbcbbdjnrtvvyvqsttx††‚}zwx|ƒ…………„„‚‚}yww{xrkjiihjkkjijmswxxywx{|xvrnnpqssrprttuwy|~~}}ztpkfeegikosurqppooosx}|yyz{zyyz|xrmnrtuvvvwwvtux{}}zwwyyz{{yvy|zrmkhiijjikjkjkmnnoppqstuvuustuvy{{{}{|}~}}||||{{{zzyxxyyyyyyyyyyxyzzzzzzz{{{{{{{{{{{{{{{ytqjebflqtutqmhecciov{€€‚‚ƒƒ‚‚ƒƒ……ƒƒƒƒ‚‚‚‚ƒƒ„„„„„„………‚}zwusqqppnnpqpgeefgfdccbdhlpswwqijlmprv{ƒƒƒ€}|‚„…††…‚~{yz|wqmkjkkkmkmkkouxzxxwyz{xvtppqrssrrrttuxz}~}|zxsnkhghkmmopppppppootz~{zz{{zz{{xplotuuvwwwwvuvy|~€zwxxy||}zv||xqljjkjgfikilijjknopprstutsolkmpsvvvwy|}~~~~~||{{{zyy{{yyyyyyxxwwwxzzzzzzz{{{{{{{{{{{{{{{|zumf`cfkmlkhdbbbgms|}€€‚‚ƒƒ‚‚ƒƒ‚‚ƒƒ‚‚‚‚ƒƒƒƒƒƒ‚‚|yvtssqppppqrstpjffghgeffb_`fmsuwvpnknprv}‚‚ƒ††„ƒ‚ƒ„…††…„‚}{z„~wqmkklllllnopqux{zyxyzzwvrppqrttrrrttuux}~~{wqnigghkmponnooooonpu|€~{zzzzzz||wolrvvuvwxyyttvy}}ywxyz||{wv||xplkkjhfgiknpjjijloqrrstusqmhgfgjnnoqrvvy||{{||{{zzzzyyxxyyyyyyxxxxxxzzzz{{{{{{{{zzy{||||||wqhb_bccddcaabfmqx~~~€€€€‚‚ƒƒ„„‚‚„ƒ€€~~}}}}{ywwtsprpnnooqsuvurkhhiiiijjd_[aluwxwqmnqvz~„†‡‰‰ˆˆ…ƒ„…†……‚|z~…ˆ‚yrmkjlmmlqsx}}xxzzxxz{zvtqpppsutqqrttuvy~~zuqmjhhilljhjkmnpomorw|~}|zzzzz|~{tontvstwwxwvqsw|~|xwyy{||ytu}}yqmjjjggghkqvjjkkloqrrstusqkfdceeggghklmpvxz{}}{{zzzzyyyyyyyyxxxxyy{{zzzz{{{{{{{{zzy{||||}}xrkdca__``aaabfltz~€|zxwy}€‚‚€~}{xwtsqqpponnnnnlijjhgghijjlorvxywrkgghikkpokaVXeouvwrnot{„…†ˆ‰‹‹‹‰…~}€…†…………‚ƒƒˆ‡€xpmknprqpt{€€yyyyxxz{{xurrrrstsqqrttuwz}zuolighjigdbehjlmnprvxz}}|||{{|}}zsnouwuvxxxwtqsw|€€|yyzy{{{wqsz}yqmlkkigghltyhjjlnprrrsuxvuplhegfffddffghlpvyz{{yyyzzyxxxxxxxxxyyzzzzzz{{{{zzzzzz{{{|||||{zysnjgc_\_^^^abglu{€€}yrkgfhpx}{xuommlkhffffedccdcbccdc`````bfkrvyywrlgfijhjqtqgZYdouwtqonrv}‚‚„‡Š‹‹Š‡„ƒ}z€……ƒƒƒƒ‚„‡„|vpmlnsx{|}~{xtuvuwxx{|zwsrrrssrqppprruy|~€}xsmifgiifd``cfjmooqvxz{}}|}}||}~}ztpquuuwxxywuqrv}€{xzz{{|yrmqz}womlmkjhfgmv}hjjlmoppqrtvvuroljjigggdddedeglprvwxyyyyxxxxxxxxxxyyzzzzzz{{{{zzzzzz{{z{|||||{zwsokgb`_^[[]^cgow~~€}xnc\\]bhnrolhda`_]]]]\\[ZWWXWYZ\]^^]]___bgjmnpqpkjhffdegmsodairuwurpnnprx~ƒ…‡ˆˆˆ†‚ƒ†‚~€ƒ‚|€…ˆ‚zvpmlot|‚…„|tohfimuwwzzzvrqqqrrqpooprswx{~{vqlhffhhec]]beknnruuxz~}|}}||}~~{xtrvxuwxxyvspqv}€{xyy{{{xqlqz{uomlmmkjiglv}ikjlnoppqqstuvurpppmmkjihgecdefilpruwxwwwwwwwwwwvvyyzzz{{x{{{{zzzzzzzzzzz{||}}{yvspnkhfc_\]\^bhmt{~}ype_YY[^beheb^\[XWYXWWVVUSTTRSVWWXYUXVX[_adggfgimkigdb_`dimrpmrwvvtqpooorx|€ƒ„„‚€‚ˆŠƒz|€~}~‡‡‚zsmlkow~„„ƒ~slgijltuyz{wsqpprsqponnnqsux{}zuoighjkkgcaabfknqqtvwy~}|~|{|~zusvvvwyy{tnlnu|‚€{xyz{{yvnkpyxqonnnkifehlu{ikkmnoppqqrsstttuuvuusrqnmkhfdccfjnprtvvuuuuuuuuvvwwyyyz{z{{{{zzzzzzzzzz{|~~}}|{ywvtqmifc`_]\\^bgnqqne^[[[]_`__][ZYXY[\^``aa_^]]\^`abca^\[ZZZ\_behikijgb`ceggdhqvswxwwurponnory}~~{|ƒŠŠ‚z{|xyˆ‰ƒ~€‚€„††ulefjompsy|yurrrstsokknortwx{~~{vqligghjjieeeimqrssuvxz~}|~|{|~{xvvwwyzzxsmlnu|{xyz{{xrjipwvqonnnopollptyhjkmmnnoqqqrrtuuuuwwxywwurpkgecabcfgkqstttsuuuuuuvwwwwwyzzzzzz{{zzzzzz{{|||}}|zwxwvsljgdc`b_\\^dihd__]^\\\\\\^_aabdegllkkkigedbdggijifda^]]\Z\_`dgihd`bhmoi]\luvyyxwvtpnmnosy|}~~|zzˆŽˆƒ€}y}…‹ŒŠ‹ŽŒ‹ˆ…„…„……}thdejlpu|}ytrsrsvspmlmoqtvyyyxsrplggikllkhjlpvwvutuwx{~}}~|{|~~{zyxxyzyxsmlmt|‚€zyzzzzxogipttonmnoprromosvikkmmnnoqqrtsuuuttwwxyxxxxvqliecba`_ahnqrrstttttuvxxxxwyzz||{{{{zzzzzz{{{{|}}|zxwsromnnlie`\[^]\\]____^^^^^ehmnpqqrtuutrplihgefggimonnmjiedbb`_``abceknnog\Vetwxxwutrrqollr{}xv|€|}†Œ†~‚‰Œ‹‰ƒytx‚…†‡ˆ‰†vpkkptvz{wsrsrsromllmprsuvvtsqnmkkkklmmnnotw{{xtrtvy~~}}~||~~}{zxxyzzysmmnt|€~zy{{zzwogirvtqrqprsuxwtqrtjllnmnopqrttvvttttwwwwxxxxwwsojgb`^\`dkopprrrrssssuuuvwxxy{||zz{zzzzzyywwvw||€}{yvtssonnnmjb^\[]^\[[\]^__`adgmtwy}}~~~{{yurnlihgghikpuuvvtrnmjhgdfdadhnqnklojeioruxwtqlonhfgpz{wt{~~{~†Œˆ„‚…ŠŽ‰…ƒ€wrv~ƒ„‡ˆ‹ŒŒˆ…‚|vruz{wsqrssromnqryxuroqpnlkkhigjlnoqsv|~|wuuvy{€~|}}}~€€€€}yzz{{yyrmlmr{zy{{zzvqgjruvtsrqpsuz{xtttkmmopppqqrttttttttwwwwxxxxxxwsqmjfa]]bhnppqqqqrrrrsstuvwwxz{zyz{zzzzzwpiikpuy||yusqqpqqmgdbba_]\][[\^]^]`aadgnu{‚‚‚‚|yuromjihiiimouy|}||zvqmifdcfhnsrjghnw{~{upqtronpmmlosy~}{}ƒ„‚†‹ŒŒ‹‹‰ˆŠˆ‰‰…„ƒ|vy}}z|„‰ŒŒŠˆ†zy{zvspnnnoot}„‰‰„xooqpnljihijknpqsuy{|{xsorvy|€€}}}~€€€€€€}yyz{||ysnkls}{z{{{{umejswtrsrqpsu}~{wuumonopprrssuuuuuuvvwwwwxxxxxxwvsrpjc[Y^fopprrqqppqqssrtuvvxz{zzzzzzzytmd`bdinrvutrpqpnlke`[XZYWXVYYY]ceeeccefgkqwz~€~zupmkkkjhhiimry}€€}yunhdbinrsrkhhk|€€~wlhgmurroqv~‚ƒ‡‰ŒŠ‡‚‚…ˆ‰‡Š‹Š‡‡†ˆ‡…‡†}€€wry‚‡‹ŒŽŽŒŒ‰‰…|~ywtqljmox‚‰‹‰€xpnopqpkjhghilnppsu{~~{uqprvy|€‚}}}}~€€zz|}||xrolmt~€~{{{{{{thdjrusrrrooqx}}|yvunpprttttttuuuuuuvvwwwwxxxxxxwvustnd\Z^empppppprrrrrrqstuuwxyxxzz{{zwphea^_adimnlklmlljhaZUTVVWUTTX]dkpqqmjgdbceilpux||zvrolkkkkjklnrv{~~~|zwqmgglsutsslhpy„ˆ‰}rnsz}{vuz€ƒ„‡‹ŒŽ‹Šˆ†‰Œ‹‹‡ƒ‚ƒ~}…ˆ„‚zpov~„‰‹‹‹‰‡„~~ysrqqrv}…ˆ„|tomnnopqpmlkiiklnpqsvzzzwspptwz~€~~~€€}{|}~||xromnt~€~{{{{{ypgdjqssrrrqqqx{{zxwvqprrttstttwwvvttuuuuwwxxxvwwusttqme]XZcmpoonppqqqqqqqqrrttuvwwxxyxxtmgc^``abeegggfgged_[TPORVWZY[`elrx{{wnhe_]]`eijlpqtqpnmmkkklmpqty|~}ytroomtz€~{{|wyƒ……ŽŒ‡|rv}ƒ……„ƒ„†ˆŠŠ‹‹ˆ‰‰ˆˆˆ‹ŒŒŒŽŽ‡ƒ†„{y‚†‡„ysqw~„†ŠŒŒ‰‰ƒ}w{}{sppq{‚‰‰ƒwmnmoopprrrpnmjjlmnoqsvz{wtqqrtx|€€~€‚‚€€~|{{}}{vsonot}|yz{{yunebjturrssrrrvzyyyvvpsuuuutuvvwwvvvvvvvwwwxxxvvvussspld\UXbiooopppooooppppppsttuwwxx{ywtohc_aabdddcca_``^][WOMLPRTX]ckry}€‚}tkda]^]^aefgijlnmmmllmoqtwy}}|{ywuqnmpu}‡Ž‹‡„„ŠŠ‚‹“‰|v{‡ŽŽŒŒŽŽŒŒˆ„„„‡‹‰Š‹ŠŠˆ„†„€…‡†€|vttz…ˆŠŒ‹ˆƒxpnqy}ztnkr‡†€uonmmlmnrtttrpmkjlmnprtwzyuqqqrtx|€€~€‚‚ƒ€~|}}}}zvsonot}~|{{{{yukbdmssrrqqrruyyxyyvvpsvvuvvvxxxxxxxxvwvwxwwwxxwwvvuuqnh_XY]djmmnnnoonnooppppqrtuuwxxxxxuqnjhdcbabdb__]^]ZXVUPMJHMU]dnw‚ƒƒ„ƒvlfddfed_^_bdfhklmnoopqtvxzzzwwsqppnnqz…Œ‘Œˆ‹Ž…‡“‘Š~rwƒ’’’“‘Œˆ…ƒƒˆ‹‹ŠŠˆ‰ŒŽ‹‡†„…†„€|yxvv{ˆŠŠŠŠˆƒwolry}xnhn~„‚|tnmmnnoopruvurpolllopqrtw|zvsppsux}€€€~}€‚ƒ‚‚ƒ~{}}||yvsomnu}}{z{}|ztjdgousqqopprvy{zyxxwvwvvwxzzyyxxxxwwwwzzzxxxxxwwvvuuspke^Z]_dilnoooonnoopppppqrrtvxxxxywtpnlkigdacddb`_[XVUSQPOOQ]gq{ƒ………„„wmhfjprojgb`adddhknsutuutsrrrqqppnmmpy†Š“’Ž“’‹ˆ‹’Ž{ˆ“‘ŽŒ“’‹‹‹ˆ†ƒ‚†‰ŠŠŠ‰Š‰‹Š‰„€€„ˆˆ…„}|y}ƒˆŠŠŠŠˆ„~qhljf_`hz„‚~yspnnnnonptwyxtqnmnnopqrux{yvsqqsux}€€€€€‚ƒ‚‚ƒ~{||||yvuqmnu}}|{||zyrjfluusqqoppruxyzyxvwwwxxwwyyzzywwwwxzzzzzzyyyxxxwvvvspmha^^^_aehknoommnnpppppprsuvwyxyxxvtroonljhhid`^\VTTTTUWXZ^it|€ƒƒƒƒƒ‚~xoffjsvurnme_^`bgimqrsqqonmomlonopomq{„‡Ž“••”–—–’‹‰ˆˆ…‚…ˆŒ”’Ž‘’“”’ŽŠˆ‡ˆ‹‹ŒŒŠ‰ˆ‡†‚ˆŒŒ‹Š‰‰…€ƒ‡‡ˆŠˆ‡‡„{te]Z_it|}xyuqprromlmrvyywtqnnnnopprx|}{xwtttvy~€‚€€€€‚ƒƒ}{{{||ywtrmqw}~}|~}|xtlhrywqppppqqsxxyzzyywwxxyyyyzzywwwz{zzzzzzyyyxxxwvwwutrkgcbbaa`agjlnmmnnnnooooqrsuvwwxzzxvvtrpnmigcb_][XWY[\_aeipx~‚ƒƒƒƒ‚ƒzskfhov|zusokghiiihjlihhghknmllnprqrx{ƒ†Ž•—™›œœš—‰„€}‡Œ‘—š˜’’—œŸŸœ˜”ŽŠ‡‡Š‹‹Šˆ††„ƒƒ†‰ŒŽŠ‹‹‹Œ‹‰†……„……ƒ}|rnuƒ~xvtwvtsuvsmlmrwz{ytqnnnnpqstw{|zxwuuvxz‚€€€€‚ƒƒƒƒ}{}}}|zwurptz}~}|{}}ytlkvyvpppppqqqruyzzzzxxxxyyzzzzyyyyyyxxxxzyyyyzyxywwuwwuqmiiea`__bcfjmonnnnnnnnqqrtuwxz{{zxxtpmlhfc^\[Y\^_bcfinty|}€‚‚‚‚‚‚|tnffjqw|{wpmpsqkljiijjjooponjiptvxz|ƒƒ†Ž•šœœœ˜•‰ƒ€„ˆŽ”š  œ”’”–™™š™—•–Š…ƒ‚…ˆ…ƒ‚„€€†ŒŽŽŠ‰Š‰‰Š‹Œ‰†ƒ€€‚„„~{|wr|†‡vtw~}xrpsqmlnsx{{wtqnmmoqqssvwwvuuuwuw|‚€€€€‚ƒƒƒƒƒ|{|}~|zvurpu{}~}|}~}{ros||tpppoopppqvzzzzzyyzz{{||{{{{yyyyzzzzzyzzz{zyywwuxxyxtqnjgda_]^bglmnnoopppppprtuwwyzytqnjgea][[YZ[^acgknru{~€€‚‚‚‚‚‚}wpheejpwuxz{€~wsrpmkihffhlmjhhpw{}€‚‚ƒ„Š’›ž˜’‹‹‹‰ˆ‰‹˜žž›˜•’Ž’‘‹…wu~€ƒƒ‚ƒ„ŠŽŠ†„„ˆŠŠˆ‰‹Š†‚~~~‚}~€}yx||zwt{ƒ†€uqrqnnoquxyvtqnmlmmmkknonlmmopuw{€‚€€€€‚ƒƒƒƒƒ|{~~~|ywvtsy}~}}{tpx~{soppqqrrqsvzzz||zzyyzz{{{yzzzzyzzyx{{{{{zz{{yyxxwwxxwwvrnjfb`__bgkmonoppoprttuwvssqnkfd`[YWX[]^`behlqux{~€€€‚‚€yskegjkqsz‚…‡‡‰ˆ†…‚~zslddd_]bjlnnrv{|~Œ™žžž˜ŒŒŽ‘˜˜˜˜™™•ŽŽŽŠ…€volov…‡ŠŒ†„„…ƒ€€‚ƒ†…„~€‚€}yxuz|€ƒ†€xtrrppqrvzytpnmmmjkkknmmonooortuy{z|~‚‚€‚‚ƒƒƒƒ‚€€€€€}zzyvv{€€~~~~€|wv~zrppppqsttu{|{{{{zz{{|||||z{{zzz{{zy{{{{{{{zzzzzyzzyyyywutqnjd`^_cglmnoooopqrsttslida^ZYWWWYZ^chjmpuz{}€€€€€€|upifkosuz„‰‹‹‹Š‰‹‹ˆ…‚}zsjfgjkhhijkrz|tuƒ”›žžœš“ŽŽ’—œœš•˜œžš•ŒŠˆ‡‰Šˆ†ƒ€€zw|†ŠŽ‰|{~|||~ƒ„„‚€‚‚„†„€€}|}€~|{|yvssrrsuxzzurlkjjjklmnmlllkmmmlnsuwxz€‚ƒƒ‚‚„„„„ƒ€€€€€{{zzy~~~€yux‚|tqppqruuz~}zz{{yy{{{{{{zz{{{{zz||{{zzzz||{{|{{{zzzz{{{{xvtqlfa__cilmnopoqssutojd^ZXWVVX[^_bhnruy{~€‚‚‚‚‚‚|yroru}€|z‚‹‘‹ˆ‹ŒŒ‹‹Ž‹‚rioma\ahlqyxqm{šœš”’‘ŽŽ”™š™˜—šš˜•‘Œ‰††ˆ‰‰ˆ††‰Š‹‹‹ŽŽŠ‚{xz}~~„………ƒ„†…ƒƒ„ƒ„†…€~~}|yyyyzzwttstx}~ysomnnkkklnprrponlkijjijorw|€‚‚ƒ„„„„„„‚€€~z{{{z~€€€€xsw€ƒ{qpqrsstw|€|y{{{||{{{{{{zzzzzz|||}||||||||{{{{||{{{{{{{{|zxvqkda_^`cefghloppolhb][[\^^^cgkoruy{~‚‚‚‚‚‚‚‚‚‚{ww~ƒ†‡…„„‰Ž“‹ˆŠŠ‹‹ŠŠ’’‡wttj`]`is|}untŠ˜œ›™—”˜——”’’–™™–“’••”‘ŽŠˆ‡‡‡ˆˆˆˆ‡‹ŒŒ‹ŒŽŽŠ…ƒyy{{z}ƒ…†ˆˆ‡††„ƒ‚ƒ‚ƒ…„€{yyzy{}|zwxvwuuux|zspoljjkknrtvwyyyyvtsssokjlntz~‚ƒ„…………„ƒ‚‚‚|{|~}ƒ‚€€}tov~€ytsrstuux}€~|{|||{{{{||||{{{{zz{{||||{}}}||}}||||{{||||zz||{ztokgd`_^^_``dgihec_]Z[]`bgimrvy{~‚‚‚‚‚‚‚‚‚‚€€‚‚‚‚€~z|€‚‚†‡‡†‡Œ’Š†…†ˆ‰Š‘’’‹|ww{slfguxpt‰•™››–”šššš™–•–—•“‘’‹Šˆˆ‡‡‡‰Š‰‰ŒŽŽŽŠ‡…‚€|zzzxxz}€ƒƒ„…‡ˆ‹‡‚€€‚zyy{z{}~}{xyxwvststtqoklloqoprvz}~~}{yyy{ztnihkq{‚ƒƒ†††‡„ƒƒ‚ƒ‚‚}{|~€‚…‚€€€€zqnt}}yvuttvvvz~€~|{||}{{||{{||{{{{zz{{||||{}}}}}}}||||{{|||||||||{ytplhe`^^\\\^ab`__^__bfjotx||~€€€‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚€}~„„ƒ…‡‡†††‰ŽŽ‰„‚ƒˆŽ’•–•Œ{|ƒ…„}vzxnn—š›˜“•šš™™™–”““’‘ŽŒŒŒ‹‹‹Š‰………‰‹Š‰ˆŠŒ‹‰ˆˆˆ„~y{yusuy~}yx{„‰Š‡…ƒ‚{zy{z{|~|zyxxxuronppoorstttstx|€€€‚‚‚~wnhhpzƒ„†ˆ‡‡…„…ƒ‚‚€~~€„„‚€€‚€zokr||wusuuvvvz~€~|{|}{{||||||{{{{{{||||{{{{}}~~}}||||||}}}}||||zzyxwsnjea]]][\\^`__`bglswy|}€€‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚}€……„ƒ†‡ˆ‡††…ŠŽŽŠ†€|}}‹“˜—–ƒ€ƒ‰Š„„whfs†”š›™š—ššš™”’‘ŽŒŽŽŠ‰Š‹‹ŒŒŒ‰……†‹‹Šˆ‡‰‹‹‹Š‰ˆyz{wutvy|}zxw‡ŒŠ†€€‚}wz{yz{|€€|yzxwtqnlknoqqruuuvwxz}€€€€‚€ƒ„„…‚|rjhp|‚…†‡ˆ‡‡†……ƒ‚‚‚ƒ…„zqnw~|xuuvvwvwy~~|~~~~{{||{{{{{{{{{{||||||}}}}}}}}||||||}}}}|||||||{ywvrpmkgba^^`befjnrxz|~€€€€€‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‰Œ…„„†‡‡‡ˆ†„†‰Š†‚|{||€Ž—™™•‹„‚ƒ†‹Ž‹…†‡~tmt…‘—š—™™ššš™•’Ž‹‹ŠŠŠ‰‰‡‡ˆŠŒŒŠ††‡ŒŠ‰ŠŒŒŒ‹ˆ„|yyzyz|~|{{€†Š‰ˆ†„†‡ƒ}ywwxz}~zuqqonkjjmprrrsvxxyyz{€€€€‚‚‚‚„……†…ƒ{pfhr~„‡ˆˆ‡‡†……„ƒ‚‚ƒ„„……„‚‚‚|uu{}xwwwwywwy~|ywusq{{{{zzzzyyz|zz{{{{}}}}~~}|}}}}}}||}}{{||||||||{{||ywspniedehknrv~~€€€€€‚‚‚‚‚‚‚‚ƒƒ‚‚‚‚ƒƒƒƒƒ‚‚‚‚‚ƒ‡‹‰„„‡ˆ‰‰‰ˆ††††ˆ‰ˆ„|{{}Š–››š˜Œ„‚‚†ˆ†‡‰…|totƒŽ–˜™™š››››™”Œ‰‰‰‰Šˆ……†‰Œ‹ŠŠ‹’”’Ž‘’ŠŠ‰‰ˆ…|{zyy{‚„††ˆŠ‰ˆ†‡ŒŒ‹‰…}vuvw{zvsqpmmnoruutuwxy||}}}}~€‚ƒƒƒ„……‡‡‡‡ˆ…zkdiu‚†‡‰ˆ‡‡†‡……ƒƒ‚…†‡†ƒƒƒƒƒƒƒ~z{„zwvuxxvw{€€{rjiii{{{{zzzzyyz{}}||}}}}}}~~}|}}}}}}||}}zz||||||||{{||{zxzxvsrqsvx{}€€~~~‚‚‚‚‚‚‚‚ƒƒ‚‚ƒƒƒƒƒƒƒƒƒ‚‡‰ŒŽŠ‹ŒŠˆˆˆˆˆˆ†…‡†ƒ~zz~‹”™œœ›‘‡‚‚ƒ††‰Š‡}pko}Œ•—˜˜šœš–‘Œ‹‹Š‰‡ƒƒ…‰ŒŒ‹‹Œ”–•“’””“Ž‹‰ˆˆ‰ˆ†ƒ~vsru{‚‡‡…„„…†…„„†‡‰‹ŠŠ„zxsrqqsrqsuvwwx{zzz{}}}}}}~€‚ƒ„…†‡ˆ‡‡‡‡ˆˆƒuhejx„‰‰ˆˆ‡‡†††„„…‡ˆ‰‡…ƒƒƒƒƒƒ‚€€„†…}zxw{{yxy{skgjljj||{{zzyyyyzz||}}||~~~~}}}}||||||||zz{{zz||{|}}}}||z|~~|z{|}~€€€€€€€€‚‚‚‚‚‚ƒƒƒƒƒƒƒƒƒƒƒ„„ƒƒƒ€ˆŒ”Š‹Œ‹‹ŒŒ‹Š‰‡……‡†{{‡”–™›œš‘ˆ„…†ˆ‰‹ˆ|omr~˜™˜˜™œžŸžœ–’‘‹‰Š‹ˆˆ‰ˆŠŠŠŒ“”–•–——–“Ž‹‰ˆ‡‡………vliku€‡Š†ƒƒ„ƒ|y{€†‡ŠŒ‰†sqrrtutwyyzz}}||}~~€€€€€„†ˆˆ‰‰Š‰‰‰‡†…~peeq€…‡ˆˆˆˆˆˆ†……†ˆŠŠ‡…„„„„„„„ƒƒ…ˆƒ€}{{}}|zxvojlmkllozzzz{{zzzzzzzz{{{{~~~~}}}}||||||{{yyyxzz{{zz}}~~}}}~~€€‚‚‚€€€€‚‚‚‚‚‚ƒƒƒƒƒƒƒƒƒƒƒ„„ƒƒƒ‚…‹ŽŠ‰Š‹‹‘‘’‹‡„…ˆˆ…†Œ’“”•–™œ›‘ˆ‡‡ŠŒˆ|sqsyŠ˜›™——šœœœ˜”“’••”’’‘‘Œ‹ŠŒ‘“•–—˜—•“‹ˆ‡……„„†„€tgbehnv€ƒ„ƒzzzvv|ƒ††ˆˆ†}zwyz{|}|{{{}~}|}~~~~~€€ƒ…‡ŠŠ‹‹Š‰‰‰ˆ‡‡…{megq~†ˆ‰‰‰‰‰‡††‡‰Š‹‡…„„„„„„ƒƒ„ˆ‰„~|}}~ztlinrrpqruyyyyyyyyzzzzyyz{|}~~€€~~}}}}}}||}}{{||zzzzzzyz||}}}~€€€€~~~~~€€‚‚‚‚ƒƒƒƒ„„ƒƒ‚‚„„„‚ƒƒƒ†Š‹Š‹ŠŠŠŠŽ“—–“‹…„ˆŒ‹’•”““”—›˜ˆ‡ŠŽ‰|vy}€ˆ–œ˜——™šš™”””˜™š™——”““““’‘ŽŽ’•––”””“‹†„ƒƒ„‡ˆ†xhhnkhnv~‚ƒ€~zxxxy{‚‚ƒ„„}{z~|||{{||}~}||~€€€€€€‚„‡Š‹‹‹‰‰‰ŠŠŠˆˆŠ€pgdis‚ˆ‰Š‰ˆ‰‡…†‡ŠŒŠ†„„„„„„„ƒ…‡ˆˆƒ‚}tklnprtuwx{xxxxyyyyzzzzzzz{|}}}~~~~}}}}}}||||{{{{zzzzzzyz||}}}~€€‚‚‚‚‚‚‚‚}}}|}}~~€€‚‚‚‚ƒƒƒƒƒƒƒƒƒƒ„„„„„‚ƒƒƒ‰‘Š‡‹Š†ƒˆ‚€†—›š”ˆ‚‡’’“•”““”—šš”ŽŠ‹‹Œˆ€tpvˆ“œš—šœœ›š˜—™••žŸ™•””””•”’“””““”“’’’ŒŠ†ƒ‚„†ˆˆ„yqrusonr{€|{yuuwwuttrrv|~~||}|}||||}~}|}€€€€€€~‚„ˆŒŒ‹ŠŠŠŠŠˆ‡ˆ…yjcenz„‰ŠŠ‰‰‡…†ˆŒŒŠ†„„„„„„„„„ˆ‹Š„€€€{pkmosvy{|~€xxxxxxyyyyzz{{||}}~~}~~~}}||||||{{zz{yzzzz{|{{|||}€‚‚‚‚‚‚‚~|{{|{{{||~€€€‚‚‚‚ƒƒƒƒƒƒ……ƒƒƒƒ„„„„ƒƒ…‹‘ŽŠ†ŒŠŠ‰ˆ‚~„˜™™•‘Š‚‹’‘’•”•—˜››˜‘ŒŠŒŒˆƒztnr{‹šžœœŸŸŸžš˜—Ÿ ¡ ˜•“’’”•–—••”““””““’‰‡††ˆ‰‰‡{|{wsqt|ƒ„€|xwsnqvvtpmqx}|}}|}}|{}}}}€€€€€€€€‚…‰Œ‹ŠŠŠŠ‰‰‰Š‡pgcjx‚‰Š‹Š‰†…†‰ŒŒŠ†„„„………ƒ„„‰Œ‰…€€‚vljlpsvz}~€€xxxxxxyyyyzz{{||}~~~}~~~}}||||||{{||{yzzzz{||||||}~€‚‚‚‚‚‚‚~~|||{z{{||~€€€‚‚‚‚ƒƒƒƒƒƒƒƒƒƒƒƒ„„„„ƒƒ…‹ŽŒŒŽŒŠ‰‰…ƒŒ”–•“‘‹‚‰’‘‹“•–—˜šš˜“ŽŒŒŠ†€{tt‘œ¢¢žžœ˜–—”šŸœ™–’‘’•–—˜—––––—–”‘ŽŒŠˆ‰Š‹‹Š†ƒ€{{{~‚ƒ}{{wrlknsuusqqt{„ƒzxxz|||{}}}}€€€€€}}}~…‰‹‹ŠŠŠŠ‰‰‰ŠŠ„znikxƒˆ‹‹Š‰†…†‰ŒŒŠ†„„„………ƒ…‡Š‹‰…‚‚€wlkmosw{~~€€wwwwwwxxyyzzzz{|~~~~~~}}}}}||}{zyzzzz{{zzzzz{||}~~€€~}{{{{{{|}}~~€‚ƒƒƒƒƒƒƒƒƒƒƒƒ„„„ƒ‚‚…Œ’ŽŽ‹…ƒˆ“’…‚ˆŽŽ‹ŒŽ••———˜˜•‘Ž‹Š‡€|yy~‰•›Ÿžžš˜˜˜–˜›š™•”””•–••••–•—˜š›š˜’ŽŽŽŽŽ‘‘‹‡ƒ‚ƒ„…ƒ€€€|qeampsvxywuv|„‡ƒzxyz||z{{€€€~|{||„ˆ‹ŒŒ‹‹ŠŠŠŠŠ‰„ypnvˆŒ‹Š‰†„‡‰ŽŒ‰†„……†††……‡‰‹ˆ…ƒxnlnprvz€€wwwwwwxxyyzzzzz{||~~~}}}}}||}{zyzzzzzzzzzzz{||}~€€€~}{{{zz{|}}~~€‚‚ƒƒƒƒƒƒƒƒƒƒƒƒ„„„ƒ‚‚†Ž’‘‘‘‘‘Žˆƒ…Œˆ†ˆ‹Š‰‹Œ“–—˜˜˜™•’‘‘ŒŒˆ}‚„‹“˜˜˜™—••–•”•—–••––––˜˜––••˜˜š•˜š”ŽŽŽ‘”•”‘Š…„…‡†„€€€~yqbOViotwvusqv€‡‡ƒ…‡‡„€~{z|{}}||zzxwx{}€„ˆŽ‹ŒŒ‹‹ŠŠŠŠŠ‰‡€vptˆŒ‹Š‰†„‡‹ŽŒ‰†„……‡‡‡†…†‰Š‰…€ymkpruy|‚‚‚xxwwxxyxyyzzzz{{||}}~}~~~~}}}}}}||{{{yywwwxyzzz{||~€€‚€€~}}{zz{{z{|}}€‚‚‚‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚…Ž“’“‘‘‘”“’‹…„ŠŒ‰‡‰ŠŠ‰‰‹Œ”—™™™˜—”’Œˆ}…ƒƒ†‘”•––““““••••”•–˜™›™™———–˜š›œœ˜•“’ŽŽ‘”——–”Ž‹‹‰‰‡ƒ€€€|ylU73Whptvwwst|†‰‰‰‰ˆŠ‹‹‡‚|zz{zwuttutuw|€†ŠŽŒŒ‹ŠŠŠŠŠŠŠ‰wpt~ˆŒŒ‹‰†„ˆ‹‹‰…†…†††‡…†ˆ‹ŒŠ…{nlpsuy~„…ƒƒƒƒƒ‚‚ƒxxxxxxyxyyzzzz{{{{}}~}~~~~}}}}}}||{{|{zxxxxyzzz{{{~€€€€€~}{zz{{z{|}}€‚‚‚‚ƒƒ‚‚‚‚‚‚‚‚‚‚‚‚‚…Ž–•””““””’Žˆ„‡‹Ž‹ˆ‰ŠŠŠŠŠ‹ŒŽ‘•—™™——’Ž‹†‚‚ƒ„ƒ†‰’“’‘‘“––—•••–˜œ›ž›œœ™šš›œ›™™––•’‘’”–˜—––“Œ‹ŠŠŽ‰„‚}ucC,/Vknnrwyupv€‚}x{~ƒƒ†‚€}zyvutux~„‹Ž‘ŒŒ‹ŠŠŠŠŠŠŠ‰wsv‰Œ‹‰††‰Šˆ…††‡ˆˆ‡…†‰Œ‹ˆ~smqtuz€ƒ†„ƒƒƒƒƒ‚ƒxxzzyyxxxyyz{{}}}}}}~~~~~~}}~~}}}}|zzz{yxxwwyyzz{{}~€€€€€~}}|{{{z{{|}~~€€€‚‚‚‚‚‚ƒƒƒ‚‚‚‚‚‚‚‚„Ž–———••––”‘Žˆ„†ŠŒŽŠŠˆ‰‹‹‹‹‹ŒŽ‘“–˜™•ŒŽŽ‹‰„‚‚„…††‹Ž‘’•——••”–˜›™›¡ž˜Ÿ‘—™š™™——••••—˜—˜˜—–“‘‹‰ˆ‰‹‘Œ†€taJGMNRWPMcq}†‰‹‹‰†‚€ƒ†‰‹‘’’’’”””’‹~ustvx}~~‚‘“”””””””••’Žˆ}wxy{‡‹‹„{qmnty|wwvvwwwwwwwwwwyyxxyyzzzzzyzz{{||}}}}}}}}}}}{{{{{||}}{{}}}}}}~|zzyyy||||{{{{{yxvspkdcenswxuplkjgcjw€ytuŒ’“‹‚~~„ˆˆˆ‰‰‡‡ˆ‹‡……†ˆŠ‰Š„vfXYs~‚„„ƒ„……†…ƒ‚‚‚‚‚ƒ„……‡ˆˆŠŽ‘‘‘““‘Žˆ€srv|ƒ‡†‡†ˆ…‚yqoppljijjmtvvnieb`_`fs}ƒ„„‚xmhebcmzzrkd`]YVZ\ZYYTY_H,@_t„‹Šˆ…€€ƒ„‡ŠŽ’’‘’”””’ˆyppruz|}~„‘’“””””“•••’…|wxy|‚ˆŠˆ}toonu}uuvvwwwwwwwwwwwwxxyyzzzzzxyy{{||}}}}}}|||||}}}||{{{{}}}}}}€€~zwvuux{{{|{{{{{{zvsolfcbhnrwvtpkjgcet}~xvsvŒ’“Ž‡}†‡‡‰‰‰‰‹‘‘Œˆ‚~}{|zujaTN[y€„„ƒ„………ƒƒ€‚‚‚ƒƒƒ…†‡‰Ž‘‘‘ŽŠƒxqssy‚…†„~wustvwupnklswwvrnjfffkv‚ƒƒƒ}rlfbfszxsked`^]^cfe_^_Y=,1ATi|„†ƒ„…‰ŒŽ’’’”••”‘„xqqruz|}~„‘’“””””“•••Œƒyvyz~ƒˆŠƒxqpqu|‚ttvvwwwwvvxxwwvvwwyyzzzzzzyyz{||||{|}{}|}}}}}}||||||}}}}}~~€~~~}{wsqsvz|~|}||{z{yyvrmgbcjosuvtroljdaiqropsvƒ’’Š‚}…‡ˆˆˆˆŠŽ“””’‹ƒyoinqlf`TKLc{‚„„„„…ƒƒƒ‚‚‚ƒ„†‡ˆ‹ŒŒŽ‹Š‰‰‡‡ˆˆ‰„vnnruy|~€‚€|yxtu}‚€vpnoquvuutqpnnqv~‚ƒƒƒ{rld_cpwwtoiccbbcgjlkd[M:8FOZpƒˆˆˆ‹‹ˆˆŽŽ’’’“••”€rooovz~}€‡’‘’“””””””•“‰zwxy†‹…ztpqrx‚‡„vvvvvvwwxxxxwwxxyyyyzzzzzzzz{|{{||~~}|}|}}|{||}}||||}}}}}~~€€€|zwsqqsw{|~~~~|{zyxvrmecfnqvxxwvpmkf_biiimrxƒ’’‹……‡ˆˆ‡ˆ‹––—“Ž‡}nggkid^VOGMl|………„………„ƒ‚‚ƒ„‡ˆˆ‡††……ƒ„„ƒ„…ƒ‚{tqruyzyyzywyx{€‚…ƒzqorvyxwwwvsqqtzƒƒƒ}smfbht{{uqjhhgeddccb^TD:?Xw‡‘‘Œ‰„‚ƒ‡‹Ž’’’“““”~rqoov|}€‰’‘’“””””””•“ˆyyyz€†…}ursuwˆ‰ƒuuvvuuuvwwxxxxxxyyyyyyyz||||{|||~}~~||}||||{{{{{{{|||||||}€€€~{wrqqty}~€|{wvttqoiefjpuvxyxvtqokghjmlosz…’‘Š„‡ˆˆˆŠ”˜™—“’‹ƒulihhc[RNHEPu……………„…ƒƒ‚€€€€€€ƒ„†‡††ˆ†……„„…†„‚ƒƒzvruxzyz{yyzx|…‡‚zspsz|zywwxvssw}€‚ƒ…ƒ|tkednx|ytqmllhbbcb_a`TKHPi†“˜™–ˆxuxxwz…Œ‘‘’““””{spoqx~~‹“’“”””””•••“…~{zy{ƒvrquvzƒŒ‰ƒuuvvuuuvwwxxxxxxwwyyyyyz{{{{{|||}|||||}|{|{{{{{{{{{{||||~€€€~{wrqprx{|{vtokighihfeinsvwvxxyxurprtuwqnqx‚‘’”’Œ„„‡ˆˆŠŒ‘•™š™”“Š€vnhdb^WPKCCXy†………„…ƒƒ‚ƒ‚„……†‡‡‡‡‡†……†…„„„‚}wtwyy{{||{z}ƒ…‚ysqv}~}|zzyxuvz}‚ƒ„yqkefowysnklmmjebcfcbaZSPXp‹™Ÿž€zzwtrnlv‡‘‘’““””ˆxqppry~|€‹“’“”””””••”‘‹„|zzy{ytrsw{Š‹†€uuuuuuuvwwwxxxwwxxxxxxyyzzyyz{{z{{{{{{|z{{z|||{{{{||{}~~}~~~~|ytnnov{zwqid`_^\]`chntuxxxxy{xwspty||sosx€Œ“””’Ž…€‚ˆŠŠŠ”™š™—”Œ…unjfa[SLG@Db{„…„„„ƒƒƒƒ‚‚ƒƒ‚ƒ‚„„†††††…††††‡†‡‡……„„ƒƒ|vuxyzz~|{}~wppv€‚‚€|zwwy~ƒƒƒuokeht|ztnihjjmgdefdab_YX_tŠš Ÿ‹yuqlfcr‰’‘‘‘’“””“ˆxoppry}}}ƒŒ‘””””””””••“Šƒ{yzx{}ztrsvx€„Œ‰ttuuuuuvwwwxxxwwwwxxxxyyzzyyyzzy{{{{{{|z{{z|||{{{{||{}~~}~~€€~|vqnorxwsjeaceb_^_elrvxxxxxy{ywtqsy|{rnrv€’””•“Œ…„‡‰ŒŒ’–—™˜–’‘ŽŠ‡€|xqh_YQG@>:;:;;88888899989::988:9767755544211100..--++)'))(())((((''(((('())))**++,,**++,,,,,,,,++++*)))))))))))((''&&&&&&&&&&&&&&&&&&''&&&&&&&&&&'''''''(()+,,-/0224557778899999999::9999::8876677:>EJQYahlquwxxwuqmjhhghjlnnoooopopoqstwy|~~~~|xrldYNA4.,)++++,*+++.4:BOZenx~ƒ†…ƒ‚}wmgXH;/)%$$#$%%'*.178>CLS^emt{€€|zuoiaYSORQTTQSLD=60'%#$$""#"""!!#!"! #%&(+07=DKPW^cglrwxyxwuvvuuuok_WKFA??@@@@@@????==:99975543100/039?CFFJNU\bkptxz{}‚‚‚ƒƒ€€~}{yzvttx{€‚ƒ†ˆ‡‡…ƒ~|zxutty€„†…ƒƒƒ€|xpfXJ=4/.-,038=DNZbksx|€‚€ƒƒ‚‚~}{yvwwzz|„ˆŠ‹‹‹ŠŠŠŠˆ‡‡‡ˆˆˆ†€ypdXOHEA><;FIILLLMNNPPPPQRQTSRRSSRRRRQQPONMLJJHHHEAA@=<;<<:8:::::89999:::98888876665544211100..--++*())(())((((''(((('())))**++,,,,++,,,,,,,,++++*)))))))))))((''&&&&&&&&&&&&&&&&&&''&&&&&&&&&&'''''''(()+,-/01124557888899999999::9999::999878;=DKQY_gosyz|||ztqljkklmooppoomlkjjjkkorwz~€~~~wsk`UH<2+*++++++,,+,17?JVamw}…†…„{tk^P?3*&$$$%&(,/258=AEJRZcjr|€€zundZSOMTbpfVNE=6)#$###""#"""!!#""!!"$&+-4=CJPV\belquyz{zwuvvuuuskaVKEB??@@@@@@????>><:::755431000116<;HHIKLLNOOOPPQQQRQSRRRRRRQPPPNMLKJJHGEDA@@>=<;:8:9899899:9999::999988776655332100//..-,,++*(((((((((((((())))))))**,,,,,,,,--,,,,,,,,,,+*++**))))))((''''''''&&&&(''('%'&''&&&&&&''&&''''(((())*,..013345558888999988999999999:99778;>CGOU^bjqv{}}~{xvpnllklmnoommkigeca^adjmrw{€‚‚‚|vqi^QB6.*+***++++++05=EQ_jr{€…ˆ††‚}xncTE6-&$$%&',/148=>BDHLU^eow€„„ƒzsh`VPN]euvo`fys@$!######""""!"""##$$*.39AGMS[`ejpsx{}|xvvuvvvsoj`TMD@????@@????@>==<;::755421/0..28;BMT\hnrux|~}|{vqicVJ>4.+)))((''(((())*+,-01134589=>@AEJMSZ_fkrv{‚ƒƒƒ…†‡‡ˆ†……„†}|zwtrppquvy}ƒƒ………~~{yzz{†‡‡†‚„}yrj\N>51-,,.037;:IJJKMLNOOOPPQQQRSRRRQQPPPPOOMLKKIIGFDCA@?><;;:9977999:::998:::999988776643332100......-,+*))(())(((((((())))))))**,,,,,,,,--,,,,,,,,,,+*++***)))))((('''''''&&&&(((('&'''&&&&&&&''&&''''(((()))+-/01223555468899998899999999;:9977::?DJRY^flsx|~}|yvsnljjihhiffefd`^ZXUUX\aeluz€‚‚‚~|vmbWI;4,*,******++.28ANXcoy€†ˆ‡‡ƒ~}tj[M=0)$$$%+.168;?BDCFKOXdmu~ƒ††‚}wncWOWl`ilpkdw^I+!$####""""!"""#"%(.3;@FMRY_cgnquy}|{yvvvwwwuph`TKDB????@@????>===<;::755411./..27;BJT\fnruxwz~}|wslcZN?81+())((''))(())*+-.013334679>>DINSY_aimtx}€ƒ„††‡ˆ‰ˆˆˆ……ƒ€~|yvtqoooppsuxƒƒ„„ƒ~~}{||~†††…„‚€{vofXI;3//0..025:BMU^gpw|€~~~~|{xvtqmllnrv{‡‰Š‹‹ŒŒŠŠŠŠŠŠ‹‹Š…€wj]RKGEB?<;HIJJLLMNOOOOQQRRRQRRQQPPPOPOLJJJHGGECA@?>=<;:9::888889::::::::99888865544322210000//----,+)))(((((((((((()))))))****,,,,,,,,,,,,,,,,,,,+**++*())*())))((''''''''''''''''''''&&''''''''((((((***+--.1112455678888999999::9899998769:;@ELSY`fmsvy{yvwspkhedd__^^^^_]ZWTRONOSX_fpz€„ƒ„„}xql\OB8/+*****+++,-28?IWamw€‡ŠŠˆ‡ƒ{vm_RB4*%%%(.249<@CDEDFINU_iqz‚†‡„€yri]U\ijqYIRiqcRWG! !$##"""#"""""&(-4;?EKSW]bdintyz||{xwwwwxwsoj_TJD???>>??????>>==<:997543211///06:@KS\ekrvwy{}{zzwqg\OB81-+))((''((')(**+,-/0232479:?DHNRY`dgmrv{‚…†ˆˆŠŠ‰‰ˆˆ†…ƒ€~}xvtplkjjkmot{‚‚ƒƒ€~}}}~‚…††„ƒ‚€zslbRC82/..../26;BMWaiqwz~€~}}{wusrmiiimqv|„ŠŠŠŠ‹‹‰‰‰‰‹‹‹ŠŠ…vgZNHGEC@>=<;;:::99889:::::::::99888876543322100000////--,+*))((((())(((((())))++**++,,,,,,,,,,--,,,,,,,+++++,*))))))))((''''((((''''''''''''''((''''''(((())**++--.0103333567888999999::9899887778:;@EJPYaiouvvvtqolfc_ZYYXXZZXXYYXUOMJGEHPYcjs{‚ƒƒ€}vmcWI<2.-****+++,,07>>>>>>>>>=<;:9975432311../38?IR\fmptwz{{}|zvqi_RG:2.)))((('((*)*++,,-./13369;@FKPUZ_dhmqvz}‚„…‡‡ˆŠŠŠŠ‰‡…ƒ€zxvtokhgeegjms{‚‚‚€~~~€ƒ†‡†„ƒ„€}xri^O@71.-../226;CMXaiquz}~~~~~}{zywsmmkgggjnqx„‡ŠŠŠŠŠ‰‰ˆˆŠ‹‹‹ˆ…~uh[NIGDC@?@KKKMMMMOOOPPPPRSQQPPPPOOPOMMKKIIHFEDBA?>==<;;;998899::::::::::9988887655321111111100/.--,,++******))**(())******++,,-------,,,--,,--,,,,--++,,**++))))(((((((((((('''((((('&&&''('''((((((()**++,,,-/01233466799888888::88986655678:>BGNW_gmqoomkhgb^XWTSRUUWWXXVVUPKFB>1++/479<>BCEFFGGILPX`jr{„…ƒ|ulb^`S[NUTEOSJFDcjdZ%!$$$$$$#$&)038?ELQV[^`eimqstwzzzywvvwxxuqk`RJC?<<==>>>>>>==;;<:7786432210./.37>HQ[clqtvxz}~|zxtkaRG=40,)((((())+++,,,-../146:?EIORW\afinsux~‚…‡‡‡‡ˆ‹‹Š‰ˆ…‚}{wspnkihdbcgint{€‚‚€€€~€€‚…ˆˆ†…„ƒ€}wqeYK=61-..-.148=FOX`iqv|}}}|}|}ywwsnkjgfccejqw~„‡ŠŠŠ‰‰‰‰ˆˆ‹‹Œ‹‰ƒ}teZRJHGDB@?JILLMLPNOOPPPPOPQQPPPPOOOMMMKJJHFDCB@?>====<;;::8899::::::::::9988887655321111111100/.--,,++******))**))))******++---------,----..---,,,--++,,++++))))(((((((((((('''((((('&%&''('''((((((()**++,,,-/1112335568888888899888755455669=AEKS^ehhffedb]ZURSRQRRRVVWWVSPMGA=879AJWaks}€‚…‚‚€|voeYG:2.,*+++*+++,17?KXdq|ƒˆŠˆ‡†‚|umbRA4//269=?BDGFGGHHHKPV[emu~‚„‚|xnc`UNG:C@59:>@CKT]S##$%%$$$&&*.5;?GLQXZ]`cfgkoqsuyyzyvuvuvvuph^QHA=<<<<======<<;:98777543220//..28=FRZckqvuw{}}||{tlbWK<41-)(*****)+++,,,.//258=BEKPTY^chmpsv|~‚„‡‡†‡ˆˆŠŠŠ‰†ƒ€}yuqoljgda_`cgjnty}€€€€€€ƒ‚‚…ˆ‰ˆ†…„€}vncWJ<73.//./158>FO[cjpw{}~}||{zxurojgfcb``dipv~„‡ŠŠŠ‰‰‰‰ˆŠˆ‹Œ‹ˆ…~tgYQKHGDBA@KKNNMMOOOOPPPQQQQOPPPPOOLLMLMJHFECBA???=>=<<<<;;;;::::::;;;;::::8877744432221122220000..-,,,**))******(*)))***++**,,,-----..........----,,,+++++++)))))())((((''))((((((((((((((''((((((((()**++,,+,--021122445677887777776655443568:>CJRY_bdb``^\ZWTRPPQSTTUUVVSPMGC;5124;EP\hqy~ƒ‚ƒ€€}wpj^PA6/+****(**++/7DHNSX^cikosu|}ƒ„„†††ˆ‰‰‰‰‰†„|wtpjhfca^]_cgimou{€~€„ƒ…‡‰Šˆ††ƒƒ€|ulaTF;740/0.026=BHR\dipx|}}~}|{wuqolgd_\\\\aiov}…‰‰Š‰ˆˆ‡‡ˆŠ‹‹Œ‰„|sg\QJIFECA@LLNNMMOOOOPPPQQQRQPPPPOMLLKLJHHFDB@@??>?>=<<<<;;;;::::::;;;;::::8877744432221122110000....,,**********(**))***++**,,,,----............--,,,+++++++***)))))(((())))((((((((((((((''((((((((()**++,,+,--..00112445778877776655443333469=BINUWVWZ[[[ZZVUSSSSTUUWXVUROIC=60--27@KVcmv}‚€ƒ€zsmcVI;2-***+*)*++.29DQ^gt}ƒ‡ˆ‡†ƒ|sk_QC97:=?CEEGGGGIIIIIIINW`jsyvhkpd]][MEBWY(+4<;7=DK'%%%%&(*05:@GLRW\]`bddgeefilpsvvvssutuurmf\PG@<999:;;:;;;;;:9::765342220//./27FLU]emsw|}|}|zwurmjeb_[YYXZ`jmv~…ˆ‰Š‰ˆˆ‡‡‡‰Œ‹ŒŒˆ‚|sg]RLJFEC@>MMNNNNOOOOPPPQQQQQQQOONNMMKHIGDBA@@@????>=<;<<<<<<;;;;::::;;::98997765432233221110.011////-,,,,,++******++++****++++,,----//-.//.-....---,-,++++****)((())))(((()))((((((((((((((((((()))))))***,,++,,--///33454556666664455443211257;?EKOUVUY[[\[ZWWUVVVVWWXWVSOKF=71,*+.3;FP]gqxƒ€~{wpi]PA3.**)**))**,29@KWdny€‡ˆ‡†„|tmeWJB?>@CFGGIIJJJJHHFFEGMWcmw|}skei`ed`VA98541147=DJSYafnuz}~||{{xspjga_ZXSRSV^hmw€†‰Š‰ˆˆ††‡‡‰Š‹‹‰†‚}sh\RMHGDCA@MMNNPPOOOOPPQRQQQQNNMMMMLLJHGEBA@???@@>=>=;<<<<<:9;;;;::::99::98777765432233221110.0111///-,,,++**++****++++****++++,,--..---.//.-....-----,++++++**))))))))(((()))((((((((((((())(((()))))))*****++,,--/0100143556666664444332100146:>DINQUVZ[[\\\YYXXXXWWZXWTRMGA94.*)+-07@KWalu}‚~}ytmdUG:0+*********07=ES`jw~ƒ††…„|woh`TJEBDEGHHIIJJKKHGBBBBEP[gttoiflgegeXJC@G9123,9DHLQUX\^^]^][YYYYXWYXYTSOIB=6.+(()*.4;FR^hqy€~}{vph\N@4-*)**)****.6:BP\hq|‚………ƒ}{undZQKFFFHHIIIJJJIGD><;=@JVctihigcfd`^B3LSX<@RSD<:CHXfU<$,08=BEKQW[]dfeee`\\WUVY]djqtvtsstrplibWJ@;6666788887777666654331//10//-/14:CMV_gotwwy{{{{|yslaVJ=740/////./025:@EKQW\`ekmrty{~~~~~€€‚ƒƒ„ƒ„„ƒ„ƒ‚€zvqh_YVVY\_cfikmnoqtw{{{{{}‚„ˆŠ‹ŒŒ‹‰††‡‡ƒ{tj^RIA<;:888;>ELSY`fmtz|~}}|{wtolc]VSOMIHLQZemx‚‡ŠŠ‰ˆ††‡†ˆ‰‰‰ˆ‡„~yqj_TNHECA?>MMOOPPPOOOOOPPOONNNMLKKJIGFDBA@?@@????@@?>=<====<<;;:::::::99:877766654444333223222211000/.-,,--,,++++,,,+****++++,,--.........///....-,---,,,++++*+**))))))))(((())))))(((((((((((((()))')))***++++++,,..-./0101222333322321110./2569?EJNTXZ\__``^\[[[[YXY[XVQLE=6/*))))),07ALXbnu}€€~~}ysmcUI90*)))()****07>IWdnw~ƒ„„ƒ|wqkaXPIGGHHIJJJJKJE@<969;ES^pookie`]YSMIJLS``__XKA8+;Rnn5(7=;;>@CJQX]ejqv{~~}}}{vrmf`YRMJECCHQ\eq|„ˆ‡ˆ‰ˆ†††‡ˆ‰‰‰†…‚~wsi_TMGDB@@>NNOOOONNOOONPPQPNNLLJJIHGFCA@@@@?>?????>??>===<<<<<<::;;::;;:9887776565233443344332221110//--,..--,,,,,+******++++,,--........00//....,---,,,,,,*,+*))))))))))))))(())(((((((((())(((())**))))*)****+++++,,-/////0111122122110/.01149<@GLRVZ^^_`aa`^]]][YY[ZXSNGB:3+()))))*.3;FS^itz~{wog[N>3*)))******/5ILcmhcmwJOKTZ[YWJIKNV`iquwuwtssqkh^SF<643344554444555553221200/.,.././4:CLU^gnswwzxyz{||todZQC;663222327:@FMUY`ekotwz|€€~~}}~€€‚‚‚€€}}{xtphd[YZZ]afilooqrrtvy|}|||{ƒ…‰Š‹‰‡‡††ƒ{rj\PIDBAA?AAFIQV]bhouy}~~}{xtpjc[SLFC?=?DO[gs~„ˆˆˆ‰‡……†‡ˆ‰ˆ‡„‚}yskaUMEBA?>>NNOOOONNOOOOPPONLLKKJJHGDB@@@>>>>>?????>???===<<<<<<::;;::9988887776565565334455332221110///.-....--,,,+++****++++,,--........00//...-,---,,,,,,,,,*))))))))))))))(())(((((((((())(((())**)))))())))++++*+,---.//011111101100//.0126:=CGOTY\^`abcca___\[[ZZZUNKC=5.*'(((((),07AMXdnv}€}~}yslaTE7-*))))****-39BM[fq{€‚„‚€}zupi`WPMJJIKKKJIFA=84/-,1;QirsmM]aWUONPMGBNVb`XUX]T=9BT^qZLO[m~w€jh[Ybmpwtd\P@AJT_ksuwvvtssqkh^SF<6422332222333333432210/-..,--,,-38@HS\hoswxxyyz{{{tni]SG>8765556:?DJOW^diotxz€‚€~}||}}~~}}~zywsqngb\YY]acglmprrrsux{}}zz{|ƒˆŠŽŒ‡‡‡††ƒ{rj\RNHFFFEGIKOW^cflrx|~~~}{{ytmf^XOGA=9:>>OONNOOOOPPOPNMNLKJIHFFFDBB@@??>>==>>??@@>=>>====<<<<;:<<;;;;888777665555555554554432222210000./0.--,,-,,,,,,+***++,,------//0001....-,--..---,,,,,++****))(((((((((())**))(())(())(((())))))))))**++++*))*---....//0111100///0./0137=AFLPVZ^`bdeddabaa_\[ZYYTOF?9/*('('&&''*.5:41+**.9Pq~xe`\GYYUQRNLMHQVYLL\_KGA*FL]TO[nto€smhmlepz‚qj\F=KUalswwxxvtsqng]PD:310011112211100111/0/----,-,+,,028>GR\enrvwwy{{{zzwrk`VH@<96689;BGLS[ahmru}‚ƒƒƒ~|}}|}~}€~}}zzywurpmgb]\]_beinoqrrstvy||}zz{}ƒˆ‹Ž‘Šˆˆˆ††ƒ|rh]TPMKLLKLNRX^beknsx}~|}||ywrle]QH@:777>GSbny‡‰Šˆ‡†††††‰ˆ…„ƒ{wslbVLFA?>:9OONNOOOOOOMNMLKJJIIHFCCA??====>>>>>>??@@>=>>==>>==<<<;<<;;;;988766665555555565554444442211100/0/..--.,-,,,,,+***++,,-----.//0000....----...---,,,,++****))))(((((((())))))(())(())(((())))))))))**((((**)*,,-....//00000////--/01459?CINTZ^`cefffeda``_[ZZZWQLB<3-*('('&&&')-18CMYcmw|~€}||xql_SD6-**(())***.49DR_jr{~€‚€~{yupjaXQMLLLLKGC>:5/+('+/5Ts||{o_VHFVVQU[RKT\[jRIZdeZGBFKOJXf_lmqo€{lgl_[pfgKU@HWdnswwxxxwvsng]PD:20//000000//000000/..-,,,,++*+-.17>GQ[entxxxxyz|zzwqkbXNE?=98<@DHOW^eipty~‚ƒ…‚‚€€|{{z{}|~~~€~|zyxvvutsrpnhc__`adgjnpqrsvvxz}|{zz{}ƒˆ‹ŽŽ‰‡ˆˆˆ†ƒ{sj_XTSPPPQRVZ\bfjoqvz~~}|||yvnh`YND<8447>IVdpz‚‡ˆˆˆ‡…††ˆ‰‰‡„„‚}yxqlbVLE@>=:9ONONOONNMLKMLKJHIHEDCB@?;;<<:<<<==>>>>?@>>>>====??==<<<<<;;::877777766667754576655554443211100100/.-,-----,,,+++++++,,--.//////....---.....-,,-,++********(*((''(((())))(((('((())))))))(((((())))))**)))))*++,,.///0/00..//.../166=AGLRX]`cefgijgfca_[ZZXWVPF=8/*('(('&&&''+.5=IT^iqy|~{||}xpe[J;0+)(())**)+28BLXdow}|{{zvsne[TNLJLJGD@;50)($"'+6Ybtwxo`SLJZ[TUa_STZ\^]ST^ldTJM^NQ`ikurfko[IGTV]RXSQRMKVdntwwwvwwvskh\PC92.----.-..////.....-..,,,,,,*+-/17>GPZenquxxxxy|{{xsng\OGB?<=BHMRWaglrw|€„ƒƒ‚ƒ€}}zzyyyz{{{{|{zwrsssrsrpnifb``deiloqrsuvwz|}|{zz{}€„‰ŒŽŠˆ‰‰‡†ƒ{slb\XXWUUVX[^agloruy|~~}|{{ytnf^SJ@83137@LXgs}„‡‰ˆˆ‡†…†ˆˆˆ‡ƒƒ}|xurkbVLC?<:99ONNNMMMMLKKKJIGFFEECA>>=;;:::;<<==>>>>?@>>>>====>>==<<<<<<;::99977776666777788665555444322211110//..------,,,+++++++,,--.../////...---.....-,,-,++********))(((((((())))((((''(())))))))((((((**))))**)))))*++,,+-/./...--//./.137;BGLRV]_cfgikkjgfca_][[YWRIC;4-(''(&'('&'((,19BNXcmvz|~{||{wrj`PB5,)(())**)*/5GPZdmquxxxxwzzzwtoh^RJE@CFKPW\ahmtx}„…ƒ‚‚€~~}}zzxxwyzzzzzxwusqqqqrrpokfbbcdfiloqrsvvwz}}|{zz{}…‰Œ‹‰‡‡‡†„‚{slb^ZYXXXZ[]`gkprsx}~~~}|{{ytle[QE;52239BP_jv†ˆ‰ˆˆ‡††‡ˆˆˆ‡ƒ‚{xxvslcVLC?;999OOONMMNLKKJIHGFFDCB@=<<;77998:<<>=>>>>??>>??>>>>==>><<>>=;;;9887777766665678666646555554322102220/.//,-,------++,,++----../0....///.....-/-,--,+****))))**))))**(())))))))(*))(())))))))(((((())))))++++++**+++,----....../000227;?FLQU\`acgjjmmigfca^[[ZWSOG=70+((('&&'&&())*/4FPZclqwwxxxxz{{xvpi`VNGFHNRY]dipuzƒ†…„€€€€}}{yvvuttuuwvvvwurqqpqqrsqokhdccdgiloqrsvvxz|}}{zz{~†Œ‘‹‰‰ˆŠˆ‡†}vkda^\[\\_abeinsww{}}}}|{{zwrleYNB94224;:::978999;;;;=>>????>>==>>>>==>>====;<;;:9887777666666778966675555543331121100010---------,,--,,----....-...///.......-,,,,+****))(())**))**))))))))))))))(())))))))(((((())))))++++,,**++++++--....../00237;@EKQVZadeikkllijifc`^[YXVPI@91-*)(('&&'&'&&()-27ANWbnuz{|{{}{wqi^N?4+*())))))*05=JXalt{|}~}{{|zuof_UNJFC=72,'#!$""#>`^RV_^\URRUZ\]T`\LKZ_^gSJ[c`\VFWKRg_dcgeeedQ4:HECB=/9CP[hqwxxxxxxvtnj_PA70****)*+**++,***))))))))(()**+,.27=EQ[cjruwxxxxx{{yupjbXQMLRV\`flsw}ƒ„ƒƒ‚€€|zywtrpoprtsrrtvutppopqrrrmjebbcdgiloqrswwxz}|{{zz|}†ŒŽ‹‰‰ˆ‡ˆ‰‡ƒ}vkdb_][^^acfjorvy|}€}{{{{yvpkaUJ=73128>KXds}„‡ŠŠˆ‡†…†‡ˆˆ‡…~zyxwvtneXLD=:89:OOMMMMKIHGGGEDDA>=;:9887678:;;;;;<====>=>>>>>>>><>=====<<<;;::9877776677777777888876545543332211011//..-----,,------------...-./../.----..-,++,+**))(())(&(&)(**))))))))))))))(())(((())(((((((()))***++++++++++++----....0022894+)$!!#! /eg[FLd]VSQUURDN_iRBNXTW^VY[[XUUSPBHW]h^fcfk^<2EC=DE71:CQ\hqvxxxxvvvtoi^O@6.)''''())))(*)***)((((%%'()**+--16>ENWbkqtvxxxyz{{xvrnc[VSUX^dhms{~„ƒƒ€~~}ywwurnmllmklmpstrqonoqrrqrliec`befhlnoruwwxz}{{zzy{}‚‡ŒŽ‹‰ˆ‰‰ˆŠˆƒ}vleb_^^_`cfhlotvy|€}}yzzyvtmg^OD;6433:@O]iv€‡‰‹‰ˆ‡†‡‡‡ˆˆ…ƒ{wywvvtogYMC=989>OOMMLKIHGFEECBB?;:998787678:;;;;;<=====>>>>>>>====<<==>>==<<:;:9888877777777778866787655554333331110//..--++,,------------....//../.----..-,++,+**))(())()**)&))))))*)))))))))(())(((())(((((((()))***++++++++++++,,--.../0158EQWbkqtvxwwwy{{{wunia]\]`glpsw}‚ƒƒ„€~}|zyutpokjiihjkmpstrqonoqrrrpkhca`bbdgjlmotwwy{|{zzzy}~ƒˆŒŽ‹‰ˆˆ‰ŠŠˆƒ}vledbaaabehkosvz{}~~}|yzzyvsleYME;6435>>>>>==>>==>><<====;;:9999788889999886788766665443333332100..--,,,,,,,,------........///....--.-,,,,++))))))))*/28-')))))**))))))))(((())))(((((((((()))*+*+,,*++,,++++,,,-//00159>DIKQW[_ceikmoppprmhea_\ZXWSLC<5/)('''''''&''&&(''*/56/,&# !! !#Ohmhcab`_SPSX\ZdTEC=DLP[`VPV?BEBEFHPQPOZW\_TIOU\O,.49CP\hquvvuuuuvtnh]N@4,('&((%%''&&))(()&%%%%&&&'(*,.1148?FQWaiosvxuuwxyzzyvsmgdcdhlrv{ƒƒ‚€€€}}{zxuuqomkgdcfhjmorttrqmopqrqpmkgeaabbcfiimoquyyzzzzzyy|ƒˆŒŽŠˆ‡‡‰ŠŠ‰…}vkffdccdfjkmouwy{}}}}|z{{zwqkcXMA95237?IVdo{…‹‹‹‰‡‡†‡‰‰ˆ‡…€|xuuuwvtph[OB<7:=EMLKJJHGFECCB@=9966655444666678:::9:<<<<<==>>>>>>>>>>>>>>=<====;;::999888889999998777776665544333332100/---,,--++,,,,,,--........///....----,,,,+)))))))))*6CC4'())))**))))))))))(())((''(((((((())*++++,,*++,,++++,,,-//0258>CGMRV[`dfikmoqqppokfc`][XUTOF?81-*('''''''&''&&''')-28CR\fpuy||{||yuodVF6,((('''''),3;CQ]juy|zum_QF<3-*%"!!! !5Wmy\^ba^YXJKPZb[]YMBHFGKYWPPYC4:KORJHHFJLPPPLN\hVO)-5:DO\grvttuutvurmi]N@4+'&%%%%%&&&&()(('&%%&&&&()*,.03459?FQXajosvxuuwxy{{zxvrmjiiorw|€ƒ„‚‚~}}zxwuspplieaabfhknqrttqpnopqqpomiebaabccefhjnruyxz{{{zyy|ƒˆŽŽŠ‰‰‡ˆ‰‰‡ƒ}vkffddddfilnsuwy{{{zzyyz{yvpjbWI>85348@M\iu‡‹‹‹‰‡‡†‡‰‰‰ˆ„yvtttuvtog\QE<9=CJONLIGFFEDA?=<9666654434665557788::;<<<==>>====>>??>>>>??>>==<<<;::::::::99::::998899757776555555332200..--++++*+++++,,--------../..////.---,,,,+**))))**))2:9-)((((((((())))))(((()'(((((((())((+++,,+,-,,+,,,,,,,,.,..0237TJXdUA=O@LTSPNQ:;HMKHHNJUY\^behhaJBKFDBMNLYfVW+,2:EQ]gqssrrrstusni^OA4,(%%$$$$$$&&&'(('&%%'''()+-./1247;@FOV`hosuwuwxx{}}|yxvsqqrwy~„ƒƒ€€‚€~|{{xwuqpnkgc_acfhjlpsstsqpoooopnmkgca_``_acefhlqtx{{{|zwxy}€„‰ŽŽŠˆˆ‰ŠŠ‰ˆ…|tlgfeeeefjknqrtyz{{zyyyxxxsmg`UG=9767;DR`lx†‹‹‹ˆ‡‡ˆ‰ŠŠ‰ˆƒxussttttqg]QE=>BGNLKJGGFDCB@>;97553333323455667988::9:;;==>>==>>??????@>??>>==<<<<;:;;<;::::::::998876778876666655332210..--++++*+++,,,,--------../..////.---,,,++**))))))))**++)((((((((()))))))(&)()))(((((())))+++,,+,-,,+,----,,,././047:@EIPV[`dgilnqrrropnlhda][YVRLE<6/+*))('&&''&''''+5=ICEB^NCP[\J(+3CIOXaiosvxxwvxy{}~}{zwwwy}€ƒ„„ƒƒ€~|{zxusoolgb_^_acgjlorssutqpoooopnokeba_^]_``ddglqtwxy{|zwxy~†ŠŽŽŽŒ‹ˆˆˆˆˆŠŠˆ‚{rlgeeeefgghmpqsvxzzyxyyxxvslf_QG=:99:=FTbmyƒŠ‹‹‹ˆˆ‡ˆ‰ŠŠ‰ˆƒxussttttqg]QD>AHLPLLIGFECA@><965432212223344557888999:;;<=>>>>??????@@?????>?>====<:;;<<:::::9::99998677777568774433211/---,,+++,,,,----------.......////.--,,,+***)))((((((+5B<,'''(((((((((((()((((((((((((())++++++,++,,,,,------./..0159>DHNRY_behklopssoommkgb_\YXSOI@81,*))*''('''''''&+4-'''+.7COZhpxz|zz||~yqfWE7,('&'&'(,/49BKUaksz}€„}vl_O;-$$###$#%>>>??????@@????@@?>====;<<;;;;::::9::99997679887877775432210.---+++++++++,,,,------......./---,--,,++**))))(((())'61**'((((((((((((((()(((((((((()))*+++++++++,,,,,----..////137:AFMSX\`dijmnprqqpnllhd`][XWQKC;5.+)(()))'&''''&&'%$%'('*.5?LVcowz|{zz||ztl\K6;58AQbioqhP=F>46CGFPNMNVY[^`abb`]XTR5EGGBNaMJU]K.16=FR]iqsttssttttlh]OA5-)%%%%$$%%%$%%%%%%%&'*+.002457:<<;;?FQ^iv€ˆ‹‹Š‰ˆˆˆ‰Š‹‰Š‡€zwtsssttsqiaUKFJPVZKIGEEC@;9865422100////11333356897889;;=>=>????@@@@A@@@AA@@?>=====<<<:;<:;<;:::99:97788888877775533210.--,,++++****,,,,,,----....,./.---,--,,****))))((((**'&(((())((((''''(((((()'((')))******+++++++++,,,----....000036:>DJLT[_aeijmonooonkjjfb^\[XTOG?70+*)))*2/((''''''&&&%$&#(-1;HUamuy||zz|{zun`P?2*'%&()*/39=AIS]fov}‚‚ƒƒ‚{tiZF6,&$##%(([kX<6BACDKZTPLL9117>HR^jrtttusttsrli_QB5-)%'''%%&&%%%%&&%&''(,..01356:<>@CHMR\bhosvxyxxy{~„‡ˆˆˆ‰‰‰ˆ‡……„ƒ„€}|yvtpliidb`aabcefilnqstutrqooooponlieb_][[Z[[]]^bglrvz{|}zwz}†‰‹ŽŽŒ‰‡‡‡ˆˆˆ…‚~vnhdcb_`\\`ehknsuuuuuusuuusoj`YOD?>>?EKVbmvˆŒŠ‰‰‰‡‡‰ŠŠ‹ˆ„yvsrrttvurleVOJMT[_HHFCA?<98655222100////01213356777889;;=>=>????@@@@A@@@AAAA?>>>====<<<;;;<<;:::99999999888877775544320.--,,+++++**+,,,,,,----....,./.--.---,,****))))(((((())''((((''(('''''((((()'((')))******+++++++++,,,--..//..000136:AFLSW\`cgikmnlljjiihhc_\[[VSLC;4-**)))**)((''''''%&&&$&%(+.7CQ]hry|}{{||{vofWD3)'%&(*.26:>CJR\elu{€‚ƒƒ|ypaO>0'###$%)DKF8??=>JZdoswr[TNA>ABMKEPTYZ[^``a_\XTMGB:8MB@GUZZVE,247>JT^jrtttussssrli^PB7/+('''%%&&&&%%&&&'(*++.03457:;>@BEKQT\bhosvxwwvw}€„‡ŠŒ‹‰‡†…„ƒ‚€|{vsokhfb_`bbaceehklpqstutrqoonnomnlieb^\ZYYYXYZ\`glrvz{}{xwz}†‰‹ŽŒŠ‰‡‡‡ˆˆˆ…€zslgdb`^^\\`eiloqttssssstvsrnj`XNGC@?BIO[dpzƒ‰‹Š‰‰‰‰‰Š‹‹‹ˆ„yurrsttuusmd[SOQY`dHEDB@=984553101100..../11123566699::;;==>>????@@AAAA?A@@A@???>==<<======<<;:::::::999977997766555310..-,++++++**++++++---.--+--/--,,--..,,++**))))((('''((((''''''''''''''''''''(((((())**+++,,,,,,,--,----.0300../00047799FFPRW[]^_a`^[WRLF@852TC;GPYX\@0269@KVaksuuttstttrlh_QA60,)('''%''&&&$&&'(*,-.0357:;==BDFHOSX^djosvwyyy{„†Š‘‘ŽŒ‰ˆ‡…‚~}ytqnjhc_^`_aeefghilkoqrtttsqoonmnmlieb^[[YXYXWWY\`flqvy|}{{{z~†ˆ‹ŽŒŠˆ†‡‡‰‰…‚}wqjfc_^^\]^afinprtsssrrstttrog`VNHECCFKU_gr{…‹‹‹Š‰‰‰‰Š‹‰‹‰…~zvtstuuutrke_WVX^diGEC@=9864332200000..../11123566699::;;==>>????@@AAAAABBBA@???>==<<======<<;;::::9999::77887766553210..-,++++++**++++++,,-.---./---------,,++**))))((('''''((''''''''''''''''''''(((((())**,++,,,,,,,--,---../0..../11469?DKPTX[_cghiihffeedca`^^\[XSNE=4.*)))))((((('&''&&&&&&&&&'+.2@EKOWahpx~‚‚‚ƒƒ~wn_N=/'##$%&BuwD&)3:HTcpvxunK;./27.;VYZ]^`aa][VRLFA93.-7?EQOOL_M955:AJUbluwvuuuututlh_QA60,)(''''''''&'&'(*,-.03579<=?ADFHJQUZ`djosuvxz{€…ˆŒŽ’““’‘‰ˆ‡„€~~yvrliea`]^`_accfghillopqsusqpppnmnmkgea]ZYWVWTSTTY\djqw{||zyyz~†ˆ‹ŽŒŠˆ†‡ˆˆˆ…€zupfc`^]\]]^afjlprsrppooprrrpme]TMJGFFJNXblv€ˆ‹‹Š‰‰‰‰‹Š‹ŠŠˆ„}yvtstuvuusnja[Y\`fkFCA=:86743111./0//.....011344568::99;;==>>????@BAA@@BBAA@@????>>========<<;;;;99:998998877666644111/---,++**+++++++++++++-----,.--------,*)**)*)))((('((((''&&&&''''&&&&&&&&&&&&''(('(**+*++,,,,,,-----.--..--...//0368;@HNSTY\_behecbaabb``__`^^[WRKA:2,*****))((((''''''&&&&&&%%).3:DR`lswzzzyy{ztmcSC4+**-257>???@@BAABBBBAA@@??????========<<;;;;;;:998998877666644111/---,++**+++++++++++++,----.---------,*)**)*)))((('((((''&&&&''''&&&&&&&&&&&&''(('(**+*++,,,,,,-----..../..///001269>DINSW[^^bedb`_][\\]]_```][VOF>6.******)))(((''''''&&&&&&%%(+28BO\iqvy{zxxxzvogXF8/--/38;>@DGJMT\dlv}€‚ƒ}vl]J8+&#"" 3L="&/48DRantwxZ=$21?MUZ[[_`a`]YVQLE?80,-/20.?MQRTMII65:AMZcmuwwwvuwwtspi`RB91-**))(((((())),,-013468:;?ACEJMORW[_cglquvwy{~ƒŠŽ“–—˜—–“’Š‡…}ytnjgc`\[\^``acfffgjklnppsvusqqooolljfd`][WVUSRQPQRS[aiouy|ywxxz~‚‡‰ŒŒŠ‰‰ˆ‰‡†ƒ}vpga_^]\ZZ\_dhjmopppnnnorsttqkf^VPNLKMRXbkt}„ˆ‹‹‰ˆ‰‰‰Š‰ˆ‡‡…}xvtuuvvwwurngaabgmr@=;:8865432200..--..../111225677::::<<==<>??AAAAAAAABB@@@@??????>>>>====<;;;;;99:9988788666655554110-,-,****++++++,,++,,,,,,--,,,,,,,,,,,,))()))((((''&'((((&&&&&&&&%%&&&&&&''''''((()**++++,,,,,,----.///00..////2048;AGKOUW\]_bba^\\[[Z[]]`aa`^YTLD;3,******)))('&''''((''''&&&&'*/5?NXeovxy{zwxwtoi[M>411269>@AEGJMSZbhsz€‚‚ƒƒ„„xreS?0(#$.+10&$&226CQ`mty|iIJ[[YXXY\_`__]YUNJD=70--,.--.^SRONORI47???AAAABBAABBA@@@??????>>>>====;;;;;;:::9987888877755435322/.-,****++++++,,++--,,++,,,,,,,,,,++++)))+**((((''&&'''''&&&&&&&%%%%%%%%&&&&''(((*****+,--,,,,----.///00//00/0336:?DIMSVZ\]_aa^\ZZYYXZ]_bbb`]XRI?61,******))*))'((('((''''&&&&'*-3;HT_msxyyxwwwurjaQD7337:@DGJKNRUW[_cgknstvx{~„‰”™™š—–”“‘ŒŠˆ†}uoid^[YYZZ[^__acdefgiknoqqttrqqpoonnjhfb]XWTSRONNMLNQWahpwzzzzz{{‚‡‹‰‰‰‰‰ˆ…€ztlb^[ZYYZZ]afjjmqpqonnmoqrssold]XSPPSUZaks{‚‰ŠŠŠ‰‰‰‰‰ˆ†…„ƒ€|yvuuuvvwwwvqjhgikos;:876644322000.-....../12233566699:;<<==>>?@BBBBBBBBBB@@???>>>??==>>==<<;:;:::9999888989::99:8997975520+*)**+-+-,,,-..-,,,,)++,+,,,,,,,,+*)**))(((((''&&&&&&&&%%%%%%%%%%%%%%&&&&(((())*****+,,,,,,--...///////001235:>CGKOUXZ[_`_^]ZYYYYZ^accdb`\VPE<6/+******))**))))('((''&&'''''(+09CP]iryzywwvwxvmeWH=679;>ACCDFJMMS^flu|€€€€‚ƒ€zo^M<0'&#"$%$$$*/5AQ^mv{||{vnhc]Z^_`a]YUOID=6-+*+-....DP[fouwuvwwvvwuoh`QE:3.-,++,,,,-,+,-.123569;>@DGIKNQTW[^beimnrsvx{„‰‘—šš˜—“‘’‹‡…‚ytmd_ZXWWWYZZ]^_adefgjmopprsssqpoponmhhea[VTRQPNMLIKMOV_hptyxxwxxz„ˆŽŒŒŠŠ‰ˆˆ„€yria]ZXXXYZ]afimproonnonquvtsojc]XTRSUY]gmu~…‰Š‰‰‰‰‰‰‰†„ƒ‚~~|yxuwuvvwwwtroiginqu:9876654321111.-....../12233566699:;<<==>>?@BBBBBBBBBB@@???>>>??==<<===;:;;:::9999889:=>@@??@ABBBA>><83.+***./011121110/...--+,+,,,,,,,,+*))*))(((((''&&&&&&&&%%%%%%%%%%%%%%&&&&(((())*****+,,,,,---...///////001358LXgpuxxwvuvwtqhZPB::;=?ADCEFJJLPZbjrz~€€€€‚‚zqgVC4(&##$25!#)-5AN^luz}{xplf`^_`_]YRNIB:5/,+*+,--..2QbSSSObgB7ADHJMPSVY\_cdhmqstww{…Š–——•”‘ŽŠ‡†ƒ€|wmg`[WUUVWYZZ\]_adeggkmopprsssrpoponmigc^YURPNMLKKIGIMT]fmsvyxwwz|€„ˆŒŽŽŒŠŠŠˆˆ„xof^ZYVWWXY]ahjmmqponoopquvtsojb\ZXVUW]biqy†Š‰‰‰‰‰‰‰‰†ƒ‚~}}|yxusuvvwwwtrojhioqu98766544321110.-.....//123345677:::;==>>>>@ABBCCCCBBBAA@@@?>??????<===<<:99:::9998889;@DFHHHHIIKJJHIFA:3.,-/145679:::8854110/-+*---,,,,+***)((((((((&&&&&&%%&&%%%%%%$$%%%%%%%%&&((''))*****+,,,,--..-.....//0011368:>EKOTWX[[\^__\[ZXWYX\`cdefc^[TKA92,*+++++++++*))))))))''((((((&&),2:GUbmtvxxuuuxvskbSH=;=?ACDEEFFJMPW]fnw~‚€€€‚ƒ~wm[I7+##!#*-##'.5@O\itz}~{yumfc`_][WQLIB92-+,++,,-//115FWQKNWV<8=GT^hrxwxuwwwwvsnh_PF:42...-,,,,++-/12335::<>@CFILORTV[^acgkprvwxy|€„‰”–•”’Œ‹ˆ„‚|yrkdZTUUTTUVXZZ[^_ceghlmnooqssrqpoqpqmjea[XTQPMKHGEDEFKQZflswxxywy|€ƒ‡‹ŒŽŒ‹ŠŠŠ‰ˆ„~wnf\XWWVWX[^bgjlmqromnnoprtsrojb\YYWW[`flt|‚‡‰‰Šˆ‰ˆˆ‰‡ƒ€~}|{||yvsuwvvwxwvromklpsw8766553332110..-.....//1234556779:;<==>>??@ABBCCCCBBBA@@@@?>>>========<;:;;::999879:=AFJMOOOPRSSRRSROIC931258;>@ADDDECB?<:9730/,--,,,,,+*****)))((((&&&&&&%%&&%%%%%%$$%%%%%%%%&&''''))*****+,,,,--....../////01357<@EKOSV[\\]_^]`^\[ZZZ]`acdedc]YPG>70+++++++++++*)))))))))'((((((&&'+07BP\iquwxvttvvsmgYNB=?@BDDEFGFHKOSYahr{€€€€‚ƒƒ€{scP?0'$"!#""#'-2@MZitx~~{yungb_][WRMFA:2-+***+,-/00242LWKII[N68@JU`jtxxwwwwwwwtoj^OE<50....-,,,..-/12335:;=>ADHJMPRVX[^beimquvx{|}„‰Ž““‘‹ˆ†‚€|wtng`WSURSSRTVYZ[^_adgjlmooprssrqpopomkgb_YUQOMKHFDCAACHOYclrwxwwx{}„ˆ‹ŒŒ‹ŠŠŠŠ‰ˆ„~wnf]YYXVWY\_bhknpoopnoorstutspib\YYY[]cipw…ˆ‰ˆ‰ˆˆ‰‰ˆ…€~|{x{}}ywuvwvwwxxwtqmkknsw6666553332110/-,..////0234676778:::<<<==>??@CCCCCCBBB@@>>??>==========<<;:::9989::;=@EINRVWWXY[[ZZZ\YTKC<:;>ACIKMPSTRONJJIE@=81/-,-+++*,**))*))(('''''&&&%%%%%%%$$$$$$$$%%%%%%%%''''()**++++-----..//////1/112248<??@BBBBAAAAAA@?==?>========<<;;:::99979::<=AGKPSXZZ[\^^___a_\UMECDIMPVX[^_`b__]YVRNGB=62/.-,,,+++))*))(''''''''&%%%%%%%$$$$$$$$%%%%%%%%'''''(**++++----./////0/00011258=ADINRW[]^abdgffdb_^^^_accddec_\RJA91+++**+++++,++********((''''''(((),2;FS`kruwuuuuwvqngYNGCBDFGFGFFFHIILT^hqy~‚‚ƒ‚€xo`M<.'#%#$$$(/9AN\grwz{{xtle_\UQKC?82,*****+,,-.//..2LdXJLLX=9AKWbmvyyxwuvxxvsmg]OC<622/110/.,../1023568:<@CFIKNRSVY]_dfkoquy|€ƒƒˆ‹ŽŠ‰‡†‚|xtpic]WSQOOOPPRUVY[\^`chjlnppqrrqqqqqpomie_ZTNMKIFC@?;::@AB@CCDDBBA@?>>=>>====>>>=;;:::99987889:;=BGKORVZZ[\]^adbab`]WPNOSX\_acejjhgffc_]XVQIA<52-++-++***)(((''''&&''&%%%$$$$####$$$$$$%%%%%%%&''''))++,,--../////000/001269=BFJNTVZ]]acehjkieec`__`acefdca[WMD>7/+,,++,,,,++,,++****++)())((((((&'*/7DQ]iruuuustxwtpk_TJCDDGEGFGGFDFEFMWcnx~‚‚‚‚‚‚ƒƒ{sdSA1'$$%&&',58DNZgrvwyyurme]WQJE?80+)))++*+--..//001CfVRNPV8=CMXcnuxzxxwwxxvtmg]PD<62322222/-./02213579:=@CGJMORUXY^acglprvz~€„ƒ…‰ŒŒŠˆ‡„‚|xtpkc\WSPOOONPQSTVW[^_cdegmooopqrqqqqrpomhc^YSNKHCB@=;977:@KT_ipuwwuwz}ƒ†‰ŠŒ‹Šˆ‡†‡‡††ƒzrid]ZXZ]\_aehlooqqqqprsuutsplgb\\\^_fmszˆ‰Š‰ˆˆ‰ŠŠ‰‡}yvstx|zxyxwyxxzzxwurmkmqtu775433432111110//022333568997788::;;:=>===<<<<===<;;::9998778779<>@DILORUXWX[\_bbdfdc`[WZ^^ceggikkmikkigec_YUOH@92-,,**)**)(((''''&&%%%%%%$$$$##$$$$$$$$%%%%%%%&''''))++,,--..//////0011248;?DHLPTV^_bdeijkmllifeca`bbddedd^YSJA92.+,,++,,,,++,,++****+++)))(((((('&)-4@MYdnsvuutuwxwsofYOHDDFFFFGFDA?>BFP]jt|‚ƒ‚‚‚‚ƒƒ|vjZF6+%%&&).17>FN[grvwxxurmcXRKD>80))*))+++,--..//000?baQLOLEJU`ipuwwuxy€„†‰‹‹Šˆˆ‡…†‡††ƒ€ypfb^\\\]^_bfinpqrrqrrtuvvvtqkea]\]_bhow~„‰ŠŠ‰ˆˆ‰ŠŠ‰‡€|xsqvy{zzxwwvxxxxxwurommnqs6654443210121121113334446577899:99::<;=???@A@AAA@@>@@@?>=<=>==<<==<<;:9999887666569;?BFJMPSUUTSTX]aceegec``bcfegiiiillkliigdcb`[ULE=5.,**)**)(((&&''&%%%%%%%$$######$$###$%%%%%%$%''''()*+,,--..///////01567<@EIMSWY\_dhjmnpprqojihdcabccbca`[TMC<5/.,,,,+,,,,,-,,++++++++****(())((((*-2;;<@LXdrz‚ƒƒ‚‚‚‚‚€wp_L;.&&')-05;@IP\gqvwwwspk_UMF?5.+())**++,,,-..//1114NeRKOU^M?Q[gqwzzxxwwwwusmh\OE=854332220//////12479;>ACHKLNSUWZ^bcgmqtx~‚ƒ†‡‰Š‹‹Š‰†„ƒ}ytqkf_XTROOORRUXXZZ]^_bcegjkmnopqrtssrrspnnid]VQKDA>;:762248?JV`kqvyxvx{„‡Š‹‹ˆ‡‡†…†‡‰‡„xogb`^]_``cehjnqrrrssuvuxvvsric^]\^bflsy€‡‰‹‹ŠŠ‰ŠŠ‹Š†{vtrtx||xxxwwxxyyxwtqmkmort6654443210122221114455556577989:::;;<=>????@A@AA@@A@??>>=<<=<<==<<;;::99989966654589ACHKMORTXY]^cglptx~‚…ˆˆŠ‹Œ‰‰ˆ…ƒ‚€}ytpjd]XVTUSUXXZ\]_``bcdhiikmoopqpqsrsrrsrpomf`ZTMC=:97531028@KWakrvwxvx{„‡Š‹‹‰ˆ‡†‡ˆ‡ˆˆ…xogb``___adfiloqrssttvvwxvvrpic^\[^bgow}„ˆ‹Œ‹ŠŠ‰ŠŠŠ‰„|xsppsy{{yxxyyxxyyxwtqomnprt55554442232233333455556666679;;;;;<<<>>>????AB@@@@@?>>====>=;;;;::::::998877555544557:9<>=>>:99;CIQW_dhihgfeeba\WTTX\\]]]`adfhjkmj`VLA4-*)**(()('&&&&&%%%%%%$$$$#"####"$##$%&&&&&'''((***+,.----//0001247CJQX_hqxxyvqoh\OD90*)))))****+,--...//103;RdTTGNNRQZgtz|zzyvvvvtqng\OF>:7665553110000013567:=ACGJMORSVXY`bhjnsx}„†Š‹‰Š‰ˆ„ƒ‚|yvphc\XVWWVY[]abceegjklnnpqrssqqrssrrrqtrrqnje_XQJB<65532027@KValsvwwvz}…ˆ‹‹‹Š‰ˆˆˆ‰ŠŠ†…€yohdb`_^_bgfjnrqssstuxxxzwurlfa]]]`ejry…‰‹Œ‹ŠŠ‹ŠŒŠˆ‚|wqmpuy{{xwvvvxxwwyxutpoprsu55554444334344445555556666679;;;;<<<=?>>????@A@@@@?=====<;<<::;;::::::998776655433331456653300029@HQY]begefb_YTOHFFFHKLPQTY\afkmpnleYL>1,+++*))(('&&&&%%%%%%$$"""#####"$$$$%&&&&&''(((***+,.----//001249KYensuutuvwyyxtj_UMHFGFEC@:51024=JZgv~„†„€€‚‚€yqaN>30139>BHNSZ_hqvxyxsnh\L>4-()*)))**+++,--...//014=TNQOKQLBM]jv||{yxvvvvtqnh_RF>:7665533111100002467:=@BEHJMPSUY[^`eilrx~‚‡Š‹‹Š‡‡…ƒ‚€~{wtpjd^]XZ[[]`bdeiillnpqqrrvvvvttstsrrrtuttusokf^XPF@;8653238@KValsvwvx{~‚†ˆ‹‹‹Š‰ˆˆˆ‰ŠŠ‡„xohec`__`cefjnrsuttuvwyyywuqle^]]^`fnv|‚ˆ‹ŒŒŠ‹ŠŠ‹‹‰‡|wplosy{{xxwvvxxwwxxutqpoqsu66554444444455555555665566899:::;;;=====??????????>><;<<=;=;;;::::::99887654543211/..0110--.,+,.58AJQY\]aaa\UMC:556878:;AFKQYcjlrvtmcUJ:1-.,+*))'&&&&&&&%%%$$$##""########$%%%&&'''(((**+,,----./001348?BHNQY]acfjmrsvwz||yzzvrnljeddbba]YRMIA:3-+,,,,------........//./012210--*)))))),/3;IWcmrutttvvx{wuoeWMIGFEC@=72-,.28EUbq|‚…ƒ€€‚‚{vjYH:778>BGKOSZbjqw{yxtnh]M>3,))))****,,*,....0/0023ORY\RPRTKA[kuz|zywvwwvurmg^QF>97665432221100111345:=>ADGILPSUWY^_cfjqw~ƒ‡‰‹‹Šˆ…„‚€€}{xtojea__]^abdehijknprtuvwvxxwvvvttrrrstuvvwutrmi`\SHA:743126@LWbltxwvxz‚†ŠŒ‹‰ˆˆˆ‰ŠŠŠˆƒ~woib`_^`bdghlprttuuwyzzyzwtnic^]]_chqx„ˆŒŒŒŠŒ‹‹Œ‹‰…yrmkotxzzwvuvvxxxvxyvurpqutt6655444444445555555566668899::::;;;===>>??????>>>>==<;;;<<<;::::::::99886644442211/.....,,+,++,.04:DINRVYYXRI>40..-,+,.027?GPZcjquwskaUH<751/+)(&&&&&&&&%%%$$$##""########$%%%%&'''(()**+,----.//02358=DKNS[^cfhlnotwxz|{}yxwsplihecb`]ZXRKF?92-++,,,,------..//....///0124443.-,+**))),-2:FSaksutttuuwyzxoe]QKGFDB>:50,**06AR_my……‚€€€‚„‚~woaSC<;>BGKNSW\djqw{zyvqj^O?3,*())****,,,-,,...01123?bocRJKHPSaluz|zywvwwvurnh^QF>976654322211001113457:=>CEGKMPSWW[\_djpxƒˆ‰ŠŠˆ†„‚€~}zxtoieaa_`aacdfiikmmnqtwvxxzzyxxxutrrrsuvyyzzyvsphbZQJA;86538@LYdntwyxz{€ƒ‡‹ŽŽŒŠ‰Š‰ŠŠŠ‰ˆƒ~wngbba`_adgimqtttuuwy{{yxvrmfa^]]`cjt|†‹ŒŒ‹ŠŒŒŒ‹‰…zqllotxzzwvuvwxxxvxyxurprvvu65554444555555555555557788:::;;;<<==><=???>>>>>>>===;;;;::;::9::9:;988976554321100//..-,++++++**+/5;AGLLONJC93-*))******+.4AEEHJMPTVW\]bhow}ƒ†ˆ‰ˆ…ƒ‚€~~}{yvrmhdbbcbacddfghklnoqsvxxyz{}yyxusrrrsuw{~||z}|xqkcZRIA:6448AMXdosvzyy~…ˆŽŽŒ‹Š‹‹‹‹‹Šˆ…wmfb^]\]_cfinrstuuuvxyyzwtpld_]^^cgow|…ŠŒŒŠŠŒŒŒ‰„wpkipuwyywwwwxwwxvxzywsopruu65554433555555444466668889:::;;;<<<<>=>???>>=====<<<::::<8::;999899988765544220/....--,+******))**-4:=@CA@:4,+*)****))))),.2;GP[eluvtqiaZROJE@:3,(''*++*(&%$$$#""######$$$$$$$%%&&''())*+,--./000148;BGMTZ`deknqqtwwxy{yvtspmkhhdb_]XURMGB;51---,,,,----....///.0000//1135456520.-,,,**,-18BMZensuttttvxywrj_VLGEB>951-)(),3:FVdr}„ƒ€€„„{qbUMHIKMOQSX\cgptvwwwsmbSE>;,*++**++--...../..0246VcJBJGKIHW`mv||{zwwxxwvtoh_RH@:8555544442201111333568;>ADFIKNQSUX\bhpw}ƒˆˆˆ†…‚€~}{|ytplgcbcccc`cdefhijmopqrtwy{||zzxusrrrsvy{~}~€|yrkdZRH?7248AMZfovxyy{…ˆŽŽŒ‹Š‹‹‹‹‹Šˆ…wngb^\[]_bejorsvvuvxz|{xwsnha^\\^bhpw€†‹Œ‹Š‹Œ‹ŠŒ‰ƒ~tljjotwyywwwvwvvwxz{ywtstuvw556677555556766677777788:::::;;;==<===>>>>==<<<<<<;;;;;;99<;::888777655555223///.,----++))))*)(()(),1569840,*(((****))))))+-39EP[equvurlgb^YQMF>80,-23322-'%$$##########$$%%%%%''&(())*+,---.//0147:?FMSZ^dgjlnpqrvwwwusromkifb_\[XTRNHD?730----,,----../-.0/../111122224467641/----++++.15>JUdlrttsssvwxwslaWLFBA<83/,)''*28CRaoz€ƒ‚‚€€„…‚}wl_UONONQRUX[`fjrwwwwtmbTINJ-)))**++---...////016QlG7<9?IIJVamw{|{yxwyxwvuqlaRG@<96655433333111133443689<>AEGJMNPRT[`hqy~ƒ‡ˆˆ‡†„€}|zyvrnieba`aaaaab`abdegghjnrsuxzzzxvtrrrrtvwy{z~‚„„‚~{qkbZQE<549DP[fpwyyyz~‚†‰ŒŒ‹ŠŠŒŒ‹ŠŠ‡ypje\[ZZ\`djnsuuvvxyz{{xvqkd_[YYZ`hs{‚ˆŒŽ‹‹‹Œ‹‹ŒŒ‰ƒ}skhjnsxyxwwuuvwxvx{{zwstuuvx666677556666766677777788:::::;<<==<===>>>>==<<<<;;:::::::999998887776555543210/..,,,,,++*)))))(())(*,-00-*))(())))))(())))*+-28CP\ipuwutpllfa[TKE?:;?@A??71,%$##########$$%%%%%'''((***+,-...//037:>CJPW^cfklmopstttsrpnkifda]\ZXSPMID@;62/.----,,----.../0////011112222335652//..--,,,,-03;GS`jrtsrsstvwwslbXNF@?950,,)'').6?L[iu}‚‚‚€~ƒ„ƒ€}sg]UOOPUUWZ\^cglrvvvsk_RPGB/*****++----../////25a†]98IQOOKUclx}~}yxxxxzxuqlbTG@<966554333332246887755679<>ADFHJLNPX^gov~ƒ‡ˆˆ…„‚€~~}{ywvsmgda`^]^]\Z[Z[Z\]`cbdfimquvwwtsqqrrrtvvxzx|~ƒ„‚~zrjaYOE<9;FQ]hqxzzz}€„‡ŠŒŒ‹ŠŠŒŒ‹Šˆ…‚ztlf^XWY[bhmrtttuvxyz{yutnh`[ZYX[bkt|ƒˆŒŒ‹ŠŠŠ‰‹Œ‹ˆ‚{rliknsyyxwvwvutuvx{{zwtsuvuw77757755776676558867889989::9<==;>>?<<====<<==;;:999999999988898965555332322/..,-,,++*****))(('(('&'(())''(('())('))((((((((*.4=GS^hrwyvuuspnhb]VQNOPQSSOHA7,%#!""$$###$%%%%&&'''''()*,,,-..011258=BIOV\afikmoprutrqniiffc]ZYVTPLJGEB>820...-------------.001111334422211344220000.-,.---/39EP_gqtssrstvvusndYNEA=84.++)'').3:GWgpz‚ƒƒ€}~ƒ„ƒzoe\VTSUVXZY[\ciosvutmaXS7>7+,,++,,+,--../000136UwgIJITTQOYdpx}}|yyxzyzyyslcUG@<96644432213469=<=<<;86579<=@CDFIJPU]gryƒ‡‡‡…ƒ€~}{zxvvpleb^\YYUVUTSSSSRTTUXZ]afknnqqqrrrrrrrsssuxxz}‚„‚€xribVKB>?JS_jqy{{z~€…‰ŒŽŒ‹‹‹ŒŒŒŒŒˆƒ}wpg`]Y\^cjpsuttuwxz{zwsqjd]YWWW\dmw~†‹ŒŒ‹‹ŠŠŠ‹‹Œ‹ˆ‚{pkikmsyyxwuuvvwwvw{{{vtsvutt776777667777666677778899:9;;:;;;:;;<>>====<<;;;;999999998888888877664422120/..-+,,+++**)**))(('(('&'(('''''''())('))((((''(((+.6=HR]hrvyxxwutqnhfa`acdffd[SH:+$$""#####$%%%%&&''''())*,,,-./11147:AFMSY`dgjlnpqssolhfc`\\YURNKHEEA><750/.-..------------.0111121334443322452221110/-....-/28CP\gorssrrrtttrmdYNE@<72.+)('''+17AQ`lw€ƒƒ€}~ƒ„…ƒ~uh`YWVWWWWUWX^fmtuurhZXL7@:,,,++,,+,,-../00022>X_\TWC@NUUXfpx}}|yyxz{zyyslcUI@:866444333389:7778;<=@CGJPY`iry…ˆ‡†ƒ€€~{zzxvtnic_YTSPNMLJIJKLLLLMPQTX]ehknqpqqqprrrrrqqqruvz~€‚‚|xpg[TIABKT`krwyy{}€…‰ŒŽŒ‹‹‹ŒŒŒ‹‹‰…{tme`^^afkpsutuwxzyzxurnf`ZVWUX]eowˆŠŒ‹‹ŠŠ‹ŒŒ‹Š†wohgjmsyyxwvuvvvvwy{{zvrrttss7768988869887777667878::::;;;;<<<<<<<<<=<<<<<<::9988998888887777555521111110---,+***))))))((''''('''''''&&''''(((())))''''(('(,24>GQ[cjosvvuttssopoqruvvrjeWI9)"!!""$$$%$$%%&&&&'()*+,,,,-/012369=CIOV\afilnopsqlkfb\ZVRPLJHGCA=:9842/....------....----00012233555555445544322210////-..028BMXeosrqqqqstsqmeZOE>;60,''((')+.5?LZft|ƒ‚€}~ƒ……†€ynf]YXXWURRPV\bjrvuraXY@6<2,+,,-------../01003;[f^Z`[;HTXVhpz}}{yxxyyyyztmcUJ@96545543246;?EJNPPROKD>967779<>AFKRZclsy…‡††„€}||zxuqmg_YSPMGFFEDEDDDEFGGIJMRY^dgjloqrrsrrronkklmmoqv|€{tlb[PJFNWblsy{{}‚†ŠŽŽŒ‹‹Œ‹‹‹ŒŠ…‚}vqjfeefmpstuuwxyyyzwsnhb]WRSSX]goy‚‰‹Œ‹‹‹‹‹‹ŒŒ‰†wlefiouyywvuuvvvvuwzzzvtrqqqp888899997:889977668878::::;;;;<<<<<<<<<<<<<<::::88888877778757745555111100.--,,,+**))(('((''&&&&'&''''''&&''''((((((''''''(('(*+05=GNT]cfilnqrtuwxz|~……zsfVF5(!!""$$$%$$%%&&&&()*),----/002358;AFLSY`ehjlmnoqnhc\YTPNHDA?><;8764200///..------....0000011123345555555566444322000/0/-/.028@LVcorrqropqrqqmeZOE=84/+(()(()+-3:EVdpz€‚€}|}‚†‡†€{qi`[XWUQQOLOT^irutvbQK?882.,,--------.//000125Mk_WdV8;PWWdqz}}{yxxyyy{ytmcUJ@:7755554459@GMSX[\[YUNB;85667:>@FOV]fnv}‚†‡†„ƒ€~~~||zxuqke^TLGCABBAABAAAABCCDFKPW]`cjmqppqqrttplieebbfhntz€}yog_WPOQYbltz{{}„ˆ‰ŽŽŒ‹‹ŒŒ‹‹‰ˆƒ~zuqkkjkpsuuvvxxyzzyurlf^URPRRW]hr|„‰ŒŒ‹‹‹‹‹‹ŒŒŠ…~wmecjpvyywuuuvvvvuvxxxvsrpqpn9999999999888877877789::::::;<<<<<<<==<;<<;:::887776777777667546545311200/.-,+++**))''''&&(((())&%%&''''&&''''''''''''''''&('''')+4;BHNRXZ]bdhnuw}€„†ˆ‹ˆ†‚|reUG5&#!"$$$$$$%%%%'(****,-../011358;=BGNV\beiklooomia\TNHEA?=<96534321////----..........//00112333455566775566554433220011../137>JT`kpqroonpqppme[PC;51-*''()()*,/7BP_lw‚~}}ƒƒ……~umc^ZUQMIGGHP[jqvtkRBKNF>60,,--,,----/0//10/25H[[[i[76PTVdrz~zyyyyzzxxsmdUJ@:75564446;@HPX^bghfb_VKB964358@FKSZdkry€„‡ˆ…„‚€}|~}zxvrjbZMGC>?>>@?@@@???A@CCGNT[afimoqrqqssqpje]YUVZ]ckswz}{rld[XUY^gouz}}~„‡ŠŽ‘‘ŒŒŒŽ‹Š‡…|{{xxtpqqtuvvvwwy{zywtoic[TPOOPU\gq~…‹ŒŒ‹‹‹‹ŒŒ‰„~skffkqwzywtuvvvuuuvwwwsqnnnpn9999999999888877887789::::::;<<<<<<<==<;::::997766666566765555655431110//..,,****)(''''&&&''''((&%%&''''&&''''''''''''''''('''&&%(*059D\\M?60,,--,,----.///00.2=RY^cdTIQQOVgqz~~zyyyyzzywsmfXKA:75543448=ENU^flqrple]PE<6538=?<>==><=?>?CGNT[aeioppqpqsqqoic[QJHMTZckquzyvoia]\]bgov{{{„†ˆ‹‘ŽŒŒŒŒ‹ˆ‡„zyzz{{zxvxwwwwwxyx{zywsmf_WPPNLOU^it~†Œ‹‹‹‹ŒŒ‰zriffjqwzxwuuvvvuuuvwwvrpmnmkj888899999987887788889:::::::;<<<<<<<<<;:::::987766665455556566555211000/.-++**)*)))('&&%&%%%&&%%&%%&''''&&&&''''''''''&&&&(&%%&%%%%'*/11238@IPYcjsz€‚„……ˆ††‚xo]L8'##$$$$$$$%(((*))*+--/.0013578=AGOUZaehknolljc\TIC=96654444312200000/-----...//....//1112334455667777777766554333021100//48;ER^iprqnmkmnrpmh]QE:3.+)(&())*+*.2;FVeqz€‚~|z{~ƒ„ƒzrj\VPKB?==DMXfphI7B^i`QD80-.--,,..---./0./13A]]baUSYXSPYdq{~}{yxyzzzywtneWKA;8444446;AJT]fotyyzsj`UG=747;BJPV_fosz€ƒ††…ƒƒ~~}}||}{wqh_UJB?><<<=<>=;;<==?@DGNV[agjlnnoqqrrplg_WKCDJRX]bgjmnmmjd]ULB9874433333311100000/-----...//..../011233355557788777777765554322110110048;CNZioqqnllompqog^SG;2-*)(()))++*,39BP_lv}|zz{~‚‚{vj[SLE?:9:AKWbpkN9LjocRE90-...--....-./00123FbibSMjbRUSWfr{~}{yxxyyyzwtneWKA:7443346AIRV\bfhknnnmg`XPF;7444443333212100000/....-.--//..001111334555579999999988776655442211111148;@LWdmpponkmnnqold\NA5-))*)*))**+,18=KZht{~~}zyy|~€€}wj_RHA<557?IT`jqJ?auqgVG<4/.,,....--./00003;Zz{eHN\\HIRYdqz|yxvxxyyxuoeXJ@84332137AHPW_emrx}ƒ…‡‡†‚~~}}~~~~|wqj_SI@;<;:999<<;;;;<9=AGOV]cilnoooononmgb[OG:304;ENV`fnqsojjkloru{}~‚…ˆŠŽŽŒŠ‹ŠŠˆƒ|vrniltx}~€|{{{{{{}{xqkd\TLHFFIMWdp|…‹ŽŽ‹‹‹‹ŒŒŒˆ„~ukcaglrwyywuuuuuvtvwvvuqnkjheb::::;;:99987777788889:99::;;;;::;;;;;;:9:8776676555444544444432221/0/.,,,*(((('''&'&'&$$%%%%%%%%$$%%&&&&&&&&&&&'&&''''''&&%'&&&&$$%%%%%$%$&&&*09@EJMNSZbny‚†„ym\D-$#####$%&'())***,---/0/1369NYcer{~}{yxvxyzyxuodWJ@84311248@HP\gs|‚„ƒ~ul`THAAFIOW[cjpx}ƒ†‰‰‡„‚~~}{|~~~~{wqh^PH@;:99999::99999:=AHPY^dilnooonnomjdaWOB61./3;FQ[cjnqroonprtw{~~€ƒ‡ŒŽŽŠŠ‹Š‰†zrkebemrx|~€€||{{{{{}{vniaXOICDEGMXfr}‡ŒŽŽ‹‹ŠŒŒŒŒ‰ƒ|qjbbgltyyywuuuuuuuwwvtspmkifeb;;;;;;;;::9887888888:8999::::9:::999;:888877765454445444234441220//0.-++**)((((('&'&%&&%&&%%%%%%$$$%&&''''&&%%&'%%&&&&&&&&&&&&&&%%%%$$%%$$$$$$',158;=CKWiv€…„zn[C,##$$$$$%&'())*,,,---/0/136:>DKQX\afjjjnmid`XOF>96545444444333321000///.-----...0001133445567789988999998877665443322322136=BIT_iqrpnkmooprpkcWI<0+++))*++,+-/6;GVbnw}~{xxz}~€€~yobSD82036)##$$$$$%&'())*,,,--.001369?DJNU[_ffgknnlhcZSJC<665554444443333210000//.-----...0001133355556789999999998767665543322231247HUbmy€„…‚~uk`TLHMSXY\dmt{…‰‰Š‡„€€~~~||}{~{vpg\PD=;76667777767767>CJSY_fjmomnmmnmkf_YPE:2.++-18ALVajmtttuxy{}~€‚„ˆŒŒŒ‹Š‹Š‡{sg[SPX`kqvz~~}}}}}||zwrkcZQGB??CHQ]jw‚‰‹Œ‹‹‹ŒŒŒŠ†€xnf_bgltxyxvtttuuuuuuuspnjhfda];;;:::;;:9987788889998999;;;::::99988877655554555544333323554100....,+++**)'''''&&&%&%%$$%%%$$$$$%&%%&')'&&&''''''''&&''''&&%%%%#%$$$$####""#"$$#%&',4>N`q}„‚ykW<'##$$$$%&'()**+++,,.12249=BFKQT[adghimnlif`WNG?8766544444442222110000///.------.0001123455557779999::9999888866554433332458=BIQZckpqommooqrvqjbQC6.))+*+**,+.3:?GS`js|€}yyx{|~€€{sgWF:3149:BNYeTK^ztqk`PC70,,,+,,,----.0/3Rqzw_FEL>>LUUeiq|~{yxwwxxyxuofYJ@9412114;AIWcoz„‡‡„xm`UQQSVX[agpw}…ˆ‰‰ˆ…ƒ~~~}{{}~~}|upf[OB<864456766554457;BKSZ`gjllmmlkmlic]VMA8/,**,06?JT^imsvwwz{~~€€€‚…ˆ‹ŒŒŒ‹‹ŠŠ‡€zpeXKJP\dkrw{}~~~}}}{{xuog^XMD?==@GS_l{„‹Ž‹‹‹ŠŒŒŒ‹‰…uld`chntxxwtssssssuuttqpnjfec`[;;:;::;;:98777888888899989;;::99::8988664455544444333322222221//--,,+****)('''''&%&%&%$$$%%%$$$$$%%'(++--+('('''''''%%&&&&$$$%%%%$$$$$####""#"###$%&+1>M_p}„‚ykW>'##%%$$%&'()**+,,-./0259?DHMQV[]deijkjjjic[SJA97566544444442222221/000.--------.0001123456657778999::999999776655443333346:>CIQ[dkpqonnoqstvtnfZJ<0****+*+,/26EQQTffq|}{yxwwxxyxtneXJ@9401/15;CMYfr~†‡‡†‚|pdYVVXZ\]dirx~…ˆ‰‰‡ƒ~~~|||}~~|ytof[OC:644444555444457BKWco|†‹ŒŒ‹Œ‹ŒŒŠˆƒ|ujb`chntxwuttsrrrsttsqromjfc`][;::;;;;;9:877788877788889:::::989987776655433333443333221100//.-,+**+++*((''''&&&%%&%%%$$$$$#$$$$%&',03442-+)()))&&&%%$$$$$$#%$$&$####$#$##""#""! $')/:J\my…zkXA)"$#$%%&'()))*+*,.-0259AEJNSV]_cdfhkkkkjf_VMD>86565544433332222110../0.------...0001112346667889989::::9999776665553333567:?DIQZcjoqponqrstvuqkaQB4**++,+.036;AEMValu}‚€~z{{|€~yoaOA71148ALWdLLjvvqleVF;2-++*+,,--,-,00IzrUK^]TPWLMQPiir~}{zywyyyyxtnfXJ?8411/27?FP[hv…‡‹‰†rg]XXY\]`elrz€„ˆ‰‰†ƒ€~}~|~~}~€|yuoeYMA9522232233443348=ENV]ejkmmmnllkie^ZPE:2+))*,/38EO[cjrwy}~€‚ƒ„„„„†‰ŒŒŒŠ‰‰‰‰†…~uj`VKGMT^irx{}~}}{zwqld[OD=::;BLWgs‡‹ŒŒ‹‹‹‹‹‹‹ŠŠˆ}sga^bgotvwurnqqrrssssrqnljfa^[Y;::;;;;;:9877788787788889::::9778877776544333333222211111100//.-,+*****)((''''&&&%%&%$%$$$$$#$$$%$&'+05:=9722100.,(&$$##$$$$#%$$%#####$###"#"#"""!"%(/9GVjy„|n]J1$%#$%%&'()))*+,-.036:@FKOSX\_acfjkkkjjic\SJB;64655544433333333110/.//-------...0001112345677889989:::::988877665553355668<@EIQXaiprrpoqrstvvtneWF8.-.,+.157;@DJQX`js}ƒƒ|{{z}‚‚€{reUD82148AITcKFivvrlfWH=2-,,*+,,-----1DtwmZ8OOMSWQRROcjs}€}{zyxwyyyxuneVI>843125;AIS^lx‚‰Š‹Š†th`[[\]]bflrv|…††„‚~}}|||}~}zxtndZNA9521112211111149>GPY`gjmmmmmllkhc]WMB7/*)))+/3:FO[cipv{~€ƒ†…†ƒ„„†‡ŠŒŒŒŠ‰‰‰‰†…}vl`SKJMT^hpv{}~}}|{zvpjbVKC=::;BNZiu‰‹Œ‹Œ‹‹‹‹‹‹‹‰†€xpe`^bhntvvtrpqqqqrrrrqpmjgca^ZX;9;::;;;::89878788877899999888887766665522221111222210000/00//..,+**))))))'&%%''%%&&%%%$$$##%%$$##$&+.37;;:6777765/*%$$$$$$$####"#####""""""""!!!!#$)-6BRcs~‚~scP8'&$$'''(((**++,./25=CHMQTY]_bdhkkjmkhie^VLD?97665566553333332211///..-------../10011123455678899899999987899764434345676:>@FKPX`iqstrppqsuvxvpg\K;2----169=BEJMT[ajs|„……€~{z}~‚€xk[J;3347>ISaJJjvtrofXI<4-,,*+,,-----.KxzqJ7UNLIMSMRWchr{€~{zyxxyyywtmeYK=752548=ELXcp|†ŒŠ†}tib^\]]_aejpvyƒ…„„ƒ}}}}}~}~}yxumcYK?7321000000000/6:@GQYahmlmmmmlkie`ZUK?5-+++**.4?IS\cjqu{„††„ƒ„„†‡ŠŒ‹‹Š‰‰ˆ‰‰ˆ…~vl_RJKQVajqy|~~~|||{xsne\QG?:89=DO_lyƒŠ‹‹‹‹‹‹ŒŒŒ‹Š‰„}vlc^^bgnuutsrqppppqqqqomkgfc`\WV;9;:9:;;99998777888778889998886677665543222211112111100/0///..--+,**))))('%%%%&&%%&&%%%$$$########$%(*.166456699872-'$$$$$$$####"#####""""""""!! !#&+25347>GR^IHlvtsofXJ=4-,,*+,,------;`pjQ8[WNLPYUMTc`fv}~}zyxxyyywtofXK@96689>ELS]ht~‡ŒŽŠ„|rhc`_^]]^`djov}ƒ„‚~}}}~~~}}yvrmcYK?731000....../04:BKSZcjmlllmmlkic]WPE<3-+),+-09BLU]cjrv{ƒ…††„ƒ„„†‡ŠŒŠŠ‰ˆˆ†ˆ‡‡„}vl_RNNSZbksy|~~}~}||{wrkf\QG<888=FSco{…Š‹‹‹‹‹‹ŒŒŒ‹Š‰…|sic^^biovvtspopqqqqqqqomjgdb^ZWW::<<::::;9::9887888888889977765555445543222211222200//////./.--,++*)('''''%%%%%##%%%$$$$%%#"######$$%'(*++-///26661.(%##""""####$$#""""""""!!!"""""#%)0:JZkx€wi[F/&%*/23,)))*+,-28;AGKQUZ^`cdfhiiiigfdaYPHA<764555555554433322200//.-.-------..0011112325666788888899889888887775445567:<=AEJMQX_gouuusrrtvtvwsndUG:1027<@EHMPTX[_diq{‚‰†ƒ~s^j|€yz€|seTD83365.,,)*,,,,,,-+2LenaPn|iQO^^NLa^Vk‚|zyyyyyxxvofYJ?;;=ACHLTZbmx†‹ŒŒ‰ƒznga`_]ZXY\bipw}„‚~|}}|}~~~~}{ztqkcWI=630/...---,,-/5FXdq~‡ŠŠŠ‹‹ŒŒŒŒŒŒ‹ˆ‚{qf`\^cjrvwtsqpqrrrssqoljlge^\ZYY::<<::;;;99988878888888888777655443344332111112222/////.///..--,++)*('''''''''%##%%$$$$$%%#"######$$$$$&&&')**+///+)&%##""""####$$#"""""""! !!"""""#%)/8EWftznaP6)).59;3*))*+.16;@FLQVZ\adcfihhiihgc_YRKB<7655555555554433322200//...---..--..0011112325666688889977899888777765655668:=ADHILQW_emtwusssrttvvtphYJ>8595.,,+,,,,,,,,,-4FSkkuteb`aZNBSfkz}{zyyyyyxxsmf\PDCACGJNRZajr|„‰‹Œ‰ˆuic_\ZURSTX`fnwƒƒ~|}{{}~~~~~|yvuqkbVI=63/.---,,,+++/4Qboy€|sgWB-)1;?A7,**,-169=CJQVX^bfeggjhiihfa]VOLD<97754545566444443221100//...-------../01111233456667766889989887766665666676:=@CEIJMQW\emsvwuspssssuuqj_PC<<>AEJNRX[^aegkpvz€†…xwujfqf[hszyo^M=4249CP[PO_qsrkeZK>6/,*++++++,,--+++8YrlgdpdWVTS]hu{}{{zyzyxxxvoi_UMJJLMPTYahov~†Š‹Š‡‚zre]ZVRMJMOS]fr|€ƒ~|||}~~~~}~{xxupibTI>52.----,++++*05>GOYahknlmkkkljgcZULB8/+++04=GNV]binswy|‚„‡‡…‚ƒ„†Š‹Œ‹‹Š‰‡†‡ˆˆ‡ƒ|ulaVRU\biqx|~~~~}~~}xume^RE:6648AM\ly‚Š‹‹‹ŠŠ‹ŒŒŒ‹ˆƒ~vl`YY^elsvwusqrqqrrrrrolhgc`\\^`b;;::;;;;:98777878877777787775433111111110000001100.-////--.+,,,,)()'''&&&&&&%%$$$$$$$$$$$$##########$$%%%%$$%%$##%&%$#""""""""""""""!! !! !"#$)/9K]lw~tk\J3),4955655545544444443221100//.-.-------../0111123344566776677887788776666655688:=@BEEILMQVZdlswwvtrrrrtssqlbWNEBDJMQU[_dfikpsvz}€ƒ‚vut|dPD?@D^^XohJ8049?KZNM^lrrmeZK>6/,*++++++,,,,,,+5M]chkcDHMLS`ir{}{yywxyyyxuqkc\XRQPTV[`fnu|‚ˆ‹‹Š†xmcYSLHDBEMU^iv}‚ƒ~||}}~~}|{xwtpibTI>52----,,++++,19@HP[bhlmkllkkkieaXSJ@5/*+-3;EPU]ciorwz||‚„††…‚ƒ„†Š‹Œ‹‹‰ˆ‡†‡ˆˆ‡ƒ|ui^WUX_fmty}~~|}}}||xukf\PB84459DP_o{…Š‹‹‹‹‹‹‹ŒŒŒŠˆƒ}uh^YY^elswwusrrqqssssrolheb^^^_bd<;;;::::98988677887766776566421111110000/11100////00/.--,,-,,+,,))('&&%%&&%%%%%$"#########$$""""######$$$$$$$$$"""$$$#""""""""""""""!!  !"##'-5CVhs}{raR>.+/75/*+,.05;>DJPW\`cfiijjjiec_ZTOGB<765456544554445443222110/..---,--,,--...011112333555566777788898866666665679<=@CEFILKMOTYcltvwvtrrrrrrsqoh^TLHJOSY^bhknstvyz}€ƒ€mYZqhU[bi^PxVf`_T<:?YnbKGN_prleZK>5.-+,,,,,,,,,++,2;PVWbfQO]IELVeqz~~|xwxyz{{{yvphc^[YWZ^cgmry‡‰‹‹‰ƒvk_QLD=8;@MYeq{„„~~}€~}|{zxuupibTI=30--,,++**++-3:BJT\dhkjljjkjigc_XOF<3-)-2;FOV]djprvz{}~‚„††„„‚ƒ„†‰‹‹‹‹‰ˆ‡‡‡ˆ‰‡ƒ}vmbWSZaipx{}~~~}}}|}wuicWJ?62229DTbp}†Š‹‹‰‹Œ‹‹‹‹Š‡†zqh]WX_fmtvtutqrsrsstsqnkfca_`_adf<;;;::::988877768877666655443111111100000000//0///0/.---,,,++*))))(''%%%&&%%%%%$"######"##$$""""######$$$$$$$$"$#"$$###"""""""""""""!! !!"##%)4@N`mz}ufYI5,+-,+*+-047>CGNUZ^bdgiijjjfa\VQJD@;7665663344554445442222110/..---,--,,--../01111233355456766778878876666666789==?BEHIKLMOPS[bkpuzyusqqqqsutsne[TPQUZ_ejqux€ƒ‡†„vX@PZRP@X`^dlNJA@GKh˜“rICLcsqkf[K>5.-+,,,,,,,,-/Hn}„†„tcfjV[JN\Xeqz|}{xwzzy{{{zxvoga_][_chmsx}„‡Š‹Š‡ƒ|uiZL@7339CP\hv}ƒ†„~~€€~}zyxxwspibTI=30--,,++**++-39CKT\djkkjjjihhea[VMB81,+07BKS]fjoty}~}}~„†‡…„‚ƒ…†‰‹‹‹‹ˆ†††‡ˆ‰‡ƒ}vj_XW]emqy{}~~~|}xrh^UH;3122;FXgs€‰Š‹Œ‹‹Œ‹‹‹‰ˆ†ƒ|tkc[WX_fmtvurqqqrrqrrqpmjeca`aadef;;;:::999877776655665556422221001100////..-----//...--+++++)))*)(('&&&&%%%$$$$$$$$$$$###########$$""####$$""##""########""""""""!!!!   !!""""$(.8GWgsz|xnaR>-*((**,-058>ELRY\`cdhijihd_YTNHB>976335444455553344332211000/....,,--.---../001202344555555556666887666447679:<>ABFHJJLNLNQQYbhptz{vtqqpprtvvqkd]XW[]djqw|ƒ‡‡‡‡ˆˆˆŠ‹Œ~jVQZVZ\cfib\EDLQD@LhwmYGZwsole[K=6/--,,,,,,++-/[~€vx}xiiwkRIV_Xdq|~~|yxwwy{{|{zwojeb`bdimry}†ˆ‰‹‰…‚{qeVF;404=HUamx……„€€}|{xxwwvrkbVJ=3.,,---,,+**/3:DNXaeiiiiiijieb\WQH>5.-/5>HT\dkov{~}|}~„„††„‚„‚…ˆ‹Œ‹Šˆ‡‡‡‡ˆ‰ˆ…~uj^Z\`hosy{}~~~|xph\SE80-,09554454444455553344332211000/..--,---..--.0/0012023335555555566667775656679;=>@ABEGIKLMMMOPSV^gnuzzxusqsssuwwtpjd_^`diou~ƒ‰‘ŒpVIIW_mrklnYGQCFGIIKTb^O[tnmic[L>6/,,,,,,,,+++,.>Yhpkfd_nkPO^^Tdpy~~|yxxxzz{|{yvojeccfimqvz}…‡ˆ‰‰…‚{qeTD8218BM[fr}„„„‚€€}|{xxwwvqlbVI<3.,,---,,+**.5O_ly„‰Š‹ŠŠŠ‹‹ŠŠŠˆ…€yrk^VSXaipuvsrqrrrrssssolieddeffghg::;;;;99877766656655444200111110/0./..//..--------,,,,,+**(*)'&&((('%%&%&&%%$$##$$$$$$$$####"""###""##""""""###"""!!!!!!!!!!!!!!!!!! !  !! !#$'.8GWfpz|wocTC2,--0136:@EKRUY]aeijjjhaYSLGB=:674445555444555433333321100//..--,,----..//./11111112445555554455555445568<<>@ABFIIJLMMNONOSX[ckrwxxtsrrqsvxxxuojfdehlqw€‡Š’‘’’‘””•“Š^;BX]Wgag}pVd\KDCGOU[VOUijnlcZL>70,,,,,,++,,,,6SKNVWaPIWQNXR[Xepz}}{xwxyzz{|{{xsjfcffjotwz}‚„‡‰‡…€zqdSC725IVaoyƒ…‚€€€€€€~~}|yvvxxvqldWI;2-++,,-,+++-29AJT\bfhihhiiigd_YQJ@725=BJS\bhnsw{|}{zz{…††…ƒƒ„…†‰‹‹Š‰ˆ‡‡‡‰Š‹Š…~tia^`hlsy|~}~~{wodYJ;4-,/6CXdt€ˆ‹‹ŠŠŠŠŠŠ‰ˆ†‡…€xmeYURXajptrrqpqqstssrpmkjijjhhhfd^<<;;::998766665533331011021146674310//..--,,,+,,,,++**++)))'(())&&%%%%%#%%%%################""""$$""""""""####""!!!!!!!!!!!! !! ! ! "!!""#&,9EUcnx}zreYK;669;>>CHLRTX]adfhigc]UPJB=8766654344445544554433333211//......-------..../11001243334455554555443688:<>@BDEGGIJKLMNNOONPOQSYbjqyvttsrqsvy|}ywrmijinuz…‹Ž‘’˜™˜˜™š˜“ˆ„kSy€yob]LEV^YUQOMOPRYa\QC71--,,,,,+,,-*-5ZgWTaVcT:GLSX^hu|}}zzywwxz{{{xslgcccfiloruyy}……ƒ€zrdTE:58AN[gr{ƒƒ‚€~€€~{zyvvwxwslcVH;2-++++****+.4;FNV\cfghgghhhhd_WOG<59Pdmx~}zzyxwyzzzxuojdbbbbgkkosuxz}‚‚ypeTE96:CP^jv„…ƒ‚€~~~}{zyvwyxwslcVH;1+*********06=GOW_fhiggghhgfb]WOG?<@DIQX\bhllruvvsrswz}‚…‡†ƒ„„„‡ˆ‹Œ‹ŠŠŠ‹‹ŒŒ‹Š„~uk``flrxz|}}~~€~zskbSD81,+1;M_n{…Š‹Œ‹‹‹ŠŠ‰ˆˆ‡†|si^SPR[bjrttrrrrrrsttsqonlllljhf`[V;;99999876665343221///.//2278<>BB@=9554211/....---++*)))))((''('&&%%%%$$%%%%&&%%$$%%$$##########""""""""""""##""""!!!!!!   !!!!""#%&*3BN^ht}zsjbUGABCHLPTWZ]_bdefgeb\WOGB;85455556655445566555544442200000/..----..----///011011212222222423435678<=>ABDEEEHIHIKLLMOOMNONNMOPU^grvvtststtwy|}zuoljllmpsz„Š”•–˜š™—•‘‹„€|{{}~€{tk^RNUOPUVVMGKOSSTWRG;4+..-,,,---,..05:=QNQEDOPC>Pkgw|{xwwyyyzyxutlib[]^aeigmpquwy{~}wlbRF89=HTbny‚†„ƒ~~~}|{yxvxyxwslbVG90+))))**))+18@IRZafgihhhhggeaZUNFBAEJPY[_dhkmprqomorvz~€ƒ„…„„„„‡‰ŒŽŽ‹Š‹‹ŒŒŒŠ„~rgbbgmtyz{}~€}xri]NB7.+,3?Obrˆ‹‹Œ‹‹‹‹‹Š‰ˆ‡†yqg[QPR[clquvtsrssttttrrppolkkkfc\UP;;:9998876554333221///./.16:;@CFIFDA><;855431120..,,+***))((''('&&%%%%$$%%%%&&%%$$$$$$##########""""""""""""##""""!!!!!!   !!"#"""#%'/:GVcnx|wrjaSHHKNRVZ]`abdefedc`\UNF>966666666655555566665555443310000/..----..----///0//01011233545555555789<=AACCEGHHIIIJLLMNNNMLMLKHIJOWblsvusqrttwy{|zuolihfgglpy|‡“•––˜–’‰‚yz|~~~yqj]UURRNNLNIDGOPNJMPJD9----,,,---,--.3=PWPLCISQCAOmiw|zxwwxyyzxwtslg_YXZ`fknolnnorvy|}{ukaPD89@JVdq|ƒ‚€~~~}|yvvvxyxwslbVG90+)))))))),29AJS[bfghihhhgfb_YUMIDGJRX^_bejllmlkiilpuy|ƒ„††„„„‡‰ŒŽŽ‹‹‹‹ŒŒŒŠ…~rhcfjpuz{|}~€|wqh]N@6.+*6CTdt‡ŒŒŒ‹‹‹‹‹Š‰ˆ‡„wndVOOT[dlttsssrrrrsttssqpoonmhc_WNI<<::99888554544121/..../036:=AFJMMKHGEB??<<;87765411.,))))(''&&&%%%%&&%%&&%%%%%%%%$$##$$########""#####$""##$$""!!!! !!   !$$##!!$&+2?M\ht~|vrkdYTRVX\`ccefffifda_XQKC<6687667777776656668976553333100000/-./....-/./-/01224455566866786688;>???ACDFFHHIIJJKKKLJKKKMLKJHFCDIS^iqtvtsssuwxy{zuqkfd`_behpw{…ŒŽ‘’–•••“Ž‡~yyxz{xsnha^[YVTMMJIJFCGKGCHPMLH/).-,,+-,----,06FXQQIQUGA=>Jky|{zwvuwzzwuurldZWWY^gmcF@Dcknquz~{uj_PC9=DO]hu}‚‚€~~~~~~~~}~{zwwvvwxxvrkaSF80,(('''(()-4;BKS\cfghhiiiifc`[XRLKLQV\_ceiiijihdddintx{‚ƒ„„„……‡ŠŽŽŒŒŽŽŒˆ…tkggmsvy{|}}}~~€xofXK?5-+.8FXjw„ŠŒŒŒ‹‹‹‹‹Š‰‡„~ulbWPNS^gmttssqqrrtttuuutspoolf`ZQIH<<;:98875554332210...../036:?DHLRRPNMKIHFDDC?@@><;9952-+*))'&&&&%$%%&&%%&&%%%%%%%%%%$$$$#########"#####%$$#$$$$"!!!!  #"##"!$&'.8FXepz}{wrle`^^]adffijiiigc_[TMF>999886677777777888:89976643301000000//0/0//00.144456668::<=>=>@@ABBDDFFFFHIJIIIKKKKLLLLKKKKLKLJHGCB@?CLYdqtvtssttwyyyxvpib\XWZ\`krx€‡Š‘••–”’Žˆ|yywvtnecaZYWVYWMKLJE>??>>:64221/110/21132134577;<<;:9=IUcotusssstwwyzwtpe\SPNPS\cmqy‚ˆ‘‘““’‘Œ†|xwwwpf]YVRQMNVQFGFC;;?CGHKMOMOQ]@#*,-,-,,,-,--0<N_o}‰‹ŒŒ‹‹‹‹‹Š‰‡†„{qf[QOQY`iqttqqqqrssttuwwutrolg`XNHEJ::8877775442220/..------/247=BGKQRRRSRPONMNPPPQROPLGDA=940+)('''&&&&&&&&&&&&%&&&%%%%%%%%%%#$$$$###$$%%'%%%%%%%$#""!!    !!!!!! !!!"%').4>JZfq{€|ywtussqpooppoojgd^UNHA<;999988889989;;=BFIJMNKIC@853223333467:;=`k__K:96Maqy}ywwwuvwp_V_hiea\aS=EH?KX`YW[fryxuneXL?@EN\it|‚€€}}}}}}|~}|zyutrrsutqoh^RE80+((''&'),19CKS[cghhhhhhiihhfea][[[\_acec`]YWTRW^ekqtxz~‚…ˆŒŽŽŽŽŠ†€xpmntxz{|{|}~~~€€}wqgZK=4//3?Rcr€ˆ‹Œ‹Œ‹‹‹‹‹Š‰‡†zod[QNSY`jqssssrprstuuvwwwwspme^ULEDP888877766431111/.---,,,,,.059?CGLPQQQQQPPOQSTTVVSUSSPKGA<52-)('(''%%&&&&&(''%&&&%%&&&&%%%%%%%%&&$$%&&&&'&%%%$$$$##""  !!! !!"""$$&+/4;DQ`js|~|zzzxvxvvurtrrnid]UNF?;:;;::9999::::=AGPTY\_`^[UMA654549;=@CEFIMMOSVVZZ\^_bedghiknmorssttuvuvvwwvvtuttrpooligd_[VOKGD@=87434GR[enu~…Œ‘“‘‘Š…zxvuwvolf^XRVWTQKHDBACBDDILNTSLMJDl\9+---,/2///1@\rm_[C98>Xf]p|zxvvvuvo]]b^`[XZU[I^F@D]VMQblqurspgYKBBHS_lv~}}}}}}}~}{xvurqqrsrpmh]QE82+)((&'()-45229DVgv‚ŠŒŒ‹Œ‹‹‹‹Š‰ˆ†xnbYQNS[bkqrrqqqqqrrtvwvvwwuqoe]TJEHU8887776534311/00.---,,,,++.269>BGHJJLLMLNPPRSSWYZXXXVSPLGB:4/+)(''&%&&&&&('''(((''&&''&&&&%%&&&&&&''&&'''&%%$$$$$$$"!  !!! !"""#%%(+/27>JWblu}~||||z|zzywwuusmg]UNE?=<;;<<;;;:9;;@EMV^eknprqoj`QC;:=@CHIMQTVXZ[]cdgikmnprrqttuwyzyx{|~}~~~~}|}}}|zzzywtpjf_XPJB=94348>FR^jsuussstwxwxxtmcP@228BNXaju}†Œ‘’‘‹…|xvvwxxuqjha^XSPPKGFFGEEDFHIOYSDBBJ`h6)+--,..11;OhhVWO999J`eS]{{yuuutoh^c_VWUPOLOb\]]EOR_gsmtp}uqdWJACJVcox€ƒ~}||}}}}}{{zyvrqqqqrrpmh]QD70,)(()'(+.6>IQX`fhiihhiihjjjihfa]]]_aa_]YTQMJLPW`hmrwz{~‚…‡‹ŽŽŽŽŽŽŽŽŽŒˆ…~uttx{{|zz{}~~~€€~yrh[MA833;GXhxƒŠŒŒ‹‹Š‹‹‹‹‹Šˆ†xnbWRRW_fkqrroopqqrrtvwzzyxwtpi`TKGJ\99976553331///....,+++,,+++./379=ABEGGHIKNNNPPUWXYZZWWVSQIC>84.+*'''&&&'((('((((''''''''')%%&'''()(''&''''%%$$$$%%$$"!  !!!!!!""!"$&(*,/26>><<=<<;=>AGQV_isz}ƒ„ƒ{qcUHEHKOSX[_chhlmnqrstuwwy|z{{{|~~€‚ƒƒ‚ƒ„…„„„‚‚‚‚ƒ€}zuoibZQLF@<>ACIR^hquutsssvvxyyxy{{|^/-?IS^jt|…’’’ŽŠ…€|ywxvxyzxxricWVQRNMIKHFHEFGIMVLCADB?UK$*/,//1<`SKQP\SN9;;Q_jketzztswqeb^ZRPSQJJFDRSdaZJNOWbjih_WlbUICEOZhp{€~}|||||||{zywtponnqrqolg]PC70+)(((')+18BJT[bgiiihhiiijjiigd___^^_^YWPLIGGKQXbjpuvz|}ƒˆ‹ŽŽŽŽˆ…xuux{||z{}}}~€€~|xqh^QD;44>K]m|‡‹Œ‹ŠŠ‹‹‹ŠŠ‰‰ˆ†€xmaWTT[agmqsspopqrsrsuwz|{zwuohaULIPb8876544200////..--,+++++***++-/1488:==@CCEFHJJPSUWYZ[XYXXRMIB;62.+('''')))(())))((''''''')''''(())))('((''&%$$$$%%##"!  !!!!!!""!"%'(*+.25:@ENXdjqsuy{~}~~|{yyuld\TMFC>>=<<<==>?BHOX`kt~‰”—••”†|reacgkptuwz|}}||}}€€~~}}}}{z{{{{}~€‚€‚‚€€‚ƒ„†‡‡„†‡…„€|zvsolgd_\\_fksuvuqsrstwƒš£¡¢¥«®¨h,>OZiu€Š‘•“‘ŽŒˆ„€|{ywwxzzzwnkcODT`XYQLMKLQRUUPJBOFE<=:BcXJVF0^ysrnW>QUQ:;JY_VivrpvxvvriURW]THJJJLRTMT[L[UV\U@`g^_nbRGHKValv~€~|{{yy{{{{|{zwsponllooonje[OA80+*)((().6>HQXbgijiiiiiijligida^ZYWXUOHEA=>CIPW]fkqtxz}~~‚…‡ŒŽ‘Ž‘‘‘‘‹‰…|}||{{{{||~~€}{{rh^SG=89BSds€ˆ‹‹ŠŠ‹‹‹‹‹‹ŠŠ‰‡}vj_VRW_enrutspopqrqvuvx{}~~|zwqjcYXbr7766433110//..----+++*))**))))'*))),+-.10258<=@CEIMQUVWX[Z[YXSLD?:61.-,+++*)++)))))))((((())****))****()(('&&&%%&&$$$#"""  !!""""$%%&'+-.0469<@HNUZ`ddhnpsxz||~}€‚€€|ztnha[UMGEC@@DINV`hp|†”—˜––—‘Šwmknrsuw|~~~~~}}}}}}||||||zzz{{{}~~‚‚‚‚‚‚‚‚€€‚ƒƒ„†„…†‡‡‡„ƒ}zwwtpnmrtwwwspnnx„—¡ŸŸ¡¨¬°±®’48IZiu‚Œ’•“‘ŽŒˆ„‚~|yxxxzz{{yrbNCPSS^XQQIMUVWQN>:;?DLSZagmsvy{}~~~~‚…ˆŽ‘‘‘ŽŒˆ†€}}}}{{{{||}}€}|zwri_UJ@:;EWfv‰‹‹ŠŠ‹‹‹‹‹‹ŠŠ‰…ui^VR[bhosttspopqqsvuwz{}~€~yunh`bmy775543120/..--,,,,++++***)))'''())))**))*-..0359:?CGMSVY[]__]ZVPJC?:631,,,+**++,+++++****)+,*,,,++**++))**('&&&'''%%$$##"!  !!!!""#%$%((,/0479:>@DJPQTW\cginqvx{~€ƒƒ}zvohaXVQKFGKRXagpz„Š“—————’Œxssvwyzz{}}~}~}{||{{||zzzzzxyzzz}}~~‚‚‚‚‚‚€‚€€€‚‚‚ƒ‚ƒ…„„„††…‡‡„‚‚€€}}}}~|xsv…—¢ŸŸš¦«®¯®«šA5GYft‚”•”‘Ž‰†ƒ||zwxwz{|{si`SSR\hbWTLNWXTRK85IC=;:7:>Thf^D,`UUoVRQZI<>KUH;V}zZTabfd_]ZasiGGJJILLTXXPMINQdc`SR_cnmNIP^hs{€~|{zzyz{{{{{ywsqnllklooomjd[PB70+*)*))/4CHOUW\^`^^\ZUOID@=731/-++,+,,,,,++++++,,,---,,,,,,+***)'''''''%%$$##"!  !!!!"""#$%((*,/257:>BDDDEHLSY^bfimovyz€‚…‰‡„‚|yvpid`ZVUWZahny‰‹“––””“‡ywwxxywz{{z{{yxyyyxxuuttrrrstuvvwwzz}}}~~~~||~~€‚‚€€‚ƒ†‡ˆˆˆ‰ˆ†……………‡…ƒ€“ž ›ž¡¦ªª¨¦¥ž–T1HXguƒŒ’•”‘Œ‹ˆƒ||xyyz|~}|xrd_VWadb[RPSPRVPK7.38;;88>CLbqkDVvkYTZm_P@>?QJA@B:9<=<<<==GOQe\BEJKKKHOPUSPQRVWP=MOTTabWJVakv~~zzzzy{{{{{{ywspmllkloopnjd[MA60++*)+,27AJRYagjkkjigfffdbb^ZVSOLGB=77337>??@DGLQW\`cgimpuz~†‡†‡„€}yuroieddfkpx‚ˆŠ‹Ž””“‘‹…}xtuwuvvuuvuuwtrromkjjhgggeffdfiklmpqrstuuuutuuttuvvwxy|}~‚‚€……†ˆ‡‡‰‰‰‰ŠŠŒ‹Œ‹‰“Ÿ›™› £¤¤¢œš–”‘s3DXguƒ””““ŽŒˆƒ}yzyyyz|}}wsic][ddecXXXYVVSC62/7=<:7>KSxx]HPhpjgXUQQ@A?<7>BDFGGFEGFFMTF?BBCMPNKKJIIOVWYSEEO68FUUGCXMXdmx€}zyyyy{z{{{zxvsomkkklmonlhbYK?40,+,,.18@GNU\djljjhhhgffc_^ZUQLHB>:4/136:@DJQY`hmswz}~~~|~‚‡‰Ž‘‘‘‹ˆ„~}{{z{}}~~}}zyvrngbZPGBEPao}†‹‹‹ŠŠ‹‹‹‹‹‹‹Šˆ…}rh_Z[ckswwvspmnqqqrtvwwy~ƒ‡ˆ‡…‚||ˆ334211//.,,++,++**((((((''''((''''''&&''')))******,.19?DLOVZ]_aa_ZXXUQKGB>730..-...-,,----..........---,+++*''''''&&%%$$#"    !!""###$$%'')*-0377;<<<:CA=GDB=<;;88599@TNF@?EJJLKIFHNYb[UMFJUWNEKJEWNU\eqz€~{yyyyxyz{{{zxtqmmkkklopnlhbXJ?4///0259=DJSZ`gkkjjigfffea_ZVRKDA;60.,/25=;8776789>ACEHKLPSUWYWWVUVTSRQQPPOOOOQTX[aejovz}„……„„…………‡pŒ”™›—‹}~~|n|ŠŒ‹sLYgv„‹“‘‹‰‡…‚iamcz…‡ˆ‚wjb\VTa\FCHQRHRb]L::;6565649E@6Xh_UNTYOF;?<@AEI=4c{nWdjMKLHJIKMJFCBBAAHLMMKE=?@@KPEWX\_blu|}}zxwwxyyzzzyxzzrkkhgkklookf`UJ@::;?BDHNRVY]bgkmmiihhffd`]XSNH>5/,)))*.27::>BEJQSY_dipw}€ƒ‡†…ƒ‚ƒ„‡ŠŽ‹Ž—œœ”ŒŠ„wsƒ{r|{{‚ZZguƒ‰’Ž‹Šˆ„†‹wpqro‹””‘Œxkoh]Xfc][FGOZcmf;MG:78678659BDEZ`TOHIJ@@=?BNZcH<2Wzm]MPXRNJMNJGFLMFAA;;?DGIIFIJLJOO?XXRbhox~~}|ywvvwwvyxzwvohoz‚~sljorjig\SLHCEGJORTWY[`fhjklkkjhgfda_[VQJA8/+(()((,38;BLSZagmrwz{€€€„†Š‘ŽŽ‹ˆ…ƒ}}{}}~~~~}|{xuqmjfaYSMQ\lz„‹ŒŠ‹‹‹‹Œ‹‹Œ‹Šˆ|rfcgisy{zvvsspooopqrronquz…ˆŠŠŒŒ‘‘1//.--+*+***((''''&&&&&&%&&&&%&&%&''''(())**++********+,,,059?EIMSY^bdfiif^TH<3101/0111122110222111100//..-,++*)))(('&&&%$""!!  !! "$',/00/-+'&%$&&'(*+/359=@CFIKOPPQRRTVXZ\]]^aejrx€†ŒŽŒ‡……†ˆˆˆ‡‡‡‡ˆŠŠŠŠ‹‘”““‰|l\QNLF?;9531./0....-/2336<>BEHKMPRTWWWWXXWVTSRNJFD>:41/..28=BIOU[`gnvz~}€zvy‚Š‘™›™•‘ŽŽŽ‰{nsmly€Ž›–v~v€Ž‚zƒ‹‘“tp}px’”‚cagjcU\[\PPOIZdX9MK=8878;76=?GcTN7<68=FDAACKW]CF;8=AGIIJLLNOQXZXYXZepy}}|{xwuvtyxssvpfJL[p†™`X\SGK[^YQLHJLQVXZ[^cfkmnmnljihgecb`\TMG=4-*(()((,27=ELS[ahntxy|€€€„†Š‘ŽŽ‹ˆ…ƒ|{zz}}~~~~}}{xsolifa[URU_m}‡‹ŽŽ‹‹‹‹Œ‹‹Œ‹Šˆ{sifimtz{zwussrpoopqppnkmqv{†ˆŠŒŽ‘‘‘0/..--+)*)(()('&&&&&%%%%&&&&&&%%&&''(())))))********++,,---.06>>960,)(')))*.169=BFGJLOPRTTTTVZ\`deeefiqu{€…Œ‰‡…†‰Š‰‰ˆˆ‡ˆ‡†ˆˆŠŒ’“’ŒˆzhREA=9731000../----..048<;9;@E<98:FC@G?:987L@ACEJUSFI_]SZeRRX9BhrhUIFDABB?:7;@CGHIKMPRRUX[[X`foz~}|zwvwuuyt_`whbkrnna`JGFINNEYb\UPPQVY[]`deimopomkijhhgee`]ZTLD;1*)))())-49?FNU\cjpuw{}~€€€„ˆ’Ž‹‹‹ˆ…ƒ€~{yyz}}}~~~}{yvspliea\WUXbp~ˆŒ‹Š‹‹‹‹‹‹ŒŒŒ‹‡€{slkmrw{|zxtssoonnpqqnkhhlpv~„ˆˆ‘’/...-+*)()('((&&&&%%%%%%%%%%%%%%%&''(())))))********++,,,,,-,.29>CKQY`hmrqkcUH:32222223344444333333321/.....--+***(('&&&%$"!!   "%*/39@GKKKGC>70+)*,-/157=AFJLOORTUVVXX\`ehlmnnnprx}†‹ŒŒˆ†…†ˆ‰ŠŠŠ‰‡‡†„††ˆ‹Ž‘”Œ‡{iQD<77353//....----0036:>BDHJNQQRVWXYYZZZXWUURNJF@951-,,,++,,-05;DLT[beuŽ”—’”•™žŸ˜ykomŽ’‘’’’¡¶»¹»½½½¼»»´–‡ƒˆvpihjk_]`vwy{kUVrƒnUZjmjll`]TQTNJSvu<413;<<:@KG847?FDLE;<9;J=>A<8:<@CDGIJKLNQV[[X]gqu|}xxtsvvmUsmld]^inf^CDNPQJDH]le`ZUTWZ\^adfiloqqomkijhhgee`[XQJB90)()))*,08;AIOW^ejpvx{}€€‚…ŠŒ’ŽŽŒŒ‹‹‰†ƒ}{yz{{{|}}}zyvsqnjgd`\WW]esŠ‹‹‹‹‹‹‹‹ŒŒŒ‹‡€{spnquy}}{wsssponnnnnkihijmqz€…ˆ‘’--,,++('('''&&&%%%%%%%%%%%%%&&&&&&''(())))**********,+,,--..--.27:?JR]hmqsodXK=42222123444443333443212311/00..,,++))('''&%%#"!!!  !"#$(/5=GMRVY[VRJC<60./0449=CFJLORRUUWZZ[\`bfmrtsqqsuy~‚…Š‹‰†……†‰‹‹‹Š‡…„ƒ‚†‰Œ’’Ž‹€o]RLD<84541--//---.2268>BEIJLOPSSVWYYZ[ZZYWUTPLHB=61.++,,,,,,-148?INTYm‘™––ŒŽš”‘}]5c…„‰‘””Ÿ¸¾¼»¿¿¾½¼¾¶³º¤„ˆ†‰|_Rgcspgwz{zlW\dXL[cfb]agWHGNMLZmhJ:9ID><8DA9673EYSA39<><;:>BDCDFGFHJPSWY[]gqwxzyvtslrnmorfG6@daHKKLLHFL\kpkhc]YWYZ_achikopqpnljhhhgfdc`ZWOG?5-)'''+,04:ADKRZ^emrvy{}~ƒ†‰Œ‘ŽŽŽŒŒ‹‰‡…‚€~|zyxy{{{|}}yvsplihfc`\[Y^gu‰Œ‹‹‹‹‹‹‹ŒŒ‹Š‡{uqruy{~}zxtsrppoooopmjgfflpw}‚ˆŒ‘’--,,+)''''&&&%$$$$$$$$$$$$$$%%&&&&''(())))********,,,,----../1/.24;DLXbjsvqiYL=53323234444556666444323331/000.--,,))))('&%%#"!!!  !"$(-5>HRW^degfaYQGA;5457:?CGJLNQTVYYZ[[\`ejnrvvvttvyy~‚†ˆˆˆ„„…†‡‰ŠŠ‡…~zyyzƒŠ’‘…ylc\WPID:71./..---0168:@DHIMNOPSSVWYY[[ZZXXURPJE?94/-,,+,+,-.0368FEA<5COQ987<GT`jstqj[M<54433245544666644555434331/011/.---*)**))'&$""""" !#&+3;GPXahnoqqlg]TLF?<:=ADHJKPQSVX\[\\\`chnruvvvuvvx{~‚„‡‡†„„†„„…„‚‚~yvvslou|‡Œ’’ŽŠwqmia[VOH?93/-.-/038=@BFIJLNPQSTVWYZ[[ZXVUTRMIB<72/-,----./028=@FKOTqœœ ¤“rYghu€w”Œ‰Švvo»¾¾¾²°¼¾¿½¸“v§º¯…˜ƒQ6OVO@KCFC^YLQAE:nyM0=Bprwxteh^JGGDC@:78:<>ACEFHHJPUUUXQXmvzxxsHZspgZafeqo_TLJNNLOflkllmnnkhd_\]]_`ccgiikmkkjjhgfedc`]XRKC:1,)'&(,04;>BGMV^bhptwz|}~~~}~ƒ†Š‘ŽŽŽŽ‹‹Šˆ‡…}{ywxy{{{zyxurnjgefdc`^\\dnyƒŠŒ‹‹‹‹‹‹‹‹‹‹‹Š‡{usuy|}~}ywttsppoopoolifceintx~„Œ‘‹++**)'''$%%%$$$#########$$$$$$$$&&''(())))****++,,-------...1311238>GT`jrusiZJ=4343335665566665555543433211010.---,++*))'&%###""!#(-5@IT^fouyzxrlf_UQMGEGJIJNOPTUX[^]___aeknotvvusstuy|€‚„…„††ƒ€~~~{wsplgc^agq‡Ž’‘Œˆ~xxuqnkd^VL@91./158EITRMKKKTMKEAB?>zV12?b_fvvsk^YQFFFCB>;98;=>BDGHKLOW][YYdmvywuq;Qsd>[okvwNCSOJEQbihgjjlnoomgc_\\\\^\\^adfhjjjiggfecb`\WPIA80+('(+.38GU_jtyrh[H;33356556666666666666655554432110.,.-,+*+*'&%$##"!!!!#%*18BMW`hry}{ysoic^ZWTSNNPTSSRTWY[_``aacfikorrtsrrrtvy}~‚„„ƒ~{xtplgc_XQORYeq|†ŽŽ‰€|}||yxtnibYNB;65:=@@DGJLLMNOPRTVWXZXVWSROMID>:630..//..1358<<>@BEFIJOPRU[^Y\bnvxwttqusdQdovmK?GKJGWljihiklknnmjhb^\ZZWTRSUY\_fhjjiggfeca^\WNG?5.(&&*,059<@DHOV_fmquz|{|}}||~ƒ†‹‘ŽŽŒŒŽŽŒŠ‡…ƒ{yyxxyyyxxvspmhebcecda`aelt€‰ŒŒ‹ŠŠ‹‹‹‹‹‹ŠŠ‰‡…‚{vwxz}‚{yvsrqoooopolkgebdglpv}…Š‘‹‰*)(('&%%%%%%$#!!"""""####"""##$$&&&''()))*)*++,,++----....../321238>FS^itxsh[J;54456556666666666666655554432110110-++**('&&%$$"!!!##$'*19CMWbjrvyxvpqlhed^\[YWZ\ZZY[\_aeffffhijlpqrsrrqqqty}€€~}yvrmhd_[YTGABDMYgv„Ž‹„}{}~~}zvskdYNC??@CDFIKLLMNOPQTVWYXURSNMMID?:842/////1258:>@CFHJN^•ššžŸ—ŒwW\‰¤§–„tbYadIZ¾®†²µ©®nq‹¡š}e™Š™‹gagf]cdYTWf\Teiic\bR;<>D=E=8;:H[fLKPCCBG`RUXAF=W=>CGPXKNqupg_WNJCACC@=<>@ACFMLNVYWZZ[]`hrxyvupoqpnnryfJBJJCAJbefhhhhklmlkie_[YXTOMJLKPY_cgijigfedba]ZUNG>4+'&(+.059>@EKRXbgnswz|{|}}||~€„‡Œ‘ŽŽŒŒŽŽŒŠ‡„‚€~{yxwwxxxyyvspjfb`adddaabgoxƒ‰Œ‹‹‹‹‹‹‹‹‹‹ŠŠ‰‡…€xvuw{~zwusrqoooopolkfcbdfkpv}…Œ‘‹‰)(''%$$$##""""!!""""""##"!!!!"$$&&&''()))*++++,,----....----/133128>HT^juxrh]L=56665576666666666666666665444221000/.,+*)'&'&%$#"""""#&+2:CMW`gnopoomljjjhgcb`^^__a_cefhlkjiijjlnqtsrqonpqvz~€|zxvqke_[WRNJG@>@BFP`qŒŽ‰zuy{~€~}yuoeYPIHGGGJJJJLNNNQSUVVSQNMJIGD@:6440012357:<>@BEGIJPS„ž˜œŸ˜’vYQx–˜~wf‡_( }½»©„}—¬®w[e˜‰>Z“ ˜zUY\]twm_\]ZSVmhghVHE9=MSF@>Td\`u[FLNNGCUA=@=CA779DWS?5CBDC;@O\O>D?;9@JMUSLYjnqjZOMHB@BB?<:=AACFSUS]bba`_^bjsxxwsprpkHTxvFFMNFLgjfdfeegijkkklf`\VUQOGC@CDNV]dhiihgfcdca]ZUME<3,(')+/25:?AFLR[dipux{{||||{}}„ˆŽŽ‹‹ŒŒŽŽŒŠ‡ƒ€~}{ywxwxxxwvtqlhc_^^cdcbaejqy‚ŠŒŒ‹‹‹‹‹‹ŒŒ‹Š‰†ƒxsuw{}}|xutrrqnnooqpljgcbeglsx†Œ‘Œ‹‰(&&&%$$$""""""!!"""""""""!!!!"$$&&&''())**++++,,--......----.022128>DO[epvtkaP@77765556666666666666666665444223200/.,+*)'&'&%$#"""""#&+2;AKU\ceddcacfijjjhgecdceffhklmopnmmmnnnqstsrrppqvz|zzzwupkf]WQJHDDA@>>=>BJZl}‰Œ‹†|rgkqv{~‚‚ƒ€}{tle[SNKIIIJJMMNNPRSROKKIFFDC@;863322579;<>ACDFJLMQQdŸœ œ‘ƒ‚…cQf„ƒ†ƒƒŸ›Z*#x¼»´¥‚]|œ›ˆbVkŽ{lp–¤fC\_cz’’vmdb\haV\X=9?DKWYD9BBc\WbYKFC@:LT38>IJ<435>FJHDAG@E7@GT=@<=:H]X[]?EOM^^_ZO??@ABBB@=>AACFTYW_cecbabgltxwtsl`\rlf_TMCL[dsmhedcbcehjkkkhc]YROJC:76;7557879<>?BDFHKLMOPQTƒ£Ÿ¢Ÿ™‰pˆ‹uPW|{vs{ —@*#hº¹¶²¦…`dvƒƒi`}‡}pc„‹ODfrŽˆ’Š}|cacSRX\R3>KS^kF/05MSMQJIFDDDW;88=@732548DD;>?BAS@AF@=BJRHVgQQW@F@0OOMP@;?DFHB?>?@BBDHQYZ_fgddefinuyxuqpc]jgJHSNMYesnieccbcceeiihgd_ZRLG@9215:CLV]dfhhfeedca^[XQIB7/*('),046CKQSSOJIOTZaeghghhgijjmnpqqqrrqqquuw{{{zwttuvvwutrolga[SKD@>>?>=><<;<=AGRcx†Š‡‚ucOJOXbkrz…†‡…ƒ€yvod[SOKKLLMMMMHFCA@ABCBA=;<:9;>AABCDFHJLNOSUQScš¡ž—‘qY~‡t^Md}{vqu‚2**Mµ´±°«ˆrijmohu|mdtvb>>Xƒ¡™–•Œuf[UKMX_^GKBFZUD329>5:NIIC>;FS:589CFIFA??AACGHU\YZchceagmr{wsj^gg^^RGF::KS_rmiecbadddhgggf`]UMD=5-,.3DHKKHGFIOV[^`adcegijjkmopqqsrstvwxz{{||ywvvtrppolie^VLD?@?=>====;;;;G43BKK?3A>?713059DC>IQY`eggeddcb`_\XUPG>4-)((*,058:=BIRZbjpvvxy{{|{zz{}†‰ŒŒ‹‹Œ‹‹ŒŒŒ‹ˆ„}|zwutuvvvutroje_ZUWY]`adhptz„ŽŒŒŒŒŒŒŠŠ‹‹Š‰‡ƒ}tppptvxzxurqqonnnmmnlkkiifghms~‡ŽŒ‹‰‰$!##"!   !"!"$$%%''())))*++,,--......-.////....////029AKW`ekpnh`ULF>;:86644445555444455466665323321/-,,+*((((&%%$"""##%&+039>@CBBCGINSWYX[\\]_aadgikmpqtstuxyy|||||yzxuqnlifc_XPGB?>>==><=<<::9:;=CM^p€ˆ‰†|jS715=HO^ju}„‡†‡‡…‚~{vpi[SOMLKHB?;;;>AEFGGIIJJJJJJJJLMNOQRRRRSWVv£ž›•Œn:Ic‰ˆxXNYrxzx„`854.z¤Ÿ¢¤–‰~mdXNB?D]jhcJ@;:;>@A@>??CFGHITahk\FNN`kZOT]T4/@HCAFGGTrrlheddeccdeihe`[UK@5-())/6>IQ[bfhgedcbba_\XTOE=3-)((*,058:=AJS[bjpvvxy{{|{zz{~†‰ŒŒŒ‹‹‰‰Š‹ŒŒŒ‹ˆ„~|{yxvtuvvxwvtnic\XTTX\`aejqx~ˆŽŒŒŒ‹‹‹‹‹‹Š‰…€yollnpvwzxurqonnnnmmmmmkjjjhjov€ŠŽŽŒŠ‰‰"!!! ! !!!"!""#%%''())))*++,,--..//......00......,//15;DNYchopkd^UNGC@=:85331444444445566666544332//-,++)()''&&&$$##""$&'-15;:<;=?CEJOQTSSQQRTY]`adgjmprruwy{}€€€~{xtqmid`_YRLE><<<<<<<;::999999;>IYm~ˆŠ†€q\>*+/7?KVdnv}ƒ†‡‡…„‚yri]SLKGDB=<;>CGHJMNNPRSTSPOOOOOPQRSRSUVVV|£ž˜‹j6Ua{Œ}dRLfu}‚‰V9788I”˜ž£”…}wl`ULFI\c\ZGAFJr™•’”žžzZs‰yli\ZV]lqyvjb[TUVUU\hPC?;FNI;019G7--/48Kf`URP=>CJA=>JMFDMNI>ALVVVSWF:=A=:>AACEGEAMRQ^SOY\IERZZdY[TC:>9EG<0CHLMMKKLLMOQW[adgjmprrxyz|„‚~{xsokf^ZWSMF?::;;;;;;::99887777:=DRey…‰†‚yhL0*),09CMZfqy~‚„„„‚€vmbUKFA@CCCEHKNQTUUUVWXWUTSSQQRPTSQUTUTZ‹žœ—s,>A=8:;?AABDCCFJJIMRPHCPQBEJITS@?B@AKQfktyxunftxwdX]T^^cfea]UF;1((')07@KU\dfggfdbaa`_\XRMD;2,)))+-1578ADFHHGJLNQUZ[_cfknrrsw{|€~€}|ywqngc]XPMGB=;:::9::99998876666679>K_r€‰ˆ‚}nW;))')-4=IS]hoxƒ„„‚{uvxpaTIEDGIJMRUY[]^]YWXZ]ZXVVUTSRSTRSSV]b•˜˜•Œ}+9N`{ycNKDIPRIA=>>=<9n„Šnmjb_WPPSQJMPIJGEJvˆ„‚€ˆ’‰~[AIOMLJE>5?[ajte[NRWHJL=?:52892550-*'*1;?>TL=74774395358;;:98==;:BQDA?EMGCA=??BGIR^Z]dTV^\J=RZQ:@LGEPapvsswtssmpieJSAH]cegea[SG;1+&'*19BKT]dfggfcbaa_^[WRJC92-*+),.0468=CLW`gmrvxzzz{z{{z}ƒˆŒ‹‹Š‰‰‰ˆˆ‰‰ŠŠŠŠ‰†„€~|xvvuuuuuvuuoje]VQOQW^bfkqy}„‹ŽŽŒŠŠŠŠ‰‰‹‹Š‰†‚~vmeeejnswwtqoqponmmnmnpppnmnlpu~‡Ž‹Š‰‰‰!!!!   !"#$&&'(()**+,,,,.../////.....------,,.,,--047@FR[^cefeb`[XUSMHE=643222344445544433344100/-,++**('((&&%#%$$##$$$'+,-3789=??@CEFMRU[^`dgiklnquwzz}||zwvsojd]WPICA?>;;88889988887765555548:FUj{…‰†‚vdG-(&()),5>HU]gpv}|pe_f{xpeZONPVZbilnooplf_ZY[_^ZVVVTTRPOPPOOY™ —–”Œ318VnpSJPHA<;=@AAA@>7Er~sedaXZRKNNJKMLFGGJLYu‡…z{”ŽwF:ABCAA@>;6/2V€Œ‡vb`UQSI=D98849?=2.1/.-)8AECBIIYQjuMPipraIA@HIH=ThnpqrjZpfl^UR@@LU]chedb[SH:1+'(,2:CLU]dfggfbbaa_^\XRJB:3,++),.0158=ENW`iptvxz{{{zzzz}ƒ†Š‹‹‰‰ˆˆˆˆ‰‰ŠŠŠŠ‰†ƒzxvvvuuuuuvuuoje]VQOQW^bglrz‡ŽŽŒŠŠŠŠ‰‰‰Š‰‰†zrha`bgnsvvsqoooonoonmnpoonmnntzƒŠŽŽ‹Š‰‰‰!###!  "##$%%()()+++,--,..../.././/-----,,,,,,,-,,,.18@HQVZ^`abaa`]ZVPJE@:53322333344334333320/00.-+++*(''''%&$%##$$$##%'*,/37;>@?@CIMVZ]aiknnoopsvxwywvwutqnjc^XQJC<98;<::876567776666542333356=L`s‡ˆ…|pW9'%%'((,/7@KVajojdonSj€}th_]clv}€„‚~}}unghhie_YVSRPNLKLJIGL” š”ˆƒE.6Og\GIRJEB?@@@?>=26FQqnc]ZWVPOMIHFKLIIPRWZXix|†•ŽmC;<;899:9763/;f~††saSNICEKJ=26J;4,34/+*3?=DE953496JVI60,38:HSZckquxy{|||zyyy|€ƒ‰‹‹‹‰‰‡‡‡‡ˆˆŠŠŠŠˆ…ƒzxwvuuvvvvuusmgc\SONSY_cgmt|ƒ‰Ž‹‰‰‰‰ŠŠŠ‹Šˆ…€ymd]\`gotuusqonponnnnnnnmmnnqqv}…ŽŒ‹Š‰‰‰!#%%" ! !#$%%()()+++,--,......././/----,,,,**++,+,,,,16?FLOSX[]^`_`_\WSNHA:532233334433433321///0/-,,-+))'''&&$$#$%$$%%%'+059=ADFJKOU[^cfjnrrstvuwwwtsqomkif`\VPKB:756889::9867566655554331111237BVjy„ˆ…‚yeF,%'%')*06;DSbmhk]dxqxyxyrsrwƒ……ƒ†‚‚……ƒ}~xpg[QOLLGGECCCAzž•Š„ƒY.5A`VCHMJHEBCCBA@?^n^BXlg_XSRNOMKGHMMKMLTVWVXZo˜•„oUB8;:576421.--Dj|‰Œ€eTNJDAIJ/),/32<:,*19,'4?94343413400334331000149<:=CLD/).375BM;?UXQSV`mZ@DPb]KD@B[njnpmpvweEb¨’Kd]LBJQ`hhigb\SH:1+)*.3=GOY`dfggfdba`_^ZWQJB:322337;;<<@EMT\emswyy{||}{xxx{€ƒ‰‹ŒŒ‰‰‡‡‡‡ˆˆŠŠŠŠˆ…‚~{ywvtsvvvvuusmhbZQNOSY`dipw†‹ŽŒŠ‰‰‰‰ŠŠ‹‹Šˆƒ{rh^ZZ^ekrvusqopnonnnnnmmmmnnrrzƒ‰Œ‹‹‹ŠŠ#$&'%  !"#$%%&'()))***,..-.--..-.///.,,--,,+++++*,+,**+-/4;?DIMQVY\]_`b^]UNLB>7312233343333221111//..,,-**)''&&&%$#&%%%%)+/39;@EHNQVWX_fjmnquxzzyxwurpkkgeb^YVQMFA;74446578788665544444443211000023;Lbu‚ˆ‰‡rY=)%$%%(.1:GS[^Yacfoqqkiopu~‚„€~||xy}‡ŒŽ‘”•“ŽŠ‡{l[LA>>B><9;X˜‘Š‡ƒ€q4/=X[IKMKKTFDCE@>:_•jg`ecZSPLPOMGHJLMRMM[VKQTd–ƒl]QE;2247641.,*-He|ŒzeV]WQSOD::249?<007:+(+44644438:EOD=7542/--0.038:?>E>5/1:?@?85EURYW_mj[LNLZmS]etuls€twtyvUcODS[TAHWfgghhc[RG92-+,27@JSZaegggfecba_^[XTOJD@?BGHMOPPPSSX]ciqvzz{{{{zxyyy}ƒ†ŠŒ‹Š‰ˆ‡‡‡‡‰‰ŠŠŠŠ‡„‚}ywwuuuwwvvvvrlg_XNLOTY`elsx€‡‹ŽŒŠ‰‰‰‰ŠŠŠŠ‹†ypdYRU[cltwtspnmoonnnnlkkkknoot{„ŒŒŒŒŒ#$&'&" !"#$&&''()))***,..-------.///-,,--,,))++))**+***+,.36<@DGMPVX\_`a`_ZRKE=512233333333221111//..,,+**)''''&%$%$%(*+/49?DHLRVY\`deiptvwxyzyyxtpnigd`[WQNJHC>84311124455766543333333332111//-/015CXl~ˆŒŠ†|jQ;&%)45.0889759~–ˆ‚€Q=?Q_PMNLIJFCDB@>7Uš’‚vgVZ`YPMRNHEEJHKNNNYYSHJS’ˆq]IC43789952-***0J`|•–nadcsk\XSMD738DHI@7+/355326GLIKF67:864:A=8/+-7>AM]A2?CHBHG=GMQRU^gd]UVU`egIWorpmo„~hib[\[YMDI[`H;FOffijfa]SJ<2-,/38BKSZaeggggecdcddeeb_^YUXX[`ehillnnppstwz||{zzzxwxxz~„ŠŒ‹Š‰ˆ‡‡‡‡‰‰ŠŠŠŠ‡„~{ywuvvwwvvvvrlg_WMKNTYafnu{‚ˆŒŽŒŠ‰‰‰‰ŠŠŠŠŠ‡uk^SMQYdmsvsrpnoqonnnnlkkkkkmns|…‘ŒŒŒŒŒ!#&'&#  "#$%&''('(*+***,-/----....00.,,,*++*))**)))***)++++,.259=@EJQV[]__`a\UNG;52222223322210000....---+**(((''&&')-/38;BHMQT[_bfikpstwy{{zyxurolid`[XRLHD>;95211100000033333322222422222210.----.4C>;720)()**5Ld‚”“ˆ}wturcbXLKLB7;:75427<<:>??;:758DEK;.112>JOiqO/=RNLTQR_sZLRZgQ]R;=\fYOT`\^ZYb^VPQID@LJ@ENAEINUX]bhjmpstvxzz{yxutqojfa^XROIC=:623210/////00/02222112222243200000/.-,,-.16EZp‚Ž‹‰€yiO:CXWQRK]mpbeh]e`YdcRKE>DDEFGDI\mŽ•––‘ŒƒŠ„”™œš•ŽzYA751139AHPX_cfijlnpqsvz}€ƒ†‡…†††‡‰ŠŠ‹‹ŠŠŠŠŠŠ‹‹ŠŠŠŠŠ‡†{z|€‚‡‹‹‹ŠŠˆ‡‡ˆˆˆ‰ŠŠ‹‰…‚|zxwwwwxxwwutqke^SKINS\dkry€‡ŽŒŠ‰‰‹‹‹‹‹‹‡„|rcQIGPZeotutrpnnnnnnnmkigbdfhnv‹‘‹‹‹‹"$$" !!"#$&'''()*))++--------.....--,,,*++***))(()))*****))**++//48;511000..////////./1111112211132111/...-,++,./2BFDB=3&! !$'*+>Q`qƒ„tniu~tvpebcZ\G;<;961345369585D;;?;7;65-/9^pqeV]UTOODLSV]WSN\KEPHBZL^m]_`VM+.953674=MQcl_UGDOecihikkgc^VI?8354;FKTYbgnprvvz}ƒ‡‰ŽŒ‹‹‹Œ‹‹‹‹ŠŠ‹‹ŒŽŽŽŽŽ‹‰Š„€€ƒ„‰Œ‹ŠŠ‰ˆ‡‡‡‡ˆ‰ŠŠŠ‰…‚|{yxxwwwwwwutrkd\RIHMS\elrzƒˆŽ‘ŽŽŒŠˆ‰ŠŠ‹‹‹Š‡ƒym^MEGS]hqtusrpnnnnnnomkheccdhoxƒŒŽŒŒŽŽŒ  !!"#$&'''()*))++--------.....-,,,,*++***))))))**))**++****++-04:@GOU[beif]VJB8322122100011//....--,++*)*,148?CGNT[\bhkoruwyyxvusqplkigb^YTOKEA:62/--./--.-...../.../00000011000221110..,-,++++-/7E[rƒŒŠtq‚’•‘‹ƒ~{qXMQdZ`QJPele~|\QHEDDGFCAEFEGIV_’•š’’‘”—žœŽ”—ˆŒ‘‘’•…~|~…XhXJLKGC@A==Co“¡§›Œˆ}ukgdcaaMH]f`WIFEEC929H\hx_`XGDCEB?>8-"!#'*+,/:M\jw„‡‚xw}~‚zuj^OUH?CFZU<>31547Uid\V\[Q@;Cajh^\ZUFDL?AMXHVh^\NEK?G;?6?CH\ehxoNL=Aeffijkkgc^VLA:679?FOW`hotw|‚…ˆ‹’’’ŽŒ‹‹Š‹‹‹ŠŠ‹‹‹‹ŠŒŒŒŽŽŒ‹‰ˆ†ˆ‰ŒŒ‹Šˆ‡‡‡‡‡ˆ‰ŠŠŠ‰…‚|{yxxwwwwwwutpjaYPHGLS\elt{ƒˆŽŽŒŠŠ‹ŠŠ‹‹‹Š‡xiVIEJT^jrtusrpnnnnnnomkhebbchqy…ŒŒŽŽŒ!!!"#$%''()()**,,,-..,........-,+***++***))))((((((()(())*'))++-29?GNU_dgjd\RKB:55431200010..//.---.-,,.168?FJOT[_dilmpsuwvvtromihea`]YUPLFB;710.----.---.-----.-..//./0000//00121122/.++,+**))),0:Ndxƒ„z|‹‘Šƒ…Š†ƒ‚‚}zlX]nbVOe„‚w|n`OEAFFEHEFHJLJA?Sn‡•““˜šŽŠŒˆqŠŽ””Ž„{y{‘q…]HJKEA?;::J}—£§œekƒ~unlkjii•–’Œƒx\F>713Kekrnea]MHDDA??=2(%'),,,,-:L]gpv{†‘•„‡uoaQbVNWUXUX>5ECEBD16A:FOKFC0.;9,4JZSNP\UF=FKTLQILQUGEGJMVWHIXZM@EOIG8?C89DXny}ysR4=Qhgggijlhb^WNE=8;EPY_gkic[SMEA=876521010/.//----+-/1376/8Selsnga^UKA?A?@>81*.///-+*+:P[gjl“¡˜–˜†{kceebXTDVM<>B?IA0=;AF?GN:.?VD-5IT[JAJ<<>BMV_is}‚‡ŒŽ‘“”””””“‹ˆ„~{yy{~€‚ƒ†‡†‡‡†‡‡‡‡‰‰ŒŒŽ’““‘ŒŠˆ‡††‡‡‰Š‹‹Šˆ…‚|{zxwwwwwwwusog_UJFEKR[clw…ŠŒŽ‹Š‰Š‹‹‹‹‹Š†€ufSIJR[emrssqppommllonmjgd``cis~ˆŒŒŽŽŒ!!""$$%&&')*(**,,,,-,-........,+++++**))))))))''((''''''))())()))),19@IR\dikib\WQKGC>=;9533100...-,-//47:?GMTZ`cgmprrtsqqljhea\XTOLHEA=9510.-.++,+++++++++,,--,,,,------.........//.//..,+**)))))(*+5FZf|~†ƒqbphioWXeX\ggd__qƒ’„tlgbZMDFJPJEECDGIKLLOx˜’‘’—ž—ŠˆŽŽ…u}ˆy‹Œ{wv‰ŽqDLKIIC?>7ZŸ¥ª¥Š+4Mr‚}y{‹›¡ ”Ž†t]H;4/9MYbghc[VTOA>??AB>813310/-+*+5L_hdk˜¢¤¤¤¢˜~`VhqfUOaVE2<:DORK>/&4GWE2;GUcVCED5;F[S<737:EDHB1:AFab8CHPV]agjmptrpnljfb^[USNKFA=9311-,,--,++*++++++++++,,,,++++,,,,,,--.......//...--,+**))))))(+5Mpzyuqmii^\vso}wtfZ]bTKTbuŒ†uimmgVDAIHJGDEEFFHILMu–Ž‹—ž•‹Œ‹ŽŽ‹Œ’‡Ž‰zvv„‹‹‹zKGHFFEA>5c“¡§§¥šW2=?FI>Aj‡š™’…€ufZS;11>LXZ]^`XTPMC=>BDGB?966431.-)*+5Rb_]n€Š“ŸŸ›‹xixsh`[WZKGV`=94;AGFH89>H9DHNTY_ehkmnonmjfa^XUPLJE>;730/.-,,++,-++**++++++++++++++++++++,,,,,,,,--------,,,,,+**))))((+0a}oeivodi^f{zym]f`^|ucdlt|‹Œ“Œ…yudUKLLGDEDFFJMMJi”ŒŒ“›‰[r…‰‡Œ‘”‘†{yutŠ‘‘‹\DIHHCA<:rš£¨©¦št0<;974/P~‡‚yqeXPG5:BLRRTVXUOKHD<9AJKFD@;97651.+.17@V^[bkt~„ˆˆˆ‰…ƒ‚~uiUPUTUddLB7522;>7gZ3-0>NA6;<;;:<>BJOWagosvxzz{}€€€ƒ…‡ˆˆ‰Œˆ‡‡ˆ‰Š‹‹Š‹ˆ†~}{ywwxxwwwvvqkdZQFABHR\hqy‰ŽŒ‹Š‰Š‹‹‹ŒŒ‹Š‡€vj^YZ_gnuvtrpppnnnnnnnmjfdbabn{†’ŽŽŽŒ‹ !""$%&&'&'()**,,,--.--,-......-,++******)(((((((''((''&&&&((((&&&(**+/3:731//-++,,,,++-,++**++++++++++++++++++++,,,,,,,,,,------,,,,,++*))))*-3G€zqzwtwiikgju`MK[\egebd_g{}‡€“›££Š†xUCHHEGHHIHIIG`•ŠŒ™ˆ/9OcqˆŒŽŒ„ywuw‘šš˜•h@GFEA@=Ey™¢¥ª¦19;;;949fy{zvqg_XPLGGHIJNPNPRNJIF=4@NNKECA?:7642247:7CW]_dlpqsusvwrjhjfVIMUZQVXN12,9E?=;<<<>?BFMV]fmsvwwvwy||~€‚…†††‹ŒŒ‹ˆˆˆˆŠ‹‹ŠŠ‡†~}{ywxxxwwvutnjc[PE@CHQ\hq{ƒ‰ŽŒ‹Š‰Š‹‹‹ŒŒ‹Š‡€vj_[]dkquwupppnnnnnnnnmjfdbaep~ˆ‘’ŽŽŽŽŒ!##$$$&%&('()++,,,,,-........---,,+****))))))((((''(((('&((''((''''(*)+05=ELRX^acdcedaa^][[XTQLHEECDIMPV[_bdgihgda^[SOJE@:61--,,+++++,,,,,,,,,,++****++**************++,,,,--,,----,,++**++,,)+**.7Qdxdf‚…€ppffibOMLNMZgm_TOL^hpj€‰‘¥¨¡œ’xU@IFHIGIJHHHTŽŠ‹‹”Œ@;;>V~˜Ÿ£¦¦ž}.9=<<:54Jgkljf_VTPNKJKJLNONNNNMKGA5APRPIFFE?<96:;;8799LX]`ejjptvxpZGFHJIDDGQJFHG>;C26?ACIRYbhptwvvvwyzz{|~ƒ„„„ˆ‰‹‹‹Œ‹‹‰‰‰‹ŠŠŠ‰ˆ…~|zxxxxxvvwvsojcXNE?BHR]kt}ƒŠŽŽŒŠ‰ŠŠ‹Œ‹‹Š‰†wh__bjqvwwtrqpnnnonnoomjfeacfr€‰‘ŽŽŽŽ !"#$$$&'(('()+,,,,,,-........---,,+****))))))((((''((((((((''((''''''('*,4;@GNRVY\^^_aa`a__^\ZVSQPPPTWZ^_acgffb^[WSLGA;50-++**)+*+,++,,,,,,,,,,++****++**************++,,,,--,,,,,,++++****++)+,+0Nsysly…ˆŠ„}|vsyydUP[[K`gdb][V`cd~œž¯³­«¤Ÿ—‚G@FILMMLIDBG‰†‹‘ŽD4>>?E\y††„ztwy{—œ›œ™ŠN?EGD@?h“››Ÿ£ž}.68;:8617S__\][TMIIHFJNNNMMMLKLLLF@JRSVGDHIFD@<=BFB@>>DTXZZ[__ehnpne[TLB;<B01;CSL8@769?@@EKV^dlsyxyywxyyyyz{}€‚‚ƒ„ƒ…†‰ŠŒ‰‰‰‰ŠŠ‹Š‰ˆ…~|zxwwxxvvvusniaWMD?AHT_lv€‡ŒŽŽŒ‹Š‰ŠŠ‹Œ‹‹Š‰„}rg``hmuwyxvrqpnnnonnnnlihgdejv‚‹‘ŽŽŽ!"!"#$%'&'())**+,,,,,-......--..-,,+****))((((((((''((((((((''''''''(())'',07@@@Srƒ‚uowx|“˜˜—–“aCHHEADy•›—˜ ›q-6899:973>QVUWVRNJIIGGJMMNLMNJJHIKN\e`UEEEGLLJFDSbSCCB=OWUTPKGIOSY[Z\XK::ILROKIRXK=:.5?9:8>B:86.(*,44(9757?H;<<==>?@BEOW^hpv{}|{xxzyyyz|~ƒ„ƒ‚†‡ˆ‰ˆŒŒ‹‰‰ŒŒŠ‰‡„~{zxwwxxvvvuqmg^ULC?AIUamxˆŽ‹Š‰‰‰Š‹ŒŠŠŠˆƒyoeacjouxzxsqqponnoomnmkjihefmy†Œ‘ŽŽ "#"##$%'&'())*+,,-,,,-......--..-,,+****))((((((((''((((((((''''''''''((''().26:@CGILMSUWX\_aadedcaaa`adeca`]YRMIC;2-*)()'****++++++,,,,,,,,,,,,,,+++++++,++**********+,------,,++++++++*))))))+,?y‰j|{|y€Š€ur|}ypvy~vj\QXkgSGdwyzy‚¨­ª³¹´·¹±›h7IHGFFCCC=WŽ‰}„‹’S4@?=@BNpƒtqxz~””——–xHHLFBG˜“Œ–Ÿœb,699::85219?DE95535/$2<=CA6;CD>:878ENMEMho}|xupnkhgghiorquuuspnlllnopsv{€ƒ‡‘’“”’’ŽŒ‰„|ungc`a_^^^_^\[YTMG?<:;<<=>@DIQ[ajv|€~{yyzzz{{€ƒ‡…ƒ‚…††ˆ‰ŠŠŠ‹‹‹‹‰‡„€}{yxwwxxvvusplf]TJA>BJUcp{‚‰ŽŠŠ‰‰‰Š‹ŒŠŠ‰‡‚xmc^bipuxyvsppoonlmnnomkjhgfgp}‡ŽŽŽ‹‹‹ !!#$$$%%&'(())*+++,--......//-----,,+****))('''((((''((((((((((((''''&&&(''((()-036:=AAGKLORVW\^bbcccccaaa_[WQMHA;3.)'(())((*+*++**++,---------------,+++,,********+++++,----,,--++++++***((()))*7k‘qXowz‚††~€€„|nvspwphcVc}oG4Xv|z…œ£®·º»»»¹­ŠBBELJDBCD>F~Œ‚}…‘ˆ=8?>>ECa‚wsy~…ˆˆ“––‰ONIFAJƒšž›š›š^+677999730-8GNOOMKKIGGFKKMKMMJHFCFSMKNRSIIIJQVW[aiYLJB9EVSJB>@EH@:2//--07CE7223:;>C>DMK?=9BH96864479AC943@BJOXcnmliggiililtvuttqrrtvvx|€ƒˆŠ’”“•”‘Œ‰„~umgb^[[]]^^^]\\ZXRMH@;:::;==@EIS[foy€……‚€~}{yzz{{}ƒ…ˆ†…‚‚ƒƒ†ˆ‹‹Œ‹ŠŒ‹‰‡„€{ywvwwvwvuuupke\RG@?CKYgs}„ŠŽŽŒŠŠ‰‰‰ŠŠŠŠŠ‰‡ui]]ajouxxusqppnmlmmoonlljigjs~ˆ‘ŒŽŒŒŽŽŽ !!#$$$%%&'(())*++,,-.......//-----,,+****))('''((((''(((((((())((''''&&&''''''(+*,.0156;?@CGLPTZ]^_````]\ZVQLGA:3.+)'()((())*++**++,,,---------------,+++,,++******+++++,----,,--,,,,++***())))*,GohWYqu|‰†‡ƒ‹Œƒ}‚€{qulpmXEdsjRG]ft‚˜¨µ»»¼»ºµž]7>AEBCC?@Cd‘ˆ~~Š—f1@@>CHq€xxz~{zŒ•—[@EGHJ‚›œžšš”Q07:9:9863/.0;EMONMKIGGGEKMKKJHEFCDXQLNSTHIIKMU_rwod]KB=M[RHGMQRPH?30/00022//0325;;F;;EB938>A=7979:>>82363BO=6589:<;9=CB9:BEHR]aagihjootyzyyxvxz|~€ƒ‡‰ŒŽ’“•““ˆƒ{skc^]\[XZ\\\]]\\\YVRKE=;:9:;<<>EJR_gq|‚‡‡…ƒ€|{z{||ƒ…‰Šˆ†ƒƒ†‰‹ŒŒ‹ˆ†‚~zxvvwwvvvuttoid[OF@?EMZft~…‹ŽŠ‰‰‰‰‰ŠŠŠŠŠˆ„|re\Y^gnswvrqpppnmlmmonnmljihmvŒ‘Œ‹’“”’‹ !""$$%%&&''(****++*-............---,,+**))))((((((((((((((((''''''((''&&''''''&&((((++*-1468>BGLOQUVVYYWURNKE@83))''((')(**)**,,,+--,-......----------,,++,,,,+*++++++,,--------,-,,..06+)++**)*,5Y^cw^k~ˆ€xˆ‹||{{qmvoxu\AVp}ucY]b{Š‘“£¯º¼»»º¸§p06=BADEDDCNƒ†~}‚’ŽH1?BKm~|{€€†‰‰‡‰Ž”•e?CCEP—œœŸ›™’@3;;:::9863/.0=JLMLLLJJGIKMIDCAEDEEX[MOTSBHIKMN[mmlgfI?D[^RPVWWVK@82.*)-.../../147984852.1;A>;;69:;<981::144728BIG>75>CDFC>9?PX[Y\dmqtvy}~}}}~ƒ„‡ŠŒŒŒŽŽ’”’Œ‡‚{pg]WXY[\\]]\\\\\\[[WUPJD<:99:;<<@GKS]kv…‰‰‰†ƒ}|z{|{€„†Š‹Š†ƒ€…„‡ŒŒ‹Œ‰…‚}zwuuvvvvvvtrmgaYOE?=EQ^kv‚†‹ŽŽŠ‰‰‡‡ˆ‰ŠŠŠŠˆ‚xn_VU[blsvvtqponmkklmoppomkjjoz…Ž‘‘Ž‘‹‰ !""$$%%&&''())**++*-............---,,+**))))((((((((((((((((((''''((''&&''''''&&(((())((+,.257:>BDIJJMNKHGC@:2,)((''(('))*+***,,----,-......----------,,,,,,,,,+,,++++,,------------..4S@(++**)*2HRnŠ‡Šqim~“……ˆƒ{{€vprmM?UBbxx|nX_j€Œ˜Ÿ¨²»»»ºº¦t47<Lut€‡rX_oƒž¥®´¹»º¹£i17:<>AA@@FCDm‘†}{}€Š‰;2gƒ€}€…‡‡‰ŠŽŽƒVKDDK}’–˜›šš“P59:;<;;:941.,/@KPPLJHHLLMPQQQPMG:01aVMQM4=CFGJV]ba^^bbddddc`ZRE<977.'')**+++,,.0019=AHKFFOOKEBFORF?961433453538CF=@A9:@GKIG?7QaV^tƒ†Š‹ŠŒŒŒŒ‹Š‰ˆ‡‰‰‰‡†…wndVMFFLSVYYYZZZ[[[[[[ZZWTOIA::989;<=AJX^alw€†Š‹‹‰„€}zzy{}„‡Š‹‹ˆ†‚‚ƒ‡‰ŒŒ‹†„|xvttuuuuuutqlf]UI@:=EQ_mz…‹ŽŽŒŠˆˆ‡‡‡ˆŠŠŠ‰…~wi\QSZenuwvtrqonlkklmnoppnmkmu‚ˆ’•’Ž‹‰†‡‡…ˆˆ !!!""$$%$%'((*++**++........//------,+++**)())''''((((((((((''''''''((((''''''''''(((())))***)*,-..13677762/,)((((''(())))*+++--,,--..//../0//////-----------,,,,,,,,,,,,,------..---.-,-,,,**++-8OQy‡€Tb[OJMi›™‰†‘Ž|ty|z€yoH?^xxu„†nScq˜¦©¬°·ºµšN/9:>>=@A>ABBK{Œƒ}zx‹@wƒ}{‡‡‚†‹Ž‘‘‘’mNLEI|—–——–’U59;;<;;9743.-,9IOSVWWWWVSQY[XOB3,(,]^OPJ,/49?T[`e_`jlmhiiij`TF?::992)(,*++***)*-..2=IKMPNXOILHAFMQVNB/264//1460/8K@;85NTIA99@DGA9=Q[Tk‹‘Ž‹Š‰††„€~}}}|yxoj`VLFDGKQTXXWXXXYYYYZZZZVRLFA::989;FRan{†‹ŽŽ‹‰ˆˆ‡‡‡ˆŠŠŠ‰…}sfXPS[gqvwusrponlklmmnpqppnmqyŒ”—”‹‹‡†‡‡ˆŠŠ !!!"#$#$%&('()++++,,..////////----,,-,+***))))((((((((((((''''&&''''((((''''''''''(((((()))*))******,,----+*)('&&((((()))**+,,,-....//./001111/////-..//..--------,,,,,,,,----..........--++++,-0JKP…bMHEJSh®©¡›— ˜Œ„vvopkoO:;Hb„Š„~iV\yŸ¥§§©³©|729<=<?BQƒŠ‚{{v€wƒ|w|ƒ†|„ŠŒŒ‘’“‘{ROGIx•“””“`7<;;<==:841/+-8Xmvuoja]YV_eb\K4&%%'TcLLH-..0T`fbaexqpigjkml\IB?<===6/12210.)(%'+../9ALOIV`QJF?GKLEEJF1#093-./0,/?Mx_CDIPYboy‚†‰ŒŠˆƒ€}zxzz}ƒ…‰Œ‹Š‰ˆ„ƒ€‚‚…ˆ‹‹‹†ƒ}xxvstuuutttspjd[SG?=>HTcp‡ŒŽ‹‰ˆ†††‡ˆ‰ŠŠŠˆƒ{qcWQW`jrvvurrponmmmmmnqqqmmot~Ž–”ŽŒ‰‰ŠŠŒŒŒŒ‹Š !!!!"#$$$%%&'())))++,,--..////..----,,,+****))))((((((((((((((''&')(''((((''''''''''(((((((()*))**))))))*****))(()(((((()))*+,,,-.//////./0011111/00//..//..--------,,......--....//......--,,++-.5VIh‰KGEXr†—¡§­­¬§¢›€†‹}okmsr]??83Gmw‚~Y]p€’ž¤¡ŸŠO4;;:::8;:BMOd‰…{ywy„Ž‰|x~…€~ˆˆŒ’‹‰ƒSIHOp‹‘””’‘Œj:<;=<=<85400..[€„}soi[WUTZ]]XE,&)(*HlULJ/,/Tkkkgl{yojhegkme\RJEA@B?<9768630*##$(,-./8AFQ[`RKLTQZc_WQI5!+<<1.00.+*CMYer‡ŒŽ‹‰ˆ‡‡†‡ˆ‰ŠŠŠˆƒzobVSX`jsywurqponmmmmmnpqpomm}”˜“‰†ˆ‰ŒŒŽ‹‹ŒŒ !!""""#$%%%&&'((')***+,,,,....//.-----,+*)**(())))))))))))(((((()*,)&(''(((((('''(''''((((((()()))))****))())))((*))(())(())*+,....//.//1111111111221111/////.------..----,-...-............------./?]nnnG>aŽšŸ¨¦¡££«ªª©Žwx|zb[KHCDJqy}o^jwˆ“œ™’ŠlD:=:<=ACC?=<>BHJRmˆxuv|…Šzv{y…t}‘“‘’hEIQg„“ŽˆŽ‹u<9:<=;:944/.+K{~zvrpe_XQJHCDGB400-.AlZML01Ejmrox‚|vnifchlomh^XTPOIA=<9<986,%"#$&*..-.2D^eh\PVXdlinlidZG8846/--49=DB8=WoŠ‡{xsokhgfghjllmoic\SKGFIMRSXXXXXYZZZZ[Y[XUPJD>:989:;==BIJOJDB??HQRq‰{wuw|‡‚sqy{wkn‡wbs˜šš•{ECM^†‘Ž„wŒŒz@68;;962110,8q}ywuuk`VSTMEA:;;<9644>hZPP39fjqtˆƒ}zroqslcftxofe_XQJE@>@=<7*$$$$&--.,),Gdeg^[[dqy…†‚}{~wa<04.+6FFD?CLL^`\s|}{[FGLFG@7?@=A[s†ypnjhgggghjlnmmkf]UNGHKORUVVXXXXYZZZZZZXUPJD>:989:;D>?@?@@IEDM>:AFOIx‡}vsyz‚ƒuttpWa}wm{”•˜˜ŽUBEdš—‹r…~A77998432/0.G{‹yptpom`ps[:F@:;;;:8=]\PSA^jnx‰Šƒ~zz}|n__^iwtmeaYXUQKGDB>2'#"$#*1..+*+K^_aT\Ya`m{usyzzrjT504>?=978>Qlkb]]csi[NJFOPI?=@>A[x‚rlkihgffhjlmmnkc\VNLIKOSTVVWWXXYYYYZZYWUPHB=:87:::=CIPXakv~‚†ŠŠ‡ƒ{vvwy}…‰‹‹Š‰‡ˆˆ†‡……‚€ƒ‚ƒƒ„ƒzwsrrsrrrqppme^XNC?CJValy„‹ŽŽŒ‰‡†††‡ˆ‰ŠŠŠ†~tg\TW^gqvvtromlmmmmmmmomo‚“•ŽŒ‹ŠŠ‰ˆˆ‹‹‹‹ŽŽ !!""####"#%%&&'((())**+,,---....//.---,,,+****))))))))))))))((((((((''''''(((((((())((''(((())))))))**))))))))))))))()++**++,,..-///00111122222212422222110/////........-----.......////.......---,,./=TGCDTt›³²£¦¨¢¤¥£ª§¥ž—˜¢¡•“Šqdcdimgfkuyt|`kv}z|j<,29?>?@???BFHJA<>AJMLy‡}uvz†ˆxl[V\w€†‘Œ‹“cCCOŒ ¡–^‚?78977744566M”‰~qŸ—•†~€P;DD<;:<<;S\NWeqs‰ˆ„€ƒƒ…xg`_^`bb_ab]XUSROMHF<)$#%$%.2/.++2N\_WGQ[]]amzywwurhijZJF>.6;5+*4T‚Š‹‰~g]SNPVNLM>8=>EZƒ~sigefhfijkmmmjf_XPMKMQSTVVVVXXYYYYZZXVSNGA=::9::;>DJQYcmw~„ˆ‰‰‡ƒ}yvvwy}…‰‹‹Š‰‡ˆˆ††„…€ƒƒ‚{utssttssrqpme^VLC?DKXcn{…ŒŽŽŒ‰‡†††‡ˆ‰ŠŠˆ„|rfZSY`kswvtpponlmnnllmmrŠ–’ŒŒŠ‰ˆŠ‹‰‡‡‡‰ŽŽŽŒ !"""""##$$$$''('(((***++,,--......--..,,++,+++**))))**))))))))))(((((((((('&((((''(((((('''(('(())))))))))))))******)(****,-,--../00/.002011333322243333331100000/..//..--.----..-................---+//>FFCNq„¦²±˜š­®±²¶·¯¬£™’˜–~vi[ojdehnvz‚‚{jfr{{x_1,01:CDDA>=<9CJH::=HMCR†„|y||ƒŽ‰kHQTk€’˜“‹ˆ~mJCDv™ ¡Œs“v?:;;<>?@>CB2V  ‡}“—˜„opkU27?D?;<=>DLQZeoy~„‡‡†…|wvvx{}†Š‹‹Š‰‡ˆ††…†…‚€€€‚ƒ‚|wstrrrsssppmd]TJCAENZer}ˆŒŠ‰ˆ‡‡ˆˆ‰‹‹‡ƒ|qcXSZbksuuspnnmnlmmkmlv˜’‰ŠŠ‹Š‹ˆ†…†ŠŒŽŽŽŒ !"""""##%%%%''(((((***++,,--......----,,++,+++**))))**))))))))))(((((((((('&((((((()(((('''(('(())))))))))))))******+*****----..//00/0012322333355334433331100000/////.......-...-................---+.09AEOp†°µµžŒª¯³µ¹¸³­ ˜’•š—”{zjbi`acst|€†‘~RbpxuX,(+17<@AB@>><;AA<=;AKH9ZŠ†}{{€†‹EIV_xŠ“™š—•‰tqQDEWˆ™–v‚JBBCDFDC@==92n©¥¢›~ˆ…|yg[IA7548?@;<:Cahkqv„…‡ˆŒ’•€e`ghjjhdd^M;=LRPPQPMI>*""%'*056425EQPRF@FHP[\_[bnhfcXQRYciK301./2/,-8o„€€rv„††…xM=FB?CELbsxmjgighjlnnojg`YROMNQTUVVVVVXZZ[\\ZYVSMF@<:;;:<@CHMT\gpz„‡‡†ƒ|wwwx{}†Š‹‹‰ˆ‡ˆ††‡†…‚€€€‚‚€}xtsssrttsqpmd]SGAAGP^ju€‰ŽŒŠ‰‰ˆ‡ˆˆ‰ŠŠ‡ƒzn`UV\dmsuuspnmlmllkngw—‘ŒŠŠ‹‹ŠŠ‡„‡ŠŽŽŽŽ‹"!""!!""######$$$$''''(((***++,,--......--,,,,,,,+++*)))))**))))****))))(((((((('&''''(())((((''''''(())))))))))))))******+*++,.,--..//01111112332443344444433332211000000//..........................----,,,/C?LRQRSRJ7&""'+,3659:@KROD7CHEEGEPPS^cgghkllmolga]TOMNRUVVVUWYYYZ\]\\ZURLFA>===<>BGLPW`irz€„‡‡†ƒ~yvxxy|}†‰‹‹ˆ‡†‡†††‡…€€€}xtqsrsttrqljd]QE?BHT`kx‚ŠŽ‹Š‰ˆˆˆ‰‰ˆ‰‰†‚xm^VU]fotusqpnnmjikjh~–—‘Š‹ŒŒ‰‰‰‰‰‹ŽŒ‰"!!!""""######$$$$''''(((***++,,--......--,,,,,,,+++*)))))**))))****))))(((((((('&''''''''((((''''''(())))))))))))))******+*++,---..//001111123234444444554433332211000000//....................--..------,,,/?Qo†‰˜®¦¥²p~¦Ÿš¤¬© ™Ž…“Šˆzsljckqut}…ŒŸ©­nJejhL*+&,.>MKJBAA?<;>;;=>DHFFwˆ~{{}ƒuJRv‡…„€‡ŽyrNFCKsrLE@?=>=<::5GdX©›—ƒ~‚€f[PMMC6643126>IHBE`osx€ˆ–“sqkkjklpspjX9./3BOQRRPA-%$'+-0;?BABFGHLB89>HOQNVYYXOC3,.1334BOO:7618;6N=LE>HHKMNQUV_dilnnnkgb_VQOOSVVVVUWYYYZ\]]]YWRLFA@?>?@BEJNR[bkt{€‚……„}yvwwy|ƒ‡ŠŠŠˆ‡†‡††‡‡…„€~€€€~yssrstsssrnhaZQE?BKXcny„ŠŽ‹‰ˆˆˆˆ‰Š‰ˆˆ…wl\SW_hptusqpnlkmjil‚––‹‹‰‰‰Š‹‘Œ‰""####"#%%$$$$$%%%''''(((***++,,----....--,,--,,,+++*)))******))**)))))((((('''''&(('''''((((('''&''(())))))))***********+***,---..//0010011132455665444665444332222111000//...........-.---......--,,,,----,0Ce€Ž‹¡©š«—pr‘¥®®¬¬§ž˜„‡“Ž‚€ƒ}pifnrs{ƒ„“©®ŠG_^]G..3.-6G:>FCB@<>=<<<=BKLHzrzŠ‡€|{€…ŠlMc{€yndk}‚_EDBLcpTFEA>===;:814Zj¥“€yvmeWLCBA74334:CKSSSEGOUX\s…‡|€€zqmlopvwqiS4-005HRTQH8-*+,.2>IMNOONGEF:8:>BJOOTSI@-+A`z›£¦¥¥£”~k]WF2=@5?Yosyzj\ayŒŽ‡†JCGDMZeq}…‹ŽŒŠ‰‰‡ˆˆ‰‰ˆ‡„~tgZSW_irtuspollkljj„—•‘ŽŒŽŒŒ‰‰‰‘‘Œ‰#######$%%$$%%%&&&''''(((***++,,----------,,,,,,,+++*)))******)))))))))((((('''''&(('''''((((('')'''(())))))))***********+***,--..//00111133333355666655665444332222111000////../......-.-,,------,,++,,----,0Jx‘™“’‘™£œˆˆ©²¶ª˜˜Ž„„Šs{xtvsiqw|„ŒŒ—©°£RUWV@*+AC20037DA@@=?<==;;?DJG{vjŽ†|z†ˆ`Rfmc^^^k‚‰~H?CAABGHDB?=>;95225Td‚¥Ÿ€|vi_UTLD?834;?GRVY[XYYMIGIOU]izŠŒ†xihry~zmF.,0317IOI<;;9;@EKH|~sj„Šƒ}wzˆƒZ_d[\]\ew‚‡o?@A@?BFDB>=;98400;Ub|›|pskXUPE997ZzŽ¤»¾¾¿¾¾¾¾½º·´°ª}9(024?Mdtw‚ƒ„||‰Š‡LHHOSPZtkjie`]YYZ]^]YWUUUUUUUVWWXZZ[\\[YUSMIEDDEEGJNRV[bhpu}€ƒ„‚{wuvvx{~‚†‹Œ‹Šˆ‡‡ˆ††‡‡††„}{{}}~{zvssttssqojd^UKCBIT_jw‚‡ŒŒˆ‰‡‡ˆˆˆˆˆ‡‚{reWS[bkrvvqolkihk‰—“‹ŠŒŽ‹Š‰‰†ˆ‹‘‘’‘ŒŽŒˆ&$#$%%$%&&%%%&%&''((((())+++,,,,.......---,,,,++++++**********))****))))((((''''&&&&&&&&''''''''&&(((((()))))))*****++++++,,,,--////1122334334444566666656665544332210100/0//./.8/,,------,,,,,,,,,,,,,,--,++2g„ œ™™‘‘Œš£•“¦¤¥¡£’Ž“Œ‰‹”‚yy|~ywtv|{a3›ˆLPKE/'(,.,,+.16;FA?C=?<<;64@MONH6.,=HWvŒ¦¹¾¾¿¾¾¾¾¾¼º·³°®¥„8.4348Uhg~z}~‚‡Œ{hGDNNNOyxkhjhgojd_][\YWXYWUVVUVXYZ[\]]\ZXURNIGGGHHJNRVZ^dlrw}€„ƒ}zuuvxz|ƒ‡‹Œ‹Šˆ‡‡ˆ‡‡‡‡†††‚€}|}~~}}{wssttssqojd]TKCCJVbkyƒŠŒŽŽ‹Š‰‡‡‡ˆˆˆˆˆ‡‚zpcYT[dmtuspnmkijŒ™’Š‹‹‹‹‹Šˆ„„ˆ““‘ŽŒˆ(&$%%%%%&&&&''''(())))*+**++,,,,,,,,---,++,,,,++++************))))))(())((((''''&&&&&&&&''''''''&&(((((()))))))***++++,,,,,,----//0002223333344455565566665555553322110//...../MK0----,,,,,,++++++++++++,+,*+5h{Œ ¢¡Ÿ›š›‰Ž¢… §¦¢œ‰’Œ‰Ž„xv{}|yvokojW;]dNMPN@+')+-/;1.027JD==>@>?;;?@Djxsnu‹€||‚…^XWTT[jz}xx‡{M>@ABCGH=:8431.0XVou{|‚„{c†n>HGOPo~tmlhdedbb_[WUVWXXVVVUUWXY^`][ZZYXSNJIHHJKMQRV\`fmuz„{vtuvwy}…‰‹ŒŠ‰‡†‡‡‡†‡††……„ƒ€}}{}~}zwusssssqnicZPIDGO[cq{„ŠŒŽŽ‹‰‰ˆˆˆˆˆ‡ˆ‡†€xmbVV]fnuwsqolml‹—“Ž‹‰ŠŠŠŠ‡†„‚~‹““‘ŒŒŽ‘Ž‹‡'&$%%%''&&&&''''(()))*++**++,,,,,,,,---,++,,,,++++************))))))(())((((''''&&%%%%%%&&&&&'''&&(((((()))))))***++++,,,,,,----//0002223333344455565566665566553322110//.....,),.------,,,,++++++++++++,++++6f{‘££›™š›–‘…¢™œ§žšš•“‹‡Œƒzvuyyxrme\WQJPNNMPK<+'*),0.+,344459>>@@?=;=A?_‰vnln…“‡~{}ƒ‹pQPU^dq|xsl}‰kB?CBCFD=:7521/0;MZtz~xsh\YSF>?:5;PWY_dfqslf]XWVLLQYao‡™ —›žž›Œa130146674421036I\eow|ŠŒl:7:;<<>@=70/7?Ef—¶¾½¾¾¾¾¾¾¾»»º¶³­§ž›–U0LW@0CBaYm€}€xFM~[?EKNV`^URJIIJLMRQMKJOSWWUVTWYVT[dee^Y[_]VPKIJIKLORUY^chov|ƒ~ztsuvx{}…‰ŒŒŠ‰‡†‡‡‡†‡††…†…ƒƒ€~~~}zxutssssqnicZPFFHP\es}†ŒŒŽŽ‹‰‰ˆ‡‡ˆˆ‡‡†„vl_VX_gpuvsrpknˆ–“Ž‹‹‰Š‹‡…„ƒ~t~Š‘‘ŒŒŒŒŽ‘ŽŠ†''&&''''''''((''(((()***)*++,,,,,,,,,,,,++++***,++**********))))))(((())((((''&&&&&&&&&&%%%%&'''&&''(((()))))))*))**+++,,,,,----.0/102223433444555665566665555543311110///.-..-..----,,,,,,,++*)**********)*)6j†—Ÿš—•š’‘‰›—™›˜–‹ˆŽ“ŒŒ‹„yz{}wpbgaPEKQONOQLJ<'&()+-*)*,+/31619>>A>=?>?P‡uonlrŒƒ|~|„‚UASdpyzreY`xy\CDB?DH>:74310/:L`ƒŽ…maVTSFBB>8<9;Bz‚wwuwƒ’Š~€ˆqXahv{wn\G>HZeWECEC@@9731/0-;I`Š›ŒcGSTVHDGCCHRX_hjn€‘‰ƒwf^elgmrutw‚‡ŒŽ‰„~wpV=:9898543258:Yttuxƒ‡‰b675443020..266EqŸ´»»½¾¼¼¸´¶ºº¹³®¤™‘Œ†ZRLUR14MO=h€€||xnTKLLMNPMONLDLXUMECCEGVRQ[YSYWceemkhhfmoh^QMLNNORUX\bhnsz~€‚‚~zvtstwx|ƒ‡‹Œ‹‰ˆ‡†‡‡†††††‡†‡†„‚~~~}yusrrpnomgaZPIHMW`kx†ŠŽŽŠ‰‡†††‡‡††„{si\W\ckrwwqplƒ–’Ž‹Š‹Š‰‡„…|ddtƒ‘ŒŒŒŒŽ’Œ‰†((((((((''(((((()))))****)*,,,,,+*++++*****)(+*)))**))))))))))))((((''''''''&&&&%%%%%%%%%%%%&&&'&&&'((''))))))))))**++++,-,,..//./1011112344554555666666555555432211000/...---------,,,,++++***************0@>h˜“–š‘–•Œ—›š–‹†‡‰ƒ„……Š}yqqpmlgcTJKKMNQNJH7'((())+(*.,+,7P=83<:??=<9;l‚‚ˆ’„~‚„‚YitxzxjWD97:KaTB?CCCA841/..LP^c]_`VRHZhhlke[SUgRGX_Zcia[gmlc`Xkl^SOOOPPRUVZ^bgnuz}‚}yvusvxx|ƒ‡Š‹Š‰ˆ‡‡‡‡††††††‡ˆ‡…ƒ€€~}yusrrpomjg_YPIJQ[epy‚‡ŠŽ‹Š‰ˆ‡‡†‡‡‡‡„€zqf\X^fnuvwrp~–“‹ŠŠ‹‰…†ƒƒ{d^iwƒŽŽŽ‘‘Œ‰ˆ((((((((''(((((()))))****)*,,,,,*+++++*****)(+*)))))))))))))))))((((''''''''&&&&%%%%%%%%%%%%&&&&&&&'((''(())()))))**++++,-,-,,--./1011112333555555666666555555332211000/..---,--,,,,,,,,++++***************,.0a†’”–Ž•••—››˜–ˆ‡€Žƒƒ‹ˆ‚‹…wywvrme^UNLIKNNNKI:)((())**+---,/3+0VDB=?@>;=\“Œ‰††‹—€}‚‡qrwwytgTC8548LZODAFDD=51//.=:97779:Mv„€€„‡…€g<75631/.-/39;CU_fks{‹••Š}w€‘š›—‹‡‡†~ZDDIKQP<BGMMKHH>-(')*(()*-.,+(),.:CP;?=><>J‘Š‡†Œ“˜•ˆ~‚…duxvpcO>777:EV\O?>B?@:40./:Kg„’—h.6QdTHB>>AE[ad{‘—‚vx{p~”–‹€š¤¢¢ “~rmhgd_ZZTOIB@>=;98:4[‰ˆw{…‚|{jD6643/../15:?Ph}~ywma_becdlx€ƒ‡ˆƒ|{ncY?@7<7EZBAbvxu|‚€dL`OMijgg`^[_hjmlnnkha^__WYZXUW\]ajhB08WoWQQPQQQSVY\`djpw|€‚‚}yttvxy|ƒˆŠŒŒ‹‰ˆ‡‡††‡‡‡†‡‡‡ˆˆ‡„‚‚‚}xutttqomje^VMIOV`iu~„ˆŒŽŠˆ‡††‡‡‡‡††ƒxpd]]dksvuou–Ž‹ˆ‡‡ˆ‡†„ƒ~g__enz„‰ŠŒŒŽŽŽŽ‘’ŽŠ‰‘((((((((((()))))********+++++++++++*********))(())))))))))))))))((('&&&%%%&&&&&&%%%%$$%%%%%%%%%%&&&'''&''''((())))**++,,,----.//001110111233445556565565554444322100/.--------,,,,******))**))**/2))))(())))),Pm}ŽŒŽ‹ˆ‰››šœ˜‡‡Ž‰Š‡ƒ‰‘ƒxwwtoe_UD@CJNIKHF=-(')*'(((*,++.-3+).8<>?<:<@“ŒŠŽ‘•–—Š€…Šinwup`LB88??>;Z…~sw~€{xnQ8753300158?BVvŒŽ‡}iZVROMT[^`d`]\ZbtYB?=B4=AK7Kegnz‚…h&bqSZ]dklfhcbhopppkgd__\ZXYYYZTT\^X6(1OaURRPQQRTXZ\`fkrx}€ƒƒ}zvsuxy|„‡Œ‹‹Š‰ˆ‡‡††††‡†‡‡‡ˆ‰ˆ††„„}xtssspnlie^VMLQXakv~„ˆŒ‰‡†††‡‡‡‡††ƒwod^`gnswttŠ•‘‹‡…†‡ˆ†ƒ…iccbfq~‰ŒŽŽŽŽŽŽ‘ˆŽ”))(((())))))))****)*********++,,++******++))))((((((((((((((''''&&''&&&&&&&%%%%%%%$$$$%%$$$$$%%%&%&&&&&'''''(((()))*)+,,,,,,-...////01/11222445555654444433333201///..----,,,,++**++++***********)))'''''''()+Ik~†„‡‰…‚‹˜’‹ŽŒ}‡‹Œ‰ˆˆ„€€…’qtyslhaSLHGF<@IGENUe|…‹’ ­µ²²²¬¥ œ®³®¤ŸŽ}slec^YWXTMHB@@BCB@Cb‚†‚xw|}yvo[@:;;<7257<@E[xŠŒ‘‘‘‚qjc\VSMGFHHLXgsoLGI>G8>EDG?HWay€8E’’€IXVQVckb]lmqtqmhd`][ZYXXYZWZ]\VE?LWYUQPQPRRUY[_chltx|€‚‚€}zxwvz{}€„‡‹‹Š‰‰ˆ‡‡††††‡†††‡ˆŠˆ‡…ƒƒ~zwssssonlgd\TMNS[cmx€…‰ŒŽ‹‰‰ˆ‡‡‡‡††††ƒ~wme`bhovuq†“‹‰ˆ‡ˆ†…ˆkeebbhv‚ŒŽŒŽŒ••***)))))**))******)***********++))***)))**))))((((((((((((((''''&&''&&&&&&&%%%%%%%$$$$%%%%%%%%%%%$$$&&&&''''''(()))))+,,,,,,-...////0010122233555554444433333310////..----,,++++))****))))))))))*))&'''''''()+0'()*)('+**+,*)*)++-.17:@Kkœ™‘‘–˜—‡’Žƒ‚„‡ˆurtm[ICCCJWeok`UPPIAA@:5/2C[qƒyNc‡‚zzeG=E>NVP]yŠ–¥±³µ³°­«¤›”ª­¦¡¢–‡tnjda\bpg\QHEDDDDDP|’ƒ{z~wui_\Y]\_P48;>ABWo‚…Š‘Œywupcca]\[^afmpfBA=@BC;LAIG?Rgv€peŠ“„PPWKJVOKWqrrtqniea^\[[YY[\\\\\XVZ[XUUSQPPRRUX\_cinsw|€‚‚|yvwx{|}€„‡‹‹Š‰‰ˆ‡‡†††††…††‡ˆˆˆ††ƒƒƒzwssqqonkfbZSNPU]eq|‚‡‰ŒŽ‰ˆˆ‡††††…………ƒ~wmdcgirwr|’‘‰ŠŠŠ‡‡„‡…rhifbbjx…ŽŽŽŽŽŽ‘Œ‘˜•))****(****())++**))******++******))))((((((((((((((((((((''((''&&&&&&%%&&'%%%%%$$$$$$$$$$$$$%%%&%%&&&&&&&''''''''))(*,,++,,-...//00111012222244555544332233220./..-----,+++**))))))))('(((())))))''''''''&(),2k‹ŠŒŠƒz„ŒŽ•Š}}†‚sgd~‰†‡ŠŽq^_^`gdQQSUMLNNJFE?1'')))))(+(),*+*+,-,,047Uz‘•’”•—–›zŠ–ˆ„††‰ƒ|{vqssu}ˆ‹‚jY[ZUNGFGA72?Njxl\„•—‰„‹‘tL@>JULRmŒž¨±³°¬¨££¤¢Ÿ£œ•’‘Œ€smkhmmjsjcZSKHHICBN}”˜Ž‚€}wriiotzzve=68:>BNaz†‹„zx{{zsfdjg`\_fgjnZ[[X96:>MKCFDO`y|[rŒ’„TGRK6GMelprtsqoke_][[ZZZ\\[\][Z\ZWWUSQPPNRRUY]_cjnrv|€‚‚€~zxxy{|~„ˆ‹‹‰ˆˆ‡‡‡††…………†††‡ˆˆ‡†…ƒ€~yvrqqqonlgc\RPTY_hr|ƒ‡ŠŠ‰ˆ‡‡‡‡‡††……„~wmfegmuv{‘Œ‰‰ŠŠˆ‡†ˆ„oiiidbeozˆ‘’ŽŒŒŒŒŽ––•))))))(****+++++**))********))**)))))))(((((((((((((((((((''''''&&&&&&%%&&%%%%%%$$$$$$$$$$$$$%%%%%%%%%%&&&''''''''))(*,,++,,-...//00//1012222244444444222233310//.------++++**))((((((''&&&&''((''''''''''&()/3N‰Œˆ…€|‚†‹’Œ‹ˆwinnft‚‹‡Š‹ŠaOOPTV_]OMONKJKHGE?1''))*#*+**'&(+++*+,++.An’šš”’“•—–•z•Žˆƒ†ˆ‰‡†’”–•’”——’‰uRPRXRMGEJC64Jmˆw{”™›‘’Ÿ£Ÿ…hiZO@Mf«°°²­¥  ¡¬¤Ž £¤¤¢ž“i[do€vqnf_SJHKKGB@]Œ˜Ž‚€xuxxqv€‰„sP46;??DSiy}qlqusqmgcgf_[_cbgb\`seB7;=>FJQG;Nx{d{‹Š‚\IO>+@T]dqqrssolfa]\\[[[[Z[[\\\\ZXUUQPPPOPRUY]_chlrvy~‚€~|yy{{|~†‰‹‹ˆˆ‡‡‡‡††…………†††‡ˆˆ‡†…ƒ€~zvsqqqonlgb[RPTY`is~ƒ‡‹Ž‰ˆ‡†††††††……ƒ}wmfgjqu{‘Š‹Š‹Š‡††‡tjnliecgq~‰’‘ŽŽŽŽŽ‘’—””**))))*++***++++**))******))))))))))))((''((((((((((((((((''&&&&&&&&&&&%%%%%%%%%$$$$$$$$#$$$$%%%%%%%%%&&%%''''''''(())**)+,-,--.000011101222223333333322112322///.-----,++****))(('''&''''''''&&''&&''''''&(,1;Ek†‡…„ƒ†ƒ…‹ˆ‚ƒ„‚‡„qv‰ŒŽŽ‚[NLTXTGWb[ONNKJKFC=/%'(*,Im8#(,,))+,,././2T€˜ ˜’’•š––~{‘•‰„„ˆ‹ˆz’”˜™—–———–…`VXXZ\UJFGB:>a‰“•—–š¤¥¤ “†“š†_@OŒ¦®°¯­¢œž ¢Š¤¥¦¥ Ÿ”[257<@CJRTWXWUTSPNQSTWYP:+?8;>:?CC=>=AJGIX[J^kŠ†ƒuXFIRDBMI@LRT_bt†…xKAJZaPZopqssqnje_][ZZZZZZZZZZZXWTPNMLLMMNRUX\acdgjquz~‚ƒƒ€}~‚„ˆˆ‡‡‡†…„……ƒƒƒ„……††‡‡ˆ‡ˆ…ƒ~{vsqqppnlhd]XSV\`hry‚†‰‹Œ‹‰Š‰‡†††††††……€yrnkwŒŒŒŠŠŠŠˆ‡‹‰ulnmnpppmot|ŠŽŽŒ‹’”—”“’++++++****++++******)))))(((((((((('&&'''''(((((((''''''&&%%%%%%%%%%%%%%%%%%%%$$$$$$$$##$$$$$$$$$$$$$$$$$$$%&&&'%%'''((()+++,,-.../01111000211111021//.---..-,,,,++++**)(()(((&&%%%%%%##########%%%%%&'',1@Vu|uf^WYfdWQLEIVU\dx{|ŠŠŽŽ†lZ]`kgiknlbhopkopshmr|d<),,)())(''''((**-;NZbx“ŒŽ•‘Ž‰|…†…†”‹„‚€…Œ„|‹‘’‘”š¡—œŸ˜~h]\YPH=uœ•ŠˆnZ`ppknp‚•¦¡ˆŽŽ~s‚‰„‚†ˆ„~~phƒ¥œ™•‰yocRDCHnŽyš›‘Œ‰yskf^[ZV@,+('))+/4796;?EIK?BQNJ:=0')))-;NZiŒ‰†‰‹‡†„†Š†ƒ‡‹‘˜—‹{zvzˆ`5OYfqx’¥°°±©¡˜†nc^\PIc“™“•‰„v]u’”‹Œˆ„ˆ”œ •zx~‚}xtq’”‚mt‡˜¢ª¦¡–~ˆ‹ƒ|vqldXEg…‰Š…|“”…€wngb]UOIC:.(&'&&&&&'((((')****,.16:=CDLHG<59NG<;@G:3AIHTdw‚ve\VkhQV]fqrsrqmid^[ZZZZZZZZZYYWURONKJJJKLOSVY^_bbdhlqx}ƒƒ‚€€‚ƒ„‡ŠŠ‡††……„ƒ‚‚ƒ…†‡ˆˆˆ‰ˆ‡†„}yurppoomjfb\WUY_fmu|‚ˆŒŒ‹‹‰‡‡†††……†††„{vp‚‹‡…‰ˆˆˆˆ‡‰Š{yƒ†‡‰Š‹ŒŽŽŽŒŒŒŽ‘‘‘‘‘,,++++++++++**++****))))''''''''''''''''''''(((((())((''&&&&%$%&%%%%%%%%%%%%$$$$$$##$$$$$$$$$$$$%%##$$$$$$$$$$&&&&&&&()))*,,,,--......//./0000////..-,++,,,,,,,+*))))(((''&&%%%$##""""""""""""##$$%%%(+5Fdyxsw||zz{vtj_UE3+1?P[cb^jjVWird\[`lqsqpmid^[ZZZYYZZYYYYVTQMKJHHJKMPSVZ\^`bdgkqv{€ƒƒƒ„†‰‹Šˆ‡†…„„ƒƒ€ƒ………‡‰ˆˆ‹†‡…„€~ysrqponliea\XV\`fmt{‡ŠŒ‹‹‰†††††……†‡…ƒ€{p~Ž‹ˆ††‡‡†‡ˆŒ…„ˆŒŽŽŽŽŽŽŒŒŒŒ‹‹ŒŽŽ,,++++++++++********))))''''''''''''''''''''(((((())((''&&&&&&&&%%%%%%%%%%%%$$$$$$##$$$$$$%%##$$%%$#$$$$$$$$$$&&&&&&&()))*,,,,--......//./00//////..-,+++++++++*)))(((('&&&%%%$$##""##!!!!!!!!""$$%%%*0Dbxwqv|~|zxtph\PHQaj}~jwŠ‚ƒ‚zWQWqymr{qqquytwu€op~yto_urbmŠŒŽrN3((()**+++4GT^xŠ…€{ptx|}€„„Ž—™•Œ€vpmqz„W:O]s‡Ÿ­°¬®§Š]IMNQNN_š˜š€jP\…‰ŒŽŠˆ…Ž“{jkox€•§«§£—{SJJGYƒ˜„Šš—“ˆ}sonnofPA]‚ŽŽ…{‚€…€tbG:4342.(%%#%%%$$$%$%%%&%'(*,/015?53/*+.2>^R?@QJWsxqtz||z{zzrjf[NIWivˆ}p€‚€‚iKNPcnnhikptuqfuxyposvkeX`plW—’‘mdg?2')))(*5ALYcˆ‚}}|vnpstvrv}„Ž’Œ†{urppz…WQ`rŠš®²°©C=DEDKPP[Š›šžšT:=ds{‚…{xy‚|h_aixˆ–¢Ÿœ›“~UFHOk‹–•’š”‹trskhkoaHFSlx€{rnyy~}cB40.,*(('&$#$$$%%$%&&%$%'',/1022473-(&&)*.E_PXYZYVI,+-9QPTL\y}}‚‡zSQOXcppqpmhc]ZXWWWWXVXXWWUQOKJGFFHJLORUY[^_`aeinty‚‚‚‚‚ƒƒ„ˆ‹‹Šˆˆ†…„„ƒƒƒƒ„„‡ˆ‰‰Š‰‰‰‡†ƒ‚|vsrqonmkhe_[XX^cjry~ƒˆ‹Š‰‰‡………………„……„‚||‘‘ˆˆ†…†ˆ‰‘’’“’ŒŽŽŽŽŽŽŽŠŒŒŽŽ‹‹‹‹Š‹‹,,++++++++********))))((((''''''''''''''((''(((())))((''&&''&&''&&&&&&%%%%%%%%%%$$$$$%%%$$%%##$%%%%%%%$$$$$$$$&&&%&&(())*+,,,,------..////..---------,++****)))((((''''&&&%$$$#"##!!!!!!!!!"""###%'',8Lktrrw{|zz|yxqlbVNJ[v‡ˆxuƒ}}€yYGGSinokekfjsmhl~rosohigGjnTn”†qhft?MD)(,*)2N{••—•Œ~cS\`befUHBFWswqi`jpuuS401/,*(('%$#$$$%%$%%$&(+-.12463220.(&%%')+co]€‰dWg]hfYVI9/3>JVZ~„}zvuvrpoliecba^_agecby}qlnmlu‚}sˆ“RS[Q>:Jk„Ž…rc‰{‚pLQkvŠynifbejioy—¦žŽzvqqkM=:FCD<+'(&&&()))07?IY]U]cP@ELFMWk\SWS^kyK@]Lfkfqomgc]YWVVVVWWWWVURPKFEBABDGKNRUXZ\]^^afjou{‚‚ƒ„†‡Š‹‹Šˆ‡…„……ƒƒ„„…‡†ˆŠŠŠ‰‹Š†…}ytprqommjhc^WY_diqw}€…‹ŒŠˆˆ††……………………‚…‹‰ŠŠŒŽŽŽŽ‘‘ŽŒ‰‡……‡ŒŒ‹‰‰‹Œ’“‘Ž‘’++++++******)))))))(''''(('((((((((((((('((((())))((((((((('&&((''''&&&&''%%%%&&&&&&&&&&&&&&%%%%%%%%&&%%%%&&&&&&&'((()()**++----..----.---------,+*****)))((((((((&&&&%$###"""!! !"!!""#$$'0B\nlmq{{tqrvxxywof\RDBn…xk€xxyqVFHQbhjmfbUTNDKHHEFJE>887;BAF^ZJˆŒpa~tpnaTKHA;>JPVyƒ}ytqqqssohe_[ZWYZaaWHIqupoifkw‚u|‡[OVN?)*,,**('&%%$#$$#"$&%&,:BEEDGGB=5)&&&&&()'*/3:DDJWd`ZVF@;@LWXZ\>B[T28fQN]irpmhb\XVTTUWWWWWUTQNJEA@@ACFJMQTXYZ[\]`chmrz|~€ƒ……Š‹‹ˆ‡ˆ‡„…„„„„ƒƒ„††‡ŠŠ‹ŠŠˆ‡„}xsqpqpmljga]Z[`djqx}†‹‹‹‹ˆ††……………………‚Ž’‹‡‹ŽŽŽŒ‹ŽŒˆ†ƒ€‚ˆŒ‹Œ‰ŠŠ‘”“ŽŒŒ‘Ž++++++******)))))))(''''(('(((((((((((((((((())))))((((((((())((((''&&&&'''%&&''''''''''&&&&&&%%%%%%&&%%%%&&&&&&&'((()()**++,,,,..----.---------,+**)))))(''''''''&&%%$###""""!  !"!!!"#$%,;;@F?O]:k{…ˆ‡„~tcTMIEA@FJQr‚zvsronprnie_[YONT`^RF2Gjmnjhhnv€|c@KNNEHo¢žj\o…‹†y51Lw“‰€wkd^[^c\f™Ÿ–‡|o\al\FB@DMWakqwvunU8+-;Rht~wd^dx††„„{i_S4&)-,*)''&%%$$$$#""#$(/:BDC?:8760'''&'((''*16:=8GQX[YVOIKCLYZRPF6Q\MVjYJYrspnhb\XVTTTSVVUUTSPLJEA@?@BEJMQTXYXZZ\^aejqw}~€ƒ…†ˆ‹‹Š‰‡†„…„„„„ƒƒ„††‡Š‹‹Š‰‡‡„€|vsqqqpmljga]Z]afks{€ƒ‰‹‹‹‹ˆ††……………†††Š‹‹ŠŒŽŽŽŒŒŽ‹‰…‚€|y{~‚‰ŽŽŒ‹ŠŠŠ‹““’Ž‹’‘‘Ž,,+++++*******)))))('()(''((''))))(())(((((())))))**)(((()*((()))('''&''''''''((''''''''''''&&''&&&&&&&&%%%%%%''''(((())*+++++,,------------,,+++***)))))(''''&&&&&%##$#"""!""!!!! !!!!!!!!###&)7OmrikqvslijkoqvwtkcVK?;j‹tyrpxwbKHPag_`hWJFDICHNKLKMECB@>?>E@AJD:==;81('''(('(&*1;=79BGI[^[\GFADKKIV`R@YRcn^J\stpmgb\WUTSTTTTTTSRNKGC@??@AEILPTWXYZZZ\^bgntx}~€ƒ†ˆ‰‹‹Š‰‡†„…„„ƒƒƒƒ„…ˆˆ‹‹‹ŠŠˆ‡„€|vrqpppoljga]Z\`hnu{€…ˆ‹‹Š‰††………………„†ŠŒŒŽŽŒŒ‹‹ŒŒŒŠŠˆ†‚~|yyxy|€…‹ŽŒŠŠŒŽ““’‹‹‘Ž,,,,,,,+******)))))('()(''(((())))(())((((()))))))****))()*((()))''''&''''''''((''((((((''''&&''&&&&&&&&%%%%%%''''(((())*+++++,,------------,,+++***)))))(''''&&&&&%####""!!!!! !!!!!!!!!!!!#"#)0FftmkptsmkhijmpsurkcVJ>6f—’~quxz\CHQek_]_PDFILGMQOMJJDBA@A?@BDCCF@n„‰‡ƒzkYNKECBBADG_…xvtqnjikjga^ZVPG>63;=867VmggffgowqF:7;_Œ£¦¤¦ž‡u`eyŒ†‰K1@„—‡ƒzsbZXVVNTˆ›’„yjONYP;19BKQWY\_cfe\J60=Qcikku|~™œ’‚z|wdT>*&'))*('&%$%%$###"""#*3:=?A>>;630+&''((((&)-2319<:DU[RW\D?DQTU^i]Lbwx~gDXktomfb\WUSRSSTTTTSRNKEC@??@AEILPTVWXYYYY\`dkqx}~€ƒ†ˆ‰‹‹ˆ‡‡…„…„„ƒƒƒƒ„…ˆ‰‹‹‹ŠŠˆ„{urqpqonkhe`\[^cimv}‡‰‹‹Š‡††…………………‡ŽŽŽŒ‹‰‰‰ŠŠ‹‹‹ˆ„|yxxwwx{}ˆŽ‹‹“’Œ‹‹‘’ŽŒ‹**************)())))(((())))))))))))))))))******++**,,,+****))))))((((((''''))))))))((''((''''((&&''''&&&&&&&&'''')))***)*,,,,,,,,,,..--,,,,,+**))))))((('&&&&&&%%%$##"""!!! !! !!! "$#$(/B^qmjottlhfdadintsskbUH81U”‘„vnszm[HKSda\]ZLDJONNORSRKGGEFILA<>@?CEDW‡‡†€rgUJGCBAB@>@\‡ysopokhhhea]YTPH91155556w‘‡}shVRUTTMq›–…yn^OZaT54;GNTX\^abc`YF9?KZdgfglph“™‚ysi^T?,())())'%%$#####""!#%09<=>?@<831/*''((((&&(-.1347:=>@BFIMNRUVWXXWWX]bhnuz|‚…‡‰‰Šˆ‡‡……„„ƒƒƒƒƒ…†ˆ‰Š‹‹‰‰‡‡„€{urqppoljfc_\]_ciqx}‚‡ŠŒŒŠ‰‡‡…………………ŽŽŽ‹‰ˆˆ†‰ŠŠ‰‰ˆƒ}yvvuvvwy{{ƒŠŽŽŽŒ‹’’‘Ž‹‹‹Š’“‘ŽŒ‹**************)((())(((())))))))))))))))))***+**++--,,,+**++*)))))(((((((((())))))))((((((((''''''''''&&&&&&&&''''((*****+,,,,,,,,,,--,,,+,,,+**))))))))('&&&&&&%%%$$$"""!!! !! !! "(5%&-?Wrtlntunieb_\_inswqjaTG6-J’„trrvaOJLO]YSROCFMONKLPY[PHHIFHLPNBA@@CDA€ˆ…}n_QICA>;=<9;T…}qopnhfgec_\VUPD533113356Mplhfecltt>,O~˜ £¢˜Š}cZw`v|r:6oskXNORRQW†–‡wmcXTVZUB?DKRW[]_`caZRHELR\bdgkpy€™Ž}sng_ZU@,*'(())'%%$####!""!#,7<=>?@?<8530+('((((''&+.135557;CMOBFUZ>AO\TVY\bMS\JRivwple_WRPPQPQSSSRQNHDA>=>@AEIMOSUVWWWVVW[_fmrw}ƒ†‡‰‰Šˆ‡††…„„ƒƒƒƒƒ…†‡ˆŠŠ‹‰‰‡‡„ytrqppoljfc_\]`fkry~‚‡Š‹‹‰‡‡‡…………„‚‹’‘‘ŽŒ‹‰ˆˆ†ˆŠ‰†ƒ}xwtuvwwwxz{{€…ŠŽŽŽ‹Œ‘‘Œ‰ŠŠ‹”“Œ‹****++******))(''()))())))))))))(())**++*+++++,,++,,,,,,,,,,*)))))(()))))))))))))))))))))))))(''''''''&&&&&&&&''''((()++**,,,,,,,,,,,,,,,+--++*)))))(((('&%%%%%%$$$#%%"""!  !! !! "%(,>Vounqsvojea]WV]iqvvrk`SF58[‰|prsl\QFGPTLDACDGMKKJLN]\OFDBA@@RVD=A>D>>i‡vmd[TD;888779B~~rlljhdc_XVWVRL<..1/./0275drja`cgis_9^|™›”ŠnNR^CavsG7rxlaGGGMNNdƒˆwh^ORTPLSUPSVY\^__bdb`RJNW]`dlolp™Ž|oieb[YS=+(&''(&&%%$###$"!!"&1;==?@A@>;864-&$&&''%$&(,/0256777=>@DGJMOSUVVVUTUVY]cjpv|€‡ˆŠŠ‰ˆ‡†……„…„ƒƒƒƒ…†ˆˆ‹ŠŠŠŒŠ‡ƒ~xrpqqpnljeb^]]ahntz„ˆŠŠ‰ˆ†††††„„„‡’’‘ŽŒ‹Š‰‰‰†ˆ…‚}zwwvuwwwxyyz{|€†‹ŽŽŽŽ‘ŒŠŠ‰‹’•“Œ****++******))(''())))))))))))))(())**++++++++,,++,,,,,,,,,,*)))))))))))))))))))))))))))))))))((''''''&&&&&&&&''''((()**)+,,,,,,,,,,,,,,,+++++*)))))((((&&%%%%%%$$$#$$"""!  !! ""'-=;9D??>=A@J€tlgdTA;6764468b~smjgec_\QLNMIE6**/.,./018Oojedcabfp[c{ŠŒ„zmRFFCBW~upU:vŠtk\BEGLKRkvui^TSTVXC>?@A@?=<85+$"##$$##%&*.10379<;:9889CMLLQ[UX_aWEI_dKOqtmd]XSQPOQQQQQPOLHC@>=?ACFINOSTUVVUTTUW\bkpw|€ƒ‡‰ŠŠ‰ˆ‡‡†…„„„ƒƒƒƒ…†ˆˆ‰‹ŠŠ‹Šˆ€}wrpqqpnljda^]^biqu{€„ˆŠŠ‰ˆ††………„„…‘’Ž‹Šˆˆˆ‰ˆ‚|wwvvuvvwwwxxz{}ƒ‡ŒŽŽŽŒ‘ŽŠ‰‰ŠŒ‘“•“Œ********))*))))'()))))))))))****))**))**+,*,++,,,,----..,,,-+*++**++****))**++****))))))))))))))''''''''''''''(())(())***+++++--,,,,,,,,++*)***)))((('((&%%%%%%%$$$#"""""!! !! #')9TovpnswskeaZTMJO]iotsme[NC75I]kfhqSEGC=BEGCBDEHFGIIIKQQNF@BBCED>;B==>?@:m{rkfcM@D=411235;rrieeeb`ZQKNOKE6-**--.//.7@inifb`\`jiiw„†xlT>FBAKX}xjbBz†tfW@BHJLVdge\TSTVVE4.8Vg``_ceddggg\_imlr{r^`‚ˆ|le_]\WQPN>+%&''''&%%%$$$!"!"&1:=?@@@CB@?>;4,$"""$$""$%'*./15:===71.+.6HVXPRYYiiLIbbZLmukc\XXZROPPPQQQNJFA>=>@CCFJMPRSRUWTSSUW[bjpv|‚‡‰‹‰ˆˆ‡‡†…„……ƒƒƒ„†‡Š‹Œ‹Š‹‰…€{trppppnljc`]]_eipu|ƒ†ˆŠ‹‰ˆ†„„……„„Š‘“’‘ŽŽ‹‰‰‰ˆ‡‰„zyywwwwvwwwwvxyz{„‰ŽŽŽ‹‹‰‰†‰Ž•–”’ŽŒ‹))******)))))))())))))))))))******++**+++,++++,---------,,-,,+++++++++****++++++++**))**))))))))((((''''''''''(())(())))*+++,,----,,,,,,++*)))))**((('&&&%%%%%&$$$##"""""!   !!#%+7Vpyolqvvoga\UMDEO]gnqskbWJ>424`~hgmoJFC>:@CGEDDEDGHHIIFKOQTXdnnqokcURU_P79Wxqjg_OB@93212312Hrjcba_^YQLIMNMG;.*.1/./237Joni_\[]bmsw{|uk\A?BDMS`rn__Mu„seSAGIJMZ]ZXVTSUR@0.4C_jmnkggfgiieckortzg[W`z{lc]YXYRPMJ;*&%&(''&%%%$#"""!#-9<>AA@@B@@?>;5("####$##$%%%(*.1379862,++.=>@CFILNQQQQSTSQQTX\ckqx~€ƒ‡ŠŠŠ‰ˆ‡†……„„„ƒƒƒ„†‡Š‹‹Œ‹ŠŠ‰„€ztqppppnkic`]]`flsv|ƒ‡ŠŒ‹Šˆ†…………ƒ‡ŒŽ’’Ž‹‡‡‡ˆ‰‡€}yxyxxwwvwwwxxxzz{„ˆŽŽŽ‘ŒŠ‰ˆ‡‰Œ“––“ŽŒ‹‹(())))))(((())))))))))))))))****+++++++++++,,,,--...------..,,,,++++++**++++**,,,,+***++*)**))))((((((''''''''(())**))****++,,,,--,,,,,,**+***)))*)))&%%&%$$##%%$"!"##""!!! !! !#'5Qnxrnpuuslc]VLA=DP]forpi^WJ<411f|khokEE?99=BEFFEEDFIECFCCLi‚•š†xvwiU_qyY7?snke[M?621110./13Xkc`^]\XRMJIJMNNG9+*-/-/343RrmaWXY\amzvpjbM<@?B]`gbYZXRmm`NHJMLORRTTUSQM=303@Sgqtxzvokilljmry}rZIZPWffc^[XYTMJHD5'$&&')('%%%$#"# #)7=>=@@@@AA@@?<3& ##"#%$$#$$%'(),-.*+*+*)+-7P\RZcdba[\i[K[soi_c[@V_SOOOMLHGC@==>ABDGLOQQQRTRQPQSX\cksy}€ƒ‡ŠŠ‰ˆ‡…„„„ƒ‚‚‚‚ƒƒ…‡Š‹‹‹ŠŠŠ‰…~ysppppomjhca]^bgnsy}ƒˆŠ‹‰ˆ†††„„†Œ“‘‹‰‡……ŠŠ{{xxxxyyxvwwwxxwwz|†‰ŽŽ’‘ŒŠ‰ˆˆ‹Ž“—•’ŽŒ‹Š(())))))(((())))))))))))))))****++++++++++,,,,,.......------,,,,++++++++,,,,++,,++*+++++*)**))))((((((''''''''(())**))****++,,,,,,,,,,,,**))*)))(((((&&&&%$$##%%$$""##""!!! !!#$(7LkwmmsuvuqiaZOA:=DO^hmnlf^UH<43>tvhgncAC@:9?ECDEDCAEHKFBBHh£ªª¡•‹|wx{hWg‚{T/`ole[K=62231/./12?@@@@AA@@?<2% ##"#&&##$$&.))&%&***+*++,.3FJ[__X`dZ_YLVqrbZE)4NNLOOOMLHEB?==>ABFILOQQRSRQPPQSX_dlsy}„‡ŠŠ‰ˆ‡…„…„ƒ‚‚‚‚ƒƒ†ˆŠ‹‹‹ŠŠ‹ˆƒ~ztppppomigb`]^bhnty~ƒ‡‰ŒŠ‰‡…………†Š“‘’‹‹ŒŠ‡‹„ywyzyxxyxwwwwvwwwxz|ƒ‡ŽŽŽ‘“ŽŒ‰ˆ‡‹‘”–’‘ŽŒ‹Š))**))))(())))))))))*)))))******++++,,+++,---.-.//......-.-,-,--,,,,,,,,--,,++,,,,+++++++*++****))((((''(((())*******(******,,,,+,,,,,,,*)()))**('&&&%%%%%%%%%&'**%$#""!""! "# !!%(0Ijwmhntxtsnf\SC76?HQ^hoplg\QF:46P~rabl_ABA:6?EBBBCC@CHQJ?H^Šž¥­¥”Ž’”ŠqZb}‰w;FrkeZO@622233/.155>^`WVUTSRNLMMKLLKKH??@AAAA@@?:/#! !"""#&#!"%)+))++**++++,*,//1DVmigf_fjXRVngh`N+1FOPNNNLKGDA>==>ABDGMOPPRRQPPPORY`gou{~‚…ˆ‰‰‰ˆ‡…„„‚‚‚ƒ„†‰Š‹‹‹‹Š‰…‚~ysoooonlhda`_adjpv|€„ˆ‰‹‰‡‡…„…†‹‘”’ŽŽŽ‹ŒŒ…|{yzxyxxwwxxxxxxxwx{~„‹ŽŽ’‘Œ‹ŠŠ‰Œ”—–“‘ŽŒŒŠŠ(())(((((())))))))))*)))))**++**++++,,,,+,---..///......-.,,-,--,,----,,++,,+,,,,,,,,,,++*++****))((((''(((())*******(******,,,,+,,,,,,,*()+))))(&&&&%%%%%%%%%&*00&#"!"!""! !!!!#%&1FdyogkrxvvrjaWJ807AJT`iprme[OD81==>ABGJMNPPRRPPOONSZ`hpu{~‚…ˆ‰‰‰ˆ‡…„„‚‚‚‚…‡ŠŠ‹‹‹‹Š‰…}uqoooonlhda``bflrx~‚†‹ŒŠŠˆ‡…„†ŠŽ’’“’ŽŽŽ~y{yyxxxxwwwwwwwwwwy|‚‡‘‘ŽŽ•Œ‹ŠŠ‹•—•’ŽŒŒŠŠ(((())(()))))*****))+*++**++++,,,,++,,,,,,..--//..........--....,,..------,,,,,,,,,,,,,,,,++**++(((((((())))))))**++**+++++++++,,,,,,,,+**))))(('&%%%%%%$$$$$$&&)(%#""!!""!!!!!! !"$)-@auskkrvxxtoe\M>01:DPZbjqricXLC71Eu‚lgjqVAF>99?CDCBBCACEDIa—£¦¨ª¬ª¡œ™–‡vyz’ŽŽ|Edlf`WOGGFEEFEFJLWZTHLRSRONMLKKKKKHGDDD>73.*'#%>YYW[\]]^]bUIRYRTWVUSMJG@4;EADIMLLQTW\\VI<701=P`lptvtnkhgfiige_[NURGHMZaa^[USMGD@=1($##%%%&&&&&$""+59:<>>??@BBAA@@?:/##$#""!###""%((**++++***+,.148=BDGTfggmiNL^jjd`ZTLLNMNNNMKGC@>=>?ADFIMMPPPPPPNOOUZbiqx}ƒ…ˆˆŠˆ‡…ƒƒ‚€ƒ†ˆŠŠŠŠ‰ˆˆˆ„ztpoooomkgecbbchnuz~‚ˆŒŒŠˆ‡‡††Š’’‘Ž‘ŽŽyyxywxxxxwxxwwwwwwxz}„‰’’ŽŽŽ““ŒŒ‹‰ˆ‹‘–—•‘‹‹‹Š(((())(())))*+******+*+++*++,,,,,,++,,----..-.//..........--....,-,,,,----,,,,--.-,,,,,,,,+++*++*((((((())))))****++**+++++++++,,,,,,,,+**))))((&&%%%%%%%#$$$$##%$"#""!!""!!!!""!!#%(1?[vskkqvvvvof\QB4/5>HU^fnqng_UI=67V{‚lfiqT?E>99BEEDCCCBCEHYp‹—š¤¬«ª®£¡žœ”r†‡€ˆ’†iQd_]]\YYVTUTSTTRWWY[SNMNKLKLKJIIJHEEBA>:73-(&'&CWPPW\YWZ`bUUWYUTRPKGBB?43>HCEIJLOQUXWQF=722>N]hpxxqkiifffghgXD8FMJHR^`a\XUPHDA=9,%#"#$$%&&&&%##&08;<>@???@BBAA@@=;/# ""!!###""%'**********+/069?<=?BFHKMMPPPPOONOQV\biqz~ƒ…ˆ‰‰ˆ‡…ƒƒ‚€ƒ†‰‹‹‹Š‰ˆˆ„€|uqnooooljgebcefjqw{€„ŠŒŠ‰ˆ……‰’“”‘‘ŽŽŽŽ†wy|yyzxxwwxxxwwvvwwxz~…‹’“’‘’•‘Œ‹Š‡ŠŒ’•••‘ŒŒ‹Š''(((((())))))++++,,+++++*++,,,,,,,,----..--//////..................----..----,,------++,,++**,,+***))))**))))**********++++++++,,,,+***))((''''&%%%%%$$$$$$$$$$&$####"!""!!!!""#"%)1=XsqjlprrsrmdYNC5/07BMVbiopke[OC75Be~lhflO@C@87BGECCEFC@@Nk––ž§ªª¬¢££¤¢Œ}ƒ‰‹”šŠzRYXWUTOOPPOOOOPPRV[[UPIIFEEFEEFHHHHEA?>:853,'&(.PZUSUTSUY]bWRRNPNKFA=?A7.3=AADFKMNRWYQF:834>KWelrslhhgfffghicN6@FEIX^^\XUSKEB?;5)######$%%$$###)29=>?@@??@A@@@??>:.#!!""!!"""""$&+++)******,04666689>:9630*%'*2R_XPRURPTa[DICEFEB><>>5/,-:@AEJLMNNPOC9743:HU^glmlihfffe_XVLOULCFOZ^\ZVSOHC><93'$#####$%%$$###)3:=>?@@@?@@@@@??>:.#!!""!!""""!"$(**)******,/3444569@GIIIMC@AUhonfbcgSLMMNMLJFC@??@ADGHKMMNNOPRTTTWY_fktz}ƒ‡‰Šˆˆ…„‚‚€€€~€…ˆŒŠ‰ˆ†ƒytpooonnljgedehiov{ƒ‡‹‹‰‰‡†‹”‘’‘‘ŽŽŽŽŽsx{{yyyxxxxwwwwwwwxxy|‚ˆŽ’“’‘Ž”‘ŒŠŠˆ‰Œ’•—•”Œ‹Š''((((()))**++++++,,,,,,+-----------......////00//..............................------,,,,,,,,,,+***))********++****++**++++++++,+******))''&&&&&&$$$$$$$%$$$#$$$%#"""#""""!!!##%(*7Kjypilmppme\QD91++.8@KVajpple\QG;35Y{ylgknQ@EC<7?DEEFHGB?No…•˜‰¡¯¹º»¸ª§’|“˜¢žŸsMUXXVWVSRSSRPRRUUUUWPNJHFFEECBBAAAA@>><;:63.)(+*5T][XSQOR[aJ;??CB?;8873/--9DIKLNPOOQO?652.5DQWY]dihkkfgi\OW8LYKGNY]\ZVSQKD@<:91&####$"#$$####")4=????@A@AA@@@@@?<-#! """"""""!"#'')*+*+))++-102479669=@ED?CFAKfg`_\ZUNMNNMLJFCA??ABEHIJLLNNPRVWZZ]aaemt{~€„‡‰Š‰†„ƒƒ‚€~‚‡‹Ž‹Š‰ˆ†|wronmmnmjhgdefjlpw}€ƒˆ‹ŽŒŠ‡‡Š“‘ŽŽ‹ˆzux|}zyxxxxwwwwwvuuxxy~ƒŠ”“‘Ž“–Œ‹‹ˆˆŠŽ’•–”“Ž‹Š''((((()))**++++++,,,,,,,...--------.....///////................................--...-,,,,,,,,,,+*************++********++++++++,+******)(''&&&&&&%%$$$$$%$$$#$$$$#"""#"!!"!""##'.7EfyrkknmlkcXK?4,))-3:GP[fmqpkbXMA516^{y~mein`?CC>8>EEEFIHDHaw‰Œ”’ƒ‹¤±¹»¼º²¯©ª¬®  ¥—\XXXVTTTPQQQPPQSUXYYTPMFFEEDDEDCC@@A@>>=;;950-+,.7UUWWRQPTZUB;;>>=;8751//2>HLMMNPPOQUG(/0.2A@AAAAABA@@@@@@>/#! """"""""!"##$$()()**++,/135653668=?>CCC=I_ff\ZTUOLNNMLJFCA?@BCEHJLLLOORUYZ_aegglpw{~€„‡‰‡‡†„ƒƒ‚€~„ˆŽŒŠ‰†ƒytpnnmmnmjhfefgjmty~‚†‰ŽŽŒŠ‡ˆŽ“‘‘ŽŒ‹‹‹Š€utz|{{zxxxxwwwwwvvvxxy~…Œ’”“‘“‘‹ŒŠˆˆ‹”•–”’‹Š(())**)))******++++---------......--/......0////.../............///////.////////-./.....-,,,,,++,,,,++++++++*********+,,,,++***+++++**))('''&&%%$$%%%%%%%%$$$$$$$$########"""#$'+1=Zxrjjqpph]TH8-(%&)-5?IT^fnome]RF;20>gwvkbelvK?D@::EDDFGFL`tˆ†Ž‘„‘£±µ¶º»¶¹»º»º®¦¢žŠjZVRRQSSTPPNMMRTRTUUSQNKHFEDCDCCCBBBA@@>===9841...AWVQPQPOMUV;5;<=:8432019GMNMNNNNNQSSG=)(/5;CJMV[_bdfgfcabUVTKPY[[YUQMEA<;:60'$$$$$$##"#""""#'2>ACCB@??@BAAAA@?=3%! "!!!!!!!!!!"""&))**++*+/1324468;;:9==BGMR[fba\VPTQNNMJFB@@BBCFIKMLMMQUZ\`ekoppptw}€ƒ‡‰‰ˆ†„„‚€€„‡ŠŒŒ‹‡„{wronmmmlkjgffgilqw{€‚…ŠŽŽŠ‰ˆŒ’““’ŽŽŽŽŒ‰‡‹ˆywx{}}zyxxwwwwwwwwwwwxz€‡’“’‘““‹ˆ‹Šˆ‰Œ‘“•–“ŽŒ‹‹‰(())**))****++++,----.------......../...//./////../-............///////.........-./.....-,,,,,++,,,,++++++++*********+,,,,++**++++++**))('''&&%%$$%%%%%%%%$$$$$$$$########""""%(-8Mrxpmqtri_SF5)'%%&)09CNWbkpmh`YO@2/0Cmxxmccnzr=A?:8ACBEAG_vƒ€Ž‡Ž”“™ ¤¯®°¹¼¼¹³°µ´µ©©¡wUTYesyyyyum^NLLNNORRPNKHFEDECCCCBBA@@@?>==:9863..,@SPONNKKMQR:69:8652202=KPONNNNNNPUUTT929:;=AFRWZ[_b`a_\_YSNKOXZYXSPJ?<9750*%$$$$$$######""#&/;@ACB@?@AAABBA@?<5)!!!!!!!!!!!!!!"""$&(**(()*,/0124547755699;BJSblbZRPOSPMMJFCACCCDHJLMLNORU[afkrvwxxxy}€€„‡‰‰‡†„‚€€€‚„‡ŠŽŒŠ‡„{ysommmmmlkifgggimrx|…ˆ‹Šˆ‰’’ŽŽŽ‹‡‡Œyy{|||zyxxwwwwwwwwwwwx{‚‡“”’‘‘“‘Œ‹‹Šˆˆ‹“•••’ŽŒ‹Šˆ))))******+++++,,----.-............././0//00//////..........................................-,,,,,,,+++++++*****++++,,,,,,++*++++++*****)'&&&&%%$$%%%%%%%%$$$$$$###########"#$'+4GivnjqvvncVF5'$$$%&)1:EPZdlpkf]RG:.-4Usxxlcahw~U:@76?ACDAQu†Œ†’•“––“—š›¥¦¬¹¾¼³³²³±¬¤¨¦”€^bt~~}|{xxutk`YXWURROLJGDEFFDDDBCA@??@>=<;:98754.-/?OLMLJIGIPL66965310/5CMRRNOMMLLMPSW\VF;9<;=@IPVWZZZY[ZYUPLLSXZVRNLC9840,(%$$$$$$$$$$###""#$+8@ABCBAAA@@AA>??<7*!""!!!!!""""""##""$&+*)**)****/2244010/57<@EJ[fjaYTOPPNLKGCABAEFIKLMMMNQU]aipx|}}{zz~‚††ˆˆ‡‡„ƒ€€€ƒ†ˆ‹ŽŒ‹‰†‚~ytommmlmmkjihghhjnty~†ŠŒŠ‰’‘Ž‹‹ˆ†‰ˆyyy|}{{zyxxvvwwwwwwvvvy}ƒ‡““‘‘“ŒŠŠˆˆ”•“•‘ŒŒ‰ˆ))))****+++++,,,-..................../00//00//////......................//..................-,,,,,,,+++++++*****++++,,,,,,+++++++++*****)'&&&&%%$$%%%%%%%%$$$$$$#########$#"#%(0=^ysinvvqgZL6($#$$%&+3;FQ[ennjaZMA3--8\xws~pacht€u@=:5<@ACLf†”––˜™’„Œ‘‘š ¤²¶·´±¯¬­Ÿ™––”nt{zvwvwtssstqomjhdb]YTRNLEDDBABA@AA@?==<;:9875430--@MGGFFFFKQF686530//8IQSSQOMMKKLLPRYWXL;69<=CJMRTVTTTUVQMMSXYVSOLIB81/+%#$$$$$$%%$$%#"$""##&2=?@AACCA?=@BB@?>8,""""!!!!""""""##""$$+)*+,*(((+0//0-**1403879>OWaWQZYQPMKIFC@@CEEHLMKMMLOT[cjsz‚€~~~„†ˆ‰††ƒƒ€€€ƒ†‡‰ŠŽ‹‹ˆ…‚{wrnmmmlllkjhhghhjouz}†ŠŒ‹‰Š’‘ŽŽ‹ˆ††Œ†zxz{}{zzxwwvvvvwwwwvvvy}ƒˆ“‘““ŒŒ‰‡‡Œ’””•“‘ŽŒ‹‰ˆ))))***++++,,,,,-.......////....--//000000//////................//--00////////....///.......-,----,,++*,++++,,,,,,,,,,,,,,+,,,,,++++***)*)&&&&%%$$$$$$%%$$$$$$##########$$##%),6Nswljruqk_O:*$!""#$'+3=HR^dkje]RH=1,5Hk}}€sbcht€e5968?CB[|“‘™ž˜”œ”‡Š‘˜¤£©«¯³´©£‘Œ†tgtuutrqroonnnllifb`__^\\\XUTQMJHEA==>===<:98765210..6/.+&%$$$$$$$$$$$$$$####$+8>@@@@BA>>?>?=<;5+"!!""!""""""!!####$$'((*))(+./,+)*))*-17<=CGNW[aotbUONMKGDB@AEHHIKMMMMLORZbkv|€ƒ‚€~|…‡ˆ††„ƒƒ€€€€„‡ˆŠŒŒ‹ˆ†ƒysomkkklkkkigfghimqv{†Š‹ˆ’Ž‹Œ‰…„Š€|yy{|{{yxxwvvuuvvvvwwxz~„ŠŽ’‘“’ŽŽŽŽ‰‰ŠŽ“•“”’‘Œ‹Šˆ‡))))**+*,,,,,,---...../0////////..//000000//////....////........//--//////////....///.......-,--,,,,,,,,++++,,,,,,,,,,,,,,,,,,,,++++***)('&&&&%%$$$$$$%%$$$$$$##$$$$$$$$$$#$(+2Ek|okpttncUB-#""""#$'-5=HR]fjjbXMB705Gi|~}v}wbdfp€‚~H776=AHjŠ—š–˜£¤›ž›‹ŒŽ˜¢¦­¬®±­¦œ“‹ˆ‹‹„ˆ€kooqqpmmkjkjiihdbe`__ZYXXXWTQSRSRRPG?==;::98776422...=DBACBBDIT>664024BOSVWSSPOLJJLLMQSMNTSLB?@BDIMNMMJDDEGHFDDCEFC@91,+)'&%%$$%%$$$$%%%%%%%%#%,6<:83*#""!!"!""""""""####$$&'''&*,09<3(('&')1=CEMPQUVY_p€y^XNKLIA@BBCFGIKMNNNMNOU^gr{€ƒ‚€~…‡‡†…‚‚ƒ€€‚‚†‰Š‹Œ‹ˆ‡ƒ€{wrnlkkklkkkigfghimqv{†Œ‹‹ŠŠ‘‘ŒŒ‹‡ƒ†Œ†|zyz|||{yxwvvvuuvvvvwwxz~„ŠŽŽ‘•ŽŒˆ‰Œ‘“•“”’‘Œ‹Š‰‡†))))++,,,,,-----....../0////////0000000000//00//..////..........00////////....//......////...---,,,,,,-+++++,,,,,,,,--,,,-,,,,,,++++**))('''&&%%%%%%$$$$$$$$$$!#$$$$$$$$#$$$(.<`tlqvyqhZN5$!!##!#%'.5?=?@?<84-&#!"#!"" """""""""""#&&%&'''(')4:/(&%''+1;BEJNQSSWXYj~]^oPHGC@@@BEFIKLLNNMNMQWcmv}€~}}~‚‡‰‰‡…„‚‚ƒ„ˆŠ‹Œ‹ˆ‡†‚}wtqlkkkkkkkjhfffgimqv{‚‡‹‹‹‹Ž‘ŽŽŽŒŒ‰„‚ŠŒ|yy||}{zxwwuuuuuuvuvuuxz…‰’”ŽŽŽŒ‰ŠŽ‘”””•’ŽŒŠ‰ˆ‡)))))*,,,-----......../1////////00000000000/000/..////..........//////////..-///......////...---,,,,,,-+++++,,,,,,,,--,,--,,,,,,++++**))('''&&%%%%%%$$$$$$$$$$$$$$$$$$$$%%$&)3Oyqpw}zpdTC-#!""##$&(+3Uz›¥¦¥§¢™ Ÿ”ŽŽ—Ÿ¢Ÿœ”Šuiba\n‡v_gghhfecbdeb___]ZXZXVQOMIIIKKKLKKMNMGC?<:99875554110.9HEBEDFFJOF22437HRUTTSSSQOMKJKLKKMMKKJ?0%',29;CGFD@@A@BFJIHFC@6/*'''&''''&&&%$%'('''&$%%%%%&*-38:=>;61*$""%$"#$%!""""""""##"#$$%%&&'(&'(&'('&&%'*26>DGMNPSZcnaN^\ICA??@BFHIKLLNNMNJOS]iqy~}|}„†‡ˆ†…„‚‚‚…‰ŠŒŒ‹‰Š‡„€|wsnmlkkkkkkjhffffhlqv{‚‡‹‹ŒŒ’‘ŽŒŠ‡ƒ„Œˆ{vxyzz{ywvwuuuuuuuuvuuy{†ŒŽŽŽŽ””ŽŽ‹‰‹Ž‘””•”’Ž‹‰ˆ‡†****,-,,,-......./////00//11////0000000000000011//////.0////..//..//////........////////.....-......,,,,,,,,,,,,----,,-,--..--,,,,++**))((&&&&%%%%%%%%$$%$$$$$%$$$$$$$%%%%%'+;fwqr{yk^M:(#"!"#"#&(*1:EOZ`cc`VOQ\m}ˆŒ‡xxv|}k`^dz„ˆŽC+2<>AABFIECA:4,(('*++*()(''&&&&&&'&''&$&%$###"%+.231-'"""#$%""###"""""#"$%%&&)+'&&$&&%%''&&''(()),.18=EJPW`jvu\Y\j^G@AA?AFHIKKKLNMKJLPWemv|~~~}z~…ˆˆ‡†„ƒ€ƒˆŠ‹‹Š‹†…{wsnkkkkjjkjhggggghlqv|„‰ŠŠ‘‘ŽŽŽŽŒ‰…„‡Œ„~zyxy{{zxvvutusuuvvvuwvx}€…‹ŽŽŒ”’ŽŽŠ‰‘“”•”“’Œ‹‹‹Šˆ‡†****,-,,,,....////////00////////000000000001001100//////////..//..//////........////////.....-......,,,,,,,,,,,,----,,,-....--,,,,++**)(''&&&&%%%%%%%%$$$$$$$$!$$$$$$$%%%%&),?q„try€‚ufYF1$#"""#"#%'*19DOZ`cdaZ]gq~†‰Š„y~€j`\^v‡ŒŒz6/5Ow‹— §©©¨§¢œœ—‹‡|‚Œ’‘Šyuvsfagkr}u~lV_cb`\\_]\[\XVSSSQPMKHFDAABAACDDDEGIIJGB<8665666533227KOGGGGJGHL=028FPTTUSRPOPNMLKKKKJIFEB8*###%&'&)+/:<<@DDEFB5-*+---*++*)*)((''&&&&&&&%%&&%$$$#"##"$$""!""!##"""###"""""#$$),15975.)'((%%''&&''(((*+*,/7DMTX[\[^hiWPJC?>?ACFHIKKKKLMKJJOVbitz~~~}zƒ‡‡‡…„ƒ‚‡ˆ‹ŒŽ‹Š‰„„zvpliklljjjigfffgijnrw{†ŠŠŠ‹ŽŽŽŒŽ‹‡„…‹ˆ}yxxy{{{yvvutstuuvvvuuwy~ƒˆ‹Ž’”ŽŽŽŒ‰‰‘“”•”’ŽŒ‹‹‹Šˆ‡†**+++,--+,..//0000001111111100110011001111111111111100//////.../..////////....//..00////.....----..-,,,,,,,,------..,,,.....----,,+***)(((&&&&&&%%%%%%$$%$$$####$$$$$$$$%%%+.@outy€€p_Q=+$###"#"#%(,08CNX^ac__ku}…‰‹‹ƒ|ƒ…|m_[\t…{nv~b21Dp…”¥¨¨¨¨£œ™“„wu„ugelqj`eZpp]qyUSWYYYZZVWVTSSNOPLLKIFDA?=<;jusz€zeWI3'$#####"#$'*-6ALV]`ba_n|…ˆˆŒƒxx{}}o]Z[gf_i{ƒ|H)>d}Ž–Ÿ¤¦¨¦¢—“€u|vprpoleahhdgpeN^Ic…YMQSTUSUUSRRPNHIKJJGEB@=;98999;=IEDCDEDFHTC6BOTUTSQQOONNMLKKKIIGE?2$%#$$%&''(*)4@BB?7.+-./,..--,,++(''''''&&((('%%%%$####""""""""##"""#!#""##$$##"%*4?JSWWVRLA3'$#%&&&&)),/-,.36;eusv|paP=+$#%$######&)-6@KV\adebn€…‰ŠŒ‡uwx{~o\YZfcgpzx|=-Vs‡“š ¥§¥£Ÿš–†z{wqjihjegjhqw{rTHQW~bIOONKPMSRNLJHDEHEDDCA?<6544366779>BBBAAA@@<963465654468=FEDDEECELWG@MSUSSQPOOOMMMLKKJJHF>1$$#$%%((''(*+4960+-..--..-.-.-**($#&'*'&'())*'&&$$$##""!######"""##%$"!"!!""""#(2@LVaihgaZPB3*$$%%&&(*05:=A=8L\lvuvvsngaZQMHD@?ABDGIIJJJKKIIIIJP[grzƒ‚€„†‡‡…„„ƒ€‚…ˆŠ‹ŒŒ‹‹‡ƒ~xrnjjiijjjjihhgfhjjmrvz~ƒ‡Š““ŽŒŽŠ„ƒ…Š…}yvvyz{zxwuutsrrtuvvvvvw~ˆ‹Œ’˜‘ŽŒŠŒ‘’”””’ŽŒŠˆ‡†„„‚**+++,....-.00112222220011110011111111111110000000000000////////....//////............--..--..--,---,,,,,,,,------..--......----,,++**)(((('%%&&&&&&%$%%$$%$######$$%%$$%&)/6Aa~usuxkZJ5&"$%$$$####&)-5?IS\adgnz„†ˆ†}kpwvxzr`]^dguz{y}Žx/>hzŠ“™ž£¢¡ ›‡€zvpggfhgegox|xwob\Wg]CKJFFIJTUMGFD@?BA?@?@>:63310034459>@@???>>>>843556665467@HFDDFEEHOXKHPUURQPOOOMMLLLLKKIG?3$$#$%%''&'(*('*'(,/.///.0.-/./-'%##&'*(''(((&''&%$%$#"""######""##)52%!!!!""""$,8DS_ktxtoh\N?0*%%%').0/4;=<;<=:853211/./11159<=>>>>===;8556776888:1)'),04<<=;998778641//./-,-///269:;>>>====;877777997:<=DGFDDFEEHLPNHNPPONMLMMMMLJKJIGC9&$$%%%&''())(&%$%+../..22/0355/' !#',*(((((''''&&&&%%$$""""""#$##!""#"#!!!!""&0?LYj{‡Šˆzqf\MA8436ASalk[]bgy}‚€wttuusnibWNEBBABDFGIIIJKIIHHGGKR]is~…‡‡‡‡‡‡‡‡…ƒ‚€…‡‰‰ŒŒŠ‰ˆ†ƒ|vqljhhiihhjjihgggiknquz}„ˆŽ”•‘ŽŒŽ†‚‚†Š…€}yxvwyzyxwvsssssstuuuvwy|‚†ŠŽ’•’Ž‹ŠŽ’‘“ŽŒŒŠˆ‡†„ƒ„‚---.....//01112222222221121122111211222233333333330000//////////................//--.-,,,,,,,,,,------------------....--./....--,,+))))))(''&&''''&&%%%%%%%%%%%%"$$$%%%%'+05<867:::867553266510/-,,,(*-,,/1467:<<<;;<=977899:?EGGEDEEFGIOEAKNKLMLKKKKKKJHGGE=+#%%&&'')*,)(&%$%,.///111688:80& "'-.,*&)()'''''&%%%&$$$$$############""!!""%.=L]m‰Š‰‚}umh^TPIFHTbfekeelsvyx|zvuwtrqkcYPHCAADEGHHIIJJJJGGFGLS^it…Š‰ˆ‰ˆˆˆ†„ƒ‚‚„‡ˆŒ‹‹ˆ‰†…‚~xrnjihhhhhhhhhggghjkprv{~ˆ”•‘‘ŽŽŽŽŠƒƒ…ˆˆƒ€}zwwyzyzvvutsttttttttsvx|ƒ‡ŒŽŒŽ””Ž‹ˆ‹Ž‘‘ŽŒŠŠ‡‡†…ƒ€,-./..../001122222223321121122111211222233333322000000//////////................//,,,-,,,,,,,,,,------------------....--./....----+))))))('''&'''''&&%%%%%%%%%%%%$$$%&%%)-33=JYgrngfid@$%%$%%%%%&&%%(*.6ANat|yumaJ4.6Yxsry{laagir†ywwzzdK]pxy€ƒŠŽ’Š‚vlhddgaY\binv…|rllnqsv>1475455564322210231/..,,,,('))+,/02389:<<<<<;:89<=?YbHB@>BIIFFGFFHGHJBCHJKLKJJJJKKJHGGD@5&%%&&(***+*'%%$%,.//.048=>;=;3' "*012/-,++)''))&%%%%&$$$$$$##########""!!""'.FHJLIFGIJKJHFEC@9)$$&$(++++*(%%%&+.00.1:=@B@=:4(%-24785/-.*&&%&''%%%%$$$#%#%$%%##""""##"""#%*4GYj}†‹ŒŠ‡†ƒ~{zwmlgY[W\f^F@F5<`v|wssntog\SJDCCBEGHIIIIIIIHHEHMT_mxƒ‰ŒŒŒ‹ˆ††„‚‚„‡ˆ‹‹‹Šˆˆ†ƒ}ytoljhhhiiiihhhgggiknrtx{€Š’••’‘ŒŽ‰ƒ„„ˆ„~~zxvuwxxwvuutsssssssuuvy{€†‹ŒŽ””ŽŽŽ‹‰ˆ‹ŽŽŽŽŽ‹‰‡†††„‚‚ƒ„........00011122223322222333332233333333333333334411000000///////////.............-,,,,,,,,,,,,,,,----------------....--/.////--,,+)*****)(())))((('&%&&%%%%%%%%$'+)'%&(+,/5>LZfoqkgcfcA"$#%$%%%%%&))+3DTl…ˆ„{tj_VPIDFRabf~zncahk~‰‡{ssuvw|yOQgljkoustkgjklot|}€†‡„„yq^MD;1224---.0011.///,*++++-,,,++++(%&&'(*+.135787567779::>BDFHJFFFFHJLMKJIHKKJG@@DHJIDDGIJIHGEB?9+$$%&(+,,,+)&%%%*.00/6>@DCA?>3( !"'07<<99852.(('%&&%%%%$$$#"#$%%%%#####$$$$$#$(2GYj|‡ŒŒŒ‹ˆ„…ƒ‚{un]\cieZPVO?Kj}~vthC`qhaUMFEEEGHHIJJIIGGFFEHMT`nxƒ‰ŒŒŒ‹ˆ†„ƒ‚‚„…ˆ‰‹‹Š‰†…†ƒ€|wsmkihhhhhiihhhgggiknruy{‚””’‘ŽŒŒŠ…€ƒˆˆƒ€}zxvuwxxwvuutsssssssuuvy|‚‡ŒŒ‹–“ŒŒŠˆ‰ŽŽŽ‘‹‹‰‡††ƒ€‚ƒ†‡--..///-/011112222222222233333223333334222333333332211111000/.//////..--..........-+,,,,,,,,,,,,,,------------------............,,,+++++****))))(()('&%%%%%%&&&'*8C7'&'&)-06?L[fotpigbeZ/$##$&$$&'(,3=Ocx…‹‹…wld`YWRNOXadn{wngfhp‡Œƒyknrprw{WAWbfacegwnalos{ƒˆˆ†ŠŒŒ‰~kWD86444471*()/.----,,*)))*****(''''%#$&&''*,/13455333337889448?;697.'(*,--,,+*('''((''&'&%%%#$$#$&%'),./10222222478>CIKLLLMONNQTWXXXVROKJIGBAA?=?BCEFGFD@<5*&&'()*,,.-*('&%(-.0040031:AMmyuvvsokc[QIFFHJJJJJJJJHHFFEGKUamw‚‰ŒŒŠ‡„‚‚ƒ„‡Š‹ŒŠ‰‡…„‚€€|vpkihhhhggiihhhggijlosvz–‘’ŽŒŒ‹†€€„‰ƒ€~{wvuvxxwvtttsrrrrsssuuw{~ƒŠŒŽ’”‹ˆˆ‹ŽŽŒ‹‰†ƒƒƒƒ…‡‡‰--////0011111133333333333333333344443333443333333322441000000000................,,,,,,,,,,,,,,,,,,++,,--------------......//..--++++++**++**))))((()((((''((((()+?UP<;-)*.2:EP[dmorlhdbegC#"%&$%+.;Oi|‡†‰‰ˆxcXX^a`_]ZWWe{{ozsggfmtxyvpomoruz\36KVVXXR\ntxrbk~zˆ†}iP@98865889;?5('''*,*)(('%%%$&%%%%$$$$$$"!$%$&(*.,-///0111499?EGMNOOOQTUVWYY]ZYXURNJIIGDB><=ABABCB>8/*''&')**+,,*(((%'+/03@BGJJH@2' &5:<=>=93/--.--*(&$$#!!!#"!! !"$$%())*.-,.04EZp~}„ŒŽŒŽŽ‹Š‡}uj`WOE7.(&#&*6:@NYiottnf^SLHGIIJJJJJIIIHFFEFLWalx€ˆŒŒŒŠ†ƒ‚„†ˆŠ‹‹Š‡‡†…ƒ€{voljihhhgghhhhhghjknotw{„’”’‘ŽŒ‹Œ‹‰ƒ€€…‡„}|xwtvxwvvtssrrrrrrsstuy}‡‹Œ””‹ŒŠˆˆŒ‘‘ŠŠ‰†…ƒ‚ƒ…ˆ‡ˆ..//0000111111333333333333333355444433334433333333334410000000..................,,,,,,,,,,,,,,,,,,++,,--------------.....///..----++++**++**))))(((((((((()))))**2IXK:-*+/6>GR^gkkflmc`cif;"$$%),5Ih‚…„…†…|oXJNPX^bb``\Zc}~{‚ujjhkmouxrmnnruw^1.7KQPQMNX^ZRFBi}{uzvkP;56:987768;<;.&%%&''%%%$$$$#$##$##"""""!!##$%&)+,,----.0147;>FJKNPQQSVWZ[\Y[]\ZYUSONNKIGDB>>@@AA?<5/*''&'((*+++*(((''+..4BBGIHF?-#  )5;=?>72.+)&$(,,*&$#"#""!#"!! !!#$%'*)*,,05;9ESd{x|‰ŽŒŒ‹Š†yskfZRB9.((+09DFLONQ\kkicWOJIIJLJJJJIIHGEEEFLWanyˆŒŒŒ‹‰†ƒ‚……‡Š‹Œ‹Š‡†„ƒƒzumjhhggggghhhhhghjmorux~‹•’’Ž‹‹‹‹‹…‚€€……‚}zxwuvxwvutssrrrrrrsssux}„‰ŒŽ•“ŽŒ‹ŠŠˆˆŒŽŽ‘‘‘‘‹Š‡…„„„…ˆ‰‰Š////000022221133333333333333444455553333444444443344432011000000..........----------+++++,,,++,,++,,,,,,,,,,,,------.....///..--++,,,,**++**))**))(())'')+**,,+,-09LT<--/3:BLU`hjga`khbbcoc/$(+/>^~‡„‡…w]B5=<94.*))(((((*++****(')*.4AFHHEB7) "#,6=><940,*('$$'(*($###$$"!"""! !$%'(+--/3665IF53Vd]H<55689;;::979:;=-%%#$%%$$##"""""" !!!! #$&&%&(*+***,.49;ENT]fjjbUKHakecdnq>!(*6Qy‚}~sW9&(+,153,131*1DC843247:::;;;78:;<6(('&%&''%$""##""!!!!!! ! "#$%(***,,,.367;:4/*)&%#""!"$!%&)))&$#"$%%%&&*++,./-.;KXctxws|{„‡ŠŠŠŠ‡„y|‚…ƒ|xrib\VOB:>:=@CEGHLNUQVZJHVS_i\MIJJKIGECDHQXdoy€ˆ‰‹ˆˆ…‚‚ƒ„‡ˆŠ‹‰ˆ‰‰‡†„{vqmhgfeeeggghghhimmpqtvŽ’‘Ž‹‹Š‰ˆˆŠ‡…‚€€„†‚}{yxyyyutssrrrrrrqqssuv{‚Š‘”’ŽŒŠŠˆˆ‰‘‘‘‘ŽŽŒŠ‡††„ˆ‰ŠŠ‹ˆ‡001122333322222233432344444444445555554444444455554433323333200000......------,,,,,,+++++++,,+++*,,,,,------,,------......--..--....--,+,,,+****))+++--/0122334444444>JC9BLT]fklj`UE8Hiidbita*#+:Z|}xwc7%'*),,059@EMS[frx„’…yxse``aipofgkmnor],(*-129FA@<6**)('(,/023598::9:;;9779<;-(*+,())*)('%#$$""""!!  "#$%&)))*,,,/368905VpiccjqH$)8Z~{xs\.&&)((+,039?FKXiwyƒŒzud^\]clphggilorZ-'*-5;9RH=:92+%'$&&)-.27@=989:998668<<7-+-/,,,...,)%#$##""" "##$&'())),,/373139>AHMPQOQSUVXZ]][[adcbb_^_ab\RTRG;753320-.-+++,,++,,./003;@CCA;1$!!%'(-26:93-)(&%$""$%#$%()),-(%$$$&''()**)(+/8ETU=5*'/BXk}ˆŠ‹‰ˆ„}ukhqx‚~xsleaYRKCBFFFIPQSWTLIPNORYeV]]MKJJGEDEJS]fqyƒ†‡†…„ƒƒ…‡Š‹‹Š‰‰‰‰‰‰‡†‚~xsokggedffggghghikmprtvxˆ“ŽŒŠˆ‰ˆ‡„…ƒƒ„~ƒ†ƒ€€|xxzxwtsssrrqqrqqqstsy€†Œ’‘’•‘Œ‹Šˆ‡ŠŒŽŽ‘‘Œˆˆ‡ˆ‰‰‹‹ŠŠ‰†„222222332222333333433444444455555555555566555555556655554422111100/.....------,,,,,,,+++++,-++++,,,-,,--,,,,,,------....--------------,+++**,,++,-/03367668899888:;;;;>JURT\cjrmcWJ8.,?fmebelf;$5Qw|vq^6&'))'()*.38>GWlyu|†Œƒzvh^\[^chggegmprZ+'*-5AGNRTPRSSUUWXX[\]a_`__[[YY[VSNG@674210---++,,,,--.1333;>AA?9/$!"(+,/193+'&$&&#$#&))(*..-,)%$%'*((((((+264<1*''*5CZn~‰Š‹Š‡ƒ|qjeiqx‚‚}xsokeYRLJIOOQUY\\VQWRKNTWZUIPXKDEEEDFNT]grx‚……„ƒƒ……†‰Š‹‹‰ˆˆ‰‰‰‡‡†}xsokffedffggghiijlnpruv}Ž“Ž‹ˆ‡‡††††…„„€€……ƒ€~|yxzuwtrssrrqqqqqqrst{ˆ‘““‹‹Šˆ‡‰‘‘‘‘‹‹‰ˆˆŠŠŠ‹Œ‹‰ˆ†‚333333331344333322444555443345555555556677555555556666554432221111/...----,,,,----,,,,,,++++++++,,--,,--------------....----..--....--,+,,,,---.1222559;::;;;<;;<<===@CFOVZ\fnvnaSF5.,3Mpifcem]/*Dj{upeA&%')'&))*/48AZryos‚ˆzvna][^efffcfjlkZ-%&,17Jg[N8//,++((*++*,.13224577685596582*)+,,++,,+,+)(''&$  ""#$&''''''((*(),/28>AKQQRTSRSSSTTRUXZ\Y[^]]dc`ZRQQMI887211/0-------//34548=>><6+##$&)/19M`lpk_RA3+*&"""&-64688830-,*++*))))&%*+'%%'''(2C\p‹‹Œ‹‡„|pc\`hry€„‚}ztpkhbbca`_b_^][bmhY[^]YWP@8OVeZGFFGLU`isz€ƒ„„‚ƒ„„†‰ŠŠ‹‰‡‡ˆˆˆˆ‡ƒ}wrnjgfedfffghhiijmorstyƒŒˆ‡†‡††††…„ƒ€„„€~}|zyzvvsqrrrrrqppqqrsu{„‰Ž”‘ŒŒ‹ˆ‡ˆŽ‘ŽŒŠˆ‡‰‰‹ŒŒ‹Š‡†ƒ333333333444333322444555444456555555556677655555556666554432221111/...------,,----,,,,,,++++++++,,--,,--------------....----..--....--,+,,,,-../24758;>?=>>>@@@AAABBCDFJLSVYZjvn_OD5/,-9ZtiedjlZ.2YwvpiO*')'(&)*)+/5D`wsjn~ƒztoqf^\\`bffcgmlm\.&&++0MgYRQ9**++,,-,,-.---//14587556875@P+*-,,+++*+++,*('&$""""##$$%%''((*/98?ABACDCDCDDDEEEGHKQY_^\cpkZL?3/.-0AipldfmsY-GlwumY5&()'))*,-1;Sqxljr||tomqm`][_acfgijlm_.%*,.1Pe]WUV:+'(((()*-./-,,-.0445314433KxR!//.---,++*++*('&%" ! !!#$$#"&&&(,37=EJOSWSSPROQNQOPRTRZ`efghddZN>QNY@0;=7220000100168862452*&#%&(4J^p‚‰‹‹ŒŽŽ‹„zkT1):EMIA?EFFEA;:8:::<<:863,&! """%'7NdwˆŒ‹‹‹‹†}ncUSYckt{€†…ƒ€zzyxxwmlnmuyupklqosrikjkno]NTOGCMUZbirx{~€€‚…†‡‡‰ŠŠˆ‡ˆ††‡‡„ƒ‚}yupnjggfffeggfgijkmprvv‘ŽŠˆ‡††‡‡†ƒƒ…‚€€„‡ƒ€€~|zyxvvsqqqqqrrqqppqrx€‡‘”’ŽŒ‹‹ˆˆˆ‹‘’’ŽŠ†ˆˆŠ‹ŠŒŒ‹‡†„„ƒƒ2222333344465544444444445555554455556666556666666666666543443222110/..----,,,,----,,++,,+,,,-,+,-----,,-..--------....------....//../.,-..//113689<>?ABDEFGGGGGGHHIIIKJNU]dggadg[L?2/.-04TpkidhruJ<@LJID?967:;<<9998-$ !$$$*?Si}‰ŽŠŠŒŒŠtdSKPZeov|„††…ƒ€€{zwuqsxzyxtufadkjpqong_TWgbfTLT\ejsvz}€‚†‡ˆˆ‰Šˆ‡‡‡…††…„~|xtomifeeefeggfgijlnqqtw„’‘Œˆ‡†‡ˆ‡†„„…€‚†ˆ€€~|zzyvtsqqqqqqqooppqry€‰Ž’•’Ž‹Œ‰ˆˆŒ‘‘‘Šˆˆ‰Š‹‹‹‹Š†„ƒƒƒƒ3333444444454455554444555555555544456666455677665666666654443322110/..------------,,,,,,,,,,,,,,------,,--,,----------------....////010...013559:;?ACDDEGIIJJLKKLLLKLNOSY`gmpmd\UK?62////;_phedjtk@UwysiF(''))()*-3Fi{rkn|zqmkdavk\]\`cdcchjj]0'*+-3XhZYYVRJ>,&&'''+23/..,,-../24>FPaxŠ”w+13234321/-+((%#"####"   !! !#$&)-3:@DEFHHGJLNONNMMPOQU\`feijgd[RMJZeXF?<:7320112366876332+'')Ai‰Ž‘’‘Ž‘‘ŒŒ’†lB?FLEDMITYUOMIB==>AA>>?<2($#%%&'.BXmŒ‹‹‹‰ƒyeNBHR_ipx€†ˆˆ……€~yvutuqtwwkfc]Z`^bfb[a\Z=AQd]^[_emrvzz|‚…†‡‰Š‰ˆ‡……„„‚‚}zvsnlieeeefffffgijnosst{Ž””ŒŠˆˆ‰‰ˆ‡…„…€€‚‡‡‚‚~|yxyusrpqqqpppoopppu{‚‰‘”‘Œˆ†‡ˆŒ‘‘‘‹ˆ‰‰Š‹ŒŒŒ‰ˆ…„‚‚„†333344444454445555444455555555554445666645567766566666665444332211//..------------,,,,,,,,,,,,,,------,,--,,----..----------......0335400023378<>?CEFEHJMNNNOPPOPPPOQRRUZahnospbRC>7543//1Cjpfeely^Nt|voP+&(**((*-2Jmyolrxnlgbdopb`^^acabeih\0'+,06_e[]][POL@.('&'(.2/-,,*+,,-7BNYix„Œ–˜[-4566321/-+*)(&#"""!  !#$$&(-269>ABDEEINKOPPOPMOSV\l_aoljc[QLYe_UOIA98852212567798662,(9b‚‡Œ“——”“•–’‹Š’Ž{R@ABFGKMMOOQQSUUTTTTSSSSUV\ciqqqoe[PC<8542105Pqieffru]q€ysY0&&)*)),.5Rqvmmu~qeggiiith_^\_``cdghZ0&,-1:ee^`^[OMNQD-)('%'//+)+**+/8GWbn{†Šˆ–›“6$/3444400--.-,)&$!! !"""#%)-.16:>@BCEMSRQNMIHMQU[ejoplmj`SNK`cUPPNI?8743334478:<<<949Z‹Š’—–“•šš—’ŒŠŠŒ‘…^>IIJHLMRTZ^_bcd]Z^]ZXTE0%##%%&&-ASi}ŒŽŒŠŠ„|pbE/1>JXelw‡‹Œ‰‡†…‚€xjaZY^\WWW\^^_jeXiƒˆ}LZa^ZYforsvz|‚…‡‰‰‡„……ƒ‚ƒ~~~~}zxtqkkgedeeedeeghilnpsss‚“““‹‡‡‡ˆ†…„ƒ€€ƒ‰†€€~|zxvuqqpppoooooonopw}…–“ŒŒ‹‰ˆˆ‹‘ŽŒˆˆ‰ŠŠŠ‰‰‡‡†…„„„†44444444554444445555444444444455555566666677776666667634554433331100/.----,,---,----,,,,--,,,,,,----..,,,,,,..--......--------//134468;<<<<<;;=@DDHJLNOQRSUWXYXYXXXXXXZ[`fkpsqoeZQPKA8433208^qgdhnvuxƒ€xa3(*(**+,.enhfhqy~yi>%'*())*0Bdyolkq€ukhgiigflc_]^bbdgfgV31114Gfc]__YFFNSTRC+&$##'6=69?EOX`jx…Š“–›™U*,.10/...-,++*'$"""%'*+++,/37:@JKQQNJOLNPSW^^^acaaZVYSUc^QONNNLHE>72.+//02345Os‡ŠŒŠŠ‘€ps€Ž’Œ‡‡Š‹rHAHIJLNQSSSVWYWW\^^\XUE,!! "$%$'4Jbw„ŠŠ‰†~uiR3(56>DO^o|‚‰ŽŒŒŠ…‚€ztneZWVX^djdedRL[{‡‰Ž}of[Valonw}„†‡‡††…„ƒ‚€{|{{{yxtokheddccddeeeghloqrty–”‘Š‰‰‰‡…„ƒ„ƒ€‚ˆ„‚‚€€|yvvusqpqppoonnnnlot{„‹‘“–’ŽŒ‹‰ˆ†‡ŒŽŒ‹‡†ˆˆ‰Š‰ˆˆ†††„…†…ˆ44444444554444444434444444444455555566666677777777778DWN96544432310000....-,--------------..------..--------......------....--//23579;?DDEFEBACEIKNQSTVX[]^_````_`````bdgkpsvtrk`WMHDHMKA4101Gkkdchp}…€zpV0(*(+-17Jiumkhozojhhiieceha`_^bdeefU31112Pga_c`WEFKPQRP?)###%1DNDCMSZbiy„ˆ‰‘— –;/.00//./.-++*'$"! !#%&&*,,-/37?DEKMNKIJMOQSWZ^^a`b^YUSUc]QPNNNLHEDC>830.,+//Diƒ„„ˆŠŒŽˆwU=AYx†…ŠŒŒuPDHIIILMNORSURRTWYXVRPC.!! "$%$&2E]r‚ŠŠ‰†ƒ}sfR/&7;?AMany…‰ŒŒ‹†‚yskcYSQPKOVX^_^_v‰Ž‘•™œ›ŸŸ—}WU[TU[`cp~ƒ„ƒƒ€~|{zz{|ywsnjgdddccddeeegimpstu~’—”’ŽŒ‰ŠŠŠ†ƒƒ„ƒƒ‰ƒ€|yxvtrqqpoooonnnnnpu|…Œ‘’”•ŽŒ‹‰ˆ†‡ŒŒŒ‹‹‰ˆ‡ˆˆ‰Š‰ˆˆ‡†††††…ˆ44443455444444444444555544334455665566776677776666667:GN<555444331//00...---..-...,,--..------..---...........------------....//1469;>BHJMMLIGGJLNPSWY[^_bbcddddccddeeffjnquvwrlcZQJCBBFIID:10Pofcbfr~„~ykWC536:BJYoqjgenumkijjheecmda`_bdddeS0,*,2[idaghWIKMOOOOL9&$%(0AVXKHRXagr{‡‰‡Œ”šž„%(.320//.+)***(&# !""$&'*-/24:ADGPTYYZ[]^emvustpkf`_ZPWf`SPOOMKIFDBBC=:421+2\~‚ƒƒ…‡Š‰€mK*()=cƒŽ‡„‡ŒvPAIJKKKMOONNNKKPSSONMKC.!!""$%%(2H]rƒ‰‡†ƒ€{obM-)+/6?Malu|‚…ˆŒŠ‰ˆ…yskfYUQPR]_]\bgo{’•› Ÿ¢¥¤œWLNSQJG@;ALVl~‚ƒ~zyyz{zyvsmhedddddddeeegjmqstu†–•”‘Œ‹‹‹‹‡‚‚ƒ‚€†‰‚€€‚€€}xwuspoppppnnnnmnopu~‡‘‘•”ŽŒ‰‰††ˆŒŽŠ‹Œ‹‰‡‡‡ˆˆˆ‡†……†‡‡……„Š‘44443455444444444444555544444455555566776677776666666657:3554443310000....--..-..-..-.//------..---...........------------....//357:<@DIMORRPMJLNQSUXZ_abeegffhhiihhiiiinqtuvurlcZTMFB@>=DMNE?>\ldccis}yrf\UV[`dlrnigdlxoijihfbdbmlfecdfddcR0)**<:=CEEOghddfoy||wysppprropnhefbk{pgfffeccbmvjfeadcdbQ,**,IlgfhgfKKONNONLLKH82599=SZTOSZ_iq|„‡‰‹‘•š›™C*,,***''&&&&&%#  !"!&'*/9Nahjjijpyyvustsqooponprrtsqm`UQOONLIGECBBEJQ``P[{€{||€„~m8&(%'/NwŠ†ƒ†ˆ‚hCDIJKKJKNMMLFFJKLLKJIG>.! "#$$%(3K`w†‰…‚~{tl\G.&1)(:P[elqw{~ƒ………„ƒ€{smijkhaZ^cgkpx‚‹‚B.0;M]`nqtfVXchimmh^RVgy{xyxzzyxupkfdbbddddddeegknqss{’—•Ž‹‹ŒŒŽ‡‚ƒ€€ˆ‡‚‚ƒ~|yvtrqpoooomlllmmosy‚ŠŽ“’Œ‹‰‰ˆ‰ŒŽŠŠ‰‰ˆˆ‡‡‡ˆˆˆ‡‡‡‡‡ˆ…„„†4433444444445544555544445455445545666667776666665566555553554333332211110/..-../00//////..//..........////..--------....------/0/28;@DHLQVY[[ZUSTUZ\^begjjlmmnnnnnmnpprsuvvvtsoib]UPMHEB@=9856;CRfeceisyuxvttuxvsolfbbebi}pijhjhiijo{nihcceb^N,*+/XlhfjnaELPPOPNLKIGFB?<=;=E9.#!"""%(8Pfz‡‡‚~~{tiX>+,0,.EQZ^gkquz|}~‚€|yutspngefhkqxƒŠŒ‹\2DL\ba]nsm†Ÿ£©¦“ˆ€‚ogmvwxzywxsojedbbdddddefgimooru~—™“Œ‹ŠŒŽ‡ƒ€ˆ‡‚‚‚€}{yvtrqpoooollllmmoszƒ‘‘‘ŒŒ‹‰‰‡ˆŒŽŠŠ‰‰ˆˆ‡‰‡‡ˆ‡††‡‡ˆ†ƒ‚ƒ‰5544443333444444444555444444445555775556666666666666665555542244332111000//../....//////////////......////..../---..--..------/0146:@DHLQX\^a`ZXYZ\``dhijlmnooooooqqrrstwwvutqmfa\WQMJHDA?;96213?9;54BQZbglu€ƒ‡‰‘•—˜šs $'&'%%$$""!! "7[lica`acirzwuuwvtsrpnnlllkkloptz€wZLONLKGECCCCJT`kt€„}y{{xuoJ)&&%&&.Ntˆƒ……zO7BFJLLMLLLKHEIKKMKJHFD>.# !!!%,AHLPV\`cda^\]_abfhilnppqoooooqqrsuwxxvtsokfa\VSPMJFD@=:74111GjeacilXKRUTTNJKWhfa_\Zdxpjidgjjgdexxkhfchg[J,'.PnihlojEELPSTRQOMIEC><@E>;9=9?TV[`gmv~„ˆŠ’•—˜X&&$$$$%%""!!  4Zkje``bcclvyuvuxxxxusqpnnlljjjklnsz‚y\ONLKGECBBEJRant€}ywwsp`9,,(&'(/Os‡‚€„„vI7BFJLLMLJJICEIJIIIHIGE?-$##!"#%,@Xm€ˆ‡‚€zrdP5""&2BMPVY\aeikmqtxz|||{{|{wtttrptx~ˆŒ‹ˆ‚71EB=8@ARdQYafku}‚†‰‹Ž’–—‘=&((%&%$##""!! "Eloe^\`ddfjxzwvwxy{zyxvurqpqmkkkiijlr{„z[PLJFECDCDITant~zvsstqrZ7/0,(''2Qu„€~‚ƒp=3AHKJLMLJIABGIIIJIHIHF>,"#%#$%'/C[qƒ‰…ƒ€}ypaM2 !%9KPTVUX\^cegklsvxy{{{}~zzxxuvy|ƒ‡‰…„33:74213Nj`]`gg5'*++,.9Re_WVPMVxshggcekejkn‚tiffmv`C&BdjhilpqNBKPUVVTROLGEC?=9:@JJ?9>E[XPX`bju{„‰ŠŽ’’•†- ()'''%%%""!!  /]nk_^`cegjqzwuvwxy{}}}}ywvtrqonmkjfhgksyqULJFECCCDJTblr|zrppqnnmaE1//++5Uw…~~€i2#5EJKNMLG?=CIKJJJIHIHF<*"##"%%&0G_wˆ‰…ƒ€}wo`H-#%,BPTWWWYZZ]\`dfkqtvxyy{}}}}|xz}~€ƒ……‚y77;AMRJDo¦·¾¶ˆ| ´º¸¶‘~Š—–’˜‚uvutokgecbaabbbbcegjmost|“™—ŒŒ‹Œ‰ƒ‚ƒƒ‚€€…‰…‚‚‚€}zvtrrqpnnkkkklllnpwŠŽŽŽŒ‹††ŠŒ‹Œ‹‰Š‰‡††………ˆ‡‡ƒ‚‰’445543555544444444333353444455556655555656665566667777664444443344343311111100////////0000//./00////00.///0000/..-....----..../134679=;859FI?:9BIPPV^fmu~„‡ŠŒ’“w%"(((&%%$! !""Bjnb^`cefjpw{wtvxw{|~}~~}zxuvwtrqojgfeffmsz|iPJGFCECGLUbrxyupmnpoopomaG2-,7Vy‚~€~d0#$*;FHJH?53<:95328CG>15=CFQ[^enu{‚†ˆŠŒ‘”k%&&""#" ! #Nnl`_eddirwz|wtsuxz{{|}}||xtuxvtttrpkigefhmszx`KHDCDDHMXfrxwrqnmonnqromk`TB?\z|~€y[(""#!*8AA7/9@BFGIKLJIHD6%""""$$(:Qi{ˆŠ…„‚xmZ?&%1DRVY[^__ZYWTSY]ekmoompsx|}}‚‚|‚ƒmVCFEIE@a“¨²·¸ªŽ|†–•›•š”’’“›¦•ƒronhfdbbbbaacddfglnqrs„™™”Œ‰ŠŠŒ‘Ž‰ƒ‚‚€„‡‚‚‚‚€~zwusqooonmlkkklmknry„ŒŽŒŒŒ‹Œ‰‡‡ŠŽŽŒŠ‰ˆˆ††††††„ƒ…‹–•’Ž65554345555555444433443333445544555566666655445566756666545544443333211111110000//////0000//0000..//00//////00/..-....----..000135689XdWSQRTXirfabcaailrpq€ujffhq{zyuonprnNDJNORRROKJEA><:75331/2@C:37;9653321/2<=437AGRYYaemuy‚†…ˆ‘M  "%(+-% 7amhdgimmrxunprrqposvvwyzphcehmnopnmkkrxwoihjlmrtsdMHDCGKUcotvtnmmonkntrpljhimr{ƒ€{~|sK$!$$"$$$'*+).:AEHIKMLE<0&$$##&'0DYq…‹ˆ„‚whT4%0BLRYZ[^``_\WUSTY^afhiknpuquz|}€‚‚‚€„€n[ZZTQHDqœ¦¥£ž•—˜Š‹™—–“–š˜•šœ“‰‚jedabbabbbcceghkppsv‹š˜•ŠŠŠŒ†„ƒƒ€~€ƒ‚}zvsqnnmlllkkjjkjlnt~ˆŒŽŽŠŠ‰‹‹ˆ†‡Œ‘ŒŠˆ‡††…„„……‚‚‚ˆ“•””‘55665566645555444455443333345544555555556666666666666666665554443333332211001111/////0//00//0000///////////////..-....----..//123579:=?CGLSX_flqwurokighiot{†‡‡‡ˆ‰ˆ‡…}zwrolhdb^[[YUSOLJGC?;8621./4SdZW[cS&%(+2C]aXRQVYX`ricjjnlkmkllcgie`]cinquzzn^OMNMMNNMLKJIEB?=96522/.../6A?7>GNWaU]dlt{‚‚†‹‘•A !!!! !%(/4-Aemhehjknvwpiknrponoqttuvkcaaagjmnqnlifju{slljjlnpqmWFCCHNXeptxqnopqnjmrroljjhkt}‚|}}rC#$$"##"%%',.**4=BGHGHE;762100//,-7FE?CHNidU_glqx}~ƒ†‹‘•‰8  """""""" !#"'/8HZhhhkmmqwvligillmmopqqtr][[[_`ekopnoqmjditwqokkmlnnreIDBIQ[hqwunnpppmfirrpnkljiq{„€z|{k;$&%$%%%#%',12+$(6ADGFB7+#$%$$&*6Mcz‰‡„ƒ|reL.&7EKOSYY\^]][WUUUX\^abddccikjorx|€‚ƒ|rnkge`\\|ž¤¢œ™“‰Š‡z’“—˜••™¢Ÿž“†}kcbbbabaaccegilqrt{’š–’Š‰Š‘‹…„‚~‚ˆ†€€‚{xupnmllljjkkjkjjmqx‚‹Œ‹Œ‹‰‡ˆ‡‡…††’‹Š‹‰ˆ†…„„……ƒ‚…‹‘–•””’Œ9899998777665554444444333333444444446666556666666666666665664444433322221111111111//////--//0000110000//00/////..-....----.../013579;>@BFJOTY_eiptwsplggmwz}€ƒƒƒƒ…„‚‚~zvusplifca^[YVROMJHFC>:86330//5Y`WX\aC&),0A\]WVVYYY_sqijkmqljjlnjjdXYYR=:>BFRSWYYYVSOOKJGFDB@?<;9740/.----/8FKFFCSrcY`iisz}ƒ‡Œ”‹2! !!"####"$$%$$""(%5HMRZdjootxwiceddgggknpnojRQXW[\_cfhlpsutnjdkyyspllmlorpUDCJQ]muxrnoppnibhqspmmmmlu}„€|{yh7%''%%'(&'*./41*$%-:DC@3*$$$$'',9Pg|‡†„ƒyn_A)(@BDHLRV\agmrvvspljnuy{~€€€€|zyuroljgeb`^[ZWROMJHFC>:86330..1Ib\Y[`T((+0B[]WUUWWX\ovlnquyqoqrkike]\ZL==@HNTXY[[YVSQOMIDCBA>>=<:8520...//159CLIB2F^g]^djsy‚„†Œ•Š2! "$#$$$$$$$%%%$%5SlbNJWblqx{obaacaaccbgklnaAELRXY]bcdgmquuwsmiit~yqnkmmmprcHEJUarvxropppnb_gqupmkjhlr|ƒ~}}ze1&&'%%&(())&%)24,&%%.;>1&$%%('(-?Voˆ†„ƒ€€ynZ8(0?GJNQTY\][]][[[[]^^_`_^]^bcbcgjovy}}}€€{uttrsqop„”š–’šš”v{ƒ‰‹–™›š˜––—˜•ƒ|o`ababaabcegjmqst˜™•’ŠŠ‹‘“Š„ƒ‚€ƒˆ…€}zuspmmkkkkjkkijilns|‡‹ŒŠˆ†………„„‡‹‘ŒŒ‰‰ˆ‡‡…††……„„ˆ”•”””““ŽŒ<;::::::98887766444444333444333344556655555555666655666655444444333322221111000000////....//////00//0//////.....--....-------.012479:=?AEGKNRW]bjpuwvtmlovx{{~~|z|{{zxvqolkiecb_\YWTROMJEEA=977321-./9]aYY_a6&+0C[^YYYZ[[_kwqnmpy{xtwohef^WTG<>BHMRVZ\[ZWUSPLHDA@><<;:8764311/015;?;;JMFAB]g_`dkry€ƒ†Œ‘—ˆ/"&#$$%&##!$$%#*FhuqdLAJ`t|yg_b^_acccaaeikT?CJHOTWZaa`dintyzwtqloy{rmmmmkmmiPILWfvxuqqrqpm\^fptqmkkiks}ƒ{}xa,'('(('(*)'&'(-00'(()*//*)'&(%&0F\sƒˆˆƒ€€yjU1'5EKJLPRTXZ\\\\[\]]\]``_`]^^[Y\`elotwyz}~xrstuwutv…Œ‘“ ¬¬¢•z{ƒ‡Œ’•šž¡•”˜™’‚xdb``aabacehknrtt…——“ŒŒŠŒŽ‘’‰ƒƒƒ‚€€„ˆ…€€€{wvuqnmlkjjjkkkkjkowˆ‹‹Šˆ…„ƒƒƒ…ˆŒŽŒŒŠŠ‰ˆ†…††……ƒ…’••”””’’’ŽŒ=<;;;;::98887766544444333444333344556655555555666655666655444433333322221111000000////....//////00/////////.....--....-------.012479:=?ADEILOSY^dkrvwwrprsvwz{z{{zzzywuroljheba^[YUROMKHEB?;976411../2Ma[Y[bO'+/=X`\\\^^`agwuoqw‚‚wvxrkheUJOA:=@CKQUXYYYWVRPMJEC@>=;::::86532334<=;;=<>S]Zcijcddmsy~€ƒˆ——ƒ+('&%&&$#%$!(Onxi`c^K=<<<;;;99887765444444334433445555665555666666665566665544333333321111111111////....---.//00//00/////...........//..//.-....012468:=?ACEGJNPV[_fjntwurqrtvxyxxyyxxwutonlihfb_]ZWSPNLIFC?>:97422/.--.=^^YY`a;(-=Xe`_^_ciigs}ut|Š…ywwunj_QJPE:=@CGNQVVWVUURPMIFDA@><:::::7544579=<<;=6,3L^fginndbjqw{€„†”—…&!)')'&%$#"5qyrke_]XN?>QRVYYY\^bbeeeefWD=IKKMPPQRUWXYZZ\]_^]]]]\\]^ZWXY[[`ippquqghmosvxwz‹®³«žŽ…„‚‚ƒ‚‚Š—œœ›Ÿ¡œ–’˜‚~f`_``a`adhmprtwŠ˜•’ŒŒŒ’Žƒ€‚‚€„ˆ…€}{wsqnmlkkkkkkkllkmqx‚‰‹‹‹‡………ƒƒƒ‡‹ŽŒ‹‰ˆˆ‡†††……„‡”–•”””“‘‘‘Ž‹@@?===<<;;99887777555544334433445555665555666666665566665544333333321111111100////....---.////--00/////...........//..//.-.....02468:=?ACEILMNRV[`glpvxxtrtvvvwwuuvvutsnmkgfe`_]XUQNLJGDB?=;:8641/.---2Q`[Z^bR*,9Udcddelppir~z}‡Š|v|unf`UK=:9=;:;:7544456;>=<;;3/+*<<;:::88776666554455444444445566665566555566665554443333222222011110//////....---.--...........---............//.......02458:;>@BDFILMQUX]dhlquxtrttuuusssrtrroljhfdb_[YVSPLJHFCB?>:87641/-,,,-A_]YZ^b=)7Qffiknqutpt|‚ŠŠpmuslj_XH789:<=AEKPQPPQPONOKFB@?>=;;988543237=>><<81...+0Jchhqqogfmsx}ƒŠ“–(!'+('"3suokkmmloo[C8Rfz‡‰…‚}yo]:+9EIMOOOPOOPRRVWYZ[^^_`]YXXW][TSUYed_dehk]V^fmtz{‡Œ”¡ ž—“‹‹‹††ŠŠŠ‡‡‰›š˜ž˜–’ƒyda`aaadgiloswz’˜’‘ŽŒ‹‰‰Œ’‹„ƒ‚€€„Š…|ywtqpomkjkkjjjjjjkns}…‹Š‹Š‡…„„…ƒ†‹ŽŽŒŠ‰ŠŠ‡†††…ƒ…‹’”•••”’‘’‘ŒBAA@?@?>>;;:::88776666554455444444445566666666555566666644443333222222011110//////....------...........---............//....../123468;=@CEEGJLPSVZ]cglquuqqqpprrppqpmmljheca^ZXVTQMKHFEBA><:76420.,+++-3O`VUY`W,2Jaklmqvxvqqs€mtlnvieh^XWB8::99:=AHNPMKLNOONLHC?<;:8778854325:?@><<71//0/.+5Sjjjurhejr{€ƒ‰‹’v$('(&jwmhjprttxrZG<7GIESWYZZXE657:81:DB@BDHIQ]dgfghginnprvxwvyzuojhea_`a_dswvsqrrpiYNOU`ilijljjq||€m<%'()))*+*+**++*++/471&'()),18>@CEGINQTWY^chnnoommnnoommmkkjhgeba^[YTRQNKIHECA>>;86631/.,,++,.<\XST\_A)=Tflos{zvolgin[bq†Œxg[OSSB::;9977;CJMKHFJKMLKEB<::88887643237>BB@><5111100/,'@]flmpmjmvy~ƒˆŒŒ‘O#(&Qzqkjpuw{}`VG;3;B;?IUZO5)6;9=@=;;>BDGIQZchifabc`_eikoqstsrqqjd`_^`_chwxtturqodRMNT_fhjmkikr|„}€m@2.*++++****+,--,--08><<:988777655433344443344665566666655555555555544333322211111111000////....--,,,,--....------........--...........//0123578:;=?BEGHKORVXY]begikkkiijjiiihhfedb_^[WUSPNLJHFCA?=<886420,+++++,,2M_VQW[W,0=P^houztmh_WQJZr…Ž†€sfe`I=9;99778>CIHEEFHIHGC@<:85545653357=CDBB=94222010../,+Dcjmpqmpsv~€…ˆ‰w $"B{qhjovy|‚w\VVI9257876;8,05679:>>>EHLQTZ[aijfa^cf_Z\_abfoqmlnqjd`_^^]alvtqqrqoo`LKOU]cekmjjnu}†‚‚„rRIE<0,,+*++*+..,+--.2:92)-8>7=MbtƒŒ‰…„ƒ‚€ueJ/'0>FJKNQPPOONMNNORX]]^_^[WTSW_fhdcbejklnjcipv|‚‡‹ŠŠ‹‹“š¢£ š˜™š—•––ŽŠŽ‘’““•‡„‚vpnmooqrswxy…—–’ŒŒ“‰ƒ‚ƒ„ˆƒ~~{wurpmlljiihiiiijjmpxˆ‹‰Šˆ……„…‡‹“’ŽŒŠ‰ˆ‡…„„‰”••”“’’’‰MMIHGFDBCA@?=<;;8986556666554444444455555555556644444445544333332222222221//////......,,,,,,--,.---...------------..--..........0023668;<>@BEHHMORVXZ\`bdeddbcegfeefecba^[ZXURPOLJHHEAA>;;86421.++++++,-,<[YRU[bE&19EW`ksoha\XUIH`|‰…{uujN<9<:87658=@A@BCCCCDA?<96430//146;?BEECB=6323311210320(3Rgmmjt}wtvz‚€j7 4uujjnvw~u[TUVN>423565432788::99CJNPT\_`^ejhe`cghec_]YPP`omjlqkb]\]^\^ovqopqrqlRLLOSX\ellgimv€†……ˆ†ylc[SD4-+++++++,++++,.4;=033:>DZnŒ‰ˆˆ†„{uj][WLEADFHNONONLJD@ILTZZYX\\[WY^flooprsstqsrqsw{‚ƒ†‹Œ‹ŠˆˆŠŒ‘“—š™™”“–˜˜–“‘ŽŽŽ’”†…Š„}wustvwwxwz|Š•”’‹‹‹‹Ž‘ˆ‚‚‚‚€~~„ˆ~|ywtqpnlljjjiiihhkklqyƒŠ‰ˆŠ‡………‡‰Œ‘•’‹Šˆ†„ƒ…‰Ž”•””“’‘’Ž‹NNLKIHEDECCA?=<<:996556666553333444455555555554444444445533333332222222221//////......,,,,,,--,.--,---------------..--..........00234779:<>?ACGJMQSUUVX\\^]]^_acba``_`^]ZWVTROOKIGFCA?><;98521.,++++++-,-1G_WQZd_0(0:MX[ced^XYZRJNlztpoorbQA<54421/14554678;<><:;964332158?BEFFEB>9311222255555450/=Zinny}{ueRT8'/ltihlow~|gWWZ[XRA3468<3/1666:>?BJMOTWW[__bfgfedeehea[YRE@Qfjjkpi`[[\\\eyzppqsqpbJHKNPU\eolikp{†ˆ‰Š‰‰…ypd[I6,+++*++,++++*,/5>735=BQizˆŽ‰ˆˆ†ƒ}tj\]affeWJFGLLJKGA.*>BFVXXY\`_]^emrsstvyyxxwvxz}‚ƒ†ˆŒŒ‹‹‰‰ˆ‰Œ“•––’‘’“““‹’–•”“”‘Œ†{wyzzz||‹““ŽŠŠŠŠŒ‘…‚‚‚‚€~~€‚ˆ~{ywrqpnlkiiiiiihhjklq{…‹Œ‰‰‰†…†‡‹Œ‘““’‹Šˆ†„ƒ†Ž”••”“’‘“‘Ž‹ONNMKJHFEDCB@?>=;;977677555555444444444444444444333334544333333322222211110///....----,,,,,,,,,,--,---------------------.....-.-001445789:;=?AFGKLOPQQSVWXXXXZ[]]\]\\\ZXURRPOLJJGECAA?;9764320,,,,**,,,,,-7U[SU]eO&,4@MSUYXTRVYXUR]lmkfhibXK8-.-+*(('''''*-1489=?9#/3.GXZ\_cdcfkrvwyyyyz|~}|}€ƒ…††ŠŒŽŒŽ‘‘’’‘‘‘ŽŒŒ‘‘Œ‹Š‹Š„€€€€€ƒ„Œ’ŽŒŠ‰‰‰ŒŽ„‚‚~~‚ˆ~zxvrppnlkiihfhiiiikms}‡‹Œ‰‰ˆ†…†‰‹’”’‘Ž‹Šˆ‡…‡ˆ’–•“’’‘‘ŽŽ‘Ž‹PONNMKJHGEDB@>>=<;987677555555444444444444444444333334544333333322222211110///....----,,,,,,,,,,,,,,,,,,----------------.....-..//03345789:;=@DEIJLNMOOPQRRRUWYZYXYXXWVTQONMLJIHFDB@>;977531/.,,,,++,,,,,,/C_WSXch>*-3@LPLLLMNRVYVTgkkhcbb^[J+('&%##!!""#$&)09ENQRPOONMJIIHGGFCA<7100001567666445554105I^owyye0*izmnmnrxw\XZ[\]a__G65531-.04FJILelSSSRSW]]acb_\\\]_^ZYQNC:7=O`g`ij`\\\[ax~urpsqqlLBEIMQW^djnuy†‹’’““ŽŒˆ€vk[D0-.----,,++++,/4;;=@Nbvˆ’ŒŠˆˆ‡‚{pe_DFciea^Scmg_VL=@;9<@KY_acefhnrvyz||||}~}~€‚†††‰Ž““•––———————•••“‘Œ‹ŒŒŒŽŽŒŒ‰‡…„ƒ„…„ƒ„„„‚‚„„ƒ‡Ž‹ŠŠ‰‰‰ŒŒ‚€€€€~~‚‡~|zwurppnlkiihfffhhijovˆŒ‰‰‡…†…ŠŒ‘“”’ŽŒ‹ŠŠˆ‡ˆ”–”“‘‘‘‘ŽŽ’Ž‹TSRPNNMKIGFDCB>==;9:8677466655444444444444444443333345442222331133222211000///..--,,,,,,,,,,,+++++++,,--------------------........013355778:=@DDFHIKMMMMNNOOPSTSTTTSSRROMMKIIGGFD@?<<:75421.-,,,--,,,,,,,-.5TaVX_fa0+/6ALJFGHGHMVUSXiopcZ\daWC'%%#!!""##$')/:KUTRPOOONJJIIGDDCA=7300001446777;895653560.07?EB5,jxnnrqpzz]XZ\_aa`aO@74430/--26>ABJNQWXUTUZ]__ZZ\[]]YTQMGA<66=O\^V`lb]]]\lƒ~uqqrsp_DBFHLOV_cgr|…‰Ž‘’““”“ŽŒ†tgV=//......++,+-.07<@ERj€ŽŒ‹ŠŠ‰ƒ|pdcP=^gbeehhhhlolbODKQQYafgknrsx{€~~~€~|}~ƒ„‡”˜šž   ¡¢£¡£¢ ™—”ŒŒŒŒŒ‹‡†‚‚ƒƒƒƒƒ‚ƒ……‡ˆˆ†……‰Œ‹‹Š‰ˆˆˆŒŽ‰‚~~~~~~}€†~{yvtqonllkihgfeeggijpw€ˆŒŒˆ‰‡…„‡‹Ž“–”’‹‰ŠŠŠŠ‹‘––”’“‘Ž‹“Ž‹UTSSPOOMKIFFCB@>=;:8977765665544444444443344443233333332222211111122221100/////.--,,,,,,,,++++++****++++,,++,,------------......//01115577:<<>ACDFFHJJLMLNNNNPQPQPPONLLJJHFFEDCB@?<::86420/.--,+----,,,,,,..?a`WX_gO(*.6BMJGIEDHKMTW^mqf_`de^T9!$"! """$',1?P[YXYVRUQNJGFFDA@?<9400001244668<><9557754410/-.+1evmkoqpw{cY[^^dffP>8;84422101347:BKPX[YZYY]]]]ZZ[\\YTPMI=8656=KSTQYhe[]]awƒ}vtutsqR?BFGKOV_houˆŽ‘”–—••“’ŽŒŒ‡}sbM5---....,,,++-,27K\dtˆ’‘ŽŒ‹‹ˆ‚ylcbcZ^eccefiiggjooppokdgmrsuuyz€‚‚„„‚€€€~|{zxy{|}…œ£¦ª««««¬­®°­ªª©£Ÿš–‹‹‹‹‰‡„„€€€€€€‚ƒ‡Š‰†„…‰‹Œ‹‹‰‰‰‰‰‹ˆƒ€~~|~||}€ƒ}{yvtqomllkihffefggijqz„Œ‹ŠŠ…„„‡Œ‘••’‘ŽŒŠ‰‰Š‰‹‘”•““”‘Ž‹’ŠXWUSRRQOMKHIEC@>=;;9977765554444333333333322333344222211111100110120110011//...---,,++++++******))**++**++*+++,,,,--,,----..-.////01333356799;=?ACEEFHIIKKLMNNMMMKKJHGFDDBCCB@A@><;967410/.---,+----,,-,++--4PeYWX`g=#,/6CKJFDCBEFHQWerjihb]X\S2($!! !"%)/=S^^``\[[TQKFBAA?>778821001233478;@A=8875544320-,-6lwljonnwzh\\[]`aW:(.7;9520036435:?GPUXWTVYY^^]\Z[]^^ZTRQG;7524:FMQSVdf]^[f€ƒzwvtstvkK=BEJQU\ky~ˆ’•–˜™—•’‘Œ‹ƒym]C2.,//00...++-..4CawŽ”’ŽŒŒŒ…€ug`_aabacddggeehnrrvy~}||~~†††ˆ‡ƒ€~|{xwtrsttt~¦¬¯±´´´¶·º»»º´³²®©¥•Ž‹ˆ†‚€~zzy{{{|}ˆŠˆ……‡‰Š‹ŠŠ‰ŠŠ‰ŠŒˆƒ€€€~~}}~‚~zxurqnmkjiihffffggijs|†ŒŒ‰†„„†Œ“••’ŒŠ‰ˆˆ‰‰‘–•’’’‘‘Œ‹Ž‘ŒŠYXWWUTSPNMIHECA?><<:877765554444333322222211222233222211111100110120000000//...---,,++**********))****))+++*++,,,,--,,--....-.//001122223668::<=??ACDEGGIIIJJJLLIGFFDCDA@A@@>=><<9864520//..--,,----,,-,+,,-/;ZaZUYed3&,/9CKHEECAAAHPZkuwp^PU`bJ( !!!!#%+6N`bedb_YOHA8500277569842111233567:>=:8875544533,-Bmumjmqqvyga\`^aX:))-07:952028755:?HORX[WOOVZaa^\Z[^]^ZXVWNB<655149;;<862-*+022C_y‹““’ŽŒ‹ŠŠ…|ob_baabbdeeeggjkprtx}€„ƒƒ……„……ˆˆˆ‡‡ƒ€zxwttpqonnqu«±²·¹¹¹»¼¾½½¼»º¹·°¬¡–”•ˆ…„ƒ‚~|ywvuvvvwy{~…ˆ„„ˆ‰ŠŠ‰ŠŠ‰Š‹‡„‚€€€€}}~|yvsrnmlkkjihffhhhhimt}‡Œ‹Šˆƒƒ…ˆ””’‘Ž‹Šˆˆˆ‹’”’‘ŽŽ‹Œ‹Ž‘‹[[ZXXXTQONLIGDB@?=<;88875555323333332222221122000111111111//00001110//0/-/..--.---+++*))))**)((((()()*+++++++,,,,,,,----..--..0000000012445788:<<=>ABDDEEFGGGGFEGDCCCAAA?===:<=;9864332///..------,,---------3Hg`WU\g_)&&/5@HGFDA@@BIT_nro`PT]^W;!!""$'.D]ccdc\ND7,(#!#'(+.15:<943347679;==@:897565445307b}xmgkqsuw`Z\_abS-&(-/36477418;899>FOTUWWTPPRW^___\\^\[ZZ\\XK?<89DNTUVV[daW_v€}yvtprrjjwxXEIMVcw‚Š’”••–—™”‰„‡‹‡|qcSEGLPPPMHC@81214Ed|”“Œ‹‹‹ˆ†xk`_cbcaabcfghjossu{ƒ…†‡ˆ‰‰ˆˆ††ˆ†…„ƒ€~yvspohfimpmn˜­²³¸¼¼½½¾¾¾½¼»»»»¶°§žž–‹€€€{yxvsrorpprrvw„€{~…‹‹Š‰ŠŠŠ‹Š†„ƒ€€€~~€ƒ{xwsplllljiiihfiihhilu€‰Œ‹ŒŠ†„„†Š’•’Ž‹‹‰‡ˆŠŒ’’“‘ŽŽŽŒ‹‹‹Ž‹‰[[[[YYVTRNMJGEB@@>=<998755553333333322222211220001111111110000000000100//..-,,,++++++)))))**)((((()(')*****+++++,,,,----..--..00110000124446889:=>=>?ABBCDEEDDDCCB@>>===<<;;;:::77522221///.----..--.-...-,,--3ShZWY^iQ#%*,3>FHDCB@;>>GMVXTSSSPQSQUZ^^]ZZYYYX^d`OCB>?FRUUUVXa`[i€€|wususj`bkuxXFKXo}†‘’‘‘’’’‡€~zz‰ŒŠƒxsid_bfffb]VQJA846Ml„‘’’‹Š‰‰‡|rdb`bcbb`abdglptw{|…‡ˆ‰ŠŠŠŠˆˆ†…‚~}}}|xsnjff_hmpkoŸ¯´¸»¾¾¾½¾¾½¾½¼»»»ºµ¬¦‰zz}~zxvtromkhhjjljoy}v{‚‹Š‰ŠŠŠ‹Š†…„ƒ~~€„‚zwupnkjiijiiihfhhhhjpw‰ŒŠŒŠ†„„ˆŒ”—“‘ŒŠ‰ˆˆ‰’“’’’’“—˜“ŽŽŒŒ‹‹‹Ž‹‰[[\\[YWVSQNLJHFD@>=<:9765542223322221102222222111100000011000000//0/....---,,,+*++**))(())))(())((()()))*****++++,,,----------/1..0011223333667799<>>@@AAAAAA@??=>><;;::::999866555221110000----.....--------./8Wc\XYcd@!%)+1;FIB@@>=@EUjsrmkihieD!""#'.?^iedbL3*+)%#!#""#&(+/79666679=@BCFHGB?;8766543I~yrmilqsyud[^]aaD$)++-.047:=9>JF<9=ELPTXURRRNMONPSV[ZXZZ[VTZa]NHFCCDLRUUVV_`ar|xwvtvtd]`glssVO^sƒŽ‘‘ŽŒŒˆ…}qotvyŒŽ‹†€}zxy{}|zwrkf\VKFK[q‡”’Œˆ‰‡†…|xkaadbaccehiknquy}}‚…‡ŠŠŠ‰Šˆ‡ˆ†…ƒ~zvvwvvusmhb\_dacgm‚¡«³¸»½½½¾½¶¾¿¾½½½½¼´ª …styyyz{zxurpkgffijhdemnw{ww|‚‡‹ˆˆŠ‹‰†‡„ƒ„‚€€€„‚{wtrpllkkjjiighhhiikq|„ŠŠŠ…„ƒˆ•–”’ŽŒŠ‰ˆ‹‹‘“”“’““˜¡¡›’Ž‹‹‹ŒŒ‘ŒŠ^^\\[YWVTROLJHEECA=<:976654222331111111111111100//000000110/////./..--..,,,,--**++**))((((((''(('''()))))))******+++**,-----..--//001122445566779:;<>>>@?????>>><;:9998888778866553221110000/-..-,---,-------./0?_cXVYba.#''+.7CGFBB>;>?A>=FD?=@FMNLMLKNQMLNOOQRSVWW[\VTUWUNKKGEEHNUUVV\dj}|vusuuuua]bbekso_i{‰‹ˆ††‡„€xkhmtrx…‹ŒŠ‡„„†ŠŽŒ‹‡{tlf\\_m{‹’‘Š‡‡……„}tf]^_bdcfgklquw{„…‡ŠŒŒ‹‹Š‹Š†„€…wnjjkljd[WTQRUSW`‚Ÿ¡ª¶³µ»¸¸¼¾¼½½¿½½½½¼¹¦}imqtuvvwvuronkidipopkfltsjekpuvˆˆ‹‹‡††…………ƒ‚‚€ƒ‚}wurqllkkjiiighiiiikq{†‹Œ‹‹Š…„‰“–”“‘‹ŠŠ‰‘’““’‘‘“œ£¤œŒŠŒ‹ŒŽŽŒŠ]]\\[YXWTRPNLIGDB?<;:987643322111100111100000000////....//..........----,,,,--++**)((()(&'((&&''&&'((((())))()****++++,,,,--,.--0000112222223456778;<=====<<<<<;::::9988776888655533210000000///..--,,.---...---1Fd^UV]hV%&'&(+3?IGA??<:C^qjjbapsqM##'3NmpqnP--,)$""!!"#""$),0456678:=@DHJHGA>=875110^€xtqtuz|~gY_]bgU&,)-4=GEHLOIGDEIEFEDEKLIFCEJKIIKNRTQQRUYY\\URTQOPOKHFELSVWUYds{wrsvvvwr_[__agkqtt‚ŒŒŒˆ„ƒ‚ƒ…yecholm{…‹‹‹‹Ž“–•–“ˆƒ|wolqx„Ž“‹‰†„„„{o`Z_`^bgjnqsv|}€†ˆ‰‰ŠŒŽ‹‹‰ˆ‡‡ƒ~wz}|{sfZZXTPMWPGQ^jl‰£¨­µ¹»²Œ˜œ¬¾¾¿¾¾½½¼½·¢rYcilppqttttrqpnkgjonoppmqvm\[`emzˆŠ‹ˆ…………………‚‚‚‚„ƒ~xurolkjlhhiiihhhhjou}‡Œ‹Š‹ˆ„‚‡”•’Ž‹‹Ž“””’‘’™£¢—ŽŒ‹Š‹ŒŠ^^]][YYWVSQOLJHDB?<;:987643322221100////../000//....//////------/.--,,,,,,,,,,****((((('&'(('&''&&&'((((((()**))**++++,,,,,-,,.-..00112222223456779:;<<===<<;;97777777776654446533332111000000////..//..--...--,.3PgYRXbeF#%$#$(.;EIF@?>>Hcpncekhkf8!$(1PopokG.,)&#" !!"#"!#(+-14679:<>?AEIJIA=>>:50/[yxsqtx|{~pV\^`h^,),,0:CDEOQSXSMQOCINMGFGEDCCDIIHGMNTUTUVXZ^_YTSQOOQOLHDHQVWX[fuztrsstuwr_[\``ago|‹ŒŒŠ‡„ƒ„€wdbdljbq‰ŒŽŒ’–™š™—”Ž‹‡ƒ}…Š‘Šˆ…„„ƒ{m^[^^^einrwz|ƒ‡ŠŒŒŒŒŒŒŠ‰ˆ…‚{ytquy|vvld[T^aFDl’š™§²·¸¸¹¾¦w™Œ‘»¾¾¿¾¾¾½²]RW^cgkloqtssrqqroloromkkloooaORV`t†ˆ‹ˆ……„………„‚‚‚ƒ„ƒ€}xtpmlkihhhiiihhhkpw€‰Œ‹Œ‹ˆ„ƒ‹’–“’ŽŒ‹ŒŒ“”•’‘‘’™ Ÿ—Œ‹‹‹ŒŠ^^]][ZYWTSQONJHEC@<;:976644310110000//.....0////..........----,,--++,-,,+++***))))('')''('''&&&&&&''''''((((()))(***,,++,,,,---.111122113333335556798:::::8:::888777666666544443333222221111000////.//.-..--.----/6[bRQWab9"##""',6BHGCBB?PgbXZ]gknZ("%-Ffmjf@+,'&#! !$%##"$(+-/336:<==>AFIKC??A<25`wrlstxz||u[Z]]dg:++,-.6=AGOTXfbWICDMLIGEB@ABBCGIGHJNSVWWYZZ]a]YUSRRSNMHDEMUXZ[fsvqswxvuvq`[^]`dgsˆ‘‰†„ƒƒ„ƒtbccii^j{‡ŒŽŽ’•™ššš—–’‘Œ‰‡†ŠŒ‹Šˆ‡‡……whZTX^bhkos{„†‰ŠŽŽŒ‹Œ‹Š‡‡ƒ‚~}zzzug\armnge^_qsIZ™¬±´¹»»½¾¼»µ¬¸°³¼¾¾¾¿¾º§uHEORX\`dkoppprrqsvvrrxtlgginrriRAMWoƒ‡‹ˆ…ƒ„„„„ƒƒ‚‚„ƒ†‚~zwrnllihhhhhhhhimqz„‹‹ŠŒ‹ˆ„…Ž”•“’‘‘ŽŒ‹ŒŽ•—•‘‘“› œ–ŽŠŠŠŒŒŒŠ^]]][ZYWUTQNLIGEDB=<:9766433211100..//.....0//..,,........----,,,,+++,++***)))))(('&'(''('&&&&&&&&'''''''''((((()***++++,,,,--./111122113333335556778899999888777666666666444443333222221111000/////00/...--.-,,+--=b\TUZ_W-!"!#"(2BJJHCBAX_OLOW_hkC#)9YhibA,*&$" !$$#!!#$'+-0369<==>ADHIGCA?8Lpvmmlpvvyzs_Y]\^eI)-,11059@HOWakhWJGJNNJEA@>>??BDFHILMOSVXYXZ[]^\XXWWTONJEDGQWYZervsvzyxuwr_XY\_dkxƒŽ‘Œ‰†ƒƒ‚„‚~qbacfg]h{‡ŽŽ’“˜™šš˜–•’‹ŒŒ‰‰‡‡†„~vh^WUajghov}„‰‹ŽŒŒ‹‰†„„}}ytonj]SIennpuhU\jLo¦´¸»½¿¿¾»´³·º»½¾¾¿¿½»°—dFKSQOQUZ^bgjmprqrruyyrswqppoqwvshKEYp„ˆ‹ƒ‚„ƒ„ƒƒƒƒ„…‡‚€|yupnlkhhhhhhhhimr{…‹Š‰ŒŠ‡„‰’••’‘‘‘ŒŒŒŽ‘––“‘‘’˜œ™Œ‹‹ŒŒŽŒŽŽŒŒŠ^^^^[ZZXVSQOMJJGEB@>;96664331111//.-----....--..,,--..---,,,-,,,++++++++***)**))))(''&&&&&%%%%%%%%$%'&''(('&''))((******+,,,.../00112111223334446655777787777777666666777766664333333322222211100000//..----,,,,,,+-GdWRU[cV,!"! "'0?ABAB>;H_tqljiputvvq`Y\]^f_)+/0325888?HXil_UMMPSUSME==<<;;;;=BFLNNMPPUZZYY[\YUQOOLKJHDCACKRW`gghjmompmdcb\ZdsŽ’’’Ž‹‹‰ˆ‹ŒŒŠ‰ƒsb`bhru}Œ‘Ž‘’“”“‘Œˆ„„‡‘’‘‘‘‹Š‰ˆ…{yxsrx„†‰‹ŽŽ‹‡ƒ€||zywsqpomppkjknpqmiejc^c_v¥µº¼¾¼º¹µ£¬­¸»¼¹±“eJLSRQQQPPOOOMMQTZ^agipqrtssusv{|tq}ƒˆ‰py€ƒ†}y||}~‚ƒƒ…†ˆ…„‚€}{wrpnjihfgggkoxˆŠ‰ˆˆˆˆŒ““Ž“––‘Ž—œ—ˆˆ‹Š‹Œ‰„€ŽŽŽ‹\\\\ZYXVUTQONKIFC@?=;:9764322000//------,,,,,,,,,,,,,,,,,,++***())**)()))))(((((((&&&&%%$$$$$$$$$$#$&&&&&&&&''(())))****++,,-./000112222233333565566666666666666777666667766554333333333221110///////../----,,++,,))'):d`USV_id4!"##$%+3>MSPSgnZNNSatX#(>afYMB*""""!"$%&'%&%%(0/*))**+++,.,NrqkhgkooopviXea\ZZYUX4..366679;78@?B;19EORPRUSQJD=999;=BFLNPRTTUWYZ[\\YRLKIIGHEDCAAFKT]ddehklkmj_`hd`hw‰’’‘ŽŠŠ‰ŠŒŒ‹ŒŠ†ub_dkv{‡‘ŽŽŽ‘‘Ž‹ˆ„~~ƒ‹‘’‘ŽŽ‹Š‹Šˆ…‚€~}€„‡‰‹ŒŽŽŽ‡~xuqmmrsppqonmlklonnncY`ed`ZU`ž´¹»º¸¸¸´®°©²º¸¬QLMRRRPOQPPNMMNLNQUY^chlnrqprvx†‡…zŠ‘’‰y…ƒzx{{{|}}€‚ƒ…†‡†…„€}{zwsomjhggghkpzƒˆŠˆˆˆ‡‰”“ŽŽ’••’ŽŽ“›œš‘‹‹‹‹‹Œ†}ŒŒŠYYYYYXWUSRPNLKIFFD?==:8764332000/.---,,,,,,,--,,--++,,++******((**))))))))(((((('&$&%%%%%%%%$$$$$$"#%%%%&&''&&(())))***+,,,--//00000123323444456666666555766666676666677666655543333333322221100//../...------++,++('''BkbTRW_hc8!#$$$()*6FQTXliYPTXhsA)3FOJ:88:>AELNQTWXVVXZ\]\XPMJLJFEGDEEDAFMW`abehiikfZ]ghjp}Ž‘‰‰‰‰ŒŒŠˆŠ‹…wd_fpz‘“’ŒŒ‹ŽŽ‹‰…‚}„Š’”’ŒŒŒŒ‹Šˆ„ƒ‚„ˆŠŒŽŽŠ„{voga\_chklpqroonllnmlU@MX_[H@YŒ°¶¹¶¯µ·«¯±­¶¶ª€NOQOQQSRRSQPNNLOONNSUX\bfimoqt|…Ž–•’‡’˜šˆ„ƒvvyzzzz{~€‚ƒ†‡…„ƒ‚€~zuqolkjihjls|…ˆŠ††‰ŠŒ“’Ž’••’“™žœ–ŽŠŠŠ‹‹‹Š„}|Œ‹‹‰YYYYYXWUSRPNLKIFFDA?<<8764332000.----,++++****++,,++**))))))))(())))(()(((''''&&&&&%%%%%%%%%$$$$$$#$%%%%&&''''(())))***+,,..-//00000122233454456666666556666666665666677666655543333333322221100//......----,,++,+++(%#)HmaYWW_ggC!$%&%%$'/;JP\teWUW_rj2,,()%# !! !!#$')./,,(&+)('%$""$%WtmollnokmrrF>hle\XZPXW0+159:<<;>;55463.39DIHHFFEDBA>:88:3Np¥´¶¯‹˜°—¦²°± ˜oMOT]WSTXWURQQPOOOOOPQUX[`hq}ˆ“š¡¢ž™™—•ŽŠ…wuyzzzz{|~€……ƒ‚ƒ‚~~}xvtpnkihilt€‡Šˆ……†‰”•’Ž“•”‘Ž•žŸš‘‰ŠŒŒˆ‚w}‹ŽŒŠˆWWXXWWUTTSPNMKHEEA?=<;98532220//-,---,+*++++++++****)))))*))))))(((('''((('''&&&%%%%$$$$$$%%$$$$$$%%%%%%&&''(((((()))*++,,,--/////00122244455566666666666555555556566556666666433333332221/111//00......,,,,++,,+,*,,(%%*KmbXVW]diK##$$$$"! &2?FkwcXWUevP#''%#"! !!! !##%.9@<:/(++)&%#"#!=spklonmposk:+`kid\[UK[A)/49::=@@=86542-149@DDA=?ABAB?<:98;=BEJLQVVTTVXYZVTOJKLKHHHHJKJHDDIMV^ceefheWXdmt€‘ŽŒŠ‰‰Š‹ŽŽ‰‡‡Š†}h_jvŒ’ŽŠ‰‡…ƒƒ‚…ŠŒ‡„|‚ˆ”‘Š‰‰ˆ‹‹‹ŠŒŠ‰ŠŠŽŽŽŽŽŒ‰†zm[N\a\]^^ZZXY`ejmmmeWG=GJKNB?M^•¯²”R‡¤}š³·ª…v\MNRXQTYa]TUVVTQPRRPNOPR]n~œ¢£¢ ›Ÿ£Ÿšš˜’Œ‰Šˆ‡ƒ~xvxxzz{{z|~…„€~}{wutrollknyˆ‹ˆ†…‡Š‘””ŽŽŒŒŽ‘••‘Ž’šž•‹ŠŠ‹Œ†}rx†‹‰†WWWWUUTTRQPNLJHEDA?=<;98652220//-,,--+*+************))))))))))))(''''''(((((&&&&%%%%$$$$$$$$$$$$$$%%%%%%&&''(((((()))*++,,,--///11001222444555666666666655555555565665566666664333333322210011////....--,,,,+++++++,,)&#(/Mm`VUX^fl[&"$%#" !)3HlqbWVZhp2&#$#!! !!! !$"(.3782/++-*'%#"#-hnkjjmpllta.5_kigc`bJ[Q++<>;:=?D>7559830258<@=:7:>>>?>=:97:<:87543320//-,++++**********))))))))((((((((''''''''''''&&&%%%$$$###""$#$$$$$$%%%%&'''''((((()))**++,--.-.000022223433555666666666665555556666666666666666443322111111000/0.//.-..-,++++++)))*,--+)%$'-MobUTW]dl_4!$#"! !%*Epn`YY`rU#%"""! "$(-/2761,-./,)%$""XvmggllmukO3Cahjjhd^L\[5.:@?@@BB>835:@B<4379976567::<><<::78;>CIOQQQONOQTUSQLLKKLLKKMNNNLGDABFOT\acec\[iv‚‰‹ŽŒŠ‡…††ˆŠ‰†ˆˆˆˆ„~jbp{‡‹ˆ„‚€|wqosy|€ƒ†‡„‡‰Œ‹ˆ‡ˆ‡ˆŠŠŠŠŠ‹’’Œˆˆ…‚€xsssrqigjhiicWPQW[ZVTUUOCCTSVY]get¤€™‰…£¯¯¦|WJHIMLLRUQOPPRUWWUQRTOTfˆ¦²¹¼º¸°©¥››ž  ¡Ÿœ™™•‡{v{zzz{{{zy|~‚}}~~}}||ywrqppu}„Š‰…ƒ‡ŠŽ•”’Ž’“Ž‘–š›˜“‰ŠˆŠŒŒ†{rnw…‰Šˆ…UUTTSSSPPNLKIGEDBA@=<;7574331///-,++++**********))))))))((((((((''''''''''&&%%%%%%$$$###""##$$$$$$%%%%&'''''((((())**+++,-....00002222344445666666666666555555666666666666664444332211111100///.//.-..,,++++++*)()+++*(&(+./PndXTWZ^ffC""  !Gwm^VZeq:!##"! #&-2111,+,-12.*'$#FEA955564332599;;<<;:99<;:866532100/-,,+++*)****))))((((((((((((((((''''''&&%%%%%%$$$$$%$#########$$$$%%$%%&'''''()))))**++,,-.-./0101223345554555557766666656665566666655555555444433221111100/..--/.-,--,+++****)((**+-,)'+,.0/HkcVSTX]_fQ/!!#Uzg][^j`%"# "! !! #(*,*++),,-461,(%2lnffjijp]=;:<@DJMPSVURPONNMLKKKLLMNNPQPOOMHFCA@@CHRZaa`fxƒŠ‹‰‰†„„‚„‚|y{„‡…„€{jes‚ŽŒ‡ƒ‚€~vl`_dkosw|‚…‡Š‰‰ŠŠ‰‰‡††ˆ‰‰Š‹ŒŠŒŒŒŒŠŒŒ‡„}|zwtqqqpqqn\9489>=?DHKMPRTSSQPNMKKKKKLLNPPQTSPOLIFCB@>?@GQU[bm|†ŠŠ‰‡„ƒ‚‚‚}vqu‡…„€{jiv…Šƒ‚€~}xma]`eimqv}„„†‰‰‡‡ˆ‡††††††‡‰Š‹‰ˆ††‡ˆˆ‡‰ˆ„~|{zwvtpqqpqnM)5688@<84364107AFDB:430.013454679<>AABEFHKNOOQSRPNLJIIKKMMOPSSUSPNLIFDD@AA>AEKTar„‰‰‰ˆ†„€€€znkv€ˆˆ„€yjjw…Œ‹‡‚€}zvkd_^_ehmsy€…††ˆˆ‡‡„„‡…………………‡†ƒ|}~~‚‚„††~}zywvuutuvkR/.:<>CGKGGHOPQXXV\`aeidm‹—“œ§©¦ mAFJILKJIJIJJJJJJIKJSl–®·¼½¾¾¾¾¾¾¾¾¹µ®¥£¡š™™˜˜££Ÿ•„z~}{{{{yzxxz~sptuww|{|~~~}}{yv{ƒ‰‰…‚‡Œ‘•”‘Ž‹ŠŠ‹Œ’‘‘“–™™•‘Š‰ˆ‰Š‹Œˆuoon{…ˆˆ‡†OONNMMLLLJGFDB@@@=;::76643220///-,++,,****))))((((((((((((((''''((''&&&&%%%%%%$$$$$$##########$$$$%%%%%''''''()))**++,,,,-..001132223455555555555566666666665555555555555544333333111100/.....--,,,+++**))))))(()))-/21/-.147:=>>UjeXSRVX_ihN%  %((;qr]_akb "!!!$(*,,.---./0110,UrfcdhkpnjhgefdaabcdfY51333459AGC94451104>EFA:51/.013566779<>BBCEFJJLLLNOQSOMLIIJJLMPRTTUSOMJHFDCAAA@>?BO[pƒŠŠˆ…‚€~zqhlt‚‡‡†ymlx…‰‰„|{|vkb_\[_`fmu~ˆ‡„††‡‡„„…„……„„……ƒ€{vqpqou|~‚…‚~|{|{{{yxy{wgJ@AFP[^XKMX\]^\Zadefkkc{”‹›§§¥™cCHJIJKJIHHIIJJHHIIOfª·º¾¾½¾¾¾¾¾¾¾¾º¶«§£¡›™™š–—Ÿ¢¢œŽ€{{|{{{zxxy{}nlnqttxy{|}|||{zy{ƒŠˆ„ƒŠŽ••“ŽŽŽŠ‰Š‹Œ‘’“——˜–‘ŒŠ‰‡Š‰Š‹Š„}uoon}†‰‰‡†LLLKKKIIJGDBCA@>>;:87755421000/...,+,,****))))(((((((((())((''''''''&&'&&&&%%%%%$$$$########$$$$$$%%%&''((''()*****++,,---//0012433334455555665555555555666655555555555444333333221110///..--,--**++**)(((''''(((()-1442/236:Idm\SQQUZfma9"%',,Htjc``pF! "!%&+-/20..000221LrhbcejppigdddddcefdjbE1255348:DX]93332103:EKF;53.,/13567879;>BCDFFIGJJKLNORRRNKIHHJJMOSTURMKIGDCB@@@A@@?FSiz„‡…‚~}|ujcivƒŠˆ†‚yomy…ˆ†‚~||zufefed^^ait€ˆ‡………††„„„„„…ƒ…ƒ‚€zvmbaees{|}‚€|}€‚€~~yphdehiidW[`a``bcfgjkohbl‹–Ÿ§©¤SCFKKIJIHFGGGHJGGHI]„£²¹¼½¾¾¾¾¾¾¾¾¾¾½¸³­¥Ÿ›š˜–•‘“—™™”…zyzz{zyxxyy~pjllqrtvy|~|z|||z}ƒˆ‡„„Š–”ŽŠ‰Š‹Ž’’”˜š™˜“Œ‹Š‰ˆ‰Œ‹‹‹‡|tnon|ˆ‰‰ˆ‡KKKIJJHHGHFDBA?=<::877663210000.--,+,,****))))(((((((((())((''''''''&&'&&&&%%%%%$$##########$$$$$$%%%&''(())))****++,,,---/00112433334455555665555555555666655555555555444333333221110///.---,,+**))**)(((''''''(()-167524:<>BFJBC@?VkbWQPS[_gn^4$+,-.Oxj__`l4 "$&&)-1232244441IsmfffinphedddddeegddYC7534437;?CLN723322128CHF=830../0156669;<@BDFFGFGHHIMPPQNLKIHHHHJNPSTPMKIGDCA?@@CDCAAHYjt|€€~}|~|wnbbkw„Š‰ˆ„{omy„‡ƒ}z||yulhjhe`^^iv…‹Šˆ„„„„„„„„„„†…ƒ€}woeWNQXmvy{}€~ƒ†‡†ˆˆ‡}wqssqnjdW_a`__`eeghlogcg{•ž¥¥£‡JEKJIHFHGGGGGGGIHFLnš¯¸¼¿¾¾¾¾¾¾¾¾¾¾¾½»´¬¦¡™•‘‰…„‹…{yzyyzzyyz{€qjggnqpsvxz|z{{}{~…ˆ‡ƒ…’•’ŒŠŠ‹Œ”—˜š™—’ŒŠ‰‰ˆ‰‹‹‹‹Š†ytnno}‡‰‰‰ˆIIIIHHGEDDCBB@?><;98866543000//-.--,,+*)))**))((((((''))))))))))((''''&&%%%%%%%%$$$$$$$$$$%%%%$$%%&&%&'())**))*,++,,....///011223344446456666666666666664455545564444433422222221100//...-,,++*)))))''&&''(('&%%&')+16:976>?BHLMJIC?ABCEEEEEDGGHHIIHHFFGGGJOQROLJHFEDC@@@@CEEEEGHP[dmty{wqfb^_ky†‹‰‹…{oo{€€|{{{{}}ypoljiffq‰…‚€€‚ƒƒƒ…„‚}ztgZTKHPctvvy{}~‚„‡‹ŽŽŽ‹‰wponj[_`a``cacefgc^efhnq†šŸF@FGHGHEEEEEFEEDFOtž²º½¾¾¾¾¾¾¾¾¾¾¾¾¾½¹²¬§£Ÿ˜“Š…„…†‹‹„{yyyyyyyywy}rgeggjnnsvyz|{{{z…ˆ‡…”•“ŽŒŒŒŒ‹‹Ž“–˜—•‘ŠŠŠ‰‰ˆ‰ŠŒŒ‰†|xpmls‚ŠŽŽŽŽEEEEDDBA@@A@><;::876745422100/...-,,,,+*******))))))))******))))((''((&&&&%%&&%%%%$$%%%%%%%%%%%%&&''''()))*****,,,+,././/111111233443444555555556655554345555334443333332333111111//....-,++**)()(('''&%'%&&&&%%%%&*19?BB==FKKLJJHEB??A;I`jdYYYWY]`kgA&&&(bvdbdj:!(+*,--145688?jkgiigltkdfghhiill_H:BE>3153016A[R520/..01348<:966642-,---/04798;==?AABBCCDCCCCCCCDEDEFFGGJKNQLIGFEECA@?@CDGGFFIGMV_gosqgSX_bn{ˆŠ‰‰„|qp{~|{yxyy}}tonnkilv…’‡ƒ~€€‚ƒƒ‚‚€~ysha[WQQZhsuwxz}€‚„‡ŠŽŽ‰wpljb]`abbbacdfgd^Ydfemrz‘šžšx@BFEDHGGECDFDDCEJd’¬¸º½¾¾¾¾¾¾¾¾¾¾¾¾½¼¶²¬§¢›–‡„‚ƒ…‰‡}{{zzyxxwvw}sddgefkloswx{{{{z‡ˆ†ˆ””’‹‹‹‹”–—”“‰‰ˆˆ†‡†ŠŒŒ‹†€{yutw~ˆŽ‘‘BBBBBBAA@@@?<;::9765554322210/...-**+++*******))))))))******))))))((((&&&&%%&&%%%%$$%%%%%%%%%%%%&&''''))))*****,,,,,//.//111111134443455555555556655554445554334443333222233211110/..-.-,,+**)((((('&&%%%$%%%%%%&'(+/:BGHC=FNOPNJJEB?=<:69Mhi]XVTUY]ciV2!$,`vccbg1"*,0321378<8=iqjhjhiolfgjjgjnn[B9:?DI;3442016?PI30////276769<<9853/++,,--/159:;==>@@@@>?A@??=>>@?AACDDDDEHJJIHFDEECA@?@CDEFGFHGGJOX`fcVIO^en|‰Š‹Š…|pqz}~}yyzzz|€wtqonlnyˆ’†~~€ƒ„ƒ‚€~{xriea[WW]isuwwz}„…ˆŒŽŽŽ‰‚xpgc``abccccdfffa]]hhhkru€•ž›u><::998744343322110/..++,+,++)))******++****++++++******('(('&&&&&&&&&&&%%%&&&%%%%%%&&&&&&''')******++,---.///0011202224445566666655555555555545543333333333222122111000..----,+**)))((('&&%%%&%$$$$$%$&&*.8ELPIDGRTVRMIDA><<992.8VebWUSVX\^ggK+'craeh]'*,27=CKLI@IjsmjmiipnfhiikngZL;9:=?@;642200467;71/11/037:97:@A=:53/++,----/2669;;<;;=>>??@ACCBADFFGGFDCBB@?@?@BCCDDEEEFEGJQUQJEGWfs~ˆˆˆ‡ƒympw{}{zyyyz|€„~wqqoor}‹“Œ…€z{z~ƒ…ƒ‚~|}}}|uqmida`cbisvww{|ƒ„†ˆ‹ŽŽŽŠ‚ypebbcdccccccfdcc]^ijjmrstŒ›˜r>AEDECCEDDDEDBDKm™®¶º½¾¾¾¾¾¾¾¾¾¾¾¾¾¾º¸µ®« •‘ŽŒŠ…ƒ~zy~~{zzzyxxwvu|zhehgdejnnqtvy|}|ˆ††Ž’‘“ŽŒŒ‹‹ŒŒ“–”‘Œˆ‹‹Œ‹‡ˆŠ‹‰…„††‡‰‹‘‘”““@@@@????<<;:99887666533322110/..,+++++*)))******++****++++++****))*((('&&&&&&&&&&&&&&&&&%%%%&&'''''''()*))++++,,,--..//0001111222344556666665555555555553454333333333322122211100/.--,-,++*))(((('&&&%$$&&$$%&$#$$&)-8EOTRMKSXVSOKGC?;:732/.,=XgaYTSVX[`gbB&$^oaeeV:85=@ILQXcrrlmljkppjiklojSEFGHCABA>84420//36763.-.11149=;7;BD@=71/+,,----/14678:;=:889<<<;;;:::===>?ABCCDDEDCA@@@AA??@@AABBBBDCDDCDEFDEEShuƒ‡†‡…€tmqxxzzzyyyz{|‰ŒxurqqwŒ’‹…€{zy{~‚„ƒ‚~ywxzyzwxpnlmmlhmtwwx{}‚…††‡ˆ‹Š‡unjfbfeddcccccdb`_floljpxyŽ“oCBGDEDACDDDEDBCW…¦³·¼¾¾¾¾¾¾¾¾¾¾¾¾¾¾½½¹¸²« ™•–•”‘ŽŠ…~vuyyyzzyxwwuvyzgeedefiklnrvy}}|„ˆ†ˆ“’‘‘ŒŒ‹‹ŒŒ‘’‘ŒˆˆŠ‘––’Ž‹Š‹ŠˆŒ’’’‘’‘“‘‘’’????>>==>;;;9788767643321100.-..++++*+++********++****,,,,,,,,+**,+(**((&&''''''''''''((''&%&'''(()))))***+,,,....../0/00010022223445555666655665566554444443333332122221101000/..--,,+++*))((('''&&&%$$$&$#####$#$&+6ALVZVQSXYXTNKHD?<8310./,,Dah^VTTTX_chcE,^i]chP+7>????@@AAAAABBBCCCBBBETiv€‚‚{oiovvwxxxyzuuv‡’€yvtrx‚ŒŠƒ{xxx|‚„…ƒxpptwuwvusrsutqlktwxx{…†…†‡ˆˆ…}uspkfcefffdccbc`[]cgkqnoru{‰ˆpFCCEEEBABCDEFCJj—¬µ»¼¾¿¾¾¾¾¾¾¾¾¾¾¾¾¾¾½»¹³¨£¢  ¡œ–“Œvuwxyzzywwwvxz}heecfdghknrvy}{}…††‰”“’ŽŒŒ‹‹‹’‘ŽŒŠˆˆ‹–›š—•‘Ž‘“•–”““’’’‘‘’‘’<<<<<<;;<9888755656532211100.---++++************++***+,,,,,,,,,,+**)((((''''''''''''''((''''''''(()))))*+++,,,.../../0000010022223445555666655665566554444443333332122111100000/.--,,++***)(('''''&%%%$$$$$$#####$$%)0:FQYXQQXYXXSOLHD?;752/.-/*1HeiYSSVXY\dibVjqcdbJ8K_eggntlhjlmppkml]=-5I[]TOMEGG=74230/,0:=621.--03668::89>AEB@93/--,,-.0222455567778989:988888889;;=?@ABBBB?>??>>>>>>?@@AAA???@AAB@BAJ[ky{{|}yrkipttvwwwyxrgl€“zxvsy‚ŒŽˆƒ|yyy|ƒ…†„xoimmopsvvuuwvutpmrwz{„……††‡‡„ytpnijgffecccca]Z]cgmuuuomp†ˆpLDDDEDACCCDDEFV€£²¸»½¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¼»¸¶µ±ª¦£›”Žƒzuwxyzzywwwwwz}hedcdeghimqtz|{}„…†‰“”“’ŽŽŒŒ‹‹‹ŠŠˆˆ‹˜œ™˜–•–––———•”““““’’’‘Œ<<;;;;::998876776655211100..-,++**************++**++--,,------,,,+****))((('''((((((((((((''(((())))))*+,,+,----..////00011102222344445556665555555555444444333311110000000////.--,-,++***))(('''&&%$#$$##"#""""###$&-6CMSTPNUZ[[YTMIGB?<732/+,,,+3Ui_ZWWWYYZbjqutponligeblrjhgjnqrnj\>")3G]_ZRMB8EE;5220/./7HL<31//..3789;867?@@???>?>=<====>?@AAA@@>>>@@?BIWcrzwvzyqj^blqsuuvwwwqcWn˜‚}yx{„Œ‹†‚}zz{|€„…ˆ‡ƒ|sb_dghlnpsuxxvtstttuz~ƒ„………‡‡‡ƒ{wxusoigfedcbb_^Z^fmtwvupliv†pQGFFFDCBCDEEAJd’ª´¹½½½¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾»°«¦Ž„zuwyzyyywwwvwz}lccdddfhjmrs{z|~…††‹“–’‘ŽŒ‹‹‹‘Ž‹ŠŠ‹Ž”—œššš˜š™™—˜—–•”““”••’“‡::::;;::99776666554431110/..-,++,,************++**++----......,,++++++)))((((())))))))))))))(((())))))*+,,,,------////000111022223444455566655555555555444443333111100000////....-,+++***))(('''&&%%$$####""!!""""!#$)3>GLIFIOY\^]XQNLGC?:74/+*)+(*+===<<====>?AAAA??>?>>?AIT_mwvtvupeXOUafkoqtuywn[;2m–‹}{~„Œ‹†‚}zz|‚†‡ˆ‡†‚xiYY]`dfglrvwwvvuutwz|‚ƒƒ„……†ƒ~zyzuromgddcb__^]ajsywvupmko~YDGFEBBBCDEEANs›¬¶¹¼½¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾½¾¾¾¾½µ¬ xnpsuwwyyyywwwvwz}kccbddfhjmqtz||~„…‡““’‘ŽŒ‹‹‹Ž‘‹Š‹Ž’“•–šš›™ššš™™˜—–••““’‘‘ŽŠ…€{99:::9::8888666644442011/..--,++****)))***+++,,+++,,--........-,+,+*++))**)))))()'((((((**))))(((()*++++,,--..--..//0000001101222233334555666655554455333444222211110.//00//--...-,++**)))((''&&&%%$#"##""""!!!!!!""#'09CEA?BMWZ__^WUPKGC?90('('&&&'#*C[_YWSRUWY\`bccdcclwqiglptyh?!#((9Neidcc[L42A9420//.,-/1/011/15412579888=EGFD@;50,++../1344433333344456677667755557788:;>>=<<<<<<==<==AES_isvrrqldUB>FPX\bgmstthYVLfŽ†€‚‡ŒŠ†‚~~}„‡ˆ‰‡‰ˆq_QSUZ]`fjrttuuwvvyyz}‚„„„„ƒƒ~||zuupkhfda_][\eipsussnjigr|cGFFFDBCCDCDBR~ ­¶º½½¾¿¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾½½¾¾¾¾½µ¥Žscdeinsxyzzyxwvwwy~ncbbdfegjlou{|}‚…ˆ•”“ŽŒ‹‰‰Ž‹Š‹Ž’•—™™šœ›šš™šš˜˜––”’’Œˆ…€yumd99::99998888655532222000...--,++))++**)*+++++,,+,,,,---........,--,+,,+*++***)))*(**))))**))**))))*+++++,,--..--..//0000001112332222333455444455554444333444222211110.//....----++++**)))((''&&&%%$$##""!!!!!!!!!!""#'-8BGD>BHPW]`_^\ZTNKE?.'&&%((('&$$(@]bTOTWWZ^_acedluspkorvxe;%&(&'=]iiec_UI,+86310/.-*+,./00002672236::89>BDEEA<61,++...0133333222244444456466655555677:;<==9::;;;<===?GRZgrtqtrkcU>029?DIQZ`fmnh_ehr‚‘‹„‚‚ŠŽŠ†„‚€€„‡ˆˆŠˆˆŠ…zmVMOPTW]dkpqsttvwxyz|}}~€€‚„„„‚}|yttnjgc`^^\ajpppqqqmihfiroPLJJEBBBBABEU…£°·»½½¾¿¾¾¾¾¾¾¾¾¾¾¾¾¾¾½¼¼¼¾½¼¹©ˆhY_dehhkqxyyyywvvvy~sfccgiijlnqv|}~€‚…‰Ž’‘’ŽŒ‹‰‰Ž‹ŠŠŽ‘”˜™›››™™˜˜–————–•“‰ƒ~ztldYQP8899997788875555322220/....--,++++++***++++++,----............--..-+,,,++++*))****))****+)***+*(*++,++++,,--..--..//0000/1111222223322333345445555334433453222220011/././....-,,,++**))((((&&&%%&%$##"""!!!!!!!!! !"#&+4AFFDGIOUZ_bca_\WSM>.))%$$$&(*((($*G]]UY]`acchhmsplkmqswkN4,+&'*C^ibWRYSJ-(76000/--*,0/11121389757:<><;;@CEEB?;50,+,,-.0112332200223344444444554234679;<;978888:;<>==??BBBA?<>CP[fqussng_R@2//0369@FOW_b`[^V]tŽ†„„‡‹‰……„„„„„†‡ˆˆ‰ˆˆˆŠ†ylVJHJLR[gnnoprtwyxx{}~~~€ƒ‚ƒ€~{xvrnjd`^[]ehjmpqonligddfkWTPICBABBBAEX‡¤°¶º¼¼¾¾¾¾¾¾¾¾¾¾¾¾¾¾½¼»ºº½½»·­…\PW]`dghijrwxxxvvwx|€yqprssssttw}~‚†Š‘’‘ŽŒŒ‹ŠŠ‹Š‹“—˜˜š™š™˜•”’‘‘ŽŒˆ€wpg`XNGDHN8888887788755555322220/....--,++++++***++++++,----......//////....-.,,,,,+++**++++**++**+,***+++*++,++++,,--..--..//000000111222223322333333443333554433330002220000../....---,+++**))(')&'&&%%%$$##""!! !! !#&+4?HJJKNPSZ_eeffb^XM4)+(&$%'&&+-..+&%*:KWgljkmnkurmmosuwlSI52<=12LcdZSLMMG0%65/00.---/201212369=;988;<==>@?@B@?=92.,,,-.0112332210113344443333444233568:;989888879:;;;<=@ABA?=@NYcopqsoh\N<.-,-/00047:CINPWYRZi‹†……‡ˆ†…ƒƒ‚‚ƒƒ†‡ŠŠŠ‰ˆ‡‰Š‡~o[HDEHQcikkmprxywwy{{{|}}~~‚ƒ‚}{yurnje[PT\efgnonnligddb`XRPFDBBAAABDXˆ¥±·¹¼¾½¾¾¾¾¾¾¾¾¾¾¾¾¾¼º¸·»»½¹±•\EPY\^bfgihltxyyzz|‚…‚‚‚‚€~~~~€‚‚†ŒŽ‘ŒŒ‹ŠŠŒŒ’–——˜˜™™—•“ŒŠˆ†„ysg_VNEEFJLPS88777777765555553222200///.--,,,,,++++*,,,,,,,..--//....00////......--..,,,,+++*++++****,,++++,,**,,,,+-,,--..--..//000000011112222222343333333333333333320.0111000/......--,,+++**))(&&''&&%%$$$##""!!! !! !! !"%+3=EILOQQT[`fkjfdc[H1,/,()))')*,,-/.01128AQX`cfrroortuteQPO;;HOJO[`XURKKOF1$3500/-.-16855324469?>=;;<@@BA>0.-,--//011248DY‰¦°·¹»½½¾¾¾¾¾¾¾¾¾¾¾¾¼»¹¶µ·º»¹­‚FEOV\^`ddgiiow{{|…‡‡ŠŠ‹‹ŠŠ‰‡†„„††…„ƒƒ‡ŒŽ‘ŽŒŒŒŒŠ‰‹‘”•—–•–——–”Ž‰…}xuqncVLFBA@CHMMPT77777768765555553222200///.---,,,,++*)*,++,,--....--..//00////..----....----,+++,,**++++,,++++,,,,,,,,+--,--..--..//00000001110122222233333333332233333333210011//......----++++))))(''&'&&%%$$$##""!! !! !! !#(.6>EIKPSTW_djnliebV>.-,***--(**,,,**-.0110..((drllkhim`ILNPDCNRQY^ZY][UUTR<'/6830.--17=<:63335;@AACA?@BBA<:<>==<:51.---/11112222222222331111113321//112443345555667799::<=>?>FP]krnolf\N=0.,,,,-////00101359;>AK`f`dhkpqsvz}ƒ……‡‰‰‹‰‰†„‡ŠŒŒ„vcG?HWcfdekpzyvwyz{yxxxz{|}~}|€|zvsrooplknkkoooljhfbab`\KLOGCBBBBABV„£®¶¹¼½¾¾¾¾¾¾¾¾¾¾¾¾¾¼º¸³¯µ»»·§pHLQY\^bdgjjjms{„‡‹ŽŽ‹ˆ‡‡‡‡††…„ˆŒŽŽŽŽŒŒŒŒŠ‰‹‘“”“•““”“ŽŒ†uld`ZOE@<=>A@CHLNOR77777777665544333222201000/...-,------............11110000000000--..-.0...--..----,,,,,,,,--,,,,,,,,--,.....-...000000000.11110022221111345433222222221111110.00--------,,+,++****))((((&%&%%%$$##! !!  "$*08@EHKPSVZbfkonie^O0*)))**-1,,,,--*%#$',0016.Zvjjkolh\UJIJIEKROX]a`aaZX_^ZK/)4A@5.-0:ACB<64469;?CDEHDBABC<78999:8840--,.00111122112222221000002211//01001122444466668989;===@JVdnlnje[M;3/,+,,--..//00//00221236@EEIMSU[achotyz€†…ˆ‰†„……‡Š‡yeIGVdeacisyyvvyyzywtqvwz|}}}~}}{yvsrnnoonmlmnjggebaab]OKSPDBDCBCDRw›®¶º¼½¼½½½¾¾¾¾¾¾¿¿¼»»µ«­´¸·®šo_cdddgjjkmnnry}…‡‰‘‘ŽŒ‹†‡†……†„…‰‹Œ‹‹Œ‹ŠŒŽŒ‘‘“Œ‰…€vkaUME>98:;<>@@DFILLL77777777665544333222201000/.....------....,,...../11110000000000/...-.0.......----,,,,,,,,--,,,,,,------....-...000000000.//00//11111111233133222222221111111/00....----,,+,+**)**))((((''%%$$$###!!!!  !$',4=CFGHKQUZafhlkieZB-(*++**((++++,,,'#"%%),2;Zulikjke[MLEGE95JPQW`_[__ZZ[_VS:+/DM9//3@DEE@99:88<>?BDHID??::7555589983.-,.00111122112200000000000011///1001111233444566879899:ANYhmmje\N>520.+,,--..//00//00221220/04468<@GHNV\diouy}€‚‚ƒƒ„‡‹Œ‡|hSX_dbdowxwvvyywvpnnotwz{}}~~~}|zyvtrqqpnnmljfdbaa`a`VMMPHCCCCBDKo—¬¶¹¼½¼¼¼¼½½½½¼¼»»¸·±­¦¦«¯®¤—€xzwwvvwwwvyy}}€ƒ…ˆ‹‘’‘’’‘‘ŽŒ‰‡†…„„„„„ˆŒŒŒŒŒŒ‹‹Œ‹‰ŠŒŒŽ’‘ŽŽŒ†…}xngZOE>;:7879<=?@ADFGJJJ77777766665544333222310/00/...----..-------/..//0000000000111100000000//00////..------,,,,,,----....../-..--..//00//////////00//00001122211212222211100000000000//..----,+++**)))))('''''&%$$#""!!""!!  !#)/6>EFFFINUY`ehjiigW7.-,-)**))((((*++)(''%&*(IpjjfgecPAKMIDB@@FNXbd\Zb_WQR\YOD:36=2./08?EFB;>@:889:;?HKF?:75654467;;852/--//0011112222////0000////00//./0011112222123466777759CP^hjhf]P?6421.,,,,,./....//00110222000/0//0026DGGHGKPX]`ddeebI-++)+'))))(((((***))'&&'>hfgfgdcN@DMML><830-//00001122220///////////..//./0011111101111233444448DQ^ggd_UC54410/-,,,,--,,..../011022211100.-..,../37<@GMSY^dhlpsx€…ˆ‡€sghqy|{xwwvqlgd\TV`gmrwxz}~}~}|}{yxvutsrqomjfba___`]PHJIHDACCGQj£­°±±±±­¬ª©§¤¤¡Ÿžœ›šœœšš™˜•’‘‘ŽŒŠˆ†„ƒ…„ƒ……‡‰‹Ž‘’’’’‘Šˆ‡ƒ‚‚‚‚‚…ˆˆ‰ŠŒŒŒŒ‰ˆ‰†‡‡…‚‚}ypjcZOGB;9;;;<==<<:9:=?A@CDDCCAA887766776666553332222210110/////------........////000000111100000000//////////..------,,----,+--......./////................////00001122221121110000//000///..--....---,++*****)))('''(&%$$#$##"""!!!!  #%)/6?FHHHHKOTY[^_b`W9'*)'((((()(((()+++*(),$Cl`bbdceP;B<:KG>=EDSda]]ab_c^ab`bTHDHVJ>BG9,05;?BGB:766336AJMGB:53355679>?A>:51..//0011235510////00..////////0000000000000011222238CQ^cb`XJ;4430///-,,,,,,,--//0111111121000/.-----..--.13;?DHLRW^dipuwwrlmsxwwyvspja]XMHQZ]dmrrw}~~~~~}~}|{ywvutusplhfa_``__[NLJEEHPV^jv ¤¦¦¦¤£  Ÿžœ››™—–˜˜˜˜˜˜——––•“’’ŽŒŠˆ†ƒ„‚ƒ††‡‰Ž’““’’‘ŽŠ‡ƒ‚‚‚‚„††ˆˆ‰‰ŠŠ‡~|zvrnia\ULF>;:9:89;<<==<<;:<>>BBABC@@@?777777776666553332222210000/////------......//////000000111100000000000000////..------,,--------.......///////..............////00001111221111110000//000///..--..--,,,*+*,+**))))''''&%$$####""""!!  !#%)/4@;76@F=556Pcccddda_`debcXLDGJA;@A:1.028ADE?634012:BEFB;7445579;=?AA>:51/0011112344420/////...////////////0////00////////16AMZa]ZRE9121////..,,,,,,--//0111111121120/..-,-----./../0036:>DIMSV[^^^afjklljgd]UQMMOIEQ^fjmpu{€||||}}{yyvvttqmjhecaa```TLNQX_ks|‚”›žžŸŸœ›œ›™™™˜™˜——–––••”““‘ŽŽŒ‹ŠŠ‰‡†ƒƒ‚‚‚ƒƒ„‡‰Ž’’’’‘Šˆ…ƒ‚‚‚‚‚„„†‡‡‡„‚}wmfdaYQKHA<;;;;;:;<<;<<==<<:;<=??BBBB@@?@776678776655442211223310////////--....//..////..00000011111111111111111100////....--------------....////////................/////0//0000221111000000//00/....-..--,,++++++**)(*(''''&&%$###"#""!!!! !"%()-3;ADFHJLORSUUSM?4)''''))(((())))++-++)*0ll`_adfV8AD<239@DBKEI_baffbc_cddgeguK@>?=>@A90/.7EED@821.-/29@BB?;6543368:IW[VSLA70///...--,,,,,-.../1000001111100/.-.-,---..0..//0/00/1568<>ACEHMPTVVXUSNECCFKF?DR[aflov}€||}||}|{yxwwsqomjgebcacb\_kq{ƒŠ’˜›››œœ›››››™™˜—••••””’ŽŽ‹‹‹‰ˆ†…‚€‚‚…‡‡‹ŒŽ‘’’‘ŽŒŠ†„ƒ‚‚‚ƒ„…‚ƒ‚wl_UJE@?A?B@:<<;<;<<<<<<=<<=:9<<==?BCCB@BD776678776655443322223100//00////......////////..00000011111111111111111100////....--------------....////////................/////0//0000111111000000//00/....-,,,,+++++++***((*'''''&%%$#"""""""!!!  "#&),04D?Peegjecddb_aebfdGBBDEEEE>3/.279AE>3/.-./29>A?976645578;>@@@>:611100123444422210/....--....----.................1?BAA>;;;>BB>?ENSZbenu}~}}|}~|{yxyyvtqonkjgilrvx|‚‹’–š™šœœœ›››››››š™™šš˜—””””””Œ‰‰ˆŠŽŽŒŠˆ‡„‚€€€€~‚„ˆŠ‹Œ‘‘Ž‹‡…„‚ƒ„„€zri^TJC?@@?BA=<<;<<<<;=<<<;;;<;<<==@CDDEEFF66776866775554332222210/..///////0////....////////0000111111111111111111//.////...--,,-------------.00//////..........------....///////////000000000/////....-,,,,++++++**)())((''&&&%%%$##""!""!!!!  !$&+.27?BHIIJLNOPQQMH:,'&&&'()((((**))*))*,-]legdbfV,?@=97321011244554444410...--...-------..//..--..----,09BMUPLF?5-,,,,,,,,,,++,-...///00000011110///......////0000000000........./0/001111367:;;==AGKPW_hry~€}|}~}|{{{zvvtsqonotz‚ˆ•——™™››››››š››ššš˜š—˜——˜˜–‹’’’‘‰…ƒ…’”””•”“’ŽŒˆ†ƒ€€}†ˆŠŒŽŽŽ‹Š†„ƒƒ‚ƒ‚‚~{tmia\PB@@?B@><<<==;;;;;;<;<:<=???@BEFFGGHG77776866776554332222210/////////-/////....////////0000111111111111111110///./.....--,,-------------.00//////..........------....///////////000000000/////....-,,,,++++++**('))((''&&%%%$$##""!""!!!!  !"#&(,039AFIIIJLNOPPNID4('&&&'((((')))*(((()-[ndge`h[-2>GTYXQF8:<98?^ltrlhje^`aaff\SROLNONLLH?3/237=CF?4/---,-/477765554679<=>@A?<9621011234555577522///.-...-------......--..-----.7AJROJF?5-******++++++,-...///00000011110000////..////0000000000..--........../////13366778;<;;<<<<;;;;;;;=>??@BBBCEGGGHHHG88777766665544322222211000///000//////..////....////0001111111110/./1100///.--.,--,,,,----,,,,,,....////////........--...--.........///.//000000000000///....---,,++,,+**)''''(('&&%%%$$#""!"!!! ""  #%(),15@A>=:7520/1213455689643100.-...,,,,,++,,-----,,,----,,5@FQNIE@7.(())))****,,--..--..////0000111000//////////00000000000/...-..--.../////0001222344556;IWcmx„€}€~zyxz{{{{|€‡‹Ž’“•••–—™™šš™š˜”‘Š†|uolhffkmpqs{‚„‚thnz‹˜Ÿž ž  š™”‘Œ‰…€|z{{}€…†ˆ‰ˆˆ‰‰‰‰††ƒ‚ƒƒ‚‚€€€€€}wsh]M@?<;::;;<<;;:;;;>@@AABCDFFFHGGGFFF6677996666554432222221100////010//////..//////....//0000111111110/./00//.-..---,--,,,-----,,,,,,....////..///.....------.--.............//000000000000///....---,,+++++*)'''''&&&&&%%$$$#""!"!!! !!   !#%(++.37>EJJHEDFGIKJGC;+&&&''())'')('((()+%Oibde`ea0,38=GNUYXP4/9AEHlttto`X`[Z_chg`YVTTQPPQPLGA:53127=@:2,***)**-012357888779<>??>=:752112133467876542000/..-,,,,,++++,,,,-,,,++,,--1>ACCDDEFGGGGGFFEEEE7777666655554432222210000000//0///////-///////...../0000111111000///.......-------,,,,,,+,,,,,,,--..////00////....,.....----..........,-..//////0000/////....---,,++++*(((''''%%&&%%$$$##""!!!!!  %'*+,049@FIIHEBBFGGFE?5)&&&''((((''(()****Tm`bea`c1*566=DIPWXT>==>81/138<<3,+*)))*,/13589:9:7778:>@AA@=:642012334567535422110..---,,++++++,,++,,++**)).8>FKGDA:5.''(''&((())))()++--./../0010001221111//////////00//11//..--.......///1/00/1112344442249CS`ksyƒ€}|wqotx}ƒƒ…†ˆ‰‰ŽŽŽŽŒ†~woh`XSNLFCDDDEM]XROTZ_gn”ž ŸŸžŸž›–‘Œ†€|toiddeimsw|~€„„„„„ƒ‚‚‚€}~~~}~~€„€~zrgZF><;;<<><>>?ABCEFGGFFFFFFFFEEECCD6677776655554432220000000000///.//////-///0000......///001////00////.....---,,,++++++++++,,,,,,,--..////00////.....-....----........,,-...//////0000/////....---,,++**)(((''''&%&%%%$$###""!!!!!  "$',-/39<@FIIGB??BEFEA=/(&&&&&''''''())*+._kaab_dd5&29;>>BJOPPI>;>BCECB:,'%%&&''((()')(*)3el^`_]`j?"/4:<@B@CHJJ?8;A@<;:752111224567797553442213330/.,,,----++**+)))*-6>ABDB?7)'%%&&''((((**+':ki^__]boG -238:DGKFEHG=>Qd[DOjlmgjiegkmpqnief`\ZWTQOMLLKGF>7114363.-+))(()*+-13379:766677:=>>><<;975211123435579:76753333330/....--,,++*****)(+19@FC>:53.(''''%%%%%&%%%&')))*)*,,-..011112222210/....////..//..----------.156884320235677883333246?JYcmux~€}{yvrmjkorv{||zxwusqniida\XRPNKKKKJIFCCDFECCB@ADOZ[YYYYYVQNFA=98779?EJRV\___aceimpqvz}~|{zzzz{{{|||{{|~€yskaWKCDEEEDDEEEEEEFFEFFFFFFFEEGFGE6654556644333322102211000///////000.......-.//..----/.........----,,------,,+*******))*+,,,,----+,--..//00..........--,-----------..............///...///.------++++)))(''&&&&&&$$%%$#$$#"!!   #$(+-18===;97543002222568997995442101100///.++-*****))()-5;@C>;73/,)%$&&%%%$%%%%%&''''()**,---./012322220000.0///.--..--,,,,,,**--/1468534422345896666521134=IV`irv}€~zyvrljfkotx|{}{zzwusqmie_[XTQMLJGEB@?=@>><<;;==?????><<9:::89::9:>>=;97532111333335888876421/0000000/.-,,**))))())-6;@>:641.*&$&&$$%$%%%%%&''''''))++,,../02222221111////..------,,--,+,,,-/11344123434456866666411344><9;;99;?EJOS[^`_`_\ZY[`ejqvz{{yyxxzz|{y{{||||}~}ytmc[NEFFEDCDCBBBCCCCDEFGGIIHHFGF54224322322211110000////////......//..----++,--,+-,,,,,,-+***+++,,++,,,,++++++**)))))))*++++++++,,,,..----..........---------------,------....//--...0--------,,+*****('''&&&&&&&&%$####"""" !"%()-/4;AHMPPLIFEB@BBDJG8**'$$&('''&'$7fmc]_chhlZ&+CKJNMOTSKING@9NaVZWR]dilkkqnlnpob_eggebb_WSPLHCBACB?:30254.,,++++,,+**+-/3578652113688;<=>>>>;955312331024425775422001123431/--***))))(((18;=:6640.+)%%%%%%%%&&&&&&&&''(()***,,-.01000011111/00////.-,,,+**+*+++-.0111112333223455566766644559ANYbkqx~}xurniefgjmqvy{{{zzzxxvtpligfca]XSMIFDA>::6777677899<<==<<=<:9889<@FJPUZ]```][USTZ]dhpv{{zzxyyy{{{|}|}{{|}}|xtkb[OEDEDCCBAAACCCDEFFHHJJIHFFD33222222221110010000//......----....--,,,,++*,,+,++++++++++++,,,++**++++********)))))))*++,,++++,,,,..--,-..........----------------..----......--....--------,,,+****('''&&&&&&&&%$$$$#""  "#&)),.28>DGJKKIFEBCBCBFB0(+'&'&'''(*%@pmbb`ffhoL#*0EQJGF@BLQFKJ?>=<93/011,++++++,,+,+,,/.3565531/057789;<>@@?=:76334300011114445332222234320--,**)))(&&'*38:<865430,*(&%%%%%%%%%&&&&&&''()**,,-//0000011111000////.-,,++++**+++,-...../01221233355666555334437?KT]emty~|vrpnlgbegjoux{{zzzzyywtrnlkkihd`[XUPMHFA;877666777::;;;;::887779A@?<:8697531//.//122444433355431/.-,,*)(*)'%''-2499856720-+'%%%%$$$$$%%%%&$%%'(**,,........---/0000..0..-,,++++++**++,,,,----01111233444433332232126:BNWbjqx{zurmmkfbbekqtvzxyzyxxvttolmmmljiggb^ZVQLE?986656777789::998866569>BFMSX\^^^]YUNIHJS[ckqwz{xuvvwxwwyz{}}z{{{zvpi`XPFDCB@@ABCDDDGHHJLKJHGECA1110111100/.//-..-------,+++,,,,-,++++*+)*++***)))******++**))++))**))))))))()((''(**)****++,,,,++++,,------------..--------------,,--,,,,--,,,,.,-.-,------,,,,++++))((''''&&&&&&&%%%$$#""" !$%)*,-.16;?CEGJJIHEBBA@9.*++('()(((*>kmfcfkmog5&.10=XXKNKJLMRSNDFFPQhieafltolknnknl`bdffefghf_ZVRJB=;:866:;83/-,*(()**,-./...../124642223568999=><:;9753221/0132334433344321/.-,,+*)))('&&),15::997661/,'$%$$$$##$$$$$$&&'(**,,--......---/.....///.---,,++++**++,,,,--..////0112222211112210/0235>JV^gotyyvrpmlhcaejlptwxyyxvsqomjiijjkjmmkie`ZSKD<87666667777776666555768>DINTZ\\^^]UQJFBGOXaiotxxxvttuvvuuy{{zxyyzxvqibYPECABBCDDEGHIJIKKJJHFCAB000000//0/////0.----+,,,,*,,,,,,,+**))*)())))))*))*)****++**))))(('(((('(()))((((((())********++++++,,,,,,,,,,------..--,,------,,++++**+++++++++++-----,,+*,,+,,,****(('''''%''&&&&%%$#$#""  "%(),+.147>BEFHIKJIHDDC?0*)*(&(*&)(*)dpgeejjoW*(0/4DV\LKHILKBIRKE?IY_YX[`jpskjjelnlg_aegc_afhe_]YUNGB=:879=D?51-+)((((+,--//..--.//3345521136779;<<><9866533334444542300000.-..-,+*)))('&'(,27::;;9841.*$#####""""##$%$&()*,,,,-....--,,,---.././/....--,,,,++,,,,,,,,----..//01000000//11/.////15;FO\ckry{wtpmiheaagjoruwwtqqnkihfdddhjijljgd_YQH>987665566775544445555569?ELQWY[^_]ZSMIC@IPX`gntyywurrstrrruy{yyyz|xvpjd\SJAACDFEHJJJJIIKJIFFCA?////....//.././---,,+,,,,*,,++))(*))(()('((('())***)))))**))))))(('((((&''(('&'''''())**********++++,,,,,,,,,,--------,,,,------,,++++**+++++++++++++,,+,,+*,,++++****((''''&&''&&&&&&$#$#""  "&)*,.148>CEFIJKKJJIFFF9++**)'&&'((#TojijnpoO,43.-4CJNOQMSSSOLKDBGG[^UK\biqommjgmomifdba``_cfb^ZYVPKD?<<=<988665566775544445544568>CJNTZ\]_][WPJEDDJQZbhpswxvtrpqqqrruz{zyyz{zwsog_XMFECGHIJJJJIIIIIFDA=<....--...--,-.--,,))****+((((((('''''''''''(((''''(((())**(((())(((('&&&&&&&&&&&''&'))****++))))))+++++++,,*++,-,,,,+*++*,,+,,,,****++++**++++++******+*+,++++++******))))('&'((''%%&&%%$$!!  $(+,/137;AGLMNLJKJLKJJC.&)'%'''(('&Mqhciopi?"300/14=GRZ^XVXYUMIB6@@@?>>=;;8542/-,---,-.,,+**)))('&&&%&+/24688441.,'"#######$$$$%&'(++,,--..,,++,,--......////..,,,,------..--------..//000011//..//..--....017?IS\emswwvpjhhd_^^flnpppnlgba^[WTSSX[`ea_ZWME=986665566775566556666669@@@@AABBBAAA?=;87531//.--,,,+++**)))''&&&%$&(+.025442/-)%#######$$%%%&'(++,,,,--,,++,,--......////.-,,,,------......----.///0000111/0000/.--..//0122;EP[ainvyupmjie_Z]bhknqrplfb^YUQNLOOSXYWUPJB9786666677887777889999:::;AFKQVY[\]^ZUQKGFHMRXahlpuwyywrmmoonorv{zzz{||yxtolc\RKIIIIHHGEEDC?=;76....,+-,,,--,,,,+****(''(&&&&&''''&&''''''&&%%''''(((())(())((('''''%%%%%%%%%%%%&&&&&&''(''''())))*)*+++*++++++,++++****))+++*++**********))))**************++++******)))))((((((('%%%%%$$""!!  $&)-037;>@CHMPQONNLLMK?-)(('*('&%&TvjefjdgC#.210.-/9HORXYY^dZTMG>1??A=>?@@BBA=::8664210//-,+**+*))(('&%%$#$$$%),.1234/.+&#"$##$#%%&&&''(****++++,,,,----.-......//.-,,,+---------.-------.///00011001100/.00//00230149AJV]fkrxwqpjec]ZZ^cglmonlgb[VSPIGGHJMMKFC=88:9887788888888:<<<;;;;<;?CHMRXZZ\^[WRJIGILQUZagmqtxyxvrmonpqrsv{{{{|}€€{xsohaVNIHGGDDCDA?;:752,,,,--,*++++******((*)))&%%%&&&&&&%%&&&&''&&%%''''&&(())((((('((''&&%%%%%%%%%%%%&&&&&&'''&&&'())))*)))++*******+***++***))****))))))))))))(()))*))))))))**))**********)))))(((((((((((&%$$""!!  $(*-037?CDEHIJLMOMMLJB2''''(*)%%$StkafhiiM&.10..-+/>MWPUVY__][TH>7CQ^^UWc_[htstwyzqffki`[Z[\ZRKIHJJJKJJFDA<875654-(%&'())))+-,,-....*''&$%(*+-/25568988;<<<===;::;>>><9::::8732100.,+***+))((&&%%##$$##%&*,.11/.-*%%$##$$%%&&&''(****++++,,,,--...-......//.-,,,+,,,,-----.-------.////00000011100/1111222223236>GQYajpuxrnhc^[VY^`ekmnnnhc\VSNHDA@CCB@==:;<;::::;;;;;;;;<=======>==BDIPSWXZ\YWTNJKJJNSW\dgkqtxyxvromqrrrty|{{|€ƒ~}wqlaULIHEBCB?=;97633++,,,,,+****))))(((&&&&&&%%%%%%%%%%%%%%%%%%%%%&&''&&&'((((((((''&&%%%#%%%%%%%%%%%%&&&&%&&&&&'))(''*())()))))))****++*)))))))(((())))(()))((())))))(((())))))))))))**)))))))((())((''((&$$%$#"!!! %'*/27:@EGFGEFIKNNMJD7,*((()*+$#A{m_^edj_%+1038/++-8DLORSUZ[^]QJB=HRWYQRZRTbrstx}|wusoh`\YVUTPHHIHGGEFJGDA>976543.('''(((')*++++-//.,*'%"$'(+.046776679=<<<=<:976798877:;<<;76320/.,*****))(('&&&#$$$$$#$'*-.10/.,)&$$$%%%%&&''(())**++,,----..///,---...//.-,,++**++++,----,----....//00001112221133334455434467>?>>=>>???<<=>????@@????>>>=<>BHLPTVXYZWUPLKHINRUY_dhnrvyzyvrqqqrssvy~€€‚ƒ…„‚~yri_QGFC?==;9644330+++,,+******))(((((&&&&&&%$$$$%%%%%%%%%%%%%%%%&&''&&%&((''((((''&&%%$%%%%%%%%%%%%%&&&&%%&&&&'''''')((('(()))))))(())))))))))((((((''''(((((())))))(((((())))))))))**)))))))((())(('''''%%%$###!! %),/4:=BGIIHFEGJNONNG>3))))*)((-qwi`dijb5%.1122-**,5BLNNSW[]\ZNF<8HVY[[S`dY[bmptswwwunjaZTQPPLFGHIFDDEIJHCB?;98851+((()))*)')*++,...,-,(%$$'*.26887668:;=<<<;:9664666577;9897521//.-,,,,,**))('''&$$$$$#$%(*.01//0-)(&%%%%%&&''(()))*++,,----..-------......-,,++**++++,---..........//001122123333344455777877778;@IQYdhpttmha[ZSRV]`dkmmlid^XUOLHC?@@@AAA@@@AAA@@?@AAAA@@??==>><;;:852+)))+*+,,((')+,-.-,.,*)'&&(+.59854799<<=<::9755545555653666210///.-,/..,++,****'%$$$#$$$%),.21121.+(%#%''&&&'(((()*++++,,,,....----....,-..,+++****++,,--/.....//..//00223221243345556677888888779>EMU]emsrmib][WSV\_cgkijiec[XTOLGEDBCCDCBCCBBAAAABBBB@@?=<;;;;99:;@EKPRWYZZXSPOLOQSXY\achlqsvy{{xusuuuwz|ƒ„‚‚ƒ……ƒzqfYHA<98755211.,,**))((**))(())((''&&&&%%$$###$$$$$%%%%%%&&%%%%%%&&&&&%''(&''''&&''&&%%%%%%%%%%%%%%%%%%$$%&''''''''((((((((('''''''''((((''''''''&&&&&&''''((((((''((((''(((((())****)))))))(()))((''(('%&&&%$#""! $&(,.37;EHNRUW[ZYVSQQRTVX[]`bbhmpswx||zxusvx||~‚…„‚€‚‚~yri[J>8754220/.-+,**))(())))(()('''&&&%%$$$$$$$$%%$$$$%%%%%%%%&&&&%%'''''''&''&&&&''&%%%&%%%%%$$$$$$%%%%%%%&&&'&''(('''%'('&&&&&&&''&(''''''&&&%%%%%%&&&&&%%&&'''''''''''''((((()(((**))(()))(())))'(((((('''&$###!  $%*,-1467:<=;>@BIJLJJF>2*'()(%Syocagso]$'//.--++***,/BKMRSSQPU[ZLDNbkooerj_[heaaMWglomkjgc^]XNGB?@ACEFDDEHIIHFDB@=7/,,//03682.*'(())+,+,--..++--000011236676544111012222100.//////..--..//11/.,*))*)('&&#%$$%)-1323330/,)&%%%&'''(((**++,,,,,-...../..////..-+**++*))++++++,.//01111222222233333444556666666887799888777?FOZciqusnje`^[YX\`eghklid`\WUPKHHGFEECCBA??@@BBAAA@?=<;:987889<@DJOTWY[[YVTVVXY[\]_ddghlprtx||{xuvxz}‚„…€~|yriZI;5531/.-,++++))))(())))(()('''&&&%%$$$$$$$$%%$$$$%%%%%%%%%%&&%%'''''''&''&&&&''&%%%&%%%%%$$$$$$%%%%%%%&&&'&''(('''%%%%%&&&&&&&&&'''''%%&&%$$$$%%%%%%%&&%%&&&&''&&&&&&''''((&'((**))(()))))))))'(((((('''&#$##!  "%(+-023347:;>BDFIKKJFB6*'))&G|o`_fqqe7 ,0.---++***-=GDGQTWROQYYRJSbjq^ovbeoha`bRP\dhjhd_`b^YQGB@?@BCCDDEFFFFEDB@=7/-/13425420+)((((***,/12200..--//../02241111011012222100///////...-..//11/..,,,*)('&&$%$$$'+-2555410-+(&%%&''''((**++,,,,-....../00110000/-+++++**+,,,,+,.//011112222222222223434566655557877998887666?@CFFEDCEECBA@<72.04872-121-,*))()(),/255442..,..--///.///////00133332111..//0000..--00000/.-,,*)('&$%%$$$$(+.2455211.+)'%%''((()++++----........001111200/,,----,,--,,,,..//1111111122111101223444554444566688888888659BLV_jsx|zwrkgc`ZX]adghhjhd`\YVQLIGFEDC@??BDCDECBA@?;;;:89:::?CCBAAEFB@?ABA90.1550-/211/.,)))++/-0358830.,,,--...-.--..-0/013333332200001111../000000/.,++*)(''&%%$$$$%),0244221/-,('%%%((()++++------......000000001011/---..--,,----./00000011110011012223444455444466777777774448@LWaju|~}zvpie_YW[]cffijifb\ZVQLIGGFEBBDFGFGGECB@@?=<;9:::;=ADHNSWZ\__``aa``acegggfc``bdjosx}||zz{~€€€}xvtrqmh_VF6---+++,,,++''(((((())('&&&&&'&%%%$$$$##$$%%&&&&&&&&''&&&&&&&&&&''%%%&((''(('''''&&&$&&&%%%%%%%%%%%%&&''&&&&%%&&%%&&$$%%%%$$%%%%%$$$$#####""""""!"""""""####$$##$%%%%%%&&&&&'(((''))))))))))(())))''&((&&'%%$"" !##')*+.039<@BFJMNNOOJFA1()*[wlmvsoP #,,,,,,,,,**+15:@HORONKHKKUadXQZeigackh[Xda___\__\ZYSQORRNHB@><;;=<<=?====?DKA1**.0.//33201/,*-0110214883/+*)),+,,,,,-....///02123234433221100//..00110/.,,++*(''&&%$%#$#&'++/0111//-+)'''((())*+++,,,+++,...-0000//01001100..--..--,,----,..../00////000011222233443333466666777654447@LWclv||vrie\USX\aeiijifb_[VRMIIHGGFGHJJJJGEDCC@>=<;;==??AEHMRVZ^`accbbabedeffffc`][\djnsuy{{zz{~~~ywuronlicYM=.***+++,+))(((((((())('''&&&&&%%%$$$$##$$%%&&&&&&&&''&&&&&&&&&&&&%%%&''''((''''&%&&&%&&%%%%%%%%%%%%&&''&&&&&&&&%%%%###%%%$$$$$$$####"##"""!!!"""!!!!!!!""!!""""#$$$%%%%%&&&('''''))))))))))))))))****''''&$$#!!!!"$')*+-/16>BFIMPRRTRJF?.()9zrinwwqT! "&,,++,++,,***/05>FILLIJLMLXbcYEHST_fldda_`bike^^_^]ZRPQSRLEBA><87788989999;BPI1*),-022421/0//023445222330-*)**)+++----.////0/0100023443333221100..//00/./--,+*)()(&&%%$#$$$'(*-./0100/.,)'(()(()**)+,,....//.-////..,-////....-...--,,,,,,,-..-.//////0000001100002322222333334554444469@LYcpv{{}}wqh_VPQW\aehjjlgda\VRMLKJJJKLLMMLIGFDC@?=<<<==??CGHMRVY]__cddebddeeffffc]]ZX[aflpsyz{{yz~~}xvrpmnlje^TC1+)*)**+*(('''())('))('''''&&%%%%%%%%$$$$%%&&&&&&''''''&&&&&&%%''&&''''''''&&&&''%%&%%%$$%&%%&&%%%%%&&&%%''&&%%%%$$$$"###$$$$##""""""!!!!! !!!!!! ! !!!!!##""!"##$$$%%%%%&'&&(((((())))))))))))****((((&%$$""!!!$()*++./5=AEJSUTTVQJE;*)(Tuqmsyte* "!%*,,***+,.,+++*3=BHKGHHFDFP_SFKSQTbgeijf^_\bjdZX[\\ZSPTVSQLCB?;:7544545688:AE@8,)+,.334422145665676310//,+*)(((()+----.0/011////0001331223333110/,-/../-.,-,+++*))(&$$$$$#$$$'(+,,/0221/-+*)))******+,..00/0/-./////....////......-,,,++++,,--....//////00////////00122211111123334434578ANYenwz{|yuoeXPPQTZbfgjkjgdb]XRPOMOMNOONNLIHFED@@?>=<>>>>AEINQU[]]acefffgffggffdb_[URQV^dhkrvz|z{|zzxuqrllkif_XJ:,)+*))))(('''())('))('''''&&%%%%%%$#$$$%%%&&&&&&&&&&''&&&&&&%%''&&''''''''&&&&&&&&&%%%%%%&%%&&%%%%%&&&%%%%&&%%%%$$$$#"""####""!!!!!!  !!! !!! !$$!!!"##$%$$%%&&&'))(((((())))))))********(((('%$$##""#&())*+..48>HKRVUURKHD9)(,ksjqxsi7$"#'*++**)+,./,*,+5AGGIIF@?;=FRJ?IRYkhfhhceg\P]gaXTWXZZVQUSMMJCC@==:755534688<>>><0))+-0144434688875431/---,+*)((((()*,../0/011///////01113234445320/,+../----,++**++(''%$&$$$$#&&(*+.133220//,**++++,,,.0000/0110.////....////....//-,,,----..--....//111100//////////002211222223334454576:CNYcjsyyyurj_VOSRW\bgmklkieb\XWRQPPOPPNNJHGEDDB@@??=??AACEGNQU[]^bdgihghggggfeda]XRONORYaglptx{zyzzxxwsmmligd\SC4-*)((''((€€€€~~~~~~}}||}}~€€€€€€€€€€~~~~~~~~~}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~}|{zzzzyyyyyyyyyz}€‚…†‰ˆ‰ˆˆˆ‰‰ˆˆ†…ƒ‚‚ƒƒ‚€|{zzzzz|}ƒƒ……„‚€~‚€ƒ†‡‡„‚ƒˆ„„‚€~~~~~~~~~~€ƒƒ‚€~}{||{}~~~~~~~}}}€ƒ„„…†‡‡ƒ~}}}}||{{{{||{}||}~€€€€€€€~||{|~~~€€€ƒƒƒ€}{xwyz~€€€€~}|}~~~~‚„}|€€€€~~~~~~}}||}}€€€€€€€€€€~~~~~~~~~}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~}|{zzzzyyyyyyyyyz}€‚‡‡‰ˆ‰ˆ††‡‡††…„‚~~~‚ƒƒ‚€|{zzzzz{}~‚ƒ„…„‚€~ƒ‚‚‚€ƒ…†‡…ƒƒ……‡†~~~~€ƒƒ‚€~}{||{}}}~~~~~}}}€ƒ„„…†‡‡ƒ~}}}}||{{{{|||}}}}~€€€€€}{{{|~~~~~€€€‚‚}zxwyz~€€€€~}|}~~~~‚„~}{€€€€~~~~~~}}||}}€€€€€€€€€€€~~~~~~~~~}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~}|{zzzzyyyyyyyyyz}€‚‡ˆˆˆˆ‡„„„„„„ƒ„ƒ€~~~‚ƒƒ‚€|{zzzzz{}~€‚ƒ„„ƒ€€‚ƒƒƒ‚‚ƒ………{}ƒ†ƒ~~~~~‚‚ƒƒ‚€}{||{}}}~~~~~}}}~ƒ„„…†‡‡ƒ~}}}}||{{{{||}~~~}~€€€€€~|yy{|~~~~~€€€‚~}{zyz{~€€€€~}|}~~~~‚„}{y€€€€~~~~~~}}||}}€€€€€€€€€€€~~~~~~~~~}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~}|{zzzzyyyyyyyyyz}€‚‡‰ˆ‡‡…ƒƒ‚‚ƒƒƒ„‚~}~‚ƒƒƒ€|{zzzz{{}~€‚ƒ„ƒƒ€€‚ƒ„ƒ‚€ƒƒ„„€{tv}‚„ƒ€€~~~~€€‚ƒ„ƒƒ‚€}{||{}||~~~~~}}}}~ƒ„„…†‡‡ƒ‚~}}}}||{{{{||}~~~}~€€€€€~~}|yy{|~~~}}€€ƒ‚€}zzyz|~€€€€~}|}~~~~‚„‚}|z€€€€€~~~~}}{{}~€€€€€€€€€€€~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~}||zyyzzyyyyyyyy{}€ƒ…ˆ‡†„‚‚ƒ„„„‚~~}}‚„„ƒ~{{z{{zz{~€‚„„ƒ€€‚ƒƒƒ‚‚ƒƒ…„€yssv€†‡~~~~~€‚ƒƒƒ‚€}|}}|}|||}~~~}}}}~€‚ƒ……††‡„‚~~}}}|{zz{|}}~€€€€~}|{yz{|~~~}}~€€‚‚ƒ‚}}zzyz|}€€€€‚‚‚€~~|~~~~~~€‚ƒ~zy€€€€€~~}}}}||}~€€€€€€€€€€€~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}||zyyzzyyyyyyyyz}ƒ†ˆ…„‚€€€ƒ„…†…‚~||}‚„„ƒ‚|{z{{{{|~‚ƒ„ƒƒƒƒƒƒ‚ƒ‚€€‚‚„ƒ€yttu}…‡~~~~~€‚‚ƒƒ‚‚€}|}}}}|||}~~~~}|}~€‚ƒ……†…†…ƒ~~}}}|{{{{|~~€€€€€€€€~|zyyz{|~~~}}€€‚‚ƒ‚~|{zz{|}€€€‚‚€~~|~~~~~~€‚„|yy€€€€€~~||}}}}~€€€€€€€€€€€~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}||z{{zzyyyyyyyyy|€‚„…‚ƒ„††‡„}|{}‚„„ƒ‚€}{z{{{{{}~€‚„„ƒ‚‚ƒƒƒƒƒƒƒ„ƒyvuvz€„€~~€ƒ‚ƒƒ€‚}|}}}}|||}~~~~}|}}‚‚„…†…‡…ƒ~~}}}|{{{|}€€€€|{xwxy{|~~}|}€€‚‚„ƒ~|{zy{|}€€‚‚€}}|~~~~~~ƒ„€{yy€€€€€~~||}}}}~€€€€€€€€€€€€~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~}||z{{zzyyyyyyyyy{}€‚‚€‚‚„…†…†€|{{}‚„„ƒ‚€}{z{{{{z}~‚ƒ„„‚‚ƒƒƒƒƒƒƒ‚€€€€ƒ‚yxwwz|€€~~€€‚‚ƒƒ€‚€~}|}}}}|||}~~~~}}}}‚‚„„…†‡…ƒ~~}}}|{{{|~€€‚‚€€€€~~{zyxwy{|~~}{}€€‚‚ƒ„ƒ€}|zy{|~€ƒƒ€|||~~~~~~ƒ„{yy€€€~~}}|{}}}~€€€€€€€€€€€€€€€€€}}~~~~~}}}||}}}}}}}}}}~~~~}}~~~~~~~~~~~~~~~~~~~~}}|{{{yyyyyyxxxxyz|€€‚‚‚ƒƒ†‡†…„‚~|{{|ƒƒƒƒ~|{zzzz{}€‚„„ƒƒƒ„„ƒƒƒƒƒ~}|{z{‚€‚ƒ‚ƒ‚‚€€€€€~|{}}}}}}|}~~}}}}|~€ƒƒƒ„…†„‚€}~~}||{}~€ƒƒ‚€€€~|yxwwxz|}}}|{|‚‚……ƒ‚€~|{{{|}‚‚ƒƒ‚~{z}~~~~~‚„{yx€€€~}}|{z||}€€€€€€€€€€€€€€€€€~~~~~~~}}}||}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~}}||{{zyyyyyxxxxyz|€ƒ„„„„†‡‡…„}|{{|ƒƒƒƒ‚|{zzzz{}~€‚ƒ„„ƒƒƒƒƒƒƒƒƒ€~€~|{|yz}‚‚€€€‚ƒƒ„‚‚‚~€€€~|{}}}}}}|}~~~}}||}€ƒƒƒ„…†„‚€}~~}|}}~€€‚‚‚‚€€€~~~{yxvvxz|}}}|{|‚‚……ƒ‚€~|{{{}~‚‚‚‚€|zz|~~~~~~‚„|yx€€€}}||{z||}€€€€€€€€€€€€€€€€€~~~~}}}}||}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~}}||{{zzyyyyxxxxyz{~‚‚„…………†‡‡…‚€|{{{|ƒƒƒƒ‚€}{zzzzz|~€€‚„„„„„„ƒƒƒƒ}|}~}}|~}|~ƒ„‚€ƒ„ƒƒ‚~~€€€~|{}}}}}}}~~~~}}||}ƒƒƒ„…†„‚€}~~~}~~€‚‚‚‚‚‚€€€~|}}zywvvxz|}}}|{|‚‚……ƒ‚€~}{||}‚‚‚~{yz|~~~~~~‚ƒ‚}{y€€€}}||zz||}€€€€€€€€€€€€€€€€€€~~~~}}}|||}}}}}}}}}}~~~~~~}}~~~~~~~~~~~~~~~~~~}}||{{zzyyyyxxxxyz{~‚„……………†‡‡…~|{{{|ƒƒƒƒƒ€}{yyzzz{~€€‚„„„„ƒƒƒƒƒƒ€~|{|~~~}}}~€~}„„‚ƒ„ƒ„‚‚€}~€€€~|{}}}}}}}~~~}}|{}ƒƒƒ„…†„‚€}~~~~~‚ƒ„ƒ‚€€€€|}}zxwuvxz|}}}|{|‚‚……ƒ‚€~}|||}€‚€‚ƒ‚€€|yxz|~~‚ƒƒ}{z€€~~{|{{{{|~€€€‚‚€€€€€€€€€€€€€€€€€€}}}}}|{}}}}}}}}}}}}}}}~~~~~~}}~~}}}}}}~~~~~~}}||||{{zyyyxxxxy{|~‚ƒ„†††††…††„}{z{{}~€‚‚ƒ‚~{zzzz{{}~€€€ƒ„„„ƒƒƒƒƒ‚€|}}~~~}}€ƒ‡ˆ‚}~ƒ„ƒ‚ƒ††ƒ‚‚~}||‚‚€~}|||}}}}}}~~~|{{}~ƒƒƒ„…†…ƒ~€€‚ƒ„„ƒ€€€€€€~|}~~}zyvuuw{|}}||z|€€‚‚……ƒƒ€~}}~~€ƒ€~}zxxz}~„‚~|z€€€~}{{{{{{}~€€€€€€€€€€€€€€€€€€€€€€}}}}||{}}}}}}}}}}}}}}}~~~~~~}}~~}}}}}}~~~~~~~~}}||||{{zyyyxxxxy{|~‚„„†††††…††ƒ|{{{{|~€‚‚ƒ‚‚€|{{zz{{|~€€€‚„„„ƒƒƒƒƒ‚~||~~~~~~€ƒ††ƒ~{„…„†‡†„‚‚~|{|‚‚€~}|||}}}}}}~~~~}{{|~ƒƒƒ„…†…ƒ€‚ƒƒ„„ƒ€€€€€€~}~~~}zxvuuw{}~}||z|€€‚‚……ƒƒ~~‚‚‚€~|xvv{~~„‚€}{€~}}{{{zz{}~€€€€€€€€€€€€€€€€€€€€€€}}}}||{}}}}}}}}}}}}}}}~~~~~~}}~~}}}}}}~~~~~~}}}}||}|{{zyyyxxxxy{}‚„„†††††…†…‚|{|{{{}€‚ƒƒ‚€|{{zz{{|}€€ƒ„„ƒƒƒƒ‚~}}~~€€~€‚‚€}{|ƒ…††…„„‚€~}{~‚‚€~}|||}}}}}}~~~~}{{|‚ƒƒƒ„…†…ƒ€‚ƒƒ„ƒƒ€€~}~~~}zxuttw{~}||z|€€‚‚……ƒƒ‚€€€€‚‚‚~|wvw{~ƒ‚€}{€~}}z{zzz{}~€€€€€€€€€€€€€€€€€€€€€€€€€}}}|||{}}}}}}}}}}}}}}}~~~~~~}}~~}}}}}}~~~~~~}}}}||}}{{zyyyxxxxy{~€‚„„†††††…†…‚~|{{{{{|€‚ƒ„ƒ}{zzz{{|}€‚„„ƒƒƒƒ‚€~}}€€}}€~~€}zzƒ„„„„‚€€~}‚‚€~}|||}}}}}}~~~~}{z|ƒƒƒ„…†…ƒ€‚„„„ƒƒ€€~~~~~}~~~}zxuttw{~}||z|€€‚‚……ƒƒ‚€€€‚‚‚}|xvx{~€ƒƒ‚}€€€€~~}{{zzz{}~~~€€€€€€€€€€€€€~}}}|{{{{|||||||}}}}}}}}}}~~}}}}}}~~}}}}}}}}}}}}}}}}|||zzyxxxxwxy|€ƒ……††„………„ƒ€||||||||~€‚ƒ„ƒ~|zzz{{|}}~€€‚„„ƒƒ‚€~~€~~~€~}|{y~€‚€‚}xx{}}|ƒ‚‚~|{{{|||}|~}}~~}}{|~‚ƒƒƒƒ†…ƒ€€‚‚ƒ„„ƒ‚‚~~~~~~}~~~~{xusstw{|}}|{{}€ƒ††„„‚‚‚‚€€‚ƒ„ƒzwvx|~~~~„„~z€€€€~}|{{{{z{~~~€€€€€€€€€€€€€~}~}|{{{{|||||||}}}}}}}}}}~~}}}}||}~}}}}}}}}}}}}}}}}|||zzyxxxxxxz~€ƒ„„…†„………„~||||||||~~€ƒ„ƒ‚|{zz{{|}}€€‚„„ƒƒ‚€~~~~~}}~€€€~~}|z{y{}|spqtxz|ƒƒƒ~|{||||}}|~}}}}||{|~‚ƒƒƒƒ……ƒ€€‚ƒ„„„‚~}}~~~}~~~~zwtrstw{}~}|{{}€ƒ……„„„„ƒƒ‚‚ƒ„ƒ~zwvy}~~~€ƒ„~z€€€€~}|{{{{{|~~~€€€€€€€€€€€€€~}~}|{{{{|||||||}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}|||zzyxxywxy{€‚‚ƒ…††„………„~||||||||}~~€ƒ„ƒ‚€}zzz{{{|}€€‚„„ƒƒ‚}}~}|}~€~}|zxyzx|‚~xspprtw|ƒƒƒ~}|}}||}}|~}}}}||{|~‚ƒƒƒƒ„„‚€€€‚ƒƒƒƒ‚€~}||}}~}~~}}yvsrrsw{}~}|{{}€ƒ……„„„„ƒƒ‚‚„…ƒ}yvwy~~~~‚…ƒ{€€€€~~}|{{||{}~~~€€€€€€€€€€€€€~~~}|{{{{|||||||}}}}}}}}}}||}}}}}|||}}}}}}}}}}}}}}}}|||zzzxxzxy{|‚ƒƒ„„…†„……ƒ|||||||||{}~€ƒ„ƒƒ}zzz{{z{~~€€‚„„ƒƒ‚~~}~||}~€~}}|zxxyvy€€zvssrtx|ƒƒƒ~~}}}||}}|~}}}}||z{~‚ƒƒƒƒ„„‚€€‚‚ƒ‚‚‚€€}|||||~}~~||xurqqrw{~}|{{}€ƒ††„„„„„„‚‚„…ƒ}xvwy}~~~‚…ƒ|€€€€~}||{{|}|}~~~€€€€€€€€€€€€€€€~~~}|{zz{{||||||||}}}}}}}}}}}}|||||}||||}}}}}}}}}}||{|{{zzyxxwy|}ƒƒƒ„…„„„……„~|||||||||}~€€‚‚ƒƒ~{{{||{}~€‚„…„ƒ‚€~}}~€||}~|{{zyywvv}‚}wtuwz}~€€‚ƒƒ~}|||||}}}}}}}}|{{{}€ƒƒƒƒƒƒ‚€€‚‚‚‚€€}|{z{|}~~}}}zxusqqrvz}~}|z|~€€€ƒ……„„„„ƒƒƒ‚ƒƒ„…ƒ€{xvxy}~~~€ƒ…„€|€€~}|{{{|}}}~~~€€€€€€€€€€€€€€~}||{zz{{{{||||||}}}}}}}}}}~~||||||||||}}}}}}}}}}||{|{|zzyxxwy}„ƒ„……„„„……„~|||||||||}}~€‚‚ƒƒ~|{{||z|}~€‚ƒ…„ƒ~}~€~~~~~~|zz{zzwwvy}}xwxz{}~€€‚ƒƒ~}||||}}}}}}}}|{{z{}€€ƒƒƒƒƒƒ‚€€€‚‚‚€€~{{zy{}~~~}}}zxurqprvz}~}|{|~€€€‚ƒ……„„„„ƒƒƒ‚‚‚„…ƒ€{xvxz~~~€ƒ…„€|€€}}|{|||}}}~~~€€€€€€€€€€€€€€~||{zzz||{{||||||}}}}}}}}}}~~~~||||||||}}}}}}}}}}||{{{{zzyxyx{~€‚„„ƒ„„„„„„„„}{||||||||}}~€‚‚ƒƒ|{{||{|}~€‚‚„„ƒ€~~}€€~~|zz{{zxxvw{€~zyz{|||}€€‚ƒƒ~}||||}}}}}}}}|{{yz}€€‚ƒƒƒƒƒ‚€€€~|yyxy{~~~}}|ywtqppsvz}~|{{}~€€€‚ƒ……„„„„ƒƒ‚‚‚ƒ„‚{xvx{~~~€ƒ…„€|€€~~}||{}}|}}}~~~€€€€€€€€€€‚‚‚‚‚‚‚‚‚‚€€€€~||zyzz{{zz||||||}}}}}}}}}}~~||||||||}}}}}}}}}}||{{{{zzyxzx{€‚……ƒ„„„„„ƒƒ‚}{||||||||}}~€‚‚ƒ„|{{||z{|~€‚‚ƒ„ƒ}~}~~€}}{z||zxxvwz}}x{|}|z{}€€‚ƒƒ~}||||}}}}}}}}|{{yz}€‚ƒƒƒƒƒ€€€~}||yxwy{~~~}}{ywtqoprvz}~{z|}~€€€‚ƒ……„„„„ƒƒ‚€ƒ„{xvx|€~~~€ƒ…„€|~~}}|}|~~}~}~~~€€€€€€‚~}|{z{{{{{{{{{{||||}}~~~~}~~~€||||||||}}}}}}}}}}{{{{{{zzyyyz}€ƒ‚ƒ„„„„„ƒƒƒ}||||||}}|}|}}~€‚‚ƒƒƒ€}y{||z||}€‚ƒƒ„‚}~~~}z{~}}~~}|||{{ywwzyuy~{z{~ƒƒ~~}}}}}}}}}||zyzyyz}€‚ƒƒƒƒ‚‚‚}|{zwvwx|}~}~~}|xuspnpruy}|{|~€‚ƒ…………„‚‚‚‚‚ƒ‚}zwvy}€€~~‚†…~~~}}|}}~~~~~~~€€€€€€‚}|{{{{{{{{{{{||||||}}}}}}|~~~~||||||||}}}}}}}}||{{{{{{zzyyy{~€‚ƒ„ƒƒƒƒƒƒ‚‚€}||||||}}|||}}~€‚‚ƒƒ„€}{||||}}}€‚ƒƒ„‚}~~||wuw}|{|}~~€~}}€zuvvuy€{z{~ƒƒ~~}}}}}}}}}||zzyxxy|~‚ƒƒƒ‚‚‚‚‚€€|{zxwvwx|~~~~}{xuspnoquy~|{|~€€€‚ƒ……„„ƒ‚‚€‚‚‚€}zwwz~€€‚……~~~}}|}}~~~~~~~€€€€€€€€‚~}|{{{{{{{{{{{||||||}}||||{}~~~|||||||||}}}}}}}}||{{{{zzzzyyz|€‚ƒ„‚‚‚‚‚‚‚€}||||||}}|||}}~€‚‚ƒƒ„~}}||}~€€‚ƒ„‚}~~|ztsw}~}||}~€~„„‚|tsvvy~€{z{~ƒƒ~~}~~}}}}}}{{yyxxwx{}‚ƒƒƒ‚‚€€€€~{zywvvwz}~~|zwurpnnpuy}|{|~€‚ƒ……ƒƒ‚€€€€‚|zwx{€€‚„„~~~}}|}}~~~~~~€€€€€€€€‚~}|{{{{{{{{{{{}}||||}}||||{|}}}{||||||||}}}}}}}}{{{{{{yyzzyy{}‚‚ƒ‚‚‚‚‚‚€}||||||}}|||}}~€‚‚ƒƒ„‚}{||~€€€‚ƒ„‚}~~}ztuw~~~}}€€€ƒƒƒ‚}tswxz|~{z{~ƒƒ~~}~~}}}}}}zzzywwvwz}‚ƒƒ‚‚‚‚‚€€€€}{zywvvwz}€€€{zwtrpnnpuy{}|{|~€‚ƒ……ƒƒ‚€€‚{zwx{€€€€€‚„„~~~}~}~~~€€€€€€‚‚‚‚€~~}|{{{zz{{{{{{||}}}}}}}}}}|}||||||}}}}}}}}}}||||{{{{{{zzzzxz|}€€‚‚‚€€€€‚‚}||||||||}}}}}~€‚‚ƒƒ„ƒ€~||}~€€ƒƒƒ‚€}~~|{xx{}ƒ†„}~€€ƒ„„ƒ}vsuwz}}|z|‚‚‚}|}}}}~~}|zyyxwutvy}€‚ƒƒ€€~~|zxwvuuvy~€|zusqpnnpuz|}|{|~€€€‚„„ƒ€€€€€€}zwvy|€€€€„„~~~}~~~~€€€€‚‚‚‚€~~}|z{{zz{{{{{{||}}}}}}}}}}}}||||||}}}}}}}}}}||||{{{{{{zzzzxz|~€‚‚‚€€€€~|{||||||||}}}}}~€‚‚ƒƒ„ƒ€~}|}~€‚‚ƒ‚~~~||{{}~ƒ††€€‚ƒƒƒ‚~ysrvy|}{z|‚‚‚}|}}}}}}}{zxxxvusux}€‚ƒƒ€€€€€€}|{zxvvtuvy~€€€}yusqpnnpuz}}|{|~€€€‚„„ƒ€€€|yvvy|€€€€„„~~~}~~~€€€€€‚‚‚‚€~}|{z{{zz{{{{{{||||}}}}}}}}}}||||||}}}}}}}}}}||||{{{{{{zzzzxz|~€€€€€}{z|||||||{|}}}}~€‚‚ƒ‚ƒƒ€~}~€‚€€ƒƒ‚~~~}}{}}€ƒ…‚€€‚‚‚‚€}yspsw{|z{|‚‚‚€~}|}}}}}}|zyxwwutrtx}€‚‚‚€€€€€€€}|zywvusuvz€€€~|yutrpnnpuz}}|{|~€€€‚„„ƒ€€€€~zwuuy|€€€€„„~~}}}~~~€€€€€€‚‚‚‚‚‚€~}|{z{{zz{{{{{{||{{}}}}}}}}}}||||||}}}}}}}}}}||||{{{{{{zzzzxz|~‚€~€€€€}|{||||||||}}}}}~€‚‚ƒ‚‚ƒ€~}‚€€ƒƒ‚~~}}||}€‚‚€€‚‚‚‚‚€|xsopv{|z||‚‚‚~}|}}}}||{zxwwvusrsw|€‚€}|zxvutsuv{€€€€~|yuurpnnpuz}}|{|~€€€‚„„ƒ€€€~}yvuty|€€€€„„~~~~~€€€€€€€€€€‚‚€€€~}|{{{{{{{{{{z|||||}}||||}}}}}}}}}}~~~~~~}}}}|||z{{{{zzzyyyy{}‚~~~€€€}||{{||||||}}}}}~€‚‚ƒ‚~€‚‚ƒ€€€€‚„ƒ€~|||~}€ƒƒ‚€~{xtomu|}{{|€‚‚ƒƒ€}|||||||{yxwwvtsprv{~€‚‚€€}}}}~~}|zxvvusstw|~€€€|yvtsqompty}}||}€€€‚‚ƒƒ‚€€€€~~~~}{xusuy}€€„ƒ}~~~~€€€€€€€‚‚€€€~}|{{{{{{{{{{z|||}}}}||||}}}}}}}}}}~~~~~~}}}}|||z{{{{zzzyyyy}~€‚‚~}}~€€|||{{||||||}}}}}~€‚‚ƒ‚€€€‚‚ƒ€€€€‚„ƒ~|z{{|~~€‚‚ƒ„‚€|zwtnlt|}{{|€‚‚ƒƒ~|{|||||{zyxvvusropuz~€‚~}{{|}}}{yxvutsstw|~€€€€|yutsrompty}}||}€€€‚‚ƒƒ‚€€~~~~|yvssvz~€€„ƒ~~~€€€€€€€€‚‚€€€~}|{{{{{{{{{{z|||}}}}||||}}}}}}}}}}~~~~~~}}}}|||z{{{{zzzyyyz}‚€~}}~€€}{||{{||||||}}}}}~€‚‚ƒ‚€€€‚‚ƒ€€€‚„ƒ~{xy||~‚‚€‚‚„ƒ‚‚€}{ywuoks|}{{|€‚‚ƒƒ€}{z||||{{zywvtrppnouz}~~|zz{|}|zxxvtsrrtw}€€€€~|xttrqonpty}}||}€€€‚‚ƒƒ‚€€~~}}{xtrsv{~€€„‚~~~€€€~€€€€€€€€‚‚‚‚€€€~}|{{{{{{{{{{z|||}}}}||||}}}}}}}}}}~~~~~~}}}}|||z{{{{zzzyyyz~ƒ~}}~€‚€}{||{{||||||}}}}}~€‚ƒƒ‚€‚‚ƒ€€€‚„ƒ‚~{xz||~ƒ…„„„ƒƒ‚|yzwwqjq{|{{|€‚‚ƒƒ€}{z||||{{zywvsronlnsy}~€€~}}{yyz|||zwxvtsrrtw}€€€~|wtsrpnnqty}}||}€€€‚‚ƒƒ‚€€~~}}ywspsv{~€€„‚€€€€€€€€€€€€€‚‚‚‚‚‚‚€€€~}||{{zz{{{{{{||}}}}}}}}}}}}~~}}||~~~~~~}}}}||{{zzzzyyyxxz{€‚ƒƒ€}}~‚‚|{{{{{||||||}}}}~~€‚‚‚ƒ‚€€ƒ‚€€€‚ƒƒƒ‚€~|z{|}‚…‡‡…„ƒƒ€}{zyzxslqz|z{}€‚‚ƒƒ~{z{{|{{{zxwvsqnmlmry}~|zyxxz{}|yxwtrqpqsx}€€€€~|wtsrqonpty}}||~€€€‚‚ƒƒ€€€}|zxtpqrw}€€}}~€€‚}€€€€€€€€€€€€‚‚‚‚‚‚‚~}||{{||||{{||}}}}}}}}}}}}~~}}}}~~~~~~}}}}||{{zzzzyyyxxz|€‚ƒ„ƒ€}}€‚‚€|{{{{{||||||}}}}~~€‚‚‚ƒƒ‚€€ƒ‚€€€‚ƒƒƒ‚€~}|~}~€„…†……„„‚€|{zyxxunpy|z{}€‚‚ƒƒ~{z{{{{{zyxvtrplkjlqx}~~}zyxwwy{|{zywtrqpqtx~€€€~|vtsrqonpuz}}||~€€€‚‚ƒƒ€€€~}{yvsoprw}€€~}}~~€€}€€€€€€€€€€€€€€€‚‚‚‚‚‚ƒ„„……ƒ~}}}}~~~~}}||}}}}}}}}}}}}~~}}}}~~~~~~}}}}||{{zzzzyyyxy{~‚ƒƒ‚€€‚‚|{{{||||||||}}}}~~€‚ƒƒ‚ƒ‚€€€‚ƒƒ‚€~~}}}~„„……„……‚~|zyyxxvpnw{z{}€‚‚ƒƒ~{z{{{{{zyxutqnkjikpv|~}zxwvvvx{||zywtrqpqux~€€€~|vtsrrpnpv{}}||~€€€‚‚ƒƒ€€€}|zxtqnorw}€€}||}~€€}€€€€€€€€€€€€€€‚‚‚‚‚‚€ƒ……‡‡„ƒ~~}~~€€€€~~||}}}}}}}}}}}}~~}}~~~~~~~~}}}}||{{zzzzyyyxy|~ƒƒƒƒ€‚‚‚}{{{}}||||||}}}}~~~€€‚„„ƒ‚ƒ‚€€€€ƒƒ‚€~~~~~~‚ƒ„ƒ„„ƒ~{zzyxvtqov{z{}€‚‚ƒƒ~{z{{{{zzyxtspmjiginu|~|zyvutuxz}|yywtrqpquz‚€~|vtsrrpnpv{}}||~€€€‚‚ƒƒ€€€}|yxsomnrw}€€}||}~€€~}€€€€€€€€€€€€‚‚€‚„‡ˆˆŠŠ‡†‚€~‚‚……„€}}|||}}}}}}}~~}}~~~~~~~~~~~|||||{{zzzzyyyxz}€‚ƒ„„‚‚€ƒ‚}|{{{||||||||}}}}}}€€€€„„ƒ‚‚‚‚„ƒ‚}~€€‚‚ƒ~~€‚‚„„ƒ~{{zzyxwvpnuyy|}‚‚‚‚€}{z{{yyzxxvtqomkifiov|~{xwtsstxz{zzyvtrqpqty€€€€€zvtssrqpqw||||}~‚ƒ‚€€€€€€~|zxvrokntx~€€}z|}€€€~€€€€€€€€€€€€‚‚€‚„…ˆ‰‰ŠŠ‰ˆ„€‚„…†ˆˆ†„ƒ€~}||}}}}}}}~~}}~~~~~~~~~~~|||||{{zzzzyyyz|‚„„……ƒ‚€‚}|{{{||||||||}}}}}}~€€„„ƒƒ‚‚‚ƒƒ‚}|}€‚‚‚ƒ‚~~€‚‚ƒƒ‚€}{{zzyxwwrouyz{}‚‚‚‚€}{z{{yyzxxvtqoljhfiov|~{xvtstuxz{zyxvtrqpqtz€€€€€zwuttsqrsx||||}~‚ƒ‚€€€€€€~|zxurokouz~€€}y{}€€€€~€€€€€€‚‚€ƒ„…ˆ‰‰‰‰‰ˆ†„‚„†‡ˆŠ‰‡†…‚~~}}}}}}}}~~}}~~~~~~~~~~~|||||{{yyyyyyy{~‚„„……ƒ‚€‚€€|{{{{||||||||}}}}}}}~€‚‚ƒƒƒƒ‚‚‚ƒ‚}|}€‚‚‚ƒƒ~}‚‚ƒƒ‚~|{{zzyxwwvruyy}~‚‚‚‚€}{z{{{{{zxvuroljgfiov|~{xvtutuxz{zyxvtrqpqu{€€€€€zwutusrstx||||}~‚ƒ‚€€€€€~|zwtqnkou{€€€{y{}€€€€~‚‚€€€€‚‚€€€€‚‚€ƒ…ˆ‰‰‰‰ˆ‡ˆ…ƒ†ˆˆ‹Š‰ˆˆ†ƒ‚~~~|}}}}}}~~}}~~~~~~~~~~~|||||{{yyxxzyy{€ƒƒ…………ƒ‚€€||{{{||||||||}}}}}}}~€‚‚‚‚ƒƒ‚‚ƒƒ‚}~€‚‚‚ƒƒ~~€€‚‚‚‚‚~|{{zzyxwwvsuxy|~‚‚‚‚€}{z{{{{{zxwuroliffiov|~{ywuvuvxz{zyxwurqpqu{€€€€€{xvuutrtvx||||}~‚ƒ‚€€€€€~|zwtpnkov}€€€yy|~€€€€~€€€€€€€€€€€€€€„‡ˆ‡ˆˆˆˆ‡†…†ˆ‡‰‰‡‡ˆˆ‡„~}}}}}}}~~~~~~~~}}~~~~}}||{{zzyyxyyz|„„………„„‚€€}{{{{{||||||||}}}}}}~€‚‚‚‚ƒƒƒƒƒƒ‚€}|~€€‚ƒƒƒ€~€€‚‚‚‚‚|{zzyxxwwwwtsxz|€€‚‚€}{z{{{{zzywurolifeflu|~€|yxuvvwxz{zyxwurqrsw|€€€€€zxvvvstuvy}{{{|€€€‚ƒ‚€€€€€}yvqolkov}€€€€€}zx{~€€€€€€~}€€€€€€€€€€€€ƒ††…‡‡‡‡‡‡††‡‡ˆˆ‡‡‡‡‡…ƒ€~}}}}}}~~~~~~~~~~~~~~}}||{{zzyyxzy|ƒ…………†„ƒ‚‚~|zz{{{||||||||}}}}}}~€‚‚‚‚ƒƒƒƒƒƒ‚€~||~€€‚ƒƒ‚€€€‚‚‚‚‚‚‚‚€~|zzzyxxwwwwssx{}€€‚‚€}{z{{{{zzywurolifeflu|~|yyuwwxz{}}|{ywutsty~€€€€€€{xwwvtvuvz|{{{|€€€‚ƒ‚€€€€€€}yvqnkkqw}€€€€€}yw{~€€€€€€~}€€€€€€€€€€€€ƒ„‚ƒƒ„…††……†…††………†ˆ‡…€~}}}}}}~~~~~~~~~~~~~~}}||{{zzyyxz{~ƒ…†…†††„‚‚‚~{z{|||||||||||}}}}}}~€‚‚‚‚ƒƒƒƒƒƒ‚~{{~€€‚ƒƒƒ‚€€‚ƒ‚‚‚‚‚‚|{zzzyxxwwwwssy{|~€€‚‚€}{z{{{{zzywurolifeflu|~~}{zwxyz|}~}{zxwwx{~€€€€€}zxxwvutvy{{{{|€€€‚ƒ‚€€€€€}yvqnkkrx~€€€€}xwz~€€€€€€~}€€‚‚€€€€€€€€€€€€ƒ………„…ƒƒƒƒƒƒ…‡‰†‚€~}}}}}}~~~~~~~~~~~~}}||{{zzyyxy|€‚………‡‡††ƒ‚‚€~|{yz{}}||||||||}}}}}}~€‚‚‚‚ƒƒƒƒƒ‚~{{~€‚‚‚ƒƒƒ€€ƒ‚‚‚‚‚‚‚~{{zzzyxxwwwwtsw|}~€€‚‚€}{z{{{{zzywurolhfeflu|~~~|zxxz{~}|{yyz}€€€€€‚{yxxvusuy{{{{|€€€‚ƒƒ€€€€}yvqmjjrz€€€€}ww{~€€€€€€~}‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€€€~}}}‚„…ƒ‚}}ƒ†‡‡…‚~~}}}}}}~~~~~~~~~~}}|{{{zzyyz|€ƒ††…………††‚ƒ€€€€~||{z{{{{{{{{{{||}}||}}|}€ƒƒƒƒƒƒ‚€}||}€‚‚‚‚ƒƒ‚‚‚„ƒ„‚ƒ‚‚€|{zzzyyxxvxwtrvz~€ƒƒ|{yz{||{zyxusoligfglu|~~~}{{{}‚€}|}}€€€€}zxywutvz||||}~€€‚ƒ‚€€€€€}yupljksz~€€€{wuz~€€€€€€~‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€€€~|}}~€‚ƒ€}zxx{{}„‡‡‡ƒ€~~~~~~~~~~~~~~~~}}|{{{zzy{|~„‡††………„„‚‚€€€€}{{{{{{zz{{{{{{||}}|||||}€ƒƒƒƒƒ‚€}||}€‚‚‚‚ƒƒ‚ƒ„†……ƒƒ‚‚€|{zzzyyxwwxwurtz}€‚‚|{yz{||{zyxutpmjgghlu|~~~}}}}‚ƒ„„ƒƒ€€~~~€€€€€|zywutvz|||}~~€€‚ƒ‚€€€}yupkjmt{~€€~zwvz~€€€€€€‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~|}}~€}ywxxyy|€‚†……ƒ€€~~~~~~~~}}|{{{zzz|}„†…†………ƒƒ}zzz{{{{zz{{{{{{||}}|||||}€ƒƒƒƒƒ‚~}{{|€‚‚‚‚ƒƒ‚„…†……ƒƒ‚‚ƒ€}{zzzyyxwxxxtqry|€|{yz{||{zyxutqnjhgimu|~~~~}~~€ƒƒƒƒƒ‚‚€€€€€€€€}zxvuwz|||}~~€€‚ƒ‚€€€€~}yupllou|€€}yvvz~€€€€€€‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~|}}}~€€|xvxxyy{~………ƒ‚€€€€€€€€€~~~~~~~~}}|{{{zz{|~€‚††……………‚‚€€€€€~{yzz{{{{zzzzzzzz||}}||{{|}€ƒƒƒƒƒ‚~||{|€‚‚‚‚ƒ„„……‡†…ƒƒ‚‚ƒ‚€}{zzzyyxwwyxsnrx|€|{yz{||{zyxutqnlihimu{~}}}~€‚„…„„ƒ‚€€€€€€€€€€€|xvvxz|||}~~€€‚ƒ‚€~€~~}yupklpv}€€€€€}yuwz~€€€€€€‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~}}||}}~{xxxyy{{|~€ƒƒ„…„ƒ‚‚€€€€€€~~~~~~}}|{{{zzz}‚„…„„………„‚€€|zxxxzz{{zz{{{{{{||}}||{{{|}€€‚‚‚ƒƒ€~}||{|€‚‚ƒƒƒ…„……†††„ƒƒƒ‚‚€}|{zzyyxxxyvsnpw|€€€|{zzz}}|{zyvtrokhhinv|~}}~€‚„ƒƒƒƒ~€€€~~~€€}ywuwy|||}}€€€ƒ„}~{wtojkpw}€€€}yux{€€€€€€~‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~}}||}|}}yxxzzz{{|}}ƒƒ……„†„‚‚‚‚‚€~~~~~~}}|{{{zz{}ƒ……„……„„ƒ€€~}zyxxxzz{{zz{{{{{{{{{{{{{{{||€€‚‚‚‚‚€~}|{{|€‚‚ƒƒƒ………„††…‚‚‚‚‚‚€}|{zzyywxxzzuoqw|€€}|z{{}}}|{yxtspliijnv{~~~}~ƒ‚‚~~}|~}€€€~~~~‚€zwuwy|||}}€ƒ„}~}{wsnjkqy}€€}yux{€€€€€€~‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~}}||||{{xxy{{{||}}~€‚ƒ„…„††……………„€~~~~~~~~}}|{{{zz{~‚„„„„…„ƒ‚~~|}|zxwyzzzzzzzz{{{{zz{{zzzz{{{|}€€€‚‚‚‚‚€}|{{z{~‚‚ƒƒƒ„…††††„‚‚‚€}|{zzyywwx{}xsrw|€ƒƒ€}}{||}}|~}|yvspnkjjnv{}~}~€‚€}{zyzz{|€€€~}}|~€‚‚€{wuwy|||}}~~€ƒ„|~~~~~}{wrmkmtz~€€}yux{€€€€€€~‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~}}||}|{zyyy{{{||}|~€‚‚ƒ„†…††‡†††…}}~~~~~~}}|{{{zz|‚…„„„…„ƒ‚}}|zzzywyzzzzyyzz{{{{zzzz{{zz{{z{}€€‚‚‚€}|{{zz}~‚‚ƒƒƒ„…‡‡ˆ…ƒ€€‚‚€}|{zzyywxxz|zusw|€‚‚~}|||}}|~zxtqoljlpw|~~~~}~€€~~}ywvuvwx{€€€~||{}‚„}wuwy|||}}~~€ƒ„|}~~~~~}{wrmkmu{~€€}yux{€€€€€€~ƒƒƒƒ‚‚‚‚‚‚€€€€€€€€€€~~~~}}}}}}}|~~}}|{z{z{{{}}}}~~€€‚ƒ„ƒ†‡ˆˆˆ‰ˆ„€}~~~~~~}}||{{{{{{}ƒ„„„…†„€~|zzyywwwyzzzzzz{{{{{zyzzyzzzz{{z{~€‚‚€~}}{{{{|~€‚‚ƒ„„…††…‚€€€€~~{zyyxxwwxx{{utx|}‚‚~}|||}~€€{yurommmry}~~~€€|{ywvtrrsuy~€€€~}|||}‚„ƒ}wvxz|||~~~~~€‚ƒ‚}~}{urmjmv|~€~|xux|€€€€€€~ƒƒƒƒ‚‚‚‚‚‚€€€€€€€€€€~~~~}}}}}}}|}}||||||||||}}}}~~~~€‚„…†ˆ‰‰Šˆ…€~~~~~~~}}||{{{{{|}€„„„„……‚€}{zywwwwwwwyzzzzzz{{{{zzyzzxyyzz{{z{}€‚‚€~}|{{{{|~€‚‚ƒ„„…†…„€€|}}{yyyxxwwwwyzvux{}‚‚~}|||}~€‚‚‚{wsqpootz~€}zywutsqpqty~€€€~{zz|€ƒ„€zwxz|||~~~~~‚ƒ‚€~~}zuqmknw~€~€|wux|€€€€€€~ƒƒƒƒ‚‚‚‚‚‚€€€€€€~~~~}}}}}}}|}}||}}{{{{||}}}}}}}~~€‚ƒ…‡ˆˆ‰ˆ†~}}~~~~}}||{{{{{|~‚‚„„„‚|zxwvwwwwwwwyzzzzzz{{{{yyyy{yyyzz{{z{|~€‚‚‚~}|{{{{{}€‚‚‚ƒ„…†…ƒ€}{}}{yyyxxwwvvwwwvwz}‚‚‚‚~}|||}ƒ„„}xusqrsw{~~zyxwvusqpqty~€€€€~{vxz~ƒƒ‚|xyz|||~~~~~~~‚‚~~~}yupnlpx~€~€|wux|€€€€€€~ƒƒƒƒ‚‚‚‚‚‚€€€€€€~~~~~~~~}}}}}}}|||{{||||||}}}}}}|||}|}~€ƒ…††‡‡†}}~~~~}}||{{{{{|~‚‚„„ƒ€{zyxwvwwwwwwwyzzzzzzzz{{xxxy{zyyzz{{z{|}€€‚‚~||{{{{z|€‚‚ƒ„……„~~zy}~{zyyxxwwvuvvwvvz}ƒƒ‚‚~}|||}ƒ„ƒ~xvvuuvy|€€€~yxxwuusqpquz~€€€€€~zvvx}‚‚‚|xyz|||~~~~}}~€‚‚€~~~}xtponqy~€~€|wux|€€€€€€~ƒƒƒƒ‚‚‚‚‚‚€€€€€€€€~~~~}}||||||||||||||||||||||||||}|{{}~€ƒƒƒ„………ƒ€}}~~~}}}||{{zz{}~‚ƒ„ƒƒ{xwvwwvvxxwxxyzzzzzzzy{{yyyzzzyyyyyz{{|}€‚‚‚€~}{||zz{|‚„„ƒ~~~~}v{~|zyyxxxwvuuvxwvy}‚‚‚‚~}||}~ƒ…„ƒ{yxxzz{~€€€€~yxxwuutrqruz~€€€ƒ}yutw{€ƒ‚}zyz{{{|}}}~~~€‚‚~~|yuqoosz~€€€|wux|€€€€€}ƒƒƒƒ‚‚‚‚‚‚€€€€€€€~~~~}}||||||||||||||||||||||||||}|{{{|~‚‚ƒ„„€}}~~~}}}||{{zz{}‚ƒƒ„ƒ‚~zwvvwwvvwwwxyyzzzzzzzyzzyyzyyyxxyyyz{{|}€€‚‚€~|{||{{||‚ƒƒ‚€~}}}zw||zyyxxxwwvvvyvvy}‚‚‚‚~}||}~~€ƒ……ƒ€|{zz|||€€€~€€~yxxwuutsstv{~€€€‚~yusvz‚ƒ~{yz{{{|}}}~}~€‚‚€€}yuqnot|€€|vux|€€€€€€€}ƒƒƒƒ‚‚‚‚‚‚€€€€€€€~~~~}}||||||||||||||||||||||||||}}|{||}€€ƒ„€||~~~}}}||{{zz{}‚ƒƒƒ‚€|ywvvvvvvxxwxyyzzzzzzzyyyyyyyyyxxyyyz{{|}~€‚‚‚€~|{||{{||‚ƒ‚€||}{xx‚}{yyxxxwxyxvvtvy}‚‚‚‚~}|{||}€„ƒ‚€~}}|~~€€€~€~zyxwuutsstw{€€€€{usty~ƒ|zz{{{|}}}~}|}€~yuqnou|€€|vux|€€€€€€€}ƒƒƒƒ‚‚‚‚‚‚€€€€€€€~~~~}}||||||||||||||||||||||||||}}|||}}~~~„„€{{~~~}}}||{{zz{}ƒƒƒƒ{xvvwwwxxxxwxyyzzzzzzzyyyyyyyyyxxyyyz{{|}~€€‚‚€}|{||||||‚‚{{}{yzƒ€}{yyxxxwxzyvttvy}‚‚‚‚~}|{||}€~}~€€€~€‚~|zxxvvusttw|€€€€‚‚}wttx|€ƒ}{z{{{|}}}~}{|~€€€€~|ytomou}€€zuuw|€€€€€}„„„„‚‚‚‚‚‚€€€€€€}}}}||||||||||}}}}||||||||||}}}}~~}}{}~}|{}ƒ…„~yz}~}}}}{zzz{z|ƒƒ„ƒ~zxwwwwwwwxxxxyyzzzzzzzzzzyyxxyyxxyyyyzz|}€€‚‚‚~|{|||}|}€€‚‚‚€}|z|{x{ƒ€}{yxxxwxz~zussvz}‚‚ƒƒ€}|zzz||~€€€€€€€€€€~~€€ƒ€~{yxxwvvsuuy~€€€€‚€}yutw|€‚~z{{{{z{}}~~}{y{~~|xtpnpv}~~xttz}€€€€€~|„„„„‚‚‚‚‚‚€€€€€€€}}}}||||||||||}}}}||||||||||~~~~~~}}|~~}|{|€„†‚|yz}~}}}}|{zz{|~€ƒ„„„ƒ€}zwwwwwwwwxxyxyyzzzzzzzzzzyyxxyyxxyyyyzz}~€€‚‚‚‚€~|{|||~}~€€‚‚‚€}|z|{x{ƒ€}{yxxxwx|€zuttv{}‚‚ƒƒ€}|zzz||}~€€€€€€€€€~~€€ƒ~{yxxxvwuvv{~€€€€‚€}yutw{‚€~{{{{{z{}}}~}{xy|~|xtpoqw}~}xstz}€€€€€~|„„„„‚‚‚‚‚‚€€€€}||||||||||||||||}}}}}||||}}~~~~~~~~~~}|~‚†‡‚|yz}~}}}}|{zz{}€‚„„„„ƒ€|yxwwwwwwwxxyxyyzzzzzzzzyyyyxxyyxxyyyy{{}~€‚‚ƒƒ€|{|}}~~€€}|z|{x{ƒ€}{yxxxwy}€zuttv{}‚‚ƒƒ€}|zz{|||~€€€€€~€€ƒ|yxxxvwwxw{~€€€€‚€}zvtw{~€€|{{{{z{}}}~~{wvy||wtporx~~~|wrtz}€€€€€~|„„„„‚‚‚‚‚‚€€€€€||||||||||||||{{{|}}}}{{||~~~~~~~~~~~}„ˆ‡„}yz}~}}}}|{z{{~‚…„„ƒƒ‚€|ywvwwwwwwxxyxyyzzzzzzzzzyyyxxyyxxyyyy||~€‚‚‚ƒƒ€}{|}}~€€€€€}|z|{x{ƒ€}{yxxxwy~€yutrv{}‚‚ƒƒ€}|zz{|||}€‚‚€€€€~~~€€ƒ|zyyywwxwx|€€€€€€}zvuwz~€~||{{{z{}}}~}wvy}|wtppsy~~~|vrtz}€€€€€~|ƒƒ„„‚‚‚‚€~}}||{{|||||||||||}~~~||||{||}}}}~~~~~~€…ˆˆƒ~z{}~}}}||{||}ƒ„……ƒƒ‚|wwwwwwwwwxxyxzz{{{{{{zyzyyyxxxxxxyyyyz|~€€€ƒƒƒƒ}|}|}€€€€€€‚€~|z{zv}‚ƒ€}{yxxxxz}ytrrtz}€€ƒƒ~|zzz{{|}€ƒƒ‚‚‚€€~€€ƒ|zyyyxxwxy}€€€€€€}{wuvz}€}|{{{z{}}}}xvx{}}~~~{vsopty~€€{uruz}€€€€€~{ƒƒƒƒ‚‚‚‚€~~}||||{{||||||||||}}}}}}{||}}}}~~~~~~…ˆˆƒ~{{}~}}}|||}}€ƒ…„„ƒ„„‚{wwwwwwwwwxxyxzz{{{{{{zyzzyyxxxxxxyyyy{|~€€ƒƒƒƒ‚}|~}‚€€€‚‚‚|{zyw~‚ƒ€}{yxxxy{~}xtsrtz~€€‚‚|zzz{{|}€ƒƒƒ‚€€€~€€ƒ‚|{yyzyxxx{~€€}{xvwx{~~}|{{{{|}}}€}yvx{}}~~~{wsppuz~~{uruz}€€€€€}zƒƒƒƒ‚‚‚‚€~~~||||{{{||||||||||~€€€~}}~|||}}}}~~~~~~~~„‡‡…{{}~}}}||}~€ƒ„…„…„ƒƒ~zxwwwwwwwwxxyxzz{{{{{{zyzzyyxxxxxxyyyy|}~€ƒƒƒƒ‚€~|~€ƒ„„ƒ‚‚€||yxx~‚ƒ€}{yxxxy|~|wuurtz~€€‚‚}{{{{{|}€ƒƒƒ‚€€€€€€€~€€ƒ€}|{{{zyxy{€€€€}{xwwx{|{}||{{{|}}}}€~zwx{}}~~~{wsppuz~~~~zuruz}€€€€€{xƒƒ‚‚‚‚‚‚€}|{{{{{{||||||||||}~€~}||}}}}~~~~~~~~ƒ‡‡…{{}~}}}|}}~‚„†……„„ƒƒ}yywwwwwwwwxxyxzz{{{{{{zzzzyyxxxxxxyyyy|}~‚ƒƒƒƒ‚€~}€ƒ……ƒ‚‚‚‚‚€||yxx~‚ƒ€}{yxxxy|}vutqtz~€€~{{|{{|}€ƒƒƒ‚€€€€€~€€ƒ€}|{{{zxyy{€€‚}{xwwy{{{||{||{z{}}}€{xy{}}~~~{vrqqw|~~~}zuruz}€€€€€{wƒƒ‚‚‚€€€~~~~~~}}{{{{{{||}}}}}}}}||€~}|||~~~~~~~~~~~~~ƒ†ˆ…|z|}~}|}}~…†‡†…„…‚|yxxxwwwwxxxxyyzz{{{{{{zzzzyyyyyyyyyyyz|~€‚‚‚‚‚€~€ƒ„††‚€€€|{xvv}‚„€}{zyyyyz~~xutqtz~€€‚€~}}}|z{|‚ƒƒ‚€~~~€€~~€‚{z{{{zyyz|€‚‚~|{yxxyyz{{{{{||{|{}~€€~{wy{}~~€€~{vsqrx|}~}}~~|yssu{~€€€€€€}zuƒƒ‚‚‚‚€€~~~~~~~~~~}}||||||}}}}}}}}}}||~€€€~}|||~~~~~~~~~~~~~ƒ†ˆ†‚~|}~}|}~€‚†‡‡‡†„„‚€|zxwxxwwwwxxxxyyzz{{{{{{zzzzyyyyxxxxyxyz|~‚‚‚‚‚€€„…††ƒ‚€€€‚||yvv}‚„€}{zzyyxx||wvrtz~€€‚‚~~|{{|~ƒ„ƒ‚}|~€€~~€‚|{{{{zyyz}€~}{zzzyy{z{{{{||||{}~€€~|wy{~~~€€~{vsqsx|}~}}~~|yssv|€€€€€}zuƒƒ‚‚‚‚€€~~~~~~~~~~}}}}||||||}}}}}}}}||}~~~~}|||~~~~~~~~~~~~~ƒ†‡‡ƒ€~~€}}~„†‡ˆ‡†…ƒ‚{ywvwwwwwwxxxyyyzz{{{{{{zzzzyyyywwwwyyy{|‚‚‚‚‚‚€€ƒ††††„‚€€‚‚€}|yvv|ƒ}{zzyyxxx|~zxsuz~€€ƒ‚€€}|{|~‚ƒƒ~z|~€€~~€‚}{{{{zyyz}€€€}{{{{{{{yy{{{||||{}~€€~|yz|~~€€~{wsqsx|}~~~~~}ytsw|€€€€€€}yvƒƒ‚‚‚€€~~~~~~}}}}}}}}}}}}||}}}}}}}}|||}~~~~~}|||~~~~~~~~~~~~~ƒ…‡ˆ…~~€}}€‚‡ˆˆˆ‡†…ƒ~zxwwvvwwwwxxxyyyzz{{{{{{zzzzyyyyxxxxyyz{~€‚‚‚‚‚‚€€‚„‡‡†…„ƒ€€~€€}}yvv|‚}{zzyyxwvy~|xtv|~€€‚‚‚‚‚€}|{|~‚€{z|~€€~~€‚}{{{{zyyz}€€€€€€}{||{|||zz{{{{{||{}~€€~|{{|~~€€~{wsqsx|}~~~}ytsx~~€€€€€|ywƒƒ‚‚‚‚‚€€€€€~~~~~~~~~}}}{||}}}}}}}}}}||||||}}}}||}}}}}}~~~~~~~~‚…†‡…}~~~€ƒ†‡ˆˆ‡‡…‚{xwwvvvwwxxxxyzy{zz{{{{{{zzzzyyxxyyyyyy{}€‚‚‚‚‚‚‚„…†……ƒ„€€~~€‚‚€}}vu{‚}{zzzzzyvw}}xtu{~€€‚‚„ƒƒƒ‚‚~}||}€‚}|{{€€~€‚‚€}{{{{zyy{}€€€€€€€}|{|}}~}{zz{{{zz{{z|~€€||||}}~~~{wsruy}}~~~}xttw|€€€€{xwƒƒ‚‚‚‚€€€€€~~~~~~~~~}}}{||}}}}}}}}}}||||||}}}}}}}}}}}}~~~~~~~~‚„…†…~~~~~„‡‡ˆ‰‡…ƒ|xxwwvvvwwxxxxzz{{{{{{{{{{zzzzyyxxyyyyyz|~‚‚‚‚‚‚‚„„…„…„‚‚€€~~‚‚€€wuz€‚}{zzzz||~€€~xsu{~€€‚‚„„„ƒƒ‚€}||~}{{{{€€~€‚‚€}{{{{zyy{}€€€€€€}|{|}~~}{yz{{{zz{{z|~€€||||}~~~~{wsrvy}}~~~}xttw|€€€€|xwƒƒ‚‚‚‚€€€€~~~~~~~~~}}}{||}}}}}}}}}}||||||}}}}}}}}}}}}~~~~~~~~€ƒ…††‚}}}}~‚…†‡ˆˆ†ƒ€~zwxxvvvvwwxxyyzz{{{{{{{{{{zzzzyyyyyyyyy{|~€‚‚‚‚‚‚‚‚ƒ……†……ƒ‚€€~}€€zvy‚}{zzzz|‚ƒ~ysu{~€€‚‚„…„„„‚~||~~|z{|~€€~€‚‚€}{{{{zyy|}€€€€€€}|{|}~~}{xzz{{zz{{z|~€€||||}~~€€~{wsrwz~}~~~}xttx|€€€€€|yxƒƒ‚‚‚€€€€~~~~~~~~~~}}}{||}}}}}}}}}}||||||}}}}~~}}}}}}~~~~~~~~€‚„††‚}}}}~„†‡‡‡†‚~{xwwwuvvvwwxxyyz{{{||{{{{{{zzzzyyyyyyyy{{}€€‚‚‚‚‚‚‚ƒ„†…†‡…€~}}€€€yu{€‚}{zzzz{}xru{~€€‚„…„ƒ…„||~~|z{|€€~€‚‚€}{{{{zyy|}€€€€€€}z{|}~~}{xyz{{zz{{z|~€€||||}~~~{wssw{~}~~~}xutx|€€€€€€|zx„„‚€~~~~~~~~}}~~}|||}}}}}}}}}}||||}}}}}}}}~~}}~~~~~~~~}~~~~€‚…‡„}~~~€‚„††††„|yxwwwvwwwwwxxyyzz{{{{{{{{{{zzzzzzzzyyyy|~‚‚‚€‚‚‚ƒ„†…†…„€€~~€|{yz~€}|{zzzxyyx|{vrv{€€€€€€‚……„„„‚€}}€}{{|€€€€€€‚‚€~|}}{yyz{~€€€€€~{z|}~~~}{xyz{{zz{{z|~€{{|}}|~~|wstx{}~||||wuty}€€€€~}}ƒƒ‚€€~~~~~~~~~~~~}}~~}|||}}}}}}}}}}||||}}}}}}}}~~}}~~~~~~~~~~}~~~~€‚„††ƒ~€ƒ„†††…‚{xwvwwwwwwwwxxyyzz{{{{{{{{{{zzzzzzzzyyz|}€‚‚‚‚€‚‚ƒƒ„……†…ƒ€€€€~€ƒ‚€~}||€}|{zzzxxwyyxsrv{~€€€€€‚„„ƒƒƒ€}}€€}{{|€€€€€€€‚‚€}{||{yyz{~€€€€€~{|}}~~~}{xyz{{zz{{z|~€}{{|}~|~~€}xttx|}~||||wuty~€€€€ƒƒ‚€€~~~~}}~~}|||}}}}}}}}}}}}}}}}}}}}}}~~}}~~}}~~~~~~}~~~~ƒ…†ƒ€€€„…††…ƒ|zxwvwwwwwwwwxxyyzz{{{{{{{{{{zzzzzzzzy{{~~€‚‚‚‚‚‚‚€€‚‚ƒƒƒ„…†…ƒ€€€€€€‚‚‚ƒƒ‚€~}~€€}|{zzz{{|}zxssv{}~€€€€€‚„„ƒƒƒ€}}€€}{{|€€€€€€€‚‚€}{{{{zyz{~€€€€€~}}~~~~~}{xyz{{zz{{z|~€}{{|}~|~~€}wttx|}~||||wuuy~€€€€€‚‚‚€€€€€~~~~}}~~}|||}}}}~~~~}}~~~~}}}}}}}}~~}}~~}}}}~~~~}~~~~~€ƒ……‚€‚ƒƒ…††„~|ywvvwwwwwwwwxxyyzz{{{{{{{{{{zzzzzzzzz|}~€‚ƒ‚‚‚‚‚€€€‚‚ƒƒƒ„„†…ƒ€€‚„‚‚‚‚‚‚~}€}|{zzz{{|}{xuvv{}~€€€€€‚ƒƒ‚‚‚€€}}€€}{{}~€€€€€€€‚‚€|z{{{zyz{~€€€€€~}}~~~}{xyz{{zz{{z|~€€~{{|}~}~~€}wtty|}~||||wuuz~€€€€€€€‚‚‚€€€€€~€€~~}}}}||~~~~~~~~}}~~}}}}}}}}}}~~}}}}~~~~~~~~~ƒ…†…ƒ‚‚ƒƒ„……‡‡„|yxuvvvvwwwwwwxxyyzz{{||{{{{{{{{{{zzz{{}€ƒƒƒƒ‚‚‚€‚ƒƒƒ„„„ƒ€€~€€‚„‚‚€~€~}|{{{zy|}|xvuw{~€€€€€€‚‚‚‚€€€~}||{{}~€€€€€‚ƒƒ}{{{zzyz|~€€€€€€~~~~}yxxy{{{{zz{|€~{{|~~}}‚‚€|wtvz|~}}}}~|wtv{€€€€€€‚‚‚€€€€€~€‚ƒ€€€€€€€~~~~}}}}~~~~~~~~}}}}}}}}}}}}}}~~}}}}~~~~~~~~‚„††…ƒ„„…†‡…††ƒ}yxvvvvvwwwwwwxxyyzz{{||{{{{||{{{{zzz}}‚‚ƒƒƒƒ‚‚€~~€‚‚‚‚ƒ„„‚€€~~€‚‚€‚€~|{{{yxyz{vtuy|~€€€€€€€€}||~|{{}~€€€ƒ‚€}|{{zzy{}~€€€€€€~}|yxxyzz{{{{||~€}{{|~~}}‚‚|wtuy|~}}}}~|wtv{€€€€€€‚‚‚€€€€€~€€……ƒ‚€€}}~~~~~~~~~~~~}}}}}}}}}}}}~~}}}}~~~~~~~~ƒ††…„„…‡†ˆ†……‚€~zwwvvvvwwwwwwxxyyzz{{||||||~~}}}}}}|~‚‚‚‚‚ƒƒƒ‚‚~~€€‚ƒƒ‚€~~€€€€~€€|{{{yyzz{usu{~~€€€€€€€€}{{}}{{{}~€€€€‚‚€}||{zzy{}~€€€€€€€€€€€~~|{yxxyzz{{||||~€}{{|~~}}€‚‚|vtuz|~}}}}~|xuv{€€€€€€‚‚‚€€€€€~€‚‡ˆ…„ƒƒƒƒ€€€€}}~~~~~~~~~~~~}}}}}}}}}}}}~~}}}}~~~~~~~~ƒ††……††ˆˆˆ‡……‚|yvvvvvvwwwwwwxxyyzz{{||}}}}}}~€€‚ƒƒ‚ƒƒ„ƒƒ‚‚€~}}€‚ƒ‚‚‚‚‚€}~~€€€€€€~{{{{{yyz{vuxz}~€€€€€€€€€€€€|zz}}z{|~~~€€€€}|||zzy{}~€€€€€€€€}}|{yxxyyy{{||||~~|{{|~~}}€‚‚|vtuz|~}}}}~}xuxz€€€€€€‚€€€€€€€€„ˆ‰‡†„„‚‚‚‚€€€€~~~~~~~~~~~~}}}}}}}}}}||}}}~~|~~~~~~~~~~~~~€ƒ††††‡ˆˆˆ‡†„‚€}zwuuuuuvvvwwxyyyyy{{{{}}}}~~}~€€ƒƒƒƒ„„„„ƒ‚~}{}€‚‚‚‚‚‚€€~~~~€€€€€€€~~~{z{zzyy{vvz{}~€€€€€€€€}zzy}~~}{{}~€€€€€}|{{zz{|~€€€€€€€~||{zyyyyz{|||{|€}|||~~}}€ƒƒ{wuuz}}}}~}€~yvx{~~~~€€‚}€€€€€€€€„ˆ‰Šˆ‡‡††……ƒƒ€~~~~~~~~~~}}}}}}}}}}||}}}~~|~~~~~~~~~~~~€ƒ††††‡ˆˆˆ‡†„‚{xuuuuuuvvwy{zzzyyy{}{}€‚‚‚ƒƒƒƒƒƒƒ„„……„„„„„„ƒ‚}|}€‚‚‚‚‚€‚‚€~~~~€€€€€€€€~~€‚}z{{{yyzww{{}~€€€€‚ƒ‚}zyy}~}{z}~€€€€€}|{{zz{|~€€€€€€ƒƒ‚~|{z{yyyyz{|||{|€}|||~~}}€ƒƒ{vuv{}}}}~~€~ywy|~~~~€ƒ}€€€€€€€€„ˆ‰ˆ‡ˆˆˆˆ‡‡……‚~}~~~~~~~~~}}}}}}}}}}||}}|}}~~~~~~~~~~~~~~~€€€ƒ††††‡ˆˆˆ‡†„‚yvtttttuvvy}~}|zzz|~~€€‚‚ƒ„„…†‡‡‡‡‡‡‡‡‡‡‡‡…………„‚€~|}~€€€‚‚‚‚‚~~‚‚€~~~€€€€€€~ƒ}{{{{yzxy||}~€€€ƒ„‚~{zy~€~|zz}~€€€€€}|{{zzz|~€€€€€‚‚‚ƒƒ‚~{{z{yyyyz{|||{|€€~|||~~}}€ƒƒ{utx|}}}}~~€~yxy|~~~~~€‚ƒ‚|€€€€€€€€„†‡‡†ˆˆˆˆ‰ˆ††„€}}~~~~~~~~}}}}~~~~}}||}}{}|~~~~~~~~~~~}}~~€€€ƒ††††‡ˆˆˆ‡†„‚~ytsttssuvx|ƒƒ~|{|}ƒƒ…†‡ˆˆ‡ˆ‰‰‰‰ˆˆ‰‰‰‰ˆˆ‡‡‡‡„ƒ€}~€€‚‚‚‚€}~~‚ƒ‚~~~‚‚€€€€€~ƒƒz{||{y{{}}~~€€€‚……ƒ€|z|~~{y{}~€€€€€}|{{zzz|~€€€€€ƒ„ƒƒƒƒ‚€}{zy{yyyyz{|||{|€€~|||~~}}€ƒƒ{utx|}}}}~~€~yxy|~~~~~ƒ„ƒ|€€€€€€€€„…†‡ˆˆ‰‰‰Š‰‰†ƒ€~~}}~~~}~~~~~~}}}}}}|||||}}}~~~~~~~~~~~~€€€‚ƒ„………†‡‡‡…„‚}xtstsrrsv{‡ˆ‡…‚„ƒ†‡ˆ‰Š‹‹ŠŠ‹‹‰‰ˆˆ‰‰‰‰ˆˆˆˆˆˆ…„€~}€€€‚‚€~~}‚€~~€€€‚ƒ€€~€‚ƒ}z{{{zz{{}}~€€€€ƒ†…ƒ€~~€{zz}~€€€€€€€}||z{y{}~€€€€€‚„ƒƒƒƒ‚{z{zyyxzxz||||{}~€~||}}~}}€‚ƒ€zuuy{}}}}}~‚zy{|}~€„…„|€€€€€€€€€ƒ„ƒ…‡‡ˆˆˆŠŠŠˆ†‚~}}~~~}~~~~~~}}}}}}||||}}}}~~~~~~~~~~~~€€‚ƒ………†‡‡†…„‚}xustsssuy…ˆ‰‰‡ƒ€‚…††ˆŠŠŠŠŠŠŠŠ‰‰‰‰ˆˆˆˆˆˆ‡‡ˆˆˆˆ†„‚€€€€€‚‚…ˆ„€~~€€€‚ƒ€€~€z{|}yyy{}}€‚‚‚‚‚ƒ‚€€€~{zz}~€€€€€€€}||z{z{}~€€€€€‚„ƒƒƒƒ‚~|{{zyyxzxz||||{|}€~||}}~}}€‚ƒ€zsuz|}}}}}~‚{y{|}~€ƒ„„|€€€€€€€€€‚‚……††‡‰‰‰Šˆ…€}~~~}~~~~~~}}}}}}||||}}}}~~~~~~~~~~~~€€€‚ƒƒƒ„………†„„|yvtsttw{‚‡‡‰ˆ††ƒ„†‡ˆˆ‰‰‰‰‰‰‰‰‰‡‡ˆˆ‡‡‡‡‡‡††‡‡‡‡†………ƒ‚‚€€€‚ƒ„ˆ‹‰ƒ€€€~€€‚€€€€€€€€|~zxz}}~€ƒ‚€‚‚€€~{zz}~€€€€€€€}||z{z{}~€€€€‚„ƒƒƒ‚€}{z{zyyxzxz||||z{|~||}}~}}€ƒ„€ztvz|}}}}}~‚{y{|}~€‚ƒ„|€€€€€€€€€€‚ƒƒ„„†‡‰‰ˆ‰ˆƒ€}~~~}~~~~~~}}}}}}||||}}}}~~~~~~~~~~~~€€€‚‚‚‚ƒ„„„……„ƒ€|xsstty~ƒ†ˆ‰ˆ‡†…†‡‰Š‰‰Š‰‰†‡‡‡‡††††……††††……‡‡‡‡†‡††„„„‚€€‚ƒ‡‰‹‹…€€€€€€‚€€€€}~~‚|y}}ƒƒ€€€€~{zz~~€€€€€€€}||z{z{}~€€€€‚„ƒƒ‚€}{z{zyyxzxz||||z{|~~||}}~}}€ƒ…€zuwz|}}}}}~‚{y{|}~€‚ƒ„{€€€ƒ…‡‡ˆ‰‰‡†‚~~~}|~~|~~~|||||||||}}}~~~~~~~~~~~~~~€€€‚‚„††…„…‚~xuuvz€„‡ˆˆ††……‡ˆˆˆˆˆˆ‡‡†……„„ƒƒ„„„„…………„„……‡‡‡‡‡‡†††…‚€€‚„†ˆ‡ˆ‡‡‚€~~~~~€€€€€€€~~~€€€}{|}}‚ƒ‚€€‚‚ƒ€€€€€{z{~~~€€€€€€€€}|zz{{|~€€€ƒ„„„€€}|zzzyyyyz{{{{zz|}}{||{|{~ƒytvz|}}}}}‚‚€|z|}~~~}}~€ƒ‚{€€€€‚„†‡ˆŠ‰‡„ƒ€€~~}|}}|~~~|||||||||}}}~~~~~~~~~~~~~~€€€€€ƒ………†††ƒ~yxy}‚…‡ˆˆ††„„†ˆˆˆ‡‡†…„„„„„„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„„…………††††„€€‚…‡‡„„…†€~~~~~€€€€‚€€€€~}~~€€~~}|}~€€‚‚€~~‚‚ƒ€€€€€{z|~~~€€€€€€€€}|zz{{}€€€€‚ƒ‚€€~}zzzyyyyz{{{{zz|}}{||{|{~ƒytvz|}}}}~‚‚€}{}}~~~}}~‚|€€~~€‚ƒ…ˆŠŠˆ†ƒ‚}}}||||~~~|||||||||}}}~~~~~~~~~~~~~~€€~~}}‚ƒ„„…………~}}€„†‡††……ƒ„…†††„„…ƒƒƒƒƒƒƒ„„ƒƒƒƒƒƒƒƒ„„„„„„„„„„„„††…‚ƒ…††…‚ƒ„ƒ€€~~~~~€€€€€~~€€~}|~~€}}}}}€~|}€‚ƒ€€€€€€€€~{z|~~~€€€€€€€€}|zyz{}€€€€‚‚€~}zzzyyyyz{{{{zz|}~~}{||{|{~ƒytvz|}}}}~ƒƒ€~{}}~~~}}}~€€}€€~~}€ƒˆ‰‰ˆ‡†„ƒ€~}}||||~~~|||||||||}}}~~~~~~~~~~~~~~€€~||}}~‚„„„„……„ƒ€…††……„„ƒ„„„„„„„ƒ‚‚‚‚‚‚‚‚‚ƒƒ„„„„„„„„††„ƒ…‡‡…ƒ€ƒ€€~~~~~€€€€€€~~€€~|||}}|€~}}~€~||‚ƒ€€€€€€€~{z|~~~€€€€€€€€}|zy{|}€€€€€€~~~}zzzyyyyz{{{{zz|}}}}{||{|{~ƒytvz|}}}}~‚„„{|}~~~}}}~€€}€€~~†‡ˆˆˆˆ†„ƒ€~}}}}}}}}||||||||||}}~~~~~~~~~~~~~~~~||}}~‚ƒ……‡‡…ƒƒ„„†…„„„‚‚ƒƒƒƒ‚‚‚~€€€€€€‚‚‚ƒƒ‚„„„„„„„ƒ„…†„‚€|~„€~~}€~€‚‚€€~~€€}}|{}}||€€~€‚‚€€€€€€~}zz}~~~~~€€€€€€€}}}||}~€€€€€€€€~}~~~~}zzzzyyzz{{{{{{z{~~||||{||}ƒ€ztvz|||||~€„„€~|}~~~}{{|~~€€~~~~€„…†ˆ‰Šˆ‡…‚}}}}}}}}||||||||||}}~~~~~~~~~~~~||}}€€ƒ„…†‡……††…„„„„‚ƒ‚‚‚‚€€€~~|}~‚€€€€€€€‚ƒƒƒƒƒ‚‚ƒƒƒƒ€zz~„‚€€€~~~~~~|~‚ƒƒ€€€~~~}{{||||~€€€‚„„ƒ‚‚‚€€€€€~}z{~~~~€€€€€€€}~}|}~€€€€€€€€}}|~~~~}zzzzyyzz{{{{{{z{}}||||{||}ƒ€ztvz|||||~€ƒƒ€~~~~}{z|}€€~~~~‚‚„†ˆˆ‰‰‡„}}}}}}}}||||||||||}}~~~~~~€€~~~~~~~~~€€~~~}‚„…†……†……„„„„‚‚€|||||||~€‚‚‚‚ƒ€€}}}€ƒƒ‚‚‚‚€€€|z}„„€~}}}}z{‚„„ƒ€€€€€€~||||}~}~€€€€„……„‚‚€€€}|z{~~~~€€€€€€€€€~~~€€€€€€||{}~~~}zzzzyyzz{{{{{{z{}}||||{||}ƒ€ztvz|||||~€‚‚€~~~~}{z{|~~~€€~~}}€€ƒ…†‡‰Šˆ…€}|}}}}}}||||||||||}}~~~~~~€€€€~~~~~~~~€€~~~}~ƒƒ„………†…„ƒ„„„‚€€€~}{{zz||}~ƒƒ‚‚„‚~{{{~~~€‚‚‚‚€€~z|„…€~}}}€}y{†‡ƒ€€~€‚}|}~€}}}}€~„„ƒ„ƒ€€€€€€~|{z{~~~~€€‚‚‚‚€€€€€€€€~€€€€€€€~||{|~~~}zzzzyyzz{{{{{{z{||||||{||}ƒ€ztvz|||||~€‚‚€~~~~}{yz|}}}€€€€~~~~}}€ƒ„†‰‰‰…~{|||||{{{{{zz||||||}}~~~~‚€€€€~}}~~~€€€€€€€€~ƒƒƒ„„……††„„ƒƒ„‚|{zyyyyz|}€‚‚„„ƒƒ„„‚‚€}{{{{}}~‚‚ƒƒ‚‚€}€~}~„†€}|~€ƒ‚}~…„€€€€~~}~}zz|}~~~€‚‚‚‚‚€€€€€€}{{|€€€€€€€€€€€‚‚€€€€€€€€~}{{||~~|{zyzzyyy{{{{{{{yy{{||||{||}€‚zuw{|||{}~€‚~~~~~|zxz{}~€€€€~~~~}}€ƒ…ˆ‰‰†}{||||{{{{{{{{{{{{||}}~~~~‚ƒ‚‚‚€€~}}~‚‚€€‚€€ƒƒƒ„„„„„„ƒƒƒƒ„‚~{yyxxyyz|}€‚ƒƒ„„…………„„€~|||{|}~€ƒ„„‚~‚€„…††‚|z{~‚ƒƒ~€€€€~}~~~}}z{{}}€ƒƒƒƒ‚~€€€€~{{|~€€€€€€€€~}}~‚‚‚€€€€€€€~}{{}}~}|{zyyyyyy{{{{{zzyyzz{{{{{||}€‚zuw{|||{}~€‚€~~~~|zxy{}~€€~~~~}}}~„‡‰‰†|z{{||{{{zzz{{{{{{|||}~~~~€€ƒ„………‚~~€‚‚‚‚‚‚„ƒƒƒƒ„ƒƒ„„„„ƒƒƒƒƒƒƒ~{zyxx{{|~~€ƒƒ„„„…………„„‚~}}||}~‚…„„‚~~‚‚„‚€€€{xz}€€€~}~~~€~€€~~~~~}}{{|}|‚ƒƒƒ„ƒ‚~€€|{|~€€}~}~€‚‚‚€€€€€€€~}{{~}}}|{zyxxyyy{{{zzyyyyzz{{{{{||}€‚zvx{|||{}~€‚€~~~}zxxz|}~~€€~~~~}}|}}ƒ†‰‰†|zzz||{{{zzz{{{{{{{||}~~~~ƒ„‡‡‡ƒ‚€~~€‚‚‚‚ƒƒ„„†………„„ƒƒ„„ƒƒ‚‚‚‚‚ƒ‚€}}|{{{||}‚ƒ„„„„„„„„ƒƒ‚€~~~~}~€„…†„ƒ€~}~‚ƒƒ~~€}|||€~}~~~€€€‚ƒ‚~~~}}}|}|}|~‚‚‚‚‚~€€€€}{|}~~€~{~~‚‚€€€€€€€€~}{{~~}}|{zyxxyyy{{{yyyyyyyy{{{{{||}€‚zwy{|||{}~€‚€~~}zxxy{}~~~~~~~~~~~}}}}€…ˆˆ†zyzz||{{{zzzzz{{{{{|||}}~~„…‰ˆˆ‡ƒ€€‚ƒƒ„„„„„„†…††…†„ƒ„„ƒ‚€€€ƒ‚€€}}||~€ƒ‚ƒƒ„„„„„ƒƒ‚~~~~~€„†…‚}€€€~~~}ƒƒ}}~~~}~~~}‚€}~~~|}~~~€‚‚‚€€€€€}z{|}€‚€~}|€‚‚‚€€€€€€€€€€~}||~}|{zxwwwyzz{{zzzzyyzz{{{{|{{|~€~zwy{}||{}~€‚€~~}{xvx{|~~~~~~~~~||}}}}€…ˆˆ‡€zyzz||{{{zzzzz{{{{{{||}}~~‚„‡ŠŠŠ‰„ƒƒ‚ƒƒ„„…„„„†††……†„ƒ„„‚€€‚‚ƒƒ‚ƒ€~}}‚ƒƒƒ„„„„„„ƒƒ‚~~~~~€‚ƒ…„ƒ€~}ƒƒ~~}}z}ƒƒ}{}~~}|}|}|{||}}€€€~~||}|}}~~~~~€€€‚‚€€€€€}z{}~~€‚€~~€€ƒƒ‚€€€€€€€€€€€€€~}}}€}|{zxwwwyzz{{zzzzyyzz{{{{|{{|~€~zwy{}||{}~€‚€~~}{xvx{|~~~~~~~}}||}}}}€…ˆ‰ˆ{yzz||{{{zzzzzzzzz{{||}}~~€‚…ˆŠŠ‰ˆ…„ƒƒ„„„„„„…††…††…„„…„ƒƒƒ€~|{{~‚ƒ……†…ƒ~~€ƒƒƒƒƒ„„„„„ƒ‚~~~~‚ƒ„„‚€~}~~~~}wz€}{~~||}~||{z{{{}~}}}|{{z{}}}~~‚~~~~€}z{~~~€€~€€€‚ƒƒ‚€€€€€€€€€€€€~}~~€€€~|{zxwwwyzz{{zzzzyyzz{{{{|{{|~€~zxz{}||{}~€‚€~}{xwy|}~~~~~~~||||}}}}€…‰‰ˆ{yzz||{{zzzzzzzzzzz{{|}}~~€ƒ…‰‰Šˆ†ˆ…„„ƒƒ„„††††††‡‡…„„…„ƒ‚‚€~}yxx|‚ƒ……‡ˆ„€‚„„„„„„„„„ƒ‚€~~~ƒ„„„€}}€~~~}vx}~~€}{}}}{|{{z{|€€€~}~}|{yyz|||}~~€‚€€€€}z{~€~~€€}}€€€€‚ƒƒ‚€~€€€€€€€€€€~}~~€€€~|{zxwwwyzz{{zzzzyyzz|||||{{|~€~zy{{}||{}~€‚€~}{xxy|~~~}~~~~~~~~}}||||}}„‰Š‰‚{yz{{{{{{{{{yyzzyyz{|}||~~€ƒ†‡‰ˆˆ††‡ˆˆ††‡‡††……„„†…„…………„ƒ€~}{xvuz‚ƒƒƒ…‡ˆ‡…ƒ‚‚„„„„ƒƒ…„‚‚€€~~€‚ƒ„„}{xz}~}}|~~~€}wtw|~€€}{}}|z{zzz{{{}~~}~{{{yyyyxz|}}}~€ƒ‚€~~~€€z{|€€~~€‚‚„„ƒ€}}~€€€€€€€€}||~€€}{yxxxxxz{||zzzxyyyy{{z|||||~|yx{||{{{}~€€}zwxz}~~}~~~~~~~~}}||||}}‚…ŠŒˆ{yz{{{{{zzzzzzyyzzz{{|||~~€ƒ……ˆ‡†‡†ˆ‰‰ˆˆˆˆ††……„…†…„………ƒ‚{xvtsuz‚ƒ‚‚„…‡‡†…„ƒƒƒ„„„„ƒƒ„ƒ‚‚€€€€€ƒ„„…„€|wuvz{{z{}~{|}{uty}}~~{||}|{|{zz{|||~~|||{yzy{yzxwz|}}}}~ƒƒ€~~~€€zz}~€‚‚‚‚€€‚€€ƒ„ƒ|}}~€€€€€€€€}||~€€}{yxxxxxz{{{zzzxyyyy{{{|}}||~~|yx{||{{{}€}zwx{~~~~~~~~~~~~}}||||}}„‰‹ˆ{yz{{{{{zzzzyyzzyyz{z{||~~€ƒ…………ƒ…†ˆ‰‰ˆˆ‡‡‡‡†††‡†…„………‚{xusrruz‚ƒ‚ƒ„††††……„„„„„„ƒƒ‚€€€€€€‚„„……„€{trtxxwxyzzvyzxy||||z{z|{||||zzzyz|||~}{z|{yyy{zzxx{|}~~}~‚‚€~~~€€zz}~€ƒƒ„ƒ€€€€€‚ƒ‚€~{{|~€€€€€€€€}||~€€}{yxxxxxz{{{zzzxyyyy{{|}}}||}}|yy||{{{{}€}zwx{}~~~€~~~~~~~~}}||||}}‚‡‰‰{yzz{{{{yyyyzzzzzzz{z{||~~€ƒ„„…ƒ‚…†ˆ‰‰‰‰ˆˆˆˆ††‡‡††„……„‚€}wtrqqquz‚ƒ‚€‚‚…††††…„„„„„„ƒƒ€€€€€€€€€‚ƒ„†…†ƒ~ytssvvvwxz~|utx~~{zyyx{}z{{zzzyyyyzz|}||{{{yyz|{{zz}~~}~‚‚€~~~€€zz}~„……ƒ‚€€€€€‚‚}zz|}€€€€€€€€}||~€€}{yxxxxxz{zzzzzxyyyy{{|}}}||}}|yy||{{{{}€‚€}zwx{}~~~~~~~~}}||||}}€…‰‰ƒ~{{{{zzzyyyyzzzzzzzyz{{||}~€ƒ„„‚ƒ„‡‡ˆˆˆ‰ˆˆ‡†‡†‡ˆ††††…ƒ‚xtppqqquz€ƒ„~€„†……††„„………„ƒ‚€€‚‚ƒ„…………‚|wtstvutuvy~zwxƒ€{xxwyz|{{zxyzxwyyxxz|||}{{z{|~}}|}~~}~~€€€€€€€~zz}ƒƒƒƒ€€€€€€‚„ƒ€}|{{}~€€€€~|{}~€€}{yyyyyyyzzzyyzzyyxz{||}}{zz}}{yz{|z{{{}€‚€€€~~}zwx{~~~~~~~~~~~}}||||}}~€…ˆ‰…€}||{zzzyyyyzzzzzzzyz{{||}~€€‚ƒ„ƒ„†††††ˆ‰‰ˆ‡ˆ‡ˆˆ‡††…ƒ‚€|urqqrrrvy€ƒ„€}}‚ƒ„…††……………ƒ‚€€€ƒƒ„„„……………€{vutuvussuy}~}yy}‚€}xwvx{|{{yxzz{z{zxxy||{||{{{}~}}~~~€€€€}ƒƒƒ€~€€€‚ƒ‚€}|{{}€€€€€~}|}~~~}{yyyyyyyzzzyyyyyyxz{||}}{z{}}{zz{|{{{{}€‚€~~}zwx{~~~~~~~~~~}}||||}}}„‡ˆ…‚~}||{zzyyyyzzzzzzyxyz{||}~€€‚ƒ‚‚ƒ„…………„†ˆˆ‡ˆ‡†‡‡†…„ƒ€}wsrqqrrsvxƒ„}{}ƒ„………………„‚€ƒƒƒ„„………†…„†„~ywvtuvvtstw|}||z}~}{ywvxz|{zxwz{}}}|zzy|||}||{|}~~~~~}}€€€~~~~€~‚„„„ƒ~~€~}‚ƒ‚||{|}€€€€€~~}}~}}}{yyyyyyyzzzyyxxyyxz{||}}{{{||{z{||{{{{}€~~~~}zwxz~~~~~~~~}}}}||||}}}ƒ‡‡†ƒ€~}}{zzyyyyzzzzzzxxyy{||}~€€‚‚ƒ„……„„„…‡‡†ˆ‡†††…„„ƒ€ztrrrrsssux~ƒ„‚}{{}ƒ„„„„……ƒ‚€‚‚‚ƒƒƒ……………†…„…ƒ}zwxtuuvutuwz||||~~}z{xvxy{{yvvy|~|~~{{{}{z{{|||}~~~~}~~}}€€~~}}€€€€€‚ƒ„ƒ‚~~|y}€‚ƒ‚{z||}€€€€€~~}}~~~}{yyyyyyyzzzyyxxxyxz{||}}|{{{|{z{||{{{{}€~~~~}zwxz~~~~~~~~~~~}}}}}}}|||||~…ˆ†…‚€~|{yyyyyyyyyyyyyyzzz|}}~~€€€€€ƒƒ„„ƒƒ„…‡‡†‡‡†††„ƒƒ|wsssssrstux~‚„‚{z|~€‚ƒ……†ƒ‚‚€‚‚…†……………„……………‚~zyyvtuvussuyz|}€~{{zyvvz{zxvtv|~~zz|zyzz{{}}~~~~}}|{}~~€€~}~€€‚‚‚€€|yvy€‚‚‚{zz{~€€€€€€€€~~~~}}~~|zxxxxxxzzzzyyxxwxyzz{{~~}|{|||{z{|z||{}}}~€~}}~~yxxz~~~~~~~~~~~}}}}}}}|||||~ƒ†††ƒ‚€}yyyyyyyyyyyyyyzzz|}}~~€€€‚‚‚‚‚‚…†‡‡‡‡†………„‚€|xurssssrstux}‚„‚€|z|~~‚„„…ƒ‚‚‚‚ƒ„††‡‡‡‡†„………†„‚~{{zwtuvsrsuwx|ƒƒ{yzwuvxyxwussuy{}}}{zzzyy{||}}~}}|{{{|~~}}€€‚‚€€€€€€‚€|usx‚‚‚{zz|~€€€€€€€€~~~~}}}}|zyyxxxxzzzzyyxxwxyzz{{~~}{z||{{z{|z|{{~€}|~~}}~~yxy{~~~~~~~~}}}}}}}}}}|||||}~…………„ƒ{zyyyyyyyyyyyyzzz|}}~~€€€€‚‚ƒƒ†‡‡‡‡‡…„„„‚€~{xvtsttttstuvx|„„‚~{|}}~‚‚ƒ„„„ƒ†‡ˆ‰ˆ‡‡ˆˆ‡†…„„„ƒ~{{ywtturqruvy|€ƒƒ€|yxwvtwywusqpqtvy{|}|{zyy|||}}}|{|{|}~~~}~€€€‚‚‚€€€€€€{sry‚‚‚{z{}~€€€€€€€€~~~~|||||zyyyyzz{{zzyyxxwxyzz{{~~}zy|{{{{||z|{|~{{}~}}}~~{yz|~~~~~~~~||}}}||||||||||}}‚„„††…ƒ€~{{{yyyyyyyyyyzzz|}}~~~~€€€€€€ƒƒ……†ˆ‡‡‡‡„ƒ„ƒ€~zxxvuuuuttstuvx|„„ƒ}}}~~}‚…„„…ˆ‰ŠŠ‰‡‡ˆˆ‡†…„ƒƒ€|{zywtturrstvy}ƒ‚€|yxwuuvvuttqqprtwz{||}}||}}}~~~}{|}}~~~~~}|~€~~}~€ƒ‚€€€€}€|try‚‚‚‚{z|}~€€€€€€€€~~~~{{|||zzzzzzz||zzyyxxwxyzz{{~~~zy{{{z|}|z|{}€|y{|~}}}~~{z{|~~~~~~~~}}}}}||||||||||}}~€ƒƒ…ˆ‡…ƒ}{zyyyyyyyyyyyz{{|~}~~~€€€€‚ƒ„†‡‡ˆˆ‡†…„„ƒ€~zxvvwwvvvuutuvvw|„„ƒ€}~~‚ƒ„†…‡Š‹ŒŒŠ‰‰ˆ‰‰ˆ…ƒ‚€€|{zzwttvtttvwy~€„‚{ywvtttsrrsrrtttuvz{}}~~}}~€~||}~~}}}|~||~~~~~~€‚€€€~}€€|ss{‚ƒƒ‚{z|}~€€€€€€€€~~|{|||{yyyxyy{{zyyyyxwxyzz{{~~|{z{{{{{|{{{|~‚{z|~~~~~}}~~|z{}~~~~~~~~~~}}}}|||||||||||}}}~‚‚„‡‡†…‚|zyyyyyyyyyyyz{{|}}~~~€€‚„……ˆ‰‰‰‡‡…„ƒ‚}|xwvwxyxvvuuuuvvw{ƒ…ƒ‚}~€€€€€‚„ƒ†‰‹‹‹‡‡ˆˆ‰‰ˆ…‚€€~~}{{{xvvwutuwz{|~€€}zyuuttsppoquuuvvutx{}€€€~~€~}|}~}}}{}}}~~~~~~€€€€€~|{su|‚„„‚|{|}~€€€€€€€€€€|{|||{yyyxyy{{zyyyyxwxyzz{{~~|zy{z{{{|{{{}€‚~zz}~~~~~}}}}{z|}~~~~~~~~~~}}}}|||||||||||}}|}€€‚…††‡„€}{yyyyyyyyyyyz{{{|}~€„…‡ˆˆ‰‰‰ˆ†…„‚€~|zywuvwyyywwvvvvwwx{‚„„ƒ€~~€€~||€‚…‡‡‡……‡‡‡‡††ƒ~~}}|{||zxwwtutv{|yy|}|zxtsuusnlmqwwvvwvsvz~ƒƒ‚€~€€~}||~€~~~~~}~~~~}~€€€€|z|}zuw~‚„„‚}||}~€€€€€‚‚‚~}}}|yyyxyy{{zyyyyxwxyzz{{~~|zyzz{z{|{{{}€~xy|}~~}}}}zy}~~~~~~~~~}}}}}|||||||||||}}|}}~~€„…‡ˆ„‚~{yyyyyyyyyyyz{{{|}~‚†‡ˆŠŠ‰ˆˆ‡†„ƒ€}zyvvvwwyyyxxwwwwxxy{‚ƒ…ƒ~~€|yyy{}~‚ƒƒƒ…………„„„„ƒ}}}}|{~{yvutuuvy{wvy{zxwtstttpklqvxwvxwtvz~€ƒƒ€}}||€€€€€~€€~€~|||}~~{xz}zxz€‚„„‚{z|}~€€€€€ƒ„„ƒƒƒ„€}|zzyxyy{{zyyyyxwxyzz{{~~|yxzzzz{|{{{}€|wx|}~~€€}}||zy}~~~~~~~~~~~~~~~|||||||{||||||||}}}}}}‚ƒ…‡††‚|yyxxxxwwyyyyzz{|~€‚„†‰‰Š‰ˆ‡‡†…„‚~}{yxwwwxxxxxwwwwwwwxy{~‚ƒ„ƒ‚€}wuwxxx{}~€„……ƒ€€‚…†‚€~}‚|wuuvtwxxsqvxyywustwwpiipvyxxyxwvy|~ƒƒ‚€~{|~}€€€~‚‚~~|zz|}}€||{|}|{|}{z{€‚ƒ„‚€}|}~€‚‚ƒ……„„„‚‚€€~|{yyyz{{yxxxxwwxz{zz|~{yyzzy{{{{{{|€zvy|~~}|xxy}~~~~~~~~~~~~~~~~~|||||||{||||||||}}}}}}}}€„†‡ˆƒ~zyxxxxwwyyyyzz{}€‚ƒ†ˆŠŠ‰ˆ‡†……ƒ€|{yzxxyyyyxxyyxxxxxxxyy{}ƒ„ƒƒ‚€|ustuwvxz{}‚‚~ƒ…†…ƒ‚ƒ~€„‚}xvvvtvvupnruxzwuttvwoffmtxxyyyxwwz||~ƒ‚}}z|~~€€€€€€~~~~}||{z{|}|}}{{z{||~~~|{‚ƒ…„‚~}~€‚ƒ„„„„„ƒƒƒ‚‚‚‚‚€€~{yyz{{yxxxxwwxyzzz|~{yyyyy{{{{{{|€zvy|~~}{wvy}~~~~~~~~~~~~~}}~~|||||||{||||||||}}}}}}}}}‚„‡ˆ†€|zzzzzyyyyyyzz|~ƒ„†ˆˆŠ‰ˆ†…„ƒƒ€}|{zxyxxyyyyyyyyxxxxxxxyy{}€ƒƒ„ƒ‚}{wtsrtuvwx{||}~~}€ƒƒ„†…†…ƒ€€„‚}xuvvtrrrnknsw{yvtttuoecjswxzzzyxvx{{{}{zz}‚€€€}}}~~}{zz{{|~~{zz{|||||€€}}ƒ„…„ƒ€~~~‚‚ƒƒƒƒ‚‚€€€€~{zz{{yxxyxwwxyzzz|~{yxxyyz{{{{{|€~yvy|~~}{wvy}~~~~~~~~~~~~~}}~~|||||||{||||||||}}}}}}~~~€€ƒ…ˆˆ‚~zzzzzyyyyyyzz}ƒƒ‡ˆˆ‰‰ˆ‡„ƒ‚‚€{{zyyyyyzzxxzzzzyyyyyyxzy{|€ƒƒ…„‚~|{vrpqtutvy{|{{{}‚ƒƒƒ‚ƒ……‡…ƒ€„ƒ}yvwvupopmknrw{|zutssoeckrwxz||{ywvxyw{€€~|y{}€ƒƒ€€€€~{||~|zxxz{{}}{xyz||{|}€‚€~~ƒ„†„„‚ƒ‚ƒ„„ƒ€€‚€€€‚‚‚~|{{{yxxyxwwxxyzz|~{xxwwyz{{{{{|€~xvy|~~~zwwy}~~~~~~~~~~~~~~~~~||||||{{{{||||||}}}}~~}}}~€‚†ˆ‡„}{zzzzzyyyz{|€‚…†ˆˆ‡‡‡†…‚€€}|zz{yzzzzyyyyzzzzyyyyyyz{z{{‚ƒ}wpmotuqtyy{{z|~‚„ƒƒ„ƒƒƒƒ‚€~ƒywwuupkmnkmqw}}yuttsohgkswy{||{zxwwwtx€{z{~‚€€€~{||}{xuvxz{}|xwx{|~|~~€‚€~€€„††ƒ€€€€‚„„‚ƒ‚€€€€€€ƒ‚ƒ€}{zyyywwxxwxyzyz|~|xwwwy{{{zz|}€‚~zvx|~~{vux}~~~~~~~~~~~~~~~~~||||||{{{{||}}}}}}}}~~}}~}€‚„ˆ‡…ƒ~||zzzyyyz}~„‡ˆˆ‡†…„„‚~~{{z{|{{{zzyyzzzzzzyyyyzz{{z{|€€~||zrnkoutoqwy|{{}~‚„ƒ‚„ƒƒ‚€~‚|xwvuokkollmu{ysrrtspkkmtwy{||{{xxwvtw~‚„‚}z{~‚‚€€€€~|{{}|xwwxz||yyzz{}~€€€€‚„…†ƒ€€‚‚ƒ‚‚€~}€‚‚‚|{yyywwyxwxyzyz||xwwwyz{{zz|}€‚{wy|~€€~yuvy}~~~~~~~~~~~~~~~~~||||||{{{{||}}}}}}}}~~~~~~€„‡ˆ†„‚‚€~||{{{|~€ƒ…ˆ‡‡†…„‚€}||||{{||||{{{{{zz{{zzyyyy{{{{{|€‚‚€~~xrlinsrnpux|{{}‚„ƒƒƒƒ‚‚‚€~}ywwunmipomnrwtnnpturnlotwy{||{{yxwvsuy€„ƒ€~|‚ƒ‚€€}|{{}}{|yyz{{x{}~}€€€‚€‚ƒ…‡„‚‚‚‚€€€}|~~~€€€~|zyyyyxwwxyzyz||xwvwyz{{zz|}€‚€|xz|~€€~yuvy}~~~~~~~~~~~~~~~~~||||||{{{{||}}}}}}}}~~~€ƒ†ˆ‰…„„ƒ€|||}„‡‡††…ƒ‚€~|{z{{|||}|||{{{{{{{||zzyyyyyy{{{}~ƒ„€{qjhkpropuy}}}€‚„ƒ‚ƒƒ‚‚€}€€~zxwtmlhnpnnqsplkptvsomquwyz||{{yxwvsqu|€‚‚~€ƒ„‚€€~|{{}}}|zz{{y{€~€€€‚ƒƒ…†„€€€€‚‚‚€~}|~}}~~€€€‚€|{yyyywwwxyzyz||xwvvyz{{zz|}€‚€|y||~€€~yuvy}~~~~~}}~~~~~~}}||||||{{{{||}}}}}}~~~~ƒ†ˆ††††……‚~}}‚ƒ…‡†…„ƒ}}|{{{|{}}|}||||{{{{{{{{{{yyyyzz{{|~‚„ƒ€ƒ‚|rljlmrttwz}}}‚ƒ€€}|€‚~zxvrmiglqssromllquvspnruwxz{}{zyyxvtppuz~}ƒ„ƒ€€}|}}}~~}yx{{{|€€€€€€~‚‚‚ƒ„„„‚€€€€€€‚‚‚ƒƒƒƒ}}}|{|}~~€~{yyxxvvwyyyz{}€€|xwvvxzzzzz{}€‚~z|}~~~|yvwz~}}~~~~~~~~}}||||||{{{{||}}}}~~~~~~€‚…†‡†‡‡‡‡ƒ‚€€„…‡‡†„ƒ|{{z{||}}}}|}||||{{||||||||{{{{|||||~ƒ„‚‚€€~}zqlhkmsxzz|~}~€€€€€}|„‚{xvqkhflqvvrpmlnqtvurpruwxy{|{zyxxvvrrrvz{zz}‚ƒ‚€€€~~€~~~~}yx|}~~€€€€~~‚‚‚‚ƒ‚‚€€€€€€‚ƒ„……††„‚€~}|z{|}~€€€|yxwwvvwyyy{{}€€|xvvvxzzzzz{}€‚|}}~~~|yvw|~~}}~~~~~~}}||||||{{{{||}}}}~~~~~~€‚„†††‡ˆˆ†„‚‚„…†††„‚|zz{{|}}}}}}|}||||||||||||||{{{{|||||~ƒ„„†„€}{yslhikr{}~~€€€€~~}€||…ƒ€|yvoheelrvvtqnnnqruusrtuwwwz{}|zwvwxvttvwxww|€‚‚‚€€€€€€€€€~}}}zz}€~€€€€€~~‚‚‚‚‚‚‚€€€€€€‚ƒ„††‡‡†„‚~}|zy{{|€€~zvwwvvvxxyz|}€€|xuvvxzzzzz{}€‚€|}}~~~|yvx}}}}}~~~~~~~~}}||||||{{{{||}}}}~~~~~‚…††ˆˆˆˆ†…ƒƒ……†„„ƒ€}zz{}}}~~~~}}|}||||}}||||||||{{{{||||}~‚„…‡‡„‚€zumhhjr|€€€€€€€~~}€{|…ƒ€|yuogcelsvvvspoprqsvtuuuvvvz{}yvvxxxusuxxxw{‚‚‚€€€€€‚€~}|}|{€~€€€~}~‚‚‚‚‚‚‚‚‚„„†††‡†ƒ~}|zyyx{|€{vvvvvvxxyz{}€€}xtvvxzzzzz{}€‚|~}~~~|yvy}}}}}~~~~~~}}}}||||{{z|||}}}}~~€€ƒ„…†‡ˆ‰‰‡…„…„„„‚|{zz{|~~~~~}}}}|||||||||||||||||||}}||~‚…„‡ˆ†„ƒ‚€~{slklu~€€‚~}}€~}€}z|…„‚|ytnf`emrtwwtqqrsrtuusstuttyz}}xttuwxwstwwwx|€€€€€€€€€€~~}||zz~~~€€~€€€~}||‚‚‚‚€‚‚€€€‚‚‚……†‡‡†„‚~~~|{zzyz|€|xwvvvvxxy{z}€€}xttwyzzzzz{~€~}~~~~~~~}{ywy|}~~~~~~~~~}}}}||||{{z|||}}}}~~€€‚‚…†‡‡†………„„ƒ|z{z{||~~~~~}}}}|||||||||||||||||||}}||ƒ…„ˆˆ‡…ƒ„ƒ‚}zuopw€‚}}||~€€}|~}z{€ƒƒƒ}ztnf`fnstwwutstsstuvttutqswx||wrruvywstvvwx{}~€€€€€€€€€€€|||zyyxwy|~€€€}|}‚‚‚‚€‚‚€€€„…†……†„‚€}}~}{zzxwz}‚|zxvvvvxxyzz}€€}xttwyzzzzz{€}}~~~~~~~}{ywy|~€€~~~~~~}}}}||||{{z|||}}}}~~~~~€€ƒ„†††………ƒ‚~|{{|{}}}~~~~~}}}}|||||||||||||||||||}}}}€ƒ„…‰Š‰‡…„„„}ztsw}€€‚~}{{{|~€€~|z||y{€‚ƒ„ztnfbfmsvxxwwuuttuvwvvutqruvzzwrqsuxvttvvwwz|}~€€€€€€€€€€~}}}zy{yvvuy|~}|~€‚‚‚ƒ€‚€€‚„……„„…ƒ{{|}zyyxwy{~‚~{yvvvvxxyz{~€}xttwyzzzzz{€}{|~~~~~~~}{ywy|€~~~~~~}}}}||||{{z|||}}}}~~~~~~~‚‚……„ƒƒƒ~{|{{|||}}}}~~~}}}}|||||||||||||||||||}}~~…„†‰‹Šˆ‡†„„€~yux}€‚~|zyy{}€~|z|{xz‚……‚ztnedemsuwwwwwvuutvvwxwvqruwxxuppruwttttvwxy{{}~€€€€€€€€~~}~~{{}zyvrrw|}|~‚‚‚ƒ€€€‚‚‚‚ƒ„…†…„‚€|zzz{{yyxxxz}€}yvvvvxxyz{~€|wstwyzzzzz{€|y{|~~~~~~}{ywy|€ƒƒ~~~~~~}}||||||||||{|||~~~~~~~~~~~}~‚ƒƒ‚~|{{{|}}}~~~~~~}}|||{||||||||||||||{|}}~~|†ˆˆ‰‹‹Š‡……ƒ€‚‚~yy{~~~‚~zyyzz}€€~~}{xz€…‡†€ztniefnruvvxxxutttwxyzzxtruwwvrporttrrrtuvxzyz|}€€€€€€€€}}}~}~€~}{vrot}€}~€‚ƒƒ„ƒ€€€‚‚‚ƒ„ƒƒ‚„ƒ|yyyyzxxxvvz}xvttwxxxy{~€€zustwyzyyxz}~{xz}}|||zxwy|€€ƒ…~~~~~~}}||||||||||||||~~~~~~~~~~~}}}€‚‚€}|{{{{}}}~~~~~~~}}|||{||||||||||||||{|~~~~}„‡‰…„ˆŠ‹‰†…‚‚‚ƒ…„~zy}}zzyyy{~€€}zwy€†‡†ztojfgnruvvxyxvutuwxy{|zvtvxwuponpqqportuwyzyy{}€€€€€€€~}}~€€~|wstx~€‚‚ƒƒ„‚€€€€€€‚‚‚ƒ„ƒƒ‚ƒ‚~{xxxxyxxxvvy{~€‚|xuuwxxxy{~€€zurtwyzyyxz}€}ywz}}|||zvx{ƒƒ„…~~~~~~}}||||||||||||||~~~~~~~~~~~~}|||~€€~{{z|||}}~~~~~~~~}}|||{||||||||||||||{|}}}}~€†Š†€„‹‹Š…„„ƒƒ…‡ˆ„|y{€€€€|zyyyxz}€€€~|wux†‡†ztojfgnruvwwxxuttvwxy|}ywuwxwtpnnooonorsuwyzyz{}€€€€€€~~~€€€€|yuty€‚‚‚‚ƒ€€€€€€‚‚‚ƒ„ƒƒ‚‚€~zwwvwxwxxuvy{}€‚}xuuwxxxx{~€ytqtwyzyyxz}€}xwz|~}}|yvy€……ƒ‚~~~~~~}}||||||||||||||~~~~~~~~}}~~}||{|}~~}|||{}}}}}~~~~~~~~}}|||{||||||||||||||{|}}||~ˆŠƒ|~ƒŠ‹ˆ„ƒ„‚„†Š‡{{€ƒ{zzyywxz~€€}|vsx‚†‡†‚ztokggnruvvwxwtssuxxy||ywvxyvspnnnmmlnrtuxy{yz{|~€€~~~€ƒ€€}vrt~€ƒƒƒƒƒ‚€€€€€‚‚‚ƒ„ƒƒ‚‚€}{vvvvxwxxvvxz|‚~zwuvwwwx{~€}xtqtwyzyyxz}}vvz}€~}||yx|ƒ……ƒ~~~~~~~~}}||||||||||||~~~~~~~~~~~~~~~~~}}||}|||||||}}}~~~~~~~~~~~~}}|{||||||||}}|||||||||}ˆˆ€z}…†„‚„ƒ€‚ˆŠ‚|{ƒƒ~{|{yyvwy~~~|zurw€†ˆ‡ƒ|unkhhnsstuxywsrsuxyy||zxxyxvsomnmkhkmqtuxz{{{z{|}€~€~}~€ƒ„€~yutz€‚‚‚‚‚€€ƒ‚€€€‚ƒ„‚ƒ‚‚|yuuuvvvvuvwwyz|~|ywwxxxx{€}xsrtwxyyyx{~€}uu{}~€~}|{x{‚‚‚‚ƒ‚~~~~~~~~}}||||||||||||~~~~~~~~~~~~~~~~~|}}||||||}}}}~~~~~~~~~~~~}}|{|||||||||||||||||||}„†‚}|{|ƒ€yx~‚~zz‚‰†}|{|zywwx||~{ytrw€…‡†„}umkginstuvxxvpmoqqtvyzzwwxxuromlhfgimqtvy{||{{{{|€‚ƒƒ€~}{|€‚‚€|{xw~€€€‚ƒ‚€€‚ƒ„‚ƒ‚‚€|wuttuvvvuuvwxy|€€}yvvwwwx{~€}xsqtwxyyyx{€|tu{}~€~}}|{‚ƒ‚‚‚~~~~~~~~}}||||||||||||~~~~~~~~~~~~~~~~~|}}||||||}}}}~~~~~~~~~~~~}}|{||||||||{{|||||||||}…ƒ~{}xx{}zssy|yuuz„‡ƒ~~}~~|~}zxwvyz{zwtqw€ƒ……ƒ}umkhjnstuxxwrkghkknrtwxvvwwtqokhddggkruwz{|}|{{z{€€€„…†„‚~}zxz|}~}}}zwz‚ƒ€€‚ƒ„‚ƒ‚{wtsstvvvuuvwyy{~€€{wwwwwx{~€|wrqtwyyyyx{ysu|~€~}||~ƒƒ‚€€€€~~~~~~~~}}||||||||||||~~~~~~~~~~~~~~~~~|}}||||||}}}}~~~~~~~~~~~~}}|{||||||||{{|||||||{|}ƒ€}z{wvxzxsswzvqqu~„„~}{z|}€{zxwxz{yvspw€‚„„„~vnkiinsuvwxumecceilpruuuuvwtqnjeeeigksvy{|}}|{z{|€€‚„…†„€~|wttwxy{{|~}xz€‚‚ƒƒ€€€‚ƒ„‚ƒ‚€~yvsrrsvvvuuvvxyz~€}wvvvwx{~|urqtwz{yyx{~€}wrv|~~€~|{|„ƒ‚€€~~}}}}}|}}||||||}}}}}}~~~~~~~~~~~~~~~~}}}}||||||||~~~~~~~~~~~~}}}|||||||{{zz{{{{|||||}€|z}zsttuvttvtqooov~‚|zyx{~€~|zwvvxyzwrqx€‚„…‚}wokihlruxxwsh`^^^cjoqsqrrttrqmhdddiimrx|~~~}{z{}€}{}‚„†€}ywvvxvvtvz}}yx}€‚ƒ€€€€‚‚ƒ‚‚|yvssrsuuvutuwwxz}€~ywwwwx|~~zuqptwz{yxx{}vsx}~~}{|„„‚ƒ~~~~}}}}}}}}||||||}}}}~~~~~~~~~~~~~~~~~~}}}}||||||||~~~~~~~~~~~~~~}}}|||||||{{zz{{{{||||{|}}xy{xrtvvwvxurnoonqw~|xvv{~€~}zxvvxyywrry€ƒ„†‚}vpmiimrvxxwobYWYTYfoqsqrrrqonkgeffjkmpuy|~€~}zzz}€}zz|~€ƒ†ƒ~zyywy{zxvz{|ywz‚€ƒ€€€€€€‚‚ƒ‚‚|xussrsuuvutuwwxy|~€€~{xwwwx|~~zuqqtwz{yxx{€}uty}~~~|{}„ƒ‚‚€~~}}}}}}}|||||||}}}}~~~~~~~~~~~~~~~~~~~~}}}}||||||||~~~~~~~~~~~~~~}}}|||||||{{{{{{{{{{{{z{|zwxzwruy{{}~yrnoonnt}}xvvz}€~}{xwwxyyxuuz€„……‚}wqnjimsvxvvobUPQMVdorsqppommjkihggjlnosvz}~|zyz}€~{z|}„†‚~{ywx}~{{}}|wvy}€€€ƒ€€€€€€‚‚ƒ‚‚|wtrrrsuuuttuwwxx|}€|ywvww{~~zuqqtwz{yxx{~€{vty}~~}{|€‚ƒƒ‚~~~~~~}}}}~~}|||||||}}}}~~~~~~~~~~}}~~~~~~~~}}}}||||||||~~}}~~~~~~~~~~}}}|||||||{{{{{{{{zzzzz{}zwwzxru{}}{sooonns}~xuv{}~}{yxxxyxywwz€‚ƒ…ƒ}yrmjimsvwuvqeRKKMXenstqomliigjjihfimopsuz{}}{yyz}}{|}|€ƒ‡†|ywv{~~}€~wuvx{~€~ƒ€€€€‚‚ƒ‚‚|wtqqrsuuustuwwxx{}}€}ywuvwz~~ztqqtwz{yxx{~{vty}~~|z€ƒƒƒƒ‚€}}}}}€€~~~~}}}}}}}|||||||}}|~~~~~~~~~~~~~~~~~~~~~~~~~~~~|}}}{||||||}}}}~~~~~~~~~~}|}|||||||{{zz{{{{{{{{{|}yvuzvorxy{}tqoopos|~vuv{}~}{zxwxxwwwxz‚‚„ƒ‚}tmjkosuuuvqhSKLS]hqttrpnifeegiigefmqqsuwz{{yxxz|~}|}|{|…ˆ‰„~{yvz}€}ztrsx}}~~ƒ€€€€‚ƒƒƒ‚€|vtqqrstttstvvvwwz||}€~ywuvx{~yrpquxyyyyx{{uty}}~}{~‚„„ƒƒ~}}}}~~~~}}}}}}}|||||||}}|~~~~~~~~~~~~~~~~~~~~~~~~~~~~|}}}{||||||}}}}~~~~~~~~~~}|}|||||||{{{{{{{{{{{{{|}ytswumotsvz}|tqpponr{~wuv{}~}{zxwxxwwvxz~€€‚wplmqsuvuuqkYSSZckqtsrpojgeefffefgmrrsuwyzzxwvy|}}}~}|~‚†ˆ‹Œ‰„€|wx|~€€€~zvsrvvy}ƒ€€€€€€‚ƒƒƒ‚|vsqqrstttstvvvuvy{|}ywvwx{~xrpquxyyyyx{ytty}}}||€ƒƒ„‚‚~}}}}}~~~~~~}}}}}}}|||||||}}|~~~~~~~~~~~~~~~~~~~~~~~~~~~~|}}}{||||||}}}}~~~~~~~~~~}|}|||||||{z{{{{{{{{{{{||xqorqlmrssw{ztqqqooq{yvw{|}}~}|{yxxxvvuwy|~{}€€ysopstuvttpk`ZZagmqsqonnmjhfecccdhnsttuxyzywvtw{}}}~~…ˆ†††ˆˆ†zx{|~€€~{xvspsw|€€€€€€€‚ƒ„ƒ‚~{urqqrstttstvvvvvyz|}€ywwwx{~xrpquxyyyyx{~ysuz}}}|~‚„ƒƒ‚€}}}}}}~~~~~~}}}}}}||||||||}}|~~~~~~~~~~~~~~~~~~~~~~~~~~~~|}}}{||||||}}}}~~~~~~~~~~}|}|||||||{z{{{{{{{{{{{||wokmmlmqrqu{ztqqpqoq{zww||}}~}}{zxxxuuuvwz~~z}€{urrvvuvtrojb^]dinqpnlkmomkiecab`fouuuvxyywusrtx}}}~~€…„€€‚„ˆ…}yz{~€€~}|yopsy}€€€€‚ƒ„ƒ‚~zurqqrstttstvvvvvxz||€zwvvx{~wrpquxyyyyx{€ztuz}}||€ƒƒƒ‚€~||}}}}~~~~~~~~~~~~}}}}}}}}}}~~}~}}~~~~~~~~~~~~~~~}}}||||||||}}}}}}}}~~}}||||{|}}|{||||||{{{{|}|wohhklosqosz{uprssqt{|wx||||~~||{ywvuttvww||{}€yvuvututrnieaagnpomkhgjnqolgd_[[dpvwwxxxwtonprw|~~€‚ƒ€~~}†‹ƒ}z{|}~€€€~ztpqw|~€€~~€‚„…ƒ‚}yurpqssssstuvvuuuvx{}€€|wvvx|}vqqrvxxxwxx{zttz~~|‚„„ƒƒ€~}}}||}}~~~~~~~~~~~~}}}}}}}}}}~~~~}}~~~~~~~~~~~~~~~}}}||||||||}}}}}}}}~~}}||||{|}}|||||||||||||}}woihjknsoorx{vqruusv}|xy|||||}}|{zxwsrtuvwz{{}{xvvvvwurnjgdfkppnjgddglqpnif`[\epvxxxxvsqlkorw{€€‚ƒƒ~~~}…ˆ€||}}~€€€~€‚€{wtnsu{~~€„…ƒ‚|xtrpqssssstvvvuuuvx{}€€|wvvx}|upprwxxxxxy{~~ytuz}}~ƒ„ƒ‚}|}}}||}}~~~~~~~~~~~~}}}}}}}}}}}}~~}}~~~~~~~~~~~~~~~}}}||||||||}}}}}}}}~~}}||||{|}}||||||||}}}}|}}xojjjkmpmnpv|xssvvtv|~|yz||||{|}|zywvsstuvvx||}~~|ywwvwwwtoligjnpolhcaadiopnjgb^_gqvxxxxurnihnsvz~~€‚ƒ~~~~~‚†Œ…}|~~€€€‚‚~yrqoux{|}~€„…ƒ‚|xtqpqssssstvvutttuwz|€€|wvvx}~{uposwxxxwwy{~~xtvz|}ƒƒƒƒ‚~{y{||||}}~~~~~~~~~~~~}}}}}}}}}}}}~~}}~~~~~~~~~~~~~~~}}}||||||||}}}}}}}}~~}}||||{|}}||||||||}}}}|}~xqlkjkmnlknu{zusvvtx}~}yz||||||||zxwvutstuvw{|}~}}zxxwuuwtomkjlppokf`^]`fmomjgca_hruxxxxwrkegmtvz}~}}€‚~~~~‚ˆŠ†}|~~€€€‚‚€~ysprtwwwz{……ƒ‚|xtqpqssssstvvusttuwz}€€|wvvx}}{uqoswxxxvwy{wsux|~€ƒ‚‚ƒ€}yx{||||}}~~~~}}}}}}~~}}}}}}}}}}~~~~~~~~~~~~~~~~}~~~~~~}}}|}}||||}}}}}}}}~~~~}}}}}}{{{||||||||||||||||}~zrljkkmlhhks{zwuuuvy}~}z{|}|z|{{{zywvvsrruux|~~}}}{zxxuuwtpnlmnqrpkd^YY]cjomidb^_fptvvxxuoidfovwxyzz{€~~~~€€ƒŠ““Ž‰‚~~}~€€€‚€{yqoqrrty}‚ƒƒƒ|wtrqrrrrrstvvutsstwy|{wuvy|~ztppswyxxxwy{~|urvx{‚ƒƒ‚‚{vy{{{{{||~~~~}}}}}}}}}}}}}}}}}}~~~~~~~~~~~~~~~~}~~~~~~}}}|}}||||}}}}}}}}~~~~}}}}||{{{||||||||||||||||}zrlkkmnjefiqyzywuvwy}~}{{|}|z|z{zzyxvvtsrttvz€}}}|{xxyyxuollnpqqokd\VW]djmkid`]`fortuwwtmgbemtvxzyzz}}}}~€€…””ŽŠ„€~}~€‚‚ƒƒ€€€~ztqqsv{€‚ƒƒ‚{wsqprrrrrstvvutsstwz}{vtvy|~ztppswyxxwwy{~{vsvx|€ƒƒƒ‚‚yuy{{{{{zz~~~~}}}}}}}}}}}}}}}}}}~~~~~~~~~~~~~~~~}~~~~~~}}}|}}||||}}}}}}}}~~~~}}}}||{{{||||||||||||||||}zrljlllgdfjqy|zxwxxz}}|||{||z{zzz{yywvussstty€}}}|{yyyyyvnjmoqqqokcZVW^figecba`bgnqrtuurlfadkruwzz{|{yy{}‚‚…Š‹‰„€~}~€‚‚€€€yvux{~€€‚ƒƒ€{wrpoqrrrrstvvutsstw{~zusuy|~ytppswyxxwwy|~zvtux~‚ƒ‚|wuy{{{{{zz~~~~}}}}}}||}}}}}}}}}}~~~~~~~~~~~~~~~~}~~~~~~}}}|}}||||}}}}}}}}~~~~}}}}{{{{{||||||||||||||||}zskhklkfdfjqy~zzxyyz}}|||{||zzy{{|{zywwsrssty€~}}|{yyyyyunjmppqqokbZVX_ed`^^_abdhmpqsttqjebdjquwzz{}|yuw{ƒ…ˆŒŒ‰ˆ„€~}€‚ƒ‚‚€€€€{yxz|~~€‚ƒƒ€{vqpoprrrrstvvutsstw{~zusuy|}ytppswyxxwwy{}zvsux€ƒƒ„€zsty{{{{{{{}}~~}}}}||}}}}}}}}||}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}{{||}}}}}}~~}~~~}}||||zz{{|||}}}||||}}}}}~~{rkhkmkedgmr{€}{yzzz|}|{{|||{{zzyzzzzywtsrrsw~€€~}|||yxyyxunklopppmibZWYbc_XYZ\^cfjmpqrstqkgcciouwz{|}|yvuy~€‚††‡ˆ‡†ƒ€~}€‚ƒ€€€€€~|}}~~‚ƒ‚zupoopqqssstvvusssrv{~€€{vsuz}|wrnpsxyyyvvy{~ystv|‚ƒ~wrty{{{zzz|}}~~}}}}||}}}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}||||}}}}}}}}}~~~}}||||{{{{|||}}}}}}}~~}}~~~|qlijkiedips|€|{zzz|}|{{||||{zzy{{zyywtrrrsv}€€~}|{wwxxxuplloooonic]Z_ie[QTTV\ehknpqrrrpjfbdiotvyz|~}}|{}~€ƒ……‡ˆ†ƒ~~ƒ‚~€€~~~~~‚€~yupnopqqssstvvusssrw|€zusuy||wroqtxxxxvvy{}yssw~ƒƒ€{sruz|{{zzz}}}}}}}}}||}}}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}||||}}}}}}}}}}||||{{{{|||}}}}}}}~~}}~~}rkijkieeksu|}|||z|}|{{||||{{zz{{zyywsqqrsv{€€~~{vuvwxupmlkklmmke^^gnjZLOOPXbgjlmpqrqoiebcimrvz{|}}€€‚ƒ††‡‡‡‚~ƒ‚~}~~~~~~‚}ytpoopqqssstuuussstx|€ytsuy||wqpqtwwwwvvy{|wstx€‚‚yrrv{}{{zzz~}}||}}}}||}}}}}}}}~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}{{{{}}||}}}}}}||||||{{|||}}}}}}}~~}}~~€}tmhikjefltw|€}}}z|}|{{||||{{zz{z|{ywtppqsu{€‚€€€€|vttuxuqmjfejmolg`akok[IJJMS_gighnpqqnhcachkpw{||{|~€€‚ƒ……‡‡ˆˆ‡ƒ€~~~„‚~}}~~~~~~~€€€}ytonopqqssstttussstx}€€ytsux{|wppruwwwwvvy{{vsu{‚€~xrrx|}{{zzz€}}}}}}}}||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}||||||}}}}}}}}}}||||||||||}}}}}}}}~~~~~}tkhhijfhotx|~~}~|||{{{{|}}{{{zz{||zxrpoorsx~‚}wrstvwsoiecfjlkhcfjlh\PJGHP[egcbiopolgc`adhmv{|{{}ƒ„…‡ˆ‰‰‰Š‡ƒ€€€~}~ƒ‚~}}}~€€~~~~~~€€~|wsonoppqssstuuussstw}~xttvy{{vopruwwvvvvy{{wsv}‚€{vrrw{{zyz{{€}}}}~~}}||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}|||||}}}}}}}}}}}||||||||||}}}}}}}}~~~~~wliiijilpu{~€~}}}~~}|{{{|~~|||{{{{{xvqomorrv}€}xrrruvtohccfhhfhfikmi_YNKJPYec]]emnokfb_^aelw||{{|‚…††‡ˆˆ‰‰‰ˆƒ€~~ƒ‚}||~€~~~~~~~~€~|wrpmmnpqssstuuussstw}~xttvy{zuopruwwvvvvy{{vtxƒ‚€}xusrv{{zyz{|€}}}}}}}}||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}|||||}}}}}}}}}}}||||||||||}}}}~~~~~~~~‚‚{oijkjnptx}~}||}€~|{{{}~||{{{yzzwtpnmnqqtz€~xrqqtvsohccghg_adjlmhaXPNLOXfaWXajmnkfb_]_blu|}{{}€ƒ…†††‡ˆˆŠ‰Š…‚€€ƒ‚||}~€~~~~~~~~€~|xspmlmpqssstuuussstw}~wstvy{ztopsvwwvvvvy{{wt{ƒ‚‚{wurrw{{zyz{~‚}}}}||}}||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}|||||}}}}}}}}}}}||||||||||}}}}~~~~~~~~ƒ„~rmkkkpswz}~~|||}€}|{{{}~}}{zzyyzvspnlmoqrx€~}xrpqsusogcfhie[Z_immhbVRNNQZgbUT^gmnjeb^]`bmu}}|{}ƒ…†‡††‡ˆˆˆ‰†ƒ€‚€€ƒ‚€}}~~€~~~~~~~~€}{xronmnpqssstuuussstw}~vstvy{ytopsvwwvvvvy{{wv}ƒƒƒ‚€}zwtqsx{{zyz{ƒ||||||||||||}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}||||||||||||}}}}}}{{}}}}~~}}~~~~}}~~†‡uomlnuwy{~}{|}}~‚ƒ~|{{|}}}}|{{zywtrqmjkoqqv|€}{vqnorsroheegkg\UYdljg`VSTV[ah`VPZekkhb`^\`dmv||{|„…†……†‡ˆ‰Šˆ†ƒ‚„ƒ€€ƒƒ}}}~~~~~~~~~€‚€}{wsollmprrrssttssrrux~€|vssuz{xsopruwvuuuvx{ywz€„„‚zwvrosyzzzyz|€„||||||||||||}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}||||||||||||}}}}}}||}}}}~~~~~~~~}}ƒ‡†€vrpnpwy{}~|}~~€ƒ„ƒ€}z{}}}}}|{{yxvsqoljknppsx|}}zxspopqqqogceini]UW^gfe^WV[^`eh`XOVcig^ZY[\_dnu{~|{{~€‚ƒ„ƒ„„†ˆ‰Š‰ˆ…ƒ„„ƒ€€‚ƒ€~}}~~~}}~~~~€}{wsolkloqrrssttssrrvz~€|vssuy{wrnoruvvuuuvx{xx|‚ƒƒ}xuusqw{{{{{{~„||||||||||||}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}||||||||||||}}||}}~~}}}}~~~~~~~~}}~…ˆƒ}xuustx{|~}|}~€ƒ††„€|}}}}}}|{zyxvsqnmkkmopquxxywvqpnnppqogbelpl_VWYbcd^XY_`_ae^ZPS`hcVTV\\_dnty~}|{}~‚ƒ‚ƒƒ…ˆ‰Š‰‰…„„„ƒ€€‚ƒ€}}}~~~}}}}~~€}{wsolkloqrrssttssrrvz€{vssuyzvqnoruvvutuvxzwz‚‚€{xwywv{~~~~~}€‚„||||||||||||}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}||||||||||||}}{{}}}}~~~~~~~~~~}}}ƒˆˆ‚}zwwuvx{~}{|~~€€…‡ˆ†„~~|}}}}|{zyyvsqnmllmopqruuutsqpmnppqofbelombVUX_bb^\\``\]`\YPS^hcTOU\^`dntx~|{|}~€€‚„ˆ‰‰‰ˆ†…ƒ„„€€|}}~~~||||~~€€~}{wsoljknprrssttssrrvz{vssuxyvqmnruvvutuvxxw|‚‚€zxwz{|€€€€€‚„…||||||||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}{{||||}}||||||||}}}}}}}}~~~~~~~~}||€…ˆ†~||yyy|~~}{|~€‚ƒ†Š‰†ƒ|}|~}||zyxvspnnnmklorsrrqpqsqmknopogbeljidXQV\bca_ab_[X[WSPT^fcSJOZ\`emsx~~}||}|~€‚„†ˆŠ‰‡††„ƒ‚‚€}{}~~}||||}~€~{yvsojklopqrtttrssqrvzzsrsuyyupnoruuttstvxxy}‚‚‚€~{{~‚ƒ‚‚‚‚ƒƒ„„||||||||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}||||||||||||||||}}}}}}}}~~~~~~~~~}}ˆ‡†‚€€€}{z|~~}{}€ƒ†ŠŠ‡…~}}~}||zyxvromnnnjknqrrrpqoqtollnnmhdfhe``ZRU[ab`_ac`[WUSOQV]cbTKNY]ahoty}~~~}~}~€‚ƒ†‡‰ˆ†††„‚‚„ƒ€~~|}~~}||||}~€€~{yvsojjkopqrtttrssqrvz€~zsrsuxxtonpruuttsuvwy{‚‚‚‚‚ƒ„…„„„„„„„„||||||||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}||||||||||||||}}}}}}}}~~~~~~~~~~ƒ‰ˆ†„‚‚€€~z}~}|~€€‚…ŠŠ‰†„‚~~}||zyxvromlmmkjnprrssqnorqlklnmieghc][[TWZa`^]`ee_ZWTOSW]baVLOX_djquz}}~~}~€€€‚ƒ……‡‡††‡„‚‚„ƒ€~~~~}~}||||}~€€}zxvsojjknoqrtttrssrsw{€€~ysrsuwwtonpruuttsuvvz~‚‚‚‚ƒƒ„„ƒ„„…„„„„„„„„||||||||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}{{||||{{||||||||}}}}}}}}~~~~~~~~~~…ˆ†„ƒ‚‚€}z}~}|~€‚‚ƒ„‰‰Š‡†„~}||zywvrnljllkimprstuqnppqjjkpniegic[YZVW]b`\W\gkfaYTQWZ^baWMOX_fmtvy}}~~}~€€‚‚ƒ„„…††††„……€€}~}||||}~€}zwvsojiknoqrtttrsssuy}€€~ysrsuxwsonpruutsstuwz€‚€‚‚‚ƒƒ……„…„„„…„„„„„„„„||}}||||}}}}~~~~~~}}~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}{{||{{{{zz||||}}}}}}}}~~~~~~~~~~~~‡‡†„‚‚€€{z~€~}~€€‚ƒƒƒ„‰‰Š‡‡…‚~~|{zxwtrnjklljjlorrturmonkhmnonkghicYY[Y\`a^VPZinjbZVX]]_cdZOR[cjpvy{}}~~~~€€€€‚‚‚‚‚‚…‡ˆ†…‚ƒ††‚~}~~}|||||~€€€|{xurniijnpqrtttrrrruy}€€~yrqsuwvqnnpsutsstsvy~‚‚‚‚„„…„†„ƒ„„ƒ‚ƒƒƒƒƒƒƒ||}}||||}}}}~~~~~~}}~~}}}}}}~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~}}||||{{{{{{||||}}}}}}}}~~~~~~~~~~~€ƒˆ‡…„‚‚€€zz€~~~~‚ƒ„‚„‡‡‡‰‡…‚}}zxwtqmjjklkjkorrttrmonikoopoliijd[Y\]_b`[QP\ioiaZZ`gdcef]UY^gntwz|}}~~~~€‚ƒ‚‚‚‚‚…†ˆ‡†ƒ„††‚~~~}|||||~€€€€~{zwurniijnpqrtttrrrruy}€€~xrqsuwtpnnpsutssssuz€ƒ‚‚„………„…„‚‚ƒ‚ƒ‚ƒƒ}}}}||||}}}}~~~~~~}}}}}}}}}}~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~}}~~||{{{{}}||||}}}}}}}}~~~~~~~~~~€‚…ˆ…„ƒ‚‚€€zz€~}}~~€‚‚‚ƒ…††ˆ‰†‚€}|zxvsqmjhjjkjkoqrtsqnmkkoqpppljjkf]Z\`ac`WNT_koja\]dmlghg`Y\bjqwyz|}}}}€‚‚ƒƒ„‚‚„„†‡†„„†‡„€€~~~}|||||~€€~{zwurniijnpqrtttrrrsvz~€}wrqsuvtpmnpsutsrstu{€„„„…………ƒƒ‚€~}‚‚‚ƒƒ~~}}||||}}}}~~~~~~}}||}}}}}}~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~}}}}||{{{{||||||}}}}}}}}~~~~~~~~~~€„…‡„ƒ‚‚‚€€z{€~||~~~~ƒ…„‡‰†‚~|zxvspljgiillknqrttqmmknrrrppljkle^\^abb]QLWdmojd__eoojkha[_clrwyz|}}||‚ƒ„ƒ„‚‚‚ƒ„†ˆˆ„„…‰†€€~~~}|||||~€€}zyvurniijnpqrtttrrrtwz€}vrqsuvtpmmpsussrsuw|€‚‚„„„„……„‚€~|~|{{€‚ƒ‚‚‚}}}}|||||}}}~~~~~~}}}}}}}}}}~~}}}}~~~~~~~~~~~~~~~~}}~~~~}}||{{|{{||||||||}~~}}~~~~~~~~~~~ƒ†‡…ƒ‚ƒ‚€}yy||{|zzz}~~‚†‡†ƒ‚‚~~{zwqnmighjmnmnqrttrnlmqrppopljkke]\`deaYNQ\fnnjfcafooiji`Y\dmtxzz|}}}}~€„ƒ……ƒ‚‚‚ƒ…Š‹ˆ„†ˆˆƒ€}~}||{{{~~|zywvsokjknoqrtttrrrsw{€€|uqqsuvromnqsutsssvyƒƒ„„„„„„„€{y{||y{€‚‚€}}}}|||||}}}~~~~~~}}}}}}}}}}~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~}}||{{|{{||||||||}~~}}~~~~~~~~~~~€„ˆˆ…ƒ‚ƒ‚€}yz€}}{{zyyyz{}~€‚ˆ‡ƒƒ‚€}}|{wqnmjhikopporstttqnorqpoopnlnkbZ]bfe_VPV`hmligdbgpmffhaXZcnvxzz|}}}}~~€‚ƒƒ€€ƒ„ˆ‰‰…†ˆ‰…€~}~}||{{{~~{zxwvspljknoqrtttrrrsx|€€{uqqsuvqomnrsttsssv{‚ƒƒ„„„ƒƒƒ}yuvxz{y|€‚ƒ€€}}}}|||||}}}~~~~~~}}}}}}}}}}}}||||~~~~~~~~~~~~~~~~~~~~~~}}||{{|{{||||||||}~~~~~~~~~~~~~~…ˆ‡„ƒ‚ƒ‚€{{}|zyxwwwwy{||††„„‚~}}{xqnmkiikoqsqrssstsqrsqqqqppnol_Y]dhe]UR\fknligebhpkcbfb[\dpvy{z|}}}}~~~~€€€‚ƒ„‡‰‡†‡ˆ†ƒ~~}||{{{~~}{yxvvspljknoqrtttrrrty|€zuqqsuupmlnrsttsrrx~‚ƒƒƒƒƒ„„~|wrrtwz{y|€ƒ‚~~}}}}|||||}}}~~~~~~}}}}}}}}}}||||||~~~~~~~~}}~~~~~~~~~~~~}}||{{|{{||||||||}~~~~~~~~~~~~€€‡‰‡„ƒ‚ƒ‚€|{€~}|zxxwvwwy{|}‚„……‚~}}{xqnmkijkoqssuttsusstspqrsrqpol`Z_fhe]TWahnpmigedioibaec^`gpvy{z|}}}}~}}{||~€€€€€‚ƒ…ˆ‰†‡‡†„ƒ€~}||{{{~~|zxwvvspljknoqrtttrrruy|€zuqqsuupllnrstsrrr{€ƒ„ƒƒƒƒ„‚{vsnnswz{y}‚„‚~||}}}}||}~~~~~~~~~~~~~}}}}||||}}}}~~~~~~~~~~~~~~~~~~~~~}}}||||||||}}}}}}}~~~~~~~~~~~~~„ˆ‡…ƒƒ€ƒ‚€€~~€~|~|xxxxvvuw{||‚„…ƒ‚}|||yrnmkkklpruuvtttttuvtqrtsrrqpi`[ajmd[U[dimnkgddglmicdfcadjquy||}~~~~~}||{|~€€€€‚ƒ…††††††„‚€~~}{zzz{}~}{zxxwvtqmklmoqsssrrrruz}yspqrttplloqtssrqt|€ƒƒ‚‚‚‚~xtqpmnrxyz{„}|}}}}}}}|~~~~~~~~~~~~~~}}}}||||}}}}~~~~~~~~~~~~~~~~~~~~~}}}||||||||}}}}}}~~~~~~~~~~~~~~€ƒ†ˆ†„ƒ‚€ƒ‚‚€€€}|}zxxxxvvuwz{|„…„‚}|||zrnmmmlmqswwvtuuttvusrruusrqpi_\dmne[X_fkmmjgdehllidfgecglqv{|}}~~~~~|||z|~€€€€€€ƒ…†††‡ˆ†ƒ~€}|{zz{}~~}{zxxwvtqnllmoqsssrrrrvz}€~wrpqrttpllortssrrw~‚ƒƒ‚~xroommnryyz|€ƒ€~|z}}}}}}}}~~~~~~~~~~~~~~}}}}||||}}}}~~~~~~~~~~~~~~~~~~~~}}}}||||||||}}}}}}~~~~~~~~~~~~~~€€„‡‡…ƒ‚€€ƒ‚€‚€}{|zxxxxvvuvz|}~ƒ…„‚}|}|zrnmoonortxxwvvvttvtsrsuusrqpj_^fnog^]dilnmjfefikkhhhhfeimquz|}}~~~~~||{z|~€€€€€€€€ƒ„……ˆ‰‡„€~€~|{zz{}~~~|zxxwvuqnlmnpqsssrrrrvz~€~wrpqrtsommpstssrsyƒƒƒ‚‚~xtponmlmsyz{}€ƒ‚~{z~~}}}}}}~~~~~~~~~~~~~~}}}}||||}}}}~~~~~~~~~~~~~~~~~~~~}}}}||||||||}}}}}}~~~~~~~~~~~~~~€…‡†…ƒ‚€€ƒƒ€€‚~|{{yxxxxvvtvy{|~~ƒ„„‚}|{{{snloqqrruyyxwvvttusrrtvusrqqj``gnqiccfkmnlifegiiigjjhfgjmqvy{}}~~~}}|{{z{~€€€€€€€ƒƒ„„‡‰‡„}~}{zz{}~~~|zxxwwuqnlmnpqsssrrrrvz~€~uppqrtronnpstsrqu{‚‚ƒƒƒƒztqqponlmtzz{~€‚‚}zy~~~~}}|~~~~~~~~~~~~~~~}}}}||||}}}}}}}}}}~~~~~~~~~~~~}}}}}}}}||}}}}}}}}~~~~~~~~~~~ƒ‡ˆ…‚‚‚‚€‚~|z{yxxxxvvuvxz|}}‚„ƒƒ€}z{|ytomprstuw{{zxwuttvtrruuttrqrmedjoolghknonigfeghggimkigjloqvz||}~}~~}}{{z{}~~€€€€€€‚‚„†‡†„}~€}{{{{|~}|{zxxwvupnmmoqqrssrrrtx|€~vqpqstqnlmpsssrrx‚ƒƒ€ytrponnmlnswy{‚~{zy~~~~}}|~~~~~~~~~~~~~~~}}}}||||}}}}}}}}}}}}}}~~~~~~~~~~~~}}}}}}}}||}}}}}}}}~~~~~~~~~~€‚…ˆ‡„‚€€€‚‚€‚€}{yzxwwxxvvvvvutu{‚…„ƒ€}z{{zvompsuwx{{{|zwvuuvuttttsrrqrnhgmponjkmpokeedcdfccjmljiknpswz||}}}~}{{{zy{}~~~~~€€‚„………„~~~|{{{|~}|{zxxwvuqnmmoqqrssrrruz}€€|tqpqstqmlmpsssrsz€ƒƒƒ}ztqqponlklotyy{‚}zyy~~~~~}|~~~~~~~~~~~~~~~}}}}||||}}}}}}}}}}}}}}~~~~~~~~~~~~}}}}}}}}||}}}}}}}}~~~~~~~~~~€„†ˆ†ƒ‚‚€‚ƒ|ywxxwwxxvwwvtomny‚…„ƒ€}zyzzwqnqsuwz}}}}|xwvvvuvvssqppoqojjoppmlmpqohbacbabadjmlkkmoqux{||}|}}}zzzzyz}~~~}}€€‚ƒ„„„„ƒ~€}{{{|}|{{zxxwvuqnmnpqqrssrrruz}€{tqpqstpllmpsssru{ƒƒƒzsppooonkjmpvz{}€‚‚€}zxx~~~~~~}~~~~~~~~~~~~~~~}}}}||||}}}}}}}}}}||||~~~~~~~~}}}}}}}}}}}}||}}}}}}}}~~~~~~~~~~€…‡ˆ†ƒ€€‚‚€‚„{xvwxvvxxwxxxslijt…„ƒ€}zyzzytqptuw{}€~}zywwvvuussqponpolmpqqmnoqqpf_`aaba_djnnkloqrux{|}}}}}|zzzzyz}~~~~~€€€‚ƒƒ„„ƒ€€}{{{||{{{zxxwvuqnmnpqqrssrrruz}€~ztqpqstollmpssssw~‚ƒƒƒxqooooonlkmqxz|~‚‚|yww~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}~~~}}}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~€ƒ‡‰†ƒ~~‚ƒ‚~ƒ…|ywwvvvwyxyxxqmjjr€†„‚}yzz{yvsqtvy|~yxwvvutsssponloqoorrommprqmd__aaa`_flonmnpprvx|}}}}}|{{{zzz{}~~~~}~€€€€‚………~€}{zz{||{zzzxxwvuromnqqrssrqqruz}€€~ysqpqqqnkkmqsstu{€ƒ„„ƒtpoooomlkjntwy|€~yxww~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}~~~}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~~~€‚…ˆˆ†ƒ€~}€‚„‚€€ƒ…€|ywwvvvwyzyxwqmjjq†…‚|||{yxvsux{|~€zyxwvvttrronmkorqqsromnpsqnd__acb`djlommnpqsvz|}}}}}|{{{zz{{}~~~~}~€€€€„……‚~~}{zz{||{zzzxxwvuromnqqrssrqqruz}}xtppqqqnkknqssuw}‚ƒ„„‚|ronnnnmlkjnuwy|€~yxww~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~~~€ƒ‡ˆ†„„€~~ƒƒ‚€€„„€{ywwvvvwxyxwuqmllp}……‚}{zwuvwz|~€|{zxvvutrqnmmmpssttspnorsrnfbbcdccglnplloqrtwz|~}}}}|{{{zz{{}~~~~}~€€€‚„…‚}~|zzz{||{zzzxxwvuromnqqrssrqqsvz}~~|wtqpqqqnkknqssvz€„„ƒ„‚xponnnnmlkjnuxz}€€~yxww~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~€„‰ˆ…ƒ„~}€‚ƒƒ€…ƒ{ywwvvvwxyvuuromnoz„†‚€€€~}{zwwwz{~€|{zywwvttpmmnppsuvvupoprtsmhddcddegkopmloqrtx{}}}}}}|{{{zz{{}~~~~}~€€~~~€‚ƒ‚~~|yzz{||{zzzxxwvuromnqqrssrqqswz}~~|wsppqqqnklorrrx}ƒ„„ƒ„€unmoommmlkjnuy{~‚‚€~yxww~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~}}}}}}}}}}}}}}}}|~~~~~~~~~}}~~~~‚†‡†…ƒ‚}}ƒ„ƒ€„{ywwvvuwxxuttsqpmn{…†ƒ€€€€~|zxwxy{}~€~{yyyxvuutqnnprstvvwvsqprutpidddeeehmpomnprsuz|}}}}|||{zzzzz{}~~}~~€€€€€€€~~~~~~{yxx{||{zzyyxwwtqonnpqssrqpqtx{}|||vrppppomllorsuz‚ƒ„ƒ„~rnlnommlkkkntxz€€{yxvv~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~}}}}}}}}}}}}}}}}|~~~~~~~~~}}~~~~€„‡ˆ…„‚~|~€‚ƒ„ƒ„|ywwvvvwwvtrsvvsop{…†ƒ~}|zxyyyyy|~~|zzyxwvvurpprtutvvwwusrsuvrkfeeeefioqpnpqstv{|}}}}|||{zzzzz{}~~}~~€€€€€€€~~~|{yy{||{zzyyxwvtqonnpqssrqpqty|}||zuqppppomllortw|ƒ„„ƒ‚{ollmnmmlkkkntx{€€{xwvv~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~}}}}}}}}}}}}}}}}|~~~~~~~~~}}~~~~…‰‡…ƒ}|~€‚ƒ„„„€}ywwvvwvvurrrxzuss}…†„‚€€~{zzzzyzz|}~}|{zyxwvwvsrrsuvvwwyyxvstvvsnjhgfegipqppprtuw{|}}}}|||{zzzzz{}~~}~~~€€€€€€€€€€€}~|zz{||{zzyyxwutqonnpqssrqpqty|}||ytpopponmlmpstw|‚ƒ„„ƒwlllmnmmlkkkovz|€€~zxvvv~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~}}}}}}}}}}}}}}}}|~~~~~~~~~}}~~~~†‰†„ƒ€}{~‚„„ƒ€~ƒ„€}ywwvvwvvtqqrz}ywx~…†„‚‚‚€€~}{zzy{{{{}~}|}|{yxxwwwuttuvxxxy{zzwstwwtpkjiffgipqpqqruuw|||}}}|||{zzzzz{}~~}~~}~€€€€€€€€€~}|{zz{||{zzyyxwutronnpqssrqpqty|}||xspoppommlnpsvy~ƒ„„„‚~tlkmmnmmlkkjov{~€‚€~zwvvv~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~}}}}}}||}}~~}~~~}}~~~~…ˆˆ…„€}|}‚„„„‚~‚ƒ€}xwwvwwvuropsy}{|}€…‡†‚‚‚€~}|zzyzz|||}~}~~|{zyxxxvuvvvwwyy{|}{wvvwwtomjhgikqqqqsvvwz||}}}}}|{z{zzzz{|~~~~€€€€€€}}{zzz|{{z{{zxvtttsqoopqrrrrorv{}}|zuroooonmlmnprv{€‚ƒ„‚€{pllmmmlkkjilqx{€€}yuutv~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~}}}}}}||}}~~~~~~~~}}~~~†ˆ‡…ƒ€~{|~€‚„…ƒ‚~‚‚€~ywxwxwutrpoqwz|~€ƒ‡†‚€€€€}{z{{{{|||}~~~~|{zzyxxwwwwwxxzz|}}|xwvwwvspmkijmrrsruwwy{||}}}}}|{z{zzzz{|~~~€€€€€€~~{{|{{{}|{z{{zxvtttsrpopqrrrrprw{|||xtqoooonmlmnprx}‚ƒ„‚~vnllmmmlkkjilsx||xuutv~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~}}}}}}||}}~~~~~~~~}}~~‚‡ˆ‡…ƒ|{~€‚„…ƒ~}€‚€|xxxxxvsspmotw}€€††ƒ€€€€~{z{{|||||}~~~~|{zzyxyxxxwxxxz{|}}|yxvwwwurpnlmnsttuvyyy|||}}}}}|{z{zz{z{|~~~~~€€€€€€}|xz{}}{|||{{{zxvtsssrpopqrrrrrtx{}|zwrpoooonmlmnpty‚ƒƒƒ|smllmmmlkkjjmsx|€‚~{vuutv~~~~~~~~~~~~}}~~~~~~}}}}}}}}}}}}}}}}~~~~}}}}}}}}}}~~~~~~~~}}}}}}||}}~~~~~~~~}}~~ƒˆˆ†…‚~{{~€‚„…ƒ~}€ƒ~€{xxyyyvtqomnrx|€€ƒ†„€€€}{{{|||||}~~~~|{zzyxyyyxwxxxzz|}}|{zvwwxvsrqppqtuuwx{{z}||}}}}}|{{{z{{z{|~~~~~€€€€€~|zuvz~~||}|{zzzxvtrrsrpopqrrrrrux}{{zuqooooonmlmnpu|‚ƒ„ƒyqmklmmmlkkjjmsy|€‚‚}zuuutv~~~~~~~~~~||~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~}}}}}}}}}}}}}}}}~~~~~~~~}}~~„ˆ‡†ƒ€~||‚‚„ƒƒ€€|xxzzyxuqommrw|}||ƒ†„€€€€~~~~~~~~~|}{zyzyzyyxxwwxy{}~~}|{wwvvxxvtsqsuuvwz||{}~~}}}}}|||{zzz{{}~}}€}|{xuuy~~}{{zzzzxvtrrssqpprrrqqruy|{{ytqoooooomkmnqu{ƒ‚€yqmkllmlkkjiinty}‚‚|xvvvuv~~~~~~~~~~}}~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~}}}}}}}}}}}}~~}}~~~~~~~~~~~~†‡†…‚}||‚‚ƒ€ƒƒ€€{zzzzyvspnlqv{~}zw€„ƒ€€€€€€€€€€€€~~~||{zyzyzyzxxwwxz{}~~~}{zxwwzzxwutuvvyy{}}{}}}}}}}}|||{z{{{|~~~~€~|||yvvz~€}{zzzzxvtrrssqpprrrqqrw{|{zwspooooonmklorx}‚ƒ‚~wolkllmlkkjiinty}‚‚|xvvvvv~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~}}}}}}}}}}}}~~}}~~~~~~~~~~~ƒ‡†…„}|}}‚‚ƒƒ„‚~€|y{z|zwupomqv{}~{w~‚‚€€€€€€€€€€€~|{zzyyxyyzyxwwxz{}~~~}{|zzyz{zywwwxy{|}}}z|||}}}||||||{||{|~~€~||}|yxz}}{zzzzxvtrrssqpprrrqqsx{}zywsonoooomlkkns|€‚ƒ‚€|tnkkllmlkkjiintx|€‚€{wuuuuu~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~}}}}}}}}}}}}}}}}~~~~~~~~~~~ƒ‡†…ƒ€|}~~‚‚ƒ€‚„„‚~€{y{|}{yvrporwz|}|{}‚‚‚€€€€€€‚‚€€~|{zzyyxyyzyxwwyz|}~~~}{{z{{z|{yzyyz|~~||z{||}}}|||||}|||{}~~€~||||{yz}‚„‚}{zzzzxvtrrssqpprrrqqty{|yxvromoooomlkjms}€‚ƒ‚€{rmlkllmlkkjiintx|‚‚{wutttt~~~~~~~~~~~~}}}}~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}||}}~~~~~~~~~~~~~~~~€…‡……ƒ||}}€€€ƒ„‚‚ƒ‡„~€‚}zz}}{zwtqorvz{|{|‚ƒ‚€€€€€€€€€~|{zzywwyy{zyxwx|}~~~}|||||||||{z{~|z{yyz|}}||}}}}}}}}}~~~~~~€}}{{|z{‚‚~zyyyxxwusqrtrqqsrrrrvz||zxtrpnnnommkijnu}€ƒƒ‚€€yrmklmmmkkkjjjouz~}yuttttt~~~~~~~~~~~~}}}}~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}||}}~~~~~~~~~~~~~~~~…‡…„‚{||~€ƒ„ƒƒ†ˆƒ~}€}yy}~|{yvroruyyy{}‚„ƒ€€€€€€€€€€€€€~|{zzywxyy{{yxwy{|~~~~~~~~}}|}|{z|~~{yzyyz|}}||}}}}}}~~~~~~€~~€€}}{{{||‚‚}zyyyxxwusqrtrqqsrrssvz||zxsqpnnnommkjlpxƒƒ‚€xqlklmmmkkkjjlpv{}xtttttt~~~~~~~~~~~~}}}}~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~~~~~„†„ƒ~||}~€ƒ„ƒ„ˆˆ„~}€~zwx|}|zwsqrtwxx{}ƒ„„€€€€€€€€€€~|{zzyyyyy{{zyxxx{}~~~~~~~~~}}}|||}|yxyyzz|}}||}}}}}}}}~~~~€€€‚€~~}}{{|}}€}{zyyyxxwusqrtrqqsrrstw{||zwsqpnnnommkklrz€‚ƒ‚€}vqlklmmmkkkjjlpvz|vtsssss~~~~~~~~~~~~}}}}~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~}}~~~~~~€€€€~~~~~~„†„ƒ~||~~~~€ƒ„ƒ†‡†ƒ}|~{wvw|}{xtrqtuwy|~€ƒ…„€€€€€€€€€€~|{zzyyyyy{{zyxxx{}~~~~~~~~}}~~|yxxzyzz|}}||}}}}}}}}~~~~€‚ƒƒ‚}}}}||}~~€}{zyyyxxwusqrtrqqsrrrtw|||ywropnnnommkknt|‚‚ƒ‚€|upkklmmmkkkjjmqwz|vtsssss~~~~~~~~}}~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||}}}}~~~~~~~~~~~~~~€ƒ…„‚€~|}}~€ƒ…‡ˆ„‚|{~€{vtty€~{yusrruxz}€„„€}}{{{zzzxxxz{zxwwy}~~~~}}}}~{xxy{|}{}}|||}}}}}~}}~~~~‚ƒ„‚‚~}}{|{{}~€}|yzzxxywusqstrqqsrrsuz{||zvroooooomlkjnw~ƒ‚ƒ‚ztnljkllljjjiimrv|€‚{vsrrrrr~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||}}}}~~~~~~~~~~~~~~€ƒ……‚}|~}~€„†‡‡ƒ||~€{vtux~€|zwtrquy{~€€ƒ„€~~}}||{{zzxxxz{{xvvx|~~€€~}}}}}}}yxy{|~}~}|||}}}}}}}}~~~~ƒ„ƒ‚€~}}|{{{}€}|yzzxxywusqrsrqqsrssv{{{{yvqoooooomlkkqy€‚ƒƒ„‚yrnljkllljjjiilrw}€‚€{usrrrrr~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||}}}}~~~~~~~~~~~~~~€ƒ……‚~||~}~€‚…†‡†‚€~~}~€|wuvy~€€}{xurquy{}€~~€„€~~}}||{{zzzzz{{{xwvw{~~~€€~}}}}}||yxy{}€~~}|||}}}}}}}}~~~~€‚ƒ„„ƒ€}}|{{~€~|yzzxxywurpqrrqqsrstw{{{{xuqoooooomlkmt{ƒƒƒ„~xpmkjkllljjjjjlry~€‚~yurrrrrr~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||}}}}~~~~~~~~€ƒ…„}{|~}~€ƒ……‡…‚~~}~€}xuvy~€~|yvsruz|}~}|€„€}}}}}}{{zzzzz{{{xwvwz}~€€~}}}}}||yyy|~€~}|||}}}}}|~~~~~~€ƒ„…ƒ€€}}|{}€€~|yzzxxywuqpqrrqqsrsuy{{zzxtqoooooomlknt{‚ƒƒ‚}wpmkjkllljjjjjmsz~€‚}xurqqqqq~~~~~~~~~~~~~~~~~~~~}}}{~~~~}}}}}}}~~~}}}}}}}{||||||||||}}~~~~€€~~~~~~€~|~€„„ƒ‚~{||}~~ƒ…‡‡ƒ~~~|~€}xvwy}€~}zwtux{z{}{y{‚‚€€~~~~~~~~~~~~~~~~~~|{{z{zx{|{yxwxxz}}}~~}}~{yz{ƒ‚€~~~}}|~}}}}}}}~~~~~~€„„„„‚~}€~€|zzzyxxwuqooprrrrrsv{||zyvspooooonlkknv~‚„„ƒ|uplkkkllmkkkijotz~€€€{xusrqqqr~~~~~~~~~~~~~~~~~~~~}}}{}}}}}}}}}}}~~~}}}}}}}{||||||||||}}~~~~~~~~~~€}ƒ„„ƒ{||~~~ƒ‡ˆ†ƒ€~~~|~€}wuvy}€~}|xuwz{yz{ywx€‚~~~~~~~~~~~~~~~~|||z{zx{||{yxwvx{}~~}||}}~|{zz}€ƒƒ‚€~€€~}|}}}}}}}}~~~~~~~~€„„„„ƒ€}€€~€‚€|{zzyxxwuqooprrrrstx{||zxuspooooonlkkpy„ƒƒƒ{tollkkllmkkkijpu{€€€{wtrqqqqt~~~~~~~~~~~~~~~~~~~~}}}{}}}}}}}}}}}~~~}}}}}}}{||||||||||}}~~~~~~~~~~~~€~~~€„„„ƒ‚~||~}~~‚…‰‰…ƒ~|~€}usuz|~€€|yxy{{xyyywx~€~~~~~~~~~~~~~~~~~}}}||{y|}}|yyxwxz|~~}||}}~|{zz~„„ƒ€~}}}}}}}}}~~~~~~~~€„„„„ƒ}|~~€|{zzyxxwuqooprrrrsuy{|{ywtrpooooonlkmqz„ƒƒƒ~yspllkkllmkkkjkpv|€zvsrqqqrt~~~~~~~~~~~~~~~~~~~~}}}{||||}}}}}}}~~~}}}}}}}{||||||||||}}~~~~~~~~~~~~€€~~„……„ƒ€}|}}~„‡‰‡…ƒƒ€~|~€|tru{|~‚}xy{}{xyxwwx}~~~~~~~~~~~~~~~~~~~}}}|z}~|{{zywxz{}}}||}}}{zzƒ„„ƒ€€€~}}}}}}}~~~~~~}}€„„„„ƒ€€}}€|{{zzxxwuqooprrrrtvz|}{wvtqpooooonlkot|„ƒƒ‚€}wrollmlnnmkkkjkpv}€‚‚zusrqqqsv~~~~~~~~~~~~~~~~~~~~~~~~~~||}}}}~~~~~~~~~~}}}}}}||||||||||||}}}}~~~~~~~~€€€~~…†……„|||~}€‚…ˆ‰…„„„‚~z}€|usw{|}ƒ‚~||||yxxxwx}~~~~~~~~~~~~~~~~€~~€~~~}{|}}||zzyyyy{}~€€€~}}~~~~|zzƒ…„‚€€‚ƒƒ€~~}~~~~~~~~~}~~|~€‚ƒƒƒƒ‚‚‚‚‚‚~€€|zyzzyxvtonoprrrstv{||zvvsqpooooonmlnu~‚ƒƒƒ‚€|uqomllmmmmkjjjmqw|€ytsrrpqux~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~}}}}}}||||||||||||}}}}~~~~~~~~~~€‚‚~€‚„†……†‚}{|~~ƒ††‡‡„„……„~z}€}vtx|}}€ƒƒ~~~}zwxxxy}~~~~~~~~~~~~~~~~€~~€€~~~|}~~}}z{{zyy{|}~~}}~~~~|{{ƒ…„‚€€‚„ƒ€~~}~~~~~~~~~}}}|~€‚ƒƒƒƒƒ…„ƒƒ€€€€€€}{yzzyxvtonoprrrsux|}{yvusqpooooomlloxƒƒƒƒ‚€{tqomllmmmmkjjjnrx}€~xsrrrprwz€€~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~}}}}}}||||||||||||}}}}~~~~~~~~~~€ƒƒ~„††…†ƒ{{~€…‰ˆ†…‚‚………‚y|~yux|~|ƒƒ€}zxxyy{|~~~~~~~~~~~~~~~~€€€}}|}~~}~||{z{{z{{}~}}}}}~~}{{€ƒ…ƒ€€‚‚€€~~}~~~~~~~~~}}}~€€‚ƒƒƒƒ…†„„‚‚ƒ„„ƒ‚€}|zzzyxvtonoprrrswz}}{yussqpooooomknqz‚‚ƒ‚xsqomllmmmmkjjkosz~€|wsrrrqtx{€€€€~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}||||||||||||}}}}~~~~~~~~}}€‚‚€~€‚„††…†„|{‡‰†…‚€‚‡‡…ƒ€y|~{vx|~|ƒƒ€}yxyy{||~~~~~~~~~~~~~~~~€€€€}||}~~~~}|{{{{z{z|~~~}}}}}~~}||ƒ…„‚€€‚€~~}~~~~~~~~~}||~‚€‚ƒƒƒƒ„†„„ƒ…ˆ‰‰†……€}}{{zyxvtonoprrrsx{~}zxussqpooooolkns|‚‚‚‚wspomllmmmmkjjkosz€|vsrrqsuy|~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}||{{||||||||}}}}~~~~~~~~~~~~}……††…„||ƒˆ‰…ƒ€‚†‡†ƒxy€}xx||z|‚ƒƒ€~~ywxyz{}}}}}}~~~~~~~~~~~~€€~||||~}}~~|||||{z{||}|||{}~}}||}„…ƒ‚‚‚€€~~~~}}~~~~}||€‚‚ƒƒƒƒƒ„„„„†‡‹ŒŒŒŠˆƒ€~{{zyxwtpopqqrrux|~|zxurrqonnnmmklnu~‚‚‚}urpnllmmmmmliilpw|€€{vsrrsuw{~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}||||||||||||}}}}~~~~~~~~~~~~}~€……„„…†ƒ|„‡‡‚€~…††„ww~~zz}}z{ƒƒ‚€~~{xxyz{}}}}}}}}~~~~~~~~~~€€~}}}}~}}~~}}}}||zz{{|{{|z||}}||}……ƒ€~}~‚‚€€~~~}}~~~~}}„„ƒ‚ƒƒƒƒƒƒƒ„„‡ˆŠŒŒŒŠˆ…ƒ~}yyxwtpopqqrswz}~|zxtrqponnnmmklpx‚‚€|uqpnllmmmmmmiimry~€zvsrstwy}€~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}||||||||||||}}}}}}~~~~~~~~~~~}€€‚††ƒ‚„……|…‡„€}|„…†„uv}‚zz}}zy~‚‚€~~|zxyz{}}~~||}}}}}}}}~~~~€€~}}}}~}}~~~~}}||{{{{{{{{z|||}||}„„‚}|}€€~~~}}~~~~}~†…„ƒ€‚ƒƒƒƒ‚‚‚…ˆ‰‹‹Š‡……‚{yywtpopqrrtx{}~{ywtrqppnnnmmklqz€‚‚‚~ztromllmmmmmmiimry~yutssuy|€}}~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}||}}||||||||}}}}||~~~~~~~~~~}~~‚„††‚„††ƒ|„†‚}{{~€‚ƒ†„~tu|‚€{{~~zx|‚€~~{yxyz{}}~~{{||||}}}}~~~~€€~~~~~~}}~~~~}}||{{{{{{z{z{{||||}„„‚}|}€€€~~}}€‚„ƒ‚€‚ƒƒƒƒ‚‚„ˆŠ‹ŒŒ‡…†‡…‚}yywtpopqrrty|}~{yvurqpponnmmjlqz€ƒ‚‚}ysqnlllmmmmmmiimrz~€yuutsvz~€€€€€€~~~~~~~~~}}}}~~~~||}}}}}}}}||||||||||||}}}}}}~~}}~~~~~~~~~~€‚„…†€‚…†„~~ƒ„€zz}~€€ƒ‡…~usy€€|{~|zy{‚€{yyxz{}}~~|z||||||}}~~~~~€€€~}}}~~~~~}}}~}|||||z|{{zz{{{{}}„„‚}|}€€€€€~~}~ƒƒ„„„„ƒ‚‚„ˆ‰‹ŒŠ‡……ˆˆ„}zxwspooqqsvy}}|ywusrpooooonmklr{€‚‚‚€~xsqolllllmmmmkjns{€€~yvtrsv|€€€€~~~~~~~~~}}}}~~~~||}}}}}}}}||||||||||||}}}}}}~~}}~~~~~~~~~~€ƒ…†…€………€‚„€zz{}~‚††~usy€€}|~|{zz~€{zzxz{}}}~}{||||||||~~~~~~~€€€~}}|}~~~~}}}~}|||||{{z{zzz{{{}~ƒƒ}~|€€€€€€€€€~}€€}||ƒƒ„„ƒ‚„‡ˆŠŒŒ‹Š†„…ˆ‰„€~ywspooqrswz}}|xvtsqpoonnnmllnt~‚‚‚€~xrpmlllllmmmmjint|€}wttrtx}€€€€~~~~~~~~~}}}}~~~~~~}}}}}}}}||||||||||||}}}}}}~~}}~~~~~~~~~~€‚„…„~„…„‚ƒ‚|z{}}}††~usy~}~{z{€€€~~|{{zz{}}}~~}||||||||~~}}~~~€€€~}}|}~~~~~~}~}}}|}}||{{zzzz{{}~‚‚€~|}~‚€€€€€€€€€~€€}||ƒƒ„„„‚‚„‡ˆŠŒ‹‰†‡††‡‰‡„‚{wtqpqrsty{~}{xvsrpqonnmmmklov~‚€~xqomlllllmmmmjjou|€€}wttsvz~€‚€€€€~~~~~~~~~}}}}~~~~~~}}}}}}}}||||||||||||}}}}}}~~}}~~~~~~~~~~€„……~~……ƒ€ƒ‚~{{|}}††~usy‚€~{z{~€€~}}}{{zz{}}}~~}{|||}}||~~}}~~~€€€~}}}~~~~~~~}~}}||}}}|{zzzzz{}~‚‚€}|}~ƒƒ„ƒ€€€€€€€€€€~|}}‚‚„„ƒ‚ƒ„†‡‰Œˆ‡…†‡‡ˆŠŠˆ„}vsrqqssuz}~|zwusqpqpnnmmlklpw‚€€€}xqomlllllmmmmijov}€€}wtttw|€€~}~~}}~~~~~~~~~~}}}}}}||||||||||}}}}~~}}~~~~~~}}}}}}~~~€‚„„„~€„†„‚‚ƒƒ€{{{|~‚††}svy‚€€~|z|~€€~}}~~||{z{{|}‚~|zz||}}~~~~~~~~~~~~~~~~~}}}}}~~}|||~}{z{yzz{{|~~~~~€ƒ„„ƒƒ€€€€€€€€€€€}}}€……„‚‚‚„…††„ƒ‚‡‰ˆ‡ˆ‹Š†xsrrrqtx|~}{zvsrqqppnnmkkjkqy€€€€}wqonlmmmmlllkjkou~€€{trtux|€~}~~}}}}~~~~~~~~~~}}}}}}||||||||||}}}}~~~~~~~~~}}}}}}~~~€‚„„„€€ƒ†…„„„„‚{{{|‚‡†~ssw}€€€~|z|~}||~~~}|{{z{~……€|zz||}}}}~~~~~~}~~~~~~~~}}}~~~~||||~~~}{{zzz{{|~~~}~€‚ƒ„„ƒƒ‚€~€€€€‚€€}}~€‚‚……„‚‚‚ƒ„ƒƒƒ‚…†‡ˆ‰‰‰‰ƒ|urrrquz~|zxurqqqppnnmkkjlr{€€€€€€}wponlmmmmlllkkkpvztruvy}~}~~}}}}~~~~~~~~~~}}}}}}||||||||||}}}}~~~~~~~~~}}}}}}~~~€‚„„„€€ƒ…†„……†ƒ|z{|ƒˆ†tqu|€€~|z{}~~|{z|}~|{{{|‚†‡{zz||||}}~~~~~~}~~~~~~~~}~~~~~|||||}~~}{{{{{|~}}}€‚‚„„„ƒƒ‚~}€‚€‚‚€€}}~€‚ƒ„††„‚‚‚ƒ‚‚‚„ƒ……ƒ‡ˆ‰ˆ‰ˆˆxtsqqv{~|ywsrpqqppnnmkkjmt|€€€}wponlmmmmlllkkkrx€zutvx{}€~~}~~~~}}~~~~~~~~~~}}}}}}||||||||||}}}}~~~~~~~~~}}}}}}~~~€‚„„„€€‚…†………†ƒ~z{|ƒ‰†tsv|€€€~|z{|~~{{yz|}|||~„‡‰„|{{||{{||~~~~~~}~~~~~~~~~}}}||}~~|{|{{|~||~€€‚‚„„„„ƒ‚}|€ƒ„ƒƒ‚ƒ‚‚€€}}~‚ƒ††„‚‚‚‚‚„……ˆƒ…‡‡…ˆ†Šƒ|wsrsw}€~{xvsqpqqppnnmkkjmu}€€}wponlmmmmlllkkkry€xttwz{}€}€€€€~~~~~}}}}~~~~~~~||||||||||||||||||}~~€~~~~~~}}}}}|}}}}~‚„„‚€€€„††……‡ƒ~z{}ƒ‡…€wwx}€}{|}}{zxxx|~}}}}€‚†‰Š‡~{zzz{{{{}~~~~~~~~~~~~~~~}}~~~|}}~~€}|||}}|}~€ƒƒ‚ƒ„}{zƒ†††……†„€€~|}~€€‚††„‚‚‚ƒƒ…†…„ƒ……€‡ˆ‰†~wtrry}€{yvqppqqpponnllknw€€€~woommmmmmmmljilsz€€~ytuwz}~~{€€€€~~~~~}}}}~~~~~~~||||||||||||||||||}~~€€~~}}}}}|}}}}~„„‚€€ƒ……††‡‚}xy~…‡…‚{zz~€€}|}}|{ywvv{~~}~~‚„†‰‹‰{{zz{{{{|~~~~~}}}}~}}~~~}}}}~}~}~}}}€€€~}||||}~€€ƒƒ‚‚„~{z}„††…†‡ƒ€€}{}~€€ƒ††ƒ€‚‚‚ƒƒ„…„ƒƒz‰‘Œˆyttt|~€zxupppqqpponnlkjnv‚‚€€|upnlmmmmmmmljimt{€€~xuvy{~€€}z€€€€~~~~~}}}}~~~~~~~||||||||||||||||||}~~€~~}}}}}|}}}}~€ƒ„‚€€„„†††}xy}†‡†ƒ€}}~€€€€~}}}{zxvtuy|}~€€„‡ˆŠ‹Š„{{zz{{{{{}~~~~}}}}~~~~~~}}||}~~~}~}}|}}€€€~||||}~€‚‚‚‚‚„|z|‚ƒƒ„†‚€|{}~€„……‚€€‚‚ƒƒ„…„ƒ€}{‹—‹†yyy€~zwtpppqqpponnljinv€‚‚€€{tonlmmmmmmmljinuz€~}xux|~€€{x€€€€~~~~~~}}}}~~~~~~~||||||||||||||||||}~~€€€~~}}}}}|}}}}~€ƒ„‚€€€€„„……†~yy{€†‰ˆƒ€~~~€€€€~}}}{zwusty{|‚‚ƒ†ŠŠ‹‡}{zz{{{{{|~~~~~~~~~~~~~|{||}~~~~~}}|}|€€~||{{|~€‚ƒƒ€‚„|z|}‚„…‚€~|{}~€„……‚€€‚‚‚‚„…„ƒ€}~•‹†€}|~~~zwtpppqqpponnljinx€‚‚‚€|unnlmmmmmmmljjnuz€~|vtx|€€{w~~~~~~~~}}}}~~}}}|||||||||||||{{|||~~~‚‚ƒ~}}}}}||}}~~}‚ƒƒ€€€‚ƒ………ƒ}{|€…†‡„~~}~~}|}|{ywtttwy~‚„…ˆ‰‰ŠŒŒŠ{zzzz{||}~~~~~~~~~~~~~~}{{y||}~~~~~~}|~€€~|{{{|~ƒ‚€€‚ƒ‚~|}|~€ƒ~~{|~~€‚…ƒ‚€€‚‚‚ƒƒƒƒƒƒ‚€}€‘Ž‹…}|}{yvrooqqqooonmkhjoyƒ€|unnlmmmnnnmkiknv|€€{vuy}€€~xv~~~~~~}}}}}}}}}||||||||||||||||||~~~‚ƒ€~}}}}}}}}}~~~‚ƒƒ€€€ƒ„„…„ƒ„††…ƒ~~}}~~}|}|zxvsssuw|€ƒ…†‡‰‰‰‹ŒŒ„|zyz{{||}~~~~~~~~~~~~~~}{{y||}~~~~~~~~€€~|{{z|~‚ƒ€‚ƒƒ}||{}~‚€~~~{|~‚…ƒ€‚ƒƒƒƒƒƒƒƒ‚‚~‰Ž’‘‰}zxyzxvrooqqqooonmkijr{€‚€|tnnlmmmnnnmkikox~€€{wwz}€€|vv€€~~~~~~}}}}}}}}||||||||||||||}}|||~~~€ƒ„ƒ~}||}}}}}}~~€‚ƒƒ€€€ƒ„„„„……††‡ˆ‡…ƒ}}}~~€~|{{{yvursrtw|€‚„‡ˆ‰‰‰‹Œˆ~zz{||||}~~~~~~~~~~~~~}|{{}~~€€~~~~€€~|{zz|~ƒƒ€€~~€‚ƒƒ}|||}}}‚‚€~}~{|~‚…ƒ€‚‚ƒƒƒƒƒƒƒƒƒƒ‚ƒ‰’ˆƒ{yxxxyxvspoqqqooonmkjkt}€‚€{rmmlmmmnnnmjhkqx}€€{xy|~€€€ytu€€~~~~~~}}}}||}|||||||||||||||}}|||~~~€ƒ„„ƒ~}{{}}~~}}~~€‚ƒƒ€~€€‚ƒƒ„…†‡‰‰ˆˆ††„€||~~€~{zzzxvurrqsv{€ƒƒ…‡‰‰Š‹Ž‹}||||||}~~~~~~~~~~~~~~}|}}~€€~~€€~|zyy|~‚~|}}€ƒƒ}||}||}‚€~}~{|~€‚‚…ƒ€‚ƒ„„ƒƒƒƒƒƒ……ƒƒ…Š†|vv|zywyxsqoqqqooonmkkmt~€‚€{qlllmmmnnnmjhksx}€€{xz€€€~vrt~~~~~~}}}}}}|}||||||||||||||||||}~}}„……„€}|||}~~~€‚‚ƒ‚€€€€€‚„„……‡ˆˆˆˆ‡†„}|~~~~}{zzzwvtsqqqtx}‚‚…‰‹‹Œ‡}{|}|}}~~~~~~~~~~~~~}}}}€~~~~~€~}}{xz{€‚|}~€„„~|{|}{{|€€€}||~‚„†ƒ€‚„„„„ƒ‚ƒ„„………„†ŠŽŠƒ~{wrz{tw|ysppqpomnmmkjms€€zqmmlmmmnnmliilty}€~zxz~€}tsu~~~~~~}}}}}}|}||||||||||||||||||~~}}„„……}|||~~€€‚‚ƒ‚ƒ‚€€€„„„„…‡‡ˆˆ‡†„~|}~~~~|{zzywvtrqqqtw|„ˆŠŠŒŽŠ~||}|}}~~~~~~~~~~~~~}~~~~~~}}~€€~}}{xy{€€€~{|}ƒ‚}{|~||}€€€~~}||~‚…†ƒ€€‚ƒ…………ƒ‚„„„„„…„‡Š‡€zwrv‚‚}wv||urpqpomnmmkjmu€‚‚€€yqmmlmmmnnmliimtz~€~zz|€€€{trv~~~~~~}}}}}}|}||||||||||||||||||~~}}ƒ„……‚~||}€ƒƒ‚‚ƒ‚ƒ‚€€€~~€ƒ„ƒ‚…‡ˆ‡…„~|}~}~}|zyxxuutrrrqsv{‚€‚†ˆ‰‹‹ƒ|{||}}~~~~~~~~~~~~~~€€~~~}}}}||}~€~}}{yy}€~}{|}€ƒ‚€~|}}|~€€€~~}||~‚…†ƒ€€‚…‡‡……ƒƒ…„„„„……ˆ‹Œ†~wrq{ƒƒ€{wz|vsqqpomnmmkinw‚‚€€xpmmlmmmnnmliinuz~€~{|~€€€€~xssw~~~~~~}}}}}}|}||||||||||||||||||~~}}ƒ„……„€||~€ƒƒ„„ƒƒƒ‚ƒ‚€€€€~~€‚ƒ‚~~‚‡ˆ‡…„~|}}}~|{yxwvsstsrrqrvz~‚…‡‰‹‹Ž‡~|{|}}~~~~~~~~~~~~~~€€~~~}}}|zz{}~€~}}{yy}€~|{{{}€ƒƒ€~}€}}~€€~}||~‚…†ƒ€ƒ‡‡ˆ……ƒƒ…ƒ„„„†ˆ‰‰…|tor}…‚‚|vx{yrrqpomnmmkhnw€‚€€xommlmmmnnmljiov|€~||€€}wrsx~~}}~~}}}{||||||||||||||||}}}}~~~~~‚…†„}|~€‚ƒ„„††……„ƒƒ‚„‚}}‡ˆ…„„~|||}}|zwvxvttusssrsvx|€‚ƒ„†‡‰‹ŒŽŽŠ€|{|}}}~~~~~~~~}}~}}}}|}|{zxz{|}{zz{€€~{||{|~‚†ƒ€€€~~€~}{|‚ƒ…„‚„ˆ‰ˆ†…„…†………„„…‡ˆ‹†|tos}ƒ€~€}vv|xsrpponmkkioy€€‚€€~wpmmllkmmmkjhinv|~}€€€€€{rru{~~}}~~}}}{||||||||||||||||}}}}~~~~~€‚…††‚~}ƒ„„„††……„ƒƒ€€€€‚„‚~{}‡ˆ…„„€|{}}}}|zwwvussutssstvx{‚ƒ„ˆ‰‹ŒŽŽ„~||}}}~~~~~~~~}|~€~~|||}|{z{{yyy|~}{zz|€€~{||{}~„†„€€~€€€€~}|}€‚ƒ…„‚„ˆ‰ˆ‡…„†††………„…‡‰‰‡~ups|~{~~xv|€{trpppnmkkjpy€€‚€€}vommllkmmmkjginw|~€€~€€€€~wqru|~~}}~~}}}{||||||||||||||||}}}}~~~~~€‚…†‡ƒ~‚„…„„……„„„‚‚€€€€€‚ƒ~z}‡ˆ…„„}{}}}}|zyxvtstvutttuvxz}€‚ƒˆ‰‹‹ŽŽˆ€}}}~}~~~~~~~}}}€~}||{|{{{{|zyyz}~}{zz|€€~{|||~„†„€€~~€€~~}~€‚ƒ…„‚„ˆ‰ˆ‡…„†††…………††ˆŠ‡€vqrz~|z~{z||xspppnmkkkqz€‚€{uommllkmmmmlgiox}~€€~~€€€€}vqsx}~~}}~~}}}{||||||||||||||||}}}}~~~~~€‚…†‡ƒ€€ƒ„†„„……„„ƒ‚‚€‚ƒ‚{}‡ˆ…„…}{~~}}|z{zvttuvuutuvvyz{~‚†‰Š‹ŽŠƒ}||~}~~~~~~~~|}€€}||{|{|{z{{}|y{z|}~{zz|€€~{||}~„‡‚€€~~~€€~}}~‚ƒ…„‚„ˆ‰ˆ‡…„†††…………††‡ˆ‡‚xqqx|}|~~}|zspppnmkkkq{€‚€€{tnmmllkmmmmlgipx|}€€~€‚{upty}~~~~~~~~}}||||||||||||}}}}}}}}}}~~~~~~„…†„€„„†…ƒ……„„ƒ‚~€€€€€ƒƒ|}‡‰‡††~}~}{{zzxtuvwwvwuvxy{{}€…‰Š‹ŽŒ†}}~~~~~~~~}}~€€|{zz{z{{|}~~~|{{|}€~{xz}€€|z{|}~€„†ƒ~~€‚‚‚€~}|~€‚„…„‚„‡ˆˆ‡……††††††………†‰†ƒ|stx|€€‚€~}upppnmljkr|€€|rlmmllllmmljhjsx}~€€~wssw{}~~~~~~}}||||||||||||}}}}}}}}}}~~~~~~€ƒ…†„€„……„ƒƒƒƒƒ‚€€~€€€€€€€‚ƒ}}‡Šˆ‡†‚€~}{{{{yvwwvvvxvxzz||}„ˆŠŠŒŽ‹ƒ~}}~~}|€€{zzz{zz|~~€~~~}|}~€~{xz}€|zz{|~€„†‚}~~€~}|~‚„…„ƒ‡ˆˆ‡‡……‡‡‡†††……†ˆ†…ww{~€€€ƒ…‚€€€yqppnmljkr}€€|rlmmllllmmlihktz~€|vstx|}~~~~~~}}||||||||||||}}}}}}}}}}~~~~~~~‚„……ƒ„…„‚‚‚€~~€€€€€€€‚ƒ‚~|€‡Šˆ‡‡„€€}|{{||{xxvuuvxyz{{||}„‡‰ŠŒŽ†}~~~}}€~{zyyzz{|~€}}~€€€~{xz}€|yy{|~€…†‚~~~€€~~}|}‚„…„ƒ†‡ˆ‡ˆ†„†‡††††††‡‡‡…{{}‚€€‚„ƒ‚€}tppnmljkr}€€|tnmmllllmmlihltz~€zutuy{}€€~~~~~~}}||||||||||||}}}}}}}}}}~~~~~~}‚ƒ„†…‚‚…„ƒ€€€~~~~€€€€€€‚‚~|€‡Šˆ‡†…‚€}{{{||}zxvuuvx{{{|}}}€„‡‰ŠŒŒŒˆƒ~~~~€€~~€€~|{yyyzz{}€€€€}~€€€~{xz}€|xy{{~€…†‚€~~€€~}||}‚‚„…„€‚…†ˆ‡ˆ‡„…‡‡†††††‡‡‡…ƒ}~~}€€ƒƒƒ‚vppnmljkr}€€|tnmmllllmmmihluz~~~€€€zutvz{|€€~~~~~~~~~~~~}}}}|}{{||||||||}}}}}}}}~~~~~~~~}€‚…………ƒ„ƒ‚€€€€~~~€€€€€€€€‚ƒ~}€‡Š‰ˆ†„ƒ€|{zz|}}zxuttuyz||}}~€€€‚…‡ˆŠŠŒŒ‹†€~~~€~~~€~|{yyxy{}}€€‚€~€€€€zw{}~zwyy{‚„„~}}~€~||}~‚„ƒ‚€„†‡ˆˆ†ƒƒ††††…††‡ˆˆ…‚€}|}‚€€‚„…„€yqoonkkls~€‚€€}uollllkmmmmjimw|€~}~€~wtuxy{}~~~~~~~~~~~~}}}}||||||||||||}}}}}}}}~~~~~~~~}€………†ƒ„ƒ‚€€€€€~€€€€€€€€‚ƒ~€‡Š‰ˆ†…„‚€|{yy{}|zxussuxz||~}~€€€€„†ˆŠŠŒŒŒˆ‚~~~~~€}|{zzxy{}}~€€‚ƒƒƒ‚€€€{x{}€~zwyy{‚„„~|}~~~||}ƒ„ƒ‚€ƒ…‡ˆ‡†ƒƒ††††…‡‡‡‡‡…‚}|}‚‚€€ƒ„…„{roonkkmt~€‚€€}tnllllkmmmmjjnw}€~}~€}utuxy{~~~~~~~~~~~~~~~~}}|}}||||||||}}}}}}}}}}~~~~~~}€‚„…†„†ƒ‚€€€€€~€€€€€€€€ƒ€€‡Š‰ˆ……„ƒ€~zyxxz||{xusrsvz||~~~€€€€‚†ŠŠ‰‹ŒŒŒ‹…~~~€€~~}|{zzxz|}}~~„†…„ƒ€€~~|z|~€~zwyy{‚„„~}}}}~~}||}ƒ…„‚€ƒ„‡ˆ‡†…„††††…‡‡‡††…‚~€ƒƒ‚„„„…‚}tponkkmu~€‚€€}tnllllkmmmmjiow}€~}~€zsuvxy{~~~~~~~~~~~~~~~~}~}}}||||||||}}}}}}}}||~~~~~~}€€ƒ…†„†ƒ‚€€€€€€€~€€€€€€€~ƒ‚€€‡Š‰‡………‚|ywwwz{{|xurqstz||~€€€€€€„‰Š‰ŠŒŒŒŽˆ‚~€€~~~}|{zzz{|}}~~€„‡ˆˆ†„‚€€€~~|}~€~zwyy{ƒ…„~~~||~~|||}ƒ…„ƒ€‚ƒ‡ˆ††‡†††††…‡‰‰††…ƒ‚ƒƒ‚ƒ…††…„ƒ~wqomkkmw~€‚€€}smllllkmmmljipw}€~}~€xruwxy{~~~~~~~~~}~~~~~~~~~~}|||||||||||}}~~~~}}}}~~~~~~~~€„†††„‚€~~~€€~~ƒƒ€†Š‰‡†…„|xwuuvy{{{wusrsuy{}~€€~}†Š‹‹‹ŒŒŒ‰ƒ~~}}~}||{{{|}~~~†‰Š‰‡„ƒ€~~||}€}xvwy|‚ƒ„~}|||~~}}{{}€‚„…„‚‚‚ƒ„††††„……………†ˆˆ†…„‚‚‚ƒƒ„„‚ƒ…†……„‚€yromjjmx‚€€|tmllkklmnljijqx|~~}~€‚}xtwxyy{~~~~~~~~~}~~~~~~~~~~}|||||||||||}}~~~~}}}}~~~~~~~~€ƒ…††…„‚~~~€€~~ƒƒ€„Š‰‡…ƒ‚ytsstvy{{{xusssuy{}~~€€}|ƒ‰Œ‹‹‹‹Œ‰‡€‚€~}}}||{{{|}}}~€ƒˆ‰ŠŠˆ†„€~~}}~€}ywwy|~€ƒ„„~~|||~~}}||}ƒ„…„‚‚‚‚‚‚ƒ„†‡†‡„„„………†ˆˆ†…„ƒƒƒ„„„„ƒ„‡†……ƒ‚€{vromlox€‚€€|tmllkkmmmljijrz|~}}~€‚|xvxyyy{~~~~~~~~~}~~~~~~~~~~}|||||||||||}}~~~~}}}}~~~~~~~~€ƒ…††…„‚~~~€€~}~€‚‚‚ƒ‰Š†„€{xrooruy{{{yvsssuy{}}}~€€|{|€‡Š‹‹‹‹‹ŠŠ„€€€‚~~~}}|||{{||}}‚…ˆˆŠŠˆ†„‚€~~|}€€~yxyy|~€‚„„~~}}~~}}||}‚ƒƒ„ƒ‚‚‚‚‚ƒƒ„†‡‡…ƒƒ………†‡‡…„„ƒƒƒ„„………†ˆ‡†…ƒƒƒ€zvsporz€‚€€|slllkkmmlkjhks{}~}}~€‚zwvyzyy{~~~~~~~~~}~~~~~~~~~~}|||||||||||}}~~~~}}}}~~~~~~~~~€‚„†††…ƒ‚~~~€€~}}‚‚€€€†‰…„€~zysooquy{{{yvtssvy{}}}~€€|z|}ƒ†‹‹ŠŠŠ‹‰†€€€~~~~}|||{{|||~}€ƒ‡ˆˆ‰‰ˆˆ„‚€~~}}€€~}zxyy|~€‚‚„~~~~~~}}}}}‚ƒ„…ƒ‚‚ƒƒ‚ƒƒƒ…ˆ‡…ƒƒ………†‡‡…„…ƒƒƒ„„……‡††ˆ‡†„‚ƒƒ‚~ywsrs{€‚€€|slllkkmmlkjhls{~~}}~€‚yvwy{yy{~~~~~~~~~€€~~~~~~~~~~~~}}}|||||||||||}}}}}}~~~~~~}}~~~~ƒ„……‚€~~~~~€€}|€††‚||{wpnoty|||yyuvuvyz||}€€|{|}€…‰‰Š‰‰ˆ…€€‚€~}}}}||{{{{|~‚„ˆˆŠŠŠ‰‰…„~}}}|}|zwxx{~‚ƒ‚‚€€~~~~€~~|}}€‚ƒ‚‚‚ƒƒ‚‚‚…‡…ƒ„„„……††……„ƒ„„……††††††††„ƒƒƒƒ|ywx}€‚€~{rmkkklmmlkjimu|~~~~€‚€|xuxzzxxz~~~~~~~~~~~~~~~~~~~~~}}}|||||||||||}}}}}}~~~~~~}}~~~~€€ƒ„……‚~~~~~~~€‚|~‚ƒ‚}~zronsy|~~{{yxyyzz||~€€~|{{}~€ƒ†‰‹‰‰ˆ…€€€~}}}}}||{|}€„†‰‰Š‹Š‰‰„ƒ~}}}|~|zwxx{ƒƒƒƒ‚‚€€~~€€~|~€‚ƒ‚‚ƒ‚‚‚‚‚‚€ƒ†…ƒ„…„……††……„„…………††††††††„„„„„ƒ‚€~|{‚‚‚‚‚€~{rnkkklmmlkjhmv|~~~‚‚{wvxyyxxz~~~~~~~~~~~~~~~~~~~~~}}}|||||||||||}}}}}}~~~~~~}}~~~~‚ƒƒ€~~~~~~~€‚~|€‚€€‚‚}wqorwz~~}}{{{{{|}}}|{z|}~„ˆ‹‹‹ˆ…€~~}}}||}}}}~„„‡ŠŠ‰‹‹‰ˆƒ‚€~||}}€€|zwxz}‚‚ƒƒƒƒ~~€€~}‚ƒ‚ƒ………„„ƒ‚‚‚~‚„…ƒ„…„……†††…„„………………††††††„„„„„ƒ„ƒ~€‚‚‚‚‚€~{rnkkklmmlkihmw}~|}‚‚{wwyyywwz~~~~~~~~~~~~~~~~~~~~~~~}}}|||||||||||}}}}}}~~~~~~}}~~~~€‚‚€~}}~~~~~‚{‚‚„…€ysoquy}}}}}}~€€€€‚~}|{z{|}~ƒ‡Šˆ…‚‚€~~}}}{{{|}~€‚ƒƒ†‰‹‹Š‹Šˆ†‚€~||}}|zwxz}~‚ƒƒƒƒ‚~~~~€€~~‚ƒ‚ƒ„„„ƒ…„ƒ‚‚~~„…ƒ„…„……†††…„„……„„„„††††††……„„…ƒ„„ƒ€€‚ƒƒƒƒ‚€~{rnkkklmmlkiimw}~{|‚‚zwwzxxvvz~}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}||||~~~~~~~~~~~~}}~~~~~~~~€€€€~~~~~~€€~}}}}}~~|}€ƒ‚ƒ„„‚|uoqvy|}~~~~}~€‚‚‚}|{{{|{|€ƒ†‹‹‡„€‚€~}||{{{||}ƒ„„…‡‰ŠŠŠŠ‰†ƒ€€~||}~€€}|zxy{~€ƒƒƒ„„ƒ~~~|}€ƒƒƒƒ„„ƒƒƒ…„ƒ…‚‚~ƒ…ƒ„„……†…………ƒ‚ƒƒ……„……†‡‡‡‡‡‡†††…„„„‚‚ƒ„„„„‚€€yrnkkklnnlkgiox~|}€}zwxyyxvvz~}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~}}}}}~€}|€‚‚„„„ƒxrrux{|}}~~€‚~}{{{||{|~ƒ……„‚€~}|||{{{||~€„…†‡ˆŠŠŠŠŠˆ…}||}€€}|zxyz}€€ƒƒƒ„„ƒ€~~~}~€ƒƒƒƒ„„ƒƒƒ…„ƒ†„„‚‚‚ƒƒ„„……†††……€‚„…‡††††††††††„„„ƒƒƒ„„„„ƒ€€yrmkkklmmlkhjpx~}}€|yxyyyxvvz~}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}~~}}~~~~~~~~~~~~}}}}~~~~~~~~~~~~~~~~~~~~~}}}}}~~ƒ~{~ƒ„„ƒƒ‚|tsux{|~~~~€€€€€~}|z{{||{|}€‚‚€€€~}|{|{{{{{„†‡‡‰‹ŠŠŠŠ‡„€~}{z|}€€~||zxyz}}~€‚‚‚ƒƒƒ€~~~~€ƒƒƒƒ„„„„ƒ………†††…„‚‚ƒƒ„„……†ˆˆ†‚~€€€ƒ†………………††††„„„ƒƒƒ„„„„ƒ‚€€yqlkkkllllkikqy}~}}€€|yxyyxwvvz~}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}||~~~~~~~~~~~~~~}}||~~~~~~~~~~}}~~~~~~~~}}~}}}}}~~z|ƒ„„„ƒƒ}uptwz{~~~~€€€€~|{z{{||{||~€€‚€€~~}|{|{{{{{…†‡‡Š‹ŠŠŠŠ‡ƒ€|{yz|}€€}{|zxyy||}‚‚ƒ‚€~~~|}€ƒƒƒƒ„„……ƒ…†††……„„‚ƒ„ƒ„„……†‡‡…€}|~~~~€„……„………††††„„„ƒƒƒ„„„„„‚€€yplkkkllllkikqy}~}~€€|xxyyxvvvz~€€}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}~~}}~~~~}~~~~~}}}}}}~~~~}}~~~~~~}~~~~~~~~~~}}}}}}}}}}~‚€|z}„„ƒƒƒ‚yrrvyz}~~}|{{{{{zzz|~}~‚‚€€}|z{{{z{{|€…ˆˆˆ‰‹ŠŠ‹Šˆ…~|zyz|~€€~|z{xyzz{}€ƒƒƒ‚€€€~~|}€‚‚ƒƒ„„ƒƒ„……………„„„„„„ƒƒ„††‡†…{vy|}~€ƒ„…†„………„……„…„„„‚„„……„ƒ‚‚€ypmkkllllkjiksz~}}}€€‚|yyyywvvvy}€€}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}~~}}~~~~}~~~~~}}}}}}~~~~}}~~~~~~}~~~~~~~~~~}}}}}}}}}}}z{€ƒ„ƒƒ„ƒ{truyz|~~~}}~}|{{{{{zzz|~~}~€‚‚€€€~}|{zy{{||}ƒ‡ˆˆˆ‰ŠŠŠ‹Šˆ…‚}zyz|~€€~|{zz{zzz}€ƒƒƒ‚€€}~€‚‚ƒƒ„„ƒƒ„„……„„„„„„„„ƒƒ„†††…ƒ{trw|}~~~~~ƒ†ˆ„………„„„„…„„ƒ‚ƒƒ„„„„ƒƒ‚{rnllllllkjimu|}~~€€€|yy{}zwuuy}€€€~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}~~}}~~~~}}~~~~}}}}}}~~~~}}~~~~~~}~~~~~~~~~~}}}}}}}}}||~‚‚zz}‚„ƒ‚„„~wsuxy{}||}}~}||{{{zzz|~}~€‚‚€~}|{zzz{|}~„‡ˆˆˆˆ‰ŠŠŠ‰‰†ƒ}zyz|~€€}|{|{zyy}€‚‚‚€€‚‚€~€€‚‚ƒƒ„„ƒƒ„„……„„„„„„ƒƒ„„†………‚uopx}~~~}~~€‡ŠŠ†……„„‚‚ƒ„„…„„ƒƒƒƒ„„„„ƒ}snllllllkjimu|}€€€|yy|~xtty}€€~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}~~}}~~~~}}~~}}}}}}~~~~}}~~~~~~}~~~~~~~~~~}}}}}}}}}||‚‚‚|y{ƒƒ‚„„€yuvwy{|z{|}~}}{{{zzz|~}ƒ‚‚€€~}{{zyz|}~‚ƒ†‡‡ˆˆ‰ŠŠŠ‰ˆ‡„‚~{yz|~€€€~~{}{zyy~€€€‚‚‚~€‚‚ƒƒ„„ƒƒ„ƒ„„ƒƒ„„„„‚‚…„†„„ƒzsmow}€~}~~€ŠŒŠ†……„ƒƒ„„…„‚‚ƒƒ„„„„ƒvollllllkjinv}}€€€|yy|€ytty}€~~~~~}}}~~~~~~~~~~|}}}}}}~~~~||}|}}}}}}}}~~}}}}}}|}~~}}}}}}}}}}~~~~}}~~~~~~~~~~~~~~||}}||||}}}~€‚~xyƒ„ƒ„„‚}vvzyy}€zy||}~€~}}}||{{zyz||€~}|{zzzz~€‚ƒ„††††ˆ‰Š‰‰‰‡…ƒ€|zy}~~~~}}|zxz~€€‚‚ƒƒ‚€‚‚‚‚ƒ„ƒƒƒƒ„„ƒƒƒƒ„„„„ƒƒ„†„ƒ|snmry~~}}~}…‰…„„„„ƒ€ƒ„†‡†‚€ƒ„„……ƒxrnmmlmmkihnw~~}€€€{yy}ƒ‚ytty~€~~~~~}}}~~~~~~~~~~~~|}}}}}}~~~~}|}|}}}}}}}}~~}}}}}}|}}}}}}}}}}}}}~~~~}}~~~~~~~~~~~~~~~~||}}||||}}}~~xx}‚ƒ„„„ƒzy|yy|~zy|}~~€~}}}||{{zyz||€€€~}||{{{{~€‚‚ƒ„…………†ˆŠ‰‰‰‰ˆ„|zy}~}}~~~}||}€€€€€‚‚‚‚‚ƒƒ‚‚ƒƒ‚‚ƒ„ƒƒƒƒ„ƒ‚‚ƒƒ„„„„„„„…„„€~vpnqw{~|{~~}ƒ‹„‚ƒƒ„„‚€ƒ„†ˆ†ƒ€‚ƒƒ„„„‚}vonmnmmkihow~~}€€€~zyy}„ytty~€~~~~~}}}~~~~~~~~~~~~|}}}}}}~~~~~~}|}}}}}}}}~~}}}}}}{}}}}}}}}}}}}}~~~~}}~~~~~~}}~~~~~~~~~~}}||||}}}~~‚€{xz‚ƒ„„ƒ~}}zz|~|z|}~~€~}}}||{{zyz|}€~}|||{}~€‚‚‚‚„……„†‡‰‰ŠŠ‰‰†‚}zy|}||~~~~~€€‚ƒƒ‚‚ƒƒ‚‚ƒ„ƒƒ‚‚ƒ‚‚‚ƒƒ„„„„„„ƒ„†‡„~ursyz}}yz~~‹…€‚ƒƒ„‚€‚„…†ˆ†‚€ƒƒƒƒ„ƒyroonmmkiipx~~}€‚€~zzz~„€ytty~€~~~~~}}}~~~~~~~~~}}~|}}}}}}~~~~~~}|}}}}}}}}~~}}}}}}{}||}}}}}}}}}}~~~~}}~~~~~~}}~~~~~~~~~~}}||||}}}~~ƒ}wz}€ƒ„…„‚{y{~|z|~~€~}}}||{{zyz|~€~}||||€€‚‚‚‚‚„„†‡‡ˆ‰ŠŠŠˆ†ƒ~yy{|||~~~€‚€€€€€‚ƒƒƒƒƒ‚‚ƒ„ƒƒ‚‚‚‚ƒƒƒƒ„„††„„ƒ„ˆŠ†~tuz}}~zz||~ˆ‰€|}€ƒƒƒ€„„‡ˆˆˆ‚‚ƒƒ„„€{tpoommkiipy~~}€‚€}yzzƒ~wtty~‚€~}}}}~~~~~~~~~~~~~~|}}}}}}~~~~~~~}~~~~}}}}~~~~}}~~|}}}}}}}}}}}|||~~~~~~~}}~~~~}}}}}}}}~~}}}}||}}~‚‚zxz„……„‚€~}{}€~|}~~}}|||||{zy{}€€~~~}}}|}‚ƒ„„ƒ‚€€‚„††ˆ‰‰‰‰Š‰‡„|z{|}}~~€~~~€€€€€‚ƒƒ‚‚‚‚ƒƒƒƒ‚€€‚‚‚‚„„…………„„ˆˆ†}sw}~~~{{~}†Œ‚{z{~€‚‚~~ƒ†‡‡ˆˆ}~‚‚ƒ„„ƒ‚~zuqommjhhqz~}~€€zwxy‚ƒ{wttx|‚€€~}}}}~~~~~~~~~~~~~~|}}}}}}~~~~~~~~~~~~}}}}~~~~}}~~}}}}}}}}}}}}|||~~~~~~~}}||||}}}}}}}}}}~~}}}}||}}~‚}yy|€‚„……ƒ‚€€~|~‚€~}~~}|||||{zy|€€~~~}}}}|~‚„„†„ƒ‚€€‚ƒ…†‡ˆˆ‰‰Š‰ˆ…€~{||}}€~}~€€€€€‚‚‚‚‚‚‚ƒƒƒƒ‚€€‚‚ƒƒ„„…………„…ˆˆ‡}sv{~|z}„ŒŠ‚|yyy|~€‚………‡ˆ‚}}€ƒƒ„„ƒ~xtqomjgjs{~~~zwy|‚zvstx|‚€€~}}}}~~~~~~~~~~~~~~|}}}}}}~~~~~~~~~~~~}}}}~~~~}}~~}}}}}}}}}}}}|||~~~~~~~}}}}||~~~~}}||}}~~}}}}}}}}}}‚{xx~‚„„„‚|~‚‚}~~}||||{{z{~€€~€~}~~}}}|~~„†‡…„‚€€‚……‡ˆˆˆˆ‰‰ˆ…€|||}}~~€€€}}~€€€€€€‚‚‚‚‚ƒƒƒƒ‚€€‚‚ƒƒ„„……††…†ˆˆ…tw|~~yy~‚Š‰ƒ{yyyyz{~‚€‚‚‚„ˆƒ}~€‚‚ƒ„„ƒ}wspnkglu{~€}{xz~‚~xustx|‚€€~}}}}~~~~~~~~~~~~~~|}}}}}}~~~~~~~~~~~~}}}}~~~~}}~~}}}}}}}}}}}}|||~~~~~~~}}}}~~}}}}}}}}}}~~}}}}~~}}}}€€€xy|}ƒƒ„ƒ‚€}ƒƒ€}~~}||||{{{}€~~}€~~}}}~~€ƒ†ˆ†…‚€€‚„…†‡ˆˆˆˆ‡‡…‚|||}~~~~€€€||~€€€€€€€€‚‚ƒƒƒƒ‚€€‚‚„„„„……†††ˆˆˆ…€vx{}{yx…‰„|yzyyzzz}€€~~‚„ˆ„||€€‚ƒ„„„„€|wrokglv|€€‚~|zx|€ƒ{ttssx{‚‚€€~~~~~~~~~}}~~~~~~}}}}}}}}~~~~~~~~~~~~}}}}}}}}~~}}~~}}}}}}~~~~}}}~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}~}€{xz}„…„€‚€~~€€~~}}{zyz~€€~~~~}|}}}}}€‚…‡…„€€€„…††ˆˆˆ‡‡‡…ƒ~}|}}~~}€€€€€‚‚~||€€€€€€€‚‚‚‚ƒ€€€€€‚‚‚„„„„††‡ˆ‡‡†‚xw{{{xz€……|wvwyyyyy|€~|}~€‚…‡†}|‚‚„„„„‚}ysoljlu}€€€€}zx|‚‚yrqssw|‚‚€€~~~~~~~~}}~~~~~~}}}}}}}}~~~~~~~~~~~~}}}}}}}}~~}}~~}}}}}}~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}|~€‚~zx{}€‚„„‚€}~~~€€~~}}{zz|€€€~~}}}}|}|}|}||‚„„„‚€€€„……†ˆˆ‡‡††…„‚€~|}}|}}~€€€€€‚‚‚~||€€€€€€€€‚‚‚‚‚€€€€€‚‚‚‚‚ƒƒ……†‡††„‚zvzzyv{‚„€wvvwwwwwwyxz{|€‚„…‡‡}{ƒ„„ƒƒ‚€|wqljnv}€€€€}yx~‚vsrsqv{‚‚€€~~~~~~~~}}~~~~~~}}}}}}}}~~~~~~~~~~~~}}}}}}}}~~}}~~}}}}}}}}}}~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}|}‚|yy{~€‚„„€|{}~~€€~~}}{z|€€€€}}|{|||{}}}||€‚ƒ‚€€€ƒ„……†‡‡†„……†…‚€~~}}~€€€‚‚‚~|}€€€‚€€€‚‚€€€€€€‚‚‚‚‚ƒƒ„…„„‚|vzzwu{‚‚}vuvwvvvvurrsz~‚ƒ……†‡†ƒ}{~€€ƒƒƒ‚ƒƒ‚ztliox~€€€€|yz}vttspty‚‚€€~~~~~~~~}}~~~~~~}}}}}}}}~~~~~~~~~~~~}}}}}}}}~~}}~~}}}}}}}}}}~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}~~}‚€~zxy|~„„‚€|{|}~€€~~}}{z}~‚€~~|{|{{z|{}}~||€‚€€€€ƒƒ„…†††…„……‡‡„€~||~€€€‚‚‚~|}€€€‚‚€€‚‚€€€€€€€€‚‚‚‚‚‚‚‚‚ƒ„ƒƒƒ‚}wyyvsz‚zvuvvuuuusons{ƒ…†††‡†„|~€‚ƒƒ‚„…ƒ}volqy~€€€€~{y|€zurtroty‚‚}}~~~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}~~}}}}}}}}~~~~~~~~~~~~~~}}}}}}}|}}}}}}}}}}}}}}}}}}}yxz}€ƒƒ€€}{|}~~~~~~}|{z~€~~~}{zy{{|}~~}}~€€€€„„„„…„„„…‡ˆˆ…„‚}}~€€€ƒ‚|}€ƒƒ~~€€‚€€€€€€€‚ƒƒ‚‚€‚‚~ywurrz€~wtttutttupkpy~ƒ†………‡ˆ‡…~~~ƒ„‚‚ƒ‚ypmty}€€€€€||y}‚€ysrrqoty‚‚~~~~~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}~~}}}}}}}}~~~~~~~~~~~~~~}}}}}}}|}}}}}}}}}}}}}}}}||~€{xx|€‚‚€}||}~~~~~~~~}|||€~~~~}{zyz{}~~~~~€€€€€ƒƒƒƒ„„„„…‡ˆˆ†…ƒ€~~~€€€ƒ‚€|}€‚„„‚€~€€‚€€€€€€€ƒƒ‚‚ƒ„}€~~‚yurqrz€|vtttututrmmw|ƒ†………‡ˆˆ‡‚~‚‚ƒƒƒ{soty}€€€€€|||ƒ}xssspoty‚‚~~~~~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~}}}}}~~}}}}}}}}~~~~~~~~~~~~~~}}}}}}}|}}}}}}}}}}}}}}}}|||~zvy|‚‚}|}~~~~}}~~~||}€}~~~~}{|{{{{}~~~€€~~~€€‚‚ƒ‚‚ƒƒƒ„…†‡‡…„ƒ€€~€€ƒ‚€}~€ƒ…†„€~€€‚€€€€€€€ƒƒ„„„ƒ€z|}~~zuqqryzutttuuvtnlr{~~€…………‡ˆˆ‡„€€€€€‚ƒƒ‚|vruz}€€€€~||~€{vsssnnuz‚‚~~~~~~}}}}~~~~~~~~~~~~}}~~~~~~~~~~~~}}}}}}~~}}}}~~}}}}~~~~~~~~~~~~~~~~~~}}~~~~}|}}}}}}}}}}}}}}}}{{{~€€}wwz}‚‚€~|}~~~~}}~~~|~~€}}~~}}{|}|{z|~~~€€~}}~~€€‚‚‚‚‚ƒ‚‚„……††„„ƒƒ€€€€ƒ‚€€€ƒ††…ƒ€€‚€€‚€€€€ƒƒƒƒ„|wyz}}}‚}wqrry}yttttuuurmnx~~…†……‡ˆ‡†‡ƒ€€€|}€„„ƒ}vsu{}€€€}||~~yusttlnv{‚€€~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~}}||}}}}}}~~~~~~~~~~~~~~}}~~}}}}}}}}}}}}||||||€€€€{yx{}‚‚}||~}}~~~|}~}}~~}}|z{{z{{|~~~~~}~~€‚‚‚€‚ƒƒ„…††…„„ƒ€€‚€‚‚„†ˆ‡„‚€€€‚ƒƒ€€‚ƒ{wy{}}€‚yposz{xuttuuttpjp{~~~~††††‡‡…„‚€€€|z~€‚‚ytv{~€€~}}‚€{vssurlksx‚€€~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~}}}|}}}}}}~~~~~~~~~~~~~~}}~~}}}}}}}}}}}}||||||~~{xy|~€}{|}~~~~~}~~~}}~}}}{{{zy|}}~~~~~}~~€‚‚‚€€€‚ƒ„†‡……„„ƒ‚‚€‚‚„…ˆ‡…‚€€‚ƒ„„‚€€€€‚„{xz|~€‚‚}qor{|wuttuutslmu|~~~~~„†††‡‡…ƒƒ€~zx}€‚‚{vx{~€€~~„~xusstrlksx‚€€~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~}}}}}}}}~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}~~||||||}}|~}zyz|€‚}{{|~~~~~}~~}}~}}|{{{zz|~~~~‚‚~}~~€‚‚‚€€€€ƒ„†‡……„„ƒ„ƒƒ‚€€‚~‚„…‡‡…‚€€‚ƒ„ƒ€€€€‚„‚}€‚‚„ƒsprz{wtttuuuqkqz}~~~~}€†††‡‡…„„€yuz}€‚‚€|xx|~€~€‚‚|vssssqkmuz‚€€~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~}}}}}}}}~~~~~~~~~~~~~~}}||}}}}}}}}}}}}~~||||zz|}}~|zyy}ƒ~{{|~~}}~~~}}}}||{|y{z}€€€~~‚€~~~~€‚‚‚€€ƒ„†‡„„„„ƒ„„„‚€‚~‚ƒ„‡‡†ƒ€€‚„…„€€‚„ƒ‚‚‚‚„…€tps|{vtttuutpku~}~~~~~€†††‡‡…ƒƒ‚€€yrw}€‚‚€{wy}~€€~€‚zurssspkmv{€€€€~~~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~}}~~~~}}zzzxyz|~~yvz~‚‚{z{}}~}~~€|}}~{z{}~{|{}~|}~€~~~~€€€€ƒ„„„‚ƒƒƒ„††ƒ‚‚€€‚€~€‚ƒ††…ƒ€€€€„…„‚‚‚‚ƒ…ƒƒƒƒ‚ƒ‚ƒ††wrs{{wuuttusmkx~~~~~~~‚‡……††„€€ypty~€‚€|yz|€€€€~€}yusqrrmjlv|€€€€~~~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~}zzzxyz{~€€}yx{€‚|z{|}~~}}~~~|||{yz|}}||}~~~|}~€~~~~€€€€€€‚ƒƒƒ‚‚ƒ………„ƒƒ€€‚€~€‚ƒ††…ƒ€€€€€€€ƒ…„‚‚ƒ‚ƒ…„„ƒƒƒƒ‚ƒ……‚yutz{vuuttupjlz~~~~~~~‚†…„……ƒ€€€zpqw}‚€}z{}€€€€~€~yvsrqrqmloy~€€€€~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~}zyywwxy|~€|yz}€{z|}}}||~~~~{{{zz{|||||~~}}|}~~€~~~~€€€€€€€€€€‚‚‚€ƒ…………ƒƒ‚‚€€€‚€~€‚ƒ††…ƒ€€€€€ƒ„ƒ‚‚‚ƒ‚ƒ…„„„„ƒƒ‚ƒ……‚|wuyyvtttttojq|~~~~~~~‚…„‚„„‚€€€{qpu{|{{}€€€€~€€}xtqqqqqpqv|€€€€~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~€€~~~~~~~~~~~~~~~~~~~~}}~~~~~}~}zyywvxy{€€{z{‚{z|}||z{~~}{zzz{}||||||||}~€~~~~~€€€€€€€€€€‚‚‚€‚„……„ƒƒ‚€‚€~€‚ƒ††…ƒ€€€€€‚ƒ‚‚„„ƒ‚ƒ…„„……ƒƒ‚ƒ„„ƒwuxxwtttttnjs~~~~~~~~‚…„ƒƒ€€{qorx}€{||~€€€€€~€}zxtrqqsstv{‚€€€€€€~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~€€€€~~~~~~~~}}}}~~~~}}~~~~~~}~~}yxxvwwxy}€€€~zx}|y{{z{y|~}}zzxyz{{{||}}€}|}~€€~}|||}€€€€€~~~~‚ƒ„„ƒƒƒƒ~€ƒ‚€€‚€‚†……ƒ€~€€€‚„ƒ‚‚‚‚‚„…„„……„ƒƒƒ‚‚ƒ€ywyyustutrjkw}~~~~~~~„‚€€€‚€€|sprx}||}€€€€~€}yxwvtrsxwx}€‚€€€€~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~€€€€~~~~~~~~}}}}~~~~}}~~~~~~}~~}yxxvuuwxz}€€€}zy}€|yyyyyz~~|{zzzyyxy{||}}~}|}€€~}|||}~€~~~~~~€€€€‚ƒƒ‚‚‚‚€ƒ‚€€‚………ƒ€~€€‚€‚„ƒƒ‚‚‚‚ƒ…„„……„ƒƒƒ‚‚zwyxtssssqimy}~~~~~~~€‚€€‚€€{qpqv{~~}|}€€€€~zxxzzxvx|~‚ƒ„„€€€€~~~~~~~~}}~}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~€€€€~~~~~~~~}}}}~~~~}}~~~~~~}~~}yxxussuvx{|€€€|{|~}zxxy{}~~|{|{zyxxz{||}~~}}}~€€~}||||}~~~~~}~~~~€€€‚‚‚€€€‚ƒ‚€€‚ƒ†…ƒ€~„‚‚„ƒƒ‚‚‚‚‚„„„……„ƒƒƒywyxtsssrniqz}~~~~~~~€€ƒƒ‚€€{qpquz~~}|}€€€€~}xwy|€||‚„„……€€€€~~~~~~~~}}~}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~€€€€~~~~~~~~}}}}~~~~}}~~~~~~}}~}yxxussrtwy||€€~}}|}{xxz}}|{||zyxwxz{||}~~}}}€€~}||||}}}~}}}}~~~~€‚‚€€€‚‚ƒ‚€‚‚ƒ†„ƒ€~‚ƒ‚„ƒƒ‚‚‚‚‚ƒ„„……„ƒƒƒƒywwwtstspljsz}~~~~~~~~~€€€„…‚‚€€{rpquy}}}|}€€€€€~{vwy}‚€€ƒƒ……†…~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~€€~~~~~~~~~~~~~~~~~~~~~~~~~~~~{yyvtqrrvwy}~€~||{zz|~~}||{xxxxz{{{|}}~}}~~~€~}|||}|||}~~||}}}}}}~~~~~~€€€€€€€€ƒƒ„ƒ‚€‚‚€ƒ„ƒ~~}~~}‚ƒƒ‚‚‚„„‚‚ƒƒ„„„„„„‚€€~xvxwusssqkjt|~~~~~~~~~}~~ƒ…‡ƒ‚ƒ€€uoquw|}}}~€€€€€|yuw{~‚ƒ‚‚‚„„……†~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~€€€€~~~~~~~~~~~~~~~~~~~~~~~~~~~~|{zwtqrrtvy|}€€€~}|{{{}€€~}|}|yxxwx{{{{|}~~}}~~}~}|||}|||}}}||}}~~}}~~~~~~€€~€€„„„„ƒ‚‚‚~ƒ„ƒ~}~~|}ƒ„ƒƒƒ‚‚„„ƒƒƒƒ„„„„„„‚€€€}vtwvusssqjlw}~~~~~~~~~~~†‡‰…ƒƒ€~topuw|}}}€€€€€{xvw|‚ƒ„‚ƒƒ„„††‡~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~€€€€~~~~~~~~~~~~~~~~~~~~~~~~~~~~{zzwtqqqrvy||€€~|{z|~€€}}}}{yxwwx{{{{||~~}}}~~}~}||{|{{{|||||||}}||}~~~~~~~~~~€‚„„„„„ƒ‚‚‚~~„ƒ€~}~~|}ƒƒ„„„„„„„ƒƒƒƒƒƒ„„„„„„‚€€ztrxwvtssqjmz~~~~~~~~~~~„Šˆ‡…ƒƒ‚~uqrvx|}}}€€€€€~zxxy~€‚‚ƒƒ……†ˆ‰Š~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~€€€€~~~~~~~~~~~~~~~~~~~~~~~~~~~~zxywtqpprvz||~~~zzz|€~}||{yzywwy{{{{||~~}}}~}}~}||z{zzz{||||||||||}}~~~~~~~€‚ƒ…†…„„„ƒ‚‚‚}€„ƒ€~}~~}~‚„„……„„„„„„……ƒƒ„„„„„„‚€€~wtrwvvtstqio{~~~~~~~~~~€‰Œˆ†…‚ƒƒ€€~wsuwy|}}}€€€€€}zyy{€‚‚ƒƒƒ„…ˆ‰‹Œ~~}}~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}~~~~~~~~~~~~~~~~~~}}}}}~~~~~~~~~~~~~~{xvusrqqsv{}~~|~~€€{z|}€€}zzvwyyxxyz{{{||}~~~~|}}~~~~}{{{{{{zz{{{{||||{|}}}}~~||}}~~~ƒ„„…ƒ‚‚ƒƒƒ‚€~ƒ‚€€~}}€ƒƒ‚‚ƒƒƒƒ‚„„ƒ„„„„„„ƒƒ‚€|xutuwwuusojoz~~~~~~~~~„Œ‹†………ƒ‚}wxz{~~~~€€€€~{y{~€‚ƒ„„‡ˆ‰Š‹~~}}~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~}}}}}~~~~~~~~~~~~~~|xuusrqqtw{}}}{|}€€|{}€~{yxvwxyxyyz{{{||}~~~~|||~~~~}{{{{{{zz{{{{||||{{||}}~~}}~~~~}€ƒ„……„ƒ€‚„…ƒ~ƒ‚€€€~~€‚‚‚‚‚‚‚‚ƒ„…„„„„„„„ƒƒ‚~xwuuuvwuutpkpz}~~~~~~~†Œˆƒ„††ƒ‚€}}~€€€€€€~{z}~€ƒƒ„…‡‰ŠŒ~~}}~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}~~~~~~~~~~~~~~|xvttsrrtx{|{{z{{|~~~~}|yxwwwxzyyyz{{{||}~~|||~~~~}{{{{{{zz{{{{{{{{{{||}}}}}}}}}}}€ƒ††…ƒ€€€‚ƒ„‚€‚…„‚‚€€‚€€€€‚ƒ„„„„„ƒƒƒƒ‚~{vvuutvvvutpkpz}~~~~~~~~†‰„‚…†ƒ‚€€€€€€€€€€€€€~{{~€‚‚…„†††‡Š‹~~}}~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}~~~~~~~~~~~~~~|xwuusrrtx{|{{{}{}}}~}€}{xxwwwwyyyy{{|{|||~~|}|}~~}}{{{{{{zz{{{{{{{{zz||}}||||}}}}}…‡…ƒ‚€}~€„ƒ‚„†…„ƒ€€€‚€€€€ƒ„„„„‚‚ƒƒ‚~ywuuuuuwvutqkqz}~~~~~~~~~‡†€}‚…†ƒ‚€€‚‚‚€€€€€€~{{ƒ…„……„††‡Š‹~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~~~€€€€~~}}~~~~~~~~}~~~~~~~~~~~~~~~~~|zwwvtssty|||z{{||}|{}€~{xvwxxyyzzyz{|}||}}~€}}}}}}||zzzzzzzzzz{{{{{{{{{{{{{{{|||~~‚……„€~~~~ƒ„‡†…„‚€€€‚€}|}~~~‚„„ƒƒƒƒ‚‚€}zxuutuuwwvsqnqz}~}}~~€‚…ƒ~€‚„„ƒ‚‚‚‚ƒƒƒ„ƒƒ‚‚‚€}||~ƒ„††„„„„†ˆŠŒ~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~~~€€€€~~~~~~~~~~~~}~~~~~~~~~~~~~~~~|zwwwussuz|||z{{|||}|}€€}zwuwxyzzyyyz{}}}}}}~€~}}}||||zzzzzzzzzz{{{{{{{{{{{{{{{|||~~€ƒ„…‚~~~~‚ƒ…††…„ƒ‚€€‚‚€~{|}~~‚„„„„ƒƒ‚‚~|xvutuuvwwsploy~~~€€€ƒ„ƒ…„€€„„ƒƒƒƒ‚‚ƒƒ„„ƒƒƒƒ‚€€~|~‚„†‡ˆ†„‚‚ƒ†ˆŠ~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~~~~€€€€~~~~~~~~~~~~}~~~~~~~~~~~~~~~~|zwxwuttvy|||z{{|||~~€}{xuwxyzzyxyz{{|}}}}~€€}}|{{{{zzzzzzzzzz{{{{{{{{{{{{{{{|||~€„…ƒ~~~~~~~€€€ƒ„„„ƒ‚ƒ‚‚‚ƒƒ„„ƒ‚}~}}}‚„„„„ƒƒƒƒƒ€}zxutttuvvsolnx~€€ƒ„††……ƒ„†…‚‚‚ƒ„„„„ƒƒ‚‚ƒƒ„„ƒƒƒƒ‚€~ƒ…†ˆˆ‡…ƒƒ†ˆŠ~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~~~~€€€€~~~~~~~~~~}~~~~~~~~~~~~~~~~|zxyxvtuwy|||z{{||{|}€}||zxwwxzzyyzz{z{||}}~€}}|zz{{zzzzzzzzzz{{{{{{{{{{{{{{{|||~~ƒ„ƒ€~}}~~~~€€€‚‚ƒ‚‚„………„„ƒƒ€}~~}}€ƒƒƒ„„ƒƒƒƒ„‚}|zutssvvuqolo{€„……††‡‡†…„††„‚‚‚„„„„ƒƒ‚‚ƒƒ„„ƒƒƒƒ‚€€€€„†‡ˆˆ‡†…ƒ‚‚ƒ†ˆŠ~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~~~~~~€€€€~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~|{xyywuvx{|||{||||{|~€}||zwxyyyyxyyz{{}}}}}~}~€€}}{{{{{{{zzzzzzzz{{{{{{||||||||||||}~€‚~}}}~~~~~~€€€€€€„†‡‡…††ƒ€}}‚ƒƒƒƒƒƒƒƒƒ‚€€~}yvtsttppnnt~ƒ…††††††…„…………ƒƒƒƒ……††„ƒ‚‚‚‚‚‚‚‚ƒ„ƒ‚„ƒƒ†‡ˆ‰‡…ƒƒƒƒ„ƒ…†ˆŠŠ~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~~~~~~€€€€~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|{xyxxwxz|||||||||{}€|{{{yz{{yyyzyz{|}}|||||}~}}{{{||||zzzzzzzz{{{{{{||||||||||||}~€‚€}}}~~~~~~€€€€€€€€€‚……ˆ‰ˆ……ƒ‚‚ƒƒƒƒƒƒƒƒ€{xustrqqsv{€„…†††††…„„…………„„„„…………„ƒ‚‚‚‚‚‚‚‚‚„ƒ‚ƒ„†††ˆ‡†„ƒ‚ƒƒƒ…„…‡ˆ‰‰~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}~~~~~€€€€~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~|{xxxxxy{{||||||}|}€€zyyyyzzz{{{|zzz{}}{{{{{{|}}}{{{||||{{zzzzzz{{{{{{||||||||||||}}€~}|}}~~~~~~€€€€€€‚ƒ†‡‡‡†…„„‚‚‚€€‚‚‚ƒƒƒƒƒ‚‚€~{yvtruuy}„…†…………„ƒƒ…………„„„„„„„„†„„„‚‚ƒƒ‚…†ˆˆˆˆ†„ƒ‚€€ƒƒ„„…†‡‡‡~~}}}}}}~~~~~~~~~~~~~~~~~~€€~~~~~~~~~~}}}}}}}~~~~~€€€€~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~|{xxxxyz{{||||||}}€zwvyyyz{{{|}{{{{|}{{{{{{||}}{{{{{||||zzzzzz{{{{{{||||||||||||}}}~}|}}~~~~~~€€‚‚‚ƒ„…††……ƒƒ‚‚‚€€€‚‚ƒƒƒƒƒƒ‚ƒƒ€~{xwvy{|€‚„„…„………„‚‚…„…„ƒƒƒƒƒƒ„„†„„„‚‚€€€€‚ƒƒ……‰‰ˆ‡„„„‚ƒƒƒƒ„…†††~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~}}}}~~~~~~~~~~~~}~~~~}}~~~~~~~~~~€€~~~~~~~~~}~~}}~~{zyxxwyz{{||{||||}€€}yttvwz{{||~}|zzzz|{|zz{||}~}|{{z{{{||{{{{z{{{{{{{|}|||}}}~~}}}}}~~}}}}}}~~~~~~~~~~~~€‚ƒƒƒƒƒƒƒƒ‚ƒ‚€€€‚ƒƒƒƒƒƒƒ„„‚‚€|||€€ƒƒ„…„……„„ƒƒ„„ƒ‚€‚ƒƒ„††……‚€€ƒƒ„„„‡ˆˆ†„……‚ƒ‚ƒƒƒƒ„„…………~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}~~~~~~~~~~~~}~~~~}}~~~~~~~~~~~~~~~~~~~}~~}}~~|{yxxxzz{{||{||{|}‚‚}zusuw{||}}~}{zzzz{z{zz{|||~}}{{z{{{||{{{{z{{{||||}}|||}}}}}~~}}}~~}}}}}}}~~~~~~~~~~~~€€ƒƒƒƒƒƒ‚‚€~~€‚ƒƒƒƒƒƒ„„ƒƒ€€~€‚‚‚ƒ„…†……††„„€}~€€‚‚ƒ…††……ƒ‚€€€ƒ‚„„„†‡ˆ‡„……ƒƒƒ‚‚‚‚ƒƒ„„„„~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}~~~~~~~~~~~~}~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~}}~~}|zyyyyy{{||{||{}~‚€{vstw{||~~~}{zzzz{yzz{z||{}}}|{z{{{||{{{{z{{{||||}}|||}}}}}~~}}}~~}}}}}}}~~~~~~~~~~~~‚‚ƒƒƒƒƒƒ€€€~~€‚‚ƒƒƒƒƒ„„ƒƒƒ‚€‚‚‚ƒƒ„…‡ˆ‡‡‡‡„‚€~|z~~€‚‚„…††……ƒ‚€€€€‚ƒƒƒ…‡Š‰………ƒƒƒ‚‚‚‚ƒƒ„„……~~~~}}}}~~~~~~€€~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~~~~~~~~~~~~}~~~~}}~~~~~~~~~~~~~~~~~~~~~~~}}~~}|{yyyxx{{||{{{|~‚‚~{xutwz||~}|}{zzzzzyzz{z||{|}}|{z{{{||{{{{{{{{||||}}}}}~~~}}~~}}}~~}}}}}}~~~~~~~~~~~~€€ƒƒƒƒƒƒ€~~~€‚‚ƒƒƒƒ„„„„„ƒ‚ƒ…ƒƒ„„„…†‡‡‡…†‚€~}|z{|}~€‚„…††……ƒ‚€€€‚ƒ„„ƒ†Š‹‡……ƒƒƒ‚‚ƒƒ„„~~~~}}}}}~~~~~}~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~}}}~}{yyzyyxzz|||{{|~‚}zwuuvz|}|}}|{xxxyyzxy{{||||||zzz{{{||{{{{{|||}}}}}}||}}}}~~~~~~~~~~}}}}}}}}~~~~~~~~~~~~€€ƒ‚ƒƒƒ€~~€‚ƒ„„„„„„„…„‚ƒ„…„„……†‡††……‚{zzyy|~‚ƒƒ†††‡‡††„ƒ€‚€‚‚ƒƒƒ„ˆŠˆ…„‚€€€€ƒƒ‚‚~~~~}}}}}~~~~~~~}~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~}}}~}{yyzyyzzz|||{{}‚ƒ}zwuuuy|{z||{zxxxyyzxyzz{{{{{{zzz{{{||{{{{{||}}}}}}}||}}}}~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~€€‚‚‚‚‚€€€~~€€€€€‚ƒƒ„„„„„„……„„„……†‡‡ˆˆ†„‚€}}|zyy}‚‚„…†‡ˆˆˆ††††„ƒ‚€‚‚ƒƒƒ„‡ˆ‰…‚€€€€€‚‚~~~~}}}}}~~~~~~~~~}~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~}}}~}{yyzyyzzz{{{{|~‚‚€~|zwuuux{{z{{{zxxxyyzz{yyzz{{{{zzz{{{||{{{{{||}}}}}}}|}}}}}~~~~~~~~~~~~}}}}}}||~~~~~~~~~~€€€€€€‚ƒƒ„„„„„„…………„……†‡‡ˆ‰†ƒ€€|z{€„…‡‡‰‰‡††††††…„‚‚‚‚ƒƒ‚ƒ†‡‰…€€€€€€~~~~}}}}}~~}}}}~~~}~~~~~~~~~~~~~~}}}}}}~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~}~~}}}~}{yyzyyzzzzz{|~‚‚‚€~}|zwutsw{}|{|{yxxxyyz{{yyzz||||zzz{{{||{{{{{||}}}}}}~}}}}}}~~~~~~~~~~}}}}}}}}~~~~~~~~~~€€€€€€€€€€€€€‚ƒƒ„„„„„„…………„…„…††‡ˆ…‚€‚‚€}ƒ†ˆ‰ˆ‰ˆ†„„……††…ƒ‚€€‚‚‚ƒƒƒƒ…†‰…‚€€€€€€€~~~~}}~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|zzzzzzzz{{zz|‚€|zz|zwuvuvy|||}|yyvvwxzz{yzzz{{{{{z{{zz||||{{{|}~}}~~~~}}}}}}~~~~~~~~€€~~}}}}~~~~}}}}}}~~~~~~€€€€€€€€€€€€€€€€€‚‚ƒ……„„„……†„„ƒƒ„„„„……„ƒƒƒƒƒ‚‚‚€ƒ…†‡‡‡†…ƒƒ…………†…ƒ~‚‚ƒƒƒ„……‡†‚€€€€€€€€~~~~}}~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~€|z{{zzzzz{{z{~€ƒ‚~|{zzyxvttvz}|}||yyvvwxzz|{zzz{{{{{{zzzz||||{{{||}}}~~}}}}}}~~~~~~~~€€€~~~~~~~~~}}}}}}~~~~~~€€€€€€€€€€€€€€€€€€‚‚ƒ……„„………†„…„ƒ„„„„„……„„„„ƒƒƒƒ‚„…†††…„„€‚„†……‡‡„‚€~‚‚ƒƒƒ„……‡‡ƒ€€€€€€€€~~~~}}~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~€|z{{{zzzz{||}€‚ƒƒ||{{yxvqrv{}|||{yywwwwzz|{zzz{{{{{{zyzz{{||{{{|{|||~~~~}}}}}}~~~~~~~~€€€€~~~~~~~~~}}}}}}~~~~~~€€€€€€€€€€€€€€€€€‚‚ƒ…„„…………††……„ƒ„„…………„„„„„„„„ƒ„„…„„„ƒƒ€„†……‡ˆ†„€‚‚ƒƒƒƒ„…‡‡…‚€€~~~~}}~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~~~}}}}~~~~~~~~~~~~~‚€|z{{}|{zz{|}‚ƒ‚€}}{zzxxvqru|}|{zyyyxwvwyz|{zzz{{{{{{zyzzzz||{{{|{|||~~~~}}}}}}~~~~~~~~€€€€~~~~~~}}}}}}~~~~}~€€€€€€€€€€€€€€€‚‚ƒ…„……„……†††…„ƒ„„††‡‡…………„„„„…„ƒ…ƒƒ„ƒƒ€„…‡ˆ‡ˆˆ†‚‚‚‚ƒƒƒƒ„…†††„€~~~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~}}~€€}{{{~}|{z{{}ƒƒƒ|zyzzxwvtqv|~||zxwywwuwxy{{zzy{{{{{{{zz{zz{{}}}~|{}}}}}}}}}}~~~~~~~~~~€€€€€~~~~}}}}}}}}}}~~~~~€€‚‚ƒ„……‡†…………††„ƒ………†‡†‡‡‡‡…………„…„„ƒƒ„„‚€€‚„†‡ˆˆ†„ƒ€€‚‚ƒƒ‚‚ƒ„…†‡…‚€~~~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}~€}|{{|{|{{|~‚ƒ‚~|{zyyzzxvsqw||zxwwywwuuvxzzzzy{{||{{{zz{zz{{|||}|{||||}}}}~~~~~~~~~~~~€€‚€~~~~}}}}}}}}}}~~~~~~~~~~€€€€‚‚‚„„„………………†‡…„……„†‡†‡‡‡‡‡‡‡‡„…„„ƒƒ„„‚€€€„…†‡‰‡…ƒ‚‚ƒƒ‚ƒƒ„„…†…ƒ~~~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}~~}|{{{z||{|ƒ„„€|z{zyyyyxvqpv|}{yvvwyxxuuuxzzzzyzz{{{{{zz{{{{{{{|}|{||||||}}~~~~~~~~~~~~€€‚‚‚‚~~~~}}}}}}}}}}~~~~~~~~~~€€‚‚ƒƒƒƒ„…………†‡‡†……ƒ…††‡‡‡‡ˆˆˆˆ†…ƒƒ‚‚ƒƒ€€€€ƒ„…‡‰‡…ƒ‚‚‚‚ƒƒƒƒ‚ƒ„…†……‚€}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~€€€€~~~~~~~||}~~~~}|{{{{||z}‚„„€|{||zywxytppu{|yyvvwyyxvuvwyyzzyzzyy{{{{z{||{{{{{||{||||{{}}~~~~~~~~~~€€‚‚‚‚~~~~}}}}}}}}}}~~}~~~~~}}~~€€‚‚‚‚‚‚ƒ„………†‡†………ƒ„†‡‰‰ˆ‰‰‰Š‰‡„‚‚‚‚€€€ƒ„…‡‰ˆ…ƒ‚‚‚‚ƒƒƒƒ‚‚ƒ„…†…ƒ€€€€€}}}}}}~~~~~~~~~~~~~~~~€€€€~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}|~~|||{{{{{z}€ƒ…ƒ~|}~~{{yxwspotx{zyvvvwwxyxxwxxyzzy{zz{{{{{|}}||||}}}}}}||||}}~~~~~~~~~~~~€ƒ‚‚€~~~|}}|}}}~~}}}}}}||~~~~~~}}€€€€€€‚‚‚‚ƒ……††‡†††…ƒ„„†ˆ‰‰‰‰‰‹Š†…ƒ‚€€€€€~~~‚„†ˆ‰‡…ƒ‚‚‚‚ƒƒ‚‚‚ƒƒ„„……„€~~€€€€}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}~~|}}{{{{{{~‚ƒƒ}||~€€}|zxvroosxzyyvvvuwxzzywxxyzzy{{{{{{{||}}}|||||||}}}}~~}}~~~~~~~~~~~~€‚‚‚€~~~|}}|}}}}}}}}}}}||~~~~~~~~€€€€€€‚ƒ………†‡‡†…„„„†‡ˆ‰ˆ‰‰‰ˆ…„‚€€€€€~~~~„…†ˆˆ†„ƒƒ‚‚ƒƒ‚‚‚‚‚„„………ƒ‚€~€€€€}}}}}}~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~€€€~|~}{{{{|}‚‚€~||~|ywuqnnrvyyyvvvuwy{{zxwwyzzy{{{{{{{||}}}|||{{{{||}}~~}}~~~~~~~~~~~~€€‚‚€~~~}}|}}}||}}}}}}~~~~~~~~€€€€€€‚„……†‡‡††„„„…†‡‡‡ˆˆ‡†‚€}~~~„…††ˆ†„ƒƒ„„ƒƒ‚‚‚‚‚ƒƒ„………„€€€€€}}}}}}~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~€€€~~{{{{|€}{{‚‚€}ywurooquyyyvvvvxy||{ywwyzzy{{{{{{{||}}}|||{{{{{{||}}}}~~~~~~~~~~~~~€‚‚€~~~}}|}}}||}}}}}}~~~~~~~~€€€€€€€€€€€„……††‡‡†„„ƒ…††††††…ƒ€~~~~~~}~~~€ƒƒ……‡‡„„„„„ƒƒ‚‚‚‚‚‚ƒ„…†…ƒ€€€€€}}}}}}~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~||}~~~~~~~~~~~~~~~~~~}}~~~€€€€~~~~~~€€}|{z{z}€}{||~‚‚€}yxvsqpquyyxwuuwyz}}|{yxyyzxzzz||{|}}}}||||{{{{zz{{}}}}|~~~}}}}}}~~€‚€~~~}}}}}}}}|}}}}}}}~~~~~~~€€€€€€€‚ƒ…†…‡‡†…„„„„…‡†…„~~~~~}}~~~~‚ƒ……ˆ†…„„„„ƒ‚‚‚‚‚‚„„……‡„ƒ€€€€€}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~||}~~~~~~~~~~~~~~~~~~~~~~~€€€€~~~~~~€€€€€}|{z{{~€~|{|}~‚‚€}yxxuroptwxxwvuwz|}}|{zzyyyxyzz{{{|}}}}{{{|{{{{zz{{|||||}}}}}}}}}~~€‚€€~~}}}}}}}}|}}}}}}}~~~~~~~€€€€€€€„……‡‡‡……„„ƒ„††„‚€~~~~~}}~~~~€ƒ…†‡†…„„„„ƒ‚‚‚‚„„„†‡†‚€€€€}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|}~~~~~~~~~~~~~~~~~~~~€€€€~~~~~~€€€€~|{z{}€~}|{||~‚‚~zyxusppsuxxwwvwy{|}||z{yyywxyyzz{|}}}}{{{|||||{{{{{{{{{|||}}}}}}~~€‚‚€~~}}}}}}}}|}}}}}}}}}~~~~~€€€€€€€€€‚„……††……„„ƒ„††ƒ‚€~}~~}}}}~~~~ƒ„…‡†…„„„„ƒ‚‚‚€ƒƒ„†ˆˆ‚€€€€}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|}~~~~~~~~~~~~~~~~~~~~€€€~~~~~~€€€€}{z{~€}|{{|}~‚‚zyxuspprtwxwwwwxz{}|{z{zyxwxxxzz{|}}}}z{{{||||{{zzzzzz{|||}}}}}}~~€‚ƒ‚€~~}}}}}}}}|}}}}}}}||~~~~~€€€€€€€€ƒ……††…†„„ƒ„††…ƒ€~|~~||}}~~~~~€‚„…††…„„„„ƒ‚‚‚€ƒƒƒ†ˆˆ„‚€€€€}}}}}}~~~~~~~~~~~~~~~~~~~~~~~}~~~~~~~~}}~~}~~~~~~~~~~~~~~~~~~~€€€€€€€€~~~~~~€€|{{}ƒ~|{{{|}€‚€~|ywvsooqsuwxvwwxyy||{||zyyxwwxyyyz{{}}{{z|{{{{zzzzzzyyzz{{{|||}}~~~~~~‚ƒ‚‚€~}}}}}}}}}}}}}}}||}}~~~~€€€€€€€€€€€„„…††…†………†………‚€~}}}}}}|}}~€ƒ„……††„ƒƒƒƒƒ‚‚€€‚„„†‰ˆ†ƒ€€}}}}}}~~~~~~~~~~~~~~~~~~~~~~~}~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~€€€€€€€€~~~~~~€€€€€|{{~€‚€|{{{|}€‚~zxxtpopqsvwvxyyyy{{{||zyyxwwxyyyz{{}}{{{|{{{{zzzzzzzzzz{{{|||}}}}~~~~~ƒ‚‚€}}}}}}}}}}}}}}}||}}~~~~€€€€€€€€€€€ƒƒ…†……‡‡‡††………ƒ€€}}}}}}|}}~€‚„……††…„„„„ƒ‚€€€‚ƒ„†ˆˆ‡ƒ€€}}}}}}~~~~~~~~~~~~~~~~~~~~~~~}~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~€€€€€€€€~~~~~~€€€€€|{|‚‚€€{{{|}~€‚‚‚€~zyxtpopoqtuvyzzyy{{{||zyyxwwxyyyz{{|||||{{{{{zzzyyyzzzz{{{|||}}}}~~~~~€‚‚ƒ‚€~}}}}}}}}}}}}}}~~~~~~~~~~€€€€€€€€€€~‚ƒ…„…††‡††………„‚}}}}|||}}~€ƒ…„†††…„„„€€‚„†‡Š‡„€}}}}}}~~~~~~~~~~~~~~~~~~~~~~~}~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~€€€€€€€€~~~~~}~€|{|€‚‚~{{{}~~€ƒ„ƒ{ywtqppmnqtuxyyyyzz{||z{{yxxxyyyz{{{{}}|z{{{{zzzyyyzzzz{{{|||}}||}}~~~€‚‚ƒ‚‚€}}}}}}}}}}}}}}~~~~~~~~€€€€€€€€€€~€ƒ„„„…†ˆ††…………„‚}}||{{|}}~€€„„††„ƒƒƒƒ€€€€€‚„†‡Šˆ…€~~~~~~~~~~~~~~~~~}~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~€€€€€€€€€€~~~~~~}}€€€€~|}~‚ƒ€~|{||}|~€‚„ƒƒ}zxurqpnnoqsvwzxyyz}}}|{{zyxyyyzzyz|||||{zz{zyyzyxxzzzzzz||||||||}}~~~~€‚‚€~}}}}}||||||}}}}}}~~~~€€€€~~~~ƒ„ƒ…†‡††……………ƒ€~}{z{{|||~~~~‚‚„……„„„„ƒ€~~‚€‚ƒƒ…ˆˆ…‚€€~~~~~~~~~~~~~~~~~}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~€€€€€€€€€€~~}}€~|~€ƒƒ~}|{||}|~€‚„ƒ‚€~zwtrnnonnpstwzz{yy|{||{{z{{yyyyyyz||||||{{{zzzzyyyzzzz{{||||||||||||}~~€‚‚}}}}}||||||}}}}}}~~~~~~~~€€‚ƒƒ„†‡‡‡……………„}|z{{|||~~~~‚‚„………………ƒ‚€}}€€‚ƒƒ„ˆˆ…‚~~~~~~~~~~~~~~}~~}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~€€€€€€€€€€€€~~}}€‚‚‚€~}‚ƒ‚~|{{||}|~€‚‚‚€~{vtrnnonnprsvyz|zxyy{{{{zzzyyyyyyz||||||||{zyyyxyyyyzz||||||||||{|||}~~~€‚€€~}}}}||||||}}}}}}~~~~~~~~~~~~~~‚‚ƒ…‡‡‡……………„‚€~|{{{|||~~~~€‚‚ƒ………………ƒ‚€}|~€€‚ƒƒ„ˆˆ†ƒ‚‚~~~~~~~~~~~~~~}~~}~~~~~~~~€€~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~€€€€€€€€€€€€~~}}‚ƒƒ€~~‚ƒ‚|z{||}|~€€‚‚~{wsqqponnprsvxyz{xyy{{{{zyyyyyxxyz||||||||{zzyxxyyyyzz{|||||||||||{||~~~€€‚€}}}}||||||}}}}}}~~~~~~~~~~~~~~~€‚‚„‡‡‡………………‚€}||||||~~~~€‚ƒ………………ƒ‚€}{~€€‚ƒƒ†ˆˆ‡ƒ‚‚‚klmnooonooortuvvwxzzzzwurpqqppppqqrssuxyzzyyyy||}}~€€€€€€€€€€}~~}}}}}}}}{{||||||||||||}}~~~€‚ƒ„„…………††……††„ƒ‚|sjd^[]_ada_][^cehhe``bglsz€„……„„{qf]YX]bjtz~{urppmifaadgknprvyz€€€€€€|wpmjjjjjjjkknqqtuuuuuvvuwvwyxupf[XTV[^choy€‚‚ƒ€€~~|xvtomjggcdfgghfhjjmprtttttsrrqpoqutuuz|zvplhbbdca\Z]dknttsrrsuwz~‚ƒ…‚kmnopppopppsuvwwwxzzzywurpqqppppqqrstvwyzzyyzz||}}}€€€€€€€€€€}~~}}}}}}}}||||||}|||||||}}~~~€‚ƒ„„…………††††††„ƒyohc_\^_ac`]\Z]aehgda`beiqxƒ……„„‚zrh]YX[cisz~€~zvspomhfaacfkmnknsy~€€€€€€~}ztnkiihhhhiklnpqtuuuuuvvuwvwyxupf\WTW\^chpx€€€€~~|xusnliggcdfhhigijjmprtttttsqqqpoqsrqswy~~zuplgbbcba[Y\ckottsrrsuwz~ƒ„†ƒlnopppoqrrstuvwwxzzzzxvtrpqqppppqqrsuvvxzzyyzz{{||{}~~€€€€}~~}}}}}}}}}}||}|~|||||||}}~~~‚‚ƒ„„…………††‡‡††„ƒ}wogc_]^_bb]ZYX[_dggeb_acfmu~‚……„„‚{si^[Y[bhpx}~|xtrpmnifbbbejmonorw{€€€|zvqkihhhhhhiklnortuuuuuvvuwvwyxupg\VUW\^cgpw€€€€~~{wtpljihgdegiiiijkjmprttttsrppqpoqsrprvz||ytqmhcbcb`\Z]ckruusrrsuwz~ƒ…ˆ…npoppposttvuvwxxyzzzzxvtrpqqppppqqrssuvwzz{{zz{{||{|~~€€€€~~}~~}}}}}}}}}}||}}~~||||||}}~~~‚‚ƒ„„…………††‡‡††„ƒ|vngc__^`cb]ZXWZ_dhjhc`aaent}‚……„„‚{ria][\bgnu||zurqommifdbbdinsvwwv{~€€~~€{ytojhhhhhhhhilnortuuuuuvvuwvwyxupg]VUW[^dfow€€€€€~~{wsnliiihffhiihjjkimprttttsqppqpnqsrptx{{{xtqlheddc_\Z\dmtvvsrrsuwz~„‡ˆ…moqqrrrstutuvwxxy{{{ywusqpppppqqrrrstuuwyyy{}}|||||}}~}~~~~~||~~~~}}||}}~~~}|}}}}~~~~‚ƒƒ„„…………†††††„ƒ‚}vmga]^_cda]ZXVY]elnida``emt|‚ƒ………ƒuleb``chot{yvtqomnjihgdcdhow}ƒyx{€}{xrnjhhhiiiiijlnosuwvvvvwwvwwxxyupg]VUVZ^dhow~€€€€€€€€|zurojghhgghhjjikjkmnpstsuuropppoprrqqtxz||wsqkgecca_[\`elsutssstvxy}„‡‰†nooqssqrtuuvwyyyy{{{yvvrqpppppqqsstttvvwyyy{||||||}}}~}~~~~}}~~~~~~}}||~~}|}}~~~~~~‚ƒƒ„„……††‡‡†††„ƒ‚~vnd]\^acca_ZVUX`jqrld``aflrz€ƒ………ƒ€zrhc``dhmrxwsqnllmjjjjfdeit~†…{prz~~|xsplhhiijjiiijmoprtvvvvvwwwwwxyzvqg]WUWZ^dgow|€€~{wromighhffgijjjklmmmpsutttroooomprqprtxz{zwsniecaba_^_bflsusrrtuuxz~ƒ‡ˆ‡nonprrqrtuvwxyzz{|{{yvvrqppppprrssuuuwwxyyy{{{||||}}}~}~~~~~~~~~~}}}}~}|}}~~~~€‚ƒ„„……††‡‡†††„ƒ‚wnb]^^cccb`YTTZdotunea`bfkqwƒ………„{slfaaeilottpnmlllllljhfgkwƒ…ƒynqx‚€zvqlihjiikkjjjkmoqrtvwwwwwwwwwxyzxri_XTW[_cgnu|€€~}zvsoljhhhhffgjlllmnnllpsvtssroooomoqportxz{yvrlhca`ac``bcgnstrqqtuuwz…ˆˆˆnnmnqqrsstuvwx{z{|{{ywvtqppppprrssssuwwxyyy{{{||||}}}~}~~~~~~~~~~~~~~€€€}|}}~~~~€€€‚‚„„……††‡‡†††„ƒ‚xnea`_dee`^XUT\hqwwngaaadkqw~ƒ………„{sliccfjlortpnmlklkmmlkhhnv~ƒ„wpq{€‚~xuoljhhjjkkjjjknpqruwwwxxwwwwwxyyxskaYTV[`cgmt{~~~{xsnljgghhhgfgkmmmonlllpstsssronnpnpqoortyz{ytpmjdb`aeccdfjnrsrqqtuvxy†‰‰ˆnnmnpqsttuuvwx{|~~|{ywvurqqqppqrrtsuwxxxxxzz{{z{}}}}}~~~~~~~~}}~~~~€€€~~~~~~~€€€€‚‚ƒƒ„„‡‡‡‡…………„‚~ynhebdefc^\WUV_lu{zqjba`ciou|„„…„‚|uqjeefikoppomllkkmnnnjhinv}‚‚xqqx}wsnkhhiijjkkiiknoqruvxxxxxxxvxxyyytkbZVVZ`chms|~€~}ytojihghfefghimoooonljkpsssuuroomnppommptxxzxspmida_aebadimrturpqtuwwy}†ŠŠ‰nnmnpqstuvuvwx|}~~|{xvutrqqqpprssuuwxxxxyyzz{{y{}}}}}~~~~~~~~}}~~€€€€€€~~~€€€€€‚‚ƒƒƒ„††††……………‚yoigcdeec^\WUYdpx}|skda`bflt{€ƒ„…„ƒ}wskecgijloonmllllopnolklow~‚~xrpt|~yuqlkiijlnmmkiilopqsuwxxxxxxxwxxzzyukc[XW[_bgms{€~~~}xuqmihihgdefhjknooooljjkpsssttronmnppommquxxxvromhb`_cebdhkqtvsqpqtvwww|†ŠŠŠnnnnpqtuuvvwyz}~}}{zxvutrqqqrrrsuvwxxxxxyyzzzzy{}}}}}}~~~~~~~~~~~€€€€€€€~~€€€‚‚‚ƒƒ„……††……………‚€zrkhdcdec`^YW]ht{}vohc``dkry‚„…„„xrkdbfhjknonlllllppoonnqrx€{wtoow}|vrpkhjklooonkijlppstwxyyxxxxyxyyz{ytlcZWY]_bglsy~}zxtpoliggfedefijkmoooolijmpsssssromlnppommquxxuspmkfbaaeeehlotwvsqpqtvvuuy…ŠŠŠnnoppqtuuvxy||~~~{ywutsrqqqrrqrtuvwxxxxzyzzyyy{}}}}}}~~~~~~~~~~}}€€€€€€€‚‚‚‚‚ƒƒ„………………………‚€ztmhdbceb`^ZW`lx~~xqjd`_cjrx}€„……„€yskfaceimmmmkllllqpppmotuy€{ussojs{{vqojhjmnooqokiiknouvxyyyxxxxyxzz{{ytlc[XX\^afksy|~{vtpmjjhgedccefjlnnooooljjlpsssssromlnppommrvxxsqnkifa`cefhloqvywrppquvuqtx…‰ŠŠklnopqsuvwxzz}~~}{zxvutsrqqqqrrsuuwxxyyyz{{yyz|}}|||}~~~~~~}€€€€€€€€ƒƒ‚ƒƒ„„„…………†„…ƒztnheccb`^][\dpz‚€ztjda^bhnt{€ƒ„…ƒ{tmkfdeikjijkkkmnrssrstvxz}ysnnqphluvtokjjklnoqrpkhikmoswxyxxxxyyyyzzz{zume^ZZ\_aglrwz{}}{xspligffdbbcdfhmnppponmljhlortsrsronlnpqnmnqtvurqnmkgc`bffjoruxzwrpqruvtrtx„‰‹‹jlnopqsuvyyz{~~~~}{zxvutssqqqqrrsuvwxxyyyz{{zzz|}}|||}~~~~~~€€€~€€€€€€€€€€€€€‚‚‚ƒƒƒ„„……………„„‚€ztnhdbba`^]\^ju|€ƒ€zumgb^aflsz€ƒ„…ƒ|vomhehjkiiijllmpsuustvwx{{uoijoplhpspkijnpoprstpkhikmoswxyxxxxyyyyzzz{zvnf_ZZ\_bgkqwz{|zxupligeddcbbdehjnoprponmljhlpstsrsronlnpromnqstroolljfcacfhmrvy{zurprsuvtsrv‚ˆ‰Šjlnopqsuvyy{}~~~}|{zxvutssqqqqrruvwxxxyyyz{{{{z|}}|||}}~~~~~€€€€€€€€€€€€€€€€‚‚‚‚ƒƒ„„…………„ƒƒ‚~xslfb``ba`^^cox}ƒ€{vqkc``fkqyƒ„…ƒ}wpnigikjhijkllnqtvuttwxyyzunlnqolikoniikoqonrttpkhikmoswyyzzyyyyyyzzz{zvnfa][]_bgjowz{zwtrniggecbcddefiknoqsponmjhhkpstsrsspnlnpromnoqqolkkkhdccegjqvz}}{usrstuwtsrv€‡‡ˆjlnopqsuwyz|~~~~}|{xvutssqqqqrruvwxxxyyyz{{yyz|}}|||}}~~~~~€€€€€€€€€€€€‚‚ƒƒ„„…………„ƒƒ}wpieb``a``_`eqz~‚ƒ€{vrlea`eiow~‚„…ƒyrniehkjiilmlloquwvuuxzyxxwrpttmkjhllgjmppmmsttokhikmoswz{zzzzyyyyzzz{zvnfa^[^_bhinwz{yuqnkgeecbacffegimnppqponmjhgkqttsrstqnlnpqnmnnnnmjjiifdddfjlsz}~zurstuvwtsrt†‡‡lmooopqvxz|}~}}{yvutsqqqqppqrtuvxyyzyz{zzyy{{}}yz|}|}~~}}~~€€€€€€€€€€€‚‚€€‚‚ƒƒ„„„„„ƒƒƒ{unie`]]``^^biu|‚ƒ‚{tlfa`bgpv|„„„}{vpkigklihknmnpruvvvv{}zwwvvwyvkhiimqmnptvrmoqrpkiijmprwyzyyyxyyyyzzzzywohc`]^`cfkotxyusnligddb_abdffhlmpqrrpmmnjhhkrvutstspnlmopmklllkjjhffcbbegknt{~~xtrrtvwwtqpu}ƒ††mmnpoprwy{~~~}}{ywutrrrrrpprttuvxyzzzzz{{{{{{{{y{|}|}~~~~€€€€€€€€€€€‚‚€‚‚ƒƒƒ„„„ƒƒƒ‚€zrlgb^\]___afnv~‚‚ƒƒ}unhbabhpv|€ƒ„„ƒ~}wrljghjjijllmpsvvxxw|zuuyz{yslgfglqrqt|snqrpljijmprwz{yyyyzyyyzzzzzxoid`^^acfjnrvwspmjedccb`bceghjmmppqqomklkiilrvuuttspnlmopmihiigfgeedcbadgkrx|}wtqqtvxvsqqt{‚……mmmooprwy|~}}{yvutsssrrrrsttuvxyzz{zz{{||{{yyz||}}~~~€€€€€€€€€€€€€‚‚€‚‚ƒƒƒ„„„ƒƒƒ}xqje_]\^__`chqz‚‚ƒƒ‚~wpjdbagnuz‚„„ƒ€~xrmjgghkjjklmqtvxzzz~ysuz}}vqnheeiostz‚‚|tpqrpkiijlorw{|{{zzzzzz{{zzzxqjd`^^acfhlpttqljfdcbbdddeghjlmmnnppnlklljjnrvuuttspnlnppkhgfffeeedccbaehlrx}}{wspqtvwurqprz……mmmnnprxz}~}}{ywvusssqqrrqstuvxy{{{||{{zz{{yy{||}~~~~€€€€€€€€€€€€€‚‚‚‚ƒƒƒ„„ƒ‚ƒ‚€|xphc^]]_```clu{‚‚ƒƒƒxrkfb`flrx~„„‚~ysmjgghjimlnoruxz{{{€zsuz~|toojfeglru{€~xtrrrpigijkorw{|}}{{zz||{{z{{xslc`^^acfhlorqnjhedcbbdddgiklnoommppnllmmllnrvuuttspnloqpjfeeedcbddccacfilrx{~|{wsqttvvuqqpqy€„„llnooqtw{~~~~~||{yvvusrrqqpqqsuvvwwzzzzzzzzzzz{{{|}~}~~~~€€€€€€€€€€€‚‚‚‚‚‚‚‚‚‚‚‚‚‚€€€‚‚ƒƒƒƒƒƒƒ‚~ysngc`__```aemw‚‚‚ƒƒƒ€{umg``chou|‚ƒƒƒysnjfehjjklprtwyz|z|~wqw~~yurokjihjoqx|xrquvtojghklprw|}||{{{z||{{{{{xrkda__bcegjnpojhefdbabbcgihjmoonmmoommnomkkosuuuuuspmlopoicbeea`adcdddffhmrx||{zuqqssuurqqppx‚‚lllnorux{}~~~~||zxvvtsrrqqpqrsuvwxxzzzzzzz{{zz{{z{}~}~~€€€€€€€€€€€€‚‚‚‚‚‚‚‚‚‚‚‚€€€‚‚ƒƒƒƒƒ„„€{vnjfc^]\_``bhnx‚ƒƒƒƒƒ€}woi`_bgns{ƒƒƒ~ysnieehjjklptxz{zywyzxus{{yupmlkjlmryyuqrxyvqkhhklorw|}|||{{{{{{{{{ywqkea``bdegjmnniecedcacccfhijnoonmnppnmmnmlkpsuvvvvtqnnpqnhbbdda``bcddddghmry}|{ytqqssutsrrrrw~‚‚lllnqsvx{{}}~~{{zwvutrrrqqprstuvwxxzzzzzzz{{zz{zz{|}}~}€€€€€€€€€€€€€‚‚‚‚‚‚‚‚‚‚‚€€€‚‚ƒƒƒƒƒ…„€|wsmhdb^\Z``adjqz‚ƒƒƒƒƒ€~xqkb_bflrz€ƒƒƒ~xtnieehjjklqty{|zywvvvvw}€{wrponnmot{xtsv||xtkhhklorw|}||||{{zz{{yyxvplfb``adghikkkhbacddbdfefhjknoonmoqqpmmnnllptvvvwvurpoprmgaabb`_`abcccdghmry}|zxtqrssuttsrqqu|llmoruvy{{}}~~zzywuusrrrqqqsstuvxyxzzz{{{{zzzzzzz{{|}~~€€€€€€€€€€€‚‚‚‚‚‚‚‚‚‚‚‚€€€‚‚ƒƒƒ‚ƒ„‚~ytplgcc_[Z_`cfkt{‚ƒƒƒƒƒ€xrmc_afjpyƒƒƒ~ytnkhehjjklqtx|}|}zuvvtv|‚‚€|ytttqpopu|ysu{~}xsmihkmprw|}|||||{{{zzwwwvplgd``aehhhhghfcaddddghhikklnoonnpqqoooonlnrtwuuwwsponoplfaaaa_^aaabccefhmry}|zxurqssuurqqqqsz€€klmoqswxyz{|~~~|zyxwvutrqqrrrtuvuvxxwxxx{{z|zz{{{{{||}~~€€€€€€€€€€€€‚‚‚‚‚‚‚‚‚‚‚‚€€‚‚‚‚‚‚ƒƒƒƒ€|uqmgec`^\]__bgpy‚ƒƒƒ‚ƒƒ‚zune`bekqw}‚‚€xvpjfdfhjkmsw||}}yuutu|‚}zyxzvtursv{wqt|~}xrlhhjloqv|}}}}{|||zywwwvtrnie```cijjgggecacddhhijkkklnonmmmpnmpqqonnruvttvvspnopplf`aa^^__aabcdggimsx|{xvrprstvtqrrqqsy~klmoqtwxyz{|~~{zzyxvvttqqqqqrsttuvxxxxxyzzz{zz{{||||}}~~€€€€€€€€€€€€‚‚‚‚‚‚‚‚‚‚‚‚€€‚‚‚‚‚‚ƒƒƒ~ysnjeec`^]]abeirz€ƒƒƒƒƒƒ‚€{vofaaejpv|‚‚~yupjgeghkknuyz}}„‡zzyz}€zwyyvrpqrtx{xsu|~}yskghjmprw|}}}}||||zywwvusroiea``chhieeeebbdeeiijjkkjlnonmlmnllorronosuwuuvvtqnoppkeaba^]]^addcfiijnty|{xvrprstvtqqqpprx~|kmmoruwxyz{|}}{zyxxwuttqqqqqqrssvwxxyyzzyyyzzz||||||}}~~€€€€€€€€€€€‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚ƒƒƒ|vpjfeec`___cdhmu|€ƒƒƒƒƒƒ‚~|voga`dhnu{‚‚}zuqmhfghkkpvzy~†Šƒ}~~}}xvttroopruxzxuu}~}xrkghjnpty|}}}~}}}|zywvutspmidbaadffedffebceggjjjjjjiknonmlllllprrpoosvxuuvvtqnoppkebba^]\_adeehjjkosy{zxvrprstvtqooppqw{znomosuwxyz{|}}|zxwwvutsqqqppqrrsvxxxyyzzyyyzzz||}}||}}~~€€€€€€€€€€€‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚ƒƒƒ|ytmieeec`^_bdejqx|€ƒƒƒƒƒƒ‚~|vohb`cgmu{‚‚~zupohdfhkkow{|ƒ‰ˆ‚~}}}|xutqqnmpqsuwzxux}~}wqmihjlosx|}}}~}~~|zywuusrplidcabedddeggebcfhikkkkiiijnonmkkllmqrrqpotwxttvvurnoppjd`aa]_\_accgikklotyzyxvrprstvtqooooqwxwmmmotuvxxz{{||}{xwwvvusrppqqrsstwxxxyyzzzzyzzz{{{{||~~}€€€€€€€€ƒƒ‚‚‚‚‚‚‚‚‚‚‚‚ƒƒƒƒƒƒ‚‚‚}wqlieeeca`adegksz€ƒƒƒƒƒƒ‚‚|vohb`aglsz|wtpmieegklpvz{‚†…€zvtvxxurqnlnnortuxzxvw~€~wqokiklnqv{}~~~~~|{ywussttqmhebbbeeeffggggfgjkjkmjijjjnmmllmmnopoqroqtvutruvtqmnqokc``bbb``bcdfijllqtxzyxuqqrtuvsqpppppuutmmnptuuvxy{{||{zwvvuvusqqqqqrrstxxxxyyyzzz{{zz{{{{}}~~}€€€€€€€€€€‚‚‚‚‚‚‚‚‚‚‚‚‚‚ƒƒƒƒƒƒ‚‚‚|wqljgfdcbacfgilt|€‚ƒƒƒƒƒƒ‚‚~xpicaaglry~€€~{vspmjgehknquy{€‚„‚zupnqrtrqolkmoptxvx{zux~~xqnjikloqv{}~~~~}|{ywtsssspmhebbbefggggghhhhjkklmjijkkmmlklnmnnppsspruvutruvtqnoqokc`_cccaacdfgjjlmruxzytqqqruvuqopppppstsmnoqsttuwxzzzzzywwuutsrqqqqqrrstwwxxyyyzzz{{zz{{{{}}~~}€€€€€€€‚‚‚‚‚‚‚‚‚‚‚‚‚‚ƒƒƒƒƒƒ‚‚‚|wplifeedddeghkpw~€‚ƒƒƒƒƒƒ‚‚ƒzslgbcfkpw}|ytrpnkhehlpptx{‚„‚~wspmnpqppnmmmotxzyy{xuy~~xqmiiknprw{}~~~~|{zxvstssrolhfcaadgghhhhiijjlmmnnkjijjkkkjlmmmlprssrsvwuttvwtqopqokeaaddcbbeegijijnrvyyxurqqruwtpnppppoqrqmmprrtuvwxyyyyyxvvvvsrrqrrqqqrstvvxxxyyzzz{{zz{{{{~~~~}€€€€€€€‚‚‚‚‚‚‚‚‚‚‚‚ƒƒƒƒƒƒ‚‚‚|vpkjfdedfeggimsy~€‚ƒƒƒƒƒƒ‚ƒ‚~zrniacfjov}~{xsppokiefknotx{‚„‚|vqqmmmnnnmmnmqvz|}yxuuz€~xqmiiknpsx{}~~~~|{zwvssrrqnkggc`adgghhhijjkkmnmonljiiijjkjklomkpqrttuwwvtuvwtqopqokfccdddccegjkjjkosvyxwurqqruwtpoppppopponoqqrqrswxyyyxxxvvutsrqppppqrsttuvwwwxyz{{{{||}}~~~~~~~~~€€€€€€€€€€‚‚‚‚‚‚‚‚‚‚ƒƒƒƒƒƒƒƒƒƒƒƒ}wpjhgfgghhhjkov~€ƒƒƒƒƒƒƒƒ‚~{tojeceiot{~|zvpppomifgjnoswz~€zuppnmmnnnnnmotwzzxuu|~xpkiiknsuy|~~~~}|zxvvtttsrmjhieabdgjklkikkmnpqqpnljfggijkkmmmnmprtttwyywtuvvtpopqolfcceddcbdhjlkkkosxxxvrqppsvxvrpnponnnoonoqqrqstvxyyzyxwuuttrqqppppqssttuvwwxyyz{{{{||~~~~~~~~€€€€€€€€€‚‚‚‚‚‚‚‚‚‚ƒƒƒƒƒƒƒƒƒƒƒƒ~{unjhhgiiijjlmpw€‚ƒƒƒƒƒƒƒƒ‚{upjgceiosy|{xuppppnifeinorvz~{vqommoqomnonqvx}‚~ytty~xpkijknsvz|~~~~}|zxvvtttsqnkiifbbdimpqommnnpqrspmkjehhijjjlklmnprstuxyyvuvvvtpopqolfeedcccbdhjlllkptwxwvrqppsvxwsqnolkkknnnoqqrrstuwxxyxwwuussrqqppppqssttuvvwxyyz{{{{{{}}~~~~~~~~€€€€€€€€€‚‚‚‚‚‚‚‚ƒƒƒƒƒƒƒƒƒƒƒƒ~ysnjiijkkklllnsy€‚ƒƒƒƒƒƒƒƒ‚€|vpkfceimswzywtqppomjgdgloquz~ƒ}xsomnqpnmmooqvzƒ„€{vuz€~xpljkkorwz|~~~}|{yxvvtutspoljhgccglostspopqrqstqmkjghhijiihiklnprsuvxyxutuvusoopqolfffdcbbbdhjmmnlpsvxvurqppsvxwtqonmlkknnnoqqrrstuvwwwwwwvvssqpqppppqssttuvvwyzyz{{{{{{||}}~~~~~~€€€€€€€€€‚‚‚‚‚‚€€‚‚ƒƒƒƒƒƒƒƒƒƒ‚‚€~wrljjjklmmnnmouz€‚ƒƒƒƒƒƒƒƒ‚‚}wpkfdcgmrvyxutqppnlkhdgkoquz~€ƒ€}wrpnpqnljloory|€ƒ…‚zxwz€~xplkklprw{|~~~||{yywwuttrqmkkhgcfknrtvurqrsttutqnljiiiijiigijknpqsuuz{xursvuroopqolfddcbbbbdhjmmnmnrvwvtrqppsvxwtrpnmkjjmmopqqrsstvwwvwwwwvuttrqrqqqqrrsttuvwxyzyz||z{{z{{|||}~~}~}€€€€€€€‚‚ƒƒƒƒ‚‚‚‚ƒƒƒ‚‚€€|uojijkmnonponqv{‚ƒƒƒƒƒƒƒƒƒƒ‚‚~xqlgddglpuwwvsqoomlkiehknosw}|vrplmlggjlnpuz‚„†ƒ{vuz~wpkklmoqv{}~€€}}{yzxwutrqonkjhgfhnrvyywusrsuvwurnlijjjhiiiggjknqrsvvxyyustvuqooqrpledbcbabcehknnmmnsvwutrqopsvwwtqomkjjjlmppnnqrrtvwwvvvuuuusrsrrqqqqrrsttuuwxxyyzzzz{{yzzzz{|}}}|}|~~€€€€€€€€€€€€‚‚ƒƒƒƒ‚‚‚‚ƒƒƒ‚ƒ€€yrliijknpqpponrv|‚ƒƒƒƒƒƒƒƒƒƒƒ‚‚ysmiecfkotuvurqoollljegimosx~€~}{wrpljgfgkloru{„…†ƒ{vsw}~wpkklmoqv{}~~~~}{{zyxvtrqonljiggjrx{||zwvstwxxvsoljihhhiiihhkmnqssuwyzyustvvroorrpjddbbbabcehknnnmosvvutqqoqsvvvtqomkjiilmppnnqrrtvwwvvvssttsrsrrqqqqrrsttuuvwxyyzyyzyyvvvttvy||{z{z{{}~€€€€€€€€€€€€‚‚ƒƒƒƒ‚‚‚‚ƒƒƒ‚ƒ~wpjihiloprqqpnrw|‚ƒƒƒƒƒƒƒƒƒƒƒ‚‚€zuniecekorstsqpoommljeeimpqw}~}|{xspljghikmosx~‚…„†„|vrv}~wpkklmoqw|~~~~}}|zyxvtspooljihhmt{~~|yvtuxyyvspmlhgghiiikjjmnqsstvz{yustvwrporrpgcdb```acehknonmpswutsppqstuvvtqomkjhhklppoopqqtvwwvuussttttsrrqqqqrrsttuuuvwxyzyzzxvrqqpprwzzyyywyyyz|€€€€€€‚‚ƒƒƒƒ‚‚‚‚ƒƒƒ‚‚€|voghghmpqqpqposw|‚ƒƒƒƒƒƒƒƒƒƒƒ‚‚€{vpiebfjnprsqqpooonljfejopqu{||{zxsplijijloot{ƒ……‡†vsx}~wpkklmoqw|~€~}}|{zxvusqnoljihinw}€}zwuwy{{vspmkighhiiijjikmqsstvz{yustwwsqorrphedb__`acehkoonmptwusrppstuuwwtqomkjhhjknoooopqsuvwuttuuuuttssrrqqrrssttuvvwxyy{{ywusolkijmqxxvttrstsvy}~€€€€~€€€‚‚‚ƒƒƒƒƒƒƒƒƒƒƒƒƒ{tmhghjkoooooopt{‚‚ƒƒƒƒƒƒƒƒƒƒ‚‚€|vqjdaeimopqqppppnonjhginpptz}}}{xtqmjiiimnqv}ƒ…††‡‚yuw‚wpkkjkoqv}€€€~}|zyxvuqpnolihhjszƒzwvw{|zwtpmkjihiigiiiklnoqsuwy|yutuwvrrqrsnfabb``_`cfhknnnnquvtrqqqstuvxwtpmljihhjknooooprsuvvuttuuvvssrrrrqqrrssttuvvwxyy{|ywspmjifgjntsqokjikmquz}~~€€€€‚‚‚ƒƒƒƒƒƒƒƒ„ƒ‚‚}wokhffhjnoooopqu|€‚‚ƒƒƒƒƒƒƒƒƒƒ‚‚}wqjebdgknopppqppopokhginpqsy}}~{wuqlkiiinosx~‚„………†ƒ}wx~~vokkjkoqv}€€€~}|{yxvuqpnpmjiiku|ƒ„‚|xwx||yxurpmkihggfgiiklopqsswz|zvuvwvrrrssmd`ba```adfiknnlmquvtqoppstuvvvrnmljihhjknoppprstuvutttuuuussqqrrrqsssttuvvwwxyy{{xuqljihfegkonjgdcdfimptx{|~~~€€€€‚‚‚ƒƒƒƒƒƒƒƒ„ƒƒ‚~zrkhfdceimpppopqv|€‚‚ƒƒƒƒƒƒƒƒƒƒ‚‚~xrjecceilmpppqppprqmhfilpqsx|}}{wtqlljjjnqv{‚…………†„}uv}|vpkklmoqv}€€€~~}{zxwvrqopmkjjmv~‚„„ƒ}yxz||ywurqnlihfffghhjlpqqqrv{{yuwxwvrrrssme`a````adgilnnklquvtqopqsttuvvqnomljhhjknoqqqrstuvusttuussrrqqrrssttsttuvvwwxyy{zxtpljigfedhjhgcb`bcfjlpty{|~~~~~~€€€‚‚‚ƒƒƒƒ‚‚ƒƒ„ƒ€{vpidba`chmqpppptx|‚‚ƒƒƒƒƒƒƒƒƒƒ‚‚‚~xskfccfiklooprsssrsnheiloprw|||zusqnlkkjosx}‚…†††‡†ztt}|vqmmlmoqv}€€€~}{zywvrqoqnkjjow~‚„ƒ‚‚}zz||{ywurpljihggghhhjloprqtwz|wsvwwvrrrssme```__aaehjmnnlmquvtrqqrstsuwwqnonlkhhjkkmoopqqqstssttssrrrrrrrrssssststuvvvxyz{yxuqmkjhgeccddcbba`adffipv{}~}}~~€€€€‚‚‚‚‚‚ƒƒƒƒƒ‚€}ytnfc````fnqqqppv{~ƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚xrmfacfkjjlmoprssssoighknpqtz|{ytqqlkkllpvz~‚„…††††ztu}€~wpllklnrx}~~~}||zxvusqoqomkkpw}ƒ„€}{z{zyxwtrpmjigffiihgijnpqrsw{|yuuxwurrrrqja^_]^`bcfhkmnmlmrsuttrprtttuwwqnmmkiffhkklmnpqqqssssssssrrrrrrrrsssstttuuvvwxyz{yxvrnljhfdbaaaaa``accddekpv|}|}~~~€€€€€€‚‚‚‚‚‚ƒƒƒƒ‚€~{uqkfa]]`ciorrqqrx}€‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ysngbadijjkmoprtuutpkhhknoqtyzzvqnnjkklmrw|‚„…††††|tsz~|unkkklnrw|~~}|{xwvtrpqomlkqw|‚{yxzyxvusrpmihgggiiihikmpqrswy{xuuwwurrrrpi`]^]^`cefiknnmlmrssrsqprvutvwxsnllkifdhjklmnpqqqssssrrssrrrrrrrrrsssttvvvvvwxyz{yxvrnmjigecbabca``bdcdccfmrx{}}~~~€€€€€€‚‚‚‚‚‚ƒƒ‚‚~{vqlie_]\`ekpsrqrty~‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚{uoidacehjkmopstwwurkihjnoqtxyysnlljkkmpuy~€‚„„…††††€vrvz{unkkklnsw|~€€€|{yxvtrpqomlkqwz}€}yvvvtutsppolhgfhjiiihhkmopqswzywstvwuqqrroh`]_]^`cegilonmlmqrrqqqprvwuwyxtollkiedfhklmopqqqssssrrssrrrrrrrrqrssttvvvvwwxyz{yxvqnmmkigedcddb``ceedccfiouy{{}~~€€€€‚‚‚‚‚‚ƒƒ‚ƒ€~xrmheb^\]`fkqsrqrtzƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚|vokebdegjkmpquxyyvslihjmoptwxvrnkmkkknrv{ƒ„„…††††‚xtsyzupjjklosx}~€€~}{{ywurpqomllqvy{}{xtsrqrrqnmmkhgffiiihhgimopqsw{ywssvwuqprrog`]^]^_bdgjlonmlmprsrqqrtuvvwyxtollkidceglmpqppqqrsttttssrrsrrrrrrsttuuuuwwxxxyz{zzwtqqqpmifffeceddeecdbaadktxz||}~~~€€€€€€‚‚ƒƒƒƒƒƒƒ~xrmfc`^[\]akmqsrsvy~€‚ƒ„„„„„„„ƒƒƒƒƒƒƒ‚€}wqkeceggijmpqsxzzxunjhhoppsuvsnlkjkllnqw~‚ƒƒ…………††„{srxxupkjklnrw|€~}}||zxvsrrpnmmqtwy{}{xrqrnllnlmmkjfddeiiihgggkoppswz{zvtuutqqsrne^\\[_abegjnpnkilpqsrpppsuttuxxqmnnlhfdefmnppppqqrsssssrrrrrqrrrrssttuuuuxxxxxyz{{{yvttvvqmihhgfilmllhhdb`bfpvyzz{}}}€€€€€€‚‚ƒƒƒƒ‚‚ztnhda^\\]afmoqrsvy|ƒƒ„„„„„„„ƒƒƒƒƒƒƒ‚~xrkdadfhjjmprtx||zwpjhhmppqsrokiijlllnqx‚ƒƒƒ…………ˆ†‚yrqtwupljjknrx}€€€€}}}}|zywussrpnooruxy{wrmkkhgfhikmkhddceijjihhhhlmotxz|yutuutrrsrne^\\[_adfjmoqnkilopqpppqststuyyrmmmlhfdcemnppppqqrsssssrrssrqrrrrsstuuuuuxxwxxyz{{{zywwxxtqlkhilortrrolgbaacltwwxz|}}~~€€€€€€‚‚ƒƒƒƒ‚‚|wpjdb_^\_`dhmpqrux{~€‚ƒƒ„„„„„„„ƒƒƒƒƒƒƒ„‚xrleacdhjjmqsvz}}zxrkhhloppqmjghhjlllnqx‚ƒƒƒ……„…‡„€yrqsvtoljjknrx}€€€€~}}}}|zzxuusrpnpoqtvwwrnigfdcadgjnkhddceijjhjkkijjotxz|wstuutrrsrne^\\[^aefloopnkjmopppppqstsstzysnmmkgeccemnooppqqrsrrrrqqrrqprrrrsttuuuuuwwxxxyz{{{z{yyzzwtnmjlosuxwwsqjbbbcjpsuvyz||}}€€€€€€‚‚ƒƒƒƒ‚|unica^_^abfinqstvy}~€‚ƒƒ„„„„„„„ƒƒƒƒƒƒƒ„ƒxrlfabcghjmqux|~~{xtlghjnppmkgefejmllnqx‚ƒƒƒ……„……ƒ{tstvtnljijnry~€€~~}|}}|z{xwvtqpoonortvtmiddc`_`cgimkhedceikjhkmmljknswz|wstuutrrsrne^\\[`begknopnkkmooqqqqrtvvrsxxqmlljgebdflmmmmonpqqqqppqqppqqqqrsstuuvvvvwxxxyz{|{{{{||{{wunnmptwxzzzxsldbbcfimqsvyz{||~~€€‚‚ƒƒƒƒƒƒ‚ypkgeb```bdfkorsuy~€‚ƒƒƒƒƒƒƒ„„„„ƒƒ‚‚ƒƒƒƒ}tmhb`dghikrvy~~}zvnhgknqmifebdehkklnrx‚ƒ„„„„…………‚~xvwwuplkijosx}~~}}}||}|{{yxvtqnpnnnpsusnf```^]^cfilkkgdcfjklloonnlknqw{{xtuvvussrqog]Z[^bfhhknpomjjmppssqqsuvvtsxxqnmlihebefklmmmnnpqqqqppqqppqqqqqrttuuvvvvxxxxyz{|{{||{{{{xvrqqtwyy{{{zupifcbcdfilprrstw|~}~€€‚‚ƒƒƒƒƒƒ‚€}vnifdcaaabeknpruw{€‚ƒƒƒƒƒƒƒƒ„„„„ƒƒƒƒ„„„„‚}voicbcgiikquy}€~{vpigjmnjgcdcdehlkmnrv}‚ƒ„„„„……„ƒzxyyupkiijotx}~~~~}|{||||zxvtroonnmnqsrme`_`^^^bgjmnlhdcfiklnqqoomkmqv{{xuuvvusssrnf\Z\`cgjjloqpmijlnprsqqsuvvrtxwqnmkjiebedklmmlmnpqqqqppqqrrqqqqqrttuvvvwwxxxxyz{|{{||||{{zwuuuwyz|{|||xsnjgcb`bdgjkjkmrw|}~€€‚‚ƒƒƒƒƒƒ~zsnigdbaaefjmpptx{~ƒ„ƒƒƒƒƒƒƒƒ„„……ƒƒƒƒ„„ƒƒ~vqlfcchjkmrvy}€{xqjfhlnjebbceehllnoru|‚ƒ„„„„……ƒ€€}xvzzupkiijnsx}~~~~|yyz|||{ywusqnnnmnpqqld`_```aejmopmhdcfiknortqonlkou{{wtuvvusstsme\Y[aegkkloqplhijnprsqqsuvvquyvqnljjiebdblmmmlmnpqqqqppqqrrqqqqqstuuvvvxxxxxyyz{|{{}}{{yy{yxxvyz{}{{{{{uqkifc`abffffgimu{}~€€‚‚ƒƒƒƒƒƒ‚}wqlhecaabghlnotxz}€‚‚ƒƒƒƒƒƒƒƒ„„……ƒƒ„„……ƒƒ}vslebehjlnsx{‚€{ysjefjnjd`acefhlmnqsv}‚ƒ„„„„……ƒ€€|vtyyupljjkmrx}~~||xwxx||~|{xusqnnnnopqpkdbddbcdjmpppkgdcehkpqswrpoljnt{{vtuvvussrqme[YZ`fhkllpqqkhjimorsqqsuvvuuyvqnkjihebcckjjklmoqrrqqppqqrrrrrrstuuttvvwwxxxxyz{{||{|||{{|{zz{{{{||||{zvsplid`acaa_`cehox{~€€€€‚‚ƒƒƒƒƒƒƒ‚€{vqljgda`dikortv{}€‚ƒ‚ƒƒƒƒƒƒƒƒƒ„……†„„„„……„„ƒ~zslfcegjlpuz~‚‚}yskfehlkc`acehlnnnotx~‚ƒƒƒƒ„ƒƒ„‚|trwzupllmnmqx}€€€}yurrwz{|}|zvspnonnnqrnkeffhjlorsutpkfbbeilorvzwrnjint{{vtuvwtrssrlcZYV_gkmmnpqojggjmpsspqtuxwstxupoljjhecdfkijklmnpqqqqppqqrrrrsttuuuuuvvwwxxxxyz{{||{|||||||{{{{{{||||{zwtrnjfceb^\Z[^`bluz~€€‚‚ƒƒƒƒƒƒƒ‚ztolihfdehmptvy{ƒƒƒƒƒƒƒƒƒƒƒƒ………†††††…………ƒ|tmfcdgjmqv{~‚‚€}xtlgfhlkf`adhjnpomou{€‚ƒƒƒƒ„„„ƒ‚}wqqwzuplllmorx}€€€{vplmtx{{}{xurponnnnopokijlqstvwwxwqkfbcegjnrx{ysnhgmtzzvtuvwtrstslc[YW_imnmopqmiggjnruspruvwusuxupnlkjhedegjijklmnpppqqppqqrrrrsttuuuuuvvxxxxxxyz{||||~}}}}||||{{||||||{zxxtqljggc`][[\^`hqx}€€‚‚‚‚ƒƒƒƒƒƒƒ‚~xrmkhgggjnsvxy|~‚ƒƒƒƒƒƒƒƒƒƒ„„……††††††…………ƒ€}umfcdgjosw}‚‚‚~xtnhgilkhbbfjmqrpmou|€‚ƒƒƒƒ„……‚yroovyupllklnqx}€€€xsjffnuy||zwspoommlmoppnnotwxxyyyzwrlfbcegjmqx{|unfejszzvtuvvsrstsne^\[ahlnooppmighjostrrsvwwutvxuomlkjhfefhihjknomoppqqppqqrrrrsttuvvuuvvyyxxxxyz{|||}~}}}}{{||{{||||||{zz{urnljgda_]]\^`enw}€€‚‚‚‚ƒƒƒƒƒƒƒ‚|wqlkhggjnsw{||€‚ƒ‚ƒƒƒƒƒƒƒƒƒƒ…………††††††…………ƒ‚}ungcegjnrx~‚‚‚~ytpjgjljhdcfkottqmot{ƒƒƒƒ„……‚xqppvxupllkllpx}€€}|vofbbkry|}{vrqnmnmjmqqpqquyzzz{{zzwrlfccegilqw{~xpfchsyyvtuvvsrstsmf_]\bfjooopplhehjnrsqtuvwwutvxuomlkjhgeghjijkmnopqqqqqqrrrrrrsttuuuuuvvxxxxzzyz{{}}||}}||||{{{{{{||zz|{zywtrnlhfd`____`enw}‚‚ƒƒ„„„„ƒƒ‚€|wqlkjhhlrx{}~€€ƒƒƒƒƒƒƒƒƒƒ„„„„……††‡‡‡‡††……„ƒ|vogefhknsz€‚‚‚zupkhjkmiechnsuwrlmrx}€ƒ‚‚„„……ƒ~vooqwyvokkjkmry~|sia]]fou{{xtqpomlmmnpqppqv{}{{}}}{yskfcdehjlrx}|vnfaeoxxvtvwvttttrnha`^agjoprrqlifikprssstwxxuuxyvolllkideghjjjkmnppqqqqqqrrrrrrstuuvvvvvvxxyyyyzz|{||{{||{{{{zzzzzz{{zz||{yywuqnkhfdccb``dmv}‚‚ƒƒ„„„„ƒƒ‚zuomjjijpx|~€‚‚‚‚ƒƒƒƒƒƒƒƒƒƒ„„„„…†††‡‡‡‡††……„ƒ|xpifehkosw}‚ƒƒ€{wrljiknjgekrwyxtnmnu|‚‚‚„„„„ƒ}uooqvyvokkjknsy~‚{qg`[]enuzzvqonnnlmoppqqqsx}}}}}zrkgcegikmrw}|vme`bmuwvuvwwtuuuspjed_bhlorssrnihjmrttsstwxxusvxuolkkkiegijjjjkmnppqqqqqqrrrrrrstuuwwwwvvxxyyxyzz||||||}}||||{{{{{{{{zz|||z{ywvroljiged`^bkt}‚‚ƒƒ„„„„ƒƒƒ~ytnmjjjouz~€‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„„„„…†††‡‡‡‡††……„ƒ}yrkffgjosx~ƒƒƒ‚}zunjhjmkiipvz|{uommr|„„„„„„‚|urqquxvokkjknsy~zph`\^entyxsommonlmoppqrruz}~~~~~~|xqjgdehikmqv{{vld`bltwvuvwwtuuvtpifeadjmpsssqnigkmrutrstwxxuruxuoljjkigikmjjjkmnppqqqqqqrrrrrrstuuwwwwvvxxzzxxz{||||{{||{{{{zzzzzzzzzz|||{zyyyxsqnlkhea^ajt}‚‚ƒƒ„„„„ƒƒ„~xsmlkkkrw{ƒ„„ƒƒƒƒƒƒƒƒƒƒƒƒ„„„„…††‡‡‡‡‡††……„ƒ~yskgfgjosy~‚ƒ„„ƒ|wpkhjlmlksy{|zuokmtz€„„„„ƒƒ‚{vtrquxvokkjknsy~€zqia^`gotwtrnlmnmlnnopqppuz}~~~}{vpifdegiknpvz{uld_bmuyvuvwwtuuvtpigecejoqsssplihklrwurstwxxtqtwtoljjkifhkmijjjlmoqppppppppqrsstuvwxxxxwwxxzzwxxy{}||{{zz{{zy|zyyyy{{{{||{{zy||{zvtrplga^agr|€‚‚‚ƒƒ„„‚ƒƒ‚~xrkjklpv{~‚‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„„„††‡ˆˆˆ‡‡†††…„ƒ{uoheehmrx~‚„„ƒ‚€€|wrliimnlmu|~}ytolnrx‚„„„„ƒ‚{utvtuvvnlklkotz‚‚{ricbchorssolllkklmnoppqquz}€~}}vngcdefgjmqvyyulc_bkuxvuvwvutvvsnighggloprtrnjgfjmquustuxxwsquwrnkkjhffijmijkjlmopppppppppqrssuvvwxxxxwwxxzzyyxy{}||{{zz{{zyzzyyzzzz{{||{{{{{{{{xwtsnga^bis{€‚‚‚ƒƒ„„ƒ„ƒ~|vpljklqw|€‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„„„†‡‡ˆˆˆ‡‡†††…„ƒ~zupheehlqw}‚ƒƒ‚~zwsmjimomov~€~ytommrxƒ„„„„ƒ‚yuvzvvvwnkjkmoty~‚‚{ricdfjoqrrolmmlllmopqqqqsx}~{tnfccefhimrvyytmd_bjtwttwxvutvvsoihiihmoprtsnjhgjosvuttuxxwsquwrnkkjhfgjmnijkklmnoppppppqqqrssttuvxxyywwxxzz{zy{{}||||||{{zyxyxxyyzz{{||||||{{{{zywupha^ajt{€‚‚‚ƒƒ„„……‚€|xupmkklqw}€‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„„„†‡‡ˆˆˆ‡‡‡††…„ƒ~zupgedgjov|‚ƒƒ‚~zvvtmjimoopxztollrxƒ„„„„ƒ‚}wty}yvvwpkjknnsx}‚‚€zsjffjlnpppnnmmllmmpqqqqqsw}~zsmebcehijmswyytmf_ajsvtuvxvutvurnjijjjnprsutnjiilqtussuvxxvrrwxsnkkjhfgjoqijlllmnoppppppqqqrssuuvwxxyywwxxzzyyz{{}||}}~~{{zyyyxxzz{|{{||}}||||{{{{xwsja^`jt~€‚‚‚ƒƒ„„ƒƒ€zutolkmmsx‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„„„†‡‡ˆˆˆ‡‡‡†††„ƒ~{uogedfinu{ƒƒ‚€}yuvumkimopqy{toklrxƒ„„„„„‚yvty}zvvvqmlllmrx}‚‚{sjggklmpppnnmnlmooqrqqrrtx}}~}zrlebcegiknsxyyunha`iruuvxyvutvuqnkjjlknprsutnjjjnruursuwxxuqtxxsnkkjhfgjnpijjjklnppqpqqqrqqrssuuwxyyyyxxyyyzzz{{{{||||}}{{yyyxyyyz{z{{{{||||{{{{{{{zuka^ajt|€‚ƒƒ„„ƒ„ƒ‚€}xqolllnptz~€ƒƒƒ„„ƒƒƒƒƒƒƒƒƒƒƒƒ„„……†‡‡ˆˆˆˆˆ‡‡††…„|vpifdeims{ƒƒ€|vttuokjmopry}zuqkjovƒ„„„„„zvvz}yuttqnlmmnsy~‚|tollmmmoppmmomnmnoqqqqppsx}~~~~}ysmgbcegjkosy{ysmfabjrttuwxwvuuuqnkkllmoqsttqmkklpvwvrsvvxxtptyysnlkkiegilqijjjklmooqpqqqrqqrttvvwxyyyyxxyyxxzz{{{{||||||{{yyvuuuvwzyzz{{||||||{{{{{ytkc^ait|‚ƒƒ„„„„‚~{uomlllmqu{ƒƒƒ„„ƒƒƒƒƒƒƒƒƒƒƒƒ„„……†‡‡ˆˆˆˆˆ‡‡††„ƒ€zvqjfdejnry~‚ƒ€~zqortokkkmpsx}zvqlkow„„„„„ƒ~zxyyyvstsrnlmmosy‚‚}uqnnpnmoppmmomnmooqrqqppty}~~~~|xrkgcdfhkmqrx|ytnfbckprtuwxwvvvuqmjlmnoqrsttqmlkorvxvtuvvxxtptyysnlkkieijlqijkkkklmnppqqqqqrsuuwxwxyyyyyyyyxxzz{{{{||||zzzzyxvvttuwxxzz{{||||||||{{|yumd`ags{~ƒƒ„„ƒ„‚€{uqmkkllmqw}ƒƒƒ„„ƒƒƒƒƒƒƒƒƒƒƒƒ„„……†‡‡ˆˆˆˆˆ‡‡††ƒ‚}xurkfdeimrw}~{vnlorokjjlorw}€|vqnmqw~ƒ„„„„‚}z{|xusrrsrnlllnry‚€|vrrrsnmoppnnommnoppqqqqqtx|~~~~~|xqkgceghknstz|ytohddmprtuwxwvvvvqlijmpqrstuusommotwxvtuvvwwsrtyxrnlkkigjlnsijllljklnopqqrqqrtwwwwvwyyyyzzyyxzzz{{{{||||zzyyxxwvvvuvvwyy{{||||{{||{{}zxpd`afsz~ƒƒ„„ƒƒ‚~ysnjjkklmpw}€ƒƒƒ„„ƒƒƒƒƒƒƒƒƒƒƒƒ„„……†‡‡ˆˆˆˆˆˆ‡‡†ƒ‚~ytrlfdegkqv|€}yslkmqokijmosw}|vqmlqx~‚„„„„}z|}xtqqqtqnllknqyƒ€~yvrttqnmopponommooppqrrqquy}~~~~~|vqjfccfhkotw}|ysnjffossuvwxwvwwwqljinprtutvvsomnptwxvstvvwwrrtywqnlkkiijlpuhijllllmmoqqrrrrsuwwvvwxyyxy{{zzz{{{||||}}|{{{yyxxwwwwuutuwy{|{{{{||||||{{xpfaaeqz~‚ƒ„„ƒ‚~ysolhhiijnsy~‚ƒƒƒ„„„„ƒƒƒƒƒƒƒƒƒƒƒ„…††‡‡ˆˆˆˆˆ‡†‡…‚€}ysrmgddfiptz~~zunfgmqnjhllnsv{}vrnlrx~‚ƒ„ƒƒ‚~z{{vsopqtpmmmlmqw€‚{xssuusommppoppnmnoopprrrruz|~~}}zvoiecdghlosy}|wrliggnrttvwwwvwwwqmkloqsututvspoorvxwsstuuwwssuxvqomljjjjmqwhijllmmnopqqrrsssuwwwwxxyyxy{{{{{{{{|||{|||z{{yyyyxxvvuuuvwyz|||||||||{{{{wpe__cnx|‚„„€zunkihghijpu{€‚‚ƒƒƒƒ„„„„ƒƒƒƒƒƒƒƒƒƒƒ„…††‡‡ˆˆˆˆˆ‡†‡…‚€zuqpmheegins{|{vpjedinokimmosu{€}wrolpv~‚ƒ„„„ƒ€zzyvtppqsqnklkmpv€~yvtuwwupnnqpprqomnoppqrrssuz|~}~}zvoidcdfhlpsxz{wqlihjpstuxxwwvwwwrnlmoqsuuutvspopswzwstuuuxxttwywpnlljjjloqvhijllnooqrrrrrsstvwwwwyyzzyz||||{{||}}|z{{{z{{yyyyyywwuuvxwyy{}}}}||||{{{{xri`_clu{€ƒƒ~wskgggfgikqw|€‚‚ƒƒƒƒ„„„„ƒƒƒƒƒƒƒƒƒƒƒ„…††‡‡‡ˆˆˆˆ‡†‡…‚zuqomiffhhnsyyvpjecegnnmmonpstz}xsolou}„…„„ƒ‚}{ywurqrsqmjlklnu{}zvsuwyyvppprqqsqomnpppqrrssty|€~}|zvojeddfhkptxzzvpmkimruuuwxxwvwwvsnlmoqstuvuvsqpruxzvutuwwxxttxzwolkljjjmprshijlmnppqrrrssssuwwwxxyyzzyz||||{{}}~~}z{{{{{{yyzzyyxxuuuwwyyz{{}}||||zz{{zulc`djsz~‚‚‚}voieefefilsx~‚‚ƒƒƒƒ„„„„ƒƒƒƒƒƒƒƒƒƒƒ„…††‡‡‡ˆˆˆˆ†††„‚~xtrpniffhhlruvrmfbbdflmnoqssrsz}xsolou|€„†……ƒ„|}{urrrtplkkjknsxywtqtw{{wpporsstqomnppqqrrssty|€}|yvpkdcdehkpt{|xupmkjotwusvxxwvwwutnlmpqstvvuvsrpsuxzvuuvyyyyttvxvnllljjjkorqijjllmopqqqrrsttuuxxyzzyzzz{||{{|||||||{{{y{yyzzzzzzzzxxvwxxxy{{||}}||{zz{ztmc`cjtz~€‚‚€xtngdccdgipt|€‚ƒƒƒƒ„„„„ƒƒƒƒƒƒƒƒƒƒƒ„…†…†‡‡‡‡‡‡††…‚€{wtpnnjhhiilprrmhdba`cinpquyywvz~€|xrmmqw~‚…†……„„‚€€{urqstpljkjkmqtvtqptx{zurpssuvupnmnnoqqsqqruy}~{xuojeccehlquzzxsonmoquxwuwyzxxxxwrljmprtvwuvwtrrvx|{wuuvxyzyuuwytnkkkkkkllonijkllmooqqqrrtuuvvxxy{{{{{||||||||||||||zzy{yyyyyyyyyyxxxxyyxy{{|||||||zz{ztnf``hqx~€€}upjeaccdgksw~€€‚ƒƒƒƒ„„„„ƒƒƒƒƒƒƒƒƒƒƒ„…†…†‡‡‡‡‡‡……„~zvspnnlhgiilopoiec`abdjprs{‚}yz~~zvqmnqwƒ…†……ƒ‚€€€ytsrstpljkjjknrsrnnsx{zusquuvvspnmoopqrsrqsvy}~{xsniedcfjmqv{zwponmoswzxvy{zyyxxxrljmprtvwvvxvstxz}{wuvwxyzxuuxysmkkkkkkllmlijlllmopppqssuvvvwxxy{{{||||||||||||||{{yyyzyyyyyyzzzzyyyyyyy{{{||||{{|zz{zvpja`enw}€€|smgb`acehnv{€‚ƒƒƒƒ„„„„ƒƒƒƒƒƒƒƒƒƒƒ„„…††‡‡‡‡‡‡…„ƒ}xsqpnolhgiijmlkgda_bdhlqux}}{~zvronrwƒ…†……‚€||}xrqtvvpljkjiilpqpmlqvz{vsuvywvrpnmprrrrrsrtvy|~~{wrnieeehknrxzyvponmouxzwxz{{yyyyxrklnprtuvvwwutvy{}{wuwxxyywuwzyrmkjjjkkkljjijlmlmnopprsuvvvwwxxy{{{}}||{{||||||||zzyyyzyyxxxxzzzzyyyyzzz{{{||{{zz|zz{zwrldafnw|€€€€zslf_^acejpw}€‚€‚ƒƒƒƒ„„„„ƒƒƒƒƒƒƒƒƒƒƒ„ƒ„††‡‡‡‡‡‡…„€{vqqpnomihiiijihec`_cejorv|{{}|~„…€|ytpnryƒ…†……ƒ~|}|wqqvwwpljkjhikopnjkpuz{wsxyzxvrpnooqrrrqsstvz|~{vqnifefilpsxzytqonoquy{wy{|{yyzzxrjlmprtvvvwussuz|}zwuwxxyxwux{yqmkjiikkklihjklmmooppqrtvvvvwxyyz||||||||||||||||{zzxxwwxyyyyyzzzzyyyyzz{|||{|{{{{{{{{{zsngdekuz€ysje`__ciou|€€€‚ƒƒƒƒƒ„„„„ƒƒƒ‚‚‚‚‚ƒƒ„„„„…………†††…„‚€}urpponmkhghhiiebdccbikmpsx}yx|†‰‰‡wposyƒ…………ƒ‚}xrqwzyqkjjkjkmnnlikqu{{xvx{{xwspopprrrqrttuwy|~~{ywqlhddfgjnrvwuqqqopruzzxwy{zyyyzysmmpqsuuvuvvtsvy{}zwwxyxzyxuvzzsmjiiijjjjhijklmnopppqrtvvvvwxyyz||||||||||||||||{zyyywwxyyyyyzzzzyyyyzzyzzz{|{{{{{{{{{zuoieejqx}}}|wqhc```fltx~€€€‚ƒƒƒƒƒ„„„„ƒƒƒ‚‚‚‚‚ƒƒ„„„„…………†……„‚~ytqppnnnmhhhhiieacccdjloqtw{wty{~‚†‹ˆysrszƒ……………„„…|vtw{xqkjjjhjmmkjilrvyyywx{|yvsoopqrrrprttuwy|~~|zvqkgdefhjnsvtspppppruz{xwy{zyyy{yrnmqstuvvvwvtsvz|}zwwyyyzzxvw{{qnkjiijjjjijjklmnopppqstvvvvwxyyz|||||}}||||||||{{zyxxyyxyyyyyzzzzyyyyzzyzzz{|{{{{{{{{{zwqlfdgnvz{{zwqhc``bhowz~€€€‚ƒƒƒƒ„„„„ƒƒƒ‚‚‚‚‚ƒƒ„„„„……………„„‚€~{vtqppnnppkhgghhfbcbbdjmqtvvzvqtuuz€‡‡‚|xvw|ƒ…………„„‚‚}xvw{xrkjiihjkkjijmswxxywx{|xvrnnpqssrprttuwy|~~}}ztpkfeegikosurqppooosw||yyy{zyyz|xrmnrtuvvvwwvttx{}}zwwyyz{{yvy|zrmkhiijjikjkjklmnopppqstvuttuvwwz|||||}}||||||||{{zyxxxxxyyyyyzzzz{{yyzzz{{{{|{{{{{{{{{zxsngbdjqwyyyvojd_bfksz|€€€€‚ƒƒƒƒ„„„„ƒƒƒ‚‚‚‚‚ƒƒ„„„„……………ƒ‚~|yttqppoopqmiffggfbcbabiorsvuvsoprptz‚„~{x}ƒ…………ƒ€€}{ww{yqkjhiikjjjijmswxyywx{|ywrnnpqttrprttuwy|~}|{xrpjfeeghlprtrpppooosx}|zyz{zyy{|xqmnstuvvvxxvtvy{}~zwwyyz{{zwy|zrmliiijjhkjkjkmnnoppqrtuvuustuvx{{{}{|}~}}||||{{{zzyxxxxyyyyyyyyxyzzzzzzz{{{{{{{{{{{{{{{xspidbfmruvurniebchnu{~€€€‚ƒƒ‚‚ƒƒ……ƒƒƒƒ‚‚‚‚ƒƒ„„„„„„………‚€~{xvsqqppnnpqogeefgfcccadhmpswwqjjlmprv|ƒ‚‚||‚„…††…~{xy{wqlkijkkljljkntxzxxwyz{xvtooqrssrqrttuxz}~}|zxsnkgggjllopppppppootz~}{zz{{zz{{xplotuuvwwwwvuvy|~€zwxxy{||zv{|xqljjjjggikikijlmnoppqstutssqqsuxzzz||}~~~}}||{{{zzyyyzzyyyyxxwwxyzzzzzzz{{{{{{{{{{{{{{{zwqkd`djosrpmifccdkpx|€€€‚‚ƒƒ‚‚ƒƒƒƒƒƒ‚‚‚‚‚‚ƒƒ„„„„„„……ƒ€}zwtsqqqppppqrogeefgfdccbafkosvvtmjkmpsw{‚ƒ„ƒ}€ƒ„…††…‚€€|{}wqmkklkkkkmkkouxzxxwyz{xvsppqrssrrrttuvy}~~|zxrnjhghkmnpoopppponou{~{zz{{zz{{xqlpuvuvwxxxuuvy}~zwxxy|||yw||xqlkjkigfhklnijjknopprstutspmlnqtwwwxz|}~~~~~||{{{zyy{{yyyyyyxxwwwxzzzzzzz{{{{{{{{{{{{{{{|ytle`cglnmliebbbflr{}€€‚‚ƒƒ‚‚ƒƒ‚‚ƒƒ‚‚‚‚ƒƒƒƒƒƒƒƒ€}yvtssqppppqrsspiffghgeeea_agmsuwvpmkmprv|‚ƒ……ƒ‚ƒ„…††…ƒ‚}|{~ƒ~wqmkklkkkkmnopux{yyxyzzwvrppqrttrrrttuux}~~{wqnigghkmponnooooonpu|€~{zzzzzz||wolqvvuvwxyyttvy}}ywxyz||{wv||xplkkjhfgijnphijknopprstusrmjhjmpssuvx{||}}~~||{{{zyyzzyyyyyyyyxxvxzzzzzzz{{{{{{{{{{{{{{{|zvnha`cgiihfbbbbhnt}}~€€‚‚ƒƒ‚‚ƒƒƒƒƒƒ€€€€‚‚‚‚‚‚}zxussqnooppqqtupjgghiheffb^]emsuwvplknqtw}ƒƒ„†‡…„‚ƒ„…†††„}|xy€…wqmkjkmmmmnpqrtx{zzxyzyvsrppqruurrrttuvy}~~{vrlifghkmonmmoooonmqv|€~{zzyyzz||vomsutuvwxxwtsvy~€}ywxy{||{uv}|wpljkihfgiloqijijloqqrstusqmhgfhkooprswwz||{{||{{zzzzyyxxyyyyyyxxwxxxzzzz{{{{{{{{zzy{|||||{wqha_bcdeedaabelpw~~~€€€‚‚ƒƒ„„‚‚„ƒ€€€€€€€€}{xxutqrpnnooprtuuqkhhiiihiic^\bluwxwqmmpux|€„…†ˆˆ‡‡„ƒ„…†……~{y|„‡‚xrmkikmmlprvzzwxzzxxz{yvsqpppsutqqrttuvy~~~zuqmjghillkikkmnpomnqv|}|zzzzz{}{tontvstwwxwvqsw{~|xwyy{||ytu}}xqmjjjggghkpujjjjloqrrsturpkgfedgkkkmnqsvz{||||{{zzzzyyxxyyyyyyxxyyyyzzzz{{{{{{{{zzy{||||}}wqhb_baaaabbabfmsy~~~‚ƒƒƒ‚‚‚}}{{{{{wvuuuuurqqonnoommnoqtwwvrkggijjjlkg`W[fquvwrmnsx}€‚„‡ˆŠŠ‰ˆ…€‚…„†……ƒ‚††yqmkjlmmlpt|‚yxyzxxz{yvtrqqqsutqqrttuwz}zuplihhjjjhghiklppmpsx{~}|{{zz{|~{tontvsuwxxwuqsw|€|yxyy{|{ytu|}yqmjjjhgghlsxjjkkloqrrsturpkfdcdeggghkmnqwyz{}}{{zzzzyyyyyyyyxxxxyy{{zzzz{{{{{{{{zzy{||||}}xrjdba__``aaabfltz~€}{zy{~‚‚€~}zxvussrrpooooonjkkihhijkkmorvxywrkgghikjonj`UXeouvwrnot{€ƒ„†ˆ‰‹‹‹‰…~…†…………‚ƒƒˆ‡€xpmkmoqposz€yyyyxxz{zwurrrrstsqqrttuwz}zuolighjigecehjlmnoquxz}}|||{{|}}zsnnuwuvxxxwtqsw|€€|yyyy{{{xrsz}yqmlkkigghltyjjljloqrrstutrkfdcfefffgijlosvyz~~{{zzzzyyyyyyyywwxxzzzzzzzz{{{{{{{{zzy{||||||yskdca__```aabfksy~€~yuqrrz|zwtrponnmmkjiiiihfggdccdefhjosxyywrkffghjkoqmbVVdpvwvrnpt{……†ˆ‰‹‹‹‰…z}ƒ………„ƒƒ‰†xpmkoqttsw}}{wzyxwxz|zxsrsssrtrqqrttux{€€€~zuokhfhiifbadhjllmqswyz}}||||||}|zsmovxuvxwxwsqsw|€€|yyzy{zzunrz}yqmlkligghkuyhjjkmprrrsuwvtokgdgfffddfghimqwy{|{yyyzzyxxxxxxxxxyyzzzzzz{{{{zzzzzz{{z|||||{zyrmifb_\_^^_abglu{€~ztmihjry~|zwqoomlihhggfeddedcddec```aacglswyywrlgfhjhjqtpeYXdouwtqonrw~ƒƒ„‡Š‹‹Š‡„ƒ|z€……ƒƒƒ‚‚„‡„}vpmlnswyz{~|yvvwvwxx{|zwsrrrssrqppprruy|~€}xsmifgiigd``cgjmnnqvxz{}}|}}||}~}zsoquvuwxxxwtqrv}€{xzzz{{yrmqz}womllkjgfglv|hjjlnpqqrstwvuqmifhgggdddddehlruxyzyyyyyxxxxxxxxxxyyzzzzzz{{{{zzzzzz{{{|||||{zysnjhd`]^\\\_`ejtz€{vld``dlvxwsojhgffcaaaa`_^^_^^_`aa^_____ciosuvuqlhfghginrsk][eouvurpnorx}€„†‰ŠŠ‰‡ƒƒ|~„„‚‚…ˆ‚zvpmlnsz~€€}wqmloruww{{yurqqqrrrqppprrvy{~€€|wrmiffhhec^^bekmoortxz|}}|}}||}~}{uqquvuwxxywtqrv}€€{xyy{{|yrmqz|vomlmlkighmv}hjjlmoppqrtvvurnkiihggfdddddehmqswxxyyyyxxxxxxxxxxyyzzzzzz{{{{zzzzzz{{z{|||||{zvrnjfa_^][[]^chpx~~€}xod]\]bhpspmieba`^^^^]]\[XXYXZ[\^^^]]___bgknoqrpkjhffefhnsob_hquwurpnnpsy~ƒ…‡ˆˆˆ†‚ƒ…}€ƒ‚}€€…ˆ‚zvpmlot|„ƒ|tohfinuwwzzzvrqqqrrqpooprswx{~{vqlhffhhec]]beknnquuxz~}|}}||}~~{xtrvwuwxxyvspqv}€{xyy{{{xqlqz{uomlmmkjiglv}hjjllopppqsvvusomjjiffgdeedbefjmqtvwyyxxxxxxxxxxxxyyzzzzzz{{{{zzzzzz{{y{||||}{zxtplhda`^[\]_aemv~€{vk`[[\aglnlgdb^^\ZY[[YYYXTTVTVVYZ[\\\]]abfgjlmnpkigfeccdiqogdlrwwurponnqx~‚ƒ†‡‡†ƒ€ƒ‡†~~‚~z}…‡ƒzvomlpt}†ƒ|umfeinvvwz{zuqrrrssqonnprswx{~~zupkgffhhfd]]cflnostuwz€}|}}||}~~|xsswyuwxxyvsopv}€{xxx{{zwqlqz{uomlmkjihhlu|ikjlmoppqqstuvuqooolkjihgfdbdegimqsuwxwwwwwwwwwwvvyyzzz{{x{{{{zzzzzzzzzzz{||}|{yvromifdb^\]\^ciow|~yqf_YY[_cfigc_][YXYXWWVVUSSSRRUVWXYVXWY[_adggghjnkigec_`dhnqnkqvvvtqpooorx|€ƒ…„‚€‚ˆ‰{}€~|~‡‡‚ztmljov~ƒ„ƒ}slghiluuyz{xsqpprsqponnnqsux{}zuoighikkgc``bfknqqtvwy~}|~|{|~~ytsvwvwyyztolnu|‚€{xyy{{yvokpyxronmnkifehlu{ikjlnoppqqsttutrrrrppnmlihgedddgjnqtuvvvvvvvvvvvvvxxyyz{{y{{{{zzzzzzzzzzz{}}}}{zwtqpnkgc_\]\\_bipvzyvlb]YY\^bcda_[YWVVXWXXWWWVWWUWYZ[\\WWVXZ\_behhfhjiheb`aadhlrtqswwwurpooopv{‚‚€~‚‰Œ†|y||}|‡‡€wploosy„…„siehkmqswz{wtqpprsrommnnqsvx{~~|xsoigghjjhdbachmpqqtvwy~}|~|{|~{utvvvxyyztnlnu|{xyz{{xtkipxwronnnlkhgjnuzikklnoppqqrssttsttuttrqpmljgedcdfjoqstvvuuuuuuuuvvwwyyyz{z{{{{zzzzzzzzzz{|~~}}|{xwusplheb_^]\\^ciorrof^[ZZ]^```][ZXWXZ[\^^__]\[[[]_`ab`][ZYZZ\`cfhhjhjgb`bdffeiqvsvxwwurponnory}~||‚ŠŽ‰y{|yy‡ˆ‚|z{~}}~€„††tkefjnmpsy|xurrrstsokknortwx{~~{vqligghjjieddhlpqrruvxz~}|~|{|~{wuvwwyyzxsmlnu|{xyz{{xrjipwvqonnnnonklptyiklnnoppqqqsstuuvvwwwutsppljgedceiloqrttuuuuuuuuvvwwxxxzzz{{{{zzzzzzzzzz{}||}}|{zwwurojgc`^][\\_djnnib\[\\]`_^_][[\Z[^^accddcbaa_`cdded`^][[Y\]abehjjieaaeggfacpvsxxxxvrponmprx|€‚€}zz…‹Šƒ||{vxˆŠ‡„†Š‹‰…‚„„…‚wmeejonpsy}yusrrstspkjmortwx{~~ztplighikkidfgkotsssuwy{~}|~|{|~|yvvxxyzzwrllnu|‚€{xyz{{yqiipwuqonnnnppllpsyhjkmmnnoqqqrrtuuuuwwxxvvtqojgecabdgilqstttsuuuuuuvwwwwwyzzzzzz{{zzzzzz{{|||}~~}|zwxwurkifca_`^\\_ejie_^\]\\]]\\]^_``bcejjiiihfdcacffhihdb`]\\\Z\_aehjhd`bglmh]]muuyyxwvtpnmnosy|~|zz‡ŽŒ†‚|x|„Š‹‰‹ŽŒˆ…ƒ…ƒ…„{rgcfklpt{}ytrsrsvsplkmoqtvyyzytrplggiklljgikntvvutuwx{~}}~|{|~~{yxxxyzyxsmlmt|‚€zyzzzzxogipttonmnnoqqnlosvjklnmnnoqqqsrtttuuvvwxxxwurmhecacddefnprssstttttuvxxxxwyzz{{zz{{zzzzzz{{|||}~|{xyxvsnlligdc^][]`baa^`^^\\\[\^`defgjkkpponnkifddehhjlmkhfca`_]__`begfb`ekoqk[Xiuxxxwusqqonnosy}{{}}|{€‡Œ‹…}y~…‹ŒŒ‰‡„‡†„„†„…†xledflqt{|xtrsrsuromkloqtvxxxvrpokhhjkmmljlpswwwtrtvx|~}}~|{}~€€~|zyxxyzyxsmmnt|zyzzzzxngiqtvqonopqsusnosuikkmmnnoqqrtsuuuttwwxyxxxxupkhdbbb``binqrrstttttuvxxxxwyzz||{{{{zzzzzz{{{{|}}|zxwsromnmkhe_\[]]]\^___^^^^]]cfklnooqrttsqpligfefggilommlhgdcaa__`abbcdjnooh[Vetwxxwutrqqommrz}yw|€|}†Œ…}~ˆŒŒ‹‰„{w{‚…††‡ˆ…~tmiiosuz{wsrsrsspmlklprsuvvutqnmkkkklmmmmnsvzzxtrtvy~~}}~||~~}{yxxyzzxsmmnt|€~zy{{zzwngirutqrpprsuxvrprthjikmnnpqqstsuttuuvvwxyyxywqojgdb`^\_ekpqqrsttttuvwwwwwyzz||||{{zzzzzz{{zz|}~|ywvssomnnljda][]][Z[^_`_^^]``hlpqsuuvwxwvtqmjigfhggimooopmmihec_^__^adinnllg]Wdrvwwxwtrsrnjip{}xu|~z|…Ž†‚~…ŠŽŒ‰‡‚uos|‚……ˆˆ‰†‚zvporsxz{vsrsrsqnlllnprruvurqpmmkjjklmmooqwx{}xusuwy~~}}~|}~~€}|zxxyz{ysmlmt|€~zy{{zzvpgjrwtrqpoqrtxxuqqtikkmlnopqrttuvttttwwwwxxxxwvrnifb`^\_dkopprrrrssstuuuvwxxy{||zz{zzzzzyyxxwx|}€}{yvtssonnnmjb^\[]^\[Z\]^_^_`celruw{{|||zzyurnlihgghhjosttuspmligecec`cgmqnklmf`gpsuxwurnpohffoz{wt{~~{}†Œˆ„„‰ŽŠ…ƒ€vqu}ƒ„‡ˆŠ‹‹‡ƒ€|yuruz{wsqrssrommopvvtrprpnlkkhigjlnoqru{~}|wuuvxz€~|}}}~€€€~}yzyz{yyrmlmr{zy{{zzvqgjruvtrqppsuyzxsstjllnmoopqrttttttttwwwwxxxxyyvrkhda^\^binppqqrrsssstttvwwxy{{{zz{zzzz{{vrrquz|{xvtrqromlkjhb^]\^\\[[[\]`_`bfkry|~€€€}zxtqnkihhhhjlrvwyyxuqoljfdeecgmrojiktvsspqtwvrkhklhhjt{|wt{~~}~…‹ŽŒ‰…„‡ŠŒŒ‡„‚~usy†ˆ‹ŒŒ‹ˆ†ƒ€zvwz{wrrrtrpnoqw}‚xpoqpnmkjhigjlopqsw|~}zvssvy{€|~}}~€€€€€€}yyz{|yyrmlmr|zy{{zzvpgjsvussrqpsu{|yuttklmooppqqrttttttttwwwwxxxxxxwsplie`]]bhnppqqqqrrrrsstuvwwxz{zyz{zzzzzxqkjlqvz}}zutqqpqqmhedca_]\][[\]\]\`aadgnu{‚‚‚‚|yuromjihiiilntx{|{{yuqmifdcfglrrkghmwz}ztprusomnllkmqx~}z{ƒ€~…‹ŒŒ‹‹ˆ‡Šˆ‰‰…„‚|vx|}{}„‰Œ‹‰‡…€yy{zvspoooonsz‚‡‰„xooqpnljihiikmpprux{|{xsorvy|€€}}}~€€€€€€}yyz{|{ysnkls}zy{{{{umejswtrsrqpsu}~zvuulmmoqopqqrttttttttwwwwxxxxxxxsrolga][`fnpprrqqrrrrrrsuvvwxyzzxz{zzzzyrkffhmrx{yvrpopopqmd`__`^\[[\[\^__^`aadgmsz~€‚‚}ytqmmjihiijnovy|}}~|wqniebbgjpuqjggqy{}xpqqnorsqnnsw}~~‚‡‡ƒ€…‰ŠŠ‹Œ‰‰‰ˆ‰‰…„ƒ|wz{vx‚ˆ‹ŒŒŠˆ†‚{z{zwsnkjlnqx€‡ŠŠ‚uooqpnkjhiijlnpqsvy{{zwqorvy|€€~}}~€€€€}yyy||}ytojks}|{{{||uldirutrsrqpsu|}{wuumomopoqrrsuuuuuuuuwwwwxxxxxxwusroib[Y^fnpprrqqppqqssrtuvvxz{zyzzzzzyuneacejoswvtrpppnmlfa\Y[ZXYWYZY]acccbbdeglrx{€€~{vqmkkkjhhiimqx|€€}yunhdbglpssligjz~€|umigmtrropu|€…ˆ‹‰†…ˆ‰‡Š‹Š‡ˆ†ˆ‡…‡…~{€xsy‚‡‹ŒŽŒ‹‰ˆ„€{}ywtpkilnw€‡ŠŠƒzqnopqokjhghilnppsu{~}{uqprvy|€‚}}}}~€€zy{}||xroklt~€~{{{{{{ticjrusrrroorw}}|yvumonpqqssttuuuuuuvvwwwwxxxxxxwvtsqkc[Y^fnppqqpppprrrrrsuuuwxyyyzz{{zyrjc__aejmqsroopnmkid]XUWWVUTVWY^fhhhfdfeeglqtx}}xtpnmkkjihjjnsz~~|wsngdelqrrofbgp€€‡|lhjrxurosx€‚ƒ†‹Œ‰…ƒ†‰‰ˆŠŒŠ†„„…ƒ‚†‡‚~pow€†Š‹‹Œ‰‰†}~yutqnlnt}…Š†€wpomopqpkjhghjlnpqsvz}}ztppsvy|€‚€~~}}~€€~zz|}||xrolmt~€~{{{{{{shdjrtsrrrpoqx||{yvunppqssttttuuuuuuvvwwwwxxxxxxwvustmd\Z^empppppprrrrrrqstuuwxyxxzz{{zwphea^_aejnomllmlljhaZUTVVVUTTW\cjnookigdbdfjmqwz}}zvrolkkkkjjkmqu{~~~|zwqmgfkqtsrrjfnwƒ†~‰}plqy|zusx~‚ƒ†ŠŒŽ‹Šˆ…ˆ‹ŒŠŒŠ‡ƒ‚ƒ~…ˆ„€‚zonv~„‰‹‹‹‰‡„~ysrqqqt{„ˆ…~vpmnnopqpmljhiklnpqsvzzzxspptwz~€~~~€€}{{}~||xrolnt~€~{{{{{yqgdjqssrrrqqqx{{zxwvnpqsttuuuuuuuuuuvvwwwwxxxxxxwvutsnd\X]dmppppppqqpprrqrtttvvwxxzzzzzvngd`^^`bfjljikkjkif`YUTUWXWUVZ]entuuqmhdaaceimpuzzxtpomkkkkjlnnsw|~€~|xvpkijouvuuuqnr{€†„†}rrx}yy~„…†‡Œ‹ŠŠ‡‡ŠŒŽŒ†ƒ‚„|{ƒ‡„ƒ‚ypov€ƒˆ‹‹Šˆ‡‚}~~yqqpqtzˆˆ€vommmmopqpmlkjjklnpqsv{{xvsoptw{€€~~€€}||~~}|xronot~€~{{{{{xogdjqssrrrqqqx{{zwxvpprrttstttvvvvttuuuuwwxxxvwwutttqme]XZcmpoonppqqqqqqqqrrttuvwwxxyxxtmgc^__`befhghghhfe`\UQPSVWZXY_cjqwyzvmhe_^^afjknrsurpnmmkkkkmppsy|~}zusonlrw}}|yyzuwƒ…‹‰†|ru|„ƒ‚ƒ…‡‰‰‹‹‰‰‰ˆ‡‡‹ŒŒŒŽ‡ƒ…„{y‚††„yrpv~ƒ†ŠŒŒ‰‰„ƒ~x|}{rppqz€ˆ‰…ynnmnnppqrqommjjlmnoqsvz{wtqprtx|€€~€‚‚€€}|{{}}{vsonot}|yz{{yunebjsurrssrrqvzyyxvvqprrttttuuwwvvuuuvvvwwxxxvvvusttqme]WYbkpponppppppqqqqrrstuvwwxxyxwsmgc^``bceeeeedccba]YRONQTVXY^ciqv|ypie^[[]`fijmoononmmllklmpsvz}~|wsomopw†‡ƒ€€‚‹‡‹‘”ˆ|rv€†ŠŠ‡†‡‰ˆ‰Š‹‰…‡‡ˆ‰ˆŠ‹‹‹ŽŽ‡ƒ†„}|ƒ‡ˆƒytrx…‡ŠŒ‹‰…zwsy~ztqoq|†‹ˆ~rmmmmopqsssqnnkjlmnoqsvzzvsqqrtx|€€~€‚‚€‚€~|{{}}zvsonot}|yz{{yulcbjttrrrrrrrvyxyyvvprttuutuvvwwvvvvvvvwwwxxxvvvussspld\UXbiooopppooooppppppsttuwwxx{ywtnhc^aabdddcca```^][WONMPRTW\bipw{|sjd`\]\^bfghjklnmmmlllnpsvx}}}|zxuqnmpt|…Ž‰†ƒƒˆˆ‚ŒŽŒ”‰}uz†‹ŠŒŒ‹‹ˆ„……‡ŠˆŠ‹ŠŠŽ‡ƒ†„…‡†}vtsz…‡ŠŒ‹ˆƒyqoqy}zuolq~‡‡‚wonmmlmnrtttrpmkjlmnprtwzyuqqqrtx|€€~€‚‚ƒ€~|}}}}zvsonot}~|{{{{yukbclssrrqqrrtxyxyyvvpsuuuuuuvvwwvvvvvvvvwwxxxvuuusrrpkc[UYaimoopppooooqqqqpprstuwwxx{zyvohca`aabccbb_]^^]\YUNJINQRW^enw}€ƒ‚~ulda_`_^`cdeghinmmmllnoruwy||zywutommpv€ˆ‘ˆ„…‹Œˆ’†yw~‰ŽŒ‰„‚„ˆŒŠŒ‹‰ŠŒŽˆ…†ƒ~…†„~zvuu{ƒ†ˆŠŒŠ‡„wnlqy}ysjjs„‚|rnmmmlmnrtutsqmkjlmnpsuxzxtpqqrtx|€€~€‚‚‚ƒ€~|}}}}zvsonot}~{{{{{yulbensrrrqqrrvzyyyyvvpsvvuvuuwwxxwwwwvwvwxwwwxwvvvuttqmg^WX^ekmmnnnoonnooppppqrtuuwxxxxxuqmhfcbaabdb_^\^]ZYWUPLIINT\blu}‚ƒƒƒvleccecb^^`cdfhllmnnnopsvxz{{xwtrqpnnpy„‹‘‹‡Š„‡Ž“‘Šsw‚Œ’’Ž‘’‘ŽŒˆ„ƒƒˆ‹‹ŠŠˆ‰ŒŽŒ‡†„€„…„€|xwuu{ˆŠŠŠŠˆƒwnlry}yohm|ƒ‚}unmmnmnnoruutrpolklnoqrtw{yurppsux}€€€€€~}€‚ƒ‚‚‚~{}}||yvsomnu}}{z{}|ztjcfourqqopprvy{zyxxwrtvvvwxxxxxxxxxxwwwxywxxxxwwvvuuroia\Z]agjnonnoonnoopppppqsttwxxxxxvrnjhhgca`bbaa`^]YWUTPMKIMW_ir{ƒ„„…„€wmhfhjkid`]_cefhklnpqsuuuuvvvttqommnnt}†‹‘Š‘‡‡Œ‘’Œ}sz†Ž’‘Œ‘“’Ž‹Œ‹‰†ƒ„ˆŠ‹ŠŠˆˆ‹ŒŠˆ†ƒ€‚…†„}yxww|‚ˆŠŠŠŠˆ„|njrxxpigt‚…€ztnmmnnoopsvxvspomllopqrvx{yvsqqsux}€€€~€‚ƒ‚‚ƒ~{||||yvtomnu}}{{||zysjehpusqqoppruy{zyxvvuwvvwxzzyyxxxxwwwwzzzxxxxxwwvvuuspkd^Z]_dilnoooonnoopppppqrrtvxxxxywspmkjifc`bcdb`_[XVUSQONMP[eoy~ƒ………„„wmhfjoqniea_adddiknsttuutssssqqppnmmpx€†Š’‘Ž’‘Š‡‹’Ž€y€‡“‘ŽŒ“’‹‹‹ˆ†ƒ‚†‰ŠŠŠ‰‰‰‹Š‰„€€ƒ‡‡…ƒ€}|{x}ƒˆŠŠŠŠˆ„~phmnjbbgy„ƒyspnmnnonptwyxtqnmnnopqrux{yvsqqsux}€€€€€‚ƒ‚‚ƒ~{||||yvupmnu}}|{||zyrjfktusqqoppruxyzyxvwvvvvwwyzzzxxxxvvwwyzzyyyxxwwvvuuspje][]_chjlnnoonnooppppppqrsvxxxxzwupomkjhdcddba_]YVTTRQPQQU`kt„„„…„}ulfgkpsrnje`_bdfgjnrusttsrpppoononmmqz‚…ˆŽ““‘Ž‘•“‰ŠŽ‹ƒ€ƒ‰”‘‹Ž‘‘‹ˆ‡ƒƒ†ˆŠ‹ŠŠ‰ˆŠ‰‡‚€„ˆ‰‡†‚‚{}ƒˆŠŠŠŠˆ…skhb\Y]ky‚{ytonnnmnnptwyxurnnnnopqrvy|yvrqqsux}€€€€€‚ƒ‚‚ƒ~{{{||yvvrmnu}~}}}}{xrjgmvusqqopprtwxzzxwxwwwwwwyyzzywwwwwyyzzzzyyyxxxwvvvsplg`]]^`bfilnoommnnpppppprstvwyxyxxvsqnnmkigghc`^\VTTSSSUVX\grz€ƒƒƒƒƒ~wnffjrvtqmkc^^`cgimqssrrpononlonoonlq{„‡Ž“””“•–•‘‹‰‰Š†ƒ„‡‹”‘Œ‘‘‘’‹‰‡†ˆŠ‹ŒŒŒŠ‰‰‡†~‡‹Œ‹Šˆˆ‡ƒ‚†‡ˆŠˆ‡‡„yre\X[er{~zyvroqqnmlmquxywtqnnnnopprx|}{wvtttvy~€‚€€€€‚ƒƒ}{{{||ywtrmpv}~}|~}|xskhqxvqppppqqsxxyzyyywwxxxxyyzzywwwxyzzzzzzyyyxxxwvvvtrnic_^^_bbejmmnmmnnoopppprstvwxwxyywusqpomkiihc^]\VSTUUX[]`fpz‚ƒƒƒƒƒ‚yqgeiqvxuqoje`acghloponmjkklklnnopoos{ƒ†Ž”–—˜˜›™”ˆ…ƒ€…‰Ž”–“Ž‘”˜›™”’ŽŠˆ‡ŠŽ‹‰‡††…„…†ˆŽ‹‹‹‰‡†ˆ‡‡ˆ‡‡†„}xlfenu{{zuwuqruuqmlmrvyywtqnnnnopqrw{}{xvssuwy~€€€€‚ƒƒ}{||}|ywurmrx}~}|}}}xtlirxwqppppqqrvwyzzyywwxxyyyyzzywwwz{zzzzzzyyyxxxwvwwutqjfbaaaa`bgklnmmnnnnooooqrsuvwwxzzxvvtrpnmihdb_][XVXYZ]`cgnv~‚ƒƒƒƒ‚‚€ysjfhov{ytrnjefghhhkmjiighjmlllnpqpqw{ƒ†Ž”—™››œš–‰„€}~†‹–™—‘Ž‘–›Ÿž›—“‰†‡‹Œ‹Šˆ††„ƒƒ†‰‹ŽŠ‹‹‹ŒŠ‰†…†………ƒ}|qms|€~ywtwussuvsmlmrwz{ytqnnnnpqrtw{|zxwttvxz‚€€€€‚ƒƒƒƒ}{}}}|zwurosy}~}|{}}ytljuyvpppppqqqsuyzzzzwwxxyyyyzzywwwyzzzzzzzyyyxxxwvwwvusliedc`_^_dgklmmnnnnnnnnpqstvwyyzzywusqpnlgeba]\\XYZ^_bcinuz~€ƒƒƒƒ‚ƒ‚ztlfhnt{|wtqlklljkiijhghhglonmlnpstvy}€ƒ†•˜™›œœ›˜‘‰„€}€ˆŒ‘˜››”Ž’—œŸŸœš–’Œ‡…‡ˆ‰‰‡……„‚‚‡ŠŽŒŠŠŠŒ‹Œˆ†„‚‚ƒ…„{ztnx„‡vsuxyurstrmlmrwz{ytqnnnnprtuvzyxxwwwvx{€‚€~€€€€‚ƒƒƒƒ}{}}}|zwurptz|~}|z~}zsmmxzupppppqqpqtyzzzzxxxxyyyyzzyxxxyyxxxxzyyyyyyxxwwuwvtplhhd`___bdgjmnnnnnnnnnqqrtuwxz{{zxxtqnmifc_][Y\]^`adgkqwz|€€‚‚‚‚‚‚|tnffkrx|zwplorojljiiijjnnponjipsuxy{€‚ƒ†Ž•™›œœ™–ˆƒ€ƒˆŽ“™ŸŸš’‘”–š››š˜••‰…„ƒ†ˆ…ƒ‚„€€†ŒŽŽŠ‰Š‰‰Š‹Œˆ†ƒ€€‚„„~{{vq|†‡vtv}|wrpsqmlnsx{{wtqnmmoqqstvxxwvvuwuw|‚€€€€‚ƒƒ‚ƒƒ|{|}~|zvurpu{}~}||~}{ror{|tpppoopppqvyzzzzxxyyyy{{zzyzyyyyxxxxzyzzz{zyywwuxxwsqnjfba__`aeimnnnnnoooopprtuwxz{|zxtqljgcb`\[YZ\_begknvz~~~€€‚‚‚‚‚‚|ungehms{zxrptvtnkjhhjihlmoqpkhlrwy{~‚ƒ‚†Œ”šœœœ—’Š…„„†‰•›Ÿ ˜•”“““–•”””‘‹…}€…„ƒ‚‚€€‡ŽŠ‡‡ŠŠŠŠŒŒ‹‡ƒ~ƒ‚}{~yu{ƒƒ}usw€{qorqmlnrvyzwtqnmmnpppprsqqppsuvx|ƒ‚€€€€‚ƒƒƒƒƒ{z|~~|yvvsqv|~~}|~~~{sou}|tpppooppprvzzz{{yyzz{{||{{{{yyyyzzzzzyzzz{zyywwuxxxwspmifc`_]_cglmnnoopppppprtuwwyzzurokhfb^\\YZZ]`bfjlqsz~~€€‚‚‚‚‚‚}woheejpxvxxy}~|uqpnkjihgginnjhhpw{}€‚‚ƒ„Š’›œ—’‹‹Šˆ‡ˆŠ—žžž›—”’Ž’‘‘‘‹…~xw€ƒƒ‚‚ƒ‰Ž‹‡„„ˆŠŠ‰Š‹Š†‚~~~‚}}€|xy}}zvtz‚…tprqmnnquxyvtqnmlmnnllopnlmmoquw{€‚€€€€‚ƒƒƒƒƒ{z~~~|ywvtsx}~}}{tow}{soppqqrrqsvzzz||yyzz{{||||{{yyyyzzzzzy{{{|{zywwuxxxxvspmifb^\]`flmnnpppppppprtuwvxywqnjfda^ZZZXZ]_bdknquy}€€€€‚‚‚‚‚‚}xpidejouux}€„„ƒ|xwurmkhdcdhjiiimu{}‚‚‚‰šŸ ˜‘Š‹Œ‹‹‹Œ˜ž›šš˜–‘ŽŽŽŒŠ„}tqw|ƒƒƒ†ŒŒˆƒƒ…ˆ‡‡†‡ˆ‰…‚~~~‚~€yvyzyzu|„‡vrpqnnoqvxyutqnmlmlljjlmmkllnotvz€€€€€‚ƒƒƒƒƒ}|~}ywwuuz€~}~{usy|snppqqrrqsvzzz||zzyyzz{{{zzzyyyzzyx{{z{{zz{{yxwwwwxxwvuqmiea_^_chkmonoppoprstuvvttsplhea]ZXXZ\]^acfjosvz}€€€€‚‚€xrjegjlrsy„‡‡ˆ†ƒ‚{wpjbcc__cjknptx}€}€™žžž˜‹ŒŽ‘˜˜˜˜™˜”ŽŽŽŠ…unmqx€„†‰‡…„…„‚ƒ„††„€~€‚€€€}ywuz~€zƒ†€xsqqoopqvzytqommmjkkjmlmomnnortuy|{}~‚€‚‚ƒƒƒƒ‚€€€}zyxvv{€€~~~~€|wu}zropppqsssty{{{{{zzyy{{{{{yzz{{yzzzy{{{{{zzzzyyyyxxxxxxvrpliea`_aeimnnoooopqrrtvvrplieb^\XVVX\^aceimruy{}€€€€ytmfejkms|…‡‡ˆŠŠˆ‡‡…‚|tkiga^bhknkmlqy}}wy‡—œžž™‘ŒŽŽ’˜œ˜–˜››—ŽŽŒ‹‹Œ‰†ytmls€†‹ˆƒ‚‚ƒ~}|€ƒƒƒ}‚‚‚ƒ‚~{xvz€‚~xtsrqqqrvzytpmkkkjkllnnmnmnnnqqrvxxz}‚ƒ€‚‚‚ƒƒƒƒƒ€€€€~zzyww|€~~€{vw~‚{sqppprttwy||{{{{zz{{|||||z{{zzz{{zy{{{{{{{zzyyzyzzyyyywtspmic`^_cglmnoooopqrstttmjeb_[YWWVXZ]bfiknsyz|~€€€€€€{uohekmqtz‚…ˆŠ‹‹Š‰ŠŠ‡ƒyvphdfijihiiksz|uuƒ”›žžœš“ŽŽŽ’—œœš•˜œž™”ŒŠˆ‡‰Šˆ†‚~wty„‰Ž‰‚}|€||{~ƒ„„€~‚‚„…„|z|€~}||yussrrrtxzyurlkjjjkllnmllllmmnmosuwx{€‚ƒƒ‚‚„„„„ƒ€€€€€{{zyy~€~~€yux‚|tqpppruuy}}zz{{zz{{||{{{yzz{{yz{zy{{{{{||{{{{{yyyzzyywvvrplf`]^aelmmoppoprtttrnieb_\VWWXX[]`ekmqtx|}€‚‚‚€€€€€}uqkgkswwy{ƒ‰‹ŠŠŒŠˆˆ‡†€znjhllefikjqy{rt’šœœš“‘’—›œš•˜œž›–‘‹‰‡…ˆŠ‰‡ƒ‚ƒ‚}ˆŒˆzx}}}||{~‚„„…ƒ€‚‚‚ƒ†„€‚€}}€|zyzywstrstwx{{upkjjjjkknnmmlkjkjjjlprtwx‚‚‚ƒ‚‚„„„„ƒ‚€€{{{|y~~€€€xtxzrpppqsuv{~|yy{{yy{{{{{{zz{{{{yz|{z{zzzz||{{|{{{zzzzzzzzwuspke`__djlmoppoqssutpke_[YWVVXZ\^`flpswz}‚‚‚‚€|xrnor{~{y‹Ž‹ˆ‹Œ‹‹ŠŒŠˆ}ohnlb^cikqyxqn|‘šœš”‘ŽŽ”™š™˜—š›™•‘Œ‰†…ˆ‰‰ˆ……ˆ‰‰‰ŠŽŽŠzxz}~~~€€„„……ƒƒ…„‚ƒ„ƒƒ…„€~~}zyyyzzvtsstw|}ysolmmkkjknoqponlkjhijjkpsx}‚‚ƒƒ„„„„„‚€€~z{{{z~€€xsw€‚{qppqrstw||yz{{zz{{{{{{zzzzzz{{||||{{{{||{{{{||zzzz{{{{zxvsmgb_]aegijklorsssqmhaZXXXXX[_behlruy}€‚‚‚‚}zsqx}‚…„€†Œ’Œˆ‹ŒŒŒ‹‹Œ~pppbY\emrz{slwšœ›˜”””’”˜™–•–˜˜—”‘‹ˆ††ˆ‰ˆˆ‡†‹Š‹Œ‹Žˆ‚}xx{{|~‚„†††…ƒ…†„ƒƒƒ„…‡„||yxzz{yyywtttuy}}wrnmmlkkloqsuwutrponmmijlosx~€‚ƒ„…………„‚|z{|{|€‚€€€~wrx€zqpqrttux|€}y{{{||{{{{{{zzzzzz|||}||||||||{{{{||{{{{{{{{|zxupjc`^^adfghimpqqpmic][Z[]\\aeimpsxz}‚‚‚‚‚‚‚‚‚‚{vv}‚…‡…„ƒˆŽ“‹ˆŠŠ‹‹ŠŠ’’†vssh^\`js|}umuŠ˜œ›™—”——–“‘‘–™™–““––•’ŽŠˆ‡‡‡ˆˆˆˆ‡‹Œ‹‹ŒŽ‹…ƒyy{{z}ƒ…†ˆˆ†…†„ƒ‚ƒ‚ƒ……|yyyx{}|zwxvvuuux|{tpoljjkknqtvwyxxxusrrrnjjlou{~‚ƒ„…………„ƒ‚‚‚|{|~}~‚‚€€}tpv~€ysrrstuux}€~|{|||{{{{{{{{zz{{{{||}}||}}}}||{{||||||||{{{{|{xvrmfc_^^acdefkmoolid`\\\]_abginstx{|‚‚‚‚‚‚‚‚‚~zyy‚†‡…„…‰“ŽŠ‡‰‰ŠŒ‹Š‘’‰yuund_`fr{}vns‰—š›š—”™˜™–”’“—˜˜•“““’ŽŒŠ‡‡‡‡ˆ‰ˆˆ‡ŠŒŒ‹ŒŠ†ƒ~yz{{{}€ƒ„†ˆ‡†‡„‚‚ƒ€‚„}xxyzz{}|zxxwwvvvxyxqpoljkmmnqtvyz|{ywwusspljjlqw}‚ƒ„††………ƒ‚ƒƒ€~||~€ƒƒ€€|snt|zutrtuuvy~{{|}}{{{{||||{{{{zz{{||||{}}}||||||||{{||||zz|{zytnjfc_^^_`aaehjifd_]Z[\_aeglptxy}~€€‚‚‚‚‚‚‚‚‚‚€€‚‚‚‚€~z|‚‚†‡†…‡Œ’Š††‡ˆŠŠŒŽ‘’Š{vvxohdft~€xpt‰•™››–”ššš™˜••–—•“‘’‹Šˆ‡‡‡‡‰ŠˆˆŒŽŽŽŠˆ…‚€}{yzyyz}€ƒƒ„†‡ˆŠ†€‚zyy{z{|~}{xyxwvsttutqoklknqnoquy|}~}|zxxxywrlhhlr{€‚ƒƒ†††‡„ƒƒ‚ƒ‚‚}{|~…‚€€€€zqnt}~yvustvvvy~€~|{||}{{{{{{||{{{{zz{{||||{}}}||}}||||{{||||{{{{|{wrmieb`]]]^^adecb`^][]_bhmorvz}~€‚‚‚‚‚‚‚‚‚‚‚‚€~zƒ‚ƒ†‡‡†‡‹‘ŽŠ„ƒ„‡‡ŠŽ’’’‹}yx~~xomu€xps†“˜š›–”ššššš–•••”’ŽŽ‹‹Šˆˆ†††‰‹ŠŠ‹ŒŽŒ‰‡†„{xzywvy{€€€ƒ‡‹ˆƒ€‚zyyzz{}~}{yyyxvtqprqonmopssppty|€€~|}}{~€|toiglw~‚ƒ„†††‡…„ƒ‚ƒƒ}|}ƒ…‚€€€zplt}|xusttvvvz~€~|{||~{{||{{||{{{{zz{{||||{}}}}}}}||||{{|||||||||{ysokgd`]]\\\^ab`__^^^adhnswz{}€€‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚€}}ƒ„ƒ„‡‡†††‰ŽŽ‰ƒƒ„ˆŽ‘”•”Œ€{{‚„‚{tyxooŽ—š›—’”šš™™™–”““’‘ŽŒŒŒ‹‹Š‰‰………‰‹Š‰ˆŠŒ‹‰ˆ‡‡ƒ}y{yusvy~}zy}„‰Š†„‚‚|zy{z{|~|zyxxxusonpponqrtttrsw{~€€€~~‚|ulghq{ƒ„†ˆ‡‡…„„ƒ‚‚€}~€„„‚€€‚€zokr||wusuuvvvz~€~|{|}{{}}||||{{{{zz{{||||{}}}~~}}||||{{||||||}}|zztrniea^_\\\\_a___^`behmswz~€€€‚‚‚‚‚‚ƒƒ‚‚‚‚‚‚ƒƒ‚‚€|~ƒƒ„††‡††…‡ŽŽŠ…€€†Ž“•˜–‚{}‚†‡‚|z‚xll{‹–š›˜••šš™˜—”“’‘ŽŠ‹ŒŒŒ‹Œ‹‰………‰‹Š‰‰Š‰‰‰ˆˆ‰…~z|ztsrw|~|xux‰Šˆ‡ƒ€ƒ‚zyyzy{}~{yywuurpnnpooprtttttuy}€€€‚‚ƒ{skgnwƒ„†‡‡‡…„…ƒƒ‚€‚„„‚€€‚€zpkr{|wvtvvvvvz~€~|{|~{{||||||{{{{{{||||{{{{}}~~}}||||||}}}}||||zzywvrmid`\\\[\\^`^^_aejqux{}€€€‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚}~ƒ„„ƒ†‡ˆ†………ŠŽŽŠ†€|}~€Œ“——–‚~ƒ‰Œˆƒ€„whgt†”š›™™–šš™˜”’‘ŽŽŽŠ‰Š‹‹ŒŒŒ‰………ŠŒ‹Šˆ‡‰ŠŠŠ‰‰‡~yzzvtsvy|}zww‡Œ‹Š…€€‚~wz{yz{|€|yzxwtqnllnopqruuuuvwz}€€€€‚€‚ƒ„„zpiiq}‚…†‡ˆ‡‡†…„ƒ‚€‚ƒ…„zqmv}|xuuvvwvwy~~|}~~~{{||||||{{{{{{|||||||{}}~~}}||||||}}}}||||{{zxxtpmifb`]\\\^aaacflqvx{~€€€‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚€}‚ˆ…„„†ˆ‡†††…‰ŽŠ†{|}~‡˜˜—’†€€ƒ‡ŒŽˆ‚…ƒylhr…“˜š˜™˜ššš™•‘Ž‹Š‹ŒŒŠ‰ˆ‰‰ŠŒŒ‰…†‡ŒŽŒ‹ˆˆˆŒŒŒŒ‹‰‚|}|ywvvy|}zxv}†ŒŠ‰†}xxyyy{}€{xtsrqnkijmprqruuuxxy{~€€€€‚‚‚‚„…„†„€vlelx€„†‡ˆ‡‡†……„‚‚ƒ‚„…„‚‚zrow~}xvvwwxvwy~}{|{|{{{||{{{{{{{{{{||||||}}}}}}}}||||||}}}}||||||{zyvuqoljfa`]]_adeilqwy{~€€€€€‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‡‹…„„†‡‡‡ˆ†„†‰Š†|{||Ž—™™”Šƒƒ†‹ŽŠ„††}sls…‘—š—™™ššš™•‘Ž‹‹ŠŠŠ‰‰‡‡ˆŠŒŒŠ††‡ŒŒ‰ˆ‰ŒŒŒ‹†‚€|yyyxy|~{zz†Š‰ˆ…„…†|xwwxz}~zuqqonkjiloqrqsvwwyyz{€€€€‚‚‚‚„…„†…‚zofis„†‡ˆ‡‡†……„‚‚ƒ„ƒ„…„‚‚‚€{ttz~}xwwwwywwy~|yxvus{{||zzzz{{{{{{||||}}}}}}||}}||||||}}}}||||||}|ywwtqolidc__`cfgkpuz|}~~€€€€‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚€ƒ‹Š†„„†‡‡‡ˆ†„…‡‹Š†ƒ|{zz~—šš–†‚‚…Š‹††‡tntƒ—™—˜™››šš•’Ž‹Š‰ŠŠŠˆ††ˆŠŒŒ‹††‡ŽŒŠŠ‹ŽŒ‹Š‹‰‡‚}zyzzz|}‚‡‰‰‡†…ˆˆ†€{wvxy}}xtpqnkkjlnqsrstwxxxyz{~€€€ƒ‚‚‚ƒ„……†……~sgen{‚ˆ‰ˆ‡‡†……„ƒ„ƒƒ„…†…„ƒƒ‚‚‚|uv~}ywwxxyxwy~|wtrpm{{{{zzzzyyz|zz{{{{}}}}~~}|}}}}}}||}}{{||||||||zz{zwuqnkgcbdfilpu|}€€‚‚‚‚‚‚‚‚ƒƒ‚‚‚‚ƒƒƒƒƒ‚‚‚‚‚‚†‹‰„ƒ†‡ˆˆˆˆ†……†ˆ‰ˆƒ~|{{}Š–›šš–Šƒ‚ƒ‡‰‡‡ˆ…}totƒ–˜™™š››››˜“Œ‰‰‰‰Šˆ……†‰Œ‹‰‰Š‘“‘ŽŽ‘ŠŠ‰‰ˆ…€{{zyy{€ƒ„†ˆŠ‰ˆ†‡‹‹‹‡ƒ{uuwx|{vsqpmlmnpstttvwy{{||||~€‚ƒ‚ƒ„„…†‡††‡„xjciwƒ†‡‰ˆ‡†…†„„ƒƒ‚„†‡…ƒƒƒƒƒƒƒ~yz€ƒ~zwvuxxvwz€{tljji{{{{zzzzyyz{{{{{||}}}}~~}|}}}}}}||}}{{}}||||||{{||zxvtrnkjjlorwy~~€€€€€‚‚‚‚‚‚‚‚ƒƒ‚‚ƒƒƒƒƒƒƒƒƒ‚‚†‰‰ˆˆˆ‰‰‰‰ˆ††††‡ˆˆ„}zz}Š”™œœ™‚„‡†‡‰…}rmr€–—˜˜š›œœœš”‘ŠŠŠ‰‰ˆ„„…‰ŒŒ‹ŠŠŒ“•“‘’’ŒŠ‰ˆ‰ˆ†‚}yxxy|‚„………†‡‡……‡Š‹‹Œ‰‚zvuvwvuqqpnoqruvvvvxyz}}}}}}~€‚ƒ„„„…†‡‡‡‡‰†oefp~…ˆ‰ˆˆ‡†††…„ƒ‚††‡†ƒƒƒƒƒƒƒ}~„…€zxwvyxww{€|ulghii{{{{zzzzyyz{}}||}}}}}}~~}|}}}}}}||}}zz||||||||{{||{ywywurppruwz|€€~~~‚‚‚‚‚‚‚‚ƒƒ‚‚ƒƒƒƒƒƒƒƒƒ‚†‰‹ŒŠ‹‹Šˆˆˆˆ‡‡†…‡†ƒ~zz~Š”™œœ›†‚ƒ††ˆŠ†}pko}Œ•—˜˜šœš•‘Œ‹‹Š‰‡ƒƒ…‰ŒŒ‹Š‹Ž“••’‘“““Ž‹‰ˆˆ‰ˆ†ƒ~wtsv{‚†‡…„„…†…„„‡ˆŠŒŠ‰‚}xxssqqrqprtuwwwzyzz{}}}}}}~€‚ƒ„„…‡‡‡‡‡‡ˆˆ‚tgeky„‰‰ˆˆ‡‡†††„„„‡‡ˆ‡…ƒƒƒƒƒƒ‚€€ƒ……}zxwzzyxz|ulgikii{{{{zzzzyyz{||}}}}}}}}~~}|}}}}}}||}}{{{{||||||{{|||{{{zxuttvxz|~€€~~~‚‚‚‚‚‚‚‚‚‚ƒƒƒƒƒƒƒƒƒƒƒ„„ƒ€€ˆŠŽŽŠŒ‹ˆˆˆ‰ˆ‰†……‡ƒyxŒ”˜šœœ“Šƒ‚‚…†‰Šˆ}pko}Œ•—™˜šœžžš—’Œ‹Š‰‰†„……‰‹ŒŒŒ‘”—•””••“Ž‹‰ˆ‡ˆˆ‡„€upoqy‚‡‡…ƒ„……„ƒ€‚ƒ‡‰‰‹‡ƒzqppstrsvvwxw{||z{|}}}}}}~€‚ƒ…†‡‡‰†‡‡‡ˆˆ„ykdhvƒ‡‡ˆˆ‡ˆ†††…„…ˆˆŠ‡…ƒƒƒƒƒƒƒ†‡„€|{yx{{zyxxnhflkll||{{zzyyyyzz||}}||~~~~~~}}}}||||||||zz{{zz||{|||}}||z|}}{yy{|~~€€€€€€€€€‚‚‚‚‚‚ƒƒƒƒƒƒƒƒƒƒƒ„„ƒ‚‚€ˆŒ“Š‹Œ‹Š‹‹Š‰ˆ†……‡†€zz…”–™›œ™†ƒ„…‡‰‹ˆ|olq~—˜˜˜™œžŸžœ–’ŽŠˆ‰Š‡‡ˆˆŠŠŠŒ“”–•––—–“Ž‹‰ˆ‡‡………€uljlvˆŠ†ƒƒ„ƒ€}{}‚‡‡Š‹ˆ…~rpqrtttwxxzy}~}||}~~~~€€€„†‡‡ˆ‰‰ˆˆˆ‡†…}oeer…‡ˆˆˆˆ‡‡………†ˆŠŠ‡…„„„„„„„ƒƒ…ˆƒ€}{{|||zxwqkkkkkln{{{{{{zzzzzz{{{{{{~~~~}}}}||||||{{yyxyzz{{{{}}~~||{}~~~|}~€€€€€‚‚‚‚‚‚ƒƒƒƒƒƒƒƒƒƒƒ„„ƒƒƒ€‰’Œ‰ŠŒ‹ŽŽŒ‹‡………†ƒ€ˆ“•–™›œ•‹……†‰Š‹ˆ|pnq|Œ™š˜˜™›žš•’’‘ŽŒŒ‹‹‰‰ŠŠŠ‹’•••–˜—•’Ž‹ˆ‡††…„†zohflu~„„ƒƒƒ‚}yusv€†‰‰‰ˆwuuvwxxzzz{{}~}{|}~~€€„‡ˆ‰ŠŠŠ‰‰‰ˆ‡‡shem{ƒ†ˆ‰ˆˆˆˆ††…†‰ŠŠ‡…„„„„„„ƒƒƒ†ˆƒ}||}}|zwqjjopnoprzzzz{{zzzzzzzz{{{{~~~~}}}}||||||{{yyxxzz{{zz}}~~}}|~~~€€‚‚‚€€€€‚‚‚‚‚‚ƒƒƒƒƒƒƒƒƒƒƒ„„ƒƒƒ‚„‹Š‰Š‹‹‘ŽŠ†„…‡‡„…‹’“”•–™œš‡†‡Š‹Œˆ|rpryŠ˜›™——šœœœ˜”’‘””“‘‘Œ‹Œ‹ŠŒ‘“••—˜—•’‹ˆ‡……„„†ƒsgbejqy€ƒ„ƒzyyuu|ƒ‡†ˆˆ…|ywxyz{||{{{}~}|}~~~~~€€ƒ„‡ŠŠ‹‹Š‰‰‰ˆ‡‡„ylehs†ˆ‰ˆˆˆ‰‡††‡‰Š‹‡…„„„„„„ƒƒ„ˆ‰„~|}}~~}zumimqrpqqtzzyyzzyyz{zzzz{{||~~~~}}}}||||||zzyyzyzzzzyz}}}}~~}}€€~€€‚‚€€€€€€~~‚‚‚‚‚‚ƒƒƒƒƒƒƒƒƒƒƒ„„ƒƒƒƒ€†ŒŽ‹Š‰Š‰‹Œ’””ˆ……‰ˆ†ˆ‘““““˜šœ’ŠˆˆŠŽˆ|tru{‰—œš——™›œ›—“““—––”“““‘ŽŽŒŠŒŽ‘’•———–””‹ˆ†…„ƒ„††‚vgbeehq}‚„ƒ{{|zw|ƒ†‡ˆ†{wz{}~}}||}}}}~€€~~~~€€‚ƒ…‡‹‹ŒŒŠ‰‰‰‰ˆ††}ngen{„ˆ‰ŠŠ‰‰‡††‡Š‹‹‡…„„„„„„ƒ„…ˆ‰…~}~~€ypijpstqrtvyyyyyyyyzzzzyyz{|}~~€€~~}}}}}}||}}{{{{zzzzzzyz||}}}~~~€€€€€€€~~~~~€€‚‚‚‚ƒƒƒƒ„„ƒƒ‚‚„„„‚ƒƒƒ…‹‹‹ŠŠŠ‰ŠŠŽ“––’Š…„ˆ‹ŠŒ’””““”—›—Žˆ‡ŠŽ‰|vx{~ˆ–œ˜——™šš™””“—™™™––”““’’‘ŽŽ’•––•””“‹†„ƒƒ„‡‡…xhflignw‚ƒ€~zyyxx{€ƒ‚ƒ„…ƒ|zy}|}|{{||}~}||~€€€€‚ƒ‡‰‹‹‹‰‰‰ŠŠŠˆ‡‰ogdju‚ˆ‰Š‰ˆ‰‡…†‡ŠŒŠ†„„„„„„„ƒ…‡ˆˆƒ€~ulkmprttvwzyyyyyyyyzzzzzzz{|}~~~~}}}}}}||||{{{{zzzzzzyz||}}}~€€‚‚~~}}~~~~€€‚‚‚‚ƒƒƒƒƒƒƒƒ‚‚„„„‚ƒƒƒ‡‘‹ˆ‹‰Š‰‰ˆˆŒ”˜˜”Ž‡ƒˆ’•”’’”—›™‰‡ŠŒŽ‰~tw~ƒŠ—›—–™™š™˜””˜™œ–˜™–“””“’“‘“”••’’’’Œ‡„‚‚„„‡ˆ„wjjqoklqz€~yvwxxxxz||~‚|{}}|}||||}~}|}€€€€€€€€‚„‡‹‹‹‹‰‰ŠŠŠŠˆˆ‰‚tgcgq†ˆŠŠ‰‰‡…†‡‹ŒŠ†„„„„„„„ƒ…‡‰ˆƒ€€€xokmoruxy{|~xxxxyyyyzzzzzzz{|}}}~~~~}}}}}}||||{{{{zzzzzzyz||}}}~€€‚‚‚‚‚‚‚‚}}}|}}~~€€‚‚‚‚ƒƒƒƒƒƒƒƒƒƒ„„„„„‚ƒƒƒˆ‘Š‡‹Š‡„ˆƒ‡–›š”ˆ‚‡’’“•”’’”—šš“‰‹‹Œˆ€tqx‰”›™–š›œš™—–™––žœœ˜•””“”•“‘’“”““”’’’’Œ‰†ƒ‚„†ˆˆ„xoptsnmqz€}{xuuwwuttstw}}||}|}||||}~}|}€€€€€€~‚„ˆŒ‹‹ŠŠŠŠŠˆ‡ˆ„xicen{„‰ŠŠ‰‰‡…†ˆŒŒŠ†„„„„„„„„„‡‹Š„€€€|qkloruxz{~€xxxxyyyyzzzz{{z{|}}}~~~~}}}}}}||{{{{zzzzzzzzyz||}}}~‚‚ƒƒ‚‚‚‚‚}}||}}~~€€ƒƒ‚‚ƒƒƒƒƒƒƒƒƒƒ„„„„„‚ƒƒƒŠ’‰†‹Š†„ˆ€}„™›š•ˆ‚„’‘‘“””””—šš•‹‹Š‹ˆ€umr{…‘››˜›œ›™™˜‘—ž¡ žš•““”””•””•”“““”“‘’Š†ƒ‚„‡ˆˆ…ztvvtonr{€|{{wuututrposz~}|}||}}||}~}|~€€€€€~~‚„‰ŒŒ‹‹‹ŠŠŠˆ‡ˆ…{mddky„ŠŠ‹Š‰‡…†‰ŒŒ‰†„„„„„„„ƒ…‰‹Š…€‚~wlkmptwy{}€€xxxxxxyyyyzz{{{|}}~~}~~~}}||||||{{zz{yzzzzz{{{|||}€‚‚‚‚‚‚‚~}{{|{{{||~€€€‚‚‚‚ƒƒƒƒƒƒ„„ƒƒƒƒ„„„ƒƒƒ„‹‘ŽŠ†‹Š‰ˆˆ}„˜™™•‘‰ƒŒ’‘’””•–˜š›—ŒŠ‹Œˆ‚yqms}Œš››ŸžŸžš˜—ž ¡ ˜•“’’”•––••”“““”“’’Œˆ†…†ˆˆˆ†y{zvrps{‚ƒ€|xwsoqvuspmqx€€}|}}|}}|{}}}}€€€€€€€€‚…‰Œ‹ŠŠŠŠ‰‰ˆ‰‡~ofcjx‚‰Š‹Š‰†…†‰ŒŒŠ†„„„………ƒ„„‰Œ‰…€€xnikoruy|}€€xxxxxxyyyyzz{{||}~~~}~~~}}||||||{{{{{yzzzz{||||||}‚‚‚‚‚‚‚€~||{{{{{||~€€€‚‚‚‚ƒƒƒƒƒƒ„„ƒƒƒƒ„„„„ƒƒ…‹‘ŽŠˆ‹‹Šˆƒ„—˜—•‘Š‚Š’’‘””•—˜››˜‘ŒŠŒŒ‰ƒ~{utx†–ŸŸŸŸŸž™––•Ÿ Ÿœ˜“‘’”•–˜—–”“’””“’Šˆ‡†ˆ‰‰ˆƒ~zxvvy„„€|zwrlnuwupmqv}‚~{{|||||{}}}}€€€€€€~~€€‚…‰‹‹ŠŠŠŠ‰‰‰Šˆ€sieixƒ‰Š‹Š‰†…†‰ŒŒŠ†„„„………ƒ……ŠŒ‰…}qjkmqtw{}€€xxxxxxyyyyzz{{||}~~~}~~~}}||||||{{||{yzzzz{||||||}~€‚‚‚‚‚‚‚~~|||{z{{||~€€€‚‚‚‚ƒƒƒƒƒƒƒƒƒƒƒƒ„„„„ƒƒ…‹Ž‹‹ŽŒŠ‰‰„ƒ”–•“‘‹‚‰’‘‹“”–—˜šš˜“ŒŒŒŠ…ztt€’œ¡¢žžœ˜–—•šŸœš–’‘’•–—˜—––••–•”‘ŽŒ‰‡ˆ‰‹Š‰…‚zzz|€ƒ~|{xsmknsvurpqt{ƒ‚zxyz|||{}}}}€€€€€}}}~…‰‹‹ŠŠŠŠ‰‰‰ŠŠƒxmhjxƒˆ‹‹Š‰†…†‰ŒŒŠ†„„„………ƒ…†Š‹‰…‚‚€xmjmorvz}~€€xxxxxxyyyyzz{{||}~~~}~~~}}||||||{{zz{yzzzz{|}}|||}~‚‚‚‚‚€~~||{zz{{||~€€€‚‚‚‚ƒƒƒƒƒƒƒƒƒƒƒƒ„„„„ƒƒ…‹ŒŒŽ‰‡‰†‚‚‹“”“‘‘‹‚€ˆ‘‹‹’•——˜šš˜”ŒŒ‹‡|vt}Žš  žžœ™—˜”šž›—•’‘‘’••–——–———™–•‘ŽŽŒŠ‰Š‹ŒŒ‹†„€||}€„ƒ€|{{yohjnrtvutqsx€……|xwy{{|{}}||€€€€€~}}|}~…‰Œ‹ŠŠŠŠ‰‰‰Š‹…|pllw‚‡‹‹Š‰†…†‰ŒŒŠ†„„„………ƒ…ˆŠ‹‰…‚‚tjknptx{~€€wwwwwwxxyyzzzz{|~~~~~~~}}}}}||}{zyzzzz{{zzzz{{||}~~€€~}{{{{{{|}}~~€‚ƒƒƒƒƒƒƒƒƒƒƒƒ„„„ƒ‚‚…Œ‘ŽŽ‹ŒŠ„ƒ‰“’„ˆŽ‹ŒŽ••———˜˜”‹Š†€|yx~‰–œŸžžš˜˜˜•™››™•””“”•••••–•—˜™›™—’ŽŽŽŒŽŠ†‚€€‚ƒ…ƒ€~|qecnqsvxxvuv}„‡ywyz{|z{{€€€€€~|{||„ˆŽŽ‹‹‹‹‹ŠŠŠŠŠˆ‚womv‡Œ‹Š‰†„‡‰Œ‰†„……………„…‡‰‹ˆ…ƒ‚yokmoquy€€€€wwwwwwxxyyzzzz{|}}~~~~~}}}}}||}{zyzzzzzzzzzzz{||}~€€€~}{{{{{{|}}~~€‚ƒƒƒƒƒƒƒƒƒƒƒƒ„„„ƒ‚‚…“ŽŒ…„‡Ž’‘…ƒˆŒŒŠŒ”•—˜˜˜™–’‘ŒŠˆ|{}†‘˜œœœ›™–––•–™˜—•””•––••––••˜™œ›š™’ŽŽŽŽ‘’‘Œˆƒƒ…„†ƒ€€€zqf[inquwxusuz‚ˆ„|xz{}|y{{~€€~~}}zz{|~…‰‹ŒŒ‹‹ŠŠŠŠŠ‰…{rouˆŒ‹Š‰†„‡ŠŽŒ‰†„……‡‡‡†…†‰‹ˆ…‚~skmprtx}€wwwwwwxxyyzzzzz{||~~~}}}}}||}{zyzzzzzzzzzzz{||}~€€€~}{{{zz{|}}~~€‚‚ƒƒƒƒƒƒƒƒƒƒƒƒ„„„ƒ‚‚†Ž’‘‘‘‘‘‡ƒ…Œ‡…ˆ‹Š‰‹Œ“–—˜˜˜™•’‘‘Œ‹ˆ}~€„Œ”™˜˜™—••–•”•—–••–•––——––••˜˜š–˜š“ŽŽŽ“”“‘‰„ƒ„‡†„€€~yqcQ[jotwvusqw€‡†‚„…‚~}{z}{~~}}{{yxy{}€„ˆŽ‹ŒŒ‹‹ŠŠŠŠŠ‰‡uptˆŒ‹Š‰†„‡‹ŽŒ‰†„……‡‡‡†…†‰Šˆ…znkortx{€‚‚‚wwwwwwxxyyzzzzy{||~~~€€}}}}}||}{zyzzzzyyzzzzz{||}~€€‚€€~}{{{yy{|}}~~€‚‚ƒƒƒƒƒƒƒƒƒƒƒƒ„„„ƒ‚‚†Ž“‘‘‘‘’‘‘ˆƒ„ŒŽŽˆ‡‰‹Š‰ŠŒ‘•—˜—™˜–’‘ŽŽˆ~€ƒƒˆ•–—˜–””–•“•–•••–———™™––••—˜™—››•‘ŽŽŽŽ‘”–•‘‹‡‡‡‡‡‚~€€~xo^HGflsvvusqu~†ˆ††ˆŠŠ‰…}z{{{{yzxxvvwz}€„‰Ž‹ŒŒ‹‹ŠŠŠŠ‹Š†€vptˆŒ‹Š‰†„‡ŒŽŒ‰†„……‡‡‡†††‰Š‰†tklqsvz‚‚‚‚xxwwxxyxyyzzzzz{||}}~}~~~~}}}}}}||{zzyywwwxyzzz{||~€€‚€€~}}{zzzzz{|}}€‚‚‚‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚…Ž“’’‘‘‘“’’Š„ƒŠŒ‰‡‰ŠŠ‰‰‹Œ”—™˜™˜—”’Œˆ}„‚ƒ†’••––““”“”•••”•–˜˜š˜™——–•˜™š›œ—”’‘ŽŽ“–—–“ŠŠˆˆ†‚€€|ymX:6Zhpuvwvst|†‰‰‰Š‰ŠŒŠ†{zz{zwvttutuw|€…‰ŽŒŒŒ‹ŠŠŠŠŠŠŠˆwpt~ˆŒŒ‹‰†„‡‹‹‰…………††‡…†‡‹ŒŠ…|okortx}ƒ…ƒ‚‚‚‚‚‚ƒxxxxxxyxyyzzzz{{||}}~}~~~~}}}}}}||{{|zzxwwxyzzz{||~€€‚€€~}{zz{{z{|}}€‚‚‚‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚…Ž“’“‘‘‘””’Œ…„‰ŒŒŠˆ‰ŠŠ‰‰‹ŒŽ“–˜™™˜—“‘ŽŽˆ}„ƒƒ†‹‘“•”‘“‘“•••–••–˜››š—˜˜–™šœœ›˜–”“‘•˜—–•‘ŽŒ‹ŠŠ‰†ƒ|xjM/.Sinrtxxtrz…ˆ†„„„‡‡‡‡„|y{|xuutttuw}‚‡‹ŒŒ‹ŠŠŠŠŠŠŠ‰xqu‰Œ‹‰†„ˆ‹‹‰…†…†††‡……ˆ‹Œ‰ƒwlnqtx|€……ƒƒƒƒƒ‚ƒƒxxxxxxyxyyzzzz{{{{}}~}~~~~}}}}}}||{{|{zxxxxyzzz{{{~€€€€€~}{zz{{z{|}}€‚‚‚‚ƒƒ‚‚‚‚‚‚‚‚‚‚‚‚‚…Ž•”““’’””’Ž‡„‡‹Ž‹ˆ‰ŠŠŠŠŠ‹ŒŽ’•—™™——’ŽŠ…ƒ„ƒ†‰’“’‘‘“––—•••–˜œœœš››™šš›œ›™™––•‘‘“–˜—––“Œ‹ŠŠŒŽŒ‡ƒ|udD+.Uknorwyupvƒz}€‚‚„ƒ…€}~|yxuutux~„Š‘ŒŒ‹ŠŠŠŠŠŠŠ‰wrv‰Œ‹‰††ˆŠˆ…††‡ˆˆ‡……‰Œ‹ˆslptuz‚†„ƒƒƒƒƒ‚ƒ€xxyyxxyxyyzzzz{{zz}}~}~~~~}}}}}}||{{{y{yyyxyzzz{{{~€€€€~~}}{zz{{z{|}}€‚‚‚‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚…Ž––”•””””“’‰„†‹ŽŠˆ‰Š‰‰‹‹‹ŒŽ“”˜™—–’ŽŽŽŒˆƒ‚ƒƒ†‰Ž‘’“––—–––—™š™žžœœ™š™š››™˜–•”’‘”•—™˜——“Œ‹‰‰’“Ž†ƒ~s`C,1Ymonpwyvqt~ytx}}€‚…„‚€~{{xttux~„ŠŒŒ‹ŠŠŠŠŠ‹Š‰urv€ˆŒŒ‹‰††Š‰‡…††ˆˆˆ‡……‰ŒŒ†{qnruv{„‡„ƒƒƒƒƒ‚‚}xxzzyyxxxyyz{{}}||}}~~~~~~}}~~}}}}|zzz{yxxwwyyzz{{}~€€€€€~}}|{{{z{{|}~~€€€‚‚‚‚‚‚ƒƒƒ‚‚‚‚‚‚‚‚„Ž–——–•••–”‘Žˆ„†ŠŒŽŠŠˆ‰ŠŠ‹‹‹ŒŽ‘“—˜˜•ŒŽŽŽ‹‰„‚‚„…†‡ŒŽ‘’”——••”–˜š™š¡ŸŸ™Ÿ’“—™š™™——••””–——˜˜—–“‹‰ˆŠŒ‘‘‹†€s`H8A_pqmosvurtz}ysvy{~€€ƒ‚‚€~}{vtsvz…ŠŽŒ‹‹Š‹‹‹‹Œ‹‰€uswˆ‹Œ‹‰††ŠŽŽŠˆ…†‡ˆ‰‰‡……ŠŒ‚tnpsvx~‡ˆ‡„ƒƒ„„„‚uxxyyxxxxyyzz{{||}}}}~~~~~~}}~~}}}}|zzzzyxxwwyyxy{{}~~€€€€€€}|}|{{{z{{|}~~€€€‚‚‚‚‚‚ƒƒƒ‚‚‚‚‚‚‚‚„––˜˜•”•”“‘Žˆ„†ŠŒ‹‰‰ˆ‰ŠŠ‹‹‹ŒŽ’•˜˜”ŒŽŒŠ„‚ƒ„„†Š‘’•—˜••”–™œšŸ‘“’šŠ˜š›™˜——•••–—˜—˜˜˜–”‘‹Šˆ‡‡ŠŠ…‚wjWIKbrupopttsuy}|xww{€€€€~~{vttvz€‡‹ŽŒ‹‹Š‹‹‹‹Œ‹‰€usw‰‹Œ‹‰††ŠŽŽŠˆ††‡ˆ‰‰‡…†ŠŒŠ~qnsuw|‚‰Š‰…ƒƒ„„„‚|qxxxxxxxxyyzzzz||}}}}~~~~~~}}~~}}}}|zzzyxxxyyyyxy{{|}}€€€€€€€~~}}|{{{zz{|}~~€€€‚‚‚‚‚‚ƒ‚‚‚‚‚‚‚‚‚„‹–˜—˜•”–•”‘Ž‰……‰‹‹‰‰‰ˆ‰‰‰‹‹‹ŒŽ“–—”ŒŒ‹†€€€„ˆ‘‘“•˜™–””–™›Ÿ‡Š—‰Ž—œ›™˜˜˜•••—™™˜˜˜˜˜”‹‰‰†„†‡†‚~vm[JNfvxtpotuutw}~zx}€€€€€€~}}ywuuv{†‹Œ‹‹Š‹‹‹‹Œ‹‰€utx‚ŠŒŒ‹ˆ…†ŠŽŽŠˆˆˆ‡ˆ‰‰‡…†ŠŒ‰yppuvxˆŠ‹Š…ƒƒ„„„‚ymxxxxwwxxyzz{yy{{}}}}~~~~~~}}~~}}}}|zzzyxyyyyzzzz{{{|}~€€€€€~~~}}|{{{zy{|}~~€€€‚‚‚‚‚‚ƒ‚‚‚‚‚‚„Š”——˜•”–•”ˆ…„ˆŠ‰‡ˆˆˆ‰‰‰‹‹‹ŒŽ’”–”‘ŒŒ‡~}{|}‡‘“’‘‘“—™™–“”–™œž—‰†Šš“šž›››š™••–—š›™™˜˜˜“ŒŠ‰Š†…„ƒƒzri[LPjx{wrqswusx}‚€||~€€€~}|xwvww{†ŒŒ‹‹Š‹‹‹‹Œ‹‰€utxƒŠŒ‹‡„†ŠŽŽŠˆˆˆ‡ˆ‰‰‰‡†‹Š„torwwz‰‹…ƒƒ„„„‚wmxxxxwwyyzzz{{{{|||}}}}}}~~~~~~}}|||{{zyyyyyyyyzz{{|}}~€€€€€€~~}|}||{zzz{|}}}~€€€‚‚‚‚ƒƒƒƒ‚‚‚‚‚‚€€‚†”˜——–”–•“Œ‰…„ˆ‹Š‡ˆˆ‰ŠŠŠ‹‹‰ŠŒŽ‘’•”ŽŽŽ‡~yvxx}†••‘‘‘–šš—“”—™™‰Š’“—Ÿžžš™——–—š››š™™˜’ŠŠ‰ˆ‡„ƒ‚|ulbWMYt~~zupquvsx~ƒ}}€€€€€€~~|ywvvy}‚‡ŒŽŒŒŒŠ‹‹‹‹‹Œˆvuz„Š‹‰††ŠŽŠˆˆˆ‡‰‰‰‰‡ˆ‹‰~pqtvw}…ŠŒŒˆ„ƒƒ„„ƒ€sixxxxxxzzzzz|||{|||}}}}~~~~~~~~}}||||{zyyxxyyyyzz{{|}}~€€€€€~~}|}|}{{{{{|}}}~€€‚‚‚‚‚‚‚‚‚‚‚‚‚‚€Ž––—–••””Œ‰…†‰ŒŒ‡ˆˆˆ‰‰‰ŠŠˆ‰ŠŒŽ‘’’Œ‘‘ŽŽ‰zxwx|ƒ˜—“‘‘‘–™š–”“–šš›‰Ž””‘ ””–žžžœ™˜——š›œ›šš™“ŽŠŠˆˆ†ƒ‚€}ztkbXUbz|xonvxvx}ƒ‚€€€€€€~|yxwvz~‚‡Œ’ŽŒŒŒŠ‹‹‹‹‹Œˆvu|„ŠŽŽ‹‰†‡‹ŽŠˆˆˆ‡‰‰‰ˆ†‡‰…wosuwz‡Šˆ„„„„„ƒ~qixxxxyyzzz{z|||{|||}}}}~~~~~~}}||||{zyyxxyyyyzz{{|}}~€€€€€~}|}|}{{{{{||}}~€‚‚‚‚‚‚‚‚‚{z…“—˜—–•”’Ž‹‡†ˆ‹‰‡‡‡ˆ‰‰ŠŠˆ‰Š‹ŽŽ‘ŽŒ‘‘‘Ž‹…}yxxyŽ——•“’“•˜š•‘“•™››Žˆ––šœ““—žž žš™˜—š›œ›››š“‹Šˆˆ†ƒ{wrme_^j{€}{wpjsywx}„„€€€€ƒƒ€~{ywvvz~‚ˆ“‘ŒŒŒŠ‹‹‹‹‹Œˆvu|„‹Ž‹‰†ˆŒŽŠˆˆˆ‡‰‰‰‡…‡ˆ€qpuvw}„‰ŒŽŒˆ„„„„„‚zojxxxxyyzz{{{|||{|||}}}}~~~~~~}}||||{zyyxxyyyyzz{{|}}~€€€€€€€}|}|}{||{|}}}}~€‚‚‚‚‚‚‚‚‚‚€}wt~–˜—••“‘Ž‰‡†Š‘’‹††‡ˆˆˆŠŠˆ‰‰‹Ž‘’‘Ž‰{xyzŽ˜˜–•““”—˜•”——”ž”Œ’™˜ ˜™™›ŸžžŸž›š˜—š›œœ›™“Œ‰ˆˆ†„~ywrmiedny€|zxphoxwy}„„€~€‚‚~}{ywvvz‚ˆ’‘ŒŒŒŠ‹‹‹‹‹Œˆwv}…ŒŽ‹‰†ˆŒŽŠˆˆˆ‡‰‰‰‡…†…|nrvwx€†ŒŽŽŒˆ„…„„„‚xmkxxxxyzzz{{{{||||||~~~~~~~~}}}|{|}}}}|{z{{{{zzz{{{{~~}}~~€€€€~~{{}}||{{||}}~~‚‚‚‚‚€€€‚‚‚yqn|‹”•””•”‘‹‰‹Žˆ………‡ˆ‰‰ˆ‰ŠŠ‹ŽŽŽ’’‘‹„~~„˜˜——–””•——”–™–œ–‘—œœ ›žžž›žš œ˜˜˜œžžžœ—“Šˆ‰ˆ†„yuqppmmqw{vvuogjtxy|‚‚‚‚‚‚‚~~~{xxx|€„‰Œ’’Ž‹‹ŒŒŒŒŒ‹†}vv~†ŒŽŒ‰†‰ŽŒŠˆˆˆˆ‰Š‰ˆ†‡tnrvy}†ŠŒ‹ˆ…„†……~qjkxxxxyzzz{{{{||||||||~~~~~~~~~|{}~}}}||{||||zzz{{{{}}||}}~~||}}||||||}}~€‚‚‚‚‚€€€€|sjiyŠ“””””•“Ž‰‹ŽŽŠ…€…†ˆˆˆ‰ŠŠŠŽŽ’’‘‰‡†††Š‘˜™˜——•”•—˜–—™˜–œœ–šž  ŸŸ¡žœšŸ“Ÿ˜™šž  œžœ—“Šˆ‰ˆ‡…ƒzuttuspqtupqsphgovy}‚€€‚‚‚‚‚‚~~~yyyz~‚…‰Œ’’Ž‹‹ŒŒŒ‹†}vv†ŽŒ‰‡ŠŽŒŠˆ‰‰‰‰Š‰ˆ†…}porvz‚ŠŽŠ†ƒƒ„…ƒ{mhkxxxxyzzz{{{{||||||{{~~~~~~~|}~~~~}||{|}}|zzz{{{{||||||~~~~~~~~}}||}}||}}~€‚‚‚‚€€€€yoegy‡Ž‘’“••Ž‰Œ‘’Ž‹†{…‡ˆ‰ŠŠ‰ŒŽ’’‘‘‘‘ŽŒŒ‹Ž“–˜™˜˜–••—™——™œ˜”œšž¡ ¢Ÿ¡Ÿœ¡œŸšš› Ÿ ™œœ˜“Š‡ˆˆ‡†„€}zyzywqmnnlnsslgkqx~„„€€‚‚‚‚‚‚€~{yzz{‚…‰Œ’’‹‹ŒŒŒ‹…}wx€‡ŽŒ‰ˆŠŽŒŠˆ‰‰‰‰Š‰ˆ†yoqtv|†ŒŒŒ‹Š…€|uhgjxxxxyzzz{{{{||||||||~~~~~~}}}|}~~~~~||{|~~|zzz{{{{||}}||}}}}~~}}}}||}}||}}~€‚‚‚‚€€€€€|tjbhy„‰‰ŒŽ“•“Œ‰“’‘Œˆxz}ƒ†ˆ‰ŠŠ‰ŒŽŽ’’‘’‘‘‘‘‘””””—™™™–••—š™–˜œœ•˜žŸŸŸ¡ žŸŸžž¡“ ›šœ¢¢¡˜œœ˜“‰‡‡‡‡……ƒ}}}|ypiijjnssnhhnv†…ƒ‚‚‚‚‚‚~{zzz{€ƒ†‰Œ’’‘‹‹ŒŒŒŠ…|wz€‰ŽŒŠ‡ŠŽŽŒŠˆ‰‰ŠŠ‹Š‰†}upsuv†ŠŠ‡†‚}zzzyyuoggjyyxyyzzz{{{{||||}}}}~~~~~~}}~~~~~~||{{}}{{zz||}}}}}}}}|}}|}}~~}}}}||||||}}€€€€€€€€€€~|re_fx~„Š’‘Œ‹‘”’‘ŽŒˆ‚}{y…ˆ‰ŠŠ‰Š‹ŒŽ‘‘‘‘’’’‘’“™Ÿ™–š™—™——˜››™™š™—   Ÿ ŸŸž–˜ ¡›¡Ÿœšœ¢¥¤œ›—’‰‡†……†„ƒ‚‚€}xnbbgknrroifku‚ˆ†‚‚‚‚‚‚‚‚}|{{}„‡ŠŒ“’‘ŒŒŒŒŒ‰ƒ{xz€ˆŒŒ‰‡ŠŽŒ‹‰ŠŠŠŠ‰‰ˆƒwrstvx………~ytpqqqongefkyyxyy{{{||||||||}}}}}}~~~~}}~~~~}}||||||{{{{{{}}}}}}|}}|{}~~}}}}||||}}~~€€€€€€€€~~€€€€€~~wma_gqust}…Ž’Œ‹‘”“’ŽŒ‰…€}z€†‰‰Š‰Š‹ŒŒŒŽ‘‘‘‘“”’‘’“š¢¥ŸŠ‘™›œ›™—˜››™—™›š™¡¡¡ž›ž Ÿ”•Ÿ¡Ÿ¡žœ›œ¢¤¤Ÿ›–‘Ž‰‡†…„…„ƒƒ‚|wnfbdhkmpqlghs‚†„€€‚‚‚‚‚‚‚}|{}„‡ŠŒ““‘ŒŒŒŒŒŒŠ‚{y|ˆŽŒ‰‰‹ŽŒ‹ŠŠŠŠŠ‰Š‡|rquvwz~xuroniikiigeecfkzzyz{|||}}}}||||}}}}}}~~~~~~}}||}}||||{{||||||{||{z|}}}}}}||||}}~~€€€€~~€€€~{shb`fikloz„’Ž‹‹”“’‹‹‡„qu}„‡‰Š‰‹ŒŒŽ‘‘‘‘“”’‘‘’–ž¨¥‘‰š¤¥ š˜˜››™—˜šœ›¡¡¡–‘› ¡›™žœ Ÿž›ž¡£¢Ÿž›—‘Œ‰ˆ‡…„„‚‚€~zuplgbchlornheo}‚€€€‚‚‚‚‚‚‚~}}~€‚…‡ŠŒ““‘ŒŒŒŒŒŒ‰zx}‚‰ŽŒ‰‰ŒŽŒ‹ŠŠŠ‹‹‹Š„wqsvuuwvspkjhhfghhhfeeceizzy{{|||{{}}||||}}}}~~~~€€€€€€~~}}}}}}||||{{{{{{{{{||{z{||}}}}||||~~~€~~€€~~€€}yrgb_ceginz…‘’Œ‹Œ”“’‹‹ˆ„ƒ€mms}†‰Š‰‹Œ‹ŒŽ‘‘‘‘‘“””’’“›¦§‘ŠŽ¡ª©£œ—˜šœ›™˜›žž¡¡žœžŸž››Ÿžžœž¡£¢ ž›—‘Œ‰‡†…„„‚‚€~~{xssrnc^djospielx~|}‚‚‚‚‚‚‚}~€€‚…ˆŠŒ““‘ŽŒŒŒŒŒ‡€zx}ƒ‹ŽŒ‰‰ŒŒ‹Š‹‹‹‹Š‰‚vpuvuqrnkigffgfhihhiheddgzzyz{|}}||}}||||}}}}}}€€€€€€€€~~}}~~}}||||{{{{zz{{{{{|}}}}}}}}|}€€€€€€€€€~~}~~~|{wnfaacehinz‡’’Œ‰Ž‘”–“‹ŒŒ‡„ƒ‚rjlx‚‡‰ŠŒŒ‹‹’““’““••’’‘“š¢¥‘Š˜«®ª¥——šœœ›™›žŸ ž•˜œœœ™˜šœŸŸŸœœ £¡žœ›—‘ˆ‡†…„ƒ‚€~{vtwzwj_^ckpojfkuyz{~€‚‚‚ƒƒƒƒ€~ƒ…‡ŠŒ”“‘ŽŒŒŒŒŒŒ†~yy~…‹Œ‹‰ŠŽŒ‹Š‹‹ŒŒ‹‡~tsvtqnjfdcdeghjlmnmmkggefzzyz{|}}||}}||||}}}}}}€€€€€€€€€€~}}~~}}}}||||{{{{{{||||}}}}}}}}|~€€€€€€€€€€€~~}~~~}{siedceegfl{‰‘Œ‰’”—”’ŒŒŒ‰†„‚vlmw~„ˆˆŒŒ‹‹’““”““••“’‘’—Ÿ£“Ž¦­©¥£œ——šœ››œžžšžœšš˜˜™œž››Ÿ¢Ÿ›˜•Œˆ‡††„ƒƒ€~|yww|~sf__glnjfly|||~‚‚‚ƒƒƒƒ€€‚ƒ…‡ŠŒ”“‘ŽŒŒŒŒŒ‹…~y{†ŒŒ‹ŠŠŽ‹ŠŒŒŒŒŠ…yutsokhedceggkostvvqnjjiiizzyz{|}}}}}}||||}}}}}}€€€€€€€€~}}~~}}}}}}}}{{{{{{||||}}}}}}}}|~€€€€€€€€€€~~~}~~~|vmfegeeefgn|‰‘Ž‹‹’”—–“‘ŽŒŠ‡†ƒ{rqv|‚ˆ‰‹ŒŒ’““•““””“’‘’•›¡—ž«©£¡Ÿš™˜›œœœœœš—¡Ÿ›™š›š™œœœš››™œžž›™•’Ž‹‰‡††„ƒ„€~|zvwz€xlb^aimjfmy€€‚‚‚ƒƒƒƒ€€‚ƒ…ˆŠŒ”“‘ŽŒŒŒŒŒ‹…~y{€‡Œ‹Š‹ŽŽ‹ŠŒŒŒŒ‰tssmjgddddgjmsw{|~~vpkmkmmzzyz{|}}~~}}||||}}}}}}€€€€€€€€~}}~~}}~~}}}}{{||{{}}||}}}}}}}}|~€€€€€€€€€€~~~}~~~zrgcfhfdeego}‰‘‰Œ‘“”——•”Š‰†„vtu}ƒ‰Š‹Ž‘”•”•”“””•’’“”™ Ÿ««¤Ÿ›™˜˜›žœœœœ•›¤Ÿ›š›œ›™š›™—™™™››—”‹Š‡††„ƒƒ€|zxvy}€yqd^^gmkglw€€€€‚‚‚ƒƒƒƒ€€‚ƒ‡ŠŠŒ”“‘ŽŒŒŒŒŒŠ„~y|€‡Œ‹‹ŒŽ‹ŠŒŒˆ~qspjfeehefintz}€‚„‚zrmmmpq{{z{{}}}~~}}}}}}|||~~~~~~~}~€€€€€€€€€~~~~~~~~~~~}{||||{{||||}}}}}}}}}}€€€€€€€€€€~~~~~~~xpebehddedgp{‰Œ‰Œ‘“•˜–•”‘ŒŠ‰†€zttzƒŠ‹Ž’“”–•”“”•””““”—ž¦¬¨Ÿœ™››™™œžžžœ››œ•¢¦¤ œšš™–•™˜˜šœ›™—’ŽŒ‹‰‡†…„ƒƒ€|yxx{€|via]clmkmt|€‚‚ƒƒƒƒƒƒ‚ƒ„‡‰Š‘““’ŽŒ‹ŒŒŒŠ‚|z{€‡Œ‹ŽŒ‹ŒŒŒŒ…wnmigdfgjilqw€‚…‚„†‚zrmnptv{{{{{}}}~~}}}}}}|||~~~~~~~}€€€€€€€€€€€~~~~~~~~~}{||||{{||{{||}}}}}}}}€€€€€€€€~~~~~~~}ukebegddddhq~‹Š‰‘“–˜–•””Ž‹Šˆ„~vuz„ŽŽ’“”–•”““”•–”“”•œ§¨¢œ˜–™™˜˜›žžœœš››˜¤¦¤¡ Ÿœ™™˜••˜˜˜››™–‘ŽŒŠ‰†……„ƒƒ~||}~€€€~{pg^_imnoqx|€ƒƒƒƒƒƒƒƒƒƒ€€€ƒ…‡‰Š“““ŽŒ‹ŒŒŒ‰‚|{{‚ˆŽ‘Œ‹ŽŒŒŒŒŒ‹rmkhgggjnpw~„‰‰ˆ……†€vnlqtz}{{{{{}}}~~}}}}~~~~|~~~~~~~~€€€€€€€€€€€€~~~~~~~~~}{||||{{{{{{||}}}}}}}}€€€€€€€€€€~~~~~~|yqgeeegffccir€Œ‰Š“—™—–•–’‘ŽŒ‰†€vtz„’Ž‘’“”––•‘‘‘”–””•–™¢£ž˜””—––—šž›š››¤¤¢Ÿœ›š™˜••™˜˜šš˜”Œ‹‰ˆ†…„ƒƒƒ‚€~~~€€€€€|woc^elqqorv|‚ƒ‚‚ƒƒƒƒ„„ƒ‚ƒ…‡‰Š’““ŽŒ‹ŒŒŒˆ|{{ƒ‰Ž‘Œ‹ŽŽŒŒŒŒ‹‡yokijjlkotyˆŒ‰†††~rmmsz€„{{{{{}}}~~}}}}~~|~~~~~~~€€€€€€€€€€~~~~~~~~~}{||||{{zzzz{{}}}}}}}}€€€€€€~~€€~~~~~~{wndegggghccir€‹‹‰‹”˜™—–––“‘ŽŽ‹†€vuz„Ž’’’’“”–•”‘‘“••””•–›œ—‘“—––—šžžœš™šš ££Ÿš›š™š™˜••™š™™›™˜“Ž‹Š‰‡…„„‚ƒ‚|}€€€€~{ui`dkqqont{‚‚ƒƒƒƒ……ƒ‚ƒ†‡‰Š’““ŽŒ‹ŒŒŒˆ|{zƒŠŽ‘ŒŒŽŽŒŒ‹…umjgklmosyˆŽ’‰††„}qopw|ƒ†||{||}}~}}}}~~~~}~}~~~}~€€€~~~~~~}}}}|z||||||{{z{||||}}|}~~}€€€€€€€€~}}}}}ytidfgijjhbbjs~ˆˆ‡Œ‘”—˜˜—˜˜–”Œˆ„{x|„“‘‘’“““•”‘Ž“••••“—™›—‘”””•–šžŸ›˜•›š¢¢¢ž›———˜š™˜—–˜™˜™˜––“‹‰‰ˆ……„ƒ‚~}|}€‚€€€~yoaejoojgnyƒ‚€~ƒ„‚„„‚‚€‚‚„††ˆ‹Œ‘’’ŽŒ‹‹‡€zz~ƒ‰‘Œ‹ŒŒŒŠoifhlppt{ƒŠ‘’‘‰‡‡ƒzqnrx~„…{{{||}}~~~}}}}~~~~~~~}€€€~~}}|z{{{{{{{{z{{{|||||}~~~€€€€€€~}}}}}xofcfgjkkhbbjs……†Œ‘”—˜˜˜™™—–ŽŒ‰‡~y}„Ž’‘‘’“““•“’”••”“••™–“”””•–™œŸš—•šœ¡ Ÿ›™–––—˜—˜˜–——–˜–•”’Ž‰‰‰‡†…„ƒ‚€}|{|€€}xnlmnnibixƒ„€~ƒƒ‚„„‚‚‚ƒ…†‡‰ŠŒ‘’’ŽŒ‹Š…~{z~…‹‘Œ‹‘ŒŒŒˆxiegjotv|…ŒŽ‘’’‘‰‡‡‚xqnsy…†zzz{|}}~~~}}}}~~~~~€€€€€€€€€€}}|z{{{{zzzz{||||||||}~~€€€~~~~~~~}}}|{tidcfiklkgbbit„…†Ž‘“–˜š˜˜šš˜–ŽŽ‹Š‰z~†‘‘‘‘’““’““ŽŒŽ’••“’’”˜—•”””•–˜››˜••˜›žœ›™–••–——˜˜–•––—••”‘‰‰ˆ‡†…„ƒ‚€}{z{}€€‚~xsqpqmeivƒ†ƒ€‚„„ƒ„„‚‚ƒƒ…†‡‰Š‹‘’‘ŽŒ‹Œ‰…}|{†‹‘Œ‹ŒŒŒ‹qfehksx|ƒ‹‘‘’’‘‰‡†upps{…‡zzy{|}}~}}}}}}~~~~~~}}€€€€€€€€€€}}|z||zzzzyy{{{{zz{{|}~~€€€~~~~~~~}}}{xodacfilnkgbbhu~„…ˆ’”–™šššš™—–ŽŽŠŠ‰ƒ|~†‘‘‘’”“’‘‘‘Œ‹Ž””“’“”––””“”•–˜››™•““•™››œ›™–•••——˜˜–••––•••‰ˆˆ‡†…ƒƒ|{z{|€‚‚‚|xtuusklu‚†„‚ƒƒƒƒ„„‚‚‚ƒ„…†ˆŠŠ‹‘’ŽŒ‹Œ‰ƒ|||‡Œ‘Œ‹ŽŒŒŒŽŠ|mfgilv{‚‰Œ’“’’‘‰‡†|ropt|‚…‡{{{{}~}~~~~~~~~~~~}~€€€€€€€€€€€€€€€~~}|}}{zzzyyyyzz{{{{||}}~~~~€€€€}}}|||}}}}{vjbadglpomibckuƒ„‰’”•–™›ššš™—•‘Ž‹‰‡ƒ~…‘’“ŽŒ‹‹ŠŒŒ’”““’’••“““”•——š˜—•’’“•š›šš™™—••——˜˜———–•”•“‘‰ˆˆ††…„ƒ€~|{y{{}€‚‚‚ƒƒ‚€}|{zzyspv~…†ƒ€‚„„ƒƒ‚‚„„…††ˆŠ‹Ž’ŽŒŒŒŒŒ‡|{|‚‡‘ŒŒ‘ŽŒŒŒ‰vhejmqyƒ‰‘“““‘‘‰ˆ„zqprx€„‡‡|||||}}~~~~~~~}}~~~~~~}~€€€€€€€€€€€€€€€}}}}|zzz{{yy{{{{{{{{{|~~~~~~~~~|||}}}|wpgdaeimrpnibcjs}‚„‰”••–™›ššš™–”Ž‹‡„€‚‹ŽŽ’’Œ‰‡†…†ˆ‹Ž‘’’’’””“““•™›š—–”“‘’“•˜˜—™š›˜––——™™˜™š—•”•“Œ‰ˆˆ††…„‚€~|zzz{|€‚ƒƒƒƒ‚€~{wyzƒ†‚{y|„„ƒƒ‚ƒ„„†††ˆŠ‹ŽŽŒŒŒŒŒ†€|z|‚ˆŽ‘ŽŽ‘ŽŒŒŒ„rghmov€‰Ž‘‘““““‘Œˆ†ƒxpprz‚…‰ˆ||}}{|}~~~~~~~}}~~~~~~}~€€€€€€€€€€€€€€€}}}}|zzz{{{{{{||{{{{z|~~~~}}}}~~~}}}}}zskedcejosqngbcir|„‰”––—™›šš™—•’ŽŽŒ…~{‚ˆŽŽ‘ŽŠ†ƒ‚…ˆ‘‘““““”–¡Ÿ–“”’‘““–—˜˜—šžš˜—˜™šššœ˜•“”“Œ‰ˆ‡††…‚~|{z{{|€‚ƒƒƒƒ‚‚€€€|x…ywx€„„„„ƒƒ„…†††ˆŠ‹ŽŒŒŒŒ‹…€|{|‚ˆŽ‘‘ŽŽŒŒ‹|mfinqz†Ž‘‘’““““‘ŽŠ‡…€vpqs{ƒ‡‰‰}}}}{|~~~~~~~~||}}}}~~~€€€€€€€€€€€€€€€}}}}|zzz||{{{{||{{||{~~~~~}}||~~~~~}}|zqhddddkpsrnfbcir|…‰”˜–˜™›šš™—”‘ŽŽ…{w}}ƒˆŽŒŒŒŽ‹ˆ…€~€ƒ…‹Ž‘‘‘’“”™¡¦Ÿ—““’’“”˜˜˜˜–™œ™˜™šœšœžœ—”“”’‹ˆˆ‡††„‚~|{y{{|€‚ƒƒƒƒ‚€€€€ƒ‚„„~w{ƒ€xvy„„……ƒƒ……†††ˆŠ‹ŽŒŒŒŒŠ…€|{|ƒˆŽ‘‘Ž‘ŽŒŒ‡uhdios~Š’’’““’“‘Š‡…~spsu}ƒˆŠŠ}}}}|}~~~~~~}}||}}||~~€€€€€€€€€€€€€€€~~}}{{{{yzzzz{{||||{{{|~~~~~~}}|}€€€€}|zulededhnrsqlfadkw~ƒ…‰•˜—™››››—•“Œ‡xow|‚†Ž‹ŒŒ‰‡„€~{|ƒ‰‹‘‘‘”–œ¦«¥˜“‘‘“•—˜———˜››™™šœ››œž›˜”””’‰‡‡‡………ƒ‚~||z{|}€‚ƒƒƒƒ‚ƒ‚††€x{‚zz|‚……„„ƒƒ……††‡‰‰‹ŽŒŒŒ‹„}|„‰‘‘’’ŒŽŒ€ndeknt‚Ž’““““””“‘Œˆ†…|rnqw€‡‰‰‰~~~~}}~~~~~~}}||}}}}~~€€€€€€€€€€€€€€€~~}|{{{{yzzzz{{||||{{{|}}}}~}}{}€€}}}{yqhcdffipttqlgdfmy€„…‰•˜˜š›››š—•’ŽŒ‹‹‹Šyjqy‰Šˆ‰‹‹‹‹‰ˆ„€}{|ƒ‡‰‹‘’—¤ª­¥™”‘‘“•——–––—šš™™šœœ›œš—”“’‘Œˆ‡‡‡………ƒ‚~}}|||}€‚ƒƒƒƒ‚‚‚ƒ„ˆ‡ƒ{~ƒ€|}„……„„ƒƒ……††‡‰‰‹ŽŒŒŒŠ„}}€„‹‘‘‘’’Œ‡ykfgkny‰“”“””••”‘Œˆ†„xpory‚ˆ‰ŠŠ~~~~~~~~~~~~}}||||||}}~€€€€€€€€€€€€€€€~~}|{{{{yzzzz{{||||{{{|}}}}}}}}|}~~}}}{vldceghlqttpkhgho{„…†‹•˜™›››š™˜•’ŽŒ‹ŠŠŠ{gjv}}€‚‚†ˆŠŠŠ‡„€|{|ƒ†‰ŠŒŽ‘”›¥ª¬ª£›•’’‘’•–—•••–™™™™šœ›š›œ›˜”“‘‹ˆ‡‡‡………ƒ‚~}}}}}~€‚ƒƒƒƒ‚‚‚‚ƒ„†‰ˆ…‚{„……„„ƒƒ……†††ˆ‰‹ŽŒŒŒ‰ƒ~~…‹‘‘‘’’‹sjhikq~‘“”“””••”‘Œˆ‡uoquzƒˆ‰ŠŠ~~~~~~~~~~}}}}||}}}}~€€€€€€€€€€€€€€~}}|{{{yzzzz{{||||{{{|||||||}}~~}}~~~~}zshcdffjnqutolhgjr}†‡‡‹•˜™›œœš˜—•’Ž‹ŠŠŠ‰|feszxyz|~‚…‡‰ˆ‡…|{|ƒ…‰Š‹Ž”ž©¬«¦¢”‘‘”•–”””•™™™™šœ›™š›š˜”“Š‡‡‡‡………ƒ‚~~~}}}~€‚ƒƒƒƒ‚‚ƒƒƒ…‡ˆ‰…‚‚‚}z~‚……„„ƒƒ……††…‡‰‹ŒŽŒŒŒ‰ƒ€…Œ‘‘‘’’Ž‹~mijknv‚““””””••”‘Œˆ†}upqv{…ˆ‰‹‹~~~~~~~~||}||||||~€€€€€€€€€€€€€~~}|}|{{{z{{{{zz{{{{{{||zz||}}~~~~~~}|vmdaehjmpuwsnjghks~‡ˆ‡–™™œœ›š˜—•“Š‰‰Š€jamwvxwx|ƒ†‡††…‚}~‚†ˆ‰‹ŽŽ’ž©«¦£Ÿš•Ž‘“•”’‘”˜™—™›š—˜™—•“’‰‡‡‡ˆ‡†„ƒ~~}}~ƒƒƒƒƒ‚‚„…‡ˆ‡„‚ƒ€zy|~‚„„ƒƒƒƒ„†………ˆ‰ŠŒŽŽŒŒŒˆƒ€‚‡’‘‘‘‘‘‘ŽŒ‹†zkjjlqz‰‘””“”••••“Š‡†|spsx…‰ŠŠŒ~~~~~~~~||}||||||~€€€€€€€€€€€€€€€~~}}|||{z{{{{zz{{{{{{||{{||}}~~~}zqhbafimoswxrlighmv€ˆˆ‡•˜˜›œ›š—–•“‹‡‡ˆŠƒn`isuvwy{~ƒ††…†…ƒ‚€‚‚…‡‰‹Ž‘™¢¦£¡œ™“Ž‹‹Ž‘“’’–˜–™›œ™•––•“‘ŽŒ‰‡‡‡‡‡†„ƒ~~~ƒƒƒƒƒ‚‚ƒ…††…„ƒ…vuy}„„ƒƒƒƒ„†……†ˆ‰ŠŒŽŽŒˆƒ€€€ƒ‰Ž‘’‘‘‘‘‘‘Ž‰sjkkmtŒ“““’”••••“Š‡„zrqty€†ŠŠŠŒ~~~~~~~~~~}|||||~€€€€€€€€€€€€€€€€€€~}}|||{z{{{{zz{{{{{{||}}||}}}~~~}xne`cgjmruxwqlhgjrzƒ‹‰†Œ•˜—šœ›š˜—–”‡ƒ…ˆ‡‚p`dqvwwx|‚„…„…„„„‚ƒƒ‚„†ˆŠŒŽ”šŸš—“ŽŠˆ‰‹ŽŽ”•–˜›š˜•””“‘Ž‹ˆ‡‡†‡‡†„ƒ€ƒƒƒƒƒ‚‚‚‚‚„††…„ƒƒ}tsw|„„ƒƒƒƒ„†‡‡†ˆ‰ŠŒŽŽŽŽŒˆƒ„‰Ž‘‘‘‘‘‘‘‘ŽŒ‡|mkklow‚”““’”••••“‰†ƒxqruz‡ŠŠŠŒ~~~~~~~~~~}|||||~€€€€€€€€€€€€€€€~}|}}{z{{{{zz{{{{{{||||||}}}~~}~}vkb`dhkntwywqlhimu|…‰…‹”——šœ›š˜–•“…€„‡„sb`nuvwx{~‚ƒ…„„„ƒƒ‚ƒƒ„ƒ…‡‰‹ŒŽŽ–™šš—–’‰‡‡‰Œ‹Œ’”•˜›™—•“’ŽŒŠˆˆ‡††††„ƒ€€€ƒƒƒƒƒƒƒ‚‚‚ƒ„„„„ƒ‚{srw}„„ƒƒƒƒ„†‡‡†ˆ‰ŠŒŽŽŽŽŽŒˆƒƒŠŽ‘’‘‘‘‘‘‘ŽŠ†vjjlmq{†”““””••••“‰†vrsv|ˆŠŠŠŒ~~~}|||{}}||}}|||}~~€€€€€€€€~~}|}}{{z||{zzzz{{{|}}}}}}}}|}~~}}~}{sibagjmrxy|wqlihnt{†Ž‹‡‰•—™š˜–•“ˆ…ƒƒve`ltvwy}ƒ…„…„„„ƒƒƒƒƒƒ„†‰ŠŽ“•—˜—”“‰„…†‰Œ‹ŠŠ”“—™™–““’ŒŒŠ‰‡‡ˆ‡†††…ƒ‚€~~€‚‚ƒƒ„„ƒƒƒ‚ƒƒ„„…„„ztpu{€ƒƒƒ‚„„…††††‰‹‹ŒŽŽŽŽŽŽ‹†ƒ‚‚ƒ‡Œ’’’’’’‘ŽŽ‰‚rjnmnv€Œ“”””••••••’‰…}ursv|„‰ŠŠŠŠ~~~}}}|{}}||}}|||}~~€€€€~~~~~~}|||{{z||{zz{{{{{|}}}}}}}}|}~~~~zxpf`chkmtyzztolhhnu{…Ž‹†ˆŽ“–˜›œš—•”“‘‹ˆ‚}wh^mwz|}€ƒ„„…„„„ƒƒƒƒƒƒ„†‰Š‹Ž“•–•“’ˆƒ‚„‡ŠŠŠŠ‹’‘•–—•’’ŽŒŒŒŠ‰‡‡ˆˆ††††„ƒ€€~~€‚‚ƒƒ„„ƒƒƒƒ‚ƒƒƒ„„ƒƒ{sklt|‚‚‚„„…†††‡ŠŒŽŽŽŽŽ‰†ƒƒƒ„‰Ž’’’’’’‘Žˆ{ojnnpxƒ”“””••••••’‰„zsrtx~†Š‹‹ŠŠ€€~~~}}}}}||||}}|||}~~€€€€€€~~}}~~}|||{{z|{zzz{{{{{|||}}}}}}|}~€€{tlc`eijpvyzytmihkow{…‹…‡’•˜™š™–•”“’Š‚‚}xj_m}~€ƒƒ……„„„ƒƒƒƒƒƒ„†‡‰‹ŒŽŽ“”““‘Œˆƒ‚ƒ…ˆŠ‹‰Š‹“”•”‘ŒŠ‹‹‰ˆ‡‡ˆˆ‡‡‡†„ƒ€€‚‚ƒƒƒƒƒƒƒƒƒƒƒƒƒ„„ƒƒ|tkdjvƒ‚‚„„……††‰‹ŽŽŒŽŽŽŽŽ‹ˆ…‚ƒƒ…ŠŽ‘’’’’’’‘†wlknor{‡•”””••••••’ˆ„xqrvy€‡Š‹‹ŒŒ~~~}}}}}{{||}}|||}~~€€€€~~}}~~}|{{{{z|{yzz{{{{{|{{}}}}}}|}~zricafjltyyzxslhglrt{†Š…‡Œ’”—˜™—””””’Š……‚{mal~€ƒƒ†…„„„ƒƒƒƒƒƒ„††‡Š‹ŒŒŽ‘“’’Œˆƒ‚„†‰‹ŠŠŠŒ’“’Š‰ŠŠ‰‡‡‡ˆˆ‡‡‡‡„„€€€‚‚ƒƒ‚‚ƒƒƒƒƒƒƒƒƒ„„ƒ‚€}ukacr€„„„ƒ„……††‰‹ŽŽŒŽŽŽŽŽŒˆƒ‚„‡Œ’’’’’’’‘Œƒtklnpu}ˆ‘–”””••••••’ˆ„wqsv{ˆŠŒ‹ŒŒ€€~~~~~~||{{{{||||||||}~~~~||~~|}||||{|zy{{{{{{||{{|||}}}}}~~~~zqhbdilov{{|ytmhgmux{…Š……‰’•™˜•”“““’Ž‹‡†ƒ}p`j€‚‚ƒƒ……ƒƒƒƒƒƒƒƒ…†‡ˆŠŠŠŠŒ‘‘Œ‡ƒ‚‚‚„‰ŠŠ‰‰Š‹Ž‘‘‹‰‰‡ˆˆˆ‡‡ˆˆ‡‡‡‡……‚€€‚ƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚ƒƒƒytla\j|ƒƒƒ„…†††‰‹ŒŽŒŽŽŽ‹†‚‚ƒ†‰‘’’’’’‘’’‘Š€pjkosy‚Œ’””””””••–”“Žˆ‚vqsw}‚‰‹‹€€~~~~}}}|{{{{||||||||}~~~~~~~~~~~~~||}}|}}}}}||{{{{||{|||{{{{{|}}}}~~~~}wmdbfjmqx{||yuojjnrru‚Ž‹†…‰‘”—–”’‘’’’Œˆ…„‚saf}ƒ‚€‚‚‚‚ƒƒƒƒƒƒ„„„„ƒƒ„††ˆŠ‰ŠŠ‹ŒŽŽŠ…ƒ‚‚‚ƒ†ˆˆˆ‰‰‰ŠŽ‹‰‡ˆ‡ˆˆˆ‡‡ˆˆˆˆˆˆ‡…ƒ‚€€‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ‚|vpjbVZr€‚ƒ„…†††‰‹‹ŒŒŽŽŽŠ„‚‚„†‰Ž‘’’’’’‘’’‘Š{okkot{„“••””””••–”’Žˆvrsw~ƒ‰‹‹€€~~~~||}|{{{{{{|||||||}~~~~~~~~~~~~~~}}|}}}}}||{{{{||||||{{{{{|}}}}~~~}wlbbgjnsx{}{yuqllnnkn~Ž†…ˆ‹’•”‘‘‘ˆ…†„€vde{„„‚ƒƒ„„„„ƒƒ…………„„ƒƒƒ…†‡‰‰‰Š‹‹ŽŒ‰…ƒ‚‚‚ƒ†‡ˆˆˆ‡‰Š‹ŠŠ‰‡††‡ˆˆˆ‡‡ˆˆˆˆˆˆ‡†„ƒ€€‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚ƒ|vpkbRQi|€‚„„…†††ˆŠŠ‹ŒŽŽŽŽŒˆƒ‚ƒ…†Š‘’’’’’‘’’‘‡vnmmpt}†”•”””””••–”’Žˆursx~„‰Œ‹€€~~~~||}|{{{{zz|||||||}}}}}}}~~€€~~~~~~|}||||||||{{{|||||{{|||}}}}}~~~~vk`chiotz}}zyurnnojgkŽŽ†…†Œ’““‘‘Ž‰‡†…‚yhfz„…ƒ„„„„„„„„††……„„ƒƒƒ„†‡‰‰‰ŠŠŠŒŽŒ‰…ƒ‚‚‚ƒ„†‡ˆ‡†ˆ‰‰‰ˆ‡…„…‡ˆˆˆ‡‡ˆˆˆˆˆˆ‡†…„€€€ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚ztneQL`w~‚…†††††‡‰‹ŒŒŽŽŽŽ‹‡ƒ‚ƒ…‡Œ‘’’’’’‘’’‘Ž„snmnrw~ˆ‘”•”””””••–”’Žˆ~tqsx…ŠŒ‹€€~~|~~~~{{{{{{{|||||||}}}~~~~~~~~~~}~||}}||}}||||||{{{{||||||}}~~~|vkdfijou{}}|zvsppmdai‡ƒ…‹Ž’‘ŽŽ’Ž‰ˆ‡…‚zjew„‡…„„„„…………††…………„„ƒ…†‡ˆˆˆ‰ŠŠ‹Œˆ„‚‚€ƒƒ„…††‡‰ˆˆ††…„„…‡ˆ‰‰ˆˆ‡‡ˆˆˆˆ‰‡††„‚€€‚ƒƒƒƒƒƒƒƒƒ‚‚‚ƒ‚ƒ‚ƒƒ‚}umbMJYp~€ƒ…††††‡ˆ‰‹ŒŒ‹ŽŽŽŠ‡ƒƒ„†‰’’’’’’‘’’’plmoszŠ’••”••••–––“‘Œ‡|srtz€‡Š‹‹Œ€€~~|}}}}{{{{{{{|||||||}}}~~~~~~~~~~||}}}}}}||||||{{{{||||}}}}~~~{vlfhjlov|~{{zvsqpk`]jƒ‘‡ƒ†ŒŽ‘ŽŽŽ’Š‰†„zkdu„ˆ‡„„……‡‡‡‡‡‡‡‡‡‡„„„…‡‡ˆˆˆ‰ŠŠ‹ŒŽ‹ˆ„‚‚€€‚„††ˆ‰ˆˆ†„ƒ„„†‰‰‰ˆˆˆ‡‡‡‡‡‡‰ˆ††…„‚‚ƒƒƒƒƒƒƒƒ„€‚‚ƒƒƒƒƒƒ~uk`[]ly€‚ƒ…††††‡ˆ‰‹ŒŒ‹ŽŽŠ†ƒƒ„‡ŠŽ‘’’’’’’’“‘‡xnlopu{ƒ‹“••”••••–––“‹…zssu{ƒ‡Š‹ŽŽ~~~~}|}|{|{{{{{{|||||||}}}~~~~€€~~}}||}}}}}}||||||{{{{|||||}}}~~}{vmgiklrw}~{{ywtrph^^lƒ‘‡ƒ†Œ‘ŒŒ‹ŒŽ’Š‡„{lbrƒŠ‡††‡‡‡‡‡‡ˆˆ‡‡‡‡††……‡‡ˆˆˆ‰ŠŠŠŠŒŽŒˆ„‚‚€€€‚„††‡ˆˆˆ†„ƒ„†ˆ‰‰‰‡ˆˆ‡‡‡‡‡‡‡‡††…„„„„„„„ƒƒƒƒ‚{yx|€‚ƒƒ‚‚‚ypjkqy~‚„ƒ…††††‡ˆ‰‹ŒŒ‹ŽŽŒˆ„ƒƒ„‡ŠŽ‘’’’’’’’“’Žƒsmmpqv}…“••”••••–––“Š„xssv|„‡‹ŒŽŽ~~}}~~~|{{{}{{{{{{|||||||}}}~~~~€€€€~~||||~~}}}}||||||{{{{||||}}}}}}~~}zvnijlmty}~||{zvtpe\_nƒ‘‡ƒ‡’ŠŒŒŽŽ’Š‡„€xocoƒ‹ˆˆ‡‰‰‰‰‰‰ŠŠ‰‰‰‰††…†‡‡ˆˆˆ‰ŠŠ‰Š‹ŽŒ‰„ƒ‚€~‚ƒ†††‡ˆˆ†„„†ˆŠŠ‰ˆ‡ˆˆ‡‡††††††††…ƒ………………ƒƒƒƒ|tprx€‚ƒƒ~zxuux~‚‚ƒ…††††‡ˆ‰‹ŒŒ‹ŽŽ‹‡„ƒƒ„‡‹’’’’’’’“”“Œpmppsy€†“••”••••–––“‰ƒxssv|…Š‹ŒŽŽ~~~~}|||{{{{{{{{||{{{{|}}}}}}}}}~~}}}}}}}}}}||||||{{||{{}}|}||}}~~|ztojmlov{~~~}|zwsme]_n…†„‡‘‹Š‹ŒŒŽ“‘ˆ…‚}upfiŠŠˆˆ‰‰‰‰ˆˆŠŠ‰‰ˆˆ††……‡‡‡‡‡‡‰‰ŠŠ‹ŒŠ†„‚€€€€ƒ„††‡‡‡†…†‡‰Šˆ‡ˆ††ˆ‡‡††††………†‡†……„„„„„„„„vleis{€ƒƒ‚‚€~|||}z|‚„†……††‡ˆˆ‹ŒŒŒŒŒ‹†ƒƒƒ…‡ŠŽ‘’’’’’’’”’|nmopsz‚‰’“”•”•••––––“Šuqsv|†ŠŒŽŽ~~~~}||{z{{{{{{{||{{{{|}}}}}}}}}~~~~~~}}}}}}}}}}||||||{{||{{||{}||}}~~|ytplnnrx|~€~}zxsi_Zap„ŽŽ†„†Œ‘Œ‹ŠŠ‹ŒŽ“’‡…yqmcdˆ‹‰Š‰‰‰‰ˆˆ‰‰ˆˆ‰‰††……‡‡††‡‡‰‰‰Š‹Œ‹‡„ƒƒ‚„††‡‡‡††‡ˆŠŠ‡‡†……‡††††††………†††……„„„„„„…ƒ}rf_enx~~zz|~{|~‚ƒ…†……††‡ˆˆ‹ŒŒŒŒŒŠ„ƒƒ„…‡ŠŽ‘’’’’’’’”‰wnmnpt{‚Š“”•”•••––––“Ž‰sqtw~†ŠŽ~~~~}|{{z{{{{{{{||{{{{|}}}}}}}}}~~~~}}}}}}}}}}}}||||||{{||{{{{z|||}}~~{ytpmnory|~€}zwqgZXetƒŠŠ…ƒ†ŒŠŠŠŠŠ‹’’Œ‡„zql`d|†Š‰ŠŠŠŠŠ‰‰ŠŠ‰‰‰‰ˆˆ‡‡‡‡††‡‡ˆˆˆ‰ŠŒŒ‹‡……„€€€‚„††‡‡‡††‡ˆŠ‰‡…„„„…††††††………†…………„„„„„„„‚|qc^^gq{€€}yx|~{|~ƒ…†‡‡††‡ˆŠ‹ŒŒŒŒŒ‹ˆƒƒƒ„†‰ŒŽ‘’’’’’’’”„smlnpv|ƒŠ“”•”•••–––•’Œ‡|ssuy€‡‹ŽŽŽ~~~~}|{{zz{{{{{{||{{{{|}}}}}}}}}}}}}||}}}}}}}}}}||||||{{||{{{{z{||||}}zxtpnnnqy|~}}zuneXXht‰‰…‚…‹ŽŒŠˆŠ‰ŠŠŒŠ‡„|toaex†ŠŠ‹ŠŠŠŠ‰‰‰‰ˆˆŠŠˆˆ‡‡‡‡††‡†‡‡ˆ‰ŠŒ‹ˆ‡†ƒƒ‚€ƒ„††‡‡‡†‡ˆ‰Š‰†ƒ‚‚ƒ„……††††………†…………„„„„„„ƒ‚|rc^Zbkw~}€€~{xw|~}}}€ƒ…†‡‡††‡ˆŠŒŒŒŒŒŒ‹‡‚ƒƒ…†‰Œ’’’’’’’’”‹€plmnpw†‘“”•”•••–––”‘‹„yrsu{‚‡‹Ž}}}}||||||{{{{zz{{{{{{zz{{}}||}}|}}}}~~~~~~~~}}~~}}}}}}}}}}||{{zz{{zzzyyz||z||||yqmmlnry~€~ztl_Y\js~‡‹†ƒ„‰ŽŠ‰‰‰‰‰‹ŒŽ‰‡…‚~yskap‚‹‹‹ŒŒŠŠŠŠ‰‰ˆˆˆˆ‰‡ˆˆ††‡‡ˆŠŠˆ‰ŠŠ‹ŒŒ‹‰ˆ†„ƒ‚€‚‚„††‡‡‡†‡ˆ‰Š‰…~€‚ƒ„„……†††…†…„„ƒƒƒ„…………„ƒ|qe^Y]fov|~zyyy{~}|~‚„„…†‡†‡ˆ‰‹ŒŠ†„‚„…‡‹Ž‘’’’’’’“‘ˆ{nmlmqx€‡Ž’”••”•••–––”‹„wsru}ƒ‰ŠŽŽŒ}}}}||||}}||{{zz{{{{{{{{|||||||||}}}}~~~~~~~~~~~~}}}}}}}}}}||{{{{{{{{zyyz||zz{{zwokkjlry~€~xpeZY^ir{‡Š†‚„‰ŽŒŠ‰‰‰‰‰ŠŒŒˆ‡…„ƒ{sjr€‰ŠŠ‹‹‰‰‰‰‰‰ˆˆˆˆ‰‡ˆˆ††‡‡ˆŠŠˆˆ‰‰Š‹‹Œ‹‰ˆ…„‚€‚‚„††‡‡‡‡‡ˆŠŠ‡}z}ƒƒ‚ƒ‚ƒ„……†…„„ƒƒƒ„„„„„ƒƒ|qd]Y[clqy}{zywwy~~|z}„„…†‡‡‡ˆ‰‹Ž‹ˆ…‚ƒ……ˆŒ‘‘’’’’’’“ƒulmlnqx‡Ž’•••”•••–––”Š‚usru~…Š‹ŽŒˆ}}}}{{||}}||{{zz{{{{{{|||||||||||}}}}~~~~~~~~~~~~~~}}}}}}}}}}}}||||{{||zyyz{{yyz{yunkkjjox|~€|ulbZ[`fny…‰…„‹Ž‰ˆˆˆˆˆ‰‹‹Š‡†…†…‚€}y|‡‰‰ŠŠˆˆ‰ˆ‰‰ˆˆˆˆ‰‡ˆˆ††‡‡ˆŠŠ‰‰‰‰Š‹‹ŒŒ‹‰ˆ†„‚‚‚„††‡‡‡‡‡ˆ‰‰„~zwz€‚~ƒ„…†…„„ƒƒƒ„ƒƒƒƒƒƒ}rd][\aipwzzzyvvx||{z}„…„†‡‡‡ˆ‰ŒŠ‡…ƒƒ…‡ŠŒ‘‘’’’’’’’Œrmnlpt{‰’•••”•••–––”Š€tstw…‹ŽŽŠ…}}}}{{||}}||{{zz{{{{{{||}}}}||{{|}}}}~}}~~~~~~~~~~~}}}}}}}}}}~~}}||{{||zyyzzzyyyzxtmjhhjmw|~~wohb\_aelv„ˆ…„Ž‰ˆˆˆ‡‡‰ŠŠ‰‡†ˆ‰ˆ††…ƒƒƒ‡‰‰ŠŠˆˆ‰‡ˆˆ‡‡‡‡ˆ†‡‡††‡‡ˆ‰‰‰‰‰ˆ‰Š‹ŒŒŒŠŠˆ…„‚‚„††‡‡‡‡‡ˆ‰ˆ„|wwz~{{|€„…†…„„ƒƒƒ„ƒƒƒƒƒƒ~sf_^]_houxxzyvwwz|}{}„…ƒ†‡‡‡ˆ‰Š‡„ƒ„‡‡ŠŒ‘‘’’’’’’’‹{nmnmqu|‚Š‘’•••”•••–––”‰~vtuy†ŠŽŽŽˆ}{{{{{{y{{{{{{zzzzzz||{{}}}}}}zz}}}}}}}}~~~~~~~~~~~~}}}}}}}}}~~~~~}}||||}}{zy{zywvxywslggghmu{~zqjfcadcbhsƒˆ†„Ž’‰ˆˆ†††‡Š‰‡ˆ‡‡ˆ‰ŒŠ‰††…†‡‡ˆˆ‡‡‡†ˆ‡‡‡‡‡ˆ‡‡‡†††‡ˆ‰ŠŠ‰‰ˆ‰‰‰‹‹ŒŠ‰…„„„…††ˆ‡‡‡‡ˆ‰‡‚yvvz€}yyy}„…‡†„„ƒƒƒƒƒƒ|uha`_^fnruy|zyxx{{|{~„…„…†ˆ‡‰ŠŽŽŒŠ‰†ƒ‚„‡‰‹‘‘““““““’ˆynmnnty„Œ“••””””•–••’Žˆ~uuvyˆŒŽŽŽŽ‹…}}{{{{{yz{{{{{{zzzzzz{{{{||||{{zz}}}}}}}}~~~~~~~~~~}}}}}}}}}}}~~~~~}}~~}}||{zy{zywvwxvqkgeghktz€}umfddfdbbfq€‡…€ƒŽ”Ž‰ˆ‰‡†††‰‰‡ˆˆˆŠ‹‹‰‰††‡‡‡‡‡‡†…‡††††††††††††‡ˆ‰ŠŠŠ‰ˆ‰ŠŠ‹ŠŒŒŠ‰†…………†‡ˆ‡‡‡‡ˆ‰‡yuw|{xxy|„…†…„„ƒƒƒƒƒƒ}ypic``^cehns|||xwy{{z|~………†ˆ‡‰ŠŽŽŒŠ‡„‚‚„‡Š‘’’““““““‘†vmooov|†‘“••””””•–••’Ž‡|tuvz‰‹ŽŽŽˆ€u}{{{{{y{{{{{{{zzzzzzzz{{{{||{{||}}}}}}}}~~~~~~~~~~||}}}}}}}}}~~~~~}}||}}||z{{zzyvuvvuqjfcfgipxyqiddhjfb`cl}‡†€‚•‰ˆ‰‡††„‡‰‰ˆˆŠ‹ŽŒ‹‰‡‡‡‡‡‡‡†…‡†††††……†††††‡ŠŠ‹‹‹Š‰Š‹‹‹‹‹‹Š‰ˆ‡……††‡ˆ‡‡‡‡ˆ‰‡zvw}zwx{}ƒ……„„„ƒƒƒƒƒƒ}|vpjfeb`__`aiq{{uposvyz~„……†ˆ‡‰ŠŽŽŒŠ‡ƒ‚„†ˆ‹‘’’““““““ƒsmopqw}ƒ‡Ž’••””””•–••‘†{tuuzƒŠŒŒŽ‹…yo}{{{{{zy{{{{{{zzzzzzzz{{{{{{||}}}}}}}}}}~~~~~~~~~~||}}}}}}}}}~~~~~}}||}}{{z{{yzyvuvvtpiecdfgku}}vjecgmnib^`i}‰‰‚”‰ˆŠˆ††ƒ†‰‹ŠŠ‹‘Œ‹‹Š‡‡‡††‡‡†…‡†††††……†††††‡ŠŠŒŒŠŠŠŒŒ‹Š‹‹Š‰‰ˆ‡‡†‡‡ˆ‡‡‡‡ˆ‰‡{vw~zwy}‚ƒ……„„„ƒƒƒƒƒƒ|xmdbceba````fq{}ypiehmsx|ƒ†…†ˆ‡‰ŠŽŽŒŠ…ƒƒ…†‹‹‘‘‘““““““Ž‚pnpprx~ƒ‰ŽŽ‘••””””•–••Œ„zsutzƒŠŒŽŒŠ‚um{{yyzzzzzzyyzzyyyyzzzz{{{{{{{{{{}}}}}}}}}}}}}}}}}}||}}}}}}||~€€}}}}}}||{zzzyyyyvvttrngbbcgehrzzricflqpof_^f|ƒ€‹”ˆˆ‰‰…„„…‡‰‹‹‹Ž’‘‹Œ‰ˆ†††††………………„„……†††††‡‰ŠŽŒ‹ŒŒŒ‹‹‹‰Š‰Šˆ‡‡†‡‡ˆˆˆ‡‡ˆ‡†|y{~|z{~‚ƒ„…ƒƒ„ƒ„ƒƒƒ‚‚€{mWKQ]eca`a`_enwysngb^^grw€„„†ˆ†‰‹ŽŽŒ‹‡…‚ƒ„†ŠŒŽ‘‘““““’‘Œ}pnpptx~…‹Ž’••””””••”“‹‚yvvx~…‰ŒŽ‹‡{pk{{yyzzzzyyzzzyyyyyzzzz{{{{{{{{{{}}}}}}}}}}}}}}}}}}||||||}}||~}}}}}}||{zzzyyxxvvtsqlfabdighowxqiglrwvrkc^e}ƒ€Š‘ˆˆ‰‰…ƒ‚‚…ˆ‹‹Œ‘’’‘ŒŒŠ‡†††††………………„„………………†‡‰ŠŽŒŽŒ‹Œ‹‰Š‰Š‰‡‡ˆˆ‰ˆˆˆ‡‡ˆ‡†|{}~ƒ€|{{ƒƒ……„…„ƒ„ƒƒƒ‚‚xfMBL[eda`a``bhnqolf`\VY_jrw~‚‡ˆ†‰‹ŽŽŒŠ‡„‚ƒƒˆŠ‘‘““““’‰zqnpquz†ŒŽ‘“••””””••”“‰vvvy€†ŠŒŠ‚wnl{{{{zzyyyyyyzyyyyyyyyyzz{{{{{{{{}}}}}}}}}}}}}}}}}}||||||}}~~~~~}}~~}}||{zzzyyxxwwtspkebcfiihktwqjkpw{zupgag‘†ˆŽˆˆ‰‰‡†‚„†‰‰Œ‘““’Ž‹ˆ‡††††………………„„„„„„„„†‡‰ŠŽŽŽŽŽŽŒŠ‰‰‰Š‡‡ˆˆ‰ˆˆˆ‡‡ˆ‡†~|~€€}|}€ƒƒƒ„„ƒ‚ƒƒƒƒƒ‚‚~ueUR[bfdbaaddbdgllhe^ZUPU_aiu†ˆ‡‰‹ŽŽŒŒ‰†„‚ƒ„‰‹‘‘’““““’†xqrqtx|€‡‘“••””””••”’‡|vwwz€‡ŠŒŽ‡}smk{{{{zzxxzzyyyyyxyyxxxxyy{{{{{{{{}}}}}}}}}}}}}}}}}}||{{{{}}~~~~~}}}}||{zzzyywwxxtsojebcgijiiquskkrz~}ztkfm€‘”‡†ŽŒˆˆ‰‰‡†ƒ‚ƒ…ˆˆŠ‘“”“Œˆ†††††………………„„ƒƒ„„„„†‡ˆŠŽŽ‘ŒŠŠ‰‰Š‡‡‡ˆ‰ˆˆˆ‰‰ˆ‡†~€}|‚ƒƒƒƒ‚ƒƒƒ‚‚~ugdikjhebbbffdadjkgb\XTQZXX^m}„‰ˆ‰‹ŽŽŒŒˆ…ƒ‚ƒ„‰ŒŽ‘“”““““”‚tqtsvz‡Ž’“••””””••”’‡|uxxz€‡‹ŒŒ†zpmj{{zzyyyyyyzzxxwwxxyyxxzz{|||||{{}}}}}}||}}}}}}}}}}|||||||~}}}}}}~~~~~|}|{yxxyyxxxwurohcacilkgelsroqu{€~yplp‚“‰‚ƒŠŒˆ†ˆˆ‡†„‚ƒ…‡‡‹’”“‘ŽŽŒˆ††…††††††……„„ƒƒ„„ƒƒ„†ˆŠŽŽŽ‘Ž‹Šˆˆ‡ˆ†‡ˆˆ‰‰ˆˆˆ‡…~~‚‚‚ƒƒ‚‚€}‚‚‚}vopzytlhccejidacegfaYTPOVTPTfz„†ˆŠ‹ŽŽŒ‰†ƒƒƒ„†ŠŒ‘“’““’““Ž‚tsttx|~‚‰Ž’“””””••••”‘‡|wxx{‚‡‰ŒŠvmmmzzyyyyyyyyyyxxwwxxyyyyzz{|||||||}}}}}}||~~~~}}~~}}|||||||~}}}}~~~~~~~|}|{yxxyyyyxwvsoic`ejnnjgkppopt|€}vqt‚–Œƒ‚‰Œˆ‡ˆˆ‡†„‚ƒ„††‰‹’””’ŽŒŠ‡†………‡‡††……„„ƒƒ„„ƒƒ„†‡‰ŒŒŽŽ’‘‹‹‰ˆ†††ˆˆˆ‰‰ˆˆˆ‡„‚€€ƒ‚„ƒ|~‚ƒƒ‚€|zw{}€~zw{€yphddhlkeabbdb]TPKKMRNM]q}ƒ‡‰‹ŽŽ‹‡…‚‚ƒ„‡‹Ž‘‘“’““’““‹ussty}~‚Š‘’”””””••••”‘„{wxw|ƒˆ‹Œ‹‡|qmopzzyyxxyyyyyyxwyyyyyyyyzzz{||||}}}}}}}}~~~~~~}}~~}}|||||||~}}~~~~~~~~~|}|{yzyyyzzxwvtojebgmqqnikqqomqy‚ysu‚˜‘ƒ€‡‹‰ˆˆˆ‡†…ƒ‚ƒ……‡Š’”’‘Š†„………‡‡††……„„ƒƒ„„ƒƒ„††ˆ‰ŠŒŽŽ’’“‘ŒŠˆ……„††ˆ‰‰ˆˆˆ‡„ƒ‚‚„„ƒ{ttz~€}uqqtx|||}}~~€ƒ€xogcdhonicb``\UMIGHBLKLXgt†‰‹ŒŒ‰†ƒ‚ƒ…‡‹‘‘“’““’“’‰|tssty}‚Š‘’”””””••••”‘‹‚yvyx}…‹‹‰ƒypmpryyxxwwyyyyxxwwyyzzyyzzzzy{||||}}}}}}}}~~}}}}}}}}}}|||||||~}}~~~~~~|}|{yzz{{yyxwurokgeinssplkpqoknv}ƒ{uw‚™’„~…ŠŠˆˆˆ‡†…„‚ƒ……‡‰‹Ž‘”’“Œ‡„ƒ…††††‡‡……„„ƒƒ„„ƒƒ„††ˆ‡‰ŒŽŽ’““‘‘‹‰…„ƒƒ…ˆ‰‰ˆˆˆ‡ƒƒ„„„„}tonrw}}zrmkotxyyyy}ƒƒƒ~umgccgpokea^[VNGE@>EJHGS^l{…‡Š‹ŒŒ‹ˆ…‚‚ƒ…‡ŒŽ‘‘‘““““’“‡{srrty€ƒ‹‘’”””””••••”‘Œ‚wvyy~†Š‹†~uooqtyyyywwwwxxwyxxyyzzzzxyzzz{||||}}}}}}~~}}}}||||{{}}||||||}}~~}|}|{{{z{{||zxxwvsnjgfjpsuqnknoniis}‚‚|wxƒ˜“…}ƒˆ‰‰‰‰ˆ‡…„‚ƒ……„‡‰‹’‘‘Ž‡ƒ……††‡‡††……„ƒ‚‚ƒƒ„„„…‡ˆŠŒŽ‘’‘’’’Œ‰†…ƒ„‡ˆˆˆˆ‡†………„‡„|rlhhilsvvqmjjortsrty}ƒ}sjcbaeoplea\YTLA<78?FC@IVgw‚†‰Š‹‹Š‰„€‚ƒ„†‰Œ‘’’’’““’“Ž„wqrsuz~~ƒŠ‘“”””””””••“’Šwvyy†‹Š‚wpnqtuxxxxwwwwxxwyxxyyzzyyxyzz{{{{||||}}}}~~}}}}||||{{}}||||||}~~~}|}}|{{{{||||zxxwurnjffipturnjkkkgep|‚{ww‚–’…}€†ˆ‰‰‰‰ˆ…„ƒ„…‡„†ˆŠŽŽ‰syƒ………††††††„ƒ‚‚ƒƒ„„„…†ˆ‰Š‘‘‘“”’ŽŒˆ†„}‚…‡‡ˆ‡†††…†…rihgffgkstqnkhjlonnpv|€}tjcbadlpngb\ZULB635=GECIUcnz‚ˆ‰‹‹Š‡‚€ƒ…ˆŠŒŽ‘’’’’““””ursvw|~~ƒŠ‘“”””””””••“‰xwyz€†ŒŒˆtmnqvwxxwwwwwwxxwyxxyyyyyyxyzzzzzz||||}}}}~~}}}}||||{{}}||||||}}~~}|||{{{{{||||zxxwurnjeehotusojjjjdcmz€€zvv€•’‡}|‚†ˆ‰‰‰ˆ†…„†ˆ‡…††ˆ‹ŽŽŽ‹„tfp~‚„……††……††„ƒ‚‚ƒƒƒƒƒ„†‡ˆ‰Š‹Ž‘‘’”“ŽŠˆ…|w{€„…‡‡†††„…wlhhfefgiottqlhdehihjs|€ujfcabjqpib\ZUNG>9;DLLNTQSfr~†‰‹‹‰†‚€ƒ†‰‹‘’’’’“””’‹~ustvx}~~‚Œ‘“”””””””••’Žˆ}wxyz‡‹‹„|qmnsx{wwwwwwwwxxwyxxyyxxxxxyzzzzzz||}}}}}}~~}}}}||||{{}}||||||||~~}|{|z{{{{||||zxxwvsnjedhotuuplkjjddly€€zvuŒ”’ˆ}}‚…ˆ‰‰‰ˆ‡……ˆŠˆ‡†…‡ŠŒ‰€l^f{ƒ„„…………††„ƒ‚‚ƒƒƒƒ‚„…†ˆ‰ŠŠŽ‘“•“ŒŠ‡|uu|ƒ„‡‡†††‚ƒslkkiggfhnuvsmhbbcdbgq|€‚‚ukhdaajssle^YUQLKGILPRX]L>\i{…‰Š‰‡…‚ƒ†ŠŒŽ‘’’’’”””‘‰}sssux~~~‚‘“”””””””••’Žˆ}vxy|‡‹‹ƒyqmoty~wwvvwwwwwwwwwwyyxxyyyzzzzyzz{{||}}}}}}}}}}}{{{{{||}}{{}}}}}}~|{zzyy||||{{{{zyxvsokdcfnswwtokkjfcjw€ytu€Œ’“Š€}„ˆˆˆ‰‰‡†‡ŒŒŠ†……†‰‹Š‹…xgY\u‚„„ƒ„……†…ƒ‚‚‚‚‚ƒ„……‡ˆˆŠŽ‘‘‘““‘ŽŒˆsrw~ƒ‡†‡†‡„‚zqnookiiiimtvumhcb`_`er|‚„„wlhebblyyqjc^[WTWYWVXSX_H.Ebv…‹Šˆ…€€ƒ„†Š’’‘’”””‘ˆzppruz|}~ƒ‘’“””””“•••’…}wxy|‚ˆŠˆ~uonmt|vvvvwwwwwwwwwwxxxxyyzzzzzyzy{{||}}}}}}}}||}|||||{{||||}}}}~€€€€~zxxxxy|{{|{{{{{zyvsolebclquwvplljgdhw€‚~ytvŒ’“Ž…~~ƒ‡ˆˆ‰‰‡‡ˆŽŽ‡…ƒ„…†…ƒ~rdVSl|„„ƒ„……†„ƒ‚€ƒƒ„„…†‡ŠŽ‘‘‘‘‘‘ˆ‚ursv~„„††‡…xsssromkkjmswwsnhdaa`gt~ƒ„„ƒ{pjfcdpzysleb^[X[^]\WX^^H+4Ri|‡ˆˆ€ƒ…‡‹Ž’’‘’”””’†yqqruy{}~„Œ‘’“””””“•••‘„zvyz|ƒˆŠ†{soorx}€uuvvwwwwwwwwwwwwxxyyzzzzzxyy{{||}}}}}}|||||}}}||{{{{}}}}}}€€~zwvuux{{{|{{{{{{zvsolfbbhnrwvsokjgcfu~ywsvŒ’“Ž‡}†‡‡‰‰‰‰Š‹ˆ‚~}~{vkaTO^z€„„ƒ„………ƒƒ€‚‚‚ƒƒƒ…†‡‰Ž‘‘‘ŽŠƒxqssy€ƒ…†„wustuutomjlswwvrmheedjv‚ƒƒƒ}rlfbfszxsked_]\]bdc\]_Z?+0CXl~…†‚€€ƒ…ˆŒŽ’’’“••”‘„xqqruz|}~„Œ‘’“””””“•••Œƒyvyz~ƒˆŠƒyqpqt{€uuvvwwwwwwwwwwwwxxyyzzzzzxyy{{||}}}}}}{{{{{}}}{{zz{{}}}}}~~~}yvuttxz|{|{{{{{zyvsokfdbhnrvwuqkjgccpz{vsquŒ’“Ž‡}€…‡‡‰‰‰‰‹‘’Œˆƒ~zvxwrg_TMUs„„ƒ„……„ƒƒ€€€‚ƒƒƒ…†‡‰Ž‘ŽŽŽŽŠ„xpprw|€…†ƒ~xttsw{ysnjkrvvutpmiiinu~‚„„}skeaeqzwskfea^^^dihca^T918EPby„†…ƒƒƒ„…Š’’“”••“‘‚wpoqt{}}~…‘’“””””“•••‹‚zwyy}ƒ‰ˆwpqrw}‚ƒttvvwwwwvvxxwwvvwwyyzzzzzyyyz{||||{|}{||||}}}}||{{||}}}}}~~€~~~}zwsqsvz|}||||{z{yxuqlgbcinsuvtrnkidajstpprv‚’’‰|„‡ˆˆˆˆŠ’““‘Šƒyqlprmf`TKNf|ƒ„ƒ„„…ƒƒƒ‚‚‚ƒ„†‡ˆ‹ŒŒ‹ŠŠˆ‰Š‰‰„vnnruy|ƒ|xwtt|€~upmnquvutspommpu~‚ƒƒƒ{rkd_cpwvsnhcbaabgjljd\N97DLUk€‡ˆ‡‹‰‰‡‡ŒŽŽ’’’“••”€sooouz~}‡‘‘’“””””“”•“‰zwxy~…‹†{upqrx†ƒuuvvvvwwwwxxwwwwxxyyzzzzzzzzz{{{||{|}{}|}}}|||}}||||}}}}}~~€|zwsqruy|}}}}}{zzyyvsnfbckptvwusolje_enmlmsvƒ’’Š‚}…‡ˆˆˆ‰Š“””“‹ƒzlehkje`ULGWw€………„…„„ƒ‚€€€€‚‚ƒƒ…‡ˆŠ‹Š‰‡‡……„ƒ…†‡‚xonruyz{~€~zyxvy€„xqnprvwwwurqonrw~‚„ƒƒ{rme`dqwxupieeccdgijf`XI::HZoƒŒ‰ŠŠŠ‰ŠŽŽ’’’“”””~ponov{~}€ˆ’‘’“””””””•“‰€zxxy†Šxsqrs{„ˆ…vvvvvvwwxxxxwwxxyyyyzzzzzzzz{|{{||}~}|}|}}|{||}}||||}}}}}~~€€€€|zwsqqsw{|~~~~|{zyxvrmebempvxxvuplkf_bjiimrwƒ’’Š„…‡ˆˆ‡ˆŠ••–“†|mfgjid^VNFNo}………„………„‚‚‚ƒ„‡ˆˆˆ††……ƒ„„ƒ„…ƒ‚zspruyzyz{zwyxz‚…‚zqoruxxwwwurpptyƒƒƒ}smfagsz{uqjggfededdb^UE9=TqƒŽŒ‰†„„„ˆŒŽ’’’“““”~qpoov|}€‰’‘’“””””””•“ˆyyyz€††~urstv~‡ˆƒvvvvuuwwxxxxxxyyyyyyzzzzzz{{{}||||~~}{{{}}|{{{||||||}}}}}~~€‚€€|{wsqpsv{|~~|{zyvtpkecgoqwxxxwpmjeacghhnsyƒ’’‹……‡ˆˆ‡ˆŒ––˜”ˆ}rigmje[VPHHbzƒ……„…………ƒ‚€€‚ƒ„†‡‡…†††„ƒ„„ƒƒƒ‚‚|vqruyzzzzyxyx|ƒ„‚zrpswzxwwvvtrru{€„ƒƒ}tldbiu|ztqjjjfcb``_`]RD=B_”“‡~{|~ƒ‰’’’“““”~uqoov|€~€‰’‘’“””””””•“ˆ~zyz{€…„|rqruyŠŠ„uuvvuuuvwwxxxxxxyyyyyyyz{{||{|||}}~~|||{|||{{{{{{{|||||||}€€€~}zvrqquy|~€}|xwuuroieeiptvxxxvspniefikjosz„’‘Ž‰€„‡ˆˆˆ‰Œ“—˜—“‘Štkhiic[SNHFSw……………„…ƒƒ‚€€€€€€ƒ„†‡†…‡†……„„……„‚‚ƒ€yuruxzyz{yyzx|…†‚zspsy|zxwwwusrv|€‚ƒ„ƒ|tkecmx|ytqmllgbbba^`_SIEMg…’——•‰€yvxyy}†Œ‘‘’“““”{tpopw}~Š’’““””””•••“…~{zy{ƒwrqtvzƒŒ‰ƒuuvvuuuvwwxxxxxxxxyyyyyz{{{{{|||~}}}|||{||||{{{{{{{{|||||~€€€~{wrqptx|}~{wsrppnnhegkpvwwyywurolllnqnnqz„‘‘‘Š‚„‡ˆˆˆŠ”˜™˜“’…ypliec[ROHBHl}„…………„…ƒƒ‚‚€‚ƒ………††‡………„„……„‚„ƒ}wsuxyyz{yyzy{…‡ƒzsqt{}{zwwywuux}€ƒƒ…‚{skeenwzwrpnmmhbbcc`a`VOLSmŠ—œœ”ƒ|wtvurt€Œ‘‘’““””Œysqoqx~|€‹“’“”””””•••’…}zzy{€|sqrtv|„Œˆ‚uuvvuuuvwwxxxxxxwwyyyyyz{{{{{|||}|||||}|{|{{{{{{{{{{||||~€€€~{wrqprx{||xuqmkiijifehmrvwvxxyxuqoqrtvpmqy‚‘‘“‘‹ƒ„‡ˆ‡‰Œ•˜š™”“‰unhdb]VPJBD\z‚†………„…ƒƒ‚ƒ‚„…„…‡†‡‡‡…„„……„„„‚€|vtwyy{{{{{z|ƒ…ƒysqu}}|{yyyxuvz}‚ƒ„yqkefnwytollmmidbcfbb`YROWp‹˜žžŽ€zyvuromxˆ‘‘’““””ˆxqppry~|€‹“’“”””””••”‘‹„|zzy{€ytqsvz€‰‹†€uuvvuuuvwwxxxxxxwwyyyyyzzzzz{|||}|||||}|{{{z{{{{{{||||||~€€€~|wrqpqw{{zrpjfecdggddinuvywxwywusqtwwxsprwŒ’•“Œ„„‡ˆˆŠ‘•™šš””‹‚wniea^XQMDAOt†………„…ƒƒ‚ƒ‚‚‚‚‚‚ƒ„…†‡ˆˆ‡††‡……‡…„„ƒ‚~wtvyyz{||{y}€‚ƒxsqv~~|zzxxwuy~‚‚ƒ„ypkefoxzrmkjlljfbcecca[SRYq‹š¡Ÿ‹z{xsqmioƒ‘‘’““””†xpoorz~}‹“’“”””””••”‘Œƒ|zzy|}vtttx|ƒ‹‹…uuuuuuuvwwwxxxwwxxxxxxyyzzyyz{{z{{{{{{|z{{z{||{{{{||{}}}}~~~~|xsnnov{zxqjea`_]_acgmruwxxxxzxvspty{{sosx€‹’””’Ž„€‚ˆ‰Š‰”™š™–”Œ„}slhd`ZSLF@Fg|„…„„„ƒƒƒƒ‚‚ƒƒ‚ƒ‚„„…††††…††††‡†‡†„„ƒƒƒ‚{uuxyzz}|{|€‚~wqpv€~{ywvy~ƒƒƒ€vokegs|zsnihjjlgcdedab^WV]sŠš ŸŠ€€yuqlfev‹‘‘‘‘’““”“‡xoopry}}}‚Œ‘“““”””””••“Šƒ{yzx{~{trsuxƒŒ‰‚uuuuuuuvwwwxxxwwwwxxxxyyzzyyyz{z{{{{{{|z{{z|||{{{{||{}~~}~~|ztnnouzywpha__^Z\`ciouuxxxxy{ywtqtz}|sosw“•”’†€‚‡Š‹‹Ž”˜š™—“‘‡‚ytpjd]UMHA@Uv…„„ƒƒƒƒƒƒƒ„ƒ‚„‚„…‡††††…†††‡‡‡‡ˆ†…ƒ„ƒ‚~xvxy{{~|{}}}|vpqw‚ƒ‚‚}zxwy~ƒƒƒ~tojdgs{ytmhgijlgdfhebc`[[at‰˜Ÿž„ƒ‚{toie`jƒŽ‘‘‘‘’“””’‡wopqsy}}}ƒ‘””””””””••“‰zyzxz|wrrsvz‚‡Œ‡~ttuuuuuvwwwxxxwwwwxxxxyyzzyyyzzy{{{{{{|z{{z|||{{{{||{}~~}~~€€}{upnosxxskeabca^]_dkrvwxxxxy{ywtqsy|{rnrv€’”””’‹„ƒ‡‰ŒŒ‘–—™˜–’‘Ž‰†zwpg^XPG@=Ik{ƒ„‚‚ƒƒƒƒƒƒ„„„„ƒƒ…†††††‡‡†‡‰ˆ‰‰‰ˆ†„„‚{xy{{{~|{}€€}zyspqxƒƒ„„ƒ}yxy~ƒƒƒ~toicgs{yulhgilkhghhfdb_^^cs‡–›‰„yqiec^cxŠ‘‘‘’“””…wqrrt{}}„Ž‘””””””””••“Š€{zzxxyvqsux~…ŠŠ†ssuuuuuvwwwxxxwwvvxxxxyyzzyyxyzy{{{{{{|z{{z|||{{{{||{}~~}~~€€|wqnoqvvqhdcegea`agntwyxxxxy{yxuqtx|{qnrv}‹‘’”–“†…†‰ŒŒ‘–—™—•’ŒŠˆƒzria[SG@CTs}‚‚ƒƒƒ‚ƒ„††…ƒƒ„†ˆˆŠˆˆˆˆ‹Œ‹Šˆ‡†…„‚‚‚}{{|||}|~€€|xuqoqz„„ƒƒ‚‚}{}ƒ„„|smgciu}|wokhjlklmmjfec`_bdq€Ž—•‰…wnd^]\[e|Ž’‘‘’“””€ursst|~~…’””””””“•–•“…}z}|xuussux{‰‰‡…‚vvvvuuuvwwxxxxwwxxxxyyyyzzzzzz{|{{{{{{{{{{{{{{{{}}||||}}~€€~zspnoqsogekqurkggntwyyxxyyyyyxuttw{ztqqtx„‹’”•“‘Š‡ˆ‹‘’”•–•“‘ŽŠ‰ˆ…ƒ~vka[XQHAAKdz€„ƒƒƒƒƒƒ…‡††„„…ˆ‰‰Š‰‰‰‹ŽŒ‹Šˆ‡††…‚‚ƒ{||||~|~€€}zuqqr{„„„ƒ‚‚‚~€‚ƒ…ƒ{rmedkw}|wqlijklnoolhfebacemx…‘ˆ„‚zpf^]\[_s‰‘‘’“””Ž€tqqpt|~~‡‘””””””“•”•“…}{~}xtsrsux~„‹Šˆ…‚vvvvuuuvwwxxwwwwxxxxyyyyzzzz{{{|{{{{{{{{{{{{{{{{}}||||}}€€€€|tpmlornhhmtyxqjhntwyyxxyyyy{yvttwzyspqrwˆ’•–’‰‹‘’“”•”’Šˆ‡‡…€xne]XUMGCFVt€ƒƒƒƒƒƒƒ…†ˆ‡†„‡‰Š‰‹‰‰‹ŽŒŠ‰‡†………‚‚ƒ|||||~}|~‚|uqps~ƒ……„„‚‚‚‚„…ƒzrlcdlx}|xsokjknoooljjgdcdflr~‰Š…‚}tia__]\jƒ‘‘’“””spont|~~†‘””””””“•“•’Ž…~|~~xsqqrtx€‡ŠŠ‡…ttuuuuuvvvwwwwwwxxxxyyyyzzzz||||{{{{{{{{{{{{{{{{||||||||}~€€~xrnlnpngimv{{smlpuxyyxxyyzzzyvttvzyspqqw…‰‘——•Ž‹‘’’”•”‘Œ‰ˆ†…†ƒ|sk_YVQLFEPg}‚ƒƒƒƒƒ„†ˆˆ‡…‡‰ŠŠ‹Š‹ŒŽŒŠ‰‡………†ƒ‚ƒ|||||~||~}zuqps~„‡†…„ƒ‚‚‚ƒƒ‚„†‚yplbcmy}|xtpmmkmnnnnlljeddfloy…Š‡ƒ{ska^`^]f}Ž‘‘’“””€spont|~~„Ž‘””””””“•””’ƒ}|~~wrorrtzˆŠŠ‡„‚stuutuuvuuvwwwxxyyyyxxyyyyzz{{{{zz{{{{||{{{{{{{{||||||}}|€€€}smjkmjggqy{yvsptyzyxyyyyzzzzxwuuxxuqqrw€…‡‘——“Ž‘’’”“‘ŽŒ‰‡†…„ƒyqdYWXQLGL^x‚„‚‚‚ƒƒ‚„…ˆŠ‰ˆˆ‰‰‹‹‹‹Š††…„†ƒƒƒ€}||||}|}‚‚{tqrs}…†‡‡†„‚ƒƒ„„…†††‚wplfgq{~|xwtonllmommoomighkmmt~„„„€~xsi__aa`buŒ’‘““”“‹|roqqu}€}…“••••••“•••’‹ƒ}{~}xqossu|‰Šˆ‡…~stuuuvuuuuvwxxxxyyyyxxyyyyyz{{{{zz{{{{{z{{{{{{{{||||}}}}|€€€~wojkkgefqy}zwvxzzyxyyyyzzzyyxuuxxtppqv~„†’——“‘’“”“‘ŽŒ‰‡†…„ƒ‚}sfXWXSPOQ[q‚ƒƒ‚‚ƒƒ…ŠŒŒŠ‰ŠŠ‹ŒŒŽ‘‘‘‘‹ˆ………„…„ƒƒ~}}}}~}|„„€{trrt|ƒ†‡‡†…„ƒƒ……†‡††€uolghs|}zxxvqpnlmqonppokijmoou}‚ƒƒ}xoe_`cb`^nˆ’‘’””“Œ}rooov~€}…“••••••“••”’Šƒ}}€|wqortu|ƒ‰‰ˆ‡†~stuuvvvvvvwxyyxxyyyyxxyyyyyyzzzzzz{{zz{{{{{{{{{{||||~~}}~~€€€€€~yokkhdbeow~}{yyz{{zyyyyyzzzyzyvvxytpnqv}‚…”——“ŒŽ‘“”“‘‹ˆ††…„ƒƒ|o\UUVUSRZhzƒƒ‚€ƒ†ŒŽ‹Š‹ŒŽŽ’“‘‹‡„…„„„„„„ƒ~~}|{„„€{urqt|‚…†‡‡‡„ƒƒ„„†‡††}rmifis}|yxxvsponnrrqqppomorsswy}~zzwoe__bda_lƒ‘’“””“‹|rooow~}…‘”••••••“•””‘Šƒ}}~zuqoqux…ˆˆ‡‡†~stuuvvwwwwwyyyxxyyyyxxyyyyyyzzzzzz{{yyz{{{{{{{{{||||}}~~~}~~€€€~|pjjeb_dov{{{{{{||{yyy{{zzz{zzwwwztpnqx~‚†•–”“Œ‘’”“‘ŽŒŠ‡††…„„‚„ƒyfWRVXVS[csƒ‚€€‚†ŒŽŽŒ‹Š‹ŒŽ‘”“‘ŽŠ‡ƒ„ƒ„„ƒ……„~~€€{zzƒƒ€{urpu~ƒ„…††‡†ƒƒ„„†‡††{pmifjt}}zxxvuqpnntusqprqpruvvvvy}{wyvod__`deal‘’””“‰{ropqx€}}…‘”••••••“••“Šƒ}}}ytqoquz‡ˆˆ‡‡†~rsttuuwwwwvxyywwxxxxwwxxxyyyzzzzzzzzyzzz||{{{{{{{{{}~~~~~~~~~~~€{vokfb^`jsz}~}}|{{{yyy{{zz{{{ywvvwuqnr{~‚‡••”“ŽŽ‘’”“’Œ‰ˆ…„…„„ƒ…†vbUVZZZY_l|‚ƒ†ŒŽŽŒ‹Š‹Œ’”“Œˆ†„„„ƒ„„………‚~~}~|zz„„€|vqpvƒ†††‡ˆ‡„‚‚………ƒynmjfkv|}{ywwwrpoostrpqrqpquxxtqtwvttpkc^^begejyŠ‘”’‰zrpppx~|†“••••••”••“‘‹‚~~~vsoortzƒˆˆˆˆˆ…‚pqttuuvwwwvxxxwwxxxxwwxxxyyyzzzzzzzz{zzz{{{{{{{{||{}~~~~~~~~€€}zsmhb]^fqz~~~}|{{{yyy{{zz{{{yxvvwvurv}€ƒˆ•–””ŽŒŒŽ‘’””’Œ‰ˆ…„„„„„†‡…q`WW]_\^hwƒ€€ƒ†ŒŒŒŽ‘”’Ž‰‡†††……†…………ƒ~~}|zz„„€}vqpx€„†‡‡ˆˆˆ…‚‚………wlligny~}zyxxxutppsuroqsqpptvvrnqrrqpnke^]`dhhiu…‹Ž“‘‡xrqppx~}†“•••••••••“‰~~}vrnort|…ˆˆˆˆ‰†‚oqtttuvwwwvxwwwwxxxxwwxxwyyyzzzzzzzz{zzz{{{{{{{{}}{}~~~~~~~~€€€€€}xqje`^dpz~~~}|zzz{yy{{zz{{{yywwxxxx|‚†ŠŽ‘”•”‘ŽŒŒŽ‘’””’Ž‹‰ˆ‡†„„„„†‡†…{o`X[^_`fs}€„†ŒŽ‘’Œˆˆ‡ˆˆ††ˆ‡………ƒ€€}{z„„~wqpv„…†‡ˆˆˆ…ƒ‚‚ƒƒƒ}rlljhpy|yxyyywvspsvtnnnmlkoqqolmmnnnolh`\]cghhp†‹ŒŽŒ„xqpppx~~‡‘••••••••••“‡~{tpnosv†ˆˆˆˆ‰†‚prtttuvwwwvxwwwwxxxxwwxxwxxyzzzzzzzzzzzzzz{{{{{{}}{}~~~~~~~~€€€~~zrkfb_eoz~~~}|zzy{{{{{zz{{{yyxwyzz{€‚ƒ†ˆ‹•••‘Ž‹‹Ž‘‘““‘‹ˆ‡‡†„ƒƒƒ…††…yj][]_afp{€‚„‡ŒŽ‘‘‰†ˆ‰ŠŠ‰ˆˆ‡………ƒ‚‚€}{{„„~vporz‚……‡ˆˆˆ†ƒ‚‚‚‚‚yojkhiow}|ywyyzzxuruwulihgefjlllkjjklnnmja]]cghgly†‡ŒŠ‚wqoppx~~‡‘••••••••••“Ž…~~zsonosw†ˆˆˆˆ‰†‚oqrssvwvwwxyxxxxxxxxxxyyxyyzzzzzyyyyyzzyzzyy{{||}}|||}~~~~~~€€€€€€}wniebgoz}~}}{yzz{{{{|{{{{{zzxxw{{}„…‡†‡“–”‘Œ‹Š‰‘‘Œ‰‡………ƒƒ‚„…†……ƒ€th\X]bjs{€ƒ…†‹’‘‘‘Ž‹ˆ†ˆ‰‹ŒŒ‰ˆ††…ƒƒ‚~{yz~ƒ‚{smlnt}€……‡‡‡‡„ƒ€€~tmjljilptxyzz||||{wxxumfa_^_afihffgjmpmkgc__ahijmt{~‚…„~qppoqx~}~‰’••••••••••“Œƒ~~~yroopt{‚‡‡ˆ‰‰‰†‚opqrsuvvxxyyyyyyxxxxxxxxxxyyzzzzyyyyyzzyzzyy{{||}}|||}~~~~~~€€€€€€€€~ytojhks{~~}|{z{{{{{{||{{{{{zxxw|}ƒ„…ƒ„‰”“‘‹Š‰ˆˆ‹‹ŒŒˆ†…„„„‚‚‚„…†…†…ƒ~tf\Z]kx|}€‚„†‡ŠŽŒ‰‰‡ˆŠŒŽ‹‰‡†…ƒƒ‚~{yz~‚ƒ€wnhfgjsz„†‡‡‡…„‚€yokjljgiklquwz}}€~zywqja[XXX[aeffghilollhd`_agiilmqw|zonnnpy}~‰’••••••••••“Œƒ~~|wrppru|ƒ‡‡ˆ‰Š‰†‚noprruwvxxyyyyyyxxxxxxxxxxyyzzzzyy{{yzzyyyyy{{||}}|||}~~~~~~~~~~€€€€€€zytonpw}~}|{z{{{{{{{{{{{{{zxxy|‚‚€…’“‘Œˆ†…„†‡ˆˆˆ‡†ƒ‚‚‚‚‚„„……††‡…ƒ}tg^^jy}}€‚ƒ…†‡ŠŒŽŠŠ‡‰ˆ‰‹Š‡……ƒƒƒ‚~{yz~‚‚}rjdaceju~„†††‡……‚€vljkjiehkkkmqtxz~€}ywsmf]VUUTV\dgiklnopmmiea`cfhiiginsxxtmllmoy€~Š’••••••••••“‹‚~}vqpqsv}„†‡ˆ‰Š‰†‚nopqrtvvwwyyyyyyxxxxxxwwwwxxzzzz{{{{z{{zyyyy{{|||||||}~~}}}}~~~~€€~~€€€€||wusuz~}~}{|{{{{{{{{{{{{{{zxwz|€ƒ€||‚Š“‘Œˆ…ƒ‚ƒ……‡…„‚‚‚ƒ‚„…†…………„„|rhemx}~ƒƒ†‡ˆ‰ŒŽŒŒ‹‹Š‡ˆŠ‹ŒŠ‡„ƒƒƒ„‚~{yz~ƒznfbabbfp|‚…††‡……‚€ukikihfhmlijjlruz~{wtpjbZTQQTVZdknpqssromieb`cfghhfehmrrnkkklnx~€Š’••••••••••’Š‚~|tpqrsv}…†‡ˆ‰Š‰†‚mmopqttuvwwyyyyyyywwxxxxwwxxyyzzyyyy{{{{zz{{||{{||}}}}}}}}||}}~~~~~~€~|{z{|}|}|||{z||}}|{{|||||{{xz|€„„}{z†Ž’‘Œˆ…„‚‚ƒƒ…†ƒ‚‚‚‚‚„„†…………………ƒ€zvwz|}„„‡†ˆ‰ŽŒŒ‹Š‰Š‹‹ŒŽŽ‰‡…„ƒ‚‚‚€~zyy~uicbbbablw†††‡††„}sighiieillgfdcgkrxxsnjd^WROOSX]isxxxxwwrojdcacddefebbdhjiiiiknw~~€‹“••••••••••‘Š{sppsux€„†‡‰‰ŠŠ‡ƒ€llopqstuvwwyyyyyyywwxxxxxxxxwwxxyyzz{{{{{{{{||{{||}}}}}}}}||}}}}~~~~~~€~~~~~|~|||{z{{||||||||||{{{{|…„ƒ€}yy~ƒ‹‘‘Œ‰…„„„„„……„ƒ‚ƒƒ‚„„†……†††††„ƒ€€}~‚„…‡ˆŠ‹ŽŽŒŒ‹ŠŠ‹‹Š‹Ž‰…„…„‚‚‚€}yz~|qgedecbahr„†‡‡‡‡…‚|sighhhfjiihecaadhmnljf`[XSQRU\hu|€~}zxsqnihfeefhigedddeffffhkt{|Œ”••••••••••‘‰€~~ztrrsuz‚„†‡‰‰ŠŠ‡‚llopprstvwwyyyyyyywwxxyyyyxxwwxxyy{{{{{{{{{{||||||}}}}}}}}}}}}}}~~~~~~~~€€€€~}||{zzz{{|}}|||||{z{||}„…ƒ‚|yz~‰‘Ž‰…ƒƒƒ„„„…„„‚‚ƒƒ„„†………†††††„„…‚‚€}€ƒ„…‡ŠŒŽŽŒŠŠŠŠŠŠŒŽ‡ƒƒ„†„‚‚‚€|{~{piihhdb`dn}ƒ…†……††ƒ{skiihhfhiiifddbcbddedb`\[WWX\evƒ„‚‚|{vrponjhjkmnkigfcaccccdgqx{y~‹”••••••••••‡~||xsuusu{‚†‡‡‰Š‹‹†~oooporstvwwyyyyyyywwxxzzyyxxxxyy{{{{{{{{{{{{||}}||}}}}}}}}~~}}}}~~~~}}}}~~€€€€}~||{zzz{{||||||||{{{}}}‚……ƒ}{{}~‡ŽŠ…„„„„…„………„„„„„…†„……††‡‡ˆ†„‚ƒ~}‚ƒ…†ˆ‰‹ŽŽŒŠŠŠŠŠŠ‹ŽŒ…„…ƒ‚‚‚|~{qmlljgc`akz‚„„„ƒ…‡ƒ{qjkjgggijjjgffdfaababa`]][\]coŠ‡…„„€}~ywqsqnklorsromjfbdcccbgnuyx~Š”•••••••••”†|||xqttsu|ƒ‡ˆ‡‰ŠŒ‹…~mmnoprsuvwxyyyyyyyxxxy{{yyyyzz{{{{zz{{zzzzzz||||||||||||}}}}}}}}~~}}{{}{}}~~}|}}|{zz{{{{{{||||}}|||}{|…†ƒ}|||}‚‹Ž‹‡„„„…„……‡…………„„††…††††‰‰‡…„‚{}~‚ƒ„†ˆŠ‹Ž‘ŽŽŒŠ‰‰‰‹‹‹ŒŽŠ„~€„…ƒƒƒƒƒ‚~yrqoomkf`adr~~~}‚†ƒyojljgfhjijihggecbababcbcdbaerŠŠ‡‚€~zxuuurnnsw|{xvqlfcdcddelpsu‹••––––••••”Ž„|z{wrsstw{‚†‡†ˆŠŒ‹†‚mmnoqrsuvwyyyyyyyyxxxyzzyyyyzz{{{{{{{{{{zz{{||||||||{{{{||||}}}}}}}}{{}{||}~}|}}|{zz||{{||||||}}}|}}|}€„……}}||ˆŽŽ‡„………„……‡†………„„†‡………†‡‰‰‡…„‚|}€€ƒƒ„‡ˆ‹Ž‘ŽŽŽŠ‰ŠŠ‹‹‹ŒŽ‰ƒ|ƒ…ƒƒƒƒ‚ƒƒ€~{vtturqokc`ajsrqsx}‚wnkmlhedfghhgfedbbbbaabdgkkns•—†€€|zyzzyrrv|‚‚{ulfeeeeeimpu}Š“–––••••••”Žƒ|zzvrssuy{‚††„„ˆŒ‹‡‚€mmnopqrsvwyyyyyyyyxxxyyyyyyyzz{{{{||||{{{{||||||||||{{{{||||}}}}||}}}}}{||}~~~}|}}|{zz||{{||||||||||}}}~ƒ…†ƒ~|z}ƒ‹Ž‰†……†…††‰‡‡††……††„„„†‡‰‰‡…„‚~|~€‚„ƒ†ˆŠŒŽŽŽŒ‹‹‹ŒŒŒŒˆ‚{}‚…ƒƒƒ‚‚ƒ}ytuxwtssnd`_fmjijou||vomnmjffeefgedbcbddecaafmqty–›œ‘†‚€€}|{~xvy~„†‡…‚|skjhfeefjmr|‰”••””••••”‚|{ytrssuy}„†…†‹‹‡oonoopqrvwyyyyyyyyxxxyyyyyyyzz{{{{||}}||||||||||||||{{{{||||}}}}||}}}}}{{{}~}}}|}}|{zz||{{||||||{{||}~€€ƒ…†„|z{€‡Œ‹ˆ…††‡‰ˆˆ‡†††……‡…„„„†‡‰‰‡…„‚|~€ƒ„‡ˆ‹Ž‘ŽŒ‹‹ŒŒŒŒŒˆ„||…ƒƒƒ‚}‚}vrvyusuurg`_difeehpwxsnmlkkhhfefebaabbddgebafowz~‡”œ™†„ƒ€€~}~ƒ„‚{{|‡ˆŠˆ‡xpmjgeefjkox…Ž“””””••••“Œ‚||ysrssuz~…‡…||…‹‹‡€nonopqrswxzzyyyyyyyyyzzzyyyyzz{{||||{z{{{{||||||{{{{{{{{{|||||||}}}}{{{{||||}}}}{{}}||{{||||||||{{||||~~€„†„ƒ€}z{€†‹‹‹‰†……‡‰Šˆ††††„„……………†‡‰‰ˆ†…}}€‚‚„…ˆ‰ŒŽŽŽŽ‘‘ŒŒŒŒ‹ŒŒŠ†~{€‚ƒƒƒƒ€€€{rptvsqtxule`aeedachnsplmnmjjhhdcdcccbbddfgfcdny}‚Œ–›—‘‰„…†ƒ}}ƒ‹Œ‡~†Š‹Š‰ƒ|tqnjgefhjjt‚Š•”“”••••”‹zzwssssvz€…ˆ†{z†ŠŒ†monopqrsvwxyyyyyyyyyzzzzyyyyzz{{}}}}{z{{{{||||||{{{{{{{{{|||||||||||{{{{{{|||}||{{|||||||||||||}||}}||~~~‚……ƒ€|}„ˆˆŠˆ†……†‰Šˆ‡………„„……………†‡‰‰ˆ†…}ƒƒ……ˆŠŒŽŒŒ‘’’‘‘ŽŒŒ‹ŒŒ‹ˆ€{€‚ƒ„„ƒ€~wpostqpsxwph`acdcacchllklkkjlmmheddcbddddehlmgfjq|Š™–Žˆ„‚…†„€ƒŽ˜“„‚†Š‹ŠŠ†|wtnjhgihhq€‡Ž“““”••••“Š~yyussssv{…ˆ…{|†ŠŒ†lnnooqrsvwwxyyyyyyyyzzzzyyyyzz||~~~~}{{{{{||||||{{{{zzzz{|||||||{{{{{{{{zz|||}||{{{{||||||||||}}||}}||~~€€„…ƒ€~‚†‡ˆ‡†…„…ˆŠˆ†…„„„„„…………†‡‰‰ˆ†ƒ€€ƒ„…†‰‹ŒŽŠˆŒŽ““”“’‘Ž‹‹ŒŒ‹‰€{€‚ƒ„„ƒ{tnoqsoptxztkaaddddeeegiklkklorrnhffccddeeegmtm`[ewŠ•Œ†‚€ƒ…„ƒƒ˜ž—‘ˆ„ƒ‚…Š‹ŠŠˆƒ|xsmjijggnzƒ‹‘“”••••’ˆ|yxussrsw{‚‡‡‚|‡ŒŒ†lmnooqrsuvxyyyyyyy{{zzzzyyyyzz||~~~~}|{{{{||||||{{{{zzzz{|||||||{{{{{{{{zz|||}{{{{{{|||||||||}}}||}}||~~ƒ„ƒ‚€€ƒ…‡††…ƒƒ‡‰‡…„„„„„„…………†‡‰‰ˆ†‚€€ƒ„…†‰‹Ž‹ˆˆŒŽ“”•“’‹‹ŒŒ‹Š‚z€‚ƒ„„ƒ}yqmoppootwzvlcaddfeffdeijkjikpuuokgeeeeeeedgmuo\R]v‹’†€†ƒ€‚Š–ž š•Œ„ƒ‚…Š‹ŠŠˆƒ‚}wpmkjgglv€†Ž“”••••†{xxtssrtx|‚‡‡|ŠŒ†mnnnnpqrvxyxzzyyzz{{{{{{zz{{||}}~~}||||||||{{{{{{{{zz{{z|}}|{|||||{{{zzz{{{}}{{}}{{{{||||||}}~~~~}}}}}~€‚€ƒ„ƒƒ‚‚€€„„„…‚ƒ†††„„…„„…„„„„‡‡ˆˆ†ƒ€‚ƒ‚ƒƒ„†‰ŒŽŽ‹ˆˆŠŽ’““”“‘ŽŒ‹‹‹…€€ƒ„…„€}}vnkmoooquxyuledghiidcccfggefjnvupjfefeeccdcfkmeXJWv‹„€…†‚€ˆ•ŸŸŸ™’‰…„…ˆˆ‡‡‡…€|xtqmkgjs|‰Ž‘“••–•…{xusttsuz~…††~|„ŠŒ‡‚‚nnnnnpqruwxxzzyyzz{{{{{{{{||}}}}~~}||||||||{{||||{{{{{{z||||{|||||{yyzzz{{{{{{{{{||||||{{{{}}||~~}}}}}~€‚€€‚„„„ƒƒ€€€„ƒƒ…ƒ‚……„„„„†…„„„„†††…‚‚‚ƒƒ‚ƒƒ„ˆŠŒŽŽ‹‰‰ŠŒ’“”•”‘‘Œˆ‚ƒ„†„z~|tmjlonorvyxslffhjkmhfddefedehmrroifeeecaaa_`dd^TJUn„„‚†‰„€‡•¡£ŸŸž›•Ž‰…„‡‡††‡‡‚~{wuqniipw|ƒŠ‘••–•Ž„zxtsttsuz€†ˆ„|}†ŠŽŒ‡ƒƒnnnnnpqrtvwyyyyyzz{{{{{{{{||}}}}~~}||||||||||}}||{{{{{{z|{{z{{{{{{zyyzzz{{{zz{{{{||||||{{{{||||}}}}}}}~€‚ƒ€‚„……ƒƒƒ‚„„ƒ„ƒ„„„„„„……„„„„„„ƒ‚‚‚ƒƒ„ƒƒ„†ˆ‹ŒŽ‹‹‹‹ŒŽ’“”•”’‘ŽŽ‹…‚ƒ„†„€{{yrljlmlnqvyysmhgijlonlhhggeedfikllgggfec`_]][^^ZSPWf€„ƒ…‹‡‡•ž¢¢ŸŸžž›“Žˆ„‡‡††‰‰„~zxupliotx~…‹Ž“•””Œ€wvtsttsv{‚‡ˆ‚z€ˆŒŽŒˆ„„nnnnnpqrtvwzxxyyzz{{{{{{{{||}}}}~~}||||||||}}}}}}{{||{{z|{{z{{{{{{zyyzzz{{{{{{{||||||{{{{{{{{||~}}}}}}~€‚ƒ‚ƒ††……„ƒƒƒƒƒƒ„…‚„„„„„„……„„„„„ƒ‚‚ƒƒ„‚ƒ„†‰ŒŒŽŒ‘’“”•”““ŽŽ‘‡ƒƒ„…„€zwuqllmlknqvyyslhgilnqrrmihhgfdefgiigghfeeb`^\\\\XTWZb|Œ„ƒ†ŠŠƒ‘£¢¢ŸŸž—‹ˆ‡‡‡ˆŠ‰„€€|ywsnjmru|‚‡‘••“Œwutstttw|ƒ‡†€{‚ŠŒˆ…„oonnnpqsuvwxyyyyzzzz{{|||||||}~~~~~~}}|{{{{{||}}||||{||{{{||{{{{{{{{zzzzzzzzzzzz{{{{|||||z{{||||~}||}}}~€‚ƒ„ƒ‚…‡††…ƒƒ„„ƒ„…‡‡ƒ‚„„ƒ„……„ƒ„„„„…ƒ‚€‚‚ƒ„…ƒ„…ˆ‰ŒŽŽ‘’“”””“’Ž’Œ‡„„……wspmmoonlmntyzsmkhhlpptvtnnkhfccdcdghhihgggc\YXYYWX^_cvˆ†ƒ†‡‰‰‰–Ÿ¢¡ŸŸŸžŸžš”‹‹Š‰‡‰ˆ…‚€‚ƒzyvokortz†Œ“•’‹~wutssssx€†‡„}}…ŠŽŒ‡…ƒoonnnpqsvvwxyyyyzzzz{{{{{{|||~~~~~~~~~|{{{{{||}}||||{||{{{{{{{{{{{{{zzzzzzzzzzzz{{{{|||||z{{||||~~}}}}}~€ƒ„„„ƒ€…ˆ†††ƒ‚„…ƒ„…‡‡…„„„ƒ„……„ƒ„„„„„ƒ‚€ƒƒ„„………‡‰‹ŽŽ‘’’””””“’‘‘‘’ŽŠ†„„„‚ysnkoqpnkllrxzrmjiilprwvtqpmica``acfgkmmlmolb^][[Z[adhv…ˆ‡‹ŠŠˆ™¡ žžž  žœ™”Ž‹ˆ‡†ˆˆˆ„€‚„}|zslptsw{‚ˆŽ“”‘‹}vssqsssy†‡|‡ŠŽ‰…„oonnnpqsvvwxyyyyzzzz{{{{{{||}~~~~~~~~|{{{{{||||||||{||{{{{{zzzzzzzzzzzzzzzzzzzzzz{{{{{{{yzz{{||~~}}}}}~€ƒ„…ƒ‚€€‚‡‡‡‡„‚ƒ„………‡‡‡…„„ƒ„………„„„„„ƒ‚‚‚‚ƒ……„…‡‡‰‹‘‘’’’””••”“’‘‘‘“’‰…„„zsnnpqpmiijpwxrmihimptvutrqnjda``acdgloopqrqhcbb`^_fhkvƒŠ‹ŽŒˆ’œ¢Ÿž  žœ–‰„…†ˆˆˆ…~€ƒƒ}vmqvsuw}„‹’“‘Š}ussqssuy€…†|‚‡‹ŽŽ‹†„oonnnpqsvvwxyyyyzzzz{{zzzz||~~~~~~~~~|{{{{{||{{||||{||{{{zzzzzzzzzzzzzzzzzzzzzzyyzzzzzz{yzzzz||}}|}}}}~€ƒ„…ƒ‚‚…ˆ‰‡…‚„††…‡‡‡†„„ƒ„„„…„„„„„ƒ‚‚‚…„…„„…‡ˆ‰‘‘‘‘’’’””••”““’‘‘‘‘’Ž‹†„ƒ|rnnpqplihhnwwrmhfgnrtuuusrplgcbbbcdhlnprsttnihhebdhknw‚‹ŽŒŠ•ž¤Ÿž   žŸ”Š…„…†‰‰ˆ†}€‚‚€xptxussy‰Ž’‰|srtrssv{€„…~|ƒ‰ŽŽŽ‹†„ppoonpqtvvwxxyxxyyyy{{{{{{}}~~~}|||||{{{||||}}|}zzzyyyyyxxxxzzzzzzyyzzzzzzzzyyz{zz{z{{z{||}}}~~~}€ƒ…††…‚‚‚„‡ˆ‡ƒƒ‚…††‡‡††„ƒƒ„……†…„ƒ„„ƒ‚ƒ„……ƒ……‡Š‹Ž‘‘’’‘‘‘‘’’“”•••”””’’‘‘’ŽŽŽ‰…„|spqsrpmihgnxxrkgcgossstsqpnjedbbacdhlpqrvttspnnkjjmmpyƒŽŽŒ— ¤¡ Ÿ   Ÿ Ÿ›Œƒ‚ƒ†ˆ‰ˆ‡…zy~€~€~ysx{utrv}…Š‰zttttttx|ƒ‚|€‡Ž‹†…ppoonpqtvvwxxyxxyyyy{{{{|{}}~~~~~~~}|||}|{zz}}||}}}}z{zyyyyyxxxxxxxxyyyyzzzzzzzzzzz{{{{{{{z|||~~~~~~}€‚…‡‡†‚‚‚„„†‡„‚‚‚„†††ˆ†…„ƒƒ„…………ƒƒƒ„ƒ„……„…†‰Œ‘’‘’’‘‘‘‘’’“”••••••“’““’Ž‹‡„€{qssttppmielwwqjfcgqttsttqnliecbbacfimqrsvuusqorqprrrrz‚ŽŽ™¢¤£¡   ŸŸ ž’…€ƒ†‰‰ˆ†ƒxu{~~€zv}yvruz‚ˆŽ‡zstuttux}ƒƒ}{‚‰ŽŽ‹†…ppppnpqtvvwxxyyyyyyy{{{{{}~~~~~~~}}||}|{zz}}||}}}}||zyyyyyxxxxwwxxyyyyzzzzzzzzzzz{{{{{{{z|||~~}‚…‡‡†„„ƒƒƒ„…„ƒ‚ƒ††…‡†…„ƒƒ„……„„„ƒ„„„„…………‡ŠŽ‘’’’‘’‘‘‘‘‘’“•••••–””••’‘Ž‰…zqstuurqnhejsuoieckruvutsomkgdcbcdfjloqstwvtrqrusrsuvux‹’’›¤¦¤¢¡¡¡   ˜‡€€‚†‰Šˆ†‚vpt{~€€{y€„€xuuv~…‹Œ…ystuuuv{…|…‹ŽŽŽ‘Œ†…ppqqnpqtvvwxxyzzyy{{{{{{{~€€}}}}~~}||}|{{{}}||}}}}||zyyyyyxxxxwwxxxxyyzzzzzzzz{{{|{{{{{{z|||}}~~}‚…‡‡†……ƒ„ƒƒ„……‚€ƒ††„††…„ƒƒ„……„„ƒƒ‚ƒ‚‚…†††…†‰ŒŽ‘’‘’’‘‘‘“••••––”••”’‘‹‡{rqtuutrofekstniddnsvwvurnliecbbefhlopqsuwvsrqtxwusuxuv|†Ž‘‘•ž£¥¤£¡¡¢¡ žƒ€€€†‰‹Š‡‚ulpz€€{z‚††zwtsz„ˆ‰„xtuvvuv}‚„~|ƒˆŒŽŽ’Œ†…ppoonqruwwwwwwxzzzzzz{||}~~~~~~~|}}||||||||}}}}}{zyyyywyyxxxxxxxxyyzzyyyyz{{{{{{{||{{{{||}~~~}}~~‚…ˆˆ‡‡‡…„„ƒƒ„„ƒ‚„††…†††…„„„„„„ƒƒ„‚ƒƒ„††††…‡‰Œ‘‘‘‘‘’““••–––•••’‘ŽŠ„{srxwustpgfkrrlgcentwwvuqokjfdcdfhkpqqrruvrqoqw{zxuvvtqtŠ‘•Ÿ¤¥¥£¢¢¢¡¡š‰€€€€„‰‰Š‰„uijv~|{ƒ‰ˆxvwz€†‰‚zuvvvuw|€}}†ŠŽŽŽ‘”…ƒppoonrsuvvwwwwxyzzzzz{}}~~~€~|}}||||||||}}}}|{zyyyywxxxxxxxxxxyyyyyyyyz{{{{{{{||{{{{||}~~~~~~~…ˆˆˆ‡‡†…„ƒ‚ƒ„„„„††‡†††……„„„„„ƒƒ„‚ƒƒ†††††‡ˆŠŒ‘’““••––––••“‘‘Œ‡~wuyxvuvqhfmrqljefnuxywvrpjjgfdefiosrrrrutpljou{|wuttqmpw†˜ ¥¥¥£¢¢¢¡¡•„€€€€€‡‰Š‰…zlfp|€||„ŒŠ‚zvvx|„ˆxuvvvvx{}}‚ˆ‹ŒŽ’“†„pppppqsuuuwwwwwxyyzzz{||}~}}€|}}||||||||}}}}{zzyyyywwwxxxxxxxxyyyyyyyyyz{{{{{{||{{{{||}~~~~~„ˆˆˆ‡‡‡‡…„ƒ‚‚ƒ„†ˆˆ‡†††……„„„„„ƒ‚ƒ‚ƒ„†‡†††‡‰ŒŽŽŽŽŽ‘’““••–––––•“‘‘‘‘‘‘ŽŠzwyxxwwrkiotrmjdgpvwzzwrpiiifeehmuxvtttsrnhglsy{vsttpmmtƒŽ‘™¢¥¥¥£¢¢¢¡žŽ€€€€€€…‰Š‰…~pfm{€€}{„ŒŽ‡wsu{†vtwuuvyy|z…‰‹ŒŽ’‘‹„‚ppqqqqsuuuwwwwwxxxzzz{||}~}}€€€€€}}}||||||||}}}}{zzyyyywwwxxwwxxxxyyxxyyyyyz{{{{{{||{{{{||}~~~~~~„ˆˆˆ‰ˆˆˆ‡…„ƒ‚‚†ˆŠŠ‡††………„„„„„ƒƒƒ‚„ƒ‡††‡‡ˆ‰ŽŽŽŽŽŽ‘’““••––––––“’‘‘‘“’‘„~zyxwvwrljpsrmhehqvwzyvsnjiigfeiox|xvvurplfehqwyvsrrpnlt‚Œ‘™ ¥¥¥£¢¢¢¢›‡€€€€€€€…‰Šˆ†~qglz}{„ŒŠ‚ysuz‚}ttwvvwyxyz€†‹ŽŽ“Šƒppqqprqttuxwxxvwxx{{{||}}}}}}~~~€€|}}~~||{{||}}}}{{{zzzywxxwwvvwwwyyyxxyyzzz{{{||||{{||||}}}}~~~~~€…ˆ‰‰‰ŠŒŒŠ‡…‚€„ˆŠŠ‡†††………„‚ƒƒƒƒƒ„„††‡‡ˆˆ‰‰ŽŽŽŽ‘Ž‘‘‘’“””––––••”“’’’’“’‘„~zvwxyunmpsqmhfkuwuxxtqmjiigfekq{€|wxuromhccluxutrrqnkr€‹“™¡¥¦¥¤££  •ƒ€€€€€€…‹‹ˆ‡€shky€€~z‚Ž’Œ†~wwz|uuwuvvxwy„ŠŽŽ’•ˆzppqqprqttuvuuuvwxxyyz{|}}}}}}}}~€€|}}}}||||||}}}}{{{{zzywxxwwwwwwwyyyxxyyzz{{{{||||||||}}}}}}~~~~}ƒ†‰‰‰‹ŒŒ‹‰…‚€‚†‰‰‡‡‡‡‡‡†„‚„„„ƒ„…†‡†ˆˆ‰Š‰‹ŽŒŒŽŽŽ‘‘ŽŽ‘‘’’“””––––••”“””””“““‘‘Š„}yxzzwooprqmhflvzxyxtqmjjjiihmr|}yvuronjdbluwutssrnkq}ˆŽ”š¢¦¦¥¤¤¢¡Ÿ‚€€€€€€…‡…€rgjw€~z“ˆ‚}x|€zuuvuvvxx{ƒˆŒŽŽŽ‘Œƒysppqqrsstttutvvvwxxyyz{|}}}}}}}}}~~~~~~~~}}||||}}}}}}}}{{{{zzywxxxxxxxxwyyyxxyyzzzz{{||||||||}}}}}}~~~~}‚…‰ŠŠ‹ŒŒŒŠ‡„‚‚ƒ…‡‡‡‡‡‡‡†…‚ƒƒƒƒ„††ˆˆ‰Š‹ŒŽŽ‹ŒŽŽ‘’‘Ž‘‘‘’““””––––••–”””””””“‘‘‰‚}|}|wqpqsplhfoyzxyxuojjllkkklpx{|yvurqpkedltustsssonr|†Ž”›£¦¦¥¤¥£¢œ‰‚€€€€€€€†ŽŽ†‚}qgju~€~z€“ŽŠƒ~z}€xsuvuvwyz€†Š‹ŽŽŽŒŒ…{rorrqqrsutttvuuuvwxxyy{||}}}}}||}}~~~~}}~~}}||||}}~~}}}}{{{{zzywxxyyxxyywyyyxxyyzzyy{{||||}}||}}}}}}~~~~}~…‰‹‹‹ŒŠˆ…ƒƒƒƒ†‡‡‰ˆˆˆ††‚‚„„ƒ…††ˆ‰Š‹ŒŽŽ‹‹Ž‘‘’’Ž‘’‘’“”“”•–––••–•••••••“‘‘ˆƒ€~wsqrsokhfqzzwyyuniilkllkkoswyxxursqkfelssrttttpps|‡Ž”›¤¦¦¥¤¥¤¢—ƒ€€€€€€€‡‡zphju€€~zŒ“‹„{~€xqvvuvxy|ƒ†‰Š‹ŒŒ‰‡…}snkrrqrssttstuuvvwxyyzzz{||||}}||}~~~~~}}}}}}||{{}}}}~~}}{{{z{{yxxxyyyyzzxxyyxxxxyyyyzz{{{{{{{{{|}}}}}~~~~~€†ŠŒŒŽŽŒŠ‰†„ƒ„ƒ„„…ˆˆ‰ŠŠ‰‡††„ƒƒ…†‡‰ŠŒŽŽŽŒŒŽŽ‘’“’‘’’’““““””•–––––••••••••”’’‘‘‹†„vqqrrolhgr~}yzzuqlllonnnmlnqstuwtsrnkilqsqtutrqrsz…Ž”›¢¦¦¦¤¤¥¢‘ƒ€€€€€€‚„ŒŒ…€zqlmv~}z}Š“‹†€|€}xtvvwwx}†‰‰…‡ŠŠ‰ƒ|xpkhjrrqrsstttuuvwwwxzzzzz{||||||{|||||||}}}}}}||||}}~~~~}}{{}{{{zzyyyyzzzzyxyyxxxxyyzzzz{{{{z{{{{|}}}}}~~~~}„‹ŒŒŽŽŒŠ‰ˆ†……„ƒƒ…‡ˆ‰ŠŠŠˆ‰ˆ†„„…‡ˆˆŠŽ‘“’‘‘ŽŒŒŒŽ‘’“’’‘‘’’“”“““””•–––––•••••••••””’’’Œ‡€wrqrrolhgs€|||xsooorpppokklnpqvtsspnmlnpqtutsssswƒ‹’›¢¦§§¥¥¥ Œƒ€€€€€€‚ƒ‰Š…€yqmnv||z}Š’†‚}xuvvtty€„‰‰ƒ~‚‚~vokffimqqqrsstttuuvwwwxyyyyxyzzzzzz{{{{{{{{}}}}}}||}}~~~~~~}}||}|{{zzyyyyzzzzzyyyxxxxyyzzzz{{{{z{{{{|}}}}}~~~~}ƒŠŒŒŽŒ‰ˆˆ‡……ƒƒ„†‡ˆ‹‹Šˆˆ†…„…††‡ˆ‹““‘‘‘ŒŒŒŒŽ‘‘““’’‘’’’“””““””•–––––•••••••••””““”’‰xrqrqnkgfs€}~~zurrrtrrpokjjjklpqqqqqplloqtutuussw‚‹‘š¢¥§§¥¥¤œˆ‚€€€€€€€…‡ƒ€yqmntz~}z}‰’‘‡‚€}xuvvuuz‡Š‡~wvzyrkeccejnqqqrssttuuvvwwwxzzyywxyyyyzz{{zz{{{{}}}}}}||}}~~}}}}|z{{yyzzyyzzzz{yyyxxxxyy{{zz{{{{z{{{{|}}}}}~~~}„‰ŒŒŽ‹ŠŠˆ†…„„ƒ††ˆ‹‹Šˆ†„„„†‡‡†ˆŒ“’‘‘‘‹‹ŒŒŽ’’““’’‘‘’’’“””““••––––––•••••••••••““”“‘Šxstrpnjges‚‚~~}yuttttrrqpnkijijloppppnlloqtutttsrw‹‘™¡¥¦¦¦¦¤š†‚€€€€€€€‚ƒƒ{smnsy}}z}ˆ‘‰…‚}xuvuvv{‚ˆŠ†|tpsslfcabdjnqqrsrrttttvvwwwwyyxxwwxxyzyyzzy{zzz{||}}~~}}}~~~~}~~}}{yzzzzxxxxzzzzzzzzyyyyyyz{{{||||{|||{|}}}}~~€€~~€ƒˆŒŽ‘‘ŽŒ‹‰Š‡†„„ƒ…††ˆˆ‡†…‚ƒ„†…†…ˆŒ‘’’‘‘‘‹‹ŒŽ‘‘“””’’‘’‘“““”””•””–––––••”••••”””””””“ˆ€yuutonjgdp€ƒ}|zuttuutqsvrnlhgghknnmpnmlnrutusttv|„Š˜¡¥§§¥¥¢•…€€€€€€€€€€€}vnlqvz}y{‡‘‘Ž‹…‚€{vuuuuw}…Š‹‚wokmjgedaadhmssssssttttuuvvvvxxwwwwwwxxyyyyxzzzz{||}}}}}}}~~~~}}}|zzzzzxxyyzzzzzzzzyyyyyyz{{{||||||||{|}}}}~~~~€ƒˆŒ‘’‘‹ˆˆ††††……††ˆˆ‡†„‚„………„„‰Œ‘’“‘ŽŒ‹‹ŒŽ‘‘“”•”’‘‘’‘“““””••””––––••””••••””””””•“…~zxyzuqlheq…}|yuttrrsotyxsoigfdgkkjnmllmquuvtuwz‚‡‹—Ÿ¥§§¦¥¡’„€€€€€€€€€€€€~{rnquz|x{ˆ‘’‹…‚€yvuuuvy†Š‡}tnkljjiigfhmquuttttttttttuuuuvvwwwwwwwwxxyyxzzz{|||}}|||~}}~~~~}}}|zzzzzzzyyzzzzyyzzyyyyyyz{{{||||||||{|}}}}~~~~‚‡ŒŽ‘’’‹ˆ‡††††…†††††‡†ƒ‚„………‚ƒ‰’’‘‹Œ‹ŒŽ‘““”•”’‘‘’‘“““””••––•••••”””••••”””””““’‹‚|{{~€}vpifs‚†‚}zwuuussronv}zunjgefhhilljjknquvvwx|„ˆ‹Ž–œ£¥§§¦¢„€€€€€€€€€€€€ƒ‚yrqsy{xz‡‘Š†‚~wtuuuwz‡‡yqpnmnopqpoqvxttuuttttttttuuuuvvvvwwvvvvxxxxwyzy{{||}}||{~}}~~~~}}}}{zzzzzzzzzzzzxxzzyyyyyyz{{{||||||||{|}}}}~~~~~~‡ŒŽ‘’’‘‹ˆ††‡††…‡††……‡†ƒƒ„……„ƒƒˆŽ’‘‹Œ‹ŒŽ’““”••”‘‘’‘“““””••––••••”””“••••””””•“’‡~zz‚…„|vlgs‚†€}yvuvvttrnjt~{smideeegkkiijjottuvy~†‰ŒŽ•›¡¥¦§¦¡„€€€€€€€€€‚†‰†uqrx{y{„‹‰…ƒ‚}vsuuuvy€…„{uqprqstvvvy|~ttttttttssuuuuttvvwwwwwwwwvwwwwwyy{|||}}||||}}~~~~~}}}{{{zzzzzzzzyyyyyyzzzzzz{{{{{{{{{{z{|||}~~~~~~}}|€†‹‘‘””‘‹ˆ††‡‡‡‡‡‡‡†‡…„ƒ‚…ˆ‡†…†‰ŽŽŽŽŒŒŽ’“””–•”’’‘‘’’“””––••””••””””““••”””””“‘Œƒ{}„‡‰ˆ…ult€ƒ{xuuuwutrmio|ƒyqlhcaaeghikjjnqruwy…‰Œ”š £¦¦¦ŸŽƒ€€€€€€€‚†’Œ‚xrqwzww~„„‚|vuvuuwy€„‚xqqsvvvxz|‚ƒ…ƒttttttttttuuuuttvvvvvvuuvvwwwwwwxxz{|||||||||}|}~~}}}{{{zzzzzzzzyyyyyyzzzzzz{{{{{{{{{{z{|||}~~~~~~~~}}}…‹’’‹ˆ‡‡‡‡‰ˆˆˆˆ‡ˆ…ƒ‚…‰‡‡‡ˆ‹ŽŽŽŽŒŽŽ‘“””•––•”’‘‘’’”””••••••”””“““““””“““““’Žˆ‚€~‚†‰‹‹‹ˆ‚zz‚‚zxtstuvusngkw€ƒ~xsmd__bdehkjlnorty}‚…‰Œ’™Ÿ£¦¦¦ŸŽƒ€€€€€€€‚ƒƒƒŒ”™’‡}vruurpsxwvvvx{zuvvuuxz€ƒtpsuxxy}…ˆ‰‰‡„ttssttttttuuttttuuuuuuuuvvwxxxxxxxz{{{{{|||||}|}~~}}}{{{zzzzzzzzyyyyyyzzzzzz{{{{{{{{{{z{|||}}}}}~~~~}}~€†Œ‹ˆˆˆˆ‰ŠŠŠŠŠŠˆƒ‚‚ƒ†‰‰ˆ‡Š‹ŒŒŽŽ‘“””•––—•’‘‘’’”””””””““””“““’““““’’’’“‘Œ…€‚‡ŠŠ‹ŽŒŠ………ƒ~xtstuwvsmggp}€|wodaaabcfhikmoqs{€„†‹Œ—¢¥¦¦Ÿƒ€€€€€€€‚…„ƒ˜›”‹zttqolkmjkkkptusvvuuy{€{srtvyz}‚‡ŠŒ‰ˆ…‚ttssttttuuuussttuussssuuuuwwwwwwxxyzzz{{|||||}}}~~}}}{{{zzzzzzzzyyyyyyzzzzzz{{{{{{{{{{z{|||}}}}}~~~~}}~†ŒŠŠŠŠŠŠ‹‹‹‹‹‹ˆƒ‚…ˆ‰ŠŠ‰Š‹ŽŽ‹‹‹ŒŽ‘“””•——–”’‘‘’’”””””“““““““““’““““’’’’‘ŽŠ…€‚„‰ŒŒ‹ŽŒŠ‡ˆ†…zustvwvrlfdky€‚~zpgcc`badfhilnpt|ƒ…‡‹ŒŽ•œ¢¥¦¦ „€€€€€€€ƒ‡„ƒ˜œ”ƒ|sqqmiggceeekprrvvuuz{€ztuvwz}„‰Œ‹‡…‚€ttssrsttuuuuttuuuuttstuuuvwwvvwwxxyyzz{{{{||||}}}~~~~~~~~||{{{zyyy{{zzzzzyz{zz{{{{|z{{{{{|{{||||~~}}}}}}~}‚ˆŒŽ‹ŠˆŠŠŠŠ‹Š‰Š‰†„‚ƒ†‰‹‹‹‰‹ŒŽŒ‰‰ŠŒ‘“”–––––•“’‘‘““””•”““’’““““““‘‘‘‘‘‘‘‘‘Š…ƒ„‰ŒŽŽŽŒŒŒŠˆ†€zxyxwtqlgehq{€zsigfb``afgghjnr{…‡ŒŒ”š ¤§§¡‘„€€€€ƒ…„…”“’Š‚zrmjgecba``bhmqqstuvx~€~vuvvw}‚…‰ŒŠ…‚{ttssrsttuuuuuuuuttttstuuuwwwvvwwxxyyzz{{{{||||}}}~~~~~~~~||{{{zyzz{{{{zzz{{{{{{{|||{{{{{{|{{||||~~}}}}}}}}ƒˆ‹ŒŽŽŽ‹ŠˆŠŠŠŠŠ‰ˆ‰‡„ƒƒ„‡‰ŠŠŠŠŽ‰ˆ‹’‘“”•–––––”“‘‘““””””’’‘‘’’’’’’‘‘‘‘‘‘‘‘ˆ…„‡ŒŽŽŽŽŽ‹Šˆ…~|wsmhefkuz{tpikjebbcdefghlpx~‚…Š‹‹Ž’˜ž¤¦¦ ’„‚€‚‚‚„‡…„€zumjgedca`]]`ejmqrtuvx}}zutwy~„ˆ‹Œ‰…‚{ttssrsttuuuuuuttttsssttutvvvvvwwxxyyzz{{{{||||}}}~~~~~~||{{{zy{{{{{{zz{{{|{{|||||{{{{{{|||||||~~~~}}}}}€„‰‹ŽŠŠˆŠŠŠŠ‹Šˆˆ†ƒ‚ƒ…†‡ˆˆ‰‹ŽŽŠ‰‘’‘‘““••––——•“““““““““’‘‘‘‘‘‘‘‘‘‹ˆ…‡‹ŽŽŽŽ‹‹‰‡‡ˆ†…‚zsliginuuokjmnjhggffffghlsy~‚‡Š‹Œ‘—Ÿ£¥¤ž‘†ƒƒ‚€}}|zzyxvqpjfedccb`]\_adhnpsuuxz{wvy}†ŠŒŽŽŠˆ…‚ttssrsttuuuuuuttssrrsttutvvvvvwwxxyyzz{{{{||||}}}~€€~~~~~||{{{zyyy{{{{zz{{||||}|}}}{{{{{{|}}||||~~}}}}~€€„‰ŠŽŽŒŠŠˆŠŠŠŠŠŠˆ‡†ƒ‚„‡†‡‡ˆŠŒ‘‘ŽŒ‹ŒŽ‘’’‘‘‘““••––˜˜–”““““’’““‘‘‘‘‘‘‘‘Ž‹‡…‰ŒŽŽŽŽŽŒ‹Š‰‰‹ˆ‡‡ƒypmjikqqmjjnplmmlihffgfiouz€„‰ŠŠ•¢¤¡›…‚€~||{yyxusrqplkfffdcdb`^\``bfkoruuwz{y{€„ˆ‹ŒŽŒ‰‡…‚‚ssttstttuuuuttsssssstuuvuvvvvvvvvvxxyyyz{{{{{{{|}}~~~~~}}{z{{zzyy{{zz{{z{{{||{{{|||{{{|||}}{{||~~~~}}|~~ƒ…‰‹Ž‹‰‰‰ŠŠŠŠŠŠŠ‡…‚ƒ„…„…‡ˆ‰ŒŽ’‘ŒŒŒŽ‘’‘‘‘‘’”•••——–•““’‘‘‘’’‘‘‘Ž‹‡‡ŠŽŽŽŽŽŽŽŽŒ‹‹‹‹ŠŠ‰‰‰†~xqmopnlilprqorpmifeddhmsw}†‰‰’œ¢¡š“ˆ~~}{zzzyxvuttsqolhgeeddbcdeda``adglotvz|„‰ŽŽ‰‰‰†…ƒ‚‚ssttttttuuuuttttssssttuvvvvvvvwwwwyyyyyz{{{{{{{|}}~~~~~}}|{{{zz{{{{{{{{{{{|||||||||||{}||}}{{{{~~~~}}|~‚…‡‰‹ŽŽŽŒŠ‰‰‰ŠŠŠŠŠŠˆ„ƒ‚ƒ…†…„…ˆ‰ŒŽŒŽ‘’’‘‘‘’”•••––––”“‘‘‘ŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŒ‰‰ŠŽŽŽŽŽŽŽŒŒ‹‹Š‰‰Šˆ†„ƒ~{yxvsqswxyz|ztpkgddfkotx|„ˆŒ˜š–Œ†{z{yxyxyyxwuttsqolhgfdcbdceggcaaadfilqwƒ‡ˆ‹ŽŒ‹‰ˆ‡†…„ƒƒrrssttttuuuuttttssssttuvvvvvwwwwxxyyyyyz{{{{{{{|}}~~~~~}}}|||{{{{{{{{||{|||||{{||||}}{}||||{{{{~~~~}}|~ƒ…ˆ‰ŠŒŽŽ‹Š‰ŠŠŠŠŠŠŠ‰†ƒ‚„†‡†††‡ŠŒŒŽ’“’‘‘’”•••––––•“ŽŽŒŒŽŽŽŽŽŽŽŽŽŽŒŠ‰ŠŒŽŽŽŽŽŽŽ‹ŠŠŠŠ‰ˆˆ†‡…ƒƒ€}{}€€‚€zvpifddgjosx{~…‰Ž‰‚}zyxzyxyyyyzzwxvusqokihdcacefhhfeccdefimv‚‡Š‹Ž‹ŠŠ‰‡†…„ƒ„qqrrttttuuuuttuussssttuvvvvvxxxxxxxxyyyz{{{{{{{|}}~~~~~}}}}}}||||{{||}}|||}||||||||}}{}||{{{{||~~~~}}|~ƒ†‰‰ŠŒŽŽ‹Š‰‹‹ŠŠŠŠŠˆ…ƒ‚„ˆ‰ˆ‡…ˆŠŒŒŒŽŽŒŒŽ’“”“‘‘”•••••–––”’‘ŽŽŒŒŒŒŒŒŒŒŒŒŒ‹Š‰ŠŒŽŒŒŽŽŽŽŽŽ‹Š‹‹ŠŠˆˆˆ‡ˆ‡‡…†ƒ„…„†…„ƒ‚{umgdcdglotwz‚…ˆˆ„~|yxyyyzzz{z{|}{{zxusqmiidcbaefiihgcdddegku€†‹ŒŒŽŽŒ‹Š‰‡†‡…„„rrssssssttutssttssrsrsttvvwwwwwwxxyyzzz{{{||zzz|}}~~~~}}}}~~}|||||||{{}|||||||{{||}}~~|}}}||||||~~~~}|~~‚„‡‰Š‹ŒŽŒ‹ŠŠŠŠ‰‰ŠŠ‰‡ƒ‚…ŠŒŠ†…‡‰‹‹ŒŒŽŽŽŽ’“’‘’“••””–•••’ŽŒ‹‹‹‹‹‹ŒŒŒŒŒŒŒŒŒŒŒŒŒŠ‰Š‹ŽŽŒ‹‹‰‰ˆˆˆˆˆˆŠ‰ˆ‰ˆ‰‹‹‰ˆ……„‚|slgcdfjkmps{~~}{xxxxyxxz|}~~€‚|xutrnkhda`cgghiihfecccgmy‚‰‹ŒŽŽŒ‹‹Š‡‡‡†„„ssssssssuuutttttssrsrsuuvvwwwwxxxxxxzzzz{{||{{{|}}~~~~}}}}}}|{{{{{{{||}|||||||{{||}}~~~~~~~~||||~~~~}|}€ƒ…ˆŠŠ‹ŒŽ‹ŠŠŠŠ‰‰‰‰ˆ„€ƒ†ŠŒ‡††ˆŠŠŒŽŽ’“’‘’“”••”–•••’Œ‹ŠŠŠŠŠŠŠ‹‹‹‹‹‹‹‹‹‹‹‹Š‰‰‹ŒŒŒŒŒŒŒŒŒŒŽŽ‹‹‰‰‰ˆˆˆˆˆˆ‰ŠŠ‰‹Œ‹ŠŠ‰ˆƒ{qieefhjjmotvttvuxxyzzz}ƒ…†„……‡ˆ…zwvuqmjgcbcfhjlljigdddegny…‰ŒŒŽŽ‹‹Šˆˆ‡‡……ttttuuuuvvutttttssrstuvwwwwwxxxxxxyyyyyy{{|||||}}}~~~~}}}}}}|{{{||||}}}|||||||||||}}~~}}}}}}||||~~~~~}}‚ƒ†ˆŠŠ‹ŒŽ‹ŠŠŠŠ‰‰‡ˆ†ƒ€ƒ…‡ŠŽ‰††‡ˆŠ‹ŽŽŽŽŽŽ’“’‘‘’“”•–••••“‘Œ‹‹Š‰‰‰‰‰‰ŠŠŠŠŠŠŠŠŠŠ‹‹Š‰ˆŠ‹‹‹‹‹‹‹‹‹‹ŒŒŽŽ‹‹ŠˆˆˆˆˆˆˆŠŠŠ‹ŽŽŽŒˆxmhfghihikmopqvvxxy{|~…‰ŽŽŠ‰‹‹†zwwuromjhgeeimnnlkhfccedfo~‡ŠŒŽŽŽŒ‹ŠŠŠ‰‰‡‡ttuuuuuuvvutuuttssrsuvvwwwvvyyyywwxxxxyy{{|||||}}}~~~~}}}}}}|{{{}}}}}}}|||||||}}||}}~~{{{{{{||||~~~~}~~‚…ˆ‰ŠŠ‹ŒŽŽ‹ŠŠŠŠ‰‰ˆ‡…ƒ€€„†‡‰ŒŠ‡†‡ˆŠ‹ŽŽŽŽŽ’“’‘’““”–••••”‘‹ŠŠŠˆˆˆˆˆˆ‰‰‰‰‰‰‰‰‰‰ŠŠŠ‰ˆ‰‹‹‹‹‹‹‹‹‹‹‹‹ŽŽ‹‰ˆˆ‰‰‰‰ŠŠ‹ŒŽŽŽŒŽŽŽŒ†{rjhggiggjjlmpuvwwy|†Œ’–™–“Š„{yxxurqqnlkecimppmkjgdddediyƒŠŒŽŽŽ‹ŠŠŠ‰‰‡‡ssttuuuuuuutttttssrtuvwwwwwwxyzzyyxxyyzz{{|||||}|~~~}}}|}}||||}}}}}}||||{{}}~~}}}}}}|{{{{{||||{{{|{}€ƒ…‡‰Š‹ŒŽŠ‰‰‰‰ŠŠˆ…ƒ~‚†‡†ˆ‹Œ‹ˆ††‡‹ŽŽŽŽŽ’’“’‘‘“”””””“‘‹Šˆ‡ˆ†‡‡‡‡ˆˆ‰‰‰‰ˆˆˆˆˆ‰Šˆˆˆˆ‰‰‰‰‰‰‰‰‰‰ŠŽŽŽŽŽŽŒŒŒŠ‰‰ˆˆ‰‰‰‰‹‹ŒŽŽŽŽŽŽ‹ƒynhihiikkhhjmptsv{€Š•˜š›˜“‘’ˆyxwxxuwwtsnihkmqqnnjiecddber‰ŽŽŽŽ‹ŠŠŠ‰‰‡‡ssttuuuuuuutttttssstuvwxxxxxxyzz{{zzzzzz{{|||||}|~~~~~}|}}||||}}}}}}||||||}~~~~~}}}}}{{{{{||||{{{|{}ƒ…‡‰‹‹ŒŽŽŒŠ‰‰‰‰‰‰ˆƒ€}„‡ˆ†‰‹ŒŒŠ‡‡ˆ‹ŒŽŽŽ’’“’‘‘“”””””“‘ŒŠˆˆˆ‡‡‡††‡‡ˆˆˆˆˆˆˆˆˆ‰‰ˆˆˆˆ‰‰‰‰‰‰‰‰‰‰ŠŒŒŽŽŽŽŒŒŒŠŠ‰ˆˆ‰‰‰‰‹‹‹ŽŽŒ‡skjihikkfegknruy‡•––˜˜–’‘Ž‡{zz|}‚}vplllnqqpomlgdddcel|‡ŽŽ‹ŠŠŠ‰‰ˆˆuuuuuuuuuuutttttsstuvvwxxxyyxyzz{{zzzzzz{{|||||}|~~~~~}|}}||||}}}}}}||||||}~~~~~}}}}}{{{{{||||{{{|}‚„‡ˆ‰‹‹ŽŽŽŽŒŠ‰‰‰‰ˆ‡†€}}€†‡ˆ‡ˆ‰‹ŒŒ‰‡ˆŠ‹ŒŒ’’“’‘‘’“”””““‘ŒŠ‰ˆ‡†…………††‡‡‡‡ˆˆˆˆˆ‰ˆ‡‡‡‡ˆˆˆˆˆˆˆˆˆ‰Š‹‹ŒŒŽŽŽŽŒŒŒŠŠ‰ŠŠ‰‰‰‰‹‹ŠŒŽ‹‰„yplihillgeehimrv‡‘““”””’‘ˆƒ‚€ƒ„‰Šˆ„{soonpqqpppnjfddccgt„ŽŽ‹ŠŠŠ‰‰ŠŠuuuuuuuuuuutttttsstuvwwxxyyyxyzz||{{zzzz{{|||||}|~~~}|}}||||}}}}}}||||}}}~~~~~}}}}}{{{{{||||{{{|~‚†‡‰Š‹ŒŽŽŽŽ‹‰‰‰‰‰ˆ†ƒ~|~‡‰ˆˆˆ‰Š‹ŒŠˆ‰‰Š‹‰Œ’’“’‘‘’“”””““‘ŒŠ‰ˆ‡†„„„„„…††††‡‡‡‡ˆ‰ˆ‡‡‡‡ˆˆˆˆˆˆˆˆˆ‰Š‹‹ŒŒŽŽŽŽŒŒŒŠŠ‰ŠŠ‰‰ŠŠŠŠ‰ŠŽŽŽ‹Š†ulihillifeffjnrz†‹’‘Šˆˆˆˆ‰‹ŽŒ†~uoopqssqqpnjgcddbenŠŽŽ‹ŠŠŠ‰‰ŠŠvvuuvvuuvvvutssstttvwwxyzyzzzz{{||{{{{zz{{zz{{}}}}~~}~||||}}}}}}}}||||}}~~~~~}||||{{{{|||||||||}€„‡‰Š‹ŽŒŠ‰‰‰‰‡ƒ|~€„Š‹Š‰ˆŠŠ‹Œ‹‰ˆˆ‰‰‰‘‘‘‘‘‘’’“‘‘‘‘’““’‘‘‘ŽŒŒŠ‰ˆ‡†„ƒƒƒ„…††††„†ˆˆˆˆˆˆ‡†‡‡ˆ‰‰ˆ‰‰‰‰‰‰ŠŠ‹ŒŒŒŒ‹Š‰‰‰‰‰‰ˆŠ‰‹ŽŒŠˆƒyojfgjmkifceghlpvy„ˆ‰ŒŒŽŽŽŒ‹‹Ž‘Šwomruwwtqpomifeedciz†Ž‘ŽŒ‹ŠŠŠŠŠŠŠuuuuvvuuvvvutssstttvxwzz{zyyzzzz||{{{{zz{{zz{{}}}}~~}~||||}}}}}}}}}}||}}}}~~~}||||{{{{|||||||||}‚…ˆŠ‹ŒŽŒŠŠ‰‰‰‡…{z‚‡‹‹Š‰ˆ‰Š‹Œ‹‰ˆˆ‰‰ŠŽ’‘‘’““’’’‘‘‘‘‘‘“’‘‘ŽŒŒŠ‰ˆ‡†„ƒƒƒ„…††……„†‡‡‡‡‡‡†………†‡ˆˆˆˆ‰‰‰‰‰‰ŠŒŒ‹ŒŒŒŒŒ‹‹Š‰‰‰‰‰‰ˆŠŠ‹ŽŠ‰†yqhgjlmlifedcefjmqvz…‡ŠŒŒŒŒŒŽŽŽ‘‘ŽŠƒxplow|}wsponkhgfecft‹ŽŽŒ‹ŠŠŠŠŠŠŠuuuuuuuuvvvutssstttvwxzz{zyyzzzz||||{{zz{{{{||}}}}~~~}||||}}}}}}}}~~||}}}}}}}}|{||{{||}}|||||||}‚…‰‹‹ŽŒ‰Š‰‰‰†‚|zzƒ‰ŒŒŠ‰ˆ‰Š‹‰ˆˆˆŠŒ‘‘‘’““’‘’‘‘‘‘‘’’ŽŒŒŠ‰ˆˆ††…„„„…††……„†‡‡‡‡‡‡†………ƒ…‡‡‡‡ˆˆˆˆˆˆŠŒ‹‹ŒŒ‹‹Œ‹ŠŠ‰‰‰‰‰‰ˆŠ‹‹ŽŽŽ‹Š†…xniijmpomecbcdehikot}‚…‰ŠŒŒŒŽŽŽ‘‘‹…{qlmu}€|xspnliigfegnz†Œ‹ŠŠŠŠŠŠŠvvuuttuuvvvutssstttuwyzz{zzz{{{{||}}{{zz{{||}}}}}}~~}||||}}}}}}}}~~||}}||||}}|{||{{{{|||||||||}€ƒ†‰‹Œ‹Š‰‰‰ˆ„~y{}†‰‹Œ‰‰ˆ‰Š‹‰ˆˆˆ‰‹Ž‘‘‘“““‘‘‘‘‘‘‘‘‘‘ŽŒŒ‹‰ˆˆ†‡†„„ƒ…††„„„†††††††…„ƒƒƒ…‡‡‡‡‡‡‡‡ˆˆ‰‹ŠŠ‹‹‹‹Œ‹ŠŠ‰‰‰‰‰‰ˆŠ‹ŒŽ‹Š‡‡†ulhhmsrogdcccdfeeiltz„ˆŒŒŒŽŽŽ‘‘’ˆ~snos{‚{tqnmkjhfghjt€ŠŽŒ‹ŠŠŠŠŠŠŠttvuttttvvvussrrsuuvwyz{|{||}}}}}}}}||{{||}}}}~~~~~~~~~~}}}}{{||}}}}}}}}}}}|}}~~}|}|||||{|||||||{~…ˆŠ‹Ž‹Š‰‰ˆ‡‚ywy}†‰ŠŒŒŠˆˆˆŠŽŒ‰‡‡ˆ‰‹Ž’’“””’‘’‘‘‘Œ‹ˆ‡ˆ‡†††……………„„……††††††…„„‚‚„‡‡‡ˆ‡‡‡‡‡ˆ‰Š‹‹‰‹‹‹Œ‹Š‰‰‰‰‰‰‰‰ŠŠŠ‹ŽŽ‹‰‡‡†…{offhprojeecbdfcadfjqy€†Š‹Ž‘’’‘Œwonrzƒƒxplkmmkhgghoy„‹ŽŽŒ‹‰‰‰‰‰‰uuvuuuuuvvvusssssuvvwyz{||||~~||}}}}||||}}}}}}~~~~~~~~~~}}}}||||}}}}}}~~~~}||~~~}||{{{{{{|||||||{~‚…ˆŠŽŒŠ‰‰‰‡„~wx|‰‹‹‹‹Š‰ˆˆŠ‰‡‡ˆŠ‹Ž’”“””’ŽŽŽŽŒ‹‰‡ˆ‡‡‡‡…†………„„……††‡‡†††„„‚‚ƒ…†‡ˆ‡‡‡‡‡ˆ‰‰‰‰‰‹‹‹‹‹Š‰‰‰‰‰‰‰ŠŠŠŠ‹ŒŽŽŽŽŒŠˆˆ‡‡ƒxlfflprokfc`aefddbdipyˆ‹ŒŒŽ‘‘‘Œƒ{snpw†„}ullnomkghgls~‡ŽŽŒ‹‰‰‰‰‰‰wwvuvvvvvvuvttsstvwwwyz{||}}~~}}}}}}}}||}}}}}}~~~~~~~~~~}}}}||||}}}}}}~~€€}|{}~~|||{{{zz{|||||||{~ƒ…‰‹ŽŽŽŒŠ‰‰ˆ†€zxz€…‹‹Œ‹‰ˆˆ‹ŽŠˆ‰Š‹ŒŽ‘’””•”’ŽŽŽŽŽŽŽŒ‹Š‰ˆ‡ˆˆ‡††………††……†††††††„„‚‚ƒ„†‡‡‡‡‡‡ˆˆˆ‰‰‰‹‹‹‹ŠŠ‰‰‰‰‰‰‰‰‰‰‰Š‹ŒŒŒ‹ŠŠ‰‰†~sgehmrspje``dhjhdceks~†‰‹‹ŒŒŽ‘‘‘Œ†~vopw€……€wmklppojhhknw„‹ŒŒ‹‰‰‰‰‰‰vvvuvvvvvvuvuutttvwwwyz{||~~}}}}}}}}~~||}}}}}}~~~~~~~~~~}}}}}}||}}}}}}~~}|{|~~||{zzzyyz{||||||{~ƒ†ŠŒŒŠ‰Šˆƒ~xx|‚ˆŽŒŒ‹Š‰‰ŒŒ‹ˆ‰Š‹ŒŽ’””–—”’ŽŽŽŽŒ‹Š‰Šˆˆ‰ˆ‡†………††‡‡††……†††„„‚‚‚„…‡‡‡‡‡‡ˆˆˆ‰‰‰‹‹‹‹ŠŠ‰‰‰‰‰‰‰ˆˆˆˆŠ‹‹‹ŒŒ‹‰ŠŠŠŠ‡ƒxkffkournhcafjmnjffir~…ˆ‹‹Ž‘‘‘Žˆ€xqpv……‚ynklprrmiiijq~‡‹Œ‹‰‰‰‰‰‰wwwwwwwwwwvvvvuutuvwwxz{}}~|}}}}||}}||}}}}}}~~~~~~~~~~}}||}}}}|}~~~~~~~~}}||}|}|{{{zyxyzz{}}|||„‡‹ŽŽ‘Ž‹‰‰ˆ‚{wz†‹ŽŽŒŒ‹Šˆ‰‹Š‰‰‰Š‹Ž’”•—˜”’ŽŽŽŽŒŒŽŽŒŒŒ‹ŠŠŠ‰ˆ‡†††……‡‡††……††††„ƒ‚~€‚„†‡‡ˆˆ‡ˆˆˆ‰‰ŠŠ‹‹‰‰‰‰ŠŠ‰‰ˆˆˆˆˆˆ‰ŠŠŠ‹‹‹‹Š‰ŠŠŠŠŠˆ€tgdglrspjedimrtroknu}†‰‹‹ŒŒŽ‘‘‘Ž‰{uqt|ƒ†…}slkptxsliijmv€‰‹‹‹Š‰ŠŠŠŠwwxxwwwwwwvvwwuuuvwwyyz|}~~|}}}}||||||}}}}}}~~~~~~~~}}||}}}}|}~~~~~~~~}}}|}|}|{{zzyxyzz{||||}€…ˆŒŒŽ‘ŽŒŠŠŠˆ„~xw}„‰ŒŒŒ‹‹ˆ‰‹ŽŠ‰‰‰ŠŒŽ‘’”•—˜–“‘‘ŽŽŽŽŒŒŒŒ‹ŒŽŽŒŒ‹ŒŠŠ‰‰ˆ‡††……‡‡††††††‡‡…„‚ƒ†‡‡ˆˆ‡ˆˆˆ‰‰‰‰ŠŠ‰‰‰‰‰‰‰‰ˆˆˆˆˆˆˆ‰‰‰ŠŠŠŠŠŠŠŠŠŠ‹Š‡}oedfmsvqjggjqx}{xy|ƒ‡ŠŒŒŒŒŽ‘‘‘Š„~uoqz‚†„vnjntyxokjijow‡‰‰‰‰ŠŠŠŠxxxxwwwwwwvvwwvvvwyy{{{|}~~~~}}||||}}}}}}}}~~~~~~~~}}~~}}}}|}~~~~~~}}~~}||{zzzyxwxyz{||||}€…‰‹ŒŽ‘Ž‹ŠŠŠ‡€zxy€‡‹ŒŒŒ‹‹ŠŠ‹Ž‘‹‰‰‰ŠŒ‘’’”•–——”’‘‘ŽŽŽŽŒŒŒŒ‹‹Œ‹‹ŠŠ‰ˆ††‡‡‡‡††††††‡‡†„ƒ€~€‚…†‡ˆˆ‡ˆˆˆ‰‰‰‰‰‰‰‰‰‰ˆˆ‰‰ˆˆˆˆˆˆˆ‰ˆˆ‰‰‰‰ŠŠŠŠŠŠŠ‹Š…wlfdgpwvqlgjow‚ƒƒ…ˆŠŽŽŽŽ‘‘‘‘Œ‡€vppx€„ƒyqklpvzuojhghnx†ˆ‰‰ŠŠŠŠyyxxwwwwwwvvxxxxvwz{|{|}}~~}}||~~}}}}}}~~~~~~€€~~}}~~}}|}€€~~~~~~}}~~}|{zzzyyxwxyz{{{||}€…‰ŒŒ‘‘‹ŠŠŠ†~xy}‚‰ŒŒŒŒŒŒ‹Š‰Š‹ŒŽ‹‰‰‰Š‘’‘“”•——•“’‘ŽŽŽŽŒ‹‹‹ŠŠŒŽŽŽŽ‹‹Š‰‡‡‡‡‡‡††††††‡‡‡…„€}„†‡ˆˆ‡ˆˆˆ‰‰ˆˆ‰‰‰‰‰‰ˆˆ‰‰ˆˆˆˆˆˆ†‡‡‡ˆˆˆˆ‰‰ŠŠŠŠ‰‹Šˆtkfcjtyvnkknt}‚†‡‹‹Œ’’““‘ŽŒ‡xpqwƒ„€yrmkmuzxskifeiq{ƒ…ˆ‰ŠŠŠŠxxwwwuwwwwwwxxxxvwz{|}}~~~~~}}~~}|||}}~~~~~~~~~~~~~~~~~}}~~~~}}}}}}}}|{{{{yyyxxxwyzz{{||†‰‹Œ‘‘‘Œ‹Šˆƒyvz†‹ŒŒ‹‹Š‰‰ŠŠˆˆ‰Œ‘‘“”•••••“’ŽŽŽŒ‹‹ŠŠ‹ŒŽŽŽŽ‹‰‰ˆˆˆ†††††††††‡„ƒ}}‚††ˆˆˆˆ‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‰‰ˆˆˆˆ‡‡†‡‡‡ˆˆˆˆ‰‰‰‰‰‰ŠŠŠŠ…|ricepy{vqmjnw†‹ŒŽ‘’““’‘ŽŒˆƒwppv~‚‚€|vqoouyzwoiggeis…‰‰‰‰‹‹xxwwxvxxwwwwxxxxwxyz|~}~~~~~}}~~}|||}}~~}}~~~~~~~~~~~~~}~~~~~}}}}}}}}|{{yyxxxxxxwyzz{{||‚‡ŠŒŒ‹‰†vuz€ˆŒ‹ŽŽ‹Šˆ‡‰‹ˆ‰ŠŒ’‘‘““•”•••““ŽŽŽŒ‹‹ŠŠ‹ŒŒŒŒŽŽŽŽ‹‹‰ˆˆ†††††††††‡„ƒ~||…†ˆˆˆˆ‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‰‰ˆˆˆˆ‡‡‡‡‡‡ˆˆˆˆˆˆ‰‰‰‰‰‰‰‰‡ƒ}qeagu{{wpikr{„‰‹Ž‘’““’‘ŽŒ‰ƒxppu}‚‚€~xttpswzztmhfcem{„‰‰‰‰ŠŠxxxxyxxxwwwwxxyywxz{|~}~~~~~}}~~}|||}}}}}}~~~~~~~~}|}}~~~}}}}}}}}|{{xxwwxwwwvxzz{{||‚‡ŠŒŽŽŒ‹ˆ…|uv|ƒ‰ŒŒŒ‹ŽŽŒŠˆ‡ˆŽŒŠŠ‹Œ’’“““”””•–•”‘ŽŒŒŒŠŠ‹ŒŒŒŽŽŽŒŒŠˆˆˆ††††††††††„‚~z|ƒ…ˆˆˆˆ‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‰‰ˆˆˆˆ‡‡‡‡‡‡ˆˆˆˆ‡‡ˆˆ‰‰ˆˆˆˆ‰‡„ylaaly~{skjnu~…Š‘’“”’‘ŽŒ‰„zrpt{„|xwrruy{voieccivˆ‰‰‰‰‰xxyyxwwwwwwwxxzzxyz{|~}~~~~~}}~~}|||}}||~~€€€€~~~~~~~~}|||~~~}}}}}}}}|{{xxxxwwwwvxzz{{||‚‡‹ŽŽŽŽŒ‹‡zvx€†‹ŒŒ‹ŽŽŒŠ‰‡ˆŒŽŽ‹ŒŠ‹Œ“””“““““”––•’ŽŒŒŒŠ‹Œ‹‹‹ŒŽŽŒŒŒ‹‰‰ˆˆˆˆˆ††††††„‚z|~…ˆˆˆˆ‰‰‰‰‰‰ˆˆˆˆˆˆˆˆ‰‰ˆˆˆˆ‡‡‡†††‡‡‡‡††‡‡‰‰ˆˆˆˆ‰‰†rc_ft}}wmjkq{ƒ‰‹‘’“•“‘ŽŒŠ…{uosz‚„‚|{vstwzwoiebbeq~‡ˆ‰‰‰‰yyzzyxxxxxwwxxyyyyy{{|~~}€€~~}}}}}}}}}}||}~€€~~~~}}}}}}}}~~~~}|||{zzyxxwuxxwwxyxz{|ƒˆ‹ŒŽŽŽ‹†}ww{ˆŒŒŒŽŽŽ‹Šˆ‰‹‹‹Œ’“”“““””••••’‘‘‘‘ŽŒ‹‹‹Š‹ŽŽŽŒ‹Š‰‰ˆˆˆˆ††…††††††ƒ|{|‚…‡‡‰‰‰‰‰‰ˆˆˆˆˆ‰‰‰‰ŠŠ‰‰‡‡††††††††††††‡‡ˆˆˆˆ‰‰ŠŠ‰†yh]]hx|{smnnt~†‹Œ’””•’ŽŒŠ†~wqry„ƒ‚€€zwsvxwqiedbem}†ˆ‰‡ˆˆzzzzyxxxxxxxyyyyzyyz{|~~}}}}}}}}}}}}}}~~~~~}}}}}}}}~~~~}|||zzzyxxxvwwwwwwxzz{~ƒˆ‹ŒŽŽŽŒ‰ƒwty}‚ˆŒŒŒŒŽŒ‹‰Š‹Ž‘ŒŒ’“”“““““••••’‘’’’‘ŽŽŽŽ‹ŒŒŠŒŽŒŠ‹Š‰ˆˆˆˆˆ††…†‡‡‡‡†„€}{|„†‡‰‰‰‰‰‰ˆˆˆˆˆˆˆ‰‰‰‰ˆˆ‡‡††††††††††‡‡‡‡ˆˆˆˆ‰‰ŠŠŠˆ€qd\akwyztpnqy‚‡ŒŽ“–••’‘ŒŠ†€xrsx}‚ƒ€‚€{tuwwtmgebel{„ˆ‰‡ˆˆzzzzyxxxxxxxyyyyzyy{{|~~}€€~~}}}}}}}}}}}~~~~~~~}}}}}}}}~~~~|}||zyyyxxwuvvvvvwxzz{~ƒˆ‹ŒŽŽŽŽ‹†uuy„‰ŒŒŒŒŒ‹Œ‹ŠŠŒŽ‘‘Ž’“”“““““””””’‘’“’‘ŽŽŽŒŒŒŒŽŒŠŠŠ‰ˆˆˆ‡‡††…†‡‡‡‡†…‚~}€ƒ…‡‰‰‰‰‰‰ˆˆˆˆˆˆˆ‰‰ˆˆˆˆ‡‡††††††‡‡‡‡‡‡‡‡ˆˆˆˆ‰‰ŠŠŠ‰†|l^\`jr{{uont~„‹‘“•—–•“‘ŒŠ†ƒzuru{€‚‚†…yvwxwqjifejx‡ˆ†‡‡zzzzyxxxxxyyzz{{{{|{{|~~}€€€€}}}}}}}}~~}~€€}}~~~~}}}}}}}}}}}}|}||zyyxxxutvvuuuvwyz{}‚ˆ‹ŒŽŽŽŽŒŒŒ‹„{sv|‚†ŠŒŒŒ‹‹‹Œ‹‹‹Œ‘‘‘ŽŽ’“”“““’’““““‘‘“’‘ŽŽŒŒ‹ŽŠŠŠ‰ˆˆˆ††††…†‡‡‡‡‡†„‚€~~€‚…‡‰‰‰‰‰‰ˆˆˆˆˆ‡‡‰‰ˆˆ‡‡‡‡††††††ˆˆˆˆ‡‡‡‡ˆˆˆˆ‰‰ŠŠŠ‹‰scZZ`ly}wrmqz‚Š“•—˜˜–“‘ŒŠ†ƒ{urtz‚‚ƒ‡‰„}xvxwslkhehv€†‡†‡‡{{yyxxxxyyyyyyzzz||||}}}}~~}}}}}}~~}}~€~~~~~~~~~~~}}}|||||||{zywxxwuuvvuuvvxyy{}‚‡ŠŒŒ‰ŠŒˆ‚xtx~ƒ‡ŠŒŒŒ‹Š‹ŒŽŽŒ‹ŒŒ’’“‘‘’“”““’’“’““‘’’‘‘‘ŽŽŒŽŒ‹‹‰ˆŠ‰‡‡††„„……†‡††‡††„€€‚‚†ˆ‰‰ˆˆˆˆˆˆˆˆˆˆˆˆˆˆ‡‡‡†††††††‡‡ˆˆˆˆˆˆˆˆ‰‰‰ŠŠŠ‰Š‹ˆ{g\Y[ew~ytpnu~‡“”—˜—–”’‹‡„|vqsv|‚„‡‹‰wvxvsnmiefr|…†‡††{{yyxxxxyyyyyyzz{{|}}}}}}€€~~~~~~~~~~}}€~~~~~~~}}}||||||{zzzwxxwuuvvuuuvxxy{~‚‡Š‹Œ‹‰‰‰ŠŒˆ€wv{„ˆŠŒŒŒŒ‹ŒŽŽŽŒŒŽ‘’’“’‘‘’“”“““““’““ŽŽ’’‘‘‘ŽŽŽŒŒ‹Š‰ˆˆ‡‡‡‡†„„………‡††‡††„ƒ€‚‚…ˆ‰‰ˆˆˆˆˆˆˆˆˆˆˆˆˆˆ‡‡‡†‡‡‡‡‡‡‡‡ˆˆˆˆˆˆˆˆ‰‰‰ŠŠŠ‰ŠŠƒp_WW^mx{wropx‚ŠŽ‘•—˜˜–”’ŽŒŠˆ…~wspsy€‚‡ŒŒ…~yxvtpmjfemw‚…†……{{yyxxxxyyzz{{zz{|}}}}}}}€€}}€~~~~~~~~~}}}||||||{zzywxwvuuvvuuvwwxxz}†‰‹Œ‹‰ˆˆ‰ŠŒˆ~vx}ƒ†ˆŠ‹ŒŒŒŒ‹ŽŽŽŽŒŒŽ’‘‘’’“““““’‘’’ŽŽ’’‘‘‘‘‘ŽŽŽŽŽŒ‹‹Š‰‡††‡‡‡†„„……„†††‡††……„ƒ‚‚‚„‡ˆˆˆˆˆˆˆˆ‡‡‡‡ˆˆˆˆ‡‡‡†‡‡‡‡‡‡‡‡ˆˆˆˆˆˆˆˆ‰‰‰ŠŠŠ‰Š‹Œ‰yf[VYdu}}xsos~†‘•———–”’ŽŒ‰ˆƒysmow|~€†ŒŽŠƒ{wvuqnkgfit„„„„{{yyxxxxyyzz{{zz{|||}}}}}€€~~}}€~~~~~~~~~}}}|||||||{{zwwvuuuvvuuwwwwxz}…ˆŠ‹Š‰ˆˆ‰ŠŒˆ~vx…‡ˆŠŠ‹ŒŒŒ‹ŽŽŒŒŽ‘’‘‘‘’’’““““’‘‘’’‘‘‘ŽŽŽŒ‹ŠŠ‰‡‡‡‡‡‡†„„……„…††‡†‡…„ƒƒƒ‚‚„†‡‡ˆˆˆˆˆˆ††††ˆˆˆˆ‡‡‡†‡‡‡‡‡‡‡‡ˆˆˆˆˆˆˆˆ‰‰‰ŠŠŠ‰Š‰ŒŠl^WV`s|{voq{…“–˜——–”’‹ˆ‰ƒ€yrmptz|}†‹Ž†|wvuqnkgehq}„„…… \ No newline at end of file diff --git a/media/test/data/bali_640x360_RGB24.rgb b/media/test/data/bali_640x360_RGB24.rgb new file mode 100644 index 0000000000..99ebd1e246 --- /dev/null +++ b/media/test/data/bali_640x360_RGB24.rgb @@ -0,0 +1,1107 @@ +>M?NAQ!DT$GT'GT'GU*HV+HV-JX/JX/JX/HX/IY0K]1IZ.I\2H[1H[1H[1I\2I\2HX6HX6GV8IX:GU:GU:DS:CR9BQ8@P7=N7;L5/5>/5:+5:+08"29#294: 4=090;0;0:0:0:1;1<1<0;1<0=0=/<.;.:0=/;-9*8+9+8)6)6)6'3'3'.&,&,&,%*%*"'"'''%%#!##    ! !   ! ! """"""""!!         !"! "!$%&&'*'*).*/*1!,3!,3!,3",6",6!.7!.7!.7!.7!.7!.7!-9!-9".:".:!.7!.7!.7!.7".:".: ,8 ,8!,3 +2&*1',2-+00/498/A@7ML.UT6ad1jm;xy9}~>‰:…Ž?Š’<‹“>‰’Aˆ‘@…@ˆ<|‚:x~7xy9xy9wz3x|4x€4{‚6}†5}†5zŠ3zŠ3xˆ8xˆ8u†At…@s„DrƒCn„Jq‡LrˆNu‹QxO{”R~šL~šL‚™L‚™L•M}Ix…Jq~CgpA[d5LQ3=B$). "'" %%''(&%%%")++$222?2ANAPc>\pKk‚CwŽO‚œH‡¡N§KŽ¦I‹¥FŠ¤Dˆ™G’@v‚)3C.4H.4H.=N0BS5H_5Pg=WpAazKi‚Ln‡QuŽQwSz‘T|“V™R™RƒšV„›X„™[„™[…š]…š]ƒ—[ƒ—[ƒ—]ƒ—]€—X€—X€“Y~’X|ŽYzŒWyXt‰Sp„Vp„Vq‡_t‹cyŽjzlzq{‘r”vƒ—x†•w†•w†’uƒr|Šlyˆjw†fu„ds_r€^r€^x…c‚Žj‡“o‰–oˆ•n…“j…“j‡‘m„Žj†l{gqy\fmPY[BIK1:<"/1*'(&!% #'#"*'$1,*72,D28P>DaFMkO[yPe‚Yl‹Yq]~–\€™^œS€›Rƒ›Sƒ›S…œY…œY„œV„œVƒšO‚˜N€›E~™C|˜=x•:y“@y“@{’N{’Ny\•b–j…›n…™z‡š|‰š|‰š|Š™yŠ™yŠšwŠšw‡—u†–t†•u†•u‰•z‰•z‰“}‡‘{††x~~pwolia^UQWKGL??E;;B36A/2=+36)25@ODS BR"EV%GT'GT'GU*HV+HV-JX/JX/JX/HX/IY0J[0IZ.K]4I\2H[1H[1I\2I\2HX6HX6HW9HW9GU:GU:DS:CR9BQ8@P7=N7;L5;K69I4;G7;G77C43?/5>/3=.49*38)29#3:$5;!29092;2=2=2<2<0:1;1<1<1<2=0=0=/<.;.:.:.:.:+9*8*7*7)6)6'3'3'.&,&,&,%*%*"'"'''%%$"##    ! ! ! !   ! ! """"""""!!         !""!%$&&'(&)'*).*/*1!,3"-4"-4",6",6!.7!.7!.7!.7!.7!.7!-9!-9".:".:!.7!.7!.7!.7".:".:!-9!-9#.6"-4',2(-3205427@?6HG>UT6_]@hk8qtB€A…†FŠ’C‹“E˜B˜B—E‹”C„Œ?ˆ<{9x~7|}=|}=}€9~:~†9~†9‰7‰7zŠ3zŠ3v…6u„4o€;n:l}=l}=g}Cg}Cl‚Hp…KuŽLy‘P~šLP„œO‚™L„—P„—P}‹Py†KoyJcl=TZ;EJ,38)', %%''''&&%&'(!...<.;I;K^9XkFjBuN€šG…ŸKŽ¦I§KŽ§H¦GžK†—D~ŠDt€:bk7RZ':?#,1#"$&'+//3096?;E?J$EP*NY3Ve7`pBj{Dt„M’T„—X‰›RˆšP˜O‡’Iƒ‰H{@os9dg.V],QX'KT*S]2ekEu{VtmLaZ9PO6EE,<8)40!!! #%'12<&;H!CP*LY,S`3^i5ep%1>%/<#,9 ,:,:)5'3'3%2$0".!+!+, . 0&6-A5I!E\Ne&Ywd‚)m‹+r‘1z—.€œ4„9…ž:ƒ—@ƒ—@‘A€@zƒ@py5hk4Z]&NK$@=.-('!  !$!"#$$"'%'%*( .'"/)$2+%3,)6+,9.0A,2B-2F,5J0>O1CT6Kc8Rj?YrCazKi‚Ln‡QvRz“V~”X€—Z‚œV‚œV‡Z‡Z‰ž_‰ž_ŠžbŠžb‰a‰a‡š`‡š`‚š[€—X€“Y~’X}Z|ŽYuŠTq…Pn‚Sn‚Sn…]rˆ`sˆev‹hwŒn{‘r~’s€“u„’u„’u†’uƒr}‹nz‰kx‡gv…ex…cw„bx…c~‹iˆ”p‰•q‰–oˆ•nˆ–m„’i†lƒi~…kw~dnuYahLRT;BD*57-/*'(&"&"&'#"*'$1,*72*B06O=A_CMkO[yPe‚Yl‹Yq]~–\€™^œS€›R™Qƒ›SƒšVƒšVƒ›U‚™TƒšO–L}˜Ay”>y–;v’7u;u;vJyLwY}“_–j…›n‡š|‰œ~‰š|‰š|‹šz‹šz‹›x‹›xˆ™v‡—u‡—v‡—vŠ–{Š–{‰“}‡‘{‡‡y}}oumjg_\TPUJFK@@G<M6;K6;K69G77F67C46B23?/1=.7<.5:+4:'39&2:"09 072:1:091<1<0:1;1;2<1<1<1<1<0=0=/</</;/;.:.:+9+9*7*7)4)4&1&1'/&.%+%+$($("'"''%&%%#!!      ! ! ! ! ! ! ""# # """"""####! ""!!    "#$#$%(&))+)+).*/*1*1"-4"-4",6",6!.7!.7!.7!.7 -6 -6!-9!-9!-9!-9!,:!,:!,:!,:!-9".:!-9!-9!,3!,3(-3,06570;=6DF1MO:^Z6heAop7xy@…B†‹HŒ”G–J—H˜IŠ•F‡‘B~Cwˆ^i;?@F BH#EO"IT'R^3\i>`v>lIyŒN„—X‹X‹X‹V‡˜Q…‘K}‰Cpz;gq2[a.TZ'JW)[h;^pIqƒ\f†]^~USkEZrL~„ew}^A>'!  # ,&21<;EDPKW%T_)]h2co6hu†šE…™D†–F€@}†Bs|9nr8_c)QM*C?/0()! !"$ '%(&(&*(-& .'$2+%3,,:,-;-/@)0A*7H*=N0BU+I\2Rg6Wm<`vAe|Fl…MpŠRxV}•Z‚™\ƒš]ƒœZƒœZˆž]‰Ÿ^ŠžbŠžb‹žg‰œdˆšcˆšc‡™b‰œd•`“^|‘[{ZyŒYu‰Vp‡Qn„Ng€Qg€Qb€Wg…\h„`lˆctŠkwŒn}‘r}‘r„’u„’u†’up|‹k|‹kxˆfv†cw‰aw‰axŠc‘j‰•qŠ–rŠ—q‰–o„’i‡”k…k€Šf|„et|]lsR\cBJT.7B-5(0)(%')"-"#2')8,/C0:O;D_?OiI[vOe€ZnŠZs_}•Z—]šVšV˜R‚™T‚™T‚™T™Q™Q€˜K~–I{•?y”>v’7t5pŠ6pŠ6rˆEuŒHuŠY}’a’r†˜x…˜|…˜|Šš~Šš~œ|œ|ŠšwŠšwŠšwŠšwŠ™yŠ™yŠ–{Š–{Š”~ˆ’|††x{|nrjfbZWPJTJDN<>K8;H.4E+1B#0:"/8DQ$ER%CQ(DR)IT+GS*HU.IV/IV/IV/JW0JW0IY0IY0IW5JX6IY9HX8HX8HX8GV8GV8FU7FU7FT8FT8ER9ER9@O8?N7>M6>M6:J5:J57F66E56B25A13?/1=.6;-5:+39&28%2:"09 1919/8/81<1<1;2<2<2<1<1<0;2=0=0=/</</;/;.:.:+9+9*7*7'3&2&1&1'/&.%+%+"'"'"'"' ( ('&%#""  !!  ! ! ! ! ! ! ""# # """"""####! ""!!!   !"#$%%(&)'*'*(-*/*1*1)0 +2",6",6!.7!.7!.7!.7 -6 -6!-9!-9!-9!-9!,:!,:!,:!,:#/;".:!-9!-9!,3!,3*/5*/5691<>7GJ5PS>c_;heAtu<{|Cƒ‡D‰J•I—K—H–Gˆ“C„@{‹@u…:s;p8p8o~7n.n.o„*l'l‚%k$jƒ$g!ey"bw\o!YmPg#Pg#Oh+Tm0Zr8^wsGvƒJyˆO~ŒS€’XW~V|Sx‰Px‰Px‰RyŠSyˆVyˆVw…UqOhtI_k@O\4DR):H&8E#0A#0A#,A%,A%/B&/B&.A%.A%,A%,A%/?$.>".=$.=$-<#,;",:,:)5'3'3%2".".) ***!1'7,@4H ?XKd'Vu b€,o*t“/{—0š4—6ƒ›9ˆ›F†šE‡—G‘AˆDw€'4E.czDh~InˆOsŒTz“X~–\ƒš]…›_‡Ÿ^‡Ÿ^Š _‹¡`Œ¡d‹ c‹žg‹žgˆšcˆšc…˜aƒ–^€”_~’\{ZxŒWtˆUr…RlƒMjKfPfPaVaVe\gƒ_n„ewŒn}‘r}‘rƒ‘sƒ‘sƒrp|‹k|‹kz‹hxˆfy‹dy‹d|f‘j‰•q‰•q‰–oˆ•n‡”k„’i„Žj~ˆdx€bpxYhnMW^=DN(4>*2&."+#,'')!,! 0$&6*.B/:O;B\=LgGYsMc~Wm‰Ys_}•Z~–\˜T˜T€—Q€—Q˜R€—Q€˜P}–M~–I{’Fx“=v‘:r4nŠ/m‡3m‡3m„@rˆEr‡Vx\}n„–u„—{…˜|‰™}‰™}Œ›{Œ›{ŠšwŠšwŠšwŠšwŠ™yŠ™y˜}˜}Œ•†y††x{|npheaYVNISFAK913_b6gj>tu<|}DŠ@„ŽC†”Bˆ–D€”C|‘@{‘DwŒ@q†Ak;f}9by6cz0cz0bv%bv%`w`wbybyf|cy`s\pSjPhE_D^EY+I^/Mb8Uj@^wAi‚Ly‘P€˜W‰ŸU‡žT‹ S‹ S‹œP†—KMx…EqyL^f:MR6>C',3&"("!##&&'''(!&',"0.*968L2H\BZn?h|MwN‚—XŒ¡\¥_¦\¤ZŽ¢Y‰žT„‘O~‹Jt~Edm5RY1@F+3 '  !(*-//3599>=BAHBJAL@KBL"FP%LY%Ta-_m7kyBrƒL}V†—^‹œcŸ_›[ˆ—U€M|†Gq{gtNZq]bye:ZS(H@2MVMgqZqwJagLQCRWILE1" # + !))14=8A AK HR'T^*Xc/_k3eq9gt;myAo€Gv‡N|‹W}ŒXZZ~ŽW{‹TyŠSyŠSyŠSyŠS{ŠUy‰TuƒSp~NlvL_i?Q\4EP('->'0@&0@&0@&0@&0@&0@&/>%/>%,=&,=&+<$(9"'8!'8!'5%3%2$0#."-"- +**/&6*?1F>ZGc&Tu ^€+i‹(r“0z˜2{š3€›7‚9ˆ›B†™?…•E…•E„ŒK}…Dqv>di2WS/HD 64 .,%##! " #!&$'%)'+(*&,(%2''4)*6'0<-6G);L.FS1JX6Rc9[kBas>ewBl€Dr†JvŠP|V”Z„˜^ˆœ`‰a‹ c‹ cŒ¡gŒ¡gŠ fŠ f‰h‰h‡›f†še…—b‚”_€‘^~]uŠWrˆUn…TiOc{NazM^yL^yL[yP^|Rb{Yh_rˆiwŒn|o|ottq}Šo~Žl|ŒixcxcxezŽf‚’k†•n‰–o‰–o‡”k…“j†‘k„h…‹j†ew}\ouTejGRW4?E%29)0&- ) )(((!,! 1%%7+.C+:P8D_=PkI[vOdXoŠVsŽYz•X|—Z—V—V€—Q~–P˜R˜R~˜M|•Ky’Cw@w8v7p‡4kƒ/k€6k€6l‚Ap†Er‡VyŽ]}Œlƒ’r…˜|…˜|Š˜zŠ˜z‹šz‹šzˆ˜xˆ˜xˆ—yˆ—y‹˜}‹˜}‹˜Š–~Ž”ˆŽ{…†vz{kkf`\WQJDNC=G<:L97J-6I)3F'2@%0>ER%ER%FR)EP(GQ-GQ-HT0HT0IS4IS4JU5JU5IT3IT3JU5LW8JV9IU8HS8HS8GV8GV8FT8FT8FS:ER9EQ:BO8?M9=K6=K6=K67H65G43C52B45@33>13‚>zŽ>v‹:r‡;o„8f{5bw2[r/Vl)Vm#Vm#YnYn\s\s[r_u_u]qZnMeKb?YYm>ezKuŠL€•VŠŸYŽ¤^¦\¤ZŽ¢Y‹ŸU‰–T‚Mz„Kis;X_7FL$.5#+!!""(,.12476:eo6`j2anHhuO[r^TkW8XQ+KD1KUMgqAX]3JOHM>QVGd]JaYF_PE) &)108"9B!?H'HR'OY.Xc/_j6`m4eq9lx@q}Dr„Jx‰P|‹W}ŒXZZ}V}VyŠSyŠSyŠSyŠSxˆSw‡Rv„TqOlvLak@P[2DO';E&9C$.=$.=$->'->'/>%/>%/>%/>%/>%/>%/>%/>%,=&+<$):#(9"'8!'8!'5%3%2$0#.$0"-"-))."2(<0DO1DU7N\9Ta?Yj@_pFfxCl~Ip…Iv‹N{ŽU~’X„˜^‡š`ˆœ`ŠžbŠžb‹ cŒ¡gŒ¡gŒ¡gŒ¡gŠŸiˆœg†šeƒ˜b‚”_“^{ŒYyŠWrˆUp†Rh€Nc{J^vI]uHXsFXsFWuKZxO`yWg€^rˆiwŒn|o|o€Žsq~Œp|‰n|Œi|Œi}’h{f{g|hƒ“l‡–oŠ—q‰–o‡”k…“j‰“m„h‚‰h}ƒbv|[krQ`eBOT1;B!18(/$,( )(*!,!!,! 1%%7+/E-:P8Ea?PkI[vOdXmˆTsŽYx”Vz•X€–T€–T€—Q€—Q~–P|”N{”Iz“Hy’Cu>p‰1p‰1m…2i€-i}3i}3h~=mƒBlQuŠY}Œlƒ’r‚•x…˜|Š˜zŠ˜zŠ™yŠ™yˆ˜xˆ˜x‡–x‡–xŠ—|‹˜}‹˜‹˜‹’~ˆŽ{„„tyzjlga]XRJDND>H<:L86I-6I)3F(3A)4CFR'FR'FR)IT+IU*IU*GT-IV/IT3IT3JU4JU4JV2JV2LX6MY7IU8IU8HS8HS8FS:FS:EQ:EQ:BR=AQ<>N;>N;>J;>J;fxJpSzŽ[•b‰™bŠšc‡™]‘U}ˆMs}Cen?`i:[dNKU?IZU:KF "%-(00:8BCMKU$O]&Uc,]i1`m4cm:hs?iyBn~GpƒLs…Nv…RyˆV}ŒY}ŒY}ŒX|‹Wy‰TxˆSxˆSy‰T{‰W{‰Ww„WrRmvNajBNX4EO+9G%4B -> -> ,>$,>$/>'/>'/>'/>'/>'/>'.<&.<&):#):#+<$(9"%6%6(7 &4%2$0#.#.",!+( )*"0%9-A ;TE^(Qr$Z{-iŒ+n‘1x—.z™0}™7›9†›CˆœE‡•J…“H…ŽJ€‰Fv{Bjp6ZX0NK#>;"40+'&"   ! ""$ %!&' + /#$3!(8%0@%7G,BM,IT3L[0Ra5Zh8`n>fv?iyBo€Gu†LwˆO{ŒR€“Y„˜^ˆ›aŠždŠŸaŠŸaŠŸa‹ b£g£gŒŸlŠžk‰›m†˜i‚”f‘b|Ž`zŒ^u†[q‚VgTe|R_zM]xJVyFQuAMs?Nt@UuKWwN_zXf_r†lw‹qzo|q€o~m~m~m{jzŒi{’hz‘g|‘g~“i†–m‰™p‹™p‰—nˆ—i‡–h‡“j„g‚Šf{ƒ_t}WfoI]a9LP):>26(/#+ ) )'(,!/!$6#*<)2I.=S9Ea=OkF]yGfƒQn‹Pu’Wx—Px—P~˜M}–L~˜K}—J€—M|“HyŽKyŽKuŒAo†;m‚8l7i|7hz5`w3`w3`vM7;O5:N.7J,5H)4D(2CEQ&DP%GS*GS*IU*GT)JW0HU.IT3IT3JU4JU4JV2JV2IT3JU4IU8IU8HS8HS8FS:FS:EQ:EQ:AQ<>O:>N;>N;>J;=I9=I9;G78D66A45?44>33kx4n|8j0h~-f~/d}.c|-ay*Wp,Tm)Rf#Nc Pd!Nc SbTcVdVd]n]n^t ^t _r \oRgOc BU;N .B(<%7':.C)9M3Lb5XnAg€>q‰H€—M„›PˆSŒ VŒ›TŒ›TŠ•X…‘T|†Ut~MfnKY`=@G108"!)&$! %#'''&''%&'(#..,<.:J"4/'<@#9<+3/734!78+?,/C0BS'M^2_dATY6 + +# (+32:"8BBK(KU$Q[*We/Zh1_k3co6fq=jtAgw@k|EnIpƒLuƒQw†T|ŠX|ŠX}ŒX|‹WxˆSw‡RxˆSw‡Rx‡Ux‡Uw„Wq~QjsK^g?MW3CM)7D"2@-> -> +<#+<#.<&.<&.<&.<&.<&.<&-;$-;$):#(9"'8!&7 %6%6'5%3%2$0#.#.!+ * )(*!/&:,@8QF_)Pq#Z{-gŠ*n‘1z™0y˜/{˜6€;†›C†›C‡•J‡•Jˆ‘N€‰Fw}Ckq7`^5RP'=:!40,( '#    ! ""%!&"' +$ 0$$3(,;)2A/7G,>N3JU4OZ8Tc8Zi>bp@hvFn~Gq‚Ku†Lx‰PW‚“Y„˜^‡š`ŠždŠžd‰ž_ŠŸa‹ b‹ bŒ¢fŒ¢fŒŸlŠžk‡™kƒ•g€’d|Ž`xŠ[s…WnTl}QczO`xM[uHWrENq>KoLS8;A'*0$"( !$$$"&&%%$'!.0+7.6B:IX?WfMjxUw„b‚”_‰›fŒ _¡`Œ _‹ž^‰š\‡—Z‚‹Uw€KkrA]d3KO(:>/5.4285<:>>BDJFL FO FO GP!GP!HR HR HR GQHO%KS)MY0S^6Yj?ctHl€TwŠ^“^•`…•^€‘Zw‡Rn}IcoF_jBQV?RX@RUM<>7.15#&*5;kyBlzDhyGk|In‚Or…Rv‡Vy‰YyŠWyŠWs…Ns…Nw‡Pv†Ov…Rv…Rt€UnzOiuL]i@KW3AM)3D!/?)< (;'<"'<",9%,9%*8$,9%,9%,9%,9%,9%*8$)7"(6!(6!%5 $4%4"2%1#.#.#. -,,)* -)9.>"4O@Z&Ro&Zw.g†*p4w–4x—5š6œ8ƒ›Aƒ›Aƒ–Jƒ–J‚K|ŠFu|Djr:bc7ST(?@#67*( %$!!!!"" " $' &"&"!,!&0%.7$3<(Jn:Go6Iq8Nu?SyCWwN\|Rb{Yi‚`tˆgyl{Œl|Žm{‹o{‹ozŠn}qzl{m|–k{•j€—m‚™oŠœnŒo‰šn‡˜l‹–o‡’l‰’l†ŽhŠby‚ZuxLhk?Y\0HL 8=39-2).$*%+*!,.$2"*<'0B-7M,BY8Ng>WpH_xIg€QuŒTy‘Y|–R|–R‚˜N€—M˜I~—G{FyŽDt†ErƒCq‚;k|5gy0bt*\m-\m-Zi5Zi5XnAawJd{YlƒbrŠm|“v†–{†–{Œ™wŠ˜v‡™r‡™r†—r†—r‡–xŠ˜z‹˜}‹˜}™ƒŒ˜‚Š”~…Žx‚rwxhmf``YSMFUE>M8:4<85965964:04:03:-4;.3;*2:)3:$3:$3;#3;#2: 2: 4=4=2<2<2<2<2<2<2?2?2?2?0=0=0<0<.9.9-7-7-7)3)2)2(/'.'.'.&,&,'.'.&,&,%+%+#*#* ( ( ' '%%%%##!!!!!!!! !!!!!  """"# # $$%%%%%%$!$!$!$!%%$$###"!!!!!!      " "! "!$!$"&"&$*$*%+%+'-)0)0*1*8*8 +9 +9*8*8*8*8+4+4*3*3(1(1'0'0'0'0*- ,/'2,,71:?/BH7FV-N^5Te,Sd+Rg)Vk,[n'[n']l,\j+\j,We(Vd&Ta$TdTdZg[h^j^j`qar[r Zq Tl QiFZ?S .E&< .+&,!#3%-=/BCG EKEKFO FO FO FO HR HR HR HR HO%HO%DO'JU-Pa5[l@dxLo‚VxŒW“^…•^x‰RhwCk{Fq}TcoF[aI[aIWYRGIB9<@69=LSTNUV!2'<&'@#;2@:I$HK6! !#(+208"9B!BK)IS)PZ0Zb1`h6_k3co6eq9gt;es=iw@ft>ft>duBgxFi|ImNsƒSv‡Vv‡Uv‡Us…Ns…Nw‡Pv†Ow†Tw†Tt€UnzOfqIZe=JV2@L(3D!/?':':%9&: ,9%,9%*8$,9%,9%,9%,9%,9%*8$)7"*8$*8$%5 $4%4"2%1#.#.#.,**)*!.'7-=!4O@Z&Ol#[x/g†*p4w–4x—5š6œ8ƒ›Aƒ›Aƒ–Jƒ–J‚K{‰DwGnu=cd8TU)CD&67+*!#! " "!!!!!"#!$"&("($ +')4).8.9B.?H4GQ2KV6Ra5Xg;]o:dvAlzDqHvƒJ|‰PŽSƒU„’Y†•\„—W†šY‡œ[‡œ[‰ž_‰ž_‡žaˆŸb‰žg‰žg‹hŠœg‡˜l…–j‘h|cu†\qXexNasJZrGVmCOn@Ji;Hk8Hk8Hp7Mt(4G2;R1F\;Oh@XqId}Mk„Ts‹Sy‘Y|–R{”Q€—M–L}•Fy’CyŽDvŠ@pAl}=hz3ev/bt*]o%Yj)Yj)Ue0Xg3Wl@bxLbxWlƒbs‹n|“v…•z†–{Œ™wŠ˜v‡™r‡™r†—r†—r†•wˆ—yŒš~‹˜}Œ˜‚Œ˜‚‰“}‚Œv‚rwxhmf`aZTNGVG@O9=S48N/8M-5K'2G%0EIU*IU*JV+JV+JV+JV+IW.IW.IV/IV/JV2JV2HT7IU8IT:IT:EU:EU:ET;ET;AR:AR:@P;@P;5>4=4=4=4=2<2<0<0<2=2=0<0</:.9/8/8-5-5,3+2)0(/'.'.(/(/'.'.&,&,&,%*"(%*$+$+")")")")'%&&%%""!!!!!!####!!  !!!!""# # $$& & $%& & $!# $!$!%%$$$#$#!!!! " "!! # #!$!$$'$'#((-(-).*1)0)3)3*4*4*5*5*5*5(1(1*3*3(1(1'0&/%.%.(*+.#/-'422<-9C4FN/KS4T_0U`1Vb*[g._l*_l*bn*al)`j)\g%\f'Zd%[g#[g#]i]i`k`kao`n[pXlMdH_>Q4G%70%#"%!,%'5.6G0BS-(.$! ! $$%%&&&&-#2/+:7C=A?DCHFK"HN HN HQ"HQ"IR#IR#IR#IR#GP!GP!@K%@K%;K$>M&@U-L`9XoGdzRq‡[wa|cqXguS`nLerR[hH^jV]iUW_[KSO39:-345=9*2/$+##/++ 0/A5GelB-5 +""#$%-,42;9B!DJ%KQ,V],[c2\h/`m4bl8ep"+9&6!&6!$6#$6#)6$*7%,8&*7%*8$*8$*8$*8$)7")7"(6!(6!'4"%1'2%1#.#.#.", )!**), -$7)=5O>X'Ml%Xw0e†*m3w–4x—5—:€˜;–C‚—Dƒ—Iƒ—I‚‘JI{ƒIpx>hihrCnzDs~Hv…JzŠN~R‘U…•W‡—Z…™V…™VšV‚›X‡œ[ˆž]‡œ]ˆ^ŠeŠe†œi†œi‚œj›i–j|’e{‹bzŠaqXfvM_tJYnDMi>KgX[/HL ;?5926/3)0&,"-%1'7.>"2I'9P.E\4Lc;Vo?\uEgKo‡TyQ|”U~˜P{–M–L~•K~”L{‘IwˆHs„Dl}=iz:`s4]p2Zh1We/P]/N[.G\4K_7PeI\qT^u_ikvŒr}“y„˜x…šyˆ™t‡˜s‡™r…—o…–q†—r†•wˆ—yŠ–~‹˜‹˜ˆ•|‡“v‚Žq‚‡mw{alj[^]MPHTKBN;:Q99O26L15K*3H)2GIU*IU*JV+JV+LX-LX-IW.IW.IV/IV/JV2JV2IU8JV9IT:IT:EU:EU:BQ8BQ8>O8>O8>O:>O:;L<;L<7I;5G97D;5A82=70<51;30:14914915)1.$+%,$0():-> 9L">P'fnCJR'""!$ "#() .62:";D"BK)KQ,RX3Ya/]d3`m4bn5do;do;dp@eqAdrD_l>[g>Ze=Tg=Vi?]oHbtLk|PnTt…Ru†Tt‡Os…Nu„Pu„PuƒSuƒSp{UjuOcnHXb".; &6!&6!#5"#5"(5#)6$)6$(5#(6!(6!(6!(6!'5 '5 &3&3&3!%1'2'2$/#.#.", )!*)()!.$7)=4N@Z(Nm&Yx1e†*m3w–4x—5—:€˜;–C‚—Dƒ—Iƒ—I…”MI|„Jqy?hiXqB`yJm…Qs‹W|”U–W™Q{–M–L–L}“KyHwˆHpAj{;ar2Yl-Uh*Tb+P^(LY,KX+CW/G\4NcFZoR^u_ikwŽs–{„˜x„˜xˆ™t‡˜s†˜q…—o…–q†—r†•wˆ—yŠ–~Š–~‡”{†“z†’up€„kw{alj[`_OPHTKBN;:Q87N15K04J)2G(1FIX#IX#KY)KY)MY.MY.KY0JX/KU/KU/KU1KU1JU5JU5ES7ES7FS:FS:?Q<>P;=M9=M9:K;8I89J98I85D;4C:2@90=70<52=73963964914914;.4;.5=.5=.2;'2;'2<$1;#4="4="4>!4>!3=3=4<4<4<4<4<4<0=0=.:.:.9.9/;.9-7-7,5,5,5+4)1)1(/(/(/(/'-'-'-%*'-'-%*','+'+%)%)$)"("(!'%%$$##""""%%#"!!!!""""""# # $!$!%%%%%%%& &"&"$!$!$!$!# "# # # "! ! ""!!!!!!$%% &!$!$"'#(#(%+&-'/%,)0*1*1*4*4)3)3(1&/(1'0'2$/'-&,!*",', */$--'011;+8B2EJ.IN2SX.X]3[e-`j2fn/fn/en+fp,en+cl)ak#ak#al!al!]m\lao_maoVhQd@W8O.@&8(%!#$"+),8(8D5HV;VdIdwDn‚OzO‚˜W…›S…›S…œJ„šI‚–O€”L|‰Nu‚GjuH\g:JR39B#'.!& ###$&&#"'"--&220?3@ODRbF`pTl€Tya„”dˆ˜hˆœ`ˆœ`…œY—T€“U~‘S{†Rs~Jiq@^e4S\(LU!FPFPDPGRGQHR HR HR IS"IS"IQ%HP$DM$@I!5@3>0<2>!/C$;N0K^;ZmJp~acrTbpTcqVanU\iP`kW]iUWbUT`R4>3"-"GK9PTA[^;;=HD\Y.`Y/NH9?6<5I!;O'Mg/^w?Ta-7D(( -,658>>D%EJ LQ'YZ(`a/ef4gh6gq@jtBfrCfrCfqDak>Ze=Ze=N`9L^6I^;La>QeDYmM`wOhWr…RtˆUv‡Nu†Lw…Nx†Ov‚Ps€NmzLivIalDT`7CN.7B#.:#(4&3&3$3!%4"&6!&6!&6!&6!'5 '5 '5 '5 &3!&3!&3!&3!%1#0"/"/", ) )",#*") (&*,4&;1K]_1MM+>>33/0')&("("("&"& &"'#*%,-227"9B!?H'HQ)OX0Xb1^h6cn8ht>r|BtDz†G|ˆI€O‚‘Q‚—Q‚—Q™Q€˜P€˜P€˜P‚™U‚™Uƒ™X„šY…š]…š]‡™b…˜a…–h…–h”jƒ•k”l~’k~jwˆcr„]l~WbuKWj@Nd8Ka4Ec3Hf6Jk8Mn8:69.7.7,80<1A9I'?S-G[6Pd8Yl@`sGh{Or…RyŒY{’U~”X€•R€•R–L~•K{HxŒDt‚EqAbu7[n0Qb/M_,EW/BT-:N-9M,9P5?V;D`IPlV\tbhoxŽv~”|‡šw‡šwŠšs‰™r‡•q‡•qˆ•u‡”t‰•zŠ–{Š•|Š•|‡–x†•w…‘r~‹k|ƒgrz]pm\d`OSKULDN?:O;6L34L01J*0H).GIX#IX#KY)KY)LX-LX-JX/IW.KU/KU/KU1KU1JU5JU5ES7ES7DQ8DQ8>P;!4>!3=3=4<4<4<4<4<4<0=0=0=/;/;0<.9-8-7-7,5,5,5+4)1)1)0)0(/(/(.'-'-(.'-'-',',&*&*%)%)%*$)"(!'%%%%##""##%%%#!!!!""""""# # $!$!%%%%%%%& &"&"$!$!$!$!# "# # # "! ! ""!!!!!!!$$%%"&"& &"'#($*%,#+%,&-'-'-'0'0'0'0&/&/'0&/$/$/&,%+!*",', */$--'012<-9C4GL0LQ5W\2[`7^h/`j2ho0ho0ir.ir.fp,dm*cn%cn%cn$cn$_n]maocq_n]lSfM`;S2I +&80 $#  !!" '%'3#3?/AO4O]B]p=k~Ku‹J~”S„šR…›S†L„šI„—P‚–O€Ry†KpzMdoBRZ;DL--4'#) !!"#&&##&&#..+:/8G;L\A[kPi|Ps‡[€`…–e‡›_‡›_…œY—Tƒ•W’T}ˆTvMryHfm<[d0RZ'JSHQFQGRGQHR IS"IS"IS"IS"JR&IQ%AJ";D3>/;*6-9)=5I*GZ7TgDkz\jy[jx]esXcpW^kSYdPU`LNZMGSF@K@lh=ng=e_5JP+?D(<-,E F`(q~Jq~J33$$54;:!>D%BH*PU+]b8cd1ef4kl:yzHmwFU_-KV'U`1bl?[e8Yd<`kCHZ3FX0BW4H]9NbAVjI_vNg~Vs‡Ts‡Tv‡Nu†Lx†Ov„Ms€Nr~Mo|OhuH_jBR]5BL-6A!*6 '3&3&3#2$3!%5 %5 $4$4&3&3&3&3%1%1#0#0"/"/!.!.#-!* ) )") (''*+4&;1KC.DM,LU4S\4\e=cm;jtBmyBr}Gz„J}ˆM‚ŽO…’R‡–V‡–V‚—Q€–P~—O~—O~—O™Q‚™U‚™U‚˜Wƒ™X…š]…š]…˜a„—`‚”f‚”f€“i’h{g{gyŠev‡bp‚ZgxQ]pFUh>Mc7Lb5Hf6Lj:Lm:Qr?OyAS}DSCV„EXƒNXƒN]T_ƒVl„`p‡cu‰js†hvˆgvˆgwŠgwŠgyl|o~“pƒ˜uƒŸtƒŸtˆ sˆ sŸoŠlŠ™mŠ™mŠ—qŠ—q‰‘m‡Žk€Š_x‚WrvJdh":9N:5J22K/0I+1I).GJY%JY%KY)KY)KW,KW,HV-HV-GV/GV/GU1FT0FU5FU5EU:DT8BQ8BQ8;M:;M:9J99J98G;7F:3C<2B;/?8,=5-;4-;41:41:42911804:04:06:.6:.4;-3:+4=)4=)3>%2<$4="4="3< 3< 3=3=5=5=2<2<4=4=0=0=2>2>0</;.9.9-6-6-5,4+2,3+1'.(/(/)0)0(.(.)/)/(.(.',','+&*&*&*%)$($(!%'% & &$$##%%%#""!!""""""""# # $!$!%%%%%%("("&"&"$!$!$"$"! "!"!"!! ! ""! !  "!""""" # #!$#(#(#(#(#*$+%,%,%.%.&/&/%.&/&/%.&,%+$(#'%*&+&)*-)2,-605>+>F3IP/PW6Y`1^d6bn1bn1hr,is-ju+ju+it*fr'eo)eo)ep(cn%_m!_m!cp!bo]lXfL^DV8G .> /& !!! $". '8*8H.GW>XfDesQt„T{‹[ƒ—T…™V‡›R…™OƒšN‚™L‚S}‹Nt~MkuC[b?LS05;*(. !!$$&&##%% ,,)602@9EVAUePexSp„^}Œaƒ’fƒ˜aƒ˜a…š]‚–Zƒ“\Y{‰Ww†TtKkvB`j2T^&LUIRGQGQHQ"JS$JS$JS$JS$IR#EO$AK ;D!5?,8%1#0"/&63C'DU7UfH^hKmwZgnZLS@bl[]hVR\JKUDLRA@E4KPBKPB?L3GT;Rc9\lCamB`lA`jD_iCWeCN\90E#8)F8U Mk4h…Nx‚N6A 68?BNN%QR)lp;x|GpwAjq;tzL†WKO(QU.FP,QZ7Zc;[dN,>O1BS5I\>UhIeuNn~Ws‡Tu‰VwˆOy‹Qx†Ow…NwƒQtOkyKhuH\h?P[2>I*3=(4%2"/"/!0 !0 #1!#1!!0 !0 #0#0%1%1%1%1%1"/!.!. -!.!*!* )(&(' ((*3&: 1K;V)Hg&Rq1c~3k‡;wŽ<{’A}”<˜@”A€•B•F‚–G…”M…”M~†Lx€Fmq>be3UW0EG 88#22.0"*-%,%,%,&.%0+5.;5B#>K&GS/R_2Vd6ao8ft>o}BtFz‡L}‹PƒP…’Rˆ˜Tˆ˜T‡˜Q†—Pƒ˜R‚—Q~”S~”S—V—V~–T—V€—X€—X‚—`‚—`‚•d”c€‘e€‘e}fzcxŒfv‰du„dp_k}VbtL]pFSeM"DR"M[+Ob/Wk8]p=dwDl€Ms‡TzS”Z~•R€–S‚—Q€–P€’O}Lx‰Is„Dl{Bds:Yh5P^,GP/@I(7C-2>(,:,.<.*A97NFC\TQjbazkm‡x|‘u–z‰–v‰–vŠ˜vˆ–t†“s†“s‡“vˆ”w‰•zŠ–{‰“}ˆ’|ƒ‘s€qŽn|‰j|…cu~\opYdeNVOPMEG=8H:4E34D02A+1B+1BJY%JY%KY)KY)KW,KW,HV-HV-GV/GV/GU1GU1FU5FU5CS7BR6?O5?O5:L9:L99J99J97F:5E90@9.>7+;4+;4-;4+92/82/821801804:04:06:.6:.4;-3:+4=)4=)4?&2<$4="4="3< 3< 3=3=5=5=2<2<4=4=0=0=/;/;.9.9.9.9-6-6-5,4+2,3+1+1,2+1(/(/)/)/+0+0(.(.',','+&*&*&*%)$($($( (' & & % %$$%%%###!!""""""""# # $!$!%%%%%%("("&"&"$!$!$""!! "!"!"!! ! ""! !  " " """""!" #!$!&!&"'#(#*$+%,%,%.%.%.%.$-%.%.$-%+#*$(#'%*&+'*",/*3-.719B.>F3MS3SY8]c5`g8bn1ep3jt.ku/lx-lx-ju+hs(hr,hr,do'cn%ao$`n#boboZiRaHZ?Q2A)8*%   " +#3%3B)@P7Q_=_mKm}Mv‡V€•Rƒ—T†šP…™O€˜K‚™L„’U€ŽPyƒQq{IbiFSZ7=C2-3"$  !!##&&##"#)*%3,0=7>O:O_J^qLj~X|‹_‚‘e‚—`…šb„™\•Y„”]€‘Z}ŒYw†Tu€LmxDeo6Zd,R[%OX"IS"IS"HQ"JS$JS$JS$IR#HQ"BL"=F6@0:'4".*) 0,< EV8arTr{^s|`lr_GM:U`NZdSNYGLVEIO>HM=JO@GK=I*3=(4%2!.!. / /...."/"/"/"/"/"/#0"/!.!. -, )'((&'&%&'2$8-H7Q$Ed$Po.d€4lˆ=wŽ<{’A•>•>€•B€•B•F‚–G„“L„“L~†LwEswDei6XY2JK$;;'44 /1#.0")/#)/#)0!*1".9 4?&:G'AN.IU1R^:\i$5>$6=!6=!6>6>5?5?5?5?2>0=3?3?2?2?2?2?.:.:.9-7-7-7,5,5-2-2-1-1-0-0-/!-/!-/!+. -/!-/!+-!+-!*, )+),),),),&+%*%*%*#) '"(#)&$%$'(&&%%$$$#""####$ $ $ $ %%((("("(")#$"$"$"$""!! "!"!$"$""!"!"!! ! ! ""!!  !!!!!!!$!$!$"&"'"'"'$(#*$+&,&,&,&,%+%+#*#*%(&*#'$(%*&+")*'-.28,7=1>H+EO2PW-W^4`f1dk6gq2is4my.oz/m|(m|(fy&gz'hv+hv+ft(bp%aq!`o]o]oUe O_HO9EM7EN8?H2IR>OXDPZKAKR,H]9VkGixQq€Yu‰Vu‰V{‹T{‹Tx‰Rv†OyƒPvMs{OksF_e@PV0>G&2;$0".,,--..//--.-,- - -+, *('''&'&%&',!/(6!+E5O$Da&Pm2^z6i„@t‹?xC{’A{’A€”C‚—F‚”K‚”K‘M‘M}‹Nx†HvxHik;[\7KL'=A#9</6",2*2!,4#-90<:E#@K)GT)P\1[f7bm>lx@r~Ex…J{ˆM†”R‰–TŒ™X›Y‹V‹V‡N„šK…™O–L€–O€–O{”Q|–R|•Vz”U}–Y}–Y~•]—^}‘b}‘b|az`z‹fwˆcuˆct‡bs„aq_n~[k{YhzRasK[oAUi;Ni:Ok;Km?OqBLy?Q}DS…HT†IVˆLW‰MY‡R[ˆSbƒ^d…`p„js‡muˆlv‰mu…iu…iv‡ix‰kyŒn}‘r˜v…›zˆ¡yŠ£{¥y‹¡tŽžn‹›kšmšm‹–n‹–n‡’l…jƒŒ_x€SruDeh8[]-VY)OV%LT#MU$MU$JU!KV"NY#R^(Yd0`k7eq9huM6=L59K68J58H:7G92C:/@6/@6,=4*95*95*72*720520521721721801801801804;.4;.7=,7=,5=%4<$5>$5>$6=!6=!7? 7? 6@6@5?5?3?2>3?3?2?2?2?2?/;.:.9-7,5,5,5,5-2-2-1-1-0-0-/!-/!.0"-/!-/!-/!+-!+-!+-!+-!+. +. ),),&+&+&+%*#)"(#)"(&&%%)''&%%$$$#""####$ $ $ $ %&((("("("("$"$"$"$""!"!"!"!$"$"$""!"!"!! ! ""!!     # #!$"&"'"'"'$(#*$+%+%+%+%+#*#*#*#*"&"&$(%*&+*.%+,)014;.9?3BL/HQ5U\2\c9dk6gm8jt5mv7p{1p{1p*o})i|)fy&gu*gu*ft(ao$`o`o^pZlQbK\8K1D 2+"!#!'&&3(3@4BP5P^C^pBj|MwŒP}’U„˜N†šPƒšN€˜Kƒ”Mƒ”M‹QxƒIqvQbgBPU>>D,(0%%""##$$!#&.%%4*4A6DQFUdMbqZlZv‰d|‘`~“b„–a“^„–Z‚”X|[z‹Xy…UsOpyDfo9[e-S]%MV%KU$KS)KS)IT'HS&BN#=I7B2=+6$/ ( % +# +( ,'3MV@qzd}…m}…mz€vlrhY]XOSN=F7:D5MYCMYCMR=RWAW^HLT>BL6MV@V_KU^Jdn_HRCG&2;#/!-++,,,,,,++--,,,,+*)(&&&&%%#%() .'5 +E5O$C`%Ol1^z6i„@vŽA{’F}“B}“B“B€”C“Iƒ•L‘M‘M}‹Nv„GvxHkn=]^:RS.CG(<@!4:'/6",4#19(5B$:F)AL*IT3T`5\i>frCkwGu‚Iz†N€R†“XŒ™X›Y[Œ™XŠ›Tˆ™R†œM…›L…™O–L~”L~”Ly’Nz“Oz”Uy“T|•X|•X|“[~•]|ay_w‹]vŠ[u†arƒ^p„^o‚]p€^o\n~[k{Yi{SbtL\pBWl=Qm=Sn>NpAQsEO{BR~ES…HU‡KVˆLW‰M[ˆS^‹Vd…`f‡bq…ktˆnuˆlt‡ku…iu…iv‡ix‰kyŒn}‘r˜v…›zˆ¡yŠ£{Œ¢u‹¡tŒlŠšjšmšm™p‹–n‡’l…j‚Š^yTtwGhj:_b2[]-V],RZ)RZ)RZ)Q\(R])Wc,\g1^i5epel;]d3NT-BI!5;!07'.!'.!!1*)920EB?UQMg^[ulm„nvw‚•wƒ—x‡—v‡—vˆ–t†“q‡“v‡“v‰”{Š•|ˆ”€†‘}ƒxƒx€q}‹n|‹fx†b{…_r}WsrYgfMYSKMG??:B95<11?00=$.:#-9IX+HW)IX+HW)IX+IX+HV-HV-EU+CT*@R+BT-?R/>Q.;L59J28I77H63B72A61>90<8-:6,95'40'40+51+51,2//41/50/5007/07/3803803:-3:-6<+7=,5=%5=%5>$5>$6=!6=!6>6>8@!8@!6>6>3>3>3>3>3>2=2=0<0;.9-7-7-7-7-6-6.3.3.2.2/2"/2"-/!+. -/!/1#./$./$-/!-/!-/!-/!+-!+-!+-!*, '*!&) &) &) #)#)$*#)!+ *)(&'&&&&$$$#####""""$ $ %!&"&"&"%$%$%$$"$"$"$""!"!"!$"$"$"$"$""!! ! "!! !!       !"" % %"'$($($(%+#*%+%+")")%(%(#'#'"'$('("--*/'27/;?,BF3IQ-PX4Wd0]j6cq2fu5m{.n}/q-s/t‚0q-l~-hz*hw)ft'ap"`o!`n#]k Zh$Yg#L\%@Q0@*:)#""&#-),62J9F0=)6#/' $  !$*1"PZK[eUnylq}oq{wfpmW^]GOMAL:?I8TaAUbBOX=PY>\cTYaRIPJKQKPYQR\SRdJQcIA\6C]7Jk?Z|OSw=Ad*7[:_Js/8aC_&Uq8ktLmvNw`t|]`mResX^ufG^O*EE'CC0TP3VS;cQ.VD7c@1]:3\34]44U02S.I^;YnKkzSrZu‰Vu‰VyŠSx‰RyŠSyŠS|‡SyƒPoyJluF^e=PW/=F%1:#/+&&&&'&''****((((('''%%%%&&$%(*.%5 +B5M)B])Nh4^z6i„@rŠ=wB}“B}“B“B“B€’Hƒ•L‚’N‚’NOy‡IuGmw>`g8QX)DL&>F 6A!3=4>:D%?N'ET-L]+Rc1`o;gvBoAv†H}Kƒ“Qˆ˜TX‹V‹VŠ›T‹V‡™V‡™Vƒ–V„—W€•R€•R{Q{QvŽSvŽStŽUuVt[t[t^t^wavŒ_u‹^q‡[nUo‚VmƒVmƒVnXoYnXk}Vg}PawJ^uDYp?Op;Op;MuANwCM|GP€KR…MT‡OU‰NWŠP[‰X\ŠZe†aiŠes‡mtˆnw‡lv†kw…iw…iv‡ix‰kzo~’s‚™x†œ{ˆ¡y‰¢z£w‹¡tŽžn‹›kŽ›pŽ›pŒ–r‹•q‡’l…j‚Œ[yƒQu|Diq9bj0]f+[g*Zf)Xc&Xc&Ye([g*_k,bo/ds1kz8q@tƒCvˆEy‹H|‘K”O‚—Q‚—Q€–O•M~”L~”L~Ox‰IuGlv=di:X\.IM-=B!04!*. *!",#0+);61LA?ZOSl]azkpˆpx‘x†•uˆ˜x‰™r‰™rˆ–t‡•s†’u‡“v‰”{‰”{‡“}†’|ttmz‹h{Šew…as‚\pXmtScjI[TGOH$5>$6=!6=!6>6>7? 7? 6>6>3>3>3>3>3>3>2=0<0;/:/9/9-7-7-6-6.3.3.2.2/2"/2"/1#/1#03%03%./$./$-/!-/!-/!-/!+-!+-!+-!*, '*!'*!'*!&) $*$*$*#) * *))''&&&&$$$#####""""$ $ %!%!%!&"%$%$%$%$$"$"$""!"!"!$"$"$"$"$""!! ! "!! !!       !"" % %$!&$(#'#*")")")!(!(%(%(#'$("'&+)*#..04-8=5BF3GK9PX4U\9]j6_m8fu5jx9o~0q€2v„2v„2t‚0q-l~-hz*hw)ft'cr$ap"ao$_m!Zh$TbDT=M.>%6'!  #($'2.4A-BO;O`;\mHh~JrˆU{‘P}“R‚˜P~”Lƒ•Lƒ•L„TŠPx€SnwJafINS7:?0+/!"##$$!#%% ,,)605C„d~‡_owXfnOWeJM[?JaR4K<341195-PM3[I"K9.Y71]:'P(&O&-N)2S.I^;YnKl{Us‚\u‰VwŠW{‹TyŠS|ŒU|ŒU}ˆT{†RqzKmvG^e=PW/=F%1:!-)$$$$%&&&))))'''''&&&%%%%%%$$'(.%5 +B5M)B])Nh4]y5hƒ?rŠ=wB}“B}“B“B“B}F“I‘M‘M}‹NzˆJv€Hnx@bi:T[,GO*AI$7B#;E&>I*DO/HW0P`9Xi6^o&6>&7>$7>$7? 7? 6?6?7@7@5?5?7A7A4A2?2>2>/;.:.9-7-6-6-5-5,4,4,3,3-2.3/1#03%.0".0".0".0"+. .0"-/!-/!-/!-/!-/!+. (-',',&+%*',',',!* (' ( (%&%%%%%&&##$$""$$$$%!%!%$&%$"$"$"$"$%$%$%#$$"$"$"$""!%$"!! "!"!!  !!    !!        !         ! ! ! ! ! !!!""$%!&!&!&!&#'#'#'#'#'#'$*%+&(&()')')0(.5-8<)@D2KP/QU5V^2^f:cp5dq6hv2m{7r€5r€5t„9t„9o€2m~0l|1hy-hv+dr'ao$ao$^m$Zj!Pb$K^>O#2C)4 !, ! !!#%$!+)-7,9D9IX?TdJbuInUyŒR|V}—J|–I€•I‚˜K”O~‘Kz†Nr~EhoGX_7CI/29!)!  !!!#!!#$()$1,0<8BO=R_MbqQjyYu†[{Œa|‘[{Z~”U}’T“R~’QŽU}‹R|„Qt|Iio<_d1T\+MU$HQ"HQ"BK%>F 2<,6$2) !   +    25+IK=RTF`j`^h]Xd[P\SNWQ!*$3@'R_FUc?P_:JS@W`LILJHKHNYRITNL]JFWE@T:PdJKcDF^?Nj?QmBZ{HTuC@g1>5_&4]%4^#Z…If‚Kj…N_pE]nBiqKW`:Z[IVXEBK?*3&$>&/J1#L#3\3>c9(M# < 7*@(:P8QaE_oSk}Vr„]yŠWyŠWzŽRzŽR{ŒR{ŒRz‡Ux„Rs{OmuIbgBOU/pŠ?w‘Fy‘D{’F‘F‘FI’K‚’N‚’NM|‹Ku‚Gmz?cm:Wb.LV+DM#>K%AN'FV-J[1Tg0Yl4cu9i{?s„DyŠJŽS…”Y‰›YŸ\ŒŸX‹žW…œR„›P…›S„šR„šR„šR~–P~–P|TzŽRpŠRpŠRmŠOl‰NjˆQk‰Rg†Xi‰[l†[l†[n†[o‡\q„XnUlRlRkSl€TjUlVm‚XjUhPczKZwIUrEPrCPrCKsFOwJM|IPLP…MR‡OS‰PTŠQZŠ[ZŠ[d‡dg‰fq‰lrŠmxˆmv†kw…iw…iv†kzŠn{Žq“w„{‡ ‹¢zŽ¤|¥v‹ŸpŒœnŒœn‹™pŽ›r‹˜rŠ—q–n‡h„Z|ˆRt;kw3ht.dq*`l-_k,`l-`l-ds*fu,gw,k{/m6s…;yˆF}K}ŽI‚’N‚”R‚”R€”S€”S“R~’Q‚SQ}‹Nx†Hs}Dkt<_b6RU)@D#59)/'-+".%#9,.E8:WGLhX]uckƒqx’u|–xŠšvˆ™t‰˜sˆ—r‡”t†“s‡”tˆ•u‰•z‰•z‡•z…“w’o~ŽlyŠev‡bw…aw…avƒ]tZsyShnI]XGQK:C:9>55205/.2'/2(04IY)IY)IY)GW'ET)ET)DR)CQ(@S)@S)$7>$7? 7? 6?6?7@7@6@6@6@6@2?3@2>2>0=/;.9.9-6-6-5-5,4,4,3,3.3.3/1#/1#03%14&.0".0".0"/1#-/!-/!-/!-/!-/!+. (-(-(-&+&+',&+&+!*!*!*"+!*&&&%%%%&&$$%%##$$$$%!%!$"$"#!$"$"$"$%$%$%#$$"$"$"$"$"$""!! ! ! !    !!  !!                     ! ! ! !!"!"!!""$$$$!&!&#'#'#'#'#'#'$*%+&((* *(%.-.5-3;3?C0FJ7QU5W[;\d7dl?gt9hv;o}9r;t‚6uƒ8sƒ8o€4p‚4o€2l|1hy-gu*dr'ao$_m![k"Yi L_!DW5F->"-) !#!)''1'3=2BQ8L[B[nBi|Pu‰O{ŽU|–I}—J€•I€•I‚•P€“N}ŠQvƒJmtL`g?JP68?%%-##!!!!!!!&& -)*72=J8MZHZiIgvVrƒWz‹_{Z|‘[€•V~”U~’Q~’QWŽU}…Rv~KntAfk8X_.PW&IR#DM?G!8@-7'2*$      58/]`V\_QNQCKUKU`UR^VP\SJSMFOIHUVe@W`LMVB[^[VYV=IBh/0Z!8c(Q{@So8Yu>^oCctHfoIfoIfhVegUGPC%.! ;"1K2%N%#L#&K E/:/E->T37(.&,+"$2)%;/4K>?[KMiYc{in†ux’u|–xŠšvŠšv‰˜sˆ—r‡”t†“s‡”tˆ•u‰•z‰•z†”x„‘vm|Œiwˆcv‡bw…au„_vƒ]tZsyShnI]XGOJ9D;:<320/4.,1'/2(04IY)IY)GW'GW'EW'EW'BS'@Q%;N)9M(9J,9J,5G-4F,2D/.A,-;-,:,)7*(6('3*&2)(2)'1(&.*'/+(0,*2/.4..4..4..4..6*/8+28,28,49*49*5<'4;&5=%5=%5>$5>$7>"7>"7@7@5>7@6?6?6@6@6@5?3?3?2=2=0;0;/9.8-6-6-5-5.3.3/3 /3 /3 /3 /1#/1#/1#/1#03%03%03%03%/1#.0"-/!+. -/!-/!)-!(, (, (, ',',&+&+#)$*", )*)('&&&&%%%%%%$ $ #!#!#!#!$ $ $"$"$"#!$"%$#$#$$%#$"!"!"!"!$"$""!!   !   !!               ! ! ! !""""$$$$!&!&"&"&#'#'!*!*%+%+*&*&*3&/8+9A)@H0HO.OV5X^2^d7dm5is;jy7l|9r€:tƒG&,3%")       $%,%'5.7H3GWBWhI_pRnUuˆ\zŽY|‘[–W–W€”S€”S‚‘XW{‰Tw„PrzFem:X_.QX'CL@I5A/;%4) #     +"(dgYgi[Y]J@D2EM5aiQYaIQYAKV>IT;NX@NX@JT>:D.DKCW_WcfjHLP2:;@HIGVSCQOFXLN`TGYDJ]HMcBMcBGb;D^8@`6=^4:[/*K-R&>d7Gkfy@lEs‹C|”L‚˜N…œR‰žX‡W‡œY…™V…™V„˜U…šUƒ˜R—V~–T~”X|“VvPsNoPm‹Mc‡M_‚H^€I\HZI\€J[{M\|Nb|QeToXpƒYpUnTj~Qi|Pj}SlUlZlZj~XlZcyQ_vNTtFRqCJo@MqBJuGKvIHzILNMƒMN…NQ‡SU‹W]‰_]‰_d†eg‰hr‰np‡lv…lu„kw„kvƒjt…nv‡pzŽt€”z†›~‰ž‚Ž¡~ }Ž y‹œuŒšq‹™pŒ™sŒ™s‹—uŽ™wšm‰”g„’V|‰Nu‚9mz1iv.fs*bq*bq*dr,es-hy-l|1n1s„6t‡;zA|ŽE€’H•M…˜Q„™T€–P€–P|‘K}’M}’M~MzŒJx‡LpDkw>`m4QW+DJ6<06'/&.-#&5,'>19OBEbPSp^f€irŒu‚•w„˜y›wŠ™t‰—uˆ–t‡”tˆ•uˆ•uˆ•u‰•z‰•z„’ur}‹nx‡isƒcv…euƒat‚`v‚^t€\tyVkpM_\AQN3C=3<6,/.1.-0'01-67IY)IY)GW'GW'CV%BU$@Q%?P$9M(8L&7H*7H*3E+2C*0B-,?))7*(6('5''5'&2)%1((2)'1(&.*'/+(0,*2/.4..4..4..4..6*/8+28,28,49*49*4;&5<'5=%5=%5>$5>$7>"7>"6?6?6?6?5>5>6@6@7A7A4@4@3>3>0;2<0:/9.7.7.6.6/4/4/3 /3 /3 /3 /1#/1#/1#/1#03%03%.0".0"/1#03%/1#.0"-/!-/!+/#+/#)-!(, (-(-(-(-$*$*",!***((&&$$%%%%%%$ $ #!#!#!#!$ $ $"$"$"$"%$%$#$#$$%#$"!"!"!"!$"$""!!   !         !          ! ! ! !""""$$$$ % %"&"&#'#'!*",%+&, -)$1,/8+6?2@H0EM5OV5V]<^d7bhm}:q€>tƒ)>O:M^@[lNj~Qs‡[yX|‘[–W–W‚•T‚•T‚‘XWŒXy†Rv~KltA_f5RZ)FO ?H0=)6 .'        +1%VYKgi[hlYX\I]eMYaI`hP[dL?I1EO7P[BMW?JT>GQ:GNFV^VgjnKNR#+,/896DB8GD3E9ASGM_JPcNNdCH_==W12M&-M$-M$*K1R&0V)/U(?c4Jo@>W.Oh@[gI[gI^gZS\O?T>3H30H)C[<5L+%<$$ ,#(4,3?6COFP_M^m[j{]pcy‰Yy‰YvŠPvŠPx‰Px‰Py…TwƒQt}NmvG`g?OV.9B!.7+%    !!##"!     !#%%',,2$8'E/M$?^+Fe2Yt7cBpˆBt‹FvCy“F{‘D{‘D}F€’H‚’O‚’O‚T}‹P{‡OsGmyBdo9]i8\h6Yk6]o:cwBi}HoƒIs†LwH~—O†S‡žT‡W‰žX…™V…™Vƒ—Tƒ—T…šU„™T~–T|•S{’UySvPq‹LlŠLg…G`„I[EZ}EY|DW{FVzDZzL[{Mb|QeToXpƒYpUnTj~Qi|Pj}SlUlZlZlZi}WaxP^uLRqCPoAJo@MqBGqDItFGyHJ}LJKK‚LN„PTŠV]‰_]‰_f‡ghŠiqˆmp‡lu„ku„kw„kvƒjv‡pwˆq{u•{†›~‰ž‚Ž¡~ }Ž y‹œuŒšq‹™p‹˜rŒ™sŽ™wŽ™wšm‰”g„’V|‰Nu‚9mz1ky0iv.fu.fu.fu.gv/k{/n~3rƒ5v‡9yŒ@|C“I‚”K‚–O„—P‚—Q€–P”O|‘K}’M}’M~MzŒJw†Ko~Chu<[g.OT(EK6<06'/&. /&)7.,B6=TGJgTXubjƒmtŽwƒ—x‡š|ŒšvŠ™t‰—uˆ–t‡”tˆ•uˆ•uˆ•u‰•z‰•z„’u€qz‰kv„gsƒcr‚bt‚`t‚`v‚^t€\qvSglI_\AQN3C=3<6,.-0/.1)23/89FY&EX%AV'@T&?S%$5>$7>"7>"7@7@6@6@7A7A6?6?7@7@5=5=6=!6=!4="4="2:"2:"2918171735"13 04!04!05 05 15"15"15"15"04!04!./$/1%02&02&/1#.0"./%./%.0"-/!+. +. *, *, &- &- &- &- #,"+))''&&$$$$$$##$ $ $ $ $ $ %!%!$"$"$"$"$"$"%$%$%$$"$"$"$"#!"!$""!! ! ! !  ! $%#').-0! #    !!            "!" # #!"" # #""$$ % % %"&&%&%%*%*(& -++4'1:-M]CR\RPZO]acKOP;<@9:>28.:@6AKOEN`K\nYl}Zq_x†Vx†VwˆOwˆOwˆOwˆOxˆSv…Qs|MluF_f>NT-9C+5)$ ""  !  " # % '*,006 ;(D!0L*?[0Fb7Xq;azDl„Cq‰HxE{‘GyŽByŽB}D‘F€K‘MŒV€‹U|‡SyƒPp}IivBeu9dt8cu9hz>mCt†Jz‹K€‘Q‡šU‰œW‡žT†S…šU…šU€™U€™U€™U˜T|‘Z|‘Zv‘VuUqTm‰Pk‰Rf„M`‚M^€JW~HSyCMv@Lu?QwDTzFXzL\~Of}SjVn„Wn„Wn‚SlRf|MhPkQlRnXl~Wi€Qf|MazK]vFRsINnDHmGIoIEoIFqJCxIG{LC}JGONƒQP…TY†_]‹cd‡heˆim†km†ku†hu†hx„gx„gu‡myŠq{u•{ˆœ{‹Ÿ¡zŽ y tŒq‹™pŠ˜oˆ—qŠšsŒ›tŒ›tœlŒ—h„’U|ŠMt‚6n|1n{2ky0jw/jw/k{+n}-p‚2r„3p†7rˆ9y@{‘C|“H~•K‚–O‚–O‚•P‚•P”O}’M~’Q~’QŽU{ŠQtƒJm|Cfq=Yd0KT*=F19-5!/"0!2'*<00I:?YIMlUZxap‡l|’x‰›x‰›xœuŒ™sŠ•tŠ•t‡”tˆ•uŠ–yŠ–y‰”{ˆ“z„’u~ŒoyŠeu†as_s_s_uƒatƒ^r€\s{WjqNc_BUQ4F=/?6(/-+21/0619@:EX%CW$@T&?S%?S%=Q"9O$8M#7K%5H#3G&2F%1@).<&)7")7"(2!(2!(1"'0!'/#%.!%-#%-#&/)&/)(.)(.))/**0+,4*,4*.6'-4&.6%19(39&39&3:$3:$4<$4<$5>$5>$7>"7>"7@7@7A7A7A7A7@7@7@7@6>5=6=!6=!4="4="2:"2:"2929171735"24!04!04!05 05 15"15"15"15"15"15"02&/1%/1%/1%/1#/1#./%./%.0"-/!-/!+. +-!*, &- &- &- &- #,"+))(&&&$$%%########$ $ %!%!$"$"$"$"$"$"%$%$%$$"$"$"$"#!"!"!"!! ! ! !  (,-7;<;:<))+    !!            !""" # #!"" # #""$$ % % %"&&%&%%*', -+$0.1:-7@3@H.GO5PY3U]7]f7cl=eu9hwt‚=v„@x‰=x‰=w‰9w‰9v‰6r„2l,h{(cv(_s$]p"YmVg UfK]!@R0D(;,( "#()(3&5@3FVF0,3$  " *():%3C.CU;QcIbvPo‚]xŒW{Z–W–Wƒ—Tƒ—T‡—Z‡—Z‡“ZU{†Rr|Idk:S[*BM 7A&2+%    '-'NUOdloŒ”˜l|{JZZNaRSeW\fNXcJR]EQ\C?M1@N3DS:P_FYcYQ[PUY[PTVEEJ88=?D:IS6!% .'&4-0A7=NDN`K[mXn~[r‚`v„Tv„TwˆOwˆOv‡Nx‰Pw‡RtƒNr{LmvG_f>NT-9C+5(# +!"    $ %'*,/347!<(D!0L*?[0Gc8Xq;b{El„Cq‰HxE{‘GyŽByŽB}D‘F€K‚’N‚ŽWŒV‰U|‡SvƒNp}Il|@k{?i{?p‚Ft†Jy‹P’R†—W‰œW‹X†S†S…šU…šUšVšV~—S|–R|‘ZyŽVsRqŒQm‰Pk‡NeƒLeƒL\~IY{FPwALry@{‘C|“H~•K€”L€”L’M’M|‘K|‘K}P~’Q~ŒSz‰Ps‚Il{BepQ%"-;$)8!&3&3(2!(2!'0!%/ %.!$- %-#(0%(/''.&'.&'.&)2%)2%,3%,3%06%06%28%39&39&39&4;&4;&5>$5>$4>!4>!6=!6=!7A7A8B8B7A7A7A7A8B8B7A7A6?6?3=3=3=2<28 28 27"27"46$46$27$27$15$15$26%26%25'25'14&14&03#03#15$15$/2"-0/1#/1#/1#.0"-/!-/!--$--$).$).$$,"$,"!,!!,!**((&&####!#""""##$ $ $ $ #!#!#!#!$"$"%$$"$"%$%$%$%$$""!"!"!! " " "  #'),02+/1!#        ! ! !!" #" #"$ # #" #!#!#!#!# % % %"' %#'$$'&*&+'%3%+8+9C+>H0GT+LZ1Td/Zi5_p2fw9l{;n}=t€AuBs†As†Av‹?v‹?s‰;rˆ9q‡8mƒ5i~2dy-^q,[n)Zk+Vg'Qa*L\%CR&;J)9"2%!"' %/$1<1@P7M]C\oEi|RtˆLyQ“I“I€•I–Jƒ“Q‚’O‹Ry…LpuL`e;GN45;!!*!%!+"+4'6?2FQ6R^C`oKkzUu†[{Œa€“Y”Z‚—X‚—X†˜\…—[‡–]„’Y}‡Vv€OmtCYa/EM'6>$.*$  LY[TacCZg]t€1H[2E=PS1DHMVP[d^NQN7:7061EKF1:4?HBGQMEPLDMGBJEGOE,4*28%AG4EO7BM4?L,?L,HR3CN.T[8Y`=`d=bg?gjEknIjqNjqNVkG;P-"G(,R3$J2"H/(B04O=6R;3O9F`CVoRMZAN[B! #+$&4-1B8?PFObL[mXl}Zo\uƒStRt…Kt…Ku†Lv‡Nw‡Ru„PszPmuJ`fAOU/;B%+3'"  "#&(,-/246;!?%H,O%9[+Ad3Sq8\{Ah†Am‹FwEyHyŽD{F‘G‘G‚’O„”R„“Sƒ’R~ŽP}Oz‹KwˆHr‡Dr‡Dt†Jy‹P|ŽR‚”X…™X‰œ[‡V‡Vƒ›U‚™T‚˜W„šY‚–Z€”X}’U|TxYu‹Um‹Tk‰Rd„OcƒN_‚J[~FSzBNv=Gq8Jt;Lw;Oz?T{DV}G_|Nc€Sm‚Xm‚XpƒWo‚Vn€RmQlNlNlNlNk€Oi~Md~La{JYwETs@LnDIlBFlFFlF?lEBoG@sGBvI?xIB{KFOL…UT…_X‰dfˆlfˆln…oo†pu…iq‚fw‚gxƒhvˆnzŒr|‘w‚–|ŠŠ¡}žy tŒq‹™p‹™pŽ™sštštŽ™sœjŒ˜fƒ’Rz‰Iu;o{5nz4my3hz1hz1h},j~-lƒ2m„3oŠ3r7tŒ=vŽ?z“H{”Iz“Oz“O{‘PzOzPzPy‹Ty‹T{ŠUu„Pn}IgvBam;T`.BM$6A-:+8$3!%4"%8*0B4:UCJeSZt^h‚lw‹q“yšxšxœu‹˜rŠ•tŠ•t‰–vŠ—xŒ˜zŒ˜zŠ•|‰”{ƒr‹nw…as‚]qXqXr‚Yr‚YrZrZt}WiqKadASV3EC/<:&7:+<>0$5>$4>!4>!7>"7>"8B8B8B8B8B8B9C7A8B8B7A7A6?6?3=3=3=3=49"28 48#48#78&67%27$27$26%26%26%26%25'25'14&14&03#03#/2".1!/2"/2"03%03%/1#.0".0".0"./%./%).$).$$,"$,"!,!!,!+ *((&&####!#""####$ $ $ $ #!#!#!#!$"$"%$$"$"%$%$%$%$$""!"!"!! " "   !!            ! ! !!" #" #"$ # #" #"$"$"$"$ % % %"'$(#'&%'&-(!0+(6(/=/;F-@J2JX/Q_6Yh4_n:ev8iy;m|",$  '.-ivxHUX,CO"9E0C/B2EI7JNKTNXa[MPM475"(##)$,4.1:4.955@<4=79BE2@J2BM4;H(@M-=H(HR3[b?^fBeiBgkDimHknIfnKdlHNc?-BB"*O0!F.#I0(B0HcQ6R;)E.%C )K!0R(9[+Cf5Tr9]|Bh†Am‹FxŽF{‘I}‘G{F|ŽE‘G€M‚’O„“S†”T‚“U€SP{LyŽKyŽK|ŽR€’V„–Z‡™]‰œ[‰œ[‡V‡Vƒ›Uƒ›U‚˜W—V€”X}’U|T{Su‹UqˆRi‡Pd‚Kb‚M_~IWyBQtvEy’Gy’Ny’NzOyMzPzPy‹Ty‹TxˆSu„Pm|HfuA_l:O[*BM$6A.;-:'6$(8%(:,2E6=WEMgU\u_jƒm{uƒ˜~šxšxœu‹˜r‹—uŠ•t‰–vŠ—xŒ˜zŒ˜zŠ•|‰”{ƒr‹nw…as‚]qXqXr‚Yr‚YrZrZt}WiqKadASV3DB.=;';=/CE7AL:FP?CU'CU'>R$$7>$8@!8@!8A 8A 8C8C9D9D8B8B8B8B8A 7@8@!7? 4>4>4>!4>!78&46$67%67%78&78&46$46$26%26%26%15$25'25'14&14&14&14&03%.0"/1#/1#/1#/1#/1#-/!.0"03%-1%-1%%.!%.!$- $- !, ++ (&&%$$#$$####$ $ $ $ $ $ $ $ #!#!$"$"$"$"#$#$#$$%$%$%&"$!"!"!! ! !  !#-)2;:+43!"     """""" %$$ % % % % %!&!&!&!&"$"$#&$'$&$&&&'(#/&'3*/>'6E.AM)HT0N\,Vd5[n7_r:fx7j{;m>o€@rƒCs„DuŠDuŠDn†>n†>l…:l…:i‚:e}5_u4[q0\l.Yi+Wf-R`'E['>T 3C#*9 +% ! $#)-!$ &'1(5D+CR9Rd;buKoƒIyŒR“K•M“I“Iƒ•Lƒ•L†“X€RxQlrDX\;"$! -5/7+7!,8"6D'5C%4?@K)DP,CO+GT'LY,U_-Yc2afNX-;D!-6*% "$&',.2468:<> D#%N%,U,7^.@g8Pt8X|@h…CmŠHvFy‘I{HyE{ŒE}HP„“S€“U‚”V‚”Vƒ•W„–Zƒ•Yƒ•Y„–Z†˜\†˜\‰š_Šœ`Š›a‰š`†—^†—^…š]ƒ—[„™\‚–Zz‘[xYuYqŠVnŠSh„Ma€Ma€MY|KRuDMtP{DR~GV€I[„NaScƒUq…Wq…Wr„VpSr‚Ro€Op~NqOq~Sq~Sk}OizLd|O`xLXtISoDEmBDlA?l>?l>;kA:j@:lDBT&@R$$7>$8@!8@!8A 8A 8C8C9D9D8B8B8B8B9B!9B!8@!7? 4>4>4>!4>!67%78&78&67%67%67%67%46$26%26%26%15$25'25'14&14&14&14&/1#.0"/1#14&03%03%/1#03%/1#/1#-1%-1%'/#%.!$- #,!, +*'&&%###$$""""####$ $ $ $ #!#!$"$"$"$"#$#$#$$%"#"## ""!"!! !   $.*'&        !""""""$$$ % % % % %!&!&!&!&#&#&$'$'%'%''(**%1((4,3A*8G0GS/MZ6Uc3Zh8]o8at=k|;">F.5=%%2(47E(J(JU4HT0GS/JW)S`3[d3]g5bg>ej@fjCgkDelDciB[cDU^?KX=HV;7+J,%K3%K3$C01Q>LgS4P;7M3DZ@SbK>M6% )#$--,552B;@PIQaL_oZn}Xq[vYvYuƒQuƒQv†Ov†Ox„Rx„RozKjuF]g!@ A "G%%N%,U,7^.@g8Pt8X|@h…CmŠHuEz’J}I{H|FI‚‘Q†”Tƒ•W…˜Z‡š\‰›]‡™]‰š_‰š_‰š_ŒžbŒžb‹aŠœ`Š›a‰š`‡™_‡™_ƒ—[‚–Z‚–Z~“VyZvŒWpˆUm…Qj…NdI_}K[zGSvEPsBLs;Ls;Kv:Kv:Jv=MzAOzCSHZƒM\…OcƒUf…Wq…Wq…Wr„VpSo€On~Np~Np~Np|Qp|Qk}OizLazM^vIUqFQmBDlACj@>k=>k=7h>7h>7h@8jB9sC ,<%:%*?)*G57TBFdOSq\h€htŒtƒ—x‡š|›wŽšvŒ™t‹—s‹—u‹—uŒ•{Ž–|˜}Š–{Œ–~ˆ“z€my†fu„_r€\q€Yq€Yq€YrZrZq€Yr}WitNfiDX[6LL3FF-EL1LS8M\>R`CBT&@R$6>6=!6=!8:&79$79$79$46$67%78&46$47&58(47&26%26%26%14&14&25'14&/1#/1#02&02&02&02&03%03%/1#/1#-1%-1%'/#'/#"- "- -,) '&&$$##$$!!!!######$ $ #!#!$"$"$"$"!"#$$%#$"#"## ""!"!! !   !!!!!!""""""$$$ % % %!&!&!&!&"'"'#&#&%(%(&(&(** --(5*-:/8D.?K5ER,O\5Yg7]k<`p9du>i|>j}?nAp„CoƒBp„Cn„Cn„CkƒBg€>h~=h~=dy:_t5Xo2Vm0Yj1Uf-O_/IY)9M(0D#5 -#" !&,#(   # ($,8(;G7L[;ZiIl{Mu…W~ŒSW•M“K‘Mƒ”O€SQ{…Ts}KchCPV06>('.!" ((./5$4>!9B%FL,RX7Yf@dqJl}Qt…Z|]’_ƒ—]„˜^†šY‡›Z‡™_†—^…“c~Œ\z€ZmsN\bH$ER,JW0GT-@M&=IGT)S]0Wb5Zd7]h;ak>ak>dlGckE`fL[bHSYHOTDBM@8D6'=+,A0C-!F0G2$O;9bG2[@8V:3Q5;L<%6&($($!..)777D>BPIRbN`p\p~Zr€\vƒVvƒVw‡Pu…Nv†Ov†OwƒQv‚PoxNkuJ_hBOW2:E#+6)% !!$)--2468:>BD G!#K%$T&*Z+4a,@m8Ov9V}@h„FmˆKvŽHyJ~’H}‘G}‘G~’H€’O„–S…—[‰š_Šžb‹ c‹Ÿe‹ŸeŒ fŒ fŒ f‹ŸeŠždˆ›aˆšc‡™b…˜a‡™b•`€”_|‘[{ZtŠ[p‡Xl„WgS_|L[yIYxJSsEMqBJo@JsI{?Q}DTHYƒJ\†Mg†Qh‡Rs‡Tr…RpƒPn‚On€Kn€Kn~Nn~NoQn~Pi|PgzN_xIZsDQr?Mnk~@nAoƒBm€@m€@h~=h~=e}boIizNs„XzŽ[’_‚•\ƒ—]†šY…™X‡™_‡™_…“c]€†`u{VdjDOU/9>")."  6BK8'=L5V`AalMpzVu[}†Yx€Sad:Y[2LT.=E7A;D!:G GT-DQ+=J$LX-Q]2Yc6Zd7[e8^iak>bjD_hBZaFU\BOTDGL;9E84?2"8' 6%,Q;D/C.K64]A9bGDaF?]A3D4')&!,(!..)779G@ERKRbN`p\p~Zr€\vƒVvƒVw‡Pu…Nu…Nu…NwƒQv‚PoxNkuJ^gANV0:E#-8 ,'!  "$((-0678:>@D !F#"J#%M'(W)/^06d/@m8Ov9V}@h„FmˆKvŽHyJ{F{FzE{F‚”R†˜U‰š_ŒžbŽ¢f¤h‘¥k‘¥k‘¥k‘¥k¢hŒ fŠžd‰œcˆšc‡™b…˜a„—`€”_“^{ZzŽYp‡XmƒTf~QazMZxHXuEQpBMm?Jo@MqBKt=JsL~AS€FTH[…L\†Mg†Qh‡Rs‡Tr…RpƒPn‚On€Kn€Km}Mm}Mn~Pl{Mi|PgzN_xIZsDQr?Lm:Bi>?g<8e@7c?4e@5fA4jC8nH@vRF{WQcV„he‡mfˆnrŠmp‡ksƒeq‚du…ixˆm{s“y‚–~…˜€‰œ~ŠŒxŠšvŠ›qˆ˜oŠ—qŠ—q‹—s‹—s‹™pˆ–m„“`}ŒYw‡IoAm|5ix1gv/es-`p.`p.Zn+Zn+_s0dy6i7mƒ;l‡>n‰AtŠItŠIsˆIsˆIq‡Lq‡Lq…Pr†Qt‰Sq…Pr„Mm€HhzE]o:Qb7FW+AaLKlUYzcm‡iw‘t„™v‡œxŽšvŒ™tŒ™tŒ™tŒ˜vŒ˜vŽ–|˜}–€–€‰•x…‘sŽgx‡`s„XpUpXq€YrZrZs‚\s‚\uYnxRinF_c!4>!7? 7? 8A 8A 8B8B9D9D;E;E;E;E8C;E9C9C;C$9B#8@!8@!8@#7>"6=!6=!5;!5;!6<$6<$6;&6;&6;&6;&5:$5:$48%27$26%26%25'25'44(44(33'33'13'13'/1%/1%/2*/2*-/(-/(,-),-)'-''-'%-)"*'($'#&&%$####""!!""""##%%$ &!#!#! #!#!%$"!"!! ! "#"##$#$" "   !!""$% % % % % % %!&!& %!&!&!&!&"'%*(-%*%*#'#'$(%*'&'&!,(%/+/6.7>6BI4HO9NY3T_9Yh4]m8ds:ix>izAk}Cg€CeBa|Aa|A_y>_y>ay>`x=`v>^s;^p9Yl4Zi6Yh5Vd5R`0KX2BO(0?((7 '#"        ("%0#0;.ES7SaEbpGo}Ty‰T}ŒX~‘S}Q”O”O’R’RŠTxƒMksN[c=FN14;& %&++/!1469":B@H#JP+OU/Va;alEivPrX{Š]€aƒ–^ƒ–^‚—Xƒ˜Z†˜\‡™]‡—b…•`‚ŒbyƒXnrKY]6CG(25!&   587FGIACE-82%0*'6&'6&+=!;N1L^6[mEr}WvZy†[v‚WmzLJW)9J8I)>'<3C B! B!"I%$K'$O(&Q*)_+-c/7k0>r7Mx=TDd‚Ki‡PrŒMsNz’Jz’J~‘J€”L‡™V‹Z‰œcŽ¡g‘¢p“¤r”¦v”¦vŽ¥v¤uŠ¤rˆ¢p„ iƒŸh„›j‚™h€•d|‘`’fzŽbt‰_nƒYh~Xe{UZvRXtPPoJKjEFkAHnCGn?JpALwCLwCI{AK|BL;M‚Ih:Ch=@e;;c=9b@8a?6a?8dA6hE:mI=rUDy\P|eV‚kbƒpe‡tpˆpn†nv‡iv‡iu…izŠn{s“yƒ•{‡˜‹œ~ŒŽvŒ›t‰™p‡—nŠ—qŠ—qŒ™tŒ™tˆ–m„’i‚Ž^{‡Wt…Gl}?jy7fv3bq/`p.^o/\m-[p,\q.`t1fz7i~9o„?q‡?tŠBvŠGu‰Fs†Ls†LrƒNrƒNo„Sp…Tp…Tp…TnƒRjNawCYn;K`6BW-5N,1J(+D.+D.(I2.O76[CAgOQqU]~bpŠf|•q‰œw‰œwœuŽ›tŒ™sŒ™sŠ—xŠ—xŒ–~—Š“€–‚Š”~†y~‹ky†ft„]rZq‚VrƒWs‚\s‚\sƒZr‚Yt€UkxMkpFdi?X`5W^4Qb8Xi?[pFavL!4>!7? 7? 8A 8A 8B8B9D9D;E;E;E;E;E"6=!6=!5;!5;!6<$6<$6;&6;&6;&6;&5:$5:$48%48%26%26%25'25'33'33'33'33'24(24(/1%/1%.1).1)-/(-/(,-),-)'-''-'"*'!)&($'#&&%$####""!!""""##$$$ &!#!#!#!$"%$#!"!"!"!"!"#"#"#"#" "   !! #"$% % % % % % %!&!& %!&!&!&"'"'$(%*#'#'#'#'$(&+((++$.*'2.2918?7DK6JQhy@izAc|?b{>^x=^x=\w<\w<]v;\t:\q9[p8Zm6Zm6Zi6Yh5Uc3P].HU.=J$.<%!   !     "%!,-8+>L0M[?`nEkxOy‰T}ŒX~‘S}Q~‘K~‘K€‘Q’RŒV{‡PmvP^gAJQ58@#%,"#"'(--2#379<%;DAI$JP+OU/T_9`jDfsLo|VyˆZ€aƒ–^„—`ƒ˜Z„™[†˜\‡™]‡—b…•`„c{…[uyRdh@NQ3:=&+!   8::suwuwy4?9# /(;9K/K\5\nGq|Vxƒ]{‡\wƒXo|OFS&0A 13!6 +; 14@S_4Vc8Xd9_g<`h>ek>fl?bjD]e@Y_ESZ?LS@EL98D63>1%1/*(&!% 6%?.J0)X> Z7 Z7/S4EiJ>K@'4)'+,'+,&22.::9G@FSMWgRcs^s‚\u…^v‡Uu†Tw‡Pw‡Py‡Px†Oy…Tx„RqxNlsIbdHQT8;A'.4#+&! "$)*-036:<!?!"@""D#$F%$K'&N*&Q*)S-+a-0f29m3@t:Mx=TDd‚Ki‡Pq‹LrŒMy‘I{“K€”L†šRŒž[¡_£j’¦l”¦s—¨u–©x–©x‘§y¦w‹¥s‰£q„ iƒŸh„›j˜g~“b|‘`|cv‰]p…[lVcxS_uOTqLPlGMkFIhCFkAHnCGn?JpAJu@Ju@I{AK|BL;M‚c99a;:b<4]<3\:2^<2^<2eA6hE;pSBwZP|eWƒld†re‡to‡om…mt…gt…gu…izŠn{s“yƒ•{‡˜‹œ~ŒŽvŒ›t‰™p‡—nŠ—q‹˜r‹—s‹—sˆ–m‚gŠ[y…Us„Fhx:fv3cr0`p._o,\m-]n.[p,\q.`t1fz7j:m‚6>7@7@8B8B8B8B9D"7>"6<"6<"6<$6<$6<$6<$6;&6;&59&59&47&47&47&47&14&14&44(33'33'21&13'13'02&02&-1%-1%,0$,0$,.',.'%-%%-% )# )#)#' %%%$##!!""""""""####"$ #!#!#!#!" $""!"!"!"!"#"#"#"#" "!""%% % % % %!&!&!&!&!&"'!&!&"'"'!&!&#'#'#'$($(%*** --(1+,4.5=,?G6JR3PX9Q]2Vc8\i5_m8aq:du>fyAbu>\r?[q=Xo>Xo>Yp?Yp?Yn=Yn=[k=[k=_l>\i<\iO13C%&7'." !!!!    #$"-"*5*8E,HU<[jCixQs„QwˆV}Q}Q’M~‘K~O€‘QŒVz…OpzMdoBPX9=E&)1&##!''--2/47< :?#?F#BJ&IO*NT.R\6[f@grJozRv†X€a‚“`ƒ”bƒ—[ƒ—[…™V…™V‡™]†˜\…a~‰Z{XlpIW\9AF#*1#   + -)WhlQbf""' (8$3C.IY9[jJozRvYw„Wu‚TmvNajB27#% *#.1<KT'[c6^d7_e8bh@ciB_i@bkC_gDY`=PWAKR=>I<7B5*72#0+&+$("*(0,*20K9(UB![=X:6]@-U8DSCJYI#+)%,+&22.::;IBJXQXhSdt_s‚\u…^zŠZy‰Yw‡Rw‡Rx„R{ˆVxƒTvRt{SovNcgFRW6:A+/7! ,'##"!#'),.1257<?B"D%H%J(O*"S-"X+%[.)b2.g75n7;u>MyBT€Ic…IhŠNpŒNqPw‘Jy“M€•R…™VŒd‘¢h’¤v•§y–¨–¨™§Š—¦ˆ¢†¡…‡Ÿ€…~œv~™r˜o|–mzju‹et†cn€^fzYcwVYrNUoKMjIHeEHeEJgGHmEHmEFr?HtBHyAK{CG{AG{AI~>K€AQ‚CT„E[„G]†Il…MnˆOs‡Rq…PpƒLpƒLnInImEn‚Fl~Gi|EcyF`uBYsAVp>Ik1bC6gH=nVEv^OzfU€l`„ld‡om‡ij„gsƒesƒeu…izŠn|Žt‚”z„—{ˆš~‹|‹|ŽžuŒœr‰™p‰™p‰–o‹˜rŠ–r‰•q‡”k€d~‰Zw‚SoAfw9es5cq3`n1_m0\n+[m*[p+\r,aw/dz3j€8n„eHIqT[|^h‰kz’n€—t‹œwŠšvŒ™s‹˜rŒ™sŒ™sŠ—xŒ˜yŒ–~Š•|Š“€Š“€‡‘{‚Œv|‰kwƒes‚]q[r‚Yr‚Yt…Zt…Zu‡Yu‡Yw…Ur€QszIiq@]i8]i8Yj?_pEdxLj~Q7J5H/A,?-<,;*8)7'5&4&1&1$1 - - -"-"- * *")")")%+$,$,&,'.)1+2,3.5 .7.7/8/84<4<6>6>7@7@8B8B8B9C9Dat=_r:Yn;Wm:Sk9Qh7Ri8Ri8Uj9Uj9[k=]l>_l>_l>[h;Yf8Q_6IW.8I+/@""3")  !!!!!   !!,!(2(6C*ER9Ve>euNpOv‡U|ŽP~‘S’M}J}ŽM}ŽMŒV|ˆRr}PisFU^?@I*.5 $+!&!&$)(.05 48#9>";@$@G$DK(IO*MR-Q[5Ze>coFmxOv†X~Ž`‚“`ƒ”bƒ—[ƒ—[…™V…™V†˜\‡™]‡“c‚Ž^~ƒ[swO_dAIN+07!'  +'5FJ$59 ' !-&&6!4D/HX8ZiIozRvYy†Y{ˆZZc;8A"-2(3 8CKU(T_2\d7]e8_e8di=ciBdjCajB]f>Y`=U\9KR=EM77B50;.$1,(# %"&#,'/(& &$A.$Q?![="\>4\?,T7:H8:H8&-,%,+&22.::?D%G'J(!L*#T.&W2'\/*`3.g72l<9r<>w@OzCT€Id†Ji‹OpŒNqPx’Kz”N‚–S‡œY¡g”¥l–¨z™«}™ªƒš¬„™§Š—¦ˆ¢†¡…‡Ÿ€…~œv~™r~—ny’jvŒfr‡bn€^gyW`tT]qPTnJQjFHeEGdDHeEJgGHmEHmEFr?HtBIzBIzBG{AI|BLBM‚CRƒDU…G]†I]†Im‡NpŠRs‡Rq…PpƒLpƒLnInIn‚FkCk}Fh{D`uB]s@Un=Qk9Gi:Bd59c78b65^74\6/[6.Z5-Z:.[;+\>5fG9kSCt\NyeU€lb…meˆpl…hiƒfsƒesƒeu…izŠn|Žt‚”z„—{ˆš~‹|‹|ŽžuŒœrŠ›qŠ›q‹˜rŒ™sŠ–r‰•q…“j~Œcz†VsOiy;br4bo2`n1_m0^l.Zl)Zl)Zo*]s-cy1h~6j€8n„q‡?q†Cq†Cm€Gm€GmJn€Kn‚SoƒTp„Vp„Vj€TcyM^uDTl:G`7@Y08U35S1,Q4.S70X@7_GElOOwY`bj‹m|”p™u‹œw‹œwŒ™s‹˜rŒ™sŒ™sŠ—xŒ˜yŠ•|‰”{ˆ‘}ˆ‘}†y‹u|‰kwƒetƒ^r€\sƒZsƒZt…Zt…Zu‡Yu‡Yw…UtRu|KmtCbn.:.:,8+7'5&4%2%2#0#0 , ,!)!)"*"*"*"*$,&.&-&-'.'.+2,3-4-409 09 09095< 5< 6=!6=!7=9? :A :A =C"=C"=D!=D!;D!;D!;D!;D!$7>$7=%7=%6;&6;&6:'6:'58(47&46(46(47&47&14&14&43*32)32)21(22%11#02&02&,0$,0$,1",1"+/#+/#+/#+/#'-#$* "* !)&%%$!!!!""$$##$$""####" " $ $ "" " " " " " " " "! ! $$$$ % % % %!#!#"$"$"$"$#&$'$($(%*%*#'#'%'%'%'%')''%+)$0.06*7=1BI,FN1MW,T^3Zb/`h5_k4bm7apYg>aiCbjDbkCajB^h>Yb8MZ8CQ/4F,);"-"%!!####!!!   ! & '-'2=+DN=Q_=`nLn~Pv†X{ŒR|S€’O‘N{K}L{‹Px‡Lt}NmvG[d$+2&-&-'.,3177=%9B#;C$CH%FK(GP(KT+P[2Xc;anAhuHsƒS{‹[‚“`ƒ”b†—`…•^„šY…›Z‰˜_‹™`–e‡‘_†\xUhoGTZ2:@!)/# & '&'%,!,!(57D+IW5[hFjyRrZ~‰Z]svFNQ QR ef4ei-cg+]f+]f+]d3`h6bkHQ6=H;6A4*64#/-%*!&!( & ,+"&#'BcR5VF&\<#X9)V6+X7@TC6I8!,(%/+'44,::?LFJXQ[jSgu_vƒ]y†_yˆVyˆVw†Tw†Tz†Vz†V{†Yy„Wt{SovNejGRW4>D,28 #,&""!!   !"&'),-1 36":!;"@!"C%I'#L*"R*$S,!Y-&]2%`0'c2-l90n;9w=<{AK~DPƒIa†Ie‰LpŒNtRx‘T}–Y‚˜^ˆžd w–¦}›§Šœ¨‹—¨‘–§‘£•Ž¡’‹¡‰ž€™ƒ}—}™~z•zz“svŽor‰fk‚_gyXasRYkQVgNLdLJbJF`GGaHGeIGeICkGCkGCsBEuECwADyCC}?E~@IDKFP‚HT…K_„L`…Mn‰Nn‰Nq†Np…MpƒLo‚KnInImEmEi~Bg{?`vA[r2cE9kQCuZNzcV‚k`ƒmc…pm‡il…hs„dt…eu†hzŠl{s“y…˜|ŠžyžytŠ›qŠ›qŠ›qŒ˜vŒ˜v‹—uŠ•tƒj|‰cz†Vp{Lct4^o/`p._o,]n']n'Yn)Xm'[q)`v.az0g€6k:o…=pˆBsŠDp†Eo…Dl‚Hl‚Hj}Jl€MpUq‚Vn„Wn„Wg€QazKSsCKk;Ab6;\03Z61X4)W9+Z;3^C;fKGrRR|]e…doŽnƒ•s‡šwŠ™tŠ™tŽ›r‹™pŒ™tŒ™tŒ˜yŠ—xˆ“z‡‘y†y…Žx~v|Štwˆjt…gtƒ^s‚]pƒYr…[v†Xv†XwŠWwŠWvˆSu‡Rt‚MlyEdt?br=`sCdwFe{LmƒT2A.>.>,;,8+7+7+7'5&4%2%2#0#0 ,+!)!)"*"*"*"*$,&.&-&-)1(0+2,3-4-4/7/7/8095< 5< 6=!6=!9? :@!:A :A =C"=C"=D!=D!$7>$7=%7=%6;&6;&6:'6:'47&47&46(46(47&47&25'25'43*32)32)21(11#22%02&02&.2&-1%-2#-2#+/#+/#)-!(, ).$&,"#+!#+!('%$!!!!""$$##$$""$ $ ##!!"""" " " " " " "!!   ##$%!&!& % %!#!#"$"$"$"$#&$'$($(%*%*$($(&(&(%'&(+(+(".,&204;.:@4DK/IP3R[1U_4^f3`h5_k4bm7`o;`o;Zk9Xi6Pe9Pe9Nc9Nc9Oa:Rc[i@ckEckEclDajB]gE*/5(0(0-408"5:#8>&;C$=E&@E"CH%EN&IR*KV.S^6_l>gtFo€O{‹[’_ƒ”b…•^…•^ƒ™Xƒ™X†•\†•\‹•d‹•d„ŒbzWovN[a9BH*/4& 07/$-, $((57D+IW5[hFjyRrZ}ˆYŠ[‡ŠY…‡W€Nz{Iqu9gl0ai/`h.`h6bj9bk>AOHMZTZiRft]u‚\w„^yˆVw†Tx‡Ux‡Uz†Vz†V{†Yy„Wu|TnuMbfDRW4>D,28 $- '##"!  ! ""$$''*,.1 5!7#:!!>$!B$#D&"K)%N,%T-&U.#[0(`4)e5,g7.m:3r?:y?>}CMFQ„Je‰LfŠNpŒNsŽQx‘T|•X…š`Š f‘¢x—§~©Œ©Œ˜©’•¦Ž¢” ‘Š ŽˆŒ€™ƒ}—{–{y”ywprŠkk‚_d{XasR[mMRdJO`GF_FF_FF`GF`GEbGHfJCkGCkGBrADtCCwADyCC}?E~@IDKFS„JT…K`…M`…Mn‰Nn‰Np…Mn„LpƒLo‚KnInImEmEh}@fz>`vA[r0^@7cG@kOMxXWaj‰ir‘q†™v‡šwŒšvŒšvŒšqŒšqŒ™tŒ™tŠ—x‰–v‡‘y†x…Žxƒw|Št{‰rwˆjt…gtƒ^s‚]q„Zq„Zv†Xv†Xu‰Vu‰Vw‰TvˆSt‚MlyEhwCgvBexGgzJhPmƒT2?0=.;-:,:,:)7)7&5%4'2%0#1#1",!+"+"+"*"*"*"*$,&.'/'/(0(0-5.6,6,6/9/90:2;5=5=6>6>9? 9? :A ;B!>D#>D#>E">E""7>"8@#8@#6<"6<"7>$7>$6<$6<$5:$5:$48%27$47&26%26%26%14&14&34$34$23#23#03%14&62#73$:5$:5$9696=7:5;6;6567817/4)4%0!,&"!""$&%'%%&' ( ('%$$# """  ! ! ! ! """" ###$ % % % %!#!#"$"$#&#&#&$'$($($($($($(&(&(&+',)* ,,,3-172=B-BF1HP+MU/Va-Yd0\g1]h2_n5ao6]n7\m6Yi9Vf6Tb9Tb9Ta;Ta;Va?ZfD`jFblHdlHemIckEaiCZf;S_4HV4;I'+<$$5)$!!####!!##!!!!    !&%+5&7A1HS8WbHguLrV|ŒU}V|OyLyIxH{L}ŽM|†Ur|JhoGX_7EK*8>.6/7194;7? ;C$=G#=G#BH#DJ%GP(KT+IT+P[2\i%," #)'25@,IV6XeFnxRxƒ]„‹\…Œ]…ŽZ„ŒY€ˆPwGpx@jr:aj4^g1di9fk:gi@hjA]f>Yb9N[;GT4>H78C1,62$.*"$ "&'!(")!,!,"*#2;TodOj_*X:+Z;#Z1*a8HeLA^E$2)#1()770>>@NGMZT^lUhw`u‚\w„^w†Tx‡UyˆVyˆVz†Vz†V{†Yy„Wt{SlsKchEQV3?D/27"$,"&%$""$$$$%$#$&', -!02!5!8$<%@(D'$I,!O,$Q/ V/#Y3!`2$d5&i7*m<,q?1vD8{C=€GK„IM…J]ˆM_‰Nn‹PqŽRxW}”\…–jŠ›p „—§‹”¦š”¦šŽ¡ž †œ–„š”ƒœ•}–{•Šv…t~p{qŒqj…jj€abwYYkQSeLN^IK[GFYIFYIDZIF\KDaFDaFAhDBjF?nBBpDCwCDxDCzDE{EJ‚BN…EX‡FYˆGa†Gc‰Jo‡Lo‡Ls…NpƒLq‚Kp€Io‚KnIm~Kl}JdxCdxC^u?Zp;Oh8Ib3:a28_03\32[2-W3,V2)U1&R-(S1*V3)W91_A7gRBr\OzgT~lfƒod‚mnƒgm‚fs„ds„du†h{Œn~wƒ•{‰šƒŠ›„{ŒœzžwŽvŒœrŒœrŒ˜v‹—u‹—u‡’p‚hz‡`v…Jk{?bt1]o,\k)Yi'Yk#Yk#Vn!Xo#[s&`w+d~1g4n…:qˆ>r‰Cn†@n„CmƒBkGl‚Hk~Kn‚Oo…Xp†ZlƒYk‚XbQ\yLPsBIl;;e:6`5.]8/^9,^A0cF7iHBtSP{YY„bmŽkv–s†›s‡œtŠšsŠšsŒ˜vŒ˜vŒ—xŒ—xŠ–y‡“v…uƒt~Žt}Œsw‰pw‰pu‰jr…gs†ao‚]s†\q„Zv†Xv†Xu‰Vu‰Vw‰Tt†Qt…KpHizAgx>eyFk~Kj‚Qo‡U0=/<.;-:,:,:+8)7&5%4&1&1$2$2",!+"+"+"*"*"*"*$,&.'/'/(0(0-5.6,6,6/9/90:2;5=5=6>6>9? 9? :A ;B!>D#>D#>E">E""7>"8@#8@#6<"6<"5;!5;!6<$6<$6;&48#27$48%47&26%26%26%14&14&34$34$23#23#14&25'<8)=9*A<+A<+@=%@=%C>&E?'F@(F@(BC%AB$9? 9? 1XcB`jFblHdlHemIckEbjDZf;S_4ER09G%):#!2&$!!$$##!!##!!!!   "!%/ 1;+CO4Q]BesJo}Tw‡P{‹T{ŽMyLxHwŒGz‹K{Lz„Rv€OlsK\b;NU4>D#2;2;4;6=!8@!;C$>H$=G#CI$DJ%GP(GP(GS*LW/Xe7anAhwLr€U{‹[€`„“_…•`‚—X‚—X„—W…™Xˆ–`ˆ–`‰‘fˆ^v|WkqKU\9?F#)1# !+"%.&  "&'25@,FS3XeFmwQw‚\ƒŠ[†^‰‘^„ŒYƒŠRyIu|Dnu=en8bk6fk:gl;fh?cf-\>)`7%[3MjQSpV0>5#1('442@@CQJP^W_mVixau‚\w„^x‡Uw†Tx‡Ux‡Uz†Vz†Vz…XwTu|TmtL_dAQV3?D/27"$,"&&%##$$$$$#$&'(, /#3" 6%7# 9%>&@( E)$I,"P.%S0"X2&\5#c4'f7(k9,n=-r@2wE7zA<~FK„IP‰M_‰N`‹OoŒQoŒQxW}”\†—kŒqŽŸƒ•¦Š‘¢–‘¢–Šš‰œ™„š”‚™“~—‘y’Œv…u„qŽ|n‹xmˆmgƒgbwY[pRUfMO`GJZEHXCDWFEXGDZIF\KDaFDaF@gCAhD?nBBpDCwCDxDE{EE{EKƒCN…EX‡FYˆGa†Gc‰Jo‡Lo‡Ls…Nr„Mq‚Kp€Io‚KnIm~Kl}JeyDcwB\s=Vm7Le6Ha28_06]-/Y0.W/+U1*T/(T/&R-#O,&Q/&U7.]?5eP?pZKvdR}kb€kfƒonƒgm‚fqƒcu‡fwˆj|o~wƒ•{ˆ™‚Š›„Žž|{žwŽvŒœrŒœrŽ™w‹—u‹—u‡’pŽgy†_r‚Fhw<^p.Zl)[j(Wg$Xi"Xi"Um Vn![s&`w+f€3h‚5n…:n…:pˆBo‡An„CmƒBl‚Hl‚HmNn‚Oo…Xp†ZlƒYk‚XbQ\yLOrAHk:=g<;e:3a<2`;.`D5gK=oMEwVT€]]‰gq‘nw—t‡œt†›sŠšsŠšsŒ˜vŒ˜vŒ—xŒ—xŠ–y‡“vƒt‚Žs{Šqz‰pvˆnvˆnu‰jr…gs†ao‚]oXq„Zv†Xv†Xu‰Vu‰Vw‰Tt†Qt…KpHj{Bhy@eyFl€Mj‚Qo‡U/>.=-;,:*:)9'8'8(6 '5 &3 &3 &2%1 "-!, "+"+"+"+"*$+$-&/'1(2(2)3-3.4/5/52929294: 6=!6=!7>"7>"9? 9? ;A#=B$$LC)QF(SI*SK&SK&WMXO XOZR!SSSSOQPRDM?H0=(5+(!)$+*.-214253759=;=;=::88854-4*1(.'- +)#"$$$ """"!  !!###$ % % % %!&!&"'"'!&"'"'"'#'#'$($(%*%*&+&+(-"+1*0,,2/7:+?B4JN/OR4U^/Xa2[e-_i1_j/`k0^m4_n5_m8\i5Yh5Xf4Ua8T`7Vb9Ua8Xf=]jBbnEcoFhoGjpIdmE^g?Yc?QZ7>N33C''4),!"  """"$$$$$#!!!!!!!!    ! !,!*5*>H0NX@_kGlxTt†QvˆS{L{LuŽDuŽDuŒHyL~‡R{„NrwKgmATZ2GM&:@8=8?:B=D!?F#@H#AI$EI$FJ%DJ%IO*KS-NV0Pa5Wh<^rJg{Ss†\|Žd•b‚–c‚•\‚•\‚–Zƒ—[…•^†—`‰“i„c{„^pxR]c=HN(16#(   !$ &)15>+ES7TbFetOr€\{Š]~Ž`„‘\€Y~ŒUy‡Pt}Glu?hq;en8bj=_g;Ye:T`5MV5GP/>J46B+%4*+""$" %$'(*+"-"-$1$1,/,?C2]I+VB S4#W8<^D;]C%61 2,&571ACGQMR]Y`nXlzcw†_u…^{‰Ww†TyˆVyˆV{‰T{‰Tz‡UwƒQqzRjsK_gDNU2>F.09 #-%!+"%!%!%"$!" " " " ""&( +!,"/"/"3! 8':%<(@(C+I+ L. Q0#U4#\1&`4$d5'f7(n:,r>)vC,xE6~E;ƒJMˆLOŠNbŽPcQqŽRrS{\€“`ƒ‘sˆ—y‰™‹Ÿ‘‹Ÿ›Šš…›œƒ™š{•šx‘–t“qlŒ‡j‰„kŒ{e†uj„ka|c]tYUlQL_GEX@DVHDVHCTHCTHBWFCYG?^GA`I=dC?eD>mA?nBBuCDwFE{EF|FL‚HN„IU„HV…I`„Hb‡Jq†Nq†Nr„MpƒLo„Gn‚FpƒLo‚KpƒLl~Gh{Dbu>\n>Ug7Gf3Ca/7^.6]-.Y+,W)&R)%Q(#M("L'!L,"M-&Q7-X>7aJEoXNsdW{lg€lhmqƒgr„hoƒdr…guˆlzŒp}Žw‚“|‰˜Š™€Žž|{ŽœzšxŒ™wŒ™wŒ˜yŠ—x‰–v…‘r‚ay†Yq‚Bhy9^q%Yl Vj"Vj"Si!Tj"Sm"Wp%Zt)_x.e€0hƒ4m„8n…9pˆBr‰CnƒElBm€Gm€Gj€MkNj‚Ul„Wg„VeƒU^€QXzLKrBDk;8f=8f=0bA0bA,gG2mMAtPH{W^„^ggv“l|™rˆ™tˆ™tŒšv‰˜sŒ—x‹–v‹•xŒ–yˆ“xƒt€Šr~ˆpz†ry…pp†nq‡ou‰jr…gr…[oXu…Ww‡Yv‡Vv‡Vw‰TyŠUwŠSv‰Rt…KpHnEk}Ci}Hm‚Lo…Qs‰V.=-<,:,:)9)9&6 &6 (6 '5 &3 &3 %1 "/ "-!, "+"+"+"+"*$+$-&/'1(2)3+4-3.4/5/518294: 5;!6=!6=!7>"7>":@!:@!;A#=B$"7>"6=!6=!6<"6<"6<$6<$6<$6<$5:$48#27$48%47&26%06%/5$13'13'01(/0'54$65%>9&C>*OE+SJ0YN0[Q3[S.[S.`W)cY+d[*d[*\]&\]&\])Z\(OX)HQ";H!/<$0".'.+2266: ;> =A#?C"BF&HG#HG#IG%GE#DC#@@ 7=4;18/5$0!,(%$$"""""!   !!###$ % % % %!&!&"'"'"'"'"'"'$($($($($(%*&+)- */#,2/41396?B4FI;OR4TW9W`1]f7`j2`j2al1dn4^m4]l3an:^k7Zi6Yh5Xc;Xc;Xc;[g>]jB^lCbnEcoFhoGgnFdmE]f>V`=LV29J./?$%2'*!!""""$$$$$#!!!!!!!!!     )'1'8B*HS:Xd@gsOp‚Mu‡RyŽK{LvEtBt‹GvJ~‡R{„NtzMmsF[a9NT-@F :@:B;C>E"@G$@H#AI$FJ%HK&DJ%GM'HP+MU/K]1Rd8XlD`tLn€Vx‹a€“`•b‚•\”Z‚–Z‚–Z†—`‡˜a‰“i†e€ˆcwYflGPV0:?#).  %%.5>+BP5R_DetOr€\yˆZ€a„‘\€Y~ŒUy‡Pu~Hmv@gp:dm7_g;]e8T`5N[0IR0@I(5A*,8"+"&"!##'(+!-#.#.%2'5(,3FJ2]I$O;Q2Q2AbH1S9/* 2,)9;5DFHRNU_[bqZn|fw†_w†_yˆVyˆVyˆVyˆV{‰T{‰T{ˆVx„Rr{SluM^fBMT1=E-3;#%.&( %!%!%"%"#!" " " &!&!&( +!,"/"/"3! 8';& =)@( D,J,"O1!S1$V5$^2&`4'f7)i:(n:,r>+wD.zG9€G=…LN‰MSŽRbŽPdRt‘Vu’W|]•bƒ‘sˆ—yˆ˜ŠŽžˆ›˜†™–€—˜–—y’—v•qoŠj‰„h‡‚e†u_€od~f]w_YoUQgMH[DCW?DVH@SDASGASG>TCAVE=[D@_H=dC?eD>mA?nB@rACvEE{EHHL‚HN„IV…IV…Ia†IcˆKq†Nq†Nr„MpƒLo„Gn‚Fo‚KnIl~Gi|Eew@_r:Yl6>9@:B:B;C=F%=F%=F%=F%=F%=F%"6=!6=!6<"6<"6<$6<$6<$6<$6;&6;&5:$5:$48%27$26%26%/5$/5$02&13'63(63(:7%<9'A>&GD,RK*XQ0^Y,c^1da/da/ec/gd0ig1ig1hg,hg,fh+hk-cg+]a%NT!EK9@6>)?M9O_=`pMnMrƒPw‹Jw‹JyŒGyŒGx‹FzH}ŒLz‰IuKo{EbkF ?G!AI$@K%@K%BJ&DK(FK(FK(FM*KR/ES/JY4Qc@[nKdyUnƒ`x`}”eƒ—]ƒ—]…˜Z…˜Zˆ–`‹˜b‰•eˆ”d‚‹ex€ZmrOY^;CG(14" +%'15?)EQ3T`CdsNq[}Š]€`]]~‹Wy†Rq{Hhs?fq=`k7\f;V`5MV5GP/:F05A*(5*,!#! !!&')*+ ,"-"- . .(0BT\:UJ6PE(P0)Q10S7;]A&9/"6,*<76HCKUKU`Ugt[s€gx‡`x‡`{ˆYw…Uw…Ux†V}ˆY}ˆYz…XwTqzRjsK]eAMT1PD;OB>RFau;^q8Vj5Pd/B_,?[)6X.2U+-T+)P(#L# I!FCCG !K-)S57ZIAeSQo_Yxghml…qr„ho‚fo€irƒky‰t|Œw€‘y„•~‰˜‹šŒœzŠšwŠ˜vŠ˜vŒ™wŒ™wŠ—xŠ—xˆ•uƒq„‘d|‰[q‚Bfx7_r&Yl Wl"Wl"TlUm Vp#Zt']w*`z-f€3iƒ6n…:o†;o†Co†Co„Go„GlIm‚KlQnƒRm…Xm…Xh…WeƒU[‚KU|FGt?An97h>5e;1fD6jI5oO:uUI|YT‡cgŽen•l˜o‚›sŒ›t‰™r‹—s‹—s‰“t‹–v‹•xŠ”w‚Žq~Šl{‡ly„inƒinƒijƒioˆms‹eq‰cq‡[p†ZuƒZuƒZw‡YyˆZzŠZzŠZzŒWw‰Tv‡Nr„JnEk}Cj~InƒMp†RrˆU.:-9.9.9.8-7+6*5(5'4&1&1%/%/",!+'-'-(.(.(.(.*2-4(2)3,5-7/5/507182929294: 5=5=6>6>9@:B:B;C6>8@#7>"6=!6=!6<"6<"6<$6<$6<$6<$5:$5:$48#48#27$27$26%15$/5$/5$/1%13'63(63(;8&<9'C@'JG.TM,ZS2`Z.e`3hd3hd3if2jg3lj4lj4nm2nm2ln0nq3jn2fj.Z`-QV#CK AHFJLO$UV"XZ%ac(ce*ik)ln,pp,qq-qr2no/jo0gl.ce._b+XZ,TV(FL$@F4>,6#/ ,('%%#""" !!$$$$!&!&!&!&#'$($($($($(%*$($+$+%*&+&+', +.$.127/7;4@A1FG7LS0PX4Zb/_g4al1bm2hr3is4kw3oz7jz5jz5ew5cu2]n7\m6\i<\i<]hB`jD`mG`mGcnHcnHfpGdmE[hBXe>LX6CN-0A*'8!+ $!!!!""$$$%$$########  #!*$,9%8F2IZ7YiFi{HrƒPw‹JyLyŒGyŒGyŒGyŒG}ŒL|‹Kv‚Ls~HktE[d5NV)FN!?G!>F @H#BK%BL&@K%DK(BJ&DI&DI&EL)FM*@N*CR-I[8TgD^sPh}ZtŠ[za„˜^ƒ—]…˜Z…˜Z‡•^ˆ–`‰•eˆ”d†Žh}…_tyVchEMP29< )! "(-7!7@*FR5VbEcqMp~Zy†Y}Š]€Œ\€Œ\}ŠUx…Qq{Hhs?bl8^i5T^3OY.FO.=F%2>(*6 ,!%  !!&'**+ ,"-"- . .'/3EMMh]?ZO)Q1+S4/R5=`C&9/!4**<76HCLWLYcYhu\rfx‡`x‡`{ˆYy‡Ww…Ux†V}ˆY}ˆYz…XwTpyQirJ]eAMT1;C+3;#&/'!+"*&&"(&(&'%&$$"" %!%!' )#("+$-#/&3$5&9'<)!B+%F."K0&O4"S4%V8$`6%a7%f9(i='q>)s@-xH/{J;‚K@‡PNˆOSTfWi“Zy”^z•_ƒ”iƒ”i‡”{‹˜‡•Ž‰—ƒ•—‚”•yŽ”x“r‹qŠn†‰kƒ…e`|z^{oYvjWp\PiUMaNFZGCTFAQC=NB=NB;OBkD>oEBrHF{IH}LLKOƒMY†KY†Kf†Jh‡Kp‡Hn†Gp†Gp†Gp†Gp†Go„Gn‚FlEg{Abv<[o5Th3Ma,>Z(2gE6jI7rQ>yXN]V‰ek‘ip—o€™q‚›sŒ›t‰™r‹—s‹—s‹–vŒ—x‹•xŠ”w‚Žq~Šly„iw‚gj~dj~djƒin‡lrŠdn‡atŠ]p†Zw…\w…\z‰[z‰[zŠZzŠZyŠUw‰Tv‡Nr„JnEk}CkJm‚LnƒPq‡T/> /> +9 ++9 ++9+9)7(6 *5)4(2(2&2&2%1 "/ $-(0)1)1(0(0*2+4+4-7-7-7/70819192;2;4=4=6?6?8A 8A 9B!7@9B!;D"F'>F'7>"7>"6>6>6=!6=!5>$5>$5=%4<$2:"2:"09 09 07$/6"/5$/5$.3%-2#.2&.2&15$26%48%6:'B@*HF0RM.WQ3\Y.a]3dc6dc6gf4hg5ii4jj6no6qs9or4nq3mt1kq.go)`h"V_T]U^Zcci#hm(jq,ls.ru0uw3z}8z}8t}3s|2rz2rz2pt3lo/kl.ef(ad&[^ JTAK7@/8%0+$$$"! !!   "" # #"$"$#&#&$($($($($(%*%*%*$(%*%(&)!('&-,/2)47.<@-@D2KL.PQ3W]0Y_3`i,cl0co+hs0jx-m{/n~3p5r‚6o€4f|/f|/bu7_q3]i@]i@_hE`jF`kJbnLfoLcmIdlGbjDWc?R^:@O86E.+:/#2'&""!"!"$$ % %%%$$%%$$####$$! !!       &")4 8C/IW5WeCgvJr€UwŠLwŠLyŽDyŽDwŠDx‹F}O{ŒNx„Rs€NozFbl8U_,IT AKBLBJFN!CJ"FL$DJ%FK&DJ)BI(I*;H(E)LT.Zb(&4&*#  " "$% ) )!,!,"-"-#.#.%/):DSk\AXI4[9/V50X87_?4($:./=;:IGKWPXc]ku_s}gy†_{ˆa{ˆY{ˆYy‡Wy‡W{‰W{‰Wv‡Vt„TpzOisH]dCNU4R?=T>?WA?Y@A[C>`?CdD@kDAlEDsGEtHHwDKyFT‚IV…K^„I^„Ii…Gj†Ir‡Hp†Gp†Gp†GpƒLr„Mo‚KnIlEfy@bt8\n2Rh0Ka)<\%8X!1R$+M&K #H"DA?<=@!C).P68WGEdSSna[vii|lknqgo~em‚fq†it‡r{Žy’{‚–~…˜|ˆš~šxŒ™w‹—uŠ•t‰”sŠ•tˆ–t‡•s‡•qƒ’mƒ–^{ŽVvŠ@k€6g|+_t#Zp!WmXr[u!Zs"]w%^x+b|/f~6kƒ;n…An…Ap†Gp†Gr„Jr„Jo€NrƒPqƒTs…Wo…Xn„We…TaQVKQyEAtA:m:6jB6jB2mF3nG;tRC}ZRƒ`[‹ho‘pw™x†™v‡šwŒ™s‹˜r‹—uŒ˜vŽ—zŽ—zŒ–yŠ”wƒp~ˆkt„hp€eg|dh~fhƒjmˆoqˆen†bv‰]uˆ\w‡Yz‰[yˆZyˆZ{ŒY{ŒYy‹TwŠSuŠLr‡Hl‚Ai>h€El…Jn†Ro‡T/> /> -: +9 +(7 (7 )7(6 )4(3 (2(2&2&2%1 $0 &.'/)1)1*2*2*2-5,5.8/9/9/7/708082;2;4=4=2;5>8A 8A 8A 9B!9B!;D";D";D";D";D"7? 6>6=!6=!5=5=5< 5< 4="4="4<$3;#2:"2:"09 09 /6"/6"/5$.4#-2#-2#.2&-1%/2"15$59&7;(?>'DC,PK,SN/YU+\Y.^]0aa3ba/cb0gf2hh3lm4op7or4qt6rx5ov3nv0kr-cl)^g$bk'fp,jp*pu0qx3sz5wz5y|7|~:|~:zƒ9u~4x€8x€8w{:ux8uu7rs5ln0eg)Xb*Q[#EN&;D-9%0&%#! !!   "" # #"$"$#&#&$($($($($($(%*%*&+&+'**-(/.,32690apEm|PuˆIx‹MyŽDyŽDx‹FyŒG{ŒN}O{ˆVwƒQtKjtA[e1OZ&GQBLBJDM DK#DK#DJ%FK&BI(@G&9C$6A!3@ 8E%8I+DU7QeD`tTp‚Zy‹d„–a…—b„™\„™\†—^†—^‰–d‰–d‰‘fƒ‹a|‚]ntO\a>DI&,5) !"&&,-3"7>"@H+LT.[c=gqMt}Zz†V{‡W}‰W}‰Wy…Tv‚Pp{Ldp@Wb5P[.FP,>H$1='*6 *" ! " "% & ) )!,!,"-"-#.#.#.$5@OfWNeV3Z8-T2/W7+S4-D7#9,.<:&r?)vC*wH.|M6ƒO;‡TI‰SNŽXf‘\k•a|–d›i„•p‡˜sŽ•—‚ˆ”‹‰•Œ‘}yŠŽxˆŒn…‹k‚‡i€f}{`xt\tpWoeNf[LaRDYJAUD?RBBPBANA?P?@Q@@TAAVBBYCBYCB]DD_FAcCCdD@kDDnGCqEEtHLzHN}JT‚IV…K^„I^„Ii…Gj†It‰JsˆIp†Gp†GpƒLr„Mo‚KnIk~Dg{Abt8Zl0Pe-H]%9Z"2S+M'I ED?>;:;>A'-O5:XHFeUSna[vii|lknqgo~enƒgp…hxŠu|z’{‚–~‡™}‡™}Œ™w‰—u‹—uŠ•tˆ“q‰”sˆ–t‡•s‡•qƒ’m‚•]zŒUsˆ>h|2ey)`u$\r#\r#Zt [u!\v$]w%_y,d~1g7m…=o†Cp‡Dr‡Hr‡Hr„Jt…KrƒPt…Rs…Wt†Xp†Zp†Zf†VbƒRU~JNwC@s?;n;6jB7kC3nG7qJ>wTF]W‡d_lr”sxšy‡šw‡šwŒ™s‹˜r‹—uŒ˜vŽ—zŽ—zŒ–yŠ”w‚Œo}‡jsƒgm}adzbj€hj„kl†nqˆeqˆeuˆ\s‡[w‡Yv†XyˆZyˆZyŠWyŠWy‹TwŠSuŠLr‡Hn„Cl‚Ah€EiGjƒOm…Q.< +.< +-: +9 +)9 )9 (7 '6 +(3 '1 (2)3(2(2)3(2(0(0*2*2*2+4+4+4,7+6-8-80819192;1:1:2;2;5>4=6?8A 8A 8A 9B!;D"9B!;D";D";D"9B!9B!7? 9B#9B#9B#8@!7? 6=!5< 6=!7>"6<$6<$5:#5:#4<$4<$3;#3;#2:"09 /7!/7!/6"/6".4#.4#-2#,1",0$,0$-0.1!15"48%8>&q|:q|:qy8mv5pu2os1mq.gk)\c&QXAM8C+7"/ (&#"!!  !" # #"$"$#&#&$($($($(%'%'$'%('(#+,*.)+/*77);;.EE,JI0QR-XY4^a1ac3di2hm5mv5rz9t4x„7y‡5{‰7w‹6z8tŠ=r‡;l}=k|aoFlzQu„PxˆSz‹Kz‹KzEzEzH~‘K~O{Lz†NmyAbl8T^*HR CMBJCKFL$FL$DJ%CI$AG(>D%3>$0; .9%/:&-@+;N9I]CWkQn}]w†f„”d…–e…˜a„—`…–]†—^ˆ•aˆ•aŠ’gˆe}„\u|TbfDKP-6;&+   #!(%+/46;;EFP%PY*^g8itEt€Qz‡S{‰T{ˆV{ˆVwƒQs€NmxK_j=T[8KR/*Q6/X46_;LfI8R5,>+AS@O\O]j]lz_sf{‰`{‰`{‰W{‰WyˆVyˆVyˆVyˆVu…UsƒSpyQjsK\bCMS4=E/4;&(2(%/$-&,%+'*&)')''%&$&"&"&"&"&$(&*&-(3'!5)9*#=-"B/%D1$M4(P7#T5%V8%_:'a=$g<'j?&qA+vE*wH.|M7„Q<ˆUL‹VPZi”`n™d}˜dœg†—rˆ™tŠ–‹—€‰•…‡”„‚‘ˆ„z‰‡yˆ…s……n€j~xdxrdvj]ocZkXQcPM]BIY=HV;ES7IR7IR7LW8LW8NZ=Q]@O_?RaANc?QfCPiAQjBQoERpGRsGUvJX|HY}J^‚E_ƒGf„Fg…Gl†GnˆHt‡Ns†Ls…PrƒNr„Ms…No‚Km€Hm€HgzBct;[l3Sd+M^%@V#8N.J+F#B"A=<778= B(-O59[GDfSUs^\zeh€hl„kn…dlƒbn„erˆiw‹q|‘w€“|ƒ—‡™}‡™}‰˜sˆ—rŠ–r‰•qˆ“q‰”sˆ–tŠ˜vˆ˜o…•k”ZyŒRr‡;i~2ez'cx%^t&]s$]x$_y%_x&b{*b|/f€3h€:m…?p†ErˆGp†Jp†Js…Ns…Nu…Uv‡VsƒZw‡]p†Zp†Ze…Tc„SS{GLt@6?7@8A 8A 8A 8A 9B!;D"9B!;D";D"9B!9B!;C$9B#8@!8@!7? 7? 6=!5< 5< 6=!5:#5:#6<$6<$3;#3;#2:"2:"09 09 /7!/7!/6".5!/5$/5$,1",1",0$+/#+/-004!15"5:#8>&>D#BI(GN$JR'GR%GR%FR GS"GT FSO[#Ua(]g*cl0hp/ow6r{8r{8p|6my3kw3mx4qy8px7my3my3ky0ky0ky0jw/my3my3nx7oz8oz8nx7qy8s|:vz8vz8vz8sw4kr5cj-R].IU%9E!,8!-&""!!   !" # #"$"$#&#&$($($($(%'%'$'%(!*+$---1,04.==/DD6MM4SS:VW3]^:bd4ik;mr;qv>s|:x€?z‡9}‰<;;“>}‘C'+0#!#%%+)039:@!AK KT*U^/_h9juFt€Q{‰T}ŠU{ˆV{ˆVv‚Pr~MkvI\g:NU2EL)5@',7*%  !!%% & &( )!,!,"-"-%/%/&1&1"75SgfLrX2Y>0Z5(Q-HaDPiL9K8%p@&qA*wH.|M6ƒO;‡TL‹VPZi”`n™d€šfƒžj‡˜sŠšv‹—€Œ˜‚†’ƒ†’ƒ†~Œƒyˆ…x‡„r„ƒn€j~xdxrcui\nbWiVOaNM]BK[?KX=LZ>MVS^?S^?T`CVbEUdDWgGUjFUjFUnEVoGSqHXvLWxLWxLY}J\L`„H`„Hg…Gh†Ik…Fl†Gs†Lr…Ks…PrƒNr„Ms…Nr„Mo‚KnIl~Gfw=_p6Xi/Qb(BX%7M*E&B!@=;864 6=!C).P6:\IFgTUs^]{fh€hiin…dlƒbn„erˆiw‹q|‘w€“|ƒ—‡™}‡™}Š™t‰˜sŠ–r‰•qŠ•t‹—uˆ–t‰—u‰™p†–m”ZyŒRr‡;i~2ez'cx%aw(_v']x$]x$`y(c}+d~1g4k‚5>7=9? 9@9@:A :A :A :A ;D"!4>!4="4="5>$4="2: 2: 2: 2: 08"08"08"08"06%06%/5$/5$,5!,5!+3"+3")0!)0!)/#)/#)-!)-!+/#+/#+4 .7$-919J@Q%M](Td/_o4eu9ny>oz?px>ow=jx:hv9dz4av1Zt4Tn.Hi)Ef%Ef%Ij*Tn.Tn.Uo0Uo0Yp,]s0ds3gv6pz4r|6v7x€8|ƒ9x5ir.]f#KV"?I)3!+%$##  ! !!#$ %"'!&!&!&!&$($(%*%*$*%+!')#*+//(55.><(DB.RN,XS2[]/]_1_h)fo0iu/o{5r4v…7x‰6{‹9~7‚’;<†•@}“B{’Az‹DvˆAl|LgwGdqFcpEamIamIalM`kK`kJ_jIWfHP_AIZCCT=6E50?/%2.*&" "  % % % % ( ( ( (&&!)!) ( ( ( (!)!)!& % & & & &$## !!!!   "*$%0*0?(BP9WfAetOpOt…Rw‹Jw‹Jv‹FwŒGxŒKyL€R€R}‹Pu‚Gkv@^j3Q\(HSFN!FN!GM'CI$DK(AI%;E&4>+4 '0#/!#/!'5'0>1AP>ScPcsYtƒj‚g‡”kŠ™e‡—b‡–]†•\‡–]‰˜_‹”k‰’j„Œf{„^ouP\b?U=R]IalXr}by„i}Šd{ˆa}‹[|ŠZxˆSxˆSx‡Ux‡Uv„Tr€QszPkrH^bBOS3$\<%]>%c>'f@"i@*qH+uI1{P8~P=ƒUJˆXPŽ]h‘bm–f~—j™lŠšv‹œwˆ˜xŠ™yˆ•|‡”{{Œz|‹{{‰yy†zu‚wr€pn}mkwabnX`mGXe>S`:Q^7V^:V^:U`3Yc6[g5]i8bn5>5>8>:@!9@9@:A :A :A :A 9B!;D"9B!9B!7C!7C!6A!4>4>!4>!4>!4>!35FCRM](Yh-]m1gr7ku;ow=mu;jx:es5]s-Vl&Ke%E_7X4U4U4U=W@ZA[F`!KbOe"[i)^m-jt.pz4x€8z‚:†<}„:w€…”>‡—@ˆ—C‰˜D€—F{’Az‹Dx‰Bl|LhyHgsHfrGamI`lH`kK_jJ]hFZfDP_AM\>AR:9J2/>.&5%*&$ " "  % % % % ( ( ( ( ( (!)!) ( ( ( (!)!)"'"' & &"'"'!'%$#!!!!   "' #.'/>'>M6Ra=`oKm~Ks„Qw‹Jw‹Jv‹FwŒGxŒKyL€R€RŒQz‡LnzDbm7Va-MW#GO"DM FK&DJ%AI%?F#7B#0;)1$-*!,!/!,:,;J8N]J_nUp€f€d‡”k‹šf‰˜c‡–]†•\†•\‡–]ˆ‘iˆ‘i†Žh~†`v|WdjDNQ3;> ,0)-*.,059;?DGKN"PW&V],`e4fk:lwCvM|†UˆW~‰Z}ˆYw‚Sq|MhtIZf;ER-5A'0(! !!##$$'( ) )"*"*"*#,"/#0$/(3"4:=OU/PA3SE-Y4(T/7Z51U/.C+:P8U`Ldp\t€e{‡l}Šd|‰c|ŠZ{ˆYxˆSxˆSx‡Ux‡Uv„Tr€QszPlsIbfERW6#b=(gA$kB(oF(rF.xL7}O:€SI‡VPŽ]i’cn—g‚šm…žq‹œw‹œwŠ™y†•u†“z„‘xŒz~‹y{‰yzˆxw„ytvo~nlzjkwadpZanHZgAYf@S`:Za>[b?[e8]h;am;co=er@fsAivIivIi|Ii|Im|OoQlRlRl„Pl„PrŠRrŠRrŒMrŒMr‹Gr‹Gq‹DrŒFuˆHt‡Gt„Mt„Mt‡OuˆPt‡Ot‡Ou‰Os†Ln‚Fi~Bfx<\n2Ug+L^":Q3J'A$>;:876<!C).P69[GFgTVrd_zlj}mknn€fq‚it‡iw‹l|s“w…•z‰™}‹™|‹™|Ššwˆ™v‹—u‹—u‰–vŠ—xŒ™wŒ™wˆ™m…–j—VyMr‰8j0h)f|'ay#_x"[z ]{"`z-b|/d|4h8o„?t‰CuˆHw‹Jt‡Nt‡Nu†Tv‡Uu†\xˆ_x†bx†bq‡[o…Xb„O\~ILy>Dq69p99p92oA4qC7xLA‚UN‰bUhl”ns›uƒžu„ŸvŒžvŠ›tœuŒ›tŽ˜y™z˜}Œ•{Š”wƒp{‡ju‚dez^auY\xaa}fd…mf‡op‹dl‡au‰ZsˆYv…Zv…Zy‡\y‡\wˆWwˆWyŒRyŒRu‰MtˆLp‡Do†Cl„Cn‡EqˆIs‹L0: 0: /8/8-6-6-6-6-6-6-6-6/6/6/6/6-6-6-5-5.6.6-5-5.6.608191:2;2;2;4<4<4<6>8>8>8?8?:A :A :A :A 8A 8A 8A 8A 6A!6A!5@ 5@ 3< 2;30F!6L&DT&K[-Yg.ds:t~8w;|ˆB€F~ŠDv‚A*EH1RT-UW0ad1ei6hs1ju3pz4t~8v…7|Š=}Œ<€@’@…•Cƒ–Jƒ–J€’H“I}PxŒKt„MoHmzLjwJdoIcnHclKajI]jJ\iIQdHL_C@TA:O;6G;,>2$4-,%#!! # # % %!&!& ( ( ( (!)!)!)!)!)!)!)!)"*"*"'#($*%+&,&, %+$* & &#!!!##  #&" +',9%{LH…WXŒ_`”gp˜murˆžvˆžvŽvŒ›tŽœx›wšzŽ›{˜}Œ•{ˆ‘uŠnt…el}]^xY[vVZwe`}jf‚mi„poˆfk„cr†Zq„Xv†Xv†XyˆZyˆZyŠWv‡UyQzŽRvŒKu‹Jr‰Co‡AnˆBsGrŠIrŠI0: 0: /8/8-6-6-6-6-6-6-6-6/6/6/6/6-6-6-5-5.6.6.6.6080819192;2;2;2;4<4<4<6>8>8>9@9@:A :A :A :A 8A 8A 7@7@5@ 5@ 4>4>3< 2;2: 2: 3".8")2!*%!!) "0'.?1BRDSdOdt_z„eƒŽnŠ˜hŠ˜hˆ—^†•\†•\†•\ˆ”d‹–gˆ‘i„d}ƒ^tzUdjDTY4AG!9?:@=DDJJPPWTZX_'\d,bj2ks;nyEvM}‡W‚‹\Š[~‰Zz†Vt€QnvL`h>KU18B(/ '   #### % %( )((")")"*$-#1#1'-(/&96Ob_VxcHjU.]8%S.(U.$R*(bB"dA&iE&lC)pG3vK:}RI„VR_k”do˜iƒšr„›sŠœrŠœr‡˜s…–qƒ‘s€qŒs}Šq|Štzˆqz‡nw„kw…ar€\n~PgwIdv<`q8dr4bo2cq2ds3dt0fw2gy1j{4m6m6m=p‚?n€DoEt‚Mx…Qw†[v…ZyŠWyŠWyUyUx‘MwLyLyLuGt‹Ft‡Gt‡Gt„Mu…Nu‡RvˆSzŒWzŒW{ŽU{ŽUzŒWvˆSuƒSqOhvFao?Xg3M](CU 9J.D*A "< :5;#B+1P9>^KKjX[tgb{nkinƒkq…ds‡ft‡izo}t‚•x‰™}ŒœŽŒš}Ššwˆ™vŽ™wŒ˜vŒ˜yŒ˜yŒ™wŠ˜v‰šnƒ”i€–TwLqˆ7i€/e{&e{&bz$ay#[z ^|#_}-c€0g7l„4>!4>!2: 2: 2: 2: 09 09 2:"09 09 /709 09 -7!.8"08%-6",5!,5!-6"+4 (2!'1'0!%/ $- #,"* "* !*!* ( (''&%####$$##',%5,< 7G>N$AU)AU)=Z,N IX+\j+fu5r‚=xˆCŽH}ŒEˆDt~:gq2V`!OX DN:D6@17.5 !- )$ +"    !!!$ %!&!&"'"'$($($'%(!')%+,.0+340?>,IH6TV/Y[4af5fk:hs8mx=p|?tBy…A|‡C}ŠAŒDFFƒ“I…•L•M~‘J{LyŽKr„Ip‚Fm}MizJcsJ_pF]hF_jIXeFUbBK^BEX<=P?8K;,@:$82/,)&%$$# % % % %!&!&!&!&"'!&"'"'#(#("'#(!)!)!)!)"*"*#+%-&,!).!).$+1%*.#)-$(#' & &%%#!!# #%('2,2=7BU8PbFctHm~St‡Nt‡NtŒDtŒDvŽAvŽAzŒJ}LŽS~RtƒJkzAbm7Wc,KU$FO@I(=F%7@'3;#*5#$/%"&#.',:3;IBN[N_m_tfŒsˆ–mˆ–m‰˜_†•\†•Z…”YŠ”c‹•d”jŠ’g„‰fz\txJae7MW!CLCM EOJTMWT^WaZb!]e$dl-lt5oyAz„K}‡V‚Œ[‹Y‹Y}†YvRkuJ^h>NU4=C")0")  !!##%%&&")")")")#,#,"*"*$-$-&/&/2HKYnrDd\>7 Q9M6#Q5F*4Q:>ZCUePdt_t€e{‡l}Šd|‰c{ˆZx…Xx‡UyˆVyŠSx‰RxˆSv…Qn|SftKZdGKU8>F34=)(4,&2) -) -).).)-&-&*#*#!+"!+"( ( *! *!+ -"."0$0&1'8+#Cr8Ap68p<8p<4vG8zKD‚VMŒ_]Žhg™s{œy~ž{‹žyŠxŽšvŽšvš{š{‘š‘šŽ˜€Š•|…w‰qq‚diz\[wUYtRYzc_€hgkjƒmpˆbm†_u†[u†[w‡YyˆZwˆWwˆWy‹Tv‰RyŒR{ŽUzOwLs‹Cn†>m†DpˆGq‹Lq‹L0; /: /9 /9 -8 -8 ,7 +,7 +/9 /9 /8/8/8/8-6-6-5-50707.8.80:0:0;1<2=2=2<4=4=4=5?5?5?5?5A4@5A6B6B6B5A5A6?6?6>6>4>3=3< 3< 090909093;#.609 09 2:"/7/7/7,6 -7!-6"-6",5!,5!+4 *3'1'1%/ %/ #,#,!) (''''&&%$""""##!!&&+#4.?2B3G7J,J+H @9,+ '&&&$$####$'-#22A@O!P_]l,hy4q<}ŒE~F€‰F}†Bq{Q53G6.A0!50/))&$!$#$# % % % %!&!&!&!&"'"'"'"'#(#(#("'#+#+#+#+"*"*$,$, '-#*/!).#*/&+/%*.!'*$("' &%% %## %"$&)&1+2=7>Q5N`D`qFk|Ps†Lu‰OtŒDtŒDvŽAvŽAzŒJ}L~R{‹Pv„Kl{Bbm7Wc,KU$FO?H'wIz„R|†U}‡V}‡V}†YyTpzOcmBRX7AH'5<(28%" ""##%% ( (")")")")"*#,"*"*$-&/(1*4=RVLbe)IA@8P8L4$R6"O4/K4@\EUePdt_t€e{‡l}Šd|‰c}Š]y†YyˆV{‰W{‹TyŠSxˆSv…Qn|SftK[eHLV9?H45>+(4,&2) -) -).).)-&-&-&-&!+"!+"( *! *! *!+ -"."."0&1'5(8+=/ @2 F4"H6 M8"P;V;!Z>_>a@#f@(kE1tG8{NK…SS[jeq–lƒ›nƒ›n‰šn‡˜l†—k‚“g‘j~h{Œlz‹kxˆmv†ky‰hv…et‚Yo}TmAgz;dy-cx,hy-hy-fy-fy-cw&f{*g})h~+j~-l€0i~9j:jBl„Em‚LnƒMp‚Rr…TuˆWxŠZzŽ[{\y’UwS{’NyLwŒGuŠDr†Jr†JrƒNs…PtˆUx‹Xx\|‘`z`|a~h‘jj{‹dzˆ_rVmwMeoEXe7N[.AR6G)<#7 +8"=&C*4Q8?^NMl\\vfd~nj‚llƒmr„ht‡ky‹o{Žq€‘xƒ•{Œ›‚ƒŒœŠš~™zŽ˜y™z™zšxšx›wŒšv‰™p†–m•YwŒPsˆ5k€-g|"bxZvYuWt$Yw'Yy/\|3f}@lƒFp…Mq†Nt‰Rt‰Rs‡TtˆUw‡]y‰`{‹dz‰cu‡]s†\i„Tc~OT{CLs;Bq7@o56n:8p<3uE:|LE„WMŒ_b“mišt}zŸ|‹žyŠx›wŽšv™zš{˜}Ž–|Ž˜€Š•|ƒŽu~ˆpnaevWZvTZvTXya^ggkjƒmpˆbm†_u†[u†[v†Xw‡Yv‡Vv‡VwŠSy‹T{ŽU|VzOwLtŒDs‹CrŠIs‹JrŒMsN/; /; 0; 0; 1<1</9 /9 /: /: /9 0: 1;0:080818181818/9/90:0:2=2=2=2=2<4=4=4=6?6?5>6?4@4@5A5A5A5A4@4@5?5?5>5>3< 3< 2;2;2: 2: 2:"2:"/7/73;#2:"08"08".5 .5 ,5!+4 +4 +4 *3)1)1)1'1'1#-#-$- ( ( ('%&&%%##!!!!#"!!#"%(0 5"6%995.*& $""##$$!!!!!!&($3+:=K"JX/[g5fsAvƒJ{‡O~‹J}ŠH|ˆBu;o}2iw,fp*ak%W`S[JPAG :> 15,0-13749483715+0&$!!#$ %!&!&!&"'$($(%*'(++)/*,3-:;+BC3OP2VW9^c9bg>ir=mv@p{@s}Cw‚@y„Cx‡AzˆB|ŽE}F{H{HwŠLuˆIp†JlƒFh}LezJcwJ`sGWnESjBPf@Oe?I^BEZ=@TA9BRDTdVjucwp€Žlƒ‘oˆ•aˆ•a†–X…•W‡“Zˆ”\–gŽ—hŠ’g„ŒbˆTs{Hfo0Zd%W_V]W_V]Y`Za\c%_g(_k,eq2hv=l{BtRy‡Wy†Yy†Yy†[v‚WpxRckESY8FL,HQ6CL1&!  ##%% ( (!(")")")#,#,#,#,%*'+".,AMKelk9A@2(%8.:"A)&P0&P01K9?YGVdTdrbufz…k}Šd|‰c|ŠX{‰WyŠS|ŒU{‹TyŠSxˆSw‡Rv~TqxNdgIRV8@F37>+-6.)3*&.*&.* -) -)!-& +% +% +%"+%"+% )# )#,,$0$0%1%1"/&3! 4(!5)!9,#"Y<$[>"`=*hE2oH6?4@4@5A5A5A5A4@4@5?5?5>5>2;2;0:0:090909 09 09 /7/7/7/7!/7!.5 .5 ,5!+4 +4 +4 *3)1)1)1'1%0$.#-"+!* (&'%%%$$##"!!!""!!##$&*+//-) & & $ $##""##  !!!!%&,#2.<;I NZ)\h6myAu‚I}ŠHKŒE~ŠDy‡ABGCHEHBF@E7; (2", ###$ %!&"'"'"'$($(%*)*#..,3-172?@0GH8RS5Z[=bg>hmClu?r{Es}CtDxƒAy„C{ŠC|‹DzŒBzŒBwŠCv‰Bq„Fo‚Cg~Be|?`uD]rAZmAUiNd1!8+0*+&%'$&!&!& ( ( % %!&!&!&!&"'"'"'$(%*$(#(#(#($*%,%,%,%,%/%/%/%/(-(- */"+1*/',$($(")") ( ( % % % %"#&& +%)5.9I0GW>]hFhtRr‚Rt„TsˆIr‡HuŠDuŠDx‹FzH{ŠQ{ŠQw„Po|Hco=Wd2LV+BL"7B#6A!-7!)2+&# $" /**95;K=M]Ocn\q|j|Šh‚nˆ•aˆ•a†–X…•W†’Y‰•]‹”e–g‹“iˆe†[{ƒPoy:cm.^e W_W_X`^e"^e"^f'bi+`l-bo/ds:ix>mzKtRx…Xx…Xx…ZuVmvP_hBPW6NU4@H.:C(("!!##%% ( (!(!(")")#,#,#,#,$((, -+T`^ƒŠ‰SZY!4* 3)*M53V>-W7-W72M;>XFXgWcqaw‚g|ˆm€f~‹e|ŠX{‰W{‹T{‹T{‹T}V{ŠUw‡Rv~TqxNehJUX:@F37>+-6.)3*&.*&.* -) -)!-& +% +% +%"+%"+%!*$!*$!/!$1$*5!*5!)4 )4 %1%12%3'6)!9,=0@4F6H8L7!O9T7W9\8'eB0mF:xPLVT‰^k’cq˜i€›mœn…›n‚˜k…–h‚”f€‘e~c|fzŒev‰dt‡bs„_rƒ^qƒSj|Le|8ax5`w,_u+^t']r&Zt)Yr(Xq'Vo$Sq%Rp$Oq'Np&Lo0Mp1Op;Rt?OqBQsEVsJYwN^|Rb€Wi‡WjˆXoŒQoŒQr‹GqŠFpˆBpˆBr‡Hr‡Hr„Mt‡OtˆUtˆUu‡]wŠ`s‡_w‹dz‹k}n}t~‘u€n|Œi{‰`rVlvLblA[d5OX)?N5C'?)A,K-9X:CaQPn^^xhf€qm…mm…ms†hvŠkzŒp~‘u‚”z…—~ƒŽž…‚Šš~Œ›{‹šzŠ™yŠ™yŽš}Žš}šzŒ˜y‰›m†˜i„šY{‘Pz:q‡1l‚%bxUqTpRn#Up%Wv5^} +1;3> +3> +1: 1: 1< 1< 0: 0: 1:09/6/6292906180:0:1;1;0;1<2=2=1;5?6@6@4=7@7@8A 5>5>6?6?4>4>3=3=4>!4>!2: 2: 09/8/8/8-9-9-9-9/7/7/7.6.5 .5 /7!.5 -6"*3)1)1'2'2$0$0#.$/#-#-)((%&%%#$#""""!! !!""!! !##!""    !!!!'+%50?>L(KZ6Zj2JH2QO9Y\9`c@fm>lrDowDrzFv}Ex€H{ƒD~…G}ŒE|‹DtŠ@r‰?kƒBe}Fb@C^+/6.+2*&-,&-,!+)!+)!*+ )*)')' *!",#%1(3/94>!5?6A5?5?29#.5 $3!#2!3%#6':, ;.?3!B6F6H8N8O9&Y:,_@5kD@vPU„X]Œ`u’dy–i„™o„™oˆ™m†—kƒ”i’f€‘e~c|‹fz‰dy‡ew„br†Zr†ZkNg|Ibx9_t5Zo2Xl0Ri-Ri-Hc-Id.Db2C`0=_1=_1;\7;\78Z9:\<6\=8]>7aC9cF;hH@mLGvJM|PW‚KW‚KbˆGbˆGlŠAn‹BrŠBrŠBpˆGpˆGr„Mr„MsƒSsƒSoXq„Zu„du„ds„mv‡py†y|Š|~z|Œw|Žls…bvUkuJgoCr1>m-6l*6l*,k/2q55yC?„MS‹_[“hu—v{œ|Šœ{Šœ{œuœuŒšvŒšvŽ—z˜|‹˜Œ™€–‚‰’~‚Œvz„ng{[avUWtTYvVVw_]~fj†hj†hq‡_o†^sƒZsƒZv‡Vv‡VwˆVwˆVvˆSw‰T|V|V|’QvŒKt‹GsŠFtKs‹JpŠJpŠJ3= 3= 2< 3> +3> +3> +2< +2< +1< 1< 1<1<09090707181818180:0:1;1;2=1<4>4>2<4=4=4=2;4=4=5>7@7@6?6?4>4>3=3=2;2;2: 2: /8/8/8/8-9-9-9-9.6.6.6.6.5 .5 .5 .5 +4 +4 *3*3%1%1#.#.",#.!* )((&$%%##$#"!""!! !!   !##!""      !!$ (.(72@?M)L\.Yi;jxAv„M|‹K€OGG}H{ŒE€ˆG}…D{„:t}3t{1nu+ov%pw&rw+sy-xx4xx4su5ij*X`&KS0?.   !!# % % %!&"'#(%+%+&)!)-'.-*209=1>B6OM7VT>]_=egEipAmsEpyEs{HwGyI}„E†H{ŠCv…>mƒ9h4bz9^v5Yo:Tk5Ri8Nf5H^8DZ4:U36Q/3L1/H-/G/*C*';/%9,2*0''&&%$'"&#'#'!&!&!&!&!&!&!&!&!&!&!&!&"'$*%+%+%+%+',%+'/'/(1(1(1'0&2%1%/(1)3&/%/%/$.$.$.#-", ) * * * *!&$(*&$1,7@3FOBVcCcpPn~Nr‚RuˆIuˆItˆEtˆEt‡Gv‰Ix‡Lx‡LvƒNp}IcqAVd5JT.?J$7@'3;#)2%#,%"")$#0+.=4APFUdQcr_sƒc~m…–e…–e„—Xƒ•W‚”S„•T‡–]‰˜_–gŽ—h•dˆ^}‹Gn|8gu*_m!^i!]h ^g$^g$\e)\e)Tc*Ve,Sc5Zj,,3-# # #$$ % %$ %!%"&")")%(&*&*&*'-'-%6:CTXQ]]NYZETHHWL%J&!F#/X18`9@ZAC^E\jZgvfsƒgxˆm|Œe{‹d|ŠZ|ŠZ{ˆY}‹[~‹^}Š]|ˆ]|ˆ]wVoxNdlHT[8DH59>+,4,*1)&-,&-,!+)!+)!*+ )* *( *(",#(2)+7"/:&;E(>H+?J$@K%?I%>H$:A+5<'(8%%4"!3%!3%5(9+:.;/A1E4K6O9&Y:0dE8nHCyRU„X]Œ`u’d|™k…šp„™o‡˜lƒ”i’f€‘e€‘e~cz‰dy‡cy‡ew„br†ZpƒWiKcyF`u7[q2Ti-Oc'Kb&H_"@[%>Z#9W'7U%2T%3U&2S.3T/1S21S2-S4.T5.X:/Y;0]=5bB=l@FuIP{DSH_…CbˆGkˆ?lŠAp‰Ap‰Am†DpˆGr„Mr„Mr‚Rr‚RlUlUl|\n}]m~gnhssw…xzŠu|Œw~‘nwŠg}†\s}SmuB_g4W`*JS5H6I4Q1?\ +3> +2=4@ 5@ 4? 4> 4> 0: 3>2<2<1;1;19190808192:/90:2<2<2=2=4>4>4=4=5?5?5>5>5>5>5>5>5>6?3=3=3< 3< 3< 3< 0909/8/8.7.7-9-9,7,7,7,7,7,7+4 +4 +4 +4 )1)1)1)1#."-"-"-",",",!*&&&%#"""!!     !!  !!!!  !!!"'!.#4/?$ˆ>‚Š;x2oz/^jDV1C*"   !#$ % % % %!&#($*%+', (+$,/+2*07/=A.DH5MS3U\;\g:bl?lsBowFs}DuGvFw‚Gw†Kt„IgDe~CZwBUr=Lg>Je4":/5/4.10,+)*%'$&$&$'$'"&"&!&!&!&!&"'"'"'"'!&!&!&!&$*$*$*%+&-&-'/'/)3)3)3)3(4(4'3'3 (4 (4'3'3&2%1%1%1$.#-",",!,!,) *"'%*()$/02>5>KBO]B^lQl~PqƒTr‡Hp†GrˆBrˆBt†Dw‰Fx‡Lw†Kt‚Mp}IdpGXc;KS4?G(3='09#(/'!( " "$$ ,,,:3;IBL^IZlWnawˆj…“j„’i„”]€‘Z„“X…”Y‡•^‰—aŒ—hŒ—h™gˆ’`‚ŒSuGny5co+ak%_i#]g*\e)V`.R\+L[0JY-O];VdB]jJfsTp~Ztƒ^s…WpSVmCKc8J_C-B&&0%-7,!('! " # %"&"&"&"&"&"&"&")")#,$-%.$-'(++60C\UQmXHdOAe?HlFEh<C-R&:`4A_C?]AZn[cwdu…ixˆm|Œez‰c|ŠZ{ˆY{ˆY|ŠZ~ŠX~ŠX~‰ZŠ[x‚WpzOdlHT[8EI69>++2*(/'%,+#+)#,+#,+!+) *(*$!-&&3,9%4B ;I'EUJZ#P_P_Ta!Q]LR&DJ7>"19&3!'4"3'3'6):,?+ B/"K0(Q62\8;eAHrGR|Qc„Xj‹_wf~—m†šl…™k‡™k…–h€“`’_‘b|Ž`{‹b{‹byˆaw†_s‚\n~WjzQcsJYj@Rc9IZ6FW2:Q73J00H20H2-F2,E1+F2*E1(E2(E2'F4(H5%I6%I6$M8%N9&T8,Z>6f>lŠAr‰Cr‰Cq‰HpˆGr†Jr†Jn€KmJiyKiyKgxShyTbt_dwa\xhb~nm„wq‡zwŽsvŒrx‹apƒYqAes5]l,P_GT BO@T3J^=OgUZsaezilpp‡lp‡lvŠkyŒn|s“w…—~Šœ‚……ŸŽž}{Œœz‹šz‹šzŒš}‹™|‹šz‹šzŒŸsŠqŠœ^†™[€™Jx‘BtŒ9l„0h€*f)d{1f|2j€EmƒIqƒSr…Tq„Xq„Xs†\u‡]xˆ_xˆ_x‡`yˆas…^oYg~O`wHQt:Ko4zJHƒS[Šef•o~™y›|Œœz‹›xšzšz™|™|—‘™Œ˜‚Œ˜‚Š”~‡‘{ƒŒqw€fcvWZnOQoTUsWXya_€hj†hj†ho‡\n†[s…Ws…Wv‡Vv‡Vv‡Uv‡Uu‡Rw‰T{ŽU{ŽU{Qv‹MtŠIrˆGn†Gn†Gl…Jk„I4? 4? 4@ 4@ 5@ 5@ 5?5?1<4?2<2<3=3=191908082:2:/90:2<2<2=2=4>4>4=4=5?5?5>5>5>5>5>5>5>5>3=3=3< 3< 0:0:0909.7.7.7.7,7,7+6+6+5+5,7+5)1+4 +4 '0)1)1)1)1"-"-"-"-!+!+('&%%%#""!!   !!      !!!"%&/%50@ak>fqDnuDpxGs}Dt~Eu€EvFt„Iq€Ebz@\t:Nk6Jh3C^5>Y08U51N.*F/&C,"@+!?*!;):(4)3(0*.(*)'&'(%'%'%'$'$'"&"&!&!&!&!&"'"'"'"'%*%*%*%*$*%+%+%+&-'/'/(0)3)3)3)3(4(4(4(4")5")5'3'3'3&2%1%1#-#-#-",#-!,)!,"'%*()$/0/;3=IAM[?\jOl~PpSr‡Hp†Gt‰Cp…@s…Ct†Dv…Jt„It‚Mp}IdpGXc;KS4?G(2<&,6 %-% '!  ""#)*&4-3A:GYDWjUiz\u†h‚g„’i„”]€‘Z‘U‚’V…“\ˆ–`˜iŽšj™gˆ’`„ŽVx‚Jq}9gr.cm'_i#\e)Zc&Q[*Q[*IW,ET)GU3MZ8XeFerRp~Zs‚]r„Vt†XWnDC[0:O3,A%(2((2(!('$" # #"&"&"&"&"&"&"&"&")#*#,$-$-$-())*/)2KEWs^IeP>a+,4,)0('.-%,+#,+#,+#,+!+)!-&#.')7"1?+>K)ER0P`)Uf/]l,^m-_k,]i)X^2PV)3>2<2<2:2:1919291818180:1;2<2<2<2<2<2<4=5>5>5>5>5>5>5>4?4?3>2=3=3=2;0:0:0:.7.7-6-6-6,5+6+6+6+6+5+5*4*4+4 )1'0*3'2%1'2$0",",#-!*,*('&%%%""!!  !!          !!!! !!""$'!4)< 0F%7M,>W-B\1Md5Pf7Sm4Ys:av9fz>s‚B{ŠJ}F„–Mˆ›OŸS‘¡V“¤X—§\“¤X“¡VŽœQ‡•J|Š?kw7Xd%@N#+9"   $$$$& (!)!)$(%*&+&+)+ +.(0,,4008.6>3BF1JO9W\9^c@gmAkpDowFryHq{It~Mt€It€IjGezBTo?Ni:?\:8U3-J6)G2$@2"=0"91!804.0*.+,(-),())((&%&%%(%("&"&"&"&#'#'"'"'"'"'"'"'"'"'$($(%*%*%,%,'-(/'/'/(0 )1)3)3*4*4!*6!*6(4(4 (7 (7'6'6&5&5%3%3%1%1#.#.%.%.!*!*"*%,(-#,2-:/;H=IW@WeNgwPm}Vp„Jr…Kp„Cp„CqCs„FtƒJs‚IsOp{LdpGYdF05<'(/'!( " " !&!&!#!#"$"$!(!(#*%+#,#,%.$-%(),0'-F>AcGAcGCiWoI_xPhXpŠ_x’gƒ–j†™m‹›kˆ˜h‡–h„”f€‘ed|c{‹b~f|Œey‡cw…at„]pXhuO_lEVb>GS/1&=0%>1%>1!=4 <3 <3 <3 ?5>4D4D4#J6+R>4^:=fBHs`ƒBj†@l‰BrˆEp‡Dp†GsˆIs†Lp„Js€LlyEhrC_h9N`@J\;CZDH_IC_VJf]Qqc[{leƒgi‡ktbr_p‡Hi€Agy0]o%Wi'Tf#Nd8Tj>[nVdw`g~hm„nr‰nr‰nuŠnyŽq|‘w€”zƒ—ˆ›„ŽŸ‡ŽŸ‡ŽŸž€‹›Šš~Šš~Šš~Žœ~šxŒ™w†™o„–m–^{Y|”L|”L{‘G{‘Gx>sŠ9t‰?t‰?vˆLw‰MtˆUtˆUr…[s†\u„_x†bz‡gy†fx…eu‚cp\j{V_xIXqBKl2Cd*4c"3a!+a$,c%.h.6p6BxMNƒXgko—s…›z†œ{›~Žš}™z™z˜|˜|Ž˜‚™ƒ˜„Ž—ƒŠ“€…z€ˆns|aatX\nRQnWWs]Zzealk†kj…jo‡\lƒYs†Ut‡Vv‡Vv‡Vu…Uu…Uu†Tv‡UwŠSwŠSuKqŠFn†Gk‚Cm€Gm€GiƒKgH5@ 5@ 5@ 5@ 5@ 5@ 5?5?3>3>2<2<2:2:1919292918180:1;2<2<2<2<2<2<4=5>5>5>5>5>5>5>4?4?3>2=0;0;0:0:/9/9-6-6,5,5,5,5*5)4*5*5+5*4)3)3)1)1*3)1'2%1$0"-",",!* )*)(&&###"!            !!!! !!   #( / 7%<)C1K 6M;Q">XC]$Oc'Xl0gv6s‚B|ŽEƒ•Lˆ›OŸS’¢W”¥Y–¦Z–¦Z–¤Y’ UR‡•Jv‚Chu5L[09G+ $%%% ( (!)!)$(%*&+(-*-",/'/+,402:08@6EJ5MR=Y^;`eBioCmsFpxGszIt~Ms}Kr}Gr}GdyA[p8Lg7@[,3Q.+I&$B- =)8*5(1)0'.(.(,(*&*&+''&'&&%&%%(%("&"&"&"&#'#'"'"'"'"'"'"'"'"'$($(%*%*%,&-(/(/(0(0 )1 )1 *4 *4,5,5!*6!*6!*6!*6!*8!*8 (7 (7(6(6%3%3&2%1$/#.#-#-",","*%,(-#,2,9.9F:GV?UdMfvOm}Vs†Ls†Lq…DoƒBp€BqCrHv„Kr~NozKgrJZe=KV6=H(.6''/ %"!  ! !%%-+'53;I9N\L]nUk|c{Šek…”b‚^YYZ„“_†•c‰—e‰•j‡”i„‘[}‹TwƒFmx;dq/]j(U`*P\%CN&?K"7E(7E(X(!H(OPf@Pf@GN86>((/'!( " " !&!&!#!#"$"$!(!(")#*#,#,$-$-$'), 918RI>aEDgJLrEMsFA_44R'9[39[35T:=\CWmZbyeu„kz‰p~Ždy‰`|ŠX|ŠX|ŠX|ŠXŒXŒX~ŠX{ˆVwVpzOhmOW]?FI9;>-,3-)/*%+,%+,!*+ )*!)-!)-*2(08.;I'FS1Nd*Xn4f}1m„8v7xŽ8yŠ7u…3kw7an.PW-CK 3< ,5#0# . "4&'9+,D+6N64^:=fBHsb†EkˆAkˆAp‡Do†Cp†GsˆIp„Jp„JrKkxDfo@]f7EW6=O.3J48P:8UK?\RHhZQqc^|`ceq‹^pŠ]tŒMl„Em6dv,]o,\n+Si_%4c"1` '^!+a$.h.8r9EzOR‡\gkq˜t‡ž|‡ž|›~Žš}™z™z˜|˜|Ž˜‚™ƒ˜„Ž—ƒŠ“€Šv{„ir{`_rV\nRQnWVr\Zzealk†kh„io‡\m„Zs†Ut‡Vv‡Vv‡Vu…Uu…Uu†Tv‡UwŠSwŠStJp‰Em…FjBm€GlEc}Eb|D4? 4? 4? 4? 5@ 5@ 5?5?3>3>2<1;2:2:1919292929291;2<2<2<2<2<2<2<4=5>5>5>5>5>5>5>3>3>2=0<0;0;0:0:-9,7-6-6,3,3,3,3)2'1)2)2)3)3*4)3*3*3)1)1'1#.",",!,!,!+ *('%%##!#!!!             !!     !&,//2 4&</EYj8EV$&9&   ###$% ( ("*!)%*%*&+(- */#,2(1+.717?->F4MR6TY=\c9dkAhqDltHozFs~JrKo|Hk|IhyGXnHPf@AZ?4M2)C1"<*5,1)0,.+-+-+,-,-*-'+),),%*%*%*%*&*%("&"&"&"&"&#'"'"'$($("'"'"'"'$($(&+&+%,&-(/(/)0)0*1*1*4*4+6+6+6+6*8*8*8*8)7)7(6(6'5&3&2&2#.%1%.%.#-#-"*"*).$.3)6-5A8FSATaOcrRk{[rƒPpOo‚CnBm~@p€Bq€Ev…Js€Lp}IhsK[g>KV6=H(.7$&/%! !!#$!#&()'336B;ITNZhZhvhyƒk€Šr†’i‚dZ}ŒX~V‚“Y…•`‡—b‰–h‡”g…aŠ[x‚NnyE\i5Ub.JV2DP,8A+4>(,:,,:,4A6>K@MYK^i\ez^\qT2S'>,LLlOV5@G&,2(#(# $!"#"# & &")")!(!(!(")#*%+#)$*')*+,>2M_SIeHOkMJlI<^;9]7@d>;d;5^66W8@aCSkUbzdv…lz‰p|czŠa|ŠX{‰W|ŠX}ŒYŒXŒX~ŠX{ˆVx‚Wq{PflNV\=FI9;>-.4.*0+#*+#*+!*+!*+++"--)8(0?/;R*G]5Sn3^x=n‡=tB|–B|–B•FyŒ>n|?cq3T^3DM#4>!-7'0+4 .:#6B+?N.FU5K`6Uj@`tEj~PqƒTy‹]€gƒ”j‡˜l‡˜lˆ˜h…–e…•gb€d€dzŒezŒe{‹d{‹d|‹fz‰dwƒap|ZhqP]fERX>EL16?02<-+90)7.%70%70$82#71#:2"917.7.90:1:1=3@1"D6%K3-S;9b9?h?Ls;SzB^@aCk„@k„@n„Cp†Ep„Jr…Kr„Mo‚Kk|IevC]k=Ta4DP,;G#+<#.@&+G87SC:^MDgVNsVTy\gˆUgˆUrŽJlˆDkƒ6g~2gy1ev/^n>arA`tUj}^nƒmr‡rsŠpqˆmwŒozs“y•{‡šƒ‰…ŽŸ‡Œ†ž€Œ‹›Šš~‰™}‰™}‹˜ˆ•|ˆ”wƒr|hxetWtWx•Uv’S|•S|•S€–O~”L~’Q}P{X{Xx‰]x‰]w†_x‡`y†fy†f{ˆhz‡gy†fs€`l{VetOZmARe9?c):]#/^-\'\$+`',f27r=G{STˆ`lŽrt–z‰œ‰œŽœzšx™z™zš}š}™ƒ™ƒ™…Ž—ƒŠ“€Švy„inz_^sPYnKNoQTuW[|ea‚kk‡ih…gn†[m„Zs†Us†UvˆSvˆSvˆSvˆSu†Tv‡UwŠSwŠSwŒPs‡KnInIk{Kk{KbyJ`wH5@ 5@ 5@ 5@ 5@ 5@ 5?5?3>3>2<1;2:2:1919292929292<2<2<2<2<2<2<2<4=5>5>5>5>5>5>5>3>3>2=0<0;0;/9/9,7+6-6-6,3,3,3,3'1'1)2)2'2'2)3)3&/&/&/'0'1#.",",!,!,)(''$%$# !!!              !!     ! $&&) ( ,3 -C5KBV#M`-Yk6dvAk~Dr…KtŒMxPy’N{”Q†S‡žT£SŽ¢Qˆ–P|‹Dar@M_,.B#)     !$%&& (!)!)"*%*%*&+(-"+1$.3)2,/82:B1AI8PU9W\@_g(.8"'5'(6(0=2LWJYdWez^_tX8Y-@7X'ZzJn~P`pBPW6AH'-3)#(# $!$$$$!(!(")")")")!(")#*%+%+&,)**+2D8SeYWsVOkM9\92U2TxRKnI4]48a89Z<>_AVmWc{ev…lz‰p|czŠa|ŠX{‰W{‰W|ŠX~‹W~‹W‹Y{ˆVx‚Wq{PflNV\=FI9;>-,3-)/*#*+#*+ )* )*++"--+9)2@0>U-I`7Yt9eDuŽD|•KƒI„žJ„˜J{@qAes5T^3FP%3< -7+4 08%6B+=I2ET4M\^sIg{LoƒTxŠ[‘b‚’i†–m‡˜l‡˜l…–eƒ“cƒ’eb~Œc~Œc|fy‹d|Œe~f|‹fz‰dwƒap|ZhqP]fEQW=EL17A12<-*8/)7.#6.#6.!50#71!807.7.7.90909090<.B3%K3-S;9b9?h?Jr:QyA_€B_€Bjƒ?m†Cp†Eo…Dp„Jr…KnIl~GhyGbtAZg:N[.CO+6B%7$5 <,)E50SB9\KDiMKpT\~Ke†SsKn‰En…9i4m~7j{4euEgwGdwYlao„nr‡rsŠpu‹qyŽq|‘u€”z‚–|‡šƒ‰…ŽŸ‡Œ†ž€Œ‹›Šš~‰™}‰™}Š–~‡”{…‘s€ŒovŠctˆ`oŒSk‡No‹LqŽN{”R|•Sƒ™Qƒ™Qƒ–V‚•T|ŽY|ŽYyŠ^yŠ^w†_x‡`y†fy†f{ˆhz‡gx…er_jxTcqMUiI~UU‰an‘twš~‰œ‰œ{Žœz™z™zš}š}™ƒ™ƒŽ—ƒ–‚ˆ‘}ˆtw‚gjuZZoLWlIRsTVwY[|ea‚kh…gj†ho‡\n†[t‡Vt‡VvˆSvˆSvˆSvˆSu†Tv‡UwŠSwŠSv‹Ns‡KnIl~GfvFfvF_uF\sD8B 8B 8A 8A 8A 8A 8A 7@ 3?3?2=1<1:1:18182:2:2:2:2=1<1<1</</<2?2?.>.>.>.>2>2>3>3>3=3=0;2<.;0<-9-9,7,7+5+5*3)1)1)1)1'0)1)1)2'1'1'1'0&/&/'0".!-+ ,+)))'(%$!!   !!       "         $!-'30@%8H-@T3G[:Ld>TlFZsD_xIkCo„GGˆ—N’¡Q“£SžU‚‘Ha}?Mi+.J&/   !!#%% (&"$#&"'$($*%+'**-&-,)0/5729:5FH5PR@W[;^bBgn=lsBnvCrzFs}Ks}Ko}Mn|LarMYjEG]E>T<.E8!8+1+.(,+,++.+.)-)-)-)-(,'+(,'+%*%*%*%*%*$(#'#'#'#'"&#'!&!&$($("'"'%*%*&+&+&+&+(-(-)0*1 )1 )1 )1#,3!-9!-9!,:!,:-:-:,<,< *; *;):):)7)7(6(6'3'3%1%1$/$/%.%.%,%,).".3)40/:6?J?LWLaiQlt\o|Vo|Vn‚Hm€Gk>nAq‚Bq‚BtƒJrHoyLepC\cBLR17?,)1$ !#$####" # '&(/.,54dJ>dJ&O/(P08X<@aEWkQfz`u„k{Šqd{Œa|ŠZ{ˆYx‡U{‰W}‡Z~ˆ[~‰Z}ˆY{‚Zt{ShkOY\@CH97<.(0,#+( (+ (+(*')+'!0+'8,1C7BW4Nc?Yv=g„Kv’K}™R…ŸXƒW~—SsŒHlE^q8P^5BP'6B 3>5@ 9C$AK,JU5T`7]i@drCn|LtR{ˆY€Ž^‡•eˆ™fŠ›iŠ›i‰šg„”d‘a€a€azŽbzŽb|Žd|Žd~f~fŽg}Šdw„_p}XnsPbfDMV.>2>2>2=2=2<2<0;/:.;,8+6+6*4*4+5*3)1)1)1'0'0'0)1'0'1'1'1'1'0&/%.%.!- ,*++*(((&##!!              "         ! &/)9/D#5I(9Q+`eDkrAmtCpyEs{Hs}Ks}Kn|LguE\mHTe@DYA7M5*@3!8+1+.(,+,++.+.)-)-)-)-(,'+(,'+%*%*%*%*%*$(#'#'#'#'"&"&!&!&"'"'"'"'%*%*&+&+&+',(-(-)0*1 )1 )1"*2#,3!-9!-9!,:!,: .< .< -= -=!+< *; *;):*8)7)7(6'3'3%1%1%1%1%.%.%,'/).".3)40/:6>I>KUK^fNks[o|Vo|Vn‚Hm€Gm€@oƒBq‚Bq‚Bs‚Is‚IpzMisF_fEPW69B.*3% !!#$$$## !%$&-,)32:CBOWM^f\ryczk€Šk‹l€Ž^}‹[{‹T{‹T}V€‘Zƒb…’e…d‹a{†YoyL_i?PZ0>G-4="&/' *!!,(&0,(812B;DTARbO_uVWmN$E*K[vFlˆXp|QfrGY^=IM-4:'+1$!"$$ &")")")!(!(")#*%($'$&%'&&1<I*BL-IS4R]=Ydg8Dm>Ux9[~?gƒ^p;Va4MX+:E#,7 /,,1<&(H33Y:?dENt@V|He†?hŠBrŒ=n‰9r†>q…=q€Nt‚Pp~bsfp…osˆsuŠpw‹qxp{t€”z†š€ˆ›„‰…ˆŸ…‡žƒˆ‡œ‡˜z‡˜z‰˜‡—~ˆ•|…’yŽnw†flUcyMXw>Ut:Tz;^ƒDe‡MlŽTw’Wz”Y~–\~–\‘a{Ž]{‹bzŠaz‰cz‰czˆfzˆf|‰kz†it‚`kyWevJ\mAKg0A\&2Y+R$V%WX&_#/i7?yGQ„Z^‘fx–x~œ~ŒœŒœ‹™|‹™|Œ˜zŽš}Ž™~Ž™~˜„˜„Ž—ƒŠ“€ƒw{…ol|\dtTOnGOnGMrSRxY\€fb†kn‡cn‡cq‡[o…Xs‡Ts‡Ts‡Ts‡Tu‡Ru‡Rw‹Qw‹Qv‹Mt‰Js‡Ko„Gl~Ii{FcyF`uBZsDWpA:C :C :B:B:A:A:A:A4@4@3>2=2<1;2;2;2:2:2:2:2=0;1<1<1<2=2=2=2<1;2<2<0=/;/;/;2<0;.9.9,8,8+7+7+6*5)3'2)1'0'0'0)1'/'/'/%/&0'1'1'0$-%.%.+**+('%%%%"!!!    ! ! !!""! !    "     & .%2&8);"0A7H#JWXe,u~6„E›L–¡Q‹ S”He€JOj4-G(- + + !!#&&& ( ) )",#-$'&)$)((-,23*78/BB.JJ6SS:[[B`c>fiDlrEpvJpxGpxGs|Mr{LgwIaqCUjFK`=;S:0H0%:24,/,.+,+-,+-+-+.+.+.+.)-)-)-)-(,'+%*%*%*$($($(#'"&"&"&!&!&"'"'"'%*%*%*&+&+(-(-).).*1*1+2,3,3 .4!-9!-9 +9 +9-:-:,<,H8KTE[dJgpVr}Wt~XqƒGoEk>nAr„Ar„AsƒGv…Ju€LozFfmEX_7BJ23;##(#""!!#$" #!"# & (.).8&++1&"# !&!& & &!(!(")")")")"&$'&((*-7,U`Uiq[kr\e~^G_@)I61Q>/OC=]Q5WD3UB8X+(2)#-%'*&)')(*,)#2/*;*5F6DX8ShG`|Eo‹Ty—Y}›]ƒœ_š]~‘^s‡TizJ]m=Q]2GT)BL&?J$BO(JW0Q^7Vc<_k@gsHo{Px…ZŒ_…’eŒ™loŽžn‹›k‰™iƒ“c’fd~c~c{‹d{‹dyŠeyŠeii€Œh|ˆdw„_p}XmrO`eBLT5@I*4>(-7!&2)$0' 2,.)1+1+1+1+2*3+2+2+0*2,!;)(B0/M+;Y6Di7In=[}:],6+')-4 =)&M+5\:Fk:PuDa‚Ad…El‹=l‹=pŠ?pŠ?t†Qu‡Rw„dy†fu‡mw‰ptŠkxŽo{t~“v‚–|ƒ˜~ˆ›…‰œ‡†‚†‚‡œ†›~‰š|‡˜z‰˜‡—~…•zt|Œis„agzNZmANl5Jh1Hq6Qz?UF_‰PlŽXq“^z•`y“_~c|b{‹d{‹d{‰g|Šh{ˆh{ˆh{‡lxƒhq}`iuWbsGVg;Ea*;V +S(P P"QT )_+5i@DxPZƒafn|˜{‚žŽž}‹šz‹šz‹šz˜}˜}Ž˜€Ž˜€˜„Ž—ƒŒ”‡|~‹rx…lg|Y\qMLlINnKGpUMv[[~fc†nn‡el…dq‡[o…Xr…Rr…RtˆUtˆUvˆSvˆSvŠPvŠPuŠLsˆIo„GmEhzEfxCasJ^qGVlFRhB:C :C :B:B8@8@:A:A4@4@3>2=1;2<2;2;2:2:2:2:1<1<1<1<1<2=2=4>1;2<4=4=/;/;/;/;0;.9-7-7+7)5*6*6*5*5'2&1'0'0*3)1'/'/'/&.%/&0'1'1'0$-$-$-*(()'&%%%%"!!     ! ! !!""! !    "        ! "*, 1 (9@LQ^%ox0‰AšJ•ŸP‰ŸR~“Gc~HKg0'B") + + !!#&&& ( )!*#-#-&))+(-,+0/890>?6II5NN9XW>_^EdgBloJlrEmsFryHu|Ks|MqzKfuH`pBNc?EZ75M5-E-#700)/,.+-,-,+-+-+.+.+.+.)-)-)-)-(,'+%*%*%*%*$($(#'"&"&"&!&!&"'"'"'%*%*%*&+&+(-(-(-*/*1*1*1+2,3 .4!-9!-9!,:!,:-:-:,<,H8HRCYbHdmRozSt~XqƒGoEm€@oƒBr„Ar„AsƒGv…Jx‚Nq{HjpI]c&'-#$ # $!#$"" "!'"'-'/9(:E3NY>\gLks[v~f~ˆkŠn‚a~‹^{ŠU{ŠU|ŒUY€`ƒbƒŽf‚d~‰ct~XdnKPY68A-+4 !)&'# )*$--$4/0?:AO?O]M[pRMcD-N"<]1k‡Uj†Tp~NjxHbfENR19?,,2&"# $$% &!(!(")")")")$'$'&()+>I>bmbhpZu|g_wW9Q2)I6(H5(H<.NB-O8X+(2)#-%'*&)(*(*+(#2/+<+7H7H\;WkJdIrWz˜Z~œ_„`€™\~‘^s‡TizJ]m=Q]2GT)CM'IS-M[4S`:Ta;Xe>cpEnzOv‚W~Š_…’eŠ—iŽ›nŽ›nŽžnŠšj‡—g‚’b€‘e~c~c~c{‹d{‹dz‹fx‰dii€Œh|ˆdv‚^o{WkpM^c@LT5>F'2<&09#%1($0'/*/*1+1+1+1+2*0)2+2+0*1+#=+)C13Q.=[9Bg6In=Z|9^€=h…lƒ=m…?j‚Aj‚Am€GlEi|Edv?[m8Tf1NX-AK 0;'2+'')08# G%-T2?d3Lq?^?bƒBpApAtBuŽDyŠUzŒWz‡g|‰jw‰p{sypxŽozs|‘u•{†š€‡™„‡™„…›…›†›~†›~‰š|‡˜z‰˜‡—~„”x}qxˆfo\atHSf:Ca*@^'Aj/Js8UF\†MhŠUmYw’^y“_~c|b|Œe|Œe|Šh|Šh{ˆh{ˆhy„iufp|^gtV^oCTe9C_(7S(P$LN#R V"+a-7kCFzR]†dj“q|˜{œ|‹šzŒ›{‹šz˜}˜}Ž˜€Ž˜€˜„Ž—ƒŒ”‡|~‹ruiezW\qMIjGMmJIrVNw\\g`„ln‡el…dp†Zn„Wr…Rr…Rs‡Ts‡Tu‡Ru‡Ru‰Ou‰OuŠLsˆIo„GmEhzEfxC`rI]pFVlFQgA8C 8C 8B6A 8A 8A :B:B4A3@3?2>/;/;0;0;2:2:3;3;1<0;1;1;1;4=4=4=2;2;0=0=-<-<-;,:,9,9+8+8*5)4)4)4)2'1)1)1)1)1'/'/&.&.&.&.%.&/)1)1'/$,!+!+))(('%%%$$#           "$!   !!    ""        ")+2?DRW&mr-†A‘—H™ŸPŠœS€’He|FNe/)> &   #$$%$$&&"'%+'*'*%*)+0/86/>;5FD0LJ6SW2WZ5ad:hjAkpAosEluFmvGr{Ls|MoxNkuJbpL[iEJ]?@S5-H6$?-3+2*/,/,-,,++.+.+.+.+.+.'-'-'-'-'+'+%*%*%*%*$($($("'!&!&!&!&!&!&"'$*$*$*%+%+',(-)0*1*1*1)3!+5,5,5!-9!-9!,:!,: .< .<,<,<,<,<+;+;+;+;(9(9(6(6&5&5&2&2&2&2%/(1#)1&,40456:;@E;IOEV]G`gRivPo|Vp‚Mn€KnBnBt„At„Av…Ex†G|ˆRv‚LnwJemAMV5=F%*0$ '$#$#" !$!(+"04*8?%BH.PX9_gHjrUu|`}†l‚‹pf~Œcz‹Xz‹X{‰W~[Œ_‚a†g†g€ˆcwYkoNX\<@D326%&+#$)!!*+'01$4/.=8>O>J[KVmSCY?0T,FjBwYp†Rt‚Mm{FciBPW/;C+-5'#"&&% & & &!(!(!(!(")$+&$+(FRCjvgyƒku€gMfM0H0%F5-N=>/>/)Q94\C8^=JqO[sTd}]yˆj}‹n~Œhz‰dzˆ]y‡\wˆWwˆW{ˆZ{ˆZ|‰[{ˆZ{‚Zt{SjmO[^@EH89=,-0.'+('(()')'),)%42,>26G;H]AVkOd|Vq‰c~˜h‚›l„›l—hdy‡\nzO_k@V^2QY-QX0TZ2W]7Y_:[c=bjDisFs~Q{†Y‚`Š•m™pŽ›nŽ›n‹™pˆ–m…•gb€Žc€Žc~Žd|cz‰dz‰dz‹f|hi~Œh~‹f}‰eu‚\o|VjoJ]c=KS4ay>d}Bk‚l€DkCiyDbr=Yi9Qa1GT-9F,9"/'%#%.5! E!-R.:b0Fn<[>`ƒBmŒCpFwIwIyŒYzŽ[~‹i~‹i|o|ozo{Žpzs~“v”}…˜€‡™„‡™„…›ƒ„š‚†š€†š€‰™}‰™}‡˜|‡˜|„”x|Œpy‡cm|W`qFQb7>V#=U"<]*Jk8MuCU}Ka„SgŠYr_tbzŽfzŽfzhxŒfzŠjzŠjx‡ix‡iwƒesbl{VcqMVk1< /;/;0;0;2:2:29290;1<1;1;0:1;4=4=2;2;/;/;.=.=,:-;,9,9*7*7'3'3)4)4)2'1'0'0'/'/&.&.&.&.%-%-%.%.%.%.%-$, * *((&&%$$$$#!        !"%&)(%#""!    ""   !' (.?DQV%lq,…?‘—H™ŸPŠœS€’He|FNe/+A"&   #$$%%% (!)#($*'*#+.+0/276@>7EBT:9^6QuMrˆUq‡TvƒNp}IfmETZ2/!F7/W>/W>6]]e@enHkvIv€S}‡Z„ŽaŠ•m™pŽ›nŽ›nŠ˜o…“jƒ’eb€Žc€Žc~Žd{‹b{Še{Šez‹f|hi~Œh}‰ez†btZn{UjoJ]c=KS4=E&.7$*3!-%!-%-(-(.&/'/'/'.&.&.&.&0&2(!<(*E16T2@^;Lj:SqAbz@e~Ck‚g{?g{?fuA`o;Wg7O_/BO(7D&3#0&#"$.2?'L(6],Dk:X{:`ƒBl‹BpFz‘K|”N{\’_€Žlm~q~q|q}‘r{t~“v‚–~…˜€ˆ›…ˆ›……›ƒ„š‚ƒ˜~‚–|‡˜|‡˜|‡˜|…•zƒ“w{‹ow…ajxT^oCN_3=U"=U":[(De2Jq?RyH_‚QgŠYq‹^tbzŽfzŽfxŒfzh|‹k|‹kyˆjx‡ix„gr~aiwS`oKVk 1= /= /= 0=0=2<1;19190;0;0:0:1;2<2<2<0=0=-<,;-;-;,9+8+5+5*4*4)2)2'0&/&0&0&/&/'/'/&.&.$/$/#.#.$,$,#+#+ * **)('%%%%&%!!               ! '%,(0)2)2'0+) & % +%%%! ! !          $ &+8=KP&fl+z€?ˆ‘G‘šP’ŸV…’Ig~BQh+.E$) +  !##$%#% )(!$(#&*-*+1//AV_D`iOjsRpyXr€QqOpƒEo‚Cv†Dx‡EyˆHz‰I|ˆR{‡PvRoxKbhGOV5:A+)1!! ! ! "!"$%&"*-.0"89 ?@'GKQT([_1hl>qwO{‚Z€‰h…Žm†’i„g‚^}ŒY~ŽY~ŽYZƒ’^‡“c†‘b„d~‡_pyX`iGIP:9@*+0($)!'(!*+&0.0:97K?DXLQmQ5Q55U-XxPr†Zr†ZtRoyLhmKW\9>F029#!*$$$$$% & &!(!( &!(!#&() :F=y€qpwhHUIH`VG_T8YB4U=<_A/S4*W2/[62Z4OwQ[uQe[|‹k}Œl|Šhzˆf{Š^zˆ]wˆWzŠZ|ŠZ|ŠZ|ŠZ{ˆYx‚Wq{PhnMW^=EH88<+,/,'+( %) %)%%()#/-,864D6@PBMaG\pVl„`wŽj€”s‚–u‰šz†˜x‰“t‹lt€UgsH_g;Yb5Y`8[a9^d>_e@aiCfoIkyNr€U{Š^‚‘e‡”k‹™pŒœnŒœn‰˜k…•g‚‘ed€d~Œc~f{‹d~f~fz‹f|hi€Žj}‰ez†bu]nzVhmK[`=IP3;B%/6"+1!,!!,!*$ +%+$+$,%,%-&-&,%,%/# 4($>'-G0:W0C`9Ol8XuAf}@g~Blƒ=lƒ=l‚AmƒBkCkCk}CizAdsA\k9Xc;LW/=H(0;%0( ""#%,1;+J*7Z.Eh 1= /= /= 0=0=1;2<19190;0;0:0:1;2<2<2<0=/;+9+9,:,:+8+8+5+5*4)3'1'1&/&/&0&0&/&/%-%-%-%-",",",",$,$,#+#+ * **)('%%$$%#!!               !&%,+309 4="/8)7#1#1"0!-!-+($!           # &+7<HM#_e$v|<ˆ‘G‘šP‘žU‡”Lk‚EWn19P.2  !##$%% & )#+$'+(+/200977FB5LH;SQ6XU:]^:bc>djjo?nsBpxGryHoyJoyJnwHnwHivIboBTe@IZ67M5.C+!7-1'.(0*-,-,-,,++.+.)-)-)-)-)/)/)/)/'+'+%*$(#'$($("&!&!&!&!&!&!&"'"'"'$*$*$*%+%+%+','/)0*1+2!+5!+5",6",6!-9!-9 +9!,: .< .< -= -= -=,<+;+;+;*:*:(9)7(6(6(6%3%3(4(4$+1$+1(.1-263;39@8@I5IR>S\B^gLirQqzYtRtRq„FpƒEv†Dx‡EyˆHz‰I|ˆR|ˆRz‚Vs{OfmLV]<>F0.5 &$#$!"!"$&(#+,'/1#46(=?&BD*MQ%UY-]b3gk=nuMyX€‰h‡o‡“j…hƒ‘_Ž\~ŽY~ŽY}ŒX€[ˆ”dˆ”d†g€‰at}[dmLPWA]e8]c<^e=_e@_e@ckEhpJkyNr€Uv…Z}Œaƒ‘hˆ–m‰˜k‰˜k‡–h„”fdd€d~Œc|Œe{‹d{‹d{‹dz‹f|hi|‹f{‡cy…at€\myUglI\a>JQ5;B%/6"+1!,! * )#)#+$+$)#)#)#)#*#*#/#!5)%?(/I3,L+7Z.Eh0=/<0</;0=0=0</:/90:1;1;/;/;/;.9.9.9,:,:+8+8*5*5*3*3)2)2#0#0#0#0"-"-"-"-#.#.#.#.$-#,#+#+#)"(#)#)))((&%$$####""       +! ' **//448#48#27".3/5/5/5/5(6'5.( ! +        ##'28@F X_#ls7…ˆA’•NŽ›SŠ—NwˆHdu5BX2&<!   $$%%%!'""'%&*/,&86/G@-MF2UP/YU3][2c`8eh8hj:iq9ks;nx@r{CtzEryDp~Gm{EhwCiyD^qGVi?I\>=Q2.D2(>-3.1+-/-/,.,.-1-1,/,/)-)-)-)-(/(/'-'-&,&,#*#*$(#'#'"&!&!&!&!&!&!&"'"'#(%+$*$*%+%+%+',(-).*1*1 *4!+5",8",8!-9!-9 +9!,:!,:!,:!+A:DK7JQ=U^?_gHirQr{Zu‚TvƒVt†JqƒGs…Ct†Dw‰Fy‹Hz‹M}OzˆRsKiqK\d>FN14;',!&$$$$)!,1!/5$<=BC%GKMQ%SZ$cj-jq4lwJwT‰e‰’oŠ–kŠ–k‚”_€‘\|V{ŽU€Ž^‚`ƒ‘a…“c†’g„e{ƒdktUW_GCK306*&- )*!*+#..+672G==SIJjN/O3/R-TxRp…[nƒYv€Sr}PlmM[]=BH539&#,&$ # ##$&&"&"&"&"&"&#'8GCl|wuqjvg?H;)2%BWH:N@/R5,N20U27\9(Z+.`1:d9HrGZtNe€Zx‡i~Œo~fz‰c{Š]z‰[yˆZyˆZ|‰[|‰[|ŠZy‡WvƒVn{MfnKY`=FK66;&-,.+*,#&*'*.#+((0,09,9B5?M9M[GXlKg{[qŠh}–t†š€‡›‰›…—~‡“v|‰kzYmtLemA`i<^f:_g;]e@_hB^kDboIdvOk}Vp„^t‡b}Ži’m†–m…•k‡–h…•gd~b|c|c|c|c|f}gzŒe|fŒj|Šhz…cy„bw`nvWdjKY^@FM38?%+2#&.,!+ )")"("(") ) (#(#* (3""8')B'1J0?[0He9Xr9`zAj‚Ai@lƒ=lƒ=n‚?n‚?mCk}BizAdv<_m=Xf6T]5HQ):@!/4'%####,3(D 4P,?`.Jk8Rv;Z~Db‡IgŒMp’Vu—Zy“h|–k–s–s|’s{‘r|’s|’s“y€”z…–ˆ˜ƒ‰šƒ‡˜€‡˜€…–„–|„–|”}‚–~†•~…”}„y|ˆqv‚`kvT[j?L[0?T#@U$A]-Gb3LoCVzMbXl‰bsŠhuŒkzŠnzŠn}‹nyˆjy‡ly‡lz†iwƒes{cmu]bmNXcCGY21>0=/</;/;0=0=0</:/90:0:0:/;/;/;.9.9.9*8*8+8+8*5*5)2)2'1&0#0#0#0#0"-"-"-"-#.",",",$-#,#+"*#)"("("(((&&%&$$####! + + + +   + # %$)(-.3.3+0-2.4.41818*9)7!1+#        + +  !%-39?SYel0€„=Ž’KŽ›SšRz‹Khy9H^8,B% !# #!!$$%% &"(%&**+/74.>;5NG4SK8\W6^Y8ec:hf>hj:kn=mtc@2d5(Z+6`5HrGQkEXrLrcz‰k~f|Œe{Š]z‰[yˆZyˆZ|‰[|‰[|ŠZy‡WvƒVp}PhoLW_;FK69>)/.1,+-(+/*-1)1./739B5AJ=GUASaL^rQl€_sŒk~—u†š€ˆœ‚‰›…—~…‘s{‡jx~VlsKfnBck>bj=`i<]e@]e@ZgA]jC]oHdvOh{Vp„^z‹f’m…•k‚’iƒ’eƒ’e€Žcd|c|c|c~Žd}g|f|fzŒeŒj|Šhz…cv‚`t|]nvWdjKY^@FM38?%+2#&.+ *)")"&&''%!%!(* 1 "8',E*6O4A^2Jf;Zt;b|Dj‚Ai@k‚@@D$O(/[>BnPRyc`‡qx’y}˜‡›z‡›z‰šz‰šz‹™|‹™|Šš~Šš~Šš~‰™}Š–ˆ”~„yy…ok~`_rTRmMLgG@hJElOMsZTza`ƒf`ƒfj‚\i[m}Tl|So~SpTpQpQp‚Mp‚Mp‚Mp‚Mn€Kl~IhyGevC[qEYoBNiBJd><^D<^D6C +6C +9D9D4B 4B 4@4@6A3?4>4>1<0;0;/:0:0:090909090909/;/;-8-8+8*7)6)6)5)5'2'2)2)2'1&0#/#/#/#/",",#.#.#-#-!*!* * * ) ) ( (& ('&&%$$#"      + + + + + +   !"$'') ,"-"-%0*5(5(5 .*$ ! + + +              "+.6:OS be3x€8‡GšM‘›NIo}9Wg7>O -##$$"'(&&)'!!#$%!#%')00)44-C>*JE1RM.YT5^^.dd4jl2mn5nr8os9pvqz>qz>mz?mz?iw@hv?_rA\n>Jb>@W43K2*C*!9.3(0,-),.,.,.,.,/,/,/,/+.+.)-)-(/'-'-'-%+%+#*#*#'"&#'"&!&!&!&!&!&!&"'"'$*$*%+%+%+%+',(-',*/ +2 +2 *4!+5",8",8 ,8 ,8 +9 +9!,:!,: +9 +9,<+;+;+;+;+;*:*:*8(6'3'3'6'6"*4#+5(04+362527:7?B4EH9KO5OS:U_;]gDgrJp|SwƒXwƒXw‡Pu…Nv†Hv†HxˆJz‹Mx‡Nz‰P{ŠQw…Lq{PeoERZ;BJ+07$&,%+'./55;!=DCJ"MQSW"X^]c"_i%cl)ho0mu6oz?xƒI€Š_ˆ’gŽ›p‹—l…”i€ŽcuV\i>juOŠd€gxˆ_{‡c‚Žj‡hw}^fkOSX<$%.&&!"!"#$(( & & & &"& $(,5FJRg_]rjVcWBODiscyƒt]pZAT>9U7JfI@kD-X1/V-HnFPhDG^;euU€oi{Še{Š]z‰[zŒWzŒW|ŠZ|ŠZ{‰W{‰W|…Vt}NhqGYb8EL18?%46(46(86.=;2=@0CF6EM5OW?TaA]jJfvZsƒg{Œu’{…–…†—†ˆ–†„“ƒŽuw„kqySiqKdjDchCbgB_e@Xa?U^=Na>ReBSmI[uQg{\oƒdz‹fk…•k‚’ib€a}Œa~b|Œe{‹d|Œe~f}g|f|h|h|Šhzˆf{†et^r{\ktUdjKV\=DK/6=!*2!&.)(%!%!$"#!%%$$')2!%:)-F+7P5D`5Mi>[t>b{Ej‚Ag€>k>i€=m>m>lBh}?fy@bv<[j8Tc1KU/>I#19(/&$#%&* 5-A'7P'B[3Ie5Pl";<<C$$L-5`GCnUVzgeŠvz•z}™~…˜|…˜|‰š|‰š|Œš~Œš~Œ—ƒŒ—ƒŒ—ƒŒ—ƒ‹–‚‡’~€‹wxƒoj{]^oPOiIJdEBeNHkSNr_W|hdhe‚ilƒYk‚XnQm€OpOrƒPr‚Rr‚Rs…Ps…Pp‚Mn€Kk|HhzEkzLetF\qIThAJfBHd?Bc>Bc>6C +6C +9D9D4B 4B 6A6A6A3?3=3=0;0;0;/:0:0:090909090909.9.9-8-8+8*7)6)6'4'4&1&1'1'1&0&0#/".".".",",#.#.#-#- ) ) * * )( ( ( (&'&&%$$"#      + + + + + +      ! ! " %' ' ( "- , ,'$" ! + + +               "*-47IM^b/rz2‚ŠB™L‘›Nƒ‘Muƒ?^n>JZ*(5& !'++)/0.030&)'!#$!'$&(*+-44-993HD0OK7YT5_Z;dd4ff6no6qs9nr8rupymz?ly>iw@dr;Yl$%.&&!"!"%&(( & & & &!%!%"&*..C;=RJancancq{lpzjXkVUgRMiKNjL;f?-X1$J"7^6ZqM_wSw†fŽn|‹fz‰d{Š]z‰[zŒWzŒW|ŠZ|ŠZ{‰W{‰Wy‚Sr{LhqG\f;LS8>E*=?1;=/?=4DB9EH8JM=LT\bi€=m>m>k€Ag|>bv<_r9[j8Tc1KU/>I#19(/&%$$* 1!,@&5J0YuJb~Sj…^p‹dwŒkwŒkz‰kyˆjx…lz‡n|‡n{†mz…ks~dow_goW^hIOY:>O*0A#<";:=D%)Q1:dKJu\[liy|˜|}™~…˜|…˜|‰š|‰š|Œš~Œš~Œ—ƒŒ—ƒŒ—ƒŒ—ƒ‹–‚…|ŠvvmgxZ[lNIcDIcDBeNHkSMq^Vzgdhe‚ik‚XjVnQnQrƒPrƒPr‚Rr‚Rs…Ps…PrƒNn€Ki{FhzEfuHdsEYmESg@JfBKgCHiDHiD6B 6B 6A 6A 6C6C6A6A4@3?3>2=1:1:0;/:.:.:-9-9.:.:-8-8.9-8+8+8*6*6%4$3&2&2&2&2%.%.%.%.",","+"+"+"+!*!*",",!*!*!* ( ( ('%''%%&&#!!!!     + +            " $ &# #   +             + +   #( -2?CUY-ks2zƒAˆ‘G™O‡“O}ˆDgvBTd/4E',"",04.483799+-- !# $$%),-*/0.::&AA-OK.WS6_^3a`5jl2op7tx5sw4sz7sz7v~8t|6q|:q|:jz>hwfpFpzOuVx…ZyˆVw†Tu…Nq‚Kw…Lw…Lw…Nw…Ny‡Py‡Pr€QjxH]hBLW0=F%5>9>;@AGELMURZ Xa _h'am'dq*hu,mz1k~2o‚6w…A~ŒH}V„”]‹–nŠ•my…aw„_u€_htRfkTrx`bh\U[Obk^oxk{nz€mtt```LKK=88*++&)($%*)+0/7?4FNDCaJ6T=3Y3EkEf‚Ri„Tt€UkxMfnKZa>FK67<'&.*&" %#''))''(( ( (%% !"01DWVatsekl_ef[gYitgQmPB^A9a;6_87^6CjA\rQk‚`x‡i}‹n|Œez‰c|Œ\{‹[zŒW{X|ŠX{‰W{ŠU{ŠUz‡Ur~MkvI_j=X[6OR-IL.IL.MM4ON5PS7UWU\9JV9EQ3;N0>R3AS7EX(1K.;U7Jd9TnC`yCc|FkƒBi@lƒ?i€=i;i;k@h~=ew@`s;Ve7P_2ER-:F".7$-%%%+2*<'7L)@U1K`6Sh>[nBcwJi|Po‚Vq†^tˆ`s‹gwŽjxtz‘w~’~’~{zw{u|‘w‘|ƒ–ƒ–…„—†ƒ–…ƒ–…ƒ•‚‚”€’‘~~“}”~ƒ“~‚’}yy…pu€_kvT^n>Qa1J_)Nb-Pi:WpA_vNhWq†buŠgw‹lw‹lzˆqzˆqx‡pzˆq|‰nzˆmuiq~ejs]`jTXdIJV;9I',="8"849 C-,Q;<`SNsfd‚rn|˜™€‰™}‰™}‡˜|‡˜|‹˜Œ™€Œ—ƒŒ—ƒ—†‹•„‰‚ƒ‹|~†tt|keuYWgKDaCDaCBeNJnVRu_[}hd„ce…dm…XkƒVn‚OpƒPpOpOrƒPrƒPrƒPrƒPrƒPnMm{IhwDcuE^q@So=Nj8Hj;Jl>Jp>MrA8C 8C 6A 6A 6C6C6A6A4@3?2=1<1:1:0;/:.:.:-9-9-9-9-8-8.9-8+8+8)5'4$3$3$/$/$/$/$-$-$-$-!+!+!*!*!*!*",",!*!*(' ( ( ( (&&'#%%&&! !     + +                + +"  + +                       !& (-9=NR&en,w>†D˜NŠ•Q~ŠFk{FZi5=N0"3"&'+&150476'*)!#""'&'+./,341AA-FF1TP3ZV9a`5fe:no6qs9tx5uy7t{8t{8t|6v~8q|:oz8eu9bq6Sj2Md,=W15O),H*%A$5*1&/)0*.+.+.,-+-.-.+.+.+.+.*.*.*.)-'-'-&,&,$+$+#*#*"'!&"'!&!&!&!&!&!&!&"'"'#($*%+%+%+%+',(-(-).)0*1 *4 *4!*6!*6*4*4+6+6,9,9*8*8 +9 +9*8*8)7)7)7)7*5(4(4*5$,6$,6*04.37786:;8?B4?B4DI4GL7KO3OT8U]7ZbA`I4R;0V0DjDa|LhƒSt€UnzOfnKZa>FK67<'&.*&" %#''))''((&&&& !",.6IHI[[Z`b`fhcoaZeX,H*1M/.W0-U/7^6FmE]tRh~]x‡i}‹n|Œez‰czŠZzŠZw‰TyŠU|ŠX|ŠX|‹W{ŠUy…TtOmxKdoB`c>[_:RV8QU6RQ8WV=WZ>]_DaiChpJqySy\{‡j‚Žq…’€‰–„‡”‡…“†{‰ysƒgfvZ^lJR`>RY6IQ-?K.:F)2E'5I*>Q5HZ>PdJ]qWlet‰m‚’kƒ“l’fdb€a}Œa}Œa|b|bff|h|h|‹f{Šez‡gw„dw„_t€\q{WisOcjISY8FJ09=$+0'-%%$ $ ##""##',!8")A+3L/k>k>i;i;i>g}^p9Sc5M]/CO+7D+4$-$%( /':%3F0BW4H]9Sh>ZoEcwJj~QnUs‡[u‰aw‹ds‹gwŽjxtz‘w}‘~}‘~~{zw{u|‘w‘|ƒ–ƒ–…„—†ƒ–…ƒ–…‚”“€€’‘~~“}”~ƒ“~‚’}yy…pu€_grQZk:Rb2Nb-Qf0Sl=\uEdzRl‚Zr‡cv‹hw‹lw‹lw†ox‡px‡px‡py‡ly‡luiq~egq[bkUVaFHS86F#*:665:!"G10U??cWRvif„tr€˜™€‰™}‰™}‰™}‰™}‹˜‹˜Œ—ƒŒ—ƒŒ–…Š”‚‰‚ƒ‹||„ss{j`pTTdIDaCDaCBeNJnVRu_[}he…de…dm…XkƒVpƒPpƒPpOpOs„Qs„Qs„Qs„QrƒPnMm{IhwD`sC]p?Pm:Pm:Jl>Km?MrAOtC9D8C 8B8B4@4@4?4?3?2>3>2=0<.9/:/:.;.;-:-:,8,8-7-7*6)5*5*5'2%0"/"/$.$.$.$.#,#,#,#, *","+"+!*!* ( ( ( (!)!) (&%%%%&%%#%%!!   + + + + +   + +       + + + +                  +               + + + +         ! #(16AF#[`/puD‰CŠ”O˜Oˆ“Kv‡Gbs3J[03D+'''1(%,+%$  ""$#$&)*,64+98/FE'ML.VW,^_4fj0kn5pv/tz2w~4w~4t7t7n:m~9bx7`v5Uo7Pj1C_4=Y.-J1(E+#9,3'0*/)-+.,0,.+-+-+-.-.+.+.+.,/*.*.)-(,'-'-%,%,$+#*")")!&!&!& %!&!& % %!&!&"'"'"'$*%+%+%+%+',(-(-(-*1*1*1*1 *4 *4+6+6+6+6 +9 +9 +9!,: +9 +9)7)7)7)7)7)7 *4)3"*2#,3).4-18235579;>4=@7@D2DH5IO1HN/KR/MT1S[5Ya;dnCoxNvYx„[y‡Wx†Vv‡Nt…Kv…Jv…Jv„Mv„Mv„Mw…Nr‚Mp€KjuF^j:W^-MU$MPOS S[!X`&ai(fo.kv.r}4t‚5w†8y‰DzŠFz‹K~O}V€‘Z‚‘dƒ’e…’m‚ŽjhuZP^CP\LjvgdiaNSKYXTa`\igg\ZZLLE{{tSTKff]_[^]Z]OMR31621/875TYMmqeSiQ8N6+N+4V3OkBcVpUizNfmLZ`?FJ77;(%-)$!!&$((**(((( #"  !+*!64+PLDWSKTTMaaZ`kWGR>;W3KgC(S*#N%/S4;^?XlTfzbw„k|ˆp~f{‹dy‰YwˆWyŠUzŒW}ŒY~[~ŽY~ŽY~ŒUzˆRsKjxAho@bi:ab=_`;Z]:^`>bfAhlGmsFtzMxR€‰ZƒiŒ–rˆ•|‹˜‡•…„“ƒ}‹}y†yrferYYfGIV6AN58E,,=&&7 %9(+?.J(3>&/(#'.)9&4H.?S9I_9QgA[pFbwMkSnUr†ZwŠ^vŠcxeuŠnzsvzy’|}zŽ}zxxŽv{Žy|z‚“ƒƒ•‡ƒ•‡ƒ•‡ƒ•‡‚”“€€’€’~’“€„”‚‘yz†rv‚`lwU_p=Rc1K^.Tf6VnA_wJg~Vq‡_t‰fv‹hw‹lw‹lzŠnxˆmz‡nz‡ny†mz‡nuis}ggnY`gRR[ACL12A#(61108$"D64WHCgZSwjl†vw—ƒ˜€‹˜}ˆ–{‰—€Š˜‚‹–‚‹–‚‹–‚‹–‚Š”‚†‡~‰xyƒmnxb`pTScHB]DC^ECdSKl\Tva\~if…`d‚^m†Wl…Vn‚OpƒPs…PrƒNs…Ps…Pt†Qs…PpOm~KkzHftB`q?^oSyA9D8C 8B8B4@4@4?4?3?2>2=2=/:/:/:.8.;.;-:-:,8,8,6,6)5)5'3'3&1$."/"/$.$.$.$.#,#,#,#,!+!+!*!* ( (!* ( ( (!) (&%%%%%%##"""!!   + + + + +   + +       + + + +             +    + + +              + + + +     +     +$/4=BRW≷x‚<‡‘K˜OŠ•MyŠJhy9Rd8@Q%%3)#%!   !#!%&')++,.98/?=4JH+RP3\]2bc7hl2mq7qw0u{3w~4w~4t7t7n:jz5^t3Xn-Ke,E_&7S(0L!%B)<#3'2%0*/)/-/-,(,(-+-+-.-.+.+.+.,/*.*.(,(,'-'-%,%,$+#*")")!&!&!& %!&!& % %!&!&"'"'#($*%+%+%+%+',(-(-(-*1*1)0*1 *4!+5*5*5+6+6 +9 +9*8 +9 +9*8)7)7)7)7)7)7 *4!+5$-4%.6.39.3946889;=@7AD:DH5FJ7IO1KP2MT1NU2RZ4[c=cmBmwMq}TwƒZ}‹[|ŠZwˆOu†Lt„It„It‚Lt‚Lw…Ny‡Pv…Qu„Pq|MfrC_f5W^-VZ'W[(Zb(`h.hp/ow6t7}‡?Ž@ƒ‘D‡—R‡—R…–V†—W…•^‡˜aŠ™l‰˜k‡“oir€eO]B1=.DP@TXQJOHLKG994USS_\]\\Vcc]mmdJKAEBE;7:76;@>CFEChfdž¢–˜œe{c6L4'I&1T1ToGg‚YnTgxMhnM[a@FJ77;(%-)$!!&$((**((((!$$'AB?mnk‚x‹‰€‹‚‰€vvobb[`kWdp\C_;Ie@)T+-Y0BeF=aBXlTfzbw„ky†m|Œez‰cy‰YwˆW{X{X|ŠX~[~ŽY~ŽYV}‹TzˆRr€ImsEfm>fgBcd@_a?dfDhlGnqLtzM{TˆY„^‰’oŒ–rŠ–~‹˜…”„‚€}‹}uƒuq~ecpWTaACP13@')6 1 1#6&/B1@R=N`K^pVn€fz‹h’o‡–o…”m’fdd€Žc€fŽg€Žl€ŽlŒjŒj|Œiz‹hy‡ex…cw„bw„bw‚cs}^qzYirQchLSXf=h~=h~=g|>f{^oQgA\qLbwMh}SpƒWv‰]zŽb|cxexeuŠnyŽqvzy’|~‘€|~zxxŽv{Žy~{‚“ƒƒ•‡ƒ•‡ƒ•‡ƒ•‡“€‘~‘~‘~~’“€„”‚‘yz†rv‚`htR[m:Sd2Pc2Wj9ZsFd|OhWrˆ`t‰fv‹hyŒnyŒn{‹ozŠn|ˆp|ˆpy†mz‡nw€jpycfmXZbLPY>AJ/.<$3010;&$G89\MIm`W{np‰zz”„—ƒ˜€Œš~‹˜}‰—€Š˜‚‹–‚‹–‚‹–‚ˆ”€‡’€…~ƒ‹z{ƒrr|fgq[ZjOQaEB]DC^ECdSKl\Tva\~if…`e„_jƒShRn‚On‚OrƒNrƒNp‚MrƒNrƒNp‚Mo€Nl}JjyGdsA`q?^oRr;Uv>Rw?SyA:B:B:A8@4@4@1>1>1=0</:/:/:/:.8.8-7-7,8,8)6)6)5*6%2#0#0#0#0"/!.!.$.$.#,#,"*"*"*"*((%%%%% (('''%%####$!!!#!       + + + +    + + + + + +             + +   + + + +           + + + +  + + +    +       !",.8DM$V_7ov>}„L‰ŽP‹RƒMwBbnW\atmr†^nlFWT5H3L^Iu„_q[p{UmwQdmNYaBDJ94:)&.*&" & &((((((''!(#*GX\o€„pƒ‡dw{fz}lƒn|u]kd[o\klUxU8[8&S,5b;GgM?_EWgYfvh{ƒr~†t€Œh}‰ez‹XyŠWw‰Tw‰TzŒW|ŽY|[}Ž\|ŽY{X{ˆVr~MruLmoFilBgi@gmAioCovGtzLzƒM‚‹U‚Ž^‡“c‰”s‹—uŠ–{˜}†’„‚€~‰|vtpvebhWQXC>F008%'0*#/!*9.7F:FXCTfQdt_qlŽn†“sˆ•n‡”mƒ‘hfŽgŽgii€Žl€ŽlŒjŒj~Šh}‰g|‰cy†_y„bwƒaydx€ct{^ksVcgMUY?GJ58:&(."($$$$# "#"$$,1":".F.XuCc{Ac{Ae~:e~:e~:e~:h}?g|>bw;_s7Xj5Rd/KZ.AO$5@+6+*. 5+A"7L.I\7Re@[oIcwRj{Vrƒ^xŠc|f}gzŒewŠgx‹huŠnyŽqv}v}{{zzxw|xzv~}“€ƒ•‡„–ˆƒ–…•„“€€’€’€’~’“€„“ƒƒ’‚„~|…vu€`hsT\i>Xd9Vg;[l@\tNd|Vi€\p‡ctˆgv‹jzŠnzŠn{‹o{‹oyŠqyŠq|‰ny‡luikx_hnVZ`HPW:?G*.9%/+).!<()L;>aPOnb_sv}{“‚…–‡˜€‰—€‡–Š”‚‹•„‡–„…•‚…–…–…”„€ˆwx€os|hjs_WiMK^B=Z?@^BHgTPo\Vwf[{kh„bg‚`n‚SkQp„Np„Nq†Np…MpƒLt‡Os…Nr„Mn€Kk|HgyDbt?^r=^r=^r=_t>]v;^w<`vAbyC;C;C:A8@4@4@1>1>0<0</:/:/:/:.8.8-7-7,8,8)6)6)5'4#0#0#0#0"/!.!. ,"+"+$-#,"*"*"*"*((%%%% (&('&&%%#####$!!!!       + + + +   + + + + + +             + +   + +                   + + + +  + + +    +       ( (2:CNW/fm5v}E„‰K‹R…P}‡HlyG[g5BQ3(6&% !! $$#&#'+(,-)23.==-EF6OP2WX:ad1ei6jq3mu6q|2t€5xƒ9v6p‚4i{-\r,Xm'D`);V /L%*G!%@%:1&1&-)-).+-)-)-),+,+,.,.,/,/)-)-+.+.)-)-(,(,%,%,$+$+$+#*")")!&!& %!&!&!&#&#&"$"$#&%($'%(%(&)',$*',(-'/'/*1*1*1*1*1*1)3)3 *4 *4 *4 *4!+5!+5+6(4*5(4 )5 )5!+5#.7)08,2:34856;999:::?@7BB9FK/HM1IQ-KR/KS-KS-MU/NV0P[2S^6\i>gsHo|Ow„W}ŒY}ŒYyŒRu‰Ou‡Kr„Iw…Lw…Lw…LyˆO}‰S}‰Sz…OuKp}=iv6hr*fq)iq0mv5s|9zƒ@ŠHŠ”S›W”¢^“£n“£n“¡q“¡q‘ t‘ t‘¡z’¢{“€–ŸƒŽ—ƒmvcMTL>E=@H7PXG_bMpr]vtkmlcmkiomkVSVB>AKFU;6D<7N>8O::S::S:?SEI]Qb_L]Z?QkDFfL:Z@WgYeugx€o~†t€Œh}‰ez‹XyŠWyŠUyŠU{X{X|[}Ž\|ŽYzŒWz‡Ur~MruLmoFjmCjmClrEouItzLxQ~‡RƒŒV‚Ž^‡“cˆ“qŠ•t‰•zŠ–{†’„‚€~‰|vtpvebhWOV@ZvDc{Ac{Ae~:e~:e~:e~:g|>f{*/RAAeSRrfbuv}|”ƒ…–‡˜€‡–‡–Š”‚‹•„‡–„…•‚…–…–ƒ’‚€Ž~€ˆwx€opxegp]ReII[?;Y>A_CHgTQp^Wxg]~mh„be€^n‚SlRp„Np„Np…Mp…Ms…Ns…Ns…Ns…Nn€Kk|HgyDbt?_t>_t>`u?bvA`x=ay>byCawB6A 6A 8A8A6A6A1>1>0</;/:/:.8.8.8-7,9,9+7+7'4'4'4%1#-#-$.$.$.$.$.#-"+#, )"* ) )"*"*((%%%%%%&&%%###""" #!      + + + + + +       + +    + + +                  + + + +    +  + + + + +     + + + + +  +      + +   +   " *09BK)X`4jrEy‚G…S‰’O„It‚Eft7O]2;J%0)!'!''((**, ./$45';;.EC/LJ6VW4ZZ8af.ej3js0ox4q4r€5n€7n€7d~8\v0Li0Eb)9T-3N(*C-$>'8(3$2*/&-)-).+.+.+.+,+,+,+-,,.,.+.)-)-)-)-)-)-(,%,%,$+$+#*#*")")!&!& % %!&!&"$"$#&#&$'$'#&$'&)&)%+%+%+%+%,&-)0)0*1*1*1*1)3)3(1(1)3)3)3)3)3(1(0 )1#)1$*2*-1/261413637:39;4>=2BB6FI2FI2GL0IN2KR/KR/LT.MU/LT.MU/R[1Wa7Ye:bnCkvPt~Xw„_y…ay‰Yt„Tu†Lt…Kv…Jt„Iw†KzŠN‹R‹R‹R{‡Ot‚En|?lz4ix1mx4p|8w;}†AŽHŒ˜R ]”£a”¦q”¦q—¥z–¤y—¢€—¢€” ƒ—£†™¡‰š¢Š˜ ˆŽ–~\`O47&@A*YZC]eAV^:ioPbhIgnR€ˆkux\WZ>ggU^]KHDC@<:=6DB:IIDRPKYUVZOPTCLDJSKcm^dn_mt_kr\bfSX\IEI?58/'/+'#%%((((''((&&"*2CLT>DGBGKKQTLRUT__@KL2G=BXM3V@/R=;_E4X>CbH?_EXhZeuguƒnx†rz‹hxˆfyŠWwˆVyŠUzŒW{ŽV{ŽV|W}Y|V|V}ŠUw„PpxGlsBhp?lsBjtAnyEt€Iz…O}ˆTŒX€`ƒb„o‡‘rˆ’|Š”~„’}‚{|ŠuuƒnpveagVMU?;B-+4 %.%0#-8+3B7BRFObL^q\n€fyŠq‚n…’p„“gƒ’f‚‘e‚‘eŽgŽgii€m€mŒlŒl~Šh}‰gz‡`vƒ]v‚`y„b{‚ex€crz]mtWeiPVZAFH468#'-!'##$$&%##!$+ 3"&@!1L,@\3Lg>Ur=ZwBb|d|;d|;e}]o8Vg4O`-GT+;I .9&0#1)7,>6I&>R,K^9VhA^pIewPnXr…[yŒb|Žd}f|ŽdzcvˆgxŠjw‰ty‹vvŒ‚xƒ{‚zŽxŒyxŒy|Ž{~}‚“ƒƒ•‡ƒ•‡•„€”ƒ“€€’€’€’’‚€”ƒ„‘†ƒ…„{|‡su€_iuS[l>Rd6Uk?[qE_wSf~Zmƒds‰jwŠnv‰mz‰pz‰p}Šq}ŠqyŠqyŠq}‰rx„nsijv`ciQV\EGO26=!)2!*'(2#%?/4SHFeYWvjiˆ|z’€}–„‡˜€…–‡–‡–Š”‚Š”‚…•‚„”„“ƒ„“ƒ‚‚€€‡{x~rmuddl[N_FI[A;WB@[GHeWQoaXwe[{hf‚^e\j~Pj~Pn‚On‚Op„Jr…Kr„Mr„Ms…Nr„Ml~Ii{FfxCbt?bw?bw?cvCeyFeyDdxCcvC`tA6A 6A 8A8A6A6A1>1>0</;/:/:.8.8.8-7+8+8'3'3$1$1%1$0#-",$.%0$.%0%0$.%/%/$-"* ) )"*"*((%%%%%%##!!""" ""#! !     + + + +   + +     + +    + + +                  + + + +    +  + + + + +     + + + + +  +      + +   +    +%)28A OW+ck>r{@~†LŠ”PŠ”P}‹NqAXg;IW,5A&%0'-'--.#./$02&24(::-??1HE1OL8UU3\\:ch1hm5kt1py5p~3q4n€7k}3]w1Uo)Gc*>["2M&,G!$>' :$4%3$0'/&-)-)-)-)-)-),+,+,+,++-+-,/+.)-)-)-)-)-(,%,%,$+$+#*")")")!&!&"'"'!&!&"$"$#&#&$'$'#&$'&)&)%+%+%+%+'/'/)0)0)0)0*1*1)3)3(1(1)3)3)3)3(1)3 )1"*2&,4(/7/260375968;8;=6<>7BB6ED9GJ3GJ3HM1IN2LS0LS0MU/MU/LT.MU/R[1S]2Vc8\i>hsLozSu]y…awˆWu…Uu†Lt…Kv…Jt„Iw†KzŠNUUU}ŠQ{‰Lt‚Ep8lz4q}9r~:x‚<€ŠD†“L™S‘¡_˜¨e”¦q–¨s–¤y˜§{š¦„š¦„™¦ˆ›§Š¥›¤Œš¢Š–ž†wzj\`OWXA[[ENU2,4]cDw}^ƒŠn†Žqƒ†j}€dnn\^]KSOMVRQNGVPHWGBPGBPRSXPQUEOFBKCLUFV`QdkUaiS_cPV[HGJ@9<2'/+$!%%((((''''$&&-.7?SY\X]aDJMPUYMXXALMI_T?TJ;%"D/%I/+O5:Z@FfLXhZfvhuƒnx†ry‰gxˆfyŠWwˆVyŠUzŒWzŒUzŒU|W}Y|V|Vz‡SrKpxGlsBkrAnuDlwCq{HuK{‡P{†R‰U~‹^Œ_‹l„o†yˆ’|„’}‚{|ŠuuƒnpveagVMU?;B-,5!&/'2%/:-8G;GVKTfQdwarƒj{sƒ‘o†“q„“g‚‘edd‚h‚h‚Žj‚Žj€mŒl~‹k~‹k~Šh}‰gz‡`vƒ]v‚`y„b{‚ex€crz]mtWeiPVZAFH468#'-!'##$$%$###&.#6&*E%5P0C^5Lg>Ur=ZwBb|d|;d|;e}"4F#=P-G[6Re@[mEbtLi{SoYs†\x‹ayŒbzcx‹awŠ`u‡fvˆgw‰ty‹vxƒyŽ„{‚zŽyzyz|Ž{~}‚“ƒƒ•‡ƒ•‡•„€”ƒ“€€’€’€’€”ƒ•„‡“ˆ…’‡„{|‡st^grQ\n?Xj/>.=-<,:,:,9,9+8+8&5&5&3&3"0!/$/$/%-'/(.(.-2040617+3*2$/#.",", ) )&&%%%###%%##!!""     !!   + +     + + + + + + + +             + +     +  +  +  +  +  +  +  + + +   + + + +   + + + +      + +      + +      +") 19CH%V[8jp=w}J…ŒMŠ’S‡‘K~ˆBkv9]h+HT$6A272777::=<$=<$EC(KI.QO-XV4Y\0]a5gj3lo8ou6rw9n|8o}9g|7bw2Qm4Hd+<[/5T(*E*$?$6&5%0*0*/-.,-),(-)-),+,+-,-,+-+-,.,.+.+.)-)-*+*+*+)*&+&+$($(!*!*!*!*"'"'!&!& & & & &"$#&#&#&#&$'&)&)$*$*%+',(0'/'/'/(0(0 )1 )1*1*1)0*1)3)3!'1!'1 &0$)4*-1*-101/341770993<>0>A2?D/BF1FG.HJ0IL.JN/KP-MR/NS0NS0MU/MU/LT.NV0MW,OY.P\1Vc8]lEfvOn}Xx†bt„]rZtˆLs‡Kt‡Gr†EwˆHz‹K€ŽPƒ‘T„’V€R}‹Nx†Ht?o|:p~:o}9uƒ?}‹G’K‡˜QŠž[‘¥b’¦l”¨n–¦v—§wœ«„¬…š©‰š©‰Ÿ¦‘ §’ž¥˜ŸŠ“y—s‹˜b…“\rzFV^*~‰Z‡“c„‘d…’eƒg|†`ryXcjI[aIGM5?A5SUI\Y\VSVRMUMHPIFNGDKGGNHHOJMLRUT^`YXZSMNE=>4+.+$'$ $ $&&((*)** )&"$,.NYR]hbLTQIQMV__IRSPd_?SM8(+G8/O:7WBC[JJbQ\k`kzoy…oz†p|‹fy‡c{Š]z‰[wˆVwˆVyŠU{X|[|[|[yŠWs„Qk|IisFdoBdp?dp?hrEkvIo{Ir~MrƒPu†Tzˆ_zˆ_~‡mŠoŠv„{‚Ž~‹|zŠusƒnpxe_hUMV@;E/.6%)1&5%0?/>N@M]O[mXhzev…l}Œsrr‚’k€hŽgŽg€Œhi‚lƒŽm€mŒlŒl~‹kzˆfy‡ez…cv‚`v‚`wƒay‚ewdw|`otWfhOWY?GF487%),#&##""""##&*2%:%0L*:U3Eb4Li;Zr?^vBay>bz@ey=ey=fy@fy@fwDfwDaqC[k=Rb4IX+@M$3A)8-=/A6H =N'FX0Oa:WiB_oHeuNkzSpXt„]x‡`xˆ_u†\r…[u‡]t…gu†hsˆwu‹yvŠ…xŒ†yŽ†xŒ…xwŒ}{Œ|~~’‚…–…†–ˆ†–ˆƒ”†ƒ”†€”ƒ’‚€”ƒ€”ƒ…“†…“†‡”‡…“†…z|…rw‚\juO_o?^n>[nDdwMe{\kbq†it‰mv‰mv‰m|ˆp|ˆp~Št~Št|Št{‰r{‰rv…nt~hku_eiQVZBDH.49*0"(%,8+/G:=\RPoed|ypˆ„’‚ƒ–…‡˜€…–‹–‚‹–‚‡•‡•†“…’€„Ž„‹€„Ž„‹€}„|ryqipa]dUHZ@CU;:SFB[NFfWPpb\{dbikƒ]h€ZqƒSnQoLoLnƒMp„Ns„Qs„Qp‚Mn€Kl€DkCk€Ai@i~Ff{DhyGfwDct=ct=\n>Tf67D6C4A4A4?4?0?.=.=-<,:,:+8+8,9)6&5&5%2$1 . .#.#.&.$,(.+2/3266;7=2:07)4&2$.$.#+#+ ' ' ' '&#$$%%##!!!!        + +   + + + + + + + + + +     + +       + +     +  +  +  +  +  +  +  +   + + + + + +    + +      + +       +         & )09>HM*`f3pvC~…GˆQŠ”O‚ŒFtBht7Vb2DO 9>8=<>#BA(DC*LJ/QN3XV4][9]a5cg;hk4mp9qv7qv7n|8n|8f{5_t.Li0Eb)6U)0O#$?$:5%4$/)/)-+/-0,-)-)-),+,+-,-,././,.,.+.+.)-)-*+*+*+*+&+&+%*$(",",!*!*"'"'!&!& & & & &"$#&#&#&#&$'&)&)$*$*%+',&-(0'/'/(0(0(0(0)0)0'/)0(1(1#(3#(3%*5&,6.15/26341675;;4==6>A2@C5@E0CH2HJ0HJ0IL.JN/KP-MR/NS0NS0MU/MU/MU/MU/NX-MW,N[0S_4Wg@_oHiwSs‚]w†_t„]tˆLs‡Kt‡Gr†EwˆHz‹K}‹NQ„’V‚T€ŽPy‡Iu‚@p}OXXIRSBVQ9MG*F6>ZJ;[F)J4%=+:R@Wf[bqeuj|ˆq|‹fy‡c{Š]z‰[yŠWwˆVzŒW{X{ŒY{ŒYyŠWu†TnMhyGepCcnAco=co=cnAisFmzHmzHnMs„QuƒZy†]{„i~‡m}ˆtŠv~Š{~Š{y‰tr‚mmvc`iVMV@;E/-5$*2!(7'3A1@PBP`R^q\li{Šqw„’ur‚’k€hŽgŽg€ŒhiŒjŒjŒl~‹k~‹k|‰jzˆfy‡ez…cv‚`wƒaz…cy‚ewdw|`otWfhOWY?GF487%(*!#!!!!""##%, 5(='1M+;V4Fc5Ol?^vB`xDc{Aay>ey=ey=fy@fy@evCduB_nAYi;Rb4IX+@M$6D1@5E8J#>P(GY2Oa:Tf>[mEdsLixQixQpXs‚\t„]t„[qXn€VoXq‚du†hp†uvŒ{xŒ†zŽˆyŽ†uŠ‚xx|}€‘€’‚…–…‡—‰†–ˆ…•‡ƒ”†•„•„ƒ–…ƒ–…ˆ•ˆˆ•ˆ‡”‡…“†…z~†sw‚\kvP]m=]m=`rIgyOh}^n„eq†isˆlv‰mv‰m|ˆp|ˆp}‰r}‰r|Št{‰rzˆquƒmqzdgq[cfORU>@D*26(."(&-8+4M@CbXUsjh€|tŒˆ•„ƒ–…ˆ™‚‡˜€‹–‚‹–‚‡•‡•†“…’€……„Ž„ƒ‚}‡}y€xovnel^X`QEW>BT::SFE^PGgYPpb]|e`~gj‚\h€Zp‚Rp‚RrƒNrƒNp„Nq…Pt…Rt…Rs…Pp‚Mn‚FmEk€Ak€Ai~Fi~FhyGfwDfv?_o8Ug7Ob16C 6C 3@ 3@ 3@ 3@ /@.? -<,:,:,:+8(5)6(5%3%3$1"/ . ."+$-$-'1)1/7::?>ACFHBI?G:B5>/7/7-3+1*-*-(+&*"+"+"*!)''##!       + + + + + + + + + +       +  +  +  +  +  +   + +                   + +  + +       "*1?/@C,AE-AF*AF*GJ,HK-EJ,FL-JO,KP-KQ,MR-OU/OU/KS-LT.MU/LT.JS0IR/GU3HV4L[;VfFaoRn|^t„]t„]t†Qs…Pt‰JsˆIv‰Jv‰Jy‹P|ŽRW€’X}Sw‰MrƒCo€@n|?qAqAr€Bs‡?wŠC}•Fƒ›L‰ S§Z“¦h—©k™©{šª|™¦‡œ©‰ž©¨™¥Ž–¢Œ’Ÿ†‹˜…”m€h‹a~Š_~[€]€Žcƒ’f‚h|‰cv^ktSZbJLT;IB@NGDRKIWIKTKLURPWLJQ@>C205''.$!)!))(**++ )(")")%(+.$4/(72APF>LC5TC'F5"F2/S?6OH'@9+4;@IPelfagbsiy…oz‰dw…aw‡Yw‡Yz‹Xz‹Xz‹X{ŒYzŠZy‰Yu…Ut„Tk{KgwGao?Yg7[i9\j:`lAdqFiuLgrJlyRo|Vp}Xu]q‚ft„hr…ouˆsw‡yw‡yx„uq}niq_]eTJS@[4Lf9TnA^w?_y@c|?b{>fz>fz>fy@ex>evCbtA^m?Ve7P]6HU.>L#:H9G=L AR&HY-Tb9We<[i@aoFftKiwNlzQo}Tn€VlUj~Qh{Of}Uj€Xleq†iq†ytˆ|uˆ…vŠ‡xŒ…v‹ƒxxzŽ}~‘€€”ƒ„—†ˆ™‰ˆ™‰…–…ƒ”„‡•…‡•…ˆ–†ˆ–†‰–‹‰–‹ˆ•‰…’‡ƒx|ˆqrVesJ]p?]p?_qNfxUj~dp„jq…kr†lv‰mwŠn}Šq}Šq}‰r}‰r|Šu{ˆtx…ssmrwfgm\]bFLQ5>C'16$*!'' /"=15ODGd]Ywpk‡|u’†ƒ—„ƒ—„ˆ™‚‡˜€‹–‚‹–‚‰–„‰–„‡”„†’ƒ……„Ž„ƒ‚}‡}vrluhclVU_HCU;BT:=CCGIJLJQ FNBJ?G9A7?6;28/2/2-1,0&.&.'.%, ) )$$"!         + + + + + + + + + +       +  +  +  +  +  +   + +                   + +  + +     &,29FN!W_3ls7y€C…F‰•JI|‡Cp|?fq4X],KPMN"PQ&VU(ZZ,__/dd4ij3mm6jr1ks2kx1my3nz4my3ey1dx0]q5Xl0L`2DX)2K*)B 6)3%2'2'0*0*0*0*0,0,.+.+-,-,-,-,././././,/,/,/,/+-+-*+*+&+%*%*%*%($'#&#&!&!&!&!&"&"&!$!$"#"#$%$%$%&&$%$%!$("%)!$("%)"$+#%,$'-$'-')/%(.')/')/')/')/')/')/%*.',0,-1-.2341452;;2;;2?@0?@0AE-DG0EJ.EJ.HK-HK-FL-GM.KP-KP-MR-NT.NT.NT.KS-IR,KS-IR,FP,CM)@N,AO-ET4N]=[iLguWq€Yt„]u‡Rs…Pp†Gr‡Hv‰Jv‰Jy‹P|ŽR~VW}Sw‰MrƒCo€@m{>lz;IGDRHFTDEN?AIGDKJHOCAF<:?--4& ( ()(**++ )(!(!(#&)+(72>MHHWM@NE/N=%C3(L83WD7PJ'@9.7??HOhnicjdsiy…oy‡cw…aw‡Yw‡YyŠWz‹Xz‹X{ŒYy‰YwˆWt„TsƒSk{KeuE]k.9J9GXGWhXfzbs†o}Œsu€n~ŽlŽgŽgŽgŽg~Œhi~‹iŒj‹n~Šl|‰k{‡jx…ct‚`v‚`v‚`y„bz…cy‚ewds|`ktWcgMUY?DH.37%+%      '-#9!,B*5R+?\6Mg:UpB^w?_y@b{>c|?fz>fz>fy@ex>duB_p=[k=Td6P]6GT-BP'+; +8)7+7)6)5&1'2&1"/ - - -"+$-'/*2468;D@IEQNUSWXWXRWOSJRGPDJ@F<(A>*CA-DB.DG+DG+GJ,GJ,JM(JM(KM-KM-LP+LP+LQ'MR)IQ&JR'FR)FR)KT+IR*FP,EO+=L,:J*5E)6F*8L2CX>TdIaqVn|Zr€^tˆUr…Rs†Ls†Lw…LyˆO{‰S}‹T~ŒU€ŽWVy‡Pt‚Lm{EhuAer>_n:]m8_v2by6h€1qŠ;y•<~šAŠ¡V’©^’¢r–¦v• …™¥Š–¢Œ–¢Œ–¢Ž”Ÿ‹Žš„†’|k~ŒhvŠ[vŠ[wˆ\yŠ^|ˆdy…as{cmu]fk\^cTZ\WWXTTTVQPSOKUGCMIAPE>MD7M;4I<9N@>S>9L95H=;MFDWIBNGAME?B("%!" ' (&('+* ) ) ) )!#&(/*0B=IYMAPE7TB.K87QHH,7A*/A+1B=CFcimv€oy„rzˆfy‡ew‡Yv†Xu†TwˆVyXyXuˆWs†Uu…WrSkzLaqCVf6Rb2R^:Ta=^dFioPnr_bfS@@:9936@7Zd[]r]av`dydi~hmƒqq‡vv‚ro{ldp\WcOFQ=7B.)5&.:*2B4?OAO`P\m\o~exˆn€q€qk€Žj~f~f~f~f~f~f~‹k~‹k|ˆm~‰nz†iy…hw„_w„_v‚^v‚^wƒay„by‚ev€cr{^isV`fLPV<@F.06#+& (1 %= .E(:R,C\6Pi:XqB`vAawBfy@fy@g{Ag{AgzBgzBdtD`p@]m=Wg7Qf0J_)EYDXGW"M](Sb/Zi6[k=`pBdqFiuJksNksNjvMiuLdvH_qCYqFYqF_tQezWj}fn‚jrvw†zv‰wŠ‚xŒ‚xŒ‚x€zŽz~|’€•„…˜‡‰šŠ‰šŠŠ™‰Š™‰ˆ–†ˆ–†‰–‹‰–‹ˆ–ˆ–ˆ•ˆƒ‘ƒ†’u‹nt‚YiwNctHctHfwYm~`n€frƒjtˆgu‰hvŠkvŠk}‰rz†p|ˆq|ˆq}‹v|Šu{‰rsjnv^dlTY^;JO,<@!14#(!&($3(,C=AXRRnea}tpŽ€w•‡„™ƒ„™ƒ‹™ƒŠ˜‚‰–„‰–„‰–„‰–„‰”‡‡“†ˆ††Ž„ƒ‹€|„yu{jkp_ahLT\?FT8DQ6?SIL_VQk`Xrgd}id}ik€[k€[lRlRn„Nn„Nr†Qr†Qp‡QqˆRqŠMqŠMtˆLs‡Km„GlƒFn‚Hk~DhtBam;Xa>MW34=1717+3'/!'%"!!     + + + + + + + + + + + + +  + +     + +  + + + + + +  +  +     + + + +                    +     " "*49DI&_e-ot<{…?‡‘KŠ—Jˆ•G„H~ˆBy>7>>7BB6BB6BC3BC3IF2JH4KI5JH4HJ/HJ/JN/JN/LP+LP+MO/MO/MQ,MQ,LQ'LQ'JR'JR'GS*FR)IR*GP(CM)BK(7F&6E%1A&0@%1E+;O5N_C[kPn|Zr€^tˆUr…Rs†Ls†Lx‡Nx‡N{‰S~ŒU~ŒU~ŒU}‹TzˆRsKkyBcp<\i5Ue0Td/Vl)Xo+_w(k„5t7{–>„›PŒ£YŽžn“¤s”Ÿ„˜¤‰–¢Œ—¤• Œ“žŠŽš„‡“}k{ŠevŠ[vŠ[u†[t…Zt€\myUbkS`hP^cTV[LTVQRTOQPSTTVROYGCMF?NG@OE=N?7H5.C3+A74I<9N>9L>9L>Wc?lrTjpQIM:6:'>>7993(2)S]T[pZVkV\q\g|flpmƒqsomyibmYU`LAM93?+,8(0<-5F8EUGScSbsbqg|‹rƒ‘sr€Žji~f~f|Œe|Œe|Œe|Œe|‰jŒl~‰n|ˆmz†ix„gw„_v‚^v‚^v‚^wƒay„by‚ev€cpz]hrU`fLPV<@F.06$,%!*1 (?"2J->V0H`:Rkfy@g{Ag{AgzBgzBdtD`p@^n>Zk:Th3Oc.J^$I\#N^)Td/Yh5\k9^m?brDgsHhtIjrLksNfqIalDYk=Xj -> -> +; ): +(6 (6 (4'3&1%0&1$/ , , - -"+"+$-)146:<FDMKTSXW\` ^a!\bZa[^ Y\Y\WYTUUWTUSTRSRSSSRRJMHKFHCE:B5>-7(3 +&$# !          + +    + +     +  +        + +         + +            +         "!(15=B!WZ*ik;w>‚‹JŠ”P–R‰”L…G‚Œ?}‡;{‚6z5v}3v}3v}3z€6x€8x€8v€8v€8p€7p€7k7fz3]t5Xp1Fg5>_,0R1(J)@-;(5.5.5.5.3*3*2+2+3,3,3/2.3,3,"4(#5).7(/8)5<(6=)6>(5<'1<*,7%&/'#-%)')'&)$'&)&)&%%$#)$"(#$)!&+#)+(**.+01133358:;<;<==@@DD"DD"GF!LJ&NJ&PL(SN(UP*UQ)WR*YT,YT,ZV-\X0\X0_Z2^Y1\X0_\/_\/^])^])^\*^\*\[)\[)^^'^^'\^$_`'X^$V\"V[$UZ#TX#TX#QU"OS LU!LU!HQ"HQ"DO'@L#;F%:E#1>%.:"(8$'7".;)PZ4FY/DV-F\)I_,Vr'^z/p)t’-~™:ˆ¤D¡`•©h–£{–£{”¢†”¢†“ ‡’Ÿ†Œ˜‚…‘{Œj{‰gwˆ\u†[u†\u†\qy\fmP[]QVXMSPSNKNKHRGCMHDPQMYKFU>:H@9L>8K;4I2*@2)C6-H92L>7Q?8RB\aEbgK^XN`ZPXNPE<=;771,-ADA^a_VeSVeS\p]g{hklj~kr}kkvdflYW]JBN:3?++9)2@07I;FYJUhYbtfuƒn}‹v€Žs€Žs€ŽlŒj|Šh|Šh|Šh|Šh|Šh|Šh{ˆh~‹k~Šl{‡jy†fw„dv‚^t€\u]t€\v‚`wƒawdubpz]hrU_eKQW=BF105 #*$  , 3"+C&6M0=Y.Jf;Vp>ZtCax@byAfz>fz>fy@fy@hzEfxCfyAdv?_s7]q5\n+Wi'Wh!Wh!Uf&Zk+]l3ao6gr>jtAhrChrCepCcnA]j?Vc8Oa:Oa:NeBUlI\pVdx^h}gllq‚qu†vt‡}t‡}uˆ~uˆ~vŠ~zŽ|„”‡†˜Œ†˜Œˆ˜Šˆ˜Š‰—‰‰—‰‰–‹‰–‹Š–Š–Š™ˆ—‰—‡…”„†“s~‹kw†[lzOiwNkxOlz_p~brƒkt…nuˆlt‡ks…ku‡m|‡s|‡s}ˆt~‰u}‹v|Šuw†oo~goy\blOY^;HM*:=/3$)$) 0$+:/1LHE`[Vsnfƒ~|“†•ˆ‰˜†ˆ—…Š—…‰–„‰•…‰•…‰–„‰–„‡“††’„†‚…Žƒ‹xtpxbdkUYc?MW3ET4GW6F\KOdSWmb`vlf|if|ih~Xh~XlRlRnƒRo„Sq„Qr…Rp†Rq‡Ts‹Ss‹SqŠMp‰Lr†Jn‚Fo}Dix>cl=Yb3NP5GI.5D2;J82B 2B 0@ 0@ -> -> -> -> +; ): +)7'5 '3'3%0$/ ". ". , , ++!*!*&.)11458DBJHRQVV[_\` ZaZa^`"\_!Z]Y\XY WXXY Z["Z["Z["[\%\]&UX'VY)QS%LM BJ?G6A1<'2#-&$!            +     +   + +  "        + +         + +                    #")/4;?OR"bd4px7}…Dˆ‘N˜UŽ™P‰”Lˆ’E†C„Œ?…@ƒŠ@ƒŠ@‡=†<|…=|…={†>{†>v†=v†=l€8i|5_w8Xp1Bc0:[(-N.$F%<);(5.5.5.5.4+4+4,4,4.4.2.3/6060(9..?3:D5>H8CJ6GM:HO9DK6>H77A01;3(2) -+*('*'*)+)+ *( *(%,&(.)+0(.3,45*68,59&8<)=>)BB.DG+DG+JK-LM/TT2TT2TT2VW4\[6^]9`\9c_;gbsq?sq?sq?sq?qr;qr;op7no6hm4ek1di2ch1_d/[_*W[(TW%MV"KS GP!FO >I!,9 *7#3#3-:(6C1HT>VbLhtRt^v‡Ut…Rs‡Ks‡Kw†Kx‡Lz‰P{ŠQ}‹T{‰S{ˆVx„Rp|Jdp?XbM>8K<6I<5J91G90J;2L92L>7QC=WG@ZGEZDAVCBMFDPED@\\WmhbKGA"))*+(()()!,#-!+5)68Q]`[jnM\`O[`8DI(+='*<$(7>BRX_^jrpvmz†rx‡`u…^w‡Yw‡Yu†Tv‡Uu†[m~SYiBO^7YgEdrPetO`oK`jDZe>bgKRV:<6,E@5I?A>56HCDWSS[^[RVSJYGO^LXlXezfn‚olmq|jisbbiVSZGAM92=)/>.5D4fz>fy@fy@gyDgyDfyAfyAdxJO,:=/3&+&+-",;06QLJd`Yvrj‡‚{’…•ˆˆ—…‡–„Š—…‰–„‰•…‰•…‰–„‰–„‡“††’„†‚…Žˆ{w€snu`aiSYc?MW3DS3IY9F\KOdSXnd`vle{he{hk€[k€[n‚SkQnƒRo„Sr…Rs‡Tq‡TrˆUs‹Ss‹SsPsPs‡Ko„GpEfu<`i:V_0KN2CF*4C1BQ?0A0A0@ 0@ -? +-? +-= ,< ,:)8 (5&3 &1&1&1$/ . - , , )) ' ' ("+%.+47;>BEIJN QWV[$Y^ Y^ [\#[\#[]"Z\!\\![Z]]"`_$ab"ab"ed$ed$b_$da&b_$b_$X\"RVHQAK3C+; #5/& $ !"     "!!              ! !  + + !    "          + + + +                         %$)-36;@JOY.em:pyE}…K‡U™UŠ•Q‡–I…”F‡–A‡–A‡‘D„ŽB‡‘B„@„@ƒŽ?|‹;~Ž>z‰@z‰@q…=k7[w9So1@c08[(&L-D%;*:(70706/6/5/5/6.6.50506,6,,;)0@-FJ)PT4_]-eb3ji0nl4mo3jl1ch1\a*KS-=E,2+1),%+-&)-!/3':7%<9'A>&EB)EI"FK#PO$US(WS(YU+`Z.c^1e^/jc4le/ng2sk1tl2wo5{s9wr8{v;|w<}x>€{>ƒB‚~?„A„†C…‡D…‡D†ˆF„ˆG…‰H†‹H…ŠGˆ‹Fˆ‹FŠ‹IŠ‹IˆŠHˆŠH†‡G‡ˆH„ˆF„ˆF‚ˆ?†<~…;~…;{‚8w~4u|2qx.lr(gn$[g!S_LXHS@L;H4?.9)6%2$/%038(@E4JQS]9GQ-7C%.;3B>L(Fb$Ql/f&q‹0w”7‚ŸAŠž[’¦c“¡x–£{–¡†”Ÿ„’ž‡›…Š˜z„’uiy‡cy†]v„[u„Xw†[v‚`nyXor[hkT`\VYUONJPSOTRQUOMRIHRBAKA;L<6G82J71H92L81K:4N:4N@:RC>UDCSKJZKKTCCLGDKCAH@>7olf^\N53&!#!!))(",',#(#(#(%+/7GP3IT:]hMis[ny`u~dr{`szdowaclXR[HBK78A-0@27G9ASGOaU_raj}m{‰r}ŒuŽnŽnŒj~‹i|Šh|Šh|Šh|Šh|‰j|‰j|‰j~‹k~Šh|‡fy…av‚^u‚\rXp}Wp}WrXs€Yr}[o{YlwUfqP[bHMT:?C203#%+!#!&2);&/J(:U3Hd4Pl<[u=`zAc}>c}>fz>fz>gzBgzBfyAh{Dh}?g|>e|8dz7dy-^t'`p%`p%_p+br-cq3es5fq=gr>fnB_g;T]5PY1GW.FV-@X2H`:OfLYoUbt_hzei|lnpnƒtq†xs†|s†|q†|s‰~xŠ~|{‘†€•‹…–Œ‡˜Š—ŒŠ—ŒŠ—ŒŠ—ŒŠ–Š–Š•‹—Œš‘‰˜‹—‡‡”„†•w€q{ˆarXn}Xn}Xr€ew…iuˆlt‡ks…is…iv…nx‡p{ˆtz‡sz†r}ˆt|‰w{ˆvw†osjox^dmRZa>IQ-;?15'/'/#4*0A7;VOOjc`{vn‰„”†‚—ˆ†˜ƒ…—‚‹—‡Š–†‰•…‰•…‰”‡‰”‡‡“††’„……ƒ‚}‡vumoxWajIYc6OZ-FT0KZ6M^MUfU[oebvlh{ch{ch€Uh€UjRjRlQnƒRpSr„VtˆUu‰Vt‰St‰Su‹Qu‹QuŠLp†GvFku;cg;Y\0KJ3ED-9E +-? +-? +,< +; (7 )8 (5&3 &1$/ %0%0 . - , , )) ' '''"+'0477;?CDHLQMRQVQVUWUWWYUWZY\\!\\!^^#`a!`a!fe%ig(jg,he*he*he*_c)\_&R\$MW@P :J,>%7 ,(#"     "!! !!!!!  ""  ! !   ! ! ! ! ! ! ""  ""!           + + +                       %$)+1179CGQ&Zb/goN;W[;`eDspAzwH~E€G‚„H‚GzHtyBaiCOW2;A.28%03,46/7;/;?3IF4JG5QN5UR:W[4Y]6a`5db7gc8ie;rm@snAyrC{tF€yC‚{E…}C‡€FŠ‚HŠ‚H‡GŠ…JŠ…J‹†LŽ‰L‹O‘NŒM‹JŽN‘O’”Q’R“S”Q‘•R“–Q“–Q“•R“•R’”Q’”Q‘’RQ“P“P•K”J‹’H‹’H‹’HŠ‘GˆŽD„‹A†J4?/;%2$1%0*5#5;*>D3LT>ZbLhtRs~\u…Uu…Uu‡Ku‡KuˆIv‰J{‰S}‹Ty‰T{ŠU}‹Rx‡Np}Idq=LV29C%1%1,;8F"B] Mi+b|!l†,w”7€ž@‹Ÿ\’¦c”¢y–£{• …”Ÿ„†›…‹™|„’ui{Šey†]v„[v…Zw†[y„by„by|etx`njdlga`\a\X]SRWMLQHGPHGPD?O@:K<7N<7N>7Q;5O;5O:4N<7N?9Q>=MEDTRR[KKT<:A:7>>;5GE>c`Slj\.+,') )("'"'%+%+)16AHNZfkZfkERTFSU?HO%.6%,:%,:=AIW[c\g\GRGUbIx…lz‰cx‡`w„Ww„Ww…Uv„Tp|QhtI\dFbjK]bOSWDTVJRTHNLDMKBKGAOJDk^\dWUc[Qc[QCC0ONA1/2"#($$'"4+>(4O-=X6Ie5Sn>^w?`zAc}>c}>fz>fz>gzBgzBfyAh{Dh}?h}?g~:f}9h}1bw+ar&ar&_p+br-dr4dr4bl8_j6[c6U]0OX0KT+AQ(CT*@X2H`:QgMZpVbt_hzekno‚qnƒtq†xs†|s†|q†|q†|xŠ~|z…”Š†—ˆ™Š—ŒŠ—ŒŠ—ŒŠ—ŒŠ–Š–Š•‹—Š™ˆ—™ŠŠ–†‡–x„’u€fu‚\r€\r€\u‚gx†kt‡kuˆls…is…iv…nx‡pz‡sz‡s|‡s~‰u|‰w{ˆvzˆqt‚kr{`foT[b?KR/?C"48(0(0%6-3D;=XRPkdb}yoŠ…”†‚—ˆ†˜ƒ…—‚‰•…‡”„‰•…‰•…‰”‡‰”‡‰”‡‡“†……ƒ‚}‡vumoxWajIVa4P[.JY4P_:QbRZjZ[oebvlh{ch{cf}Sf}Si€QjRlQnƒRpSr„VtˆUu‰VyXyXxŽSwŒRwNt‰Jw‚Goz?gj>Y\0LK4HF0;G>P\S1B 1B 1A /> +,> +<+; (9(8 +(8 +&4 $1$1 +$1 +"/ "/ / / , ++ ) ) ( (''( "+&/ *31:3<6D;H>KANJOJOKQLSPVTZ TZ TZ X\"X\"bc#de%eg'gh(hi)hi)fd'fd'cd$`a!W_ NU?N9I 0>+9 +"/ ,*&$ +$ +! !  """"!"""""""""""""$  ! """#$#"" ""!!         + + + +       + +            ""%&('.,35=?!BP'M[2Zq.h8K@9LB=KGBPQOTEDI99@77=88F66C459NOSDFA$$$%"( #'"%)341]][MQGDG>ANJ@MHH[X=QM>FP%-7,,>,,>EKNU[_fn_go`djSv|d}ˆa}ˆavƒVu‚Ty‡Wr€QdpGalD\dGX_CRNFPLDRKQOINE?KE?K>;E<9BNOFPPGejNbgKZc;GP(H[!I\#Rg)_t5f{DezBdvHYk=P[AitY]gQNXA>J47C-3D4?P?J]LZm\cvfpƒr}Œs}Œs~Žl}j~Œh|‹f{Še{Še{Še{Še{‰g{‰g{‰gzˆf{‡cz†bw„^tZo}Tn|Sm{Rm{Rp}WrXp|ZnyXkuVeoP[`KLQ<=A./3 #)#%,$80D*7R+C]7Mi7Uq?_y>a|Ac}>c}>d{Q.AV:I^BSjM\tWexck~hj}mo‚qo‚zpƒ{s„s„q†|q†|w‰}y‹{‘†”Š†—‡˜Š—ŒŠ—Œ‹˜‹˜‹—‹—Š•‹—ˆ˜‘ˆ˜‘‰™‹‡—‰ƒ“~€{‹nwƒes_s_uix…lu…pu…ps…it‡kx‡px‡px†rz‡s}ˆt~‰u|‰wz†tw‡ms‚ip{\fqQ`fAQW2DG$:<)2)2):08I?A][Tpmi}vŽŠƒ•‡„–ˆ‡—‚…–‡”‚‰–„‰–„‰–„‡“†‡“††’„†’„…ƒƒŽ~‰uu€lnxT`jFVb2S^/M[7Uc?TeT[l[^sdbwhi~ai~agTf}Si€QjRnƒRo„SpSqƒTs…Wu‡YyŒY{\zŽYyXwŠSuˆPr‚Fjz>dm>V_0ML5JH2BNEWcZ0A0A/> +.= +<);): +'7%4%4$1$1$1 +$1 +"/ "/ . . , ++ ) ) ''&&' ( !* #, (1*3 +9 +0>2? 4B >D>DAHELGMIOJPMSQUQU\]`a!bc#de%gh(hi)ki,he(gh(eg'_g(X`!KZFUb|.#?/!;2 91#;0$<10A/7H6KU/U`:gq2t}>‚Œ?Ž˜Kœ©N¥±V©¶[­¹^¦¶_¦¶_¥²cª[“ŸY‡”M|†Gmv7lo8nq:ww:||>…?„‰D…Ž?ˆA‹•A—D™C™C›=›===ŽŸDŽŸDŒž@Œž@ˆž=ˆž=ˆž=ˆž=„9ƒ›8„9„9‚ž7‚ž7„¢7†£8Š¤<‰£;‹¥=‹¥=‹§:‹§:Œ§<Š¥:Ž¤AŽ¤A¢B¢B¢B¢BŒ BŒ B‹¡E‹¡E‹¤>Œ¦@¤F‘¥G“©F”ªG˜¨J•¤G—¤N˜¥O– S•ŸR˜P‹”L‰N„ŠJ„K|€Fy|Csv=ot8jn2dj0dj0hl7pt?oxKyTu†Tv‡UuˆPpƒLw†Kv…J}ˆF~‰G†H“›V°¸k»Âv´Àu¶Áv»Á€ÂȇÄÈ“½ÁŒtsH.-9@MS3TcB97J/-?0-D,)@88>^^eSXJCH9STK@A8#%%X[Zz{wstpttmoohNUO172ALMFQQCGV(,<,-==?NPTXW[_QVGgl^|€bx{]u~Q|…X}ˆYz†Vv‡UrƒPguLP^5RR>XXD]ZVSPKGF4?G6:K;GXGRdV_qcnhwˆqŽn~m|hyŠexŠcxŠcx‡`x‡`zˆfzˆfz‡gz‡g{Šez‰d{ˆaw„^sXo}Tn}Qm|PkyNkyNn}Qn}QnzVmyUhsTbmNY_EKQ79?,.5!#)#  !* 3"+@$7L/A[0Ic8Ys:_y@e|?f}@g{Ag{AgyDgyDgxFgxFfzGi|Ie|?cy=g9ay3dv,`r)^m$]l#Zi"[j#Xc&Q\AR=N9G4C3B9G#@N*HW2N]?UdF^mTcsYdzbh~flmoƒpr‚{s„|s„s„q…{s†|vŠ~zŽ|Ž‡‚”‡™‡™‰™‹ˆ˜Š‰™Œ›ˆ–‰—Š—“Œ˜”Š™”Š™”Š™”…”‡•…„“ƒ‚’v{‹o{ˆh|‰j{†m{†mv…lv…lr†lr†lu†ou†ow‡tw‡twˆvxŠwy†yv„vrˆirˆinUbuI\j:P].FM#:A,7-9,@3?SGIdd[vvn†‚w‹ƒ•‡ƒ•‡…–…–‰–„‰–„‰•…‰•…‰”‡‰”‡‡“†‡“††“„‘~‡nv~flvL_i?Ua/P]+R_9[hB\iWgtbezei~hl‚\k€[i~Th}SkSl€TnUl€Tr†Zq„XpˆUs‹WvWx’Z|•V|•V}“R{‘Py‹Qr„JnwHfo@Y]CW[BWeWjwj-@-@,= +,= +'<&;&9$7$5 #4"2"2 0 0 //. . ,,* +) ' ' ' ' % % & & &****.,!."2&5'4)6 -; 0? 9C:E?ICLINNSUXY]#`c%ad&df$eg%ij*hi)gj,eg)af*\`$KW!BN:F4@,9&3 . - )( ''%$''%%%%%%%$ $ $ $ $ %%''''& & ''%%#$""!    + +    +                + +  "& '#+'/,4/73=7B#AN'HU.R^3Xd9_m;cr@et;jy@o‚Cq„FwŠD}Jƒ”F…—I‰˜DŒšFŠ™GŽœJŸKŽHŒœL‡—G‡‘K€ŠDuGnx@^m?Wf8D\=:S3+G8)E5'@8#=4%=3*B78I7>O=S^7_iCpz;yƒD‹•I—¡T£°U©¶[­¹^®º`¨·`¨·`©¶f¢¯_˜¤^™S‹Lv€AxzC{~G„„G……H…ŠEˆH–G˜IšFšF›E›Eœ?œ?==Šœ@Šœ@‰›=‰›=‡<‡<‡<‡<ƒ›8ƒ›8ƒ›8„9‚ž7‚ž7„¢7†£8ˆ¢9‰£;Œ¦>Œ¦>Œ¨;Œ¨;Œ§<Œ§<Ž¤AŽ¤A¢B¢B¢B¢BŒ BŒ B‹¡E‹¡E‹¤>Œ¦@¤F¤F‘§D“©F•¤G–¥H—¤N˜¥O˜¢U˜¢U•U“œT‘—V•T’YV‰S†‰P†ŠN‚†J{GyEx|G~‚Mz‚V}†YwˆVwˆVs…No‚Kq€Eq€EƒL‘›Z«²m¶¾x¶¾q¶¾q´Àu½È}ÃɈÈÎÇË–ÄÈ“¥¤y86 29FL,TcBAEI:?0SXJ˜œ}Ÿ£„‚Š^t|P}ˆY}ˆY{ŒYyŠWrV_mDWWCffRc`\PMIH=HMBMFAKE?ICAHGDKLNIOPKZeFYdDYg7Sa1Mb&CW>NYi2jwCivBcqA\j:_fIu|`ZaFMT:BJ9AI8:K;IZJTgXaserƒkwˆqŽn~mz‹fyŠexŠcxŠcx‡`x‡`zˆfzˆfz‡gz‡g{Šey‡cz‡`u‚\rVn|SlzOlzOkyNkyNn}Qn}QnzVmyUhsTbmNY_EKQ77>+-3 #)# !$-"5%0E(:O3E^4Nh=[u=`zAe|?f}@g{Afy@gyDgyDfwDfwDdwDeyFcy=`w;_w1]t.]o%Yk"[k"[k"VdP_JUDP:K5F0?2@4C:I$CR-KZ6R`CZhK_nUfu\g|dj€hn‚op„qr‚{s„|qƒ~qƒ~q…{s†|vŠ~zŽ}ˆƒ•Ž‡™‡™‰™‹‰™‹‰™‰™ˆ–‰—Š—“Œ˜”Š™”Š™”ˆ˜“†•‘ˆ–†ƒ’‚ƒ“w|Œp|‰j|‰j|‡n|‡nv…lv…lr†lr†lu†ou†ov†sv†swˆvxŠwv„vuƒuq‡hmƒdnUcwJ]kUa/P]+Wd=_lE`m[hucg|fh}gl‚\k€[i~Th}SkSl€Tl€To‚Vr†Zq„XqŠVuYvWx’Z|•V~˜X—V€–T|SwˆOs|MluFaeKcgMdqdrr-@-@+< +< ';&:$6%7 #4"3 1 1//..--,,+ + ) ) ) (' ' $$$&& & & & ''& & ' + - -!1$4+7/;4>:DAIFN!QV%X],aa+ee.kh-mj0pn/pn/in+fj'_f#X_KWCNk‚Ct‡BzH‘F‚•Iˆ™K‰šLŠJ‹žK¡L¡LžJˆšE…•E€@x…>p|6bq6Wg+Q`4KZ.@Q,:K&}Œ>€B„’@†”B‡—@‡—@ˆ™?Š›B†@ˆžA†?ˆŸ@ˆœ@†š>ƒšBƒšB‚˜C‚˜Cƒ˜Gƒ˜G”H”H”H”H“I‘G€’H“Iƒ”Fƒ”F†˜H†˜H†›H†›HŠ LŠ L‰ŸJŠ KŠŸNŠŸNŠQ‰œPˆšPŠœSˆ™Rˆ™R‡—Rˆ˜T‹›R‹›RŒžNŒžNŒ K‹ŸJ‹ H¢JŽ¡H F¢I’¥K¤J¤J¢M‘¥O‘¢T¡S’¡Z’¡Z›[Œ™YŒ–^Š”\Ž’]ŠZŠ]Š]ˆZŠ[‡‘^ƒŽZ‹Q{†K‚ŒF”žX«´j¸Áv²»x²»xª´u®¸y²Â†¸ÇŒ½Ë¿Ì‘¿Í‹¼É‡¦²z?K+7@L(R]EblTr_‚o‘˜ƒ™ ‹–¢Ž• Œ’ž‡†Žš„™ƒˆ–{…“w’o}j{‹d{‹d{‰`w…\y‡\w†[{Š^|‹_‹b~Šau~\irQ]bQMSBMPILOHTZUbhc[_aNRTMNPCEGFJEPTOSWKOSGLPFDG>*0,',)ME>MB=KE@OEEGONPS\BS\BLZ*IV'BXH^Ia"_w8`r=]o:M[2LZ1[k=`pBmzSlyRDS3>M-@R?PbOZl`gxltƒqz‰w}Šo|‰ny‰gxˆfw‡ew‡ex…cy‡ez‡gz‡gz†iz†iz‡gx…ew…\sXpOm~Kk|Ik|IixMkyNn|Sn|SnyXlwUhrUakNY^HLQ<:@--3 #)#!! #*4%>#1L%:U/Hd2Qn<\v>a{Bg~Bg~Bfz>ey=ex>fy@ewBewBbvA_t>_t=]r:]s2Wm,Vj)Th'Pb!N_HVBP;J4C,;+9*81>8E%?L,HU5N[;SaEYfK`oVhw^h}gk€ko‚qpƒrr‚{s„|p‚}p‚}q…{s†|vŠ~zŽ~‘‰„—ˆšŽˆšŽŠ›Š›ˆ—Œ‡–‹ˆ–‰—Š—“Œ˜”Š™”Š™”ˆ˜“†•‘‰—‰„’„ƒ‘|~Œw}Šqz‡nz‡ny†mv…nv…nq„mr…nu„rv†sw†vw†vu†vt…uv†rsƒnp†elƒbkQcwI_m=R`0MT*@G0; 2="5I=I]PPljb~{t‹‰y‘„–ˆ„–ˆ‡—‚‡—‚‰–„‰–„‰•…‰•…‰”‡‰”‡‰”‡‡“†ˆ”€ƒŽz‚‹iv^kvB_j6Xd-[f0Yf@dqJgs\o{dj~dken\m€[h}Sg|RkSl€TkSo‚Vr†Zq„Xq†\uŠ`w‹d{g{f~“i€•d”c~^zŠZvZozShrUmwZp}k|‰w+=+=):):&:%9$6$6 1 1//..,,,,****(((&&&&&&$$ $ $ $ $ $ & & & % +((*, +"/ &2+5 +/84<;CEJKPYY#``*fc)li.nm.qp0jo,jo,fm*dk'Xc&Q\HSBM9F6B+:&6 20 ,* ())*++++) ) ) ) ) ) **+,,,****))('%%$"""       + +               !$"(%,+0-3"08"5<'6C*8E,8G)8G)7K%;N)?V'G^/Rh0Xm6]t5by:k~9pƒ=t‡;|C‚“Eƒ”FŠJŒŸL’£N–¨S”¥Q‘¢MŽžN‡—G„J€FsƒGk{?apE]k@Rc>M^9L]8N_:Xd9`lApz;wB†“CœMš¨QªTŸ¯X¤´]¨·`¨·`£´a£´a¢±cŸ­`£W‰œP‚“E‘CCƒ‘D…“A†”B„“<‡—@ˆ™?ˆ™?ƒ™=„š>„›<‚™:ƒ–:„—;€—?€—?•?•?{?{?zAzAxŠ?xŠ?xŠ@y‹AzŒB{D~B~B‘A‘A–C–C„šG„šG„šE†œF‡›J‡›J‡šN†˜L†˜N†˜Nƒ”Mƒ”M†–Q‡—R‰˜O‰˜O‰›J‰›Jˆ›F‰œGŠžG‹ H F FŽ¡HŽ¡HŠŸEŠŸEŒ K¡L‘¢T’£U“¢[“¢[“ `”¡a“e‘›c”˜c”˜c”–f”–f’˜c”›eŽ˜e‹–bŠ•Z‹–[¤®h±»uµ¾t²»q­·s±ºw²¼}¸Âƒ·Æ‹·Æ‹¶Ä‰´Á†µÂ€­ºx¢®uUa(&2AM)Q\CcmUs€`ƒq—‚—žˆ–¢Ž• Œ’ž‡†›…Œ˜‚‹˜}‡•z’o}j{‹d{‹dy†]zˆ_zˆ]{Š^}Œa€Žc€Œc‹b{„bt}[agV[aPQSLRUMZ`[]d^[_aSWXJLNHJLKPJHLGLPDQUIJMDDG>).+$!"'-(-3)+B)+B*#F*#F1.E74KEDI_]bnvggo`6A4KVIp{pcncR[HMVBP]JfsaU\]DJL12D/0B4/EIDZAAH77=986<:822-10,333444/26/26+34,55.549A@IJGKLJfad\VZ@5@C9DE>MF?ND?ND?NA@BIHJJS8PY>P].LZ*Ka Lb!Og(Ph)J\'4FGT+IW.O^0O^0^kD_lEN]=?N.GYFTeS]ocj|pw‡t{Šx}Šo|‰nw‡ew‡ew‡ew‡ex…czˆfz‡gz‡gz†iz†iz‡gx…ew…\sXo€Nl}Jk|Ik|IixMkyNn|Sn|So{YmxWhrUakNY^HIM89?,,2#)#"! "'-8*C)7R+@[4Jg5Tp>_y@b|Dg~Bg~Bh}@g{?ex>dw=dvAdvA_t>]q<]r:Xm6Vl+Qg&Pc"K^I[DU>L8F-<-<&5&5+91>8E%?L,HU5P]=VdI^lQcsYhw^i~hnƒmq„tr†ur‚{s„|p‚}p‚}q…{s†|vŠ~zŽ~‘‰„—ˆšŽˆšŽŠ›Š›ˆ—Œ‡–‹ˆ–‰—Š—“Œ˜”Š™”Š™”ˆ˜“†•‘‰—‰„’„ƒ‘|yŒs}Šqz‡ny†mv…nv…nq„mr…nv†sw‡tw†vw†vu†vt…uu…psƒnn…dk‚`j~PbvH`n>Vd5PW-CK 4@%7B'7K?K_SRnld€~uŒŠ{’„–ˆ„–ˆ‡—‚‡—‚‰–„‰–„‰•…‰•…‰”‡‰”‡‡“†‰”‡ˆ”€ƒŽz‚‹iv^kvB`k7[f0[f0^kDfsLjv`r~hl€fj~dm€[i}Wh}Sg|RkSl€Tl€TnUr†Zq„Xq†\uŠ`vŠczŽf~“i–k„™hƒ˜gƒ“c~^|†`w‚\t~ay‚e{ˆv„‘+>+>+<);%9#6#5"3 210/0/--**((((((&&$$% +% +% +$ $ $ % +% +" " # " !"##&&&&) ,"/ )62=;EJMTV&^a(ae+hk-jm/mq.nr0nq*lp)il'eh#ZaW^MVGPAK;F 2C.? !2 . - + +* ,,,,+ + ++ +****+---,,,,,,))('%%%$ # # ""        +   + +            +   "&"* $.(1"+7!0<&-=(-=(.?(.?(1A'6E,9K+?Q1I\+Pc2Vj5[o:bt8gy=i}:n‚?o‡AuG”H„™L‰šS Y”£U“¢T”¥Q‘¢MœH™Eˆ“CƒŽ?~†EzƒAoxBkt>is;is;nx7t~=yˆ;ƒ‘DP—¤W˜¨V™©Wš¬\›­]¡µ`¡µ` ´_ž±\—°Z“«UŒ¤N‚›E•?z:{‘<}”>{?|‘@|B{@yŽDzEyŽDyŽD{HxŒDtˆEtˆEp„CnAi~Bh}@hyHfvFavEavEcvC`tA`uB`uBar@duBgzBi|EkCl€Dq…Dr†EtˆEu‰FvŒDwEuŒHuŒHw‰MvˆLw‰Mw‰Mt‡Ot‡Ou‡RvˆSx‰RyŠS}O~ŽP“Q‚”R„˜N…™O‰žMŠŸN‰žK‰žKˆ L†žJ‹ŸJ‹ŸJ¥K¥K‘¥O“§R’£U’£U”¥Y”¥Y”¢`”¢`– f– f–¡k• j˜¢j—¡h–¡`¢­k²¾s­¹n¥¶o§¹q©º­¾„ª¼ª¼Ÿ¹Žš³‰œ¶~š´{¸d›µaž±\{Ž9,8@LQ]BcnSs~eƒŽu“™ˆ™ŸŽ• Ž”ž–Ÿ‹’›ˆš„™ƒŒ™€‡”{r{Œn~‹ez‡`wŠ^v‰]z‰[z‰[‰^‹a€Œc€Œc{cw}^kl\deUW]SUZPad]ad]bd_`b]OVPOVPOWMPYNQVGQVGMRD;@1*+-%') 0)':.*J-)I(%J%!F+,J:7Q@4V>3U;4P>6S>5P>5PG@SOH[@:F82>>6@>6@=6CIBNLFPJDNH?LH?LC?KB>JBD=IKDQ[>R\?Th.Ma'?T?TK^.A0@ @PN`0N`0'7 ,"/"1&4,9 3@'8F*?M1GV8P_AYfKbpTft]m{dk€ko„nr…vs†xsƒ~sƒ~qzs„|s†|s†|v‰|…€‰†–‡™‡™‰™‹‰™‹‰™‰™‰—Š˜‘Š—“Š—“Œ˜”Œ˜”Œ˜”‰–’…•Žƒ“Œ‘ƒ|‰y}†w|†tz…st„ot„osƒnt„ov†sv†sw†vw†vt†st†ssƒnr‚mn‚bj~]g~O_uFao?Xf6PX,FN!:C'=G*>PDRcWVqqf‚u‰{“ƒ–…ƒ–……•‚…•‚‡•…‡•…‡•…‡•…‡•…‡•…‡•……”„‡’~„{~ŒcrVny>dn4^g1_h2`mGivPo}`s‚dq‚dpcm‚Xi~Tj{Ok|Pn€Rn€Rl€TnUpƒYr…[q†^q†^p‡cui{“o€—tƒ›p‚™o„˜i€”f‘h{‹b{ˆhŽn‰”{š(<(<);'9$7$7"3"3 20/../--**((((''&&$$% +% +$ $ $ $ $ $ ! ! " " !###&&&&' ' , + . (3 1<BEHKSWWZ!ad&fh+in+lp-ps,ps,np,hk&ah%ah%Zc&U^"LWHR:K5F):$50.-,---,, , ++++--------,,***)((('# # ""         + +            + +   +   !%!)$.)3#)5.:#+;&+;&+<$+<$0@&5D+9K+>P/H[*Ob1Rg1Wk6[m1_q5cx5fz7h€:lƒ=v‹?{‘D‚“Lˆ™R‘ŸR•¤W•§R•§R˜¥Q–¢N‘œM—HŠ“R…L}†P{„Nx‚JwI|†E~‰G‡–IŽP—¤W˜¥X˜¨V›¬Yš¬\›­] ´_ ´_Ÿ³]›¯Z•®X’ªTˆ¡K—A{‘hz>h{Dh{DgyDfxCiyBk|EqCr‚EvˆExŠG{F“Iƒ˜G†šI‡œIˆJˆ L‹¢O¢M¢M£I£I¢M£N‘¢T’£U“¤X“¤X–£a—¤b™¤i™¤i–¡k˜¤mš¤lš¤l§±pªµsªµk¥±f¤µn«¼u«½ƒª»‚ ²„—©{›px’gw‘Y§o™³_“­Y™­XŒ K=HBNQ]BcnSs~eƒŽu–…—Œ• Ž”ž’›ˆ™…š„Ž˜‚Š–~†“zr{Œn€f}Šdyaya{Š]{Š]€Š_‰^€Œc€Œc€†gz€b_`PbcSX^TOUK[^WZ]Vac^ac^RXRNUOMUKV^T[`RKPBRWI;@1-/1&(*#!4+*<,'H,'H'$I%!F$%C-/L;;M78J%)/.39:CB6S:3O82J2,D1*D0)C2&H.#D,%A)!>/&A/&A71DOH[JCP@:F;3>:2=?9EE?KE?IGBLH?LF=J>:FA=IHJCTWP_iLWaDPc*GZ @UEYRe%Th'JY%?OBU$AT#9K$N`9BR6JZ>L^RVh\fulqv~ˆw}‡v|Šlx‡iv†cv†cv…ev…ew„bx…cy†fz‡gx…lx…lw†fu„dtZp}Wl{Ml{MizJizJiyKkzLn|So}TmyUkwSjoSchLU[CEK36<+)/"*"**,!/%2+;0?6I&=P-E\4Nd[lOe=Nc>Ka;E]7BZ4@W4?V3>R3;N09K/9K/7J.5H,7E(8G)AL*AL*DN(FQ+KV'P\,P^(Uc,Wf-Zh/[j/^n2cq2cq2_q3[n0Zo2Xl0Wk/Ym1Zj3\m6]k=Zg:Ue@C9//(==60,:0,:.)E+&B# C$!D$ E'$I))F43Q,.;')6268<@A7;5EICMNLMNLtuy_`dFCX20E-)G51P72N?:VA8R6-H>7M.'32QEEpekRGMI>KA6CB9HG=MC;LB9K?6J@8K94I:5J>;IGDRUTRSQOGQ9FP8DQ+>K%EW>OcR_rajzlr‚t}‰rz†pzŠjw†fv†cu…bv…ev…ey‡ey‡ex…ez‡g|‡f{†ezˆ_v„[vƒVrRo}MjxHjxHkyJmzLmzLmzSp}WnyXlwUeoP^hIPY>AJ/7@',4'2)3-7/93=7B#=I%AM)GT)N[0Oa3Xj<_s@eyFg|Ig|Ig{Hg{HfwDduBeuEarAaqC_nA^m?Xg:Ra5L[0ET)=L 5@/:%1+%%#(-#3%-:&5C.=K.ES6O_=WhEaoRdsUiy_o~ekloƒpr‚{s„|u„u„v†u…~t‡}v‰|…”Š†–ˆ˜‘Š›ˆšŽ‰™‰™‰—‰—‰—‰—Š—“Š—“Œ——Œ——Š––‰”•„“Ž‘Œ€Ž…|‹‚~‰||‡zx„uv‚ru…pu…psƒnu…pw‡tw‡tw†vw†vu†vt…us‚proo„ck^hPaxIcpC[h;W^4MT*AJ.DN1EWKXi]]zumŠ…w‘ˆ|•…˜‡…˜‡‡–„‡–„ˆ•ˆˆ•ˆ‡”‡…“†…”„…”„‡•…‡•…‡“}„y}‹bsXhw<^n2]k4dr;grQr}[u€gwis‚dq€bl}Xk|Wn|Zn|Zm|Wm|Wl~WnXn~[n~[l}]m~^lesˆlxw}’|‚–~ƒ—…™zƒ—x†—r†—r‡—u‹›xŽŸ&9&9%6%6$7$7"30.,--**++))((&&$$%%$ $ $ $ $ $ ##$$" " ! " ####$$%%%%%%&&#- )32>:FENKTT[Za"bg$ei&fk&in)nq*or+pt+os*mt(ls']l#VeCY:P*@!7 +2 2 / 0 0 10///00000000/...***)((('%%""!!            + +  +  !""&!)'/$)3#.7(/:&0;'3?)3?);H(@M-CN-GR0IW.M[2O^0O^0L_3K^2L_3L_3Nc2Sh7Tn6^w?j‚As‹J€˜K†žQŽ§Q”¬V—«T–ªR•¥U‘ P“¡O”¢PŽ KžJ‹œGŠ›F›I“¡O•¤W˜¦Y˜¦Y™¨Z˜©[˜©[™ª\›­_¯^ž°_ž°_ž°_–­\Ž¥T–EvŒ;k…3h0fy9cw6ar9[l3Wf8Sc5L_3I]1BY0…™O’¨`•«c—ªe°kŸµt¤ºy¤µ‚¡²€«€r’f|bzdzdq”Ua„EwœD‡¬T˜°M—¯L{‡HMZV^AgnRx}h‰x•‚”›ˆ”Ÿ‹’‰œ…‹™ƒ‹›‰™}†—y„•wˆ“q„n}matQRlF`zTVpEq‹`‡“cŒ—h”˜j•šk›š{ŽŽn}zonk`_aZXZSQWDOUB^fNX`H'&D7:G14A(,-OSUdhcY]XPQNHHFMMRRSXEBW:8M,(F0-K/*F3.K90J=4OB;P4-B)'.`^e‡vz‚qsvZX[?kfUrm\QEENCCMBII>DI>KH=JH?NKAPE=N@8J=4H<3F83H72G74B?CL17@'09 .9 0:"6@#:C'Yi;Sc5M\1HV+AO$5D-8&1* ' %%#(-#3%-:&5C.=K.ES6Pa>YiF^mOetVm|co~ekloƒps„|u…~u„u„v†u…~uˆ~w‹|…”Š†–ˆ˜‘Š›ˆšŽ‰™‰™‰—‰—‰—‰—‰–’‰–’Œ——Œ——‰”•ˆ“”ƒ’ŽŠ„|‹‚~‰||‡z{‡wx„uu…pu…pu…pv†rw‡tw‡tw†vw†vu†vt…utƒqq€mn‚bj~]f|MaxIdrD]k=Ya7OV,EO2HQ5GYMZl`^{vn‹‡y“Š|•„—†„—†‡–„‡–„ˆ•ˆˆ•ˆ‡”‡…“†‡•…‡•…‡•…‡•…‡“}ƒx|Šap~Uix=ap5bp9iw@nyXu€_yƒkyƒks‚dq€bnZl}Xn|Zn|Zm|Wm|Wl~WnXn~[n~[l}]l}]jbp…hsˆszz”}ƒ—…™z„˜y‹œw‹œw‹›x{Ÿž€)9(7(6&3#5#5"3.//.-+*))((&&$$$$" " " " #"##"#$$" " # # $$# # $$%%%%%%%%')"-)42>9ECNHSPZ V`&_i#ak%lq'os*pv(qw)qy&s{(m{/dr'Rf#G\5J(> +6 5 +2 +1 3 3 2223443345522222////--+*((&%%%##    + +         + + +  +" $" ! !   #"&#,'/#+6$/9(8>&•EŠŸN¦Q—­X–­U”ªR’¥R£P¢Q‘£S“¤O“¤O’¤M‘£L“¢L”£M—¦R˜§S–¨V–¨V˜ªZ˜ªZ–ª[—«\™­_œ¯aŸ¯dž®c—¬bŽ¢Y{”QnˆD`y%@A(DI(GK*KP&OT*RY*V\.Xa+Xa+Zd,Zd,Ze*Ze*Yd)Yd)Xe,T`'P\%OZ$JX&FU#AN'UhE[qEawJkQsˆY€[„“_‰˜_Žd‹aŠœ`‹›YŠšX‡•W‹™\œZ ]’¦\–«a“¥b–¨e”¨n°vž²†ž²†“§Ž¢}’žsœq‡˜ewˆVqƒS‘aƒ”jzŠap„N{Z„Q„Q•`bf1Xc;grJu€_…‘oˆ–{‚‘žŠŽœˆ‹œ…Š›„œ~‹™|™v—s•g‘˜i•›t}„\kSl€Tp…Kl‚H‘£e›®p£¬q ¨n–œv„_kn`psefj^Y]QUZEfkU`jMYcFW[O>B6>B;I.,:)$@*%A( D)"E*#F( D'!?+%D84F:6I;:DTS\[[`MMRCFQ;>I6RI@S>5P90J9/L2(E2(E6,I:6I>9LB>JB>J>=BB@ECDAEFDAG=GLBGQ94?&O_=O_=BW;UjM\m\dueq~qw…x~‡q|†p}ˆhz„eu„_tƒ^v‚`wƒaw‚cvbz„eyƒd{†ewƒav„[n|SfuHn~PyŠ^‚“g‚‹hu[ivTftRntcrwffo\enZcnSVaFPX7GP/CN&=H ?I%BK(GO*MU/PZ0S]2Vb2Yd5[g5am;ftBhwDj{Bk}Ci~Fg}Ei~Bh}@fxCewBdtDarA^lA[j?WeYc6R\/ER0IW5L^P_qce~|pŠˆ}“•†—†ƒ”„‡–„‡–„‡”‡‡”‡ˆ•ˆ‡”‡…–…†—†‰•…‡”„‡”{Œs}‰er~Zhw,=C+@F.DK/FN1IS4JU5JU5KV6LZ1LZ1O]2Q`4Ub7Xd9Xg;YhTm0G`#>Y);W'2J+*B##<#!9!31+!()* ''&&$' #,$- (, ,0$58(7:*@A(CE,GK*JN.OT*RW-T[,W]/Zc-Zc-Zd,Zd,[f+[f+Ze*Yd)Ua(T`'R^(NY#ET"@OauFo~JxˆSŽU„’Y€’V„–Z‘N|ŒJ€ŽPŠ˜[”£a—§d™­c™­cš¬i£µr¡µ{Ÿ²y–ª~’¥yŒ zŽ¢}” u” u‘¢pŒkz\k~MqXjzQi}HxŒWŠ–Wš§g­²}¨¬wx„[‚dv‚`‚l›Žœ~Œwuƒn}Žw‡˜€œ~œ~“y“yž¤v §x™ xzYkSzŽb•ZmƒI{O—©k¡©o£¬qŠkflG`bTgi[hl``dXRWAZ_JWaDXbEJNBJNBGKF@D?TXR_c^QXK-4'  +GHCB@N2/=*%A*%A)"E*#F.&I)"E("@0*I40C>9L^]gMLUFFK+,0-0;&)4"(>(.D26S04Q0/O0/O55I>>SNOZUVa79@:=C333^^^V`J]gQbnLWb@;?':>&GD@@=9LDLZRZUMWNFPE:LB8J@8KC:M;2L4+E5+H2(E.$A4*G72E>9LB>JB>JCAFEDIEFDHHFGLBIOEQ\CT^FO_=Pa>I^BLaDXiYevfrrv„v|†p{…o|‡gyƒdu„_s‚]v‚`t^z„eyƒds}^s}^v‚`o{YdqICQ(EU'Wf8m~S‡˜l¡«‡ƒi[hFQ_=X^MMSB>F3BK7U`EXdIV_>MV5GS*CN&EO+GQ-OW2U]7Wa7Zd9[f7^j:dp?htBkzHn|Jo€GnEk€Hi~Fh}@g{?fxCewBbsC`p@_nB]k@Xf=O\4GS/@L(4>)4*'  $!,!'1',=&5F/AO4IW[e8T_2JX6N\9ObS`rdh‚€tŽŒ}“•ˆ™‰ˆ™‰‡–„‡–„‡”‡‡”‡ˆ•ˆ‡”‡…–…†—†‰•…‡”„‡”{Œs|ˆds[k{?hw7 5 6664555666664555555210/--,,++('''$$!     +  + +    +  +  ! '%0. +64?CAEAEAE5?2< +'1 "-& % +!#!!! &&,,1!186<"D]05M .C'*?"#6'!3%,')$'&'&%(#&#&$'!$!$ $ $$!$!%()-#03#58(<>%@A(DI(HL,KO(MR*QW+TZ.T])Ya.]d/]d/`g/`g/`f1`f1]d/]d/Zc-V_)V[/OT(HQ)BK#bsEp~GsK|ŠM{‰L‹–Tš¥c«f›©eŸªm ¬ož­t¤³z¤µ‰¢³‡›ªŠƒ’rhxU’¢i›¨t›ªoš©n›ªq™§n›žt’iª¯šÀÅ°ÄÍÀÅÎÁÆÌÇÆÌÇÆÏÂÃË¿¼Â¨§­“”£||Œepˆby‘k|—wupd~Zh‚^~“i„™o‹šn{Š^Rb;z‰c…•giyK{Š]žp€•dh}LZoEo„Zu„dsƒcdo]T^M`f`dkeglediadfZacW__XNNHOLSPMU@BK9;DIHJ_^a\XV#82750?3.<0)C-'A.)E3.K94P/*F+'A*&@,(B:6P74I31F9:L01C*)@)(?$*;#(:=@T/2F03G35I7:E=@KHNVELT<Z]FhgWRQARNHYUO51*@;5ni\zuhlh`VRIF>HB:E>4J;1G80M80M4,K.&E+#B0'F3.L71P>6S?7TB:RD;PDAVDEN9;DP\H[fRXf=P^5LW/LW/MW3S]9Ya;[c=]gO[7EP1n}ItƒNy‚c~†g}…o{‚mwƒer~aq\q\lyYlyYiyWiyWk|Wl}Xl~Wi{SgtUdqQ`pTeuYctjj{qo„|v‹ƒ{’ƒ{’ƒ…›ƒˆž†Ž „¡…ŽŸƒ‚"5 4 2 21/,+)*)())&&&&$$"""""""""""""#$$# # $$$$$$%%%%%%%%''(())+ - - / '7 /?=ECKRWY^"gj#or+x.~…4~Œ:}‹9k:bx0He*9V$A9 666655 6 6888876666666420/..--++('''$$!    +  + +      $!,)0.;:CBLPPUPUPUFOAK7B/:#.( #$$%"'$* +0-3"6<";A'@I*EM.IQ-MT1MT1PX4S[5T\6U\9U\9Y^;Y^;^f:ck>mu;px>s€>t?u„=u„=u„=w†?z‰@G†˜J‹œN¤Q•ªW–¬V–¬V“¦S‘¤Q¤M‘¥N“¨P”©Q–©T–©T”¨W’§V¥R¥R£S¡P¤Q¤Q‘¤X•¨\˜ªaœ®dŸ±h›­c’¦cŒ ]x‘Tc|?C[.4L)>!#8!3%/ .),'&%&%#&#&#&#&!$!$ $ $!&#!&#(+",/%26%7:*@A(BD*GK*IM-PT-SW0SY-TZ.Ya.Zb/]d/_e0_f.`g/`f1`f1]d/\c-Xa+Xa+TZ.PV)GP(BK#9@&18)1$,%$$$##%%(+%4,;"8I$AR-M_0Ug8cq:ft>{‰L˜¦i£®l§±pŸ­i¡¯k¢®q§³u©¸ª¹€œ­x‰]euUjyYhxUŽž|™¦q›¨tš©n›ªo›ªq›ªq²µŒËͤÉ͸Æ˶ÃË¿ÅÎÁÅËÆÅËÆÄÍÀÃË¿ÉϵÀÇ­™©‚ˆ—q{”m™ssnhƒcc}Y[uQ`uKbwMixM[j?Wg@[jCv†Xw‡Yz‰[|Œ^ezJLa0K`6lV‘qixXLVER\Jdkehnifkdimfmnc_`U[[TQQJKIPOLSDEN?AIMMOvuxyus731-'-*$)(#22-;1*D1*D,'D3.K@;W<7S*&@%!;)%>2.H:8M86J?@R78J,+B-,C%+<'->;=Q+-A03G35I7:EQG@OJBQMJMSPSU\MU\MK[HQ`N[k]fvhm|lu„tz‡nuiw„dr_q€Yt„]v…ZlzOR^3uVnzOmyN_pFWg>Xi?euLkwZbnPZ]V;=6:9BFENDDXEEZ?=Q86J=>GUW`gr^^jV]jBVc;R]5Q\4T^:Xa>\d>^gAblAeoEhsDkwGm|Hp€KrƒLrƒLpHnEg}EezBhzEfxCfwDevCbqFbqF]jCWd=S`;KW3CN.:D%/8%.#""""#&+*5!.9%3D$’£C§H–¬N•°L’­I‘§K¦I¦I‘§K’«N•­P“ªW“ªW¥_Œ¡\Š]‰œ[…›Zƒ™Xƒ™X„šY‰ [Œ¤^–¦b›«f›¬l›¬l–§i“£e„–Zp‚F[j8N]+IQ-@G$5:$05 +. -/!%,&"(## # %$%$"#"#!#"$')$')$.0"03%;=$@A(AI$FN(IQ%KT'OY'PZ)V],V],Ya/Zb1]e1]e1^h/_i1^h/^h/[f0Xd-Va+U`*NY,IT'CL*URQahgwfdiCAF4.43-2B>J<8D3/B1-?,'<:5J73D-*:*$<+%=" :75OPMdIF]73D+*:/.>//<>>KE@S84F68G79H58C=@KML\98H88F++9BHEsyvorjile)''<9:977;89w|g|kqxWioNabQPQALEKB5R@8N>7MA9OC+cgU\]a??D@>S@>S?>W?>W;;O88MCFEVXXip]ovckvPhsLbnC[g<[a9X_7[`;\b<_gDbiF`mGfsLixFkzHp€KqLr„Jq‚ImEkCh|Bfy@fyAfyAeuEdtDapE`oC]jCVc’£C‘¨I“ªK‘¬H‘¬H¥H¥H¦I‘§K©L’«N’©V’©VŒ¡\ŠŸY…™X‚•T{‘PzOzO{‘P˜R…W”¤_™ªe›¬lš«k—¨j•¥hŠœ`|ŽRm{Ibq?\c@V^:LQA225')/*"(#$!&"$"$""#"#!#!%&&(#,-)03%25'=?&BD*EM'FN(MU(NV)OY'PZ)V],V],Ya/Zb1]e1]e1_i1_i1^h/^h/Ye/Ye/Va+R^(NY,GR%?H'8A -5$'/"&$! ! ! " ! # $!#( &+#,1!38(U<;REDTA@QKJNSRW9382,1>:F=9E72E1-?)$994IGDTIFV-(?,&>'%>-+D>;RYUmA>NC@QFEUDCSLLYEER95H:6I45EFHX?BM=@KKJZ65E::H//7755523523rpp|ky~it{ZfmLhiXWXHE>DB6G:1K7.I2+G.'D-$F0'H2)J5,N92N:3O>5R@6SA9OB;PCS@>S;9R64NGG\\\qgjiikkms`ms`nxRkvPfrG`lA^e=\b;_e@`fAcjGcjGboIerKm{In|Jn}Ip€KpHpHl€Di~Bh|Bfy@fyAfyAdtDbsC`oC_nBZgATa;P]6GT-=H(4>)2 )!"' . &4&0; 8C(;J%CR-M\5TcG;A8>Ž¤C¨B’«E­D«C©@©@¨I¦G‡£Oˆ¤P…žZ‚›X€–c|‘^p‰`l…]j…^g[_ySb}Vq…Wy_Œœg’¡m™«o™«o™ªh–¨e”¥^‰šSI{†Bv€Aq{B/8(1"*(&%$'%(*-03%:;$=>'@D#DI(HO'IP(KT%NW(PZ)Q[*V],W^-Ya/Zb1]d3^e4_g4_g4^e4[c2W`1V_0S]2PZ0IR0DM,8B*1;#)2%#, & $"# $$# $"'#(),03%8:&;>)DI&JO,QW+W]0xƒI«µ{¦¶{¨·|§·‡¬¼Œ”£ƒn}]N_C_oSfpSub„Œmy‚c•ž›£„’ pŽœlŠ¢gr‹Pr„]j|U¢ª˜ÅÍ»ÆÌÈÆÌÈÈÇк¹Â¸´ÇÆÂÔÆÌÈÇÍÊÃÉʽÃņ”¦dr…£´«¹ÊÁµÅ¢„”q}d ³‡ž¨‰…qEQB-9)9EAY@@V>=T84FB>QGDR:8F50F50F7/L2+G*$C60N<:S64N(%C)&D&"@(%C'(A-.G"(7?ETEQV=IN+8;8EG1:@4>C5>8T]WNXP?IAEHS36A97J,+=ijsvw€EFH#$&23788=mufpwhu…^w†_vYdpGjmV^bJED@BA8B94I50F,(B)%>*$C,'E/)G1+J50J73L;5O<6P?:O?:OA>NHEUPMVPMVNRMQUPEMJMUR`pijzsqƒpn€mu„do~^?L*Ta?o€[k|WarMRc>^iGdoMbkSpy`ppi]^WQJWGAM?=PDBTDENACLGLBbg]ktWisVkxOkxOl{Mm|Oo{PkxMjtIeoEbh@^e=_e@_e@_gDahE`mG`mGetFgwIexGgzJl}Ji{Hi|Eh{Dh{DfyAevCduBbsCarA`oC]k@Yf@S`:LY4DP,;E&0;&. (" !&!*%2.:#5@ :D%@M>-Q^7ZgA[iLbpSgxal}fm~nq‚qqzr‚{oƒ€oƒ€m‚l€m„~o…sŠ„w‡‘Œ†˜“Šš’ˆ˜‘‡—‡—ˆ–ˆ–ˆ•‘ˆ•‘…““…““…““…““ƒƒ|ŠzŠˆu†ƒsƒrƒzpwn€rk~on~rpsr€wr€wr€yq~xpsn~rmzojvkcu`^q\[rNXpL\sIYqF\pBYm>Zi>Xg;TcE]lNaqjn~wuˆ…}‘Ž…•Ž„”…•‡…•‡‰”‡‰”‡‡”‡‡”‡‡”‡‡”‡…•‡…•‡‰–„‡”‚†˜q‘j|Tu‰Mw‰Fy‹H|‹WZ~‹f€Œh|‰jx…eu‚cr_r€^q\m{Ym{YlzXlzXm|Wl{Vj{VgxSetObpLZhS\jV]m_csed{lh€qmˆvt}‚™ƒ†žˆŽŸ‡‹œ…‹š‰˜..++*(&&!"""!!!             " " # +# +# # $$$$&&%%&&(())))))*+,, 0"2 2 2'1(28;?BRSab"ov#z‚/‚5…‘8uŽG&4?&-8$- (%$# # ### $$* '-#03%25':=(@C.GL)JO,RX,di=¤®t«µ{¦¶{¬¼€©ºŠ«»‹œ«‹†•uv†k}qŒ–y’œ”œ}{ƒdnvW_gH|ŠZw…UxVZr8btLy‹d©±ŸÂʹÆÌÈÅÊÇÃÃÌ£¢«™¬³¯Â¾ÃÀÅÊÇÃÉÊÃÉÊ£±Ãz‰›Œ“®¿µ©¹—†–tyaš­Ž˜ygrRCO?IUEAMDeqhyslsfP]EVcJYlPcuYdrbcqa\dbbih~€‡^agQW`KRZGKZ=AQ,/H&)B#(B).H34T88XEFcBDa<@X:=V>=T>=TMI[E@SB@N;9G72G83H4-I3,H/)G71P75O0.H.+I,(F*'E,(F01J()B'.=:@O9EJ6BG,9<+8;3=B4>C=F@LUOMWODNE=@K58C64F1/BwxMOW$&(%')459]^bYaRbiZt„]t„]u€XkwN^bJY\EMLH@@;@;E@;E94I83H1-G.)C+%D*$C.(F0*I1-G62K:4N>7Q?:OB=SC@QGDTROYYV`UYTRVQNVS[c``pijzspomlq€`l|\0=IW5o€[]nI1BSd?nyXiuSv~fw€gJJC==6PIUKDQ?=P97JGIR[]eekadj`eoRhrUhvMkxOm|On~Pp|QnzOisHdnCbh@^e=^d>^d>\c@^fBXe>Xe>Zj<^m?_rAbtDfwDhyGh{Dh{DgzBew@evCduBbsC`p@_nB]k@Xe>R_9JV2BN*9C$.9%-'! !%($.*6 /;$6A!9C$@M&HU.Q^7Yf@]lNetVgxal}fm~nq‚qqzr‚{oƒ€oƒ€m‚l€m„~o…r‰ƒvŒ†’…—’Šš’‡—‡—‡—ˆ–ˆ–ˆ•‘ˆ•‘…““…““…““…““„‘‘„‘‘x‰‡v‡„u†ƒsƒq‚xo€vlqk~on~rpsr€wr€wq~xp}wn~rm}qjvkfshas^\nYZqMWoKZrGXpE\pBYm>]k@Zi>WfHbpScslp€yx‹ˆ}‘Ž…•Ž„”…•‡…•‡‰”‡‰”‡‡”‡‡”‡‡”‡‡”‡…•‡…•‡‰–„‡”‚†˜q‘j}’Uv‹NxŠGy‹H~ŽYZ€Œh~‹fz‡gx…es€`r_q\p}[m{Ym{YlzXlzXm|Wl{VizUevRcqMbpLZhS\jV]m_cse_vggomˆvt}‚™ƒ†žˆŽŸ‡‹œ…Š™€‡—~)'**&%$$##""""  !!!!"""$ % +% +& & %%%&&&''''++**++--// 1#4 1/)3*46<=CRRbb%rt$~€0†‘4Œ—:v5j„)Mp)7Z#D: +88 :"< : :"<"<";";";";":":":":":":":":999988764431 , /-, ) ( ('# " ! +     $& ,/:;FGSY^d*oq.xz8‚Š;Š’C•KŒ“Iˆ‘GŠ@z…=s~6pu0jp*hh+de'aa$``"VY VY X\"]`'^_&^_&\^#_a%^d*af-ci/hm4gq2gq2hs1hs1hy+l}/o„,r†.z(~“+|—%~™'{™%z˜#|•(|•(~”1€–3„™8ˆž=ŠCŽ¡H¥H¥H‹¢J‰ H›N€šLv“QsMl†TgO_xTYrNNjSIeO?[K7SC3QA6UDH^FVlTnT{Œaœc–¥l™¨h˜§g—«\’¦XˆJƒ™Eˆ™G‡˜E‹•Jˆ‘G‰‹M„‡I}}HwwBkoP@EX?CW49M;@T69K58JIHZ@>Q;7J:6I?8RA:UCJO7CO*6B.NeVI`Q:VA9T@,9<0=?/4A;@M`aq!#2'+3,/8@GHPVWU`NakZt~_u€`q{WjtPbbMVVANHEIC@>;E:6@96D96D62J1.E/*F/*F1,H3.K83H;6L>7MB;PCAHIFNLILNKNRRKYYRXbLR\FL^PSeWcqhl{qs~qq}or_r_r}Ww‚\wYenHMW3cmIprfxznnjoFBG4/E>9NB=SA=DPPWir\fpZivIhuHgvJixMmzLkyKozRozRpyQluMlrLekEbfA`c>^_?^_?X\Xf=R`7HT0@L(6A!+5"+$ #&.#3+:/= 7DBV9>QBG[CFX36H>XF?YJC_TLi94P/*F/,H75P0)J*#D''I&&H77WMMmZ`o;BQ8DI>JO-9D-9D,9@1?FMdUYEA]H,9<2?B,1=JO\./?/1@59A9=FDJLMTU@K9DN=p{\u€`q{WgqMbbMXXDNHEIC@;8A95?96D96D30G0-D/*F1,H3.K50L94I<7MG?UE>THELQOVVSVTQTXXQXXQU_HWaKQcUZl^hvmn}ttrp{ns€`mz[p{Ur}Ws|VqySoyUt}Z|}rfg\EAF<8=AR+DX0Nd8Uk?ZqB_uFeyDf{EgyDewBdtDbsCaqC_nA^lAYht†=x…=vƒ:w;t~8x|OE[gIn{]‡“b” n˜¨e—§d¥[‡›R}—Cw‘=€”C‚—FˆšH‹žKœMœMŽ™PŒ–NŠ’S†OˆLzEot;af-T[%LSIQHPHN HN KP&KP&KQ%KQ%MS'PV)PV)PV)QZ+T].V_0W`1U`3R\/MY0JU-IR/EO+DK/BI,'5;CNUU`hPV^U[cmq€OSb67P=>WDAVDAV99O54KFIa.2J(.D+2G,-D34K.0M#$B#F!%I.3TDIj?JZ(2C )11:A07C29F/:A%18);6;LGJ[X;LI-3=4:D02A,-=),914A?DCBGGLTIIRG?O5N^EdsUiwZn{]frUZZLMM?KEHE?B:6B73?84F84F2/F/+C,'D0+G50J50J95H<8KMKRPMUMTAY`LalEalE`lA_k@YhDXgBSgT\p]gxnl}tsoq}no}`kz\r€Uo~SoyHGQWX8‚b€uuH==F7JO?RJBQ@9HEEEeeehqWdmRcoFfqIdqFdqFetFgwIjwJkyKizJizJmzLfsEak@\f;W[;VZ9OT8MR6@I,;E(3B$7E(2I&>U1G`7Oh@YrC^wHcyFcyFfxCewBdtD`p@aqC`pB^lAYhK&ER-LY4Uc@_mKeqTmy\o}hs€lrvrvo€}o€}m€m€l|n‚~n‚}s‡vŠ…{Š•ƒ—’‡–’†•‘ˆ–‡•Ž…‘…‘†‘‘†‘‘…’”…’”†“•†“•…““„‘‘}Ž’z‹w‡‡t„ƒq‚€p~n€yl~wi}qjrm|sn}tn}tn}to{snzqnxojtleqa`l\ZiPUeLRfFRfFXpEYqFZpCYoBXmC\qG`tUh|]luv‰’Š‚”‰–‹ˆ•‰‡”‡‡”‡…•‡…•‡…•‡…•‡†–ˆ†–ˆ…–„ƒ•‚†–{ƒ“wƒ—d’_{‘Iu‹Cw‹HyŽK~‹^€`~‰j}ˆhw„ds€`r€^p}[p}[n|ZkzUkzUl{Vl{Vn|Zm{YivTftRcnS^iOYfT\iWYmZ_s_a}ffƒlqŠvy’~†—…Œ‹ƒŠ™€—Š•|)'$$"!!!" " " # +# +$ %%&&''&&'')),,,,,,,,- - .0!3!3(3 )46==DNO[\mpy{)+†–2v”4kŠ*Tx.@d)MB!=!="< : : :";";";";";"; < < < < < < < <;;;;:999778722/., + ) ' # " # "   +      %( -0<=CDNVZa*bn*jv2m{.kz-gy0fx/hu,jw/pz4s}7x|9=798987597.>57H>T`CiuW‚]k–¥c•¤b¡W–LtŽ:g-o„3v‹:~‘>„—DŒ™IšJ›S›S‘™ZŽ–WŠ‘UˆS€†Lw}Cms>ah2V^*PX%LS$JQ"JO%JO%KQ%KQ%OT(OT(PV)PV)PY*R[,T].R[,MX+HS&FR)DO'@J&@J&>E)=D(7?)29#.4#,1!(+#(+#'*"'*"+.$-0'/3'26*58(7:*=?*?A-DC*FF-MM+OP-QS*UX.UY-koC¬¯Š®±Œ¢«‘§°–˜§žŒš‘m„‘lƒk‚žDZv3DcK\|v˜u€—y†”uƒ‘s†Œqƒ‰–¦¬‘¡¨HPf.&js‰ÆÆËÅÅʲ¸Ð¡¦¿n|žCPsmv’’›·•—¹£QR}CDo_dxˆ¡‹•‹v€vct[gy_“¡”¤±¤Z`o17FLOnORrTWvor‘‹¯‹¯nn‰ddWZtUWrPNn^\|VTtIGgPOmKJh,+I'&D*/I05O8=WFKeHPa08I!/6,9@.9@T_gPV^JPYW[jLP`<=V67P74I31F*)@?>UEH`!:"(>)/E:;R;B D',M05V5?P3=M2;B.7?5=I-4A1AQO&74/5?+0;./?*,<;=JSUcOTSSXWU]S2:07F-CR9BQ3VeGXdGYfHZZLMM?:58:5873?94A84F84F62J30G0+G1,H50J50J95H<8KOLSTRYRYE[bObmGdoIbnCamB\jF]lGYmZ_s_hyol}tq}nnzjo}`guW]k@Xg;x‚Pq{IlmMdeEYNMQEEC4FM>QYR`c\kssslllfoTbkQcoFbnEamBbnCbrDfuHjwJkyKizJizJhuHboB]g$= $= < < !>!>"? = = =<<<<====!=!=!=!=::999965 442 2 -, * ) & +% # "      ( '05:?D!IN$RW-X`-_g4\k2Wf-Ud"Rb Yg#`n*ls.ry4v7y9z:w|7ux8ux8ty4v{6x}1x}1{‚6{‚6{„3}†5}ˆ9€‹;~Œ:~Œ:‹3~Š1zŒ.{/},€‘/„”/„”/†œ+‡,€›#~™ €›œ~žƒ '„¡(€œ5|˜2z’>wŽ;j†Ad€%LDC:<;;9;<;<:;797:7:3669,<75D?QYAemU€‰Z™j–¡d’ža…œYwŽK`~9Mk'Tk,\s4i}K%Ea =)-J8();./A-2F#(; ;: !A @$'@25N68G,-=31?42@67@56?GNO18939:8?@34?01<66C??MJIS?>GMRQafeEPEEPENYG3>,+#-GQ9GQ9EO7LNB9;/213658<6I>8KA9Q:2J4.F3-E4.F50G82J82J:6I?;MKIPTRYV]G[cMdqIesJarAarA^oL`pM]o\cubkypp~uu€lq|ht~Ts}Sfr:_k3nvLkrHIC@GA>RITLDNIEJVRXbf\svlmwZgqTdqFbnC`pB_nA`pB`pBbqFbqFgvJgvJjsKirJcoF]i@^a6E%9H!?N'GV/P`9[fGfqQluZr{`qktmqvs‚xo€}o€}n~~n~~n|n|p~sƒzŠ‰ƒ““„””ƒ•Ž‚”…–Œ†—ƒ‘ƒ‘…““…““„“•„“•ƒ‘–ƒ‘–„‘“ƒ’~‹z‡Št‚‡q€„p€€oj~wg|tj}nk~ol{qm|so{so{sn{pmzolvkfpean\ZgUOcIJ_ELeCKdBOl@RnCZqBZqB[qE`vIez^nƒgs„{†„•‹…–Œ…•‡ƒ”†‡•…‡•…ƒ•‡ƒ•‡…“Š…“Šƒ“‡ƒ“‡ƒ”„ƒ”„„•w‘s‚”_zŒWvŠGtˆEw†K{‹P|Ša}‹b}†l|…kyƒdu€`p~Zm|Wm|WkzUk{Rk{RkzSl{Ui{ShzRgwTdtRdnVcmU\iW^jX[n_ase^ygl‡uy’~š†ŠˆŠˆ›‡Œ—ƒ‹—€Š–'&""   + +# +# +" " $ & %&&&(())))))**++**,,,,.../#1 #1 )8 0?AINVdiqv*z…(ƒ1z3vŒ0b€,Rq@`4T)G#A +&?$= < < <<<< = =;;;;<<<< < < < <:::99985 442 . +, + ) ' & +% # "    +   $ +%.27;@CHLQ'S\(V^*R`'M\#IYHWQ_We dk&ls.qy1s{3v{6uz5vz9vz9uz5w|7z€4z€4|ƒ7}…8‰7Š9‰:‰:~Œ:~Œ:Œ4Œ4}1“5ƒ”2†—4Œœ7Œœ7Š /‰Ÿ.„Ÿ&€›#‚ƒž‚¡‚¡Ÿ&$z–/x“-rŠ6o†3a|8Zu1Ei:;`1,P6$H.>6<3:8;98:798:6859485869,<73C>LT<`hPˆY˜i–¡d’ža„›XuŒHZx3CaAYG^Ui(au4r=z‡E‡“F™LM’¡Q“¢T”£U”žX‘›VŽ–U†ŽM~ŠMv‚Eis;_i1X],SX'NS$NS$OS'OS'OT*OT*MS+MS+EN&BK#;H!9F7D9E!8D'9E(M]*9I'0&/!04'5:+/ 158DT2>O6;S/4L-.G)*C68NEG](*@#$;();)*<)-A49M'*E:==$'@-0IBCS8:J2/=42@)*3DENNUVELMNUV>EF+,766B77DGGU76??>GMRQLQPCMBFQFDN=)3"5>+%.#-@J2BM4R]E9;/./$002547;4H>8KB:R>6N71H4.F4.F71H71H93K>9L?;MPMUXV]T\FV]G`nEftK`p@bsCZjGaqNbsahyfr€wm|sozfdp\^h>isHkw>`m4`h>RZ0F@X[6OR-BF&:>+4!*), 3+=!6K(@U1Kb:SjB[pF^sIcuEbtDbsCbsC`p@^n>_jB_jB\gAYc=O[7IU1AL*6B +7".&$ !',&5)9.=2B!9H!?N'GV/Rb;^hIgrRnw]u~dqktmqvs‚xo€}o€}n~~n~~n|n|q‚€u†ƒzŠ‰ƒ““„””ƒ•Ž‚”„•‹„•‹ƒ‘ƒ‘…““…““ƒ’”ƒ’”ƒ‘–ƒ‘–„‘“€|ˆ‹x…‡s…pƒp€€m}|j~wg|ti{mj}nm|sn}to{so{smzolynjujeod^jXXeSMaGI]CF_=JcAOl@RnCYpAZqB^sGezNf{_p…hx‰’‰…–Œ…–Œ†–ˆƒ”†‡•…‡•…ƒ•‡ƒ•‡…“Š…“Šƒ“‡ƒ“‡ƒ”„ƒ”„„•w‘s€‘\vˆSr‡Dq†Cv…Jy‰My†]{‰`}†lzƒhw‚ct~_p~Zm|Wm|WkzUk{Rk{RkzSkzShzRewPdtRdtRdnVcmU\iW\iWZl^`rd^ygl‡uy’~š†ŠˆŠˆ˜„‹–‚Š–ˆ”~"      + +" " # $"""#%%''))*******+,,--,,--..00%4+:;BFNY^ fk-t|-z‚3yŒ3yŒ3m‚/ez'PmEb5S-K'C %A#< #< "<9 :;;;;;;;; ; ; ; ;;: < ; ;:886886 6 3 1 - - +)''$ +$ +   + +  + $"*,149;ABH#EO$HR'HT$EP!FODNJSQZ$[c)ai/do.gq0hu,ky0jv2mx4mz8o|:t}6:;797=6<5<5<5<3:38382839&:5.B=HR<\eOyTŠ“f”žf’œd†™[yŒN[t>@Z#'A-F7J'BT1Rc9^nEl~IyŠU‡”Yš_“¢[’¡Z•£X”¢W’ŸVšRŠ”S„MŠHt~=fp/[f$T\$QX MU$OV%IS)KT*GP1@I*;D)6?%4;2:296<"=A'?C)AC(AC(CD&CD&CE%CE%FE&FE&ED%FE&FF$FF$JJ(MM+NO+OP,TR.UT/UX.SV,SY-TZ.\]2\]2w{V³¶‘¤¯ž©—‘¡¡‹››s„1BM3AU[h}{Œ«m~T_‰4?h5>f\fdp‘coapŽYh†j|žUg‰GDAG££¼¯°É©ª¼ª«½¤¤½…†Ÿzv›qm’^\…SPyIDeNJkLST^debn`ZeXbmmallZbsAIZ%H$-P:Cmqz¤¢È—œÂŒ®‘”³ªªŽ³ji65`'&QJOw^c‹MXtEPlCH`W67P73L73L>?QFGY68F"%2/7<4;A%02&14$.:",8)0C3;N!#9 "823J<>T<>T.0F""732G-/E.0F%'; 4!:6#$;')?66J55I32;109HJJ`cbY]]chgWYb./80.A/-?53EDBTGHS>?J7;DCGO;AB@GHEHE03035.>A:47.;>49=,8<+00)((!-*264;53E32D30G51H51H30G30G51H71H?9Q@`x=Tf6Yl88=FFK7=8>D?]hVq|jjwWcpPcnH`jD_nBapE]qE\pCZsF\tGduIduIfqIbnE^h>Wa7PV0DJ%7< ,1'$$+#3-=(8N/AW8Lb=UjE\rF_uHetFbrDapEapE`oC_nB_jB]i@Ze>U`:P\8JV2@K)5@'3+$# #',%4(7,<0?8F"@N*JY4Tb>YkJbtTiy^p€ep~is€lp~ur€wn|o€}n~~m}|m~{n|p~sƒ{‹Š~ŽŽ‚’‘ƒ““ƒ”‘‚“ƒ’ƒ’„’ƒ‘ƒ„‘‘‚‘“‚‘“ƒ‘–ƒ‘–’{ŽvˆŠs…†s…pƒom}|g|tezrhzli{ml{pl{pn{pmzolynjvkhshbmb\iWUbPL`FFZ@H]AJ_COgASkEVo?YrC^vIgSjbq†iz‹‚…–Œ‰˜ˆ—ˆ•‰ˆ•‰ˆ•ˆˆ•ˆ†•‰†•‰ƒ“‡ƒ“‡…–……–…ƒ•‚‚”„—t’o~[t†Qq„Fq„FqOv„Tt€ew‚gw…iu‚gq€`n}]n}Xn}Xl{VkzUk{Rk{RjyRjyRi{SgxQevSevSepVepV^kW_lXZn[`taa{imˆvz“š†‡œ‡†›…Œ™€‹˜ˆ”~ˆ”~     + +" " # $"""#%%''))*******+,,--,,--..//#2 %47?AHRW`e'mu&s{,t†-yŒ3sˆ5l.[x(Nk?]8V-I)F (B&@"<"<::::;;;;; ; ; ; ;;: < ; ; ;:868876 3 1 . +. +- +''% % !    +  +  &).,15;;A;E?I>I>IAKFOJSPY#X`&\e*_i(]h'an%bo'bn*co+dq/fs1jr1mv5uz5w|7z€6|ƒ9}‡;‰<„9ƒŽ8‚’4ƒ“5…˜6†š7ˆš5‹9‹Ÿ5‹Ÿ5‰¡2‰¡2„Ÿ-… .Ÿ*}›'v—"s”m id€,`}(Sq4Ki+6Z2+P(C2?.;3;379797=5;5<4;4;2827161728$82+?9EN8XbLu~Qˆd“e”žf‹ž_’Tb{EGa*#<5 %7.@?O&GW.Zl7hzE{ˆM„’VW’¡Z“¡V”¢W”¡Y’ŸVŽ™XŠ”S†‘O€‹Jy„Cq|:ah1X_'QX'OV%IS)HR'CK,$;A'AE,BG-FH,FH,JK-JK-KM-KM-ML-ML-ML-ML-MM+MM+OP-QQ/RS.ST/WV2XW3VY0VY0TZ.V[/^_4]^3~‚]³¶‘¡«š¨–Œœœ‚’‘[lw/:0=RN\pt…¤s„£bm—=Hr4=d@Jq]iŠdp‘et‘brl BUv#L IF?stŒ¤¤½ ¡³£¤¶¤¤½“”­„¦wt™b_ˆWU~MIjB=^4:;0786A4S^Q^ij\ghT\m7?P!)L+4W7@iktž–›Á•šÀ’•´“–µ¡¡½žž¹Ž³VUz/.Y<;fmršns›do‹ZeU[sQVnEF_;GIBDG>GJ@9=,7:*99344-30864;1/B,+=.*B1.E30G51H30G1.E4.F4.F95H<8K@>CU`:O[7IU1=I'4?&2+$# #',%4(7,<0?7E!AP+KZ6Uc?YkJbtTiy^p€ep~is€lp~ur€wn|o€}n~~m}|m~{n|q‚€u†ƒ{‹Š~ŽŽ‚’‘ƒ““‚“‚“ƒ’ƒ’€Œ€Œƒ‚‘“‚‘“‚•ƒ‘–’{ŽvˆŠs…†q€„o~‚n~~l|{h}uf{shzli{ml{pl{pq}ro|qn{plyngqgalaZgUR_MI]CEY?EZ=I^BNf@SkEVo?ZsD`xLhTnƒguŠn†…–Œ‰˜ˆ—‰–‹ˆ•‰ˆ•ˆˆ•ˆ…”ˆ…”ˆ…”ˆ…”ˆ…–……–…ƒ•‚‚”‚”q|ŽlzŒWn€KmAmAo}MqOt€eufw…iu‚gq€`n}]n}Xl{VkzUkzUk{Rk{RjyRjyRhzRhzRhxUevSfrWfrWbp\`nY\p]bvcdmpŠx}–‚‚›‡‡œ‡†›…Œ™€Š–~ˆ”~ˆ”~    !""""#%%&&(())********..,,,,,,, 0 1 +"4 +: 2AFNRY!ek#pv/r0z‡8{‰9vƒ3g|+`u$RjJb>Y9T /O ,K (E +&C> <: 7 ::::< < < < <<==;;;:998873 +3 +1. +- , +*' +( $ +$ +" "    +  +  + !!)&.+42;0<3>4?6AI5P\HpyQ„d“Ÿb–¡d_†–XnƒMVj5+B * ).%7.A%@P.M];apEm|Pv†X~Ž`‰›Zž^¤U¤U’¡Q‘ PŽœQ‹™N†J~ˆBmz?_l1R_)IW!HS&DN!>H$6;9? AF#FK(MK)OM+UR+VS-TT+VV-XY.Z[/[Z4ZY2VW,UV+SW)SW)QU.QU.TW-UX.VX1XY2VW3XY4\[5]\6[_3[_3‰e¯¶“¡ªš£–ˆ––ƒ\go +2BM[P[ijt’~ˆ¦ftIW€6@n/9gOV…ah—hržnx¤r€¯7Dt"TR Q Q46_‹¶’—¯™ž¶£¥¹‘“¨„‚”zy‹{p„ocwaXeTKXAEI:>B8@JNV`V\mNSeJNi38S*.R04X/7c]e’‹”¼†·‹Ž­¯›œº›œºˆ‹¬oq“FKqchŽx‚©fo–P`‰M]…BLx?JvGDnOLvg`lf‡xpŽtl‹fax]WoUQdMI[LK[ML\DL]DL]LQjZ_xBC\34L*/I%*E07S:@\6:U%*E5 6%*>5:N#(:.,/#&3(*8@DF`deY^[LQNHLPFIM0.A1/B52N=:V3/M.+I--H;;V<@X47P65L@@VDAV>;P1/C52GCBMOMYOQQOQQPKRTOW<9J.+<2/?73D0.A,+=1.E51H53E75H;8H>:K=8H93DICMNISMLJ\[YPQFKMAY]A\aE@X;TJiTrVua€%Vo Yq"Sa*?M499>"32)A@7BB;55.#,1:&LRAoudufp{`pzVpzVgtNfsLezWXmJXwRVtPKqDKqDSvERuDbsC^n>[e8R\/EK*7=*-# " %+ 5*@(7O0AZ:Me?VnH^rF`sGesHdrGapE_nB^lA^lA]jB[i@ZgATa;LY3GT-EY:J]?Md@QiEWpA]vFdzTmƒ]t…n{Œu‚‘ˆ‡•Œ†–…•Ž†”‹†”‹ˆ•‰ˆ•‰ƒ“Œƒ“Œ‡“Š‡“Š‡“ˆˆ•‰ƒ–”~rx‰kv„To}MizAizAmvTqzYo|jq~lt‚kphq‚]nZn~Wl{UkzSjyRlyRlyRgxQi{SgzUgzUfyTfyTgvVeuUeq]cnZYpZ`wahmuŽz‚—†„š‰†™‰†™‰Œ–…ˆ“Š”‚Š”‚     !!""""#%%&&()))**+*****,,,,,,,,, - 0 0 &5+: >EKR[agm%m{+r0x…6x…6l€0g|+Zr#UnIeC_7W5U.K+H !C@ +: : ::::< < < < <<==;;:999886 6 3 +1. +- , +*( ' +$ +$ +" "    +  + +  +$ !)'1)3(4+6/:0<6=;BAHGN$MT%OU'OW#LU!JWKXKXMY SZ$U\&^g%ck*lu+oy.s~/v2z†2}Š6€‘/€‘/‡š2ˆ›4‡œ5‰Ÿ7¢;¤=“¨@¦>ˆ¡;„7™7y“1t.o‰*f‚'\yRrNoCb)<[",L+$C#9*9*727263634646353524241313/2/20000 5-$918C/HT@fpG~‡_›^” c_ˆ™[xŒWdxC;R11(' +/+;7G%GU*Vd9dsEp€R~O„•TŠžP R‘ P‘ PŽœQ‹™NŽ˜R—Q|‰NrDcq:Tb+HS&BM ;D!9C=F%=F%?E&AG(GL)JO,TR0WU3]Z4^[5\\3\\3]^3^_4__8^^7\]2[\0X\.X\.SW0SW0VY0TW-Y[4XY2UV2Z[6[Z4\[5Y\0`c7“šw©°ž§š™¡•ˆ––…““gry&%0>EP^^i‡wŸftN[„0:h)3a18gOV…_i•gq[h˜)6f#U$V%V%V"%MWY‚†Œ¤Š§“–ªƒ—sr„nl~xmpeyf^jYP\FIMFIMEMWLT^FK]AGX?D^6:U04X15Y+3_OW„~‡¯{„«~ ƒ†¥Ž­“”±Ž²€ƒ¤ntšms˜ENv4=d1Ai4Dm0;f3=iFCm>;dHBcibƒŠ‚¡Ž†¥mhqkƒ†‚”mi|[ZjRQa:BS2:K8>VGMeAAZ@@Y27R%*E18T;A^+0K :2"8#(;)-A$*;0,.,"%2/342687<9285@CGMPT?=PBAS<9U,)E'$B(%C))E))E&)B),D/.E/.E52G0-B*(<,*?0.:1/;9;;?AAE@HIDKHEU52C73D:7G53E1/B2/F51H53E64F:7G96F;5F>9JE?IE?IDB@IGENPDLNBEJ.?D(H`Ib 8W;ZCbBaQiPh;I:H>C';@$=;2HG>MMFff_gp]r{gzoy~ms~dkv[foLv€\y†_w„^YnKK`=IhC?]8Fl?Fl?PsBSvEbsC^n>]h;T_2BI(6<*-# " $,!7+A)8P1D\=Nf@WoI`sGatHesHdrGapE_nB^lA^lA]jB[i@Xe>S`:LY3GT-UfH`nPguWly`rfq€mrooxoxl|l|l~k|~j}zk~{o‚r„ƒzŒŒ‘€’‘€’‘ƒ‘Ž€Œ€Œ€Œ€ŒŽ‘Ž‘‘‘‘‘}zŒŒu†Šrƒ‡lƒj}h{zfyxgzpfyoi{mi{mi{mi{ml|nl|njynetigodai^WbPOZHGV?CQ:CV8H[=Md@QiEZsD_xIf|Vn„^u†o|v‚‘ˆ‡•Œ†–…•Ž…“Š…“Š‡“ˆ‡“ˆ‘Š‘Š‡“Š‡“Š‡“ˆˆ•‰”~‘|}Žpu†hp~NhvFct;gx>ktSqzYo|jq~lq€io~go€[l}Xn~Wl{UkzSjyRlyRlyRgxQi{SgzUgzUgzUgzUhwWhwWeq]eq]^u_e|fm†ry’~„š‰…›Š†™‰„—†‹•„ˆ“Š”‚Š”‚         !!"$$%&%&&())****+*******++,,+*+) + ,, +"2/> 6EJRU^#bn*kw3q~7q~7s}7pz4ky0fs*^m$XgOdLa@[:V /M(F "@ >;;9 9 : +: +< < ; ; <<; ; ; : 9 9 8 8 6 5 +430 /. +- + *& +% " " " "  +      +"!)%-&.(0'1,5091:7B;E@JBL"EO"EO"HQKTMXPZ W`\e"dk&el'jt'nx+r},xƒ2{ˆ2€7€2‘3—6†›;…›?Š DˆœEŠžG†šE…™D~”E{‘CpŠ?m†;a|:\w5On4Ih/:Z06W-+J3&D-:18.424242313434141414141313/2.1././-1-1/.21-;+"W*-E*0H$*B%;!7&);'*<$1#'4#'4!.($#$$+00642@31?II]??T,,G%%@ @##C%!F!C @'%G#"? ='#;*'>0)C0)C.(B-'A*&@0,E0+G.)E*)B42KEEZ66J54B22@=9EGBN@;H;7C76?21:43>43>86B>[e8R\/DJ)6<*,#% $,#7$.B/:P1EZ AJHQVbal)dq*jw0oy3pz4mz1iv.et+_n%Wm TiKgE`9W5S*H"@ < +;8 +8 +9 : +< < ; ; <<; ; ; : 9 9 ::6 5 +5 +40 /- - + + & +% % % " "  +  +     + +" +' (!)(2-7091:6A8C:D;E?JALFPOX"T_$Xc(cl)fp,kr-ow1s}0u2xƒ2y„3{ˆ2Œ5ƒ“5†•8…š9…š9†@…›?…šBƒ—@“>~’=w>rˆ9h7az0Up.Ni'@_%8W+K!(I"A)!?(8.8.202020202222030303030202.1-/-.-.,/,/,+00(7'6E5RZ;jrT„Œ[—f” c‘œ_ˆ—^z‰P_pF>N$+%  ##*%51@ =L,N^5Wg>guCq€NzŠN‚’VWs‚Iht>bm7nt?†WŠTw€Kkt<^h/QY&PX%XX*__1fe1oo:ywA}{D}G‚~H€F€Gy{Drt=jj:dd4e`8gb:lh=jg¡¥’©­š™¡•— ”œ˜†“Žw€…(-$/ ,8?Ig[eƒWc‘5Ao+5h2 =($<($<%91*D81K92L<7Q/+D+&B+&B,*C.,E88M88M66C11?.*63/;62>73?76?32;75A;:E97C86B@@G@@GTUSKLJjjdwwpKI@NLDkrQszYvUblADJ0;A'786AB?B?GA>E461OPKflYms`ow_py`p|^frUT]Gmwa‰qblTktWZdGRZ;NW8-B&-B&+S56]@=iDDpKXxHTuDcqAao?[e8R\/EK*4;*,#% !&-$8%/C0;Q2F\=SfA[oI`sGatHesHdrG_nB_nB^lA^lA[hBZgAXd@S`;LW8CN.:C'0:(0 '##!$(!*"-'2*6 0<&5H,@R6J]AUgK_qWgy_l|gn~iqosqpzpzn}m|~m}|m}|m~yp‚}sƒwˆ…}ŠŠƒƒ‚ŽŒ‚ŽŒ‹‹Ž‹Ž‹€€Ž’Ž’Ž’Ž’|Žy‹Šu†Šq…i|€fz}fyxfyxh{qgzpi{mi{mizpizpl{qkypkypdridlb^f\U_PMWGET=CQ:?V:F]ALhDQmI]sKcyQh}^q‡hyŠy€‘€‡•Œˆ—ˆ–†“…’‰…’‰…’‡…’‡€“„€“„„ˆ…’‰‚‘ˆ‚‘ˆ~{Šxy…hp|^hvF`n>_m;ap=anUiv]izhl~kl€fi}cjWh|Ul{Ul{Ul{UkzSj|Uj|Ug{SfzRgzUi}Wh{Vh{VizUhyTiyWiyWjbq†iwŽz–‚†™‰†™‰‰—‡‡•…Š“„ˆ’ƒˆ’ƒˆ’ƒ                + +   "#"#'''(''')))*+***)++))))*)))++))*)**/ "3.>7GGRQ]&Wc,\g1dn/gq2nt,ou.nu+nu+iu(er$^o!YjJbC[9V3P &G B<<9 9 9 9 : : ; ; : : ; : : : : 8 +65661 0 --, +'&% % "  +!  +    !$ '&-*2398==C?E>E?FILPSWYac(fj'in+pv(y1z„0}‡3{ˆ2|‰3z‹0{1}Ž9€’=“B}’A‚“E‘C|F}HuGt‹Fn†Gk‚Cb|DZt;Nk>Gd7;X83P0'D0<(6)5(8-!9. 4/ 4/2.1-/-.,/-0.0000././././-.,-)*+++++++*-,$1%,9.CO2[gI{€V‰Žd’ža“ŸbŽœ^ƒ‘TqLTd/-<' + +  $$+-45EBR+Oa3\n?luOrzUnrTgkMvyVuxUZ\,twGŒbˆ^‚ƒQtuCrj7oh4xn;ƒyFŽƒS—Œ[š_›`›•c˜’a’Šd‘‰c‘‰c‡Z}wSuoKxpKxpKyrJunFlj8ec1[b,X^)RX3PV0LQ5IN2DL4EM5CJ6BH5@D6EJ<™œ“§ª¡˜¡¢‘š›‰“˜‹y…Š1=B , )57A_S]{@Ly'3a,1k6†OU”`g¥cro~©~‘·v‰¯PaŒ0[%U&V"YV W W ZYUTM H%SMW…ir£s{¬{¢ehŒTXnCG]BEP$;! @'%F$"B+)I60N82Q/+C)&=(%:'$9%$;#"9""7##8""7""7!#9#$;)%>)%>((D((D-/C;=QMM[>>K(*1!#* &)(*/.,10/269<@43=0/821:55>:A;RXRV\PIPC9?4CI?U_HclV_jIYeCEC:=;2C;JE>M;8H2/?TUSlmjlt\lt\oy\r{^r|fT]G7=8gmgpzqnxo‰|>E2R^<]hFJa97N&(Q-0Z5?h?KtL]vF^wHitEcn?_d:TY0IJ,89*,#%!"*/!%;#0F.@S5J]?WjE[oI`sGatHetFdsEaqC_nA^lC]jB[hDZfBXd@Ta=LW8BL-VcC[hH[j^dshjynkzoo€bm~`n~Wl{Ui{Sk}Vj|Ui{Si|Ri|Ri|Ri|Rg{Sg{SfzRfzRi{Si{Sl}Xl}Xn€fvˆn|~†™‰‰šŠ‡˜‡Š–†‰•…Š“„ˆ’ƒˆ’ƒˆ’ƒ               +   "#"#'''(''')))*****)++))))))))( ( (()'**--&5,; “>}’AzŽ>{Œ>xŠ[-8U'*G' =6"43%5(4)5* 4/ 4/3/2./-0..,/-././-.-.-.-.,-++++()()())(*) -"%2'8D'O[>puL‚‡]š]“ŸbŽœ^Š˜[|‹Wdt?]4:f27d25p14o69‚9<„:=…9<„;A€=D‚TcŽ„“¾~‘·j}£RcŽ4Ep-]"Q"Y!XRUUSON G F C&/^R[‹gp ~‚¦‚†©x|’X\rILWBEP=BH6;A58<>AEA?K" ,)-!,"%0#%,/18./4#$&#,/// )))42@-+9% =$;! @ ?! @<$=% >!5!5$"7$"7#"9"!8""7 43356%!;+'A++F((D,.B35IAAO88F "(! #"&)().-'++69=BFJ0/843=NMVQPZGNHIPJOUI[aUjoeTYO5?):D.JU4_jI]\SHG>>7F92A84EURbmnkhigmu]ow_lvYoy\t~hui^e_9@:YcZª´¬—ŠDK7amKYeC=T,2H )S.1[6DnENwN^wH_xIitEcn?`e;V[1IJ,89*,#%!"(. (>&3I1AT6L`AWjE[oI`sGatHetFdsEaqC_nA^lC]jB[hDZfBVb>R^:KV6CN.Q5GY=NaJXlTdvZk}ao€irƒkrstvs~s~p€‚n}j{zj{zj|wn€zr…‚uˆ…~ŽŽ‘‚“‚“‚ŽŒ‚ŽŒ€ŒŠ€ŒŠ~ŒŒ~ŒŒ}ŒŽ}ŒŽŽ’Ž’Ž’Ž’}ŒzˆŒt…‰o€„j}h{g{vfzufzneylj}nj}nj|pj|pkypkypivpboibj`[cYR[LGQB@Q8AS9CX6EACGMNQRZZ]^ cg dh!hlpt$v~)zƒ-~†1ˆ22…’7…•<‡—>…—@„–?“B‘AyŽDvŠ@q†Ak;i~Bey=^tA[q=Pk>Je8E_;?Y56S5.J-&@19*2+/(-+-+0*/)1* 2+0- 1. 1..,.--,-,,+,-,-,-,-,-,-++)*()()'('('&'&+"-#09#CM7ckEy\Œ–^“e”¡a’ž_‰˜Xx†GTf14F*& +!$ + )$-.6'>E6GSFQ\OP]YJWST`^Vb``f`jpknxbnxbitUgrRpyXqzY}\ˆ‹f‘f“”hŽ‘`ŒŽ^‰ŽY‰ŽYƒŠR„‹Sˆ’U”až¢f ¥i¡¤m¤¦o¥§y¦¨z¤¥zžŸtšj–šgˆVw|D]i1KX7F3B1='6B+1:-/8+)3*,5-MTS˜ŸžŽ–™…‘€Š{†‰z‡hpu#'0"*(.DGNcGKt26_22o44r20r20r6<}&+m&(l%'k%*k%f!%[!WQOxŽ·‰Š£`azU_vMWnR]tPZrLPdCH\DEW?@RBDXACW:@X39Q17Z39]-7l.8m2:{,4u02vAC‡;>.1r18{4;?RŒyŒÇkˆº]z¬DaŒ3O{/Bh!4Z*H=?!A"J!H EA<: +7 ;(8ZJZ|iu•}‰©„‰¡otŒ^_jMNY[ZXTSQQQ=TT?QP7DC*41-41-''')))+.425;118##*%#(-+00.5$"%#"9#"9$"B! @! >! >$!8*'>.,:;9GIFO<9B41:-*3)$7'#6%7"411#2 /!3$"7(&@*)B2/F1.E;9G31?+).$"'$(#.2-2933:4483,0+)).;<@NQNJNKPXPNUMW^XhnibihQXWAIADKCCK:T\Jjs_KTAY\Sbf\twnuxoipaqxi‚‰tƒŠut~awdrxlx~rnu{HOUR^ju?KI2>a^nGeuNlz_r€ewƒmwƒmvtvtt~|t~|o|~ly|n{{n{{l}xp‚}uˆ…yŒ‰‚’‘‘Ž‚ŽŒ‹€ŒŠ€ŒŠ~ŒŒ~ŒŒ~~Ž’Ž’Ž’Ž’|Šx‡‹t…‰nƒh{ex|g{veysg{og{ok~ok~ojzsjzslzslzshunangai^W_UOZHDN==N7AR:CY?H_EOiIToO_xVg€^lmvŠw€Ž……“Š†•‘‡–’†“ƒ‘Š„ˆ„ˆ‚‘ˆ‚‘ˆƒŒƒŒƒŒƒŒ‰‘ƒ‹‡‡{x~roy\akNVa;NY3LWCS^JPe]Zogf{li~ol€_k^i~Tg|RhzRj|Uj|Ui{SkzUkzUkzUiwScvScvScvScvSgzUh{VhzYn€_p‡lz‘wƒ™‡†œ‹Ž›‹Š–†Œ–†Œ–†•‹•‹Œ”‰Œ”‰ +  !                + +  +   !!!"""$$$&((((((((()))*(''))))%%) ) %%%%&%&&* + "/ (4,:3A;I@MJRQYV^]e$aj"fn'jt'ku(fy&ex%]w%XqHf@]3S*J > 99 9 : : : : 9 9 9 9 776 6 6 6 3 +3 +2 2 //, +) ( % % # # "  +   "$!( &--53;@CFIMPQU"\^$ab)gg)jj-or+sw0w{+{€/Š4†Ž9ˆ‘;Š’<‰•:Š–;‡—>†–=„–?ƒ•>|Ž>w‰9q…;k€6g|7cx3]q5Wk/Nd1G]*>X+9T&5N*/I%%A$:1!0 +$+$)(*).(.(/'/'0-.,.,-+,++*+*+*++++++++++++)*()()()%'%'$#&%) *!)29C-W`:owQˆ’Y“e˜¤e–¢bž^ƒ’RhzEK](.?' +%)2+4 )#,18)BI;BM@9E85B>5B>?KI>JHDJEHOIEN8IS=OY:R]=S\;R[9^a@„>Aƒ7:|&-q)0s+>yr…Àn‹½c€±Jg’3O{&9_2X"@ ?$E%F%L%L!FB; 7 +7 +7 >+;]HTuiu•Ž”¬•­€‹iju`^\cb`ffRyydrqX`_FWTPRNJGGG<<<)+2$'-,,3::AA?DB@E75<-*2% !*"!8%$;#!A ?<$#@<8OA>U@=KB@N>;E,)2-*30-7,(;*&9'"9.)@71H2,D,(;"0-0'%>/-G51HB?VWUc74B'%*6599=8?C>6<7=C>>B<261>>CEEJILJJNKLSKV^Vagb]d^SZYJQPMTLLSK[cQ`iWclX@I5RULnqhrukosijqblse‡Žx€‡qfpSgqT]cWU[OPW]OV\FR^8DP2><8DBQaEWgK5O)%@*N(5X3SsLSsLevJfwLhqGblAae>VZ2KN+;=+/%)'"*"/(5#2C*P\HQf^\qiezkh}nk^j~]i~Tg|Rj|Ul~Wj|Ui{SkzUkzUkzUiwScvScvScvScvScwRfyTgyXm~^qˆm|’x…›ŠˆŒŽ›‹Š–†Œ–†Œ–†•‹•‹Œ”‰Œ”‰ +    +                    ! !!"%&&&#&')''''(())++(&&&%&&%%%&&%%%&%%$'''') +%0)6-;8B;ECNIT U^"[d(el'go)js(js(ku(lv)^r#ViBa9Y%H +A;;: : 8 8 : : 8 8 86 5 5 5 +5 +2 2 1 1 ..-+ ) ) % % % $ +%$$%(#- *1.6 8= m€@i}<]v;Yq6Qh7Me3DZ4=S-7L/2G*'>(#;%6'2#,$+#)&)&'&'&'&'&'&'&+*+*+(+(+(+()*)*)*)**),+*)*)*)*)*)*))('&%$$#$!$!$ %!%0/9(IO1flN‹Yši™¥f˜¤e•¢gš_{†Rhs?MY'3@(.8PX9RZ;=I99F6:QRDZ[?V^5KS8HU9IV=KY?LZDPcKWj8GW1@P2B@D?CGBRTF\_QgoPqy[„‹\”eœ§e¡¬j ªi§±p¤³q °n’±a­\žIw•Ax“JuHˆ•a—¤p£¥w¦¨zŸtŠˆ_fkAIN$1:*3'2*4-1%AE9„…ƒŒ‹~…†x€ƒ…ƒ…njbJF>D@qr…¸cˆ»[€³Qwª2XŠ.Z + LA!,O-2Q',K!)I$E?=3 2133#?3>\OYxv”‹”©Ž– ‰“z~sxwp}dq~etVbnEen?Yb3KR*JQ)IM5>A*341GGEFDP<;F/1@'(8'+B&*@%"E# C% =#:')6-/<109..752;63=43>0.:0.5-*2+*,.-0:<<;>=FDD412$ &($)$$+%%,11???MGEQecotlwMEO($"842JNKFIFCGBMQKELKFNLW[]ostLVY;FI@NNJWWYff?MMQ[YDNL.5-07/R_M^jXT[HIO=DO;5@,+> 9L.$>4RgDavSivPhuOjrLckE^c@UZ7LK,<;,-'(#&&('1/9(7H*@Q3I]5Rf>]qEatHewIcuFfuHdsEfsEhuHjzEm|Hq}KtOx‚Pv€Oz„RyƒQu}Su}Sy}Q~V„‡[…ˆ]†‹cŠŽg‹f’i’j‹h†g‡h†g†g‡Žm‡Žm‡r‡r‰u‰u„Šy†v~„x|ƒvwƒtv‚rn{pmzon~wr‚{w‡‡~ŽŽ‚‘“’ƒ’Ž‘‚ŽŒ‚ŽŒ€‹Œ€‹Œ}ŒŽ}ŒŽ{Ž{Ž}Ž’}Ž’“}Ž’yŠŽu†Šo‚…k~‚i}~fz|g{wg{wkzwkzwn{yn{ykzwkzwiwuhvtjtpcmi`e^X]VKTEBL=9J9;L ;: : 7 8 : : 8 7 5 6 5 5 5 +5 +2 2 1 1 .., + * ) % % % % &* +) +"-(2+56>;CGLLQ VZ^b&hf'ml,su,z|3|‚-€†1‹1…5„’2†”5‡–9‰™;‰–@Š—A†”B…“A?~Ž>x‰=v‡;l€8h{4`s3\p/Oh-Ha&CZ)9;?:@C5BD6>F'DL-`g8tzL‹–Tœ§e¤¯n¦°o¢±oœ«iŒ«[¡QŒªV‘°[‘¬c…ŸW…’^ªv«­¯°ƒ®¬ƒ¨¦~šŸuƒˆ^V_>;D"+51;#@D8EI=fgd“”’†Ž}ƒ…ƒ…ƒ…„€xxtkqnjqnj_\xMKf54m98q23u((j((l&%j""f$$h#fc"XMO…€†©–œ¿™²z† c{“Wn†Ga:Tr=Lj:If;D`6?[39U.4Q26S6:W19^19^+1g)0f$,m(/p-2t%*k,.e+-c-0f47m&9m_r¦[€³Pv©Io¢;a”4`(T$F&1S17V.3R'.O%F= 3 , , +- / 13&D8B`OWmjs‰†Ž˜Ž– ”“ƒ‡‡q~edqXwƒZiuLv€QˆYgnFmtLmqYY\ECDARSQXWbIHS56F-.>%)?#'=&#F!A!8 7!.!#1)(1*)2,)2.+4.-8,*6/-4@=DIHJ@?A-0/9;;=:;/-- ! !$$$+DDQbbpgfqjhte]h_WbEA?>:8>A>9<9MQKHLGCJIGOMTXZhlmV`c;FIM[[XeeJWW:HH7A@'1/7>6DKCbo]Q^L;A.`gSepV`lQKZJ:H88E@1>9*88*883B7;K?8@>'.-%,2.5;>CPLQ^FNL@HG12,-(**-+6$5?-;L.DU7Mb:UjB\pC`sGewIfxJkzLm|Oq~QrRu„PxˆS‹Y‚]ˆ’`‹•d™gšiŽ•k–l“–k“–k”˜l–šn–šr–šr—œr—œr–šr–šr’›r’›r’›r’›r“™x“™x—z—z–|–|Ž“‚Š‡‡{{‡w{‡wtvs€uqzv†v††|ŒŒ’’ƒ’Ž‘ƒŽ‹€‹Œ€‹Œ}ŒŽ}ŒŽ{Ž|Ž}Ž’}Ž’“|‘xˆŒt…‰o‚…k~‚i}~fz|g{wg{wkzwkzwn{yn{ykzwkzwiwuhvthsoalh`e^X]VISD@J;8I8>O>?VBJ`MSlQ[tYe|bn„jvˆz~‚„•’†—”‰•“‡“‘†“ƒ‘Š‚‰‚‰…’Œ…’Œ…’Œ…’Œ…‘…‘‚Œˆ~‰…}€wrukbgRMR=@J2>H0>NGJZRNfdZqpf|jg}ll^j[k}Vi{SkzSkzSkzSkzSkzSkzSjxTgvRcrRapPYjL[lN\oP^qS[y[dƒeq‹{}–‡†šŽˆœ™‹‰”‡‹”‡‹”‡–‰–‰–‰‹”‡                   +        "###&&''''''''((((('('''$%%$$$$$!!###%%%%%##&&) ) //'6,;7A>HHPPX%^d#ek*nu+qx.oz+oz+[t$Rj@_7V#F?;989 8 +8 +7 5 +5 +5 +6 6 442 2 1 1 / / . + + * ( '() $0(31<4@ =HDO NVU]Zc_ggslwu}#z‚(~Š*.‚0„’2„–6†™8ƒ–:ƒ–:–<•;|‘@zŽ>xˆCu†AnEk}CewB_qP+5L+5L/4L05N69K36H06A38C4H?R\Sjvgz†vz‹kxŠjŠ™e”¤ošªl °rž®r–¦j•Ÿlž©u¦°¬¶„«´|¢¬t¨­u¦¬tœ k†ŠUno=kl:w|K„‰XŽ’q¢¦…“˜Š…Š|ƒ†}€ƒy‹‹p˜˜}’–|ˆŒsjouMRX78e89f,0m).k'&k$$h""f d%+j-3r5Azdqª~“»ˆœÄ‡¼uŠ©\|—TtAg…5Zx5Ss5Ss;On2Ed0;W/:U56V77W54]66_/2e,/c%+j&,k(/m%+j"\#]'0_7Ao2H{H^‘R}¶=h¡1_œ-Z—?u +a)W.[,5X*3V*3M$-G"8 )    "),/'0E7IXHZi`ozu„‡‘–‡‘–|ˆ‡jvtaom[igq{l|…vv{fx}hqride\Y[iZ\jLOfNRh37V%)H'(L%'K))>&&:$&(&(*-,#/-$.+ 2/$10,-,'./,++)<=:22025+69044+/0'44+./%)-'#$%)01TY_intjnw\`iMOWUW`LOSKNRCJICJI2?:;HDAOOESSMZ\FSUCNQ>HKNW^:CK5:GBGT=>G67@RVVAFEP\ZbnlM^TO`WQbVEWKK@CPE`dXkoc]^URSI>F4:B1CR9\lS\kKcrRhpQiqRktSktSkoNfjJbcFYZC5@N3KX=Pa>[kIavEi~MmNpƒPxˆSxˆSŒX‚[…’^‰–bši’œk˜¡r™¢s›£v™¢u—žt–s“p“p‘Ÿo’ p“žo“žo‘Ÿo‘ŸožlžlžpžpŸr‘ s’¢t’¢t•£s•£s” u” u”œ}“›|‘—}Ž•{”‰x„ˆuƒ‡t€‡xƒ‹|~ˆ‡„ŽŒ†“…’’’‚ŽŽŒ€Š€Š|‹|‹{Ž|Ž}”}””~Œ“xˆŽt„‹o‡k~„j}h{h{zh{zizwizwkzwkzwkzwkzwiwuhvtjsraki_b`VYVHQD>G:7G9?HHPW]^d#gn$ov,q{,u€1f~/[t$Kj#=\.P"E;:9 88 +8 +6 5 +5 +5 +6 6 442 2 1 1 / / - , ++ * ( ) + "1-9 +2>U&5L$0G&;"7.+((()% % %"$!$$$$$$$$$$&%$$$$$$&%'&'&'%'%'%'%)&)&(%(%'&))))(((((('&%$%$""##""""""&(-6=F,]c=x}X‹—a–¡k–¡d‰”Wz„Rv€Oˆ‘i›¤|œ¥†˜ ‰™}tw†vt‚r`srCVU0HM5LRG_p;Sd@Yn/H])?U0F\L^tTf|N^rk{izŒDUf;E]1;R17O05N47I7;L61 +!  !%,-04.=2DSHVbXgrmw||†‹€ŒŠz†„l{ykzwz„u|…v†‹v}‚mwxojkbTWdCERFJ`7;Q'*I03R56Z=>bMMbII]235.02;:175,2/$30%<;7++&'(&563675;<9>B8VYORSI56,78/*+"$(#)-'#*+'-.JOUafl[_hRV^MOWTV^SVZGJN197,321>9Ta\\jjZghN[]LY[LVYFPS4=E2;B=BO.3@56?BDMOTS=AAGSQ\hfN_VL]SP[UZg\^k`…yy}qpqhJKACK:2:)3B)\lS`oO`oOiqRjrTktSktSkoNfjJbcFYZC5ES7N\AYiFbrPjNp…Tu‰V{\ƒ’^†–aŒ™eœh“ l—¤p›¤s§u¦w¦w›£v˜ t–s—žt“p“pn‘Ÿo“žo“žonn‘Ÿm‘ŸmžpžpŽoŸrŸrŸr“¡q“¡q“Ÿt“Ÿt•ž”œ}•œ‚”›’—‚‘–€Ž’€‘~‡Ž‰‚„ŽŒˆ‘‡‘”†“ƒ“’Œ€‹Œ€Š€Š|‹|‹{Ž|Ž}”}””~Œ“xˆŽt„‹o‡k~„j}h{h{zh{zizwizwkzwkzwkzwkzwiwuhvtgqo`jh\_\RVSFOB=F95F8;K=?UDJ`NRjYZsad}il…qwŠ‚}ˆƒ““†––†””†””†“ƒ‘Šƒ‘Š…’ŒƒŒƒŒ…‘…‘…‘ƒŽ‚Œˆ{…y~mgm\SZ?DJ09D0?J6ARML^YTkk]tufzng{ok~`j}^k|WizUkzSkzSkzSkzSkzUl{VjxUguSbpT^lQYiMYiMVgW\m\^vmhwtŒ†}–†š…™‹—‡‹—‡Œ–†Œ–†Ž—ŠŽ—Š–‰Œ•ˆ                         !" !##&&&'))')))))(((&&%###$%###""##!!!!!! !  ""# $$((!+'13;:BLM TV(`b&lm2ox0s{3r4kz-\r#Pf@]6R (E #? = < +: +78 +65 +5 +5 4 333 3 0/ . . -,+ + $0(3 2< 4? BK +JSQ\ Wb_jgslyq!u†&y‰)xŒ){,’0ƒ”2„–8‚”6~“;|9vŒ;r‰8n†7i‚2h~6dz3^q8]p6Yh^JHhDGfGJjFJ`IMc148'*.?A5=?3AC0>?->A2(*..';;402->@;IJFDFA@?==;9'$&$!$213/.1&%'09AKLT^EKU@EPBHRPU`JOU8=C*554@@4DK?OU/EO5KU.BJ1EM;LP@QU3?D1=B5>F9BI@EKKPVHQY7?G2@KCR]FU^6FN';7-@=?LF8E?7C<&1+2917>6(2()4):B1QYHksbx€o~„szowxhPQA). 49*GQBbl\elVelVgnRipThrUktWhpQaiJbcGZZ?TL;IA0@4/:.)82.930;A0EK:N[;YfGesJp~Uz‰[~Ž`ƒ’f‰˜lšp•Ÿtž¥tŸ¦uŸ¦u ¨vž«y©x¦w¦w—¡w– u‘›uštŠ•tŠ•tˆ–tˆ–tŠ—q‹˜rŽ›rŽ›rœqœqŒlŒlŠžkŒŸl iŽŸh‹h¡l¢m¢m‘¡j“£l”¤o•¥p™ x˜Ÿw•œ{•œ{——ƒ——ƒ”•Œ™šŽ–”Œ“’†“‚~ŒŒ{ŠŒ{ŠŒ|Š|Š~Œ“”}•}•{Ž”y‹’xˆŽt„‹q‡m}„j|‚i{gy{gy{iyyiyykzwkzwiyyiyyiwwgttfpn]fe[_YRVQEN?"= <8 +6 5 +5 4 433 3 / / . . ) ,/ 1*6 /:9CALNVU]^ifq"nz!s~&x†(|‰,}Ž.‘1“0“0’0‚“1~2{/v‹3tˆ1lƒ2f},ay*\u&Xn&Tj"Ma'L`&HV+DS'5J'0E"'< "71.''$$" $!"$!#"$!#""""####"$"$"$"$ ""$#&#&$$$$##$$%%%%%%%%&%&%&%&%'&(('&'&'&'&&%$$ ! ! ! !!"$%-9B!QZ8fpGr{Sy}\‡‹j’˜~•œ‚““ŒŠŠƒ‚„†xy{twvtwv†‹†‹z†v}‰ymƒFZ[*J^Gg{JmŽEgˆA^|7TqVj€r†œ}‘ g{ŠWr‡Ni}K`x=RjDKeCEAF^eD‡Žm¤­wž§qž¥}Ÿ¦~¤¬ª²…§·n”¤[‘§R–¬V•§]¡Wv„K{ŠQ˜iœlˆl™ }‘—†‚ˆww‚nu€l|†b”žzž¦zŸ§{–•ŠdcX35Y67[00k+,g(&h)'i36w),n[59xk|µƒ•Í„©È‡­Ë‚¢Ë2Rz=/LpTv—[}žSt“Rs’Vr‹[wis‘t”± œ¼¢œÂ™“¹ˆ‹¸{~«dj¢KQ‰.6p%_T K T3?t@V‡H_Dgœ?a–4YŽ-R‡2O'Dv*8f-Z$,Q&.T(/I)0K+0C$)<#/', -000-,--=8GWEU`ScnVgrXiti~€•¦˜£¸Ž™®™“­œ•¯•‡£š~sŽk`{`VubXwZYyWUuGJjCFe26LGKaBFJ/2679-<=2:<)FH5;=/')66/44-9:5>@;683@A('(18@JEMWOT_;AK06A;AK-18-18;GG;GG1AG:JP*40:3;#7?.?CBSWEQV2>C3;@1RWI\fW`iZelVfmXhoSjrUhrUktWhpQckMddH[\@UMKYWVebXmq]svczkczkh~Xg}Wk}Vj|UjxTjxTivTivTlyYkxXjwWfsTeoRakNYeNYeNVeZ]la_wskƒ€uŠ}˜’„›Ž„›Ž‹˜†Š—…Œ•ˆ–‰Ž—ŠŽ—Š–‰Œ•ˆ              + +    !" #"$%%%&%&''((((((%$%%%%##""!!!!""  !!  !! !!""##"((.7:?BPP Z[+hl+qu5u~6x€8q€2ix*^r"XmQdJ]DV?R 7K +6I 0G-E%@">"= ;77221 0./ 2 2): +-= 6C +{U63j41h0/o)(h$$a##`U?F~oˆ¸‚šË‡¨ÅŒ®Ë¦Í_†­EP&:kQe–_w¨`y©ZužUqš`s™r†«˜»”ŸÂžÄ¶lˆµc~¬Ngœ3L 3i#YM G$N0;e:KqEU|BZ†D\‡:V‚0Mx1Gq.Eo1@d /T#-K$.M(1F*3H05F+1B&*9"1 $3 $3"1!00.,+,'73BRIXhRdqM`lOf{i•†™±‘¤½œ¥¿œ¥¿ª£½§¡»¤“±†u“cPsWDghZ|re‡b\tNH`CFQX[fOOV;;B731EA?XWG;:*+,78(24(>@4IMALPDBI;3:+$&+(+B>APPW;;B!$/,/:7@0KE4H:3I;4ID7PK>V_@aiJkvPw‚\ƒŽfŒ—o‘Ÿo“¡q—¦q˜§s™¨t›«v›ªx›ªx›§uœ¨v›¥z– u“™xŽ•t†‹o}‚evybor[fkUafQ]bQ]bQ_eTagVejZio^ltcpxfs‚iv…lu‰jyŒn|‹k~mƒ’rƒ’r„“s„“s‡—nˆ˜o‹œjŒkŽŸjŽŸjŸo’¢r˜¤y˜¤y›£†›£†šœŽ™›–‹‘Œ„ŽƒŒ~‹~‹{‰Ž|Š~Œ“”——Œ–|‰’x‡tƒŒo€‰k}…j|‚i{hy}gx|jz|jz|kyykyyiy{iy{ivxerudnj\fbV\RHND>D8:@44A6;H=:OGFZRNf[Zrgc|vm…x‹~‘•ƒ’”…”—†””„‘‘…‘ƒŽƒŽ…‘ƒƒ„‘‘„‘‘ˆ‘‘‡ƒŒ„|…}ubdmPU^6MV.JR:RZBS_X]hb^qncvsj|ihyfg{\exZk{[jyYkyWkyWkxXkxXkwZkwZluZhqWenSbkQZeQWcOSf]Zmd^xtm‡ƒ{”Œ›’†šŽ…™Œ—ŠŒ—ŠŽ–ŒŽ–ŒŽ–Œ•‹•‹Œ”‰            + +   !"$ "#%%%&%%''((((((%$%%%%$"""!!!!""!  !     !!!!"" &$*038;HHST$_b"jn.ox0t|4r4p1f{*`u$Yl WjPcL^FZ@T9P4L .I *F (C&@ :9321 1 2 +3 $7 (:/?5EAMGSV`]g!ir!nw&s€*v„-z‡1}‹4€:~8y‰9w‡7s…4p‚2h~-cz)\r,Yn)Pd(Mb&DZ&@V#8M#3H,A'< 30-)&&%%#" !"""""""""" # # # #"" " "!#!#!"!"!"!"####$$$$$$$$$$$$$$&%&%$$$$$$"#"#!" ! #11MM8uuamz{wuwrnnujjqccqccqXUeVScvr~so{nkt~{…iw~fszGbr9Td1Vo7[u Hm>c*Ih;YxYr‡g€•tmˆ–Qt…CfwKj|Kj|O`v;Lc,6N(3J:;R9:Q;=T99O30g0.e,+k+*j&&d##`RJPˆtŒ½„͉«Ç‰«Ç¦Ír™À0ExM'Y*[3c6g,U/XL_…n§Œ—º‹–¹}´m~¤\w¥Ok˜=WŒ/I~-?u#YII*S/:d9Jp;Lr7O{8P|6S~-Ju.Eo*@j.=b#2V",J#-K,4J.7L5;L05F.2A'+:$(7$(7&5%3!3/.)+,(7:IYK]jGZg?UkSi~j}•uˆ¡ˆ‘«”·¤¸¤¸§–´•„¢lm[}{mug‰d^v[UmQT_LOZTTZBBID@>UQOcaQ:8)34$-.45*;<1@D8>B6?F7,3%/2*570C@B524;;B33:),7-0;9=JCHUJPa>CT).?'->UWg56F.0=8;HNR_RWd7;D $,"' ,1-/%465AE1=B-8;*473=B7@E1@I?NWPYaXaiinmejici]`gZcn\^iWem\jrakoadhZLO?RUEik__`Ubf_imf]`Vbf\kn`il^inYdiTbfSfjWilUilUglVkp[irXirXlpWhlRdfL\]DXRAOJ9RE>VHATPC^YLckMpxY|†`…jŽšq’u“¡q”¢r—¦q—¦q˜§sšªu™¨u™¨u™¥s–¢q’›q‰“i€‡fv|[lqUafIW[CPT=T?>U?S8@Q2:K28I-3D,-=)+:#)8"(7'6#11-2!6);'3F=N_FWi?RjGZrId€ToŠ_z–fs…¢s…¢}†›~‡œ‚ƒ“‘„€Œ€|ˆusxgejJQPDKJNSIMRHVTFgeXkdXOH<@=031#-1)-%.08%3:+18)6917:3+))/---*2/-4118006*.7.2:36A*.8('7! 0.*+=,*C/-G)(8a`pRUY$'+"#%&'77:JI(<9/,)519FA@LJBNL=EIX`cq{wozvpwhmufmsbio^jn]il\imagk_gl^]bSQUIfj^sun]_XTXRnrmltlhogel_dj^fjWdiVhiXhiXikXjlYjoZns]nt\mr[lrTkqRgjNdfK`aC]]@]]A``EhhMppUy€]„‹h‰—nŽ›r‘¢p‘¢p‘¡q“¤s”¥u”¥u”¢y”¢yžyŒšvŒšq‰—n„Œfx€ZmqQcgFVW@JK4A;7CE>IKDNWJZcVakZjucmzhp}kq€ms‚pqsr‚tt…ux‰x|o|o•i„—k†že‡Ÿg‹ŸeŒ f“¢p•¤r•Ÿ{•Ÿ{•—‹“•‰Ž‘Ž‰ŒŠ‚Š‚Š}Œ~‘Ž’“˜}Œ—˜{‰•uˆ’o‚k~‹j}Šj|‚hz€kw|kw|jy}jy}iy{iy{mw|lv{nvyhptcim[adVXQKNF>B69=12?39F:Yb8X_C]eHch`kphkypl{ql{ijyfiy_iy_ky^iw[kwZkwZkwZkwZiv]iv]jt\fqX`mT]jQWeUVdTQc\_qjh€|u‰˜…œ”ŠšŽ‰™—Š—Š‘—‘—Œ—Œ‹•‹Š“‹‰’Š       +          +     !"##%%%&&'''$%''''((&%##""""""!   !!!!!! ##!( &-,5 7@LNTV&ab)ij1ms2ou5p{1oz/oy-nx+ny*kv&ft$an^kZfTfPcK`G\;T7P .G ++D*B *B 4G4G?M DSLZSa^ker$kv%my'rƒ)t…,t†.qƒ,n.n.h{/dw+Xp(Tm%Kd'F_";W6S/F*A#:60.+)%%##""!!"# """!!"""""""" ! ! ! !""""""""!"!"!"!"" " " " !#!#"$"$"$"$"$"$!"!"!"!"!" ! !!#,,DD6Š„|ƒ}uvopz{yu{vrwwv{gejecqgesc_pfcs{n€cVhKAPH?NOSbPTcH^vK`x<]|9Yx5[~/Ux<`}Tx”X~Žd‰šf‚‹|™¢¡©–ª³”ª´£­„‘¡}Šš„—ns‡JI`54K;7U;7U72U95W?3\?3\D3_C2]D3_D3_D6QaSn œ”›—Ž”’—‚—›†¡¦‘ƒ&'/:IS4`p@pQ–W‹ b’¤h”¦j’˜~“š‰…t|xoxymvwy~k˜œ‰¦¬‡¦¬‡¢§‹ž£‡d_t50F31a20`-,e('`#%b "_!-h]i¥y•ÂƒŸÍªÅ„°Ë|¨ÎqÄ[v­O Y"[![![YS WGUŒWnZqŸUqœPm˜Hf”<[ˆ5P€-Hx,?l':g)4^)4^-7\.8]+<[/@`,Cd)Aa(Bg*Di+@a';],OaATlCVoA\xJeOj†Pk‡[l‰]n‹emƒhq†qsƒlm}hdpfbnhgkdbgKRQ,*)-"&-6";D04;-18)BD=CE>)''1//308.,3,,3$$+"*)-6>AL69D! 0! 0'(:)*<#";-+D+*:0/?GJN" " &(.-!11*'1DAXeabojBNL@LJ;CFhpts}ynyupwhlsemsbio^ko^jn]hl`imamrclqbpthpthuxpY\TAE@jninumiphel_ci]fjWfjWhiXhiXikXjlYinYkp[kqYmr[pvWpvWqsWnqUopRpqTrrVww\~~c……j‹’o’™v‘Ÿv’ w¡n¡n pŸo‘¡q‘¡qtŒšq‡•q€Žj~Œcv„[pxRfoI_cCW[;OP9FF0A;7>A:GIBMVIU^Q`jXhramzhp}kololl|nm}opps„twˆjzŠl~‘e’f€˜`„›c‰œc‰œc‹šg‘Ÿm’œx’œx“•‰“•‰’ƒ‹Žƒ‹Ž}Œ}ŒŽ’“˜}Œ—}Œ—zˆ”uˆ’o‚k~‹j}Šj|‚hz€kw|lx}jy}jy}iy{iy{lv{kuzltwempbhkZ`cWYRJLE=A57;/3@49F:;PHH]UTif^tph~qˆ‰xŒ”}‘™ƒ•—‚”•„’ƒ‘Ž€Œ‘Œƒ’ƒ‘ƒ‘„‘‘„‘‘…‘ƒŽ„€|ˆxv‚^htP_i?[e:[cFckNglenslkypm|sm|jgwdiy_iy_iw[iw[kwZkwZkwZkwZiv]iv]jt\fqX`mT]jQWeUVdTTg_atlkƒ€w‹„›’…œ”ŠšŽˆ—Œ—Š—Š‘—‘—Œ—Œ‹•‹‹•ŒŠ“‹     + + +  + + +          ! !"$$%%%%%&((&&''''&&&$$#""""!!!!!!     "#%%/+48@AI$MT%T[,_e-fk4jq3lt5my.lx-s{,qz+nv(nv(jt!is eqeq]oYjQfK`EYAV CT AR DU JZQ_Tc]ico"hu%kx)mz-q}0o€2n1i|5ey1`s4\o1Ui4K`*AX.;R(1I*+C$$9$ 5+ &&%#"    ! !  # # " " " " # #""!!!!""!!!!!!!!!!!!!! ! !!"!" # #!$!$ # #"$"$"$"$ # # ! ! ! !!"!" $$//(QLHgb^ƒsk\hn^j‹|‡’‰–ƒŒŠ|Šwjwqizf^od^ogar[[iEER>CP=BO0[A2]@1c>/a@0eA2fA0bA0bA7TOEb”‘”““‘†‘’‡’ˆ™œ“ŒŽ•36<..:..:-19<@Heg`~zŒ’z•›ƒ—‰”…t}~mvwnwxoxy{l›ŸŒ¯±ˆ®°‡«²ˆ¦®ƒ{|y:;800M65S53h-+`!$_!$_/B|]p«m”ÄvœÌr¦ÉvªÌx¨ÐnžÆVq¨L Y$]"["[ XRQ+3kIXˆM]Ha‹F_ˆA[‚9Sz/Ho,Fm,Ag)?d(<_';^.=[-//;'-7$)4$+?%-@,C)@)=+?+>5ATCP`IVfJXmP^rRc{Rc{Yk^o…bs…du‡]l|DSb/M1@P0BH*AL03>)28)282@@KXX^da[a]MMOIHJTSX|zu}qqzmrvhmrcio^hn]jl`ik_gl^hm_lpdmqeptjrukqupjniekhpvrksiemcel^cj[hiXhiXikXikXikXkmZhlYkp]qx^tz`v^x`y‚Zzƒ[}†\‰^‚‹e‡j•o’št’u“žv’ w‘Ÿvšx‹—u‹–vˆ’s‡sƒp€‰l|†iy‚er{^nvWhpQchL\aE[ZHRR@OH@E?7B55>00=.0?03B05B05C27D38E69G9;J@BTKLYUSc_]dgdmpnpvrsyvpxupxunvyow{nz}o|~m|~n}szuƒ|yŒu{w~—j~—jƒ›`‡ŸeŒ f¢h‘›u‘›u•†”…‹ˆŒŽ€Œ‘€Œ‘{•yŽ”{Œ—{Œ—z‰™yˆ˜t†“oŽk~‹i{ˆg{ƒdxhxhxhxhxhw{hw{kx{jwyksvfnqbihZa`SUPHIE=?:6832<89C??FHN TY!X^&]e&ah)dp%fr'js$lt%nv(nv(mw#nx$jv"jv"dv!bs\pWkRfOdPaPaRbWg\k`o!fs&ht'jw(ly*q}0p|/l}/gx*^r*[n'Tg)Pb$CX">R0H)A71*($##!"! !   ! !  # # " " " " # #""!!!!""!!!!!!!!!!!!!! ! !!"!" # #!$!$ # # # # # #"" ! ! ! ! "!'' JJCysp€zw}nyueq„u€’ƒŽ™†œˆ’†y†wˆxpsm~zt…vvƒ]]kHMYBGTKT`KT`,BSDZk5V5,N>0[B3_C5gE6hE5jC4hB1c<+^:0M@6S…‚…•’•Ž„‹’ˆ–™Ž‘—8:A%&112=/3;04==@8XZS|j‹‘y‡Ž…Œ}s||luvoxyrz{~‚oŸ£­¯†«®…©°†¥¬‚ŽŽŒHHF.-K54R10d.,a#&a"%`DW‘v‰Ãq—Èq—ÈkŸÂm¡Ät¤ÌnžÆVq¨LUX!Y XVTNU1Aq?O9R{6Nx7Pw5Nu,Fm$>e$9_$9_$8\"6Y-U:G^;I]?La3;N07J7?I8@J:;F89D/5?+0;(/B.5H,9P(4K#3G!1E%1C,8J?K[CP`DQfDQfAQjEVoEWmL^tRctYj{]l|[jzR_oGSd=L[3BR(:@28+60+60@E4MSBNMBUTI]^N]^NZ^D26#+ +4.7+4-3"27&-/*:;7;<9220570:<5%*&,48%=A.PQFGI=.+474>)&;(%:)(?-,CCDTNP`LF>C=4C66?11>/2>/2B05C27D38F49C57I;=LCDYOQ^ZYea`iliqtqv{xuzwt|xt|xow{qy|nz}nz}l{}l{}q~xr€ytˆpx‹t|”g|”gš_ƒ›`…™_ˆ›a‹–oŽ™sŽ’„•†ˆŒŽˆŒŽ€Œ‘€Œ‘yŽ”yŽ”}˜{Œ—z‰™yˆ˜t†“oŽk~‹i{ˆg{ƒdxgw~gw~hxhxhw{hw{jwyivxksvempahgX_^RTOGHC<>96830;79C??ROK_[Tnl`zxnƒˆv‹|‘–~“™ƒ•—‚”•„’ƒ‘€ŽŽ‘Œ‘Œƒ‘„’ƒ’ƒ’ƒŽ‚ŽŒ}x„urVesJam;am;hqPnwUuxow{qrsq}rphlzcj{_iy^ixXixXkv[lw]kv[kv[iv]iv]is[epWboVanUYhXZiYZmeh{spˆ„{“†š’„™‘˜Œ—Œ‹–‰‹–‰˜˜Œ–‹•Œ‹“Œ”‘     + + +  + + +            !!""#%%&&&'((((''&&&&$#$#""""!!      !!##"" '%,-73=?IBL"IT NX%U_ V`![h _l#gs!ju$jt"lu$ov%nt$px#ow"kw#lx$dv!ct aq!aq!bp%dr'du'du'fw)hy+iz.k{/dx0cw/\n2Xj.K`/DY(8N-2I')<$ 4/)%#"!! !! " " " " # # # # # # # #"""" ! !" !!!!!!!!!!!!""!$!$ # #!$!$ # # ! !""""!!!'#*%L@BŠ~€s…x…Žyˆ‡rˆq~‘z‡“‚‹|…q~p€t†|ouh|obvh]oh]onixe`ndapRP^6V(=U&S.?W1B[>SkH^vDXp8Kc/9L/9L1;E3>H?BM?BM=?M8;H,6N=H_GYt5Hb!7L!7L$6J0D7FV@O_@M[?LZ8FX2@S%S1EZ5I_BOfEQhDQfFShAPb2@S -= -=:D@=HDJRAGO>BI;@G8NPDUWKFK66;&09"++46@#/709 .4#5;*:=304*.1),.'"' $ ')1)1+1.)(8'&6'$B0-K99T++F23J-/E#/;%1=HALJTLVO]Q^\g^jbkcmjq nt$px#px#o|(q})k}(j{'hw(hw(hv+gu*fw)i{-k|.hy+fv+eu*]q)Yl%N`$IZ>S"7L'>4+'%#" ""!!  !! " " " " " " # # # # # # # #"""" ! !" #""!!!!!!!!!!" #!$!$!$!$!$!$ # # ! !""""!!!&!<71„xz–ŠŒpcp…x…Šu„‹v…Šs€’|ˆš‰’Ž}‡}ozk{ƒvˆ„xŠ€r†uh|zp~s…€{‰vq€fdrVTbCISKQ[SfyOau)Id;V9^xOtŽSyŠUz‹fyƒpƒŽ‘”“®±°¸¹°µ¶¬½À¸ÄÇ¿ÀÀºÄĽÈÄÿ»¹£˜³h]x+K@3`?.g>,f:,c:,c5*^5*^5/T.(MPPW—Œ‹ƒ„|~…‡‡‹““•œKG\'#81.L0-K0-D30G:6@HDNipq†‡hszdpwqyzs||‚†q•š…£«s£«s¥°s¥°s ¥‡}‚d9=J9=J78e01^")c'/i_t®yÉo™ÌdŽÀO‡¹[“ÅožËlšÇ9QˆJU YY Z XVJGC)O(<_/Cf/Eb1Hd1F`-B\,BZ+AY#:V#:V'9V(:W0CV-?S/BT.AS,CV,CV(>S%Q0:M3>H6@J?BMEHSFIVDFT6AX;E]BToASn0F\,CX%7K+?/>M?N^/>@4>@405 8=(5>$3+6 ,81@==@FJ`fihrix‚z}…oy€juzlotfireenafl[ejZej[fk\gk_nrfnvkltjovuovumwuktsgtiergiq_iq_ooaqqcrtasucwydz}h€…i†‹o‡‘mŒ–r“Ÿt˜¤y—§w˜¨x›ªx™¨u›¥z™¢x–Ÿw”uŽ˜t‰’o}‰eu]mxWepN`iO]fK\gLYeJXdIXdI^bJ_cL^aL\_J[]JYZHYREQI=J@;@71?03=.0@/2A03A03B14A35C57D9?J?EOJM[UX`]^jhhrvxy}ƒ…~‚ƒ}‚|€t{rypx€qzozozpz„pz„s‚„tƒ…y‹~„€•q}’o•`‚–aˆ›a‰œc‹–o˜rŽ†‡…‘…‘——}˜}˜x‹˜vˆ•r„“nj}Šhz‡fx…dw„gvgvhxhxhu~hu~kuzirxcpr_kn^deSZ[ONPCBE98:54749==CF@QSPbcWrtd~l…Œt”‘—”š„””ƒ““€ŽŽ€ŽŽ€ŽŽƒ„‘‘‚‹€ˆ‚‰{w~onxTblHZi>_nBeoYmwaqwru{ut~sp{pozfkwcky^jx]jx]iw[gt[hu\iv]iv]jv`ht]erYdqX`nX_mVZkX[lZatlpƒ{xŽˆ•†™‘…˜Š—ŒŠ—ŒŒ•–‘•‘•“Ž‘Œ‘Œ‘Œ     + + + +       + +    !!"###%&&''&&&&((&&&&%$$#""""!! ""$ %*!.(4,70=4A;H;HFOJT#MXQ\!Xb]g!^i!do'ft$kx)iy)jz*jx-jx-jw0jw0ds3ds3`s4]p2Wi4Rd/E[.@V*9N*0E"':%1)#  !   !!! "!!#!#"#"#!$!$!$!$!$!$!$!$!$!$ #" ! ! # #!!!!!!!!""""" #!$!$!$!$ # #!$!$ ! !""""5+.rgkž¤xh_GfzbŠpsƒ˜}‹‚¥Š—‚x‰’z‹Žz“’‡vˆwex~o{l~wg|o„qjyhap`]gQNW\\jzzˆdi|5:N#>?Mh^s†eziz„v†‘“—œ¢¦­­¤¹º±ÀƺÃʽÇÊÀÇÊÀÇÊÀÄǾºµ¶’Ž6/P6/P7,b?4j91k2*d0'b1)c2-^+&W67Pwx‘Œ–€‚‹~}‚„ˆ…Œ–“š‡‡•00=&'@./H),K),K56O34LV[f|‚Œiueq|pv…w}Œ€|ˆ‰…”x”x ¦z¤©}¦£ˆ–”yIEQHDP66]23Y +]+5haw¯|’ÊpœÉm™Æf—Èg˜Éo™Ìl–È4M„HUVVYWWNJA>&D-8V4CY5DZ3DZ0BX,AT,AT*>R'R.AK.AK5AM=IUCN\DO]EPlLWsK`8Nl'AZ%>X5M+B'9M;Ma=I\3?Q&6J!1E2K%8P(9Y0O+D!:88446'>/7H19J%+3%-!')")*,4*.6+5;*;A05;*1DH5OD?ZOHjUUwbZw]Xu\]pY[nValXcnZel^ipaejbimfnvrpxumxrlwqpzjlvgr{`r{`x\z‚^z‚^}„aŠb†g‹“i‘™nžl—¥s—©t™«všªuœ¬w›¨{˜¥w• z’œv—v‹‘p‚‹iw€_nwUgpNaiJ\dFY_GY_G]_L]_L^`N^`N\`O[^NZ]MZ]MZZLWWJTODOI?K@?A66<02:.08/08/0:02<24>24A57C:@H?EPLOYVX`dejnppz}x‚…~ˆ‹~ˆ‹|„‡z‚…z}†x|…pz„nx‚mzƒmzƒnz†nz†n}†q€‰s†Šv‰Œ}’ƒ{~—nz“k€š[›\‡™_‹œc–u“™x‘ŒŽ’‡•…Œ”‚›ŒšxŠ™v‡—r„“nhz‡fx…cvƒbtgvgvfu~gvhu~gt}it{it{eot_in^bfTW[NJPB>C75<64;59A>BKDT[Uek[s{f†n‡uŽ–€’™”š„””‚’‘€€€Ž‘Ž‘Ž‘‚ŽŽ€‹Œ„zsyoipV[bHYbH^gLbn`hsfkxsn{wq}tmypozflxdkwakwaiu_ht]gs\ht]ht]jv`jucisbgs\gs\bqZapY[lZ^p]ewpq„|yŠ˜’ˆœ…™‰˜†ˆ—…š„Ž˜‚™~‘š–œ}•›|–žz–žz     + + + +       + +    !!"##%%&'''&&&&((&&&&%$$#""""!!   !!" $ (' + +". %2 &3 -: .; 8AR'D327&27&=B4:?0/5$(..3%5:+9<247.-/*/1,-0'/2)04(59-59-.2&%''&)(((5$$243JCBY..B&&:%%7();#-9$.:'29%18',9,1=/8F.6E0415&@5)D8(K64WAGdKLiOTgO[nVcnZbmYfn_lselqirwot|xu}yr}wr}wu~or|my‚g{„i‰e„‹h†j‰‘mŽ—o™q–s˜ u—¥s™¨u˜ªuš¬wšªušªu—¤v“ s˜r‡’l€‡fw}\ktSbkJ\eD[dCZbCYaBU[CX]F\^K\^K\^K]_L[^NZ]MZ]MZ]MYYKUUGTODLG32<02:.07./8/0:02;13=13?35C:@I@FNKN]Z]aeflprs~€z…‡€Š€Š€ˆ‹}…‰|€ˆ{‡q|…pz„mzƒn{„o{‡o{‡q€‰u„v‰Œz‘”†}’ƒ˜o{”l™Z™Z…–]‡™_“s—v‘ŒŽ’‰—‰—‚›Œšw‰˜u†–p‚‘k}Œgy†dw„btbtgvgvfu~fu~hu~gt}hszhszdns]gl]`dSVZKGL@$-G3O.;D-:C,7A1;E29F3:G/9LEObAQj0AZ/C^4Ic7I_$5K/-59-26*213,+-/.20/42.83/9/*9.)8$!/.,:*+= !3#"9#"9'';!!5*(;64F>>K<;I30@+(8+!>5+H92N:3O:6P73L-/C')>&5>:IR;ZO?^R0dE4gHGoPRzZhz`l~dv~hzkzƒv|„x|„u|„u€†n‚‡p‡n‰q‘o’•r–™t˜›v˜žr˜žr˜ t˜ t—£x™¥z™§~—¥|—Ÿ~’›zŒ“x†s|…kox^coRWcFP[AQ]BT\FW^HZ^KZ^K[^N[^NZ[RZ[RZ[RZ[R\ZS\ZS[XR[XRVTMTQKPKEID>D<9;306/05./5-16.2603714;04?59F=CJAGKKRWW^ainnu{s…zˆŒ~Œ“~Œ“~‹”{ˆ‘w„s‰r{Šqzˆkx†lz‡m{ˆlz‡rv„’u†–y‹š{—y–}y|}•b|”`šV†Ÿ[‰™bŒœe’˜~“šŽ“”˜‚Ž™x‹˜t†“l‚“f|cyˆ_v„cs€cs€ftftfu€fu€ftftfs|cpzbjt[cmZ\cPSYIFN=;B6594271;E?ISDZdSis\vj„q‰’v˜€“—€“—ƒ‘–Ž’€Œ‘€Œ‘|Š|Š}Œ~‘}Ž’}Ž’’’…Œ~…†w{ukoj]`RSUGLUHS\OU`aallcsyfv}jyuhwrjvgiuflv`ku_jt\grYgsXitYfs_hvagwcgwcfx^du\ar[`qZ\o`btfi~{v‹ˆ€–’„™–ˆœŽ„™Š‹ž{ }˜§s—¦q—§p•¦o™¥o—£l™Ÿj–h                      !""##%%#'&&&&&&&&&&&&%%&%$#""        ##$ $ + +, 0 #5-9/;4B 9G@OBQHWIYGXK\J[!FW?R>Q7J3G(=4*' !""# # # #  "!#"$"$$"$"$"$"!$!$!$!$!$!$"$"$!$!$ # # # # # #%%%$ # #"""" # # & & & & & & & &!&!&!$!$"&"&%)-IMQ379!!!! !+$,E>EXDXyex Ÿœ~œžƒœfy`m~er–x†§‰—®Ž£ž~“ž}”¡€˜“{”ŠrŠ†sŠŒy‹w€m…uh|xj~nguIAP61@PKY>4CcYir‚‰zŠyr…}wŠah{GNbMXhZeuxˆ†–šŸœ¢¨¤¬´ª¸À¶ÃË¿ÃË¿ÅË¿ÃʽÉÇÁ²°©pm„%"9!P'#V(#a(#a-(f/*h/(d/(d/*a2-eZXsŽŒ¨Ž—€‰zx}{y~…„‰”“˜‰‡•:8F.95HTTbu|‹szˆnzt€“|†|†‹Ž…“‰œ—}žš¡žx¥¢|£ž„¡œ{x€B?G25U,0O&1a-8h^t§yÂt”Èt”Èl™Êl™Êm”ÈhÄ3mV"^Z[[[[WRIG<8#=-7P/BT3FW/EV/EV,BS*?P+@Q-CT3AU4BV4AQ/HR:AN29F*4G/9LCTl3C\-B\4Ic7I_ 2H+9M/S4BV4CY7F]>QwCV|Qp?SwD3@E4AF6:?/>B66:.324-,./.2-+0-*3/,6(#2)%3)&4*'5"#5 !34! 7&&: 4$#5)':,,:++941A1->0'D5+H1)F4-I95O@C;8:2.6/05./4,05-1603714>48B7;C:@MDJPPW\\c`gmkrxs…{‰ŽŽ•Ž•Œ–}Š“w„s‰qzˆpy‡kx†lz‡lz‡m{ˆrv„’w‰˜{Œ›{—y–€”ƒ|~}•b|”`šV‚›X‡˜a‹›d’˜~’˜~‘•‘•‚Ž™˜w‰–r…’i~e{Œbx†^tƒbrbrftfteseses€es€fs|cpzbjt[cmY[aORXDBI96=3164271;E?ISE[eTjt^x„k…‘tŒ–x™€“—€“—ƒ‘–Ž’€Œ‘€Œ‘~‘~‘}Œ~‘}Ž’}Ž’’’„ŠŒ{‚uytimg[^PPRDIRFRZNVabbmmdtzgw~ixsgvqjvgiuflv`js]is[grYgsXitYfs_hvagwchxdgy_fx^cs\ar[`rdhzll‚~zŒ—“‚˜”„™Š†›“¦ƒŽ¡~“£n’¡m iŽŸh’žh‘g•œg•œg     + + +       +    !######$&&&&&&&(($$$$%%%$$$""           ""##%%$'+,$1'3+8.;4B 7D"5F!5F!4E%3D$*>$'<""40'#!!#$#!""# "!#"$"$"$#$#$$%$%$'$'$'$'!$!$"$"$!$!$!$!$!$!$ # #%%%% # # # #%%%% & & & & & & & &!&!& &$$%#' %)"#! "G5:aPUrRg’r‡ŸŠ¢…o‡|™|w“JAcA8YWCckVw§…¡Ÿ|™—t’ }œž|˜w—•›Œx’|l|lo„{k€{m‚uh|eWlUG[?-BO=R‘zƒl‚†l}”z‹”„’|lzMO^UWgblxyƒŠ”™›¤ª­¯ª·¹´ÀõÆÉ»Ç˽ÅʼÄÆÁ«¬§_`y7!M$"P%!['#]*%e*%e)#d)#d.,i+(f01W``‡‘¡ƒ„”~}‚|z~}‚‚€…Ž‹’ŒŠ‘.-;##1]an}‚p~l{j~’o„˜z‡•z‡•Žˆ’‰žyŸžz¡¢r¡¢r¡ yžžw‡‡SSL8=W05O$0^,8f\r£u‹¼o‘Âq“Äm˜Èl—Æn•ÊfÁ&:wW^_`a``ZYMI>;8'0L.?W4E]2F\-AW+?U(FK9=H*.8 3!4LXq?Kd0AZ5F^4AX$;$.E+5L,8R-9S+<[9Ji9Oy?U9S6O}6P|;U€=V}?X€?Y~?Y~?X>W€8S}1Lv*Dk4\-I)F)>)> 1)' +)++--,-* ,$*$* )#)2,2<$7A)AK,DO/>J(>J(IR7IR7DH<=A588=126=8H5/@<9JGDTLHT>:F43=-,6*)2$#,%&1$%0$$2%%3''3$%0)(1&%/.+4;8A@9H6/=91C;2D1(>2)?7.I@7QBE2.-820_A8YO:[o[|¸•±µ“¯£žŸ|š¥ƒ¤ªˆ©ŸŠ¤œˆ¢…uŠ|l‚r†…uŠsˆˆz€r†tf{K8M@.ChQg‡o…‹p‚‡m‘“ƒ‘ghxGIYT^jdnzyƒˆ”£®°«²³®·º¬¼¾°ÂǸÅʼÀÁ¼ ¢@@Y5#!O$"P'#]'#]& a)#d*%e'!b(&d*'e..U89_vx‡Š‹›…„‰~}‚{y~yw|~…Œ“33Aot}‚l{jxŠk€“r‡šz‡•t‚‹„“ŠžyŸžz££s¤¤t¢¡{¢¡{™™’nng<@[9>Y%1_*5c[q¢rˆ¹p’Ão‘Âh“Ãh“ÃiÅd‹À,@}W^``a``YVMK><5(D,Nb>Nb?M_;J\;IVDR`IW\ETXAHN29>"%0( *.GSlHTm3C\4E]1>U25$;)B$0I6Gg?Oo9JGARIFVOL\HDP:6B"!*%$-*)2''0 , ,""/%%3((4!!-"!*,+4DAK74>4,;0)8-%6JBSQG]D:P90J/&A-(?50G65L99O,8?#.6 927PJ?`M6XEYs]˜‚”¡‘ž’ž|‘œ{•œy“šw’™v˜t“t“t‹‘p†Œl†‹h„‰f€‰j€‰j}†lzƒhyinv^hpZ]dNQXCEM7>E2D8BI0.:31=0;J>IXF\kUkz^{Šk‡—vŽšyœ€’š€’š“~‘‹‹|Š|Š|Š}Œ}Ž’}Ž’’€Œ‘‚‡‹x~‚qssbddSSSJJJGKTQU]Ucn`ozdv~ewixsgvqjvmgsjlsekrciq^gp]ir\js]hqdireitgjvhivdivdeu`dt_byel‚oo‹{|˜ˆ¦„‘ªˆ›ªx—¥s•¥i“¢g™Ÿl”šg“—i”˜j›g‘œh’Ÿj’Ÿj            +   !####%%''' ' ' ' &"&"&"&"$$$$$$%$#"""!!!!      !!!!"#!!!!!!##''''&&#" ! ! "# $!$!$!$! %" %"#" $$!&%!&%"($"($"($"($$%$%$%$%$'!$#&#&$'$'"&"&!$!$!$!$ & & & &%%%%%%%% & & & &"'"'"'"'"'"'!)!)!)!) & &$$$$ "!#)$%GBCV@F\FL–~”¡Š ]VrE=Z53a1/];8]EBgf[{”Š©¿®Çº¨Â²º«–³£’°­œº •¯—‹¦‡¡‡~˜|k‰|k‰tcud‚k`{pe€GAY.)@4*@C9OmWj•~’¢†–›†zˆmboKL\RScrvƒ‹¤¦¨«­¯µ²§µ²§¸µªÃÀµ®­½yxˆ%"E?'!O+$S,$W+"V'![*$^*%d)$b&'f!"b$%`((c=@fwz Šˆš€‘vz‚vz‚rs~~Š‰Ž›oty’qyŠat†gzŒi‚—m…šq€kzŠ‡Š€Ž‘‡œ›uœ›u¦¢w§£y¦¡‚¤ž›•‘‚}yFCaC?^)0f,3hTk o†»pÁn¾i¿i¿hÀdŠ¼8M‰ Zcacd d d][MI@<;%H9KnRc‡XpŽWoTj€NdzH[lCVgBPd?LaMUmT\tQ[lJTe:FhBR{IY‚=U;T~396+E<=RHJQIXPHWI?Q8-?/'?0(@0+G0+G,4G")<2<2FP=XRRmg†¢€Š¥ƒ’žz‘y™w–u“sŒ’q‡Œp†j{€fw{av{^v{^twbwydsxhpventcio^fgY\\NWREKF9B@:A?8AB?EFDNNHQQJUVMUVMUVMUVMVWPVWPXVTXVTXVTXVTTVQTVQYXTXWRUPQOJKMEGF>@A87;213.12-05./6/0723834945@;.0=/1?1=M?K[F]pUld~‘n‡šx›z‘™}Ž—|Œ“{‹’{ˆ{ˆ{ˆ|Š}‹’~Œ“}”}”ƒŒ”Š’ƒŒxz‚rnub]eTOWMHPLOZWZeRfu[o~`x{`x{kwuhtrlumisjjsfirelpdlpdjogjoghogiphjujjujhwgdrbbt_dwafdqŠpŠ¢w“«€ªv–£o–Ÿp’›l›n’œo’žl” n”Ÿp•¡q–Ÿp–Ÿp•›t”šr           +   !!  ##%%&&&&' ' &"&"%!%!$$$$$$$#""""!!!!!!  !   !!"#!!!!!!######"! ! ! ! # $! %" %" %" %" %" %"#" $$!&%!&%"($"($"($"($ &($%%&%&$'$'#&#&$'$'"&"&!$!$!$!$ & & & &%%"'"'!'!'!'!' & &"'"'"'"'#(#("'"'!)!)!)!) & &%%$$!# "$/*+UPQT>Cxbhšƒ™‘zB;W>6S1/]GEsjf‹}¢’±¨ž½·¦¿¾­ÆÀ«È¿ªÇµ¤Â¯Ÿ½£˜³„y“‰€š† „s’tco_}raui„thƒWQi4.F8/E0&<6 3N7K€dtŒoŠŒ†zˆNP`SUdglyz‹•–˜£¤¦²¯¤®« ª§œ¬©žŠ‰™EDT!A)'I)#Q("P( T( T"V% Y"[% ^"#c!"b((c45p;>dSV|ˆ‡™„‚”vz‚tx€stvv‚~ƒŠœ€ˆ™qyŠbv‡i}Žk„™g€•pm}Œ‹Ž…‘”‹——pœ›u¥¡v§£y£~—x’‰‹†‚GDb<8W+1g3:oKb—j·kŠ¼oŽ¿i¿f½e‹¾_…¸DY• !]ca ed dc[WMHA?K`=J_HOgKSkKUfEP`58J-'+(+1S437:FhU`ƒO_ˆO_ˆB[…Ha‹VrŸSoHdB_Š>\‰:X†8[‰=_Ž9c0Z‡.N{"Co;a5\3O2N"5K2H0A,>*:(9"+9 (7%3!0&  #("#$$-&83J8PI^&Uj2_m;N]+FM3GN4OOOKKKVPa`ZkYTkPJbIGU@=K+(/$308109$#, '##*!%&"$! -7,p{p”œ‹‘™ˆŸœ‘‚kabJ@B81@E>MNDUNDUKC[H@X/*F)$@.5H07J&9C/:%@9>FCFQMPPV^Y`hbnzmy„n‚‘s‡–vŠ™xŒ›v‰›t‡˜q‚”n~iyfvŠgx‰gx‰hyŠm}o‚”r…–yŒ™|Ž›}˜{Œ—y‹“x‰’|Ž‡|Ž‡z–mw’j{›X{›XƒœZ…[Š”p—sŠŽŽˆŒ†‘|‚Œp{Šjv„et„cr‚bqcr‚co€co€dqdqdqdqan~^k{^erW^kVT`JIT@;H62>/1?57D6CSDQaH_rWo‚d~‘n‡šx›z‘™}Ž—|Œ“{‹’|Š|Š{ˆ|Š}‹’~Œ“}”}”ƒŒ”Š’ƒŒwxpksa\dSNVPKRMP[WZeRfu]q€d|ay|kwuhtrjtlisjjsfirelpdlpdjogjoghogiphitijujgvffuebt_bt_p‰n‹¤‰–­ƒ§}•¢ni˜i‘šk›n”Ÿr” n–¢q•¡q•¡q•žo•žo–u–u   + +         !! "###$&&&&&&&&&"&"%!$ $$$$$#"!""!!!!!!!!!!!!    ""#$'# #  !!       !            ! #"#"#"$! %" %"$! %" %""($"($"($"($"($"($"($"($"($"($!')!') &( &(&)&)$'$'$'$'$'"&!$!$!$!$ & &"'"' & & & & %!&"'"'"' &"'"'"'"'"'"'"'"'!)!)!)!) & & & & & & !&""'@38cV[qhu…|‰ge~ge~36W)+MXTtŒˆ©™µ–Ÿ»ž¬Àœª¾œ£·Ÿ¦¹£¤»¬­Ä¥­Ä¥­Ä—®ÃxŽ¤]rŒf{•sxžqvœnn’sr—sz›kr“GVz?Nr,:a(7]+5L,6NG=Ntj|†t€Šy…tiv`Ubdcusr„‰…˜–’¥œ ª™œ§” …Š—gb~83O,#M0&Q*"O-$Q0$T5)X7+\7+\,&Y*$W%!['#](&f/-m/1n8:w^a‡}€¦u}lt…kq€lsqzˆ|„“{‰iv‹^q‰dwe~˜b|•q€hw†ˆ‹‘”‹‡Šlz}_ˆ‰f  ~¢¡ƒ ž›Œ›™‰`_v76M,3h5V`x²jˆ¼nŒÀb‰·[‚°fˆ·b…³QgŸ%\\]!_"a^]VPIDA =$e“;b4_‡(S|)Lr$Gm.Cb-Ba,=S"4J$1A#/?#,:'/>%-7$,6$)4(#'!)!)$!%"32H)Xo2`w;f~6Xp(J`Qg&Sg-au;myNiuJut]sq[qegk_abQZL;D8)48)4/&.2(1"#%!## %+(-9)0<-1D4H\Kdmiƒqz“xˆ¡†‹¡‡~”zfshCPE6;A?DJKKXLLYFIV/1?)*<./A67I44F$+8*1=@XM\ti•„~‘€z„nw€juxhpsckn`hj\gh]fg\gh^hi`jjdmmfmmdnoeprfik_dcX\[PTPFJGACCJKKRNV`[cmaq~k{ˆl”q…™r‰œsŠq‡m„™j~“i|’gw‹fvŠfvˆct†hyŠm}n“s†—w‹šzŽ{šyŒ™y‰–y‰–|‰’{ˆ‘{wŒ}{—gy”e€›Y€›Y…›_‡ža‰•q‰•qˆ‰……‡‚v{ˆrvƒdsƒbq_n€aobp‚bp‚bp‚bp‚bp‚ao^j|\hz[apV\kSS`GGU@;I61@/3C6:J8FXFTfJcvYr…b~’mˆz‘z‘™™Ž•|Š}ˆ}ˆ{ˆ|Š}‹’~Œ“}”}”‚‹“€‰‚‚‰ww}niq_ZaNMVMLUJTeS^nSk{Zr‚ax~^uzlwqhtmmsilrhiphhoglnglngjmjjmjhmjiokjsmjsmgvfdrbg{\”v›­}›­}Ÿ¨r™¢l’šp–l“sŽ•tŽ—v‘šx‘žw‘žwœx›wŒ˜všx’›— … + +       +  !!"####$&&&&&&&&&"&"%!$ $$$$#""!""!!!!!!!!!!!!!!    ####)+6$&"  !!                       "!! ! $$ $$$! %"!&#"($!&#!&#"($"($"($"($"($"($"($"($"($"($!')!')!')!')&)&)$'$'$'$'$'"&!$!$!$!$ & &"'"' & & & & %!&"'"'"'"'"'"'"'"'"'"'"'"'!)!)"*"*"'"' & & & & !&""'G:?~qvvƒf^j53LFE^.0RGIk‹‡§ª¦Ç¦Âœ¤Á˜¦»œª¾¤«¿¡¨»£¤»®°Æª²Ê©±Éš°Å¦»{ªz©†¬y~¤|{ ~}¡|ƒ¤gnRa†P_ƒDSyJYGQhR]tVL]_Ug„r~ˆv‚‚w„h]j`^pnl~zv‰‡ƒ–ŒšŒš„‰–lq}?:V61M,#M5+U/'S.&R0$T6*Z>2c?3e?9l93f.*d,(b$#c$#c-/l79v>AgbeŒ€ˆ™px‰mtƒkq€ktƒqzˆx…šr€”[n†Xk„[uŽ^w‘et„WfvmpgŠ„}cehJ}}[ž{¨¦‰ª©‹¦¥• ŸvuŒ76M%,a07m4L…cz´kŠ¾h†ºV}«Gnœc†µc†µWm¤)aX[^^\XMLFE>"LPh^v›Mq›Ko™An—An—7c‰*V}#KnHj"Ik@c7X2T(L)M*K+L)D%@#=#=!.JR_{AQg6E\8EZ$8)BQ]wPa€XiˆVle{ži‚ªc}¤Tv Qrœ?i•U-:J'4D)2@+4C*2<)1;%*5&#+ (!)" , 5K,_v9`w;e}5Zs*Wm,Wm,au;p„J|ˆ]Š–k‘yŒ‹tˆ|~†z|Œ{„„s|hYd=.9+!*/&. "$!(-*:@=8D56B2*=-/B13N<3N4E]Rgzi}xkmwaorbjn]hj\gi[gh]gh]gh^hi`jjdmmfopgnoenodkmahg\]\QVSHNK@BB;CCAFFLOOVQYc^fpbrl|Šl”q…™r‰œsŠq‡lƒ˜j~“fzfvŠdt‰du‡du‡izŒn~p„•uˆšw‹šxŒ›yŒ™x‹˜y‰–xˆ•|‰’|‰’zŽ€wŒ}z–fx“dšXœZƒš]…›_…’m‡“o…‡‚‡ˆƒy}Šrvƒdsƒbqao_n€aoaobp‚aoao_n€^j|\hzZ`oTZiPP^EER<7F61@04D7;K:I[HWiLfx[u‡e€•n‰žz‘z‘€’š}Ž—”|Š}ˆ}ˆ{ˆ|Š}‹’~Œ“}”}”‚‹“€‰€€‡tt{jem[V^MLUMLUJTeT_oSk{Zr‚ax~^uzkvpgrlmsilrhgnfgnfmohmohjmjjmjhmjiokirlltndrbo~n‹Ÿ€•¨Š—ªy‘¤s— j”žh˜m–lŒ’q“s–u™wœuœu›w›w‘œ{“Ÿ}–Ÿ„’›   + + + +       ""$$$$$%%&%%&&&&%$%$$"#!$$##$#!!!!      !!!!              !!!!!!""""  ! !" $!"# %%!'"!'""(#"(#"($"($#)&#)&#)$#)$#)$#)$"($#)&"($"($ '& '& '& '& &( &(&&$%$'$'$'$'"&"&"&!$"'"'"'"'!&!&!&!& (")") ("'"'"'"'$'$'$'$'!)!)!)!)!)!)!$!$ & &%% '""(:8Fjgu;=Z01N+-Q13WEB`ƒž¤«Àª°Æ—¯Ä•®Ã¤·¤·¤º¤º¡µŸ±Å¤´É¤´É•ª½‹ ´‘£·“¦¹š¸Ž¬}¯~‘°sŠ²Vm•Ka’;R‚@W†KbC_…B^„K_uJ^tgmwzŠˆ€‹ogqUXiehzjo‰rw’u€—v˜hsŽ=Hc:A1'R8.Y5$W6%X5(W4&V3)V3)V<4`7/\0.\:9f#&Z!U$'d*,i59o.2hhp‘z¢p{‹hrƒgo~nv…iv‹r€”i€šYpŠXnŠXnŠak~DNa^aW~‚x}~grs\„€q™‰¥™”¦š–¢ž¢žŒŸJK](+]+/`?QŽqƒÀx”Æt‘Ã`…³Ch–W~¬aˆ¶Ws§,`VVYYVQHGDEB#5_Jr¢\…µ7r¢-h—-l)h™'g–%e”X'k”B}¨2m—*Ow)P6]/V'O(P'P'P'O$L+Q=Pv13:%'))"1//)''>L7Xn4Yo5^q8Ob(Ph;Le8RqXu^Ws]QgTKbNGaQNhX_wqSlf=ZZ-KK-GE(B@=JL>KM'5;*8?E]Rft|Š|jwjkoain`khdjgcgh^ff]ff]hi`jkbmmdnodnodoqekmabcZZ[RWSKNJAGGADD=DFAIJFMPIOQJQTKQTKTULTULUUPUUPVTTVTTVTTVTTTWTTWTTUSRSQTOPNIJE@A>9::346/02-.1,-4/24/22/26359:>@AEBHPKRZP[i[fubr†k{j€–o…šsŠŸsŠŸn‡œi‚—f~bzŠ`s…`s…at†dwˆjzŽo“r„˜v‰œv‹Ÿv‹Ÿw‹švŠ™x‡—yˆ˜y‡›w„™zŠx‹ˆ|—nx”k|—Z}™[…œY„›Xƒ˜aƒ˜a…‘rƒq}€„wz~ks„go€am€am€anƒ_m‚_n€_n€^m}]l|ak~^gzZ^nRVeOP[DDP77D22@/8F7@N;MaHZnLg~[vh„–pŒŸz’œ{“€’™~–€‹“~‰ˆˆ{ˆ|Š{‹’|Œ“}”}”ƒŒ”ˆ€}„tqyg`mZT`IJZLN]FXgQbrXo}^tƒhw{ftyltphpmhmjglijmjjmjjmjjmjhlmhlmfkohnqbpgdri|q£…š«y”¦sš¡r˜Ÿp—œr–›q’št™s‘•u‘•u‘˜|‘˜|‹˜}‹˜}Žš„™ƒ˜„›‡Ž—Š–‰   + + + +   !""$$$$$%%&%%&&&&%$%$$"#!$$##$#!!!!      !!!!              !!!!!!""""   ! !!!!! $! $!# %" & & !'"!'""(#"(#"($"($#)&#)&#)$#)$#)$#)$"($#)&%+(#)&!('!('!('!(' &( &(&&$%$'$'$'$'"&"&"&"&"'"'"'"'!&!&!&!& (")")")"'"'"'"'$'$'$'$'!)!)!)!)!)!)"&!$ & &%% '""(2/=MJX57T12P01VCDhnk‰›˜¶±·Í°¶ÌŽ§¼’«À˜­À‘¦¹”¨½•©¿”§»¯Ã›«¿™©½¡µ„™­ˆš®“¦¹”¡¿†“±~‘°z¬m„¬Pg?U†>T…?V„E\ŠGcŠ@\ƒK_uQe{ms}rw‚xpzy„UXiaevjo‰rw’kuŒmwVa}!,G8?+!L2(S5$W6%X5(W4&V2(U2(U4+X80]53a75c&)]!$W!#`$'d/3i27m5<]jq’|†—p{‹ir€jsiv‹q~“m„žo† \sNeIReCL_X[Rw{qˆ‰ry›—ˆžš‹œ‹›Š™””œ—˜‹ŒžZ[m)-^)-^'9vn€½›Í€Ïm’À,QT{©cŠ¸[w«*^VXYVUUIIJL!L*G:=C/18.#20/))!/BO;Vl2Yo5Rf,@SC[.Og:Mm8Mm8Vs8dEwŠ^v‰]w€gw€gwvrtsnnciodjtdmbQZECC@>>/82%3"#9(5 ) (3=YBrŽw–‚…œˆ}–‡z”„k„~Qic?]\3QQ1KI4NLERTY‚B]†Ig†Rpcyˆl‚‘~„‡v|bds^_oaj†lu‘hq™dn•GQxC8= D*"O9*\:+^<,\9(X1&S0%R-$Q)!N1-[95c11e !UZ""]//j45p+.[@K);F%>L0N\AOfDNdCNh=TnCj‚Uwct†cj}Zdj^el_nkliggkba~vutf`OA:63('$#+#+"74./4:W>su™€‡˜|‚’vZhKVeGj}^u‰jnƒ`_tQO_=@P.5#:9$<:\qiv‹ƒ{ˆ}hujflbdj`fhadf_gh^ijafkdimfloekndnqhkndgh^_`V[WNRNFIIBGGAHIEKMHMPIOQJQTKQTKSSLSSLSQOUTRVVVVVVVVVWWWTVVTVVVVVRRRRMMKFGB>>;77:4281041-41-3/.620747747:;?CDICIRNT\P_o\k{bug{“j›n…Ÿn…Ÿm„žkƒ›g–cy]t‰`r†`r†bu‰fxŒh{Žm“q…™uŠv‹Ÿv‹Ÿw‹švŠ™xˆ•y‰–w†–w†–x‡yˆ‘x‘}tyz•_{–`|›T|›T‚›YƒœZƒ’f„“g†w{‚tov~kqyfo‚cm€_m‚_m‚_oƒ_oƒao]k~]k~ZhzW^lOUdKKW?@K77D54B19J,4G+3X7?d_f‡y€¡ˆ¤®´Ê­½Ë­½Ë‰¡¶tŒ¡¯Ã¢µÉ±»Ç³½É¼ÃÉ»Âȵ¸Ê®±Ã¥¦ªœ¡•——˜›š”žª‘š¦}’³`u–Wo D]=PŠEX’:N†;O‡OQQQ[[[e––z¢£~©ª…ª¦§£ š™’Œ‹nojk};?i,0Z,j4Ag}µ{‘ÈwšÈo‘ÀHj Vx®4p*f(f!)h(*g*,i,*f+)d#(^ $Z %Y TOM_•w¬Ór§ÎQªËI¡Ã2Äz¯(…½%‚¹z¬v¨]“O… 8j/` "N L I#N+V,W'Q(S&P1[;SD\‡E`‰LgNo“^€£_€¦b‚¨]„«a‰°l·p’ºV{ 6\.V{6^ƒ4_†6bˆ6c‡4`„5[~5[~4Sw ?c *N,Q#?_*Ff0E]0E]7G[6FZ:DP6?K/4:#!'!.&7 (9&6#4*11J(=J/FT8McBNdCQk@Mg.+ +($, ).2/,,9 ^y`v‘x|Œp}q~Œon|^n‚c{Žp~“p–s…•s„”quiBO7,9.6C8(?7$;3'><)@?5JBOd\kxlq}rjoeflbeg`gjbgh^hi`fkdimfmpgmpgnqhhlbgh^_`V[WNRNFKKDIIBHIEKMHOQJPRKQTKQTKSSLSSLSQOUTRVVVVVVWWWXXXVXXTVVTTTQQQRMMKFGB>>;7792160/62.62.40/731969=:FWDUmRc{Wqˆd~•nŠœt¢{“|”ž”š~–”€‹“‚‹“Š’|Š}‹’{‹’|Œ“}”}”€‰{„Œwxijs]YcPMVGL^NSeM\sXg}[oƒ]r…gu{eryipognmhmlgkkhmlgkkijlgikgok_gcm}oŠ›—«ƒ¤|•¤r’¡n’œo’œo•›t•›t”—y”—yŽ‘z‹ŽwŒ“‚—Š’™——Ž•Ž••’•’Ž“’‹     + +    !!  !!!#####%%%%&&&&&&%%##""""#"!!!   !!""!!!!""""!!!!!!!!  ""     !!!!        !!!!!!"!""!"#$ ""## $!%& ()"+"+"+"+"* "* #,$- $- #,%.!%.!$,"$,"%-#%-#%,&%,&%*)%*)")(")( *( *(!'*!'*&)&)%(%(%(%($*$*#(#("'"'"'"'"'"'"'"'!*!*!*!*"'"'"'"'"'"'"'"'!)!)!)!)!)!) ( (!&!&%%(!,%-D.6N_2Ed1Dc6Fa7Gb:I[1?Q!*8 $ + +$)-$5(:&9+> ,?+4C8AODMNAJJ8A4)2%-7,3=2:G:BPBIW@L[DJ^=QeDSkEVnHN_ARcEX[RAD:8191*23"-=+7R<@dOR]HB@+%.(.(%/$.0/36S%IkDStMlƒip‡ltƒjv…l{‡j~Šl‚’k€hu‡YgyKpzVakGOUB5<(9<9?B?5>?5>?)CA'A?.RE.RE1VG>cTLf[Qk`]i`^jaanccodenffogjqignfch^\aW_\QURGIKDGIBFKCJOHRRKSSLSSLSSLPQNRSQTTTTTTRUTTVVVXXWZYVXXVXXVVVPPPPLLIEED>=?87;53:42:42:42723945<9:B?@EDIJHMJPZUZeSdu]n€cvŽj}•h‚›l…Ÿl…Ÿk„ži€šcz”`v‹\rˆ`u‰`u‰bvŠezf{Žk€“o†™r‰œvŒvŒwˆšv‡˜u…—v‡˜u…™u…™w‡”xˆ•vŒ†q‡v‘hv‘hz˜Zy—Y{œU{œU}–`}–`ƒŠv~…rux|pswblen_m‚anƒ^o…^o…]m‚\l€ZfyXdwW^kOVcIHS;:E22@66C3RˆG\‘Pf’Uj—Rm\wšm…˜v ž§¨ª²³²µ¹ilp/>UN]tR^…P\ƒ1;` +971:<3'RE8cB4fA2e6*Z5)X3)Z0&X)"W'!V)$[% X!]$!_##`**g10g..e.4Ygm“oz”‹¤y‡›m{jvjvnx’u™…žiq‚AAHJJQzzmŽŽ€Œ‘u‹sˆe‡Žd”t›Ÿ~˜•Š€}rniƒD@Y(.h%*d 3tNa£eƒ¾g…ÀGcž7r*o$i"h e gec` ZT+0fMRˆ5Fxev¨ƒ®Ës»D•Î-}·z³·{·\˜MŠ@}m“@j9cˆ;cˆ:b‡7c‰8d‹9j&2E*6H(4G*6H2;J3 =cz_o~etƒjy…h‹ns‚\]lEWi;gyKr|Xr|XˆŽ{lr_363CGD5>?-67+DB+DB(L?*NB+PA/TE=?87=76<65;53<65;77>9:?==ECCHFKLKPOT_W\gUfx`q‚dwj}•f€™iƒœiƒœh‚›g~˜by“`v‹\rˆ_t‡_t‡bvŠezi}‘m‚•p‡šsŠu‹œu‹œwˆšv‡˜u…—v‡˜u…™u…™xˆ•xˆ•vŒ†t‹…w’jv‘hy—Yw•Xz›Tz›T}–`|•_‡t€†svy}orvencm€anƒbo„]n„]n„_oƒ]m‚]i{VbtT[gLS_IHS;:E22@66C6@SEObD\sQi€Yvfƒšo‹s¡}“}“‚’™•~Œ“}‹’}‹’}‹’}Š“~‹”Œ–~‹”{Š“{Š“~‡Œyƒˆuu~ihqVU^KKTHMaQVjPayYj‚\q‚]rƒeq{boxipognmelkckihkodgkaifemi|Žt“¥Œ™¬|‘¤s•¢n“ l—o™ q™œw™œw’–x’–x’“|””~‘“…—™‹”˜‘‘––—‘•’•’•’•’•“•“Š‘†ŽŒ        + +      !!  !!!#####%%%%$$&&&&%%### # ""#"!!!   """"""!!""!!!!! !! !!       !!!!!!!!!"!!! #"#"## $!%& & (") * !)!)"* "* "+$- #-%/ '/#'/#(1$(1$'/$%-#%-#%-#(.)(.)'++%*)#+)#+) *( *(!'*!'*'*'*&)&)&)%($*$*#(#("'"'"'"'"'"'"'"'"'"'"'!&"'!&!&!&"'"'"'"'!)!) ( (&&&&!&!& & &("-+5LR]tb{›r‹¬jŠ¥„¤¿“®Å‚œ³ƒ ·”°Çt™³Gl†Ko…o“©‘ª½›µÇµÆ›³Ã›³Ã•­¾“¤­Œ¦‰”•|ˆˆ~ŠŒ˜z¥l—dw¢g{¥`r¡Qc’I\‰EX…Kd‹OiPlŽZu—g‚—hƒ˜Ž˜›¨²µ··¹Ž1;NMWjFPwENv-5[>4!:76& E94Y*"O/'S:.`7+\4)]1'Z+"W-$Z*"Z)!Y% \% \$"]*(c44j55l14_ko™]jˆgs‘y‡¢vƒŸl|•gxjvp|•z‚˜ˆcgt>CP^`bz|~~ƒ‚v{zaseUhYctVx‰k‘›~‘›~†…ƒ]\Z12X01W"(f-3r@K–OZ¦09… )u%*m %h"c!b d d^]TLM=HyH\cw¨ƒ¥Ín¸UŠ½Cy«1p¨.m¥!aX”G :r +0c /b.\"P !F +E !B "C)L3V =a)Fj'Do'Do2`5c%=n,Du)Hz.M~Di—Y~¬X‚­Ku K{¥K{¥@tž6j”1e0dŽ4g“5h”j5a‡)Gi#E 8 ; =#C(9Y4Ed8Hc5E`1:T'A1.,-1#5#0E0=R0BX1CY1EZ1EZ6CX-;O1;G2;7@=9CA?GFDHLNMQSNV`W_iViz_rƒazf~“f€™h‚›h‚›f€™f}—`w‘]t‰\rˆ_t‡^s†dyŒezi}‘m‚•qˆ›sŠtŠ›tŠ›v‡˜u…—u…—v‡˜v†šv†šv†šu…™tˆŽq†ŒvŒ{t‰xs‘cs‘cy•\w”[z’^{“_ˆt~†szwuszgo€go€am„am„bo…am„_m‚^l€ZfyS_rQYeIP]GEQ;:E11?88F6BUFQdF^vTl„^{’g„›pŒŸs¡|’œ}“ƒ“š€–”~Œ“~Œ“|Š}Š“}Š“~‹”~‹”{Š“z‰’|†‹u„rp|camRQaIIYDMbMVkMcyWnƒ^q‚_rƒgq{cnwhnqejnejncimelkbih`qZ~x›­}˜«zš¦t–¢q˜žr™Ÿs—žv˜Ÿw–™{–™{“~“~Œ„‘•‰”˜’’—‘˜’Ž—‘Ž–“Ž–“•“•“Ž–”Ž–”Ž•–Ž•–Š‘†Ž        +   + +      !!  !!!#####%%%%$$$$$$%%##""""#"!!!   """"""!!!!!!!!! !! !!     !  !!!!!!!!!"!!! #"#"#$!$!& & ("(" * * "* "* $,"$,"$- $- $.$.'/#'/#(1$(1$(0%(0%'/$'/$(.)(.)'++%*)#+)#+) *( *(!'*!'*'*'*&)&)&)%($*$*#(#(#(#("'"'#("'"'"'"'"'"'!&"'!& % %!&!&!&!& ( (&&%%&&!&!& & &("-3=UhsŠu¯˜¹t”®oªy“ªw’©›²Š§¾zŸ¹cˆ¢e‰ž}¡·•¯Â ¹Ì§¿Ï™±ÁwŸ„œ¬‘£¬Œ¦ˆ“”|ˆˆz†‹ˆ”™uˆ¡[n†^rœZn˜Vh—Xj™Th”I\‰NhUo–[v˜d¢r¡r¡“ ¨²µº¹»«ª¬>H[BK^>Go=Fn%-S 90.2J32P<>>"G'K8,^5(Z1'Z1'Z,#Y.%[)!Y*"Z&!]#[! [&$`,,b33i/3^lpšboTamz•~Œ§sƒœk{”kwiuŽs{‘{ƒ™ƒˆ•UYfJLNbcehml\a`NaRNaRTeGctV„Žq‘›~Ž‹‚€~55\*+Q#)h &e#o$p!*v"+w$(l"&j #d!b!ea\WMMN6ArAU†dx©ƒ¥Í|žÆCy«?t§.m¥^•QED{ :r 1d+^$QK !F)N/P#8Y,Eg1Il0Mq2Ot-Ju.Kv0Jx"o!Fu+O~5_ŠHsžW†±Y‰³N‚¬>rœ-aŠ+_‰7j–?ržEy£Fz¤Ht›9eŒ8Y>6 ;!@>%E*:Z3C^,G>FK@GL_ucg}lVpYOiRIlU?bJ/]G/]G6[L=bSE`SMh[Wf[Wf[\bVU[OPRKOQJKPINSKQSLPRKQSLQSLOOMPQNSSSTTTRUTTVVUWWWZYVXXVXXSSSPPPOKJIEDFA;A<6B;5A94@;5@;5@=9B?;FECKIGKOPOSUQYcZblXk|`s…azg€•f€™h‚›gšd}—by“]uŽ\rˆ[q‡_t‡`u‰dyŒezk€“o„˜r‰œtŒŸtŠ›tŠ›v‡˜u…—u…—v‡˜v†šv†šu…™u…™uŠtˆŽw|t‰xu’ds‘cw”[y•\z’^z’^~†sˆt{x€uszdl}dl}_lƒam„am„am„anƒ]kZfyS_rPWdGN[ECN97C33A99G;GZIUgIaxWn†_|“i…œpŒŸs¡|’œ}“‚’™•”~Œ“}‹’}‹’}Š“}Š“~‹”~‹”|‹”z‰’{…Šs}‚qo{b`lPP`KJZIQgR[pOf{Wnƒ[o€^q‚epzblviorfkocimbhkckicki„•~–§—ªy‘¤s“Ÿm“Ÿm˜žr™Ÿs–u•›t•˜z‘•v’}“~‘•‰–šŽ•™”‘•Œ•–Ž–“•’Œ“’Œ“’Ž–”Ž–”Ž•–Ž•–Š‘†Ž + + +! !  + +    !!""!!!!#%%%%%%%%%%%%%%$""""#!!!!!""!!!!!!!!""!!!!##""##!!!!           ! ! ! ! ! !" " " " $ $ &"&"& & *! *!!,!,"- "- #/!"- "- #/!#/!#/!'.!)/#*/%*/%*/'*/'*/'*/''-'(.)(.)(.)%-)%-)#+)#+) +' +'()()'(&&&)%($*$*$*#("(!' &"($"' & &"' & & & & &%%%%%%&&&&&& % %!$!$!$!$'"-EQjwƒœz•¹™¾t¢½h•°\Š£d“«g©n”°s—³xœ¸ž¼…¢¿•©Æ›¯Ë˜²Ë’¬Ås’ª}›´Š£¸‡ µˆ”ž~‹”ƒ’‰“˜~’¡[o~WqVpŽ[m–_q™dr™YhŽ^q’dv˜e|h w‡¢ošgmwœ¡¬¹µ»¯«°KN`@CT8B`1;Y"A 2/%&>33N78;>>)N7+\9-_0$U0']-$Z+"W) V'!V%T% X+&^+-c24j-4bipŸiv›]iŽP`‚n~ u‡¤mœjx“cqŒiuŒo{’y‡›s•LRaQXgWcaMYWN]RO^SR_M\iWu}l‚ŠyˆŒ‡“Žng|6/E+)Y,*Z%%b$$a&$k*)p((l&%j!e d`^XS K K$X4Bv;R…Yq¤u›Çs˜ÄIt¤;f–6iž-_•J}Fy?r 2e %Q %Q #M)S1[!9c @i&Fo%Is(Lv!Kv$Oz%R{$Qy.U~'Nw9e2^1^&9f-Bq8L{A]‰WtŸaˆ¶k’À]ŠºAnžV‡M(n ;³A…µC‡·U…´7g– #H>; =$@$@=&D,4R(0O<6: < <;,I8Lh9Xw9Xw@`JjŒKd‡^KFYJDVHJUHJUHNOQNOQPOQPOQSQQTRRRRRSSSRVVRVVUXUVYVTZVSXUTUSPQNRNJKGCLC;G?7F>4F>4C?7C?7DB9GF=JHFNMKKQSQWXQ^eXelYm|_s‚by“f}—efžf~œbz™bw‘]rŒ[q‡\rˆ\r‰^tŒbwe{“j€–o…švŠ xŒ¡{ŠŸy‰w‡›v†šv†šw‡›s…™s…™t†št†šs†—s†—r‡o„‰qŠvn‡sr‹cr‹cxŽbxŽb€ˆp|„l{z|vuxkn€kn€bn‡bn‡_n„_n„\l€Zj~XdwQ]pTWdJLYBAK98A45E<=M:K]GXjJcYrŽf€—l†tŽžw’¢‚“ž‚“ž‘—€–{Š“|‹”z‰’z‰’yŠ•yŠ•{‰•{‰•{‰•zˆ”z‚Œrz„pnz`_jPM]KHYKReSZmQe{Ymƒ^tƒ^tƒdoxblvgioehncgibfhcq]†”€«€˜§{•¡q“žo–›q˜sšžy—šu”˜w’—v’“|y‰‚”…’–—’–—‘™•˜””–Œ‘‘“‘“‰’“Š“”‹”•Œ•–Ž–”Ž–”Š‘…Œ‹!  + + +! !  + +    !!""!!!"#%%%%%%%%%%%%%%$# # ""##!!!!""!!!!!!!!""!!!!##""##!!!!           ! ! ! ! ! !" " " " $ $ &"&"& & *! *!!,!,"- "- #/!"- "- #/!#/!#/!'.!)/#*/%*/%*/'*/'*/'*/''-'(.)(.)(.)%-)%-)#+)#+) +' +'()()'(&&&)%($*#($*#("(!'"(!'&-3#(%% & & & & & &%%%%%%&&&&&& % %!$!$ #"'%/S_xt€šq‹°œÀw¤¿r ºoµoµk’®k’®h‹¨xœ¸ˆ¥Ãx•²~’®”¨ÄŽ§Á¨Â†¤½ˆ§¿{“¨v¤Š— ‚˜‚Œ‘…Ž“ƒ—¦n‚‘Yt’Yt’asœdwŸgvœbp–]o‘ZlXo_v—fv‘HXs &0y~‰£Ÿ¤‰Ž=@RBEW2<[+5T: +1-26365<?(M.!S:.`5(Z.%[3*`,#Y.%[)"W)"W% X'"Y&)_,.e)0_fm›lx]iŽTc…VfˆyŠ§x‰¦p~™iw’jwŽmz‘t‚–y‡›hn}CJYLXVBNLFUIHWLN[I^jXt|k€ˆw€„‡‹†„}“PI^*'X*'X"!_%%b'&l'&l'&k'&k d d\ZSO K K&Z0>s7O‚Um fŒ¸cˆ´Hs¢;f–.`–,^“Oƒ 9m8k.`K L #M-W!9c%>h%Em&Fo(Lv)Mw$Oz&P{&S|'U}1W€1W€#?j4_ 3`':g,Ao7KzA]‰Vržd‹¹o–Äj—ÆFs¢Wˆ)d•C‰»Q—ÉR–ÆS—ÇZŠ¸O~>><!?%B%B"A ?"A ?55;">#>)D4HeAUr>\|Gf…Rr”Ww˜UnB[~8Nl'<[#0G&=!%5/3CEDKGGNFGI?@B*+/ !&"%619C=GTOYzq~”‹˜²«³ÈÁÉÈÊÊÈÊÊÇÌËÆËÊÆËÊÆËÊÄÌÈÄÌÈÆËÁÃÉ¿ÄΪ¿É¥ÄÎÂËŒÅÁ{Ÿ›U8<-0%7$66";FZ@\pV]mXwˆs{ˆvu‚pz†p|ˆq‚Žsˆ“x˜€|„lfi`@C9<9BHDNFBSFBSEGNvxy}oin`hlYjn\`m[_lZZweTq_Cj[=eU9cS7`PBaN>^KFYJGZKNZMLWJNOQOQSQPSPOQSQQUSSTTTVVVSXWTYXWZXWZXTZVRWTSTRPQNPMILIELC;JA9JB8JB8GC:GC:GF=LJANMKSQOPVWT[\Sah\jq[o~au„by“f}—fžee}›`x–au[pŠ[q‡\rˆ^tŒavŽcyi~–k‚—p†œvŠ xŒ¡{ŠŸy‰w‡›v†šv†šw‡›t†št†št†št†šs†—s†—uŠp…‹r‹wn‡ssŒdtezczc~‡n~‡n|{~wwykn€kn€bn‡bn‡_n„_n„\l€Zj~XdwQ]pSUcIKXBAK98A56F=?N=N_K\mKd€[tg™oŠ¡tŽžw’¢€‘œ€‘œ•}”|‹”z‰’z‰’z‰’yŠ•yŠ•{‰•{‰•{‰•zˆ”z‚Œqyƒmlw^\hRO`MIZKReU\pTh}\p…]s‚[q€cnwakuhjqehncgidhjŠ˜ƒ™§’—¥z‘ t’n“žo—œr—œr—šu—šu”˜w‘•uŽwŽw‰‚’š‹”˜š”˜š˜”Ž–““•‘“‘“‘“‰’“Š“”‹”•Œ•–Ž–”Ž–”Š‘…Œ‹$ !  + + + + +! ! ! ! """"""##""#%$$%%&&%%%%%%$$$#!!##""!!!!!!!!!!!!!!""!!  !!!!##!!!!           ! ! ! !!"!"!"!"" " #!#!$ $ &"&"("("( !+"!,!,"- "- "- "- ".#/ #/ #/ )/#)/#).$*/%)-&)-&*/'*/'(.)(.)'-''-'%-)%-)%,+%,+ )* )*'*'*'('(%($'#("'"'"'!'!'"(@EKQTf]]kDDQ>BSBEW:AY)1I/*+-35))E9:=E#K;3f4,_."Y."Y-#[/%^)$[*%]#!X#!X$%^%'_(/^T[Šiw`o•YjŽSdˆ]l|‹°x‡§lzšiwiwm{”p~—x„”JWgKTUJSTGSJGSJQYUbjgwz~z~‚uyzuyzˆ‡zy‚>?X-.G)+V*,W*)`+*a-0f/1g $Z!WVQMKDF&T4@n6M{Vmœl‹¶Xw¢Hl”Di;d1Zƒ/O~:i5d)W#P "N L,XA"C%F 'H"C<889;"@)G*@^4Tu<\~?hJt›W€©\…®\w¡Kf:Lp1U*E)D#)?06LGHSNOZIHR87@ , ,)%696FKAPdZj„²§²ÀÅÄÇÌËÆÌÈÆÌÈÆËÊÆËÊÆËÊÆËÊÆÌÇÅËÆÅÍ»Ã˺ÇΫÄ˨ÉÑ‹ÃË…ËÓbÁÊYŒ/?B46<> FL$175?"S]@KYChw`met†l}Šoy‡l€oƒ’rz‰d^mH‰‘ymu]555AAA>:KGDTHEUliy€}„tqypjmoiljane\i`]n_\l_Yj_YjO[bKV]:WR8UP3XN4ZO1^K1^K9[G9[G?XK>WIDVHFYJIXOJYPR^VUaXU]SS[PPZOPZOTYKSXJPRDJL>KH6JG5KF2KF2IH6JJ7KJ?ONCNNHSSLPZXU^]Sdj\lr]s‚cyˆb{—e~™fžc}›bz™^v”\q‹Zn‰[q‡\rˆ]s‹`udz’i~–m„™r‰žvŠ xŒ¡y‰xˆœv†šu…™t†št†šr‡šq…™r‡šq…™q†—o…–r†“p…’s‰…p…‚r‰nr‰nt‹cve€‰l~ˆk{|ywxvmp‚kn€am†am†_n„_n„\l€Yi}WcuP\nPR_DFT>?H8:B5PBEWDDQDDQ>BSBEW6>V%-D/*,+26628 @HH!M#N)T/$Z-#[/%^+&^*%]&#Z#!X"#\&(`")XGN}t‚©^l“SdˆPb†Udˆo£‚°t‚¢kz“hvjy’q€™‹›_l|CLMBKLHTKR^V[c`jroy|€ux|ostgkl||…ŠŠ“ab{22K(*U-.Y*)`+*a,.e*,b $Z!WURLKEF%S.:h3JyRi—SrœWv¡Lq˜Fk“6^‡(Py*Jy#Cr3a*X+X&R L'S"@m*Hu"Lx)S%[„']‡0k”2m–*d‘#]ŠQKz"Ju!It!;h!;h+@m4Jv9TJd^°s”ÅuÍj“Ãg˜Ëj›Îb¡ÆaŸÅašÈO‰¶-R‡O H E? C#D#D$E A!@=<;!?%B&;ZNC@QGARLGX?JR:FM,ID+HC+PF/UJ0]J0]J8YF9[G=UH@YLH[MEWIESJM[RYe]Zf^^f\V^TNYNQ[P[`RY^PSUGLO@LI7JG5MH5LG4JJ7KK9MLAPPDRRKVWPR\ZXb`Vfl^nt^tƒe{Šb{—e~™ec}›ay—]u“Zn‰Ym‡[q‡\rˆ^tŒbwdz’i~–m„™r‰žxŒ¡xŒ¡y‰xˆœv†šu…™t†št†šr‡šq…™r‡šq…™q†—o…–s‡”r†“s‰…s‰…vŒrsŠpwŽfwŽfŠn~ˆk{|yyywmp‚loam†am†_n„_n„\l€Yi}WcuP\nPR_DFT:ZˆA]ŠJnŸe‰ºi—Ëe”Çd–Ëb•ÊX“ÉUÆS‹ÄF~·8`š/i R RM!N$N#L!HD@ A!@"A"?%7T8Vx?]:d‹En•Eu¢M~ª[‡µaºo†´=T‚G!M#F"E"A"A&='>$<": 3 3$8#(;84Esp€³³ºÃÃÊÅÊÉÆËÊÆËÊÇÌËÆËÊÅÊÉÉÌÊÇÊÇÊÍÄÈËÁÅ˺ÃɸÈΠÄËœÊÓo¾ÇdÅÐ(¿Ê"½Ë¨¶ebb`d_*c^(,2 5;MYXCDTLN]FPSEOR7YK9\M5bM6cN7bM.ZE4\J7`N:cNAjUBfL=bGHaDOhK[lN[lNbnPWcFMQ7IN4JM1KN2KM4MN5QQ=UU@UVMZ[RU^]\edVgr]ny^s†f{Žcz–f|˜d|šbz™ax’]uŽYpŠXo‰Xo‰[rŒ\tŒayd|”i™p†œt‹ xŒ v‹Ÿxˆœw‡›v†šu…™t†št†šq…™q…™p‡šo†™o†™o†™o…–q†—vŠ‰r‡†vŽvpˆpygwŽf~Šl|‰k{zx|wkq‚lrƒbn‡bn‡]m†Zk„[jXg}W`sPYlNR_BGT<OAIZBVlNbxVr‹`|•i†šm‹ŸvŽžy‘¢€’š™‹—~Š–z‰’yˆ‘y‡’zˆ”{‰•{‰•yŠ•yŠ•|‡•w‚‘s{…jr|ddrUUcJM_LPaHYoQbyUm…Zr‰br†`p„`oxZjsbrd€‚œ­”¥yš¥v–¢r•›u’˜s‘•v‘•vŽ’x‰Žtˆ‰yŒ}{zfjeclsxˆ{—–ž†•™…”˜‰“˜‰“˜†•†•†•†•‡‘–ˆ’—Š•˜—š—š‹“—…’€‡Œ""""" " " " """"""##""##"####$%%%%%%########""## #!!    !!""!!!!        ""  " " ! ! ! !   !!!!! "!""#" " "#"#$"$"& ("( ( ( !+"!,!,".#/ #/ #/ %0#%0##/!%0#'0!'0!(1$(1$'/$'/$(/'(/'&/'&/'%-)%-)%-)%-)%,+#+)")*")* &( &(&)&)%($'$'$'"&!$ & & &"'"' & & & &%%%%%%%$$#!########!!!!" ,*(4]`€„‡¦ƒ•½ƒ•½s•¸zœ¿bˆ­g²s˜¹l‘²i‹¬\~Ÿp²t”µu•¾w—¿i–ºj—»h“µ_‹­[\ƒžj€–sŠŸ‡‘{ƒy„ŒŠ’kv‹q|‘guŽ`n‡We…Q_HVvR`€LWz0;^!'D-3PCCWCCWGFVEDTDDXCCW7;Q$(>*+*+,,/.137: J'#Q92h5/d%W&X+%_+%_("\)#]!#YV#V&+^go›px¤]n”]n”Yn[p‘i{¯w‡¢kz•iz“k{”r{ˆ˜hqrKTU\aYdiavux|{~utkiuRW]9>D/4:;?EPR_\_lJIg54R1/V31X--[)*W')Y!QPKIGC@'P*7`aBY‰I`Kg•Ok˜FišImžLz®R€´S†»Vˆ½Hƒ¹B}³=u®5m¦;cœD~'Z#W#Q"P$N#L GF@?"A&D*GTI?UJ@VLBXI?UKAWL@VI=T>5IG>RSKcPH`E>X;5O;2L;2L79H9;KGQTBMP1SE=_P2`K+YD4`K2]IBjXDlZDmYNvbQu[NrXXrTVoRfwYhy[dpSXdGOS:KO5KN2NP5NO6OQ7RR>VVAUVMZ[RWa_^hf[lwaq|cw‹g|f|˜h~›f~œay—_vZq‹Xo‰Wn‡Xo‰[rŒ\tŒayd|”i™p†œt‹ xŒ v‹Ÿxˆœw‡›v†šu…™t†št†šq…™q…™o†™o†™o†™p‡šq†—r‡˜wŒŠt‰ˆvŽvs‹rygwŽf~Šl~Šl|€{x|wkq‚in€_k…_k…Yj‚Wg€Zi€Xg}W`sPYlNR_BGT@?H?>G:BSFN_DXnQe{Xtc˜i†šm‹ŸwŸx¡€’š™‹—~Š–z‰’yˆ‘y‡’y‡’zˆ”zˆ”wˆ’wˆ’x„’u€Žrz„iq{ccqTTbJM_PSeJ[rSe{Um…[s‹cs‡\l€_nwZjs{‹}‘¡”•¦z¡u“žo’n•›u”™t’–x“u‹uŒv‡ˆx‚r_c^_c^jsz{„Œ“›–ž†•™…”˜‰“˜‰“˜†•†•†•†•‡‘–ˆ’—Š•˜—šŒ”˜ˆ“„‹‘ˆŽ########""##""#######$$$$#$&%%%%$#$$##!!!!! !               ! !         !!!!!""#" " $"$"&"&"$!&" )#(" *! *! + +!-".#/ #/ %0#%0##/!%0#'0!'0!(1$(1$(0%(0%(/'(/'%.&%.&%-)%-)%-)%-)#+)")(()()'('(%(%(%($'#&#&"&!$ & & & &!$!$!$!$ # # # #""""!!!!!!!!!!!!!!%&54?31=Z]|…ˆ§‚‘Á|Œ»s’½x—ÂgŒ³iŽ¶s“»lŒ´r“¹hˆ®t•»yšÀnšÀi•»V‰¯P„©V†§Xˆ©YŸZ‚ e~™i‚žz„”s}}‰‡‹”r{Šnv…bj‚bj‚]g†Zd‚ZbƒU\}PYu?Gc7V>?Q@ASBBVEEZ@AX;;l(%U"SM) [&X*$^*$^&$Y%#W T!"VV`‡}†®n|œn|œfz™i|›qƒ¥y‹­}‘­l€gz”i{•l€–n‚˜v‚ŽFR^iejvrw{w|}y~yv}hfmLPY6:B).4',2*-:>@NVXlGI]01N,.K-1T-1T-/X*-U !LHC@<<)N0:_1OqRo‘q—¼xžÃe®&Bo4c4c4Ew5Fx(;h"5b0\/[#;g#;g'Gt,MzP~(Z‰(m¢;€´?Ž¾G–ÇP›ÑEÆ9q¬QBy;r/RŒIl¦Mw°Oy²S…µU‡¸V‰¼K~±5k)^‘5jŸBw¬?sª8l£8hž1a—4[Ž+S†)@u*_'V&U#R"Q!NM!H!H!F"G'M4GmOo—Xx¡Kx£HtŸBy§H­O€¬Sƒ°_¬6Vƒ$T"Q!JG?=87345#9)(?-,C9:XFGe^e††®œ¤Â¥­Ë±¸Ìµ¼Ïµ¹Á©­¶¦¢§™Ÿ¤¢›´²¬½¿¦À¨ÃΊ»Æ‚·ÓC©Å5­Í «Ë¬Ê ¨Æ jqX`UQ"RMZ_5JO%'3/;=I')5@L6p|ftox…sq}Šou…^`pIƒ‹sow_755GEENHM^X]db[][T[bO]cPSUINPDCB>XWRfgdfgdkjliikdadYVXPKRRMUgbjNJQ@;ISN]O\_JVYNd_UkfJc\C\UWhe^nlcofXd[YbSOYJio^kp_ZbJMV>MQ7MQ7OQ6PS7PR8RT;RVCSWDVWRZ\WU^e[ck[h~cp‡_u‘bx•c{žd}Ÿa~›\y–[uŽXr‹YpŠWn‡Zq‹\s\tŒayd|”i™p†œsŠŸv‹ŸuŠxˆœw‡›v†šv†št†št†šq…™q…™o†™o†™o†™o†™r‡˜s‰šxŒvŠ‰{wyŒu~m~m‰q~ˆp|~€xy{ipƒgn[k†[k†Uk‚TiVg}RdzW`sMWjLQ^BGT>>K??M=GZIReJ]wWi„YuŽd€™jˆšmŒwžx‘Ÿ™}Ž—‹—~Š–{‰•zˆ”z‡•y†”x‡—x‡—x‡—x‡—x„’tsxƒhnx``mTTbGNbNUiO^wYg€]q‡^rˆau„[o~_tlo„|™¦‡–£ƒ“ s‘žp’™v’™v•—|’•yŒv‘w‡Š|‡Š|~||caaPZ_]glcvq„{’—~–›†•™†•™‰“˜‡‘–‡‘–‡‘–ˆ’—ˆ’—‡‘–ˆ’—Œ–™Œ–™‡“˜„•€‹ŽŠ########""##""#######$$$$#$&%%%%#$$$##!!!!! !             ! !         !!!!!""#" #!" " $ $ $!&" )#(" *! *! + +!-".".".%0#%0#%0#%0#'0!'0!(1$(1$(0%(0%(/'(/'%.&%.&%-)%-)%-)%-)")(")(()()'('(%(%(%($'#&#&!$!$ &% & & # # # # # # # #""""!!!!!!!!!!!!!! " *#!-RUt}€Ÿ{Šºxˆ¸q»s’½e‰±f‹²r’ºr’ºr“¹t•»yšÀyšÀk—¾i•»S†«R…ªM}Ÿ^Ž¯e«YŸg€œp‰¥}‡˜u~‚‹‰•|„“ktƒlt‹iq‰dn_jˆ\c„RYzJSo@He?QBBVBBV@AX<>T:;R(*@-+++--//2488;?J@IEAv2*d0'b("\*$^)'\'%Z!"V$$YDMu„µzˆ¨v„¤o‚¡o‚¡t‡¨y‹­‚–³w‹§j|—fy“l€–k•|ˆ”bnztpuyu{yu{|x}tqyebjIMU59A(-3%)/#&3(*8=@TNPd?@^23Q*.R04X.0Y.0Y&'RIC@=<)N2=b>\~Wt–n“¹s˜½Lh–M,Z#:h);l,=o':g-Z(T+X/[+Co)Jv/O|'Yˆ5g–;€´Q–ÊPŸÐOžÏI”Ê0|±1h¤2j¥7`—%M„?y/RŒBl¥GqªS…µkÎd—Êf™Ì[Ã>s¦.c˜3h5i¡3gž0`–)Y.Vˆ$L0G|&=r0_-\)X'W$R#Q#J#J#H&J'M*=cNn–^~¦Mz¥Mz¥H­Kƒ°O€¬Hy¥Xx¥:[ˆ&V PI!J @>7635"8%)?,+B54KFGeQSpS[|Y`ck‰lt’ƒŠŽ•©”˜¡‡‹”€|‚yu{†„}š—‘¦¨§©®¹u§²n§Ã3¡½-¨È¨È§Åž¼ltRZNJTO LQ'SX.LX-5A3>GR0ht]z†pq~lsm|‰n|‰nixQYiBŒ”|„Œt=:;IFGYRXd^cdb[fd^ip]flYVXMLNBZYUfe`kkilmjlkmmlomilgdf`[c_ZaUQXLGOC>MGBPKXZJVYG]XDZT=VO5NGCTQQb_Zf^HTK=F75>/Y_NagVRZBKS;OS:MQ7PS7PS7PR8RT;SWDUYGVWRZ\WW`h]fm\i€dqˆ`v’cz–c{žd}Ÿa~›\y–[uŽXr‹Wn‡YpŠ[rŒ\s_wŽbz’g–kƒ›p†œsŠŸv‹ŸuŠxˆœw‡›v†šv†št†št†šq…™q…™o†™o†™o†™o†™r‡˜r‡˜yŽxŒ{wyŒu€o€o‰q~ˆp{}wxzipƒgnZj…Zj…Vl„TiVg}RdzW`sMWjKP]AFR>>KAAO?I\KUgM_zWi„\x‘f‚›k‰›nŸwžx‘Ÿ™}Ž—‹—~Š–{‰•zˆ”y†”x…“x‡—yˆ˜x‡—w†–x„’trw‚gmw``mTTbLSfSZmSaz[i‚_s‰^rˆ_s‚Zn}j~w‰ž–—¤„‘ž‘žpŽ›n˜t˜t“w‘”xŒv‹u‰‹}}qcaa\ZZS\a^hmbu€r†{’—|“™…”˜…”˜‰“˜ˆ’—‡‘–‡‘–†•†•‡‘–ˆ’—Œ–™Œ–™‡“˜„•‘Œ„“Ž####!!!!"""#$$$$&&%%%%$$$$$$##########""!!!!        !         + +    !! " "!""###$ %!%!%!("("( *! *!( * * +!,"- "- %.!%.!'0!'0!%0#&1$'/#(1$'/$'/$(/''.&#,&&/)%-'%-'#,+!+)!+)!+))''&()&&$'$'#&"$"$"$"$"$!$!$ # #!"!"$$$$$$!!""!!!!"#!  !!#&LLltu•r‚²xˆ¸o¾jŠ¹b‰·^…³jŠ¹i‰¸j†±|™ÄzœÆxšÄi˜Âl›Æb“½T…¯]‡­_‰°q±s²r‰¥n„ ƒ‰š{’„‹‹•ŽŽœ~~‹txŽnr‰jo‰hm‡`c‚UXxMQn@Da17M)/E23J79O@AS@AS<>S9;O7:R+.G3-,./--.1496738;A"$O/+^>:m% X*%]("\)#]'$T)&W59]†Š®„©{‡¡rƒ q‚Ÿv‰¨~‘°‚—¶~”²o…¢cz–f}’j€–t„–m}]_hqs{yw|vuyonr`_cEFO13;'*5'*5%*6(-:5:GIN[PQjAAZ(.M',K*/U&,Q)-Q"&J A=:<(J.;^B]‚d£lŽ¸r”¾>[‹G P)?pAVƒ.Dp 6b/[+U+U.\3`(L}/S„%WŒ@r§K¾YžÌSÏL–È8x¹*j«@l§Doª5bŸEs¯U“¿W•Â:†À-y³O£º[®Å[«Å[«Å^£ÌO“½;y§-k™1b•+\2Z/W‰/Pƒ)J~/Jz(Cs)SUGVYKVXX[]]U_k\frYkat‡]x”b}˜b~žb~ža~›\y–YuŽTp‰Vo‰Xr‹Zt[uŽcyf|”k‚—q‡sŠŸvŒ¡xŒ v‹Ÿx‡žw†v…œv…œs„šs„šp†œp†œp…o„œp†œp†œnˆ˜o‰šw‹vŽŠ|‘uzs€o€oƒŠuˆr~~€yxzkq‚jo`o…`o…Vl„TiVg}RdzV]qNUiKM[@CP:?KAFR?LaJXmKa}Yo‹[{–b‚k‰›pŽ x‘Ÿwž…‘‹—}‰•|ˆ”{†”{†”|†—|†—v‡˜v‡˜u…—u…—w‚‘s~Œnvemw]]kUUcKTiS\qOd~Ym‡^q‰]pˆ\qpcxwŸ•§‡–¡t“p‘–s•rŒ“w•xŒ’z‹‘y‰Œ{‡‹z‚€~gecUVhUVhN_qYj{_zˆj†“tŽ“u”„•„•ˆ’—ˆ’—‡—‰’š‰’š‡—†‘™†‘™Š–™‹˜š†–‘Š†˜x¡####!!!!"""#$$$$&&%%%%$$$$$$#######!""""!!!!               + +    !! " "!""#$ $ $ %!&"&"("(" *! *! *!( * * +!,"- "- %.!%.!'0!'0!%0#&1$'/#(1$'/$'/$(/''.&%-'%-'#,&#,&!+)!+) *()')''&&&&&$'#&"$"$"$"$"$"$!$!$ # #!"!"######  !!  !! #'#&*    #&@A`bb‚br¡v†µe…µg‡¶aˆ¶b‰·d„³a°fƒ®{˜Ãw˜Ãv—Âl›Æg–ÁRƒ­Rƒ­b‹²aŠ±pŽ°mŠ¬p†£qˆ¤€…—y…‚ŒŒ‰“’’Ÿnr‰mq‡kp‹hm‡be„VYyOSpDHe06L+2G34K;S9;O47P*-E3-,./,--/1657:<C=;F"Q'"Y)$[*$^'![$"R'$T)-Q†Š®†’«~Š£u‡¤z‹©{®€“²•³€–µqˆ¤d{—h~“f}’t„–yŠœcemijswv{usxonr][`ACL67@(+6(+6+/<49FNR_\`mSTmGG`8>].3R*/U'-S(,P#'K!C><<)K+8Z5Ot\v›lŽ¸lŽ¸,JyL+ArOf–Xnšo5YŠ0c˜;m¢P”Â[ ÎK•Ç4~¯&g§8x¹t Û{§âk™Ör Ýj¨Ôj¨ÔM™ÓH”ÎgºÑi¼Ód´Î^®È[ŸÈT˜ÁEƒ²7u¤7h›-]‘0XŠ*R…-M)J~+Fv&Aq';l$8i2_-Z(U(U)W*X*X)W%V7Iz]|®iˆºMw¤@j—=ožEw¦Gz¨Bt¢Jo?c’)@n #Q"KICC::7 $<&(<.0E44FKL^nq…ˆŠž…Š£‡¥€‰¬x¤jtšS]‚HOiCJdHHVEERGGGOOO[`D^cGit+mx0i†eƒdb‹m‹‚ cl!HQB@@>@D#/46B;GDR)-;@P.^oL_nUgv]y„i‚Žs†“sdqQ"_hMtugRRD\WQ_ZTcfXkn`iuWbnPfnV`hPaeRhlYprfqsgoqloqliiieeeaaa[[[Y[]VXZTTZQQXPTXPTXNTXPUYKOPKOPTYXV[ZQUP*.)")!GMA\bVRX@NTB NM($Y'"WM#$Q7;_‘µ‹–°‚Ž¨wŒ¦t‰£y‘¯}•³|“´|“´|‘°m‚¡h}•i~–q€™v„zŽho{uvsuvsnljYWUDFA340,0++/*:=:Y]Zihqpox^asQTf>Hm5?d(4[!-T&/V"+S%H?>;%H&3V;RzQhj…³e®">p+-A/2F77LQQesu‚‡Š—Ž–”“œ–’ž–’ž“Ž„Žrlxjdpb_bZWZUSLROIMN5FG.FMIO!O\Sa +_}qŽ™#|”SWNQPF4C:'DD423#7>$?F,>H$BK(4D?O&N]?ZhKx‚jŒs€‹p,7;G*•¡„™žˆ„‰tHC=YUOUOSOJMMSO\b_coaYdWS`Neq_jognslrttnqpjlldff_ba[]]WZYUWWRVWPTVOTSOTSPUTRVVNTQRWTU\VTZUOTL;@8491CHAPXGR[IRWAMR=LR:MS;NQ:PTRWARYMU[OU\]Z`bXdp\ht\p…`tŠ]w•b|šb~žb~ž`|š\y–[wXtYsŒXr‹]v^w‘dz’h}•lƒ˜p†œt‹ t‹ uŠt‰œvˆžu‡t…œt…œs„šs„šo…šo…šp…o„œo…šo…šr‡˜s‰š}‘Ž{Ž‹‚•x€’vƒrƒrƒŠv€†syy€uu|emƒemƒ[n†[n†TiSh€Tf|O`vS^nJTeGLW>DN@DQEJWERgN\pNf‚Zsb}˜gƒžnŠœrŽ zŸyž}Œ›z‰™z‰™yˆ˜xˆ•xˆ•x‡—x‡—u…—u…—u…—u…—w‚’q|ŒmtbiuZ_lUYfMZoTavTi\r‰]s‚Xo}{’|Š¡‹—¡{“žw“šw˜t“{Ž‘z“{‹‘yŠŽ{‘~…ƒƒkii`\q`\qQYqQYqJbyZr‰h‚”t ‚”œ“›‰“˜‰“˜‹–‹–ˆ‘™†–‡—‡—†‘™ˆ”›ˆ•˜ˆ•˜„™ƒ…š„˜ª|˜ª|%%%$$$$$%%$$%%%%%%$%$$$$$$$$""##  !!!   !!       + +             ! ! ! ! ""# $!%%' ' (() ) + * +!,!,!,"- "- %1!%1!%1!%1!%0##/!#.##.##-%#-%"+%"+%"+%"+% *( *('&&%%$%$$%$%#&#&"$"$"$"$ # #""""##!!        !  #%-+Dmk„ky¨t‚²c‚´_~¯`…³V{©Op¡Su¦h‰´q“½eŒ³a‰°^¸X‡²FtœP}¦d„¬a€©fxœew›Yeƒboƒ—~•ƒ‚‰ˆ“’‘œ|{†ljljhi‚hi‚hg~ZYpNNcEEZ=DW:BU9;O69M12D33E;=Q57L16I+0C3)-./.-,2134646768=@FI!R$ U)*W67d[_ƒ”˜¼™²„©z©{ª}•³€—¶~–¶lƒ¤{¯v‹ªk™j€—ršv„ˆ•krrrpturnljYWUBC?;<89=89=8ADAPSQ``ilktaevUXiu%M„7nªK‚½YËj¡Ý¹Ð¼ÒÁÉ›¿Ç»ÈŒ·Åi¶Ìa®Ä6£È.š¿N¾ÃQÂÆIµÆC°ÀG©Ð9›Â7‚¸.y¯5jŸ.c˜6]’/V‹1R†,L€5S‡Ec—s¡G{©Ew¦=ožGk›9]5O‚2L~8Jt=NyDOrGRu>B_!>$'@'+C-/C03G44HLLahjx~Ž†…ŽŒ‹”–’ž”œ‹™ƒ~|u‚yswtwqnqdb[db[dfL_aH`g8_f7hvly#p x•}•rŠHKGJB8&F<)>?/?@04: GN4;D!DN*>N$4DHW9aoRtf€Šrmy^`lQŒ˜z–¢…“˜ƒ‰xPKEPKEVQTHCF?EAMSOCNA>IRWAQXKV\PU\]Z`bYeq_kw[o„_s‰]w•b|šb~žb~ž^{™[x•YuŽVr‹YsŒZt^w‘_y’dz’h}•lƒ˜p†œt‹ t‹ uŠt‰œvˆžu‡t…œt…œs„šs„šo…šo…šo„œmƒ›o…šo…šr‡˜s‰š{Ž‹{Ž‹“w“wƒrƒr…Œy‡tyy€uu|emƒemƒXk„Xk„TiSh€Se{M_uQ[lHRbEKU@EPBGTHMYGUiP^rRk‡_x”d›j… nŠœrŽ zŸyžz‰™yˆ˜yˆ˜x‡—w‡”w‡”w†–w†–t„–t„–t„–t„–w‚’q|ŒmtbiuY^kX]jS`uUcwWm…]s‹Yp~e{Š‰ ŠˆŸ‰“žwŽ™s’™v’™v’•~Ž‘zŒ’z‰ŽwŒ}ŠŽ{tqrhffe`ub]rQYqQYqLd{\tŒk…˜uŽ¡‚”œ‚”œ‰“˜ˆ’—Œ‘—Œ‘—ˆ‘™ˆ‘™‡—‡—†‘™ˆ”›Š–™ˆ•˜„™ƒŠŸ‰œ®˜ª|$$%%%%#%&&&$ ##%%$$##$$##""####""""!!                    ! !   ""$&&&((***!*! * ) *!!+"!,!,!-!-#/ #/ %0#%0#%0#%0##.##.#",#",#!+"!+"",#",#*$*$&%$"%$$"$"#!!$!$!$!$ #"""##!!!!!!!!    "!$/cgqx„­{†¯c‚´e…¶]„²U|ªJr V}«a‡³cˆ´f‹²h´h’½[…±Pv Pv i€§d{£]k‹O]}SZtqy“„‡›ƒ—Œ†’‰–šnmxVUgWVhVQmYTp_]v[ZsGFVGFVGHZJK]>BS=@R@BV@BV9=S48N37M,0F 6*,...+++//0535478<;=A!I%MHApsl›ŠŒ®˜š¼Š’±‡­}’±~”²|•¸{”·}›»Wu”kˆ¥y–³o…¢joƒžoƒž|Š˜uƒ‘y€z}ƒ~€‚oz|irx`uzcuzdw|g€…r‘~••‘‘Š~ŽberBLlENo9Iy4Cs-;k%3c&4['6\$/R$FB$,Q/?cP_ƒXmœJ_Ž*LXz°dÄf’ÆR…±L«Z‰­a´Hl”>e -\ +Z7k%Dx:mAt3_šWƒ¾t¡¾€­Ê¼É‘¿Ì‘¸Í³È€«Íz¥Çb©ÉcªÊM­ÆI©Ã@ªÔ8¢Ì)˜Ê&”Ç%’Ç"Å/ŠÀ!|²&h¤a3\˜/Y”7^š7^š,^“6iž*_’"WŠ#L|Dt"8i5e 5d"6e/`.^;qTq¨gÄk’ÉXˆ½Jz¯Bz­Ay«>v¨7o¡7h›-]‘5]&+?+0C05F>CTXYiuv†€‰‡†‘Œ“‰„‹}y~{w|„{z„{z…}s}ukpkPmiNxvGusCor+jn'nzv‚!w‰z‡3hok[Gk[GcPS?,/2+*60/59-GK?DL4;C+>K%S7;Q59O37M,0F 6*,../$,/.1-.05576:;<<B0*Xe^ˆ·”–¸”–¸‰‘¯‡­|‘°~”²|•¸{”·v”´Wu”^{™x•²vŒ©o…¢k€šoƒž{‰–|Š˜†Œ‡…‹†™šˆ›Š›¡‰£Œœ¡Œ™žˆ›ŸŒŸ£ŸŸ˜™™’†‰–oq:Dd8Ab0@o7Gv2@o,:j'6\%3Z/:]'2T!GE+:^Tc‡k€®Wl›Np¦kÃi”Èk—Ë[Žº\¼r¡Äv¥ÉzžÆ[€¨<[‹=]Œ+J~=q,_o¢>o¢>g—Dm\{Ÿf…©r€›mz•imƒ@DZ!#9$%<',@,1E,2C28IGIYacrxw€~‡€|ƒrnuminrnt}ts{rq{shvndqlRlhMusCtrBnq*im%nzrqƒw‰qy&jrqaM‡wcs_bJ7:3-+81048,59->F.CK3KX2@M&2?HU.u‚gx†k[jSv…n‹™|œ~”†Šw^XUHB?MFN92:"37;IQKS\V[d^jsmjroltpotsotspommkiddd^^^WZYVXXTXZSWXSXWSXWSXWRVVSXWSXWTZUTZUTZUTZUTYOQWMPVEPVEMR=LQtU…»‚«Æ‹´Ï‘ºÈ¹Ç°Ñƒ£År™Çt›ÉeÈi¡ÌW£ÖAÀ/‹·M©ÕH°ÍI±ÎF±Í@«ÇC¥ÎA¢ÌJÉN‡%c5sPx¬Bu¨Cv©]{¡eƒªs€¢n|žop‰VVo%$;%$;&,=).?*/A-3D;=JJLYdahkhokhkb_bkdcmgfnf\g_UibNibNc`@a^>de:ef;eh/dg.gm%lr*mt*lr(WP"_X*jYPwe]qY]V>B=1*G<4<9.30%;?*=B-@K%NY3DQESdmL{„b†v‡Œ{‰“}–€ˆŽ{~…rRNHID>SKU80:)>IM8IN7JO8JO8KQ9LQCPUFRXRU\VU]`X`cR_oXeuVk…[pŠ[t”_y™]{¡^|£Z|Wz›Wx•Uv“[w]y’^w‘`z“e{“i~–o„œrˆ s‰¡tŠ¢sˆ›sˆ›t…œt…œs„šs„šrƒ™rƒ™m„™m„™m„™m„™o„œo„œq‡–rˆ—yŠyŠ…”{ƒ’y…u‚Žs„…|€xvv‚qr}am„am„ViˆUh‡Wi†UfƒVf|Q`vOXkEObELX@GTFN_LTeH[sRf~Sp‹]{–f‚›k‡ v x¢}Žž|{‹šz‰™x‡—w†–u‡”u‡”u…—u…—v‡˜v‡˜u…—rƒ•w‚‘ozˆmubjt[ap[ap[kxbre€yw“Œ§~Š¡y“šw˜t•–}–˜~’•~Ž‘z‹x{‹Š†xwsggsggsbf|ad{U`|Q]xM]zRaYqˆf~•rŽ›x“¡‚•›”šˆ‘™ˆ‘™‡—‡—„–„–ƒ‘—„’™†“•‡”—Šœ‰Šœ‰—«—«ªv›¨t%%%%%%%%&%%%$$%%$$""%%""""!!""!!!     + + + +     +    !"# "$ $&((((*!*! * ) *!!+"!,!,!,!,"- "- !,!,"- "- !,!!,! * * *! *! *! *!("' %$%$$%"#"#"#!$!$!$ #""!!,*42%CC0[ZHjqvmtz^v›g¤Xz©Xz©_€±Yz¬Uu¤Rr¡QqžXx¥[sŸg~ª€“¾ŠžÈœÃœÃ‰Ž­Š®“‘«Šˆ¡Ž‹›Œœˆˆ–ii~LLaAA\CC^QNsXUzPIqMGnPH`g_wgar[VgC@Q<9J=AQ<@O9>P5;L16I&+?0 %)*---)<2.A*'*+-.03:;8;<9(*LRTvst›•–¼”ž¼‡‘¯’ª”¬|—µ|—µu’´u’´e…¦Ww˜Zxša }•³s‹©g€œg€œnšs†ž„™x‚Ž…€“Ž˜›‹œ ¡¥’¢ž¤‘Ÿ¥’¥”¥” ¤¥“—˜aiˆBJh;J|9Hz;L~=O€>Q~6Iv.;f$0\(1Y$.U-VSc‹q‰µuŒ¸f½dŠ»b“Æa’Åe—Ás¥Ï€¯Î|ªÉpš¾S}¡@k”U€©\™Èc Ïj¥ÖMˆ¹"Rˆ0`–už¹†¯Ë¶Ã¶Ã‹«Í€ Áo–Äo–Ä`˜ÄdœÇF’Æ.z­BŸËG£Ï7Ÿ»8 ½9¤À9¤À;Æ6˜ÁBˆÁK„%c*h7s3e¡HÍB‹È,v®gŸ'^‘Vˆ Cw8k5g2e0a2b4j,b>uJs©X‰ÁK}´?y­GµH†ºIˆ»M‡»I‚¶Gz­Ex«]{¡_}¤iw™fs–lm…]]v/.E! 7 &7#(:).?).?35B8;HLJQXV][X[YVX`ZYc\[bZP[SIaYFaYF[X8UR3Z[/[\0`d*ae+fl$io(biMT +NGZS$bPHn\SgkrZ^;0)>2+2/$.+ (-6;&Va;Yc=ft>Vd-S\;qzY‚ˆw‰~ˆ’|‡‘{…ŒyˆŽ{fa[HC=G?IA9D,*620G?>G45E'(8+'))**&'))**)*--348:: D7<]SXydo’¯Šœ¹„–³|’®}“¯€—·|“´s’¶oŽ²Y¢SzœSx˜Tyšo‹«v’²o†§k‚¢f~œi v‡ ~¨w~Šsz‡ƒƒŠ‰‰”‘”™–˜˜›š›–Ÿž•ž‘š¦‹—[i‹KY{@P„BR†4L~2J}9O‚7L5Dv'6h,^*\+X[‹&L@e˜V˜Áb£ÍW±ÉZ³ÌN™Ê.x©Z¹jɃ§Ê‡«Î}¡Òq”ÅiŽÊhÉWŽÉZ‘Í?ˆÅ?ˆÅIœÎB•ÇDËDËIŽÉLÌW‘ÊSŽÆK|¶F€ +,b /d7sRŽPŸÉJ™Ã,…¿u¯"ia•(V…Bq4f.` -`3f3o +.j*iQ@wµWËPŠÃG¹LŠÄOŽÈTŠÁO†½O€¬Iz§_}¤\z dp‘`lŒfh~^_v?;M"02!5$*;'->-1@/3C98AA@INJPQMRTSQUTRZUIXSFYV=XTA8>C:@56;&'+(0,7?NO?MPIQSLPVWT[\S[eV^hTbtVdwTiWm…Uq‘[w—Z{Ÿ[| ]¢]¢\|›Yz™]x”\w’ax’cz”f}—h™kƒœo† p†œo…šq‡p†œq„œq„œq„œoƒ›l›l›l›m‚œkƒ›kƒ›n‡œn‡œv‰”v‰”|…{Ž„†“z„‘x‡w„Œt€„{zqu‚mr_l‡]k†Vj‡Ui†TiƒRfRc{L]vOWjGNbCKWBIVAM_JViH]wSg‚Vtb›g„›kˆžrŒŸuŽ¡{žxŒ›{‹šz‰™u†–t…”s†—s†—s†—s†—t†št†šu…—t„–t‘mzŠlscix[fu[fu\rnv‹ˆ¦„Œ¢’žs‘r—™x”–v–”~”“|Œ}Ž~Š…„€kgzjfyaf~bh€XfZhYf‚S`|Nb†ThŒWw‹d„˜zŸ|“¡„‘š„‘š„–„–…Ž›…Ž›ƒŒ˜ƒŒ˜ƒ—†š‡˜ˆ™¤|•ª‚š­v™¬tž¥tš¢q&&&&&&&&%%%%%%$$####""##!!!!!!!!!!      !!!##$%(((**!*!*!*!+ + + -"-- . . ,!+ + + ) ) ) ) & & &"&"%$%$"#"#!$!$ # # # #"! !  %(/1KH"c`9{zO}R`aW_`VCRh?Ne:Pz@V€=Wƒ4Ny'Em-Js5Mw6NxEL{_f”ƒ†³˜›È’’»¶‘®ŒŒ©ˆ„žŒˆ¡Œ£Œ‹¢xy—STqHEhJHj\W|a[c[]U{`Yz]WxgZzh[{xi€r‰xkxtftjblf^iWV`BAK34D 0&&))))&'))**)*--3456=$&JCHiX]~`k€‹®ƒ•²|Ž«|’®€—³€—·{’³p´l‹¯SzœMt–Ot•Ot•e¡x”´sŠ«k‚¢h€že}›o€™u†žˆœ~…‘ŠŠ‘–—“–™–˜˜›š›“›ˆ‘‰•isR`‚O\~EVŠEVŠ=Uˆ8Pƒ8M6K~9Hz/>p"1c'Y&S9LxqŠ³y’»eŽ·]†¯\Š·YˆµUƒ°V„±T}¦?h‘;Yˆ5S‚(M€2XŠJŒ¶d¦ÏO©ÁL¥¾M˜ÉAŒ½WŠ¶`“¿| Ã£Æu™Êm‘ÂiŽÊd‰ÅSŠÆJ¼7€½J“Ð>‘Ä?’ÅCÉBŽÈGŒÇGŒÇPŠÃQ‹ÄO¹@pª6YŽ:p >6D5#G9'E2%J8+M@)M@)B:"C;$<;"A@'IP(FL$JU[f+_g4KS ^dFmsUƒŠvŽ”‰~…‹z{woYUMDMPIQSLNUVRXZS[eV^hQ`rTbtRgWm…TpZv–XzZ{Ÿ[| [| \|›Xy˜^y•_z–cz”d|•g~˜i€škƒœo† p†œp†œp†œp†œoƒ›oƒ›oƒ›oƒ›l›k€šl›l›kƒ›kƒ›n‡œn‡œv‰”v‰”|…{Ž„†“z„‘x‡w„Œt€„|€{rvƒjn{]k†\j…Vj‡Ui†TiƒRfQbzK\tNUiGNbFMYFMYCObJViK_zUj„Vtb›g„›kˆžrŒŸuŽ¡zŽw‹š{‹šz‰™v‡—u†–s†—s†—s†—s†—t†št†šu…—t„–rkxˆio~cix^iw\hvp…‚†œ˜Œ¢…›zœqŽ›p”–v–—w”“|’‘zŒ}€Œ‹‡rrmkgznj}cibh€[i‚ZhXeTb}Nb†XlZzŽi‰zŸ{’ …’›„‘š„–„–ƒŒ˜ƒŒ˜‹—‹—…™‡’›ˆ™‹œ’•ª‚”©™¬t˜«s›£rš¢q''''''&&%%%%%%$$####""""!!   !        !!!##$%&&$' *!*!*!*!***,!*!*!+"+"+ *(+ ) () &%%&"&"$"#!"#"# # # #"""" "$&(66 FFhi)„…EŽSn{@YfTTaOAN\BP]@NiDQm@Mk5A_#3T,<^5Bf8EjFIt\_Š€{¬“Ž¾ƒyª„z¬“Œ­†§Ž‰ž’Ž£©}z–]X‚HCmF>kQIucZ‚[RzaUyh\€dZ~^TxcVxj]|i…weƒmzdu}mnyiiznƒ|ogfb>=9!&%''&))+*)-*+/17:#E/4UAHiS[|`lŠ…’¯}‘¬{ª{“±}•³u“³p®mŒ°h‡«Z{ŸXzSx˜V{œ`£s’¶r°hƒ¥d}Ÿd}Ÿd{›k‚¢v‡§v‡§s~š‚©ˆ“¯…¬ˆ‘­ˆ‘­„²v¤p}¢o| f{£g|¤_t£XmœTlŸRjKb—;R‡9L5H|+>q 3g*Y*Y\t —Ãb¶[†¯Q‡²O„°P}®Gs¥E`’2L~;O€OM?HGKVR^^ZfDJMPUYhonipojrpmtsostlprimodhj`afYZ_UX\SVZPUYPUYPUYPUYPUYPUYPUYPUYRVVRVVRXROVPPWJMSGJP?HM=IK=GJLg7Db1>\%5W*:[;Gl:FkHLvQU€nišrmžwmŸ‡~¯†§Šƒ¤‹¡‹¡ƒ€œeb~NIrQLvWN{c[ˆaX€cZ‚i]ma…i_ƒ]SwhZ|pb„wer_{|gx}hyqqtddvob|uh…„€~z[c`-51 %++)(+++***)*/169#(I:>`FMnOVwdq„‘®vŠ¥x§|”²w®p®nŒ¬mŒ°f…©Uvš_¤_ƒ¤]‚£c‚¦mŒ°w’´lˆªf¢d}Ÿd{›bzšo€ xˆ¨t€›kv’}ˆ£„ªƒŒ¨‡¬¯…³Š—»’ŸÃ…šÂ~“»‚—ƆšÉy‘Äo‡º[r§BYŽ9L8K~4G{*=p"5d(WSk—€˜Äh“¼_Š³Q‡²O„°Jw©6c•/J|6QƒOc”Oc”C]‘G`•/eœDz±@‘¼F–ÂQ¥À[°ËZ¢ÓBŠ¼M¶UŠ¾X…ÂW„ÁZ…ÂY„Á;r´.e§2v´6y·@€ÃG‡ÊS‡ÅFz¸@rµDv¹F}ºI½F{µ?u®Gs®7cž!X›Axº`¯ÆV¤¼>›Ì9–Ç2…¾r«.e™ W‹Av 0f0f6lc¢=‰È:Æ7‹Ä%p­?ŠÆZšÈ]Ì[˜ÎOÂO†½L‚¹Kz©Aq Nj‘EaˆK\|BSrIQgHPfML\32C-,('(() -%'0)*3-/1124873873::.::.96)42%*)$#$!%#((..55GG%`]0EB>9 D>JBQITO VR#HALEWS6TP3KK0;; MS'MS'PZ!Xb*Wa/bk:fsLz‡`„Œv‡Žxvrp731:/HM=EH9DF8DF8FI;GI=JK@JNKNQNNTXSY\S[eU]gO^pSasMbzUk‚RnWs“Vx›[| [¢[¢[~Z}›`|š`|šd}—e~˜i€šj›o† qˆ¢q‡p†œp†œo…šnšnšnšm€™k€šk€šk€šl›kƒ›kƒ›m…šm…švŠ’vŠ’††ˆ’|†y‡‹z…ˆx€€€{{{lrƒhm~\j…\j…Ui†Ui†Sg‚PePayK\tJSfEObDL]FN_AQfJZnJd}Sm†Uu`€›g†œj‰ rŒŸt zŽw‹šx‰›v‡˜v‡˜u…—s†—s†—q†—q†—s…™s…™u…—q‚”o|Œgt„dnz]gs^tpzŒ†˜ƒ†˜ƒŽ›tŒ™s™wŽ—v”xŽ“wy“{‰~~wlfwpk|fi„hj…ak…dm‡fpˆfpˆkr‡ls‰n{‹vƒ“}Ž’“‰“‘‰“‘ŽŽ’“‘’—’—•œ•œ”£ƒ—§‡›¬sš«rœ©nœ©n¥qš£o&&&&''&&&&%%$$$$######""    !!      !!"###""$%'') ) ) ) *!*!*+ + + )) * * + + *'%%%%$ "$ %!"#!" ! !!!!!"20@>`c#‚…E‡“F‚BuˆIgz;NXAIS=Y\Tpslpyl\eXU]SU]S?LN6CE7>QKReSUwTVxkfgbŒx Š€©…ž†€ŸŠ‡£…ƒžfdCAjB;jJCrg\‰qf“j^‡mbŠl`„oc‡oaƒn`‚p`wg‡ratct_u~i~ˆny€eqxab‡pq€rkŽ€y—“„‹‡x]]]---",+--.+ 6)(?0+++.4#*F8?[FNjPYut‚›Ž¦q…¢vŠ¦s­p‰©h†§e‚¤jˆªh†§i„¦hƒ¥mŠ¬jˆªa…¨g‹®r“¹mŽ´iƒ¨b|¡bvšbvšdu›l}£p‚«Na‰$0l7C~AJ”EO˜Wd©x…ÉÈ„“Ì•¦Ã–§Ä™±È—®ÆŠ¤Ð{•Àc·Mj¡7P‡4M„3I|+At%6o"3kF[|‘Äk•ÃbŒ¹W‰·L~­Js©=f,I‚Heže‡½^€¶Ks¥Go¢Bq¤Ix«6|²3z°7”¾I¦ÐV¨ÏHšÁC¾E‚ÁO}¾L{¼P{½Nz¼;m«0a /c¡=q¯M}»RÀLxºEq³D{¶R‰ÄV¿]¤ÆS¡ÈG•½6y·(k© kª/z¹DœÐ>•É8ŽÍ2ˆÇ2€º$s¬,g$_•(R†?t/hC{$tµ3ƒÄÅy½h®#}ÃGÆO•ÎU•ÊLŒÁO†»H´My§AmšIeŠB^ƒDWv;OnBNa;GZDCS:9J!!.&##"#$#!##!!   !"%*,016;nyE‚‡m€„ka_fHEL84F84F57D@CPGQTS]`gmolrspqsqrtpqsnprjkoefj]`dVY]RX[QVZNVYNVYNVYNVYNVYNVYNVYNVYNUVNUVNTQLQNMQJINFILBGJ@EG;EG;DE;FG>CGBGKFIMOMQSJS[MV]OZhR]kN^rN^rOe}Sh€OlŠWt’SyžX}£\€£\€£\~Ÿ[}ž`|š`|šb~—c˜h‚›l…Ÿpˆ q‰¡s†žr…r…oƒ›m€™m€™i™i™j™i~˜k€šl›iƒœk„žnˆ˜o‰šwŽwŽ’ƒ~‚ˆ‘}‡|‡Š|…ˆy~€€xzziq‚em~Uk‚Uk‚Sj„Sj„TiƒOd~PayK\tKUgDNaBL]HRbCUiI[oId€ToŠYx•`œi…œmŠ¡tŒ¡tŒ¡x‹Ÿv‰œu‡›t†šq…™q…™q…™o„˜m…˜o†™q†—q†—t„–p“n{‹gt„`rt^pq}“y…›†–t‡—u”x”x–|Ž•{“{‹ŽwŒ}‘„ƒ†pprnnzrs~sz€u|‚u}€w€ƒ‚„…‡‚‡‹‡‹Œ”|˜€–žz–žz•œr”›q˜œu™v—žv—žv™ x›¡yœ¤w¥yž§xž§xž©l¡­pŸ«l©iž©l¨k£u›¢s''''((''&&%%$$$$######""        !!"###""$%''((&() ) (*+ + * * ))**('%%%%$ """!"!" ! !!%86TR0{~>‹NŽ@}‰<{OuˆIku_t~hwzswzsqzmcl_[cYT\R3@C$'+2F\cwbd†gi‹vq›e`Š‡~¦„¬…žˆƒ¡†„ xv’IGp53\@9hLFtqf“lbqeŽsœuil`„k^€ug‰Œ|œzk‹kZxkZxxcy‚mƒ”y„Šp{xabhiˆzs„wpŒ}™•†X\d'+3$(--/.5/.E3)--.3#*F:@\FNjW`||Š£x‡ oƒŸr†£nˆ¨jƒ¤e‚¤b€¢e‚¤i‡©g‚¤c~ fƒ¥jˆªk²i°u–¼t•»l‡«a{ `t—_s–\m“aq˜dwŸoª@LˆV+4~6@‰CQ•P]¢Zi¢yˆÀ•¦Ã¢³ÐžµÍŸ·Î¨Ó„žÊs‘Ç_|²G_–:RŠ8M6K~*;t"3k>S†v‹¾m–Äf½`’ÁR„³R{²Bj¡/K„Kg h‹ÀkÃ_†¹\„·W†¹Sµ8~µ=ƒ¹:—ÁD¡ÊR¥ÌF˜¿2o®0m¬Cq³Hv·Dp±>j¬5g¥2c¢BvµVŠÈ]ŒËHw¶1]Ÿ9e§Gº[’Îa§Éj±Ó]«ÓX¥ÍM‘Ï4x¶+u´6À7ŽÂ8Ã0…Å'|¼&u¯n¨,g&a—-V‹H}2j*Y‘-~¾2‚Ã!…ÉÃs¸v¼F‹ÄIÈQÆHˆ¾G~³Ax­Coœ8d’@]‚;W|?Rq6Ih9EW2>P76F,,<,%!"!!## !    ! #"%*,018=;C@!LI)8<04?IHSEUSc%gr>}ˆTˆŒsz~eb`gXV]OK^gcv`bpEGUGQTOY\^dejqrpqsqrtpqsnprjkoefj]`dVY]QVZPUYNVYNVYNVYNVYNVYNVYNVYNVYNUVNUVNTQLQNJOHGLDHKADG>EG;EG;EF=FG>CGBGKFIMOMQSJS[PYaMYgQ\jK[oM]qLayPf~NkˆVs‘RxW|¡[¢[¢\~Ÿ[}ž`|š`|šd€™ešh‚›l…Ÿq‰¡q‰¡r…q„œq„œoƒ›oƒ›nši™h€—i~˜i~˜j™k€šiƒœk„žnˆ˜o‰šwŽwŽ€“„’ƒˆ‘}‡|‰‹}…ˆy|~xzziq‚em~Uk‚Uk‚RiƒRiƒRfNc}M^wIZrJSfDNaBL]FQaEXkM`tKg‚Up‹Yx•`œkˆžpŒ£tŒ¡r‹ x‹Ÿv‰œt†št†šq…™q…™q…™o„˜m…˜o†™q†—q†—rƒ•o€‘ly‰fsƒ_qrt†‡ˆŸ…ƒ™†–t„”q•y”x”z”z“{Ž‘z‘’’‚|{~zy{}}‰‚ˆŽ‚‰„Œ…‘Œ‘‘•‰‘•‰’š‚’š‚–žz•œy—žt—žt˜œu™v–u–u—žv˜Ÿw›£vœ¤wœ¥vœ¥vž©lž©l©i©i¨k›§j£u£u''%%&&%%%%$$##$$!!!!    !!#$%%$$%%&&&&&&(('(****((((&&$#""" " " " """! '( +34QNsq;ŠJ‰ŽIŠ@„D…’R…’RŽUŽU€Yz‡Sr…RfzG[j?P^3=B438)?AI]_hefnn•oh’jcŠ£‰€¢…š…š˜ec|>;`A=bUJycX‡o_sb“{l˜yj•ykteŠnare…„y“vk…te…l]}oZvwb~Žo†qˆ“w…|ao‚iqu~ƒss‰yy‘’khŠŽnƒbV[L"'")'*+----04)0L”*:…#3~7O‰n†Àt˜Éj¾d‘ÁU‚±Qw®AhŸ,M…Hi¡]ŽÁXˆ¼IŠ¶P‘½L•¿EŽ¸=±=±:‹¾B“ÆL™Ê>‹¼%c¤Z›5e©:j­)\3f§V—ºg©Ìj´Ìh²Êa›ÏB{¯!F‰:}"C‰7XžH{µ^‘ËV˜ÈQ’ÃD‹Æ=„¿>’ÂFšÊ5‘Ë+‡Á-}·"s¬#k¤ h )ež&c›&XŽI~1m&Y•1y¹5}¼'…Å(†Ç'‚Àw¶?…Ã>„ÂN†ÁF}¹Ju¬Al¢<[‹,L|1Ei.Be5C\/=V.5H#*>/'"$  !$"($)1638-2.3687:@GHOZaJQ9@:A6;OUUW)OQ#c`9C@02') *4 :DN]$[j1jt5is4bg>puLhlbQTKNRTcginsp]c`TZVSXUY]]gkkjrpmtsnprmoqjkoefj]`dVY]QVZPUYNVYNVYLTXLTXNVYNVYLTXLTXNUVNUVMSOKPMIMHEICDG?CE>CC™N[¯frÇ„ŸÁ”¯Ñ›·Ê—³Å™³Ì‘«Äe¹1K„+~0ƒ0†#3‰#3~#3~5M‡k‚¼r–Æo“Äj—Æ^Œ»Ov­6\“=u*KƒM~±Rƒ¶IŠ¶MŽºEŽ¸A‰´A…µ8|­1‚´;Œ¿E’Ã)v§\]ž0_£:j­AtµZÍk¬Ïo±Ôg°Éb¬ÄZ”È9s§>3v2x .tIƒGz´U—ÇL¾6}¸=„¿G›ËD˜È5‘Ë(„¾&v°k¤ežd&c›'dœ'Y @u,hM‰1y¹?‡Ç+ŠÊ*‰É%¾t²4z·1xµE|¸?v²=iŸ(TŠ5d&U !E C$=!:1*%%!#   "$&%' &,+0:?#/4.3*/ (*),-318CJho,ZbDLCIW]#OQ#?AYV0NK$)+ ') -7 =FZh/_n5V`!Yc$JO%QV,twnruk~‚ƒ}‚rxuiokIOLGMJUZYbgfipolsrnprmoqjkoefj]`dVY]QVZPUYKSWKSWJRUJRUKSWKSWLTXLTXMTUMTULQNIOLHLGCGBCE>AC,1L5?YDNhN[ro{’s‡l€–bx–bx–\yYvšJo•Af‹EhŽHl‘Ll’No•KjPn•RxZ€¥fŠ´jŽ¸l‹¶h‡±g¦Zt™Yl’UhŽSgŠSgŠcp™kx¡rx°ŸËKÅ3x¬!B}1k1m'9v=_b„µ_”Â^“ÁD‹»J‘ÀO™ÍH’ÆB„À'j¥/g 0h¡']›#Z—)]›.a %RŠ5m2kAz _ž.m¬!xºr´h¦c¢1oª1oªGt¬Fs«6R‹,e NI> ;30('$"    !$% &(#%)'++.-115,0%'!!""#&2AMfs*Taalcmfk&ej%]a.NQ,,+*'.5<S\(RZ'[a%QXdg6†ˆX‚‡m‚‡m‚Šyˆy€qKSDMOCKMAQUP^b]kqnkqnjrpipohkobei\_cUX\PUYMSWJQWJQWJQWJQWKRXIPVKRXKRXJSTJSTLQPGLKIJGDECCDA@@>@>>@>>C@BEBECDIGHLDKQHOUJR\MU_JXeKYgFYmHZnF^vKczIg†PnKršRy¡V~£V~£XŸXŸ`€›aœešf‚›m‡ pŠ£rŠ¢q‰¡s†žs†žq„œoƒ›nšnši™i™i€ši€šj›j›n†o‡žvŒ–vŒ–’Š~‘‰†“†“Š€‰Ž…‡‚„†yz…rs~em„ckƒTiŠRgˆOg…Ne„Oe}LayL^tEWmESeAPb?OcFVjB[uJd}MlŠUu’[{–aœk‡ nŠ£sŠŸr‰žv‰œt†šoƒ™oƒ™m„™m„™mƒ›mƒ›nšoƒ›m„™lƒ˜p‚i{ˆo‚xˆ›‘”¡œ|”x”xyŒx“t•v™q•žv™¢xš¤y›¦p¨r¡«l¢¬m¡¬iŸªf©e©e™¦d™¦dœ£ež¦gš¤lš¤l˜¤m˜¤mš¡rš¡r™žt™žt—žv—žv•w•w—žv—žv”›q–s– n˜¢q˜¥l•¡h•¡h•¡h•£e”¢d•£e•£e''%%&&%%%%##""""""!!      !!"#$$$$%%&&&&&&((((''&&%%$ $ $ $ #!"   $'25SR'xvK‚†J€„H~ˆ@„ŽF†“L‡”M‘N‘N’R~O{ŒRr„Jk}F_r:Q`4HV+AG;U[Olrƒ€…—¨ll•qg’x¢€t’€t’€{xsˆPJk<5V93[GAicX‡i]n[“iWŽbR‚iY‰hV}l[‚yf‰r_‚n[xs`|†u“‡v”zf…vc…i€€d{x_ly`my^jT9Dxdk}hp]V<{tZ£©v“™ft€QjuFbkUr|f268BFH88F&*..- :'+F5?YDNhQ^uv‚™r†œk•_t“]s’Ur–Xt™In“>d‰>a‡Cf‹Ee‹@a‡CaˆCaˆAf‹EkTx¢^‚¬e„¯c‚­h‚§e€¤_r˜TgXlUiUb‹^k•ou­u{³FM—@G‘P`°k{Ê™¾«Ï”·ÊŒ¯ÂKi¤;Y”0“ ‚}}y(/‹2=3>‘2F…fy¸qŽ¾{™ÈsšÈr™ÇY|±#Ez [ ++eBz*Y‘)q¢-u§(p #l›'gš%f™#gž&j¡)eŸ"^˜,^œ4f¤Ar³N~¿W‘Ê]—ÐDšÃ<’»/¼-Ž¹D‰¾2v« A{&a%b%8tPq¢gˆºX»^“ÁN”ÄP–ÆJ“Ç?‰½3u±V’ C|M†PŽS"V”#W•D}5m.f2kG…(g¦n¯g¨]œS‘ ^˜'eŸ=i¢=i¢#@yUIK? ;30('#"   !!!"#!&'$')$)+&,0$(, +.+.)-&+! !"#.7BXe]k"Va_j_dhm(^b/MPBB0/-4 =DKS QY&X_#_f*X[+dg6osYw{ax€o‡vˆyt|mnodVXMEICPTOioknspjrpjrphkobei\_cUX\PUYMSWJQWJQWJQWJQWJQWJQWKRXJQWJSTJSTKOOFKJHHFBC@@@>>?=?==?==B>ADACCDIGHLDKQHOUJR\MU_IWdIWdEXkHZnF^vKczEcƒLjŠIp—PwžV~£V~£XŸXŸ`€›aœgƒœi…žm‡ pŠ£rŠ¢rŠ¢t‡ r…q„œoƒ›nšnši™i™i€ši€šj›j›m…œn†vŒ–vŒ–~‘‰’Š‡”‚…’€‹‚‰Ž…‡‚‚„xy„rs~em„ckƒTiŠRgˆOg…Ne„Oe}LayJ[rDVlESeAPbAQfHXmD^wLfPoŒWv“[{–aœk‡ nŠ£sŠŸq‡v‰œt†šoƒ™oƒ™m„™m„™mƒ›mƒ›nšoƒ›m„™lƒ˜oŽj}Š“‰†šœ|Š—xŽ“wŽ“wŽ‘z’•~”š{–œ}™¢yš£{›¥z›¥z›¦p™¥o ªkŸ©iŸªfœ¨dŸªfœ¨dªh™¦d¥fž¦g›¥m™£k˜¤m˜¤m›¢s›¢s™žt˜s–u–u”œv”œv–u–u—žt›¢xœ¦t™£r™¦m˜¥l—£k—£k”¢d–¤f•£e•£e%%%%&&%%####""""     !!!!""###%%%%&&&&%%((&&&&&&%%$!""""  !# +%' 79 LNnq3ƒ†H{„E}‡H~ˆP„ŽVƒ‘T€ŽP~O~O€‘Q}ŽMwŠLuˆIh}@]q5P^5CQ(AG=^dZu|‡Ž¢~¥ij|s•x™{rŒ|sŽyuie~@;`71W?8bSLvbV†cX‡cUŒdVbQ†_M‚aQeU…jY…n]ˆo^yi‹{lŒ‹{›xf†fTuŽz”Žz”…p…ze{s`qL8IZDOˆr|nZWO<8ŠpvqVelKhnMU_GdnVhi`opgvuyVTY**?-//#;*0H4>WBLeHVqr€›l€ey–Xo[s“Qo–Pn•Fk“>c‹8aˆ9c‰:]‡4X‚5Z;_‡9\‚=`…CeFhKm•Mo—Vo˜[tdyŸWl’TgQdŠUcŠYhŽ^mšjx¦es®L[–Wg°izÂtƒÑz‘ÛMd®5C¢O\¼+2‰rc!n5?†OY O]ŸFT–z°'iž!c˜"_• ^”+^˜1dž0bš,]•/]˜=k¦H‚¯eŸÌWªÐN Ç)ˆÆ~¼(v°%t®9n¢#WŒ4i,`%]$8pEcžZx³L´R„ºG‡µNŽ¼OŠÀD¶Cn¦:sOS,`%>s-Q‰+O‡=u6n:lEx.d›:q¨%h¡ašTL‡'WŒ5ešEd–?^'7eB@A:5/-*)$$" ""  !")&"+($*( +*!,*1.!34/0+''$"!! !!$/2"AFFK"SaSa\iS`ahu|2ILGI?EKQ%OV%T\+Z_&SYc`5a]3f_Pc]Nddbijhz|~rtvnnn^^^BEDOQQcifnspnrrkpojkocdh\_cUX\NTXLRUJQWJQWIPVIPVJQWJQWJQWJQWJSTIRSINMEJIGGEAB?>?=<=:8::9;;;=??@B@CGDGKBLQFOUGQ]JS_GUgHWiDVjFYmE]tJbyEcƒKi‰Ho–OvS{ S{ W~žW~ž`€›b‚i…žj†Ÿnˆ¡pŠ£rŠ¢q‰¡s†žq„œq„œoƒ›oƒ›oƒ›i™i™j›j›j›i€šo…šp†œy–y–‘Š€‰‡’€Š”‚Ž„‹………uv†oq€_k†^i…TiŠRgˆNe„Ne„Nd{LayK]sEWm?OcAQfCWmFZoE`{Ni„OpWx•^~˜cƒžlˆ¡pŒ¥sŠŸq‡u‡›s…™q„œoƒ›mƒ›mƒ›kƒ›kƒ›mƒ›mƒ›mƒ”k€‘m…w‹Ž¡ƒ‰œ~™zŠ•uŒ•tŒ•tn’ p›¤oŸ¨rŸªoŸªo ªiŸ©hœ¦g§hœ§e¨gœ¦g›¥f¥mŸ¦n™¢s›¤u˜Ÿw–u”˜~“—}“‚’•…‘–‘–’”‘“Ž‹‹‰‡ŽŠ˜ƒ‹™„’¡}•£›¦wš¥v™£v—¢u•¡q”Ÿp—žt—žt–s–s%%%%&&%%####""""    !! !!"""##%%%%&&&&%%&&&&&&&&%%"!!! $& .0GJcf5}€B~Cx‚C€ŠK„ŽV„ŽV}‹Nx†HyŠJ{Lz‹K{LwŠLv‰Jey=Ym1P^5CQ(@E;ekaw~‘ˆ£€¦ggŽx™{r”{rŒ}tzv[Wq<6[94YD=gWOyZN~\Q€`RˆdVdR‡dR‡fV†`P€RAmJ9dUEgYHkWHhdUukYzQ?`cOi_Jd^I_WBX]J[H5FE0:p[e€mikXTŠpŽ‰o—velKP[BcmU[\Shi`dbgTSXKK_<gŽ:d‹;_‰6Z„4X€3W3W|0Sy1S{3V~:]…8Z‚>W€=U[q–`v›TgNa‡UcŠTbˆP_Œ[i—ft¯`n©N_§gwÀnˆ¼r‹À"9ƒ#:„.<›#0so'-yMS hrºt~Ågv¸Q_¡@QŠt†¾lˆ¼lˆ¼`³Pq¢Xw¯Cbš6t#;y5]–Bj£OŠÀI„º/q¦"eš ^”[ T$W‘,]•+\”4bœ?n¨\–Ãn§ÔP£É=·s±o­h¢h¢+`•=r '\$X$\"6n4RIg¢Dw¬K}³E…³V–ÅOŠÀ@{±9cœ0iOKK)^0T‹4X,M…&H€$L4[ŽF|³K¸6z³&i£Y“N‰)Y1a—>]1P‚"3`?>A85/-)'$$" ""!#$$$$-*&/+'0.%21(;9+DB5AB+BC,;8&'$"'*5:>CJX]k&er#\iip&pw-X[$FHEKMS'FNOV%^d*v{Bhd:^Z/\VGXQBZ[Yijhxy{BDF777YYYCFEbddekh`eblqpjonjkocdh\_cUX\NTXLRUIPVIPVIPVIPVJQWJQWJQWJQWIRSHQQHMLFKJDEC>?==><:;87998:::<>>?A@CGDGKBLQFOUGQ]JS_FTfGUgDVjDVjBZqG_wDb‚JhˆGn•NuœU}¡U}¡XŸXŸaœb‚i…žj†Ÿo‰¢pŠ£rŠ¢q‰¡s†žr…oƒ›nšoƒ›oƒ›i™i™j›j›kƒœn…Ÿo…šq‡y–y–‘Š€‰‹•„Š”‚‹Š‹€ƒƒƒ~~~tu…no]h„_k†TiŠRgˆNe„Ne„Nd{LayJ[rDVl>Nb@PdCWmI]sGb}Pk‡QsXz–^~˜cƒžnŠ£pŒ¥sŠŸq‡u‡›s…™q„œoƒ›mƒ›mƒ›kƒ›kƒ›mƒ›mƒ›o…–l‚“tŒˆ€˜”‰œ~‡š|Œ—xŽ˜y™w’›z•£s—¤uš£m¦p§m§mœ§e›¦dœ¦gœ¦g›¦d¨g ªk ªkŸ¦n¥m˜¡r–Ÿp’™q—oŒvŒvŒ’•…‘–‘–‘“Ž‘‹ˆŒ†ŽŒ‰Š˜ƒ›‡•£–¥€š¥v™¤u—¢u–¡t•¡q•¡qœ£y¤z›¢x™¡w$$$$$$####""!!!!    !!""##%%%%&&%%&&'&%%&&&&%%"!   +"30 +HEhj.}Cu;v‚/$=6)B>1JC1DH7JhW\dRWOE;—ƒ–˜~uw^dfK†ˆlxxjssfojr`[cKG`@—%--5–+.„s c)2uVg¡r‚½€“Æ€“Æq„¾L_™;L‡iy´f‚·hƒ¸a‚µFg›9t8Q8R’8R’Dm«Dm«L‡ÂB~¹1n¤%c˜&Y“ T#N„(TŠ/X.WŽ*]—Fy²\¨ÎX¤Ê2ŽÏ%Âj·]©RV¡(I0u(j#d a.o)F†8U•Di£Nt­T‚¶Zˆ¼K~±@s¦(R…)[CCJ5a9\Hkž?b“&J{%Iz5YŠCy«P†¸BŠ¾7~²=€³&iœ2a“3b”>W‡,Du+R94631/-)'##  000<<5??8EA2D@1FD4FD4D=/>8)5,$'! &&*/27"BLBLMY\i"uz)qu%lj&ge!QKJD<7:75965967:77;<:>@;BH>FK?ISCMWEP^FR`DRdESeDThEUiBWqF[uF^Kd‡IjSs™QvœSyžVyŸX{ aŸc‚¡eƒžk‰¤pŠ£pŠ£o‡žn†s‡r†œnšoƒ›nšnšj‚šj‚ši€ši€šj›m„žo†™p‡š{‘{‘‡“ˆ…’‡Œ’†‰ƒŠŽ‚‡‹ƒ‚…~~€qt…kn€Zj…Yi„RhŒQgŠNe„LdƒNc}K_zHXqCTlAQgCRh@XqE\vFc€NkˆRt‘Xz–_™e…ŸrŠ£rŠ£tŠ¢tŠ¢u†žsƒœq„œoƒ›mƒ›mƒ›mƒ›mƒ›mƒ›mƒ›tˆŽp…‹†‚‹¡‡rŠ™m‘Ÿo“¡q˜¥l™¦m™§k›¨mš§eš§e›¦k›¦k™£k™£k˜Ÿn—žm›£r¤s¢xšŸu™™~˜˜}‚‹ƒƒŠˆv€ŠxƒŒ{‰–Žœ…‘–…‘–†Ž˜…—ˆ™„‰–‘‘‡˜‡ŒœŒ™¥ƒ˜£‚—¢u—¢u–Ÿw”uœx“ {—œ…˜†•™‡”˜…$$$$$$####""!!!!    !!""##$$$$&&%%&&'&%%&&&&%%"!      (%A>_\6‚GFp|6wƒ=z‰I€Ox‡NpEn‚?q†Cx‡EyˆF{L}ŽMxJoƒ@_s7Qe)ET-2A*6(`k^{„—|…˜chŠchŠun‹ph…yp†|rˆg`}F>[3/V94\HBn]Wƒ]WŠa[ŽndYOˆDCu10c..^11a*+X56cB=g<7a>9\=8[=9Y62R@5T9.M7-E4)B6)B;/GH7J@/AWEJgV[=2(vka‚„kŽv‘”x“w‹‹}„„vvqyb]eKG`B>X>9U94P0/O/.N46S9:X9Cb_jˆf}ž]t”Ro“NkKjHfŒ?fAh?i•:e7_‰2[„/T{*Ow0Ls%@g%>h,Eo8S~6P|(Cl5^ I!4aL^‡PbŠUb‹P^‡NZ†NZ†S_’\i›`p²[k­:G w (‰#,Œ+.„!$z$.qR[žt…Àƒ”ωœÐ…˜Ër…ÀFY“8HƒN^™_{°f‚·\}±Mn¢WS5uMg§bŠÈ^‡ÄF‚¼;w²1n¤#`–#VO‰ K‚$O…,T‹$Lƒ%X’`”ÍW£ÉM™¿%Âtµ`¬J–M™$Z¦&G -s(j&g+l"5v&B‚1NŽ7\–>c?m¡>l 9lŸ0d–Fx$VBD'S*Bn8[ŽFiœQu¦Imž3Wˆ,P#X‹:p¢:¶:¶<²<²Ds¤/^7P€)BrF6 3831.,)'##   $$$000<<5>>7C?0>:+65%43#71"6/!/&$   ((!.327":D4> JVUbhlkoki%ge![V)TO"NR&EHQS%ab5fb7\Y.\P7QE+4.$SNCXYVGGEOPTfgkRSXABFNTXlquqrvmnrkmodfh[]dTV]MRXKPVGNSGNSGNSEMRGQVGQVFOUFOUENSCMRFJLAEGADA;><7:75964755966:;9=?;BH>FK?ISCMWEP^FR`APbDRdAQfDTh@UoDXsB[~HaƒHiOp–RxSyžVyŸX{ aŸc‚¡g„Ÿi†¢pŠ£pŠ£q‰¡pˆ r†œq…šnšoƒ›nšnšj‚šj‚ši€ši€šj›m„žo†™p‡š{‘}‘’‡“ˆ…’‡Š‘„ˆŽ‚ŠŽ‚‡‹‚ƒ}|nqƒkn€Zj…Zj…RhŒQgŠNe„LdƒNc}K_zHXqCTlAQgETkBYsG_xGdPm‹Vw”\}šaœhˆ£rŠ£rŠ£tŠ¢tŠ¢u†žsƒœq„œoƒ›mƒ›mƒ›mƒ›mƒ›o„œo„œuŠyŽ”‡žƒƒ™rŽœq’ p”¢r˜¥l—£k˜¥j™§k™¦d™¦d™¤i™¤i˜¢j˜¢j›£rš¢q™¡p™¡p—œr”™puŒŒq„†x‚txxzzs~ˆxƒŒ|Š˜Žœ†’—†’—†Ž˜…—…Š—…Š—‘’‰šŠŽŸŽ˜£‚—¢€• s–¡t•žv“œt‘y—£˜†˜†•™‡”˜…%%$$$$$###""""!!   !!!!    !!#$$$##%%%%%%&&&&%%&&$$##!     )'98[Z~}B€…GuzdVO~]V…OK€QLXTŽDAz33h./c*._03e)-^/2d69k26g39d28c8;b/2X1/V0.U/.N-+K.-K-,J;5M50GC2BMi&7b"RC$3^N]ˆN]ˆIX„N\ŠLZ‡P[‹Va‘]hŸal£#+sf$n(,w(/p%-nN^’zŠ¾ŸÈŽ¡ÉŽ ËŒžÈu„¶Wf—>R9N|cw¦r‡µg}®h~¯,dH $bZr°[“ÌN†¿8}¶-s¬3m¦!\”%P‡"Mƒ$I~#H}JBy.m b ÓG•Ï:‰Â)v»c©G:ƒ"C‰&G6v !a ]*f2p$={$D(Iƒ+R‰/UŒ-Y2^’/`‘#U†?A9:<57946826837938<7=@8AG;EJSm@Uo?YwE_}EfŠLm‘Lr—RxUxžX{ aŸeƒ¢i†¢jˆ£pŠ£pŠ£q‰¡pˆ r†œq…šnšoƒ›nšnši™i™i€ši€šj›kƒœs‰˜s‰˜‘‘‡“ˆ…’‡Œ’†‰ƒŠŽ‚‡‹€€‡{{‚jq…em€Zi‡Yh†QgŠQgŠPh†LdƒNc}K_zHXqCTl?QgBSjwƒF|ˆJy‡Ir€Bm}Bjz>i{?j|@rDu‚GwŠDyŒGt‰Ck;`s;Qd,AO-3A*,,bdd•”±Ž¬wy”ux“njsnƒ{oŠ}rŒVLp9/S;5ZE?eZT‚a[‰OK€MI~MJƒ<8r,,a./c03e37h-1c48j9=n7:l4;e17a58^.1W0.U.-T/.N0/O.-K/.L82J:4LE4DE4DJ<>C57ut]w”•y’’w‰‰|qkfpVQ[D<`@9\70Z5-X4-U4-U./O23R/6WKRsi€£Zq”Ro“Pl‘JhGe‹>`ˆ=_‡;a:_‹6\ˆ/U0Q|+Mw/Jl(Ce!8[-P#G C'Q)T!QO"M7FqRaŒKZ…KY†IX…KV‡MXˆT_–]hŸT\£"*qeb`AIŠr‚¶ŒÑ’¤Ì¢Ê’£Î‰šÅn}¯Yhš6;DPXd%ep!Zede'jj-LIFCHF$WU3]W8^X9je?wrLecAQO-gfMf}{r„‚yech<:?QQXggnrswlmqjkobbg[]dTV]LQWJOUEMRDKQEMREMREMUEMUEMUEMUCMRBLQDHI@DF;=?9:<57946826837938<7=@8AG;EJSmq.6i-2a-2a32W43X:7Z=;]0-R+'L3,H5.K:.E?3IE8ED6D\SL”‹„~†„|vtkcbkNMV=8[95W3-Y2,X0,T2-U/,Q-*O+-QLMrn‚¥]q•NkJf‹IgŽHfŒAbˆ>^„>^„>^„:Y„5U3Py.Lt.Gj*Be$:Y)G<<!D!D G GM&TOZŠR]ŽOZŠMXˆHT‚JV„N[„TaŠhp£=ExKM (b[bœ€‘ËΑ¤Ê“¦Ì”ŸÍ‚Ž¼o|¨T`ŒGN+\l'\l'MVXa$SY&EK@?CCNM'ML&QU"VZ'SYdi+zyL†…Wwq`qlZe^fA:BRQUljoqvuqvukojeid\`bSWXMRXGLRBKSAJR>KM>KMAMTCNUAMTAMTBMPALNCHL=CF;>=8::157045/5817;0:=4?B6@J:EN8FQMd:Me;Nf;TnAZtB_IfˆMn’St˜Uvš[| ^ža‚¡h… jˆ£nˆ¡o‰¢q‡žp…p†œm„™m‚œl›j›i€ši€ši€ši€ši€šk„™m…šuŠ–v‹˜Žƒ‘‰•…†’ƒŠ’ƒˆ€ˆ‹ˆ…ˆ…€ˆzy‚gp…cl‚ViˆUh‡OhŠNg‰LdƒJb€G_xD[uCVo@SkAUkCWm@YzG`HjŒPs”Vy˜\žh„¢kˆ¥rŒ£rŒ£uˆ¡t‡ p…p…m‚œm‚œkƒœkƒœi…˜i…˜rŽƒ{˜ŒŠ£tŠ£t•¦o•¦o˜¦i—¥h•¡h’Ÿfšmšm”q˜t’˜w’˜wŽ”|Ž”|Œˆ‰†‚x{ˆtvƒbp‰bp‰[l\n‘Xm“Yo”Wr–Zt™Vv—ZzœcškŠ¢v¡x“£‚Ÿ‚ŸƒŽœƒŽœ„•ƒ“‰˜†žŒ•£”¢~” u‘r—™x—™x”•™‡•™”–š•”œŒ™†Ž˜…—######!!""!!""          !  ##$$""!   ! !#####%%%%%%%%%%%%%$$%$$$#"!!       +-& +* @?!bc,‚‚K‚ˆGy>w€C~‡J{ˆMs€EmyBht>ap=]l:Xi6[m:myBs~Hu‹CyHtˆAl€8^p;Oa,>J+*7##ED@’Ž¡•‘¤…’rorl}rl}rm‰ZUqA>a;8[A8`Er<=q98o+*a..e66m98o87n04l15m6:r@D|AH|4^„;\‚<]ƒ:[8W‚3R}2Ox+Hq0Hk*Be 5T$C!?!???EELM.9jWb’R]ŽNY‰KW…JV„HU~R`‰^f™^f™!%[ F18rgo©€‘ɚ̎¡Ç Æ†‘¿u¯fsžHU€9Kv[m—@Q~Zk˜bu¤_q Qc›"ZZ@`œT‰Ä@u±(h¤_›I~ =s>o"As(Bn'AmG}R}´Q”Ç?ƒµ#l®a¢S– FŠ>ƒA†)F†#@€+e(a0a 9i"Bq(Hw$N|'P~#S‚$Tƒ(VŠ%T‡#K~Bt5g1c:j&Aq*O{1V‚)Yˆ,\‹3`‘9f—Gp Ox¨f–ÇZ‰ºBw¥6k™6fŽ.^†1S}+Mw4Fn-V +:7,-*++)%%  "'-.+60,71-:/.;0-=1,;0+7.&2)'++$)('#(!""""&&&*)'-+)-/!-/!186=AQ M]S\DMHNZ`-ba;AAEDUT.dh5[^,_d&ch)ff8mm?QK:XRA\U]G@HLKPgejrwvsxwmqlimg^bdW[]LQWFKQ@IP@IP?LN>KM?JRAMTAMTAMTBMPALNBGK;@D:<<799157045/5817;.9<3>@6@J:EN9GR=LW@NYAO[@O_@O_>Md=Lc9Ld:Me9Rl=Wp?]Ge‡IkŽOq”Vx›[| _€Ÿcƒ¢h… jˆ£nˆ¡o‰¢q‡žp…o…šo…šm‚œl›j›i€ši€ši€ši€ši€šk„™m…št‰•uŠ–ŽŽ‰•…†’ƒŠ’ƒˆ€ˆ‹ˆ…ˆ…~‡xw€fo„cl‚ViˆUh‡OhŠNg‰LdƒJb€G_xD[uCVo@SkAUkDXnC]}IcƒIlQt•Wz™\žh„¢kˆ¥p‹¢p‹¢t‡ r…p…p…m‚œm‚œkƒœkƒœh„–f‚”x•Šž’Ž§wŽ§w•¦o•¦o˜¦i–¤f•¡h’Ÿfšmšm”q—s’˜w—v‹‘y‡u‚{}yuqtnp}aoˆbp‰]o“]o“Yo”Zp•Ys˜Zt™Vv—\{e„œkŠ¢v¡x“£‚Ÿ‚ŸƒŽœƒŽœƒ“„•Ž‹Ž‹‘ {žy’žsŽ›p–—w–—w‘•‚—›ˆ—›––š•Ž’›‹—†Ž˜…—$$$$%%##""!!         !!!!!!!""""! ! ""! ! !"!"!"!"  !!!"%%""%%%%%%%%%%%%%%%$%%""!          $$() CF_b+|ƒ@ƒŠGwƒ?{†By‡I|ŠMr€IlzDdrC`n>Yi;Rb4Qb/Yj8mz?v„I|F|FtŠBk:]o8M`)h42[71_B;jC?r96h,,b((_+)d,*f/.g65n43l43l/2m14o46qEHƒBI~3:o*0a(._./S,.R/.N/.NFBcKGg84N0,E71H3-EB4J;-C?28qdj’‚‹†{zwspmi_]kUR`IEc63Q)%M&"I(!I(!I%!F%!F#$J..Ucw›cw›MjŽFc‡Dc‰B`‡D]„?X€@X};Sx1Kr/Ho2Ip1Ho0Fd)?],I +969:9=>D"JLMTƒ]d“T[ŠFS|DR{HV}M[‚S\„_hCP{$O@Lco¤w†¸ƒ’ć•Ã»w‚­mw£Wa1g}5Yl4J]%:F))5.,*IGE{s~…xo|pgtojzsm~TOr?:]EBk31Z4-\=7e51d1.`((_&&],*f-+g/.g.-f21j21j.1k.1k69t=@z;Bw3:o)/`)/`24X*,P-+K,*J,'H)%F84N2.H4.F3-E:+B?0GA4;M?F‹†{Š„zxuqokgc`n_]kIEc3/M)%M$G' H&G!C!C G"#ICWzcw›NkGdˆB`‡>]ƒ@Z>W~;Sx8Pu)Bi#=d'>f&=e&;Z!6U%B41796<=AC!P5GdW`a\kD?NEFOlnwrtvjkma`dYX\OQZIKTCIRBHP?HNAKP?JR?JR?JR?JR@JM?IL?EI;@D7;<37915704525;57=3:@6>C6@Jw„;wˆH{Lw…Lm|CdrD`m?Xa>QZ7IS-EP*KW,[g?@!C)K[i‹OjŽE_„[1CY3DZ0@T)9M'<1,,3367:9#H%-SQ[‡WaO\…KY‚GT}DR{DMuHRyR^…Q]„K\‰[l™j{¨m}ªkvŸbm—T_‰8Dm*S&4]".Z -X(8g7Gv`q¥ZkŸLZCQ…,bWn£V¯Ajš-[ˆJw /Y1[9]!;`#=b/In9Z€<]ƒ2[„(Py CxAw&Az)C|/Cz/g SL +!V-Dy4YŽ,Q†*T)S€,Xƒ.[†+[ƒ+[ƒ2\‰2\‰4[‰(O}'Qƒ3]7b’6a‘Cg˜Nr¢Kf˜4N4Qƒ\y«]‚®Ot :cŒ2[„1S{/Qy2Ny,It+Bi*Ah+?c-P 4- ')'''&"""  '&*3-.71,;0-=1-=/-=//C0.B/-=/,<./:-,7*%-#$#"!&$"'&%(+*,00044427/27/-/*&(#$$!!&&0/$ED9UTI[VKRMBXS;a[Cc]>upQslYQJ7JC=g`Za[eXR\?BOfhuqszehn```XXXQURQURPWVFNL?IL@JM@IP@IPAJRAJRAKP>GL;BH6>C58<148.37/58/6>39A1:A4=E7AM:DP:HTM]@O_:J_9I]7I_:Ka2Mh7Rm;YxDb‚IhŒPo“Wx—Z{šbœh„¢n‡£p‰¥nˆ m‡žo‡žn†p…o„œmƒ›l‚škƒœkƒœi€ši€ši™j‚šs‡”t‰•‘Š”‰‹•‹“‡Œ’†’ˆŒ†††‹€…xv‰pn_k†]h„Si‡Si‡OhŠMeˆLdƒJb€F[uBWqATlATl=WpD^wC`‚KhŠMpUx—_c„¡k†¢mˆ¤tŠ¢rˆ q‡žo„œl›l›kƒ›kƒ›l…“l…“™|ˆ¡„–¬r•«qœªl˜¦i˜¢q•Ÿm’šp’špŽ—v–uŠ€Œ‘ƒƒƒwxƒjt‹jt‹]o‘]o‘[o“[o“Vn“Wo”Uo–Uo–So–Rn•Tp—Uq˜Uu–Vv—X{œ^¢jˆ¡o¦x¡z’£ Žž„’™ƒ‘—‡™†Šœ‰Ÿz›w‘šwŽ˜t–x“tŽ’”—‡–šœ—›—¡•Ÿ†ŽŸ„ŒƒŽœŒš!!##""""  !!      !!!#!!###$# # # # "!"!"#"#" " #!" "    !!!!!!        !!""!!""""%&&&&&''&&%%%%%%$$#!    !!            '& 54aa,KŠŽK~ƒ@u‚9{ˆ?z‹Kz‹KrHix>`m?Zg:R\8IR/>I#?J$KW,[g$'3)(&+*(aYa„|„hblgakhezjh|;5a60]/,\)&W&!R-(Y-,^10c-,e,+d,)g-*h,*f/.i1/j1/j.1k.1k*.f04l3:n5=p:Bp?FuQW|]bˆ_dƒbh‡deƒ_a~XYrHHaFF[JJ^_VeNDT5)27+4XSOyuvponhgeam[WcC?^40N+-Q#%IBA>?@>A+8ZOjŽF`…:X~9W}>Ww;Uu>St8Mn3Db-?\,=S0BX4CX2BV/;O!-B1,4776:=C#H-7cXbŽUb‹O\…CQz@MvCLtENvIU|VaˆWg”[l™`pžarŸ_j“S^ˆBMv"-W+U"/X$0\.;f1Aq@P€Sd˜O_“?M‚?M‚"9nQhT}­@i™,Z‡Er0Z7a :_!;`$?c3Nr1Rw-Nt#Lu!Is CxAw'B{$>w)aNL S.c7Nƒ8]’;`•:d’7aŽ0]ˆ0]ˆ-]….^†5^Œ5^Œ2Y‡0W…5_‘:d–=h˜?kšMp¡6Z‹0K},Fx4QƒSo¡NsŸ2.>2-=/-=/.B/+?,-=/,<./:-,7*'/$!""$&%((((----2*,1),-)')$""!!#"&&76+PPDe_U^XNc]EhbJlfGysTpiUjcORKEJC=@;EPJT=?M^`mnpvikrbbbXXXPSQY]ZT[ZT[ZJUX?IL@IP@IP@IP@IP>GL:DI9@E4;A58<148.37/58/6>39A4=E8AH9COXqE_xFd†NlŽNqUx—_d…¢mˆ¤p‹¦tŠ¢s‰¡q‡žo„œm‚œm‚œkƒ›kƒ›k„’p‰—‚œ~„ž•«q•«q™§j˜¦i•Ÿmši˜m˜m–uŽ—vŠ€‚‡xz{†vv‚hsŠjt‹^q’^q’[o“[o“Vn“Wo”Uo–Uo–Tp—Tp—Tp—Wr™Uu–Vv—X{œ^¢i‡ o¦y‘¢y‘¢€Ÿ€Ÿƒ‘—„’™Šœ‰Œ‹ŽœxŒšvŽ˜t—sŽ”u–x’•…š—›—›Ž– Š’œ†ŽŸ„ŒŒšŒš!!        !!!!!!!!!!!!!!!!!####$# $!$!$!"!"!"#"##!#!$"$"" " " " !!!!!!    ! ! ! !    !!""##!!####$$%%%%&&%%%%%%##%#$$!!!           + $$9:YZ(}@‰ŒL~†Ey@u†A{‹Gw‹JuˆHm{Ibq?Wg@O^7CP17C$2@:I$LX-[g'5*( *( jbeƒ{kfphblfh|adx/0]/0]$'[ T R$%W**^-.b,.f,.f++h++h,*f.,h20k-+g'*c*.f),`),`38gUZ‰ry¡‚‰±‚“²ˆ™¸p«e…ŸPuNsQu‹Aez@OXK[ctrl~{u_WK70#>63{spsmkoihbafWUZ@AS01C $@<>><;78< ?7MkMc>Y{:UxSk9Of6I\0CV1BM0AK7DF:GJBIHBIH<@A+/1"!)!,,/ =">@Il?In@KpBNsHTyX`Šgo™bk•[dŽN^‡EU},`)@g,Ck&Dl(Fo(Cs)Eu)Br'@p(9m&Z +OKM+\Bw2YŽ2d 5h¤:o¤/+>/*@/*@/+>/+>/0;.-8+$," "! #"""! ! !!%$1+0PHL^VZTHQ^RZh]ci^dg`Zf_Y_\J`]Kmhd]WTBALUS_ostjnpem\YbP`gRW^H5>+OXDU][GOM?HO?HO?HOF39A16<.39,39-4:.7?08@2:D5=H9CO=FR;IU;IU;IUjz>pDq…=o‚;f{5\r,Pc+AS1>'5,+"98/zrv|txhblgakeg{XZn*+X--[%(\!U R&'Y-.b++`*+d+,e**g)(f('b-+g1/j42n*.f&)b(+^/2eTX‡†µ˜ Ç¡¨Ï›«Ë¡Àv–±jŠ¥Uz”OtŽRvŒVzVenBQZif`ˆ††rYRE,$ e]YtnmqjibafWUZ>?Q./A $@<?@<:78<=,KE[y>Y{9Tw9Qm6Ok9Of7Ld7J]4FZ3DO2CN8EG8EG>EDCJIBFH?CE69=(+/ #.)*, =;7Q‚@T…?MpŸ;m›5k–4i•2i—2i—1g—3i™4iž5jŸ=kŸ!Oƒ2k%D|#9w,j%;y/F„0O1P‚.Nx+Ju,Gn,Gn,Bl%;e"7_4\ .N? ,*))''%$""" ")*'61(72)92*:3+=1+=1+>/+>/*@/*@/+>/+>/0;.-8+#+! !&    ! !!!!$#& &.&*D<@K?H_S\dX_dX_[TOe^XifU^[Ib\X[UQCBMOMYlprnrs]eTT\J=E/$'0FO;BIH?GE?HO?HO?HOoƒ9gz:Zn-G\+:O.7$'02+*PJHŠz†|lx`Yfa[gedyUUj**Z,+[&)_ WP$'[)-e&)b&(e&(e')f')f%%b)(f3-l=8v53h(&[32bLL|}¬•˜Ã¥Ã¦®Í¨Â{”®l§p”¬m—®aŒ¢W‚–W‚–To}9Ub_eR~…r˜™p„„[:9 FF-vtkmlcdadXTWEER33A$@<<<!:!:76;#?$?#.JBToDWq6Ne5Md8M^6L];KY:JXHG>HG@GAAHBCJ=BICD:341$$"!%20 ) +*#=IUoKXtGUpCOo?KkANpDRtIX|JY~VgScŠ@Ru2Dg(U&S+\:N9KŠ.?~1=”1=”0<ž*6˜(2‡'|,b+?c*Dk1Kr2Ny6S~3N~/Jz':n0c$V MK +M2b.Iy+[Š7g–4m¡2l .gœ-fš3i™2h˜/c”,`,b”0e˜.d›,cš,d˜*a–=cš8o :v'A}.o-n'h#9c"5[1V*E 6$$"#$%%#   '*%02'40)51+92,:3*<0+=1,>2,>2.?1.?1-=/-=/0;0*5* ' !  !"!"!!!!!!!!"# !"%&!($!(&#+>6GSK\pewkarobfnaeh_PpgXtib_TMNJPSOTiokafcdk\[bSHL@# *!8B:?HN@JOF4:B07?-4:,39*39+5:,7A.8B0:D3>H:DP=FR:HT:HTUv=Tu@Ww=Z~Da…EiŒLp“Sx—X}›`‚že†£nˆ¡pŠ£rˆ p…pƒ¢m p‚˜rƒ™uŽ†{”ŒŒ¦vŽ§w›°l™­j™§j˜¦iš¢qš¢q—œr˜s—™–˜~†…Ž{z„bu”`s’[r™Xo—Wq˜Vp—Vp—Uo–Un—Vo˜Vo˜Vo˜RošRošPošPošMo—Nq˜Pw—Sz›b„šjŒ¢}‘ ~’¡’’„‘š„‘š‹™‹œ”¡{’žs‘r™v™vŒ“€–ƒ•™›™ž˜¨Š”¥†‘¤‚Ž¡~Œž~Œž{Šœ{Šœ       !!##""""""$$$$!#$$# $!$! %"&"&"$!$!$"$"#$#$"##$ " ""# ""#"#! ! "!"!"#"# " "  ! !  ! ! ! ! ! ! ! ! ! !     !!""####%%$!$$$$$$%%%%#%%%%%%%#!"$""!!!!)!)   !!..OMqp>ŒM€Aqy8v~=x†GMxŒKxŒKp…Mf{D\lCP`7?N.*9!0)87E!AP+S^/al=o{>wƒFvŠ@p„:dx7Yl,EZ)8M,5!$-921d]\€Œyjvd^jf_lgg{PPd))Y..^%'^VP!$W'*c(,d$'d$'d')f')f('e**g/*h3-l/-b64iSSƒww§“½—›Åœ¤Â¥­Ë‘«Ä…Ÿ¸t—¯w›³v¡·g‘¨R}’Ju‰XsLguˆŽ{“š‡¢¢y••lkjQ00srilkbdadZWZHHV77D<755//1258:8,F9KfHG@GABICBI<786))/ '!41 -' +0*DAOjFToJVvKWwJWzHUwAPtBQuL\ƒL\ƒDUy&8\$R.[0Cu5Iz0A€5F…8D›9Eœ,9›$0“#-‚ *~.d!1g'>h3Is&?b7Y)>_/De-@d-@d*Dk.Gn0Mx7T5P€+Fv%8l*^"T MI R8h5P€1a6f”1kŸ4m¡0jž*d˜-d”,b“(\&ZŠ)^‘*_’$[‘$[‘R‡Oƒ#I 3k-G‚9t 0r/q(=w5J„4N2L~1Lw-Gs0Fp,Bl#9c4^2X.T$? +3 % %""%%$%!!! $*$.1'25'40)51,:3-;4*<0+=1,>2,>2.?1.?1-=/-=/0;0*5* '!!!"!"!!!!!!!!!"!"$ '&!((&-.,391CQIZcYjj`qvimxkpsj\meVoc\\QIIEJTPUcifdjg`hYX`QX\PSWKJSKCLD>GLH7AK:DPUv>UvAXx?\€Fc‡GkŽNr•U{™Zžc„¡hŠ§r‹¥o‰¢s‰¡q‡žpƒ¢m p‚˜s„šy“Š~˜¨y¨yš®k™­j›¨k—¥h™¡pš¢q™žtšŸu˜š—™~}†xw€bu”`s’Yq˜Xo—Vp—Vp—Vp—Uo–Un—Un—Un—Un—Qn™Qn™On™On™Mo—OršQx˜XŸf‡mŽ¤~’¡~’¡„”Ÿ„”Ÿ„‘š„‘šŒš’ ’’ ~{’žs‘r™v™v•‚”›ˆ–šœ™žŒ—§‰“¤„£‚Ž¡~Œž~Œž{Šœ{Šœ     ! !!!!""##$$$$##$$$$##&&# # & & $!$!$!$!$!$!$"$"#$#$"#"##$#$#$#$! ! $"$""#"#"#"#"!"!! ! ! ! ! ! ! ! ! ! ! ! ! ! ! !     !!""""""##&&$$%%%%%%$$$%%%%%%%%###""""       %$ 1/HEnq1…‰HˆDv;r~>z†G~‰N‹Q{‹Tv†On~PbrDTe@BS/1@"!/$0.;>I#LW0\f4eo=n{BvƒJv…Jk{?`s;Sf/ER,;H!/0'() E8E}o}Œ~Žsdte`nhcrfi‚GJc**Z00`#%[VW$%^')f(*g')f&(e&(e')f&'b))d-,e,+d41hPM„su¥¾˜ Çœ£Ëš©Ç¬É™±È—®Æ„¦º~¡´yž¯u›«d‰šPv†YxŠ[y‹œŠ—Œ›£iœ¥j‡ŽYGNeiPosYff]_`VTULKLB@A<@A<>?==><===>>>9=?:>@;AK=CMFO]JRa?J]1=O/BU6I\9I]8H\7GT6FS:FM9DL@H>BI?6<=:<=:33:++2 %2,,) +& (%.HDNh@Mk>KhCOoDPpCPsCPsBQuAPtET6Ep$1o.;y0<“(4‹(3“,7—-4—,3–)3",ˆ#-{)w'bU%S%1_".U&2Y,8_05.>5.=4.=4-<3-<30:1*4, & !!!"!"!! ! ! !!""%$ '(",+&0,)92/?83H:5J>9LMI[h_li`mmcetjlqdjRDKICI^X]bkebke]fYYaUTWTMPM=FG=FGF39A/6>-4:,39+5:,6;,7A0:D2=F6@J:DP:DP:HT:HT:HT:HT8GW8GW4DV5EW3DZ:Ka9QjCZtEbNkˆRs’Xy˜\|™a€i‚žm†¢m‡ o‰¢o† n…Ÿm‚œk€šk™j€—g–f~•e~˜e~˜e–e–kƒ”o‡—wy’‘Œ‘Œ‡Š†‰‡Žˆ‡Žˆ‡‹ƒ‡ˆy~‰sxƒbo…]jVfˆVfˆPf‰Pf‰Kd‡Ib…G]€DZ~BYzAXxAXxBYzB^ƒIeŠKo’Qt˜Wz™\ži†£nŠ¨r‹¥o‰¢q† p…Ÿp†œo…šwŒ‰{‘ŽŠ£}¥™¨t˜§s¦pš£m›¤o›¤o™¢s™¢sžŸšœ|–’‘‡ƒhp“hp“Xp•Yq–Uo–Vp—To˜To˜To˜Sn—To˜To˜Sn—Sn—Qn™Qn™On™On™Nq˜Qs›RyšZ¢hŠ p’¨•¤•¤†’ „†•’†•’’¢†’¢†‘Ÿvžu’œx‘šwŽ”|“{“‡–šŽ—™¢™š£Œ—§ˆ’¢ƒ¢Ÿ}ŠŸ}ŠŸ}ŠŸ{‰ !      ! !!"!""$$$$$$##$$%%$$&&# $!& & $!$!$!$!$!$!$"$"#$#$"#"##$#$#$#$! "!! !  " ""#"#"!"!! ! ! ! "!"!$""!! ! ! ! ! ! ! !       !!""""####&&$$%%%%%%$$$%%%%%%%%###""""  $# +.-FDge5†ŠJƒ†Fv;v;x…E~‹K~‰N~‰Nx‰Rp€IdsEXg:GX36G"%4 .)53?"CM'R\6`j9jtBsGvƒJq€Eix=[n7Pc+BO(4A./%/0'YKY„v„Œ~Žsdtd_mgbqfi‚EH`((X//_#%[VW()b)+h)+h(*g')f')f')f((c))d-,e10iFD{a^–°¾Ž•½š¡ÉŸ®Ëž­Ê—®Æ›³Ë†©¼„¦º~¤µ|¢²s˜©Kq‚i‡™jˆšy†zz‡|•žc¡©o“šdqxBOS:eiP]^U[\S[\SZ[RTVQTVQQROOOMPPPOOOIMOKOPGLWDJTFO]FO]DPcGSe7J]1DX2BV4CX2BO3CQ8CK9DLF;>F;AG;?E9?@7?@7:;899733:..5%*6 %20* )* ,+4N>Kh6C`6Bb>JjBOr>Ln:Im=MqBQ}ET2?}2?}.90<“)4”(3“(0“&- *†%$r$r%aUJ)W+6]%1X'3Z,8_+:^-<`)@f+Ch.Eq1Iu2Gu+@n"1c'Y!N I I)W#>i5O{4^‰=g’>p >p 6g˜/`‘-^-^+ZŒ+ZŒ)Z*[Ž-Z“,Y‘#G~ /g!X1h$7r!4n 0m,5.>5.=4.=4-<3-<3.7/,5- & !! ! ! ! ! !!"&!('"*.(21,641A1->2-B1,A40C:6IH?LOFSdZ[xopvhoL>ED=CWQWNWQU^X]fY^gZUXUKOLIRSCLMD%MT*W^4io@3>@6@>9BA=C>=C>AB?=><77<459148,04$+8 (4$5- ) ' -4:C_FNjY.A[/B\0Ef6Kl6Ks-Bi"3`%R"NH E,P&<_4Jm6[‚>c‹;i˜;EB?@ZXXJRAW_N^jV`kWS\ONWJN\UCQJ:GJJ8BN8EN8EN:FP:FP8FQ9GR8GW7FV7EW8FX3H[9Na=XoB\sGdOlŠSrZy–\|—aœgƒžk†¢o‰¢m‡ q† m‚œl›j™h€—f~•d~•d~•_{”c˜d~•e–n…“rˆ—Ž’‚•‰“‡‘‡Œ†Ž‹ƒ‹‚ŒŠ~†Šz‚…ru†lo[i„ZgƒSh‰Sh‰MeŠMeŠIb…G`‚E]€B[~B[~@Y{B^€D_Dd†LlŽPs”Vxš]b†¢lŠ¥oŒ§u¤tŒ£uŠo„˜u‰‘vŠ’ˆš‡žŒ˜©v—¨u™¦q—¤p—¡p—¡pœ rœ r™ž}˜œ|š•Š˜’ˆwsŒok„]r˜]r˜To˜UqšQqšQqšUq˜Tp—Vo˜Un—Qn™Qn™Qn™Qn™PošSrœOq›Mo™Mp–RušY›`†¢n‘¢t—¨€—£–¢„“ž„“žˆšŽŽ ”˜¥†’Ÿ€“žw’œv˜|Œ–y‹‘€Ž“‚’•”–˜˜“š¦—¤‹”©†¤ƒŽ£Œ¡}¡}¡|Š£x‡ !!!!######$$%%$$%%$$%%%%%%&&%%&"&"&"&"$!$!$!$!$!$!$"$"$"$"$"$"#$#$#$#$$"$"$"$""#"#"#"#$"$""!"!"!"!$"$""#"#"#"#"#"# " "! ! ! !   ! ! "!!!  !!!!!!""####$$####$$$$%%%%%%$$####""!! " ');: XX*|ƒ@„‹Ht7v€8wByƒDx„Kx„Kt€QitE[l@O`4?S-0D!0)"*'.7=DI+RZ0]d:ntAv|ItFtFizA_p6Sd3DT$4A!%2(&$DB@xh}†v‹ƒzmdwc_pfcsab;=Z)(Z-,^)*c XV%'_()i)*j()i)*j).k).k))d$%`+*aJJno£€€µ„Ž¼ˆ‘¿Œµ‚¹ˆ™¸’£ÃŸ²Ì¢´Î›µÇž·Ê™¶Æ“°ÀŽ´Âp—¥Pr…Rtˆˆ“•¡«®²·ˆµ¹‹¨³h‘Rcl=QZ+PX@MV>NTKR>KR=KR=LS@LS@IOEIOEFLHFLHCJPEMRCLXAKWAN\;IV6EP4BN/>I-;G-:C,8B-8;.9?H>?H@AEEEJ;8A74>;89ROPdl[ltcit``kW[dW\eX[hbCQJ9FI:GJ6FM8HN6FM5EL9CH4>C3:@/7<.5;.5;-7<.8=0;B3?F5>J8BN8EN8EN:FP:FP9GR:HT:IY=L[=K]=K]:Obr~Eu‚Iq€Nn|J`qFVg;EW66H''90&&"*.5 ;CHP,W`1dm>pzBwIw‚Gs}CdsAZi6IV6>J+06*&- -'1VQ[„q‚nƒymcye`niesgd€EC^))Y//_)+a!#YR$'[(+f),g)*j*,k/.l--k&(c"%`48nZ^”ry¬}…¸~Ž½‚‘ÁxŠ´p‚¬w«Œ¢Àª¶Â¶ÂλÆɼÇʯÃËŸ³»—­¼•¤g}‡~”ž’˜›˜¡´´’¯¯´¼uŸ§`{‰DO]R\6U`:W[CUXATYDSXCMU?LT>MTAMTAJPFHNDHNKHNKFPSFPSDOVDOVFPZ>HR=HcGRnCOo?Kk;Fd9Cb5@b8Cf?LuFS|)4k"Y$d$d#)h"(f $c^VVRMB@8'F07J6=P8@Q9AR:CV=;B<:A44;99@>A:9;4DN1dmPcpP[hHUdMR`JRZOLTI@HI>GHC3:@08=.5;.5;.8=09>1KR>IOEHNDFLHGMJCNQFPSGRZHT[HS\CMW>IP:FM2=F2=F13;>16<16<57=36IeJj@Jh:Dc7Be6Ad6Dm=Kt6Ax*b``#b#b!a^VUOKBB=%-K5j+>i4Hr5Lv7Mw3Kp2Jo0Hh+Bb'<[ 5T1N0M+F)D%<3% +!  "! ""!! #$+1-4:.::-99,<7,<7,<7,<7,<7.=8,<7+;6+;6+;6-:6-:6196/73 $$ "" #$"'&#,(%/+&4,'5)&4&$2*(4*(4.+441:64;53:99@99@;=646/BL/[eHboOboOR`JP^GKSHLTIAJJ=FGC3:@08=.5;/7<09>1:@148>68>57=36<36<13:029029029'.6&,4'3#/112,3HDJgCIf;Cb:B`;Bc:Ab5@b2=`BV@EKBFLBFNBFN@DQ@DQ;GZ=I\9Ha+:S /E. +4&;-B%5N->V&@`1Jk2Nw7R{5X}7Z€5Zˆ6[‰3ZŠ0V†1R†3S‡'Fs)Gt2Em(:c,@h6Ks;Nt;Nt8Nl4Ih3E`.A[+7P&2L!-F+E +@'<3)""""$$""$&-+4;,91=;0<:+;6*95*95+;6.=8,<7,=5,=5.<5-;4.95,62$)(!!!! #!$&!((#+*%/)#-)&0)&0,(4.*62.82.82-71,64354358>2>D8EQ3JV9Qf<^sIYjLXiKN_NGXG>MHCRM?KP08@.9@/:A4N %6("  %(36!?A-PR0Z]:gn?rxJw€Kt}Gjy@ao6Rd8GX,3B$#2"(#)?4Fpewllƒy–lb^[k^[kghzuvˆchŠ&*L&+Z$)X!US$(`'*c*%h-)k&$g-+nGJ…be ov¬ls¨xˆ¸v†µh€¬pˆ´y·x¶…–ºžÂž§ºª´Ç¯´Á²¶ÃºÄɾÇÌÂÆ̾Ãɹ½¿¶º»ÂÁ¼ÀÀ»ÊÆ°¼¸¢ÀĈ¶»Ÿ«`„DT_2S]0Y]AfkOwyd~€kz€mz€mxpszlhniV]WBJF?GD=FG@HI>GL?HN@KT@KT>IP48>57=36<36<36<25;13:029029(/7'.6$+8")5#7120)0L?Fb:B`9A_8?`8?`0;^0;^2f(:c+?g5Jr9Lr8Kq7Mk1Ge2D_+=X$0I!-F)B'@&; 5,&""""$$""#"*'08+8;,9<1=;0<:+;6*95+;6,<7,<7,<7.>7.>7.<5-;4.95+51'++ ! #"%&!('!+(",'$-)&0+'3)%1-*3-*3-'1-'1,+-/.1-4'06*8D'AM0G\2YnDevWYjLL\LBSC>MH=LGAMR>JO:FK:FK=BH8=C1;>.9<1:A1:A/:A0;B7?I9AK;BO5:@37=36<25;25;36<13:029/18.06(/7'.6$+8#*6'6&5105(0H9B\5?Y8?[7>Z5:Y49X/7V2:X:@d5;_!H!H"OLIGDC?F13NHR?JZDN_DTh=Ma,9P,C+>!/A-B"/D!3I,=S.D`5Kg4Lj7On3Nr3Nr.Lt0Nw1Lw0Jv.Fp(@j+=`+=`5Eg;Km=Nn9Ji8G_4B[3AU)6K#)A"'@#;6. *%$""""$$$$"!!!  !"%$-4+7<,8=0;;1<<.=8,<7,<7,<7-97-97,<7,<7+83,95.95+51).-$#!# #! # ###$ *'$-'$-)&0)'.%"*$$+##*"#%)*,'1'-7,-C$3I*3W"Gk5V~5Zƒ:Ts8Jj.IY=CS7DJLDJL>GL;EJw~HsGn{B`n>Xf6BT-5F"1*!%.2&^S`sylŒuh‡~w‘mg^Zf`\hdaqrozy—mlŠ',T$(Q "RL#"Y''^('`+*c53oSRux±ƒ»zˆ¸Œ¼½½~”¾•¿pŽ·`~¦nˆ­sŽ²{ª†›µ•£·š§¼¶»Á»¿Æ¿Ǿ¼Ã½º½»·ºµ·²¶¸³¨«›¡¤”¨ªz¨ªz¥±f©^s{Oz‚V|~y{}xvvvwwwvwuwxvturstqqrtqrtptvlprfopdmm^hm\ekWafU^dOWaJR\GLWDJT@FN=DL537=16<57=57=36<25;029029/18.06(/7'.6$+8#*6'6&5#7 333'1K6@Z18T18T06U06U.6U.6U4:^;Ae,3[!H!NLIGCB?"#I8:UAC^EIZEIZEJPCHN@FJ@FJIY@Pd>NbANe3@W.@(:+?!.C/E%6L'=Y*A].Fd0Hg/In,Gk(Fo(Fo,Fq-Gs)Bk$=f,>b3Ei;Km9/>9,<7,86*64+;6.=80<8-:6.95-73*/.! $!#$"% #   $%"+$ *$ *%"+!&##$$+')+#$&"-"(2(#9%;%I9]'Dl$Px0Hh,Aa%P`DN_CELMDJLC3:@/739A3'    %&.8<"EI/PZ0^h>ir=r{En{Bhu<[gT5;Q/8M09N/3N.2M*2N/8TYEGbLN\LN\ILRGJPFIMEHL@CG@CG>DG>DG_2Ae2Ae6Ad6Ad6Fa6Fa8E\6BY4@S2>P07J)0C$(7//,&%$$""""$$$$""""!!!! )&,6-3=-7<-7<+99+99.<:,;9)83)83,95+83,86*64-51,40'*."  #!! $ !&""'#"!#!!# &($%/+);&*<'+I&/M+1\(EFHBDF9>D6;A09>-7<.8=3=B5@G5@G6@J8BL:DP:DP:DP:DP5DO9GR8I[ARdG[qQe{Zr‰ayf|˜g}™k€šj™h|—ez”_yb}”c~™gƒžj… k†¢mƒŸmƒŸj›i€ši€šg~˜d}—d}—d}—d}—jƒ˜n‡œwŠ•y—„‘“…’”ˆ“‡’‹ˆŽ{‚w~ŠluˆenVhƒTfKa…Ka…Ga†Hc‡Ga†Ga†Ga†E_„B]‚A\€B]‚D^ƒE`‚Ie‡JiPo“Rv™Vz]}žc‚¤kˆ¥o‹©uŽ¡rŒŸuŠ{–Š£{¦}”¤o“£n“¢p‘Ÿm—w—w”™}•yˆ~‡‹}Œ‹’–tz—ou’[p˜[p˜VsœWtRsžRsžQqšPp˜Pp˜Oo—Pm–Pm–Mm•Mm•Lm˜Lm˜Lm˜Lm˜Kn›Kn›Jq˜Mt›QyžX€¥i‰¤nŽ©•¤“¢…”—†–˜’¡ˆ‘ ‡”žz”žz–œ}–œ}“~“~‰””˜›’šžˆ”¦‰•¨ƒ’¨‘§Ž¥Ž¥}Š¦|‰¥{ˆ£y‡¢w… vƒŸ!######$$$%%%''(* ( ( ( ( ( ( ( (&& ( (!)!)!)!)!)!) ( (((((&&$!$!&"&"&"&"$!$!$!$!$!$!$!$!&"&"# # &"&"&"&"& & & & &"&"$!$!$!$!&"&"&"$!$!$!$!$!$!$!$#$$%%%%########%%%%%%%%%%%%&&&&%%%%$$$$###" $12GH$z{BŽU|„>x:{ƒD€ˆIz…Os~H`p@P`06H /    ')18<"EI/PZ0]gD25;029029)16(05*.8),7$+8#*6!*8 (7&6#4!7222(.D06L,4J*3H/3N.2M*2N,5Q29Z?Fg#,O"E!GE@BC,/S?A\CFaKM[LN\ILRILRFIMEHLAEI?BF=CF?EI;DL"1+,)'&%%""##$$$$########"($)4)28,6;*88*88+:7*96*95*95-:6+83)53&20(0,"*'!#%##*-.2126&'+ !%+,623=93F0q%Ex,a4t£GvRVo2QYJFM>BDFCEG09>1:@2=E3?F6@J8BL:DP2< !&G G)&O-(Y?:jnh–Ž‡¶•–¼™™À–œ¿™ Ã’ Â–£Å‰ Á„›¼œ¿}˜ºmŠ¬l‰«sŠ«z‘²‘žº˜¥Á£¨»¢¦º¢¥²¡£±¢¢°¡¡®”œ¦Œ”ž}l~~n|fsjyŠJ€‘Q’£bk|=F57=/18)16'.4&05$.3#.7",6",8 )5(6(6!42!32#50:M1:O/8M17O.3K.6N/7O2:W $?38SAFZEI]ILWJMXLNUHKQHLPEHLBFJ@CG>EF>EF:EG:EG>FK>FK>GT=FR>DS?ET9AR08I (9#4&,=,2C,5D1:H0:M1;N1M;?N;=J8;H35B-/< 0*(')'''$$##$%$%''&&&$$%###'"-%*5'16+5:.78,55.86/97,95)51&0,!,("! !'(-./4"#% !'()6-2>57F/=L5@U0CY3A`.A`.Bm$Cn%H| \z¨3S \n2p‚FNN@EE7B?@=:;17;17;-6>08@1;E2=F6@J8BL9CO9COI^25K-!    %&.7;!DH.PX4\c@emAjrEcpC_l>N_3BS'>K=JKV"cm:„Š^˜žrŠ†|xulŽtkwqys’gbq^Yg_XejdpvŠ‰€“}~—ƒ„HLn7D"H+&WFAqsl›Œ†´•–¼˜˜¿£Æž¤È—¤Æ™§É‹¢Ã˜¹… Â„ŸÁu’´mŠ¬t‹¬t‹¬ˆ–±¸œ¡´Ÿ¤¸ ¢°ž ­žž¬••£ƒ‹–w‰ewwWiiYgQXfO\m-q‚BŽŸ_z‹K]_Sgh]ebkfclfcld`jb`g`^e_]da_fb`g_]d[Xb[Xb[XbYV`XR\UPZXR\UPZSNVMHPKFNIDKB=KB=KB=KD?ND@LD@LEAMD@LBAKEDMFENEDM<>D79@.5;+28&05%/4#.7",6!*6(4(6(6%8!4"4!31!+>3;Q/8M.3K17O.6N08P.7S3EF=CE:EG;FIQ:>M9=L8;H57D14A&(5/)''' &''%%$$&%$%'('''&$%##""!&!&(-&/0(12,54-75'40"/*($  +"$0'/>'6E.7M(>T/]*=hFp'Tˆa•$}«6X†J\ \n2\\NFF8=:;;89069069-6>08@2=F5?I6@J8BL9CO9CO7"rrB‘‘b‹L{„E|„J†ŽT‰•^€‹Ug{HXl9>V+(@%   "&*27"?D/OR4\_AehChlGflGchCW`1OX)Sa`n*|ƒ7–J“œg˜¡kŒˆ{w|wk”ti‘yq—zr˜idu]Wh\X_c^fvƒ‹‚‡ƒŒ¦†‰©/2Q8A-%OIAkuo”‹…«““³œ¼£¦Æ¦©È£ªË¡©Ê—¤Æœ¾ˆŸ¿Š¡Â~šºpŒ¬r†«pƒ©x…£Œª‹’¥’™¬–œ­—®‡‹š€„“u}ˆmuckn]ehdk\el^bn9JAMA?MA?MB@NEBPEFOEFO9?G39A)28%/4!+5!+5 (7'6)7)7(7%5%5%5 0!2-6I6@S59O48N59O59O-5L08P2:W6?[$.N>>?((D77SFGYII[LMVIKTMLQLKPIMQGJNHJLFGIBGGAFEAEGAEG>CI>CI=BH?J;:D98A76?..7!&3*(('+--, *)(''$$$$&&'&''&$'&$""' *!&,"'-"&(!# "     "#$)#$2+*>$3G-;T%B[,Hl"Sw-`’n (uŸ"X‚Ys\vluO^gAB@>986599599*6;,8=0=F3?I4@K6BN6BN6BN5DO8FQ6EP4BN1AN3CQ4HW^‚>^‚@`Dd†FjLp“OxšR{\€£b†©kŠ¡kŠ¡}—Œ€›¥{¥{”¥u’¢r™¡w˜ u˜žy˜žy”™}•yˆŒ€‡‹ŠˆŽ“x€˜qy‘_q™]p˜RošSp›RsžRsžSr›Pp˜Pm˜Pm˜Ok—Nj•Ok—Li”Kl—Kl—Lm™Lm™Lm™Kk˜Kn›Jm™Lr—RxX|]‚£lŒ¢p¥|‘zŽ›ŠšŽž“•¢‚‘ž”œ}”œ}“š–|Ž„“•‰–—’˜š‰–¦Š—§ƒ•©‚”¨“¨{¥x‹£vŠ¢vŠ¢vŠ¢uˆ¢s… r„Ÿqƒž"# ""##%%&& & & & & & & & & !*!*))))))))!*!*!*!*!)!)!)!)!)!)!)"* !)!)"* "* ))''''''& & & & %%& & $!$!&"&"&"&"&"&"$!$!$!$!$!$!$!$!&"&"&"&"&"&"&"&"%%%%%$%%$!$!$!$!########%%%%%%%%%%%&&&&&&&%%%%##""!,&B<&O••e~ˆI|†G„ŒRŒ”ZŠ–`{‡PbuBRf36N#5    "&*16!>C.OR4\_AehChlGgmHdjD\e6_h9m{7x†B—K™¡T”žh–Ÿi‰…Š|x}~r›sœ~v›}tšhcs]WhZU]\X_zq~Ž…’‹¥Œˆ¡„‡¦or‘ !E=%GC;fql‘‰ƒ¨““³ž½£¦Æ¦©È¡©Ê §È™§É“¡Ã„›¼„›¼z–¶lˆ¨n§dxiv”p}›„‹ž‹’¥Œ‘¢ƒ‰šrv…mq€lt~iq{\dgV^acj[go`v‚P‚]{ŠJ†”Toq\UXCYYb^]g_Xl]ViXSbXSbYYbWV`XU^WT]XT`SO[QMYMIUPHWPHWMFULETIAPF?NC;J@9H<6G93D93D:4E94B94B;6D<7F:8F:8F;9G>;I>?H>?H;AI7>F09>)28",6 *4 (7'6)7)7)9(7%5%5#4#4(;6@S?CY59O59O59O19Q5=U/8T19U3=]"+L=?&&A66RDEWII[KLULMVMLQLKPFIMEHLHJLFGIBGGAFE@DF@DF>CI>CI=BHX(G`0In#Mq'P +O€ Z…fk…*VpNV0GO*=;9875165276,8=/;@0=F3?I4@K6BN6BN6BN4BN5DO6EP4BN1AN1AN3GV;O^E]mMeuVq…]xŒc{“c{“g{“fy’_wŒd}’gšl…Ÿl…Ÿl…Ÿm‚œl›iœg}™g}™g}™i}™j~›p‡šqˆ›{š|Ž›‡“˜†’—‡Ž“…’„‹“~…zƒ’u}Œkv‹fq†Wi†RdK_‚Ma…Jb‡Jb‡Da…Da…Da…C`„@]‚?\€A\€A\€>^‚A`„BbƒGgˆGkŽMq”Nv™R{_‚¦c‡ªkŠ¡kŠ¡{•Š€›¢xŒ¡w’¢r’¢r–s—žt˜žy—w“˜|Ž“w‡‹ˆŒ€’‹ŠŽu}•px]p˜\o—RošSp›RsžRsžTtœQqšPm˜Pm˜Ok—Nj•Li”Nj•Kl—Kl—Lm™Lm™Lm™Kk˜Il˜Kn›Ms˜Syž[€ a†¦lŒ¢oŽ¤{œ|‘œž“’Ÿ€}”œ}”œ}’˜~Ž•{Ž„“•‰–—’˜š‰–¦Š—§ƒ•©“§|¦zŽ¤x‹£vŠ¢vŠ¢vŠ¢uˆ¢s… r„Ÿqƒž!!!#$$!#%% ' '!)!)!( !( !)!)"+"+"* "* + + + +** + +!*!*"+"+!)!)"* "* "* "* "* "* "* "* "* "* * * *! *!( ( ''& & & & %%%& $!$!&"&"&"&"&"&"&"&"$!$!$!$!&"&"$!$!'#'#&"&"&"&"$"$"$"$"$!# # # # $!$!# ####$$$$&&&&%%&&$$$&&&&&&&&&%%#"""!  !#+)?>'zzQf}‡O|†N€ŠWˆ“_„“`q€NWl=G[-,@+ + +    %%*04!=A.NO6Y[BbcFfgIflGbgBfn4t|B…Ž?—H˜ŸSœ¤X—p—p‡ƒŠz‚x „{£}wwq—jfwYVgWT\XV]np}‚„‘nu_f€dnŽmw—OTzB?41Vmf‚†~›“‘¬ž›·¥¦¿¨©Â¤©Ä¤©Äž©Å˜¤¿ˆœ¹…™µz’°h€ždxdx_o‘]moz•t€›s~“ep…UbpR`naisfnxbijW]^`gZTZNu~Qu~Qcp){‡A‚„bXZ8MKRRPWTLdTLdTNaVObTPaOL\RL]QK\OH[MGZMGZHATGCVHDWC?RC?RB>Q@3,?60C71D72E51D95H;7J::H::H5:G38D19C.6A'0T8CB>CB^jMXdGRTFJL>CE@=?:49=17;,5=-6>,8=/;@0=F3?I3?J5AM6BN6BN6EP6EP4BN3AM0@M1AN3GV8L[?WgLdtSoƒ[w‹c{“d|”j}•i|”b{h–gšk„žl…Ÿk„žm‚œm‚œiœf|˜f|˜g}™h}•k™rˆ—t‹™~–€’™‰“˜ˆ’—‡Ž“…’…Œ”€†Žxr{Šes‡anƒSgƒNbI_‚J`ƒH`…H`…Da…Da…Da…B^ƒ@]‚?\€A\€B]‚>^‚A`„Dc‡IhŒJm’Nq—SwšVza„žfˆ£s’s’†Ÿ}‡ “£u’¢t” u“Ÿt•w–žy—›{˜œ|’”ŒŽy…‡‚‹ˆŠŒ™ƒmy—hu’Zo—Yn–Qp›SrœRsžQrœQqšPp˜Pm˜Pm˜Ok—Nj•Nj•Li”Jk•Kl—Lm™Lm™Kk˜Kk˜Jm™Kn›Ot™Syž^‚ždˆ¤pŽ r¢}’—”š¢Œ‹ Š’Ÿ€‘ž”œ}“›|•~•~‘’ˆ“”‹”Ÿ– „•¥…—¦•«€”ªy¦xŽ¥vŠ¥t‰£t‰£sˆ¢r†£q…¢q…¢p„ !!!#$$% & %% ' '!)!)!( !( !)!)"+"+"* "* + + + +** + +!*!*"+"+!)!)"* "* "* "* "* "* "* "* !)!) * * *! *!( ( ''& & & & %%%& $!$!&"&"&"&"&"&"&"&"$!$!$!$!'#'#'#'#&"&"$!$!&"&"$"$"$"$"$!# # # # $!$!# ####$$$$&&&&%%&&&&&&&&&&&&&&%%#"""!  "$,*=<%tuLf}‡O{…LŒXˆ“_}ŒYdsANb4>R$ 4& +  #!&.2;?,KM4WY?abDefHekEdjDfn4wE–G˜ Q›£W›£W˜žr™Ÿs‡ƒŠ{v}ul”xo—xs˜xs˜liyWTdVSZWT\_an^`mMToY`zjs”s}ms˜16\6-*O_Xt|u’ŒŠ¥•“¯žŸ¸¤¤½¢§Á¤©Äœ§Ã—¢¾‰º‚–³z’°d|šYl’atš^nWg‰_k†]h„^i~Zf{R`nN[i\dn\dn[bc_efmtg`gZNV)`iQA=PA=P>9L;7J84F51D4-B2*@/(>.'2.A1-?40C84F77D77D5:G5:G08B08B+5A'0CB>CB069,5=-6>,8=/;@0=F3?I3?J5AM6BN6BN4BN4BN3AM2@K/?L0@M/CR6JY?WgKcsQlZvŠc{“d|”j}•i|”d}’h–iƒœk„žl…Ÿk„žm‚œm‚œh~›h~›g}™f|˜j€—mƒ›t‹™v›€’™€’™ˆ’—ˆ’—‡Ž“…’ƒŠ’~…vox†dr†]kOc€NbJ`ƒH^H`…H`…Da…Da…Da…B^ƒ@]‚?\€A\€B]‚@_ƒBa…Dc‡IhŒJm’Nq—SwšX{Ÿa„že‡¢w”–z—™Š£‡ ‘ s‘ s“Ÿt’žs”œv”œv™ž}˜œ|Ž‘|‹x‡ˆƒ‹ˆ†‰–}jw•gs‘Yn–Yn–Qp›SrœRsžQrœQqšPp˜Pm˜Pm˜Ok—Nj•Nj•Li”Jk•Kl—Lm™Lm™Jj—Lm™Kn›LoœRxW|¡_ƒ fŠ§pŽ pŽ ~“™€•›¤ŠŸ‰}}”œ}“›|•~‘–”•Œ——Ž”Ÿ– …—¦†˜§“¨{¥xŽ¥wŒ¤vŠ¥t‰£sˆ¢r‡¡q…¢p„ oƒŸoƒŸ  !" $ $%%$% ' '"* "* !)!)!)!)!*!*!*!* + + + +!*!*"+"+!*!*"+"+"* "* "* "* "+"+"+!*!*!* ( (!)!)!( !( !( !( !( !( ''''''''&&&&&"&"&"&"&"&"$!$!$!$!$!$!$!$!$!$!$!$!&"&"$!$!# # $!$!$!$!"# # # ####$$$$%%%%%%&&%%&&&&&&&&&&%%##""!    !&.1;>"mnI‹Œg„T}‚Qz„Z‹aoQ^m?FX00B( $,1"7<.FK6SXC^aCdgImoAopCms2{@’›J˜¡P”¦X–§Y”žsŽ˜m‹sq}ol‘qm’rq–uu™jjxTTbTQ[VR\Z`oV\kQ^z[i„as”fyš`o”et˜&*L9QFfsh‡ˆœ–ªœ™°£ ·£¦¿¥¨ÁŸ¦À¤¾’ ¹Œš³•²oƒŸ_q™`ršYo“RhŒRb}Qa|XbsZeuVajYcmbhk`fintju{q|†ir{^Q[5CM'S^#Ze*ˆ’Xgr7BACIHJGBWFAVBTA%,:"(7%3&5 (7'6 (7(6'5'5)7*<$0B29M18L26L26L37M37M+5L-7O3>Z@Kg-9S%1K8AV?H]JLYGJWIKTGIRHIMGHLGHJGHJBGGBGGBHEAGDBEDBEDACCACC?AA33:$$+ ""''%%'))%%3-,7*(4# .)./1012/..,/.''% "&%)%$$%&&'$##!!!!"%(&596CO4OZ?`o;iyDj}8i|7dq6\i.RU2BD".,$!!$(%*.&20*640<5,71$3(;K?N]Rapdp{poyorysrysqwkkqegg```YXVTMLJGEJA?D6<>189.6908<,8B.;D3=I6?K6@J7AK3BK3BK4BN4BN4@K4@K0@M0@M-AP4HW=UdKcrSoƒ]xŒf|”k™l—j}•g–j‚šk…œl†l…Ÿiƒœl›l›i€šf}—j€—i~–m„™q‡w—xŽ˜†•‡‘–‡‘–†•‡Ž“‡Ž“‚ˆ’}ƒs|‹lu„]m‚Yi}LbLbE`‚E`‚F`…F`…F`…F`…E_„D^ƒD^ƒB]‚A\€D^ƒBa…Ba…CeˆIkŽLn–Qs›SzœY¢f‡ŸiŠ¢ƒ›‹ˆŸ–¦}‘¢x”¡t“ s—žv–u•w— z˜œ}”—y‹Š‰‰}††‹ŒŒ‘|‚˜u{‘du›`p–Rn•Rn•QqšSr›PtžOrœNq˜Mo—Ml—Ml—Lk•Ji”Ih“Ih“Hk—Il˜Jm™Jm™Jm™Jm™Hm™Io›QyžU}¡e†£hŠ§t uŽ¡}”“ƒš˜–©‹Ž¡ƒ‘y‘y”œ}’š{’•~”—€••˜˜‘” –£…•©…•©}’ªz¨v§uŒ¦tŠ¦qˆ¤o…¢n„ m‚¡j€žj€žh~œ  !" $ $%%$%!( !( "* "* #+!#+!#+!#+!#,#,!*!* + + + +!*!*"+"+"+"+"+"+"* "* "* "* "+"+"+!*!*!*!*!*!)!)!( !( !( !( !( !( ''''''''&&&&&"&"&"&"&"&"$!$!$!$!$!$!$!$!$!$!$!$!# # $!$!# # $!$!# # "# # # ####$$$$%%%%%%&&%%&&&&&&&&&&%%##""!   !$(24?A%hiDŠ‹f„T}‚QyƒX}†\iyKVe7?Q*'8" $+/!6;-DI4PT?^aCdgImoAqsE{@‰N“œK— O‘¢T“¤W—l‚Œbgfqmlwol‘nknn’qp”nn{YYfYV`ZWaW^l[apanŠgtbt–`r“fušz‰­jo‘<5+Jf[{yrŒŒ…Ÿ”‘¨›˜¯›ž¶¡¤¼›¢½š¡»“¡º›´zŽ«p„ fx _q™Xn‘QgŠO_zO_zV`pXbsYcmVaj_dhhnqw|r{wy‚ewdp{UalE`k0Ze*mx=al1;:9N<7M:5J50F4/E83H72G4/E51D40C51D3/B.)@*$<&!8&!8$4#3#3&!7'#6'#6)$7-),4>+3=+3=+1@+1@$*9&5%3'6'6 (7)7)7(6'5(:);+2F5W3;Q@@44;&&- ""%%$%')%$&"%*0/2242!2/, //0+$""&%)'%$%%%#$$#  !!!!#'(1%$-&,/,:=:KW`c@PR0<:-+("!"&$(#'#)-)537CALXQOZT@ODkzox‡|m}qmwlp{prysqwrrxlnuhnngff_[ZXQOMEDI>=B4:;07808<08<-:C/‡ˆdƒW{~Rt~Tt~TgyOM`6.C .    %',26(CC/QQ=`ax~D„’:žE¢D“¥G‰š\‹›]y„iYeJEHLhkodbwcauii~ii~oo{]^i^[c]Za\`mko|my“frŒ`nŽdr’t¡xƒ¦}ƒ PVs$!8SOgri‚„|”Ž…“‹£˜—®Ÿžµœ ¶š´’´Ž˜¯xˆ£sƒžm€¦h{¡SfŒI\‚HYoHYoV^oZbs[ckU^eT[\^denrfw{ok{YjzXpˆVsŠYy“Fnˆ;~‹Kv‚C.1(;I85C71H71H4.F71H;5M;5M:4L71H93K3-E0*B2,D1+C0*B1,A2-B.)?,'<)#;&!8%7$6$4"2 1"2"4"4"4'$9)*<--?./A/0B-1@-1@-1@,0?*0?*0?'.=$*9(4(4 )5!*6!*8 (7"+9"+9"*;$,=).?28I7?=97<+).  """$%$%((%##"'*. 04211!0!0!,!,%"+&#,$ *! $*'%%%$$$$$$     ($""'28.BH>Tb>etOvŽH€—Q‹¡P…œJ–Cx:my:`l-OS+=A,*# !#&##+)-43=DCIPO>IE4>:?MD?MDScPw‡tŠv{„pvypvypswrswrrxnntjnoeff]\\WSRNGFH>>@6:;2684;A08=,8B.;D3=I6?K6?M6?M3@N4BO2@K2@K3?J3?J-=K-=K+?M3GV8RdIbuQn…]zg™k…œm…œkƒ›m…œm…œl†l†l…Ÿiƒœk€šj™g–g–lƒ˜lƒ˜s‰˜t‹™}Ž’’–†•†•ƒ’‹‡‘~†|„u}‰ht‡dp‚Wg€Rc{H`|H`|E`‚E`‚D^ƒD^ƒF`…F`…E_„D^ƒB]‚B]‚A\€E_„Cb†EeˆGhŒLm‘OršSu_™f† x’•}˜š¦ƒ¦ƒ˜¦v•£s™¢s˜¡r—žt˜ u™Ÿz™Ÿz–”~Œu……~ŠŠƒˆŠ“†ˆt~–oz‘\u–Xr’Qo–Pn•Nq˜Qs›Mq›LpšMo—Ln–Lk•Ji”Ji”Ji”Ji”Ji”Hk—Il˜Il˜Il˜Il˜Jm™Mq›QuŸX~¡\ƒ¦fŠ jŽ£t™u’›†›Œ¡’“¢‚ €”Ÿ€”Ÿ€“š–|ƒ””†’–—”–Š•£Š•£’¨‘§wŽ¨t‹¥tŠ¦r‰¥p†¤nƒ¢m‚¡nƒ¢k‚žiœiœg}™! " $ $##%% & !'"")!")!"* "* #,#,$- $- #-#-#,#,#,#,#,#,"+"+"+"+!*!*"+"+"* "* "* "* #,#,"+"+$- $- $- $- "* "* ")!")!!( '!( !( ''''''''''''& & & & &"&"&"&"$!$!$!$!$!$!$!$!$!# # # $!$!# "# # ""$$$$$$$$%%%%%%%%%%%%%%&&&&&&&&&&%%""!!!   "()/8:DG+\]9‚^ƒW{~Rt~Tr|Qi|RI\2%:'  $(-/4&AA-PP<`a+%=+&;,'<*%:'#8$6"4"4 2"20.0 2 2 2#!5&&9)*<*+=,,>-1@-1@-1@,0?*0?*0?*0?'.=",8!*6!*6!*6!*8!*8#,:#,:!):$,=).?*/A28I5;L49M27J,7L.9N+7P+7P-;T1@Y8E\6BY7CTFFTFFTGDRFCQEDMCCLBFJBFJBFHBFHCFEACCBEDACCBC@@@>;9>/.2  """$%%'((%##"#*//421#5#2 /"-"%0+'1+'1$ *!$*'%%%%%$$$$!!!!    #$(4:0CI?Ra=fuP|”NŠ¢\’¨W¦U‰žK–Cx…Elx9`d=OS+?.;D0=F3=I6?K6?M6?M3@N4BO2@K2@K3?J3?J-=K-=K.BQ6JY=ViKdwRo†_|“i„›l†n†n†n†n†l†l†l…Ÿiƒœk€šj™g–f~•m„™p†œt‹™t‹™€‘•’–†•ƒ’‚Œ‘‹‡‘{ƒy€sz‡fr…`lTe~Rc{H`|H`|E`‚E`‚D^ƒD^ƒF`…F`…E_„D^ƒB]‚B]‚B]‚E_„Cb†GfŠHjMn’Qs›Tvžb‚g‡¢z”—›ž¨„Œ£—¤u”¢r— q–Ÿp—žt™¡w› {˜žy‘yŒ‹t††‹‹„Š‹”„…Žr|“nyYs“Xr’Pn•Qo–OršQs›Mq›LpšMo—Ln–Ji”Ji”Ji”Ji”Ji”Ji”Hk—Il˜Il˜Il˜Jm™Kn›Mq›QuŸX~¡\ƒ¦g‹¡k¤u’›v“œ‰žŒ¡’“¢‚Ÿ“ž“ž”›‘—}’’„––‰”–“•‰”¢ˆ“¡‚“ª~¦v§t‹¥tŠ¦qˆ¤o„£nƒ¢m‚¡j€žiœjh~›g}™$$$&&&&&"'"'#($* $* $* %+!%+!%*"%*"%*"%*"#+!#+!#+!"* $*%,$*$*#-#-",","+#,"+"+#,#,#,#,$- $- $- $- $.$.$.$.$- $- !*!*!( !( ' '((((''((&&&&&&&&&&%%$!$!$!$!&"&"# # $!# ##########$$$$$$$$$$%%%%%%%%&&&&%%&(&&&&%%%%#!!!!!"!          #%*+0;>JM([b3tzL‡ZoxKjvDmzHj{ORd84 $  !),03%?B$MP2dg6ruD…Ž=—EŽœC‹™A‚—D}“@m‚LVj5.='291`g_vu…nm}hi‚lm…wv†dcsa_f`^eY]efjrdp‚lxŠt€žt€žlx’jvmv‹oxooƒ<,(B,(B,'D*%A(#?)$@'#;'#;$!8# 7($<($<'#;"6!3 20.--(+.-- 0$4'#8*&7+(8,+=/-?/-?/-?).;).;)0<*1=%,:#)8!*8"+9%+<%+<',D05N4>V(3J+0C&+?.0E57L59O59O17M06L/7O/7O.8R/9S2;U9B\+7P&2L6?T:BX<>S=@T@BR?AQBALBALBBIBBIBCGBCGACC?AA>>C>>C;<@237%""##%%()+('$#"%-1244 4 4&#,,)22-.2-.62,4/))%#!( )&&!%#&$$$$$! ""!!'28.CI?WfAjxT—VŒ¢a©WŽ¨VŒ¢F†@}‘9:>77<07?4:B3F5?I5?I6?K6?K5@N5@N3@N3@N/=K/=K,;K-=L/BT7J\+"<%!;'"<'"<'"<(#?% =$;#:# 7# 7"6!5# 7$!8"6 411..--(')),-"2#3%"2*(;+*<,+=/-?+/<+/<)0<)0<'.=&-<"+9#,:).?*/A*0HHNfOYq1;R04H-2F-/C24H8S?AQ>@OA?KA?KAAHAAHBCGBCGACC?AA>>C>>C:;?56;)(1 ""##')))*)%##"%-12423!$8.+441:945612:5/83-,('$!"+$#,%$-" ,)(')&$$&&! "" !!'16,BH>XgBkzU~”S‹¡`‘ªX‘ªX’¨LŒ¢F‰œG‚•@}‰Jw„DrwKlrEceIX[?TVJUWKWZXbeb_h`U_WW\MMRDIO>Y_NcgVNRAEH8ko^nuhjpdpsptwuqwrqwrpsllngcd[Z[RNNIED@9:>88=39A4:B3F6@J6@J6?K6?K3>L3>L1>L1>L/=K/=K,;K-=L/BT7J\;VkJezTra~™i‡ l‹£nŠ£m‰¢rŠ£qˆ¢o† n…Ÿm„žj›h™h™g–h€—m…˜p‡š{Œ—}˜…•†–ƒ‹Žƒ‹Ž‚ˆ’‚ˆ’z…v€Šmwˆfp\k„XfObM`F^F^D_D_F`…F`…F`…F`…D^ƒB]‚B]‚B]‚C`„Da…CeˆHjJl”OršRv—Uz›c‰“m’œ† Žˆ£‘•©|’¥y˜ u—žt˜Ÿp™ qš t¢všz’•r†Šw…‰vŠ‡ˆ‹‹ƒ‡–|€kz—gv”Yq–Wo”Nm˜PošSrœQp›Mo™Mo™Ln–Km•Lk•Ji”Ji”Ji”Li”Li”Hi–Jj—Jj—Jj—Lm™Lm™Mt›Ry¡[„ aŠ¥p¡qŽ£y’—|–›Ž¤ŒŠŸ‡Ÿœ|‘œ{‘œ{Ž–|Œ•{‘Š“–‰’šˆ‘™‚¢ƒŽ£}£|Ž¢wŒ¦t‰£qˆ¤qˆ¤m†¢l… kƒ¡f~œe}›d|ši•k‚—&&&&&&&&#)#)#)$*$* $* %+!%+!%*"%*"&+#&+#$,"$,"#+!#+!%,&- &- &- $.$.#-#-$- $- $- $- $- $- $- $- $- $- $- $- $,"$,"$,"$,"#+!#+!"* "* !)!)!)!)(((((())(('''&&&&&%%%%%%$!$!$!# ""########$$##$$$$$$$$%%%%%%%%&&&&%%&(((&&%%$$# """""!!!         $ *&/,5dq=huAO`4.   !(+24JM_b2€ƒ>–™T™¡L”œGˆšEƒ”?t†=j|2Te%AR6F,; /6"OUBqr}klwceyuwŒyz…efq`_b_^a\`iaem`q‰r‚›o‚¡fz™_n‹]l‰kpƒlq…ru†svˆ[YlA@R]Vioi|qh|tk~tpƒ{wŠ~•}€”nvŽckƒZbzW_v[awZ`v^bqdhwaht_gs\juap{lw…jv„my„q}‰v…‰lzZnoL`aATQ4GD@G?;P:8M<;K>=MA@IBAKAAH??E<=B;<@9:>56;-,6#  # '****)'###&,1 3 31"#,,.7851;84B>/?<,<:&86"/- !"!%''++%//(0/+-,'$% !# "$ """"  #%-%dtR~”S‰Ÿ^‘ªX’«Z–©T’¦Q‘¤Q N‰™Nˆ˜L„WƒŒV}ƒ^rxRmr\glVYYI[\LRWIX]NfhZ\_Q@D39=,:<>9:<39A28@3;E5=H5@G6AH6?K6?K4?M4?M2?M2?M1>L1>L+:J/>M0DU8L]=XoNhVs‘c€jˆ¡nŒ¥q¦pŒ¥t‹¥rŠ£o† m„žm„žj›h™h™i€“l„–t‰•uŠ–——…Ž“ƒ’ƒŠ’ƒŠ’†’{‚u€Žp{ŠhuŠbo„ViRf~Lb€J_~F^F^E`‚E`‚E_„E_„D^ƒD^ƒD^ƒB]‚B]‚B]‚C`„Eb†EfŠIkŽJn‘Nr•Xy^•q•Š{ž”¨‚¨‚™¦y˜¥w› w™žt—Ÿs–žr™ž{˜z’”{‹t…†}‰‰€ƒ‡ˆ‡‹x–qzex™as”Xp•Vn“Ml—On™PošPošOn™Nm˜Mm•Mm•Nj•Li”Li”Li”Li”Li”Hi–Hi–Kk˜Kk˜Lm™OpPtœVz¢_…¡d‹§q¡pŽ ~—‘…ž˜“¤†ŽŸ’}’}”œ‘˜|Ž’€‘~“‘†œ†œ~¤Ž¥yŽ¨x§uŒ¦rŠ£n‡£l… kƒ¡kƒ¡kƒŸhj”j”o†Œpˆ&&&&&&&&#)#)#)$*$* $* %+!%+!%*"%*"&+#&+##+!#+!#+!#+!%,&- &- &- $.$.#-#-$- $- $- $- $- $- $- $- $- $- $- $- $,"$,"$,"$,"%-#%-#"* "* !)!)!)!)))(((())(('''&&&&&%%%%%%$!$!# """############$$$$$$$$%%%%%%%%&&&&%%(&((((%%$$# """""!!!!!      %#  %'$-+4=BMR)`e4nsBv~Mx€OozFkvBdq=huA^oC7H  +!$.1BE)[]-wyIšX [™¡L•žH†—C~:n€7bt*Uf&N_BQ$E2LS@Z[f[\g\^rwzŽwxƒiju`_b^]_`dlcgpiz“v‡ pƒ¢bu”]l‰]l‰kpƒlq…or„uxŠvt†ECVKEXe^rmdwjaugcvlh{moƒkmfn…[c{W_vZbz]dy_e{fjylpsz‡t{ˆr€‹y‡’|‡•x„’x„sŠlzbquH\]4HJ,?T5=U4V2>W1=V/9S'1K+1I05N9:Q;;P86J87G<;K@?HA@I@@G??E>>C;<@88=459-,6%  "#'*+++*($##%+1 3 32()213;;84@=9C?0A=-><(=;'.,!#%%+--55.55.10,10,.0++,'%') "$  !!!""   #*"dtR}“RŠ _’«Z“¬[˜¬V˜¬V—ªW“¦SŸS U—aŒ•`‰j‚ˆcw|got_]^N\]M`eWgl^dgYXZLLO?SVFGQ94?&>M4brXv†rwˆsr{npylbj`7?4[aUnuhhj\`bTPRKGIB<>@;=?5M0DU8L]>YpOi€Vs‘c€jˆ¡nŒ¥q¦pŒ¥t‹¥rŠ£o† m„žj›i€šh™h™l„–m…˜uŠ–v‹˜——ƒ’‚Œ‘‡€†Ž†’{‚tozˆes‡anƒTgQd}K`J_~F^F^D_D_E_„E_„D^ƒD^ƒD^ƒB]‚B]‚B]‚C`„Eb†EfŠIkŽKo’Os–Xy`‚—u˜Ž} –§¥˜¥w•¢ušŸu˜s–žr™¢u™ž{”™vŽv‰‹q„…|‡ˆ‡‹‡‹w€•t}’dv˜as”Xp•Vn“Ml—On™PošPošOn™Nm˜Mm•Mm•Nj•Li”Li”Li”Li”Li”Hi–Hi–Kk˜Kk˜Lm™OpQvX}¤`†¢eŒ¨pŽ nŸ˜’‡ š’£…ŽŸ‘œ|š{’™}—z‘~Ž’€’‘•’†œ†œ~¤‘§yŽ¨wŒ¦rŠ£rŠ£n‡£l… kƒ¡kƒ¡g€œd|˜j”k‚•sŠt‹$$'' ( ( (& (!*"+"+"+"+#.#.#,#,#,#,%,%,%,%,%,&- &- &- $.$.#-#-$.$.$.$.$.$.%/ #-#+!#+!$,"$,"$- $- $- $- $+#$+##*"#*"")!")!")!")!")!!( !( !( *)** ' ' ' '&&$$&&&&&&&&%%$!##############$$$$$$%%%%%%%%%%%%&&&&&&&&&&&&###"####""""         %05 =C+/5" ! $)'0.7@EOT%eh/ruiq9drDXe7.!!"%.4:@SZ$jq;‰‹M˜›]¡¢b¡¢b’Y™UŒLs@kw3gr._l#\i!R`$N[ KS4U^?Z]a]`daizpx‰qtgiwedfdce^dlgnvvƒ |ˆ¦k{¤`o˜Yb~]f‚hl€ej~ll~qr„vw‰LM_74BPN\eYij^mdXgfZj^^na`pqp‡gf}XXledyhjymn~zv‰ƒ’…†˜…†˜}†”Š™€Œ€Œ|ˆ›o{ŽYb~BKg,6N(@&<$9#%5#%5%%5)(8$!/'%& /.,,..--)((())''+(%%%%#!"$$%'+ -$0"!1$#3%%5'&6+&4(#2&$2&$2&$2&$2)(8*)9*(;/-?44H88M8;S9EX=DW?FZV5:R6;S5:R-3P*1M,2N/5R48N04J44H55I<;I<;I?=I@>JA=I>:F73?62>/+7 (""#%(,, . .,)'#!")01.#1./456;CB2HF7GC-D@*B@*<:$*) '*"03,89):;+<8)85%43#20!+,#)*! # "!#-5C5M\>aoRxUˆe’«b‘©a—­`—­`—«\•¨Z’§V’§V‘ŸbŽœ^•hˆdƒˆeuzWgjNorVtsann\mia]YP_\Qli^[[M//!9?,ahUt}iŠv}uw{orvj_cW^dZmsijl``aVUUPJIE<>@;=?5F6@J6@J4AH5CI7AK7AK6?K6?K102A,-=('7+*:+(6 ,$%)+,,--,+'%%%''$$&'$###!""!"$%') -. /"!1! 0'"1'"1%#1%#1%#1%#1%%5)(8*(;1/B55I<W=?U=?U@@UCCWBBVBBVCFXGJ[JLaKNbGNbGNbEL_@G[=BZ8>V7:F<8D73?3/;+'3&##%')+++ .,)'%$%(01!0!01269:>CB2HF7LH2GC-B@*87 #" #&#-/(24-66&66&;7(:6&65%31"+,#() "#! !%! #! ! %"(#&4&6D6P_AdsUyŽV‰žg’«b’«b–«_–«_˜¬^˜¬^–ªZ–ªZ•£ežaŽ–i‡b€…bx}ZloS`cGON@6=E9?G7AK7AK5CI5CI7AK7AK7AM7AM3=M3=M.PR81;#0:"*& ' ,)32<ENRZ'cj-mt8x=zƒ@|…Hu~Arx>ms9eq9hu<UT/ywAWšž]™\˜ a˜ a•›h“`}ˆYdp@Wg+Wg+fp*jt.it*hs(cp)`l&Vb1Vb1`e^zwnx‰`j{sw†ko~ceecee`cghkohl‰lpfm›bi˜_e‰^dˆdfzfh|nl~qo‚su‰PRf*&7J>NWK[WK[YM]UPZNISXRcmgxrm‚wr‡pj‚]Wogcx~yŽ{{{{ou‹…›{‚œpw’W^:Ab)-Q!%I"$F"$F"#@ "?&&A&&A+'A.)C4.F)#;'%%%)+)(''%### #  ##       "!#& ,*+"-$-$-% .&!/$!/$!/'$5-*:)(?0/F75O98Q>?X@@YC@\C@\EAZGC]KG_LH`KK_LLaPMdPMdSQkPNgLMfKLeFIaBE^)-- ),)05'38)8:&9<'8:'35"*+"&&!#!#"!&! $&##!  !&%%(0#49MRf7mƒI~“Y€”S‰œ[¦e“©h”ªb“©a”©]•ª^˜«_˜«_•¨Z”§Y£W„—K{Dn€7gm8\c-WR1KG%5/%+%$!$& 24:=ADRU)_b6qqOxxV||a||asr`jiW^_ORRBEGBAB>9?CO3=M.NQEUPDTMHRICMHBSSN^XSiTOdJE\<7N83He`uxxvvŠio…ou‹ho‰[b}:Ab!)I D!E$'H#%G$%C#$B%%@$$?*&@.)C1+C0*B.$##$%%###"""" "! !%)**+!+!+!+",# .$!/'$5+(8,+B/.E87P=;U<=V@@YDA]EC^GC]IE_MJaNKbPPdQQeTQhPMdQOhSQkPQjNNgJNfFIaAG_=BZ>AY>AY:=V8;S3:O06L04H+0C+0C-2F13C34D74B42@1.<)&4 -'%%$%(()***)'''''-1.&5:9;:9;FB8HE:KG1HE.<<('' ),)26(49*:=(9<'13 +-$%!"%%&!#! "$( %0#,7*):7H*I^/]rCzV•Z{ŽM“RŒ¢a“©h”ªb“©a”©]”©]—ª^—ª^•¨Z”§Y¢V‡šN€’Hy‹AtzEnt?d_>ZV4B<272'+"!$#"+-58FIHK"OS'SV+RR0UU3``ErrVoo]mlZdeUVWGIJFCE@AI%IQ-X^&_e-fo%js(jw(m{+gs5gs5_d]qvnrz˜{ƒ¢{ƒ¢lt’kkrggnhigghfd`zgb|cd’ff”af‡[`aevcfwfi{mp‚ipƒOWj!5."*/+7E=GH@KG-0B,0?+/>)+:#%5.)(((((((+,,))))('(),#&3;7:@=@KB:KB:G@4D=03/'#!*-*28.39/77)33&--!)($!" !"&# " $"!"$$".&2"+EC^*^e”$j— qž…©1…©1€™C…G£b”¨g’¤_“¥`™©`™©`–©]”¦Z‘£Z¡W£W¢V‡šN€“G|ˆIs@lrD`g8TT;EE,530.5050:5 ?;NEXNWQ VO ZW\Y +^] lklh)kg(kc>phCWV=JI0CB>FEAEHE@C@j}ZI[8  +"%/1IK*{zF—–b˜™Y˜™Y•]„‹SkuJFP%"-'%3'56=@G$OTX^&ak dm#er#jw(iu8lw:diasxpt|›z‚ yŸqx—nnueelefcddbb^xgb|^^Œ[\‰Y^Y^aevcfwfi{hk}jq…OWj.+'"*-%0B:EG1>7**&" *-*16,4:066(11#*)&&# !!!# !#!$(%''%''* ,,GPk6mœ,w§6}ª|©¥-„§0†žH†žH¡`£b’¤_“¥`˜¨^–¥\’¥Y£WŸU‰›RŠQŸS‹žRƒ–JŒLw„DovGho@`_FRQ8FD0A>*?9;6<7>:I? +NEPJSL VSYV\[ba ZW^ZSK&UM(bbIkjQPOJRQMTWT?B?6>C;BH;AI;AI7@N7@N1+5/:7BCMNX&[b$em.pz4t~8n:eu1Tc(HXIRfo9tzEms>lq:mr;q~Jt‚M-A # %65UU5ˆˆX’“c„^†`q[P_:'7"!  $%$*-25:$AC!IK)T\$Y`(bk'hq-is;s}Dprr~€€vƒ§n{Ÿgp˜isšmn~`aqe`cc]ac_pjfwfeŠba…_`€]^}bcucdvhi{hi{cj~NUi-+*)'-(6BBV8=P26L)-C#)8 '6"(7&5 2//..,)())++,,,,-,+, "/')6969>;>E=:D<9A945.(#$"!+),5-/90.4*'-#&%  !! #""#"&"& #-,< B[Qj(d"x“6…—9„–8}‹@‡•J‰•V„Qš_bŽ£bŽ£b’¥^’¥^¢YŸU‰šS€‘Jƒ’RŠ™YŽœ]‹šZ”O}JvˆEk}:cs5\l.[_1SW)JGA>IEE@ +JFNJ UQWTZZ[[`cbfhscnep juQ] OZ ]jZghn2sz>^bBMQ0BBBCCC;@MO1AC F!#N !L$"P$"P%"N&#O,'I,'I)&D'$B)#B+%D/*F.)E.()**%&&'&%$    ### $"&** /$#3&&:((<)(?-,C3/M74R=9YC?_HBcHBcD@`D@`IEcKHfOIjSLmTLrTLrWPsVOrYRsZStYUuWRsUSoUSoSUkPQhOQeOQeMQeLPdHMa8=P>BX7;Q)/>#)8&5#2#4 20/0.,)))++,,,,,.+,!.,.;969>;>E=:D<9>713+&$" *()3*,5--3)).$'&"!  #!! #"""" !"23C'H`Ph'>Y8S;M?Q:H`n#s@z†G†“XœaŽ£bŽ£b¤\¤\‘£Z‹Tƒ”M{ŒEyˆH‚‘Qˆ—W›[‰œW„–Q}Ls…Cm~@iy;fjP2F\@TjB^wOk„Wu`}˜gƒžj… nˆ¡m‡ n†j‚šh€—h€—n„•o…–{‰•|‹–ƒŒ”ƒŒ”‰Ž’‰Ž’ˆ‹‘ˆ‹‘‚‹“‚‹“|…˜{„—s€—p|“cu]oŠTjˆPe„G^~E\}C\B[~C[€C[€@\ƒ@\ƒB^„?[‚@\ƒC_…A_…A_…Dc‰Ge‹FiGjMpNqg‡u•›ˆž††œ„›wŽšv–x•vŽ•t‹‘pŒv‘w‰Œ{‰Œ{……~ƒƒ|}€…„}†”|„“l~™k}˜f{šcy—Zu—Ws•Rq—Sr˜Qp›On™Lk•Lk•Lj“Lj“Ki’Ki’JhJhJhIfJg’Jg’Li”Li”Gn•Jq˜QzœY‚¤j‹¨m«y•¡x” ‰‹Ÿ““ ‡š‘–€‘–€”’}‚Ž‘ƒ“’‘”“†’ †’ ’¦’¦{Ž¦x‹£tˆ¥s‡¤p…Ÿoƒžvˆ•w‰–|„~’†‡šw‰›xŒo‰›mŒk‡˜e%)%)&*'+',',(-(-%-%-%-%-&.&.&.&.&.&.'/&.&.'/ '/ '/ %1%1%1%1)2)2)2)2)1)1)1)1)1)1)1)1)1)1'0'0'/ '/ '/ '/ )/#)/#(0%(0%%-#%-#%-#%-#%-##+!#+!#+!"+!*!*!*( ( '&&&&&$$$$%%$$$$$$######$%####$$#$%%&&%%%%%%&&&&$$&&&&%%%%%%%%$$$$$$#"!!#"!!####'' + +"-#.$/$/%0'1(2!(2!'1(2!'2(3)5)5,7,7ˆ‹f„ˆcy„bs~\LcF.    !%&#(, /3'<@ DI(RQ&a`5sqO„_vy}…‰~Œ®z‡©fv¡ap›gp…S\qZX_XV]YW^`^eed{ih\^y[]xa_rcatcgtfkxhm~LQb.'),)&40-;1,6NISE>D8272.51,4'#/'#%"$+.67A#F',T$(Q#!O" N%!O&"P)"L)"L(#F&!D( D*#F/*F/*F%';0)+ -*((,,,*(#!"     ! !!!&&"-&$2)':$#5 6"!8)%F/+K3/M;7UB=[F@_GA`E?^GAbICdLEfMFgOGmRIoSMsSMsQJrQJrXQy[U|\Ux[TwYUuUQrRSlSTmVVoWXqPSeDGYGJ[DGY7;K)-=!):&7 %2 %2#2!0./.,--..,,--/0 0 0$%0..:95;=9>C:9A87:10.%$ !$'*&-,*20///(((! # %&!! #'*6 9E/QZ7R\8:=14$ $':=3O\CerYzŽb‡›nŒ¡i¢jŽ£`¢_X†”P}ˆMr|BnuDx€O‰U‰‘^‰˜XŠ™Y„˜N}‘GwŠDoS3J_?UkC_xPl…Wua~™cœg„Ÿl…Ÿk„ži„›hƒškƒ”kƒ”v†‘x‰”‘’‰“‡Œ’†Š“†Š“„‹“„‹“~‰˜~‰˜x…šw„™n—iz“^sYm‡Rg†McE^~E^~@[~?Z|A[‚A[‚B]‚B]‚B\ƒD]„B\ƒD]„C`„Eb†EdŠIgŽIjLl’Ps‹RuqŽ~››Šˆš~—z•x•y”xŽ“w‹sŒ}‰Šz‡ˆ‰‰€ƒ…€€|}€„…‰y„™u€•i}™h|˜dzbyœ[ušVq•Pp˜Sr›Pp˜Oo—Lk”Jj“Jj“Jj“Ii’Ii’Jg’Jg’Kh“Jg’Jg’Jg’Li”Nj•Hk“OršV}\„¤l‹¨p¬}–¢{•¡œ ”–‡—‚‘•‚”‘~Œ}Ž’†”ˆŒ‘—’˜„…Ÿ’¤’¤{¨y‹¦tŒ£o‡žq‰špˆ˜y‹zŒ„˜y‡š|ŸpŒooŽ›n‹—l‰•j'+'+'+'+',',(-(-%-%-%-%-&.&.&.&.&.&.'/&.&.'/ '/ '/ %1%1%1%1)2)2)2)2)1)1)1)1)1)1)1)1*3*3)1)1)0!)0!)0!)0!)/#)/#(0%(0%'/$'/$'/$'/$%-#%-##+!#+!"+"+"+"+( ( '&&&&&$$$$%%$$$$$$$#####$%####$$$%%%&&%%%%%%&&&&$$&&&&%%%%%%%%$$$$$$#"!!""$$##%& *!+$0$0(3)4 (2!(2!*5#*5#+6$+6$*5#*5#*5!,8$.:#.:#0; 0; 5@CN-T]5PY1Wa(`j2jv2r~:~ˆ@xƒ:bw3Ti%CU .@ &1$/@EnrDx~Dot;gs4kw7i|IbuB.>#16QV3ƒ^…‰dv‚`p|ZNfI 7 !"&(, 48;?NL!`_4wtS†„bqtxy|€s€¢z‡©kz¥ap›hq†XavZX_XV]WT\ZX_ZYp`_v[]x[]x^]oa_rdhuglyhm~LQb,'),)&41.<=7Ab]g[TZ>7=/*21,4($0 (+'#'-.35< D#'O!&NJJ#M$ N(!K'J(#F'"E( D'C)$@)$@(+?!5))+*''*+, -+)('##        !   "#+$!/" 3!245% A*&G/,J3/M<6TD>\HCaKEcF?`HBcICdICdLDiLDiLFlMGmMGnQJrRKsSLtZRvWPsWRsUQrSTmOOhOOhLMfLPaORdIL^EIZ@DS7;K+3D (9%*6!&3#2"1 2...--..--../!3"$3"$3$%0..:738;7557..,#"$%$'&))0/-43===<<<547('*$  ' '#"#!+"(4%25?(2$' $ $+.$;>4R_Fiv]|cˆœpŒ¡i¢j¤aŒ ]›W…“OxƒIp{@krAowFxM‰U‡–VŠ™YˆS‚—M}JwŠDvƒAr=st9eg+^_!XXYXXWcd cd ekjomxp{ +l}evYl`s VoNgRnYubufy bc#TUTN?]WH@BK8:B3;E3;E-:J,9I,:N6CX4K`?UkE`zQm†Vt^|—b›eƒžiƒœh‚›g™g™m…–m…–wˆ’z‹–‘’‰“†‹‘…‰’…‰’„‹“„‹“~‰˜|‡•x…šw„™m~–hy’^sYm‡Rg†McC]}C]}@[~?Z|A[‚A[‚B]‚B]‚B\ƒD]„E^…E^…Da…Fc‡Ge‹IgŽIjMm“Ps‹X{“x–•~››Š‡™}•xŒ“wŽ“w’v’v’vŽ~Œ}‰‰€‰‰€‚„‚~~…„‡‹xƒ˜u€•h|˜fz—byœax›[ušWr–Pp˜Sr›Mm•Oo—Lk”Ii’Jj“Jj“Ii’Ii’Jg’Jg’Jg’Jg’Jg’Jg’Kh“Li”Jl”RtœW~ž_†¦mŒ©q‘®z” z” Ž’Ž’‘˜ƒ–€‘•‚”‘~Œ}Œ„‘•‰Ž“™”š†’ ˆ“¡’¤’¤{¨v‰£rŠ¢pˆ q‰šq‰š{‘Ž{‘Ž…™z‡š|Ž rŸpŽ›nšm‹—l†’g(-(-(-(-(.(.(.(.$-&/'0'0&.&.&.&.%-%-'/'/'/)1)1)1%1%1$0$0'/)1)1)1)1)1)1)1)1)1*2!*2!+3"+3")1)1)0!)0!)0!)0!)0!)0!(1$(1$(0%(0%'/$'/$%-#%-#$,"#+!#*"#*"")!")! *! *!'&&&%%$$####$$$$##########""""$$%%%%&&&&&&&&&&&&$$&&&&$$$$%%$$%%%%$$#"##$$%%&'"+#,#.#.'0!'0!+5&.7(,6',6'.7(.7(.7(/8).7(.7(/:&/:&0<&0<&2<$5@';E(>H+MV.U^6^e4`h6nw:w€C~ŒHuƒ?\r8Ka':N"': . , +02OP0y{TqsLntAjp=hrAr|JWfA"0 *>H$nuM‚‰ay„Ws~Q\qI3G !   %$+. 03%E@%b]C~wb†€jjmqorvn|žu‚¥fq©al£hj“_aŠ\[dWV`YWU\[Y`^lb_m`[u`[u^Ynb]rdcugew_evLQb. %$+$!/+(6C?KeamWT]HDN.,1$"'""(%''##)*/.57>@DDI J'J'J&H(!K%D%D+%D("@'$9*(-/E30L>iJBnMErRIqTKsNInQKpTPqSOoSPn[XvXZpUVmPPbFGYEHSEHS@DN=?*.*"%!"+*6)6,9.<.<.9(4%,!)%"##"!     !)&5=9PaJgxayf‡žt£gŽ¥h‘¦`Œ¡\œZƒ“Qu‚Ifr:`c>ehCnrKz~V‚ŠWŠ’_‹ZˆšX‚–O“K|ŒAwˆAY\[Z;mlL\VRF@<6;H6;H-/5@,5@,6B+6B+9C+:E,>H+CM0IR*R[2Ya/]d3`i,r{?~ŒHuƒ?Yo5G]#8K':"/ , +)+ 79`b;~€YqwDmr?gq@nxGiwSTb>$. )3[a9}„\z…Xs~QauNCW/&" ! &(-/!HC)idI„}gycdgkilpiw™o}Ÿal£Zeœ`b‹bebajXWaZXVZXV[Xf]Zi`[u`[u^Ync^shfygewbgyNSe/ &$*)# .GBNeamTQ[LISKJN/.2##''((,+017699@@FI%G(!K'J%G%D& E*$C)#B'$931FDGR'$ ((('''&***+)'%$   + + + +      ""$$((+0')?&(>-*F86RAQXWoyh~ˆw‰’pŠ“q“s‰ompr]c_VUQH>9:&"" #/2)7C%K%=K"9F;F%:E#3:$)1'.!'.!'+'+'+&*%(%(%($'"  #+(;C?SdMm~g~–k‡žtŽ¥hŽ¥h‘¦`¥_‹›Y‚’OvƒJdp8Z^9]`;eiBpuM{ƒP‚ŠWˆšXˆšX‰V†šR…–JD‰33=25?46@65A16B25C.6D/8C/8C/9E/9E/9F-(=."2J>N}ztPNH"%"#("' ) ) )())')*)'%$#            !!$'-$#5)&=0-D;4PA:VG=aKBeNHhICdGBgD>dF@gChB:dE?fCkRGtYN{\V~^W]\z^]{Y[qY[qNRa@DS11?GGUFENRR[379!%&)16,39#)8#2#4 2!2!2!2"3!2!2"1"(7$+8$+8$(5$1%%3'&4#$/&!#$"''0AH9X`Qm}V‚’kŽži‘ l‘£g’¤h—¤i˜¥jš¥j—¡g—cƒ‹XouPU[5,.#$ 49AE,GS/BN*7H4E=O>P>P=O;G4@0=.;0;0;0=3?3@0=.9,6*."& '1(BKC[mMqƒc…žhŠ£m§_§_‘§_‘§_‰¢W~˜Moƒ@bw3Z]3XZ1_`;jkGrxJ|ƒT€‘Q†—W YŒŸX‰¢L†žHƒ™=ƒ™=„œ4ƒš2ƒ¡& $vžus¥r¤y°~µy¯s©xyžƒ€›ƒ¡‚ u˜x›x“z”ƒŒ(„)mj0\Y]U=WO7@@@;;;8DPBNY;SdE]mKdwUo‚\uŠ`yŽe|‘h~“d€’f‚”o…–q†—|Š|Š†ŽŒ‡ŽŠ‘‡ŠŽ„‰…Š‡‘‡‘~‡•~‡•r‚–q•j€—f|”by“]uŽWnQh‰OeˆJ`ƒE\‚E\‚C[€C[€?\€>[@]‚@]‚=\‚>]ƒ=`…>a‡?bˆAeŠChEj’LsLsbƒ‡u–šŠ ††‚Žšv‹—sŠ•t‰”s‰“vŠ”wŒ’z‹‘y…‰}…‰}ˆ‰…„†€„€„…‹„‰s•q~“e|d{›`{]x›YuœWr™QqšPp˜Pm–Pm–Lj“JhJhJhJhJhIi’Ii’HhHhGf‘Gf‘Fh’Gi“Nq—X{ `„§g‹®q’£s”¦~˜–ƒ›“ŸˆŽš„˜€—‘”„‘€‘€‘€‘˜–”Š’žŠ’ž’ª’ªx©v§uŠq…™uu}™~š…rˆŸu’žs‘rŒ–r‹•q‰u‰uƒŽuƒŽu%-%-%-%-&.&.&.&.'1'1'1*3)2)2'1'1'0'0'0'0'1'1'/'/)1)1)1)1)1)1'/'/)0!)0!)0!)0!*1"*1"*1"*1")1)1*2!*2!*1"*1"*1"*1"*0$*0$*0$*0$(0%(0%&0%%/$%-#$,"%-#%-#&+#%*"%*"%*" *! *!( '&&$$$$####$$$$####$$#%%%&%$%%%%%&%%&''&&&&&&%%&&&&$$# # # # $!$!$!$! %" %" % %('"# && ( ("+"+$- (1$*3&+4'/8+1:-0;03=24>36@67B78C89D99D99F69F69G39G3=H4=H4>J4>J4>K2@M4CN.HR3T^3]gmuBmx=fq6iu8mx;Wi4EW"4C%4". -!*%.13WX8{|SvvMqvFkp@ltHx€S{ƒXIQ&9@dkAƒŒV‰Sq‚Ibs:*A% !!# )'=;2kaN‡}j‚zivn]eiosw}ex™^q’_d—X]\^‰`aŒts~_^i][T\ZSYUZ`\a`Zmb\od\rd\rcauecxceyQSg2 ',..$#5MIUeamWT]TQ[SU\ORXBFL).4&"##!#',334288>AAB B? <&"B/%D/%DHhH>kH>kI?lNCpSLt\V~\[x\[x^_v]^uY]lNRaLLYMM[BAK@?HRVWDHI$+1%,2*0?&-<"'9#4"3"3"3"3"3"3#2#)8$+8$+8$(5!&3##1$$2!!-#  %+98A\[dw~o‚‰{Ššs y”¤o‘ lŽ d‘£g™§k›¨mš¥j›¦kš£o™¡nŽ”n}ƒ^OQ7+-49>B(GS/:F"3D1B7IEW"CU BT>J8D/<,9-70;2>3?3@/</:/:04!#' 0:1GQIasRxŠj†ŸiŒ¥oŒ¦^Œ¦^’¨`’¨`Ž§\ƒœRvŠGcx5XZ1NQ'TU0`aVfH`qLfxWpƒZsˆ_wŒd{f}’d€’f‚”q†—r‡˜}‹’}‹’†ŽŒ‡Ž‡ŠŽ†‰„‰„‰~†‡‘}†”|„“r‚–o“i~–e{“ax’\sVmPgˆNd‡I_‚C[€BZBZBZ?\€>[@]‚@]‚=\‚>]ƒ=`…>a‡@c‰Cf‹DiDiJqŠMtŽh‰y™Š †ˆŸ…œx‹—sŠ•t‰”sŠ”w‹•xŒ’z‹‘y‡‹‡‹ˆ‰…ƒ…€€„‚†ƒˆŽ…Šq~“q~“d{›e|`{]x›YuœWr™Sr›QqšPm–Nk”Lj“JhJhJhJhJhHhHhFfŽFfŽGf‘Gf‘Fh’Gi“Os˜X{ b†©hŒ¯q’£q’£š˜„žœ“ŸˆŽš„‘™—’•…Ž’‘€‘€’Ž˜–”Š’ž‰}‘©}‘©x©uŒ¦uŠsˆ›uvŽ}™~š‡žt‡žt‘rœq‹•q‰’o‡Žt‡ŽtƒŽuƒŽu&/&/&/&/'1'1'1'1'1'1'1)2'1'1)2)2)1)1'0'0'1'1)1)1)1)1)1)1)1)1)1)1'/'/'/)1*2!*2!*2!*2!'0)1)1*2!+2#+2#*1"*1")0!*1"*1"*1"(1"(1"&2"&2"'/$%-#%-#%-#"-""-"!,!!,! *! *!( '&&$$$$$$$$$$$$##$$$$%%%%%%%%%%%%&&%%&&%%&&&&%%%%$$$$# # # # $!$!$!$! %" %""&"&!)"* !)&&&!*"+$- '/#'/#+4',5(.6*2;.5=15?46@66@67B79D9L7>L7AM9AM9AM7@L6AO4DQ6IT3MY7Yb8ak@muBt|Iw„Dt€Aiv6`l-Pc*DX4D*:%1".$-$-*,8:fgByzVruDoqAkq>rxE~„QtyFAGZ`-„ŒK†ŽMy‰Dm~98O ,!!"!.,DB5wlWŒl~wbvoYkmm{~}cuXk…NaK^‹KW~HS{nqƒaevV[SW\TZ[R_`V`_ca`d`[j`[jb`sdcu`cwQSg 3),-."#5ON^baqRR^PQ\LR\IOYBIX9?N)0C.$$$$$)##1$$20/426877:!='!;3-GD;CPFOlfG‡bž¢_©®k{…[#*!)+(*'/)0 &0%/#.!,+)%%            $(.$#5.)?50F:4L;5M<6P?8R=6W<5V=8[?:]D;aE=bD;cD;cB9bB9bF\…=_‡>`ˆCeDfŽFmFmNr…V{Žz˜Ÿ–‘¤ˆŒŸƒ’š{˜y•x•xŒ”|Œ”|‘~Œ}ˆŠƒ‡‰‚ˆ‰…‚„€„‚†„Š„Šr}™r}™c|c|^zœ]x›YuœUq˜Pp˜QqšOl•Lj“Ki’IfJhJhJhIfGgGgFfŽFfŽGf‘Gf‘Ef‘Kl—Rv™Z~¡b‰£iªx“¡w’ †›‘‰Ÿ””‡™ƒ“—„’–ƒ‘€‹Ž~ŒŽ‚ƒ‘–•—ˆ‘ ˆ‘ ‘§‘§{¥vŠ wŒ’x“~“„”†…w†žxŠ rŠ rŽ›p™nŠ‘u‰s„Šy„Šyƒ‹€…‚&/&/&/&/'1'1'1'1'1'1)2'1'1'1)2)2)1)1'0'0'1'1)1)1)1)1)1)1)1)1)1)1'/'/'/)1*2!*2!*2!*2!'0)1)1*2!+2#+2#*1"*1")0!*1"*1"*1"(1"(1"&2"&2"'/$%-#%-#%-#"-""-"!,!!,! *! *!''&&$$$$$$$$$$$$##$$$$%%%%%%%%%%%%&&%%&&%%&&&&&&%%$$$$# # # # $!$!$!$!$!$!#( &+#$,"'/$%-#!)!)!)#,$- $- )2%*3&/8+1:-2;.7@39B58C87B7;E:=G=@K@BLABLABLACO?DP@DR=CQmKCoRJvc_„SPuVTtfd„cc|`azXZnOQeEGU?BOPYN^f\YdLNX@EO>>H73;7)1.&,4&,4 (3%/#2"1#2 '6")5#*6%*6',9**8((5&*2!%-(#)01Y_a‚tˆ“z p”¥ušªsŸ¯x¡¯x«tœ¬pž®r¤°pŸ«l›§j—¢e– f™¤i¨r˜¤m‚Š_RZ07< BG+@K0AL1?O-@P.BX)G^/Qe+Sg-Wj%\o*ds1ds1^n2Tc(Pc*K_%K]!J\ KX"DQ=B$-3 "$3;7LTQfvZ}q¥r¦s¤h£g“¥`“¥`‘£Z‰›R}ŒEix1NQ;? @=OL-Z]:egEm{Fz‡S‚•P‹XŽ¦R¨U’ªP‘©O«Dˆ¦@‚¥0„§1„¬(x p™fi—f”fŽir’ e…d† [}^v`xn€t†p|dpfz n‚š®6“§/IM +KO d[:ndD[XRa^XS_kYeqXk|]p\rˆ^uŠ[w‹_zbg„”t‡‘wŠ•ŒŽƒŽ‘ŠŒˆŠŠ…‰Šƒ‡ˆ}…Š}…Šx„v‚Žq‘o}f}’f}’cz”ax’Yt’UoSj‹Ne…Ka…H^BZBZBZBZ?\€?\€>[>[=[„>\…=_‡>`ˆCeDfŽFmFmNr…W||›‘Ÿ–‘¤ˆŽ „“›|Ž—x•x•xŒ”|Œ”|‘~‹|ˆŠƒ‡‰‚…‡‚‚„€„‚†„Š„Šq|˜q|˜c|c|^zœ]x›YuœUq˜QqšOo—Ol•Lj“Ki’IfIfIfJhJhGgGgFfŽFfŽGf‘Gf‘Fh’Lm˜Swš\€£dŒ¥iªw’ w’ ˆž“‰Ÿ”’œ†™ƒ’–ƒ‘•‚Ž’ŒŒŽ‚Ž„‘””–‡Ÿ‡Ÿ~¦}¥y£xŒ¡wŒ’x“”†”†…wƒœv†n†n™nŒ˜mŠ‘u‰s‡Œ{‡Œ{„Œˆ†&0&0'1'1'2'2'2'2+2+2)1)1)1)1)1)1)2)2'1'1'1'1'1'1)2)2'1'1'/'/)1)1)1)1)1)1*2!*2!*2!*2!*1"*1"+2#+2#*1"*1"*1"*1"*1"*1"*2!*2!+2#*1"(1"%/ '/#'/#%.!%.!$,"$,"$,"$," *! *!( ( &&%%$$##$$$#$$$$##&&&&&&&&&&%%&&&&$$&'&&%%%%&&&&&&%%$ $ # # $!$! %" %" $ $%(&) '/ )0!)3#)3#(2!(2!)3")3"+6$+6$19/3;13=24>38C89D9>I>@K@@L?CNACNAEPCCQCESFJUHJUHITGITGIVDIVDGUAGUAIV>IV>N[;O\<\d>dlGqvF{€Ox†Dx†Dr…@fy4Yl,L`;J3A.9+5'1%/$-#,32RQ8}{WsrNopCprDnsB|P…Rci6‚I’“Z€Ky‰DPg1!7 # # !! $!3/TPAw]„}cvsZvsZwtw‚~`j„R\vUY‚UY‚YZ…YZ…c_ypl…deiYZ_\\WZYU_\Xa]Y]Yc`]ga[ne^r`asPPb 2&.0 4**?\^n\^nPWeSYhNVgKSd9A^6?[8@\;D`,6N3('" " , -)),..- 20&>B7OXNYe[eys]ˆ‚l››_ Ÿd¡§K²¸\±¾c¨µZ*7"' ,%*-*-)0)0$+$+!&!&!) ((%!                      #(. /" 3(&9.)@3-E5-L80N51Q73T@;`GBgH?gG>fF:cE9b@4]?3\E7eI"DK/[>[@\…B]†?_‡Bb‹EdGgHlHlNs€`…‘†¡†¡–¥~’¢{“žw‘›uŒ•tŒ•tŒ“x”z‰Žˆ~‡Š€…‰‚„}‚„}…€ƒ‡‚ˆŠ“…†q|˜q|˜`|›`|›a|ž]x›WvšUt˜Rq—Pn•Ol•JhJhIfIfIfHeŽHeŽHfŒHfŒHdHdEdŽFeCg‘Ko™Py›Y‚¤kŽ¦m¨z–œy”›¡•Œž’’œ†Ž˜‚–ƒ•‚Œ’‹‘€‰…’ˆ“—“—… … }’¦z¢zŽu‰˜{‘†|’ˆšzšzŠœrˆ›qŠ›qˆ˜oŽ˜m—lŠv‰uƒŠ‚ƒŠ‚Œ‡‡”'1'1'1'1'2'2'2'2+2+2)1)1)1)1)1)1)2)2)2)2'1'1'1'1'1'1'1'1'/'/)1)1)1)1)1)1*2!*2!*2!*2!*1"*1"+2#+2#+2#+2#*1"*1"*1"*1"*2!*2!+2#*1"$.%/ '/#'/#%.!%.!$,"$,"$,"$," *! *!( ( '&%%$$##$$$#$$$$##&&''&&&&&&&&&&&&$$&&&&&&%%&&&&&&%%$ $ # # # # %" %"$(#&*%)-#)-#*1",3%.7(/8)/9(/9(/9(/9(.8&.8&08.3;16@66@6;E:=G=?J?BLACNAEPCFRDGSFHUHJXJMYKNZMMYKNZMMZHMZHM[GM[GO\CO\CTaAUbBaiChpJtyHy~N{ˆFx†Dr…@fy4Xk+Ma JY-DS'9C$.9'1&0&/%.-,65feA}{WqsEmoApuDw|K€†S…R‰ŠQ–—]ˆ˜TJZp;$;% '! ! $!;7(^ZJ‡€ez`vsZurYurt‚~fp‰Xb{W\„W\„YZ…YZ…d`zkgjko\]a[ZVZYU^[Wda]^[d_\ed]pf_s^^pNOa 2,1""7##800E^_o[]lPWeOUdNVgJRb6?[3f@4]8,U?3\B7_F9fK>lPFsWMzWNw^U~`]‚]Y~g`ibƒa^|ROmKI[MK^BGGHML_lMQ^?MY7IT3JR:JR:GJ@AD:57=*,3!0/#/ %2$(7$(7&(5&(5&&9&&9+0;&,6037PTXwpŠ”‚›w›w’u˜£{˜¢|š¥~˜¥~–£|—¤uœªz «w£­z¨r—£lœhœh—¢n›¥q”šnsyL@H+@H+dmLv…R‚^‹Z“¥b–«_•ª^–«X•ªW”¬O©LŠ¥Aˆ£?„—=€“:€:m|(m&n'k$^tXu\zn‹"n‹"oƒ'fyO^KZ Fvg—–əͦÂ,ˆ¤bd^aSL2YR8RR[cbk]g~cm„]r…]r…[v„a|ŠfƒŠh†Œz‰‡}‹‰…Š…Š‹ˆ‰Š‡ˆ‡„‹ˆ…Œ|…xˆu€Žtk{iy_x”`y•^x–\v”Xs‘TnŒRjˆKcI`D[|BZAY~AY~AY~?Z|?Z|?\€?\€?Z„@\…?_‡Bb‹EdGgFjJn‘Qv‚f‹—ˆ¤‡¢Ž”£| y‘›uŽ™sŒ•tŒ•t‹‘w‹‘wˆ~ˆ~‡Š€‡Š€„‡ƒ|€„‚††ˆ„…Žq|˜q|˜`|›`|›^zœ]x›Ut˜Ss–Qo–Nl’Lj“JhJhIfHeŽHeŽHeŽHeŽHfŒHfŒHdHdEdŽEdŽEi“LpšU} _ˆªn‘©n‘©z–œ{—ž¡•Œž’š„™ƒ‘˜„–ƒ‹‘€ŠˆŽ„’ˆŽ‘•Ž‘•ƒžƒžz¢xŒ w‹štˆ—{‘†|’ˆšzšzŠœrˆ›qŠ›qˆ˜oŒ–k‹”j‰u‰u„‹ƒ„‹ƒ€ˆˆ•‘'3'3'3'3'2'2'2'2+2+2)1)1)1)1)1)1'1'1&0'1'1'1'1'1'1'1)2)2'1'1)2)2)1)1)1)1*2!*2!*2!*2!*1"*1"+2#+2#+2#+2#+2#+2#+2#+2#+4 +4 ,4#7?-7A0.8&(1$(1$%.!%.!%-#%-#$,"#+!",# *!( ( ( '&&&&##%%%$%%%%%%%%%%%%%%&&''((''''%%%%&&&&&&%%#!#!#!#!#$#$#$#$# # %" %"&(#(*%-/!-/!05 48#6<$;@)7B'8C(8C(5A&0;'0;'3IT`#21&$5.!6/"9;(@B/IS4ZeFnM}Ž\†šYŽ¢a•§d–¨e”©]”©]”¨WŽ¢QŠ LˆJƒ•E|Ž>wƒ?mx4^i,Zf)Wf&Yh(Xv&_}-h„2a}+b}.a|-Dn=gOt´¢Ê¥ÌÁÉÃ̱¾,Ÿ«†(|ƒgf2aa,`aWmmddmtclskur|ˆyƒˆ|†‹‡ˆŠˆŠŒŠ‰Œ‰ˆŠ‡ˆŠ†‡‰ƒ„ƒŒw‹xƒŒp~o}dwex\w’\w’\v”Yt’Xs‘TnŒPh†KcH_D[|AY~AY~@X}@X}?Y~?Y~>Y‚>Y‚>X„@[†>]‡BaŒBfŽDiCmFoU~€m•—‰¥‡£Œ–¢~“ {’™v˜t—v—vŒ“xŠv‡‹}…Š|…Š€ƒ‰ƒ…~ƒ|€„‚†Š’|…l~™l~™`|›`|›\zœYv˜Qs–No“Pn•Om“Lj“JhIfJhIfIfHeŽHeŽGe‹Ge‹GcŽGcŽBaŒFeHk•PtžXƒ `‹¨p’¨q“©}•˜‚š¤‹ŸŒ›‚Ž˜€”„Œ’‰‚‡Ž†Œ‡Œ’Œ‘—‹–ŽžŽžzŒ x‹Ÿz‹yŠŽƒ“~ƒ“~Š™tŒšvŠšs‰™r‹—s‰•q‰’w‰’w…Š€†‹ŠŠ~‰‰{Ž„–˜'3'3'3'3'2'2'2'2+2+2)1)1)1)1)1)1'1'1&0'1'1'1'1'1'1'1'1'1'1'1)2)2)1)1)1)1*2!*2!*2!*2!*1"*1"+2#+2#+2#+2#+2#+2#+2#+2#+4 +4 ,4#:B1NYGDN=+4'(1$'/#%.!%-#%-#$,"#+!",# *!( ( ( ( &&&&$#%%%%%%%%%%%%%%&&&&&&''((''''%%%%&&&&&&%%#!#!#!#!#$#$#$#$# # %" %"')$(*%-/!/1#16!48#8>&>D,:E*;G,I>7JJC>@4=?3KI@RPG`YLibUwmVŠ€h“^™•cŸ¡Z¡¢[­´N©°K¬ºF³ÁM±Ãl¦¸`)9& &$$"$+$+#*#*"'#(!) (''&"  +  +                    ##)+.08"=%#C.,M41T53U>8_@:bD;c@7_>5]?6^B;^E=aE@cGBeNGjRJnTNtTNtXRwWQvWUuRQqNNdJI`GLRJOU^gSW`LKW3JV2HR3HR3HR3EP1?G6;C28>:7<9-67&/0 (+%( *() -"*6:Bcjdˆ‚‰x‰x“z’–|’š{“›|”œ•x}~gVW@8:&=?*V]C{g— z•w—lŒ–k’žl” n˜Ÿp}„ULT5>F'?H2@J4:K4:K49P5:Q7@U1AV3H\0I]1Pb-L^)Pa#Sc%Xf)[h+Yg*We(T\+RZ)GA+/(&,5,GQ2^hIm~Kz‹Xƒ–VŠ]¢`•§d”©]”©]•©X£SŠ L‰žK‚”C{ºÀ[¾Å`¶¶­¬x€xTULFOVMV]CLXDMYMV\S\a^`bmoq€ƒ‚…ƒ…‡…†ˆƒŒƒŒv€Šu‰m|ŽkyŒdwcvŽ[v‘\w’]w•Yt’WqRm‹Og…Jb€G^~CZ{AY~AY~@X}@X}?Y~?Y~>Y‚>Y‚>X„@[†?^‰CcChFk“FoGp[ƒ†s›‹§‡£Œ—£’žz”›x˜t’˜w’˜w–|‹‘w…Š|…Š|…Š€ƒ‰ƒ…~ƒ|…€ƒ‡‚‚‹“{„Œk}˜k}˜a}`|›\zœYv˜Qs–Pr•Pn•Nl’Ki’JhJhIfHeŽHeŽHeŽHeŽGe‹Ge‹GcŽGcŽEdŽGf‘Im—QuŸY„¡aŒ©q“©s”ª~–™ƒ›žŽ¢ˆ‰›‚Ž˜€”„Œ’‰‚‡Ž†Œ‡Œ’‹–Š•€Œ‹›x‹Ÿv‰œ{Œz‹ƒ“~ƒ“~Š™tŒšvŠšs‰™r‹—sŠ–r‰’w‰’w‡‚‡‚ŠŠ~‰‰{Ž„–˜%4%4%4%4&1'2)3)3'1'1'1'1'1'1'1'1'2'2'2'2)1)1)1)1'1'1&0&0'2'2)3)3*3*3)2)2*2!*2!+3"+3"*1"*1"+2#+2#+2#+2#*1"*1"*1"*1"*1"*1"+3".6%>F4FN=/8)'0!'/#'/#%-#%-#%-#$,"$,""* ( (**''&%&&&&%&&&$$%%&&%%%%&&''&&&'''''&&&&&&%%&&%%$$$ $ #!#!%%%% & & ((!++%0/$33'68#9<'@C%GJ,EO+IR/EQ2CP1>K2I*BK0BK0f1;b)A +"#+=FU^6[d/LUMM$XY0c\;ngFwrE„~RŽŽS••Zž W¡¢Y¢©D¨¯J­»@±ÀE¶ÆM»ËR‹X/!()+(*$+#*#*")%#$$&#                          #%).0 9";")D*2L44O77SB@`GEeNMfNMfRNfSOgUSgVTi^\qheztqsp€qm~rokj|edv`^pYWiUUjPPdDHPLPY`mTYfMK]1HY-IW,IW,GR0DP.@K0N3@Q5CS7CS7@S5@S5FT0BQ,@R+FX0K]1K]1GX,FW+IR/FP,AB+() ',7@EQ_Qjwj’oˆ™v†›j…ši…œY‚™U€–Grˆ9iu1Q\'-#(!&&+.38=(GP3^hKkxOuƒZYˆ–`ŒŸY¢]”§b’¤_’¤\‘¢[ŽŸX‡˜Q€Ky‰Do}Bjw<[e1Va-QZ+PY*P]+]i8_q/]o,Xv_}$_h™|µ ‘Ê!­Í$±Ñ'ÂÊEÇÐKÂÈXÆÌ[ÅÇpÄÆoÅ©‰£‡gnJWb=JR?TXEZMDXE

9L51D,(F3/M;>WGJccc|wx‘x€‘y’us}ky”gt]t]tYv‘Zw’Yu“Wt’XpŽTl‹Ne„H`~D[|CZ{?Y~?Y~?Y~?Y~?Y~?Y~>Z>Z[†>_ŠAc@jBl“HpŒIri‰{¢›Ž¨„§ƒ˜£{“žv–u•›t‘œt‘œt‘–Œ’z„‰{„‰{ƒ‡‚‚††~€…}ƒƒ‡‰‰„ˆ˜|€lzšlzš`}¡^zŸ^{ZxšPr•Oq”Om“Lk‘IfHeŽIfIfKfKfIdIdIdIdEcŒGdDeEf‘Go“Qyž`…¤gŒ«s•¤s•¤‚—–ˆœ“Ÿ™Šš„Ž˜‚Œ“€Œ“€‰~‰~‰ŒŠŽ‘Žˆœƒ‹—{Š |‹¡{—xŒ”|’€|’€„•w…–x‰™r‰™rˆ—r‡•qˆ“qˆ“q‰|Š‘}†‰„‡‹v†“t„’yŽ”–œ%4%4%4%4&1'2)3)3'1'1'1'1'1'1'1'1'2'2'2'2)1)1)1)1'1'1'1'1'2'2)3)3)2)2)2)2*2!*2!+3"+3"*1"*1"+2#+2#+2#+2#*1"*1"*1"*1"*1"*1"*2!*2!)1+3",6'$.'/#'/#%-#%-#%-#$,"$,""* !)!)**''&&&&&&%&&%&&%&((%%%%&&''&&&'''''&&&&&&%%&&%%$$$ $ #!#!%%%% & & **#--&10%44(79$;>)BE'HK-IR/KU1JW8JW8FS:BO7=I9?KN3@Q5BR6BR6>R3>R3@N*Z>Z[†>_ŠAc@jBl“IrKtq—‘|£‘ª†‹¥€” w’u–u•›t’u’u’˜€“{„‰{„‰{ƒ‡‚‚††~€…}ƒƒ‡‰‰ƒ‡–{Žm{›m{›`}¡^zŸYv˜Wt–Pr•Oq”Om“Lk‘IfHeŽIfHeŽJeŽJeŽIdGcŒIdIdEcŒGdDeEf‘Hp•RzŸa†¥i­s•¤q“¢‚—–‡œ›’žŽœŒš„Ž˜‚Œ“€Œ“€‹‘€ŠŒŒ’‡Ž›…Œ˜y‰Ÿy‰Ÿw‹“vŠ’}“‚}“‚„•w…–x‰™r‰™rˆ—r‡•qˆ“q‰”s‰|Š‘}†‰„‡‹v†“v†“~“™–œ)2)2)2)2'2'2'2'2'2'2'2'2'1'1'1'1)1)1)1)1)1)1'0'0)2'1)2)2'0'0)1)1'/)1*2!*2!*2!*2!*2!+3"+2#+2#*1"*1"*1"*1"*1"*1")0!)0!*1"*1")0!)0!*1"&.%.!'/#'/$'/$%.!$- $- $- $,"#+!#+!#+! )# )#("%'&&&%&''&&&&''((''((&&&&''''''((((&&%%%%%%$$&&$ $ #!#!!#!#"!"!!&%!&%(+",/%03%46(<<(AA-FG*LM/NS0RW4U\9W_;N]=IY9CQ:DRMagvWarV`pRaxQ`vM[}.;^(8g-CTVZi^bqberberccqddrklwvv‚xv‚ts~sszqqxrqzqpypmwnktnlsomtlktkjsklwnnznpvpryorvlpthqdYaUNZ=IU8IU1IU1HS1EQ/@K0>I.9D27A02@02@03E29K8?S@QeQQdS>Q@O^Ltƒq~Štx„nƒrƒr„ˆu‰z†‚xrod4-.  " "(DFLs}nŠ“„‰•q…’m–n˜pŠŽnlpPAB+BC,@H7AI8>L7>L7;L5O8>O8=N78H.8H.=O.>P/?S-?S-BQ,AP+FK/DI,99+&' (-:DIUaXo{s…”tˆ˜x†˜i‚”f‚”V~‘SxŒDo‚;bj2JQ(+"(* 24(JS8W`EfnKovSvM}ˆT‚ŽV†’Y‹˜_ŽšaŽ›`Ž›`›[‹šZˆ—U‚’OyˆFr?m~9n:o€;l|8fu.^l&bt$hz*k‡oŒ#sŸ}©Œ¼—ƨÄ,©Å-µ¯U¤žD\:+E#;0H(=S6beItcNxs^ˆleˆpiŒX\wEJdEObR\nXelYgm^nm^nmVgiJ\]>MQCQVUbpjw…jwŽfsŠ`s^r]v]vWt’Vs‘WoQi‡KcE]|CZ{AXx=W|?Y~?Y~?Y~?Y~?Y~=Y€>Z]ƒ?bˆCf‹FhHk“Jn‹Tx””„£™”§„Ž¡~’žz›w“œt”u’u” w•›ƒŒ’z„‡~…‰„„€…}€…}ƒƒ‰Œ‹€…˜y~‘h}žh}ž_~¢]| Yy›Ww˜Qs–No“Nl’KjJhIfHeŽHeŽJeŽJeŽGcŒFa‹Fa‹Fa‹EcŒEcŒBfFj”It˜T~£cŠ¥hŽªx“¡w’ „˜“ˆœ–‘žŒŽ›‰Ž–…•„Œ’Œ’‹‚Œ‘ƒŽ“’ƒ‹—ƒ‹—|‹”|‹”}†}†„”x„”x†–t‡—u‹–v‹–vŠ“xŠ“xŠ‘|Š‘|ˆ‹ˆ‰ŒŠx„”vƒ“n„•q†—~–™~–™)2)2(0(0'2'2'2'2'2'2'2'2)2)2'1'1)1)1)1)1'0'0'0'0)2'1)2)2'0'0)1)1'/)1*2!*2!*2!*2!*2!+3"+2#+2#*1"*1"*1"*1"*1"*1")0!)0!*1"*1")0!)0!)0!)0!'/#$- '/$'/$%.!$- $- $- $,"$,"#+!#+! )# )# )# )#( '''%&'(( ( ''(())''((&&&&''''''((((&&%%%%%%$$&&%!%!#!#!!#!#"!"! $$!&%$''*!03%46(=>)BB.IJ,MN1QV3W\9Y`=[b?SbBRaAJXAGV?GTGHUHNZOP]RT]WYb\\e__hbbkebkeemgfoifpegqggregreiscischraisbmvcmvcrzbs{cw€_x`z…Xz…Xz‰IyˆHt…@m~9at5[n0N_3HY-GS/AM);F-8B*7>+4:'/6".5!-2.389"AB+TX3loJkpDhnBkrAowF}†I„PŽ@…‘D‚ŒF€ŠD‚„Oƒ…P‡‰T„†Rƒ‚N~~I{wLtpEmhImhImgVidRmdl„{ƒme„e]|b[…`Xƒb[…`Xƒa\a\d`zjf€xy„ijughffgd^a_^a_^_j[\gRVe@DS-+,0IP^agvXbsV`pTczYh~HUw'4W)9h.>m0=m/O8@Q9>O8=N7P/?Q1@T/@T/BQ,AP+FK/DI,99+&' $.3@JO\h_sv†•u†•u‚”f~a‚”V~‘SxŒDk7]e-?G%( &) '("$$&?A5KT:V_D^fBipMozFvM{‡OUƒW„‘X†“XŠ—\‰˜Xˆ—W‡–Tƒ“Q€M{‹HzŠFy‰Du†Asƒ?m|5ky3l~-n€0oŒ#v“*}©‰µ&–Å˜Ç ¥Â)£À'±«Qxr3I(R2G\;QdHskOzdOy_Jtib…og‹`d}œ˜¡´¦¹£±¸ ®´–¦¥Ššš{Žrƒ…v…‰uƒˆ^lyUbpZf}dqˆ]qŽ^r]v[uŽUrVs‘Tl‹Ph†Jb€D\zCZ{AXx=W|?Y~?Y~?Y~?Y~?Y~>Z?[‚>]ƒA_…AeŠDgŒDfŽGj’MqW{—„£™‡¥œ’¥‚ }‘yœx“œt’›r’u” w•›ƒŒ’z…‰ƒ†}€ƒ€„†~†~ƒƒ‰Œ‹€…˜y~‘h}žh}ž^}¡\{ŸXxšVv—Qs–No“Nl’KjJhIfHeŽHeŽJeŽJeŽFa‹Fa‹Fa‹Fa‹EcŒEcŒBfFj”Ju™U¤g©k’®y•¢w’ „˜“†š”Ž›‰Š—…Ž–…•„Œ’Œ’Š€‹‚Ž“’ƒ‹—ƒ‹—|‹”|‹”}†}†„”x†–{†–t†–t‹–vŠ•u‰’w‰’wŠ‘|Š‘|‰ŒŠ‡Š‡u‚’t‘mƒ”tŠ›~–™~–™)4)4'3'3'2'2&1&1&2&2'3'3'3'3'2'2'1'1'1)2)1)1'0'0'0'0'0'0'0'0)1)1)1)1+3"+3")1)1)1*2!*2!*2!*2!*2!*2!*2!*1"*1"*/%*/%*/%*/%*/%*/%).$).$'/#'/#'/$%-##+!#+!%-#%-#$+%$+%#*"")! )# )#("(")(('&'''''(((())))))))))((((''((((''''(&&&&&$ $ $"$"!#!#!#!#"!"!%$&%$'$'+(.-)22-?:/D?4KF2OK7UU5]]=bb@deB`jF_hEPaCM^@L\IN]JP\SUaXU`YYd^`gfahgdjgglijoekqglsflsflv`lv`nv^nv^pz]pz]t{^t{^u[v€\}„\}„\zˆRy‡Pz‰Iv…Ep€Bhx:^p;Xj5R_9KX2GO5CL1?I1:E,7?,4=)08&-5$,2'.&.(07< V[?psPhkHlpIkoGx}M|Pow=px>sv>rt=xwEzxFxuJwsItqDurEwrLtnIrkOqjNmdXi_Sl^lsueŒpaˆh^ˆcY„_[‚\W^V|aY~b[~b[~zwŽnj‚dfhbce]c`Z_\_ckX\dNTc>DS-+)'/?Yar]evTavUcwVdXe5Bm".Z*6j/:o,p+f'7>$8?%3?6A/=-;DKU]%hf/qo9{x7~<‹:˜œG ¦<¤ªA§°=«µA°¾<µÃA·È?¸É@°ÄTƒ—& +  !"                                 ')6QS`gfvcbr^\j\Yg]Xf^Yg^Zmeatpm{yv„stqr}qr}strt}pqzqpypoxklwijuiiwggtihqihqghmghmkhonlsmmtrryyz|€ƒu}nT[LCN5GQ9FS1CQ/@N3M6;I23F08J5:N4:N4>S2

]ƒA_…@c‹CeDiFk“Lp‡`ƒ›…¨—‚¦••¢‚‘ž“žw‘›u›r‘œt”žz™£•™‡‰zƒ…€ƒ…€‚…ƒ„‚~ƒ…€ƒ…‰‹|™w|•g~Ÿe|\z ZyŸXw›Ut˜Pq—No•Nk”Ki’IfHeŽHeŽHeŽIdGcŒFa‹Fa‹DaŒDaŒEdŽBaŒ@fFl•OwœZ‚§h‹¨m‘®|•œ{”›Šš’ˆ˜‘™‹‹–‰Ž•†”…Œ’Œ’Š‘„Œ’†Ž’”Ž’”ƒ“ƒ“~‹}Š€”z€”z‡—u†–tˆ—rˆ—rŠ“x‰’w‹‘y‹‘yŽ‘ƒ‚…Š…‹j}•l—o‡—wŸ˜š~–™)4)4'3'3'2'2&1&1&2&2'3'3'3'3'2'2'1'1'1)2)1)1'0'0'0'0'0'0'0'0)1)1)1)1*2!*2!)1)1)1*2!*2!*2!*2!*2!*2!*2!*1"*1"*/%*/%*/%*/%*/%*/%).$).$'/#'/#%-#'/$%-#%-#%-#%-#$+%$+%#*"")! )# )#("(")(('&'((''((((*)))))))))((((''((((('''''&&&&$ $ $"$"!#!#!#!#$"$"&%&%$'$'+(.-)22-=8-A;1KF2OK7TS4[Z;bb@ggEblHcmIXiKTeGP_MQ`NS_WVbYWb\\g``gfahggliiokmsimsinuhlsflv`lv`nv^nv^pz]pz]t{^u|`w]yƒ_…]…]zˆRx†OyˆHtƒCm~@hx:^p;Xj5Q^7M[4JS8GO5AL3+2:).6%-3 )0&.&.*/DI,psPkmKfjChlEqvFuzI]f+NWZ]&^a*_],_],XT)SO%TQ$b_2vqKtnIlfIjcGd[NbXLi\isqb‰j[‚cY„]S~]Y€`\ƒd\‚aY~]Vy^Wzuq‰uq‰gikdfh`eb\b_`dl_ckMSb9?N-'/>FWaiz[ctTavXf{ZgƒTb}%2]%2]+7l0

+ + diff --git a/media/test/data/sfx.m4a b/media/test/data/sfx.m4a new file mode 100644 index 0000000000..6dc4621a8f Binary files /dev/null and b/media/test/data/sfx.m4a differ diff --git a/media/test/data/sfx.mp3 b/media/test/data/sfx.mp3 new file mode 100644 index 0000000000..d9eb2f3f1d Binary files /dev/null and b/media/test/data/sfx.mp3 differ diff --git a/media/test/data/sfx.ogg b/media/test/data/sfx.ogg new file mode 100644 index 0000000000..c569c8f407 Binary files /dev/null and b/media/test/data/sfx.ogg differ diff --git a/media/test/data/sfx_f32le.wav b/media/test/data/sfx_f32le.wav new file mode 100644 index 0000000000..d5ea6a18d9 Binary files /dev/null and b/media/test/data/sfx_f32le.wav differ diff --git a/media/test/data/sfx_s16le.wav b/media/test/data/sfx_s16le.wav new file mode 100644 index 0000000000..00bc95fbd2 Binary files /dev/null and b/media/test/data/sfx_s16le.wav differ diff --git a/media/test/data/sfx_s24le.wav b/media/test/data/sfx_s24le.wav new file mode 100644 index 0000000000..d2d5d46fd3 Binary files /dev/null and b/media/test/data/sfx_s24le.wav differ diff --git a/media/test/data/sfx_u8.wav b/media/test/data/sfx_u8.wav new file mode 100644 index 0000000000..f8b397c9aa Binary files /dev/null and b/media/test/data/sfx_u8.wav differ diff --git a/media/test/data/speech_16b_mono_44kHz.raw b/media/test/data/speech_16b_mono_44kHz.raw new file mode 100644 index 0000000000..29f77047d4 Binary files /dev/null and b/media/test/data/speech_16b_mono_44kHz.raw differ diff --git a/media/test/data/speech_16b_mono_48kHz.raw b/media/test/data/speech_16b_mono_48kHz.raw new file mode 100644 index 0000000000..3241743cef Binary files /dev/null and b/media/test/data/speech_16b_mono_48kHz.raw differ diff --git a/media/test/data/speech_16b_stereo_44kHz.raw b/media/test/data/speech_16b_stereo_44kHz.raw new file mode 100644 index 0000000000..cdbb644d80 Binary files /dev/null and b/media/test/data/speech_16b_stereo_44kHz.raw differ diff --git a/media/test/data/speech_16b_stereo_48kHz.raw b/media/test/data/speech_16b_stereo_48kHz.raw new file mode 100644 index 0000000000..e29432e939 Binary files /dev/null and b/media/test/data/speech_16b_stereo_48kHz.raw differ diff --git a/media/test/data/speex_audio_vorbis_video.ogv b/media/test/data/speex_audio_vorbis_video.ogv new file mode 100644 index 0000000000..313edddfe4 Binary files /dev/null and b/media/test/data/speex_audio_vorbis_video.ogv differ diff --git a/media/test/data/sweep02_16b_mono_16KHz.raw b/media/test/data/sweep02_16b_mono_16KHz.raw new file mode 100644 index 0000000000..5dd4dc47ca Binary files /dev/null and b/media/test/data/sweep02_16b_mono_16KHz.raw differ diff --git a/media/test/data/ten_byte_file b/media/test/data/ten_byte_file new file mode 100644 index 0000000000..ad471007bd --- /dev/null +++ b/media/test/data/ten_byte_file @@ -0,0 +1 @@ +0123456789 \ No newline at end of file diff --git a/media/test/data/vorbis-extradata b/media/test/data/vorbis-extradata new file mode 100644 index 0000000000..1f9530988a Binary files /dev/null and b/media/test/data/vorbis-extradata differ diff --git a/media/test/data/vorbis-packet-0 b/media/test/data/vorbis-packet-0 new file mode 100644 index 0000000000..0e3739c242 Binary files /dev/null and b/media/test/data/vorbis-packet-0 differ diff --git a/media/test/data/vorbis-packet-1 b/media/test/data/vorbis-packet-1 new file mode 100644 index 0000000000..035ccdb77a --- /dev/null +++ b/media/test/data/vorbis-packet-1 @@ -0,0 +1 @@ +ìÊRdïÊRdKL£Š8/YÏù8v'·)ê}7¢‘Õ¢‘‰“Ÿ7o \ No newline at end of file diff --git a/media/test/data/vorbis-packet-2 b/media/test/data/vorbis-packet-2 new file mode 100644 index 0000000000..a01cb5d8f1 --- /dev/null +++ b/media/test/data/vorbis-packet-2 @@ -0,0 +1 @@ +:Éu³ü!~ €€I®›åñø eFCD°ÓæðÙÿO¤îŽª£à&ØÅŠ)bµªÕ4íØbq²[MLl›&Í"ÛF¤½ŠÛËÝLáêfã|›ÙëÑë÷ݨH1l+3ÓhöL¶úTÞ­é«^¶„ÜŸü]Ë{EWÑ Ò$¹¤ŸNn ¿Þfs„ÍÙ‡òÙ«RzG'Š•ÔÑNu”t¿×¿ª¸˜eOŠPaÄ \ No newline at end of file diff --git a/media/test/data/vorbis-packet-3 b/media/test/data/vorbis-packet-3 new file mode 100644 index 0000000000..b222e7d35c Binary files /dev/null and b/media/test/data/vorbis-packet-3 differ diff --git a/media/test/data/vorbis_audio_wmv_video.mkv b/media/test/data/vorbis_audio_wmv_video.mkv new file mode 100644 index 0000000000..612319f9cd Binary files /dev/null and b/media/test/data/vorbis_audio_wmv_video.mkv differ diff --git a/media/test/data/vp8-I-frame-160x240 b/media/test/data/vp8-I-frame-160x240 new file mode 100644 index 0000000000..65e4337234 Binary files /dev/null and b/media/test/data/vp8-I-frame-160x240 differ diff --git a/media/test/data/vp8-I-frame-320x120 b/media/test/data/vp8-I-frame-320x120 new file mode 100644 index 0000000000..ba4e1e22b5 Binary files /dev/null and b/media/test/data/vp8-I-frame-320x120 differ diff --git a/media/test/data/vp8-I-frame-320x240 b/media/test/data/vp8-I-frame-320x240 new file mode 100644 index 0000000000..5b7bfb901d Binary files /dev/null and b/media/test/data/vp8-I-frame-320x240 differ diff --git a/media/test/data/vp8-I-frame-320x480 b/media/test/data/vp8-I-frame-320x480 new file mode 100644 index 0000000000..1ed59f082a Binary files /dev/null and b/media/test/data/vp8-I-frame-320x480 differ diff --git a/media/test/data/vp8-I-frame-640x240 b/media/test/data/vp8-I-frame-640x240 new file mode 100644 index 0000000000..87299cc0db Binary files /dev/null and b/media/test/data/vp8-I-frame-640x240 differ diff --git a/media/test/data/vp8-corrupt-I-frame b/media/test/data/vp8-corrupt-I-frame new file mode 100644 index 0000000000..23afa845d3 Binary files /dev/null and b/media/test/data/vp8-corrupt-I-frame differ diff --git a/media/test/data/webm_content_encodings b/media/test/data/webm_content_encodings new file mode 100644 index 0000000000..c49fed200b Binary files /dev/null and b/media/test/data/webm_content_encodings differ diff --git a/media/test/data/webm_ebml_element b/media/test/data/webm_ebml_element new file mode 100644 index 0000000000..9f0eb1b1d5 Binary files /dev/null and b/media/test/data/webm_ebml_element differ diff --git a/media/test/data/webm_info_element b/media/test/data/webm_info_element new file mode 100644 index 0000000000..1097d13534 Binary files /dev/null and b/media/test/data/webm_info_element differ diff --git a/media/test/data/webm_vorbis_track_entry b/media/test/data/webm_vorbis_track_entry new file mode 100644 index 0000000000..a873521aaa Binary files /dev/null and b/media/test/data/webm_vorbis_track_entry differ diff --git a/media/test/data/webm_vp8_track_entry b/media/test/data/webm_vp8_track_entry new file mode 100644 index 0000000000..f544e9ddae Binary files /dev/null and b/media/test/data/webm_vp8_track_entry differ diff --git a/media/webm/cluster_builder.cc b/media/webm/cluster_builder.cc new file mode 100644 index 0000000000..e320cbb665 --- /dev/null +++ b/media/webm/cluster_builder.cc @@ -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. + +#include "media/webm/cluster_builder.h" + +#include "base/logging.h" +#include "media/base/data_buffer.h" + +namespace media { + +static const uint8 kClusterHeader[] = { + 0x1F, 0x43, 0xB6, 0x75, // CLUSTER ID + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // cluster(size = 0) + 0xE7, // Timecode ID + 0x88, // timecode(size=8) + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // timecode value +}; + +static const uint8 kSimpleBlockHeader[] = { + 0xA3, // SimpleBlock ID + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SimpleBlock(size = 0) +}; + +static const uint8 kBlockGroupHeader[] = { + 0xA0, // BlockGroup ID + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // BlockGroup(size = 0) + 0x9B, // BlockDuration ID + 0x88, // BlockDuration(size = 8) + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // duration + 0xA1, // Block ID + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Block(size = 0) +}; + +enum { + kClusterSizeOffset = 4, + kClusterTimecodeOffset = 14, + + kSimpleBlockSizeOffset = 1, + + kBlockGroupSizeOffset = 1, + kBlockGroupDurationOffset = 11, + kBlockGroupBlockSizeOffset = 20, + + kInitialBufferSize = 32768, +}; + +Cluster::Cluster(scoped_ptr data, int size) + : data_(data.Pass()), size_(size) {} +Cluster::~Cluster() {} + +ClusterBuilder::ClusterBuilder() { Reset(); } +ClusterBuilder::~ClusterBuilder() {} + +void ClusterBuilder::SetClusterTimecode(int64 cluster_timecode) { + DCHECK_EQ(cluster_timecode_, -1); + + cluster_timecode_ = cluster_timecode; + + // Write the timecode into the header. + uint8* buf = buffer_.get() + kClusterTimecodeOffset; + for (int i = 7; i >= 0; --i) { + buf[i] = cluster_timecode & 0xff; + cluster_timecode >>= 8; + } +} + +void ClusterBuilder::AddSimpleBlock(int track_num, int64 timecode, int flags, + const uint8* data, int size) { + int block_size = size + 4; + int bytes_needed = sizeof(kSimpleBlockHeader) + block_size; + if (bytes_needed > (buffer_size_ - bytes_used_)) + ExtendBuffer(bytes_needed); + + uint8* buf = buffer_.get() + bytes_used_; + int block_offset = bytes_used_; + memcpy(buf, kSimpleBlockHeader, sizeof(kSimpleBlockHeader)); + UpdateUInt64(block_offset + kSimpleBlockSizeOffset, block_size); + buf += sizeof(kSimpleBlockHeader); + + WriteBlock(buf, track_num, timecode, flags, data, size); + + bytes_used_ += bytes_needed; +} + +void ClusterBuilder::AddBlockGroup(int track_num, int64 timecode, int duration, + int flags, const uint8* data, int size) { + int block_size = size + 4; + int bytes_needed = sizeof(kBlockGroupHeader) + block_size; + int block_group_size = bytes_needed - 9; + + if (bytes_needed > (buffer_size_ - bytes_used_)) + ExtendBuffer(bytes_needed); + + uint8* buf = buffer_.get() + bytes_used_; + int block_group_offset = bytes_used_; + memcpy(buf, kBlockGroupHeader, sizeof(kBlockGroupHeader)); + UpdateUInt64(block_group_offset + kBlockGroupSizeOffset, block_group_size); + UpdateUInt64(block_group_offset + kBlockGroupDurationOffset, duration); + UpdateUInt64(block_group_offset + kBlockGroupBlockSizeOffset, block_size); + buf += sizeof(kBlockGroupHeader); + + // Make sure the 4 most-significant bits are 0. + // http://www.matroska.org/technical/specs/index.html#block_structure + flags &= 0x0f; + + WriteBlock(buf, track_num, timecode, flags, data, size); + + bytes_used_ += bytes_needed; +} + +void ClusterBuilder::WriteBlock(uint8* buf, int track_num, int64 timecode, + int flags, const uint8* data, int size) { + DCHECK_GE(track_num, 0); + DCHECK_LE(track_num, 126); + DCHECK_GE(flags, 0); + DCHECK_LE(flags, 0xff); + DCHECK(data); + DCHECK_GT(size, 0); + DCHECK_NE(cluster_timecode_, -1); + + int64 timecode_delta = timecode - cluster_timecode_; + DCHECK_GE(timecode_delta, -32768); + DCHECK_LE(timecode_delta, 32767); + + buf[0] = 0x80 | (track_num & 0x7F); + buf[1] = (timecode_delta >> 8) & 0xff; + buf[2] = timecode_delta & 0xff; + buf[3] = flags & 0xff; + memcpy(buf + 4, data, size); +} + +scoped_ptr ClusterBuilder::Finish() { + DCHECK_NE(cluster_timecode_, -1); + + UpdateUInt64(kClusterSizeOffset, bytes_used_ - (kClusterSizeOffset + 8)); + + scoped_ptr ret(new Cluster(buffer_.Pass(), bytes_used_)); + Reset(); + return ret.Pass(); +} + +void ClusterBuilder::Reset() { + buffer_size_ = kInitialBufferSize; + buffer_.reset(new uint8[buffer_size_]); + memcpy(buffer_.get(), kClusterHeader, sizeof(kClusterHeader)); + bytes_used_ = sizeof(kClusterHeader); + cluster_timecode_ = -1; +} + +void ClusterBuilder::ExtendBuffer(int bytes_needed) { + int new_buffer_size = 2 * buffer_size_; + + while ((new_buffer_size - bytes_used_) < bytes_needed) + new_buffer_size *= 2; + + scoped_ptr new_buffer(new uint8[new_buffer_size]); + + memcpy(new_buffer.get(), buffer_.get(), bytes_used_); + buffer_.reset(new_buffer.release()); + buffer_size_ = new_buffer_size; +} + +void ClusterBuilder::UpdateUInt64(int offset, int64 value) { + DCHECK_LE(offset + 7, buffer_size_); + uint8* buf = buffer_.get() + offset; + + // Fill the last 7 bytes of size field in big-endian order. + for (int i = 7; i > 0; i--) { + buf[i] = value & 0xff; + value >>= 8; + } +} + +} // namespace media diff --git a/media/webm/cluster_builder.h b/media/webm/cluster_builder.h new file mode 100644 index 0000000000..3482cfbb90 --- /dev/null +++ b/media/webm/cluster_builder.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 MEDIA_WEBM_CLUSTER_BUILDER_H_ +#define MEDIA_WEBM_CLUSTER_BUILDER_H_ + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "media/base/buffers.h" + +namespace media { + +class Cluster { + public: + Cluster(scoped_ptr data, int size); + ~Cluster(); + + const uint8* data() const { return data_.get(); } + int size() const { return size_; } + + private: + scoped_ptr data_; + int size_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(Cluster); +}; + +class ClusterBuilder { + public: + ClusterBuilder(); + ~ClusterBuilder(); + + void SetClusterTimecode(int64 cluster_timecode); + void AddSimpleBlock(int track_num, int64 timecode, int flags, + const uint8* data, int size); + void AddBlockGroup(int track_num, int64 timecode, int duration, int flags, + const uint8* data, int size); + + scoped_ptr Finish(); + + private: + void Reset(); + void ExtendBuffer(int bytes_needed); + void UpdateUInt64(int offset, int64 value); + void WriteBlock(uint8* buf, int track_num, int64 timecode, int flags, + const uint8* data, int size); + + scoped_ptr buffer_; + int buffer_size_; + int bytes_used_; + int64 cluster_timecode_; + + DISALLOW_COPY_AND_ASSIGN(ClusterBuilder); +}; + +} // namespace media + +#endif // MEDIA_WEBM_CLUSTER_BUILDER_H_ diff --git a/media/webm/tracks_builder.cc b/media/webm/tracks_builder.cc new file mode 100644 index 0000000000..370ca82a18 --- /dev/null +++ b/media/webm/tracks_builder.cc @@ -0,0 +1,211 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/webm/tracks_builder.h" + +#include "media/webm/webm_constants.h" + +namespace media { + +// Returns size of an integer, formatted using Matroska serialization. +static int GetUIntMkvSize(uint64 value) { + if (value < 0x07FULL) + return 1; + if (value < 0x03FFFULL) + return 2; + if (value < 0x01FFFFFULL) + return 3; + if (value < 0x0FFFFFFFULL) + return 4; + if (value < 0x07FFFFFFFFULL) + return 5; + if (value < 0x03FFFFFFFFFFULL) + return 6; + if (value < 0x01FFFFFFFFFFFFULL) + return 7; + return 8; +} + +// Returns the minimium size required to serialize an integer value. +static int GetUIntSize(uint64 value) { + if (value < 0x0100ULL) + return 1; + if (value < 0x010000ULL) + return 2; + if (value < 0x01000000ULL) + return 3; + if (value < 0x0100000000ULL) + return 4; + if (value < 0x010000000000ULL) + return 5; + if (value < 0x01000000000000ULL) + return 6; + if (value < 0x0100000000000000ULL) + return 7; + return 8; +} + +static int MasterElementSize(int element_id, int payload_size) { + return GetUIntSize(element_id) + GetUIntMkvSize(payload_size) + payload_size; +} + +static int IntElementSize(int element_id, int value) { + return GetUIntSize(element_id) + 1 + GetUIntSize(value); +} + +static int StringElementSize(int element_id, const std::string& value) { + return GetUIntSize(element_id) + + GetUIntMkvSize(value.length()) + + value.length(); +} + +static void SerializeInt(uint8** buf_ptr, int* buf_size_ptr, + int64 value, int size) { + uint8*& buf = *buf_ptr; + int& buf_size = *buf_size_ptr; + + for (int idx = 1; idx <= size; ++idx) { + *buf++ = static_cast(value >> ((size - idx) * 8)); + --buf_size; + } +} + +static void WriteElementId(uint8** buf, int* buf_size, int element_id) { + SerializeInt(buf, buf_size, element_id, GetUIntSize(element_id)); +} + +static void WriteUInt(uint8** buf, int* buf_size, uint64 value) { + const int size = GetUIntMkvSize(value); + value |= (1ULL << (size * 7)); // Matroska formatting + SerializeInt(buf, buf_size, value, size); +} + +static void WriteMasterElement(uint8** buf, int* buf_size, + int element_id, int payload_size) { + WriteElementId(buf, buf_size, element_id); + WriteUInt(buf, buf_size, payload_size); +} + +static void WriteIntElement(uint8** buf, int* buf_size, + int element_id, int value) { + WriteElementId(buf, buf_size, element_id); + + const int size = GetUIntSize(value); + WriteUInt(buf, buf_size, size); + + SerializeInt(buf, buf_size, value, size); +} + +static void WriteStringElement(uint8** buf_ptr, int* buf_size_ptr, + int element_id, const std::string& value) { + uint8*& buf = *buf_ptr; + int& buf_size = *buf_size_ptr; + + WriteElementId(&buf, &buf_size, element_id); + + const uint64 size = value.length(); + WriteUInt(&buf, &buf_size, size); + + memcpy(buf, value.data(), size); + buf += size; + buf_size -= size; +} + +TracksBuilder::TracksBuilder() {} +TracksBuilder::~TracksBuilder() {} + +void TracksBuilder::AddTrack( + int track_num, + int track_type, + const std::string& codec_id, + const std::string& name, + const std::string& language) { + tracks_.push_back(Track(track_num, track_type, codec_id, name, language)); +} + +std::vector TracksBuilder::Finish() { + // Allocate the storage + std::vector buffer; + buffer.resize(GetTracksSize()); + + // Populate the storage with a tracks header + WriteTracks(&buffer[0], buffer.size()); + + return buffer; +} + +int TracksBuilder::GetTracksSize() const { + return MasterElementSize(kWebMIdTracks, GetTracksPayloadSize()); +} + +int TracksBuilder::GetTracksPayloadSize() const { + int payload_size = 0; + + for (TrackList::const_iterator itr = tracks_.begin(); + itr != tracks_.end(); ++itr) { + payload_size += itr->GetSize(); + } + + return payload_size; +} + +void TracksBuilder::WriteTracks(uint8* buf, int buf_size) const { + WriteMasterElement(&buf, &buf_size, kWebMIdTracks, GetTracksPayloadSize()); + + for (TrackList::const_iterator itr = tracks_.begin(); + itr != tracks_.end(); ++itr) { + itr->Write(&buf, &buf_size); + } +} + +TracksBuilder::Track::Track(int track_num, int track_type, + const std::string& codec_id, + const std::string& name, + const std::string& language) + : track_num_(track_num), + track_type_(track_type), + codec_id_(codec_id), + name_(name), + language_(language) { +} + +int TracksBuilder::Track::GetSize() const { + return MasterElementSize(kWebMIdTrackEntry, GetPayloadSize()); +} + +int TracksBuilder::Track::GetPayloadSize() const { + int size = 0; + + size += IntElementSize(kWebMIdTrackNumber, track_num_); + size += IntElementSize(kWebMIdTrackType, track_type_); + + if (!codec_id_.empty()) + size += StringElementSize(kWebMIdCodecID, codec_id_); + + if (!name_.empty()) + size += StringElementSize(kWebMIdName, name_); + + if (!language_.empty()) + size += StringElementSize(kWebMIdLanguage, language_); + + return size; +} + +void TracksBuilder::Track::Write(uint8** buf, int* buf_size) const { + WriteMasterElement(buf, buf_size, kWebMIdTrackEntry, GetPayloadSize()); + + WriteIntElement(buf, buf_size, kWebMIdTrackNumber, track_num_); + WriteIntElement(buf, buf_size, kWebMIdTrackType, track_type_); + + if (!codec_id_.empty()) + WriteStringElement(buf, buf_size, kWebMIdCodecID, codec_id_); + + if (!name_.empty()) + WriteStringElement(buf, buf_size, kWebMIdName, name_); + + if (!language_.empty()) + WriteStringElement(buf, buf_size, kWebMIdLanguage, language_); +} + +} // namespace media diff --git a/media/webm/tracks_builder.h b/media/webm/tracks_builder.h new file mode 100644 index 0000000000..87ceaed3ad --- /dev/null +++ b/media/webm/tracks_builder.h @@ -0,0 +1,56 @@ +// 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 MEDIA_WEBM_TRACKS_BUILDER_H_ +#define MEDIA_WEBM_TRACKS_BUILDER_H_ + +#include +#include +#include + +#include "base/basictypes.h" + +namespace media { + +class TracksBuilder { + public: + TracksBuilder(); + ~TracksBuilder(); + + void AddTrack(int track_num, int track_type, const std::string& codec_id, + const std::string& name, const std::string& language); + + std::vector Finish(); + + private: + int GetTracksSize() const; + int GetTracksPayloadSize() const; + void WriteTracks(uint8* buffer, int buffer_size) const; + + class Track { + public: + Track(int track_num, int track_type, const std::string& codec_id, + const std::string& name, const std::string& language); + + int GetSize() const; + void Write(uint8** buf, int* buf_size) const; + private: + int GetPayloadSize() const; + + int track_num_; + int track_type_; + std::string codec_id_; + std::string name_; + std::string language_; + }; + + typedef std::list TrackList; + TrackList tracks_; + + DISALLOW_COPY_AND_ASSIGN(TracksBuilder); +}; + +} // namespace media + +#endif // MEDIA_WEBM_TRACKS_BUILDER_H_ diff --git a/media/webm/webm_audio_client.cc b/media/webm/webm_audio_client.cc new file mode 100644 index 0000000000..e52f44b4a9 --- /dev/null +++ b/media/webm/webm_audio_client.cc @@ -0,0 +1,112 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/webm/webm_audio_client.h" + +#include "media/base/audio_decoder_config.h" +#include "media/base/channel_layout.h" +#include "media/webm/webm_constants.h" + +namespace media { + +WebMAudioClient::WebMAudioClient(const LogCB& log_cb) + : log_cb_(log_cb) { + Reset(); +} + +WebMAudioClient::~WebMAudioClient() { +} + +void WebMAudioClient::Reset() { + channels_ = -1; + samples_per_second_ = -1; + output_samples_per_second_ = -1; +} + +bool WebMAudioClient::InitializeConfig( + const std::string& codec_id, const std::vector& codec_private, + bool is_encrypted, AudioDecoderConfig* config) { + DCHECK(config); + + AudioCodec audio_codec = kUnknownAudioCodec; + if (codec_id == "A_VORBIS") { + audio_codec = kCodecVorbis; + } else { + MEDIA_LOG(log_cb_) << "Unsupported audio codec_id " << codec_id; + return false; + } + + if (samples_per_second_ <= 0) + return false; + + // Set channel layout default if a Channels element was not present. + if (channels_ == -1) + channels_ = 1; + + ChannelLayout channel_layout = GuessChannelLayout(channels_); + + if (channel_layout == CHANNEL_LAYOUT_UNSUPPORTED) { + MEDIA_LOG(log_cb_) << "Unsupported channel count " << channels_; + return false; + } + + int samples_per_second = samples_per_second_; + if (output_samples_per_second_ > 0) + samples_per_second = output_samples_per_second_; + + const uint8* extra_data = NULL; + size_t extra_data_size = 0; + if (codec_private.size() > 0) { + extra_data = &codec_private[0]; + extra_data_size = codec_private.size(); + } + + config->Initialize( + audio_codec, kSampleFormatPlanarF32, channel_layout, + samples_per_second, extra_data, extra_data_size, is_encrypted, true); + return config->IsValidConfig(); +} + +bool WebMAudioClient::OnUInt(int id, int64 val) { + if (id == kWebMIdChannels) { + if (channels_ != -1) { + MEDIA_LOG(log_cb_) << "Multiple values for id " << std::hex << id + << " specified. (" << channels_ << " and " << val + << ")"; + return false; + } + + channels_ = val; + } + return true; +} + +bool WebMAudioClient::OnFloat(int id, double val) { + double* dst = NULL; + + switch (id) { + case kWebMIdSamplingFrequency: + dst = &samples_per_second_; + break; + case kWebMIdOutputSamplingFrequency: + dst = &output_samples_per_second_; + break; + default: + return true; + } + + if (val <= 0) + return false; + + if (*dst != -1) { + MEDIA_LOG(log_cb_) << "Multiple values for id " << std::hex << id + << " specified (" << *dst << " and " << val << ")"; + return false; + } + + *dst = val; + return true; +} + +} // namespace media diff --git a/media/webm/webm_audio_client.h b/media/webm/webm_audio_client.h new file mode 100644 index 0000000000..1338f5cbd6 --- /dev/null +++ b/media/webm/webm_audio_client.h @@ -0,0 +1,52 @@ +// 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 MEDIA_WEBM_WEBM_AUDIO_CLIENT_H_ +#define MEDIA_WEBM_WEBM_AUDIO_CLIENT_H_ + +#include +#include + +#include "media/base/media_log.h" +#include "media/webm/webm_parser.h" + +namespace media { +class AudioDecoderConfig; + +// Helper class used to parse an Audio element inside a TrackEntry element. +class WebMAudioClient : public WebMParserClient { + public: + explicit WebMAudioClient(const LogCB& log_cb); + virtual ~WebMAudioClient(); + + // Reset this object's state so it can process a new audio track element. + void Reset(); + + // Initialize |config| with the data in |codec_id|, |codec_private|, + // |is_encrypted| and the fields parsed from the last audio track element this + // object was used to parse. + // Returns true if |config| was successfully initialized. + // Returns false if there was unexpected values in the provided parameters or + // audio track element fields. + bool InitializeConfig(const std::string& codec_id, + const std::vector& codec_private, + bool is_encrypted, + AudioDecoderConfig* config); + + private: + // WebMParserClient implementation. + virtual bool OnUInt(int id, int64 val) OVERRIDE; + virtual bool OnFloat(int id, double val) OVERRIDE; + + LogCB log_cb_; + int channels_; + double samples_per_second_; + double output_samples_per_second_; + + DISALLOW_COPY_AND_ASSIGN(WebMAudioClient); +}; + +} // namespace media + +#endif // MEDIA_WEBM_WEBM_AUDIO_CLIENT_H_ diff --git a/media/webm/webm_cluster_parser.cc b/media/webm/webm_cluster_parser.cc new file mode 100644 index 0000000000..9991d6b4d1 --- /dev/null +++ b/media/webm/webm_cluster_parser.cc @@ -0,0 +1,420 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/webm/webm_cluster_parser.h" + +#include + +#include "base/logging.h" +#include "base/sys_byteorder.h" +#include "media/base/buffers.h" +#include "media/base/decrypt_config.h" +#include "media/webm/webm_constants.h" +#include "media/webm/webm_crypto_helpers.h" + +namespace media { + +WebMClusterParser::TextTrackIterator::TextTrackIterator( + const TextTrackMap& text_track_map) : + iterator_(text_track_map.begin()), + iterator_end_(text_track_map.end()) { +} + +WebMClusterParser::TextTrackIterator::TextTrackIterator( + const TextTrackIterator& rhs) : + iterator_(rhs.iterator_), + iterator_end_(rhs.iterator_end_) { +} + +WebMClusterParser::TextTrackIterator::~TextTrackIterator() { +} + +bool WebMClusterParser::TextTrackIterator::operator()( + int* track_num, + const BufferQueue** buffers) { + if (iterator_ == iterator_end_) { + *track_num = 0; + *buffers = NULL; + + return false; + } + + *track_num = iterator_->first; + *buffers = &iterator_->second.buffers(); + + ++iterator_; + return true; +} + +WebMClusterParser::WebMClusterParser( + int64 timecode_scale, int audio_track_num, int video_track_num, + const WebMTracksParser::TextTracks& text_tracks, + const std::set& ignored_tracks, + const std::string& audio_encryption_key_id, + const std::string& video_encryption_key_id, + const LogCB& log_cb) + : timecode_multiplier_(timecode_scale / 1000.0), + ignored_tracks_(ignored_tracks), + audio_encryption_key_id_(audio_encryption_key_id), + video_encryption_key_id_(video_encryption_key_id), + parser_(kWebMIdCluster, this), + last_block_timecode_(-1), + block_data_size_(-1), + block_duration_(-1), + block_add_id_(-1), + block_additional_data_size_(-1), + cluster_timecode_(-1), + cluster_start_time_(kNoTimestamp()), + cluster_ended_(false), + audio_(audio_track_num, false), + video_(video_track_num, true), + log_cb_(log_cb) { + for (WebMTracksParser::TextTracks::const_iterator it = text_tracks.begin(); + it != text_tracks.end(); + ++it) { + text_track_map_.insert(std::make_pair(it->first, Track(it->first, false))); + } +} + +WebMClusterParser::~WebMClusterParser() {} + +void WebMClusterParser::Reset() { + last_block_timecode_ = -1; + cluster_timecode_ = -1; + cluster_start_time_ = kNoTimestamp(); + cluster_ended_ = false; + parser_.Reset(); + audio_.Reset(); + video_.Reset(); + ResetTextTracks(); +} + +int WebMClusterParser::Parse(const uint8* buf, int size) { + audio_.Reset(); + video_.Reset(); + ResetTextTracks(); + + int result = parser_.Parse(buf, size); + + if (result < 0) { + cluster_ended_ = false; + return result; + } + + cluster_ended_ = parser_.IsParsingComplete(); + if (cluster_ended_) { + // If there were no buffers in this cluster, set the cluster start time to + // be the |cluster_timecode_|. + if (cluster_start_time_ == kNoTimestamp()) { + DCHECK_GT(cluster_timecode_, -1); + cluster_start_time_ = base::TimeDelta::FromMicroseconds( + cluster_timecode_ * timecode_multiplier_); + } + + // Reset the parser if we're done parsing so that + // it is ready to accept another cluster on the next + // call. + parser_.Reset(); + + last_block_timecode_ = -1; + cluster_timecode_ = -1; + } + + return result; +} + +WebMClusterParser::TextTrackIterator +WebMClusterParser::CreateTextTrackIterator() const { + return TextTrackIterator(text_track_map_); +} + +WebMParserClient* WebMClusterParser::OnListStart(int id) { + if (id == kWebMIdCluster) { + cluster_timecode_ = -1; + cluster_start_time_ = kNoTimestamp(); + } else if (id == kWebMIdBlockGroup) { + block_data_.reset(); + block_data_size_ = -1; + block_duration_ = -1; + } else if (id == kWebMIdBlockAdditions) { + block_add_id_ = -1; + block_additional_data_.reset(); + block_additional_data_size_ = -1; + } + + return this; +} + +bool WebMClusterParser::OnListEnd(int id) { + if (id != kWebMIdBlockGroup) + return true; + + // Make sure the BlockGroup actually had a Block. + if (block_data_size_ == -1) { + MEDIA_LOG(log_cb_) << "Block missing from BlockGroup."; + return false; + } + + bool result = ParseBlock(false, block_data_.get(), block_data_size_, + block_additional_data_.get(), + block_additional_data_size_, block_duration_); + block_data_.reset(); + block_data_size_ = -1; + block_duration_ = -1; + block_add_id_ = -1; + block_additional_data_.reset(); + block_additional_data_size_ = -1; + return result; +} + +bool WebMClusterParser::OnUInt(int id, int64 val) { + int64* dst; + switch (id) { + case kWebMIdTimecode: + dst = &cluster_timecode_; + break; + case kWebMIdBlockDuration: + dst = &block_duration_; + break; + case kWebMIdBlockAddID: + dst = &block_add_id_; + break; + default: + return true; + } + if (*dst != -1) + return false; + *dst = val; + return true; +} + +bool WebMClusterParser::ParseBlock(bool is_simple_block, const uint8* buf, + int size, const uint8* additional, + int additional_size, int duration) { + if (size < 4) + return false; + + // Return an error if the trackNum > 127. We just aren't + // going to support large track numbers right now. + if (!(buf[0] & 0x80)) { + MEDIA_LOG(log_cb_) << "TrackNumber over 127 not supported"; + return false; + } + + int track_num = buf[0] & 0x7f; + int timecode = buf[1] << 8 | buf[2]; + int flags = buf[3] & 0xff; + int lacing = (flags >> 1) & 0x3; + + if (lacing) { + MEDIA_LOG(log_cb_) << "Lacing " << lacing << " is not supported yet."; + return false; + } + + // Sign extend negative timecode offsets. + if (timecode & 0x8000) + timecode |= ~0xffff; + + const uint8* frame_data = buf + 4; + int frame_size = size - (frame_data - buf); + return OnBlock(is_simple_block, track_num, timecode, duration, flags, + frame_data, frame_size, additional, additional_size); +} + +bool WebMClusterParser::OnBinary(int id, const uint8* data, int size) { + switch (id) { + case kWebMIdSimpleBlock: + return ParseBlock(true, data, size, NULL, -1, -1); + + case kWebMIdBlock: + if (block_data_) { + MEDIA_LOG(log_cb_) << "More than 1 Block in a BlockGroup is not " + "supported."; + return false; + } + block_data_.reset(new uint8[size]); + memcpy(block_data_.get(), data, size); + block_data_size_ = size; + return true; + + case kWebMIdBlockAdditional: { + uint64 block_add_id = base::HostToNet64(block_add_id_); + if (block_additional_data_) { + // TODO(vigneshv): Technically, more than 1 BlockAdditional is allowed + // as per matroska spec. But for now we don't have a use case to + // support parsing of such files. Take a look at this again when such a + // case arises. + MEDIA_LOG(log_cb_) << "More than 1 BlockAdditional in a BlockGroup is " + "not supported."; + return false; + } + // First 8 bytes of side_data in DecoderBuffer is the BlockAddID + // element's value in Big Endian format. This is done to mimic ffmpeg + // demuxer's behavior. + block_additional_data_size_ = size + sizeof(block_add_id); + block_additional_data_.reset(new uint8[block_additional_data_size_]); + memcpy(block_additional_data_.get(), &block_add_id, + sizeof(block_add_id)); + memcpy(block_additional_data_.get() + 8, data, size); + return true; + } + + default: + return true; + } +} + +bool WebMClusterParser::OnBlock(bool is_simple_block, int track_num, + int timecode, + int block_duration, + int flags, + const uint8* data, int size, + const uint8* additional, int additional_size) { + DCHECK_GE(size, 0); + if (cluster_timecode_ == -1) { + MEDIA_LOG(log_cb_) << "Got a block before cluster timecode."; + return false; + } + + // TODO(acolwell): Should relative negative timecode offsets be rejected? Or + // only when the absolute timecode is negative? See http://crbug.com/271794 + if (timecode < 0) { + MEDIA_LOG(log_cb_) << "Got a block with negative timecode offset " + << timecode; + return false; + } + + if (last_block_timecode_ != -1 && timecode < last_block_timecode_) { + MEDIA_LOG(log_cb_) + << "Got a block with a timecode before the previous block."; + return false; + } + + Track* track = NULL; + std::string encryption_key_id; + if (track_num == audio_.track_num()) { + track = &audio_; + encryption_key_id = audio_encryption_key_id_; + } else if (track_num == video_.track_num()) { + track = &video_; + encryption_key_id = video_encryption_key_id_; + } else if (ignored_tracks_.find(track_num) != ignored_tracks_.end()) { + return true; + } else if (Track* const text_track = FindTextTrack(track_num)) { + if (is_simple_block) // BlockGroup is required for WebVTT cues + return false; + if (block_duration < 0) // not specified + return false; + track = text_track; + } else { + MEDIA_LOG(log_cb_) << "Unexpected track number " << track_num; + return false; + } + + last_block_timecode_ = timecode; + + base::TimeDelta timestamp = base::TimeDelta::FromMicroseconds( + (cluster_timecode_ + timecode) * timecode_multiplier_); + + // The first bit of the flags is set when a SimpleBlock contains only + // keyframes. If this is a Block, then inspection of the payload is + // necessary to determine whether it contains a keyframe or not. + // http://www.matroska.org/technical/specs/index.html + bool is_keyframe = + is_simple_block ? (flags & 0x80) != 0 : track->IsKeyframe(data, size); + + scoped_refptr buffer = + StreamParserBuffer::CopyFrom(data, size, additional, additional_size, + is_keyframe); + + // Every encrypted Block has a signal byte and IV prepended to it. Current + // encrypted WebM request for comments specification is here + // http://wiki.webmproject.org/encryption/webm-encryption-rfc + if (!encryption_key_id.empty()) { + scoped_ptr config(WebMCreateDecryptConfig( + data, size, + reinterpret_cast(encryption_key_id.data()), + encryption_key_id.size())); + if (!config) + return false; + buffer->set_decrypt_config(config.Pass()); + } + + buffer->set_timestamp(timestamp); + if (cluster_start_time_ == kNoTimestamp()) + cluster_start_time_ = timestamp; + + if (block_duration >= 0) { + buffer->set_duration(base::TimeDelta::FromMicroseconds( + block_duration * timecode_multiplier_)); + } + + return track->AddBuffer(buffer); +} + +WebMClusterParser::Track::Track(int track_num, bool is_video) + : track_num_(track_num), + is_video_(is_video) { +} + +WebMClusterParser::Track::~Track() {} + +bool WebMClusterParser::Track::AddBuffer( + const scoped_refptr& buffer) { + DVLOG(2) << "AddBuffer() : " << track_num_ + << " ts " << buffer->timestamp().InSecondsF() + << " dur " << buffer->duration().InSecondsF() + << " kf " << buffer->IsKeyframe() + << " size " << buffer->data_size(); + + buffers_.push_back(buffer); + return true; +} + +void WebMClusterParser::Track::Reset() { + buffers_.clear(); +} + +bool WebMClusterParser::Track::IsKeyframe(const uint8* data, int size) const { + // For now, assume that all blocks are keyframes for datatypes other than + // video. This is a valid assumption for Vorbis, WebVTT, & Opus. + if (!is_video_) + return true; + + // Make sure the block is big enough for the minimal keyframe header size. + if (size < 7) + return false; + + // The LSb of the first byte must be a 0 for a keyframe. + // http://tools.ietf.org/html/rfc6386 Section 19.1 + if ((data[0] & 0x01) != 0) + return false; + + // Verify VP8 keyframe startcode. + // http://tools.ietf.org/html/rfc6386 Section 19.1 + if (data[3] != 0x9d || data[4] != 0x01 || data[5] != 0x2a) + return false; + + return true; +} + +void WebMClusterParser::ResetTextTracks() { + for (TextTrackMap::iterator it = text_track_map_.begin(); + it != text_track_map_.end(); + ++it) { + it->second.Reset(); + } +} + +WebMClusterParser::Track* +WebMClusterParser::FindTextTrack(int track_num) { + const TextTrackMap::iterator it = text_track_map_.find(track_num); + + if (it == text_track_map_.end()) + return NULL; + + return &it->second; +} + +} // namespace media diff --git a/media/webm/webm_cluster_parser.h b/media/webm/webm_cluster_parser.h new file mode 100644 index 0000000000..e156d47c23 --- /dev/null +++ b/media/webm/webm_cluster_parser.h @@ -0,0 +1,155 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_WEBM_WEBM_CLUSTER_PARSER_H_ +#define MEDIA_WEBM_WEBM_CLUSTER_PARSER_H_ + +#include +#include +#include +#include + +#include "base/memory/scoped_ptr.h" +#include "media/base/media_export.h" +#include "media/base/media_log.h" +#include "media/base/stream_parser_buffer.h" +#include "media/webm/webm_parser.h" +#include "media/webm/webm_tracks_parser.h" + +namespace media { + +class MEDIA_EXPORT WebMClusterParser : public WebMParserClient { + private: + // Helper class that manages per-track state. + class Track { + public: + Track(int track_num, bool is_video); + ~Track(); + + int track_num() const { return track_num_; } + const std::deque >& buffers() const { + return buffers_; + } + + bool AddBuffer(const scoped_refptr& buffer); + + // Clears all buffer state. + void Reset(); + + // Helper function used to inspect block data to determine if the + // block is a keyframe. + // |data| contains the bytes in the block. + // |size| indicates the number of bytes in |data|. + bool IsKeyframe(const uint8* data, int size) const; + + private: + int track_num_; + std::deque > buffers_; + bool is_video_; + }; + + typedef std::map TextTrackMap; + + public: + typedef std::deque > BufferQueue; + + class MEDIA_EXPORT TextTrackIterator { + public: + explicit TextTrackIterator(const TextTrackMap& text_track_map); + TextTrackIterator(const TextTrackIterator& rhs); + ~TextTrackIterator(); + + // To visit each text track. If the iterator is exhausted, it returns + // as parameters the values 0 and NULL, and the function returns false. + // Otherwise, it returns the buffers for the associated track, and the + // function returns true. + bool operator()(int* track_num, const BufferQueue** buffers); + private: + TextTrackIterator& operator=(const TextTrackIterator&); + + TextTrackMap::const_iterator iterator_; + const TextTrackMap::const_iterator iterator_end_; + }; + + WebMClusterParser(int64 timecode_scale, + int audio_track_num, + int video_track_num, + const WebMTracksParser::TextTracks& text_tracks, + const std::set& ignored_tracks, + const std::string& audio_encryption_key_id, + const std::string& video_encryption_key_id, + const LogCB& log_cb); + virtual ~WebMClusterParser(); + + // Resets the parser state so it can accept a new cluster. + void Reset(); + + // Parses a WebM cluster element in |buf|. + // + // Returns -1 if the parse fails. + // Returns 0 if more data is needed. + // Returns the number of bytes parsed on success. + int Parse(const uint8* buf, int size); + + base::TimeDelta cluster_start_time() const { return cluster_start_time_; } + const BufferQueue& audio_buffers() const { return audio_.buffers(); } + const BufferQueue& video_buffers() const { return video_.buffers(); } + + // Returns an iterator object, allowing each text track to be visited. + TextTrackIterator CreateTextTrackIterator() const; + + // Returns true if the last Parse() call stopped at the end of a cluster. + bool cluster_ended() const { return cluster_ended_; } + + private: + // WebMParserClient methods. + virtual WebMParserClient* OnListStart(int id) OVERRIDE; + virtual bool OnListEnd(int id) OVERRIDE; + virtual bool OnUInt(int id, int64 val) OVERRIDE; + virtual bool OnBinary(int id, const uint8* data, int size) OVERRIDE; + + bool ParseBlock(bool is_simple_block, const uint8* buf, int size, + const uint8* additional, int additional_size, int duration); + bool OnBlock(bool is_simple_block, int track_num, int timecode, int duration, + int flags, const uint8* data, int size, + const uint8* additional, int additional_size); + + // Resets the Track objects associated with each text track. + void ResetTextTracks(); + + // Search for the indicated track_num among the text tracks. Returns NULL + // if that track num is not a text track. + Track* FindTextTrack(int track_num); + + double timecode_multiplier_; // Multiplier used to convert timecodes into + // microseconds. + std::set ignored_tracks_; + std::string audio_encryption_key_id_; + std::string video_encryption_key_id_; + + WebMListParser parser_; + + int64 last_block_timecode_; + scoped_ptr block_data_; + int block_data_size_; + int64 block_duration_; + int64 block_add_id_; + scoped_ptr block_additional_data_; + int block_additional_data_size_; + + int64 cluster_timecode_; + base::TimeDelta cluster_start_time_; + bool cluster_ended_; + + Track audio_; + Track video_; + TextTrackMap text_track_map_; + LogCB log_cb_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(WebMClusterParser); +}; + +} // namespace media + +#endif // MEDIA_WEBM_WEBM_CLUSTER_PARSER_H_ diff --git a/media/webm/webm_cluster_parser_unittest.cc b/media/webm/webm_cluster_parser_unittest.cc new file mode 100644 index 0000000000..5c5837fa86 --- /dev/null +++ b/media/webm/webm_cluster_parser_unittest.cc @@ -0,0 +1,533 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/bind.h" +#include "base/logging.h" +#include "media/base/decrypt_config.h" +#include "media/webm/cluster_builder.h" +#include "media/webm/webm_cluster_parser.h" +#include "media/webm/webm_constants.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::InSequence; +using ::testing::Return; +using ::testing::_; + +namespace media { + +enum { + kTimecodeScale = 1000000, // Timecode scale for millisecond timestamps. + kAudioTrackNum = 1, + kVideoTrackNum = 2, + kTextTrackNum = 3, +}; + +struct BlockInfo { + int track_num; + int timestamp; + int duration; + bool use_simple_block; +}; + +static const BlockInfo kDefaultBlockInfo[] = { + { kAudioTrackNum, 0, 23, true }, + { kAudioTrackNum, 23, 23, true }, + { kVideoTrackNum, 33, 34, true }, + { kAudioTrackNum, 46, 23, true }, + { kVideoTrackNum, 67, 33, false }, + { kAudioTrackNum, 69, 23, false }, + { kVideoTrackNum, 100, 33, false }, +}; + +static const uint8 kEncryptedFrame[] = { + 0x01, // Block is encrypted + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 // IV +}; + +static scoped_ptr CreateCluster(int timecode, + const BlockInfo* block_info, + int block_count) { + ClusterBuilder cb; + cb.SetClusterTimecode(0); + + for (int i = 0; i < block_count; i++) { + uint8 data[] = { 0x00 }; + if (block_info[i].use_simple_block) { + cb.AddSimpleBlock(block_info[i].track_num, + block_info[i].timestamp, + 0, data, sizeof(data)); + continue; + } + + CHECK_GE(block_info[i].duration, 0); + cb.AddBlockGroup(block_info[i].track_num, + block_info[i].timestamp, + block_info[i].duration, + 0, data, sizeof(data)); + } + + return cb.Finish(); +} + +// Creates a Cluster with one encrypted Block. |bytes_to_write| is number of +// bytes of the encrypted frame to write. +static scoped_ptr CreateEncryptedCluster(int bytes_to_write) { + CHECK_GT(bytes_to_write, 0); + CHECK_LE(bytes_to_write, static_cast(sizeof(kEncryptedFrame))); + + ClusterBuilder cb; + cb.SetClusterTimecode(0); + cb.AddSimpleBlock(kVideoTrackNum, 0, 0, kEncryptedFrame, bytes_to_write); + return cb.Finish(); +} + +static bool VerifyBuffers(const WebMClusterParser::BufferQueue& audio_buffers, + const WebMClusterParser::BufferQueue& video_buffers, + const WebMClusterParser::BufferQueue& text_buffers, + const BlockInfo* block_info, + int block_count) { + size_t audio_offset = 0; + size_t video_offset = 0; + size_t text_offset = 0; + for (int i = 0; i < block_count; i++) { + const WebMClusterParser::BufferQueue* buffers = NULL; + size_t* offset; + + if (block_info[i].track_num == kAudioTrackNum) { + buffers = &audio_buffers; + offset = &audio_offset; + } else if (block_info[i].track_num == kVideoTrackNum) { + buffers = &video_buffers; + offset = &video_offset; + } else if (block_info[i].track_num == kTextTrackNum) { + buffers = &text_buffers; + offset = &text_offset; + } else { + LOG(ERROR) << "Unexpected track number " << block_info[i].track_num; + return false; + } + + if (*offset >= buffers->size()) + return false; + + scoped_refptr buffer = (*buffers)[(*offset)++]; + + + EXPECT_EQ(buffer->timestamp().InMilliseconds(), block_info[i].timestamp); + + if (!block_info[i].use_simple_block) + EXPECT_NE(buffer->duration(), kNoTimestamp()); + + if (buffer->duration() != kNoTimestamp()) + EXPECT_EQ(buffer->duration().InMilliseconds(), block_info[i].duration); + } + + return true; +} + +static bool VerifyBuffers(const scoped_ptr& parser, + const BlockInfo* block_info, + int block_count) { + typedef WebMClusterParser::TextTrackIterator TextTrackIterator; + TextTrackIterator text_it = parser->CreateTextTrackIterator(); + + int text_track_num; + const WebMClusterParser::BufferQueue* text_buffers; + + while (text_it(&text_track_num, &text_buffers)) + break; + + const WebMClusterParser::BufferQueue no_text_buffers; + + if (text_buffers == NULL) + text_buffers = &no_text_buffers; + + return VerifyBuffers(parser->audio_buffers(), + parser->video_buffers(), + *text_buffers, + block_info, + block_count); +} + +static bool VerifyTextBuffers( + const scoped_ptr& parser, + const BlockInfo* block_info_ptr, + int block_count, + int text_track_num, + const WebMClusterParser::BufferQueue& text_buffers) { + const BlockInfo* const block_info_end = block_info_ptr + block_count; + + typedef WebMClusterParser::BufferQueue::const_iterator TextBufferIter; + TextBufferIter buffer_iter = text_buffers.begin(); + const TextBufferIter buffer_end = text_buffers.end(); + + while (block_info_ptr != block_info_end) { + const BlockInfo& block_info = *block_info_ptr++; + + if (block_info.track_num != text_track_num) + continue; + + EXPECT_FALSE(block_info.use_simple_block); + EXPECT_FALSE(buffer_iter == buffer_end); + + const scoped_refptr buffer = *buffer_iter++; + EXPECT_EQ(buffer->timestamp().InMilliseconds(), block_info.timestamp); + EXPECT_EQ(buffer->duration().InMilliseconds(), block_info.duration); + } + + EXPECT_TRUE(buffer_iter == buffer_end); + return true; +} + +static bool VerifyEncryptedBuffer( + scoped_refptr buffer) { + EXPECT_TRUE(buffer->decrypt_config()); + EXPECT_EQ(static_cast(DecryptConfig::kDecryptionKeySize), + buffer->decrypt_config()->iv().length()); + const uint8* data = buffer->data(); + return data[0] & kWebMFlagEncryptedFrame; +} + +static void AppendToEnd(const WebMClusterParser::BufferQueue& src, + WebMClusterParser::BufferQueue* dest) { + for (WebMClusterParser::BufferQueue::const_iterator itr = src.begin(); + itr != src.end(); ++itr) { + dest->push_back(*itr); + } +} + +class WebMClusterParserTest : public testing::Test { + public: + WebMClusterParserTest() + : parser_(new WebMClusterParser(kTimecodeScale, + kAudioTrackNum, + kVideoTrackNum, + WebMTracksParser::TextTracks(), + std::set(), + std::string(), + std::string(), + LogCB())) {} + + protected: + scoped_ptr parser_; +}; + +TEST_F(WebMClusterParserTest, Reset) { + InSequence s; + + int block_count = arraysize(kDefaultBlockInfo); + scoped_ptr cluster(CreateCluster(0, kDefaultBlockInfo, block_count)); + + // Send slightly less than the full cluster so all but the last block is + // parsed. + int result = parser_->Parse(cluster->data(), cluster->size() - 1); + EXPECT_GT(result, 0); + EXPECT_LT(result, cluster->size()); + + ASSERT_TRUE(VerifyBuffers(parser_, kDefaultBlockInfo, block_count - 1)); + parser_->Reset(); + + // Now parse a whole cluster to verify that all the blocks will get parsed. + result = parser_->Parse(cluster->data(), cluster->size()); + EXPECT_EQ(result, cluster->size()); + ASSERT_TRUE(VerifyBuffers(parser_, kDefaultBlockInfo, block_count)); +} + +TEST_F(WebMClusterParserTest, ParseClusterWithSingleCall) { + int block_count = arraysize(kDefaultBlockInfo); + scoped_ptr cluster(CreateCluster(0, kDefaultBlockInfo, block_count)); + + int result = parser_->Parse(cluster->data(), cluster->size()); + EXPECT_EQ(cluster->size(), result); + ASSERT_TRUE(VerifyBuffers(parser_, kDefaultBlockInfo, block_count)); +} + +TEST_F(WebMClusterParserTest, ParseClusterWithMultipleCalls) { + int block_count = arraysize(kDefaultBlockInfo); + scoped_ptr cluster(CreateCluster(0, kDefaultBlockInfo, block_count)); + + WebMClusterParser::BufferQueue audio_buffers; + WebMClusterParser::BufferQueue video_buffers; + const WebMClusterParser::BufferQueue no_text_buffers; + + const uint8* data = cluster->data(); + int size = cluster->size(); + int default_parse_size = 3; + int parse_size = std::min(default_parse_size, size); + + while (size > 0) { + int result = parser_->Parse(data, parse_size); + ASSERT_GE(result, 0); + ASSERT_LE(result, parse_size); + + if (result == 0) { + // The parser needs more data so increase the parse_size a little. + parse_size += default_parse_size; + parse_size = std::min(parse_size, size); + continue; + } + + AppendToEnd(parser_->audio_buffers(), &audio_buffers); + AppendToEnd(parser_->video_buffers(), &video_buffers); + + parse_size = default_parse_size; + + data += result; + size -= result; + } + ASSERT_TRUE(VerifyBuffers(audio_buffers, video_buffers, + no_text_buffers, kDefaultBlockInfo, + block_count)); +} + +// Verify that both BlockGroups with the BlockDuration before the Block +// and BlockGroups with the BlockDuration after the Block are supported +// correctly. +// Note: Raw bytes are use here because ClusterBuilder only generates +// one of these scenarios. +TEST_F(WebMClusterParserTest, ParseBlockGroup) { + const BlockInfo kBlockInfo[] = { + { kAudioTrackNum, 0, 23, false }, + { kVideoTrackNum, 33, 34, false }, + }; + int block_count = arraysize(kBlockInfo); + + const uint8 kClusterData[] = { + 0x1F, 0x43, 0xB6, 0x75, 0x9B, // Cluster(size=27) + 0xE7, 0x81, 0x00, // Timecode(size=1, value=0) + // BlockGroup with BlockDuration before Block. + 0xA0, 0x8A, // BlockGroup(size=10) + 0x9B, 0x81, 0x17, // BlockDuration(size=1, value=23) + 0xA1, 0x85, 0x81, 0x00, 0x00, 0x00, 0xaa, // Block(size=5, track=1, ts=0) + // BlockGroup with BlockDuration after Block. + 0xA0, 0x8A, // BlockGroup(size=10) + 0xA1, 0x85, 0x82, 0x00, 0x21, 0x00, 0x55, // Block(size=5, track=2, ts=33) + 0x9B, 0x81, 0x22, // BlockDuration(size=1, value=34) + }; + const int kClusterSize = sizeof(kClusterData); + + int result = parser_->Parse(kClusterData, kClusterSize); + EXPECT_EQ(result, kClusterSize); + ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo, block_count)); +} + +TEST_F(WebMClusterParserTest, ParseSimpleBlockAndBlockGroupMixture) { + const BlockInfo kBlockInfo[] = { + { kAudioTrackNum, 0, 23, true }, + { kAudioTrackNum, 23, 23, false }, + { kVideoTrackNum, 33, 34, true }, + { kAudioTrackNum, 46, 23, false }, + { kVideoTrackNum, 67, 33, false }, + }; + int block_count = arraysize(kBlockInfo); + scoped_ptr cluster(CreateCluster(0, kBlockInfo, block_count)); + + int result = parser_->Parse(cluster->data(), cluster->size()); + EXPECT_EQ(cluster->size(), result); + ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo, block_count)); +} + +TEST_F(WebMClusterParserTest, IgnoredTracks) { + std::set ignored_tracks; + ignored_tracks.insert(kTextTrackNum); + + parser_.reset(new WebMClusterParser(kTimecodeScale, + kAudioTrackNum, + kVideoTrackNum, + WebMTracksParser::TextTracks(), + ignored_tracks, + std::string(), + std::string(), + LogCB())); + + const BlockInfo kInputBlockInfo[] = { + { kAudioTrackNum, 0, 23, true }, + { kAudioTrackNum, 23, 23, true }, + { kVideoTrackNum, 33, 33, true }, + { kTextTrackNum, 33, 99, true }, + { kAudioTrackNum, 46, 23, true }, + { kVideoTrackNum, 67, 33, true }, + }; + int input_block_count = arraysize(kInputBlockInfo); + + const BlockInfo kOutputBlockInfo[] = { + { kAudioTrackNum, 0, 23, true }, + { kAudioTrackNum, 23, 23, true }, + { kVideoTrackNum, 33, 33, true }, + { kAudioTrackNum, 46, 23, true }, + { kVideoTrackNum, 67, 33, true }, + }; + int output_block_count = arraysize(kOutputBlockInfo); + + scoped_ptr cluster( + CreateCluster(0, kInputBlockInfo, input_block_count)); + + int result = parser_->Parse(cluster->data(), cluster->size()); + EXPECT_EQ(cluster->size(), result); + ASSERT_TRUE(VerifyBuffers(parser_, kOutputBlockInfo, output_block_count)); +} + +TEST_F(WebMClusterParserTest, ParseTextTracks) { + typedef WebMTracksParser::TextTracks TextTracks; + TextTracks text_tracks; + WebMTracksParser::TextTrackInfo text_track_info; + + text_track_info.kind = kTextSubtitles; + text_tracks.insert(std::make_pair(TextTracks::key_type(kTextTrackNum), + text_track_info)); + + parser_.reset(new WebMClusterParser(kTimecodeScale, + kAudioTrackNum, + kVideoTrackNum, + text_tracks, + std::set(), + std::string(), + std::string(), + LogCB())); + + const BlockInfo kInputBlockInfo[] = { + { kAudioTrackNum, 0, 23, true }, + { kAudioTrackNum, 23, 23, true }, + { kVideoTrackNum, 33, 33, true }, + { kTextTrackNum, 33, 42, false }, + { kAudioTrackNum, 46, 23, true }, + { kTextTrackNum, 55, 44, false }, + { kVideoTrackNum, 67, 33, true }, + }; + int input_block_count = arraysize(kInputBlockInfo); + + scoped_ptr cluster( + CreateCluster(0, kInputBlockInfo, input_block_count)); + + int result = parser_->Parse(cluster->data(), cluster->size()); + EXPECT_EQ(cluster->size(), result); + ASSERT_TRUE(VerifyBuffers(parser_, kInputBlockInfo, input_block_count)); +} + +TEST_F(WebMClusterParserTest, TextTracksSimpleBlock) { + typedef WebMTracksParser::TextTracks TextTracks; + TextTracks text_tracks; + WebMTracksParser::TextTrackInfo text_track_info; + + text_track_info.kind = kTextSubtitles; + text_tracks.insert(std::make_pair(TextTracks::key_type(kTextTrackNum), + text_track_info)); + + parser_.reset(new WebMClusterParser(kTimecodeScale, + kAudioTrackNum, + kVideoTrackNum, + text_tracks, + std::set(), + std::string(), + std::string(), + LogCB())); + + const BlockInfo kInputBlockInfo[] = { + { kTextTrackNum, 33, 42, true }, + }; + int input_block_count = arraysize(kInputBlockInfo); + + scoped_ptr cluster( + CreateCluster(0, kInputBlockInfo, input_block_count)); + + int result = parser_->Parse(cluster->data(), cluster->size()); + EXPECT_LT(result, 0); +} + +TEST_F(WebMClusterParserTest, ParseMultipleTextTracks) { + typedef WebMTracksParser::TextTracks TextTracks; + TextTracks text_tracks; + WebMTracksParser::TextTrackInfo text_track_info; + + const int kSubtitleTextTrackNum = kTextTrackNum; + const int kCaptionTextTrackNum = kTextTrackNum + 1; + + text_track_info.kind = kTextSubtitles; + text_tracks.insert(std::make_pair(TextTracks::key_type(kSubtitleTextTrackNum), + text_track_info)); + + text_track_info.kind = kTextCaptions; + text_tracks.insert(std::make_pair(TextTracks::key_type(kCaptionTextTrackNum), + text_track_info)); + + parser_.reset(new WebMClusterParser(kTimecodeScale, + kAudioTrackNum, + kVideoTrackNum, + text_tracks, + std::set(), + std::string(), + std::string(), + LogCB())); + + const BlockInfo kInputBlockInfo[] = { + { kAudioTrackNum, 0, 23, true }, + { kAudioTrackNum, 23, 23, true }, + { kVideoTrackNum, 33, 33, true }, + { kSubtitleTextTrackNum, 33, 42, false }, + { kAudioTrackNum, 46, 23, true }, + { kCaptionTextTrackNum, 55, 44, false }, + { kVideoTrackNum, 67, 33, true }, + { kSubtitleTextTrackNum, 67, 33, false }, + }; + int input_block_count = arraysize(kInputBlockInfo); + + scoped_ptr cluster( + CreateCluster(0, kInputBlockInfo, input_block_count)); + + int result = parser_->Parse(cluster->data(), cluster->size()); + EXPECT_EQ(cluster->size(), result); + + WebMClusterParser::TextTrackIterator text_it = + parser_->CreateTextTrackIterator(); + + int text_track_num; + const WebMClusterParser::BufferQueue* text_buffers; + + while (text_it(&text_track_num, &text_buffers)) { + const WebMTracksParser::TextTracks::const_iterator find_result = + text_tracks.find(text_track_num); + ASSERT_TRUE(find_result != text_tracks.end()); + ASSERT_TRUE(VerifyTextBuffers(parser_, kInputBlockInfo, input_block_count, + text_track_num, *text_buffers)); + } +} + +TEST_F(WebMClusterParserTest, ParseEncryptedBlock) { + scoped_ptr cluster(CreateEncryptedCluster(sizeof(kEncryptedFrame))); + + parser_.reset(new WebMClusterParser(kTimecodeScale, + kAudioTrackNum, + kVideoTrackNum, + WebMTracksParser::TextTracks(), + std::set(), + std::string(), + "video_key_id", + LogCB())); + int result = parser_->Parse(cluster->data(), cluster->size()); + EXPECT_EQ(cluster->size(), result); + ASSERT_EQ(1UL, parser_->video_buffers().size()); + scoped_refptr buffer = parser_->video_buffers()[0]; + EXPECT_TRUE(VerifyEncryptedBuffer(buffer)); +} + +TEST_F(WebMClusterParserTest, ParseBadEncryptedBlock) { + scoped_ptr cluster( + CreateEncryptedCluster(sizeof(kEncryptedFrame) - 1)); + + parser_.reset(new WebMClusterParser(kTimecodeScale, + kAudioTrackNum, + kVideoTrackNum, + WebMTracksParser::TextTracks(), + std::set(), + std::string(), + "video_key_id", + LogCB())); + int result = parser_->Parse(cluster->data(), cluster->size()); + EXPECT_EQ(-1, result); +} + +} // namespace media diff --git a/media/webm/webm_constants.cc b/media/webm/webm_constants.cc new file mode 100644 index 0000000000..13ae086eca --- /dev/null +++ b/media/webm/webm_constants.cc @@ -0,0 +1,14 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/webm/webm_constants.h" + +namespace media { + +const char kWebMCodecSubtitles[] = "D_WEBVTT/SUBTITLES"; +const char kWebMCodecCaptions[] = "D_WEBVTT/CAPTIONS"; +const char kWebMCodecDescriptions[] = "D_WEBVTT/DESCRIPTIONS"; +const char kWebMCodecMetadata[] = "D_WEBVTT/METADATA"; + +} // namespace media diff --git a/media/webm/webm_constants.h b/media/webm/webm_constants.h new file mode 100644 index 0000000000..cda45e00d4 --- /dev/null +++ b/media/webm/webm_constants.h @@ -0,0 +1,225 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_WEBM_WEBM_CONSTANTS_H_ +#define MEDIA_WEBM_WEBM_CONSTANTS_H_ + +#include "base/basictypes.h" +#include "media/base/media_export.h" + +namespace media { + +// WebM element IDs. +// This is a subset of the IDs in the Matroska spec. +// http://www.matroska.org/technical/specs/index.html +const int kWebMIdAESSettingsCipherMode = 0x47E8; +const int kWebMIdAlphaMode = 0x53C0; +const int kWebMIdAspectRatioType = 0x54B3; +const int kWebMIdAttachedFile = 0x61A7; +const int kWebMIdAttachmentLink = 0x7446; +const int kWebMIdAttachments = 0x1941A469; +const int kWebMIdAudio = 0xE1; +const int kWebMIdBitDepth = 0x6264; +const int kWebMIdBlock = 0xA1; +const int kWebMIdBlockAddID = 0xEE; +const int kWebMIdBlockAdditions = 0x75A1; +const int kWebMIdBlockAdditional = 0xA5; +const int kWebMIdBlockDuration = 0x9B; +const int kWebMIdBlockGroup = 0xA0; +const int kWebMIdBlockMore = 0xA6; +const int kWebMIdChannels = 0x9F; +const int kWebMIdChapCountry = 0x437E; +const int kWebMIdChapLanguage = 0x437C; +const int kWebMIdChapProcess = 0x6944; +const int kWebMIdChapProcessCodecID = 0x6955; +const int kWebMIdChapProcessCommand = 0x6911; +const int kWebMIdChapProcessData = 0x6933; +const int kWebMIdChapProcessPrivate = 0x450D; +const int kWebMIdChapProcessTime = 0x6922; +const int kWebMIdChapString = 0x85; +const int kWebMIdChapterAtom = 0xB6; +const int kWebMIdChapterDisplay = 0x80; +const int kWebMIdChapterFlagEnabled = 0x4598; +const int kWebMIdChapterFlagHidden = 0x98; +const int kWebMIdChapterPhysicalEquiv = 0x63C3; +const int kWebMIdChapters = 0x1043A770; +const int kWebMIdChapterSegmentEditionUID = 0x6EBC; +const int kWebMIdChapterSegmentUID = 0x6E67; +const int kWebMIdChapterTimeEnd = 0x92; +const int kWebMIdChapterTimeStart = 0x91; +const int kWebMIdChapterTrack = 0x8F; +const int kWebMIdChapterTrackNumber = 0x89; +const int kWebMIdChapterTranslate = 0x6924; +const int kWebMIdChapterTranslateCodec = 0x69BF; +const int kWebMIdChapterTranslateEditionUID = 0x69FC; +const int kWebMIdChapterTranslateID = 0x69A5; +const int kWebMIdChapterUID = 0x73C4; +const int kWebMIdCluster = 0x1F43B675; +const int kWebMIdCodecDecodeAll = 0xAA; +const int kWebMIdCodecID = 0x86; +const int kWebMIdCodecName = 0x258688; +const int kWebMIdCodecPrivate = 0x63A2; +const int kWebMIdCodecState = 0xA4; +const int kWebMIdColorSpace = 0x2EB524; +const int kWebMIdContentCompAlgo = 0x4254; +const int kWebMIdContentCompression = 0x5034; +const int kWebMIdContentCompSettings = 0x4255; +const int kWebMIdContentEncAESSettings = 0x47E7; +const int kWebMIdContentEncAlgo = 0x47E1; +const int kWebMIdContentEncKeyID = 0x47E2; +const int kWebMIdContentEncoding = 0x6240; +const int kWebMIdContentEncodingOrder = 0x5031; +const int kWebMIdContentEncodings = 0x6D80; +const int kWebMIdContentEncodingScope = 0x5032; +const int kWebMIdContentEncodingType = 0x5033; +const int kWebMIdContentEncryption = 0x5035; +const int kWebMIdContentSigAlgo = 0x47E5; +const int kWebMIdContentSigHashAlgo = 0x47E6; +const int kWebMIdContentSigKeyID = 0x47E4; +const int kWebMIdContentSignature = 0x47E3; +const int kWebMIdCRC32 = 0xBF; +const int kWebMIdCueBlockNumber = 0x5378; +const int kWebMIdCueClusterPosition = 0xF1; +const int kWebMIdCueCodecState = 0xEA; +const int kWebMIdCuePoint = 0xBB; +const int kWebMIdCueReference = 0xDB; +const int kWebMIdCueRefTime = 0x96; +const int kWebMIdCues = 0x1C53BB6B; +const int kWebMIdCueTime = 0xB3; +const int kWebMIdCueTrack = 0xF7; +const int kWebMIdCueTrackPositions = 0xB7; +const int kWebMIdDateUTC = 0x4461; +const int kWebMIdDefaultDuration = 0x23E383; +const int kWebMIdDisplayHeight = 0x54BA; +const int kWebMIdDisplayUnit = 0x54B2; +const int kWebMIdDisplayWidth = 0x54B0; +const int kWebMIdDocType = 0x4282; +const int kWebMIdDocTypeReadVersion = 0x4285; +const int kWebMIdDocTypeVersion = 0x4287; +const int kWebMIdDuration = 0x4489; +const int kWebMIdEBMLHeader = 0x1A45DFA3; +const int kWebMIdEBMLMaxIDLength = 0x42F2; +const int kWebMIdEBMLMaxSizeLength = 0x42F3; +const int kWebMIdEBMLReadVersion = 0x42F7; +const int kWebMIdEBMLVersion = 0x4286; +const int kWebMIdEditionEntry = 0x45B9; +const int kWebMIdEditionFlagDefault = 0x45DB; +const int kWebMIdEditionFlagHidden = 0x45BD; +const int kWebMIdEditionFlagOrdered = 0x45DD; +const int kWebMIdEditionUID = 0x45BC; +const int kWebMIdFileData = 0x465C; +const int kWebMIdFileDescription = 0x467E; +const int kWebMIdFileMimeType = 0x4660; +const int kWebMIdFileName = 0x466E; +const int kWebMIdFileUID = 0x46AE; +const int kWebMIdFlagDefault = 0x88; +const int kWebMIdFlagEnabled = 0xB9; +const int kWebMIdFlagForced = 0x55AA; +const int kWebMIdFlagInterlaced = 0x9A; +const int kWebMIdFlagLacing = 0x9C; +const int kWebMIdInfo = 0x1549A966; +const int kWebMIdJoinBlocks = 0xE9; +const int kWebMIdLaceNumber = 0xCC; +const int kWebMIdLanguage = 0x22B59C; +const int kWebMIdMaxBlockAdditionId = 0x55EE; +const int kWebMIdMaxCache = 0x6DF8; +const int kWebMIdMinCache = 0x6DE7; +const int kWebMIdMuxingApp = 0x4D80; +const int kWebMIdName = 0x536E; +const int kWebMIdNextFilename = 0x3E83BB; +const int kWebMIdNextUID = 0x3EB923; +const int kWebMIdOutputSamplingFrequency = 0x78B5; +const int kWebMIdPixelCropBottom = 0x54AA; +const int kWebMIdPixelCropLeft = 0x54CC; +const int kWebMIdPixelCropRight = 0x54DD; +const int kWebMIdPixelCropTop = 0x54BB; +const int kWebMIdPixelHeight = 0xBA; +const int kWebMIdPixelWidth = 0xB0; +const int kWebMIdPosition = 0xA7; +const int kWebMIdPrevFilename = 0x3C83AB; +const int kWebMIdPrevSize = 0xAB; +const int kWebMIdPrevUID = 0x3CB923; +const int kWebMIdReferenceBlock = 0xFB; +const int kWebMIdReferencePriority = 0xFA; +const int kWebMIdSamplingFrequency = 0xB5; +const int kWebMIdSeek = 0x4DBB; +const int kWebMIdSeekHead = 0x114D9B74; +const int kWebMIdSeekID = 0x53AB; +const int kWebMIdSeekPosition = 0x53AC; +const int kWebMIdSegment = 0x18538067; +const int kWebMIdSegmentFamily = 0x4444; +const int kWebMIdSegmentFilename = 0x7384; +const int kWebMIdSegmentUID = 0x73A4; +const int kWebMIdSilentTrackNumber = 0x58D7; +const int kWebMIdSilentTracks = 0x5854; +const int kWebMIdSimpleBlock = 0xA3; +const int kWebMIdSimpleTag = 0x67C8; +const int kWebMIdSlices = 0x8E; +const int kWebMIdStereoMode = 0x53B8; +const int kWebMIdTag = 0x7373; +const int kWebMIdTagAttachmentUID = 0x63C6; +const int kWebMIdTagBinary = 0x4485; +const int kWebMIdTagChapterUID = 0x63C4; +const int kWebMIdTagDefault = 0x4484; +const int kWebMIdTagEditionUID = 0x63C9; +const int kWebMIdTagLanguage = 0x447A; +const int kWebMIdTagName = 0x45A3; +const int kWebMIdTags = 0x1254C367; +const int kWebMIdTagString = 0x4487; +const int kWebMIdTagTrackUID = 0x63C5; +const int kWebMIdTargets = 0x63C0; +const int kWebMIdTargetType = 0x63CA; +const int kWebMIdTargetTypeValue = 0x68CA; +const int kWebMIdTimecode = 0xE7; +const int kWebMIdTimecodeScale = 0x2AD7B1; +const int kWebMIdTimeSlice = 0xE8; +const int kWebMIdTitle = 0x7BA9; +const int kWebMIdTrackCombinePlanes = 0xE3; +const int kWebMIdTrackEntry = 0xAE; +const int kWebMIdTrackJoinUID = 0xED; +const int kWebMIdTrackNumber = 0xD7; +const int kWebMIdTrackOperation = 0xE2; +const int kWebMIdTrackOverlay = 0x6FAB; +const int kWebMIdTrackPlane = 0xE4; +const int kWebMIdTrackPlaneType = 0xE6; +const int kWebMIdTrackPlaneUID = 0xE5; +const int kWebMIdTracks = 0x1654AE6B; +const int kWebMIdTrackTimecodeScale = 0x23314F; +const int kWebMIdTrackTranslate = 0x6624; +const int kWebMIdTrackTranslateCodec = 0x66BF; +const int kWebMIdTrackTranslateEditionUID = 0x66FC; +const int kWebMIdTrackTranslateTrackID = 0x66A5; +const int kWebMIdTrackType = 0x83; +const int kWebMIdTrackUID = 0x73C5; +const int kWebMIdVideo = 0xE0; +const int kWebMIdVoid = 0xEC; +const int kWebMIdWritingApp = 0x5741; + +const int64 kWebMReservedId = 0x1FFFFFFF; +const int64 kWebMUnknownSize = GG_LONGLONG(0x00FFFFFFFFFFFFFF); + +const uint8 kWebMFlagKeyframe = 0x80; + +// Current encrypted WebM request for comments specification is here +// http://wiki.webmproject.org/encryption/webm-encryption-rfc +const uint8 kWebMFlagEncryptedFrame = 0x1; +const int kWebMIvSize = 8; +const int kWebMSignalByteSize = 1; + +// Current specification for WebVTT embedded in WebM +// http://wiki.webmproject.org/webm-metadata/temporal-metadata/webvtt-in-webm + +const int kWebMTrackTypeVideo = 1; +const int kWebMTrackTypeAudio = 2; +const int kWebMTrackTypeSubtitlesOrCaptions = 0x11; +const int kWebMTrackTypeDescriptionsOrMetadata = 0x21; + +MEDIA_EXPORT extern const char kWebMCodecSubtitles[]; +MEDIA_EXPORT extern const char kWebMCodecCaptions[]; +MEDIA_EXPORT extern const char kWebMCodecDescriptions[]; +MEDIA_EXPORT extern const char kWebMCodecMetadata[]; + +} // namespace media + +#endif // MEDIA_WEBM_WEBM_CONSTANTS_H_ diff --git a/media/webm/webm_content_encodings.cc b/media/webm/webm_content_encodings.cc new file mode 100644 index 0000000000..9789c0f302 --- /dev/null +++ b/media/webm/webm_content_encodings.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/logging.h" +#include "media/webm/webm_content_encodings.h" + +namespace media { + +ContentEncoding::ContentEncoding() + : order_(kOrderInvalid), + scope_(kScopeInvalid), + type_(kTypeInvalid), + encryption_algo_(kEncAlgoInvalid), + cipher_mode_(kCipherModeInvalid) { +} + +ContentEncoding::~ContentEncoding() {} + +void ContentEncoding::SetEncryptionKeyId(const uint8* encryption_key_id, + int size) { + DCHECK(encryption_key_id); + DCHECK_GT(size, 0); + encryption_key_id_.assign(reinterpret_cast(encryption_key_id), + size); +} + +} // namespace media diff --git a/media/webm/webm_content_encodings.h b/media/webm/webm_content_encodings.h new file mode 100644 index 0000000000..2866f253f0 --- /dev/null +++ b/media/webm/webm_content_encodings.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 MEDIA_WEBM_WEBM_CONTENT_ENCODINGS_H_ +#define MEDIA_WEBM_WEBM_CONTENT_ENCODINGS_H_ + +#include + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "media/base/media_export.h" + +namespace media { + +class MEDIA_EXPORT ContentEncoding { + public: + // The following enum definitions are based on the ContentEncoding element + // specified in the Matroska spec. + + static const int kOrderInvalid = -1; + + enum Scope { + kScopeInvalid = 0, + kScopeAllFrameContents = 1, + kScopeTrackPrivateData = 2, + kScopeNextContentEncodingData = 4, + kScopeMax = 7, + }; + + enum Type { + kTypeInvalid = -1, + kTypeCompression = 0, + kTypeEncryption = 1, + }; + + enum EncryptionAlgo { + kEncAlgoInvalid = -1, + kEncAlgoNotEncrypted = 0, + kEncAlgoDes = 1, + kEncAlgo3des = 2, + kEncAlgoTwofish = 3, + kEncAlgoBlowfish = 4, + kEncAlgoAes = 5, + }; + + enum CipherMode { + kCipherModeInvalid = 0, + kCipherModeCtr = 1, + }; + + ContentEncoding(); + ~ContentEncoding(); + + int64 order() const { return order_; } + void set_order(int64 order) { order_ = order; } + + Scope scope() const { return scope_; } + void set_scope(Scope scope) { scope_ = scope; } + + Type type() const { return type_; } + void set_type(Type type) { type_ = type; } + + EncryptionAlgo encryption_algo() const { return encryption_algo_; } + void set_encryption_algo(EncryptionAlgo encryption_algo) { + encryption_algo_ = encryption_algo; + } + + const std::string& encryption_key_id() const { return encryption_key_id_; } + void SetEncryptionKeyId(const uint8* encryption_key_id, int size); + + CipherMode cipher_mode() const { return cipher_mode_; } + void set_cipher_mode(CipherMode mode) { cipher_mode_ = mode; } + + private: + int64 order_; + Scope scope_; + Type type_; + EncryptionAlgo encryption_algo_; + std::string encryption_key_id_; + CipherMode cipher_mode_; + + DISALLOW_COPY_AND_ASSIGN(ContentEncoding); +}; + +} // namespace media + +#endif // MEDIA_WEBM_WEBM_CONTENT_ENCODINGS_H_ diff --git a/media/webm/webm_content_encodings_client.cc b/media/webm/webm_content_encodings_client.cc new file mode 100644 index 0000000000..bcf964ed31 --- /dev/null +++ b/media/webm/webm_content_encodings_client.cc @@ -0,0 +1,265 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/webm/webm_content_encodings_client.h" + +#include "base/logging.h" +#include "base/stl_util.h" +#include "media/webm/webm_constants.h" + +namespace media { + +WebMContentEncodingsClient::WebMContentEncodingsClient(const LogCB& log_cb) + : log_cb_(log_cb), + content_encryption_encountered_(false), + content_encodings_ready_(false) { +} + +WebMContentEncodingsClient::~WebMContentEncodingsClient() { + STLDeleteElements(&content_encodings_); +} + +const ContentEncodings& WebMContentEncodingsClient::content_encodings() const { + DCHECK(content_encodings_ready_); + return content_encodings_; +} + +WebMParserClient* WebMContentEncodingsClient::OnListStart(int id) { + if (id == kWebMIdContentEncodings) { + DCHECK(!cur_content_encoding_.get()); + DCHECK(!content_encryption_encountered_); + STLDeleteElements(&content_encodings_); + content_encodings_ready_ = false; + return this; + } + + if (id == kWebMIdContentEncoding) { + DCHECK(!cur_content_encoding_.get()); + DCHECK(!content_encryption_encountered_); + cur_content_encoding_.reset(new ContentEncoding()); + return this; + } + + if (id == kWebMIdContentEncryption) { + DCHECK(cur_content_encoding_.get()); + if (content_encryption_encountered_) { + MEDIA_LOG(log_cb_) << "Unexpected multiple ContentEncryption."; + return NULL; + } + content_encryption_encountered_ = true; + return this; + } + + if (id == kWebMIdContentEncAESSettings) { + DCHECK(cur_content_encoding_.get()); + return this; + } + + // This should not happen if WebMListParser is working properly. + DCHECK(false); + return NULL; +} + +// Mandatory occurrence restriction is checked in this function. Multiple +// occurrence restriction is checked in OnUInt and OnBinary. +bool WebMContentEncodingsClient::OnListEnd(int id) { + if (id == kWebMIdContentEncodings) { + // ContentEncoding element is mandatory. Check this! + if (content_encodings_.empty()) { + MEDIA_LOG(log_cb_) << "Missing ContentEncoding."; + return false; + } + content_encodings_ready_ = true; + return true; + } + + if (id == kWebMIdContentEncoding) { + DCHECK(cur_content_encoding_.get()); + + // + // Specify default values to missing mandatory elements. + // + + if (cur_content_encoding_->order() == ContentEncoding::kOrderInvalid) { + // Default value of encoding order is 0, which should only be used on the + // first ContentEncoding. + if (!content_encodings_.empty()) { + MEDIA_LOG(log_cb_) << "Missing ContentEncodingOrder."; + return false; + } + cur_content_encoding_->set_order(0); + } + + if (cur_content_encoding_->scope() == ContentEncoding::kScopeInvalid) + cur_content_encoding_->set_scope(ContentEncoding::kScopeAllFrameContents); + + if (cur_content_encoding_->type() == ContentEncoding::kTypeInvalid) + cur_content_encoding_->set_type(ContentEncoding::kTypeCompression); + + // Check for elements valid in spec but not supported for now. + if (cur_content_encoding_->type() == ContentEncoding::kTypeCompression) { + MEDIA_LOG(log_cb_) << "ContentCompression not supported."; + return false; + } + + // Enforce mandatory elements without default values. + DCHECK(cur_content_encoding_->type() == ContentEncoding::kTypeEncryption); + if (!content_encryption_encountered_) { + MEDIA_LOG(log_cb_) << "ContentEncodingType is encryption but" + << " ContentEncryption is missing."; + return false; + } + + content_encodings_.push_back(cur_content_encoding_.release()); + content_encryption_encountered_ = false; + return true; + } + + if (id == kWebMIdContentEncryption) { + DCHECK(cur_content_encoding_.get()); + // Specify default value for elements that are not present. + if (cur_content_encoding_->encryption_algo() == + ContentEncoding::kEncAlgoInvalid) { + cur_content_encoding_->set_encryption_algo( + ContentEncoding::kEncAlgoNotEncrypted); + } + return true; + } + + if (id == kWebMIdContentEncAESSettings) { + if (cur_content_encoding_->cipher_mode() == + ContentEncoding::kCipherModeInvalid) + cur_content_encoding_->set_cipher_mode(ContentEncoding::kCipherModeCtr); + return true; + } + + // This should not happen if WebMListParser is working properly. + DCHECK(false); + return false; +} + +// Multiple occurrence restriction and range are checked in this function. +// Mandatory occurrence restriction is checked in OnListEnd. +bool WebMContentEncodingsClient::OnUInt(int id, int64 val) { + DCHECK(cur_content_encoding_.get()); + + if (id == kWebMIdContentEncodingOrder) { + if (cur_content_encoding_->order() != ContentEncoding::kOrderInvalid) { + MEDIA_LOG(log_cb_) << "Unexpected multiple ContentEncodingOrder."; + return false; + } + + if (val != static_cast(content_encodings_.size())) { + // According to the spec, encoding order starts with 0 and counts upwards. + MEDIA_LOG(log_cb_) << "Unexpected ContentEncodingOrder."; + return false; + } + + cur_content_encoding_->set_order(val); + return true; + } + + if (id == kWebMIdContentEncodingScope) { + if (cur_content_encoding_->scope() != ContentEncoding::kScopeInvalid) { + MEDIA_LOG(log_cb_) << "Unexpected multiple ContentEncodingScope."; + return false; + } + + if (val == ContentEncoding::kScopeInvalid || + val > ContentEncoding::kScopeMax) { + MEDIA_LOG(log_cb_) << "Unexpected ContentEncodingScope."; + return false; + } + + if (val & ContentEncoding::kScopeNextContentEncodingData) { + MEDIA_LOG(log_cb_) << "Encoded next ContentEncoding is not supported."; + return false; + } + + cur_content_encoding_->set_scope(static_cast(val)); + return true; + } + + if (id == kWebMIdContentEncodingType) { + if (cur_content_encoding_->type() != ContentEncoding::kTypeInvalid) { + MEDIA_LOG(log_cb_) << "Unexpected multiple ContentEncodingType."; + return false; + } + + if (val == ContentEncoding::kTypeCompression) { + MEDIA_LOG(log_cb_) << "ContentCompression not supported."; + return false; + } + + if (val != ContentEncoding::kTypeEncryption) { + MEDIA_LOG(log_cb_) << "Unexpected ContentEncodingType " << val << "."; + return false; + } + + cur_content_encoding_->set_type(static_cast(val)); + return true; + } + + if (id == kWebMIdContentEncAlgo) { + if (cur_content_encoding_->encryption_algo() != + ContentEncoding::kEncAlgoInvalid) { + MEDIA_LOG(log_cb_) << "Unexpected multiple ContentEncAlgo."; + return false; + } + + if (val < ContentEncoding::kEncAlgoNotEncrypted || + val > ContentEncoding::kEncAlgoAes) { + MEDIA_LOG(log_cb_) << "Unexpected ContentEncAlgo " << val << "."; + return false; + } + + cur_content_encoding_->set_encryption_algo( + static_cast(val)); + return true; + } + + if (id == kWebMIdAESSettingsCipherMode) { + if (cur_content_encoding_->cipher_mode() != + ContentEncoding::kCipherModeInvalid) { + MEDIA_LOG(log_cb_) << "Unexpected multiple AESSettingsCipherMode."; + return false; + } + + if (val != ContentEncoding::kCipherModeCtr) { + MEDIA_LOG(log_cb_) << "Unexpected AESSettingsCipherMode " << val << "."; + return false; + } + + cur_content_encoding_->set_cipher_mode( + static_cast(val)); + return true; + } + + // This should not happen if WebMListParser is working properly. + DCHECK(false); + return false; +} + +// Multiple occurrence restriction is checked in this function. Mandatory +// restriction is checked in OnListEnd. +bool WebMContentEncodingsClient::OnBinary(int id, const uint8* data, int size) { + DCHECK(cur_content_encoding_.get()); + DCHECK(data); + DCHECK_GT(size, 0); + + if (id == kWebMIdContentEncKeyID) { + if (!cur_content_encoding_->encryption_key_id().empty()) { + MEDIA_LOG(log_cb_) << "Unexpected multiple ContentEncKeyID"; + return false; + } + cur_content_encoding_->SetEncryptionKeyId(data, size); + return true; + } + + // This should not happen if WebMListParser is working properly. + DCHECK(false); + return false; +} + +} // namespace media diff --git a/media/webm/webm_content_encodings_client.h b/media/webm/webm_content_encodings_client.h new file mode 100644 index 0000000000..e477fcf380 --- /dev/null +++ b/media/webm/webm_content_encodings_client.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 MEDIA_WEBM_WEBM_CONTENT_ENCODINGS_CLIENT_H_ +#define MEDIA_WEBM_WEBM_CONTENT_ENCODINGS_CLIENT_H_ + +#include + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "media/base/media_export.h" +#include "media/base/media_log.h" +#include "media/webm/webm_content_encodings.h" +#include "media/webm/webm_parser.h" + +namespace media { + +typedef std::vector ContentEncodings; + +// Parser for WebM ContentEncodings element. +class MEDIA_EXPORT WebMContentEncodingsClient : public WebMParserClient { + public: + explicit WebMContentEncodingsClient(const LogCB& log_cb); + virtual ~WebMContentEncodingsClient(); + + const ContentEncodings& content_encodings() const; + + // WebMParserClient methods + virtual WebMParserClient* OnListStart(int id) OVERRIDE; + virtual bool OnListEnd(int id) OVERRIDE; + virtual bool OnUInt(int id, int64 val) OVERRIDE; + virtual bool OnBinary(int id, const uint8* data, int size) OVERRIDE; + + private: + LogCB log_cb_; + scoped_ptr cur_content_encoding_; + bool content_encryption_encountered_; + ContentEncodings content_encodings_; + + // |content_encodings_| is ready. For debugging purpose. + bool content_encodings_ready_; + + DISALLOW_COPY_AND_ASSIGN(WebMContentEncodingsClient); +}; + +} // namespace media + +#endif // MEDIA_WEBM_WEBM_CONTENT_ENCODINGS_CLIENT_H_ diff --git a/media/webm/webm_content_encodings_client_unittest.cc b/media/webm/webm_content_encodings_client_unittest.cc new file mode 100644 index 0000000000..bb9e694312 --- /dev/null +++ b/media/webm/webm_content_encodings_client_unittest.cc @@ -0,0 +1,238 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/bind.h" +#include "media/webm/webm_constants.h" +#include "media/webm/webm_content_encodings_client.h" +#include "media/webm/webm_parser.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +class WebMContentEncodingsClientTest : public testing::Test { + public: + WebMContentEncodingsClientTest() + : client_(LogCB()), + parser_(kWebMIdContentEncodings, &client_) {} + + void ParseAndExpectToFail(const uint8* buf, int size) { + int result = parser_.Parse(buf, size); + EXPECT_EQ(-1, result); + } + + protected: + WebMContentEncodingsClient client_; + WebMListParser parser_; +}; + +TEST_F(WebMContentEncodingsClientTest, EmptyContentEncodings) { + const uint8 kContentEncodings[] = { + 0x6D, 0x80, 0x80, // ContentEncodings (size = 0) + }; + int size = sizeof(kContentEncodings); + ParseAndExpectToFail(kContentEncodings, size); +} + +TEST_F(WebMContentEncodingsClientTest, EmptyContentEncoding) { + const uint8 kContentEncodings[] = { + 0x6D, 0x80, 0x83, // ContentEncodings (size = 3) + 0x63, 0x40, 0x80, // ContentEncoding (size = 0) + }; + int size = sizeof(kContentEncodings); + ParseAndExpectToFail(kContentEncodings, size); +} + +TEST_F(WebMContentEncodingsClientTest, SingleContentEncoding) { + const uint8 kContentEncodings[] = { + 0x6D, 0x80, 0xA1, // ContentEncodings (size = 33) + 0x62, 0x40, 0x9e, // ContentEncoding (size = 30) + 0x50, 0x31, 0x81, 0x00, // ContentEncodingOrder (size = 1) + 0x50, 0x32, 0x81, 0x01, // ContentEncodingScope (size = 1) + 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1) + 0x50, 0x35, 0x8F, // ContentEncryption (size = 15) + 0x47, 0xE1, 0x81, 0x05, // ContentEncAlgo (size = 1) + 0x47, 0xE2, 0x88, // ContentEncKeyID (size = 8) + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + }; + int size = sizeof(kContentEncodings); + + int result = parser_.Parse(kContentEncodings, size); + ASSERT_EQ(size, result); + const ContentEncodings& content_encodings = client_.content_encodings(); + + ASSERT_EQ(1u, content_encodings.size()); + ASSERT_TRUE(content_encodings[0]); + EXPECT_EQ(0, content_encodings[0]->order()); + EXPECT_EQ(ContentEncoding::kScopeAllFrameContents, + content_encodings[0]->scope()); + EXPECT_EQ(ContentEncoding::kTypeEncryption, content_encodings[0]->type()); + EXPECT_EQ(ContentEncoding::kEncAlgoAes, + content_encodings[0]->encryption_algo()); + EXPECT_EQ(8u, content_encodings[0]->encryption_key_id().size()); +} + +TEST_F(WebMContentEncodingsClientTest, MultipleContentEncoding) { + const uint8 kContentEncodings[] = { + 0x6D, 0x80, 0xC2, // ContentEncodings (size = 66) + 0x62, 0x40, 0x9e, // ContentEncoding (size = 30) + 0x50, 0x31, 0x81, 0x00, // ContentEncodingOrder (size = 1) + 0x50, 0x32, 0x81, 0x03, // ContentEncodingScope (size = 1) + 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1) + 0x50, 0x35, 0x8F, // ContentEncryption (size = 15) + 0x47, 0xE1, 0x81, 0x05, // ContentEncAlgo (size = 1) + 0x47, 0xE2, 0x88, // ContentEncKeyID (size = 8) + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0x62, 0x40, 0x9e, // ContentEncoding (size = 30) + 0x50, 0x31, 0x81, 0x01, // ContentEncodingOrder (size = 1) + 0x50, 0x32, 0x81, 0x03, // ContentEncodingScope (size = 1) + 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1) + 0x50, 0x35, 0x8F, // ContentEncryption (size = 15) + 0x47, 0xE1, 0x81, 0x01, // ContentEncAlgo (size = 1) + 0x47, 0xE2, 0x88, // ContentEncKeyID (size = 8) + 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, + }; + int size = sizeof(kContentEncodings); + + int result = parser_.Parse(kContentEncodings, size); + ASSERT_EQ(size, result); + const ContentEncodings& content_encodings = client_.content_encodings(); + ASSERT_EQ(2u, content_encodings.size()); + + for (int i = 0; i < 2; ++i) { + ASSERT_TRUE(content_encodings[i]); + EXPECT_EQ(i, content_encodings[i]->order()); + EXPECT_EQ(ContentEncoding::kScopeAllFrameContents | + ContentEncoding::kScopeTrackPrivateData, + content_encodings[i]->scope()); + EXPECT_EQ(ContentEncoding::kTypeEncryption, content_encodings[i]->type()); + EXPECT_EQ(!i ? ContentEncoding::kEncAlgoAes : ContentEncoding::kEncAlgoDes, + content_encodings[i]->encryption_algo()); + EXPECT_EQ(8u, content_encodings[i]->encryption_key_id().size()); + } +} + +TEST_F(WebMContentEncodingsClientTest, DefaultValues) { + const uint8 kContentEncodings[] = { + 0x6D, 0x80, 0x8A, // ContentEncodings (size = 10) + 0x62, 0x40, 0x87, // ContentEncoding (size = 7) + // ContentEncodingOrder missing + // ContentEncodingScope missing + 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1) + 0x50, 0x35, 0x80, // ContentEncryption (size = 0) + // ContentEncAlgo missing + }; + int size = sizeof(kContentEncodings); + + int result = parser_.Parse(kContentEncodings, size); + ASSERT_EQ(size, result); + const ContentEncodings& content_encodings = client_.content_encodings(); + + ASSERT_EQ(1u, content_encodings.size()); + ASSERT_TRUE(content_encodings[0]); + EXPECT_EQ(0, content_encodings[0]->order()); + EXPECT_EQ(ContentEncoding::kScopeAllFrameContents, + content_encodings[0]->scope()); + EXPECT_EQ(ContentEncoding::kTypeEncryption, content_encodings[0]->type()); + EXPECT_EQ(ContentEncoding::kEncAlgoNotEncrypted, + content_encodings[0]->encryption_algo()); + EXPECT_TRUE(content_encodings[0]->encryption_key_id().empty()); +} + +TEST_F(WebMContentEncodingsClientTest, ContentEncodingsClientReuse) { + const uint8 kContentEncodings[] = { + 0x6D, 0x80, 0xA1, // ContentEncodings (size = 33) + 0x62, 0x40, 0x9e, // ContentEncoding (size = 30) + 0x50, 0x31, 0x81, 0x00, // ContentEncodingOrder (size = 1) + 0x50, 0x32, 0x81, 0x01, // ContentEncodingScope (size = 1) + 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1) + 0x50, 0x35, 0x8F, // ContentEncryption (size = 15) + 0x47, 0xE1, 0x81, 0x05, // ContentEncAlgo (size = 1) + 0x47, 0xE2, 0x88, // ContentEncKeyID (size = 8) + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + }; + int size = sizeof(kContentEncodings); + + // Parse for the first time. + int result = parser_.Parse(kContentEncodings, size); + ASSERT_EQ(size, result); + + // Parse again. + parser_.Reset(); + result = parser_.Parse(kContentEncodings, size); + ASSERT_EQ(size, result); + const ContentEncodings& content_encodings = client_.content_encodings(); + + ASSERT_EQ(1u, content_encodings.size()); + ASSERT_TRUE(content_encodings[0]); + EXPECT_EQ(0, content_encodings[0]->order()); + EXPECT_EQ(ContentEncoding::kScopeAllFrameContents, + content_encodings[0]->scope()); + EXPECT_EQ(ContentEncoding::kTypeEncryption, content_encodings[0]->type()); + EXPECT_EQ(ContentEncoding::kEncAlgoAes, + content_encodings[0]->encryption_algo()); + EXPECT_EQ(8u, content_encodings[0]->encryption_key_id().size()); +} + +TEST_F(WebMContentEncodingsClientTest, InvalidContentEncodingOrder) { + const uint8 kContentEncodings[] = { + 0x6D, 0x80, 0x8E, // ContentEncodings (size = 14) + 0x62, 0x40, 0x8B, // ContentEncoding (size = 11) + 0x50, 0x31, 0x81, 0xEE, // ContentEncodingOrder (size = 1), invalid + 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1) + 0x50, 0x35, 0x80, // ContentEncryption (size = 0) + }; + int size = sizeof(kContentEncodings); + ParseAndExpectToFail(kContentEncodings, size); +} + +TEST_F(WebMContentEncodingsClientTest, InvalidContentEncodingScope) { + const uint8 kContentEncodings[] = { + 0x6D, 0x80, 0x8E, // ContentEncodings (size = 14) + 0x62, 0x40, 0x8B, // ContentEncoding (size = 11) + 0x50, 0x32, 0x81, 0xEE, // ContentEncodingScope (size = 1), invalid + 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1) + 0x50, 0x35, 0x80, // ContentEncryption (size = 0) + }; + int size = sizeof(kContentEncodings); + ParseAndExpectToFail(kContentEncodings, size); +} + +TEST_F(WebMContentEncodingsClientTest, InvalidContentEncodingType) { + const uint8 kContentEncodings[] = { + 0x6D, 0x80, 0x8E, // ContentEncodings (size = 14) + 0x62, 0x40, 0x8B, // ContentEncoding (size = 11) + 0x50, 0x33, 0x81, 0x00, // ContentEncodingType (size = 1), invalid + 0x50, 0x35, 0x80, // ContentEncryption (size = 0) + }; + int size = sizeof(kContentEncodings); + ParseAndExpectToFail(kContentEncodings, size); +} + +// ContentEncodingType is encryption but no ContentEncryption present. +TEST_F(WebMContentEncodingsClientTest, MissingContentEncryption) { + const uint8 kContentEncodings[] = { + 0x6D, 0x80, 0x87, // ContentEncodings (size = 7) + 0x62, 0x40, 0x84, // ContentEncoding (size = 4) + 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1) + // ContentEncryption missing + }; + int size = sizeof(kContentEncodings); + ParseAndExpectToFail(kContentEncodings, size); +} + +TEST_F(WebMContentEncodingsClientTest, InvalidContentEncAlgo) { + const uint8 kContentEncodings[] = { + 0x6D, 0x80, 0x99, // ContentEncodings (size = 25) + 0x62, 0x40, 0x96, // ContentEncoding (size = 22) + 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1) + 0x50, 0x35, 0x8F, // ContentEncryption (size = 15) + 0x47, 0xE1, 0x81, 0xEE, // ContentEncAlgo (size = 1), invalid + 0x47, 0xE2, 0x88, // ContentEncKeyID (size = 8) + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + }; + int size = sizeof(kContentEncodings); + ParseAndExpectToFail(kContentEncodings, size); +} + +} // namespace media diff --git a/media/webm/webm_crypto_helpers.cc b/media/webm/webm_crypto_helpers.cc new file mode 100644 index 0000000000..a663f3cd6a --- /dev/null +++ b/media/webm/webm_crypto_helpers.cc @@ -0,0 +1,60 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/webm/webm_crypto_helpers.h" + +#include "base/logging.h" +#include "base/sys_byteorder.h" +#include "media/base/decrypt_config.h" +#include "media/webm/webm_constants.h" + +namespace media { +namespace { + +// Generates a 16 byte CTR counter block. The CTR counter block format is a +// CTR IV appended with a CTR block counter. |iv| is an 8 byte CTR IV. +// |iv_size| is the size of |iv| in btyes. Returns a string of +// kDecryptionKeySize bytes. +std::string GenerateWebMCounterBlock(const uint8* iv, int iv_size) { + std::string counter_block(reinterpret_cast(iv), iv_size); + counter_block.append(DecryptConfig::kDecryptionKeySize - iv_size, 0); + return counter_block; +} + +} // namespace anonymous + +scoped_ptr WebMCreateDecryptConfig( + const uint8* data, int data_size, + const uint8* key_id, int key_id_size) { + if (data_size < kWebMSignalByteSize) { + DVLOG(1) << "Got a block from an encrypted stream with no data."; + return scoped_ptr(); + } + + uint8 signal_byte = data[0]; + int frame_offset = sizeof(signal_byte); + + // Setting the DecryptConfig object of the buffer while leaving the + // initialization vector empty will tell the decryptor that the frame is + // unencrypted. + std::string counter_block; + + if (signal_byte & kWebMFlagEncryptedFrame) { + if (data_size < kWebMSignalByteSize + kWebMIvSize) { + DVLOG(1) << "Got an encrypted block with not enough data " << data_size; + return scoped_ptr(); + } + counter_block = GenerateWebMCounterBlock(data + frame_offset, kWebMIvSize); + frame_offset += kWebMIvSize; + } + + scoped_ptr config(new DecryptConfig( + std::string(reinterpret_cast(key_id), key_id_size), + counter_block, + frame_offset, + std::vector())); + return config.Pass(); +} + +} // namespace media diff --git a/media/webm/webm_crypto_helpers.h b/media/webm/webm_crypto_helpers.h new file mode 100644 index 0000000000..c5f1f15eca --- /dev/null +++ b/media/webm/webm_crypto_helpers.h @@ -0,0 +1,32 @@ +// 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 MEDIA_WEBM_WEBM_CRYPTO_HELPERS_H_ +#define MEDIA_WEBM_WEBM_CRYPTO_HELPERS_H_ + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "media/base/decoder_buffer.h" + +namespace media { + +// TODO(xhwang): Figure out the init data type appropriately once it's spec'ed. +// See https://www.w3.org/Bugs/Public/show_bug.cgi?id=19096 for more +// information. +const char kWebMEncryptInitDataType[] = "video/webm"; + +// Returns an initialized DecryptConfig, which can be sent to the Decryptor if +// the stream has potentially encrypted frames. Every encrypted Block has a +// signal byte, and if the frame is encrypted, an initialization vector +// prepended to the frame. Leaving the IV empty will tell the decryptor that the +// frame is unencrypted. Returns NULL if |data| is invalid. Current encrypted +// WebM request for comments specification is here +// http://wiki.webmproject.org/encryption/webm-encryption-rfc +scoped_ptr WebMCreateDecryptConfig( + const uint8* data, int data_size, + const uint8* key_id, int key_id_size); + +} // namespace media + +#endif // MEDIA_WEBM_WEBM_CRYPT_HELPERS_H_ diff --git a/media/webm/webm_info_parser.cc b/media/webm/webm_info_parser.cc new file mode 100644 index 0000000000..6df1690bf8 --- /dev/null +++ b/media/webm/webm_info_parser.cc @@ -0,0 +1,84 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/webm/webm_info_parser.h" + +#include "base/logging.h" +#include "media/webm/webm_constants.h" + +namespace media { + +// Default timecode scale if the TimecodeScale element is +// not specified in the INFO element. +static const int kWebMDefaultTimecodeScale = 1000000; + +WebMInfoParser::WebMInfoParser() + : timecode_scale_(-1), + duration_(-1) { +} + +WebMInfoParser::~WebMInfoParser() {} + +int WebMInfoParser::Parse(const uint8* buf, int size) { + timecode_scale_ = -1; + duration_ = -1; + + WebMListParser parser(kWebMIdInfo, this); + int result = parser.Parse(buf, size); + + if (result <= 0) + return result; + + // For now we do all or nothing parsing. + return parser.IsParsingComplete() ? result : 0; +} + +WebMParserClient* WebMInfoParser::OnListStart(int id) { return this; } + +bool WebMInfoParser::OnListEnd(int id) { + if (id == kWebMIdInfo && timecode_scale_ == -1) { + // Set timecode scale to default value if it isn't present in + // the Info element. + timecode_scale_ = kWebMDefaultTimecodeScale; + } + return true; +} + +bool WebMInfoParser::OnUInt(int id, int64 val) { + if (id != kWebMIdTimecodeScale) + return true; + + if (timecode_scale_ != -1) { + DVLOG(1) << "Multiple values for id " << std::hex << id << " specified"; + return false; + } + + timecode_scale_ = val; + return true; +} + +bool WebMInfoParser::OnFloat(int id, double val) { + if (id != kWebMIdDuration) { + DVLOG(1) << "Unexpected float for id" << std::hex << id; + return false; + } + + if (duration_ != -1) { + DVLOG(1) << "Multiple values for duration."; + return false; + } + + duration_ = val; + return true; +} + +bool WebMInfoParser::OnBinary(int id, const uint8* data, int size) { + return true; +} + +bool WebMInfoParser::OnString(int id, const std::string& str) { + return true; +} + +} // namespace media diff --git a/media/webm/webm_info_parser.h b/media/webm/webm_info_parser.h new file mode 100644 index 0000000000..ab5de43b1d --- /dev/null +++ b/media/webm/webm_info_parser.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 MEDIA_WEBM_WEBM_INFO_PARSER_H_ +#define MEDIA_WEBM_WEBM_INFO_PARSER_H_ + +#include "base/compiler_specific.h" +#include "media/base/media_export.h" +#include "media/webm/webm_parser.h" + +namespace media { + +// Parser for WebM Info element. +class MEDIA_EXPORT WebMInfoParser : public WebMParserClient { + public: + WebMInfoParser(); + virtual ~WebMInfoParser(); + + // Parses a WebM Info element in |buf|. + // + // Returns -1 if the parse fails. + // Returns 0 if more data is needed. + // Returns the number of bytes parsed on success. + int Parse(const uint8* buf, int size); + + int64 timecode_scale() const { return timecode_scale_; } + double duration() const { return duration_; } + + private: + // WebMParserClient methods + virtual WebMParserClient* OnListStart(int id) OVERRIDE; + virtual bool OnListEnd(int id) OVERRIDE; + virtual bool OnUInt(int id, int64 val) OVERRIDE; + virtual bool OnFloat(int id, double val) OVERRIDE; + virtual bool OnBinary(int id, const uint8* data, int size) OVERRIDE; + virtual bool OnString(int id, const std::string& str) OVERRIDE; + + int64 timecode_scale_; + double duration_; + + DISALLOW_COPY_AND_ASSIGN(WebMInfoParser); +}; + +} // namespace media + +#endif // MEDIA_WEBM_WEBM_INFO_PARSER_H_ diff --git a/media/webm/webm_parser.cc b/media/webm/webm_parser.cc new file mode 100644 index 0000000000..30e5c1b5e5 --- /dev/null +++ b/media/webm/webm_parser.cc @@ -0,0 +1,943 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/webm/webm_parser.h" + +// This file contains code to parse WebM file elements. It was created +// from information in the Matroska spec. +// http://www.matroska.org/technical/specs/index.html +// This file contains code for encrypted WebM. Current WebM +// encrypted request for comments specification is here +// http://wiki.webmproject.org/encryption/webm-encryption-rfc + +#include + +#include "base/logging.h" +#include "media/webm/webm_constants.h" + +namespace media { + +enum ElementType { + UNKNOWN, + LIST, // Referred to as Master Element in the Matroska spec. + UINT, + FLOAT, + BINARY, + STRING, + SKIP, +}; + +struct ElementIdInfo { + ElementType type_; + int id_; +}; + +struct ListElementInfo { + int id_; + int level_; + const ElementIdInfo* id_info_; + int id_info_count_; +}; + +// The following are tables indicating what IDs are valid sub-elements +// of particular elements. If an element is encountered that doesn't +// appear in the list, a parsing error is signalled. Some elements are +// marked as SKIP because they are valid, but we don't care about them +// right now. +static const ElementIdInfo kEBMLHeaderIds[] = { + {UINT, kWebMIdEBMLVersion}, + {UINT, kWebMIdEBMLReadVersion}, + {UINT, kWebMIdEBMLMaxIDLength}, + {UINT, kWebMIdEBMLMaxSizeLength}, + {STRING, kWebMIdDocType}, + {UINT, kWebMIdDocTypeVersion}, + {UINT, kWebMIdDocTypeReadVersion}, +}; + +static const ElementIdInfo kSegmentIds[] = { + {LIST, kWebMIdSeekHead}, + {LIST, kWebMIdInfo}, + {LIST, kWebMIdCluster}, + {LIST, kWebMIdTracks}, + {LIST, kWebMIdCues}, + {LIST, kWebMIdAttachments}, + {LIST, kWebMIdChapters}, + {LIST, kWebMIdTags}, +}; + +static const ElementIdInfo kSeekHeadIds[] = { + {LIST, kWebMIdSeek}, +}; + +static const ElementIdInfo kSeekIds[] = { + {BINARY, kWebMIdSeekID}, + {UINT, kWebMIdSeekPosition}, +}; + +static const ElementIdInfo kInfoIds[] = { + {BINARY, kWebMIdSegmentUID}, + {STRING, kWebMIdSegmentFilename}, + {BINARY, kWebMIdPrevUID}, + {STRING, kWebMIdPrevFilename}, + {BINARY, kWebMIdNextUID}, + {STRING, kWebMIdNextFilename}, + {BINARY, kWebMIdSegmentFamily}, + {LIST, kWebMIdChapterTranslate}, + {UINT, kWebMIdTimecodeScale}, + {FLOAT, kWebMIdDuration}, + {BINARY, kWebMIdDateUTC}, + {STRING, kWebMIdTitle}, + {STRING, kWebMIdMuxingApp}, + {STRING, kWebMIdWritingApp}, +}; + +static const ElementIdInfo kChapterTranslateIds[] = { + {UINT, kWebMIdChapterTranslateEditionUID}, + {UINT, kWebMIdChapterTranslateCodec}, + {BINARY, kWebMIdChapterTranslateID}, +}; + +static const ElementIdInfo kClusterIds[] = { + {BINARY, kWebMIdSimpleBlock}, + {UINT, kWebMIdTimecode}, + {LIST, kWebMIdSilentTracks}, + {UINT, kWebMIdPosition}, + {UINT, kWebMIdPrevSize}, + {LIST, kWebMIdBlockGroup}, +}; + +static const ElementIdInfo kSilentTracksIds[] = { + {UINT, kWebMIdSilentTrackNumber}, +}; + +static const ElementIdInfo kBlockGroupIds[] = { + {BINARY, kWebMIdBlock}, + {LIST, kWebMIdBlockAdditions}, + {UINT, kWebMIdBlockDuration}, + {UINT, kWebMIdReferencePriority}, + {BINARY, kWebMIdReferenceBlock}, + {BINARY, kWebMIdCodecState}, + {LIST, kWebMIdSlices}, +}; + +static const ElementIdInfo kBlockAdditionsIds[] = { + {LIST, kWebMIdBlockMore}, +}; + +static const ElementIdInfo kBlockMoreIds[] = { + {UINT, kWebMIdBlockAddID}, + {BINARY, kWebMIdBlockAdditional}, +}; + +static const ElementIdInfo kSlicesIds[] = { + {LIST, kWebMIdTimeSlice}, +}; + +static const ElementIdInfo kTimeSliceIds[] = { + {UINT, kWebMIdLaceNumber}, +}; + +static const ElementIdInfo kTracksIds[] = { + {LIST, kWebMIdTrackEntry}, +}; + +static const ElementIdInfo kTrackEntryIds[] = { + {UINT, kWebMIdTrackNumber}, + {UINT, kWebMIdTrackUID}, + {UINT, kWebMIdTrackType}, + {UINT, kWebMIdFlagEnabled}, + {UINT, kWebMIdFlagDefault}, + {UINT, kWebMIdFlagForced}, + {UINT, kWebMIdFlagLacing}, + {UINT, kWebMIdMinCache}, + {UINT, kWebMIdMaxCache}, + {UINT, kWebMIdDefaultDuration}, + {FLOAT, kWebMIdTrackTimecodeScale}, + {UINT, kWebMIdMaxBlockAdditionId}, + {STRING, kWebMIdName}, + {STRING, kWebMIdLanguage}, + {STRING, kWebMIdCodecID}, + {BINARY, kWebMIdCodecPrivate}, + {STRING, kWebMIdCodecName}, + {UINT, kWebMIdAttachmentLink}, + {UINT, kWebMIdCodecDecodeAll}, + {UINT, kWebMIdTrackOverlay}, + {LIST, kWebMIdTrackTranslate}, + {LIST, kWebMIdVideo}, + {LIST, kWebMIdAudio}, + {LIST, kWebMIdTrackOperation}, + {LIST, kWebMIdContentEncodings}, +}; + +static const ElementIdInfo kTrackTranslateIds[] = { + {UINT, kWebMIdTrackTranslateEditionUID}, + {UINT, kWebMIdTrackTranslateCodec}, + {BINARY, kWebMIdTrackTranslateTrackID}, +}; + +static const ElementIdInfo kVideoIds[] = { + {UINT, kWebMIdFlagInterlaced}, + {UINT, kWebMIdStereoMode}, + {UINT, kWebMIdAlphaMode}, + {UINT, kWebMIdPixelWidth}, + {UINT, kWebMIdPixelHeight}, + {UINT, kWebMIdPixelCropBottom}, + {UINT, kWebMIdPixelCropTop}, + {UINT, kWebMIdPixelCropLeft}, + {UINT, kWebMIdPixelCropRight}, + {UINT, kWebMIdDisplayWidth}, + {UINT, kWebMIdDisplayHeight}, + {UINT, kWebMIdDisplayUnit}, + {UINT, kWebMIdAspectRatioType}, + {BINARY, kWebMIdColorSpace}, +}; + +static const ElementIdInfo kAudioIds[] = { + {FLOAT, kWebMIdSamplingFrequency}, + {FLOAT, kWebMIdOutputSamplingFrequency}, + {UINT, kWebMIdChannels}, + {UINT, kWebMIdBitDepth}, +}; + +static const ElementIdInfo kTrackOperationIds[] = { + {LIST, kWebMIdTrackCombinePlanes}, + {LIST, kWebMIdJoinBlocks}, +}; + +static const ElementIdInfo kTrackCombinePlanesIds[] = { + {LIST, kWebMIdTrackPlane}, +}; + +static const ElementIdInfo kTrackPlaneIds[] = { + {UINT, kWebMIdTrackPlaneUID}, + {UINT, kWebMIdTrackPlaneType}, +}; + +static const ElementIdInfo kJoinBlocksIds[] = { + {UINT, kWebMIdTrackJoinUID}, +}; + +static const ElementIdInfo kContentEncodingsIds[] = { + {LIST, kWebMIdContentEncoding}, +}; + +static const ElementIdInfo kContentEncodingIds[] = { + {UINT, kWebMIdContentEncodingOrder}, + {UINT, kWebMIdContentEncodingScope}, + {UINT, kWebMIdContentEncodingType}, + {LIST, kWebMIdContentCompression}, + {LIST, kWebMIdContentEncryption}, +}; + +static const ElementIdInfo kContentCompressionIds[] = { + {UINT, kWebMIdContentCompAlgo}, + {BINARY, kWebMIdContentCompSettings}, +}; + +static const ElementIdInfo kContentEncryptionIds[] = { + {LIST, kWebMIdContentEncAESSettings}, + {UINT, kWebMIdContentEncAlgo}, + {BINARY, kWebMIdContentEncKeyID}, + {BINARY, kWebMIdContentSignature}, + {BINARY, kWebMIdContentSigKeyID}, + {UINT, kWebMIdContentSigAlgo}, + {UINT, kWebMIdContentSigHashAlgo}, +}; + +static const ElementIdInfo kContentEncAESSettingsIds[] = { + {UINT, kWebMIdAESSettingsCipherMode}, +}; + +static const ElementIdInfo kCuesIds[] = { + {LIST, kWebMIdCuePoint}, +}; + +static const ElementIdInfo kCuePointIds[] = { + {UINT, kWebMIdCueTime}, + {LIST, kWebMIdCueTrackPositions}, +}; + +static const ElementIdInfo kCueTrackPositionsIds[] = { + {UINT, kWebMIdCueTrack}, + {UINT, kWebMIdCueClusterPosition}, + {UINT, kWebMIdCueBlockNumber}, + {UINT, kWebMIdCueCodecState}, + {LIST, kWebMIdCueReference}, +}; + +static const ElementIdInfo kCueReferenceIds[] = { + {UINT, kWebMIdCueRefTime}, +}; + +static const ElementIdInfo kAttachmentsIds[] = { + {LIST, kWebMIdAttachedFile}, +}; + +static const ElementIdInfo kAttachedFileIds[] = { + {STRING, kWebMIdFileDescription}, + {STRING, kWebMIdFileName}, + {STRING, kWebMIdFileMimeType}, + {BINARY, kWebMIdFileData}, + {UINT, kWebMIdFileUID}, +}; + +static const ElementIdInfo kChaptersIds[] = { + {LIST, kWebMIdEditionEntry}, +}; + +static const ElementIdInfo kEditionEntryIds[] = { + {UINT, kWebMIdEditionUID}, + {UINT, kWebMIdEditionFlagHidden}, + {UINT, kWebMIdEditionFlagDefault}, + {UINT, kWebMIdEditionFlagOrdered}, + {LIST, kWebMIdChapterAtom}, +}; + +static const ElementIdInfo kChapterAtomIds[] = { + {UINT, kWebMIdChapterUID}, + {UINT, kWebMIdChapterTimeStart}, + {UINT, kWebMIdChapterTimeEnd}, + {UINT, kWebMIdChapterFlagHidden}, + {UINT, kWebMIdChapterFlagEnabled}, + {BINARY, kWebMIdChapterSegmentUID}, + {UINT, kWebMIdChapterSegmentEditionUID}, + {UINT, kWebMIdChapterPhysicalEquiv}, + {LIST, kWebMIdChapterTrack}, + {LIST, kWebMIdChapterDisplay}, + {LIST, kWebMIdChapProcess}, +}; + +static const ElementIdInfo kChapterTrackIds[] = { + {UINT, kWebMIdChapterTrackNumber}, +}; + +static const ElementIdInfo kChapterDisplayIds[] = { + {STRING, kWebMIdChapString}, + {STRING, kWebMIdChapLanguage}, + {STRING, kWebMIdChapCountry}, +}; + +static const ElementIdInfo kChapProcessIds[] = { + {UINT, kWebMIdChapProcessCodecID}, + {BINARY, kWebMIdChapProcessPrivate}, + {LIST, kWebMIdChapProcessCommand}, +}; + +static const ElementIdInfo kChapProcessCommandIds[] = { + {UINT, kWebMIdChapProcessTime}, + {BINARY, kWebMIdChapProcessData}, +}; + +static const ElementIdInfo kTagsIds[] = { + {LIST, kWebMIdTag}, +}; + +static const ElementIdInfo kTagIds[] = { + {LIST, kWebMIdTargets}, + {LIST, kWebMIdSimpleTag}, +}; + +static const ElementIdInfo kTargetsIds[] = { + {UINT, kWebMIdTargetTypeValue}, + {STRING, kWebMIdTargetType}, + {UINT, kWebMIdTagTrackUID}, + {UINT, kWebMIdTagEditionUID}, + {UINT, kWebMIdTagChapterUID}, + {UINT, kWebMIdTagAttachmentUID}, +}; + +static const ElementIdInfo kSimpleTagIds[] = { + {STRING, kWebMIdTagName}, + {STRING, kWebMIdTagLanguage}, + {UINT, kWebMIdTagDefault}, + {STRING, kWebMIdTagString}, + {BINARY, kWebMIdTagBinary}, +}; + +#define LIST_ELEMENT_INFO(id, level, id_info) \ + { (id), (level), (id_info), arraysize(id_info) } + +static const ListElementInfo kListElementInfo[] = { + LIST_ELEMENT_INFO(kWebMIdCluster, 1, kClusterIds), + LIST_ELEMENT_INFO(kWebMIdEBMLHeader, 0, kEBMLHeaderIds), + LIST_ELEMENT_INFO(kWebMIdSegment, 0, kSegmentIds), + LIST_ELEMENT_INFO(kWebMIdSeekHead, 1, kSeekHeadIds), + LIST_ELEMENT_INFO(kWebMIdSeek, 2, kSeekIds), + LIST_ELEMENT_INFO(kWebMIdInfo, 1, kInfoIds), + LIST_ELEMENT_INFO(kWebMIdChapterTranslate, 2, kChapterTranslateIds), + LIST_ELEMENT_INFO(kWebMIdSilentTracks, 2, kSilentTracksIds), + LIST_ELEMENT_INFO(kWebMIdBlockGroup, 2, kBlockGroupIds), + LIST_ELEMENT_INFO(kWebMIdBlockAdditions, 3, kBlockAdditionsIds), + LIST_ELEMENT_INFO(kWebMIdBlockMore, 4, kBlockMoreIds), + LIST_ELEMENT_INFO(kWebMIdSlices, 3, kSlicesIds), + LIST_ELEMENT_INFO(kWebMIdTimeSlice, 4, kTimeSliceIds), + LIST_ELEMENT_INFO(kWebMIdTracks, 1, kTracksIds), + LIST_ELEMENT_INFO(kWebMIdTrackEntry, 2, kTrackEntryIds), + LIST_ELEMENT_INFO(kWebMIdTrackTranslate, 3, kTrackTranslateIds), + LIST_ELEMENT_INFO(kWebMIdVideo, 3, kVideoIds), + LIST_ELEMENT_INFO(kWebMIdAudio, 3, kAudioIds), + LIST_ELEMENT_INFO(kWebMIdTrackOperation, 3, kTrackOperationIds), + LIST_ELEMENT_INFO(kWebMIdTrackCombinePlanes, 4, kTrackCombinePlanesIds), + LIST_ELEMENT_INFO(kWebMIdTrackPlane, 5, kTrackPlaneIds), + LIST_ELEMENT_INFO(kWebMIdJoinBlocks, 4, kJoinBlocksIds), + LIST_ELEMENT_INFO(kWebMIdContentEncodings, 3, kContentEncodingsIds), + LIST_ELEMENT_INFO(kWebMIdContentEncoding, 4, kContentEncodingIds), + LIST_ELEMENT_INFO(kWebMIdContentCompression, 5, kContentCompressionIds), + LIST_ELEMENT_INFO(kWebMIdContentEncryption, 5, kContentEncryptionIds), + LIST_ELEMENT_INFO(kWebMIdContentEncAESSettings, 6, kContentEncAESSettingsIds), + LIST_ELEMENT_INFO(kWebMIdCues, 1, kCuesIds), + LIST_ELEMENT_INFO(kWebMIdCuePoint, 2, kCuePointIds), + LIST_ELEMENT_INFO(kWebMIdCueTrackPositions, 3, kCueTrackPositionsIds), + LIST_ELEMENT_INFO(kWebMIdCueReference, 4, kCueReferenceIds), + LIST_ELEMENT_INFO(kWebMIdAttachments, 1, kAttachmentsIds), + LIST_ELEMENT_INFO(kWebMIdAttachedFile, 2, kAttachedFileIds), + LIST_ELEMENT_INFO(kWebMIdChapters, 1, kChaptersIds), + LIST_ELEMENT_INFO(kWebMIdEditionEntry, 2, kEditionEntryIds), + LIST_ELEMENT_INFO(kWebMIdChapterAtom, 3, kChapterAtomIds), + LIST_ELEMENT_INFO(kWebMIdChapterTrack, 4, kChapterTrackIds), + LIST_ELEMENT_INFO(kWebMIdChapterDisplay, 4, kChapterDisplayIds), + LIST_ELEMENT_INFO(kWebMIdChapProcess, 4, kChapProcessIds), + LIST_ELEMENT_INFO(kWebMIdChapProcessCommand, 5, kChapProcessCommandIds), + LIST_ELEMENT_INFO(kWebMIdTags, 1, kTagsIds), + LIST_ELEMENT_INFO(kWebMIdTag, 2, kTagIds), + LIST_ELEMENT_INFO(kWebMIdTargets, 3, kTargetsIds), + LIST_ELEMENT_INFO(kWebMIdSimpleTag, 3, kSimpleTagIds), +}; + +// Parses an element header id or size field. These fields are variable length +// encoded. The first byte indicates how many bytes the field occupies. +// |buf| - The buffer to parse. +// |size| - The number of bytes in |buf| +// |max_bytes| - The maximum number of bytes the field can be. ID fields +// set this to 4 & element size fields set this to 8. If the +// first byte indicates a larger field size than this it is a +// parser error. +// |mask_first_byte| - For element size fields the field length encoding bits +// need to be masked off. This parameter is true for +// element size fields and is false for ID field values. +// +// Returns: The number of bytes parsed on success. -1 on error. +static int ParseWebMElementHeaderField(const uint8* buf, int size, + int max_bytes, bool mask_first_byte, + int64* num) { + DCHECK(buf); + DCHECK(num); + + if (size < 0) + return -1; + + if (size == 0) + return 0; + + int mask = 0x80; + uint8 ch = buf[0]; + int extra_bytes = -1; + bool all_ones = false; + for (int i = 0; i < max_bytes; ++i) { + if ((ch & mask) != 0) { + mask = ~mask & 0xff; + *num = mask_first_byte ? ch & mask : ch; + all_ones = (ch & mask) == mask; + extra_bytes = i; + break; + } + mask = 0x80 | mask >> 1; + } + + if (extra_bytes == -1) + return -1; + + // Return 0 if we need more data. + if ((1 + extra_bytes) > size) + return 0; + + int bytes_used = 1; + + for (int i = 0; i < extra_bytes; ++i) { + ch = buf[bytes_used++]; + all_ones &= (ch == 0xff); + *num = (*num << 8) | ch; + } + + if (all_ones) + *num = kint64max; + + return bytes_used; +} + +int WebMParseElementHeader(const uint8* buf, int size, + int* id, int64* element_size) { + DCHECK(buf); + DCHECK_GE(size, 0); + DCHECK(id); + DCHECK(element_size); + + if (size == 0) + return 0; + + int64 tmp = 0; + int num_id_bytes = ParseWebMElementHeaderField(buf, size, 4, false, &tmp); + + if (num_id_bytes <= 0) + return num_id_bytes; + + if (tmp == kint64max) + tmp = kWebMReservedId; + + *id = static_cast(tmp); + + int num_size_bytes = ParseWebMElementHeaderField(buf + num_id_bytes, + size - num_id_bytes, + 8, true, &tmp); + + if (num_size_bytes <= 0) + return num_size_bytes; + + if (tmp == kint64max) + tmp = kWebMUnknownSize; + + *element_size = tmp; + DVLOG(3) << "WebMParseElementHeader() : id " << std::hex << *id << std::dec + << " size " << *element_size; + return num_id_bytes + num_size_bytes; +} + +// Finds ElementType for a specific ID. +static ElementType FindIdType(int id, + const ElementIdInfo* id_info, + int id_info_count) { + + // Check for global element IDs that can be anywhere. + if (id == kWebMIdVoid || id == kWebMIdCRC32) + return SKIP; + + for (int i = 0; i < id_info_count; ++i) { + if (id == id_info[i].id_) + return id_info[i].type_; + } + + return UNKNOWN; +} + +// Finds ListElementInfo for a specific ID. +static const ListElementInfo* FindListInfo(int id) { + for (size_t i = 0; i < arraysize(kListElementInfo); ++i) { + if (id == kListElementInfo[i].id_) + return &kListElementInfo[i]; + } + + return NULL; +} + +static int FindListLevel(int id) { + const ListElementInfo* list_info = FindListInfo(id); + if (list_info) + return list_info->level_; + + return -1; +} + +static int ParseUInt(const uint8* buf, int size, int id, + WebMParserClient* client) { + if ((size <= 0) || (size > 8)) + return -1; + + // Read in the big-endian integer. + int64 value = 0; + for (int i = 0; i < size; ++i) + value = (value << 8) | buf[i]; + + if (!client->OnUInt(id, value)) + return -1; + + return size; +} + +static int ParseFloat(const uint8* buf, int size, int id, + WebMParserClient* client) { + + if ((size != 4) && (size != 8)) + return -1; + + double value = -1; + + // Read the bytes from big-endian form into a native endian integer. + int64 tmp = 0; + for (int i = 0; i < size; ++i) + tmp = (tmp << 8) | buf[i]; + + // Use a union to convert the integer bit pattern into a floating point + // number. + if (size == 4) { + union { + int32 src; + float dst; + } tmp2; + tmp2.src = static_cast(tmp); + value = tmp2.dst; + } else if (size == 8) { + union { + int64 src; + double dst; + } tmp2; + tmp2.src = tmp; + value = tmp2.dst; + } else { + return -1; + } + + if (!client->OnFloat(id, value)) + return -1; + + return size; +} + +static int ParseBinary(const uint8* buf, int size, int id, + WebMParserClient* client) { + return client->OnBinary(id, buf, size) ? size : -1; +} + +static int ParseString(const uint8* buf, int size, int id, + WebMParserClient* client) { + const uint8* end = static_cast(memchr(buf, '\0', size)); + int length = (end != NULL) ? static_cast(end - buf) : size; + std::string str(reinterpret_cast(buf), length); + return client->OnString(id, str) ? size : -1; +} + +static int ParseNonListElement(ElementType type, int id, int64 element_size, + const uint8* buf, int size, + WebMParserClient* client) { + DCHECK_GE(size, element_size); + + int result = -1; + switch(type) { + case LIST: + NOTIMPLEMENTED(); + result = -1; + break; + case UINT: + result = ParseUInt(buf, element_size, id, client); + break; + case FLOAT: + result = ParseFloat(buf, element_size, id, client); + break; + case BINARY: + result = ParseBinary(buf, element_size, id, client); + break; + case STRING: + result = ParseString(buf, element_size, id, client); + break; + case SKIP: + result = element_size; + break; + default: + DVLOG(1) << "Unhandled ID type " << type; + return -1; + }; + + DCHECK_LE(result, size); + return result; +} + +WebMParserClient::WebMParserClient() {} +WebMParserClient::~WebMParserClient() {} + +WebMParserClient* WebMParserClient::OnListStart(int id) { + DVLOG(1) << "Unexpected list element start with ID " << std::hex << id; + return NULL; +} + +bool WebMParserClient::OnListEnd(int id) { + DVLOG(1) << "Unexpected list element end with ID " << std::hex << id; + return false; +} + +bool WebMParserClient::OnUInt(int id, int64 val) { + DVLOG(1) << "Unexpected unsigned integer element with ID " << std::hex << id; + return false; +} + +bool WebMParserClient::OnFloat(int id, double val) { + DVLOG(1) << "Unexpected float element with ID " << std::hex << id; + return false; +} + +bool WebMParserClient::OnBinary(int id, const uint8* data, int size) { + DVLOG(1) << "Unexpected binary element with ID " << std::hex << id; + return false; +} + +bool WebMParserClient::OnString(int id, const std::string& str) { + DVLOG(1) << "Unexpected string element with ID " << std::hex << id; + return false; +} + +WebMListParser::WebMListParser(int id, WebMParserClient* client) + : state_(NEED_LIST_HEADER), + root_id_(id), + root_level_(FindListLevel(id)), + root_client_(client) { + DCHECK_GE(root_level_, 0); + DCHECK(client); +} + +WebMListParser::~WebMListParser() {} + +void WebMListParser::Reset() { + ChangeState(NEED_LIST_HEADER); + list_state_stack_.clear(); +} + +int WebMListParser::Parse(const uint8* buf, int size) { + DCHECK(buf); + + if (size < 0 || state_ == PARSE_ERROR || state_ == DONE_PARSING_LIST) + return -1; + + if (size == 0) + return 0; + + const uint8* cur = buf; + int cur_size = size; + int bytes_parsed = 0; + + while (cur_size > 0 && state_ != PARSE_ERROR && state_ != DONE_PARSING_LIST) { + int element_id = 0; + int64 element_size = 0; + int result = WebMParseElementHeader(cur, cur_size, &element_id, + &element_size); + + if (result < 0) + return result; + + if (result == 0) + return bytes_parsed; + + switch(state_) { + case NEED_LIST_HEADER: { + if (element_id != root_id_) { + ChangeState(PARSE_ERROR); + return -1; + } + + // Only allow Segment & Cluster to have an unknown size. + if (element_size == kWebMUnknownSize && + (element_id != kWebMIdSegment) && + (element_id != kWebMIdCluster)) { + ChangeState(PARSE_ERROR); + return -1; + } + + ChangeState(INSIDE_LIST); + if (!OnListStart(root_id_, element_size)) + return -1; + + break; + } + + case INSIDE_LIST: { + int header_size = result; + const uint8* element_data = cur + header_size; + int element_data_size = cur_size - header_size; + + if (element_size < element_data_size) + element_data_size = element_size; + + result = ParseListElement(header_size, element_id, element_size, + element_data, element_data_size); + + DCHECK_LE(result, header_size + element_data_size); + if (result < 0) { + ChangeState(PARSE_ERROR); + return -1; + } + + if (result == 0) + return bytes_parsed; + + break; + } + case DONE_PARSING_LIST: + case PARSE_ERROR: + // Shouldn't be able to get here. + NOTIMPLEMENTED(); + break; + } + + cur += result; + cur_size -= result; + bytes_parsed += result; + } + + return (state_ == PARSE_ERROR) ? -1 : bytes_parsed; +} + +bool WebMListParser::IsParsingComplete() const { + return state_ == DONE_PARSING_LIST; +} + +void WebMListParser::ChangeState(State new_state) { + state_ = new_state; +} + +int WebMListParser::ParseListElement(int header_size, + int id, int64 element_size, + const uint8* data, int size) { + DCHECK_GT(list_state_stack_.size(), 0u); + + ListState& list_state = list_state_stack_.back(); + DCHECK(list_state.element_info_); + + const ListElementInfo* element_info = list_state.element_info_; + ElementType id_type = + FindIdType(id, element_info->id_info_, element_info->id_info_count_); + + // Unexpected ID. + if (id_type == UNKNOWN) { + if (list_state.size_ != kWebMUnknownSize || + !IsSiblingOrAncestor(list_state.id_, id)) { + DVLOG(1) << "No ElementType info for ID 0x" << std::hex << id; + return -1; + } + + // We've reached the end of a list of unknown size. Update the size now that + // we know it and dispatch the end of list calls. + list_state.size_ = list_state.bytes_parsed_; + + if (!OnListEnd()) + return -1; + + // Check to see if all open lists have ended. + if (list_state_stack_.size() == 0) + return 0; + + list_state = list_state_stack_.back(); + } + + // Make sure the whole element can fit inside the current list. + int64 total_element_size = header_size + element_size; + if (list_state.size_ != kWebMUnknownSize && + list_state.size_ < list_state.bytes_parsed_ + total_element_size) { + return -1; + } + + if (id_type == LIST) { + list_state.bytes_parsed_ += header_size; + + if (!OnListStart(id, element_size)) + return -1; + return header_size; + } + + // Make sure we have the entire element before trying to parse a non-list + // element. + if (size < element_size) + return 0; + + int bytes_parsed = ParseNonListElement(id_type, id, element_size, + data, size, list_state.client_); + DCHECK_LE(bytes_parsed, size); + + // Return if an error occurred or we need more data. + // Note: bytes_parsed is 0 for a successful parse of a size 0 element. We + // need to check the element_size to disambiguate the "need more data" case + // from a successful parse. + if (bytes_parsed < 0 || (bytes_parsed == 0 && element_size != 0)) + return bytes_parsed; + + int result = header_size + bytes_parsed; + list_state.bytes_parsed_ += result; + + // See if we have reached the end of the current list. + if (list_state.bytes_parsed_ == list_state.size_) { + if (!OnListEnd()) + return -1; + } + + return result; +} + +bool WebMListParser::OnListStart(int id, int64 size) { + const ListElementInfo* element_info = FindListInfo(id); + if (!element_info) + return false; + + int current_level = root_level_ + list_state_stack_.size() - 1; + if (current_level + 1 != element_info->level_) + return false; + + WebMParserClient* current_list_client = NULL; + if (!list_state_stack_.empty()) { + // Make sure the new list doesn't go past the end of the current list. + ListState current_list_state = list_state_stack_.back(); + if (current_list_state.size_ != kWebMUnknownSize && + current_list_state.size_ < current_list_state.bytes_parsed_ + size) + return false; + current_list_client = current_list_state.client_; + } else { + current_list_client = root_client_; + } + + WebMParserClient* new_list_client = current_list_client->OnListStart(id); + if (!new_list_client) + return false; + + ListState new_list_state = { id, size, 0, element_info, new_list_client }; + list_state_stack_.push_back(new_list_state); + + if (size == 0) + return OnListEnd(); + + return true; +} + +bool WebMListParser::OnListEnd() { + int lists_ended = 0; + for (; !list_state_stack_.empty(); ++lists_ended) { + const ListState& list_state = list_state_stack_.back(); + + if (list_state.bytes_parsed_ != list_state.size_) + break; + + list_state_stack_.pop_back(); + + int64 bytes_parsed = list_state.bytes_parsed_; + WebMParserClient* client = NULL; + if (!list_state_stack_.empty()) { + // Update the bytes_parsed_ for the parent element. + list_state_stack_.back().bytes_parsed_ += bytes_parsed; + client = list_state_stack_.back().client_; + } else { + client = root_client_; + } + + if (!client->OnListEnd(list_state.id_)) + return false; + } + + DCHECK_GE(lists_ended, 1); + + if (list_state_stack_.empty()) + ChangeState(DONE_PARSING_LIST); + + return true; +} + +bool WebMListParser::IsSiblingOrAncestor(int id_a, int id_b) const { + DCHECK((id_a == kWebMIdSegment) || (id_a == kWebMIdCluster)); + + if (id_a == kWebMIdCluster) { + // kWebMIdCluster siblings. + for (size_t i = 0; i < arraysize(kSegmentIds); i++) { + if (kSegmentIds[i].id_ == id_b) + return true; + } + } + + // kWebMIdSegment siblings. + return ((id_b == kWebMIdSegment) || (id_b == kWebMIdEBMLHeader)); +} + +} // namespace media diff --git a/media/webm/webm_parser.h b/media/webm/webm_parser.h new file mode 100644 index 0000000000..68611a85bd --- /dev/null +++ b/media/webm/webm_parser.h @@ -0,0 +1,158 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_WEBM_WEBM_PARSER_H_ +#define MEDIA_WEBM_WEBM_PARSER_H_ + +#include +#include + +#include "base/basictypes.h" +#include "media/base/media_export.h" + +namespace media { + +// Interface for receiving WebM parser events. +// +// Each method is called when an element of the specified type is parsed. +// The ID of the element that was parsed is given along with the value +// stored in the element. List elements generate calls at the start and +// end of the list. Any pointers passed to these methods are only guaranteed +// to be valid for the life of that call. Each method (except for OnListStart) +// returns a bool that indicates whether the parsed data is valid. OnListStart +// returns a pointer to a WebMParserClient object, which should be used to +// handle elements parsed out of the list being started. If false (or NULL by +// OnListStart) is returned then the parse is immediately terminated and an +// error is reported by the parser. +class MEDIA_EXPORT WebMParserClient { + public: + virtual ~WebMParserClient(); + + virtual WebMParserClient* OnListStart(int id); + virtual bool OnListEnd(int id); + virtual bool OnUInt(int id, int64 val); + virtual bool OnFloat(int id, double val); + virtual bool OnBinary(int id, const uint8* data, int size); + virtual bool OnString(int id, const std::string& str); + + protected: + WebMParserClient(); + + DISALLOW_COPY_AND_ASSIGN(WebMParserClient); +}; + +struct ListElementInfo; + +// Parses a WebM list element and all of its children. This +// class supports incremental parsing of the list so Parse() +// can be called multiple times with pieces of the list. +// IsParsingComplete() will return true once the entire list has +// been parsed. +class MEDIA_EXPORT WebMListParser { + public: + // |id| - Element ID of the list we intend to parse. + // |client| - Called as different elements in the list are parsed. + WebMListParser(int id, WebMParserClient* client); + ~WebMListParser(); + + // Resets the state of the parser so it can start parsing a new list. + void Reset(); + + // Parses list data contained in |buf|. + // + // Returns < 0 if the parse fails. + // Returns 0 if more data is needed. + // Returning > 0 indicates success & the number of bytes parsed. + int Parse(const uint8* buf, int size); + + // Returns true if the entire list has been parsed. + bool IsParsingComplete() const; + + private: + enum State { + NEED_LIST_HEADER, + INSIDE_LIST, + DONE_PARSING_LIST, + PARSE_ERROR, + }; + + struct ListState { + int id_; + int64 size_; + int64 bytes_parsed_; + const ListElementInfo* element_info_; + WebMParserClient* client_; + }; + + void ChangeState(State new_state); + + // Parses a single element in the current list. + // + // |header_size| - The size of the element header + // |id| - The ID of the element being parsed. + // |element_size| - The size of the element body. + // |data| - Pointer to the element contents. + // |size| - Number of bytes in |data| + // |client| - Client to pass the parsed data to. + // + // Returns < 0 if the parse fails. + // Returns 0 if more data is needed. + // Returning > 0 indicates success & the number of bytes parsed. + int ParseListElement(int header_size, + int id, int64 element_size, + const uint8* data, int size); + + // Called when starting to parse a new list. + // + // |id| - The ID of the new list. + // |size| - The size of the new list. + // |client| - The client object to notify that a new list is being parsed. + // + // Returns true if this list can be started in the current context. False + // if starting this list causes some sort of parse error. + bool OnListStart(int id, int64 size); + + // Called when the end of the current list has been reached. This may also + // signal the end of the current list's ancestors if the current list happens + // to be at the end of its parent. + // + // Returns true if no errors occurred while ending this list(s). + bool OnListEnd(); + + // Checks to see if |id_b| is a sibling or ancestor of |id_a|. + bool IsSiblingOrAncestor(int id_a, int id_b) const; + + State state_; + + // Element ID passed to the constructor. + const int root_id_; + + // Element level for |root_id_|. Used to verify that elements appear at + // the correct level. + const int root_level_; + + // WebMParserClient to handle the root list. + WebMParserClient* const root_client_; + + // Stack of state for all the lists currently being parsed. Lists are + // added and removed from this stack as they are parsed. + std::vector list_state_stack_; + + DISALLOW_COPY_AND_ASSIGN(WebMListParser); +}; + +// Parses an element header & returns the ID and element size. +// +// Returns < 0 if the parse fails. +// Returns 0 if more data is needed. +// Returning > 0 indicates success & the number of bytes parsed. +// |*id| contains the element ID on success and is undefined otherwise. +// |*element_size| contains the element size on success and is undefined +// otherwise. +int MEDIA_EXPORT WebMParseElementHeader(const uint8* buf, int size, + int* id, int64* element_size); + +} // namespace media + +#endif // MEDIA_WEBM_WEBM_PARSER_H_ diff --git a/media/webm/webm_parser_unittest.cc b/media/webm/webm_parser_unittest.cc new file mode 100644 index 0000000000..cb71fe98bd --- /dev/null +++ b/media/webm/webm_parser_unittest.cc @@ -0,0 +1,395 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/webm/cluster_builder.h" +#include "media/webm/webm_constants.h" +#include "media/webm/webm_parser.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::InSequence; +using ::testing::Return; +using ::testing::ReturnNull; +using ::testing::StrictMock; +using ::testing::_; + +namespace media { + +enum { kBlockCount = 5 }; + +class MockWebMParserClient : public WebMParserClient { + public: + virtual ~MockWebMParserClient() {} + + // WebMParserClient methods. + MOCK_METHOD1(OnListStart, WebMParserClient*(int)); + MOCK_METHOD1(OnListEnd, bool(int)); + MOCK_METHOD2(OnUInt, bool(int, int64)); + MOCK_METHOD2(OnFloat, bool(int, double)); + MOCK_METHOD3(OnBinary, bool(int, const uint8*, int)); + MOCK_METHOD2(OnString, bool(int, const std::string&)); +}; + +class WebMParserTest : public testing::Test { + protected: + StrictMock client_; +}; + +static scoped_ptr CreateCluster(int block_count) { + ClusterBuilder cb; + cb.SetClusterTimecode(0); + + for (int i = 0; i < block_count; i++) { + uint8 data[] = { 0x00 }; + cb.AddSimpleBlock(0, i, 0, data, sizeof(data)); + } + + return cb.Finish(); +} + +static void CreateClusterExpectations(int block_count, + bool is_complete_cluster, + MockWebMParserClient* client) { + + InSequence s; + EXPECT_CALL(*client, OnListStart(kWebMIdCluster)).WillOnce(Return(client)); + EXPECT_CALL(*client, OnUInt(kWebMIdTimecode, 0)) + .WillOnce(Return(true)); + + for (int i = 0; i < block_count; i++) { + EXPECT_CALL(*client, OnBinary(kWebMIdSimpleBlock, _, _)) + .WillOnce(Return(true)); + } + + if (is_complete_cluster) + EXPECT_CALL(*client, OnListEnd(kWebMIdCluster)).WillOnce(Return(true)); +} + +TEST_F(WebMParserTest, EmptyCluster) { + const uint8 kEmptyCluster[] = { + 0x1F, 0x43, 0xB6, 0x75, 0x80 // CLUSTER (size = 0) + }; + int size = sizeof(kEmptyCluster); + + InSequence s; + EXPECT_CALL(client_, OnListStart(kWebMIdCluster)).WillOnce(Return(&client_)); + EXPECT_CALL(client_, OnListEnd(kWebMIdCluster)).WillOnce(Return(true)); + + WebMListParser parser(kWebMIdCluster, &client_); + int result = parser.Parse(kEmptyCluster, size); + EXPECT_EQ(size, result); + EXPECT_TRUE(parser.IsParsingComplete()); +} + +TEST_F(WebMParserTest, EmptyClusterInSegment) { + const uint8 kBuffer[] = { + 0x18, 0x53, 0x80, 0x67, 0x85, // SEGMENT (size = 5) + 0x1F, 0x43, 0xB6, 0x75, 0x80, // CLUSTER (size = 0) + }; + int size = sizeof(kBuffer); + + InSequence s; + EXPECT_CALL(client_, OnListStart(kWebMIdSegment)).WillOnce(Return(&client_)); + EXPECT_CALL(client_, OnListStart(kWebMIdCluster)).WillOnce(Return(&client_)); + EXPECT_CALL(client_, OnListEnd(kWebMIdCluster)).WillOnce(Return(true)); + EXPECT_CALL(client_, OnListEnd(kWebMIdSegment)).WillOnce(Return(true)); + + WebMListParser parser(kWebMIdSegment, &client_); + int result = parser.Parse(kBuffer, size); + EXPECT_EQ(size, result); + EXPECT_TRUE(parser.IsParsingComplete()); +} + +// Test the case where a non-list child element has a size +// that is beyond the end of the parent. +TEST_F(WebMParserTest, ChildNonListLargerThanParent) { + const uint8 kBuffer[] = { + 0x1F, 0x43, 0xB6, 0x75, 0x81, // CLUSTER (size = 1) + 0xE7, 0x81, 0x01, // Timecode (size=1, value=1) + }; + int size = sizeof(kBuffer); + + InSequence s; + EXPECT_CALL(client_, OnListStart(kWebMIdCluster)).WillOnce(Return(&client_)); + + WebMListParser parser(kWebMIdCluster, &client_); + int result = parser.Parse(kBuffer, size); + EXPECT_EQ(-1, result); + EXPECT_FALSE(parser.IsParsingComplete()); +} + +// Test the case where a list child element has a size +// that is beyond the end of the parent. +TEST_F(WebMParserTest, ChildListLargerThanParent) { + const uint8 kBuffer[] = { + 0x18, 0x53, 0x80, 0x67, 0x85, // SEGMENT (size = 5) + 0x1F, 0x43, 0xB6, 0x75, 0x81, 0x11 // CLUSTER (size = 1) + }; + int size = sizeof(kBuffer); + + InSequence s; + EXPECT_CALL(client_, OnListStart(kWebMIdSegment)).WillOnce(Return(&client_)); + + WebMListParser parser(kWebMIdSegment, &client_); + int result = parser.Parse(kBuffer, size); + EXPECT_EQ(-1, result); + EXPECT_FALSE(parser.IsParsingComplete()); +} + +// Expecting to parse a Cluster, but get a Segment. +TEST_F(WebMParserTest, ListIdDoesNotMatch) { + const uint8 kBuffer[] = { + 0x18, 0x53, 0x80, 0x67, 0x80, // SEGMENT (size = 0) + }; + int size = sizeof(kBuffer); + + WebMListParser parser(kWebMIdCluster, &client_); + int result = parser.Parse(kBuffer, size); + EXPECT_EQ(-1, result); + EXPECT_FALSE(parser.IsParsingComplete()); +} + +TEST_F(WebMParserTest, InvalidElementInList) { + const uint8 kBuffer[] = { + 0x18, 0x53, 0x80, 0x67, 0x82, // SEGMENT (size = 2) + 0xAE, 0x80, // TrackEntry (size = 0) + }; + int size = sizeof(kBuffer); + + InSequence s; + EXPECT_CALL(client_, OnListStart(kWebMIdSegment)).WillOnce(Return(&client_)); + + WebMListParser parser(kWebMIdSegment, &client_); + int result = parser.Parse(kBuffer, size); + EXPECT_EQ(-1, result); + EXPECT_FALSE(parser.IsParsingComplete()); +} + +TEST_F(WebMParserTest, VoidAndCRC32InList) { + const uint8 kBuffer[] = { + 0x18, 0x53, 0x80, 0x67, 0x99, // SEGMENT (size = 25) + 0xEC, 0x83, 0x00, 0x00, 0x00, // Void (size = 3) + 0xBF, 0x83, 0x00, 0x00, 0x00, // CRC32 (size = 3) + 0x1F, 0x43, 0xB6, 0x75, 0x8A, // CLUSTER (size = 10) + 0xEC, 0x83, 0x00, 0x00, 0x00, // Void (size = 3) + 0xBF, 0x83, 0x00, 0x00, 0x00, // CRC32 (size = 3) + }; + int size = sizeof(kBuffer); + + InSequence s; + EXPECT_CALL(client_, OnListStart(kWebMIdSegment)).WillOnce(Return(&client_)); + EXPECT_CALL(client_, OnListStart(kWebMIdCluster)).WillOnce(Return(&client_)); + EXPECT_CALL(client_, OnListEnd(kWebMIdCluster)).WillOnce(Return(true)); + EXPECT_CALL(client_, OnListEnd(kWebMIdSegment)).WillOnce(Return(true)); + + WebMListParser parser(kWebMIdSegment, &client_); + int result = parser.Parse(kBuffer, size); + EXPECT_EQ(size, result); + EXPECT_TRUE(parser.IsParsingComplete()); +} + + +TEST_F(WebMParserTest, ParseListElementWithSingleCall) { + scoped_ptr cluster(CreateCluster(kBlockCount)); + CreateClusterExpectations(kBlockCount, true, &client_); + + WebMListParser parser(kWebMIdCluster, &client_); + int result = parser.Parse(cluster->data(), cluster->size()); + EXPECT_EQ(cluster->size(), result); + EXPECT_TRUE(parser.IsParsingComplete()); +} + +TEST_F(WebMParserTest, ParseListElementWithMultipleCalls) { + scoped_ptr cluster(CreateCluster(kBlockCount)); + CreateClusterExpectations(kBlockCount, true, &client_); + + const uint8* data = cluster->data(); + int size = cluster->size(); + int default_parse_size = 3; + WebMListParser parser(kWebMIdCluster, &client_); + int parse_size = std::min(default_parse_size, size); + + while (size > 0) { + int result = parser.Parse(data, parse_size); + ASSERT_GE(result, 0); + ASSERT_LE(result, parse_size); + + if (result == 0) { + // The parser needs more data so increase the parse_size a little. + EXPECT_FALSE(parser.IsParsingComplete()); + parse_size += default_parse_size; + parse_size = std::min(parse_size, size); + continue; + } + + parse_size = default_parse_size; + + data += result; + size -= result; + + EXPECT_EQ((size == 0), parser.IsParsingComplete()); + } + EXPECT_TRUE(parser.IsParsingComplete()); +} + +TEST_F(WebMParserTest, Reset) { + InSequence s; + scoped_ptr cluster(CreateCluster(kBlockCount)); + + // First expect all but the last block. + CreateClusterExpectations(kBlockCount - 1, false, &client_); + + // Now expect all blocks. + CreateClusterExpectations(kBlockCount, true, &client_); + + WebMListParser parser(kWebMIdCluster, &client_); + + // Send slightly less than the full cluster so all but the last block is + // parsed. + int result = parser.Parse(cluster->data(), cluster->size() - 1); + EXPECT_GT(result, 0); + EXPECT_LT(result, cluster->size()); + EXPECT_FALSE(parser.IsParsingComplete()); + + parser.Reset(); + + // Now parse a whole cluster to verify that all the blocks will get parsed. + result = parser.Parse(cluster->data(), cluster->size()); + EXPECT_EQ(result, cluster->size()); + EXPECT_TRUE(parser.IsParsingComplete()); +} + +// Test the case where multiple clients are used for different lists. +TEST_F(WebMParserTest, MultipleClients) { + const uint8 kBuffer[] = { + 0x18, 0x53, 0x80, 0x67, 0x94, // SEGMENT (size = 20) + 0x16, 0x54, 0xAE, 0x6B, 0x85, // TRACKS (size = 5) + 0xAE, 0x83, // TRACKENTRY (size = 3) + 0xD7, 0x81, 0x01, // TRACKNUMBER (size = 1) + 0x1F, 0x43, 0xB6, 0x75, 0x85, // CLUSTER (size = 5) + 0xEC, 0x83, 0x00, 0x00, 0x00, // Void (size = 3) + }; + int size = sizeof(kBuffer); + + StrictMock c1_; + StrictMock c2_; + StrictMock c3_; + + InSequence s; + EXPECT_CALL(client_, OnListStart(kWebMIdSegment)).WillOnce(Return(&c1_)); + EXPECT_CALL(c1_, OnListStart(kWebMIdTracks)).WillOnce(Return(&c2_)); + EXPECT_CALL(c2_, OnListStart(kWebMIdTrackEntry)).WillOnce(Return(&c3_)); + EXPECT_CALL(c3_, OnUInt(kWebMIdTrackNumber, 1)).WillOnce(Return(true)); + EXPECT_CALL(c2_, OnListEnd(kWebMIdTrackEntry)).WillOnce(Return(true)); + EXPECT_CALL(c1_, OnListEnd(kWebMIdTracks)).WillOnce(Return(true)); + EXPECT_CALL(c1_, OnListStart(kWebMIdCluster)).WillOnce(Return(&c2_)); + EXPECT_CALL(c1_, OnListEnd(kWebMIdCluster)).WillOnce(Return(true)); + EXPECT_CALL(client_, OnListEnd(kWebMIdSegment)).WillOnce(Return(true)); + + WebMListParser parser(kWebMIdSegment, &client_); + int result = parser.Parse(kBuffer, size); + EXPECT_EQ(size, result); + EXPECT_TRUE(parser.IsParsingComplete()); +} + +// Test the case where multiple clients are used for different lists. +TEST_F(WebMParserTest, InvalidClient) { + const uint8 kBuffer[] = { + 0x18, 0x53, 0x80, 0x67, 0x85, // SEGMENT (size = 20) + 0x16, 0x54, 0xAE, 0x6B, 0x80, // TRACKS (size = 5) + }; + int size = sizeof(kBuffer); + + InSequence s; + EXPECT_CALL(client_, OnListStart(kWebMIdSegment)).WillOnce(ReturnNull()); + + WebMListParser parser(kWebMIdSegment, &client_); + int result = parser.Parse(kBuffer, size); + EXPECT_EQ(-1, result); + EXPECT_FALSE(parser.IsParsingComplete()); +} + +TEST_F(WebMParserTest, ReservedIds) { + const uint8 k1ByteReservedId[] = { 0xFF, 0x81 }; + const uint8 k2ByteReservedId[] = { 0x7F, 0xFF, 0x81 }; + const uint8 k3ByteReservedId[] = { 0x3F, 0xFF, 0xFF, 0x81 }; + const uint8 k4ByteReservedId[] = { 0x1F, 0xFF, 0xFF, 0xFF, 0x81 }; + const uint8* kBuffers[] = { + k1ByteReservedId, + k2ByteReservedId, + k3ByteReservedId, + k4ByteReservedId + }; + + for (size_t i = 0; i < arraysize(kBuffers); i++) { + int id; + int64 element_size; + int buffer_size = 2 + i; + EXPECT_EQ(buffer_size, WebMParseElementHeader(kBuffers[i], buffer_size, + &id, &element_size)); + EXPECT_EQ(id, kWebMReservedId); + EXPECT_EQ(element_size, 1); + } +} + +TEST_F(WebMParserTest, ReservedSizes) { + const uint8 k1ByteReservedSize[] = { 0xA3, 0xFF }; + const uint8 k2ByteReservedSize[] = { 0xA3, 0x7F, 0xFF }; + const uint8 k3ByteReservedSize[] = { 0xA3, 0x3F, 0xFF, 0xFF }; + const uint8 k4ByteReservedSize[] = { 0xA3, 0x1F, 0xFF, 0xFF, 0xFF }; + const uint8 k5ByteReservedSize[] = { 0xA3, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF }; + const uint8 k6ByteReservedSize[] = { 0xA3, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF }; + const uint8 k7ByteReservedSize[] = { 0xA3, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF }; + const uint8 k8ByteReservedSize[] = { 0xA3, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF }; + const uint8* kBuffers[] = { + k1ByteReservedSize, + k2ByteReservedSize, + k3ByteReservedSize, + k4ByteReservedSize, + k5ByteReservedSize, + k6ByteReservedSize, + k7ByteReservedSize, + k8ByteReservedSize + }; + + for (size_t i = 0; i < arraysize(kBuffers); i++) { + int id; + int64 element_size; + int buffer_size = 2 + i; + EXPECT_EQ(buffer_size, WebMParseElementHeader(kBuffers[i], buffer_size, + &id, &element_size)); + EXPECT_EQ(id, 0xA3); + EXPECT_EQ(element_size, kWebMUnknownSize); + } +} + +TEST_F(WebMParserTest, ZeroPaddedStrings) { + const uint8 kBuffer[] = { + 0x1A, 0x45, 0xDF, 0xA3, 0x91, // EBMLHEADER (size = 17) + 0x42, 0x82, 0x80, // DocType (size = 0) + 0x42, 0x82, 0x81, 0x00, // DocType (size = 1) "" + 0x42, 0x82, 0x81, 'a', // DocType (size = 1) "a" + 0x42, 0x82, 0x83, 'a', 0x00, 0x00 // DocType (size = 3) "a" + }; + int size = sizeof(kBuffer); + + InSequence s; + EXPECT_CALL(client_, OnListStart(kWebMIdEBMLHeader)) + .WillOnce(Return(&client_)); + EXPECT_CALL(client_, OnString(kWebMIdDocType, "")).WillOnce(Return(true)); + EXPECT_CALL(client_, OnString(kWebMIdDocType, "")).WillOnce(Return(true)); + EXPECT_CALL(client_, OnString(kWebMIdDocType, "a")).WillOnce(Return(true)); + EXPECT_CALL(client_, OnString(kWebMIdDocType, "a")).WillOnce(Return(true)); + EXPECT_CALL(client_, OnListEnd(kWebMIdEBMLHeader)).WillOnce(Return(true)); + + WebMListParser parser(kWebMIdEBMLHeader, &client_); + int result = parser.Parse(kBuffer, size); + EXPECT_EQ(size, result); + EXPECT_TRUE(parser.IsParsingComplete()); +} + +} // namespace media diff --git a/media/webm/webm_stream_parser.cc b/media/webm/webm_stream_parser.cc new file mode 100644 index 0000000000..796a1b3b09 --- /dev/null +++ b/media/webm/webm_stream_parser.cc @@ -0,0 +1,329 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/webm/webm_stream_parser.h" + +#include + +#include "base/callback.h" +#include "base/logging.h" +#include "base/stl_util.h" +#include "media/webm/webm_cluster_parser.h" +#include "media/webm/webm_constants.h" +#include "media/webm/webm_content_encodings.h" +#include "media/webm/webm_crypto_helpers.h" +#include "media/webm/webm_info_parser.h" +#include "media/webm/webm_tracks_parser.h" + +namespace media { + +WebMStreamParser::WebMStreamParser() + : state_(kWaitingForInit), + waiting_for_buffers_(false) { +} + +WebMStreamParser::~WebMStreamParser() { + STLDeleteValues(&text_track_map_); +} + +void WebMStreamParser::Init(const InitCB& init_cb, + const NewConfigCB& config_cb, + const NewBuffersCB& new_buffers_cb, + const NewTextBuffersCB& text_cb, + const NeedKeyCB& need_key_cb, + const AddTextTrackCB& add_text_track_cb, + const NewMediaSegmentCB& new_segment_cb, + const base::Closure& end_of_segment_cb, + const LogCB& log_cb) { + DCHECK_EQ(state_, kWaitingForInit); + DCHECK(init_cb_.is_null()); + DCHECK(!init_cb.is_null()); + DCHECK(!config_cb.is_null()); + DCHECK(!new_buffers_cb.is_null()); + DCHECK(!text_cb.is_null()); + DCHECK(!need_key_cb.is_null()); + DCHECK(!new_segment_cb.is_null()); + DCHECK(!end_of_segment_cb.is_null()); + + ChangeState(kParsingHeaders); + init_cb_ = init_cb; + config_cb_ = config_cb; + new_buffers_cb_ = new_buffers_cb; + text_cb_ = text_cb; + need_key_cb_ = need_key_cb; + add_text_track_cb_ = add_text_track_cb; + new_segment_cb_ = new_segment_cb; + end_of_segment_cb_ = end_of_segment_cb; + log_cb_ = log_cb; +} + +void WebMStreamParser::Flush() { + DCHECK_NE(state_, kWaitingForInit); + + byte_queue_.Reset(); + + if (state_ != kParsingClusters) + return; + + cluster_parser_->Reset(); +} + +bool WebMStreamParser::Parse(const uint8* buf, int size) { + DCHECK_NE(state_, kWaitingForInit); + + if (state_ == kError) + return false; + + byte_queue_.Push(buf, size); + + int result = 0; + int bytes_parsed = 0; + const uint8* cur = NULL; + int cur_size = 0; + + byte_queue_.Peek(&cur, &cur_size); + while (cur_size > 0) { + State oldState = state_; + switch (state_) { + case kParsingHeaders: + result = ParseInfoAndTracks(cur, cur_size); + break; + + case kParsingClusters: + result = ParseCluster(cur, cur_size); + break; + + case kWaitingForInit: + case kError: + return false; + } + + if (result < 0) { + ChangeState(kError); + return false; + } + + if (state_ == oldState && result == 0) + break; + + DCHECK_GE(result, 0); + cur += result; + cur_size -= result; + bytes_parsed += result; + } + + byte_queue_.Pop(bytes_parsed); + return true; +} + +void WebMStreamParser::ChangeState(State new_state) { + DVLOG(1) << "ChangeState() : " << state_ << " -> " << new_state; + state_ = new_state; +} + +int WebMStreamParser::ParseInfoAndTracks(const uint8* data, int size) { + DVLOG(2) << "ParseInfoAndTracks()"; + DCHECK(data); + DCHECK_GT(size, 0); + + const uint8* cur = data; + int cur_size = size; + int bytes_parsed = 0; + + int id; + int64 element_size; + int result = WebMParseElementHeader(cur, cur_size, &id, &element_size); + + if (result <= 0) + return result; + + switch (id) { + case kWebMIdEBMLHeader: + case kWebMIdSeekHead: + case kWebMIdVoid: + case kWebMIdCRC32: + case kWebMIdCues: + case kWebMIdChapters: + if (cur_size < (result + element_size)) { + // We don't have the whole element yet. Signal we need more data. + return 0; + } + // Skip the element. + return result + element_size; + break; + case kWebMIdSegment: + // Just consume the segment header. + return result; + break; + case kWebMIdInfo: + // We've found the element we are looking for. + break; + default: { + MEDIA_LOG(log_cb_) << "Unexpected element ID 0x" << std::hex << id; + return -1; + } + } + + WebMInfoParser info_parser; + result = info_parser.Parse(cur, cur_size); + + if (result <= 0) + return result; + + cur += result; + cur_size -= result; + bytes_parsed += result; + + WebMTracksParser tracks_parser(log_cb_, add_text_track_cb_.is_null()); + result = tracks_parser.Parse(cur, cur_size); + + if (result <= 0) + return result; + + bytes_parsed += result; + + base::TimeDelta duration = kInfiniteDuration(); + + if (info_parser.duration() > 0) { + double mult = info_parser.timecode_scale() / 1000.0; + int64 duration_in_us = info_parser.duration() * mult; + duration = base::TimeDelta::FromMicroseconds(duration_in_us); + } + + const AudioDecoderConfig& audio_config = tracks_parser.audio_decoder_config(); + if (audio_config.is_encrypted()) + FireNeedKey(tracks_parser.audio_encryption_key_id()); + + const VideoDecoderConfig& video_config = tracks_parser.video_decoder_config(); + if (video_config.is_encrypted()) + FireNeedKey(tracks_parser.video_encryption_key_id()); + + if (!config_cb_.Run(audio_config, video_config)) { + DVLOG(1) << "New config data isn't allowed."; + return -1; + } + + typedef WebMTracksParser::TextTracks TextTracks; + const TextTracks& text_tracks = tracks_parser.text_tracks(); + + for (TextTracks::const_iterator itr = text_tracks.begin(); + itr != text_tracks.end(); ++itr) { + const WebMTracksParser::TextTrackInfo& text_track_info = itr->second; + + // TODO(matthewjheaney): verify that WebVTT uses ISO 639-2 for lang + scoped_ptr text_track = + add_text_track_cb_.Run(text_track_info.kind, + text_track_info.name, + text_track_info.language); + + // Assume ownership of pointer, and cache the text track object, for use + // later when we have text track buffers. (The text track objects are + // deallocated in the dtor for this class.) + + if (text_track) + text_track_map_.insert(std::make_pair(itr->first, text_track.release())); + } + + cluster_parser_.reset(new WebMClusterParser( + info_parser.timecode_scale(), + tracks_parser.audio_track_num(), + tracks_parser.video_track_num(), + text_tracks, + tracks_parser.ignored_tracks(), + tracks_parser.audio_encryption_key_id(), + tracks_parser.video_encryption_key_id(), + log_cb_)); + + ChangeState(kParsingClusters); + + if (!init_cb_.is_null()) { + init_cb_.Run(true, duration); + init_cb_.Reset(); + } + + return bytes_parsed; +} + +int WebMStreamParser::ParseCluster(const uint8* data, int size) { + if (!cluster_parser_) + return -1; + + int id; + int64 element_size; + int result = WebMParseElementHeader(data, size, &id, &element_size); + + if (result <= 0) + return result; + + if (id == kWebMIdCluster) + waiting_for_buffers_ = true; + + // TODO(matthewjheaney): implement support for chapters + if (id == kWebMIdCues || id == kWebMIdChapters) { + if (size < (result + element_size)) { + // We don't have the whole element yet. Signal we need more data. + return 0; + } + // Skip the element. + return result + element_size; + } + + if (id == kWebMIdEBMLHeader) { + ChangeState(kParsingHeaders); + return 0; + } + + int bytes_parsed = cluster_parser_->Parse(data, size); + + if (bytes_parsed <= 0) + return bytes_parsed; + + const BufferQueue& audio_buffers = cluster_parser_->audio_buffers(); + const BufferQueue& video_buffers = cluster_parser_->video_buffers(); + bool cluster_ended = cluster_parser_->cluster_ended(); + + if (waiting_for_buffers_ && + cluster_parser_->cluster_start_time() != kNoTimestamp()) { + new_segment_cb_.Run(); + waiting_for_buffers_ = false; + } + + if ((!audio_buffers.empty() || !video_buffers.empty()) && + !new_buffers_cb_.Run(audio_buffers, video_buffers)) { + return -1; + } + + WebMClusterParser::TextTrackIterator text_track_iter = + cluster_parser_->CreateTextTrackIterator(); + + int text_track_num; + const BufferQueue* text_buffers; + + while (text_track_iter(&text_track_num, &text_buffers)) { + TextTrackMap::iterator find_result = text_track_map_.find(text_track_num); + + if (find_result == text_track_map_.end()) + continue; + + TextTrack* const text_track = find_result->second; + + if (!text_buffers->empty() && !text_cb_.Run(text_track, *text_buffers)) + return -1; + } + + if (cluster_ended) + end_of_segment_cb_.Run(); + + return bytes_parsed; +} + +void WebMStreamParser::FireNeedKey(const std::string& key_id) { + int key_id_size = key_id.size(); + DCHECK_GT(key_id_size, 0); + scoped_ptr key_id_array(new uint8[key_id_size]); + memcpy(key_id_array.get(), key_id.data(), key_id_size); + need_key_cb_.Run(kWebMEncryptInitDataType, key_id_array.Pass(), key_id_size); +} + +} // namespace media diff --git a/media/webm/webm_stream_parser.h b/media/webm/webm_stream_parser.h new file mode 100644 index 0000000000..9c3a6d544f --- /dev/null +++ b/media/webm/webm_stream_parser.h @@ -0,0 +1,98 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_WEBM_WEBM_STREAM_PARSER_H_ +#define MEDIA_WEBM_WEBM_STREAM_PARSER_H_ + +#include + +#include "base/callback_forward.h" +#include "base/memory/ref_counted.h" +#include "media/base/audio_decoder_config.h" +#include "media/base/buffers.h" +#include "media/base/byte_queue.h" +#include "media/base/stream_parser.h" +#include "media/base/video_decoder_config.h" + +namespace media { + +class WebMClusterParser; + +class WebMStreamParser : public StreamParser { + public: + WebMStreamParser(); + virtual ~WebMStreamParser(); + + // StreamParser implementation. + virtual void Init(const InitCB& init_cb, const NewConfigCB& config_cb, + const NewBuffersCB& new_buffers_cb, + const NewTextBuffersCB& text_cb, + const NeedKeyCB& need_key_cb, + const AddTextTrackCB& add_text_track_cb, + const NewMediaSegmentCB& new_segment_cb, + const base::Closure& end_of_segment_cb, + const LogCB& log_cb) OVERRIDE; + virtual void Flush() OVERRIDE; + virtual bool Parse(const uint8* buf, int size) OVERRIDE; + + private: + enum State { + kWaitingForInit, + kParsingHeaders, + kParsingClusters, + kError + }; + + void ChangeState(State new_state); + + // Parses WebM Header, Info, Tracks elements. It also skips other level 1 + // elements that are not used right now. Once the Info & Tracks elements have + // been parsed, this method will transition the parser from PARSING_HEADERS to + // PARSING_CLUSTERS. + // + // Returns < 0 if the parse fails. + // Returns 0 if more data is needed. + // Returning > 0 indicates success & the number of bytes parsed. + int ParseInfoAndTracks(const uint8* data, int size); + + // Incrementally parses WebM cluster elements. This method also skips + // CUES elements if they are encountered since we currently don't use the + // data in these elements. + // + // Returns < 0 if the parse fails. + // Returns 0 if more data is needed. + // Returning > 0 indicates success & the number of bytes parsed. + int ParseCluster(const uint8* data, int size); + + // Fire needkey event through the |need_key_cb_|. + void FireNeedKey(const std::string& key_id); + + State state_; + InitCB init_cb_; + NewConfigCB config_cb_; + NewBuffersCB new_buffers_cb_; + NewTextBuffersCB text_cb_; + NeedKeyCB need_key_cb_; + AddTextTrackCB add_text_track_cb_; + + typedef std::map TextTrackMap; + TextTrackMap text_track_map_; + + NewMediaSegmentCB new_segment_cb_; + base::Closure end_of_segment_cb_; + LogCB log_cb_; + + // True if a new cluster id has been seen, but no audio or video buffers have + // been parsed yet. + bool waiting_for_buffers_; + + scoped_ptr cluster_parser_; + ByteQueue byte_queue_; + + DISALLOW_COPY_AND_ASSIGN(WebMStreamParser); +}; + +} // namespace media + +#endif // MEDIA_WEBM_WEBM_STREAM_PARSER_H_ diff --git a/media/webm/webm_tracks_parser.cc b/media/webm/webm_tracks_parser.cc new file mode 100644 index 0000000000..67bac044e4 --- /dev/null +++ b/media/webm/webm_tracks_parser.cc @@ -0,0 +1,284 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/webm/webm_tracks_parser.h" + +#include "base/logging.h" +#include "base/strings/string_util.h" +#include "media/base/buffers.h" +#include "media/webm/webm_constants.h" +#include "media/webm/webm_content_encodings.h" + +namespace media { + +static TextKind CodecIdToTextKind(const std::string& codec_id) { + if (codec_id == kWebMCodecSubtitles) + return kTextSubtitles; + + if (codec_id == kWebMCodecCaptions) + return kTextCaptions; + + if (codec_id == kWebMCodecDescriptions) + return kTextDescriptions; + + if (codec_id == kWebMCodecMetadata) + return kTextMetadata; + + return kTextNone; +} + +WebMTracksParser::WebMTracksParser(const LogCB& log_cb, bool ignore_text_tracks) + : track_type_(-1), + track_num_(-1), + audio_track_num_(-1), + video_track_num_(-1), + ignore_text_tracks_(ignore_text_tracks), + log_cb_(log_cb), + audio_client_(log_cb), + video_client_(log_cb) { +} + +WebMTracksParser::~WebMTracksParser() {} + +int WebMTracksParser::Parse(const uint8* buf, int size) { + track_type_ =-1; + track_num_ = -1; + track_name_.clear(); + track_language_.clear(); + audio_track_num_ = -1; + audio_decoder_config_ = AudioDecoderConfig(); + video_track_num_ = -1; + video_decoder_config_ = VideoDecoderConfig(); + text_tracks_.clear(); + ignored_tracks_.clear(); + + WebMListParser parser(kWebMIdTracks, this); + int result = parser.Parse(buf, size); + + if (result <= 0) + return result; + + // For now we do all or nothing parsing. + return parser.IsParsingComplete() ? result : 0; +} + +WebMParserClient* WebMTracksParser::OnListStart(int id) { + if (id == kWebMIdContentEncodings) { + DCHECK(!track_content_encodings_client_.get()); + track_content_encodings_client_.reset( + new WebMContentEncodingsClient(log_cb_)); + return track_content_encodings_client_->OnListStart(id); + } + + if (id == kWebMIdTrackEntry) { + track_type_ = -1; + track_num_ = -1; + track_name_.clear(); + track_language_.clear(); + codec_id_ = ""; + codec_private_.clear(); + audio_client_.Reset(); + video_client_.Reset(); + return this; + } + + if (id == kWebMIdAudio) + return &audio_client_; + + if (id == kWebMIdVideo) + return &video_client_; + + return this; +} + +bool WebMTracksParser::OnListEnd(int id) { + if (id == kWebMIdContentEncodings) { + DCHECK(track_content_encodings_client_.get()); + return track_content_encodings_client_->OnListEnd(id); + } + + if (id == kWebMIdTrackEntry) { + if (track_type_ == -1 || track_num_ == -1) { + MEDIA_LOG(log_cb_) << "Missing TrackEntry data for " + << " TrackType " << track_type_ + << " TrackNum " << track_num_; + return false; + } + + if (track_type_ != kWebMTrackTypeAudio && + track_type_ != kWebMTrackTypeVideo && + track_type_ != kWebMTrackTypeSubtitlesOrCaptions && + track_type_ != kWebMTrackTypeDescriptionsOrMetadata) { + MEDIA_LOG(log_cb_) << "Unexpected TrackType " << track_type_; + return false; + } + + TextKind text_track_kind = kTextNone; + if (track_type_ == kWebMTrackTypeSubtitlesOrCaptions) { + text_track_kind = CodecIdToTextKind(codec_id_); + if (text_track_kind == kTextNone) { + MEDIA_LOG(log_cb_) << "Missing TrackEntry CodecID" + << " TrackNum " << track_num_; + return false; + } + + if (text_track_kind != kTextSubtitles && + text_track_kind != kTextCaptions) { + MEDIA_LOG(log_cb_) << "Wrong TrackEntry CodecID" + << " TrackNum " << track_num_; + return false; + } + } else if (track_type_ == kWebMTrackTypeDescriptionsOrMetadata) { + text_track_kind = CodecIdToTextKind(codec_id_); + if (text_track_kind == kTextNone) { + MEDIA_LOG(log_cb_) << "Missing TrackEntry CodecID" + << " TrackNum " << track_num_; + return false; + } + + if (text_track_kind != kTextDescriptions && + text_track_kind != kTextMetadata) { + MEDIA_LOG(log_cb_) << "Wrong TrackEntry CodecID" + << " TrackNum " << track_num_; + return false; + } + } + + std::string encryption_key_id; + if (track_content_encodings_client_) { + DCHECK(!track_content_encodings_client_->content_encodings().empty()); + // If we have multiple ContentEncoding in one track. Always choose the + // key id in the first ContentEncoding as the key id of the track. + encryption_key_id = track_content_encodings_client_-> + content_encodings()[0]->encryption_key_id(); + } + + if (track_type_ == kWebMTrackTypeAudio) { + if (audio_track_num_ == -1) { + audio_track_num_ = track_num_; + audio_encryption_key_id_ = encryption_key_id; + + DCHECK(!audio_decoder_config_.IsValidConfig()); + if (!audio_client_.InitializeConfig( + codec_id_, codec_private_, !audio_encryption_key_id_.empty(), + &audio_decoder_config_)) { + return false; + } + } else { + MEDIA_LOG(log_cb_) << "Ignoring audio track " << track_num_; + ignored_tracks_.insert(track_num_); + } + } else if (track_type_ == kWebMTrackTypeVideo) { + if (video_track_num_ == -1) { + video_track_num_ = track_num_; + video_encryption_key_id_ = encryption_key_id; + + DCHECK(!video_decoder_config_.IsValidConfig()); + if (!video_client_.InitializeConfig( + codec_id_, codec_private_, !video_encryption_key_id_.empty(), + &video_decoder_config_)) { + return false; + } + } else { + MEDIA_LOG(log_cb_) << "Ignoring video track " << track_num_; + ignored_tracks_.insert(track_num_); + } + } else if (track_type_ == kWebMTrackTypeSubtitlesOrCaptions || + track_type_ == kWebMTrackTypeDescriptionsOrMetadata) { + if (ignore_text_tracks_) { + MEDIA_LOG(log_cb_) << "Ignoring text track " << track_num_; + ignored_tracks_.insert(track_num_); + } else { + TextTrackInfo& text_track_info = text_tracks_[track_num_]; + text_track_info.kind = text_track_kind; + text_track_info.name = track_name_; + text_track_info.language = track_language_; + } + } else { + MEDIA_LOG(log_cb_) << "Unexpected TrackType " << track_type_; + return false; + } + + track_type_ = -1; + track_num_ = -1; + track_name_.clear(); + track_language_.clear(); + codec_id_ = ""; + codec_private_.clear(); + track_content_encodings_client_.reset(); + + audio_client_.Reset(); + video_client_.Reset(); + return true; + } + + return true; +} + +bool WebMTracksParser::OnUInt(int id, int64 val) { + int64* dst = NULL; + + switch (id) { + case kWebMIdTrackNumber: + dst = &track_num_; + break; + case kWebMIdTrackType: + dst = &track_type_; + break; + default: + return true; + } + + if (*dst != -1) { + MEDIA_LOG(log_cb_) << "Multiple values for id " << std::hex << id + << " specified"; + return false; + } + + *dst = val; + return true; +} + +bool WebMTracksParser::OnFloat(int id, double val) { + return true; +} + +bool WebMTracksParser::OnBinary(int id, const uint8* data, int size) { + if (id == kWebMIdCodecPrivate) { + if (!codec_private_.empty()) { + MEDIA_LOG(log_cb_) << "Multiple CodecPrivate fields in a track."; + return false; + } + + codec_private_.assign(data, data + size); + return true; + } + return true; +} + +bool WebMTracksParser::OnString(int id, const std::string& str) { + if (id == kWebMIdCodecID) { + if (!codec_id_.empty()) { + MEDIA_LOG(log_cb_) << "Multiple CodecID fields in a track"; + return false; + } + + codec_id_ = str; + return true; + } + + if (id == kWebMIdName) { + track_name_ = str; + return true; + } + + if (id == kWebMIdLanguage) { + track_language_ = str; + return true; + } + + return true; +} + +} // namespace media diff --git a/media/webm/webm_tracks_parser.h b/media/webm/webm_tracks_parser.h new file mode 100644 index 0000000000..81588e4b51 --- /dev/null +++ b/media/webm/webm_tracks_parser.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 MEDIA_WEBM_WEBM_TRACKS_PARSER_H_ +#define MEDIA_WEBM_WEBM_TRACKS_PARSER_H_ + +#include +#include +#include +#include + +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "media/base/audio_decoder_config.h" +#include "media/base/media_log.h" +#include "media/base/text_track.h" +#include "media/base/video_decoder_config.h" +#include "media/webm/webm_audio_client.h" +#include "media/webm/webm_content_encodings_client.h" +#include "media/webm/webm_parser.h" +#include "media/webm/webm_video_client.h" + +namespace media { + +// Parser for WebM Tracks element. +class MEDIA_EXPORT WebMTracksParser : public WebMParserClient { + public: + explicit WebMTracksParser(const LogCB& log_cb, bool ignore_text_tracks); + virtual ~WebMTracksParser(); + + // Parses a WebM Tracks element in |buf|. + // + // Returns -1 if the parse fails. + // Returns 0 if more data is needed. + // Returns the number of bytes parsed on success. + int Parse(const uint8* buf, int size); + + int64 audio_track_num() const { return audio_track_num_; } + int64 video_track_num() const { return video_track_num_; } + const std::set& ignored_tracks() const { return ignored_tracks_; } + + const std::string& audio_encryption_key_id() const { + return audio_encryption_key_id_; + } + + const AudioDecoderConfig& audio_decoder_config() { + return audio_decoder_config_; + } + + const std::string& video_encryption_key_id() const { + return video_encryption_key_id_; + } + + const VideoDecoderConfig& video_decoder_config() { + return video_decoder_config_; + } + + struct TextTrackInfo { + TextKind kind; + std::string name; + std::string language; + }; + + typedef std::map TextTracks; + + const TextTracks& text_tracks() const { + return text_tracks_; + } + + private: + // WebMParserClient implementation. + virtual WebMParserClient* OnListStart(int id) OVERRIDE; + virtual bool OnListEnd(int id) OVERRIDE; + virtual bool OnUInt(int id, int64 val) OVERRIDE; + virtual bool OnFloat(int id, double val) OVERRIDE; + virtual bool OnBinary(int id, const uint8* data, int size) OVERRIDE; + virtual bool OnString(int id, const std::string& str) OVERRIDE; + + int64 track_type_; + int64 track_num_; + std::string track_name_; + std::string track_language_; + std::string codec_id_; + std::vector codec_private_; + scoped_ptr track_content_encodings_client_; + + int64 audio_track_num_; + int64 video_track_num_; + bool ignore_text_tracks_; + TextTracks text_tracks_; + std::set ignored_tracks_; + std::string audio_encryption_key_id_; + std::string video_encryption_key_id_; + LogCB log_cb_; + + WebMAudioClient audio_client_; + AudioDecoderConfig audio_decoder_config_; + + WebMVideoClient video_client_; + VideoDecoderConfig video_decoder_config_; + + DISALLOW_COPY_AND_ASSIGN(WebMTracksParser); +}; + +} // namespace media + +#endif // MEDIA_WEBM_WEBM_TRACKS_PARSER_H_ diff --git a/media/webm/webm_tracks_parser_unittest.cc b/media/webm/webm_tracks_parser_unittest.cc new file mode 100644 index 0000000000..1ba3111789 --- /dev/null +++ b/media/webm/webm_tracks_parser_unittest.cc @@ -0,0 +1,125 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/logging.h" +#include "media/webm/tracks_builder.h" +#include "media/webm/webm_constants.h" +#include "media/webm/webm_tracks_parser.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::InSequence; +using ::testing::Return; +using ::testing::_; + +namespace media { + +static const int kTypeSubtitlesOrCaptions = 0x11; +static const int kTypeDescriptionsOrMetadata = 0x21; + +class WebMTracksParserTest : public testing::Test { + public: + WebMTracksParserTest() {} +}; + +static void VerifyTextTrackInfo(const uint8* buffer, + int buffer_size, + TextKind text_kind, + const std::string& name, + const std::string& language) { + scoped_ptr parser(new WebMTracksParser(LogCB(), false)); + + int result = parser->Parse(buffer, buffer_size); + EXPECT_GT(result, 0); + EXPECT_EQ(result, buffer_size); + + const WebMTracksParser::TextTracks& text_tracks = parser->text_tracks(); + EXPECT_EQ(text_tracks.size(), WebMTracksParser::TextTracks::size_type(1)); + + const WebMTracksParser::TextTracks::const_iterator itr = text_tracks.begin(); + EXPECT_EQ(itr->first, 1); // track num + + const WebMTracksParser::TextTrackInfo& info = itr->second; + EXPECT_EQ(info.kind, text_kind); + EXPECT_TRUE(info.name == name); + EXPECT_TRUE(info.language == language); +} + +TEST_F(WebMTracksParserTest, SubtitleNoNameNoLang) { + InSequence s; + + TracksBuilder tb; + tb.AddTrack(1, kWebMTrackTypeSubtitlesOrCaptions, + kWebMCodecSubtitles, "", ""); + + const std::vector buf = tb.Finish(); + VerifyTextTrackInfo(&buf[0], buf.size(), kTextSubtitles, "", ""); +} + +TEST_F(WebMTracksParserTest, SubtitleYesNameNoLang) { + InSequence s; + + TracksBuilder tb; + tb.AddTrack(1, kWebMTrackTypeSubtitlesOrCaptions, + kWebMCodecSubtitles, "Spock", ""); + + const std::vector buf = tb.Finish(); + VerifyTextTrackInfo(&buf[0], buf.size(), kTextSubtitles, "Spock", ""); +} + +TEST_F(WebMTracksParserTest, SubtitleNoNameYesLang) { + InSequence s; + + TracksBuilder tb; + tb.AddTrack(1, kWebMTrackTypeSubtitlesOrCaptions, + kWebMCodecSubtitles, "", "eng"); + + const std::vector buf = tb.Finish(); + VerifyTextTrackInfo(&buf[0], buf.size(), kTextSubtitles, "", "eng"); +} + +TEST_F(WebMTracksParserTest, SubtitleYesNameYesLang) { + InSequence s; + + TracksBuilder tb; + tb.AddTrack(1, kWebMTrackTypeSubtitlesOrCaptions, + kWebMCodecSubtitles, "Picard", "fre"); + + const std::vector buf = tb.Finish(); + VerifyTextTrackInfo(&buf[0], buf.size(), kTextSubtitles, "Picard", "fre"); +} + +TEST_F(WebMTracksParserTest, IgnoringTextTracks) { + InSequence s; + + TracksBuilder tb; + tb.AddTrack(1, kWebMTrackTypeSubtitlesOrCaptions, + kWebMCodecSubtitles, "Subtitles", "fre"); + tb.AddTrack(2, kWebMTrackTypeSubtitlesOrCaptions, + kWebMCodecSubtitles, "Commentary", "fre"); + + const std::vector buf = tb.Finish(); + scoped_ptr parser(new WebMTracksParser(LogCB(), true)); + + int result = parser->Parse(&buf[0], buf.size()); + EXPECT_GT(result, 0); + EXPECT_EQ(result, static_cast(buf.size())); + + EXPECT_EQ(parser->text_tracks().size(), 0u); + + const std::set& ignored_tracks = parser->ignored_tracks(); + EXPECT_TRUE(ignored_tracks.find(1) != ignored_tracks.end()); + EXPECT_TRUE(ignored_tracks.find(2) != ignored_tracks.end()); + + // Test again w/o ignoring the test tracks. + parser.reset(new WebMTracksParser(LogCB(), false)); + + result = parser->Parse(&buf[0], buf.size()); + EXPECT_GT(result, 0); + + EXPECT_EQ(parser->ignored_tracks().size(), 0u); + EXPECT_EQ(parser->text_tracks().size(), 2u); +} + +} // namespace media diff --git a/media/webm/webm_video_client.cc b/media/webm/webm_video_client.cc new file mode 100644 index 0000000000..1d0cbcb2ac --- /dev/null +++ b/media/webm/webm_video_client.cc @@ -0,0 +1,163 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/webm/webm_video_client.h" + +#include "media/base/video_decoder_config.h" +#include "media/webm/webm_constants.h" + +namespace media { + +WebMVideoClient::WebMVideoClient(const LogCB& log_cb) + : log_cb_(log_cb) { + Reset(); +} + +WebMVideoClient::~WebMVideoClient() { +} + +void WebMVideoClient::Reset() { + pixel_width_ = -1; + pixel_height_ = -1; + crop_bottom_ = -1; + crop_top_ = -1; + crop_left_ = -1; + crop_right_ = -1; + display_width_ = -1; + display_height_ = -1; + display_unit_ = -1; + alpha_mode_ = -1; +} + +bool WebMVideoClient::InitializeConfig( + const std::string& codec_id, const std::vector& codec_private, + bool is_encrypted, VideoDecoderConfig* config) { + DCHECK(config); + + VideoCodec video_codec = kUnknownVideoCodec; + VideoCodecProfile profile = VIDEO_CODEC_PROFILE_UNKNOWN; + if (codec_id == "V_VP8") { + video_codec = kCodecVP8; + profile = VP8PROFILE_MAIN; + } else if (codec_id == "V_VP9") { + video_codec = kCodecVP9; + profile = VP9PROFILE_MAIN; + } else { + MEDIA_LOG(log_cb_) << "Unsupported video codec_id " << codec_id; + return false; + } + + VideoFrame::Format format = + (alpha_mode_ == 1) ? VideoFrame::YV12A : VideoFrame::YV12; + + if (pixel_width_ <= 0 || pixel_height_ <= 0) + return false; + + // Set crop and display unit defaults if these elements are not present. + if (crop_bottom_ == -1) + crop_bottom_ = 0; + + if (crop_top_ == -1) + crop_top_ = 0; + + if (crop_left_ == -1) + crop_left_ = 0; + + if (crop_right_ == -1) + crop_right_ = 0; + + if (display_unit_ == -1) + display_unit_ = 0; + + gfx::Size coded_size(pixel_width_, pixel_height_); + gfx::Rect visible_rect(crop_top_, crop_left_, + pixel_width_ - (crop_left_ + crop_right_), + pixel_height_ - (crop_top_ + crop_bottom_)); + gfx::Size natural_size = coded_size; + if (display_unit_ == 0) { + if (display_width_ <= 0) + display_width_ = pixel_width_; + if (display_height_ <= 0) + display_height_ = pixel_height_; + natural_size = gfx::Size(display_width_, display_height_); + } else if (display_unit_ == 3) { + if (display_width_ <= 0 || display_height_ <= 0) + return false; + natural_size = gfx::Size(display_width_, display_height_); + } else { + MEDIA_LOG(log_cb_) << "Unsupported display unit type " << display_unit_; + return false; + } + const uint8* extra_data = NULL; + size_t extra_data_size = 0; + if (codec_private.size() > 0) { + extra_data = &codec_private[0]; + extra_data_size = codec_private.size(); + } + + config->Initialize( + video_codec, profile, format, coded_size, visible_rect, natural_size, + extra_data, extra_data_size, is_encrypted, true); + return config->IsValidConfig(); +} + +bool WebMVideoClient::OnUInt(int id, int64 val) { + int64* dst = NULL; + + switch (id) { + case kWebMIdPixelWidth: + dst = &pixel_width_; + break; + case kWebMIdPixelHeight: + dst = &pixel_height_; + break; + case kWebMIdPixelCropTop: + dst = &crop_top_; + break; + case kWebMIdPixelCropBottom: + dst = &crop_bottom_; + break; + case kWebMIdPixelCropLeft: + dst = &crop_left_; + break; + case kWebMIdPixelCropRight: + dst = &crop_right_; + break; + case kWebMIdDisplayWidth: + dst = &display_width_; + break; + case kWebMIdDisplayHeight: + dst = &display_height_; + break; + case kWebMIdDisplayUnit: + dst = &display_unit_; + break; + case kWebMIdAlphaMode: + dst = &alpha_mode_; + break; + default: + return true; + } + + if (*dst != -1) { + MEDIA_LOG(log_cb_) << "Multiple values for id " << std::hex << id + << " specified (" << *dst << " and " << val << ")"; + return false; + } + + *dst = val; + return true; +} + +bool WebMVideoClient::OnBinary(int id, const uint8* data, int size) { + // Accept binary fields we don't care about for now. + return true; +} + +bool WebMVideoClient::OnFloat(int id, double val) { + // Accept float fields we don't care about for now. + return true; +} + +} // namespace media diff --git a/media/webm/webm_video_client.h b/media/webm/webm_video_client.h new file mode 100644 index 0000000000..d1872baebb --- /dev/null +++ b/media/webm/webm_video_client.h @@ -0,0 +1,61 @@ +// 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 MEDIA_WEBM_WEBM_VIDEO_CLIENT_H_ +#define MEDIA_WEBM_WEBM_VIDEO_CLIENT_H_ + +#include +#include + +#include "media/base/media_log.h" +#include "media/webm/webm_parser.h" + +namespace media { +class VideoDecoderConfig; + +// Helper class used to parse a Video element inside a TrackEntry element. +class WebMVideoClient : public WebMParserClient { + public: + explicit WebMVideoClient(const LogCB& log_cb); + virtual ~WebMVideoClient(); + + // Reset this object's state so it can process a new video track element. + void Reset(); + + // Initialize |config| with the data in |codec_id|, |codec_private|, + // |is_encrypted| and the fields parsed from the last video track element this + // object was used to parse. + // Returns true if |config| was successfully initialized. + // Returns false if there was unexpected values in the provided parameters or + // video track element fields. The contents of |config| are undefined in this + // case and should not be relied upon. + bool InitializeConfig(const std::string& codec_id, + const std::vector& codec_private, + bool is_encrypted, + VideoDecoderConfig* config); + + private: + // WebMParserClient implementation. + virtual bool OnUInt(int id, int64 val) OVERRIDE; + virtual bool OnBinary(int id, const uint8* data, int size) OVERRIDE; + virtual bool OnFloat(int id, double val) OVERRIDE; + + LogCB log_cb_; + int64 pixel_width_; + int64 pixel_height_; + int64 crop_bottom_; + int64 crop_top_; + int64 crop_left_; + int64 crop_right_; + int64 display_width_; + int64 display_height_; + int64 display_unit_; + int64 alpha_mode_; + + DISALLOW_COPY_AND_ASSIGN(WebMVideoClient); +}; + +} // namespace media + +#endif // MEDIA_WEBM_WEBM_VIDEO_CLIENT_H_ diff --git a/media/webm/webm_webvtt_parser.cc b/media/webm/webm_webvtt_parser.cc new file mode 100644 index 0000000000..d77bfbcfd8 --- /dev/null +++ b/media/webm/webm_webvtt_parser.cc @@ -0,0 +1,78 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/webm/webm_webvtt_parser.h" + +namespace media { + +void WebMWebVTTParser::Parse(const uint8* payload, int payload_size, + std::string* id, + std::string* settings, + std::string* content) { + WebMWebVTTParser parser(payload, payload_size); + parser.Parse(id, settings, content); +} + +WebMWebVTTParser::WebMWebVTTParser(const uint8* payload, int payload_size) + : ptr_(payload), + ptr_end_(payload + payload_size) { +} + +void WebMWebVTTParser::Parse(std::string* id, + std::string* settings, + std::string* content) { + ParseLine(id); + ParseLine(settings); + content->assign(ptr_, ptr_end_); +} + +bool WebMWebVTTParser::GetByte(uint8* byte) { + if (ptr_ >= ptr_end_) + return false; // indicates end-of-stream + + *byte = *ptr_++; + return true; +} + +void WebMWebVTTParser::UngetByte() { + --ptr_; +} + +void WebMWebVTTParser::ParseLine(std::string* line) { + line->clear(); + + // Consume characters from the stream, until we reach end-of-line. + + // The WebVTT spec states that lines may be terminated in any of the following + // three ways: + // LF + // CR + // CR LF + + // The spec is here: + // http://wiki.webmproject.org/webm-metadata/temporal-metadata/webvtt-in-webm + + enum { + kLF = '\x0A', + kCR = '\x0D' + }; + + for (;;) { + uint8 byte; + + if (!GetByte(&byte) || byte == kLF) + return; + + if (byte == kCR) { + if (GetByte(&byte) && byte != kLF) + UngetByte(); + + return; + } + + line->push_back(byte); + } +} + +} // namespace media diff --git a/media/webm/webm_webvtt_parser.h b/media/webm/webm_webvtt_parser.h new file mode 100644 index 0000000000..a6aa316d5e --- /dev/null +++ b/media/webm/webm_webvtt_parser.h @@ -0,0 +1,49 @@ +// 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 MEDIA_WEBM_WEBM_WEBVTT_PARSER_H_ +#define MEDIA_WEBM_WEBM_WEBVTT_PARSER_H_ + +#include + +#include "base/basictypes.h" +#include "media/base/media_export.h" + +namespace media { + +class MEDIA_EXPORT WebMWebVTTParser { + public: + // Utility function to parse the WebVTT cue from a byte stream. + static void Parse(const uint8* payload, int payload_size, + std::string* id, + std::string* settings, + std::string* content); + + private: + // The payload is the embedded WebVTT cue, stored in a WebM block. + // The parser treats this as a UTF-8 byte stream. + WebMWebVTTParser(const uint8* payload, int payload_size); + + // Parse the cue identifier, settings, and content from the stream. + void Parse(std::string* id, std::string* settings, std::string* content); + // Remove a byte from the stream, advancing the stream pointer. + // Returns true if a character was returned; false means "end of stream". + bool GetByte(uint8* byte); + + // Backup the stream pointer. + void UngetByte(); + + // Parse a line of text from the stream. + void ParseLine(std::string* line); + + // Represents the portion of the stream that has not been consumed yet. + const uint8* ptr_; + const uint8* const ptr_end_; + + DISALLOW_COPY_AND_ASSIGN(WebMWebVTTParser); +}; + +} // namespace media + +#endif // MEDIA_WEBM_WEBM_WEBVTT_PARSER_H_ diff --git a/media/webm/webm_webvtt_parser_unittest.cc b/media/webm/webm_webvtt_parser_unittest.cc new file mode 100644 index 0000000000..db514a1247 --- /dev/null +++ b/media/webm/webm_webvtt_parser_unittest.cc @@ -0,0 +1,105 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/webm/webm_webvtt_parser.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::InSequence; + +namespace media { + +typedef std::vector Cue; + +static Cue EncodeCue(const std::string& id, + const std::string& settings, + const std::string& content) { + const std::string result = id + '\n' + settings + '\n' + content; + const uint8* const buf = reinterpret_cast(result.data()); + return Cue(buf, buf + result.length()); +} + +static void DecodeCue(const Cue& cue, + std::string* id, + std::string* settings, + std::string* content) { + WebMWebVTTParser::Parse(&cue[0], static_cast(cue.size()), + id, settings, content); +} + +class WebMWebVTTParserTest : public testing::Test { + public: + WebMWebVTTParserTest() {} +}; + +TEST_F(WebMWebVTTParserTest, Blank) { + InSequence s; + + const Cue cue = EncodeCue("", "", "Subtitle"); + std::string id, settings, content; + + DecodeCue(cue, &id, &settings, &content); + EXPECT_EQ(id, ""); + EXPECT_EQ(settings, ""); + EXPECT_EQ(content, "Subtitle"); +} + +TEST_F(WebMWebVTTParserTest, Id) { + InSequence s; + + for (int i = 1; i <= 9; ++i) { + const std::string idsrc(1, '0'+i); + const Cue cue = EncodeCue(idsrc, "", "Subtitle"); + std::string id, settings, content; + + DecodeCue(cue, &id, &settings, &content); + EXPECT_EQ(id, idsrc); + EXPECT_EQ(settings, ""); + EXPECT_EQ(content, "Subtitle"); + } +} + +TEST_F(WebMWebVTTParserTest, Settings) { + InSequence s; + + enum { kSettingsCount = 4 }; + const char* const settings_str[kSettingsCount] = { + "vertical:lr", + "line:50%", + "position:42%", + "vertical:rl line:42% position:100%" }; + + for (int i = 0; i < kSettingsCount; ++i) { + const Cue cue = EncodeCue("", settings_str[i], "Subtitle"); + std::string id, settings, content; + + DecodeCue(cue, &id, &settings, &content); + EXPECT_EQ(id, ""); + EXPECT_EQ(settings, settings_str[i]); + EXPECT_EQ(content, "Subtitle"); + } +} + +TEST_F(WebMWebVTTParserTest, Content) { + InSequence s; + + enum { kContentCount = 4 }; + const char* const content_str[kContentCount] = { + "Subtitle", + "Another Subtitle", + "Yet Another Subtitle", + "Another Subtitle\nSplit Across Two Lines" }; + + for (int i = 0; i < kContentCount; ++i) { + const Cue cue = EncodeCue("", "", content_str[i]); + std::string id, settings, content; + + DecodeCue(cue, &id, &settings, &content); + EXPECT_EQ(id, ""); + EXPECT_EQ(settings, ""); + EXPECT_EQ(content, content_str[i]); + } +} + +} // namespace media diff --git a/test.txt b/test.txt deleted file mode 100644 index 04afedf815..0000000000 --- a/test.txt +++ /dev/null @@ -1 +0,0 @@ -dtest diff --git a/testing/OWNERS b/testing/OWNERS new file mode 100644 index 0000000000..72e8ffc0db --- /dev/null +++ b/testing/OWNERS @@ -0,0 +1 @@ +* diff --git a/testing/PRESUBMIT.py b/testing/PRESUBMIT.py new file mode 100644 index 0000000000..90e524f455 --- /dev/null +++ b/testing/PRESUBMIT.py @@ -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. + +"""Top-level presubmit script for testing. + +See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts for +details on the presubmit API built into gcl. +""" + + +def CommonChecks(input_api, output_api): + output = [] + blacklist = [r'gmock.*', r'gtest.*'] + output.extend(input_api.canned_checks.RunPylint( + input_api, output_api, black_list=blacklist)) + return output + + +def CheckChangeOnUpload(input_api, output_api): + return CommonChecks(input_api, output_api) + + +def CheckChangeOnCommit(input_api, output_api): + return CommonChecks(input_api, output_api) diff --git a/testing/android/AndroidManifest.xml b/testing/android/AndroidManifest.xml new file mode 100644 index 0000000000..73a0c14881 --- /dev/null +++ b/testing/android/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/testing/android/OWNERS b/testing/android/OWNERS new file mode 100644 index 0000000000..87d5d22497 --- /dev/null +++ b/testing/android/OWNERS @@ -0,0 +1,2 @@ +bulach@chromium.org +yfriedman@chromium.org diff --git a/testing/android/README.chromium b/testing/android/README.chromium new file mode 100644 index 0000000000..c00255a43a --- /dev/null +++ b/testing/android/README.chromium @@ -0,0 +1,2 @@ +apk-based runner for Chromium unit test bundles. This is a simple wrapper APK +that should include a single gtest native library. diff --git a/testing/android/java/src/org/chromium/native_test/ChromeNativeTestActivity.java b/testing/android/java/src/org/chromium/native_test/ChromeNativeTestActivity.java new file mode 100644 index 0000000000..6486a14f3c --- /dev/null +++ b/testing/android/java/src/org/chromium/native_test/ChromeNativeTestActivity.java @@ -0,0 +1,84 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.native_test; + +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; +import android.util.Log; + +import org.chromium.base.ChromiumActivity; +import org.chromium.base.PathUtils; +import org.chromium.base.PowerMonitor; + +// TODO(cjhopman): This should not refer to content. NativeLibraries should be moved to base. +import org.chromium.content.app.NativeLibraries; + +import java.io.File; + +// Android's NativeActivity is mostly useful for pure-native code. +// Our tests need to go up to our own java classes, which is not possible using +// the native activity class loader. +public class ChromeNativeTestActivity extends ChromiumActivity { + private static final String TAG = "ChromeNativeTestActivity"; + private static final String EXTRA_RUN_IN_SUB_THREAD = "RunInSubThread"; + // We post a delayed task to run tests so that we do not block onCreate(). + private static final long RUN_TESTS_DELAY_IN_MS = 300; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // Needed by path_utils_unittest.cc + PathUtils.setPrivateDataDirectorySuffix("chrome"); + + // Needed by system_monitor_unittest.cc + PowerMonitor.createForTests(this); + + loadLibraries(); + Bundle extras = this.getIntent().getExtras(); + if (extras != null && extras.containsKey(EXTRA_RUN_IN_SUB_THREAD)) { + // Create a new thread and run tests on it. + new Thread() { + @Override + public void run() { + runTests(); + } + }.start(); + } else { + // Post a task to run the tests. This allows us to not block + // onCreate and still run tests on the main thread. + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + runTests(); + } + }, RUN_TESTS_DELAY_IN_MS); + } + } + + private void runTests() { + // This directory is used by build/android/pylib/test_package_apk.py. + nativeRunTests(getFilesDir().getAbsolutePath(), getApplicationContext()); + } + + // Signal a failure of the native test loader to python scripts + // which run tests. For example, we look for + // RUNNER_FAILED build/android/test_package.py. + private void nativeTestFailed() { + Log.e(TAG, "[ RUNNER_FAILED ] could not load native library"); + } + + private void loadLibraries() { + for (String library: NativeLibraries.libraries) { + Log.i(TAG, "loading: " + library); + System.loadLibrary(library); + Log.i(TAG, "loaded: " + library); + } + } + + private native void nativeRunTests(String filesDir, Context appContext); +} diff --git a/testing/android/native_test.gyp b/testing/android/native_test.gyp new file mode 100644 index 0000000000..4a3cc4d8fa --- /dev/null +++ b/testing/android/native_test.gyp @@ -0,0 +1,64 @@ +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'conditions': [ + ['OS=="android"', { + 'targets': [ + { + 'target_name': 'native_test_native_code', + 'message': 'building native pieces of native test package', + 'type': 'static_library', + 'sources': [ + 'native_test_launcher.cc', + ], + 'direct_dependent_settings': { + 'ldflags!': [ + # JNI_OnLoad is implemented in a .a and we need to + # re-export in the .so. + '-Wl,--exclude-libs=ALL', + ], + }, + 'dependencies': [ + '../../base/base.gyp:base', + '../../base/base.gyp:test_support_base', + '../../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', + '../gtest.gyp:gtest', + 'native_test_jni_headers', + 'native_test_util', + ], + }, + { + 'target_name': 'native_test_jni_headers', + 'type': 'none', + 'sources': [ + 'java/src/org/chromium/native_test/ChromeNativeTestActivity.java' + ], + 'variables': { + 'jni_gen_package': 'testing', + }, + 'includes': [ '../../build/jni_generator.gypi' ], + # So generated jni headers can be found by targets that + # depend on this. + 'direct_dependent_settings': { + 'include_dirs': [ + '<(SHARED_INTERMEDIATE_DIR)', + ], + }, + }, + { + 'target_name': 'native_test_util', + 'type': 'static_library', + 'sources': [ + 'native_test_util.cc', + 'native_test_util.h', + ], + 'dependencies': [ + '../../base/base.gyp:base', + ], + }, + ], + }] + ], +} diff --git a/testing/android/native_test_launcher.cc b/testing/android/native_test_launcher.cc new file mode 100644 index 0000000000..e39eb2bdcc --- /dev/null +++ b/testing/android/native_test_launcher.cc @@ -0,0 +1,202 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This class sets up the environment for running the native tests inside an +// android application. It outputs (to a fifo) markers identifying the +// START/PASSED/CRASH of the test suite, FAILURE/SUCCESS of individual tests, +// etc. +// These markers are read by the test runner script to generate test results. +// It installs signal handlers to detect crashes. + +#include +#include + +#include "base/android/base_jni_registrar.h" +#include "base/android/fifo_utils.h" +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/android/scoped_java_ref.h" +#include "base/at_exit.h" +#include "base/base_switches.h" +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/strings/stringprintf.h" +#include "gtest/gtest.h" +#include "testing/android/native_test_util.h" +#include "testing/jni/ChromeNativeTestActivity_jni.h" + +using testing::native_test_util::ArgsToArgv; +using testing::native_test_util::ParseArgsFromCommandLineFile; +using testing::native_test_util::ScopedMainEntryLogger; + +// The main function of the program to be wrapped as a test apk. +extern int main(int argc, char** argv); + +namespace { + +// These two command line flags are supported for DumpRenderTree, which needs +// three fifos rather than a combined one: one for stderr, stdin and stdout. +const char kSeparateStderrFifo[] = "separate-stderr-fifo"; +const char kCreateStdinFifo[] = "create-stdin-fifo"; + +// The test runner script writes the command line file in +// "/data/local/tmp". +static const char kCommandLineFilePath[] = + "/data/local/tmp/chrome-native-tests-command-line"; + +const char kLogTag[] = "chromium"; +const char kCrashedMarker[] = "[ CRASHED ]\n"; + +// The list of signals which are considered to be crashes. +const int kExceptionSignals[] = { + SIGSEGV, SIGABRT, SIGFPE, SIGILL, SIGBUS, -1 +}; + +struct sigaction g_old_sa[NSIG]; + +// This function runs in a compromised context. It should not allocate memory. +void SignalHandler(int sig, siginfo_t* info, void* reserved) { + // Output the crash marker. + write(STDOUT_FILENO, kCrashedMarker, sizeof(kCrashedMarker)); + g_old_sa[sig].sa_sigaction(sig, info, reserved); +} + +// TODO(nileshagrawal): now that we're using FIFO, test scripts can detect EOF. +// Remove the signal handlers. +void InstallHandlers() { + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + + sa.sa_sigaction = SignalHandler; + sa.sa_flags = SA_SIGINFO; + + for (unsigned int i = 0; kExceptionSignals[i] != -1; ++i) { + sigaction(kExceptionSignals[i], &sa, &g_old_sa[kExceptionSignals[i]]); + } +} + +// Writes printf() style string to Android's logger where |priority| is one of +// the levels defined in . +void AndroidLog(int priority, const char* format, ...) { + va_list args; + va_start(args, format); + __android_log_vprint(priority, kLogTag, format, args); + va_end(args); +} + +// Ensures that the fifo at |path| is created by deleting whatever is at |path| +// prior to (re)creating the fifo, otherwise logs the error and terminates the +// program. +void EnsureCreateFIFO(const base::FilePath& path) { + unlink(path.value().c_str()); + if (base::android::CreateFIFO(path, 0666)) + return; + + AndroidLog(ANDROID_LOG_ERROR, "Failed to create fifo %s: %s\n", + path.value().c_str(), strerror(errno)); + exit(EXIT_FAILURE); +} + +// Ensures that |stream| is redirected to |path|, otherwise logs the error and +// terminates the program. +void EnsureRedirectStream(FILE* stream, + const base::FilePath& path, + const char* mode) { + if (base::android::RedirectStream(stream, path, mode)) + return; + + AndroidLog(ANDROID_LOG_ERROR, "Failed to redirect stream to file: %s: %s\n", + path.value().c_str(), strerror(errno)); + exit(EXIT_FAILURE); +} + +} // namespace + +// This method is called on a separate java thread so that we won't trigger +// an ANR. +static void RunTests(JNIEnv* env, + jobject obj, + jstring jfiles_dir, + jobject app_context) { + base::AtExitManager exit_manager; + + // Command line initialized basically, will be fully initialized later. + static const char* const kInitialArgv[] = { "ChromeTestActivity" }; + CommandLine::Init(arraysize(kInitialArgv), kInitialArgv); + + // Set the application context in base. + base::android::ScopedJavaLocalRef scoped_context( + env, env->NewLocalRef(app_context)); + base::android::InitApplicationContext(scoped_context); + base::android::RegisterJni(env); + + std::vector args; + ParseArgsFromCommandLineFile(kCommandLineFilePath, &args); + + std::vector argv; + int argc = ArgsToArgv(args, &argv); + + // Fully initialize command line with arguments. + CommandLine::ForCurrentProcess()->AppendArguments( + CommandLine(argc, &argv[0]), false); + const CommandLine& command_line = *CommandLine::ForCurrentProcess(); + + base::FilePath files_dir( + base::android::ConvertJavaStringToUTF8(env, jfiles_dir)); + + // A few options, such "--gtest_list_tests", will just use printf directly + // Always redirect stdout to a known file. + base::FilePath fifo_path(files_dir.Append(base::FilePath("test.fifo"))); + EnsureCreateFIFO(fifo_path); + + base::FilePath stderr_fifo_path, stdin_fifo_path; + + // DumpRenderTree needs a separate fifo for the stderr output. For all + // other tests, insert stderr content to the same fifo we use for stdout. + if (command_line.HasSwitch(kSeparateStderrFifo)) { + stderr_fifo_path = files_dir.Append(base::FilePath("stderr.fifo")); + EnsureCreateFIFO(stderr_fifo_path); + } + + // DumpRenderTree uses stdin to receive input about which test to run. + if (command_line.HasSwitch(kCreateStdinFifo)) { + stdin_fifo_path = files_dir.Append(base::FilePath("stdin.fifo")); + EnsureCreateFIFO(stdin_fifo_path); + } + + // Only redirect the streams after all fifos have been created. + EnsureRedirectStream(stdout, fifo_path, "w"); + if (!stdin_fifo_path.empty()) + EnsureRedirectStream(stdin, stdin_fifo_path, "r"); + if (!stderr_fifo_path.empty()) + EnsureRedirectStream(stderr, stderr_fifo_path, "w"); + else + dup2(STDOUT_FILENO, STDERR_FILENO); + + if (command_line.HasSwitch(switches::kWaitForDebugger)) { + AndroidLog(ANDROID_LOG_VERBOSE, + "Native test waiting for GDB because flag %s was supplied", + switches::kWaitForDebugger); + base::debug::WaitForDebugger(24 * 60 * 60, false); + } + + ScopedMainEntryLogger scoped_main_entry_logger; + main(argc, &argv[0]); +} + +// This is called by the VM when the shared library is first loaded. +JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { + // Install signal handlers to detect crashes. + InstallHandlers(); + + base::android::InitVM(vm); + JNIEnv* env = base::android::AttachCurrentThread(); + if (!RegisterNativesImpl(env)) { + return -1; + } + + return JNI_VERSION_1_4; +} diff --git a/testing/android/native_test_util.cc b/testing/android/native_test_util.cc new file mode 100644 index 0000000000..c0ea7b0fef --- /dev/null +++ b/testing/android/native_test_util.cc @@ -0,0 +1,54 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "testing/android/native_test_util.h" + +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/strings/string_tokenizer.h" +#include "base/strings/string_util.h" + +namespace { + +void ParseArgsFromString(const std::string& command_line, + std::vector* args) { + base::StringTokenizer tokenizer(command_line, kWhitespaceASCII); + tokenizer.set_quote_chars("\""); + while (tokenizer.GetNext()) { + std::string token; + RemoveChars(tokenizer.token(), "\"", &token); + args->push_back(token); + } +} + +} // namespace + +namespace testing { +namespace native_test_util { + +void ParseArgsFromCommandLineFile( + const char* path, std::vector* args) { + base::FilePath command_line(path); + std::string command_line_string; + if (file_util::ReadFileToString(command_line, &command_line_string)) { + ParseArgsFromString(command_line_string, args); + } +} + +int ArgsToArgv(const std::vector& args, + std::vector* argv) { + // We need to pass in a non-const char**. + int argc = args.size(); + + argv->resize(argc + 1); + for (int i = 0; i < argc; ++i) { + (*argv)[i] = const_cast(args[i].c_str()); + } + (*argv)[argc] = NULL; // argv must be NULL terminated. + + return argc; +} + +} // namespace native_test_util +} // namespace testing diff --git a/testing/android/native_test_util.h b/testing/android/native_test_util.h new file mode 100644 index 0000000000..a7567392b1 --- /dev/null +++ b/testing/android/native_test_util.h @@ -0,0 +1,37 @@ +// 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 TESTING_ANDROID_NATIVE_TEST_UTIL_ +#define TESTING_ANDROID_NATIVE_TEST_UTIL_ + +#include +#include +#include + +// Helper methods for setting up environment for running gtest tests +// inside an APK. +namespace testing { +namespace native_test_util { + +class ScopedMainEntryLogger { + public: + ScopedMainEntryLogger() { + printf(">>ScopedMainEntryLogger\n"); + } + + ~ScopedMainEntryLogger() { + printf("<* args); +int ArgsToArgv(const std::vector& args, std::vector* argv); + +} // namespace native_test_util +} // namespace testing + +#endif // TESTING_ANDROID_NATIVE_TEST_UTIL_ diff --git a/testing/generate_gmock_mutant.py b/testing/generate_gmock_mutant.py new file mode 100755 index 0000000000..6d814f0829 --- /dev/null +++ b/testing/generate_gmock_mutant.py @@ -0,0 +1,457 @@ +#!/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. + +import string +import sys + +HEADER = """\ +// 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 file automatically generated by testing/generate_gmock_mutant.py. +// DO NOT EDIT. + +#ifndef TESTING_GMOCK_MUTANT_H_ +#define TESTING_GMOCK_MUTANT_H_ + +// The intention of this file is to make possible using GMock actions in +// all of its syntactic beauty. Classes and helper functions can be used as +// more generic variants of Task and Callback classes (see base/task.h) +// Mutant supports both pre-bound arguments (like Task) and call-time +// arguments (like Callback) - hence the name. :-) +// +// DispatchToMethod/Function supports two sets of arguments: pre-bound (P) and +// call-time (C). The arguments as well as the return type are templatized. +// DispatchToMethod/Function will also try to call the selected method or +// function even if provided pre-bound arguments does not match exactly with +// the function signature hence the X1, X2 ... XN parameters in CreateFunctor. +// DispatchToMethod will try to invoke method that may not belong to the +// object's class itself but to the object's class base class. +// +// Additionally you can bind the object at calltime by binding a pointer to +// pointer to the object at creation time - before including this file you +// have to #define GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING. +// +// TODO(stoyan): It's yet not clear to me should we use T& and T&* instead +// of T* and T** when we invoke CreateFunctor to match the EXPECT_CALL style. +// +// +// Sample usage with gMock: +// +// struct Mock : public ObjectDelegate { +// MOCK_METHOD2(string, OnRequest(int n, const string& request)); +// MOCK_METHOD1(void, OnQuit(int exit_code)); +// MOCK_METHOD2(void, LogMessage(int level, const string& message)); +// +// string HandleFlowers(const string& reply, int n, const string& request) { +// string result = SStringPrintf("In request of %d %s ", n, request); +// for (int i = 0; i < n; ++i) result.append(reply) +// return result; +// } +// +// void DoLogMessage(int level, const string& message) { +// } +// +// void QuitMessageLoop(int seconds) { +// MessageLoop* loop = MessageLoop::current(); +// loop->PostDelayedTask(FROM_HERE, MessageLoop::QuitClosure(), +// 1000 * seconds); +// } +// }; +// +// Mock mock; +// // Will invoke mock.HandleFlowers("orchids", n, request) +// // "orchids" is a pre-bound argument, and and are call-time +// // arguments - they are not known until the OnRequest mock is invoked. +// EXPECT_CALL(mock, OnRequest(Ge(5), StartsWith("flower")) +// .Times(1) +// .WillOnce(Invoke(CreateFunctor(&mock, &Mock::HandleFlowers, +// string("orchids")))); +// +// +// // No pre-bound arguments, two call-time arguments passed +// // directly to DoLogMessage +// EXPECT_CALL(mock, OnLogMessage(_, _)) +// .Times(AnyNumber()) +// .WillAlways(Invoke(CreateFunctor, &mock, &Mock::DoLogMessage)); +// +// +// // In this case we have a single pre-bound argument - 3. We ignore +// // all of the arguments of OnQuit. +// EXCEPT_CALL(mock, OnQuit(_)) +// .Times(1) +// .WillOnce(InvokeWithoutArgs(CreateFunctor( +// &mock, &Mock::QuitMessageLoop, 3))); +// +// MessageLoop loop; +// loop.Run(); +// +// +// // Here is another example of how we can set an action that invokes +// // method of an object that is not yet created. +// struct Mock : public ObjectDelegate { +// MOCK_METHOD1(void, DemiurgeCreated(Demiurge*)); +// MOCK_METHOD2(void, OnRequest(int count, const string&)); +// +// void StoreDemiurge(Demiurge* w) { +// demiurge_ = w; +// } +// +// Demiurge* demiurge; +// } +// +// EXPECT_CALL(mock, DemiurgeCreated(_)).Times(1) +// .WillOnce(Invoke(CreateFunctor(&mock, &Mock::StoreDemiurge))); +// +// EXPECT_CALL(mock, OnRequest(_, StrEq("Moby Dick"))) +// .Times(AnyNumber()) +// .WillAlways(WithArgs<0>(Invoke( +// CreateFunctor(&mock->demiurge_, &Demiurge::DecreaseMonsters)))); +// + +#include "base/memory/linked_ptr.h" +#include "base/tuple.h" // for Tuple + +namespace testing {""" + +MUTANT = """\ + +// Interface that is exposed to the consumer, that does the actual calling +// of the method. +template +class MutantRunner { + public: + virtual R RunWithParams(const Params& params) = 0; + virtual ~MutantRunner() {} +}; + +// Mutant holds pre-bound arguments (like Task). Like Callback +// allows call-time arguments. You bind a pointer to the object +// at creation time. +template +class Mutant : public MutantRunner { + public: + Mutant(T* obj, Method method, const PreBound& pb) + : obj_(obj), method_(method), pb_(pb) { + } + + // MutantRunner implementation + virtual R RunWithParams(const Params& params) { + return DispatchToMethod(this->obj_, this->method_, pb_, params); + } + + T* obj_; + Method method_; + PreBound pb_; +}; + +template +class MutantFunction : public MutantRunner { + public: + MutantFunction(Function function, const PreBound& pb) + : function_(function), pb_(pb) { + } + + // MutantRunner implementation + virtual R RunWithParams(const Params& params) { + return DispatchToFunction(function_, pb_, params); + } + + Function function_; + PreBound pb_; +}; + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +// MutantLateBind is like Mutant, but you bind a pointer to a pointer +// to the object. This way you can create actions for an object +// that is not yet created (has only storage for a pointer to it). +template +class MutantLateObjectBind : public MutantRunner { + public: + MutantLateObjectBind(T** obj, Method method, const PreBound& pb) + : obj_(obj), method_(method), pb_(pb) { + } + + // MutantRunner implementation. + virtual R RunWithParams(const Params& params) { + EXPECT_THAT(*this->obj_, testing::NotNull()); + if (NULL == *this->obj_) + return R(); + return DispatchToMethod( *this->obj_, this->method_, pb_, params); + } + + T** obj_; + Method method_; + PreBound pb_; +}; +#endif + +// Simple MutantRunner<> wrapper acting as a functor. +// Redirects operator() to MutantRunner::Run() +template +struct MutantFunctor { + explicit MutantFunctor(MutantRunner* cb) : impl_(cb) { + } + + ~MutantFunctor() { + } + + inline R operator()() { + return impl_->RunWithParams(Tuple0()); + } + + template + inline R operator()(const Arg1& a) { + return impl_->RunWithParams(Params(a)); + } + + template + inline R operator()(const Arg1& a, const Arg2& b) { + return impl_->RunWithParams(Params(a, b)); + } + + template + inline R operator()(const Arg1& a, const Arg2& b, const Arg3& c) { + return impl_->RunWithParams(Params(a, b, c)); + } + + template + inline R operator()(const Arg1& a, const Arg2& b, const Arg3& c, + const Arg4& d) { + return impl_->RunWithParams(Params(a, b, c, d)); + } + + private: + // We need copy constructor since MutantFunctor is copied few times + // inside GMock machinery, hence no DISALLOW_EVIL_CONTRUCTORS + MutantFunctor(); + linked_ptr > impl_; +}; +""" + +FOOTER = """\ +} // namespace testing + +#endif // TESTING_GMOCK_MUTANT_H_""" + +# Templates for DispatchToMethod/DispatchToFunction functions. +# template_params - typename P1, typename P2.. typename C1.. +# prebound - TupleN +# calltime - TupleN +# args - p.a, p.b.., c.a, c.b.. +DISPATCH_TO_METHOD_TEMPLATE = """\ +template +inline R DispatchToMethod(T* obj, Method method, + const %(prebound)s& p, + const %(calltime)s& c) { + return (obj->*method)(%(args)s); +} +""" + +DISPATCH_TO_FUNCTION_TEMPLATE = """\ +template +inline R DispatchToFunction(Function function, + const %(prebound)s& p, + const %(calltime)s& c) { + return (*function)(%(args)s); +} +""" + +# Templates for CreateFunctor functions. +# template_params - typename P1, typename P2.. typename C1.. typename X1.. +# prebound - TupleN +# calltime - TupleN +# params - X1,.. , A1, .. +# args - const P1& p1 .. +# call_args - p1, p2, p3.. +CREATE_METHOD_FUNCTOR_TEMPLATE = """\ +template +inline MutantFunctor +CreateFunctor(T* obj, R (U::*method)(%(params)s), %(args)s) { + MutantRunner* t = + new Mutant + (obj, method, MakeTuple(%(call_args)s)); + return MutantFunctor(t); +} +""" + +CREATE_FUNCTION_FUNCTOR_TEMPLATE = """\ +template +inline MutantFunctor +CreateFunctor(R (*function)(%(params)s), %(args)s) { + MutantRunner* t = + new MutantFunction + (function, MakeTuple(%(call_args)s)); + return MutantFunctor(t); +} +""" + +def SplitLine(line, width): + """Splits a single line at comma, at most |width| characters long.""" + if len(line) < width: + return (line, None) + n = 1 + line[:width].rfind(",") + if n == 0: # If comma cannot be found give up and return the entire line. + return (line, None) + # Assume there is a space after the comma + assert line[n] == " " + return (line[:n], line[n + 1:]) + + +def Wrap(s, width, subsequent_offset=4): + """Wraps a single line |s| at commas so every line is at most |width| + characters long. + """ + w = [] + spaces = " " * subsequent_offset + while s: + (f, s) = SplitLine(s, width) + w.append(f) + if s: + s = spaces + s + return "\n".join(w) + + +def Clean(s): + """Cleans artifacts from generated C++ code. + + Our simple string formatting/concatenation may introduce extra commas. + """ + s = s.replace("<>", "") + s = s.replace(", >", ">") + s = s.replace(", )", ")") + s = s.replace(">>", "> >") + return s + + +def ExpandPattern(pattern, it): + """Return list of expanded pattern strings. + + Each string is created by replacing all '%' in |pattern| with element of |it|. + """ + return [pattern.replace("%", x) for x in it] + + +def Gen(pattern, n): + """Expands pattern replacing '%' with sequential integers. + + Expanded patterns will be joined with comma separator. + GenAlphs("X%", 3) will return "X1, X2, X3". + """ + it = string.hexdigits[1:n + 1] + return ", ".join(ExpandPattern(pattern, it)) + + +def GenAlpha(pattern, n): + """Expands pattern replacing '%' with sequential small ASCII letters. + + Expanded patterns will be joined with comma separator. + GenAlphs("X%", 3) will return "Xa, Xb, Xc". + """ + it = string.ascii_lowercase[0:n] + return ", ".join(ExpandPattern(pattern, it)) + + +def Merge(a): + return ", ".join(filter(len, a)) + + +def GenTuple(pattern, n): + return Clean("Tuple%d<%s>" % (n, Gen(pattern, n))) + + +def FixCode(s): + lines = Clean(s).splitlines() + # Wrap sometimes very long 1st and 3rd line at 80th column. + lines[0] = Wrap(lines[0], 80, 10) + lines[2] = Wrap(lines[2], 80, 4) + return "\n".join(lines) + + +def GenerateDispatch(prebound, calltime): + print "\n// %d - %d" % (prebound, calltime) + args = { + "template_params": Merge([Gen("typename P%", prebound), + Gen("typename C%", calltime)]), + "prebound": GenTuple("P%", prebound), + "calltime": GenTuple("C%", calltime), + "args": Merge([GenAlpha("p.%", prebound), GenAlpha("c.%", calltime)]), + } + + print FixCode(DISPATCH_TO_METHOD_TEMPLATE % args) + print FixCode(DISPATCH_TO_FUNCTION_TEMPLATE % args) + + +def GenerateCreateFunctor(prebound, calltime): + print "// %d - %d" % (prebound, calltime) + args = { + "calltime": GenTuple("A%", calltime), + "prebound": GenTuple("P%", prebound), + "params": Merge([Gen("X%", prebound), Gen("A%", calltime)]), + "args": Gen("const P%& p%", prebound), + "call_args": Gen("p%", prebound), + "template_params": Merge([Gen("typename P%", prebound), + Gen("typename A%", calltime), + Gen("typename X%", prebound)]) + } + + mutant = FixCode(CREATE_METHOD_FUNCTOR_TEMPLATE % args) + print mutant + + # Slightly different version for free function call. + print "\n", FixCode(CREATE_FUNCTION_FUNCTOR_TEMPLATE % args) + + # Functor with pointer to a pointer of the object. + print "\n#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING" + mutant2 = mutant.replace("CreateFunctor(T* obj,", "CreateFunctor(T** obj,") + mutant2 = mutant2.replace("new Mutant", "new MutantLateObjectBind") + mutant2 = mutant2.replace(" " * 17 + "Tuple", " " * 31 + "Tuple") + print mutant2 + print "#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING\n" + + # OS_WIN specific. Same functors but with stdcall calling conventions. + # These are not for WIN64 (x86_64) because there is only one calling + # convention in WIN64. + # Functor for method with __stdcall calling conventions. + print "#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64)" + stdcall_method = CREATE_METHOD_FUNCTOR_TEMPLATE + stdcall_method = stdcall_method.replace("U::", "__stdcall U::") + stdcall_method = FixCode(stdcall_method % args) + print stdcall_method + # Functor for free function with __stdcall calling conventions. + stdcall_function = CREATE_FUNCTION_FUNCTOR_TEMPLATE + stdcall_function = stdcall_function.replace("R (*", "R (__stdcall *") + print "\n", FixCode(stdcall_function % args) + + print "#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING" + stdcall2 = stdcall_method + stdcall2 = stdcall2.replace("CreateFunctor(T* obj,", "CreateFunctor(T** obj,") + stdcall2 = stdcall2.replace("new Mutant", "new MutantLateObjectBind") + stdcall2 = stdcall2.replace(" " * 17 + "Tuple", " " * 31 + "Tuple") + print stdcall2 + print "#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING" + print "#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64)\n" + + +def main(): + print HEADER + for prebound in xrange(0, 6 + 1): + for args in xrange(0, 6 + 1): + GenerateDispatch(prebound, args) + print MUTANT + for prebound in xrange(0, 6 + 1): + for args in xrange(0, 6 + 1): + GenerateCreateFunctor(prebound, args) + print FOOTER + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/testing/gmock b/testing/gmock new file mode 160000 index 0000000000..6b1759c381 --- /dev/null +++ b/testing/gmock @@ -0,0 +1 @@ +Subproject commit 6b1759c3816d574bddde3e1725c51a811c8870e7 diff --git a/testing/gmock.gyp b/testing/gmock.gyp new file mode 100644 index 0000000000..22a1893bd2 --- /dev/null +++ b/testing/gmock.gyp @@ -0,0 +1,62 @@ +# 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. + +{ + 'targets': [ + { + 'target_name': 'gmock', + 'type': 'static_library', + 'dependencies': [ + 'gtest.gyp:gtest', + ], + 'sources': [ + # Sources based on files in r173 of gmock. + 'gmock/include/gmock/gmock-actions.h', + 'gmock/include/gmock/gmock-cardinalities.h', + 'gmock/include/gmock/gmock-generated-actions.h', + 'gmock/include/gmock/gmock-generated-function-mockers.h', + 'gmock/include/gmock/gmock-generated-matchers.h', + 'gmock/include/gmock/gmock-generated-nice-strict.h', + 'gmock/include/gmock/gmock-matchers.h', + 'gmock/include/gmock/gmock-spec-builders.h', + 'gmock/include/gmock/gmock.h', + 'gmock/include/gmock/internal/gmock-generated-internal-utils.h', + 'gmock/include/gmock/internal/gmock-internal-utils.h', + 'gmock/include/gmock/internal/gmock-port.h', + 'gmock/src/gmock-all.cc', + 'gmock/src/gmock-cardinalities.cc', + 'gmock/src/gmock-internal-utils.cc', + 'gmock/src/gmock-matchers.cc', + 'gmock/src/gmock-spec-builders.cc', + 'gmock/src/gmock.cc', + 'gmock_mutant.h', # gMock helpers + ], + 'sources!': [ + 'gmock/src/gmock-all.cc', # Not needed by our build. + ], + 'include_dirs': [ + 'gmock', + 'gmock/include', + ], + 'direct_dependent_settings': { + 'include_dirs': [ + 'gmock/include', # So that gmock headers can find themselves. + ], + }, + 'export_dependent_settings': [ + 'gtest.gyp:gtest', + ], + }, + { + 'target_name': 'gmock_main', + 'type': 'static_library', + 'dependencies': [ + 'gmock', + ], + 'sources': [ + 'gmock/src/gmock_main.cc', + ], + }, + ], +} diff --git a/testing/gmock_mutant.h b/testing/gmock_mutant.h new file mode 100644 index 0000000000..90d303efec --- /dev/null +++ b/testing/gmock_mutant.h @@ -0,0 +1,4995 @@ +// 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 file automatically generated by testing/generate_gmock_mutant.py. +// DO NOT EDIT. + +#ifndef TESTING_GMOCK_MUTANT_H_ +#define TESTING_GMOCK_MUTANT_H_ + +// The intention of this file is to make possible using GMock actions in +// all of its syntactic beauty. Classes and helper functions can be used as +// more generic variants of Task and Callback classes (see base/task.h) +// Mutant supports both pre-bound arguments (like Task) and call-time +// arguments (like Callback) - hence the name. :-) +// +// DispatchToMethod/Function supports two sets of arguments: pre-bound (P) and +// call-time (C). The arguments as well as the return type are templatized. +// DispatchToMethod/Function will also try to call the selected method or +// function even if provided pre-bound arguments does not match exactly with +// the function signature hence the X1, X2 ... XN parameters in CreateFunctor. +// DispatchToMethod will try to invoke method that may not belong to the +// object's class itself but to the object's class base class. +// +// Additionally you can bind the object at calltime by binding a pointer to +// pointer to the object at creation time - before including this file you +// have to #define GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING. +// +// TODO(stoyan): It's yet not clear to me should we use T& and T&* instead +// of T* and T** when we invoke CreateFunctor to match the EXPECT_CALL style. +// +// +// Sample usage with gMock: +// +// struct Mock : public ObjectDelegate { +// MOCK_METHOD2(string, OnRequest(int n, const string& request)); +// MOCK_METHOD1(void, OnQuit(int exit_code)); +// MOCK_METHOD2(void, LogMessage(int level, const string& message)); +// +// string HandleFlowers(const string& reply, int n, const string& request) { +// string result = SStringPrintf("In request of %d %s ", n, request); +// for (int i = 0; i < n; ++i) result.append(reply) +// return result; +// } +// +// void DoLogMessage(int level, const string& message) { +// } +// +// void QuitMessageLoop(int seconds) { +// base::MessageLoop* loop = base::MessageLoop::current(); +// loop->PostDelayedTask(FROM_HERE, base::MessageLoop::QuitClosure(), +// 1000 * seconds); +// } +// }; +// +// Mock mock; +// // Will invoke mock.HandleFlowers("orchids", n, request) +// // "orchids" is a pre-bound argument, and and are call-time +// // arguments - they are not known until the OnRequest mock is invoked. +// EXPECT_CALL(mock, OnRequest(Ge(5), StartsWith("flower")) +// .Times(1) +// .WillOnce(Invoke(CreateFunctor(&mock, &Mock::HandleFlowers, +// string("orchids")))); +// +// +// // No pre-bound arguments, two call-time arguments passed +// // directly to DoLogMessage +// EXPECT_CALL(mock, OnLogMessage(_, _)) +// .Times(AnyNumber()) +// .WillAlways(Invoke(CreateFunctor, &mock, &Mock::DoLogMessage)); +// +// +// // In this case we have a single pre-bound argument - 3. We ignore +// // all of the arguments of OnQuit. +// EXCEPT_CALL(mock, OnQuit(_)) +// .Times(1) +// .WillOnce(InvokeWithoutArgs(CreateFunctor( +// &mock, &Mock::QuitMessageLoop, 3))); +// +// MessageLoop loop; +// loop.Run(); +// +// +// // Here is another example of how we can set an action that invokes +// // method of an object that is not yet created. +// struct Mock : public ObjectDelegate { +// MOCK_METHOD1(void, DemiurgeCreated(Demiurge*)); +// MOCK_METHOD2(void, OnRequest(int count, const string&)); +// +// void StoreDemiurge(Demiurge* w) { +// demiurge_ = w; +// } +// +// Demiurge* demiurge; +// } +// +// EXPECT_CALL(mock, DemiurgeCreated(_)).Times(1) +// .WillOnce(Invoke(CreateFunctor(&mock, &Mock::StoreDemiurge))); +// +// EXPECT_CALL(mock, OnRequest(_, StrEq("Moby Dick"))) +// .Times(AnyNumber()) +// .WillAlways(WithArgs<0>(Invoke( +// CreateFunctor(&mock->demiurge_, &Demiurge::DecreaseMonsters)))); +// + +#include "base/memory/linked_ptr.h" +#include "base/tuple.h" // for Tuple + +namespace testing { + +// 0 - 0 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple0& p, + const Tuple0& c) { + return (obj->*method)(); +} +template +inline R DispatchToFunction(Function function, + const Tuple0& p, + const Tuple0& c) { + return (*function)(); +} + +// 0 - 1 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple0& p, + const Tuple1& c) { + return (obj->*method)(c.a); +} +template +inline R DispatchToFunction(Function function, + const Tuple0& p, + const Tuple1& c) { + return (*function)(c.a); +} + +// 0 - 2 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple0& p, + const Tuple2& c) { + return (obj->*method)(c.a, c.b); +} +template +inline R DispatchToFunction(Function function, + const Tuple0& p, + const Tuple2& c) { + return (*function)(c.a, c.b); +} + +// 0 - 3 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple0& p, + const Tuple3& c) { + return (obj->*method)(c.a, c.b, c.c); +} +template +inline R DispatchToFunction(Function function, + const Tuple0& p, + const Tuple3& c) { + return (*function)(c.a, c.b, c.c); +} + +// 0 - 4 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple0& p, + const Tuple4& c) { + return (obj->*method)(c.a, c.b, c.c, c.d); +} +template +inline R DispatchToFunction(Function function, + const Tuple0& p, + const Tuple4& c) { + return (*function)(c.a, c.b, c.c, c.d); +} + +// 0 - 5 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple0& p, + const Tuple5& c) { + return (obj->*method)(c.a, c.b, c.c, c.d, c.e); +} +template +inline R DispatchToFunction(Function function, + const Tuple0& p, + const Tuple5& c) { + return (*function)(c.a, c.b, c.c, c.d, c.e); +} + +// 0 - 6 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple0& p, + const Tuple6& c) { + return (obj->*method)(c.a, c.b, c.c, c.d, c.e, c.f); +} +template +inline R DispatchToFunction(Function function, + const Tuple0& p, + const Tuple6& c) { + return (*function)(c.a, c.b, c.c, c.d, c.e, c.f); +} + +// 1 - 0 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple1& p, + const Tuple0& c) { + return (obj->*method)(p.a); +} +template +inline R DispatchToFunction(Function function, + const Tuple1& p, + const Tuple0& c) { + return (*function)(p.a); +} + +// 1 - 1 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple1& p, + const Tuple1& c) { + return (obj->*method)(p.a, c.a); +} +template +inline R DispatchToFunction(Function function, + const Tuple1& p, + const Tuple1& c) { + return (*function)(p.a, c.a); +} + +// 1 - 2 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple1& p, + const Tuple2& c) { + return (obj->*method)(p.a, c.a, c.b); +} +template +inline R DispatchToFunction(Function function, + const Tuple1& p, + const Tuple2& c) { + return (*function)(p.a, c.a, c.b); +} + +// 1 - 3 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple1& p, + const Tuple3& c) { + return (obj->*method)(p.a, c.a, c.b, c.c); +} +template +inline R DispatchToFunction(Function function, + const Tuple1& p, + const Tuple3& c) { + return (*function)(p.a, c.a, c.b, c.c); +} + +// 1 - 4 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple1& p, + const Tuple4& c) { + return (obj->*method)(p.a, c.a, c.b, c.c, c.d); +} +template +inline R DispatchToFunction(Function function, + const Tuple1& p, + const Tuple4& c) { + return (*function)(p.a, c.a, c.b, c.c, c.d); +} + +// 1 - 5 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple1& p, + const Tuple5& c) { + return (obj->*method)(p.a, c.a, c.b, c.c, c.d, c.e); +} +template +inline R DispatchToFunction(Function function, + const Tuple1& p, + const Tuple5& c) { + return (*function)(p.a, c.a, c.b, c.c, c.d, c.e); +} + +// 1 - 6 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple1& p, + const Tuple6& c) { + return (obj->*method)(p.a, c.a, c.b, c.c, c.d, c.e, c.f); +} +template +inline R DispatchToFunction(Function function, + const Tuple1& p, + const Tuple6& c) { + return (*function)(p.a, c.a, c.b, c.c, c.d, c.e, c.f); +} + +// 2 - 0 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple2& p, + const Tuple0& c) { + return (obj->*method)(p.a, p.b); +} +template +inline R DispatchToFunction(Function function, + const Tuple2& p, + const Tuple0& c) { + return (*function)(p.a, p.b); +} + +// 2 - 1 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple2& p, + const Tuple1& c) { + return (obj->*method)(p.a, p.b, c.a); +} +template +inline R DispatchToFunction(Function function, + const Tuple2& p, + const Tuple1& c) { + return (*function)(p.a, p.b, c.a); +} + +// 2 - 2 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple2& p, + const Tuple2& c) { + return (obj->*method)(p.a, p.b, c.a, c.b); +} +template +inline R DispatchToFunction(Function function, + const Tuple2& p, + const Tuple2& c) { + return (*function)(p.a, p.b, c.a, c.b); +} + +// 2 - 3 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple2& p, + const Tuple3& c) { + return (obj->*method)(p.a, p.b, c.a, c.b, c.c); +} +template +inline R DispatchToFunction(Function function, + const Tuple2& p, + const Tuple3& c) { + return (*function)(p.a, p.b, c.a, c.b, c.c); +} + +// 2 - 4 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple2& p, + const Tuple4& c) { + return (obj->*method)(p.a, p.b, c.a, c.b, c.c, c.d); +} +template +inline R DispatchToFunction(Function function, + const Tuple2& p, + const Tuple4& c) { + return (*function)(p.a, p.b, c.a, c.b, c.c, c.d); +} + +// 2 - 5 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple2& p, + const Tuple5& c) { + return (obj->*method)(p.a, p.b, c.a, c.b, c.c, c.d, c.e); +} +template +inline R DispatchToFunction(Function function, + const Tuple2& p, + const Tuple5& c) { + return (*function)(p.a, p.b, c.a, c.b, c.c, c.d, c.e); +} + +// 2 - 6 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple2& p, + const Tuple6& c) { + return (obj->*method)(p.a, p.b, c.a, c.b, c.c, c.d, c.e, c.f); +} +template +inline R DispatchToFunction(Function function, + const Tuple2& p, + const Tuple6& c) { + return (*function)(p.a, p.b, c.a, c.b, c.c, c.d, c.e, c.f); +} + +// 3 - 0 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple3& p, + const Tuple0& c) { + return (obj->*method)(p.a, p.b, p.c); +} +template +inline R DispatchToFunction(Function function, + const Tuple3& p, + const Tuple0& c) { + return (*function)(p.a, p.b, p.c); +} + +// 3 - 1 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple3& p, + const Tuple1& c) { + return (obj->*method)(p.a, p.b, p.c, c.a); +} +template +inline R DispatchToFunction(Function function, + const Tuple3& p, + const Tuple1& c) { + return (*function)(p.a, p.b, p.c, c.a); +} + +// 3 - 2 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple3& p, + const Tuple2& c) { + return (obj->*method)(p.a, p.b, p.c, c.a, c.b); +} +template +inline R DispatchToFunction(Function function, + const Tuple3& p, + const Tuple2& c) { + return (*function)(p.a, p.b, p.c, c.a, c.b); +} + +// 3 - 3 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple3& p, + const Tuple3& c) { + return (obj->*method)(p.a, p.b, p.c, c.a, c.b, c.c); +} +template +inline R DispatchToFunction(Function function, + const Tuple3& p, + const Tuple3& c) { + return (*function)(p.a, p.b, p.c, c.a, c.b, c.c); +} + +// 3 - 4 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple3& p, + const Tuple4& c) { + return (obj->*method)(p.a, p.b, p.c, c.a, c.b, c.c, c.d); +} +template +inline R DispatchToFunction(Function function, + const Tuple3& p, + const Tuple4& c) { + return (*function)(p.a, p.b, p.c, c.a, c.b, c.c, c.d); +} + +// 3 - 5 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple3& p, + const Tuple5& c) { + return (obj->*method)(p.a, p.b, p.c, c.a, c.b, c.c, c.d, c.e); +} +template +inline R DispatchToFunction(Function function, + const Tuple3& p, + const Tuple5& c) { + return (*function)(p.a, p.b, p.c, c.a, c.b, c.c, c.d, c.e); +} + +// 3 - 6 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple3& p, + const Tuple6& c) { + return (obj->*method)(p.a, p.b, p.c, c.a, c.b, c.c, c.d, c.e, c.f); +} +template +inline R DispatchToFunction(Function function, + const Tuple3& p, + const Tuple6& c) { + return (*function)(p.a, p.b, p.c, c.a, c.b, c.c, c.d, c.e, c.f); +} + +// 4 - 0 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple4& p, + const Tuple0& c) { + return (obj->*method)(p.a, p.b, p.c, p.d); +} +template +inline R DispatchToFunction(Function function, + const Tuple4& p, + const Tuple0& c) { + return (*function)(p.a, p.b, p.c, p.d); +} + +// 4 - 1 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple4& p, + const Tuple1& c) { + return (obj->*method)(p.a, p.b, p.c, p.d, c.a); +} +template +inline R DispatchToFunction(Function function, + const Tuple4& p, + const Tuple1& c) { + return (*function)(p.a, p.b, p.c, p.d, c.a); +} + +// 4 - 2 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple4& p, + const Tuple2& c) { + return (obj->*method)(p.a, p.b, p.c, p.d, c.a, c.b); +} +template +inline R DispatchToFunction(Function function, + const Tuple4& p, + const Tuple2& c) { + return (*function)(p.a, p.b, p.c, p.d, c.a, c.b); +} + +// 4 - 3 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple4& p, + const Tuple3& c) { + return (obj->*method)(p.a, p.b, p.c, p.d, c.a, c.b, c.c); +} +template +inline R DispatchToFunction(Function function, + const Tuple4& p, + const Tuple3& c) { + return (*function)(p.a, p.b, p.c, p.d, c.a, c.b, c.c); +} + +// 4 - 4 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple4& p, + const Tuple4& c) { + return (obj->*method)(p.a, p.b, p.c, p.d, c.a, c.b, c.c, c.d); +} +template +inline R DispatchToFunction(Function function, + const Tuple4& p, + const Tuple4& c) { + return (*function)(p.a, p.b, p.c, p.d, c.a, c.b, c.c, c.d); +} + +// 4 - 5 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple4& p, + const Tuple5& c) { + return (obj->*method)(p.a, p.b, p.c, p.d, c.a, c.b, c.c, c.d, c.e); +} +template +inline R DispatchToFunction(Function function, + const Tuple4& p, + const Tuple5& c) { + return (*function)(p.a, p.b, p.c, p.d, c.a, c.b, c.c, c.d, c.e); +} + +// 4 - 6 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple4& p, + const Tuple6& c) { + return (obj->*method)(p.a, p.b, p.c, p.d, c.a, c.b, c.c, c.d, c.e, c.f); +} +template +inline R DispatchToFunction(Function function, + const Tuple4& p, + const Tuple6& c) { + return (*function)(p.a, p.b, p.c, p.d, c.a, c.b, c.c, c.d, c.e, c.f); +} + +// 5 - 0 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple5& p, + const Tuple0& c) { + return (obj->*method)(p.a, p.b, p.c, p.d, p.e); +} +template +inline R DispatchToFunction(Function function, + const Tuple5& p, + const Tuple0& c) { + return (*function)(p.a, p.b, p.c, p.d, p.e); +} + +// 5 - 1 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple5& p, + const Tuple1& c) { + return (obj->*method)(p.a, p.b, p.c, p.d, p.e, c.a); +} +template +inline R DispatchToFunction(Function function, + const Tuple5& p, + const Tuple1& c) { + return (*function)(p.a, p.b, p.c, p.d, p.e, c.a); +} + +// 5 - 2 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple5& p, + const Tuple2& c) { + return (obj->*method)(p.a, p.b, p.c, p.d, p.e, c.a, c.b); +} +template +inline R DispatchToFunction(Function function, + const Tuple5& p, + const Tuple2& c) { + return (*function)(p.a, p.b, p.c, p.d, p.e, c.a, c.b); +} + +// 5 - 3 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple5& p, + const Tuple3& c) { + return (obj->*method)(p.a, p.b, p.c, p.d, p.e, c.a, c.b, c.c); +} +template +inline R DispatchToFunction(Function function, + const Tuple5& p, + const Tuple3& c) { + return (*function)(p.a, p.b, p.c, p.d, p.e, c.a, c.b, c.c); +} + +// 5 - 4 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple5& p, + const Tuple4& c) { + return (obj->*method)(p.a, p.b, p.c, p.d, p.e, c.a, c.b, c.c, c.d); +} +template +inline R DispatchToFunction(Function function, + const Tuple5& p, + const Tuple4& c) { + return (*function)(p.a, p.b, p.c, p.d, p.e, c.a, c.b, c.c, c.d); +} + +// 5 - 5 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple5& p, + const Tuple5& c) { + return (obj->*method)(p.a, p.b, p.c, p.d, p.e, c.a, c.b, c.c, c.d, c.e); +} +template +inline R DispatchToFunction(Function function, + const Tuple5& p, + const Tuple5& c) { + return (*function)(p.a, p.b, p.c, p.d, p.e, c.a, c.b, c.c, c.d, c.e); +} + +// 5 - 6 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple5& p, + const Tuple6& c) { + return (obj->*method)(p.a, p.b, p.c, p.d, p.e, c.a, c.b, c.c, c.d, c.e, c.f); +} +template +inline R DispatchToFunction(Function function, + const Tuple5& p, + const Tuple6& c) { + return (*function)(p.a, p.b, p.c, p.d, p.e, c.a, c.b, c.c, c.d, c.e, c.f); +} + +// 6 - 0 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple6& p, + const Tuple0& c) { + return (obj->*method)(p.a, p.b, p.c, p.d, p.e, p.f); +} +template +inline R DispatchToFunction(Function function, + const Tuple6& p, + const Tuple0& c) { + return (*function)(p.a, p.b, p.c, p.d, p.e, p.f); +} + +// 6 - 1 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple6& p, + const Tuple1& c) { + return (obj->*method)(p.a, p.b, p.c, p.d, p.e, p.f, c.a); +} +template +inline R DispatchToFunction(Function function, + const Tuple6& p, + const Tuple1& c) { + return (*function)(p.a, p.b, p.c, p.d, p.e, p.f, c.a); +} + +// 6 - 2 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple6& p, + const Tuple2& c) { + return (obj->*method)(p.a, p.b, p.c, p.d, p.e, p.f, c.a, c.b); +} +template +inline R DispatchToFunction(Function function, + const Tuple6& p, + const Tuple2& c) { + return (*function)(p.a, p.b, p.c, p.d, p.e, p.f, c.a, c.b); +} + +// 6 - 3 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple6& p, + const Tuple3& c) { + return (obj->*method)(p.a, p.b, p.c, p.d, p.e, p.f, c.a, c.b, c.c); +} +template +inline R DispatchToFunction(Function function, + const Tuple6& p, + const Tuple3& c) { + return (*function)(p.a, p.b, p.c, p.d, p.e, p.f, c.a, c.b, c.c); +} + +// 6 - 4 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple6& p, + const Tuple4& c) { + return (obj->*method)(p.a, p.b, p.c, p.d, p.e, p.f, c.a, c.b, c.c, c.d); +} +template +inline R DispatchToFunction(Function function, + const Tuple6& p, + const Tuple4& c) { + return (*function)(p.a, p.b, p.c, p.d, p.e, p.f, c.a, c.b, c.c, c.d); +} + +// 6 - 5 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple6& p, + const Tuple5& c) { + return (obj->*method)(p.a, p.b, p.c, p.d, p.e, p.f, c.a, c.b, c.c, c.d, c.e); +} +template +inline R DispatchToFunction(Function function, + const Tuple6& p, + const Tuple5& c) { + return (*function)(p.a, p.b, p.c, p.d, p.e, p.f, c.a, c.b, c.c, c.d, c.e); +} + +// 6 - 6 +template +inline R DispatchToMethod(T* obj, Method method, + const Tuple6& p, + const Tuple6& c) { + return (obj->*method)(p.a, p.b, p.c, p.d, p.e, p.f, c.a, c.b, c.c, c.d, c.e, c.f); +} +template +inline R DispatchToFunction(Function function, + const Tuple6& p, + const Tuple6& c) { + return (*function)(p.a, p.b, p.c, p.d, p.e, p.f, c.a, c.b, c.c, c.d, c.e, c.f); +} + +// Interface that is exposed to the consumer, that does the actual calling +// of the method. +template +class MutantRunner { + public: + virtual R RunWithParams(const Params& params) = 0; + virtual ~MutantRunner() {} +}; + +// Mutant holds pre-bound arguments (like Task). Like Callback +// allows call-time arguments. You bind a pointer to the object +// at creation time. +template +class Mutant : public MutantRunner { + public: + Mutant(T* obj, Method method, const PreBound& pb) + : obj_(obj), method_(method), pb_(pb) { + } + + // MutantRunner implementation + virtual R RunWithParams(const Params& params) { + return DispatchToMethod(this->obj_, this->method_, pb_, params); + } + + T* obj_; + Method method_; + PreBound pb_; +}; + +template +class MutantFunction : public MutantRunner { + public: + MutantFunction(Function function, const PreBound& pb) + : function_(function), pb_(pb) { + } + + // MutantRunner implementation + virtual R RunWithParams(const Params& params) { + return DispatchToFunction(function_, pb_, params); + } + + Function function_; + PreBound pb_; +}; + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +// MutantLateBind is like Mutant, but you bind a pointer to a pointer +// to the object. This way you can create actions for an object +// that is not yet created (has only storage for a pointer to it). +template +class MutantLateObjectBind : public MutantRunner { + public: + MutantLateObjectBind(T** obj, Method method, const PreBound& pb) + : obj_(obj), method_(method), pb_(pb) { + } + + // MutantRunner implementation. + virtual R RunWithParams(const Params& params) { + EXPECT_THAT(*this->obj_, testing::NotNull()); + if (NULL == *this->obj_) + return R(); + return DispatchToMethod( *this->obj_, this->method_, pb_, params); + } + + T** obj_; + Method method_; + PreBound pb_; +}; +#endif + +// Simple MutantRunner<> wrapper acting as a functor. +// Redirects operator() to MutantRunner::Run() +template +struct MutantFunctor { + explicit MutantFunctor(MutantRunner* cb) : impl_(cb) { + } + + ~MutantFunctor() { + } + + inline R operator()() { + return impl_->RunWithParams(Tuple0()); + } + + template + inline R operator()(const Arg1& a) { + return impl_->RunWithParams(Params(a)); + } + + template + inline R operator()(const Arg1& a, const Arg2& b) { + return impl_->RunWithParams(Params(a, b)); + } + + template + inline R operator()(const Arg1& a, const Arg2& b, const Arg3& c) { + return impl_->RunWithParams(Params(a, b, c)); + } + + template + inline R operator()(const Arg1& a, const Arg2& b, const Arg3& c, + const Arg4& d) { + return impl_->RunWithParams(Params(a, b, c, d)); + } + + private: + // We need copy constructor since MutantFunctor is copied few times + // inside GMock machinery, hence no DISALLOW_EVIL_CONTRUCTORS + MutantFunctor(); + linked_ptr > impl_; +}; + +// 0 - 0 +template +inline MutantFunctor +CreateFunctor(T* obj, R (U::*method)()) { + MutantRunner* t = + new Mutant + (obj, method, MakeTuple()); + return MutantFunctor(t); +} + +template +inline MutantFunctor +CreateFunctor(R (*function)()) { + MutantRunner* t = + new MutantFunction + (function, MakeTuple()); + return MutantFunctor(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor +CreateFunctor(T** obj, R (U::*method)()) { + MutantRunner* t = + new MutantLateObjectBind + (obj, method, MakeTuple()); + return MutantFunctor(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor +CreateFunctor(T* obj, R (__stdcall U::*method)()) { + MutantRunner* t = + new Mutant + (obj, method, MakeTuple()); + return MutantFunctor(t); +} + +template +inline MutantFunctor +CreateFunctor(R (__stdcall *function)()) { + MutantRunner* t = + new MutantFunction + (function, MakeTuple()); + return MutantFunctor(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor +CreateFunctor(T** obj, R (__stdcall U::*method)()) { + MutantRunner* t = + new MutantLateObjectBind + (obj, method, MakeTuple()); + return MutantFunctor(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 0 - 1 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(A1)) { + MutantRunner >* t = + new Mutant > + (obj, method, MakeTuple()); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(A1)) { + MutantRunner >* t = + new MutantFunction > + (function, MakeTuple()); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(A1)) { + MutantRunner >* t = + new MutantLateObjectBind > + (obj, method, MakeTuple()); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(A1)) { + MutantRunner >* t = + new Mutant > + (obj, method, MakeTuple()); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(A1)) { + MutantRunner >* t = + new MutantFunction > + (function, MakeTuple()); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(A1)) { + MutantRunner >* t = + new MutantLateObjectBind > + (obj, method, MakeTuple()); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 0 - 2 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(A1, A2)) { + MutantRunner >* t = + new Mutant > + (obj, method, MakeTuple()); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(A1, A2)) { + MutantRunner >* t = + new MutantFunction > + (function, MakeTuple()); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(A1, A2)) { + MutantRunner >* t = + new MutantLateObjectBind > + (obj, method, MakeTuple()); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(A1, A2)) { + MutantRunner >* t = + new Mutant > + (obj, method, MakeTuple()); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(A1, A2)) { + MutantRunner >* t = + new MutantFunction > + (function, MakeTuple()); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(A1, A2)) { + MutantRunner >* t = + new MutantLateObjectBind > + (obj, method, MakeTuple()); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 0 - 3 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(A1, A2, A3)) { + MutantRunner >* t = + new Mutant > + (obj, method, MakeTuple()); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(A1, A2, A3)) { + MutantRunner >* t = + new MutantFunction > + (function, MakeTuple()); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(A1, A2, A3)) { + MutantRunner >* t = + new MutantLateObjectBind > + (obj, method, MakeTuple()); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(A1, A2, A3)) { + MutantRunner >* t = + new Mutant > + (obj, method, MakeTuple()); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(A1, A2, A3)) { + MutantRunner >* t = + new MutantFunction > + (function, MakeTuple()); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(A1, A2, A3)) { + MutantRunner >* t = + new MutantLateObjectBind > + (obj, method, MakeTuple()); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 0 - 4 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(A1, A2, A3, A4)) { + MutantRunner >* t = + new Mutant > + (obj, method, MakeTuple()); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(A1, A2, A3, A4)) { + MutantRunner >* t = + new MutantFunction > + (function, MakeTuple()); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(A1, A2, A3, A4)) { + MutantRunner >* t = + new MutantLateObjectBind > + (obj, method, MakeTuple()); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(A1, A2, A3, A4)) { + MutantRunner >* t = + new Mutant > + (obj, method, MakeTuple()); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(A1, A2, A3, A4)) { + MutantRunner >* t = + new MutantFunction > + (function, MakeTuple()); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(A1, A2, A3, A4)) { + MutantRunner >* t = + new MutantLateObjectBind > + (obj, method, MakeTuple()); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 0 - 5 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(A1, A2, A3, A4, A5)) { + MutantRunner >* t = + new Mutant > + (obj, method, MakeTuple()); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(A1, A2, A3, A4, A5)) { + MutantRunner >* t = + new MutantFunction > + (function, MakeTuple()); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(A1, A2, A3, A4, A5)) { + MutantRunner >* t = + new MutantLateObjectBind > + (obj, method, MakeTuple()); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(A1, A2, A3, A4, A5)) { + MutantRunner >* t = + new Mutant > + (obj, method, MakeTuple()); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(A1, A2, A3, A4, A5)) { + MutantRunner >* t = + new MutantFunction > + (function, MakeTuple()); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(A1, A2, A3, A4, A5)) { + MutantRunner >* t = + new MutantLateObjectBind > + (obj, method, MakeTuple()); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 0 - 6 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(A1, A2, A3, A4, A5, A6)) { + MutantRunner >* t = + new Mutant > + (obj, method, MakeTuple()); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(A1, A2, A3, A4, A5, A6)) { + MutantRunner >* t = + new MutantFunction > + (function, MakeTuple()); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(A1, A2, A3, A4, A5, A6)) { + MutantRunner >* t = + new MutantLateObjectBind > + (obj, method, MakeTuple()); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(A1, A2, A3, A4, A5, A6)) { + MutantRunner >* t = + new Mutant > + (obj, method, MakeTuple()); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(A1, A2, A3, A4, A5, A6)) { + MutantRunner >* t = + new MutantFunction > + (function, MakeTuple()); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(A1, A2, A3, A4, A5, A6)) { + MutantRunner >* t = + new MutantLateObjectBind > + (obj, method, MakeTuple()); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 1 - 0 +template +inline MutantFunctor +CreateFunctor(T* obj, R (U::*method)(X1), const P1& p1) { + MutantRunner* t = + new Mutant, Tuple0> + (obj, method, MakeTuple(p1)); + return MutantFunctor(t); +} + +template +inline MutantFunctor +CreateFunctor(R (*function)(X1), const P1& p1) { + MutantRunner* t = + new MutantFunction, Tuple0> + (function, MakeTuple(p1)); + return MutantFunctor(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor +CreateFunctor(T** obj, R (U::*method)(X1), const P1& p1) { + MutantRunner* t = + new MutantLateObjectBind, Tuple0> + (obj, method, MakeTuple(p1)); + return MutantFunctor(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor +CreateFunctor(T* obj, R (__stdcall U::*method)(X1), const P1& p1) { + MutantRunner* t = + new Mutant, Tuple0> + (obj, method, MakeTuple(p1)); + return MutantFunctor(t); +} + +template +inline MutantFunctor +CreateFunctor(R (__stdcall *function)(X1), const P1& p1) { + MutantRunner* t = + new MutantFunction, Tuple0> + (function, MakeTuple(p1)); + return MutantFunctor(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor +CreateFunctor(T** obj, R (__stdcall U::*method)(X1), const P1& p1) { + MutantRunner* t = + new MutantLateObjectBind, Tuple0> + (obj, method, MakeTuple(p1)); + return MutantFunctor(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 1 - 1 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, A1), const P1& p1) { + MutantRunner >* t = + new Mutant, Tuple1 > + (obj, method, MakeTuple(p1)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, A1), const P1& p1) { + MutantRunner >* t = + new MutantFunction, Tuple1 > + (function, MakeTuple(p1)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, A1), const P1& p1) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple1 > + (obj, method, MakeTuple(p1)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, A1), const P1& p1) { + MutantRunner >* t = + new Mutant, Tuple1 > + (obj, method, MakeTuple(p1)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, A1), const P1& p1) { + MutantRunner >* t = + new MutantFunction, Tuple1 > + (function, MakeTuple(p1)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, A1), const P1& p1) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple1 > + (obj, method, MakeTuple(p1)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 1 - 2 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, A1, A2), const P1& p1) { + MutantRunner >* t = + new Mutant, Tuple2 > + (obj, method, MakeTuple(p1)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, A1, A2), const P1& p1) { + MutantRunner >* t = + new MutantFunction, Tuple2 > + (function, MakeTuple(p1)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, A1, A2), const P1& p1) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple2 > + (obj, method, MakeTuple(p1)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, A1, A2), const P1& p1) { + MutantRunner >* t = + new Mutant, Tuple2 > + (obj, method, MakeTuple(p1)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, A1, A2), const P1& p1) { + MutantRunner >* t = + new MutantFunction, Tuple2 > + (function, MakeTuple(p1)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, A1, A2), const P1& p1) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple2 > + (obj, method, MakeTuple(p1)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 1 - 3 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, A1, A2, A3), const P1& p1) { + MutantRunner >* t = + new Mutant, Tuple3 > + (obj, method, MakeTuple(p1)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, A1, A2, A3), const P1& p1) { + MutantRunner >* t = + new MutantFunction, Tuple3 > + (function, MakeTuple(p1)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, A1, A2, A3), const P1& p1) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple3 > + (obj, method, MakeTuple(p1)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, A1, A2, A3), const P1& p1) { + MutantRunner >* t = + new Mutant, Tuple3 > + (obj, method, MakeTuple(p1)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, A1, A2, A3), const P1& p1) { + MutantRunner >* t = + new MutantFunction, Tuple3 > + (function, MakeTuple(p1)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, A1, A2, A3), const P1& p1) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple3 > + (obj, method, MakeTuple(p1)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 1 - 4 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, A1, A2, A3, A4), const P1& p1) { + MutantRunner >* t = + new Mutant, Tuple4 > + (obj, method, MakeTuple(p1)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, A1, A2, A3, A4), const P1& p1) { + MutantRunner >* t = + new MutantFunction, Tuple4 > + (function, MakeTuple(p1)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, A1, A2, A3, A4), const P1& p1) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple4 > + (obj, method, MakeTuple(p1)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, A1, A2, A3, A4), + const P1& p1) { + MutantRunner >* t = + new Mutant, Tuple4 > + (obj, method, MakeTuple(p1)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, A1, A2, A3, A4), const P1& p1) { + MutantRunner >* t = + new MutantFunction, Tuple4 > + (function, MakeTuple(p1)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, A1, A2, A3, A4), + const P1& p1) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple4 > + (obj, method, MakeTuple(p1)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 1 - 5 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, A1, A2, A3, A4, A5), const P1& p1) { + MutantRunner >* t = + new Mutant, Tuple5 > + (obj, method, MakeTuple(p1)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, A1, A2, A3, A4, A5), const P1& p1) { + MutantRunner >* t = + new MutantFunction, Tuple5 > + (function, MakeTuple(p1)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, A1, A2, A3, A4, A5), const P1& p1) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple5 > + (obj, method, MakeTuple(p1)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, A1, A2, A3, A4, A5), + const P1& p1) { + MutantRunner >* t = + new Mutant, Tuple5 > + (obj, method, MakeTuple(p1)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, A1, A2, A3, A4, A5), const P1& p1) { + MutantRunner >* t = + new MutantFunction, Tuple5 > + (function, MakeTuple(p1)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, A1, A2, A3, A4, A5), + const P1& p1) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple5 > + (obj, method, MakeTuple(p1)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 1 - 6 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, A1, A2, A3, A4, A5, A6), + const P1& p1) { + MutantRunner >* t = + new Mutant, Tuple6 > + (obj, method, MakeTuple(p1)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, A1, A2, A3, A4, A5, A6), const P1& p1) { + MutantRunner >* t = + new MutantFunction, Tuple6 > + (function, MakeTuple(p1)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, A1, A2, A3, A4, A5, A6), + const P1& p1) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple6 > + (obj, method, MakeTuple(p1)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, A1, A2, A3, A4, A5, A6), + const P1& p1) { + MutantRunner >* t = + new Mutant, Tuple6 > + (obj, method, MakeTuple(p1)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, A1, A2, A3, A4, A5, A6), + const P1& p1) { + MutantRunner >* t = + new MutantFunction, Tuple6 > + (function, MakeTuple(p1)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, A1, A2, A3, A4, A5, A6), + const P1& p1) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple6 > + (obj, method, MakeTuple(p1)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 2 - 0 +template +inline MutantFunctor +CreateFunctor(T* obj, R (U::*method)(X1, X2), const P1& p1, const P2& p2) { + MutantRunner* t = + new Mutant, Tuple0> + (obj, method, MakeTuple(p1, p2)); + return MutantFunctor(t); +} + +template +inline MutantFunctor +CreateFunctor(R (*function)(X1, X2), const P1& p1, const P2& p2) { + MutantRunner* t = + new MutantFunction, Tuple0> + (function, MakeTuple(p1, p2)); + return MutantFunctor(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor +CreateFunctor(T** obj, R (U::*method)(X1, X2), const P1& p1, const P2& p2) { + MutantRunner* t = + new MutantLateObjectBind, Tuple0> + (obj, method, MakeTuple(p1, p2)); + return MutantFunctor(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2), const P1& p1, + const P2& p2) { + MutantRunner* t = + new Mutant, Tuple0> + (obj, method, MakeTuple(p1, p2)); + return MutantFunctor(t); +} + +template +inline MutantFunctor +CreateFunctor(R (__stdcall *function)(X1, X2), const P1& p1, const P2& p2) { + MutantRunner* t = + new MutantFunction, Tuple0> + (function, MakeTuple(p1, p2)); + return MutantFunctor(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2), const P1& p1, + const P2& p2) { + MutantRunner* t = + new MutantLateObjectBind, Tuple0> + (obj, method, MakeTuple(p1, p2)); + return MutantFunctor(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 2 - 1 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, X2, A1), const P1& p1, const P2& p2) { + MutantRunner >* t = + new Mutant, Tuple1 > + (obj, method, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, X2, A1), const P1& p1, const P2& p2) { + MutantRunner >* t = + new MutantFunction, Tuple1 > + (function, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, X2, A1), const P1& p1, const P2& p2) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple1 > + (obj, method, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2, A1), const P1& p1, + const P2& p2) { + MutantRunner >* t = + new Mutant, Tuple1 > + (obj, method, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, X2, A1), const P1& p1, + const P2& p2) { + MutantRunner >* t = + new MutantFunction, Tuple1 > + (function, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2, A1), const P1& p1, + const P2& p2) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple1 > + (obj, method, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 2 - 2 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, X2, A1, A2), const P1& p1, + const P2& p2) { + MutantRunner >* t = + new Mutant, Tuple2 > + (obj, method, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, X2, A1, A2), const P1& p1, const P2& p2) { + MutantRunner >* t = + new MutantFunction, Tuple2 > + (function, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, X2, A1, A2), const P1& p1, + const P2& p2) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple2 > + (obj, method, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2, A1, A2), const P1& p1, + const P2& p2) { + MutantRunner >* t = + new Mutant, Tuple2 > + (obj, method, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, X2, A1, A2), const P1& p1, + const P2& p2) { + MutantRunner >* t = + new MutantFunction, Tuple2 > + (function, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2, A1, A2), const P1& p1, + const P2& p2) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple2 > + (obj, method, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 2 - 3 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, X2, A1, A2, A3), const P1& p1, + const P2& p2) { + MutantRunner >* t = + new Mutant, Tuple3 > + (obj, method, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, X2, A1, A2, A3), const P1& p1, const P2& p2) { + MutantRunner >* t = + new MutantFunction, Tuple3 > + (function, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, X2, A1, A2, A3), const P1& p1, + const P2& p2) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple3 > + (obj, method, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2, A1, A2, A3), + const P1& p1, const P2& p2) { + MutantRunner >* t = + new Mutant, Tuple3 > + (obj, method, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, X2, A1, A2, A3), const P1& p1, + const P2& p2) { + MutantRunner >* t = + new MutantFunction, Tuple3 > + (function, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2, A1, A2, A3), + const P1& p1, const P2& p2) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple3 > + (obj, method, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 2 - 4 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, X2, A1, A2, A3, A4), const P1& p1, + const P2& p2) { + MutantRunner >* t = + new Mutant, Tuple4 > + (obj, method, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, X2, A1, A2, A3, A4), const P1& p1, + const P2& p2) { + MutantRunner >* t = + new MutantFunction, Tuple4 > + (function, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, X2, A1, A2, A3, A4), const P1& p1, + const P2& p2) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple4 > + (obj, method, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2, A1, A2, A3, A4), + const P1& p1, const P2& p2) { + MutantRunner >* t = + new Mutant, Tuple4 > + (obj, method, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, X2, A1, A2, A3, A4), const P1& p1, + const P2& p2) { + MutantRunner >* t = + new MutantFunction, Tuple4 > + (function, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2, A1, A2, A3, A4), + const P1& p1, const P2& p2) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple4 > + (obj, method, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 2 - 5 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, X2, A1, A2, A3, A4, A5), const P1& p1, + const P2& p2) { + MutantRunner >* t = + new Mutant, Tuple5 > + (obj, method, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, X2, A1, A2, A3, A4, A5), const P1& p1, + const P2& p2) { + MutantRunner >* t = + new MutantFunction, Tuple5 > + (function, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, X2, A1, A2, A3, A4, A5), const P1& p1, + const P2& p2) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple5 > + (obj, method, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2, A1, A2, A3, A4, A5), + const P1& p1, const P2& p2) { + MutantRunner >* t = + new Mutant, Tuple5 > + (obj, method, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, X2, A1, A2, A3, A4, A5), const P1& p1, + const P2& p2) { + MutantRunner >* t = + new MutantFunction, Tuple5 > + (function, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2, A1, A2, A3, A4, A5), + const P1& p1, const P2& p2) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple5 > + (obj, method, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 2 - 6 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, X2, A1, A2, A3, A4, A5, A6), + const P1& p1, const P2& p2) { + MutantRunner >* t = + new Mutant, Tuple6 > + (obj, method, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, X2, A1, A2, A3, A4, A5, A6), const P1& p1, + const P2& p2) { + MutantRunner >* t = + new MutantFunction, Tuple6 > + (function, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, X2, A1, A2, A3, A4, A5, A6), + const P1& p1, const P2& p2) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple6 > + (obj, method, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2, A1, A2, A3, A4, A5, A6), + const P1& p1, const P2& p2) { + MutantRunner >* t = + new Mutant, Tuple6 > + (obj, method, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, X2, A1, A2, A3, A4, A5, A6), + const P1& p1, const P2& p2) { + MutantRunner >* t = + new MutantFunction, Tuple6 > + (function, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2, A1, A2, A3, A4, A5, A6), + const P1& p1, const P2& p2) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple6 > + (obj, method, MakeTuple(p1, p2)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 3 - 0 +template +inline MutantFunctor +CreateFunctor(T* obj, R (U::*method)(X1, X2, X3), const P1& p1, const P2& p2, + const P3& p3) { + MutantRunner* t = + new Mutant, Tuple0> + (obj, method, MakeTuple(p1, p2, p3)); + return MutantFunctor(t); +} + +template +inline MutantFunctor +CreateFunctor(R (*function)(X1, X2, X3), const P1& p1, const P2& p2, + const P3& p3) { + MutantRunner* t = + new MutantFunction, Tuple0> + (function, MakeTuple(p1, p2, p3)); + return MutantFunctor(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor +CreateFunctor(T** obj, R (U::*method)(X1, X2, X3), const P1& p1, const P2& p2, + const P3& p3) { + MutantRunner* t = + new MutantLateObjectBind, Tuple0> + (obj, method, MakeTuple(p1, p2, p3)); + return MutantFunctor(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2, X3), const P1& p1, + const P2& p2, const P3& p3) { + MutantRunner* t = + new Mutant, Tuple0> + (obj, method, MakeTuple(p1, p2, p3)); + return MutantFunctor(t); +} + +template +inline MutantFunctor +CreateFunctor(R (__stdcall *function)(X1, X2, X3), const P1& p1, const P2& p2, + const P3& p3) { + MutantRunner* t = + new MutantFunction, Tuple0> + (function, MakeTuple(p1, p2, p3)); + return MutantFunctor(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2, X3), const P1& p1, + const P2& p2, const P3& p3) { + MutantRunner* t = + new MutantLateObjectBind, Tuple0> + (obj, method, MakeTuple(p1, p2, p3)); + return MutantFunctor(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 3 - 1 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, X2, X3, A1), const P1& p1, + const P2& p2, const P3& p3) { + MutantRunner >* t = + new Mutant, Tuple1 > + (obj, method, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, X2, X3, A1), const P1& p1, const P2& p2, + const P3& p3) { + MutantRunner >* t = + new MutantFunction, Tuple1 > + (function, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, X2, X3, A1), const P1& p1, + const P2& p2, const P3& p3) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple1 > + (obj, method, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2, X3, A1), const P1& p1, + const P2& p2, const P3& p3) { + MutantRunner >* t = + new Mutant, Tuple1 > + (obj, method, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, X2, X3, A1), const P1& p1, + const P2& p2, const P3& p3) { + MutantRunner >* t = + new MutantFunction, Tuple1 > + (function, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2, X3, A1), const P1& p1, + const P2& p2, const P3& p3) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple1 > + (obj, method, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 3 - 2 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, X2, X3, A1, A2), const P1& p1, + const P2& p2, const P3& p3) { + MutantRunner >* t = + new Mutant, Tuple2 > + (obj, method, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, X2, X3, A1, A2), const P1& p1, const P2& p2, + const P3& p3) { + MutantRunner >* t = + new MutantFunction, Tuple2 > + (function, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, X2, X3, A1, A2), const P1& p1, + const P2& p2, const P3& p3) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple2 > + (obj, method, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2, X3, A1, A2), + const P1& p1, const P2& p2, const P3& p3) { + MutantRunner >* t = + new Mutant, Tuple2 > + (obj, method, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, X2, X3, A1, A2), const P1& p1, + const P2& p2, const P3& p3) { + MutantRunner >* t = + new MutantFunction, Tuple2 > + (function, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2, X3, A1, A2), + const P1& p1, const P2& p2, const P3& p3) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple2 > + (obj, method, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 3 - 3 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, X2, X3, A1, A2, A3), const P1& p1, + const P2& p2, const P3& p3) { + MutantRunner >* t = + new Mutant, Tuple3 > + (obj, method, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, X2, X3, A1, A2, A3), const P1& p1, const P2& p2, + const P3& p3) { + MutantRunner >* t = + new MutantFunction, Tuple3 > + (function, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, X2, X3, A1, A2, A3), const P1& p1, + const P2& p2, const P3& p3) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple3 > + (obj, method, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2, X3, A1, A2, A3), + const P1& p1, const P2& p2, const P3& p3) { + MutantRunner >* t = + new Mutant, Tuple3 > + (obj, method, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, X2, X3, A1, A2, A3), const P1& p1, + const P2& p2, const P3& p3) { + MutantRunner >* t = + new MutantFunction, Tuple3 > + (function, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2, X3, A1, A2, A3), + const P1& p1, const P2& p2, const P3& p3) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple3 > + (obj, method, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 3 - 4 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, X2, X3, A1, A2, A3, A4), const P1& p1, + const P2& p2, const P3& p3) { + MutantRunner >* t = + new Mutant, Tuple4 > + (obj, method, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, X2, X3, A1, A2, A3, A4), const P1& p1, + const P2& p2, const P3& p3) { + MutantRunner >* t = + new MutantFunction, Tuple4 > + (function, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, X2, X3, A1, A2, A3, A4), const P1& p1, + const P2& p2, const P3& p3) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple4 > + (obj, method, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2, X3, A1, A2, A3, A4), + const P1& p1, const P2& p2, const P3& p3) { + MutantRunner >* t = + new Mutant, Tuple4 > + (obj, method, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, X2, X3, A1, A2, A3, A4), const P1& p1, + const P2& p2, const P3& p3) { + MutantRunner >* t = + new MutantFunction, Tuple4 > + (function, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2, X3, A1, A2, A3, A4), + const P1& p1, const P2& p2, const P3& p3) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple4 > + (obj, method, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 3 - 5 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, X2, X3, A1, A2, A3, A4, A5), + const P1& p1, const P2& p2, const P3& p3) { + MutantRunner >* t = + new Mutant, Tuple5 > + (obj, method, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, X2, X3, A1, A2, A3, A4, A5), const P1& p1, + const P2& p2, const P3& p3) { + MutantRunner >* t = + new MutantFunction, Tuple5 > + (function, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, X2, X3, A1, A2, A3, A4, A5), + const P1& p1, const P2& p2, const P3& p3) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple5 > + (obj, method, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2, X3, A1, A2, A3, A4, A5), + const P1& p1, const P2& p2, const P3& p3) { + MutantRunner >* t = + new Mutant, Tuple5 > + (obj, method, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, X2, X3, A1, A2, A3, A4, A5), + const P1& p1, const P2& p2, const P3& p3) { + MutantRunner >* t = + new MutantFunction, Tuple5 > + (function, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2, X3, A1, A2, A3, A4, A5), + const P1& p1, const P2& p2, const P3& p3) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple5 > + (obj, method, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 3 - 6 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, X2, X3, A1, A2, A3, A4, A5, A6), + const P1& p1, const P2& p2, const P3& p3) { + MutantRunner >* t = + new Mutant, Tuple6 > + (obj, method, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, X2, X3, A1, A2, A3, A4, A5, A6), const P1& p1, + const P2& p2, const P3& p3) { + MutantRunner >* t = + new MutantFunction, Tuple6 > + (function, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, X2, X3, A1, A2, A3, A4, A5, A6), + const P1& p1, const P2& p2, const P3& p3) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple6 > + (obj, method, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2, X3, A1, A2, A3, A4, A5, + A6), const P1& p1, const P2& p2, const P3& p3) { + MutantRunner >* t = + new Mutant, Tuple6 > + (obj, method, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, X2, X3, A1, A2, A3, A4, A5, A6), + const P1& p1, const P2& p2, const P3& p3) { + MutantRunner >* t = + new MutantFunction, Tuple6 > + (function, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2, X3, A1, A2, A3, A4, A5, + A6), const P1& p1, const P2& p2, const P3& p3) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple6 > + (obj, method, MakeTuple(p1, p2, p3)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 4 - 0 +template +inline MutantFunctor +CreateFunctor(T* obj, R (U::*method)(X1, X2, X3, X4), const P1& p1, + const P2& p2, const P3& p3, const P4& p4) { + MutantRunner* t = + new Mutant, Tuple0> + (obj, method, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor(t); +} + +template +inline MutantFunctor +CreateFunctor(R (*function)(X1, X2, X3, X4), const P1& p1, const P2& p2, + const P3& p3, const P4& p4) { + MutantRunner* t = + new MutantFunction, Tuple0> + (function, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor +CreateFunctor(T** obj, R (U::*method)(X1, X2, X3, X4), const P1& p1, + const P2& p2, const P3& p3, const P4& p4) { + MutantRunner* t = + new MutantLateObjectBind, Tuple0> + (obj, method, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2, X3, X4), const P1& p1, + const P2& p2, const P3& p3, const P4& p4) { + MutantRunner* t = + new Mutant, Tuple0> + (obj, method, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor(t); +} + +template +inline MutantFunctor +CreateFunctor(R (__stdcall *function)(X1, X2, X3, X4), const P1& p1, + const P2& p2, const P3& p3, const P4& p4) { + MutantRunner* t = + new MutantFunction, Tuple0> + (function, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2, X3, X4), const P1& p1, + const P2& p2, const P3& p3, const P4& p4) { + MutantRunner* t = + new MutantLateObjectBind, Tuple0> + (obj, method, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 4 - 1 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, X2, X3, X4, A1), const P1& p1, + const P2& p2, const P3& p3, const P4& p4) { + MutantRunner >* t = + new Mutant, Tuple1 > + (obj, method, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, X2, X3, X4, A1), const P1& p1, const P2& p2, + const P3& p3, const P4& p4) { + MutantRunner >* t = + new MutantFunction, Tuple1 > + (function, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, X2, X3, X4, A1), const P1& p1, + const P2& p2, const P3& p3, const P4& p4) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple1 > + (obj, method, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2, X3, X4, A1), + const P1& p1, const P2& p2, const P3& p3, const P4& p4) { + MutantRunner >* t = + new Mutant, Tuple1 > + (obj, method, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, X2, X3, X4, A1), const P1& p1, + const P2& p2, const P3& p3, const P4& p4) { + MutantRunner >* t = + new MutantFunction, Tuple1 > + (function, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2, X3, X4, A1), + const P1& p1, const P2& p2, const P3& p3, const P4& p4) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple1 > + (obj, method, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 4 - 2 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, X2, X3, X4, A1, A2), const P1& p1, + const P2& p2, const P3& p3, const P4& p4) { + MutantRunner >* t = + new Mutant, Tuple2 > + (obj, method, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, X2, X3, X4, A1, A2), const P1& p1, const P2& p2, + const P3& p3, const P4& p4) { + MutantRunner >* t = + new MutantFunction, Tuple2 > + (function, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, X2, X3, X4, A1, A2), const P1& p1, + const P2& p2, const P3& p3, const P4& p4) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple2 > + (obj, method, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2, X3, X4, A1, A2), + const P1& p1, const P2& p2, const P3& p3, const P4& p4) { + MutantRunner >* t = + new Mutant, Tuple2 > + (obj, method, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, X2, X3, X4, A1, A2), const P1& p1, + const P2& p2, const P3& p3, const P4& p4) { + MutantRunner >* t = + new MutantFunction, Tuple2 > + (function, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2, X3, X4, A1, A2), + const P1& p1, const P2& p2, const P3& p3, const P4& p4) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple2 > + (obj, method, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 4 - 3 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, X2, X3, X4, A1, A2, A3), const P1& p1, + const P2& p2, const P3& p3, const P4& p4) { + MutantRunner >* t = + new Mutant, Tuple3 > + (obj, method, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, X2, X3, X4, A1, A2, A3), const P1& p1, + const P2& p2, const P3& p3, const P4& p4) { + MutantRunner >* t = + new MutantFunction, Tuple3 > + (function, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, X2, X3, X4, A1, A2, A3), const P1& p1, + const P2& p2, const P3& p3, const P4& p4) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple3 > + (obj, method, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2, X3, X4, A1, A2, A3), + const P1& p1, const P2& p2, const P3& p3, const P4& p4) { + MutantRunner >* t = + new Mutant, Tuple3 > + (obj, method, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, X2, X3, X4, A1, A2, A3), const P1& p1, + const P2& p2, const P3& p3, const P4& p4) { + MutantRunner >* t = + new MutantFunction, Tuple3 > + (function, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2, X3, X4, A1, A2, A3), + const P1& p1, const P2& p2, const P3& p3, const P4& p4) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple3 > + (obj, method, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 4 - 4 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, X2, X3, X4, A1, A2, A3, A4), + const P1& p1, const P2& p2, const P3& p3, const P4& p4) { + MutantRunner >* t = + new Mutant, Tuple4 > + (obj, method, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, X2, X3, X4, A1, A2, A3, A4), const P1& p1, + const P2& p2, const P3& p3, const P4& p4) { + MutantRunner >* t = + new MutantFunction, Tuple4 > + (function, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, X2, X3, X4, A1, A2, A3, A4), + const P1& p1, const P2& p2, const P3& p3, const P4& p4) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple4 > + (obj, method, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2, X3, X4, A1, A2, A3, A4), + const P1& p1, const P2& p2, const P3& p3, const P4& p4) { + MutantRunner >* t = + new Mutant, Tuple4 > + (obj, method, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, X2, X3, X4, A1, A2, A3, A4), + const P1& p1, const P2& p2, const P3& p3, const P4& p4) { + MutantRunner >* t = + new MutantFunction, Tuple4 > + (function, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2, X3, X4, A1, A2, A3, A4), + const P1& p1, const P2& p2, const P3& p3, const P4& p4) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple4 > + (obj, method, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 4 - 5 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, X2, X3, X4, A1, A2, A3, A4, A5), + const P1& p1, const P2& p2, const P3& p3, const P4& p4) { + MutantRunner >* t = + new Mutant, Tuple5 > + (obj, method, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, X2, X3, X4, A1, A2, A3, A4, A5), const P1& p1, + const P2& p2, const P3& p3, const P4& p4) { + MutantRunner >* t = + new MutantFunction, Tuple5 > + (function, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, X2, X3, X4, A1, A2, A3, A4, A5), + const P1& p1, const P2& p2, const P3& p3, const P4& p4) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple5 > + (obj, method, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2, X3, X4, A1, A2, A3, A4, + A5), const P1& p1, const P2& p2, const P3& p3, const P4& p4) { + MutantRunner >* t = + new Mutant, Tuple5 > + (obj, method, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, X2, X3, X4, A1, A2, A3, A4, A5), + const P1& p1, const P2& p2, const P3& p3, const P4& p4) { + MutantRunner >* t = + new MutantFunction, Tuple5 > + (function, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2, X3, X4, A1, A2, A3, A4, + A5), const P1& p1, const P2& p2, const P3& p3, const P4& p4) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple5 > + (obj, method, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 4 - 6 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, X2, X3, X4, A1, A2, A3, A4, A5, A6), + const P1& p1, const P2& p2, const P3& p3, const P4& p4) { + MutantRunner >* t = + new Mutant, Tuple6 > + (obj, method, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, X2, X3, X4, A1, A2, A3, A4, A5, A6), + const P1& p1, const P2& p2, const P3& p3, const P4& p4) { + MutantRunner >* t = + new MutantFunction, Tuple6 > + (function, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, X2, X3, X4, A1, A2, A3, A4, A5, A6), + const P1& p1, const P2& p2, const P3& p3, const P4& p4) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple6 > + (obj, method, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2, X3, X4, A1, A2, A3, A4, + A5, A6), const P1& p1, const P2& p2, const P3& p3, const P4& p4) { + MutantRunner >* t = + new Mutant, Tuple6 > + (obj, method, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, X2, X3, X4, A1, A2, A3, A4, A5, A6), + const P1& p1, const P2& p2, const P3& p3, const P4& p4) { + MutantRunner >* t = + new MutantFunction, Tuple6 > + (function, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2, X3, X4, A1, A2, A3, A4, + A5, A6), const P1& p1, const P2& p2, const P3& p3, const P4& p4) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple6 > + (obj, method, MakeTuple(p1, p2, p3, p4)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 5 - 0 +template +inline MutantFunctor +CreateFunctor(T* obj, R (U::*method)(X1, X2, X3, X4, X5), const P1& p1, + const P2& p2, const P3& p3, const P4& p4, const P5& p5) { + MutantRunner* t = + new Mutant, Tuple0> + (obj, method, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor(t); +} + +template +inline MutantFunctor +CreateFunctor(R (*function)(X1, X2, X3, X4, X5), const P1& p1, const P2& p2, + const P3& p3, const P4& p4, const P5& p5) { + MutantRunner* t = + new MutantFunction, Tuple0> + (function, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor +CreateFunctor(T** obj, R (U::*method)(X1, X2, X3, X4, X5), const P1& p1, + const P2& p2, const P3& p3, const P4& p4, const P5& p5) { + MutantRunner* t = + new MutantLateObjectBind, Tuple0> + (obj, method, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2, X3, X4, X5), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5) { + MutantRunner* t = + new Mutant, Tuple0> + (obj, method, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor(t); +} + +template +inline MutantFunctor +CreateFunctor(R (__stdcall *function)(X1, X2, X3, X4, X5), const P1& p1, + const P2& p2, const P3& p3, const P4& p4, const P5& p5) { + MutantRunner* t = + new MutantFunction, Tuple0> + (function, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2, X3, X4, X5), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5) { + MutantRunner* t = + new MutantLateObjectBind, Tuple0> + (obj, method, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 5 - 1 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, X2, X3, X4, X5, A1), const P1& p1, + const P2& p2, const P3& p3, const P4& p4, const P5& p5) { + MutantRunner >* t = + new Mutant, Tuple1 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, X2, X3, X4, X5, A1), const P1& p1, const P2& p2, + const P3& p3, const P4& p4, const P5& p5) { + MutantRunner >* t = + new MutantFunction, Tuple1 > + (function, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, X2, X3, X4, X5, A1), const P1& p1, + const P2& p2, const P3& p3, const P4& p4, const P5& p5) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple1 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2, X3, X4, X5, A1), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5) { + MutantRunner >* t = + new Mutant, Tuple1 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, X2, X3, X4, X5, A1), const P1& p1, + const P2& p2, const P3& p3, const P4& p4, const P5& p5) { + MutantRunner >* t = + new MutantFunction, Tuple1 > + (function, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2, X3, X4, X5, A1), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple1 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 5 - 2 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, X2, X3, X4, X5, A1, A2), const P1& p1, + const P2& p2, const P3& p3, const P4& p4, const P5& p5) { + MutantRunner >* t = + new Mutant, Tuple2 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, X2, X3, X4, X5, A1, A2), const P1& p1, + const P2& p2, const P3& p3, const P4& p4, const P5& p5) { + MutantRunner >* t = + new MutantFunction, Tuple2 > + (function, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, X2, X3, X4, X5, A1, A2), const P1& p1, + const P2& p2, const P3& p3, const P4& p4, const P5& p5) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple2 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2, X3, X4, X5, A1, A2), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5) { + MutantRunner >* t = + new Mutant, Tuple2 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, X2, X3, X4, X5, A1, A2), const P1& p1, + const P2& p2, const P3& p3, const P4& p4, const P5& p5) { + MutantRunner >* t = + new MutantFunction, Tuple2 > + (function, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2, X3, X4, X5, A1, A2), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple2 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 5 - 3 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, X2, X3, X4, X5, A1, A2, A3), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5) { + MutantRunner >* t = + new Mutant, Tuple3 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, X2, X3, X4, X5, A1, A2, A3), const P1& p1, + const P2& p2, const P3& p3, const P4& p4, const P5& p5) { + MutantRunner >* t = + new MutantFunction, Tuple3 > + (function, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, X2, X3, X4, X5, A1, A2, A3), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple3 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2, X3, X4, X5, A1, A2, A3), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5) { + MutantRunner >* t = + new Mutant, Tuple3 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, X2, X3, X4, X5, A1, A2, A3), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5) { + MutantRunner >* t = + new MutantFunction, Tuple3 > + (function, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2, X3, X4, X5, A1, A2, A3), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple3 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 5 - 4 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, X2, X3, X4, X5, A1, A2, A3, A4), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5) { + MutantRunner >* t = + new Mutant, Tuple4 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, X2, X3, X4, X5, A1, A2, A3, A4), const P1& p1, + const P2& p2, const P3& p3, const P4& p4, const P5& p5) { + MutantRunner >* t = + new MutantFunction, Tuple4 > + (function, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, X2, X3, X4, X5, A1, A2, A3, A4), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple4 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2, X3, X4, X5, A1, A2, A3, + A4), const P1& p1, const P2& p2, const P3& p3, const P4& p4, + const P5& p5) { + MutantRunner >* t = + new Mutant, Tuple4 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, X2, X3, X4, X5, A1, A2, A3, A4), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5) { + MutantRunner >* t = + new MutantFunction, Tuple4 > + (function, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2, X3, X4, X5, A1, A2, A3, + A4), const P1& p1, const P2& p2, const P3& p3, const P4& p4, + const P5& p5) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple4 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 5 - 5 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, X2, X3, X4, X5, A1, A2, A3, A4, A5), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5) { + MutantRunner >* t = + new Mutant, Tuple5 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, X2, X3, X4, X5, A1, A2, A3, A4, A5), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5) { + MutantRunner >* t = + new MutantFunction, Tuple5 > + (function, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, X2, X3, X4, X5, A1, A2, A3, A4, A5), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple5 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2, X3, X4, X5, A1, A2, A3, + A4, A5), const P1& p1, const P2& p2, const P3& p3, const P4& p4, + const P5& p5) { + MutantRunner >* t = + new Mutant, Tuple5 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, X2, X3, X4, X5, A1, A2, A3, A4, A5), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5) { + MutantRunner >* t = + new MutantFunction, Tuple5 > + (function, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2, X3, X4, X5, A1, A2, A3, + A4, A5), const P1& p1, const P2& p2, const P3& p3, const P4& p4, + const P5& p5) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple5 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 5 - 6 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, X2, X3, X4, X5, A1, A2, A3, A4, A5, + A6), const P1& p1, const P2& p2, const P3& p3, const P4& p4, + const P5& p5) { + MutantRunner >* t = + new Mutant, Tuple6 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, X2, X3, X4, X5, A1, A2, A3, A4, A5, A6), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5) { + MutantRunner >* t = + new MutantFunction, Tuple6 > + (function, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, X2, X3, X4, X5, A1, A2, A3, A4, A5, + A6), const P1& p1, const P2& p2, const P3& p3, const P4& p4, + const P5& p5) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple6 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2, X3, X4, X5, A1, A2, A3, + A4, A5, A6), const P1& p1, const P2& p2, const P3& p3, const P4& p4, + const P5& p5) { + MutantRunner >* t = + new Mutant, Tuple6 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, X2, X3, X4, X5, A1, A2, A3, A4, A5, + A6), const P1& p1, const P2& p2, const P3& p3, const P4& p4, + const P5& p5) { + MutantRunner >* t = + new MutantFunction, Tuple6 > + (function, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2, X3, X4, X5, A1, A2, A3, + A4, A5, A6), const P1& p1, const P2& p2, const P3& p3, const P4& p4, + const P5& p5) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple6 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 6 - 0 +template +inline MutantFunctor +CreateFunctor(T* obj, R (U::*method)(X1, X2, X3, X4, X5, X6), const P1& p1, + const P2& p2, const P3& p3, const P4& p4, const P5& p5, const P6& p6) { + MutantRunner* t = + new Mutant, Tuple0> + (obj, method, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor(t); +} + +template +inline MutantFunctor +CreateFunctor(R (*function)(X1, X2, X3, X4, X5, X6), const P1& p1, const P2& p2, + const P3& p3, const P4& p4, const P5& p5, const P6& p6) { + MutantRunner* t = + new MutantFunction, Tuple0> + (function, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor +CreateFunctor(T** obj, R (U::*method)(X1, X2, X3, X4, X5, X6), const P1& p1, + const P2& p2, const P3& p3, const P4& p4, const P5& p5, const P6& p6) { + MutantRunner* t = + new MutantLateObjectBind, Tuple0> + (obj, method, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2, X3, X4, X5, X6), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, + const P6& p6) { + MutantRunner* t = + new Mutant, Tuple0> + (obj, method, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor(t); +} + +template +inline MutantFunctor +CreateFunctor(R (__stdcall *function)(X1, X2, X3, X4, X5, X6), const P1& p1, + const P2& p2, const P3& p3, const P4& p4, const P5& p5, const P6& p6) { + MutantRunner* t = + new MutantFunction, Tuple0> + (function, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2, X3, X4, X5, X6), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, + const P6& p6) { + MutantRunner* t = + new MutantLateObjectBind, Tuple0> + (obj, method, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 6 - 1 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, X2, X3, X4, X5, X6, A1), const P1& p1, + const P2& p2, const P3& p3, const P4& p4, const P5& p5, const P6& p6) { + MutantRunner >* t = + new Mutant, Tuple1 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, X2, X3, X4, X5, X6, A1), const P1& p1, + const P2& p2, const P3& p3, const P4& p4, const P5& p5, const P6& p6) { + MutantRunner >* t = + new MutantFunction, Tuple1 > + (function, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, X2, X3, X4, X5, X6, A1), const P1& p1, + const P2& p2, const P3& p3, const P4& p4, const P5& p5, const P6& p6) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple1 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2, X3, X4, X5, X6, A1), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, + const P6& p6) { + MutantRunner >* t = + new Mutant, Tuple1 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, X2, X3, X4, X5, X6, A1), const P1& p1, + const P2& p2, const P3& p3, const P4& p4, const P5& p5, const P6& p6) { + MutantRunner >* t = + new MutantFunction, Tuple1 > + (function, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2, X3, X4, X5, X6, A1), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, + const P6& p6) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple1 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 6 - 2 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, X2, X3, X4, X5, X6, A1, A2), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, + const P6& p6) { + MutantRunner >* t = + new Mutant, Tuple2 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, X2, X3, X4, X5, X6, A1, A2), const P1& p1, + const P2& p2, const P3& p3, const P4& p4, const P5& p5, const P6& p6) { + MutantRunner >* t = + new MutantFunction, Tuple2 > + (function, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, X2, X3, X4, X5, X6, A1, A2), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, + const P6& p6) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple2 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2, X3, X4, X5, X6, A1, A2), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, + const P6& p6) { + MutantRunner >* t = + new Mutant, Tuple2 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, X2, X3, X4, X5, X6, A1, A2), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, + const P6& p6) { + MutantRunner >* t = + new MutantFunction, Tuple2 > + (function, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2, X3, X4, X5, X6, A1, A2), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, + const P6& p6) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple2 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 6 - 3 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, X2, X3, X4, X5, X6, A1, A2, A3), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, + const P6& p6) { + MutantRunner >* t = + new Mutant, Tuple3 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, X2, X3, X4, X5, X6, A1, A2, A3), const P1& p1, + const P2& p2, const P3& p3, const P4& p4, const P5& p5, const P6& p6) { + MutantRunner >* t = + new MutantFunction, Tuple3 > + (function, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, X2, X3, X4, X5, X6, A1, A2, A3), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, + const P6& p6) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple3 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2, X3, X4, X5, X6, A1, A2, + A3), const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, + const P6& p6) { + MutantRunner >* t = + new Mutant, Tuple3 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, X2, X3, X4, X5, X6, A1, A2, A3), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, + const P6& p6) { + MutantRunner >* t = + new MutantFunction, Tuple3 > + (function, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2, X3, X4, X5, X6, A1, A2, + A3), const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, + const P6& p6) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple3 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 6 - 4 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, X2, X3, X4, X5, X6, A1, A2, A3, A4), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, + const P6& p6) { + MutantRunner >* t = + new Mutant, Tuple4 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, X2, X3, X4, X5, X6, A1, A2, A3, A4), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, + const P6& p6) { + MutantRunner >* t = + new MutantFunction, Tuple4 > + (function, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, X2, X3, X4, X5, X6, A1, A2, A3, A4), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, + const P6& p6) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple4 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2, X3, X4, X5, X6, A1, A2, + A3, A4), const P1& p1, const P2& p2, const P3& p3, const P4& p4, + const P5& p5, const P6& p6) { + MutantRunner >* t = + new Mutant, Tuple4 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, X2, X3, X4, X5, X6, A1, A2, A3, A4), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, + const P6& p6) { + MutantRunner >* t = + new MutantFunction, Tuple4 > + (function, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2, X3, X4, X5, X6, A1, A2, + A3, A4), const P1& p1, const P2& p2, const P3& p3, const P4& p4, + const P5& p5, const P6& p6) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple4 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 6 - 5 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, X2, X3, X4, X5, X6, A1, A2, A3, A4, + A5), const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, + const P6& p6) { + MutantRunner >* t = + new Mutant, Tuple5 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, X2, X3, X4, X5, X6, A1, A2, A3, A4, A5), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, + const P6& p6) { + MutantRunner >* t = + new MutantFunction, Tuple5 > + (function, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, X2, X3, X4, X5, X6, A1, A2, A3, A4, + A5), const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, + const P6& p6) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple5 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2, X3, X4, X5, X6, A1, A2, + A3, A4, A5), const P1& p1, const P2& p2, const P3& p3, const P4& p4, + const P5& p5, const P6& p6) { + MutantRunner >* t = + new Mutant, Tuple5 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, X2, X3, X4, X5, X6, A1, A2, A3, A4, + A5), const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, + const P6& p6) { + MutantRunner >* t = + new MutantFunction, Tuple5 > + (function, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2, X3, X4, X5, X6, A1, A2, + A3, A4, A5), const P1& p1, const P2& p2, const P3& p3, const P4& p4, + const P5& p5, const P6& p6) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple5 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +// 6 - 6 +template +inline MutantFunctor > +CreateFunctor(T* obj, R (U::*method)(X1, X2, X3, X4, X5, X6, A1, A2, A3, A4, A5, + A6), const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, + const P6& p6) { + MutantRunner >* t = + new Mutant, Tuple6 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (*function)(X1, X2, X3, X4, X5, X6, A1, A2, A3, A4, A5, A6), + const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, + const P6& p6) { + MutantRunner >* t = + new MutantFunction, Tuple6 > + (function, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} + +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (U::*method)(X1, X2, X3, X4, X5, X6, A1, A2, A3, A4, A5, + A6), const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, + const P6& p6) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple6 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING + +#if defined (OS_WIN) && !defined (ARCH_CPU_X86_64) +template +inline MutantFunctor > +CreateFunctor(T* obj, R (__stdcall U::*method)(X1, X2, X3, X4, X5, X6, A1, A2, + A3, A4, A5, A6), const P1& p1, const P2& p2, const P3& p3, const P4& p4, + const P5& p5, const P6& p6) { + MutantRunner >* t = + new Mutant, Tuple6 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} + +template +inline MutantFunctor > +CreateFunctor(R (__stdcall *function)(X1, X2, X3, X4, X5, X6, A1, A2, A3, A4, + A5, A6), const P1& p1, const P2& p2, const P3& p3, const P4& p4, + const P5& p5, const P6& p6) { + MutantRunner >* t = + new MutantFunction, Tuple6 > + (function, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} +#ifdef GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +template +inline MutantFunctor > +CreateFunctor(T** obj, R (__stdcall U::*method)(X1, X2, X3, X4, X5, X6, A1, A2, + A3, A4, A5, A6), const P1& p1, const P2& p2, const P3& p3, const P4& p4, + const P5& p5, const P6& p6) { + MutantRunner >* t = + new MutantLateObjectBind, Tuple6 > + (obj, method, MakeTuple(p1, p2, p3, p4, p5, p6)); + return MutantFunctor >(t); +} +#endif // GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#endif // defined (OS_WIN) && !defined (ARCH_CPU_X86_64) + +} // namespace testing + +#endif // TESTING_GMOCK_MUTANT_H_ diff --git a/testing/gtest b/testing/gtest new file mode 160000 index 0000000000..a6772271f7 --- /dev/null +++ b/testing/gtest @@ -0,0 +1 @@ +Subproject commit a6772271f71672e889776bfe49ec4efd9da036df diff --git a/testing/gtest.gyp b/testing/gtest.gyp new file mode 100644 index 0000000000..517bd06e4c --- /dev/null +++ b/testing/gtest.gyp @@ -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. + +{ + 'targets': [ + { + 'target_name': 'gtest', + 'type': 'static_library', + 'sources': [ + 'gtest/include/gtest/gtest-death-test.h', + 'gtest/include/gtest/gtest-message.h', + 'gtest/include/gtest/gtest-param-test.h', + 'gtest/include/gtest/gtest-printers.h', + 'gtest/include/gtest/gtest-spi.h', + 'gtest/include/gtest/gtest-test-part.h', + 'gtest/include/gtest/gtest-typed-test.h', + 'gtest/include/gtest/gtest.h', + 'gtest/include/gtest/gtest_pred_impl.h', + 'gtest/include/gtest/internal/gtest-death-test-internal.h', + 'gtest/include/gtest/internal/gtest-filepath.h', + 'gtest/include/gtest/internal/gtest-internal.h', + 'gtest/include/gtest/internal/gtest-linked_ptr.h', + 'gtest/include/gtest/internal/gtest-param-util-generated.h', + 'gtest/include/gtest/internal/gtest-param-util.h', + 'gtest/include/gtest/internal/gtest-port.h', + 'gtest/include/gtest/internal/gtest-string.h', + 'gtest/include/gtest/internal/gtest-tuple.h', + 'gtest/include/gtest/internal/gtest-type-util.h', + 'gtest/src/gtest-all.cc', + 'gtest/src/gtest-death-test.cc', + 'gtest/src/gtest-filepath.cc', + 'gtest/src/gtest-internal-inl.h', + 'gtest/src/gtest-port.cc', + 'gtest/src/gtest-printers.cc', + 'gtest/src/gtest-test-part.cc', + 'gtest/src/gtest-typed-test.cc', + 'gtest/src/gtest.cc', + 'multiprocess_func_list.cc', + 'multiprocess_func_list.h', + 'platform_test.h', + ], + 'sources!': [ + 'gtest/src/gtest-all.cc', # Not needed by our build. + ], + 'include_dirs': [ + 'gtest', + 'gtest/include', + ], + 'dependencies': [ + 'gtest_prod', + ], + 'conditions': [ + ['OS == "mac" or OS == "ios"', { + 'sources': [ + 'gtest_mac.h', + 'gtest_mac.mm', + 'platform_test_mac.mm' + ], + 'link_settings': { + 'libraries': [ + '$(SDKROOT)/System/Library/Frameworks/Foundation.framework', + ], + }, + }], + ['OS == "ios"', { + 'dependencies' : [ + '<(DEPTH)/testing/iossim/iossim.gyp:iossim#host', + ], + 'direct_dependent_settings': { + 'target_conditions': [ + # Turn all tests into bundles on iOS because that's the only + # type of executable supported for iOS. + ['_type=="executable"', { + 'variables': { + # Use a variable so the path gets fixed up so it is always + # correct when INFOPLIST_FILE finally gets set. + 'ios_unittest_info_plist_path': + '<(DEPTH)/testing/gtest_ios/unittest-Info.plist', + }, + 'mac_bundle': 1, + 'xcode_settings': { + 'BUNDLE_ID_TEST_NAME': + '>!(echo ">(_target_name)" | sed -e "s/_//g")', + 'INFOPLIST_FILE': '>(ios_unittest_info_plist_path)', + }, + 'mac_bundle_resources': [ + '<(ios_unittest_info_plist_path)', + '<(DEPTH)/testing/gtest_ios/Default-568h@2x.png', + ], + 'mac_bundle_resources!': [ + '<(ios_unittest_info_plist_path)', + ], + }], + ], + }, + }], + ['OS=="ios" and asan==1', { + 'direct_dependent_settings': { + 'target_conditions': [ + # Package the ASan runtime dylib into the test app bundles. + ['_type=="executable"', { + 'postbuilds': [ + { + 'variables': { + # Define copy_asan_dylib_path in a variable ending in + # _path so that gyp understands it's a path and + # performs proper relativization during dict merging. + 'copy_asan_dylib_path': + '<(DEPTH)/build/mac/copy_asan_runtime_dylib.sh', + }, + 'postbuild_name': 'Copy ASan runtime dylib', + 'action': [ + '>(copy_asan_dylib_path)', + ], + }, + ], + }], + ], + }, + }], + ['os_posix == 1', { + 'defines': [ + # gtest isn't able to figure out when RTTI is disabled for gcc + # versions older than 4.3.2, and assumes it's enabled. Our Mac + # and Linux builds disable RTTI, and cannot guarantee that the + # compiler will be 4.3.2. or newer. The Mac, for example, uses + # 4.2.1 as that is the latest available on that platform. gtest + # must be instructed that RTTI is disabled here, and for any + # direct dependents that might include gtest headers. + 'GTEST_HAS_RTTI=0', + ], + 'direct_dependent_settings': { + 'defines': [ + 'GTEST_HAS_RTTI=0', + ], + }, + }], + ['OS=="android" and android_app_abi=="x86"', { + 'defines': [ + 'GTEST_HAS_CLONE=0', + ], + 'direct_dependent_settings': { + 'defines': [ + 'GTEST_HAS_CLONE=0', + ], + }, + }], + ['OS=="android"', { + # We want gtest features that use tr1::tuple, but we currently + # don't support the variadic templates used by libstdc++'s + # implementation. gtest supports this scenario by providing its + # own implementation but we must opt in to it. + 'defines': [ + 'GTEST_USE_OWN_TR1_TUPLE=1', + # GTEST_USE_OWN_TR1_TUPLE only works if GTEST_HAS_TR1_TUPLE is set. + # gtest r625 made it so that GTEST_HAS_TR1_TUPLE is set to 0 + # automatically on android, so it has to be set explicitly here. + 'GTEST_HAS_TR1_TUPLE=1', + ], + 'direct_dependent_settings': { + 'defines': [ + 'GTEST_USE_OWN_TR1_TUPLE=1', + 'GTEST_HAS_TR1_TUPLE=1', + ], + }, + }], + ['OS=="win" and (MSVS_VERSION=="2012" or MSVS_VERSION=="2012e")', { + 'defines': [ + '_VARIADIC_MAX=10', + ], + 'direct_dependent_settings': { + 'defines': [ + '_VARIADIC_MAX=10', + ], + }, + }], + ], + 'direct_dependent_settings': { + 'defines': [ + 'UNIT_TEST', + ], + 'include_dirs': [ + 'gtest/include', # So that gtest headers can find themselves. + ], + 'target_conditions': [ + ['_type=="executable"', { + 'test': 1, + 'conditions': [ + ['OS=="mac"', { + 'run_as': { + 'action????': ['${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}'], + }, + }], + ['OS=="ios"', { + 'variables': { + # Use a variable so the path gets fixed up so it is always + # correct when the action finally gets used. + 'ios_run_unittest_script_path': + '<(DEPTH)/testing/gtest_ios/run-unittest.sh', + }, + 'run_as': { + 'action????': ['>(ios_run_unittest_script_path)'], + }, + }], + ['OS=="win"', { + 'run_as': { + 'action????': ['$(TargetPath)', '--gtest_print_time'], + }, + }], + ], + }], + ], + 'msvs_disabled_warnings': [4800], + }, + }, + { + 'target_name': 'gtest_main', + 'type': 'static_library', + 'dependencies': [ + 'gtest', + ], + 'sources': [ + 'gtest/src/gtest_main.cc', + ], + }, + { + 'target_name': 'gtest_prod', + 'toolsets': ['host', 'target'], + 'type': 'none', + 'sources': [ + 'gtest/include/gtest/gtest_prod.h', + ], + }, + ], +} diff --git a/testing/gtest_ios/Default-568h@2x.png b/testing/gtest_ios/Default-568h@2x.png new file mode 100644 index 0000000000..8c9089d5c6 Binary files /dev/null and b/testing/gtest_ios/Default-568h@2x.png differ diff --git a/testing/gtest_ios/run-unittest.sh b/testing/gtest_ios/run-unittest.sh new file mode 100755 index 0000000000..1598630984 --- /dev/null +++ b/testing/gtest_ios/run-unittest.sh @@ -0,0 +1,87 @@ +#!/bin/bash -p + +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +set -eu + +# Environment sanitization. Set a known-safe PATH. Clear environment variables +# that might impact the interpreter's operation. The |bash -p| invocation +# on the #! line takes the bite out of BASH_ENV, ENV, and SHELLOPTS (among +# other features), but clearing them here ensures that they won't impact any +# shell scripts used as utility programs. SHELLOPTS is read-only and can't be +# unset, only unexported. +export PATH="/usr/bin:/bin:/usr/sbin:/sbin" +unset BASH_ENV CDPATH ENV GLOBIGNORE IFS POSIXLY_CORRECT +export -n SHELLOPTS + +readonly ScriptDir=$(dirname "$(echo ${0} | sed -e "s,^\([^/]\),$(pwd)/\1,")") +readonly ScriptName=$(basename "${0}") +readonly ThisScript="${ScriptDir}/${ScriptName}" +readonly SimExecutable="${BUILD_DIR}/ninja-iossim/${CONFIGURATION}/iossim" + +# Helper to print a line formatted for Xcodes build output parser. +XcodeNote() { + echo "${ThisScript}:${1}: note: ${2}" +} + +# Helper to print a divider to make things stick out in a busy output window. +XcodeHeader() { + echo "note: _________________________________________________________________" + echo "note: _________________________________________________________________" + echo "note: _________________________________________________________________" + XcodeNote "$1" ">>>>> $2" + echo "note: _________________________________________________________________" + echo "note: _________________________________________________________________" + echo "note: _________________________________________________________________" +} + +# Kills the iPhone Simulator if it is running. +KillSimulator() { + /usr/bin/killall "iPhone Simulator" 2> /dev/null || true +} + +# Runs tests via the iPhone Simulator for multiple devices. +RunTests() { + local -r appPath="${TARGET_BUILD_DIR}/${PRODUCT_NAME}.app" + + if [[ ! -x "${SimExecutable}" ]]; then + echo "Unable to run tests: ${SimExecutable} was not found/executable." + exit 1 + fi + + for device in 'iPhone' 'iPad'; do + iosVersion="6.1" + KillSimulator + local command=( + "${SimExecutable}" "-d${device}" "-s${iosVersion}" "${appPath}" + ) + # Pass along any command line flags + if [[ "$#" -gt 0 ]]; then + command+=( "--" "${@}" ) + fi + XcodeHeader ${LINENO} "Launching tests for ${device} (iOS ${iosVersion})" + "${command[@]}" + + # If the command didn't exit successfully, abort. + if [[ $? -ne 0 ]]; then + exit $?; + fi + done +} + +# Time to get to work. + +if [[ "${PLATFORM_NAME}" != "iphonesimulator" ]]; then + XcodeNote ${LINENO} "Skipping running of unittests for device build." +else + if [[ "$#" -gt 0 ]]; then + RunTests "${@}" + else + RunTests + fi + KillSimulator +fi + +exit 0 diff --git a/testing/gtest_ios/unittest-Info.plist b/testing/gtest_ios/unittest-Info.plist new file mode 100644 index 0000000000..fc21034c57 --- /dev/null +++ b/testing/gtest_ios/unittest-Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleDisplayName + ${PRODUCT_NAME} + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + com.google.gtest.${BUNDLE_ID_TEST_NAME} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleSignature + ???? + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + UISupportedInterfaceOrientation + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/testing/gtest_mac.h b/testing/gtest_mac.h new file mode 100644 index 0000000000..aa48c94543 --- /dev/null +++ b/testing/gtest_mac.h @@ -0,0 +1,48 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef TESTING_GTEST_MAC_H_ +#define TESTING_GTEST_MAC_H_ + +#include +#include + +#ifdef GTEST_OS_MAC + +#import + +namespace testing { +namespace internal { + +// This overloaded version allows comparison between ObjC objects that conform +// to the NSObject protocol. Used to implement {ASSERT|EXPECT}_EQ(). +GTEST_API_ AssertionResult CmpHelperNSEQ(const char* expected_expression, + const char* actual_expression, + id expected, + id actual); + +// This overloaded version allows comparison between ObjC objects that conform +// to the NSObject protocol. Used to implement {ASSERT|EXPECT}_NE(). +GTEST_API_ AssertionResult CmpHelperNSNE(const char* expected_expression, + const char* actual_expression, + id expected, + id actual); + +} // namespace internal +} // namespace testing + +// Tests that [expected isEqual:actual]. +#define EXPECT_NSEQ(expected, actual) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperNSEQ, expected, actual) +#define EXPECT_NSNE(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperNSNE, val1, val2) + +#define ASSERT_NSEQ(expected, actual) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperNSEQ, expected, actual) +#define ASSERT_NSNE(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperNSNE, val1, val2) + +#endif // GTEST_OS_MAC + +#endif // TESTING_GTEST_MAC_H_ diff --git a/testing/gtest_mac.mm b/testing/gtest_mac.mm new file mode 100644 index 0000000000..b39d258c13 --- /dev/null +++ b/testing/gtest_mac.mm @@ -0,0 +1,61 @@ +// 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. + +#import "gtest_mac.h" + +#include + +#include +#include +#include + +#ifdef GTEST_OS_MAC + +#import + +namespace testing { +namespace internal { + +// Handles nil values for |obj| properly by using safe printing of %@ in +// -stringWithFormat:. +static inline const char* StringDescription(id obj) { + return [[NSString stringWithFormat:@"%@", obj] UTF8String]; +} + +// This overloaded version allows comparison between ObjC objects that conform +// to the NSObject protocol. Used to implement {ASSERT|EXPECT}_EQ(). +GTEST_API_ AssertionResult CmpHelperNSEQ(const char* expected_expression, + const char* actual_expression, + id expected, + id actual) { + if (expected == actual || [expected isEqual:actual]) { + return AssertionSuccess(); + } + return EqFailure(expected_expression, + actual_expression, + std::string(StringDescription(expected)), + std::string(StringDescription(actual)), + false); +} + +// This overloaded version allows comparison between ObjC objects that conform +// to the NSObject protocol. Used to implement {ASSERT|EXPECT}_NE(). +GTEST_API_ AssertionResult CmpHelperNSNE(const char* expected_expression, + const char* actual_expression, + id expected, + id actual) { + if (expected != actual && ![expected isEqual:actual]) { + return AssertionSuccess(); + } + Message msg; + msg << "Expected: (" << expected_expression << ") != (" << actual_expression + << "), actual: " << StringDescription(expected) + << " vs " << StringDescription(actual); + return AssertionFailure(msg); +} + +} // namespace internal +} // namespace testing + +#endif // GTEST_OS_MAC diff --git a/testing/gtest_mac_unittest.mm b/testing/gtest_mac_unittest.mm new file mode 100644 index 0000000000..9363b410fd --- /dev/null +++ b/testing/gtest_mac_unittest.mm @@ -0,0 +1,57 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Note that while this file is in testing/ and tests GTest macros, it is built +// as part of Chromium's unit_tests target because the project does not build +// or run GTest's internal test suite. + +#import "testing/gtest_mac.h" + +#import + +#include "base/mac/scoped_nsautorelease_pool.h" +#include "testing/gtest/include/gtest/internal/gtest-port.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(GTestMac, ExpectNSEQ) { + base::mac::ScopedNSAutoreleasePool pool; + + EXPECT_NSEQ(@"a", @"a"); + + NSString* s1 = [NSString stringWithUTF8String:"a"]; + NSString* s2 = @"a"; + EXPECT_NE(s1, s2); + EXPECT_NSEQ(s1, s2); +} + +TEST(GTestMac, AssertNSEQ) { + base::mac::ScopedNSAutoreleasePool pool; + + NSString* s1 = [NSString stringWithUTF8String:"a"]; + NSString* s2 = @"a"; + EXPECT_NE(s1, s2); + ASSERT_NSEQ(s1, s2); +} + +TEST(GTestMac, ExpectNSNE) { + base::mac::ScopedNSAutoreleasePool pool; + + EXPECT_NSNE([NSNumber numberWithInt:2], [NSNumber numberWithInt:42]); +} + +TEST(GTestMac, AssertNSNE) { + base::mac::ScopedNSAutoreleasePool pool; + + ASSERT_NSNE(@"a", @"b"); +} + +TEST(GTestMac, ExpectNSNil) { + base::mac::ScopedNSAutoreleasePool pool; + + EXPECT_NSEQ(nil, nil); + EXPECT_NSNE(nil, @"a"); + EXPECT_NSNE(@"a", nil); + + // TODO(shess): Test that EXPECT_NSNE(nil, nil) fails. +} diff --git a/testing/iossim/OWNERS b/testing/iossim/OWNERS new file mode 100644 index 0000000000..7f8c2f8368 --- /dev/null +++ b/testing/iossim/OWNERS @@ -0,0 +1,3 @@ +lliabraa@chromium.org +rohitrao@chromium.org +stuartmorgan@chromium.org diff --git a/testing/iossim/iossim.gyp b/testing/iossim/iossim.gyp new file mode 100644 index 0000000000..b699b53421 --- /dev/null +++ b/testing/iossim/iossim.gyp @@ -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. + +{ + 'conditions': [ + ['OS!="ios" or "<(GENERATOR)"=="ninja"', { + 'targets': [ + { + 'target_name': 'iossim', + 'toolsets': ['host'], + 'type': 'executable', + 'variables': { + 'developer_dir': ' +#include +#include +#include +#include + +// An executable (iossim) that runs an app in the iOS Simulator. +// Run 'iossim -h' for usage information. +// +// For best results, the iOS Simulator application should not be running when +// iossim is invoked. +// +// Headers for the iPhoneSimulatorRemoteClient framework used in this tool are +// generated by class-dump, via GYP. +// (class-dump is available at http://www.codethecode.com/projects/class-dump/) +// +// However, there are some forward declarations required to get things to +// compile. Also, the DTiPhoneSimulatorSessionDelegate protocol is referenced +// by the iPhoneSimulatorRemoteClient framework, but not defined in the object +// file, so it must be defined here before importing the generated +// iPhoneSimulatorRemoteClient.h file. + +@class DTiPhoneSimulatorApplicationSpecifier; +@class DTiPhoneSimulatorSession; +@class DTiPhoneSimulatorSessionConfig; +@class DTiPhoneSimulatorSystemRoot; + +@protocol DTiPhoneSimulatorSessionDelegate +- (void)session:(DTiPhoneSimulatorSession*)session + didEndWithError:(NSError*)error; +- (void)session:(DTiPhoneSimulatorSession*)session + didStart:(BOOL)started + withError:(NSError*)error; +@end + +#import "iPhoneSimulatorRemoteClient.h" + +// An undocumented system log key included in messages from launchd. The value +// is the PID of the process the message is about (as opposed to launchd's PID). +#define ASL_KEY_REF_PID "RefPID" + +namespace { + +// Name of environment variables that control the user's home directory in the +// simulator. +const char* const kUserHomeEnvVariable = "CFFIXED_USER_HOME"; +const char* const kHomeEnvVariable = "HOME"; + +// Device family codes for iPhone and iPad. +const int kIPhoneFamily = 1; +const int kIPadFamily = 2; + +// Max number of seconds to wait for the simulator session to start. +// This timeout must allow time to start up iOS Simulator, install the app +// and perform any other black magic that is encoded in the +// iPhoneSimulatorRemoteClient framework to kick things off. Normal start up +// time is only a couple seconds but machine load, disk caches, etc., can all +// affect startup time in the wild so the timeout needs to be fairly generous. +// If this timeout occurs iossim will likely exit with non-zero status; the +// exception being if the app is invoked and completes execution before the +// session is started (this case is handled in session:didStart:withError). +const NSTimeInterval kDefaultSessionStartTimeoutSeconds = 30; + +// While the simulated app is running, its stdout is redirected to a file which +// is polled by iossim and written to iossim's stdout using the following +// polling interval. +const NSTimeInterval kOutputPollIntervalSeconds = 0.1; + +// The path within the developer dir of the private Simulator frameworks. +NSString* const kSimulatorFrameworkRelativePath = + @"Platforms/iPhoneSimulator.platform/Developer/Library/PrivateFrameworks/" + @"iPhoneSimulatorRemoteClient.framework"; +NSString* const kDevToolsFoundationRelativePath = + @"../OtherFrameworks/DevToolsFoundation.framework"; +NSString* const kSimulatorRelativePath = + @"Platforms/iPhoneSimulator.platform/Developer/Applications/" + @"iPhone Simulator.app"; + +// Simulator Error String Key. This can be found by looking in the Simulator's +// Localizable.strings files. +NSString* const kSimulatorAppQuitErrorKey = @"The simulated application quit."; + +const char* gToolName = "iossim"; + +// Exit status codes. +const int kExitSuccess = EXIT_SUCCESS; +const int kExitFailure = EXIT_FAILURE; +const int kExitInvalidArguments = 2; +const int kExitInitializationFailure = 3; +const int kExitAppFailedToStart = 4; +const int kExitAppCrashed = 5; + +void LogError(NSString* format, ...) { + va_list list; + va_start(list, format); + + NSString* message = + [[[NSString alloc] initWithFormat:format arguments:list] autorelease]; + + fprintf(stderr, "%s: ERROR: %s\n", gToolName, [message UTF8String]); + fflush(stderr); + + va_end(list); +} + +void LogWarning(NSString* format, ...) { + va_list list; + va_start(list, format); + + NSString* message = + [[[NSString alloc] initWithFormat:format arguments:list] autorelease]; + + fprintf(stderr, "%s: WARNING: %s\n", gToolName, [message UTF8String]); + fflush(stderr); + + va_end(list); +} + +} // namespace + +// A delegate that is called when the simulated app is started or ended in the +// simulator. +@interface SimulatorDelegate : NSObject { + @private + NSString* stdioPath_; + NSString* developerDir_; + NSThread* outputThread_; + NSBundle* simulatorBundle_; + BOOL appRunning_; +} +@end + +// An implementation that copies the simulated app's stdio to stdout of this +// executable. While it would be nice to get stdout and stderr independently +// from iOS Simulator, issues like I/O buffering and interleaved output +// between iOS Simulator and the app would cause iossim to display things out +// of order here. Printing all output to a single file keeps the order correct. +// Instances of this classe should be initialized with the location of the +// simulated app's output file. When the simulated app starts, a thread is +// started which handles copying data from the simulated app's output file to +// the stdout of this executable. +@implementation SimulatorDelegate + +// Specifies the file locations of the simulated app's stdout and stderr. +- (SimulatorDelegate*)initWithStdioPath:(NSString*)stdioPath + developerDir:(NSString*)developerDir { + self = [super init]; + if (self) { + stdioPath_ = [stdioPath copy]; + developerDir_ = [developerDir copy]; + } + + return self; +} + +- (void)dealloc { + [stdioPath_ release]; + [developerDir_ release]; + [simulatorBundle_ release]; + [super dealloc]; +} + +// Reads data from the simulated app's output and writes it to stdout. This +// method blocks, so it should be called in a separate thread. The iOS +// Simulator takes a file path for the simulated app's stdout and stderr, but +// this path isn't always available (e.g. when the stdout is Xcode's build +// window). As a workaround, iossim creates a temp file to hold output, which +// this method reads and copies to stdout. +- (void)tailOutput { + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + // Copy data to stdout/stderr while the app is running. + NSFileHandle* simio = [NSFileHandle fileHandleForReadingAtPath:stdioPath_]; + NSFileHandle* standardOutput = [NSFileHandle fileHandleWithStandardOutput]; + while (appRunning_) { + NSAutoreleasePool* innerPool = [[NSAutoreleasePool alloc] init]; + [standardOutput writeData:[simio readDataToEndOfFile]]; + [NSThread sleepForTimeInterval:kOutputPollIntervalSeconds]; + [innerPool drain]; + } + + // Once the app is no longer running, copy any data that was written during + // the last sleep cycle. + [standardOutput writeData:[simio readDataToEndOfFile]]; + + [pool drain]; +} + +// Fetches a localized error string from the Simulator. +- (NSString *)localizedSimulatorErrorString:(NSString*)stringKey { + // Lazy load of the simulator bundle. + if (simulatorBundle_ == nil) { + NSString* simulatorPath = [developerDir_ + stringByAppendingPathComponent:kSimulatorRelativePath]; + simulatorBundle_ = [NSBundle bundleWithPath:simulatorPath]; + } + NSString *localizedStr = + [simulatorBundle_ localizedStringForKey:stringKey + value:nil + table:nil]; + if ([localizedStr length]) + return localizedStr; + // Failed to get a value, follow Cocoa conventions and use the key as the + // string. + return stringKey; +} + +- (void)session:(DTiPhoneSimulatorSession*)session + didStart:(BOOL)started + withError:(NSError*)error { + if (!started) { + // If the test executes very quickly (<30ms), the SimulatorDelegate may not + // get the initial session:started:withError: message indicating successful + // startup of the simulated app. Instead the delegate will get a + // session:started:withError: message after the timeout has elapsed. To + // account for this case, check if the simulated app's stdio file was + // ever created and if it exists dump it to stdout and return success. + NSFileManager* fileManager = [NSFileManager defaultManager]; + if ([fileManager fileExistsAtPath:stdioPath_]) { + appRunning_ = NO; + [self tailOutput]; + // Note that exiting in this state leaves a process running + // (e.g. /.../iPhoneSimulator4.3.sdk/usr/libexec/installd -t 30) that will + // prevent future simulator sessions from being started for 30 seconds + // unless the iOS Simulator application is killed altogether. + [self session:session didEndWithError:nil]; + + // session:didEndWithError should not return (because it exits) so + // the execution path should never get here. + exit(kExitFailure); + } + + LogError(@"Simulator failed to start: \"%@\" (%@:%ld)", + [error localizedDescription], + [error domain], static_cast([error code])); + exit(kExitAppFailedToStart); + } + + // Start a thread to write contents of outputPath to stdout. + appRunning_ = YES; + outputThread_ = [[NSThread alloc] initWithTarget:self + selector:@selector(tailOutput) + object:nil]; + [outputThread_ start]; +} + +- (void)session:(DTiPhoneSimulatorSession*)session + didEndWithError:(NSError*)error { + appRunning_ = NO; + // Wait for the output thread to finish copying data to stdout. + if (outputThread_) { + while (![outputThread_ isFinished]) { + [NSThread sleepForTimeInterval:kOutputPollIntervalSeconds]; + } + [outputThread_ release]; + outputThread_ = nil; + } + + if (error) { + // There appears to be a race condition where sometimes the simulator + // framework will end with an error, but the error is that the simulated + // app cleanly shut down; try to trap this error and don't fail the + // simulator run. + NSString* localizedDescription = [error localizedDescription]; + NSString* ignorableErrorStr = + [self localizedSimulatorErrorString:kSimulatorAppQuitErrorKey]; + if ([ignorableErrorStr isEqual:localizedDescription]) { + LogWarning(@"Ignoring that Simulator ended with: \"%@\" (%@:%ld)", + localizedDescription, [error domain], + static_cast([error code])); + } else { + LogError(@"Simulator ended with error: \"%@\" (%@:%ld)", + localizedDescription, [error domain], + static_cast([error code])); + exit(kExitFailure); + } + } + + // Check if the simulated app exited abnormally by looking for system log + // messages from launchd that refer to the simulated app's PID. Limit query + // to messages in the last minute since PIDs are cyclical. + aslmsg query = asl_new(ASL_TYPE_QUERY); + asl_set_query(query, ASL_KEY_SENDER, "launchd", + ASL_QUERY_OP_EQUAL | ASL_QUERY_OP_SUBSTRING); + asl_set_query(query, ASL_KEY_REF_PID, + [[[session simulatedApplicationPID] stringValue] UTF8String], + ASL_QUERY_OP_EQUAL); + asl_set_query(query, ASL_KEY_TIME, "-1m", ASL_QUERY_OP_GREATER_EQUAL); + + // Log any messages found, and take note of any messages that may indicate the + // app crashed or did not exit cleanly. + aslresponse response = asl_search(NULL, query); + BOOL badEntryFound = NO; + aslmsg entry; + while ((entry = aslresponse_next(response)) != NULL) { + const char* message = asl_get(entry, ASL_KEY_MSG); + LogWarning(@"Console message: %s", message); + // Some messages are harmless, so don't trigger a failure for them. + if (strstr(message, "The following job tried to hijack the service")) + continue; + badEntryFound = YES; + } + + // If the query returned any nasty-looking results, iossim should exit with + // non-zero status. + if (badEntryFound) { + LogError(@"Simulated app crashed or exited with non-zero status"); + exit(kExitAppCrashed); + } + exit(kExitSuccess); +} +@end + +namespace { + +// Finds the developer dir via xcode-select or the DEVELOPER_DIR environment +// variable. +NSString* FindDeveloperDir() { + // Check the env first. + NSDictionary* env = [[NSProcessInfo processInfo] environment]; + NSString* developerDir = [env objectForKey:@"DEVELOPER_DIR"]; + if ([developerDir length] > 0) + return developerDir; + + // Go look for it via xcode-select. + NSTask* xcodeSelectTask = [[[NSTask alloc] init] autorelease]; + [xcodeSelectTask setLaunchPath:@"/usr/bin/xcode-select"]; + [xcodeSelectTask setArguments:[NSArray arrayWithObject:@"-print-path"]]; + + NSPipe* outputPipe = [NSPipe pipe]; + [xcodeSelectTask setStandardOutput:outputPipe]; + NSFileHandle* outputFile = [outputPipe fileHandleForReading]; + + [xcodeSelectTask launch]; + NSData* outputData = [outputFile readDataToEndOfFile]; + [xcodeSelectTask terminate]; + + NSString* output = + [[[NSString alloc] initWithData:outputData + encoding:NSUTF8StringEncoding] autorelease]; + output = [output stringByTrimmingCharactersInSet: + [NSCharacterSet whitespaceAndNewlineCharacterSet]]; + if ([output length] == 0) + output = nil; + return output; +} + +// Loads the Simulator framework from the given developer dir. +NSBundle* LoadSimulatorFramework(NSString* developerDir) { + // The Simulator framework depends on some of the other Xcode private + // frameworks; manually load them first so everything can be linked up. + NSString* devToolsFoundationPath = [developerDir + stringByAppendingPathComponent:kDevToolsFoundationRelativePath]; + NSBundle* devToolsFoundationBundle = + [NSBundle bundleWithPath:devToolsFoundationPath]; + if (![devToolsFoundationBundle load]) + return nil; + NSString* simBundlePath = [developerDir + stringByAppendingPathComponent:kSimulatorFrameworkRelativePath]; + NSBundle* simBundle = [NSBundle bundleWithPath:simBundlePath]; + if (![simBundle load]) + return nil; + return simBundle; +} + +// Helper to find a class by name and die if it isn't found. +Class FindClassByName(NSString* nameOfClass) { + Class theClass = NSClassFromString(nameOfClass); + if (!theClass) { + LogError(@"Failed to find class %@ at runtime.", nameOfClass); + exit(kExitInitializationFailure); + } + return theClass; +} + +// Converts the given app path to an application spec, which requires an +// absolute path. +DTiPhoneSimulatorApplicationSpecifier* BuildAppSpec(NSString* appPath) { + Class applicationSpecifierClass = + FindClassByName(@"DTiPhoneSimulatorApplicationSpecifier"); + if (![appPath isAbsolutePath]) { + NSString* cwd = [[NSFileManager defaultManager] currentDirectoryPath]; + appPath = [cwd stringByAppendingPathComponent:appPath]; + } + appPath = [appPath stringByStandardizingPath]; + return [applicationSpecifierClass specifierWithApplicationPath:appPath]; +} + +// Returns the system root for the given SDK version. If sdkVersion is nil, the +// default system root is returned. Will return nil if the sdkVersion is not +// valid. +DTiPhoneSimulatorSystemRoot* BuildSystemRoot(NSString* sdkVersion) { + Class systemRootClass = FindClassByName(@"DTiPhoneSimulatorSystemRoot"); + DTiPhoneSimulatorSystemRoot* systemRoot = [systemRootClass defaultRoot]; + if (sdkVersion) + systemRoot = [systemRootClass rootWithSDKVersion:sdkVersion]; + + return systemRoot; +} + +// Builds a config object for starting the specified app. +DTiPhoneSimulatorSessionConfig* BuildSessionConfig( + DTiPhoneSimulatorApplicationSpecifier* appSpec, + DTiPhoneSimulatorSystemRoot* systemRoot, + NSString* stdoutPath, + NSString* stderrPath, + NSArray* appArgs, + NSDictionary* appEnv, + NSNumber* deviceFamily) { + Class sessionConfigClass = FindClassByName(@"DTiPhoneSimulatorSessionConfig"); + DTiPhoneSimulatorSessionConfig* sessionConfig = + [[[sessionConfigClass alloc] init] autorelease]; + sessionConfig.applicationToSimulateOnStart = appSpec; + sessionConfig.simulatedSystemRoot = systemRoot; + sessionConfig.localizedClientName = @"chromium"; + sessionConfig.simulatedApplicationStdErrPath = stderrPath; + sessionConfig.simulatedApplicationStdOutPath = stdoutPath; + sessionConfig.simulatedApplicationLaunchArgs = appArgs; + sessionConfig.simulatedApplicationLaunchEnvironment = appEnv; + sessionConfig.simulatedDeviceFamily = deviceFamily; + return sessionConfig; +} + +// Builds a simulator session that will use the given delegate. +DTiPhoneSimulatorSession* BuildSession(SimulatorDelegate* delegate) { + Class sessionClass = FindClassByName(@"DTiPhoneSimulatorSession"); + DTiPhoneSimulatorSession* session = + [[[sessionClass alloc] init] autorelease]; + session.delegate = delegate; + return session; +} + +// Creates a temporary directory with a unique name based on the provided +// template. The template should not contain any path separators and be suffixed +// with X's, which will be substituted with a unique alphanumeric string (see +// 'man mkdtemp' for details). The directory will be created as a subdirectory +// of NSTemporaryDirectory(). For example, if dirNameTemplate is 'test-XXX', +// this method would return something like '/path/to/tempdir/test-3n2'. +// +// Returns the absolute path of the newly-created directory, or nill if unable +// to create a unique directory. +NSString* CreateTempDirectory(NSString* dirNameTemplate) { + NSString* fullPathTemplate = + [NSTemporaryDirectory() stringByAppendingPathComponent:dirNameTemplate]; + char* fullPath = mkdtemp(const_cast([fullPathTemplate UTF8String])); + if (fullPath == NULL) + return nil; + + return [NSString stringWithUTF8String:fullPath]; +} + +// Creates the necessary directory structure under the given user home directory +// path. +// Returns YES if successful, NO if unable to create the directories. +BOOL CreateHomeDirSubDirs(NSString* userHomePath) { + NSFileManager* fileManager = [NSFileManager defaultManager]; + + // Create user home and subdirectories. + NSArray* subDirsToCreate = [NSArray arrayWithObjects: + @"Documents", + @"Library/Caches", + @"Library/Preferences", + nil]; + for (NSString* subDir in subDirsToCreate) { + NSString* path = [userHomePath stringByAppendingPathComponent:subDir]; + NSError* error; + if (![fileManager createDirectoryAtPath:path + withIntermediateDirectories:YES + attributes:nil + error:&error]) { + LogError(@"Unable to create directory: %@. Error: %@", + path, [error localizedDescription]); + return NO; + } + } + + return YES; +} + +// Creates the necessary directory structure under the given user home directory +// path, then sets the path in the appropriate environment variable. +// Returns YES if successful, NO if unable to create or initialize the given +// directory. +BOOL InitializeSimulatorUserHome(NSString* userHomePath, NSString* deviceName) { + if (!CreateHomeDirSubDirs(userHomePath)) + return NO; + + // Set the device to simulate. Note that the iOS Simulator must not be running + // for this setting to take effect. + CFStringRef iPhoneSimulatorAppID = CFSTR("com.apple.iphonesimulator"); + CFPreferencesSetAppValue(CFSTR("SimulateDevice"), + deviceName, + iPhoneSimulatorAppID); + CFPreferencesAppSynchronize(iPhoneSimulatorAppID); + + // Update the environment to use the specified directory as the user home + // directory. + // Note: the third param of setenv specifies whether or not to overwrite the + // variable's value if it has already been set. + if ((setenv(kUserHomeEnvVariable, [userHomePath UTF8String], YES) == -1) || + (setenv(kHomeEnvVariable, [userHomePath UTF8String], YES) == -1)) { + LogError(@"Unable to set environment variables for home directory."); + return NO; + } + + return YES; +} + +// Performs a case-insensitive search to see if |stringToSearch| begins with +// |prefixToFind|. Returns true if a match is found. +BOOL CaseInsensitivePrefixSearch(NSString* stringToSearch, + NSString* prefixToFind) { + NSStringCompareOptions options = (NSAnchoredSearch | NSCaseInsensitiveSearch); + NSRange range = [stringToSearch rangeOfString:prefixToFind + options:options]; + return range.location != NSNotFound; +} + +// Prints the usage information to stderr. +void PrintUsage() { + fprintf(stderr, "Usage: iossim [-d device] [-s sdkVersion] [-u homeDir] " + "[-e envKey=value]* [-t startupTimeout] []\n" + " where is the path to the .app directory and appArgs are any" + " arguments to send the simulated app.\n" + "\n" + "Options:\n" + " -d Specifies the device (must be one of the values from the iOS" + " Simulator's Hardware -> Device menu. Defaults to 'iPhone'.\n" + " -s Specifies the SDK version to use (e.g '4.3')." + " Will use system default if not specified.\n" + " -u Specifies a user home directory for the simulator." + " Will create a new directory if not specified.\n" + " -e Specifies an environment key=value pair that will be" + " set in the simulated application's environment.\n" + " -t Specifies the session startup timeout (in seconds)." + " Defaults to %d.\n", + static_cast(kDefaultSessionStartTimeoutSeconds)); +} + +} // namespace + +int main(int argc, char* const argv[]) { + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + // basename() may modify the passed in string and it returns a pointer to an + // internal buffer. Give it a copy to modify, and copy what it returns. + char* worker = strdup(argv[0]); + char* toolName = basename(worker); + if (toolName != NULL) { + toolName = strdup(toolName); + if (toolName != NULL) + gToolName = toolName; + } + if (worker != NULL) + free(worker); + + NSString* appPath = nil; + NSString* appName = nil; + NSString* sdkVersion = nil; + NSString* deviceName = @"iPhone"; + NSString* simHomePath = nil; + NSMutableArray* appArgs = [NSMutableArray array]; + NSMutableDictionary* appEnv = [NSMutableDictionary dictionary]; + NSTimeInterval sessionStartTimeout = kDefaultSessionStartTimeoutSeconds; + + // Parse the optional arguments + int c; + while ((c = getopt(argc, argv, "hs:d:u:e:t:")) != -1) { + switch (c) { + case 's': + sdkVersion = [NSString stringWithUTF8String:optarg]; + break; + case 'd': + deviceName = [NSString stringWithUTF8String:optarg]; + break; + case 'u': + simHomePath = [[NSFileManager defaultManager] + stringWithFileSystemRepresentation:optarg length:strlen(optarg)]; + break; + case 'e': { + NSString* envLine = [NSString stringWithUTF8String:optarg]; + NSRange range = [envLine rangeOfString:@"="]; + if (range.location == NSNotFound) { + LogError(@"Invalid key=value argument for -e."); + PrintUsage(); + exit(kExitInvalidArguments); + } + NSString* key = [envLine substringToIndex:range.location]; + NSString* value = [envLine substringFromIndex:(range.location + 1)]; + [appEnv setObject:value forKey:key]; + } + break; + case 't': { + int timeout = atoi(optarg); + if (timeout > 0) { + sessionStartTimeout = static_cast(timeout); + } else { + LogError(@"Invalid startup timeout (%s).", optarg); + PrintUsage(); + exit(kExitInvalidArguments); + } + } + break; + case 'h': + PrintUsage(); + exit(kExitSuccess); + default: + PrintUsage(); + exit(kExitInvalidArguments); + } + } + + // There should be at least one arg left, specifying the app path. Any + // additional args are passed as arguments to the app. + if (optind < argc) { + appPath = [[NSFileManager defaultManager] + stringWithFileSystemRepresentation:argv[optind] + length:strlen(argv[optind])]; + appName = [appPath lastPathComponent]; + while (++optind < argc) { + [appArgs addObject:[NSString stringWithUTF8String:argv[optind]]]; + } + } else { + LogError(@"Unable to parse command line arguments."); + PrintUsage(); + exit(kExitInvalidArguments); + } + + NSString* developerDir = FindDeveloperDir(); + if (!developerDir) { + LogError(@"Unable to find developer directory."); + exit(kExitInitializationFailure); + } + + NSBundle* simulatorFramework = LoadSimulatorFramework(developerDir); + if (!simulatorFramework) { + LogError(@"Failed to load the Simulator Framework."); + exit(kExitInitializationFailure); + } + + // Make sure the app path provided is legit. + DTiPhoneSimulatorApplicationSpecifier* appSpec = BuildAppSpec(appPath); + if (!appSpec) { + LogError(@"Invalid app path: %@", appPath); + exit(kExitInitializationFailure); + } + + // Make sure the SDK path provided is legit (or nil). + DTiPhoneSimulatorSystemRoot* systemRoot = BuildSystemRoot(sdkVersion); + if (!systemRoot) { + LogError(@"Invalid SDK version: %@", sdkVersion); + exit(kExitInitializationFailure); + } + + // Get the paths for stdout and stderr so the simulated app's output will show + // up in the caller's stdout/stderr. + NSString* outputDir = CreateTempDirectory(@"iossim-XXXXXX"); + NSString* stdioPath = [outputDir stringByAppendingPathComponent:@"stdio.txt"]; + + // Determine the deviceFamily based on the deviceName + NSNumber* deviceFamily = nil; + if (!deviceName || CaseInsensitivePrefixSearch(deviceName, @"iPhone")) { + deviceFamily = [NSNumber numberWithInt:kIPhoneFamily]; + } else if (CaseInsensitivePrefixSearch(deviceName, @"iPad")) { + deviceFamily = [NSNumber numberWithInt:kIPadFamily]; + } else { + LogError(@"Invalid device name: %@. Must begin with 'iPhone' or 'iPad'", + deviceName); + exit(kExitInvalidArguments); + } + + // Set up the user home directory for the simulator + if (!simHomePath) { + NSString* dirNameTemplate = + [NSString stringWithFormat:@"iossim-%@-%@-XXXXXX", appName, deviceName]; + simHomePath = CreateTempDirectory(dirNameTemplate); + if (!simHomePath) { + LogError(@"Unable to create unique directory for template %@", + dirNameTemplate); + exit(kExitInitializationFailure); + } + } + if (!InitializeSimulatorUserHome(simHomePath, deviceName)) { + LogError(@"Unable to initialize home directory for simulator: %@", + simHomePath); + exit(kExitInitializationFailure); + } + + // Create the config and simulator session. + DTiPhoneSimulatorSessionConfig* config = BuildSessionConfig(appSpec, + systemRoot, + stdioPath, + stdioPath, + appArgs, + appEnv, + deviceFamily); + SimulatorDelegate* delegate = + [[[SimulatorDelegate alloc] initWithStdioPath:stdioPath + developerDir:developerDir] autorelease]; + DTiPhoneSimulatorSession* session = BuildSession(delegate); + + // Start the simulator session. + NSError* error; + BOOL started = [session requestStartWithConfig:config + timeout:sessionStartTimeout + error:&error]; + + // Spin the runtime indefinitely. When the delegate gets the message that the + // app has quit it will exit this program. + if (started) { + [[NSRunLoop mainRunLoop] run]; + } else { + LogError(@"Simulator failed request to start: \"%@\" (%@:%ld)", + [error localizedDescription], + [error domain], static_cast([error code])); + } + + // Note that this code is only executed if the simulator fails to start + // because once the main run loop is started, only the delegate calling + // exit() will end the program. + [pool drain]; + return kExitFailure; +} diff --git a/testing/iossim/redirect-stdout.sh b/testing/iossim/redirect-stdout.sh new file mode 100755 index 0000000000..feff2c9694 --- /dev/null +++ b/testing/iossim/redirect-stdout.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# Copyright (c) 2012 The Chromium 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 executes the command given as the first argument and redirects +# the command's stdout to the file given as the second argument. +# +# Example: Write the text 'foo' to a file called out.txt: +# RedirectStdout.sh "echo foo" out.txt +# +# This script is invoked from iossim.gyp in order to redirect the output of +# class-dump to a file (because gyp actions don't support redirecting output). + +if [ ${#} -ne 2 ] ; then + echo "usage: ${0} " + exit 2 +fi + +exec $1 > $2 \ No newline at end of file diff --git a/testing/multiprocess_func_list.cc b/testing/multiprocess_func_list.cc new file mode 100644 index 0000000000..49ae07dd3e --- /dev/null +++ b/testing/multiprocess_func_list.cc @@ -0,0 +1,57 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "multiprocess_func_list.h" + +#include + +// Helper functions to maintain mapping of "test name"->test func. +// The information is accessed via a global map. +namespace multi_process_function_list { + +namespace { + +struct ProcessFunctions { + ProcessFunctions() : main(NULL), setup(NULL) {} + ProcessFunctions(TestMainFunctionPtr main, SetupFunctionPtr setup) + : main(main), + setup(setup) { + } + TestMainFunctionPtr main; + SetupFunctionPtr setup; +}; + +typedef std::map MultiProcessTestMap; + +// Retrieve a reference to the global 'func name' -> func ptr map. +MultiProcessTestMap& GetMultiprocessFuncMap() { + static MultiProcessTestMap test_name_to_func_ptr_map; + return test_name_to_func_ptr_map; +} + +} // namespace + +AppendMultiProcessTest::AppendMultiProcessTest( + std::string test_name, + TestMainFunctionPtr main_func_ptr, + SetupFunctionPtr setup_func_ptr) { + GetMultiprocessFuncMap()[test_name] = + ProcessFunctions(main_func_ptr, setup_func_ptr); +} + +int InvokeChildProcessTest(std::string test_name) { + MultiProcessTestMap& func_lookup_table = GetMultiprocessFuncMap(); + MultiProcessTestMap::iterator it = func_lookup_table.find(test_name); + if (it != func_lookup_table.end()) { + const ProcessFunctions& process_functions = it->second; + if (process_functions.setup) + (*process_functions.setup)(); + if (process_functions.main) + return (*process_functions.main)(); + } + + return -1; +} + +} // namespace multi_process_function_list diff --git a/testing/multiprocess_func_list.h b/testing/multiprocess_func_list.h new file mode 100644 index 0000000000..f806d53c93 --- /dev/null +++ b/testing/multiprocess_func_list.h @@ -0,0 +1,70 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef TESTING_MULTIPROCESS_FUNC_LIST_H_ +#define TESTING_MULTIPROCESS_FUNC_LIST_H_ + +#include + +// This file provides the plumbing to register functions to be executed +// as the main function of a child process in a multi-process test. +// This complements the MultiProcessTest class which provides facilities +// for launching such tests. +// +// The MULTIPROCESS_TEST_MAIN() macro registers a string -> func_ptr mapping +// by creating a new global instance of the AppendMultiProcessTest() class +// this means that by the time that we reach our main() function the mapping +// is already in place. +// +// Example usage: +// MULTIPROCESS_TEST_MAIN(a_test_func) { +// // Code here runs in a child process. +// return 0; +// } +// +// The prototype of a_test_func is implicitly +// int test_main_func_name(); + +namespace multi_process_function_list { + +// Type for child process main functions. +typedef int (*TestMainFunctionPtr)(); + +// Type for child setup functions. +typedef void (*SetupFunctionPtr)(); + +// Helper class to append a test function to the global mapping. +// Used by the MULTIPROCESS_TEST_MAIN macro. +class AppendMultiProcessTest { + public: + // |main_func_ptr| is the main function that is run in the child process. + // |setup_func_ptr| is a function run when the global mapping is added. + AppendMultiProcessTest(std::string test_name, + TestMainFunctionPtr main_func_ptr, + SetupFunctionPtr setup_func_ptr); +}; + +// Invoke the main function of a test previously registered with +// MULTIPROCESS_TEST_MAIN() +int InvokeChildProcessTest(std::string test_name); + +// This macro creates a global MultiProcessTest::AppendMultiProcessTest object +// whose constructor does the work of adding the global mapping. +#define MULTIPROCESS_TEST_MAIN(test_main) \ + MULTIPROCESS_TEST_MAIN_WITH_SETUP(test_main, NULL) + +// Same as above but lets callers specify a setup method that is run in the +// child process, just before the main function is run. This facilitates +// adding a generic one-time setup function for multiple tests. +#define MULTIPROCESS_TEST_MAIN_WITH_SETUP(test_main, test_setup) \ + int test_main(); \ + namespace { \ + multi_process_function_list::AppendMultiProcessTest \ + AddMultiProcessTest##_##test_main(#test_main, (test_main), (test_setup)); \ + } \ + int test_main() + +} // namespace multi_process_function_list + +#endif // TESTING_MULTIPROCESS_FUNC_LIST_H_ diff --git a/testing/platform_test.h b/testing/platform_test.h new file mode 100644 index 0000000000..04fc845bd9 --- /dev/null +++ b/testing/platform_test.h @@ -0,0 +1,36 @@ +// 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 TESTING_PLATFORM_TEST_H_ +#define TESTING_PLATFORM_TEST_H_ + +#include + +#if defined(GTEST_OS_MAC) +#ifdef __OBJC__ +@class NSAutoreleasePool; +#else +class NSAutoreleasePool; +#endif + +// The purpose of this class us to provide a hook for platform-specific +// operations across unit tests. For example, on the Mac, it creates and +// releases an outer NSAutoreleasePool for each test case. For now, it's only +// implemented on the Mac. To enable this for another platform, just adjust +// the #ifdefs and add a platform_test_.cc implementation file. +class PlatformTest : public testing::Test { + public: + virtual ~PlatformTest(); + + protected: + PlatformTest(); + + private: + NSAutoreleasePool* pool_; +}; +#else +typedef testing::Test PlatformTest; +#endif // GTEST_OS_MAC + +#endif // TESTING_PLATFORM_TEST_H_ diff --git a/testing/platform_test_mac.mm b/testing/platform_test_mac.mm new file mode 100644 index 0000000000..bd22cd5b45 --- /dev/null +++ b/testing/platform_test_mac.mm @@ -0,0 +1,15 @@ +// 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 "platform_test.h" + +#import + +PlatformTest::PlatformTest() + : pool_([[NSAutoreleasePool alloc] init]) { +} + +PlatformTest::~PlatformTest() { + [pool_ release]; +} diff --git a/testing/test_env.py b/testing/test_env.py new file mode 100755 index 0000000000..ec98a11ce6 --- /dev/null +++ b/testing/test_env.py @@ -0,0 +1,93 @@ +#!/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. + +"""Sets environment variables needed to run a chromium unit test.""" + +import os +import stat +import subprocess +import sys + +# This is hardcoded to be src/ relative to this script. +ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +CHROME_SANDBOX_ENV = 'CHROME_DEVEL_SANDBOX' +CHROME_SANDBOX_PATH = '/opt/chromium/chrome_sandbox' + + +def should_enable_sandbox(sandbox_path): + """Return a boolean indicating that the current slave is capable of using the + sandbox and should enable it. This should return True iff the slave is a + Linux host with the sandbox file present and configured correctly.""" + if not (sys.platform.startswith('linux') and + os.path.exists(sandbox_path)): + return False + sandbox_stat = os.stat(sandbox_path) + if ((sandbox_stat.st_mode & stat.S_ISUID) and + (sandbox_stat.st_mode & stat.S_IRUSR) and + (sandbox_stat.st_mode & stat.S_IXUSR) and + (sandbox_stat.st_uid == 0)): + return True + return False + + +def enable_sandbox_if_required(env, verbose=False): + """Checks enables the sandbox if it is required, otherwise it disables it.""" + chrome_sandbox_path = env.get(CHROME_SANDBOX_ENV, CHROME_SANDBOX_PATH) + + if should_enable_sandbox(chrome_sandbox_path): + if verbose: + print 'Enabling sandbox. Setting environment variable:' + print ' %s="%s"' % (CHROME_SANDBOX_ENV, chrome_sandbox_path) + env[CHROME_SANDBOX_ENV] = chrome_sandbox_path + else: + if verbose: + print 'Sandbox not properly installed. Unsetting:' + print ' %s' % CHROME_SANDBOX_ENV + # The variable should be removed from the environment, making + # the variable empty silently disables the sandbox. + if env.get(CHROME_SANDBOX_ENV): + env.pop(CHROME_SANDBOX_ENV) + + +def fix_python_path(cmd): + """Returns the fixed command line to call the right python executable.""" + out = cmd[:] + if out[0] == 'python': + out[0] = sys.executable + elif out[0].endswith('.py'): + out.insert(0, sys.executable) + return out + + +def run_executable(cmd, env): + """Runs an executable with: + - environment variable CR_SOURCE_ROOT set to the root directory. + - environment variable LANGUAGE to en_US.UTF-8. + - environment variable CHROME_DEVEL_SANDBOX set if need + - Reuses sys.executable automatically. + """ + # Many tests assume a English interface... + env['LANG'] = 'en_US.UTF-8' + # Used by base/base_paths_linux.cc as an override. Just make sure the default + # logic is used. + env.pop('CR_SOURCE_ROOT', None) + enable_sandbox_if_required(env) + # Ensure paths are correctly separated on windows. + cmd[0] = cmd[0].replace('/', os.path.sep) + cmd = fix_python_path(cmd) + try: + return subprocess.call(cmd, env=env) + except OSError: + print >> sys.stderr, 'Failed to start %s' % cmd + raise + + +def main(): + return run_executable(sys.argv[1:], os.environ.copy()) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/testing/xvfb.py b/testing/xvfb.py new file mode 100755 index 0000000000..6ac0056710 --- /dev/null +++ b/testing/xvfb.py @@ -0,0 +1,133 @@ +#!/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. + +"""Runs the test with xvfb on linux. Runs the test normally on other platforms. + +For simplicity in gyp targets, this script just runs the test normal on +non-linux platforms. +""" + +import os +import platform +import signal +import subprocess +import sys + +import test_env + + +def kill(pid): + """Kills a process and traps exception if the process doesn't exist anymore. + """ + # If the process doesn't exist, it raises an exception that we can ignore. + try: + os.kill(pid, signal.SIGKILL) + except OSError: + pass + + +def get_xvfb_path(server_dir): + """Figures out which X server to use.""" + xvfb_path = os.path.join(server_dir, 'Xvfb.' + platform.architecture()[0]) + if not os.path.exists(xvfb_path): + xvfb_path = os.path.join(server_dir, 'Xvfb') + if not os.path.exists(xvfb_path): + print >> sys.stderr, ( + 'No Xvfb found in designated server path: %s' % server_dir) + raise Exception('No virtual server') + return xvfb_path + + +def start_xvfb(xvfb_path, display): + """Starts a virtual X server that we run the tests in. + + This makes it so we can run the tests even if we didn't start the tests from + an X session. + + Args: + xvfb_path: Path to Xvfb. + """ + cmd = [xvfb_path, display, '-screen', '0', '1024x768x24', '-ac'] + try: + proc = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + except OSError: + print >> sys.stderr, 'Failed to run %s' % ' '.join(cmd) + return 0 + return proc.pid + + +def wait_for_xvfb(xdisplaycheck, env): + """Waits for xvfb to be fully initialized by using xdisplaycheck.""" + try: + subprocess.check_call( + [xdisplaycheck], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + env=env) + except OSError: + print >> sys.stderr, 'Failed to load %s with cwd=%s' % ( + xdisplaycheck, os.getcwd()) + return False + except subprocess.CalledProcessError: + print >> sys.stderr, ( + 'Xvfb failed to load properly while trying to run %s' % xdisplaycheck) + return False + return True + + +def run_executable(cmd, build_dir, env): + """Runs an executable within a xvfb buffer on linux or normally on other + platforms. + + Requires that both xvfb and icewm are installed on linux. + + Detects recursion with an environment variable and do not create a recursive X + buffer if present. + """ + # First look if we are inside a display. + if env.get('_CHROMIUM_INSIDE_XVFB') == '1': + # No need to recurse. + return test_env.run_executable(cmd, env) + + pid = None + xvfb = 'Xvfb' + try: + if sys.platform == 'linux2': + # Defaults to X display 9. + display = ':9' + pid = start_xvfb(xvfb, display) + if not pid: + return 1 + env['DISPLAY'] = display + if not wait_for_xvfb(os.path.join(build_dir, 'xdisplaycheck'), env): + return 3 + # Inhibit recursion. + env['_CHROMIUM_INSIDE_XVFB'] = '1' + # Some ChromeOS tests need a window manager. Technically, it could be + # another script but that would be overkill. + try: + ice_cmd = ['icewm'] + subprocess.Popen( + ice_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env) + except OSError: + print >> sys.stderr, 'Failed to run %s' % ' '.join(ice_cmd) + return 1 + return test_env.run_executable(cmd, env) + finally: + if pid: + kill(pid) + + +def main(): + if len(sys.argv) < 3: + print >> sys.stderr, ( + 'Usage: xvfb.py [path to build_dir] [command args...]') + return 2 + return run_executable(sys.argv[2:], sys.argv[1], os.environ.copy()) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/third_party/icu b/third_party/icu new file mode 160000 index 0000000000..e39fea8978 --- /dev/null +++ b/third_party/icu @@ -0,0 +1 @@ +Subproject commit e39fea897879c8004609e382d1e568a85c3ab345 diff --git a/third_party/libevent/ChangeLog b/third_party/libevent/ChangeLog new file mode 100644 index 0000000000..50eb6b3676 --- /dev/null +++ b/third_party/libevent/ChangeLog @@ -0,0 +1,201 @@ +Changes in 1.4.13-stable: + o If the kernel tells us that there are a negative number of bytes to read from a socket, do not believe it. Fixes bug 2841177; found by Alexander Pronchenkov. + o Do not allocate the maximum event queue and fd array for the epoll backend at startup. Instead, start out accepting 32 events at a time, and double the queue's size when it seems that the OS is generating events faster than we're requesting them. Saves up to 512K per epoll-based event_base. Resolves bug 2839240. + o Fix compilation on Android, which forgot to define fd_mask in its sys/select.h + o Do not drop data from evbuffer when out of memory; reported by Jacek Masiulaniec + o Rename our replacement compat/sys/_time.h header to avoid build a conflict on HPUX; reported by Kathryn Hogg. + o Build kqueue.c correctly on GNU/kFreeBSD platforms. Patch pulled upstream from Debian. + o Fix a problem with excessive memory allocation when using multiple event priorities. + o When running set[ug]id, don't check the environment. Based on a patch from OpenBSD. + + +Changes in 1.4.12-stable: + o Try to contain degree of failure when running on a win32 version so heavily firewalled that we can't fake a socketpair. + o Fix an obscure timing-dependent, allocator-dependent crash in the evdns code. + o Use __VA_ARGS__ syntax for varargs macros in event_rpcgen when compiler is not GCC. + o Activate fd events in a pseudorandom order with O(N) backends, so that we don't systematically favor low fds (select) or earlier-added fds (poll, win32). + o Fix another pair of fencepost bugs in epoll.c. [Patch from Adam Langley.] + o Do not break evdns connections to nameservers when our IP changes. + o Set truncated flag correctly in evdns server replies. + o Disable strict aliasing with GCC: our code is not compliant with it. + +Changes in 1.4.11-stable: + o Fix a bug when removing a timeout from the heap. [Patch from Marko Kreen] + o Remove the limit on size of HTTP headers by removing static buffers. + o Fix a nasty dangling pointer bug in epoll.c that could occur after epoll_recalc(). [Patch from Kevin Springborn] + o Distribute Win32-Code/event-config.h, not ./event-config.h + +Changes in 1.4.10-stable: + o clean up buffered http connection data on reset; reported by Brian O'Kelley + o bug fix and potential race condition in signal handling; from Alexander Drozdov + o rename the Solaris event ports backend to evport + o support compilation on Haiku + o fix signal processing when a signal callback delivers a signal; from Alexander Drozdov + o const-ify some arguments to evdns functions. + o off-by-one error in epoll_recalc; reported by Victor Goya + o include Doxyfile in tar ball; from Jeff Garzik + o correctly parse queries with encoded \r, \n or + characters + +Changes in 1.4.9-stable: + o event_add would not return error for some backends; from Dean McNamee + o Clear the timer cache on entering the event loop; reported by Victor Chang + o Only bind the socket on connect when a local address has been provided; reported by Alejo Sanchez + o Allow setting of local port for evhttp connections to support millions of connections from a single system; from Richard Jones. + o Clear the timer cache when leaving the event loop; reported by Robin Haberkorn + o Fix a typo in setting the global event base; reported by lance. + o Fix a memory leak when reading multi-line headers + o Fix a memory leak by not running explicit close detection for server connections + +Changes in 1.4.8-stable: + o Match the query in DNS replies to the query in the request; from Vsevolod Stakhov. + o Fix a merge problem in which name_from_addr returned pointers to the stack; found by Jiang Hong. + o Do not remove Accept-Encoding header + +Changes in 1.4.7-stable: + o Fix a bug where headers arriving in multiple packets were not parsed; fix from Jiang Hong; test by me. + +Changes in 1.4.6-stable: + o evutil.h now includes directly + o switch all uses of [v]snprintf over to evutil + o Correct handling of trailing headers in chunked replies; from Scott Lamb. + o Support multi-line HTTP headers; based on a patch from Moshe Litvin + o Reject negative Content-Length headers; anonymous bug report + o Detect CLOCK_MONOTONIC at runtime for evdns; anonymous bug report + o Fix a bug where deleting signals with the kqueue backend would cause subsequent adds to fail + o Support multiple events listening on the same signal; make signals regular events that go on the same event queue; problem report by Alexander Drozdov. + o Deal with evbuffer_read() returning -1 on EINTR|EAGAIN; from Adam Langley. + o Fix a bug in which the DNS server would incorrectly set the type of a cname reply to a. + o Fix a bug where setting the timeout on a bufferevent would take not effect if the event was already pending. + o Fix a memory leak when using signals for some event bases; reported by Alexander Drozdov. + o Add libevent.vcproj file to distribution to help with Windows build. + o Fix a problem with epoll() and reinit; problem report by Alexander Drozdov. + o Fix off-by-one errors in devpoll; from Ian Bell + o Make event_add not change any state if it fails; reported by Ian Bell. + o Do not warn on accept when errno is either EAGAIN or EINTR + +Changes in 1.4.5-stable: + o Fix connection keep-alive behavior for HTTP/1.0 + o Fix use of freed memory in event_reinit; pointed out by Peter Postma + o Constify struct timeval * where possible; pointed out by Forest Wilkinson + o allow min_heap_erase to be called on removed members; from liusifan. + o Rename INPUT and OUTPUT to EVRPC_INPUT and EVRPC_OUTPUT. Retain INPUT/OUTPUT aliases on on-win32 platforms for backwards compatibility. + o Do not use SO_REUSEADDR when connecting + o Fix Windows build + o Fix a bug in event_rpcgen when generated fixed-sized entries + +Changes in 1.4.4-stable: + o Correct the documentation on buffer printf functions. + o Don't warn on unimplemented epoll_create(): this isn't a problem, just a reason to fall back to poll or select. + o Correctly handle timeouts larger than 35 minutes on Linux with epoll.c. This is probably a kernel defect, but we'll have to support old kernels anyway even if it gets fixed. + o Fix a potential stack corruption bug in tagging on 64-bit CPUs. + o expose bufferevent_setwatermark via header files and fix high watermark on read + o fix a bug in bufferevent read water marks and add a test for them + o introduce bufferevent_setcb and bufferevent_setfd to allow better manipulation of bufferevents + o use libevent's internal timercmp on all platforms, to avoid bugs on old platforms where timercmp(a,b,<=) is buggy. + o reduce system calls for getting current time by caching it. + o fix evhttp_bind_socket() so that multiple sockets can be bound by the same http server. + o Build test directory correctly with CPPFLAGS set. + o Fix build under Visual C++ 2005. + o Expose evhttp_accept_socket() API. + o Merge windows gettimeofday() replacement into a new evutil_gettimeofday() function. + o Fix autoconf script behavior on IRIX. + o Make sure winsock2.h include always comes before windows.h include. + +Changes in 1.4.3-stable: + o include Content-Length in reply for HTTP/1.0 requests with keep-alive + o Patch from Tani Hosokawa: make some functions in http.c threadsafe. + o Do not free the kqop file descriptor in other processes, also allow it to be 0; from Andrei Nigmatulin + o make event_rpcgen.py generate code include event-config.h; reported by Sam Banks. + o make event methods static so that they are not exported; from Andrei Nigmatulin + o make RPC replies use application/octet-stream as mime type + o do not delete uninitialized timeout event in evdns + +Changes in 1.4.2-rc: + o remove pending timeouts on event_base_free() + o also check EAGAIN for Solaris' event ports; from W.C.A. Wijngaards + o devpoll and evport need reinit; tested by W.C.A Wijngaards + o event_base_get_method; from Springande Ulv + o Send CRLF after each chunk in HTTP output, for compliance with RFC2626. Patch from "propanbutan". Fixes bug 1894184. + o Add a int64_t parsing function, with unit tests, so we can apply Scott Lamb's fix to allow large HTTP values. + o Use a 64-bit field to hold HTTP content-lengths. Patch from Scott Lamb. + o Allow regression code to build even without Python installed + o remove NDEBUG ifdefs from evdns.c + o update documentation of event_loop and event_base_loop; from Tani Hosokawa. + o detect integer types properly on platforms without stdint.h + o Remove "AM_MAINTAINER_MODE" declaration in configure.in: now makefiles and configure should get re-generated automatically when Makefile.am or configure.in chanes. + o do not insert event into list when evsel->add fails + +Changes in 1.4.1-beta: + o free minheap on event_base_free(); from Christopher Layne + o debug cleanups in signal.c; from Christopher Layne + o provide event_base_new() that does not set the current_base global + o bufferevent_write now uses a const source argument; report from Charles Kerr + o better documentation for event_base_loopexit; from Scott Lamb. + o Make kqueue have the same behavior as other backends when a signal is caught between event_add() and event_loop(). Previously, it would catch and ignore such signals. + o Make kqueue restore signal handlers correctly when event_del() is called. + o provide event_reinit() to reintialize an event_base after fork + o small improvements to evhttp documentation + o always generate Date and Content-Length headers for HTTP/1.1 replies + o set the correct event base for HTTP close events + o New function, event_{base_}loopbreak. Like event_loopexit, it makes an event loop stop executing and return. Unlike event_loopexit, it keeps subsequent pending events from getting executed. Patch from Scott Lamb + o Removed obsoleted recalc code + o pull setters/getters out of RPC structures into a base class to which we just need to store a pointer; this reduces the memory footprint of these structures. + o fix a bug with event_rpcgen for integers + o move EV_PERSIST handling out of the event backends + o support for 32-bit tag numbers in rpc structures; this is wire compatible, but changes the API slightly. + o prefix {encode,decode}_tag functions with evtag to avoid collisions + o Correctly handle DNS replies with no answers set (Fixes bug 1846282) + o The configure script now takes an --enable-gcc-warnigns option that turns on many optional gcc warnings. (Nick has been building with these for a while, but they might be useful to other developers.) + o When building with GCC, use the "format" attribute to verify type correctness of calls to printf-like functions. + o removed linger from http server socket; reported by Ilya Martynov + o allow \r or \n individually to separate HTTP headers instead of the standard "\r\n"; from Charles Kerr. + o demote most http warnings to debug messages + o Fix Solaris compilation; from Magne Mahre + o Add a "Date" header to HTTP responses, as required by HTTP 1.1. + o Support specifying the local address of an evhttp_connection using set_local_address + o Fix a memory leak in which failed HTTP connections would not free the request object + o Make adding of array members in event_rpcgen more efficient, but doubling memory allocation + o Fix a memory leak in the DNS server + o Fix compilation when DNS_USE_OPENSSL_FOR_ID is enabled + o Fix buffer size and string generation in evdns_resolve_reverse_ipv6(). + o Respond to nonstandard DNS queries with "NOTIMPL" rather than by ignoring them. + o In DNS responses, the CD flag should be preserved, not the TC flag. + o Fix http.c to compile properly with USE_DEBUG; from Christopher Layne + o Handle NULL timeouts correctly on Solaris; from Trond Norbye + o Recalculate pending events properly when reallocating event array on Solaris; from Trond Norbye + o Add Doxygen documentation to header files; from Mark Heily + o Add a evdns_set_transaction_id_fn() function to override the default + transaction ID generation code. + o Add an evutil module (with header evutil.h) to implement our standard cross-platform hacks, on the theory that somebody else would like to use them too. + o Fix signals implementation on windows. + o Fix http module on windows to close sockets properly. + o Make autogen.sh script run correctly on systems where /bin/sh isn't bash. (Patch from Trond Norbye, rewritten by Hagne Mahre and then Hannah Schroeter.) + o Skip calling gettime() in timeout_process if we are not in fact waiting for any events. (Patch from Trond Norbye) + o Make test subdirectory compile under mingw. + o Fix win32 buffer.c behavior so that it is correct for sockets (which do not like ReadFile and WriteFile). + o Make the test.sh script run unit tests for the evpoll method. + o Make the entire evdns.h header enclosed in "extern C" as appropriate. + o Fix implementation of strsep on platforms that lack it + o Fix implementation of getaddrinfo on platforms that lack it; mainly, this will make Windows http.c work better. Original patch by Lubomir Marinov. + o Fix evport implementation: port_disassociate called on unassociated events resulting in bogus errors; more efficient memory management; from Trond Norbye and Prakash Sangappa + o support for hooks on rpc input and output; can be used to implement rpc independent processing such as compression or authentication. + o use a min heap instead of a red-black tree for timeouts; as a result finding the min is a O(1) operation now; from Maxim Yegorushkin + o associate an event base with an rpc pool + o added two additional libraries: libevent_core and libevent_extra in addition to the regular libevent. libevent_core contains only the event core whereas libevent_extra contains dns, http and rpc support + o Begin using libtool's library versioning support correctly. If we don't mess up, this will more or less guarantee binaries linked against old versions of libevent continue working when we make changes to libevent that do not break backward compatibility. + o Fix evhttp.h compilation when TAILQ_ENTRY is not defined. + o Small code cleanups in epoll_dispatch(). + o Increase the maximum number of addresses read from a packet in evdns to 32. + o Remove support for the rtsig method: it hasn't compiled for a while, and nobody seems to miss it very much. Let us know if there's a good reason to put it back in. + o Rename the "class" field in evdns_server_request to dns_question_class, so that it won't break compilation under C++. Use a macro so that old code won't break. Mark the macro as deprecated. + o Fix DNS unit tests so that having a DNS server with broken IPv6 support is no longer cause for aborting the unit tests. + o Make event_base_free() succeed even if there are pending non-internal events on a base. This may still leak memory and fds, but at least it no longer crashes. + o Post-process the config.h file into a new, installed event-config.h file that we can install, and whose macros will be safe to include in header files. + o Remove the long-deprecated acconfig.h file. + o Do not require #include before #include . + o Add new evutil_timer* functions to wrap (or replace) the regular timeval manipulation functions. + o Fix many build issues when using the Microsoft C compiler. + o Remove a bash-ism in autogen.sh + o When calling event_del on a signal, restore the signal handler's previous value rather than setting it to SIG_DFL. Patch from Christopher Layne. + o Make the logic for active events work better with internal events; patch from Christopher Layne. + o We do not need to specially remove a timeout before calling event_del; patch from Christopher Layne. diff --git a/third_party/libevent/LICENSE b/third_party/libevent/LICENSE new file mode 100644 index 0000000000..af977a4115 --- /dev/null +++ b/third_party/libevent/LICENSE @@ -0,0 +1,24 @@ +Copyright 2000-2007 Niels Provos +Copyright 2007-2009 Niels Provos and Nick Mathewson + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/third_party/libevent/Makefile.am b/third_party/libevent/Makefile.am new file mode 100644 index 0000000000..dc78ef175a --- /dev/null +++ b/third_party/libevent/Makefile.am @@ -0,0 +1,124 @@ +AUTOMAKE_OPTIONS = foreign no-dependencies + +# This is the point release for libevent. It shouldn't include any +# a/b/c/d/e notations. +RELEASE = 1.4 + +# This is the version info for the libevent binary API. It has three +# numbers: +# Current -- the number of the binary API that we're implementing +# Revision -- which iteration of the implementation of the binary +# API are we supplying? +# Age -- How many previous binary API versions do we also +# support? +# +# If we release a new version that does not change the binary API, +# increment Revision. +# +# If we release a new version that changes the binary API, but does +# not break programs compiled against the old binary API, increment +# Current and Age. Set Revision to 0, since this is the first +# implementation of the new API. +# +# Otherwise, we're changing the binary API and breaking bakward +# compatibility with old binaries. Increment Current. Set Age to 0, +# since we're backward compatible with no previous APIs. Set Revision +# to 0 too. + +# History: +# Libevent 1.4.1 was 2:0:0 +# Libevent 1.4.2 should be 3:0:0 +# Libevent 1.4.5 is 3:0:1 (we forgot to increment in the past) +VERSION_INFO = 3:3:1 + +bin_SCRIPTS = event_rpcgen.py + +EXTRA_DIST = autogen.sh event.h event-internal.h log.h evsignal.h evdns.3 \ + evrpc.h evrpc-internal.h min_heap.h \ + event.3 \ + Doxyfile \ + kqueue.c epoll_sub.c epoll.c select.c poll.c signal.c \ + evport.c devpoll.c event_rpcgen.py \ + sample/Makefile.am sample/Makefile.in sample/event-test.c \ + sample/signal-test.c sample/time-test.c \ + test/Makefile.am test/Makefile.in test/bench.c test/regress.c \ + test/test-eof.c test/test-weof.c test/test-time.c \ + test/test-init.c test/test.sh \ + compat/sys/queue.h compat/sys/_libevent_time.h \ + WIN32-Code/config.h \ + WIN32-Code/event-config.h \ + WIN32-Code/win32.c \ + WIN32-Code/tree.h \ + WIN32-Prj/event_test/event_test.dsp \ + WIN32-Prj/event_test/test.txt WIN32-Prj/libevent.dsp \ + WIN32-Prj/libevent.dsw WIN32-Prj/signal_test/signal_test.dsp \ + WIN32-Prj/time_test/time_test.dsp WIN32-Prj/regress/regress.vcproj \ + WIN32-Prj/libevent.sln WIN32-Prj/libevent.vcproj + +lib_LTLIBRARIES = libevent.la libevent_core.la libevent_extra.la + +if BUILD_WIN32 + +SUBDIRS = . sample +SYS_LIBS = -lws2_32 +SYS_SRC = WIN32-Code/win32.c +SYS_INCLUDES = -IWIN32-Code + +else + +SUBDIRS = . sample test +SYS_LIBS = +SYS_SRC = +SYS_INCLUDES = + +endif + +BUILT_SOURCES = event-config.h + +event-config.h: config.h + echo '/* event-config.h' > $@ + echo ' * Generated by autoconf; post-processed by libevent.' >> $@ + echo ' * Do not edit this file.' >> $@ + echo ' * Do not rely on macros in this file existing in later versions.'>> $@ + echo ' */' >> $@ + echo '#ifndef _EVENT_CONFIG_H_' >> $@ + echo '#define _EVENT_CONFIG_H_' >> $@ + + sed -e 's/#define /#define _EVENT_/' \ + -e 's/#undef /#undef _EVENT_/' \ + -e 's/#ifndef /#ifndef _EVENT_/' < config.h >> $@ + echo "#endif" >> $@ + +CORE_SRC = event.c buffer.c evbuffer.c log.c evutil.c $(SYS_SRC) +EXTRA_SRC = event_tagging.c http.c evhttp.h http-internal.h evdns.c \ + evdns.h evrpc.c evrpc.h evrpc-internal.h \ + strlcpy.c strlcpy-internal.h strlcpy-internal.h + +libevent_la_SOURCES = $(CORE_SRC) $(EXTRA_SRC) +libevent_la_LIBADD = @LTLIBOBJS@ $(SYS_LIBS) +libevent_la_LDFLAGS = -release $(RELEASE) -version-info $(VERSION_INFO) + +libevent_core_la_SOURCES = $(CORE_SRC) +libevent_core_la_LIBADD = @LTLIBOBJS@ $(SYS_LIBS) +libevent_core_la_LDFLAGS = -release $(RELEASE) -version-info $(VERSION_INFO) + +libevent_extra_la_SOURCES = $(EXTRA_SRC) +libevent_extra_la_LIBADD = @LTLIBOBJS@ $(SYS_LIBS) +libevent_extra_la_LDFLAGS = -release $(RELEASE) -version-info $(VERSION_INFO) + +include_HEADERS = event.h evhttp.h evdns.h evrpc.h evutil.h + +nodist_include_HEADERS = event-config.h + +INCLUDES = -I$(srcdir)/compat $(SYS_INCLUDES) + +man_MANS = event.3 evdns.3 + +verify: libevent.la + cd test && make verify + +doxygen: FORCE + doxygen $(srcdir)/Doxyfile +FORCE: + +DISTCLEANFILES = *~ event-config.h diff --git a/third_party/libevent/Makefile.in b/third_party/libevent/Makefile.in new file mode 100644 index 0000000000..4d96c74cfa --- /dev/null +++ b/third_party/libevent/Makefile.in @@ -0,0 +1,976 @@ +# Makefile.in generated by automake 1.10.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = . +DIST_COMMON = README $(am__configure_deps) $(include_HEADERS) \ + $(srcdir)/Makefile.am $(srcdir)/Makefile.in \ + $(srcdir)/config.h.in $(top_srcdir)/configure ChangeLog \ + config.guess config.sub devpoll.c epoll.c epoll_sub.c evport.c \ + install-sh kqueue.c ltmain.sh missing mkinstalldirs poll.c \ + select.c signal.c +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.in +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \ + configure.lineno config.status.lineno +mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs +CONFIG_HEADER = config.h +CONFIG_CLEAN_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = `echo $$p | sed -e 's|^.*/||'`; +am__installdirs = "$(DESTDIR)$(libdir)" "$(DESTDIR)$(bindir)" \ + "$(DESTDIR)$(man3dir)" "$(DESTDIR)$(includedir)" \ + "$(DESTDIR)$(includedir)" +libLTLIBRARIES_INSTALL = $(INSTALL) +LTLIBRARIES = $(lib_LTLIBRARIES) +am__DEPENDENCIES_1 = +libevent_la_DEPENDENCIES = @LTLIBOBJS@ $(am__DEPENDENCIES_1) +am__libevent_la_SOURCES_DIST = event.c buffer.c evbuffer.c log.c \ + evutil.c WIN32-Code/win32.c event_tagging.c http.c evhttp.h \ + http-internal.h evdns.c evdns.h evrpc.c evrpc.h \ + evrpc-internal.h strlcpy.c strlcpy-internal.h +@BUILD_WIN32_TRUE@am__objects_1 = win32.lo +am__objects_2 = event.lo buffer.lo evbuffer.lo log.lo evutil.lo \ + $(am__objects_1) +am__objects_3 = event_tagging.lo http.lo evdns.lo evrpc.lo strlcpy.lo +am_libevent_la_OBJECTS = $(am__objects_2) $(am__objects_3) +libevent_la_OBJECTS = $(am_libevent_la_OBJECTS) +libevent_la_LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(libevent_la_LDFLAGS) $(LDFLAGS) -o $@ +libevent_core_la_DEPENDENCIES = @LTLIBOBJS@ $(am__DEPENDENCIES_1) +am__libevent_core_la_SOURCES_DIST = event.c buffer.c evbuffer.c log.c \ + evutil.c WIN32-Code/win32.c +am_libevent_core_la_OBJECTS = $(am__objects_2) +libevent_core_la_OBJECTS = $(am_libevent_core_la_OBJECTS) +libevent_core_la_LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(libevent_core_la_LDFLAGS) $(LDFLAGS) -o $@ +libevent_extra_la_DEPENDENCIES = @LTLIBOBJS@ $(am__DEPENDENCIES_1) +am_libevent_extra_la_OBJECTS = $(am__objects_3) +libevent_extra_la_OBJECTS = $(am_libevent_extra_la_OBJECTS) +libevent_extra_la_LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(libevent_extra_la_LDFLAGS) $(LDFLAGS) -o $@ +binSCRIPT_INSTALL = $(INSTALL_SCRIPT) +SCRIPTS = $(bin_SCRIPTS) +DEFAULT_INCLUDES = -I.@am__isrc@ +depcomp = +am__depfiles_maybe = +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ + --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +CCLD = $(CC) +LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ + --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ + $(LDFLAGS) -o $@ +SOURCES = $(libevent_la_SOURCES) $(libevent_core_la_SOURCES) \ + $(libevent_extra_la_SOURCES) +DIST_SOURCES = $(am__libevent_la_SOURCES_DIST) \ + $(am__libevent_core_la_SOURCES_DIST) \ + $(libevent_extra_la_SOURCES) +RECURSIVE_TARGETS = all-recursive check-recursive dvi-recursive \ + html-recursive info-recursive install-data-recursive \ + install-dvi-recursive install-exec-recursive \ + install-html-recursive install-info-recursive \ + install-pdf-recursive install-ps-recursive install-recursive \ + installcheck-recursive installdirs-recursive pdf-recursive \ + ps-recursive uninstall-recursive +man3dir = $(mandir)/man3 +NROFF = nroff +MANS = $(man_MANS) +includeHEADERS_INSTALL = $(INSTALL_HEADER) +nodist_includeHEADERS_INSTALL = $(INSTALL_HEADER) +HEADERS = $(include_HEADERS) $(nodist_include_HEADERS) +RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ + distclean-recursive maintainer-clean-recursive +ETAGS = etags +CTAGS = ctags +DIST_SUBDIRS = . sample test +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +distdir = $(PACKAGE)-$(VERSION) +top_distdir = $(distdir) +am__remove_distdir = \ + { test ! -d $(distdir) \ + || { find $(distdir) -type d ! -perm -200 -exec chmod u+w {} ';' \ + && rm -fr $(distdir); }; } +DIST_ARCHIVES = $(distdir).tar.gz +GZIP_ENV = --best +distuninstallcheck_listfiles = find . -type f -print +distcleancheck_listfiles = find . -type f -print +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DSYMUTIL = @DSYMUTIL@ +ECHO = @ECHO@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +F77 = @F77@ +FFLAGS = @FFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIBTOOL_DEPS = @LIBTOOL_DEPS@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +NMEDIT = @NMEDIT@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_F77 = @ac_ct_F77@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AUTOMAKE_OPTIONS = foreign no-dependencies + +# This is the point release for libevent. It shouldn't include any +# a/b/c/d/e notations. +RELEASE = 1.4 + +# This is the version info for the libevent binary API. It has three +# numbers: +# Current -- the number of the binary API that we're implementing +# Revision -- which iteration of the implementation of the binary +# API are we supplying? +# Age -- How many previous binary API versions do we also +# support? +# +# If we release a new version that does not change the binary API, +# increment Revision. +# +# If we release a new version that changes the binary API, but does +# not break programs compiled against the old binary API, increment +# Current and Age. Set Revision to 0, since this is the first +# implementation of the new API. +# +# Otherwise, we're changing the binary API and breaking bakward +# compatibility with old binaries. Increment Current. Set Age to 0, +# since we're backward compatible with no previous APIs. Set Revision +# to 0 too. + +# History: +# Libevent 1.4.1 was 2:0:0 +# Libevent 1.4.2 should be 3:0:0 +# Libevent 1.4.5 is 3:0:1 (we forgot to increment in the past) +VERSION_INFO = 3:3:1 +bin_SCRIPTS = event_rpcgen.py +EXTRA_DIST = autogen.sh event.h event-internal.h log.h evsignal.h evdns.3 \ + evrpc.h evrpc-internal.h min_heap.h \ + event.3 \ + Doxyfile \ + kqueue.c epoll_sub.c epoll.c select.c poll.c signal.c \ + evport.c devpoll.c event_rpcgen.py \ + sample/Makefile.am sample/Makefile.in sample/event-test.c \ + sample/signal-test.c sample/time-test.c \ + test/Makefile.am test/Makefile.in test/bench.c test/regress.c \ + test/test-eof.c test/test-weof.c test/test-time.c \ + test/test-init.c test/test.sh \ + compat/sys/queue.h compat/sys/_libevent_time.h \ + WIN32-Code/config.h \ + WIN32-Code/event-config.h \ + WIN32-Code/win32.c \ + WIN32-Code/tree.h \ + WIN32-Prj/event_test/event_test.dsp \ + WIN32-Prj/event_test/test.txt WIN32-Prj/libevent.dsp \ + WIN32-Prj/libevent.dsw WIN32-Prj/signal_test/signal_test.dsp \ + WIN32-Prj/time_test/time_test.dsp WIN32-Prj/regress/regress.vcproj \ + WIN32-Prj/libevent.sln WIN32-Prj/libevent.vcproj + +lib_LTLIBRARIES = libevent.la libevent_core.la libevent_extra.la +@BUILD_WIN32_FALSE@SUBDIRS = . sample test +@BUILD_WIN32_TRUE@SUBDIRS = . sample +@BUILD_WIN32_FALSE@SYS_LIBS = +@BUILD_WIN32_TRUE@SYS_LIBS = -lws2_32 +@BUILD_WIN32_FALSE@SYS_SRC = +@BUILD_WIN32_TRUE@SYS_SRC = WIN32-Code/win32.c +@BUILD_WIN32_FALSE@SYS_INCLUDES = +@BUILD_WIN32_TRUE@SYS_INCLUDES = -IWIN32-Code +BUILT_SOURCES = event-config.h +CORE_SRC = event.c buffer.c evbuffer.c log.c evutil.c $(SYS_SRC) +EXTRA_SRC = event_tagging.c http.c evhttp.h http-internal.h evdns.c \ + evdns.h evrpc.c evrpc.h evrpc-internal.h \ + strlcpy.c strlcpy-internal.h strlcpy-internal.h + +libevent_la_SOURCES = $(CORE_SRC) $(EXTRA_SRC) +libevent_la_LIBADD = @LTLIBOBJS@ $(SYS_LIBS) +libevent_la_LDFLAGS = -release $(RELEASE) -version-info $(VERSION_INFO) +libevent_core_la_SOURCES = $(CORE_SRC) +libevent_core_la_LIBADD = @LTLIBOBJS@ $(SYS_LIBS) +libevent_core_la_LDFLAGS = -release $(RELEASE) -version-info $(VERSION_INFO) +libevent_extra_la_SOURCES = $(EXTRA_SRC) +libevent_extra_la_LIBADD = @LTLIBOBJS@ $(SYS_LIBS) +libevent_extra_la_LDFLAGS = -release $(RELEASE) -version-info $(VERSION_INFO) +include_HEADERS = event.h evhttp.h evdns.h evrpc.h evutil.h +nodist_include_HEADERS = event-config.h +INCLUDES = -I$(srcdir)/compat $(SYS_INCLUDES) +man_MANS = event.3 evdns.3 +DISTCLEANFILES = *~ event-config.h +all: $(BUILT_SOURCES) config.h + $(MAKE) $(AM_MAKEFLAGS) all-recursive + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +am--refresh: + @: +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + echo ' cd $(srcdir) && $(AUTOMAKE) --foreign '; \ + cd $(srcdir) && $(AUTOMAKE) --foreign \ + && exit 0; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign Makefile'; \ + cd $(top_srcdir) && \ + $(AUTOMAKE) --foreign Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + echo ' $(SHELL) ./config.status'; \ + $(SHELL) ./config.status;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + $(SHELL) ./config.status --recheck + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(srcdir) && $(AUTOCONF) +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(srcdir) && $(ACLOCAL) $(ACLOCAL_AMFLAGS) + +config.h: stamp-h1 + @if test ! -f $@; then \ + rm -f stamp-h1; \ + $(MAKE) $(AM_MAKEFLAGS) stamp-h1; \ + else :; fi + +stamp-h1: $(srcdir)/config.h.in $(top_builddir)/config.status + @rm -f stamp-h1 + cd $(top_builddir) && $(SHELL) ./config.status config.h +$(srcdir)/config.h.in: $(am__configure_deps) + cd $(top_srcdir) && $(AUTOHEADER) + rm -f stamp-h1 + touch $@ + +distclean-hdr: + -rm -f config.h stamp-h1 +install-libLTLIBRARIES: $(lib_LTLIBRARIES) + @$(NORMAL_INSTALL) + test -z "$(libdir)" || $(MKDIR_P) "$(DESTDIR)$(libdir)" + @list='$(lib_LTLIBRARIES)'; for p in $$list; do \ + if test -f $$p; then \ + f=$(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(libLTLIBRARIES_INSTALL) $(INSTALL_STRIP_FLAG) '$$p' '$(DESTDIR)$(libdir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(libLTLIBRARIES_INSTALL) $(INSTALL_STRIP_FLAG) "$$p" "$(DESTDIR)$(libdir)/$$f"; \ + else :; fi; \ + done + +uninstall-libLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(lib_LTLIBRARIES)'; for p in $$list; do \ + p=$(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$p'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$p"; \ + done + +clean-libLTLIBRARIES: + -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES) + @list='$(lib_LTLIBRARIES)'; for p in $$list; do \ + dir="`echo $$p | sed -e 's|/[^/]*$$||'`"; \ + test "$$dir" != "$$p" || dir=.; \ + echo "rm -f \"$${dir}/so_locations\""; \ + rm -f "$${dir}/so_locations"; \ + done +libevent.la: $(libevent_la_OBJECTS) $(libevent_la_DEPENDENCIES) + $(libevent_la_LINK) -rpath $(libdir) $(libevent_la_OBJECTS) $(libevent_la_LIBADD) $(LIBS) +libevent_core.la: $(libevent_core_la_OBJECTS) $(libevent_core_la_DEPENDENCIES) + $(libevent_core_la_LINK) -rpath $(libdir) $(libevent_core_la_OBJECTS) $(libevent_core_la_LIBADD) $(LIBS) +libevent_extra.la: $(libevent_extra_la_OBJECTS) $(libevent_extra_la_DEPENDENCIES) + $(libevent_extra_la_LINK) -rpath $(libdir) $(libevent_extra_la_OBJECTS) $(libevent_extra_la_LIBADD) $(LIBS) +install-binSCRIPTS: $(bin_SCRIPTS) + @$(NORMAL_INSTALL) + test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)" + @list='$(bin_SCRIPTS)'; for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + if test -f $$d$$p; then \ + f=`echo "$$p" | sed 's|^.*/||;$(transform)'`; \ + echo " $(binSCRIPT_INSTALL) '$$d$$p' '$(DESTDIR)$(bindir)/$$f'"; \ + $(binSCRIPT_INSTALL) "$$d$$p" "$(DESTDIR)$(bindir)/$$f"; \ + else :; fi; \ + done + +uninstall-binSCRIPTS: + @$(NORMAL_UNINSTALL) + @list='$(bin_SCRIPTS)'; for p in $$list; do \ + f=`echo "$$p" | sed 's|^.*/||;$(transform)'`; \ + echo " rm -f '$(DESTDIR)$(bindir)/$$f'"; \ + rm -f "$(DESTDIR)$(bindir)/$$f"; \ + done + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +.c.o: + $(COMPILE) -c $< + +.c.obj: + $(COMPILE) -c `$(CYGPATH_W) '$<'` + +.c.lo: + $(LTCOMPILE) -c -o $@ $< + +win32.lo: WIN32-Code/win32.c + $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o win32.lo `test -f 'WIN32-Code/win32.c' || echo '$(srcdir)/'`WIN32-Code/win32.c + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +distclean-libtool: + -rm -f libtool +install-man3: $(man3_MANS) $(man_MANS) + @$(NORMAL_INSTALL) + test -z "$(man3dir)" || $(MKDIR_P) "$(DESTDIR)$(man3dir)" + @list='$(man3_MANS) $(dist_man3_MANS) $(nodist_man3_MANS)'; \ + l2='$(man_MANS) $(dist_man_MANS) $(nodist_man_MANS)'; \ + for i in $$l2; do \ + case "$$i" in \ + *.3*) list="$$list $$i" ;; \ + esac; \ + done; \ + for i in $$list; do \ + if test -f $(srcdir)/$$i; then file=$(srcdir)/$$i; \ + else file=$$i; fi; \ + ext=`echo $$i | sed -e 's/^.*\\.//'`; \ + case "$$ext" in \ + 3*) ;; \ + *) ext='3' ;; \ + esac; \ + inst=`echo $$i | sed -e 's/\\.[0-9a-z]*$$//'`; \ + inst=`echo $$inst | sed -e 's/^.*\///'`; \ + inst=`echo $$inst | sed '$(transform)'`.$$ext; \ + echo " $(INSTALL_DATA) '$$file' '$(DESTDIR)$(man3dir)/$$inst'"; \ + $(INSTALL_DATA) "$$file" "$(DESTDIR)$(man3dir)/$$inst"; \ + done +uninstall-man3: + @$(NORMAL_UNINSTALL) + @list='$(man3_MANS) $(dist_man3_MANS) $(nodist_man3_MANS)'; \ + l2='$(man_MANS) $(dist_man_MANS) $(nodist_man_MANS)'; \ + for i in $$l2; do \ + case "$$i" in \ + *.3*) list="$$list $$i" ;; \ + esac; \ + done; \ + for i in $$list; do \ + ext=`echo $$i | sed -e 's/^.*\\.//'`; \ + case "$$ext" in \ + 3*) ;; \ + *) ext='3' ;; \ + esac; \ + inst=`echo $$i | sed -e 's/\\.[0-9a-z]*$$//'`; \ + inst=`echo $$inst | sed -e 's/^.*\///'`; \ + inst=`echo $$inst | sed '$(transform)'`.$$ext; \ + echo " rm -f '$(DESTDIR)$(man3dir)/$$inst'"; \ + rm -f "$(DESTDIR)$(man3dir)/$$inst"; \ + done +install-includeHEADERS: $(include_HEADERS) + @$(NORMAL_INSTALL) + test -z "$(includedir)" || $(MKDIR_P) "$(DESTDIR)$(includedir)" + @list='$(include_HEADERS)'; for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + f=$(am__strip_dir) \ + echo " $(includeHEADERS_INSTALL) '$$d$$p' '$(DESTDIR)$(includedir)/$$f'"; \ + $(includeHEADERS_INSTALL) "$$d$$p" "$(DESTDIR)$(includedir)/$$f"; \ + done + +uninstall-includeHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(include_HEADERS)'; for p in $$list; do \ + f=$(am__strip_dir) \ + echo " rm -f '$(DESTDIR)$(includedir)/$$f'"; \ + rm -f "$(DESTDIR)$(includedir)/$$f"; \ + done +install-nodist_includeHEADERS: $(nodist_include_HEADERS) + @$(NORMAL_INSTALL) + test -z "$(includedir)" || $(MKDIR_P) "$(DESTDIR)$(includedir)" + @list='$(nodist_include_HEADERS)'; for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + f=$(am__strip_dir) \ + echo " $(nodist_includeHEADERS_INSTALL) '$$d$$p' '$(DESTDIR)$(includedir)/$$f'"; \ + $(nodist_includeHEADERS_INSTALL) "$$d$$p" "$(DESTDIR)$(includedir)/$$f"; \ + done + +uninstall-nodist_includeHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(nodist_include_HEADERS)'; for p in $$list; do \ + f=$(am__strip_dir) \ + echo " rm -f '$(DESTDIR)$(includedir)/$$f'"; \ + rm -f "$(DESTDIR)$(includedir)/$$f"; \ + done + +# This directory's subdirectories are mostly independent; you can cd +# into them and run `make' without going through this Makefile. +# To change the values of `make' variables: instead of editing Makefiles, +# (1) if the variable is set in `config.status', edit `config.status' +# (which will cause the Makefiles to be regenerated when you run `make'); +# (2) otherwise, pass the desired values on the `make' command line. +$(RECURSIVE_TARGETS): + @failcom='exit 1'; \ + for f in x $$MAKEFLAGS; do \ + case $$f in \ + *=* | --[!k]*);; \ + *k*) failcom='fail=yes';; \ + esac; \ + done; \ + dot_seen=no; \ + target=`echo $@ | sed s/-recursive//`; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + dot_seen=yes; \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done; \ + if test "$$dot_seen" = "no"; then \ + $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ + fi; test -z "$$fail" + +$(RECURSIVE_CLEAN_TARGETS): + @failcom='exit 1'; \ + for f in x $$MAKEFLAGS; do \ + case $$f in \ + *=* | --[!k]*);; \ + *k*) failcom='fail=yes';; \ + esac; \ + done; \ + dot_seen=no; \ + case "$@" in \ + distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ + *) list='$(SUBDIRS)' ;; \ + esac; \ + rev=''; for subdir in $$list; do \ + if test "$$subdir" = "."; then :; else \ + rev="$$subdir $$rev"; \ + fi; \ + done; \ + rev="$$rev ."; \ + target=`echo $@ | sed s/-recursive//`; \ + for subdir in $$rev; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done && test -z "$$fail" +tags-recursive: + list='$(SUBDIRS)'; for subdir in $$list; do \ + test "$$subdir" = . || (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) tags); \ + done +ctags-recursive: + list='$(SUBDIRS)'; for subdir in $$list; do \ + test "$$subdir" = . || (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) ctags); \ + done + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonemtpy = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: tags-recursive $(HEADERS) $(SOURCES) config.h.in $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ + include_option=--etags-include; \ + empty_fix=.; \ + else \ + include_option=--include; \ + empty_fix=; \ + fi; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test ! -f $$subdir/TAGS || \ + tags="$$tags $$include_option=$$here/$$subdir/TAGS"; \ + fi; \ + done; \ + list='$(SOURCES) $(HEADERS) config.h.in $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$tags $$unique; \ + fi +ctags: CTAGS +CTAGS: ctags-recursive $(HEADERS) $(SOURCES) config.h.in $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + list='$(SOURCES) $(HEADERS) config.h.in $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$tags$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$tags $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && cd $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) $$here + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + $(am__remove_distdir) + test -d $(distdir) || mkdir $(distdir) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \ + fi; \ + cp -pR $$d/$$file $(distdir)$$dir || exit 1; \ + else \ + test -f $(distdir)/$$file \ + || cp -p $$d/$$file $(distdir)/$$file \ + || exit 1; \ + fi; \ + done + list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ + distdir=`$(am__cd) $(distdir) && pwd`; \ + top_distdir=`$(am__cd) $(top_distdir) && pwd`; \ + (cd $$subdir && \ + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$$top_distdir" \ + distdir="$$distdir/$$subdir" \ + am__remove_distdir=: \ + am__skip_length_check=: \ + distdir) \ + || exit 1; \ + fi; \ + done + -find $(distdir) -type d ! -perm -777 -exec chmod a+rwx {} \; -o \ + ! -type d ! -perm -444 -links 1 -exec chmod a+r {} \; -o \ + ! -type d ! -perm -400 -exec chmod a+r {} \; -o \ + ! -type d ! -perm -444 -exec $(install_sh) -c -m a+r {} {} \; \ + || chmod -R a+r $(distdir) +dist-gzip: distdir + tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz + $(am__remove_distdir) + +dist-bzip2: distdir + tardir=$(distdir) && $(am__tar) | bzip2 -9 -c >$(distdir).tar.bz2 + $(am__remove_distdir) + +dist-lzma: distdir + tardir=$(distdir) && $(am__tar) | lzma -9 -c >$(distdir).tar.lzma + $(am__remove_distdir) + +dist-tarZ: distdir + tardir=$(distdir) && $(am__tar) | compress -c >$(distdir).tar.Z + $(am__remove_distdir) + +dist-shar: distdir + shar $(distdir) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).shar.gz + $(am__remove_distdir) + +dist-zip: distdir + -rm -f $(distdir).zip + zip -rq $(distdir).zip $(distdir) + $(am__remove_distdir) + +dist dist-all: distdir + tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz + $(am__remove_distdir) + +# This target untars the dist file and tries a VPATH configuration. Then +# it guarantees that the distribution is self-contained by making another +# tarfile. +distcheck: dist + case '$(DIST_ARCHIVES)' in \ + *.tar.gz*) \ + GZIP=$(GZIP_ENV) gunzip -c $(distdir).tar.gz | $(am__untar) ;;\ + *.tar.bz2*) \ + bunzip2 -c $(distdir).tar.bz2 | $(am__untar) ;;\ + *.tar.lzma*) \ + unlzma -c $(distdir).tar.lzma | $(am__untar) ;;\ + *.tar.Z*) \ + uncompress -c $(distdir).tar.Z | $(am__untar) ;;\ + *.shar.gz*) \ + GZIP=$(GZIP_ENV) gunzip -c $(distdir).shar.gz | unshar ;;\ + *.zip*) \ + unzip $(distdir).zip ;;\ + esac + chmod -R a-w $(distdir); chmod a+w $(distdir) + mkdir $(distdir)/_build + mkdir $(distdir)/_inst + chmod a-w $(distdir) + dc_install_base=`$(am__cd) $(distdir)/_inst && pwd | sed -e 's,^[^:\\/]:[\\/],/,'` \ + && dc_destdir="$${TMPDIR-/tmp}/am-dc-$$$$/" \ + && cd $(distdir)/_build \ + && ../configure --srcdir=.. --prefix="$$dc_install_base" \ + $(DISTCHECK_CONFIGURE_FLAGS) \ + && $(MAKE) $(AM_MAKEFLAGS) \ + && $(MAKE) $(AM_MAKEFLAGS) dvi \ + && $(MAKE) $(AM_MAKEFLAGS) check \ + && $(MAKE) $(AM_MAKEFLAGS) install \ + && $(MAKE) $(AM_MAKEFLAGS) installcheck \ + && $(MAKE) $(AM_MAKEFLAGS) uninstall \ + && $(MAKE) $(AM_MAKEFLAGS) distuninstallcheck_dir="$$dc_install_base" \ + distuninstallcheck \ + && chmod -R a-w "$$dc_install_base" \ + && ({ \ + (cd ../.. && umask 077 && mkdir "$$dc_destdir") \ + && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" install \ + && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" uninstall \ + && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" \ + distuninstallcheck_dir="$$dc_destdir" distuninstallcheck; \ + } || { rm -rf "$$dc_destdir"; exit 1; }) \ + && rm -rf "$$dc_destdir" \ + && $(MAKE) $(AM_MAKEFLAGS) dist \ + && rm -rf $(DIST_ARCHIVES) \ + && $(MAKE) $(AM_MAKEFLAGS) distcleancheck + $(am__remove_distdir) + @(echo "$(distdir) archives ready for distribution: "; \ + list='$(DIST_ARCHIVES)'; for i in $$list; do echo $$i; done) | \ + sed -e 1h -e 1s/./=/g -e 1p -e 1x -e '$$p' -e '$$x' +distuninstallcheck: + @cd $(distuninstallcheck_dir) \ + && test `$(distuninstallcheck_listfiles) | wc -l` -le 1 \ + || { echo "ERROR: files left after uninstall:" ; \ + if test -n "$(DESTDIR)"; then \ + echo " (check DESTDIR support)"; \ + fi ; \ + $(distuninstallcheck_listfiles) ; \ + exit 1; } >&2 +distcleancheck: distclean + @if test '$(srcdir)' = . ; then \ + echo "ERROR: distcleancheck can only run from a VPATH build" ; \ + exit 1 ; \ + fi + @test `$(distcleancheck_listfiles) | wc -l` -eq 0 \ + || { echo "ERROR: files left in build directory after distclean:" ; \ + $(distcleancheck_listfiles) ; \ + exit 1; } >&2 +check-am: all-am +check: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) check-recursive +all-am: Makefile $(LTLIBRARIES) $(SCRIPTS) $(MANS) $(HEADERS) config.h +installdirs: installdirs-recursive +installdirs-am: + for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(bindir)" "$(DESTDIR)$(man3dir)" "$(DESTDIR)$(includedir)" "$(DESTDIR)$(includedir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." + -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES) +clean: clean-recursive + +clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \ + mostlyclean-am + +distclean: distclean-recursive + -rm -f $(am__CONFIG_DISTCLEAN_FILES) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-hdr distclean-libtool distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +info: info-recursive + +info-am: + +install-data-am: install-includeHEADERS install-man \ + install-nodist_includeHEADERS + +install-dvi: install-dvi-recursive + +install-exec-am: install-binSCRIPTS install-libLTLIBRARIES + +install-html: install-html-recursive + +install-info: install-info-recursive + +install-man: install-man3 + +install-pdf: install-pdf-recursive + +install-ps: install-ps-recursive + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -f $(am__CONFIG_DISTCLEAN_FILES) + -rm -rf $(top_srcdir)/autom4te.cache + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +uninstall-am: uninstall-binSCRIPTS uninstall-includeHEADERS \ + uninstall-libLTLIBRARIES uninstall-man \ + uninstall-nodist_includeHEADERS + +uninstall-man: uninstall-man3 + +.MAKE: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) install-am \ + install-strip + +.PHONY: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) CTAGS GTAGS \ + all all-am am--refresh check check-am clean clean-generic \ + clean-libLTLIBRARIES clean-libtool ctags ctags-recursive dist \ + dist-all dist-bzip2 dist-gzip dist-lzma dist-shar dist-tarZ \ + dist-zip distcheck distclean distclean-compile \ + distclean-generic distclean-hdr distclean-libtool \ + distclean-tags distcleancheck distdir distuninstallcheck dvi \ + dvi-am html html-am info info-am install install-am \ + install-binSCRIPTS install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-includeHEADERS install-info \ + install-info-am install-libLTLIBRARIES install-man \ + install-man3 install-nodist_includeHEADERS install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs installdirs-am \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ + pdf pdf-am ps ps-am tags tags-recursive uninstall uninstall-am \ + uninstall-binSCRIPTS uninstall-includeHEADERS \ + uninstall-libLTLIBRARIES uninstall-man uninstall-man3 \ + uninstall-nodist_includeHEADERS + + +event-config.h: config.h + echo '/* event-config.h' > $@ + echo ' * Generated by autoconf; post-processed by libevent.' >> $@ + echo ' * Do not edit this file.' >> $@ + echo ' * Do not rely on macros in this file existing in later versions.'>> $@ + echo ' */' >> $@ + echo '#ifndef _EVENT_CONFIG_H_' >> $@ + echo '#define _EVENT_CONFIG_H_' >> $@ + + sed -e 's/#define /#define _EVENT_/' \ + -e 's/#undef /#undef _EVENT_/' \ + -e 's/#ifndef /#ifndef _EVENT_/' < config.h >> $@ + echo "#endif" >> $@ + +verify: libevent.la + cd test && make verify + +doxygen: FORCE + doxygen $(srcdir)/Doxyfile +FORCE: +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/third_party/libevent/README b/third_party/libevent/README new file mode 100644 index 0000000000..b0650392ed --- /dev/null +++ b/third_party/libevent/README @@ -0,0 +1,57 @@ +To build libevent, type + +$ ./configure && make + + (If you got libevent from the subversion repository, you will + first need to run the included "autogen.sh" script in order to + generate the configure script.) + +Install as root via + +# make install + +You can run the regression tests by + +$ make verify + +Before, reporting any problems, please run the regression tests. + +To enable the low-level tracing build the library as: + +CFLAGS=-DUSE_DEBUG ./configure [...] + +Acknowledgements: +----------------- + +The following people have helped with suggestions, ideas, code or +fixing bugs: + + Alejo + Weston Andros Adamson + William Ahern + Stas Bekman + Andrew Danforth + Mike Davis + Shie Erlich + Alexander von Gernler + Artur Grabowski + Aaron Hopkins + Claudio Jeker + Scott Lamb + Adam Langley + Philip Lewis + David Libenzi + Nick Mathewson + Andrey Matveev + Richard Nyberg + Jon Oberheide + Phil Oleson + Dave Pacheco + Tassilo von Parseval + Pierre Phaneuf + Jon Poland + Bert JW Regeer + Dug Song + Taral + +If I have forgotten your name, please contact me. diff --git a/third_party/libevent/README.chromium b/third_party/libevent/README.chromium new file mode 100644 index 0000000000..99695661ec --- /dev/null +++ b/third_party/libevent/README.chromium @@ -0,0 +1,20 @@ +Name: libevent +URL: http://www.monkey.org/~provos/libevent/ +Version: 1.4.13 +License: BSD +Security Critical: yes + +Local Modifications: +Rather than use libevent's own build system, we just build a Chrome +static library using GYP. + +1) Run configure and "make event-config.h" on Linux, FreeBSD, Solaris, + and Mac and copy config.h and event-config.h to linux/, freebsd/, + solaris/, and mac/ respectively. +2) Add libevent.gyp. +3) chromium.patch is applied to allow libevent to be used without + being installed. +4) The directories WIN32-Code and WIN32-Prj are not included. +5) Apply r87338. +6) The configs for android were copied from Linux's which were very close to + android one with the exception of HAVE_FD_MASK and HAVE_STRLCPY. diff --git a/third_party/libevent/aclocal.m4 b/third_party/libevent/aclocal.m4 new file mode 100644 index 0000000000..4af9376529 --- /dev/null +++ b/third_party/libevent/aclocal.m4 @@ -0,0 +1,7498 @@ +# generated automatically by aclocal 1.10.1 -*- Autoconf -*- + +# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, +# 2005, 2006, 2007, 2008 Free Software Foundation, Inc. +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +m4_ifndef([AC_AUTOCONF_VERSION], + [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl +m4_if(AC_AUTOCONF_VERSION, [2.63],, +[m4_warning([this file was generated for autoconf 2.63. +You have another version of autoconf. It may work, but is not guaranteed to. +If you have problems, you may need to regenerate the build system entirely. +To do so, use the procedure documented by the package, typically `autoreconf'.])]) + +# libtool.m4 - Configure libtool for the host system. -*-Autoconf-*- + +# serial 52 AC_PROG_LIBTOOL + + +# AC_PROVIDE_IFELSE(MACRO-NAME, IF-PROVIDED, IF-NOT-PROVIDED) +# ----------------------------------------------------------- +# If this macro is not defined by Autoconf, define it here. +m4_ifdef([AC_PROVIDE_IFELSE], + [], + [m4_define([AC_PROVIDE_IFELSE], + [m4_ifdef([AC_PROVIDE_$1], + [$2], [$3])])]) + + +# AC_PROG_LIBTOOL +# --------------- +AC_DEFUN([AC_PROG_LIBTOOL], +[AC_REQUIRE([_AC_PROG_LIBTOOL])dnl +dnl If AC_PROG_CXX has already been expanded, run AC_LIBTOOL_CXX +dnl immediately, otherwise, hook it in at the end of AC_PROG_CXX. + AC_PROVIDE_IFELSE([AC_PROG_CXX], + [AC_LIBTOOL_CXX], + [define([AC_PROG_CXX], defn([AC_PROG_CXX])[AC_LIBTOOL_CXX + ])]) +dnl And a similar setup for Fortran 77 support + AC_PROVIDE_IFELSE([AC_PROG_F77], + [AC_LIBTOOL_F77], + [define([AC_PROG_F77], defn([AC_PROG_F77])[AC_LIBTOOL_F77 +])]) + +dnl Quote A][M_PROG_GCJ so that aclocal doesn't bring it in needlessly. +dnl If either AC_PROG_GCJ or A][M_PROG_GCJ have already been expanded, run +dnl AC_LIBTOOL_GCJ immediately, otherwise, hook it in at the end of both. + AC_PROVIDE_IFELSE([AC_PROG_GCJ], + [AC_LIBTOOL_GCJ], + [AC_PROVIDE_IFELSE([A][M_PROG_GCJ], + [AC_LIBTOOL_GCJ], + [AC_PROVIDE_IFELSE([LT_AC_PROG_GCJ], + [AC_LIBTOOL_GCJ], + [ifdef([AC_PROG_GCJ], + [define([AC_PROG_GCJ], defn([AC_PROG_GCJ])[AC_LIBTOOL_GCJ])]) + ifdef([A][M_PROG_GCJ], + [define([A][M_PROG_GCJ], defn([A][M_PROG_GCJ])[AC_LIBTOOL_GCJ])]) + ifdef([LT_AC_PROG_GCJ], + [define([LT_AC_PROG_GCJ], + defn([LT_AC_PROG_GCJ])[AC_LIBTOOL_GCJ])])])]) +])])# AC_PROG_LIBTOOL + + +# _AC_PROG_LIBTOOL +# ---------------- +AC_DEFUN([_AC_PROG_LIBTOOL], +[AC_REQUIRE([AC_LIBTOOL_SETUP])dnl +AC_BEFORE([$0],[AC_LIBTOOL_CXX])dnl +AC_BEFORE([$0],[AC_LIBTOOL_F77])dnl +AC_BEFORE([$0],[AC_LIBTOOL_GCJ])dnl + +# This can be used to rebuild libtool when needed +LIBTOOL_DEPS="$ac_aux_dir/ltmain.sh" + +# Always use our own libtool. +LIBTOOL='$(SHELL) $(top_builddir)/libtool' +AC_SUBST(LIBTOOL)dnl + +# Prevent multiple expansion +define([AC_PROG_LIBTOOL], []) +])# _AC_PROG_LIBTOOL + + +# AC_LIBTOOL_SETUP +# ---------------- +AC_DEFUN([AC_LIBTOOL_SETUP], +[AC_PREREQ(2.50)dnl +AC_REQUIRE([AC_ENABLE_SHARED])dnl +AC_REQUIRE([AC_ENABLE_STATIC])dnl +AC_REQUIRE([AC_ENABLE_FAST_INSTALL])dnl +AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([AC_CANONICAL_BUILD])dnl +AC_REQUIRE([AC_PROG_CC])dnl +AC_REQUIRE([AC_PROG_LD])dnl +AC_REQUIRE([AC_PROG_LD_RELOAD_FLAG])dnl +AC_REQUIRE([AC_PROG_NM])dnl + +AC_REQUIRE([AC_PROG_LN_S])dnl +AC_REQUIRE([AC_DEPLIBS_CHECK_METHOD])dnl +# Autoconf 2.13's AC_OBJEXT and AC_EXEEXT macros only works for C compilers! +AC_REQUIRE([AC_OBJEXT])dnl +AC_REQUIRE([AC_EXEEXT])dnl +dnl +AC_LIBTOOL_SYS_MAX_CMD_LEN +AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE +AC_LIBTOOL_OBJDIR + +AC_REQUIRE([_LT_AC_SYS_COMPILER])dnl +_LT_AC_PROG_ECHO_BACKSLASH + +case $host_os in +aix3*) + # AIX sometimes has problems with the GCC collect2 program. For some + # reason, if we set the COLLECT_NAMES environment variable, the problems + # vanish in a puff of smoke. + if test "X${COLLECT_NAMES+set}" != Xset; then + COLLECT_NAMES= + export COLLECT_NAMES + fi + ;; +esac + +# Sed substitution that helps us do robust quoting. It backslashifies +# metacharacters that are still active within double-quoted strings. +Xsed='sed -e 1s/^X//' +[sed_quote_subst='s/\([\\"\\`$\\\\]\)/\\\1/g'] + +# Same as above, but do not quote variable references. +[double_quote_subst='s/\([\\"\\`\\\\]\)/\\\1/g'] + +# Sed substitution to delay expansion of an escaped shell variable in a +# double_quote_subst'ed string. +delay_variable_subst='s/\\\\\\\\\\\$/\\\\\\$/g' + +# Sed substitution to avoid accidental globbing in evaled expressions +no_glob_subst='s/\*/\\\*/g' + +# Constants: +rm="rm -f" + +# Global variables: +default_ofile=libtool +can_build_shared=yes + +# All known linkers require a `.a' archive for static linking (except MSVC, +# which needs '.lib'). +libext=a +ltmain="$ac_aux_dir/ltmain.sh" +ofile="$default_ofile" +with_gnu_ld="$lt_cv_prog_gnu_ld" + +AC_CHECK_TOOL(AR, ar, false) +AC_CHECK_TOOL(RANLIB, ranlib, :) +AC_CHECK_TOOL(STRIP, strip, :) + +old_CC="$CC" +old_CFLAGS="$CFLAGS" + +# Set sane defaults for various variables +test -z "$AR" && AR=ar +test -z "$AR_FLAGS" && AR_FLAGS=cru +test -z "$AS" && AS=as +test -z "$CC" && CC=cc +test -z "$LTCC" && LTCC=$CC +test -z "$LTCFLAGS" && LTCFLAGS=$CFLAGS +test -z "$DLLTOOL" && DLLTOOL=dlltool +test -z "$LD" && LD=ld +test -z "$LN_S" && LN_S="ln -s" +test -z "$MAGIC_CMD" && MAGIC_CMD=file +test -z "$NM" && NM=nm +test -z "$SED" && SED=sed +test -z "$OBJDUMP" && OBJDUMP=objdump +test -z "$RANLIB" && RANLIB=: +test -z "$STRIP" && STRIP=: +test -z "$ac_objext" && ac_objext=o + +# Determine commands to create old-style static archives. +old_archive_cmds='$AR $AR_FLAGS $oldlib$oldobjs' +old_postinstall_cmds='chmod 644 $oldlib' +old_postuninstall_cmds= + +if test -n "$RANLIB"; then + case $host_os in + openbsd*) + old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB -t \$oldlib" + ;; + *) + old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB \$oldlib" + ;; + esac + old_archive_cmds="$old_archive_cmds~\$RANLIB \$oldlib" +fi + +_LT_CC_BASENAME([$compiler]) + +# Only perform the check for file, if the check method requires it +case $deplibs_check_method in +file_magic*) + if test "$file_magic_cmd" = '$MAGIC_CMD'; then + AC_PATH_MAGIC + fi + ;; +esac + +_LT_REQUIRED_DARWIN_CHECKS + +AC_PROVIDE_IFELSE([AC_LIBTOOL_DLOPEN], enable_dlopen=yes, enable_dlopen=no) +AC_PROVIDE_IFELSE([AC_LIBTOOL_WIN32_DLL], +enable_win32_dll=yes, enable_win32_dll=no) + +AC_ARG_ENABLE([libtool-lock], + [AC_HELP_STRING([--disable-libtool-lock], + [avoid locking (might break parallel builds)])]) +test "x$enable_libtool_lock" != xno && enable_libtool_lock=yes + +AC_ARG_WITH([pic], + [AC_HELP_STRING([--with-pic], + [try to use only PIC/non-PIC objects @<:@default=use both@:>@])], + [pic_mode="$withval"], + [pic_mode=default]) +test -z "$pic_mode" && pic_mode=default + +# Use C for the default configuration in the libtool script +tagname= +AC_LIBTOOL_LANG_C_CONFIG +_LT_AC_TAGCONFIG +])# AC_LIBTOOL_SETUP + + +# _LT_AC_SYS_COMPILER +# ------------------- +AC_DEFUN([_LT_AC_SYS_COMPILER], +[AC_REQUIRE([AC_PROG_CC])dnl + +# If no C compiler was specified, use CC. +LTCC=${LTCC-"$CC"} + +# If no C compiler flags were specified, use CFLAGS. +LTCFLAGS=${LTCFLAGS-"$CFLAGS"} + +# Allow CC to be a program name with arguments. +compiler=$CC +])# _LT_AC_SYS_COMPILER + + +# _LT_CC_BASENAME(CC) +# ------------------- +# Calculate cc_basename. Skip known compiler wrappers and cross-prefix. +AC_DEFUN([_LT_CC_BASENAME], +[for cc_temp in $1""; do + case $cc_temp in + compile | *[[\\/]]compile | ccache | *[[\\/]]ccache ) ;; + distcc | *[[\\/]]distcc | purify | *[[\\/]]purify ) ;; + \-*) ;; + *) break;; + esac +done +cc_basename=`$echo "X$cc_temp" | $Xsed -e 's%.*/%%' -e "s%^$host_alias-%%"` +]) + + +# _LT_COMPILER_BOILERPLATE +# ------------------------ +# Check for compiler boilerplate output or warnings with +# the simple compiler test code. +AC_DEFUN([_LT_COMPILER_BOILERPLATE], +[AC_REQUIRE([LT_AC_PROG_SED])dnl +ac_outfile=conftest.$ac_objext +echo "$lt_simple_compile_test_code" >conftest.$ac_ext +eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_compiler_boilerplate=`cat conftest.err` +$rm conftest* +])# _LT_COMPILER_BOILERPLATE + + +# _LT_LINKER_BOILERPLATE +# ---------------------- +# Check for linker boilerplate output or warnings with +# the simple link test code. +AC_DEFUN([_LT_LINKER_BOILERPLATE], +[AC_REQUIRE([LT_AC_PROG_SED])dnl +ac_outfile=conftest.$ac_objext +echo "$lt_simple_link_test_code" >conftest.$ac_ext +eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_linker_boilerplate=`cat conftest.err` +$rm -r conftest* +])# _LT_LINKER_BOILERPLATE + +# _LT_REQUIRED_DARWIN_CHECKS +# -------------------------- +# Check for some things on darwin +AC_DEFUN([_LT_REQUIRED_DARWIN_CHECKS],[ + case $host_os in + rhapsody* | darwin*) + AC_CHECK_TOOL([DSYMUTIL], [dsymutil], [:]) + AC_CHECK_TOOL([NMEDIT], [nmedit], [:]) + + AC_CACHE_CHECK([for -single_module linker flag],[lt_cv_apple_cc_single_mod], + [lt_cv_apple_cc_single_mod=no + if test -z "${LT_MULTI_MODULE}"; then + # By default we will add the -single_module flag. You can override + # by either setting the environment variable LT_MULTI_MODULE + # non-empty at configure time, or by adding -multi_module to the + # link flags. + echo "int foo(void){return 1;}" > conftest.c + $LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ + -dynamiclib ${wl}-single_module conftest.c + if test -f libconftest.dylib; then + lt_cv_apple_cc_single_mod=yes + rm -rf libconftest.dylib* + fi + rm conftest.c + fi]) + AC_CACHE_CHECK([for -exported_symbols_list linker flag], + [lt_cv_ld_exported_symbols_list], + [lt_cv_ld_exported_symbols_list=no + save_LDFLAGS=$LDFLAGS + echo "_main" > conftest.sym + LDFLAGS="$LDFLAGS -Wl,-exported_symbols_list,conftest.sym" + AC_LINK_IFELSE([AC_LANG_PROGRAM([],[])], + [lt_cv_ld_exported_symbols_list=yes], + [lt_cv_ld_exported_symbols_list=no]) + LDFLAGS="$save_LDFLAGS" + ]) + case $host_os in + rhapsody* | darwin1.[[0123]]) + _lt_dar_allow_undefined='${wl}-undefined ${wl}suppress' ;; + darwin1.*) + _lt_dar_allow_undefined='${wl}-flat_namespace ${wl}-undefined ${wl}suppress' ;; + darwin*) + # if running on 10.5 or later, the deployment target defaults + # to the OS version, if on x86, and 10.4, the deployment + # target defaults to 10.4. Don't you love it? + case ${MACOSX_DEPLOYMENT_TARGET-10.0},$host in + 10.0,*86*-darwin8*|10.0,*-darwin[[91]]*) + _lt_dar_allow_undefined='${wl}-undefined ${wl}dynamic_lookup' ;; + 10.[[012]]*) + _lt_dar_allow_undefined='${wl}-flat_namespace ${wl}-undefined ${wl}suppress' ;; + 10.*) + _lt_dar_allow_undefined='${wl}-undefined ${wl}dynamic_lookup' ;; + esac + ;; + esac + if test "$lt_cv_apple_cc_single_mod" = "yes"; then + _lt_dar_single_mod='$single_module' + fi + if test "$lt_cv_ld_exported_symbols_list" = "yes"; then + _lt_dar_export_syms=' ${wl}-exported_symbols_list,$output_objdir/${libname}-symbols.expsym' + else + _lt_dar_export_syms="~$NMEDIT -s \$output_objdir/\${libname}-symbols.expsym \${lib}" + fi + if test "$DSYMUTIL" != ":"; then + _lt_dsymutil="~$DSYMUTIL \$lib || :" + else + _lt_dsymutil= + fi + ;; + esac +]) + +# _LT_AC_SYS_LIBPATH_AIX +# ---------------------- +# Links a minimal program and checks the executable +# for the system default hardcoded library path. In most cases, +# this is /usr/lib:/lib, but when the MPI compilers are used +# the location of the communication and MPI libs are included too. +# If we don't find anything, use the default library path according +# to the aix ld manual. +AC_DEFUN([_LT_AC_SYS_LIBPATH_AIX], +[AC_REQUIRE([LT_AC_PROG_SED])dnl +AC_LINK_IFELSE(AC_LANG_PROGRAM,[ +lt_aix_libpath_sed=' + /Import File Strings/,/^$/ { + /^0/ { + s/^0 *\(.*\)$/\1/ + p + } + }' +aix_libpath=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` +# Check for a 64-bit object if we didn't find anything. +if test -z "$aix_libpath"; then + aix_libpath=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` +fi],[]) +if test -z "$aix_libpath"; then aix_libpath="/usr/lib:/lib"; fi +])# _LT_AC_SYS_LIBPATH_AIX + + +# _LT_AC_SHELL_INIT(ARG) +# ---------------------- +AC_DEFUN([_LT_AC_SHELL_INIT], +[ifdef([AC_DIVERSION_NOTICE], + [AC_DIVERT_PUSH(AC_DIVERSION_NOTICE)], + [AC_DIVERT_PUSH(NOTICE)]) +$1 +AC_DIVERT_POP +])# _LT_AC_SHELL_INIT + + +# _LT_AC_PROG_ECHO_BACKSLASH +# -------------------------- +# Add some code to the start of the generated configure script which +# will find an echo command which doesn't interpret backslashes. +AC_DEFUN([_LT_AC_PROG_ECHO_BACKSLASH], +[_LT_AC_SHELL_INIT([ +# Check that we are running under the correct shell. +SHELL=${CONFIG_SHELL-/bin/sh} + +case X$ECHO in +X*--fallback-echo) + # Remove one level of quotation (which was required for Make). + ECHO=`echo "$ECHO" | sed 's,\\\\\[$]\\[$]0,'[$]0','` + ;; +esac + +echo=${ECHO-echo} +if test "X[$]1" = X--no-reexec; then + # Discard the --no-reexec flag, and continue. + shift +elif test "X[$]1" = X--fallback-echo; then + # Avoid inline document here, it may be left over + : +elif test "X`($echo '\t') 2>/dev/null`" = 'X\t' ; then + # Yippee, $echo works! + : +else + # Restart under the correct shell. + exec $SHELL "[$]0" --no-reexec ${1+"[$]@"} +fi + +if test "X[$]1" = X--fallback-echo; then + # used as fallback echo + shift + cat </dev/null 2>&1 && unset CDPATH + +if test -z "$ECHO"; then +if test "X${echo_test_string+set}" != Xset; then +# find a string as large as possible, as long as the shell can cope with it + for cmd in 'sed 50q "[$]0"' 'sed 20q "[$]0"' 'sed 10q "[$]0"' 'sed 2q "[$]0"' 'echo test'; do + # expected sizes: less than 2Kb, 1Kb, 512 bytes, 16 bytes, ... + if (echo_test_string=`eval $cmd`) 2>/dev/null && + echo_test_string=`eval $cmd` && + (test "X$echo_test_string" = "X$echo_test_string") 2>/dev/null + then + break + fi + done +fi + +if test "X`($echo '\t') 2>/dev/null`" = 'X\t' && + echo_testing_string=`($echo "$echo_test_string") 2>/dev/null` && + test "X$echo_testing_string" = "X$echo_test_string"; then + : +else + # The Solaris, AIX, and Digital Unix default echo programs unquote + # backslashes. This makes it impossible to quote backslashes using + # echo "$something" | sed 's/\\/\\\\/g' + # + # So, first we look for a working echo in the user's PATH. + + lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR + for dir in $PATH /usr/ucb; do + IFS="$lt_save_ifs" + if (test -f $dir/echo || test -f $dir/echo$ac_exeext) && + test "X`($dir/echo '\t') 2>/dev/null`" = 'X\t' && + echo_testing_string=`($dir/echo "$echo_test_string") 2>/dev/null` && + test "X$echo_testing_string" = "X$echo_test_string"; then + echo="$dir/echo" + break + fi + done + IFS="$lt_save_ifs" + + if test "X$echo" = Xecho; then + # We didn't find a better echo, so look for alternatives. + if test "X`(print -r '\t') 2>/dev/null`" = 'X\t' && + echo_testing_string=`(print -r "$echo_test_string") 2>/dev/null` && + test "X$echo_testing_string" = "X$echo_test_string"; then + # This shell has a builtin print -r that does the trick. + echo='print -r' + elif (test -f /bin/ksh || test -f /bin/ksh$ac_exeext) && + test "X$CONFIG_SHELL" != X/bin/ksh; then + # If we have ksh, try running configure again with it. + ORIGINAL_CONFIG_SHELL=${CONFIG_SHELL-/bin/sh} + export ORIGINAL_CONFIG_SHELL + CONFIG_SHELL=/bin/ksh + export CONFIG_SHELL + exec $CONFIG_SHELL "[$]0" --no-reexec ${1+"[$]@"} + else + # Try using printf. + echo='printf %s\n' + if test "X`($echo '\t') 2>/dev/null`" = 'X\t' && + echo_testing_string=`($echo "$echo_test_string") 2>/dev/null` && + test "X$echo_testing_string" = "X$echo_test_string"; then + # Cool, printf works + : + elif echo_testing_string=`($ORIGINAL_CONFIG_SHELL "[$]0" --fallback-echo '\t') 2>/dev/null` && + test "X$echo_testing_string" = 'X\t' && + echo_testing_string=`($ORIGINAL_CONFIG_SHELL "[$]0" --fallback-echo "$echo_test_string") 2>/dev/null` && + test "X$echo_testing_string" = "X$echo_test_string"; then + CONFIG_SHELL=$ORIGINAL_CONFIG_SHELL + export CONFIG_SHELL + SHELL="$CONFIG_SHELL" + export SHELL + echo="$CONFIG_SHELL [$]0 --fallback-echo" + elif echo_testing_string=`($CONFIG_SHELL "[$]0" --fallback-echo '\t') 2>/dev/null` && + test "X$echo_testing_string" = 'X\t' && + echo_testing_string=`($CONFIG_SHELL "[$]0" --fallback-echo "$echo_test_string") 2>/dev/null` && + test "X$echo_testing_string" = "X$echo_test_string"; then + echo="$CONFIG_SHELL [$]0 --fallback-echo" + else + # maybe with a smaller string... + prev=: + + for cmd in 'echo test' 'sed 2q "[$]0"' 'sed 10q "[$]0"' 'sed 20q "[$]0"' 'sed 50q "[$]0"'; do + if (test "X$echo_test_string" = "X`eval $cmd`") 2>/dev/null + then + break + fi + prev="$cmd" + done + + if test "$prev" != 'sed 50q "[$]0"'; then + echo_test_string=`eval $prev` + export echo_test_string + exec ${ORIGINAL_CONFIG_SHELL-${CONFIG_SHELL-/bin/sh}} "[$]0" ${1+"[$]@"} + else + # Oops. We lost completely, so just stick with echo. + echo=echo + fi + fi + fi + fi +fi +fi + +# Copy echo and quote the copy suitably for passing to libtool from +# the Makefile, instead of quoting the original, which is used later. +ECHO=$echo +if test "X$ECHO" = "X$CONFIG_SHELL [$]0 --fallback-echo"; then + ECHO="$CONFIG_SHELL \\\$\[$]0 --fallback-echo" +fi + +AC_SUBST(ECHO) +])])# _LT_AC_PROG_ECHO_BACKSLASH + + +# _LT_AC_LOCK +# ----------- +AC_DEFUN([_LT_AC_LOCK], +[AC_ARG_ENABLE([libtool-lock], + [AC_HELP_STRING([--disable-libtool-lock], + [avoid locking (might break parallel builds)])]) +test "x$enable_libtool_lock" != xno && enable_libtool_lock=yes + +# Some flags need to be propagated to the compiler or linker for good +# libtool support. +case $host in +ia64-*-hpux*) + # Find out which ABI we are using. + echo 'int i;' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + case `/usr/bin/file conftest.$ac_objext` in + *ELF-32*) + HPUX_IA64_MODE="32" + ;; + *ELF-64*) + HPUX_IA64_MODE="64" + ;; + esac + fi + rm -rf conftest* + ;; +*-*-irix6*) + # Find out which ABI we are using. + echo '[#]line __oline__ "configure"' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + if test "$lt_cv_prog_gnu_ld" = yes; then + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + LD="${LD-ld} -melf32bsmip" + ;; + *N32*) + LD="${LD-ld} -melf32bmipn32" + ;; + *64-bit*) + LD="${LD-ld} -melf64bmip" + ;; + esac + else + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + LD="${LD-ld} -32" + ;; + *N32*) + LD="${LD-ld} -n32" + ;; + *64-bit*) + LD="${LD-ld} -64" + ;; + esac + fi + fi + rm -rf conftest* + ;; + +x86_64-*kfreebsd*-gnu|x86_64-*linux*|ppc*-*linux*|powerpc*-*linux*| \ +s390*-*linux*|sparc*-*linux*) + # Find out which ABI we are using. + echo 'int i;' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + case `/usr/bin/file conftest.o` in + *32-bit*) + case $host in + x86_64-*kfreebsd*-gnu) + LD="${LD-ld} -m elf_i386_fbsd" + ;; + x86_64-*linux*) + LD="${LD-ld} -m elf_i386" + ;; + ppc64-*linux*|powerpc64-*linux*) + LD="${LD-ld} -m elf32ppclinux" + ;; + s390x-*linux*) + LD="${LD-ld} -m elf_s390" + ;; + sparc64-*linux*) + LD="${LD-ld} -m elf32_sparc" + ;; + esac + ;; + *64-bit*) + case $host in + x86_64-*kfreebsd*-gnu) + LD="${LD-ld} -m elf_x86_64_fbsd" + ;; + x86_64-*linux*) + LD="${LD-ld} -m elf_x86_64" + ;; + ppc*-*linux*|powerpc*-*linux*) + LD="${LD-ld} -m elf64ppc" + ;; + s390*-*linux*) + LD="${LD-ld} -m elf64_s390" + ;; + sparc*-*linux*) + LD="${LD-ld} -m elf64_sparc" + ;; + esac + ;; + esac + fi + rm -rf conftest* + ;; + +*-*-sco3.2v5*) + # On SCO OpenServer 5, we need -belf to get full-featured binaries. + SAVE_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS -belf" + AC_CACHE_CHECK([whether the C compiler needs -belf], lt_cv_cc_needs_belf, + [AC_LANG_PUSH(C) + AC_TRY_LINK([],[],[lt_cv_cc_needs_belf=yes],[lt_cv_cc_needs_belf=no]) + AC_LANG_POP]) + if test x"$lt_cv_cc_needs_belf" != x"yes"; then + # this is probably gcc 2.8.0, egcs 1.0 or newer; no need for -belf + CFLAGS="$SAVE_CFLAGS" + fi + ;; +sparc*-*solaris*) + # Find out which ABI we are using. + echo 'int i;' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + case `/usr/bin/file conftest.o` in + *64-bit*) + case $lt_cv_prog_gnu_ld in + yes*) LD="${LD-ld} -m elf64_sparc" ;; + *) + if ${LD-ld} -64 -r -o conftest2.o conftest.o >/dev/null 2>&1; then + LD="${LD-ld} -64" + fi + ;; + esac + ;; + esac + fi + rm -rf conftest* + ;; + +AC_PROVIDE_IFELSE([AC_LIBTOOL_WIN32_DLL], +[*-*-cygwin* | *-*-mingw* | *-*-pw32*) + AC_CHECK_TOOL(DLLTOOL, dlltool, false) + AC_CHECK_TOOL(AS, as, false) + AC_CHECK_TOOL(OBJDUMP, objdump, false) + ;; + ]) +esac + +need_locks="$enable_libtool_lock" + +])# _LT_AC_LOCK + + +# AC_LIBTOOL_COMPILER_OPTION(MESSAGE, VARIABLE-NAME, FLAGS, +# [OUTPUT-FILE], [ACTION-SUCCESS], [ACTION-FAILURE]) +# ---------------------------------------------------------------- +# Check whether the given compiler option works +AC_DEFUN([AC_LIBTOOL_COMPILER_OPTION], +[AC_REQUIRE([LT_AC_PROG_SED]) +AC_CACHE_CHECK([$1], [$2], + [$2=no + ifelse([$4], , [ac_outfile=conftest.$ac_objext], [ac_outfile=$4]) + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + lt_compiler_flag="$3" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + # The option is referenced via a variable to avoid confusing sed. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [[^ ]]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:__oline__: $lt_compile\"" >&AS_MESSAGE_LOG_FD) + (eval "$lt_compile" 2>conftest.err) + ac_status=$? + cat conftest.err >&AS_MESSAGE_LOG_FD + echo "$as_me:__oline__: \$? = $ac_status" >&AS_MESSAGE_LOG_FD + if (exit $ac_status) && test -s "$ac_outfile"; then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings other than the usual output. + $echo "X$_lt_compiler_boilerplate" | $Xsed -e '/^$/d' >conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then + $2=yes + fi + fi + $rm conftest* +]) + +if test x"[$]$2" = xyes; then + ifelse([$5], , :, [$5]) +else + ifelse([$6], , :, [$6]) +fi +])# AC_LIBTOOL_COMPILER_OPTION + + +# AC_LIBTOOL_LINKER_OPTION(MESSAGE, VARIABLE-NAME, FLAGS, +# [ACTION-SUCCESS], [ACTION-FAILURE]) +# ------------------------------------------------------------ +# Check whether the given compiler option works +AC_DEFUN([AC_LIBTOOL_LINKER_OPTION], +[AC_REQUIRE([LT_AC_PROG_SED])dnl +AC_CACHE_CHECK([$1], [$2], + [$2=no + save_LDFLAGS="$LDFLAGS" + LDFLAGS="$LDFLAGS $3" + echo "$lt_simple_link_test_code" > conftest.$ac_ext + if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then + # The linker can only warn and ignore the option if not recognized + # So say no if there are warnings + if test -s conftest.err; then + # Append any errors to the config.log. + cat conftest.err 1>&AS_MESSAGE_LOG_FD + $echo "X$_lt_linker_boilerplate" | $Xsed -e '/^$/d' > conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if diff conftest.exp conftest.er2 >/dev/null; then + $2=yes + fi + else + $2=yes + fi + fi + $rm -r conftest* + LDFLAGS="$save_LDFLAGS" +]) + +if test x"[$]$2" = xyes; then + ifelse([$4], , :, [$4]) +else + ifelse([$5], , :, [$5]) +fi +])# AC_LIBTOOL_LINKER_OPTION + + +# AC_LIBTOOL_SYS_MAX_CMD_LEN +# -------------------------- +AC_DEFUN([AC_LIBTOOL_SYS_MAX_CMD_LEN], +[# find the maximum length of command line arguments +AC_MSG_CHECKING([the maximum length of command line arguments]) +AC_CACHE_VAL([lt_cv_sys_max_cmd_len], [dnl + i=0 + teststring="ABCD" + + case $build_os in + msdosdjgpp*) + # On DJGPP, this test can blow up pretty badly due to problems in libc + # (any single argument exceeding 2000 bytes causes a buffer overrun + # during glob expansion). Even if it were fixed, the result of this + # check would be larger than it should be. + lt_cv_sys_max_cmd_len=12288; # 12K is about right + ;; + + gnu*) + # Under GNU Hurd, this test is not required because there is + # no limit to the length of command line arguments. + # Libtool will interpret -1 as no limit whatsoever + lt_cv_sys_max_cmd_len=-1; + ;; + + cygwin* | mingw*) + # On Win9x/ME, this test blows up -- it succeeds, but takes + # about 5 minutes as the teststring grows exponentially. + # Worse, since 9x/ME are not pre-emptively multitasking, + # you end up with a "frozen" computer, even though with patience + # the test eventually succeeds (with a max line length of 256k). + # Instead, let's just punt: use the minimum linelength reported by + # all of the supported platforms: 8192 (on NT/2K/XP). + lt_cv_sys_max_cmd_len=8192; + ;; + + amigaos*) + # On AmigaOS with pdksh, this test takes hours, literally. + # So we just punt and use a minimum line length of 8192. + lt_cv_sys_max_cmd_len=8192; + ;; + + netbsd* | freebsd* | openbsd* | darwin* | dragonfly*) + # This has been around since 386BSD, at least. Likely further. + if test -x /sbin/sysctl; then + lt_cv_sys_max_cmd_len=`/sbin/sysctl -n kern.argmax` + elif test -x /usr/sbin/sysctl; then + lt_cv_sys_max_cmd_len=`/usr/sbin/sysctl -n kern.argmax` + else + lt_cv_sys_max_cmd_len=65536 # usable default for all BSDs + fi + # And add a safety zone + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` + ;; + + interix*) + # We know the value 262144 and hardcode it with a safety zone (like BSD) + lt_cv_sys_max_cmd_len=196608 + ;; + + osf*) + # Dr. Hans Ekkehard Plesser reports seeing a kernel panic running configure + # due to this test when exec_disable_arg_limit is 1 on Tru64. It is not + # nice to cause kernel panics so lets avoid the loop below. + # First set a reasonable default. + lt_cv_sys_max_cmd_len=16384 + # + if test -x /sbin/sysconfig; then + case `/sbin/sysconfig -q proc exec_disable_arg_limit` in + *1*) lt_cv_sys_max_cmd_len=-1 ;; + esac + fi + ;; + sco3.2v5*) + lt_cv_sys_max_cmd_len=102400 + ;; + sysv5* | sco5v6* | sysv4.2uw2*) + kargmax=`grep ARG_MAX /etc/conf/cf.d/stune 2>/dev/null` + if test -n "$kargmax"; then + lt_cv_sys_max_cmd_len=`echo $kargmax | sed 's/.*[[ ]]//'` + else + lt_cv_sys_max_cmd_len=32768 + fi + ;; + *) + lt_cv_sys_max_cmd_len=`(getconf ARG_MAX) 2> /dev/null` + if test -n "$lt_cv_sys_max_cmd_len"; then + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` + else + SHELL=${SHELL-${CONFIG_SHELL-/bin/sh}} + while (test "X"`$SHELL [$]0 --fallback-echo "X$teststring" 2>/dev/null` \ + = "XX$teststring") >/dev/null 2>&1 && + new_result=`expr "X$teststring" : ".*" 2>&1` && + lt_cv_sys_max_cmd_len=$new_result && + test $i != 17 # 1/2 MB should be enough + do + i=`expr $i + 1` + teststring=$teststring$teststring + done + teststring= + # Add a significant safety factor because C++ compilers can tack on massive + # amounts of additional arguments before passing them to the linker. + # It appears as though 1/2 is a usable value. + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 2` + fi + ;; + esac +]) +if test -n $lt_cv_sys_max_cmd_len ; then + AC_MSG_RESULT($lt_cv_sys_max_cmd_len) +else + AC_MSG_RESULT(none) +fi +])# AC_LIBTOOL_SYS_MAX_CMD_LEN + + +# _LT_AC_CHECK_DLFCN +# ------------------ +AC_DEFUN([_LT_AC_CHECK_DLFCN], +[AC_CHECK_HEADERS(dlfcn.h)dnl +])# _LT_AC_CHECK_DLFCN + + +# _LT_AC_TRY_DLOPEN_SELF (ACTION-IF-TRUE, ACTION-IF-TRUE-W-USCORE, +# ACTION-IF-FALSE, ACTION-IF-CROSS-COMPILING) +# --------------------------------------------------------------------- +AC_DEFUN([_LT_AC_TRY_DLOPEN_SELF], +[AC_REQUIRE([_LT_AC_CHECK_DLFCN])dnl +if test "$cross_compiling" = yes; then : + [$4] +else + lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 + lt_status=$lt_dlunknown + cat > conftest.$ac_ext < +#endif + +#include + +#ifdef RTLD_GLOBAL +# define LT_DLGLOBAL RTLD_GLOBAL +#else +# ifdef DL_GLOBAL +# define LT_DLGLOBAL DL_GLOBAL +# else +# define LT_DLGLOBAL 0 +# endif +#endif + +/* We may have to define LT_DLLAZY_OR_NOW in the command line if we + find out it does not work in some platform. */ +#ifndef LT_DLLAZY_OR_NOW +# ifdef RTLD_LAZY +# define LT_DLLAZY_OR_NOW RTLD_LAZY +# else +# ifdef DL_LAZY +# define LT_DLLAZY_OR_NOW DL_LAZY +# else +# ifdef RTLD_NOW +# define LT_DLLAZY_OR_NOW RTLD_NOW +# else +# ifdef DL_NOW +# define LT_DLLAZY_OR_NOW DL_NOW +# else +# define LT_DLLAZY_OR_NOW 0 +# endif +# endif +# endif +# endif +#endif + +#ifdef __cplusplus +extern "C" void exit (int); +#endif + +void fnord() { int i=42;} +int main () +{ + void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW); + int status = $lt_dlunknown; + + if (self) + { + if (dlsym (self,"fnord")) status = $lt_dlno_uscore; + else if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore; + /* dlclose (self); */ + } + else + puts (dlerror ()); + + exit (status); +}] +EOF + if AC_TRY_EVAL(ac_link) && test -s conftest${ac_exeext} 2>/dev/null; then + (./conftest; exit; ) >&AS_MESSAGE_LOG_FD 2>/dev/null + lt_status=$? + case x$lt_status in + x$lt_dlno_uscore) $1 ;; + x$lt_dlneed_uscore) $2 ;; + x$lt_dlunknown|x*) $3 ;; + esac + else : + # compilation failed + $3 + fi +fi +rm -fr conftest* +])# _LT_AC_TRY_DLOPEN_SELF + + +# AC_LIBTOOL_DLOPEN_SELF +# ---------------------- +AC_DEFUN([AC_LIBTOOL_DLOPEN_SELF], +[AC_REQUIRE([_LT_AC_CHECK_DLFCN])dnl +if test "x$enable_dlopen" != xyes; then + enable_dlopen=unknown + enable_dlopen_self=unknown + enable_dlopen_self_static=unknown +else + lt_cv_dlopen=no + lt_cv_dlopen_libs= + + case $host_os in + beos*) + lt_cv_dlopen="load_add_on" + lt_cv_dlopen_libs= + lt_cv_dlopen_self=yes + ;; + + mingw* | pw32*) + lt_cv_dlopen="LoadLibrary" + lt_cv_dlopen_libs= + ;; + + cygwin*) + lt_cv_dlopen="dlopen" + lt_cv_dlopen_libs= + ;; + + darwin*) + # if libdl is installed we need to link against it + AC_CHECK_LIB([dl], [dlopen], + [lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-ldl"],[ + lt_cv_dlopen="dyld" + lt_cv_dlopen_libs= + lt_cv_dlopen_self=yes + ]) + ;; + + *) + AC_CHECK_FUNC([shl_load], + [lt_cv_dlopen="shl_load"], + [AC_CHECK_LIB([dld], [shl_load], + [lt_cv_dlopen="shl_load" lt_cv_dlopen_libs="-ldld"], + [AC_CHECK_FUNC([dlopen], + [lt_cv_dlopen="dlopen"], + [AC_CHECK_LIB([dl], [dlopen], + [lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-ldl"], + [AC_CHECK_LIB([svld], [dlopen], + [lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-lsvld"], + [AC_CHECK_LIB([dld], [dld_link], + [lt_cv_dlopen="dld_link" lt_cv_dlopen_libs="-ldld"]) + ]) + ]) + ]) + ]) + ]) + ;; + esac + + if test "x$lt_cv_dlopen" != xno; then + enable_dlopen=yes + else + enable_dlopen=no + fi + + case $lt_cv_dlopen in + dlopen) + save_CPPFLAGS="$CPPFLAGS" + test "x$ac_cv_header_dlfcn_h" = xyes && CPPFLAGS="$CPPFLAGS -DHAVE_DLFCN_H" + + save_LDFLAGS="$LDFLAGS" + wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $export_dynamic_flag_spec\" + + save_LIBS="$LIBS" + LIBS="$lt_cv_dlopen_libs $LIBS" + + AC_CACHE_CHECK([whether a program can dlopen itself], + lt_cv_dlopen_self, [dnl + _LT_AC_TRY_DLOPEN_SELF( + lt_cv_dlopen_self=yes, lt_cv_dlopen_self=yes, + lt_cv_dlopen_self=no, lt_cv_dlopen_self=cross) + ]) + + if test "x$lt_cv_dlopen_self" = xyes; then + wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $lt_prog_compiler_static\" + AC_CACHE_CHECK([whether a statically linked program can dlopen itself], + lt_cv_dlopen_self_static, [dnl + _LT_AC_TRY_DLOPEN_SELF( + lt_cv_dlopen_self_static=yes, lt_cv_dlopen_self_static=yes, + lt_cv_dlopen_self_static=no, lt_cv_dlopen_self_static=cross) + ]) + fi + + CPPFLAGS="$save_CPPFLAGS" + LDFLAGS="$save_LDFLAGS" + LIBS="$save_LIBS" + ;; + esac + + case $lt_cv_dlopen_self in + yes|no) enable_dlopen_self=$lt_cv_dlopen_self ;; + *) enable_dlopen_self=unknown ;; + esac + + case $lt_cv_dlopen_self_static in + yes|no) enable_dlopen_self_static=$lt_cv_dlopen_self_static ;; + *) enable_dlopen_self_static=unknown ;; + esac +fi +])# AC_LIBTOOL_DLOPEN_SELF + + +# AC_LIBTOOL_PROG_CC_C_O([TAGNAME]) +# --------------------------------- +# Check to see if options -c and -o are simultaneously supported by compiler +AC_DEFUN([AC_LIBTOOL_PROG_CC_C_O], +[AC_REQUIRE([LT_AC_PROG_SED])dnl +AC_REQUIRE([_LT_AC_SYS_COMPILER])dnl +AC_CACHE_CHECK([if $compiler supports -c -o file.$ac_objext], + [_LT_AC_TAGVAR(lt_cv_prog_compiler_c_o, $1)], + [_LT_AC_TAGVAR(lt_cv_prog_compiler_c_o, $1)=no + $rm -r conftest 2>/dev/null + mkdir conftest + cd conftest + mkdir out + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + lt_compiler_flag="-o out/conftest2.$ac_objext" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [[^ ]]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:__oline__: $lt_compile\"" >&AS_MESSAGE_LOG_FD) + (eval "$lt_compile" 2>out/conftest.err) + ac_status=$? + cat out/conftest.err >&AS_MESSAGE_LOG_FD + echo "$as_me:__oline__: \$? = $ac_status" >&AS_MESSAGE_LOG_FD + if (exit $ac_status) && test -s out/conftest2.$ac_objext + then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + $echo "X$_lt_compiler_boilerplate" | $Xsed -e '/^$/d' > out/conftest.exp + $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 + if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then + _LT_AC_TAGVAR(lt_cv_prog_compiler_c_o, $1)=yes + fi + fi + chmod u+w . 2>&AS_MESSAGE_LOG_FD + $rm conftest* + # SGI C++ compiler will create directory out/ii_files/ for + # template instantiation + test -d out/ii_files && $rm out/ii_files/* && rmdir out/ii_files + $rm out/* && rmdir out + cd .. + rmdir conftest + $rm conftest* +]) +])# AC_LIBTOOL_PROG_CC_C_O + + +# AC_LIBTOOL_SYS_HARD_LINK_LOCKS([TAGNAME]) +# ----------------------------------------- +# Check to see if we can do hard links to lock some files if needed +AC_DEFUN([AC_LIBTOOL_SYS_HARD_LINK_LOCKS], +[AC_REQUIRE([_LT_AC_LOCK])dnl + +hard_links="nottested" +if test "$_LT_AC_TAGVAR(lt_cv_prog_compiler_c_o, $1)" = no && test "$need_locks" != no; then + # do not overwrite the value of need_locks provided by the user + AC_MSG_CHECKING([if we can lock with hard links]) + hard_links=yes + $rm conftest* + ln conftest.a conftest.b 2>/dev/null && hard_links=no + touch conftest.a + ln conftest.a conftest.b 2>&5 || hard_links=no + ln conftest.a conftest.b 2>/dev/null && hard_links=no + AC_MSG_RESULT([$hard_links]) + if test "$hard_links" = no; then + AC_MSG_WARN([`$CC' does not support `-c -o', so `make -j' may be unsafe]) + need_locks=warn + fi +else + need_locks=no +fi +])# AC_LIBTOOL_SYS_HARD_LINK_LOCKS + + +# AC_LIBTOOL_OBJDIR +# ----------------- +AC_DEFUN([AC_LIBTOOL_OBJDIR], +[AC_CACHE_CHECK([for objdir], [lt_cv_objdir], +[rm -f .libs 2>/dev/null +mkdir .libs 2>/dev/null +if test -d .libs; then + lt_cv_objdir=.libs +else + # MS-DOS does not allow filenames that begin with a dot. + lt_cv_objdir=_libs +fi +rmdir .libs 2>/dev/null]) +objdir=$lt_cv_objdir +])# AC_LIBTOOL_OBJDIR + + +# AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH([TAGNAME]) +# ---------------------------------------------- +# Check hardcoding attributes. +AC_DEFUN([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH], +[AC_MSG_CHECKING([how to hardcode library paths into programs]) +_LT_AC_TAGVAR(hardcode_action, $1)= +if test -n "$_LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)" || \ + test -n "$_LT_AC_TAGVAR(runpath_var, $1)" || \ + test "X$_LT_AC_TAGVAR(hardcode_automatic, $1)" = "Xyes" ; then + + # We can hardcode non-existant directories. + if test "$_LT_AC_TAGVAR(hardcode_direct, $1)" != no && + # If the only mechanism to avoid hardcoding is shlibpath_var, we + # have to relink, otherwise we might link with an installed library + # when we should be linking with a yet-to-be-installed one + ## test "$_LT_AC_TAGVAR(hardcode_shlibpath_var, $1)" != no && + test "$_LT_AC_TAGVAR(hardcode_minus_L, $1)" != no; then + # Linking always hardcodes the temporary library directory. + _LT_AC_TAGVAR(hardcode_action, $1)=relink + else + # We can link without hardcoding, and we can hardcode nonexisting dirs. + _LT_AC_TAGVAR(hardcode_action, $1)=immediate + fi +else + # We cannot hardcode anything, or else we can only hardcode existing + # directories. + _LT_AC_TAGVAR(hardcode_action, $1)=unsupported +fi +AC_MSG_RESULT([$_LT_AC_TAGVAR(hardcode_action, $1)]) + +if test "$_LT_AC_TAGVAR(hardcode_action, $1)" = relink; then + # Fast installation is not supported + enable_fast_install=no +elif test "$shlibpath_overrides_runpath" = yes || + test "$enable_shared" = no; then + # Fast installation is not necessary + enable_fast_install=needless +fi +])# AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH + + +# AC_LIBTOOL_SYS_LIB_STRIP +# ------------------------ +AC_DEFUN([AC_LIBTOOL_SYS_LIB_STRIP], +[striplib= +old_striplib= +AC_MSG_CHECKING([whether stripping libraries is possible]) +if test -n "$STRIP" && $STRIP -V 2>&1 | grep "GNU strip" >/dev/null; then + test -z "$old_striplib" && old_striplib="$STRIP --strip-debug" + test -z "$striplib" && striplib="$STRIP --strip-unneeded" + AC_MSG_RESULT([yes]) +else +# FIXME - insert some real tests, host_os isn't really good enough + case $host_os in + darwin*) + if test -n "$STRIP" ; then + striplib="$STRIP -x" + old_striplib="$STRIP -S" + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) +fi + ;; + *) + AC_MSG_RESULT([no]) + ;; + esac +fi +])# AC_LIBTOOL_SYS_LIB_STRIP + + +# AC_LIBTOOL_SYS_DYNAMIC_LINKER +# ----------------------------- +# PORTME Fill in your ld.so characteristics +AC_DEFUN([AC_LIBTOOL_SYS_DYNAMIC_LINKER], +[AC_REQUIRE([LT_AC_PROG_SED])dnl +AC_MSG_CHECKING([dynamic linker characteristics]) +library_names_spec= +libname_spec='lib$name' +soname_spec= +shrext_cmds=".so" +postinstall_cmds= +postuninstall_cmds= +finish_cmds= +finish_eval= +shlibpath_var= +shlibpath_overrides_runpath=unknown +version_type=none +dynamic_linker="$host_os ld.so" +sys_lib_dlsearch_path_spec="/lib /usr/lib" +m4_if($1,[],[ +if test "$GCC" = yes; then + case $host_os in + darwin*) lt_awk_arg="/^libraries:/,/LR/" ;; + *) lt_awk_arg="/^libraries:/" ;; + esac + lt_search_path_spec=`$CC -print-search-dirs | awk $lt_awk_arg | $SED -e "s/^libraries://" -e "s,=/,/,g"` + if echo "$lt_search_path_spec" | grep ';' >/dev/null ; then + # if the path contains ";" then we assume it to be the separator + # otherwise default to the standard path separator (i.e. ":") - it is + # assumed that no part of a normal pathname contains ";" but that should + # okay in the real world where ";" in dirpaths is itself problematic. + lt_search_path_spec=`echo "$lt_search_path_spec" | $SED -e 's/;/ /g'` + else + lt_search_path_spec=`echo "$lt_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + fi + # Ok, now we have the path, separated by spaces, we can step through it + # and add multilib dir if necessary. + lt_tmp_lt_search_path_spec= + lt_multi_os_dir=`$CC $CPPFLAGS $CFLAGS $LDFLAGS -print-multi-os-directory 2>/dev/null` + for lt_sys_path in $lt_search_path_spec; do + if test -d "$lt_sys_path/$lt_multi_os_dir"; then + lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path/$lt_multi_os_dir" + else + test -d "$lt_sys_path" && \ + lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path" + fi + done + lt_search_path_spec=`echo $lt_tmp_lt_search_path_spec | awk ' +BEGIN {RS=" "; FS="/|\n";} { + lt_foo=""; + lt_count=0; + for (lt_i = NF; lt_i > 0; lt_i--) { + if ($lt_i != "" && $lt_i != ".") { + if ($lt_i == "..") { + lt_count++; + } else { + if (lt_count == 0) { + lt_foo="/" $lt_i lt_foo; + } else { + lt_count--; + } + } + } + } + if (lt_foo != "") { lt_freq[[lt_foo]]++; } + if (lt_freq[[lt_foo]] == 1) { print lt_foo; } +}'` + sys_lib_search_path_spec=`echo $lt_search_path_spec` +else + sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" +fi]) +need_lib_prefix=unknown +hardcode_into_libs=no + +# when you set need_version to no, make sure it does not cause -set_version +# flags to be left without arguments +need_version=unknown + +case $host_os in +aix3*) + version_type=linux + library_names_spec='${libname}${release}${shared_ext}$versuffix $libname.a' + shlibpath_var=LIBPATH + + # AIX 3 has no versioning support, so we append a major version to the name. + soname_spec='${libname}${release}${shared_ext}$major' + ;; + +aix[[4-9]]*) + version_type=linux + need_lib_prefix=no + need_version=no + hardcode_into_libs=yes + if test "$host_cpu" = ia64; then + # AIX 5 supports IA64 + library_names_spec='${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext}$versuffix $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + else + # With GCC up to 2.95.x, collect2 would create an import file + # for dependence libraries. The import file would start with + # the line `#! .'. This would cause the generated library to + # depend on `.', always an invalid library. This was fixed in + # development snapshots of GCC prior to 3.0. + case $host_os in + aix4 | aix4.[[01]] | aix4.[[01]].*) + if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)' + echo ' yes ' + echo '#endif'; } | ${CC} -E - | grep yes > /dev/null; then + : + else + can_build_shared=no + fi + ;; + esac + # AIX (on Power*) has no versioning support, so currently we can not hardcode correct + # soname into executable. Probably we can add versioning support to + # collect2, so additional links can be useful in future. + if test "$aix_use_runtimelinking" = yes; then + # If using run time linking (on AIX 4.2 or later) use lib.so + # instead of lib.a to let people know that these are not + # typical AIX shared libraries. + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + else + # We preserve .a as extension for shared libraries through AIX4.2 + # and later when we are not doing run time linking. + library_names_spec='${libname}${release}.a $libname.a' + soname_spec='${libname}${release}${shared_ext}$major' + fi + shlibpath_var=LIBPATH + fi + ;; + +amigaos*) + library_names_spec='$libname.ixlibrary $libname.a' + # Create ${libname}_ixlibrary.a entries in /sys/libs. + finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`$echo "X$lib" | $Xsed -e '\''s%^.*/\([[^/]]*\)\.ixlibrary$%\1%'\''`; test $rm /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done' + ;; + +beos*) + library_names_spec='${libname}${shared_ext}' + dynamic_linker="$host_os ld.so" + shlibpath_var=LIBRARY_PATH + ;; + +bsdi[[45]]*) + version_type=linux + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib" + sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib" + # the default ld.so.conf also contains /usr/contrib/lib and + # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow + # libtool to hard-code these into programs + ;; + +cygwin* | mingw* | pw32*) + version_type=windows + shrext_cmds=".dll" + need_version=no + need_lib_prefix=no + + case $GCC,$host_os in + yes,cygwin* | yes,mingw* | yes,pw32*) + library_names_spec='$libname.dll.a' + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \${file}`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\${base_file}'\''i;echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname~ + chmod a+x \$dldir/$dlname' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $rm \$dlpath' + shlibpath_overrides_runpath=yes + + case $host_os in + cygwin*) + # Cygwin DLLs use 'cyg' prefix rather than 'lib' + soname_spec='`echo ${libname} | sed -e 's/^lib/cyg/'``echo ${release} | $SED -e 's/[[.]]/-/g'`${versuffix}${shared_ext}' + sys_lib_search_path_spec="/usr/lib /lib/w32api /lib /usr/local/lib" + ;; + mingw*) + # MinGW DLLs use traditional 'lib' prefix + soname_spec='${libname}`echo ${release} | $SED -e 's/[[.]]/-/g'`${versuffix}${shared_ext}' + sys_lib_search_path_spec=`$CC -print-search-dirs | grep "^libraries:" | $SED -e "s/^libraries://" -e "s,=/,/,g"` + if echo "$sys_lib_search_path_spec" | [grep ';[c-zC-Z]:/' >/dev/null]; then + # It is most probably a Windows format PATH printed by + # mingw gcc, but we are running on Cygwin. Gcc prints its search + # path with ; separators, and with drive letters. We can handle the + # drive letters (cygwin fileutils understands them), so leave them, + # especially as we might pass files found there to a mingw objdump, + # which wouldn't understand a cygwinified path. Ahh. + sys_lib_search_path_spec=`echo "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'` + else + sys_lib_search_path_spec=`echo "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + fi + ;; + pw32*) + # pw32 DLLs use 'pw' prefix rather than 'lib' + library_names_spec='`echo ${libname} | sed -e 's/^lib/pw/'``echo ${release} | $SED -e 's/[[.]]/-/g'`${versuffix}${shared_ext}' + ;; + esac + ;; + + *) + library_names_spec='${libname}`echo ${release} | $SED -e 's/[[.]]/-/g'`${versuffix}${shared_ext} $libname.lib' + ;; + esac + dynamic_linker='Win32 ld.exe' + # FIXME: first we should search . and the directory the executable is in + shlibpath_var=PATH + ;; + +darwin* | rhapsody*) + dynamic_linker="$host_os dyld" + version_type=darwin + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${versuffix}$shared_ext ${libname}${release}${major}$shared_ext ${libname}$shared_ext' + soname_spec='${libname}${release}${major}$shared_ext' + shlibpath_overrides_runpath=yes + shlibpath_var=DYLD_LIBRARY_PATH + shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`' + m4_if([$1], [],[ + sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/local/lib"]) + sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib' + ;; + +dgux*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname$shared_ext' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +freebsd1*) + dynamic_linker=no + ;; + +freebsd* | dragonfly*) + # DragonFly does not have aout. When/if they implement a new + # versioning mechanism, adjust this. + if test -x /usr/bin/objformat; then + objformat=`/usr/bin/objformat` + else + case $host_os in + freebsd[[123]]*) objformat=aout ;; + *) objformat=elf ;; + esac + fi + version_type=freebsd-$objformat + case $version_type in + freebsd-elf*) + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext} $libname${shared_ext}' + need_version=no + need_lib_prefix=no + ;; + freebsd-*) + library_names_spec='${libname}${release}${shared_ext}$versuffix $libname${shared_ext}$versuffix' + need_version=yes + ;; + esac + shlibpath_var=LD_LIBRARY_PATH + case $host_os in + freebsd2*) + shlibpath_overrides_runpath=yes + ;; + freebsd3.[[01]]* | freebsdelf3.[[01]]*) + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + freebsd3.[[2-9]]* | freebsdelf3.[[2-9]]* | \ + freebsd4.[[0-5]] | freebsdelf4.[[0-5]] | freebsd4.1.1 | freebsdelf4.1.1) + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + *) # from 4.6 on, and DragonFly + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + esac + ;; + +gnu*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}${major} ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + hardcode_into_libs=yes + ;; + +hpux9* | hpux10* | hpux11*) + # Give a soname corresponding to the major version so that dld.sl refuses to + # link against other versions. + version_type=sunos + need_lib_prefix=no + need_version=no + case $host_cpu in + ia64*) + shrext_cmds='.so' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.so" + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + if test "X$HPUX_IA64_MODE" = X32; then + sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib" + else + sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64" + fi + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + hppa*64*) + shrext_cmds='.sl' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.sl" + shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + *) + shrext_cmds='.sl' + dynamic_linker="$host_os dld.sl" + shlibpath_var=SHLIB_PATH + shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + ;; + esac + # HP-UX runs *really* slowly unless shared libraries are mode 555. + postinstall_cmds='chmod 555 $lib' + ;; + +interix[[3-9]]*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +irix5* | irix6* | nonstopux*) + case $host_os in + nonstopux*) version_type=nonstopux ;; + *) + if test "$lt_cv_prog_gnu_ld" = yes; then + version_type=linux + else + version_type=irix + fi ;; + esac + need_lib_prefix=no + need_version=no + soname_spec='${libname}${release}${shared_ext}$major' + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext} $libname${shared_ext}' + case $host_os in + irix5* | nonstopux*) + libsuff= shlibsuff= + ;; + *) + case $LD in # libtool.m4 will add one of these switches to LD + *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") + libsuff= shlibsuff= libmagic=32-bit;; + *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") + libsuff=32 shlibsuff=N32 libmagic=N32;; + *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") + libsuff=64 shlibsuff=64 libmagic=64-bit;; + *) libsuff= shlibsuff= libmagic=never-match;; + esac + ;; + esac + shlibpath_var=LD_LIBRARY${shlibsuff}_PATH + shlibpath_overrides_runpath=no + sys_lib_search_path_spec="/usr/lib${libsuff} /lib${libsuff} /usr/local/lib${libsuff}" + sys_lib_dlsearch_path_spec="/usr/lib${libsuff} /lib${libsuff}" + hardcode_into_libs=yes + ;; + +# No shared lib support for Linux oldld, aout, or coff. +linux*oldld* | linux*aout* | linux*coff*) + dynamic_linker=no + ;; + +# This must be Linux ELF. +linux* | k*bsd*-gnu) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + # This implies no fast_install, which is unacceptable. + # Some rework will be needed to allow for fast_install + # before this can be enabled. + hardcode_into_libs=yes + + # Append ld.so.conf contents to the search path + if test -f /etc/ld.so.conf; then + lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \[$]2)); skip = 1; } { if (!skip) print \[$]0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[ ]*hwcap[ ]/d;s/[:, ]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;/^$/d' | tr '\n' ' '` + sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra" + fi + + # We used to test for /lib/ld.so.1 and disable shared libraries on + # powerpc, because MkLinux only supported shared libraries with the + # GNU dynamic linker. Since this was broken with cross compilers, + # most powerpc-linux boxes support dynamic linking these days and + # people can always --disable-shared, the test was removed, and we + # assume the GNU/Linux dynamic linker is in use. + dynamic_linker='GNU/Linux ld.so' + ;; + +netbsd*) + version_type=sunos + need_lib_prefix=no + need_version=no + if echo __ELF__ | $CC -E - | grep __ELF__ >/dev/null; then + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + dynamic_linker='NetBSD (a.out) ld.so' + else + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + dynamic_linker='NetBSD ld.elf_so' + fi + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + +newsos6) + version_type=linux + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +nto-qnx*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +openbsd*) + version_type=sunos + sys_lib_dlsearch_path_spec="/usr/lib" + need_lib_prefix=no + # Some older versions of OpenBSD (3.3 at least) *do* need versioned libs. + case $host_os in + openbsd3.3 | openbsd3.3.*) need_version=yes ;; + *) need_version=no ;; + esac + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + shlibpath_var=LD_LIBRARY_PATH + if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then + case $host_os in + openbsd2.[[89]] | openbsd2.[[89]].*) + shlibpath_overrides_runpath=no + ;; + *) + shlibpath_overrides_runpath=yes + ;; + esac + else + shlibpath_overrides_runpath=yes + fi + ;; + +os2*) + libname_spec='$name' + shrext_cmds=".dll" + need_lib_prefix=no + library_names_spec='$libname${shared_ext} $libname.a' + dynamic_linker='OS/2 ld.exe' + shlibpath_var=LIBPATH + ;; + +osf3* | osf4* | osf5*) + version_type=osf + need_lib_prefix=no + need_version=no + soname_spec='${libname}${release}${shared_ext}$major' + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib" + sys_lib_dlsearch_path_spec="$sys_lib_search_path_spec" + ;; + +rdos*) + dynamic_linker=no + ;; + +solaris*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + # ldd complains unless libraries are executable + postinstall_cmds='chmod +x $lib' + ;; + +sunos4*) + version_type=sunos + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix' + finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + if test "$with_gnu_ld" = yes; then + need_lib_prefix=no + fi + need_version=yes + ;; + +sysv4 | sysv4.3*) + version_type=linux + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + case $host_vendor in + sni) + shlibpath_overrides_runpath=no + need_lib_prefix=no + export_dynamic_flag_spec='${wl}-Blargedynsym' + runpath_var=LD_RUN_PATH + ;; + siemens) + need_lib_prefix=no + ;; + motorola) + need_lib_prefix=no + need_version=no + shlibpath_overrides_runpath=no + sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib' + ;; + esac + ;; + +sysv4*MP*) + if test -d /usr/nec ;then + version_type=linux + library_names_spec='$libname${shared_ext}.$versuffix $libname${shared_ext}.$major $libname${shared_ext}' + soname_spec='$libname${shared_ext}.$major' + shlibpath_var=LD_LIBRARY_PATH + fi + ;; + +sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + version_type=freebsd-elf + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext} $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + hardcode_into_libs=yes + if test "$with_gnu_ld" = yes; then + sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib' + shlibpath_overrides_runpath=no + else + sys_lib_search_path_spec='/usr/ccs/lib /usr/lib' + shlibpath_overrides_runpath=yes + case $host_os in + sco3.2v5*) + sys_lib_search_path_spec="$sys_lib_search_path_spec /lib" + ;; + esac + fi + sys_lib_dlsearch_path_spec='/usr/lib' + ;; + +uts4*) + version_type=linux + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +*) + dynamic_linker=no + ;; +esac +AC_MSG_RESULT([$dynamic_linker]) +test "$dynamic_linker" = no && can_build_shared=no + +AC_CACHE_VAL([lt_cv_sys_lib_search_path_spec], +[lt_cv_sys_lib_search_path_spec="$sys_lib_search_path_spec"]) +sys_lib_search_path_spec="$lt_cv_sys_lib_search_path_spec" +AC_CACHE_VAL([lt_cv_sys_lib_dlsearch_path_spec], +[lt_cv_sys_lib_dlsearch_path_spec="$sys_lib_dlsearch_path_spec"]) +sys_lib_dlsearch_path_spec="$lt_cv_sys_lib_dlsearch_path_spec" + +variables_saved_for_relink="PATH $shlibpath_var $runpath_var" +if test "$GCC" = yes; then + variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH" +fi +])# AC_LIBTOOL_SYS_DYNAMIC_LINKER + + +# _LT_AC_TAGCONFIG +# ---------------- +AC_DEFUN([_LT_AC_TAGCONFIG], +[AC_REQUIRE([LT_AC_PROG_SED])dnl +AC_ARG_WITH([tags], + [AC_HELP_STRING([--with-tags@<:@=TAGS@:>@], + [include additional configurations @<:@automatic@:>@])], + [tagnames="$withval"]) + +if test -f "$ltmain" && test -n "$tagnames"; then + if test ! -f "${ofile}"; then + AC_MSG_WARN([output file `$ofile' does not exist]) + fi + + if test -z "$LTCC"; then + eval "`$SHELL ${ofile} --config | grep '^LTCC='`" + if test -z "$LTCC"; then + AC_MSG_WARN([output file `$ofile' does not look like a libtool script]) + else + AC_MSG_WARN([using `LTCC=$LTCC', extracted from `$ofile']) + fi + fi + if test -z "$LTCFLAGS"; then + eval "`$SHELL ${ofile} --config | grep '^LTCFLAGS='`" + fi + + # Extract list of available tagged configurations in $ofile. + # Note that this assumes the entire list is on one line. + available_tags=`grep "^available_tags=" "${ofile}" | $SED -e 's/available_tags=\(.*$\)/\1/' -e 's/\"//g'` + + lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR," + for tagname in $tagnames; do + IFS="$lt_save_ifs" + # Check whether tagname contains only valid characters + case `$echo "X$tagname" | $Xsed -e 's:[[-_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890,/]]::g'` in + "") ;; + *) AC_MSG_ERROR([invalid tag name: $tagname]) + ;; + esac + + if grep "^# ### BEGIN LIBTOOL TAG CONFIG: $tagname$" < "${ofile}" > /dev/null + then + AC_MSG_ERROR([tag name \"$tagname\" already exists]) + fi + + # Update the list of available tags. + if test -n "$tagname"; then + echo appending configuration tag \"$tagname\" to $ofile + + case $tagname in + CXX) + if test -n "$CXX" && ( test "X$CXX" != "Xno" && + ( (test "X$CXX" = "Xg++" && `g++ -v >/dev/null 2>&1` ) || + (test "X$CXX" != "Xg++"))) ; then + AC_LIBTOOL_LANG_CXX_CONFIG + else + tagname="" + fi + ;; + + F77) + if test -n "$F77" && test "X$F77" != "Xno"; then + AC_LIBTOOL_LANG_F77_CONFIG + else + tagname="" + fi + ;; + + GCJ) + if test -n "$GCJ" && test "X$GCJ" != "Xno"; then + AC_LIBTOOL_LANG_GCJ_CONFIG + else + tagname="" + fi + ;; + + RC) + AC_LIBTOOL_LANG_RC_CONFIG + ;; + + *) + AC_MSG_ERROR([Unsupported tag name: $tagname]) + ;; + esac + + # Append the new tag name to the list of available tags. + if test -n "$tagname" ; then + available_tags="$available_tags $tagname" + fi + fi + done + IFS="$lt_save_ifs" + + # Now substitute the updated list of available tags. + if eval "sed -e 's/^available_tags=.*\$/available_tags=\"$available_tags\"/' \"$ofile\" > \"${ofile}T\""; then + mv "${ofile}T" "$ofile" + chmod +x "$ofile" + else + rm -f "${ofile}T" + AC_MSG_ERROR([unable to update list of available tagged configurations.]) + fi +fi +])# _LT_AC_TAGCONFIG + + +# AC_LIBTOOL_DLOPEN +# ----------------- +# enable checks for dlopen support +AC_DEFUN([AC_LIBTOOL_DLOPEN], + [AC_BEFORE([$0],[AC_LIBTOOL_SETUP]) +])# AC_LIBTOOL_DLOPEN + + +# AC_LIBTOOL_WIN32_DLL +# -------------------- +# declare package support for building win32 DLLs +AC_DEFUN([AC_LIBTOOL_WIN32_DLL], +[AC_BEFORE([$0], [AC_LIBTOOL_SETUP]) +])# AC_LIBTOOL_WIN32_DLL + + +# AC_ENABLE_SHARED([DEFAULT]) +# --------------------------- +# implement the --enable-shared flag +# DEFAULT is either `yes' or `no'. If omitted, it defaults to `yes'. +AC_DEFUN([AC_ENABLE_SHARED], +[define([AC_ENABLE_SHARED_DEFAULT], ifelse($1, no, no, yes))dnl +AC_ARG_ENABLE([shared], + [AC_HELP_STRING([--enable-shared@<:@=PKGS@:>@], + [build shared libraries @<:@default=]AC_ENABLE_SHARED_DEFAULT[@:>@])], + [p=${PACKAGE-default} + case $enableval in + yes) enable_shared=yes ;; + no) enable_shared=no ;; + *) + enable_shared=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR," + for pkg in $enableval; do + IFS="$lt_save_ifs" + if test "X$pkg" = "X$p"; then + enable_shared=yes + fi + done + IFS="$lt_save_ifs" + ;; + esac], + [enable_shared=]AC_ENABLE_SHARED_DEFAULT) +])# AC_ENABLE_SHARED + + +# AC_DISABLE_SHARED +# ----------------- +# set the default shared flag to --disable-shared +AC_DEFUN([AC_DISABLE_SHARED], +[AC_BEFORE([$0],[AC_LIBTOOL_SETUP])dnl +AC_ENABLE_SHARED(no) +])# AC_DISABLE_SHARED + + +# AC_ENABLE_STATIC([DEFAULT]) +# --------------------------- +# implement the --enable-static flag +# DEFAULT is either `yes' or `no'. If omitted, it defaults to `yes'. +AC_DEFUN([AC_ENABLE_STATIC], +[define([AC_ENABLE_STATIC_DEFAULT], ifelse($1, no, no, yes))dnl +AC_ARG_ENABLE([static], + [AC_HELP_STRING([--enable-static@<:@=PKGS@:>@], + [build static libraries @<:@default=]AC_ENABLE_STATIC_DEFAULT[@:>@])], + [p=${PACKAGE-default} + case $enableval in + yes) enable_static=yes ;; + no) enable_static=no ;; + *) + enable_static=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR," + for pkg in $enableval; do + IFS="$lt_save_ifs" + if test "X$pkg" = "X$p"; then + enable_static=yes + fi + done + IFS="$lt_save_ifs" + ;; + esac], + [enable_static=]AC_ENABLE_STATIC_DEFAULT) +])# AC_ENABLE_STATIC + + +# AC_DISABLE_STATIC +# ----------------- +# set the default static flag to --disable-static +AC_DEFUN([AC_DISABLE_STATIC], +[AC_BEFORE([$0],[AC_LIBTOOL_SETUP])dnl +AC_ENABLE_STATIC(no) +])# AC_DISABLE_STATIC + + +# AC_ENABLE_FAST_INSTALL([DEFAULT]) +# --------------------------------- +# implement the --enable-fast-install flag +# DEFAULT is either `yes' or `no'. If omitted, it defaults to `yes'. +AC_DEFUN([AC_ENABLE_FAST_INSTALL], +[define([AC_ENABLE_FAST_INSTALL_DEFAULT], ifelse($1, no, no, yes))dnl +AC_ARG_ENABLE([fast-install], + [AC_HELP_STRING([--enable-fast-install@<:@=PKGS@:>@], + [optimize for fast installation @<:@default=]AC_ENABLE_FAST_INSTALL_DEFAULT[@:>@])], + [p=${PACKAGE-default} + case $enableval in + yes) enable_fast_install=yes ;; + no) enable_fast_install=no ;; + *) + enable_fast_install=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR," + for pkg in $enableval; do + IFS="$lt_save_ifs" + if test "X$pkg" = "X$p"; then + enable_fast_install=yes + fi + done + IFS="$lt_save_ifs" + ;; + esac], + [enable_fast_install=]AC_ENABLE_FAST_INSTALL_DEFAULT) +])# AC_ENABLE_FAST_INSTALL + + +# AC_DISABLE_FAST_INSTALL +# ----------------------- +# set the default to --disable-fast-install +AC_DEFUN([AC_DISABLE_FAST_INSTALL], +[AC_BEFORE([$0],[AC_LIBTOOL_SETUP])dnl +AC_ENABLE_FAST_INSTALL(no) +])# AC_DISABLE_FAST_INSTALL + + +# AC_LIBTOOL_PICMODE([MODE]) +# -------------------------- +# implement the --with-pic flag +# MODE is either `yes' or `no'. If omitted, it defaults to `both'. +AC_DEFUN([AC_LIBTOOL_PICMODE], +[AC_BEFORE([$0],[AC_LIBTOOL_SETUP])dnl +pic_mode=ifelse($#,1,$1,default) +])# AC_LIBTOOL_PICMODE + + +# AC_PROG_EGREP +# ------------- +# This is predefined starting with Autoconf 2.54, so this conditional +# definition can be removed once we require Autoconf 2.54 or later. +m4_ifndef([AC_PROG_EGREP], [AC_DEFUN([AC_PROG_EGREP], +[AC_CACHE_CHECK([for egrep], [ac_cv_prog_egrep], + [if echo a | (grep -E '(a|b)') >/dev/null 2>&1 + then ac_cv_prog_egrep='grep -E' + else ac_cv_prog_egrep='egrep' + fi]) + EGREP=$ac_cv_prog_egrep + AC_SUBST([EGREP]) +])]) + + +# AC_PATH_TOOL_PREFIX +# ------------------- +# find a file program which can recognize shared library +AC_DEFUN([AC_PATH_TOOL_PREFIX], +[AC_REQUIRE([AC_PROG_EGREP])dnl +AC_MSG_CHECKING([for $1]) +AC_CACHE_VAL(lt_cv_path_MAGIC_CMD, +[case $MAGIC_CMD in +[[\\/*] | ?:[\\/]*]) + lt_cv_path_MAGIC_CMD="$MAGIC_CMD" # Let the user override the test with a path. + ;; +*) + lt_save_MAGIC_CMD="$MAGIC_CMD" + lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR +dnl $ac_dummy forces splitting on constant user-supplied paths. +dnl POSIX.2 word splitting is done only on the output of word expansions, +dnl not every word. This closes a longstanding sh security hole. + ac_dummy="ifelse([$2], , $PATH, [$2])" + for ac_dir in $ac_dummy; do + IFS="$lt_save_ifs" + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$1; then + lt_cv_path_MAGIC_CMD="$ac_dir/$1" + if test -n "$file_magic_test_file"; then + case $deplibs_check_method in + "file_magic "*) + file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"` + MAGIC_CMD="$lt_cv_path_MAGIC_CMD" + if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null | + $EGREP "$file_magic_regex" > /dev/null; then + : + else + cat <&2 + +*** Warning: the command libtool uses to detect shared libraries, +*** $file_magic_cmd, produces output that libtool cannot recognize. +*** The result is that libtool may fail to recognize shared libraries +*** as such. This will affect the creation of libtool libraries that +*** depend on shared libraries, but programs linked with such libtool +*** libraries will work regardless of this problem. Nevertheless, you +*** may want to report the problem to your system manager and/or to +*** bug-libtool@gnu.org + +EOF + fi ;; + esac + fi + break + fi + done + IFS="$lt_save_ifs" + MAGIC_CMD="$lt_save_MAGIC_CMD" + ;; +esac]) +MAGIC_CMD="$lt_cv_path_MAGIC_CMD" +if test -n "$MAGIC_CMD"; then + AC_MSG_RESULT($MAGIC_CMD) +else + AC_MSG_RESULT(no) +fi +])# AC_PATH_TOOL_PREFIX + + +# AC_PATH_MAGIC +# ------------- +# find a file program which can recognize a shared library +AC_DEFUN([AC_PATH_MAGIC], +[AC_PATH_TOOL_PREFIX(${ac_tool_prefix}file, /usr/bin$PATH_SEPARATOR$PATH) +if test -z "$lt_cv_path_MAGIC_CMD"; then + if test -n "$ac_tool_prefix"; then + AC_PATH_TOOL_PREFIX(file, /usr/bin$PATH_SEPARATOR$PATH) + else + MAGIC_CMD=: + fi +fi +])# AC_PATH_MAGIC + + +# AC_PROG_LD +# ---------- +# find the pathname to the GNU or non-GNU linker +AC_DEFUN([AC_PROG_LD], +[AC_ARG_WITH([gnu-ld], + [AC_HELP_STRING([--with-gnu-ld], + [assume the C compiler uses GNU ld @<:@default=no@:>@])], + [test "$withval" = no || with_gnu_ld=yes], + [with_gnu_ld=no]) +AC_REQUIRE([LT_AC_PROG_SED])dnl +AC_REQUIRE([AC_PROG_CC])dnl +AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([AC_CANONICAL_BUILD])dnl +ac_prog=ld +if test "$GCC" = yes; then + # Check if gcc -print-prog-name=ld gives a path. + AC_MSG_CHECKING([for ld used by $CC]) + case $host in + *-*-mingw*) + # gcc leaves a trailing carriage return which upsets mingw + ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; + *) + ac_prog=`($CC -print-prog-name=ld) 2>&5` ;; + esac + case $ac_prog in + # Accept absolute paths. + [[\\/]]* | ?:[[\\/]]*) + re_direlt='/[[^/]][[^/]]*/\.\./' + # Canonicalize the pathname of ld + ac_prog=`echo $ac_prog| $SED 's%\\\\%/%g'` + while echo $ac_prog | grep "$re_direlt" > /dev/null 2>&1; do + ac_prog=`echo $ac_prog| $SED "s%$re_direlt%/%"` + done + test -z "$LD" && LD="$ac_prog" + ;; + "") + # If it fails, then pretend we aren't using GCC. + ac_prog=ld + ;; + *) + # If it is relative, then search for the first ld in PATH. + with_gnu_ld=unknown + ;; + esac +elif test "$with_gnu_ld" = yes; then + AC_MSG_CHECKING([for GNU ld]) +else + AC_MSG_CHECKING([for non-GNU ld]) +fi +AC_CACHE_VAL(lt_cv_path_LD, +[if test -z "$LD"; then + lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR + for ac_dir in $PATH; do + IFS="$lt_save_ifs" + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then + lt_cv_path_LD="$ac_dir/$ac_prog" + # Check to see if the program is GNU ld. I'd rather use --version, + # but apparently some variants of GNU ld only accept -v. + # Break only if it was the GNU/non-GNU ld that we prefer. + case `"$lt_cv_path_LD" -v 2>&1 &1 /dev/null 2>&1; then + lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' + lt_cv_file_magic_cmd='func_win32_libid' + else + lt_cv_deplibs_check_method='file_magic file format pei*-i386(.*architecture: i386)?' + lt_cv_file_magic_cmd='$OBJDUMP -f' + fi + ;; + +darwin* | rhapsody*) + lt_cv_deplibs_check_method=pass_all + ;; + +freebsd* | dragonfly*) + if echo __ELF__ | $CC -E - | grep __ELF__ > /dev/null; then + case $host_cpu in + i*86 ) + # Not sure whether the presence of OpenBSD here was a mistake. + # Let's accept both of them until this is cleared up. + lt_cv_deplibs_check_method='file_magic (FreeBSD|OpenBSD|DragonFly)/i[[3-9]]86 (compact )?demand paged shared library' + lt_cv_file_magic_cmd=/usr/bin/file + lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*` + ;; + esac + else + lt_cv_deplibs_check_method=pass_all + fi + ;; + +gnu*) + lt_cv_deplibs_check_method=pass_all + ;; + +hpux10.20* | hpux11*) + lt_cv_file_magic_cmd=/usr/bin/file + case $host_cpu in + ia64*) + lt_cv_deplibs_check_method='file_magic (s[[0-9]][[0-9]][[0-9]]|ELF-[[0-9]][[0-9]]) shared object file - IA64' + lt_cv_file_magic_test_file=/usr/lib/hpux32/libc.so + ;; + hppa*64*) + [lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF-[0-9][0-9]) shared object file - PA-RISC [0-9].[0-9]'] + lt_cv_file_magic_test_file=/usr/lib/pa20_64/libc.sl + ;; + *) + lt_cv_deplibs_check_method='file_magic (s[[0-9]][[0-9]][[0-9]]|PA-RISC[[0-9]].[[0-9]]) shared library' + lt_cv_file_magic_test_file=/usr/lib/libc.sl + ;; + esac + ;; + +interix[[3-9]]*) + # PIC code is broken on Interix 3.x, that's why |\.a not |_pic\.a here + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so|\.a)$' + ;; + +irix5* | irix6* | nonstopux*) + case $LD in + *-32|*"-32 ") libmagic=32-bit;; + *-n32|*"-n32 ") libmagic=N32;; + *-64|*"-64 ") libmagic=64-bit;; + *) libmagic=never-match;; + esac + lt_cv_deplibs_check_method=pass_all + ;; + +# This must be Linux ELF. +linux* | k*bsd*-gnu) + lt_cv_deplibs_check_method=pass_all + ;; + +netbsd*) + if echo __ELF__ | $CC -E - | grep __ELF__ > /dev/null; then + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|_pic\.a)$' + else + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so|_pic\.a)$' + fi + ;; + +newos6*) + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (executable|dynamic lib)' + lt_cv_file_magic_cmd=/usr/bin/file + lt_cv_file_magic_test_file=/usr/lib/libnls.so + ;; + +nto-qnx*) + lt_cv_deplibs_check_method=unknown + ;; + +openbsd*) + if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|\.so|_pic\.a)$' + else + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|_pic\.a)$' + fi + ;; + +osf3* | osf4* | osf5*) + lt_cv_deplibs_check_method=pass_all + ;; + +rdos*) + lt_cv_deplibs_check_method=pass_all + ;; + +solaris*) + lt_cv_deplibs_check_method=pass_all + ;; + +sysv4 | sysv4.3*) + case $host_vendor in + motorola) + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (shared object|dynamic lib) M[[0-9]][[0-9]]* Version [[0-9]]' + lt_cv_file_magic_test_file=`echo /usr/lib/libc.so*` + ;; + ncr) + lt_cv_deplibs_check_method=pass_all + ;; + sequent) + lt_cv_file_magic_cmd='/bin/file' + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB (shared object|dynamic lib )' + ;; + sni) + lt_cv_file_magic_cmd='/bin/file' + lt_cv_deplibs_check_method="file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB dynamic lib" + lt_cv_file_magic_test_file=/lib/libc.so + ;; + siemens) + lt_cv_deplibs_check_method=pass_all + ;; + pc) + lt_cv_deplibs_check_method=pass_all + ;; + esac + ;; + +sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + lt_cv_deplibs_check_method=pass_all + ;; +esac +]) +file_magic_cmd=$lt_cv_file_magic_cmd +deplibs_check_method=$lt_cv_deplibs_check_method +test -z "$deplibs_check_method" && deplibs_check_method=unknown +])# AC_DEPLIBS_CHECK_METHOD + + +# AC_PROG_NM +# ---------- +# find the pathname to a BSD-compatible name lister +AC_DEFUN([AC_PROG_NM], +[AC_CACHE_CHECK([for BSD-compatible nm], lt_cv_path_NM, +[if test -n "$NM"; then + # Let the user override the test. + lt_cv_path_NM="$NM" +else + lt_nm_to_check="${ac_tool_prefix}nm" + if test -n "$ac_tool_prefix" && test "$build" = "$host"; then + lt_nm_to_check="$lt_nm_to_check nm" + fi + for lt_tmp_nm in $lt_nm_to_check; do + lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR + for ac_dir in $PATH /usr/ccs/bin/elf /usr/ccs/bin /usr/ucb /bin; do + IFS="$lt_save_ifs" + test -z "$ac_dir" && ac_dir=. + tmp_nm="$ac_dir/$lt_tmp_nm" + if test -f "$tmp_nm" || test -f "$tmp_nm$ac_exeext" ; then + # Check to see if the nm accepts a BSD-compat flag. + # Adding the `sed 1q' prevents false positives on HP-UX, which says: + # nm: unknown option "B" ignored + # Tru64's nm complains that /dev/null is an invalid object file + case `"$tmp_nm" -B /dev/null 2>&1 | sed '1q'` in + */dev/null* | *'Invalid file or object type'*) + lt_cv_path_NM="$tmp_nm -B" + break + ;; + *) + case `"$tmp_nm" -p /dev/null 2>&1 | sed '1q'` in + */dev/null*) + lt_cv_path_NM="$tmp_nm -p" + break + ;; + *) + lt_cv_path_NM=${lt_cv_path_NM="$tmp_nm"} # keep the first match, but + continue # so that we can try to find one that supports BSD flags + ;; + esac + ;; + esac + fi + done + IFS="$lt_save_ifs" + done + test -z "$lt_cv_path_NM" && lt_cv_path_NM=nm +fi]) +NM="$lt_cv_path_NM" +])# AC_PROG_NM + + +# AC_CHECK_LIBM +# ------------- +# check for math library +AC_DEFUN([AC_CHECK_LIBM], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +LIBM= +case $host in +*-*-beos* | *-*-cygwin* | *-*-pw32* | *-*-darwin*) + # These system don't have libm, or don't need it + ;; +*-ncr-sysv4.3*) + AC_CHECK_LIB(mw, _mwvalidcheckl, LIBM="-lmw") + AC_CHECK_LIB(m, cos, LIBM="$LIBM -lm") + ;; +*) + AC_CHECK_LIB(m, cos, LIBM="-lm") + ;; +esac +])# AC_CHECK_LIBM + + +# AC_LIBLTDL_CONVENIENCE([DIRECTORY]) +# ----------------------------------- +# sets LIBLTDL to the link flags for the libltdl convenience library and +# LTDLINCL to the include flags for the libltdl header and adds +# --enable-ltdl-convenience to the configure arguments. Note that +# AC_CONFIG_SUBDIRS is not called here. If DIRECTORY is not provided, +# it is assumed to be `libltdl'. LIBLTDL will be prefixed with +# '${top_builddir}/' and LTDLINCL will be prefixed with '${top_srcdir}/' +# (note the single quotes!). If your package is not flat and you're not +# using automake, define top_builddir and top_srcdir appropriately in +# the Makefiles. +AC_DEFUN([AC_LIBLTDL_CONVENIENCE], +[AC_BEFORE([$0],[AC_LIBTOOL_SETUP])dnl + case $enable_ltdl_convenience in + no) AC_MSG_ERROR([this package needs a convenience libltdl]) ;; + "") enable_ltdl_convenience=yes + ac_configure_args="$ac_configure_args --enable-ltdl-convenience" ;; + esac + LIBLTDL='${top_builddir}/'ifelse($#,1,[$1],['libltdl'])/libltdlc.la + LTDLINCL='-I${top_srcdir}/'ifelse($#,1,[$1],['libltdl']) + # For backwards non-gettext consistent compatibility... + INCLTDL="$LTDLINCL" +])# AC_LIBLTDL_CONVENIENCE + + +# AC_LIBLTDL_INSTALLABLE([DIRECTORY]) +# ----------------------------------- +# sets LIBLTDL to the link flags for the libltdl installable library and +# LTDLINCL to the include flags for the libltdl header and adds +# --enable-ltdl-install to the configure arguments. Note that +# AC_CONFIG_SUBDIRS is not called here. If DIRECTORY is not provided, +# and an installed libltdl is not found, it is assumed to be `libltdl'. +# LIBLTDL will be prefixed with '${top_builddir}/'# and LTDLINCL with +# '${top_srcdir}/' (note the single quotes!). If your package is not +# flat and you're not using automake, define top_builddir and top_srcdir +# appropriately in the Makefiles. +# In the future, this macro may have to be called after AC_PROG_LIBTOOL. +AC_DEFUN([AC_LIBLTDL_INSTALLABLE], +[AC_BEFORE([$0],[AC_LIBTOOL_SETUP])dnl + AC_CHECK_LIB(ltdl, lt_dlinit, + [test x"$enable_ltdl_install" != xyes && enable_ltdl_install=no], + [if test x"$enable_ltdl_install" = xno; then + AC_MSG_WARN([libltdl not installed, but installation disabled]) + else + enable_ltdl_install=yes + fi + ]) + if test x"$enable_ltdl_install" = x"yes"; then + ac_configure_args="$ac_configure_args --enable-ltdl-install" + LIBLTDL='${top_builddir}/'ifelse($#,1,[$1],['libltdl'])/libltdl.la + LTDLINCL='-I${top_srcdir}/'ifelse($#,1,[$1],['libltdl']) + else + ac_configure_args="$ac_configure_args --enable-ltdl-install=no" + LIBLTDL="-lltdl" + LTDLINCL= + fi + # For backwards non-gettext consistent compatibility... + INCLTDL="$LTDLINCL" +])# AC_LIBLTDL_INSTALLABLE + + +# AC_LIBTOOL_CXX +# -------------- +# enable support for C++ libraries +AC_DEFUN([AC_LIBTOOL_CXX], +[AC_REQUIRE([_LT_AC_LANG_CXX]) +])# AC_LIBTOOL_CXX + + +# _LT_AC_LANG_CXX +# --------------- +AC_DEFUN([_LT_AC_LANG_CXX], +[AC_REQUIRE([AC_PROG_CXX]) +AC_REQUIRE([_LT_AC_PROG_CXXCPP]) +_LT_AC_SHELL_INIT([tagnames=${tagnames+${tagnames},}CXX]) +])# _LT_AC_LANG_CXX + +# _LT_AC_PROG_CXXCPP +# ------------------ +AC_DEFUN([_LT_AC_PROG_CXXCPP], +[ +AC_REQUIRE([AC_PROG_CXX]) +if test -n "$CXX" && ( test "X$CXX" != "Xno" && + ( (test "X$CXX" = "Xg++" && `g++ -v >/dev/null 2>&1` ) || + (test "X$CXX" != "Xg++"))) ; then + AC_PROG_CXXCPP +fi +])# _LT_AC_PROG_CXXCPP + +# AC_LIBTOOL_F77 +# -------------- +# enable support for Fortran 77 libraries +AC_DEFUN([AC_LIBTOOL_F77], +[AC_REQUIRE([_LT_AC_LANG_F77]) +])# AC_LIBTOOL_F77 + + +# _LT_AC_LANG_F77 +# --------------- +AC_DEFUN([_LT_AC_LANG_F77], +[AC_REQUIRE([AC_PROG_F77]) +_LT_AC_SHELL_INIT([tagnames=${tagnames+${tagnames},}F77]) +])# _LT_AC_LANG_F77 + + +# AC_LIBTOOL_GCJ +# -------------- +# enable support for GCJ libraries +AC_DEFUN([AC_LIBTOOL_GCJ], +[AC_REQUIRE([_LT_AC_LANG_GCJ]) +])# AC_LIBTOOL_GCJ + + +# _LT_AC_LANG_GCJ +# --------------- +AC_DEFUN([_LT_AC_LANG_GCJ], +[AC_PROVIDE_IFELSE([AC_PROG_GCJ],[], + [AC_PROVIDE_IFELSE([A][M_PROG_GCJ],[], + [AC_PROVIDE_IFELSE([LT_AC_PROG_GCJ],[], + [ifdef([AC_PROG_GCJ],[AC_REQUIRE([AC_PROG_GCJ])], + [ifdef([A][M_PROG_GCJ],[AC_REQUIRE([A][M_PROG_GCJ])], + [AC_REQUIRE([A][C_PROG_GCJ_OR_A][M_PROG_GCJ])])])])])]) +_LT_AC_SHELL_INIT([tagnames=${tagnames+${tagnames},}GCJ]) +])# _LT_AC_LANG_GCJ + + +# AC_LIBTOOL_RC +# ------------- +# enable support for Windows resource files +AC_DEFUN([AC_LIBTOOL_RC], +[AC_REQUIRE([LT_AC_PROG_RC]) +_LT_AC_SHELL_INIT([tagnames=${tagnames+${tagnames},}RC]) +])# AC_LIBTOOL_RC + + +# AC_LIBTOOL_LANG_C_CONFIG +# ------------------------ +# Ensure that the configuration vars for the C compiler are +# suitably defined. Those variables are subsequently used by +# AC_LIBTOOL_CONFIG to write the compiler configuration to `libtool'. +AC_DEFUN([AC_LIBTOOL_LANG_C_CONFIG], [_LT_AC_LANG_C_CONFIG]) +AC_DEFUN([_LT_AC_LANG_C_CONFIG], +[lt_save_CC="$CC" +AC_LANG_PUSH(C) + +# Source file extension for C test sources. +ac_ext=c + +# Object file extension for compiled C test sources. +objext=o +_LT_AC_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="int some_variable = 0;" + +# Code to be used in simple link tests +lt_simple_link_test_code='int main(){return(0);}' + +_LT_AC_SYS_COMPILER + +# save warnings/boilerplate of simple test code +_LT_COMPILER_BOILERPLATE +_LT_LINKER_BOILERPLATE + +AC_LIBTOOL_PROG_COMPILER_NO_RTTI($1) +AC_LIBTOOL_PROG_COMPILER_PIC($1) +AC_LIBTOOL_PROG_CC_C_O($1) +AC_LIBTOOL_SYS_HARD_LINK_LOCKS($1) +AC_LIBTOOL_PROG_LD_SHLIBS($1) +AC_LIBTOOL_SYS_DYNAMIC_LINKER($1) +AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH($1) +AC_LIBTOOL_SYS_LIB_STRIP +AC_LIBTOOL_DLOPEN_SELF + +# Report which library types will actually be built +AC_MSG_CHECKING([if libtool supports shared libraries]) +AC_MSG_RESULT([$can_build_shared]) + +AC_MSG_CHECKING([whether to build shared libraries]) +test "$can_build_shared" = "no" && enable_shared=no + +# On AIX, shared libraries and static libraries use the same namespace, and +# are all built from PIC. +case $host_os in +aix3*) + test "$enable_shared" = yes && enable_static=no + if test -n "$RANLIB"; then + archive_cmds="$archive_cmds~\$RANLIB \$lib" + postinstall_cmds='$RANLIB $lib' + fi + ;; + +aix[[4-9]]*) + if test "$host_cpu" != ia64 && test "$aix_use_runtimelinking" = no ; then + test "$enable_shared" = yes && enable_static=no + fi + ;; +esac +AC_MSG_RESULT([$enable_shared]) + +AC_MSG_CHECKING([whether to build static libraries]) +# Make sure either enable_shared or enable_static is yes. +test "$enable_shared" = yes || enable_static=yes +AC_MSG_RESULT([$enable_static]) + +AC_LIBTOOL_CONFIG($1) + +AC_LANG_POP +CC="$lt_save_CC" +])# AC_LIBTOOL_LANG_C_CONFIG + + +# AC_LIBTOOL_LANG_CXX_CONFIG +# -------------------------- +# Ensure that the configuration vars for the C compiler are +# suitably defined. Those variables are subsequently used by +# AC_LIBTOOL_CONFIG to write the compiler configuration to `libtool'. +AC_DEFUN([AC_LIBTOOL_LANG_CXX_CONFIG], [_LT_AC_LANG_CXX_CONFIG(CXX)]) +AC_DEFUN([_LT_AC_LANG_CXX_CONFIG], +[AC_LANG_PUSH(C++) +AC_REQUIRE([AC_PROG_CXX]) +AC_REQUIRE([_LT_AC_PROG_CXXCPP]) + +_LT_AC_TAGVAR(archive_cmds_need_lc, $1)=no +_LT_AC_TAGVAR(allow_undefined_flag, $1)= +_LT_AC_TAGVAR(always_export_symbols, $1)=no +_LT_AC_TAGVAR(archive_expsym_cmds, $1)= +_LT_AC_TAGVAR(export_dynamic_flag_spec, $1)= +_LT_AC_TAGVAR(hardcode_direct, $1)=no +_LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)= +_LT_AC_TAGVAR(hardcode_libdir_flag_spec_ld, $1)= +_LT_AC_TAGVAR(hardcode_libdir_separator, $1)= +_LT_AC_TAGVAR(hardcode_minus_L, $1)=no +_LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=unsupported +_LT_AC_TAGVAR(hardcode_automatic, $1)=no +_LT_AC_TAGVAR(module_cmds, $1)= +_LT_AC_TAGVAR(module_expsym_cmds, $1)= +_LT_AC_TAGVAR(link_all_deplibs, $1)=unknown +_LT_AC_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_AC_TAGVAR(no_undefined_flag, $1)= +_LT_AC_TAGVAR(whole_archive_flag_spec, $1)= +_LT_AC_TAGVAR(enable_shared_with_static_runtimes, $1)=no + +# Dependencies to place before and after the object being linked: +_LT_AC_TAGVAR(predep_objects, $1)= +_LT_AC_TAGVAR(postdep_objects, $1)= +_LT_AC_TAGVAR(predeps, $1)= +_LT_AC_TAGVAR(postdeps, $1)= +_LT_AC_TAGVAR(compiler_lib_search_path, $1)= +_LT_AC_TAGVAR(compiler_lib_search_dirs, $1)= + +# Source file extension for C++ test sources. +ac_ext=cpp + +# Object file extension for compiled C++ test sources. +objext=o +_LT_AC_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="int some_variable = 0;" + +# Code to be used in simple link tests +lt_simple_link_test_code='int main(int, char *[[]]) { return(0); }' + +# ltmain only uses $CC for tagged configurations so make sure $CC is set. +_LT_AC_SYS_COMPILER + +# save warnings/boilerplate of simple test code +_LT_COMPILER_BOILERPLATE +_LT_LINKER_BOILERPLATE + +# Allow CC to be a program name with arguments. +lt_save_CC=$CC +lt_save_LD=$LD +lt_save_GCC=$GCC +GCC=$GXX +lt_save_with_gnu_ld=$with_gnu_ld +lt_save_path_LD=$lt_cv_path_LD +if test -n "${lt_cv_prog_gnu_ldcxx+set}"; then + lt_cv_prog_gnu_ld=$lt_cv_prog_gnu_ldcxx +else + $as_unset lt_cv_prog_gnu_ld +fi +if test -n "${lt_cv_path_LDCXX+set}"; then + lt_cv_path_LD=$lt_cv_path_LDCXX +else + $as_unset lt_cv_path_LD +fi +test -z "${LDCXX+set}" || LD=$LDCXX +CC=${CXX-"c++"} +compiler=$CC +_LT_AC_TAGVAR(compiler, $1)=$CC +_LT_CC_BASENAME([$compiler]) + +# We don't want -fno-exception wen compiling C++ code, so set the +# no_builtin_flag separately +if test "$GXX" = yes; then + _LT_AC_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -fno-builtin' +else + _LT_AC_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)= +fi + +if test "$GXX" = yes; then + # Set up default GNU C++ configuration + + AC_PROG_LD + + # Check if GNU C++ uses GNU ld as the underlying linker, since the + # archiving commands below assume that GNU ld is being used. + if test "$with_gnu_ld" = yes; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}--rpath ${wl}$libdir' + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic' + + # If archive_cmds runs LD, not CC, wlarc should be empty + # XXX I think wlarc can be eliminated in ltcf-cxx, but I need to + # investigate it a little bit more. (MM) + wlarc='${wl}' + + # ancient GNU ld didn't support --whole-archive et. al. + if eval "`$CC -print-prog-name=ld` --help 2>&1" | \ + grep 'no-whole-archive' > /dev/null; then + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive' + else + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)= + fi + else + with_gnu_ld=no + wlarc= + + # A generic and very simple default shared library creation + # command for GNU C++ for the case where it uses the native + # linker, instead of GNU ld. If possible, this setting should + # overridden to take advantage of the native linker features on + # the platform it is being used on. + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib' + fi + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | grep "\-L"' + +else + GXX=no + with_gnu_ld=no + wlarc= +fi + +# PORTME: fill in a description of your system's C++ link characteristics +AC_MSG_CHECKING([whether the $compiler linker ($LD) supports shared libraries]) +_LT_AC_TAGVAR(ld_shlibs, $1)=yes +case $host_os in + aix3*) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + aix[[4-9]]*) + if test "$host_cpu" = ia64; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + exp_sym_flag='-Bexport' + no_entry_flag="" + else + aix_use_runtimelinking=no + + # Test if we are trying to use run time linking or normal + # AIX style linking. If -brtl is somewhere in LDFLAGS, we + # need to do runtime linking. + case $host_os in aix4.[[23]]|aix4.[[23]].*|aix[[5-9]]*) + for ld_flag in $LDFLAGS; do + case $ld_flag in + *-brtl*) + aix_use_runtimelinking=yes + break + ;; + esac + done + ;; + esac + + exp_sym_flag='-bexport' + no_entry_flag='-bnoentry' + fi + + # When large executables or shared objects are built, AIX ld can + # have problems creating the table of contents. If linking a library + # or program results in "error TOC overflow" add -mminimal-toc to + # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not + # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. + + _LT_AC_TAGVAR(archive_cmds, $1)='' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_AC_TAGVAR(link_all_deplibs, $1)=yes + + if test "$GXX" = yes; then + case $host_os in aix4.[[012]]|aix4.[[012]].*) + # We only want to do this on AIX 4.2 and lower, the check + # below for broken collect2 doesn't work under 4.3+ + collect2name=`${CC} -print-prog-name=collect2` + if test -f "$collect2name" && \ + strings "$collect2name" | grep resolve_lib_name >/dev/null + then + # We have reworked collect2 + : + else + # We have old collect2 + _LT_AC_TAGVAR(hardcode_direct, $1)=unsupported + # It fails to find uninstalled libraries when the uninstalled + # path is not listed in the libpath. Setting hardcode_minus_L + # to unsupported forces relinking + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)= + fi + ;; + esac + shared_flag='-shared' + if test "$aix_use_runtimelinking" = yes; then + shared_flag="$shared_flag "'${wl}-G' + fi + else + # not using gcc + if test "$host_cpu" = ia64; then + # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release + # chokes on -Wl,-G. The following line is correct: + shared_flag='-G' + else + if test "$aix_use_runtimelinking" = yes; then + shared_flag='${wl}-G' + else + shared_flag='${wl}-bM:SRE' + fi + fi + fi + + # It seems that -bexpall does not export symbols beginning with + # underscore (_), so it is better to generate a list of symbols to export. + _LT_AC_TAGVAR(always_export_symbols, $1)=yes + if test "$aix_use_runtimelinking" = yes; then + # Warning - without using the other runtime loading flags (-brtl), + # -berok will link without error, but may produce a broken library. + _LT_AC_TAGVAR(allow_undefined_flag, $1)='-berok' + # Determine the default libpath from the value encoded in an empty executable. + _LT_AC_SYS_LIBPATH_AIX + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-blibpath:$libdir:'"$aix_libpath" + + _LT_AC_TAGVAR(archive_expsym_cmds, $1)="\$CC"' -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags `if test "x${allow_undefined_flag}" != "x"; then echo "${wl}${allow_undefined_flag}"; else :; fi` '"\${wl}$exp_sym_flag:\$export_symbols $shared_flag" + else + if test "$host_cpu" = ia64; then + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-R $libdir:/usr/lib:/lib' + _LT_AC_TAGVAR(allow_undefined_flag, $1)="-z nodefs" + _LT_AC_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags ${wl}${allow_undefined_flag} '"\${wl}$exp_sym_flag:\$export_symbols" + else + # Determine the default libpath from the value encoded in an empty executable. + _LT_AC_SYS_LIBPATH_AIX + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-blibpath:$libdir:'"$aix_libpath" + # Warning - without using the other run time loading flags, + # -berok will link without error, but may produce a broken library. + _LT_AC_TAGVAR(no_undefined_flag, $1)=' ${wl}-bernotok' + _LT_AC_TAGVAR(allow_undefined_flag, $1)=' ${wl}-berok' + # Exported symbols can be pulled into shared objects from archives + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)='$convenience' + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=yes + # This is similar to how AIX traditionally builds its shared libraries. + _LT_AC_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs ${wl}-bnoentry $compiler_flags ${wl}-bE:$export_symbols${allow_undefined_flag}~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$soname' + fi + fi + ;; + + beos*) + if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + _LT_AC_TAGVAR(allow_undefined_flag, $1)=unsupported + # Joseph Beckenbach says some releases of gcc + # support --undefined. This deserves some investigation. FIXME + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -nostart $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + else + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + chorus*) + case $cc_basename in + *) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + cygwin* | mingw* | pw32*) + # _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1) is actually meaningless, + # as there is no search path for DLLs. + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_AC_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_AC_TAGVAR(always_export_symbols, $1)=no + _LT_AC_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + + if $LD --help 2>&1 | grep 'auto-import' > /dev/null; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + # If the export-symbols file already is a .def file (1st line + # is EXPORTS), use it as is; otherwise, prepend... + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then + cp $export_symbols $output_objdir/$soname.def; + else + echo EXPORTS > $output_objdir/$soname.def; + cat $export_symbols >> $output_objdir/$soname.def; + fi~ + $CC -shared -nostdlib $output_objdir/$soname.def $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + else + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; + darwin* | rhapsody*) + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_AC_TAGVAR(hardcode_direct, $1)=no + _LT_AC_TAGVAR(hardcode_automatic, $1)=yes + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=unsupported + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)='' + _LT_AC_TAGVAR(link_all_deplibs, $1)=yes + _LT_AC_TAGVAR(allow_undefined_flag, $1)="$_lt_dar_allow_undefined" + if test "$GXX" = yes ; then + output_verbose_link_cmd='echo' + _LT_AC_TAGVAR(archive_cmds, $1)="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod${_lt_dsymutil}" + _LT_AC_TAGVAR(module_cmds, $1)="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags${_lt_dsymutil}" + _LT_AC_TAGVAR(archive_expsym_cmds, $1)="sed 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring ${_lt_dar_single_mod}${_lt_dar_export_syms}${_lt_dsymutil}" + _LT_AC_TAGVAR(module_expsym_cmds, $1)="sed -e 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags${_lt_dar_export_syms}${_lt_dsymutil}" + if test "$lt_cv_apple_cc_single_mod" != "yes"; then + _LT_AC_TAGVAR(archive_cmds, $1)="\$CC -r -keep_private_externs -nostdlib -o \${lib}-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \${lib}-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring${_lt_dsymutil}" + _LT_AC_TAGVAR(archive_expsym_cmds, $1)="sed 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC -r -keep_private_externs -nostdlib -o \${lib}-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \${lib}-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring${_lt_dar_export_syms}${_lt_dsymutil}" + fi + else + case $cc_basename in + xlc*) + output_verbose_link_cmd='echo' + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -qmkshrobj ${wl}-single_module $allow_undefined_flag -o $lib $libobjs $deplibs $compiler_flags ${wl}-install_name ${wl}`echo $rpath/$soname` $xlcverstring' + _LT_AC_TAGVAR(module_cmds, $1)='$CC $allow_undefined_flag -o $lib -bundle $libobjs $deplibs$compiler_flags' + # Don't fix this by using the ld -exported_symbols_list flag, it doesn't exist in older darwin lds + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='sed -e "s,#.*,," -e "s,^[ ]*,," -e "s,^\(..*\),_&," < $export_symbols > $output_objdir/${libname}-symbols.expsym~$CC -qmkshrobj ${wl}-single_module $allow_undefined_flag -o $lib $libobjs $deplibs $compiler_flags ${wl}-install_name ${wl}$rpath/$soname $xlcverstring~nmedit -s $output_objdir/${libname}-symbols.expsym ${lib}' + _LT_AC_TAGVAR(module_expsym_cmds, $1)='sed -e "s,#.*,," -e "s,^[ ]*,," -e "s,^\(..*\),_&," < $export_symbols > $output_objdir/${libname}-symbols.expsym~$CC $allow_undefined_flag -o $lib -bundle $libobjs $deplibs$compiler_flags~nmedit -s $output_objdir/${libname}-symbols.expsym ${lib}' + ;; + *) + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + esac + fi + ;; + + dgux*) + case $cc_basename in + ec++*) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + ghcx*) + # Green Hills C++ Compiler + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + freebsd[[12]]*) + # C++ shared libraries reported to be fairly broken before switch to ELF + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + freebsd-elf*) + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=no + ;; + freebsd* | dragonfly*) + # FreeBSD 3 and later use GNU C++ and GNU ld with standard ELF + # conventions + _LT_AC_TAGVAR(ld_shlibs, $1)=yes + ;; + gnu*) + ;; + hpux9*) + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes # Not in the search PATH, + # but as the default + # location of the library. + + case $cc_basename in + CC*) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + aCC*) + _LT_AC_TAGVAR(archive_cmds, $1)='$rm $output_objdir/$soname~$CC -b ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib' + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | grep "[[-]]L"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; echo $list' + ;; + *) + if test "$GXX" = yes; then + _LT_AC_TAGVAR(archive_cmds, $1)='$rm $output_objdir/$soname~$CC -shared -nostdlib -fPIC ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib' + else + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + hpux10*|hpux11*) + if test $with_gnu_ld = no; then + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + + case $host_cpu in + hppa*64*|ia64*) ;; + *) + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E' + ;; + esac + fi + case $host_cpu in + hppa*64*|ia64*) + _LT_AC_TAGVAR(hardcode_direct, $1)=no + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + *) + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes # Not in the search PATH, + # but as the default + # location of the library. + ;; + esac + + case $cc_basename in + CC*) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + aCC*) + case $host_cpu in + hppa*64*) + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + ia64*) + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + *) + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + esac + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | grep "\-L"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; echo $list' + ;; + *) + if test "$GXX" = yes; then + if test $with_gnu_ld = no; then + case $host_cpu in + hppa*64*) + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib -fPIC ${wl}+h ${wl}$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + ia64*) + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib -fPIC ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + *) + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib -fPIC ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + esac + fi + else + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + interix[[3-9]]*) + _LT_AC_TAGVAR(hardcode_direct, $1)=no + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir' + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E' + # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. + # Instead, shared libraries are loaded at an image base (0x10000000 by + # default) and relocated if they conflict, which is a slow very memory + # consuming and fragmenting process. To avoid this, we pick a random, + # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link + # time. Moving up from 0x10000000 also allows more sbrk(2) space. + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='sed "s,^,_," $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--retain-symbols-file,$output_objdir/$soname.expsym ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + ;; + irix5* | irix6*) + case $cc_basename in + CC*) + # SGI C++ + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -all -multigot $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib' + + # Archives containing C++ object files must be created using + # "CC -ar", where "CC" is the IRIX C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + _LT_AC_TAGVAR(old_archive_cmds, $1)='$CC -ar -WR,-u -o $oldlib $oldobjs' + ;; + *) + if test "$GXX" = yes; then + if test "$with_gnu_ld" = no; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + else + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` -o $lib' + fi + fi + _LT_AC_TAGVAR(link_all_deplibs, $1)=yes + ;; + esac + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + ;; + linux* | k*bsd*-gnu) + case $cc_basename in + KCC*) + # Kuck and Associates, Inc. (KAI) C++ Compiler + + # KCC will only create a shared library if the output file + # ends with ".so" (or ".sl" for HP-UX), so rename the library + # to its proper name (with version) after linking. + _LT_AC_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\${tempext}\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\${tempext}\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib ${wl}-retain-symbols-file,$export_symbols; mv \$templib $lib' + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 | grep "ld"`; rm -f libconftest$shared_ext; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; echo $list' + + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}--rpath,$libdir' + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic' + + # Archives containing C++ object files must be created using + # "CC -Bstatic", where "CC" is the KAI C++ compiler. + _LT_AC_TAGVAR(old_archive_cmds, $1)='$CC -Bstatic -o $oldlib $oldobjs' + ;; + icpc*) + # Intel C++ + with_gnu_ld=yes + # version 8.0 and above of icpc choke on multiply defined symbols + # if we add $predep_objects and $postdep_objects, however 7.1 and + # earlier do not add the objects themselves. + case `$CC -V 2>&1` in + *"Version 7."*) + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + ;; + *) # Version 8.0 or newer + tmp_idyn= + case $host_cpu in + ia64*) tmp_idyn=' -i_dynamic';; + esac + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + ;; + esac + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir' + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic' + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive$convenience ${wl}--no-whole-archive' + ;; + pgCC* | pgcpp*) + # Portland Group C++ compiler + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname ${wl}-retain-symbols-file ${wl}$export_symbols -o $lib' + + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}--rpath ${wl}$libdir' + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic' + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; $echo \"$new_convenience\"` ${wl}--no-whole-archive' + ;; + cxx*) + # Compaq C++ + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname -o $lib ${wl}-retain-symbols-file $wl$export_symbols' + + runpath_var=LD_RUN_PATH + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | grep "ld"`; templist=`echo $templist | $SED "s/\(^.*ld.*\)\( .*ld .*$\)/\1/"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; echo $list' + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) + # Sun C++ 5.9 + _LT_AC_TAGVAR(no_undefined_flag, $1)=' -zdefs' + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -G${allow_undefined_flag} -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$CC -G${allow_undefined_flag} -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-retain-symbols-file ${wl}$export_symbols' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; $echo \"$new_convenience\"` ${wl}--no-whole-archive' + + # Not sure whether something based on + # $CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 + # would be better. + output_verbose_link_cmd='echo' + + # Archives containing C++ object files must be created using + # "CC -xar", where "CC" is the Sun C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + _LT_AC_TAGVAR(old_archive_cmds, $1)='$CC -xar -o $oldlib $oldobjs' + ;; + esac + ;; + esac + ;; + lynxos*) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + m88k*) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + mvs*) + case $cc_basename in + cxx*) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + netbsd*) + if echo __ELF__ | $CC -E - | grep __ELF__ >/dev/null; then + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $predep_objects $libobjs $deplibs $postdep_objects $linker_flags' + wlarc= + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + fi + # Workaround some broken pre-1.5 toolchains + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | grep conftest.$objext | $SED -e "s:-lgcc -lc -lgcc::"' + ;; + openbsd2*) + # C++ shared libraries are fairly broken + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + openbsd*) + if test -f /usr/libexec/ld.so; then + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir' + if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-retain-symbols-file,$export_symbols -o $lib' + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E' + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive' + fi + output_verbose_link_cmd='echo' + else + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; + osf3*) + case $cc_basename in + KCC*) + # Kuck and Associates, Inc. (KAI) C++ Compiler + + # KCC will only create a shared library if the output file + # ends with ".so" (or ".sl" for HP-UX), so rename the library + # to its proper name (with version) after linking. + _LT_AC_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\${tempext}\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib' + + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + + # Archives containing C++ object files must be created using + # "CC -Bstatic", where "CC" is the KAI C++ compiler. + _LT_AC_TAGVAR(old_archive_cmds, $1)='$CC -Bstatic -o $oldlib $oldobjs' + + ;; + RCC*) + # Rational C++ 2.4.1 + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + cxx*) + _LT_AC_TAGVAR(allow_undefined_flag, $1)=' ${wl}-expect_unresolved ${wl}\*' + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $soname `test -n "$verstring" && echo ${wl}-set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib' + + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | grep "ld" | grep -v "ld:"`; templist=`echo $templist | $SED "s/\(^.*ld.*\)\( .*ld.*$\)/\1/"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; echo $list' + ;; + *) + if test "$GXX" = yes && test "$with_gnu_ld" = no; then + _LT_AC_TAGVAR(allow_undefined_flag, $1)=' ${wl}-expect_unresolved ${wl}\*' + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib ${allow_undefined_flag} $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | grep "\-L"' + + else + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + osf4* | osf5*) + case $cc_basename in + KCC*) + # Kuck and Associates, Inc. (KAI) C++ Compiler + + # KCC will only create a shared library if the output file + # ends with ".so" (or ".sl" for HP-UX), so rename the library + # to its proper name (with version) after linking. + _LT_AC_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\${tempext}\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib' + + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + + # Archives containing C++ object files must be created using + # the KAI C++ compiler. + _LT_AC_TAGVAR(old_archive_cmds, $1)='$CC -o $oldlib $oldobjs' + ;; + RCC*) + # Rational C++ 2.4.1 + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + cxx*) + _LT_AC_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done~ + echo "-hidden">> $lib.exp~ + $CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname -Wl,-input -Wl,$lib.exp `test -n "$verstring" && echo -set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib~ + $rm $lib.exp' + + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | grep "ld" | grep -v "ld:"`; templist=`echo $templist | $SED "s/\(^.*ld.*\)\( .*ld.*$\)/\1/"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; echo $list' + ;; + *) + if test "$GXX" = yes && test "$with_gnu_ld" = no; then + _LT_AC_TAGVAR(allow_undefined_flag, $1)=' ${wl}-expect_unresolved ${wl}\*' + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib ${allow_undefined_flag} $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-msym ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | grep "\-L"' + + else + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + psos*) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + sunos4*) + case $cc_basename in + CC*) + # Sun C++ 4.x + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + lcc*) + # Lucid + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + solaris*) + case $cc_basename in + CC*) + # Sun C++ 4.2, 5.x and Centerline C++ + _LT_AC_TAGVAR(archive_cmds_need_lc,$1)=yes + _LT_AC_TAGVAR(no_undefined_flag, $1)=' -zdefs' + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -G${allow_undefined_flag} -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~$echo "local: *; };" >> $lib.exp~ + $CC -G${allow_undefined_flag} ${wl}-M ${wl}$lib.exp -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$rm $lib.exp' + + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + case $host_os in + solaris2.[[0-5]] | solaris2.[[0-5]].*) ;; + *) + # The compiler driver will combine and reorder linker options, + # but understands `-z linker_flag'. + # Supported since Solaris 2.6 (maybe 2.5.1?) + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)='-z allextract$convenience -z defaultextract' + ;; + esac + _LT_AC_TAGVAR(link_all_deplibs, $1)=yes + + output_verbose_link_cmd='echo' + + # Archives containing C++ object files must be created using + # "CC -xar", where "CC" is the Sun C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + _LT_AC_TAGVAR(old_archive_cmds, $1)='$CC -xar -o $oldlib $oldobjs' + ;; + gcx*) + # Green Hills C++ Compiler + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-h $wl$soname -o $lib' + + # The C++ compiler must be used to create the archive. + _LT_AC_TAGVAR(old_archive_cmds, $1)='$CC $LDFLAGS -archive -o $oldlib $oldobjs' + ;; + *) + # GNU C++ compiler with Solaris linker + if test "$GXX" = yes && test "$with_gnu_ld" = no; then + _LT_AC_TAGVAR(no_undefined_flag, $1)=' ${wl}-z ${wl}defs' + if $CC --version | grep -v '^2\.7' > /dev/null; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $LDFLAGS $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-h $wl$soname -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~$echo "local: *; };" >> $lib.exp~ + $CC -shared -nostdlib ${wl}-M $wl$lib.exp -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$rm $lib.exp' + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd="$CC -shared $CFLAGS -v conftest.$objext 2>&1 | grep \"\-L\"" + else + # g++ 2.7 appears to require `-G' NOT `-shared' on this + # platform. + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -G -nostdlib $LDFLAGS $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-h $wl$soname -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~$echo "local: *; };" >> $lib.exp~ + $CC -G -nostdlib ${wl}-M $wl$lib.exp -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$rm $lib.exp' + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd="$CC -G $CFLAGS -v conftest.$objext 2>&1 | grep \"\-L\"" + fi + + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-R $wl$libdir' + case $host_os in + solaris2.[[0-5]] | solaris2.[[0-5]].*) ;; + *) + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)='${wl}-z ${wl}allextract$convenience ${wl}-z ${wl}defaultextract' + ;; + esac + fi + ;; + esac + ;; + sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[[01]].[[10]]* | unixware7* | sco3.2v5.0.[[024]]*) + _LT_AC_TAGVAR(no_undefined_flag, $1)='${wl}-z,text' + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + runpath_var='LD_RUN_PATH' + + case $cc_basename in + CC*) + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + ;; + sysv5* | sco3.2v5* | sco5v6*) + # Note: We can NOT use -z defs as we might desire, because we do not + # link with -lc, and that would cause any symbols used from libc to + # always be unresolved, which means just about no library would + # ever link correctly. If we're not using GNU ld we use -z text + # though, which does catch some bad symbols but isn't as heavy-handed + # as -z defs. + # For security reasons, it is highly recommended that you always + # use absolute paths for naming shared libraries, and exclude the + # DT_RUNPATH tag from executables and libraries. But doing so + # requires that you compile everything twice, which is a pain. + # So that behaviour is only enabled if SCOABSPATH is set to a + # non-empty value in the environment. Most likely only useful for + # creating official distributions of packages. + # This is a hack until libtool officially supports absolute path + # names for shared libraries. + _LT_AC_TAGVAR(no_undefined_flag, $1)='${wl}-z,text' + _LT_AC_TAGVAR(allow_undefined_flag, $1)='${wl}-z,nodefs' + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='`test -z "$SCOABSPATH" && echo ${wl}-R,$libdir`' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_AC_TAGVAR(link_all_deplibs, $1)=yes + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-Bexport' + runpath_var='LD_RUN_PATH' + + case $cc_basename in + CC*) + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -G ${wl}-h,\${SCOABSPATH:+${install_libdir}/}$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,\${SCOABSPATH:+${install_libdir}/}$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared ${wl}-h,\${SCOABSPATH:+${install_libdir}/}$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,\${SCOABSPATH:+${install_libdir}/}$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + ;; + tandem*) + case $cc_basename in + NCC*) + # NonStop-UX NCC 3.20 + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + vxworks*) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; +esac +AC_MSG_RESULT([$_LT_AC_TAGVAR(ld_shlibs, $1)]) +test "$_LT_AC_TAGVAR(ld_shlibs, $1)" = no && can_build_shared=no + +_LT_AC_TAGVAR(GCC, $1)="$GXX" +_LT_AC_TAGVAR(LD, $1)="$LD" + +AC_LIBTOOL_POSTDEP_PREDEP($1) +AC_LIBTOOL_PROG_COMPILER_PIC($1) +AC_LIBTOOL_PROG_CC_C_O($1) +AC_LIBTOOL_SYS_HARD_LINK_LOCKS($1) +AC_LIBTOOL_PROG_LD_SHLIBS($1) +AC_LIBTOOL_SYS_DYNAMIC_LINKER($1) +AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH($1) + +AC_LIBTOOL_CONFIG($1) + +AC_LANG_POP +CC=$lt_save_CC +LDCXX=$LD +LD=$lt_save_LD +GCC=$lt_save_GCC +with_gnu_ldcxx=$with_gnu_ld +with_gnu_ld=$lt_save_with_gnu_ld +lt_cv_path_LDCXX=$lt_cv_path_LD +lt_cv_path_LD=$lt_save_path_LD +lt_cv_prog_gnu_ldcxx=$lt_cv_prog_gnu_ld +lt_cv_prog_gnu_ld=$lt_save_with_gnu_ld +])# AC_LIBTOOL_LANG_CXX_CONFIG + +# AC_LIBTOOL_POSTDEP_PREDEP([TAGNAME]) +# ------------------------------------ +# Figure out "hidden" library dependencies from verbose +# compiler output when linking a shared library. +# Parse the compiler output and extract the necessary +# objects, libraries and library flags. +AC_DEFUN([AC_LIBTOOL_POSTDEP_PREDEP], +[AC_REQUIRE([LT_AC_PROG_SED])dnl +dnl we can't use the lt_simple_compile_test_code here, +dnl because it contains code intended for an executable, +dnl not a library. It's possible we should let each +dnl tag define a new lt_????_link_test_code variable, +dnl but it's only used here... +ifelse([$1],[],[cat > conftest.$ac_ext < conftest.$ac_ext < conftest.$ac_ext < conftest.$ac_ext <&1 | sed 5q` in + *Sun\ C*) + # Sun C++ 5.9 + # + # The more standards-conforming stlport4 library is + # incompatible with the Cstd library. Avoid specifying + # it if it's in CXXFLAGS. Ignore libCrun as + # -library=stlport4 depends on it. + case " $CXX $CXXFLAGS " in + *" -library=stlport4 "*) + solaris_use_stlport4=yes + ;; + esac + if test "$solaris_use_stlport4" != yes; then + _LT_AC_TAGVAR(postdeps,$1)='-library=Cstd -library=Crun' + fi + ;; + esac + ;; + +solaris*) + case $cc_basename in + CC*) + # The more standards-conforming stlport4 library is + # incompatible with the Cstd library. Avoid specifying + # it if it's in CXXFLAGS. Ignore libCrun as + # -library=stlport4 depends on it. + case " $CXX $CXXFLAGS " in + *" -library=stlport4 "*) + solaris_use_stlport4=yes + ;; + esac + + # Adding this requires a known-good setup of shared libraries for + # Sun compiler versions before 5.6, else PIC objects from an old + # archive will be linked into the output, leading to subtle bugs. + if test "$solaris_use_stlport4" != yes; then + _LT_AC_TAGVAR(postdeps,$1)='-library=Cstd -library=Crun' + fi + ;; + esac + ;; +esac +]) +case " $_LT_AC_TAGVAR(postdeps, $1) " in +*" -lc "*) _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=no ;; +esac +])# AC_LIBTOOL_POSTDEP_PREDEP + +# AC_LIBTOOL_LANG_F77_CONFIG +# -------------------------- +# Ensure that the configuration vars for the C compiler are +# suitably defined. Those variables are subsequently used by +# AC_LIBTOOL_CONFIG to write the compiler configuration to `libtool'. +AC_DEFUN([AC_LIBTOOL_LANG_F77_CONFIG], [_LT_AC_LANG_F77_CONFIG(F77)]) +AC_DEFUN([_LT_AC_LANG_F77_CONFIG], +[AC_REQUIRE([AC_PROG_F77]) +AC_LANG_PUSH(Fortran 77) + +_LT_AC_TAGVAR(archive_cmds_need_lc, $1)=no +_LT_AC_TAGVAR(allow_undefined_flag, $1)= +_LT_AC_TAGVAR(always_export_symbols, $1)=no +_LT_AC_TAGVAR(archive_expsym_cmds, $1)= +_LT_AC_TAGVAR(export_dynamic_flag_spec, $1)= +_LT_AC_TAGVAR(hardcode_direct, $1)=no +_LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)= +_LT_AC_TAGVAR(hardcode_libdir_flag_spec_ld, $1)= +_LT_AC_TAGVAR(hardcode_libdir_separator, $1)= +_LT_AC_TAGVAR(hardcode_minus_L, $1)=no +_LT_AC_TAGVAR(hardcode_automatic, $1)=no +_LT_AC_TAGVAR(module_cmds, $1)= +_LT_AC_TAGVAR(module_expsym_cmds, $1)= +_LT_AC_TAGVAR(link_all_deplibs, $1)=unknown +_LT_AC_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_AC_TAGVAR(no_undefined_flag, $1)= +_LT_AC_TAGVAR(whole_archive_flag_spec, $1)= +_LT_AC_TAGVAR(enable_shared_with_static_runtimes, $1)=no + +# Source file extension for f77 test sources. +ac_ext=f + +# Object file extension for compiled f77 test sources. +objext=o +_LT_AC_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="\ + subroutine t + return + end +" + +# Code to be used in simple link tests +lt_simple_link_test_code="\ + program t + end +" + +# ltmain only uses $CC for tagged configurations so make sure $CC is set. +_LT_AC_SYS_COMPILER + +# save warnings/boilerplate of simple test code +_LT_COMPILER_BOILERPLATE +_LT_LINKER_BOILERPLATE + +# Allow CC to be a program name with arguments. +lt_save_CC="$CC" +CC=${F77-"f77"} +compiler=$CC +_LT_AC_TAGVAR(compiler, $1)=$CC +_LT_CC_BASENAME([$compiler]) + +AC_MSG_CHECKING([if libtool supports shared libraries]) +AC_MSG_RESULT([$can_build_shared]) + +AC_MSG_CHECKING([whether to build shared libraries]) +test "$can_build_shared" = "no" && enable_shared=no + +# On AIX, shared libraries and static libraries use the same namespace, and +# are all built from PIC. +case $host_os in +aix3*) + test "$enable_shared" = yes && enable_static=no + if test -n "$RANLIB"; then + archive_cmds="$archive_cmds~\$RANLIB \$lib" + postinstall_cmds='$RANLIB $lib' + fi + ;; +aix[[4-9]]*) + if test "$host_cpu" != ia64 && test "$aix_use_runtimelinking" = no ; then + test "$enable_shared" = yes && enable_static=no + fi + ;; +esac +AC_MSG_RESULT([$enable_shared]) + +AC_MSG_CHECKING([whether to build static libraries]) +# Make sure either enable_shared or enable_static is yes. +test "$enable_shared" = yes || enable_static=yes +AC_MSG_RESULT([$enable_static]) + +_LT_AC_TAGVAR(GCC, $1)="$G77" +_LT_AC_TAGVAR(LD, $1)="$LD" + +AC_LIBTOOL_PROG_COMPILER_PIC($1) +AC_LIBTOOL_PROG_CC_C_O($1) +AC_LIBTOOL_SYS_HARD_LINK_LOCKS($1) +AC_LIBTOOL_PROG_LD_SHLIBS($1) +AC_LIBTOOL_SYS_DYNAMIC_LINKER($1) +AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH($1) + +AC_LIBTOOL_CONFIG($1) + +AC_LANG_POP +CC="$lt_save_CC" +])# AC_LIBTOOL_LANG_F77_CONFIG + + +# AC_LIBTOOL_LANG_GCJ_CONFIG +# -------------------------- +# Ensure that the configuration vars for the C compiler are +# suitably defined. Those variables are subsequently used by +# AC_LIBTOOL_CONFIG to write the compiler configuration to `libtool'. +AC_DEFUN([AC_LIBTOOL_LANG_GCJ_CONFIG], [_LT_AC_LANG_GCJ_CONFIG(GCJ)]) +AC_DEFUN([_LT_AC_LANG_GCJ_CONFIG], +[AC_LANG_SAVE + +# Source file extension for Java test sources. +ac_ext=java + +# Object file extension for compiled Java test sources. +objext=o +_LT_AC_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="class foo {}" + +# Code to be used in simple link tests +lt_simple_link_test_code='public class conftest { public static void main(String[[]] argv) {}; }' + +# ltmain only uses $CC for tagged configurations so make sure $CC is set. +_LT_AC_SYS_COMPILER + +# save warnings/boilerplate of simple test code +_LT_COMPILER_BOILERPLATE +_LT_LINKER_BOILERPLATE + +# Allow CC to be a program name with arguments. +lt_save_CC="$CC" +CC=${GCJ-"gcj"} +compiler=$CC +_LT_AC_TAGVAR(compiler, $1)=$CC +_LT_CC_BASENAME([$compiler]) + +# GCJ did not exist at the time GCC didn't implicitly link libc in. +_LT_AC_TAGVAR(archive_cmds_need_lc, $1)=no + +_LT_AC_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds + +AC_LIBTOOL_PROG_COMPILER_NO_RTTI($1) +AC_LIBTOOL_PROG_COMPILER_PIC($1) +AC_LIBTOOL_PROG_CC_C_O($1) +AC_LIBTOOL_SYS_HARD_LINK_LOCKS($1) +AC_LIBTOOL_PROG_LD_SHLIBS($1) +AC_LIBTOOL_SYS_DYNAMIC_LINKER($1) +AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH($1) + +AC_LIBTOOL_CONFIG($1) + +AC_LANG_RESTORE +CC="$lt_save_CC" +])# AC_LIBTOOL_LANG_GCJ_CONFIG + + +# AC_LIBTOOL_LANG_RC_CONFIG +# ------------------------- +# Ensure that the configuration vars for the Windows resource compiler are +# suitably defined. Those variables are subsequently used by +# AC_LIBTOOL_CONFIG to write the compiler configuration to `libtool'. +AC_DEFUN([AC_LIBTOOL_LANG_RC_CONFIG], [_LT_AC_LANG_RC_CONFIG(RC)]) +AC_DEFUN([_LT_AC_LANG_RC_CONFIG], +[AC_LANG_SAVE + +# Source file extension for RC test sources. +ac_ext=rc + +# Object file extension for compiled RC test sources. +objext=o +_LT_AC_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code='sample MENU { MENUITEM "&Soup", 100, CHECKED }' + +# Code to be used in simple link tests +lt_simple_link_test_code="$lt_simple_compile_test_code" + +# ltmain only uses $CC for tagged configurations so make sure $CC is set. +_LT_AC_SYS_COMPILER + +# save warnings/boilerplate of simple test code +_LT_COMPILER_BOILERPLATE +_LT_LINKER_BOILERPLATE + +# Allow CC to be a program name with arguments. +lt_save_CC="$CC" +CC=${RC-"windres"} +compiler=$CC +_LT_AC_TAGVAR(compiler, $1)=$CC +_LT_CC_BASENAME([$compiler]) +_LT_AC_TAGVAR(lt_cv_prog_compiler_c_o, $1)=yes + +AC_LIBTOOL_CONFIG($1) + +AC_LANG_RESTORE +CC="$lt_save_CC" +])# AC_LIBTOOL_LANG_RC_CONFIG + + +# AC_LIBTOOL_CONFIG([TAGNAME]) +# ---------------------------- +# If TAGNAME is not passed, then create an initial libtool script +# with a default configuration from the untagged config vars. Otherwise +# add code to config.status for appending the configuration named by +# TAGNAME from the matching tagged config vars. +AC_DEFUN([AC_LIBTOOL_CONFIG], +[# The else clause should only fire when bootstrapping the +# libtool distribution, otherwise you forgot to ship ltmain.sh +# with your package, and you will get complaints that there are +# no rules to generate ltmain.sh. +if test -f "$ltmain"; then + # See if we are running on zsh, and set the options which allow our commands through + # without removal of \ escapes. + if test -n "${ZSH_VERSION+set}" ; then + setopt NO_GLOB_SUBST + fi + # Now quote all the things that may contain metacharacters while being + # careful not to overquote the AC_SUBSTed values. We take copies of the + # variables and quote the copies for generation of the libtool script. + for var in echo old_CC old_CFLAGS AR AR_FLAGS EGREP RANLIB LN_S LTCC LTCFLAGS NM \ + SED SHELL STRIP \ + libname_spec library_names_spec soname_spec extract_expsyms_cmds \ + old_striplib striplib file_magic_cmd finish_cmds finish_eval \ + deplibs_check_method reload_flag reload_cmds need_locks \ + lt_cv_sys_global_symbol_pipe lt_cv_sys_global_symbol_to_cdecl \ + lt_cv_sys_global_symbol_to_c_name_address \ + sys_lib_search_path_spec sys_lib_dlsearch_path_spec \ + old_postinstall_cmds old_postuninstall_cmds \ + _LT_AC_TAGVAR(compiler, $1) \ + _LT_AC_TAGVAR(CC, $1) \ + _LT_AC_TAGVAR(LD, $1) \ + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1) \ + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1) \ + _LT_AC_TAGVAR(lt_prog_compiler_static, $1) \ + _LT_AC_TAGVAR(lt_prog_compiler_no_builtin_flag, $1) \ + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1) \ + _LT_AC_TAGVAR(thread_safe_flag_spec, $1) \ + _LT_AC_TAGVAR(whole_archive_flag_spec, $1) \ + _LT_AC_TAGVAR(enable_shared_with_static_runtimes, $1) \ + _LT_AC_TAGVAR(old_archive_cmds, $1) \ + _LT_AC_TAGVAR(old_archive_from_new_cmds, $1) \ + _LT_AC_TAGVAR(predep_objects, $1) \ + _LT_AC_TAGVAR(postdep_objects, $1) \ + _LT_AC_TAGVAR(predeps, $1) \ + _LT_AC_TAGVAR(postdeps, $1) \ + _LT_AC_TAGVAR(compiler_lib_search_path, $1) \ + _LT_AC_TAGVAR(compiler_lib_search_dirs, $1) \ + _LT_AC_TAGVAR(archive_cmds, $1) \ + _LT_AC_TAGVAR(archive_expsym_cmds, $1) \ + _LT_AC_TAGVAR(postinstall_cmds, $1) \ + _LT_AC_TAGVAR(postuninstall_cmds, $1) \ + _LT_AC_TAGVAR(old_archive_from_expsyms_cmds, $1) \ + _LT_AC_TAGVAR(allow_undefined_flag, $1) \ + _LT_AC_TAGVAR(no_undefined_flag, $1) \ + _LT_AC_TAGVAR(export_symbols_cmds, $1) \ + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1) \ + _LT_AC_TAGVAR(hardcode_libdir_flag_spec_ld, $1) \ + _LT_AC_TAGVAR(hardcode_libdir_separator, $1) \ + _LT_AC_TAGVAR(hardcode_automatic, $1) \ + _LT_AC_TAGVAR(module_cmds, $1) \ + _LT_AC_TAGVAR(module_expsym_cmds, $1) \ + _LT_AC_TAGVAR(lt_cv_prog_compiler_c_o, $1) \ + _LT_AC_TAGVAR(fix_srcfile_path, $1) \ + _LT_AC_TAGVAR(exclude_expsyms, $1) \ + _LT_AC_TAGVAR(include_expsyms, $1); do + + case $var in + _LT_AC_TAGVAR(old_archive_cmds, $1) | \ + _LT_AC_TAGVAR(old_archive_from_new_cmds, $1) | \ + _LT_AC_TAGVAR(archive_cmds, $1) | \ + _LT_AC_TAGVAR(archive_expsym_cmds, $1) | \ + _LT_AC_TAGVAR(module_cmds, $1) | \ + _LT_AC_TAGVAR(module_expsym_cmds, $1) | \ + _LT_AC_TAGVAR(old_archive_from_expsyms_cmds, $1) | \ + _LT_AC_TAGVAR(export_symbols_cmds, $1) | \ + extract_expsyms_cmds | reload_cmds | finish_cmds | \ + postinstall_cmds | postuninstall_cmds | \ + old_postinstall_cmds | old_postuninstall_cmds | \ + sys_lib_search_path_spec | sys_lib_dlsearch_path_spec) + # Double-quote double-evaled strings. + eval "lt_$var=\\\"\`\$echo \"X\$$var\" | \$Xsed -e \"\$double_quote_subst\" -e \"\$sed_quote_subst\" -e \"\$delay_variable_subst\"\`\\\"" + ;; + *) + eval "lt_$var=\\\"\`\$echo \"X\$$var\" | \$Xsed -e \"\$sed_quote_subst\"\`\\\"" + ;; + esac + done + + case $lt_echo in + *'\[$]0 --fallback-echo"') + lt_echo=`$echo "X$lt_echo" | $Xsed -e 's/\\\\\\\[$]0 --fallback-echo"[$]/[$]0 --fallback-echo"/'` + ;; + esac + +ifelse([$1], [], + [cfgfile="${ofile}T" + trap "$rm \"$cfgfile\"; exit 1" 1 2 15 + $rm -f "$cfgfile" + AC_MSG_NOTICE([creating $ofile])], + [cfgfile="$ofile"]) + + cat <<__EOF__ >> "$cfgfile" +ifelse([$1], [], +[#! $SHELL + +# `$echo "$cfgfile" | sed 's%^.*/%%'` - Provide generalized library-building support services. +# Generated automatically by $PROGRAM (GNU $PACKAGE $VERSION$TIMESTAMP) +# NOTE: Changes made to this file will be lost: look at ltmain.sh. +# +# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 +# Free Software Foundation, Inc. +# +# This file is part of GNU Libtool: +# Originally by Gordon Matzigkeit , 1996 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# A sed program that does not truncate output. +SED=$lt_SED + +# Sed that helps us avoid accidentally triggering echo(1) options like -n. +Xsed="$SED -e 1s/^X//" + +# The HP-UX ksh and POSIX shell print the target directory to stdout +# if CDPATH is set. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +# The names of the tagged configurations supported by this script. +available_tags= + +# ### BEGIN LIBTOOL CONFIG], +[# ### BEGIN LIBTOOL TAG CONFIG: $tagname]) + +# Libtool was configured on host `(hostname || uname -n) 2>/dev/null | sed 1q`: + +# Shell to use when invoking shell scripts. +SHELL=$lt_SHELL + +# Whether or not to build shared libraries. +build_libtool_libs=$enable_shared + +# Whether or not to build static libraries. +build_old_libs=$enable_static + +# Whether or not to add -lc for building shared libraries. +build_libtool_need_lc=$_LT_AC_TAGVAR(archive_cmds_need_lc, $1) + +# Whether or not to disallow shared libs when runtime libs are static +allow_libtool_libs_with_static_runtimes=$_LT_AC_TAGVAR(enable_shared_with_static_runtimes, $1) + +# Whether or not to optimize for fast installation. +fast_install=$enable_fast_install + +# The host system. +host_alias=$host_alias +host=$host +host_os=$host_os + +# The build system. +build_alias=$build_alias +build=$build +build_os=$build_os + +# An echo program that does not interpret backslashes. +echo=$lt_echo + +# The archiver. +AR=$lt_AR +AR_FLAGS=$lt_AR_FLAGS + +# A C compiler. +LTCC=$lt_LTCC + +# LTCC compiler flags. +LTCFLAGS=$lt_LTCFLAGS + +# A language-specific compiler. +CC=$lt_[]_LT_AC_TAGVAR(compiler, $1) + +# Is the compiler the GNU C compiler? +with_gcc=$_LT_AC_TAGVAR(GCC, $1) + +# An ERE matcher. +EGREP=$lt_EGREP + +# The linker used to build libraries. +LD=$lt_[]_LT_AC_TAGVAR(LD, $1) + +# Whether we need hard or soft links. +LN_S=$lt_LN_S + +# A BSD-compatible nm program. +NM=$lt_NM + +# A symbol stripping program +STRIP=$lt_STRIP + +# Used to examine libraries when file_magic_cmd begins "file" +MAGIC_CMD=$MAGIC_CMD + +# Used on cygwin: DLL creation program. +DLLTOOL="$DLLTOOL" + +# Used on cygwin: object dumper. +OBJDUMP="$OBJDUMP" + +# Used on cygwin: assembler. +AS="$AS" + +# The name of the directory that contains temporary libtool files. +objdir=$objdir + +# How to create reloadable object files. +reload_flag=$lt_reload_flag +reload_cmds=$lt_reload_cmds + +# How to pass a linker flag through the compiler. +wl=$lt_[]_LT_AC_TAGVAR(lt_prog_compiler_wl, $1) + +# Object file suffix (normally "o"). +objext="$ac_objext" + +# Old archive suffix (normally "a"). +libext="$libext" + +# Shared library suffix (normally ".so"). +shrext_cmds='$shrext_cmds' + +# Executable file suffix (normally ""). +exeext="$exeext" + +# Additional compiler flags for building library objects. +pic_flag=$lt_[]_LT_AC_TAGVAR(lt_prog_compiler_pic, $1) +pic_mode=$pic_mode + +# What is the maximum length of a command? +max_cmd_len=$lt_cv_sys_max_cmd_len + +# Does compiler simultaneously support -c and -o options? +compiler_c_o=$lt_[]_LT_AC_TAGVAR(lt_cv_prog_compiler_c_o, $1) + +# Must we lock files when doing compilation? +need_locks=$lt_need_locks + +# Do we need the lib prefix for modules? +need_lib_prefix=$need_lib_prefix + +# Do we need a version for libraries? +need_version=$need_version + +# Whether dlopen is supported. +dlopen_support=$enable_dlopen + +# Whether dlopen of programs is supported. +dlopen_self=$enable_dlopen_self + +# Whether dlopen of statically linked programs is supported. +dlopen_self_static=$enable_dlopen_self_static + +# Compiler flag to prevent dynamic linking. +link_static_flag=$lt_[]_LT_AC_TAGVAR(lt_prog_compiler_static, $1) + +# Compiler flag to turn off builtin functions. +no_builtin_flag=$lt_[]_LT_AC_TAGVAR(lt_prog_compiler_no_builtin_flag, $1) + +# Compiler flag to allow reflexive dlopens. +export_dynamic_flag_spec=$lt_[]_LT_AC_TAGVAR(export_dynamic_flag_spec, $1) + +# Compiler flag to generate shared objects directly from archives. +whole_archive_flag_spec=$lt_[]_LT_AC_TAGVAR(whole_archive_flag_spec, $1) + +# Compiler flag to generate thread-safe objects. +thread_safe_flag_spec=$lt_[]_LT_AC_TAGVAR(thread_safe_flag_spec, $1) + +# Library versioning type. +version_type=$version_type + +# Format of library name prefix. +libname_spec=$lt_libname_spec + +# List of archive names. First name is the real one, the rest are links. +# The last name is the one that the linker finds with -lNAME. +library_names_spec=$lt_library_names_spec + +# The coded name of the library, if different from the real name. +soname_spec=$lt_soname_spec + +# Commands used to build and install an old-style archive. +RANLIB=$lt_RANLIB +old_archive_cmds=$lt_[]_LT_AC_TAGVAR(old_archive_cmds, $1) +old_postinstall_cmds=$lt_old_postinstall_cmds +old_postuninstall_cmds=$lt_old_postuninstall_cmds + +# Create an old-style archive from a shared archive. +old_archive_from_new_cmds=$lt_[]_LT_AC_TAGVAR(old_archive_from_new_cmds, $1) + +# Create a temporary old-style archive to link instead of a shared archive. +old_archive_from_expsyms_cmds=$lt_[]_LT_AC_TAGVAR(old_archive_from_expsyms_cmds, $1) + +# Commands used to build and install a shared archive. +archive_cmds=$lt_[]_LT_AC_TAGVAR(archive_cmds, $1) +archive_expsym_cmds=$lt_[]_LT_AC_TAGVAR(archive_expsym_cmds, $1) +postinstall_cmds=$lt_postinstall_cmds +postuninstall_cmds=$lt_postuninstall_cmds + +# Commands used to build a loadable module (assumed same as above if empty) +module_cmds=$lt_[]_LT_AC_TAGVAR(module_cmds, $1) +module_expsym_cmds=$lt_[]_LT_AC_TAGVAR(module_expsym_cmds, $1) + +# Commands to strip libraries. +old_striplib=$lt_old_striplib +striplib=$lt_striplib + +# Dependencies to place before the objects being linked to create a +# shared library. +predep_objects=$lt_[]_LT_AC_TAGVAR(predep_objects, $1) + +# Dependencies to place after the objects being linked to create a +# shared library. +postdep_objects=$lt_[]_LT_AC_TAGVAR(postdep_objects, $1) + +# Dependencies to place before the objects being linked to create a +# shared library. +predeps=$lt_[]_LT_AC_TAGVAR(predeps, $1) + +# Dependencies to place after the objects being linked to create a +# shared library. +postdeps=$lt_[]_LT_AC_TAGVAR(postdeps, $1) + +# The directories searched by this compiler when creating a shared +# library +compiler_lib_search_dirs=$lt_[]_LT_AC_TAGVAR(compiler_lib_search_dirs, $1) + +# The library search path used internally by the compiler when linking +# a shared library. +compiler_lib_search_path=$lt_[]_LT_AC_TAGVAR(compiler_lib_search_path, $1) + +# Method to check whether dependent libraries are shared objects. +deplibs_check_method=$lt_deplibs_check_method + +# Command to use when deplibs_check_method == file_magic. +file_magic_cmd=$lt_file_magic_cmd + +# Flag that allows shared libraries with undefined symbols to be built. +allow_undefined_flag=$lt_[]_LT_AC_TAGVAR(allow_undefined_flag, $1) + +# Flag that forces no undefined symbols. +no_undefined_flag=$lt_[]_LT_AC_TAGVAR(no_undefined_flag, $1) + +# Commands used to finish a libtool library installation in a directory. +finish_cmds=$lt_finish_cmds + +# Same as above, but a single script fragment to be evaled but not shown. +finish_eval=$lt_finish_eval + +# Take the output of nm and produce a listing of raw symbols and C names. +global_symbol_pipe=$lt_lt_cv_sys_global_symbol_pipe + +# Transform the output of nm in a proper C declaration +global_symbol_to_cdecl=$lt_lt_cv_sys_global_symbol_to_cdecl + +# Transform the output of nm in a C name address pair +global_symbol_to_c_name_address=$lt_lt_cv_sys_global_symbol_to_c_name_address + +# This is the shared library runtime path variable. +runpath_var=$runpath_var + +# This is the shared library path variable. +shlibpath_var=$shlibpath_var + +# Is shlibpath searched before the hard-coded library search path? +shlibpath_overrides_runpath=$shlibpath_overrides_runpath + +# How to hardcode a shared library path into an executable. +hardcode_action=$_LT_AC_TAGVAR(hardcode_action, $1) + +# Whether we should hardcode library paths into libraries. +hardcode_into_libs=$hardcode_into_libs + +# Flag to hardcode \$libdir into a binary during linking. +# This must work even if \$libdir does not exist. +hardcode_libdir_flag_spec=$lt_[]_LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1) + +# If ld is used when linking, flag to hardcode \$libdir into +# a binary during linking. This must work even if \$libdir does +# not exist. +hardcode_libdir_flag_spec_ld=$lt_[]_LT_AC_TAGVAR(hardcode_libdir_flag_spec_ld, $1) + +# Whether we need a single -rpath flag with a separated argument. +hardcode_libdir_separator=$lt_[]_LT_AC_TAGVAR(hardcode_libdir_separator, $1) + +# Set to yes if using DIR/libNAME${shared_ext} during linking hardcodes DIR into the +# resulting binary. +hardcode_direct=$_LT_AC_TAGVAR(hardcode_direct, $1) + +# Set to yes if using the -LDIR flag during linking hardcodes DIR into the +# resulting binary. +hardcode_minus_L=$_LT_AC_TAGVAR(hardcode_minus_L, $1) + +# Set to yes if using SHLIBPATH_VAR=DIR during linking hardcodes DIR into +# the resulting binary. +hardcode_shlibpath_var=$_LT_AC_TAGVAR(hardcode_shlibpath_var, $1) + +# Set to yes if building a shared library automatically hardcodes DIR into the library +# and all subsequent libraries and executables linked against it. +hardcode_automatic=$_LT_AC_TAGVAR(hardcode_automatic, $1) + +# Variables whose values should be saved in libtool wrapper scripts and +# restored at relink time. +variables_saved_for_relink="$variables_saved_for_relink" + +# Whether libtool must link a program against all its dependency libraries. +link_all_deplibs=$_LT_AC_TAGVAR(link_all_deplibs, $1) + +# Compile-time system search path for libraries +sys_lib_search_path_spec=$lt_sys_lib_search_path_spec + +# Run-time system search path for libraries +sys_lib_dlsearch_path_spec=$lt_sys_lib_dlsearch_path_spec + +# Fix the shell variable \$srcfile for the compiler. +fix_srcfile_path=$lt_fix_srcfile_path + +# Set to yes if exported symbols are required. +always_export_symbols=$_LT_AC_TAGVAR(always_export_symbols, $1) + +# The commands to list exported symbols. +export_symbols_cmds=$lt_[]_LT_AC_TAGVAR(export_symbols_cmds, $1) + +# The commands to extract the exported symbol list from a shared archive. +extract_expsyms_cmds=$lt_extract_expsyms_cmds + +# Symbols that should not be listed in the preloaded symbols. +exclude_expsyms=$lt_[]_LT_AC_TAGVAR(exclude_expsyms, $1) + +# Symbols that must always be exported. +include_expsyms=$lt_[]_LT_AC_TAGVAR(include_expsyms, $1) + +ifelse([$1],[], +[# ### END LIBTOOL CONFIG], +[# ### END LIBTOOL TAG CONFIG: $tagname]) + +__EOF__ + +ifelse([$1],[], [ + case $host_os in + aix3*) + cat <<\EOF >> "$cfgfile" + +# AIX sometimes has problems with the GCC collect2 program. For some +# reason, if we set the COLLECT_NAMES environment variable, the problems +# vanish in a puff of smoke. +if test "X${COLLECT_NAMES+set}" != Xset; then + COLLECT_NAMES= + export COLLECT_NAMES +fi +EOF + ;; + esac + + # We use sed instead of cat because bash on DJGPP gets confused if + # if finds mixed CR/LF and LF-only lines. Since sed operates in + # text mode, it properly converts lines to CR/LF. This bash problem + # is reportedly fixed, but why not run on old versions too? + sed '$q' "$ltmain" >> "$cfgfile" || (rm -f "$cfgfile"; exit 1) + + mv -f "$cfgfile" "$ofile" || \ + (rm -f "$ofile" && cp "$cfgfile" "$ofile" && rm -f "$cfgfile") + chmod +x "$ofile" +]) +else + # If there is no Makefile yet, we rely on a make rule to execute + # `config.status --recheck' to rerun these tests and create the + # libtool script then. + ltmain_in=`echo $ltmain | sed -e 's/\.sh$/.in/'` + if test -f "$ltmain_in"; then + test -f Makefile && make "$ltmain" + fi +fi +])# AC_LIBTOOL_CONFIG + + +# AC_LIBTOOL_PROG_COMPILER_NO_RTTI([TAGNAME]) +# ------------------------------------------- +AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_NO_RTTI], +[AC_REQUIRE([_LT_AC_SYS_COMPILER])dnl + +_LT_AC_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)= + +if test "$GCC" = yes; then + _LT_AC_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -fno-builtin' + + AC_LIBTOOL_COMPILER_OPTION([if $compiler supports -fno-rtti -fno-exceptions], + lt_cv_prog_compiler_rtti_exceptions, + [-fno-rtti -fno-exceptions], [], + [_LT_AC_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)="$_LT_AC_TAGVAR(lt_prog_compiler_no_builtin_flag, $1) -fno-rtti -fno-exceptions"]) +fi +])# AC_LIBTOOL_PROG_COMPILER_NO_RTTI + + +# AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE +# --------------------------------- +AC_DEFUN([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE], +[AC_REQUIRE([AC_CANONICAL_HOST]) +AC_REQUIRE([LT_AC_PROG_SED]) +AC_REQUIRE([AC_PROG_NM]) +AC_REQUIRE([AC_OBJEXT]) +# Check for command to grab the raw symbol name followed by C symbol from nm. +AC_MSG_CHECKING([command to parse $NM output from $compiler object]) +AC_CACHE_VAL([lt_cv_sys_global_symbol_pipe], +[ +# These are sane defaults that work on at least a few old systems. +# [They come from Ultrix. What could be older than Ultrix?!! ;)] + +# Character class describing NM global symbol codes. +symcode='[[BCDEGRST]]' + +# Regexp to match symbols that can be accessed directly from C. +sympat='\([[_A-Za-z]][[_A-Za-z0-9]]*\)' + +# Transform an extracted symbol line into a proper C declaration +lt_cv_sys_global_symbol_to_cdecl="sed -n -e 's/^. .* \(.*\)$/extern int \1;/p'" + +# Transform an extracted symbol line into symbol name and symbol address +lt_cv_sys_global_symbol_to_c_name_address="sed -n -e 's/^: \([[^ ]]*\) $/ {\\\"\1\\\", (lt_ptr) 0},/p' -e 's/^$symcode \([[^ ]]*\) \([[^ ]]*\)$/ {\"\2\", (lt_ptr) \&\2},/p'" + +# Define system-specific variables. +case $host_os in +aix*) + symcode='[[BCDT]]' + ;; +cygwin* | mingw* | pw32*) + symcode='[[ABCDGISTW]]' + ;; +hpux*) # Its linker distinguishes data from code symbols + if test "$host_cpu" = ia64; then + symcode='[[ABCDEGRST]]' + fi + lt_cv_sys_global_symbol_to_cdecl="sed -n -e 's/^T .* \(.*\)$/extern int \1();/p' -e 's/^$symcode* .* \(.*\)$/extern char \1;/p'" + lt_cv_sys_global_symbol_to_c_name_address="sed -n -e 's/^: \([[^ ]]*\) $/ {\\\"\1\\\", (lt_ptr) 0},/p' -e 's/^$symcode* \([[^ ]]*\) \([[^ ]]*\)$/ {\"\2\", (lt_ptr) \&\2},/p'" + ;; +linux* | k*bsd*-gnu) + if test "$host_cpu" = ia64; then + symcode='[[ABCDGIRSTW]]' + lt_cv_sys_global_symbol_to_cdecl="sed -n -e 's/^T .* \(.*\)$/extern int \1();/p' -e 's/^$symcode* .* \(.*\)$/extern char \1;/p'" + lt_cv_sys_global_symbol_to_c_name_address="sed -n -e 's/^: \([[^ ]]*\) $/ {\\\"\1\\\", (lt_ptr) 0},/p' -e 's/^$symcode* \([[^ ]]*\) \([[^ ]]*\)$/ {\"\2\", (lt_ptr) \&\2},/p'" + fi + ;; +irix* | nonstopux*) + symcode='[[BCDEGRST]]' + ;; +osf*) + symcode='[[BCDEGQRST]]' + ;; +solaris*) + symcode='[[BDRT]]' + ;; +sco3.2v5*) + symcode='[[DT]]' + ;; +sysv4.2uw2*) + symcode='[[DT]]' + ;; +sysv5* | sco5v6* | unixware* | OpenUNIX*) + symcode='[[ABDT]]' + ;; +sysv4) + symcode='[[DFNSTU]]' + ;; +esac + +# Handle CRLF in mingw tool chain +opt_cr= +case $build_os in +mingw*) + opt_cr=`echo 'x\{0,1\}' | tr x '\015'` # option cr in regexp + ;; +esac + +# If we're using GNU nm, then use its standard symbol codes. +case `$NM -V 2>&1` in +*GNU* | *'with BFD'*) + symcode='[[ABCDGIRSTW]]' ;; +esac + +# Try without a prefix undercore, then with it. +for ac_symprfx in "" "_"; do + + # Transform symcode, sympat, and symprfx into a raw symbol and a C symbol. + symxfrm="\\1 $ac_symprfx\\2 \\2" + + # Write the raw and C identifiers. + lt_cv_sys_global_symbol_pipe="sed -n -e 's/^.*[[ ]]\($symcode$symcode*\)[[ ]][[ ]]*$ac_symprfx$sympat$opt_cr$/$symxfrm/p'" + + # Check to see that the pipe works correctly. + pipe_works=no + + rm -f conftest* + cat > conftest.$ac_ext < $nlist) && test -s "$nlist"; then + # Try sorting and uniquifying the output. + if sort "$nlist" | uniq > "$nlist"T; then + mv -f "$nlist"T "$nlist" + else + rm -f "$nlist"T + fi + + # Make sure that we snagged all the symbols we need. + if grep ' nm_test_var$' "$nlist" >/dev/null; then + if grep ' nm_test_func$' "$nlist" >/dev/null; then + cat < conftest.$ac_ext +#ifdef __cplusplus +extern "C" { +#endif + +EOF + # Now generate the symbol file. + eval "$lt_cv_sys_global_symbol_to_cdecl"' < "$nlist" | grep -v main >> conftest.$ac_ext' + + cat <> conftest.$ac_ext +#if defined (__STDC__) && __STDC__ +# define lt_ptr_t void * +#else +# define lt_ptr_t char * +# define const +#endif + +/* The mapping between symbol names and symbols. */ +const struct { + const char *name; + lt_ptr_t address; +} +lt_preloaded_symbols[[]] = +{ +EOF + $SED "s/^$symcode$symcode* \(.*\) \(.*\)$/ {\"\2\", (lt_ptr_t) \&\2},/" < "$nlist" | grep -v main >> conftest.$ac_ext + cat <<\EOF >> conftest.$ac_ext + {0, (lt_ptr_t) 0} +}; + +#ifdef __cplusplus +} +#endif +EOF + # Now try linking the two files. + mv conftest.$ac_objext conftstm.$ac_objext + lt_save_LIBS="$LIBS" + lt_save_CFLAGS="$CFLAGS" + LIBS="conftstm.$ac_objext" + CFLAGS="$CFLAGS$_LT_AC_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)" + if AC_TRY_EVAL(ac_link) && test -s conftest${ac_exeext}; then + pipe_works=yes + fi + LIBS="$lt_save_LIBS" + CFLAGS="$lt_save_CFLAGS" + else + echo "cannot find nm_test_func in $nlist" >&AS_MESSAGE_LOG_FD + fi + else + echo "cannot find nm_test_var in $nlist" >&AS_MESSAGE_LOG_FD + fi + else + echo "cannot run $lt_cv_sys_global_symbol_pipe" >&AS_MESSAGE_LOG_FD + fi + else + echo "$progname: failed program was:" >&AS_MESSAGE_LOG_FD + cat conftest.$ac_ext >&5 + fi + rm -rf conftest* conftst* + + # Do not use the global_symbol_pipe unless it works. + if test "$pipe_works" = yes; then + break + else + lt_cv_sys_global_symbol_pipe= + fi +done +]) +if test -z "$lt_cv_sys_global_symbol_pipe"; then + lt_cv_sys_global_symbol_to_cdecl= +fi +if test -z "$lt_cv_sys_global_symbol_pipe$lt_cv_sys_global_symbol_to_cdecl"; then + AC_MSG_RESULT(failed) +else + AC_MSG_RESULT(ok) +fi +]) # AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE + + +# AC_LIBTOOL_PROG_COMPILER_PIC([TAGNAME]) +# --------------------------------------- +AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_PIC], +[_LT_AC_TAGVAR(lt_prog_compiler_wl, $1)= +_LT_AC_TAGVAR(lt_prog_compiler_pic, $1)= +_LT_AC_TAGVAR(lt_prog_compiler_static, $1)= + +AC_MSG_CHECKING([for $compiler option to produce PIC]) + ifelse([$1],[CXX],[ + # C++ specific cases for pic, static, wl, etc. + if test "$GXX" = yes; then + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-static' + + case $host_os in + aix*) + # All AIX code is PIC. + if test "$host_cpu" = ia64; then + # AIX 5 now supports IA64 processor + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + fi + ;; + amigaos*) + # FIXME: we need at least 68020 code to build shared libraries, but + # adding the `-m68020' flag to GCC prevents building anything better, + # like `-m68040'. + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-m68020 -resident32 -malways-restore-a4' + ;; + beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) + # PIC is the default for these OSes. + ;; + mingw* | cygwin* | os2* | pw32*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + # Although the cygwin gcc ignores -fPIC, still need this for old-style + # (--disable-auto-import) libraries + m4_if([$1], [GCJ], [], + [_LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) + ;; + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common' + ;; + *djgpp*) + # DJGPP does not support shared libraries at all + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)= + ;; + interix[[3-9]]*) + # Interix 3.x gcc -fpic/-fPIC options generate broken code. + # Instead, we relocate shared libraries at runtime. + ;; + sysv4*MP*) + if test -d /usr/nec; then + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)=-Kconform_pic + fi + ;; + hpux*) + # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but + # not for PA HP-UX. + case $host_cpu in + hppa*64*|ia64*) + ;; + *) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + ;; + *) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + else + case $host_os in + aix[[4-9]]*) + # All AIX code is PIC. + if test "$host_cpu" = ia64; then + # AIX 5 now supports IA64 processor + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + else + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-bnso -bI:/lib/syscalls.exp' + fi + ;; + chorus*) + case $cc_basename in + cxch68*) + # Green Hills C++ Compiler + # _LT_AC_TAGVAR(lt_prog_compiler_static, $1)="--no_auto_instantiation -u __main -u __premain -u _abort -r $COOL_DIR/lib/libOrb.a $MVME_DIR/lib/CC/libC.a $MVME_DIR/lib/classix/libcx.s.a" + ;; + esac + ;; + darwin*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + case $cc_basename in + xlc*) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-qnocommon' + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + ;; + esac + ;; + dgux*) + case $cc_basename in + ec++*) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + ;; + ghcx*) + # Green Hills C++ Compiler + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + ;; + *) + ;; + esac + ;; + freebsd* | dragonfly*) + # FreeBSD uses GNU C++ + ;; + hpux9* | hpux10* | hpux11*) + case $cc_basename in + CC*) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='${wl}-a ${wl}archive' + if test "$host_cpu" != ia64; then + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='+Z' + fi + ;; + aCC*) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='${wl}-a ${wl}archive' + case $host_cpu in + hppa*64*|ia64*) + # +Z the default + ;; + *) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='+Z' + ;; + esac + ;; + *) + ;; + esac + ;; + interix*) + # This is c89, which is MS Visual C++ (no shared libs) + # Anyone wants to do a port? + ;; + irix5* | irix6* | nonstopux*) + case $cc_basename in + CC*) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + # CC pic flag -KPIC is the default. + ;; + *) + ;; + esac + ;; + linux* | k*bsd*-gnu) + case $cc_basename in + KCC*) + # KAI C++ Compiler + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='--backend -Wl,' + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + icpc* | ecpc*) + # Intel C++ + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + pgCC* | pgcpp*) + # Portland Group C++ compiler. + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-fpic' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + cxx*) + # Compaq C++ + # Make sure the PIC flag is empty. It appears that all Alpha + # Linux and Compaq Tru64 Unix objects are PIC. + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)= + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) + # Sun C++ 5.9 + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' + ;; + esac + ;; + esac + ;; + lynxos*) + ;; + m88k*) + ;; + mvs*) + case $cc_basename in + cxx*) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-W c,exportall' + ;; + *) + ;; + esac + ;; + netbsd*) + ;; + osf3* | osf4* | osf5*) + case $cc_basename in + KCC*) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='--backend -Wl,' + ;; + RCC*) + # Rational C++ 2.4.1 + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + ;; + cxx*) + # Digital/Compaq C++ + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # Make sure the PIC flag is empty. It appears that all Alpha + # Linux and Compaq Tru64 Unix objects are PIC. + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)= + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + *) + ;; + esac + ;; + psos*) + ;; + solaris*) + case $cc_basename in + CC*) + # Sun C++ 4.2, 5.x and Centerline C++ + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' + ;; + gcx*) + # Green Hills C++ Compiler + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' + ;; + *) + ;; + esac + ;; + sunos4*) + case $cc_basename in + CC*) + # Sun C++ 4.x + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + lcc*) + # Lucid + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + ;; + *) + ;; + esac + ;; + tandem*) + case $cc_basename in + NCC*) + # NonStop-UX NCC 3.20 + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + ;; + *) + ;; + esac + ;; + sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) + case $cc_basename in + CC*) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + esac + ;; + vxworks*) + ;; + *) + _LT_AC_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + ;; + esac + fi +], +[ + if test "$GCC" = yes; then + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-static' + + case $host_os in + aix*) + # All AIX code is PIC. + if test "$host_cpu" = ia64; then + # AIX 5 now supports IA64 processor + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + fi + ;; + + amigaos*) + # FIXME: we need at least 68020 code to build shared libraries, but + # adding the `-m68020' flag to GCC prevents building anything better, + # like `-m68040'. + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-m68020 -resident32 -malways-restore-a4' + ;; + + beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) + # PIC is the default for these OSes. + ;; + + mingw* | cygwin* | pw32* | os2*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + # Although the cygwin gcc ignores -fPIC, still need this for old-style + # (--disable-auto-import) libraries + m4_if([$1], [GCJ], [], + [_LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) + ;; + + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common' + ;; + + interix[[3-9]]*) + # Interix 3.x gcc -fpic/-fPIC options generate broken code. + # Instead, we relocate shared libraries at runtime. + ;; + + msdosdjgpp*) + # Just because we use GCC doesn't mean we suddenly get shared libraries + # on systems that don't support them. + _LT_AC_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + enable_shared=no + ;; + + sysv4*MP*) + if test -d /usr/nec; then + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)=-Kconform_pic + fi + ;; + + hpux*) + # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but + # not for PA HP-UX. + case $host_cpu in + hppa*64*|ia64*) + # +Z the default + ;; + *) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + ;; + + *) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + else + # PORTME Check for flag to pass linker flags through the system compiler. + case $host_os in + aix*) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + if test "$host_cpu" = ia64; then + # AIX 5 now supports IA64 processor + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + else + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-bnso -bI:/lib/syscalls.exp' + fi + ;; + darwin*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + case $cc_basename in + xlc*) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-qnocommon' + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + ;; + esac + ;; + + mingw* | cygwin* | pw32* | os2*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + m4_if([$1], [GCJ], [], + [_LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) + ;; + + hpux9* | hpux10* | hpux11*) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but + # not for PA HP-UX. + case $host_cpu in + hppa*64*|ia64*) + # +Z the default + ;; + *) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='+Z' + ;; + esac + # Is there a better lt_prog_compiler_static that works with the bundled CC? + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='${wl}-a ${wl}archive' + ;; + + irix5* | irix6* | nonstopux*) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # PIC (with -KPIC) is the default. + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + + newsos6) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + linux* | k*bsd*-gnu) + case $cc_basename in + icc* | ecc*) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + pgcc* | pgf77* | pgf90* | pgf95*) + # Portland Group compilers (*not* the Pentium gcc compiler, + # which looks to be a dead project) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-fpic' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + ccc*) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # All Alpha code is PIC. + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) + # Sun C 5.9 + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + ;; + *Sun\ F*) + # Sun Fortran 8.3 passes all unrecognized flags to the linker + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='' + ;; + esac + ;; + esac + ;; + + osf3* | osf4* | osf5*) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # All OSF/1 code is PIC. + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + + rdos*) + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + + solaris*) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + case $cc_basename in + f77* | f90* | f95*) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ';; + *) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,';; + esac + ;; + + sunos4*) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + sysv4 | sysv4.2uw2* | sysv4.3*) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + sysv4*MP*) + if test -d /usr/nec ;then + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-Kconform_pic' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + fi + ;; + + sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + unicos*) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_AC_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + ;; + + uts4*) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + *) + _LT_AC_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + ;; + esac + fi +]) +AC_MSG_RESULT([$_LT_AC_TAGVAR(lt_prog_compiler_pic, $1)]) + +# +# Check to make sure the PIC flag actually works. +# +if test -n "$_LT_AC_TAGVAR(lt_prog_compiler_pic, $1)"; then + AC_LIBTOOL_COMPILER_OPTION([if $compiler PIC flag $_LT_AC_TAGVAR(lt_prog_compiler_pic, $1) works], + _LT_AC_TAGVAR(lt_cv_prog_compiler_pic_works, $1), + [$_LT_AC_TAGVAR(lt_prog_compiler_pic, $1)ifelse([$1],[],[ -DPIC],[ifelse([$1],[CXX],[ -DPIC],[])])], [], + [case $_LT_AC_TAGVAR(lt_prog_compiler_pic, $1) in + "" | " "*) ;; + *) _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)=" $_LT_AC_TAGVAR(lt_prog_compiler_pic, $1)" ;; + esac], + [_LT_AC_TAGVAR(lt_prog_compiler_pic, $1)= + _LT_AC_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no]) +fi +case $host_os in + # For platforms which do not support PIC, -DPIC is meaningless: + *djgpp*) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)= + ;; + *) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)="$_LT_AC_TAGVAR(lt_prog_compiler_pic, $1)ifelse([$1],[],[ -DPIC],[ifelse([$1],[CXX],[ -DPIC],[])])" + ;; +esac + +# +# Check to make sure the static flag actually works. +# +wl=$_LT_AC_TAGVAR(lt_prog_compiler_wl, $1) eval lt_tmp_static_flag=\"$_LT_AC_TAGVAR(lt_prog_compiler_static, $1)\" +AC_LIBTOOL_LINKER_OPTION([if $compiler static flag $lt_tmp_static_flag works], + _LT_AC_TAGVAR(lt_cv_prog_compiler_static_works, $1), + $lt_tmp_static_flag, + [], + [_LT_AC_TAGVAR(lt_prog_compiler_static, $1)=]) +]) + + +# AC_LIBTOOL_PROG_LD_SHLIBS([TAGNAME]) +# ------------------------------------ +# See if the linker supports building shared libraries. +AC_DEFUN([AC_LIBTOOL_PROG_LD_SHLIBS], +[AC_REQUIRE([LT_AC_PROG_SED])dnl +AC_MSG_CHECKING([whether the $compiler linker ($LD) supports shared libraries]) +ifelse([$1],[CXX],[ + _LT_AC_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + case $host_os in + aix[[4-9]]*) + # If we're using GNU nm, then we don't want the "-C" option. + # -C means demangle to AIX nm, but means don't demangle with GNU nm + if $NM -V 2>&1 | grep 'GNU' > /dev/null; then + _LT_AC_TAGVAR(export_symbols_cmds, $1)='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\[$]2 == "T") || (\[$]2 == "D") || (\[$]2 == "B")) && ([substr](\[$]3,1,1) != ".")) { print \[$]3 } }'\'' | sort -u > $export_symbols' + else + _LT_AC_TAGVAR(export_symbols_cmds, $1)='$NM -BCpg $libobjs $convenience | awk '\''{ if (((\[$]2 == "T") || (\[$]2 == "D") || (\[$]2 == "B")) && ([substr](\[$]3,1,1) != ".")) { print \[$]3 } }'\'' | sort -u > $export_symbols' + fi + ;; + pw32*) + _LT_AC_TAGVAR(export_symbols_cmds, $1)="$ltdll_cmds" + ;; + cygwin* | mingw*) + _LT_AC_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1 DATA/;/^.*[[ ]]__nm__/s/^.*[[ ]]__nm__\([[^ ]]*\)[[ ]][[^ ]]*/\1 DATA/;/^I[[ ]]/d;/^[[AITW]][[ ]]/s/.*[[ ]]//'\'' | sort | uniq > $export_symbols' + ;; + *) + _LT_AC_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + ;; + esac + _LT_AC_TAGVAR(exclude_expsyms, $1)=['_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*'] +],[ + runpath_var= + _LT_AC_TAGVAR(allow_undefined_flag, $1)= + _LT_AC_TAGVAR(enable_shared_with_static_runtimes, $1)=no + _LT_AC_TAGVAR(archive_cmds, $1)= + _LT_AC_TAGVAR(archive_expsym_cmds, $1)= + _LT_AC_TAGVAR(old_archive_From_new_cmds, $1)= + _LT_AC_TAGVAR(old_archive_from_expsyms_cmds, $1)= + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)= + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)= + _LT_AC_TAGVAR(thread_safe_flag_spec, $1)= + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)= + _LT_AC_TAGVAR(hardcode_libdir_flag_spec_ld, $1)= + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)= + _LT_AC_TAGVAR(hardcode_direct, $1)=no + _LT_AC_TAGVAR(hardcode_minus_L, $1)=no + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=unsupported + _LT_AC_TAGVAR(link_all_deplibs, $1)=unknown + _LT_AC_TAGVAR(hardcode_automatic, $1)=no + _LT_AC_TAGVAR(module_cmds, $1)= + _LT_AC_TAGVAR(module_expsym_cmds, $1)= + _LT_AC_TAGVAR(always_export_symbols, $1)=no + _LT_AC_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + # include_expsyms should be a list of space-separated symbols to be *always* + # included in the symbol list + _LT_AC_TAGVAR(include_expsyms, $1)= + # exclude_expsyms can be an extended regexp of symbols to exclude + # it will be wrapped by ` (' and `)$', so one must not match beginning or + # end of line. Example: `a|bc|.*d.*' will exclude the symbols `a' and `bc', + # as well as any symbol that contains `d'. + _LT_AC_TAGVAR(exclude_expsyms, $1)=['_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*'] + # Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out + # platforms (ab)use it in PIC code, but their linkers get confused if + # the symbol is explicitly referenced. Since portable code cannot + # rely on this symbol name, it's probably fine to never include it in + # preloaded symbol tables. + # Exclude shared library initialization/finalization symbols. +dnl Note also adjust exclude_expsyms for C++ above. + extract_expsyms_cmds= + # Just being paranoid about ensuring that cc_basename is set. + _LT_CC_BASENAME([$compiler]) + case $host_os in + cygwin* | mingw* | pw32*) + # FIXME: the MSVC++ port hasn't been tested in a loooong time + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + if test "$GCC" != yes; then + with_gnu_ld=no + fi + ;; + interix*) + # we just hope/assume this is gcc and not c89 (= MSVC++) + with_gnu_ld=yes + ;; + openbsd*) + with_gnu_ld=no + ;; + esac + + _LT_AC_TAGVAR(ld_shlibs, $1)=yes + if test "$with_gnu_ld" = yes; then + # If archive_cmds runs LD, not CC, wlarc should be empty + wlarc='${wl}' + + # Set some defaults for GNU ld with shared library support. These + # are reset later if shared libraries are not supported. Putting them + # here allows them to be overridden if necessary. + runpath_var=LD_RUN_PATH + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}--rpath ${wl}$libdir' + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic' + # ancient GNU ld didn't support --whole-archive et. al. + if $LD --help 2>&1 | grep 'no-whole-archive' > /dev/null; then + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive' + else + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)= + fi + supports_anon_versioning=no + case `$LD -v 2>/dev/null` in + *\ [[01]].* | *\ 2.[[0-9]].* | *\ 2.10.*) ;; # catch versions < 2.11 + *\ 2.11.93.0.2\ *) supports_anon_versioning=yes ;; # RH7.3 ... + *\ 2.11.92.0.12\ *) supports_anon_versioning=yes ;; # Mandrake 8.2 ... + *\ 2.11.*) ;; # other 2.11 versions + *) supports_anon_versioning=yes ;; + esac + + # See if GNU ld supports shared libraries. + case $host_os in + aix[[3-9]]*) + # On AIX/PPC, the GNU linker is very broken + if test "$host_cpu" != ia64; then + _LT_AC_TAGVAR(ld_shlibs, $1)=no + cat <&2 + +*** Warning: the GNU linker, at least up to release 2.9.1, is reported +*** to be unable to reliably create shared libraries on AIX. +*** Therefore, libtool is disabling shared libraries support. If you +*** really care for shared libraries, you may want to modify your PATH +*** so that a non-GNU linker is found, and then restart. + +EOF + fi + ;; + + amigaos*) + _LT_AC_TAGVAR(archive_cmds, $1)='$rm $output_objdir/a2ixlibrary.data~$echo "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$echo "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$echo "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$echo "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes + + # Samuel A. Falvo II reports + # that the semantics of dynamic libraries on AmigaOS, at least up + # to version 4, is to share data among multiple programs linked + # with the same dynamic library. Since this doesn't match the + # behavior of shared libraries on other platforms, we can't use + # them. + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + + beos*) + if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + _LT_AC_TAGVAR(allow_undefined_flag, $1)=unsupported + # Joseph Beckenbach says some releases of gcc + # support --undefined. This deserves some investigation. FIXME + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -nostart $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + else + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + cygwin* | mingw* | pw32*) + # _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1) is actually meaningless, + # as there is no search path for DLLs. + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_AC_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_AC_TAGVAR(always_export_symbols, $1)=no + _LT_AC_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + _LT_AC_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1 DATA/'\'' -e '\''/^[[AITW]][[ ]]/s/.*[[ ]]//'\'' | sort | uniq > $export_symbols' + + if $LD --help 2>&1 | grep 'auto-import' > /dev/null; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + # If the export-symbols file already is a .def file (1st line + # is EXPORTS), use it as is; otherwise, prepend... + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then + cp $export_symbols $output_objdir/$soname.def; + else + echo EXPORTS > $output_objdir/$soname.def; + cat $export_symbols >> $output_objdir/$soname.def; + fi~ + $CC -shared $output_objdir/$soname.def $libobjs $deplibs $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + else + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + interix[[3-9]]*) + _LT_AC_TAGVAR(hardcode_direct, $1)=no + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir' + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E' + # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. + # Instead, shared libraries are loaded at an image base (0x10000000 by + # default) and relocated if they conflict, which is a slow very memory + # consuming and fragmenting process. To avoid this, we pick a random, + # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link + # time. Moving up from 0x10000000 also allows more sbrk(2) space. + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='sed "s,^,_," $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--retain-symbols-file,$output_objdir/$soname.expsym ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + ;; + + gnu* | linux* | k*bsd*-gnu) + if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + tmp_addflag= + case $cc_basename,$host_cpu in + pgcc*) # Portland Group C compiler + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; $echo \"$new_convenience\"` ${wl}--no-whole-archive' + tmp_addflag=' $pic_flag' + ;; + pgf77* | pgf90* | pgf95*) # Portland Group f77 and f90 compilers + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; $echo \"$new_convenience\"` ${wl}--no-whole-archive' + tmp_addflag=' $pic_flag -Mnomain' ;; + ecc*,ia64* | icc*,ia64*) # Intel C compiler on ia64 + tmp_addflag=' -i_dynamic' ;; + efc*,ia64* | ifort*,ia64*) # Intel Fortran compiler on ia64 + tmp_addflag=' -i_dynamic -nofor_main' ;; + ifc* | ifort*) # Intel Fortran compiler + tmp_addflag=' -nofor_main' ;; + esac + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) # Sun C 5.9 + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; $echo \"$new_convenience\"` ${wl}--no-whole-archive' + tmp_sharedflag='-G' ;; + *Sun\ F*) # Sun Fortran 8.3 + tmp_sharedflag='-G' ;; + *) + tmp_sharedflag='-shared' ;; + esac + _LT_AC_TAGVAR(archive_cmds, $1)='$CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + + if test $supports_anon_versioning = yes; then + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + $echo "local: *; };" >> $output_objdir/$libname.ver~ + $CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-version-script ${wl}$output_objdir/$libname.ver -o $lib' + fi + else + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + netbsd*) + if echo __ELF__ | $CC -E - | grep __ELF__ >/dev/null; then + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -Bshareable $libobjs $deplibs $linker_flags -o $lib' + wlarc= + else + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + fi + ;; + + solaris*) + if $LD -v 2>&1 | grep 'BFD 2\.8' > /dev/null; then + _LT_AC_TAGVAR(ld_shlibs, $1)=no + cat <&2 + +*** Warning: The releases 2.8.* of the GNU linker cannot reliably +*** create shared libraries on Solaris systems. Therefore, libtool +*** is disabling shared libraries support. We urge you to upgrade GNU +*** binutils to release 2.9.1 or newer. Another option is to modify +*** your PATH or compiler configuration so that the native linker is +*** used, and then restart. + +EOF + elif $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + else + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*) + case `$LD -v 2>&1` in + *\ [[01]].* | *\ 2.[[0-9]].* | *\ 2.1[[0-5]].*) + _LT_AC_TAGVAR(ld_shlibs, $1)=no + cat <<_LT_EOF 1>&2 + +*** Warning: Releases of the GNU linker prior to 2.16.91.0.3 can not +*** reliably create shared libraries on SCO systems. Therefore, libtool +*** is disabling shared libraries support. We urge you to upgrade GNU +*** binutils to release 2.16.91.0.3 or newer. Another option is to modify +*** your PATH or compiler configuration so that the native linker is +*** used, and then restart. + +_LT_EOF + ;; + *) + if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='`test -z "$SCOABSPATH" && echo ${wl}-rpath,$libdir`' + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname,\${SCOABSPATH:+${install_libdir}/}$soname -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname,\${SCOABSPATH:+${install_libdir}/}$soname,-retain-symbols-file,$export_symbols -o $lib' + else + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + + sunos4*) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linker_flags' + wlarc= + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + *) + if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + else + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + + if test "$_LT_AC_TAGVAR(ld_shlibs, $1)" = no; then + runpath_var= + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)= + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)= + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)= + fi + else + # PORTME fill in a description of your system's linker (not GNU ld) + case $host_os in + aix3*) + _LT_AC_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_AC_TAGVAR(always_export_symbols, $1)=yes + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$LD -o $output_objdir/$soname $libobjs $deplibs $linker_flags -bE:$export_symbols -T512 -H512 -bM:SRE~$AR $AR_FLAGS $lib $output_objdir/$soname' + # Note: this linker hardcodes the directories in LIBPATH if there + # are no directories specified by -L. + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes + if test "$GCC" = yes && test -z "$lt_prog_compiler_static"; then + # Neither direct hardcoding nor static linking is supported with a + # broken collect2. + _LT_AC_TAGVAR(hardcode_direct, $1)=unsupported + fi + ;; + + aix[[4-9]]*) + if test "$host_cpu" = ia64; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + exp_sym_flag='-Bexport' + no_entry_flag="" + else + # If we're using GNU nm, then we don't want the "-C" option. + # -C means demangle to AIX nm, but means don't demangle with GNU nm + if $NM -V 2>&1 | grep 'GNU' > /dev/null; then + _LT_AC_TAGVAR(export_symbols_cmds, $1)='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\[$]2 == "T") || (\[$]2 == "D") || (\[$]2 == "B")) && ([substr](\[$]3,1,1) != ".")) { print \[$]3 } }'\'' | sort -u > $export_symbols' + else + _LT_AC_TAGVAR(export_symbols_cmds, $1)='$NM -BCpg $libobjs $convenience | awk '\''{ if (((\[$]2 == "T") || (\[$]2 == "D") || (\[$]2 == "B")) && ([substr](\[$]3,1,1) != ".")) { print \[$]3 } }'\'' | sort -u > $export_symbols' + fi + aix_use_runtimelinking=no + + # Test if we are trying to use run time linking or normal + # AIX style linking. If -brtl is somewhere in LDFLAGS, we + # need to do runtime linking. + case $host_os in aix4.[[23]]|aix4.[[23]].*|aix[[5-9]]*) + for ld_flag in $LDFLAGS; do + if (test $ld_flag = "-brtl" || test $ld_flag = "-Wl,-brtl"); then + aix_use_runtimelinking=yes + break + fi + done + ;; + esac + + exp_sym_flag='-bexport' + no_entry_flag='-bnoentry' + fi + + # When large executables or shared objects are built, AIX ld can + # have problems creating the table of contents. If linking a library + # or program results in "error TOC overflow" add -mminimal-toc to + # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not + # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. + + _LT_AC_TAGVAR(archive_cmds, $1)='' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_AC_TAGVAR(link_all_deplibs, $1)=yes + + if test "$GCC" = yes; then + case $host_os in aix4.[[012]]|aix4.[[012]].*) + # We only want to do this on AIX 4.2 and lower, the check + # below for broken collect2 doesn't work under 4.3+ + collect2name=`${CC} -print-prog-name=collect2` + if test -f "$collect2name" && \ + strings "$collect2name" | grep resolve_lib_name >/dev/null + then + # We have reworked collect2 + : + else + # We have old collect2 + _LT_AC_TAGVAR(hardcode_direct, $1)=unsupported + # It fails to find uninstalled libraries when the uninstalled + # path is not listed in the libpath. Setting hardcode_minus_L + # to unsupported forces relinking + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)= + fi + ;; + esac + shared_flag='-shared' + if test "$aix_use_runtimelinking" = yes; then + shared_flag="$shared_flag "'${wl}-G' + fi + else + # not using gcc + if test "$host_cpu" = ia64; then + # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release + # chokes on -Wl,-G. The following line is correct: + shared_flag='-G' + else + if test "$aix_use_runtimelinking" = yes; then + shared_flag='${wl}-G' + else + shared_flag='${wl}-bM:SRE' + fi + fi + fi + + # It seems that -bexpall does not export symbols beginning with + # underscore (_), so it is better to generate a list of symbols to export. + _LT_AC_TAGVAR(always_export_symbols, $1)=yes + if test "$aix_use_runtimelinking" = yes; then + # Warning - without using the other runtime loading flags (-brtl), + # -berok will link without error, but may produce a broken library. + _LT_AC_TAGVAR(allow_undefined_flag, $1)='-berok' + # Determine the default libpath from the value encoded in an empty executable. + _LT_AC_SYS_LIBPATH_AIX + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-blibpath:$libdir:'"$aix_libpath" + _LT_AC_TAGVAR(archive_expsym_cmds, $1)="\$CC"' -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags `if test "x${allow_undefined_flag}" != "x"; then echo "${wl}${allow_undefined_flag}"; else :; fi` '"\${wl}$exp_sym_flag:\$export_symbols $shared_flag" + else + if test "$host_cpu" = ia64; then + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-R $libdir:/usr/lib:/lib' + _LT_AC_TAGVAR(allow_undefined_flag, $1)="-z nodefs" + _LT_AC_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags ${wl}${allow_undefined_flag} '"\${wl}$exp_sym_flag:\$export_symbols" + else + # Determine the default libpath from the value encoded in an empty executable. + _LT_AC_SYS_LIBPATH_AIX + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-blibpath:$libdir:'"$aix_libpath" + # Warning - without using the other run time loading flags, + # -berok will link without error, but may produce a broken library. + _LT_AC_TAGVAR(no_undefined_flag, $1)=' ${wl}-bernotok' + _LT_AC_TAGVAR(allow_undefined_flag, $1)=' ${wl}-berok' + # Exported symbols can be pulled into shared objects from archives + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)='$convenience' + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=yes + # This is similar to how AIX traditionally builds its shared libraries. + _LT_AC_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs ${wl}-bnoentry $compiler_flags ${wl}-bE:$export_symbols${allow_undefined_flag}~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$soname' + fi + fi + ;; + + amigaos*) + _LT_AC_TAGVAR(archive_cmds, $1)='$rm $output_objdir/a2ixlibrary.data~$echo "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$echo "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$echo "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$echo "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes + # see comment about different semantics on the GNU ld section + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + + bsdi[[45]]*) + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)=-rdynamic + ;; + + cygwin* | mingw* | pw32*) + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)=' ' + _LT_AC_TAGVAR(allow_undefined_flag, $1)=unsupported + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=".dll" + # FIXME: Setting linknames here is a bad hack. + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -o $lib $libobjs $compiler_flags `echo "$deplibs" | $SED -e '\''s/ -lc$//'\''` -link -dll~linknames=' + # The linker will automatically build a .lib file if we build a DLL. + _LT_AC_TAGVAR(old_archive_From_new_cmds, $1)='true' + # FIXME: Should let the user specify the lib program. + _LT_AC_TAGVAR(old_archive_cmds, $1)='lib -OUT:$oldlib$oldobjs$old_deplibs' + _LT_AC_TAGVAR(fix_srcfile_path, $1)='`cygpath -w "$srcfile"`' + _LT_AC_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + ;; + + darwin* | rhapsody*) + _LT_AC_TAGVAR(allow_undefined_flag, $1)="$_lt_dar_allow_undefined" + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_AC_TAGVAR(hardcode_direct, $1)=no + _LT_AC_TAGVAR(hardcode_automatic, $1)=yes + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=unsupported + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)='' + _LT_AC_TAGVAR(link_all_deplibs, $1)=yes + if test "$GCC" = yes ; then + output_verbose_link_cmd='echo' + _LT_AC_TAGVAR(archive_cmds, $1)="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod${_lt_dsymutil}" + _LT_AC_TAGVAR(module_cmds, $1)="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags${_lt_dsymutil}" + _LT_AC_TAGVAR(archive_expsym_cmds, $1)="sed 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring ${_lt_dar_single_mod}${_lt_dar_export_syms}${_lt_dsymutil}" + _LT_AC_TAGVAR(module_expsym_cmds, $1)="sed -e 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags${_lt_dar_export_syms}${_lt_dsymutil}" + else + case $cc_basename in + xlc*) + output_verbose_link_cmd='echo' + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -qmkshrobj $allow_undefined_flag -o $lib $libobjs $deplibs $compiler_flags ${wl}-install_name ${wl}`echo $rpath/$soname` $xlcverstring' + _LT_AC_TAGVAR(module_cmds, $1)='$CC $allow_undefined_flag -o $lib -bundle $libobjs $deplibs$compiler_flags' + # Don't fix this by using the ld -exported_symbols_list flag, it doesn't exist in older darwin lds + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='sed -e "s,#.*,," -e "s,^[ ]*,," -e "s,^\(..*\),_&," < $export_symbols > $output_objdir/${libname}-symbols.expsym~$CC -qmkshrobj $allow_undefined_flag -o $lib $libobjs $deplibs $compiler_flags ${wl}-install_name ${wl}$rpath/$soname $xlcverstring~nmedit -s $output_objdir/${libname}-symbols.expsym ${lib}' + _LT_AC_TAGVAR(module_expsym_cmds, $1)='sed -e "s,#.*,," -e "s,^[ ]*,," -e "s,^\(..*\),_&," < $export_symbols > $output_objdir/${libname}-symbols.expsym~$CC $allow_undefined_flag -o $lib -bundle $libobjs $deplibs$compiler_flags~nmedit -s $output_objdir/${libname}-symbols.expsym ${lib}' + ;; + *) + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + esac + fi + ;; + + dgux*) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + freebsd1*) + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + + # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor + # support. Future versions do this automatically, but an explicit c++rt0.o + # does not break anything, and helps significantly (at the cost of a little + # extra space). + freebsd2.2*) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags /usr/lib/c++rt0.o' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + # Unfortunately, older versions of FreeBSD 2 do not have this feature. + freebsd2*) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + # FreeBSD 3 and greater uses gcc -shared to do shared libraries. + freebsd* | dragonfly*) + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -o $lib $libobjs $deplibs $compiler_flags' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + hpux9*) + if test "$GCC" = yes; then + _LT_AC_TAGVAR(archive_cmds, $1)='$rm $output_objdir/$soname~$CC -shared -fPIC ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $libobjs $deplibs $compiler_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib' + else + _LT_AC_TAGVAR(archive_cmds, $1)='$rm $output_objdir/$soname~$LD -b +b $install_libdir -o $output_objdir/$soname $libobjs $deplibs $linker_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib' + fi + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E' + ;; + + hpux10*) + if test "$GCC" = yes -a "$with_gnu_ld" = no; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -fPIC ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + else + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags' + fi + if test "$with_gnu_ld" = no; then + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E' + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes + fi + ;; + + hpux11*) + if test "$GCC" = yes -a "$with_gnu_ld" = no; then + case $host_cpu in + hppa*64*) + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared ${wl}+h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + ia64*) + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -fPIC ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + else + case $host_cpu in + hppa*64*) + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + ia64*) + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + fi + if test "$with_gnu_ld" = no; then + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + + case $host_cpu in + hppa*64*|ia64*) + _LT_AC_TAGVAR(hardcode_libdir_flag_spec_ld, $1)='+b $libdir' + _LT_AC_TAGVAR(hardcode_direct, $1)=no + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + *) + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E' + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes + ;; + esac + fi + ;; + + irix5* | irix6* | nonstopux*) + if test "$GCC" = yes; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + else + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -shared $libobjs $deplibs $linker_flags -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec_ld, $1)='-rpath $libdir' + fi + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_AC_TAGVAR(link_all_deplibs, $1)=yes + ;; + + netbsd*) + if echo __ELF__ | $CC -E - | grep __ELF__ >/dev/null; then + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' # a.out + else + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -shared -o $lib $libobjs $deplibs $linker_flags' # ELF + fi + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + newsos6) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + openbsd*) + if test -f /usr/libexec/ld.so; then + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags ${wl}-retain-symbols-file,$export_symbols' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir' + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E' + else + case $host_os in + openbsd[[01]].* | openbsd2.[[0-7]] | openbsd2.[[0-7]].*) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + ;; + *) + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir' + ;; + esac + fi + else + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + os2*) + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes + _LT_AC_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_AC_TAGVAR(archive_cmds, $1)='$echo "LIBRARY $libname INITINSTANCE" > $output_objdir/$libname.def~$echo "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~$echo DATA >> $output_objdir/$libname.def~$echo " SINGLE NONSHARED" >> $output_objdir/$libname.def~$echo EXPORTS >> $output_objdir/$libname.def~emxexp $libobjs >> $output_objdir/$libname.def~$CC -Zdll -Zcrtdll -o $lib $libobjs $deplibs $compiler_flags $output_objdir/$libname.def' + _LT_AC_TAGVAR(old_archive_From_new_cmds, $1)='emximp -o $output_objdir/$libname.a $output_objdir/$libname.def' + ;; + + osf3*) + if test "$GCC" = yes; then + _LT_AC_TAGVAR(allow_undefined_flag, $1)=' ${wl}-expect_unresolved ${wl}\*' + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + else + _LT_AC_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -shared${allow_undefined_flag} $libobjs $deplibs $linker_flags -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib' + fi + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + ;; + + osf4* | osf5*) # as osf3* with the addition of -msym flag + if test "$GCC" = yes; then + _LT_AC_TAGVAR(allow_undefined_flag, $1)=' ${wl}-expect_unresolved ${wl}\*' + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags ${wl}-msym ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + else + _LT_AC_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -shared${allow_undefined_flag} $libobjs $deplibs $linker_flags -msym -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done; echo "-hidden">> $lib.exp~ + $LD -shared${allow_undefined_flag} -input $lib.exp $linker_flags $libobjs $deplibs -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib~$rm $lib.exp' + + # Both c and cxx compiler support -rpath directly + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' + fi + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + ;; + + solaris*) + _LT_AC_TAGVAR(no_undefined_flag, $1)=' -z text' + if test "$GCC" = yes; then + wlarc='${wl}' + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared ${wl}-h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~$echo "local: *; };" >> $lib.exp~ + $CC -shared ${wl}-M ${wl}$lib.exp ${wl}-h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags~$rm $lib.exp' + else + wlarc='' + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -G${allow_undefined_flag} -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~$echo "local: *; };" >> $lib.exp~ + $LD -G${allow_undefined_flag} -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$rm $lib.exp' + fi + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + case $host_os in + solaris2.[[0-5]] | solaris2.[[0-5]].*) ;; + *) + # The compiler driver will combine and reorder linker options, + # but understands `-z linker_flag'. GCC discards it without `$wl', + # but is careful enough not to reorder. + # Supported since Solaris 2.6 (maybe 2.5.1?) + if test "$GCC" = yes; then + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)='${wl}-z ${wl}allextract$convenience ${wl}-z ${wl}defaultextract' + else + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)='-z allextract$convenience -z defaultextract' + fi + ;; + esac + _LT_AC_TAGVAR(link_all_deplibs, $1)=yes + ;; + + sunos4*) + if test "x$host_vendor" = xsequent; then + # Use $CC to link under sequent, because it throws in some extra .o + # files that make .init and .fini sections work. + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -G ${wl}-h $soname -o $lib $libobjs $deplibs $compiler_flags' + else + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linker_flags' + fi + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + sysv4) + case $host_vendor in + sni) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes # is this really true??? + ;; + siemens) + ## LD is ld it makes a PLAMLIB + ## CC just makes a GrossModule. + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -G -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(reload_cmds, $1)='$CC -r -o $output$reload_objs' + _LT_AC_TAGVAR(hardcode_direct, $1)=no + ;; + motorola) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(hardcode_direct, $1)=no #Motorola manual says yes, but my tests say they lie + ;; + esac + runpath_var='LD_RUN_PATH' + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + sysv4.3*) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='-Bexport' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + runpath_var=LD_RUN_PATH + hardcode_runpath_var=yes + _LT_AC_TAGVAR(ld_shlibs, $1)=yes + fi + ;; + + sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[[01]].[[10]]* | unixware7* | sco3.2v5.0.[[024]]*) + _LT_AC_TAGVAR(no_undefined_flag, $1)='${wl}-z,text' + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + runpath_var='LD_RUN_PATH' + + if test "$GCC" = yes; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + else + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + fi + ;; + + sysv5* | sco3.2v5* | sco5v6*) + # Note: We can NOT use -z defs as we might desire, because we do not + # link with -lc, and that would cause any symbols used from libc to + # always be unresolved, which means just about no library would + # ever link correctly. If we're not using GNU ld we use -z text + # though, which does catch some bad symbols but isn't as heavy-handed + # as -z defs. + _LT_AC_TAGVAR(no_undefined_flag, $1)='${wl}-z,text' + _LT_AC_TAGVAR(allow_undefined_flag, $1)='${wl}-z,nodefs' + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='`test -z "$SCOABSPATH" && echo ${wl}-R,$libdir`' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_AC_TAGVAR(link_all_deplibs, $1)=yes + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-Bexport' + runpath_var='LD_RUN_PATH' + + if test "$GCC" = yes; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared ${wl}-h,\${SCOABSPATH:+${install_libdir}/}$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,\${SCOABSPATH:+${install_libdir}/}$soname -o $lib $libobjs $deplibs $compiler_flags' + else + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -G ${wl}-h,\${SCOABSPATH:+${install_libdir}/}$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,\${SCOABSPATH:+${install_libdir}/}$soname -o $lib $libobjs $deplibs $compiler_flags' + fi + ;; + + uts4*) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + *) + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + esac + fi +]) +AC_MSG_RESULT([$_LT_AC_TAGVAR(ld_shlibs, $1)]) +test "$_LT_AC_TAGVAR(ld_shlibs, $1)" = no && can_build_shared=no + +# +# Do we need to explicitly link libc? +# +case "x$_LT_AC_TAGVAR(archive_cmds_need_lc, $1)" in +x|xyes) + # Assume -lc should be added + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=yes + + if test "$enable_shared" = yes && test "$GCC" = yes; then + case $_LT_AC_TAGVAR(archive_cmds, $1) in + *'~'*) + # FIXME: we may have to deal with multi-command sequences. + ;; + '$CC '*) + # Test whether the compiler implicitly links with -lc since on some + # systems, -lgcc has to come before -lc. If gcc already passes -lc + # to ld, don't add -lc before -lgcc. + AC_MSG_CHECKING([whether -lc should be explicitly linked in]) + $rm conftest* + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + if AC_TRY_EVAL(ac_compile) 2>conftest.err; then + soname=conftest + lib=conftest + libobjs=conftest.$ac_objext + deplibs= + wl=$_LT_AC_TAGVAR(lt_prog_compiler_wl, $1) + pic_flag=$_LT_AC_TAGVAR(lt_prog_compiler_pic, $1) + compiler_flags=-v + linker_flags=-v + verstring= + output_objdir=. + libname=conftest + lt_save_allow_undefined_flag=$_LT_AC_TAGVAR(allow_undefined_flag, $1) + _LT_AC_TAGVAR(allow_undefined_flag, $1)= + if AC_TRY_EVAL(_LT_AC_TAGVAR(archive_cmds, $1) 2\>\&1 \| grep \" -lc \" \>/dev/null 2\>\&1) + then + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=no + else + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=yes + fi + _LT_AC_TAGVAR(allow_undefined_flag, $1)=$lt_save_allow_undefined_flag + else + cat conftest.err 1>&5 + fi + $rm conftest* + AC_MSG_RESULT([$_LT_AC_TAGVAR(archive_cmds_need_lc, $1)]) + ;; + esac + fi + ;; +esac +])# AC_LIBTOOL_PROG_LD_SHLIBS + + +# _LT_AC_FILE_LTDLL_C +# ------------------- +# Be careful that the start marker always follows a newline. +AC_DEFUN([_LT_AC_FILE_LTDLL_C], [ +# /* ltdll.c starts here */ +# #define WIN32_LEAN_AND_MEAN +# #include +# #undef WIN32_LEAN_AND_MEAN +# #include +# +# #ifndef __CYGWIN__ +# # ifdef __CYGWIN32__ +# # define __CYGWIN__ __CYGWIN32__ +# # endif +# #endif +# +# #ifdef __cplusplus +# extern "C" { +# #endif +# BOOL APIENTRY DllMain (HINSTANCE hInst, DWORD reason, LPVOID reserved); +# #ifdef __cplusplus +# } +# #endif +# +# #ifdef __CYGWIN__ +# #include +# DECLARE_CYGWIN_DLL( DllMain ); +# #endif +# HINSTANCE __hDllInstance_base; +# +# BOOL APIENTRY +# DllMain (HINSTANCE hInst, DWORD reason, LPVOID reserved) +# { +# __hDllInstance_base = hInst; +# return TRUE; +# } +# /* ltdll.c ends here */ +])# _LT_AC_FILE_LTDLL_C + + +# _LT_AC_TAGVAR(VARNAME, [TAGNAME]) +# --------------------------------- +AC_DEFUN([_LT_AC_TAGVAR], [ifelse([$2], [], [$1], [$1_$2])]) + + +# old names +AC_DEFUN([AM_PROG_LIBTOOL], [AC_PROG_LIBTOOL]) +AC_DEFUN([AM_ENABLE_SHARED], [AC_ENABLE_SHARED($@)]) +AC_DEFUN([AM_ENABLE_STATIC], [AC_ENABLE_STATIC($@)]) +AC_DEFUN([AM_DISABLE_SHARED], [AC_DISABLE_SHARED($@)]) +AC_DEFUN([AM_DISABLE_STATIC], [AC_DISABLE_STATIC($@)]) +AC_DEFUN([AM_PROG_LD], [AC_PROG_LD]) +AC_DEFUN([AM_PROG_NM], [AC_PROG_NM]) + +# This is just to silence aclocal about the macro not being used +ifelse([AC_DISABLE_FAST_INSTALL]) + +AC_DEFUN([LT_AC_PROG_GCJ], +[AC_CHECK_TOOL(GCJ, gcj, no) + test "x${GCJFLAGS+set}" = xset || GCJFLAGS="-g -O2" + AC_SUBST(GCJFLAGS) +]) + +AC_DEFUN([LT_AC_PROG_RC], +[AC_CHECK_TOOL(RC, windres, no) +]) + + +# Cheap backport of AS_EXECUTABLE_P and required macros +# from Autoconf 2.59; we should not use $as_executable_p directly. + +# _AS_TEST_PREPARE +# ---------------- +m4_ifndef([_AS_TEST_PREPARE], +[m4_defun([_AS_TEST_PREPARE], +[if test -x / >/dev/null 2>&1; then + as_executable_p='test -x' +else + as_executable_p='test -f' +fi +])])# _AS_TEST_PREPARE + +# AS_EXECUTABLE_P +# --------------- +# Check whether a file is executable. +m4_ifndef([AS_EXECUTABLE_P], +[m4_defun([AS_EXECUTABLE_P], +[AS_REQUIRE([_AS_TEST_PREPARE])dnl +$as_executable_p $1[]dnl +])])# AS_EXECUTABLE_P + +# NOTE: This macro has been submitted for inclusion into # +# GNU Autoconf as AC_PROG_SED. When it is available in # +# a released version of Autoconf we should remove this # +# macro and use it instead. # +# LT_AC_PROG_SED +# -------------- +# Check for a fully-functional sed program, that truncates +# as few characters as possible. Prefer GNU sed if found. +AC_DEFUN([LT_AC_PROG_SED], +[AC_MSG_CHECKING([for a sed that does not truncate output]) +AC_CACHE_VAL(lt_cv_path_SED, +[# Loop through the user's path and test for sed and gsed. +# Then use that list of sed's as ones to test for truncation. +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for lt_ac_prog in sed gsed; do + for ac_exec_ext in '' $ac_executable_extensions; do + if AS_EXECUTABLE_P(["$as_dir/$lt_ac_prog$ac_exec_ext"]); then + lt_ac_sed_list="$lt_ac_sed_list $as_dir/$lt_ac_prog$ac_exec_ext" + fi + done + done +done +IFS=$as_save_IFS +lt_ac_max=0 +lt_ac_count=0 +# Add /usr/xpg4/bin/sed as it is typically found on Solaris +# along with /bin/sed that truncates output. +for lt_ac_sed in $lt_ac_sed_list /usr/xpg4/bin/sed; do + test ! -f $lt_ac_sed && continue + cat /dev/null > conftest.in + lt_ac_count=0 + echo $ECHO_N "0123456789$ECHO_C" >conftest.in + # Check for GNU sed and select it if it is found. + if "$lt_ac_sed" --version 2>&1 < /dev/null | grep 'GNU' > /dev/null; then + lt_cv_path_SED=$lt_ac_sed + break + fi + while true; do + cat conftest.in conftest.in >conftest.tmp + mv conftest.tmp conftest.in + cp conftest.in conftest.nl + echo >>conftest.nl + $lt_ac_sed -e 's/a$//' < conftest.nl >conftest.out || break + cmp -s conftest.out conftest.nl || break + # 10000 chars as input seems more than enough + test $lt_ac_count -gt 10 && break + lt_ac_count=`expr $lt_ac_count + 1` + if test $lt_ac_count -gt $lt_ac_max; then + lt_ac_max=$lt_ac_count + lt_cv_path_SED=$lt_ac_sed + fi + done +done +]) +SED=$lt_cv_path_SED +AC_SUBST([SED]) +AC_MSG_RESULT([$SED]) +]) + +# Copyright (C) 2002, 2003, 2005, 2006, 2007 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_AUTOMAKE_VERSION(VERSION) +# ---------------------------- +# Automake X.Y traces this macro to ensure aclocal.m4 has been +# generated from the m4 files accompanying Automake X.Y. +# (This private macro should not be called outside this file.) +AC_DEFUN([AM_AUTOMAKE_VERSION], +[am__api_version='1.10' +dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to +dnl require some minimum version. Point them to the right macro. +m4_if([$1], [1.10.1], [], + [AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl +]) + +# _AM_AUTOCONF_VERSION(VERSION) +# ----------------------------- +# aclocal traces this macro to find the Autoconf version. +# This is a private macro too. Using m4_define simplifies +# the logic in aclocal, which can simply ignore this definition. +m4_define([_AM_AUTOCONF_VERSION], []) + +# AM_SET_CURRENT_AUTOMAKE_VERSION +# ------------------------------- +# Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced. +# This function is AC_REQUIREd by AC_INIT_AUTOMAKE. +AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION], +[AM_AUTOMAKE_VERSION([1.10.1])dnl +m4_ifndef([AC_AUTOCONF_VERSION], + [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl +_AM_AUTOCONF_VERSION(AC_AUTOCONF_VERSION)]) + +# AM_AUX_DIR_EXPAND -*- Autoconf -*- + +# Copyright (C) 2001, 2003, 2005 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# For projects using AC_CONFIG_AUX_DIR([foo]), Autoconf sets +# $ac_aux_dir to `$srcdir/foo'. In other projects, it is set to +# `$srcdir', `$srcdir/..', or `$srcdir/../..'. +# +# Of course, Automake must honor this variable whenever it calls a +# tool from the auxiliary directory. The problem is that $srcdir (and +# therefore $ac_aux_dir as well) can be either absolute or relative, +# depending on how configure is run. This is pretty annoying, since +# it makes $ac_aux_dir quite unusable in subdirectories: in the top +# source directory, any form will work fine, but in subdirectories a +# relative path needs to be adjusted first. +# +# $ac_aux_dir/missing +# fails when called from a subdirectory if $ac_aux_dir is relative +# $top_srcdir/$ac_aux_dir/missing +# fails if $ac_aux_dir is absolute, +# fails when called from a subdirectory in a VPATH build with +# a relative $ac_aux_dir +# +# The reason of the latter failure is that $top_srcdir and $ac_aux_dir +# are both prefixed by $srcdir. In an in-source build this is usually +# harmless because $srcdir is `.', but things will broke when you +# start a VPATH build or use an absolute $srcdir. +# +# So we could use something similar to $top_srcdir/$ac_aux_dir/missing, +# iff we strip the leading $srcdir from $ac_aux_dir. That would be: +# am_aux_dir='\$(top_srcdir)/'`expr "$ac_aux_dir" : "$srcdir//*\(.*\)"` +# and then we would define $MISSING as +# MISSING="\${SHELL} $am_aux_dir/missing" +# This will work as long as MISSING is not called from configure, because +# unfortunately $(top_srcdir) has no meaning in configure. +# However there are other variables, like CC, which are often used in +# configure, and could therefore not use this "fixed" $ac_aux_dir. +# +# Another solution, used here, is to always expand $ac_aux_dir to an +# absolute PATH. The drawback is that using absolute paths prevent a +# configured tree to be moved without reconfiguration. + +AC_DEFUN([AM_AUX_DIR_EXPAND], +[dnl Rely on autoconf to set up CDPATH properly. +AC_PREREQ([2.50])dnl +# expand $ac_aux_dir to an absolute path +am_aux_dir=`cd $ac_aux_dir && pwd` +]) + +# AM_CONDITIONAL -*- Autoconf -*- + +# Copyright (C) 1997, 2000, 2001, 2003, 2004, 2005, 2006 +# Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 8 + +# AM_CONDITIONAL(NAME, SHELL-CONDITION) +# ------------------------------------- +# Define a conditional. +AC_DEFUN([AM_CONDITIONAL], +[AC_PREREQ(2.52)dnl + ifelse([$1], [TRUE], [AC_FATAL([$0: invalid condition: $1])], + [$1], [FALSE], [AC_FATAL([$0: invalid condition: $1])])dnl +AC_SUBST([$1_TRUE])dnl +AC_SUBST([$1_FALSE])dnl +_AM_SUBST_NOTMAKE([$1_TRUE])dnl +_AM_SUBST_NOTMAKE([$1_FALSE])dnl +if $2; then + $1_TRUE= + $1_FALSE='#' +else + $1_TRUE='#' + $1_FALSE= +fi +AC_CONFIG_COMMANDS_PRE( +[if test -z "${$1_TRUE}" && test -z "${$1_FALSE}"; then + AC_MSG_ERROR([[conditional "$1" was never defined. +Usually this means the macro was only invoked conditionally.]]) +fi])]) + +# Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006 +# Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 9 + +# There are a few dirty hacks below to avoid letting `AC_PROG_CC' be +# written in clear, in which case automake, when reading aclocal.m4, +# will think it sees a *use*, and therefore will trigger all it's +# C support machinery. Also note that it means that autoscan, seeing +# CC etc. in the Makefile, will ask for an AC_PROG_CC use... + + +# _AM_DEPENDENCIES(NAME) +# ---------------------- +# See how the compiler implements dependency checking. +# NAME is "CC", "CXX", "GCJ", or "OBJC". +# We try a few techniques and use that to set a single cache variable. +# +# We don't AC_REQUIRE the corresponding AC_PROG_CC since the latter was +# modified to invoke _AM_DEPENDENCIES(CC); we would have a circular +# dependency, and given that the user is not expected to run this macro, +# just rely on AC_PROG_CC. +AC_DEFUN([_AM_DEPENDENCIES], +[AC_REQUIRE([AM_SET_DEPDIR])dnl +AC_REQUIRE([AM_OUTPUT_DEPENDENCY_COMMANDS])dnl +AC_REQUIRE([AM_MAKE_INCLUDE])dnl +AC_REQUIRE([AM_DEP_TRACK])dnl + +ifelse([$1], CC, [depcc="$CC" am_compiler_list=], + [$1], CXX, [depcc="$CXX" am_compiler_list=], + [$1], OBJC, [depcc="$OBJC" am_compiler_list='gcc3 gcc'], + [$1], UPC, [depcc="$UPC" am_compiler_list=], + [$1], GCJ, [depcc="$GCJ" am_compiler_list='gcc3 gcc'], + [depcc="$$1" am_compiler_list=]) + +AC_CACHE_CHECK([dependency style of $depcc], + [am_cv_$1_dependencies_compiler_type], +[if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then + # We make a subdir and do the tests there. Otherwise we can end up + # making bogus files that we don't know about and never remove. For + # instance it was reported that on HP-UX the gcc test will end up + # making a dummy file named `D' -- because `-MD' means `put the output + # in D'. + mkdir conftest.dir + # Copy depcomp to subdir because otherwise we won't find it if we're + # using a relative directory. + cp "$am_depcomp" conftest.dir + cd conftest.dir + # We will build objects and dependencies in a subdirectory because + # it helps to detect inapplicable dependency modes. For instance + # both Tru64's cc and ICC support -MD to output dependencies as a + # side effect of compilation, but ICC will put the dependencies in + # the current directory while Tru64 will put them in the object + # directory. + mkdir sub + + am_cv_$1_dependencies_compiler_type=none + if test "$am_compiler_list" = ""; then + am_compiler_list=`sed -n ['s/^#*\([a-zA-Z0-9]*\))$/\1/p'] < ./depcomp` + fi + for depmode in $am_compiler_list; do + # Setup a source with many dependencies, because some compilers + # like to wrap large dependency lists on column 80 (with \), and + # we should not choose a depcomp mode which is confused by this. + # + # We need to recreate these files for each test, as the compiler may + # overwrite some of them when testing with obscure command lines. + # This happens at least with the AIX C compiler. + : > sub/conftest.c + for i in 1 2 3 4 5 6; do + echo '#include "conftst'$i'.h"' >> sub/conftest.c + # Using `: > sub/conftst$i.h' creates only sub/conftst1.h with + # Solaris 8's {/usr,}/bin/sh. + touch sub/conftst$i.h + done + echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf + + case $depmode in + nosideeffect) + # after this tag, mechanisms are not by side-effect, so they'll + # only be used when explicitly requested + if test "x$enable_dependency_tracking" = xyes; then + continue + else + break + fi + ;; + none) break ;; + esac + # We check with `-c' and `-o' for the sake of the "dashmstdout" + # mode. It turns out that the SunPro C++ compiler does not properly + # handle `-M -o', and we need to detect this. + if depmode=$depmode \ + source=sub/conftest.c object=sub/conftest.${OBJEXT-o} \ + depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ + $SHELL ./depcomp $depcc -c -o sub/conftest.${OBJEXT-o} sub/conftest.c \ + >/dev/null 2>conftest.err && + grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && + grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && + grep sub/conftest.${OBJEXT-o} sub/conftest.Po > /dev/null 2>&1 && + ${MAKE-make} -s -f confmf > /dev/null 2>&1; then + # icc doesn't choke on unknown options, it will just issue warnings + # or remarks (even with -Werror). So we grep stderr for any message + # that says an option was ignored or not supported. + # When given -MP, icc 7.0 and 7.1 complain thusly: + # icc: Command line warning: ignoring option '-M'; no argument required + # The diagnosis changed in icc 8.0: + # icc: Command line remark: option '-MP' not supported + if (grep 'ignoring option' conftest.err || + grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else + am_cv_$1_dependencies_compiler_type=$depmode + break + fi + fi + done + + cd .. + rm -rf conftest.dir +else + am_cv_$1_dependencies_compiler_type=none +fi +]) +AC_SUBST([$1DEPMODE], [depmode=$am_cv_$1_dependencies_compiler_type]) +AM_CONDITIONAL([am__fastdep$1], [ + test "x$enable_dependency_tracking" != xno \ + && test "$am_cv_$1_dependencies_compiler_type" = gcc3]) +]) + + +# AM_SET_DEPDIR +# ------------- +# Choose a directory name for dependency files. +# This macro is AC_REQUIREd in _AM_DEPENDENCIES +AC_DEFUN([AM_SET_DEPDIR], +[AC_REQUIRE([AM_SET_LEADING_DOT])dnl +AC_SUBST([DEPDIR], ["${am__leading_dot}deps"])dnl +]) + + +# AM_DEP_TRACK +# ------------ +AC_DEFUN([AM_DEP_TRACK], +[AC_ARG_ENABLE(dependency-tracking, +[ --disable-dependency-tracking speeds up one-time build + --enable-dependency-tracking do not reject slow dependency extractors]) +if test "x$enable_dependency_tracking" != xno; then + am_depcomp="$ac_aux_dir/depcomp" + AMDEPBACKSLASH='\' +fi +AM_CONDITIONAL([AMDEP], [test "x$enable_dependency_tracking" != xno]) +AC_SUBST([AMDEPBACKSLASH])dnl +_AM_SUBST_NOTMAKE([AMDEPBACKSLASH])dnl +]) + +# Generate code to set up dependency tracking. -*- Autoconf -*- + +# Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 +# Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +#serial 3 + +# _AM_OUTPUT_DEPENDENCY_COMMANDS +# ------------------------------ +AC_DEFUN([_AM_OUTPUT_DEPENDENCY_COMMANDS], +[for mf in $CONFIG_FILES; do + # Strip MF so we end up with the name of the file. + mf=`echo "$mf" | sed -e 's/:.*$//'` + # Check whether this is an Automake generated Makefile or not. + # We used to match only the files named `Makefile.in', but + # some people rename them; so instead we look at the file content. + # Grep'ing the first line is not enough: some people post-process + # each Makefile.in and add a new line on top of each file to say so. + # Grep'ing the whole file is not good either: AIX grep has a line + # limit of 2048, but all sed's we know have understand at least 4000. + if sed -n 's,^#.*generated by automake.*,X,p' "$mf" | grep X >/dev/null 2>&1; then + dirpart=`AS_DIRNAME("$mf")` + else + continue + fi + # Extract the definition of DEPDIR, am__include, and am__quote + # from the Makefile without running `make'. + DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"` + test -z "$DEPDIR" && continue + am__include=`sed -n 's/^am__include = //p' < "$mf"` + test -z "am__include" && continue + am__quote=`sed -n 's/^am__quote = //p' < "$mf"` + # When using ansi2knr, U may be empty or an underscore; expand it + U=`sed -n 's/^U = //p' < "$mf"` + # Find all dependency output files, they are included files with + # $(DEPDIR) in their names. We invoke sed twice because it is the + # simplest approach to changing $(DEPDIR) to its actual value in the + # expansion. + for file in `sed -n " + s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \ + sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g' -e 's/\$U/'"$U"'/g'`; do + # Make sure the directory exists. + test -f "$dirpart/$file" && continue + fdir=`AS_DIRNAME(["$file"])` + AS_MKDIR_P([$dirpart/$fdir]) + # echo "creating $dirpart/$file" + echo '# dummy' > "$dirpart/$file" + done +done +])# _AM_OUTPUT_DEPENDENCY_COMMANDS + + +# AM_OUTPUT_DEPENDENCY_COMMANDS +# ----------------------------- +# This macro should only be invoked once -- use via AC_REQUIRE. +# +# This code is only required when automatic dependency tracking +# is enabled. FIXME. This creates each `.P' file that we will +# need in order to bootstrap the dependency handling code. +AC_DEFUN([AM_OUTPUT_DEPENDENCY_COMMANDS], +[AC_CONFIG_COMMANDS([depfiles], + [test x"$AMDEP_TRUE" != x"" || _AM_OUTPUT_DEPENDENCY_COMMANDS], + [AMDEP_TRUE="$AMDEP_TRUE" ac_aux_dir="$ac_aux_dir"]) +]) + +# Copyright (C) 1996, 1997, 2000, 2001, 2003, 2005 +# Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 8 + +# AM_CONFIG_HEADER is obsolete. It has been replaced by AC_CONFIG_HEADERS. +AU_DEFUN([AM_CONFIG_HEADER], [AC_CONFIG_HEADERS($@)]) + +# Do all the work for Automake. -*- Autoconf -*- + +# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, +# 2005, 2006, 2008 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 13 + +# This macro actually does too much. Some checks are only needed if +# your package does certain things. But this isn't really a big deal. + +# AM_INIT_AUTOMAKE(PACKAGE, VERSION, [NO-DEFINE]) +# AM_INIT_AUTOMAKE([OPTIONS]) +# ----------------------------------------------- +# The call with PACKAGE and VERSION arguments is the old style +# call (pre autoconf-2.50), which is being phased out. PACKAGE +# and VERSION should now be passed to AC_INIT and removed from +# the call to AM_INIT_AUTOMAKE. +# We support both call styles for the transition. After +# the next Automake release, Autoconf can make the AC_INIT +# arguments mandatory, and then we can depend on a new Autoconf +# release and drop the old call support. +AC_DEFUN([AM_INIT_AUTOMAKE], +[AC_PREREQ([2.60])dnl +dnl Autoconf wants to disallow AM_ names. We explicitly allow +dnl the ones we care about. +m4_pattern_allow([^AM_[A-Z]+FLAGS$])dnl +AC_REQUIRE([AM_SET_CURRENT_AUTOMAKE_VERSION])dnl +AC_REQUIRE([AC_PROG_INSTALL])dnl +if test "`cd $srcdir && pwd`" != "`pwd`"; then + # Use -I$(srcdir) only when $(srcdir) != ., so that make's output + # is not polluted with repeated "-I." + AC_SUBST([am__isrc], [' -I$(srcdir)'])_AM_SUBST_NOTMAKE([am__isrc])dnl + # test to see if srcdir already configured + if test -f $srcdir/config.status; then + AC_MSG_ERROR([source directory already configured; run "make distclean" there first]) + fi +fi + +# test whether we have cygpath +if test -z "$CYGPATH_W"; then + if (cygpath --version) >/dev/null 2>/dev/null; then + CYGPATH_W='cygpath -w' + else + CYGPATH_W=echo + fi +fi +AC_SUBST([CYGPATH_W]) + +# Define the identity of the package. +dnl Distinguish between old-style and new-style calls. +m4_ifval([$2], +[m4_ifval([$3], [_AM_SET_OPTION([no-define])])dnl + AC_SUBST([PACKAGE], [$1])dnl + AC_SUBST([VERSION], [$2])], +[_AM_SET_OPTIONS([$1])dnl +dnl Diagnose old-style AC_INIT with new-style AM_AUTOMAKE_INIT. +m4_if(m4_ifdef([AC_PACKAGE_NAME], 1)m4_ifdef([AC_PACKAGE_VERSION], 1), 11,, + [m4_fatal([AC_INIT should be called with package and version arguments])])dnl + AC_SUBST([PACKAGE], ['AC_PACKAGE_TARNAME'])dnl + AC_SUBST([VERSION], ['AC_PACKAGE_VERSION'])])dnl + +_AM_IF_OPTION([no-define],, +[AC_DEFINE_UNQUOTED(PACKAGE, "$PACKAGE", [Name of package]) + AC_DEFINE_UNQUOTED(VERSION, "$VERSION", [Version number of package])])dnl + +# Some tools Automake needs. +AC_REQUIRE([AM_SANITY_CHECK])dnl +AC_REQUIRE([AC_ARG_PROGRAM])dnl +AM_MISSING_PROG(ACLOCAL, aclocal-${am__api_version}) +AM_MISSING_PROG(AUTOCONF, autoconf) +AM_MISSING_PROG(AUTOMAKE, automake-${am__api_version}) +AM_MISSING_PROG(AUTOHEADER, autoheader) +AM_MISSING_PROG(MAKEINFO, makeinfo) +AM_PROG_INSTALL_SH +AM_PROG_INSTALL_STRIP +AC_REQUIRE([AM_PROG_MKDIR_P])dnl +# We need awk for the "check" target. The system "awk" is bad on +# some platforms. +AC_REQUIRE([AC_PROG_AWK])dnl +AC_REQUIRE([AC_PROG_MAKE_SET])dnl +AC_REQUIRE([AM_SET_LEADING_DOT])dnl +_AM_IF_OPTION([tar-ustar], [_AM_PROG_TAR([ustar])], + [_AM_IF_OPTION([tar-pax], [_AM_PROG_TAR([pax])], + [_AM_PROG_TAR([v7])])]) +_AM_IF_OPTION([no-dependencies],, +[AC_PROVIDE_IFELSE([AC_PROG_CC], + [_AM_DEPENDENCIES(CC)], + [define([AC_PROG_CC], + defn([AC_PROG_CC])[_AM_DEPENDENCIES(CC)])])dnl +AC_PROVIDE_IFELSE([AC_PROG_CXX], + [_AM_DEPENDENCIES(CXX)], + [define([AC_PROG_CXX], + defn([AC_PROG_CXX])[_AM_DEPENDENCIES(CXX)])])dnl +AC_PROVIDE_IFELSE([AC_PROG_OBJC], + [_AM_DEPENDENCIES(OBJC)], + [define([AC_PROG_OBJC], + defn([AC_PROG_OBJC])[_AM_DEPENDENCIES(OBJC)])])dnl +]) +]) + + +# When config.status generates a header, we must update the stamp-h file. +# This file resides in the same directory as the config header +# that is generated. The stamp files are numbered to have different names. + +# Autoconf calls _AC_AM_CONFIG_HEADER_HOOK (when defined) in the +# loop where config.status creates the headers, so we can generate +# our stamp files there. +AC_DEFUN([_AC_AM_CONFIG_HEADER_HOOK], +[# Compute $1's index in $config_headers. +_am_arg=$1 +_am_stamp_count=1 +for _am_header in $config_headers :; do + case $_am_header in + $_am_arg | $_am_arg:* ) + break ;; + * ) + _am_stamp_count=`expr $_am_stamp_count + 1` ;; + esac +done +echo "timestamp for $_am_arg" >`AS_DIRNAME(["$_am_arg"])`/stamp-h[]$_am_stamp_count]) + +# Copyright (C) 2001, 2003, 2005 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_PROG_INSTALL_SH +# ------------------ +# Define $install_sh. +AC_DEFUN([AM_PROG_INSTALL_SH], +[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl +install_sh=${install_sh-"\$(SHELL) $am_aux_dir/install-sh"} +AC_SUBST(install_sh)]) + +# Copyright (C) 2003, 2005 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 2 + +# Check whether the underlying file-system supports filenames +# with a leading dot. For instance MS-DOS doesn't. +AC_DEFUN([AM_SET_LEADING_DOT], +[rm -rf .tst 2>/dev/null +mkdir .tst 2>/dev/null +if test -d .tst; then + am__leading_dot=. +else + am__leading_dot=_ +fi +rmdir .tst 2>/dev/null +AC_SUBST([am__leading_dot])]) + +# Check to see how 'make' treats includes. -*- Autoconf -*- + +# Copyright (C) 2001, 2002, 2003, 2005 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 3 + +# AM_MAKE_INCLUDE() +# ----------------- +# Check to see how make treats includes. +AC_DEFUN([AM_MAKE_INCLUDE], +[am_make=${MAKE-make} +cat > confinc << 'END' +am__doit: + @echo done +.PHONY: am__doit +END +# If we don't find an include directive, just comment out the code. +AC_MSG_CHECKING([for style of include used by $am_make]) +am__include="#" +am__quote= +_am_result=none +# First try GNU make style include. +echo "include confinc" > confmf +# We grep out `Entering directory' and `Leaving directory' +# messages which can occur if `w' ends up in MAKEFLAGS. +# In particular we don't look at `^make:' because GNU make might +# be invoked under some other name (usually "gmake"), in which +# case it prints its new name instead of `make'. +if test "`$am_make -s -f confmf 2> /dev/null | grep -v 'ing directory'`" = "done"; then + am__include=include + am__quote= + _am_result=GNU +fi +# Now try BSD make style include. +if test "$am__include" = "#"; then + echo '.include "confinc"' > confmf + if test "`$am_make -s -f confmf 2> /dev/null`" = "done"; then + am__include=.include + am__quote="\"" + _am_result=BSD + fi +fi +AC_SUBST([am__include]) +AC_SUBST([am__quote]) +AC_MSG_RESULT([$_am_result]) +rm -f confinc confmf +]) + +# Fake the existence of programs that GNU maintainers use. -*- Autoconf -*- + +# Copyright (C) 1997, 1999, 2000, 2001, 2003, 2004, 2005 +# Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 5 + +# AM_MISSING_PROG(NAME, PROGRAM) +# ------------------------------ +AC_DEFUN([AM_MISSING_PROG], +[AC_REQUIRE([AM_MISSING_HAS_RUN]) +$1=${$1-"${am_missing_run}$2"} +AC_SUBST($1)]) + + +# AM_MISSING_HAS_RUN +# ------------------ +# Define MISSING if not defined so far and test if it supports --run. +# If it does, set am_missing_run to use it, otherwise, to nothing. +AC_DEFUN([AM_MISSING_HAS_RUN], +[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl +AC_REQUIRE_AUX_FILE([missing])dnl +test x"${MISSING+set}" = xset || MISSING="\${SHELL} $am_aux_dir/missing" +# Use eval to expand $SHELL +if eval "$MISSING --run true"; then + am_missing_run="$MISSING --run " +else + am_missing_run= + AC_MSG_WARN([`missing' script is too old or missing]) +fi +]) + +# Copyright (C) 2003, 2004, 2005, 2006 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_PROG_MKDIR_P +# --------------- +# Check for `mkdir -p'. +AC_DEFUN([AM_PROG_MKDIR_P], +[AC_PREREQ([2.60])dnl +AC_REQUIRE([AC_PROG_MKDIR_P])dnl +dnl Automake 1.8 to 1.9.6 used to define mkdir_p. We now use MKDIR_P, +dnl while keeping a definition of mkdir_p for backward compatibility. +dnl @MKDIR_P@ is magic: AC_OUTPUT adjusts its value for each Makefile. +dnl However we cannot define mkdir_p as $(MKDIR_P) for the sake of +dnl Makefile.ins that do not define MKDIR_P, so we do our own +dnl adjustment using top_builddir (which is defined more often than +dnl MKDIR_P). +AC_SUBST([mkdir_p], ["$MKDIR_P"])dnl +case $mkdir_p in + [[\\/$]]* | ?:[[\\/]]*) ;; + */*) mkdir_p="\$(top_builddir)/$mkdir_p" ;; +esac +]) + +# Helper functions for option handling. -*- Autoconf -*- + +# Copyright (C) 2001, 2002, 2003, 2005 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 3 + +# _AM_MANGLE_OPTION(NAME) +# ----------------------- +AC_DEFUN([_AM_MANGLE_OPTION], +[[_AM_OPTION_]m4_bpatsubst($1, [[^a-zA-Z0-9_]], [_])]) + +# _AM_SET_OPTION(NAME) +# ------------------------------ +# Set option NAME. Presently that only means defining a flag for this option. +AC_DEFUN([_AM_SET_OPTION], +[m4_define(_AM_MANGLE_OPTION([$1]), 1)]) + +# _AM_SET_OPTIONS(OPTIONS) +# ---------------------------------- +# OPTIONS is a space-separated list of Automake options. +AC_DEFUN([_AM_SET_OPTIONS], +[AC_FOREACH([_AM_Option], [$1], [_AM_SET_OPTION(_AM_Option)])]) + +# _AM_IF_OPTION(OPTION, IF-SET, [IF-NOT-SET]) +# ------------------------------------------- +# Execute IF-SET if OPTION is set, IF-NOT-SET otherwise. +AC_DEFUN([_AM_IF_OPTION], +[m4_ifset(_AM_MANGLE_OPTION([$1]), [$2], [$3])]) + +# Check to make sure that the build environment is sane. -*- Autoconf -*- + +# Copyright (C) 1996, 1997, 2000, 2001, 2003, 2005 +# Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 4 + +# AM_SANITY_CHECK +# --------------- +AC_DEFUN([AM_SANITY_CHECK], +[AC_MSG_CHECKING([whether build environment is sane]) +# Just in case +sleep 1 +echo timestamp > conftest.file +# Do `set' in a subshell so we don't clobber the current shell's +# arguments. Must try -L first in case configure is actually a +# symlink; some systems play weird games with the mod time of symlinks +# (eg FreeBSD returns the mod time of the symlink's containing +# directory). +if ( + set X `ls -Lt $srcdir/configure conftest.file 2> /dev/null` + if test "$[*]" = "X"; then + # -L didn't work. + set X `ls -t $srcdir/configure conftest.file` + fi + rm -f conftest.file + if test "$[*]" != "X $srcdir/configure conftest.file" \ + && test "$[*]" != "X conftest.file $srcdir/configure"; then + + # If neither matched, then we have a broken ls. This can happen + # if, for instance, CONFIG_SHELL is bash and it inherits a + # broken ls alias from the environment. This has actually + # happened. Such a system could not be considered "sane". + AC_MSG_ERROR([ls -t appears to fail. Make sure there is not a broken +alias in your environment]) + fi + + test "$[2]" = conftest.file + ) +then + # Ok. + : +else + AC_MSG_ERROR([newly created file is older than distributed files! +Check your system clock]) +fi +AC_MSG_RESULT(yes)]) + +# Copyright (C) 2001, 2003, 2005 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_PROG_INSTALL_STRIP +# --------------------- +# One issue with vendor `install' (even GNU) is that you can't +# specify the program used to strip binaries. This is especially +# annoying in cross-compiling environments, where the build's strip +# is unlikely to handle the host's binaries. +# Fortunately install-sh will honor a STRIPPROG variable, so we +# always use install-sh in `make install-strip', and initialize +# STRIPPROG with the value of the STRIP variable (set by the user). +AC_DEFUN([AM_PROG_INSTALL_STRIP], +[AC_REQUIRE([AM_PROG_INSTALL_SH])dnl +# Installed binaries are usually stripped using `strip' when the user +# run `make install-strip'. However `strip' might not be the right +# tool to use in cross-compilation environments, therefore Automake +# will honor the `STRIP' environment variable to overrule this program. +dnl Don't test for $cross_compiling = yes, because it might be `maybe'. +if test "$cross_compiling" != no; then + AC_CHECK_TOOL([STRIP], [strip], :) +fi +INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s" +AC_SUBST([INSTALL_STRIP_PROGRAM])]) + +# Copyright (C) 2006 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# _AM_SUBST_NOTMAKE(VARIABLE) +# --------------------------- +# Prevent Automake from outputting VARIABLE = @VARIABLE@ in Makefile.in. +# This macro is traced by Automake. +AC_DEFUN([_AM_SUBST_NOTMAKE]) + +# Check how to create a tarball. -*- Autoconf -*- + +# Copyright (C) 2004, 2005 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 2 + +# _AM_PROG_TAR(FORMAT) +# -------------------- +# Check how to create a tarball in format FORMAT. +# FORMAT should be one of `v7', `ustar', or `pax'. +# +# Substitute a variable $(am__tar) that is a command +# writing to stdout a FORMAT-tarball containing the directory +# $tardir. +# tardir=directory && $(am__tar) > result.tar +# +# Substitute a variable $(am__untar) that extract such +# a tarball read from stdin. +# $(am__untar) < result.tar +AC_DEFUN([_AM_PROG_TAR], +[# Always define AMTAR for backward compatibility. +AM_MISSING_PROG([AMTAR], [tar]) +m4_if([$1], [v7], + [am__tar='${AMTAR} chof - "$$tardir"'; am__untar='${AMTAR} xf -'], + [m4_case([$1], [ustar],, [pax],, + [m4_fatal([Unknown tar format])]) +AC_MSG_CHECKING([how to create a $1 tar archive]) +# Loop over all known methods to create a tar archive until one works. +_am_tools='gnutar m4_if([$1], [ustar], [plaintar]) pax cpio none' +_am_tools=${am_cv_prog_tar_$1-$_am_tools} +# Do not fold the above two line into one, because Tru64 sh and +# Solaris sh will not grok spaces in the rhs of `-'. +for _am_tool in $_am_tools +do + case $_am_tool in + gnutar) + for _am_tar in tar gnutar gtar; + do + AM_RUN_LOG([$_am_tar --version]) && break + done + am__tar="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$$tardir"' + am__tar_="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$tardir"' + am__untar="$_am_tar -xf -" + ;; + plaintar) + # Must skip GNU tar: if it does not support --format= it doesn't create + # ustar tarball either. + (tar --version) >/dev/null 2>&1 && continue + am__tar='tar chf - "$$tardir"' + am__tar_='tar chf - "$tardir"' + am__untar='tar xf -' + ;; + pax) + am__tar='pax -L -x $1 -w "$$tardir"' + am__tar_='pax -L -x $1 -w "$tardir"' + am__untar='pax -r' + ;; + cpio) + am__tar='find "$$tardir" -print | cpio -o -H $1 -L' + am__tar_='find "$tardir" -print | cpio -o -H $1 -L' + am__untar='cpio -i -H $1 -d' + ;; + none) + am__tar=false + am__tar_=false + am__untar=false + ;; + esac + + # If the value was cached, stop now. We just wanted to have am__tar + # and am__untar set. + test -n "${am_cv_prog_tar_$1}" && break + + # tar/untar a dummy directory, and stop if the command works + rm -rf conftest.dir + mkdir conftest.dir + echo GrepMe > conftest.dir/file + AM_RUN_LOG([tardir=conftest.dir && eval $am__tar_ >conftest.tar]) + rm -rf conftest.dir + if test -s conftest.tar; then + AM_RUN_LOG([$am__untar /dev/null 2>&1 && break + fi +done +rm -rf conftest.dir + +AC_CACHE_VAL([am_cv_prog_tar_$1], [am_cv_prog_tar_$1=$_am_tool]) +AC_MSG_RESULT([$am_cv_prog_tar_$1])]) +AC_SUBST([am__tar]) +AC_SUBST([am__untar]) +]) # _AM_PROG_TAR + diff --git a/third_party/libevent/android/config.h b/third_party/libevent/android/config.h new file mode 100644 index 0000000000..91f4ddaf14 --- /dev/null +++ b/third_party/libevent/android/config.h @@ -0,0 +1,266 @@ +/* Copied from Linux version and changed the features according Android, which + * is close to Linux */ + +/* Define if clock_gettime is available in libc */ +#define DNS_USE_CPU_CLOCK_FOR_ID 1 + +/* Define is no secure id variant is available */ +/* #undef DNS_USE_GETTIMEOFDAY_FOR_ID */ + +/* Define to 1 if you have the `clock_gettime' function. */ +#define HAVE_CLOCK_GETTIME 1 + +/* Define if /dev/poll is available */ +/* #undef HAVE_DEVPOLL */ + +/* Define to 1 if you have the header file. */ +#define HAVE_DLFCN_H 1 + +/* Define if your system supports the epoll system calls */ +#define HAVE_EPOLL 1 + +/* Define to 1 if you have the `epoll_ctl' function. */ +#define HAVE_EPOLL_CTL 1 + +/* Define if your system supports event ports */ +/* #undef HAVE_EVENT_PORTS */ + +/* Define to 1 if you have the `fcntl' function. */ +#define HAVE_FCNTL 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_FCNTL_H 1 + +/* Define to 1 if the system has the type `fd_mask'. */ +/* #undef HAVE_FD_MASK */ + +/* Define to 1 if you have the `getaddrinfo' function. */ +#define HAVE_GETADDRINFO 1 + +/* Define to 1 if you have the `getegid' function. */ +#define HAVE_GETEGID 1 + +/* Define to 1 if you have the `geteuid' function. */ +#define HAVE_GETEUID 1 + +/* Define to 1 if you have the `getnameinfo' function. */ +#define HAVE_GETNAMEINFO 1 + +/* Define to 1 if you have the `gettimeofday' function. */ +#define HAVE_GETTIMEOFDAY 1 + +/* Define to 1 if you have the `inet_ntop' function. */ +#define HAVE_INET_NTOP 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the `issetugid' function. */ +/* #undef HAVE_ISSETUGID */ + +/* Define to 1 if you have the `kqueue' function. */ +/* #undef HAVE_KQUEUE */ + +/* Define to 1 if you have the `nsl' library (-lnsl). */ +#define HAVE_LIBNSL 1 + +/* Define to 1 if you have the `resolv' library (-lresolv). */ +#define HAVE_LIBRESOLV 1 + +/* Define to 1 if you have the `rt' library (-lrt). */ +#define HAVE_LIBRT 1 + +/* Define to 1 if you have the `socket' library (-lsocket). */ +/* #undef HAVE_LIBSOCKET */ + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_NETINET_IN6_H */ + +/* Define to 1 if you have the `poll' function. */ +#define HAVE_POLL 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_POLL_H 1 + +/* Define to 1 if you have the `port_create' function. */ +/* #undef HAVE_PORT_CREATE */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_PORT_H */ + +/* Define to 1 if you have the `select' function. */ +#define HAVE_SELECT 1 + +/* Define if F_SETFD is defined in */ +#define HAVE_SETFD 1 + +/* Define to 1 if you have the `sigaction' function. */ +#define HAVE_SIGACTION 1 + +/* Define to 1 if you have the `signal' function. */ +#define HAVE_SIGNAL 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SIGNAL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDARG_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the `strlcpy' function. */ +#define HAVE_STRLCPY 1 + +/* Define to 1 if you have the `strsep' function. */ +#define HAVE_STRSEP 1 + +/* Define to 1 if you have the `strtok_r' function. */ +#define HAVE_STRTOK_R 1 + +/* Define to 1 if you have the `strtoll' function. */ +#define HAVE_STRTOLL 1 + +/* Define to 1 if the system has the type `struct in6_addr'. */ +#define HAVE_STRUCT_IN6_ADDR 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_DEVPOLL_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_EPOLL_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_EVENT_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_IOCTL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_PARAM_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_QUEUE_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SELECT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SOCKET_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define if TAILQ_FOREACH is defined in */ +#define HAVE_TAILQFOREACH 1 + +/* Define if timeradd is defined in */ +#define HAVE_TIMERADD 1 + +/* Define if timerclear is defined in */ +#define HAVE_TIMERCLEAR 1 + +/* Define if timercmp is defined in */ +#define HAVE_TIMERCMP 1 + +/* Define if timerisset is defined in */ +#define HAVE_TIMERISSET 1 + +/* Define to 1 if the system has the type `uint16_t'. */ +#define HAVE_UINT16_T 1 + +/* Define to 1 if the system has the type `uint32_t'. */ +#define HAVE_UINT32_T 1 + +/* Define to 1 if the system has the type `uint64_t'. */ +#define HAVE_UINT64_T 1 + +/* Define to 1 if the system has the type `uint8_t'. */ +#define HAVE_UINT8_T 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to 1 if you have the `vasprintf' function. */ +#define HAVE_VASPRINTF 1 + +/* Define if kqueue works correctly with pipes */ +/* #undef HAVE_WORKING_KQUEUE */ + +/* Name of package */ +#define PACKAGE "libevent" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "" + +/* The size of `int', as computed by sizeof. */ +#define SIZEOF_INT 4 + +/* The size of `long', as computed by sizeof. */ +#define SIZEOF_LONG 8 + +/* The size of `long long', as computed by sizeof. */ +#define SIZEOF_LONG_LONG 8 + +/* The size of `short', as computed by sizeof. */ +#define SIZEOF_SHORT 2 + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Define to 1 if you can safely include both and . */ +#define TIME_WITH_SYS_TIME 1 + +/* Version number of package */ +#define VERSION "1.4.13-stable" + +/* Define to appropriate substitue if compiler doesnt have __func__ */ +/* #undef __func__ */ + +/* Define to empty if `const' does not conform to ANSI C. */ +/* #undef const */ + +/* Define to `__inline__' or `__inline' if that's what the C compiler + calls it, or to nothing if 'inline' is not supported under any name. */ +#ifndef __cplusplus +/* #undef inline */ +#endif + +/* Define to `int' if does not define. */ +/* #undef pid_t */ + +/* Define to `unsigned int' if does not define. */ +/* #undef size_t */ + +/* Define to unsigned int if you dont have it */ +/* #undef socklen_t */ diff --git a/third_party/libevent/android/event-config.h b/third_party/libevent/android/event-config.h new file mode 100644 index 0000000000..7745519882 --- /dev/null +++ b/third_party/libevent/android/event-config.h @@ -0,0 +1,271 @@ +/* Copied from Linux version and changed the features according Android, which + * is close to Linux */ +#ifndef _EVENT_CONFIG_H_ +#define _EVENT_CONFIG_H_ +/* config.h. Generated from config.h.in by configure. */ +/* config.h.in. Generated from configure.in by autoheader. */ + +/* Define if clock_gettime is available in libc */ +#define _EVENT_DNS_USE_CPU_CLOCK_FOR_ID 1 + +/* Define is no secure id variant is available */ +/* #undef _EVENT_DNS_USE_GETTIMEOFDAY_FOR_ID */ + +/* Define to 1 if you have the `clock_gettime' function. */ +#define _EVENT_HAVE_CLOCK_GETTIME 1 + +/* Define if /dev/poll is available */ +/* #undef _EVENT_HAVE_DEVPOLL */ + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_DLFCN_H 1 + +/* Define if your system supports the epoll system calls */ +#define _EVENT_HAVE_EPOLL 1 + +/* Define to 1 if you have the `epoll_ctl' function. */ +#define _EVENT_HAVE_EPOLL_CTL 1 + +/* Define if your system supports event ports */ +/* #undef _EVENT_HAVE_EVENT_PORTS */ + +/* Define to 1 if you have the `fcntl' function. */ +#define _EVENT_HAVE_FCNTL 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_FCNTL_H 1 + +/* Define to 1 if the system has the type `fd_mask'. */ +/* #undef _EVENT_HAVE_FD_MASK 1 */ + +/* Define to 1 if you have the `getaddrinfo' function. */ +#define _EVENT_HAVE_GETADDRINFO 1 + +/* Define to 1 if you have the `getegid' function. */ +#define _EVENT_HAVE_GETEGID 1 + +/* Define to 1 if you have the `geteuid' function. */ +#define _EVENT_HAVE_GETEUID 1 + +/* Define to 1 if you have the `getnameinfo' function. */ +#define _EVENT_HAVE_GETNAMEINFO 1 + +/* Define to 1 if you have the `gettimeofday' function. */ +#define _EVENT_HAVE_GETTIMEOFDAY 1 + +/* Define to 1 if you have the `inet_ntop' function. */ +#define _EVENT_HAVE_INET_NTOP 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the `issetugid' function. */ +/* #undef _EVENT_HAVE_ISSETUGID */ + +/* Define to 1 if you have the `kqueue' function. */ +/* #undef _EVENT_HAVE_KQUEUE */ + +/* Define to 1 if you have the `nsl' library (-lnsl). */ +#define _EVENT_HAVE_LIBNSL 1 + +/* Define to 1 if you have the `resolv' library (-lresolv). */ +#define _EVENT_HAVE_LIBRESOLV 1 + +/* Define to 1 if you have the `rt' library (-lrt). */ +#define _EVENT_HAVE_LIBRT 1 + +/* Define to 1 if you have the `socket' library (-lsocket). */ +/* #undef _EVENT_HAVE_LIBSOCKET */ + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_MEMORY_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef _EVENT_HAVE_NETINET_IN6_H */ + +/* Define to 1 if you have the `poll' function. */ +#define _EVENT_HAVE_POLL 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_POLL_H 1 + +/* Define to 1 if you have the `port_create' function. */ +/* #undef _EVENT_HAVE_PORT_CREATE */ + +/* Define to 1 if you have the header file. */ +/* #undef _EVENT_HAVE_PORT_H */ + +/* Define to 1 if you have the `select' function. */ +#define _EVENT_HAVE_SELECT 1 + +/* Define if F_SETFD is defined in */ +#define _EVENT_HAVE_SETFD 1 + +/* Define to 1 if you have the `sigaction' function. */ +#define _EVENT_HAVE_SIGACTION 1 + +/* Define to 1 if you have the `signal' function. */ +#define _EVENT_HAVE_SIGNAL 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SIGNAL_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_STDARG_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_STRING_H 1 + +/* Define to 1 if you have the `strlcpy' function. */ +#define _EVENT_HAVE_STRLCPY 1 + +/* Define to 1 if you have the `strsep' function. */ +#define _EVENT_HAVE_STRSEP 1 + +/* Define to 1 if you have the `strtok_r' function. */ +#define _EVENT_HAVE_STRTOK_R 1 + +/* Define to 1 if you have the `strtoll' function. */ +#define _EVENT_HAVE_STRTOLL 1 + +/* Define to 1 if the system has the type `struct in6_addr'. */ +#define _EVENT_HAVE_STRUCT_IN6_ADDR 1 + +/* Define to 1 if you have the header file. */ +/* #undef _EVENT_HAVE_SYS_DEVPOLL_H */ + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_EPOLL_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef _EVENT_HAVE_SYS_EVENT_H */ + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_IOCTL_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_PARAM_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_QUEUE_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_SELECT_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_SOCKET_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_TYPES_H 1 + +/* Define if TAILQ_FOREACH is defined in */ +#define _EVENT_HAVE_TAILQFOREACH 1 + +/* Define if timeradd is defined in */ +#define _EVENT_HAVE_TIMERADD 1 + +/* Define if timerclear is defined in */ +#define _EVENT_HAVE_TIMERCLEAR 1 + +/* Define if timercmp is defined in */ +#define _EVENT_HAVE_TIMERCMP 1 + +/* Define if timerisset is defined in */ +#define _EVENT_HAVE_TIMERISSET 1 + +/* Define to 1 if the system has the type `uint16_t'. */ +#define _EVENT_HAVE_UINT16_T 1 + +/* Define to 1 if the system has the type `uint32_t'. */ +#define _EVENT_HAVE_UINT32_T 1 + +/* Define to 1 if the system has the type `uint64_t'. */ +#define _EVENT_HAVE_UINT64_T 1 + +/* Define to 1 if the system has the type `uint8_t'. */ +#define _EVENT_HAVE_UINT8_T 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_UNISTD_H 1 + +/* Define to 1 if you have the `vasprintf' function. */ +#define _EVENT_HAVE_VASPRINTF 1 + +/* Define if kqueue works correctly with pipes */ +/* #undef _EVENT_HAVE_WORKING_KQUEUE */ + +/* Name of package */ +#define _EVENT_PACKAGE "libevent" + +/* Define to the address where bug reports for this package should be sent. */ +#define _EVENT_PACKAGE_BUGREPORT "" + +/* Define to the full name of this package. */ +#define _EVENT_PACKAGE_NAME "" + +/* Define to the full name and version of this package. */ +#define _EVENT_PACKAGE_STRING "" + +/* Define to the one symbol short name of this package. */ +#define _EVENT_PACKAGE_TARNAME "" + +/* Define to the version of this package. */ +#define _EVENT_PACKAGE_VERSION "" + +/* The size of `int', as computed by sizeof. */ +#define _EVENT_SIZEOF_INT 4 + +/* The size of `long', as computed by sizeof. */ +#define _EVENT_SIZEOF_LONG 8 + +/* The size of `long long', as computed by sizeof. */ +#define _EVENT_SIZEOF_LONG_LONG 8 + +/* The size of `short', as computed by sizeof. */ +#define _EVENT_SIZEOF_SHORT 2 + +/* Define to 1 if you have the ANSI C header files. */ +#define _EVENT_STDC_HEADERS 1 + +/* Define to 1 if you can safely include both and . */ +#define _EVENT_TIME_WITH_SYS_TIME 1 + +/* Version number of package */ +#define _EVENT_VERSION "1.4.13-stable" + +/* Define to appropriate substitue if compiler doesnt have __func__ */ +/* #undef _EVENT___func__ */ + +/* Define to empty if `const' does not conform to ANSI C. */ +/* #undef _EVENT_const */ + +/* Define to `__inline__' or `__inline' if that's what the C compiler + calls it, or to nothing if 'inline' is not supported under any name. */ +#ifndef _EVENT___cplusplus +/* #undef _EVENT_inline */ +#endif + +/* Define to `int' if does not define. */ +/* #undef _EVENT_pid_t */ + +/* Define to `unsigned int' if does not define. */ +/* #undef _EVENT_size_t */ + +/* Define to unsigned int if you dont have it */ +/* #undef _EVENT_socklen_t */ +#endif diff --git a/third_party/libevent/autogen.sh b/third_party/libevent/autogen.sh new file mode 100644 index 0000000000..6d4275a639 --- /dev/null +++ b/third_party/libevent/autogen.sh @@ -0,0 +1,11 @@ +#!/bin/sh +LIBTOOLIZE=libtoolize +SYSNAME=`uname` +if [ "x$SYSNAME" = "xDarwin" ] ; then + LIBTOOLIZE=glibtoolize +fi +aclocal && \ + autoheader && \ + $LIBTOOLIZE && \ + autoconf && \ + automake --add-missing --copy diff --git a/third_party/libevent/buffer.c b/third_party/libevent/buffer.c new file mode 100644 index 0000000000..dfaca5d63f --- /dev/null +++ b/third_party/libevent/buffer.c @@ -0,0 +1,450 @@ +/* + * Copyright (c) 2002, 2003 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef WIN32 +#include +#include +#endif + +#ifdef HAVE_VASPRINTF +/* If we have vasprintf, we need to define this before we include stdio.h. */ +#define _GNU_SOURCE +#endif + +#include + +#ifdef HAVE_SYS_TIME_H +#include +#endif + +#ifdef HAVE_SYS_IOCTL_H +#include +#endif + +#include +#include +#include +#include +#include +#ifdef HAVE_STDARG_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "event.h" +#include "config.h" +#include "evutil.h" + +struct evbuffer * +evbuffer_new(void) +{ + struct evbuffer *buffer; + + buffer = calloc(1, sizeof(struct evbuffer)); + + return (buffer); +} + +void +evbuffer_free(struct evbuffer *buffer) +{ + if (buffer->orig_buffer != NULL) + free(buffer->orig_buffer); + free(buffer); +} + +/* + * This is a destructive add. The data from one buffer moves into + * the other buffer. + */ + +#define SWAP(x,y) do { \ + (x)->buffer = (y)->buffer; \ + (x)->orig_buffer = (y)->orig_buffer; \ + (x)->misalign = (y)->misalign; \ + (x)->totallen = (y)->totallen; \ + (x)->off = (y)->off; \ +} while (0) + +int +evbuffer_add_buffer(struct evbuffer *outbuf, struct evbuffer *inbuf) +{ + int res; + + /* Short cut for better performance */ + if (outbuf->off == 0) { + struct evbuffer tmp; + size_t oldoff = inbuf->off; + + /* Swap them directly */ + SWAP(&tmp, outbuf); + SWAP(outbuf, inbuf); + SWAP(inbuf, &tmp); + + /* + * Optimization comes with a price; we need to notify the + * buffer if necessary of the changes. oldoff is the amount + * of data that we transfered from inbuf to outbuf + */ + if (inbuf->off != oldoff && inbuf->cb != NULL) + (*inbuf->cb)(inbuf, oldoff, inbuf->off, inbuf->cbarg); + if (oldoff && outbuf->cb != NULL) + (*outbuf->cb)(outbuf, 0, oldoff, outbuf->cbarg); + + return (0); + } + + res = evbuffer_add(outbuf, inbuf->buffer, inbuf->off); + if (res == 0) { + /* We drain the input buffer on success */ + evbuffer_drain(inbuf, inbuf->off); + } + + return (res); +} + +int +evbuffer_add_vprintf(struct evbuffer *buf, const char *fmt, va_list ap) +{ + char *buffer; + size_t space; + size_t oldoff = buf->off; + int sz; + va_list aq; + + /* make sure that at least some space is available */ + evbuffer_expand(buf, 64); + for (;;) { + size_t used = buf->misalign + buf->off; + buffer = (char *)buf->buffer + buf->off; + assert(buf->totallen >= used); + space = buf->totallen - used; + +#ifndef va_copy +#define va_copy(dst, src) memcpy(&(dst), &(src), sizeof(va_list)) +#endif + va_copy(aq, ap); + + sz = evutil_vsnprintf(buffer, space, fmt, aq); + + va_end(aq); + + if (sz < 0) + return (-1); + if ((size_t)sz < space) { + buf->off += sz; + if (buf->cb != NULL) + (*buf->cb)(buf, oldoff, buf->off, buf->cbarg); + return (sz); + } + if (evbuffer_expand(buf, sz + 1) == -1) + return (-1); + + } + /* NOTREACHED */ +} + +int +evbuffer_add_printf(struct evbuffer *buf, const char *fmt, ...) +{ + int res = -1; + va_list ap; + + va_start(ap, fmt); + res = evbuffer_add_vprintf(buf, fmt, ap); + va_end(ap); + + return (res); +} + +/* Reads data from an event buffer and drains the bytes read */ + +int +evbuffer_remove(struct evbuffer *buf, void *data, size_t datlen) +{ + size_t nread = datlen; + if (nread >= buf->off) + nread = buf->off; + + memcpy(data, buf->buffer, nread); + evbuffer_drain(buf, nread); + + return (nread); +} + +/* + * Reads a line terminated by either '\r\n', '\n\r' or '\r' or '\n'. + * The returned buffer needs to be freed by the called. + */ + +char * +evbuffer_readline(struct evbuffer *buffer) +{ + u_char *data = EVBUFFER_DATA(buffer); + size_t len = EVBUFFER_LENGTH(buffer); + char *line; + unsigned int i; + + for (i = 0; i < len; i++) { + if (data[i] == '\r' || data[i] == '\n') + break; + } + + if (i == len) + return (NULL); + + if ((line = malloc(i + 1)) == NULL) { + fprintf(stderr, "%s: out of memory\n", __func__); + return (NULL); + } + + memcpy(line, data, i); + line[i] = '\0'; + + /* + * Some protocols terminate a line with '\r\n', so check for + * that, too. + */ + if ( i < len - 1 ) { + char fch = data[i], sch = data[i+1]; + + /* Drain one more character if needed */ + if ( (sch == '\r' || sch == '\n') && sch != fch ) + i += 1; + } + + evbuffer_drain(buffer, i + 1); + + return (line); +} + +/* Adds data to an event buffer */ + +static void +evbuffer_align(struct evbuffer *buf) +{ + memmove(buf->orig_buffer, buf->buffer, buf->off); + buf->buffer = buf->orig_buffer; + buf->misalign = 0; +} + +/* Expands the available space in the event buffer to at least datlen */ + +int +evbuffer_expand(struct evbuffer *buf, size_t datlen) +{ + size_t need = buf->misalign + buf->off + datlen; + + /* If we can fit all the data, then we don't have to do anything */ + if (buf->totallen >= need) + return (0); + + /* + * If the misalignment fulfills our data needs, we just force an + * alignment to happen. Afterwards, we have enough space. + */ + if (buf->misalign >= datlen) { + evbuffer_align(buf); + } else { + void *newbuf; + size_t length = buf->totallen; + + if (length < 256) + length = 256; + while (length < need) + length <<= 1; + + if (buf->orig_buffer != buf->buffer) + evbuffer_align(buf); + if ((newbuf = realloc(buf->buffer, length)) == NULL) + return (-1); + + buf->orig_buffer = buf->buffer = newbuf; + buf->totallen = length; + } + + return (0); +} + +int +evbuffer_add(struct evbuffer *buf, const void *data, size_t datlen) +{ + size_t need = buf->misalign + buf->off + datlen; + size_t oldoff = buf->off; + + if (buf->totallen < need) { + if (evbuffer_expand(buf, datlen) == -1) + return (-1); + } + + memcpy(buf->buffer + buf->off, data, datlen); + buf->off += datlen; + + if (datlen && buf->cb != NULL) + (*buf->cb)(buf, oldoff, buf->off, buf->cbarg); + + return (0); +} + +void +evbuffer_drain(struct evbuffer *buf, size_t len) +{ + size_t oldoff = buf->off; + + if (len >= buf->off) { + buf->off = 0; + buf->buffer = buf->orig_buffer; + buf->misalign = 0; + goto done; + } + + buf->buffer += len; + buf->misalign += len; + + buf->off -= len; + + done: + /* Tell someone about changes in this buffer */ + if (buf->off != oldoff && buf->cb != NULL) + (*buf->cb)(buf, oldoff, buf->off, buf->cbarg); + +} + +/* + * Reads data from a file descriptor into a buffer. + */ + +#define EVBUFFER_MAX_READ 4096 + +int +evbuffer_read(struct evbuffer *buf, int fd, int howmuch) +{ + u_char *p; + size_t oldoff = buf->off; + int n = EVBUFFER_MAX_READ; + +#if defined(FIONREAD) +#ifdef WIN32 + long lng = n; + if (ioctlsocket(fd, FIONREAD, &lng) == -1 || (n=lng) <= 0) { +#else + if (ioctl(fd, FIONREAD, &n) == -1 || n <= 0) { +#endif + n = EVBUFFER_MAX_READ; + } else if (n > EVBUFFER_MAX_READ && n > howmuch) { + /* + * It's possible that a lot of data is available for + * reading. We do not want to exhaust resources + * before the reader has a chance to do something + * about it. If the reader does not tell us how much + * data we should read, we artifically limit it. + */ + if ((size_t)n > buf->totallen << 2) + n = buf->totallen << 2; + if (n < EVBUFFER_MAX_READ) + n = EVBUFFER_MAX_READ; + } +#endif + if (howmuch < 0 || howmuch > n) + howmuch = n; + + /* If we don't have FIONREAD, we might waste some space here */ + if (evbuffer_expand(buf, howmuch) == -1) + return (-1); + + /* We can append new data at this point */ + p = buf->buffer + buf->off; + +#ifndef WIN32 + n = read(fd, p, howmuch); +#else + n = recv(fd, p, howmuch, 0); +#endif + if (n == -1) + return (-1); + if (n == 0) + return (0); + + buf->off += n; + + /* Tell someone about changes in this buffer */ + if (buf->off != oldoff && buf->cb != NULL) + (*buf->cb)(buf, oldoff, buf->off, buf->cbarg); + + return (n); +} + +int +evbuffer_write(struct evbuffer *buffer, int fd) +{ + int n; + +#ifndef WIN32 + n = write(fd, buffer->buffer, buffer->off); +#else + n = send(fd, buffer->buffer, buffer->off, 0); +#endif + if (n == -1) + return (-1); + if (n == 0) + return (0); + evbuffer_drain(buffer, n); + + return (n); +} + +u_char * +evbuffer_find(struct evbuffer *buffer, const u_char *what, size_t len) +{ + u_char *search = buffer->buffer, *end = search + buffer->off; + u_char *p; + + while (search < end && + (p = memchr(search, *what, end - search)) != NULL) { + if (p + len > end) + break; + if (memcmp(p, what, len) == 0) + return (p); + search = p + 1; + } + + return (NULL); +} + +void evbuffer_setcb(struct evbuffer *buffer, + void (*cb)(struct evbuffer *, size_t, size_t, void *), + void *cbarg) +{ + buffer->cb = cb; + buffer->cbarg = cbarg; +} diff --git a/third_party/libevent/chromium.patch b/third_party/libevent/chromium.patch new file mode 100644 index 0000000000..31bc327c4d --- /dev/null +++ b/third_party/libevent/chromium.patch @@ -0,0 +1,83 @@ +diff --git a/third_party/libevent/evdns.c b/third_party/libevent/evdns.c +index f07ecc9..da6ea19 100644 +--- a/third_party/libevent/evdns.c ++++ b/third_party/libevent/evdns.c +@@ -134,7 +134,7 @@ + typedef ev_uint8_t u_char; + typedef unsigned int uint; + #endif +-#include ++#include "event.h" + + #define u64 ev_uint64_t + #define u32 ev_uint32_t +diff --git a/third_party/libevent/evdns.h b/third_party/libevent/evdns.h +index 1eb5c38..fca4ac3 100644 +--- a/third_party/libevent/evdns.h ++++ b/third_party/libevent/evdns.h +@@ -165,7 +165,7 @@ extern "C" { + #endif + + /* For integer types. */ +-#include ++#include "evutil.h" + + /** Error codes 0-5 are as described in RFC 1035. */ + #define DNS_ERR_NONE 0 +diff --git a/third_party/libevent/event-config.h b/third_party/libevent/event-config.h +new file mode 100644 +index 0000000..78a4727 +--- /dev/null ++++ b/third_party/libevent/event-config.h +@@ -0,0 +1,16 @@ ++// 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 Chromium-specific, and brings in the appropriate ++// event-config.h depending on your platform. ++ ++#if defined(__APPLE__) ++#include "mac/event-config.h" ++#elif defined(__linux__) ++#include "linux/event-config.h" ++#elif defined(__FreeBSD__) ++#include "freebsd/event-config.h" ++#else ++#error generate event-config.h for your platform ++#endif +diff --git a/third_party/libevent/event.h b/third_party/libevent/event.h +index cfa0fc3..72e9b8b 100644 +--- a/third_party/libevent/event.h ++++ b/third_party/libevent/event.h +@@ -159,7 +159,7 @@ + extern "C" { + #endif + +-#include ++#include "event-config.h" + #ifdef _EVENT_HAVE_SYS_TYPES_H + #include + #endif +@@ -172,7 +172,7 @@ extern "C" { + #include + + /* For int types. */ +-#include ++#include "evutil.h" + + #ifdef WIN32 + #define WIN32_LEAN_AND_MEAN +diff --git a/third_party/libevent/evutil.h b/third_party/libevent/evutil.h +index dcb0013..8b664b9 100644 +--- a/third_party/libevent/evutil.h ++++ b/third_party/libevent/evutil.h +@@ -38,7 +38,7 @@ + extern "C" { + #endif + +-#include ++#include "event-config.h" + #ifdef _EVENT_HAVE_SYS_TIME_H + #include + #endif diff --git a/third_party/libevent/compat/sys/_libevent_time.h b/third_party/libevent/compat/sys/_libevent_time.h new file mode 100644 index 0000000000..8cabb0d55e --- /dev/null +++ b/third_party/libevent/compat/sys/_libevent_time.h @@ -0,0 +1,163 @@ +/* $OpenBSD: time.h,v 1.11 2000/10/10 13:36:48 itojun Exp $ */ +/* $NetBSD: time.h,v 1.18 1996/04/23 10:29:33 mycroft Exp $ */ + +/* + * Copyright (c) 1982, 1986, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of the University 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 REGENTS 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 REGENTS 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. + * + * @(#)time.h 8.2 (Berkeley) 7/10/94 + */ + +#ifndef _SYS_TIME_H_ +#define _SYS_TIME_H_ + +#include + +/* + * Structure returned by gettimeofday(2) system call, + * and used in other calls. + */ +struct timeval { + long tv_sec; /* seconds */ + long tv_usec; /* and microseconds */ +}; + +/* + * Structure defined by POSIX.1b to be like a timeval. + */ +struct timespec { + time_t tv_sec; /* seconds */ + long tv_nsec; /* and nanoseconds */ +}; + +#define TIMEVAL_TO_TIMESPEC(tv, ts) { \ + (ts)->tv_sec = (tv)->tv_sec; \ + (ts)->tv_nsec = (tv)->tv_usec * 1000; \ +} +#define TIMESPEC_TO_TIMEVAL(tv, ts) { \ + (tv)->tv_sec = (ts)->tv_sec; \ + (tv)->tv_usec = (ts)->tv_nsec / 1000; \ +} + +struct timezone { + int tz_minuteswest; /* minutes west of Greenwich */ + int tz_dsttime; /* type of dst correction */ +}; +#define DST_NONE 0 /* not on dst */ +#define DST_USA 1 /* USA style dst */ +#define DST_AUST 2 /* Australian style dst */ +#define DST_WET 3 /* Western European dst */ +#define DST_MET 4 /* Middle European dst */ +#define DST_EET 5 /* Eastern European dst */ +#define DST_CAN 6 /* Canada */ + +/* Operations on timevals. */ +#define timerclear(tvp) (tvp)->tv_sec = (tvp)->tv_usec = 0 +#define timerisset(tvp) ((tvp)->tv_sec || (tvp)->tv_usec) +#define timercmp(tvp, uvp, cmp) \ + (((tvp)->tv_sec == (uvp)->tv_sec) ? \ + ((tvp)->tv_usec cmp (uvp)->tv_usec) : \ + ((tvp)->tv_sec cmp (uvp)->tv_sec)) +#define timeradd(tvp, uvp, vvp) \ + do { \ + (vvp)->tv_sec = (tvp)->tv_sec + (uvp)->tv_sec; \ + (vvp)->tv_usec = (tvp)->tv_usec + (uvp)->tv_usec; \ + if ((vvp)->tv_usec >= 1000000) { \ + (vvp)->tv_sec++; \ + (vvp)->tv_usec -= 1000000; \ + } \ + } while (0) +#define timersub(tvp, uvp, vvp) \ + do { \ + (vvp)->tv_sec = (tvp)->tv_sec - (uvp)->tv_sec; \ + (vvp)->tv_usec = (tvp)->tv_usec - (uvp)->tv_usec; \ + if ((vvp)->tv_usec < 0) { \ + (vvp)->tv_sec--; \ + (vvp)->tv_usec += 1000000; \ + } \ + } while (0) + +/* Operations on timespecs. */ +#define timespecclear(tsp) (tsp)->tv_sec = (tsp)->tv_nsec = 0 +#define timespecisset(tsp) ((tsp)->tv_sec || (tsp)->tv_nsec) +#define timespeccmp(tsp, usp, cmp) \ + (((tsp)->tv_sec == (usp)->tv_sec) ? \ + ((tsp)->tv_nsec cmp (usp)->tv_nsec) : \ + ((tsp)->tv_sec cmp (usp)->tv_sec)) +#define timespecadd(tsp, usp, vsp) \ + do { \ + (vsp)->tv_sec = (tsp)->tv_sec + (usp)->tv_sec; \ + (vsp)->tv_nsec = (tsp)->tv_nsec + (usp)->tv_nsec; \ + if ((vsp)->tv_nsec >= 1000000000L) { \ + (vsp)->tv_sec++; \ + (vsp)->tv_nsec -= 1000000000L; \ + } \ + } while (0) +#define timespecsub(tsp, usp, vsp) \ + do { \ + (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \ + (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \ + if ((vsp)->tv_nsec < 0) { \ + (vsp)->tv_sec--; \ + (vsp)->tv_nsec += 1000000000L; \ + } \ + } while (0) + +/* + * Names of the interval timers, and structure + * defining a timer setting. + */ +#define ITIMER_REAL 0 +#define ITIMER_VIRTUAL 1 +#define ITIMER_PROF 2 + +struct itimerval { + struct timeval it_interval; /* timer interval */ + struct timeval it_value; /* current value */ +}; + +/* + * Getkerninfo clock information structure + */ +struct clockinfo { + int hz; /* clock frequency */ + int tick; /* micro-seconds per hz tick */ + int tickadj; /* clock skew rate for adjtime() */ + int stathz; /* statistics clock frequency */ + int profhz; /* profiling clock frequency */ +}; + +#define CLOCK_REALTIME 0 +#define CLOCK_VIRTUAL 1 +#define CLOCK_PROF 2 + +#define TIMER_RELTIME 0x0 /* relative timer */ +#define TIMER_ABSTIME 0x1 /* absolute timer */ + +/* --- stuff got cut here - niels --- */ + +#endif /* !_SYS_TIME_H_ */ diff --git a/third_party/libevent/compat/sys/_time.h b/third_party/libevent/compat/sys/_time.h new file mode 100644 index 0000000000..8cabb0d55e --- /dev/null +++ b/third_party/libevent/compat/sys/_time.h @@ -0,0 +1,163 @@ +/* $OpenBSD: time.h,v 1.11 2000/10/10 13:36:48 itojun Exp $ */ +/* $NetBSD: time.h,v 1.18 1996/04/23 10:29:33 mycroft Exp $ */ + +/* + * Copyright (c) 1982, 1986, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of the University 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 REGENTS 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 REGENTS 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. + * + * @(#)time.h 8.2 (Berkeley) 7/10/94 + */ + +#ifndef _SYS_TIME_H_ +#define _SYS_TIME_H_ + +#include + +/* + * Structure returned by gettimeofday(2) system call, + * and used in other calls. + */ +struct timeval { + long tv_sec; /* seconds */ + long tv_usec; /* and microseconds */ +}; + +/* + * Structure defined by POSIX.1b to be like a timeval. + */ +struct timespec { + time_t tv_sec; /* seconds */ + long tv_nsec; /* and nanoseconds */ +}; + +#define TIMEVAL_TO_TIMESPEC(tv, ts) { \ + (ts)->tv_sec = (tv)->tv_sec; \ + (ts)->tv_nsec = (tv)->tv_usec * 1000; \ +} +#define TIMESPEC_TO_TIMEVAL(tv, ts) { \ + (tv)->tv_sec = (ts)->tv_sec; \ + (tv)->tv_usec = (ts)->tv_nsec / 1000; \ +} + +struct timezone { + int tz_minuteswest; /* minutes west of Greenwich */ + int tz_dsttime; /* type of dst correction */ +}; +#define DST_NONE 0 /* not on dst */ +#define DST_USA 1 /* USA style dst */ +#define DST_AUST 2 /* Australian style dst */ +#define DST_WET 3 /* Western European dst */ +#define DST_MET 4 /* Middle European dst */ +#define DST_EET 5 /* Eastern European dst */ +#define DST_CAN 6 /* Canada */ + +/* Operations on timevals. */ +#define timerclear(tvp) (tvp)->tv_sec = (tvp)->tv_usec = 0 +#define timerisset(tvp) ((tvp)->tv_sec || (tvp)->tv_usec) +#define timercmp(tvp, uvp, cmp) \ + (((tvp)->tv_sec == (uvp)->tv_sec) ? \ + ((tvp)->tv_usec cmp (uvp)->tv_usec) : \ + ((tvp)->tv_sec cmp (uvp)->tv_sec)) +#define timeradd(tvp, uvp, vvp) \ + do { \ + (vvp)->tv_sec = (tvp)->tv_sec + (uvp)->tv_sec; \ + (vvp)->tv_usec = (tvp)->tv_usec + (uvp)->tv_usec; \ + if ((vvp)->tv_usec >= 1000000) { \ + (vvp)->tv_sec++; \ + (vvp)->tv_usec -= 1000000; \ + } \ + } while (0) +#define timersub(tvp, uvp, vvp) \ + do { \ + (vvp)->tv_sec = (tvp)->tv_sec - (uvp)->tv_sec; \ + (vvp)->tv_usec = (tvp)->tv_usec - (uvp)->tv_usec; \ + if ((vvp)->tv_usec < 0) { \ + (vvp)->tv_sec--; \ + (vvp)->tv_usec += 1000000; \ + } \ + } while (0) + +/* Operations on timespecs. */ +#define timespecclear(tsp) (tsp)->tv_sec = (tsp)->tv_nsec = 0 +#define timespecisset(tsp) ((tsp)->tv_sec || (tsp)->tv_nsec) +#define timespeccmp(tsp, usp, cmp) \ + (((tsp)->tv_sec == (usp)->tv_sec) ? \ + ((tsp)->tv_nsec cmp (usp)->tv_nsec) : \ + ((tsp)->tv_sec cmp (usp)->tv_sec)) +#define timespecadd(tsp, usp, vsp) \ + do { \ + (vsp)->tv_sec = (tsp)->tv_sec + (usp)->tv_sec; \ + (vsp)->tv_nsec = (tsp)->tv_nsec + (usp)->tv_nsec; \ + if ((vsp)->tv_nsec >= 1000000000L) { \ + (vsp)->tv_sec++; \ + (vsp)->tv_nsec -= 1000000000L; \ + } \ + } while (0) +#define timespecsub(tsp, usp, vsp) \ + do { \ + (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \ + (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \ + if ((vsp)->tv_nsec < 0) { \ + (vsp)->tv_sec--; \ + (vsp)->tv_nsec += 1000000000L; \ + } \ + } while (0) + +/* + * Names of the interval timers, and structure + * defining a timer setting. + */ +#define ITIMER_REAL 0 +#define ITIMER_VIRTUAL 1 +#define ITIMER_PROF 2 + +struct itimerval { + struct timeval it_interval; /* timer interval */ + struct timeval it_value; /* current value */ +}; + +/* + * Getkerninfo clock information structure + */ +struct clockinfo { + int hz; /* clock frequency */ + int tick; /* micro-seconds per hz tick */ + int tickadj; /* clock skew rate for adjtime() */ + int stathz; /* statistics clock frequency */ + int profhz; /* profiling clock frequency */ +}; + +#define CLOCK_REALTIME 0 +#define CLOCK_VIRTUAL 1 +#define CLOCK_PROF 2 + +#define TIMER_RELTIME 0x0 /* relative timer */ +#define TIMER_ABSTIME 0x1 /* absolute timer */ + +/* --- stuff got cut here - niels --- */ + +#endif /* !_SYS_TIME_H_ */ diff --git a/third_party/libevent/compat/sys/queue.h b/third_party/libevent/compat/sys/queue.h new file mode 100644 index 0000000000..c0956ddce4 --- /dev/null +++ b/third_party/libevent/compat/sys/queue.h @@ -0,0 +1,488 @@ +/* $OpenBSD: queue.h,v 1.16 2000/09/07 19:47:59 art Exp $ */ +/* $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $ */ + +/* + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of the University 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 REGENTS 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 REGENTS 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. + * + * @(#)queue.h 8.5 (Berkeley) 8/20/94 + */ + +#ifndef _SYS_QUEUE_H_ +#define _SYS_QUEUE_H_ + +/* + * This file defines five types of data structures: singly-linked lists, + * lists, simple queues, tail queues, and circular queues. + * + * + * A singly-linked list is headed by a single forward pointer. The elements + * are singly linked for minimum space and pointer manipulation overhead at + * the expense of O(n) removal for arbitrary elements. New elements can be + * added to the list after an existing element or at the head of the list. + * Elements being removed from the head of the list should use the explicit + * macro for this purpose for optimum efficiency. A singly-linked list may + * only be traversed in the forward direction. Singly-linked lists are ideal + * for applications with large datasets and few or no removals or for + * implementing a LIFO queue. + * + * A list is headed by a single forward pointer (or an array of forward + * pointers for a hash table header). The elements are doubly linked + * so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before + * or after an existing element or at the head of the list. A list + * may only be traversed in the forward direction. + * + * A simple queue is headed by a pair of pointers, one the head of the + * list and the other to the tail of the list. The elements are singly + * linked to save space, so elements can only be removed from the + * head of the list. New elements can be added to the list before or after + * an existing element, at the head of the list, or at the end of the + * list. A simple queue may only be traversed in the forward direction. + * + * A tail queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or + * after an existing element, at the head of the list, or at the end of + * the list. A tail queue may be traversed in either direction. + * + * A circle queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or after + * an existing element, at the head of the list, or at the end of the list. + * A circle queue may be traversed in either direction, but has a more + * complex end of list detection. + * + * For details on the use of these macros, see the queue(3) manual page. + */ + +/* + * Singly-linked List definitions. + */ +#define SLIST_HEAD(name, type) \ +struct name { \ + struct type *slh_first; /* first element */ \ +} + +#define SLIST_HEAD_INITIALIZER(head) \ + { NULL } + +#ifndef WIN32 +#define SLIST_ENTRY(type) \ +struct { \ + struct type *sle_next; /* next element */ \ +} +#endif + +/* + * Singly-linked List access methods. + */ +#define SLIST_FIRST(head) ((head)->slh_first) +#define SLIST_END(head) NULL +#define SLIST_EMPTY(head) (SLIST_FIRST(head) == SLIST_END(head)) +#define SLIST_NEXT(elm, field) ((elm)->field.sle_next) + +#define SLIST_FOREACH(var, head, field) \ + for((var) = SLIST_FIRST(head); \ + (var) != SLIST_END(head); \ + (var) = SLIST_NEXT(var, field)) + +/* + * Singly-linked List functions. + */ +#define SLIST_INIT(head) { \ + SLIST_FIRST(head) = SLIST_END(head); \ +} + +#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ + (elm)->field.sle_next = (slistelm)->field.sle_next; \ + (slistelm)->field.sle_next = (elm); \ +} while (0) + +#define SLIST_INSERT_HEAD(head, elm, field) do { \ + (elm)->field.sle_next = (head)->slh_first; \ + (head)->slh_first = (elm); \ +} while (0) + +#define SLIST_REMOVE_HEAD(head, field) do { \ + (head)->slh_first = (head)->slh_first->field.sle_next; \ +} while (0) + +/* + * List definitions. + */ +#define LIST_HEAD(name, type) \ +struct name { \ + struct type *lh_first; /* first element */ \ +} + +#define LIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define LIST_ENTRY(type) \ +struct { \ + struct type *le_next; /* next element */ \ + struct type **le_prev; /* address of previous next element */ \ +} + +/* + * List access methods + */ +#define LIST_FIRST(head) ((head)->lh_first) +#define LIST_END(head) NULL +#define LIST_EMPTY(head) (LIST_FIRST(head) == LIST_END(head)) +#define LIST_NEXT(elm, field) ((elm)->field.le_next) + +#define LIST_FOREACH(var, head, field) \ + for((var) = LIST_FIRST(head); \ + (var)!= LIST_END(head); \ + (var) = LIST_NEXT(var, field)) + +/* + * List functions. + */ +#define LIST_INIT(head) do { \ + LIST_FIRST(head) = LIST_END(head); \ +} while (0) + +#define LIST_INSERT_AFTER(listelm, elm, field) do { \ + if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \ + (listelm)->field.le_next->field.le_prev = \ + &(elm)->field.le_next; \ + (listelm)->field.le_next = (elm); \ + (elm)->field.le_prev = &(listelm)->field.le_next; \ +} while (0) + +#define LIST_INSERT_BEFORE(listelm, elm, field) do { \ + (elm)->field.le_prev = (listelm)->field.le_prev; \ + (elm)->field.le_next = (listelm); \ + *(listelm)->field.le_prev = (elm); \ + (listelm)->field.le_prev = &(elm)->field.le_next; \ +} while (0) + +#define LIST_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.le_next = (head)->lh_first) != NULL) \ + (head)->lh_first->field.le_prev = &(elm)->field.le_next;\ + (head)->lh_first = (elm); \ + (elm)->field.le_prev = &(head)->lh_first; \ +} while (0) + +#define LIST_REMOVE(elm, field) do { \ + if ((elm)->field.le_next != NULL) \ + (elm)->field.le_next->field.le_prev = \ + (elm)->field.le_prev; \ + *(elm)->field.le_prev = (elm)->field.le_next; \ +} while (0) + +#define LIST_REPLACE(elm, elm2, field) do { \ + if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \ + (elm2)->field.le_next->field.le_prev = \ + &(elm2)->field.le_next; \ + (elm2)->field.le_prev = (elm)->field.le_prev; \ + *(elm2)->field.le_prev = (elm2); \ +} while (0) + +/* + * Simple queue definitions. + */ +#define SIMPLEQ_HEAD(name, type) \ +struct name { \ + struct type *sqh_first; /* first element */ \ + struct type **sqh_last; /* addr of last next element */ \ +} + +#define SIMPLEQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).sqh_first } + +#define SIMPLEQ_ENTRY(type) \ +struct { \ + struct type *sqe_next; /* next element */ \ +} + +/* + * Simple queue access methods. + */ +#define SIMPLEQ_FIRST(head) ((head)->sqh_first) +#define SIMPLEQ_END(head) NULL +#define SIMPLEQ_EMPTY(head) (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head)) +#define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next) + +#define SIMPLEQ_FOREACH(var, head, field) \ + for((var) = SIMPLEQ_FIRST(head); \ + (var) != SIMPLEQ_END(head); \ + (var) = SIMPLEQ_NEXT(var, field)) + +/* + * Simple queue functions. + */ +#define SIMPLEQ_INIT(head) do { \ + (head)->sqh_first = NULL; \ + (head)->sqh_last = &(head)->sqh_first; \ +} while (0) + +#define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \ + (head)->sqh_last = &(elm)->field.sqe_next; \ + (head)->sqh_first = (elm); \ +} while (0) + +#define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.sqe_next = NULL; \ + *(head)->sqh_last = (elm); \ + (head)->sqh_last = &(elm)->field.sqe_next; \ +} while (0) + +#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\ + (head)->sqh_last = &(elm)->field.sqe_next; \ + (listelm)->field.sqe_next = (elm); \ +} while (0) + +#define SIMPLEQ_REMOVE_HEAD(head, elm, field) do { \ + if (((head)->sqh_first = (elm)->field.sqe_next) == NULL) \ + (head)->sqh_last = &(head)->sqh_first; \ +} while (0) + +/* + * Tail queue definitions. + */ +#define TAILQ_HEAD(name, type) \ +struct name { \ + struct type *tqh_first; /* first element */ \ + struct type **tqh_last; /* addr of last next element */ \ +} + +#define TAILQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).tqh_first } + +#define TAILQ_ENTRY(type) \ +struct { \ + struct type *tqe_next; /* next element */ \ + struct type **tqe_prev; /* address of previous next element */ \ +} + +/* + * tail queue access methods + */ +#define TAILQ_FIRST(head) ((head)->tqh_first) +#define TAILQ_END(head) NULL +#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) +#define TAILQ_LAST(head, headname) \ + (*(((struct headname *)((head)->tqh_last))->tqh_last)) +/* XXX */ +#define TAILQ_PREV(elm, headname, field) \ + (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) +#define TAILQ_EMPTY(head) \ + (TAILQ_FIRST(head) == TAILQ_END(head)) + +#define TAILQ_FOREACH(var, head, field) \ + for((var) = TAILQ_FIRST(head); \ + (var) != TAILQ_END(head); \ + (var) = TAILQ_NEXT(var, field)) + +#define TAILQ_FOREACH_REVERSE(var, head, field, headname) \ + for((var) = TAILQ_LAST(head, headname); \ + (var) != TAILQ_END(head); \ + (var) = TAILQ_PREV(var, headname, field)) + +/* + * Tail queue functions. + */ +#define TAILQ_INIT(head) do { \ + (head)->tqh_first = NULL; \ + (head)->tqh_last = &(head)->tqh_first; \ +} while (0) + +#define TAILQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \ + (head)->tqh_first->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (head)->tqh_first = (elm); \ + (elm)->field.tqe_prev = &(head)->tqh_first; \ +} while (0) + +#define TAILQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.tqe_next = NULL; \ + (elm)->field.tqe_prev = (head)->tqh_last; \ + *(head)->tqh_last = (elm); \ + (head)->tqh_last = &(elm)->field.tqe_next; \ +} while (0) + +#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\ + (elm)->field.tqe_next->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (listelm)->field.tqe_next = (elm); \ + (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ +} while (0) + +#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ + (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ + (elm)->field.tqe_next = (listelm); \ + *(listelm)->field.tqe_prev = (elm); \ + (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ +} while (0) + +#define TAILQ_REMOVE(head, elm, field) do { \ + if (((elm)->field.tqe_next) != NULL) \ + (elm)->field.tqe_next->field.tqe_prev = \ + (elm)->field.tqe_prev; \ + else \ + (head)->tqh_last = (elm)->field.tqe_prev; \ + *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ +} while (0) + +#define TAILQ_REPLACE(head, elm, elm2, field) do { \ + if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \ + (elm2)->field.tqe_next->field.tqe_prev = \ + &(elm2)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm2)->field.tqe_next; \ + (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \ + *(elm2)->field.tqe_prev = (elm2); \ +} while (0) + +/* + * Circular queue definitions. + */ +#define CIRCLEQ_HEAD(name, type) \ +struct name { \ + struct type *cqh_first; /* first element */ \ + struct type *cqh_last; /* last element */ \ +} + +#define CIRCLEQ_HEAD_INITIALIZER(head) \ + { CIRCLEQ_END(&head), CIRCLEQ_END(&head) } + +#define CIRCLEQ_ENTRY(type) \ +struct { \ + struct type *cqe_next; /* next element */ \ + struct type *cqe_prev; /* previous element */ \ +} + +/* + * Circular queue access methods + */ +#define CIRCLEQ_FIRST(head) ((head)->cqh_first) +#define CIRCLEQ_LAST(head) ((head)->cqh_last) +#define CIRCLEQ_END(head) ((void *)(head)) +#define CIRCLEQ_NEXT(elm, field) ((elm)->field.cqe_next) +#define CIRCLEQ_PREV(elm, field) ((elm)->field.cqe_prev) +#define CIRCLEQ_EMPTY(head) \ + (CIRCLEQ_FIRST(head) == CIRCLEQ_END(head)) + +#define CIRCLEQ_FOREACH(var, head, field) \ + for((var) = CIRCLEQ_FIRST(head); \ + (var) != CIRCLEQ_END(head); \ + (var) = CIRCLEQ_NEXT(var, field)) + +#define CIRCLEQ_FOREACH_REVERSE(var, head, field) \ + for((var) = CIRCLEQ_LAST(head); \ + (var) != CIRCLEQ_END(head); \ + (var) = CIRCLEQ_PREV(var, field)) + +/* + * Circular queue functions. + */ +#define CIRCLEQ_INIT(head) do { \ + (head)->cqh_first = CIRCLEQ_END(head); \ + (head)->cqh_last = CIRCLEQ_END(head); \ +} while (0) + +#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + (elm)->field.cqe_next = (listelm)->field.cqe_next; \ + (elm)->field.cqe_prev = (listelm); \ + if ((listelm)->field.cqe_next == CIRCLEQ_END(head)) \ + (head)->cqh_last = (elm); \ + else \ + (listelm)->field.cqe_next->field.cqe_prev = (elm); \ + (listelm)->field.cqe_next = (elm); \ +} while (0) + +#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \ + (elm)->field.cqe_next = (listelm); \ + (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \ + if ((listelm)->field.cqe_prev == CIRCLEQ_END(head)) \ + (head)->cqh_first = (elm); \ + else \ + (listelm)->field.cqe_prev->field.cqe_next = (elm); \ + (listelm)->field.cqe_prev = (elm); \ +} while (0) + +#define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \ + (elm)->field.cqe_next = (head)->cqh_first; \ + (elm)->field.cqe_prev = CIRCLEQ_END(head); \ + if ((head)->cqh_last == CIRCLEQ_END(head)) \ + (head)->cqh_last = (elm); \ + else \ + (head)->cqh_first->field.cqe_prev = (elm); \ + (head)->cqh_first = (elm); \ +} while (0) + +#define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.cqe_next = CIRCLEQ_END(head); \ + (elm)->field.cqe_prev = (head)->cqh_last; \ + if ((head)->cqh_first == CIRCLEQ_END(head)) \ + (head)->cqh_first = (elm); \ + else \ + (head)->cqh_last->field.cqe_next = (elm); \ + (head)->cqh_last = (elm); \ +} while (0) + +#define CIRCLEQ_REMOVE(head, elm, field) do { \ + if ((elm)->field.cqe_next == CIRCLEQ_END(head)) \ + (head)->cqh_last = (elm)->field.cqe_prev; \ + else \ + (elm)->field.cqe_next->field.cqe_prev = \ + (elm)->field.cqe_prev; \ + if ((elm)->field.cqe_prev == CIRCLEQ_END(head)) \ + (head)->cqh_first = (elm)->field.cqe_next; \ + else \ + (elm)->field.cqe_prev->field.cqe_next = \ + (elm)->field.cqe_next; \ +} while (0) + +#define CIRCLEQ_REPLACE(head, elm, elm2, field) do { \ + if (((elm2)->field.cqe_next = (elm)->field.cqe_next) == \ + CIRCLEQ_END(head)) \ + (head).cqh_last = (elm2); \ + else \ + (elm2)->field.cqe_next->field.cqe_prev = (elm2); \ + if (((elm2)->field.cqe_prev = (elm)->field.cqe_prev) == \ + CIRCLEQ_END(head)) \ + (head).cqh_first = (elm2); \ + else \ + (elm2)->field.cqe_prev->field.cqe_next = (elm2); \ +} while (0) + +#endif /* !_SYS_QUEUE_H_ */ diff --git a/third_party/libevent/config.guess b/third_party/libevent/config.guess new file mode 100644 index 0000000000..f32079abda --- /dev/null +++ b/third_party/libevent/config.guess @@ -0,0 +1,1526 @@ +#! /bin/sh +# Attempt to guess a canonical system name. +# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, +# 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 +# Free Software Foundation, Inc. + +timestamp='2008-01-23' + +# This file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA +# 02110-1301, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + + +# Originally written by Per Bothner . +# Please send patches to . Submit a context +# diff and a properly formatted ChangeLog entry. +# +# This script attempts to guess a canonical system name similar to +# config.sub. If it succeeds, it prints the system name on stdout, and +# exits with 0. Otherwise, it exits with 1. +# +# The plan is that this can be called by configure scripts if you +# don't specify an explicit build system type. + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] + +Output the configuration name of the system \`$me' is run on. + +Operation modes: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.guess ($timestamp) + +Originally written by Per Bothner. +Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, +2002, 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit ;; + --version | -v ) + echo "$version" ; exit ;; + --help | --h* | -h ) + echo "$usage"; exit ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" >&2 + exit 1 ;; + * ) + break ;; + esac +done + +if test $# != 0; then + echo "$me: too many arguments$help" >&2 + exit 1 +fi + +trap 'exit 1' 1 2 15 + +# CC_FOR_BUILD -- compiler used by this script. Note that the use of a +# compiler to aid in system detection is discouraged as it requires +# temporary files to be created and, as you can see below, it is a +# headache to deal with in a portable fashion. + +# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still +# use `HOST_CC' if defined, but it is deprecated. + +# Portable tmp directory creation inspired by the Autoconf team. + +set_cc_for_build=' +trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && exit \$exitcode" 0 ; +trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ; +: ${TMPDIR=/tmp} ; + { tmp=`(umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } || + { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } || + { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } || + { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ; +dummy=$tmp/dummy ; +tmpfiles="$dummy.c $dummy.o $dummy.rel $dummy" ; +case $CC_FOR_BUILD,$HOST_CC,$CC in + ,,) echo "int x;" > $dummy.c ; + for c in cc gcc c89 c99 ; do + if ($c -c -o $dummy.o $dummy.c) >/dev/null 2>&1 ; then + CC_FOR_BUILD="$c"; break ; + fi ; + done ; + if test x"$CC_FOR_BUILD" = x ; then + CC_FOR_BUILD=no_compiler_found ; + fi + ;; + ,,*) CC_FOR_BUILD=$CC ;; + ,*,*) CC_FOR_BUILD=$HOST_CC ;; +esac ; set_cc_for_build= ;' + +# This is needed to find uname on a Pyramid OSx when run in the BSD universe. +# (ghazi@noc.rutgers.edu 1994-08-24) +if (test -f /.attbin/uname) >/dev/null 2>&1 ; then + PATH=$PATH:/.attbin ; export PATH +fi + +UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown +UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown +UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown +UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown + +# Note: order is significant - the case branches are not exclusive. + +case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in + *:NetBSD:*:*) + # NetBSD (nbsd) targets should (where applicable) match one or + # more of the tupples: *-*-netbsdelf*, *-*-netbsdaout*, + # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently + # switched to ELF, *-*-netbsd* would select the old + # object file format. This provides both forward + # compatibility and a consistent mechanism for selecting the + # object file format. + # + # Note: NetBSD doesn't particularly care about the vendor + # portion of the name. We always set it to "unknown". + sysctl="sysctl -n hw.machine_arch" + UNAME_MACHINE_ARCH=`(/sbin/$sysctl 2>/dev/null || \ + /usr/sbin/$sysctl 2>/dev/null || echo unknown)` + case "${UNAME_MACHINE_ARCH}" in + armeb) machine=armeb-unknown ;; + arm*) machine=arm-unknown ;; + sh3el) machine=shl-unknown ;; + sh3eb) machine=sh-unknown ;; + sh5el) machine=sh5le-unknown ;; + *) machine=${UNAME_MACHINE_ARCH}-unknown ;; + esac + # The Operating System including object format, if it has switched + # to ELF recently, or will in the future. + case "${UNAME_MACHINE_ARCH}" in + arm*|i386|m68k|ns32k|sh3*|sparc|vax) + eval $set_cc_for_build + if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep __ELF__ >/dev/null + then + # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). + # Return netbsd for either. FIX? + os=netbsd + else + os=netbsdelf + fi + ;; + *) + os=netbsd + ;; + esac + # The OS release + # Debian GNU/NetBSD machines have a different userland, and + # thus, need a distinct triplet. However, they do not need + # kernel version information, so it can be replaced with a + # suitable tag, in the style of linux-gnu. + case "${UNAME_VERSION}" in + Debian*) + release='-gnu' + ;; + *) + release=`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` + ;; + esac + # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: + # contains redundant information, the shorter form: + # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. + echo "${machine}-${os}${release}" + exit ;; + *:OpenBSD:*:*) + UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'` + echo ${UNAME_MACHINE_ARCH}-unknown-openbsd${UNAME_RELEASE} + exit ;; + *:ekkoBSD:*:*) + echo ${UNAME_MACHINE}-unknown-ekkobsd${UNAME_RELEASE} + exit ;; + *:SolidBSD:*:*) + echo ${UNAME_MACHINE}-unknown-solidbsd${UNAME_RELEASE} + exit ;; + macppc:MirBSD:*:*) + echo powerpc-unknown-mirbsd${UNAME_RELEASE} + exit ;; + *:MirBSD:*:*) + echo ${UNAME_MACHINE}-unknown-mirbsd${UNAME_RELEASE} + exit ;; + alpha:OSF1:*:*) + case $UNAME_RELEASE in + *4.0) + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` + ;; + *5.*) + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'` + ;; + esac + # According to Compaq, /usr/sbin/psrinfo has been available on + # OSF/1 and Tru64 systems produced since 1995. I hope that + # covers most systems running today. This code pipes the CPU + # types through head -n 1, so we only detect the type of CPU 0. + ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1` + case "$ALPHA_CPU_TYPE" in + "EV4 (21064)") + UNAME_MACHINE="alpha" ;; + "EV4.5 (21064)") + UNAME_MACHINE="alpha" ;; + "LCA4 (21066/21068)") + UNAME_MACHINE="alpha" ;; + "EV5 (21164)") + UNAME_MACHINE="alphaev5" ;; + "EV5.6 (21164A)") + UNAME_MACHINE="alphaev56" ;; + "EV5.6 (21164PC)") + UNAME_MACHINE="alphapca56" ;; + "EV5.7 (21164PC)") + UNAME_MACHINE="alphapca57" ;; + "EV6 (21264)") + UNAME_MACHINE="alphaev6" ;; + "EV6.7 (21264A)") + UNAME_MACHINE="alphaev67" ;; + "EV6.8CB (21264C)") + UNAME_MACHINE="alphaev68" ;; + "EV6.8AL (21264B)") + UNAME_MACHINE="alphaev68" ;; + "EV6.8CX (21264D)") + UNAME_MACHINE="alphaev68" ;; + "EV6.9A (21264/EV69A)") + UNAME_MACHINE="alphaev69" ;; + "EV7 (21364)") + UNAME_MACHINE="alphaev7" ;; + "EV7.9 (21364A)") + UNAME_MACHINE="alphaev79" ;; + esac + # A Pn.n version is a patched version. + # A Vn.n version is a released version. + # A Tn.n version is a released field test version. + # A Xn.n version is an unreleased experimental baselevel. + # 1.2 uses "1.2" for uname -r. + echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[PVTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` + exit ;; + Alpha\ *:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # Should we change UNAME_MACHINE based on the output of uname instead + # of the specific Alpha model? + echo alpha-pc-interix + exit ;; + 21064:Windows_NT:50:3) + echo alpha-dec-winnt3.5 + exit ;; + Amiga*:UNIX_System_V:4.0:*) + echo m68k-unknown-sysv4 + exit ;; + *:[Aa]miga[Oo][Ss]:*:*) + echo ${UNAME_MACHINE}-unknown-amigaos + exit ;; + *:[Mm]orph[Oo][Ss]:*:*) + echo ${UNAME_MACHINE}-unknown-morphos + exit ;; + *:OS/390:*:*) + echo i370-ibm-openedition + exit ;; + *:z/VM:*:*) + echo s390-ibm-zvmoe + exit ;; + *:OS400:*:*) + echo powerpc-ibm-os400 + exit ;; + arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) + echo arm-acorn-riscix${UNAME_RELEASE} + exit ;; + arm:riscos:*:*|arm:RISCOS:*:*) + echo arm-unknown-riscos + exit ;; + SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) + echo hppa1.1-hitachi-hiuxmpp + exit ;; + Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) + # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. + if test "`(/bin/universe) 2>/dev/null`" = att ; then + echo pyramid-pyramid-sysv3 + else + echo pyramid-pyramid-bsd + fi + exit ;; + NILE*:*:*:dcosx) + echo pyramid-pyramid-svr4 + exit ;; + DRS?6000:unix:4.0:6*) + echo sparc-icl-nx6 + exit ;; + DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*) + case `/usr/bin/uname -p` in + sparc) echo sparc-icl-nx7; exit ;; + esac ;; + sun4H:SunOS:5.*:*) + echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) + echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + i86pc:SunOS:5.*:* | i86xen:SunOS:5.*:*) + echo i386-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + sun4*:SunOS:6*:*) + # According to config.sub, this is the proper way to canonicalize + # SunOS6. Hard to guess exactly what SunOS6 will be like, but + # it's likely to be more like Solaris than SunOS4. + echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + sun4*:SunOS:*:*) + case "`/usr/bin/arch -k`" in + Series*|S4*) + UNAME_RELEASE=`uname -v` + ;; + esac + # Japanese Language versions have a version number like `4.1.3-JL'. + echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'` + exit ;; + sun3*:SunOS:*:*) + echo m68k-sun-sunos${UNAME_RELEASE} + exit ;; + sun*:*:4.2BSD:*) + UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` + test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3 + case "`/bin/arch`" in + sun3) + echo m68k-sun-sunos${UNAME_RELEASE} + ;; + sun4) + echo sparc-sun-sunos${UNAME_RELEASE} + ;; + esac + exit ;; + aushp:SunOS:*:*) + echo sparc-auspex-sunos${UNAME_RELEASE} + exit ;; + # The situation for MiNT is a little confusing. The machine name + # can be virtually everything (everything which is not + # "atarist" or "atariste" at least should have a processor + # > m68000). The system name ranges from "MiNT" over "FreeMiNT" + # to the lowercase version "mint" (or "freemint"). Finally + # the system name "TOS" denotes a system which is actually not + # MiNT. But MiNT is downward compatible to TOS, so this should + # be no problem. + atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit ;; + atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit ;; + *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit ;; + milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) + echo m68k-milan-mint${UNAME_RELEASE} + exit ;; + hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) + echo m68k-hades-mint${UNAME_RELEASE} + exit ;; + *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) + echo m68k-unknown-mint${UNAME_RELEASE} + exit ;; + m68k:machten:*:*) + echo m68k-apple-machten${UNAME_RELEASE} + exit ;; + powerpc:machten:*:*) + echo powerpc-apple-machten${UNAME_RELEASE} + exit ;; + RISC*:Mach:*:*) + echo mips-dec-mach_bsd4.3 + exit ;; + RISC*:ULTRIX:*:*) + echo mips-dec-ultrix${UNAME_RELEASE} + exit ;; + VAX*:ULTRIX*:*:*) + echo vax-dec-ultrix${UNAME_RELEASE} + exit ;; + 2020:CLIX:*:* | 2430:CLIX:*:*) + echo clipper-intergraph-clix${UNAME_RELEASE} + exit ;; + mips:*:*:UMIPS | mips:*:*:RISCos) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c +#ifdef __cplusplus +#include /* for printf() prototype */ + int main (int argc, char *argv[]) { +#else + int main (argc, argv) int argc; char *argv[]; { +#endif + #if defined (host_mips) && defined (MIPSEB) + #if defined (SYSTYPE_SYSV) + printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_SVR4) + printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) + printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0); + #endif + #endif + exit (-1); + } +EOF + $CC_FOR_BUILD -o $dummy $dummy.c && + dummyarg=`echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` && + SYSTEM_NAME=`$dummy $dummyarg` && + { echo "$SYSTEM_NAME"; exit; } + echo mips-mips-riscos${UNAME_RELEASE} + exit ;; + Motorola:PowerMAX_OS:*:*) + echo powerpc-motorola-powermax + exit ;; + Motorola:*:4.3:PL8-*) + echo powerpc-harris-powermax + exit ;; + Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) + echo powerpc-harris-powermax + exit ;; + Night_Hawk:Power_UNIX:*:*) + echo powerpc-harris-powerunix + exit ;; + m88k:CX/UX:7*:*) + echo m88k-harris-cxux7 + exit ;; + m88k:*:4*:R4*) + echo m88k-motorola-sysv4 + exit ;; + m88k:*:3*:R3*) + echo m88k-motorola-sysv3 + exit ;; + AViiON:dgux:*:*) + # DG/UX returns AViiON for all architectures + UNAME_PROCESSOR=`/usr/bin/uname -p` + if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ] + then + if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \ + [ ${TARGET_BINARY_INTERFACE}x = x ] + then + echo m88k-dg-dgux${UNAME_RELEASE} + else + echo m88k-dg-dguxbcs${UNAME_RELEASE} + fi + else + echo i586-dg-dgux${UNAME_RELEASE} + fi + exit ;; + M88*:DolphinOS:*:*) # DolphinOS (SVR3) + echo m88k-dolphin-sysv3 + exit ;; + M88*:*:R3*:*) + # Delta 88k system running SVR3 + echo m88k-motorola-sysv3 + exit ;; + XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) + echo m88k-tektronix-sysv3 + exit ;; + Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) + echo m68k-tektronix-bsd + exit ;; + *:IRIX*:*:*) + echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'` + exit ;; + ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. + echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id + exit ;; # Note that: echo "'`uname -s`'" gives 'AIX ' + i*86:AIX:*:*) + echo i386-ibm-aix + exit ;; + ia64:AIX:*:*) + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} + fi + echo ${UNAME_MACHINE}-ibm-aix${IBM_REV} + exit ;; + *:AIX:2:3) + if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include + + main() + { + if (!__power_pc()) + exit(1); + puts("powerpc-ibm-aix3.2.5"); + exit(0); + } +EOF + if $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy` + then + echo "$SYSTEM_NAME" + else + echo rs6000-ibm-aix3.2.5 + fi + elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then + echo rs6000-ibm-aix3.2.4 + else + echo rs6000-ibm-aix3.2 + fi + exit ;; + *:AIX:*:[456]) + IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'` + if /usr/sbin/lsattr -El ${IBM_CPU_ID} | grep ' POWER' >/dev/null 2>&1; then + IBM_ARCH=rs6000 + else + IBM_ARCH=powerpc + fi + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} + fi + echo ${IBM_ARCH}-ibm-aix${IBM_REV} + exit ;; + *:AIX:*:*) + echo rs6000-ibm-aix + exit ;; + ibmrt:4.4BSD:*|romp-ibm:BSD:*) + echo romp-ibm-bsd4.4 + exit ;; + ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and + echo romp-ibm-bsd${UNAME_RELEASE} # 4.3 with uname added to + exit ;; # report: romp-ibm BSD 4.3 + *:BOSX:*:*) + echo rs6000-bull-bosx + exit ;; + DPX/2?00:B.O.S.:*:*) + echo m68k-bull-sysv3 + exit ;; + 9000/[34]??:4.3bsd:1.*:*) + echo m68k-hp-bsd + exit ;; + hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) + echo m68k-hp-bsd4.4 + exit ;; + 9000/[34678]??:HP-UX:*:*) + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + case "${UNAME_MACHINE}" in + 9000/31? ) HP_ARCH=m68000 ;; + 9000/[34]?? ) HP_ARCH=m68k ;; + 9000/[678][0-9][0-9]) + if [ -x /usr/bin/getconf ]; then + sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` + sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` + case "${sc_cpu_version}" in + 523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0 + 528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1 + 532) # CPU_PA_RISC2_0 + case "${sc_kernel_bits}" in + 32) HP_ARCH="hppa2.0n" ;; + 64) HP_ARCH="hppa2.0w" ;; + '') HP_ARCH="hppa2.0" ;; # HP-UX 10.20 + esac ;; + esac + fi + if [ "${HP_ARCH}" = "" ]; then + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + + #define _HPUX_SOURCE + #include + #include + + int main () + { + #if defined(_SC_KERNEL_BITS) + long bits = sysconf(_SC_KERNEL_BITS); + #endif + long cpu = sysconf (_SC_CPU_VERSION); + + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1"); break; + case CPU_PA_RISC2_0: + #if defined(_SC_KERNEL_BITS) + switch (bits) + { + case 64: puts ("hppa2.0w"); break; + case 32: puts ("hppa2.0n"); break; + default: puts ("hppa2.0"); break; + } break; + #else /* !defined(_SC_KERNEL_BITS) */ + puts ("hppa2.0"); break; + #endif + default: puts ("hppa1.0"); break; + } + exit (0); + } +EOF + (CCOPTS= $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null) && HP_ARCH=`$dummy` + test -z "$HP_ARCH" && HP_ARCH=hppa + fi ;; + esac + if [ ${HP_ARCH} = "hppa2.0w" ] + then + eval $set_cc_for_build + + # hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating + # 32-bit code. hppa64-hp-hpux* has the same kernel and a compiler + # generating 64-bit code. GNU and HP use different nomenclature: + # + # $ CC_FOR_BUILD=cc ./config.guess + # => hppa2.0w-hp-hpux11.23 + # $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess + # => hppa64-hp-hpux11.23 + + if echo __LP64__ | (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | + grep __LP64__ >/dev/null + then + HP_ARCH="hppa2.0w" + else + HP_ARCH="hppa64" + fi + fi + echo ${HP_ARCH}-hp-hpux${HPUX_REV} + exit ;; + ia64:HP-UX:*:*) + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + echo ia64-hp-hpux${HPUX_REV} + exit ;; + 3050*:HI-UX:*:*) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include + int + main () + { + long cpu = sysconf (_SC_CPU_VERSION); + /* The order matters, because CPU_IS_HP_MC68K erroneously returns + true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct + results, however. */ + if (CPU_IS_PA_RISC (cpu)) + { + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; + case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; + default: puts ("hppa-hitachi-hiuxwe2"); break; + } + } + else if (CPU_IS_HP_MC68K (cpu)) + puts ("m68k-hitachi-hiuxwe2"); + else puts ("unknown-hitachi-hiuxwe2"); + exit (0); + } +EOF + $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy` && + { echo "$SYSTEM_NAME"; exit; } + echo unknown-hitachi-hiuxwe2 + exit ;; + 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* ) + echo hppa1.1-hp-bsd + exit ;; + 9000/8??:4.3bsd:*:*) + echo hppa1.0-hp-bsd + exit ;; + *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) + echo hppa1.0-hp-mpeix + exit ;; + hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* ) + echo hppa1.1-hp-osf + exit ;; + hp8??:OSF1:*:*) + echo hppa1.0-hp-osf + exit ;; + i*86:OSF1:*:*) + if [ -x /usr/sbin/sysversion ] ; then + echo ${UNAME_MACHINE}-unknown-osf1mk + else + echo ${UNAME_MACHINE}-unknown-osf1 + fi + exit ;; + parisc*:Lites*:*:*) + echo hppa1.1-hp-lites + exit ;; + C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) + echo c1-convex-bsd + exit ;; + C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit ;; + C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) + echo c34-convex-bsd + exit ;; + C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) + echo c38-convex-bsd + exit ;; + C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) + echo c4-convex-bsd + exit ;; + CRAY*Y-MP:*:*:*) + echo ymp-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*[A-Z]90:*:*:*) + echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \ + | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ + -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ + -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*TS:*:*:*) + echo t90-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*T3E:*:*:*) + echo alphaev5-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*SV1:*:*:*) + echo sv1-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + *:UNICOS/mp:*:*) + echo craynv-cray-unicosmp${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) + FUJITSU_PROC=`uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` + FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` + FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'` + echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit ;; + 5000:UNIX_System_V:4.*:*) + FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` + FUJITSU_REL=`echo ${UNAME_RELEASE} | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/ /_/'` + echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit ;; + i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) + echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE} + exit ;; + sparc*:BSD/OS:*:*) + echo sparc-unknown-bsdi${UNAME_RELEASE} + exit ;; + *:BSD/OS:*:*) + echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE} + exit ;; + *:FreeBSD:*:*) + case ${UNAME_MACHINE} in + pc98) + echo i386-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;; + amd64) + echo x86_64-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;; + *) + echo ${UNAME_MACHINE}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;; + esac + exit ;; + i*:CYGWIN*:*) + echo ${UNAME_MACHINE}-pc-cygwin + exit ;; + *:MINGW*:*) + echo ${UNAME_MACHINE}-pc-mingw32 + exit ;; + i*:windows32*:*) + # uname -m includes "-pc" on this system. + echo ${UNAME_MACHINE}-mingw32 + exit ;; + i*:PW*:*) + echo ${UNAME_MACHINE}-pc-pw32 + exit ;; + *:Interix*:[3456]*) + case ${UNAME_MACHINE} in + x86) + echo i586-pc-interix${UNAME_RELEASE} + exit ;; + EM64T | authenticamd) + echo x86_64-unknown-interix${UNAME_RELEASE} + exit ;; + IA64) + echo ia64-unknown-interix${UNAME_RELEASE} + exit ;; + esac ;; + [345]86:Windows_95:* | [345]86:Windows_98:* | [345]86:Windows_NT:*) + echo i${UNAME_MACHINE}-pc-mks + exit ;; + i*:Windows_NT*:* | Pentium*:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # It also conflicts with pre-2.0 versions of AT&T UWIN. Should we + # UNAME_MACHINE based on the output of uname instead of i386? + echo i586-pc-interix + exit ;; + i*:UWIN*:*) + echo ${UNAME_MACHINE}-pc-uwin + exit ;; + amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*) + echo x86_64-unknown-cygwin + exit ;; + p*:CYGWIN*:*) + echo powerpcle-unknown-cygwin + exit ;; + prep*:SunOS:5.*:*) + echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + *:GNU:*:*) + # the GNU system + echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-gnu`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'` + exit ;; + *:GNU/*:*:*) + # other systems with GNU libc and userland + echo ${UNAME_MACHINE}-unknown-`echo ${UNAME_SYSTEM} | sed 's,^[^/]*/,,' | tr '[A-Z]' '[a-z]'``echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`-gnu + exit ;; + i*86:Minix:*:*) + echo ${UNAME_MACHINE}-pc-minix + exit ;; + arm*:Linux:*:*) + eval $set_cc_for_build + if echo __ARM_EABI__ | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ARM_EABI__ + then + echo ${UNAME_MACHINE}-unknown-linux-gnu + else + echo ${UNAME_MACHINE}-unknown-linux-gnueabi + fi + exit ;; + avr32*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + cris:Linux:*:*) + echo cris-axis-linux-gnu + exit ;; + crisv32:Linux:*:*) + echo crisv32-axis-linux-gnu + exit ;; + frv:Linux:*:*) + echo frv-unknown-linux-gnu + exit ;; + ia64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + m32r*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + m68*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + mips:Linux:*:*) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #undef CPU + #undef mips + #undef mipsel + #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) + CPU=mipsel + #else + #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) + CPU=mips + #else + CPU= + #endif + #endif +EOF + eval "`$CC_FOR_BUILD -E $dummy.c 2>/dev/null | sed -n ' + /^CPU/{ + s: ::g + p + }'`" + test x"${CPU}" != x && { echo "${CPU}-unknown-linux-gnu"; exit; } + ;; + mips64:Linux:*:*) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #undef CPU + #undef mips64 + #undef mips64el + #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) + CPU=mips64el + #else + #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) + CPU=mips64 + #else + CPU= + #endif + #endif +EOF + eval "`$CC_FOR_BUILD -E $dummy.c 2>/dev/null | sed -n ' + /^CPU/{ + s: ::g + p + }'`" + test x"${CPU}" != x && { echo "${CPU}-unknown-linux-gnu"; exit; } + ;; + or32:Linux:*:*) + echo or32-unknown-linux-gnu + exit ;; + ppc:Linux:*:*) + echo powerpc-unknown-linux-gnu + exit ;; + ppc64:Linux:*:*) + echo powerpc64-unknown-linux-gnu + exit ;; + alpha:Linux:*:*) + case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in + EV5) UNAME_MACHINE=alphaev5 ;; + EV56) UNAME_MACHINE=alphaev56 ;; + PCA56) UNAME_MACHINE=alphapca56 ;; + PCA57) UNAME_MACHINE=alphapca56 ;; + EV6) UNAME_MACHINE=alphaev6 ;; + EV67) UNAME_MACHINE=alphaev67 ;; + EV68*) UNAME_MACHINE=alphaev68 ;; + esac + objdump --private-headers /bin/sh | grep ld.so.1 >/dev/null + if test "$?" = 0 ; then LIBC="libc1" ; else LIBC="" ; fi + echo ${UNAME_MACHINE}-unknown-linux-gnu${LIBC} + exit ;; + parisc:Linux:*:* | hppa:Linux:*:*) + # Look for CPU level + case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in + PA7*) echo hppa1.1-unknown-linux-gnu ;; + PA8*) echo hppa2.0-unknown-linux-gnu ;; + *) echo hppa-unknown-linux-gnu ;; + esac + exit ;; + parisc64:Linux:*:* | hppa64:Linux:*:*) + echo hppa64-unknown-linux-gnu + exit ;; + s390:Linux:*:* | s390x:Linux:*:*) + echo ${UNAME_MACHINE}-ibm-linux + exit ;; + sh64*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + sh*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + sparc:Linux:*:* | sparc64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + vax:Linux:*:*) + echo ${UNAME_MACHINE}-dec-linux-gnu + exit ;; + x86_64:Linux:*:*) + echo x86_64-unknown-linux-gnu + exit ;; + xtensa*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + i*86:Linux:*:*) + # The BFD linker knows what the default object file format is, so + # first see if it will tell us. cd to the root directory to prevent + # problems with other programs or directories called `ld' in the path. + # Set LC_ALL=C to ensure ld outputs messages in English. + ld_supported_targets=`cd /; LC_ALL=C ld --help 2>&1 \ + | sed -ne '/supported targets:/!d + s/[ ][ ]*/ /g + s/.*supported targets: *// + s/ .*// + p'` + case "$ld_supported_targets" in + elf32-i386) + TENTATIVE="${UNAME_MACHINE}-pc-linux-gnu" + ;; + a.out-i386-linux) + echo "${UNAME_MACHINE}-pc-linux-gnuaout" + exit ;; + coff-i386) + echo "${UNAME_MACHINE}-pc-linux-gnucoff" + exit ;; + "") + # Either a pre-BFD a.out linker (linux-gnuoldld) or + # one that does not give us useful --help. + echo "${UNAME_MACHINE}-pc-linux-gnuoldld" + exit ;; + esac + # Determine whether the default compiler is a.out or elf + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include + #ifdef __ELF__ + # ifdef __GLIBC__ + # if __GLIBC__ >= 2 + LIBC=gnu + # else + LIBC=gnulibc1 + # endif + # else + LIBC=gnulibc1 + # endif + #else + #if defined(__INTEL_COMPILER) || defined(__PGI) || defined(__SUNPRO_C) || defined(__SUNPRO_CC) + LIBC=gnu + #else + LIBC=gnuaout + #endif + #endif + #ifdef __dietlibc__ + LIBC=dietlibc + #endif +EOF + eval "`$CC_FOR_BUILD -E $dummy.c 2>/dev/null | sed -n ' + /^LIBC/{ + s: ::g + p + }'`" + test x"${LIBC}" != x && { + echo "${UNAME_MACHINE}-pc-linux-${LIBC}" + exit + } + test x"${TENTATIVE}" != x && { echo "${TENTATIVE}"; exit; } + ;; + i*86:DYNIX/ptx:4*:*) + # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. + # earlier versions are messed up and put the nodename in both + # sysname and nodename. + echo i386-sequent-sysv4 + exit ;; + i*86:UNIX_SV:4.2MP:2.*) + # Unixware is an offshoot of SVR4, but it has its own version + # number series starting with 2... + # I am not positive that other SVR4 systems won't match this, + # I just have to hope. -- rms. + # Use sysv4.2uw... so that sysv4* matches it. + echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION} + exit ;; + i*86:OS/2:*:*) + # If we were able to find `uname', then EMX Unix compatibility + # is probably installed. + echo ${UNAME_MACHINE}-pc-os2-emx + exit ;; + i*86:XTS-300:*:STOP) + echo ${UNAME_MACHINE}-unknown-stop + exit ;; + i*86:atheos:*:*) + echo ${UNAME_MACHINE}-unknown-atheos + exit ;; + i*86:syllable:*:*) + echo ${UNAME_MACHINE}-pc-syllable + exit ;; + i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.0*:*) + echo i386-unknown-lynxos${UNAME_RELEASE} + exit ;; + i*86:*DOS:*:*) + echo ${UNAME_MACHINE}-pc-msdosdjgpp + exit ;; + i*86:*:4.*:* | i*86:SYSTEM_V:4.*:*) + UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'` + if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then + echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL} + else + echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL} + fi + exit ;; + i*86:*:5:[678]*) + # UnixWare 7.x, OpenUNIX and OpenServer 6. + case `/bin/uname -X | grep "^Machine"` in + *486*) UNAME_MACHINE=i486 ;; + *Pentium) UNAME_MACHINE=i586 ;; + *Pent*|*Celeron) UNAME_MACHINE=i686 ;; + esac + echo ${UNAME_MACHINE}-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION} + exit ;; + i*86:*:3.2:*) + if test -f /usr/options/cb.name; then + UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then + UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')` + (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486 + (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \ + && UNAME_MACHINE=i586 + (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \ + && UNAME_MACHINE=i686 + (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \ + && UNAME_MACHINE=i686 + echo ${UNAME_MACHINE}-pc-sco$UNAME_REL + else + echo ${UNAME_MACHINE}-pc-sysv32 + fi + exit ;; + pc:*:*:*) + # Left here for compatibility: + # uname -m prints for DJGPP always 'pc', but it prints nothing about + # the processor, so we play safe by assuming i386. + echo i386-pc-msdosdjgpp + exit ;; + Intel:Mach:3*:*) + echo i386-pc-mach3 + exit ;; + paragon:*:*:*) + echo i860-intel-osf1 + exit ;; + i860:*:4.*:*) # i860-SVR4 + if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then + echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4 + else # Add other i860-SVR4 vendors below as they are discovered. + echo i860-unknown-sysv${UNAME_RELEASE} # Unknown i860-SVR4 + fi + exit ;; + mini*:CTIX:SYS*5:*) + # "miniframe" + echo m68010-convergent-sysv + exit ;; + mc68k:UNIX:SYSTEM5:3.51m) + echo m68k-convergent-sysv + exit ;; + M680?0:D-NIX:5.3:*) + echo m68k-diab-dnix + exit ;; + M68*:*:R3V[5678]*:*) + test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;; + 3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0) + OS_REL='' + test -r /etc/.relid \ + && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4.3${OS_REL}; exit; } + /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ + && { echo i586-ncr-sysv4.3${OS_REL}; exit; } ;; + 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4; exit; } ;; + m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) + echo m68k-unknown-lynxos${UNAME_RELEASE} + exit ;; + mc68030:UNIX_System_V:4.*:*) + echo m68k-atari-sysv4 + exit ;; + TSUNAMI:LynxOS:2.*:*) + echo sparc-unknown-lynxos${UNAME_RELEASE} + exit ;; + rs6000:LynxOS:2.*:*) + echo rs6000-unknown-lynxos${UNAME_RELEASE} + exit ;; + PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.0*:*) + echo powerpc-unknown-lynxos${UNAME_RELEASE} + exit ;; + SM[BE]S:UNIX_SV:*:*) + echo mips-dde-sysv${UNAME_RELEASE} + exit ;; + RM*:ReliantUNIX-*:*:*) + echo mips-sni-sysv4 + exit ;; + RM*:SINIX-*:*:*) + echo mips-sni-sysv4 + exit ;; + *:SINIX-*:*:*) + if uname -p 2>/dev/null >/dev/null ; then + UNAME_MACHINE=`(uname -p) 2>/dev/null` + echo ${UNAME_MACHINE}-sni-sysv4 + else + echo ns32k-sni-sysv + fi + exit ;; + PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort + # says + echo i586-unisys-sysv4 + exit ;; + *:UNIX_System_V:4*:FTX*) + # From Gerald Hewes . + # How about differentiating between stratus architectures? -djm + echo hppa1.1-stratus-sysv4 + exit ;; + *:*:*:FTX*) + # From seanf@swdc.stratus.com. + echo i860-stratus-sysv4 + exit ;; + i*86:VOS:*:*) + # From Paul.Green@stratus.com. + echo ${UNAME_MACHINE}-stratus-vos + exit ;; + *:VOS:*:*) + # From Paul.Green@stratus.com. + echo hppa1.1-stratus-vos + exit ;; + mc68*:A/UX:*:*) + echo m68k-apple-aux${UNAME_RELEASE} + exit ;; + news*:NEWS-OS:6*:*) + echo mips-sony-newsos6 + exit ;; + R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) + if [ -d /usr/nec ]; then + echo mips-nec-sysv${UNAME_RELEASE} + else + echo mips-unknown-sysv${UNAME_RELEASE} + fi + exit ;; + BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. + echo powerpc-be-beos + exit ;; + BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. + echo powerpc-apple-beos + exit ;; + BePC:BeOS:*:*) # BeOS running on Intel PC compatible. + echo i586-pc-beos + exit ;; + SX-4:SUPER-UX:*:*) + echo sx4-nec-superux${UNAME_RELEASE} + exit ;; + SX-5:SUPER-UX:*:*) + echo sx5-nec-superux${UNAME_RELEASE} + exit ;; + SX-6:SUPER-UX:*:*) + echo sx6-nec-superux${UNAME_RELEASE} + exit ;; + SX-7:SUPER-UX:*:*) + echo sx7-nec-superux${UNAME_RELEASE} + exit ;; + SX-8:SUPER-UX:*:*) + echo sx8-nec-superux${UNAME_RELEASE} + exit ;; + SX-8R:SUPER-UX:*:*) + echo sx8r-nec-superux${UNAME_RELEASE} + exit ;; + Power*:Rhapsody:*:*) + echo powerpc-apple-rhapsody${UNAME_RELEASE} + exit ;; + *:Rhapsody:*:*) + echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE} + exit ;; + *:Darwin:*:*) + UNAME_PROCESSOR=`uname -p` || UNAME_PROCESSOR=unknown + case $UNAME_PROCESSOR in + unknown) UNAME_PROCESSOR=powerpc ;; + esac + echo ${UNAME_PROCESSOR}-apple-darwin${UNAME_RELEASE} + exit ;; + *:procnto*:*:* | *:QNX:[0123456789]*:*) + UNAME_PROCESSOR=`uname -p` + if test "$UNAME_PROCESSOR" = "x86"; then + UNAME_PROCESSOR=i386 + UNAME_MACHINE=pc + fi + echo ${UNAME_PROCESSOR}-${UNAME_MACHINE}-nto-qnx${UNAME_RELEASE} + exit ;; + *:QNX:*:4*) + echo i386-pc-qnx + exit ;; + NSE-?:NONSTOP_KERNEL:*:*) + echo nse-tandem-nsk${UNAME_RELEASE} + exit ;; + NSR-?:NONSTOP_KERNEL:*:*) + echo nsr-tandem-nsk${UNAME_RELEASE} + exit ;; + *:NonStop-UX:*:*) + echo mips-compaq-nonstopux + exit ;; + BS2000:POSIX*:*:*) + echo bs2000-siemens-sysv + exit ;; + DS/*:UNIX_System_V:*:*) + echo ${UNAME_MACHINE}-${UNAME_SYSTEM}-${UNAME_RELEASE} + exit ;; + *:Plan9:*:*) + # "uname -m" is not consistent, so use $cputype instead. 386 + # is converted to i386 for consistency with other x86 + # operating systems. + if test "$cputype" = "386"; then + UNAME_MACHINE=i386 + else + UNAME_MACHINE="$cputype" + fi + echo ${UNAME_MACHINE}-unknown-plan9 + exit ;; + *:TOPS-10:*:*) + echo pdp10-unknown-tops10 + exit ;; + *:TENEX:*:*) + echo pdp10-unknown-tenex + exit ;; + KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) + echo pdp10-dec-tops20 + exit ;; + XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) + echo pdp10-xkl-tops20 + exit ;; + *:TOPS-20:*:*) + echo pdp10-unknown-tops20 + exit ;; + *:ITS:*:*) + echo pdp10-unknown-its + exit ;; + SEI:*:*:SEIUX) + echo mips-sei-seiux${UNAME_RELEASE} + exit ;; + *:DragonFly:*:*) + echo ${UNAME_MACHINE}-unknown-dragonfly`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` + exit ;; + *:*VMS:*:*) + UNAME_MACHINE=`(uname -p) 2>/dev/null` + case "${UNAME_MACHINE}" in + A*) echo alpha-dec-vms ; exit ;; + I*) echo ia64-dec-vms ; exit ;; + V*) echo vax-dec-vms ; exit ;; + esac ;; + *:XENIX:*:SysV) + echo i386-pc-xenix + exit ;; + i*86:skyos:*:*) + echo ${UNAME_MACHINE}-pc-skyos`echo ${UNAME_RELEASE}` | sed -e 's/ .*$//' + exit ;; + i*86:rdos:*:*) + echo ${UNAME_MACHINE}-pc-rdos + exit ;; +esac + +#echo '(No uname command or uname output not recognized.)' 1>&2 +#echo "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" 1>&2 + +eval $set_cc_for_build +cat >$dummy.c < +# include +#endif +main () +{ +#if defined (sony) +#if defined (MIPSEB) + /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed, + I don't know.... */ + printf ("mips-sony-bsd\n"); exit (0); +#else +#include + printf ("m68k-sony-newsos%s\n", +#ifdef NEWSOS4 + "4" +#else + "" +#endif + ); exit (0); +#endif +#endif + +#if defined (__arm) && defined (__acorn) && defined (__unix) + printf ("arm-acorn-riscix\n"); exit (0); +#endif + +#if defined (hp300) && !defined (hpux) + printf ("m68k-hp-bsd\n"); exit (0); +#endif + +#if defined (NeXT) +#if !defined (__ARCHITECTURE__) +#define __ARCHITECTURE__ "m68k" +#endif + int version; + version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`; + if (version < 4) + printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version); + else + printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version); + exit (0); +#endif + +#if defined (MULTIMAX) || defined (n16) +#if defined (UMAXV) + printf ("ns32k-encore-sysv\n"); exit (0); +#else +#if defined (CMU) + printf ("ns32k-encore-mach\n"); exit (0); +#else + printf ("ns32k-encore-bsd\n"); exit (0); +#endif +#endif +#endif + +#if defined (__386BSD__) + printf ("i386-pc-bsd\n"); exit (0); +#endif + +#if defined (sequent) +#if defined (i386) + printf ("i386-sequent-dynix\n"); exit (0); +#endif +#if defined (ns32000) + printf ("ns32k-sequent-dynix\n"); exit (0); +#endif +#endif + +#if defined (_SEQUENT_) + struct utsname un; + + uname(&un); + + if (strncmp(un.version, "V2", 2) == 0) { + printf ("i386-sequent-ptx2\n"); exit (0); + } + if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */ + printf ("i386-sequent-ptx1\n"); exit (0); + } + printf ("i386-sequent-ptx\n"); exit (0); + +#endif + +#if defined (vax) +# if !defined (ultrix) +# include +# if defined (BSD) +# if BSD == 43 + printf ("vax-dec-bsd4.3\n"); exit (0); +# else +# if BSD == 199006 + printf ("vax-dec-bsd4.3reno\n"); exit (0); +# else + printf ("vax-dec-bsd\n"); exit (0); +# endif +# endif +# else + printf ("vax-dec-bsd\n"); exit (0); +# endif +# else + printf ("vax-dec-ultrix\n"); exit (0); +# endif +#endif + +#if defined (alliant) && defined (i860) + printf ("i860-alliant-bsd\n"); exit (0); +#endif + + exit (1); +} +EOF + +$CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null && SYSTEM_NAME=`$dummy` && + { echo "$SYSTEM_NAME"; exit; } + +# Apollos put the system type in the environment. + +test -d /usr/apollo && { echo ${ISP}-apollo-${SYSTYPE}; exit; } + +# Convex versions that predate uname can use getsysinfo(1) + +if [ -x /usr/convex/getsysinfo ] +then + case `getsysinfo -f cpu_type` in + c1*) + echo c1-convex-bsd + exit ;; + c2*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit ;; + c34*) + echo c34-convex-bsd + exit ;; + c38*) + echo c38-convex-bsd + exit ;; + c4*) + echo c4-convex-bsd + exit ;; + esac +fi + +cat >&2 < in order to provide the needed +information to handle your system. + +config.guess timestamp = $timestamp + +uname -m = `(uname -m) 2>/dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null` + +hostinfo = `(hostinfo) 2>/dev/null` +/bin/universe = `(/bin/universe) 2>/dev/null` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` +/bin/arch = `(/bin/arch) 2>/dev/null` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` + +UNAME_MACHINE = ${UNAME_MACHINE} +UNAME_RELEASE = ${UNAME_RELEASE} +UNAME_SYSTEM = ${UNAME_SYSTEM} +UNAME_VERSION = ${UNAME_VERSION} +EOF + +exit 1 + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/third_party/libevent/config.h.in b/third_party/libevent/config.h.in new file mode 100644 index 0000000000..149942c11b --- /dev/null +++ b/third_party/libevent/config.h.in @@ -0,0 +1,265 @@ +/* config.h.in. Generated from configure.in by autoheader. */ + +/* Define if clock_gettime is available in libc */ +#undef DNS_USE_CPU_CLOCK_FOR_ID + +/* Define is no secure id variant is available */ +#undef DNS_USE_GETTIMEOFDAY_FOR_ID + +/* Define to 1 if you have the `clock_gettime' function. */ +#undef HAVE_CLOCK_GETTIME + +/* Define if /dev/poll is available */ +#undef HAVE_DEVPOLL + +/* Define to 1 if you have the header file. */ +#undef HAVE_DLFCN_H + +/* Define if your system supports the epoll system calls */ +#undef HAVE_EPOLL + +/* Define to 1 if you have the `epoll_ctl' function. */ +#undef HAVE_EPOLL_CTL + +/* Define if your system supports event ports */ +#undef HAVE_EVENT_PORTS + +/* Define to 1 if you have the `fcntl' function. */ +#undef HAVE_FCNTL + +/* Define to 1 if you have the header file. */ +#undef HAVE_FCNTL_H + +/* Define to 1 if the system has the type `fd_mask'. */ +#undef HAVE_FD_MASK + +/* Define to 1 if you have the `getaddrinfo' function. */ +#undef HAVE_GETADDRINFO + +/* Define to 1 if you have the `getegid' function. */ +#undef HAVE_GETEGID + +/* Define to 1 if you have the `geteuid' function. */ +#undef HAVE_GETEUID + +/* Define to 1 if you have the `getnameinfo' function. */ +#undef HAVE_GETNAMEINFO + +/* Define to 1 if you have the `gettimeofday' function. */ +#undef HAVE_GETTIMEOFDAY + +/* Define to 1 if you have the `inet_ntop' function. */ +#undef HAVE_INET_NTOP + +/* Define to 1 if you have the header file. */ +#undef HAVE_INTTYPES_H + +/* Define to 1 if you have the `issetugid' function. */ +#undef HAVE_ISSETUGID + +/* Define to 1 if you have the `kqueue' function. */ +#undef HAVE_KQUEUE + +/* Define to 1 if you have the `nsl' library (-lnsl). */ +#undef HAVE_LIBNSL + +/* Define to 1 if you have the `resolv' library (-lresolv). */ +#undef HAVE_LIBRESOLV + +/* Define to 1 if you have the `rt' library (-lrt). */ +#undef HAVE_LIBRT + +/* Define to 1 if you have the `socket' library (-lsocket). */ +#undef HAVE_LIBSOCKET + +/* Define to 1 if you have the header file. */ +#undef HAVE_MEMORY_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_NETINET_IN6_H + +/* Define to 1 if you have the `poll' function. */ +#undef HAVE_POLL + +/* Define to 1 if you have the header file. */ +#undef HAVE_POLL_H + +/* Define to 1 if you have the `port_create' function. */ +#undef HAVE_PORT_CREATE + +/* Define to 1 if you have the header file. */ +#undef HAVE_PORT_H + +/* Define to 1 if you have the `select' function. */ +#undef HAVE_SELECT + +/* Define if F_SETFD is defined in */ +#undef HAVE_SETFD + +/* Define to 1 if you have the `sigaction' function. */ +#undef HAVE_SIGACTION + +/* Define to 1 if you have the `signal' function. */ +#undef HAVE_SIGNAL + +/* Define to 1 if you have the header file. */ +#undef HAVE_SIGNAL_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDARG_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDINT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDLIB_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRINGS_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRING_H + +/* Define to 1 if you have the `strlcpy' function. */ +#undef HAVE_STRLCPY + +/* Define to 1 if you have the `strsep' function. */ +#undef HAVE_STRSEP + +/* Define to 1 if you have the `strtok_r' function. */ +#undef HAVE_STRTOK_R + +/* Define to 1 if you have the `strtoll' function. */ +#undef HAVE_STRTOLL + +/* Define to 1 if the system has the type `struct in6_addr'. */ +#undef HAVE_STRUCT_IN6_ADDR + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_DEVPOLL_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_EPOLL_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_EVENT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_IOCTL_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_PARAM_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_QUEUE_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_SELECT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_SOCKET_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_STAT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_TIME_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_TYPES_H + +/* Define if TAILQ_FOREACH is defined in */ +#undef HAVE_TAILQFOREACH + +/* Define if timeradd is defined in */ +#undef HAVE_TIMERADD + +/* Define if timerclear is defined in */ +#undef HAVE_TIMERCLEAR + +/* Define if timercmp is defined in */ +#undef HAVE_TIMERCMP + +/* Define if timerisset is defined in */ +#undef HAVE_TIMERISSET + +/* Define to 1 if the system has the type `uint16_t'. */ +#undef HAVE_UINT16_T + +/* Define to 1 if the system has the type `uint32_t'. */ +#undef HAVE_UINT32_T + +/* Define to 1 if the system has the type `uint64_t'. */ +#undef HAVE_UINT64_T + +/* Define to 1 if the system has the type `uint8_t'. */ +#undef HAVE_UINT8_T + +/* Define to 1 if you have the header file. */ +#undef HAVE_UNISTD_H + +/* Define to 1 if you have the `vasprintf' function. */ +#undef HAVE_VASPRINTF + +/* Define if kqueue works correctly with pipes */ +#undef HAVE_WORKING_KQUEUE + +/* Name of package */ +#undef PACKAGE + +/* Define to the address where bug reports for this package should be sent. */ +#undef PACKAGE_BUGREPORT + +/* Define to the full name of this package. */ +#undef PACKAGE_NAME + +/* Define to the full name and version of this package. */ +#undef PACKAGE_STRING + +/* Define to the one symbol short name of this package. */ +#undef PACKAGE_TARNAME + +/* Define to the version of this package. */ +#undef PACKAGE_VERSION + +/* The size of `int', as computed by sizeof. */ +#undef SIZEOF_INT + +/* The size of `long', as computed by sizeof. */ +#undef SIZEOF_LONG + +/* The size of `long long', as computed by sizeof. */ +#undef SIZEOF_LONG_LONG + +/* The size of `short', as computed by sizeof. */ +#undef SIZEOF_SHORT + +/* Define to 1 if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Define to 1 if you can safely include both and . */ +#undef TIME_WITH_SYS_TIME + +/* Version number of package */ +#undef VERSION + +/* Define to appropriate substitue if compiler doesnt have __func__ */ +#undef __func__ + +/* Define to empty if `const' does not conform to ANSI C. */ +#undef const + +/* Define to `__inline__' or `__inline' if that's what the C compiler + calls it, or to nothing if 'inline' is not supported under any name. */ +#ifndef __cplusplus +#undef inline +#endif + +/* Define to `int' if does not define. */ +#undef pid_t + +/* Define to `unsigned int' if does not define. */ +#undef size_t + +/* Define to unsigned int if you dont have it */ +#undef socklen_t diff --git a/third_party/libevent/config.sub b/third_party/libevent/config.sub new file mode 100644 index 0000000000..6759825a5b --- /dev/null +++ b/third_party/libevent/config.sub @@ -0,0 +1,1658 @@ +#! /bin/sh +# Configuration validation subroutine script. +# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, +# 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 +# Free Software Foundation, Inc. + +timestamp='2008-01-16' + +# This file is (in principle) common to ALL GNU software. +# The presence of a machine in this file suggests that SOME GNU software +# can handle that machine. It does not imply ALL GNU software can. +# +# This file is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA +# 02110-1301, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + + +# Please send patches to . Submit a context +# diff and a properly formatted ChangeLog entry. +# +# Configuration subroutine to validate and canonicalize a configuration type. +# Supply the specified configuration type as an argument. +# If it is invalid, we print an error message on stderr and exit with code 1. +# Otherwise, we print the canonical config type on stdout and succeed. + +# This file is supposed to be the same for all GNU packages +# and recognize all the CPU types, system types and aliases +# that are meaningful with *any* GNU software. +# Each package is responsible for reporting which valid configurations +# it does not support. The user should be able to distinguish +# a failure to support a valid configuration from a meaningless +# configuration. + +# The goal of this file is to map all the various variations of a given +# machine specification into a single specification in the form: +# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM +# or in some cases, the newer four-part form: +# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM +# It is wrong to echo any other type of specification. + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] CPU-MFR-OPSYS + $0 [OPTION] ALIAS + +Canonicalize a configuration name. + +Operation modes: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.sub ($timestamp) + +Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, +2002, 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit ;; + --version | -v ) + echo "$version" ; exit ;; + --help | --h* | -h ) + echo "$usage"; exit ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" + exit 1 ;; + + *local*) + # First pass through any local machine types. + echo $1 + exit ;; + + * ) + break ;; + esac +done + +case $# in + 0) echo "$me: missing argument$help" >&2 + exit 1;; + 1) ;; + *) echo "$me: too many arguments$help" >&2 + exit 1;; +esac + +# Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any). +# Here we must recognize all the valid KERNEL-OS combinations. +maybe_os=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'` +case $maybe_os in + nto-qnx* | linux-gnu* | linux-dietlibc | linux-newlib* | linux-uclibc* | \ + uclinux-uclibc* | uclinux-gnu* | kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* | \ + storm-chaos* | os2-emx* | rtmk-nova*) + os=-$maybe_os + basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'` + ;; + *) + basic_machine=`echo $1 | sed 's/-[^-]*$//'` + if [ $basic_machine != $1 ] + then os=`echo $1 | sed 's/.*-/-/'` + else os=; fi + ;; +esac + +### Let's recognize common machines as not being operating systems so +### that things like config.sub decstation-3100 work. We also +### recognize some manufacturers as not being operating systems, so we +### can provide default operating systems below. +case $os in + -sun*os*) + # Prevent following clause from handling this invalid input. + ;; + -dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \ + -att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \ + -unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \ + -convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\ + -c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \ + -harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \ + -apple | -axis | -knuth | -cray) + os= + basic_machine=$1 + ;; + -sim | -cisco | -oki | -wec | -winbond) + os= + basic_machine=$1 + ;; + -scout) + ;; + -wrs) + os=-vxworks + basic_machine=$1 + ;; + -chorusos*) + os=-chorusos + basic_machine=$1 + ;; + -chorusrdb) + os=-chorusrdb + basic_machine=$1 + ;; + -hiux*) + os=-hiuxwe2 + ;; + -sco6) + os=-sco5v6 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco5) + os=-sco3.2v5 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco4) + os=-sco3.2v4 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2.[4-9]*) + os=`echo $os | sed -e 's/sco3.2./sco3.2v/'` + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2v[4-9]*) + # Don't forget version if it is 3.2v4 or newer. + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco5v6*) + # Don't forget version if it is 3.2v4 or newer. + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco*) + os=-sco3.2v2 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -udk*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -isc) + os=-isc2.2 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -clix*) + basic_machine=clipper-intergraph + ;; + -isc*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -lynx*) + os=-lynxos + ;; + -ptx*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-sequent/'` + ;; + -windowsnt*) + os=`echo $os | sed -e 's/windowsnt/winnt/'` + ;; + -psos*) + os=-psos + ;; + -mint | -mint[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; +esac + +# Decode aliases for certain CPU-COMPANY combinations. +case $basic_machine in + # Recognize the basic CPU types without company name. + # Some are omitted here because they have special meanings below. + 1750a | 580 \ + | a29k \ + | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] | alphapca5[67] \ + | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] | alpha64pca5[67] \ + | am33_2.0 \ + | arc | arm | arm[bl]e | arme[lb] | armv[2345] | armv[345][lb] | avr | avr32 \ + | bfin \ + | c4x | clipper \ + | d10v | d30v | dlx | dsp16xx \ + | fido | fr30 | frv \ + | h8300 | h8500 | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \ + | i370 | i860 | i960 | ia64 \ + | ip2k | iq2000 \ + | m32c | m32r | m32rle | m68000 | m68k | m88k \ + | maxq | mb | microblaze | mcore | mep \ + | mips | mipsbe | mipseb | mipsel | mipsle \ + | mips16 \ + | mips64 | mips64el \ + | mips64vr | mips64vrel \ + | mips64orion | mips64orionel \ + | mips64vr4100 | mips64vr4100el \ + | mips64vr4300 | mips64vr4300el \ + | mips64vr5000 | mips64vr5000el \ + | mips64vr5900 | mips64vr5900el \ + | mipsisa32 | mipsisa32el \ + | mipsisa32r2 | mipsisa32r2el \ + | mipsisa64 | mipsisa64el \ + | mipsisa64r2 | mipsisa64r2el \ + | mipsisa64sb1 | mipsisa64sb1el \ + | mipsisa64sr71k | mipsisa64sr71kel \ + | mipstx39 | mipstx39el \ + | mn10200 | mn10300 \ + | mt \ + | msp430 \ + | nios | nios2 \ + | ns16k | ns32k \ + | or32 \ + | pdp10 | pdp11 | pj | pjl \ + | powerpc | powerpc64 | powerpc64le | powerpcle | ppcbe \ + | pyramid \ + | score \ + | sh | sh[1234] | sh[24]a | sh[23]e | sh[34]eb | sheb | shbe | shle | sh[1234]le | sh3ele \ + | sh64 | sh64le \ + | sparc | sparc64 | sparc64b | sparc64v | sparc86x | sparclet | sparclite \ + | sparcv8 | sparcv9 | sparcv9b | sparcv9v \ + | spu | strongarm \ + | tahoe | thumb | tic4x | tic80 | tron \ + | v850 | v850e \ + | we32k \ + | x86 | xc16x | xscale | xscalee[bl] | xstormy16 | xtensa \ + | z8k) + basic_machine=$basic_machine-unknown + ;; + m6811 | m68hc11 | m6812 | m68hc12) + # Motorola 68HC11/12. + basic_machine=$basic_machine-unknown + os=-none + ;; + m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | v70 | w65 | z8k) + ;; + ms1) + basic_machine=mt-unknown + ;; + + # We use `pc' rather than `unknown' + # because (1) that's what they normally are, and + # (2) the word "unknown" tends to confuse beginning users. + i*86 | x86_64) + basic_machine=$basic_machine-pc + ;; + # Object if more than one company name word. + *-*-*) + echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 + exit 1 + ;; + # Recognize the basic CPU types with company name. + 580-* \ + | a29k-* \ + | alpha-* | alphaev[4-8]-* | alphaev56-* | alphaev6[78]-* \ + | alpha64-* | alpha64ev[4-8]-* | alpha64ev56-* | alpha64ev6[78]-* \ + | alphapca5[67]-* | alpha64pca5[67]-* | arc-* \ + | arm-* | armbe-* | armle-* | armeb-* | armv*-* \ + | avr-* | avr32-* \ + | bfin-* | bs2000-* \ + | c[123]* | c30-* | [cjt]90-* | c4x-* | c54x-* | c55x-* | c6x-* \ + | clipper-* | craynv-* | cydra-* \ + | d10v-* | d30v-* | dlx-* \ + | elxsi-* \ + | f30[01]-* | f700-* | fido-* | fr30-* | frv-* | fx80-* \ + | h8300-* | h8500-* \ + | hppa-* | hppa1.[01]-* | hppa2.0-* | hppa2.0[nw]-* | hppa64-* \ + | i*86-* | i860-* | i960-* | ia64-* \ + | ip2k-* | iq2000-* \ + | m32c-* | m32r-* | m32rle-* \ + | m68000-* | m680[012346]0-* | m68360-* | m683?2-* | m68k-* \ + | m88110-* | m88k-* | maxq-* | mcore-* \ + | mips-* | mipsbe-* | mipseb-* | mipsel-* | mipsle-* \ + | mips16-* \ + | mips64-* | mips64el-* \ + | mips64vr-* | mips64vrel-* \ + | mips64orion-* | mips64orionel-* \ + | mips64vr4100-* | mips64vr4100el-* \ + | mips64vr4300-* | mips64vr4300el-* \ + | mips64vr5000-* | mips64vr5000el-* \ + | mips64vr5900-* | mips64vr5900el-* \ + | mipsisa32-* | mipsisa32el-* \ + | mipsisa32r2-* | mipsisa32r2el-* \ + | mipsisa64-* | mipsisa64el-* \ + | mipsisa64r2-* | mipsisa64r2el-* \ + | mipsisa64sb1-* | mipsisa64sb1el-* \ + | mipsisa64sr71k-* | mipsisa64sr71kel-* \ + | mipstx39-* | mipstx39el-* \ + | mmix-* \ + | mt-* \ + | msp430-* \ + | nios-* | nios2-* \ + | none-* | np1-* | ns16k-* | ns32k-* \ + | orion-* \ + | pdp10-* | pdp11-* | pj-* | pjl-* | pn-* | power-* \ + | powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* | ppcbe-* \ + | pyramid-* \ + | romp-* | rs6000-* \ + | sh-* | sh[1234]-* | sh[24]a-* | sh[23]e-* | sh[34]eb-* | sheb-* | shbe-* \ + | shle-* | sh[1234]le-* | sh3ele-* | sh64-* | sh64le-* \ + | sparc-* | sparc64-* | sparc64b-* | sparc64v-* | sparc86x-* | sparclet-* \ + | sparclite-* \ + | sparcv8-* | sparcv9-* | sparcv9b-* | sparcv9v-* | strongarm-* | sv1-* | sx?-* \ + | tahoe-* | thumb-* \ + | tic30-* | tic4x-* | tic54x-* | tic55x-* | tic6x-* | tic80-* \ + | tron-* \ + | v850-* | v850e-* | vax-* \ + | we32k-* \ + | x86-* | x86_64-* | xc16x-* | xps100-* | xscale-* | xscalee[bl]-* \ + | xstormy16-* | xtensa*-* \ + | ymp-* \ + | z8k-*) + ;; + # Recognize the basic CPU types without company name, with glob match. + xtensa*) + basic_machine=$basic_machine-unknown + ;; + # Recognize the various machine names and aliases which stand + # for a CPU type and a company and sometimes even an OS. + 386bsd) + basic_machine=i386-unknown + os=-bsd + ;; + 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc) + basic_machine=m68000-att + ;; + 3b*) + basic_machine=we32k-att + ;; + a29khif) + basic_machine=a29k-amd + os=-udi + ;; + abacus) + basic_machine=abacus-unknown + ;; + adobe68k) + basic_machine=m68010-adobe + os=-scout + ;; + alliant | fx80) + basic_machine=fx80-alliant + ;; + altos | altos3068) + basic_machine=m68k-altos + ;; + am29k) + basic_machine=a29k-none + os=-bsd + ;; + amd64) + basic_machine=x86_64-pc + ;; + amd64-*) + basic_machine=x86_64-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + amdahl) + basic_machine=580-amdahl + os=-sysv + ;; + amiga | amiga-*) + basic_machine=m68k-unknown + ;; + amigaos | amigados) + basic_machine=m68k-unknown + os=-amigaos + ;; + amigaunix | amix) + basic_machine=m68k-unknown + os=-sysv4 + ;; + apollo68) + basic_machine=m68k-apollo + os=-sysv + ;; + apollo68bsd) + basic_machine=m68k-apollo + os=-bsd + ;; + aux) + basic_machine=m68k-apple + os=-aux + ;; + balance) + basic_machine=ns32k-sequent + os=-dynix + ;; + blackfin) + basic_machine=bfin-unknown + os=-linux + ;; + blackfin-*) + basic_machine=bfin-`echo $basic_machine | sed 's/^[^-]*-//'` + os=-linux + ;; + c90) + basic_machine=c90-cray + os=-unicos + ;; + convex-c1) + basic_machine=c1-convex + os=-bsd + ;; + convex-c2) + basic_machine=c2-convex + os=-bsd + ;; + convex-c32) + basic_machine=c32-convex + os=-bsd + ;; + convex-c34) + basic_machine=c34-convex + os=-bsd + ;; + convex-c38) + basic_machine=c38-convex + os=-bsd + ;; + cray | j90) + basic_machine=j90-cray + os=-unicos + ;; + craynv) + basic_machine=craynv-cray + os=-unicosmp + ;; + cr16) + basic_machine=cr16-unknown + os=-elf + ;; + crds | unos) + basic_machine=m68k-crds + ;; + crisv32 | crisv32-* | etraxfs*) + basic_machine=crisv32-axis + ;; + cris | cris-* | etrax*) + basic_machine=cris-axis + ;; + crx) + basic_machine=crx-unknown + os=-elf + ;; + da30 | da30-*) + basic_machine=m68k-da30 + ;; + decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn) + basic_machine=mips-dec + ;; + decsystem10* | dec10*) + basic_machine=pdp10-dec + os=-tops10 + ;; + decsystem20* | dec20*) + basic_machine=pdp10-dec + os=-tops20 + ;; + delta | 3300 | motorola-3300 | motorola-delta \ + | 3300-motorola | delta-motorola) + basic_machine=m68k-motorola + ;; + delta88) + basic_machine=m88k-motorola + os=-sysv3 + ;; + djgpp) + basic_machine=i586-pc + os=-msdosdjgpp + ;; + dpx20 | dpx20-*) + basic_machine=rs6000-bull + os=-bosx + ;; + dpx2* | dpx2*-bull) + basic_machine=m68k-bull + os=-sysv3 + ;; + ebmon29k) + basic_machine=a29k-amd + os=-ebmon + ;; + elxsi) + basic_machine=elxsi-elxsi + os=-bsd + ;; + encore | umax | mmax) + basic_machine=ns32k-encore + ;; + es1800 | OSE68k | ose68k | ose | OSE) + basic_machine=m68k-ericsson + os=-ose + ;; + fx2800) + basic_machine=i860-alliant + ;; + genix) + basic_machine=ns32k-ns + ;; + gmicro) + basic_machine=tron-gmicro + os=-sysv + ;; + go32) + basic_machine=i386-pc + os=-go32 + ;; + h3050r* | hiux*) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + h8300hms) + basic_machine=h8300-hitachi + os=-hms + ;; + h8300xray) + basic_machine=h8300-hitachi + os=-xray + ;; + h8500hms) + basic_machine=h8500-hitachi + os=-hms + ;; + harris) + basic_machine=m88k-harris + os=-sysv3 + ;; + hp300-*) + basic_machine=m68k-hp + ;; + hp300bsd) + basic_machine=m68k-hp + os=-bsd + ;; + hp300hpux) + basic_machine=m68k-hp + os=-hpux + ;; + hp3k9[0-9][0-9] | hp9[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k2[0-9][0-9] | hp9k31[0-9]) + basic_machine=m68000-hp + ;; + hp9k3[2-9][0-9]) + basic_machine=m68k-hp + ;; + hp9k6[0-9][0-9] | hp6[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k7[0-79][0-9] | hp7[0-79][0-9]) + basic_machine=hppa1.1-hp + ;; + hp9k78[0-9] | hp78[0-9]) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][13679] | hp8[0-9][13679]) + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][0-9] | hp8[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hppa-next) + os=-nextstep3 + ;; + hppaosf) + basic_machine=hppa1.1-hp + os=-osf + ;; + hppro) + basic_machine=hppa1.1-hp + os=-proelf + ;; + i370-ibm* | ibm*) + basic_machine=i370-ibm + ;; +# I'm not sure what "Sysv32" means. Should this be sysv3.2? + i*86v32) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv32 + ;; + i*86v4*) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv4 + ;; + i*86v) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv + ;; + i*86sol2) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-solaris2 + ;; + i386mach) + basic_machine=i386-mach + os=-mach + ;; + i386-vsta | vsta) + basic_machine=i386-unknown + os=-vsta + ;; + iris | iris4d) + basic_machine=mips-sgi + case $os in + -irix*) + ;; + *) + os=-irix4 + ;; + esac + ;; + isi68 | isi) + basic_machine=m68k-isi + os=-sysv + ;; + m68knommu) + basic_machine=m68k-unknown + os=-linux + ;; + m68knommu-*) + basic_machine=m68k-`echo $basic_machine | sed 's/^[^-]*-//'` + os=-linux + ;; + m88k-omron*) + basic_machine=m88k-omron + ;; + magnum | m3230) + basic_machine=mips-mips + os=-sysv + ;; + merlin) + basic_machine=ns32k-utek + os=-sysv + ;; + mingw32) + basic_machine=i386-pc + os=-mingw32 + ;; + mingw32ce) + basic_machine=arm-unknown + os=-mingw32ce + ;; + miniframe) + basic_machine=m68000-convergent + ;; + *mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; + mips3*-*) + basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'` + ;; + mips3*) + basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`-unknown + ;; + monitor) + basic_machine=m68k-rom68k + os=-coff + ;; + morphos) + basic_machine=powerpc-unknown + os=-morphos + ;; + msdos) + basic_machine=i386-pc + os=-msdos + ;; + ms1-*) + basic_machine=`echo $basic_machine | sed -e 's/ms1-/mt-/'` + ;; + mvs) + basic_machine=i370-ibm + os=-mvs + ;; + ncr3000) + basic_machine=i486-ncr + os=-sysv4 + ;; + netbsd386) + basic_machine=i386-unknown + os=-netbsd + ;; + netwinder) + basic_machine=armv4l-rebel + os=-linux + ;; + news | news700 | news800 | news900) + basic_machine=m68k-sony + os=-newsos + ;; + news1000) + basic_machine=m68030-sony + os=-newsos + ;; + news-3600 | risc-news) + basic_machine=mips-sony + os=-newsos + ;; + necv70) + basic_machine=v70-nec + os=-sysv + ;; + next | m*-next ) + basic_machine=m68k-next + case $os in + -nextstep* ) + ;; + -ns2*) + os=-nextstep2 + ;; + *) + os=-nextstep3 + ;; + esac + ;; + nh3000) + basic_machine=m68k-harris + os=-cxux + ;; + nh[45]000) + basic_machine=m88k-harris + os=-cxux + ;; + nindy960) + basic_machine=i960-intel + os=-nindy + ;; + mon960) + basic_machine=i960-intel + os=-mon960 + ;; + nonstopux) + basic_machine=mips-compaq + os=-nonstopux + ;; + np1) + basic_machine=np1-gould + ;; + nsr-tandem) + basic_machine=nsr-tandem + ;; + op50n-* | op60c-*) + basic_machine=hppa1.1-oki + os=-proelf + ;; + openrisc | openrisc-*) + basic_machine=or32-unknown + ;; + os400) + basic_machine=powerpc-ibm + os=-os400 + ;; + OSE68000 | ose68000) + basic_machine=m68000-ericsson + os=-ose + ;; + os68k) + basic_machine=m68k-none + os=-os68k + ;; + pa-hitachi) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + paragon) + basic_machine=i860-intel + os=-osf + ;; + parisc) + basic_machine=hppa-unknown + os=-linux + ;; + parisc-*) + basic_machine=hppa-`echo $basic_machine | sed 's/^[^-]*-//'` + os=-linux + ;; + pbd) + basic_machine=sparc-tti + ;; + pbb) + basic_machine=m68k-tti + ;; + pc532 | pc532-*) + basic_machine=ns32k-pc532 + ;; + pc98) + basic_machine=i386-pc + ;; + pc98-*) + basic_machine=i386-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentium | p5 | k5 | k6 | nexgen | viac3) + basic_machine=i586-pc + ;; + pentiumpro | p6 | 6x86 | athlon | athlon_*) + basic_machine=i686-pc + ;; + pentiumii | pentium2 | pentiumiii | pentium3) + basic_machine=i686-pc + ;; + pentium4) + basic_machine=i786-pc + ;; + pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*) + basic_machine=i586-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentiumpro-* | p6-* | 6x86-* | athlon-*) + basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*) + basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentium4-*) + basic_machine=i786-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pn) + basic_machine=pn-gould + ;; + power) basic_machine=power-ibm + ;; + ppc) basic_machine=powerpc-unknown + ;; + ppc-*) basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppcle | powerpclittle | ppc-le | powerpc-little) + basic_machine=powerpcle-unknown + ;; + ppcle-* | powerpclittle-*) + basic_machine=powerpcle-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppc64) basic_machine=powerpc64-unknown + ;; + ppc64-*) basic_machine=powerpc64-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppc64le | powerpc64little | ppc64-le | powerpc64-little) + basic_machine=powerpc64le-unknown + ;; + ppc64le-* | powerpc64little-*) + basic_machine=powerpc64le-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ps2) + basic_machine=i386-ibm + ;; + pw32) + basic_machine=i586-unknown + os=-pw32 + ;; + rdos) + basic_machine=i386-pc + os=-rdos + ;; + rom68k) + basic_machine=m68k-rom68k + os=-coff + ;; + rm[46]00) + basic_machine=mips-siemens + ;; + rtpc | rtpc-*) + basic_machine=romp-ibm + ;; + s390 | s390-*) + basic_machine=s390-ibm + ;; + s390x | s390x-*) + basic_machine=s390x-ibm + ;; + sa29200) + basic_machine=a29k-amd + os=-udi + ;; + sb1) + basic_machine=mipsisa64sb1-unknown + ;; + sb1el) + basic_machine=mipsisa64sb1el-unknown + ;; + sde) + basic_machine=mipsisa32-sde + os=-elf + ;; + sei) + basic_machine=mips-sei + os=-seiux + ;; + sequent) + basic_machine=i386-sequent + ;; + sh) + basic_machine=sh-hitachi + os=-hms + ;; + sh5el) + basic_machine=sh5le-unknown + ;; + sh64) + basic_machine=sh64-unknown + ;; + sparclite-wrs | simso-wrs) + basic_machine=sparclite-wrs + os=-vxworks + ;; + sps7) + basic_machine=m68k-bull + os=-sysv2 + ;; + spur) + basic_machine=spur-unknown + ;; + st2000) + basic_machine=m68k-tandem + ;; + stratus) + basic_machine=i860-stratus + os=-sysv4 + ;; + sun2) + basic_machine=m68000-sun + ;; + sun2os3) + basic_machine=m68000-sun + os=-sunos3 + ;; + sun2os4) + basic_machine=m68000-sun + os=-sunos4 + ;; + sun3os3) + basic_machine=m68k-sun + os=-sunos3 + ;; + sun3os4) + basic_machine=m68k-sun + os=-sunos4 + ;; + sun4os3) + basic_machine=sparc-sun + os=-sunos3 + ;; + sun4os4) + basic_machine=sparc-sun + os=-sunos4 + ;; + sun4sol2) + basic_machine=sparc-sun + os=-solaris2 + ;; + sun3 | sun3-*) + basic_machine=m68k-sun + ;; + sun4) + basic_machine=sparc-sun + ;; + sun386 | sun386i | roadrunner) + basic_machine=i386-sun + ;; + sv1) + basic_machine=sv1-cray + os=-unicos + ;; + symmetry) + basic_machine=i386-sequent + os=-dynix + ;; + t3e) + basic_machine=alphaev5-cray + os=-unicos + ;; + t90) + basic_machine=t90-cray + os=-unicos + ;; + tic54x | c54x*) + basic_machine=tic54x-unknown + os=-coff + ;; + tic55x | c55x*) + basic_machine=tic55x-unknown + os=-coff + ;; + tic6x | c6x*) + basic_machine=tic6x-unknown + os=-coff + ;; + tile*) + basic_machine=tile-unknown + os=-linux-gnu + ;; + tx39) + basic_machine=mipstx39-unknown + ;; + tx39el) + basic_machine=mipstx39el-unknown + ;; + toad1) + basic_machine=pdp10-xkl + os=-tops20 + ;; + tower | tower-32) + basic_machine=m68k-ncr + ;; + tpf) + basic_machine=s390x-ibm + os=-tpf + ;; + udi29k) + basic_machine=a29k-amd + os=-udi + ;; + ultra3) + basic_machine=a29k-nyu + os=-sym1 + ;; + v810 | necv810) + basic_machine=v810-nec + os=-none + ;; + vaxv) + basic_machine=vax-dec + os=-sysv + ;; + vms) + basic_machine=vax-dec + os=-vms + ;; + vpp*|vx|vx-*) + basic_machine=f301-fujitsu + ;; + vxworks960) + basic_machine=i960-wrs + os=-vxworks + ;; + vxworks68) + basic_machine=m68k-wrs + os=-vxworks + ;; + vxworks29k) + basic_machine=a29k-wrs + os=-vxworks + ;; + w65*) + basic_machine=w65-wdc + os=-none + ;; + w89k-*) + basic_machine=hppa1.1-winbond + os=-proelf + ;; + xbox) + basic_machine=i686-pc + os=-mingw32 + ;; + xps | xps100) + basic_machine=xps100-honeywell + ;; + ymp) + basic_machine=ymp-cray + os=-unicos + ;; + z8k-*-coff) + basic_machine=z8k-unknown + os=-sim + ;; + none) + basic_machine=none-none + os=-none + ;; + +# Here we handle the default manufacturer of certain CPU types. It is in +# some cases the only manufacturer, in others, it is the most popular. + w89k) + basic_machine=hppa1.1-winbond + ;; + op50n) + basic_machine=hppa1.1-oki + ;; + op60c) + basic_machine=hppa1.1-oki + ;; + romp) + basic_machine=romp-ibm + ;; + mmix) + basic_machine=mmix-knuth + ;; + rs6000) + basic_machine=rs6000-ibm + ;; + vax) + basic_machine=vax-dec + ;; + pdp10) + # there are many clones, so DEC is not a safe bet + basic_machine=pdp10-unknown + ;; + pdp11) + basic_machine=pdp11-dec + ;; + we32k) + basic_machine=we32k-att + ;; + sh[1234] | sh[24]a | sh[34]eb | sh[1234]le | sh[23]ele) + basic_machine=sh-unknown + ;; + sparc | sparcv8 | sparcv9 | sparcv9b | sparcv9v) + basic_machine=sparc-sun + ;; + cydra) + basic_machine=cydra-cydrome + ;; + orion) + basic_machine=orion-highlevel + ;; + orion105) + basic_machine=clipper-highlevel + ;; + mac | mpw | mac-mpw) + basic_machine=m68k-apple + ;; + pmac | pmac-mpw) + basic_machine=powerpc-apple + ;; + *-unknown) + # Make sure to match an already-canonicalized machine name. + ;; + *) + echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 + exit 1 + ;; +esac + +# Here we canonicalize certain aliases for manufacturers. +case $basic_machine in + *-digital*) + basic_machine=`echo $basic_machine | sed 's/digital.*/dec/'` + ;; + *-commodore*) + basic_machine=`echo $basic_machine | sed 's/commodore.*/cbm/'` + ;; + *) + ;; +esac + +# Decode manufacturer-specific aliases for certain operating systems. + +if [ x"$os" != x"" ] +then +case $os in + # First match some system type aliases + # that might get confused with valid system types. + # -solaris* is a basic system type, with this one exception. + -solaris1 | -solaris1.*) + os=`echo $os | sed -e 's|solaris1|sunos4|'` + ;; + -solaris) + os=-solaris2 + ;; + -svr4*) + os=-sysv4 + ;; + -unixware*) + os=-sysv4.2uw + ;; + -gnu/linux*) + os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'` + ;; + # First accept the basic system types. + # The portable systems comes first. + # Each alternative MUST END IN A *, to match a version number. + # -sysv* is not here because it comes later, after sysvr4. + -gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \ + | -*vms* | -sco* | -esix* | -isc* | -aix* | -sunos | -sunos[34]*\ + | -hpux* | -unos* | -osf* | -luna* | -dgux* | -solaris* | -sym* \ + | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \ + | -aos* \ + | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \ + | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \ + | -hiux* | -386bsd* | -knetbsd* | -mirbsd* | -netbsd* \ + | -openbsd* | -solidbsd* \ + | -ekkobsd* | -kfreebsd* | -freebsd* | -riscix* | -lynxos* \ + | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \ + | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \ + | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \ + | -chorusos* | -chorusrdb* \ + | -cygwin* | -pe* | -psos* | -moss* | -proelf* | -rtems* \ + | -mingw32* | -linux-gnu* | -linux-newlib* | -linux-uclibc* \ + | -uxpv* | -beos* | -mpeix* | -udk* \ + | -interix* | -uwin* | -mks* | -rhapsody* | -darwin* | -opened* \ + | -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \ + | -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* \ + | -os2* | -vos* | -palmos* | -uclinux* | -nucleus* \ + | -morphos* | -superux* | -rtmk* | -rtmk-nova* | -windiss* \ + | -powermax* | -dnix* | -nx6 | -nx7 | -sei* | -dragonfly* \ + | -skyos* | -haiku* | -rdos* | -toppers* | -drops*) + # Remember, each alternative MUST END IN *, to match a version number. + ;; + -qnx*) + case $basic_machine in + x86-* | i*86-*) + ;; + *) + os=-nto$os + ;; + esac + ;; + -nto-qnx*) + ;; + -nto*) + os=`echo $os | sed -e 's|nto|nto-qnx|'` + ;; + -sim | -es1800* | -hms* | -xray | -os68k* | -none* | -v88r* \ + | -windows* | -osx | -abug | -netware* | -os9* | -beos* | -haiku* \ + | -macos* | -mpw* | -magic* | -mmixware* | -mon960* | -lnews*) + ;; + -mac*) + os=`echo $os | sed -e 's|mac|macos|'` + ;; + -linux-dietlibc) + os=-linux-dietlibc + ;; + -linux*) + os=`echo $os | sed -e 's|linux|linux-gnu|'` + ;; + -sunos5*) + os=`echo $os | sed -e 's|sunos5|solaris2|'` + ;; + -sunos6*) + os=`echo $os | sed -e 's|sunos6|solaris3|'` + ;; + -opened*) + os=-openedition + ;; + -os400*) + os=-os400 + ;; + -wince*) + os=-wince + ;; + -osfrose*) + os=-osfrose + ;; + -osf*) + os=-osf + ;; + -utek*) + os=-bsd + ;; + -dynix*) + os=-bsd + ;; + -acis*) + os=-aos + ;; + -atheos*) + os=-atheos + ;; + -syllable*) + os=-syllable + ;; + -386bsd) + os=-bsd + ;; + -ctix* | -uts*) + os=-sysv + ;; + -nova*) + os=-rtmk-nova + ;; + -ns2 ) + os=-nextstep2 + ;; + -nsk*) + os=-nsk + ;; + # Preserve the version number of sinix5. + -sinix5.*) + os=`echo $os | sed -e 's|sinix|sysv|'` + ;; + -sinix*) + os=-sysv4 + ;; + -tpf*) + os=-tpf + ;; + -triton*) + os=-sysv3 + ;; + -oss*) + os=-sysv3 + ;; + -svr4) + os=-sysv4 + ;; + -svr3) + os=-sysv3 + ;; + -sysvr4) + os=-sysv4 + ;; + # This must come after -sysvr4. + -sysv*) + ;; + -ose*) + os=-ose + ;; + -es1800*) + os=-ose + ;; + -xenix) + os=-xenix + ;; + -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) + os=-mint + ;; + -aros*) + os=-aros + ;; + -kaos*) + os=-kaos + ;; + -zvmoe) + os=-zvmoe + ;; + -none) + ;; + *) + # Get rid of the `-' at the beginning of $os. + os=`echo $os | sed 's/[^-]*-//'` + echo Invalid configuration \`$1\': system \`$os\' not recognized 1>&2 + exit 1 + ;; +esac +else + +# Here we handle the default operating systems that come with various machines. +# The value should be what the vendor currently ships out the door with their +# machine or put another way, the most popular os provided with the machine. + +# Note that if you're going to try to match "-MANUFACTURER" here (say, +# "-sun"), then you have to tell the case statement up towards the top +# that MANUFACTURER isn't an operating system. Otherwise, code above +# will signal an error saying that MANUFACTURER isn't an operating +# system, and we'll never get to this point. + +case $basic_machine in + score-*) + os=-elf + ;; + spu-*) + os=-elf + ;; + *-acorn) + os=-riscix1.2 + ;; + arm*-rebel) + os=-linux + ;; + arm*-semi) + os=-aout + ;; + c4x-* | tic4x-*) + os=-coff + ;; + # This must come before the *-dec entry. + pdp10-*) + os=-tops20 + ;; + pdp11-*) + os=-none + ;; + *-dec | vax-*) + os=-ultrix4.2 + ;; + m68*-apollo) + os=-domain + ;; + i386-sun) + os=-sunos4.0.2 + ;; + m68000-sun) + os=-sunos3 + # This also exists in the configure program, but was not the + # default. + # os=-sunos4 + ;; + m68*-cisco) + os=-aout + ;; + mep-*) + os=-elf + ;; + mips*-cisco) + os=-elf + ;; + mips*-*) + os=-elf + ;; + or32-*) + os=-coff + ;; + *-tti) # must be before sparc entry or we get the wrong os. + os=-sysv3 + ;; + sparc-* | *-sun) + os=-sunos4.1.1 + ;; + *-be) + os=-beos + ;; + *-haiku) + os=-haiku + ;; + *-ibm) + os=-aix + ;; + *-knuth) + os=-mmixware + ;; + *-wec) + os=-proelf + ;; + *-winbond) + os=-proelf + ;; + *-oki) + os=-proelf + ;; + *-hp) + os=-hpux + ;; + *-hitachi) + os=-hiux + ;; + i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent) + os=-sysv + ;; + *-cbm) + os=-amigaos + ;; + *-dg) + os=-dgux + ;; + *-dolphin) + os=-sysv3 + ;; + m68k-ccur) + os=-rtu + ;; + m88k-omron*) + os=-luna + ;; + *-next ) + os=-nextstep + ;; + *-sequent) + os=-ptx + ;; + *-crds) + os=-unos + ;; + *-ns) + os=-genix + ;; + i370-*) + os=-mvs + ;; + *-next) + os=-nextstep3 + ;; + *-gould) + os=-sysv + ;; + *-highlevel) + os=-bsd + ;; + *-encore) + os=-bsd + ;; + *-sgi) + os=-irix + ;; + *-siemens) + os=-sysv4 + ;; + *-masscomp) + os=-rtu + ;; + f30[01]-fujitsu | f700-fujitsu) + os=-uxpv + ;; + *-rom68k) + os=-coff + ;; + *-*bug) + os=-coff + ;; + *-apple) + os=-macos + ;; + *-atari*) + os=-mint + ;; + *) + os=-none + ;; +esac +fi + +# Here we handle the case where we know the os, and the CPU type, but not the +# manufacturer. We pick the logical manufacturer. +vendor=unknown +case $basic_machine in + *-unknown) + case $os in + -riscix*) + vendor=acorn + ;; + -sunos*) + vendor=sun + ;; + -aix*) + vendor=ibm + ;; + -beos*) + vendor=be + ;; + -hpux*) + vendor=hp + ;; + -mpeix*) + vendor=hp + ;; + -hiux*) + vendor=hitachi + ;; + -unos*) + vendor=crds + ;; + -dgux*) + vendor=dg + ;; + -luna*) + vendor=omron + ;; + -genix*) + vendor=ns + ;; + -mvs* | -opened*) + vendor=ibm + ;; + -os400*) + vendor=ibm + ;; + -ptx*) + vendor=sequent + ;; + -tpf*) + vendor=ibm + ;; + -vxsim* | -vxworks* | -windiss*) + vendor=wrs + ;; + -aux*) + vendor=apple + ;; + -hms*) + vendor=hitachi + ;; + -mpw* | -macos*) + vendor=apple + ;; + -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) + vendor=atari + ;; + -vos*) + vendor=stratus + ;; + esac + basic_machine=`echo $basic_machine | sed "s/unknown/$vendor/"` + ;; +esac + +echo $basic_machine$os +exit + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/third_party/libevent/configure b/third_party/libevent/configure new file mode 100755 index 0000000000..c98d1ff7e1 --- /dev/null +++ b/third_party/libevent/configure @@ -0,0 +1,26147 @@ +#! /bin/sh +# Guess values for system-dependent variables and create Makefiles. +# Generated by GNU Autoconf 2.63. +# +# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, +# 2002, 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc. +# This configure script is free software; the Free Software Foundation +# gives unlimited permission to copy, distribute and modify it. +## --------------------- ## +## M4sh Initialization. ## +## --------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in + *posix*) set -o posix ;; +esac + +fi + + + + +# PATH needs CR +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + +as_nl=' +' +export as_nl +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +if (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi + +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + +# Support unset when possible. +if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then + as_unset=unset +else + as_unset=false +fi + + +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + +# Find who we are. Look in the path if we contain no directory separator. +case $0 in + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break +done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as `sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + { (exit 1); exit 1; } +fi + +# Work around bugs in pre-3.0 UWIN ksh. +for as_var in ENV MAIL MAILPATH +do ($as_unset $as_var) >/dev/null 2>&1 && $as_unset $as_var +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# Required to use basename. +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + + +# Name of the executable. +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# CDPATH. +$as_unset CDPATH + + +if test "x$CONFIG_SHELL" = x; then + if (eval ":") 2>/dev/null; then + as_have_required=yes +else + as_have_required=no +fi + + if test $as_have_required = yes && (eval ": +(as_func_return () { + (exit \$1) +} +as_func_success () { + as_func_return 0 +} +as_func_failure () { + as_func_return 1 +} +as_func_ret_success () { + return 0 +} +as_func_ret_failure () { + return 1 +} + +exitcode=0 +if as_func_success; then + : +else + exitcode=1 + echo as_func_success failed. +fi + +if as_func_failure; then + exitcode=1 + echo as_func_failure succeeded. +fi + +if as_func_ret_success; then + : +else + exitcode=1 + echo as_func_ret_success failed. +fi + +if as_func_ret_failure; then + exitcode=1 + echo as_func_ret_failure succeeded. +fi + +if ( set x; as_func_ret_success y && test x = \"\$1\" ); then + : +else + exitcode=1 + echo positional parameters were not saved. +fi + +test \$exitcode = 0) || { (exit 1); exit 1; } + +( + as_lineno_1=\$LINENO + as_lineno_2=\$LINENO + test \"x\$as_lineno_1\" != \"x\$as_lineno_2\" && + test \"x\`expr \$as_lineno_1 + 1\`\" = \"x\$as_lineno_2\") || { (exit 1); exit 1; } +") 2> /dev/null; then + : +else + as_candidate_shells= + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + case $as_dir in + /*) + for as_base in sh bash ksh sh5; do + as_candidate_shells="$as_candidate_shells $as_dir/$as_base" + done;; + esac +done +IFS=$as_save_IFS + + + for as_shell in $as_candidate_shells $SHELL; do + # Try only shells that exist, to save several forks. + if { test -f "$as_shell" || test -f "$as_shell.exe"; } && + { ("$as_shell") 2> /dev/null <<\_ASEOF +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in + *posix*) set -o posix ;; +esac + +fi + + +: +_ASEOF +}; then + CONFIG_SHELL=$as_shell + as_have_required=yes + if { "$as_shell" 2> /dev/null <<\_ASEOF +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in + *posix*) set -o posix ;; +esac + +fi + + +: +(as_func_return () { + (exit $1) +} +as_func_success () { + as_func_return 0 +} +as_func_failure () { + as_func_return 1 +} +as_func_ret_success () { + return 0 +} +as_func_ret_failure () { + return 1 +} + +exitcode=0 +if as_func_success; then + : +else + exitcode=1 + echo as_func_success failed. +fi + +if as_func_failure; then + exitcode=1 + echo as_func_failure succeeded. +fi + +if as_func_ret_success; then + : +else + exitcode=1 + echo as_func_ret_success failed. +fi + +if as_func_ret_failure; then + exitcode=1 + echo as_func_ret_failure succeeded. +fi + +if ( set x; as_func_ret_success y && test x = "$1" ); then + : +else + exitcode=1 + echo positional parameters were not saved. +fi + +test $exitcode = 0) || { (exit 1); exit 1; } + +( + as_lineno_1=$LINENO + as_lineno_2=$LINENO + test "x$as_lineno_1" != "x$as_lineno_2" && + test "x`expr $as_lineno_1 + 1`" = "x$as_lineno_2") || { (exit 1); exit 1; } + +_ASEOF +}; then + break +fi + +fi + + done + + if test "x$CONFIG_SHELL" != x; then + for as_var in BASH_ENV ENV + do ($as_unset $as_var) >/dev/null 2>&1 && $as_unset $as_var + done + export CONFIG_SHELL + exec "$CONFIG_SHELL" "$as_myself" ${1+"$@"} +fi + + + if test $as_have_required = no; then + echo This script requires a shell more modern than all the + echo shells that I found on your system. Please install a + echo modern shell, or manually run the script under such a + echo shell if you do have one. + { (exit 1); exit 1; } +fi + + +fi + +fi + + + +(eval "as_func_return () { + (exit \$1) +} +as_func_success () { + as_func_return 0 +} +as_func_failure () { + as_func_return 1 +} +as_func_ret_success () { + return 0 +} +as_func_ret_failure () { + return 1 +} + +exitcode=0 +if as_func_success; then + : +else + exitcode=1 + echo as_func_success failed. +fi + +if as_func_failure; then + exitcode=1 + echo as_func_failure succeeded. +fi + +if as_func_ret_success; then + : +else + exitcode=1 + echo as_func_ret_success failed. +fi + +if as_func_ret_failure; then + exitcode=1 + echo as_func_ret_failure succeeded. +fi + +if ( set x; as_func_ret_success y && test x = \"\$1\" ); then + : +else + exitcode=1 + echo positional parameters were not saved. +fi + +test \$exitcode = 0") || { + echo No shell found that supports shell functions. + echo Please tell bug-autoconf@gnu.org about your system, + echo including any error possibly output before this message. + echo This can help us improve future autoconf versions. + echo Configuration will now proceed without shell functions. +} + + + + as_lineno_1=$LINENO + as_lineno_2=$LINENO + test "x$as_lineno_1" != "x$as_lineno_2" && + test "x`expr $as_lineno_1 + 1`" = "x$as_lineno_2" || { + + # Create $as_me.lineno as a copy of $as_myself, but with $LINENO + # uniformly replaced by the line number. The first 'sed' inserts a + # line-number line after each line using $LINENO; the second 'sed' + # does the real work. The second script uses 'N' to pair each + # line-number line with the line containing $LINENO, and appends + # trailing '-' during substitution so that $LINENO is not a special + # case at line end. + # (Raja R Harinath suggested sed '=', and Paul Eggert wrote the + # scripts with optimization help from Paolo Bonzini. Blame Lee + # E. McMahon (1931-1989) for sed's syntax. :-) + sed -n ' + p + /[$]LINENO/= + ' <$as_myself | + sed ' + s/[$]LINENO.*/&-/ + t lineno + b + :lineno + N + :loop + s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ + t loop + s/-\n.*// + ' >$as_me.lineno && + chmod +x "$as_me.lineno" || + { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2 + { (exit 1); exit 1; }; } + + # Don't try to exec as it changes $[0], causing all sort of problems + # (the dirname of $[0] is not the place where we might find the + # original and so on. Autoconf is especially sensitive to this). + . "./$as_me.lineno" + # Exit status is that of the last command. + exit +} + + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in +-n*) + case `echo 'x\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + *) ECHO_C='\c';; + esac;; +*) + ECHO_N='-n';; +esac +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -p'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -p' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -p' + fi +else + as_ln_s='cp -p' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + +if mkdir -p . 2>/dev/null; then + as_mkdir_p=: +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + +if test -x / >/dev/null 2>&1; then + as_test_x='test -x' +else + if ls -dL / >/dev/null 2>&1; then + as_ls_L_option=L + else + as_ls_L_option= + fi + as_test_x=' + eval sh -c '\'' + if test -d "$1"; then + test -d "$1/."; + else + case $1 in + -*)set "./$1";; + esac; + case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in + ???[sx]*):;;*)false;;esac;fi + '\'' sh + ' +fi +as_executable_p=$as_test_x + +# Sed expression to map a string onto a valid CPP name. +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" + +# Sed expression to map a string onto a valid variable name. +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" + + + + +# Check that we are running under the correct shell. +SHELL=${CONFIG_SHELL-/bin/sh} + +case X$ECHO in +X*--fallback-echo) + # Remove one level of quotation (which was required for Make). + ECHO=`echo "$ECHO" | sed 's,\\\\\$\\$0,'$0','` + ;; +esac + +echo=${ECHO-echo} +if test "X$1" = X--no-reexec; then + # Discard the --no-reexec flag, and continue. + shift +elif test "X$1" = X--fallback-echo; then + # Avoid inline document here, it may be left over + : +elif test "X`($echo '\t') 2>/dev/null`" = 'X\t' ; then + # Yippee, $echo works! + : +else + # Restart under the correct shell. + exec $SHELL "$0" --no-reexec ${1+"$@"} +fi + +if test "X$1" = X--fallback-echo; then + # used as fallback echo + shift + cat </dev/null 2>&1 && unset CDPATH + +if test -z "$ECHO"; then +if test "X${echo_test_string+set}" != Xset; then +# find a string as large as possible, as long as the shell can cope with it + for cmd in 'sed 50q "$0"' 'sed 20q "$0"' 'sed 10q "$0"' 'sed 2q "$0"' 'echo test'; do + # expected sizes: less than 2Kb, 1Kb, 512 bytes, 16 bytes, ... + if (echo_test_string=`eval $cmd`) 2>/dev/null && + echo_test_string=`eval $cmd` && + (test "X$echo_test_string" = "X$echo_test_string") 2>/dev/null + then + break + fi + done +fi + +if test "X`($echo '\t') 2>/dev/null`" = 'X\t' && + echo_testing_string=`($echo "$echo_test_string") 2>/dev/null` && + test "X$echo_testing_string" = "X$echo_test_string"; then + : +else + # The Solaris, AIX, and Digital Unix default echo programs unquote + # backslashes. This makes it impossible to quote backslashes using + # echo "$something" | sed 's/\\/\\\\/g' + # + # So, first we look for a working echo in the user's PATH. + + lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR + for dir in $PATH /usr/ucb; do + IFS="$lt_save_ifs" + if (test -f $dir/echo || test -f $dir/echo$ac_exeext) && + test "X`($dir/echo '\t') 2>/dev/null`" = 'X\t' && + echo_testing_string=`($dir/echo "$echo_test_string") 2>/dev/null` && + test "X$echo_testing_string" = "X$echo_test_string"; then + echo="$dir/echo" + break + fi + done + IFS="$lt_save_ifs" + + if test "X$echo" = Xecho; then + # We didn't find a better echo, so look for alternatives. + if test "X`(print -r '\t') 2>/dev/null`" = 'X\t' && + echo_testing_string=`(print -r "$echo_test_string") 2>/dev/null` && + test "X$echo_testing_string" = "X$echo_test_string"; then + # This shell has a builtin print -r that does the trick. + echo='print -r' + elif (test -f /bin/ksh || test -f /bin/ksh$ac_exeext) && + test "X$CONFIG_SHELL" != X/bin/ksh; then + # If we have ksh, try running configure again with it. + ORIGINAL_CONFIG_SHELL=${CONFIG_SHELL-/bin/sh} + export ORIGINAL_CONFIG_SHELL + CONFIG_SHELL=/bin/ksh + export CONFIG_SHELL + exec $CONFIG_SHELL "$0" --no-reexec ${1+"$@"} + else + # Try using printf. + echo='printf %s\n' + if test "X`($echo '\t') 2>/dev/null`" = 'X\t' && + echo_testing_string=`($echo "$echo_test_string") 2>/dev/null` && + test "X$echo_testing_string" = "X$echo_test_string"; then + # Cool, printf works + : + elif echo_testing_string=`($ORIGINAL_CONFIG_SHELL "$0" --fallback-echo '\t') 2>/dev/null` && + test "X$echo_testing_string" = 'X\t' && + echo_testing_string=`($ORIGINAL_CONFIG_SHELL "$0" --fallback-echo "$echo_test_string") 2>/dev/null` && + test "X$echo_testing_string" = "X$echo_test_string"; then + CONFIG_SHELL=$ORIGINAL_CONFIG_SHELL + export CONFIG_SHELL + SHELL="$CONFIG_SHELL" + export SHELL + echo="$CONFIG_SHELL $0 --fallback-echo" + elif echo_testing_string=`($CONFIG_SHELL "$0" --fallback-echo '\t') 2>/dev/null` && + test "X$echo_testing_string" = 'X\t' && + echo_testing_string=`($CONFIG_SHELL "$0" --fallback-echo "$echo_test_string") 2>/dev/null` && + test "X$echo_testing_string" = "X$echo_test_string"; then + echo="$CONFIG_SHELL $0 --fallback-echo" + else + # maybe with a smaller string... + prev=: + + for cmd in 'echo test' 'sed 2q "$0"' 'sed 10q "$0"' 'sed 20q "$0"' 'sed 50q "$0"'; do + if (test "X$echo_test_string" = "X`eval $cmd`") 2>/dev/null + then + break + fi + prev="$cmd" + done + + if test "$prev" != 'sed 50q "$0"'; then + echo_test_string=`eval $prev` + export echo_test_string + exec ${ORIGINAL_CONFIG_SHELL-${CONFIG_SHELL-/bin/sh}} "$0" ${1+"$@"} + else + # Oops. We lost completely, so just stick with echo. + echo=echo + fi + fi + fi + fi +fi +fi + +# Copy echo and quote the copy suitably for passing to libtool from +# the Makefile, instead of quoting the original, which is used later. +ECHO=$echo +if test "X$ECHO" = "X$CONFIG_SHELL $0 --fallback-echo"; then + ECHO="$CONFIG_SHELL \\\$\$0 --fallback-echo" +fi + + + + +tagnames=${tagnames+${tagnames},}CXX + +tagnames=${tagnames+${tagnames},}F77 + +exec 7<&0 &1 + +# Name of the host. +# hostname on some systems (SVR3.2, Linux) returns a bogus exit status, +# so uname gets run too. +ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` + +# +# Initializations. +# +ac_default_prefix=/usr/local +ac_clean_files= +ac_config_libobj_dir=. +LIBOBJS= +cross_compiling=no +subdirs= +MFLAGS= +MAKEFLAGS= +SHELL=${CONFIG_SHELL-/bin/sh} + +# Identity of this package. +PACKAGE_NAME= +PACKAGE_TARNAME= +PACKAGE_VERSION= +PACKAGE_STRING= +PACKAGE_BUGREPORT= + +ac_unique_file="event.c" +# Factoring default headers for most tests. +ac_includes_default="\ +#include +#ifdef HAVE_SYS_TYPES_H +# include +#endif +#ifdef HAVE_SYS_STAT_H +# include +#endif +#ifdef STDC_HEADERS +# include +# include +#else +# ifdef HAVE_STDLIB_H +# include +# endif +#endif +#ifdef HAVE_STRING_H +# if !defined STDC_HEADERS && defined HAVE_MEMORY_H +# include +# endif +# include +#endif +#ifdef HAVE_STRINGS_H +# include +#endif +#ifdef HAVE_INTTYPES_H +# include +#endif +#ifdef HAVE_STDINT_H +# include +#endif +#ifdef HAVE_UNISTD_H +# include +#endif" + +ac_subst_vars='LTLIBOBJS +LIBOBJS +BUILD_WIN32_FALSE +BUILD_WIN32_TRUE +LIBTOOL_DEPS +LIBTOOL +ac_ct_F77 +FFLAGS +F77 +CXXCPP +am__fastdepCXX_FALSE +am__fastdepCXX_TRUE +CXXDEPMODE +ac_ct_CXX +CXXFLAGS +CXX +NMEDIT +DSYMUTIL +RANLIB +AR +ECHO +SED +host_os +host_vendor +host_cpu +host +build_os +build_vendor +build_cpu +build +EGREP +GREP +CPP +LN_S +am__fastdepCC_FALSE +am__fastdepCC_TRUE +CCDEPMODE +AMDEPBACKSLASH +AMDEP_FALSE +AMDEP_TRUE +am__quote +am__include +DEPDIR +OBJEXT +EXEEXT +ac_ct_CC +CPPFLAGS +LDFLAGS +CFLAGS +CC +am__untar +am__tar +AMTAR +am__leading_dot +SET_MAKE +AWK +mkdir_p +MKDIR_P +INSTALL_STRIP_PROGRAM +STRIP +install_sh +MAKEINFO +AUTOHEADER +AUTOMAKE +AUTOCONF +ACLOCAL +VERSION +PACKAGE +CYGPATH_W +am__isrc +INSTALL_DATA +INSTALL_SCRIPT +INSTALL_PROGRAM +target_alias +host_alias +build_alias +LIBS +ECHO_T +ECHO_N +ECHO_C +DEFS +mandir +localedir +libdir +psdir +pdfdir +dvidir +htmldir +infodir +docdir +oldincludedir +includedir +localstatedir +sharedstatedir +sysconfdir +datadir +datarootdir +libexecdir +sbindir +bindir +program_transform_name +prefix +exec_prefix +PACKAGE_BUGREPORT +PACKAGE_STRING +PACKAGE_VERSION +PACKAGE_TARNAME +PACKAGE_NAME +PATH_SEPARATOR +SHELL' +ac_subst_files='' +ac_user_opts=' +enable_option_checking +enable_dependency_tracking +enable_gcc_warnings +enable_shared +enable_static +enable_fast_install +with_gnu_ld +enable_libtool_lock +with_pic +with_tags +' + ac_precious_vars='build_alias +host_alias +target_alias +CC +CFLAGS +LDFLAGS +LIBS +CPPFLAGS +CPP +CXX +CXXFLAGS +CCC +CXXCPP +F77 +FFLAGS' + + +# Initialize some variables set by options. +ac_init_help= +ac_init_version=false +ac_unrecognized_opts= +ac_unrecognized_sep= +# The variables have the same names as the options, with +# dashes changed to underlines. +cache_file=/dev/null +exec_prefix=NONE +no_create= +no_recursion= +prefix=NONE +program_prefix=NONE +program_suffix=NONE +program_transform_name=s,x,x, +silent= +site= +srcdir= +verbose= +x_includes=NONE +x_libraries=NONE + +# Installation directory options. +# These are left unexpanded so users can "make install exec_prefix=/foo" +# and all the variables that are supposed to be based on exec_prefix +# by default will actually change. +# Use braces instead of parens because sh, perl, etc. also accept them. +# (The list follows the same order as the GNU Coding Standards.) +bindir='${exec_prefix}/bin' +sbindir='${exec_prefix}/sbin' +libexecdir='${exec_prefix}/libexec' +datarootdir='${prefix}/share' +datadir='${datarootdir}' +sysconfdir='${prefix}/etc' +sharedstatedir='${prefix}/com' +localstatedir='${prefix}/var' +includedir='${prefix}/include' +oldincludedir='/usr/include' +docdir='${datarootdir}/doc/${PACKAGE}' +infodir='${datarootdir}/info' +htmldir='${docdir}' +dvidir='${docdir}' +pdfdir='${docdir}' +psdir='${docdir}' +libdir='${exec_prefix}/lib' +localedir='${datarootdir}/locale' +mandir='${datarootdir}/man' + +ac_prev= +ac_dashdash= +for ac_option +do + # If the previous option needs an argument, assign it. + if test -n "$ac_prev"; then + eval $ac_prev=\$ac_option + ac_prev= + continue + fi + + case $ac_option in + *=*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; + *) ac_optarg=yes ;; + esac + + # Accept the important Cygnus configure options, so we can diagnose typos. + + case $ac_dashdash$ac_option in + --) + ac_dashdash=yes ;; + + -bindir | --bindir | --bindi | --bind | --bin | --bi) + ac_prev=bindir ;; + -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) + bindir=$ac_optarg ;; + + -build | --build | --buil | --bui | --bu) + ac_prev=build_alias ;; + -build=* | --build=* | --buil=* | --bui=* | --bu=*) + build_alias=$ac_optarg ;; + + -cache-file | --cache-file | --cache-fil | --cache-fi \ + | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) + ac_prev=cache_file ;; + -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ + | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) + cache_file=$ac_optarg ;; + + --config-cache | -C) + cache_file=config.cache ;; + + -datadir | --datadir | --datadi | --datad) + ac_prev=datadir ;; + -datadir=* | --datadir=* | --datadi=* | --datad=*) + datadir=$ac_optarg ;; + + -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ + | --dataroo | --dataro | --datar) + ac_prev=datarootdir ;; + -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ + | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) + datarootdir=$ac_optarg ;; + + -disable-* | --disable-*) + ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + { $as_echo "$as_me: error: invalid feature name: $ac_useropt" >&2 + { (exit 1); exit 1; }; } + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=no ;; + + -docdir | --docdir | --docdi | --doc | --do) + ac_prev=docdir ;; + -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) + docdir=$ac_optarg ;; + + -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) + ac_prev=dvidir ;; + -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) + dvidir=$ac_optarg ;; + + -enable-* | --enable-*) + ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + { $as_echo "$as_me: error: invalid feature name: $ac_useropt" >&2 + { (exit 1); exit 1; }; } + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=\$ac_optarg ;; + + -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ + | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ + | --exec | --exe | --ex) + ac_prev=exec_prefix ;; + -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ + | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ + | --exec=* | --exe=* | --ex=*) + exec_prefix=$ac_optarg ;; + + -gas | --gas | --ga | --g) + # Obsolete; use --with-gas. + with_gas=yes ;; + + -help | --help | --hel | --he | -h) + ac_init_help=long ;; + -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) + ac_init_help=recursive ;; + -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) + ac_init_help=short ;; + + -host | --host | --hos | --ho) + ac_prev=host_alias ;; + -host=* | --host=* | --hos=* | --ho=*) + host_alias=$ac_optarg ;; + + -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) + ac_prev=htmldir ;; + -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ + | --ht=*) + htmldir=$ac_optarg ;; + + -includedir | --includedir | --includedi | --included | --include \ + | --includ | --inclu | --incl | --inc) + ac_prev=includedir ;; + -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ + | --includ=* | --inclu=* | --incl=* | --inc=*) + includedir=$ac_optarg ;; + + -infodir | --infodir | --infodi | --infod | --info | --inf) + ac_prev=infodir ;; + -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) + infodir=$ac_optarg ;; + + -libdir | --libdir | --libdi | --libd) + ac_prev=libdir ;; + -libdir=* | --libdir=* | --libdi=* | --libd=*) + libdir=$ac_optarg ;; + + -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ + | --libexe | --libex | --libe) + ac_prev=libexecdir ;; + -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ + | --libexe=* | --libex=* | --libe=*) + libexecdir=$ac_optarg ;; + + -localedir | --localedir | --localedi | --localed | --locale) + ac_prev=localedir ;; + -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) + localedir=$ac_optarg ;; + + -localstatedir | --localstatedir | --localstatedi | --localstated \ + | --localstate | --localstat | --localsta | --localst | --locals) + ac_prev=localstatedir ;; + -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ + | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) + localstatedir=$ac_optarg ;; + + -mandir | --mandir | --mandi | --mand | --man | --ma | --m) + ac_prev=mandir ;; + -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) + mandir=$ac_optarg ;; + + -nfp | --nfp | --nf) + # Obsolete; use --without-fp. + with_fp=no ;; + + -no-create | --no-create | --no-creat | --no-crea | --no-cre \ + | --no-cr | --no-c | -n) + no_create=yes ;; + + -no-recursion | --no-recursion | --no-recursio | --no-recursi \ + | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) + no_recursion=yes ;; + + -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ + | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ + | --oldin | --oldi | --old | --ol | --o) + ac_prev=oldincludedir ;; + -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ + | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ + | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) + oldincludedir=$ac_optarg ;; + + -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) + ac_prev=prefix ;; + -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) + prefix=$ac_optarg ;; + + -program-prefix | --program-prefix | --program-prefi | --program-pref \ + | --program-pre | --program-pr | --program-p) + ac_prev=program_prefix ;; + -program-prefix=* | --program-prefix=* | --program-prefi=* \ + | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) + program_prefix=$ac_optarg ;; + + -program-suffix | --program-suffix | --program-suffi | --program-suff \ + | --program-suf | --program-su | --program-s) + ac_prev=program_suffix ;; + -program-suffix=* | --program-suffix=* | --program-suffi=* \ + | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) + program_suffix=$ac_optarg ;; + + -program-transform-name | --program-transform-name \ + | --program-transform-nam | --program-transform-na \ + | --program-transform-n | --program-transform- \ + | --program-transform | --program-transfor \ + | --program-transfo | --program-transf \ + | --program-trans | --program-tran \ + | --progr-tra | --program-tr | --program-t) + ac_prev=program_transform_name ;; + -program-transform-name=* | --program-transform-name=* \ + | --program-transform-nam=* | --program-transform-na=* \ + | --program-transform-n=* | --program-transform-=* \ + | --program-transform=* | --program-transfor=* \ + | --program-transfo=* | --program-transf=* \ + | --program-trans=* | --program-tran=* \ + | --progr-tra=* | --program-tr=* | --program-t=*) + program_transform_name=$ac_optarg ;; + + -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) + ac_prev=pdfdir ;; + -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) + pdfdir=$ac_optarg ;; + + -psdir | --psdir | --psdi | --psd | --ps) + ac_prev=psdir ;; + -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) + psdir=$ac_optarg ;; + + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + silent=yes ;; + + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) + ac_prev=sbindir ;; + -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ + | --sbi=* | --sb=*) + sbindir=$ac_optarg ;; + + -sharedstatedir | --sharedstatedir | --sharedstatedi \ + | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ + | --sharedst | --shareds | --shared | --share | --shar \ + | --sha | --sh) + ac_prev=sharedstatedir ;; + -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ + | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ + | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ + | --sha=* | --sh=*) + sharedstatedir=$ac_optarg ;; + + -site | --site | --sit) + ac_prev=site ;; + -site=* | --site=* | --sit=*) + site=$ac_optarg ;; + + -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) + ac_prev=srcdir ;; + -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) + srcdir=$ac_optarg ;; + + -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ + | --syscon | --sysco | --sysc | --sys | --sy) + ac_prev=sysconfdir ;; + -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ + | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) + sysconfdir=$ac_optarg ;; + + -target | --target | --targe | --targ | --tar | --ta | --t) + ac_prev=target_alias ;; + -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) + target_alias=$ac_optarg ;; + + -v | -verbose | --verbose | --verbos | --verbo | --verb) + verbose=yes ;; + + -version | --version | --versio | --versi | --vers | -V) + ac_init_version=: ;; + + -with-* | --with-*) + ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + { $as_echo "$as_me: error: invalid package name: $ac_useropt" >&2 + { (exit 1); exit 1; }; } + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=\$ac_optarg ;; + + -without-* | --without-*) + ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + { $as_echo "$as_me: error: invalid package name: $ac_useropt" >&2 + { (exit 1); exit 1; }; } + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=no ;; + + --x) + # Obsolete; use --with-x. + with_x=yes ;; + + -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ + | --x-incl | --x-inc | --x-in | --x-i) + ac_prev=x_includes ;; + -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ + | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) + x_includes=$ac_optarg ;; + + -x-libraries | --x-libraries | --x-librarie | --x-librari \ + | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) + ac_prev=x_libraries ;; + -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ + | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) + x_libraries=$ac_optarg ;; + + -*) { $as_echo "$as_me: error: unrecognized option: $ac_option +Try \`$0 --help' for more information." >&2 + { (exit 1); exit 1; }; } + ;; + + *=*) + ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` + # Reject names that are not valid shell variable names. + expr "x$ac_envvar" : ".*[^_$as_cr_alnum]" >/dev/null && + { $as_echo "$as_me: error: invalid variable name: $ac_envvar" >&2 + { (exit 1); exit 1; }; } + eval $ac_envvar=\$ac_optarg + export $ac_envvar ;; + + *) + # FIXME: should be removed in autoconf 3.0. + $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2 + expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && + $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2 + : ${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option} + ;; + + esac +done + +if test -n "$ac_prev"; then + ac_option=--`echo $ac_prev | sed 's/_/-/g'` + { $as_echo "$as_me: error: missing argument to $ac_option" >&2 + { (exit 1); exit 1; }; } +fi + +if test -n "$ac_unrecognized_opts"; then + case $enable_option_checking in + no) ;; + fatal) { $as_echo "$as_me: error: unrecognized options: $ac_unrecognized_opts" >&2 + { (exit 1); exit 1; }; } ;; + *) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; + esac +fi + +# Check all directory arguments for consistency. +for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ + datadir sysconfdir sharedstatedir localstatedir includedir \ + oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ + libdir localedir mandir +do + eval ac_val=\$$ac_var + # Remove trailing slashes. + case $ac_val in + */ ) + ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` + eval $ac_var=\$ac_val;; + esac + # Be sure to have absolute directory names. + case $ac_val in + [\\/$]* | ?:[\\/]* ) continue;; + NONE | '' ) case $ac_var in *prefix ) continue;; esac;; + esac + { $as_echo "$as_me: error: expected an absolute directory name for --$ac_var: $ac_val" >&2 + { (exit 1); exit 1; }; } +done + +# There might be people who depend on the old broken behavior: `$host' +# used to hold the argument of --host etc. +# FIXME: To remove some day. +build=$build_alias +host=$host_alias +target=$target_alias + +# FIXME: To remove some day. +if test "x$host_alias" != x; then + if test "x$build_alias" = x; then + cross_compiling=maybe + $as_echo "$as_me: WARNING: If you wanted to set the --build type, don't use --host. + If a cross compiler is detected then cross compile mode will be used." >&2 + elif test "x$build_alias" != "x$host_alias"; then + cross_compiling=yes + fi +fi + +ac_tool_prefix= +test -n "$host_alias" && ac_tool_prefix=$host_alias- + +test "$silent" = yes && exec 6>/dev/null + + +ac_pwd=`pwd` && test -n "$ac_pwd" && +ac_ls_di=`ls -di .` && +ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || + { $as_echo "$as_me: error: working directory cannot be determined" >&2 + { (exit 1); exit 1; }; } +test "X$ac_ls_di" = "X$ac_pwd_ls_di" || + { $as_echo "$as_me: error: pwd does not report name of working directory" >&2 + { (exit 1); exit 1; }; } + + +# Find the source files, if location was not specified. +if test -z "$srcdir"; then + ac_srcdir_defaulted=yes + # Try the directory containing this script, then the parent directory. + ac_confdir=`$as_dirname -- "$as_myself" || +$as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_myself" : 'X\(//\)[^/]' \| \ + X"$as_myself" : 'X\(//\)$' \| \ + X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_myself" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + srcdir=$ac_confdir + if test ! -r "$srcdir/$ac_unique_file"; then + srcdir=.. + fi +else + ac_srcdir_defaulted=no +fi +if test ! -r "$srcdir/$ac_unique_file"; then + test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." + { $as_echo "$as_me: error: cannot find sources ($ac_unique_file) in $srcdir" >&2 + { (exit 1); exit 1; }; } +fi +ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" +ac_abs_confdir=`( + cd "$srcdir" && test -r "./$ac_unique_file" || { $as_echo "$as_me: error: $ac_msg" >&2 + { (exit 1); exit 1; }; } + pwd)` +# When building in place, set srcdir=. +if test "$ac_abs_confdir" = "$ac_pwd"; then + srcdir=. +fi +# Remove unnecessary trailing slashes from srcdir. +# Double slashes in file names in object file debugging info +# mess up M-x gdb in Emacs. +case $srcdir in +*/) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; +esac +for ac_var in $ac_precious_vars; do + eval ac_env_${ac_var}_set=\${${ac_var}+set} + eval ac_env_${ac_var}_value=\$${ac_var} + eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} + eval ac_cv_env_${ac_var}_value=\$${ac_var} +done + +# +# Report the --help message. +# +if test "$ac_init_help" = "long"; then + # Omit some internal or obsolete options to make the list less imposing. + # This message is too long to be a string in the A/UX 3.1 sh. + cat <<_ACEOF +\`configure' configures this package to adapt to many kinds of systems. + +Usage: $0 [OPTION]... [VAR=VALUE]... + +To assign environment variables (e.g., CC, CFLAGS...), specify them as +VAR=VALUE. See below for descriptions of some of the useful variables. + +Defaults for the options are specified in brackets. + +Configuration: + -h, --help display this help and exit + --help=short display options specific to this package + --help=recursive display the short help of all the included packages + -V, --version display version information and exit + -q, --quiet, --silent do not print \`checking...' messages + --cache-file=FILE cache test results in FILE [disabled] + -C, --config-cache alias for \`--cache-file=config.cache' + -n, --no-create do not create output files + --srcdir=DIR find the sources in DIR [configure dir or \`..'] + +Installation directories: + --prefix=PREFIX install architecture-independent files in PREFIX + [$ac_default_prefix] + --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX + [PREFIX] + +By default, \`make install' will install all the files in +\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify +an installation prefix other than \`$ac_default_prefix' using \`--prefix', +for instance \`--prefix=\$HOME'. + +For better control, use the options below. + +Fine tuning of the installation directories: + --bindir=DIR user executables [EPREFIX/bin] + --sbindir=DIR system admin executables [EPREFIX/sbin] + --libexecdir=DIR program executables [EPREFIX/libexec] + --sysconfdir=DIR read-only single-machine data [PREFIX/etc] + --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] + --localstatedir=DIR modifiable single-machine data [PREFIX/var] + --libdir=DIR object code libraries [EPREFIX/lib] + --includedir=DIR C header files [PREFIX/include] + --oldincludedir=DIR C header files for non-gcc [/usr/include] + --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] + --datadir=DIR read-only architecture-independent data [DATAROOTDIR] + --infodir=DIR info documentation [DATAROOTDIR/info] + --localedir=DIR locale-dependent data [DATAROOTDIR/locale] + --mandir=DIR man documentation [DATAROOTDIR/man] + --docdir=DIR documentation root [DATAROOTDIR/doc/PACKAGE] + --htmldir=DIR html documentation [DOCDIR] + --dvidir=DIR dvi documentation [DOCDIR] + --pdfdir=DIR pdf documentation [DOCDIR] + --psdir=DIR ps documentation [DOCDIR] +_ACEOF + + cat <<\_ACEOF + +Program names: + --program-prefix=PREFIX prepend PREFIX to installed program names + --program-suffix=SUFFIX append SUFFIX to installed program names + --program-transform-name=PROGRAM run sed PROGRAM on installed program names + +System types: + --build=BUILD configure for building on BUILD [guessed] + --host=HOST cross-compile to build programs to run on HOST [BUILD] +_ACEOF +fi + +if test -n "$ac_init_help"; then + + cat <<\_ACEOF + +Optional Features: + --disable-option-checking ignore unrecognized --enable/--with options + --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) + --enable-FEATURE[=ARG] include FEATURE [ARG=yes] + --disable-dependency-tracking speeds up one-time build + --enable-dependency-tracking do not reject slow dependency extractors + --enable-gcc-warnings enable verbose warnings with GCC + --enable-shared[=PKGS] build shared libraries [default=yes] + --enable-static[=PKGS] build static libraries [default=yes] + --enable-fast-install[=PKGS] + optimize for fast installation [default=yes] + --disable-libtool-lock avoid locking (might break parallel builds) + +Optional Packages: + --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] + --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) + --with-gnu-ld assume the C compiler uses GNU ld [default=no] + --with-pic try to use only PIC/non-PIC objects [default=use + both] + --with-tags[=TAGS] include additional configurations [automatic] + +Some influential environment variables: + CC C compiler command + CFLAGS C compiler flags + LDFLAGS linker flags, e.g. -L if you have libraries in a + nonstandard directory + LIBS libraries to pass to the linker, e.g. -l + CPPFLAGS C/C++/Objective C preprocessor flags, e.g. -I if + you have headers in a nonstandard directory + CPP C preprocessor + CXX C++ compiler command + CXXFLAGS C++ compiler flags + CXXCPP C++ preprocessor + F77 Fortran 77 compiler command + FFLAGS Fortran 77 compiler flags + +Use these variables to override the choices made by `configure' or to help +it to find libraries and programs with nonstandard names/locations. + +_ACEOF +ac_status=$? +fi + +if test "$ac_init_help" = "recursive"; then + # If there are subdirs, report their specific --help. + for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue + test -d "$ac_dir" || + { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || + continue + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + cd "$ac_dir" || { ac_status=$?; continue; } + # Check for guested configure. + if test -f "$ac_srcdir/configure.gnu"; then + echo && + $SHELL "$ac_srcdir/configure.gnu" --help=recursive + elif test -f "$ac_srcdir/configure"; then + echo && + $SHELL "$ac_srcdir/configure" --help=recursive + else + $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2 + fi || ac_status=$? + cd "$ac_pwd" || { ac_status=$?; break; } + done +fi + +test -n "$ac_init_help" && exit $ac_status +if $ac_init_version; then + cat <<\_ACEOF +configure +generated by GNU Autoconf 2.63 + +Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, +2002, 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc. +This configure script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it. +_ACEOF + exit +fi +cat >config.log <<_ACEOF +This file contains any messages produced by compilers while +running configure, to aid debugging if configure makes a mistake. + +It was created by $as_me, which was +generated by GNU Autoconf 2.63. Invocation command line was + + $ $0 $@ + +_ACEOF +exec 5>>config.log +{ +cat <<_ASUNAME +## --------- ## +## Platform. ## +## --------- ## + +hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` +uname -m = `(uname -m) 2>/dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` + +/bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` +/usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` +/bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` +/bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` + +_ASUNAME + +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + $as_echo "PATH: $as_dir" +done +IFS=$as_save_IFS + +} >&5 + +cat >&5 <<_ACEOF + + +## ----------- ## +## Core tests. ## +## ----------- ## + +_ACEOF + + +# Keep a trace of the command line. +# Strip out --no-create and --no-recursion so they do not pile up. +# Strip out --silent because we don't want to record it for future runs. +# Also quote any args containing shell meta-characters. +# Make two passes to allow for proper duplicate-argument suppression. +ac_configure_args= +ac_configure_args0= +ac_configure_args1= +ac_must_keep_next=false +for ac_pass in 1 2 +do + for ac_arg + do + case $ac_arg in + -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + continue ;; + *\'*) + ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; + esac + case $ac_pass in + 1) ac_configure_args0="$ac_configure_args0 '$ac_arg'" ;; + 2) + ac_configure_args1="$ac_configure_args1 '$ac_arg'" + if test $ac_must_keep_next = true; then + ac_must_keep_next=false # Got value, back to normal. + else + case $ac_arg in + *=* | --config-cache | -C | -disable-* | --disable-* \ + | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ + | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ + | -with-* | --with-* | -without-* | --without-* | --x) + case "$ac_configure_args0 " in + "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; + esac + ;; + -* ) ac_must_keep_next=true ;; + esac + fi + ac_configure_args="$ac_configure_args '$ac_arg'" + ;; + esac + done +done +$as_unset ac_configure_args0 || test "${ac_configure_args0+set}" != set || { ac_configure_args0=; export ac_configure_args0; } +$as_unset ac_configure_args1 || test "${ac_configure_args1+set}" != set || { ac_configure_args1=; export ac_configure_args1; } + +# When interrupted or exit'd, cleanup temporary files, and complete +# config.log. We remove comments because anyway the quotes in there +# would cause problems or look ugly. +# WARNING: Use '\'' to represent an apostrophe within the trap. +# WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. +trap 'exit_status=$? + # Save into config.log some information that might help in debugging. + { + echo + + cat <<\_ASBOX +## ---------------- ## +## Cache variables. ## +## ---------------- ## +_ASBOX + echo + # The following way of writing the cache mishandles newlines in values, +( + for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do + eval ac_val=\$$ac_var + case $ac_val in #( + *${as_nl}*) + case $ac_var in #( + *_cv_*) { $as_echo "$as_me:$LINENO: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + esac + case $ac_var in #( + _ | IFS | as_nl) ;; #( + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) $as_unset $ac_var ;; + esac ;; + esac + done + (set) 2>&1 | + case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( + *${as_nl}ac_space=\ *) + sed -n \ + "s/'\''/'\''\\\\'\'''\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" + ;; #( + *) + sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" + ;; + esac | + sort +) + echo + + cat <<\_ASBOX +## ----------------- ## +## Output variables. ## +## ----------------- ## +_ASBOX + echo + for ac_var in $ac_subst_vars + do + eval ac_val=\$$ac_var + case $ac_val in + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + esac + $as_echo "$ac_var='\''$ac_val'\''" + done | sort + echo + + if test -n "$ac_subst_files"; then + cat <<\_ASBOX +## ------------------- ## +## File substitutions. ## +## ------------------- ## +_ASBOX + echo + for ac_var in $ac_subst_files + do + eval ac_val=\$$ac_var + case $ac_val in + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + esac + $as_echo "$ac_var='\''$ac_val'\''" + done | sort + echo + fi + + if test -s confdefs.h; then + cat <<\_ASBOX +## ----------- ## +## confdefs.h. ## +## ----------- ## +_ASBOX + echo + cat confdefs.h + echo + fi + test "$ac_signal" != 0 && + $as_echo "$as_me: caught signal $ac_signal" + $as_echo "$as_me: exit $exit_status" + } >&5 + rm -f core *.core core.conftest.* && + rm -f -r conftest* confdefs* conf$$* $ac_clean_files && + exit $exit_status +' 0 +for ac_signal in 1 2 13 15; do + trap 'ac_signal='$ac_signal'; { (exit 1); exit 1; }' $ac_signal +done +ac_signal=0 + +# confdefs.h avoids OS command line length limits that DEFS can exceed. +rm -f -r conftest* confdefs.h + +# Predefined preprocessor variables. + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_NAME "$PACKAGE_NAME" +_ACEOF + + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_TARNAME "$PACKAGE_TARNAME" +_ACEOF + + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_VERSION "$PACKAGE_VERSION" +_ACEOF + + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_STRING "$PACKAGE_STRING" +_ACEOF + + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT" +_ACEOF + + +# Let the site file select an alternate cache file if it wants to. +# Prefer an explicitly selected file to automatically selected ones. +ac_site_file1=NONE +ac_site_file2=NONE +if test -n "$CONFIG_SITE"; then + ac_site_file1=$CONFIG_SITE +elif test "x$prefix" != xNONE; then + ac_site_file1=$prefix/share/config.site + ac_site_file2=$prefix/etc/config.site +else + ac_site_file1=$ac_default_prefix/share/config.site + ac_site_file2=$ac_default_prefix/etc/config.site +fi +for ac_site_file in "$ac_site_file1" "$ac_site_file2" +do + test "x$ac_site_file" = xNONE && continue + if test -r "$ac_site_file"; then + { $as_echo "$as_me:$LINENO: loading site script $ac_site_file" >&5 +$as_echo "$as_me: loading site script $ac_site_file" >&6;} + sed 's/^/| /' "$ac_site_file" >&5 + . "$ac_site_file" + fi +done + +if test -r "$cache_file"; then + # Some versions of bash will fail to source /dev/null (special + # files actually), so we avoid doing that. + if test -f "$cache_file"; then + { $as_echo "$as_me:$LINENO: loading cache $cache_file" >&5 +$as_echo "$as_me: loading cache $cache_file" >&6;} + case $cache_file in + [\\/]* | ?:[\\/]* ) . "$cache_file";; + *) . "./$cache_file";; + esac + fi +else + { $as_echo "$as_me:$LINENO: creating cache $cache_file" >&5 +$as_echo "$as_me: creating cache $cache_file" >&6;} + >$cache_file +fi + +# Check that the precious variables saved in the cache have kept the same +# value. +ac_cache_corrupted=false +for ac_var in $ac_precious_vars; do + eval ac_old_set=\$ac_cv_env_${ac_var}_set + eval ac_new_set=\$ac_env_${ac_var}_set + eval ac_old_val=\$ac_cv_env_${ac_var}_value + eval ac_new_val=\$ac_env_${ac_var}_value + case $ac_old_set,$ac_new_set in + set,) + { $as_echo "$as_me:$LINENO: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,set) + { $as_echo "$as_me:$LINENO: error: \`$ac_var' was not set in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,);; + *) + if test "x$ac_old_val" != "x$ac_new_val"; then + # differences in whitespace do not lead to failure. + ac_old_val_w=`echo x $ac_old_val` + ac_new_val_w=`echo x $ac_new_val` + if test "$ac_old_val_w" != "$ac_new_val_w"; then + { $as_echo "$as_me:$LINENO: error: \`$ac_var' has changed since the previous run:" >&5 +$as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} + ac_cache_corrupted=: + else + { $as_echo "$as_me:$LINENO: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 +$as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} + eval $ac_var=\$ac_old_val + fi + { $as_echo "$as_me:$LINENO: former value: \`$ac_old_val'" >&5 +$as_echo "$as_me: former value: \`$ac_old_val'" >&2;} + { $as_echo "$as_me:$LINENO: current value: \`$ac_new_val'" >&5 +$as_echo "$as_me: current value: \`$ac_new_val'" >&2;} + fi;; + esac + # Pass precious variables to config.status. + if test "$ac_new_set" = set; then + case $ac_new_val in + *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; + *) ac_arg=$ac_var=$ac_new_val ;; + esac + case " $ac_configure_args " in + *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. + *) ac_configure_args="$ac_configure_args '$ac_arg'" ;; + esac + fi +done +if $ac_cache_corrupted; then + { $as_echo "$as_me:$LINENO: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} + { $as_echo "$as_me:$LINENO: error: changes in the environment can compromise the build" >&5 +$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;} + { { $as_echo "$as_me:$LINENO: error: run \`make distclean' and/or \`rm $cache_file' and start over" >&5 +$as_echo "$as_me: error: run \`make distclean' and/or \`rm $cache_file' and start over" >&2;} + { (exit 1); exit 1; }; } +fi + + + + + + + + + + + + + + + + + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + +am__api_version='1.10' + +ac_aux_dir= +for ac_dir in "$srcdir" "$srcdir/.." "$srcdir/../.."; do + if test -f "$ac_dir/install-sh"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install-sh -c" + break + elif test -f "$ac_dir/install.sh"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install.sh -c" + break + elif test -f "$ac_dir/shtool"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/shtool install -c" + break + fi +done +if test -z "$ac_aux_dir"; then + { { $as_echo "$as_me:$LINENO: error: cannot find install-sh or install.sh in \"$srcdir\" \"$srcdir/..\" \"$srcdir/../..\"" >&5 +$as_echo "$as_me: error: cannot find install-sh or install.sh in \"$srcdir\" \"$srcdir/..\" \"$srcdir/../..\"" >&2;} + { (exit 1); exit 1; }; } +fi + +# These three variables are undocumented and unsupported, +# and are intended to be withdrawn in a future Autoconf release. +# They can cause serious problems if a builder's source tree is in a directory +# whose full name contains unusual characters. +ac_config_guess="$SHELL $ac_aux_dir/config.guess" # Please don't use this var. +ac_config_sub="$SHELL $ac_aux_dir/config.sub" # Please don't use this var. +ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var. + + +# Find a good install program. We prefer a C program (faster), +# so one script is as good as another. But avoid the broken or +# incompatible versions: +# SysV /etc/install, /usr/sbin/install +# SunOS /usr/etc/install +# IRIX /sbin/install +# AIX /bin/install +# AmigaOS /C/install, which installs bootblocks on floppy discs +# AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag +# AFS /usr/afsws/bin/install, which mishandles nonexistent args +# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" +# OS/2's system install, which has a completely different semantic +# ./install, which can be erroneously created by make from ./install.sh. +# Reject install programs that cannot install multiple files. +{ $as_echo "$as_me:$LINENO: checking for a BSD-compatible install" >&5 +$as_echo_n "checking for a BSD-compatible install... " >&6; } +if test -z "$INSTALL"; then +if test "${ac_cv_path_install+set}" = set; then + $as_echo_n "(cached) " >&6 +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + # Account for people who put trailing slashes in PATH elements. +case $as_dir/ in + ./ | .// | /cC/* | \ + /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \ + ?:\\/os2\\/install\\/* | ?:\\/OS2\\/INSTALL\\/* | \ + /usr/ucb/* ) ;; + *) + # OSF1 and SCO ODT 3.0 have their own names for install. + # Don't use installbsd from OSF since it installs stuff as root + # by default. + for ac_prog in ginstall scoinst install; do + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_prog$ac_exec_ext" && $as_test_x "$as_dir/$ac_prog$ac_exec_ext"; }; then + if test $ac_prog = install && + grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then + # AIX install. It has an incompatible calling convention. + : + elif test $ac_prog = install && + grep pwplus "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then + # program-specific install script used by HP pwplus--don't use. + : + else + rm -rf conftest.one conftest.two conftest.dir + echo one > conftest.one + echo two > conftest.two + mkdir conftest.dir + if "$as_dir/$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir" && + test -s conftest.one && test -s conftest.two && + test -s conftest.dir/conftest.one && + test -s conftest.dir/conftest.two + then + ac_cv_path_install="$as_dir/$ac_prog$ac_exec_ext -c" + break 3 + fi + fi + fi + done + done + ;; +esac + +done +IFS=$as_save_IFS + +rm -rf conftest.one conftest.two conftest.dir + +fi + if test "${ac_cv_path_install+set}" = set; then + INSTALL=$ac_cv_path_install + else + # As a last resort, use the slow shell script. Don't cache a + # value for INSTALL within a source directory, because that will + # break other packages using the cache if that directory is + # removed, or if the value is a relative name. + INSTALL=$ac_install_sh + fi +fi +{ $as_echo "$as_me:$LINENO: result: $INSTALL" >&5 +$as_echo "$INSTALL" >&6; } + +# Use test -z because SunOS4 sh mishandles braces in ${var-val}. +# It thinks the first close brace ends the variable substitution. +test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' + +test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}' + +test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' + +{ $as_echo "$as_me:$LINENO: checking whether build environment is sane" >&5 +$as_echo_n "checking whether build environment is sane... " >&6; } +# Just in case +sleep 1 +echo timestamp > conftest.file +# Do `set' in a subshell so we don't clobber the current shell's +# arguments. Must try -L first in case configure is actually a +# symlink; some systems play weird games with the mod time of symlinks +# (eg FreeBSD returns the mod time of the symlink's containing +# directory). +if ( + set X `ls -Lt $srcdir/configure conftest.file 2> /dev/null` + if test "$*" = "X"; then + # -L didn't work. + set X `ls -t $srcdir/configure conftest.file` + fi + rm -f conftest.file + if test "$*" != "X $srcdir/configure conftest.file" \ + && test "$*" != "X conftest.file $srcdir/configure"; then + + # If neither matched, then we have a broken ls. This can happen + # if, for instance, CONFIG_SHELL is bash and it inherits a + # broken ls alias from the environment. This has actually + # happened. Such a system could not be considered "sane". + { { $as_echo "$as_me:$LINENO: error: ls -t appears to fail. Make sure there is not a broken +alias in your environment" >&5 +$as_echo "$as_me: error: ls -t appears to fail. Make sure there is not a broken +alias in your environment" >&2;} + { (exit 1); exit 1; }; } + fi + + test "$2" = conftest.file + ) +then + # Ok. + : +else + { { $as_echo "$as_me:$LINENO: error: newly created file is older than distributed files! +Check your system clock" >&5 +$as_echo "$as_me: error: newly created file is older than distributed files! +Check your system clock" >&2;} + { (exit 1); exit 1; }; } +fi +{ $as_echo "$as_me:$LINENO: result: yes" >&5 +$as_echo "yes" >&6; } +test "$program_prefix" != NONE && + program_transform_name="s&^&$program_prefix&;$program_transform_name" +# Use a double $ so make ignores it. +test "$program_suffix" != NONE && + program_transform_name="s&\$&$program_suffix&;$program_transform_name" +# Double any \ or $. +# By default was `s,x,x', remove it if useless. +ac_script='s/[\\$]/&&/g;s/;s,x,x,$//' +program_transform_name=`$as_echo "$program_transform_name" | sed "$ac_script"` + +# expand $ac_aux_dir to an absolute path +am_aux_dir=`cd $ac_aux_dir && pwd` + +test x"${MISSING+set}" = xset || MISSING="\${SHELL} $am_aux_dir/missing" +# Use eval to expand $SHELL +if eval "$MISSING --run true"; then + am_missing_run="$MISSING --run " +else + am_missing_run= + { $as_echo "$as_me:$LINENO: WARNING: \`missing' script is too old or missing" >&5 +$as_echo "$as_me: WARNING: \`missing' script is too old or missing" >&2;} +fi + +{ $as_echo "$as_me:$LINENO: checking for a thread-safe mkdir -p" >&5 +$as_echo_n "checking for a thread-safe mkdir -p... " >&6; } +if test -z "$MKDIR_P"; then + if test "${ac_cv_path_mkdir+set}" = set; then + $as_echo_n "(cached) " >&6 +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/opt/sfw/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in mkdir gmkdir; do + for ac_exec_ext in '' $ac_executable_extensions; do + { test -f "$as_dir/$ac_prog$ac_exec_ext" && $as_test_x "$as_dir/$ac_prog$ac_exec_ext"; } || continue + case `"$as_dir/$ac_prog$ac_exec_ext" --version 2>&1` in #( + 'mkdir (GNU coreutils) '* | \ + 'mkdir (coreutils) '* | \ + 'mkdir (fileutils) '4.1*) + ac_cv_path_mkdir=$as_dir/$ac_prog$ac_exec_ext + break 3;; + esac + done + done +done +IFS=$as_save_IFS + +fi + + if test "${ac_cv_path_mkdir+set}" = set; then + MKDIR_P="$ac_cv_path_mkdir -p" + else + # As a last resort, use the slow shell script. Don't cache a + # value for MKDIR_P within a source directory, because that will + # break other packages using the cache if that directory is + # removed, or if the value is a relative name. + test -d ./--version && rmdir ./--version + MKDIR_P="$ac_install_sh -d" + fi +fi +{ $as_echo "$as_me:$LINENO: result: $MKDIR_P" >&5 +$as_echo "$MKDIR_P" >&6; } + +mkdir_p="$MKDIR_P" +case $mkdir_p in + [\\/$]* | ?:[\\/]*) ;; + */*) mkdir_p="\$(top_builddir)/$mkdir_p" ;; +esac + +for ac_prog in gawk mawk nawk awk +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:$LINENO: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_AWK+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test -n "$AWK"; then + ac_cv_prog_AWK="$AWK" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_AWK="$ac_prog" + $as_echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done +done +IFS=$as_save_IFS + +fi +fi +AWK=$ac_cv_prog_AWK +if test -n "$AWK"; then + { $as_echo "$as_me:$LINENO: result: $AWK" >&5 +$as_echo "$AWK" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$AWK" && break +done + +{ $as_echo "$as_me:$LINENO: checking whether ${MAKE-make} sets \$(MAKE)" >&5 +$as_echo_n "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; } +set x ${MAKE-make} +ac_make=`$as_echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'` +if { as_var=ac_cv_prog_make_${ac_make}_set; eval "test \"\${$as_var+set}\" = set"; }; then + $as_echo_n "(cached) " >&6 +else + cat >conftest.make <<\_ACEOF +SHELL = /bin/sh +all: + @echo '@@@%%%=$(MAKE)=@@@%%%' +_ACEOF +# GNU make sometimes prints "make[1]: Entering...", which would confuse us. +case `${MAKE-make} -f conftest.make 2>/dev/null` in + *@@@%%%=?*=@@@%%%*) + eval ac_cv_prog_make_${ac_make}_set=yes;; + *) + eval ac_cv_prog_make_${ac_make}_set=no;; +esac +rm -f conftest.make +fi +if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then + { $as_echo "$as_me:$LINENO: result: yes" >&5 +$as_echo "yes" >&6; } + SET_MAKE= +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } + SET_MAKE="MAKE=${MAKE-make}" +fi + +rm -rf .tst 2>/dev/null +mkdir .tst 2>/dev/null +if test -d .tst; then + am__leading_dot=. +else + am__leading_dot=_ +fi +rmdir .tst 2>/dev/null + +if test "`cd $srcdir && pwd`" != "`pwd`"; then + # Use -I$(srcdir) only when $(srcdir) != ., so that make's output + # is not polluted with repeated "-I." + am__isrc=' -I$(srcdir)' + # test to see if srcdir already configured + if test -f $srcdir/config.status; then + { { $as_echo "$as_me:$LINENO: error: source directory already configured; run \"make distclean\" there first" >&5 +$as_echo "$as_me: error: source directory already configured; run \"make distclean\" there first" >&2;} + { (exit 1); exit 1; }; } + fi +fi + +# test whether we have cygpath +if test -z "$CYGPATH_W"; then + if (cygpath --version) >/dev/null 2>/dev/null; then + CYGPATH_W='cygpath -w' + else + CYGPATH_W=echo + fi +fi + + +# Define the identity of the package. + PACKAGE=libevent + VERSION=1.4.13-stable + + +cat >>confdefs.h <<_ACEOF +#define PACKAGE "$PACKAGE" +_ACEOF + + +cat >>confdefs.h <<_ACEOF +#define VERSION "$VERSION" +_ACEOF + +# Some tools Automake needs. + +ACLOCAL=${ACLOCAL-"${am_missing_run}aclocal-${am__api_version}"} + + +AUTOCONF=${AUTOCONF-"${am_missing_run}autoconf"} + + +AUTOMAKE=${AUTOMAKE-"${am_missing_run}automake-${am__api_version}"} + + +AUTOHEADER=${AUTOHEADER-"${am_missing_run}autoheader"} + + +MAKEINFO=${MAKEINFO-"${am_missing_run}makeinfo"} + +install_sh=${install_sh-"\$(SHELL) $am_aux_dir/install-sh"} + +# Installed binaries are usually stripped using `strip' when the user +# run `make install-strip'. However `strip' might not be the right +# tool to use in cross-compilation environments, therefore Automake +# will honor the `STRIP' environment variable to overrule this program. +if test "$cross_compiling" != no; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args. +set dummy ${ac_tool_prefix}strip; ac_word=$2 +{ $as_echo "$as_me:$LINENO: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_STRIP+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test -n "$STRIP"; then + ac_cv_prog_STRIP="$STRIP" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_STRIP="${ac_tool_prefix}strip" + $as_echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done +done +IFS=$as_save_IFS + +fi +fi +STRIP=$ac_cv_prog_STRIP +if test -n "$STRIP"; then + { $as_echo "$as_me:$LINENO: result: $STRIP" >&5 +$as_echo "$STRIP" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_STRIP"; then + ac_ct_STRIP=$STRIP + # Extract the first word of "strip", so it can be a program name with args. +set dummy strip; ac_word=$2 +{ $as_echo "$as_me:$LINENO: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_ac_ct_STRIP+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_STRIP"; then + ac_cv_prog_ac_ct_STRIP="$ac_ct_STRIP" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_ac_ct_STRIP="strip" + $as_echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done +done +IFS=$as_save_IFS + +fi +fi +ac_ct_STRIP=$ac_cv_prog_ac_ct_STRIP +if test -n "$ac_ct_STRIP"; then + { $as_echo "$as_me:$LINENO: result: $ac_ct_STRIP" >&5 +$as_echo "$ac_ct_STRIP" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_STRIP" = x; then + STRIP=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:$LINENO: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + STRIP=$ac_ct_STRIP + fi +else + STRIP="$ac_cv_prog_STRIP" +fi + +fi +INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s" + +# We need awk for the "check" target. The system "awk" is bad on +# some platforms. +# Always define AMTAR for backward compatibility. + +AMTAR=${AMTAR-"${am_missing_run}tar"} + +am__tar='${AMTAR} chof - "$$tardir"'; am__untar='${AMTAR} xf -' + + + + + +ac_config_headers="$ac_config_headers config.h" + + +if test "$prefix" = "NONE"; then + prefix="/usr/local" +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. +set dummy ${ac_tool_prefix}gcc; ac_word=$2 +{ $as_echo "$as_me:$LINENO: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_CC+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_CC="${ac_tool_prefix}gcc" + $as_echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done +done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:$LINENO: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_CC"; then + ac_ct_CC=$CC + # Extract the first word of "gcc", so it can be a program name with args. +set dummy gcc; ac_word=$2 +{ $as_echo "$as_me:$LINENO: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_ac_ct_CC+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_ac_ct_CC="gcc" + $as_echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done +done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:$LINENO: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:$LINENO: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +else + CC="$ac_cv_prog_CC" +fi + +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. +set dummy ${ac_tool_prefix}cc; ac_word=$2 +{ $as_echo "$as_me:$LINENO: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_CC+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_CC="${ac_tool_prefix}cc" + $as_echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done +done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:$LINENO: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi + + + fi +fi +if test -z "$CC"; then + # Extract the first word of "cc", so it can be a program name with args. +set dummy cc; ac_word=$2 +{ $as_echo "$as_me:$LINENO: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_CC+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + ac_prog_rejected=no +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then + ac_prog_rejected=yes + continue + fi + ac_cv_prog_CC="cc" + $as_echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done +done +IFS=$as_save_IFS + +if test $ac_prog_rejected = yes; then + # We found a bogon in the path, so make sure we never use it. + set dummy $ac_cv_prog_CC + shift + if test $# != 0; then + # We chose a different compiler from the bogus one. + # However, it has the same basename, so the bogon will be chosen + # first if we set CC to just the basename; use the full file name. + shift + ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@" + fi +fi +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:$LINENO: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + for ac_prog in cl.exe + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:$LINENO: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_CC+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_CC="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done +done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:$LINENO: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$CC" && break + done +fi +if test -z "$CC"; then + ac_ct_CC=$CC + for ac_prog in cl.exe +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:$LINENO: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_ac_ct_CC+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_ac_ct_CC="$ac_prog" + $as_echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done +done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:$LINENO: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_CC" && break +done + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:$LINENO: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +fi + +fi + + +test -z "$CC" && { { $as_echo "$as_me:$LINENO: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +{ { $as_echo "$as_me:$LINENO: error: no acceptable C compiler found in \$PATH +See \`config.log' for more details." >&5 +$as_echo "$as_me: error: no acceptable C compiler found in \$PATH +See \`config.log' for more details." >&2;} + { (exit 1); exit 1; }; }; } + +# Provide some information about the compiler. +$as_echo "$as_me:$LINENO: checking for C compiler version" >&5 +set X $ac_compile +ac_compiler=$2 +{ (ac_try="$ac_compiler --version >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compiler --version >&5") 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } +{ (ac_try="$ac_compiler -v >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compiler -v >&5") 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } +{ (ac_try="$ac_compiler -V >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compiler -V >&5") 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } + +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" +# Try to create an executable without -o first, disregard a.out. +# It will help us diagnose broken compilers, and finding out an intuition +# of exeext. +{ $as_echo "$as_me:$LINENO: checking for C compiler default output file name" >&5 +$as_echo_n "checking for C compiler default output file name... " >&6; } +ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'` + +# The possible output files: +ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" + +ac_rmfiles= +for ac_file in $ac_files +do + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + * ) ac_rmfiles="$ac_rmfiles $ac_file";; + esac +done +rm -f $ac_rmfiles + +if { (ac_try="$ac_link_default" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link_default") 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; then + # Autoconf-2.13 could set the ac_cv_exeext variable to `no'. +# So ignore a value of `no', otherwise this would lead to `EXEEXT = no' +# in a Makefile. We should not override ac_cv_exeext if it was cached, +# so that the user can short-circuit this test for compilers unknown to +# Autoconf. +for ac_file in $ac_files '' +do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) + ;; + [ab].out ) + # We found the default executable, but exeext='' is most + # certainly right. + break;; + *.* ) + if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no; + then :; else + ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + fi + # We set ac_cv_exeext here because the later test for it is not + # safe: cross compilers may not add the suffix if given an `-o' + # argument, so we may need to know it at that point already. + # Even if this section looks crufty: it has the advantage of + # actually working. + break;; + * ) + break;; + esac +done +test "$ac_cv_exeext" = no && ac_cv_exeext= + +else + ac_file='' +fi + +{ $as_echo "$as_me:$LINENO: result: $ac_file" >&5 +$as_echo "$ac_file" >&6; } +if test -z "$ac_file"; then + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { $as_echo "$as_me:$LINENO: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +{ { $as_echo "$as_me:$LINENO: error: C compiler cannot create executables +See \`config.log' for more details." >&5 +$as_echo "$as_me: error: C compiler cannot create executables +See \`config.log' for more details." >&2;} + { (exit 77); exit 77; }; }; } +fi + +ac_exeext=$ac_cv_exeext + +# Check that the compiler produces executables we can run. If not, either +# the compiler is broken, or we cross compile. +{ $as_echo "$as_me:$LINENO: checking whether the C compiler works" >&5 +$as_echo_n "checking whether the C compiler works... " >&6; } +# FIXME: These cross compiler hacks should be removed for Autoconf 3.0 +# If not cross compiling, check that we can run a simple program. +if test "$cross_compiling" != yes; then + if { ac_try='./$ac_file' + { (case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; }; then + cross_compiling=no + else + if test "$cross_compiling" = maybe; then + cross_compiling=yes + else + { { $as_echo "$as_me:$LINENO: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +{ { $as_echo "$as_me:$LINENO: error: cannot run C compiled programs. +If you meant to cross compile, use \`--host'. +See \`config.log' for more details." >&5 +$as_echo "$as_me: error: cannot run C compiled programs. +If you meant to cross compile, use \`--host'. +See \`config.log' for more details." >&2;} + { (exit 1); exit 1; }; }; } + fi + fi +fi +{ $as_echo "$as_me:$LINENO: result: yes" >&5 +$as_echo "yes" >&6; } + +rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out +ac_clean_files=$ac_clean_files_save +# Check that the compiler produces executables we can run. If not, either +# the compiler is broken, or we cross compile. +{ $as_echo "$as_me:$LINENO: checking whether we are cross compiling" >&5 +$as_echo_n "checking whether we are cross compiling... " >&6; } +{ $as_echo "$as_me:$LINENO: result: $cross_compiling" >&5 +$as_echo "$cross_compiling" >&6; } + +{ $as_echo "$as_me:$LINENO: checking for suffix of executables" >&5 +$as_echo_n "checking for suffix of executables... " >&6; } +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; then + # If both `conftest.exe' and `conftest' are `present' (well, observable) +# catch `conftest.exe'. For instance with Cygwin, `ls conftest' will +# work properly (i.e., refer to `conftest.exe'), while it won't with +# `rm'. +for ac_file in conftest.exe conftest conftest.*; do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + break;; + * ) break;; + esac +done +else + { { $as_echo "$as_me:$LINENO: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +{ { $as_echo "$as_me:$LINENO: error: cannot compute suffix of executables: cannot compile and link +See \`config.log' for more details." >&5 +$as_echo "$as_me: error: cannot compute suffix of executables: cannot compile and link +See \`config.log' for more details." >&2;} + { (exit 1); exit 1; }; }; } +fi + +rm -f conftest$ac_cv_exeext +{ $as_echo "$as_me:$LINENO: result: $ac_cv_exeext" >&5 +$as_echo "$ac_cv_exeext" >&6; } + +rm -f conftest.$ac_ext +EXEEXT=$ac_cv_exeext +ac_exeext=$EXEEXT +{ $as_echo "$as_me:$LINENO: checking for suffix of object files" >&5 +$as_echo_n "checking for suffix of object files... " >&6; } +if test "${ac_cv_objext+set}" = set; then + $as_echo_n "(cached) " >&6 +else + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.o conftest.obj +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; then + for ac_file in conftest.o conftest.obj conftest.*; do + test -f "$ac_file" || continue; + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;; + *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` + break;; + esac +done +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { $as_echo "$as_me:$LINENO: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +{ { $as_echo "$as_me:$LINENO: error: cannot compute suffix of object files: cannot compile +See \`config.log' for more details." >&5 +$as_echo "$as_me: error: cannot compute suffix of object files: cannot compile +See \`config.log' for more details." >&2;} + { (exit 1); exit 1; }; }; } +fi + +rm -f conftest.$ac_cv_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_objext" >&5 +$as_echo "$ac_cv_objext" >&6; } +OBJEXT=$ac_cv_objext +ac_objext=$OBJEXT +{ $as_echo "$as_me:$LINENO: checking whether we are using the GNU C compiler" >&5 +$as_echo_n "checking whether we are using the GNU C compiler... " >&6; } +if test "${ac_cv_c_compiler_gnu+set}" = set; then + $as_echo_n "(cached) " >&6 +else + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +int +main () +{ +#ifndef __GNUC__ + choke me +#endif + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_compiler_gnu=yes +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_compiler_gnu=no +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +ac_cv_c_compiler_gnu=$ac_compiler_gnu + +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_c_compiler_gnu" >&5 +$as_echo "$ac_cv_c_compiler_gnu" >&6; } +if test $ac_compiler_gnu = yes; then + GCC=yes +else + GCC= +fi +ac_test_CFLAGS=${CFLAGS+set} +ac_save_CFLAGS=$CFLAGS +{ $as_echo "$as_me:$LINENO: checking whether $CC accepts -g" >&5 +$as_echo_n "checking whether $CC accepts -g... " >&6; } +if test "${ac_cv_prog_cc_g+set}" = set; then + $as_echo_n "(cached) " >&6 +else + ac_save_c_werror_flag=$ac_c_werror_flag + ac_c_werror_flag=yes + ac_cv_prog_cc_g=no + CFLAGS="-g" + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_cv_prog_cc_g=yes +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + CFLAGS="" + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + : +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_c_werror_flag=$ac_save_c_werror_flag + CFLAGS="-g" + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_cv_prog_cc_g=yes +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_c_werror_flag=$ac_save_c_werror_flag +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_prog_cc_g" >&5 +$as_echo "$ac_cv_prog_cc_g" >&6; } +if test "$ac_test_CFLAGS" = set; then + CFLAGS=$ac_save_CFLAGS +elif test $ac_cv_prog_cc_g = yes; then + if test "$GCC" = yes; then + CFLAGS="-g -O2" + else + CFLAGS="-g" + fi +else + if test "$GCC" = yes; then + CFLAGS="-O2" + else + CFLAGS= + fi +fi +{ $as_echo "$as_me:$LINENO: checking for $CC option to accept ISO C89" >&5 +$as_echo_n "checking for $CC option to accept ISO C89... " >&6; } +if test "${ac_cv_prog_cc_c89+set}" = set; then + $as_echo_n "(cached) " >&6 +else + ac_cv_prog_cc_c89=no +ac_save_CC=$CC +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#include +#include +#include +#include +/* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */ +struct buf { int x; }; +FILE * (*rcsopen) (struct buf *, struct stat *, int); +static char *e (p, i) + char **p; + int i; +{ + return p[i]; +} +static char *f (char * (*g) (char **, int), char **p, ...) +{ + char *s; + va_list v; + va_start (v,p); + s = g (p, va_arg (v,int)); + va_end (v); + return s; +} + +/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has + function prototypes and stuff, but not '\xHH' hex character constants. + These don't provoke an error unfortunately, instead are silently treated + as 'x'. The following induces an error, until -std is added to get + proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an + array size at least. It's necessary to write '\x00'==0 to get something + that's true only with -std. */ +int osf4_cc_array ['\x00' == 0 ? 1 : -1]; + +/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters + inside strings and character constants. */ +#define FOO(x) 'x' +int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1]; + +int test (int i, double x); +struct s1 {int (*f) (int a);}; +struct s2 {int (*f) (double a);}; +int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int); +int argc; +char **argv; +int +main () +{ +return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]; + ; + return 0; +} +_ACEOF +for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \ + -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" +do + CC="$ac_save_CC $ac_arg" + rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_cv_prog_cc_c89=$ac_arg +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + +fi + +rm -f core conftest.err conftest.$ac_objext + test "x$ac_cv_prog_cc_c89" != "xno" && break +done +rm -f conftest.$ac_ext +CC=$ac_save_CC + +fi +# AC_CACHE_VAL +case "x$ac_cv_prog_cc_c89" in + x) + { $as_echo "$as_me:$LINENO: result: none needed" >&5 +$as_echo "none needed" >&6; } ;; + xno) + { $as_echo "$as_me:$LINENO: result: unsupported" >&5 +$as_echo "unsupported" >&6; } ;; + *) + CC="$CC $ac_cv_prog_cc_c89" + { $as_echo "$as_me:$LINENO: result: $ac_cv_prog_cc_c89" >&5 +$as_echo "$ac_cv_prog_cc_c89" >&6; } ;; +esac + + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +DEPDIR="${am__leading_dot}deps" + +ac_config_commands="$ac_config_commands depfiles" + + +am_make=${MAKE-make} +cat > confinc << 'END' +am__doit: + @echo done +.PHONY: am__doit +END +# If we don't find an include directive, just comment out the code. +{ $as_echo "$as_me:$LINENO: checking for style of include used by $am_make" >&5 +$as_echo_n "checking for style of include used by $am_make... " >&6; } +am__include="#" +am__quote= +_am_result=none +# First try GNU make style include. +echo "include confinc" > confmf +# We grep out `Entering directory' and `Leaving directory' +# messages which can occur if `w' ends up in MAKEFLAGS. +# In particular we don't look at `^make:' because GNU make might +# be invoked under some other name (usually "gmake"), in which +# case it prints its new name instead of `make'. +if test "`$am_make -s -f confmf 2> /dev/null | grep -v 'ing directory'`" = "done"; then + am__include=include + am__quote= + _am_result=GNU +fi +# Now try BSD make style include. +if test "$am__include" = "#"; then + echo '.include "confinc"' > confmf + if test "`$am_make -s -f confmf 2> /dev/null`" = "done"; then + am__include=.include + am__quote="\"" + _am_result=BSD + fi +fi + + +{ $as_echo "$as_me:$LINENO: result: $_am_result" >&5 +$as_echo "$_am_result" >&6; } +rm -f confinc confmf + +# Check whether --enable-dependency-tracking was given. +if test "${enable_dependency_tracking+set}" = set; then + enableval=$enable_dependency_tracking; +fi + +if test "x$enable_dependency_tracking" != xno; then + am_depcomp="$ac_aux_dir/depcomp" + AMDEPBACKSLASH='\' +fi + if test "x$enable_dependency_tracking" != xno; then + AMDEP_TRUE= + AMDEP_FALSE='#' +else + AMDEP_TRUE='#' + AMDEP_FALSE= +fi + + + +depcc="$CC" am_compiler_list= + +{ $as_echo "$as_me:$LINENO: checking dependency style of $depcc" >&5 +$as_echo_n "checking dependency style of $depcc... " >&6; } +if test "${am_cv_CC_dependencies_compiler_type+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then + # We make a subdir and do the tests there. Otherwise we can end up + # making bogus files that we don't know about and never remove. For + # instance it was reported that on HP-UX the gcc test will end up + # making a dummy file named `D' -- because `-MD' means `put the output + # in D'. + mkdir conftest.dir + # Copy depcomp to subdir because otherwise we won't find it if we're + # using a relative directory. + cp "$am_depcomp" conftest.dir + cd conftest.dir + # We will build objects and dependencies in a subdirectory because + # it helps to detect inapplicable dependency modes. For instance + # both Tru64's cc and ICC support -MD to output dependencies as a + # side effect of compilation, but ICC will put the dependencies in + # the current directory while Tru64 will put them in the object + # directory. + mkdir sub + + am_cv_CC_dependencies_compiler_type=none + if test "$am_compiler_list" = ""; then + am_compiler_list=`sed -n 's/^#*\([a-zA-Z0-9]*\))$/\1/p' < ./depcomp` + fi + for depmode in $am_compiler_list; do + # Setup a source with many dependencies, because some compilers + # like to wrap large dependency lists on column 80 (with \), and + # we should not choose a depcomp mode which is confused by this. + # + # We need to recreate these files for each test, as the compiler may + # overwrite some of them when testing with obscure command lines. + # This happens at least with the AIX C compiler. + : > sub/conftest.c + for i in 1 2 3 4 5 6; do + echo '#include "conftst'$i'.h"' >> sub/conftest.c + # Using `: > sub/conftst$i.h' creates only sub/conftst1.h with + # Solaris 8's {/usr,}/bin/sh. + touch sub/conftst$i.h + done + echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf + + case $depmode in + nosideeffect) + # after this tag, mechanisms are not by side-effect, so they'll + # only be used when explicitly requested + if test "x$enable_dependency_tracking" = xyes; then + continue + else + break + fi + ;; + none) break ;; + esac + # We check with `-c' and `-o' for the sake of the "dashmstdout" + # mode. It turns out that the SunPro C++ compiler does not properly + # handle `-M -o', and we need to detect this. + if depmode=$depmode \ + source=sub/conftest.c object=sub/conftest.${OBJEXT-o} \ + depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ + $SHELL ./depcomp $depcc -c -o sub/conftest.${OBJEXT-o} sub/conftest.c \ + >/dev/null 2>conftest.err && + grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && + grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && + grep sub/conftest.${OBJEXT-o} sub/conftest.Po > /dev/null 2>&1 && + ${MAKE-make} -s -f confmf > /dev/null 2>&1; then + # icc doesn't choke on unknown options, it will just issue warnings + # or remarks (even with -Werror). So we grep stderr for any message + # that says an option was ignored or not supported. + # When given -MP, icc 7.0 and 7.1 complain thusly: + # icc: Command line warning: ignoring option '-M'; no argument required + # The diagnosis changed in icc 8.0: + # icc: Command line remark: option '-MP' not supported + if (grep 'ignoring option' conftest.err || + grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else + am_cv_CC_dependencies_compiler_type=$depmode + break + fi + fi + done + + cd .. + rm -rf conftest.dir +else + am_cv_CC_dependencies_compiler_type=none +fi + +fi +{ $as_echo "$as_me:$LINENO: result: $am_cv_CC_dependencies_compiler_type" >&5 +$as_echo "$am_cv_CC_dependencies_compiler_type" >&6; } +CCDEPMODE=depmode=$am_cv_CC_dependencies_compiler_type + + if + test "x$enable_dependency_tracking" != xno \ + && test "$am_cv_CC_dependencies_compiler_type" = gcc3; then + am__fastdepCC_TRUE= + am__fastdepCC_FALSE='#' +else + am__fastdepCC_TRUE='#' + am__fastdepCC_FALSE= +fi + + +# Find a good install program. We prefer a C program (faster), +# so one script is as good as another. But avoid the broken or +# incompatible versions: +# SysV /etc/install, /usr/sbin/install +# SunOS /usr/etc/install +# IRIX /sbin/install +# AIX /bin/install +# AmigaOS /C/install, which installs bootblocks on floppy discs +# AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag +# AFS /usr/afsws/bin/install, which mishandles nonexistent args +# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" +# OS/2's system install, which has a completely different semantic +# ./install, which can be erroneously created by make from ./install.sh. +# Reject install programs that cannot install multiple files. +{ $as_echo "$as_me:$LINENO: checking for a BSD-compatible install" >&5 +$as_echo_n "checking for a BSD-compatible install... " >&6; } +if test -z "$INSTALL"; then +if test "${ac_cv_path_install+set}" = set; then + $as_echo_n "(cached) " >&6 +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + # Account for people who put trailing slashes in PATH elements. +case $as_dir/ in + ./ | .// | /cC/* | \ + /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \ + ?:\\/os2\\/install\\/* | ?:\\/OS2\\/INSTALL\\/* | \ + /usr/ucb/* ) ;; + *) + # OSF1 and SCO ODT 3.0 have their own names for install. + # Don't use installbsd from OSF since it installs stuff as root + # by default. + for ac_prog in ginstall scoinst install; do + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_prog$ac_exec_ext" && $as_test_x "$as_dir/$ac_prog$ac_exec_ext"; }; then + if test $ac_prog = install && + grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then + # AIX install. It has an incompatible calling convention. + : + elif test $ac_prog = install && + grep pwplus "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then + # program-specific install script used by HP pwplus--don't use. + : + else + rm -rf conftest.one conftest.two conftest.dir + echo one > conftest.one + echo two > conftest.two + mkdir conftest.dir + if "$as_dir/$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir" && + test -s conftest.one && test -s conftest.two && + test -s conftest.dir/conftest.one && + test -s conftest.dir/conftest.two + then + ac_cv_path_install="$as_dir/$ac_prog$ac_exec_ext -c" + break 3 + fi + fi + fi + done + done + ;; +esac + +done +IFS=$as_save_IFS + +rm -rf conftest.one conftest.two conftest.dir + +fi + if test "${ac_cv_path_install+set}" = set; then + INSTALL=$ac_cv_path_install + else + # As a last resort, use the slow shell script. Don't cache a + # value for INSTALL within a source directory, because that will + # break other packages using the cache if that directory is + # removed, or if the value is a relative name. + INSTALL=$ac_install_sh + fi +fi +{ $as_echo "$as_me:$LINENO: result: $INSTALL" >&5 +$as_echo "$INSTALL" >&6; } + +# Use test -z because SunOS4 sh mishandles braces in ${var-val}. +# It thinks the first close brace ends the variable substitution. +test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' + +test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}' + +test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' + +{ $as_echo "$as_me:$LINENO: checking whether ln -s works" >&5 +$as_echo_n "checking whether ln -s works... " >&6; } +LN_S=$as_ln_s +if test "$LN_S" = "ln -s"; then + { $as_echo "$as_me:$LINENO: result: yes" >&5 +$as_echo "yes" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no, using $LN_S" >&5 +$as_echo "no, using $LN_S" >&6; } +fi + + + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +{ $as_echo "$as_me:$LINENO: checking how to run the C preprocessor" >&5 +$as_echo_n "checking how to run the C preprocessor... " >&6; } +# On Suns, sometimes $CPP names a directory. +if test -n "$CPP" && test -d "$CPP"; then + CPP= +fi +if test -z "$CPP"; then + if test "${ac_cv_prog_CPP+set}" = set; then + $as_echo_n "(cached) " >&6 +else + # Double quotes because CPP needs to be expanded + for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp" + do + ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if { (ac_try="$ac_cpp conftest.$ac_ext" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } >/dev/null && { + test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || + test ! -s conftest.err + }; then + : +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + # Broken: fails on valid input. +continue +fi + +rm -f conftest.err conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#include +_ACEOF +if { (ac_try="$ac_cpp conftest.$ac_ext" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } >/dev/null && { + test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || + test ! -s conftest.err + }; then + # Broken: success on invalid input. +continue +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + # Passes both tests. +ac_preproc_ok=: +break +fi + +rm -f conftest.err conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.err conftest.$ac_ext +if $ac_preproc_ok; then + break +fi + + done + ac_cv_prog_CPP=$CPP + +fi + CPP=$ac_cv_prog_CPP +else + ac_cv_prog_CPP=$CPP +fi +{ $as_echo "$as_me:$LINENO: result: $CPP" >&5 +$as_echo "$CPP" >&6; } +ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if { (ac_try="$ac_cpp conftest.$ac_ext" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } >/dev/null && { + test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || + test ! -s conftest.err + }; then + : +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + # Broken: fails on valid input. +continue +fi + +rm -f conftest.err conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#include +_ACEOF +if { (ac_try="$ac_cpp conftest.$ac_ext" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } >/dev/null && { + test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || + test ! -s conftest.err + }; then + # Broken: success on invalid input. +continue +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + # Passes both tests. +ac_preproc_ok=: +break +fi + +rm -f conftest.err conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.err conftest.$ac_ext +if $ac_preproc_ok; then + : +else + { { $as_echo "$as_me:$LINENO: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +{ { $as_echo "$as_me:$LINENO: error: C preprocessor \"$CPP\" fails sanity check +See \`config.log' for more details." >&5 +$as_echo "$as_me: error: C preprocessor \"$CPP\" fails sanity check +See \`config.log' for more details." >&2;} + { (exit 1); exit 1; }; }; } +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +{ $as_echo "$as_me:$LINENO: checking for grep that handles long lines and -e" >&5 +$as_echo_n "checking for grep that handles long lines and -e... " >&6; } +if test "${ac_cv_path_GREP+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test -z "$GREP"; then + ac_path_GREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in grep ggrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext" + { test -f "$ac_path_GREP" && $as_test_x "$ac_path_GREP"; } || continue +# Check for GNU ac_path_GREP and select it if it is found. + # Check for GNU $ac_path_GREP +case `"$ac_path_GREP" --version 2>&1` in +*GNU*) + ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'GREP' >> "conftest.nl" + "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + ac_count=`expr $ac_count + 1` + if test $ac_count -gt ${ac_path_GREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_GREP="$ac_path_GREP" + ac_path_GREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_GREP_found && break 3 + done + done +done +IFS=$as_save_IFS + if test -z "$ac_cv_path_GREP"; then + { { $as_echo "$as_me:$LINENO: error: no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" >&5 +$as_echo "$as_me: error: no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" >&2;} + { (exit 1); exit 1; }; } + fi +else + ac_cv_path_GREP=$GREP +fi + +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_path_GREP" >&5 +$as_echo "$ac_cv_path_GREP" >&6; } + GREP="$ac_cv_path_GREP" + + +{ $as_echo "$as_me:$LINENO: checking for egrep" >&5 +$as_echo_n "checking for egrep... " >&6; } +if test "${ac_cv_path_EGREP+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if echo a | $GREP -E '(a|b)' >/dev/null 2>&1 + then ac_cv_path_EGREP="$GREP -E" + else + if test -z "$EGREP"; then + ac_path_EGREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in egrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext" + { test -f "$ac_path_EGREP" && $as_test_x "$ac_path_EGREP"; } || continue +# Check for GNU ac_path_EGREP and select it if it is found. + # Check for GNU $ac_path_EGREP +case `"$ac_path_EGREP" --version 2>&1` in +*GNU*) + ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'EGREP' >> "conftest.nl" + "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + ac_count=`expr $ac_count + 1` + if test $ac_count -gt ${ac_path_EGREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_EGREP="$ac_path_EGREP" + ac_path_EGREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_EGREP_found && break 3 + done + done +done +IFS=$as_save_IFS + if test -z "$ac_cv_path_EGREP"; then + { { $as_echo "$as_me:$LINENO: error: no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" >&5 +$as_echo "$as_me: error: no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" >&2;} + { (exit 1); exit 1; }; } + fi +else + ac_cv_path_EGREP=$EGREP +fi + + fi +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_path_EGREP" >&5 +$as_echo "$ac_cv_path_EGREP" >&6; } + EGREP="$ac_cv_path_EGREP" + + +if test $ac_cv_c_compiler_gnu = yes; then + { $as_echo "$as_me:$LINENO: checking whether $CC needs -traditional" >&5 +$as_echo_n "checking whether $CC needs -traditional... " >&6; } +if test "${ac_cv_prog_gcc_traditional+set}" = set; then + $as_echo_n "(cached) " >&6 +else + ac_pattern="Autoconf.*'x'" + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#include +Autoconf TIOCGETP +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "$ac_pattern" >/dev/null 2>&1; then + ac_cv_prog_gcc_traditional=yes +else + ac_cv_prog_gcc_traditional=no +fi +rm -f conftest* + + + if test $ac_cv_prog_gcc_traditional = no; then + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#include +Autoconf TCGETA +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "$ac_pattern" >/dev/null 2>&1; then + ac_cv_prog_gcc_traditional=yes +fi +rm -f conftest* + + fi +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_prog_gcc_traditional" >&5 +$as_echo "$ac_cv_prog_gcc_traditional" >&6; } + if test $ac_cv_prog_gcc_traditional = yes; then + CC="$CC -traditional" + fi +fi + +if test "$GCC" = yes ; then + CFLAGS="$CFLAGS -Wall" + # And disable the strict-aliasing optimization, since it breaks + # our sockaddr-handling code in strange ways. + CFLAGS="$CFLAGS -fno-strict-aliasing" +fi + +# Check whether --enable-gcc-warnings was given. +if test "${enable_gcc_warnings+set}" = set; then + enableval=$enable_gcc_warnings; +fi + + +# Check whether --enable-shared was given. +if test "${enable_shared+set}" = set; then + enableval=$enable_shared; p=${PACKAGE-default} + case $enableval in + yes) enable_shared=yes ;; + no) enable_shared=no ;; + *) + enable_shared=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR," + for pkg in $enableval; do + IFS="$lt_save_ifs" + if test "X$pkg" = "X$p"; then + enable_shared=yes + fi + done + IFS="$lt_save_ifs" + ;; + esac +else + enable_shared=yes +fi + + +# Check whether --enable-static was given. +if test "${enable_static+set}" = set; then + enableval=$enable_static; p=${PACKAGE-default} + case $enableval in + yes) enable_static=yes ;; + no) enable_static=no ;; + *) + enable_static=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR," + for pkg in $enableval; do + IFS="$lt_save_ifs" + if test "X$pkg" = "X$p"; then + enable_static=yes + fi + done + IFS="$lt_save_ifs" + ;; + esac +else + enable_static=yes +fi + + +# Check whether --enable-fast-install was given. +if test "${enable_fast_install+set}" = set; then + enableval=$enable_fast_install; p=${PACKAGE-default} + case $enableval in + yes) enable_fast_install=yes ;; + no) enable_fast_install=no ;; + *) + enable_fast_install=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR," + for pkg in $enableval; do + IFS="$lt_save_ifs" + if test "X$pkg" = "X$p"; then + enable_fast_install=yes + fi + done + IFS="$lt_save_ifs" + ;; + esac +else + enable_fast_install=yes +fi + + +# Make sure we can run config.sub. +$SHELL "$ac_aux_dir/config.sub" sun4 >/dev/null 2>&1 || + { { $as_echo "$as_me:$LINENO: error: cannot run $SHELL $ac_aux_dir/config.sub" >&5 +$as_echo "$as_me: error: cannot run $SHELL $ac_aux_dir/config.sub" >&2;} + { (exit 1); exit 1; }; } + +{ $as_echo "$as_me:$LINENO: checking build system type" >&5 +$as_echo_n "checking build system type... " >&6; } +if test "${ac_cv_build+set}" = set; then + $as_echo_n "(cached) " >&6 +else + ac_build_alias=$build_alias +test "x$ac_build_alias" = x && + ac_build_alias=`$SHELL "$ac_aux_dir/config.guess"` +test "x$ac_build_alias" = x && + { { $as_echo "$as_me:$LINENO: error: cannot guess build type; you must specify one" >&5 +$as_echo "$as_me: error: cannot guess build type; you must specify one" >&2;} + { (exit 1); exit 1; }; } +ac_cv_build=`$SHELL "$ac_aux_dir/config.sub" $ac_build_alias` || + { { $as_echo "$as_me:$LINENO: error: $SHELL $ac_aux_dir/config.sub $ac_build_alias failed" >&5 +$as_echo "$as_me: error: $SHELL $ac_aux_dir/config.sub $ac_build_alias failed" >&2;} + { (exit 1); exit 1; }; } + +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_build" >&5 +$as_echo "$ac_cv_build" >&6; } +case $ac_cv_build in +*-*-*) ;; +*) { { $as_echo "$as_me:$LINENO: error: invalid value of canonical build" >&5 +$as_echo "$as_me: error: invalid value of canonical build" >&2;} + { (exit 1); exit 1; }; };; +esac +build=$ac_cv_build +ac_save_IFS=$IFS; IFS='-' +set x $ac_cv_build +shift +build_cpu=$1 +build_vendor=$2 +shift; shift +# Remember, the first character of IFS is used to create $*, +# except with old shells: +build_os=$* +IFS=$ac_save_IFS +case $build_os in *\ *) build_os=`echo "$build_os" | sed 's/ /-/g'`;; esac + + +{ $as_echo "$as_me:$LINENO: checking host system type" >&5 +$as_echo_n "checking host system type... " >&6; } +if test "${ac_cv_host+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test "x$host_alias" = x; then + ac_cv_host=$ac_cv_build +else + ac_cv_host=`$SHELL "$ac_aux_dir/config.sub" $host_alias` || + { { $as_echo "$as_me:$LINENO: error: $SHELL $ac_aux_dir/config.sub $host_alias failed" >&5 +$as_echo "$as_me: error: $SHELL $ac_aux_dir/config.sub $host_alias failed" >&2;} + { (exit 1); exit 1; }; } +fi + +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_host" >&5 +$as_echo "$ac_cv_host" >&6; } +case $ac_cv_host in +*-*-*) ;; +*) { { $as_echo "$as_me:$LINENO: error: invalid value of canonical host" >&5 +$as_echo "$as_me: error: invalid value of canonical host" >&2;} + { (exit 1); exit 1; }; };; +esac +host=$ac_cv_host +ac_save_IFS=$IFS; IFS='-' +set x $ac_cv_host +shift +host_cpu=$1 +host_vendor=$2 +shift; shift +# Remember, the first character of IFS is used to create $*, +# except with old shells: +host_os=$* +IFS=$ac_save_IFS +case $host_os in *\ *) host_os=`echo "$host_os" | sed 's/ /-/g'`;; esac + + +{ $as_echo "$as_me:$LINENO: checking for a sed that does not truncate output" >&5 +$as_echo_n "checking for a sed that does not truncate output... " >&6; } +if test "${lt_cv_path_SED+set}" = set; then + $as_echo_n "(cached) " >&6 +else + # Loop through the user's path and test for sed and gsed. +# Then use that list of sed's as ones to test for truncation. +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for lt_ac_prog in sed gsed; do + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$lt_ac_prog$ac_exec_ext" && $as_test_x "$as_dir/$lt_ac_prog$ac_exec_ext"; }; then + lt_ac_sed_list="$lt_ac_sed_list $as_dir/$lt_ac_prog$ac_exec_ext" + fi + done + done +done +IFS=$as_save_IFS +lt_ac_max=0 +lt_ac_count=0 +# Add /usr/xpg4/bin/sed as it is typically found on Solaris +# along with /bin/sed that truncates output. +for lt_ac_sed in $lt_ac_sed_list /usr/xpg4/bin/sed; do + test ! -f $lt_ac_sed && continue + cat /dev/null > conftest.in + lt_ac_count=0 + echo $ECHO_N "0123456789$ECHO_C" >conftest.in + # Check for GNU sed and select it if it is found. + if "$lt_ac_sed" --version 2>&1 < /dev/null | grep 'GNU' > /dev/null; then + lt_cv_path_SED=$lt_ac_sed + break + fi + while true; do + cat conftest.in conftest.in >conftest.tmp + mv conftest.tmp conftest.in + cp conftest.in conftest.nl + echo >>conftest.nl + $lt_ac_sed -e 's/a$//' < conftest.nl >conftest.out || break + cmp -s conftest.out conftest.nl || break + # 10000 chars as input seems more than enough + test $lt_ac_count -gt 10 && break + lt_ac_count=`expr $lt_ac_count + 1` + if test $lt_ac_count -gt $lt_ac_max; then + lt_ac_max=$lt_ac_count + lt_cv_path_SED=$lt_ac_sed + fi + done +done + +fi + +SED=$lt_cv_path_SED + +{ $as_echo "$as_me:$LINENO: result: $SED" >&5 +$as_echo "$SED" >&6; } + + +# Check whether --with-gnu-ld was given. +if test "${with_gnu_ld+set}" = set; then + withval=$with_gnu_ld; test "$withval" = no || with_gnu_ld=yes +else + with_gnu_ld=no +fi + +ac_prog=ld +if test "$GCC" = yes; then + # Check if gcc -print-prog-name=ld gives a path. + { $as_echo "$as_me:$LINENO: checking for ld used by $CC" >&5 +$as_echo_n "checking for ld used by $CC... " >&6; } + case $host in + *-*-mingw*) + # gcc leaves a trailing carriage return which upsets mingw + ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; + *) + ac_prog=`($CC -print-prog-name=ld) 2>&5` ;; + esac + case $ac_prog in + # Accept absolute paths. + [\\/]* | ?:[\\/]*) + re_direlt='/[^/][^/]*/\.\./' + # Canonicalize the pathname of ld + ac_prog=`echo $ac_prog| $SED 's%\\\\%/%g'` + while echo $ac_prog | grep "$re_direlt" > /dev/null 2>&1; do + ac_prog=`echo $ac_prog| $SED "s%$re_direlt%/%"` + done + test -z "$LD" && LD="$ac_prog" + ;; + "") + # If it fails, then pretend we aren't using GCC. + ac_prog=ld + ;; + *) + # If it is relative, then search for the first ld in PATH. + with_gnu_ld=unknown + ;; + esac +elif test "$with_gnu_ld" = yes; then + { $as_echo "$as_me:$LINENO: checking for GNU ld" >&5 +$as_echo_n "checking for GNU ld... " >&6; } +else + { $as_echo "$as_me:$LINENO: checking for non-GNU ld" >&5 +$as_echo_n "checking for non-GNU ld... " >&6; } +fi +if test "${lt_cv_path_LD+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test -z "$LD"; then + lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR + for ac_dir in $PATH; do + IFS="$lt_save_ifs" + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then + lt_cv_path_LD="$ac_dir/$ac_prog" + # Check to see if the program is GNU ld. I'd rather use --version, + # but apparently some variants of GNU ld only accept -v. + # Break only if it was the GNU/non-GNU ld that we prefer. + case `"$lt_cv_path_LD" -v 2>&1 &5 +$as_echo "$LD" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi +test -z "$LD" && { { $as_echo "$as_me:$LINENO: error: no acceptable ld found in \$PATH" >&5 +$as_echo "$as_me: error: no acceptable ld found in \$PATH" >&2;} + { (exit 1); exit 1; }; } +{ $as_echo "$as_me:$LINENO: checking if the linker ($LD) is GNU ld" >&5 +$as_echo_n "checking if the linker ($LD) is GNU ld... " >&6; } +if test "${lt_cv_prog_gnu_ld+set}" = set; then + $as_echo_n "(cached) " >&6 +else + # I'd rather use --version here, but apparently some GNU lds only accept -v. +case `$LD -v 2>&1 &5 +$as_echo "$lt_cv_prog_gnu_ld" >&6; } +with_gnu_ld=$lt_cv_prog_gnu_ld + + +{ $as_echo "$as_me:$LINENO: checking for $LD option to reload object files" >&5 +$as_echo_n "checking for $LD option to reload object files... " >&6; } +if test "${lt_cv_ld_reload_flag+set}" = set; then + $as_echo_n "(cached) " >&6 +else + lt_cv_ld_reload_flag='-r' +fi +{ $as_echo "$as_me:$LINENO: result: $lt_cv_ld_reload_flag" >&5 +$as_echo "$lt_cv_ld_reload_flag" >&6; } +reload_flag=$lt_cv_ld_reload_flag +case $reload_flag in +"" | " "*) ;; +*) reload_flag=" $reload_flag" ;; +esac +reload_cmds='$LD$reload_flag -o $output$reload_objs' +case $host_os in + darwin*) + if test "$GCC" = yes; then + reload_cmds='$LTCC $LTCFLAGS -nostdlib ${wl}-r -o $output$reload_objs' + else + reload_cmds='$LD$reload_flag -o $output$reload_objs' + fi + ;; +esac + +{ $as_echo "$as_me:$LINENO: checking for BSD-compatible nm" >&5 +$as_echo_n "checking for BSD-compatible nm... " >&6; } +if test "${lt_cv_path_NM+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test -n "$NM"; then + # Let the user override the test. + lt_cv_path_NM="$NM" +else + lt_nm_to_check="${ac_tool_prefix}nm" + if test -n "$ac_tool_prefix" && test "$build" = "$host"; then + lt_nm_to_check="$lt_nm_to_check nm" + fi + for lt_tmp_nm in $lt_nm_to_check; do + lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR + for ac_dir in $PATH /usr/ccs/bin/elf /usr/ccs/bin /usr/ucb /bin; do + IFS="$lt_save_ifs" + test -z "$ac_dir" && ac_dir=. + tmp_nm="$ac_dir/$lt_tmp_nm" + if test -f "$tmp_nm" || test -f "$tmp_nm$ac_exeext" ; then + # Check to see if the nm accepts a BSD-compat flag. + # Adding the `sed 1q' prevents false positives on HP-UX, which says: + # nm: unknown option "B" ignored + # Tru64's nm complains that /dev/null is an invalid object file + case `"$tmp_nm" -B /dev/null 2>&1 | sed '1q'` in + */dev/null* | *'Invalid file or object type'*) + lt_cv_path_NM="$tmp_nm -B" + break + ;; + *) + case `"$tmp_nm" -p /dev/null 2>&1 | sed '1q'` in + */dev/null*) + lt_cv_path_NM="$tmp_nm -p" + break + ;; + *) + lt_cv_path_NM=${lt_cv_path_NM="$tmp_nm"} # keep the first match, but + continue # so that we can try to find one that supports BSD flags + ;; + esac + ;; + esac + fi + done + IFS="$lt_save_ifs" + done + test -z "$lt_cv_path_NM" && lt_cv_path_NM=nm +fi +fi +{ $as_echo "$as_me:$LINENO: result: $lt_cv_path_NM" >&5 +$as_echo "$lt_cv_path_NM" >&6; } +NM="$lt_cv_path_NM" + +{ $as_echo "$as_me:$LINENO: checking how to recognize dependent libraries" >&5 +$as_echo_n "checking how to recognize dependent libraries... " >&6; } +if test "${lt_cv_deplibs_check_method+set}" = set; then + $as_echo_n "(cached) " >&6 +else + lt_cv_file_magic_cmd='$MAGIC_CMD' +lt_cv_file_magic_test_file= +lt_cv_deplibs_check_method='unknown' +# Need to set the preceding variable on all platforms that support +# interlibrary dependencies. +# 'none' -- dependencies not supported. +# `unknown' -- same as none, but documents that we really don't know. +# 'pass_all' -- all dependencies passed with no checks. +# 'test_compile' -- check by making test program. +# 'file_magic [[regex]]' -- check by looking for files in library path +# which responds to the $file_magic_cmd with a given extended regex. +# If you have `file' or equivalent on your system and you're not sure +# whether `pass_all' will *always* work, you probably want this one. + +case $host_os in +aix[4-9]*) + lt_cv_deplibs_check_method=pass_all + ;; + +beos*) + lt_cv_deplibs_check_method=pass_all + ;; + +bsdi[45]*) + lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (shared object|dynamic lib)' + lt_cv_file_magic_cmd='/usr/bin/file -L' + lt_cv_file_magic_test_file=/shlib/libc.so + ;; + +cygwin*) + # func_win32_libid is a shell function defined in ltmain.sh + lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' + lt_cv_file_magic_cmd='func_win32_libid' + ;; + +mingw* | pw32*) + # Base MSYS/MinGW do not provide the 'file' command needed by + # func_win32_libid shell function, so use a weaker test based on 'objdump', + # unless we find 'file', for example because we are cross-compiling. + if ( file / ) >/dev/null 2>&1; then + lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' + lt_cv_file_magic_cmd='func_win32_libid' + else + lt_cv_deplibs_check_method='file_magic file format pei*-i386(.*architecture: i386)?' + lt_cv_file_magic_cmd='$OBJDUMP -f' + fi + ;; + +darwin* | rhapsody*) + lt_cv_deplibs_check_method=pass_all + ;; + +freebsd* | dragonfly*) + if echo __ELF__ | $CC -E - | grep __ELF__ > /dev/null; then + case $host_cpu in + i*86 ) + # Not sure whether the presence of OpenBSD here was a mistake. + # Let's accept both of them until this is cleared up. + lt_cv_deplibs_check_method='file_magic (FreeBSD|OpenBSD|DragonFly)/i[3-9]86 (compact )?demand paged shared library' + lt_cv_file_magic_cmd=/usr/bin/file + lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*` + ;; + esac + else + lt_cv_deplibs_check_method=pass_all + fi + ;; + +gnu*) + lt_cv_deplibs_check_method=pass_all + ;; + +hpux10.20* | hpux11*) + lt_cv_file_magic_cmd=/usr/bin/file + case $host_cpu in + ia64*) + lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF-[0-9][0-9]) shared object file - IA64' + lt_cv_file_magic_test_file=/usr/lib/hpux32/libc.so + ;; + hppa*64*) + lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF-[0-9][0-9]) shared object file - PA-RISC [0-9].[0-9]' + lt_cv_file_magic_test_file=/usr/lib/pa20_64/libc.sl + ;; + *) + lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|PA-RISC[0-9].[0-9]) shared library' + lt_cv_file_magic_test_file=/usr/lib/libc.sl + ;; + esac + ;; + +interix[3-9]*) + # PIC code is broken on Interix 3.x, that's why |\.a not |_pic\.a here + lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so|\.a)$' + ;; + +irix5* | irix6* | nonstopux*) + case $LD in + *-32|*"-32 ") libmagic=32-bit;; + *-n32|*"-n32 ") libmagic=N32;; + *-64|*"-64 ") libmagic=64-bit;; + *) libmagic=never-match;; + esac + lt_cv_deplibs_check_method=pass_all + ;; + +# This must be Linux ELF. +linux* | k*bsd*-gnu) + lt_cv_deplibs_check_method=pass_all + ;; + +netbsd*) + if echo __ELF__ | $CC -E - | grep __ELF__ > /dev/null; then + lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|_pic\.a)$' + else + lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so|_pic\.a)$' + fi + ;; + +newos6*) + lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (executable|dynamic lib)' + lt_cv_file_magic_cmd=/usr/bin/file + lt_cv_file_magic_test_file=/usr/lib/libnls.so + ;; + +nto-qnx*) + lt_cv_deplibs_check_method=unknown + ;; + +openbsd*) + if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then + lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|\.so|_pic\.a)$' + else + lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|_pic\.a)$' + fi + ;; + +osf3* | osf4* | osf5*) + lt_cv_deplibs_check_method=pass_all + ;; + +rdos*) + lt_cv_deplibs_check_method=pass_all + ;; + +solaris*) + lt_cv_deplibs_check_method=pass_all + ;; + +sysv4 | sysv4.3*) + case $host_vendor in + motorola) + lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (shared object|dynamic lib) M[0-9][0-9]* Version [0-9]' + lt_cv_file_magic_test_file=`echo /usr/lib/libc.so*` + ;; + ncr) + lt_cv_deplibs_check_method=pass_all + ;; + sequent) + lt_cv_file_magic_cmd='/bin/file' + lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [LM]SB (shared object|dynamic lib )' + ;; + sni) + lt_cv_file_magic_cmd='/bin/file' + lt_cv_deplibs_check_method="file_magic ELF [0-9][0-9]*-bit [LM]SB dynamic lib" + lt_cv_file_magic_test_file=/lib/libc.so + ;; + siemens) + lt_cv_deplibs_check_method=pass_all + ;; + pc) + lt_cv_deplibs_check_method=pass_all + ;; + esac + ;; + +sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + lt_cv_deplibs_check_method=pass_all + ;; +esac + +fi +{ $as_echo "$as_me:$LINENO: result: $lt_cv_deplibs_check_method" >&5 +$as_echo "$lt_cv_deplibs_check_method" >&6; } +file_magic_cmd=$lt_cv_file_magic_cmd +deplibs_check_method=$lt_cv_deplibs_check_method +test -z "$deplibs_check_method" && deplibs_check_method=unknown + + + + +# If no C compiler was specified, use CC. +LTCC=${LTCC-"$CC"} + +# If no C compiler flags were specified, use CFLAGS. +LTCFLAGS=${LTCFLAGS-"$CFLAGS"} + +# Allow CC to be a program name with arguments. +compiler=$CC + +# Check whether --enable-libtool-lock was given. +if test "${enable_libtool_lock+set}" = set; then + enableval=$enable_libtool_lock; +fi + +test "x$enable_libtool_lock" != xno && enable_libtool_lock=yes + +# Some flags need to be propagated to the compiler or linker for good +# libtool support. +case $host in +ia64-*-hpux*) + # Find out which ABI we are using. + echo 'int i;' > conftest.$ac_ext + if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; then + case `/usr/bin/file conftest.$ac_objext` in + *ELF-32*) + HPUX_IA64_MODE="32" + ;; + *ELF-64*) + HPUX_IA64_MODE="64" + ;; + esac + fi + rm -rf conftest* + ;; +*-*-irix6*) + # Find out which ABI we are using. + echo '#line 4804 "configure"' > conftest.$ac_ext + if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; then + if test "$lt_cv_prog_gnu_ld" = yes; then + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + LD="${LD-ld} -melf32bsmip" + ;; + *N32*) + LD="${LD-ld} -melf32bmipn32" + ;; + *64-bit*) + LD="${LD-ld} -melf64bmip" + ;; + esac + else + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + LD="${LD-ld} -32" + ;; + *N32*) + LD="${LD-ld} -n32" + ;; + *64-bit*) + LD="${LD-ld} -64" + ;; + esac + fi + fi + rm -rf conftest* + ;; + +x86_64-*kfreebsd*-gnu|x86_64-*linux*|ppc*-*linux*|powerpc*-*linux*| \ +s390*-*linux*|sparc*-*linux*) + # Find out which ABI we are using. + echo 'int i;' > conftest.$ac_ext + if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; then + case `/usr/bin/file conftest.o` in + *32-bit*) + case $host in + x86_64-*kfreebsd*-gnu) + LD="${LD-ld} -m elf_i386_fbsd" + ;; + x86_64-*linux*) + LD="${LD-ld} -m elf_i386" + ;; + ppc64-*linux*|powerpc64-*linux*) + LD="${LD-ld} -m elf32ppclinux" + ;; + s390x-*linux*) + LD="${LD-ld} -m elf_s390" + ;; + sparc64-*linux*) + LD="${LD-ld} -m elf32_sparc" + ;; + esac + ;; + *64-bit*) + case $host in + x86_64-*kfreebsd*-gnu) + LD="${LD-ld} -m elf_x86_64_fbsd" + ;; + x86_64-*linux*) + LD="${LD-ld} -m elf_x86_64" + ;; + ppc*-*linux*|powerpc*-*linux*) + LD="${LD-ld} -m elf64ppc" + ;; + s390*-*linux*) + LD="${LD-ld} -m elf64_s390" + ;; + sparc*-*linux*) + LD="${LD-ld} -m elf64_sparc" + ;; + esac + ;; + esac + fi + rm -rf conftest* + ;; + +*-*-sco3.2v5*) + # On SCO OpenServer 5, we need -belf to get full-featured binaries. + SAVE_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS -belf" + { $as_echo "$as_me:$LINENO: checking whether the C compiler needs -belf" >&5 +$as_echo_n "checking whether the C compiler needs -belf... " >&6; } +if test "${lt_cv_cc_needs_belf+set}" = set; then + $as_echo_n "(cached) " >&6 +else + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + $as_test_x conftest$ac_exeext + }; then + lt_cv_cc_needs_belf=yes +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + lt_cv_cc_needs_belf=no +fi + +rm -rf conftest.dSYM +rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ + conftest$ac_exeext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +fi +{ $as_echo "$as_me:$LINENO: result: $lt_cv_cc_needs_belf" >&5 +$as_echo "$lt_cv_cc_needs_belf" >&6; } + if test x"$lt_cv_cc_needs_belf" != x"yes"; then + # this is probably gcc 2.8.0, egcs 1.0 or newer; no need for -belf + CFLAGS="$SAVE_CFLAGS" + fi + ;; +sparc*-*solaris*) + # Find out which ABI we are using. + echo 'int i;' > conftest.$ac_ext + if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; then + case `/usr/bin/file conftest.o` in + *64-bit*) + case $lt_cv_prog_gnu_ld in + yes*) LD="${LD-ld} -m elf64_sparc" ;; + *) + if ${LD-ld} -64 -r -o conftest2.o conftest.o >/dev/null 2>&1; then + LD="${LD-ld} -64" + fi + ;; + esac + ;; + esac + fi + rm -rf conftest* + ;; + + +esac + +need_locks="$enable_libtool_lock" + + +{ $as_echo "$as_me:$LINENO: checking for ANSI C header files" >&5 +$as_echo_n "checking for ANSI C header files... " >&6; } +if test "${ac_cv_header_stdc+set}" = set; then + $as_echo_n "(cached) " >&6 +else + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#include +#include +#include +#include + +int +main () +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_cv_header_stdc=yes +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_cv_header_stdc=no +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +if test $ac_cv_header_stdc = yes; then + # SunOS 4.x string.h does not declare mem*, contrary to ANSI. + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "memchr" >/dev/null 2>&1; then + : +else + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "free" >/dev/null 2>&1; then + : +else + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. + if test "$cross_compiling" = yes; then + : +else + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#include +#include +#if ((' ' & 0x0FF) == 0x020) +# define ISLOWER(c) ('a' <= (c) && (c) <= 'z') +# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) +#else +# define ISLOWER(c) \ + (('a' <= (c) && (c) <= 'i') \ + || ('j' <= (c) && (c) <= 'r') \ + || ('s' <= (c) && (c) <= 'z')) +# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c)) +#endif + +#define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) +int +main () +{ + int i; + for (i = 0; i < 256; i++) + if (XOR (islower (i), ISLOWER (i)) + || toupper (i) != TOUPPER (i)) + return 2; + return 0; +} +_ACEOF +rm -f conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { ac_try='./conftest$ac_exeext' + { (case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; }; then + : +else + $as_echo "$as_me: program exited with status $ac_status" >&5 +$as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +( exit $ac_status ) +ac_cv_header_stdc=no +fi +rm -rf conftest.dSYM +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext conftest.$ac_objext conftest.$ac_ext +fi + + +fi +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_header_stdc" >&5 +$as_echo "$ac_cv_header_stdc" >&6; } +if test $ac_cv_header_stdc = yes; then + +cat >>confdefs.h <<\_ACEOF +#define STDC_HEADERS 1 +_ACEOF + +fi + +# On IRIX 5.3, sys/types and inttypes.h are conflicting. + + + + + + + + + +for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \ + inttypes.h stdint.h unistd.h +do +as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` +{ $as_echo "$as_me:$LINENO: checking for $ac_header" >&5 +$as_echo_n "checking for $ac_header... " >&6; } +if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then + $as_echo_n "(cached) " >&6 +else + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default + +#include <$ac_header> +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + eval "$as_ac_Header=yes" +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + eval "$as_ac_Header=no" +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +ac_res=`eval 'as_val=${'$as_ac_Header'} + $as_echo "$as_val"'` + { $as_echo "$as_me:$LINENO: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +as_val=`eval 'as_val=${'$as_ac_Header'} + $as_echo "$as_val"'` + if test "x$as_val" = x""yes; then + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 +_ACEOF + +fi + +done + + + +for ac_header in dlfcn.h +do +as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` +if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then + { $as_echo "$as_me:$LINENO: checking for $ac_header" >&5 +$as_echo_n "checking for $ac_header... " >&6; } +if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then + $as_echo_n "(cached) " >&6 +fi +ac_res=`eval 'as_val=${'$as_ac_Header'} + $as_echo "$as_val"'` + { $as_echo "$as_me:$LINENO: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +else + # Is the header compilable? +{ $as_echo "$as_me:$LINENO: checking $ac_header usability" >&5 +$as_echo_n "checking $ac_header usability... " >&6; } +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +#include <$ac_header> +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_header_compiler=yes +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_header_compiler=no +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +{ $as_echo "$as_me:$LINENO: result: $ac_header_compiler" >&5 +$as_echo "$ac_header_compiler" >&6; } + +# Is the header present? +{ $as_echo "$as_me:$LINENO: checking $ac_header presence" >&5 +$as_echo_n "checking $ac_header presence... " >&6; } +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#include <$ac_header> +_ACEOF +if { (ac_try="$ac_cpp conftest.$ac_ext" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } >/dev/null && { + test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || + test ! -s conftest.err + }; then + ac_header_preproc=yes +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_header_preproc=no +fi + +rm -f conftest.err conftest.$ac_ext +{ $as_echo "$as_me:$LINENO: result: $ac_header_preproc" >&5 +$as_echo "$ac_header_preproc" >&6; } + +# So? What about this header? +case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in + yes:no: ) + { $as_echo "$as_me:$LINENO: WARNING: $ac_header: accepted by the compiler, rejected by the preprocessor!" >&5 +$as_echo "$as_me: WARNING: $ac_header: accepted by the compiler, rejected by the preprocessor!" >&2;} + { $as_echo "$as_me:$LINENO: WARNING: $ac_header: proceeding with the compiler's result" >&5 +$as_echo "$as_me: WARNING: $ac_header: proceeding with the compiler's result" >&2;} + ac_header_preproc=yes + ;; + no:yes:* ) + { $as_echo "$as_me:$LINENO: WARNING: $ac_header: present but cannot be compiled" >&5 +$as_echo "$as_me: WARNING: $ac_header: present but cannot be compiled" >&2;} + { $as_echo "$as_me:$LINENO: WARNING: $ac_header: check for missing prerequisite headers?" >&5 +$as_echo "$as_me: WARNING: $ac_header: check for missing prerequisite headers?" >&2;} + { $as_echo "$as_me:$LINENO: WARNING: $ac_header: see the Autoconf documentation" >&5 +$as_echo "$as_me: WARNING: $ac_header: see the Autoconf documentation" >&2;} + { $as_echo "$as_me:$LINENO: WARNING: $ac_header: section \"Present But Cannot Be Compiled\"" >&5 +$as_echo "$as_me: WARNING: $ac_header: section \"Present But Cannot Be Compiled\"" >&2;} + { $as_echo "$as_me:$LINENO: WARNING: $ac_header: proceeding with the preprocessor's result" >&5 +$as_echo "$as_me: WARNING: $ac_header: proceeding with the preprocessor's result" >&2;} + { $as_echo "$as_me:$LINENO: WARNING: $ac_header: in the future, the compiler will take precedence" >&5 +$as_echo "$as_me: WARNING: $ac_header: in the future, the compiler will take precedence" >&2;} + + ;; +esac +{ $as_echo "$as_me:$LINENO: checking for $ac_header" >&5 +$as_echo_n "checking for $ac_header... " >&6; } +if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then + $as_echo_n "(cached) " >&6 +else + eval "$as_ac_Header=\$ac_header_preproc" +fi +ac_res=`eval 'as_val=${'$as_ac_Header'} + $as_echo "$as_val"'` + { $as_echo "$as_me:$LINENO: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + +fi +as_val=`eval 'as_val=${'$as_ac_Header'} + $as_echo "$as_val"'` + if test "x$as_val" = x""yes; then + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 +_ACEOF + +fi + +done + +ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu +if test -z "$CXX"; then + if test -n "$CCC"; then + CXX=$CCC + else + if test -n "$ac_tool_prefix"; then + for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:$LINENO: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_CXX+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test -n "$CXX"; then + ac_cv_prog_CXX="$CXX" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_CXX="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done +done +IFS=$as_save_IFS + +fi +fi +CXX=$ac_cv_prog_CXX +if test -n "$CXX"; then + { $as_echo "$as_me:$LINENO: result: $CXX" >&5 +$as_echo "$CXX" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$CXX" && break + done +fi +if test -z "$CXX"; then + ac_ct_CXX=$CXX + for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:$LINENO: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_ac_ct_CXX+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CXX"; then + ac_cv_prog_ac_ct_CXX="$ac_ct_CXX" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_ac_ct_CXX="$ac_prog" + $as_echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done +done +IFS=$as_save_IFS + +fi +fi +ac_ct_CXX=$ac_cv_prog_ac_ct_CXX +if test -n "$ac_ct_CXX"; then + { $as_echo "$as_me:$LINENO: result: $ac_ct_CXX" >&5 +$as_echo "$ac_ct_CXX" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_CXX" && break +done + + if test "x$ac_ct_CXX" = x; then + CXX="g++" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:$LINENO: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CXX=$ac_ct_CXX + fi +fi + + fi +fi +# Provide some information about the compiler. +$as_echo "$as_me:$LINENO: checking for C++ compiler version" >&5 +set X $ac_compile +ac_compiler=$2 +{ (ac_try="$ac_compiler --version >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compiler --version >&5") 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } +{ (ac_try="$ac_compiler -v >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compiler -v >&5") 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } +{ (ac_try="$ac_compiler -V >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compiler -V >&5") 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } + +{ $as_echo "$as_me:$LINENO: checking whether we are using the GNU C++ compiler" >&5 +$as_echo_n "checking whether we are using the GNU C++ compiler... " >&6; } +if test "${ac_cv_cxx_compiler_gnu+set}" = set; then + $as_echo_n "(cached) " >&6 +else + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +int +main () +{ +#ifndef __GNUC__ + choke me +#endif + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_cxx_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_compiler_gnu=yes +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_compiler_gnu=no +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +ac_cv_cxx_compiler_gnu=$ac_compiler_gnu + +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_cxx_compiler_gnu" >&5 +$as_echo "$ac_cv_cxx_compiler_gnu" >&6; } +if test $ac_compiler_gnu = yes; then + GXX=yes +else + GXX= +fi +ac_test_CXXFLAGS=${CXXFLAGS+set} +ac_save_CXXFLAGS=$CXXFLAGS +{ $as_echo "$as_me:$LINENO: checking whether $CXX accepts -g" >&5 +$as_echo_n "checking whether $CXX accepts -g... " >&6; } +if test "${ac_cv_prog_cxx_g+set}" = set; then + $as_echo_n "(cached) " >&6 +else + ac_save_cxx_werror_flag=$ac_cxx_werror_flag + ac_cxx_werror_flag=yes + ac_cv_prog_cxx_g=no + CXXFLAGS="-g" + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_cxx_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_cv_prog_cxx_g=yes +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + CXXFLAGS="" + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_cxx_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + : +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_cxx_werror_flag=$ac_save_cxx_werror_flag + CXXFLAGS="-g" + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_cxx_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_cv_prog_cxx_g=yes +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_cxx_werror_flag=$ac_save_cxx_werror_flag +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_prog_cxx_g" >&5 +$as_echo "$ac_cv_prog_cxx_g" >&6; } +if test "$ac_test_CXXFLAGS" = set; then + CXXFLAGS=$ac_save_CXXFLAGS +elif test $ac_cv_prog_cxx_g = yes; then + if test "$GXX" = yes; then + CXXFLAGS="-g -O2" + else + CXXFLAGS="-g" + fi +else + if test "$GXX" = yes; then + CXXFLAGS="-O2" + else + CXXFLAGS= + fi +fi +ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + +depcc="$CXX" am_compiler_list= + +{ $as_echo "$as_me:$LINENO: checking dependency style of $depcc" >&5 +$as_echo_n "checking dependency style of $depcc... " >&6; } +if test "${am_cv_CXX_dependencies_compiler_type+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then + # We make a subdir and do the tests there. Otherwise we can end up + # making bogus files that we don't know about and never remove. For + # instance it was reported that on HP-UX the gcc test will end up + # making a dummy file named `D' -- because `-MD' means `put the output + # in D'. + mkdir conftest.dir + # Copy depcomp to subdir because otherwise we won't find it if we're + # using a relative directory. + cp "$am_depcomp" conftest.dir + cd conftest.dir + # We will build objects and dependencies in a subdirectory because + # it helps to detect inapplicable dependency modes. For instance + # both Tru64's cc and ICC support -MD to output dependencies as a + # side effect of compilation, but ICC will put the dependencies in + # the current directory while Tru64 will put them in the object + # directory. + mkdir sub + + am_cv_CXX_dependencies_compiler_type=none + if test "$am_compiler_list" = ""; then + am_compiler_list=`sed -n 's/^#*\([a-zA-Z0-9]*\))$/\1/p' < ./depcomp` + fi + for depmode in $am_compiler_list; do + # Setup a source with many dependencies, because some compilers + # like to wrap large dependency lists on column 80 (with \), and + # we should not choose a depcomp mode which is confused by this. + # + # We need to recreate these files for each test, as the compiler may + # overwrite some of them when testing with obscure command lines. + # This happens at least with the AIX C compiler. + : > sub/conftest.c + for i in 1 2 3 4 5 6; do + echo '#include "conftst'$i'.h"' >> sub/conftest.c + # Using `: > sub/conftst$i.h' creates only sub/conftst1.h with + # Solaris 8's {/usr,}/bin/sh. + touch sub/conftst$i.h + done + echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf + + case $depmode in + nosideeffect) + # after this tag, mechanisms are not by side-effect, so they'll + # only be used when explicitly requested + if test "x$enable_dependency_tracking" = xyes; then + continue + else + break + fi + ;; + none) break ;; + esac + # We check with `-c' and `-o' for the sake of the "dashmstdout" + # mode. It turns out that the SunPro C++ compiler does not properly + # handle `-M -o', and we need to detect this. + if depmode=$depmode \ + source=sub/conftest.c object=sub/conftest.${OBJEXT-o} \ + depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ + $SHELL ./depcomp $depcc -c -o sub/conftest.${OBJEXT-o} sub/conftest.c \ + >/dev/null 2>conftest.err && + grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && + grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && + grep sub/conftest.${OBJEXT-o} sub/conftest.Po > /dev/null 2>&1 && + ${MAKE-make} -s -f confmf > /dev/null 2>&1; then + # icc doesn't choke on unknown options, it will just issue warnings + # or remarks (even with -Werror). So we grep stderr for any message + # that says an option was ignored or not supported. + # When given -MP, icc 7.0 and 7.1 complain thusly: + # icc: Command line warning: ignoring option '-M'; no argument required + # The diagnosis changed in icc 8.0: + # icc: Command line remark: option '-MP' not supported + if (grep 'ignoring option' conftest.err || + grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else + am_cv_CXX_dependencies_compiler_type=$depmode + break + fi + fi + done + + cd .. + rm -rf conftest.dir +else + am_cv_CXX_dependencies_compiler_type=none +fi + +fi +{ $as_echo "$as_me:$LINENO: result: $am_cv_CXX_dependencies_compiler_type" >&5 +$as_echo "$am_cv_CXX_dependencies_compiler_type" >&6; } +CXXDEPMODE=depmode=$am_cv_CXX_dependencies_compiler_type + + if + test "x$enable_dependency_tracking" != xno \ + && test "$am_cv_CXX_dependencies_compiler_type" = gcc3; then + am__fastdepCXX_TRUE= + am__fastdepCXX_FALSE='#' +else + am__fastdepCXX_TRUE='#' + am__fastdepCXX_FALSE= +fi + + + + +if test -n "$CXX" && ( test "X$CXX" != "Xno" && + ( (test "X$CXX" = "Xg++" && `g++ -v >/dev/null 2>&1` ) || + (test "X$CXX" != "Xg++"))) ; then + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu +{ $as_echo "$as_me:$LINENO: checking how to run the C++ preprocessor" >&5 +$as_echo_n "checking how to run the C++ preprocessor... " >&6; } +if test -z "$CXXCPP"; then + if test "${ac_cv_prog_CXXCPP+set}" = set; then + $as_echo_n "(cached) " >&6 +else + # Double quotes because CXXCPP needs to be expanded + for CXXCPP in "$CXX -E" "/lib/cpp" + do + ac_preproc_ok=false +for ac_cxx_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if { (ac_try="$ac_cpp conftest.$ac_ext" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } >/dev/null && { + test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || + test ! -s conftest.err + }; then + : +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + # Broken: fails on valid input. +continue +fi + +rm -f conftest.err conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#include +_ACEOF +if { (ac_try="$ac_cpp conftest.$ac_ext" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } >/dev/null && { + test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || + test ! -s conftest.err + }; then + # Broken: success on invalid input. +continue +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + # Passes both tests. +ac_preproc_ok=: +break +fi + +rm -f conftest.err conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.err conftest.$ac_ext +if $ac_preproc_ok; then + break +fi + + done + ac_cv_prog_CXXCPP=$CXXCPP + +fi + CXXCPP=$ac_cv_prog_CXXCPP +else + ac_cv_prog_CXXCPP=$CXXCPP +fi +{ $as_echo "$as_me:$LINENO: result: $CXXCPP" >&5 +$as_echo "$CXXCPP" >&6; } +ac_preproc_ok=false +for ac_cxx_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if { (ac_try="$ac_cpp conftest.$ac_ext" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } >/dev/null && { + test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || + test ! -s conftest.err + }; then + : +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + # Broken: fails on valid input. +continue +fi + +rm -f conftest.err conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#include +_ACEOF +if { (ac_try="$ac_cpp conftest.$ac_ext" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } >/dev/null && { + test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || + test ! -s conftest.err + }; then + # Broken: success on invalid input. +continue +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + # Passes both tests. +ac_preproc_ok=: +break +fi + +rm -f conftest.err conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.err conftest.$ac_ext +if $ac_preproc_ok; then + : +else + { { $as_echo "$as_me:$LINENO: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +{ { $as_echo "$as_me:$LINENO: error: C++ preprocessor \"$CXXCPP\" fails sanity check +See \`config.log' for more details." >&5 +$as_echo "$as_me: error: C++ preprocessor \"$CXXCPP\" fails sanity check +See \`config.log' for more details." >&2;} + { (exit 1); exit 1; }; }; } +fi + +ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + +fi + + +ac_ext=f +ac_compile='$F77 -c $FFLAGS conftest.$ac_ext >&5' +ac_link='$F77 -o conftest$ac_exeext $FFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_f77_compiler_gnu +if test -n "$ac_tool_prefix"; then + for ac_prog in g77 xlf f77 frt pgf77 cf77 fort77 fl32 af77 xlf90 f90 pgf90 pghpf epcf90 gfortran g95 xlf95 f95 fort ifort ifc efc pgf95 lf95 ftn + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:$LINENO: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_F77+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test -n "$F77"; then + ac_cv_prog_F77="$F77" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_F77="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done +done +IFS=$as_save_IFS + +fi +fi +F77=$ac_cv_prog_F77 +if test -n "$F77"; then + { $as_echo "$as_me:$LINENO: result: $F77" >&5 +$as_echo "$F77" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$F77" && break + done +fi +if test -z "$F77"; then + ac_ct_F77=$F77 + for ac_prog in g77 xlf f77 frt pgf77 cf77 fort77 fl32 af77 xlf90 f90 pgf90 pghpf epcf90 gfortran g95 xlf95 f95 fort ifort ifc efc pgf95 lf95 ftn +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:$LINENO: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_ac_ct_F77+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_F77"; then + ac_cv_prog_ac_ct_F77="$ac_ct_F77" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_ac_ct_F77="$ac_prog" + $as_echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done +done +IFS=$as_save_IFS + +fi +fi +ac_ct_F77=$ac_cv_prog_ac_ct_F77 +if test -n "$ac_ct_F77"; then + { $as_echo "$as_me:$LINENO: result: $ac_ct_F77" >&5 +$as_echo "$ac_ct_F77" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_F77" && break +done + + if test "x$ac_ct_F77" = x; then + F77="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:$LINENO: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + F77=$ac_ct_F77 + fi +fi + + +# Provide some information about the compiler. +$as_echo "$as_me:$LINENO: checking for Fortran 77 compiler version" >&5 +set X $ac_compile +ac_compiler=$2 +{ (ac_try="$ac_compiler --version >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compiler --version >&5") 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } +{ (ac_try="$ac_compiler -v >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compiler -v >&5") 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } +{ (ac_try="$ac_compiler -V >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compiler -V >&5") 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } +rm -f a.out + +# If we don't use `.F' as extension, the preprocessor is not run on the +# input file. (Note that this only needs to work for GNU compilers.) +ac_save_ext=$ac_ext +ac_ext=F +{ $as_echo "$as_me:$LINENO: checking whether we are using the GNU Fortran 77 compiler" >&5 +$as_echo_n "checking whether we are using the GNU Fortran 77 compiler... " >&6; } +if test "${ac_cv_f77_compiler_gnu+set}" = set; then + $as_echo_n "(cached) " >&6 +else + cat >conftest.$ac_ext <<_ACEOF + program main +#ifndef __GNUC__ + choke me +#endif + + end +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_f77_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_compiler_gnu=yes +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_compiler_gnu=no +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +ac_cv_f77_compiler_gnu=$ac_compiler_gnu + +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_f77_compiler_gnu" >&5 +$as_echo "$ac_cv_f77_compiler_gnu" >&6; } +ac_ext=$ac_save_ext +ac_test_FFLAGS=${FFLAGS+set} +ac_save_FFLAGS=$FFLAGS +FFLAGS= +{ $as_echo "$as_me:$LINENO: checking whether $F77 accepts -g" >&5 +$as_echo_n "checking whether $F77 accepts -g... " >&6; } +if test "${ac_cv_prog_f77_g+set}" = set; then + $as_echo_n "(cached) " >&6 +else + FFLAGS=-g +cat >conftest.$ac_ext <<_ACEOF + program main + + end +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_f77_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_cv_prog_f77_g=yes +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_cv_prog_f77_g=no +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_prog_f77_g" >&5 +$as_echo "$ac_cv_prog_f77_g" >&6; } +if test "$ac_test_FFLAGS" = set; then + FFLAGS=$ac_save_FFLAGS +elif test $ac_cv_prog_f77_g = yes; then + if test "x$ac_cv_f77_compiler_gnu" = xyes; then + FFLAGS="-g -O2" + else + FFLAGS="-g" + fi +else + if test "x$ac_cv_f77_compiler_gnu" = xyes; then + FFLAGS="-O2" + else + FFLAGS= + fi +fi + +if test $ac_compiler_gnu = yes; then + G77=yes +else + G77= +fi +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + +# Autoconf 2.13's AC_OBJEXT and AC_EXEEXT macros only works for C compilers! +# find the maximum length of command line arguments +{ $as_echo "$as_me:$LINENO: checking the maximum length of command line arguments" >&5 +$as_echo_n "checking the maximum length of command line arguments... " >&6; } +if test "${lt_cv_sys_max_cmd_len+set}" = set; then + $as_echo_n "(cached) " >&6 +else + i=0 + teststring="ABCD" + + case $build_os in + msdosdjgpp*) + # On DJGPP, this test can blow up pretty badly due to problems in libc + # (any single argument exceeding 2000 bytes causes a buffer overrun + # during glob expansion). Even if it were fixed, the result of this + # check would be larger than it should be. + lt_cv_sys_max_cmd_len=12288; # 12K is about right + ;; + + gnu*) + # Under GNU Hurd, this test is not required because there is + # no limit to the length of command line arguments. + # Libtool will interpret -1 as no limit whatsoever + lt_cv_sys_max_cmd_len=-1; + ;; + + cygwin* | mingw*) + # On Win9x/ME, this test blows up -- it succeeds, but takes + # about 5 minutes as the teststring grows exponentially. + # Worse, since 9x/ME are not pre-emptively multitasking, + # you end up with a "frozen" computer, even though with patience + # the test eventually succeeds (with a max line length of 256k). + # Instead, let's just punt: use the minimum linelength reported by + # all of the supported platforms: 8192 (on NT/2K/XP). + lt_cv_sys_max_cmd_len=8192; + ;; + + amigaos*) + # On AmigaOS with pdksh, this test takes hours, literally. + # So we just punt and use a minimum line length of 8192. + lt_cv_sys_max_cmd_len=8192; + ;; + + netbsd* | freebsd* | openbsd* | darwin* | dragonfly*) + # This has been around since 386BSD, at least. Likely further. + if test -x /sbin/sysctl; then + lt_cv_sys_max_cmd_len=`/sbin/sysctl -n kern.argmax` + elif test -x /usr/sbin/sysctl; then + lt_cv_sys_max_cmd_len=`/usr/sbin/sysctl -n kern.argmax` + else + lt_cv_sys_max_cmd_len=65536 # usable default for all BSDs + fi + # And add a safety zone + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` + ;; + + interix*) + # We know the value 262144 and hardcode it with a safety zone (like BSD) + lt_cv_sys_max_cmd_len=196608 + ;; + + osf*) + # Dr. Hans Ekkehard Plesser reports seeing a kernel panic running configure + # due to this test when exec_disable_arg_limit is 1 on Tru64. It is not + # nice to cause kernel panics so lets avoid the loop below. + # First set a reasonable default. + lt_cv_sys_max_cmd_len=16384 + # + if test -x /sbin/sysconfig; then + case `/sbin/sysconfig -q proc exec_disable_arg_limit` in + *1*) lt_cv_sys_max_cmd_len=-1 ;; + esac + fi + ;; + sco3.2v5*) + lt_cv_sys_max_cmd_len=102400 + ;; + sysv5* | sco5v6* | sysv4.2uw2*) + kargmax=`grep ARG_MAX /etc/conf/cf.d/stune 2>/dev/null` + if test -n "$kargmax"; then + lt_cv_sys_max_cmd_len=`echo $kargmax | sed 's/.*[ ]//'` + else + lt_cv_sys_max_cmd_len=32768 + fi + ;; + *) + lt_cv_sys_max_cmd_len=`(getconf ARG_MAX) 2> /dev/null` + if test -n "$lt_cv_sys_max_cmd_len"; then + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` + else + SHELL=${SHELL-${CONFIG_SHELL-/bin/sh}} + while (test "X"`$SHELL $0 --fallback-echo "X$teststring" 2>/dev/null` \ + = "XX$teststring") >/dev/null 2>&1 && + new_result=`expr "X$teststring" : ".*" 2>&1` && + lt_cv_sys_max_cmd_len=$new_result && + test $i != 17 # 1/2 MB should be enough + do + i=`expr $i + 1` + teststring=$teststring$teststring + done + teststring= + # Add a significant safety factor because C++ compilers can tack on massive + # amounts of additional arguments before passing them to the linker. + # It appears as though 1/2 is a usable value. + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 2` + fi + ;; + esac + +fi + +if test -n $lt_cv_sys_max_cmd_len ; then + { $as_echo "$as_me:$LINENO: result: $lt_cv_sys_max_cmd_len" >&5 +$as_echo "$lt_cv_sys_max_cmd_len" >&6; } +else + { $as_echo "$as_me:$LINENO: result: none" >&5 +$as_echo "none" >&6; } +fi + + + + + +# Check for command to grab the raw symbol name followed by C symbol from nm. +{ $as_echo "$as_me:$LINENO: checking command to parse $NM output from $compiler object" >&5 +$as_echo_n "checking command to parse $NM output from $compiler object... " >&6; } +if test "${lt_cv_sys_global_symbol_pipe+set}" = set; then + $as_echo_n "(cached) " >&6 +else + +# These are sane defaults that work on at least a few old systems. +# [They come from Ultrix. What could be older than Ultrix?!! ;)] + +# Character class describing NM global symbol codes. +symcode='[BCDEGRST]' + +# Regexp to match symbols that can be accessed directly from C. +sympat='\([_A-Za-z][_A-Za-z0-9]*\)' + +# Transform an extracted symbol line into a proper C declaration +lt_cv_sys_global_symbol_to_cdecl="sed -n -e 's/^. .* \(.*\)$/extern int \1;/p'" + +# Transform an extracted symbol line into symbol name and symbol address +lt_cv_sys_global_symbol_to_c_name_address="sed -n -e 's/^: \([^ ]*\) $/ {\\\"\1\\\", (lt_ptr) 0},/p' -e 's/^$symcode \([^ ]*\) \([^ ]*\)$/ {\"\2\", (lt_ptr) \&\2},/p'" + +# Define system-specific variables. +case $host_os in +aix*) + symcode='[BCDT]' + ;; +cygwin* | mingw* | pw32*) + symcode='[ABCDGISTW]' + ;; +hpux*) # Its linker distinguishes data from code symbols + if test "$host_cpu" = ia64; then + symcode='[ABCDEGRST]' + fi + lt_cv_sys_global_symbol_to_cdecl="sed -n -e 's/^T .* \(.*\)$/extern int \1();/p' -e 's/^$symcode* .* \(.*\)$/extern char \1;/p'" + lt_cv_sys_global_symbol_to_c_name_address="sed -n -e 's/^: \([^ ]*\) $/ {\\\"\1\\\", (lt_ptr) 0},/p' -e 's/^$symcode* \([^ ]*\) \([^ ]*\)$/ {\"\2\", (lt_ptr) \&\2},/p'" + ;; +linux* | k*bsd*-gnu) + if test "$host_cpu" = ia64; then + symcode='[ABCDGIRSTW]' + lt_cv_sys_global_symbol_to_cdecl="sed -n -e 's/^T .* \(.*\)$/extern int \1();/p' -e 's/^$symcode* .* \(.*\)$/extern char \1;/p'" + lt_cv_sys_global_symbol_to_c_name_address="sed -n -e 's/^: \([^ ]*\) $/ {\\\"\1\\\", (lt_ptr) 0},/p' -e 's/^$symcode* \([^ ]*\) \([^ ]*\)$/ {\"\2\", (lt_ptr) \&\2},/p'" + fi + ;; +irix* | nonstopux*) + symcode='[BCDEGRST]' + ;; +osf*) + symcode='[BCDEGQRST]' + ;; +solaris*) + symcode='[BDRT]' + ;; +sco3.2v5*) + symcode='[DT]' + ;; +sysv4.2uw2*) + symcode='[DT]' + ;; +sysv5* | sco5v6* | unixware* | OpenUNIX*) + symcode='[ABDT]' + ;; +sysv4) + symcode='[DFNSTU]' + ;; +esac + +# Handle CRLF in mingw tool chain +opt_cr= +case $build_os in +mingw*) + opt_cr=`echo 'x\{0,1\}' | tr x '\015'` # option cr in regexp + ;; +esac + +# If we're using GNU nm, then use its standard symbol codes. +case `$NM -V 2>&1` in +*GNU* | *'with BFD'*) + symcode='[ABCDGIRSTW]' ;; +esac + +# Try without a prefix undercore, then with it. +for ac_symprfx in "" "_"; do + + # Transform symcode, sympat, and symprfx into a raw symbol and a C symbol. + symxfrm="\\1 $ac_symprfx\\2 \\2" + + # Write the raw and C identifiers. + lt_cv_sys_global_symbol_pipe="sed -n -e 's/^.*[ ]\($symcode$symcode*\)[ ][ ]*$ac_symprfx$sympat$opt_cr$/$symxfrm/p'" + + # Check to see that the pipe works correctly. + pipe_works=no + + rm -f conftest* + cat > conftest.$ac_ext <&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; then + # Now try to grab the symbols. + nlist=conftest.nm + if { (eval echo "$as_me:$LINENO: \"$NM conftest.$ac_objext \| $lt_cv_sys_global_symbol_pipe \> $nlist\"") >&5 + (eval $NM conftest.$ac_objext \| $lt_cv_sys_global_symbol_pipe \> $nlist) 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && test -s "$nlist"; then + # Try sorting and uniquifying the output. + if sort "$nlist" | uniq > "$nlist"T; then + mv -f "$nlist"T "$nlist" + else + rm -f "$nlist"T + fi + + # Make sure that we snagged all the symbols we need. + if grep ' nm_test_var$' "$nlist" >/dev/null; then + if grep ' nm_test_func$' "$nlist" >/dev/null; then + cat < conftest.$ac_ext +#ifdef __cplusplus +extern "C" { +#endif + +EOF + # Now generate the symbol file. + eval "$lt_cv_sys_global_symbol_to_cdecl"' < "$nlist" | grep -v main >> conftest.$ac_ext' + + cat <> conftest.$ac_ext +#if defined (__STDC__) && __STDC__ +# define lt_ptr_t void * +#else +# define lt_ptr_t char * +# define const +#endif + +/* The mapping between symbol names and symbols. */ +const struct { + const char *name; + lt_ptr_t address; +} +lt_preloaded_symbols[] = +{ +EOF + $SED "s/^$symcode$symcode* \(.*\) \(.*\)$/ {\"\2\", (lt_ptr_t) \&\2},/" < "$nlist" | grep -v main >> conftest.$ac_ext + cat <<\EOF >> conftest.$ac_ext + {0, (lt_ptr_t) 0} +}; + +#ifdef __cplusplus +} +#endif +EOF + # Now try linking the two files. + mv conftest.$ac_objext conftstm.$ac_objext + lt_save_LIBS="$LIBS" + lt_save_CFLAGS="$CFLAGS" + LIBS="conftstm.$ac_objext" + CFLAGS="$CFLAGS$lt_prog_compiler_no_builtin_flag" + if { (eval echo "$as_me:$LINENO: \"$ac_link\"") >&5 + (eval $ac_link) 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && test -s conftest${ac_exeext}; then + pipe_works=yes + fi + LIBS="$lt_save_LIBS" + CFLAGS="$lt_save_CFLAGS" + else + echo "cannot find nm_test_func in $nlist" >&5 + fi + else + echo "cannot find nm_test_var in $nlist" >&5 + fi + else + echo "cannot run $lt_cv_sys_global_symbol_pipe" >&5 + fi + else + echo "$progname: failed program was:" >&5 + cat conftest.$ac_ext >&5 + fi + rm -rf conftest* conftst* + + # Do not use the global_symbol_pipe unless it works. + if test "$pipe_works" = yes; then + break + else + lt_cv_sys_global_symbol_pipe= + fi +done + +fi + +if test -z "$lt_cv_sys_global_symbol_pipe"; then + lt_cv_sys_global_symbol_to_cdecl= +fi +if test -z "$lt_cv_sys_global_symbol_pipe$lt_cv_sys_global_symbol_to_cdecl"; then + { $as_echo "$as_me:$LINENO: result: failed" >&5 +$as_echo "failed" >&6; } +else + { $as_echo "$as_me:$LINENO: result: ok" >&5 +$as_echo "ok" >&6; } +fi + +{ $as_echo "$as_me:$LINENO: checking for objdir" >&5 +$as_echo_n "checking for objdir... " >&6; } +if test "${lt_cv_objdir+set}" = set; then + $as_echo_n "(cached) " >&6 +else + rm -f .libs 2>/dev/null +mkdir .libs 2>/dev/null +if test -d .libs; then + lt_cv_objdir=.libs +else + # MS-DOS does not allow filenames that begin with a dot. + lt_cv_objdir=_libs +fi +rmdir .libs 2>/dev/null +fi +{ $as_echo "$as_me:$LINENO: result: $lt_cv_objdir" >&5 +$as_echo "$lt_cv_objdir" >&6; } +objdir=$lt_cv_objdir + + + + + +case $host_os in +aix3*) + # AIX sometimes has problems with the GCC collect2 program. For some + # reason, if we set the COLLECT_NAMES environment variable, the problems + # vanish in a puff of smoke. + if test "X${COLLECT_NAMES+set}" != Xset; then + COLLECT_NAMES= + export COLLECT_NAMES + fi + ;; +esac + +# Sed substitution that helps us do robust quoting. It backslashifies +# metacharacters that are still active within double-quoted strings. +Xsed='sed -e 1s/^X//' +sed_quote_subst='s/\([\\"\\`$\\\\]\)/\\\1/g' + +# Same as above, but do not quote variable references. +double_quote_subst='s/\([\\"\\`\\\\]\)/\\\1/g' + +# Sed substitution to delay expansion of an escaped shell variable in a +# double_quote_subst'ed string. +delay_variable_subst='s/\\\\\\\\\\\$/\\\\\\$/g' + +# Sed substitution to avoid accidental globbing in evaled expressions +no_glob_subst='s/\*/\\\*/g' + +# Constants: +rm="rm -f" + +# Global variables: +default_ofile=libtool +can_build_shared=yes + +# All known linkers require a `.a' archive for static linking (except MSVC, +# which needs '.lib'). +libext=a +ltmain="$ac_aux_dir/ltmain.sh" +ofile="$default_ofile" +with_gnu_ld="$lt_cv_prog_gnu_ld" + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}ar", so it can be a program name with args. +set dummy ${ac_tool_prefix}ar; ac_word=$2 +{ $as_echo "$as_me:$LINENO: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_AR+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test -n "$AR"; then + ac_cv_prog_AR="$AR" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_AR="${ac_tool_prefix}ar" + $as_echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done +done +IFS=$as_save_IFS + +fi +fi +AR=$ac_cv_prog_AR +if test -n "$AR"; then + { $as_echo "$as_me:$LINENO: result: $AR" >&5 +$as_echo "$AR" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_AR"; then + ac_ct_AR=$AR + # Extract the first word of "ar", so it can be a program name with args. +set dummy ar; ac_word=$2 +{ $as_echo "$as_me:$LINENO: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_ac_ct_AR+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_AR"; then + ac_cv_prog_ac_ct_AR="$ac_ct_AR" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_ac_ct_AR="ar" + $as_echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done +done +IFS=$as_save_IFS + +fi +fi +ac_ct_AR=$ac_cv_prog_ac_ct_AR +if test -n "$ac_ct_AR"; then + { $as_echo "$as_me:$LINENO: result: $ac_ct_AR" >&5 +$as_echo "$ac_ct_AR" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_AR" = x; then + AR="false" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:$LINENO: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + AR=$ac_ct_AR + fi +else + AR="$ac_cv_prog_AR" +fi + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}ranlib", so it can be a program name with args. +set dummy ${ac_tool_prefix}ranlib; ac_word=$2 +{ $as_echo "$as_me:$LINENO: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_RANLIB+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test -n "$RANLIB"; then + ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_RANLIB="${ac_tool_prefix}ranlib" + $as_echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done +done +IFS=$as_save_IFS + +fi +fi +RANLIB=$ac_cv_prog_RANLIB +if test -n "$RANLIB"; then + { $as_echo "$as_me:$LINENO: result: $RANLIB" >&5 +$as_echo "$RANLIB" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_RANLIB"; then + ac_ct_RANLIB=$RANLIB + # Extract the first word of "ranlib", so it can be a program name with args. +set dummy ranlib; ac_word=$2 +{ $as_echo "$as_me:$LINENO: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_ac_ct_RANLIB+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_RANLIB"; then + ac_cv_prog_ac_ct_RANLIB="$ac_ct_RANLIB" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_ac_ct_RANLIB="ranlib" + $as_echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done +done +IFS=$as_save_IFS + +fi +fi +ac_ct_RANLIB=$ac_cv_prog_ac_ct_RANLIB +if test -n "$ac_ct_RANLIB"; then + { $as_echo "$as_me:$LINENO: result: $ac_ct_RANLIB" >&5 +$as_echo "$ac_ct_RANLIB" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_RANLIB" = x; then + RANLIB=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:$LINENO: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + RANLIB=$ac_ct_RANLIB + fi +else + RANLIB="$ac_cv_prog_RANLIB" +fi + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args. +set dummy ${ac_tool_prefix}strip; ac_word=$2 +{ $as_echo "$as_me:$LINENO: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_STRIP+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test -n "$STRIP"; then + ac_cv_prog_STRIP="$STRIP" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_STRIP="${ac_tool_prefix}strip" + $as_echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done +done +IFS=$as_save_IFS + +fi +fi +STRIP=$ac_cv_prog_STRIP +if test -n "$STRIP"; then + { $as_echo "$as_me:$LINENO: result: $STRIP" >&5 +$as_echo "$STRIP" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_STRIP"; then + ac_ct_STRIP=$STRIP + # Extract the first word of "strip", so it can be a program name with args. +set dummy strip; ac_word=$2 +{ $as_echo "$as_me:$LINENO: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_ac_ct_STRIP+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_STRIP"; then + ac_cv_prog_ac_ct_STRIP="$ac_ct_STRIP" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_ac_ct_STRIP="strip" + $as_echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done +done +IFS=$as_save_IFS + +fi +fi +ac_ct_STRIP=$ac_cv_prog_ac_ct_STRIP +if test -n "$ac_ct_STRIP"; then + { $as_echo "$as_me:$LINENO: result: $ac_ct_STRIP" >&5 +$as_echo "$ac_ct_STRIP" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_STRIP" = x; then + STRIP=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:$LINENO: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + STRIP=$ac_ct_STRIP + fi +else + STRIP="$ac_cv_prog_STRIP" +fi + + +old_CC="$CC" +old_CFLAGS="$CFLAGS" + +# Set sane defaults for various variables +test -z "$AR" && AR=ar +test -z "$AR_FLAGS" && AR_FLAGS=cru +test -z "$AS" && AS=as +test -z "$CC" && CC=cc +test -z "$LTCC" && LTCC=$CC +test -z "$LTCFLAGS" && LTCFLAGS=$CFLAGS +test -z "$DLLTOOL" && DLLTOOL=dlltool +test -z "$LD" && LD=ld +test -z "$LN_S" && LN_S="ln -s" +test -z "$MAGIC_CMD" && MAGIC_CMD=file +test -z "$NM" && NM=nm +test -z "$SED" && SED=sed +test -z "$OBJDUMP" && OBJDUMP=objdump +test -z "$RANLIB" && RANLIB=: +test -z "$STRIP" && STRIP=: +test -z "$ac_objext" && ac_objext=o + +# Determine commands to create old-style static archives. +old_archive_cmds='$AR $AR_FLAGS $oldlib$oldobjs' +old_postinstall_cmds='chmod 644 $oldlib' +old_postuninstall_cmds= + +if test -n "$RANLIB"; then + case $host_os in + openbsd*) + old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB -t \$oldlib" + ;; + *) + old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB \$oldlib" + ;; + esac + old_archive_cmds="$old_archive_cmds~\$RANLIB \$oldlib" +fi + +for cc_temp in $compiler""; do + case $cc_temp in + compile | *[\\/]compile | ccache | *[\\/]ccache ) ;; + distcc | *[\\/]distcc | purify | *[\\/]purify ) ;; + \-*) ;; + *) break;; + esac +done +cc_basename=`$echo "X$cc_temp" | $Xsed -e 's%.*/%%' -e "s%^$host_alias-%%"` + + +# Only perform the check for file, if the check method requires it +case $deplibs_check_method in +file_magic*) + if test "$file_magic_cmd" = '$MAGIC_CMD'; then + { $as_echo "$as_me:$LINENO: checking for ${ac_tool_prefix}file" >&5 +$as_echo_n "checking for ${ac_tool_prefix}file... " >&6; } +if test "${lt_cv_path_MAGIC_CMD+set}" = set; then + $as_echo_n "(cached) " >&6 +else + case $MAGIC_CMD in +[\\/*] | ?:[\\/]*) + lt_cv_path_MAGIC_CMD="$MAGIC_CMD" # Let the user override the test with a path. + ;; +*) + lt_save_MAGIC_CMD="$MAGIC_CMD" + lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR + ac_dummy="/usr/bin$PATH_SEPARATOR$PATH" + for ac_dir in $ac_dummy; do + IFS="$lt_save_ifs" + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/${ac_tool_prefix}file; then + lt_cv_path_MAGIC_CMD="$ac_dir/${ac_tool_prefix}file" + if test -n "$file_magic_test_file"; then + case $deplibs_check_method in + "file_magic "*) + file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"` + MAGIC_CMD="$lt_cv_path_MAGIC_CMD" + if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null | + $EGREP "$file_magic_regex" > /dev/null; then + : + else + cat <&2 + +*** Warning: the command libtool uses to detect shared libraries, +*** $file_magic_cmd, produces output that libtool cannot recognize. +*** The result is that libtool may fail to recognize shared libraries +*** as such. This will affect the creation of libtool libraries that +*** depend on shared libraries, but programs linked with such libtool +*** libraries will work regardless of this problem. Nevertheless, you +*** may want to report the problem to your system manager and/or to +*** bug-libtool@gnu.org + +EOF + fi ;; + esac + fi + break + fi + done + IFS="$lt_save_ifs" + MAGIC_CMD="$lt_save_MAGIC_CMD" + ;; +esac +fi + +MAGIC_CMD="$lt_cv_path_MAGIC_CMD" +if test -n "$MAGIC_CMD"; then + { $as_echo "$as_me:$LINENO: result: $MAGIC_CMD" >&5 +$as_echo "$MAGIC_CMD" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi + +if test -z "$lt_cv_path_MAGIC_CMD"; then + if test -n "$ac_tool_prefix"; then + { $as_echo "$as_me:$LINENO: checking for file" >&5 +$as_echo_n "checking for file... " >&6; } +if test "${lt_cv_path_MAGIC_CMD+set}" = set; then + $as_echo_n "(cached) " >&6 +else + case $MAGIC_CMD in +[\\/*] | ?:[\\/]*) + lt_cv_path_MAGIC_CMD="$MAGIC_CMD" # Let the user override the test with a path. + ;; +*) + lt_save_MAGIC_CMD="$MAGIC_CMD" + lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR + ac_dummy="/usr/bin$PATH_SEPARATOR$PATH" + for ac_dir in $ac_dummy; do + IFS="$lt_save_ifs" + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/file; then + lt_cv_path_MAGIC_CMD="$ac_dir/file" + if test -n "$file_magic_test_file"; then + case $deplibs_check_method in + "file_magic "*) + file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"` + MAGIC_CMD="$lt_cv_path_MAGIC_CMD" + if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null | + $EGREP "$file_magic_regex" > /dev/null; then + : + else + cat <&2 + +*** Warning: the command libtool uses to detect shared libraries, +*** $file_magic_cmd, produces output that libtool cannot recognize. +*** The result is that libtool may fail to recognize shared libraries +*** as such. This will affect the creation of libtool libraries that +*** depend on shared libraries, but programs linked with such libtool +*** libraries will work regardless of this problem. Nevertheless, you +*** may want to report the problem to your system manager and/or to +*** bug-libtool@gnu.org + +EOF + fi ;; + esac + fi + break + fi + done + IFS="$lt_save_ifs" + MAGIC_CMD="$lt_save_MAGIC_CMD" + ;; +esac +fi + +MAGIC_CMD="$lt_cv_path_MAGIC_CMD" +if test -n "$MAGIC_CMD"; then + { $as_echo "$as_me:$LINENO: result: $MAGIC_CMD" >&5 +$as_echo "$MAGIC_CMD" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi + + else + MAGIC_CMD=: + fi +fi + + fi + ;; +esac + + + case $host_os in + rhapsody* | darwin*) + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}dsymutil", so it can be a program name with args. +set dummy ${ac_tool_prefix}dsymutil; ac_word=$2 +{ $as_echo "$as_me:$LINENO: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_DSYMUTIL+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test -n "$DSYMUTIL"; then + ac_cv_prog_DSYMUTIL="$DSYMUTIL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_DSYMUTIL="${ac_tool_prefix}dsymutil" + $as_echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done +done +IFS=$as_save_IFS + +fi +fi +DSYMUTIL=$ac_cv_prog_DSYMUTIL +if test -n "$DSYMUTIL"; then + { $as_echo "$as_me:$LINENO: result: $DSYMUTIL" >&5 +$as_echo "$DSYMUTIL" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_DSYMUTIL"; then + ac_ct_DSYMUTIL=$DSYMUTIL + # Extract the first word of "dsymutil", so it can be a program name with args. +set dummy dsymutil; ac_word=$2 +{ $as_echo "$as_me:$LINENO: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_ac_ct_DSYMUTIL+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_DSYMUTIL"; then + ac_cv_prog_ac_ct_DSYMUTIL="$ac_ct_DSYMUTIL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_ac_ct_DSYMUTIL="dsymutil" + $as_echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done +done +IFS=$as_save_IFS + +fi +fi +ac_ct_DSYMUTIL=$ac_cv_prog_ac_ct_DSYMUTIL +if test -n "$ac_ct_DSYMUTIL"; then + { $as_echo "$as_me:$LINENO: result: $ac_ct_DSYMUTIL" >&5 +$as_echo "$ac_ct_DSYMUTIL" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_DSYMUTIL" = x; then + DSYMUTIL=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:$LINENO: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + DSYMUTIL=$ac_ct_DSYMUTIL + fi +else + DSYMUTIL="$ac_cv_prog_DSYMUTIL" +fi + + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}nmedit", so it can be a program name with args. +set dummy ${ac_tool_prefix}nmedit; ac_word=$2 +{ $as_echo "$as_me:$LINENO: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_NMEDIT+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test -n "$NMEDIT"; then + ac_cv_prog_NMEDIT="$NMEDIT" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_NMEDIT="${ac_tool_prefix}nmedit" + $as_echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done +done +IFS=$as_save_IFS + +fi +fi +NMEDIT=$ac_cv_prog_NMEDIT +if test -n "$NMEDIT"; then + { $as_echo "$as_me:$LINENO: result: $NMEDIT" >&5 +$as_echo "$NMEDIT" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_NMEDIT"; then + ac_ct_NMEDIT=$NMEDIT + # Extract the first word of "nmedit", so it can be a program name with args. +set dummy nmedit; ac_word=$2 +{ $as_echo "$as_me:$LINENO: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_ac_ct_NMEDIT+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_NMEDIT"; then + ac_cv_prog_ac_ct_NMEDIT="$ac_ct_NMEDIT" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_ac_ct_NMEDIT="nmedit" + $as_echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done +done +IFS=$as_save_IFS + +fi +fi +ac_ct_NMEDIT=$ac_cv_prog_ac_ct_NMEDIT +if test -n "$ac_ct_NMEDIT"; then + { $as_echo "$as_me:$LINENO: result: $ac_ct_NMEDIT" >&5 +$as_echo "$ac_ct_NMEDIT" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_NMEDIT" = x; then + NMEDIT=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:$LINENO: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + NMEDIT=$ac_ct_NMEDIT + fi +else + NMEDIT="$ac_cv_prog_NMEDIT" +fi + + + { $as_echo "$as_me:$LINENO: checking for -single_module linker flag" >&5 +$as_echo_n "checking for -single_module linker flag... " >&6; } +if test "${lt_cv_apple_cc_single_mod+set}" = set; then + $as_echo_n "(cached) " >&6 +else + lt_cv_apple_cc_single_mod=no + if test -z "${LT_MULTI_MODULE}"; then + # By default we will add the -single_module flag. You can override + # by either setting the environment variable LT_MULTI_MODULE + # non-empty at configure time, or by adding -multi_module to the + # link flags. + echo "int foo(void){return 1;}" > conftest.c + $LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ + -dynamiclib ${wl}-single_module conftest.c + if test -f libconftest.dylib; then + lt_cv_apple_cc_single_mod=yes + rm -rf libconftest.dylib* + fi + rm conftest.c + fi +fi +{ $as_echo "$as_me:$LINENO: result: $lt_cv_apple_cc_single_mod" >&5 +$as_echo "$lt_cv_apple_cc_single_mod" >&6; } + { $as_echo "$as_me:$LINENO: checking for -exported_symbols_list linker flag" >&5 +$as_echo_n "checking for -exported_symbols_list linker flag... " >&6; } +if test "${lt_cv_ld_exported_symbols_list+set}" = set; then + $as_echo_n "(cached) " >&6 +else + lt_cv_ld_exported_symbols_list=no + save_LDFLAGS=$LDFLAGS + echo "_main" > conftest.sym + LDFLAGS="$LDFLAGS -Wl,-exported_symbols_list,conftest.sym" + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + $as_test_x conftest$ac_exeext + }; then + lt_cv_ld_exported_symbols_list=yes +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + lt_cv_ld_exported_symbols_list=no +fi + +rm -rf conftest.dSYM +rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ + conftest$ac_exeext conftest.$ac_ext + LDFLAGS="$save_LDFLAGS" + +fi +{ $as_echo "$as_me:$LINENO: result: $lt_cv_ld_exported_symbols_list" >&5 +$as_echo "$lt_cv_ld_exported_symbols_list" >&6; } + case $host_os in + rhapsody* | darwin1.[0123]) + _lt_dar_allow_undefined='${wl}-undefined ${wl}suppress' ;; + darwin1.*) + _lt_dar_allow_undefined='${wl}-flat_namespace ${wl}-undefined ${wl}suppress' ;; + darwin*) + # if running on 10.5 or later, the deployment target defaults + # to the OS version, if on x86, and 10.4, the deployment + # target defaults to 10.4. Don't you love it? + case ${MACOSX_DEPLOYMENT_TARGET-10.0},$host in + 10.0,*86*-darwin8*|10.0,*-darwin[91]*) + _lt_dar_allow_undefined='${wl}-undefined ${wl}dynamic_lookup' ;; + 10.[012]*) + _lt_dar_allow_undefined='${wl}-flat_namespace ${wl}-undefined ${wl}suppress' ;; + 10.*) + _lt_dar_allow_undefined='${wl}-undefined ${wl}dynamic_lookup' ;; + esac + ;; + esac + if test "$lt_cv_apple_cc_single_mod" = "yes"; then + _lt_dar_single_mod='$single_module' + fi + if test "$lt_cv_ld_exported_symbols_list" = "yes"; then + _lt_dar_export_syms=' ${wl}-exported_symbols_list,$output_objdir/${libname}-symbols.expsym' + else + _lt_dar_export_syms="~$NMEDIT -s \$output_objdir/\${libname}-symbols.expsym \${lib}" + fi + if test "$DSYMUTIL" != ":"; then + _lt_dsymutil="~$DSYMUTIL \$lib || :" + else + _lt_dsymutil= + fi + ;; + esac + + +enable_dlopen=no +enable_win32_dll=no + +# Check whether --enable-libtool-lock was given. +if test "${enable_libtool_lock+set}" = set; then + enableval=$enable_libtool_lock; +fi + +test "x$enable_libtool_lock" != xno && enable_libtool_lock=yes + + +# Check whether --with-pic was given. +if test "${with_pic+set}" = set; then + withval=$with_pic; pic_mode="$withval" +else + pic_mode=default +fi + +test -z "$pic_mode" && pic_mode=default + +# Use C for the default configuration in the libtool script +tagname= +lt_save_CC="$CC" +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +# Source file extension for C test sources. +ac_ext=c + +# Object file extension for compiled C test sources. +objext=o +objext=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="int some_variable = 0;" + +# Code to be used in simple link tests +lt_simple_link_test_code='int main(){return(0);}' + + +# If no C compiler was specified, use CC. +LTCC=${LTCC-"$CC"} + +# If no C compiler flags were specified, use CFLAGS. +LTCFLAGS=${LTCFLAGS-"$CFLAGS"} + +# Allow CC to be a program name with arguments. +compiler=$CC + + +# save warnings/boilerplate of simple test code +ac_outfile=conftest.$ac_objext +echo "$lt_simple_compile_test_code" >conftest.$ac_ext +eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_compiler_boilerplate=`cat conftest.err` +$rm conftest* + +ac_outfile=conftest.$ac_objext +echo "$lt_simple_link_test_code" >conftest.$ac_ext +eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_linker_boilerplate=`cat conftest.err` +$rm -r conftest* + + + +lt_prog_compiler_no_builtin_flag= + +if test "$GCC" = yes; then + lt_prog_compiler_no_builtin_flag=' -fno-builtin' + + +{ $as_echo "$as_me:$LINENO: checking if $compiler supports -fno-rtti -fno-exceptions" >&5 +$as_echo_n "checking if $compiler supports -fno-rtti -fno-exceptions... " >&6; } +if test "${lt_cv_prog_compiler_rtti_exceptions+set}" = set; then + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_rtti_exceptions=no + ac_outfile=conftest.$ac_objext + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + lt_compiler_flag="-fno-rtti -fno-exceptions" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + # The option is referenced via a variable to avoid confusing sed. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:7647: $lt_compile\"" >&5) + (eval "$lt_compile" 2>conftest.err) + ac_status=$? + cat conftest.err >&5 + echo "$as_me:7651: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s "$ac_outfile"; then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings other than the usual output. + $echo "X$_lt_compiler_boilerplate" | $Xsed -e '/^$/d' >conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler_rtti_exceptions=yes + fi + fi + $rm conftest* + +fi +{ $as_echo "$as_me:$LINENO: result: $lt_cv_prog_compiler_rtti_exceptions" >&5 +$as_echo "$lt_cv_prog_compiler_rtti_exceptions" >&6; } + +if test x"$lt_cv_prog_compiler_rtti_exceptions" = xyes; then + lt_prog_compiler_no_builtin_flag="$lt_prog_compiler_no_builtin_flag -fno-rtti -fno-exceptions" +else + : +fi + +fi + +lt_prog_compiler_wl= +lt_prog_compiler_pic= +lt_prog_compiler_static= + +{ $as_echo "$as_me:$LINENO: checking for $compiler option to produce PIC" >&5 +$as_echo_n "checking for $compiler option to produce PIC... " >&6; } + + if test "$GCC" = yes; then + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_static='-static' + + case $host_os in + aix*) + # All AIX code is PIC. + if test "$host_cpu" = ia64; then + # AIX 5 now supports IA64 processor + lt_prog_compiler_static='-Bstatic' + fi + ;; + + amigaos*) + # FIXME: we need at least 68020 code to build shared libraries, but + # adding the `-m68020' flag to GCC prevents building anything better, + # like `-m68040'. + lt_prog_compiler_pic='-m68020 -resident32 -malways-restore-a4' + ;; + + beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) + # PIC is the default for these OSes. + ;; + + mingw* | cygwin* | pw32* | os2*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + # Although the cygwin gcc ignores -fPIC, still need this for old-style + # (--disable-auto-import) libraries + lt_prog_compiler_pic='-DDLL_EXPORT' + ;; + + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + lt_prog_compiler_pic='-fno-common' + ;; + + interix[3-9]*) + # Interix 3.x gcc -fpic/-fPIC options generate broken code. + # Instead, we relocate shared libraries at runtime. + ;; + + msdosdjgpp*) + # Just because we use GCC doesn't mean we suddenly get shared libraries + # on systems that don't support them. + lt_prog_compiler_can_build_shared=no + enable_shared=no + ;; + + sysv4*MP*) + if test -d /usr/nec; then + lt_prog_compiler_pic=-Kconform_pic + fi + ;; + + hpux*) + # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but + # not for PA HP-UX. + case $host_cpu in + hppa*64*|ia64*) + # +Z the default + ;; + *) + lt_prog_compiler_pic='-fPIC' + ;; + esac + ;; + + *) + lt_prog_compiler_pic='-fPIC' + ;; + esac + else + # PORTME Check for flag to pass linker flags through the system compiler. + case $host_os in + aix*) + lt_prog_compiler_wl='-Wl,' + if test "$host_cpu" = ia64; then + # AIX 5 now supports IA64 processor + lt_prog_compiler_static='-Bstatic' + else + lt_prog_compiler_static='-bnso -bI:/lib/syscalls.exp' + fi + ;; + darwin*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + case $cc_basename in + xlc*) + lt_prog_compiler_pic='-qnocommon' + lt_prog_compiler_wl='-Wl,' + ;; + esac + ;; + + mingw* | cygwin* | pw32* | os2*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + lt_prog_compiler_pic='-DDLL_EXPORT' + ;; + + hpux9* | hpux10* | hpux11*) + lt_prog_compiler_wl='-Wl,' + # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but + # not for PA HP-UX. + case $host_cpu in + hppa*64*|ia64*) + # +Z the default + ;; + *) + lt_prog_compiler_pic='+Z' + ;; + esac + # Is there a better lt_prog_compiler_static that works with the bundled CC? + lt_prog_compiler_static='${wl}-a ${wl}archive' + ;; + + irix5* | irix6* | nonstopux*) + lt_prog_compiler_wl='-Wl,' + # PIC (with -KPIC) is the default. + lt_prog_compiler_static='-non_shared' + ;; + + newsos6) + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + ;; + + linux* | k*bsd*-gnu) + case $cc_basename in + icc* | ecc*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-static' + ;; + pgcc* | pgf77* | pgf90* | pgf95*) + # Portland Group compilers (*not* the Pentium gcc compiler, + # which looks to be a dead project) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-fpic' + lt_prog_compiler_static='-Bstatic' + ;; + ccc*) + lt_prog_compiler_wl='-Wl,' + # All Alpha code is PIC. + lt_prog_compiler_static='-non_shared' + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) + # Sun C 5.9 + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + lt_prog_compiler_wl='-Wl,' + ;; + *Sun\ F*) + # Sun Fortran 8.3 passes all unrecognized flags to the linker + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + lt_prog_compiler_wl='' + ;; + esac + ;; + esac + ;; + + osf3* | osf4* | osf5*) + lt_prog_compiler_wl='-Wl,' + # All OSF/1 code is PIC. + lt_prog_compiler_static='-non_shared' + ;; + + rdos*) + lt_prog_compiler_static='-non_shared' + ;; + + solaris*) + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + case $cc_basename in + f77* | f90* | f95*) + lt_prog_compiler_wl='-Qoption ld ';; + *) + lt_prog_compiler_wl='-Wl,';; + esac + ;; + + sunos4*) + lt_prog_compiler_wl='-Qoption ld ' + lt_prog_compiler_pic='-PIC' + lt_prog_compiler_static='-Bstatic' + ;; + + sysv4 | sysv4.2uw2* | sysv4.3*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + ;; + + sysv4*MP*) + if test -d /usr/nec ;then + lt_prog_compiler_pic='-Kconform_pic' + lt_prog_compiler_static='-Bstatic' + fi + ;; + + sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + ;; + + unicos*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_can_build_shared=no + ;; + + uts4*) + lt_prog_compiler_pic='-pic' + lt_prog_compiler_static='-Bstatic' + ;; + + *) + lt_prog_compiler_can_build_shared=no + ;; + esac + fi + +{ $as_echo "$as_me:$LINENO: result: $lt_prog_compiler_pic" >&5 +$as_echo "$lt_prog_compiler_pic" >&6; } + +# +# Check to make sure the PIC flag actually works. +# +if test -n "$lt_prog_compiler_pic"; then + +{ $as_echo "$as_me:$LINENO: checking if $compiler PIC flag $lt_prog_compiler_pic works" >&5 +$as_echo_n "checking if $compiler PIC flag $lt_prog_compiler_pic works... " >&6; } +if test "${lt_cv_prog_compiler_pic_works+set}" = set; then + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_pic_works=no + ac_outfile=conftest.$ac_objext + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + lt_compiler_flag="$lt_prog_compiler_pic -DPIC" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + # The option is referenced via a variable to avoid confusing sed. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:7937: $lt_compile\"" >&5) + (eval "$lt_compile" 2>conftest.err) + ac_status=$? + cat conftest.err >&5 + echo "$as_me:7941: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s "$ac_outfile"; then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings other than the usual output. + $echo "X$_lt_compiler_boilerplate" | $Xsed -e '/^$/d' >conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler_pic_works=yes + fi + fi + $rm conftest* + +fi +{ $as_echo "$as_me:$LINENO: result: $lt_cv_prog_compiler_pic_works" >&5 +$as_echo "$lt_cv_prog_compiler_pic_works" >&6; } + +if test x"$lt_cv_prog_compiler_pic_works" = xyes; then + case $lt_prog_compiler_pic in + "" | " "*) ;; + *) lt_prog_compiler_pic=" $lt_prog_compiler_pic" ;; + esac +else + lt_prog_compiler_pic= + lt_prog_compiler_can_build_shared=no +fi + +fi +case $host_os in + # For platforms which do not support PIC, -DPIC is meaningless: + *djgpp*) + lt_prog_compiler_pic= + ;; + *) + lt_prog_compiler_pic="$lt_prog_compiler_pic -DPIC" + ;; +esac + +# +# Check to make sure the static flag actually works. +# +wl=$lt_prog_compiler_wl eval lt_tmp_static_flag=\"$lt_prog_compiler_static\" +{ $as_echo "$as_me:$LINENO: checking if $compiler static flag $lt_tmp_static_flag works" >&5 +$as_echo_n "checking if $compiler static flag $lt_tmp_static_flag works... " >&6; } +if test "${lt_cv_prog_compiler_static_works+set}" = set; then + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_static_works=no + save_LDFLAGS="$LDFLAGS" + LDFLAGS="$LDFLAGS $lt_tmp_static_flag" + echo "$lt_simple_link_test_code" > conftest.$ac_ext + if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then + # The linker can only warn and ignore the option if not recognized + # So say no if there are warnings + if test -s conftest.err; then + # Append any errors to the config.log. + cat conftest.err 1>&5 + $echo "X$_lt_linker_boilerplate" | $Xsed -e '/^$/d' > conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler_static_works=yes + fi + else + lt_cv_prog_compiler_static_works=yes + fi + fi + $rm -r conftest* + LDFLAGS="$save_LDFLAGS" + +fi +{ $as_echo "$as_me:$LINENO: result: $lt_cv_prog_compiler_static_works" >&5 +$as_echo "$lt_cv_prog_compiler_static_works" >&6; } + +if test x"$lt_cv_prog_compiler_static_works" = xyes; then + : +else + lt_prog_compiler_static= +fi + + +{ $as_echo "$as_me:$LINENO: checking if $compiler supports -c -o file.$ac_objext" >&5 +$as_echo_n "checking if $compiler supports -c -o file.$ac_objext... " >&6; } +if test "${lt_cv_prog_compiler_c_o+set}" = set; then + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_c_o=no + $rm -r conftest 2>/dev/null + mkdir conftest + cd conftest + mkdir out + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + lt_compiler_flag="-o out/conftest2.$ac_objext" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:8041: $lt_compile\"" >&5) + (eval "$lt_compile" 2>out/conftest.err) + ac_status=$? + cat out/conftest.err >&5 + echo "$as_me:8045: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s out/conftest2.$ac_objext + then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + $echo "X$_lt_compiler_boilerplate" | $Xsed -e '/^$/d' > out/conftest.exp + $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 + if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then + lt_cv_prog_compiler_c_o=yes + fi + fi + chmod u+w . 2>&5 + $rm conftest* + # SGI C++ compiler will create directory out/ii_files/ for + # template instantiation + test -d out/ii_files && $rm out/ii_files/* && rmdir out/ii_files + $rm out/* && rmdir out + cd .. + rmdir conftest + $rm conftest* + +fi +{ $as_echo "$as_me:$LINENO: result: $lt_cv_prog_compiler_c_o" >&5 +$as_echo "$lt_cv_prog_compiler_c_o" >&6; } + + +hard_links="nottested" +if test "$lt_cv_prog_compiler_c_o" = no && test "$need_locks" != no; then + # do not overwrite the value of need_locks provided by the user + { $as_echo "$as_me:$LINENO: checking if we can lock with hard links" >&5 +$as_echo_n "checking if we can lock with hard links... " >&6; } + hard_links=yes + $rm conftest* + ln conftest.a conftest.b 2>/dev/null && hard_links=no + touch conftest.a + ln conftest.a conftest.b 2>&5 || hard_links=no + ln conftest.a conftest.b 2>/dev/null && hard_links=no + { $as_echo "$as_me:$LINENO: result: $hard_links" >&5 +$as_echo "$hard_links" >&6; } + if test "$hard_links" = no; then + { $as_echo "$as_me:$LINENO: WARNING: \`$CC' does not support \`-c -o', so \`make -j' may be unsafe" >&5 +$as_echo "$as_me: WARNING: \`$CC' does not support \`-c -o', so \`make -j' may be unsafe" >&2;} + need_locks=warn + fi +else + need_locks=no +fi + +{ $as_echo "$as_me:$LINENO: checking whether the $compiler linker ($LD) supports shared libraries" >&5 +$as_echo_n "checking whether the $compiler linker ($LD) supports shared libraries... " >&6; } + + runpath_var= + allow_undefined_flag= + enable_shared_with_static_runtimes=no + archive_cmds= + archive_expsym_cmds= + old_archive_From_new_cmds= + old_archive_from_expsyms_cmds= + export_dynamic_flag_spec= + whole_archive_flag_spec= + thread_safe_flag_spec= + hardcode_libdir_flag_spec= + hardcode_libdir_flag_spec_ld= + hardcode_libdir_separator= + hardcode_direct=no + hardcode_minus_L=no + hardcode_shlibpath_var=unsupported + link_all_deplibs=unknown + hardcode_automatic=no + module_cmds= + module_expsym_cmds= + always_export_symbols=no + export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + # include_expsyms should be a list of space-separated symbols to be *always* + # included in the symbol list + include_expsyms= + # exclude_expsyms can be an extended regexp of symbols to exclude + # it will be wrapped by ` (' and `)$', so one must not match beginning or + # end of line. Example: `a|bc|.*d.*' will exclude the symbols `a' and `bc', + # as well as any symbol that contains `d'. + exclude_expsyms='_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*' + # Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out + # platforms (ab)use it in PIC code, but their linkers get confused if + # the symbol is explicitly referenced. Since portable code cannot + # rely on this symbol name, it's probably fine to never include it in + # preloaded symbol tables. + # Exclude shared library initialization/finalization symbols. + extract_expsyms_cmds= + # Just being paranoid about ensuring that cc_basename is set. + for cc_temp in $compiler""; do + case $cc_temp in + compile | *[\\/]compile | ccache | *[\\/]ccache ) ;; + distcc | *[\\/]distcc | purify | *[\\/]purify ) ;; + \-*) ;; + *) break;; + esac +done +cc_basename=`$echo "X$cc_temp" | $Xsed -e 's%.*/%%' -e "s%^$host_alias-%%"` + + case $host_os in + cygwin* | mingw* | pw32*) + # FIXME: the MSVC++ port hasn't been tested in a loooong time + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + if test "$GCC" != yes; then + with_gnu_ld=no + fi + ;; + interix*) + # we just hope/assume this is gcc and not c89 (= MSVC++) + with_gnu_ld=yes + ;; + openbsd*) + with_gnu_ld=no + ;; + esac + + ld_shlibs=yes + if test "$with_gnu_ld" = yes; then + # If archive_cmds runs LD, not CC, wlarc should be empty + wlarc='${wl}' + + # Set some defaults for GNU ld with shared library support. These + # are reset later if shared libraries are not supported. Putting them + # here allows them to be overridden if necessary. + runpath_var=LD_RUN_PATH + hardcode_libdir_flag_spec='${wl}--rpath ${wl}$libdir' + export_dynamic_flag_spec='${wl}--export-dynamic' + # ancient GNU ld didn't support --whole-archive et. al. + if $LD --help 2>&1 | grep 'no-whole-archive' > /dev/null; then + whole_archive_flag_spec="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive' + else + whole_archive_flag_spec= + fi + supports_anon_versioning=no + case `$LD -v 2>/dev/null` in + *\ [01].* | *\ 2.[0-9].* | *\ 2.10.*) ;; # catch versions < 2.11 + *\ 2.11.93.0.2\ *) supports_anon_versioning=yes ;; # RH7.3 ... + *\ 2.11.92.0.12\ *) supports_anon_versioning=yes ;; # Mandrake 8.2 ... + *\ 2.11.*) ;; # other 2.11 versions + *) supports_anon_versioning=yes ;; + esac + + # See if GNU ld supports shared libraries. + case $host_os in + aix[3-9]*) + # On AIX/PPC, the GNU linker is very broken + if test "$host_cpu" != ia64; then + ld_shlibs=no + cat <&2 + +*** Warning: the GNU linker, at least up to release 2.9.1, is reported +*** to be unable to reliably create shared libraries on AIX. +*** Therefore, libtool is disabling shared libraries support. If you +*** really care for shared libraries, you may want to modify your PATH +*** so that a non-GNU linker is found, and then restart. + +EOF + fi + ;; + + amigaos*) + archive_cmds='$rm $output_objdir/a2ixlibrary.data~$echo "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$echo "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$echo "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$echo "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + + # Samuel A. Falvo II reports + # that the semantics of dynamic libraries on AmigaOS, at least up + # to version 4, is to share data among multiple programs linked + # with the same dynamic library. Since this doesn't match the + # behavior of shared libraries on other platforms, we can't use + # them. + ld_shlibs=no + ;; + + beos*) + if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + allow_undefined_flag=unsupported + # Joseph Beckenbach says some releases of gcc + # support --undefined. This deserves some investigation. FIXME + archive_cmds='$CC -nostart $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + else + ld_shlibs=no + fi + ;; + + cygwin* | mingw* | pw32*) + # _LT_AC_TAGVAR(hardcode_libdir_flag_spec, ) is actually meaningless, + # as there is no search path for DLLs. + hardcode_libdir_flag_spec='-L$libdir' + allow_undefined_flag=unsupported + always_export_symbols=no + enable_shared_with_static_runtimes=yes + export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1 DATA/'\'' -e '\''/^[AITW][ ]/s/.*[ ]//'\'' | sort | uniq > $export_symbols' + + if $LD --help 2>&1 | grep 'auto-import' > /dev/null; then + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + # If the export-symbols file already is a .def file (1st line + # is EXPORTS), use it as is; otherwise, prepend... + archive_expsym_cmds='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then + cp $export_symbols $output_objdir/$soname.def; + else + echo EXPORTS > $output_objdir/$soname.def; + cat $export_symbols >> $output_objdir/$soname.def; + fi~ + $CC -shared $output_objdir/$soname.def $libobjs $deplibs $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + else + ld_shlibs=no + fi + ;; + + interix[3-9]*) + hardcode_direct=no + hardcode_shlibpath_var=no + hardcode_libdir_flag_spec='${wl}-rpath,$libdir' + export_dynamic_flag_spec='${wl}-E' + # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. + # Instead, shared libraries are loaded at an image base (0x10000000 by + # default) and relocated if they conflict, which is a slow very memory + # consuming and fragmenting process. To avoid this, we pick a random, + # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link + # time. Moving up from 0x10000000 also allows more sbrk(2) space. + archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + archive_expsym_cmds='sed "s,^,_," $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--retain-symbols-file,$output_objdir/$soname.expsym ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + ;; + + gnu* | linux* | k*bsd*-gnu) + if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + tmp_addflag= + case $cc_basename,$host_cpu in + pgcc*) # Portland Group C compiler + whole_archive_flag_spec='${wl}--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; $echo \"$new_convenience\"` ${wl}--no-whole-archive' + tmp_addflag=' $pic_flag' + ;; + pgf77* | pgf90* | pgf95*) # Portland Group f77 and f90 compilers + whole_archive_flag_spec='${wl}--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; $echo \"$new_convenience\"` ${wl}--no-whole-archive' + tmp_addflag=' $pic_flag -Mnomain' ;; + ecc*,ia64* | icc*,ia64*) # Intel C compiler on ia64 + tmp_addflag=' -i_dynamic' ;; + efc*,ia64* | ifort*,ia64*) # Intel Fortran compiler on ia64 + tmp_addflag=' -i_dynamic -nofor_main' ;; + ifc* | ifort*) # Intel Fortran compiler + tmp_addflag=' -nofor_main' ;; + esac + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) # Sun C 5.9 + whole_archive_flag_spec='${wl}--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; $echo \"$new_convenience\"` ${wl}--no-whole-archive' + tmp_sharedflag='-G' ;; + *Sun\ F*) # Sun Fortran 8.3 + tmp_sharedflag='-G' ;; + *) + tmp_sharedflag='-shared' ;; + esac + archive_cmds='$CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + + if test $supports_anon_versioning = yes; then + archive_expsym_cmds='$echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + $echo "local: *; };" >> $output_objdir/$libname.ver~ + $CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-version-script ${wl}$output_objdir/$libname.ver -o $lib' + fi + else + ld_shlibs=no + fi + ;; + + netbsd*) + if echo __ELF__ | $CC -E - | grep __ELF__ >/dev/null; then + archive_cmds='$LD -Bshareable $libobjs $deplibs $linker_flags -o $lib' + wlarc= + else + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + archive_expsym_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + fi + ;; + + solaris*) + if $LD -v 2>&1 | grep 'BFD 2\.8' > /dev/null; then + ld_shlibs=no + cat <&2 + +*** Warning: The releases 2.8.* of the GNU linker cannot reliably +*** create shared libraries on Solaris systems. Therefore, libtool +*** is disabling shared libraries support. We urge you to upgrade GNU +*** binutils to release 2.9.1 or newer. Another option is to modify +*** your PATH or compiler configuration so that the native linker is +*** used, and then restart. + +EOF + elif $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + archive_expsym_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + else + ld_shlibs=no + fi + ;; + + sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*) + case `$LD -v 2>&1` in + *\ [01].* | *\ 2.[0-9].* | *\ 2.1[0-5].*) + ld_shlibs=no + cat <<_LT_EOF 1>&2 + +*** Warning: Releases of the GNU linker prior to 2.16.91.0.3 can not +*** reliably create shared libraries on SCO systems. Therefore, libtool +*** is disabling shared libraries support. We urge you to upgrade GNU +*** binutils to release 2.16.91.0.3 or newer. Another option is to modify +*** your PATH or compiler configuration so that the native linker is +*** used, and then restart. + +_LT_EOF + ;; + *) + if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + hardcode_libdir_flag_spec='`test -z "$SCOABSPATH" && echo ${wl}-rpath,$libdir`' + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname,\${SCOABSPATH:+${install_libdir}/}$soname -o $lib' + archive_expsym_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname,\${SCOABSPATH:+${install_libdir}/}$soname,-retain-symbols-file,$export_symbols -o $lib' + else + ld_shlibs=no + fi + ;; + esac + ;; + + sunos4*) + archive_cmds='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linker_flags' + wlarc= + hardcode_direct=yes + hardcode_shlibpath_var=no + ;; + + *) + if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + archive_expsym_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + else + ld_shlibs=no + fi + ;; + esac + + if test "$ld_shlibs" = no; then + runpath_var= + hardcode_libdir_flag_spec= + export_dynamic_flag_spec= + whole_archive_flag_spec= + fi + else + # PORTME fill in a description of your system's linker (not GNU ld) + case $host_os in + aix3*) + allow_undefined_flag=unsupported + always_export_symbols=yes + archive_expsym_cmds='$LD -o $output_objdir/$soname $libobjs $deplibs $linker_flags -bE:$export_symbols -T512 -H512 -bM:SRE~$AR $AR_FLAGS $lib $output_objdir/$soname' + # Note: this linker hardcodes the directories in LIBPATH if there + # are no directories specified by -L. + hardcode_minus_L=yes + if test "$GCC" = yes && test -z "$lt_prog_compiler_static"; then + # Neither direct hardcoding nor static linking is supported with a + # broken collect2. + hardcode_direct=unsupported + fi + ;; + + aix[4-9]*) + if test "$host_cpu" = ia64; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + exp_sym_flag='-Bexport' + no_entry_flag="" + else + # If we're using GNU nm, then we don't want the "-C" option. + # -C means demangle to AIX nm, but means don't demangle with GNU nm + if $NM -V 2>&1 | grep 'GNU' > /dev/null; then + export_symbols_cmds='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$2 == "T") || (\$2 == "D") || (\$2 == "B")) && (substr(\$3,1,1) != ".")) { print \$3 } }'\'' | sort -u > $export_symbols' + else + export_symbols_cmds='$NM -BCpg $libobjs $convenience | awk '\''{ if (((\$2 == "T") || (\$2 == "D") || (\$2 == "B")) && (substr(\$3,1,1) != ".")) { print \$3 } }'\'' | sort -u > $export_symbols' + fi + aix_use_runtimelinking=no + + # Test if we are trying to use run time linking or normal + # AIX style linking. If -brtl is somewhere in LDFLAGS, we + # need to do runtime linking. + case $host_os in aix4.[23]|aix4.[23].*|aix[5-9]*) + for ld_flag in $LDFLAGS; do + if (test $ld_flag = "-brtl" || test $ld_flag = "-Wl,-brtl"); then + aix_use_runtimelinking=yes + break + fi + done + ;; + esac + + exp_sym_flag='-bexport' + no_entry_flag='-bnoentry' + fi + + # When large executables or shared objects are built, AIX ld can + # have problems creating the table of contents. If linking a library + # or program results in "error TOC overflow" add -mminimal-toc to + # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not + # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. + + archive_cmds='' + hardcode_direct=yes + hardcode_libdir_separator=':' + link_all_deplibs=yes + + if test "$GCC" = yes; then + case $host_os in aix4.[012]|aix4.[012].*) + # We only want to do this on AIX 4.2 and lower, the check + # below for broken collect2 doesn't work under 4.3+ + collect2name=`${CC} -print-prog-name=collect2` + if test -f "$collect2name" && \ + strings "$collect2name" | grep resolve_lib_name >/dev/null + then + # We have reworked collect2 + : + else + # We have old collect2 + hardcode_direct=unsupported + # It fails to find uninstalled libraries when the uninstalled + # path is not listed in the libpath. Setting hardcode_minus_L + # to unsupported forces relinking + hardcode_minus_L=yes + hardcode_libdir_flag_spec='-L$libdir' + hardcode_libdir_separator= + fi + ;; + esac + shared_flag='-shared' + if test "$aix_use_runtimelinking" = yes; then + shared_flag="$shared_flag "'${wl}-G' + fi + else + # not using gcc + if test "$host_cpu" = ia64; then + # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release + # chokes on -Wl,-G. The following line is correct: + shared_flag='-G' + else + if test "$aix_use_runtimelinking" = yes; then + shared_flag='${wl}-G' + else + shared_flag='${wl}-bM:SRE' + fi + fi + fi + + # It seems that -bexpall does not export symbols beginning with + # underscore (_), so it is better to generate a list of symbols to export. + always_export_symbols=yes + if test "$aix_use_runtimelinking" = yes; then + # Warning - without using the other runtime loading flags (-brtl), + # -berok will link without error, but may produce a broken library. + allow_undefined_flag='-berok' + # Determine the default libpath from the value encoded in an empty executable. + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + $as_test_x conftest$ac_exeext + }; then + +lt_aix_libpath_sed=' + /Import File Strings/,/^$/ { + /^0/ { + s/^0 *\(.*\)$/\1/ + p + } + }' +aix_libpath=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` +# Check for a 64-bit object if we didn't find anything. +if test -z "$aix_libpath"; then + aix_libpath=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` +fi +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + +fi + +rm -rf conftest.dSYM +rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ + conftest$ac_exeext conftest.$ac_ext +if test -z "$aix_libpath"; then aix_libpath="/usr/lib:/lib"; fi + + hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:'"$aix_libpath" + archive_expsym_cmds="\$CC"' -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags `if test "x${allow_undefined_flag}" != "x"; then echo "${wl}${allow_undefined_flag}"; else :; fi` '"\${wl}$exp_sym_flag:\$export_symbols $shared_flag" + else + if test "$host_cpu" = ia64; then + hardcode_libdir_flag_spec='${wl}-R $libdir:/usr/lib:/lib' + allow_undefined_flag="-z nodefs" + archive_expsym_cmds="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags ${wl}${allow_undefined_flag} '"\${wl}$exp_sym_flag:\$export_symbols" + else + # Determine the default libpath from the value encoded in an empty executable. + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + $as_test_x conftest$ac_exeext + }; then + +lt_aix_libpath_sed=' + /Import File Strings/,/^$/ { + /^0/ { + s/^0 *\(.*\)$/\1/ + p + } + }' +aix_libpath=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` +# Check for a 64-bit object if we didn't find anything. +if test -z "$aix_libpath"; then + aix_libpath=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` +fi +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + +fi + +rm -rf conftest.dSYM +rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ + conftest$ac_exeext conftest.$ac_ext +if test -z "$aix_libpath"; then aix_libpath="/usr/lib:/lib"; fi + + hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:'"$aix_libpath" + # Warning - without using the other run time loading flags, + # -berok will link without error, but may produce a broken library. + no_undefined_flag=' ${wl}-bernotok' + allow_undefined_flag=' ${wl}-berok' + # Exported symbols can be pulled into shared objects from archives + whole_archive_flag_spec='$convenience' + archive_cmds_need_lc=yes + # This is similar to how AIX traditionally builds its shared libraries. + archive_expsym_cmds="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs ${wl}-bnoentry $compiler_flags ${wl}-bE:$export_symbols${allow_undefined_flag}~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$soname' + fi + fi + ;; + + amigaos*) + archive_cmds='$rm $output_objdir/a2ixlibrary.data~$echo "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$echo "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$echo "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$echo "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + # see comment about different semantics on the GNU ld section + ld_shlibs=no + ;; + + bsdi[45]*) + export_dynamic_flag_spec=-rdynamic + ;; + + cygwin* | mingw* | pw32*) + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + hardcode_libdir_flag_spec=' ' + allow_undefined_flag=unsupported + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=".dll" + # FIXME: Setting linknames here is a bad hack. + archive_cmds='$CC -o $lib $libobjs $compiler_flags `echo "$deplibs" | $SED -e '\''s/ -lc$//'\''` -link -dll~linknames=' + # The linker will automatically build a .lib file if we build a DLL. + old_archive_From_new_cmds='true' + # FIXME: Should let the user specify the lib program. + old_archive_cmds='lib -OUT:$oldlib$oldobjs$old_deplibs' + fix_srcfile_path='`cygpath -w "$srcfile"`' + enable_shared_with_static_runtimes=yes + ;; + + darwin* | rhapsody*) + allow_undefined_flag="$_lt_dar_allow_undefined" + archive_cmds_need_lc=no + hardcode_direct=no + hardcode_automatic=yes + hardcode_shlibpath_var=unsupported + whole_archive_flag_spec='' + link_all_deplibs=yes + if test "$GCC" = yes ; then + output_verbose_link_cmd='echo' + archive_cmds="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod${_lt_dsymutil}" + module_cmds="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags${_lt_dsymutil}" + archive_expsym_cmds="sed 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring ${_lt_dar_single_mod}${_lt_dar_export_syms}${_lt_dsymutil}" + module_expsym_cmds="sed -e 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags${_lt_dar_export_syms}${_lt_dsymutil}" + else + case $cc_basename in + xlc*) + output_verbose_link_cmd='echo' + archive_cmds='$CC -qmkshrobj $allow_undefined_flag -o $lib $libobjs $deplibs $compiler_flags ${wl}-install_name ${wl}`echo $rpath/$soname` $xlcverstring' + module_cmds='$CC $allow_undefined_flag -o $lib -bundle $libobjs $deplibs$compiler_flags' + # Don't fix this by using the ld -exported_symbols_list flag, it doesn't exist in older darwin lds + archive_expsym_cmds='sed -e "s,#.*,," -e "s,^[ ]*,," -e "s,^\(..*\),_&," < $export_symbols > $output_objdir/${libname}-symbols.expsym~$CC -qmkshrobj $allow_undefined_flag -o $lib $libobjs $deplibs $compiler_flags ${wl}-install_name ${wl}$rpath/$soname $xlcverstring~nmedit -s $output_objdir/${libname}-symbols.expsym ${lib}' + module_expsym_cmds='sed -e "s,#.*,," -e "s,^[ ]*,," -e "s,^\(..*\),_&," < $export_symbols > $output_objdir/${libname}-symbols.expsym~$CC $allow_undefined_flag -o $lib -bundle $libobjs $deplibs$compiler_flags~nmedit -s $output_objdir/${libname}-symbols.expsym ${lib}' + ;; + *) + ld_shlibs=no + ;; + esac + fi + ;; + + dgux*) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_libdir_flag_spec='-L$libdir' + hardcode_shlibpath_var=no + ;; + + freebsd1*) + ld_shlibs=no + ;; + + # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor + # support. Future versions do this automatically, but an explicit c++rt0.o + # does not break anything, and helps significantly (at the cost of a little + # extra space). + freebsd2.2*) + archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags /usr/lib/c++rt0.o' + hardcode_libdir_flag_spec='-R$libdir' + hardcode_direct=yes + hardcode_shlibpath_var=no + ;; + + # Unfortunately, older versions of FreeBSD 2 do not have this feature. + freebsd2*) + archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' + hardcode_direct=yes + hardcode_minus_L=yes + hardcode_shlibpath_var=no + ;; + + # FreeBSD 3 and greater uses gcc -shared to do shared libraries. + freebsd* | dragonfly*) + archive_cmds='$CC -shared -o $lib $libobjs $deplibs $compiler_flags' + hardcode_libdir_flag_spec='-R$libdir' + hardcode_direct=yes + hardcode_shlibpath_var=no + ;; + + hpux9*) + if test "$GCC" = yes; then + archive_cmds='$rm $output_objdir/$soname~$CC -shared -fPIC ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $libobjs $deplibs $compiler_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib' + else + archive_cmds='$rm $output_objdir/$soname~$LD -b +b $install_libdir -o $output_objdir/$soname $libobjs $deplibs $linker_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib' + fi + hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir' + hardcode_libdir_separator=: + hardcode_direct=yes + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + hardcode_minus_L=yes + export_dynamic_flag_spec='${wl}-E' + ;; + + hpux10*) + if test "$GCC" = yes -a "$with_gnu_ld" = no; then + archive_cmds='$CC -shared -fPIC ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + else + archive_cmds='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags' + fi + if test "$with_gnu_ld" = no; then + hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir' + hardcode_libdir_separator=: + + hardcode_direct=yes + export_dynamic_flag_spec='${wl}-E' + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + hardcode_minus_L=yes + fi + ;; + + hpux11*) + if test "$GCC" = yes -a "$with_gnu_ld" = no; then + case $host_cpu in + hppa*64*) + archive_cmds='$CC -shared ${wl}+h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + ia64*) + archive_cmds='$CC -shared ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + archive_cmds='$CC -shared -fPIC ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + else + case $host_cpu in + hppa*64*) + archive_cmds='$CC -b ${wl}+h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + ia64*) + archive_cmds='$CC -b ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + archive_cmds='$CC -b ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + fi + if test "$with_gnu_ld" = no; then + hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir' + hardcode_libdir_separator=: + + case $host_cpu in + hppa*64*|ia64*) + hardcode_libdir_flag_spec_ld='+b $libdir' + hardcode_direct=no + hardcode_shlibpath_var=no + ;; + *) + hardcode_direct=yes + export_dynamic_flag_spec='${wl}-E' + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + hardcode_minus_L=yes + ;; + esac + fi + ;; + + irix5* | irix6* | nonstopux*) + if test "$GCC" = yes; then + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + else + archive_cmds='$LD -shared $libobjs $deplibs $linker_flags -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib' + hardcode_libdir_flag_spec_ld='-rpath $libdir' + fi + hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' + hardcode_libdir_separator=: + link_all_deplibs=yes + ;; + + netbsd*) + if echo __ELF__ | $CC -E - | grep __ELF__ >/dev/null; then + archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' # a.out + else + archive_cmds='$LD -shared -o $lib $libobjs $deplibs $linker_flags' # ELF + fi + hardcode_libdir_flag_spec='-R$libdir' + hardcode_direct=yes + hardcode_shlibpath_var=no + ;; + + newsos6) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_direct=yes + hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' + hardcode_libdir_separator=: + hardcode_shlibpath_var=no + ;; + + openbsd*) + if test -f /usr/libexec/ld.so; then + hardcode_direct=yes + hardcode_shlibpath_var=no + if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then + archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags ${wl}-retain-symbols-file,$export_symbols' + hardcode_libdir_flag_spec='${wl}-rpath,$libdir' + export_dynamic_flag_spec='${wl}-E' + else + case $host_os in + openbsd[01].* | openbsd2.[0-7] | openbsd2.[0-7].*) + archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' + hardcode_libdir_flag_spec='-R$libdir' + ;; + *) + archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + hardcode_libdir_flag_spec='${wl}-rpath,$libdir' + ;; + esac + fi + else + ld_shlibs=no + fi + ;; + + os2*) + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + allow_undefined_flag=unsupported + archive_cmds='$echo "LIBRARY $libname INITINSTANCE" > $output_objdir/$libname.def~$echo "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~$echo DATA >> $output_objdir/$libname.def~$echo " SINGLE NONSHARED" >> $output_objdir/$libname.def~$echo EXPORTS >> $output_objdir/$libname.def~emxexp $libobjs >> $output_objdir/$libname.def~$CC -Zdll -Zcrtdll -o $lib $libobjs $deplibs $compiler_flags $output_objdir/$libname.def' + old_archive_From_new_cmds='emximp -o $output_objdir/$libname.a $output_objdir/$libname.def' + ;; + + osf3*) + if test "$GCC" = yes; then + allow_undefined_flag=' ${wl}-expect_unresolved ${wl}\*' + archive_cmds='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + else + allow_undefined_flag=' -expect_unresolved \*' + archive_cmds='$LD -shared${allow_undefined_flag} $libobjs $deplibs $linker_flags -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib' + fi + hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' + hardcode_libdir_separator=: + ;; + + osf4* | osf5*) # as osf3* with the addition of -msym flag + if test "$GCC" = yes; then + allow_undefined_flag=' ${wl}-expect_unresolved ${wl}\*' + archive_cmds='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags ${wl}-msym ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' + else + allow_undefined_flag=' -expect_unresolved \*' + archive_cmds='$LD -shared${allow_undefined_flag} $libobjs $deplibs $linker_flags -msym -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib' + archive_expsym_cmds='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done; echo "-hidden">> $lib.exp~ + $LD -shared${allow_undefined_flag} -input $lib.exp $linker_flags $libobjs $deplibs -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib~$rm $lib.exp' + + # Both c and cxx compiler support -rpath directly + hardcode_libdir_flag_spec='-rpath $libdir' + fi + hardcode_libdir_separator=: + ;; + + solaris*) + no_undefined_flag=' -z text' + if test "$GCC" = yes; then + wlarc='${wl}' + archive_cmds='$CC -shared ${wl}-h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='$echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~$echo "local: *; };" >> $lib.exp~ + $CC -shared ${wl}-M ${wl}$lib.exp ${wl}-h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags~$rm $lib.exp' + else + wlarc='' + archive_cmds='$LD -G${allow_undefined_flag} -h $soname -o $lib $libobjs $deplibs $linker_flags' + archive_expsym_cmds='$echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~$echo "local: *; };" >> $lib.exp~ + $LD -G${allow_undefined_flag} -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$rm $lib.exp' + fi + hardcode_libdir_flag_spec='-R$libdir' + hardcode_shlibpath_var=no + case $host_os in + solaris2.[0-5] | solaris2.[0-5].*) ;; + *) + # The compiler driver will combine and reorder linker options, + # but understands `-z linker_flag'. GCC discards it without `$wl', + # but is careful enough not to reorder. + # Supported since Solaris 2.6 (maybe 2.5.1?) + if test "$GCC" = yes; then + whole_archive_flag_spec='${wl}-z ${wl}allextract$convenience ${wl}-z ${wl}defaultextract' + else + whole_archive_flag_spec='-z allextract$convenience -z defaultextract' + fi + ;; + esac + link_all_deplibs=yes + ;; + + sunos4*) + if test "x$host_vendor" = xsequent; then + # Use $CC to link under sequent, because it throws in some extra .o + # files that make .init and .fini sections work. + archive_cmds='$CC -G ${wl}-h $soname -o $lib $libobjs $deplibs $compiler_flags' + else + archive_cmds='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linker_flags' + fi + hardcode_libdir_flag_spec='-L$libdir' + hardcode_direct=yes + hardcode_minus_L=yes + hardcode_shlibpath_var=no + ;; + + sysv4) + case $host_vendor in + sni) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_direct=yes # is this really true??? + ;; + siemens) + ## LD is ld it makes a PLAMLIB + ## CC just makes a GrossModule. + archive_cmds='$LD -G -o $lib $libobjs $deplibs $linker_flags' + reload_cmds='$CC -r -o $output$reload_objs' + hardcode_direct=no + ;; + motorola) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_direct=no #Motorola manual says yes, but my tests say they lie + ;; + esac + runpath_var='LD_RUN_PATH' + hardcode_shlibpath_var=no + ;; + + sysv4.3*) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_shlibpath_var=no + export_dynamic_flag_spec='-Bexport' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_shlibpath_var=no + runpath_var=LD_RUN_PATH + hardcode_runpath_var=yes + ld_shlibs=yes + fi + ;; + + sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[01].[10]* | unixware7* | sco3.2v5.0.[024]*) + no_undefined_flag='${wl}-z,text' + archive_cmds_need_lc=no + hardcode_shlibpath_var=no + runpath_var='LD_RUN_PATH' + + if test "$GCC" = yes; then + archive_cmds='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + else + archive_cmds='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + fi + ;; + + sysv5* | sco3.2v5* | sco5v6*) + # Note: We can NOT use -z defs as we might desire, because we do not + # link with -lc, and that would cause any symbols used from libc to + # always be unresolved, which means just about no library would + # ever link correctly. If we're not using GNU ld we use -z text + # though, which does catch some bad symbols but isn't as heavy-handed + # as -z defs. + no_undefined_flag='${wl}-z,text' + allow_undefined_flag='${wl}-z,nodefs' + archive_cmds_need_lc=no + hardcode_shlibpath_var=no + hardcode_libdir_flag_spec='`test -z "$SCOABSPATH" && echo ${wl}-R,$libdir`' + hardcode_libdir_separator=':' + link_all_deplibs=yes + export_dynamic_flag_spec='${wl}-Bexport' + runpath_var='LD_RUN_PATH' + + if test "$GCC" = yes; then + archive_cmds='$CC -shared ${wl}-h,\${SCOABSPATH:+${install_libdir}/}$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,\${SCOABSPATH:+${install_libdir}/}$soname -o $lib $libobjs $deplibs $compiler_flags' + else + archive_cmds='$CC -G ${wl}-h,\${SCOABSPATH:+${install_libdir}/}$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,\${SCOABSPATH:+${install_libdir}/}$soname -o $lib $libobjs $deplibs $compiler_flags' + fi + ;; + + uts4*) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_libdir_flag_spec='-L$libdir' + hardcode_shlibpath_var=no + ;; + + *) + ld_shlibs=no + ;; + esac + fi + +{ $as_echo "$as_me:$LINENO: result: $ld_shlibs" >&5 +$as_echo "$ld_shlibs" >&6; } +test "$ld_shlibs" = no && can_build_shared=no + +# +# Do we need to explicitly link libc? +# +case "x$archive_cmds_need_lc" in +x|xyes) + # Assume -lc should be added + archive_cmds_need_lc=yes + + if test "$enable_shared" = yes && test "$GCC" = yes; then + case $archive_cmds in + *'~'*) + # FIXME: we may have to deal with multi-command sequences. + ;; + '$CC '*) + # Test whether the compiler implicitly links with -lc since on some + # systems, -lgcc has to come before -lc. If gcc already passes -lc + # to ld, don't add -lc before -lgcc. + { $as_echo "$as_me:$LINENO: checking whether -lc should be explicitly linked in" >&5 +$as_echo_n "checking whether -lc should be explicitly linked in... " >&6; } + $rm conftest* + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } 2>conftest.err; then + soname=conftest + lib=conftest + libobjs=conftest.$ac_objext + deplibs= + wl=$lt_prog_compiler_wl + pic_flag=$lt_prog_compiler_pic + compiler_flags=-v + linker_flags=-v + verstring= + output_objdir=. + libname=conftest + lt_save_allow_undefined_flag=$allow_undefined_flag + allow_undefined_flag= + if { (eval echo "$as_me:$LINENO: \"$archive_cmds 2\>\&1 \| grep \" -lc \" \>/dev/null 2\>\&1\"") >&5 + (eval $archive_cmds 2\>\&1 \| grep \" -lc \" \>/dev/null 2\>\&1) 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } + then + archive_cmds_need_lc=no + else + archive_cmds_need_lc=yes + fi + allow_undefined_flag=$lt_save_allow_undefined_flag + else + cat conftest.err 1>&5 + fi + $rm conftest* + { $as_echo "$as_me:$LINENO: result: $archive_cmds_need_lc" >&5 +$as_echo "$archive_cmds_need_lc" >&6; } + ;; + esac + fi + ;; +esac + +{ $as_echo "$as_me:$LINENO: checking dynamic linker characteristics" >&5 +$as_echo_n "checking dynamic linker characteristics... " >&6; } +library_names_spec= +libname_spec='lib$name' +soname_spec= +shrext_cmds=".so" +postinstall_cmds= +postuninstall_cmds= +finish_cmds= +finish_eval= +shlibpath_var= +shlibpath_overrides_runpath=unknown +version_type=none +dynamic_linker="$host_os ld.so" +sys_lib_dlsearch_path_spec="/lib /usr/lib" + +if test "$GCC" = yes; then + case $host_os in + darwin*) lt_awk_arg="/^libraries:/,/LR/" ;; + *) lt_awk_arg="/^libraries:/" ;; + esac + lt_search_path_spec=`$CC -print-search-dirs | awk $lt_awk_arg | $SED -e "s/^libraries://" -e "s,=/,/,g"` + if echo "$lt_search_path_spec" | grep ';' >/dev/null ; then + # if the path contains ";" then we assume it to be the separator + # otherwise default to the standard path separator (i.e. ":") - it is + # assumed that no part of a normal pathname contains ";" but that should + # okay in the real world where ";" in dirpaths is itself problematic. + lt_search_path_spec=`echo "$lt_search_path_spec" | $SED -e 's/;/ /g'` + else + lt_search_path_spec=`echo "$lt_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + fi + # Ok, now we have the path, separated by spaces, we can step through it + # and add multilib dir if necessary. + lt_tmp_lt_search_path_spec= + lt_multi_os_dir=`$CC $CPPFLAGS $CFLAGS $LDFLAGS -print-multi-os-directory 2>/dev/null` + for lt_sys_path in $lt_search_path_spec; do + if test -d "$lt_sys_path/$lt_multi_os_dir"; then + lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path/$lt_multi_os_dir" + else + test -d "$lt_sys_path" && \ + lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path" + fi + done + lt_search_path_spec=`echo $lt_tmp_lt_search_path_spec | awk ' +BEGIN {RS=" "; FS="/|\n";} { + lt_foo=""; + lt_count=0; + for (lt_i = NF; lt_i > 0; lt_i--) { + if ($lt_i != "" && $lt_i != ".") { + if ($lt_i == "..") { + lt_count++; + } else { + if (lt_count == 0) { + lt_foo="/" $lt_i lt_foo; + } else { + lt_count--; + } + } + } + } + if (lt_foo != "") { lt_freq[lt_foo]++; } + if (lt_freq[lt_foo] == 1) { print lt_foo; } +}'` + sys_lib_search_path_spec=`echo $lt_search_path_spec` +else + sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" +fi +need_lib_prefix=unknown +hardcode_into_libs=no + +# when you set need_version to no, make sure it does not cause -set_version +# flags to be left without arguments +need_version=unknown + +case $host_os in +aix3*) + version_type=linux + library_names_spec='${libname}${release}${shared_ext}$versuffix $libname.a' + shlibpath_var=LIBPATH + + # AIX 3 has no versioning support, so we append a major version to the name. + soname_spec='${libname}${release}${shared_ext}$major' + ;; + +aix[4-9]*) + version_type=linux + need_lib_prefix=no + need_version=no + hardcode_into_libs=yes + if test "$host_cpu" = ia64; then + # AIX 5 supports IA64 + library_names_spec='${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext}$versuffix $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + else + # With GCC up to 2.95.x, collect2 would create an import file + # for dependence libraries. The import file would start with + # the line `#! .'. This would cause the generated library to + # depend on `.', always an invalid library. This was fixed in + # development snapshots of GCC prior to 3.0. + case $host_os in + aix4 | aix4.[01] | aix4.[01].*) + if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)' + echo ' yes ' + echo '#endif'; } | ${CC} -E - | grep yes > /dev/null; then + : + else + can_build_shared=no + fi + ;; + esac + # AIX (on Power*) has no versioning support, so currently we can not hardcode correct + # soname into executable. Probably we can add versioning support to + # collect2, so additional links can be useful in future. + if test "$aix_use_runtimelinking" = yes; then + # If using run time linking (on AIX 4.2 or later) use lib.so + # instead of lib.a to let people know that these are not + # typical AIX shared libraries. + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + else + # We preserve .a as extension for shared libraries through AIX4.2 + # and later when we are not doing run time linking. + library_names_spec='${libname}${release}.a $libname.a' + soname_spec='${libname}${release}${shared_ext}$major' + fi + shlibpath_var=LIBPATH + fi + ;; + +amigaos*) + library_names_spec='$libname.ixlibrary $libname.a' + # Create ${libname}_ixlibrary.a entries in /sys/libs. + finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`$echo "X$lib" | $Xsed -e '\''s%^.*/\([^/]*\)\.ixlibrary$%\1%'\''`; test $rm /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done' + ;; + +beos*) + library_names_spec='${libname}${shared_ext}' + dynamic_linker="$host_os ld.so" + shlibpath_var=LIBRARY_PATH + ;; + +bsdi[45]*) + version_type=linux + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib" + sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib" + # the default ld.so.conf also contains /usr/contrib/lib and + # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow + # libtool to hard-code these into programs + ;; + +cygwin* | mingw* | pw32*) + version_type=windows + shrext_cmds=".dll" + need_version=no + need_lib_prefix=no + + case $GCC,$host_os in + yes,cygwin* | yes,mingw* | yes,pw32*) + library_names_spec='$libname.dll.a' + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \${file}`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\${base_file}'\''i;echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname~ + chmod a+x \$dldir/$dlname' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $rm \$dlpath' + shlibpath_overrides_runpath=yes + + case $host_os in + cygwin*) + # Cygwin DLLs use 'cyg' prefix rather than 'lib' + soname_spec='`echo ${libname} | sed -e 's/^lib/cyg/'``echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}' + sys_lib_search_path_spec="/usr/lib /lib/w32api /lib /usr/local/lib" + ;; + mingw*) + # MinGW DLLs use traditional 'lib' prefix + soname_spec='${libname}`echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}' + sys_lib_search_path_spec=`$CC -print-search-dirs | grep "^libraries:" | $SED -e "s/^libraries://" -e "s,=/,/,g"` + if echo "$sys_lib_search_path_spec" | grep ';[c-zC-Z]:/' >/dev/null; then + # It is most probably a Windows format PATH printed by + # mingw gcc, but we are running on Cygwin. Gcc prints its search + # path with ; separators, and with drive letters. We can handle the + # drive letters (cygwin fileutils understands them), so leave them, + # especially as we might pass files found there to a mingw objdump, + # which wouldn't understand a cygwinified path. Ahh. + sys_lib_search_path_spec=`echo "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'` + else + sys_lib_search_path_spec=`echo "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + fi + ;; + pw32*) + # pw32 DLLs use 'pw' prefix rather than 'lib' + library_names_spec='`echo ${libname} | sed -e 's/^lib/pw/'``echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}' + ;; + esac + ;; + + *) + library_names_spec='${libname}`echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext} $libname.lib' + ;; + esac + dynamic_linker='Win32 ld.exe' + # FIXME: first we should search . and the directory the executable is in + shlibpath_var=PATH + ;; + +darwin* | rhapsody*) + dynamic_linker="$host_os dyld" + version_type=darwin + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${versuffix}$shared_ext ${libname}${release}${major}$shared_ext ${libname}$shared_ext' + soname_spec='${libname}${release}${major}$shared_ext' + shlibpath_overrides_runpath=yes + shlibpath_var=DYLD_LIBRARY_PATH + shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`' + + sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/local/lib" + sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib' + ;; + +dgux*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname$shared_ext' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +freebsd1*) + dynamic_linker=no + ;; + +freebsd* | dragonfly*) + # DragonFly does not have aout. When/if they implement a new + # versioning mechanism, adjust this. + if test -x /usr/bin/objformat; then + objformat=`/usr/bin/objformat` + else + case $host_os in + freebsd[123]*) objformat=aout ;; + *) objformat=elf ;; + esac + fi + version_type=freebsd-$objformat + case $version_type in + freebsd-elf*) + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext} $libname${shared_ext}' + need_version=no + need_lib_prefix=no + ;; + freebsd-*) + library_names_spec='${libname}${release}${shared_ext}$versuffix $libname${shared_ext}$versuffix' + need_version=yes + ;; + esac + shlibpath_var=LD_LIBRARY_PATH + case $host_os in + freebsd2*) + shlibpath_overrides_runpath=yes + ;; + freebsd3.[01]* | freebsdelf3.[01]*) + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + freebsd3.[2-9]* | freebsdelf3.[2-9]* | \ + freebsd4.[0-5] | freebsdelf4.[0-5] | freebsd4.1.1 | freebsdelf4.1.1) + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + *) # from 4.6 on, and DragonFly + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + esac + ;; + +gnu*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}${major} ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + hardcode_into_libs=yes + ;; + +hpux9* | hpux10* | hpux11*) + # Give a soname corresponding to the major version so that dld.sl refuses to + # link against other versions. + version_type=sunos + need_lib_prefix=no + need_version=no + case $host_cpu in + ia64*) + shrext_cmds='.so' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.so" + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + if test "X$HPUX_IA64_MODE" = X32; then + sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib" + else + sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64" + fi + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + hppa*64*) + shrext_cmds='.sl' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.sl" + shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + *) + shrext_cmds='.sl' + dynamic_linker="$host_os dld.sl" + shlibpath_var=SHLIB_PATH + shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + ;; + esac + # HP-UX runs *really* slowly unless shared libraries are mode 555. + postinstall_cmds='chmod 555 $lib' + ;; + +interix[3-9]*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +irix5* | irix6* | nonstopux*) + case $host_os in + nonstopux*) version_type=nonstopux ;; + *) + if test "$lt_cv_prog_gnu_ld" = yes; then + version_type=linux + else + version_type=irix + fi ;; + esac + need_lib_prefix=no + need_version=no + soname_spec='${libname}${release}${shared_ext}$major' + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext} $libname${shared_ext}' + case $host_os in + irix5* | nonstopux*) + libsuff= shlibsuff= + ;; + *) + case $LD in # libtool.m4 will add one of these switches to LD + *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") + libsuff= shlibsuff= libmagic=32-bit;; + *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") + libsuff=32 shlibsuff=N32 libmagic=N32;; + *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") + libsuff=64 shlibsuff=64 libmagic=64-bit;; + *) libsuff= shlibsuff= libmagic=never-match;; + esac + ;; + esac + shlibpath_var=LD_LIBRARY${shlibsuff}_PATH + shlibpath_overrides_runpath=no + sys_lib_search_path_spec="/usr/lib${libsuff} /lib${libsuff} /usr/local/lib${libsuff}" + sys_lib_dlsearch_path_spec="/usr/lib${libsuff} /lib${libsuff}" + hardcode_into_libs=yes + ;; + +# No shared lib support for Linux oldld, aout, or coff. +linux*oldld* | linux*aout* | linux*coff*) + dynamic_linker=no + ;; + +# This must be Linux ELF. +linux* | k*bsd*-gnu) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + # This implies no fast_install, which is unacceptable. + # Some rework will be needed to allow for fast_install + # before this can be enabled. + hardcode_into_libs=yes + + # Append ld.so.conf contents to the search path + if test -f /etc/ld.so.conf; then + lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \$2)); skip = 1; } { if (!skip) print \$0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[ ]*hwcap[ ]/d;s/[:, ]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;/^$/d' | tr '\n' ' '` + sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra" + fi + + # We used to test for /lib/ld.so.1 and disable shared libraries on + # powerpc, because MkLinux only supported shared libraries with the + # GNU dynamic linker. Since this was broken with cross compilers, + # most powerpc-linux boxes support dynamic linking these days and + # people can always --disable-shared, the test was removed, and we + # assume the GNU/Linux dynamic linker is in use. + dynamic_linker='GNU/Linux ld.so' + ;; + +netbsd*) + version_type=sunos + need_lib_prefix=no + need_version=no + if echo __ELF__ | $CC -E - | grep __ELF__ >/dev/null; then + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + dynamic_linker='NetBSD (a.out) ld.so' + else + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + dynamic_linker='NetBSD ld.elf_so' + fi + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + +newsos6) + version_type=linux + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +nto-qnx*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +openbsd*) + version_type=sunos + sys_lib_dlsearch_path_spec="/usr/lib" + need_lib_prefix=no + # Some older versions of OpenBSD (3.3 at least) *do* need versioned libs. + case $host_os in + openbsd3.3 | openbsd3.3.*) need_version=yes ;; + *) need_version=no ;; + esac + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + shlibpath_var=LD_LIBRARY_PATH + if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then + case $host_os in + openbsd2.[89] | openbsd2.[89].*) + shlibpath_overrides_runpath=no + ;; + *) + shlibpath_overrides_runpath=yes + ;; + esac + else + shlibpath_overrides_runpath=yes + fi + ;; + +os2*) + libname_spec='$name' + shrext_cmds=".dll" + need_lib_prefix=no + library_names_spec='$libname${shared_ext} $libname.a' + dynamic_linker='OS/2 ld.exe' + shlibpath_var=LIBPATH + ;; + +osf3* | osf4* | osf5*) + version_type=osf + need_lib_prefix=no + need_version=no + soname_spec='${libname}${release}${shared_ext}$major' + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib" + sys_lib_dlsearch_path_spec="$sys_lib_search_path_spec" + ;; + +rdos*) + dynamic_linker=no + ;; + +solaris*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + # ldd complains unless libraries are executable + postinstall_cmds='chmod +x $lib' + ;; + +sunos4*) + version_type=sunos + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix' + finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + if test "$with_gnu_ld" = yes; then + need_lib_prefix=no + fi + need_version=yes + ;; + +sysv4 | sysv4.3*) + version_type=linux + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + case $host_vendor in + sni) + shlibpath_overrides_runpath=no + need_lib_prefix=no + export_dynamic_flag_spec='${wl}-Blargedynsym' + runpath_var=LD_RUN_PATH + ;; + siemens) + need_lib_prefix=no + ;; + motorola) + need_lib_prefix=no + need_version=no + shlibpath_overrides_runpath=no + sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib' + ;; + esac + ;; + +sysv4*MP*) + if test -d /usr/nec ;then + version_type=linux + library_names_spec='$libname${shared_ext}.$versuffix $libname${shared_ext}.$major $libname${shared_ext}' + soname_spec='$libname${shared_ext}.$major' + shlibpath_var=LD_LIBRARY_PATH + fi + ;; + +sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + version_type=freebsd-elf + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext} $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + hardcode_into_libs=yes + if test "$with_gnu_ld" = yes; then + sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib' + shlibpath_overrides_runpath=no + else + sys_lib_search_path_spec='/usr/ccs/lib /usr/lib' + shlibpath_overrides_runpath=yes + case $host_os in + sco3.2v5*) + sys_lib_search_path_spec="$sys_lib_search_path_spec /lib" + ;; + esac + fi + sys_lib_dlsearch_path_spec='/usr/lib' + ;; + +uts4*) + version_type=linux + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +*) + dynamic_linker=no + ;; +esac +{ $as_echo "$as_me:$LINENO: result: $dynamic_linker" >&5 +$as_echo "$dynamic_linker" >&6; } +test "$dynamic_linker" = no && can_build_shared=no + +if test "${lt_cv_sys_lib_search_path_spec+set}" = set; then + $as_echo_n "(cached) " >&6 +else + lt_cv_sys_lib_search_path_spec="$sys_lib_search_path_spec" +fi + +sys_lib_search_path_spec="$lt_cv_sys_lib_search_path_spec" +if test "${lt_cv_sys_lib_dlsearch_path_spec+set}" = set; then + $as_echo_n "(cached) " >&6 +else + lt_cv_sys_lib_dlsearch_path_spec="$sys_lib_dlsearch_path_spec" +fi + +sys_lib_dlsearch_path_spec="$lt_cv_sys_lib_dlsearch_path_spec" + +variables_saved_for_relink="PATH $shlibpath_var $runpath_var" +if test "$GCC" = yes; then + variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH" +fi + +{ $as_echo "$as_me:$LINENO: checking how to hardcode library paths into programs" >&5 +$as_echo_n "checking how to hardcode library paths into programs... " >&6; } +hardcode_action= +if test -n "$hardcode_libdir_flag_spec" || \ + test -n "$runpath_var" || \ + test "X$hardcode_automatic" = "Xyes" ; then + + # We can hardcode non-existant directories. + if test "$hardcode_direct" != no && + # If the only mechanism to avoid hardcoding is shlibpath_var, we + # have to relink, otherwise we might link with an installed library + # when we should be linking with a yet-to-be-installed one + ## test "$_LT_AC_TAGVAR(hardcode_shlibpath_var, )" != no && + test "$hardcode_minus_L" != no; then + # Linking always hardcodes the temporary library directory. + hardcode_action=relink + else + # We can link without hardcoding, and we can hardcode nonexisting dirs. + hardcode_action=immediate + fi +else + # We cannot hardcode anything, or else we can only hardcode existing + # directories. + hardcode_action=unsupported +fi +{ $as_echo "$as_me:$LINENO: result: $hardcode_action" >&5 +$as_echo "$hardcode_action" >&6; } + +if test "$hardcode_action" = relink; then + # Fast installation is not supported + enable_fast_install=no +elif test "$shlibpath_overrides_runpath" = yes || + test "$enable_shared" = no; then + # Fast installation is not necessary + enable_fast_install=needless +fi + +striplib= +old_striplib= +{ $as_echo "$as_me:$LINENO: checking whether stripping libraries is possible" >&5 +$as_echo_n "checking whether stripping libraries is possible... " >&6; } +if test -n "$STRIP" && $STRIP -V 2>&1 | grep "GNU strip" >/dev/null; then + test -z "$old_striplib" && old_striplib="$STRIP --strip-debug" + test -z "$striplib" && striplib="$STRIP --strip-unneeded" + { $as_echo "$as_me:$LINENO: result: yes" >&5 +$as_echo "yes" >&6; } +else +# FIXME - insert some real tests, host_os isn't really good enough + case $host_os in + darwin*) + if test -n "$STRIP" ; then + striplib="$STRIP -x" + old_striplib="$STRIP -S" + { $as_echo "$as_me:$LINENO: result: yes" >&5 +$as_echo "yes" >&6; } + else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi + ;; + *) + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } + ;; + esac +fi + +if test "x$enable_dlopen" != xyes; then + enable_dlopen=unknown + enable_dlopen_self=unknown + enable_dlopen_self_static=unknown +else + lt_cv_dlopen=no + lt_cv_dlopen_libs= + + case $host_os in + beos*) + lt_cv_dlopen="load_add_on" + lt_cv_dlopen_libs= + lt_cv_dlopen_self=yes + ;; + + mingw* | pw32*) + lt_cv_dlopen="LoadLibrary" + lt_cv_dlopen_libs= + ;; + + cygwin*) + lt_cv_dlopen="dlopen" + lt_cv_dlopen_libs= + ;; + + darwin*) + # if libdl is installed we need to link against it + { $as_echo "$as_me:$LINENO: checking for dlopen in -ldl" >&5 +$as_echo_n "checking for dlopen in -ldl... " >&6; } +if test "${ac_cv_lib_dl_dlopen+set}" = set; then + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldl $LIBS" +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dlopen (); +int +main () +{ +return dlopen (); + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + $as_test_x conftest$ac_exeext + }; then + ac_cv_lib_dl_dlopen=yes +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_cv_lib_dl_dlopen=no +fi + +rm -rf conftest.dSYM +rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_lib_dl_dlopen" >&5 +$as_echo "$ac_cv_lib_dl_dlopen" >&6; } +if test "x$ac_cv_lib_dl_dlopen" = x""yes; then + lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-ldl" +else + + lt_cv_dlopen="dyld" + lt_cv_dlopen_libs= + lt_cv_dlopen_self=yes + +fi + + ;; + + *) + { $as_echo "$as_me:$LINENO: checking for shl_load" >&5 +$as_echo_n "checking for shl_load... " >&6; } +if test "${ac_cv_func_shl_load+set}" = set; then + $as_echo_n "(cached) " >&6 +else + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +/* Define shl_load to an innocuous variant, in case declares shl_load. + For example, HP-UX 11i declares gettimeofday. */ +#define shl_load innocuous_shl_load + +/* System header to define __stub macros and hopefully few prototypes, + which can conflict with char shl_load (); below. + Prefer to if __STDC__ is defined, since + exists even on freestanding compilers. */ + +#ifdef __STDC__ +# include +#else +# include +#endif + +#undef shl_load + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char shl_load (); +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined __stub_shl_load || defined __stub___shl_load +choke me +#endif + +int +main () +{ +return shl_load (); + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + $as_test_x conftest$ac_exeext + }; then + ac_cv_func_shl_load=yes +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_cv_func_shl_load=no +fi + +rm -rf conftest.dSYM +rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ + conftest$ac_exeext conftest.$ac_ext +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_func_shl_load" >&5 +$as_echo "$ac_cv_func_shl_load" >&6; } +if test "x$ac_cv_func_shl_load" = x""yes; then + lt_cv_dlopen="shl_load" +else + { $as_echo "$as_me:$LINENO: checking for shl_load in -ldld" >&5 +$as_echo_n "checking for shl_load in -ldld... " >&6; } +if test "${ac_cv_lib_dld_shl_load+set}" = set; then + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldld $LIBS" +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char shl_load (); +int +main () +{ +return shl_load (); + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + $as_test_x conftest$ac_exeext + }; then + ac_cv_lib_dld_shl_load=yes +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_cv_lib_dld_shl_load=no +fi + +rm -rf conftest.dSYM +rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_lib_dld_shl_load" >&5 +$as_echo "$ac_cv_lib_dld_shl_load" >&6; } +if test "x$ac_cv_lib_dld_shl_load" = x""yes; then + lt_cv_dlopen="shl_load" lt_cv_dlopen_libs="-ldld" +else + { $as_echo "$as_me:$LINENO: checking for dlopen" >&5 +$as_echo_n "checking for dlopen... " >&6; } +if test "${ac_cv_func_dlopen+set}" = set; then + $as_echo_n "(cached) " >&6 +else + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +/* Define dlopen to an innocuous variant, in case declares dlopen. + For example, HP-UX 11i declares gettimeofday. */ +#define dlopen innocuous_dlopen + +/* System header to define __stub macros and hopefully few prototypes, + which can conflict with char dlopen (); below. + Prefer to if __STDC__ is defined, since + exists even on freestanding compilers. */ + +#ifdef __STDC__ +# include +#else +# include +#endif + +#undef dlopen + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dlopen (); +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined __stub_dlopen || defined __stub___dlopen +choke me +#endif + +int +main () +{ +return dlopen (); + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + $as_test_x conftest$ac_exeext + }; then + ac_cv_func_dlopen=yes +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_cv_func_dlopen=no +fi + +rm -rf conftest.dSYM +rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ + conftest$ac_exeext conftest.$ac_ext +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_func_dlopen" >&5 +$as_echo "$ac_cv_func_dlopen" >&6; } +if test "x$ac_cv_func_dlopen" = x""yes; then + lt_cv_dlopen="dlopen" +else + { $as_echo "$as_me:$LINENO: checking for dlopen in -ldl" >&5 +$as_echo_n "checking for dlopen in -ldl... " >&6; } +if test "${ac_cv_lib_dl_dlopen+set}" = set; then + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldl $LIBS" +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dlopen (); +int +main () +{ +return dlopen (); + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + $as_test_x conftest$ac_exeext + }; then + ac_cv_lib_dl_dlopen=yes +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_cv_lib_dl_dlopen=no +fi + +rm -rf conftest.dSYM +rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_lib_dl_dlopen" >&5 +$as_echo "$ac_cv_lib_dl_dlopen" >&6; } +if test "x$ac_cv_lib_dl_dlopen" = x""yes; then + lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-ldl" +else + { $as_echo "$as_me:$LINENO: checking for dlopen in -lsvld" >&5 +$as_echo_n "checking for dlopen in -lsvld... " >&6; } +if test "${ac_cv_lib_svld_dlopen+set}" = set; then + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lsvld $LIBS" +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dlopen (); +int +main () +{ +return dlopen (); + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + $as_test_x conftest$ac_exeext + }; then + ac_cv_lib_svld_dlopen=yes +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_cv_lib_svld_dlopen=no +fi + +rm -rf conftest.dSYM +rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_lib_svld_dlopen" >&5 +$as_echo "$ac_cv_lib_svld_dlopen" >&6; } +if test "x$ac_cv_lib_svld_dlopen" = x""yes; then + lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-lsvld" +else + { $as_echo "$as_me:$LINENO: checking for dld_link in -ldld" >&5 +$as_echo_n "checking for dld_link in -ldld... " >&6; } +if test "${ac_cv_lib_dld_dld_link+set}" = set; then + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldld $LIBS" +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dld_link (); +int +main () +{ +return dld_link (); + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + $as_test_x conftest$ac_exeext + }; then + ac_cv_lib_dld_dld_link=yes +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_cv_lib_dld_dld_link=no +fi + +rm -rf conftest.dSYM +rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_lib_dld_dld_link" >&5 +$as_echo "$ac_cv_lib_dld_dld_link" >&6; } +if test "x$ac_cv_lib_dld_dld_link" = x""yes; then + lt_cv_dlopen="dld_link" lt_cv_dlopen_libs="-ldld" +fi + + +fi + + +fi + + +fi + + +fi + + +fi + + ;; + esac + + if test "x$lt_cv_dlopen" != xno; then + enable_dlopen=yes + else + enable_dlopen=no + fi + + case $lt_cv_dlopen in + dlopen) + save_CPPFLAGS="$CPPFLAGS" + test "x$ac_cv_header_dlfcn_h" = xyes && CPPFLAGS="$CPPFLAGS -DHAVE_DLFCN_H" + + save_LDFLAGS="$LDFLAGS" + wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $export_dynamic_flag_spec\" + + save_LIBS="$LIBS" + LIBS="$lt_cv_dlopen_libs $LIBS" + + { $as_echo "$as_me:$LINENO: checking whether a program can dlopen itself" >&5 +$as_echo_n "checking whether a program can dlopen itself... " >&6; } +if test "${lt_cv_dlopen_self+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test "$cross_compiling" = yes; then : + lt_cv_dlopen_self=cross +else + lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 + lt_status=$lt_dlunknown + cat > conftest.$ac_ext < +#endif + +#include + +#ifdef RTLD_GLOBAL +# define LT_DLGLOBAL RTLD_GLOBAL +#else +# ifdef DL_GLOBAL +# define LT_DLGLOBAL DL_GLOBAL +# else +# define LT_DLGLOBAL 0 +# endif +#endif + +/* We may have to define LT_DLLAZY_OR_NOW in the command line if we + find out it does not work in some platform. */ +#ifndef LT_DLLAZY_OR_NOW +# ifdef RTLD_LAZY +# define LT_DLLAZY_OR_NOW RTLD_LAZY +# else +# ifdef DL_LAZY +# define LT_DLLAZY_OR_NOW DL_LAZY +# else +# ifdef RTLD_NOW +# define LT_DLLAZY_OR_NOW RTLD_NOW +# else +# ifdef DL_NOW +# define LT_DLLAZY_OR_NOW DL_NOW +# else +# define LT_DLLAZY_OR_NOW 0 +# endif +# endif +# endif +# endif +#endif + +#ifdef __cplusplus +extern "C" void exit (int); +#endif + +void fnord() { int i=42;} +int main () +{ + void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW); + int status = $lt_dlunknown; + + if (self) + { + if (dlsym (self,"fnord")) status = $lt_dlno_uscore; + else if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore; + /* dlclose (self); */ + } + else + puts (dlerror ()); + + exit (status); +} +EOF + if { (eval echo "$as_me:$LINENO: \"$ac_link\"") >&5 + (eval $ac_link) 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && test -s conftest${ac_exeext} 2>/dev/null; then + (./conftest; exit; ) >&5 2>/dev/null + lt_status=$? + case x$lt_status in + x$lt_dlno_uscore) lt_cv_dlopen_self=yes ;; + x$lt_dlneed_uscore) lt_cv_dlopen_self=yes ;; + x$lt_dlunknown|x*) lt_cv_dlopen_self=no ;; + esac + else : + # compilation failed + lt_cv_dlopen_self=no + fi +fi +rm -fr conftest* + + +fi +{ $as_echo "$as_me:$LINENO: result: $lt_cv_dlopen_self" >&5 +$as_echo "$lt_cv_dlopen_self" >&6; } + + if test "x$lt_cv_dlopen_self" = xyes; then + wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $lt_prog_compiler_static\" + { $as_echo "$as_me:$LINENO: checking whether a statically linked program can dlopen itself" >&5 +$as_echo_n "checking whether a statically linked program can dlopen itself... " >&6; } +if test "${lt_cv_dlopen_self_static+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test "$cross_compiling" = yes; then : + lt_cv_dlopen_self_static=cross +else + lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 + lt_status=$lt_dlunknown + cat > conftest.$ac_ext < +#endif + +#include + +#ifdef RTLD_GLOBAL +# define LT_DLGLOBAL RTLD_GLOBAL +#else +# ifdef DL_GLOBAL +# define LT_DLGLOBAL DL_GLOBAL +# else +# define LT_DLGLOBAL 0 +# endif +#endif + +/* We may have to define LT_DLLAZY_OR_NOW in the command line if we + find out it does not work in some platform. */ +#ifndef LT_DLLAZY_OR_NOW +# ifdef RTLD_LAZY +# define LT_DLLAZY_OR_NOW RTLD_LAZY +# else +# ifdef DL_LAZY +# define LT_DLLAZY_OR_NOW DL_LAZY +# else +# ifdef RTLD_NOW +# define LT_DLLAZY_OR_NOW RTLD_NOW +# else +# ifdef DL_NOW +# define LT_DLLAZY_OR_NOW DL_NOW +# else +# define LT_DLLAZY_OR_NOW 0 +# endif +# endif +# endif +# endif +#endif + +#ifdef __cplusplus +extern "C" void exit (int); +#endif + +void fnord() { int i=42;} +int main () +{ + void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW); + int status = $lt_dlunknown; + + if (self) + { + if (dlsym (self,"fnord")) status = $lt_dlno_uscore; + else if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore; + /* dlclose (self); */ + } + else + puts (dlerror ()); + + exit (status); +} +EOF + if { (eval echo "$as_me:$LINENO: \"$ac_link\"") >&5 + (eval $ac_link) 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && test -s conftest${ac_exeext} 2>/dev/null; then + (./conftest; exit; ) >&5 2>/dev/null + lt_status=$? + case x$lt_status in + x$lt_dlno_uscore) lt_cv_dlopen_self_static=yes ;; + x$lt_dlneed_uscore) lt_cv_dlopen_self_static=yes ;; + x$lt_dlunknown|x*) lt_cv_dlopen_self_static=no ;; + esac + else : + # compilation failed + lt_cv_dlopen_self_static=no + fi +fi +rm -fr conftest* + + +fi +{ $as_echo "$as_me:$LINENO: result: $lt_cv_dlopen_self_static" >&5 +$as_echo "$lt_cv_dlopen_self_static" >&6; } + fi + + CPPFLAGS="$save_CPPFLAGS" + LDFLAGS="$save_LDFLAGS" + LIBS="$save_LIBS" + ;; + esac + + case $lt_cv_dlopen_self in + yes|no) enable_dlopen_self=$lt_cv_dlopen_self ;; + *) enable_dlopen_self=unknown ;; + esac + + case $lt_cv_dlopen_self_static in + yes|no) enable_dlopen_self_static=$lt_cv_dlopen_self_static ;; + *) enable_dlopen_self_static=unknown ;; + esac +fi + + +# Report which library types will actually be built +{ $as_echo "$as_me:$LINENO: checking if libtool supports shared libraries" >&5 +$as_echo_n "checking if libtool supports shared libraries... " >&6; } +{ $as_echo "$as_me:$LINENO: result: $can_build_shared" >&5 +$as_echo "$can_build_shared" >&6; } + +{ $as_echo "$as_me:$LINENO: checking whether to build shared libraries" >&5 +$as_echo_n "checking whether to build shared libraries... " >&6; } +test "$can_build_shared" = "no" && enable_shared=no + +# On AIX, shared libraries and static libraries use the same namespace, and +# are all built from PIC. +case $host_os in +aix3*) + test "$enable_shared" = yes && enable_static=no + if test -n "$RANLIB"; then + archive_cmds="$archive_cmds~\$RANLIB \$lib" + postinstall_cmds='$RANLIB $lib' + fi + ;; + +aix[4-9]*) + if test "$host_cpu" != ia64 && test "$aix_use_runtimelinking" = no ; then + test "$enable_shared" = yes && enable_static=no + fi + ;; +esac +{ $as_echo "$as_me:$LINENO: result: $enable_shared" >&5 +$as_echo "$enable_shared" >&6; } + +{ $as_echo "$as_me:$LINENO: checking whether to build static libraries" >&5 +$as_echo_n "checking whether to build static libraries... " >&6; } +# Make sure either enable_shared or enable_static is yes. +test "$enable_shared" = yes || enable_static=yes +{ $as_echo "$as_me:$LINENO: result: $enable_static" >&5 +$as_echo "$enable_static" >&6; } + +# The else clause should only fire when bootstrapping the +# libtool distribution, otherwise you forgot to ship ltmain.sh +# with your package, and you will get complaints that there are +# no rules to generate ltmain.sh. +if test -f "$ltmain"; then + # See if we are running on zsh, and set the options which allow our commands through + # without removal of \ escapes. + if test -n "${ZSH_VERSION+set}" ; then + setopt NO_GLOB_SUBST + fi + # Now quote all the things that may contain metacharacters while being + # careful not to overquote the AC_SUBSTed values. We take copies of the + # variables and quote the copies for generation of the libtool script. + for var in echo old_CC old_CFLAGS AR AR_FLAGS EGREP RANLIB LN_S LTCC LTCFLAGS NM \ + SED SHELL STRIP \ + libname_spec library_names_spec soname_spec extract_expsyms_cmds \ + old_striplib striplib file_magic_cmd finish_cmds finish_eval \ + deplibs_check_method reload_flag reload_cmds need_locks \ + lt_cv_sys_global_symbol_pipe lt_cv_sys_global_symbol_to_cdecl \ + lt_cv_sys_global_symbol_to_c_name_address \ + sys_lib_search_path_spec sys_lib_dlsearch_path_spec \ + old_postinstall_cmds old_postuninstall_cmds \ + compiler \ + CC \ + LD \ + lt_prog_compiler_wl \ + lt_prog_compiler_pic \ + lt_prog_compiler_static \ + lt_prog_compiler_no_builtin_flag \ + export_dynamic_flag_spec \ + thread_safe_flag_spec \ + whole_archive_flag_spec \ + enable_shared_with_static_runtimes \ + old_archive_cmds \ + old_archive_from_new_cmds \ + predep_objects \ + postdep_objects \ + predeps \ + postdeps \ + compiler_lib_search_path \ + compiler_lib_search_dirs \ + archive_cmds \ + archive_expsym_cmds \ + postinstall_cmds \ + postuninstall_cmds \ + old_archive_from_expsyms_cmds \ + allow_undefined_flag \ + no_undefined_flag \ + export_symbols_cmds \ + hardcode_libdir_flag_spec \ + hardcode_libdir_flag_spec_ld \ + hardcode_libdir_separator \ + hardcode_automatic \ + module_cmds \ + module_expsym_cmds \ + lt_cv_prog_compiler_c_o \ + fix_srcfile_path \ + exclude_expsyms \ + include_expsyms; do + + case $var in + old_archive_cmds | \ + old_archive_from_new_cmds | \ + archive_cmds | \ + archive_expsym_cmds | \ + module_cmds | \ + module_expsym_cmds | \ + old_archive_from_expsyms_cmds | \ + export_symbols_cmds | \ + extract_expsyms_cmds | reload_cmds | finish_cmds | \ + postinstall_cmds | postuninstall_cmds | \ + old_postinstall_cmds | old_postuninstall_cmds | \ + sys_lib_search_path_spec | sys_lib_dlsearch_path_spec) + # Double-quote double-evaled strings. + eval "lt_$var=\\\"\`\$echo \"X\$$var\" | \$Xsed -e \"\$double_quote_subst\" -e \"\$sed_quote_subst\" -e \"\$delay_variable_subst\"\`\\\"" + ;; + *) + eval "lt_$var=\\\"\`\$echo \"X\$$var\" | \$Xsed -e \"\$sed_quote_subst\"\`\\\"" + ;; + esac + done + + case $lt_echo in + *'\$0 --fallback-echo"') + lt_echo=`$echo "X$lt_echo" | $Xsed -e 's/\\\\\\\$0 --fallback-echo"$/$0 --fallback-echo"/'` + ;; + esac + +cfgfile="${ofile}T" + trap "$rm \"$cfgfile\"; exit 1" 1 2 15 + $rm -f "$cfgfile" + { $as_echo "$as_me:$LINENO: creating $ofile" >&5 +$as_echo "$as_me: creating $ofile" >&6;} + + cat <<__EOF__ >> "$cfgfile" +#! $SHELL + +# `$echo "$cfgfile" | sed 's%^.*/%%'` - Provide generalized library-building support services. +# Generated automatically by $PROGRAM (GNU $PACKAGE $VERSION$TIMESTAMP) +# NOTE: Changes made to this file will be lost: look at ltmain.sh. +# +# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 +# Free Software Foundation, Inc. +# +# This file is part of GNU Libtool: +# Originally by Gordon Matzigkeit , 1996 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# A sed program that does not truncate output. +SED=$lt_SED + +# Sed that helps us avoid accidentally triggering echo(1) options like -n. +Xsed="$SED -e 1s/^X//" + +# The HP-UX ksh and POSIX shell print the target directory to stdout +# if CDPATH is set. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +# The names of the tagged configurations supported by this script. +available_tags= + +# ### BEGIN LIBTOOL CONFIG + +# Libtool was configured on host `(hostname || uname -n) 2>/dev/null | sed 1q`: + +# Shell to use when invoking shell scripts. +SHELL=$lt_SHELL + +# Whether or not to build shared libraries. +build_libtool_libs=$enable_shared + +# Whether or not to build static libraries. +build_old_libs=$enable_static + +# Whether or not to add -lc for building shared libraries. +build_libtool_need_lc=$archive_cmds_need_lc + +# Whether or not to disallow shared libs when runtime libs are static +allow_libtool_libs_with_static_runtimes=$enable_shared_with_static_runtimes + +# Whether or not to optimize for fast installation. +fast_install=$enable_fast_install + +# The host system. +host_alias=$host_alias +host=$host +host_os=$host_os + +# The build system. +build_alias=$build_alias +build=$build +build_os=$build_os + +# An echo program that does not interpret backslashes. +echo=$lt_echo + +# The archiver. +AR=$lt_AR +AR_FLAGS=$lt_AR_FLAGS + +# A C compiler. +LTCC=$lt_LTCC + +# LTCC compiler flags. +LTCFLAGS=$lt_LTCFLAGS + +# A language-specific compiler. +CC=$lt_compiler + +# Is the compiler the GNU C compiler? +with_gcc=$GCC + +# An ERE matcher. +EGREP=$lt_EGREP + +# The linker used to build libraries. +LD=$lt_LD + +# Whether we need hard or soft links. +LN_S=$lt_LN_S + +# A BSD-compatible nm program. +NM=$lt_NM + +# A symbol stripping program +STRIP=$lt_STRIP + +# Used to examine libraries when file_magic_cmd begins "file" +MAGIC_CMD=$MAGIC_CMD + +# Used on cygwin: DLL creation program. +DLLTOOL="$DLLTOOL" + +# Used on cygwin: object dumper. +OBJDUMP="$OBJDUMP" + +# Used on cygwin: assembler. +AS="$AS" + +# The name of the directory that contains temporary libtool files. +objdir=$objdir + +# How to create reloadable object files. +reload_flag=$lt_reload_flag +reload_cmds=$lt_reload_cmds + +# How to pass a linker flag through the compiler. +wl=$lt_lt_prog_compiler_wl + +# Object file suffix (normally "o"). +objext="$ac_objext" + +# Old archive suffix (normally "a"). +libext="$libext" + +# Shared library suffix (normally ".so"). +shrext_cmds='$shrext_cmds' + +# Executable file suffix (normally ""). +exeext="$exeext" + +# Additional compiler flags for building library objects. +pic_flag=$lt_lt_prog_compiler_pic +pic_mode=$pic_mode + +# What is the maximum length of a command? +max_cmd_len=$lt_cv_sys_max_cmd_len + +# Does compiler simultaneously support -c and -o options? +compiler_c_o=$lt_lt_cv_prog_compiler_c_o + +# Must we lock files when doing compilation? +need_locks=$lt_need_locks + +# Do we need the lib prefix for modules? +need_lib_prefix=$need_lib_prefix + +# Do we need a version for libraries? +need_version=$need_version + +# Whether dlopen is supported. +dlopen_support=$enable_dlopen + +# Whether dlopen of programs is supported. +dlopen_self=$enable_dlopen_self + +# Whether dlopen of statically linked programs is supported. +dlopen_self_static=$enable_dlopen_self_static + +# Compiler flag to prevent dynamic linking. +link_static_flag=$lt_lt_prog_compiler_static + +# Compiler flag to turn off builtin functions. +no_builtin_flag=$lt_lt_prog_compiler_no_builtin_flag + +# Compiler flag to allow reflexive dlopens. +export_dynamic_flag_spec=$lt_export_dynamic_flag_spec + +# Compiler flag to generate shared objects directly from archives. +whole_archive_flag_spec=$lt_whole_archive_flag_spec + +# Compiler flag to generate thread-safe objects. +thread_safe_flag_spec=$lt_thread_safe_flag_spec + +# Library versioning type. +version_type=$version_type + +# Format of library name prefix. +libname_spec=$lt_libname_spec + +# List of archive names. First name is the real one, the rest are links. +# The last name is the one that the linker finds with -lNAME. +library_names_spec=$lt_library_names_spec + +# The coded name of the library, if different from the real name. +soname_spec=$lt_soname_spec + +# Commands used to build and install an old-style archive. +RANLIB=$lt_RANLIB +old_archive_cmds=$lt_old_archive_cmds +old_postinstall_cmds=$lt_old_postinstall_cmds +old_postuninstall_cmds=$lt_old_postuninstall_cmds + +# Create an old-style archive from a shared archive. +old_archive_from_new_cmds=$lt_old_archive_from_new_cmds + +# Create a temporary old-style archive to link instead of a shared archive. +old_archive_from_expsyms_cmds=$lt_old_archive_from_expsyms_cmds + +# Commands used to build and install a shared archive. +archive_cmds=$lt_archive_cmds +archive_expsym_cmds=$lt_archive_expsym_cmds +postinstall_cmds=$lt_postinstall_cmds +postuninstall_cmds=$lt_postuninstall_cmds + +# Commands used to build a loadable module (assumed same as above if empty) +module_cmds=$lt_module_cmds +module_expsym_cmds=$lt_module_expsym_cmds + +# Commands to strip libraries. +old_striplib=$lt_old_striplib +striplib=$lt_striplib + +# Dependencies to place before the objects being linked to create a +# shared library. +predep_objects=$lt_predep_objects + +# Dependencies to place after the objects being linked to create a +# shared library. +postdep_objects=$lt_postdep_objects + +# Dependencies to place before the objects being linked to create a +# shared library. +predeps=$lt_predeps + +# Dependencies to place after the objects being linked to create a +# shared library. +postdeps=$lt_postdeps + +# The directories searched by this compiler when creating a shared +# library +compiler_lib_search_dirs=$lt_compiler_lib_search_dirs + +# The library search path used internally by the compiler when linking +# a shared library. +compiler_lib_search_path=$lt_compiler_lib_search_path + +# Method to check whether dependent libraries are shared objects. +deplibs_check_method=$lt_deplibs_check_method + +# Command to use when deplibs_check_method == file_magic. +file_magic_cmd=$lt_file_magic_cmd + +# Flag that allows shared libraries with undefined symbols to be built. +allow_undefined_flag=$lt_allow_undefined_flag + +# Flag that forces no undefined symbols. +no_undefined_flag=$lt_no_undefined_flag + +# Commands used to finish a libtool library installation in a directory. +finish_cmds=$lt_finish_cmds + +# Same as above, but a single script fragment to be evaled but not shown. +finish_eval=$lt_finish_eval + +# Take the output of nm and produce a listing of raw symbols and C names. +global_symbol_pipe=$lt_lt_cv_sys_global_symbol_pipe + +# Transform the output of nm in a proper C declaration +global_symbol_to_cdecl=$lt_lt_cv_sys_global_symbol_to_cdecl + +# Transform the output of nm in a C name address pair +global_symbol_to_c_name_address=$lt_lt_cv_sys_global_symbol_to_c_name_address + +# This is the shared library runtime path variable. +runpath_var=$runpath_var + +# This is the shared library path variable. +shlibpath_var=$shlibpath_var + +# Is shlibpath searched before the hard-coded library search path? +shlibpath_overrides_runpath=$shlibpath_overrides_runpath + +# How to hardcode a shared library path into an executable. +hardcode_action=$hardcode_action + +# Whether we should hardcode library paths into libraries. +hardcode_into_libs=$hardcode_into_libs + +# Flag to hardcode \$libdir into a binary during linking. +# This must work even if \$libdir does not exist. +hardcode_libdir_flag_spec=$lt_hardcode_libdir_flag_spec + +# If ld is used when linking, flag to hardcode \$libdir into +# a binary during linking. This must work even if \$libdir does +# not exist. +hardcode_libdir_flag_spec_ld=$lt_hardcode_libdir_flag_spec_ld + +# Whether we need a single -rpath flag with a separated argument. +hardcode_libdir_separator=$lt_hardcode_libdir_separator + +# Set to yes if using DIR/libNAME${shared_ext} during linking hardcodes DIR into the +# resulting binary. +hardcode_direct=$hardcode_direct + +# Set to yes if using the -LDIR flag during linking hardcodes DIR into the +# resulting binary. +hardcode_minus_L=$hardcode_minus_L + +# Set to yes if using SHLIBPATH_VAR=DIR during linking hardcodes DIR into +# the resulting binary. +hardcode_shlibpath_var=$hardcode_shlibpath_var + +# Set to yes if building a shared library automatically hardcodes DIR into the library +# and all subsequent libraries and executables linked against it. +hardcode_automatic=$hardcode_automatic + +# Variables whose values should be saved in libtool wrapper scripts and +# restored at relink time. +variables_saved_for_relink="$variables_saved_for_relink" + +# Whether libtool must link a program against all its dependency libraries. +link_all_deplibs=$link_all_deplibs + +# Compile-time system search path for libraries +sys_lib_search_path_spec=$lt_sys_lib_search_path_spec + +# Run-time system search path for libraries +sys_lib_dlsearch_path_spec=$lt_sys_lib_dlsearch_path_spec + +# Fix the shell variable \$srcfile for the compiler. +fix_srcfile_path=$lt_fix_srcfile_path + +# Set to yes if exported symbols are required. +always_export_symbols=$always_export_symbols + +# The commands to list exported symbols. +export_symbols_cmds=$lt_export_symbols_cmds + +# The commands to extract the exported symbol list from a shared archive. +extract_expsyms_cmds=$lt_extract_expsyms_cmds + +# Symbols that should not be listed in the preloaded symbols. +exclude_expsyms=$lt_exclude_expsyms + +# Symbols that must always be exported. +include_expsyms=$lt_include_expsyms + +# ### END LIBTOOL CONFIG + +__EOF__ + + + case $host_os in + aix3*) + cat <<\EOF >> "$cfgfile" + +# AIX sometimes has problems with the GCC collect2 program. For some +# reason, if we set the COLLECT_NAMES environment variable, the problems +# vanish in a puff of smoke. +if test "X${COLLECT_NAMES+set}" != Xset; then + COLLECT_NAMES= + export COLLECT_NAMES +fi +EOF + ;; + esac + + # We use sed instead of cat because bash on DJGPP gets confused if + # if finds mixed CR/LF and LF-only lines. Since sed operates in + # text mode, it properly converts lines to CR/LF. This bash problem + # is reportedly fixed, but why not run on old versions too? + sed '$q' "$ltmain" >> "$cfgfile" || (rm -f "$cfgfile"; exit 1) + + mv -f "$cfgfile" "$ofile" || \ + (rm -f "$ofile" && cp "$cfgfile" "$ofile" && rm -f "$cfgfile") + chmod +x "$ofile" + +else + # If there is no Makefile yet, we rely on a make rule to execute + # `config.status --recheck' to rerun these tests and create the + # libtool script then. + ltmain_in=`echo $ltmain | sed -e 's/\.sh$/.in/'` + if test -f "$ltmain_in"; then + test -f Makefile && make "$ltmain" + fi +fi + + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +CC="$lt_save_CC" + + +# Check whether --with-tags was given. +if test "${with_tags+set}" = set; then + withval=$with_tags; tagnames="$withval" +fi + + +if test -f "$ltmain" && test -n "$tagnames"; then + if test ! -f "${ofile}"; then + { $as_echo "$as_me:$LINENO: WARNING: output file \`$ofile' does not exist" >&5 +$as_echo "$as_me: WARNING: output file \`$ofile' does not exist" >&2;} + fi + + if test -z "$LTCC"; then + eval "`$SHELL ${ofile} --config | grep '^LTCC='`" + if test -z "$LTCC"; then + { $as_echo "$as_me:$LINENO: WARNING: output file \`$ofile' does not look like a libtool script" >&5 +$as_echo "$as_me: WARNING: output file \`$ofile' does not look like a libtool script" >&2;} + else + { $as_echo "$as_me:$LINENO: WARNING: using \`LTCC=$LTCC', extracted from \`$ofile'" >&5 +$as_echo "$as_me: WARNING: using \`LTCC=$LTCC', extracted from \`$ofile'" >&2;} + fi + fi + if test -z "$LTCFLAGS"; then + eval "`$SHELL ${ofile} --config | grep '^LTCFLAGS='`" + fi + + # Extract list of available tagged configurations in $ofile. + # Note that this assumes the entire list is on one line. + available_tags=`grep "^available_tags=" "${ofile}" | $SED -e 's/available_tags=\(.*$\)/\1/' -e 's/\"//g'` + + lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR," + for tagname in $tagnames; do + IFS="$lt_save_ifs" + # Check whether tagname contains only valid characters + case `$echo "X$tagname" | $Xsed -e 's:[-_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890,/]::g'` in + "") ;; + *) { { $as_echo "$as_me:$LINENO: error: invalid tag name: $tagname" >&5 +$as_echo "$as_me: error: invalid tag name: $tagname" >&2;} + { (exit 1); exit 1; }; } + ;; + esac + + if grep "^# ### BEGIN LIBTOOL TAG CONFIG: $tagname$" < "${ofile}" > /dev/null + then + { { $as_echo "$as_me:$LINENO: error: tag name \"$tagname\" already exists" >&5 +$as_echo "$as_me: error: tag name \"$tagname\" already exists" >&2;} + { (exit 1); exit 1; }; } + fi + + # Update the list of available tags. + if test -n "$tagname"; then + echo appending configuration tag \"$tagname\" to $ofile + + case $tagname in + CXX) + if test -n "$CXX" && ( test "X$CXX" != "Xno" && + ( (test "X$CXX" = "Xg++" && `g++ -v >/dev/null 2>&1` ) || + (test "X$CXX" != "Xg++"))) ; then + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + + + +archive_cmds_need_lc_CXX=no +allow_undefined_flag_CXX= +always_export_symbols_CXX=no +archive_expsym_cmds_CXX= +export_dynamic_flag_spec_CXX= +hardcode_direct_CXX=no +hardcode_libdir_flag_spec_CXX= +hardcode_libdir_flag_spec_ld_CXX= +hardcode_libdir_separator_CXX= +hardcode_minus_L_CXX=no +hardcode_shlibpath_var_CXX=unsupported +hardcode_automatic_CXX=no +module_cmds_CXX= +module_expsym_cmds_CXX= +link_all_deplibs_CXX=unknown +old_archive_cmds_CXX=$old_archive_cmds +no_undefined_flag_CXX= +whole_archive_flag_spec_CXX= +enable_shared_with_static_runtimes_CXX=no + +# Dependencies to place before and after the object being linked: +predep_objects_CXX= +postdep_objects_CXX= +predeps_CXX= +postdeps_CXX= +compiler_lib_search_path_CXX= +compiler_lib_search_dirs_CXX= + +# Source file extension for C++ test sources. +ac_ext=cpp + +# Object file extension for compiled C++ test sources. +objext=o +objext_CXX=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="int some_variable = 0;" + +# Code to be used in simple link tests +lt_simple_link_test_code='int main(int, char *[]) { return(0); }' + +# ltmain only uses $CC for tagged configurations so make sure $CC is set. + +# If no C compiler was specified, use CC. +LTCC=${LTCC-"$CC"} + +# If no C compiler flags were specified, use CFLAGS. +LTCFLAGS=${LTCFLAGS-"$CFLAGS"} + +# Allow CC to be a program name with arguments. +compiler=$CC + + +# save warnings/boilerplate of simple test code +ac_outfile=conftest.$ac_objext +echo "$lt_simple_compile_test_code" >conftest.$ac_ext +eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_compiler_boilerplate=`cat conftest.err` +$rm conftest* + +ac_outfile=conftest.$ac_objext +echo "$lt_simple_link_test_code" >conftest.$ac_ext +eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_linker_boilerplate=`cat conftest.err` +$rm -r conftest* + + +# Allow CC to be a program name with arguments. +lt_save_CC=$CC +lt_save_LD=$LD +lt_save_GCC=$GCC +GCC=$GXX +lt_save_with_gnu_ld=$with_gnu_ld +lt_save_path_LD=$lt_cv_path_LD +if test -n "${lt_cv_prog_gnu_ldcxx+set}"; then + lt_cv_prog_gnu_ld=$lt_cv_prog_gnu_ldcxx +else + $as_unset lt_cv_prog_gnu_ld +fi +if test -n "${lt_cv_path_LDCXX+set}"; then + lt_cv_path_LD=$lt_cv_path_LDCXX +else + $as_unset lt_cv_path_LD +fi +test -z "${LDCXX+set}" || LD=$LDCXX +CC=${CXX-"c++"} +compiler=$CC +compiler_CXX=$CC +for cc_temp in $compiler""; do + case $cc_temp in + compile | *[\\/]compile | ccache | *[\\/]ccache ) ;; + distcc | *[\\/]distcc | purify | *[\\/]purify ) ;; + \-*) ;; + *) break;; + esac +done +cc_basename=`$echo "X$cc_temp" | $Xsed -e 's%.*/%%' -e "s%^$host_alias-%%"` + + +# We don't want -fno-exception wen compiling C++ code, so set the +# no_builtin_flag separately +if test "$GXX" = yes; then + lt_prog_compiler_no_builtin_flag_CXX=' -fno-builtin' +else + lt_prog_compiler_no_builtin_flag_CXX= +fi + +if test "$GXX" = yes; then + # Set up default GNU C++ configuration + + +# Check whether --with-gnu-ld was given. +if test "${with_gnu_ld+set}" = set; then + withval=$with_gnu_ld; test "$withval" = no || with_gnu_ld=yes +else + with_gnu_ld=no +fi + +ac_prog=ld +if test "$GCC" = yes; then + # Check if gcc -print-prog-name=ld gives a path. + { $as_echo "$as_me:$LINENO: checking for ld used by $CC" >&5 +$as_echo_n "checking for ld used by $CC... " >&6; } + case $host in + *-*-mingw*) + # gcc leaves a trailing carriage return which upsets mingw + ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; + *) + ac_prog=`($CC -print-prog-name=ld) 2>&5` ;; + esac + case $ac_prog in + # Accept absolute paths. + [\\/]* | ?:[\\/]*) + re_direlt='/[^/][^/]*/\.\./' + # Canonicalize the pathname of ld + ac_prog=`echo $ac_prog| $SED 's%\\\\%/%g'` + while echo $ac_prog | grep "$re_direlt" > /dev/null 2>&1; do + ac_prog=`echo $ac_prog| $SED "s%$re_direlt%/%"` + done + test -z "$LD" && LD="$ac_prog" + ;; + "") + # If it fails, then pretend we aren't using GCC. + ac_prog=ld + ;; + *) + # If it is relative, then search for the first ld in PATH. + with_gnu_ld=unknown + ;; + esac +elif test "$with_gnu_ld" = yes; then + { $as_echo "$as_me:$LINENO: checking for GNU ld" >&5 +$as_echo_n "checking for GNU ld... " >&6; } +else + { $as_echo "$as_me:$LINENO: checking for non-GNU ld" >&5 +$as_echo_n "checking for non-GNU ld... " >&6; } +fi +if test "${lt_cv_path_LD+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test -z "$LD"; then + lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR + for ac_dir in $PATH; do + IFS="$lt_save_ifs" + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then + lt_cv_path_LD="$ac_dir/$ac_prog" + # Check to see if the program is GNU ld. I'd rather use --version, + # but apparently some variants of GNU ld only accept -v. + # Break only if it was the GNU/non-GNU ld that we prefer. + case `"$lt_cv_path_LD" -v 2>&1 &5 +$as_echo "$LD" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi +test -z "$LD" && { { $as_echo "$as_me:$LINENO: error: no acceptable ld found in \$PATH" >&5 +$as_echo "$as_me: error: no acceptable ld found in \$PATH" >&2;} + { (exit 1); exit 1; }; } +{ $as_echo "$as_me:$LINENO: checking if the linker ($LD) is GNU ld" >&5 +$as_echo_n "checking if the linker ($LD) is GNU ld... " >&6; } +if test "${lt_cv_prog_gnu_ld+set}" = set; then + $as_echo_n "(cached) " >&6 +else + # I'd rather use --version here, but apparently some GNU lds only accept -v. +case `$LD -v 2>&1 &5 +$as_echo "$lt_cv_prog_gnu_ld" >&6; } +with_gnu_ld=$lt_cv_prog_gnu_ld + + + + # Check if GNU C++ uses GNU ld as the underlying linker, since the + # archiving commands below assume that GNU ld is being used. + if test "$with_gnu_ld" = yes; then + archive_cmds_CXX='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname -o $lib' + archive_expsym_cmds_CXX='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + + hardcode_libdir_flag_spec_CXX='${wl}--rpath ${wl}$libdir' + export_dynamic_flag_spec_CXX='${wl}--export-dynamic' + + # If archive_cmds runs LD, not CC, wlarc should be empty + # XXX I think wlarc can be eliminated in ltcf-cxx, but I need to + # investigate it a little bit more. (MM) + wlarc='${wl}' + + # ancient GNU ld didn't support --whole-archive et. al. + if eval "`$CC -print-prog-name=ld` --help 2>&1" | \ + grep 'no-whole-archive' > /dev/null; then + whole_archive_flag_spec_CXX="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive' + else + whole_archive_flag_spec_CXX= + fi + else + with_gnu_ld=no + wlarc= + + # A generic and very simple default shared library creation + # command for GNU C++ for the case where it uses the native + # linker, instead of GNU ld. If possible, this setting should + # overridden to take advantage of the native linker features on + # the platform it is being used on. + archive_cmds_CXX='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib' + fi + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | grep "\-L"' + +else + GXX=no + with_gnu_ld=no + wlarc= +fi + +# PORTME: fill in a description of your system's C++ link characteristics +{ $as_echo "$as_me:$LINENO: checking whether the $compiler linker ($LD) supports shared libraries" >&5 +$as_echo_n "checking whether the $compiler linker ($LD) supports shared libraries... " >&6; } +ld_shlibs_CXX=yes +case $host_os in + aix3*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + aix[4-9]*) + if test "$host_cpu" = ia64; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + exp_sym_flag='-Bexport' + no_entry_flag="" + else + aix_use_runtimelinking=no + + # Test if we are trying to use run time linking or normal + # AIX style linking. If -brtl is somewhere in LDFLAGS, we + # need to do runtime linking. + case $host_os in aix4.[23]|aix4.[23].*|aix[5-9]*) + for ld_flag in $LDFLAGS; do + case $ld_flag in + *-brtl*) + aix_use_runtimelinking=yes + break + ;; + esac + done + ;; + esac + + exp_sym_flag='-bexport' + no_entry_flag='-bnoentry' + fi + + # When large executables or shared objects are built, AIX ld can + # have problems creating the table of contents. If linking a library + # or program results in "error TOC overflow" add -mminimal-toc to + # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not + # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. + + archive_cmds_CXX='' + hardcode_direct_CXX=yes + hardcode_libdir_separator_CXX=':' + link_all_deplibs_CXX=yes + + if test "$GXX" = yes; then + case $host_os in aix4.[012]|aix4.[012].*) + # We only want to do this on AIX 4.2 and lower, the check + # below for broken collect2 doesn't work under 4.3+ + collect2name=`${CC} -print-prog-name=collect2` + if test -f "$collect2name" && \ + strings "$collect2name" | grep resolve_lib_name >/dev/null + then + # We have reworked collect2 + : + else + # We have old collect2 + hardcode_direct_CXX=unsupported + # It fails to find uninstalled libraries when the uninstalled + # path is not listed in the libpath. Setting hardcode_minus_L + # to unsupported forces relinking + hardcode_minus_L_CXX=yes + hardcode_libdir_flag_spec_CXX='-L$libdir' + hardcode_libdir_separator_CXX= + fi + ;; + esac + shared_flag='-shared' + if test "$aix_use_runtimelinking" = yes; then + shared_flag="$shared_flag "'${wl}-G' + fi + else + # not using gcc + if test "$host_cpu" = ia64; then + # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release + # chokes on -Wl,-G. The following line is correct: + shared_flag='-G' + else + if test "$aix_use_runtimelinking" = yes; then + shared_flag='${wl}-G' + else + shared_flag='${wl}-bM:SRE' + fi + fi + fi + + # It seems that -bexpall does not export symbols beginning with + # underscore (_), so it is better to generate a list of symbols to export. + always_export_symbols_CXX=yes + if test "$aix_use_runtimelinking" = yes; then + # Warning - without using the other runtime loading flags (-brtl), + # -berok will link without error, but may produce a broken library. + allow_undefined_flag_CXX='-berok' + # Determine the default libpath from the value encoded in an empty executable. + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_cxx_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + $as_test_x conftest$ac_exeext + }; then + +lt_aix_libpath_sed=' + /Import File Strings/,/^$/ { + /^0/ { + s/^0 *\(.*\)$/\1/ + p + } + }' +aix_libpath=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` +# Check for a 64-bit object if we didn't find anything. +if test -z "$aix_libpath"; then + aix_libpath=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` +fi +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + +fi + +rm -rf conftest.dSYM +rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ + conftest$ac_exeext conftest.$ac_ext +if test -z "$aix_libpath"; then aix_libpath="/usr/lib:/lib"; fi + + hardcode_libdir_flag_spec_CXX='${wl}-blibpath:$libdir:'"$aix_libpath" + + archive_expsym_cmds_CXX="\$CC"' -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags `if test "x${allow_undefined_flag}" != "x"; then echo "${wl}${allow_undefined_flag}"; else :; fi` '"\${wl}$exp_sym_flag:\$export_symbols $shared_flag" + else + if test "$host_cpu" = ia64; then + hardcode_libdir_flag_spec_CXX='${wl}-R $libdir:/usr/lib:/lib' + allow_undefined_flag_CXX="-z nodefs" + archive_expsym_cmds_CXX="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags ${wl}${allow_undefined_flag} '"\${wl}$exp_sym_flag:\$export_symbols" + else + # Determine the default libpath from the value encoded in an empty executable. + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_cxx_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + $as_test_x conftest$ac_exeext + }; then + +lt_aix_libpath_sed=' + /Import File Strings/,/^$/ { + /^0/ { + s/^0 *\(.*\)$/\1/ + p + } + }' +aix_libpath=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` +# Check for a 64-bit object if we didn't find anything. +if test -z "$aix_libpath"; then + aix_libpath=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` +fi +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + +fi + +rm -rf conftest.dSYM +rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ + conftest$ac_exeext conftest.$ac_ext +if test -z "$aix_libpath"; then aix_libpath="/usr/lib:/lib"; fi + + hardcode_libdir_flag_spec_CXX='${wl}-blibpath:$libdir:'"$aix_libpath" + # Warning - without using the other run time loading flags, + # -berok will link without error, but may produce a broken library. + no_undefined_flag_CXX=' ${wl}-bernotok' + allow_undefined_flag_CXX=' ${wl}-berok' + # Exported symbols can be pulled into shared objects from archives + whole_archive_flag_spec_CXX='$convenience' + archive_cmds_need_lc_CXX=yes + # This is similar to how AIX traditionally builds its shared libraries. + archive_expsym_cmds_CXX="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs ${wl}-bnoentry $compiler_flags ${wl}-bE:$export_symbols${allow_undefined_flag}~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$soname' + fi + fi + ;; + + beos*) + if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + allow_undefined_flag_CXX=unsupported + # Joseph Beckenbach says some releases of gcc + # support --undefined. This deserves some investigation. FIXME + archive_cmds_CXX='$CC -nostart $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + else + ld_shlibs_CXX=no + fi + ;; + + chorus*) + case $cc_basename in + *) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + esac + ;; + + cygwin* | mingw* | pw32*) + # _LT_AC_TAGVAR(hardcode_libdir_flag_spec, CXX) is actually meaningless, + # as there is no search path for DLLs. + hardcode_libdir_flag_spec_CXX='-L$libdir' + allow_undefined_flag_CXX=unsupported + always_export_symbols_CXX=no + enable_shared_with_static_runtimes_CXX=yes + + if $LD --help 2>&1 | grep 'auto-import' > /dev/null; then + archive_cmds_CXX='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + # If the export-symbols file already is a .def file (1st line + # is EXPORTS), use it as is; otherwise, prepend... + archive_expsym_cmds_CXX='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then + cp $export_symbols $output_objdir/$soname.def; + else + echo EXPORTS > $output_objdir/$soname.def; + cat $export_symbols >> $output_objdir/$soname.def; + fi~ + $CC -shared -nostdlib $output_objdir/$soname.def $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + else + ld_shlibs_CXX=no + fi + ;; + darwin* | rhapsody*) + archive_cmds_need_lc_CXX=no + hardcode_direct_CXX=no + hardcode_automatic_CXX=yes + hardcode_shlibpath_var_CXX=unsupported + whole_archive_flag_spec_CXX='' + link_all_deplibs_CXX=yes + allow_undefined_flag_CXX="$_lt_dar_allow_undefined" + if test "$GXX" = yes ; then + output_verbose_link_cmd='echo' + archive_cmds_CXX="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod${_lt_dsymutil}" + module_cmds_CXX="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags${_lt_dsymutil}" + archive_expsym_cmds_CXX="sed 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring ${_lt_dar_single_mod}${_lt_dar_export_syms}${_lt_dsymutil}" + module_expsym_cmds_CXX="sed -e 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags${_lt_dar_export_syms}${_lt_dsymutil}" + if test "$lt_cv_apple_cc_single_mod" != "yes"; then + archive_cmds_CXX="\$CC -r -keep_private_externs -nostdlib -o \${lib}-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \${lib}-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring${_lt_dsymutil}" + archive_expsym_cmds_CXX="sed 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC -r -keep_private_externs -nostdlib -o \${lib}-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \${lib}-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring${_lt_dar_export_syms}${_lt_dsymutil}" + fi + else + case $cc_basename in + xlc*) + output_verbose_link_cmd='echo' + archive_cmds_CXX='$CC -qmkshrobj ${wl}-single_module $allow_undefined_flag -o $lib $libobjs $deplibs $compiler_flags ${wl}-install_name ${wl}`echo $rpath/$soname` $xlcverstring' + module_cmds_CXX='$CC $allow_undefined_flag -o $lib -bundle $libobjs $deplibs$compiler_flags' + # Don't fix this by using the ld -exported_symbols_list flag, it doesn't exist in older darwin lds + archive_expsym_cmds_CXX='sed -e "s,#.*,," -e "s,^[ ]*,," -e "s,^\(..*\),_&," < $export_symbols > $output_objdir/${libname}-symbols.expsym~$CC -qmkshrobj ${wl}-single_module $allow_undefined_flag -o $lib $libobjs $deplibs $compiler_flags ${wl}-install_name ${wl}$rpath/$soname $xlcverstring~nmedit -s $output_objdir/${libname}-symbols.expsym ${lib}' + module_expsym_cmds_CXX='sed -e "s,#.*,," -e "s,^[ ]*,," -e "s,^\(..*\),_&," < $export_symbols > $output_objdir/${libname}-symbols.expsym~$CC $allow_undefined_flag -o $lib -bundle $libobjs $deplibs$compiler_flags~nmedit -s $output_objdir/${libname}-symbols.expsym ${lib}' + ;; + *) + ld_shlibs_CXX=no + ;; + esac + fi + ;; + + dgux*) + case $cc_basename in + ec++*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + ghcx*) + # Green Hills C++ Compiler + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + *) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + esac + ;; + freebsd[12]*) + # C++ shared libraries reported to be fairly broken before switch to ELF + ld_shlibs_CXX=no + ;; + freebsd-elf*) + archive_cmds_need_lc_CXX=no + ;; + freebsd* | dragonfly*) + # FreeBSD 3 and later use GNU C++ and GNU ld with standard ELF + # conventions + ld_shlibs_CXX=yes + ;; + gnu*) + ;; + hpux9*) + hardcode_libdir_flag_spec_CXX='${wl}+b ${wl}$libdir' + hardcode_libdir_separator_CXX=: + export_dynamic_flag_spec_CXX='${wl}-E' + hardcode_direct_CXX=yes + hardcode_minus_L_CXX=yes # Not in the search PATH, + # but as the default + # location of the library. + + case $cc_basename in + CC*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + aCC*) + archive_cmds_CXX='$rm $output_objdir/$soname~$CC -b ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib' + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | grep "[-]L"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; echo $list' + ;; + *) + if test "$GXX" = yes; then + archive_cmds_CXX='$rm $output_objdir/$soname~$CC -shared -nostdlib -fPIC ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib' + else + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + fi + ;; + esac + ;; + hpux10*|hpux11*) + if test $with_gnu_ld = no; then + hardcode_libdir_flag_spec_CXX='${wl}+b ${wl}$libdir' + hardcode_libdir_separator_CXX=: + + case $host_cpu in + hppa*64*|ia64*) ;; + *) + export_dynamic_flag_spec_CXX='${wl}-E' + ;; + esac + fi + case $host_cpu in + hppa*64*|ia64*) + hardcode_direct_CXX=no + hardcode_shlibpath_var_CXX=no + ;; + *) + hardcode_direct_CXX=yes + hardcode_minus_L_CXX=yes # Not in the search PATH, + # but as the default + # location of the library. + ;; + esac + + case $cc_basename in + CC*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + aCC*) + case $host_cpu in + hppa*64*) + archive_cmds_CXX='$CC -b ${wl}+h ${wl}$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + ia64*) + archive_cmds_CXX='$CC -b ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + *) + archive_cmds_CXX='$CC -b ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + esac + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | grep "\-L"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; echo $list' + ;; + *) + if test "$GXX" = yes; then + if test $with_gnu_ld = no; then + case $host_cpu in + hppa*64*) + archive_cmds_CXX='$CC -shared -nostdlib -fPIC ${wl}+h ${wl}$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + ia64*) + archive_cmds_CXX='$CC -shared -nostdlib -fPIC ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + *) + archive_cmds_CXX='$CC -shared -nostdlib -fPIC ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + esac + fi + else + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + fi + ;; + esac + ;; + interix[3-9]*) + hardcode_direct_CXX=no + hardcode_shlibpath_var_CXX=no + hardcode_libdir_flag_spec_CXX='${wl}-rpath,$libdir' + export_dynamic_flag_spec_CXX='${wl}-E' + # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. + # Instead, shared libraries are loaded at an image base (0x10000000 by + # default) and relocated if they conflict, which is a slow very memory + # consuming and fragmenting process. To avoid this, we pick a random, + # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link + # time. Moving up from 0x10000000 also allows more sbrk(2) space. + archive_cmds_CXX='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + archive_expsym_cmds_CXX='sed "s,^,_," $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--retain-symbols-file,$output_objdir/$soname.expsym ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + ;; + irix5* | irix6*) + case $cc_basename in + CC*) + # SGI C++ + archive_cmds_CXX='$CC -shared -all -multigot $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib' + + # Archives containing C++ object files must be created using + # "CC -ar", where "CC" is the IRIX C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + old_archive_cmds_CXX='$CC -ar -WR,-u -o $oldlib $oldobjs' + ;; + *) + if test "$GXX" = yes; then + if test "$with_gnu_ld" = no; then + archive_cmds_CXX='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + else + archive_cmds_CXX='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` -o $lib' + fi + fi + link_all_deplibs_CXX=yes + ;; + esac + hardcode_libdir_flag_spec_CXX='${wl}-rpath ${wl}$libdir' + hardcode_libdir_separator_CXX=: + ;; + linux* | k*bsd*-gnu) + case $cc_basename in + KCC*) + # Kuck and Associates, Inc. (KAI) C++ Compiler + + # KCC will only create a shared library if the output file + # ends with ".so" (or ".sl" for HP-UX), so rename the library + # to its proper name (with version) after linking. + archive_cmds_CXX='tempext=`echo $shared_ext | $SED -e '\''s/\([^()0-9A-Za-z{}]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\${tempext}\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib' + archive_expsym_cmds_CXX='tempext=`echo $shared_ext | $SED -e '\''s/\([^()0-9A-Za-z{}]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\${tempext}\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib ${wl}-retain-symbols-file,$export_symbols; mv \$templib $lib' + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 | grep "ld"`; rm -f libconftest$shared_ext; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; echo $list' + + hardcode_libdir_flag_spec_CXX='${wl}--rpath,$libdir' + export_dynamic_flag_spec_CXX='${wl}--export-dynamic' + + # Archives containing C++ object files must be created using + # "CC -Bstatic", where "CC" is the KAI C++ compiler. + old_archive_cmds_CXX='$CC -Bstatic -o $oldlib $oldobjs' + ;; + icpc*) + # Intel C++ + with_gnu_ld=yes + # version 8.0 and above of icpc choke on multiply defined symbols + # if we add $predep_objects and $postdep_objects, however 7.1 and + # earlier do not add the objects themselves. + case `$CC -V 2>&1` in + *"Version 7."*) + archive_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname -o $lib' + archive_expsym_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + ;; + *) # Version 8.0 or newer + tmp_idyn= + case $host_cpu in + ia64*) tmp_idyn=' -i_dynamic';; + esac + archive_cmds_CXX='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + archive_expsym_cmds_CXX='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + ;; + esac + archive_cmds_need_lc_CXX=no + hardcode_libdir_flag_spec_CXX='${wl}-rpath,$libdir' + export_dynamic_flag_spec_CXX='${wl}--export-dynamic' + whole_archive_flag_spec_CXX='${wl}--whole-archive$convenience ${wl}--no-whole-archive' + ;; + pgCC* | pgcpp*) + # Portland Group C++ compiler + archive_cmds_CXX='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname -o $lib' + archive_expsym_cmds_CXX='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname ${wl}-retain-symbols-file ${wl}$export_symbols -o $lib' + + hardcode_libdir_flag_spec_CXX='${wl}--rpath ${wl}$libdir' + export_dynamic_flag_spec_CXX='${wl}--export-dynamic' + whole_archive_flag_spec_CXX='${wl}--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; $echo \"$new_convenience\"` ${wl}--no-whole-archive' + ;; + cxx*) + # Compaq C++ + archive_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname -o $lib' + archive_expsym_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname -o $lib ${wl}-retain-symbols-file $wl$export_symbols' + + runpath_var=LD_RUN_PATH + hardcode_libdir_flag_spec_CXX='-rpath $libdir' + hardcode_libdir_separator_CXX=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | grep "ld"`; templist=`echo $templist | $SED "s/\(^.*ld.*\)\( .*ld .*$\)/\1/"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; echo $list' + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) + # Sun C++ 5.9 + no_undefined_flag_CXX=' -zdefs' + archive_cmds_CXX='$CC -G${allow_undefined_flag} -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + archive_expsym_cmds_CXX='$CC -G${allow_undefined_flag} -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-retain-symbols-file ${wl}$export_symbols' + hardcode_libdir_flag_spec_CXX='-R$libdir' + whole_archive_flag_spec_CXX='${wl}--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; $echo \"$new_convenience\"` ${wl}--no-whole-archive' + + # Not sure whether something based on + # $CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 + # would be better. + output_verbose_link_cmd='echo' + + # Archives containing C++ object files must be created using + # "CC -xar", where "CC" is the Sun C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + old_archive_cmds_CXX='$CC -xar -o $oldlib $oldobjs' + ;; + esac + ;; + esac + ;; + lynxos*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + m88k*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + mvs*) + case $cc_basename in + cxx*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + *) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + esac + ;; + netbsd*) + if echo __ELF__ | $CC -E - | grep __ELF__ >/dev/null; then + archive_cmds_CXX='$LD -Bshareable -o $lib $predep_objects $libobjs $deplibs $postdep_objects $linker_flags' + wlarc= + hardcode_libdir_flag_spec_CXX='-R$libdir' + hardcode_direct_CXX=yes + hardcode_shlibpath_var_CXX=no + fi + # Workaround some broken pre-1.5 toolchains + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | grep conftest.$objext | $SED -e "s:-lgcc -lc -lgcc::"' + ;; + openbsd2*) + # C++ shared libraries are fairly broken + ld_shlibs_CXX=no + ;; + openbsd*) + if test -f /usr/libexec/ld.so; then + hardcode_direct_CXX=yes + hardcode_shlibpath_var_CXX=no + archive_cmds_CXX='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib' + hardcode_libdir_flag_spec_CXX='${wl}-rpath,$libdir' + if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then + archive_expsym_cmds_CXX='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-retain-symbols-file,$export_symbols -o $lib' + export_dynamic_flag_spec_CXX='${wl}-E' + whole_archive_flag_spec_CXX="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive' + fi + output_verbose_link_cmd='echo' + else + ld_shlibs_CXX=no + fi + ;; + osf3*) + case $cc_basename in + KCC*) + # Kuck and Associates, Inc. (KAI) C++ Compiler + + # KCC will only create a shared library if the output file + # ends with ".so" (or ".sl" for HP-UX), so rename the library + # to its proper name (with version) after linking. + archive_cmds_CXX='tempext=`echo $shared_ext | $SED -e '\''s/\([^()0-9A-Za-z{}]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\${tempext}\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib' + + hardcode_libdir_flag_spec_CXX='${wl}-rpath,$libdir' + hardcode_libdir_separator_CXX=: + + # Archives containing C++ object files must be created using + # "CC -Bstatic", where "CC" is the KAI C++ compiler. + old_archive_cmds_CXX='$CC -Bstatic -o $oldlib $oldobjs' + + ;; + RCC*) + # Rational C++ 2.4.1 + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + cxx*) + allow_undefined_flag_CXX=' ${wl}-expect_unresolved ${wl}\*' + archive_cmds_CXX='$CC -shared${allow_undefined_flag} $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $soname `test -n "$verstring" && echo ${wl}-set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib' + + hardcode_libdir_flag_spec_CXX='${wl}-rpath ${wl}$libdir' + hardcode_libdir_separator_CXX=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | grep "ld" | grep -v "ld:"`; templist=`echo $templist | $SED "s/\(^.*ld.*\)\( .*ld.*$\)/\1/"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; echo $list' + ;; + *) + if test "$GXX" = yes && test "$with_gnu_ld" = no; then + allow_undefined_flag_CXX=' ${wl}-expect_unresolved ${wl}\*' + archive_cmds_CXX='$CC -shared -nostdlib ${allow_undefined_flag} $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + + hardcode_libdir_flag_spec_CXX='${wl}-rpath ${wl}$libdir' + hardcode_libdir_separator_CXX=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | grep "\-L"' + + else + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + fi + ;; + esac + ;; + osf4* | osf5*) + case $cc_basename in + KCC*) + # Kuck and Associates, Inc. (KAI) C++ Compiler + + # KCC will only create a shared library if the output file + # ends with ".so" (or ".sl" for HP-UX), so rename the library + # to its proper name (with version) after linking. + archive_cmds_CXX='tempext=`echo $shared_ext | $SED -e '\''s/\([^()0-9A-Za-z{}]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\${tempext}\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib' + + hardcode_libdir_flag_spec_CXX='${wl}-rpath,$libdir' + hardcode_libdir_separator_CXX=: + + # Archives containing C++ object files must be created using + # the KAI C++ compiler. + old_archive_cmds_CXX='$CC -o $oldlib $oldobjs' + ;; + RCC*) + # Rational C++ 2.4.1 + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + cxx*) + allow_undefined_flag_CXX=' -expect_unresolved \*' + archive_cmds_CXX='$CC -shared${allow_undefined_flag} $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib' + archive_expsym_cmds_CXX='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done~ + echo "-hidden">> $lib.exp~ + $CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname -Wl,-input -Wl,$lib.exp `test -n "$verstring" && echo -set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib~ + $rm $lib.exp' + + hardcode_libdir_flag_spec_CXX='-rpath $libdir' + hardcode_libdir_separator_CXX=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | grep "ld" | grep -v "ld:"`; templist=`echo $templist | $SED "s/\(^.*ld.*\)\( .*ld.*$\)/\1/"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; echo $list' + ;; + *) + if test "$GXX" = yes && test "$with_gnu_ld" = no; then + allow_undefined_flag_CXX=' ${wl}-expect_unresolved ${wl}\*' + archive_cmds_CXX='$CC -shared -nostdlib ${allow_undefined_flag} $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-msym ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + + hardcode_libdir_flag_spec_CXX='${wl}-rpath ${wl}$libdir' + hardcode_libdir_separator_CXX=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | grep "\-L"' + + else + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + fi + ;; + esac + ;; + psos*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + sunos4*) + case $cc_basename in + CC*) + # Sun C++ 4.x + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + lcc*) + # Lucid + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + *) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + esac + ;; + solaris*) + case $cc_basename in + CC*) + # Sun C++ 4.2, 5.x and Centerline C++ + archive_cmds_need_lc_CXX=yes + no_undefined_flag_CXX=' -zdefs' + archive_cmds_CXX='$CC -G${allow_undefined_flag} -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + archive_expsym_cmds_CXX='$echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~$echo "local: *; };" >> $lib.exp~ + $CC -G${allow_undefined_flag} ${wl}-M ${wl}$lib.exp -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$rm $lib.exp' + + hardcode_libdir_flag_spec_CXX='-R$libdir' + hardcode_shlibpath_var_CXX=no + case $host_os in + solaris2.[0-5] | solaris2.[0-5].*) ;; + *) + # The compiler driver will combine and reorder linker options, + # but understands `-z linker_flag'. + # Supported since Solaris 2.6 (maybe 2.5.1?) + whole_archive_flag_spec_CXX='-z allextract$convenience -z defaultextract' + ;; + esac + link_all_deplibs_CXX=yes + + output_verbose_link_cmd='echo' + + # Archives containing C++ object files must be created using + # "CC -xar", where "CC" is the Sun C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + old_archive_cmds_CXX='$CC -xar -o $oldlib $oldobjs' + ;; + gcx*) + # Green Hills C++ Compiler + archive_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-h $wl$soname -o $lib' + + # The C++ compiler must be used to create the archive. + old_archive_cmds_CXX='$CC $LDFLAGS -archive -o $oldlib $oldobjs' + ;; + *) + # GNU C++ compiler with Solaris linker + if test "$GXX" = yes && test "$with_gnu_ld" = no; then + no_undefined_flag_CXX=' ${wl}-z ${wl}defs' + if $CC --version | grep -v '^2\.7' > /dev/null; then + archive_cmds_CXX='$CC -shared -nostdlib $LDFLAGS $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-h $wl$soname -o $lib' + archive_expsym_cmds_CXX='$echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~$echo "local: *; };" >> $lib.exp~ + $CC -shared -nostdlib ${wl}-M $wl$lib.exp -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$rm $lib.exp' + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd="$CC -shared $CFLAGS -v conftest.$objext 2>&1 | grep \"\-L\"" + else + # g++ 2.7 appears to require `-G' NOT `-shared' on this + # platform. + archive_cmds_CXX='$CC -G -nostdlib $LDFLAGS $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-h $wl$soname -o $lib' + archive_expsym_cmds_CXX='$echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~$echo "local: *; };" >> $lib.exp~ + $CC -G -nostdlib ${wl}-M $wl$lib.exp -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$rm $lib.exp' + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd="$CC -G $CFLAGS -v conftest.$objext 2>&1 | grep \"\-L\"" + fi + + hardcode_libdir_flag_spec_CXX='${wl}-R $wl$libdir' + case $host_os in + solaris2.[0-5] | solaris2.[0-5].*) ;; + *) + whole_archive_flag_spec_CXX='${wl}-z ${wl}allextract$convenience ${wl}-z ${wl}defaultextract' + ;; + esac + fi + ;; + esac + ;; + sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[01].[10]* | unixware7* | sco3.2v5.0.[024]*) + no_undefined_flag_CXX='${wl}-z,text' + archive_cmds_need_lc_CXX=no + hardcode_shlibpath_var_CXX=no + runpath_var='LD_RUN_PATH' + + case $cc_basename in + CC*) + archive_cmds_CXX='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds_CXX='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + archive_cmds_CXX='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds_CXX='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + ;; + sysv5* | sco3.2v5* | sco5v6*) + # Note: We can NOT use -z defs as we might desire, because we do not + # link with -lc, and that would cause any symbols used from libc to + # always be unresolved, which means just about no library would + # ever link correctly. If we're not using GNU ld we use -z text + # though, which does catch some bad symbols but isn't as heavy-handed + # as -z defs. + # For security reasons, it is highly recommended that you always + # use absolute paths for naming shared libraries, and exclude the + # DT_RUNPATH tag from executables and libraries. But doing so + # requires that you compile everything twice, which is a pain. + # So that behaviour is only enabled if SCOABSPATH is set to a + # non-empty value in the environment. Most likely only useful for + # creating official distributions of packages. + # This is a hack until libtool officially supports absolute path + # names for shared libraries. + no_undefined_flag_CXX='${wl}-z,text' + allow_undefined_flag_CXX='${wl}-z,nodefs' + archive_cmds_need_lc_CXX=no + hardcode_shlibpath_var_CXX=no + hardcode_libdir_flag_spec_CXX='`test -z "$SCOABSPATH" && echo ${wl}-R,$libdir`' + hardcode_libdir_separator_CXX=':' + link_all_deplibs_CXX=yes + export_dynamic_flag_spec_CXX='${wl}-Bexport' + runpath_var='LD_RUN_PATH' + + case $cc_basename in + CC*) + archive_cmds_CXX='$CC -G ${wl}-h,\${SCOABSPATH:+${install_libdir}/}$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds_CXX='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,\${SCOABSPATH:+${install_libdir}/}$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + archive_cmds_CXX='$CC -shared ${wl}-h,\${SCOABSPATH:+${install_libdir}/}$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds_CXX='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,\${SCOABSPATH:+${install_libdir}/}$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + ;; + tandem*) + case $cc_basename in + NCC*) + # NonStop-UX NCC 3.20 + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + *) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + esac + ;; + vxworks*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + *) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; +esac +{ $as_echo "$as_me:$LINENO: result: $ld_shlibs_CXX" >&5 +$as_echo "$ld_shlibs_CXX" >&6; } +test "$ld_shlibs_CXX" = no && can_build_shared=no + +GCC_CXX="$GXX" +LD_CXX="$LD" + +cat > conftest.$ac_ext <&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; then + # Parse the compiler output and extract the necessary + # objects, libraries and library flags. + + # Sentinel used to keep track of whether or not we are before + # the conftest object file. + pre_test_object_deps_done=no + + # The `*' in the case matches for architectures that use `case' in + # $output_verbose_cmd can trigger glob expansion during the loop + # eval without this substitution. + output_verbose_link_cmd=`$echo "X$output_verbose_link_cmd" | $Xsed -e "$no_glob_subst"` + + for p in `eval $output_verbose_link_cmd`; do + case $p in + + -L* | -R* | -l*) + # Some compilers place space between "-{L,R}" and the path. + # Remove the space. + if test $p = "-L" \ + || test $p = "-R"; then + prev=$p + continue + else + prev= + fi + + if test "$pre_test_object_deps_done" = no; then + case $p in + -L* | -R*) + # Internal compiler library paths should come after those + # provided the user. The postdeps already come after the + # user supplied libs so there is no need to process them. + if test -z "$compiler_lib_search_path_CXX"; then + compiler_lib_search_path_CXX="${prev}${p}" + else + compiler_lib_search_path_CXX="${compiler_lib_search_path_CXX} ${prev}${p}" + fi + ;; + # The "-l" case would never come before the object being + # linked, so don't bother handling this case. + esac + else + if test -z "$postdeps_CXX"; then + postdeps_CXX="${prev}${p}" + else + postdeps_CXX="${postdeps_CXX} ${prev}${p}" + fi + fi + ;; + + *.$objext) + # This assumes that the test object file only shows up + # once in the compiler output. + if test "$p" = "conftest.$objext"; then + pre_test_object_deps_done=yes + continue + fi + + if test "$pre_test_object_deps_done" = no; then + if test -z "$predep_objects_CXX"; then + predep_objects_CXX="$p" + else + predep_objects_CXX="$predep_objects_CXX $p" + fi + else + if test -z "$postdep_objects_CXX"; then + postdep_objects_CXX="$p" + else + postdep_objects_CXX="$postdep_objects_CXX $p" + fi + fi + ;; + + *) ;; # Ignore the rest. + + esac + done + + # Clean up. + rm -f a.out a.exe +else + echo "libtool.m4: error: problem compiling CXX test program" +fi + +$rm -f confest.$objext + +compiler_lib_search_dirs_CXX= +if test -n "$compiler_lib_search_path_CXX"; then + compiler_lib_search_dirs_CXX=`echo " ${compiler_lib_search_path_CXX}" | ${SED} -e 's! -L! !g' -e 's!^ !!'` +fi + +# PORTME: override above test on systems where it is broken +case $host_os in +interix[3-9]*) + # Interix 3.5 installs completely hosed .la files for C++, so rather than + # hack all around it, let's just trust "g++" to DTRT. + predep_objects_CXX= + postdep_objects_CXX= + postdeps_CXX= + ;; + +linux*) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) + # Sun C++ 5.9 + # + # The more standards-conforming stlport4 library is + # incompatible with the Cstd library. Avoid specifying + # it if it's in CXXFLAGS. Ignore libCrun as + # -library=stlport4 depends on it. + case " $CXX $CXXFLAGS " in + *" -library=stlport4 "*) + solaris_use_stlport4=yes + ;; + esac + if test "$solaris_use_stlport4" != yes; then + postdeps_CXX='-library=Cstd -library=Crun' + fi + ;; + esac + ;; + +solaris*) + case $cc_basename in + CC*) + # The more standards-conforming stlport4 library is + # incompatible with the Cstd library. Avoid specifying + # it if it's in CXXFLAGS. Ignore libCrun as + # -library=stlport4 depends on it. + case " $CXX $CXXFLAGS " in + *" -library=stlport4 "*) + solaris_use_stlport4=yes + ;; + esac + + # Adding this requires a known-good setup of shared libraries for + # Sun compiler versions before 5.6, else PIC objects from an old + # archive will be linked into the output, leading to subtle bugs. + if test "$solaris_use_stlport4" != yes; then + postdeps_CXX='-library=Cstd -library=Crun' + fi + ;; + esac + ;; +esac + +case " $postdeps_CXX " in +*" -lc "*) archive_cmds_need_lc_CXX=no ;; +esac + +lt_prog_compiler_wl_CXX= +lt_prog_compiler_pic_CXX= +lt_prog_compiler_static_CXX= + +{ $as_echo "$as_me:$LINENO: checking for $compiler option to produce PIC" >&5 +$as_echo_n "checking for $compiler option to produce PIC... " >&6; } + + # C++ specific cases for pic, static, wl, etc. + if test "$GXX" = yes; then + lt_prog_compiler_wl_CXX='-Wl,' + lt_prog_compiler_static_CXX='-static' + + case $host_os in + aix*) + # All AIX code is PIC. + if test "$host_cpu" = ia64; then + # AIX 5 now supports IA64 processor + lt_prog_compiler_static_CXX='-Bstatic' + fi + ;; + amigaos*) + # FIXME: we need at least 68020 code to build shared libraries, but + # adding the `-m68020' flag to GCC prevents building anything better, + # like `-m68040'. + lt_prog_compiler_pic_CXX='-m68020 -resident32 -malways-restore-a4' + ;; + beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) + # PIC is the default for these OSes. + ;; + mingw* | cygwin* | os2* | pw32*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + # Although the cygwin gcc ignores -fPIC, still need this for old-style + # (--disable-auto-import) libraries + lt_prog_compiler_pic_CXX='-DDLL_EXPORT' + ;; + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + lt_prog_compiler_pic_CXX='-fno-common' + ;; + *djgpp*) + # DJGPP does not support shared libraries at all + lt_prog_compiler_pic_CXX= + ;; + interix[3-9]*) + # Interix 3.x gcc -fpic/-fPIC options generate broken code. + # Instead, we relocate shared libraries at runtime. + ;; + sysv4*MP*) + if test -d /usr/nec; then + lt_prog_compiler_pic_CXX=-Kconform_pic + fi + ;; + hpux*) + # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but + # not for PA HP-UX. + case $host_cpu in + hppa*64*|ia64*) + ;; + *) + lt_prog_compiler_pic_CXX='-fPIC' + ;; + esac + ;; + *) + lt_prog_compiler_pic_CXX='-fPIC' + ;; + esac + else + case $host_os in + aix[4-9]*) + # All AIX code is PIC. + if test "$host_cpu" = ia64; then + # AIX 5 now supports IA64 processor + lt_prog_compiler_static_CXX='-Bstatic' + else + lt_prog_compiler_static_CXX='-bnso -bI:/lib/syscalls.exp' + fi + ;; + chorus*) + case $cc_basename in + cxch68*) + # Green Hills C++ Compiler + # _LT_AC_TAGVAR(lt_prog_compiler_static, CXX)="--no_auto_instantiation -u __main -u __premain -u _abort -r $COOL_DIR/lib/libOrb.a $MVME_DIR/lib/CC/libC.a $MVME_DIR/lib/classix/libcx.s.a" + ;; + esac + ;; + darwin*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + case $cc_basename in + xlc*) + lt_prog_compiler_pic_CXX='-qnocommon' + lt_prog_compiler_wl_CXX='-Wl,' + ;; + esac + ;; + dgux*) + case $cc_basename in + ec++*) + lt_prog_compiler_pic_CXX='-KPIC' + ;; + ghcx*) + # Green Hills C++ Compiler + lt_prog_compiler_pic_CXX='-pic' + ;; + *) + ;; + esac + ;; + freebsd* | dragonfly*) + # FreeBSD uses GNU C++ + ;; + hpux9* | hpux10* | hpux11*) + case $cc_basename in + CC*) + lt_prog_compiler_wl_CXX='-Wl,' + lt_prog_compiler_static_CXX='${wl}-a ${wl}archive' + if test "$host_cpu" != ia64; then + lt_prog_compiler_pic_CXX='+Z' + fi + ;; + aCC*) + lt_prog_compiler_wl_CXX='-Wl,' + lt_prog_compiler_static_CXX='${wl}-a ${wl}archive' + case $host_cpu in + hppa*64*|ia64*) + # +Z the default + ;; + *) + lt_prog_compiler_pic_CXX='+Z' + ;; + esac + ;; + *) + ;; + esac + ;; + interix*) + # This is c89, which is MS Visual C++ (no shared libs) + # Anyone wants to do a port? + ;; + irix5* | irix6* | nonstopux*) + case $cc_basename in + CC*) + lt_prog_compiler_wl_CXX='-Wl,' + lt_prog_compiler_static_CXX='-non_shared' + # CC pic flag -KPIC is the default. + ;; + *) + ;; + esac + ;; + linux* | k*bsd*-gnu) + case $cc_basename in + KCC*) + # KAI C++ Compiler + lt_prog_compiler_wl_CXX='--backend -Wl,' + lt_prog_compiler_pic_CXX='-fPIC' + ;; + icpc* | ecpc*) + # Intel C++ + lt_prog_compiler_wl_CXX='-Wl,' + lt_prog_compiler_pic_CXX='-KPIC' + lt_prog_compiler_static_CXX='-static' + ;; + pgCC* | pgcpp*) + # Portland Group C++ compiler. + lt_prog_compiler_wl_CXX='-Wl,' + lt_prog_compiler_pic_CXX='-fpic' + lt_prog_compiler_static_CXX='-Bstatic' + ;; + cxx*) + # Compaq C++ + # Make sure the PIC flag is empty. It appears that all Alpha + # Linux and Compaq Tru64 Unix objects are PIC. + lt_prog_compiler_pic_CXX= + lt_prog_compiler_static_CXX='-non_shared' + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) + # Sun C++ 5.9 + lt_prog_compiler_pic_CXX='-KPIC' + lt_prog_compiler_static_CXX='-Bstatic' + lt_prog_compiler_wl_CXX='-Qoption ld ' + ;; + esac + ;; + esac + ;; + lynxos*) + ;; + m88k*) + ;; + mvs*) + case $cc_basename in + cxx*) + lt_prog_compiler_pic_CXX='-W c,exportall' + ;; + *) + ;; + esac + ;; + netbsd*) + ;; + osf3* | osf4* | osf5*) + case $cc_basename in + KCC*) + lt_prog_compiler_wl_CXX='--backend -Wl,' + ;; + RCC*) + # Rational C++ 2.4.1 + lt_prog_compiler_pic_CXX='-pic' + ;; + cxx*) + # Digital/Compaq C++ + lt_prog_compiler_wl_CXX='-Wl,' + # Make sure the PIC flag is empty. It appears that all Alpha + # Linux and Compaq Tru64 Unix objects are PIC. + lt_prog_compiler_pic_CXX= + lt_prog_compiler_static_CXX='-non_shared' + ;; + *) + ;; + esac + ;; + psos*) + ;; + solaris*) + case $cc_basename in + CC*) + # Sun C++ 4.2, 5.x and Centerline C++ + lt_prog_compiler_pic_CXX='-KPIC' + lt_prog_compiler_static_CXX='-Bstatic' + lt_prog_compiler_wl_CXX='-Qoption ld ' + ;; + gcx*) + # Green Hills C++ Compiler + lt_prog_compiler_pic_CXX='-PIC' + ;; + *) + ;; + esac + ;; + sunos4*) + case $cc_basename in + CC*) + # Sun C++ 4.x + lt_prog_compiler_pic_CXX='-pic' + lt_prog_compiler_static_CXX='-Bstatic' + ;; + lcc*) + # Lucid + lt_prog_compiler_pic_CXX='-pic' + ;; + *) + ;; + esac + ;; + tandem*) + case $cc_basename in + NCC*) + # NonStop-UX NCC 3.20 + lt_prog_compiler_pic_CXX='-KPIC' + ;; + *) + ;; + esac + ;; + sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) + case $cc_basename in + CC*) + lt_prog_compiler_wl_CXX='-Wl,' + lt_prog_compiler_pic_CXX='-KPIC' + lt_prog_compiler_static_CXX='-Bstatic' + ;; + esac + ;; + vxworks*) + ;; + *) + lt_prog_compiler_can_build_shared_CXX=no + ;; + esac + fi + +{ $as_echo "$as_me:$LINENO: result: $lt_prog_compiler_pic_CXX" >&5 +$as_echo "$lt_prog_compiler_pic_CXX" >&6; } + +# +# Check to make sure the PIC flag actually works. +# +if test -n "$lt_prog_compiler_pic_CXX"; then + +{ $as_echo "$as_me:$LINENO: checking if $compiler PIC flag $lt_prog_compiler_pic_CXX works" >&5 +$as_echo_n "checking if $compiler PIC flag $lt_prog_compiler_pic_CXX works... " >&6; } +if test "${lt_cv_prog_compiler_pic_works_CXX+set}" = set; then + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_pic_works_CXX=no + ac_outfile=conftest.$ac_objext + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + lt_compiler_flag="$lt_prog_compiler_pic_CXX -DPIC" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + # The option is referenced via a variable to avoid confusing sed. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:12932: $lt_compile\"" >&5) + (eval "$lt_compile" 2>conftest.err) + ac_status=$? + cat conftest.err >&5 + echo "$as_me:12936: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s "$ac_outfile"; then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings other than the usual output. + $echo "X$_lt_compiler_boilerplate" | $Xsed -e '/^$/d' >conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler_pic_works_CXX=yes + fi + fi + $rm conftest* + +fi +{ $as_echo "$as_me:$LINENO: result: $lt_cv_prog_compiler_pic_works_CXX" >&5 +$as_echo "$lt_cv_prog_compiler_pic_works_CXX" >&6; } + +if test x"$lt_cv_prog_compiler_pic_works_CXX" = xyes; then + case $lt_prog_compiler_pic_CXX in + "" | " "*) ;; + *) lt_prog_compiler_pic_CXX=" $lt_prog_compiler_pic_CXX" ;; + esac +else + lt_prog_compiler_pic_CXX= + lt_prog_compiler_can_build_shared_CXX=no +fi + +fi +case $host_os in + # For platforms which do not support PIC, -DPIC is meaningless: + *djgpp*) + lt_prog_compiler_pic_CXX= + ;; + *) + lt_prog_compiler_pic_CXX="$lt_prog_compiler_pic_CXX -DPIC" + ;; +esac + +# +# Check to make sure the static flag actually works. +# +wl=$lt_prog_compiler_wl_CXX eval lt_tmp_static_flag=\"$lt_prog_compiler_static_CXX\" +{ $as_echo "$as_me:$LINENO: checking if $compiler static flag $lt_tmp_static_flag works" >&5 +$as_echo_n "checking if $compiler static flag $lt_tmp_static_flag works... " >&6; } +if test "${lt_cv_prog_compiler_static_works_CXX+set}" = set; then + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_static_works_CXX=no + save_LDFLAGS="$LDFLAGS" + LDFLAGS="$LDFLAGS $lt_tmp_static_flag" + echo "$lt_simple_link_test_code" > conftest.$ac_ext + if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then + # The linker can only warn and ignore the option if not recognized + # So say no if there are warnings + if test -s conftest.err; then + # Append any errors to the config.log. + cat conftest.err 1>&5 + $echo "X$_lt_linker_boilerplate" | $Xsed -e '/^$/d' > conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler_static_works_CXX=yes + fi + else + lt_cv_prog_compiler_static_works_CXX=yes + fi + fi + $rm -r conftest* + LDFLAGS="$save_LDFLAGS" + +fi +{ $as_echo "$as_me:$LINENO: result: $lt_cv_prog_compiler_static_works_CXX" >&5 +$as_echo "$lt_cv_prog_compiler_static_works_CXX" >&6; } + +if test x"$lt_cv_prog_compiler_static_works_CXX" = xyes; then + : +else + lt_prog_compiler_static_CXX= +fi + + +{ $as_echo "$as_me:$LINENO: checking if $compiler supports -c -o file.$ac_objext" >&5 +$as_echo_n "checking if $compiler supports -c -o file.$ac_objext... " >&6; } +if test "${lt_cv_prog_compiler_c_o_CXX+set}" = set; then + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_c_o_CXX=no + $rm -r conftest 2>/dev/null + mkdir conftest + cd conftest + mkdir out + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + lt_compiler_flag="-o out/conftest2.$ac_objext" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:13036: $lt_compile\"" >&5) + (eval "$lt_compile" 2>out/conftest.err) + ac_status=$? + cat out/conftest.err >&5 + echo "$as_me:13040: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s out/conftest2.$ac_objext + then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + $echo "X$_lt_compiler_boilerplate" | $Xsed -e '/^$/d' > out/conftest.exp + $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 + if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then + lt_cv_prog_compiler_c_o_CXX=yes + fi + fi + chmod u+w . 2>&5 + $rm conftest* + # SGI C++ compiler will create directory out/ii_files/ for + # template instantiation + test -d out/ii_files && $rm out/ii_files/* && rmdir out/ii_files + $rm out/* && rmdir out + cd .. + rmdir conftest + $rm conftest* + +fi +{ $as_echo "$as_me:$LINENO: result: $lt_cv_prog_compiler_c_o_CXX" >&5 +$as_echo "$lt_cv_prog_compiler_c_o_CXX" >&6; } + + +hard_links="nottested" +if test "$lt_cv_prog_compiler_c_o_CXX" = no && test "$need_locks" != no; then + # do not overwrite the value of need_locks provided by the user + { $as_echo "$as_me:$LINENO: checking if we can lock with hard links" >&5 +$as_echo_n "checking if we can lock with hard links... " >&6; } + hard_links=yes + $rm conftest* + ln conftest.a conftest.b 2>/dev/null && hard_links=no + touch conftest.a + ln conftest.a conftest.b 2>&5 || hard_links=no + ln conftest.a conftest.b 2>/dev/null && hard_links=no + { $as_echo "$as_me:$LINENO: result: $hard_links" >&5 +$as_echo "$hard_links" >&6; } + if test "$hard_links" = no; then + { $as_echo "$as_me:$LINENO: WARNING: \`$CC' does not support \`-c -o', so \`make -j' may be unsafe" >&5 +$as_echo "$as_me: WARNING: \`$CC' does not support \`-c -o', so \`make -j' may be unsafe" >&2;} + need_locks=warn + fi +else + need_locks=no +fi + +{ $as_echo "$as_me:$LINENO: checking whether the $compiler linker ($LD) supports shared libraries" >&5 +$as_echo_n "checking whether the $compiler linker ($LD) supports shared libraries... " >&6; } + + export_symbols_cmds_CXX='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + case $host_os in + aix[4-9]*) + # If we're using GNU nm, then we don't want the "-C" option. + # -C means demangle to AIX nm, but means don't demangle with GNU nm + if $NM -V 2>&1 | grep 'GNU' > /dev/null; then + export_symbols_cmds_CXX='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$2 == "T") || (\$2 == "D") || (\$2 == "B")) && (substr(\$3,1,1) != ".")) { print \$3 } }'\'' | sort -u > $export_symbols' + else + export_symbols_cmds_CXX='$NM -BCpg $libobjs $convenience | awk '\''{ if (((\$2 == "T") || (\$2 == "D") || (\$2 == "B")) && (substr(\$3,1,1) != ".")) { print \$3 } }'\'' | sort -u > $export_symbols' + fi + ;; + pw32*) + export_symbols_cmds_CXX="$ltdll_cmds" + ;; + cygwin* | mingw*) + export_symbols_cmds_CXX='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1 DATA/;/^.*[ ]__nm__/s/^.*[ ]__nm__\([^ ]*\)[ ][^ ]*/\1 DATA/;/^I[ ]/d;/^[AITW][ ]/s/.*[ ]//'\'' | sort | uniq > $export_symbols' + ;; + *) + export_symbols_cmds_CXX='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + ;; + esac + exclude_expsyms_CXX='_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*' + +{ $as_echo "$as_me:$LINENO: result: $ld_shlibs_CXX" >&5 +$as_echo "$ld_shlibs_CXX" >&6; } +test "$ld_shlibs_CXX" = no && can_build_shared=no + +# +# Do we need to explicitly link libc? +# +case "x$archive_cmds_need_lc_CXX" in +x|xyes) + # Assume -lc should be added + archive_cmds_need_lc_CXX=yes + + if test "$enable_shared" = yes && test "$GCC" = yes; then + case $archive_cmds_CXX in + *'~'*) + # FIXME: we may have to deal with multi-command sequences. + ;; + '$CC '*) + # Test whether the compiler implicitly links with -lc since on some + # systems, -lgcc has to come before -lc. If gcc already passes -lc + # to ld, don't add -lc before -lgcc. + { $as_echo "$as_me:$LINENO: checking whether -lc should be explicitly linked in" >&5 +$as_echo_n "checking whether -lc should be explicitly linked in... " >&6; } + $rm conftest* + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } 2>conftest.err; then + soname=conftest + lib=conftest + libobjs=conftest.$ac_objext + deplibs= + wl=$lt_prog_compiler_wl_CXX + pic_flag=$lt_prog_compiler_pic_CXX + compiler_flags=-v + linker_flags=-v + verstring= + output_objdir=. + libname=conftest + lt_save_allow_undefined_flag=$allow_undefined_flag_CXX + allow_undefined_flag_CXX= + if { (eval echo "$as_me:$LINENO: \"$archive_cmds_CXX 2\>\&1 \| grep \" -lc \" \>/dev/null 2\>\&1\"") >&5 + (eval $archive_cmds_CXX 2\>\&1 \| grep \" -lc \" \>/dev/null 2\>\&1) 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } + then + archive_cmds_need_lc_CXX=no + else + archive_cmds_need_lc_CXX=yes + fi + allow_undefined_flag_CXX=$lt_save_allow_undefined_flag + else + cat conftest.err 1>&5 + fi + $rm conftest* + { $as_echo "$as_me:$LINENO: result: $archive_cmds_need_lc_CXX" >&5 +$as_echo "$archive_cmds_need_lc_CXX" >&6; } + ;; + esac + fi + ;; +esac + +{ $as_echo "$as_me:$LINENO: checking dynamic linker characteristics" >&5 +$as_echo_n "checking dynamic linker characteristics... " >&6; } +library_names_spec= +libname_spec='lib$name' +soname_spec= +shrext_cmds=".so" +postinstall_cmds= +postuninstall_cmds= +finish_cmds= +finish_eval= +shlibpath_var= +shlibpath_overrides_runpath=unknown +version_type=none +dynamic_linker="$host_os ld.so" +sys_lib_dlsearch_path_spec="/lib /usr/lib" + +need_lib_prefix=unknown +hardcode_into_libs=no + +# when you set need_version to no, make sure it does not cause -set_version +# flags to be left without arguments +need_version=unknown + +case $host_os in +aix3*) + version_type=linux + library_names_spec='${libname}${release}${shared_ext}$versuffix $libname.a' + shlibpath_var=LIBPATH + + # AIX 3 has no versioning support, so we append a major version to the name. + soname_spec='${libname}${release}${shared_ext}$major' + ;; + +aix[4-9]*) + version_type=linux + need_lib_prefix=no + need_version=no + hardcode_into_libs=yes + if test "$host_cpu" = ia64; then + # AIX 5 supports IA64 + library_names_spec='${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext}$versuffix $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + else + # With GCC up to 2.95.x, collect2 would create an import file + # for dependence libraries. The import file would start with + # the line `#! .'. This would cause the generated library to + # depend on `.', always an invalid library. This was fixed in + # development snapshots of GCC prior to 3.0. + case $host_os in + aix4 | aix4.[01] | aix4.[01].*) + if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)' + echo ' yes ' + echo '#endif'; } | ${CC} -E - | grep yes > /dev/null; then + : + else + can_build_shared=no + fi + ;; + esac + # AIX (on Power*) has no versioning support, so currently we can not hardcode correct + # soname into executable. Probably we can add versioning support to + # collect2, so additional links can be useful in future. + if test "$aix_use_runtimelinking" = yes; then + # If using run time linking (on AIX 4.2 or later) use lib.so + # instead of lib.a to let people know that these are not + # typical AIX shared libraries. + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + else + # We preserve .a as extension for shared libraries through AIX4.2 + # and later when we are not doing run time linking. + library_names_spec='${libname}${release}.a $libname.a' + soname_spec='${libname}${release}${shared_ext}$major' + fi + shlibpath_var=LIBPATH + fi + ;; + +amigaos*) + library_names_spec='$libname.ixlibrary $libname.a' + # Create ${libname}_ixlibrary.a entries in /sys/libs. + finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`$echo "X$lib" | $Xsed -e '\''s%^.*/\([^/]*\)\.ixlibrary$%\1%'\''`; test $rm /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done' + ;; + +beos*) + library_names_spec='${libname}${shared_ext}' + dynamic_linker="$host_os ld.so" + shlibpath_var=LIBRARY_PATH + ;; + +bsdi[45]*) + version_type=linux + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib" + sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib" + # the default ld.so.conf also contains /usr/contrib/lib and + # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow + # libtool to hard-code these into programs + ;; + +cygwin* | mingw* | pw32*) + version_type=windows + shrext_cmds=".dll" + need_version=no + need_lib_prefix=no + + case $GCC,$host_os in + yes,cygwin* | yes,mingw* | yes,pw32*) + library_names_spec='$libname.dll.a' + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \${file}`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\${base_file}'\''i;echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname~ + chmod a+x \$dldir/$dlname' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $rm \$dlpath' + shlibpath_overrides_runpath=yes + + case $host_os in + cygwin*) + # Cygwin DLLs use 'cyg' prefix rather than 'lib' + soname_spec='`echo ${libname} | sed -e 's/^lib/cyg/'``echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}' + sys_lib_search_path_spec="/usr/lib /lib/w32api /lib /usr/local/lib" + ;; + mingw*) + # MinGW DLLs use traditional 'lib' prefix + soname_spec='${libname}`echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}' + sys_lib_search_path_spec=`$CC -print-search-dirs | grep "^libraries:" | $SED -e "s/^libraries://" -e "s,=/,/,g"` + if echo "$sys_lib_search_path_spec" | grep ';[c-zC-Z]:/' >/dev/null; then + # It is most probably a Windows format PATH printed by + # mingw gcc, but we are running on Cygwin. Gcc prints its search + # path with ; separators, and with drive letters. We can handle the + # drive letters (cygwin fileutils understands them), so leave them, + # especially as we might pass files found there to a mingw objdump, + # which wouldn't understand a cygwinified path. Ahh. + sys_lib_search_path_spec=`echo "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'` + else + sys_lib_search_path_spec=`echo "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + fi + ;; + pw32*) + # pw32 DLLs use 'pw' prefix rather than 'lib' + library_names_spec='`echo ${libname} | sed -e 's/^lib/pw/'``echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}' + ;; + esac + ;; + + *) + library_names_spec='${libname}`echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext} $libname.lib' + ;; + esac + dynamic_linker='Win32 ld.exe' + # FIXME: first we should search . and the directory the executable is in + shlibpath_var=PATH + ;; + +darwin* | rhapsody*) + dynamic_linker="$host_os dyld" + version_type=darwin + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${versuffix}$shared_ext ${libname}${release}${major}$shared_ext ${libname}$shared_ext' + soname_spec='${libname}${release}${major}$shared_ext' + shlibpath_overrides_runpath=yes + shlibpath_var=DYLD_LIBRARY_PATH + shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`' + + sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib' + ;; + +dgux*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname$shared_ext' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +freebsd1*) + dynamic_linker=no + ;; + +freebsd* | dragonfly*) + # DragonFly does not have aout. When/if they implement a new + # versioning mechanism, adjust this. + if test -x /usr/bin/objformat; then + objformat=`/usr/bin/objformat` + else + case $host_os in + freebsd[123]*) objformat=aout ;; + *) objformat=elf ;; + esac + fi + version_type=freebsd-$objformat + case $version_type in + freebsd-elf*) + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext} $libname${shared_ext}' + need_version=no + need_lib_prefix=no + ;; + freebsd-*) + library_names_spec='${libname}${release}${shared_ext}$versuffix $libname${shared_ext}$versuffix' + need_version=yes + ;; + esac + shlibpath_var=LD_LIBRARY_PATH + case $host_os in + freebsd2*) + shlibpath_overrides_runpath=yes + ;; + freebsd3.[01]* | freebsdelf3.[01]*) + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + freebsd3.[2-9]* | freebsdelf3.[2-9]* | \ + freebsd4.[0-5] | freebsdelf4.[0-5] | freebsd4.1.1 | freebsdelf4.1.1) + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + *) # from 4.6 on, and DragonFly + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + esac + ;; + +gnu*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}${major} ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + hardcode_into_libs=yes + ;; + +hpux9* | hpux10* | hpux11*) + # Give a soname corresponding to the major version so that dld.sl refuses to + # link against other versions. + version_type=sunos + need_lib_prefix=no + need_version=no + case $host_cpu in + ia64*) + shrext_cmds='.so' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.so" + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + if test "X$HPUX_IA64_MODE" = X32; then + sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib" + else + sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64" + fi + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + hppa*64*) + shrext_cmds='.sl' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.sl" + shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + *) + shrext_cmds='.sl' + dynamic_linker="$host_os dld.sl" + shlibpath_var=SHLIB_PATH + shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + ;; + esac + # HP-UX runs *really* slowly unless shared libraries are mode 555. + postinstall_cmds='chmod 555 $lib' + ;; + +interix[3-9]*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +irix5* | irix6* | nonstopux*) + case $host_os in + nonstopux*) version_type=nonstopux ;; + *) + if test "$lt_cv_prog_gnu_ld" = yes; then + version_type=linux + else + version_type=irix + fi ;; + esac + need_lib_prefix=no + need_version=no + soname_spec='${libname}${release}${shared_ext}$major' + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext} $libname${shared_ext}' + case $host_os in + irix5* | nonstopux*) + libsuff= shlibsuff= + ;; + *) + case $LD in # libtool.m4 will add one of these switches to LD + *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") + libsuff= shlibsuff= libmagic=32-bit;; + *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") + libsuff=32 shlibsuff=N32 libmagic=N32;; + *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") + libsuff=64 shlibsuff=64 libmagic=64-bit;; + *) libsuff= shlibsuff= libmagic=never-match;; + esac + ;; + esac + shlibpath_var=LD_LIBRARY${shlibsuff}_PATH + shlibpath_overrides_runpath=no + sys_lib_search_path_spec="/usr/lib${libsuff} /lib${libsuff} /usr/local/lib${libsuff}" + sys_lib_dlsearch_path_spec="/usr/lib${libsuff} /lib${libsuff}" + hardcode_into_libs=yes + ;; + +# No shared lib support for Linux oldld, aout, or coff. +linux*oldld* | linux*aout* | linux*coff*) + dynamic_linker=no + ;; + +# This must be Linux ELF. +linux* | k*bsd*-gnu) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + # This implies no fast_install, which is unacceptable. + # Some rework will be needed to allow for fast_install + # before this can be enabled. + hardcode_into_libs=yes + + # Append ld.so.conf contents to the search path + if test -f /etc/ld.so.conf; then + lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \$2)); skip = 1; } { if (!skip) print \$0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[ ]*hwcap[ ]/d;s/[:, ]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;/^$/d' | tr '\n' ' '` + sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra" + fi + + # We used to test for /lib/ld.so.1 and disable shared libraries on + # powerpc, because MkLinux only supported shared libraries with the + # GNU dynamic linker. Since this was broken with cross compilers, + # most powerpc-linux boxes support dynamic linking these days and + # people can always --disable-shared, the test was removed, and we + # assume the GNU/Linux dynamic linker is in use. + dynamic_linker='GNU/Linux ld.so' + ;; + +netbsd*) + version_type=sunos + need_lib_prefix=no + need_version=no + if echo __ELF__ | $CC -E - | grep __ELF__ >/dev/null; then + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + dynamic_linker='NetBSD (a.out) ld.so' + else + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + dynamic_linker='NetBSD ld.elf_so' + fi + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + +newsos6) + version_type=linux + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +nto-qnx*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +openbsd*) + version_type=sunos + sys_lib_dlsearch_path_spec="/usr/lib" + need_lib_prefix=no + # Some older versions of OpenBSD (3.3 at least) *do* need versioned libs. + case $host_os in + openbsd3.3 | openbsd3.3.*) need_version=yes ;; + *) need_version=no ;; + esac + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + shlibpath_var=LD_LIBRARY_PATH + if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then + case $host_os in + openbsd2.[89] | openbsd2.[89].*) + shlibpath_overrides_runpath=no + ;; + *) + shlibpath_overrides_runpath=yes + ;; + esac + else + shlibpath_overrides_runpath=yes + fi + ;; + +os2*) + libname_spec='$name' + shrext_cmds=".dll" + need_lib_prefix=no + library_names_spec='$libname${shared_ext} $libname.a' + dynamic_linker='OS/2 ld.exe' + shlibpath_var=LIBPATH + ;; + +osf3* | osf4* | osf5*) + version_type=osf + need_lib_prefix=no + need_version=no + soname_spec='${libname}${release}${shared_ext}$major' + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib" + sys_lib_dlsearch_path_spec="$sys_lib_search_path_spec" + ;; + +rdos*) + dynamic_linker=no + ;; + +solaris*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + # ldd complains unless libraries are executable + postinstall_cmds='chmod +x $lib' + ;; + +sunos4*) + version_type=sunos + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix' + finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + if test "$with_gnu_ld" = yes; then + need_lib_prefix=no + fi + need_version=yes + ;; + +sysv4 | sysv4.3*) + version_type=linux + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + case $host_vendor in + sni) + shlibpath_overrides_runpath=no + need_lib_prefix=no + export_dynamic_flag_spec='${wl}-Blargedynsym' + runpath_var=LD_RUN_PATH + ;; + siemens) + need_lib_prefix=no + ;; + motorola) + need_lib_prefix=no + need_version=no + shlibpath_overrides_runpath=no + sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib' + ;; + esac + ;; + +sysv4*MP*) + if test -d /usr/nec ;then + version_type=linux + library_names_spec='$libname${shared_ext}.$versuffix $libname${shared_ext}.$major $libname${shared_ext}' + soname_spec='$libname${shared_ext}.$major' + shlibpath_var=LD_LIBRARY_PATH + fi + ;; + +sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + version_type=freebsd-elf + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext} $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + hardcode_into_libs=yes + if test "$with_gnu_ld" = yes; then + sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib' + shlibpath_overrides_runpath=no + else + sys_lib_search_path_spec='/usr/ccs/lib /usr/lib' + shlibpath_overrides_runpath=yes + case $host_os in + sco3.2v5*) + sys_lib_search_path_spec="$sys_lib_search_path_spec /lib" + ;; + esac + fi + sys_lib_dlsearch_path_spec='/usr/lib' + ;; + +uts4*) + version_type=linux + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +*) + dynamic_linker=no + ;; +esac +{ $as_echo "$as_me:$LINENO: result: $dynamic_linker" >&5 +$as_echo "$dynamic_linker" >&6; } +test "$dynamic_linker" = no && can_build_shared=no + +if test "${lt_cv_sys_lib_search_path_spec+set}" = set; then + $as_echo_n "(cached) " >&6 +else + lt_cv_sys_lib_search_path_spec="$sys_lib_search_path_spec" +fi + +sys_lib_search_path_spec="$lt_cv_sys_lib_search_path_spec" +if test "${lt_cv_sys_lib_dlsearch_path_spec+set}" = set; then + $as_echo_n "(cached) " >&6 +else + lt_cv_sys_lib_dlsearch_path_spec="$sys_lib_dlsearch_path_spec" +fi + +sys_lib_dlsearch_path_spec="$lt_cv_sys_lib_dlsearch_path_spec" + +variables_saved_for_relink="PATH $shlibpath_var $runpath_var" +if test "$GCC" = yes; then + variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH" +fi + +{ $as_echo "$as_me:$LINENO: checking how to hardcode library paths into programs" >&5 +$as_echo_n "checking how to hardcode library paths into programs... " >&6; } +hardcode_action_CXX= +if test -n "$hardcode_libdir_flag_spec_CXX" || \ + test -n "$runpath_var_CXX" || \ + test "X$hardcode_automatic_CXX" = "Xyes" ; then + + # We can hardcode non-existant directories. + if test "$hardcode_direct_CXX" != no && + # If the only mechanism to avoid hardcoding is shlibpath_var, we + # have to relink, otherwise we might link with an installed library + # when we should be linking with a yet-to-be-installed one + ## test "$_LT_AC_TAGVAR(hardcode_shlibpath_var, CXX)" != no && + test "$hardcode_minus_L_CXX" != no; then + # Linking always hardcodes the temporary library directory. + hardcode_action_CXX=relink + else + # We can link without hardcoding, and we can hardcode nonexisting dirs. + hardcode_action_CXX=immediate + fi +else + # We cannot hardcode anything, or else we can only hardcode existing + # directories. + hardcode_action_CXX=unsupported +fi +{ $as_echo "$as_me:$LINENO: result: $hardcode_action_CXX" >&5 +$as_echo "$hardcode_action_CXX" >&6; } + +if test "$hardcode_action_CXX" = relink; then + # Fast installation is not supported + enable_fast_install=no +elif test "$shlibpath_overrides_runpath" = yes || + test "$enable_shared" = no; then + # Fast installation is not necessary + enable_fast_install=needless +fi + + +# The else clause should only fire when bootstrapping the +# libtool distribution, otherwise you forgot to ship ltmain.sh +# with your package, and you will get complaints that there are +# no rules to generate ltmain.sh. +if test -f "$ltmain"; then + # See if we are running on zsh, and set the options which allow our commands through + # without removal of \ escapes. + if test -n "${ZSH_VERSION+set}" ; then + setopt NO_GLOB_SUBST + fi + # Now quote all the things that may contain metacharacters while being + # careful not to overquote the AC_SUBSTed values. We take copies of the + # variables and quote the copies for generation of the libtool script. + for var in echo old_CC old_CFLAGS AR AR_FLAGS EGREP RANLIB LN_S LTCC LTCFLAGS NM \ + SED SHELL STRIP \ + libname_spec library_names_spec soname_spec extract_expsyms_cmds \ + old_striplib striplib file_magic_cmd finish_cmds finish_eval \ + deplibs_check_method reload_flag reload_cmds need_locks \ + lt_cv_sys_global_symbol_pipe lt_cv_sys_global_symbol_to_cdecl \ + lt_cv_sys_global_symbol_to_c_name_address \ + sys_lib_search_path_spec sys_lib_dlsearch_path_spec \ + old_postinstall_cmds old_postuninstall_cmds \ + compiler_CXX \ + CC_CXX \ + LD_CXX \ + lt_prog_compiler_wl_CXX \ + lt_prog_compiler_pic_CXX \ + lt_prog_compiler_static_CXX \ + lt_prog_compiler_no_builtin_flag_CXX \ + export_dynamic_flag_spec_CXX \ + thread_safe_flag_spec_CXX \ + whole_archive_flag_spec_CXX \ + enable_shared_with_static_runtimes_CXX \ + old_archive_cmds_CXX \ + old_archive_from_new_cmds_CXX \ + predep_objects_CXX \ + postdep_objects_CXX \ + predeps_CXX \ + postdeps_CXX \ + compiler_lib_search_path_CXX \ + compiler_lib_search_dirs_CXX \ + archive_cmds_CXX \ + archive_expsym_cmds_CXX \ + postinstall_cmds_CXX \ + postuninstall_cmds_CXX \ + old_archive_from_expsyms_cmds_CXX \ + allow_undefined_flag_CXX \ + no_undefined_flag_CXX \ + export_symbols_cmds_CXX \ + hardcode_libdir_flag_spec_CXX \ + hardcode_libdir_flag_spec_ld_CXX \ + hardcode_libdir_separator_CXX \ + hardcode_automatic_CXX \ + module_cmds_CXX \ + module_expsym_cmds_CXX \ + lt_cv_prog_compiler_c_o_CXX \ + fix_srcfile_path_CXX \ + exclude_expsyms_CXX \ + include_expsyms_CXX; do + + case $var in + old_archive_cmds_CXX | \ + old_archive_from_new_cmds_CXX | \ + archive_cmds_CXX | \ + archive_expsym_cmds_CXX | \ + module_cmds_CXX | \ + module_expsym_cmds_CXX | \ + old_archive_from_expsyms_cmds_CXX | \ + export_symbols_cmds_CXX | \ + extract_expsyms_cmds | reload_cmds | finish_cmds | \ + postinstall_cmds | postuninstall_cmds | \ + old_postinstall_cmds | old_postuninstall_cmds | \ + sys_lib_search_path_spec | sys_lib_dlsearch_path_spec) + # Double-quote double-evaled strings. + eval "lt_$var=\\\"\`\$echo \"X\$$var\" | \$Xsed -e \"\$double_quote_subst\" -e \"\$sed_quote_subst\" -e \"\$delay_variable_subst\"\`\\\"" + ;; + *) + eval "lt_$var=\\\"\`\$echo \"X\$$var\" | \$Xsed -e \"\$sed_quote_subst\"\`\\\"" + ;; + esac + done + + case $lt_echo in + *'\$0 --fallback-echo"') + lt_echo=`$echo "X$lt_echo" | $Xsed -e 's/\\\\\\\$0 --fallback-echo"$/$0 --fallback-echo"/'` + ;; + esac + +cfgfile="$ofile" + + cat <<__EOF__ >> "$cfgfile" +# ### BEGIN LIBTOOL TAG CONFIG: $tagname + +# Libtool was configured on host `(hostname || uname -n) 2>/dev/null | sed 1q`: + +# Shell to use when invoking shell scripts. +SHELL=$lt_SHELL + +# Whether or not to build shared libraries. +build_libtool_libs=$enable_shared + +# Whether or not to build static libraries. +build_old_libs=$enable_static + +# Whether or not to add -lc for building shared libraries. +build_libtool_need_lc=$archive_cmds_need_lc_CXX + +# Whether or not to disallow shared libs when runtime libs are static +allow_libtool_libs_with_static_runtimes=$enable_shared_with_static_runtimes_CXX + +# Whether or not to optimize for fast installation. +fast_install=$enable_fast_install + +# The host system. +host_alias=$host_alias +host=$host +host_os=$host_os + +# The build system. +build_alias=$build_alias +build=$build +build_os=$build_os + +# An echo program that does not interpret backslashes. +echo=$lt_echo + +# The archiver. +AR=$lt_AR +AR_FLAGS=$lt_AR_FLAGS + +# A C compiler. +LTCC=$lt_LTCC + +# LTCC compiler flags. +LTCFLAGS=$lt_LTCFLAGS + +# A language-specific compiler. +CC=$lt_compiler_CXX + +# Is the compiler the GNU C compiler? +with_gcc=$GCC_CXX + +# An ERE matcher. +EGREP=$lt_EGREP + +# The linker used to build libraries. +LD=$lt_LD_CXX + +# Whether we need hard or soft links. +LN_S=$lt_LN_S + +# A BSD-compatible nm program. +NM=$lt_NM + +# A symbol stripping program +STRIP=$lt_STRIP + +# Used to examine libraries when file_magic_cmd begins "file" +MAGIC_CMD=$MAGIC_CMD + +# Used on cygwin: DLL creation program. +DLLTOOL="$DLLTOOL" + +# Used on cygwin: object dumper. +OBJDUMP="$OBJDUMP" + +# Used on cygwin: assembler. +AS="$AS" + +# The name of the directory that contains temporary libtool files. +objdir=$objdir + +# How to create reloadable object files. +reload_flag=$lt_reload_flag +reload_cmds=$lt_reload_cmds + +# How to pass a linker flag through the compiler. +wl=$lt_lt_prog_compiler_wl_CXX + +# Object file suffix (normally "o"). +objext="$ac_objext" + +# Old archive suffix (normally "a"). +libext="$libext" + +# Shared library suffix (normally ".so"). +shrext_cmds='$shrext_cmds' + +# Executable file suffix (normally ""). +exeext="$exeext" + +# Additional compiler flags for building library objects. +pic_flag=$lt_lt_prog_compiler_pic_CXX +pic_mode=$pic_mode + +# What is the maximum length of a command? +max_cmd_len=$lt_cv_sys_max_cmd_len + +# Does compiler simultaneously support -c and -o options? +compiler_c_o=$lt_lt_cv_prog_compiler_c_o_CXX + +# Must we lock files when doing compilation? +need_locks=$lt_need_locks + +# Do we need the lib prefix for modules? +need_lib_prefix=$need_lib_prefix + +# Do we need a version for libraries? +need_version=$need_version + +# Whether dlopen is supported. +dlopen_support=$enable_dlopen + +# Whether dlopen of programs is supported. +dlopen_self=$enable_dlopen_self + +# Whether dlopen of statically linked programs is supported. +dlopen_self_static=$enable_dlopen_self_static + +# Compiler flag to prevent dynamic linking. +link_static_flag=$lt_lt_prog_compiler_static_CXX + +# Compiler flag to turn off builtin functions. +no_builtin_flag=$lt_lt_prog_compiler_no_builtin_flag_CXX + +# Compiler flag to allow reflexive dlopens. +export_dynamic_flag_spec=$lt_export_dynamic_flag_spec_CXX + +# Compiler flag to generate shared objects directly from archives. +whole_archive_flag_spec=$lt_whole_archive_flag_spec_CXX + +# Compiler flag to generate thread-safe objects. +thread_safe_flag_spec=$lt_thread_safe_flag_spec_CXX + +# Library versioning type. +version_type=$version_type + +# Format of library name prefix. +libname_spec=$lt_libname_spec + +# List of archive names. First name is the real one, the rest are links. +# The last name is the one that the linker finds with -lNAME. +library_names_spec=$lt_library_names_spec + +# The coded name of the library, if different from the real name. +soname_spec=$lt_soname_spec + +# Commands used to build and install an old-style archive. +RANLIB=$lt_RANLIB +old_archive_cmds=$lt_old_archive_cmds_CXX +old_postinstall_cmds=$lt_old_postinstall_cmds +old_postuninstall_cmds=$lt_old_postuninstall_cmds + +# Create an old-style archive from a shared archive. +old_archive_from_new_cmds=$lt_old_archive_from_new_cmds_CXX + +# Create a temporary old-style archive to link instead of a shared archive. +old_archive_from_expsyms_cmds=$lt_old_archive_from_expsyms_cmds_CXX + +# Commands used to build and install a shared archive. +archive_cmds=$lt_archive_cmds_CXX +archive_expsym_cmds=$lt_archive_expsym_cmds_CXX +postinstall_cmds=$lt_postinstall_cmds +postuninstall_cmds=$lt_postuninstall_cmds + +# Commands used to build a loadable module (assumed same as above if empty) +module_cmds=$lt_module_cmds_CXX +module_expsym_cmds=$lt_module_expsym_cmds_CXX + +# Commands to strip libraries. +old_striplib=$lt_old_striplib +striplib=$lt_striplib + +# Dependencies to place before the objects being linked to create a +# shared library. +predep_objects=$lt_predep_objects_CXX + +# Dependencies to place after the objects being linked to create a +# shared library. +postdep_objects=$lt_postdep_objects_CXX + +# Dependencies to place before the objects being linked to create a +# shared library. +predeps=$lt_predeps_CXX + +# Dependencies to place after the objects being linked to create a +# shared library. +postdeps=$lt_postdeps_CXX + +# The directories searched by this compiler when creating a shared +# library +compiler_lib_search_dirs=$lt_compiler_lib_search_dirs_CXX + +# The library search path used internally by the compiler when linking +# a shared library. +compiler_lib_search_path=$lt_compiler_lib_search_path_CXX + +# Method to check whether dependent libraries are shared objects. +deplibs_check_method=$lt_deplibs_check_method + +# Command to use when deplibs_check_method == file_magic. +file_magic_cmd=$lt_file_magic_cmd + +# Flag that allows shared libraries with undefined symbols to be built. +allow_undefined_flag=$lt_allow_undefined_flag_CXX + +# Flag that forces no undefined symbols. +no_undefined_flag=$lt_no_undefined_flag_CXX + +# Commands used to finish a libtool library installation in a directory. +finish_cmds=$lt_finish_cmds + +# Same as above, but a single script fragment to be evaled but not shown. +finish_eval=$lt_finish_eval + +# Take the output of nm and produce a listing of raw symbols and C names. +global_symbol_pipe=$lt_lt_cv_sys_global_symbol_pipe + +# Transform the output of nm in a proper C declaration +global_symbol_to_cdecl=$lt_lt_cv_sys_global_symbol_to_cdecl + +# Transform the output of nm in a C name address pair +global_symbol_to_c_name_address=$lt_lt_cv_sys_global_symbol_to_c_name_address + +# This is the shared library runtime path variable. +runpath_var=$runpath_var + +# This is the shared library path variable. +shlibpath_var=$shlibpath_var + +# Is shlibpath searched before the hard-coded library search path? +shlibpath_overrides_runpath=$shlibpath_overrides_runpath + +# How to hardcode a shared library path into an executable. +hardcode_action=$hardcode_action_CXX + +# Whether we should hardcode library paths into libraries. +hardcode_into_libs=$hardcode_into_libs + +# Flag to hardcode \$libdir into a binary during linking. +# This must work even if \$libdir does not exist. +hardcode_libdir_flag_spec=$lt_hardcode_libdir_flag_spec_CXX + +# If ld is used when linking, flag to hardcode \$libdir into +# a binary during linking. This must work even if \$libdir does +# not exist. +hardcode_libdir_flag_spec_ld=$lt_hardcode_libdir_flag_spec_ld_CXX + +# Whether we need a single -rpath flag with a separated argument. +hardcode_libdir_separator=$lt_hardcode_libdir_separator_CXX + +# Set to yes if using DIR/libNAME${shared_ext} during linking hardcodes DIR into the +# resulting binary. +hardcode_direct=$hardcode_direct_CXX + +# Set to yes if using the -LDIR flag during linking hardcodes DIR into the +# resulting binary. +hardcode_minus_L=$hardcode_minus_L_CXX + +# Set to yes if using SHLIBPATH_VAR=DIR during linking hardcodes DIR into +# the resulting binary. +hardcode_shlibpath_var=$hardcode_shlibpath_var_CXX + +# Set to yes if building a shared library automatically hardcodes DIR into the library +# and all subsequent libraries and executables linked against it. +hardcode_automatic=$hardcode_automatic_CXX + +# Variables whose values should be saved in libtool wrapper scripts and +# restored at relink time. +variables_saved_for_relink="$variables_saved_for_relink" + +# Whether libtool must link a program against all its dependency libraries. +link_all_deplibs=$link_all_deplibs_CXX + +# Compile-time system search path for libraries +sys_lib_search_path_spec=$lt_sys_lib_search_path_spec + +# Run-time system search path for libraries +sys_lib_dlsearch_path_spec=$lt_sys_lib_dlsearch_path_spec + +# Fix the shell variable \$srcfile for the compiler. +fix_srcfile_path=$lt_fix_srcfile_path + +# Set to yes if exported symbols are required. +always_export_symbols=$always_export_symbols_CXX + +# The commands to list exported symbols. +export_symbols_cmds=$lt_export_symbols_cmds_CXX + +# The commands to extract the exported symbol list from a shared archive. +extract_expsyms_cmds=$lt_extract_expsyms_cmds + +# Symbols that should not be listed in the preloaded symbols. +exclude_expsyms=$lt_exclude_expsyms_CXX + +# Symbols that must always be exported. +include_expsyms=$lt_include_expsyms_CXX + +# ### END LIBTOOL TAG CONFIG: $tagname + +__EOF__ + + +else + # If there is no Makefile yet, we rely on a make rule to execute + # `config.status --recheck' to rerun these tests and create the + # libtool script then. + ltmain_in=`echo $ltmain | sed -e 's/\.sh$/.in/'` + if test -f "$ltmain_in"; then + test -f Makefile && make "$ltmain" + fi +fi + + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +CC=$lt_save_CC +LDCXX=$LD +LD=$lt_save_LD +GCC=$lt_save_GCC +with_gnu_ldcxx=$with_gnu_ld +with_gnu_ld=$lt_save_with_gnu_ld +lt_cv_path_LDCXX=$lt_cv_path_LD +lt_cv_path_LD=$lt_save_path_LD +lt_cv_prog_gnu_ldcxx=$lt_cv_prog_gnu_ld +lt_cv_prog_gnu_ld=$lt_save_with_gnu_ld + + else + tagname="" + fi + ;; + + F77) + if test -n "$F77" && test "X$F77" != "Xno"; then + +ac_ext=f +ac_compile='$F77 -c $FFLAGS conftest.$ac_ext >&5' +ac_link='$F77 -o conftest$ac_exeext $FFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_f77_compiler_gnu + + +archive_cmds_need_lc_F77=no +allow_undefined_flag_F77= +always_export_symbols_F77=no +archive_expsym_cmds_F77= +export_dynamic_flag_spec_F77= +hardcode_direct_F77=no +hardcode_libdir_flag_spec_F77= +hardcode_libdir_flag_spec_ld_F77= +hardcode_libdir_separator_F77= +hardcode_minus_L_F77=no +hardcode_automatic_F77=no +module_cmds_F77= +module_expsym_cmds_F77= +link_all_deplibs_F77=unknown +old_archive_cmds_F77=$old_archive_cmds +no_undefined_flag_F77= +whole_archive_flag_spec_F77= +enable_shared_with_static_runtimes_F77=no + +# Source file extension for f77 test sources. +ac_ext=f + +# Object file extension for compiled f77 test sources. +objext=o +objext_F77=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="\ + subroutine t + return + end +" + +# Code to be used in simple link tests +lt_simple_link_test_code="\ + program t + end +" + +# ltmain only uses $CC for tagged configurations so make sure $CC is set. + +# If no C compiler was specified, use CC. +LTCC=${LTCC-"$CC"} + +# If no C compiler flags were specified, use CFLAGS. +LTCFLAGS=${LTCFLAGS-"$CFLAGS"} + +# Allow CC to be a program name with arguments. +compiler=$CC + + +# save warnings/boilerplate of simple test code +ac_outfile=conftest.$ac_objext +echo "$lt_simple_compile_test_code" >conftest.$ac_ext +eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_compiler_boilerplate=`cat conftest.err` +$rm conftest* + +ac_outfile=conftest.$ac_objext +echo "$lt_simple_link_test_code" >conftest.$ac_ext +eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_linker_boilerplate=`cat conftest.err` +$rm -r conftest* + + +# Allow CC to be a program name with arguments. +lt_save_CC="$CC" +CC=${F77-"f77"} +compiler=$CC +compiler_F77=$CC +for cc_temp in $compiler""; do + case $cc_temp in + compile | *[\\/]compile | ccache | *[\\/]ccache ) ;; + distcc | *[\\/]distcc | purify | *[\\/]purify ) ;; + \-*) ;; + *) break;; + esac +done +cc_basename=`$echo "X$cc_temp" | $Xsed -e 's%.*/%%' -e "s%^$host_alias-%%"` + + +{ $as_echo "$as_me:$LINENO: checking if libtool supports shared libraries" >&5 +$as_echo_n "checking if libtool supports shared libraries... " >&6; } +{ $as_echo "$as_me:$LINENO: result: $can_build_shared" >&5 +$as_echo "$can_build_shared" >&6; } + +{ $as_echo "$as_me:$LINENO: checking whether to build shared libraries" >&5 +$as_echo_n "checking whether to build shared libraries... " >&6; } +test "$can_build_shared" = "no" && enable_shared=no + +# On AIX, shared libraries and static libraries use the same namespace, and +# are all built from PIC. +case $host_os in +aix3*) + test "$enable_shared" = yes && enable_static=no + if test -n "$RANLIB"; then + archive_cmds="$archive_cmds~\$RANLIB \$lib" + postinstall_cmds='$RANLIB $lib' + fi + ;; +aix[4-9]*) + if test "$host_cpu" != ia64 && test "$aix_use_runtimelinking" = no ; then + test "$enable_shared" = yes && enable_static=no + fi + ;; +esac +{ $as_echo "$as_me:$LINENO: result: $enable_shared" >&5 +$as_echo "$enable_shared" >&6; } + +{ $as_echo "$as_me:$LINENO: checking whether to build static libraries" >&5 +$as_echo_n "checking whether to build static libraries... " >&6; } +# Make sure either enable_shared or enable_static is yes. +test "$enable_shared" = yes || enable_static=yes +{ $as_echo "$as_me:$LINENO: result: $enable_static" >&5 +$as_echo "$enable_static" >&6; } + +GCC_F77="$G77" +LD_F77="$LD" + +lt_prog_compiler_wl_F77= +lt_prog_compiler_pic_F77= +lt_prog_compiler_static_F77= + +{ $as_echo "$as_me:$LINENO: checking for $compiler option to produce PIC" >&5 +$as_echo_n "checking for $compiler option to produce PIC... " >&6; } + + if test "$GCC" = yes; then + lt_prog_compiler_wl_F77='-Wl,' + lt_prog_compiler_static_F77='-static' + + case $host_os in + aix*) + # All AIX code is PIC. + if test "$host_cpu" = ia64; then + # AIX 5 now supports IA64 processor + lt_prog_compiler_static_F77='-Bstatic' + fi + ;; + + amigaos*) + # FIXME: we need at least 68020 code to build shared libraries, but + # adding the `-m68020' flag to GCC prevents building anything better, + # like `-m68040'. + lt_prog_compiler_pic_F77='-m68020 -resident32 -malways-restore-a4' + ;; + + beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) + # PIC is the default for these OSes. + ;; + + mingw* | cygwin* | pw32* | os2*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + # Although the cygwin gcc ignores -fPIC, still need this for old-style + # (--disable-auto-import) libraries + lt_prog_compiler_pic_F77='-DDLL_EXPORT' + ;; + + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + lt_prog_compiler_pic_F77='-fno-common' + ;; + + interix[3-9]*) + # Interix 3.x gcc -fpic/-fPIC options generate broken code. + # Instead, we relocate shared libraries at runtime. + ;; + + msdosdjgpp*) + # Just because we use GCC doesn't mean we suddenly get shared libraries + # on systems that don't support them. + lt_prog_compiler_can_build_shared_F77=no + enable_shared=no + ;; + + sysv4*MP*) + if test -d /usr/nec; then + lt_prog_compiler_pic_F77=-Kconform_pic + fi + ;; + + hpux*) + # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but + # not for PA HP-UX. + case $host_cpu in + hppa*64*|ia64*) + # +Z the default + ;; + *) + lt_prog_compiler_pic_F77='-fPIC' + ;; + esac + ;; + + *) + lt_prog_compiler_pic_F77='-fPIC' + ;; + esac + else + # PORTME Check for flag to pass linker flags through the system compiler. + case $host_os in + aix*) + lt_prog_compiler_wl_F77='-Wl,' + if test "$host_cpu" = ia64; then + # AIX 5 now supports IA64 processor + lt_prog_compiler_static_F77='-Bstatic' + else + lt_prog_compiler_static_F77='-bnso -bI:/lib/syscalls.exp' + fi + ;; + darwin*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + case $cc_basename in + xlc*) + lt_prog_compiler_pic_F77='-qnocommon' + lt_prog_compiler_wl_F77='-Wl,' + ;; + esac + ;; + + mingw* | cygwin* | pw32* | os2*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + lt_prog_compiler_pic_F77='-DDLL_EXPORT' + ;; + + hpux9* | hpux10* | hpux11*) + lt_prog_compiler_wl_F77='-Wl,' + # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but + # not for PA HP-UX. + case $host_cpu in + hppa*64*|ia64*) + # +Z the default + ;; + *) + lt_prog_compiler_pic_F77='+Z' + ;; + esac + # Is there a better lt_prog_compiler_static that works with the bundled CC? + lt_prog_compiler_static_F77='${wl}-a ${wl}archive' + ;; + + irix5* | irix6* | nonstopux*) + lt_prog_compiler_wl_F77='-Wl,' + # PIC (with -KPIC) is the default. + lt_prog_compiler_static_F77='-non_shared' + ;; + + newsos6) + lt_prog_compiler_pic_F77='-KPIC' + lt_prog_compiler_static_F77='-Bstatic' + ;; + + linux* | k*bsd*-gnu) + case $cc_basename in + icc* | ecc*) + lt_prog_compiler_wl_F77='-Wl,' + lt_prog_compiler_pic_F77='-KPIC' + lt_prog_compiler_static_F77='-static' + ;; + pgcc* | pgf77* | pgf90* | pgf95*) + # Portland Group compilers (*not* the Pentium gcc compiler, + # which looks to be a dead project) + lt_prog_compiler_wl_F77='-Wl,' + lt_prog_compiler_pic_F77='-fpic' + lt_prog_compiler_static_F77='-Bstatic' + ;; + ccc*) + lt_prog_compiler_wl_F77='-Wl,' + # All Alpha code is PIC. + lt_prog_compiler_static_F77='-non_shared' + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) + # Sun C 5.9 + lt_prog_compiler_pic_F77='-KPIC' + lt_prog_compiler_static_F77='-Bstatic' + lt_prog_compiler_wl_F77='-Wl,' + ;; + *Sun\ F*) + # Sun Fortran 8.3 passes all unrecognized flags to the linker + lt_prog_compiler_pic_F77='-KPIC' + lt_prog_compiler_static_F77='-Bstatic' + lt_prog_compiler_wl_F77='' + ;; + esac + ;; + esac + ;; + + osf3* | osf4* | osf5*) + lt_prog_compiler_wl_F77='-Wl,' + # All OSF/1 code is PIC. + lt_prog_compiler_static_F77='-non_shared' + ;; + + rdos*) + lt_prog_compiler_static_F77='-non_shared' + ;; + + solaris*) + lt_prog_compiler_pic_F77='-KPIC' + lt_prog_compiler_static_F77='-Bstatic' + case $cc_basename in + f77* | f90* | f95*) + lt_prog_compiler_wl_F77='-Qoption ld ';; + *) + lt_prog_compiler_wl_F77='-Wl,';; + esac + ;; + + sunos4*) + lt_prog_compiler_wl_F77='-Qoption ld ' + lt_prog_compiler_pic_F77='-PIC' + lt_prog_compiler_static_F77='-Bstatic' + ;; + + sysv4 | sysv4.2uw2* | sysv4.3*) + lt_prog_compiler_wl_F77='-Wl,' + lt_prog_compiler_pic_F77='-KPIC' + lt_prog_compiler_static_F77='-Bstatic' + ;; + + sysv4*MP*) + if test -d /usr/nec ;then + lt_prog_compiler_pic_F77='-Kconform_pic' + lt_prog_compiler_static_F77='-Bstatic' + fi + ;; + + sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) + lt_prog_compiler_wl_F77='-Wl,' + lt_prog_compiler_pic_F77='-KPIC' + lt_prog_compiler_static_F77='-Bstatic' + ;; + + unicos*) + lt_prog_compiler_wl_F77='-Wl,' + lt_prog_compiler_can_build_shared_F77=no + ;; + + uts4*) + lt_prog_compiler_pic_F77='-pic' + lt_prog_compiler_static_F77='-Bstatic' + ;; + + *) + lt_prog_compiler_can_build_shared_F77=no + ;; + esac + fi + +{ $as_echo "$as_me:$LINENO: result: $lt_prog_compiler_pic_F77" >&5 +$as_echo "$lt_prog_compiler_pic_F77" >&6; } + +# +# Check to make sure the PIC flag actually works. +# +if test -n "$lt_prog_compiler_pic_F77"; then + +{ $as_echo "$as_me:$LINENO: checking if $compiler PIC flag $lt_prog_compiler_pic_F77 works" >&5 +$as_echo_n "checking if $compiler PIC flag $lt_prog_compiler_pic_F77 works... " >&6; } +if test "${lt_cv_prog_compiler_pic_works_F77+set}" = set; then + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_pic_works_F77=no + ac_outfile=conftest.$ac_objext + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + lt_compiler_flag="$lt_prog_compiler_pic_F77" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + # The option is referenced via a variable to avoid confusing sed. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:14619: $lt_compile\"" >&5) + (eval "$lt_compile" 2>conftest.err) + ac_status=$? + cat conftest.err >&5 + echo "$as_me:14623: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s "$ac_outfile"; then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings other than the usual output. + $echo "X$_lt_compiler_boilerplate" | $Xsed -e '/^$/d' >conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler_pic_works_F77=yes + fi + fi + $rm conftest* + +fi +{ $as_echo "$as_me:$LINENO: result: $lt_cv_prog_compiler_pic_works_F77" >&5 +$as_echo "$lt_cv_prog_compiler_pic_works_F77" >&6; } + +if test x"$lt_cv_prog_compiler_pic_works_F77" = xyes; then + case $lt_prog_compiler_pic_F77 in + "" | " "*) ;; + *) lt_prog_compiler_pic_F77=" $lt_prog_compiler_pic_F77" ;; + esac +else + lt_prog_compiler_pic_F77= + lt_prog_compiler_can_build_shared_F77=no +fi + +fi +case $host_os in + # For platforms which do not support PIC, -DPIC is meaningless: + *djgpp*) + lt_prog_compiler_pic_F77= + ;; + *) + lt_prog_compiler_pic_F77="$lt_prog_compiler_pic_F77" + ;; +esac + +# +# Check to make sure the static flag actually works. +# +wl=$lt_prog_compiler_wl_F77 eval lt_tmp_static_flag=\"$lt_prog_compiler_static_F77\" +{ $as_echo "$as_me:$LINENO: checking if $compiler static flag $lt_tmp_static_flag works" >&5 +$as_echo_n "checking if $compiler static flag $lt_tmp_static_flag works... " >&6; } +if test "${lt_cv_prog_compiler_static_works_F77+set}" = set; then + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_static_works_F77=no + save_LDFLAGS="$LDFLAGS" + LDFLAGS="$LDFLAGS $lt_tmp_static_flag" + echo "$lt_simple_link_test_code" > conftest.$ac_ext + if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then + # The linker can only warn and ignore the option if not recognized + # So say no if there are warnings + if test -s conftest.err; then + # Append any errors to the config.log. + cat conftest.err 1>&5 + $echo "X$_lt_linker_boilerplate" | $Xsed -e '/^$/d' > conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler_static_works_F77=yes + fi + else + lt_cv_prog_compiler_static_works_F77=yes + fi + fi + $rm -r conftest* + LDFLAGS="$save_LDFLAGS" + +fi +{ $as_echo "$as_me:$LINENO: result: $lt_cv_prog_compiler_static_works_F77" >&5 +$as_echo "$lt_cv_prog_compiler_static_works_F77" >&6; } + +if test x"$lt_cv_prog_compiler_static_works_F77" = xyes; then + : +else + lt_prog_compiler_static_F77= +fi + + +{ $as_echo "$as_me:$LINENO: checking if $compiler supports -c -o file.$ac_objext" >&5 +$as_echo_n "checking if $compiler supports -c -o file.$ac_objext... " >&6; } +if test "${lt_cv_prog_compiler_c_o_F77+set}" = set; then + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_c_o_F77=no + $rm -r conftest 2>/dev/null + mkdir conftest + cd conftest + mkdir out + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + lt_compiler_flag="-o out/conftest2.$ac_objext" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:14723: $lt_compile\"" >&5) + (eval "$lt_compile" 2>out/conftest.err) + ac_status=$? + cat out/conftest.err >&5 + echo "$as_me:14727: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s out/conftest2.$ac_objext + then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + $echo "X$_lt_compiler_boilerplate" | $Xsed -e '/^$/d' > out/conftest.exp + $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 + if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then + lt_cv_prog_compiler_c_o_F77=yes + fi + fi + chmod u+w . 2>&5 + $rm conftest* + # SGI C++ compiler will create directory out/ii_files/ for + # template instantiation + test -d out/ii_files && $rm out/ii_files/* && rmdir out/ii_files + $rm out/* && rmdir out + cd .. + rmdir conftest + $rm conftest* + +fi +{ $as_echo "$as_me:$LINENO: result: $lt_cv_prog_compiler_c_o_F77" >&5 +$as_echo "$lt_cv_prog_compiler_c_o_F77" >&6; } + + +hard_links="nottested" +if test "$lt_cv_prog_compiler_c_o_F77" = no && test "$need_locks" != no; then + # do not overwrite the value of need_locks provided by the user + { $as_echo "$as_me:$LINENO: checking if we can lock with hard links" >&5 +$as_echo_n "checking if we can lock with hard links... " >&6; } + hard_links=yes + $rm conftest* + ln conftest.a conftest.b 2>/dev/null && hard_links=no + touch conftest.a + ln conftest.a conftest.b 2>&5 || hard_links=no + ln conftest.a conftest.b 2>/dev/null && hard_links=no + { $as_echo "$as_me:$LINENO: result: $hard_links" >&5 +$as_echo "$hard_links" >&6; } + if test "$hard_links" = no; then + { $as_echo "$as_me:$LINENO: WARNING: \`$CC' does not support \`-c -o', so \`make -j' may be unsafe" >&5 +$as_echo "$as_me: WARNING: \`$CC' does not support \`-c -o', so \`make -j' may be unsafe" >&2;} + need_locks=warn + fi +else + need_locks=no +fi + +{ $as_echo "$as_me:$LINENO: checking whether the $compiler linker ($LD) supports shared libraries" >&5 +$as_echo_n "checking whether the $compiler linker ($LD) supports shared libraries... " >&6; } + + runpath_var= + allow_undefined_flag_F77= + enable_shared_with_static_runtimes_F77=no + archive_cmds_F77= + archive_expsym_cmds_F77= + old_archive_From_new_cmds_F77= + old_archive_from_expsyms_cmds_F77= + export_dynamic_flag_spec_F77= + whole_archive_flag_spec_F77= + thread_safe_flag_spec_F77= + hardcode_libdir_flag_spec_F77= + hardcode_libdir_flag_spec_ld_F77= + hardcode_libdir_separator_F77= + hardcode_direct_F77=no + hardcode_minus_L_F77=no + hardcode_shlibpath_var_F77=unsupported + link_all_deplibs_F77=unknown + hardcode_automatic_F77=no + module_cmds_F77= + module_expsym_cmds_F77= + always_export_symbols_F77=no + export_symbols_cmds_F77='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + # include_expsyms should be a list of space-separated symbols to be *always* + # included in the symbol list + include_expsyms_F77= + # exclude_expsyms can be an extended regexp of symbols to exclude + # it will be wrapped by ` (' and `)$', so one must not match beginning or + # end of line. Example: `a|bc|.*d.*' will exclude the symbols `a' and `bc', + # as well as any symbol that contains `d'. + exclude_expsyms_F77='_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*' + # Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out + # platforms (ab)use it in PIC code, but their linkers get confused if + # the symbol is explicitly referenced. Since portable code cannot + # rely on this symbol name, it's probably fine to never include it in + # preloaded symbol tables. + # Exclude shared library initialization/finalization symbols. + extract_expsyms_cmds= + # Just being paranoid about ensuring that cc_basename is set. + for cc_temp in $compiler""; do + case $cc_temp in + compile | *[\\/]compile | ccache | *[\\/]ccache ) ;; + distcc | *[\\/]distcc | purify | *[\\/]purify ) ;; + \-*) ;; + *) break;; + esac +done +cc_basename=`$echo "X$cc_temp" | $Xsed -e 's%.*/%%' -e "s%^$host_alias-%%"` + + case $host_os in + cygwin* | mingw* | pw32*) + # FIXME: the MSVC++ port hasn't been tested in a loooong time + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + if test "$GCC" != yes; then + with_gnu_ld=no + fi + ;; + interix*) + # we just hope/assume this is gcc and not c89 (= MSVC++) + with_gnu_ld=yes + ;; + openbsd*) + with_gnu_ld=no + ;; + esac + + ld_shlibs_F77=yes + if test "$with_gnu_ld" = yes; then + # If archive_cmds runs LD, not CC, wlarc should be empty + wlarc='${wl}' + + # Set some defaults for GNU ld with shared library support. These + # are reset later if shared libraries are not supported. Putting them + # here allows them to be overridden if necessary. + runpath_var=LD_RUN_PATH + hardcode_libdir_flag_spec_F77='${wl}--rpath ${wl}$libdir' + export_dynamic_flag_spec_F77='${wl}--export-dynamic' + # ancient GNU ld didn't support --whole-archive et. al. + if $LD --help 2>&1 | grep 'no-whole-archive' > /dev/null; then + whole_archive_flag_spec_F77="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive' + else + whole_archive_flag_spec_F77= + fi + supports_anon_versioning=no + case `$LD -v 2>/dev/null` in + *\ [01].* | *\ 2.[0-9].* | *\ 2.10.*) ;; # catch versions < 2.11 + *\ 2.11.93.0.2\ *) supports_anon_versioning=yes ;; # RH7.3 ... + *\ 2.11.92.0.12\ *) supports_anon_versioning=yes ;; # Mandrake 8.2 ... + *\ 2.11.*) ;; # other 2.11 versions + *) supports_anon_versioning=yes ;; + esac + + # See if GNU ld supports shared libraries. + case $host_os in + aix[3-9]*) + # On AIX/PPC, the GNU linker is very broken + if test "$host_cpu" != ia64; then + ld_shlibs_F77=no + cat <&2 + +*** Warning: the GNU linker, at least up to release 2.9.1, is reported +*** to be unable to reliably create shared libraries on AIX. +*** Therefore, libtool is disabling shared libraries support. If you +*** really care for shared libraries, you may want to modify your PATH +*** so that a non-GNU linker is found, and then restart. + +EOF + fi + ;; + + amigaos*) + archive_cmds_F77='$rm $output_objdir/a2ixlibrary.data~$echo "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$echo "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$echo "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$echo "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + hardcode_libdir_flag_spec_F77='-L$libdir' + hardcode_minus_L_F77=yes + + # Samuel A. Falvo II reports + # that the semantics of dynamic libraries on AmigaOS, at least up + # to version 4, is to share data among multiple programs linked + # with the same dynamic library. Since this doesn't match the + # behavior of shared libraries on other platforms, we can't use + # them. + ld_shlibs_F77=no + ;; + + beos*) + if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + allow_undefined_flag_F77=unsupported + # Joseph Beckenbach says some releases of gcc + # support --undefined. This deserves some investigation. FIXME + archive_cmds_F77='$CC -nostart $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + else + ld_shlibs_F77=no + fi + ;; + + cygwin* | mingw* | pw32*) + # _LT_AC_TAGVAR(hardcode_libdir_flag_spec, F77) is actually meaningless, + # as there is no search path for DLLs. + hardcode_libdir_flag_spec_F77='-L$libdir' + allow_undefined_flag_F77=unsupported + always_export_symbols_F77=no + enable_shared_with_static_runtimes_F77=yes + export_symbols_cmds_F77='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1 DATA/'\'' -e '\''/^[AITW][ ]/s/.*[ ]//'\'' | sort | uniq > $export_symbols' + + if $LD --help 2>&1 | grep 'auto-import' > /dev/null; then + archive_cmds_F77='$CC -shared $libobjs $deplibs $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + # If the export-symbols file already is a .def file (1st line + # is EXPORTS), use it as is; otherwise, prepend... + archive_expsym_cmds_F77='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then + cp $export_symbols $output_objdir/$soname.def; + else + echo EXPORTS > $output_objdir/$soname.def; + cat $export_symbols >> $output_objdir/$soname.def; + fi~ + $CC -shared $output_objdir/$soname.def $libobjs $deplibs $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + else + ld_shlibs_F77=no + fi + ;; + + interix[3-9]*) + hardcode_direct_F77=no + hardcode_shlibpath_var_F77=no + hardcode_libdir_flag_spec_F77='${wl}-rpath,$libdir' + export_dynamic_flag_spec_F77='${wl}-E' + # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. + # Instead, shared libraries are loaded at an image base (0x10000000 by + # default) and relocated if they conflict, which is a slow very memory + # consuming and fragmenting process. To avoid this, we pick a random, + # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link + # time. Moving up from 0x10000000 also allows more sbrk(2) space. + archive_cmds_F77='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + archive_expsym_cmds_F77='sed "s,^,_," $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--retain-symbols-file,$output_objdir/$soname.expsym ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + ;; + + gnu* | linux* | k*bsd*-gnu) + if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + tmp_addflag= + case $cc_basename,$host_cpu in + pgcc*) # Portland Group C compiler + whole_archive_flag_spec_F77='${wl}--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; $echo \"$new_convenience\"` ${wl}--no-whole-archive' + tmp_addflag=' $pic_flag' + ;; + pgf77* | pgf90* | pgf95*) # Portland Group f77 and f90 compilers + whole_archive_flag_spec_F77='${wl}--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; $echo \"$new_convenience\"` ${wl}--no-whole-archive' + tmp_addflag=' $pic_flag -Mnomain' ;; + ecc*,ia64* | icc*,ia64*) # Intel C compiler on ia64 + tmp_addflag=' -i_dynamic' ;; + efc*,ia64* | ifort*,ia64*) # Intel Fortran compiler on ia64 + tmp_addflag=' -i_dynamic -nofor_main' ;; + ifc* | ifort*) # Intel Fortran compiler + tmp_addflag=' -nofor_main' ;; + esac + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) # Sun C 5.9 + whole_archive_flag_spec_F77='${wl}--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; $echo \"$new_convenience\"` ${wl}--no-whole-archive' + tmp_sharedflag='-G' ;; + *Sun\ F*) # Sun Fortran 8.3 + tmp_sharedflag='-G' ;; + *) + tmp_sharedflag='-shared' ;; + esac + archive_cmds_F77='$CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + + if test $supports_anon_versioning = yes; then + archive_expsym_cmds_F77='$echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + $echo "local: *; };" >> $output_objdir/$libname.ver~ + $CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-version-script ${wl}$output_objdir/$libname.ver -o $lib' + fi + else + ld_shlibs_F77=no + fi + ;; + + netbsd*) + if echo __ELF__ | $CC -E - | grep __ELF__ >/dev/null; then + archive_cmds_F77='$LD -Bshareable $libobjs $deplibs $linker_flags -o $lib' + wlarc= + else + archive_cmds_F77='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + archive_expsym_cmds_F77='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + fi + ;; + + solaris*) + if $LD -v 2>&1 | grep 'BFD 2\.8' > /dev/null; then + ld_shlibs_F77=no + cat <&2 + +*** Warning: The releases 2.8.* of the GNU linker cannot reliably +*** create shared libraries on Solaris systems. Therefore, libtool +*** is disabling shared libraries support. We urge you to upgrade GNU +*** binutils to release 2.9.1 or newer. Another option is to modify +*** your PATH or compiler configuration so that the native linker is +*** used, and then restart. + +EOF + elif $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + archive_cmds_F77='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + archive_expsym_cmds_F77='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + else + ld_shlibs_F77=no + fi + ;; + + sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*) + case `$LD -v 2>&1` in + *\ [01].* | *\ 2.[0-9].* | *\ 2.1[0-5].*) + ld_shlibs_F77=no + cat <<_LT_EOF 1>&2 + +*** Warning: Releases of the GNU linker prior to 2.16.91.0.3 can not +*** reliably create shared libraries on SCO systems. Therefore, libtool +*** is disabling shared libraries support. We urge you to upgrade GNU +*** binutils to release 2.16.91.0.3 or newer. Another option is to modify +*** your PATH or compiler configuration so that the native linker is +*** used, and then restart. + +_LT_EOF + ;; + *) + if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + hardcode_libdir_flag_spec_F77='`test -z "$SCOABSPATH" && echo ${wl}-rpath,$libdir`' + archive_cmds_F77='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname,\${SCOABSPATH:+${install_libdir}/}$soname -o $lib' + archive_expsym_cmds_F77='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname,\${SCOABSPATH:+${install_libdir}/}$soname,-retain-symbols-file,$export_symbols -o $lib' + else + ld_shlibs_F77=no + fi + ;; + esac + ;; + + sunos4*) + archive_cmds_F77='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linker_flags' + wlarc= + hardcode_direct_F77=yes + hardcode_shlibpath_var_F77=no + ;; + + *) + if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + archive_cmds_F77='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + archive_expsym_cmds_F77='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + else + ld_shlibs_F77=no + fi + ;; + esac + + if test "$ld_shlibs_F77" = no; then + runpath_var= + hardcode_libdir_flag_spec_F77= + export_dynamic_flag_spec_F77= + whole_archive_flag_spec_F77= + fi + else + # PORTME fill in a description of your system's linker (not GNU ld) + case $host_os in + aix3*) + allow_undefined_flag_F77=unsupported + always_export_symbols_F77=yes + archive_expsym_cmds_F77='$LD -o $output_objdir/$soname $libobjs $deplibs $linker_flags -bE:$export_symbols -T512 -H512 -bM:SRE~$AR $AR_FLAGS $lib $output_objdir/$soname' + # Note: this linker hardcodes the directories in LIBPATH if there + # are no directories specified by -L. + hardcode_minus_L_F77=yes + if test "$GCC" = yes && test -z "$lt_prog_compiler_static"; then + # Neither direct hardcoding nor static linking is supported with a + # broken collect2. + hardcode_direct_F77=unsupported + fi + ;; + + aix[4-9]*) + if test "$host_cpu" = ia64; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + exp_sym_flag='-Bexport' + no_entry_flag="" + else + # If we're using GNU nm, then we don't want the "-C" option. + # -C means demangle to AIX nm, but means don't demangle with GNU nm + if $NM -V 2>&1 | grep 'GNU' > /dev/null; then + export_symbols_cmds_F77='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$2 == "T") || (\$2 == "D") || (\$2 == "B")) && (substr(\$3,1,1) != ".")) { print \$3 } }'\'' | sort -u > $export_symbols' + else + export_symbols_cmds_F77='$NM -BCpg $libobjs $convenience | awk '\''{ if (((\$2 == "T") || (\$2 == "D") || (\$2 == "B")) && (substr(\$3,1,1) != ".")) { print \$3 } }'\'' | sort -u > $export_symbols' + fi + aix_use_runtimelinking=no + + # Test if we are trying to use run time linking or normal + # AIX style linking. If -brtl is somewhere in LDFLAGS, we + # need to do runtime linking. + case $host_os in aix4.[23]|aix4.[23].*|aix[5-9]*) + for ld_flag in $LDFLAGS; do + if (test $ld_flag = "-brtl" || test $ld_flag = "-Wl,-brtl"); then + aix_use_runtimelinking=yes + break + fi + done + ;; + esac + + exp_sym_flag='-bexport' + no_entry_flag='-bnoentry' + fi + + # When large executables or shared objects are built, AIX ld can + # have problems creating the table of contents. If linking a library + # or program results in "error TOC overflow" add -mminimal-toc to + # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not + # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. + + archive_cmds_F77='' + hardcode_direct_F77=yes + hardcode_libdir_separator_F77=':' + link_all_deplibs_F77=yes + + if test "$GCC" = yes; then + case $host_os in aix4.[012]|aix4.[012].*) + # We only want to do this on AIX 4.2 and lower, the check + # below for broken collect2 doesn't work under 4.3+ + collect2name=`${CC} -print-prog-name=collect2` + if test -f "$collect2name" && \ + strings "$collect2name" | grep resolve_lib_name >/dev/null + then + # We have reworked collect2 + : + else + # We have old collect2 + hardcode_direct_F77=unsupported + # It fails to find uninstalled libraries when the uninstalled + # path is not listed in the libpath. Setting hardcode_minus_L + # to unsupported forces relinking + hardcode_minus_L_F77=yes + hardcode_libdir_flag_spec_F77='-L$libdir' + hardcode_libdir_separator_F77= + fi + ;; + esac + shared_flag='-shared' + if test "$aix_use_runtimelinking" = yes; then + shared_flag="$shared_flag "'${wl}-G' + fi + else + # not using gcc + if test "$host_cpu" = ia64; then + # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release + # chokes on -Wl,-G. The following line is correct: + shared_flag='-G' + else + if test "$aix_use_runtimelinking" = yes; then + shared_flag='${wl}-G' + else + shared_flag='${wl}-bM:SRE' + fi + fi + fi + + # It seems that -bexpall does not export symbols beginning with + # underscore (_), so it is better to generate a list of symbols to export. + always_export_symbols_F77=yes + if test "$aix_use_runtimelinking" = yes; then + # Warning - without using the other runtime loading flags (-brtl), + # -berok will link without error, but may produce a broken library. + allow_undefined_flag_F77='-berok' + # Determine the default libpath from the value encoded in an empty executable. + cat >conftest.$ac_ext <<_ACEOF + program main + + end +_ACEOF +rm -f conftest.$ac_objext conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_f77_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + $as_test_x conftest$ac_exeext + }; then + +lt_aix_libpath_sed=' + /Import File Strings/,/^$/ { + /^0/ { + s/^0 *\(.*\)$/\1/ + p + } + }' +aix_libpath=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` +# Check for a 64-bit object if we didn't find anything. +if test -z "$aix_libpath"; then + aix_libpath=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` +fi +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + +fi + +rm -rf conftest.dSYM +rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ + conftest$ac_exeext conftest.$ac_ext +if test -z "$aix_libpath"; then aix_libpath="/usr/lib:/lib"; fi + + hardcode_libdir_flag_spec_F77='${wl}-blibpath:$libdir:'"$aix_libpath" + archive_expsym_cmds_F77="\$CC"' -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags `if test "x${allow_undefined_flag}" != "x"; then echo "${wl}${allow_undefined_flag}"; else :; fi` '"\${wl}$exp_sym_flag:\$export_symbols $shared_flag" + else + if test "$host_cpu" = ia64; then + hardcode_libdir_flag_spec_F77='${wl}-R $libdir:/usr/lib:/lib' + allow_undefined_flag_F77="-z nodefs" + archive_expsym_cmds_F77="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags ${wl}${allow_undefined_flag} '"\${wl}$exp_sym_flag:\$export_symbols" + else + # Determine the default libpath from the value encoded in an empty executable. + cat >conftest.$ac_ext <<_ACEOF + program main + + end +_ACEOF +rm -f conftest.$ac_objext conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_f77_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + $as_test_x conftest$ac_exeext + }; then + +lt_aix_libpath_sed=' + /Import File Strings/,/^$/ { + /^0/ { + s/^0 *\(.*\)$/\1/ + p + } + }' +aix_libpath=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` +# Check for a 64-bit object if we didn't find anything. +if test -z "$aix_libpath"; then + aix_libpath=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` +fi +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + +fi + +rm -rf conftest.dSYM +rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ + conftest$ac_exeext conftest.$ac_ext +if test -z "$aix_libpath"; then aix_libpath="/usr/lib:/lib"; fi + + hardcode_libdir_flag_spec_F77='${wl}-blibpath:$libdir:'"$aix_libpath" + # Warning - without using the other run time loading flags, + # -berok will link without error, but may produce a broken library. + no_undefined_flag_F77=' ${wl}-bernotok' + allow_undefined_flag_F77=' ${wl}-berok' + # Exported symbols can be pulled into shared objects from archives + whole_archive_flag_spec_F77='$convenience' + archive_cmds_need_lc_F77=yes + # This is similar to how AIX traditionally builds its shared libraries. + archive_expsym_cmds_F77="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs ${wl}-bnoentry $compiler_flags ${wl}-bE:$export_symbols${allow_undefined_flag}~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$soname' + fi + fi + ;; + + amigaos*) + archive_cmds_F77='$rm $output_objdir/a2ixlibrary.data~$echo "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$echo "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$echo "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$echo "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + hardcode_libdir_flag_spec_F77='-L$libdir' + hardcode_minus_L_F77=yes + # see comment about different semantics on the GNU ld section + ld_shlibs_F77=no + ;; + + bsdi[45]*) + export_dynamic_flag_spec_F77=-rdynamic + ;; + + cygwin* | mingw* | pw32*) + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + hardcode_libdir_flag_spec_F77=' ' + allow_undefined_flag_F77=unsupported + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=".dll" + # FIXME: Setting linknames here is a bad hack. + archive_cmds_F77='$CC -o $lib $libobjs $compiler_flags `echo "$deplibs" | $SED -e '\''s/ -lc$//'\''` -link -dll~linknames=' + # The linker will automatically build a .lib file if we build a DLL. + old_archive_From_new_cmds_F77='true' + # FIXME: Should let the user specify the lib program. + old_archive_cmds_F77='lib -OUT:$oldlib$oldobjs$old_deplibs' + fix_srcfile_path_F77='`cygpath -w "$srcfile"`' + enable_shared_with_static_runtimes_F77=yes + ;; + + darwin* | rhapsody*) + allow_undefined_flag_F77="$_lt_dar_allow_undefined" + archive_cmds_need_lc_F77=no + hardcode_direct_F77=no + hardcode_automatic_F77=yes + hardcode_shlibpath_var_F77=unsupported + whole_archive_flag_spec_F77='' + link_all_deplibs_F77=yes + if test "$GCC" = yes ; then + output_verbose_link_cmd='echo' + archive_cmds_F77="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod${_lt_dsymutil}" + module_cmds_F77="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags${_lt_dsymutil}" + archive_expsym_cmds_F77="sed 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring ${_lt_dar_single_mod}${_lt_dar_export_syms}${_lt_dsymutil}" + module_expsym_cmds_F77="sed -e 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags${_lt_dar_export_syms}${_lt_dsymutil}" + else + case $cc_basename in + xlc*) + output_verbose_link_cmd='echo' + archive_cmds_F77='$CC -qmkshrobj $allow_undefined_flag -o $lib $libobjs $deplibs $compiler_flags ${wl}-install_name ${wl}`echo $rpath/$soname` $xlcverstring' + module_cmds_F77='$CC $allow_undefined_flag -o $lib -bundle $libobjs $deplibs$compiler_flags' + # Don't fix this by using the ld -exported_symbols_list flag, it doesn't exist in older darwin lds + archive_expsym_cmds_F77='sed -e "s,#.*,," -e "s,^[ ]*,," -e "s,^\(..*\),_&," < $export_symbols > $output_objdir/${libname}-symbols.expsym~$CC -qmkshrobj $allow_undefined_flag -o $lib $libobjs $deplibs $compiler_flags ${wl}-install_name ${wl}$rpath/$soname $xlcverstring~nmedit -s $output_objdir/${libname}-symbols.expsym ${lib}' + module_expsym_cmds_F77='sed -e "s,#.*,," -e "s,^[ ]*,," -e "s,^\(..*\),_&," < $export_symbols > $output_objdir/${libname}-symbols.expsym~$CC $allow_undefined_flag -o $lib -bundle $libobjs $deplibs$compiler_flags~nmedit -s $output_objdir/${libname}-symbols.expsym ${lib}' + ;; + *) + ld_shlibs_F77=no + ;; + esac + fi + ;; + + dgux*) + archive_cmds_F77='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_libdir_flag_spec_F77='-L$libdir' + hardcode_shlibpath_var_F77=no + ;; + + freebsd1*) + ld_shlibs_F77=no + ;; + + # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor + # support. Future versions do this automatically, but an explicit c++rt0.o + # does not break anything, and helps significantly (at the cost of a little + # extra space). + freebsd2.2*) + archive_cmds_F77='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags /usr/lib/c++rt0.o' + hardcode_libdir_flag_spec_F77='-R$libdir' + hardcode_direct_F77=yes + hardcode_shlibpath_var_F77=no + ;; + + # Unfortunately, older versions of FreeBSD 2 do not have this feature. + freebsd2*) + archive_cmds_F77='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' + hardcode_direct_F77=yes + hardcode_minus_L_F77=yes + hardcode_shlibpath_var_F77=no + ;; + + # FreeBSD 3 and greater uses gcc -shared to do shared libraries. + freebsd* | dragonfly*) + archive_cmds_F77='$CC -shared -o $lib $libobjs $deplibs $compiler_flags' + hardcode_libdir_flag_spec_F77='-R$libdir' + hardcode_direct_F77=yes + hardcode_shlibpath_var_F77=no + ;; + + hpux9*) + if test "$GCC" = yes; then + archive_cmds_F77='$rm $output_objdir/$soname~$CC -shared -fPIC ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $libobjs $deplibs $compiler_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib' + else + archive_cmds_F77='$rm $output_objdir/$soname~$LD -b +b $install_libdir -o $output_objdir/$soname $libobjs $deplibs $linker_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib' + fi + hardcode_libdir_flag_spec_F77='${wl}+b ${wl}$libdir' + hardcode_libdir_separator_F77=: + hardcode_direct_F77=yes + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + hardcode_minus_L_F77=yes + export_dynamic_flag_spec_F77='${wl}-E' + ;; + + hpux10*) + if test "$GCC" = yes -a "$with_gnu_ld" = no; then + archive_cmds_F77='$CC -shared -fPIC ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + else + archive_cmds_F77='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags' + fi + if test "$with_gnu_ld" = no; then + hardcode_libdir_flag_spec_F77='${wl}+b ${wl}$libdir' + hardcode_libdir_separator_F77=: + + hardcode_direct_F77=yes + export_dynamic_flag_spec_F77='${wl}-E' + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + hardcode_minus_L_F77=yes + fi + ;; + + hpux11*) + if test "$GCC" = yes -a "$with_gnu_ld" = no; then + case $host_cpu in + hppa*64*) + archive_cmds_F77='$CC -shared ${wl}+h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + ia64*) + archive_cmds_F77='$CC -shared ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + archive_cmds_F77='$CC -shared -fPIC ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + else + case $host_cpu in + hppa*64*) + archive_cmds_F77='$CC -b ${wl}+h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + ia64*) + archive_cmds_F77='$CC -b ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + archive_cmds_F77='$CC -b ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + fi + if test "$with_gnu_ld" = no; then + hardcode_libdir_flag_spec_F77='${wl}+b ${wl}$libdir' + hardcode_libdir_separator_F77=: + + case $host_cpu in + hppa*64*|ia64*) + hardcode_libdir_flag_spec_ld_F77='+b $libdir' + hardcode_direct_F77=no + hardcode_shlibpath_var_F77=no + ;; + *) + hardcode_direct_F77=yes + export_dynamic_flag_spec_F77='${wl}-E' + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + hardcode_minus_L_F77=yes + ;; + esac + fi + ;; + + irix5* | irix6* | nonstopux*) + if test "$GCC" = yes; then + archive_cmds_F77='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + else + archive_cmds_F77='$LD -shared $libobjs $deplibs $linker_flags -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib' + hardcode_libdir_flag_spec_ld_F77='-rpath $libdir' + fi + hardcode_libdir_flag_spec_F77='${wl}-rpath ${wl}$libdir' + hardcode_libdir_separator_F77=: + link_all_deplibs_F77=yes + ;; + + netbsd*) + if echo __ELF__ | $CC -E - | grep __ELF__ >/dev/null; then + archive_cmds_F77='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' # a.out + else + archive_cmds_F77='$LD -shared -o $lib $libobjs $deplibs $linker_flags' # ELF + fi + hardcode_libdir_flag_spec_F77='-R$libdir' + hardcode_direct_F77=yes + hardcode_shlibpath_var_F77=no + ;; + + newsos6) + archive_cmds_F77='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_direct_F77=yes + hardcode_libdir_flag_spec_F77='${wl}-rpath ${wl}$libdir' + hardcode_libdir_separator_F77=: + hardcode_shlibpath_var_F77=no + ;; + + openbsd*) + if test -f /usr/libexec/ld.so; then + hardcode_direct_F77=yes + hardcode_shlibpath_var_F77=no + if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then + archive_cmds_F77='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds_F77='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags ${wl}-retain-symbols-file,$export_symbols' + hardcode_libdir_flag_spec_F77='${wl}-rpath,$libdir' + export_dynamic_flag_spec_F77='${wl}-E' + else + case $host_os in + openbsd[01].* | openbsd2.[0-7] | openbsd2.[0-7].*) + archive_cmds_F77='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' + hardcode_libdir_flag_spec_F77='-R$libdir' + ;; + *) + archive_cmds_F77='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + hardcode_libdir_flag_spec_F77='${wl}-rpath,$libdir' + ;; + esac + fi + else + ld_shlibs_F77=no + fi + ;; + + os2*) + hardcode_libdir_flag_spec_F77='-L$libdir' + hardcode_minus_L_F77=yes + allow_undefined_flag_F77=unsupported + archive_cmds_F77='$echo "LIBRARY $libname INITINSTANCE" > $output_objdir/$libname.def~$echo "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~$echo DATA >> $output_objdir/$libname.def~$echo " SINGLE NONSHARED" >> $output_objdir/$libname.def~$echo EXPORTS >> $output_objdir/$libname.def~emxexp $libobjs >> $output_objdir/$libname.def~$CC -Zdll -Zcrtdll -o $lib $libobjs $deplibs $compiler_flags $output_objdir/$libname.def' + old_archive_From_new_cmds_F77='emximp -o $output_objdir/$libname.a $output_objdir/$libname.def' + ;; + + osf3*) + if test "$GCC" = yes; then + allow_undefined_flag_F77=' ${wl}-expect_unresolved ${wl}\*' + archive_cmds_F77='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + else + allow_undefined_flag_F77=' -expect_unresolved \*' + archive_cmds_F77='$LD -shared${allow_undefined_flag} $libobjs $deplibs $linker_flags -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib' + fi + hardcode_libdir_flag_spec_F77='${wl}-rpath ${wl}$libdir' + hardcode_libdir_separator_F77=: + ;; + + osf4* | osf5*) # as osf3* with the addition of -msym flag + if test "$GCC" = yes; then + allow_undefined_flag_F77=' ${wl}-expect_unresolved ${wl}\*' + archive_cmds_F77='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags ${wl}-msym ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + hardcode_libdir_flag_spec_F77='${wl}-rpath ${wl}$libdir' + else + allow_undefined_flag_F77=' -expect_unresolved \*' + archive_cmds_F77='$LD -shared${allow_undefined_flag} $libobjs $deplibs $linker_flags -msym -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib' + archive_expsym_cmds_F77='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done; echo "-hidden">> $lib.exp~ + $LD -shared${allow_undefined_flag} -input $lib.exp $linker_flags $libobjs $deplibs -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib~$rm $lib.exp' + + # Both c and cxx compiler support -rpath directly + hardcode_libdir_flag_spec_F77='-rpath $libdir' + fi + hardcode_libdir_separator_F77=: + ;; + + solaris*) + no_undefined_flag_F77=' -z text' + if test "$GCC" = yes; then + wlarc='${wl}' + archive_cmds_F77='$CC -shared ${wl}-h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds_F77='$echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~$echo "local: *; };" >> $lib.exp~ + $CC -shared ${wl}-M ${wl}$lib.exp ${wl}-h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags~$rm $lib.exp' + else + wlarc='' + archive_cmds_F77='$LD -G${allow_undefined_flag} -h $soname -o $lib $libobjs $deplibs $linker_flags' + archive_expsym_cmds_F77='$echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~$echo "local: *; };" >> $lib.exp~ + $LD -G${allow_undefined_flag} -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$rm $lib.exp' + fi + hardcode_libdir_flag_spec_F77='-R$libdir' + hardcode_shlibpath_var_F77=no + case $host_os in + solaris2.[0-5] | solaris2.[0-5].*) ;; + *) + # The compiler driver will combine and reorder linker options, + # but understands `-z linker_flag'. GCC discards it without `$wl', + # but is careful enough not to reorder. + # Supported since Solaris 2.6 (maybe 2.5.1?) + if test "$GCC" = yes; then + whole_archive_flag_spec_F77='${wl}-z ${wl}allextract$convenience ${wl}-z ${wl}defaultextract' + else + whole_archive_flag_spec_F77='-z allextract$convenience -z defaultextract' + fi + ;; + esac + link_all_deplibs_F77=yes + ;; + + sunos4*) + if test "x$host_vendor" = xsequent; then + # Use $CC to link under sequent, because it throws in some extra .o + # files that make .init and .fini sections work. + archive_cmds_F77='$CC -G ${wl}-h $soname -o $lib $libobjs $deplibs $compiler_flags' + else + archive_cmds_F77='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linker_flags' + fi + hardcode_libdir_flag_spec_F77='-L$libdir' + hardcode_direct_F77=yes + hardcode_minus_L_F77=yes + hardcode_shlibpath_var_F77=no + ;; + + sysv4) + case $host_vendor in + sni) + archive_cmds_F77='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_direct_F77=yes # is this really true??? + ;; + siemens) + ## LD is ld it makes a PLAMLIB + ## CC just makes a GrossModule. + archive_cmds_F77='$LD -G -o $lib $libobjs $deplibs $linker_flags' + reload_cmds_F77='$CC -r -o $output$reload_objs' + hardcode_direct_F77=no + ;; + motorola) + archive_cmds_F77='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_direct_F77=no #Motorola manual says yes, but my tests say they lie + ;; + esac + runpath_var='LD_RUN_PATH' + hardcode_shlibpath_var_F77=no + ;; + + sysv4.3*) + archive_cmds_F77='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_shlibpath_var_F77=no + export_dynamic_flag_spec_F77='-Bexport' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + archive_cmds_F77='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_shlibpath_var_F77=no + runpath_var=LD_RUN_PATH + hardcode_runpath_var=yes + ld_shlibs_F77=yes + fi + ;; + + sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[01].[10]* | unixware7* | sco3.2v5.0.[024]*) + no_undefined_flag_F77='${wl}-z,text' + archive_cmds_need_lc_F77=no + hardcode_shlibpath_var_F77=no + runpath_var='LD_RUN_PATH' + + if test "$GCC" = yes; then + archive_cmds_F77='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds_F77='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + else + archive_cmds_F77='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds_F77='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + fi + ;; + + sysv5* | sco3.2v5* | sco5v6*) + # Note: We can NOT use -z defs as we might desire, because we do not + # link with -lc, and that would cause any symbols used from libc to + # always be unresolved, which means just about no library would + # ever link correctly. If we're not using GNU ld we use -z text + # though, which does catch some bad symbols but isn't as heavy-handed + # as -z defs. + no_undefined_flag_F77='${wl}-z,text' + allow_undefined_flag_F77='${wl}-z,nodefs' + archive_cmds_need_lc_F77=no + hardcode_shlibpath_var_F77=no + hardcode_libdir_flag_spec_F77='`test -z "$SCOABSPATH" && echo ${wl}-R,$libdir`' + hardcode_libdir_separator_F77=':' + link_all_deplibs_F77=yes + export_dynamic_flag_spec_F77='${wl}-Bexport' + runpath_var='LD_RUN_PATH' + + if test "$GCC" = yes; then + archive_cmds_F77='$CC -shared ${wl}-h,\${SCOABSPATH:+${install_libdir}/}$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds_F77='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,\${SCOABSPATH:+${install_libdir}/}$soname -o $lib $libobjs $deplibs $compiler_flags' + else + archive_cmds_F77='$CC -G ${wl}-h,\${SCOABSPATH:+${install_libdir}/}$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds_F77='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,\${SCOABSPATH:+${install_libdir}/}$soname -o $lib $libobjs $deplibs $compiler_flags' + fi + ;; + + uts4*) + archive_cmds_F77='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_libdir_flag_spec_F77='-L$libdir' + hardcode_shlibpath_var_F77=no + ;; + + *) + ld_shlibs_F77=no + ;; + esac + fi + +{ $as_echo "$as_me:$LINENO: result: $ld_shlibs_F77" >&5 +$as_echo "$ld_shlibs_F77" >&6; } +test "$ld_shlibs_F77" = no && can_build_shared=no + +# +# Do we need to explicitly link libc? +# +case "x$archive_cmds_need_lc_F77" in +x|xyes) + # Assume -lc should be added + archive_cmds_need_lc_F77=yes + + if test "$enable_shared" = yes && test "$GCC" = yes; then + case $archive_cmds_F77 in + *'~'*) + # FIXME: we may have to deal with multi-command sequences. + ;; + '$CC '*) + # Test whether the compiler implicitly links with -lc since on some + # systems, -lgcc has to come before -lc. If gcc already passes -lc + # to ld, don't add -lc before -lgcc. + { $as_echo "$as_me:$LINENO: checking whether -lc should be explicitly linked in" >&5 +$as_echo_n "checking whether -lc should be explicitly linked in... " >&6; } + $rm conftest* + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } 2>conftest.err; then + soname=conftest + lib=conftest + libobjs=conftest.$ac_objext + deplibs= + wl=$lt_prog_compiler_wl_F77 + pic_flag=$lt_prog_compiler_pic_F77 + compiler_flags=-v + linker_flags=-v + verstring= + output_objdir=. + libname=conftest + lt_save_allow_undefined_flag=$allow_undefined_flag_F77 + allow_undefined_flag_F77= + if { (eval echo "$as_me:$LINENO: \"$archive_cmds_F77 2\>\&1 \| grep \" -lc \" \>/dev/null 2\>\&1\"") >&5 + (eval $archive_cmds_F77 2\>\&1 \| grep \" -lc \" \>/dev/null 2\>\&1) 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } + then + archive_cmds_need_lc_F77=no + else + archive_cmds_need_lc_F77=yes + fi + allow_undefined_flag_F77=$lt_save_allow_undefined_flag + else + cat conftest.err 1>&5 + fi + $rm conftest* + { $as_echo "$as_me:$LINENO: result: $archive_cmds_need_lc_F77" >&5 +$as_echo "$archive_cmds_need_lc_F77" >&6; } + ;; + esac + fi + ;; +esac + +{ $as_echo "$as_me:$LINENO: checking dynamic linker characteristics" >&5 +$as_echo_n "checking dynamic linker characteristics... " >&6; } +library_names_spec= +libname_spec='lib$name' +soname_spec= +shrext_cmds=".so" +postinstall_cmds= +postuninstall_cmds= +finish_cmds= +finish_eval= +shlibpath_var= +shlibpath_overrides_runpath=unknown +version_type=none +dynamic_linker="$host_os ld.so" +sys_lib_dlsearch_path_spec="/lib /usr/lib" + +need_lib_prefix=unknown +hardcode_into_libs=no + +# when you set need_version to no, make sure it does not cause -set_version +# flags to be left without arguments +need_version=unknown + +case $host_os in +aix3*) + version_type=linux + library_names_spec='${libname}${release}${shared_ext}$versuffix $libname.a' + shlibpath_var=LIBPATH + + # AIX 3 has no versioning support, so we append a major version to the name. + soname_spec='${libname}${release}${shared_ext}$major' + ;; + +aix[4-9]*) + version_type=linux + need_lib_prefix=no + need_version=no + hardcode_into_libs=yes + if test "$host_cpu" = ia64; then + # AIX 5 supports IA64 + library_names_spec='${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext}$versuffix $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + else + # With GCC up to 2.95.x, collect2 would create an import file + # for dependence libraries. The import file would start with + # the line `#! .'. This would cause the generated library to + # depend on `.', always an invalid library. This was fixed in + # development snapshots of GCC prior to 3.0. + case $host_os in + aix4 | aix4.[01] | aix4.[01].*) + if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)' + echo ' yes ' + echo '#endif'; } | ${CC} -E - | grep yes > /dev/null; then + : + else + can_build_shared=no + fi + ;; + esac + # AIX (on Power*) has no versioning support, so currently we can not hardcode correct + # soname into executable. Probably we can add versioning support to + # collect2, so additional links can be useful in future. + if test "$aix_use_runtimelinking" = yes; then + # If using run time linking (on AIX 4.2 or later) use lib.so + # instead of lib.a to let people know that these are not + # typical AIX shared libraries. + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + else + # We preserve .a as extension for shared libraries through AIX4.2 + # and later when we are not doing run time linking. + library_names_spec='${libname}${release}.a $libname.a' + soname_spec='${libname}${release}${shared_ext}$major' + fi + shlibpath_var=LIBPATH + fi + ;; + +amigaos*) + library_names_spec='$libname.ixlibrary $libname.a' + # Create ${libname}_ixlibrary.a entries in /sys/libs. + finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`$echo "X$lib" | $Xsed -e '\''s%^.*/\([^/]*\)\.ixlibrary$%\1%'\''`; test $rm /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done' + ;; + +beos*) + library_names_spec='${libname}${shared_ext}' + dynamic_linker="$host_os ld.so" + shlibpath_var=LIBRARY_PATH + ;; + +bsdi[45]*) + version_type=linux + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib" + sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib" + # the default ld.so.conf also contains /usr/contrib/lib and + # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow + # libtool to hard-code these into programs + ;; + +cygwin* | mingw* | pw32*) + version_type=windows + shrext_cmds=".dll" + need_version=no + need_lib_prefix=no + + case $GCC,$host_os in + yes,cygwin* | yes,mingw* | yes,pw32*) + library_names_spec='$libname.dll.a' + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \${file}`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\${base_file}'\''i;echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname~ + chmod a+x \$dldir/$dlname' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $rm \$dlpath' + shlibpath_overrides_runpath=yes + + case $host_os in + cygwin*) + # Cygwin DLLs use 'cyg' prefix rather than 'lib' + soname_spec='`echo ${libname} | sed -e 's/^lib/cyg/'``echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}' + sys_lib_search_path_spec="/usr/lib /lib/w32api /lib /usr/local/lib" + ;; + mingw*) + # MinGW DLLs use traditional 'lib' prefix + soname_spec='${libname}`echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}' + sys_lib_search_path_spec=`$CC -print-search-dirs | grep "^libraries:" | $SED -e "s/^libraries://" -e "s,=/,/,g"` + if echo "$sys_lib_search_path_spec" | grep ';[c-zC-Z]:/' >/dev/null; then + # It is most probably a Windows format PATH printed by + # mingw gcc, but we are running on Cygwin. Gcc prints its search + # path with ; separators, and with drive letters. We can handle the + # drive letters (cygwin fileutils understands them), so leave them, + # especially as we might pass files found there to a mingw objdump, + # which wouldn't understand a cygwinified path. Ahh. + sys_lib_search_path_spec=`echo "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'` + else + sys_lib_search_path_spec=`echo "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + fi + ;; + pw32*) + # pw32 DLLs use 'pw' prefix rather than 'lib' + library_names_spec='`echo ${libname} | sed -e 's/^lib/pw/'``echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}' + ;; + esac + ;; + + *) + library_names_spec='${libname}`echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext} $libname.lib' + ;; + esac + dynamic_linker='Win32 ld.exe' + # FIXME: first we should search . and the directory the executable is in + shlibpath_var=PATH + ;; + +darwin* | rhapsody*) + dynamic_linker="$host_os dyld" + version_type=darwin + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${versuffix}$shared_ext ${libname}${release}${major}$shared_ext ${libname}$shared_ext' + soname_spec='${libname}${release}${major}$shared_ext' + shlibpath_overrides_runpath=yes + shlibpath_var=DYLD_LIBRARY_PATH + shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`' + + sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib' + ;; + +dgux*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname$shared_ext' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +freebsd1*) + dynamic_linker=no + ;; + +freebsd* | dragonfly*) + # DragonFly does not have aout. When/if they implement a new + # versioning mechanism, adjust this. + if test -x /usr/bin/objformat; then + objformat=`/usr/bin/objformat` + else + case $host_os in + freebsd[123]*) objformat=aout ;; + *) objformat=elf ;; + esac + fi + version_type=freebsd-$objformat + case $version_type in + freebsd-elf*) + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext} $libname${shared_ext}' + need_version=no + need_lib_prefix=no + ;; + freebsd-*) + library_names_spec='${libname}${release}${shared_ext}$versuffix $libname${shared_ext}$versuffix' + need_version=yes + ;; + esac + shlibpath_var=LD_LIBRARY_PATH + case $host_os in + freebsd2*) + shlibpath_overrides_runpath=yes + ;; + freebsd3.[01]* | freebsdelf3.[01]*) + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + freebsd3.[2-9]* | freebsdelf3.[2-9]* | \ + freebsd4.[0-5] | freebsdelf4.[0-5] | freebsd4.1.1 | freebsdelf4.1.1) + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + *) # from 4.6 on, and DragonFly + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + esac + ;; + +gnu*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}${major} ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + hardcode_into_libs=yes + ;; + +hpux9* | hpux10* | hpux11*) + # Give a soname corresponding to the major version so that dld.sl refuses to + # link against other versions. + version_type=sunos + need_lib_prefix=no + need_version=no + case $host_cpu in + ia64*) + shrext_cmds='.so' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.so" + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + if test "X$HPUX_IA64_MODE" = X32; then + sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib" + else + sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64" + fi + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + hppa*64*) + shrext_cmds='.sl' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.sl" + shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + *) + shrext_cmds='.sl' + dynamic_linker="$host_os dld.sl" + shlibpath_var=SHLIB_PATH + shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + ;; + esac + # HP-UX runs *really* slowly unless shared libraries are mode 555. + postinstall_cmds='chmod 555 $lib' + ;; + +interix[3-9]*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +irix5* | irix6* | nonstopux*) + case $host_os in + nonstopux*) version_type=nonstopux ;; + *) + if test "$lt_cv_prog_gnu_ld" = yes; then + version_type=linux + else + version_type=irix + fi ;; + esac + need_lib_prefix=no + need_version=no + soname_spec='${libname}${release}${shared_ext}$major' + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext} $libname${shared_ext}' + case $host_os in + irix5* | nonstopux*) + libsuff= shlibsuff= + ;; + *) + case $LD in # libtool.m4 will add one of these switches to LD + *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") + libsuff= shlibsuff= libmagic=32-bit;; + *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") + libsuff=32 shlibsuff=N32 libmagic=N32;; + *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") + libsuff=64 shlibsuff=64 libmagic=64-bit;; + *) libsuff= shlibsuff= libmagic=never-match;; + esac + ;; + esac + shlibpath_var=LD_LIBRARY${shlibsuff}_PATH + shlibpath_overrides_runpath=no + sys_lib_search_path_spec="/usr/lib${libsuff} /lib${libsuff} /usr/local/lib${libsuff}" + sys_lib_dlsearch_path_spec="/usr/lib${libsuff} /lib${libsuff}" + hardcode_into_libs=yes + ;; + +# No shared lib support for Linux oldld, aout, or coff. +linux*oldld* | linux*aout* | linux*coff*) + dynamic_linker=no + ;; + +# This must be Linux ELF. +linux* | k*bsd*-gnu) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + # This implies no fast_install, which is unacceptable. + # Some rework will be needed to allow for fast_install + # before this can be enabled. + hardcode_into_libs=yes + + # Append ld.so.conf contents to the search path + if test -f /etc/ld.so.conf; then + lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \$2)); skip = 1; } { if (!skip) print \$0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[ ]*hwcap[ ]/d;s/[:, ]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;/^$/d' | tr '\n' ' '` + sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra" + fi + + # We used to test for /lib/ld.so.1 and disable shared libraries on + # powerpc, because MkLinux only supported shared libraries with the + # GNU dynamic linker. Since this was broken with cross compilers, + # most powerpc-linux boxes support dynamic linking these days and + # people can always --disable-shared, the test was removed, and we + # assume the GNU/Linux dynamic linker is in use. + dynamic_linker='GNU/Linux ld.so' + ;; + +netbsd*) + version_type=sunos + need_lib_prefix=no + need_version=no + if echo __ELF__ | $CC -E - | grep __ELF__ >/dev/null; then + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + dynamic_linker='NetBSD (a.out) ld.so' + else + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + dynamic_linker='NetBSD ld.elf_so' + fi + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + +newsos6) + version_type=linux + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +nto-qnx*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +openbsd*) + version_type=sunos + sys_lib_dlsearch_path_spec="/usr/lib" + need_lib_prefix=no + # Some older versions of OpenBSD (3.3 at least) *do* need versioned libs. + case $host_os in + openbsd3.3 | openbsd3.3.*) need_version=yes ;; + *) need_version=no ;; + esac + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + shlibpath_var=LD_LIBRARY_PATH + if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then + case $host_os in + openbsd2.[89] | openbsd2.[89].*) + shlibpath_overrides_runpath=no + ;; + *) + shlibpath_overrides_runpath=yes + ;; + esac + else + shlibpath_overrides_runpath=yes + fi + ;; + +os2*) + libname_spec='$name' + shrext_cmds=".dll" + need_lib_prefix=no + library_names_spec='$libname${shared_ext} $libname.a' + dynamic_linker='OS/2 ld.exe' + shlibpath_var=LIBPATH + ;; + +osf3* | osf4* | osf5*) + version_type=osf + need_lib_prefix=no + need_version=no + soname_spec='${libname}${release}${shared_ext}$major' + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib" + sys_lib_dlsearch_path_spec="$sys_lib_search_path_spec" + ;; + +rdos*) + dynamic_linker=no + ;; + +solaris*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + # ldd complains unless libraries are executable + postinstall_cmds='chmod +x $lib' + ;; + +sunos4*) + version_type=sunos + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix' + finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + if test "$with_gnu_ld" = yes; then + need_lib_prefix=no + fi + need_version=yes + ;; + +sysv4 | sysv4.3*) + version_type=linux + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + case $host_vendor in + sni) + shlibpath_overrides_runpath=no + need_lib_prefix=no + export_dynamic_flag_spec='${wl}-Blargedynsym' + runpath_var=LD_RUN_PATH + ;; + siemens) + need_lib_prefix=no + ;; + motorola) + need_lib_prefix=no + need_version=no + shlibpath_overrides_runpath=no + sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib' + ;; + esac + ;; + +sysv4*MP*) + if test -d /usr/nec ;then + version_type=linux + library_names_spec='$libname${shared_ext}.$versuffix $libname${shared_ext}.$major $libname${shared_ext}' + soname_spec='$libname${shared_ext}.$major' + shlibpath_var=LD_LIBRARY_PATH + fi + ;; + +sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + version_type=freebsd-elf + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext} $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + hardcode_into_libs=yes + if test "$with_gnu_ld" = yes; then + sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib' + shlibpath_overrides_runpath=no + else + sys_lib_search_path_spec='/usr/ccs/lib /usr/lib' + shlibpath_overrides_runpath=yes + case $host_os in + sco3.2v5*) + sys_lib_search_path_spec="$sys_lib_search_path_spec /lib" + ;; + esac + fi + sys_lib_dlsearch_path_spec='/usr/lib' + ;; + +uts4*) + version_type=linux + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +*) + dynamic_linker=no + ;; +esac +{ $as_echo "$as_me:$LINENO: result: $dynamic_linker" >&5 +$as_echo "$dynamic_linker" >&6; } +test "$dynamic_linker" = no && can_build_shared=no + +if test "${lt_cv_sys_lib_search_path_spec+set}" = set; then + $as_echo_n "(cached) " >&6 +else + lt_cv_sys_lib_search_path_spec="$sys_lib_search_path_spec" +fi + +sys_lib_search_path_spec="$lt_cv_sys_lib_search_path_spec" +if test "${lt_cv_sys_lib_dlsearch_path_spec+set}" = set; then + $as_echo_n "(cached) " >&6 +else + lt_cv_sys_lib_dlsearch_path_spec="$sys_lib_dlsearch_path_spec" +fi + +sys_lib_dlsearch_path_spec="$lt_cv_sys_lib_dlsearch_path_spec" + +variables_saved_for_relink="PATH $shlibpath_var $runpath_var" +if test "$GCC" = yes; then + variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH" +fi + +{ $as_echo "$as_me:$LINENO: checking how to hardcode library paths into programs" >&5 +$as_echo_n "checking how to hardcode library paths into programs... " >&6; } +hardcode_action_F77= +if test -n "$hardcode_libdir_flag_spec_F77" || \ + test -n "$runpath_var_F77" || \ + test "X$hardcode_automatic_F77" = "Xyes" ; then + + # We can hardcode non-existant directories. + if test "$hardcode_direct_F77" != no && + # If the only mechanism to avoid hardcoding is shlibpath_var, we + # have to relink, otherwise we might link with an installed library + # when we should be linking with a yet-to-be-installed one + ## test "$_LT_AC_TAGVAR(hardcode_shlibpath_var, F77)" != no && + test "$hardcode_minus_L_F77" != no; then + # Linking always hardcodes the temporary library directory. + hardcode_action_F77=relink + else + # We can link without hardcoding, and we can hardcode nonexisting dirs. + hardcode_action_F77=immediate + fi +else + # We cannot hardcode anything, or else we can only hardcode existing + # directories. + hardcode_action_F77=unsupported +fi +{ $as_echo "$as_me:$LINENO: result: $hardcode_action_F77" >&5 +$as_echo "$hardcode_action_F77" >&6; } + +if test "$hardcode_action_F77" = relink; then + # Fast installation is not supported + enable_fast_install=no +elif test "$shlibpath_overrides_runpath" = yes || + test "$enable_shared" = no; then + # Fast installation is not necessary + enable_fast_install=needless +fi + + +# The else clause should only fire when bootstrapping the +# libtool distribution, otherwise you forgot to ship ltmain.sh +# with your package, and you will get complaints that there are +# no rules to generate ltmain.sh. +if test -f "$ltmain"; then + # See if we are running on zsh, and set the options which allow our commands through + # without removal of \ escapes. + if test -n "${ZSH_VERSION+set}" ; then + setopt NO_GLOB_SUBST + fi + # Now quote all the things that may contain metacharacters while being + # careful not to overquote the AC_SUBSTed values. We take copies of the + # variables and quote the copies for generation of the libtool script. + for var in echo old_CC old_CFLAGS AR AR_FLAGS EGREP RANLIB LN_S LTCC LTCFLAGS NM \ + SED SHELL STRIP \ + libname_spec library_names_spec soname_spec extract_expsyms_cmds \ + old_striplib striplib file_magic_cmd finish_cmds finish_eval \ + deplibs_check_method reload_flag reload_cmds need_locks \ + lt_cv_sys_global_symbol_pipe lt_cv_sys_global_symbol_to_cdecl \ + lt_cv_sys_global_symbol_to_c_name_address \ + sys_lib_search_path_spec sys_lib_dlsearch_path_spec \ + old_postinstall_cmds old_postuninstall_cmds \ + compiler_F77 \ + CC_F77 \ + LD_F77 \ + lt_prog_compiler_wl_F77 \ + lt_prog_compiler_pic_F77 \ + lt_prog_compiler_static_F77 \ + lt_prog_compiler_no_builtin_flag_F77 \ + export_dynamic_flag_spec_F77 \ + thread_safe_flag_spec_F77 \ + whole_archive_flag_spec_F77 \ + enable_shared_with_static_runtimes_F77 \ + old_archive_cmds_F77 \ + old_archive_from_new_cmds_F77 \ + predep_objects_F77 \ + postdep_objects_F77 \ + predeps_F77 \ + postdeps_F77 \ + compiler_lib_search_path_F77 \ + compiler_lib_search_dirs_F77 \ + archive_cmds_F77 \ + archive_expsym_cmds_F77 \ + postinstall_cmds_F77 \ + postuninstall_cmds_F77 \ + old_archive_from_expsyms_cmds_F77 \ + allow_undefined_flag_F77 \ + no_undefined_flag_F77 \ + export_symbols_cmds_F77 \ + hardcode_libdir_flag_spec_F77 \ + hardcode_libdir_flag_spec_ld_F77 \ + hardcode_libdir_separator_F77 \ + hardcode_automatic_F77 \ + module_cmds_F77 \ + module_expsym_cmds_F77 \ + lt_cv_prog_compiler_c_o_F77 \ + fix_srcfile_path_F77 \ + exclude_expsyms_F77 \ + include_expsyms_F77; do + + case $var in + old_archive_cmds_F77 | \ + old_archive_from_new_cmds_F77 | \ + archive_cmds_F77 | \ + archive_expsym_cmds_F77 | \ + module_cmds_F77 | \ + module_expsym_cmds_F77 | \ + old_archive_from_expsyms_cmds_F77 | \ + export_symbols_cmds_F77 | \ + extract_expsyms_cmds | reload_cmds | finish_cmds | \ + postinstall_cmds | postuninstall_cmds | \ + old_postinstall_cmds | old_postuninstall_cmds | \ + sys_lib_search_path_spec | sys_lib_dlsearch_path_spec) + # Double-quote double-evaled strings. + eval "lt_$var=\\\"\`\$echo \"X\$$var\" | \$Xsed -e \"\$double_quote_subst\" -e \"\$sed_quote_subst\" -e \"\$delay_variable_subst\"\`\\\"" + ;; + *) + eval "lt_$var=\\\"\`\$echo \"X\$$var\" | \$Xsed -e \"\$sed_quote_subst\"\`\\\"" + ;; + esac + done + + case $lt_echo in + *'\$0 --fallback-echo"') + lt_echo=`$echo "X$lt_echo" | $Xsed -e 's/\\\\\\\$0 --fallback-echo"$/$0 --fallback-echo"/'` + ;; + esac + +cfgfile="$ofile" + + cat <<__EOF__ >> "$cfgfile" +# ### BEGIN LIBTOOL TAG CONFIG: $tagname + +# Libtool was configured on host `(hostname || uname -n) 2>/dev/null | sed 1q`: + +# Shell to use when invoking shell scripts. +SHELL=$lt_SHELL + +# Whether or not to build shared libraries. +build_libtool_libs=$enable_shared + +# Whether or not to build static libraries. +build_old_libs=$enable_static + +# Whether or not to add -lc for building shared libraries. +build_libtool_need_lc=$archive_cmds_need_lc_F77 + +# Whether or not to disallow shared libs when runtime libs are static +allow_libtool_libs_with_static_runtimes=$enable_shared_with_static_runtimes_F77 + +# Whether or not to optimize for fast installation. +fast_install=$enable_fast_install + +# The host system. +host_alias=$host_alias +host=$host +host_os=$host_os + +# The build system. +build_alias=$build_alias +build=$build +build_os=$build_os + +# An echo program that does not interpret backslashes. +echo=$lt_echo + +# The archiver. +AR=$lt_AR +AR_FLAGS=$lt_AR_FLAGS + +# A C compiler. +LTCC=$lt_LTCC + +# LTCC compiler flags. +LTCFLAGS=$lt_LTCFLAGS + +# A language-specific compiler. +CC=$lt_compiler_F77 + +# Is the compiler the GNU C compiler? +with_gcc=$GCC_F77 + +# An ERE matcher. +EGREP=$lt_EGREP + +# The linker used to build libraries. +LD=$lt_LD_F77 + +# Whether we need hard or soft links. +LN_S=$lt_LN_S + +# A BSD-compatible nm program. +NM=$lt_NM + +# A symbol stripping program +STRIP=$lt_STRIP + +# Used to examine libraries when file_magic_cmd begins "file" +MAGIC_CMD=$MAGIC_CMD + +# Used on cygwin: DLL creation program. +DLLTOOL="$DLLTOOL" + +# Used on cygwin: object dumper. +OBJDUMP="$OBJDUMP" + +# Used on cygwin: assembler. +AS="$AS" + +# The name of the directory that contains temporary libtool files. +objdir=$objdir + +# How to create reloadable object files. +reload_flag=$lt_reload_flag +reload_cmds=$lt_reload_cmds + +# How to pass a linker flag through the compiler. +wl=$lt_lt_prog_compiler_wl_F77 + +# Object file suffix (normally "o"). +objext="$ac_objext" + +# Old archive suffix (normally "a"). +libext="$libext" + +# Shared library suffix (normally ".so"). +shrext_cmds='$shrext_cmds' + +# Executable file suffix (normally ""). +exeext="$exeext" + +# Additional compiler flags for building library objects. +pic_flag=$lt_lt_prog_compiler_pic_F77 +pic_mode=$pic_mode + +# What is the maximum length of a command? +max_cmd_len=$lt_cv_sys_max_cmd_len + +# Does compiler simultaneously support -c and -o options? +compiler_c_o=$lt_lt_cv_prog_compiler_c_o_F77 + +# Must we lock files when doing compilation? +need_locks=$lt_need_locks + +# Do we need the lib prefix for modules? +need_lib_prefix=$need_lib_prefix + +# Do we need a version for libraries? +need_version=$need_version + +# Whether dlopen is supported. +dlopen_support=$enable_dlopen + +# Whether dlopen of programs is supported. +dlopen_self=$enable_dlopen_self + +# Whether dlopen of statically linked programs is supported. +dlopen_self_static=$enable_dlopen_self_static + +# Compiler flag to prevent dynamic linking. +link_static_flag=$lt_lt_prog_compiler_static_F77 + +# Compiler flag to turn off builtin functions. +no_builtin_flag=$lt_lt_prog_compiler_no_builtin_flag_F77 + +# Compiler flag to allow reflexive dlopens. +export_dynamic_flag_spec=$lt_export_dynamic_flag_spec_F77 + +# Compiler flag to generate shared objects directly from archives. +whole_archive_flag_spec=$lt_whole_archive_flag_spec_F77 + +# Compiler flag to generate thread-safe objects. +thread_safe_flag_spec=$lt_thread_safe_flag_spec_F77 + +# Library versioning type. +version_type=$version_type + +# Format of library name prefix. +libname_spec=$lt_libname_spec + +# List of archive names. First name is the real one, the rest are links. +# The last name is the one that the linker finds with -lNAME. +library_names_spec=$lt_library_names_spec + +# The coded name of the library, if different from the real name. +soname_spec=$lt_soname_spec + +# Commands used to build and install an old-style archive. +RANLIB=$lt_RANLIB +old_archive_cmds=$lt_old_archive_cmds_F77 +old_postinstall_cmds=$lt_old_postinstall_cmds +old_postuninstall_cmds=$lt_old_postuninstall_cmds + +# Create an old-style archive from a shared archive. +old_archive_from_new_cmds=$lt_old_archive_from_new_cmds_F77 + +# Create a temporary old-style archive to link instead of a shared archive. +old_archive_from_expsyms_cmds=$lt_old_archive_from_expsyms_cmds_F77 + +# Commands used to build and install a shared archive. +archive_cmds=$lt_archive_cmds_F77 +archive_expsym_cmds=$lt_archive_expsym_cmds_F77 +postinstall_cmds=$lt_postinstall_cmds +postuninstall_cmds=$lt_postuninstall_cmds + +# Commands used to build a loadable module (assumed same as above if empty) +module_cmds=$lt_module_cmds_F77 +module_expsym_cmds=$lt_module_expsym_cmds_F77 + +# Commands to strip libraries. +old_striplib=$lt_old_striplib +striplib=$lt_striplib + +# Dependencies to place before the objects being linked to create a +# shared library. +predep_objects=$lt_predep_objects_F77 + +# Dependencies to place after the objects being linked to create a +# shared library. +postdep_objects=$lt_postdep_objects_F77 + +# Dependencies to place before the objects being linked to create a +# shared library. +predeps=$lt_predeps_F77 + +# Dependencies to place after the objects being linked to create a +# shared library. +postdeps=$lt_postdeps_F77 + +# The directories searched by this compiler when creating a shared +# library +compiler_lib_search_dirs=$lt_compiler_lib_search_dirs_F77 + +# The library search path used internally by the compiler when linking +# a shared library. +compiler_lib_search_path=$lt_compiler_lib_search_path_F77 + +# Method to check whether dependent libraries are shared objects. +deplibs_check_method=$lt_deplibs_check_method + +# Command to use when deplibs_check_method == file_magic. +file_magic_cmd=$lt_file_magic_cmd + +# Flag that allows shared libraries with undefined symbols to be built. +allow_undefined_flag=$lt_allow_undefined_flag_F77 + +# Flag that forces no undefined symbols. +no_undefined_flag=$lt_no_undefined_flag_F77 + +# Commands used to finish a libtool library installation in a directory. +finish_cmds=$lt_finish_cmds + +# Same as above, but a single script fragment to be evaled but not shown. +finish_eval=$lt_finish_eval + +# Take the output of nm and produce a listing of raw symbols and C names. +global_symbol_pipe=$lt_lt_cv_sys_global_symbol_pipe + +# Transform the output of nm in a proper C declaration +global_symbol_to_cdecl=$lt_lt_cv_sys_global_symbol_to_cdecl + +# Transform the output of nm in a C name address pair +global_symbol_to_c_name_address=$lt_lt_cv_sys_global_symbol_to_c_name_address + +# This is the shared library runtime path variable. +runpath_var=$runpath_var + +# This is the shared library path variable. +shlibpath_var=$shlibpath_var + +# Is shlibpath searched before the hard-coded library search path? +shlibpath_overrides_runpath=$shlibpath_overrides_runpath + +# How to hardcode a shared library path into an executable. +hardcode_action=$hardcode_action_F77 + +# Whether we should hardcode library paths into libraries. +hardcode_into_libs=$hardcode_into_libs + +# Flag to hardcode \$libdir into a binary during linking. +# This must work even if \$libdir does not exist. +hardcode_libdir_flag_spec=$lt_hardcode_libdir_flag_spec_F77 + +# If ld is used when linking, flag to hardcode \$libdir into +# a binary during linking. This must work even if \$libdir does +# not exist. +hardcode_libdir_flag_spec_ld=$lt_hardcode_libdir_flag_spec_ld_F77 + +# Whether we need a single -rpath flag with a separated argument. +hardcode_libdir_separator=$lt_hardcode_libdir_separator_F77 + +# Set to yes if using DIR/libNAME${shared_ext} during linking hardcodes DIR into the +# resulting binary. +hardcode_direct=$hardcode_direct_F77 + +# Set to yes if using the -LDIR flag during linking hardcodes DIR into the +# resulting binary. +hardcode_minus_L=$hardcode_minus_L_F77 + +# Set to yes if using SHLIBPATH_VAR=DIR during linking hardcodes DIR into +# the resulting binary. +hardcode_shlibpath_var=$hardcode_shlibpath_var_F77 + +# Set to yes if building a shared library automatically hardcodes DIR into the library +# and all subsequent libraries and executables linked against it. +hardcode_automatic=$hardcode_automatic_F77 + +# Variables whose values should be saved in libtool wrapper scripts and +# restored at relink time. +variables_saved_for_relink="$variables_saved_for_relink" + +# Whether libtool must link a program against all its dependency libraries. +link_all_deplibs=$link_all_deplibs_F77 + +# Compile-time system search path for libraries +sys_lib_search_path_spec=$lt_sys_lib_search_path_spec + +# Run-time system search path for libraries +sys_lib_dlsearch_path_spec=$lt_sys_lib_dlsearch_path_spec + +# Fix the shell variable \$srcfile for the compiler. +fix_srcfile_path=$lt_fix_srcfile_path + +# Set to yes if exported symbols are required. +always_export_symbols=$always_export_symbols_F77 + +# The commands to list exported symbols. +export_symbols_cmds=$lt_export_symbols_cmds_F77 + +# The commands to extract the exported symbol list from a shared archive. +extract_expsyms_cmds=$lt_extract_expsyms_cmds + +# Symbols that should not be listed in the preloaded symbols. +exclude_expsyms=$lt_exclude_expsyms_F77 + +# Symbols that must always be exported. +include_expsyms=$lt_include_expsyms_F77 + +# ### END LIBTOOL TAG CONFIG: $tagname + +__EOF__ + + +else + # If there is no Makefile yet, we rely on a make rule to execute + # `config.status --recheck' to rerun these tests and create the + # libtool script then. + ltmain_in=`echo $ltmain | sed -e 's/\.sh$/.in/'` + if test -f "$ltmain_in"; then + test -f Makefile && make "$ltmain" + fi +fi + + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +CC="$lt_save_CC" + + else + tagname="" + fi + ;; + + GCJ) + if test -n "$GCJ" && test "X$GCJ" != "Xno"; then + + +# Source file extension for Java test sources. +ac_ext=java + +# Object file extension for compiled Java test sources. +objext=o +objext_GCJ=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="class foo {}" + +# Code to be used in simple link tests +lt_simple_link_test_code='public class conftest { public static void main(String[] argv) {}; }' + +# ltmain only uses $CC for tagged configurations so make sure $CC is set. + +# If no C compiler was specified, use CC. +LTCC=${LTCC-"$CC"} + +# If no C compiler flags were specified, use CFLAGS. +LTCFLAGS=${LTCFLAGS-"$CFLAGS"} + +# Allow CC to be a program name with arguments. +compiler=$CC + + +# save warnings/boilerplate of simple test code +ac_outfile=conftest.$ac_objext +echo "$lt_simple_compile_test_code" >conftest.$ac_ext +eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_compiler_boilerplate=`cat conftest.err` +$rm conftest* + +ac_outfile=conftest.$ac_objext +echo "$lt_simple_link_test_code" >conftest.$ac_ext +eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_linker_boilerplate=`cat conftest.err` +$rm -r conftest* + + +# Allow CC to be a program name with arguments. +lt_save_CC="$CC" +CC=${GCJ-"gcj"} +compiler=$CC +compiler_GCJ=$CC +for cc_temp in $compiler""; do + case $cc_temp in + compile | *[\\/]compile | ccache | *[\\/]ccache ) ;; + distcc | *[\\/]distcc | purify | *[\\/]purify ) ;; + \-*) ;; + *) break;; + esac +done +cc_basename=`$echo "X$cc_temp" | $Xsed -e 's%.*/%%' -e "s%^$host_alias-%%"` + + +# GCJ did not exist at the time GCC didn't implicitly link libc in. +archive_cmds_need_lc_GCJ=no + +old_archive_cmds_GCJ=$old_archive_cmds + + +lt_prog_compiler_no_builtin_flag_GCJ= + +if test "$GCC" = yes; then + lt_prog_compiler_no_builtin_flag_GCJ=' -fno-builtin' + + +{ $as_echo "$as_me:$LINENO: checking if $compiler supports -fno-rtti -fno-exceptions" >&5 +$as_echo_n "checking if $compiler supports -fno-rtti -fno-exceptions... " >&6; } +if test "${lt_cv_prog_compiler_rtti_exceptions+set}" = set; then + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_rtti_exceptions=no + ac_outfile=conftest.$ac_objext + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + lt_compiler_flag="-fno-rtti -fno-exceptions" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + # The option is referenced via a variable to avoid confusing sed. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:16920: $lt_compile\"" >&5) + (eval "$lt_compile" 2>conftest.err) + ac_status=$? + cat conftest.err >&5 + echo "$as_me:16924: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s "$ac_outfile"; then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings other than the usual output. + $echo "X$_lt_compiler_boilerplate" | $Xsed -e '/^$/d' >conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler_rtti_exceptions=yes + fi + fi + $rm conftest* + +fi +{ $as_echo "$as_me:$LINENO: result: $lt_cv_prog_compiler_rtti_exceptions" >&5 +$as_echo "$lt_cv_prog_compiler_rtti_exceptions" >&6; } + +if test x"$lt_cv_prog_compiler_rtti_exceptions" = xyes; then + lt_prog_compiler_no_builtin_flag_GCJ="$lt_prog_compiler_no_builtin_flag_GCJ -fno-rtti -fno-exceptions" +else + : +fi + +fi + +lt_prog_compiler_wl_GCJ= +lt_prog_compiler_pic_GCJ= +lt_prog_compiler_static_GCJ= + +{ $as_echo "$as_me:$LINENO: checking for $compiler option to produce PIC" >&5 +$as_echo_n "checking for $compiler option to produce PIC... " >&6; } + + if test "$GCC" = yes; then + lt_prog_compiler_wl_GCJ='-Wl,' + lt_prog_compiler_static_GCJ='-static' + + case $host_os in + aix*) + # All AIX code is PIC. + if test "$host_cpu" = ia64; then + # AIX 5 now supports IA64 processor + lt_prog_compiler_static_GCJ='-Bstatic' + fi + ;; + + amigaos*) + # FIXME: we need at least 68020 code to build shared libraries, but + # adding the `-m68020' flag to GCC prevents building anything better, + # like `-m68040'. + lt_prog_compiler_pic_GCJ='-m68020 -resident32 -malways-restore-a4' + ;; + + beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) + # PIC is the default for these OSes. + ;; + + mingw* | cygwin* | pw32* | os2*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + # Although the cygwin gcc ignores -fPIC, still need this for old-style + # (--disable-auto-import) libraries + + ;; + + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + lt_prog_compiler_pic_GCJ='-fno-common' + ;; + + interix[3-9]*) + # Interix 3.x gcc -fpic/-fPIC options generate broken code. + # Instead, we relocate shared libraries at runtime. + ;; + + msdosdjgpp*) + # Just because we use GCC doesn't mean we suddenly get shared libraries + # on systems that don't support them. + lt_prog_compiler_can_build_shared_GCJ=no + enable_shared=no + ;; + + sysv4*MP*) + if test -d /usr/nec; then + lt_prog_compiler_pic_GCJ=-Kconform_pic + fi + ;; + + hpux*) + # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but + # not for PA HP-UX. + case $host_cpu in + hppa*64*|ia64*) + # +Z the default + ;; + *) + lt_prog_compiler_pic_GCJ='-fPIC' + ;; + esac + ;; + + *) + lt_prog_compiler_pic_GCJ='-fPIC' + ;; + esac + else + # PORTME Check for flag to pass linker flags through the system compiler. + case $host_os in + aix*) + lt_prog_compiler_wl_GCJ='-Wl,' + if test "$host_cpu" = ia64; then + # AIX 5 now supports IA64 processor + lt_prog_compiler_static_GCJ='-Bstatic' + else + lt_prog_compiler_static_GCJ='-bnso -bI:/lib/syscalls.exp' + fi + ;; + darwin*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + case $cc_basename in + xlc*) + lt_prog_compiler_pic_GCJ='-qnocommon' + lt_prog_compiler_wl_GCJ='-Wl,' + ;; + esac + ;; + + mingw* | cygwin* | pw32* | os2*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + + ;; + + hpux9* | hpux10* | hpux11*) + lt_prog_compiler_wl_GCJ='-Wl,' + # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but + # not for PA HP-UX. + case $host_cpu in + hppa*64*|ia64*) + # +Z the default + ;; + *) + lt_prog_compiler_pic_GCJ='+Z' + ;; + esac + # Is there a better lt_prog_compiler_static that works with the bundled CC? + lt_prog_compiler_static_GCJ='${wl}-a ${wl}archive' + ;; + + irix5* | irix6* | nonstopux*) + lt_prog_compiler_wl_GCJ='-Wl,' + # PIC (with -KPIC) is the default. + lt_prog_compiler_static_GCJ='-non_shared' + ;; + + newsos6) + lt_prog_compiler_pic_GCJ='-KPIC' + lt_prog_compiler_static_GCJ='-Bstatic' + ;; + + linux* | k*bsd*-gnu) + case $cc_basename in + icc* | ecc*) + lt_prog_compiler_wl_GCJ='-Wl,' + lt_prog_compiler_pic_GCJ='-KPIC' + lt_prog_compiler_static_GCJ='-static' + ;; + pgcc* | pgf77* | pgf90* | pgf95*) + # Portland Group compilers (*not* the Pentium gcc compiler, + # which looks to be a dead project) + lt_prog_compiler_wl_GCJ='-Wl,' + lt_prog_compiler_pic_GCJ='-fpic' + lt_prog_compiler_static_GCJ='-Bstatic' + ;; + ccc*) + lt_prog_compiler_wl_GCJ='-Wl,' + # All Alpha code is PIC. + lt_prog_compiler_static_GCJ='-non_shared' + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) + # Sun C 5.9 + lt_prog_compiler_pic_GCJ='-KPIC' + lt_prog_compiler_static_GCJ='-Bstatic' + lt_prog_compiler_wl_GCJ='-Wl,' + ;; + *Sun\ F*) + # Sun Fortran 8.3 passes all unrecognized flags to the linker + lt_prog_compiler_pic_GCJ='-KPIC' + lt_prog_compiler_static_GCJ='-Bstatic' + lt_prog_compiler_wl_GCJ='' + ;; + esac + ;; + esac + ;; + + osf3* | osf4* | osf5*) + lt_prog_compiler_wl_GCJ='-Wl,' + # All OSF/1 code is PIC. + lt_prog_compiler_static_GCJ='-non_shared' + ;; + + rdos*) + lt_prog_compiler_static_GCJ='-non_shared' + ;; + + solaris*) + lt_prog_compiler_pic_GCJ='-KPIC' + lt_prog_compiler_static_GCJ='-Bstatic' + case $cc_basename in + f77* | f90* | f95*) + lt_prog_compiler_wl_GCJ='-Qoption ld ';; + *) + lt_prog_compiler_wl_GCJ='-Wl,';; + esac + ;; + + sunos4*) + lt_prog_compiler_wl_GCJ='-Qoption ld ' + lt_prog_compiler_pic_GCJ='-PIC' + lt_prog_compiler_static_GCJ='-Bstatic' + ;; + + sysv4 | sysv4.2uw2* | sysv4.3*) + lt_prog_compiler_wl_GCJ='-Wl,' + lt_prog_compiler_pic_GCJ='-KPIC' + lt_prog_compiler_static_GCJ='-Bstatic' + ;; + + sysv4*MP*) + if test -d /usr/nec ;then + lt_prog_compiler_pic_GCJ='-Kconform_pic' + lt_prog_compiler_static_GCJ='-Bstatic' + fi + ;; + + sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) + lt_prog_compiler_wl_GCJ='-Wl,' + lt_prog_compiler_pic_GCJ='-KPIC' + lt_prog_compiler_static_GCJ='-Bstatic' + ;; + + unicos*) + lt_prog_compiler_wl_GCJ='-Wl,' + lt_prog_compiler_can_build_shared_GCJ=no + ;; + + uts4*) + lt_prog_compiler_pic_GCJ='-pic' + lt_prog_compiler_static_GCJ='-Bstatic' + ;; + + *) + lt_prog_compiler_can_build_shared_GCJ=no + ;; + esac + fi + +{ $as_echo "$as_me:$LINENO: result: $lt_prog_compiler_pic_GCJ" >&5 +$as_echo "$lt_prog_compiler_pic_GCJ" >&6; } + +# +# Check to make sure the PIC flag actually works. +# +if test -n "$lt_prog_compiler_pic_GCJ"; then + +{ $as_echo "$as_me:$LINENO: checking if $compiler PIC flag $lt_prog_compiler_pic_GCJ works" >&5 +$as_echo_n "checking if $compiler PIC flag $lt_prog_compiler_pic_GCJ works... " >&6; } +if test "${lt_cv_prog_compiler_pic_works_GCJ+set}" = set; then + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_pic_works_GCJ=no + ac_outfile=conftest.$ac_objext + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + lt_compiler_flag="$lt_prog_compiler_pic_GCJ" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + # The option is referenced via a variable to avoid confusing sed. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:17210: $lt_compile\"" >&5) + (eval "$lt_compile" 2>conftest.err) + ac_status=$? + cat conftest.err >&5 + echo "$as_me:17214: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s "$ac_outfile"; then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings other than the usual output. + $echo "X$_lt_compiler_boilerplate" | $Xsed -e '/^$/d' >conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler_pic_works_GCJ=yes + fi + fi + $rm conftest* + +fi +{ $as_echo "$as_me:$LINENO: result: $lt_cv_prog_compiler_pic_works_GCJ" >&5 +$as_echo "$lt_cv_prog_compiler_pic_works_GCJ" >&6; } + +if test x"$lt_cv_prog_compiler_pic_works_GCJ" = xyes; then + case $lt_prog_compiler_pic_GCJ in + "" | " "*) ;; + *) lt_prog_compiler_pic_GCJ=" $lt_prog_compiler_pic_GCJ" ;; + esac +else + lt_prog_compiler_pic_GCJ= + lt_prog_compiler_can_build_shared_GCJ=no +fi + +fi +case $host_os in + # For platforms which do not support PIC, -DPIC is meaningless: + *djgpp*) + lt_prog_compiler_pic_GCJ= + ;; + *) + lt_prog_compiler_pic_GCJ="$lt_prog_compiler_pic_GCJ" + ;; +esac + +# +# Check to make sure the static flag actually works. +# +wl=$lt_prog_compiler_wl_GCJ eval lt_tmp_static_flag=\"$lt_prog_compiler_static_GCJ\" +{ $as_echo "$as_me:$LINENO: checking if $compiler static flag $lt_tmp_static_flag works" >&5 +$as_echo_n "checking if $compiler static flag $lt_tmp_static_flag works... " >&6; } +if test "${lt_cv_prog_compiler_static_works_GCJ+set}" = set; then + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_static_works_GCJ=no + save_LDFLAGS="$LDFLAGS" + LDFLAGS="$LDFLAGS $lt_tmp_static_flag" + echo "$lt_simple_link_test_code" > conftest.$ac_ext + if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then + # The linker can only warn and ignore the option if not recognized + # So say no if there are warnings + if test -s conftest.err; then + # Append any errors to the config.log. + cat conftest.err 1>&5 + $echo "X$_lt_linker_boilerplate" | $Xsed -e '/^$/d' > conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler_static_works_GCJ=yes + fi + else + lt_cv_prog_compiler_static_works_GCJ=yes + fi + fi + $rm -r conftest* + LDFLAGS="$save_LDFLAGS" + +fi +{ $as_echo "$as_me:$LINENO: result: $lt_cv_prog_compiler_static_works_GCJ" >&5 +$as_echo "$lt_cv_prog_compiler_static_works_GCJ" >&6; } + +if test x"$lt_cv_prog_compiler_static_works_GCJ" = xyes; then + : +else + lt_prog_compiler_static_GCJ= +fi + + +{ $as_echo "$as_me:$LINENO: checking if $compiler supports -c -o file.$ac_objext" >&5 +$as_echo_n "checking if $compiler supports -c -o file.$ac_objext... " >&6; } +if test "${lt_cv_prog_compiler_c_o_GCJ+set}" = set; then + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_c_o_GCJ=no + $rm -r conftest 2>/dev/null + mkdir conftest + cd conftest + mkdir out + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + lt_compiler_flag="-o out/conftest2.$ac_objext" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:17314: $lt_compile\"" >&5) + (eval "$lt_compile" 2>out/conftest.err) + ac_status=$? + cat out/conftest.err >&5 + echo "$as_me:17318: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s out/conftest2.$ac_objext + then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + $echo "X$_lt_compiler_boilerplate" | $Xsed -e '/^$/d' > out/conftest.exp + $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 + if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then + lt_cv_prog_compiler_c_o_GCJ=yes + fi + fi + chmod u+w . 2>&5 + $rm conftest* + # SGI C++ compiler will create directory out/ii_files/ for + # template instantiation + test -d out/ii_files && $rm out/ii_files/* && rmdir out/ii_files + $rm out/* && rmdir out + cd .. + rmdir conftest + $rm conftest* + +fi +{ $as_echo "$as_me:$LINENO: result: $lt_cv_prog_compiler_c_o_GCJ" >&5 +$as_echo "$lt_cv_prog_compiler_c_o_GCJ" >&6; } + + +hard_links="nottested" +if test "$lt_cv_prog_compiler_c_o_GCJ" = no && test "$need_locks" != no; then + # do not overwrite the value of need_locks provided by the user + { $as_echo "$as_me:$LINENO: checking if we can lock with hard links" >&5 +$as_echo_n "checking if we can lock with hard links... " >&6; } + hard_links=yes + $rm conftest* + ln conftest.a conftest.b 2>/dev/null && hard_links=no + touch conftest.a + ln conftest.a conftest.b 2>&5 || hard_links=no + ln conftest.a conftest.b 2>/dev/null && hard_links=no + { $as_echo "$as_me:$LINENO: result: $hard_links" >&5 +$as_echo "$hard_links" >&6; } + if test "$hard_links" = no; then + { $as_echo "$as_me:$LINENO: WARNING: \`$CC' does not support \`-c -o', so \`make -j' may be unsafe" >&5 +$as_echo "$as_me: WARNING: \`$CC' does not support \`-c -o', so \`make -j' may be unsafe" >&2;} + need_locks=warn + fi +else + need_locks=no +fi + +{ $as_echo "$as_me:$LINENO: checking whether the $compiler linker ($LD) supports shared libraries" >&5 +$as_echo_n "checking whether the $compiler linker ($LD) supports shared libraries... " >&6; } + + runpath_var= + allow_undefined_flag_GCJ= + enable_shared_with_static_runtimes_GCJ=no + archive_cmds_GCJ= + archive_expsym_cmds_GCJ= + old_archive_From_new_cmds_GCJ= + old_archive_from_expsyms_cmds_GCJ= + export_dynamic_flag_spec_GCJ= + whole_archive_flag_spec_GCJ= + thread_safe_flag_spec_GCJ= + hardcode_libdir_flag_spec_GCJ= + hardcode_libdir_flag_spec_ld_GCJ= + hardcode_libdir_separator_GCJ= + hardcode_direct_GCJ=no + hardcode_minus_L_GCJ=no + hardcode_shlibpath_var_GCJ=unsupported + link_all_deplibs_GCJ=unknown + hardcode_automatic_GCJ=no + module_cmds_GCJ= + module_expsym_cmds_GCJ= + always_export_symbols_GCJ=no + export_symbols_cmds_GCJ='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + # include_expsyms should be a list of space-separated symbols to be *always* + # included in the symbol list + include_expsyms_GCJ= + # exclude_expsyms can be an extended regexp of symbols to exclude + # it will be wrapped by ` (' and `)$', so one must not match beginning or + # end of line. Example: `a|bc|.*d.*' will exclude the symbols `a' and `bc', + # as well as any symbol that contains `d'. + exclude_expsyms_GCJ='_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*' + # Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out + # platforms (ab)use it in PIC code, but their linkers get confused if + # the symbol is explicitly referenced. Since portable code cannot + # rely on this symbol name, it's probably fine to never include it in + # preloaded symbol tables. + # Exclude shared library initialization/finalization symbols. + extract_expsyms_cmds= + # Just being paranoid about ensuring that cc_basename is set. + for cc_temp in $compiler""; do + case $cc_temp in + compile | *[\\/]compile | ccache | *[\\/]ccache ) ;; + distcc | *[\\/]distcc | purify | *[\\/]purify ) ;; + \-*) ;; + *) break;; + esac +done +cc_basename=`$echo "X$cc_temp" | $Xsed -e 's%.*/%%' -e "s%^$host_alias-%%"` + + case $host_os in + cygwin* | mingw* | pw32*) + # FIXME: the MSVC++ port hasn't been tested in a loooong time + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + if test "$GCC" != yes; then + with_gnu_ld=no + fi + ;; + interix*) + # we just hope/assume this is gcc and not c89 (= MSVC++) + with_gnu_ld=yes + ;; + openbsd*) + with_gnu_ld=no + ;; + esac + + ld_shlibs_GCJ=yes + if test "$with_gnu_ld" = yes; then + # If archive_cmds runs LD, not CC, wlarc should be empty + wlarc='${wl}' + + # Set some defaults for GNU ld with shared library support. These + # are reset later if shared libraries are not supported. Putting them + # here allows them to be overridden if necessary. + runpath_var=LD_RUN_PATH + hardcode_libdir_flag_spec_GCJ='${wl}--rpath ${wl}$libdir' + export_dynamic_flag_spec_GCJ='${wl}--export-dynamic' + # ancient GNU ld didn't support --whole-archive et. al. + if $LD --help 2>&1 | grep 'no-whole-archive' > /dev/null; then + whole_archive_flag_spec_GCJ="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive' + else + whole_archive_flag_spec_GCJ= + fi + supports_anon_versioning=no + case `$LD -v 2>/dev/null` in + *\ [01].* | *\ 2.[0-9].* | *\ 2.10.*) ;; # catch versions < 2.11 + *\ 2.11.93.0.2\ *) supports_anon_versioning=yes ;; # RH7.3 ... + *\ 2.11.92.0.12\ *) supports_anon_versioning=yes ;; # Mandrake 8.2 ... + *\ 2.11.*) ;; # other 2.11 versions + *) supports_anon_versioning=yes ;; + esac + + # See if GNU ld supports shared libraries. + case $host_os in + aix[3-9]*) + # On AIX/PPC, the GNU linker is very broken + if test "$host_cpu" != ia64; then + ld_shlibs_GCJ=no + cat <&2 + +*** Warning: the GNU linker, at least up to release 2.9.1, is reported +*** to be unable to reliably create shared libraries on AIX. +*** Therefore, libtool is disabling shared libraries support. If you +*** really care for shared libraries, you may want to modify your PATH +*** so that a non-GNU linker is found, and then restart. + +EOF + fi + ;; + + amigaos*) + archive_cmds_GCJ='$rm $output_objdir/a2ixlibrary.data~$echo "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$echo "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$echo "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$echo "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + hardcode_libdir_flag_spec_GCJ='-L$libdir' + hardcode_minus_L_GCJ=yes + + # Samuel A. Falvo II reports + # that the semantics of dynamic libraries on AmigaOS, at least up + # to version 4, is to share data among multiple programs linked + # with the same dynamic library. Since this doesn't match the + # behavior of shared libraries on other platforms, we can't use + # them. + ld_shlibs_GCJ=no + ;; + + beos*) + if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + allow_undefined_flag_GCJ=unsupported + # Joseph Beckenbach says some releases of gcc + # support --undefined. This deserves some investigation. FIXME + archive_cmds_GCJ='$CC -nostart $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + else + ld_shlibs_GCJ=no + fi + ;; + + cygwin* | mingw* | pw32*) + # _LT_AC_TAGVAR(hardcode_libdir_flag_spec, GCJ) is actually meaningless, + # as there is no search path for DLLs. + hardcode_libdir_flag_spec_GCJ='-L$libdir' + allow_undefined_flag_GCJ=unsupported + always_export_symbols_GCJ=no + enable_shared_with_static_runtimes_GCJ=yes + export_symbols_cmds_GCJ='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1 DATA/'\'' -e '\''/^[AITW][ ]/s/.*[ ]//'\'' | sort | uniq > $export_symbols' + + if $LD --help 2>&1 | grep 'auto-import' > /dev/null; then + archive_cmds_GCJ='$CC -shared $libobjs $deplibs $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + # If the export-symbols file already is a .def file (1st line + # is EXPORTS), use it as is; otherwise, prepend... + archive_expsym_cmds_GCJ='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then + cp $export_symbols $output_objdir/$soname.def; + else + echo EXPORTS > $output_objdir/$soname.def; + cat $export_symbols >> $output_objdir/$soname.def; + fi~ + $CC -shared $output_objdir/$soname.def $libobjs $deplibs $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + else + ld_shlibs_GCJ=no + fi + ;; + + interix[3-9]*) + hardcode_direct_GCJ=no + hardcode_shlibpath_var_GCJ=no + hardcode_libdir_flag_spec_GCJ='${wl}-rpath,$libdir' + export_dynamic_flag_spec_GCJ='${wl}-E' + # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. + # Instead, shared libraries are loaded at an image base (0x10000000 by + # default) and relocated if they conflict, which is a slow very memory + # consuming and fragmenting process. To avoid this, we pick a random, + # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link + # time. Moving up from 0x10000000 also allows more sbrk(2) space. + archive_cmds_GCJ='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + archive_expsym_cmds_GCJ='sed "s,^,_," $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--retain-symbols-file,$output_objdir/$soname.expsym ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + ;; + + gnu* | linux* | k*bsd*-gnu) + if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + tmp_addflag= + case $cc_basename,$host_cpu in + pgcc*) # Portland Group C compiler + whole_archive_flag_spec_GCJ='${wl}--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; $echo \"$new_convenience\"` ${wl}--no-whole-archive' + tmp_addflag=' $pic_flag' + ;; + pgf77* | pgf90* | pgf95*) # Portland Group f77 and f90 compilers + whole_archive_flag_spec_GCJ='${wl}--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; $echo \"$new_convenience\"` ${wl}--no-whole-archive' + tmp_addflag=' $pic_flag -Mnomain' ;; + ecc*,ia64* | icc*,ia64*) # Intel C compiler on ia64 + tmp_addflag=' -i_dynamic' ;; + efc*,ia64* | ifort*,ia64*) # Intel Fortran compiler on ia64 + tmp_addflag=' -i_dynamic -nofor_main' ;; + ifc* | ifort*) # Intel Fortran compiler + tmp_addflag=' -nofor_main' ;; + esac + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) # Sun C 5.9 + whole_archive_flag_spec_GCJ='${wl}--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; $echo \"$new_convenience\"` ${wl}--no-whole-archive' + tmp_sharedflag='-G' ;; + *Sun\ F*) # Sun Fortran 8.3 + tmp_sharedflag='-G' ;; + *) + tmp_sharedflag='-shared' ;; + esac + archive_cmds_GCJ='$CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + + if test $supports_anon_versioning = yes; then + archive_expsym_cmds_GCJ='$echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + $echo "local: *; };" >> $output_objdir/$libname.ver~ + $CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-version-script ${wl}$output_objdir/$libname.ver -o $lib' + fi + else + ld_shlibs_GCJ=no + fi + ;; + + netbsd*) + if echo __ELF__ | $CC -E - | grep __ELF__ >/dev/null; then + archive_cmds_GCJ='$LD -Bshareable $libobjs $deplibs $linker_flags -o $lib' + wlarc= + else + archive_cmds_GCJ='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + archive_expsym_cmds_GCJ='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + fi + ;; + + solaris*) + if $LD -v 2>&1 | grep 'BFD 2\.8' > /dev/null; then + ld_shlibs_GCJ=no + cat <&2 + +*** Warning: The releases 2.8.* of the GNU linker cannot reliably +*** create shared libraries on Solaris systems. Therefore, libtool +*** is disabling shared libraries support. We urge you to upgrade GNU +*** binutils to release 2.9.1 or newer. Another option is to modify +*** your PATH or compiler configuration so that the native linker is +*** used, and then restart. + +EOF + elif $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + archive_cmds_GCJ='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + archive_expsym_cmds_GCJ='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + else + ld_shlibs_GCJ=no + fi + ;; + + sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*) + case `$LD -v 2>&1` in + *\ [01].* | *\ 2.[0-9].* | *\ 2.1[0-5].*) + ld_shlibs_GCJ=no + cat <<_LT_EOF 1>&2 + +*** Warning: Releases of the GNU linker prior to 2.16.91.0.3 can not +*** reliably create shared libraries on SCO systems. Therefore, libtool +*** is disabling shared libraries support. We urge you to upgrade GNU +*** binutils to release 2.16.91.0.3 or newer. Another option is to modify +*** your PATH or compiler configuration so that the native linker is +*** used, and then restart. + +_LT_EOF + ;; + *) + if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + hardcode_libdir_flag_spec_GCJ='`test -z "$SCOABSPATH" && echo ${wl}-rpath,$libdir`' + archive_cmds_GCJ='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname,\${SCOABSPATH:+${install_libdir}/}$soname -o $lib' + archive_expsym_cmds_GCJ='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname,\${SCOABSPATH:+${install_libdir}/}$soname,-retain-symbols-file,$export_symbols -o $lib' + else + ld_shlibs_GCJ=no + fi + ;; + esac + ;; + + sunos4*) + archive_cmds_GCJ='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linker_flags' + wlarc= + hardcode_direct_GCJ=yes + hardcode_shlibpath_var_GCJ=no + ;; + + *) + if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + archive_cmds_GCJ='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib' + archive_expsym_cmds_GCJ='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + else + ld_shlibs_GCJ=no + fi + ;; + esac + + if test "$ld_shlibs_GCJ" = no; then + runpath_var= + hardcode_libdir_flag_spec_GCJ= + export_dynamic_flag_spec_GCJ= + whole_archive_flag_spec_GCJ= + fi + else + # PORTME fill in a description of your system's linker (not GNU ld) + case $host_os in + aix3*) + allow_undefined_flag_GCJ=unsupported + always_export_symbols_GCJ=yes + archive_expsym_cmds_GCJ='$LD -o $output_objdir/$soname $libobjs $deplibs $linker_flags -bE:$export_symbols -T512 -H512 -bM:SRE~$AR $AR_FLAGS $lib $output_objdir/$soname' + # Note: this linker hardcodes the directories in LIBPATH if there + # are no directories specified by -L. + hardcode_minus_L_GCJ=yes + if test "$GCC" = yes && test -z "$lt_prog_compiler_static"; then + # Neither direct hardcoding nor static linking is supported with a + # broken collect2. + hardcode_direct_GCJ=unsupported + fi + ;; + + aix[4-9]*) + if test "$host_cpu" = ia64; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + exp_sym_flag='-Bexport' + no_entry_flag="" + else + # If we're using GNU nm, then we don't want the "-C" option. + # -C means demangle to AIX nm, but means don't demangle with GNU nm + if $NM -V 2>&1 | grep 'GNU' > /dev/null; then + export_symbols_cmds_GCJ='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$2 == "T") || (\$2 == "D") || (\$2 == "B")) && (substr(\$3,1,1) != ".")) { print \$3 } }'\'' | sort -u > $export_symbols' + else + export_symbols_cmds_GCJ='$NM -BCpg $libobjs $convenience | awk '\''{ if (((\$2 == "T") || (\$2 == "D") || (\$2 == "B")) && (substr(\$3,1,1) != ".")) { print \$3 } }'\'' | sort -u > $export_symbols' + fi + aix_use_runtimelinking=no + + # Test if we are trying to use run time linking or normal + # AIX style linking. If -brtl is somewhere in LDFLAGS, we + # need to do runtime linking. + case $host_os in aix4.[23]|aix4.[23].*|aix[5-9]*) + for ld_flag in $LDFLAGS; do + if (test $ld_flag = "-brtl" || test $ld_flag = "-Wl,-brtl"); then + aix_use_runtimelinking=yes + break + fi + done + ;; + esac + + exp_sym_flag='-bexport' + no_entry_flag='-bnoentry' + fi + + # When large executables or shared objects are built, AIX ld can + # have problems creating the table of contents. If linking a library + # or program results in "error TOC overflow" add -mminimal-toc to + # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not + # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. + + archive_cmds_GCJ='' + hardcode_direct_GCJ=yes + hardcode_libdir_separator_GCJ=':' + link_all_deplibs_GCJ=yes + + if test "$GCC" = yes; then + case $host_os in aix4.[012]|aix4.[012].*) + # We only want to do this on AIX 4.2 and lower, the check + # below for broken collect2 doesn't work under 4.3+ + collect2name=`${CC} -print-prog-name=collect2` + if test -f "$collect2name" && \ + strings "$collect2name" | grep resolve_lib_name >/dev/null + then + # We have reworked collect2 + : + else + # We have old collect2 + hardcode_direct_GCJ=unsupported + # It fails to find uninstalled libraries when the uninstalled + # path is not listed in the libpath. Setting hardcode_minus_L + # to unsupported forces relinking + hardcode_minus_L_GCJ=yes + hardcode_libdir_flag_spec_GCJ='-L$libdir' + hardcode_libdir_separator_GCJ= + fi + ;; + esac + shared_flag='-shared' + if test "$aix_use_runtimelinking" = yes; then + shared_flag="$shared_flag "'${wl}-G' + fi + else + # not using gcc + if test "$host_cpu" = ia64; then + # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release + # chokes on -Wl,-G. The following line is correct: + shared_flag='-G' + else + if test "$aix_use_runtimelinking" = yes; then + shared_flag='${wl}-G' + else + shared_flag='${wl}-bM:SRE' + fi + fi + fi + + # It seems that -bexpall does not export symbols beginning with + # underscore (_), so it is better to generate a list of symbols to export. + always_export_symbols_GCJ=yes + if test "$aix_use_runtimelinking" = yes; then + # Warning - without using the other runtime loading flags (-brtl), + # -berok will link without error, but may produce a broken library. + allow_undefined_flag_GCJ='-berok' + # Determine the default libpath from the value encoded in an empty executable. + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + $as_test_x conftest$ac_exeext + }; then + +lt_aix_libpath_sed=' + /Import File Strings/,/^$/ { + /^0/ { + s/^0 *\(.*\)$/\1/ + p + } + }' +aix_libpath=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` +# Check for a 64-bit object if we didn't find anything. +if test -z "$aix_libpath"; then + aix_libpath=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` +fi +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + +fi + +rm -rf conftest.dSYM +rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ + conftest$ac_exeext conftest.$ac_ext +if test -z "$aix_libpath"; then aix_libpath="/usr/lib:/lib"; fi + + hardcode_libdir_flag_spec_GCJ='${wl}-blibpath:$libdir:'"$aix_libpath" + archive_expsym_cmds_GCJ="\$CC"' -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags `if test "x${allow_undefined_flag}" != "x"; then echo "${wl}${allow_undefined_flag}"; else :; fi` '"\${wl}$exp_sym_flag:\$export_symbols $shared_flag" + else + if test "$host_cpu" = ia64; then + hardcode_libdir_flag_spec_GCJ='${wl}-R $libdir:/usr/lib:/lib' + allow_undefined_flag_GCJ="-z nodefs" + archive_expsym_cmds_GCJ="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags ${wl}${allow_undefined_flag} '"\${wl}$exp_sym_flag:\$export_symbols" + else + # Determine the default libpath from the value encoded in an empty executable. + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + $as_test_x conftest$ac_exeext + }; then + +lt_aix_libpath_sed=' + /Import File Strings/,/^$/ { + /^0/ { + s/^0 *\(.*\)$/\1/ + p + } + }' +aix_libpath=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` +# Check for a 64-bit object if we didn't find anything. +if test -z "$aix_libpath"; then + aix_libpath=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` +fi +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + +fi + +rm -rf conftest.dSYM +rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ + conftest$ac_exeext conftest.$ac_ext +if test -z "$aix_libpath"; then aix_libpath="/usr/lib:/lib"; fi + + hardcode_libdir_flag_spec_GCJ='${wl}-blibpath:$libdir:'"$aix_libpath" + # Warning - without using the other run time loading flags, + # -berok will link without error, but may produce a broken library. + no_undefined_flag_GCJ=' ${wl}-bernotok' + allow_undefined_flag_GCJ=' ${wl}-berok' + # Exported symbols can be pulled into shared objects from archives + whole_archive_flag_spec_GCJ='$convenience' + archive_cmds_need_lc_GCJ=yes + # This is similar to how AIX traditionally builds its shared libraries. + archive_expsym_cmds_GCJ="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs ${wl}-bnoentry $compiler_flags ${wl}-bE:$export_symbols${allow_undefined_flag}~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$soname' + fi + fi + ;; + + amigaos*) + archive_cmds_GCJ='$rm $output_objdir/a2ixlibrary.data~$echo "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$echo "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$echo "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$echo "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + hardcode_libdir_flag_spec_GCJ='-L$libdir' + hardcode_minus_L_GCJ=yes + # see comment about different semantics on the GNU ld section + ld_shlibs_GCJ=no + ;; + + bsdi[45]*) + export_dynamic_flag_spec_GCJ=-rdynamic + ;; + + cygwin* | mingw* | pw32*) + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + hardcode_libdir_flag_spec_GCJ=' ' + allow_undefined_flag_GCJ=unsupported + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=".dll" + # FIXME: Setting linknames here is a bad hack. + archive_cmds_GCJ='$CC -o $lib $libobjs $compiler_flags `echo "$deplibs" | $SED -e '\''s/ -lc$//'\''` -link -dll~linknames=' + # The linker will automatically build a .lib file if we build a DLL. + old_archive_From_new_cmds_GCJ='true' + # FIXME: Should let the user specify the lib program. + old_archive_cmds_GCJ='lib -OUT:$oldlib$oldobjs$old_deplibs' + fix_srcfile_path_GCJ='`cygpath -w "$srcfile"`' + enable_shared_with_static_runtimes_GCJ=yes + ;; + + darwin* | rhapsody*) + allow_undefined_flag_GCJ="$_lt_dar_allow_undefined" + archive_cmds_need_lc_GCJ=no + hardcode_direct_GCJ=no + hardcode_automatic_GCJ=yes + hardcode_shlibpath_var_GCJ=unsupported + whole_archive_flag_spec_GCJ='' + link_all_deplibs_GCJ=yes + if test "$GCC" = yes ; then + output_verbose_link_cmd='echo' + archive_cmds_GCJ="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod${_lt_dsymutil}" + module_cmds_GCJ="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags${_lt_dsymutil}" + archive_expsym_cmds_GCJ="sed 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring ${_lt_dar_single_mod}${_lt_dar_export_syms}${_lt_dsymutil}" + module_expsym_cmds_GCJ="sed -e 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags${_lt_dar_export_syms}${_lt_dsymutil}" + else + case $cc_basename in + xlc*) + output_verbose_link_cmd='echo' + archive_cmds_GCJ='$CC -qmkshrobj $allow_undefined_flag -o $lib $libobjs $deplibs $compiler_flags ${wl}-install_name ${wl}`echo $rpath/$soname` $xlcverstring' + module_cmds_GCJ='$CC $allow_undefined_flag -o $lib -bundle $libobjs $deplibs$compiler_flags' + # Don't fix this by using the ld -exported_symbols_list flag, it doesn't exist in older darwin lds + archive_expsym_cmds_GCJ='sed -e "s,#.*,," -e "s,^[ ]*,," -e "s,^\(..*\),_&," < $export_symbols > $output_objdir/${libname}-symbols.expsym~$CC -qmkshrobj $allow_undefined_flag -o $lib $libobjs $deplibs $compiler_flags ${wl}-install_name ${wl}$rpath/$soname $xlcverstring~nmedit -s $output_objdir/${libname}-symbols.expsym ${lib}' + module_expsym_cmds_GCJ='sed -e "s,#.*,," -e "s,^[ ]*,," -e "s,^\(..*\),_&," < $export_symbols > $output_objdir/${libname}-symbols.expsym~$CC $allow_undefined_flag -o $lib -bundle $libobjs $deplibs$compiler_flags~nmedit -s $output_objdir/${libname}-symbols.expsym ${lib}' + ;; + *) + ld_shlibs_GCJ=no + ;; + esac + fi + ;; + + dgux*) + archive_cmds_GCJ='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_libdir_flag_spec_GCJ='-L$libdir' + hardcode_shlibpath_var_GCJ=no + ;; + + freebsd1*) + ld_shlibs_GCJ=no + ;; + + # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor + # support. Future versions do this automatically, but an explicit c++rt0.o + # does not break anything, and helps significantly (at the cost of a little + # extra space). + freebsd2.2*) + archive_cmds_GCJ='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags /usr/lib/c++rt0.o' + hardcode_libdir_flag_spec_GCJ='-R$libdir' + hardcode_direct_GCJ=yes + hardcode_shlibpath_var_GCJ=no + ;; + + # Unfortunately, older versions of FreeBSD 2 do not have this feature. + freebsd2*) + archive_cmds_GCJ='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' + hardcode_direct_GCJ=yes + hardcode_minus_L_GCJ=yes + hardcode_shlibpath_var_GCJ=no + ;; + + # FreeBSD 3 and greater uses gcc -shared to do shared libraries. + freebsd* | dragonfly*) + archive_cmds_GCJ='$CC -shared -o $lib $libobjs $deplibs $compiler_flags' + hardcode_libdir_flag_spec_GCJ='-R$libdir' + hardcode_direct_GCJ=yes + hardcode_shlibpath_var_GCJ=no + ;; + + hpux9*) + if test "$GCC" = yes; then + archive_cmds_GCJ='$rm $output_objdir/$soname~$CC -shared -fPIC ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $libobjs $deplibs $compiler_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib' + else + archive_cmds_GCJ='$rm $output_objdir/$soname~$LD -b +b $install_libdir -o $output_objdir/$soname $libobjs $deplibs $linker_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib' + fi + hardcode_libdir_flag_spec_GCJ='${wl}+b ${wl}$libdir' + hardcode_libdir_separator_GCJ=: + hardcode_direct_GCJ=yes + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + hardcode_minus_L_GCJ=yes + export_dynamic_flag_spec_GCJ='${wl}-E' + ;; + + hpux10*) + if test "$GCC" = yes -a "$with_gnu_ld" = no; then + archive_cmds_GCJ='$CC -shared -fPIC ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + else + archive_cmds_GCJ='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags' + fi + if test "$with_gnu_ld" = no; then + hardcode_libdir_flag_spec_GCJ='${wl}+b ${wl}$libdir' + hardcode_libdir_separator_GCJ=: + + hardcode_direct_GCJ=yes + export_dynamic_flag_spec_GCJ='${wl}-E' + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + hardcode_minus_L_GCJ=yes + fi + ;; + + hpux11*) + if test "$GCC" = yes -a "$with_gnu_ld" = no; then + case $host_cpu in + hppa*64*) + archive_cmds_GCJ='$CC -shared ${wl}+h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + ia64*) + archive_cmds_GCJ='$CC -shared ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + archive_cmds_GCJ='$CC -shared -fPIC ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + else + case $host_cpu in + hppa*64*) + archive_cmds_GCJ='$CC -b ${wl}+h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + ia64*) + archive_cmds_GCJ='$CC -b ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + archive_cmds_GCJ='$CC -b ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + fi + if test "$with_gnu_ld" = no; then + hardcode_libdir_flag_spec_GCJ='${wl}+b ${wl}$libdir' + hardcode_libdir_separator_GCJ=: + + case $host_cpu in + hppa*64*|ia64*) + hardcode_libdir_flag_spec_ld_GCJ='+b $libdir' + hardcode_direct_GCJ=no + hardcode_shlibpath_var_GCJ=no + ;; + *) + hardcode_direct_GCJ=yes + export_dynamic_flag_spec_GCJ='${wl}-E' + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + hardcode_minus_L_GCJ=yes + ;; + esac + fi + ;; + + irix5* | irix6* | nonstopux*) + if test "$GCC" = yes; then + archive_cmds_GCJ='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + else + archive_cmds_GCJ='$LD -shared $libobjs $deplibs $linker_flags -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib' + hardcode_libdir_flag_spec_ld_GCJ='-rpath $libdir' + fi + hardcode_libdir_flag_spec_GCJ='${wl}-rpath ${wl}$libdir' + hardcode_libdir_separator_GCJ=: + link_all_deplibs_GCJ=yes + ;; + + netbsd*) + if echo __ELF__ | $CC -E - | grep __ELF__ >/dev/null; then + archive_cmds_GCJ='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' # a.out + else + archive_cmds_GCJ='$LD -shared -o $lib $libobjs $deplibs $linker_flags' # ELF + fi + hardcode_libdir_flag_spec_GCJ='-R$libdir' + hardcode_direct_GCJ=yes + hardcode_shlibpath_var_GCJ=no + ;; + + newsos6) + archive_cmds_GCJ='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_direct_GCJ=yes + hardcode_libdir_flag_spec_GCJ='${wl}-rpath ${wl}$libdir' + hardcode_libdir_separator_GCJ=: + hardcode_shlibpath_var_GCJ=no + ;; + + openbsd*) + if test -f /usr/libexec/ld.so; then + hardcode_direct_GCJ=yes + hardcode_shlibpath_var_GCJ=no + if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then + archive_cmds_GCJ='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds_GCJ='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags ${wl}-retain-symbols-file,$export_symbols' + hardcode_libdir_flag_spec_GCJ='${wl}-rpath,$libdir' + export_dynamic_flag_spec_GCJ='${wl}-E' + else + case $host_os in + openbsd[01].* | openbsd2.[0-7] | openbsd2.[0-7].*) + archive_cmds_GCJ='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' + hardcode_libdir_flag_spec_GCJ='-R$libdir' + ;; + *) + archive_cmds_GCJ='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + hardcode_libdir_flag_spec_GCJ='${wl}-rpath,$libdir' + ;; + esac + fi + else + ld_shlibs_GCJ=no + fi + ;; + + os2*) + hardcode_libdir_flag_spec_GCJ='-L$libdir' + hardcode_minus_L_GCJ=yes + allow_undefined_flag_GCJ=unsupported + archive_cmds_GCJ='$echo "LIBRARY $libname INITINSTANCE" > $output_objdir/$libname.def~$echo "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~$echo DATA >> $output_objdir/$libname.def~$echo " SINGLE NONSHARED" >> $output_objdir/$libname.def~$echo EXPORTS >> $output_objdir/$libname.def~emxexp $libobjs >> $output_objdir/$libname.def~$CC -Zdll -Zcrtdll -o $lib $libobjs $deplibs $compiler_flags $output_objdir/$libname.def' + old_archive_From_new_cmds_GCJ='emximp -o $output_objdir/$libname.a $output_objdir/$libname.def' + ;; + + osf3*) + if test "$GCC" = yes; then + allow_undefined_flag_GCJ=' ${wl}-expect_unresolved ${wl}\*' + archive_cmds_GCJ='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + else + allow_undefined_flag_GCJ=' -expect_unresolved \*' + archive_cmds_GCJ='$LD -shared${allow_undefined_flag} $libobjs $deplibs $linker_flags -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib' + fi + hardcode_libdir_flag_spec_GCJ='${wl}-rpath ${wl}$libdir' + hardcode_libdir_separator_GCJ=: + ;; + + osf4* | osf5*) # as osf3* with the addition of -msym flag + if test "$GCC" = yes; then + allow_undefined_flag_GCJ=' ${wl}-expect_unresolved ${wl}\*' + archive_cmds_GCJ='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags ${wl}-msym ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + hardcode_libdir_flag_spec_GCJ='${wl}-rpath ${wl}$libdir' + else + allow_undefined_flag_GCJ=' -expect_unresolved \*' + archive_cmds_GCJ='$LD -shared${allow_undefined_flag} $libobjs $deplibs $linker_flags -msym -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib' + archive_expsym_cmds_GCJ='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done; echo "-hidden">> $lib.exp~ + $LD -shared${allow_undefined_flag} -input $lib.exp $linker_flags $libobjs $deplibs -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib~$rm $lib.exp' + + # Both c and cxx compiler support -rpath directly + hardcode_libdir_flag_spec_GCJ='-rpath $libdir' + fi + hardcode_libdir_separator_GCJ=: + ;; + + solaris*) + no_undefined_flag_GCJ=' -z text' + if test "$GCC" = yes; then + wlarc='${wl}' + archive_cmds_GCJ='$CC -shared ${wl}-h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds_GCJ='$echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~$echo "local: *; };" >> $lib.exp~ + $CC -shared ${wl}-M ${wl}$lib.exp ${wl}-h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags~$rm $lib.exp' + else + wlarc='' + archive_cmds_GCJ='$LD -G${allow_undefined_flag} -h $soname -o $lib $libobjs $deplibs $linker_flags' + archive_expsym_cmds_GCJ='$echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~$echo "local: *; };" >> $lib.exp~ + $LD -G${allow_undefined_flag} -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$rm $lib.exp' + fi + hardcode_libdir_flag_spec_GCJ='-R$libdir' + hardcode_shlibpath_var_GCJ=no + case $host_os in + solaris2.[0-5] | solaris2.[0-5].*) ;; + *) + # The compiler driver will combine and reorder linker options, + # but understands `-z linker_flag'. GCC discards it without `$wl', + # but is careful enough not to reorder. + # Supported since Solaris 2.6 (maybe 2.5.1?) + if test "$GCC" = yes; then + whole_archive_flag_spec_GCJ='${wl}-z ${wl}allextract$convenience ${wl}-z ${wl}defaultextract' + else + whole_archive_flag_spec_GCJ='-z allextract$convenience -z defaultextract' + fi + ;; + esac + link_all_deplibs_GCJ=yes + ;; + + sunos4*) + if test "x$host_vendor" = xsequent; then + # Use $CC to link under sequent, because it throws in some extra .o + # files that make .init and .fini sections work. + archive_cmds_GCJ='$CC -G ${wl}-h $soname -o $lib $libobjs $deplibs $compiler_flags' + else + archive_cmds_GCJ='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linker_flags' + fi + hardcode_libdir_flag_spec_GCJ='-L$libdir' + hardcode_direct_GCJ=yes + hardcode_minus_L_GCJ=yes + hardcode_shlibpath_var_GCJ=no + ;; + + sysv4) + case $host_vendor in + sni) + archive_cmds_GCJ='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_direct_GCJ=yes # is this really true??? + ;; + siemens) + ## LD is ld it makes a PLAMLIB + ## CC just makes a GrossModule. + archive_cmds_GCJ='$LD -G -o $lib $libobjs $deplibs $linker_flags' + reload_cmds_GCJ='$CC -r -o $output$reload_objs' + hardcode_direct_GCJ=no + ;; + motorola) + archive_cmds_GCJ='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_direct_GCJ=no #Motorola manual says yes, but my tests say they lie + ;; + esac + runpath_var='LD_RUN_PATH' + hardcode_shlibpath_var_GCJ=no + ;; + + sysv4.3*) + archive_cmds_GCJ='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_shlibpath_var_GCJ=no + export_dynamic_flag_spec_GCJ='-Bexport' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + archive_cmds_GCJ='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_shlibpath_var_GCJ=no + runpath_var=LD_RUN_PATH + hardcode_runpath_var=yes + ld_shlibs_GCJ=yes + fi + ;; + + sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[01].[10]* | unixware7* | sco3.2v5.0.[024]*) + no_undefined_flag_GCJ='${wl}-z,text' + archive_cmds_need_lc_GCJ=no + hardcode_shlibpath_var_GCJ=no + runpath_var='LD_RUN_PATH' + + if test "$GCC" = yes; then + archive_cmds_GCJ='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds_GCJ='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + else + archive_cmds_GCJ='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds_GCJ='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + fi + ;; + + sysv5* | sco3.2v5* | sco5v6*) + # Note: We can NOT use -z defs as we might desire, because we do not + # link with -lc, and that would cause any symbols used from libc to + # always be unresolved, which means just about no library would + # ever link correctly. If we're not using GNU ld we use -z text + # though, which does catch some bad symbols but isn't as heavy-handed + # as -z defs. + no_undefined_flag_GCJ='${wl}-z,text' + allow_undefined_flag_GCJ='${wl}-z,nodefs' + archive_cmds_need_lc_GCJ=no + hardcode_shlibpath_var_GCJ=no + hardcode_libdir_flag_spec_GCJ='`test -z "$SCOABSPATH" && echo ${wl}-R,$libdir`' + hardcode_libdir_separator_GCJ=':' + link_all_deplibs_GCJ=yes + export_dynamic_flag_spec_GCJ='${wl}-Bexport' + runpath_var='LD_RUN_PATH' + + if test "$GCC" = yes; then + archive_cmds_GCJ='$CC -shared ${wl}-h,\${SCOABSPATH:+${install_libdir}/}$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds_GCJ='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,\${SCOABSPATH:+${install_libdir}/}$soname -o $lib $libobjs $deplibs $compiler_flags' + else + archive_cmds_GCJ='$CC -G ${wl}-h,\${SCOABSPATH:+${install_libdir}/}$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds_GCJ='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,\${SCOABSPATH:+${install_libdir}/}$soname -o $lib $libobjs $deplibs $compiler_flags' + fi + ;; + + uts4*) + archive_cmds_GCJ='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_libdir_flag_spec_GCJ='-L$libdir' + hardcode_shlibpath_var_GCJ=no + ;; + + *) + ld_shlibs_GCJ=no + ;; + esac + fi + +{ $as_echo "$as_me:$LINENO: result: $ld_shlibs_GCJ" >&5 +$as_echo "$ld_shlibs_GCJ" >&6; } +test "$ld_shlibs_GCJ" = no && can_build_shared=no + +# +# Do we need to explicitly link libc? +# +case "x$archive_cmds_need_lc_GCJ" in +x|xyes) + # Assume -lc should be added + archive_cmds_need_lc_GCJ=yes + + if test "$enable_shared" = yes && test "$GCC" = yes; then + case $archive_cmds_GCJ in + *'~'*) + # FIXME: we may have to deal with multi-command sequences. + ;; + '$CC '*) + # Test whether the compiler implicitly links with -lc since on some + # systems, -lgcc has to come before -lc. If gcc already passes -lc + # to ld, don't add -lc before -lgcc. + { $as_echo "$as_me:$LINENO: checking whether -lc should be explicitly linked in" >&5 +$as_echo_n "checking whether -lc should be explicitly linked in... " >&6; } + $rm conftest* + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } 2>conftest.err; then + soname=conftest + lib=conftest + libobjs=conftest.$ac_objext + deplibs= + wl=$lt_prog_compiler_wl_GCJ + pic_flag=$lt_prog_compiler_pic_GCJ + compiler_flags=-v + linker_flags=-v + verstring= + output_objdir=. + libname=conftest + lt_save_allow_undefined_flag=$allow_undefined_flag_GCJ + allow_undefined_flag_GCJ= + if { (eval echo "$as_me:$LINENO: \"$archive_cmds_GCJ 2\>\&1 \| grep \" -lc \" \>/dev/null 2\>\&1\"") >&5 + (eval $archive_cmds_GCJ 2\>\&1 \| grep \" -lc \" \>/dev/null 2\>\&1) 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } + then + archive_cmds_need_lc_GCJ=no + else + archive_cmds_need_lc_GCJ=yes + fi + allow_undefined_flag_GCJ=$lt_save_allow_undefined_flag + else + cat conftest.err 1>&5 + fi + $rm conftest* + { $as_echo "$as_me:$LINENO: result: $archive_cmds_need_lc_GCJ" >&5 +$as_echo "$archive_cmds_need_lc_GCJ" >&6; } + ;; + esac + fi + ;; +esac + +{ $as_echo "$as_me:$LINENO: checking dynamic linker characteristics" >&5 +$as_echo_n "checking dynamic linker characteristics... " >&6; } +library_names_spec= +libname_spec='lib$name' +soname_spec= +shrext_cmds=".so" +postinstall_cmds= +postuninstall_cmds= +finish_cmds= +finish_eval= +shlibpath_var= +shlibpath_overrides_runpath=unknown +version_type=none +dynamic_linker="$host_os ld.so" +sys_lib_dlsearch_path_spec="/lib /usr/lib" + +need_lib_prefix=unknown +hardcode_into_libs=no + +# when you set need_version to no, make sure it does not cause -set_version +# flags to be left without arguments +need_version=unknown + +case $host_os in +aix3*) + version_type=linux + library_names_spec='${libname}${release}${shared_ext}$versuffix $libname.a' + shlibpath_var=LIBPATH + + # AIX 3 has no versioning support, so we append a major version to the name. + soname_spec='${libname}${release}${shared_ext}$major' + ;; + +aix[4-9]*) + version_type=linux + need_lib_prefix=no + need_version=no + hardcode_into_libs=yes + if test "$host_cpu" = ia64; then + # AIX 5 supports IA64 + library_names_spec='${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext}$versuffix $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + else + # With GCC up to 2.95.x, collect2 would create an import file + # for dependence libraries. The import file would start with + # the line `#! .'. This would cause the generated library to + # depend on `.', always an invalid library. This was fixed in + # development snapshots of GCC prior to 3.0. + case $host_os in + aix4 | aix4.[01] | aix4.[01].*) + if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)' + echo ' yes ' + echo '#endif'; } | ${CC} -E - | grep yes > /dev/null; then + : + else + can_build_shared=no + fi + ;; + esac + # AIX (on Power*) has no versioning support, so currently we can not hardcode correct + # soname into executable. Probably we can add versioning support to + # collect2, so additional links can be useful in future. + if test "$aix_use_runtimelinking" = yes; then + # If using run time linking (on AIX 4.2 or later) use lib.so + # instead of lib.a to let people know that these are not + # typical AIX shared libraries. + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + else + # We preserve .a as extension for shared libraries through AIX4.2 + # and later when we are not doing run time linking. + library_names_spec='${libname}${release}.a $libname.a' + soname_spec='${libname}${release}${shared_ext}$major' + fi + shlibpath_var=LIBPATH + fi + ;; + +amigaos*) + library_names_spec='$libname.ixlibrary $libname.a' + # Create ${libname}_ixlibrary.a entries in /sys/libs. + finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`$echo "X$lib" | $Xsed -e '\''s%^.*/\([^/]*\)\.ixlibrary$%\1%'\''`; test $rm /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done' + ;; + +beos*) + library_names_spec='${libname}${shared_ext}' + dynamic_linker="$host_os ld.so" + shlibpath_var=LIBRARY_PATH + ;; + +bsdi[45]*) + version_type=linux + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib" + sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib" + # the default ld.so.conf also contains /usr/contrib/lib and + # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow + # libtool to hard-code these into programs + ;; + +cygwin* | mingw* | pw32*) + version_type=windows + shrext_cmds=".dll" + need_version=no + need_lib_prefix=no + + case $GCC,$host_os in + yes,cygwin* | yes,mingw* | yes,pw32*) + library_names_spec='$libname.dll.a' + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \${file}`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\${base_file}'\''i;echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname~ + chmod a+x \$dldir/$dlname' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $rm \$dlpath' + shlibpath_overrides_runpath=yes + + case $host_os in + cygwin*) + # Cygwin DLLs use 'cyg' prefix rather than 'lib' + soname_spec='`echo ${libname} | sed -e 's/^lib/cyg/'``echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}' + sys_lib_search_path_spec="/usr/lib /lib/w32api /lib /usr/local/lib" + ;; + mingw*) + # MinGW DLLs use traditional 'lib' prefix + soname_spec='${libname}`echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}' + sys_lib_search_path_spec=`$CC -print-search-dirs | grep "^libraries:" | $SED -e "s/^libraries://" -e "s,=/,/,g"` + if echo "$sys_lib_search_path_spec" | grep ';[c-zC-Z]:/' >/dev/null; then + # It is most probably a Windows format PATH printed by + # mingw gcc, but we are running on Cygwin. Gcc prints its search + # path with ; separators, and with drive letters. We can handle the + # drive letters (cygwin fileutils understands them), so leave them, + # especially as we might pass files found there to a mingw objdump, + # which wouldn't understand a cygwinified path. Ahh. + sys_lib_search_path_spec=`echo "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'` + else + sys_lib_search_path_spec=`echo "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + fi + ;; + pw32*) + # pw32 DLLs use 'pw' prefix rather than 'lib' + library_names_spec='`echo ${libname} | sed -e 's/^lib/pw/'``echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}' + ;; + esac + ;; + + *) + library_names_spec='${libname}`echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext} $libname.lib' + ;; + esac + dynamic_linker='Win32 ld.exe' + # FIXME: first we should search . and the directory the executable is in + shlibpath_var=PATH + ;; + +darwin* | rhapsody*) + dynamic_linker="$host_os dyld" + version_type=darwin + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${versuffix}$shared_ext ${libname}${release}${major}$shared_ext ${libname}$shared_ext' + soname_spec='${libname}${release}${major}$shared_ext' + shlibpath_overrides_runpath=yes + shlibpath_var=DYLD_LIBRARY_PATH + shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`' + + sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib' + ;; + +dgux*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname$shared_ext' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +freebsd1*) + dynamic_linker=no + ;; + +freebsd* | dragonfly*) + # DragonFly does not have aout. When/if they implement a new + # versioning mechanism, adjust this. + if test -x /usr/bin/objformat; then + objformat=`/usr/bin/objformat` + else + case $host_os in + freebsd[123]*) objformat=aout ;; + *) objformat=elf ;; + esac + fi + version_type=freebsd-$objformat + case $version_type in + freebsd-elf*) + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext} $libname${shared_ext}' + need_version=no + need_lib_prefix=no + ;; + freebsd-*) + library_names_spec='${libname}${release}${shared_ext}$versuffix $libname${shared_ext}$versuffix' + need_version=yes + ;; + esac + shlibpath_var=LD_LIBRARY_PATH + case $host_os in + freebsd2*) + shlibpath_overrides_runpath=yes + ;; + freebsd3.[01]* | freebsdelf3.[01]*) + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + freebsd3.[2-9]* | freebsdelf3.[2-9]* | \ + freebsd4.[0-5] | freebsdelf4.[0-5] | freebsd4.1.1 | freebsdelf4.1.1) + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + *) # from 4.6 on, and DragonFly + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + esac + ;; + +gnu*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}${major} ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + hardcode_into_libs=yes + ;; + +hpux9* | hpux10* | hpux11*) + # Give a soname corresponding to the major version so that dld.sl refuses to + # link against other versions. + version_type=sunos + need_lib_prefix=no + need_version=no + case $host_cpu in + ia64*) + shrext_cmds='.so' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.so" + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + if test "X$HPUX_IA64_MODE" = X32; then + sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib" + else + sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64" + fi + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + hppa*64*) + shrext_cmds='.sl' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.sl" + shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + *) + shrext_cmds='.sl' + dynamic_linker="$host_os dld.sl" + shlibpath_var=SHLIB_PATH + shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + ;; + esac + # HP-UX runs *really* slowly unless shared libraries are mode 555. + postinstall_cmds='chmod 555 $lib' + ;; + +interix[3-9]*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +irix5* | irix6* | nonstopux*) + case $host_os in + nonstopux*) version_type=nonstopux ;; + *) + if test "$lt_cv_prog_gnu_ld" = yes; then + version_type=linux + else + version_type=irix + fi ;; + esac + need_lib_prefix=no + need_version=no + soname_spec='${libname}${release}${shared_ext}$major' + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext} $libname${shared_ext}' + case $host_os in + irix5* | nonstopux*) + libsuff= shlibsuff= + ;; + *) + case $LD in # libtool.m4 will add one of these switches to LD + *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") + libsuff= shlibsuff= libmagic=32-bit;; + *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") + libsuff=32 shlibsuff=N32 libmagic=N32;; + *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") + libsuff=64 shlibsuff=64 libmagic=64-bit;; + *) libsuff= shlibsuff= libmagic=never-match;; + esac + ;; + esac + shlibpath_var=LD_LIBRARY${shlibsuff}_PATH + shlibpath_overrides_runpath=no + sys_lib_search_path_spec="/usr/lib${libsuff} /lib${libsuff} /usr/local/lib${libsuff}" + sys_lib_dlsearch_path_spec="/usr/lib${libsuff} /lib${libsuff}" + hardcode_into_libs=yes + ;; + +# No shared lib support for Linux oldld, aout, or coff. +linux*oldld* | linux*aout* | linux*coff*) + dynamic_linker=no + ;; + +# This must be Linux ELF. +linux* | k*bsd*-gnu) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + # This implies no fast_install, which is unacceptable. + # Some rework will be needed to allow for fast_install + # before this can be enabled. + hardcode_into_libs=yes + + # Append ld.so.conf contents to the search path + if test -f /etc/ld.so.conf; then + lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \$2)); skip = 1; } { if (!skip) print \$0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[ ]*hwcap[ ]/d;s/[:, ]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;/^$/d' | tr '\n' ' '` + sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra" + fi + + # We used to test for /lib/ld.so.1 and disable shared libraries on + # powerpc, because MkLinux only supported shared libraries with the + # GNU dynamic linker. Since this was broken with cross compilers, + # most powerpc-linux boxes support dynamic linking these days and + # people can always --disable-shared, the test was removed, and we + # assume the GNU/Linux dynamic linker is in use. + dynamic_linker='GNU/Linux ld.so' + ;; + +netbsd*) + version_type=sunos + need_lib_prefix=no + need_version=no + if echo __ELF__ | $CC -E - | grep __ELF__ >/dev/null; then + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + dynamic_linker='NetBSD (a.out) ld.so' + else + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + dynamic_linker='NetBSD ld.elf_so' + fi + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + +newsos6) + version_type=linux + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +nto-qnx*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +openbsd*) + version_type=sunos + sys_lib_dlsearch_path_spec="/usr/lib" + need_lib_prefix=no + # Some older versions of OpenBSD (3.3 at least) *do* need versioned libs. + case $host_os in + openbsd3.3 | openbsd3.3.*) need_version=yes ;; + *) need_version=no ;; + esac + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + shlibpath_var=LD_LIBRARY_PATH + if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then + case $host_os in + openbsd2.[89] | openbsd2.[89].*) + shlibpath_overrides_runpath=no + ;; + *) + shlibpath_overrides_runpath=yes + ;; + esac + else + shlibpath_overrides_runpath=yes + fi + ;; + +os2*) + libname_spec='$name' + shrext_cmds=".dll" + need_lib_prefix=no + library_names_spec='$libname${shared_ext} $libname.a' + dynamic_linker='OS/2 ld.exe' + shlibpath_var=LIBPATH + ;; + +osf3* | osf4* | osf5*) + version_type=osf + need_lib_prefix=no + need_version=no + soname_spec='${libname}${release}${shared_ext}$major' + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib" + sys_lib_dlsearch_path_spec="$sys_lib_search_path_spec" + ;; + +rdos*) + dynamic_linker=no + ;; + +solaris*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + # ldd complains unless libraries are executable + postinstall_cmds='chmod +x $lib' + ;; + +sunos4*) + version_type=sunos + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix' + finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + if test "$with_gnu_ld" = yes; then + need_lib_prefix=no + fi + need_version=yes + ;; + +sysv4 | sysv4.3*) + version_type=linux + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + case $host_vendor in + sni) + shlibpath_overrides_runpath=no + need_lib_prefix=no + export_dynamic_flag_spec='${wl}-Blargedynsym' + runpath_var=LD_RUN_PATH + ;; + siemens) + need_lib_prefix=no + ;; + motorola) + need_lib_prefix=no + need_version=no + shlibpath_overrides_runpath=no + sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib' + ;; + esac + ;; + +sysv4*MP*) + if test -d /usr/nec ;then + version_type=linux + library_names_spec='$libname${shared_ext}.$versuffix $libname${shared_ext}.$major $libname${shared_ext}' + soname_spec='$libname${shared_ext}.$major' + shlibpath_var=LD_LIBRARY_PATH + fi + ;; + +sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + version_type=freebsd-elf + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext} $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + hardcode_into_libs=yes + if test "$with_gnu_ld" = yes; then + sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib' + shlibpath_overrides_runpath=no + else + sys_lib_search_path_spec='/usr/ccs/lib /usr/lib' + shlibpath_overrides_runpath=yes + case $host_os in + sco3.2v5*) + sys_lib_search_path_spec="$sys_lib_search_path_spec /lib" + ;; + esac + fi + sys_lib_dlsearch_path_spec='/usr/lib' + ;; + +uts4*) + version_type=linux + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +*) + dynamic_linker=no + ;; +esac +{ $as_echo "$as_me:$LINENO: result: $dynamic_linker" >&5 +$as_echo "$dynamic_linker" >&6; } +test "$dynamic_linker" = no && can_build_shared=no + +if test "${lt_cv_sys_lib_search_path_spec+set}" = set; then + $as_echo_n "(cached) " >&6 +else + lt_cv_sys_lib_search_path_spec="$sys_lib_search_path_spec" +fi + +sys_lib_search_path_spec="$lt_cv_sys_lib_search_path_spec" +if test "${lt_cv_sys_lib_dlsearch_path_spec+set}" = set; then + $as_echo_n "(cached) " >&6 +else + lt_cv_sys_lib_dlsearch_path_spec="$sys_lib_dlsearch_path_spec" +fi + +sys_lib_dlsearch_path_spec="$lt_cv_sys_lib_dlsearch_path_spec" + +variables_saved_for_relink="PATH $shlibpath_var $runpath_var" +if test "$GCC" = yes; then + variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH" +fi + +{ $as_echo "$as_me:$LINENO: checking how to hardcode library paths into programs" >&5 +$as_echo_n "checking how to hardcode library paths into programs... " >&6; } +hardcode_action_GCJ= +if test -n "$hardcode_libdir_flag_spec_GCJ" || \ + test -n "$runpath_var_GCJ" || \ + test "X$hardcode_automatic_GCJ" = "Xyes" ; then + + # We can hardcode non-existant directories. + if test "$hardcode_direct_GCJ" != no && + # If the only mechanism to avoid hardcoding is shlibpath_var, we + # have to relink, otherwise we might link with an installed library + # when we should be linking with a yet-to-be-installed one + ## test "$_LT_AC_TAGVAR(hardcode_shlibpath_var, GCJ)" != no && + test "$hardcode_minus_L_GCJ" != no; then + # Linking always hardcodes the temporary library directory. + hardcode_action_GCJ=relink + else + # We can link without hardcoding, and we can hardcode nonexisting dirs. + hardcode_action_GCJ=immediate + fi +else + # We cannot hardcode anything, or else we can only hardcode existing + # directories. + hardcode_action_GCJ=unsupported +fi +{ $as_echo "$as_me:$LINENO: result: $hardcode_action_GCJ" >&5 +$as_echo "$hardcode_action_GCJ" >&6; } + +if test "$hardcode_action_GCJ" = relink; then + # Fast installation is not supported + enable_fast_install=no +elif test "$shlibpath_overrides_runpath" = yes || + test "$enable_shared" = no; then + # Fast installation is not necessary + enable_fast_install=needless +fi + + +# The else clause should only fire when bootstrapping the +# libtool distribution, otherwise you forgot to ship ltmain.sh +# with your package, and you will get complaints that there are +# no rules to generate ltmain.sh. +if test -f "$ltmain"; then + # See if we are running on zsh, and set the options which allow our commands through + # without removal of \ escapes. + if test -n "${ZSH_VERSION+set}" ; then + setopt NO_GLOB_SUBST + fi + # Now quote all the things that may contain metacharacters while being + # careful not to overquote the AC_SUBSTed values. We take copies of the + # variables and quote the copies for generation of the libtool script. + for var in echo old_CC old_CFLAGS AR AR_FLAGS EGREP RANLIB LN_S LTCC LTCFLAGS NM \ + SED SHELL STRIP \ + libname_spec library_names_spec soname_spec extract_expsyms_cmds \ + old_striplib striplib file_magic_cmd finish_cmds finish_eval \ + deplibs_check_method reload_flag reload_cmds need_locks \ + lt_cv_sys_global_symbol_pipe lt_cv_sys_global_symbol_to_cdecl \ + lt_cv_sys_global_symbol_to_c_name_address \ + sys_lib_search_path_spec sys_lib_dlsearch_path_spec \ + old_postinstall_cmds old_postuninstall_cmds \ + compiler_GCJ \ + CC_GCJ \ + LD_GCJ \ + lt_prog_compiler_wl_GCJ \ + lt_prog_compiler_pic_GCJ \ + lt_prog_compiler_static_GCJ \ + lt_prog_compiler_no_builtin_flag_GCJ \ + export_dynamic_flag_spec_GCJ \ + thread_safe_flag_spec_GCJ \ + whole_archive_flag_spec_GCJ \ + enable_shared_with_static_runtimes_GCJ \ + old_archive_cmds_GCJ \ + old_archive_from_new_cmds_GCJ \ + predep_objects_GCJ \ + postdep_objects_GCJ \ + predeps_GCJ \ + postdeps_GCJ \ + compiler_lib_search_path_GCJ \ + compiler_lib_search_dirs_GCJ \ + archive_cmds_GCJ \ + archive_expsym_cmds_GCJ \ + postinstall_cmds_GCJ \ + postuninstall_cmds_GCJ \ + old_archive_from_expsyms_cmds_GCJ \ + allow_undefined_flag_GCJ \ + no_undefined_flag_GCJ \ + export_symbols_cmds_GCJ \ + hardcode_libdir_flag_spec_GCJ \ + hardcode_libdir_flag_spec_ld_GCJ \ + hardcode_libdir_separator_GCJ \ + hardcode_automatic_GCJ \ + module_cmds_GCJ \ + module_expsym_cmds_GCJ \ + lt_cv_prog_compiler_c_o_GCJ \ + fix_srcfile_path_GCJ \ + exclude_expsyms_GCJ \ + include_expsyms_GCJ; do + + case $var in + old_archive_cmds_GCJ | \ + old_archive_from_new_cmds_GCJ | \ + archive_cmds_GCJ | \ + archive_expsym_cmds_GCJ | \ + module_cmds_GCJ | \ + module_expsym_cmds_GCJ | \ + old_archive_from_expsyms_cmds_GCJ | \ + export_symbols_cmds_GCJ | \ + extract_expsyms_cmds | reload_cmds | finish_cmds | \ + postinstall_cmds | postuninstall_cmds | \ + old_postinstall_cmds | old_postuninstall_cmds | \ + sys_lib_search_path_spec | sys_lib_dlsearch_path_spec) + # Double-quote double-evaled strings. + eval "lt_$var=\\\"\`\$echo \"X\$$var\" | \$Xsed -e \"\$double_quote_subst\" -e \"\$sed_quote_subst\" -e \"\$delay_variable_subst\"\`\\\"" + ;; + *) + eval "lt_$var=\\\"\`\$echo \"X\$$var\" | \$Xsed -e \"\$sed_quote_subst\"\`\\\"" + ;; + esac + done + + case $lt_echo in + *'\$0 --fallback-echo"') + lt_echo=`$echo "X$lt_echo" | $Xsed -e 's/\\\\\\\$0 --fallback-echo"$/$0 --fallback-echo"/'` + ;; + esac + +cfgfile="$ofile" + + cat <<__EOF__ >> "$cfgfile" +# ### BEGIN LIBTOOL TAG CONFIG: $tagname + +# Libtool was configured on host `(hostname || uname -n) 2>/dev/null | sed 1q`: + +# Shell to use when invoking shell scripts. +SHELL=$lt_SHELL + +# Whether or not to build shared libraries. +build_libtool_libs=$enable_shared + +# Whether or not to build static libraries. +build_old_libs=$enable_static + +# Whether or not to add -lc for building shared libraries. +build_libtool_need_lc=$archive_cmds_need_lc_GCJ + +# Whether or not to disallow shared libs when runtime libs are static +allow_libtool_libs_with_static_runtimes=$enable_shared_with_static_runtimes_GCJ + +# Whether or not to optimize for fast installation. +fast_install=$enable_fast_install + +# The host system. +host_alias=$host_alias +host=$host +host_os=$host_os + +# The build system. +build_alias=$build_alias +build=$build +build_os=$build_os + +# An echo program that does not interpret backslashes. +echo=$lt_echo + +# The archiver. +AR=$lt_AR +AR_FLAGS=$lt_AR_FLAGS + +# A C compiler. +LTCC=$lt_LTCC + +# LTCC compiler flags. +LTCFLAGS=$lt_LTCFLAGS + +# A language-specific compiler. +CC=$lt_compiler_GCJ + +# Is the compiler the GNU C compiler? +with_gcc=$GCC_GCJ + +# An ERE matcher. +EGREP=$lt_EGREP + +# The linker used to build libraries. +LD=$lt_LD_GCJ + +# Whether we need hard or soft links. +LN_S=$lt_LN_S + +# A BSD-compatible nm program. +NM=$lt_NM + +# A symbol stripping program +STRIP=$lt_STRIP + +# Used to examine libraries when file_magic_cmd begins "file" +MAGIC_CMD=$MAGIC_CMD + +# Used on cygwin: DLL creation program. +DLLTOOL="$DLLTOOL" + +# Used on cygwin: object dumper. +OBJDUMP="$OBJDUMP" + +# Used on cygwin: assembler. +AS="$AS" + +# The name of the directory that contains temporary libtool files. +objdir=$objdir + +# How to create reloadable object files. +reload_flag=$lt_reload_flag +reload_cmds=$lt_reload_cmds + +# How to pass a linker flag through the compiler. +wl=$lt_lt_prog_compiler_wl_GCJ + +# Object file suffix (normally "o"). +objext="$ac_objext" + +# Old archive suffix (normally "a"). +libext="$libext" + +# Shared library suffix (normally ".so"). +shrext_cmds='$shrext_cmds' + +# Executable file suffix (normally ""). +exeext="$exeext" + +# Additional compiler flags for building library objects. +pic_flag=$lt_lt_prog_compiler_pic_GCJ +pic_mode=$pic_mode + +# What is the maximum length of a command? +max_cmd_len=$lt_cv_sys_max_cmd_len + +# Does compiler simultaneously support -c and -o options? +compiler_c_o=$lt_lt_cv_prog_compiler_c_o_GCJ + +# Must we lock files when doing compilation? +need_locks=$lt_need_locks + +# Do we need the lib prefix for modules? +need_lib_prefix=$need_lib_prefix + +# Do we need a version for libraries? +need_version=$need_version + +# Whether dlopen is supported. +dlopen_support=$enable_dlopen + +# Whether dlopen of programs is supported. +dlopen_self=$enable_dlopen_self + +# Whether dlopen of statically linked programs is supported. +dlopen_self_static=$enable_dlopen_self_static + +# Compiler flag to prevent dynamic linking. +link_static_flag=$lt_lt_prog_compiler_static_GCJ + +# Compiler flag to turn off builtin functions. +no_builtin_flag=$lt_lt_prog_compiler_no_builtin_flag_GCJ + +# Compiler flag to allow reflexive dlopens. +export_dynamic_flag_spec=$lt_export_dynamic_flag_spec_GCJ + +# Compiler flag to generate shared objects directly from archives. +whole_archive_flag_spec=$lt_whole_archive_flag_spec_GCJ + +# Compiler flag to generate thread-safe objects. +thread_safe_flag_spec=$lt_thread_safe_flag_spec_GCJ + +# Library versioning type. +version_type=$version_type + +# Format of library name prefix. +libname_spec=$lt_libname_spec + +# List of archive names. First name is the real one, the rest are links. +# The last name is the one that the linker finds with -lNAME. +library_names_spec=$lt_library_names_spec + +# The coded name of the library, if different from the real name. +soname_spec=$lt_soname_spec + +# Commands used to build and install an old-style archive. +RANLIB=$lt_RANLIB +old_archive_cmds=$lt_old_archive_cmds_GCJ +old_postinstall_cmds=$lt_old_postinstall_cmds +old_postuninstall_cmds=$lt_old_postuninstall_cmds + +# Create an old-style archive from a shared archive. +old_archive_from_new_cmds=$lt_old_archive_from_new_cmds_GCJ + +# Create a temporary old-style archive to link instead of a shared archive. +old_archive_from_expsyms_cmds=$lt_old_archive_from_expsyms_cmds_GCJ + +# Commands used to build and install a shared archive. +archive_cmds=$lt_archive_cmds_GCJ +archive_expsym_cmds=$lt_archive_expsym_cmds_GCJ +postinstall_cmds=$lt_postinstall_cmds +postuninstall_cmds=$lt_postuninstall_cmds + +# Commands used to build a loadable module (assumed same as above if empty) +module_cmds=$lt_module_cmds_GCJ +module_expsym_cmds=$lt_module_expsym_cmds_GCJ + +# Commands to strip libraries. +old_striplib=$lt_old_striplib +striplib=$lt_striplib + +# Dependencies to place before the objects being linked to create a +# shared library. +predep_objects=$lt_predep_objects_GCJ + +# Dependencies to place after the objects being linked to create a +# shared library. +postdep_objects=$lt_postdep_objects_GCJ + +# Dependencies to place before the objects being linked to create a +# shared library. +predeps=$lt_predeps_GCJ + +# Dependencies to place after the objects being linked to create a +# shared library. +postdeps=$lt_postdeps_GCJ + +# The directories searched by this compiler when creating a shared +# library +compiler_lib_search_dirs=$lt_compiler_lib_search_dirs_GCJ + +# The library search path used internally by the compiler when linking +# a shared library. +compiler_lib_search_path=$lt_compiler_lib_search_path_GCJ + +# Method to check whether dependent libraries are shared objects. +deplibs_check_method=$lt_deplibs_check_method + +# Command to use when deplibs_check_method == file_magic. +file_magic_cmd=$lt_file_magic_cmd + +# Flag that allows shared libraries with undefined symbols to be built. +allow_undefined_flag=$lt_allow_undefined_flag_GCJ + +# Flag that forces no undefined symbols. +no_undefined_flag=$lt_no_undefined_flag_GCJ + +# Commands used to finish a libtool library installation in a directory. +finish_cmds=$lt_finish_cmds + +# Same as above, but a single script fragment to be evaled but not shown. +finish_eval=$lt_finish_eval + +# Take the output of nm and produce a listing of raw symbols and C names. +global_symbol_pipe=$lt_lt_cv_sys_global_symbol_pipe + +# Transform the output of nm in a proper C declaration +global_symbol_to_cdecl=$lt_lt_cv_sys_global_symbol_to_cdecl + +# Transform the output of nm in a C name address pair +global_symbol_to_c_name_address=$lt_lt_cv_sys_global_symbol_to_c_name_address + +# This is the shared library runtime path variable. +runpath_var=$runpath_var + +# This is the shared library path variable. +shlibpath_var=$shlibpath_var + +# Is shlibpath searched before the hard-coded library search path? +shlibpath_overrides_runpath=$shlibpath_overrides_runpath + +# How to hardcode a shared library path into an executable. +hardcode_action=$hardcode_action_GCJ + +# Whether we should hardcode library paths into libraries. +hardcode_into_libs=$hardcode_into_libs + +# Flag to hardcode \$libdir into a binary during linking. +# This must work even if \$libdir does not exist. +hardcode_libdir_flag_spec=$lt_hardcode_libdir_flag_spec_GCJ + +# If ld is used when linking, flag to hardcode \$libdir into +# a binary during linking. This must work even if \$libdir does +# not exist. +hardcode_libdir_flag_spec_ld=$lt_hardcode_libdir_flag_spec_ld_GCJ + +# Whether we need a single -rpath flag with a separated argument. +hardcode_libdir_separator=$lt_hardcode_libdir_separator_GCJ + +# Set to yes if using DIR/libNAME${shared_ext} during linking hardcodes DIR into the +# resulting binary. +hardcode_direct=$hardcode_direct_GCJ + +# Set to yes if using the -LDIR flag during linking hardcodes DIR into the +# resulting binary. +hardcode_minus_L=$hardcode_minus_L_GCJ + +# Set to yes if using SHLIBPATH_VAR=DIR during linking hardcodes DIR into +# the resulting binary. +hardcode_shlibpath_var=$hardcode_shlibpath_var_GCJ + +# Set to yes if building a shared library automatically hardcodes DIR into the library +# and all subsequent libraries and executables linked against it. +hardcode_automatic=$hardcode_automatic_GCJ + +# Variables whose values should be saved in libtool wrapper scripts and +# restored at relink time. +variables_saved_for_relink="$variables_saved_for_relink" + +# Whether libtool must link a program against all its dependency libraries. +link_all_deplibs=$link_all_deplibs_GCJ + +# Compile-time system search path for libraries +sys_lib_search_path_spec=$lt_sys_lib_search_path_spec + +# Run-time system search path for libraries +sys_lib_dlsearch_path_spec=$lt_sys_lib_dlsearch_path_spec + +# Fix the shell variable \$srcfile for the compiler. +fix_srcfile_path=$lt_fix_srcfile_path + +# Set to yes if exported symbols are required. +always_export_symbols=$always_export_symbols_GCJ + +# The commands to list exported symbols. +export_symbols_cmds=$lt_export_symbols_cmds_GCJ + +# The commands to extract the exported symbol list from a shared archive. +extract_expsyms_cmds=$lt_extract_expsyms_cmds + +# Symbols that should not be listed in the preloaded symbols. +exclude_expsyms=$lt_exclude_expsyms_GCJ + +# Symbols that must always be exported. +include_expsyms=$lt_include_expsyms_GCJ + +# ### END LIBTOOL TAG CONFIG: $tagname + +__EOF__ + + +else + # If there is no Makefile yet, we rely on a make rule to execute + # `config.status --recheck' to rerun these tests and create the + # libtool script then. + ltmain_in=`echo $ltmain | sed -e 's/\.sh$/.in/'` + if test -f "$ltmain_in"; then + test -f Makefile && make "$ltmain" + fi +fi + + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +CC="$lt_save_CC" + + else + tagname="" + fi + ;; + + RC) + + +# Source file extension for RC test sources. +ac_ext=rc + +# Object file extension for compiled RC test sources. +objext=o +objext_RC=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code='sample MENU { MENUITEM "&Soup", 100, CHECKED }' + +# Code to be used in simple link tests +lt_simple_link_test_code="$lt_simple_compile_test_code" + +# ltmain only uses $CC for tagged configurations so make sure $CC is set. + +# If no C compiler was specified, use CC. +LTCC=${LTCC-"$CC"} + +# If no C compiler flags were specified, use CFLAGS. +LTCFLAGS=${LTCFLAGS-"$CFLAGS"} + +# Allow CC to be a program name with arguments. +compiler=$CC + + +# save warnings/boilerplate of simple test code +ac_outfile=conftest.$ac_objext +echo "$lt_simple_compile_test_code" >conftest.$ac_ext +eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_compiler_boilerplate=`cat conftest.err` +$rm conftest* + +ac_outfile=conftest.$ac_objext +echo "$lt_simple_link_test_code" >conftest.$ac_ext +eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_linker_boilerplate=`cat conftest.err` +$rm -r conftest* + + +# Allow CC to be a program name with arguments. +lt_save_CC="$CC" +CC=${RC-"windres"} +compiler=$CC +compiler_RC=$CC +for cc_temp in $compiler""; do + case $cc_temp in + compile | *[\\/]compile | ccache | *[\\/]ccache ) ;; + distcc | *[\\/]distcc | purify | *[\\/]purify ) ;; + \-*) ;; + *) break;; + esac +done +cc_basename=`$echo "X$cc_temp" | $Xsed -e 's%.*/%%' -e "s%^$host_alias-%%"` + +lt_cv_prog_compiler_c_o_RC=yes + +# The else clause should only fire when bootstrapping the +# libtool distribution, otherwise you forgot to ship ltmain.sh +# with your package, and you will get complaints that there are +# no rules to generate ltmain.sh. +if test -f "$ltmain"; then + # See if we are running on zsh, and set the options which allow our commands through + # without removal of \ escapes. + if test -n "${ZSH_VERSION+set}" ; then + setopt NO_GLOB_SUBST + fi + # Now quote all the things that may contain metacharacters while being + # careful not to overquote the AC_SUBSTed values. We take copies of the + # variables and quote the copies for generation of the libtool script. + for var in echo old_CC old_CFLAGS AR AR_FLAGS EGREP RANLIB LN_S LTCC LTCFLAGS NM \ + SED SHELL STRIP \ + libname_spec library_names_spec soname_spec extract_expsyms_cmds \ + old_striplib striplib file_magic_cmd finish_cmds finish_eval \ + deplibs_check_method reload_flag reload_cmds need_locks \ + lt_cv_sys_global_symbol_pipe lt_cv_sys_global_symbol_to_cdecl \ + lt_cv_sys_global_symbol_to_c_name_address \ + sys_lib_search_path_spec sys_lib_dlsearch_path_spec \ + old_postinstall_cmds old_postuninstall_cmds \ + compiler_RC \ + CC_RC \ + LD_RC \ + lt_prog_compiler_wl_RC \ + lt_prog_compiler_pic_RC \ + lt_prog_compiler_static_RC \ + lt_prog_compiler_no_builtin_flag_RC \ + export_dynamic_flag_spec_RC \ + thread_safe_flag_spec_RC \ + whole_archive_flag_spec_RC \ + enable_shared_with_static_runtimes_RC \ + old_archive_cmds_RC \ + old_archive_from_new_cmds_RC \ + predep_objects_RC \ + postdep_objects_RC \ + predeps_RC \ + postdeps_RC \ + compiler_lib_search_path_RC \ + compiler_lib_search_dirs_RC \ + archive_cmds_RC \ + archive_expsym_cmds_RC \ + postinstall_cmds_RC \ + postuninstall_cmds_RC \ + old_archive_from_expsyms_cmds_RC \ + allow_undefined_flag_RC \ + no_undefined_flag_RC \ + export_symbols_cmds_RC \ + hardcode_libdir_flag_spec_RC \ + hardcode_libdir_flag_spec_ld_RC \ + hardcode_libdir_separator_RC \ + hardcode_automatic_RC \ + module_cmds_RC \ + module_expsym_cmds_RC \ + lt_cv_prog_compiler_c_o_RC \ + fix_srcfile_path_RC \ + exclude_expsyms_RC \ + include_expsyms_RC; do + + case $var in + old_archive_cmds_RC | \ + old_archive_from_new_cmds_RC | \ + archive_cmds_RC | \ + archive_expsym_cmds_RC | \ + module_cmds_RC | \ + module_expsym_cmds_RC | \ + old_archive_from_expsyms_cmds_RC | \ + export_symbols_cmds_RC | \ + extract_expsyms_cmds | reload_cmds | finish_cmds | \ + postinstall_cmds | postuninstall_cmds | \ + old_postinstall_cmds | old_postuninstall_cmds | \ + sys_lib_search_path_spec | sys_lib_dlsearch_path_spec) + # Double-quote double-evaled strings. + eval "lt_$var=\\\"\`\$echo \"X\$$var\" | \$Xsed -e \"\$double_quote_subst\" -e \"\$sed_quote_subst\" -e \"\$delay_variable_subst\"\`\\\"" + ;; + *) + eval "lt_$var=\\\"\`\$echo \"X\$$var\" | \$Xsed -e \"\$sed_quote_subst\"\`\\\"" + ;; + esac + done + + case $lt_echo in + *'\$0 --fallback-echo"') + lt_echo=`$echo "X$lt_echo" | $Xsed -e 's/\\\\\\\$0 --fallback-echo"$/$0 --fallback-echo"/'` + ;; + esac + +cfgfile="$ofile" + + cat <<__EOF__ >> "$cfgfile" +# ### BEGIN LIBTOOL TAG CONFIG: $tagname + +# Libtool was configured on host `(hostname || uname -n) 2>/dev/null | sed 1q`: + +# Shell to use when invoking shell scripts. +SHELL=$lt_SHELL + +# Whether or not to build shared libraries. +build_libtool_libs=$enable_shared + +# Whether or not to build static libraries. +build_old_libs=$enable_static + +# Whether or not to add -lc for building shared libraries. +build_libtool_need_lc=$archive_cmds_need_lc_RC + +# Whether or not to disallow shared libs when runtime libs are static +allow_libtool_libs_with_static_runtimes=$enable_shared_with_static_runtimes_RC + +# Whether or not to optimize for fast installation. +fast_install=$enable_fast_install + +# The host system. +host_alias=$host_alias +host=$host +host_os=$host_os + +# The build system. +build_alias=$build_alias +build=$build +build_os=$build_os + +# An echo program that does not interpret backslashes. +echo=$lt_echo + +# The archiver. +AR=$lt_AR +AR_FLAGS=$lt_AR_FLAGS + +# A C compiler. +LTCC=$lt_LTCC + +# LTCC compiler flags. +LTCFLAGS=$lt_LTCFLAGS + +# A language-specific compiler. +CC=$lt_compiler_RC + +# Is the compiler the GNU C compiler? +with_gcc=$GCC_RC + +# An ERE matcher. +EGREP=$lt_EGREP + +# The linker used to build libraries. +LD=$lt_LD_RC + +# Whether we need hard or soft links. +LN_S=$lt_LN_S + +# A BSD-compatible nm program. +NM=$lt_NM + +# A symbol stripping program +STRIP=$lt_STRIP + +# Used to examine libraries when file_magic_cmd begins "file" +MAGIC_CMD=$MAGIC_CMD + +# Used on cygwin: DLL creation program. +DLLTOOL="$DLLTOOL" + +# Used on cygwin: object dumper. +OBJDUMP="$OBJDUMP" + +# Used on cygwin: assembler. +AS="$AS" + +# The name of the directory that contains temporary libtool files. +objdir=$objdir + +# How to create reloadable object files. +reload_flag=$lt_reload_flag +reload_cmds=$lt_reload_cmds + +# How to pass a linker flag through the compiler. +wl=$lt_lt_prog_compiler_wl_RC + +# Object file suffix (normally "o"). +objext="$ac_objext" + +# Old archive suffix (normally "a"). +libext="$libext" + +# Shared library suffix (normally ".so"). +shrext_cmds='$shrext_cmds' + +# Executable file suffix (normally ""). +exeext="$exeext" + +# Additional compiler flags for building library objects. +pic_flag=$lt_lt_prog_compiler_pic_RC +pic_mode=$pic_mode + +# What is the maximum length of a command? +max_cmd_len=$lt_cv_sys_max_cmd_len + +# Does compiler simultaneously support -c and -o options? +compiler_c_o=$lt_lt_cv_prog_compiler_c_o_RC + +# Must we lock files when doing compilation? +need_locks=$lt_need_locks + +# Do we need the lib prefix for modules? +need_lib_prefix=$need_lib_prefix + +# Do we need a version for libraries? +need_version=$need_version + +# Whether dlopen is supported. +dlopen_support=$enable_dlopen + +# Whether dlopen of programs is supported. +dlopen_self=$enable_dlopen_self + +# Whether dlopen of statically linked programs is supported. +dlopen_self_static=$enable_dlopen_self_static + +# Compiler flag to prevent dynamic linking. +link_static_flag=$lt_lt_prog_compiler_static_RC + +# Compiler flag to turn off builtin functions. +no_builtin_flag=$lt_lt_prog_compiler_no_builtin_flag_RC + +# Compiler flag to allow reflexive dlopens. +export_dynamic_flag_spec=$lt_export_dynamic_flag_spec_RC + +# Compiler flag to generate shared objects directly from archives. +whole_archive_flag_spec=$lt_whole_archive_flag_spec_RC + +# Compiler flag to generate thread-safe objects. +thread_safe_flag_spec=$lt_thread_safe_flag_spec_RC + +# Library versioning type. +version_type=$version_type + +# Format of library name prefix. +libname_spec=$lt_libname_spec + +# List of archive names. First name is the real one, the rest are links. +# The last name is the one that the linker finds with -lNAME. +library_names_spec=$lt_library_names_spec + +# The coded name of the library, if different from the real name. +soname_spec=$lt_soname_spec + +# Commands used to build and install an old-style archive. +RANLIB=$lt_RANLIB +old_archive_cmds=$lt_old_archive_cmds_RC +old_postinstall_cmds=$lt_old_postinstall_cmds +old_postuninstall_cmds=$lt_old_postuninstall_cmds + +# Create an old-style archive from a shared archive. +old_archive_from_new_cmds=$lt_old_archive_from_new_cmds_RC + +# Create a temporary old-style archive to link instead of a shared archive. +old_archive_from_expsyms_cmds=$lt_old_archive_from_expsyms_cmds_RC + +# Commands used to build and install a shared archive. +archive_cmds=$lt_archive_cmds_RC +archive_expsym_cmds=$lt_archive_expsym_cmds_RC +postinstall_cmds=$lt_postinstall_cmds +postuninstall_cmds=$lt_postuninstall_cmds + +# Commands used to build a loadable module (assumed same as above if empty) +module_cmds=$lt_module_cmds_RC +module_expsym_cmds=$lt_module_expsym_cmds_RC + +# Commands to strip libraries. +old_striplib=$lt_old_striplib +striplib=$lt_striplib + +# Dependencies to place before the objects being linked to create a +# shared library. +predep_objects=$lt_predep_objects_RC + +# Dependencies to place after the objects being linked to create a +# shared library. +postdep_objects=$lt_postdep_objects_RC + +# Dependencies to place before the objects being linked to create a +# shared library. +predeps=$lt_predeps_RC + +# Dependencies to place after the objects being linked to create a +# shared library. +postdeps=$lt_postdeps_RC + +# The directories searched by this compiler when creating a shared +# library +compiler_lib_search_dirs=$lt_compiler_lib_search_dirs_RC + +# The library search path used internally by the compiler when linking +# a shared library. +compiler_lib_search_path=$lt_compiler_lib_search_path_RC + +# Method to check whether dependent libraries are shared objects. +deplibs_check_method=$lt_deplibs_check_method + +# Command to use when deplibs_check_method == file_magic. +file_magic_cmd=$lt_file_magic_cmd + +# Flag that allows shared libraries with undefined symbols to be built. +allow_undefined_flag=$lt_allow_undefined_flag_RC + +# Flag that forces no undefined symbols. +no_undefined_flag=$lt_no_undefined_flag_RC + +# Commands used to finish a libtool library installation in a directory. +finish_cmds=$lt_finish_cmds + +# Same as above, but a single script fragment to be evaled but not shown. +finish_eval=$lt_finish_eval + +# Take the output of nm and produce a listing of raw symbols and C names. +global_symbol_pipe=$lt_lt_cv_sys_global_symbol_pipe + +# Transform the output of nm in a proper C declaration +global_symbol_to_cdecl=$lt_lt_cv_sys_global_symbol_to_cdecl + +# Transform the output of nm in a C name address pair +global_symbol_to_c_name_address=$lt_lt_cv_sys_global_symbol_to_c_name_address + +# This is the shared library runtime path variable. +runpath_var=$runpath_var + +# This is the shared library path variable. +shlibpath_var=$shlibpath_var + +# Is shlibpath searched before the hard-coded library search path? +shlibpath_overrides_runpath=$shlibpath_overrides_runpath + +# How to hardcode a shared library path into an executable. +hardcode_action=$hardcode_action_RC + +# Whether we should hardcode library paths into libraries. +hardcode_into_libs=$hardcode_into_libs + +# Flag to hardcode \$libdir into a binary during linking. +# This must work even if \$libdir does not exist. +hardcode_libdir_flag_spec=$lt_hardcode_libdir_flag_spec_RC + +# If ld is used when linking, flag to hardcode \$libdir into +# a binary during linking. This must work even if \$libdir does +# not exist. +hardcode_libdir_flag_spec_ld=$lt_hardcode_libdir_flag_spec_ld_RC + +# Whether we need a single -rpath flag with a separated argument. +hardcode_libdir_separator=$lt_hardcode_libdir_separator_RC + +# Set to yes if using DIR/libNAME${shared_ext} during linking hardcodes DIR into the +# resulting binary. +hardcode_direct=$hardcode_direct_RC + +# Set to yes if using the -LDIR flag during linking hardcodes DIR into the +# resulting binary. +hardcode_minus_L=$hardcode_minus_L_RC + +# Set to yes if using SHLIBPATH_VAR=DIR during linking hardcodes DIR into +# the resulting binary. +hardcode_shlibpath_var=$hardcode_shlibpath_var_RC + +# Set to yes if building a shared library automatically hardcodes DIR into the library +# and all subsequent libraries and executables linked against it. +hardcode_automatic=$hardcode_automatic_RC + +# Variables whose values should be saved in libtool wrapper scripts and +# restored at relink time. +variables_saved_for_relink="$variables_saved_for_relink" + +# Whether libtool must link a program against all its dependency libraries. +link_all_deplibs=$link_all_deplibs_RC + +# Compile-time system search path for libraries +sys_lib_search_path_spec=$lt_sys_lib_search_path_spec + +# Run-time system search path for libraries +sys_lib_dlsearch_path_spec=$lt_sys_lib_dlsearch_path_spec + +# Fix the shell variable \$srcfile for the compiler. +fix_srcfile_path=$lt_fix_srcfile_path + +# Set to yes if exported symbols are required. +always_export_symbols=$always_export_symbols_RC + +# The commands to list exported symbols. +export_symbols_cmds=$lt_export_symbols_cmds_RC + +# The commands to extract the exported symbol list from a shared archive. +extract_expsyms_cmds=$lt_extract_expsyms_cmds + +# Symbols that should not be listed in the preloaded symbols. +exclude_expsyms=$lt_exclude_expsyms_RC + +# Symbols that must always be exported. +include_expsyms=$lt_include_expsyms_RC + +# ### END LIBTOOL TAG CONFIG: $tagname + +__EOF__ + + +else + # If there is no Makefile yet, we rely on a make rule to execute + # `config.status --recheck' to rerun these tests and create the + # libtool script then. + ltmain_in=`echo $ltmain | sed -e 's/\.sh$/.in/'` + if test -f "$ltmain_in"; then + test -f Makefile && make "$ltmain" + fi +fi + + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +CC="$lt_save_CC" + + ;; + + *) + { { $as_echo "$as_me:$LINENO: error: Unsupported tag name: $tagname" >&5 +$as_echo "$as_me: error: Unsupported tag name: $tagname" >&2;} + { (exit 1); exit 1; }; } + ;; + esac + + # Append the new tag name to the list of available tags. + if test -n "$tagname" ; then + available_tags="$available_tags $tagname" + fi + fi + done + IFS="$lt_save_ifs" + + # Now substitute the updated list of available tags. + if eval "sed -e 's/^available_tags=.*\$/available_tags=\"$available_tags\"/' \"$ofile\" > \"${ofile}T\""; then + mv "${ofile}T" "$ofile" + chmod +x "$ofile" + else + rm -f "${ofile}T" + { { $as_echo "$as_me:$LINENO: error: unable to update list of available tagged configurations." >&5 +$as_echo "$as_me: error: unable to update list of available tagged configurations." >&2;} + { (exit 1); exit 1; }; } + fi +fi + + + +# This can be used to rebuild libtool when needed +LIBTOOL_DEPS="$ac_aux_dir/ltmain.sh" + +# Always use our own libtool. +LIBTOOL='$(SHELL) $(top_builddir)/libtool' + +# Prevent multiple expansion + + + + + + + + + + + + + + + + + + + + + + + + +{ $as_echo "$as_me:$LINENO: checking for socket in -lsocket" >&5 +$as_echo_n "checking for socket in -lsocket... " >&6; } +if test "${ac_cv_lib_socket_socket+set}" = set; then + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lsocket $LIBS" +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char socket (); +int +main () +{ +return socket (); + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + $as_test_x conftest$ac_exeext + }; then + ac_cv_lib_socket_socket=yes +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_cv_lib_socket_socket=no +fi + +rm -rf conftest.dSYM +rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_lib_socket_socket" >&5 +$as_echo "$ac_cv_lib_socket_socket" >&6; } +if test "x$ac_cv_lib_socket_socket" = x""yes; then + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBSOCKET 1 +_ACEOF + + LIBS="-lsocket $LIBS" + +fi + + +{ $as_echo "$as_me:$LINENO: checking for inet_aton in -lresolv" >&5 +$as_echo_n "checking for inet_aton in -lresolv... " >&6; } +if test "${ac_cv_lib_resolv_inet_aton+set}" = set; then + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lresolv $LIBS" +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char inet_aton (); +int +main () +{ +return inet_aton (); + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + $as_test_x conftest$ac_exeext + }; then + ac_cv_lib_resolv_inet_aton=yes +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_cv_lib_resolv_inet_aton=no +fi + +rm -rf conftest.dSYM +rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_lib_resolv_inet_aton" >&5 +$as_echo "$ac_cv_lib_resolv_inet_aton" >&6; } +if test "x$ac_cv_lib_resolv_inet_aton" = x""yes; then + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBRESOLV 1 +_ACEOF + + LIBS="-lresolv $LIBS" + +fi + + +{ $as_echo "$as_me:$LINENO: checking for clock_gettime in -lrt" >&5 +$as_echo_n "checking for clock_gettime in -lrt... " >&6; } +if test "${ac_cv_lib_rt_clock_gettime+set}" = set; then + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lrt $LIBS" +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char clock_gettime (); +int +main () +{ +return clock_gettime (); + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + $as_test_x conftest$ac_exeext + }; then + ac_cv_lib_rt_clock_gettime=yes +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_cv_lib_rt_clock_gettime=no +fi + +rm -rf conftest.dSYM +rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_lib_rt_clock_gettime" >&5 +$as_echo "$ac_cv_lib_rt_clock_gettime" >&6; } +if test "x$ac_cv_lib_rt_clock_gettime" = x""yes; then + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBRT 1 +_ACEOF + + LIBS="-lrt $LIBS" + +fi + + +{ $as_echo "$as_me:$LINENO: checking for inet_ntoa in -lnsl" >&5 +$as_echo_n "checking for inet_ntoa in -lnsl... " >&6; } +if test "${ac_cv_lib_nsl_inet_ntoa+set}" = set; then + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lnsl $LIBS" +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char inet_ntoa (); +int +main () +{ +return inet_ntoa (); + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + $as_test_x conftest$ac_exeext + }; then + ac_cv_lib_nsl_inet_ntoa=yes +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_cv_lib_nsl_inet_ntoa=no +fi + +rm -rf conftest.dSYM +rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_lib_nsl_inet_ntoa" >&5 +$as_echo "$ac_cv_lib_nsl_inet_ntoa" >&6; } +if test "x$ac_cv_lib_nsl_inet_ntoa" = x""yes; then + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBNSL 1 +_ACEOF + + LIBS="-lnsl $LIBS" + +fi + + +{ $as_echo "$as_me:$LINENO: checking for ANSI C header files" >&5 +$as_echo_n "checking for ANSI C header files... " >&6; } +if test "${ac_cv_header_stdc+set}" = set; then + $as_echo_n "(cached) " >&6 +else + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#include +#include +#include +#include + +int +main () +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_cv_header_stdc=yes +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_cv_header_stdc=no +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +if test $ac_cv_header_stdc = yes; then + # SunOS 4.x string.h does not declare mem*, contrary to ANSI. + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "memchr" >/dev/null 2>&1; then + : +else + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "free" >/dev/null 2>&1; then + : +else + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. + if test "$cross_compiling" = yes; then + : +else + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#include +#include +#if ((' ' & 0x0FF) == 0x020) +# define ISLOWER(c) ('a' <= (c) && (c) <= 'z') +# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) +#else +# define ISLOWER(c) \ + (('a' <= (c) && (c) <= 'i') \ + || ('j' <= (c) && (c) <= 'r') \ + || ('s' <= (c) && (c) <= 'z')) +# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c)) +#endif + +#define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) +int +main () +{ + int i; + for (i = 0; i < 256; i++) + if (XOR (islower (i), ISLOWER (i)) + || toupper (i) != TOUPPER (i)) + return 2; + return 0; +} +_ACEOF +rm -f conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { ac_try='./conftest$ac_exeext' + { (case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; }; then + : +else + $as_echo "$as_me: program exited with status $ac_status" >&5 +$as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +( exit $ac_status ) +ac_cv_header_stdc=no +fi +rm -rf conftest.dSYM +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext conftest.$ac_objext conftest.$ac_ext +fi + + +fi +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_header_stdc" >&5 +$as_echo "$ac_cv_header_stdc" >&6; } +if test $ac_cv_header_stdc = yes; then + +cat >>confdefs.h <<\_ACEOF +#define STDC_HEADERS 1 +_ACEOF + +fi + + + + + + + + + + + + + + + + + + + +for ac_header in fcntl.h stdarg.h inttypes.h stdint.h poll.h signal.h unistd.h sys/epoll.h sys/time.h sys/queue.h sys/event.h sys/param.h sys/ioctl.h sys/select.h sys/devpoll.h port.h netinet/in6.h sys/socket.h +do +as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` +if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then + { $as_echo "$as_me:$LINENO: checking for $ac_header" >&5 +$as_echo_n "checking for $ac_header... " >&6; } +if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then + $as_echo_n "(cached) " >&6 +fi +ac_res=`eval 'as_val=${'$as_ac_Header'} + $as_echo "$as_val"'` + { $as_echo "$as_me:$LINENO: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +else + # Is the header compilable? +{ $as_echo "$as_me:$LINENO: checking $ac_header usability" >&5 +$as_echo_n "checking $ac_header usability... " >&6; } +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +#include <$ac_header> +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_header_compiler=yes +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_header_compiler=no +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +{ $as_echo "$as_me:$LINENO: result: $ac_header_compiler" >&5 +$as_echo "$ac_header_compiler" >&6; } + +# Is the header present? +{ $as_echo "$as_me:$LINENO: checking $ac_header presence" >&5 +$as_echo_n "checking $ac_header presence... " >&6; } +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#include <$ac_header> +_ACEOF +if { (ac_try="$ac_cpp conftest.$ac_ext" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } >/dev/null && { + test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || + test ! -s conftest.err + }; then + ac_header_preproc=yes +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_header_preproc=no +fi + +rm -f conftest.err conftest.$ac_ext +{ $as_echo "$as_me:$LINENO: result: $ac_header_preproc" >&5 +$as_echo "$ac_header_preproc" >&6; } + +# So? What about this header? +case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in + yes:no: ) + { $as_echo "$as_me:$LINENO: WARNING: $ac_header: accepted by the compiler, rejected by the preprocessor!" >&5 +$as_echo "$as_me: WARNING: $ac_header: accepted by the compiler, rejected by the preprocessor!" >&2;} + { $as_echo "$as_me:$LINENO: WARNING: $ac_header: proceeding with the compiler's result" >&5 +$as_echo "$as_me: WARNING: $ac_header: proceeding with the compiler's result" >&2;} + ac_header_preproc=yes + ;; + no:yes:* ) + { $as_echo "$as_me:$LINENO: WARNING: $ac_header: present but cannot be compiled" >&5 +$as_echo "$as_me: WARNING: $ac_header: present but cannot be compiled" >&2;} + { $as_echo "$as_me:$LINENO: WARNING: $ac_header: check for missing prerequisite headers?" >&5 +$as_echo "$as_me: WARNING: $ac_header: check for missing prerequisite headers?" >&2;} + { $as_echo "$as_me:$LINENO: WARNING: $ac_header: see the Autoconf documentation" >&5 +$as_echo "$as_me: WARNING: $ac_header: see the Autoconf documentation" >&2;} + { $as_echo "$as_me:$LINENO: WARNING: $ac_header: section \"Present But Cannot Be Compiled\"" >&5 +$as_echo "$as_me: WARNING: $ac_header: section \"Present But Cannot Be Compiled\"" >&2;} + { $as_echo "$as_me:$LINENO: WARNING: $ac_header: proceeding with the preprocessor's result" >&5 +$as_echo "$as_me: WARNING: $ac_header: proceeding with the preprocessor's result" >&2;} + { $as_echo "$as_me:$LINENO: WARNING: $ac_header: in the future, the compiler will take precedence" >&5 +$as_echo "$as_me: WARNING: $ac_header: in the future, the compiler will take precedence" >&2;} + + ;; +esac +{ $as_echo "$as_me:$LINENO: checking for $ac_header" >&5 +$as_echo_n "checking for $ac_header... " >&6; } +if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then + $as_echo_n "(cached) " >&6 +else + eval "$as_ac_Header=\$ac_header_preproc" +fi +ac_res=`eval 'as_val=${'$as_ac_Header'} + $as_echo "$as_val"'` + { $as_echo "$as_me:$LINENO: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + +fi +as_val=`eval 'as_val=${'$as_ac_Header'} + $as_echo "$as_val"'` + if test "x$as_val" = x""yes; then + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 +_ACEOF + +fi + +done + +if test "x$ac_cv_header_sys_queue_h" = "xyes"; then + { $as_echo "$as_me:$LINENO: checking for TAILQ_FOREACH in sys/queue.h" >&5 +$as_echo_n "checking for TAILQ_FOREACH in sys/queue.h... " >&6; } + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +#include +#ifdef TAILQ_FOREACH + yes +#endif + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "yes" >/dev/null 2>&1; then + { $as_echo "$as_me:$LINENO: result: yes" >&5 +$as_echo "yes" >&6; } + +cat >>confdefs.h <<\_ACEOF +#define HAVE_TAILQFOREACH 1 +_ACEOF + +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } + +fi +rm -f conftest* + +fi + +if test "x$ac_cv_header_sys_time_h" = "xyes"; then + { $as_echo "$as_me:$LINENO: checking for timeradd in sys/time.h" >&5 +$as_echo_n "checking for timeradd in sys/time.h... " >&6; } + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +#include +#ifdef timeradd + yes +#endif + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "yes" >/dev/null 2>&1; then + +cat >>confdefs.h <<\_ACEOF +#define HAVE_TIMERADD 1 +_ACEOF + + { $as_echo "$as_me:$LINENO: result: yes" >&5 +$as_echo "yes" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } + +fi +rm -f conftest* + +fi + +if test "x$ac_cv_header_sys_time_h" = "xyes"; then + { $as_echo "$as_me:$LINENO: checking for timercmp in sys/time.h" >&5 +$as_echo_n "checking for timercmp in sys/time.h... " >&6; } + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +#include +#ifdef timercmp + yes +#endif + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "yes" >/dev/null 2>&1; then + +cat >>confdefs.h <<\_ACEOF +#define HAVE_TIMERCMP 1 +_ACEOF + + { $as_echo "$as_me:$LINENO: result: yes" >&5 +$as_echo "yes" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } + +fi +rm -f conftest* + +fi + +if test "x$ac_cv_header_sys_time_h" = "xyes"; then + { $as_echo "$as_me:$LINENO: checking for timerclear in sys/time.h" >&5 +$as_echo_n "checking for timerclear in sys/time.h... " >&6; } + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +#include +#ifdef timerclear + yes +#endif + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "yes" >/dev/null 2>&1; then + +cat >>confdefs.h <<\_ACEOF +#define HAVE_TIMERCLEAR 1 +_ACEOF + + { $as_echo "$as_me:$LINENO: result: yes" >&5 +$as_echo "yes" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } + +fi +rm -f conftest* + +fi + +if test "x$ac_cv_header_sys_time_h" = "xyes"; then + { $as_echo "$as_me:$LINENO: checking for timerisset in sys/time.h" >&5 +$as_echo_n "checking for timerisset in sys/time.h... " >&6; } + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +#include +#ifdef timerisset + yes +#endif + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "yes" >/dev/null 2>&1; then + +cat >>confdefs.h <<\_ACEOF +#define HAVE_TIMERISSET 1 +_ACEOF + + { $as_echo "$as_me:$LINENO: result: yes" >&5 +$as_echo "yes" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } + +fi +rm -f conftest* + +fi + +{ $as_echo "$as_me:$LINENO: checking for WIN32" >&5 +$as_echo_n "checking for WIN32... " >&6; } +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +int +main () +{ + +#ifndef WIN32 +die horribly +#endif + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + bwin32=true; { $as_echo "$as_me:$LINENO: result: yes" >&5 +$as_echo "yes" >&6; } +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + bwin32=false; { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + + if test x$bwin32 = xtrue; then + BUILD_WIN32_TRUE= + BUILD_WIN32_FALSE='#' +else + BUILD_WIN32_TRUE='#' + BUILD_WIN32_FALSE= +fi + + +{ $as_echo "$as_me:$LINENO: checking for an ANSI C-conforming const" >&5 +$as_echo_n "checking for an ANSI C-conforming const... " >&6; } +if test "${ac_cv_c_const+set}" = set; then + $as_echo_n "(cached) " >&6 +else + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +int +main () +{ +/* FIXME: Include the comments suggested by Paul. */ +#ifndef __cplusplus + /* Ultrix mips cc rejects this. */ + typedef int charset[2]; + const charset cs; + /* SunOS 4.1.1 cc rejects this. */ + char const *const *pcpcc; + char **ppc; + /* NEC SVR4.0.2 mips cc rejects this. */ + struct point {int x, y;}; + static struct point const zero = {0,0}; + /* AIX XL C 1.02.0.0 rejects this. + It does not let you subtract one const X* pointer from another in + an arm of an if-expression whose if-part is not a constant + expression */ + const char *g = "string"; + pcpcc = &g + (g ? g-g : 0); + /* HPUX 7.0 cc rejects these. */ + ++pcpcc; + ppc = (char**) pcpcc; + pcpcc = (char const *const *) ppc; + { /* SCO 3.2v4 cc rejects this. */ + char *t; + char const *s = 0 ? (char *) 0 : (char const *) 0; + + *t++ = 0; + if (s) return 0; + } + { /* Someone thinks the Sun supposedly-ANSI compiler will reject this. */ + int x[] = {25, 17}; + const int *foo = &x[0]; + ++foo; + } + { /* Sun SC1.0 ANSI compiler rejects this -- but not the above. */ + typedef const int *iptr; + iptr p = 0; + ++p; + } + { /* AIX XL C 1.02.0.0 rejects this saying + "k.c", line 2.27: 1506-025 (S) Operand must be a modifiable lvalue. */ + struct s { int j; const int *ap[3]; }; + struct s *b; b->j = 5; + } + { /* ULTRIX-32 V3.1 (Rev 9) vcc rejects this */ + const int foo = 10; + if (!foo) return 0; + } + return !cs[0] && !zero.x; +#endif + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_cv_c_const=yes +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_cv_c_const=no +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_c_const" >&5 +$as_echo "$ac_cv_c_const" >&6; } +if test $ac_cv_c_const = no; then + +cat >>confdefs.h <<\_ACEOF +#define const /**/ +_ACEOF + +fi + +{ $as_echo "$as_me:$LINENO: checking for inline" >&5 +$as_echo_n "checking for inline... " >&6; } +if test "${ac_cv_c_inline+set}" = set; then + $as_echo_n "(cached) " >&6 +else + ac_cv_c_inline=no +for ac_kw in inline __inline__ __inline; do + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#ifndef __cplusplus +typedef int foo_t; +static $ac_kw foo_t static_foo () {return 0; } +$ac_kw foo_t foo () {return 0; } +#endif + +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_cv_c_inline=$ac_kw +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + test "$ac_cv_c_inline" != no && break +done + +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_c_inline" >&5 +$as_echo "$ac_cv_c_inline" >&6; } + + +case $ac_cv_c_inline in + inline | yes) ;; + *) + case $ac_cv_c_inline in + no) ac_val=;; + *) ac_val=$ac_cv_c_inline;; + esac + cat >>confdefs.h <<_ACEOF +#ifndef __cplusplus +#define inline $ac_val +#endif +_ACEOF + ;; +esac + +{ $as_echo "$as_me:$LINENO: checking whether time.h and sys/time.h may both be included" >&5 +$as_echo_n "checking whether time.h and sys/time.h may both be included... " >&6; } +if test "${ac_cv_header_time+set}" = set; then + $as_echo_n "(cached) " >&6 +else + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#include +#include +#include + +int +main () +{ +if ((struct tm *) 0) +return 0; + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_cv_header_time=yes +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_cv_header_time=no +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_header_time" >&5 +$as_echo "$ac_cv_header_time" >&6; } +if test $ac_cv_header_time = yes; then + +cat >>confdefs.h <<\_ACEOF +#define TIME_WITH_SYS_TIME 1 +_ACEOF + +fi + + + + + + + + + + + + + + + + + + +for ac_func in gettimeofday vasprintf fcntl clock_gettime strtok_r strsep getaddrinfo getnameinfo strlcpy inet_ntop signal sigaction strtoll issetugid geteuid getegid +do +as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` +{ $as_echo "$as_me:$LINENO: checking for $ac_func" >&5 +$as_echo_n "checking for $ac_func... " >&6; } +if { as_var=$as_ac_var; eval "test \"\${$as_var+set}\" = set"; }; then + $as_echo_n "(cached) " >&6 +else + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +/* Define $ac_func to an innocuous variant, in case declares $ac_func. + For example, HP-UX 11i declares gettimeofday. */ +#define $ac_func innocuous_$ac_func + +/* System header to define __stub macros and hopefully few prototypes, + which can conflict with char $ac_func (); below. + Prefer to if __STDC__ is defined, since + exists even on freestanding compilers. */ + +#ifdef __STDC__ +# include +#else +# include +#endif + +#undef $ac_func + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char $ac_func (); +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined __stub_$ac_func || defined __stub___$ac_func +choke me +#endif + +int +main () +{ +return $ac_func (); + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + $as_test_x conftest$ac_exeext + }; then + eval "$as_ac_var=yes" +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + eval "$as_ac_var=no" +fi + +rm -rf conftest.dSYM +rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ + conftest$ac_exeext conftest.$ac_ext +fi +ac_res=`eval 'as_val=${'$as_ac_var'} + $as_echo "$as_val"'` + { $as_echo "$as_me:$LINENO: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +as_val=`eval 'as_val=${'$as_ac_var'} + $as_echo "$as_val"'` + if test "x$as_val" = x""yes; then + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 +_ACEOF + +fi +done + + +# The cast to long int works around a bug in the HP C Compiler +# version HP92453-01 B.11.11.23709.GP, which incorrectly rejects +# declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'. +# This bug is HP SR number 8606223364. +{ $as_echo "$as_me:$LINENO: checking size of long" >&5 +$as_echo_n "checking size of long... " >&6; } +if test "${ac_cv_sizeof_long+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test "$cross_compiling" = yes; then + # Depending upon the size, compute the lo and hi bounds. +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +int +main () +{ +static int test_array [1 - 2 * !(((long int) (sizeof (long))) >= 0)]; +test_array [0] = 0 + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_lo=0 ac_mid=0 + while :; do + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +int +main () +{ +static int test_array [1 - 2 * !(((long int) (sizeof (long))) <= $ac_mid)]; +test_array [0] = 0 + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_hi=$ac_mid; break +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_lo=`expr $ac_mid + 1` + if test $ac_lo -le $ac_mid; then + ac_lo= ac_hi= + break + fi + ac_mid=`expr 2 '*' $ac_mid + 1` +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + done +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +int +main () +{ +static int test_array [1 - 2 * !(((long int) (sizeof (long))) < 0)]; +test_array [0] = 0 + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_hi=-1 ac_mid=-1 + while :; do + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +int +main () +{ +static int test_array [1 - 2 * !(((long int) (sizeof (long))) >= $ac_mid)]; +test_array [0] = 0 + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_lo=$ac_mid; break +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_hi=`expr '(' $ac_mid ')' - 1` + if test $ac_mid -le $ac_hi; then + ac_lo= ac_hi= + break + fi + ac_mid=`expr 2 '*' $ac_mid` +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + done +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_lo= ac_hi= +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +# Binary search between lo and hi bounds. +while test "x$ac_lo" != "x$ac_hi"; do + ac_mid=`expr '(' $ac_hi - $ac_lo ')' / 2 + $ac_lo` + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +int +main () +{ +static int test_array [1 - 2 * !(((long int) (sizeof (long))) <= $ac_mid)]; +test_array [0] = 0 + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_hi=$ac_mid +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_lo=`expr '(' $ac_mid ')' + 1` +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +done +case $ac_lo in +?*) ac_cv_sizeof_long=$ac_lo;; +'') if test "$ac_cv_type_long" = yes; then + { { $as_echo "$as_me:$LINENO: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +{ { $as_echo "$as_me:$LINENO: error: cannot compute sizeof (long) +See \`config.log' for more details." >&5 +$as_echo "$as_me: error: cannot compute sizeof (long) +See \`config.log' for more details." >&2;} + { (exit 77); exit 77; }; }; } + else + ac_cv_sizeof_long=0 + fi ;; +esac +else + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +static long int longval () { return (long int) (sizeof (long)); } +static unsigned long int ulongval () { return (long int) (sizeof (long)); } +#include +#include +int +main () +{ + + FILE *f = fopen ("conftest.val", "w"); + if (! f) + return 1; + if (((long int) (sizeof (long))) < 0) + { + long int i = longval (); + if (i != ((long int) (sizeof (long)))) + return 1; + fprintf (f, "%ld", i); + } + else + { + unsigned long int i = ulongval (); + if (i != ((long int) (sizeof (long)))) + return 1; + fprintf (f, "%lu", i); + } + /* Do not output a trailing newline, as this causes \r\n confusion + on some platforms. */ + return ferror (f) || fclose (f) != 0; + + ; + return 0; +} +_ACEOF +rm -f conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { ac_try='./conftest$ac_exeext' + { (case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; }; then + ac_cv_sizeof_long=`cat conftest.val` +else + $as_echo "$as_me: program exited with status $ac_status" >&5 +$as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +( exit $ac_status ) +if test "$ac_cv_type_long" = yes; then + { { $as_echo "$as_me:$LINENO: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +{ { $as_echo "$as_me:$LINENO: error: cannot compute sizeof (long) +See \`config.log' for more details." >&5 +$as_echo "$as_me: error: cannot compute sizeof (long) +See \`config.log' for more details." >&2;} + { (exit 77); exit 77; }; }; } + else + ac_cv_sizeof_long=0 + fi +fi +rm -rf conftest.dSYM +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext conftest.$ac_objext conftest.$ac_ext +fi +rm -f conftest.val +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_sizeof_long" >&5 +$as_echo "$ac_cv_sizeof_long" >&6; } + + + +cat >>confdefs.h <<_ACEOF +#define SIZEOF_LONG $ac_cv_sizeof_long +_ACEOF + + + +if test "x$ac_cv_func_clock_gettime" = "xyes"; then + +cat >>confdefs.h <<\_ACEOF +#define DNS_USE_CPU_CLOCK_FOR_ID 1 +_ACEOF + +else + +cat >>confdefs.h <<\_ACEOF +#define DNS_USE_GETTIMEOFDAY_FOR_ID 1 +_ACEOF + +fi + +{ $as_echo "$as_me:$LINENO: checking for F_SETFD in fcntl.h" >&5 +$as_echo_n "checking for F_SETFD in fcntl.h... " >&6; } +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +#define _GNU_SOURCE +#include +#ifdef F_SETFD +yes +#endif + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "yes" >/dev/null 2>&1; then + +cat >>confdefs.h <<\_ACEOF +#define HAVE_SETFD 1 +_ACEOF + + { $as_echo "$as_me:$LINENO: result: yes" >&5 +$as_echo "yes" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi +rm -f conftest* + + +needsignal=no +haveselect=no + +for ac_func in select +do +as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` +{ $as_echo "$as_me:$LINENO: checking for $ac_func" >&5 +$as_echo_n "checking for $ac_func... " >&6; } +if { as_var=$as_ac_var; eval "test \"\${$as_var+set}\" = set"; }; then + $as_echo_n "(cached) " >&6 +else + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +/* Define $ac_func to an innocuous variant, in case declares $ac_func. + For example, HP-UX 11i declares gettimeofday. */ +#define $ac_func innocuous_$ac_func + +/* System header to define __stub macros and hopefully few prototypes, + which can conflict with char $ac_func (); below. + Prefer to if __STDC__ is defined, since + exists even on freestanding compilers. */ + +#ifdef __STDC__ +# include +#else +# include +#endif + +#undef $ac_func + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char $ac_func (); +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined __stub_$ac_func || defined __stub___$ac_func +choke me +#endif + +int +main () +{ +return $ac_func (); + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + $as_test_x conftest$ac_exeext + }; then + eval "$as_ac_var=yes" +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + eval "$as_ac_var=no" +fi + +rm -rf conftest.dSYM +rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ + conftest$ac_exeext conftest.$ac_ext +fi +ac_res=`eval 'as_val=${'$as_ac_var'} + $as_echo "$as_val"'` + { $as_echo "$as_me:$LINENO: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +as_val=`eval 'as_val=${'$as_ac_var'} + $as_echo "$as_val"'` + if test "x$as_val" = x""yes; then + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 +_ACEOF + haveselect=yes +fi +done + +if test "x$haveselect" = "xyes" ; then + case " $LIBOBJS " in + *" select.$ac_objext "* ) ;; + *) LIBOBJS="$LIBOBJS select.$ac_objext" + ;; +esac + + needsignal=yes +fi + +havepoll=no + +for ac_func in poll +do +as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` +{ $as_echo "$as_me:$LINENO: checking for $ac_func" >&5 +$as_echo_n "checking for $ac_func... " >&6; } +if { as_var=$as_ac_var; eval "test \"\${$as_var+set}\" = set"; }; then + $as_echo_n "(cached) " >&6 +else + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +/* Define $ac_func to an innocuous variant, in case declares $ac_func. + For example, HP-UX 11i declares gettimeofday. */ +#define $ac_func innocuous_$ac_func + +/* System header to define __stub macros and hopefully few prototypes, + which can conflict with char $ac_func (); below. + Prefer to if __STDC__ is defined, since + exists even on freestanding compilers. */ + +#ifdef __STDC__ +# include +#else +# include +#endif + +#undef $ac_func + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char $ac_func (); +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined __stub_$ac_func || defined __stub___$ac_func +choke me +#endif + +int +main () +{ +return $ac_func (); + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + $as_test_x conftest$ac_exeext + }; then + eval "$as_ac_var=yes" +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + eval "$as_ac_var=no" +fi + +rm -rf conftest.dSYM +rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ + conftest$ac_exeext conftest.$ac_ext +fi +ac_res=`eval 'as_val=${'$as_ac_var'} + $as_echo "$as_val"'` + { $as_echo "$as_me:$LINENO: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +as_val=`eval 'as_val=${'$as_ac_var'} + $as_echo "$as_val"'` + if test "x$as_val" = x""yes; then + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 +_ACEOF + havepoll=yes +fi +done + +if test "x$havepoll" = "xyes" ; then + case " $LIBOBJS " in + *" poll.$ac_objext "* ) ;; + *) LIBOBJS="$LIBOBJS poll.$ac_objext" + ;; +esac + + needsignal=yes +fi + +haveepoll=no + +for ac_func in epoll_ctl +do +as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` +{ $as_echo "$as_me:$LINENO: checking for $ac_func" >&5 +$as_echo_n "checking for $ac_func... " >&6; } +if { as_var=$as_ac_var; eval "test \"\${$as_var+set}\" = set"; }; then + $as_echo_n "(cached) " >&6 +else + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +/* Define $ac_func to an innocuous variant, in case declares $ac_func. + For example, HP-UX 11i declares gettimeofday. */ +#define $ac_func innocuous_$ac_func + +/* System header to define __stub macros and hopefully few prototypes, + which can conflict with char $ac_func (); below. + Prefer to if __STDC__ is defined, since + exists even on freestanding compilers. */ + +#ifdef __STDC__ +# include +#else +# include +#endif + +#undef $ac_func + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char $ac_func (); +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined __stub_$ac_func || defined __stub___$ac_func +choke me +#endif + +int +main () +{ +return $ac_func (); + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + $as_test_x conftest$ac_exeext + }; then + eval "$as_ac_var=yes" +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + eval "$as_ac_var=no" +fi + +rm -rf conftest.dSYM +rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ + conftest$ac_exeext conftest.$ac_ext +fi +ac_res=`eval 'as_val=${'$as_ac_var'} + $as_echo "$as_val"'` + { $as_echo "$as_me:$LINENO: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +as_val=`eval 'as_val=${'$as_ac_var'} + $as_echo "$as_val"'` + if test "x$as_val" = x""yes; then + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 +_ACEOF + haveepoll=yes +fi +done + +if test "x$haveepoll" = "xyes" ; then + +cat >>confdefs.h <<\_ACEOF +#define HAVE_EPOLL 1 +_ACEOF + + case " $LIBOBJS " in + *" epoll.$ac_objext "* ) ;; + *) LIBOBJS="$LIBOBJS epoll.$ac_objext" + ;; +esac + + needsignal=yes +fi + +havedevpoll=no +if test "x$ac_cv_header_sys_devpoll_h" = "xyes"; then + +cat >>confdefs.h <<\_ACEOF +#define HAVE_DEVPOLL 1 +_ACEOF + + case " $LIBOBJS " in + *" devpoll.$ac_objext "* ) ;; + *) LIBOBJS="$LIBOBJS devpoll.$ac_objext" + ;; +esac + +fi + +havekqueue=no +if test "x$ac_cv_header_sys_event_h" = "xyes"; then + +for ac_func in kqueue +do +as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` +{ $as_echo "$as_me:$LINENO: checking for $ac_func" >&5 +$as_echo_n "checking for $ac_func... " >&6; } +if { as_var=$as_ac_var; eval "test \"\${$as_var+set}\" = set"; }; then + $as_echo_n "(cached) " >&6 +else + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +/* Define $ac_func to an innocuous variant, in case declares $ac_func. + For example, HP-UX 11i declares gettimeofday. */ +#define $ac_func innocuous_$ac_func + +/* System header to define __stub macros and hopefully few prototypes, + which can conflict with char $ac_func (); below. + Prefer to if __STDC__ is defined, since + exists even on freestanding compilers. */ + +#ifdef __STDC__ +# include +#else +# include +#endif + +#undef $ac_func + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char $ac_func (); +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined __stub_$ac_func || defined __stub___$ac_func +choke me +#endif + +int +main () +{ +return $ac_func (); + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + $as_test_x conftest$ac_exeext + }; then + eval "$as_ac_var=yes" +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + eval "$as_ac_var=no" +fi + +rm -rf conftest.dSYM +rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ + conftest$ac_exeext conftest.$ac_ext +fi +ac_res=`eval 'as_val=${'$as_ac_var'} + $as_echo "$as_val"'` + { $as_echo "$as_me:$LINENO: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +as_val=`eval 'as_val=${'$as_ac_var'} + $as_echo "$as_val"'` + if test "x$as_val" = x""yes; then + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 +_ACEOF + havekqueue=yes +fi +done + + if test "x$havekqueue" = "xyes" ; then + { $as_echo "$as_me:$LINENO: checking for working kqueue" >&5 +$as_echo_n "checking for working kqueue... " >&6; } + if test "$cross_compiling" = yes; then + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +else + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#include +#include +#include +#include +#include +#include + +int +main(int argc, char **argv) +{ + int kq; + int n; + int fd[2]; + struct kevent ev; + struct timespec ts; + char buf[8000]; + + if (pipe(fd) == -1) + exit(1); + if (fcntl(fd[1], F_SETFL, O_NONBLOCK) == -1) + exit(1); + + while ((n = write(fd[1], buf, sizeof(buf))) == sizeof(buf)) + ; + + if ((kq = kqueue()) == -1) + exit(1); + + ev.ident = fd[1]; + ev.filter = EVFILT_WRITE; + ev.flags = EV_ADD | EV_ENABLE; + n = kevent(kq, &ev, 1, NULL, 0, NULL); + if (n == -1) + exit(1); + + read(fd[0], buf, sizeof(buf)); + + ts.tv_sec = 0; + ts.tv_nsec = 0; + n = kevent(kq, NULL, 0, &ev, 1, &ts); + if (n == -1 || n == 0) + exit(1); + + exit(0); +} +_ACEOF +rm -f conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { ac_try='./conftest$ac_exeext' + { (case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; }; then + { $as_echo "$as_me:$LINENO: result: yes" >&5 +$as_echo "yes" >&6; } + +cat >>confdefs.h <<\_ACEOF +#define HAVE_WORKING_KQUEUE 1 +_ACEOF + + case " $LIBOBJS " in + *" kqueue.$ac_objext "* ) ;; + *) LIBOBJS="$LIBOBJS kqueue.$ac_objext" + ;; +esac + +else + $as_echo "$as_me: program exited with status $ac_status" >&5 +$as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +( exit $ac_status ) +{ $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi +rm -rf conftest.dSYM +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext conftest.$ac_objext conftest.$ac_ext +fi + + + fi +fi + +haveepollsyscall=no +if test "x$ac_cv_header_sys_epoll_h" = "xyes"; then + if test "x$haveepoll" = "xno" ; then + { $as_echo "$as_me:$LINENO: checking for epoll system call" >&5 +$as_echo_n "checking for epoll system call... " >&6; } + if test "$cross_compiling" = yes; then + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +else + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#include +#include +#include +#include +#include +#include + +int +epoll_create(int size) +{ + return (syscall(__NR_epoll_create, size)); +} + +int +main(int argc, char **argv) +{ + int epfd; + + epfd = epoll_create(256); + exit (epfd == -1 ? 1 : 0); +} +_ACEOF +rm -f conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { ac_try='./conftest$ac_exeext' + { (case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; }; then + { $as_echo "$as_me:$LINENO: result: yes" >&5 +$as_echo "yes" >&6; } + +cat >>confdefs.h <<\_ACEOF +#define HAVE_EPOLL 1 +_ACEOF + + needsignal=yes + case " $LIBOBJS " in + *" epoll_sub.$ac_objext "* ) ;; + *) LIBOBJS="$LIBOBJS epoll_sub.$ac_objext" + ;; +esac + + case " $LIBOBJS " in + *" epoll.$ac_objext "* ) ;; + *) LIBOBJS="$LIBOBJS epoll.$ac_objext" + ;; +esac + +else + $as_echo "$as_me: program exited with status $ac_status" >&5 +$as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +( exit $ac_status ) +{ $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi +rm -rf conftest.dSYM +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext conftest.$ac_objext conftest.$ac_ext +fi + + + fi +fi + +haveeventports=no + +for ac_func in port_create +do +as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` +{ $as_echo "$as_me:$LINENO: checking for $ac_func" >&5 +$as_echo_n "checking for $ac_func... " >&6; } +if { as_var=$as_ac_var; eval "test \"\${$as_var+set}\" = set"; }; then + $as_echo_n "(cached) " >&6 +else + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +/* Define $ac_func to an innocuous variant, in case declares $ac_func. + For example, HP-UX 11i declares gettimeofday. */ +#define $ac_func innocuous_$ac_func + +/* System header to define __stub macros and hopefully few prototypes, + which can conflict with char $ac_func (); below. + Prefer to if __STDC__ is defined, since + exists even on freestanding compilers. */ + +#ifdef __STDC__ +# include +#else +# include +#endif + +#undef $ac_func + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char $ac_func (); +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined __stub_$ac_func || defined __stub___$ac_func +choke me +#endif + +int +main () +{ +return $ac_func (); + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + $as_test_x conftest$ac_exeext + }; then + eval "$as_ac_var=yes" +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + eval "$as_ac_var=no" +fi + +rm -rf conftest.dSYM +rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ + conftest$ac_exeext conftest.$ac_ext +fi +ac_res=`eval 'as_val=${'$as_ac_var'} + $as_echo "$as_val"'` + { $as_echo "$as_me:$LINENO: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +as_val=`eval 'as_val=${'$as_ac_var'} + $as_echo "$as_val"'` + if test "x$as_val" = x""yes; then + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 +_ACEOF + haveeventports=yes +fi +done + +if test "x$haveeventports" = "xyes" ; then + +cat >>confdefs.h <<\_ACEOF +#define HAVE_EVENT_PORTS 1 +_ACEOF + + case " $LIBOBJS " in + *" evport.$ac_objext "* ) ;; + *) LIBOBJS="$LIBOBJS evport.$ac_objext" + ;; +esac + + needsignal=yes +fi +if test "x$bwin32" = "xtrue"; then + needsignal=yes +fi +if test "x$bwin32" = "xtrue"; then + needsignal=yes +fi +if test "x$needsignal" = "xyes" ; then + case " $LIBOBJS " in + *" signal.$ac_objext "* ) ;; + *) LIBOBJS="$LIBOBJS signal.$ac_objext" + ;; +esac + +fi + +{ $as_echo "$as_me:$LINENO: checking for pid_t" >&5 +$as_echo_n "checking for pid_t... " >&6; } +if test "${ac_cv_type_pid_t+set}" = set; then + $as_echo_n "(cached) " >&6 +else + ac_cv_type_pid_t=no +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +int +main () +{ +if (sizeof (pid_t)) + return 0; + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +int +main () +{ +if (sizeof ((pid_t))) + return 0; + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + : +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_cv_type_pid_t=yes +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_type_pid_t" >&5 +$as_echo "$ac_cv_type_pid_t" >&6; } +if test "x$ac_cv_type_pid_t" = x""yes; then + : +else + +cat >>confdefs.h <<_ACEOF +#define pid_t int +_ACEOF + +fi + +{ $as_echo "$as_me:$LINENO: checking for size_t" >&5 +$as_echo_n "checking for size_t... " >&6; } +if test "${ac_cv_type_size_t+set}" = set; then + $as_echo_n "(cached) " >&6 +else + ac_cv_type_size_t=no +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +int +main () +{ +if (sizeof (size_t)) + return 0; + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +int +main () +{ +if (sizeof ((size_t))) + return 0; + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + : +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_cv_type_size_t=yes +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_type_size_t" >&5 +$as_echo "$ac_cv_type_size_t" >&6; } +if test "x$ac_cv_type_size_t" = x""yes; then + : +else + +cat >>confdefs.h <<_ACEOF +#define size_t unsigned int +_ACEOF + +fi + +{ $as_echo "$as_me:$LINENO: checking for uint64_t" >&5 +$as_echo_n "checking for uint64_t... " >&6; } +if test "${ac_cv_type_uint64_t+set}" = set; then + $as_echo_n "(cached) " >&6 +else + ac_cv_type_uint64_t=no +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#ifdef HAVE_STDINT_H +#include +#elif defined(HAVE_INTTYPES_H) +#include +#endif +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +int +main () +{ +if (sizeof (uint64_t)) + return 0; + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#ifdef HAVE_STDINT_H +#include +#elif defined(HAVE_INTTYPES_H) +#include +#endif +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +int +main () +{ +if (sizeof ((uint64_t))) + return 0; + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + : +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_cv_type_uint64_t=yes +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_type_uint64_t" >&5 +$as_echo "$ac_cv_type_uint64_t" >&6; } +if test "x$ac_cv_type_uint64_t" = x""yes; then + +cat >>confdefs.h <<_ACEOF +#define HAVE_UINT64_T 1 +_ACEOF + + +fi +{ $as_echo "$as_me:$LINENO: checking for uint32_t" >&5 +$as_echo_n "checking for uint32_t... " >&6; } +if test "${ac_cv_type_uint32_t+set}" = set; then + $as_echo_n "(cached) " >&6 +else + ac_cv_type_uint32_t=no +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#ifdef HAVE_STDINT_H +#include +#elif defined(HAVE_INTTYPES_H) +#include +#endif +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +int +main () +{ +if (sizeof (uint32_t)) + return 0; + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#ifdef HAVE_STDINT_H +#include +#elif defined(HAVE_INTTYPES_H) +#include +#endif +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +int +main () +{ +if (sizeof ((uint32_t))) + return 0; + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + : +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_cv_type_uint32_t=yes +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_type_uint32_t" >&5 +$as_echo "$ac_cv_type_uint32_t" >&6; } +if test "x$ac_cv_type_uint32_t" = x""yes; then + +cat >>confdefs.h <<_ACEOF +#define HAVE_UINT32_T 1 +_ACEOF + + +fi +{ $as_echo "$as_me:$LINENO: checking for uint16_t" >&5 +$as_echo_n "checking for uint16_t... " >&6; } +if test "${ac_cv_type_uint16_t+set}" = set; then + $as_echo_n "(cached) " >&6 +else + ac_cv_type_uint16_t=no +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#ifdef HAVE_STDINT_H +#include +#elif defined(HAVE_INTTYPES_H) +#include +#endif +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +int +main () +{ +if (sizeof (uint16_t)) + return 0; + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#ifdef HAVE_STDINT_H +#include +#elif defined(HAVE_INTTYPES_H) +#include +#endif +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +int +main () +{ +if (sizeof ((uint16_t))) + return 0; + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + : +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_cv_type_uint16_t=yes +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_type_uint16_t" >&5 +$as_echo "$ac_cv_type_uint16_t" >&6; } +if test "x$ac_cv_type_uint16_t" = x""yes; then + +cat >>confdefs.h <<_ACEOF +#define HAVE_UINT16_T 1 +_ACEOF + + +fi +{ $as_echo "$as_me:$LINENO: checking for uint8_t" >&5 +$as_echo_n "checking for uint8_t... " >&6; } +if test "${ac_cv_type_uint8_t+set}" = set; then + $as_echo_n "(cached) " >&6 +else + ac_cv_type_uint8_t=no +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#ifdef HAVE_STDINT_H +#include +#elif defined(HAVE_INTTYPES_H) +#include +#endif +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +int +main () +{ +if (sizeof (uint8_t)) + return 0; + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#ifdef HAVE_STDINT_H +#include +#elif defined(HAVE_INTTYPES_H) +#include +#endif +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +int +main () +{ +if (sizeof ((uint8_t))) + return 0; + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + : +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_cv_type_uint8_t=yes +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_type_uint8_t" >&5 +$as_echo "$ac_cv_type_uint8_t" >&6; } +if test "x$ac_cv_type_uint8_t" = x""yes; then + +cat >>confdefs.h <<_ACEOF +#define HAVE_UINT8_T 1 +_ACEOF + + +fi + +{ $as_echo "$as_me:$LINENO: checking for fd_mask" >&5 +$as_echo_n "checking for fd_mask... " >&6; } +if test "${ac_cv_type_fd_mask+set}" = set; then + $as_echo_n "(cached) " >&6 +else + ac_cv_type_fd_mask=no +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SELECT_H +#include +#endif + +int +main () +{ +if (sizeof (fd_mask)) + return 0; + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SELECT_H +#include +#endif + +int +main () +{ +if (sizeof ((fd_mask))) + return 0; + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + : +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_cv_type_fd_mask=yes +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_type_fd_mask" >&5 +$as_echo "$ac_cv_type_fd_mask" >&6; } +if test "x$ac_cv_type_fd_mask" = x""yes; then + +cat >>confdefs.h <<_ACEOF +#define HAVE_FD_MASK 1 +_ACEOF + + +fi + + +# The cast to long int works around a bug in the HP C Compiler +# version HP92453-01 B.11.11.23709.GP, which incorrectly rejects +# declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'. +# This bug is HP SR number 8606223364. +{ $as_echo "$as_me:$LINENO: checking size of long long" >&5 +$as_echo_n "checking size of long long... " >&6; } +if test "${ac_cv_sizeof_long_long+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test "$cross_compiling" = yes; then + # Depending upon the size, compute the lo and hi bounds. +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +int +main () +{ +static int test_array [1 - 2 * !(((long int) (sizeof (long long))) >= 0)]; +test_array [0] = 0 + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_lo=0 ac_mid=0 + while :; do + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +int +main () +{ +static int test_array [1 - 2 * !(((long int) (sizeof (long long))) <= $ac_mid)]; +test_array [0] = 0 + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_hi=$ac_mid; break +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_lo=`expr $ac_mid + 1` + if test $ac_lo -le $ac_mid; then + ac_lo= ac_hi= + break + fi + ac_mid=`expr 2 '*' $ac_mid + 1` +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + done +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +int +main () +{ +static int test_array [1 - 2 * !(((long int) (sizeof (long long))) < 0)]; +test_array [0] = 0 + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_hi=-1 ac_mid=-1 + while :; do + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +int +main () +{ +static int test_array [1 - 2 * !(((long int) (sizeof (long long))) >= $ac_mid)]; +test_array [0] = 0 + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_lo=$ac_mid; break +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_hi=`expr '(' $ac_mid ')' - 1` + if test $ac_mid -le $ac_hi; then + ac_lo= ac_hi= + break + fi + ac_mid=`expr 2 '*' $ac_mid` +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + done +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_lo= ac_hi= +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +# Binary search between lo and hi bounds. +while test "x$ac_lo" != "x$ac_hi"; do + ac_mid=`expr '(' $ac_hi - $ac_lo ')' / 2 + $ac_lo` + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +int +main () +{ +static int test_array [1 - 2 * !(((long int) (sizeof (long long))) <= $ac_mid)]; +test_array [0] = 0 + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_hi=$ac_mid +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_lo=`expr '(' $ac_mid ')' + 1` +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +done +case $ac_lo in +?*) ac_cv_sizeof_long_long=$ac_lo;; +'') if test "$ac_cv_type_long_long" = yes; then + { { $as_echo "$as_me:$LINENO: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +{ { $as_echo "$as_me:$LINENO: error: cannot compute sizeof (long long) +See \`config.log' for more details." >&5 +$as_echo "$as_me: error: cannot compute sizeof (long long) +See \`config.log' for more details." >&2;} + { (exit 77); exit 77; }; }; } + else + ac_cv_sizeof_long_long=0 + fi ;; +esac +else + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +static long int longval () { return (long int) (sizeof (long long)); } +static unsigned long int ulongval () { return (long int) (sizeof (long long)); } +#include +#include +int +main () +{ + + FILE *f = fopen ("conftest.val", "w"); + if (! f) + return 1; + if (((long int) (sizeof (long long))) < 0) + { + long int i = longval (); + if (i != ((long int) (sizeof (long long)))) + return 1; + fprintf (f, "%ld", i); + } + else + { + unsigned long int i = ulongval (); + if (i != ((long int) (sizeof (long long)))) + return 1; + fprintf (f, "%lu", i); + } + /* Do not output a trailing newline, as this causes \r\n confusion + on some platforms. */ + return ferror (f) || fclose (f) != 0; + + ; + return 0; +} +_ACEOF +rm -f conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { ac_try='./conftest$ac_exeext' + { (case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; }; then + ac_cv_sizeof_long_long=`cat conftest.val` +else + $as_echo "$as_me: program exited with status $ac_status" >&5 +$as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +( exit $ac_status ) +if test "$ac_cv_type_long_long" = yes; then + { { $as_echo "$as_me:$LINENO: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +{ { $as_echo "$as_me:$LINENO: error: cannot compute sizeof (long long) +See \`config.log' for more details." >&5 +$as_echo "$as_me: error: cannot compute sizeof (long long) +See \`config.log' for more details." >&2;} + { (exit 77); exit 77; }; }; } + else + ac_cv_sizeof_long_long=0 + fi +fi +rm -rf conftest.dSYM +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext conftest.$ac_objext conftest.$ac_ext +fi +rm -f conftest.val +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_sizeof_long_long" >&5 +$as_echo "$ac_cv_sizeof_long_long" >&6; } + + + +cat >>confdefs.h <<_ACEOF +#define SIZEOF_LONG_LONG $ac_cv_sizeof_long_long +_ACEOF + + +# The cast to long int works around a bug in the HP C Compiler +# version HP92453-01 B.11.11.23709.GP, which incorrectly rejects +# declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'. +# This bug is HP SR number 8606223364. +{ $as_echo "$as_me:$LINENO: checking size of int" >&5 +$as_echo_n "checking size of int... " >&6; } +if test "${ac_cv_sizeof_int+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test "$cross_compiling" = yes; then + # Depending upon the size, compute the lo and hi bounds. +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +int +main () +{ +static int test_array [1 - 2 * !(((long int) (sizeof (int))) >= 0)]; +test_array [0] = 0 + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_lo=0 ac_mid=0 + while :; do + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +int +main () +{ +static int test_array [1 - 2 * !(((long int) (sizeof (int))) <= $ac_mid)]; +test_array [0] = 0 + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_hi=$ac_mid; break +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_lo=`expr $ac_mid + 1` + if test $ac_lo -le $ac_mid; then + ac_lo= ac_hi= + break + fi + ac_mid=`expr 2 '*' $ac_mid + 1` +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + done +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +int +main () +{ +static int test_array [1 - 2 * !(((long int) (sizeof (int))) < 0)]; +test_array [0] = 0 + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_hi=-1 ac_mid=-1 + while :; do + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +int +main () +{ +static int test_array [1 - 2 * !(((long int) (sizeof (int))) >= $ac_mid)]; +test_array [0] = 0 + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_lo=$ac_mid; break +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_hi=`expr '(' $ac_mid ')' - 1` + if test $ac_mid -le $ac_hi; then + ac_lo= ac_hi= + break + fi + ac_mid=`expr 2 '*' $ac_mid` +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + done +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_lo= ac_hi= +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +# Binary search between lo and hi bounds. +while test "x$ac_lo" != "x$ac_hi"; do + ac_mid=`expr '(' $ac_hi - $ac_lo ')' / 2 + $ac_lo` + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +int +main () +{ +static int test_array [1 - 2 * !(((long int) (sizeof (int))) <= $ac_mid)]; +test_array [0] = 0 + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_hi=$ac_mid +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_lo=`expr '(' $ac_mid ')' + 1` +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +done +case $ac_lo in +?*) ac_cv_sizeof_int=$ac_lo;; +'') if test "$ac_cv_type_int" = yes; then + { { $as_echo "$as_me:$LINENO: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +{ { $as_echo "$as_me:$LINENO: error: cannot compute sizeof (int) +See \`config.log' for more details." >&5 +$as_echo "$as_me: error: cannot compute sizeof (int) +See \`config.log' for more details." >&2;} + { (exit 77); exit 77; }; }; } + else + ac_cv_sizeof_int=0 + fi ;; +esac +else + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +static long int longval () { return (long int) (sizeof (int)); } +static unsigned long int ulongval () { return (long int) (sizeof (int)); } +#include +#include +int +main () +{ + + FILE *f = fopen ("conftest.val", "w"); + if (! f) + return 1; + if (((long int) (sizeof (int))) < 0) + { + long int i = longval (); + if (i != ((long int) (sizeof (int)))) + return 1; + fprintf (f, "%ld", i); + } + else + { + unsigned long int i = ulongval (); + if (i != ((long int) (sizeof (int)))) + return 1; + fprintf (f, "%lu", i); + } + /* Do not output a trailing newline, as this causes \r\n confusion + on some platforms. */ + return ferror (f) || fclose (f) != 0; + + ; + return 0; +} +_ACEOF +rm -f conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { ac_try='./conftest$ac_exeext' + { (case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; }; then + ac_cv_sizeof_int=`cat conftest.val` +else + $as_echo "$as_me: program exited with status $ac_status" >&5 +$as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +( exit $ac_status ) +if test "$ac_cv_type_int" = yes; then + { { $as_echo "$as_me:$LINENO: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +{ { $as_echo "$as_me:$LINENO: error: cannot compute sizeof (int) +See \`config.log' for more details." >&5 +$as_echo "$as_me: error: cannot compute sizeof (int) +See \`config.log' for more details." >&2;} + { (exit 77); exit 77; }; }; } + else + ac_cv_sizeof_int=0 + fi +fi +rm -rf conftest.dSYM +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext conftest.$ac_objext conftest.$ac_ext +fi +rm -f conftest.val +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_sizeof_int" >&5 +$as_echo "$ac_cv_sizeof_int" >&6; } + + + +cat >>confdefs.h <<_ACEOF +#define SIZEOF_INT $ac_cv_sizeof_int +_ACEOF + + +# The cast to long int works around a bug in the HP C Compiler +# version HP92453-01 B.11.11.23709.GP, which incorrectly rejects +# declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'. +# This bug is HP SR number 8606223364. +{ $as_echo "$as_me:$LINENO: checking size of short" >&5 +$as_echo_n "checking size of short... " >&6; } +if test "${ac_cv_sizeof_short+set}" = set; then + $as_echo_n "(cached) " >&6 +else + if test "$cross_compiling" = yes; then + # Depending upon the size, compute the lo and hi bounds. +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +int +main () +{ +static int test_array [1 - 2 * !(((long int) (sizeof (short))) >= 0)]; +test_array [0] = 0 + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_lo=0 ac_mid=0 + while :; do + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +int +main () +{ +static int test_array [1 - 2 * !(((long int) (sizeof (short))) <= $ac_mid)]; +test_array [0] = 0 + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_hi=$ac_mid; break +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_lo=`expr $ac_mid + 1` + if test $ac_lo -le $ac_mid; then + ac_lo= ac_hi= + break + fi + ac_mid=`expr 2 '*' $ac_mid + 1` +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + done +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +int +main () +{ +static int test_array [1 - 2 * !(((long int) (sizeof (short))) < 0)]; +test_array [0] = 0 + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_hi=-1 ac_mid=-1 + while :; do + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +int +main () +{ +static int test_array [1 - 2 * !(((long int) (sizeof (short))) >= $ac_mid)]; +test_array [0] = 0 + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_lo=$ac_mid; break +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_hi=`expr '(' $ac_mid ')' - 1` + if test $ac_mid -le $ac_hi; then + ac_lo= ac_hi= + break + fi + ac_mid=`expr 2 '*' $ac_mid` +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + done +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_lo= ac_hi= +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +# Binary search between lo and hi bounds. +while test "x$ac_lo" != "x$ac_hi"; do + ac_mid=`expr '(' $ac_hi - $ac_lo ')' / 2 + $ac_lo` + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +int +main () +{ +static int test_array [1 - 2 * !(((long int) (sizeof (short))) <= $ac_mid)]; +test_array [0] = 0 + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_hi=$ac_mid +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_lo=`expr '(' $ac_mid ')' + 1` +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +done +case $ac_lo in +?*) ac_cv_sizeof_short=$ac_lo;; +'') if test "$ac_cv_type_short" = yes; then + { { $as_echo "$as_me:$LINENO: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +{ { $as_echo "$as_me:$LINENO: error: cannot compute sizeof (short) +See \`config.log' for more details." >&5 +$as_echo "$as_me: error: cannot compute sizeof (short) +See \`config.log' for more details." >&2;} + { (exit 77); exit 77; }; }; } + else + ac_cv_sizeof_short=0 + fi ;; +esac +else + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +static long int longval () { return (long int) (sizeof (short)); } +static unsigned long int ulongval () { return (long int) (sizeof (short)); } +#include +#include +int +main () +{ + + FILE *f = fopen ("conftest.val", "w"); + if (! f) + return 1; + if (((long int) (sizeof (short))) < 0) + { + long int i = longval (); + if (i != ((long int) (sizeof (short)))) + return 1; + fprintf (f, "%ld", i); + } + else + { + unsigned long int i = ulongval (); + if (i != ((long int) (sizeof (short)))) + return 1; + fprintf (f, "%lu", i); + } + /* Do not output a trailing newline, as this causes \r\n confusion + on some platforms. */ + return ferror (f) || fclose (f) != 0; + + ; + return 0; +} +_ACEOF +rm -f conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { ac_try='./conftest$ac_exeext' + { (case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; }; then + ac_cv_sizeof_short=`cat conftest.val` +else + $as_echo "$as_me: program exited with status $ac_status" >&5 +$as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +( exit $ac_status ) +if test "$ac_cv_type_short" = yes; then + { { $as_echo "$as_me:$LINENO: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +{ { $as_echo "$as_me:$LINENO: error: cannot compute sizeof (short) +See \`config.log' for more details." >&5 +$as_echo "$as_me: error: cannot compute sizeof (short) +See \`config.log' for more details." >&2;} + { (exit 77); exit 77; }; }; } + else + ac_cv_sizeof_short=0 + fi +fi +rm -rf conftest.dSYM +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext conftest.$ac_objext conftest.$ac_ext +fi +rm -f conftest.val +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_sizeof_short" >&5 +$as_echo "$ac_cv_sizeof_short" >&6; } + + + +cat >>confdefs.h <<_ACEOF +#define SIZEOF_SHORT $ac_cv_sizeof_short +_ACEOF + + +{ $as_echo "$as_me:$LINENO: checking for struct in6_addr" >&5 +$as_echo_n "checking for struct in6_addr... " >&6; } +if test "${ac_cv_type_struct_in6_addr+set}" = set; then + $as_echo_n "(cached) " >&6 +else + ac_cv_type_struct_in6_addr=no +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#ifdef WIN32 +#include +#else +#include +#include +#include +#endif +#ifdef HAVE_NETINET_IN6_H +#include +#endif + +int +main () +{ +if (sizeof (struct in6_addr)) + return 0; + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#ifdef WIN32 +#include +#else +#include +#include +#include +#endif +#ifdef HAVE_NETINET_IN6_H +#include +#endif + +int +main () +{ +if (sizeof ((struct in6_addr))) + return 0; + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + : +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_cv_type_struct_in6_addr=yes +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:$LINENO: result: $ac_cv_type_struct_in6_addr" >&5 +$as_echo "$ac_cv_type_struct_in6_addr" >&6; } +if test "x$ac_cv_type_struct_in6_addr" = x""yes; then + +cat >>confdefs.h <<_ACEOF +#define HAVE_STRUCT_IN6_ADDR 1 +_ACEOF + + +fi + + +{ $as_echo "$as_me:$LINENO: checking for socklen_t" >&5 +$as_echo_n "checking for socklen_t... " >&6; } +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + + #include + #include +int +main () +{ +socklen_t x; + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + { $as_echo "$as_me:$LINENO: result: yes" >&5 +$as_echo "yes" >&6; } +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } + +cat >>confdefs.h <<\_ACEOF +#define socklen_t unsigned int +_ACEOF + + +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +{ $as_echo "$as_me:$LINENO: checking whether our compiler supports __func__" >&5 +$as_echo_n "checking whether our compiler supports __func__... " >&6; } +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +int +main () +{ + const char *cp = __func__; + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + { $as_echo "$as_me:$LINENO: result: yes" >&5 +$as_echo "yes" >&6; } +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } + { $as_echo "$as_me:$LINENO: checking whether our compiler supports __FUNCTION__" >&5 +$as_echo_n "checking whether our compiler supports __FUNCTION__... " >&6; } + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +int +main () +{ + const char *cp = __FUNCTION__; + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + { $as_echo "$as_me:$LINENO: result: yes" >&5 +$as_echo "yes" >&6; } + +cat >>confdefs.h <<\_ACEOF +#define __func__ __FUNCTION__ +_ACEOF + +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } + +cat >>confdefs.h <<\_ACEOF +#define __func__ __FILE__ +_ACEOF + +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + + +# Add some more warnings which we use in development but not in the +# released versions. (Some relevant gcc versions can't handle these.) +if test x$enable_gcc_warnings = xyes; then + + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +int +main () +{ + +#if !defined(__GNUC__) || (__GNUC__ < 4) +#error +#endif + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + have_gcc4=yes +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + have_gcc4=no +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +int +main () +{ + +#if !defined(__GNUC__) || (__GNUC__ < 4) || (__GNUC__ == 4 && __GNUC_MINOR__ < 2) +#error +#endif + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + have_gcc42=yes +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + have_gcc42=no +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + + CFLAGS="$CFLAGS -W -Wfloat-equal -Wundef -Wpointer-arith -Wstrict-prototypes -Wmissing-prototypes -Wwrite-strings -Wredundant-decls -Wchar-subscripts -Wcomment -Wformat=2 -Wwrite-strings -Wmissing-declarations -Wredundant-decls -Wnested-externs -Wbad-function-cast -Wswitch-enum -Werror" + CFLAGS="$CFLAGS -Wno-unused-parameter -Wno-sign-compare -Wstrict-aliasing" + + if test x$have_gcc4 = xyes ; then + # These warnings break gcc 3.3.5 and work on gcc 4.0.2 + CFLAGS="$CFLAGS -Winit-self -Wmissing-field-initializers -Wdeclaration-after-statement" + #CFLAGS="$CFLAGS -Wold-style-definition" + fi + + if test x$have_gcc42 = xyes ; then + # These warnings break gcc 4.0.2 and work on gcc 4.2 + CFLAGS="$CFLAGS -Waddress -Wnormalized=id -Woverride-init" + fi + +##This will break the world on some 64-bit architectures +# CFLAGS="$CFLAGS -Winline" + +fi + +ac_config_files="$ac_config_files Makefile test/Makefile sample/Makefile" + +cat >confcache <<\_ACEOF +# This file is a shell script that caches the results of configure +# tests run on this system so they can be shared between configure +# scripts and configure runs, see configure's option --config-cache. +# It is not useful on other systems. If it contains results you don't +# want to keep, you may remove or edit it. +# +# config.status only pays attention to the cache file if you give it +# the --recheck option to rerun configure. +# +# `ac_cv_env_foo' variables (set or unset) will be overridden when +# loading this file, other *unset* `ac_cv_foo' will be assigned the +# following values. + +_ACEOF + +# The following way of writing the cache mishandles newlines in values, +# but we know of no workaround that is simple, portable, and efficient. +# So, we kill variables containing newlines. +# Ultrix sh set writes to stderr and can't be redirected directly, +# and sets the high bit in the cache file unless we assign to the vars. +( + for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do + eval ac_val=\$$ac_var + case $ac_val in #( + *${as_nl}*) + case $ac_var in #( + *_cv_*) { $as_echo "$as_me:$LINENO: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + esac + case $ac_var in #( + _ | IFS | as_nl) ;; #( + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) $as_unset $ac_var ;; + esac ;; + esac + done + + (set) 2>&1 | + case $as_nl`(ac_space=' '; set) 2>&1` in #( + *${as_nl}ac_space=\ *) + # `set' does not quote correctly, so add quotes (double-quote + # substitution turns \\\\ into \\, and sed turns \\ into \). + sed -n \ + "s/'/'\\\\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" + ;; #( + *) + # `set' quotes correctly as required by POSIX, so do not add quotes. + sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" + ;; + esac | + sort +) | + sed ' + /^ac_cv_env_/b end + t clear + :clear + s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/ + t end + s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ + :end' >>confcache +if diff "$cache_file" confcache >/dev/null 2>&1; then :; else + if test -w "$cache_file"; then + test "x$cache_file" != "x/dev/null" && + { $as_echo "$as_me:$LINENO: updating cache $cache_file" >&5 +$as_echo "$as_me: updating cache $cache_file" >&6;} + cat confcache >$cache_file + else + { $as_echo "$as_me:$LINENO: not updating unwritable cache $cache_file" >&5 +$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;} + fi +fi +rm -f confcache + +test "x$prefix" = xNONE && prefix=$ac_default_prefix +# Let make expand exec_prefix. +test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' + +DEFS=-DHAVE_CONFIG_H + +ac_libobjs= +ac_ltlibobjs= +for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue + # 1. Remove the extension, and $U if already installed. + ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' + ac_i=`$as_echo "$ac_i" | sed "$ac_script"` + # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR + # will be set to the directory where LIBOBJS objects are built. + ac_libobjs="$ac_libobjs \${LIBOBJDIR}$ac_i\$U.$ac_objext" + ac_ltlibobjs="$ac_ltlibobjs \${LIBOBJDIR}$ac_i"'$U.lo' +done +LIBOBJS=$ac_libobjs + +LTLIBOBJS=$ac_ltlibobjs + + +if test -z "${AMDEP_TRUE}" && test -z "${AMDEP_FALSE}"; then + { { $as_echo "$as_me:$LINENO: error: conditional \"AMDEP\" was never defined. +Usually this means the macro was only invoked conditionally." >&5 +$as_echo "$as_me: error: conditional \"AMDEP\" was never defined. +Usually this means the macro was only invoked conditionally." >&2;} + { (exit 1); exit 1; }; } +fi +if test -z "${am__fastdepCC_TRUE}" && test -z "${am__fastdepCC_FALSE}"; then + { { $as_echo "$as_me:$LINENO: error: conditional \"am__fastdepCC\" was never defined. +Usually this means the macro was only invoked conditionally." >&5 +$as_echo "$as_me: error: conditional \"am__fastdepCC\" was never defined. +Usually this means the macro was only invoked conditionally." >&2;} + { (exit 1); exit 1; }; } +fi +if test -z "${am__fastdepCXX_TRUE}" && test -z "${am__fastdepCXX_FALSE}"; then + { { $as_echo "$as_me:$LINENO: error: conditional \"am__fastdepCXX\" was never defined. +Usually this means the macro was only invoked conditionally." >&5 +$as_echo "$as_me: error: conditional \"am__fastdepCXX\" was never defined. +Usually this means the macro was only invoked conditionally." >&2;} + { (exit 1); exit 1; }; } +fi +if test -z "${BUILD_WIN32_TRUE}" && test -z "${BUILD_WIN32_FALSE}"; then + { { $as_echo "$as_me:$LINENO: error: conditional \"BUILD_WIN32\" was never defined. +Usually this means the macro was only invoked conditionally." >&5 +$as_echo "$as_me: error: conditional \"BUILD_WIN32\" was never defined. +Usually this means the macro was only invoked conditionally." >&2;} + { (exit 1); exit 1; }; } +fi + +: ${CONFIG_STATUS=./config.status} +ac_write_fail=0 +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files $CONFIG_STATUS" +{ $as_echo "$as_me:$LINENO: creating $CONFIG_STATUS" >&5 +$as_echo "$as_me: creating $CONFIG_STATUS" >&6;} +cat >$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +#! $SHELL +# Generated by $as_me. +# Run this file to recreate the current configuration. +# Compiler output produced by configure, useful for debugging +# configure, is in config.log if it exists. + +debug=false +ac_cs_recheck=false +ac_cs_silent=false +SHELL=\${CONFIG_SHELL-$SHELL} +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +## --------------------- ## +## M4sh Initialization. ## +## --------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in + *posix*) set -o posix ;; +esac + +fi + + + + +# PATH needs CR +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + +as_nl=' +' +export as_nl +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +if (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi + +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + +# Support unset when possible. +if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then + as_unset=unset +else + as_unset=false +fi + + +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + +# Find who we are. Look in the path if we contain no directory separator. +case $0 in + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break +done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as `sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + { (exit 1); exit 1; } +fi + +# Work around bugs in pre-3.0 UWIN ksh. +for as_var in ENV MAIL MAILPATH +do ($as_unset $as_var) >/dev/null 2>&1 && $as_unset $as_var +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# Required to use basename. +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + + +# Name of the executable. +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# CDPATH. +$as_unset CDPATH + + + + as_lineno_1=$LINENO + as_lineno_2=$LINENO + test "x$as_lineno_1" != "x$as_lineno_2" && + test "x`expr $as_lineno_1 + 1`" = "x$as_lineno_2" || { + + # Create $as_me.lineno as a copy of $as_myself, but with $LINENO + # uniformly replaced by the line number. The first 'sed' inserts a + # line-number line after each line using $LINENO; the second 'sed' + # does the real work. The second script uses 'N' to pair each + # line-number line with the line containing $LINENO, and appends + # trailing '-' during substitution so that $LINENO is not a special + # case at line end. + # (Raja R Harinath suggested sed '=', and Paul Eggert wrote the + # scripts with optimization help from Paolo Bonzini. Blame Lee + # E. McMahon (1931-1989) for sed's syntax. :-) + sed -n ' + p + /[$]LINENO/= + ' <$as_myself | + sed ' + s/[$]LINENO.*/&-/ + t lineno + b + :lineno + N + :loop + s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ + t loop + s/-\n.*// + ' >$as_me.lineno && + chmod +x "$as_me.lineno" || + { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2 + { (exit 1); exit 1; }; } + + # Don't try to exec as it changes $[0], causing all sort of problems + # (the dirname of $[0] is not the place where we might find the + # original and so on. Autoconf is especially sensitive to this). + . "./$as_me.lineno" + # Exit status is that of the last command. + exit +} + + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in +-n*) + case `echo 'x\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + *) ECHO_C='\c';; + esac;; +*) + ECHO_N='-n';; +esac +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -p'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -p' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -p' + fi +else + as_ln_s='cp -p' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + +if mkdir -p . 2>/dev/null; then + as_mkdir_p=: +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + +if test -x / >/dev/null 2>&1; then + as_test_x='test -x' +else + if ls -dL / >/dev/null 2>&1; then + as_ls_L_option=L + else + as_ls_L_option= + fi + as_test_x=' + eval sh -c '\'' + if test -d "$1"; then + test -d "$1/."; + else + case $1 in + -*)set "./$1";; + esac; + case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in + ???[sx]*):;;*)false;;esac;fi + '\'' sh + ' +fi +as_executable_p=$as_test_x + +# Sed expression to map a string onto a valid CPP name. +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" + +# Sed expression to map a string onto a valid variable name. +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" + + +exec 6>&1 + +# Save the log message, to keep $[0] and so on meaningful, and to +# report actual input values of CONFIG_FILES etc. instead of their +# values after options handling. +ac_log=" +This file was extended by $as_me, which was +generated by GNU Autoconf 2.63. Invocation command line was + + CONFIG_FILES = $CONFIG_FILES + CONFIG_HEADERS = $CONFIG_HEADERS + CONFIG_LINKS = $CONFIG_LINKS + CONFIG_COMMANDS = $CONFIG_COMMANDS + $ $0 $@ + +on `(hostname || uname -n) 2>/dev/null | sed 1q` +" + +_ACEOF + +case $ac_config_files in *" +"*) set x $ac_config_files; shift; ac_config_files=$*;; +esac + +case $ac_config_headers in *" +"*) set x $ac_config_headers; shift; ac_config_headers=$*;; +esac + + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +# Files that config.status was made for. +config_files="$ac_config_files" +config_headers="$ac_config_headers" +config_commands="$ac_config_commands" + +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +ac_cs_usage="\ +\`$as_me' instantiates files from templates according to the +current configuration. + +Usage: $0 [OPTION]... [FILE]... + + -h, --help print this help, then exit + -V, --version print version number and configuration settings, then exit + -q, --quiet, --silent + do not print progress messages + -d, --debug don't remove temporary files + --recheck update $as_me by reconfiguring in the same conditions + --file=FILE[:TEMPLATE] + instantiate the configuration file FILE + --header=FILE[:TEMPLATE] + instantiate the configuration header FILE + +Configuration files: +$config_files + +Configuration headers: +$config_headers + +Configuration commands: +$config_commands + +Report bugs to ." + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_cs_version="\\ +config.status +configured by $0, generated by GNU Autoconf 2.63, + with options \\"`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`\\" + +Copyright (C) 2008 Free Software Foundation, Inc. +This config.status script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it." + +ac_pwd='$ac_pwd' +srcdir='$srcdir' +INSTALL='$INSTALL' +MKDIR_P='$MKDIR_P' +AWK='$AWK' +test -n "\$AWK" || AWK=awk +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# The default lists apply if the user does not specify any file. +ac_need_defaults=: +while test $# != 0 +do + case $1 in + --*=*) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` + ac_shift=: + ;; + *) + ac_option=$1 + ac_optarg=$2 + ac_shift=shift + ;; + esac + + case $ac_option in + # Handling of the options. + -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) + ac_cs_recheck=: ;; + --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) + $as_echo "$ac_cs_version"; exit ;; + --debug | --debu | --deb | --de | --d | -d ) + debug=: ;; + --file | --fil | --fi | --f ) + $ac_shift + case $ac_optarg in + *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; + esac + CONFIG_FILES="$CONFIG_FILES '$ac_optarg'" + ac_need_defaults=false;; + --header | --heade | --head | --hea ) + $ac_shift + case $ac_optarg in + *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; + esac + CONFIG_HEADERS="$CONFIG_HEADERS '$ac_optarg'" + ac_need_defaults=false;; + --he | --h) + # Conflict between --help and --header + { $as_echo "$as_me: error: ambiguous option: $1 +Try \`$0 --help' for more information." >&2 + { (exit 1); exit 1; }; };; + --help | --hel | -h ) + $as_echo "$ac_cs_usage"; exit ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil | --si | --s) + ac_cs_silent=: ;; + + # This is an error. + -*) { $as_echo "$as_me: error: unrecognized option: $1 +Try \`$0 --help' for more information." >&2 + { (exit 1); exit 1; }; } ;; + + *) ac_config_targets="$ac_config_targets $1" + ac_need_defaults=false ;; + + esac + shift +done + +ac_configure_extra_args= + +if $ac_cs_silent; then + exec 6>/dev/null + ac_configure_extra_args="$ac_configure_extra_args --silent" +fi + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +if \$ac_cs_recheck; then + set X '$SHELL' '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion + shift + \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6 + CONFIG_SHELL='$SHELL' + export CONFIG_SHELL + exec "\$@" +fi + +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +exec 5>>config.log +{ + echo + sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX +## Running $as_me. ## +_ASBOX + $as_echo "$ac_log" +} >&5 + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +# +# INIT-COMMANDS +# +AMDEP_TRUE="$AMDEP_TRUE" ac_aux_dir="$ac_aux_dir" + +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 + +# Handling of arguments. +for ac_config_target in $ac_config_targets +do + case $ac_config_target in + "config.h") CONFIG_HEADERS="$CONFIG_HEADERS config.h" ;; + "depfiles") CONFIG_COMMANDS="$CONFIG_COMMANDS depfiles" ;; + "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; + "test/Makefile") CONFIG_FILES="$CONFIG_FILES test/Makefile" ;; + "sample/Makefile") CONFIG_FILES="$CONFIG_FILES sample/Makefile" ;; + + *) { { $as_echo "$as_me:$LINENO: error: invalid argument: $ac_config_target" >&5 +$as_echo "$as_me: error: invalid argument: $ac_config_target" >&2;} + { (exit 1); exit 1; }; };; + esac +done + + +# If the user did not use the arguments to specify the items to instantiate, +# then the envvar interface is used. Set only those that are not. +# We use the long form for the default assignment because of an extremely +# bizarre bug on SunOS 4.1.3. +if $ac_need_defaults; then + test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files + test "${CONFIG_HEADERS+set}" = set || CONFIG_HEADERS=$config_headers + test "${CONFIG_COMMANDS+set}" = set || CONFIG_COMMANDS=$config_commands +fi + +# Have a temporary directory for convenience. Make it in the build tree +# simply because there is no reason against having it here, and in addition, +# creating and moving files from /tmp can sometimes cause problems. +# Hook for its removal unless debugging. +# Note that there is a small window in which the directory will not be cleaned: +# after its creation but before its name has been assigned to `$tmp'. +$debug || +{ + tmp= + trap 'exit_status=$? + { test -z "$tmp" || test ! -d "$tmp" || rm -fr "$tmp"; } && exit $exit_status +' 0 + trap '{ (exit 1); exit 1; }' 1 2 13 15 +} +# Create a (secure) tmp directory for tmp files. + +{ + tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && + test -n "$tmp" && test -d "$tmp" +} || +{ + tmp=./conf$$-$RANDOM + (umask 077 && mkdir "$tmp") +} || +{ + $as_echo "$as_me: cannot create a temporary directory in ." >&2 + { (exit 1); exit 1; } +} + +# Set up the scripts for CONFIG_FILES section. +# No need to generate them if there are no CONFIG_FILES. +# This happens for instance with `./config.status config.h'. +if test -n "$CONFIG_FILES"; then + + +ac_cr=' ' +ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null` +if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then + ac_cs_awk_cr='\\r' +else + ac_cs_awk_cr=$ac_cr +fi + +echo 'BEGIN {' >"$tmp/subs1.awk" && +_ACEOF + + +{ + echo "cat >conf$$subs.awk <<_ACEOF" && + echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && + echo "_ACEOF" +} >conf$$subs.sh || + { { $as_echo "$as_me:$LINENO: error: could not make $CONFIG_STATUS" >&5 +$as_echo "$as_me: error: could not make $CONFIG_STATUS" >&2;} + { (exit 1); exit 1; }; } +ac_delim_num=`echo "$ac_subst_vars" | grep -c '$'` +ac_delim='%!_!# ' +for ac_last_try in false false false false false :; do + . ./conf$$subs.sh || + { { $as_echo "$as_me:$LINENO: error: could not make $CONFIG_STATUS" >&5 +$as_echo "$as_me: error: could not make $CONFIG_STATUS" >&2;} + { (exit 1); exit 1; }; } + + ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` + if test $ac_delim_n = $ac_delim_num; then + break + elif $ac_last_try; then + { { $as_echo "$as_me:$LINENO: error: could not make $CONFIG_STATUS" >&5 +$as_echo "$as_me: error: could not make $CONFIG_STATUS" >&2;} + { (exit 1); exit 1; }; } + else + ac_delim="$ac_delim!$ac_delim _$ac_delim!! " + fi +done +rm -f conf$$subs.sh + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +cat >>"\$tmp/subs1.awk" <<\\_ACAWK && +_ACEOF +sed -n ' +h +s/^/S["/; s/!.*/"]=/ +p +g +s/^[^!]*!// +:repl +t repl +s/'"$ac_delim"'$// +t delim +:nl +h +s/\(.\{148\}\).*/\1/ +t more1 +s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ +p +n +b repl +:more1 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t nl +:delim +h +s/\(.\{148\}\).*/\1/ +t more2 +s/["\\]/\\&/g; s/^/"/; s/$/"/ +p +b +:more2 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t delim +' >$CONFIG_STATUS || ac_write_fail=1 +rm -f conf$$subs.awk +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +_ACAWK +cat >>"\$tmp/subs1.awk" <<_ACAWK && + for (key in S) S_is_set[key] = 1 + FS = "" + +} +{ + line = $ 0 + nfields = split(line, field, "@") + substed = 0 + len = length(field[1]) + for (i = 2; i < nfields; i++) { + key = field[i] + keylen = length(key) + if (S_is_set[key]) { + value = S[key] + line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) + len += length(value) + length(field[++i]) + substed = 1 + } else + len += 1 + keylen + } + + print line +} + +_ACAWK +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then + sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" +else + cat +fi < "$tmp/subs1.awk" > "$tmp/subs.awk" \ + || { { $as_echo "$as_me:$LINENO: error: could not setup config files machinery" >&5 +$as_echo "$as_me: error: could not setup config files machinery" >&2;} + { (exit 1); exit 1; }; } +_ACEOF + +# VPATH may cause trouble with some makes, so we remove $(srcdir), +# ${srcdir} and @srcdir@ from VPATH if srcdir is ".", strip leading and +# trailing colons and then remove the whole line if VPATH becomes empty +# (actually we leave an empty line to preserve line numbers). +if test "x$srcdir" = x.; then + ac_vpsub='/^[ ]*VPATH[ ]*=/{ +s/:*\$(srcdir):*/:/ +s/:*\${srcdir}:*/:/ +s/:*@srcdir@:*/:/ +s/^\([^=]*=[ ]*\):*/\1/ +s/:*$// +s/^[^=]*=[ ]*$// +}' +fi + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +fi # test -n "$CONFIG_FILES" + +# Set up the scripts for CONFIG_HEADERS section. +# No need to generate them if there are no CONFIG_HEADERS. +# This happens for instance with `./config.status Makefile'. +if test -n "$CONFIG_HEADERS"; then +cat >"$tmp/defines.awk" <<\_ACAWK || +BEGIN { +_ACEOF + +# Transform confdefs.h into an awk script `defines.awk', embedded as +# here-document in config.status, that substitutes the proper values into +# config.h.in to produce config.h. + +# Create a delimiter string that does not exist in confdefs.h, to ease +# handling of long lines. +ac_delim='%!_!# ' +for ac_last_try in false false :; do + ac_t=`sed -n "/$ac_delim/p" confdefs.h` + if test -z "$ac_t"; then + break + elif $ac_last_try; then + { { $as_echo "$as_me:$LINENO: error: could not make $CONFIG_HEADERS" >&5 +$as_echo "$as_me: error: could not make $CONFIG_HEADERS" >&2;} + { (exit 1); exit 1; }; } + else + ac_delim="$ac_delim!$ac_delim _$ac_delim!! " + fi +done + +# For the awk script, D is an array of macro values keyed by name, +# likewise P contains macro parameters if any. Preserve backslash +# newline sequences. + +ac_word_re=[_$as_cr_Letters][_$as_cr_alnum]* +sed -n ' +s/.\{148\}/&'"$ac_delim"'/g +t rset +:rset +s/^[ ]*#[ ]*define[ ][ ]*/ / +t def +d +:def +s/\\$// +t bsnl +s/["\\]/\\&/g +s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ +D["\1"]=" \3"/p +s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2"/p +d +:bsnl +s/["\\]/\\&/g +s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ +D["\1"]=" \3\\\\\\n"\\/p +t cont +s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2\\\\\\n"\\/p +t cont +d +:cont +n +s/.\{148\}/&'"$ac_delim"'/g +t clear +:clear +s/\\$// +t bsnlc +s/["\\]/\\&/g; s/^/"/; s/$/"/p +d +:bsnlc +s/["\\]/\\&/g; s/^/"/; s/$/\\\\\\n"\\/p +b cont +' >$CONFIG_STATUS || ac_write_fail=1 + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 + for (key in D) D_is_set[key] = 1 + FS = "" +} +/^[\t ]*#[\t ]*(define|undef)[\t ]+$ac_word_re([\t (]|\$)/ { + line = \$ 0 + split(line, arg, " ") + if (arg[1] == "#") { + defundef = arg[2] + mac1 = arg[3] + } else { + defundef = substr(arg[1], 2) + mac1 = arg[2] + } + split(mac1, mac2, "(") #) + macro = mac2[1] + prefix = substr(line, 1, index(line, defundef) - 1) + if (D_is_set[macro]) { + # Preserve the white space surrounding the "#". + print prefix "define", macro P[macro] D[macro] + next + } else { + # Replace #undef with comments. This is necessary, for example, + # in the case of _POSIX_SOURCE, which is predefined and required + # on some systems where configure will not decide to define it. + if (defundef == "undef") { + print "/*", prefix defundef, macro, "*/" + next + } + } +} +{ print } +_ACAWK +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 + { { $as_echo "$as_me:$LINENO: error: could not setup config headers machinery" >&5 +$as_echo "$as_me: error: could not setup config headers machinery" >&2;} + { (exit 1); exit 1; }; } +fi # test -n "$CONFIG_HEADERS" + + +eval set X " :F $CONFIG_FILES :H $CONFIG_HEADERS :C $CONFIG_COMMANDS" +shift +for ac_tag +do + case $ac_tag in + :[FHLC]) ac_mode=$ac_tag; continue;; + esac + case $ac_mode$ac_tag in + :[FHL]*:*);; + :L* | :C*:*) { { $as_echo "$as_me:$LINENO: error: invalid tag $ac_tag" >&5 +$as_echo "$as_me: error: invalid tag $ac_tag" >&2;} + { (exit 1); exit 1; }; };; + :[FH]-) ac_tag=-:-;; + :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; + esac + ac_save_IFS=$IFS + IFS=: + set x $ac_tag + IFS=$ac_save_IFS + shift + ac_file=$1 + shift + + case $ac_mode in + :L) ac_source=$1;; + :[FH]) + ac_file_inputs= + for ac_f + do + case $ac_f in + -) ac_f="$tmp/stdin";; + *) # Look for the file first in the build tree, then in the source tree + # (if the path is not absolute). The absolute path cannot be DOS-style, + # because $ac_f cannot contain `:'. + test -f "$ac_f" || + case $ac_f in + [\\/$]*) false;; + *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; + esac || + { { $as_echo "$as_me:$LINENO: error: cannot find input file: $ac_f" >&5 +$as_echo "$as_me: error: cannot find input file: $ac_f" >&2;} + { (exit 1); exit 1; }; };; + esac + case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac + ac_file_inputs="$ac_file_inputs '$ac_f'" + done + + # Let's still pretend it is `configure' which instantiates (i.e., don't + # use $as_me), people would be surprised to read: + # /* config.h. Generated by config.status. */ + configure_input='Generated from '` + $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' + `' by configure.' + if test x"$ac_file" != x-; then + configure_input="$ac_file. $configure_input" + { $as_echo "$as_me:$LINENO: creating $ac_file" >&5 +$as_echo "$as_me: creating $ac_file" >&6;} + fi + # Neutralize special characters interpreted by sed in replacement strings. + case $configure_input in #( + *\&* | *\|* | *\\* ) + ac_sed_conf_input=`$as_echo "$configure_input" | + sed 's/[\\\\&|]/\\\\&/g'`;; #( + *) ac_sed_conf_input=$configure_input;; + esac + + case $ac_tag in + *:-:* | *:-) cat >"$tmp/stdin" \ + || { { $as_echo "$as_me:$LINENO: error: could not create $ac_file" >&5 +$as_echo "$as_me: error: could not create $ac_file" >&2;} + { (exit 1); exit 1; }; } ;; + esac + ;; + esac + + ac_dir=`$as_dirname -- "$ac_file" || +$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$ac_file" : 'X\(//\)[^/]' \| \ + X"$ac_file" : 'X\(//\)$' \| \ + X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$ac_file" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + { as_dir="$ac_dir" + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || { $as_mkdir_p && mkdir -p "$as_dir"; } || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || { { $as_echo "$as_me:$LINENO: error: cannot create directory $as_dir" >&5 +$as_echo "$as_me: error: cannot create directory $as_dir" >&2;} + { (exit 1); exit 1; }; }; } + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + + case $ac_mode in + :F) + # + # CONFIG_FILE + # + + case $INSTALL in + [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;; + *) ac_INSTALL=$ac_top_build_prefix$INSTALL ;; + esac + ac_MKDIR_P=$MKDIR_P + case $MKDIR_P in + [\\/$]* | ?:[\\/]* ) ;; + */*) ac_MKDIR_P=$ac_top_build_prefix$MKDIR_P ;; + esac +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# If the template does not know about datarootdir, expand it. +# FIXME: This hack should be removed a few years after 2.60. +ac_datarootdir_hack=; ac_datarootdir_seen= + +ac_sed_dataroot=' +/datarootdir/ { + p + q +} +/@datadir@/p +/@docdir@/p +/@infodir@/p +/@localedir@/p +/@mandir@/p +' +case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in +*datarootdir*) ac_datarootdir_seen=yes;; +*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) + { $as_echo "$as_me:$LINENO: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 +$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 + ac_datarootdir_hack=' + s&@datadir@&$datadir&g + s&@docdir@&$docdir&g + s&@infodir@&$infodir&g + s&@localedir@&$localedir&g + s&@mandir@&$mandir&g + s&\\\${datarootdir}&$datarootdir&g' ;; +esac +_ACEOF + +# Neutralize VPATH when `$srcdir' = `.'. +# Shell code in configure.ac might set extrasub. +# FIXME: do we really want to maintain this feature? +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_sed_extra="$ac_vpsub +$extrasub +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +:t +/@[a-zA-Z_][a-zA-Z_0-9]*@/!b +s|@configure_input@|$ac_sed_conf_input|;t t +s&@top_builddir@&$ac_top_builddir_sub&;t t +s&@top_build_prefix@&$ac_top_build_prefix&;t t +s&@srcdir@&$ac_srcdir&;t t +s&@abs_srcdir@&$ac_abs_srcdir&;t t +s&@top_srcdir@&$ac_top_srcdir&;t t +s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t +s&@builddir@&$ac_builddir&;t t +s&@abs_builddir@&$ac_abs_builddir&;t t +s&@abs_top_builddir@&$ac_abs_top_builddir&;t t +s&@INSTALL@&$ac_INSTALL&;t t +s&@MKDIR_P@&$ac_MKDIR_P&;t t +$ac_datarootdir_hack +" +eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$tmp/subs.awk" >$tmp/out \ + || { { $as_echo "$as_me:$LINENO: error: could not create $ac_file" >&5 +$as_echo "$as_me: error: could not create $ac_file" >&2;} + { (exit 1); exit 1; }; } + +test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && + { ac_out=`sed -n '/\${datarootdir}/p' "$tmp/out"`; test -n "$ac_out"; } && + { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' "$tmp/out"`; test -z "$ac_out"; } && + { $as_echo "$as_me:$LINENO: WARNING: $ac_file contains a reference to the variable \`datarootdir' +which seems to be undefined. Please make sure it is defined." >&5 +$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' +which seems to be undefined. Please make sure it is defined." >&2;} + + rm -f "$tmp/stdin" + case $ac_file in + -) cat "$tmp/out" && rm -f "$tmp/out";; + *) rm -f "$ac_file" && mv "$tmp/out" "$ac_file";; + esac \ + || { { $as_echo "$as_me:$LINENO: error: could not create $ac_file" >&5 +$as_echo "$as_me: error: could not create $ac_file" >&2;} + { (exit 1); exit 1; }; } + ;; + :H) + # + # CONFIG_HEADER + # + if test x"$ac_file" != x-; then + { + $as_echo "/* $configure_input */" \ + && eval '$AWK -f "$tmp/defines.awk"' "$ac_file_inputs" + } >"$tmp/config.h" \ + || { { $as_echo "$as_me:$LINENO: error: could not create $ac_file" >&5 +$as_echo "$as_me: error: could not create $ac_file" >&2;} + { (exit 1); exit 1; }; } + if diff "$ac_file" "$tmp/config.h" >/dev/null 2>&1; then + { $as_echo "$as_me:$LINENO: $ac_file is unchanged" >&5 +$as_echo "$as_me: $ac_file is unchanged" >&6;} + else + rm -f "$ac_file" + mv "$tmp/config.h" "$ac_file" \ + || { { $as_echo "$as_me:$LINENO: error: could not create $ac_file" >&5 +$as_echo "$as_me: error: could not create $ac_file" >&2;} + { (exit 1); exit 1; }; } + fi + else + $as_echo "/* $configure_input */" \ + && eval '$AWK -f "$tmp/defines.awk"' "$ac_file_inputs" \ + || { { $as_echo "$as_me:$LINENO: error: could not create -" >&5 +$as_echo "$as_me: error: could not create -" >&2;} + { (exit 1); exit 1; }; } + fi +# Compute "$ac_file"'s index in $config_headers. +_am_arg="$ac_file" +_am_stamp_count=1 +for _am_header in $config_headers :; do + case $_am_header in + $_am_arg | $_am_arg:* ) + break ;; + * ) + _am_stamp_count=`expr $_am_stamp_count + 1` ;; + esac +done +echo "timestamp for $_am_arg" >`$as_dirname -- "$_am_arg" || +$as_expr X"$_am_arg" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$_am_arg" : 'X\(//\)[^/]' \| \ + X"$_am_arg" : 'X\(//\)$' \| \ + X"$_am_arg" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$_am_arg" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'`/stamp-h$_am_stamp_count + ;; + + :C) { $as_echo "$as_me:$LINENO: executing $ac_file commands" >&5 +$as_echo "$as_me: executing $ac_file commands" >&6;} + ;; + esac + + + case $ac_file$ac_mode in + "depfiles":C) test x"$AMDEP_TRUE" != x"" || for mf in $CONFIG_FILES; do + # Strip MF so we end up with the name of the file. + mf=`echo "$mf" | sed -e 's/:.*$//'` + # Check whether this is an Automake generated Makefile or not. + # We used to match only the files named `Makefile.in', but + # some people rename them; so instead we look at the file content. + # Grep'ing the first line is not enough: some people post-process + # each Makefile.in and add a new line on top of each file to say so. + # Grep'ing the whole file is not good either: AIX grep has a line + # limit of 2048, but all sed's we know have understand at least 4000. + if sed -n 's,^#.*generated by automake.*,X,p' "$mf" | grep X >/dev/null 2>&1; then + dirpart=`$as_dirname -- "$mf" || +$as_expr X"$mf" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$mf" : 'X\(//\)[^/]' \| \ + X"$mf" : 'X\(//\)$' \| \ + X"$mf" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$mf" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + else + continue + fi + # Extract the definition of DEPDIR, am__include, and am__quote + # from the Makefile without running `make'. + DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"` + test -z "$DEPDIR" && continue + am__include=`sed -n 's/^am__include = //p' < "$mf"` + test -z "am__include" && continue + am__quote=`sed -n 's/^am__quote = //p' < "$mf"` + # When using ansi2knr, U may be empty or an underscore; expand it + U=`sed -n 's/^U = //p' < "$mf"` + # Find all dependency output files, they are included files with + # $(DEPDIR) in their names. We invoke sed twice because it is the + # simplest approach to changing $(DEPDIR) to its actual value in the + # expansion. + for file in `sed -n " + s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \ + sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g' -e 's/\$U/'"$U"'/g'`; do + # Make sure the directory exists. + test -f "$dirpart/$file" && continue + fdir=`$as_dirname -- "$file" || +$as_expr X"$file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$file" : 'X\(//\)[^/]' \| \ + X"$file" : 'X\(//\)$' \| \ + X"$file" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$file" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + { as_dir=$dirpart/$fdir + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || { $as_mkdir_p && mkdir -p "$as_dir"; } || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || { { $as_echo "$as_me:$LINENO: error: cannot create directory $as_dir" >&5 +$as_echo "$as_me: error: cannot create directory $as_dir" >&2;} + { (exit 1); exit 1; }; }; } + # echo "creating $dirpart/$file" + echo '# dummy' > "$dirpart/$file" + done +done + ;; + + esac +done # for ac_tag + + +{ (exit 0); exit 0; } +_ACEOF +chmod +x $CONFIG_STATUS +ac_clean_files=$ac_clean_files_save + +test $ac_write_fail = 0 || + { { $as_echo "$as_me:$LINENO: error: write failure creating $CONFIG_STATUS" >&5 +$as_echo "$as_me: error: write failure creating $CONFIG_STATUS" >&2;} + { (exit 1); exit 1; }; } + + +# configure is writing to config.log, and then calls config.status. +# config.status does its own redirection, appending to config.log. +# Unfortunately, on DOS this fails, as config.log is still kept open +# by configure, so config.status won't be able to write to it; its +# output is simply discarded. So we exec the FD to /dev/null, +# effectively closing config.log, so it can be properly (re)opened and +# appended to by config.status. When coming back to configure, we +# need to make the FD available again. +if test "$no_create" != yes; then + ac_cs_success=: + ac_config_status_args= + test "$silent" = yes && + ac_config_status_args="$ac_config_status_args --quiet" + exec 5>/dev/null + $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false + exec 5>>config.log + # Use ||, not &&, to avoid exiting from the if with $? = 1, which + # would make configure fail if this is the last instruction. + $ac_cs_success || { (exit 1); exit 1; } +fi +if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then + { $as_echo "$as_me:$LINENO: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 +$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} +fi + diff --git a/third_party/libevent/configure.in b/third_party/libevent/configure.in new file mode 100644 index 0000000000..bf21399a64 --- /dev/null +++ b/third_party/libevent/configure.in @@ -0,0 +1,395 @@ +dnl configure.in for libevent +dnl Dug Song +AC_INIT(event.c) + +AM_INIT_AUTOMAKE(libevent,1.4.13-stable) +AM_CONFIG_HEADER(config.h) +dnl AM_MAINTAINER_MODE + +dnl Initialize prefix. +if test "$prefix" = "NONE"; then + prefix="/usr/local" +fi + +dnl Checks for programs. +AC_PROG_CC +AC_PROG_INSTALL +AC_PROG_LN_S + +AC_PROG_GCC_TRADITIONAL +if test "$GCC" = yes ; then + CFLAGS="$CFLAGS -Wall" + # And disable the strict-aliasing optimization, since it breaks + # our sockaddr-handling code in strange ways. + CFLAGS="$CFLAGS -fno-strict-aliasing" +fi + +AC_ARG_ENABLE(gcc-warnings, + AS_HELP_STRING(--enable-gcc-warnings, enable verbose warnings with GCC)) + +AC_PROG_LIBTOOL + +dnl Uncomment "AC_DISABLE_SHARED" to make shared librraries not get +dnl built by default. You can also turn shared libs on and off from +dnl the command line with --enable-shared and --disable-shared. +dnl AC_DISABLE_SHARED +AC_SUBST(LIBTOOL_DEPS) + +dnl Checks for libraries. +AC_CHECK_LIB(socket, socket) +AC_CHECK_LIB(resolv, inet_aton) +AC_CHECK_LIB(rt, clock_gettime) +AC_CHECK_LIB(nsl, inet_ntoa) + +dnl Checks for header files. +AC_HEADER_STDC +AC_CHECK_HEADERS(fcntl.h stdarg.h inttypes.h stdint.h poll.h signal.h unistd.h sys/epoll.h sys/time.h sys/queue.h sys/event.h sys/param.h sys/ioctl.h sys/select.h sys/devpoll.h port.h netinet/in6.h sys/socket.h) +if test "x$ac_cv_header_sys_queue_h" = "xyes"; then + AC_MSG_CHECKING(for TAILQ_FOREACH in sys/queue.h) + AC_EGREP_CPP(yes, +[ +#include +#ifdef TAILQ_FOREACH + yes +#endif +], [AC_MSG_RESULT(yes) + AC_DEFINE(HAVE_TAILQFOREACH, 1, + [Define if TAILQ_FOREACH is defined in ])], + AC_MSG_RESULT(no) + ) +fi + +if test "x$ac_cv_header_sys_time_h" = "xyes"; then + AC_MSG_CHECKING(for timeradd in sys/time.h) + AC_EGREP_CPP(yes, +[ +#include +#ifdef timeradd + yes +#endif +], [ AC_DEFINE(HAVE_TIMERADD, 1, + [Define if timeradd is defined in ]) + AC_MSG_RESULT(yes)] ,AC_MSG_RESULT(no) +) +fi + +if test "x$ac_cv_header_sys_time_h" = "xyes"; then + AC_MSG_CHECKING(for timercmp in sys/time.h) + AC_EGREP_CPP(yes, +[ +#include +#ifdef timercmp + yes +#endif +], [ AC_DEFINE(HAVE_TIMERCMP, 1, + [Define if timercmp is defined in ]) + AC_MSG_RESULT(yes)] ,AC_MSG_RESULT(no) +) +fi + +if test "x$ac_cv_header_sys_time_h" = "xyes"; then + AC_MSG_CHECKING(for timerclear in sys/time.h) + AC_EGREP_CPP(yes, +[ +#include +#ifdef timerclear + yes +#endif +], [ AC_DEFINE(HAVE_TIMERCLEAR, 1, + [Define if timerclear is defined in ]) + AC_MSG_RESULT(yes)] ,AC_MSG_RESULT(no) +) +fi + +if test "x$ac_cv_header_sys_time_h" = "xyes"; then + AC_MSG_CHECKING(for timerisset in sys/time.h) + AC_EGREP_CPP(yes, +[ +#include +#ifdef timerisset + yes +#endif +], [ AC_DEFINE(HAVE_TIMERISSET, 1, + [Define if timerisset is defined in ]) + AC_MSG_RESULT(yes)] ,AC_MSG_RESULT(no) +) +fi + +dnl - check if the macro WIN32 is defined on this compiler. +dnl - (this is how we check for a windows version of GCC) +AC_MSG_CHECKING(for WIN32) +AC_TRY_COMPILE(, + [ +#ifndef WIN32 +die horribly +#endif + ], + bwin32=true; AC_MSG_RESULT(yes), + bwin32=false; AC_MSG_RESULT(no), +) + +AM_CONDITIONAL(BUILD_WIN32, test x$bwin32 = xtrue) + +dnl Checks for typedefs, structures, and compiler characteristics. +AC_C_CONST +AC_C_INLINE +AC_HEADER_TIME + +dnl Checks for library functions. +AC_CHECK_FUNCS(gettimeofday vasprintf fcntl clock_gettime strtok_r strsep getaddrinfo getnameinfo strlcpy inet_ntop signal sigaction strtoll issetugid geteuid getegid) + +AC_CHECK_SIZEOF(long) + +if test "x$ac_cv_func_clock_gettime" = "xyes"; then + AC_DEFINE(DNS_USE_CPU_CLOCK_FOR_ID, 1, [Define if clock_gettime is available in libc]) +else + AC_DEFINE(DNS_USE_GETTIMEOFDAY_FOR_ID, 1, [Define is no secure id variant is available]) +fi + +AC_MSG_CHECKING(for F_SETFD in fcntl.h) +AC_EGREP_CPP(yes, +[ +#define _GNU_SOURCE +#include +#ifdef F_SETFD +yes +#endif +], [ AC_DEFINE(HAVE_SETFD, 1, + [Define if F_SETFD is defined in ]) + AC_MSG_RESULT(yes) ], AC_MSG_RESULT(no)) + +needsignal=no +haveselect=no +AC_CHECK_FUNCS(select, [haveselect=yes], ) +if test "x$haveselect" = "xyes" ; then + AC_LIBOBJ(select) + needsignal=yes +fi + +havepoll=no +AC_CHECK_FUNCS(poll, [havepoll=yes], ) +if test "x$havepoll" = "xyes" ; then + AC_LIBOBJ(poll) + needsignal=yes +fi + +haveepoll=no +AC_CHECK_FUNCS(epoll_ctl, [haveepoll=yes], ) +if test "x$haveepoll" = "xyes" ; then + AC_DEFINE(HAVE_EPOLL, 1, + [Define if your system supports the epoll system calls]) + AC_LIBOBJ(epoll) + needsignal=yes +fi + +havedevpoll=no +if test "x$ac_cv_header_sys_devpoll_h" = "xyes"; then + AC_DEFINE(HAVE_DEVPOLL, 1, + [Define if /dev/poll is available]) + AC_LIBOBJ(devpoll) +fi + +havekqueue=no +if test "x$ac_cv_header_sys_event_h" = "xyes"; then + AC_CHECK_FUNCS(kqueue, [havekqueue=yes], ) + if test "x$havekqueue" = "xyes" ; then + AC_MSG_CHECKING(for working kqueue) + AC_TRY_RUN( +#include +#include +#include +#include +#include +#include + +int +main(int argc, char **argv) +{ + int kq; + int n; + int fd[[2]]; + struct kevent ev; + struct timespec ts; + char buf[[8000]]; + + if (pipe(fd) == -1) + exit(1); + if (fcntl(fd[[1]], F_SETFL, O_NONBLOCK) == -1) + exit(1); + + while ((n = write(fd[[1]], buf, sizeof(buf))) == sizeof(buf)) + ; + + if ((kq = kqueue()) == -1) + exit(1); + + ev.ident = fd[[1]]; + ev.filter = EVFILT_WRITE; + ev.flags = EV_ADD | EV_ENABLE; + n = kevent(kq, &ev, 1, NULL, 0, NULL); + if (n == -1) + exit(1); + + read(fd[[0]], buf, sizeof(buf)); + + ts.tv_sec = 0; + ts.tv_nsec = 0; + n = kevent(kq, NULL, 0, &ev, 1, &ts); + if (n == -1 || n == 0) + exit(1); + + exit(0); +}, [AC_MSG_RESULT(yes) + AC_DEFINE(HAVE_WORKING_KQUEUE, 1, + [Define if kqueue works correctly with pipes]) + AC_LIBOBJ(kqueue)], AC_MSG_RESULT(no), AC_MSG_RESULT(no)) + fi +fi + +haveepollsyscall=no +if test "x$ac_cv_header_sys_epoll_h" = "xyes"; then + if test "x$haveepoll" = "xno" ; then + AC_MSG_CHECKING(for epoll system call) + AC_TRY_RUN( +#include +#include +#include +#include +#include +#include + +int +epoll_create(int size) +{ + return (syscall(__NR_epoll_create, size)); +} + +int +main(int argc, char **argv) +{ + int epfd; + + epfd = epoll_create(256); + exit (epfd == -1 ? 1 : 0); +}, [AC_MSG_RESULT(yes) + AC_DEFINE(HAVE_EPOLL, 1, + [Define if your system supports the epoll system calls]) + needsignal=yes + AC_LIBOBJ(epoll_sub) + AC_LIBOBJ(epoll)], AC_MSG_RESULT(no), AC_MSG_RESULT(no)) + fi +fi + +haveeventports=no +AC_CHECK_FUNCS(port_create, [haveeventports=yes], ) +if test "x$haveeventports" = "xyes" ; then + AC_DEFINE(HAVE_EVENT_PORTS, 1, + [Define if your system supports event ports]) + AC_LIBOBJ(evport) + needsignal=yes +fi +if test "x$bwin32" = "xtrue"; then + needsignal=yes +fi +if test "x$bwin32" = "xtrue"; then + needsignal=yes +fi +if test "x$needsignal" = "xyes" ; then + AC_LIBOBJ(signal) +fi + +AC_TYPE_PID_T +AC_TYPE_SIZE_T +AC_CHECK_TYPES([uint64_t, uint32_t, uint16_t, uint8_t], , , +[#ifdef HAVE_STDINT_H +#include +#elif defined(HAVE_INTTYPES_H) +#include +#endif +#ifdef HAVE_SYS_TYPES_H +#include +#endif]) +AC_CHECK_TYPES([fd_mask], , , +[#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SELECT_H +#include +#endif]) + +AC_CHECK_SIZEOF(long long) +AC_CHECK_SIZEOF(int) +AC_CHECK_SIZEOF(short) +AC_CHECK_TYPES([struct in6_addr], , , +[#ifdef WIN32 +#include +#else +#include +#include +#include +#endif +#ifdef HAVE_NETINET_IN6_H +#include +#endif]) + +AC_MSG_CHECKING([for socklen_t]) +AC_TRY_COMPILE([ + #include + #include ], + [socklen_t x;], + AC_MSG_RESULT([yes]), + [AC_MSG_RESULT([no]) + AC_DEFINE(socklen_t, unsigned int, + [Define to unsigned int if you dont have it])] +) + +AC_MSG_CHECKING([whether our compiler supports __func__]) +AC_TRY_COMPILE([], + [ const char *cp = __func__; ], + AC_MSG_RESULT([yes]), + AC_MSG_RESULT([no]) + AC_MSG_CHECKING([whether our compiler supports __FUNCTION__]) + AC_TRY_COMPILE([], + [ const char *cp = __FUNCTION__; ], + AC_MSG_RESULT([yes]) + AC_DEFINE(__func__, __FUNCTION__, + [Define to appropriate substitue if compiler doesnt have __func__]), + AC_MSG_RESULT([no]) + AC_DEFINE(__func__, __FILE__, + [Define to appropriate substitue if compiler doesnt have __func__]))) + + +# Add some more warnings which we use in development but not in the +# released versions. (Some relevant gcc versions can't handle these.) +if test x$enable_gcc_warnings = xyes; then + + AC_COMPILE_IFELSE(AC_LANG_PROGRAM([], [ +#if !defined(__GNUC__) || (__GNUC__ < 4) +#error +#endif]), have_gcc4=yes, have_gcc4=no) + + AC_COMPILE_IFELSE(AC_LANG_PROGRAM([], [ +#if !defined(__GNUC__) || (__GNUC__ < 4) || (__GNUC__ == 4 && __GNUC_MINOR__ < 2) +#error +#endif]), have_gcc42=yes, have_gcc42=no) + + CFLAGS="$CFLAGS -W -Wfloat-equal -Wundef -Wpointer-arith -Wstrict-prototypes -Wmissing-prototypes -Wwrite-strings -Wredundant-decls -Wchar-subscripts -Wcomment -Wformat=2 -Wwrite-strings -Wmissing-declarations -Wredundant-decls -Wnested-externs -Wbad-function-cast -Wswitch-enum -Werror" + CFLAGS="$CFLAGS -Wno-unused-parameter -Wno-sign-compare -Wstrict-aliasing" + + if test x$have_gcc4 = xyes ; then + # These warnings break gcc 3.3.5 and work on gcc 4.0.2 + CFLAGS="$CFLAGS -Winit-self -Wmissing-field-initializers -Wdeclaration-after-statement" + #CFLAGS="$CFLAGS -Wold-style-definition" + fi + + if test x$have_gcc42 = xyes ; then + # These warnings break gcc 4.0.2 and work on gcc 4.2 + CFLAGS="$CFLAGS -Waddress -Wnormalized=id -Woverride-init" + fi + +##This will break the world on some 64-bit architectures +# CFLAGS="$CFLAGS -Winline" + +fi + +AC_OUTPUT(Makefile test/Makefile sample/Makefile) diff --git a/third_party/libevent/devpoll.c b/third_party/libevent/devpoll.c new file mode 100644 index 0000000000..2d34ae3400 --- /dev/null +++ b/third_party/libevent/devpoll.c @@ -0,0 +1,417 @@ +/* + * Copyright 2000-2004 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#ifdef HAVE_SYS_TIME_H +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "event.h" +#include "event-internal.h" +#include "evsignal.h" +#include "log.h" + +/* due to limitations in the devpoll interface, we need to keep track of + * all file descriptors outself. + */ +struct evdevpoll { + struct event *evread; + struct event *evwrite; +}; + +struct devpollop { + struct evdevpoll *fds; + int nfds; + struct pollfd *events; + int nevents; + int dpfd; + struct pollfd *changes; + int nchanges; +}; + +static void *devpoll_init (struct event_base *); +static int devpoll_add (void *, struct event *); +static int devpoll_del (void *, struct event *); +static int devpoll_dispatch (struct event_base *, void *, struct timeval *); +static void devpoll_dealloc (struct event_base *, void *); + +const struct eventop devpollops = { + "devpoll", + devpoll_init, + devpoll_add, + devpoll_del, + devpoll_dispatch, + devpoll_dealloc, + 1 /* need reinit */ +}; + +#define NEVENT 32000 + +static int +devpoll_commit(struct devpollop *devpollop) +{ + /* + * Due to a bug in Solaris, we have to use pwrite with an offset of 0. + * Write is limited to 2GB of data, until it will fail. + */ + if (pwrite(devpollop->dpfd, devpollop->changes, + sizeof(struct pollfd) * devpollop->nchanges, 0) == -1) + return(-1); + + devpollop->nchanges = 0; + return(0); +} + +static int +devpoll_queue(struct devpollop *devpollop, int fd, int events) { + struct pollfd *pfd; + + if (devpollop->nchanges >= devpollop->nevents) { + /* + * Change buffer is full, must commit it to /dev/poll before + * adding more + */ + if (devpoll_commit(devpollop) != 0) + return(-1); + } + + pfd = &devpollop->changes[devpollop->nchanges++]; + pfd->fd = fd; + pfd->events = events; + pfd->revents = 0; + + return(0); +} + +static void * +devpoll_init(struct event_base *base) +{ + int dpfd, nfiles = NEVENT; + struct rlimit rl; + struct devpollop *devpollop; + + /* Disable devpoll when this environment variable is set */ + if (evutil_getenv("EVENT_NODEVPOLL")) + return (NULL); + + if (!(devpollop = calloc(1, sizeof(struct devpollop)))) + return (NULL); + + if (getrlimit(RLIMIT_NOFILE, &rl) == 0 && + rl.rlim_cur != RLIM_INFINITY) + nfiles = rl.rlim_cur; + + /* Initialize the kernel queue */ + if ((dpfd = open("/dev/poll", O_RDWR)) == -1) { + event_warn("open: /dev/poll"); + free(devpollop); + return (NULL); + } + + devpollop->dpfd = dpfd; + + /* Initialize fields */ + devpollop->events = calloc(nfiles, sizeof(struct pollfd)); + if (devpollop->events == NULL) { + free(devpollop); + close(dpfd); + return (NULL); + } + devpollop->nevents = nfiles; + + devpollop->fds = calloc(nfiles, sizeof(struct evdevpoll)); + if (devpollop->fds == NULL) { + free(devpollop->events); + free(devpollop); + close(dpfd); + return (NULL); + } + devpollop->nfds = nfiles; + + devpollop->changes = calloc(nfiles, sizeof(struct pollfd)); + if (devpollop->changes == NULL) { + free(devpollop->fds); + free(devpollop->events); + free(devpollop); + close(dpfd); + return (NULL); + } + + evsignal_init(base); + + return (devpollop); +} + +static int +devpoll_recalc(struct event_base *base, void *arg, int max) +{ + struct devpollop *devpollop = arg; + + if (max >= devpollop->nfds) { + struct evdevpoll *fds; + int nfds; + + nfds = devpollop->nfds; + while (nfds <= max) + nfds <<= 1; + + fds = realloc(devpollop->fds, nfds * sizeof(struct evdevpoll)); + if (fds == NULL) { + event_warn("realloc"); + return (-1); + } + devpollop->fds = fds; + memset(fds + devpollop->nfds, 0, + (nfds - devpollop->nfds) * sizeof(struct evdevpoll)); + devpollop->nfds = nfds; + } + + return (0); +} + +static int +devpoll_dispatch(struct event_base *base, void *arg, struct timeval *tv) +{ + struct devpollop *devpollop = arg; + struct pollfd *events = devpollop->events; + struct dvpoll dvp; + struct evdevpoll *evdp; + int i, res, timeout = -1; + + if (devpollop->nchanges) + devpoll_commit(devpollop); + + if (tv != NULL) + timeout = tv->tv_sec * 1000 + (tv->tv_usec + 999) / 1000; + + dvp.dp_fds = devpollop->events; + dvp.dp_nfds = devpollop->nevents; + dvp.dp_timeout = timeout; + + res = ioctl(devpollop->dpfd, DP_POLL, &dvp); + + if (res == -1) { + if (errno != EINTR) { + event_warn("ioctl: DP_POLL"); + return (-1); + } + + evsignal_process(base); + return (0); + } else if (base->sig.evsignal_caught) { + evsignal_process(base); + } + + event_debug(("%s: devpoll_wait reports %d", __func__, res)); + + for (i = 0; i < res; i++) { + int which = 0; + int what = events[i].revents; + struct event *evread = NULL, *evwrite = NULL; + + assert(events[i].fd < devpollop->nfds); + evdp = &devpollop->fds[events[i].fd]; + + if (what & POLLHUP) + what |= POLLIN | POLLOUT; + else if (what & POLLERR) + what |= POLLIN | POLLOUT; + + if (what & POLLIN) { + evread = evdp->evread; + which |= EV_READ; + } + + if (what & POLLOUT) { + evwrite = evdp->evwrite; + which |= EV_WRITE; + } + + if (!which) + continue; + + if (evread != NULL && !(evread->ev_events & EV_PERSIST)) + event_del(evread); + if (evwrite != NULL && evwrite != evread && + !(evwrite->ev_events & EV_PERSIST)) + event_del(evwrite); + + if (evread != NULL) + event_active(evread, EV_READ, 1); + if (evwrite != NULL) + event_active(evwrite, EV_WRITE, 1); + } + + return (0); +} + + +static int +devpoll_add(void *arg, struct event *ev) +{ + struct devpollop *devpollop = arg; + struct evdevpoll *evdp; + int fd, events; + + if (ev->ev_events & EV_SIGNAL) + return (evsignal_add(ev)); + + fd = ev->ev_fd; + if (fd >= devpollop->nfds) { + /* Extend the file descriptor array as necessary */ + if (devpoll_recalc(ev->ev_base, devpollop, fd) == -1) + return (-1); + } + evdp = &devpollop->fds[fd]; + + /* + * It's not necessary to OR the existing read/write events that we + * are currently interested in with the new event we are adding. + * The /dev/poll driver ORs any new events with the existing events + * that it has cached for the fd. + */ + + events = 0; + if (ev->ev_events & EV_READ) { + if (evdp->evread && evdp->evread != ev) { + /* There is already a different read event registered */ + return(-1); + } + events |= POLLIN; + } + + if (ev->ev_events & EV_WRITE) { + if (evdp->evwrite && evdp->evwrite != ev) { + /* There is already a different write event registered */ + return(-1); + } + events |= POLLOUT; + } + + if (devpoll_queue(devpollop, fd, events) != 0) + return(-1); + + /* Update events responsible */ + if (ev->ev_events & EV_READ) + evdp->evread = ev; + if (ev->ev_events & EV_WRITE) + evdp->evwrite = ev; + + return (0); +} + +static int +devpoll_del(void *arg, struct event *ev) +{ + struct devpollop *devpollop = arg; + struct evdevpoll *evdp; + int fd, events; + int needwritedelete = 1, needreaddelete = 1; + + if (ev->ev_events & EV_SIGNAL) + return (evsignal_del(ev)); + + fd = ev->ev_fd; + if (fd >= devpollop->nfds) + return (0); + evdp = &devpollop->fds[fd]; + + events = 0; + if (ev->ev_events & EV_READ) + events |= POLLIN; + if (ev->ev_events & EV_WRITE) + events |= POLLOUT; + + /* + * The only way to remove an fd from the /dev/poll monitored set is + * to use POLLREMOVE by itself. This removes ALL events for the fd + * provided so if we care about two events and are only removing one + * we must re-add the other event after POLLREMOVE. + */ + + if (devpoll_queue(devpollop, fd, POLLREMOVE) != 0) + return(-1); + + if ((events & (POLLIN|POLLOUT)) != (POLLIN|POLLOUT)) { + /* + * We're not deleting all events, so we must resubmit the + * event that we are still interested in if one exists. + */ + + if ((events & POLLIN) && evdp->evwrite != NULL) { + /* Deleting read, still care about write */ + devpoll_queue(devpollop, fd, POLLOUT); + needwritedelete = 0; + } else if ((events & POLLOUT) && evdp->evread != NULL) { + /* Deleting write, still care about read */ + devpoll_queue(devpollop, fd, POLLIN); + needreaddelete = 0; + } + } + + if (needreaddelete) + evdp->evread = NULL; + if (needwritedelete) + evdp->evwrite = NULL; + + return (0); +} + +static void +devpoll_dealloc(struct event_base *base, void *arg) +{ + struct devpollop *devpollop = arg; + + evsignal_dealloc(base); + if (devpollop->fds) + free(devpollop->fds); + if (devpollop->events) + free(devpollop->events); + if (devpollop->changes) + free(devpollop->changes); + if (devpollop->dpfd >= 0) + close(devpollop->dpfd); + + memset(devpollop, 0, sizeof(struct devpollop)); + free(devpollop); +} diff --git a/third_party/libevent/epoll.c b/third_party/libevent/epoll.c new file mode 100644 index 0000000000..4387ef896d --- /dev/null +++ b/third_party/libevent/epoll.c @@ -0,0 +1,377 @@ +/* + * Copyright 2000-2003 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#ifdef HAVE_SYS_TIME_H +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_FCNTL_H +#include +#endif + +#include "event.h" +#include "event-internal.h" +#include "evsignal.h" +#include "log.h" + +/* due to limitations in the epoll interface, we need to keep track of + * all file descriptors outself. + */ +struct evepoll { + struct event *evread; + struct event *evwrite; +}; + +struct epollop { + struct evepoll *fds; + int nfds; + struct epoll_event *events; + int nevents; + int epfd; +}; + +static void *epoll_init (struct event_base *); +static int epoll_add (void *, struct event *); +static int epoll_del (void *, struct event *); +static int epoll_dispatch (struct event_base *, void *, struct timeval *); +static void epoll_dealloc (struct event_base *, void *); + +const struct eventop epollops = { + "epoll", + epoll_init, + epoll_add, + epoll_del, + epoll_dispatch, + epoll_dealloc, + 1 /* need reinit */ +}; + +#ifdef HAVE_SETFD +#define FD_CLOSEONEXEC(x) do { \ + if (fcntl(x, F_SETFD, 1) == -1) \ + event_warn("fcntl(%d, F_SETFD)", x); \ +} while (0) +#else +#define FD_CLOSEONEXEC(x) +#endif + +/* On Linux kernels at least up to 2.6.24.4, epoll can't handle timeout + * values bigger than (LONG_MAX - 999ULL)/HZ. HZ in the wild can be + * as big as 1000, and LONG_MAX can be as small as (1<<31)-1, so the + * largest number of msec we can support here is 2147482. Let's + * round that down by 47 seconds. + */ +#define MAX_EPOLL_TIMEOUT_MSEC (35*60*1000) + +#define INITIAL_NFILES 32 +#define INITIAL_NEVENTS 32 +#define MAX_NEVENTS 4096 + +static void * +epoll_init(struct event_base *base) +{ + int epfd; + struct epollop *epollop; + + /* Disable epollueue when this environment variable is set */ + if (evutil_getenv("EVENT_NOEPOLL")) + return (NULL); + + /* Initalize the kernel queue */ + if ((epfd = epoll_create(32000)) == -1) { + if (errno != ENOSYS) + event_warn("epoll_create"); + return (NULL); + } + + FD_CLOSEONEXEC(epfd); + + if (!(epollop = calloc(1, sizeof(struct epollop)))) + return (NULL); + + epollop->epfd = epfd; + + /* Initalize fields */ + epollop->events = malloc(INITIAL_NEVENTS * sizeof(struct epoll_event)); + if (epollop->events == NULL) { + free(epollop); + return (NULL); + } + epollop->nevents = INITIAL_NEVENTS; + + epollop->fds = calloc(INITIAL_NFILES, sizeof(struct evepoll)); + if (epollop->fds == NULL) { + free(epollop->events); + free(epollop); + return (NULL); + } + epollop->nfds = INITIAL_NFILES; + + evsignal_init(base); + + return (epollop); +} + +static int +epoll_recalc(struct event_base *base, void *arg, int max) +{ + struct epollop *epollop = arg; + + if (max >= epollop->nfds) { + struct evepoll *fds; + int nfds; + + nfds = epollop->nfds; + while (nfds <= max) + nfds <<= 1; + + fds = realloc(epollop->fds, nfds * sizeof(struct evepoll)); + if (fds == NULL) { + event_warn("realloc"); + return (-1); + } + epollop->fds = fds; + memset(fds + epollop->nfds, 0, + (nfds - epollop->nfds) * sizeof(struct evepoll)); + epollop->nfds = nfds; + } + + return (0); +} + +static int +epoll_dispatch(struct event_base *base, void *arg, struct timeval *tv) +{ + struct epollop *epollop = arg; + struct epoll_event *events = epollop->events; + struct evepoll *evep; + int i, res, timeout = -1; + + if (tv != NULL) + timeout = tv->tv_sec * 1000 + (tv->tv_usec + 999) / 1000; + + if (timeout > MAX_EPOLL_TIMEOUT_MSEC) { + /* Linux kernels can wait forever if the timeout is too big; + * see comment on MAX_EPOLL_TIMEOUT_MSEC. */ + timeout = MAX_EPOLL_TIMEOUT_MSEC; + } + + res = epoll_wait(epollop->epfd, events, epollop->nevents, timeout); + + if (res == -1) { + if (errno != EINTR) { + event_warn("epoll_wait"); + return (-1); + } + + evsignal_process(base); + return (0); + } else if (base->sig.evsignal_caught) { + evsignal_process(base); + } + + event_debug(("%s: epoll_wait reports %d", __func__, res)); + + for (i = 0; i < res; i++) { + int what = events[i].events; + struct event *evread = NULL, *evwrite = NULL; + int fd = events[i].data.fd; + + if (fd < 0 || fd >= epollop->nfds) + continue; + evep = &epollop->fds[fd]; + + if (what & (EPOLLHUP|EPOLLERR)) { + evread = evep->evread; + evwrite = evep->evwrite; + } else { + if (what & EPOLLIN) { + evread = evep->evread; + } + + if (what & EPOLLOUT) { + evwrite = evep->evwrite; + } + } + + if (!(evread||evwrite)) + continue; + + if (evread != NULL) + event_active(evread, EV_READ, 1); + if (evwrite != NULL) + event_active(evwrite, EV_WRITE, 1); + } + + if (res == epollop->nevents && epollop->nevents < MAX_NEVENTS) { + /* We used all of the event space this time. We should + be ready for more events next time. */ + int new_nevents = epollop->nevents * 2; + struct epoll_event *new_events; + + new_events = realloc(epollop->events, + new_nevents * sizeof(struct epoll_event)); + if (new_events) { + epollop->events = new_events; + epollop->nevents = new_nevents; + } + } + + return (0); +} + + +static int +epoll_add(void *arg, struct event *ev) +{ + struct epollop *epollop = arg; + struct epoll_event epev = {0, {0}}; + struct evepoll *evep; + int fd, op, events; + + if (ev->ev_events & EV_SIGNAL) + return (evsignal_add(ev)); + + fd = ev->ev_fd; + if (fd >= epollop->nfds) { + /* Extent the file descriptor array as necessary */ + if (epoll_recalc(ev->ev_base, epollop, fd) == -1) + return (-1); + } + evep = &epollop->fds[fd]; + op = EPOLL_CTL_ADD; + events = 0; + if (evep->evread != NULL) { + events |= EPOLLIN; + op = EPOLL_CTL_MOD; + } + if (evep->evwrite != NULL) { + events |= EPOLLOUT; + op = EPOLL_CTL_MOD; + } + + if (ev->ev_events & EV_READ) + events |= EPOLLIN; + if (ev->ev_events & EV_WRITE) + events |= EPOLLOUT; + + epev.data.fd = fd; + epev.events = events; + if (epoll_ctl(epollop->epfd, op, ev->ev_fd, &epev) == -1) + return (-1); + + /* Update events responsible */ + if (ev->ev_events & EV_READ) + evep->evread = ev; + if (ev->ev_events & EV_WRITE) + evep->evwrite = ev; + + return (0); +} + +static int +epoll_del(void *arg, struct event *ev) +{ + struct epollop *epollop = arg; + struct epoll_event epev = {0, {0}}; + struct evepoll *evep; + int fd, events, op; + int needwritedelete = 1, needreaddelete = 1; + + if (ev->ev_events & EV_SIGNAL) + return (evsignal_del(ev)); + + fd = ev->ev_fd; + if (fd >= epollop->nfds) + return (0); + evep = &epollop->fds[fd]; + + op = EPOLL_CTL_DEL; + events = 0; + + if (ev->ev_events & EV_READ) + events |= EPOLLIN; + if (ev->ev_events & EV_WRITE) + events |= EPOLLOUT; + + if ((events & (EPOLLIN|EPOLLOUT)) != (EPOLLIN|EPOLLOUT)) { + if ((events & EPOLLIN) && evep->evwrite != NULL) { + needwritedelete = 0; + events = EPOLLOUT; + op = EPOLL_CTL_MOD; + } else if ((events & EPOLLOUT) && evep->evread != NULL) { + needreaddelete = 0; + events = EPOLLIN; + op = EPOLL_CTL_MOD; + } + } + + epev.events = events; + epev.data.fd = fd; + + if (needreaddelete) + evep->evread = NULL; + if (needwritedelete) + evep->evwrite = NULL; + + if (epoll_ctl(epollop->epfd, op, fd, &epev) == -1) + return (-1); + + return (0); +} + +static void +epoll_dealloc(struct event_base *base, void *arg) +{ + struct epollop *epollop = arg; + + evsignal_dealloc(base); + if (epollop->fds) + free(epollop->fds); + if (epollop->events) + free(epollop->events); + if (epollop->epfd >= 0) + close(epollop->epfd); + + memset(epollop, 0, sizeof(struct epollop)); + free(epollop); +} diff --git a/third_party/libevent/epoll_sub.c b/third_party/libevent/epoll_sub.c new file mode 100644 index 0000000000..431970c73a --- /dev/null +++ b/third_party/libevent/epoll_sub.c @@ -0,0 +1,52 @@ +/* + * Copyright 2003 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include + +#include +#include +#include +#include +#include + +int +epoll_create(int size) +{ + return (syscall(__NR_epoll_create, size)); +} + +int +epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) +{ + + return (syscall(__NR_epoll_ctl, epfd, op, fd, event)); +} + +int +epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) +{ + return (syscall(__NR_epoll_wait, epfd, events, maxevents, timeout)); +} diff --git a/third_party/libevent/evbuffer.c b/third_party/libevent/evbuffer.c new file mode 100644 index 0000000000..f2179a5044 --- /dev/null +++ b/third_party/libevent/evbuffer.c @@ -0,0 +1,455 @@ +/* + * Copyright (c) 2002-2004 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_SYS_TIME_H +#include +#endif + +#include +#include +#include +#include +#ifdef HAVE_STDARG_H +#include +#endif + +#ifdef WIN32 +#include +#endif + +#include "evutil.h" +#include "event.h" + +/* prototypes */ + +void bufferevent_read_pressure_cb(struct evbuffer *, size_t, size_t, void *); + +static int +bufferevent_add(struct event *ev, int timeout) +{ + struct timeval tv, *ptv = NULL; + + if (timeout) { + evutil_timerclear(&tv); + tv.tv_sec = timeout; + ptv = &tv; + } + + return (event_add(ev, ptv)); +} + +/* + * This callback is executed when the size of the input buffer changes. + * We use it to apply back pressure on the reading side. + */ + +void +bufferevent_read_pressure_cb(struct evbuffer *buf, size_t old, size_t now, + void *arg) { + struct bufferevent *bufev = arg; + /* + * If we are below the watermark then reschedule reading if it's + * still enabled. + */ + if (bufev->wm_read.high == 0 || now < bufev->wm_read.high) { + evbuffer_setcb(buf, NULL, NULL); + + if (bufev->enabled & EV_READ) + bufferevent_add(&bufev->ev_read, bufev->timeout_read); + } +} + +static void +bufferevent_readcb(int fd, short event, void *arg) +{ + struct bufferevent *bufev = arg; + int res = 0; + short what = EVBUFFER_READ; + size_t len; + int howmuch = -1; + + if (event == EV_TIMEOUT) { + what |= EVBUFFER_TIMEOUT; + goto error; + } + + /* + * If we have a high watermark configured then we don't want to + * read more data than would make us reach the watermark. + */ + if (bufev->wm_read.high != 0) { + howmuch = bufev->wm_read.high - EVBUFFER_LENGTH(bufev->input); + /* we might have lowered the watermark, stop reading */ + if (howmuch <= 0) { + struct evbuffer *buf = bufev->input; + event_del(&bufev->ev_read); + evbuffer_setcb(buf, + bufferevent_read_pressure_cb, bufev); + return; + } + } + + res = evbuffer_read(bufev->input, fd, howmuch); + if (res == -1) { + if (errno == EAGAIN || errno == EINTR) + goto reschedule; + /* error case */ + what |= EVBUFFER_ERROR; + } else if (res == 0) { + /* eof case */ + what |= EVBUFFER_EOF; + } + + if (res <= 0) + goto error; + + bufferevent_add(&bufev->ev_read, bufev->timeout_read); + + /* See if this callbacks meets the water marks */ + len = EVBUFFER_LENGTH(bufev->input); + if (bufev->wm_read.low != 0 && len < bufev->wm_read.low) + return; + if (bufev->wm_read.high != 0 && len >= bufev->wm_read.high) { + struct evbuffer *buf = bufev->input; + event_del(&bufev->ev_read); + + /* Now schedule a callback for us when the buffer changes */ + evbuffer_setcb(buf, bufferevent_read_pressure_cb, bufev); + } + + /* Invoke the user callback - must always be called last */ + if (bufev->readcb != NULL) + (*bufev->readcb)(bufev, bufev->cbarg); + return; + + reschedule: + bufferevent_add(&bufev->ev_read, bufev->timeout_read); + return; + + error: + (*bufev->errorcb)(bufev, what, bufev->cbarg); +} + +static void +bufferevent_writecb(int fd, short event, void *arg) +{ + struct bufferevent *bufev = arg; + int res = 0; + short what = EVBUFFER_WRITE; + + if (event == EV_TIMEOUT) { + what |= EVBUFFER_TIMEOUT; + goto error; + } + + if (EVBUFFER_LENGTH(bufev->output)) { + res = evbuffer_write(bufev->output, fd); + if (res == -1) { +#ifndef WIN32 +/*todo. evbuffer uses WriteFile when WIN32 is set. WIN32 system calls do not + *set errno. thus this error checking is not portable*/ + if (errno == EAGAIN || + errno == EINTR || + errno == EINPROGRESS) + goto reschedule; + /* error case */ + what |= EVBUFFER_ERROR; + +#else + goto reschedule; +#endif + + } else if (res == 0) { + /* eof case */ + what |= EVBUFFER_EOF; + } + if (res <= 0) + goto error; + } + + if (EVBUFFER_LENGTH(bufev->output) != 0) + bufferevent_add(&bufev->ev_write, bufev->timeout_write); + + /* + * Invoke the user callback if our buffer is drained or below the + * low watermark. + */ + if (bufev->writecb != NULL && + EVBUFFER_LENGTH(bufev->output) <= bufev->wm_write.low) + (*bufev->writecb)(bufev, bufev->cbarg); + + return; + + reschedule: + if (EVBUFFER_LENGTH(bufev->output) != 0) + bufferevent_add(&bufev->ev_write, bufev->timeout_write); + return; + + error: + (*bufev->errorcb)(bufev, what, bufev->cbarg); +} + +/* + * Create a new buffered event object. + * + * The read callback is invoked whenever we read new data. + * The write callback is invoked whenever the output buffer is drained. + * The error callback is invoked on a write/read error or on EOF. + * + * Both read and write callbacks maybe NULL. The error callback is not + * allowed to be NULL and have to be provided always. + */ + +struct bufferevent * +bufferevent_new(int fd, evbuffercb readcb, evbuffercb writecb, + everrorcb errorcb, void *cbarg) +{ + struct bufferevent *bufev; + + if ((bufev = calloc(1, sizeof(struct bufferevent))) == NULL) + return (NULL); + + if ((bufev->input = evbuffer_new()) == NULL) { + free(bufev); + return (NULL); + } + + if ((bufev->output = evbuffer_new()) == NULL) { + evbuffer_free(bufev->input); + free(bufev); + return (NULL); + } + + event_set(&bufev->ev_read, fd, EV_READ, bufferevent_readcb, bufev); + event_set(&bufev->ev_write, fd, EV_WRITE, bufferevent_writecb, bufev); + + bufferevent_setcb(bufev, readcb, writecb, errorcb, cbarg); + + /* + * Set to EV_WRITE so that using bufferevent_write is going to + * trigger a callback. Reading needs to be explicitly enabled + * because otherwise no data will be available. + */ + bufev->enabled = EV_WRITE; + + return (bufev); +} + +void +bufferevent_setcb(struct bufferevent *bufev, + evbuffercb readcb, evbuffercb writecb, everrorcb errorcb, void *cbarg) +{ + bufev->readcb = readcb; + bufev->writecb = writecb; + bufev->errorcb = errorcb; + + bufev->cbarg = cbarg; +} + +void +bufferevent_setfd(struct bufferevent *bufev, int fd) +{ + event_del(&bufev->ev_read); + event_del(&bufev->ev_write); + + event_set(&bufev->ev_read, fd, EV_READ, bufferevent_readcb, bufev); + event_set(&bufev->ev_write, fd, EV_WRITE, bufferevent_writecb, bufev); + if (bufev->ev_base != NULL) { + event_base_set(bufev->ev_base, &bufev->ev_read); + event_base_set(bufev->ev_base, &bufev->ev_write); + } + + /* might have to manually trigger event registration */ +} + +int +bufferevent_priority_set(struct bufferevent *bufev, int priority) +{ + if (event_priority_set(&bufev->ev_read, priority) == -1) + return (-1); + if (event_priority_set(&bufev->ev_write, priority) == -1) + return (-1); + + return (0); +} + +/* Closing the file descriptor is the responsibility of the caller */ + +void +bufferevent_free(struct bufferevent *bufev) +{ + event_del(&bufev->ev_read); + event_del(&bufev->ev_write); + + evbuffer_free(bufev->input); + evbuffer_free(bufev->output); + + free(bufev); +} + +/* + * Returns 0 on success; + * -1 on failure. + */ + +int +bufferevent_write(struct bufferevent *bufev, const void *data, size_t size) +{ + int res; + + res = evbuffer_add(bufev->output, data, size); + + if (res == -1) + return (res); + + /* If everything is okay, we need to schedule a write */ + if (size > 0 && (bufev->enabled & EV_WRITE)) + bufferevent_add(&bufev->ev_write, bufev->timeout_write); + + return (res); +} + +int +bufferevent_write_buffer(struct bufferevent *bufev, struct evbuffer *buf) +{ + int res; + + res = bufferevent_write(bufev, buf->buffer, buf->off); + if (res != -1) + evbuffer_drain(buf, buf->off); + + return (res); +} + +size_t +bufferevent_read(struct bufferevent *bufev, void *data, size_t size) +{ + struct evbuffer *buf = bufev->input; + + if (buf->off < size) + size = buf->off; + + /* Copy the available data to the user buffer */ + memcpy(data, buf->buffer, size); + + if (size) + evbuffer_drain(buf, size); + + return (size); +} + +int +bufferevent_enable(struct bufferevent *bufev, short event) +{ + if (event & EV_READ) { + if (bufferevent_add(&bufev->ev_read, bufev->timeout_read) == -1) + return (-1); + } + if (event & EV_WRITE) { + if (bufferevent_add(&bufev->ev_write, bufev->timeout_write) == -1) + return (-1); + } + + bufev->enabled |= event; + return (0); +} + +int +bufferevent_disable(struct bufferevent *bufev, short event) +{ + if (event & EV_READ) { + if (event_del(&bufev->ev_read) == -1) + return (-1); + } + if (event & EV_WRITE) { + if (event_del(&bufev->ev_write) == -1) + return (-1); + } + + bufev->enabled &= ~event; + return (0); +} + +/* + * Sets the read and write timeout for a buffered event. + */ + +void +bufferevent_settimeout(struct bufferevent *bufev, + int timeout_read, int timeout_write) { + bufev->timeout_read = timeout_read; + bufev->timeout_write = timeout_write; + + if (event_pending(&bufev->ev_read, EV_READ, NULL)) + bufferevent_add(&bufev->ev_read, timeout_read); + if (event_pending(&bufev->ev_write, EV_WRITE, NULL)) + bufferevent_add(&bufev->ev_write, timeout_write); +} + +/* + * Sets the water marks + */ + +void +bufferevent_setwatermark(struct bufferevent *bufev, short events, + size_t lowmark, size_t highmark) +{ + if (events & EV_READ) { + bufev->wm_read.low = lowmark; + bufev->wm_read.high = highmark; + } + + if (events & EV_WRITE) { + bufev->wm_write.low = lowmark; + bufev->wm_write.high = highmark; + } + + /* If the watermarks changed then see if we should call read again */ + bufferevent_read_pressure_cb(bufev->input, + 0, EVBUFFER_LENGTH(bufev->input), bufev); +} + +int +bufferevent_base_set(struct event_base *base, struct bufferevent *bufev) +{ + int res; + + bufev->ev_base = base; + + res = event_base_set(base, &bufev->ev_read); + if (res == -1) + return (res); + + res = event_base_set(base, &bufev->ev_write); + return (res); +} diff --git a/third_party/libevent/evdns.3 b/third_party/libevent/evdns.3 new file mode 100644 index 0000000000..10414fa2ef --- /dev/null +++ b/third_party/libevent/evdns.3 @@ -0,0 +1,322 @@ +.\" +.\" Copyright (c) 2006 Niels Provos +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. 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. +.\" 3. The name of the author may not be used to endorse or promote products +.\" derived from this software without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, +.\" INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +.\" AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +.\" THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +.\" EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +.\" PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +.\" OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +.\" WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +.\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +.\" ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.Dd October 7, 2006 +.Dt EVDNS 3 +.Os +.Sh NAME +.Nm evdns_init +.Nm evdns_shutdown +.Nm evdns_err_to_string +.Nm evdns_nameserver_add +.Nm evdns_count_nameservers +.Nm evdns_clear_nameservers_and_suspend +.Nm evdns_resume +.Nm evdns_nameserver_ip_add +.Nm evdns_resolve_ipv4 +.Nm evdns_resolve_reverse +.Nm evdns_resolv_conf_parse +.Nm evdns_config_windows_nameservers +.Nm evdns_search_clear +.Nm evdns_search_add +.Nm evdns_search_ndots_set +.Nm evdns_set_log_fn +.Nd asynchronous functions for DNS resolution. +.Sh SYNOPSIS +.Fd #include +.Fd #include +.Fd #include +.Ft int +.Fn evdns_init +.Ft void +.Fn evdns_shutdown "int fail_requests" +.Ft "const char *" +.Fn evdns_err_to_string "int err" +.Ft int +.Fn evdns_nameserver_add "unsigned long int address" +.Ft int +.Fn evdns_count_nameservers +.Ft int +.Fn evdns_clear_nameservers_and_suspend +.Ft int +.Fn evdns_resume +.Ft int +.Fn evdns_nameserver_ip_add(const char *ip_as_string); +.Ft int +.Fn evdns_resolve_ipv4 "const char *name" "int flags" "evdns_callback_type callback" "void *ptr" +.Ft int +.Fn evdns_resolve_reverse "struct in_addr *in" "int flags" "evdns_callback_type callback" "void *ptr" +.Ft int +.Fn evdns_resolv_conf_parse "int flags" "const char *" +.Ft void +.Fn evdns_search_clear +.Ft void +.Fn evdns_search_add "const char *domain" +.Ft void +.Fn evdns_search_ndots_set "const int ndots" +.Ft void +.Fn evdns_set_log_fn "evdns_debug_log_fn_type fn" +.Ft int +.Fn evdns_config_windows_nameservers +.Sh DESCRIPTION +Welcome, gentle reader +.Pp +Async DNS lookups are really a whole lot harder than they should be, +mostly stemming from the fact that the libc resolver has never been +very good at them. Before you use this library you should see if libc +can do the job for you with the modern async call getaddrinfo_a +(see http://www.imperialviolet.org/page25.html#e498). Otherwise, +please continue. +.Pp +This code is based on libevent and you must call event_init before +any of the APIs in this file. You must also seed the OpenSSL random +source if you are using OpenSSL for ids (see below). +.Pp +This library is designed to be included and shipped with your source +code. You statically link with it. You should also test for the +existence of strtok_r and define HAVE_STRTOK_R if you have it. +.Pp +The DNS protocol requires a good source of id numbers and these +numbers should be unpredictable for spoofing reasons. There are +three methods for generating them here and you must define exactly +one of them. In increasing order of preference: +.Pp +.Bl -tag -width "DNS_USE_GETTIMEOFDAY_FOR_ID" -compact -offset indent +.It DNS_USE_GETTIMEOFDAY_FOR_ID +Using the bottom 16 bits of the usec result from gettimeofday. This +is a pretty poor solution but should work anywhere. +.It DNS_USE_CPU_CLOCK_FOR_ID +Using the bottom 16 bits of the nsec result from the CPU's time +counter. This is better, but may not work everywhere. Requires +POSIX realtime support and you'll need to link against -lrt on +glibc systems at least. +.It DNS_USE_OPENSSL_FOR_ID +Uses the OpenSSL RAND_bytes call to generate the data. You must +have seeded the pool before making any calls to this library. +.El +.Pp +The library keeps track of the state of nameservers and will avoid +them when they go down. Otherwise it will round robin between them. +.Pp +Quick start guide: + #include "evdns.h" + void callback(int result, char type, int count, int ttl, + void *addresses, void *arg); + evdns_resolv_conf_parse(DNS_OPTIONS_ALL, "/etc/resolv.conf"); + evdns_resolve("www.hostname.com", 0, callback, NULL); +.Pp +When the lookup is complete the callback function is called. The +first argument will be one of the DNS_ERR_* defines in evdns.h. +Hopefully it will be DNS_ERR_NONE, in which case type will be +DNS_IPv4_A, count will be the number of IP addresses, ttl is the time +which the data can be cached for (in seconds), addresses will point +to an array of uint32_t's and arg will be whatever you passed to +evdns_resolve. +.Pp +Searching: +.Pp +In order for this library to be a good replacement for glibc's resolver it +supports searching. This involves setting a list of default domains, in +which names will be queried for. The number of dots in the query name +determines the order in which this list is used. +.Pp +Searching appears to be a single lookup from the point of view of the API, +although many DNS queries may be generated from a single call to +evdns_resolve. Searching can also drastically slow down the resolution +of names. +.Pp +To disable searching: +.Bl -enum -compact -offset indent +.It +Never set it up. If you never call +.Fn evdns_resolv_conf_parse, +.Fn evdns_init, +or +.Fn evdns_search_add +then no searching will occur. +.It +If you do call +.Fn evdns_resolv_conf_parse +then don't pass +.Va DNS_OPTION_SEARCH +(or +.Va DNS_OPTIONS_ALL, +which implies it). +.It +When calling +.Fn evdns_resolve, +pass the +.Va DNS_QUERY_NO_SEARCH +flag. +.El +.Pp +The order of searches depends on the number of dots in the name. If the +number is greater than the ndots setting then the names is first tried +globally. Otherwise each search domain is appended in turn. +.Pp +The ndots setting can either be set from a resolv.conf, or by calling +evdns_search_ndots_set. +.Pp +For example, with ndots set to 1 (the default) and a search domain list of +["myhome.net"]: + Query: www + Order: www.myhome.net, www. +.Pp + Query: www.abc + Order: www.abc., www.abc.myhome.net +.Pp +.Sh API reference +.Pp +.Bl -tag -width 0123456 +.It Ft int Fn evdns_init +Initializes support for non-blocking name resolution by calling +.Fn evdns_resolv_conf_parse +on UNIX and +.Fn evdns_config_windows_nameservers +on Windows. +.It Ft int Fn evdns_nameserver_add "unsigned long int address" +Add a nameserver. The address should be an IP address in +network byte order. The type of address is chosen so that +it matches in_addr.s_addr. +Returns non-zero on error. +.It Ft int Fn evdns_nameserver_ip_add "const char *ip_as_string" +This wraps the above function by parsing a string as an IP +address and adds it as a nameserver. +Returns non-zero on error +.It Ft int Fn evdns_resolve "const char *name" "int flags" "evdns_callback_type callback" "void *ptr" +Resolve a name. The name parameter should be a DNS name. +The flags parameter should be 0, or DNS_QUERY_NO_SEARCH +which disables searching for this query. (see defn of +searching above). +.Pp +The callback argument is a function which is called when +this query completes and ptr is an argument which is passed +to that callback function. +.Pp +Returns non-zero on error +.It Ft void Fn evdns_search_clear +Clears the list of search domains +.It Ft void Fn evdns_search_add "const char *domain" +Add a domain to the list of search domains +.It Ft void Fn evdns_search_ndots_set "int ndots" +Set the number of dots which, when found in a name, causes +the first query to be without any search domain. +.It Ft int Fn evdns_count_nameservers "void" +Return the number of configured nameservers (not necessarily the +number of running nameservers). This is useful for double-checking +whether our calls to the various nameserver configuration functions +have been successful. +.It Ft int Fn evdns_clear_nameservers_and_suspend "void" +Remove all currently configured nameservers, and suspend all pending +resolves. Resolves will not necessarily be re-attempted until +evdns_resume() is called. +.It Ft int Fn evdns_resume "void" +Re-attempt resolves left in limbo after an earlier call to +evdns_clear_nameservers_and_suspend(). +.It Ft int Fn evdns_config_windows_nameservers "void" +Attempt to configure a set of nameservers based on platform settings on +a win32 host. Preferentially tries to use GetNetworkParams; if that fails, +looks in the registry. Returns 0 on success, nonzero on failure. +.It Ft int Fn evdns_resolv_conf_parse "int flags" "const char *filename" +Parse a resolv.conf like file from the given filename. +.Pp +See the man page for resolv.conf for the format of this file. +The flags argument determines what information is parsed from +this file: +.Bl -tag -width "DNS_OPTION_NAMESERVERS" -offset indent -compact -nested +.It DNS_OPTION_SEARCH +domain, search and ndots options +.It DNS_OPTION_NAMESERVERS +nameserver lines +.It DNS_OPTION_MISC +timeout and attempts options +.It DNS_OPTIONS_ALL +all of the above +.El +.Pp +The following directives are not parsed from the file: + sortlist, rotate, no-check-names, inet6, debug +.Pp +Returns non-zero on error: +.Bl -tag -width "0" -offset indent -compact -nested +.It 0 +no errors +.It 1 +failed to open file +.It 2 +failed to stat file +.It 3 +file too large +.It 4 +out of memory +.It 5 +short read from file +.El +.El +.Sh Internals: +Requests are kept in two queues. The first is the inflight queue. In +this queue requests have an allocated transaction id and nameserver. +They will soon be transmitted if they haven't already been. +.Pp +The second is the waiting queue. The size of the inflight ring is +limited and all other requests wait in waiting queue for space. This +bounds the number of concurrent requests so that we don't flood the +nameserver. Several algorithms require a full walk of the inflight +queue and so bounding its size keeps thing going nicely under huge +(many thousands of requests) loads. +.Pp +If a nameserver loses too many requests it is considered down and we +try not to use it. After a while we send a probe to that nameserver +(a lookup for google.com) and, if it replies, we consider it working +again. If the nameserver fails a probe we wait longer to try again +with the next probe. +.Sh SEE ALSO +.Xr event 3 , +.Xr gethostbyname 3 , +.Xr resolv.conf 5 +.Sh HISTORY +The +.Nm evdns +API was developed by Adam Langley on top of the +.Nm libevent +API. +The code was integrate into +.Nm Tor +by Nick Mathewson and finally put into +.Nm libevent +itself by Niels Provos. +.Sh AUTHORS +The +.Nm evdns +API and code was written by Adam Langley with significant +contributions by Nick Mathewson. +.Sh BUGS +This documentation is neither complete nor authoritative. +If you are in doubt about the usage of this API then +check the source code to find out how it works, write +up the missing piece of documentation and send it to +me for inclusion in this man page. diff --git a/third_party/libevent/evdns.c b/third_party/libevent/evdns.c new file mode 100644 index 0000000000..da6ea197a2 --- /dev/null +++ b/third_party/libevent/evdns.c @@ -0,0 +1,3188 @@ +/* $Id: evdns.c 6979 2006-08-04 18:31:13Z nickm $ */ + +/* The original version of this module was written by Adam Langley; for + * a history of modifications, check out the subversion logs. + * + * When editing this module, try to keep it re-mergeable by Adam. Don't + * reformat the whitespace, add Tor dependencies, or so on. + * + * TODO: + * - Support IPv6 and PTR records. + * - Replace all externally visible magic numbers with #defined constants. + * - Write doccumentation for APIs of all external functions. + */ + +/* Async DNS Library + * Adam Langley + * http://www.imperialviolet.org/eventdns.html + * Public Domain code + * + * This software is Public Domain. To view a copy of the public domain dedication, + * visit http://creativecommons.org/licenses/publicdomain/ or send a letter to + * Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA. + * + * I ask and expect, but do not require, that all derivative works contain an + * attribution similar to: + * Parts developed by Adam Langley + * + * You may wish to replace the word "Parts" with something else depending on + * the amount of original code. + * + * (Derivative works does not include programs which link against, run or include + * the source verbatim in their source distributions) + * + * Version: 0.1b + */ + +#include +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef DNS_USE_FTIME_FOR_ID +#include +#endif + +#ifndef DNS_USE_CPU_CLOCK_FOR_ID +#ifndef DNS_USE_GETTIMEOFDAY_FOR_ID +#ifndef DNS_USE_OPENSSL_FOR_ID +#ifndef DNS_USE_FTIME_FOR_ID +#error Must configure at least one id generation method. +#error Please see the documentation. +#endif +#endif +#endif +#endif + +/* #define _POSIX_C_SOURCE 200507 */ +#define _GNU_SOURCE + +#ifdef DNS_USE_CPU_CLOCK_FOR_ID +#ifdef DNS_USE_OPENSSL_FOR_ID +#error Multiple id options selected +#endif +#ifdef DNS_USE_GETTIMEOFDAY_FOR_ID +#error Multiple id options selected +#endif +#include +#endif + +#ifdef DNS_USE_OPENSSL_FOR_ID +#ifdef DNS_USE_GETTIMEOFDAY_FOR_ID +#error Multiple id options selected +#endif +#include +#endif + +#ifndef _FORTIFY_SOURCE +#define _FORTIFY_SOURCE 3 +#endif + +#include +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif +#ifdef HAVE_STDINT_H +#include +#endif +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#include +#include +#include +#include + +#include "evdns.h" +#include "evutil.h" +#include "log.h" +#ifdef WIN32 +#include +#include +#include +#include +#else +#include +#include +#include +#endif + +#ifdef HAVE_NETINET_IN6_H +#include +#endif + +#define EVDNS_LOG_DEBUG 0 +#define EVDNS_LOG_WARN 1 + +#ifndef HOST_NAME_MAX +#define HOST_NAME_MAX 255 +#endif + +#include + +#undef MIN +#define MIN(a,b) ((a)<(b)?(a):(b)) + +#ifdef __USE_ISOC99B +/* libevent doesn't work without this */ +typedef ev_uint8_t u_char; +typedef unsigned int uint; +#endif +#include "event.h" + +#define u64 ev_uint64_t +#define u32 ev_uint32_t +#define u16 ev_uint16_t +#define u8 ev_uint8_t + +#ifdef WIN32 +#define open _open +#define read _read +#define close _close +#define strdup _strdup +#endif + +#define MAX_ADDRS 32 /* maximum number of addresses from a single packet */ +/* which we bother recording */ + +#define TYPE_A EVDNS_TYPE_A +#define TYPE_CNAME 5 +#define TYPE_PTR EVDNS_TYPE_PTR +#define TYPE_AAAA EVDNS_TYPE_AAAA + +#define CLASS_INET EVDNS_CLASS_INET + +struct request { + u8 *request; /* the dns packet data */ + unsigned int request_len; + int reissue_count; + int tx_count; /* the number of times that this packet has been sent */ + unsigned int request_type; /* TYPE_PTR or TYPE_A */ + void *user_pointer; /* the pointer given to us for this request */ + evdns_callback_type user_callback; + struct nameserver *ns; /* the server which we last sent it */ + + /* elements used by the searching code */ + int search_index; + struct search_state *search_state; + char *search_origname; /* needs to be free()ed */ + int search_flags; + + /* these objects are kept in a circular list */ + struct request *next, *prev; + + struct event timeout_event; + + u16 trans_id; /* the transaction id */ + char request_appended; /* true if the request pointer is data which follows this struct */ + char transmit_me; /* needs to be transmitted */ +}; + +#ifndef HAVE_STRUCT_IN6_ADDR +struct in6_addr { + u8 s6_addr[16]; +}; +#endif + +struct reply { + unsigned int type; + unsigned int have_answer; + union { + struct { + u32 addrcount; + u32 addresses[MAX_ADDRS]; + } a; + struct { + u32 addrcount; + struct in6_addr addresses[MAX_ADDRS]; + } aaaa; + struct { + char name[HOST_NAME_MAX]; + } ptr; + } data; +}; + +struct nameserver { + int socket; /* a connected UDP socket */ + u32 address; + u16 port; + int failed_times; /* number of times which we have given this server a chance */ + int timedout; /* number of times in a row a request has timed out */ + struct event event; + /* these objects are kept in a circular list */ + struct nameserver *next, *prev; + struct event timeout_event; /* used to keep the timeout for */ + /* when we next probe this server. */ + /* Valid if state == 0 */ + char state; /* zero if we think that this server is down */ + char choked; /* true if we have an EAGAIN from this server's socket */ + char write_waiting; /* true if we are waiting for EV_WRITE events */ +}; + +static struct request *req_head = NULL, *req_waiting_head = NULL; +static struct nameserver *server_head = NULL; + +/* Represents a local port where we're listening for DNS requests. Right now, */ +/* only UDP is supported. */ +struct evdns_server_port { + int socket; /* socket we use to read queries and write replies. */ + int refcnt; /* reference count. */ + char choked; /* Are we currently blocked from writing? */ + char closing; /* Are we trying to close this port, pending writes? */ + evdns_request_callback_fn_type user_callback; /* Fn to handle requests */ + void *user_data; /* Opaque pointer passed to user_callback */ + struct event event; /* Read/write event */ + /* circular list of replies that we want to write. */ + struct server_request *pending_replies; +}; + +/* Represents part of a reply being built. (That is, a single RR.) */ +struct server_reply_item { + struct server_reply_item *next; /* next item in sequence. */ + char *name; /* name part of the RR */ + u16 type : 16; /* The RR type */ + u16 class : 16; /* The RR class (usually CLASS_INET) */ + u32 ttl; /* The RR TTL */ + char is_name; /* True iff data is a label */ + u16 datalen; /* Length of data; -1 if data is a label */ + void *data; /* The contents of the RR */ +}; + +/* Represents a request that we've received as a DNS server, and holds */ +/* the components of the reply as we're constructing it. */ +struct server_request { + /* Pointers to the next and previous entries on the list of replies */ + /* that we're waiting to write. Only set if we have tried to respond */ + /* and gotten EAGAIN. */ + struct server_request *next_pending; + struct server_request *prev_pending; + + u16 trans_id; /* Transaction id. */ + struct evdns_server_port *port; /* Which port received this request on? */ + struct sockaddr_storage addr; /* Where to send the response */ + socklen_t addrlen; /* length of addr */ + + int n_answer; /* how many answer RRs have been set? */ + int n_authority; /* how many authority RRs have been set? */ + int n_additional; /* how many additional RRs have been set? */ + + struct server_reply_item *answer; /* linked list of answer RRs */ + struct server_reply_item *authority; /* linked list of authority RRs */ + struct server_reply_item *additional; /* linked list of additional RRs */ + + /* Constructed response. Only set once we're ready to send a reply. */ + /* Once this is set, the RR fields are cleared, and no more should be set. */ + char *response; + size_t response_len; + + /* Caller-visible fields: flags, questions. */ + struct evdns_server_request base; +}; + +/* helper macro */ +#define OFFSET_OF(st, member) ((off_t) (((char*)&((st*)0)->member)-(char*)0)) + +/* Given a pointer to an evdns_server_request, get the corresponding */ +/* server_request. */ +#define TO_SERVER_REQUEST(base_ptr) \ + ((struct server_request*) \ + (((char*)(base_ptr) - OFFSET_OF(struct server_request, base)))) + +/* The number of good nameservers that we have */ +static int global_good_nameservers = 0; + +/* inflight requests are contained in the req_head list */ +/* and are actually going out across the network */ +static int global_requests_inflight = 0; +/* requests which aren't inflight are in the waiting list */ +/* and are counted here */ +static int global_requests_waiting = 0; + +static int global_max_requests_inflight = 64; + +static struct timeval global_timeout = {5, 0}; /* 5 seconds */ +static int global_max_reissues = 1; /* a reissue occurs when we get some errors from the server */ +static int global_max_retransmits = 3; /* number of times we'll retransmit a request which timed out */ +/* number of timeouts in a row before we consider this server to be down */ +static int global_max_nameserver_timeout = 3; + +/* These are the timeout values for nameservers. If we find a nameserver is down */ +/* we try to probe it at intervals as given below. Values are in seconds. */ +static const struct timeval global_nameserver_timeouts[] = {{10, 0}, {60, 0}, {300, 0}, {900, 0}, {3600, 0}}; +static const int global_nameserver_timeouts_length = sizeof(global_nameserver_timeouts)/sizeof(struct timeval); + +static struct nameserver *nameserver_pick(void); +static void evdns_request_insert(struct request *req, struct request **head); +static void nameserver_ready_callback(int fd, short events, void *arg); +static int evdns_transmit(void); +static int evdns_request_transmit(struct request *req); +static void nameserver_send_probe(struct nameserver *const ns); +static void search_request_finished(struct request *const); +static int search_try_next(struct request *const req); +static int search_request_new(int type, const char *const name, int flags, evdns_callback_type user_callback, void *user_arg); +static void evdns_requests_pump_waiting_queue(void); +static u16 transaction_id_pick(void); +static struct request *request_new(int type, const char *name, int flags, evdns_callback_type callback, void *ptr); +static void request_submit(struct request *const req); + +static int server_request_free(struct server_request *req); +static void server_request_free_answers(struct server_request *req); +static void server_port_free(struct evdns_server_port *port); +static void server_port_ready_callback(int fd, short events, void *arg); + +static int strtoint(const char *const str); + +#ifdef WIN32 +static int +last_error(int sock) +{ + int optval, optvallen=sizeof(optval); + int err = WSAGetLastError(); + if (err == WSAEWOULDBLOCK && sock >= 0) { + if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (void*)&optval, + &optvallen)) + return err; + if (optval) + return optval; + } + return err; + +} +static int +error_is_eagain(int err) +{ + return err == EAGAIN || err == WSAEWOULDBLOCK; +} +static int +inet_aton(const char *c, struct in_addr *addr) +{ + ev_uint32_t r; + if (strcmp(c, "255.255.255.255") == 0) { + addr->s_addr = 0xffffffffu; + } else { + r = inet_addr(c); + if (r == INADDR_NONE) + return 0; + addr->s_addr = r; + } + return 1; +} +#else +#define last_error(sock) (errno) +#define error_is_eagain(err) ((err) == EAGAIN) +#endif +#define CLOSE_SOCKET(s) EVUTIL_CLOSESOCKET(s) + +#define ISSPACE(c) isspace((int)(unsigned char)(c)) +#define ISDIGIT(c) isdigit((int)(unsigned char)(c)) + +static const char * +debug_ntoa(u32 address) +{ + static char buf[32]; + u32 a = ntohl(address); + evutil_snprintf(buf, sizeof(buf), "%d.%d.%d.%d", + (int)(u8)((a>>24)&0xff), + (int)(u8)((a>>16)&0xff), + (int)(u8)((a>>8 )&0xff), + (int)(u8)((a )&0xff)); + return buf; +} + +static evdns_debug_log_fn_type evdns_log_fn = NULL; + +void +evdns_set_log_fn(evdns_debug_log_fn_type fn) +{ + evdns_log_fn = fn; +} + +#ifdef __GNUC__ +#define EVDNS_LOG_CHECK __attribute__ ((format(printf, 2, 3))) +#else +#define EVDNS_LOG_CHECK +#endif + +static void _evdns_log(int warn, const char *fmt, ...) EVDNS_LOG_CHECK; +static void +_evdns_log(int warn, const char *fmt, ...) +{ + va_list args; + static char buf[512]; + if (!evdns_log_fn) + return; + va_start(args,fmt); + evutil_vsnprintf(buf, sizeof(buf), fmt, args); + buf[sizeof(buf)-1] = '\0'; + evdns_log_fn(warn, buf); + va_end(args); +} + +#define log _evdns_log + +/* This walks the list of inflight requests to find the */ +/* one with a matching transaction id. Returns NULL on */ +/* failure */ +static struct request * +request_find_from_trans_id(u16 trans_id) { + struct request *req = req_head, *const started_at = req_head; + + if (req) { + do { + if (req->trans_id == trans_id) return req; + req = req->next; + } while (req != started_at); + } + + return NULL; +} + +/* a libevent callback function which is called when a nameserver */ +/* has gone down and we want to test if it has came back to life yet */ +static void +nameserver_prod_callback(int fd, short events, void *arg) { + struct nameserver *const ns = (struct nameserver *) arg; + (void)fd; + (void)events; + + nameserver_send_probe(ns); +} + +/* a libevent callback which is called when a nameserver probe (to see if */ +/* it has come back to life) times out. We increment the count of failed_times */ +/* and wait longer to send the next probe packet. */ +static void +nameserver_probe_failed(struct nameserver *const ns) { + const struct timeval * timeout; + (void) evtimer_del(&ns->timeout_event); + if (ns->state == 1) { + /* This can happen if the nameserver acts in a way which makes us mark */ + /* it as bad and then starts sending good replies. */ + return; + } + + timeout = + &global_nameserver_timeouts[MIN(ns->failed_times, + global_nameserver_timeouts_length - 1)]; + ns->failed_times++; + + if (evtimer_add(&ns->timeout_event, (struct timeval *) timeout) < 0) { + log(EVDNS_LOG_WARN, + "Error from libevent when adding timer event for %s", + debug_ntoa(ns->address)); + /* ???? Do more? */ + } +} + +/* called when a nameserver has been deemed to have failed. For example, too */ +/* many packets have timed out etc */ +static void +nameserver_failed(struct nameserver *const ns, const char *msg) { + struct request *req, *started_at; + /* if this nameserver has already been marked as failed */ + /* then don't do anything */ + if (!ns->state) return; + + log(EVDNS_LOG_WARN, "Nameserver %s has failed: %s", + debug_ntoa(ns->address), msg); + global_good_nameservers--; + assert(global_good_nameservers >= 0); + if (global_good_nameservers == 0) { + log(EVDNS_LOG_WARN, "All nameservers have failed"); + } + + ns->state = 0; + ns->failed_times = 1; + + if (evtimer_add(&ns->timeout_event, (struct timeval *) &global_nameserver_timeouts[0]) < 0) { + log(EVDNS_LOG_WARN, + "Error from libevent when adding timer event for %s", + debug_ntoa(ns->address)); + /* ???? Do more? */ + } + + /* walk the list of inflight requests to see if any can be reassigned to */ + /* a different server. Requests in the waiting queue don't have a */ + /* nameserver assigned yet */ + + /* if we don't have *any* good nameservers then there's no point */ + /* trying to reassign requests to one */ + if (!global_good_nameservers) return; + + req = req_head; + started_at = req_head; + if (req) { + do { + if (req->tx_count == 0 && req->ns == ns) { + /* still waiting to go out, can be moved */ + /* to another server */ + req->ns = nameserver_pick(); + } + req = req->next; + } while (req != started_at); + } +} + +static void +nameserver_up(struct nameserver *const ns) { + if (ns->state) return; + log(EVDNS_LOG_WARN, "Nameserver %s is back up", + debug_ntoa(ns->address)); + evtimer_del(&ns->timeout_event); + ns->state = 1; + ns->failed_times = 0; + ns->timedout = 0; + global_good_nameservers++; +} + +static void +request_trans_id_set(struct request *const req, const u16 trans_id) { + req->trans_id = trans_id; + *((u16 *) req->request) = htons(trans_id); +} + +/* Called to remove a request from a list and dealloc it. */ +/* head is a pointer to the head of the list it should be */ +/* removed from or NULL if the request isn't in a list. */ +static void +request_finished(struct request *const req, struct request **head) { + if (head) { + if (req->next == req) { + /* only item in the list */ + *head = NULL; + } else { + req->next->prev = req->prev; + req->prev->next = req->next; + if (*head == req) *head = req->next; + } + } + + log(EVDNS_LOG_DEBUG, "Removing timeout for request %lx", + (unsigned long) req); + evtimer_del(&req->timeout_event); + + search_request_finished(req); + global_requests_inflight--; + + if (!req->request_appended) { + /* need to free the request data on it's own */ + free(req->request); + } else { + /* the request data is appended onto the header */ + /* so everything gets free()ed when we: */ + } + + free(req); + + evdns_requests_pump_waiting_queue(); +} + +/* This is called when a server returns a funny error code. */ +/* We try the request again with another server. */ +/* */ +/* return: */ +/* 0 ok */ +/* 1 failed/reissue is pointless */ +static int +request_reissue(struct request *req) { + const struct nameserver *const last_ns = req->ns; + /* the last nameserver should have been marked as failing */ + /* by the caller of this function, therefore pick will try */ + /* not to return it */ + req->ns = nameserver_pick(); + if (req->ns == last_ns) { + /* ... but pick did return it */ + /* not a lot of point in trying again with the */ + /* same server */ + return 1; + } + + req->reissue_count++; + req->tx_count = 0; + req->transmit_me = 1; + + return 0; +} + +/* this function looks for space on the inflight queue and promotes */ +/* requests from the waiting queue if it can. */ +static void +evdns_requests_pump_waiting_queue(void) { + while (global_requests_inflight < global_max_requests_inflight && + global_requests_waiting) { + struct request *req; + /* move a request from the waiting queue to the inflight queue */ + assert(req_waiting_head); + if (req_waiting_head->next == req_waiting_head) { + /* only one item in the queue */ + req = req_waiting_head; + req_waiting_head = NULL; + } else { + req = req_waiting_head; + req->next->prev = req->prev; + req->prev->next = req->next; + req_waiting_head = req->next; + } + + global_requests_waiting--; + global_requests_inflight++; + + req->ns = nameserver_pick(); + request_trans_id_set(req, transaction_id_pick()); + + evdns_request_insert(req, &req_head); + evdns_request_transmit(req); + evdns_transmit(); + } +} + +static void +reply_callback(struct request *const req, u32 ttl, u32 err, struct reply *reply) { + switch (req->request_type) { + case TYPE_A: + if (reply) + req->user_callback(DNS_ERR_NONE, DNS_IPv4_A, + reply->data.a.addrcount, ttl, + reply->data.a.addresses, + req->user_pointer); + else + req->user_callback(err, 0, 0, 0, NULL, req->user_pointer); + return; + case TYPE_PTR: + if (reply) { + char *name = reply->data.ptr.name; + req->user_callback(DNS_ERR_NONE, DNS_PTR, 1, ttl, + &name, req->user_pointer); + } else { + req->user_callback(err, 0, 0, 0, NULL, + req->user_pointer); + } + return; + case TYPE_AAAA: + if (reply) + req->user_callback(DNS_ERR_NONE, DNS_IPv6_AAAA, + reply->data.aaaa.addrcount, ttl, + reply->data.aaaa.addresses, + req->user_pointer); + else + req->user_callback(err, 0, 0, 0, NULL, req->user_pointer); + return; + } + assert(0); +} + +/* this processes a parsed reply packet */ +static void +reply_handle(struct request *const req, u16 flags, u32 ttl, struct reply *reply) { + int error; + static const int error_codes[] = { + DNS_ERR_FORMAT, DNS_ERR_SERVERFAILED, DNS_ERR_NOTEXIST, + DNS_ERR_NOTIMPL, DNS_ERR_REFUSED + }; + + if (flags & 0x020f || !reply || !reply->have_answer) { + /* there was an error */ + if (flags & 0x0200) { + error = DNS_ERR_TRUNCATED; + } else { + u16 error_code = (flags & 0x000f) - 1; + if (error_code > 4) { + error = DNS_ERR_UNKNOWN; + } else { + error = error_codes[error_code]; + } + } + + switch(error) { + case DNS_ERR_NOTIMPL: + case DNS_ERR_REFUSED: + /* we regard these errors as marking a bad nameserver */ + if (req->reissue_count < global_max_reissues) { + char msg[64]; + evutil_snprintf(msg, sizeof(msg), + "Bad response %d (%s)", + error, evdns_err_to_string(error)); + nameserver_failed(req->ns, msg); + if (!request_reissue(req)) return; + } + break; + case DNS_ERR_SERVERFAILED: + /* rcode 2 (servfailed) sometimes means "we + * are broken" and sometimes (with some binds) + * means "that request was very confusing." + * Treat this as a timeout, not a failure. + */ + log(EVDNS_LOG_DEBUG, "Got a SERVERFAILED from nameserver %s; " + "will allow the request to time out.", + debug_ntoa(req->ns->address)); + break; + default: + /* we got a good reply from the nameserver */ + nameserver_up(req->ns); + } + + if (req->search_state && req->request_type != TYPE_PTR) { + /* if we have a list of domains to search in, + * try the next one */ + if (!search_try_next(req)) { + /* a new request was issued so this + * request is finished and */ + /* the user callback will be made when + * that request (or a */ + /* child of it) finishes. */ + request_finished(req, &req_head); + return; + } + } + + /* all else failed. Pass the failure up */ + reply_callback(req, 0, error, NULL); + request_finished(req, &req_head); + } else { + /* all ok, tell the user */ + reply_callback(req, ttl, 0, reply); + nameserver_up(req->ns); + request_finished(req, &req_head); + } +} + +static int +name_parse(u8 *packet, int length, int *idx, char *name_out, int name_out_len) { + int name_end = -1; + int j = *idx; + int ptr_count = 0; +#define GET32(x) do { if (j + 4 > length) goto err; memcpy(&_t32, packet + j, 4); j += 4; x = ntohl(_t32); } while(0) +#define GET16(x) do { if (j + 2 > length) goto err; memcpy(&_t, packet + j, 2); j += 2; x = ntohs(_t); } while(0) +#define GET8(x) do { if (j >= length) goto err; x = packet[j++]; } while(0) + + char *cp = name_out; + const char *const end = name_out + name_out_len; + + /* Normally, names are a series of length prefixed strings terminated */ + /* with a length of 0 (the lengths are u8's < 63). */ + /* However, the length can start with a pair of 1 bits and that */ + /* means that the next 14 bits are a pointer within the current */ + /* packet. */ + + for(;;) { + u8 label_len; + if (j >= length) return -1; + GET8(label_len); + if (!label_len) break; + if (label_len & 0xc0) { + u8 ptr_low; + GET8(ptr_low); + if (name_end < 0) name_end = j; + j = (((int)label_len & 0x3f) << 8) + ptr_low; + /* Make sure that the target offset is in-bounds. */ + if (j < 0 || j >= length) return -1; + /* If we've jumped more times than there are characters in the + * message, we must have a loop. */ + if (++ptr_count > length) return -1; + continue; + } + if (label_len > 63) return -1; + if (cp != name_out) { + if (cp + 1 >= end) return -1; + *cp++ = '.'; + } + if (cp + label_len >= end) return -1; + memcpy(cp, packet + j, label_len); + cp += label_len; + j += label_len; + } + if (cp >= end) return -1; + *cp = '\0'; + if (name_end < 0) + *idx = j; + else + *idx = name_end; + return 0; + err: + return -1; +} + +/* parses a raw request from a nameserver */ +static int +reply_parse(u8 *packet, int length) { + int j = 0, k = 0; /* index into packet */ + u16 _t; /* used by the macros */ + u32 _t32; /* used by the macros */ + char tmp_name[256], cmp_name[256]; /* used by the macros */ + + u16 trans_id, questions, answers, authority, additional, datalength; + u16 flags = 0; + u32 ttl, ttl_r = 0xffffffff; + struct reply reply; + struct request *req = NULL; + unsigned int i; + + GET16(trans_id); + GET16(flags); + GET16(questions); + GET16(answers); + GET16(authority); + GET16(additional); + (void) authority; /* suppress "unused variable" warnings. */ + (void) additional; /* suppress "unused variable" warnings. */ + + req = request_find_from_trans_id(trans_id); + if (!req) return -1; + + memset(&reply, 0, sizeof(reply)); + + /* If it's not an answer, it doesn't correspond to any request. */ + if (!(flags & 0x8000)) return -1; /* must be an answer */ + if (flags & 0x020f) { + /* there was an error */ + goto err; + } + /* if (!answers) return; */ /* must have an answer of some form */ + + /* This macro skips a name in the DNS reply. */ +#define SKIP_NAME \ + do { tmp_name[0] = '\0'; \ + if (name_parse(packet, length, &j, tmp_name, sizeof(tmp_name))<0)\ + goto err; \ + } while(0) +#define TEST_NAME \ + do { tmp_name[0] = '\0'; \ + cmp_name[0] = '\0'; \ + k = j; \ + if (name_parse(packet, length, &j, tmp_name, sizeof(tmp_name))<0)\ + goto err; \ + if (name_parse(req->request, req->request_len, &k, cmp_name, sizeof(cmp_name))<0) \ + goto err; \ + if (memcmp(tmp_name, cmp_name, strlen (tmp_name)) != 0) \ + return (-1); /* we ignore mismatching names */ \ + } while(0) + + reply.type = req->request_type; + + /* skip over each question in the reply */ + for (i = 0; i < questions; ++i) { + /* the question looks like + * + */ + TEST_NAME; + j += 4; + if (j > length) goto err; + } + + /* now we have the answer section which looks like + * + */ + + for (i = 0; i < answers; ++i) { + u16 type, class; + + SKIP_NAME; + GET16(type); + GET16(class); + GET32(ttl); + GET16(datalength); + + if (type == TYPE_A && class == CLASS_INET) { + int addrcount, addrtocopy; + if (req->request_type != TYPE_A) { + j += datalength; continue; + } + if ((datalength & 3) != 0) /* not an even number of As. */ + goto err; + addrcount = datalength >> 2; + addrtocopy = MIN(MAX_ADDRS - reply.data.a.addrcount, (unsigned)addrcount); + + ttl_r = MIN(ttl_r, ttl); + /* we only bother with the first four addresses. */ + if (j + 4*addrtocopy > length) goto err; + memcpy(&reply.data.a.addresses[reply.data.a.addrcount], + packet + j, 4*addrtocopy); + j += 4*addrtocopy; + reply.data.a.addrcount += addrtocopy; + reply.have_answer = 1; + if (reply.data.a.addrcount == MAX_ADDRS) break; + } else if (type == TYPE_PTR && class == CLASS_INET) { + if (req->request_type != TYPE_PTR) { + j += datalength; continue; + } + if (name_parse(packet, length, &j, reply.data.ptr.name, + sizeof(reply.data.ptr.name))<0) + goto err; + ttl_r = MIN(ttl_r, ttl); + reply.have_answer = 1; + break; + } else if (type == TYPE_AAAA && class == CLASS_INET) { + int addrcount, addrtocopy; + if (req->request_type != TYPE_AAAA) { + j += datalength; continue; + } + if ((datalength & 15) != 0) /* not an even number of AAAAs. */ + goto err; + addrcount = datalength >> 4; /* each address is 16 bytes long */ + addrtocopy = MIN(MAX_ADDRS - reply.data.aaaa.addrcount, (unsigned)addrcount); + ttl_r = MIN(ttl_r, ttl); + + /* we only bother with the first four addresses. */ + if (j + 16*addrtocopy > length) goto err; + memcpy(&reply.data.aaaa.addresses[reply.data.aaaa.addrcount], + packet + j, 16*addrtocopy); + reply.data.aaaa.addrcount += addrtocopy; + j += 16*addrtocopy; + reply.have_answer = 1; + if (reply.data.aaaa.addrcount == MAX_ADDRS) break; + } else { + /* skip over any other type of resource */ + j += datalength; + } + } + + reply_handle(req, flags, ttl_r, &reply); + return 0; + err: + if (req) + reply_handle(req, flags, 0, NULL); + return -1; +} + +/* Parse a raw request (packet,length) sent to a nameserver port (port) from */ +/* a DNS client (addr,addrlen), and if it's well-formed, call the corresponding */ +/* callback. */ +static int +request_parse(u8 *packet, int length, struct evdns_server_port *port, struct sockaddr *addr, socklen_t addrlen) +{ + int j = 0; /* index into packet */ + u16 _t; /* used by the macros */ + char tmp_name[256]; /* used by the macros */ + + int i; + u16 trans_id, flags, questions, answers, authority, additional; + struct server_request *server_req = NULL; + + /* Get the header fields */ + GET16(trans_id); + GET16(flags); + GET16(questions); + GET16(answers); + GET16(authority); + GET16(additional); + + if (flags & 0x8000) return -1; /* Must not be an answer. */ + flags &= 0x0110; /* Only RD and CD get preserved. */ + + server_req = malloc(sizeof(struct server_request)); + if (server_req == NULL) return -1; + memset(server_req, 0, sizeof(struct server_request)); + + server_req->trans_id = trans_id; + memcpy(&server_req->addr, addr, addrlen); + server_req->addrlen = addrlen; + + server_req->base.flags = flags; + server_req->base.nquestions = 0; + server_req->base.questions = malloc(sizeof(struct evdns_server_question *) * questions); + if (server_req->base.questions == NULL) + goto err; + + for (i = 0; i < questions; ++i) { + u16 type, class; + struct evdns_server_question *q; + int namelen; + if (name_parse(packet, length, &j, tmp_name, sizeof(tmp_name))<0) + goto err; + GET16(type); + GET16(class); + namelen = strlen(tmp_name); + q = malloc(sizeof(struct evdns_server_question) + namelen); + if (!q) + goto err; + q->type = type; + q->dns_question_class = class; + memcpy(q->name, tmp_name, namelen+1); + server_req->base.questions[server_req->base.nquestions++] = q; + } + + /* Ignore answers, authority, and additional. */ + + server_req->port = port; + port->refcnt++; + + /* Only standard queries are supported. */ + if (flags & 0x7800) { + evdns_server_request_respond(&(server_req->base), DNS_ERR_NOTIMPL); + return -1; + } + + port->user_callback(&(server_req->base), port->user_data); + + return 0; +err: + if (server_req) { + if (server_req->base.questions) { + for (i = 0; i < server_req->base.nquestions; ++i) + free(server_req->base.questions[i]); + free(server_req->base.questions); + } + free(server_req); + } + return -1; + +#undef SKIP_NAME +#undef GET32 +#undef GET16 +#undef GET8 +} + +static u16 +default_transaction_id_fn(void) +{ + u16 trans_id; +#ifdef DNS_USE_CPU_CLOCK_FOR_ID + struct timespec ts; + static int clkid = -1; + if (clkid == -1) { + clkid = CLOCK_REALTIME; +#ifdef CLOCK_MONOTONIC + if (clock_gettime(CLOCK_MONOTONIC, &ts) != -1) + clkid = CLOCK_MONOTONIC; +#endif + } + if (clock_gettime(clkid, &ts) == -1) + event_err(1, "clock_gettime"); + trans_id = ts.tv_nsec & 0xffff; +#endif + +#ifdef DNS_USE_FTIME_FOR_ID + struct _timeb tb; + _ftime(&tb); + trans_id = tb.millitm & 0xffff; +#endif + +#ifdef DNS_USE_GETTIMEOFDAY_FOR_ID + struct timeval tv; + evutil_gettimeofday(&tv, NULL); + trans_id = tv.tv_usec & 0xffff; +#endif + +#ifdef DNS_USE_OPENSSL_FOR_ID + if (RAND_pseudo_bytes((u8 *) &trans_id, 2) == -1) { + /* in the case that the RAND call fails we back */ + /* down to using gettimeofday. */ + /* + struct timeval tv; + evutil_gettimeofday(&tv, NULL); + trans_id = tv.tv_usec & 0xffff; + */ + abort(); + } +#endif + return trans_id; +} + +static ev_uint16_t (*trans_id_function)(void) = default_transaction_id_fn; + +void +evdns_set_transaction_id_fn(ev_uint16_t (*fn)(void)) +{ + if (fn) + trans_id_function = fn; + else + trans_id_function = default_transaction_id_fn; +} + +/* Try to choose a strong transaction id which isn't already in flight */ +static u16 +transaction_id_pick(void) { + for (;;) { + const struct request *req = req_head, *started_at; + u16 trans_id = trans_id_function(); + + if (trans_id == 0xffff) continue; + /* now check to see if that id is already inflight */ + req = started_at = req_head; + if (req) { + do { + if (req->trans_id == trans_id) break; + req = req->next; + } while (req != started_at); + } + /* we didn't find it, so this is a good id */ + if (req == started_at) return trans_id; + } +} + +/* choose a namesever to use. This function will try to ignore */ +/* nameservers which we think are down and load balance across the rest */ +/* by updating the server_head global each time. */ +static struct nameserver * +nameserver_pick(void) { + struct nameserver *started_at = server_head, *picked; + if (!server_head) return NULL; + + /* if we don't have any good nameservers then there's no */ + /* point in trying to find one. */ + if (!global_good_nameservers) { + server_head = server_head->next; + return server_head; + } + + /* remember that nameservers are in a circular list */ + for (;;) { + if (server_head->state) { + /* we think this server is currently good */ + picked = server_head; + server_head = server_head->next; + return picked; + } + + server_head = server_head->next; + if (server_head == started_at) { + /* all the nameservers seem to be down */ + /* so we just return this one and hope for the */ + /* best */ + assert(global_good_nameservers == 0); + picked = server_head; + server_head = server_head->next; + return picked; + } + } +} + +static int +address_is_correct(struct nameserver *ns, struct sockaddr *sa, socklen_t slen) +{ + struct sockaddr_in *sin = (struct sockaddr_in*) sa; + if (sa->sa_family != AF_INET || slen != sizeof(struct sockaddr_in)) + return 0; + if (sin->sin_addr.s_addr != ns->address) + return 0; + return 1; +} + +/* this is called when a namesever socket is ready for reading */ +static void +nameserver_read(struct nameserver *ns) { + u8 packet[1500]; + struct sockaddr_storage ss; + socklen_t addrlen = sizeof(ss); + + for (;;) { + const int r = recvfrom(ns->socket, packet, sizeof(packet), 0, + (struct sockaddr*)&ss, &addrlen); + if (r < 0) { + int err = last_error(ns->socket); + if (error_is_eagain(err)) return; + nameserver_failed(ns, strerror(err)); + return; + } + if (!address_is_correct(ns, (struct sockaddr*)&ss, addrlen)) { + log(EVDNS_LOG_WARN, "Address mismatch on received " + "DNS packet."); + return; + } + ns->timedout = 0; + reply_parse(packet, r); + } +} + +/* Read a packet from a DNS client on a server port s, parse it, and */ +/* act accordingly. */ +static void +server_port_read(struct evdns_server_port *s) { + u8 packet[1500]; + struct sockaddr_storage addr; + socklen_t addrlen; + int r; + + for (;;) { + addrlen = sizeof(struct sockaddr_storage); + r = recvfrom(s->socket, packet, sizeof(packet), 0, + (struct sockaddr*) &addr, &addrlen); + if (r < 0) { + int err = last_error(s->socket); + if (error_is_eagain(err)) return; + log(EVDNS_LOG_WARN, "Error %s (%d) while reading request.", + strerror(err), err); + return; + } + request_parse(packet, r, s, (struct sockaddr*) &addr, addrlen); + } +} + +/* Try to write all pending replies on a given DNS server port. */ +static void +server_port_flush(struct evdns_server_port *port) +{ + while (port->pending_replies) { + struct server_request *req = port->pending_replies; + int r = sendto(port->socket, req->response, req->response_len, 0, + (struct sockaddr*) &req->addr, req->addrlen); + if (r < 0) { + int err = last_error(port->socket); + if (error_is_eagain(err)) + return; + log(EVDNS_LOG_WARN, "Error %s (%d) while writing response to port; dropping", strerror(err), err); + } + if (server_request_free(req)) { + /* we released the last reference to req->port. */ + return; + } + } + + /* We have no more pending requests; stop listening for 'writeable' events. */ + (void) event_del(&port->event); + event_set(&port->event, port->socket, EV_READ | EV_PERSIST, + server_port_ready_callback, port); + if (event_add(&port->event, NULL) < 0) { + log(EVDNS_LOG_WARN, "Error from libevent when adding event for DNS server."); + /* ???? Do more? */ + } +} + +/* set if we are waiting for the ability to write to this server. */ +/* if waiting is true then we ask libevent for EV_WRITE events, otherwise */ +/* we stop these events. */ +static void +nameserver_write_waiting(struct nameserver *ns, char waiting) { + if (ns->write_waiting == waiting) return; + + ns->write_waiting = waiting; + (void) event_del(&ns->event); + event_set(&ns->event, ns->socket, EV_READ | (waiting ? EV_WRITE : 0) | EV_PERSIST, + nameserver_ready_callback, ns); + if (event_add(&ns->event, NULL) < 0) { + log(EVDNS_LOG_WARN, "Error from libevent when adding event for %s", + debug_ntoa(ns->address)); + /* ???? Do more? */ + } +} + +/* a callback function. Called by libevent when the kernel says that */ +/* a nameserver socket is ready for writing or reading */ +static void +nameserver_ready_callback(int fd, short events, void *arg) { + struct nameserver *ns = (struct nameserver *) arg; + (void)fd; + + if (events & EV_WRITE) { + ns->choked = 0; + if (!evdns_transmit()) { + nameserver_write_waiting(ns, 0); + } + } + if (events & EV_READ) { + nameserver_read(ns); + } +} + +/* a callback function. Called by libevent when the kernel says that */ +/* a server socket is ready for writing or reading. */ +static void +server_port_ready_callback(int fd, short events, void *arg) { + struct evdns_server_port *port = (struct evdns_server_port *) arg; + (void) fd; + + if (events & EV_WRITE) { + port->choked = 0; + server_port_flush(port); + } + if (events & EV_READ) { + server_port_read(port); + } +} + +/* This is an inefficient representation; only use it via the dnslabel_table_* + * functions, so that is can be safely replaced with something smarter later. */ +#define MAX_LABELS 128 +/* Structures used to implement name compression */ +struct dnslabel_entry { char *v; off_t pos; }; +struct dnslabel_table { + int n_labels; /* number of current entries */ + /* map from name to position in message */ + struct dnslabel_entry labels[MAX_LABELS]; +}; + +/* Initialize dnslabel_table. */ +static void +dnslabel_table_init(struct dnslabel_table *table) +{ + table->n_labels = 0; +} + +/* Free all storage held by table, but not the table itself. */ +static void +dnslabel_clear(struct dnslabel_table *table) +{ + int i; + for (i = 0; i < table->n_labels; ++i) + free(table->labels[i].v); + table->n_labels = 0; +} + +/* return the position of the label in the current message, or -1 if the label */ +/* hasn't been used yet. */ +static int +dnslabel_table_get_pos(const struct dnslabel_table *table, const char *label) +{ + int i; + for (i = 0; i < table->n_labels; ++i) { + if (!strcmp(label, table->labels[i].v)) + return table->labels[i].pos; + } + return -1; +} + +/* remember that we've used the label at position pos */ +static int +dnslabel_table_add(struct dnslabel_table *table, const char *label, off_t pos) +{ + char *v; + int p; + if (table->n_labels == MAX_LABELS) + return (-1); + v = strdup(label); + if (v == NULL) + return (-1); + p = table->n_labels++; + table->labels[p].v = v; + table->labels[p].pos = pos; + + return (0); +} + +/* Converts a string to a length-prefixed set of DNS labels, starting */ +/* at buf[j]. name and buf must not overlap. name_len should be the length */ +/* of name. table is optional, and is used for compression. */ +/* */ +/* Input: abc.def */ +/* Output: <3>abc<3>def<0> */ +/* */ +/* Returns the first index after the encoded name, or negative on error. */ +/* -1 label was > 63 bytes */ +/* -2 name too long to fit in buffer. */ +/* */ +static off_t +dnsname_to_labels(u8 *const buf, size_t buf_len, off_t j, + const char *name, const int name_len, + struct dnslabel_table *table) { + const char *end = name + name_len; + int ref = 0; + u16 _t; + +#define APPEND16(x) do { \ + if (j + 2 > (off_t)buf_len) \ + goto overflow; \ + _t = htons(x); \ + memcpy(buf + j, &_t, 2); \ + j += 2; \ + } while (0) +#define APPEND32(x) do { \ + if (j + 4 > (off_t)buf_len) \ + goto overflow; \ + _t32 = htonl(x); \ + memcpy(buf + j, &_t32, 4); \ + j += 4; \ + } while (0) + + if (name_len > 255) return -2; + + for (;;) { + const char *const start = name; + if (table && (ref = dnslabel_table_get_pos(table, name)) >= 0) { + APPEND16(ref | 0xc000); + return j; + } + name = strchr(name, '.'); + if (!name) { + const unsigned int label_len = end - start; + if (label_len > 63) return -1; + if ((size_t)(j+label_len+1) > buf_len) return -2; + if (table) dnslabel_table_add(table, start, j); + buf[j++] = label_len; + + memcpy(buf + j, start, end - start); + j += end - start; + break; + } else { + /* append length of the label. */ + const unsigned int label_len = name - start; + if (label_len > 63) return -1; + if ((size_t)(j+label_len+1) > buf_len) return -2; + if (table) dnslabel_table_add(table, start, j); + buf[j++] = label_len; + + memcpy(buf + j, start, name - start); + j += name - start; + /* hop over the '.' */ + name++; + } + } + + /* the labels must be terminated by a 0. */ + /* It's possible that the name ended in a . */ + /* in which case the zero is already there */ + if (!j || buf[j-1]) buf[j++] = 0; + return j; + overflow: + return (-2); +} + +/* Finds the length of a dns request for a DNS name of the given */ +/* length. The actual request may be smaller than the value returned */ +/* here */ +static int +evdns_request_len(const int name_len) { + return 96 + /* length of the DNS standard header */ + name_len + 2 + + 4; /* space for the resource type */ +} + +/* build a dns request packet into buf. buf should be at least as long */ +/* as evdns_request_len told you it should be. */ +/* */ +/* Returns the amount of space used. Negative on error. */ +static int +evdns_request_data_build(const char *const name, const int name_len, + const u16 trans_id, const u16 type, const u16 class, + u8 *const buf, size_t buf_len) { + off_t j = 0; /* current offset into buf */ + u16 _t; /* used by the macros */ + + APPEND16(trans_id); + APPEND16(0x0100); /* standard query, recusion needed */ + APPEND16(1); /* one question */ + APPEND16(0); /* no answers */ + APPEND16(0); /* no authority */ + APPEND16(0); /* no additional */ + + j = dnsname_to_labels(buf, buf_len, j, name, name_len, NULL); + if (j < 0) { + return (int)j; + } + + APPEND16(type); + APPEND16(class); + + return (int)j; + overflow: + return (-1); +} + +/* exported function */ +struct evdns_server_port * +evdns_add_server_port(int socket, int is_tcp, evdns_request_callback_fn_type cb, void *user_data) +{ + struct evdns_server_port *port; + if (!(port = malloc(sizeof(struct evdns_server_port)))) + return NULL; + memset(port, 0, sizeof(struct evdns_server_port)); + + assert(!is_tcp); /* TCP sockets not yet implemented */ + port->socket = socket; + port->refcnt = 1; + port->choked = 0; + port->closing = 0; + port->user_callback = cb; + port->user_data = user_data; + port->pending_replies = NULL; + + event_set(&port->event, port->socket, EV_READ | EV_PERSIST, + server_port_ready_callback, port); + event_add(&port->event, NULL); /* check return. */ + return port; +} + +/* exported function */ +void +evdns_close_server_port(struct evdns_server_port *port) +{ + if (--port->refcnt == 0) + server_port_free(port); + port->closing = 1; +} + +/* exported function */ +int +evdns_server_request_add_reply(struct evdns_server_request *_req, int section, const char *name, int type, int class, int ttl, int datalen, int is_name, const char *data) +{ + struct server_request *req = TO_SERVER_REQUEST(_req); + struct server_reply_item **itemp, *item; + int *countp; + + if (req->response) /* have we already answered? */ + return (-1); + + switch (section) { + case EVDNS_ANSWER_SECTION: + itemp = &req->answer; + countp = &req->n_answer; + break; + case EVDNS_AUTHORITY_SECTION: + itemp = &req->authority; + countp = &req->n_authority; + break; + case EVDNS_ADDITIONAL_SECTION: + itemp = &req->additional; + countp = &req->n_additional; + break; + default: + return (-1); + } + while (*itemp) { + itemp = &((*itemp)->next); + } + item = malloc(sizeof(struct server_reply_item)); + if (!item) + return -1; + item->next = NULL; + if (!(item->name = strdup(name))) { + free(item); + return -1; + } + item->type = type; + item->dns_question_class = class; + item->ttl = ttl; + item->is_name = is_name != 0; + item->datalen = 0; + item->data = NULL; + if (data) { + if (item->is_name) { + if (!(item->data = strdup(data))) { + free(item->name); + free(item); + return -1; + } + item->datalen = (u16)-1; + } else { + if (!(item->data = malloc(datalen))) { + free(item->name); + free(item); + return -1; + } + item->datalen = datalen; + memcpy(item->data, data, datalen); + } + } + + *itemp = item; + ++(*countp); + return 0; +} + +/* exported function */ +int +evdns_server_request_add_a_reply(struct evdns_server_request *req, const char *name, int n, void *addrs, int ttl) +{ + return evdns_server_request_add_reply( + req, EVDNS_ANSWER_SECTION, name, TYPE_A, CLASS_INET, + ttl, n*4, 0, addrs); +} + +/* exported function */ +int +evdns_server_request_add_aaaa_reply(struct evdns_server_request *req, const char *name, int n, void *addrs, int ttl) +{ + return evdns_server_request_add_reply( + req, EVDNS_ANSWER_SECTION, name, TYPE_AAAA, CLASS_INET, + ttl, n*16, 0, addrs); +} + +/* exported function */ +int +evdns_server_request_add_ptr_reply(struct evdns_server_request *req, struct in_addr *in, const char *inaddr_name, const char *hostname, int ttl) +{ + u32 a; + char buf[32]; + assert(in || inaddr_name); + assert(!(in && inaddr_name)); + if (in) { + a = ntohl(in->s_addr); + evutil_snprintf(buf, sizeof(buf), "%d.%d.%d.%d.in-addr.arpa", + (int)(u8)((a )&0xff), + (int)(u8)((a>>8 )&0xff), + (int)(u8)((a>>16)&0xff), + (int)(u8)((a>>24)&0xff)); + inaddr_name = buf; + } + return evdns_server_request_add_reply( + req, EVDNS_ANSWER_SECTION, inaddr_name, TYPE_PTR, CLASS_INET, + ttl, -1, 1, hostname); +} + +/* exported function */ +int +evdns_server_request_add_cname_reply(struct evdns_server_request *req, const char *name, const char *cname, int ttl) +{ + return evdns_server_request_add_reply( + req, EVDNS_ANSWER_SECTION, name, TYPE_CNAME, CLASS_INET, + ttl, -1, 1, cname); +} + + +static int +evdns_server_request_format_response(struct server_request *req, int err) +{ + unsigned char buf[1500]; + size_t buf_len = sizeof(buf); + off_t j = 0, r; + u16 _t; + u32 _t32; + int i; + u16 flags; + struct dnslabel_table table; + + if (err < 0 || err > 15) return -1; + + /* Set response bit and error code; copy OPCODE and RD fields from + * question; copy RA and AA if set by caller. */ + flags = req->base.flags; + flags |= (0x8000 | err); + + dnslabel_table_init(&table); + APPEND16(req->trans_id); + APPEND16(flags); + APPEND16(req->base.nquestions); + APPEND16(req->n_answer); + APPEND16(req->n_authority); + APPEND16(req->n_additional); + + /* Add questions. */ + for (i=0; i < req->base.nquestions; ++i) { + const char *s = req->base.questions[i]->name; + j = dnsname_to_labels(buf, buf_len, j, s, strlen(s), &table); + if (j < 0) { + dnslabel_clear(&table); + return (int) j; + } + APPEND16(req->base.questions[i]->type); + APPEND16(req->base.questions[i]->dns_question_class); + } + + /* Add answer, authority, and additional sections. */ + for (i=0; i<3; ++i) { + struct server_reply_item *item; + if (i==0) + item = req->answer; + else if (i==1) + item = req->authority; + else + item = req->additional; + while (item) { + r = dnsname_to_labels(buf, buf_len, j, item->name, strlen(item->name), &table); + if (r < 0) + goto overflow; + j = r; + + APPEND16(item->type); + APPEND16(item->dns_question_class); + APPEND32(item->ttl); + if (item->is_name) { + off_t len_idx = j, name_start; + j += 2; + name_start = j; + r = dnsname_to_labels(buf, buf_len, j, item->data, strlen(item->data), &table); + if (r < 0) + goto overflow; + j = r; + _t = htons( (short) (j-name_start) ); + memcpy(buf+len_idx, &_t, 2); + } else { + APPEND16(item->datalen); + if (j+item->datalen > (off_t)buf_len) + goto overflow; + memcpy(buf+j, item->data, item->datalen); + j += item->datalen; + } + item = item->next; + } + } + + if (j > 512) { +overflow: + j = 512; + buf[2] |= 0x02; /* set the truncated bit. */ + } + + req->response_len = j; + + if (!(req->response = malloc(req->response_len))) { + server_request_free_answers(req); + dnslabel_clear(&table); + return (-1); + } + memcpy(req->response, buf, req->response_len); + server_request_free_answers(req); + dnslabel_clear(&table); + return (0); +} + +/* exported function */ +int +evdns_server_request_respond(struct evdns_server_request *_req, int err) +{ + struct server_request *req = TO_SERVER_REQUEST(_req); + struct evdns_server_port *port = req->port; + int r; + if (!req->response) { + if ((r = evdns_server_request_format_response(req, err))<0) + return r; + } + + r = sendto(port->socket, req->response, req->response_len, 0, + (struct sockaddr*) &req->addr, req->addrlen); + if (r<0) { + int sock_err = last_error(port->socket); + if (! error_is_eagain(sock_err)) + return -1; + + if (port->pending_replies) { + req->prev_pending = port->pending_replies->prev_pending; + req->next_pending = port->pending_replies; + req->prev_pending->next_pending = + req->next_pending->prev_pending = req; + } else { + req->prev_pending = req->next_pending = req; + port->pending_replies = req; + port->choked = 1; + + (void) event_del(&port->event); + event_set(&port->event, port->socket, (port->closing?0:EV_READ) | EV_WRITE | EV_PERSIST, server_port_ready_callback, port); + + if (event_add(&port->event, NULL) < 0) { + log(EVDNS_LOG_WARN, "Error from libevent when adding event for DNS server"); + } + + } + + return 1; + } + if (server_request_free(req)) + return 0; + + if (port->pending_replies) + server_port_flush(port); + + return 0; +} + +/* Free all storage held by RRs in req. */ +static void +server_request_free_answers(struct server_request *req) +{ + struct server_reply_item *victim, *next, **list; + int i; + for (i = 0; i < 3; ++i) { + if (i==0) + list = &req->answer; + else if (i==1) + list = &req->authority; + else + list = &req->additional; + + victim = *list; + while (victim) { + next = victim->next; + free(victim->name); + if (victim->data) + free(victim->data); + free(victim); + victim = next; + } + *list = NULL; + } +} + +/* Free all storage held by req, and remove links to it. */ +/* return true iff we just wound up freeing the server_port. */ +static int +server_request_free(struct server_request *req) +{ + int i, rc=1; + if (req->base.questions) { + for (i = 0; i < req->base.nquestions; ++i) + free(req->base.questions[i]); + free(req->base.questions); + } + + if (req->port) { + if (req->port->pending_replies == req) { + if (req->next_pending) + req->port->pending_replies = req->next_pending; + else + req->port->pending_replies = NULL; + } + rc = --req->port->refcnt; + } + + if (req->response) { + free(req->response); + } + + server_request_free_answers(req); + + if (req->next_pending && req->next_pending != req) { + req->next_pending->prev_pending = req->prev_pending; + req->prev_pending->next_pending = req->next_pending; + } + + if (rc == 0) { + server_port_free(req->port); + free(req); + return (1); + } + free(req); + return (0); +} + +/* Free all storage held by an evdns_server_port. Only called when */ +static void +server_port_free(struct evdns_server_port *port) +{ + assert(port); + assert(!port->refcnt); + assert(!port->pending_replies); + if (port->socket > 0) { + CLOSE_SOCKET(port->socket); + port->socket = -1; + } + (void) event_del(&port->event); + /* XXXX actually free the port? -NM */ +} + +/* exported function */ +int +evdns_server_request_drop(struct evdns_server_request *_req) +{ + struct server_request *req = TO_SERVER_REQUEST(_req); + server_request_free(req); + return 0; +} + +/* exported function */ +int +evdns_server_request_get_requesting_addr(struct evdns_server_request *_req, struct sockaddr *sa, int addr_len) +{ + struct server_request *req = TO_SERVER_REQUEST(_req); + if (addr_len < (int)req->addrlen) + return -1; + memcpy(sa, &(req->addr), req->addrlen); + return req->addrlen; +} + +#undef APPEND16 +#undef APPEND32 + +/* this is a libevent callback function which is called when a request */ +/* has timed out. */ +static void +evdns_request_timeout_callback(int fd, short events, void *arg) { + struct request *const req = (struct request *) arg; + (void) fd; + (void) events; + + log(EVDNS_LOG_DEBUG, "Request %lx timed out", (unsigned long) arg); + + req->ns->timedout++; + if (req->ns->timedout > global_max_nameserver_timeout) { + req->ns->timedout = 0; + nameserver_failed(req->ns, "request timed out."); + } + + (void) evtimer_del(&req->timeout_event); + if (req->tx_count >= global_max_retransmits) { + /* this request has failed */ + reply_callback(req, 0, DNS_ERR_TIMEOUT, NULL); + request_finished(req, &req_head); + } else { + /* retransmit it */ + evdns_request_transmit(req); + } +} + +/* try to send a request to a given server. */ +/* */ +/* return: */ +/* 0 ok */ +/* 1 temporary failure */ +/* 2 other failure */ +static int +evdns_request_transmit_to(struct request *req, struct nameserver *server) { + struct sockaddr_in sin; + int r; + memset(&sin, 0, sizeof(sin)); + sin.sin_addr.s_addr = req->ns->address; + sin.sin_port = req->ns->port; + sin.sin_family = AF_INET; + + r = sendto(server->socket, req->request, req->request_len, 0, + (struct sockaddr*)&sin, sizeof(sin)); + if (r < 0) { + int err = last_error(server->socket); + if (error_is_eagain(err)) return 1; + nameserver_failed(req->ns, strerror(err)); + return 2; + } else if (r != (int)req->request_len) { + return 1; /* short write */ + } else { + return 0; + } +} + +/* try to send a request, updating the fields of the request */ +/* as needed */ +/* */ +/* return: */ +/* 0 ok */ +/* 1 failed */ +static int +evdns_request_transmit(struct request *req) { + int retcode = 0, r; + + /* if we fail to send this packet then this flag marks it */ + /* for evdns_transmit */ + req->transmit_me = 1; + if (req->trans_id == 0xffff) abort(); + + if (req->ns->choked) { + /* don't bother trying to write to a socket */ + /* which we have had EAGAIN from */ + return 1; + } + + r = evdns_request_transmit_to(req, req->ns); + switch (r) { + case 1: + /* temp failure */ + req->ns->choked = 1; + nameserver_write_waiting(req->ns, 1); + return 1; + case 2: + /* failed in some other way */ + retcode = 1; + /* fall through */ + default: + /* all ok */ + log(EVDNS_LOG_DEBUG, + "Setting timeout for request %lx", (unsigned long) req); + if (evtimer_add(&req->timeout_event, &global_timeout) < 0) { + log(EVDNS_LOG_WARN, + "Error from libevent when adding timer for request %lx", + (unsigned long) req); + /* ???? Do more? */ + } + req->tx_count++; + req->transmit_me = 0; + return retcode; + } +} + +static void +nameserver_probe_callback(int result, char type, int count, int ttl, void *addresses, void *arg) { + struct nameserver *const ns = (struct nameserver *) arg; + (void) type; + (void) count; + (void) ttl; + (void) addresses; + + if (result == DNS_ERR_NONE || result == DNS_ERR_NOTEXIST) { + /* this is a good reply */ + nameserver_up(ns); + } else nameserver_probe_failed(ns); +} + +static void +nameserver_send_probe(struct nameserver *const ns) { + struct request *req; + /* here we need to send a probe to a given nameserver */ + /* in the hope that it is up now. */ + + log(EVDNS_LOG_DEBUG, "Sending probe to %s", debug_ntoa(ns->address)); + + req = request_new(TYPE_A, "www.google.com", DNS_QUERY_NO_SEARCH, nameserver_probe_callback, ns); + if (!req) return; + /* we force this into the inflight queue no matter what */ + request_trans_id_set(req, transaction_id_pick()); + req->ns = ns; + request_submit(req); +} + +/* returns: */ +/* 0 didn't try to transmit anything */ +/* 1 tried to transmit something */ +static int +evdns_transmit(void) { + char did_try_to_transmit = 0; + + if (req_head) { + struct request *const started_at = req_head, *req = req_head; + /* first transmit all the requests which are currently waiting */ + do { + if (req->transmit_me) { + did_try_to_transmit = 1; + evdns_request_transmit(req); + } + + req = req->next; + } while (req != started_at); + } + + return did_try_to_transmit; +} + +/* exported function */ +int +evdns_count_nameservers(void) +{ + const struct nameserver *server = server_head; + int n = 0; + if (!server) + return 0; + do { + ++n; + server = server->next; + } while (server != server_head); + return n; +} + +/* exported function */ +int +evdns_clear_nameservers_and_suspend(void) +{ + struct nameserver *server = server_head, *started_at = server_head; + struct request *req = req_head, *req_started_at = req_head; + + if (!server) + return 0; + while (1) { + struct nameserver *next = server->next; + (void) event_del(&server->event); + if (evtimer_initialized(&server->timeout_event)) + (void) evtimer_del(&server->timeout_event); + if (server->socket >= 0) + CLOSE_SOCKET(server->socket); + free(server); + if (next == started_at) + break; + server = next; + } + server_head = NULL; + global_good_nameservers = 0; + + while (req) { + struct request *next = req->next; + req->tx_count = req->reissue_count = 0; + req->ns = NULL; + /* ???? What to do about searches? */ + (void) evtimer_del(&req->timeout_event); + req->trans_id = 0; + req->transmit_me = 0; + + global_requests_waiting++; + evdns_request_insert(req, &req_waiting_head); + /* We want to insert these suspended elements at the front of + * the waiting queue, since they were pending before any of + * the waiting entries were added. This is a circular list, + * so we can just shift the start back by one.*/ + req_waiting_head = req_waiting_head->prev; + + if (next == req_started_at) + break; + req = next; + } + req_head = NULL; + global_requests_inflight = 0; + + return 0; +} + + +/* exported function */ +int +evdns_resume(void) +{ + evdns_requests_pump_waiting_queue(); + return 0; +} + +static int +_evdns_nameserver_add_impl(unsigned long int address, int port) { + /* first check to see if we already have this nameserver */ + + const struct nameserver *server = server_head, *const started_at = server_head; + struct nameserver *ns; + int err = 0; + if (server) { + do { + if (server->address == address) return 3; + server = server->next; + } while (server != started_at); + } + + ns = (struct nameserver *) malloc(sizeof(struct nameserver)); + if (!ns) return -1; + + memset(ns, 0, sizeof(struct nameserver)); + + evtimer_set(&ns->timeout_event, nameserver_prod_callback, ns); + + ns->socket = socket(PF_INET, SOCK_DGRAM, 0); + if (ns->socket < 0) { err = 1; goto out1; } + evutil_make_socket_nonblocking(ns->socket); + + ns->address = address; + ns->port = htons(port); + ns->state = 1; + event_set(&ns->event, ns->socket, EV_READ | EV_PERSIST, nameserver_ready_callback, ns); + if (event_add(&ns->event, NULL) < 0) { + err = 2; + goto out2; + } + + log(EVDNS_LOG_DEBUG, "Added nameserver %s", debug_ntoa(address)); + + /* insert this nameserver into the list of them */ + if (!server_head) { + ns->next = ns->prev = ns; + server_head = ns; + } else { + ns->next = server_head->next; + ns->prev = server_head; + server_head->next = ns; + if (server_head->prev == server_head) { + server_head->prev = ns; + } + } + + global_good_nameservers++; + + return 0; + +out2: + CLOSE_SOCKET(ns->socket); +out1: + free(ns); + log(EVDNS_LOG_WARN, "Unable to add nameserver %s: error %d", debug_ntoa(address), err); + return err; +} + +/* exported function */ +int +evdns_nameserver_add(unsigned long int address) { + return _evdns_nameserver_add_impl(address, 53); +} + +/* exported function */ +int +evdns_nameserver_ip_add(const char *ip_as_string) { + struct in_addr ina; + int port; + char buf[20]; + const char *cp; + cp = strchr(ip_as_string, ':'); + if (! cp) { + cp = ip_as_string; + port = 53; + } else { + port = strtoint(cp+1); + if (port < 0 || port > 65535) { + return 4; + } + if ((cp-ip_as_string) >= (int)sizeof(buf)) { + return 4; + } + memcpy(buf, ip_as_string, cp-ip_as_string); + buf[cp-ip_as_string] = '\0'; + cp = buf; + } + if (!inet_aton(cp, &ina)) { + return 4; + } + return _evdns_nameserver_add_impl(ina.s_addr, port); +} + +/* insert into the tail of the queue */ +static void +evdns_request_insert(struct request *req, struct request **head) { + if (!*head) { + *head = req; + req->next = req->prev = req; + return; + } + + req->prev = (*head)->prev; + req->prev->next = req; + req->next = *head; + (*head)->prev = req; +} + +static int +string_num_dots(const char *s) { + int count = 0; + while ((s = strchr(s, '.'))) { + s++; + count++; + } + return count; +} + +static struct request * +request_new(int type, const char *name, int flags, + evdns_callback_type callback, void *user_ptr) { + const char issuing_now = + (global_requests_inflight < global_max_requests_inflight) ? 1 : 0; + + const int name_len = strlen(name); + const int request_max_len = evdns_request_len(name_len); + const u16 trans_id = issuing_now ? transaction_id_pick() : 0xffff; + /* the request data is alloced in a single block with the header */ + struct request *const req = + (struct request *) malloc(sizeof(struct request) + request_max_len); + int rlen; + (void) flags; + + if (!req) return NULL; + memset(req, 0, sizeof(struct request)); + + evtimer_set(&req->timeout_event, evdns_request_timeout_callback, req); + + /* request data lives just after the header */ + req->request = ((u8 *) req) + sizeof(struct request); + /* denotes that the request data shouldn't be free()ed */ + req->request_appended = 1; + rlen = evdns_request_data_build(name, name_len, trans_id, + type, CLASS_INET, req->request, request_max_len); + if (rlen < 0) + goto err1; + req->request_len = rlen; + req->trans_id = trans_id; + req->tx_count = 0; + req->request_type = type; + req->user_pointer = user_ptr; + req->user_callback = callback; + req->ns = issuing_now ? nameserver_pick() : NULL; + req->next = req->prev = NULL; + + return req; +err1: + free(req); + return NULL; +} + +static void +request_submit(struct request *const req) { + if (req->ns) { + /* if it has a nameserver assigned then this is going */ + /* straight into the inflight queue */ + evdns_request_insert(req, &req_head); + global_requests_inflight++; + evdns_request_transmit(req); + } else { + evdns_request_insert(req, &req_waiting_head); + global_requests_waiting++; + } +} + +/* exported function */ +int evdns_resolve_ipv4(const char *name, int flags, + evdns_callback_type callback, void *ptr) { + log(EVDNS_LOG_DEBUG, "Resolve requested for %s", name); + if (flags & DNS_QUERY_NO_SEARCH) { + struct request *const req = + request_new(TYPE_A, name, flags, callback, ptr); + if (req == NULL) + return (1); + request_submit(req); + return (0); + } else { + return (search_request_new(TYPE_A, name, flags, callback, ptr)); + } +} + +/* exported function */ +int evdns_resolve_ipv6(const char *name, int flags, + evdns_callback_type callback, void *ptr) { + log(EVDNS_LOG_DEBUG, "Resolve requested for %s", name); + if (flags & DNS_QUERY_NO_SEARCH) { + struct request *const req = + request_new(TYPE_AAAA, name, flags, callback, ptr); + if (req == NULL) + return (1); + request_submit(req); + return (0); + } else { + return (search_request_new(TYPE_AAAA, name, flags, callback, ptr)); + } +} + +int evdns_resolve_reverse(const struct in_addr *in, int flags, evdns_callback_type callback, void *ptr) { + char buf[32]; + struct request *req; + u32 a; + assert(in); + a = ntohl(in->s_addr); + evutil_snprintf(buf, sizeof(buf), "%d.%d.%d.%d.in-addr.arpa", + (int)(u8)((a )&0xff), + (int)(u8)((a>>8 )&0xff), + (int)(u8)((a>>16)&0xff), + (int)(u8)((a>>24)&0xff)); + log(EVDNS_LOG_DEBUG, "Resolve requested for %s (reverse)", buf); + req = request_new(TYPE_PTR, buf, flags, callback, ptr); + if (!req) return 1; + request_submit(req); + return 0; +} + +int evdns_resolve_reverse_ipv6(const struct in6_addr *in, int flags, evdns_callback_type callback, void *ptr) { + /* 32 nybbles, 32 periods, "ip6.arpa", NUL. */ + char buf[73]; + char *cp; + struct request *req; + int i; + assert(in); + cp = buf; + for (i=15; i >= 0; --i) { + u8 byte = in->s6_addr[i]; + *cp++ = "0123456789abcdef"[byte & 0x0f]; + *cp++ = '.'; + *cp++ = "0123456789abcdef"[byte >> 4]; + *cp++ = '.'; + } + assert(cp + strlen("ip6.arpa") < buf+sizeof(buf)); + memcpy(cp, "ip6.arpa", strlen("ip6.arpa")+1); + log(EVDNS_LOG_DEBUG, "Resolve requested for %s (reverse)", buf); + req = request_new(TYPE_PTR, buf, flags, callback, ptr); + if (!req) return 1; + request_submit(req); + return 0; +} + +/*/////////////////////////////////////////////////////////////////// */ +/* Search support */ +/* */ +/* the libc resolver has support for searching a number of domains */ +/* to find a name. If nothing else then it takes the single domain */ +/* from the gethostname() call. */ +/* */ +/* It can also be configured via the domain and search options in a */ +/* resolv.conf. */ +/* */ +/* The ndots option controls how many dots it takes for the resolver */ +/* to decide that a name is non-local and so try a raw lookup first. */ + +struct search_domain { + int len; + struct search_domain *next; + /* the text string is appended to this structure */ +}; + +struct search_state { + int refcount; + int ndots; + int num_domains; + struct search_domain *head; +}; + +static struct search_state *global_search_state = NULL; + +static void +search_state_decref(struct search_state *const state) { + if (!state) return; + state->refcount--; + if (!state->refcount) { + struct search_domain *next, *dom; + for (dom = state->head; dom; dom = next) { + next = dom->next; + free(dom); + } + free(state); + } +} + +static struct search_state * +search_state_new(void) { + struct search_state *state = (struct search_state *) malloc(sizeof(struct search_state)); + if (!state) return NULL; + memset(state, 0, sizeof(struct search_state)); + state->refcount = 1; + state->ndots = 1; + + return state; +} + +static void +search_postfix_clear(void) { + search_state_decref(global_search_state); + + global_search_state = search_state_new(); +} + +/* exported function */ +void +evdns_search_clear(void) { + search_postfix_clear(); +} + +static void +search_postfix_add(const char *domain) { + int domain_len; + struct search_domain *sdomain; + while (domain[0] == '.') domain++; + domain_len = strlen(domain); + + if (!global_search_state) global_search_state = search_state_new(); + if (!global_search_state) return; + global_search_state->num_domains++; + + sdomain = (struct search_domain *) malloc(sizeof(struct search_domain) + domain_len); + if (!sdomain) return; + memcpy( ((u8 *) sdomain) + sizeof(struct search_domain), domain, domain_len); + sdomain->next = global_search_state->head; + sdomain->len = domain_len; + + global_search_state->head = sdomain; +} + +/* reverse the order of members in the postfix list. This is needed because, */ +/* when parsing resolv.conf we push elements in the wrong order */ +static void +search_reverse(void) { + struct search_domain *cur, *prev = NULL, *next; + cur = global_search_state->head; + while (cur) { + next = cur->next; + cur->next = prev; + prev = cur; + cur = next; + } + + global_search_state->head = prev; +} + +/* exported function */ +void +evdns_search_add(const char *domain) { + search_postfix_add(domain); +} + +/* exported function */ +void +evdns_search_ndots_set(const int ndots) { + if (!global_search_state) global_search_state = search_state_new(); + if (!global_search_state) return; + global_search_state->ndots = ndots; +} + +static void +search_set_from_hostname(void) { + char hostname[HOST_NAME_MAX + 1], *domainname; + + search_postfix_clear(); + if (gethostname(hostname, sizeof(hostname))) return; + domainname = strchr(hostname, '.'); + if (!domainname) return; + search_postfix_add(domainname); +} + +/* warning: returns malloced string */ +static char * +search_make_new(const struct search_state *const state, int n, const char *const base_name) { + const int base_len = strlen(base_name); + const char need_to_append_dot = base_name[base_len - 1] == '.' ? 0 : 1; + struct search_domain *dom; + + for (dom = state->head; dom; dom = dom->next) { + if (!n--) { + /* this is the postfix we want */ + /* the actual postfix string is kept at the end of the structure */ + const u8 *const postfix = ((u8 *) dom) + sizeof(struct search_domain); + const int postfix_len = dom->len; + char *const newname = (char *) malloc(base_len + need_to_append_dot + postfix_len + 1); + if (!newname) return NULL; + memcpy(newname, base_name, base_len); + if (need_to_append_dot) newname[base_len] = '.'; + memcpy(newname + base_len + need_to_append_dot, postfix, postfix_len); + newname[base_len + need_to_append_dot + postfix_len] = 0; + return newname; + } + } + + /* we ran off the end of the list and still didn't find the requested string */ + abort(); + return NULL; /* unreachable; stops warnings in some compilers. */ +} + +static int +search_request_new(int type, const char *const name, int flags, evdns_callback_type user_callback, void *user_arg) { + assert(type == TYPE_A || type == TYPE_AAAA); + if ( ((flags & DNS_QUERY_NO_SEARCH) == 0) && + global_search_state && + global_search_state->num_domains) { + /* we have some domains to search */ + struct request *req; + if (string_num_dots(name) >= global_search_state->ndots) { + req = request_new(type, name, flags, user_callback, user_arg); + if (!req) return 1; + req->search_index = -1; + } else { + char *const new_name = search_make_new(global_search_state, 0, name); + if (!new_name) return 1; + req = request_new(type, new_name, flags, user_callback, user_arg); + free(new_name); + if (!req) return 1; + req->search_index = 0; + } + req->search_origname = strdup(name); + req->search_state = global_search_state; + req->search_flags = flags; + global_search_state->refcount++; + request_submit(req); + return 0; + } else { + struct request *const req = request_new(type, name, flags, user_callback, user_arg); + if (!req) return 1; + request_submit(req); + return 0; + } +} + +/* this is called when a request has failed to find a name. We need to check */ +/* if it is part of a search and, if so, try the next name in the list */ +/* returns: */ +/* 0 another request has been submitted */ +/* 1 no more requests needed */ +static int +search_try_next(struct request *const req) { + if (req->search_state) { + /* it is part of a search */ + char *new_name; + struct request *newreq; + req->search_index++; + if (req->search_index >= req->search_state->num_domains) { + /* no more postfixes to try, however we may need to try */ + /* this name without a postfix */ + if (string_num_dots(req->search_origname) < req->search_state->ndots) { + /* yep, we need to try it raw */ + newreq = request_new(req->request_type, req->search_origname, req->search_flags, req->user_callback, req->user_pointer); + log(EVDNS_LOG_DEBUG, "Search: trying raw query %s", req->search_origname); + if (newreq) { + request_submit(newreq); + return 0; + } + } + return 1; + } + + new_name = search_make_new(req->search_state, req->search_index, req->search_origname); + if (!new_name) return 1; + log(EVDNS_LOG_DEBUG, "Search: now trying %s (%d)", new_name, req->search_index); + newreq = request_new(req->request_type, new_name, req->search_flags, req->user_callback, req->user_pointer); + free(new_name); + if (!newreq) return 1; + newreq->search_origname = req->search_origname; + req->search_origname = NULL; + newreq->search_state = req->search_state; + newreq->search_flags = req->search_flags; + newreq->search_index = req->search_index; + newreq->search_state->refcount++; + request_submit(newreq); + return 0; + } + return 1; +} + +static void +search_request_finished(struct request *const req) { + if (req->search_state) { + search_state_decref(req->search_state); + req->search_state = NULL; + } + if (req->search_origname) { + free(req->search_origname); + req->search_origname = NULL; + } +} + +/*/////////////////////////////////////////////////////////////////// */ +/* Parsing resolv.conf files */ + +static void +evdns_resolv_set_defaults(int flags) { + /* if the file isn't found then we assume a local resolver */ + if (flags & DNS_OPTION_SEARCH) search_set_from_hostname(); + if (flags & DNS_OPTION_NAMESERVERS) evdns_nameserver_ip_add("127.0.0.1"); +} + +#ifndef HAVE_STRTOK_R +static char * +strtok_r(char *s, const char *delim, char **state) { + return strtok(s, delim); +} +#endif + +/* helper version of atoi which returns -1 on error */ +static int +strtoint(const char *const str) { + char *endptr; + const int r = strtol(str, &endptr, 10); + if (*endptr) return -1; + return r; +} + +/* helper version of atoi that returns -1 on error and clips to bounds. */ +static int +strtoint_clipped(const char *const str, int min, int max) +{ + int r = strtoint(str); + if (r == -1) + return r; + else if (rmax) + return max; + else + return r; +} + +/* exported function */ +int +evdns_set_option(const char *option, const char *val, int flags) +{ + if (!strncmp(option, "ndots:", 6)) { + const int ndots = strtoint(val); + if (ndots == -1) return -1; + if (!(flags & DNS_OPTION_SEARCH)) return 0; + log(EVDNS_LOG_DEBUG, "Setting ndots to %d", ndots); + if (!global_search_state) global_search_state = search_state_new(); + if (!global_search_state) return -1; + global_search_state->ndots = ndots; + } else if (!strncmp(option, "timeout:", 8)) { + const int timeout = strtoint(val); + if (timeout == -1) return -1; + if (!(flags & DNS_OPTION_MISC)) return 0; + log(EVDNS_LOG_DEBUG, "Setting timeout to %d", timeout); + global_timeout.tv_sec = timeout; + } else if (!strncmp(option, "max-timeouts:", 12)) { + const int maxtimeout = strtoint_clipped(val, 1, 255); + if (maxtimeout == -1) return -1; + if (!(flags & DNS_OPTION_MISC)) return 0; + log(EVDNS_LOG_DEBUG, "Setting maximum allowed timeouts to %d", + maxtimeout); + global_max_nameserver_timeout = maxtimeout; + } else if (!strncmp(option, "max-inflight:", 13)) { + const int maxinflight = strtoint_clipped(val, 1, 65000); + if (maxinflight == -1) return -1; + if (!(flags & DNS_OPTION_MISC)) return 0; + log(EVDNS_LOG_DEBUG, "Setting maximum inflight requests to %d", + maxinflight); + global_max_requests_inflight = maxinflight; + } else if (!strncmp(option, "attempts:", 9)) { + int retries = strtoint(val); + if (retries == -1) return -1; + if (retries > 255) retries = 255; + if (!(flags & DNS_OPTION_MISC)) return 0; + log(EVDNS_LOG_DEBUG, "Setting retries to %d", retries); + global_max_retransmits = retries; + } + return 0; +} + +static void +resolv_conf_parse_line(char *const start, int flags) { + char *strtok_state; + static const char *const delims = " \t"; +#define NEXT_TOKEN strtok_r(NULL, delims, &strtok_state) + + char *const first_token = strtok_r(start, delims, &strtok_state); + if (!first_token) return; + + if (!strcmp(first_token, "nameserver") && (flags & DNS_OPTION_NAMESERVERS)) { + const char *const nameserver = NEXT_TOKEN; + struct in_addr ina; + + if (inet_aton(nameserver, &ina)) { + /* address is valid */ + evdns_nameserver_add(ina.s_addr); + } + } else if (!strcmp(first_token, "domain") && (flags & DNS_OPTION_SEARCH)) { + const char *const domain = NEXT_TOKEN; + if (domain) { + search_postfix_clear(); + search_postfix_add(domain); + } + } else if (!strcmp(first_token, "search") && (flags & DNS_OPTION_SEARCH)) { + const char *domain; + search_postfix_clear(); + + while ((domain = NEXT_TOKEN)) { + search_postfix_add(domain); + } + search_reverse(); + } else if (!strcmp(first_token, "options")) { + const char *option; + while ((option = NEXT_TOKEN)) { + const char *val = strchr(option, ':'); + evdns_set_option(option, val ? val+1 : "", flags); + } + } +#undef NEXT_TOKEN +} + +/* exported function */ +/* returns: */ +/* 0 no errors */ +/* 1 failed to open file */ +/* 2 failed to stat file */ +/* 3 file too large */ +/* 4 out of memory */ +/* 5 short read from file */ +int +evdns_resolv_conf_parse(int flags, const char *const filename) { + struct stat st; + int fd, n, r; + u8 *resolv; + char *start; + int err = 0; + + log(EVDNS_LOG_DEBUG, "Parsing resolv.conf file %s", filename); + + fd = open(filename, O_RDONLY); + if (fd < 0) { + evdns_resolv_set_defaults(flags); + return 1; + } + + if (fstat(fd, &st)) { err = 2; goto out1; } + if (!st.st_size) { + evdns_resolv_set_defaults(flags); + err = (flags & DNS_OPTION_NAMESERVERS) ? 6 : 0; + goto out1; + } + if (st.st_size > 65535) { err = 3; goto out1; } /* no resolv.conf should be any bigger */ + + resolv = (u8 *) malloc((size_t)st.st_size + 1); + if (!resolv) { err = 4; goto out1; } + + n = 0; + while ((r = read(fd, resolv+n, (size_t)st.st_size-n)) > 0) { + n += r; + if (n == st.st_size) + break; + assert(n < st.st_size); + } + if (r < 0) { err = 5; goto out2; } + resolv[n] = 0; /* we malloced an extra byte; this should be fine. */ + + start = (char *) resolv; + for (;;) { + char *const newline = strchr(start, '\n'); + if (!newline) { + resolv_conf_parse_line(start, flags); + break; + } else { + *newline = 0; + resolv_conf_parse_line(start, flags); + start = newline + 1; + } + } + + if (!server_head && (flags & DNS_OPTION_NAMESERVERS)) { + /* no nameservers were configured. */ + evdns_nameserver_ip_add("127.0.0.1"); + err = 6; + } + if (flags & DNS_OPTION_SEARCH && (!global_search_state || global_search_state->num_domains == 0)) { + search_set_from_hostname(); + } + +out2: + free(resolv); +out1: + close(fd); + return err; +} + +#ifdef WIN32 +/* Add multiple nameservers from a space-or-comma-separated list. */ +static int +evdns_nameserver_ip_add_line(const char *ips) { + const char *addr; + char *buf; + int r; + while (*ips) { + while (ISSPACE(*ips) || *ips == ',' || *ips == '\t') + ++ips; + addr = ips; + while (ISDIGIT(*ips) || *ips == '.' || *ips == ':') + ++ips; + buf = malloc(ips-addr+1); + if (!buf) return 4; + memcpy(buf, addr, ips-addr); + buf[ips-addr] = '\0'; + r = evdns_nameserver_ip_add(buf); + free(buf); + if (r) return r; + } + return 0; +} + +typedef DWORD(WINAPI *GetNetworkParams_fn_t)(FIXED_INFO *, DWORD*); + +/* Use the windows GetNetworkParams interface in iphlpapi.dll to */ +/* figure out what our nameservers are. */ +static int +load_nameservers_with_getnetworkparams(void) +{ + /* Based on MSDN examples and inspection of c-ares code. */ + FIXED_INFO *fixed; + HMODULE handle = 0; + ULONG size = sizeof(FIXED_INFO); + void *buf = NULL; + int status = 0, r, added_any; + IP_ADDR_STRING *ns; + GetNetworkParams_fn_t fn; + + if (!(handle = LoadLibrary("iphlpapi.dll"))) { + log(EVDNS_LOG_WARN, "Could not open iphlpapi.dll"); + status = -1; + goto done; + } + if (!(fn = (GetNetworkParams_fn_t) GetProcAddress(handle, "GetNetworkParams"))) { + log(EVDNS_LOG_WARN, "Could not get address of function."); + status = -1; + goto done; + } + + buf = malloc(size); + if (!buf) { status = 4; goto done; } + fixed = buf; + r = fn(fixed, &size); + if (r != ERROR_SUCCESS && r != ERROR_BUFFER_OVERFLOW) { + status = -1; + goto done; + } + if (r != ERROR_SUCCESS) { + free(buf); + buf = malloc(size); + if (!buf) { status = 4; goto done; } + fixed = buf; + r = fn(fixed, &size); + if (r != ERROR_SUCCESS) { + log(EVDNS_LOG_DEBUG, "fn() failed."); + status = -1; + goto done; + } + } + + assert(fixed); + added_any = 0; + ns = &(fixed->DnsServerList); + while (ns) { + r = evdns_nameserver_ip_add_line(ns->IpAddress.String); + if (r) { + log(EVDNS_LOG_DEBUG,"Could not add nameserver %s to list,error: %d", + (ns->IpAddress.String),(int)GetLastError()); + status = r; + goto done; + } else { + log(EVDNS_LOG_DEBUG,"Succesfully added %s as nameserver",ns->IpAddress.String); + } + + added_any++; + ns = ns->Next; + } + + if (!added_any) { + log(EVDNS_LOG_DEBUG, "No nameservers added."); + status = -1; + } + + done: + if (buf) + free(buf); + if (handle) + FreeLibrary(handle); + return status; +} + +static int +config_nameserver_from_reg_key(HKEY key, const char *subkey) +{ + char *buf; + DWORD bufsz = 0, type = 0; + int status = 0; + + if (RegQueryValueEx(key, subkey, 0, &type, NULL, &bufsz) + != ERROR_MORE_DATA) + return -1; + if (!(buf = malloc(bufsz))) + return -1; + + if (RegQueryValueEx(key, subkey, 0, &type, (LPBYTE)buf, &bufsz) + == ERROR_SUCCESS && bufsz > 1) { + status = evdns_nameserver_ip_add_line(buf); + } + + free(buf); + return status; +} + +#define SERVICES_KEY "System\\CurrentControlSet\\Services\\" +#define WIN_NS_9X_KEY SERVICES_KEY "VxD\\MSTCP" +#define WIN_NS_NT_KEY SERVICES_KEY "Tcpip\\Parameters" + +static int +load_nameservers_from_registry(void) +{ + int found = 0; + int r; +#define TRY(k, name) \ + if (!found && config_nameserver_from_reg_key(k,name) == 0) { \ + log(EVDNS_LOG_DEBUG,"Found nameservers in %s/%s",#k,name); \ + found = 1; \ + } else if (!found) { \ + log(EVDNS_LOG_DEBUG,"Didn't find nameservers in %s/%s", \ + #k,#name); \ + } + + if (((int)GetVersion()) > 0) { /* NT */ + HKEY nt_key = 0, interfaces_key = 0; + + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_NS_NT_KEY, 0, + KEY_READ, &nt_key) != ERROR_SUCCESS) { + log(EVDNS_LOG_DEBUG,"Couldn't open nt key, %d",(int)GetLastError()); + return -1; + } + r = RegOpenKeyEx(nt_key, "Interfaces", 0, + KEY_QUERY_VALUE|KEY_ENUMERATE_SUB_KEYS, + &interfaces_key); + if (r != ERROR_SUCCESS) { + log(EVDNS_LOG_DEBUG,"Couldn't open interfaces key, %d",(int)GetLastError()); + return -1; + } + TRY(nt_key, "NameServer"); + TRY(nt_key, "DhcpNameServer"); + TRY(interfaces_key, "NameServer"); + TRY(interfaces_key, "DhcpNameServer"); + RegCloseKey(interfaces_key); + RegCloseKey(nt_key); + } else { + HKEY win_key = 0; + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_NS_9X_KEY, 0, + KEY_READ, &win_key) != ERROR_SUCCESS) { + log(EVDNS_LOG_DEBUG, "Couldn't open registry key, %d", (int)GetLastError()); + return -1; + } + TRY(win_key, "NameServer"); + RegCloseKey(win_key); + } + + if (found == 0) { + log(EVDNS_LOG_WARN,"Didn't find any nameservers."); + } + + return found ? 0 : -1; +#undef TRY +} + +int +evdns_config_windows_nameservers(void) +{ + if (load_nameservers_with_getnetworkparams() == 0) + return 0; + return load_nameservers_from_registry(); +} +#endif + +int +evdns_init(void) +{ + int res = 0; +#ifdef WIN32 + res = evdns_config_windows_nameservers(); +#else + res = evdns_resolv_conf_parse(DNS_OPTIONS_ALL, "/etc/resolv.conf"); +#endif + + return (res); +} + +const char * +evdns_err_to_string(int err) +{ + switch (err) { + case DNS_ERR_NONE: return "no error"; + case DNS_ERR_FORMAT: return "misformatted query"; + case DNS_ERR_SERVERFAILED: return "server failed"; + case DNS_ERR_NOTEXIST: return "name does not exist"; + case DNS_ERR_NOTIMPL: return "query not implemented"; + case DNS_ERR_REFUSED: return "refused"; + + case DNS_ERR_TRUNCATED: return "reply truncated or ill-formed"; + case DNS_ERR_UNKNOWN: return "unknown"; + case DNS_ERR_TIMEOUT: return "request timed out"; + case DNS_ERR_SHUTDOWN: return "dns subsystem shut down"; + default: return "[Unknown error code]"; + } +} + +void +evdns_shutdown(int fail_requests) +{ + struct nameserver *server, *server_next; + struct search_domain *dom, *dom_next; + + while (req_head) { + if (fail_requests) + reply_callback(req_head, 0, DNS_ERR_SHUTDOWN, NULL); + request_finished(req_head, &req_head); + } + while (req_waiting_head) { + if (fail_requests) + reply_callback(req_waiting_head, 0, DNS_ERR_SHUTDOWN, NULL); + request_finished(req_waiting_head, &req_waiting_head); + } + global_requests_inflight = global_requests_waiting = 0; + + for (server = server_head; server; server = server_next) { + server_next = server->next; + if (server->socket >= 0) + CLOSE_SOCKET(server->socket); + (void) event_del(&server->event); + if (server->state == 0) + (void) event_del(&server->timeout_event); + free(server); + if (server_next == server_head) + break; + } + server_head = NULL; + global_good_nameservers = 0; + + if (global_search_state) { + for (dom = global_search_state->head; dom; dom = dom_next) { + dom_next = dom->next; + free(dom); + } + free(global_search_state); + global_search_state = NULL; + } + evdns_log_fn = NULL; +} + +#ifdef EVDNS_MAIN +void +main_callback(int result, char type, int count, int ttl, + void *addrs, void *orig) { + char *n = (char*)orig; + int i; + for (i = 0; i < count; ++i) { + if (type == DNS_IPv4_A) { + printf("%s: %s\n", n, debug_ntoa(((u32*)addrs)[i])); + } else if (type == DNS_PTR) { + printf("%s: %s\n", n, ((char**)addrs)[i]); + } + } + if (!count) { + printf("%s: No answer (%d)\n", n, result); + } + fflush(stdout); +} +void +evdns_server_callback(struct evdns_server_request *req, void *data) +{ + int i, r; + (void)data; + /* dummy; give 192.168.11.11 as an answer for all A questions, + * give foo.bar.example.com as an answer for all PTR questions. */ + for (i = 0; i < req->nquestions; ++i) { + u32 ans = htonl(0xc0a80b0bUL); + if (req->questions[i]->type == EVDNS_TYPE_A && + req->questions[i]->dns_question_class == EVDNS_CLASS_INET) { + printf(" -- replying for %s (A)\n", req->questions[i]->name); + r = evdns_server_request_add_a_reply(req, req->questions[i]->name, + 1, &ans, 10); + if (r<0) + printf("eeep, didn't work.\n"); + } else if (req->questions[i]->type == EVDNS_TYPE_PTR && + req->questions[i]->dns_question_class == EVDNS_CLASS_INET) { + printf(" -- replying for %s (PTR)\n", req->questions[i]->name); + r = evdns_server_request_add_ptr_reply(req, NULL, req->questions[i]->name, + "foo.bar.example.com", 10); + } else { + printf(" -- skipping %s [%d %d]\n", req->questions[i]->name, + req->questions[i]->type, req->questions[i]->dns_question_class); + } + } + + r = evdns_request_respond(req, 0); + if (r<0) + printf("eeek, couldn't send reply.\n"); +} + +void +logfn(int is_warn, const char *msg) { + (void) is_warn; + fprintf(stderr, "%s\n", msg); +} +int +main(int c, char **v) { + int idx; + int reverse = 0, verbose = 1, servertest = 0; + if (c<2) { + fprintf(stderr, "syntax: %s [-x] [-v] hostname\n", v[0]); + fprintf(stderr, "syntax: %s [-servertest]\n", v[0]); + return 1; + } + idx = 1; + while (idx < c && v[idx][0] == '-') { + if (!strcmp(v[idx], "-x")) + reverse = 1; + else if (!strcmp(v[idx], "-v")) + verbose = 1; + else if (!strcmp(v[idx], "-servertest")) + servertest = 1; + else + fprintf(stderr, "Unknown option %s\n", v[idx]); + ++idx; + } + event_init(); + if (verbose) + evdns_set_log_fn(logfn); + evdns_resolv_conf_parse(DNS_OPTION_NAMESERVERS, "/etc/resolv.conf"); + if (servertest) { + int sock; + struct sockaddr_in my_addr; + sock = socket(PF_INET, SOCK_DGRAM, 0); + evutil_make_socket_nonblocking(sock); + my_addr.sin_family = AF_INET; + my_addr.sin_port = htons(10053); + my_addr.sin_addr.s_addr = INADDR_ANY; + if (bind(sock, (struct sockaddr*)&my_addr, sizeof(my_addr))<0) { + perror("bind"); + exit(1); + } + evdns_add_server_port(sock, 0, evdns_server_callback, NULL); + } + for (; idx < c; ++idx) { + if (reverse) { + struct in_addr addr; + if (!inet_aton(v[idx], &addr)) { + fprintf(stderr, "Skipping non-IP %s\n", v[idx]); + continue; + } + fprintf(stderr, "resolving %s...\n",v[idx]); + evdns_resolve_reverse(&addr, 0, main_callback, v[idx]); + } else { + fprintf(stderr, "resolving (fwd) %s...\n",v[idx]); + evdns_resolve_ipv4(v[idx], 0, main_callback, v[idx]); + } + } + fflush(stdout); + event_dispatch(); + return 0; +} +#endif diff --git a/third_party/libevent/evdns.h b/third_party/libevent/evdns.h new file mode 100644 index 0000000000..fca4ac380b --- /dev/null +++ b/third_party/libevent/evdns.h @@ -0,0 +1,528 @@ +/* + * Copyright (c) 2006 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * The original DNS code is due to Adam Langley with heavy + * modifications by Nick Mathewson. Adam put his DNS software in the + * public domain. You can find his original copyright below. Please, + * aware that the code as part of libevent is governed by the 3-clause + * BSD license above. + * + * This software is Public Domain. To view a copy of the public domain dedication, + * visit http://creativecommons.org/licenses/publicdomain/ or send a letter to + * Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA. + * + * I ask and expect, but do not require, that all derivative works contain an + * attribution similar to: + * Parts developed by Adam Langley + * + * You may wish to replace the word "Parts" with something else depending on + * the amount of original code. + * + * (Derivative works does not include programs which link against, run or include + * the source verbatim in their source distributions) + */ + +/** @file evdns.h + * + * Welcome, gentle reader + * + * Async DNS lookups are really a whole lot harder than they should be, + * mostly stemming from the fact that the libc resolver has never been + * very good at them. Before you use this library you should see if libc + * can do the job for you with the modern async call getaddrinfo_a + * (see http://www.imperialviolet.org/page25.html#e498). Otherwise, + * please continue. + * + * This code is based on libevent and you must call event_init before + * any of the APIs in this file. You must also seed the OpenSSL random + * source if you are using OpenSSL for ids (see below). + * + * This library is designed to be included and shipped with your source + * code. You statically link with it. You should also test for the + * existence of strtok_r and define HAVE_STRTOK_R if you have it. + * + * The DNS protocol requires a good source of id numbers and these + * numbers should be unpredictable for spoofing reasons. There are + * three methods for generating them here and you must define exactly + * one of them. In increasing order of preference: + * + * DNS_USE_GETTIMEOFDAY_FOR_ID: + * Using the bottom 16 bits of the usec result from gettimeofday. This + * is a pretty poor solution but should work anywhere. + * DNS_USE_CPU_CLOCK_FOR_ID: + * Using the bottom 16 bits of the nsec result from the CPU's time + * counter. This is better, but may not work everywhere. Requires + * POSIX realtime support and you'll need to link against -lrt on + * glibc systems at least. + * DNS_USE_OPENSSL_FOR_ID: + * Uses the OpenSSL RAND_bytes call to generate the data. You must + * have seeded the pool before making any calls to this library. + * + * The library keeps track of the state of nameservers and will avoid + * them when they go down. Otherwise it will round robin between them. + * + * Quick start guide: + * #include "evdns.h" + * void callback(int result, char type, int count, int ttl, + * void *addresses, void *arg); + * evdns_resolv_conf_parse(DNS_OPTIONS_ALL, "/etc/resolv.conf"); + * evdns_resolve("www.hostname.com", 0, callback, NULL); + * + * When the lookup is complete the callback function is called. The + * first argument will be one of the DNS_ERR_* defines in evdns.h. + * Hopefully it will be DNS_ERR_NONE, in which case type will be + * DNS_IPv4_A, count will be the number of IP addresses, ttl is the time + * which the data can be cached for (in seconds), addresses will point + * to an array of uint32_t's and arg will be whatever you passed to + * evdns_resolve. + * + * Searching: + * + * In order for this library to be a good replacement for glibc's resolver it + * supports searching. This involves setting a list of default domains, in + * which names will be queried for. The number of dots in the query name + * determines the order in which this list is used. + * + * Searching appears to be a single lookup from the point of view of the API, + * although many DNS queries may be generated from a single call to + * evdns_resolve. Searching can also drastically slow down the resolution + * of names. + * + * To disable searching: + * 1. Never set it up. If you never call evdns_resolv_conf_parse or + * evdns_search_add then no searching will occur. + * + * 2. If you do call evdns_resolv_conf_parse then don't pass + * DNS_OPTION_SEARCH (or DNS_OPTIONS_ALL, which implies it). + * + * 3. When calling evdns_resolve, pass the DNS_QUERY_NO_SEARCH flag. + * + * The order of searches depends on the number of dots in the name. If the + * number is greater than the ndots setting then the names is first tried + * globally. Otherwise each search domain is appended in turn. + * + * The ndots setting can either be set from a resolv.conf, or by calling + * evdns_search_ndots_set. + * + * For example, with ndots set to 1 (the default) and a search domain list of + * ["myhome.net"]: + * Query: www + * Order: www.myhome.net, www. + * + * Query: www.abc + * Order: www.abc., www.abc.myhome.net + * + * Internals: + * + * Requests are kept in two queues. The first is the inflight queue. In + * this queue requests have an allocated transaction id and nameserver. + * They will soon be transmitted if they haven't already been. + * + * The second is the waiting queue. The size of the inflight ring is + * limited and all other requests wait in waiting queue for space. This + * bounds the number of concurrent requests so that we don't flood the + * nameserver. Several algorithms require a full walk of the inflight + * queue and so bounding its size keeps thing going nicely under huge + * (many thousands of requests) loads. + * + * If a nameserver loses too many requests it is considered down and we + * try not to use it. After a while we send a probe to that nameserver + * (a lookup for google.com) and, if it replies, we consider it working + * again. If the nameserver fails a probe we wait longer to try again + * with the next probe. + */ + +#ifndef EVENTDNS_H +#define EVENTDNS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* For integer types. */ +#include "evutil.h" + +/** Error codes 0-5 are as described in RFC 1035. */ +#define DNS_ERR_NONE 0 +/** The name server was unable to interpret the query */ +#define DNS_ERR_FORMAT 1 +/** The name server was unable to process this query due to a problem with the + * name server */ +#define DNS_ERR_SERVERFAILED 2 +/** The domain name does not exist */ +#define DNS_ERR_NOTEXIST 3 +/** The name server does not support the requested kind of query */ +#define DNS_ERR_NOTIMPL 4 +/** The name server refuses to reform the specified operation for policy + * reasons */ +#define DNS_ERR_REFUSED 5 +/** The reply was truncated or ill-formated */ +#define DNS_ERR_TRUNCATED 65 +/** An unknown error occurred */ +#define DNS_ERR_UNKNOWN 66 +/** Communication with the server timed out */ +#define DNS_ERR_TIMEOUT 67 +/** The request was canceled because the DNS subsystem was shut down. */ +#define DNS_ERR_SHUTDOWN 68 + +#define DNS_IPv4_A 1 +#define DNS_PTR 2 +#define DNS_IPv6_AAAA 3 + +#define DNS_QUERY_NO_SEARCH 1 + +#define DNS_OPTION_SEARCH 1 +#define DNS_OPTION_NAMESERVERS 2 +#define DNS_OPTION_MISC 4 +#define DNS_OPTIONS_ALL 7 + +/** + * The callback that contains the results from a lookup. + * - type is either DNS_IPv4_A or DNS_PTR or DNS_IPv6_AAAA + * - count contains the number of addresses of form type + * - ttl is the number of seconds the resolution may be cached for. + * - addresses needs to be cast according to type + */ +typedef void (*evdns_callback_type) (int result, char type, int count, int ttl, void *addresses, void *arg); + +/** + Initialize the asynchronous DNS library. + + This function initializes support for non-blocking name resolution by + calling evdns_resolv_conf_parse() on UNIX and + evdns_config_windows_nameservers() on Windows. + + @return 0 if successful, or -1 if an error occurred + @see evdns_shutdown() + */ +int evdns_init(void); + + +/** + Shut down the asynchronous DNS resolver and terminate all active requests. + + If the 'fail_requests' option is enabled, all active requests will return + an empty result with the error flag set to DNS_ERR_SHUTDOWN. Otherwise, + the requests will be silently discarded. + + @param fail_requests if zero, active requests will be aborted; if non-zero, + active requests will return DNS_ERR_SHUTDOWN. + @see evdns_init() + */ +void evdns_shutdown(int fail_requests); + + +/** + Convert a DNS error code to a string. + + @param err the DNS error code + @return a string containing an explanation of the error code +*/ +const char *evdns_err_to_string(int err); + + +/** + Add a nameserver. + + The address should be an IPv4 address in network byte order. + The type of address is chosen so that it matches in_addr.s_addr. + + @param address an IP address in network byte order + @return 0 if successful, or -1 if an error occurred + @see evdns_nameserver_ip_add() + */ +int evdns_nameserver_add(unsigned long int address); + + +/** + Get the number of configured nameservers. + + This returns the number of configured nameservers (not necessarily the + number of running nameservers). This is useful for double-checking + whether our calls to the various nameserver configuration functions + have been successful. + + @return the number of configured nameservers + @see evdns_nameserver_add() + */ +int evdns_count_nameservers(void); + + +/** + Remove all configured nameservers, and suspend all pending resolves. + + Resolves will not necessarily be re-attempted until evdns_resume() is called. + + @return 0 if successful, or -1 if an error occurred + @see evdns_resume() + */ +int evdns_clear_nameservers_and_suspend(void); + + +/** + Resume normal operation and continue any suspended resolve requests. + + Re-attempt resolves left in limbo after an earlier call to + evdns_clear_nameservers_and_suspend(). + + @return 0 if successful, or -1 if an error occurred + @see evdns_clear_nameservers_and_suspend() + */ +int evdns_resume(void); + + +/** + Add a nameserver. + + This wraps the evdns_nameserver_add() function by parsing a string as an IP + address and adds it as a nameserver. + + @return 0 if successful, or -1 if an error occurred + @see evdns_nameserver_add() + */ +int evdns_nameserver_ip_add(const char *ip_as_string); + + +/** + Lookup an A record for a given name. + + @param name a DNS hostname + @param flags either 0, or DNS_QUERY_NO_SEARCH to disable searching for this query. + @param callback a callback function to invoke when the request is completed + @param ptr an argument to pass to the callback function + @return 0 if successful, or -1 if an error occurred + @see evdns_resolve_ipv6(), evdns_resolve_reverse(), evdns_resolve_reverse_ipv6() + */ +int evdns_resolve_ipv4(const char *name, int flags, evdns_callback_type callback, void *ptr); + + +/** + Lookup an AAAA record for a given name. + + @param name a DNS hostname + @param flags either 0, or DNS_QUERY_NO_SEARCH to disable searching for this query. + @param callback a callback function to invoke when the request is completed + @param ptr an argument to pass to the callback function + @return 0 if successful, or -1 if an error occurred + @see evdns_resolve_ipv4(), evdns_resolve_reverse(), evdns_resolve_reverse_ipv6() + */ +int evdns_resolve_ipv6(const char *name, int flags, evdns_callback_type callback, void *ptr); + +struct in_addr; +struct in6_addr; + +/** + Lookup a PTR record for a given IP address. + + @param in an IPv4 address + @param flags either 0, or DNS_QUERY_NO_SEARCH to disable searching for this query. + @param callback a callback function to invoke when the request is completed + @param ptr an argument to pass to the callback function + @return 0 if successful, or -1 if an error occurred + @see evdns_resolve_reverse_ipv6() + */ +int evdns_resolve_reverse(const struct in_addr *in, int flags, evdns_callback_type callback, void *ptr); + + +/** + Lookup a PTR record for a given IPv6 address. + + @param in an IPv6 address + @param flags either 0, or DNS_QUERY_NO_SEARCH to disable searching for this query. + @param callback a callback function to invoke when the request is completed + @param ptr an argument to pass to the callback function + @return 0 if successful, or -1 if an error occurred + @see evdns_resolve_reverse_ipv6() + */ +int evdns_resolve_reverse_ipv6(const struct in6_addr *in, int flags, evdns_callback_type callback, void *ptr); + + +/** + Set the value of a configuration option. + + The currently available configuration options are: + + ndots, timeout, max-timeouts, max-inflight, and attempts + + @param option the name of the configuration option to be modified + @param val the value to be set + @param flags either 0 | DNS_OPTION_SEARCH | DNS_OPTION_MISC + @return 0 if successful, or -1 if an error occurred + */ +int evdns_set_option(const char *option, const char *val, int flags); + + +/** + Parse a resolv.conf file. + + The 'flags' parameter determines what information is parsed from the + resolv.conf file. See the man page for resolv.conf for the format of this + file. + + The following directives are not parsed from the file: sortlist, rotate, + no-check-names, inet6, debug. + + If this function encounters an error, the possible return values are: 1 = + failed to open file, 2 = failed to stat file, 3 = file too large, 4 = out of + memory, 5 = short read from file, 6 = no nameservers listed in the file + + @param flags any of DNS_OPTION_NAMESERVERS|DNS_OPTION_SEARCH|DNS_OPTION_MISC| + DNS_OPTIONS_ALL + @param filename the path to the resolv.conf file + @return 0 if successful, or various positive error codes if an error + occurred (see above) + @see resolv.conf(3), evdns_config_windows_nameservers() + */ +int evdns_resolv_conf_parse(int flags, const char *const filename); + + +/** + Obtain nameserver information using the Windows API. + + Attempt to configure a set of nameservers based on platform settings on + a win32 host. Preferentially tries to use GetNetworkParams; if that fails, + looks in the registry. + + @return 0 if successful, or -1 if an error occurred + @see evdns_resolv_conf_parse() + */ +#ifdef WIN32 +int evdns_config_windows_nameservers(void); +#endif + + +/** + Clear the list of search domains. + */ +void evdns_search_clear(void); + + +/** + Add a domain to the list of search domains + + @param domain the domain to be added to the search list + */ +void evdns_search_add(const char *domain); + + +/** + Set the 'ndots' parameter for searches. + + Sets the number of dots which, when found in a name, causes + the first query to be without any search domain. + + @param ndots the new ndots parameter + */ +void evdns_search_ndots_set(const int ndots); + +/** + A callback that is invoked when a log message is generated + + @param is_warning indicates if the log message is a 'warning' + @param msg the content of the log message + */ +typedef void (*evdns_debug_log_fn_type)(int is_warning, const char *msg); + + +/** + Set the callback function to handle log messages. + + @param fn the callback to be invoked when a log message is generated + */ +void evdns_set_log_fn(evdns_debug_log_fn_type fn); + +/** + Set a callback that will be invoked to generate transaction IDs. By + default, we pick transaction IDs based on the current clock time. + + @param fn the new callback, or NULL to use the default. + */ +void evdns_set_transaction_id_fn(ev_uint16_t (*fn)(void)); + +#define DNS_NO_SEARCH 1 + +/* + * Structures and functions used to implement a DNS server. + */ + +struct evdns_server_request { + int flags; + int nquestions; + struct evdns_server_question **questions; +}; +struct evdns_server_question { + int type; +#ifdef __cplusplus + int dns_question_class; +#else + /* You should refer to this field as "dns_question_class". The + * name "class" works in C for backward compatibility, and will be + * removed in a future version. (1.5 or later). */ + int class; +#define dns_question_class class +#endif + char name[1]; +}; +typedef void (*evdns_request_callback_fn_type)(struct evdns_server_request *, void *); +#define EVDNS_ANSWER_SECTION 0 +#define EVDNS_AUTHORITY_SECTION 1 +#define EVDNS_ADDITIONAL_SECTION 2 + +#define EVDNS_TYPE_A 1 +#define EVDNS_TYPE_NS 2 +#define EVDNS_TYPE_CNAME 5 +#define EVDNS_TYPE_SOA 6 +#define EVDNS_TYPE_PTR 12 +#define EVDNS_TYPE_MX 15 +#define EVDNS_TYPE_TXT 16 +#define EVDNS_TYPE_AAAA 28 + +#define EVDNS_QTYPE_AXFR 252 +#define EVDNS_QTYPE_ALL 255 + +#define EVDNS_CLASS_INET 1 + +struct evdns_server_port *evdns_add_server_port(int socket, int is_tcp, evdns_request_callback_fn_type callback, void *user_data); +void evdns_close_server_port(struct evdns_server_port *port); + +int evdns_server_request_add_reply(struct evdns_server_request *req, int section, const char *name, int type, int dns_class, int ttl, int datalen, int is_name, const char *data); +int evdns_server_request_add_a_reply(struct evdns_server_request *req, const char *name, int n, void *addrs, int ttl); +int evdns_server_request_add_aaaa_reply(struct evdns_server_request *req, const char *name, int n, void *addrs, int ttl); +int evdns_server_request_add_ptr_reply(struct evdns_server_request *req, struct in_addr *in, const char *inaddr_name, const char *hostname, int ttl); +int evdns_server_request_add_cname_reply(struct evdns_server_request *req, const char *name, const char *cname, int ttl); + +int evdns_server_request_respond(struct evdns_server_request *req, int err); +int evdns_server_request_drop(struct evdns_server_request *req); +struct sockaddr; +int evdns_server_request_get_requesting_addr(struct evdns_server_request *_req, struct sockaddr *sa, int addr_len); + +#ifdef __cplusplus +} +#endif + +#endif /* !EVENTDNS_H */ diff --git a/third_party/libevent/event-config.h b/third_party/libevent/event-config.h new file mode 100644 index 0000000000..d6569999e3 --- /dev/null +++ b/third_party/libevent/event-config.h @@ -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. + +// This file is Chromium-specific, and brings in the appropriate +// event-config.h depending on your platform. + +#if defined(__APPLE__) +#include "mac/event-config.h" +#elif defined(ANDROID) +#include "android/event-config.h" +#elif defined(__linux__) +#include "linux/event-config.h" +#elif defined(__FreeBSD__) +#include "freebsd/event-config.h" +#elif defined(__sun) +#include "solaris/event-config.h" +#else +#error generate event-config.h for your platform +#endif diff --git a/third_party/libevent/event-internal.h b/third_party/libevent/event-internal.h new file mode 100644 index 0000000000..b7f00402be --- /dev/null +++ b/third_party/libevent/event-internal.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2000-2004 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef _EVENT_INTERNAL_H_ +#define _EVENT_INTERNAL_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "config.h" +#include "min_heap.h" +#include "evsignal.h" + +struct eventop { + const char *name; + void *(*init)(struct event_base *); + int (*add)(void *, struct event *); + int (*del)(void *, struct event *); + int (*dispatch)(struct event_base *, void *, struct timeval *); + void (*dealloc)(struct event_base *, void *); + /* set if we need to reinitialize the event base */ + int need_reinit; +}; + +struct event_base { + const struct eventop *evsel; + void *evbase; + int event_count; /* counts number of total events */ + int event_count_active; /* counts number of active events */ + + int event_gotterm; /* Set to terminate loop */ + int event_break; /* Set to terminate loop immediately */ + + /* active event management */ + struct event_list **activequeues; + int nactivequeues; + + /* signal handling info */ + struct evsignal_info sig; + + struct event_list eventqueue; + struct timeval event_tv; + + struct min_heap timeheap; + + struct timeval tv_cache; +}; + +/* Internal use only: Functions that might be missing from */ +#ifndef HAVE_TAILQFOREACH +#define TAILQ_FIRST(head) ((head)->tqh_first) +#define TAILQ_END(head) NULL +#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) +#define TAILQ_FOREACH(var, head, field) \ + for((var) = TAILQ_FIRST(head); \ + (var) != TAILQ_END(head); \ + (var) = TAILQ_NEXT(var, field)) +#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ + (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ + (elm)->field.tqe_next = (listelm); \ + *(listelm)->field.tqe_prev = (elm); \ + (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ +} while (0) +#endif /* TAILQ_FOREACH */ + +int _evsignal_set_handler(struct event_base *base, int evsignal, + void (*fn)(int)); +int _evsignal_restore_handler(struct event_base *base, int evsignal); + +/* defined in evutil.c */ +const char *evutil_getenv(const char *varname); + +#ifdef __cplusplus +} +#endif + +#endif /* _EVENT_INTERNAL_H_ */ diff --git a/third_party/libevent/event.3 b/third_party/libevent/event.3 new file mode 100644 index 0000000000..5b33ec64a9 --- /dev/null +++ b/third_party/libevent/event.3 @@ -0,0 +1,624 @@ +.\" $OpenBSD: event.3,v 1.4 2002/07/12 18:50:48 provos Exp $ +.\" +.\" Copyright (c) 2000 Artur Grabowski +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. 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. +.\" 3. The name of the author may not be used to endorse or promote products +.\" derived from this software without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, +.\" INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +.\" AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +.\" THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +.\" EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +.\" PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +.\" OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +.\" WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +.\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +.\" ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.Dd August 8, 2000 +.Dt EVENT 3 +.Os +.Sh NAME +.Nm event_init , +.Nm event_dispatch , +.Nm event_loop , +.Nm event_loopexit , +.Nm event_loopbreak , +.Nm event_set , +.Nm event_base_dispatch , +.Nm event_base_loop , +.Nm event_base_loopexit , +.Nm event_base_loopbreak , +.Nm event_base_set , +.Nm event_base_free , +.Nm event_add , +.Nm event_del , +.Nm event_once , +.Nm event_base_once , +.Nm event_pending , +.Nm event_initialized , +.Nm event_priority_init , +.Nm event_priority_set , +.Nm evtimer_set , +.Nm evtimer_add , +.Nm evtimer_del , +.Nm evtimer_pending , +.Nm evtimer_initialized , +.Nm signal_set , +.Nm signal_add , +.Nm signal_del , +.Nm signal_pending , +.Nm signal_initialized , +.Nm bufferevent_new , +.Nm bufferevent_free , +.Nm bufferevent_write , +.Nm bufferevent_write_buffer , +.Nm bufferevent_read , +.Nm bufferevent_enable , +.Nm bufferevent_disable , +.Nm bufferevent_settimeout , +.Nm bufferevent_base_set , +.Nm evbuffer_new , +.Nm evbuffer_free , +.Nm evbuffer_add , +.Nm evbuffer_add_buffer , +.Nm evbuffer_add_printf , +.Nm evbuffer_add_vprintf , +.Nm evbuffer_drain , +.Nm evbuffer_write , +.Nm evbuffer_read , +.Nm evbuffer_find , +.Nm evbuffer_readline , +.Nm evhttp_new , +.Nm evhttp_bind_socket , +.Nm evhttp_free +.Nd execute a function when a specific event occurs +.Sh SYNOPSIS +.Fd #include +.Fd #include +.Ft "struct event_base *" +.Fn "event_init" "void" +.Ft int +.Fn "event_dispatch" "void" +.Ft int +.Fn "event_loop" "int flags" +.Ft int +.Fn "event_loopexit" "struct timeval *tv" +.Ft int +.Fn "event_loopbreak" "void" +.Ft void +.Fn "event_set" "struct event *ev" "int fd" "short event" "void (*fn)(int, short, void *)" "void *arg" +.Ft int +.Fn "event_base_dispatch" "struct event_base *base" +.Ft int +.Fn "event_base_loop" "struct event_base *base" "int flags" +.Ft int +.Fn "event_base_loopexit" "struct event_base *base" "struct timeval *tv" +.Ft int +.Fn "event_base_loopbreak" "struct event_base *base" +.Ft int +.Fn "event_base_set" "struct event_base *base" "struct event *" +.Ft void +.Fn "event_base_free" "struct event_base *base" +.Ft int +.Fn "event_add" "struct event *ev" "struct timeval *tv" +.Ft int +.Fn "event_del" "struct event *ev" +.Ft int +.Fn "event_once" "int fd" "short event" "void (*fn)(int, short, void *)" "void *arg" "struct timeval *tv" +.Ft int +.Fn "event_base_once" "struct event_base *base" "int fd" "short event" "void (*fn)(int, short, void *)" "void *arg" "struct timeval *tv" +.Ft int +.Fn "event_pending" "struct event *ev" "short event" "struct timeval *tv" +.Ft int +.Fn "event_initialized" "struct event *ev" +.Ft int +.Fn "event_priority_init" "int npriorities" +.Ft int +.Fn "event_priority_set" "struct event *ev" "int priority" +.Ft void +.Fn "evtimer_set" "struct event *ev" "void (*fn)(int, short, void *)" "void *arg" +.Ft void +.Fn "evtimer_add" "struct event *ev" "struct timeval *" +.Ft void +.Fn "evtimer_del" "struct event *ev" +.Ft int +.Fn "evtimer_pending" "struct event *ev" "struct timeval *tv" +.Ft int +.Fn "evtimer_initialized" "struct event *ev" +.Ft void +.Fn "signal_set" "struct event *ev" "int signal" "void (*fn)(int, short, void *)" "void *arg" +.Ft void +.Fn "signal_add" "struct event *ev" "struct timeval *" +.Ft void +.Fn "signal_del" "struct event *ev" +.Ft int +.Fn "signal_pending" "struct event *ev" "struct timeval *tv" +.Ft int +.Fn "signal_initialized" "struct event *ev" +.Ft "struct bufferevent *" +.Fn "bufferevent_new" "int fd" "evbuffercb readcb" "evbuffercb writecb" "everrorcb" "void *cbarg" +.Ft void +.Fn "bufferevent_free" "struct bufferevent *bufev" +.Ft int +.Fn "bufferevent_write" "struct bufferevent *bufev" "void *data" "size_t size" +.Ft int +.Fn "bufferevent_write_buffer" "struct bufferevent *bufev" "struct evbuffer *buf" +.Ft size_t +.Fn "bufferevent_read" "struct bufferevent *bufev" "void *data" "size_t size" +.Ft int +.Fn "bufferevent_enable" "struct bufferevent *bufev" "short event" +.Ft int +.Fn "bufferevent_disable" "struct bufferevent *bufev" "short event" +.Ft void +.Fn "bufferevent_settimeout" "struct bufferevent *bufev" "int timeout_read" "int timeout_write" +.Ft int +.Fn "bufferevent_base_set" "struct event_base *base" "struct bufferevent *bufev" +.Ft "struct evbuffer *" +.Fn "evbuffer_new" "void" +.Ft void +.Fn "evbuffer_free" "struct evbuffer *buf" +.Ft int +.Fn "evbuffer_add" "struct evbuffer *buf" "const void *data" "size_t size" +.Ft int +.Fn "evbuffer_add_buffer" "struct evbuffer *dst" "struct evbuffer *src" +.Ft int +.Fn "evbuffer_add_printf" "struct evbuffer *buf" "const char *fmt" "..." +.Ft int +.Fn "evbuffer_add_vprintf" "struct evbuffer *buf" "const char *fmt" "va_list ap" +.Ft void +.Fn "evbuffer_drain" "struct evbuffer *buf" "size_t size" +.Ft int +.Fn "evbuffer_write" "struct evbuffer *buf" "int fd" +.Ft int +.Fn "evbuffer_read" "struct evbuffer *buf" "int fd" "int size" +.Ft "u_char *" +.Fn "evbuffer_find" "struct evbuffer *buf" "const u_char *data" "size_t size" +.Ft "char *" +.Fn "evbuffer_readline" "struct evbuffer *buf" +.Ft "struct evhttp *" +.Fn "evhttp_new" "struct event_base *base" +.Ft int +.Fn "evhttp_bind_socket" "struct evhttp *http" "const char *address" "u_short port" +.Ft "void" +.Fn "evhttp_free" "struct evhttp *http" +.Ft int +.Fa (*event_sigcb)(void) ; +.Ft volatile sig_atomic_t +.Fa event_gotsig ; +.Sh DESCRIPTION +The +.Nm event +API provides a mechanism to execute a function when a specific event +on a file descriptor occurs or after a given time has passed. +.Pp +The +.Nm event +API needs to be initialized with +.Fn event_init +before it can be used. +.Pp +In order to process events, an application needs to call +.Fn event_dispatch . +This function only returns on error, and should replace the event core +of the application program. +.Pp +The function +.Fn event_set +prepares the event structure +.Fa ev +to be used in future calls to +.Fn event_add +and +.Fn event_del . +The event will be prepared to call the function specified by the +.Fa fn +argument with an +.Fa int +argument indicating the file descriptor, a +.Fa short +argument indicating the type of event, and a +.Fa void * +argument given in the +.Fa arg +argument. +The +.Fa fd +indicates the file descriptor that should be monitored for events. +The events can be either +.Va EV_READ , +.Va EV_WRITE , +or both, +indicating that an application can read or write from the file descriptor +respectively without blocking. +.Pp +The function +.Fa fn +will be called with the file descriptor that triggered the event and +the type of event which will be either +.Va EV_TIMEOUT , +.Va EV_SIGNAL , +.Va EV_READ , +or +.Va EV_WRITE . +Additionally, an event which has registered interest in more than one of the +preceeding events, via bitwise-OR to +.Fn event_set , +can provide its callback function with a bitwise-OR of more than one triggered +event. +The additional flag +.Va EV_PERSIST +makes an +.Fn event_add +persistent until +.Fn event_del +has been called. +.Pp +Once initialized, the +.Fa ev +structure can be used repeatedly with +.Fn event_add +and +.Fn event_del +and does not need to be reinitialized unless the function called and/or +the argument to it are to be changed. +However, when an +.Fa ev +structure has been added to libevent using +.Fn event_add +the structure must persist until the event occurs (assuming +.Fa EV_PERSIST +is not set) or is removed +using +.Fn event_del . +You may not reuse the same +.Fa ev +structure for multiple monitored descriptors; each descriptor +needs its own +.Fa ev . +.Pp +The function +.Fn event_add +schedules the execution of the +.Fa ev +event when the event specified in +.Fn event_set +occurs or in at least the time specified in the +.Fa tv . +If +.Fa tv +is +.Dv NULL , +no timeout occurs and the function will only be called +if a matching event occurs on the file descriptor. +The event in the +.Fa ev +argument must be already initialized by +.Fn event_set +and may not be used in calls to +.Fn event_set +until it has timed out or been removed with +.Fn event_del . +If the event in the +.Fa ev +argument already has a scheduled timeout, the old timeout will be +replaced by the new one. +.Pp +The function +.Fn event_del +will cancel the event in the argument +.Fa ev . +If the event has already executed or has never been added +the call will have no effect. +.Pp +The functions +.Fn evtimer_set , +.Fn evtimer_add , +.Fn evtimer_del , +.Fn evtimer_initialized , +and +.Fn evtimer_pending +are abbreviations for common situations where only a timeout is required. +The file descriptor passed will be \-1, and the event type will be +.Va EV_TIMEOUT . +.Pp +The functions +.Fn signal_set , +.Fn signal_add , +.Fn signal_del , +.Fn signal_initialized , +and +.Fn signal_pending +are abbreviations. +The event type will be a persistent +.Va EV_SIGNAL . +That means +.Fn signal_set +adds +.Va EV_PERSIST . +.Pp +In order to avoid races in signal handlers, the +.Nm event +API provides two variables: +.Va event_sigcb +and +.Va event_gotsig . +A signal handler +sets +.Va event_gotsig +to indicate that a signal has been received. +The application sets +.Va event_sigcb +to a callback function. +After the signal handler sets +.Va event_gotsig , +.Nm event_dispatch +will execute the callback function to process received signals. +The callback returns 1 when no events are registered any more. +It can return \-1 to indicate an error to the +.Nm event +library, causing +.Fn event_dispatch +to terminate with +.Va errno +set to +.Er EINTR . +.Pp +The function +.Fn event_once +is similar to +.Fn event_set . +However, it schedules a callback to be called exactly once and does not +require the caller to prepare an +.Fa event +structure. +This function supports +.Fa EV_TIMEOUT , +.Fa EV_READ , +and +.Fa EV_WRITE . +.Pp +The +.Fn event_pending +function can be used to check if the event specified by +.Fa event +is pending to run. +If +.Va EV_TIMEOUT +was specified and +.Fa tv +is not +.Dv NULL , +the expiration time of the event will be returned in +.Fa tv . +.Pp +The +.Fn event_initialized +macro can be used to check if an event has been initialized. +.Pp +The +.Nm event_loop +function provides an interface for single pass execution of pending +events. +The flags +.Va EVLOOP_ONCE +and +.Va EVLOOP_NONBLOCK +are recognized. +The +.Nm event_loopexit +function exits from the event loop. The next +.Fn event_loop +iteration after the +given timer expires will complete normally (handling all queued events) then +exit without blocking for events again. Subsequent invocations of +.Fn event_loop +will proceed normally. +The +.Nm event_loopbreak +function exits from the event loop immediately. +.Fn event_loop +will abort after the next event is completed; +.Fn event_loopbreak +is typically invoked from this event's callback. This behavior is analogous +to the "break;" statement. Subsequent invocations of +.Fn event_loop +will proceed normally. +.Pp +It is the responsibility of the caller to provide these functions with +pre-allocated event structures. +.Pp +.Sh EVENT PRIORITIES +By default +.Nm libevent +schedules all active events with the same priority. +However, sometimes it is desirable to process some events with a higher +priority than others. +For that reason, +.Nm libevent +supports strict priority queues. +Active events with a lower priority are always processed before events +with a higher priority. +.Pp +The number of different priorities can be set initially with the +.Fn event_priority_init +function. +This function should be called before the first call to +.Fn event_dispatch . +The +.Fn event_priority_set +function can be used to assign a priority to an event. +By default, +.Nm libevent +assigns the middle priority to all events unless their priority +is explicitly set. +.Sh THREAD SAFE EVENTS +.Nm Libevent +has experimental support for thread-safe events. +When initializing the library via +.Fn event_init , +an event base is returned. +This event base can be used in conjunction with calls to +.Fn event_base_set , +.Fn event_base_dispatch , +.Fn event_base_loop , +.Fn event_base_loopexit , +.Fn bufferevent_base_set +and +.Fn event_base_free . +.Fn event_base_set +should be called after preparing an event with +.Fn event_set , +as +.Fn event_set +assigns the provided event to the most recently created event base. +.Fn bufferevent_base_set +should be called after preparing a bufferevent with +.Fn bufferevent_new . +.Fn event_base_free +should be used to free memory associated with the event base +when it is no longer needed. +.Sh BUFFERED EVENTS +.Nm libevent +provides an abstraction on top of the regular event callbacks. +This abstraction is called a +.Va "buffered event" . +A buffered event provides input and output buffers that get filled +and drained automatically. +The user of a buffered event no longer deals directly with the IO, +but instead is reading from input and writing to output buffers. +.Pp +A new bufferevent is created by +.Fn bufferevent_new . +The parameter +.Fa fd +specifies the file descriptor from which data is read and written to. +This file descriptor is not allowed to be a +.Xr pipe 2 . +The next three parameters are callbacks. +The read and write callback have the following form: +.Ft void +.Fn "(*cb)" "struct bufferevent *bufev" "void *arg" . +The error callback has the following form: +.Ft void +.Fn "(*cb)" "struct bufferevent *bufev" "short what" "void *arg" . +The argument is specified by the fourth parameter +.Fa "cbarg" . +A +.Fa bufferevent struct +pointer is returned on success, NULL on error. +Both the read and the write callback may be NULL. +The error callback has to be always provided. +.Pp +Once initialized, the bufferevent structure can be used repeatedly with +bufferevent_enable() and bufferevent_disable(). +The flags parameter can be a combination of +.Va EV_READ +and +.Va EV_WRITE . +When read enabled the bufferevent will try to read from the file +descriptor and call the read callback. +The write callback is executed +whenever the output buffer is drained below the write low watermark, +which is +.Va 0 +by default. +.Pp +The +.Fn bufferevent_write +function can be used to write data to the file descriptor. +The data is appended to the output buffer and written to the descriptor +automatically as it becomes available for writing. +.Fn bufferevent_write +returns 0 on success or \-1 on failure. +The +.Fn bufferevent_read +function is used to read data from the input buffer, +returning the amount of data read. +.Pp +If multiple bases are in use, bufferevent_base_set() must be called before +enabling the bufferevent for the first time. +.Sh NON-BLOCKING HTTP SUPPORT +.Nm libevent +provides a very thin HTTP layer that can be used both to host an HTTP +server and also to make HTTP requests. +An HTTP server can be created by calling +.Fn evhttp_new . +It can be bound to any port and address with the +.Fn evhttp_bind_socket +function. +When the HTTP server is no longer used, it can be freed via +.Fn evhttp_free . +.Pp +To be notified of HTTP requests, a user needs to register callbacks with the +HTTP server. +This can be done by calling +.Fn evhttp_set_cb . +The second argument is the URI for which a callback is being registered. +The corresponding callback will receive an +.Va struct evhttp_request +object that contains all information about the request. +.Pp +This section does not document all the possible function calls; please +check +.Va event.h +for the public interfaces. +.Sh ADDITIONAL NOTES +It is possible to disable support for +.Va epoll , kqueue , devpoll , poll +or +.Va select +by setting the environment variable +.Va EVENT_NOEPOLL , EVENT_NOKQUEUE , EVENT_NODEVPOLL , EVENT_NOPOLL +or +.Va EVENT_NOSELECT , +respectively. +By setting the environment variable +.Va EVENT_SHOW_METHOD , +.Nm libevent +displays the kernel notification method that it uses. +.Sh RETURN VALUES +Upon successful completion +.Fn event_add +and +.Fn event_del +return 0. +Otherwise, \-1 is returned and the global variable errno is +set to indicate the error. +.Sh SEE ALSO +.Xr kqueue 2 , +.Xr poll 2 , +.Xr select 2 , +.Xr evdns 3 , +.Xr timeout 9 +.Sh HISTORY +The +.Nm event +API manpage is based on the +.Xr timeout 9 +manpage by Artur Grabowski. +The port of +.Nm libevent +to Windows is due to Michael A. Davis. +Support for real-time signals is due to Taral. +.Sh AUTHORS +The +.Nm event +library was written by Niels Provos. +.Sh BUGS +This documentation is neither complete nor authoritative. +If you are in doubt about the usage of this API then +check the source code to find out how it works, write +up the missing piece of documentation and send it to +me for inclusion in this man page. diff --git a/third_party/libevent/event.c b/third_party/libevent/event.c new file mode 100644 index 0000000000..125335207d --- /dev/null +++ b/third_party/libevent/event.c @@ -0,0 +1,1002 @@ +/* + * Copyright (c) 2000-2004 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#undef WIN32_LEAN_AND_MEAN +#endif +#include +#ifdef HAVE_SYS_TIME_H +#include +#else +#include +#endif +#include +#include +#include +#ifndef WIN32 +#include +#endif +#include +#include +#include +#include +#include + +#include "event.h" +#include "event-internal.h" +#include "evutil.h" +#include "log.h" + +#ifdef HAVE_EVENT_PORTS +extern const struct eventop evportops; +#endif +#ifdef HAVE_SELECT +extern const struct eventop selectops; +#endif +#ifdef HAVE_POLL +extern const struct eventop pollops; +#endif +#ifdef HAVE_EPOLL +extern const struct eventop epollops; +#endif +#ifdef HAVE_WORKING_KQUEUE +extern const struct eventop kqops; +#endif +#ifdef HAVE_DEVPOLL +extern const struct eventop devpollops; +#endif +#ifdef WIN32 +extern const struct eventop win32ops; +#endif + +/* In order of preference */ +static const struct eventop *eventops[] = { +#ifdef HAVE_EVENT_PORTS + &evportops, +#endif +#ifdef HAVE_WORKING_KQUEUE + &kqops, +#endif +#ifdef HAVE_EPOLL + &epollops, +#endif +#ifdef HAVE_DEVPOLL + &devpollops, +#endif +#ifdef HAVE_POLL + &pollops, +#endif +#ifdef HAVE_SELECT + &selectops, +#endif +#ifdef WIN32 + &win32ops, +#endif + NULL +}; + +/* Global state */ +struct event_base *current_base = NULL; +extern struct event_base *evsignal_base; +static int use_monotonic; + +/* Prototypes */ +static void event_queue_insert(struct event_base *, struct event *, int); +static void event_queue_remove(struct event_base *, struct event *, int); +static int event_haveevents(struct event_base *); + +static void event_process_active(struct event_base *); + +static int timeout_next(struct event_base *, struct timeval **); +static void timeout_process(struct event_base *); +static void timeout_correct(struct event_base *, struct timeval *); + +static void +detect_monotonic(void) +{ +#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC) + struct timespec ts; + + if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) + use_monotonic = 1; +#endif +} + +static int +gettime(struct event_base *base, struct timeval *tp) +{ + if (base->tv_cache.tv_sec) { + *tp = base->tv_cache; + return (0); + } + +#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC) + if (use_monotonic) { + struct timespec ts; + + if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1) + return (-1); + + tp->tv_sec = ts.tv_sec; + tp->tv_usec = ts.tv_nsec / 1000; + return (0); + } +#endif + + return (evutil_gettimeofday(tp, NULL)); +} + +struct event_base * +event_init(void) +{ + struct event_base *base = event_base_new(); + + if (base != NULL) + current_base = base; + + return (base); +} + +struct event_base * +event_base_new(void) +{ + int i; + struct event_base *base; + + if ((base = calloc(1, sizeof(struct event_base))) == NULL) + event_err(1, "%s: calloc", __func__); + + detect_monotonic(); + gettime(base, &base->event_tv); + + min_heap_ctor(&base->timeheap); + TAILQ_INIT(&base->eventqueue); + base->sig.ev_signal_pair[0] = -1; + base->sig.ev_signal_pair[1] = -1; + + base->evbase = NULL; + for (i = 0; eventops[i] && !base->evbase; i++) { + base->evsel = eventops[i]; + + base->evbase = base->evsel->init(base); + } + + if (base->evbase == NULL) + event_errx(1, "%s: no event mechanism available", __func__); + + if (evutil_getenv("EVENT_SHOW_METHOD")) + event_msgx("libevent using: %s\n", + base->evsel->name); + + /* allocate a single active event queue */ + event_base_priority_init(base, 1); + + return (base); +} + +void +event_base_free(struct event_base *base) +{ + int i, n_deleted=0; + struct event *ev; + + if (base == NULL && current_base) + base = current_base; + if (base == current_base) + current_base = NULL; + + /* XXX(niels) - check for internal events first */ + assert(base); + /* Delete all non-internal events. */ + for (ev = TAILQ_FIRST(&base->eventqueue); ev; ) { + struct event *next = TAILQ_NEXT(ev, ev_next); + if (!(ev->ev_flags & EVLIST_INTERNAL)) { + event_del(ev); + ++n_deleted; + } + ev = next; + } + while ((ev = min_heap_top(&base->timeheap)) != NULL) { + event_del(ev); + ++n_deleted; + } + + for (i = 0; i < base->nactivequeues; ++i) { + for (ev = TAILQ_FIRST(base->activequeues[i]); ev; ) { + struct event *next = TAILQ_NEXT(ev, ev_active_next); + if (!(ev->ev_flags & EVLIST_INTERNAL)) { + event_del(ev); + ++n_deleted; + } + ev = next; + } + } + + if (n_deleted) + event_debug(("%s: %d events were still set in base", + __func__, n_deleted)); + + if (base->evsel->dealloc != NULL) + base->evsel->dealloc(base, base->evbase); + + for (i = 0; i < base->nactivequeues; ++i) + assert(TAILQ_EMPTY(base->activequeues[i])); + + assert(min_heap_empty(&base->timeheap)); + min_heap_dtor(&base->timeheap); + + for (i = 0; i < base->nactivequeues; ++i) + free(base->activequeues[i]); + free(base->activequeues); + + assert(TAILQ_EMPTY(&base->eventqueue)); + + free(base); +} + +/* reinitialized the event base after a fork */ +int +event_reinit(struct event_base *base) +{ + const struct eventop *evsel = base->evsel; + void *evbase = base->evbase; + int res = 0; + struct event *ev; + + /* check if this event mechanism requires reinit */ + if (!evsel->need_reinit) + return (0); + + /* prevent internal delete */ + if (base->sig.ev_signal_added) { + /* we cannot call event_del here because the base has + * not been reinitialized yet. */ + event_queue_remove(base, &base->sig.ev_signal, + EVLIST_INSERTED); + if (base->sig.ev_signal.ev_flags & EVLIST_ACTIVE) + event_queue_remove(base, &base->sig.ev_signal, + EVLIST_ACTIVE); + base->sig.ev_signal_added = 0; + } + + if (base->evsel->dealloc != NULL) + base->evsel->dealloc(base, base->evbase); + evbase = base->evbase = evsel->init(base); + if (base->evbase == NULL) + event_errx(1, "%s: could not reinitialize event mechanism", + __func__); + + TAILQ_FOREACH(ev, &base->eventqueue, ev_next) { + if (evsel->add(evbase, ev) == -1) + res = -1; + } + + return (res); +} + +int +event_priority_init(int npriorities) +{ + return event_base_priority_init(current_base, npriorities); +} + +int +event_base_priority_init(struct event_base *base, int npriorities) +{ + int i; + + if (base->event_count_active) + return (-1); + + if (base->nactivequeues && npriorities != base->nactivequeues) { + for (i = 0; i < base->nactivequeues; ++i) { + free(base->activequeues[i]); + } + free(base->activequeues); + } + + /* Allocate our priority queues */ + base->nactivequeues = npriorities; + base->activequeues = (struct event_list **) + calloc(base->nactivequeues, sizeof(struct event_list *)); + if (base->activequeues == NULL) + event_err(1, "%s: calloc", __func__); + + for (i = 0; i < base->nactivequeues; ++i) { + base->activequeues[i] = malloc(sizeof(struct event_list)); + if (base->activequeues[i] == NULL) + event_err(1, "%s: malloc", __func__); + TAILQ_INIT(base->activequeues[i]); + } + + return (0); +} + +int +event_haveevents(struct event_base *base) +{ + return (base->event_count > 0); +} + +/* + * Active events are stored in priority queues. Lower priorities are always + * process before higher priorities. Low priority events can starve high + * priority ones. + */ + +static void +event_process_active(struct event_base *base) +{ + struct event *ev; + struct event_list *activeq = NULL; + int i; + short ncalls; + + for (i = 0; i < base->nactivequeues; ++i) { + if (TAILQ_FIRST(base->activequeues[i]) != NULL) { + activeq = base->activequeues[i]; + break; + } + } + + assert(activeq != NULL); + + for (ev = TAILQ_FIRST(activeq); ev; ev = TAILQ_FIRST(activeq)) { + if (ev->ev_events & EV_PERSIST) + event_queue_remove(base, ev, EVLIST_ACTIVE); + else + event_del(ev); + + /* Allows deletes to work */ + ncalls = ev->ev_ncalls; + ev->ev_pncalls = &ncalls; + while (ncalls) { + ncalls--; + ev->ev_ncalls = ncalls; + (*ev->ev_callback)((int)ev->ev_fd, ev->ev_res, ev->ev_arg); + if (base->event_break) + return; + } + } +} + +/* + * Wait continously for events. We exit only if no events are left. + */ + +int +event_dispatch(void) +{ + return (event_loop(0)); +} + +int +event_base_dispatch(struct event_base *event_base) +{ + return (event_base_loop(event_base, 0)); +} + +const char * +event_base_get_method(struct event_base *base) +{ + assert(base); + return (base->evsel->name); +} + +static void +event_loopexit_cb(int fd, short what, void *arg) +{ + struct event_base *base = arg; + base->event_gotterm = 1; +} + +/* not thread safe */ +int +event_loopexit(const struct timeval *tv) +{ + return (event_once(-1, EV_TIMEOUT, event_loopexit_cb, + current_base, tv)); +} + +int +event_base_loopexit(struct event_base *event_base, const struct timeval *tv) +{ + return (event_base_once(event_base, -1, EV_TIMEOUT, event_loopexit_cb, + event_base, tv)); +} + +/* not thread safe */ +int +event_loopbreak(void) +{ + return (event_base_loopbreak(current_base)); +} + +int +event_base_loopbreak(struct event_base *event_base) +{ + if (event_base == NULL) + return (-1); + + event_base->event_break = 1; + return (0); +} + + + +/* not thread safe */ + +int +event_loop(int flags) +{ + return event_base_loop(current_base, flags); +} + +int +event_base_loop(struct event_base *base, int flags) +{ + const struct eventop *evsel = base->evsel; + void *evbase = base->evbase; + struct timeval tv; + struct timeval *tv_p; + int res, done; + + /* clear time cache */ + base->tv_cache.tv_sec = 0; + + if (base->sig.ev_signal_added) + evsignal_base = base; + done = 0; + while (!done) { + /* Terminate the loop if we have been asked to */ + if (base->event_gotterm) { + base->event_gotterm = 0; + break; + } + + if (base->event_break) { + base->event_break = 0; + break; + } + + timeout_correct(base, &tv); + + tv_p = &tv; + if (!base->event_count_active && !(flags & EVLOOP_NONBLOCK)) { + timeout_next(base, &tv_p); + } else { + /* + * if we have active events, we just poll new events + * without waiting. + */ + evutil_timerclear(&tv); + } + + /* If we have no events, we just exit */ + if (!event_haveevents(base)) { + event_debug(("%s: no events registered.", __func__)); + return (1); + } + + /* update last old time */ + gettime(base, &base->event_tv); + + /* clear time cache */ + base->tv_cache.tv_sec = 0; + + res = evsel->dispatch(base, evbase, tv_p); + + if (res == -1) + return (-1); + gettime(base, &base->tv_cache); + + timeout_process(base); + + if (base->event_count_active) { + event_process_active(base); + if (!base->event_count_active && (flags & EVLOOP_ONCE)) + done = 1; + } else if (flags & EVLOOP_NONBLOCK) + done = 1; + } + + /* clear time cache */ + base->tv_cache.tv_sec = 0; + + event_debug(("%s: asked to terminate loop.", __func__)); + return (0); +} + +/* Sets up an event for processing once */ + +struct event_once { + struct event ev; + + void (*cb)(int, short, void *); + void *arg; +}; + +/* One-time callback, it deletes itself */ + +static void +event_once_cb(int fd, short events, void *arg) +{ + struct event_once *eonce = arg; + + (*eonce->cb)(fd, events, eonce->arg); + free(eonce); +} + +/* not threadsafe, event scheduled once. */ +int +event_once(int fd, short events, + void (*callback)(int, short, void *), void *arg, const struct timeval *tv) +{ + return event_base_once(current_base, fd, events, callback, arg, tv); +} + +/* Schedules an event once */ +int +event_base_once(struct event_base *base, int fd, short events, + void (*callback)(int, short, void *), void *arg, const struct timeval *tv) +{ + struct event_once *eonce; + struct timeval etv; + int res; + + /* We cannot support signals that just fire once */ + if (events & EV_SIGNAL) + return (-1); + + if ((eonce = calloc(1, sizeof(struct event_once))) == NULL) + return (-1); + + eonce->cb = callback; + eonce->arg = arg; + + if (events == EV_TIMEOUT) { + if (tv == NULL) { + evutil_timerclear(&etv); + tv = &etv; + } + + evtimer_set(&eonce->ev, event_once_cb, eonce); + } else if (events & (EV_READ|EV_WRITE)) { + events &= EV_READ|EV_WRITE; + + event_set(&eonce->ev, fd, events, event_once_cb, eonce); + } else { + /* Bad event combination */ + free(eonce); + return (-1); + } + + res = event_base_set(base, &eonce->ev); + if (res == 0) + res = event_add(&eonce->ev, tv); + if (res != 0) { + free(eonce); + return (res); + } + + return (0); +} + +void +event_set(struct event *ev, int fd, short events, + void (*callback)(int, short, void *), void *arg) +{ + /* Take the current base - caller needs to set the real base later */ + ev->ev_base = current_base; + + ev->ev_callback = callback; + ev->ev_arg = arg; + ev->ev_fd = fd; + ev->ev_events = events; + ev->ev_res = 0; + ev->ev_flags = EVLIST_INIT; + ev->ev_ncalls = 0; + ev->ev_pncalls = NULL; + + min_heap_elem_init(ev); + + /* by default, we put new events into the middle priority */ + if(current_base) + ev->ev_pri = current_base->nactivequeues/2; +} + +int +event_base_set(struct event_base *base, struct event *ev) +{ + /* Only innocent events may be assigned to a different base */ + if (ev->ev_flags != EVLIST_INIT) + return (-1); + + ev->ev_base = base; + ev->ev_pri = base->nactivequeues/2; + + return (0); +} + +/* + * Set's the priority of an event - if an event is already scheduled + * changing the priority is going to fail. + */ + +int +event_priority_set(struct event *ev, int pri) +{ + if (ev->ev_flags & EVLIST_ACTIVE) + return (-1); + if (pri < 0 || pri >= ev->ev_base->nactivequeues) + return (-1); + + ev->ev_pri = pri; + + return (0); +} + +/* + * Checks if a specific event is pending or scheduled. + */ + +int +event_pending(struct event *ev, short event, struct timeval *tv) +{ + struct timeval now, res; + int flags = 0; + + if (ev->ev_flags & EVLIST_INSERTED) + flags |= (ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)); + if (ev->ev_flags & EVLIST_ACTIVE) + flags |= ev->ev_res; + if (ev->ev_flags & EVLIST_TIMEOUT) + flags |= EV_TIMEOUT; + + event &= (EV_TIMEOUT|EV_READ|EV_WRITE|EV_SIGNAL); + + /* See if there is a timeout that we should report */ + if (tv != NULL && (flags & event & EV_TIMEOUT)) { + gettime(ev->ev_base, &now); + evutil_timersub(&ev->ev_timeout, &now, &res); + /* correctly remap to real time */ + evutil_gettimeofday(&now, NULL); + evutil_timeradd(&now, &res, tv); + } + + return (flags & event); +} + +int +event_add(struct event *ev, const struct timeval *tv) +{ + struct event_base *base = ev->ev_base; + const struct eventop *evsel = base->evsel; + void *evbase = base->evbase; + int res = 0; + + event_debug(( + "event_add: event: %p, %s%s%scall %p", + ev, + ev->ev_events & EV_READ ? "EV_READ " : " ", + ev->ev_events & EV_WRITE ? "EV_WRITE " : " ", + tv ? "EV_TIMEOUT " : " ", + ev->ev_callback)); + + assert(!(ev->ev_flags & ~EVLIST_ALL)); + + /* + * prepare for timeout insertion further below, if we get a + * failure on any step, we should not change any state. + */ + if (tv != NULL && !(ev->ev_flags & EVLIST_TIMEOUT)) { + if (min_heap_reserve(&base->timeheap, + 1 + min_heap_size(&base->timeheap)) == -1) + return (-1); /* ENOMEM == errno */ + } + + if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) && + !(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) { + res = evsel->add(evbase, ev); + if (res != -1) + event_queue_insert(base, ev, EVLIST_INSERTED); + } + + /* + * we should change the timout state only if the previous event + * addition succeeded. + */ + if (res != -1 && tv != NULL) { + struct timeval now; + + /* + * we already reserved memory above for the case where we + * are not replacing an exisiting timeout. + */ + if (ev->ev_flags & EVLIST_TIMEOUT) + event_queue_remove(base, ev, EVLIST_TIMEOUT); + + /* Check if it is active due to a timeout. Rescheduling + * this timeout before the callback can be executed + * removes it from the active list. */ + if ((ev->ev_flags & EVLIST_ACTIVE) && + (ev->ev_res & EV_TIMEOUT)) { + /* See if we are just active executing this + * event in a loop + */ + if (ev->ev_ncalls && ev->ev_pncalls) { + /* Abort loop */ + *ev->ev_pncalls = 0; + } + + event_queue_remove(base, ev, EVLIST_ACTIVE); + } + + gettime(base, &now); + evutil_timeradd(&now, tv, &ev->ev_timeout); + + event_debug(( + "event_add: timeout in %ld seconds, call %p", + tv->tv_sec, ev->ev_callback)); + + event_queue_insert(base, ev, EVLIST_TIMEOUT); + } + + return (res); +} + +int +event_del(struct event *ev) +{ + struct event_base *base; + + event_debug(("event_del: %p, callback %p", + ev, ev->ev_callback)); + + /* An event without a base has not been added */ + if (ev->ev_base == NULL) + return (-1); + + base = ev->ev_base; + + assert(!(ev->ev_flags & ~EVLIST_ALL)); + + /* See if we are just active executing this event in a loop */ + if (ev->ev_ncalls && ev->ev_pncalls) { + /* Abort loop */ + *ev->ev_pncalls = 0; + } + + if (ev->ev_flags & EVLIST_TIMEOUT) + event_queue_remove(base, ev, EVLIST_TIMEOUT); + + if (ev->ev_flags & EVLIST_ACTIVE) + event_queue_remove(base, ev, EVLIST_ACTIVE); + + if (ev->ev_flags & EVLIST_INSERTED) { + event_queue_remove(base, ev, EVLIST_INSERTED); + return (base->evsel->del(base->evbase, ev)); + } + + return (0); +} + +void +event_active(struct event *ev, int res, short ncalls) +{ + /* We get different kinds of events, add them together */ + if (ev->ev_flags & EVLIST_ACTIVE) { + ev->ev_res |= res; + return; + } + + ev->ev_res = res; + ev->ev_ncalls = ncalls; + ev->ev_pncalls = NULL; + event_queue_insert(ev->ev_base, ev, EVLIST_ACTIVE); +} + +static int +timeout_next(struct event_base *base, struct timeval **tv_p) +{ + struct timeval now; + struct event *ev; + struct timeval *tv = *tv_p; + + if ((ev = min_heap_top(&base->timeheap)) == NULL) { + /* if no time-based events are active wait for I/O */ + *tv_p = NULL; + return (0); + } + + if (gettime(base, &now) == -1) + return (-1); + + if (evutil_timercmp(&ev->ev_timeout, &now, <=)) { + evutil_timerclear(tv); + return (0); + } + + evutil_timersub(&ev->ev_timeout, &now, tv); + + assert(tv->tv_sec >= 0); + assert(tv->tv_usec >= 0); + + event_debug(("timeout_next: in %ld seconds", tv->tv_sec)); + return (0); +} + +/* + * Determines if the time is running backwards by comparing the current + * time against the last time we checked. Not needed when using clock + * monotonic. + */ + +static void +timeout_correct(struct event_base *base, struct timeval *tv) +{ + struct event **pev; + unsigned int size; + struct timeval off; + + if (use_monotonic) + return; + + /* Check if time is running backwards */ + gettime(base, tv); + if (evutil_timercmp(tv, &base->event_tv, >=)) { + base->event_tv = *tv; + return; + } + + event_debug(("%s: time is running backwards, corrected", + __func__)); + evutil_timersub(&base->event_tv, tv, &off); + + /* + * We can modify the key element of the node without destroying + * the key, beause we apply it to all in the right order. + */ + pev = base->timeheap.p; + size = base->timeheap.n; + for (; size-- > 0; ++pev) { + struct timeval *ev_tv = &(**pev).ev_timeout; + evutil_timersub(ev_tv, &off, ev_tv); + } + /* Now remember what the new time turned out to be. */ + base->event_tv = *tv; +} + +void +timeout_process(struct event_base *base) +{ + struct timeval now; + struct event *ev; + + if (min_heap_empty(&base->timeheap)) + return; + + gettime(base, &now); + + while ((ev = min_heap_top(&base->timeheap))) { + if (evutil_timercmp(&ev->ev_timeout, &now, >)) + break; + + /* delete this event from the I/O queues */ + event_del(ev); + + event_debug(("timeout_process: call %p", + ev->ev_callback)); + event_active(ev, EV_TIMEOUT, 1); + } +} + +void +event_queue_remove(struct event_base *base, struct event *ev, int queue) +{ + if (!(ev->ev_flags & queue)) + event_errx(1, "%s: %p(fd %d) not on queue %x", __func__, + ev, ev->ev_fd, queue); + + if (~ev->ev_flags & EVLIST_INTERNAL) + base->event_count--; + + ev->ev_flags &= ~queue; + switch (queue) { + case EVLIST_INSERTED: + TAILQ_REMOVE(&base->eventqueue, ev, ev_next); + break; + case EVLIST_ACTIVE: + base->event_count_active--; + TAILQ_REMOVE(base->activequeues[ev->ev_pri], + ev, ev_active_next); + break; + case EVLIST_TIMEOUT: + min_heap_erase(&base->timeheap, ev); + break; + default: + event_errx(1, "%s: unknown queue %x", __func__, queue); + } +} + +void +event_queue_insert(struct event_base *base, struct event *ev, int queue) +{ + if (ev->ev_flags & queue) { + /* Double insertion is possible for active events */ + if (queue & EVLIST_ACTIVE) + return; + + event_errx(1, "%s: %p(fd %d) already on queue %x", __func__, + ev, ev->ev_fd, queue); + } + + if (~ev->ev_flags & EVLIST_INTERNAL) + base->event_count++; + + ev->ev_flags |= queue; + switch (queue) { + case EVLIST_INSERTED: + TAILQ_INSERT_TAIL(&base->eventqueue, ev, ev_next); + break; + case EVLIST_ACTIVE: + base->event_count_active++; + TAILQ_INSERT_TAIL(base->activequeues[ev->ev_pri], + ev,ev_active_next); + break; + case EVLIST_TIMEOUT: { + min_heap_push(&base->timeheap, ev); + break; + } + default: + event_errx(1, "%s: unknown queue %x", __func__, queue); + } +} + +/* Functions for debugging */ + +const char * +event_get_version(void) +{ + return (VERSION); +} + +/* + * No thread-safe interface needed - the information should be the same + * for all threads. + */ + +const char * +event_get_method(void) +{ + return (current_base->evsel->name); +} diff --git a/third_party/libevent/event.h b/third_party/libevent/event.h new file mode 100644 index 0000000000..72e9b8b4f2 --- /dev/null +++ b/third_party/libevent/event.h @@ -0,0 +1,1180 @@ +/* + * Copyright (c) 2000-2007 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef _EVENT_H_ +#define _EVENT_H_ + +/** @mainpage + + @section intro Introduction + + libevent is an event notification library for developing scalable network + servers. The libevent API provides a mechanism to execute a callback + function when a specific event occurs on a file descriptor or after a + timeout has been reached. Furthermore, libevent also support callbacks due + to signals or regular timeouts. + + libevent is meant to replace the event loop found in event driven network + servers. An application just needs to call event_dispatch() and then add or + remove events dynamically without having to change the event loop. + + Currently, libevent supports /dev/poll, kqueue(2), select(2), poll(2) and + epoll(4). It also has experimental support for real-time signals. The + internal event mechanism is completely independent of the exposed event API, + and a simple update of libevent can provide new functionality without having + to redesign the applications. As a result, Libevent allows for portable + application development and provides the most scalable event notification + mechanism available on an operating system. Libevent can also be used for + multi-threaded aplications; see Steven Grimm's explanation. Libevent should + compile on Linux, *BSD, Mac OS X, Solaris and Windows. + + @section usage Standard usage + + Every program that uses libevent must include the header, and pass + the -levent flag to the linker. Before using any of the functions in the + library, you must call event_init() or event_base_new() to perform one-time + initialization of the libevent library. + + @section event Event notification + + For each file descriptor that you wish to monitor, you must declare an event + structure and call event_set() to initialize the members of the structure. + To enable notification, you add the structure to the list of monitored + events by calling event_add(). The event structure must remain allocated as + long as it is active, so it should be allocated on the heap. Finally, you + call event_dispatch() to loop and dispatch events. + + @section bufferevent I/O Buffers + + libevent provides an abstraction on top of the regular event callbacks. This + abstraction is called a buffered event. A buffered event provides input and + output buffers that get filled and drained automatically. The user of a + buffered event no longer deals directly with the I/O, but instead is reading + from input and writing to output buffers. + + Once initialized via bufferevent_new(), the bufferevent structure can be + used repeatedly with bufferevent_enable() and bufferevent_disable(). + Instead of reading and writing directly to a socket, you would call + bufferevent_read() and bufferevent_write(). + + When read enabled the bufferevent will try to read from the file descriptor + and call the read callback. The write callback is executed whenever the + output buffer is drained below the write low watermark, which is 0 by + default. + + @section timers Timers + + libevent can also be used to create timers that invoke a callback after a + certain amount of time has expired. The evtimer_set() function prepares an + event struct to be used as a timer. To activate the timer, call + evtimer_add(). Timers can be deactivated by calling evtimer_del(). + + @section timeouts Timeouts + + In addition to simple timers, libevent can assign timeout events to file + descriptors that are triggered whenever a certain amount of time has passed + with no activity on a file descriptor. The timeout_set() function + initializes an event struct for use as a timeout. Once initialized, the + event must be activated by using timeout_add(). To cancel the timeout, call + timeout_del(). + + @section evdns Asynchronous DNS resolution + + libevent provides an asynchronous DNS resolver that should be used instead + of the standard DNS resolver functions. These functions can be imported by + including the header in your program. Before using any of the + resolver functions, you must call evdns_init() to initialize the library. To + convert a hostname to an IP address, you call the evdns_resolve_ipv4() + function. To perform a reverse lookup, you would call the + evdns_resolve_reverse() function. All of these functions use callbacks to + avoid blocking while the lookup is performed. + + @section evhttp Event-driven HTTP servers + + libevent provides a very simple event-driven HTTP server that can be + embedded in your program and used to service HTTP requests. + + To use this capability, you need to include the header in your + program. You create the server by calling evhttp_new(). Add addresses and + ports to listen on with evhttp_bind_socket(). You then register one or more + callbacks to handle incoming requests. Each URI can be assigned a callback + via the evhttp_set_cb() function. A generic callback function can also be + registered via evhttp_set_gencb(); this callback will be invoked if no other + callbacks have been registered for a given URI. + + @section evrpc A framework for RPC servers and clients + + libevents provides a framework for creating RPC servers and clients. It + takes care of marshaling and unmarshaling all data structures. + + @section api API Reference + + To browse the complete documentation of the libevent API, click on any of + the following links. + + event.h + The primary libevent header + + evdns.h + Asynchronous DNS resolution + + evhttp.h + An embedded libevent-based HTTP server + + evrpc.h + A framework for creating RPC servers and clients + + */ + +/** @file event.h + + A library for writing event-driven network servers + + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "event-config.h" +#ifdef _EVENT_HAVE_SYS_TYPES_H +#include +#endif +#ifdef _EVENT_HAVE_SYS_TIME_H +#include +#endif +#ifdef _EVENT_HAVE_STDINT_H +#include +#endif +#include + +/* For int types. */ +#include "evutil.h" + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#undef WIN32_LEAN_AND_MEAN +typedef unsigned char u_char; +typedef unsigned short u_short; +#endif + +#define EVLIST_TIMEOUT 0x01 +#define EVLIST_INSERTED 0x02 +#define EVLIST_SIGNAL 0x04 +#define EVLIST_ACTIVE 0x08 +#define EVLIST_INTERNAL 0x10 +#define EVLIST_INIT 0x80 + +/* EVLIST_X_ Private space: 0x1000-0xf000 */ +#define EVLIST_ALL (0xf000 | 0x9f) + +#define EV_TIMEOUT 0x01 +#define EV_READ 0x02 +#define EV_WRITE 0x04 +#define EV_SIGNAL 0x08 +#define EV_PERSIST 0x10 /* Persistant event */ + +/* Fix so that ppl dont have to run with */ +#ifndef TAILQ_ENTRY +#define _EVENT_DEFINED_TQENTRY +#define TAILQ_ENTRY(type) \ +struct { \ + struct type *tqe_next; /* next element */ \ + struct type **tqe_prev; /* address of previous next element */ \ +} +#endif /* !TAILQ_ENTRY */ + +struct event_base; +#ifndef EVENT_NO_STRUCT +struct event { + TAILQ_ENTRY (event) ev_next; + TAILQ_ENTRY (event) ev_active_next; + TAILQ_ENTRY (event) ev_signal_next; + unsigned int min_heap_idx; /* for managing timeouts */ + + struct event_base *ev_base; + + int ev_fd; + short ev_events; + short ev_ncalls; + short *ev_pncalls; /* Allows deletes in callback */ + + struct timeval ev_timeout; + + int ev_pri; /* smaller numbers are higher priority */ + + void (*ev_callback)(int, short, void *arg); + void *ev_arg; + + int ev_res; /* result passed to event callback */ + int ev_flags; +}; +#else +struct event; +#endif + +#define EVENT_SIGNAL(ev) (int)(ev)->ev_fd +#define EVENT_FD(ev) (int)(ev)->ev_fd + +/* + * Key-Value pairs. Can be used for HTTP headers but also for + * query argument parsing. + */ +struct evkeyval { + TAILQ_ENTRY(evkeyval) next; + + char *key; + char *value; +}; + +#ifdef _EVENT_DEFINED_TQENTRY +#undef TAILQ_ENTRY +struct event_list; +struct evkeyvalq; +#undef _EVENT_DEFINED_TQENTRY +#else +TAILQ_HEAD (event_list, event); +TAILQ_HEAD (evkeyvalq, evkeyval); +#endif /* _EVENT_DEFINED_TQENTRY */ + +/** + Initialize the event API. + + Use event_base_new() to initialize a new event base, but does not set + the current_base global. If using only event_base_new(), each event + added must have an event base set with event_base_set() + + @see event_base_set(), event_base_free(), event_init() + */ +struct event_base *event_base_new(void); + +/** + Initialize the event API. + + The event API needs to be initialized with event_init() before it can be + used. Sets the current_base global representing the default base for + events that have no base associated with them. + + @see event_base_set(), event_base_new() + */ +struct event_base *event_init(void); + +/** + Reinitialized the event base after a fork + + Some event mechanisms do not survive across fork. The event base needs + to be reinitialized with the event_reinit() function. + + @param base the event base that needs to be re-initialized + @return 0 if successful, or -1 if some events could not be re-added. + @see event_base_new(), event_init() +*/ +int event_reinit(struct event_base *base); + +/** + Loop to process events. + + In order to process events, an application needs to call + event_dispatch(). This function only returns on error, and should + replace the event core of the application program. + + @see event_base_dispatch() + */ +int event_dispatch(void); + + +/** + Threadsafe event dispatching loop. + + @param eb the event_base structure returned by event_init() + @see event_init(), event_dispatch() + */ +int event_base_dispatch(struct event_base *); + + +/** + Get the kernel event notification mechanism used by libevent. + + @param eb the event_base structure returned by event_base_new() + @return a string identifying the kernel event mechanism (kqueue, epoll, etc.) + */ +const char *event_base_get_method(struct event_base *); + + +/** + Deallocate all memory associated with an event_base, and free the base. + + Note that this function will not close any fds or free any memory passed + to event_set as the argument to callback. + + @param eb an event_base to be freed + */ +void event_base_free(struct event_base *); + + +#define _EVENT_LOG_DEBUG 0 +#define _EVENT_LOG_MSG 1 +#define _EVENT_LOG_WARN 2 +#define _EVENT_LOG_ERR 3 +typedef void (*event_log_cb)(int severity, const char *msg); +/** + Redirect libevent's log messages. + + @param cb a function taking two arguments: an integer severity between + _EVENT_LOG_DEBUG and _EVENT_LOG_ERR, and a string. If cb is NULL, + then the default log is used. + */ +void event_set_log_callback(event_log_cb cb); + +/** + Associate a different event base with an event. + + @param eb the event base + @param ev the event + */ +int event_base_set(struct event_base *, struct event *); + +/** + event_loop() flags + */ +/*@{*/ +#define EVLOOP_ONCE 0x01 /**< Block at most once. */ +#define EVLOOP_NONBLOCK 0x02 /**< Do not block. */ +/*@}*/ + +/** + Handle events. + + This is a more flexible version of event_dispatch(). + + @param flags any combination of EVLOOP_ONCE | EVLOOP_NONBLOCK + @return 0 if successful, -1 if an error occurred, or 1 if no events were + registered. + @see event_loopexit(), event_base_loop() +*/ +int event_loop(int); + +/** + Handle events (threadsafe version). + + This is a more flexible version of event_base_dispatch(). + + @param eb the event_base structure returned by event_init() + @param flags any combination of EVLOOP_ONCE | EVLOOP_NONBLOCK + @return 0 if successful, -1 if an error occurred, or 1 if no events were + registered. + @see event_loopexit(), event_base_loop() + */ +int event_base_loop(struct event_base *, int); + +/** + Exit the event loop after the specified time. + + The next event_loop() iteration after the given timer expires will + complete normally (handling all queued events) then exit without + blocking for events again. + + Subsequent invocations of event_loop() will proceed normally. + + @param tv the amount of time after which the loop should terminate. + @return 0 if successful, or -1 if an error occurred + @see event_loop(), event_base_loop(), event_base_loopexit() + */ +int event_loopexit(const struct timeval *); + + +/** + Exit the event loop after the specified time (threadsafe variant). + + The next event_base_loop() iteration after the given timer expires will + complete normally (handling all queued events) then exit without + blocking for events again. + + Subsequent invocations of event_base_loop() will proceed normally. + + @param eb the event_base structure returned by event_init() + @param tv the amount of time after which the loop should terminate. + @return 0 if successful, or -1 if an error occurred + @see event_loopexit() + */ +int event_base_loopexit(struct event_base *, const struct timeval *); + +/** + Abort the active event_loop() immediately. + + event_loop() will abort the loop after the next event is completed; + event_loopbreak() is typically invoked from this event's callback. + This behavior is analogous to the "break;" statement. + + Subsequent invocations of event_loop() will proceed normally. + + @return 0 if successful, or -1 if an error occurred + @see event_base_loopbreak(), event_loopexit() + */ +int event_loopbreak(void); + +/** + Abort the active event_base_loop() immediately. + + event_base_loop() will abort the loop after the next event is completed; + event_base_loopbreak() is typically invoked from this event's callback. + This behavior is analogous to the "break;" statement. + + Subsequent invocations of event_loop() will proceed normally. + + @param eb the event_base structure returned by event_init() + @return 0 if successful, or -1 if an error occurred + @see event_base_loopexit + */ +int event_base_loopbreak(struct event_base *); + + +/** + Add a timer event. + + @param ev the event struct + @param tv timeval struct + */ +#define evtimer_add(ev, tv) event_add(ev, tv) + + +/** + Define a timer event. + + @param ev event struct to be modified + @param cb callback function + @param arg argument that will be passed to the callback function + */ +#define evtimer_set(ev, cb, arg) event_set(ev, -1, 0, cb, arg) + + +/** + * Delete a timer event. + * + * @param ev the event struct to be disabled + */ +#define evtimer_del(ev) event_del(ev) +#define evtimer_pending(ev, tv) event_pending(ev, EV_TIMEOUT, tv) +#define evtimer_initialized(ev) ((ev)->ev_flags & EVLIST_INIT) + +/** + * Add a timeout event. + * + * @param ev the event struct to be disabled + * @param tv the timeout value, in seconds + */ +#define timeout_add(ev, tv) event_add(ev, tv) + + +/** + * Define a timeout event. + * + * @param ev the event struct to be defined + * @param cb the callback to be invoked when the timeout expires + * @param arg the argument to be passed to the callback + */ +#define timeout_set(ev, cb, arg) event_set(ev, -1, 0, cb, arg) + + +/** + * Disable a timeout event. + * + * @param ev the timeout event to be disabled + */ +#define timeout_del(ev) event_del(ev) + +#define timeout_pending(ev, tv) event_pending(ev, EV_TIMEOUT, tv) +#define timeout_initialized(ev) ((ev)->ev_flags & EVLIST_INIT) + +#define signal_add(ev, tv) event_add(ev, tv) +#define signal_set(ev, x, cb, arg) \ + event_set(ev, x, EV_SIGNAL|EV_PERSIST, cb, arg) +#define signal_del(ev) event_del(ev) +#define signal_pending(ev, tv) event_pending(ev, EV_SIGNAL, tv) +#define signal_initialized(ev) ((ev)->ev_flags & EVLIST_INIT) + +/** + Prepare an event structure to be added. + + The function event_set() prepares the event structure ev to be used in + future calls to event_add() and event_del(). The event will be prepared to + call the function specified by the fn argument with an int argument + indicating the file descriptor, a short argument indicating the type of + event, and a void * argument given in the arg argument. The fd indicates + the file descriptor that should be monitored for events. The events can be + either EV_READ, EV_WRITE, or both. Indicating that an application can read + or write from the file descriptor respectively without blocking. + + The function fn will be called with the file descriptor that triggered the + event and the type of event which will be either EV_TIMEOUT, EV_SIGNAL, + EV_READ, or EV_WRITE. The additional flag EV_PERSIST makes an event_add() + persistent until event_del() has been called. + + @param ev an event struct to be modified + @param fd the file descriptor to be monitored + @param event desired events to monitor; can be EV_READ and/or EV_WRITE + @param fn callback function to be invoked when the event occurs + @param arg an argument to be passed to the callback function + + @see event_add(), event_del(), event_once() + + */ +void event_set(struct event *, int, short, void (*)(int, short, void *), void *); + +/** + Schedule a one-time event to occur. + + The function event_once() is similar to event_set(). However, it schedules + a callback to be called exactly once and does not require the caller to + prepare an event structure. + + @param fd a file descriptor to monitor + @param events event(s) to monitor; can be any of EV_TIMEOUT | EV_READ | + EV_WRITE + @param callback callback function to be invoked when the event occurs + @param arg an argument to be passed to the callback function + @param timeout the maximum amount of time to wait for the event, or NULL + to wait forever + @return 0 if successful, or -1 if an error occurred + @see event_set() + + */ +int event_once(int, short, void (*)(int, short, void *), void *, + const struct timeval *); + + +/** + Schedule a one-time event (threadsafe variant) + + The function event_base_once() is similar to event_set(). However, it + schedules a callback to be called exactly once and does not require the + caller to prepare an event structure. + + @param base an event_base returned by event_init() + @param fd a file descriptor to monitor + @param events event(s) to monitor; can be any of EV_TIMEOUT | EV_READ | + EV_WRITE + @param callback callback function to be invoked when the event occurs + @param arg an argument to be passed to the callback function + @param timeout the maximum amount of time to wait for the event, or NULL + to wait forever + @return 0 if successful, or -1 if an error occurred + @see event_once() + */ +int event_base_once(struct event_base *base, int fd, short events, + void (*callback)(int, short, void *), void *arg, + const struct timeval *timeout); + + +/** + Add an event to the set of monitored events. + + The function event_add() schedules the execution of the ev event when the + event specified in event_set() occurs or in at least the time specified in + the tv. If tv is NULL, no timeout occurs and the function will only be + called if a matching event occurs on the file descriptor. The event in the + ev argument must be already initialized by event_set() and may not be used + in calls to event_set() until it has timed out or been removed with + event_del(). If the event in the ev argument already has a scheduled + timeout, the old timeout will be replaced by the new one. + + @param ev an event struct initialized via event_set() + @param timeout the maximum amount of time to wait for the event, or NULL + to wait forever + @return 0 if successful, or -1 if an error occurred + @see event_del(), event_set() + */ +int event_add(struct event *ev, const struct timeval *timeout); + + +/** + Remove an event from the set of monitored events. + + The function event_del() will cancel the event in the argument ev. If the + event has already executed or has never been added the call will have no + effect. + + @param ev an event struct to be removed from the working set + @return 0 if successful, or -1 if an error occurred + @see event_add() + */ +int event_del(struct event *); + +void event_active(struct event *, int, short); + + +/** + Checks if a specific event is pending or scheduled. + + @param ev an event struct previously passed to event_add() + @param event the requested event type; any of EV_TIMEOUT|EV_READ| + EV_WRITE|EV_SIGNAL + @param tv an alternate timeout (FIXME - is this true?) + + @return 1 if the event is pending, or 0 if the event has not occurred + + */ +int event_pending(struct event *ev, short event, struct timeval *tv); + + +/** + Test if an event structure has been initialized. + + The event_initialized() macro can be used to check if an event has been + initialized. + + @param ev an event structure to be tested + @return 1 if the structure has been initialized, or 0 if it has not been + initialized + */ +#ifdef WIN32 +#define event_initialized(ev) ((ev)->ev_flags & EVLIST_INIT && (ev)->ev_fd != (int)INVALID_HANDLE_VALUE) +#else +#define event_initialized(ev) ((ev)->ev_flags & EVLIST_INIT) +#endif + + +/** + Get the libevent version number. + + @return a string containing the version number of libevent + */ +const char *event_get_version(void); + + +/** + Get the kernel event notification mechanism used by libevent. + + @return a string identifying the kernel event mechanism (kqueue, epoll, etc.) + */ +const char *event_get_method(void); + + +/** + Set the number of different event priorities. + + By default libevent schedules all active events with the same priority. + However, some time it is desirable to process some events with a higher + priority than others. For that reason, libevent supports strict priority + queues. Active events with a lower priority are always processed before + events with a higher priority. + + The number of different priorities can be set initially with the + event_priority_init() function. This function should be called before the + first call to event_dispatch(). The event_priority_set() function can be + used to assign a priority to an event. By default, libevent assigns the + middle priority to all events unless their priority is explicitly set. + + @param npriorities the maximum number of priorities + @return 0 if successful, or -1 if an error occurred + @see event_base_priority_init(), event_priority_set() + + */ +int event_priority_init(int); + + +/** + Set the number of different event priorities (threadsafe variant). + + See the description of event_priority_init() for more information. + + @param eb the event_base structure returned by event_init() + @param npriorities the maximum number of priorities + @return 0 if successful, or -1 if an error occurred + @see event_priority_init(), event_priority_set() + */ +int event_base_priority_init(struct event_base *, int); + + +/** + Assign a priority to an event. + + @param ev an event struct + @param priority the new priority to be assigned + @return 0 if successful, or -1 if an error occurred + @see event_priority_init() + */ +int event_priority_set(struct event *, int); + + +/* These functions deal with buffering input and output */ + +struct evbuffer { + u_char *buffer; + u_char *orig_buffer; + + size_t misalign; + size_t totallen; + size_t off; + + void (*cb)(struct evbuffer *, size_t, size_t, void *); + void *cbarg; +}; + +/* Just for error reporting - use other constants otherwise */ +#define EVBUFFER_READ 0x01 +#define EVBUFFER_WRITE 0x02 +#define EVBUFFER_EOF 0x10 +#define EVBUFFER_ERROR 0x20 +#define EVBUFFER_TIMEOUT 0x40 + +struct bufferevent; +typedef void (*evbuffercb)(struct bufferevent *, void *); +typedef void (*everrorcb)(struct bufferevent *, short what, void *); + +struct event_watermark { + size_t low; + size_t high; +}; + +#ifndef EVENT_NO_STRUCT +struct bufferevent { + struct event_base *ev_base; + + struct event ev_read; + struct event ev_write; + + struct evbuffer *input; + struct evbuffer *output; + + struct event_watermark wm_read; + struct event_watermark wm_write; + + evbuffercb readcb; + evbuffercb writecb; + everrorcb errorcb; + void *cbarg; + + int timeout_read; /* in seconds */ + int timeout_write; /* in seconds */ + + short enabled; /* events that are currently enabled */ +}; +#endif + +/** + Create a new bufferevent. + + libevent provides an abstraction on top of the regular event callbacks. + This abstraction is called a buffered event. A buffered event provides + input and output buffers that get filled and drained automatically. The + user of a buffered event no longer deals directly with the I/O, but + instead is reading from input and writing to output buffers. + + Once initialized, the bufferevent structure can be used repeatedly with + bufferevent_enable() and bufferevent_disable(). + + When read enabled the bufferevent will try to read from the file descriptor + and call the read callback. The write callback is executed whenever the + output buffer is drained below the write low watermark, which is 0 by + default. + + If multiple bases are in use, bufferevent_base_set() must be called before + enabling the bufferevent for the first time. + + @param fd the file descriptor from which data is read and written to. + This file descriptor is not allowed to be a pipe(2). + @param readcb callback to invoke when there is data to be read, or NULL if + no callback is desired + @param writecb callback to invoke when the file descriptor is ready for + writing, or NULL if no callback is desired + @param errorcb callback to invoke when there is an error on the file + descriptor + @param cbarg an argument that will be supplied to each of the callbacks + (readcb, writecb, and errorcb) + @return a pointer to a newly allocated bufferevent struct, or NULL if an + error occurred + @see bufferevent_base_set(), bufferevent_free() + */ +struct bufferevent *bufferevent_new(int fd, + evbuffercb readcb, evbuffercb writecb, everrorcb errorcb, void *cbarg); + + +/** + Assign a bufferevent to a specific event_base. + + @param base an event_base returned by event_init() + @param bufev a bufferevent struct returned by bufferevent_new() + @return 0 if successful, or -1 if an error occurred + @see bufferevent_new() + */ +int bufferevent_base_set(struct event_base *base, struct bufferevent *bufev); + + +/** + Assign a priority to a bufferevent. + + @param bufev a bufferevent struct + @param pri the priority to be assigned + @return 0 if successful, or -1 if an error occurred + */ +int bufferevent_priority_set(struct bufferevent *bufev, int pri); + + +/** + Deallocate the storage associated with a bufferevent structure. + + @param bufev the bufferevent structure to be freed. + */ +void bufferevent_free(struct bufferevent *bufev); + + +/** + Changes the callbacks for a bufferevent. + + @param bufev the bufferevent object for which to change callbacks + @param readcb callback to invoke when there is data to be read, or NULL if + no callback is desired + @param writecb callback to invoke when the file descriptor is ready for + writing, or NULL if no callback is desired + @param errorcb callback to invoke when there is an error on the file + descriptor + @param cbarg an argument that will be supplied to each of the callbacks + (readcb, writecb, and errorcb) + @see bufferevent_new() + */ +void bufferevent_setcb(struct bufferevent *bufev, + evbuffercb readcb, evbuffercb writecb, everrorcb errorcb, void *cbarg); + +/** + Changes the file descriptor on which the bufferevent operates. + + @param bufev the bufferevent object for which to change the file descriptor + @param fd the file descriptor to operate on +*/ +void bufferevent_setfd(struct bufferevent *bufev, int fd); + +/** + Write data to a bufferevent buffer. + + The bufferevent_write() function can be used to write data to the file + descriptor. The data is appended to the output buffer and written to the + descriptor automatically as it becomes available for writing. + + @param bufev the bufferevent to be written to + @param data a pointer to the data to be written + @param size the length of the data, in bytes + @return 0 if successful, or -1 if an error occurred + @see bufferevent_write_buffer() + */ +int bufferevent_write(struct bufferevent *bufev, + const void *data, size_t size); + + +/** + Write data from an evbuffer to a bufferevent buffer. The evbuffer is + being drained as a result. + + @param bufev the bufferevent to be written to + @param buf the evbuffer to be written + @return 0 if successful, or -1 if an error occurred + @see bufferevent_write() + */ +int bufferevent_write_buffer(struct bufferevent *bufev, struct evbuffer *buf); + + +/** + Read data from a bufferevent buffer. + + The bufferevent_read() function is used to read data from the input buffer. + + @param bufev the bufferevent to be read from + @param data pointer to a buffer that will store the data + @param size the size of the data buffer, in bytes + @return the amount of data read, in bytes. + */ +size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size); + +/** + Enable a bufferevent. + + @param bufev the bufferevent to be enabled + @param event any combination of EV_READ | EV_WRITE. + @return 0 if successful, or -1 if an error occurred + @see bufferevent_disable() + */ +int bufferevent_enable(struct bufferevent *bufev, short event); + + +/** + Disable a bufferevent. + + @param bufev the bufferevent to be disabled + @param event any combination of EV_READ | EV_WRITE. + @return 0 if successful, or -1 if an error occurred + @see bufferevent_enable() + */ +int bufferevent_disable(struct bufferevent *bufev, short event); + + +/** + Set the read and write timeout for a buffered event. + + @param bufev the bufferevent to be modified + @param timeout_read the read timeout + @param timeout_write the write timeout + */ +void bufferevent_settimeout(struct bufferevent *bufev, + int timeout_read, int timeout_write); + + +/** + Sets the watermarks for read and write events. + + On input, a bufferevent does not invoke the user read callback unless + there is at least low watermark data in the buffer. If the read buffer + is beyond the high watermark, the buffevent stops reading from the network. + + On output, the user write callback is invoked whenever the buffered data + falls below the low watermark. + + @param bufev the bufferevent to be modified + @param events EV_READ, EV_WRITE or both + @param lowmark the lower watermark to set + @param highmark the high watermark to set +*/ + +void bufferevent_setwatermark(struct bufferevent *bufev, short events, + size_t lowmark, size_t highmark); + +#define EVBUFFER_LENGTH(x) (x)->off +#define EVBUFFER_DATA(x) (x)->buffer +#define EVBUFFER_INPUT(x) (x)->input +#define EVBUFFER_OUTPUT(x) (x)->output + + +/** + Allocate storage for a new evbuffer. + + @return a pointer to a newly allocated evbuffer struct, or NULL if an error + occurred + */ +struct evbuffer *evbuffer_new(void); + + +/** + Deallocate storage for an evbuffer. + + @param pointer to the evbuffer to be freed + */ +void evbuffer_free(struct evbuffer *); + + +/** + Expands the available space in an event buffer. + + Expands the available space in the event buffer to at least datlen + + @param buf the event buffer to be expanded + @param datlen the new minimum length requirement + @return 0 if successful, or -1 if an error occurred +*/ +int evbuffer_expand(struct evbuffer *, size_t); + + +/** + Append data to the end of an evbuffer. + + @param buf the event buffer to be appended to + @param data pointer to the beginning of the data buffer + @param datlen the number of bytes to be copied from the data buffer + */ +int evbuffer_add(struct evbuffer *, const void *, size_t); + + + +/** + Read data from an event buffer and drain the bytes read. + + @param buf the event buffer to be read from + @param data the destination buffer to store the result + @param datlen the maximum size of the destination buffer + @return the number of bytes read + */ +int evbuffer_remove(struct evbuffer *, void *, size_t); + + +/** + * Read a single line from an event buffer. + * + * Reads a line terminated by either '\r\n', '\n\r' or '\r' or '\n'. + * The returned buffer needs to be freed by the caller. + * + * @param buffer the evbuffer to read from + * @return pointer to a single line, or NULL if an error occurred + */ +char *evbuffer_readline(struct evbuffer *); + + +/** + Move data from one evbuffer into another evbuffer. + + This is a destructive add. The data from one buffer moves into + the other buffer. The destination buffer is expanded as needed. + + @param outbuf the output buffer + @param inbuf the input buffer + @return 0 if successful, or -1 if an error occurred + */ +int evbuffer_add_buffer(struct evbuffer *, struct evbuffer *); + + +/** + Append a formatted string to the end of an evbuffer. + + @param buf the evbuffer that will be appended to + @param fmt a format string + @param ... arguments that will be passed to printf(3) + @return The number of bytes added if successful, or -1 if an error occurred. + */ +int evbuffer_add_printf(struct evbuffer *, const char *fmt, ...) +#ifdef __GNUC__ + __attribute__((format(printf, 2, 3))) +#endif +; + + +/** + Append a va_list formatted string to the end of an evbuffer. + + @param buf the evbuffer that will be appended to + @param fmt a format string + @param ap a varargs va_list argument array that will be passed to vprintf(3) + @return The number of bytes added if successful, or -1 if an error occurred. + */ +int evbuffer_add_vprintf(struct evbuffer *, const char *fmt, va_list ap); + + +/** + Remove a specified number of bytes data from the beginning of an evbuffer. + + @param buf the evbuffer to be drained + @param len the number of bytes to drain from the beginning of the buffer + */ +void evbuffer_drain(struct evbuffer *, size_t); + + +/** + Write the contents of an evbuffer to a file descriptor. + + The evbuffer will be drained after the bytes have been successfully written. + + @param buffer the evbuffer to be written and drained + @param fd the file descriptor to be written to + @return the number of bytes written, or -1 if an error occurred + @see evbuffer_read() + */ +int evbuffer_write(struct evbuffer *, int); + + +/** + Read from a file descriptor and store the result in an evbuffer. + + @param buf the evbuffer to store the result + @param fd the file descriptor to read from + @param howmuch the number of bytes to be read + @return the number of bytes read, or -1 if an error occurred + @see evbuffer_write() + */ +int evbuffer_read(struct evbuffer *, int, int); + + +/** + Find a string within an evbuffer. + + @param buffer the evbuffer to be searched + @param what the string to be searched for + @param len the length of the search string + @return a pointer to the beginning of the search string, or NULL if the search failed. + */ +u_char *evbuffer_find(struct evbuffer *, const u_char *, size_t); + +/** + Set a callback to invoke when the evbuffer is modified. + + @param buffer the evbuffer to be monitored + @param cb the callback function to invoke when the evbuffer is modified + @param cbarg an argument to be provided to the callback function + */ +void evbuffer_setcb(struct evbuffer *, void (*)(struct evbuffer *, size_t, size_t, void *), void *); + +/* + * Marshaling tagged data - We assume that all tags are inserted in their + * numeric order - so that unknown tags will always be higher than the + * known ones - and we can just ignore the end of an event buffer. + */ + +void evtag_init(void); + +void evtag_marshal(struct evbuffer *evbuf, ev_uint32_t tag, const void *data, + ev_uint32_t len); + +/** + Encode an integer and store it in an evbuffer. + + We encode integer's by nibbles; the first nibble contains the number + of significant nibbles - 1; this allows us to encode up to 64-bit + integers. This function is byte-order independent. + + @param evbuf evbuffer to store the encoded number + @param number a 32-bit integer + */ +void encode_int(struct evbuffer *evbuf, ev_uint32_t number); + +void evtag_marshal_int(struct evbuffer *evbuf, ev_uint32_t tag, + ev_uint32_t integer); + +void evtag_marshal_string(struct evbuffer *buf, ev_uint32_t tag, + const char *string); + +void evtag_marshal_timeval(struct evbuffer *evbuf, ev_uint32_t tag, + struct timeval *tv); + +int evtag_unmarshal(struct evbuffer *src, ev_uint32_t *ptag, + struct evbuffer *dst); +int evtag_peek(struct evbuffer *evbuf, ev_uint32_t *ptag); +int evtag_peek_length(struct evbuffer *evbuf, ev_uint32_t *plength); +int evtag_payload_length(struct evbuffer *evbuf, ev_uint32_t *plength); +int evtag_consume(struct evbuffer *evbuf); + +int evtag_unmarshal_int(struct evbuffer *evbuf, ev_uint32_t need_tag, + ev_uint32_t *pinteger); + +int evtag_unmarshal_fixed(struct evbuffer *src, ev_uint32_t need_tag, + void *data, size_t len); + +int evtag_unmarshal_string(struct evbuffer *evbuf, ev_uint32_t need_tag, + char **pstring); + +int evtag_unmarshal_timeval(struct evbuffer *evbuf, ev_uint32_t need_tag, + struct timeval *ptv); + +#ifdef __cplusplus +} +#endif + +#endif /* _EVENT_H_ */ diff --git a/third_party/libevent/event_rpcgen.py b/third_party/libevent/event_rpcgen.py new file mode 100755 index 0000000000..4ec77a6f6e --- /dev/null +++ b/third_party/libevent/event_rpcgen.py @@ -0,0 +1,1423 @@ +#!/usr/bin/env python +# +# Copyright (c) 2005 Niels Provos +# All rights reserved. +# +# Generates marshaling code based on libevent. + +import sys +import re + +# +_NAME = "event_rpcgen.py" +_VERSION = "0.1" +_STRUCT_RE = '[a-z][a-z_0-9]*' + +# Globals +line_count = 0 + +white = re.compile(r'^\s+') +cppcomment = re.compile(r'\/\/.*$') +headerdirect = [] +cppdirect = [] + +# Holds everything that makes a struct +class Struct: + def __init__(self, name): + self._name = name + self._entries = [] + self._tags = {} + print >>sys.stderr, ' Created struct: %s' % name + + def AddEntry(self, entry): + if self._tags.has_key(entry.Tag()): + print >>sys.stderr, ( 'Entry "%s" duplicates tag number ' + '%d from "%s" around line %d' ) % ( + entry.Name(), entry.Tag(), + self._tags[entry.Tag()], line_count) + sys.exit(1) + self._entries.append(entry) + self._tags[entry.Tag()] = entry.Name() + print >>sys.stderr, ' Added entry: %s' % entry.Name() + + def Name(self): + return self._name + + def EntryTagName(self, entry): + """Creates the name inside an enumeration for distinguishing data + types.""" + name = "%s_%s" % (self._name, entry.Name()) + return name.upper() + + def PrintIdented(self, file, ident, code): + """Takes an array, add indentation to each entry and prints it.""" + for entry in code: + print >>file, '%s%s' % (ident, entry) + + def PrintTags(self, file): + """Prints the tag definitions for a structure.""" + print >>file, '/* Tag definition for %s */' % self._name + print >>file, 'enum %s_ {' % self._name.lower() + for entry in self._entries: + print >>file, ' %s=%d,' % (self.EntryTagName(entry), + entry.Tag()) + print >>file, ' %s_MAX_TAGS' % (self._name.upper()) + print >>file, '};\n' + + def PrintForwardDeclaration(self, file): + print >>file, 'struct %s;' % self._name + + def PrintDeclaration(self, file): + print >>file, '/* Structure declaration for %s */' % self._name + print >>file, 'struct %s_access_ {' % self._name + for entry in self._entries: + dcl = entry.AssignDeclaration('(*%s_assign)' % entry.Name()) + dcl.extend( + entry.GetDeclaration('(*%s_get)' % entry.Name())) + if entry.Array(): + dcl.extend( + entry.AddDeclaration('(*%s_add)' % entry.Name())) + self.PrintIdented(file, ' ', dcl) + print >>file, '};\n' + + print >>file, 'struct %s {' % self._name + print >>file, ' struct %s_access_ *base;\n' % self._name + for entry in self._entries: + dcl = entry.Declaration() + self.PrintIdented(file, ' ', dcl) + print >>file, '' + for entry in self._entries: + print >>file, ' ev_uint8_t %s_set;' % entry.Name() + print >>file, '};\n' + + print >>file, \ +"""struct %(name)s *%(name)s_new(void); +void %(name)s_free(struct %(name)s *); +void %(name)s_clear(struct %(name)s *); +void %(name)s_marshal(struct evbuffer *, const struct %(name)s *); +int %(name)s_unmarshal(struct %(name)s *, struct evbuffer *); +int %(name)s_complete(struct %(name)s *); +void evtag_marshal_%(name)s(struct evbuffer *, ev_uint32_t, + const struct %(name)s *); +int evtag_unmarshal_%(name)s(struct evbuffer *, ev_uint32_t, + struct %(name)s *);""" % { 'name' : self._name } + + + # Write a setting function of every variable + for entry in self._entries: + self.PrintIdented(file, '', entry.AssignDeclaration( + entry.AssignFuncName())) + self.PrintIdented(file, '', entry.GetDeclaration( + entry.GetFuncName())) + if entry.Array(): + self.PrintIdented(file, '', entry.AddDeclaration( + entry.AddFuncName())) + + print >>file, '/* --- %s done --- */\n' % self._name + + def PrintCode(self, file): + print >>file, ('/*\n' + ' * Implementation of %s\n' + ' */\n') % self._name + + print >>file, \ + 'static struct %(name)s_access_ __%(name)s_base = {' % \ + { 'name' : self._name } + for entry in self._entries: + self.PrintIdented(file, ' ', entry.CodeBase()) + print >>file, '};\n' + + # Creation + print >>file, ( + 'struct %(name)s *\n' + '%(name)s_new(void)\n' + '{\n' + ' struct %(name)s *tmp;\n' + ' if ((tmp = malloc(sizeof(struct %(name)s))) == NULL) {\n' + ' event_warn("%%s: malloc", __func__);\n' + ' return (NULL);\n' + ' }\n' + ' tmp->base = &__%(name)s_base;\n') % { 'name' : self._name } + + for entry in self._entries: + self.PrintIdented(file, ' ', entry.CodeNew('tmp')) + print >>file, ' tmp->%s_set = 0;\n' % entry.Name() + + print >>file, ( + ' return (tmp);\n' + '}\n') + + # Adding + for entry in self._entries: + if entry.Array(): + self.PrintIdented(file, '', entry.CodeAdd()) + print >>file, '' + + # Assigning + for entry in self._entries: + self.PrintIdented(file, '', entry.CodeAssign()) + print >>file, '' + + # Getting + for entry in self._entries: + self.PrintIdented(file, '', entry.CodeGet()) + print >>file, '' + + # Clearing + print >>file, ( 'void\n' + '%(name)s_clear(struct %(name)s *tmp)\n' + '{' + ) % { 'name' : self._name } + for entry in self._entries: + self.PrintIdented(file, ' ', entry.CodeClear('tmp')) + + print >>file, '}\n' + + # Freeing + print >>file, ( 'void\n' + '%(name)s_free(struct %(name)s *tmp)\n' + '{' + ) % { 'name' : self._name } + + for entry in self._entries: + self.PrintIdented(file, ' ', entry.CodeFree('tmp')) + + print >>file, (' free(tmp);\n' + '}\n') + + # Marshaling + print >>file, ('void\n' + '%(name)s_marshal(struct evbuffer *evbuf, ' + 'const struct %(name)s *tmp)' + '{') % { 'name' : self._name } + for entry in self._entries: + indent = ' ' + # Optional entries do not have to be set + if entry.Optional(): + indent += ' ' + print >>file, ' if (tmp->%s_set) {' % entry.Name() + self.PrintIdented( + file, indent, + entry.CodeMarshal('evbuf', self.EntryTagName(entry), 'tmp')) + if entry.Optional(): + print >>file, ' }' + + print >>file, '}\n' + + # Unmarshaling + print >>file, ('int\n' + '%(name)s_unmarshal(struct %(name)s *tmp, ' + ' struct evbuffer *evbuf)\n' + '{\n' + ' ev_uint32_t tag;\n' + ' while (EVBUFFER_LENGTH(evbuf) > 0) {\n' + ' if (evtag_peek(evbuf, &tag) == -1)\n' + ' return (-1);\n' + ' switch (tag) {\n' + ) % { 'name' : self._name } + for entry in self._entries: + print >>file, ' case %s:\n' % self.EntryTagName(entry) + if not entry.Array(): + print >>file, ( + ' if (tmp->%s_set)\n' + ' return (-1);' + ) % (entry.Name()) + + self.PrintIdented( + file, ' ', + entry.CodeUnmarshal('evbuf', + self.EntryTagName(entry), 'tmp')) + + print >>file, ( ' tmp->%s_set = 1;\n' % entry.Name() + + ' break;\n' ) + print >>file, ( ' default:\n' + ' return -1;\n' + ' }\n' + ' }\n' ) + # Check if it was decoded completely + print >>file, ( ' if (%(name)s_complete(tmp) == -1)\n' + ' return (-1);' + ) % { 'name' : self._name } + + # Successfully decoded + print >>file, ( ' return (0);\n' + '}\n') + + # Checking if a structure has all the required data + print >>file, ( + 'int\n' + '%(name)s_complete(struct %(name)s *msg)\n' + '{' ) % { 'name' : self._name } + for entry in self._entries: + self.PrintIdented( + file, ' ', + entry.CodeComplete('msg')) + print >>file, ( + ' return (0);\n' + '}\n' ) + + # Complete message unmarshaling + print >>file, ( + 'int\n' + 'evtag_unmarshal_%(name)s(struct evbuffer *evbuf, ' + 'ev_uint32_t need_tag, struct %(name)s *msg)\n' + '{\n' + ' ev_uint32_t tag;\n' + ' int res = -1;\n' + '\n' + ' struct evbuffer *tmp = evbuffer_new();\n' + '\n' + ' if (evtag_unmarshal(evbuf, &tag, tmp) == -1' + ' || tag != need_tag)\n' + ' goto error;\n' + '\n' + ' if (%(name)s_unmarshal(msg, tmp) == -1)\n' + ' goto error;\n' + '\n' + ' res = 0;\n' + '\n' + ' error:\n' + ' evbuffer_free(tmp);\n' + ' return (res);\n' + '}\n' ) % { 'name' : self._name } + + # Complete message marshaling + print >>file, ( + 'void\n' + 'evtag_marshal_%(name)s(struct evbuffer *evbuf, ev_uint32_t tag, ' + 'const struct %(name)s *msg)\n' + '{\n' + ' struct evbuffer *_buf = evbuffer_new();\n' + ' assert(_buf != NULL);\n' + ' evbuffer_drain(_buf, -1);\n' + ' %(name)s_marshal(_buf, msg);\n' + ' evtag_marshal(evbuf, tag, EVBUFFER_DATA(_buf), ' + 'EVBUFFER_LENGTH(_buf));\n' + ' evbuffer_free(_buf);\n' + '}\n' ) % { 'name' : self._name } + +class Entry: + def __init__(self, type, name, tag): + self._type = type + self._name = name + self._tag = int(tag) + self._ctype = type + self._optional = 0 + self._can_be_array = 0 + self._array = 0 + self._line_count = -1 + self._struct = None + self._refname = None + + def GetTranslation(self): + return { "parent_name" : self._struct.Name(), + "name" : self._name, + "ctype" : self._ctype, + "refname" : self._refname + } + + def SetStruct(self, struct): + self._struct = struct + + def LineCount(self): + assert self._line_count != -1 + return self._line_count + + def SetLineCount(self, number): + self._line_count = number + + def Array(self): + return self._array + + def Optional(self): + return self._optional + + def Tag(self): + return self._tag + + def Name(self): + return self._name + + def Type(self): + return self._type + + def MakeArray(self, yes=1): + self._array = yes + + def MakeOptional(self): + self._optional = 1 + + def GetFuncName(self): + return '%s_%s_get' % (self._struct.Name(), self._name) + + def GetDeclaration(self, funcname): + code = [ 'int %s(struct %s *, %s *);' % ( + funcname, self._struct.Name(), self._ctype ) ] + return code + + def CodeGet(self): + code = ( + 'int', + '%(parent_name)s_%(name)s_get(struct %(parent_name)s *msg, ' + '%(ctype)s *value)', + '{', + ' if (msg->%(name)s_set != 1)', + ' return (-1);', + ' *value = msg->%(name)s_data;', + ' return (0);', + '}' ) + code = '\n'.join(code) + code = code % self.GetTranslation() + return code.split('\n') + + def AssignFuncName(self): + return '%s_%s_assign' % (self._struct.Name(), self._name) + + def AddFuncName(self): + return '%s_%s_add' % (self._struct.Name(), self._name) + + def AssignDeclaration(self, funcname): + code = [ 'int %s(struct %s *, const %s);' % ( + funcname, self._struct.Name(), self._ctype ) ] + return code + + def CodeAssign(self): + code = [ 'int', + '%(parent_name)s_%(name)s_assign(struct %(parent_name)s *msg,' + ' const %(ctype)s value)', + '{', + ' msg->%(name)s_set = 1;', + ' msg->%(name)s_data = value;', + ' return (0);', + '}' ] + code = '\n'.join(code) + code = code % self.GetTranslation() + return code.split('\n') + + def CodeClear(self, structname): + code = [ '%s->%s_set = 0;' % (structname, self.Name()) ] + + return code + + def CodeComplete(self, structname): + if self.Optional(): + return [] + + code = [ 'if (!%s->%s_set)' % (structname, self.Name()), + ' return (-1);' ] + + return code + + def CodeFree(self, name): + return [] + + def CodeBase(self): + code = [ + '%(parent_name)s_%(name)s_assign,', + '%(parent_name)s_%(name)s_get,' + ] + if self.Array(): + code.append('%(parent_name)s_%(name)s_add,') + + code = '\n'.join(code) + code = code % self.GetTranslation() + return code.split('\n') + + def Verify(self): + if self.Array() and not self._can_be_array: + print >>sys.stderr, ( + 'Entry "%s" cannot be created as an array ' + 'around line %d' ) % (self._name, self.LineCount()) + sys.exit(1) + if not self._struct: + print >>sys.stderr, ( + 'Entry "%s" does not know which struct it belongs to ' + 'around line %d' ) % (self._name, self.LineCount()) + sys.exit(1) + if self._optional and self._array: + print >>sys.stderr, ( 'Entry "%s" has illegal combination of ' + 'optional and array around line %d' ) % ( + self._name, self.LineCount() ) + sys.exit(1) + +class EntryBytes(Entry): + def __init__(self, type, name, tag, length): + # Init base class + Entry.__init__(self, type, name, tag) + + self._length = length + self._ctype = 'ev_uint8_t' + + def GetDeclaration(self, funcname): + code = [ 'int %s(struct %s *, %s **);' % ( + funcname, self._struct.Name(), self._ctype ) ] + return code + + def AssignDeclaration(self, funcname): + code = [ 'int %s(struct %s *, const %s *);' % ( + funcname, self._struct.Name(), self._ctype ) ] + return code + + def Declaration(self): + dcl = ['ev_uint8_t %s_data[%s];' % (self._name, self._length)] + + return dcl + + def CodeGet(self): + name = self._name + code = [ 'int', + '%s_%s_get(struct %s *msg, %s **value)' % ( + self._struct.Name(), name, + self._struct.Name(), self._ctype), + '{', + ' if (msg->%s_set != 1)' % name, + ' return (-1);', + ' *value = msg->%s_data;' % name, + ' return (0);', + '}' ] + return code + + def CodeAssign(self): + name = self._name + code = [ 'int', + '%s_%s_assign(struct %s *msg, const %s *value)' % ( + self._struct.Name(), name, + self._struct.Name(), self._ctype), + '{', + ' msg->%s_set = 1;' % name, + ' memcpy(msg->%s_data, value, %s);' % ( + name, self._length), + ' return (0);', + '}' ] + return code + + def CodeUnmarshal(self, buf, tag_name, var_name): + code = [ 'if (evtag_unmarshal_fixed(%s, %s, ' % (buf, tag_name) + + '%s->%s_data, ' % (var_name, self._name) + + 'sizeof(%s->%s_data)) == -1) {' % ( + var_name, self._name), + ' event_warnx("%%s: failed to unmarshal %s", __func__);' % ( + self._name ), + ' return (-1);', + '}' + ] + return code + + def CodeMarshal(self, buf, tag_name, var_name): + code = ['evtag_marshal(%s, %s, %s->%s_data, sizeof(%s->%s_data));' % ( + buf, tag_name, var_name, self._name, var_name, self._name )] + return code + + def CodeClear(self, structname): + code = [ '%s->%s_set = 0;' % (structname, self.Name()), + 'memset(%s->%s_data, 0, sizeof(%s->%s_data));' % ( + structname, self._name, structname, self._name)] + + return code + + def CodeNew(self, name): + code = ['memset(%s->%s_data, 0, sizeof(%s->%s_data));' % ( + name, self._name, name, self._name)] + return code + + def Verify(self): + if not self._length: + print >>sys.stderr, 'Entry "%s" needs a length around line %d' % ( + self._name, self.LineCount() ) + sys.exit(1) + + Entry.Verify(self) + +class EntryInt(Entry): + def __init__(self, type, name, tag): + # Init base class + Entry.__init__(self, type, name, tag) + + self._ctype = 'ev_uint32_t' + + def CodeUnmarshal(self, buf, tag_name, var_name): + code = ['if (evtag_unmarshal_int(%s, %s, &%s->%s_data) == -1) {' % ( + buf, tag_name, var_name, self._name), + ' event_warnx("%%s: failed to unmarshal %s", __func__);' % ( + self._name ), + ' return (-1);', + '}' ] + return code + + def CodeMarshal(self, buf, tag_name, var_name): + code = ['evtag_marshal_int(%s, %s, %s->%s_data);' % ( + buf, tag_name, var_name, self._name)] + return code + + def Declaration(self): + dcl = ['ev_uint32_t %s_data;' % self._name] + + return dcl + + def CodeNew(self, name): + code = ['%s->%s_data = 0;' % (name, self._name)] + return code + +class EntryString(Entry): + def __init__(self, type, name, tag): + # Init base class + Entry.__init__(self, type, name, tag) + + self._ctype = 'char *' + + def CodeAssign(self): + name = self._name + code = """int +%(parent_name)s_%(name)s_assign(struct %(parent_name)s *msg, + const %(ctype)s value) +{ + if (msg->%(name)s_data != NULL) + free(msg->%(name)s_data); + if ((msg->%(name)s_data = strdup(value)) == NULL) + return (-1); + msg->%(name)s_set = 1; + return (0); +}""" % self.GetTranslation() + + return code.split('\n') + + def CodeUnmarshal(self, buf, tag_name, var_name): + code = ['if (evtag_unmarshal_string(%s, %s, &%s->%s_data) == -1) {' % ( + buf, tag_name, var_name, self._name), + ' event_warnx("%%s: failed to unmarshal %s", __func__);' % ( + self._name ), + ' return (-1);', + '}' + ] + return code + + def CodeMarshal(self, buf, tag_name, var_name): + code = ['evtag_marshal_string(%s, %s, %s->%s_data);' % ( + buf, tag_name, var_name, self._name)] + return code + + def CodeClear(self, structname): + code = [ 'if (%s->%s_set == 1) {' % (structname, self.Name()), + ' free (%s->%s_data);' % (structname, self.Name()), + ' %s->%s_data = NULL;' % (structname, self.Name()), + ' %s->%s_set = 0;' % (structname, self.Name()), + '}' + ] + + return code + + def CodeNew(self, name): + code = ['%s->%s_data = NULL;' % (name, self._name)] + return code + + def CodeFree(self, name): + code = ['if (%s->%s_data != NULL)' % (name, self._name), + ' free (%s->%s_data); ' % (name, self._name)] + + return code + + def Declaration(self): + dcl = ['char *%s_data;' % self._name] + + return dcl + +class EntryStruct(Entry): + def __init__(self, type, name, tag, refname): + # Init base class + Entry.__init__(self, type, name, tag) + + self._can_be_array = 1 + self._refname = refname + self._ctype = 'struct %s*' % refname + + def CodeGet(self): + name = self._name + code = [ 'int', + '%s_%s_get(struct %s *msg, %s *value)' % ( + self._struct.Name(), name, + self._struct.Name(), self._ctype), + '{', + ' if (msg->%s_set != 1) {' % name, + ' msg->%s_data = %s_new();' % (name, self._refname), + ' if (msg->%s_data == NULL)' % name, + ' return (-1);', + ' msg->%s_set = 1;' % name, + ' }', + ' *value = msg->%s_data;' % name, + ' return (0);', + '}' ] + return code + + def CodeAssign(self): + name = self._name + code = """int +%(parent_name)s_%(name)s_assign(struct %(parent_name)s *msg, + const %(ctype)s value) +{ + struct evbuffer *tmp = NULL; + if (msg->%(name)s_set) { + %(refname)s_clear(msg->%(name)s_data); + msg->%(name)s_set = 0; + } else { + msg->%(name)s_data = %(refname)s_new(); + if (msg->%(name)s_data == NULL) { + event_warn("%%s: %(refname)s_new()", __func__); + goto error; + } + } + if ((tmp = evbuffer_new()) == NULL) { + event_warn("%%s: evbuffer_new()", __func__); + goto error; + } + %(refname)s_marshal(tmp, value); + if (%(refname)s_unmarshal(msg->%(name)s_data, tmp) == -1) { + event_warnx("%%s: %(refname)s_unmarshal", __func__); + goto error; + } + msg->%(name)s_set = 1; + evbuffer_free(tmp); + return (0); + error: + if (tmp != NULL) + evbuffer_free(tmp); + if (msg->%(name)s_data != NULL) { + %(refname)s_free(msg->%(name)s_data); + msg->%(name)s_data = NULL; + } + return (-1); +}""" % self.GetTranslation() + return code.split('\n') + + def CodeComplete(self, structname): + if self.Optional(): + code = [ 'if (%s->%s_set && %s_complete(%s->%s_data) == -1)' % ( + structname, self.Name(), + self._refname, structname, self.Name()), + ' return (-1);' ] + else: + code = [ 'if (%s_complete(%s->%s_data) == -1)' % ( + self._refname, structname, self.Name()), + ' return (-1);' ] + + return code + + def CodeUnmarshal(self, buf, tag_name, var_name): + code = ['%s->%s_data = %s_new();' % ( + var_name, self._name, self._refname), + 'if (%s->%s_data == NULL)' % (var_name, self._name), + ' return (-1);', + 'if (evtag_unmarshal_%s(%s, %s, %s->%s_data) == -1) {' % ( + self._refname, buf, tag_name, var_name, self._name), + ' event_warnx("%%s: failed to unmarshal %s", __func__);' % ( + self._name ), + ' return (-1);', + '}' + ] + return code + + def CodeMarshal(self, buf, tag_name, var_name): + code = ['evtag_marshal_%s(%s, %s, %s->%s_data);' % ( + self._refname, buf, tag_name, var_name, self._name)] + return code + + def CodeClear(self, structname): + code = [ 'if (%s->%s_set == 1) {' % (structname, self.Name()), + ' %s_free(%s->%s_data);' % ( + self._refname, structname, self.Name()), + ' %s->%s_data = NULL;' % (structname, self.Name()), + ' %s->%s_set = 0;' % (structname, self.Name()), + '}' + ] + + return code + + def CodeNew(self, name): + code = ['%s->%s_data = NULL;' % (name, self._name)] + return code + + def CodeFree(self, name): + code = ['if (%s->%s_data != NULL)' % (name, self._name), + ' %s_free(%s->%s_data); ' % ( + self._refname, name, self._name)] + + return code + + def Declaration(self): + dcl = ['%s %s_data;' % (self._ctype, self._name)] + + return dcl + +class EntryVarBytes(Entry): + def __init__(self, type, name, tag): + # Init base class + Entry.__init__(self, type, name, tag) + + self._ctype = 'ev_uint8_t *' + + def GetDeclaration(self, funcname): + code = [ 'int %s(struct %s *, %s *, ev_uint32_t *);' % ( + funcname, self._struct.Name(), self._ctype ) ] + return code + + def AssignDeclaration(self, funcname): + code = [ 'int %s(struct %s *, const %s, ev_uint32_t);' % ( + funcname, self._struct.Name(), self._ctype ) ] + return code + + def CodeAssign(self): + name = self._name + code = [ 'int', + '%s_%s_assign(struct %s *msg, ' + 'const %s value, ev_uint32_t len)' % ( + self._struct.Name(), name, + self._struct.Name(), self._ctype), + '{', + ' if (msg->%s_data != NULL)' % name, + ' free (msg->%s_data);' % name, + ' msg->%s_data = malloc(len);' % name, + ' if (msg->%s_data == NULL)' % name, + ' return (-1);', + ' msg->%s_set = 1;' % name, + ' msg->%s_length = len;' % name, + ' memcpy(msg->%s_data, value, len);' % name, + ' return (0);', + '}' ] + return code + + def CodeGet(self): + name = self._name + code = [ 'int', + '%s_%s_get(struct %s *msg, %s *value, ev_uint32_t *plen)' % ( + self._struct.Name(), name, + self._struct.Name(), self._ctype), + '{', + ' if (msg->%s_set != 1)' % name, + ' return (-1);', + ' *value = msg->%s_data;' % name, + ' *plen = msg->%s_length;' % name, + ' return (0);', + '}' ] + return code + + def CodeUnmarshal(self, buf, tag_name, var_name): + code = ['if (evtag_payload_length(%s, &%s->%s_length) == -1)' % ( + buf, var_name, self._name), + ' return (-1);', + # We do not want DoS opportunities + 'if (%s->%s_length > EVBUFFER_LENGTH(%s))' % ( + var_name, self._name, buf), + ' return (-1);', + 'if ((%s->%s_data = malloc(%s->%s_length)) == NULL)' % ( + var_name, self._name, var_name, self._name), + ' return (-1);', + 'if (evtag_unmarshal_fixed(%s, %s, %s->%s_data, ' + '%s->%s_length) == -1) {' % ( + buf, tag_name, var_name, self._name, var_name, self._name), + ' event_warnx("%%s: failed to unmarshal %s", __func__);' % ( + self._name ), + ' return (-1);', + '}' + ] + return code + + def CodeMarshal(self, buf, tag_name, var_name): + code = ['evtag_marshal(%s, %s, %s->%s_data, %s->%s_length);' % ( + buf, tag_name, var_name, self._name, var_name, self._name)] + return code + + def CodeClear(self, structname): + code = [ 'if (%s->%s_set == 1) {' % (structname, self.Name()), + ' free (%s->%s_data);' % (structname, self.Name()), + ' %s->%s_data = NULL;' % (structname, self.Name()), + ' %s->%s_length = 0;' % (structname, self.Name()), + ' %s->%s_set = 0;' % (structname, self.Name()), + '}' + ] + + return code + + def CodeNew(self, name): + code = ['%s->%s_data = NULL;' % (name, self._name), + '%s->%s_length = 0;' % (name, self._name) ] + return code + + def CodeFree(self, name): + code = ['if (%s->%s_data != NULL)' % (name, self._name), + ' free (%s->%s_data); ' % (name, self._name)] + + return code + + def Declaration(self): + dcl = ['ev_uint8_t *%s_data;' % self._name, + 'ev_uint32_t %s_length;' % self._name] + + return dcl + +class EntryArray(Entry): + def __init__(self, entry): + # Init base class + Entry.__init__(self, entry._type, entry._name, entry._tag) + + self._entry = entry + self._refname = entry._refname + self._ctype = 'struct %s *' % self._refname + + def GetDeclaration(self, funcname): + """Allows direct access to elements of the array.""" + translate = self.GetTranslation() + translate["funcname"] = funcname + code = [ + 'int %(funcname)s(struct %(parent_name)s *, int, %(ctype)s *);' % + translate ] + return code + + def AssignDeclaration(self, funcname): + code = [ 'int %s(struct %s *, int, const %s);' % ( + funcname, self._struct.Name(), self._ctype ) ] + return code + + def AddDeclaration(self, funcname): + code = [ '%s %s(struct %s *);' % ( + self._ctype, funcname, self._struct.Name() ) ] + return code + + def CodeGet(self): + code = """int +%(parent_name)s_%(name)s_get(struct %(parent_name)s *msg, int offset, + %(ctype)s *value) +{ + if (!msg->%(name)s_set || offset < 0 || offset >= msg->%(name)s_length) + return (-1); + *value = msg->%(name)s_data[offset]; + return (0); +}""" % self.GetTranslation() + + return code.split('\n') + + def CodeAssign(self): + code = """int +%(parent_name)s_%(name)s_assign(struct %(parent_name)s *msg, int off, + const %(ctype)s value) +{ + struct evbuffer *tmp = NULL; + if (!msg->%(name)s_set || off < 0 || off >= msg->%(name)s_length) + return (-1); + %(refname)s_clear(msg->%(name)s_data[off]); + if ((tmp = evbuffer_new()) == NULL) { + event_warn("%%s: evbuffer_new()", __func__); + goto error; + } + %(refname)s_marshal(tmp, value); + if (%(refname)s_unmarshal(msg->%(name)s_data[off], tmp) == -1) { + event_warnx("%%s: %(refname)s_unmarshal", __func__); + goto error; + } + evbuffer_free(tmp); + return (0); +error: + if (tmp != NULL) + evbuffer_free(tmp); + %(refname)s_clear(msg->%(name)s_data[off]); + return (-1); +}""" % self.GetTranslation() + + return code.split('\n') + + def CodeAdd(self): + code = \ +"""%(ctype)s +%(parent_name)s_%(name)s_add(struct %(parent_name)s *msg) +{ + if (++msg->%(name)s_length >= msg->%(name)s_num_allocated) { + int tobe_allocated = msg->%(name)s_num_allocated; + %(ctype)s* new_data = NULL; + tobe_allocated = !tobe_allocated ? 1 : tobe_allocated << 1; + new_data = (%(ctype)s*) realloc(msg->%(name)s_data, + tobe_allocated * sizeof(%(ctype)s)); + if (new_data == NULL) + goto error; + msg->%(name)s_data = new_data; + msg->%(name)s_num_allocated = tobe_allocated; + } + msg->%(name)s_data[msg->%(name)s_length - 1] = %(refname)s_new(); + if (msg->%(name)s_data[msg->%(name)s_length - 1] == NULL) + goto error; + msg->%(name)s_set = 1; + return (msg->%(name)s_data[msg->%(name)s_length - 1]); +error: + --msg->%(name)s_length; + return (NULL); +} + """ % self.GetTranslation() + + return code.split('\n') + + def CodeComplete(self, structname): + code = [] + translate = self.GetTranslation() + + if self.Optional(): + code.append( 'if (%(structname)s->%(name)s_set)' % translate) + + translate["structname"] = structname + tmp = """{ + int i; + for (i = 0; i < %(structname)s->%(name)s_length; ++i) { + if (%(refname)s_complete(%(structname)s->%(name)s_data[i]) == -1) + return (-1); + } +}""" % translate + code.extend(tmp.split('\n')) + + return code + + def CodeUnmarshal(self, buf, tag_name, var_name): + translate = self.GetTranslation() + translate["var_name"] = var_name + translate["buf"] = buf + translate["tag_name"] = tag_name + code = """if (%(parent_name)s_%(name)s_add(%(var_name)s) == NULL) + return (-1); +if (evtag_unmarshal_%(refname)s(%(buf)s, %(tag_name)s, + %(var_name)s->%(name)s_data[%(var_name)s->%(name)s_length - 1]) == -1) { + --%(var_name)s->%(name)s_length; + event_warnx("%%s: failed to unmarshal %(name)s", __func__); + return (-1); +}""" % translate + + return code.split('\n') + + def CodeMarshal(self, buf, tag_name, var_name): + code = ['{', + ' int i;', + ' for (i = 0; i < %s->%s_length; ++i) {' % ( + var_name, self._name), + ' evtag_marshal_%s(%s, %s, %s->%s_data[i]);' % ( + self._refname, buf, tag_name, var_name, self._name), + ' }', + '}' + ] + return code + + def CodeClear(self, structname): + code = [ 'if (%s->%s_set == 1) {' % (structname, self.Name()), + ' int i;', + ' for (i = 0; i < %s->%s_length; ++i) {' % ( + structname, self.Name()), + ' %s_free(%s->%s_data[i]);' % ( + self._refname, structname, self.Name()), + ' }', + ' free(%s->%s_data);' % (structname, self.Name()), + ' %s->%s_data = NULL;' % (structname, self.Name()), + ' %s->%s_set = 0;' % (structname, self.Name()), + ' %s->%s_length = 0;' % (structname, self.Name()), + ' %s->%s_num_allocated = 0;' % (structname, self.Name()), + '}' + ] + + return code + + def CodeNew(self, name): + code = ['%s->%s_data = NULL;' % (name, self._name), + '%s->%s_length = 0;' % (name, self._name), + '%s->%s_num_allocated = 0;' % (name, self._name)] + return code + + def CodeFree(self, name): + code = ['if (%s->%s_data != NULL) {' % (name, self._name), + ' int i;', + ' for (i = 0; i < %s->%s_length; ++i) {' % ( + name, self._name), + ' %s_free(%s->%s_data[i]); ' % ( + self._refname, name, self._name), + ' %s->%s_data[i] = NULL;' % (name, self._name), + ' }', + ' free(%s->%s_data);' % (name, self._name), + ' %s->%s_data = NULL;' % (name, self._name), + ' %s->%s_length = 0;' % (name, self._name), + ' %s->%s_num_allocated = 0;' % (name, self._name), + '}' + ] + + return code + + def Declaration(self): + dcl = ['struct %s **%s_data;' % (self._refname, self._name), + 'int %s_length;' % self._name, + 'int %s_num_allocated;' % self._name ] + + return dcl + +def NormalizeLine(line): + global white + global cppcomment + + line = cppcomment.sub('', line) + line = line.strip() + line = white.sub(' ', line) + + return line + +def ProcessOneEntry(newstruct, entry): + optional = 0 + array = 0 + entry_type = '' + name = '' + tag = '' + tag_set = None + separator = '' + fixed_length = '' + + tokens = entry.split(' ') + while tokens: + token = tokens[0] + tokens = tokens[1:] + + if not entry_type: + if not optional and token == 'optional': + optional = 1 + continue + + if not array and token == 'array': + array = 1 + continue + + if not entry_type: + entry_type = token + continue + + if not name: + res = re.match(r'^([^\[\]]+)(\[.*\])?$', token) + if not res: + print >>sys.stderr, 'Cannot parse name: \"%s\" around %d' % ( + entry, line_count) + sys.exit(1) + name = res.group(1) + fixed_length = res.group(2) + if fixed_length: + fixed_length = fixed_length[1:-1] + continue + + if not separator: + separator = token + if separator != '=': + print >>sys.stderr, 'Expected "=" after name \"%s\" got %s' % ( + name, token) + sys.exit(1) + continue + + if not tag_set: + tag_set = 1 + if not re.match(r'^(0x)?[0-9]+$', token): + print >>sys.stderr, 'Expected tag number: \"%s\"' % entry + sys.exit(1) + tag = int(token, 0) + continue + + print >>sys.stderr, 'Cannot parse \"%s\"' % entry + sys.exit(1) + + if not tag_set: + print >>sys.stderr, 'Need tag number: \"%s\"' % entry + sys.exit(1) + + # Create the right entry + if entry_type == 'bytes': + if fixed_length: + newentry = EntryBytes(entry_type, name, tag, fixed_length) + else: + newentry = EntryVarBytes(entry_type, name, tag) + elif entry_type == 'int' and not fixed_length: + newentry = EntryInt(entry_type, name, tag) + elif entry_type == 'string' and not fixed_length: + newentry = EntryString(entry_type, name, tag) + else: + res = re.match(r'^struct\[(%s)\]$' % _STRUCT_RE, + entry_type, re.IGNORECASE) + if res: + # References another struct defined in our file + newentry = EntryStruct(entry_type, name, tag, res.group(1)) + else: + print >>sys.stderr, 'Bad type: "%s" in "%s"' % (entry_type, entry) + sys.exit(1) + + structs = [] + + if optional: + newentry.MakeOptional() + if array: + newentry.MakeArray() + + newentry.SetStruct(newstruct) + newentry.SetLineCount(line_count) + newentry.Verify() + + if array: + # We need to encapsulate this entry into a struct + newname = newentry.Name()+ '_array' + + # Now borgify the new entry. + newentry = EntryArray(newentry) + newentry.SetStruct(newstruct) + newentry.SetLineCount(line_count) + newentry.MakeArray() + + newstruct.AddEntry(newentry) + + return structs + +def ProcessStruct(data): + tokens = data.split(' ') + + # First three tokens are: 'struct' 'name' '{' + newstruct = Struct(tokens[1]) + + inside = ' '.join(tokens[3:-1]) + + tokens = inside.split(';') + + structs = [] + + for entry in tokens: + entry = NormalizeLine(entry) + if not entry: + continue + + # It's possible that new structs get defined in here + structs.extend(ProcessOneEntry(newstruct, entry)) + + structs.append(newstruct) + return structs + +def GetNextStruct(file): + global line_count + global cppdirect + + got_struct = 0 + + processed_lines = [] + + have_c_comment = 0 + data = '' + while 1: + line = file.readline() + if not line: + break + + line_count += 1 + line = line[:-1] + + if not have_c_comment and re.search(r'/\*', line): + if re.search(r'/\*.*\*/', line): + line = re.sub(r'/\*.*\*/', '', line) + else: + line = re.sub(r'/\*.*$', '', line) + have_c_comment = 1 + + if have_c_comment: + if not re.search(r'\*/', line): + continue + have_c_comment = 0 + line = re.sub(r'^.*\*/', '', line) + + line = NormalizeLine(line) + + if not line: + continue + + if not got_struct: + if re.match(r'#include ["<].*[>"]', line): + cppdirect.append(line) + continue + + if re.match(r'^#(if( |def)|endif)', line): + cppdirect.append(line) + continue + + if re.match(r'^#define', line): + headerdirect.append(line) + continue + + if not re.match(r'^struct %s {$' % _STRUCT_RE, + line, re.IGNORECASE): + print >>sys.stderr, 'Missing struct on line %d: %s' % ( + line_count, line) + sys.exit(1) + else: + got_struct = 1 + data += line + continue + + # We are inside the struct + tokens = line.split('}') + if len(tokens) == 1: + data += ' ' + line + continue + + if len(tokens[1]): + print >>sys.stderr, 'Trailing garbage after struct on line %d' % ( + line_count ) + sys.exit(1) + + # We found the end of the struct + data += ' %s}' % tokens[0] + break + + # Remove any comments, that might be in there + data = re.sub(r'/\*.*\*/', '', data) + + return data + + +def Parse(file): + """ + Parses the input file and returns C code and corresponding header file. + """ + + entities = [] + + while 1: + # Just gets the whole struct nicely formatted + data = GetNextStruct(file) + + if not data: + break + + entities.extend(ProcessStruct(data)) + + return entities + +def GuardName(name): + name = '_'.join(name.split('.')) + name = '_'.join(name.split('/')) + guard = '_'+name.upper()+'_' + + return guard + +def HeaderPreamble(name): + guard = GuardName(name) + pre = ( + '/*\n' + ' * Automatically generated from %s\n' + ' */\n\n' + '#ifndef %s\n' + '#define %s\n\n' ) % ( + name, guard, guard) + + # insert stdint.h - let's hope everyone has it + pre += ( + '#include \n' + '#ifdef _EVENT_HAVE_STDINT_H\n' + '#include \n' + '#endif\n' ) + + for statement in headerdirect: + pre += '%s\n' % statement + if headerdirect: + pre += '\n' + + pre += ( + '#define EVTAG_HAS(msg, member) ((msg)->member##_set == 1)\n' + '#ifdef __GNUC__\n' + '#define EVTAG_ASSIGN(msg, member, args...) ' + '(*(msg)->base->member##_assign)(msg, ## args)\n' + '#define EVTAG_GET(msg, member, args...) ' + '(*(msg)->base->member##_get)(msg, ## args)\n' + '#else\n' + '#define EVTAG_ASSIGN(msg, member, ...) ' + '(*(msg)->base->member##_assign)(msg, ## __VA_ARGS__)\n' + '#define EVTAG_GET(msg, member, ...) ' + '(*(msg)->base->member##_get)(msg, ## __VA_ARGS__)\n' + '#endif\n' + '#define EVTAG_ADD(msg, member) (*(msg)->base->member##_add)(msg)\n' + '#define EVTAG_LEN(msg, member) ((msg)->member##_length)\n' + ) + + return pre + + +def HeaderPostamble(name): + guard = GuardName(name) + return '#endif /* %s */' % guard + +def BodyPreamble(name): + global _NAME + global _VERSION + + header_file = '.'.join(name.split('.')[:-1]) + '.gen.h' + + pre = ( '/*\n' + ' * Automatically generated from %s\n' + ' * by %s/%s. DO NOT EDIT THIS FILE.\n' + ' */\n\n' ) % (name, _NAME, _VERSION) + pre += ( '#include \n' + '#ifdef _EVENT_HAVE_SYS_TIME_H\n' + '#include \n' + '#endif\n' + '#include \n' + '#include \n' + '#include \n' + '#define EVENT_NO_STRUCT\n' + '#include \n\n' + '#ifdef _EVENT___func__\n' + '#define __func__ _EVENT___func__\n' + '#endif\n' ) + + for statement in cppdirect: + pre += '%s\n' % statement + + pre += '\n#include "%s"\n\n' % header_file + + pre += 'void event_err(int eval, const char *fmt, ...);\n' + pre += 'void event_warn(const char *fmt, ...);\n' + pre += 'void event_errx(int eval, const char *fmt, ...);\n' + pre += 'void event_warnx(const char *fmt, ...);\n\n' + + return pre + +def main(argv): + if len(argv) < 2 or not argv[1]: + print >>sys.stderr, 'Need RPC description file as first argument.' + sys.exit(1) + + filename = argv[1] + + ext = filename.split('.')[-1] + if ext != 'rpc': + print >>sys.stderr, 'Unrecognized file extension: %s' % ext + sys.exit(1) + + print >>sys.stderr, 'Reading \"%s\"' % filename + + fp = open(filename, 'r') + entities = Parse(fp) + fp.close() + + header_file = '.'.join(filename.split('.')[:-1]) + '.gen.h' + impl_file = '.'.join(filename.split('.')[:-1]) + '.gen.c' + + print >>sys.stderr, '... creating "%s"' % header_file + header_fp = open(header_file, 'w') + print >>header_fp, HeaderPreamble(filename) + + # Create forward declarations: allows other structs to reference + # each other + for entry in entities: + entry.PrintForwardDeclaration(header_fp) + print >>header_fp, '' + + for entry in entities: + entry.PrintTags(header_fp) + entry.PrintDeclaration(header_fp) + print >>header_fp, HeaderPostamble(filename) + header_fp.close() + + print >>sys.stderr, '... creating "%s"' % impl_file + impl_fp = open(impl_file, 'w') + print >>impl_fp, BodyPreamble(filename) + for entry in entities: + entry.PrintCode(impl_fp) + impl_fp.close() + +if __name__ == '__main__': + main(sys.argv) diff --git a/third_party/libevent/event_tagging.c b/third_party/libevent/event_tagging.c new file mode 100644 index 0000000000..d436e3fd65 --- /dev/null +++ b/third_party/libevent/event_tagging.c @@ -0,0 +1,443 @@ +/* + * Copyright (c) 2003, 2004 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SYS_PARAM_H +#include +#endif + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#undef WIN32_LEAN_AND_MEAN +#else +#include +#endif + +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif + +#include +#include +#include +#include +#ifndef WIN32 +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "event.h" +#include "evutil.h" +#include "log.h" + +int evtag_decode_int(ev_uint32_t *pnumber, struct evbuffer *evbuf); +int evtag_encode_tag(struct evbuffer *evbuf, ev_uint32_t tag); +int evtag_decode_tag(ev_uint32_t *ptag, struct evbuffer *evbuf); + +static struct evbuffer *_buf; /* not thread safe */ + +void +evtag_init(void) +{ + if (_buf != NULL) + return; + + if ((_buf = evbuffer_new()) == NULL) + event_err(1, "%s: malloc", __func__); +} + +/* + * We encode integer's by nibbles; the first nibble contains the number + * of significant nibbles - 1; this allows us to encode up to 64-bit + * integers. This function is byte-order independent. + */ + +void +encode_int(struct evbuffer *evbuf, ev_uint32_t number) +{ + int off = 1, nibbles = 0; + ev_uint8_t data[5]; + + memset(data, 0, sizeof(ev_uint32_t)+1); + while (number) { + if (off & 0x1) + data[off/2] = (data[off/2] & 0xf0) | (number & 0x0f); + else + data[off/2] = (data[off/2] & 0x0f) | + ((number & 0x0f) << 4); + number >>= 4; + off++; + } + + if (off > 2) + nibbles = off - 2; + + /* Off - 1 is the number of encoded nibbles */ + data[0] = (data[0] & 0x0f) | ((nibbles & 0x0f) << 4); + + evbuffer_add(evbuf, data, (off + 1) / 2); +} + +/* + * Support variable length encoding of tags; we use the high bit in each + * octet as a continuation signal. + */ + +int +evtag_encode_tag(struct evbuffer *evbuf, ev_uint32_t tag) +{ + int bytes = 0; + ev_uint8_t data[5]; + + memset(data, 0, sizeof(data)); + do { + ev_uint8_t lower = tag & 0x7f; + tag >>= 7; + + if (tag) + lower |= 0x80; + + data[bytes++] = lower; + } while (tag); + + if (evbuf != NULL) + evbuffer_add(evbuf, data, bytes); + + return (bytes); +} + +static int +decode_tag_internal(ev_uint32_t *ptag, struct evbuffer *evbuf, int dodrain) +{ + ev_uint32_t number = 0; + ev_uint8_t *data = EVBUFFER_DATA(evbuf); + int len = EVBUFFER_LENGTH(evbuf); + int count = 0, shift = 0, done = 0; + + while (count++ < len) { + ev_uint8_t lower = *data++; + number |= (lower & 0x7f) << shift; + shift += 7; + + if (!(lower & 0x80)) { + done = 1; + break; + } + } + + if (!done) + return (-1); + + if (dodrain) + evbuffer_drain(evbuf, count); + + if (ptag != NULL) + *ptag = number; + + return (count); +} + +int +evtag_decode_tag(ev_uint32_t *ptag, struct evbuffer *evbuf) +{ + return (decode_tag_internal(ptag, evbuf, 1 /* dodrain */)); +} + +/* + * Marshal a data type, the general format is as follows: + * + * tag number: one byte; length: var bytes; payload: var bytes + */ + +void +evtag_marshal(struct evbuffer *evbuf, ev_uint32_t tag, + const void *data, ev_uint32_t len) +{ + evtag_encode_tag(evbuf, tag); + encode_int(evbuf, len); + evbuffer_add(evbuf, (void *)data, len); +} + +/* Marshaling for integers */ +void +evtag_marshal_int(struct evbuffer *evbuf, ev_uint32_t tag, ev_uint32_t integer) +{ + evbuffer_drain(_buf, EVBUFFER_LENGTH(_buf)); + encode_int(_buf, integer); + + evtag_encode_tag(evbuf, tag); + encode_int(evbuf, EVBUFFER_LENGTH(_buf)); + evbuffer_add_buffer(evbuf, _buf); +} + +void +evtag_marshal_string(struct evbuffer *buf, ev_uint32_t tag, const char *string) +{ + evtag_marshal(buf, tag, string, strlen(string)); +} + +void +evtag_marshal_timeval(struct evbuffer *evbuf, ev_uint32_t tag, struct timeval *tv) +{ + evbuffer_drain(_buf, EVBUFFER_LENGTH(_buf)); + + encode_int(_buf, tv->tv_sec); + encode_int(_buf, tv->tv_usec); + + evtag_marshal(evbuf, tag, EVBUFFER_DATA(_buf), + EVBUFFER_LENGTH(_buf)); +} + +static int +decode_int_internal(ev_uint32_t *pnumber, struct evbuffer *evbuf, int dodrain) +{ + ev_uint32_t number = 0; + ev_uint8_t *data = EVBUFFER_DATA(evbuf); + int len = EVBUFFER_LENGTH(evbuf); + int nibbles = 0; + + if (!len) + return (-1); + + nibbles = ((data[0] & 0xf0) >> 4) + 1; + if (nibbles > 8 || (nibbles >> 1) + 1 > len) + return (-1); + len = (nibbles >> 1) + 1; + + while (nibbles > 0) { + number <<= 4; + if (nibbles & 0x1) + number |= data[nibbles >> 1] & 0x0f; + else + number |= (data[nibbles >> 1] & 0xf0) >> 4; + nibbles--; + } + + if (dodrain) + evbuffer_drain(evbuf, len); + + *pnumber = number; + + return (len); +} + +int +evtag_decode_int(ev_uint32_t *pnumber, struct evbuffer *evbuf) +{ + return (decode_int_internal(pnumber, evbuf, 1) == -1 ? -1 : 0); +} + +int +evtag_peek(struct evbuffer *evbuf, ev_uint32_t *ptag) +{ + return (decode_tag_internal(ptag, evbuf, 0 /* dodrain */)); +} + +int +evtag_peek_length(struct evbuffer *evbuf, ev_uint32_t *plength) +{ + struct evbuffer tmp; + int res, len; + + len = decode_tag_internal(NULL, evbuf, 0 /* dodrain */); + if (len == -1) + return (-1); + + tmp = *evbuf; + tmp.buffer += len; + tmp.off -= len; + + res = decode_int_internal(plength, &tmp, 0); + if (res == -1) + return (-1); + + *plength += res + len; + + return (0); +} + +int +evtag_payload_length(struct evbuffer *evbuf, ev_uint32_t *plength) +{ + struct evbuffer tmp; + int res, len; + + len = decode_tag_internal(NULL, evbuf, 0 /* dodrain */); + if (len == -1) + return (-1); + + tmp = *evbuf; + tmp.buffer += len; + tmp.off -= len; + + res = decode_int_internal(plength, &tmp, 0); + if (res == -1) + return (-1); + + return (0); +} + +int +evtag_consume(struct evbuffer *evbuf) +{ + ev_uint32_t len; + if (decode_tag_internal(NULL, evbuf, 1 /* dodrain */) == -1) + return (-1); + if (evtag_decode_int(&len, evbuf) == -1) + return (-1); + evbuffer_drain(evbuf, len); + + return (0); +} + +/* Reads the data type from an event buffer */ + +int +evtag_unmarshal(struct evbuffer *src, ev_uint32_t *ptag, struct evbuffer *dst) +{ + ev_uint32_t len; + ev_uint32_t integer; + + if (decode_tag_internal(ptag, src, 1 /* dodrain */) == -1) + return (-1); + if (evtag_decode_int(&integer, src) == -1) + return (-1); + len = integer; + + if (EVBUFFER_LENGTH(src) < len) + return (-1); + + if (evbuffer_add(dst, EVBUFFER_DATA(src), len) == -1) + return (-1); + + evbuffer_drain(src, len); + + return (len); +} + +/* Marshaling for integers */ + +int +evtag_unmarshal_int(struct evbuffer *evbuf, ev_uint32_t need_tag, + ev_uint32_t *pinteger) +{ + ev_uint32_t tag; + ev_uint32_t len; + ev_uint32_t integer; + + if (decode_tag_internal(&tag, evbuf, 1 /* dodrain */) == -1) + return (-1); + if (need_tag != tag) + return (-1); + if (evtag_decode_int(&integer, evbuf) == -1) + return (-1); + len = integer; + + if (EVBUFFER_LENGTH(evbuf) < len) + return (-1); + + evbuffer_drain(_buf, EVBUFFER_LENGTH(_buf)); + if (evbuffer_add(_buf, EVBUFFER_DATA(evbuf), len) == -1) + return (-1); + + evbuffer_drain(evbuf, len); + + return (evtag_decode_int(pinteger, _buf)); +} + +/* Unmarshal a fixed length tag */ + +int +evtag_unmarshal_fixed(struct evbuffer *src, ev_uint32_t need_tag, void *data, + size_t len) +{ + ev_uint32_t tag; + + /* Initialize this event buffer so that we can read into it */ + evbuffer_drain(_buf, EVBUFFER_LENGTH(_buf)); + + /* Now unmarshal a tag and check that it matches the tag we want */ + if (evtag_unmarshal(src, &tag, _buf) == -1 || tag != need_tag) + return (-1); + + if (EVBUFFER_LENGTH(_buf) != len) + return (-1); + + memcpy(data, EVBUFFER_DATA(_buf), len); + return (0); +} + +int +evtag_unmarshal_string(struct evbuffer *evbuf, ev_uint32_t need_tag, + char **pstring) +{ + ev_uint32_t tag; + + evbuffer_drain(_buf, EVBUFFER_LENGTH(_buf)); + + if (evtag_unmarshal(evbuf, &tag, _buf) == -1 || tag != need_tag) + return (-1); + + *pstring = calloc(EVBUFFER_LENGTH(_buf) + 1, 1); + if (*pstring == NULL) + event_err(1, "%s: calloc", __func__); + evbuffer_remove(_buf, *pstring, EVBUFFER_LENGTH(_buf)); + + return (0); +} + +int +evtag_unmarshal_timeval(struct evbuffer *evbuf, ev_uint32_t need_tag, + struct timeval *ptv) +{ + ev_uint32_t tag; + ev_uint32_t integer; + + evbuffer_drain(_buf, EVBUFFER_LENGTH(_buf)); + if (evtag_unmarshal(evbuf, &tag, _buf) == -1 || tag != need_tag) + return (-1); + + if (evtag_decode_int(&integer, _buf) == -1) + return (-1); + ptv->tv_sec = integer; + if (evtag_decode_int(&integer, _buf) == -1) + return (-1); + ptv->tv_usec = integer; + + return (0); +} diff --git a/third_party/libevent/evhttp.h b/third_party/libevent/evhttp.h new file mode 100644 index 0000000000..30dee8bb99 --- /dev/null +++ b/third_party/libevent/evhttp.h @@ -0,0 +1,371 @@ +/* + * Copyright (c) 2000-2004 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef _EVHTTP_H_ +#define _EVHTTP_H_ + +#include "event.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#undef WIN32_LEAN_AND_MEAN +#endif + +/** @file evhttp.h + * + * Basic support for HTTP serving. + * + * As libevent is a library for dealing with event notification and most + * interesting applications are networked today, I have often found the + * need to write HTTP code. The following prototypes and definitions provide + * an application with a minimal interface for making HTTP requests and for + * creating a very simple HTTP server. + */ + +/* Response codes */ +#define HTTP_OK 200 +#define HTTP_NOCONTENT 204 +#define HTTP_MOVEPERM 301 +#define HTTP_MOVETEMP 302 +#define HTTP_NOTMODIFIED 304 +#define HTTP_BADREQUEST 400 +#define HTTP_NOTFOUND 404 +#define HTTP_SERVUNAVAIL 503 + +struct evhttp; +struct evhttp_request; +struct evkeyvalq; + +/** Create a new HTTP server + * + * @param base (optional) the event base to receive the HTTP events + * @return a pointer to a newly initialized evhttp server structure + */ +struct evhttp *evhttp_new(struct event_base *base); + +/** + * Binds an HTTP server on the specified address and port. + * + * Can be called multiple times to bind the same http server + * to multiple different ports. + * + * @param http a pointer to an evhttp object + * @param address a string containing the IP address to listen(2) on + * @param port the port number to listen on + * @return a newly allocated evhttp struct + * @see evhttp_free() + */ +int evhttp_bind_socket(struct evhttp *http, const char *address, u_short port); + +/** + * Makes an HTTP server accept connections on the specified socket + * + * This may be useful to create a socket and then fork multiple instances + * of an http server, or when a socket has been communicated via file + * descriptor passing in situations where an http servers does not have + * permissions to bind to a low-numbered port. + * + * Can be called multiple times to have the http server listen to + * multiple different sockets. + * + * @param http a pointer to an evhttp object + * @param fd a socket fd that is ready for accepting connections + * @return 0 on success, -1 on failure. + * @see evhttp_free(), evhttp_bind_socket() + */ +int evhttp_accept_socket(struct evhttp *http, int fd); + +/** + * Free the previously created HTTP server. + * + * Works only if no requests are currently being served. + * + * @param http the evhttp server object to be freed + * @see evhttp_start() + */ +void evhttp_free(struct evhttp* http); + +/** Set a callback for a specified URI */ +void evhttp_set_cb(struct evhttp *, const char *, + void (*)(struct evhttp_request *, void *), void *); + +/** Removes the callback for a specified URI */ +int evhttp_del_cb(struct evhttp *, const char *); + +/** Set a callback for all requests that are not caught by specific callbacks + */ +void evhttp_set_gencb(struct evhttp *, + void (*)(struct evhttp_request *, void *), void *); + +/** + * Set the timeout for an HTTP request. + * + * @param http an evhttp object + * @param timeout_in_secs the timeout, in seconds + */ +void evhttp_set_timeout(struct evhttp *, int timeout_in_secs); + +/* Request/Response functionality */ + +/** + * Send an HTML error message to the client. + * + * @param req a request object + * @param error the HTTP error code + * @param reason a brief explanation of the error + */ +void evhttp_send_error(struct evhttp_request *req, int error, + const char *reason); + +/** + * Send an HTML reply to the client. + * + * @param req a request object + * @param code the HTTP response code to send + * @param reason a brief message to send with the response code + * @param databuf the body of the response + */ +void evhttp_send_reply(struct evhttp_request *req, int code, + const char *reason, struct evbuffer *databuf); + +/* Low-level response interface, for streaming/chunked replies */ +void evhttp_send_reply_start(struct evhttp_request *, int, const char *); +void evhttp_send_reply_chunk(struct evhttp_request *, struct evbuffer *); +void evhttp_send_reply_end(struct evhttp_request *); + +/** + * Start an HTTP server on the specified address and port + * + * DEPRECATED: it does not allow an event base to be specified + * + * @param address the address to which the HTTP server should be bound + * @param port the port number on which the HTTP server should listen + * @return an struct evhttp object + */ +struct evhttp *evhttp_start(const char *address, u_short port); + +/* + * Interfaces for making requests + */ +enum evhttp_cmd_type { EVHTTP_REQ_GET, EVHTTP_REQ_POST, EVHTTP_REQ_HEAD }; + +enum evhttp_request_kind { EVHTTP_REQUEST, EVHTTP_RESPONSE }; + +/** + * the request structure that a server receives. + * WARNING: expect this structure to change. I will try to provide + * reasonable accessors. + */ +struct evhttp_request { +#if defined(TAILQ_ENTRY) + TAILQ_ENTRY(evhttp_request) next; +#else +struct { + struct evhttp_request *tqe_next; + struct evhttp_request **tqe_prev; +} next; +#endif + + /* the connection object that this request belongs to */ + struct evhttp_connection *evcon; + int flags; +#define EVHTTP_REQ_OWN_CONNECTION 0x0001 +#define EVHTTP_PROXY_REQUEST 0x0002 + + struct evkeyvalq *input_headers; + struct evkeyvalq *output_headers; + + /* address of the remote host and the port connection came from */ + char *remote_host; + u_short remote_port; + + enum evhttp_request_kind kind; + enum evhttp_cmd_type type; + + char *uri; /* uri after HTTP request was parsed */ + + char major; /* HTTP Major number */ + char minor; /* HTTP Minor number */ + + int response_code; /* HTTP Response code */ + char *response_code_line; /* Readable response */ + + struct evbuffer *input_buffer; /* read data */ + ev_int64_t ntoread; + int chunked; + + struct evbuffer *output_buffer; /* outgoing post or data */ + + /* Callback */ + void (*cb)(struct evhttp_request *, void *); + void *cb_arg; + + /* + * Chunked data callback - call for each completed chunk if + * specified. If not specified, all the data is delivered via + * the regular callback. + */ + void (*chunk_cb)(struct evhttp_request *, void *); +}; + +/** + * Creates a new request object that needs to be filled in with the request + * parameters. The callback is executed when the request completed or an + * error occurred. + */ +struct evhttp_request *evhttp_request_new( + void (*cb)(struct evhttp_request *, void *), void *arg); + +/** enable delivery of chunks to requestor */ +void evhttp_request_set_chunked_cb(struct evhttp_request *, + void (*cb)(struct evhttp_request *, void *)); + +/** Frees the request object and removes associated events. */ +void evhttp_request_free(struct evhttp_request *req); + +/** + * A connection object that can be used to for making HTTP requests. The + * connection object tries to establish the connection when it is given an + * http request object. + */ +struct evhttp_connection *evhttp_connection_new( + const char *address, unsigned short port); + +/** Frees an http connection */ +void evhttp_connection_free(struct evhttp_connection *evcon); + +/** sets the ip address from which http connections are made */ +void evhttp_connection_set_local_address(struct evhttp_connection *evcon, + const char *address); + +/** sets the local port from which http connections are made */ +void evhttp_connection_set_local_port(struct evhttp_connection *evcon, + unsigned short port); + +/** Sets the timeout for events related to this connection */ +void evhttp_connection_set_timeout(struct evhttp_connection *evcon, + int timeout_in_secs); + +/** Sets the retry limit for this connection - -1 repeats indefnitely */ +void evhttp_connection_set_retries(struct evhttp_connection *evcon, + int retry_max); + +/** Set a callback for connection close. */ +void evhttp_connection_set_closecb(struct evhttp_connection *evcon, + void (*)(struct evhttp_connection *, void *), void *); + +/** + * Associates an event base with the connection - can only be called + * on a freshly created connection object that has not been used yet. + */ +void evhttp_connection_set_base(struct evhttp_connection *evcon, + struct event_base *base); + +/** Get the remote address and port associated with this connection. */ +void evhttp_connection_get_peer(struct evhttp_connection *evcon, + char **address, u_short *port); + +/** The connection gets ownership of the request */ +int evhttp_make_request(struct evhttp_connection *evcon, + struct evhttp_request *req, + enum evhttp_cmd_type type, const char *uri); + +const char *evhttp_request_uri(struct evhttp_request *req); + +/* Interfaces for dealing with HTTP headers */ + +const char *evhttp_find_header(const struct evkeyvalq *, const char *); +int evhttp_remove_header(struct evkeyvalq *, const char *); +int evhttp_add_header(struct evkeyvalq *, const char *, const char *); +void evhttp_clear_headers(struct evkeyvalq *); + +/* Miscellaneous utility functions */ + + +/** + Helper function to encode a URI. + + The returned string must be freed by the caller. + + @param uri an unencoded URI + @return a newly allocated URI-encoded string + */ +char *evhttp_encode_uri(const char *uri); + + +/** + Helper function to decode a URI. + + The returned string must be freed by the caller. + + @param uri an encoded URI + @return a newly allocated unencoded URI + */ +char *evhttp_decode_uri(const char *uri); + + +/** + * Helper function to parse out arguments in a query. + * + * Parsing a uri like + * + * http://foo.com/?q=test&s=some+thing + * + * will result in two entries in the key value queue. + + * The first entry is: key="q", value="test" + * The second entry is: key="s", value="some thing" + * + * @param uri the request URI + * @param headers the head of the evkeyval queue + */ +void evhttp_parse_query(const char *uri, struct evkeyvalq *headers); + + +/** + * Escape HTML character entities in a string. + * + * Replaces <, >, ", ' and & with <, >, ", + * ' and & correspondingly. + * + * The returned string needs to be freed by the caller. + * + * @param html an unescaped HTML string + * @return an escaped HTML string + */ +char *evhttp_htmlescape(const char *html); + +#ifdef __cplusplus +} +#endif + +#endif /* _EVHTTP_H_ */ diff --git a/third_party/libevent/evport.c b/third_party/libevent/evport.c new file mode 100644 index 0000000000..a2ee1bce78 --- /dev/null +++ b/third_party/libevent/evport.c @@ -0,0 +1,513 @@ +/* + * Submitted by David Pacheco (dp.spambait@gmail.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY SUN MICROSYSTEMS, INC. ``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 SUN MICROSYSTEMS, INC. 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. + */ + +/* + * Copyright (c) 2007 Sun Microsystems. All rights reserved. + * Use is subject to license terms. + */ + +/* + * evport.c: event backend using Solaris 10 event ports. See port_create(3C). + * This implementation is loosely modeled after the one used for select(2) (in + * select.c). + * + * The outstanding events are tracked in a data structure called evport_data. + * Each entry in the ed_fds array corresponds to a file descriptor, and contains + * pointers to the read and write events that correspond to that fd. (That is, + * when the file is readable, the "read" event should handle it, etc.) + * + * evport_add and evport_del update this data structure. evport_dispatch uses it + * to determine where to callback when an event occurs (which it gets from + * port_getn). + * + * Helper functions are used: grow() grows the file descriptor array as + * necessary when large fd's come in. reassociate() takes care of maintaining + * the proper file-descriptor/event-port associations. + * + * As in the select(2) implementation, signals are handled by evsignal. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CHECK_INVARIANTS +#include +#endif + +#include "event.h" +#include "event-internal.h" +#include "log.h" +#include "evsignal.h" + + +/* + * Default value for ed_nevents, which is the maximum file descriptor number we + * can handle. If an event comes in for a file descriptor F > nevents, we will + * grow the array of file descriptors, doubling its size. + */ +#define DEFAULT_NFDS 16 + + +/* + * EVENTS_PER_GETN is the maximum number of events to retrieve from port_getn on + * any particular call. You can speed things up by increasing this, but it will + * (obviously) require more memory. + */ +#define EVENTS_PER_GETN 8 + +/* + * Per-file-descriptor information about what events we're subscribed to. These + * fields are NULL if no event is subscribed to either of them. + */ + +struct fd_info { + struct event* fdi_revt; /* the event responsible for the "read" */ + struct event* fdi_wevt; /* the event responsible for the "write" */ +}; + +#define FDI_HAS_READ(fdi) ((fdi)->fdi_revt != NULL) +#define FDI_HAS_WRITE(fdi) ((fdi)->fdi_wevt != NULL) +#define FDI_HAS_EVENTS(fdi) (FDI_HAS_READ(fdi) || FDI_HAS_WRITE(fdi)) +#define FDI_TO_SYSEVENTS(fdi) (FDI_HAS_READ(fdi) ? POLLIN : 0) | \ + (FDI_HAS_WRITE(fdi) ? POLLOUT : 0) + +struct evport_data { + int ed_port; /* event port for system events */ + int ed_nevents; /* number of allocated fdi's */ + struct fd_info *ed_fds; /* allocated fdi table */ + /* fdi's that we need to reassoc */ + int ed_pending[EVENTS_PER_GETN]; /* fd's with pending events */ +}; + +static void* evport_init (struct event_base *); +static int evport_add (void *, struct event *); +static int evport_del (void *, struct event *); +static int evport_dispatch (struct event_base *, void *, struct timeval *); +static void evport_dealloc (struct event_base *, void *); + +const struct eventop evportops = { + "evport", + evport_init, + evport_add, + evport_del, + evport_dispatch, + evport_dealloc, + 1 /* need reinit */ +}; + +/* + * Initialize the event port implementation. + */ + +static void* +evport_init(struct event_base *base) +{ + struct evport_data *evpd; + int i; + /* + * Disable event ports when this environment variable is set + */ + if (evutil_getenv("EVENT_NOEVPORT")) + return (NULL); + + if (!(evpd = calloc(1, sizeof(struct evport_data)))) + return (NULL); + + if ((evpd->ed_port = port_create()) == -1) { + free(evpd); + return (NULL); + } + + /* + * Initialize file descriptor structure + */ + evpd->ed_fds = calloc(DEFAULT_NFDS, sizeof(struct fd_info)); + if (evpd->ed_fds == NULL) { + close(evpd->ed_port); + free(evpd); + return (NULL); + } + evpd->ed_nevents = DEFAULT_NFDS; + for (i = 0; i < EVENTS_PER_GETN; i++) + evpd->ed_pending[i] = -1; + + evsignal_init(base); + + return (evpd); +} + +#ifdef CHECK_INVARIANTS +/* + * Checks some basic properties about the evport_data structure. Because it + * checks all file descriptors, this function can be expensive when the maximum + * file descriptor ever used is rather large. + */ + +static void +check_evportop(struct evport_data *evpd) +{ + assert(evpd); + assert(evpd->ed_nevents > 0); + assert(evpd->ed_port > 0); + assert(evpd->ed_fds > 0); + + /* + * Verify the integrity of the fd_info struct as well as the events to + * which it points (at least, that they're valid references and correct + * for their position in the structure). + */ + int i; + for (i = 0; i < evpd->ed_nevents; ++i) { + struct event *ev; + struct fd_info *fdi; + + fdi = &evpd->ed_fds[i]; + if ((ev = fdi->fdi_revt) != NULL) { + assert(ev->ev_fd == i); + } + if ((ev = fdi->fdi_wevt) != NULL) { + assert(ev->ev_fd == i); + } + } +} + +/* + * Verifies very basic integrity of a given port_event. + */ +static void +check_event(port_event_t* pevt) +{ + /* + * We've only registered for PORT_SOURCE_FD events. The only + * other thing we can legitimately receive is PORT_SOURCE_ALERT, + * but since we're not using port_alert either, we can assume + * PORT_SOURCE_FD. + */ + assert(pevt->portev_source == PORT_SOURCE_FD); + assert(pevt->portev_user == NULL); +} + +#else +#define check_evportop(epop) +#define check_event(pevt) +#endif /* CHECK_INVARIANTS */ + +/* + * Doubles the size of the allocated file descriptor array. + */ +static int +grow(struct evport_data *epdp, int factor) +{ + struct fd_info *tmp; + int oldsize = epdp->ed_nevents; + int newsize = factor * oldsize; + assert(factor > 1); + + check_evportop(epdp); + + tmp = realloc(epdp->ed_fds, sizeof(struct fd_info) * newsize); + if (NULL == tmp) + return -1; + epdp->ed_fds = tmp; + memset((char*) (epdp->ed_fds + oldsize), 0, + (newsize - oldsize)*sizeof(struct fd_info)); + epdp->ed_nevents = newsize; + + check_evportop(epdp); + + return 0; +} + + +/* + * (Re)associates the given file descriptor with the event port. The OS events + * are specified (implicitly) from the fd_info struct. + */ +static int +reassociate(struct evport_data *epdp, struct fd_info *fdip, int fd) +{ + int sysevents = FDI_TO_SYSEVENTS(fdip); + + if (sysevents != 0) { + if (port_associate(epdp->ed_port, PORT_SOURCE_FD, + fd, sysevents, NULL) == -1) { + event_warn("port_associate"); + return (-1); + } + } + + check_evportop(epdp); + + return (0); +} + +/* + * Main event loop - polls port_getn for some number of events, and processes + * them. + */ + +static int +evport_dispatch(struct event_base *base, void *arg, struct timeval *tv) +{ + int i, res; + struct evport_data *epdp = arg; + port_event_t pevtlist[EVENTS_PER_GETN]; + + /* + * port_getn will block until it has at least nevents events. It will + * also return how many it's given us (which may be more than we asked + * for, as long as it's less than our maximum (EVENTS_PER_GETN)) in + * nevents. + */ + int nevents = 1; + + /* + * We have to convert a struct timeval to a struct timespec + * (only difference is nanoseconds vs. microseconds). If no time-based + * events are active, we should wait for I/O (and tv == NULL). + */ + struct timespec ts; + struct timespec *ts_p = NULL; + if (tv != NULL) { + ts.tv_sec = tv->tv_sec; + ts.tv_nsec = tv->tv_usec * 1000; + ts_p = &ts; + } + + /* + * Before doing anything else, we need to reassociate the events we hit + * last time which need reassociation. See comment at the end of the + * loop below. + */ + for (i = 0; i < EVENTS_PER_GETN; ++i) { + struct fd_info *fdi = NULL; + if (epdp->ed_pending[i] != -1) { + fdi = &(epdp->ed_fds[epdp->ed_pending[i]]); + } + + if (fdi != NULL && FDI_HAS_EVENTS(fdi)) { + int fd = FDI_HAS_READ(fdi) ? fdi->fdi_revt->ev_fd : + fdi->fdi_wevt->ev_fd; + reassociate(epdp, fdi, fd); + epdp->ed_pending[i] = -1; + } + } + + if ((res = port_getn(epdp->ed_port, pevtlist, EVENTS_PER_GETN, + (unsigned int *) &nevents, ts_p)) == -1) { + if (errno == EINTR || errno == EAGAIN) { + evsignal_process(base); + return (0); + } else if (errno == ETIME) { + if (nevents == 0) + return (0); + } else { + event_warn("port_getn"); + return (-1); + } + } else if (base->sig.evsignal_caught) { + evsignal_process(base); + } + + event_debug(("%s: port_getn reports %d events", __func__, nevents)); + + for (i = 0; i < nevents; ++i) { + struct event *ev; + struct fd_info *fdi; + port_event_t *pevt = &pevtlist[i]; + int fd = (int) pevt->portev_object; + + check_evportop(epdp); + check_event(pevt); + epdp->ed_pending[i] = fd; + + /* + * Figure out what kind of event it was + * (because we have to pass this to the callback) + */ + res = 0; + if (pevt->portev_events & POLLIN) + res |= EV_READ; + if (pevt->portev_events & POLLOUT) + res |= EV_WRITE; + + assert(epdp->ed_nevents > fd); + fdi = &(epdp->ed_fds[fd]); + + /* + * We now check for each of the possible events (READ + * or WRITE). Then, we activate the event (which will + * cause its callback to be executed). + */ + + if ((res & EV_READ) && ((ev = fdi->fdi_revt) != NULL)) { + event_active(ev, res, 1); + } + + if ((res & EV_WRITE) && ((ev = fdi->fdi_wevt) != NULL)) { + event_active(ev, res, 1); + } + } /* end of all events gotten */ + + check_evportop(epdp); + + return (0); +} + + +/* + * Adds the given event (so that you will be notified when it happens via + * the callback function). + */ + +static int +evport_add(void *arg, struct event *ev) +{ + struct evport_data *evpd = arg; + struct fd_info *fdi; + int factor; + + check_evportop(evpd); + + /* + * Delegate, if it's not ours to handle. + */ + if (ev->ev_events & EV_SIGNAL) + return (evsignal_add(ev)); + + /* + * If necessary, grow the file descriptor info table + */ + + factor = 1; + while (ev->ev_fd >= factor * evpd->ed_nevents) + factor *= 2; + + if (factor > 1) { + if (-1 == grow(evpd, factor)) { + return (-1); + } + } + + fdi = &evpd->ed_fds[ev->ev_fd]; + if (ev->ev_events & EV_READ) + fdi->fdi_revt = ev; + if (ev->ev_events & EV_WRITE) + fdi->fdi_wevt = ev; + + return reassociate(evpd, fdi, ev->ev_fd); +} + +/* + * Removes the given event from the list of events to wait for. + */ + +static int +evport_del(void *arg, struct event *ev) +{ + struct evport_data *evpd = arg; + struct fd_info *fdi; + int i; + int associated = 1; + + check_evportop(evpd); + + /* + * Delegate, if it's not ours to handle + */ + if (ev->ev_events & EV_SIGNAL) { + return (evsignal_del(ev)); + } + + if (evpd->ed_nevents < ev->ev_fd) { + return (-1); + } + + for (i = 0; i < EVENTS_PER_GETN; ++i) { + if (evpd->ed_pending[i] == ev->ev_fd) { + associated = 0; + break; + } + } + + fdi = &evpd->ed_fds[ev->ev_fd]; + if (ev->ev_events & EV_READ) + fdi->fdi_revt = NULL; + if (ev->ev_events & EV_WRITE) + fdi->fdi_wevt = NULL; + + if (associated) { + if (!FDI_HAS_EVENTS(fdi) && + port_dissociate(evpd->ed_port, PORT_SOURCE_FD, + ev->ev_fd) == -1) { + /* + * Ignre EBADFD error the fd could have been closed + * before event_del() was called. + */ + if (errno != EBADFD) { + event_warn("port_dissociate"); + return (-1); + } + } else { + if (FDI_HAS_EVENTS(fdi)) { + return (reassociate(evpd, fdi, ev->ev_fd)); + } + } + } else { + if (fdi->fdi_revt == NULL && fdi->fdi_wevt == NULL) { + evpd->ed_pending[i] = -1; + } + } + return 0; +} + + +static void +evport_dealloc(struct event_base *base, void *arg) +{ + struct evport_data *evpd = arg; + + evsignal_dealloc(base); + + close(evpd->ed_port); + + if (evpd->ed_fds) + free(evpd->ed_fds); + free(evpd); +} diff --git a/third_party/libevent/evrpc-internal.h b/third_party/libevent/evrpc-internal.h new file mode 100644 index 0000000000..c900f959f9 --- /dev/null +++ b/third_party/libevent/evrpc-internal.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2006 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef _EVRPC_INTERNAL_H_ +#define _EVRPC_INTERNAL_H_ + +#include "http-internal.h" + +struct evrpc; + +#define EVRPC_URI_PREFIX "/.rpc." + +struct evrpc_hook { + TAILQ_ENTRY(evrpc_hook) (next); + + /* returns -1; if the rpc should be aborted, is allowed to rewrite */ + int (*process)(struct evhttp_request *, struct evbuffer *, void *); + void *process_arg; +}; + +TAILQ_HEAD(evrpc_hook_list, evrpc_hook); + +/* + * this is shared between the base and the pool, so that we can reuse + * the hook adding functions; we alias both evrpc_pool and evrpc_base + * to this common structure. + */ +struct _evrpc_hooks { + /* hooks for processing outbound and inbound rpcs */ + struct evrpc_hook_list in_hooks; + struct evrpc_hook_list out_hooks; +}; + +#define input_hooks common.in_hooks +#define output_hooks common.out_hooks + +struct evrpc_base { + struct _evrpc_hooks common; + + /* the HTTP server under which we register our RPC calls */ + struct evhttp* http_server; + + /* a list of all RPCs registered with us */ + TAILQ_HEAD(evrpc_list, evrpc) registered_rpcs; +}; + +struct evrpc_req_generic; +void evrpc_reqstate_free(struct evrpc_req_generic* rpc_state); + +/* A pool for holding evhttp_connection objects */ +struct evrpc_pool { + struct _evrpc_hooks common; + + struct event_base *base; + + struct evconq connections; + + int timeout; + + TAILQ_HEAD(evrpc_requestq, evrpc_request_wrapper) requests; +}; + + +#endif /* _EVRPC_INTERNAL_H_ */ diff --git a/third_party/libevent/evrpc.c b/third_party/libevent/evrpc.c new file mode 100644 index 0000000000..070fd9e710 --- /dev/null +++ b/third_party/libevent/evrpc.c @@ -0,0 +1,657 @@ +/* + * Copyright (c) 2000-2004 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#undef WIN32_LEAN_AND_MEAN +#endif + +#include +#ifndef WIN32 +#include +#endif +#ifdef HAVE_SYS_TIME_H +#include +#endif +#include +#include +#include +#ifndef WIN32 +#include +#endif +#include +#include +#include +#include + +#include "event.h" +#include "evrpc.h" +#include "evrpc-internal.h" +#include "evhttp.h" +#include "evutil.h" +#include "log.h" + +struct evrpc_base * +evrpc_init(struct evhttp *http_server) +{ + struct evrpc_base* base = calloc(1, sizeof(struct evrpc_base)); + if (base == NULL) + return (NULL); + + /* we rely on the tagging sub system */ + evtag_init(); + + TAILQ_INIT(&base->registered_rpcs); + TAILQ_INIT(&base->input_hooks); + TAILQ_INIT(&base->output_hooks); + base->http_server = http_server; + + return (base); +} + +void +evrpc_free(struct evrpc_base *base) +{ + struct evrpc *rpc; + struct evrpc_hook *hook; + + while ((rpc = TAILQ_FIRST(&base->registered_rpcs)) != NULL) { + assert(evrpc_unregister_rpc(base, rpc->uri)); + } + while ((hook = TAILQ_FIRST(&base->input_hooks)) != NULL) { + assert(evrpc_remove_hook(base, EVRPC_INPUT, hook)); + } + while ((hook = TAILQ_FIRST(&base->output_hooks)) != NULL) { + assert(evrpc_remove_hook(base, EVRPC_OUTPUT, hook)); + } + free(base); +} + +void * +evrpc_add_hook(void *vbase, + enum EVRPC_HOOK_TYPE hook_type, + int (*cb)(struct evhttp_request *, struct evbuffer *, void *), + void *cb_arg) +{ + struct _evrpc_hooks *base = vbase; + struct evrpc_hook_list *head = NULL; + struct evrpc_hook *hook = NULL; + switch (hook_type) { + case EVRPC_INPUT: + head = &base->in_hooks; + break; + case EVRPC_OUTPUT: + head = &base->out_hooks; + break; + default: + assert(hook_type == EVRPC_INPUT || hook_type == EVRPC_OUTPUT); + } + + hook = calloc(1, sizeof(struct evrpc_hook)); + assert(hook != NULL); + + hook->process = cb; + hook->process_arg = cb_arg; + TAILQ_INSERT_TAIL(head, hook, next); + + return (hook); +} + +static int +evrpc_remove_hook_internal(struct evrpc_hook_list *head, void *handle) +{ + struct evrpc_hook *hook = NULL; + TAILQ_FOREACH(hook, head, next) { + if (hook == handle) { + TAILQ_REMOVE(head, hook, next); + free(hook); + return (1); + } + } + + return (0); +} + +/* + * remove the hook specified by the handle + */ + +int +evrpc_remove_hook(void *vbase, enum EVRPC_HOOK_TYPE hook_type, void *handle) +{ + struct _evrpc_hooks *base = vbase; + struct evrpc_hook_list *head = NULL; + switch (hook_type) { + case EVRPC_INPUT: + head = &base->in_hooks; + break; + case EVRPC_OUTPUT: + head = &base->out_hooks; + break; + default: + assert(hook_type == EVRPC_INPUT || hook_type == EVRPC_OUTPUT); + } + + return (evrpc_remove_hook_internal(head, handle)); +} + +static int +evrpc_process_hooks(struct evrpc_hook_list *head, + struct evhttp_request *req, struct evbuffer *evbuf) +{ + struct evrpc_hook *hook; + TAILQ_FOREACH(hook, head, next) { + if (hook->process(req, evbuf, hook->process_arg) == -1) + return (-1); + } + + return (0); +} + +static void evrpc_pool_schedule(struct evrpc_pool *pool); +static void evrpc_request_cb(struct evhttp_request *, void *); +void evrpc_request_done(struct evrpc_req_generic*); + +/* + * Registers a new RPC with the HTTP server. The evrpc object is expected + * to have been filled in via the EVRPC_REGISTER_OBJECT macro which in turn + * calls this function. + */ + +static char * +evrpc_construct_uri(const char *uri) +{ + char *constructed_uri; + int constructed_uri_len; + + constructed_uri_len = strlen(EVRPC_URI_PREFIX) + strlen(uri) + 1; + if ((constructed_uri = malloc(constructed_uri_len)) == NULL) + event_err(1, "%s: failed to register rpc at %s", + __func__, uri); + memcpy(constructed_uri, EVRPC_URI_PREFIX, strlen(EVRPC_URI_PREFIX)); + memcpy(constructed_uri + strlen(EVRPC_URI_PREFIX), uri, strlen(uri)); + constructed_uri[constructed_uri_len - 1] = '\0'; + + return (constructed_uri); +} + +int +evrpc_register_rpc(struct evrpc_base *base, struct evrpc *rpc, + void (*cb)(struct evrpc_req_generic *, void *), void *cb_arg) +{ + char *constructed_uri = evrpc_construct_uri(rpc->uri); + + rpc->base = base; + rpc->cb = cb; + rpc->cb_arg = cb_arg; + + TAILQ_INSERT_TAIL(&base->registered_rpcs, rpc, next); + + evhttp_set_cb(base->http_server, + constructed_uri, + evrpc_request_cb, + rpc); + + free(constructed_uri); + + return (0); +} + +int +evrpc_unregister_rpc(struct evrpc_base *base, const char *name) +{ + char *registered_uri = NULL; + struct evrpc *rpc; + + /* find the right rpc; linear search might be slow */ + TAILQ_FOREACH(rpc, &base->registered_rpcs, next) { + if (strcmp(rpc->uri, name) == 0) + break; + } + if (rpc == NULL) { + /* We did not find an RPC with this name */ + return (-1); + } + TAILQ_REMOVE(&base->registered_rpcs, rpc, next); + + free((char *)rpc->uri); + free(rpc); + + registered_uri = evrpc_construct_uri(name); + + /* remove the http server callback */ + assert(evhttp_del_cb(base->http_server, registered_uri) == 0); + + free(registered_uri); + return (0); +} + +static void +evrpc_request_cb(struct evhttp_request *req, void *arg) +{ + struct evrpc *rpc = arg; + struct evrpc_req_generic *rpc_state = NULL; + + /* let's verify the outside parameters */ + if (req->type != EVHTTP_REQ_POST || + EVBUFFER_LENGTH(req->input_buffer) <= 0) + goto error; + + /* + * we might want to allow hooks to suspend the processing, + * but at the moment, we assume that they just act as simple + * filters. + */ + if (evrpc_process_hooks(&rpc->base->input_hooks, + req, req->input_buffer) == -1) + goto error; + + rpc_state = calloc(1, sizeof(struct evrpc_req_generic)); + if (rpc_state == NULL) + goto error; + + /* let's check that we can parse the request */ + rpc_state->request = rpc->request_new(); + if (rpc_state->request == NULL) + goto error; + + rpc_state->rpc = rpc; + + if (rpc->request_unmarshal( + rpc_state->request, req->input_buffer) == -1) { + /* we failed to parse the request; that's a bummer */ + goto error; + } + + /* at this point, we have a well formed request, prepare the reply */ + + rpc_state->reply = rpc->reply_new(); + if (rpc_state->reply == NULL) + goto error; + + rpc_state->http_req = req; + rpc_state->done = evrpc_request_done; + + /* give the rpc to the user; they can deal with it */ + rpc->cb(rpc_state, rpc->cb_arg); + + return; + +error: + evrpc_reqstate_free(rpc_state); + evhttp_send_error(req, HTTP_SERVUNAVAIL, "Service Error"); + return; +} + +void +evrpc_reqstate_free(struct evrpc_req_generic* rpc_state) +{ + /* clean up all memory */ + if (rpc_state != NULL) { + struct evrpc *rpc = rpc_state->rpc; + + if (rpc_state->request != NULL) + rpc->request_free(rpc_state->request); + if (rpc_state->reply != NULL) + rpc->reply_free(rpc_state->reply); + free(rpc_state); + } +} + +void +evrpc_request_done(struct evrpc_req_generic* rpc_state) +{ + struct evhttp_request *req = rpc_state->http_req; + struct evrpc *rpc = rpc_state->rpc; + struct evbuffer* data = NULL; + + if (rpc->reply_complete(rpc_state->reply) == -1) { + /* the reply was not completely filled in. error out */ + goto error; + } + + if ((data = evbuffer_new()) == NULL) { + /* out of memory */ + goto error; + } + + /* serialize the reply */ + rpc->reply_marshal(data, rpc_state->reply); + + /* do hook based tweaks to the request */ + if (evrpc_process_hooks(&rpc->base->output_hooks, + req, data) == -1) + goto error; + + /* on success, we are going to transmit marshaled binary data */ + if (evhttp_find_header(req->output_headers, "Content-Type") == NULL) { + evhttp_add_header(req->output_headers, + "Content-Type", "application/octet-stream"); + } + + evhttp_send_reply(req, HTTP_OK, "OK", data); + + evbuffer_free(data); + + evrpc_reqstate_free(rpc_state); + + return; + +error: + if (data != NULL) + evbuffer_free(data); + evrpc_reqstate_free(rpc_state); + evhttp_send_error(req, HTTP_SERVUNAVAIL, "Service Error"); + return; +} + +/* Client implementation of RPC site */ + +static int evrpc_schedule_request(struct evhttp_connection *connection, + struct evrpc_request_wrapper *ctx); + +struct evrpc_pool * +evrpc_pool_new(struct event_base *base) +{ + struct evrpc_pool *pool = calloc(1, sizeof(struct evrpc_pool)); + if (pool == NULL) + return (NULL); + + TAILQ_INIT(&pool->connections); + TAILQ_INIT(&pool->requests); + + TAILQ_INIT(&pool->input_hooks); + TAILQ_INIT(&pool->output_hooks); + + pool->base = base; + pool->timeout = -1; + + return (pool); +} + +static void +evrpc_request_wrapper_free(struct evrpc_request_wrapper *request) +{ + free(request->name); + free(request); +} + +void +evrpc_pool_free(struct evrpc_pool *pool) +{ + struct evhttp_connection *connection; + struct evrpc_request_wrapper *request; + struct evrpc_hook *hook; + + while ((request = TAILQ_FIRST(&pool->requests)) != NULL) { + TAILQ_REMOVE(&pool->requests, request, next); + /* if this gets more complicated we need our own function */ + evrpc_request_wrapper_free(request); + } + + while ((connection = TAILQ_FIRST(&pool->connections)) != NULL) { + TAILQ_REMOVE(&pool->connections, connection, next); + evhttp_connection_free(connection); + } + + while ((hook = TAILQ_FIRST(&pool->input_hooks)) != NULL) { + assert(evrpc_remove_hook(pool, EVRPC_INPUT, hook)); + } + + while ((hook = TAILQ_FIRST(&pool->output_hooks)) != NULL) { + assert(evrpc_remove_hook(pool, EVRPC_OUTPUT, hook)); + } + + free(pool); +} + +/* + * Add a connection to the RPC pool. A request scheduled on the pool + * may use any available connection. + */ + +void +evrpc_pool_add_connection(struct evrpc_pool *pool, + struct evhttp_connection *connection) { + assert(connection->http_server == NULL); + TAILQ_INSERT_TAIL(&pool->connections, connection, next); + + /* + * associate an event base with this connection + */ + if (pool->base != NULL) + evhttp_connection_set_base(connection, pool->base); + + /* + * unless a timeout was specifically set for a connection, + * the connection inherits the timeout from the pool. + */ + if (connection->timeout == -1) + connection->timeout = pool->timeout; + + /* + * if we have any requests pending, schedule them with the new + * connections. + */ + + if (TAILQ_FIRST(&pool->requests) != NULL) { + struct evrpc_request_wrapper *request = + TAILQ_FIRST(&pool->requests); + TAILQ_REMOVE(&pool->requests, request, next); + evrpc_schedule_request(connection, request); + } +} + +void +evrpc_pool_set_timeout(struct evrpc_pool *pool, int timeout_in_secs) +{ + struct evhttp_connection *evcon; + TAILQ_FOREACH(evcon, &pool->connections, next) { + evcon->timeout = timeout_in_secs; + } + pool->timeout = timeout_in_secs; +} + + +static void evrpc_reply_done(struct evhttp_request *, void *); +static void evrpc_request_timeout(int, short, void *); + +/* + * Finds a connection object associated with the pool that is currently + * idle and can be used to make a request. + */ +static struct evhttp_connection * +evrpc_pool_find_connection(struct evrpc_pool *pool) +{ + struct evhttp_connection *connection; + TAILQ_FOREACH(connection, &pool->connections, next) { + if (TAILQ_FIRST(&connection->requests) == NULL) + return (connection); + } + + return (NULL); +} + +/* + * We assume that the ctx is no longer queued on the pool. + */ +static int +evrpc_schedule_request(struct evhttp_connection *connection, + struct evrpc_request_wrapper *ctx) +{ + struct evhttp_request *req = NULL; + struct evrpc_pool *pool = ctx->pool; + struct evrpc_status status; + char *uri = NULL; + int res = 0; + + if ((req = evhttp_request_new(evrpc_reply_done, ctx)) == NULL) + goto error; + + /* serialize the request data into the output buffer */ + ctx->request_marshal(req->output_buffer, ctx->request); + + uri = evrpc_construct_uri(ctx->name); + if (uri == NULL) + goto error; + + /* we need to know the connection that we might have to abort */ + ctx->evcon = connection; + + /* apply hooks to the outgoing request */ + if (evrpc_process_hooks(&pool->output_hooks, + req, req->output_buffer) == -1) + goto error; + + if (pool->timeout > 0) { + /* + * a timeout after which the whole rpc is going to be aborted. + */ + struct timeval tv; + evutil_timerclear(&tv); + tv.tv_sec = pool->timeout; + evtimer_add(&ctx->ev_timeout, &tv); + } + + /* start the request over the connection */ + res = evhttp_make_request(connection, req, EVHTTP_REQ_POST, uri); + free(uri); + + if (res == -1) + goto error; + + return (0); + +error: + memset(&status, 0, sizeof(status)); + status.error = EVRPC_STATUS_ERR_UNSTARTED; + (*ctx->cb)(&status, ctx->request, ctx->reply, ctx->cb_arg); + evrpc_request_wrapper_free(ctx); + return (-1); +} + +int +evrpc_make_request(struct evrpc_request_wrapper *ctx) +{ + struct evrpc_pool *pool = ctx->pool; + + /* initialize the event structure for this rpc */ + evtimer_set(&ctx->ev_timeout, evrpc_request_timeout, ctx); + if (pool->base != NULL) + event_base_set(pool->base, &ctx->ev_timeout); + + /* we better have some available connections on the pool */ + assert(TAILQ_FIRST(&pool->connections) != NULL); + + /* + * if no connection is available, we queue the request on the pool, + * the next time a connection is empty, the rpc will be send on that. + */ + TAILQ_INSERT_TAIL(&pool->requests, ctx, next); + + evrpc_pool_schedule(pool); + + return (0); +} + +static void +evrpc_reply_done(struct evhttp_request *req, void *arg) +{ + struct evrpc_request_wrapper *ctx = arg; + struct evrpc_pool *pool = ctx->pool; + struct evrpc_status status; + int res = -1; + + /* cancel any timeout we might have scheduled */ + event_del(&ctx->ev_timeout); + + memset(&status, 0, sizeof(status)); + status.http_req = req; + + /* we need to get the reply now */ + if (req != NULL) { + /* apply hooks to the incoming request */ + if (evrpc_process_hooks(&pool->input_hooks, + req, req->input_buffer) == -1) { + status.error = EVRPC_STATUS_ERR_HOOKABORTED; + res = -1; + } else { + res = ctx->reply_unmarshal(ctx->reply, + req->input_buffer); + if (res == -1) { + status.error = EVRPC_STATUS_ERR_BADPAYLOAD; + } + } + } else { + status.error = EVRPC_STATUS_ERR_TIMEOUT; + } + + if (res == -1) { + /* clear everything that we might have written previously */ + ctx->reply_clear(ctx->reply); + } + + (*ctx->cb)(&status, ctx->request, ctx->reply, ctx->cb_arg); + + evrpc_request_wrapper_free(ctx); + + /* the http layer owns the request structure */ + + /* see if we can schedule another request */ + evrpc_pool_schedule(pool); +} + +static void +evrpc_pool_schedule(struct evrpc_pool *pool) +{ + struct evrpc_request_wrapper *ctx = TAILQ_FIRST(&pool->requests); + struct evhttp_connection *evcon; + + /* if no requests are pending, we have no work */ + if (ctx == NULL) + return; + + if ((evcon = evrpc_pool_find_connection(pool)) != NULL) { + TAILQ_REMOVE(&pool->requests, ctx, next); + evrpc_schedule_request(evcon, ctx); + } +} + +static void +evrpc_request_timeout(int fd, short what, void *arg) +{ + struct evrpc_request_wrapper *ctx = arg; + struct evhttp_connection *evcon = ctx->evcon; + assert(evcon != NULL); + + evhttp_connection_fail(evcon, EVCON_HTTP_TIMEOUT); +} diff --git a/third_party/libevent/evrpc.h b/third_party/libevent/evrpc.h new file mode 100644 index 0000000000..7c16b95c77 --- /dev/null +++ b/third_party/libevent/evrpc.h @@ -0,0 +1,486 @@ +/* + * Copyright (c) 2006 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef _EVRPC_H_ +#define _EVRPC_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/** @file evrpc.h + * + * This header files provides basic support for an RPC server and client. + * + * To support RPCs in a server, every supported RPC command needs to be + * defined and registered. + * + * EVRPC_HEADER(SendCommand, Request, Reply); + * + * SendCommand is the name of the RPC command. + * Request is the name of a structure generated by event_rpcgen.py. + * It contains all parameters relating to the SendCommand RPC. The + * server needs to fill in the Reply structure. + * Reply is the name of a structure generated by event_rpcgen.py. It + * contains the answer to the RPC. + * + * To register an RPC with an HTTP server, you need to first create an RPC + * base with: + * + * struct evrpc_base *base = evrpc_init(http); + * + * A specific RPC can then be registered with + * + * EVRPC_REGISTER(base, SendCommand, Request, Reply, FunctionCB, arg); + * + * when the server receives an appropriately formatted RPC, the user callback + * is invokved. The callback needs to fill in the reply structure. + * + * void FunctionCB(EVRPC_STRUCT(SendCommand)* rpc, void *arg); + * + * To send the reply, call EVRPC_REQUEST_DONE(rpc); + * + * See the regression test for an example. + */ + +struct evbuffer; +struct event_base; +struct evrpc_req_generic; + +/* Encapsulates a request */ +struct evrpc { + TAILQ_ENTRY(evrpc) next; + + /* the URI at which the request handler lives */ + const char* uri; + + /* creates a new request structure */ + void *(*request_new)(void); + + /* frees the request structure */ + void (*request_free)(void *); + + /* unmarshals the buffer into the proper request structure */ + int (*request_unmarshal)(void *, struct evbuffer *); + + /* creates a new reply structure */ + void *(*reply_new)(void); + + /* creates a new reply structure */ + void (*reply_free)(void *); + + /* verifies that the reply is valid */ + int (*reply_complete)(void *); + + /* marshals the reply into a buffer */ + void (*reply_marshal)(struct evbuffer*, void *); + + /* the callback invoked for each received rpc */ + void (*cb)(struct evrpc_req_generic *, void *); + void *cb_arg; + + /* reference for further configuration */ + struct evrpc_base *base; +}; + +/** The type of a specific RPC Message + * + * @param rpcname the name of the RPC message + */ +#define EVRPC_STRUCT(rpcname) struct evrpc_req__##rpcname + +struct evhttp_request; +struct evrpc_status; + +/* We alias the RPC specific structs to this voided one */ +struct evrpc_req_generic { + /* the unmarshaled request object */ + void *request; + + /* the empty reply object that needs to be filled in */ + void *reply; + + /* + * the static structure for this rpc; that can be used to + * automatically unmarshal and marshal the http buffers. + */ + struct evrpc *rpc; + + /* + * the http request structure on which we need to answer. + */ + struct evhttp_request* http_req; + + /* + * callback to reply and finish answering this rpc + */ + void (*done)(struct evrpc_req_generic* rpc); +}; + +/** Creates the definitions and prototypes for an RPC + * + * You need to use EVRPC_HEADER to create structures and function prototypes + * needed by the server and client implementation. The structures have to be + * defined in an .rpc file and converted to source code via event_rpcgen.py + * + * @param rpcname the name of the RPC + * @param reqstruct the name of the RPC request structure + * @param replystruct the name of the RPC reply structure + * @see EVRPC_GENERATE() + */ +#define EVRPC_HEADER(rpcname, reqstruct, rplystruct) \ +EVRPC_STRUCT(rpcname) { \ + struct reqstruct* request; \ + struct rplystruct* reply; \ + struct evrpc* rpc; \ + struct evhttp_request* http_req; \ + void (*done)(struct evrpc_status *, \ + struct evrpc* rpc, void *request, void *reply); \ +}; \ +int evrpc_send_request_##rpcname(struct evrpc_pool *, \ + struct reqstruct *, struct rplystruct *, \ + void (*)(struct evrpc_status *, \ + struct reqstruct *, struct rplystruct *, void *cbarg), \ + void *); + +/** Generates the code for receiving and sending an RPC message + * + * EVRPC_GENERATE is used to create the code corresponding to sending + * and receiving a particular RPC message + * + * @param rpcname the name of the RPC + * @param reqstruct the name of the RPC request structure + * @param replystruct the name of the RPC reply structure + * @see EVRPC_HEADER() + */ +#define EVRPC_GENERATE(rpcname, reqstruct, rplystruct) \ +int evrpc_send_request_##rpcname(struct evrpc_pool *pool, \ + struct reqstruct *request, struct rplystruct *reply, \ + void (*cb)(struct evrpc_status *, \ + struct reqstruct *, struct rplystruct *, void *cbarg), \ + void *cbarg) { \ + struct evrpc_status status; \ + struct evrpc_request_wrapper *ctx; \ + ctx = (struct evrpc_request_wrapper *) \ + malloc(sizeof(struct evrpc_request_wrapper)); \ + if (ctx == NULL) \ + goto error; \ + ctx->pool = pool; \ + ctx->evcon = NULL; \ + ctx->name = strdup(#rpcname); \ + if (ctx->name == NULL) { \ + free(ctx); \ + goto error; \ + } \ + ctx->cb = (void (*)(struct evrpc_status *, \ + void *, void *, void *))cb; \ + ctx->cb_arg = cbarg; \ + ctx->request = (void *)request; \ + ctx->reply = (void *)reply; \ + ctx->request_marshal = (void (*)(struct evbuffer *, void *))reqstruct##_marshal; \ + ctx->reply_clear = (void (*)(void *))rplystruct##_clear; \ + ctx->reply_unmarshal = (int (*)(void *, struct evbuffer *))rplystruct##_unmarshal; \ + return (evrpc_make_request(ctx)); \ +error: \ + memset(&status, 0, sizeof(status)); \ + status.error = EVRPC_STATUS_ERR_UNSTARTED; \ + (*(cb))(&status, request, reply, cbarg); \ + return (-1); \ +} + +/** Provides access to the HTTP request object underlying an RPC + * + * Access to the underlying http object; can be used to look at headers or + * for getting the remote ip address + * + * @param rpc_req the rpc request structure provided to the server callback + * @return an struct evhttp_request object that can be inspected for + * HTTP headers or sender information. + */ +#define EVRPC_REQUEST_HTTP(rpc_req) (rpc_req)->http_req + +/** Creates the reply to an RPC request + * + * EVRPC_REQUEST_DONE is used to answer a request; the reply is expected + * to have been filled in. The request and reply pointers become invalid + * after this call has finished. + * + * @param rpc_req the rpc request structure provided to the server callback + */ +#define EVRPC_REQUEST_DONE(rpc_req) do { \ + struct evrpc_req_generic *_req = (struct evrpc_req_generic *)(rpc_req); \ + _req->done(_req); \ +} while (0) + + +/* Takes a request object and fills it in with the right magic */ +#define EVRPC_REGISTER_OBJECT(rpc, name, request, reply) \ + do { \ + (rpc)->uri = strdup(#name); \ + if ((rpc)->uri == NULL) { \ + fprintf(stderr, "failed to register object\n"); \ + exit(1); \ + } \ + (rpc)->request_new = (void *(*)(void))request##_new; \ + (rpc)->request_free = (void (*)(void *))request##_free; \ + (rpc)->request_unmarshal = (int (*)(void *, struct evbuffer *))request##_unmarshal; \ + (rpc)->reply_new = (void *(*)(void))reply##_new; \ + (rpc)->reply_free = (void (*)(void *))reply##_free; \ + (rpc)->reply_complete = (int (*)(void *))reply##_complete; \ + (rpc)->reply_marshal = (void (*)(struct evbuffer*, void *))reply##_marshal; \ + } while (0) + +struct evrpc_base; +struct evhttp; + +/* functions to start up the rpc system */ + +/** Creates a new rpc base from which RPC requests can be received + * + * @param server a pointer to an existing HTTP server + * @return a newly allocated evrpc_base struct + * @see evrpc_free() + */ +struct evrpc_base *evrpc_init(struct evhttp *server); + +/** + * Frees the evrpc base + * + * For now, you are responsible for making sure that no rpcs are ongoing. + * + * @param base the evrpc_base object to be freed + * @see evrpc_init + */ +void evrpc_free(struct evrpc_base *base); + +/** register RPCs with the HTTP Server + * + * registers a new RPC with the HTTP server, each RPC needs to have + * a unique name under which it can be identified. + * + * @param base the evrpc_base structure in which the RPC should be + * registered. + * @param name the name of the RPC + * @param request the name of the RPC request structure + * @param reply the name of the RPC reply structure + * @param callback the callback that should be invoked when the RPC + * is received. The callback has the following prototype + * void (*callback)(EVRPC_STRUCT(Message)* rpc, void *arg) + * @param cbarg an additional parameter that can be passed to the callback. + * The parameter can be used to carry around state. + */ +#define EVRPC_REGISTER(base, name, request, reply, callback, cbarg) \ + do { \ + struct evrpc* rpc = (struct evrpc *)calloc(1, sizeof(struct evrpc)); \ + EVRPC_REGISTER_OBJECT(rpc, name, request, reply); \ + evrpc_register_rpc(base, rpc, \ + (void (*)(struct evrpc_req_generic*, void *))callback, cbarg); \ + } while (0) + +int evrpc_register_rpc(struct evrpc_base *, struct evrpc *, + void (*)(struct evrpc_req_generic*, void *), void *); + +/** + * Unregisters an already registered RPC + * + * @param base the evrpc_base object from which to unregister an RPC + * @param name the name of the rpc to unregister + * @return -1 on error or 0 when successful. + * @see EVRPC_REGISTER() + */ +#define EVRPC_UNREGISTER(base, name) evrpc_unregister_rpc(base, #name) + +int evrpc_unregister_rpc(struct evrpc_base *base, const char *name); + +/* + * Client-side RPC support + */ + +struct evrpc_pool; +struct evhttp_connection; + +/** + * provides information about the completed RPC request. + */ +struct evrpc_status { +#define EVRPC_STATUS_ERR_NONE 0 +#define EVRPC_STATUS_ERR_TIMEOUT 1 +#define EVRPC_STATUS_ERR_BADPAYLOAD 2 +#define EVRPC_STATUS_ERR_UNSTARTED 3 +#define EVRPC_STATUS_ERR_HOOKABORTED 4 + int error; + + /* for looking at headers or other information */ + struct evhttp_request *http_req; +}; + +struct evrpc_request_wrapper { + TAILQ_ENTRY(evrpc_request_wrapper) next; + + /* pool on which this rpc request is being made */ + struct evrpc_pool *pool; + + /* connection on which the request is being sent */ + struct evhttp_connection *evcon; + + /* event for implementing request timeouts */ + struct event ev_timeout; + + /* the name of the rpc */ + char *name; + + /* callback */ + void (*cb)(struct evrpc_status*, void *request, void *reply, void *arg); + void *cb_arg; + + void *request; + void *reply; + + /* unmarshals the buffer into the proper request structure */ + void (*request_marshal)(struct evbuffer *, void *); + + /* removes all stored state in the reply */ + void (*reply_clear)(void *); + + /* marshals the reply into a buffer */ + int (*reply_unmarshal)(void *, struct evbuffer*); +}; + +/** launches an RPC and sends it to the server + * + * EVRPC_MAKE_REQUEST() is used by the client to send an RPC to the server. + * + * @param name the name of the RPC + * @param pool the evrpc_pool that contains the connection objects over which + * the request should be sent. + * @param request a pointer to the RPC request structure - it contains the + * data to be sent to the server. + * @param reply a pointer to the RPC reply structure. It is going to be filled + * if the request was answered successfully + * @param cb the callback to invoke when the RPC request has been answered + * @param cbarg an additional argument to be passed to the client + * @return 0 on success, -1 on failure + */ +#define EVRPC_MAKE_REQUEST(name, pool, request, reply, cb, cbarg) \ + evrpc_send_request_##name(pool, request, reply, cb, cbarg) + +int evrpc_make_request(struct evrpc_request_wrapper *); + +/** creates an rpc connection pool + * + * a pool has a number of connections associated with it. + * rpc requests are always made via a pool. + * + * @param base a pointer to an struct event_based object; can be left NULL + * in singled-threaded applications + * @return a newly allocated struct evrpc_pool object + * @see evrpc_pool_free() + */ +struct evrpc_pool *evrpc_pool_new(struct event_base *base); +/** frees an rpc connection pool + * + * @param pool a pointer to an evrpc_pool allocated via evrpc_pool_new() + * @see evrpc_pool_new() + */ +void evrpc_pool_free(struct evrpc_pool *pool); +/* + * adds a connection over which rpc can be dispatched. the connection + * object must have been newly created. + */ +void evrpc_pool_add_connection(struct evrpc_pool *, + struct evhttp_connection *); + +/** + * Sets the timeout in secs after which a request has to complete. The + * RPC is completely aborted if it does not complete by then. Setting + * the timeout to 0 means that it never timeouts and can be used to + * implement callback type RPCs. + * + * Any connection already in the pool will be updated with the new + * timeout. Connections added to the pool after set_timeout has be + * called receive the pool timeout only if no timeout has been set + * for the connection itself. + * + * @param pool a pointer to a struct evrpc_pool object + * @param timeout_in_secs the number of seconds after which a request should + * timeout and a failure be returned to the callback. + */ +void evrpc_pool_set_timeout(struct evrpc_pool *pool, int timeout_in_secs); + +/** + * Hooks for changing the input and output of RPCs; this can be used to + * implement compression, authentication, encryption, ... + */ + +enum EVRPC_HOOK_TYPE { + EVRPC_INPUT, /**< apply the function to an input hook */ + EVRPC_OUTPUT /**< apply the function to an output hook */ +}; + +#ifndef WIN32 +/** Deprecated alias for EVRPC_INPUT. Not available on windows, where it + * conflicts with platform headers. */ +#define INPUT EVRPC_INPUT +/** Deprecated alias for EVRPC_OUTPUT. Not available on windows, where it + * conflicts with platform headers. */ +#define OUTPUT EVRPC_OUTPUT +#endif + +/** adds a processing hook to either an rpc base or rpc pool + * + * If a hook returns -1, the processing is aborted. + * + * The add functions return handles that can be used for removing hooks. + * + * @param vbase a pointer to either struct evrpc_base or struct evrpc_pool + * @param hook_type either INPUT or OUTPUT + * @param cb the callback to call when the hook is activated + * @param cb_arg an additional argument for the callback + * @return a handle to the hook so it can be removed later + * @see evrpc_remove_hook() + */ +void *evrpc_add_hook(void *vbase, + enum EVRPC_HOOK_TYPE hook_type, + int (*cb)(struct evhttp_request *, struct evbuffer *, void *), + void *cb_arg); + +/** removes a previously added hook + * + * @param vbase a pointer to either struct evrpc_base or struct evrpc_pool + * @param hook_type either INPUT or OUTPUT + * @param handle a handle returned by evrpc_add_hook() + * @return 1 on success or 0 on failure + * @see evrpc_add_hook() + */ +int evrpc_remove_hook(void *vbase, + enum EVRPC_HOOK_TYPE hook_type, + void *handle); + +#ifdef __cplusplus +} +#endif + +#endif /* _EVRPC_H_ */ diff --git a/third_party/libevent/evsignal.h b/third_party/libevent/evsignal.h new file mode 100644 index 0000000000..076cd8dae3 --- /dev/null +++ b/third_party/libevent/evsignal.h @@ -0,0 +1,52 @@ +/* + * Copyright 2000-2002 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef _EVSIGNAL_H_ +#define _EVSIGNAL_H_ + +typedef void (*ev_sighandler_t)(int); + +struct evsignal_info { + struct event ev_signal; + int ev_signal_pair[2]; + int ev_signal_added; + volatile sig_atomic_t evsignal_caught; + struct event_list evsigevents[NSIG]; + sig_atomic_t evsigcaught[NSIG]; +#ifdef HAVE_SIGACTION + struct sigaction **sh_old; +#else + ev_sighandler_t **sh_old; +#endif + int sh_old_max; +}; +int evsignal_init(struct event_base *); +void evsignal_process(struct event_base *); +int evsignal_add(struct event *); +int evsignal_del(struct event *); +void evsignal_dealloc(struct event_base *); + +#endif /* _EVSIGNAL_H_ */ diff --git a/third_party/libevent/evutil.c b/third_party/libevent/evutil.c new file mode 100644 index 0000000000..564377d70c --- /dev/null +++ b/third_party/libevent/evutil.c @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2007 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef WIN32 +#include +#define WIN32_LEAN_AND_MEAN +#include +#undef WIN32_LEAN_AND_MEAN +#endif + +#include +#ifdef HAVE_SYS_SOCKET_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#include +#if defined WIN32 && !defined(HAVE_GETTIMEOFDAY_H) +#include +#endif +#include +#include + +#include +#include "event.h" +#include "event-internal.h" +#include "evutil.h" +#include "log.h" + +int +evutil_socketpair(int family, int type, int protocol, int fd[2]) +{ +#ifndef WIN32 + return socketpair(family, type, protocol, fd); +#else + /* This code is originally from Tor. Used with permission. */ + + /* This socketpair does not work when localhost is down. So + * it's really not the same thing at all. But it's close enough + * for now, and really, when localhost is down sometimes, we + * have other problems too. + */ + int listener = -1; + int connector = -1; + int acceptor = -1; + struct sockaddr_in listen_addr; + struct sockaddr_in connect_addr; + int size; + int saved_errno = -1; + + if (protocol +#ifdef AF_UNIX + || family != AF_UNIX +#endif + ) { + EVUTIL_SET_SOCKET_ERROR(WSAEAFNOSUPPORT); + return -1; + } + if (!fd) { + EVUTIL_SET_SOCKET_ERROR(WSAEINVAL); + return -1; + } + + listener = socket(AF_INET, type, 0); + if (listener < 0) + return -1; + memset(&listen_addr, 0, sizeof(listen_addr)); + listen_addr.sin_family = AF_INET; + listen_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + listen_addr.sin_port = 0; /* kernel chooses port. */ + if (bind(listener, (struct sockaddr *) &listen_addr, sizeof (listen_addr)) + == -1) + goto tidy_up_and_fail; + if (listen(listener, 1) == -1) + goto tidy_up_and_fail; + + connector = socket(AF_INET, type, 0); + if (connector < 0) + goto tidy_up_and_fail; + /* We want to find out the port number to connect to. */ + size = sizeof(connect_addr); + if (getsockname(listener, (struct sockaddr *) &connect_addr, &size) == -1) + goto tidy_up_and_fail; + if (size != sizeof (connect_addr)) + goto abort_tidy_up_and_fail; + if (connect(connector, (struct sockaddr *) &connect_addr, + sizeof(connect_addr)) == -1) + goto tidy_up_and_fail; + + size = sizeof(listen_addr); + acceptor = accept(listener, (struct sockaddr *) &listen_addr, &size); + if (acceptor < 0) + goto tidy_up_and_fail; + if (size != sizeof(listen_addr)) + goto abort_tidy_up_and_fail; + EVUTIL_CLOSESOCKET(listener); + /* Now check we are talking to ourself by matching port and host on the + two sockets. */ + if (getsockname(connector, (struct sockaddr *) &connect_addr, &size) == -1) + goto tidy_up_and_fail; + if (size != sizeof (connect_addr) + || listen_addr.sin_family != connect_addr.sin_family + || listen_addr.sin_addr.s_addr != connect_addr.sin_addr.s_addr + || listen_addr.sin_port != connect_addr.sin_port) + goto abort_tidy_up_and_fail; + fd[0] = connector; + fd[1] = acceptor; + + return 0; + + abort_tidy_up_and_fail: + saved_errno = WSAECONNABORTED; + tidy_up_and_fail: + if (saved_errno < 0) + saved_errno = WSAGetLastError(); + if (listener != -1) + EVUTIL_CLOSESOCKET(listener); + if (connector != -1) + EVUTIL_CLOSESOCKET(connector); + if (acceptor != -1) + EVUTIL_CLOSESOCKET(acceptor); + + EVUTIL_SET_SOCKET_ERROR(saved_errno); + return -1; +#endif +} + +int +evutil_make_socket_nonblocking(int fd) +{ +#ifdef WIN32 + { + unsigned long nonblocking = 1; + ioctlsocket(fd, FIONBIO, (unsigned long*) &nonblocking); + } +#else + if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) { + event_warn("fcntl(O_NONBLOCK)"); + return -1; +} +#endif + return 0; +} + +ev_int64_t +evutil_strtoll(const char *s, char **endptr, int base) +{ +#ifdef HAVE_STRTOLL + return (ev_int64_t)strtoll(s, endptr, base); +#elif SIZEOF_LONG == 8 + return (ev_int64_t)strtol(s, endptr, base); +#elif defined(WIN32) && defined(_MSC_VER) && _MSC_VER < 1300 + /* XXXX on old versions of MS APIs, we only support base + * 10. */ + ev_int64_t r; + if (base != 10) + return 0; + r = (ev_int64_t) _atoi64(s); + while (isspace(*s)) + ++s; + while (isdigit(*s)) + ++s; + if (endptr) + *endptr = (char*) s; + return r; +#elif defined(WIN32) + return (ev_int64_t) _strtoi64(s, endptr, base); +#else +#error "I don't know how to parse 64-bit integers." +#endif +} + +#ifndef _EVENT_HAVE_GETTIMEOFDAY +int +evutil_gettimeofday(struct timeval *tv, struct timezone *tz) +{ + struct _timeb tb; + + if(tv == NULL) + return -1; + + _ftime(&tb); + tv->tv_sec = (long) tb.time; + tv->tv_usec = ((int) tb.millitm) * 1000; + return 0; +} +#endif + +int +evutil_snprintf(char *buf, size_t buflen, const char *format, ...) +{ + int r; + va_list ap; + va_start(ap, format); + r = evutil_vsnprintf(buf, buflen, format, ap); + va_end(ap); + return r; +} + +int +evutil_vsnprintf(char *buf, size_t buflen, const char *format, va_list ap) +{ +#ifdef _MSC_VER + int r = _vsnprintf(buf, buflen, format, ap); + buf[buflen-1] = '\0'; + if (r >= 0) + return r; + else + return _vscprintf(format, ap); +#else + int r = vsnprintf(buf, buflen, format, ap); + buf[buflen-1] = '\0'; + return r; +#endif +} + +static int +evutil_issetugid(void) +{ +#ifdef _EVENT_HAVE_ISSETUGID + return issetugid(); +#else + +#ifdef _EVENT_HAVE_GETEUID + if (getuid() != geteuid()) + return 1; +#endif +#ifdef _EVENT_HAVE_GETEGID + if (getgid() != getegid()) + return 1; +#endif + return 0; +#endif +} + +const char * +evutil_getenv(const char *varname) +{ + if (evutil_issetugid()) + return NULL; + + return getenv(varname); +} diff --git a/third_party/libevent/evutil.h b/third_party/libevent/evutil.h new file mode 100644 index 0000000000..8b664b9424 --- /dev/null +++ b/third_party/libevent/evutil.h @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2007 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef _EVUTIL_H_ +#define _EVUTIL_H_ + +/** @file evutil.h + + Common convenience functions for cross-platform portability and + related socket manipulations. + + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "event-config.h" +#ifdef _EVENT_HAVE_SYS_TIME_H +#include +#endif +#ifdef _EVENT_HAVE_STDINT_H +#include +#elif defined(_EVENT_HAVE_INTTYPES_H) +#include +#endif +#ifdef _EVENT_HAVE_SYS_TYPES_H +#include +#endif +#include + +#ifdef _EVENT_HAVE_UINT64_T +#define ev_uint64_t uint64_t +#define ev_int64_t int64_t +#elif defined(WIN32) +#define ev_uint64_t unsigned __int64 +#define ev_int64_t signed __int64 +#elif _EVENT_SIZEOF_LONG_LONG == 8 +#define ev_uint64_t unsigned long long +#define ev_int64_t long long +#elif _EVENT_SIZEOF_LONG == 8 +#define ev_uint64_t unsigned long +#define ev_int64_t long +#else +#error "No way to define ev_uint64_t" +#endif + +#ifdef _EVENT_HAVE_UINT32_T +#define ev_uint32_t uint32_t +#elif defined(WIN32) +#define ev_uint32_t unsigned int +#elif _EVENT_SIZEOF_LONG == 4 +#define ev_uint32_t unsigned long +#elif _EVENT_SIZEOF_INT == 4 +#define ev_uint32_t unsigned int +#else +#error "No way to define ev_uint32_t" +#endif + +#ifdef _EVENT_HAVE_UINT16_T +#define ev_uint16_t uint16_t +#elif defined(WIN32) +#define ev_uint16_t unsigned short +#elif _EVENT_SIZEOF_INT == 2 +#define ev_uint16_t unsigned int +#elif _EVENT_SIZEOF_SHORT == 2 +#define ev_uint16_t unsigned short +#else +#error "No way to define ev_uint16_t" +#endif + +#ifdef _EVENT_HAVE_UINT8_T +#define ev_uint8_t uint8_t +#else +#define ev_uint8_t unsigned char +#endif + +int evutil_socketpair(int d, int type, int protocol, int sv[2]); +int evutil_make_socket_nonblocking(int sock); +#ifdef WIN32 +#define EVUTIL_CLOSESOCKET(s) closesocket(s) +#else +#define EVUTIL_CLOSESOCKET(s) close(s) +#endif + +#ifdef WIN32 +#define EVUTIL_SOCKET_ERROR() WSAGetLastError() +#define EVUTIL_SET_SOCKET_ERROR(errcode) \ + do { WSASetLastError(errcode); } while (0) +#else +#define EVUTIL_SOCKET_ERROR() (errno) +#define EVUTIL_SET_SOCKET_ERROR(errcode) \ + do { errno = (errcode); } while (0) +#endif + +/* + * Manipulation functions for struct timeval + */ +#ifdef _EVENT_HAVE_TIMERADD +#define evutil_timeradd(tvp, uvp, vvp) timeradd((tvp), (uvp), (vvp)) +#define evutil_timersub(tvp, uvp, vvp) timersub((tvp), (uvp), (vvp)) +#else +#define evutil_timeradd(tvp, uvp, vvp) \ + do { \ + (vvp)->tv_sec = (tvp)->tv_sec + (uvp)->tv_sec; \ + (vvp)->tv_usec = (tvp)->tv_usec + (uvp)->tv_usec; \ + if ((vvp)->tv_usec >= 1000000) { \ + (vvp)->tv_sec++; \ + (vvp)->tv_usec -= 1000000; \ + } \ + } while (0) +#define evutil_timersub(tvp, uvp, vvp) \ + do { \ + (vvp)->tv_sec = (tvp)->tv_sec - (uvp)->tv_sec; \ + (vvp)->tv_usec = (tvp)->tv_usec - (uvp)->tv_usec; \ + if ((vvp)->tv_usec < 0) { \ + (vvp)->tv_sec--; \ + (vvp)->tv_usec += 1000000; \ + } \ + } while (0) +#endif /* !_EVENT_HAVE_HAVE_TIMERADD */ + +#ifdef _EVENT_HAVE_TIMERCLEAR +#define evutil_timerclear(tvp) timerclear(tvp) +#else +#define evutil_timerclear(tvp) (tvp)->tv_sec = (tvp)->tv_usec = 0 +#endif + +#define evutil_timercmp(tvp, uvp, cmp) \ + (((tvp)->tv_sec == (uvp)->tv_sec) ? \ + ((tvp)->tv_usec cmp (uvp)->tv_usec) : \ + ((tvp)->tv_sec cmp (uvp)->tv_sec)) + +#ifdef _EVENT_HAVE_TIMERISSET +#define evutil_timerisset(tvp) timerisset(tvp) +#else +#define evutil_timerisset(tvp) ((tvp)->tv_sec || (tvp)->tv_usec) +#endif + + +/* big-int related functions */ +ev_int64_t evutil_strtoll(const char *s, char **endptr, int base); + + +#ifdef _EVENT_HAVE_GETTIMEOFDAY +#define evutil_gettimeofday(tv, tz) gettimeofday((tv), (tz)) +#else +struct timezone; +int evutil_gettimeofday(struct timeval *tv, struct timezone *tz); +#endif + +int evutil_snprintf(char *buf, size_t buflen, const char *format, ...) +#ifdef __GNUC__ + __attribute__((format(printf, 3, 4))) +#endif + ; +int evutil_vsnprintf(char *buf, size_t buflen, const char *format, va_list ap); + +#ifdef __cplusplus +} +#endif + +#endif /* _EVUTIL_H_ */ diff --git a/third_party/libevent/freebsd/event-config.h b/third_party/libevent/freebsd/event-config.h new file mode 100644 index 0000000000..662abc871e --- /dev/null +++ b/third_party/libevent/freebsd/event-config.h @@ -0,0 +1,274 @@ +/* event-config.h + * Generated by autoconf; post-processed by libevent. + * Do not edit this file. + * Do not rely on macros in this file existing in later versions. + */ +#ifndef _EVENT_CONFIG_H_ +#define _EVENT_CONFIG_H_ +/* config.h. Generated from config.h.in by configure. */ +/* config.h.in. Generated from configure.in by autoheader. */ + +/* Define if clock_gettime is available in libc */ +#define _EVENT_DNS_USE_CPU_CLOCK_FOR_ID 1 + +/* Define is no secure id variant is available */ +/* #undef _EVENT_DNS_USE_GETTIMEOFDAY_FOR_ID */ + +/* Define to 1 if you have the `clock_gettime' function. */ +#define _EVENT_HAVE_CLOCK_GETTIME 1 + +/* Define if /dev/poll is available */ +/* #undef _EVENT_HAVE_DEVPOLL */ + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_DLFCN_H 1 + +/* Define if your system supports the epoll system calls */ +/* #undef _EVENT_HAVE_EPOLL */ + +/* Define to 1 if you have the `epoll_ctl' function. */ +/* #undef _EVENT_HAVE_EPOLL_CTL */ + +/* Define if your system supports event ports */ +/* #undef _EVENT_HAVE_EVENT_PORTS */ + +/* Define to 1 if you have the `fcntl' function. */ +#define _EVENT_HAVE_FCNTL 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_FCNTL_H 1 + +/* Define to 1 if the system has the type `fd_mask'. */ +#define _EVENT_HAVE_FD_MASK 1 + +/* Define to 1 if you have the `getaddrinfo' function. */ +#define _EVENT_HAVE_GETADDRINFO 1 + +/* Define to 1 if you have the `getegid' function. */ +#define _EVENT_HAVE_GETEGID 1 + +/* Define to 1 if you have the `geteuid' function. */ +#define _EVENT_HAVE_GETEUID 1 + +/* Define to 1 if you have the `getnameinfo' function. */ +#define _EVENT_HAVE_GETNAMEINFO 1 + +/* Define to 1 if you have the `gettimeofday' function. */ +#define _EVENT_HAVE_GETTIMEOFDAY 1 + +/* Define to 1 if you have the `inet_ntop' function. */ +#define _EVENT_HAVE_INET_NTOP 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the `issetugid' function. */ +#define _EVENT_HAVE_ISSETUGID 1 + +/* Define to 1 if you have the `kqueue' function. */ +#define _EVENT_HAVE_KQUEUE 1 + +/* Define to 1 if you have the `nsl' library (-lnsl). */ +/* #undef _EVENT_HAVE_LIBNSL */ + +/* Define to 1 if you have the `resolv' library (-lresolv). */ +/* #undef _EVENT_HAVE_LIBRESOLV */ + +/* Define to 1 if you have the `rt' library (-lrt). */ +#define _EVENT_HAVE_LIBRT 1 + +/* Define to 1 if you have the `socket' library (-lsocket). */ +/* #undef _EVENT_HAVE_LIBSOCKET */ + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_MEMORY_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef _EVENT_HAVE_NETINET_IN6_H */ + +/* Define to 1 if you have the `poll' function. */ +#define _EVENT_HAVE_POLL 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_POLL_H 1 + +/* Define to 1 if you have the `port_create' function. */ +/* #undef _EVENT_HAVE_PORT_CREATE */ + +/* Define to 1 if you have the header file. */ +/* #undef _EVENT_HAVE_PORT_H */ + +/* Define to 1 if you have the `select' function. */ +#define _EVENT_HAVE_SELECT 1 + +/* Define if F_SETFD is defined in */ +#define _EVENT_HAVE_SETFD 1 + +/* Define to 1 if you have the `sigaction' function. */ +#define _EVENT_HAVE_SIGACTION 1 + +/* Define to 1 if you have the `signal' function. */ +#define _EVENT_HAVE_SIGNAL 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SIGNAL_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_STDARG_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_STRING_H 1 + +/* Define to 1 if you have the `strlcpy' function. */ +#define _EVENT_HAVE_STRLCPY 1 + +/* Define to 1 if you have the `strsep' function. */ +#define _EVENT_HAVE_STRSEP 1 + +/* Define to 1 if you have the `strtok_r' function. */ +#define _EVENT_HAVE_STRTOK_R 1 + +/* Define to 1 if you have the `strtoll' function. */ +#define _EVENT_HAVE_STRTOLL 1 + +/* Define to 1 if the system has the type `struct in6_addr'. */ +#define _EVENT_HAVE_STRUCT_IN6_ADDR 1 + +/* Define to 1 if you have the header file. */ +/* #undef _EVENT_HAVE_SYS_DEVPOLL_H */ + +/* Define to 1 if you have the header file. */ +/* #undef _EVENT_HAVE_SYS_EPOLL_H */ + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_EVENT_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_IOCTL_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_PARAM_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_QUEUE_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_SELECT_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_SOCKET_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_TYPES_H 1 + +/* Define if TAILQ_FOREACH is defined in */ +#define _EVENT_HAVE_TAILQFOREACH 1 + +/* Define if timeradd is defined in */ +#define _EVENT_HAVE_TIMERADD 1 + +/* Define if timerclear is defined in */ +#define _EVENT_HAVE_TIMERCLEAR 1 + +/* Define if timercmp is defined in */ +#define _EVENT_HAVE_TIMERCMP 1 + +/* Define if timerisset is defined in */ +#define _EVENT_HAVE_TIMERISSET 1 + +/* Define to 1 if the system has the type `uint16_t'. */ +#define _EVENT_HAVE_UINT16_T 1 + +/* Define to 1 if the system has the type `uint32_t'. */ +#define _EVENT_HAVE_UINT32_T 1 + +/* Define to 1 if the system has the type `uint64_t'. */ +#define _EVENT_HAVE_UINT64_T 1 + +/* Define to 1 if the system has the type `uint8_t'. */ +#define _EVENT_HAVE_UINT8_T 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_UNISTD_H 1 + +/* Define to 1 if you have the `vasprintf' function. */ +#define _EVENT_HAVE_VASPRINTF 1 + +/* Define if kqueue works correctly with pipes */ +#define _EVENT_HAVE_WORKING_KQUEUE 1 + +/* Name of package */ +#define _EVENT_PACKAGE "libevent" + +/* Define to the address where bug reports for this package should be sent. */ +#define _EVENT_PACKAGE_BUGREPORT "" + +/* Define to the full name of this package. */ +#define _EVENT_PACKAGE_NAME "" + +/* Define to the full name and version of this package. */ +#define _EVENT_PACKAGE_STRING "" + +/* Define to the one symbol short name of this package. */ +#define _EVENT_PACKAGE_TARNAME "" + +/* Define to the version of this package. */ +#define _EVENT_PACKAGE_VERSION "" + +/* The size of `int', as computed by sizeof. */ +#define _EVENT_SIZEOF_INT 4 + +/* The size of `long', as computed by sizeof. */ +#define _EVENT_SIZEOF_LONG 8 + +/* The size of `long long', as computed by sizeof. */ +#define _EVENT_SIZEOF_LONG_LONG 8 + +/* The size of `short', as computed by sizeof. */ +#define _EVENT_SIZEOF_SHORT 2 + +/* Define to 1 if you have the ANSI C header files. */ +#define _EVENT_STDC_HEADERS 1 + +/* Define to 1 if you can safely include both and . */ +#define _EVENT_TIME_WITH_SYS_TIME 1 + +/* Version number of package */ +#define _EVENT_VERSION "1.4.13-stable" + +/* Define to appropriate substitue if compiler doesnt have __func__ */ +/* #undef _EVENT___func__ */ + +/* Define to empty if `const' does not conform to ANSI C. */ +/* #undef _EVENT_const */ + +/* Define to `__inline__' or `__inline' if that's what the C compiler + calls it, or to nothing if 'inline' is not supported under any name. */ +#ifndef _EVENT___cplusplus +/* #undef _EVENT_inline */ +#endif + +/* Define to `int' if does not define. */ +/* #undef _EVENT_pid_t */ + +/* Define to `unsigned int' if does not define. */ +/* #undef _EVENT_size_t */ + +/* Define to unsigned int if you dont have it */ +/* #undef _EVENT_socklen_t */ +#endif diff --git a/third_party/libevent/http-internal.h b/third_party/libevent/http-internal.h new file mode 100644 index 0000000000..9cd03cdd2b --- /dev/null +++ b/third_party/libevent/http-internal.h @@ -0,0 +1,154 @@ +/* + * Copyright 2001 Niels Provos + * All rights reserved. + * + * This header file contains definitions for dealing with HTTP requests + * that are internal to libevent. As user of the library, you should not + * need to know about these. + */ + +#ifndef _HTTP_H_ +#define _HTTP_H_ + +#define HTTP_CONNECT_TIMEOUT 45 +#define HTTP_WRITE_TIMEOUT 50 +#define HTTP_READ_TIMEOUT 50 + +#define HTTP_PREFIX "http://" +#define HTTP_DEFAULTPORT 80 + +enum message_read_status { + ALL_DATA_READ = 1, + MORE_DATA_EXPECTED = 0, + DATA_CORRUPTED = -1, + REQUEST_CANCELED = -2 +}; + +enum evhttp_connection_error { + EVCON_HTTP_TIMEOUT, + EVCON_HTTP_EOF, + EVCON_HTTP_INVALID_HEADER +}; + +struct evbuffer; +struct addrinfo; +struct evhttp_request; + +/* A stupid connection object - maybe make this a bufferevent later */ + +enum evhttp_connection_state { + EVCON_DISCONNECTED, /**< not currently connected not trying either*/ + EVCON_CONNECTING, /**< tries to currently connect */ + EVCON_IDLE, /**< connection is established */ + EVCON_READING_FIRSTLINE,/**< reading Request-Line (incoming conn) or + **< Status-Line (outgoing conn) */ + EVCON_READING_HEADERS, /**< reading request/response headers */ + EVCON_READING_BODY, /**< reading request/response body */ + EVCON_READING_TRAILER, /**< reading request/response chunked trailer */ + EVCON_WRITING /**< writing request/response headers/body */ +}; + +struct event_base; + +struct evhttp_connection { + /* we use tailq only if they were created for an http server */ + TAILQ_ENTRY(evhttp_connection) (next); + + int fd; + struct event ev; + struct event close_ev; + struct evbuffer *input_buffer; + struct evbuffer *output_buffer; + + char *bind_address; /* address to use for binding the src */ + u_short bind_port; /* local port for binding the src */ + + char *address; /* address to connect to */ + u_short port; + + int flags; +#define EVHTTP_CON_INCOMING 0x0001 /* only one request on it ever */ +#define EVHTTP_CON_OUTGOING 0x0002 /* multiple requests possible */ +#define EVHTTP_CON_CLOSEDETECT 0x0004 /* detecting if persistent close */ + + int timeout; /* timeout in seconds for events */ + int retry_cnt; /* retry count */ + int retry_max; /* maximum number of retries */ + + enum evhttp_connection_state state; + + /* for server connections, the http server they are connected with */ + struct evhttp *http_server; + + TAILQ_HEAD(evcon_requestq, evhttp_request) requests; + + void (*cb)(struct evhttp_connection *, void *); + void *cb_arg; + + void (*closecb)(struct evhttp_connection *, void *); + void *closecb_arg; + + struct event_base *base; +}; + +struct evhttp_cb { + TAILQ_ENTRY(evhttp_cb) next; + + char *what; + + void (*cb)(struct evhttp_request *req, void *); + void *cbarg; +}; + +/* both the http server as well as the rpc system need to queue connections */ +TAILQ_HEAD(evconq, evhttp_connection); + +/* each bound socket is stored in one of these */ +struct evhttp_bound_socket { + TAILQ_ENTRY(evhttp_bound_socket) (next); + + struct event bind_ev; +}; + +struct evhttp { + TAILQ_HEAD(boundq, evhttp_bound_socket) sockets; + + TAILQ_HEAD(httpcbq, evhttp_cb) callbacks; + struct evconq connections; + + int timeout; + + void (*gencb)(struct evhttp_request *req, void *); + void *gencbarg; + + struct event_base *base; +}; + +/* resets the connection; can be reused for more requests */ +void evhttp_connection_reset(struct evhttp_connection *); + +/* connects if necessary */ +int evhttp_connection_connect(struct evhttp_connection *); + +/* notifies the current request that it failed; resets connection */ +void evhttp_connection_fail(struct evhttp_connection *, + enum evhttp_connection_error error); + +void evhttp_get_request(struct evhttp *, int, struct sockaddr *, socklen_t); + +int evhttp_hostportfile(char *, char **, u_short *, char **); + +int evhttp_parse_firstline(struct evhttp_request *, struct evbuffer*); +int evhttp_parse_headers(struct evhttp_request *, struct evbuffer*); + +void evhttp_start_read(struct evhttp_connection *); +void evhttp_make_header(struct evhttp_connection *, struct evhttp_request *); + +void evhttp_write_buffer(struct evhttp_connection *, + void (*)(struct evhttp_connection *, void *), void *); + +/* response sending HTML the data in the buffer */ +void evhttp_response_code(struct evhttp_request *, int, const char *); +void evhttp_send_page(struct evhttp_request *, struct evbuffer *); + +#endif /* _HTTP_H */ diff --git a/third_party/libevent/http.c b/third_party/libevent/http.c new file mode 100644 index 0000000000..b04ad54bab --- /dev/null +++ b/third_party/libevent/http.c @@ -0,0 +1,2826 @@ +/* + * Copyright (c) 2002-2006 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_SYS_PARAM_H +#include +#endif +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +#ifdef HAVE_SYS_TIME_H +#include +#endif +#ifdef HAVE_SYS_IOCCOM_H +#include +#endif + +#ifndef WIN32 +#include +#include +#include +#include +#endif + +#include + +#ifndef WIN32 +#include +#include +#endif + +#ifdef WIN32 +#include +#endif + +#include +#include +#include +#include +#include +#include +#ifndef WIN32 +#include +#endif +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif + +#undef timeout_pending +#undef timeout_initialized + +#include "strlcpy-internal.h" +#include "event.h" +#include "evhttp.h" +#include "evutil.h" +#include "log.h" +#include "http-internal.h" + +#ifdef WIN32 +#define strcasecmp _stricmp +#define strncasecmp _strnicmp +#define strdup _strdup +#endif + +#ifndef HAVE_GETNAMEINFO +#define NI_MAXSERV 32 +#define NI_MAXHOST 1025 + +#define NI_NUMERICHOST 1 +#define NI_NUMERICSERV 2 + +static int +fake_getnameinfo(const struct sockaddr *sa, size_t salen, char *host, + size_t hostlen, char *serv, size_t servlen, int flags) +{ + struct sockaddr_in *sin = (struct sockaddr_in *)sa; + + if (serv != NULL) { + char tmpserv[16]; + evutil_snprintf(tmpserv, sizeof(tmpserv), + "%d", ntohs(sin->sin_port)); + if (strlcpy(serv, tmpserv, servlen) >= servlen) + return (-1); + } + + if (host != NULL) { + if (flags & NI_NUMERICHOST) { + if (strlcpy(host, inet_ntoa(sin->sin_addr), + hostlen) >= hostlen) + return (-1); + else + return (0); + } else { + struct hostent *hp; + hp = gethostbyaddr((char *)&sin->sin_addr, + sizeof(struct in_addr), AF_INET); + if (hp == NULL) + return (-2); + + if (strlcpy(host, hp->h_name, hostlen) >= hostlen) + return (-1); + else + return (0); + } + } + return (0); +} + +#endif + +#ifndef HAVE_GETADDRINFO +struct addrinfo { + int ai_family; + int ai_socktype; + int ai_protocol; + size_t ai_addrlen; + struct sockaddr *ai_addr; + struct addrinfo *ai_next; +}; +static int +fake_getaddrinfo(const char *hostname, struct addrinfo *ai) +{ + struct hostent *he = NULL; + struct sockaddr_in *sa; + if (hostname) { + he = gethostbyname(hostname); + if (!he) + return (-1); + } + ai->ai_family = he ? he->h_addrtype : AF_INET; + ai->ai_socktype = SOCK_STREAM; + ai->ai_protocol = 0; + ai->ai_addrlen = sizeof(struct sockaddr_in); + if (NULL == (ai->ai_addr = malloc(ai->ai_addrlen))) + return (-1); + sa = (struct sockaddr_in*)ai->ai_addr; + memset(sa, 0, ai->ai_addrlen); + if (he) { + sa->sin_family = he->h_addrtype; + memcpy(&sa->sin_addr, he->h_addr_list[0], he->h_length); + } else { + sa->sin_family = AF_INET; + sa->sin_addr.s_addr = INADDR_ANY; + } + ai->ai_next = NULL; + return (0); +} +static void +fake_freeaddrinfo(struct addrinfo *ai) +{ + free(ai->ai_addr); +} +#endif + +#ifndef MIN +#define MIN(a,b) (((a)<(b))?(a):(b)) +#endif + +/* wrapper for setting the base from the http server */ +#define EVHTTP_BASE_SET(x, y) do { \ + if ((x)->base != NULL) event_base_set((x)->base, y); \ +} while (0) + +extern int debug; + +static int socket_connect(int fd, const char *address, unsigned short port); +static int bind_socket_ai(struct addrinfo *, int reuse); +static int bind_socket(const char *, u_short, int reuse); +static void name_from_addr(struct sockaddr *, socklen_t, char **, char **); +static int evhttp_associate_new_request_with_connection( + struct evhttp_connection *evcon); +static void evhttp_connection_start_detectclose( + struct evhttp_connection *evcon); +static void evhttp_connection_stop_detectclose( + struct evhttp_connection *evcon); +static void evhttp_request_dispatch(struct evhttp_connection* evcon); +static void evhttp_read_firstline(struct evhttp_connection *evcon, + struct evhttp_request *req); +static void evhttp_read_header(struct evhttp_connection *evcon, + struct evhttp_request *req); +static int evhttp_add_header_internal(struct evkeyvalq *headers, + const char *key, const char *value); +static int evhttp_decode_uri_internal(const char *uri, size_t length, + char *ret, int always_decode_plus); + +void evhttp_read(int, short, void *); +void evhttp_write(int, short, void *); + +#ifndef HAVE_STRSEP +/* strsep replacement for platforms that lack it. Only works if + * del is one character long. */ +static char * +strsep(char **s, const char *del) +{ + char *d, *tok; + assert(strlen(del) == 1); + if (!s || !*s) + return NULL; + tok = *s; + d = strstr(tok, del); + if (d) { + *d = '\0'; + *s = d + 1; + } else + *s = NULL; + return tok; +} +#endif + +static const char * +html_replace(char ch, char *buf) +{ + switch (ch) { + case '<': + return "<"; + case '>': + return ">"; + case '"': + return """; + case '\'': + return "'"; + case '&': + return "&"; + default: + break; + } + + /* Echo the character back */ + buf[0] = ch; + buf[1] = '\0'; + + return buf; +} + +/* + * Replaces <, >, ", ' and & with <, >, ", + * ' and & correspondingly. + * + * The returned string needs to be freed by the caller. + */ + +char * +evhttp_htmlescape(const char *html) +{ + int i, new_size = 0, old_size = strlen(html); + char *escaped_html, *p; + char scratch_space[2]; + + for (i = 0; i < old_size; ++i) + new_size += strlen(html_replace(html[i], scratch_space)); + + p = escaped_html = malloc(new_size + 1); + if (escaped_html == NULL) + event_err(1, "%s: malloc(%d)", __func__, new_size + 1); + for (i = 0; i < old_size; ++i) { + const char *replaced = html_replace(html[i], scratch_space); + /* this is length checked */ + strcpy(p, replaced); + p += strlen(replaced); + } + + *p = '\0'; + + return (escaped_html); +} + +static const char * +evhttp_method(enum evhttp_cmd_type type) +{ + const char *method; + + switch (type) { + case EVHTTP_REQ_GET: + method = "GET"; + break; + case EVHTTP_REQ_POST: + method = "POST"; + break; + case EVHTTP_REQ_HEAD: + method = "HEAD"; + break; + default: + method = NULL; + break; + } + + return (method); +} + +static void +evhttp_add_event(struct event *ev, int timeout, int default_timeout) +{ + if (timeout != 0) { + struct timeval tv; + + evutil_timerclear(&tv); + tv.tv_sec = timeout != -1 ? timeout : default_timeout; + event_add(ev, &tv); + } else { + event_add(ev, NULL); + } +} + +void +evhttp_write_buffer(struct evhttp_connection *evcon, + void (*cb)(struct evhttp_connection *, void *), void *arg) +{ + event_debug(("%s: preparing to write buffer\n", __func__)); + + /* Set call back */ + evcon->cb = cb; + evcon->cb_arg = arg; + + /* check if the event is already pending */ + if (event_pending(&evcon->ev, EV_WRITE|EV_TIMEOUT, NULL)) + event_del(&evcon->ev); + + event_set(&evcon->ev, evcon->fd, EV_WRITE, evhttp_write, evcon); + EVHTTP_BASE_SET(evcon, &evcon->ev); + evhttp_add_event(&evcon->ev, evcon->timeout, HTTP_WRITE_TIMEOUT); +} + +static int +evhttp_connected(struct evhttp_connection *evcon) +{ + switch (evcon->state) { + case EVCON_DISCONNECTED: + case EVCON_CONNECTING: + return (0); + case EVCON_IDLE: + case EVCON_READING_FIRSTLINE: + case EVCON_READING_HEADERS: + case EVCON_READING_BODY: + case EVCON_READING_TRAILER: + case EVCON_WRITING: + default: + return (1); + } +} + +/* + * Create the headers needed for an HTTP request + */ +static void +evhttp_make_header_request(struct evhttp_connection *evcon, + struct evhttp_request *req) +{ + const char *method; + + evhttp_remove_header(req->output_headers, "Proxy-Connection"); + + /* Generate request line */ + method = evhttp_method(req->type); + evbuffer_add_printf(evcon->output_buffer, "%s %s HTTP/%d.%d\r\n", + method, req->uri, req->major, req->minor); + + /* Add the content length on a post request if missing */ + if (req->type == EVHTTP_REQ_POST && + evhttp_find_header(req->output_headers, "Content-Length") == NULL){ + char size[12]; + evutil_snprintf(size, sizeof(size), "%ld", + (long)EVBUFFER_LENGTH(req->output_buffer)); + evhttp_add_header(req->output_headers, "Content-Length", size); + } +} + +static int +evhttp_is_connection_close(int flags, struct evkeyvalq* headers) +{ + if (flags & EVHTTP_PROXY_REQUEST) { + /* proxy connection */ + const char *connection = evhttp_find_header(headers, "Proxy-Connection"); + return (connection == NULL || strcasecmp(connection, "keep-alive") != 0); + } else { + const char *connection = evhttp_find_header(headers, "Connection"); + return (connection != NULL && strcasecmp(connection, "close") == 0); + } +} + +static int +evhttp_is_connection_keepalive(struct evkeyvalq* headers) +{ + const char *connection = evhttp_find_header(headers, "Connection"); + return (connection != NULL + && strncasecmp(connection, "keep-alive", 10) == 0); +} + +static void +evhttp_maybe_add_date_header(struct evkeyvalq *headers) +{ + if (evhttp_find_header(headers, "Date") == NULL) { + char date[50]; +#ifndef WIN32 + struct tm cur; +#endif + struct tm *cur_p; + time_t t = time(NULL); +#ifdef WIN32 + cur_p = gmtime(&t); +#else + gmtime_r(&t, &cur); + cur_p = &cur; +#endif + if (strftime(date, sizeof(date), + "%a, %d %b %Y %H:%M:%S GMT", cur_p) != 0) { + evhttp_add_header(headers, "Date", date); + } + } +} + +static void +evhttp_maybe_add_content_length_header(struct evkeyvalq *headers, + long content_length) +{ + if (evhttp_find_header(headers, "Transfer-Encoding") == NULL && + evhttp_find_header(headers, "Content-Length") == NULL) { + char len[12]; + evutil_snprintf(len, sizeof(len), "%ld", content_length); + evhttp_add_header(headers, "Content-Length", len); + } +} + +/* + * Create the headers needed for an HTTP reply + */ + +static void +evhttp_make_header_response(struct evhttp_connection *evcon, + struct evhttp_request *req) +{ + int is_keepalive = evhttp_is_connection_keepalive(req->input_headers); + evbuffer_add_printf(evcon->output_buffer, "HTTP/%d.%d %d %s\r\n", + req->major, req->minor, req->response_code, + req->response_code_line); + + if (req->major == 1) { + if (req->minor == 1) + evhttp_maybe_add_date_header(req->output_headers); + + /* + * if the protocol is 1.0; and the connection was keep-alive + * we need to add a keep-alive header, too. + */ + if (req->minor == 0 && is_keepalive) + evhttp_add_header(req->output_headers, + "Connection", "keep-alive"); + + if (req->minor == 1 || is_keepalive) { + /* + * we need to add the content length if the + * user did not give it, this is required for + * persistent connections to work. + */ + evhttp_maybe_add_content_length_header( + req->output_headers, + (long)EVBUFFER_LENGTH(req->output_buffer)); + } + } + + /* Potentially add headers for unidentified content. */ + if (EVBUFFER_LENGTH(req->output_buffer)) { + if (evhttp_find_header(req->output_headers, + "Content-Type") == NULL) { + evhttp_add_header(req->output_headers, + "Content-Type", "text/html; charset=ISO-8859-1"); + } + } + + /* if the request asked for a close, we send a close, too */ + if (evhttp_is_connection_close(req->flags, req->input_headers)) { + evhttp_remove_header(req->output_headers, "Connection"); + if (!(req->flags & EVHTTP_PROXY_REQUEST)) + evhttp_add_header(req->output_headers, "Connection", "close"); + evhttp_remove_header(req->output_headers, "Proxy-Connection"); + } +} + +void +evhttp_make_header(struct evhttp_connection *evcon, struct evhttp_request *req) +{ + struct evkeyval *header; + + /* + * Depending if this is a HTTP request or response, we might need to + * add some new headers or remove existing headers. + */ + if (req->kind == EVHTTP_REQUEST) { + evhttp_make_header_request(evcon, req); + } else { + evhttp_make_header_response(evcon, req); + } + + TAILQ_FOREACH(header, req->output_headers, next) { + evbuffer_add_printf(evcon->output_buffer, "%s: %s\r\n", + header->key, header->value); + } + evbuffer_add(evcon->output_buffer, "\r\n", 2); + + if (EVBUFFER_LENGTH(req->output_buffer) > 0) { + /* + * For a request, we add the POST data, for a reply, this + * is the regular data. + */ + evbuffer_add_buffer(evcon->output_buffer, req->output_buffer); + } +} + +/* Separated host, port and file from URI */ + +int +evhttp_hostportfile(char *url, char **phost, u_short *pport, char **pfile) +{ + /* XXX not threadsafe. */ + static char host[1024]; + static char file[1024]; + char *p; + const char *p2; + int len; + u_short port; + + len = strlen(HTTP_PREFIX); + if (strncasecmp(url, HTTP_PREFIX, len)) + return (-1); + + url += len; + + /* We might overrun */ + if (strlcpy(host, url, sizeof (host)) >= sizeof(host)) + return (-1); + + p = strchr(host, '/'); + if (p != NULL) { + *p = '\0'; + p2 = p + 1; + } else + p2 = NULL; + + if (pfile != NULL) { + /* Generate request file */ + if (p2 == NULL) + p2 = ""; + evutil_snprintf(file, sizeof(file), "/%s", p2); + } + + p = strchr(host, ':'); + if (p != NULL) { + *p = '\0'; + port = atoi(p + 1); + + if (port == 0) + return (-1); + } else + port = HTTP_DEFAULTPORT; + + if (phost != NULL) + *phost = host; + if (pport != NULL) + *pport = port; + if (pfile != NULL) + *pfile = file; + + return (0); +} + +static int +evhttp_connection_incoming_fail(struct evhttp_request *req, + enum evhttp_connection_error error) +{ + switch (error) { + case EVCON_HTTP_TIMEOUT: + case EVCON_HTTP_EOF: + /* + * these are cases in which we probably should just + * close the connection and not send a reply. this + * case may happen when a browser keeps a persistent + * connection open and we timeout on the read. + */ + return (-1); + case EVCON_HTTP_INVALID_HEADER: + default: /* xxx: probably should just error on default */ + /* the callback looks at the uri to determine errors */ + if (req->uri) { + free(req->uri); + req->uri = NULL; + } + + /* + * the callback needs to send a reply, once the reply has + * been send, the connection should get freed. + */ + (*req->cb)(req, req->cb_arg); + } + + return (0); +} + +void +evhttp_connection_fail(struct evhttp_connection *evcon, + enum evhttp_connection_error error) +{ + struct evhttp_request* req = TAILQ_FIRST(&evcon->requests); + void (*cb)(struct evhttp_request *, void *); + void *cb_arg; + assert(req != NULL); + + if (evcon->flags & EVHTTP_CON_INCOMING) { + /* + * for incoming requests, there are two different + * failure cases. it's either a network level error + * or an http layer error. for problems on the network + * layer like timeouts we just drop the connections. + * For HTTP problems, we might have to send back a + * reply before the connection can be freed. + */ + if (evhttp_connection_incoming_fail(req, error) == -1) + evhttp_connection_free(evcon); + return; + } + + /* save the callback for later; the cb might free our object */ + cb = req->cb; + cb_arg = req->cb_arg; + + TAILQ_REMOVE(&evcon->requests, req, next); + evhttp_request_free(req); + + /* xxx: maybe we should fail all requests??? */ + + /* reset the connection */ + evhttp_connection_reset(evcon); + + /* We are trying the next request that was queued on us */ + if (TAILQ_FIRST(&evcon->requests) != NULL) + evhttp_connection_connect(evcon); + + /* inform the user */ + if (cb != NULL) + (*cb)(NULL, cb_arg); +} + +void +evhttp_write(int fd, short what, void *arg) +{ + struct evhttp_connection *evcon = arg; + int n; + + if (what == EV_TIMEOUT) { + evhttp_connection_fail(evcon, EVCON_HTTP_TIMEOUT); + return; + } + + n = evbuffer_write(evcon->output_buffer, fd); + if (n == -1) { + event_debug(("%s: evbuffer_write", __func__)); + evhttp_connection_fail(evcon, EVCON_HTTP_EOF); + return; + } + + if (n == 0) { + event_debug(("%s: write nothing", __func__)); + evhttp_connection_fail(evcon, EVCON_HTTP_EOF); + return; + } + + if (EVBUFFER_LENGTH(evcon->output_buffer) != 0) { + evhttp_add_event(&evcon->ev, + evcon->timeout, HTTP_WRITE_TIMEOUT); + return; + } + + /* Activate our call back */ + if (evcon->cb != NULL) + (*evcon->cb)(evcon, evcon->cb_arg); +} + +/** + * Advance the connection state. + * - If this is an outgoing connection, we've just processed the response; + * idle or close the connection. + * - If this is an incoming connection, we've just processed the request; + * respond. + */ +static void +evhttp_connection_done(struct evhttp_connection *evcon) +{ + struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); + int con_outgoing = evcon->flags & EVHTTP_CON_OUTGOING; + + if (con_outgoing) { + /* idle or close the connection */ + int need_close; + TAILQ_REMOVE(&evcon->requests, req, next); + req->evcon = NULL; + + evcon->state = EVCON_IDLE; + + need_close = + evhttp_is_connection_close(req->flags, req->input_headers)|| + evhttp_is_connection_close(req->flags, req->output_headers); + + /* check if we got asked to close the connection */ + if (need_close) + evhttp_connection_reset(evcon); + + if (TAILQ_FIRST(&evcon->requests) != NULL) { + /* + * We have more requests; reset the connection + * and deal with the next request. + */ + if (!evhttp_connected(evcon)) + evhttp_connection_connect(evcon); + else + evhttp_request_dispatch(evcon); + } else if (!need_close) { + /* + * The connection is going to be persistent, but we + * need to detect if the other side closes it. + */ + evhttp_connection_start_detectclose(evcon); + } + } else { + /* + * incoming connection - we need to leave the request on the + * connection so that we can reply to it. + */ + evcon->state = EVCON_WRITING; + } + + /* notify the user of the request */ + (*req->cb)(req, req->cb_arg); + + /* if this was an outgoing request, we own and it's done. so free it */ + if (con_outgoing) { + evhttp_request_free(req); + } +} + +/* + * Handles reading from a chunked request. + * return ALL_DATA_READ: + * all data has been read + * return MORE_DATA_EXPECTED: + * more data is expected + * return DATA_CORRUPTED: + * data is corrupted + * return REQUEST_CANCLED: + * request was canceled by the user calling evhttp_cancel_request + */ + +static enum message_read_status +evhttp_handle_chunked_read(struct evhttp_request *req, struct evbuffer *buf) +{ + int len; + + while ((len = EVBUFFER_LENGTH(buf)) > 0) { + if (req->ntoread < 0) { + /* Read chunk size */ + ev_int64_t ntoread; + char *p = evbuffer_readline(buf); + char *endp; + int error; + if (p == NULL) + break; + /* the last chunk is on a new line? */ + if (strlen(p) == 0) { + free(p); + continue; + } + ntoread = evutil_strtoll(p, &endp, 16); + error = (*p == '\0' || + (*endp != '\0' && *endp != ' ') || + ntoread < 0); + free(p); + if (error) { + /* could not get chunk size */ + return (DATA_CORRUPTED); + } + req->ntoread = ntoread; + if (req->ntoread == 0) { + /* Last chunk */ + return (ALL_DATA_READ); + } + continue; + } + + /* don't have enough to complete a chunk; wait for more */ + if (len < req->ntoread) + return (MORE_DATA_EXPECTED); + + /* Completed chunk */ + evbuffer_add(req->input_buffer, + EVBUFFER_DATA(buf), (size_t)req->ntoread); + evbuffer_drain(buf, (size_t)req->ntoread); + req->ntoread = -1; + if (req->chunk_cb != NULL) { + (*req->chunk_cb)(req, req->cb_arg); + evbuffer_drain(req->input_buffer, + EVBUFFER_LENGTH(req->input_buffer)); + } + } + + return (MORE_DATA_EXPECTED); +} + +static void +evhttp_read_trailer(struct evhttp_connection *evcon, struct evhttp_request *req) +{ + struct evbuffer *buf = evcon->input_buffer; + + switch (evhttp_parse_headers(req, buf)) { + case DATA_CORRUPTED: + evhttp_connection_fail(evcon, EVCON_HTTP_INVALID_HEADER); + break; + case ALL_DATA_READ: + event_del(&evcon->ev); + evhttp_connection_done(evcon); + break; + case MORE_DATA_EXPECTED: + default: + evhttp_add_event(&evcon->ev, evcon->timeout, + HTTP_READ_TIMEOUT); + break; + } +} + +static void +evhttp_read_body(struct evhttp_connection *evcon, struct evhttp_request *req) +{ + struct evbuffer *buf = evcon->input_buffer; + + if (req->chunked) { + switch (evhttp_handle_chunked_read(req, buf)) { + case ALL_DATA_READ: + /* finished last chunk */ + evcon->state = EVCON_READING_TRAILER; + evhttp_read_trailer(evcon, req); + return; + case DATA_CORRUPTED: + /* corrupted data */ + evhttp_connection_fail(evcon, + EVCON_HTTP_INVALID_HEADER); + return; + case REQUEST_CANCELED: + /* request canceled */ + evhttp_request_free(req); + return; + case MORE_DATA_EXPECTED: + default: + break; + } + } else if (req->ntoread < 0) { + /* Read until connection close. */ + evbuffer_add_buffer(req->input_buffer, buf); + } else if (EVBUFFER_LENGTH(buf) >= req->ntoread) { + /* Completed content length */ + evbuffer_add(req->input_buffer, EVBUFFER_DATA(buf), + (size_t)req->ntoread); + evbuffer_drain(buf, (size_t)req->ntoread); + req->ntoread = 0; + evhttp_connection_done(evcon); + return; + } + /* Read more! */ + event_set(&evcon->ev, evcon->fd, EV_READ, evhttp_read, evcon); + EVHTTP_BASE_SET(evcon, &evcon->ev); + evhttp_add_event(&evcon->ev, evcon->timeout, HTTP_READ_TIMEOUT); +} + +/* + * Reads data into a buffer structure until no more data + * can be read on the file descriptor or we have read all + * the data that we wanted to read. + * Execute callback when done. + */ + +void +evhttp_read(int fd, short what, void *arg) +{ + struct evhttp_connection *evcon = arg; + struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); + struct evbuffer *buf = evcon->input_buffer; + int n, len; + + if (what == EV_TIMEOUT) { + evhttp_connection_fail(evcon, EVCON_HTTP_TIMEOUT); + return; + } + n = evbuffer_read(buf, fd, -1); + len = EVBUFFER_LENGTH(buf); + event_debug(("%s: got %d on %d\n", __func__, n, fd)); + + if (n == -1) { + if (errno != EINTR && errno != EAGAIN) { + event_debug(("%s: evbuffer_read", __func__)); + evhttp_connection_fail(evcon, EVCON_HTTP_EOF); + } else { + evhttp_add_event(&evcon->ev, evcon->timeout, + HTTP_READ_TIMEOUT); + } + return; + } else if (n == 0) { + /* Connection closed */ + evhttp_connection_done(evcon); + return; + } + + switch (evcon->state) { + case EVCON_READING_FIRSTLINE: + evhttp_read_firstline(evcon, req); + break; + case EVCON_READING_HEADERS: + evhttp_read_header(evcon, req); + break; + case EVCON_READING_BODY: + evhttp_read_body(evcon, req); + break; + case EVCON_READING_TRAILER: + evhttp_read_trailer(evcon, req); + break; + case EVCON_DISCONNECTED: + case EVCON_CONNECTING: + case EVCON_IDLE: + case EVCON_WRITING: + default: + event_errx(1, "%s: illegal connection state %d", + __func__, evcon->state); + } +} + +static void +evhttp_write_connectioncb(struct evhttp_connection *evcon, void *arg) +{ + /* This is after writing the request to the server */ + struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); + assert(req != NULL); + + assert(evcon->state == EVCON_WRITING); + + /* We are done writing our header and are now expecting the response */ + req->kind = EVHTTP_RESPONSE; + + evhttp_start_read(evcon); +} + +/* + * Clean up a connection object + */ + +void +evhttp_connection_free(struct evhttp_connection *evcon) +{ + struct evhttp_request *req; + + /* notify interested parties that this connection is going down */ + if (evcon->fd != -1) { + if (evhttp_connected(evcon) && evcon->closecb != NULL) + (*evcon->closecb)(evcon, evcon->closecb_arg); + } + + /* remove all requests that might be queued on this connection */ + while ((req = TAILQ_FIRST(&evcon->requests)) != NULL) { + TAILQ_REMOVE(&evcon->requests, req, next); + evhttp_request_free(req); + } + + if (evcon->http_server != NULL) { + struct evhttp *http = evcon->http_server; + TAILQ_REMOVE(&http->connections, evcon, next); + } + + if (event_initialized(&evcon->close_ev)) + event_del(&evcon->close_ev); + + if (event_initialized(&evcon->ev)) + event_del(&evcon->ev); + + if (evcon->fd != -1) + EVUTIL_CLOSESOCKET(evcon->fd); + + if (evcon->bind_address != NULL) + free(evcon->bind_address); + + if (evcon->address != NULL) + free(evcon->address); + + if (evcon->input_buffer != NULL) + evbuffer_free(evcon->input_buffer); + + if (evcon->output_buffer != NULL) + evbuffer_free(evcon->output_buffer); + + free(evcon); +} + +void +evhttp_connection_set_local_address(struct evhttp_connection *evcon, + const char *address) +{ + assert(evcon->state == EVCON_DISCONNECTED); + if (evcon->bind_address) + free(evcon->bind_address); + if ((evcon->bind_address = strdup(address)) == NULL) + event_err(1, "%s: strdup", __func__); +} + +void +evhttp_connection_set_local_port(struct evhttp_connection *evcon, + unsigned short port) +{ + assert(evcon->state == EVCON_DISCONNECTED); + evcon->bind_port = port; +} + +static void +evhttp_request_dispatch(struct evhttp_connection* evcon) +{ + struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); + + /* this should not usually happy but it's possible */ + if (req == NULL) + return; + + /* delete possible close detection events */ + evhttp_connection_stop_detectclose(evcon); + + /* we assume that the connection is connected already */ + assert(evcon->state == EVCON_IDLE); + + evcon->state = EVCON_WRITING; + + /* Create the header from the store arguments */ + evhttp_make_header(evcon, req); + + evhttp_write_buffer(evcon, evhttp_write_connectioncb, NULL); +} + +/* Reset our connection state */ +void +evhttp_connection_reset(struct evhttp_connection *evcon) +{ + if (event_initialized(&evcon->ev)) + event_del(&evcon->ev); + + if (evcon->fd != -1) { + /* inform interested parties about connection close */ + if (evhttp_connected(evcon) && evcon->closecb != NULL) + (*evcon->closecb)(evcon, evcon->closecb_arg); + + EVUTIL_CLOSESOCKET(evcon->fd); + evcon->fd = -1; + } + evcon->state = EVCON_DISCONNECTED; + + evbuffer_drain(evcon->input_buffer, + EVBUFFER_LENGTH(evcon->input_buffer)); + evbuffer_drain(evcon->output_buffer, + EVBUFFER_LENGTH(evcon->output_buffer)); +} + +static void +evhttp_detect_close_cb(int fd, short what, void *arg) +{ + struct evhttp_connection *evcon = arg; + evhttp_connection_reset(evcon); +} + +static void +evhttp_connection_start_detectclose(struct evhttp_connection *evcon) +{ + evcon->flags |= EVHTTP_CON_CLOSEDETECT; + + if (event_initialized(&evcon->close_ev)) + event_del(&evcon->close_ev); + event_set(&evcon->close_ev, evcon->fd, EV_READ, + evhttp_detect_close_cb, evcon); + EVHTTP_BASE_SET(evcon, &evcon->close_ev); + event_add(&evcon->close_ev, NULL); +} + +static void +evhttp_connection_stop_detectclose(struct evhttp_connection *evcon) +{ + evcon->flags &= ~EVHTTP_CON_CLOSEDETECT; + event_del(&evcon->close_ev); +} + +static void +evhttp_connection_retry(int fd, short what, void *arg) +{ + struct evhttp_connection *evcon = arg; + + evcon->state = EVCON_DISCONNECTED; + evhttp_connection_connect(evcon); +} + +/* + * Call back for asynchronous connection attempt. + */ + +static void +evhttp_connectioncb(int fd, short what, void *arg) +{ + struct evhttp_connection *evcon = arg; + int error; + socklen_t errsz = sizeof(error); + + if (what == EV_TIMEOUT) { + event_debug(("%s: connection timeout for \"%s:%d\" on %d", + __func__, evcon->address, evcon->port, evcon->fd)); + goto cleanup; + } + + /* Check if the connection completed */ + if (getsockopt(evcon->fd, SOL_SOCKET, SO_ERROR, (void*)&error, + &errsz) == -1) { + event_debug(("%s: getsockopt for \"%s:%d\" on %d", + __func__, evcon->address, evcon->port, evcon->fd)); + goto cleanup; + } + + if (error) { + event_debug(("%s: connect failed for \"%s:%d\" on %d: %s", + __func__, evcon->address, evcon->port, evcon->fd, + strerror(error))); + goto cleanup; + } + + /* We are connected to the server now */ + event_debug(("%s: connected to \"%s:%d\" on %d\n", + __func__, evcon->address, evcon->port, evcon->fd)); + + /* Reset the retry count as we were successful in connecting */ + evcon->retry_cnt = 0; + evcon->state = EVCON_IDLE; + + /* try to start requests that have queued up on this connection */ + evhttp_request_dispatch(evcon); + return; + + cleanup: + if (evcon->retry_max < 0 || evcon->retry_cnt < evcon->retry_max) { + evtimer_set(&evcon->ev, evhttp_connection_retry, evcon); + EVHTTP_BASE_SET(evcon, &evcon->ev); + evhttp_add_event(&evcon->ev, MIN(3600, 2 << evcon->retry_cnt), + HTTP_CONNECT_TIMEOUT); + evcon->retry_cnt++; + return; + } + evhttp_connection_reset(evcon); + + /* for now, we just signal all requests by executing their callbacks */ + while (TAILQ_FIRST(&evcon->requests) != NULL) { + struct evhttp_request *request = TAILQ_FIRST(&evcon->requests); + TAILQ_REMOVE(&evcon->requests, request, next); + request->evcon = NULL; + + /* we might want to set an error here */ + request->cb(request, request->cb_arg); + evhttp_request_free(request); + } +} + +/* + * Check if we got a valid response code. + */ + +static int +evhttp_valid_response_code(int code) +{ + if (code == 0) + return (0); + + return (1); +} + +/* Parses the status line of a web server */ + +static int +evhttp_parse_response_line(struct evhttp_request *req, char *line) +{ + char *protocol; + char *number; + char *readable; + + protocol = strsep(&line, " "); + if (line == NULL) + return (-1); + number = strsep(&line, " "); + if (line == NULL) + return (-1); + readable = line; + + if (strcmp(protocol, "HTTP/1.0") == 0) { + req->major = 1; + req->minor = 0; + } else if (strcmp(protocol, "HTTP/1.1") == 0) { + req->major = 1; + req->minor = 1; + } else { + event_debug(("%s: bad protocol \"%s\"", + __func__, protocol)); + return (-1); + } + + req->response_code = atoi(number); + if (!evhttp_valid_response_code(req->response_code)) { + event_debug(("%s: bad response code \"%s\"", + __func__, number)); + return (-1); + } + + if ((req->response_code_line = strdup(readable)) == NULL) + event_err(1, "%s: strdup", __func__); + + return (0); +} + +/* Parse the first line of a HTTP request */ + +static int +evhttp_parse_request_line(struct evhttp_request *req, char *line) +{ + char *method; + char *uri; + char *version; + + /* Parse the request line */ + method = strsep(&line, " "); + if (line == NULL) + return (-1); + uri = strsep(&line, " "); + if (line == NULL) + return (-1); + version = strsep(&line, " "); + if (line != NULL) + return (-1); + + /* First line */ + if (strcmp(method, "GET") == 0) { + req->type = EVHTTP_REQ_GET; + } else if (strcmp(method, "POST") == 0) { + req->type = EVHTTP_REQ_POST; + } else if (strcmp(method, "HEAD") == 0) { + req->type = EVHTTP_REQ_HEAD; + } else { + event_debug(("%s: bad method %s on request %p from %s", + __func__, method, req, req->remote_host)); + return (-1); + } + + if (strcmp(version, "HTTP/1.0") == 0) { + req->major = 1; + req->minor = 0; + } else if (strcmp(version, "HTTP/1.1") == 0) { + req->major = 1; + req->minor = 1; + } else { + event_debug(("%s: bad version %s on request %p from %s", + __func__, version, req, req->remote_host)); + return (-1); + } + + if ((req->uri = strdup(uri)) == NULL) { + event_debug(("%s: evhttp_decode_uri", __func__)); + return (-1); + } + + /* determine if it's a proxy request */ + if (strlen(req->uri) > 0 && req->uri[0] != '/') + req->flags |= EVHTTP_PROXY_REQUEST; + + return (0); +} + +const char * +evhttp_find_header(const struct evkeyvalq *headers, const char *key) +{ + struct evkeyval *header; + + TAILQ_FOREACH(header, headers, next) { + if (strcasecmp(header->key, key) == 0) + return (header->value); + } + + return (NULL); +} + +void +evhttp_clear_headers(struct evkeyvalq *headers) +{ + struct evkeyval *header; + + for (header = TAILQ_FIRST(headers); + header != NULL; + header = TAILQ_FIRST(headers)) { + TAILQ_REMOVE(headers, header, next); + free(header->key); + free(header->value); + free(header); + } +} + +/* + * Returns 0, if the header was successfully removed. + * Returns -1, if the header could not be found. + */ + +int +evhttp_remove_header(struct evkeyvalq *headers, const char *key) +{ + struct evkeyval *header; + + TAILQ_FOREACH(header, headers, next) { + if (strcasecmp(header->key, key) == 0) + break; + } + + if (header == NULL) + return (-1); + + /* Free and remove the header that we found */ + TAILQ_REMOVE(headers, header, next); + free(header->key); + free(header->value); + free(header); + + return (0); +} + +static int +evhttp_header_is_valid_value(const char *value) +{ + const char *p = value; + + while ((p = strpbrk(p, "\r\n")) != NULL) { + /* we really expect only one new line */ + p += strspn(p, "\r\n"); + /* we expect a space or tab for continuation */ + if (*p != ' ' && *p != '\t') + return (0); + } + return (1); +} + +int +evhttp_add_header(struct evkeyvalq *headers, + const char *key, const char *value) +{ + event_debug(("%s: key: %s val: %s\n", __func__, key, value)); + + if (strchr(key, '\r') != NULL || strchr(key, '\n') != NULL) { + /* drop illegal headers */ + event_debug(("%s: dropping illegal header key\n", __func__)); + return (-1); + } + + if (!evhttp_header_is_valid_value(value)) { + event_debug(("%s: dropping illegal header value\n", __func__)); + return (-1); + } + + return (evhttp_add_header_internal(headers, key, value)); +} + +static int +evhttp_add_header_internal(struct evkeyvalq *headers, + const char *key, const char *value) +{ + struct evkeyval *header = calloc(1, sizeof(struct evkeyval)); + if (header == NULL) { + event_warn("%s: calloc", __func__); + return (-1); + } + if ((header->key = strdup(key)) == NULL) { + free(header); + event_warn("%s: strdup", __func__); + return (-1); + } + if ((header->value = strdup(value)) == NULL) { + free(header->key); + free(header); + event_warn("%s: strdup", __func__); + return (-1); + } + + TAILQ_INSERT_TAIL(headers, header, next); + + return (0); +} + +/* + * Parses header lines from a request or a response into the specified + * request object given an event buffer. + * + * Returns + * DATA_CORRUPTED on error + * MORE_DATA_EXPECTED when we need to read more headers + * ALL_DATA_READ when all headers have been read. + */ + +enum message_read_status +evhttp_parse_firstline(struct evhttp_request *req, struct evbuffer *buffer) +{ + char *line; + enum message_read_status status = ALL_DATA_READ; + + line = evbuffer_readline(buffer); + if (line == NULL) + return (MORE_DATA_EXPECTED); + + switch (req->kind) { + case EVHTTP_REQUEST: + if (evhttp_parse_request_line(req, line) == -1) + status = DATA_CORRUPTED; + break; + case EVHTTP_RESPONSE: + if (evhttp_parse_response_line(req, line) == -1) + status = DATA_CORRUPTED; + break; + default: + status = DATA_CORRUPTED; + } + + free(line); + return (status); +} + +static int +evhttp_append_to_last_header(struct evkeyvalq *headers, const char *line) +{ + struct evkeyval *header = TAILQ_LAST(headers, evkeyvalq); + char *newval; + size_t old_len, line_len; + + if (header == NULL) + return (-1); + + old_len = strlen(header->value); + line_len = strlen(line); + + newval = realloc(header->value, old_len + line_len + 1); + if (newval == NULL) + return (-1); + + memcpy(newval + old_len, line, line_len + 1); + header->value = newval; + + return (0); +} + +enum message_read_status +evhttp_parse_headers(struct evhttp_request *req, struct evbuffer* buffer) +{ + char *line; + enum message_read_status status = MORE_DATA_EXPECTED; + + struct evkeyvalq* headers = req->input_headers; + while ((line = evbuffer_readline(buffer)) + != NULL) { + char *skey, *svalue; + + if (*line == '\0') { /* Last header - Done */ + status = ALL_DATA_READ; + free(line); + break; + } + + /* Check if this is a continuation line */ + if (*line == ' ' || *line == '\t') { + if (evhttp_append_to_last_header(headers, line) == -1) + goto error; + free(line); + continue; + } + + /* Processing of header lines */ + svalue = line; + skey = strsep(&svalue, ":"); + if (svalue == NULL) + goto error; + + svalue += strspn(svalue, " "); + + if (evhttp_add_header(headers, skey, svalue) == -1) + goto error; + + free(line); + } + + return (status); + + error: + free(line); + return (DATA_CORRUPTED); +} + +static int +evhttp_get_body_length(struct evhttp_request *req) +{ + struct evkeyvalq *headers = req->input_headers; + const char *content_length; + const char *connection; + + content_length = evhttp_find_header(headers, "Content-Length"); + connection = evhttp_find_header(headers, "Connection"); + + if (content_length == NULL && connection == NULL) + req->ntoread = -1; + else if (content_length == NULL && + strcasecmp(connection, "Close") != 0) { + /* Bad combination, we don't know when it will end */ + event_warnx("%s: we got no content length, but the " + "server wants to keep the connection open: %s.", + __func__, connection); + return (-1); + } else if (content_length == NULL) { + req->ntoread = -1; + } else { + char *endp; + ev_int64_t ntoread = evutil_strtoll(content_length, &endp, 10); + if (*content_length == '\0' || *endp != '\0' || ntoread < 0) { + event_debug(("%s: illegal content length: %s", + __func__, content_length)); + return (-1); + } + req->ntoread = ntoread; + } + + event_debug(("%s: bytes to read: %lld (in buffer %ld)\n", + __func__, req->ntoread, + EVBUFFER_LENGTH(req->evcon->input_buffer))); + + return (0); +} + +static void +evhttp_get_body(struct evhttp_connection *evcon, struct evhttp_request *req) +{ + const char *xfer_enc; + + /* If this is a request without a body, then we are done */ + if (req->kind == EVHTTP_REQUEST && req->type != EVHTTP_REQ_POST) { + evhttp_connection_done(evcon); + return; + } + evcon->state = EVCON_READING_BODY; + xfer_enc = evhttp_find_header(req->input_headers, "Transfer-Encoding"); + if (xfer_enc != NULL && strcasecmp(xfer_enc, "chunked") == 0) { + req->chunked = 1; + req->ntoread = -1; + } else { + if (evhttp_get_body_length(req) == -1) { + evhttp_connection_fail(evcon, + EVCON_HTTP_INVALID_HEADER); + return; + } + } + evhttp_read_body(evcon, req); +} + +static void +evhttp_read_firstline(struct evhttp_connection *evcon, + struct evhttp_request *req) +{ + enum message_read_status res; + + res = evhttp_parse_firstline(req, evcon->input_buffer); + if (res == DATA_CORRUPTED) { + /* Error while reading, terminate */ + event_debug(("%s: bad header lines on %d\n", + __func__, evcon->fd)); + evhttp_connection_fail(evcon, EVCON_HTTP_INVALID_HEADER); + return; + } else if (res == MORE_DATA_EXPECTED) { + /* Need more header lines */ + evhttp_add_event(&evcon->ev, + evcon->timeout, HTTP_READ_TIMEOUT); + return; + } + + evcon->state = EVCON_READING_HEADERS; + evhttp_read_header(evcon, req); +} + +static void +evhttp_read_header(struct evhttp_connection *evcon, struct evhttp_request *req) +{ + enum message_read_status res; + int fd = evcon->fd; + + res = evhttp_parse_headers(req, evcon->input_buffer); + if (res == DATA_CORRUPTED) { + /* Error while reading, terminate */ + event_debug(("%s: bad header lines on %d\n", __func__, fd)); + evhttp_connection_fail(evcon, EVCON_HTTP_INVALID_HEADER); + return; + } else if (res == MORE_DATA_EXPECTED) { + /* Need more header lines */ + evhttp_add_event(&evcon->ev, + evcon->timeout, HTTP_READ_TIMEOUT); + return; + } + + /* Done reading headers, do the real work */ + switch (req->kind) { + case EVHTTP_REQUEST: + event_debug(("%s: checking for post data on %d\n", + __func__, fd)); + evhttp_get_body(evcon, req); + break; + + case EVHTTP_RESPONSE: + if (req->response_code == HTTP_NOCONTENT || + req->response_code == HTTP_NOTMODIFIED || + (req->response_code >= 100 && req->response_code < 200)) { + event_debug(("%s: skipping body for code %d\n", + __func__, req->response_code)); + evhttp_connection_done(evcon); + } else { + event_debug(("%s: start of read body for %s on %d\n", + __func__, req->remote_host, fd)); + evhttp_get_body(evcon, req); + } + break; + + default: + event_warnx("%s: bad header on %d", __func__, fd); + evhttp_connection_fail(evcon, EVCON_HTTP_INVALID_HEADER); + break; + } +} + +/* + * Creates a TCP connection to the specified port and executes a callback + * when finished. Failure or sucess is indicate by the passed connection + * object. + * + * Although this interface accepts a hostname, it is intended to take + * only numeric hostnames so that non-blocking DNS resolution can + * happen elsewhere. + */ + +struct evhttp_connection * +evhttp_connection_new(const char *address, unsigned short port) +{ + struct evhttp_connection *evcon = NULL; + + event_debug(("Attempting connection to %s:%d\n", address, port)); + + if ((evcon = calloc(1, sizeof(struct evhttp_connection))) == NULL) { + event_warn("%s: calloc failed", __func__); + goto error; + } + + evcon->fd = -1; + evcon->port = port; + + evcon->timeout = -1; + evcon->retry_cnt = evcon->retry_max = 0; + + if ((evcon->address = strdup(address)) == NULL) { + event_warn("%s: strdup failed", __func__); + goto error; + } + + if ((evcon->input_buffer = evbuffer_new()) == NULL) { + event_warn("%s: evbuffer_new failed", __func__); + goto error; + } + + if ((evcon->output_buffer = evbuffer_new()) == NULL) { + event_warn("%s: evbuffer_new failed", __func__); + goto error; + } + + evcon->state = EVCON_DISCONNECTED; + TAILQ_INIT(&evcon->requests); + + return (evcon); + + error: + if (evcon != NULL) + evhttp_connection_free(evcon); + return (NULL); +} + +void evhttp_connection_set_base(struct evhttp_connection *evcon, + struct event_base *base) +{ + assert(evcon->base == NULL); + assert(evcon->state == EVCON_DISCONNECTED); + evcon->base = base; +} + +void +evhttp_connection_set_timeout(struct evhttp_connection *evcon, + int timeout_in_secs) +{ + evcon->timeout = timeout_in_secs; +} + +void +evhttp_connection_set_retries(struct evhttp_connection *evcon, + int retry_max) +{ + evcon->retry_max = retry_max; +} + +void +evhttp_connection_set_closecb(struct evhttp_connection *evcon, + void (*cb)(struct evhttp_connection *, void *), void *cbarg) +{ + evcon->closecb = cb; + evcon->closecb_arg = cbarg; +} + +void +evhttp_connection_get_peer(struct evhttp_connection *evcon, + char **address, u_short *port) +{ + *address = evcon->address; + *port = evcon->port; +} + +int +evhttp_connection_connect(struct evhttp_connection *evcon) +{ + if (evcon->state == EVCON_CONNECTING) + return (0); + + evhttp_connection_reset(evcon); + + assert(!(evcon->flags & EVHTTP_CON_INCOMING)); + evcon->flags |= EVHTTP_CON_OUTGOING; + + evcon->fd = bind_socket( + evcon->bind_address, evcon->bind_port, 0 /*reuse*/); + if (evcon->fd == -1) { + event_debug(("%s: failed to bind to \"%s\"", + __func__, evcon->bind_address)); + return (-1); + } + + if (socket_connect(evcon->fd, evcon->address, evcon->port) == -1) { + EVUTIL_CLOSESOCKET(evcon->fd); evcon->fd = -1; + return (-1); + } + + /* Set up a callback for successful connection setup */ + event_set(&evcon->ev, evcon->fd, EV_WRITE, evhttp_connectioncb, evcon); + EVHTTP_BASE_SET(evcon, &evcon->ev); + evhttp_add_event(&evcon->ev, evcon->timeout, HTTP_CONNECT_TIMEOUT); + + evcon->state = EVCON_CONNECTING; + + return (0); +} + +/* + * Starts an HTTP request on the provided evhttp_connection object. + * If the connection object is not connected to the web server already, + * this will start the connection. + */ + +int +evhttp_make_request(struct evhttp_connection *evcon, + struct evhttp_request *req, + enum evhttp_cmd_type type, const char *uri) +{ + /* We are making a request */ + req->kind = EVHTTP_REQUEST; + req->type = type; + if (req->uri != NULL) + free(req->uri); + if ((req->uri = strdup(uri)) == NULL) + event_err(1, "%s: strdup", __func__); + + /* Set the protocol version if it is not supplied */ + if (!req->major && !req->minor) { + req->major = 1; + req->minor = 1; + } + + assert(req->evcon == NULL); + req->evcon = evcon; + assert(!(req->flags & EVHTTP_REQ_OWN_CONNECTION)); + + TAILQ_INSERT_TAIL(&evcon->requests, req, next); + + /* If the connection object is not connected; make it so */ + if (!evhttp_connected(evcon)) + return (evhttp_connection_connect(evcon)); + + /* + * If it's connected already and we are the first in the queue, + * then we can dispatch this request immediately. Otherwise, it + * will be dispatched once the pending requests are completed. + */ + if (TAILQ_FIRST(&evcon->requests) == req) + evhttp_request_dispatch(evcon); + + return (0); +} + +/* + * Reads data from file descriptor into request structure + * Request structure needs to be set up correctly. + */ + +void +evhttp_start_read(struct evhttp_connection *evcon) +{ + /* Set up an event to read the headers */ + if (event_initialized(&evcon->ev)) + event_del(&evcon->ev); + event_set(&evcon->ev, evcon->fd, EV_READ, evhttp_read, evcon); + EVHTTP_BASE_SET(evcon, &evcon->ev); + + evhttp_add_event(&evcon->ev, evcon->timeout, HTTP_READ_TIMEOUT); + evcon->state = EVCON_READING_FIRSTLINE; +} + +static void +evhttp_send_done(struct evhttp_connection *evcon, void *arg) +{ + int need_close; + struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); + TAILQ_REMOVE(&evcon->requests, req, next); + + /* delete possible close detection events */ + evhttp_connection_stop_detectclose(evcon); + + need_close = + (req->minor == 0 && + !evhttp_is_connection_keepalive(req->input_headers))|| + evhttp_is_connection_close(req->flags, req->input_headers) || + evhttp_is_connection_close(req->flags, req->output_headers); + + assert(req->flags & EVHTTP_REQ_OWN_CONNECTION); + evhttp_request_free(req); + + if (need_close) { + evhttp_connection_free(evcon); + return; + } + + /* we have a persistent connection; try to accept another request. */ + if (evhttp_associate_new_request_with_connection(evcon) == -1) + evhttp_connection_free(evcon); +} + +/* + * Returns an error page. + */ + +void +evhttp_send_error(struct evhttp_request *req, int error, const char *reason) +{ +#define ERR_FORMAT "\n" \ + "%d %s\n" \ + "\n" \ + "

Method Not Implemented

\n" \ + "Invalid method in request

\n" \ + "\n" + + struct evbuffer *buf = evbuffer_new(); + + /* close the connection on error */ + evhttp_add_header(req->output_headers, "Connection", "close"); + + evhttp_response_code(req, error, reason); + + evbuffer_add_printf(buf, ERR_FORMAT, error, reason); + + evhttp_send_page(req, buf); + + evbuffer_free(buf); +#undef ERR_FORMAT +} + +/* Requires that headers and response code are already set up */ + +static inline void +evhttp_send(struct evhttp_request *req, struct evbuffer *databuf) +{ + struct evhttp_connection *evcon = req->evcon; + + assert(TAILQ_FIRST(&evcon->requests) == req); + + /* xxx: not sure if we really should expose the data buffer this way */ + if (databuf != NULL) + evbuffer_add_buffer(req->output_buffer, databuf); + + /* Adds headers to the response */ + evhttp_make_header(evcon, req); + + evhttp_write_buffer(evcon, evhttp_send_done, NULL); +} + +void +evhttp_send_reply(struct evhttp_request *req, int code, const char *reason, + struct evbuffer *databuf) +{ + evhttp_response_code(req, code, reason); + + evhttp_send(req, databuf); +} + +void +evhttp_send_reply_start(struct evhttp_request *req, int code, + const char *reason) +{ + evhttp_response_code(req, code, reason); + if (req->major == 1 && req->minor == 1) { + /* use chunked encoding for HTTP/1.1 */ + evhttp_add_header(req->output_headers, "Transfer-Encoding", + "chunked"); + req->chunked = 1; + } + evhttp_make_header(req->evcon, req); + evhttp_write_buffer(req->evcon, NULL, NULL); +} + +void +evhttp_send_reply_chunk(struct evhttp_request *req, struct evbuffer *databuf) +{ + if (req->chunked) { + evbuffer_add_printf(req->evcon->output_buffer, "%x\r\n", + (unsigned)EVBUFFER_LENGTH(databuf)); + } + evbuffer_add_buffer(req->evcon->output_buffer, databuf); + if (req->chunked) { + evbuffer_add(req->evcon->output_buffer, "\r\n", 2); + } + evhttp_write_buffer(req->evcon, NULL, NULL); +} + +void +evhttp_send_reply_end(struct evhttp_request *req) +{ + struct evhttp_connection *evcon = req->evcon; + + if (req->chunked) { + evbuffer_add(req->evcon->output_buffer, "0\r\n\r\n", 5); + evhttp_write_buffer(req->evcon, evhttp_send_done, NULL); + req->chunked = 0; + } else if (!event_pending(&evcon->ev, EV_WRITE|EV_TIMEOUT, NULL)) { + /* let the connection know that we are done with the request */ + evhttp_send_done(evcon, NULL); + } else { + /* make the callback execute after all data has been written */ + evcon->cb = evhttp_send_done; + evcon->cb_arg = NULL; + } +} + +void +evhttp_response_code(struct evhttp_request *req, int code, const char *reason) +{ + req->kind = EVHTTP_RESPONSE; + req->response_code = code; + if (req->response_code_line != NULL) + free(req->response_code_line); + req->response_code_line = strdup(reason); +} + +void +evhttp_send_page(struct evhttp_request *req, struct evbuffer *databuf) +{ + if (!req->major || !req->minor) { + req->major = 1; + req->minor = 1; + } + + if (req->kind != EVHTTP_RESPONSE) + evhttp_response_code(req, 200, "OK"); + + evhttp_clear_headers(req->output_headers); + evhttp_add_header(req->output_headers, "Content-Type", "text/html"); + evhttp_add_header(req->output_headers, "Connection", "close"); + + evhttp_send(req, databuf); +} + +static const char uri_chars[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, + /* 64 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, + /* 128 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 192 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +/* + * Helper functions to encode/decode a URI. + * The returned string must be freed by the caller. + */ +char * +evhttp_encode_uri(const char *uri) +{ + struct evbuffer *buf = evbuffer_new(); + char *p; + + for (p = (char *)uri; *p != '\0'; p++) { + if (uri_chars[(u_char)(*p)]) { + evbuffer_add(buf, p, 1); + } else { + evbuffer_add_printf(buf, "%%%02X", (u_char)(*p)); + } + } + evbuffer_add(buf, "", 1); + p = strdup((char *)EVBUFFER_DATA(buf)); + evbuffer_free(buf); + + return (p); +} + +/* + * @param always_decode_plus: when true we transform plus to space even + * if we have not seen a ?. + */ +static int +evhttp_decode_uri_internal( + const char *uri, size_t length, char *ret, int always_decode_plus) +{ + char c; + int i, j, in_query = always_decode_plus; + + for (i = j = 0; uri[i] != '\0'; i++) { + c = uri[i]; + if (c == '?') { + in_query = 1; + } else if (c == '+' && in_query) { + c = ' '; + } else if (c == '%' && isxdigit((unsigned char)uri[i+1]) && + isxdigit((unsigned char)uri[i+2])) { + char tmp[] = { uri[i+1], uri[i+2], '\0' }; + c = (char)strtol(tmp, NULL, 16); + i += 2; + } + ret[j++] = c; + } + ret[j] = '\0'; + + return (j); +} + +char * +evhttp_decode_uri(const char *uri) +{ + char *ret; + + if ((ret = malloc(strlen(uri) + 1)) == NULL) + event_err(1, "%s: malloc(%lu)", __func__, + (unsigned long)(strlen(uri) + 1)); + + evhttp_decode_uri_internal(uri, strlen(uri), + ret, 0 /*always_decode_plus*/); + + return (ret); +} + +/* + * Helper function to parse out arguments in a query. + * The arguments are separated by key and value. + */ + +void +evhttp_parse_query(const char *uri, struct evkeyvalq *headers) +{ + char *line; + char *argument; + char *p; + + TAILQ_INIT(headers); + + /* No arguments - we are done */ + if (strchr(uri, '?') == NULL) + return; + + if ((line = strdup(uri)) == NULL) + event_err(1, "%s: strdup", __func__); + + + argument = line; + + /* We already know that there has to be a ? */ + strsep(&argument, "?"); + + p = argument; + while (p != NULL && *p != '\0') { + char *key, *value, *decoded_value; + argument = strsep(&p, "&"); + + value = argument; + key = strsep(&value, "="); + if (value == NULL) + goto error; + + if ((decoded_value = malloc(strlen(value) + 1)) == NULL) + event_err(1, "%s: malloc", __func__); + + evhttp_decode_uri_internal(value, strlen(value), + decoded_value, 1 /*always_decode_plus*/); + event_debug(("Query Param: %s -> %s\n", key, decoded_value)); + evhttp_add_header_internal(headers, key, decoded_value); + free(decoded_value); + } + + error: + free(line); +} + +static struct evhttp_cb * +evhttp_dispatch_callback(struct httpcbq *callbacks, struct evhttp_request *req) +{ + struct evhttp_cb *cb; + size_t offset = 0; + + /* Test for different URLs */ + char *p = strchr(req->uri, '?'); + if (p != NULL) + offset = (size_t)(p - req->uri); + + TAILQ_FOREACH(cb, callbacks, next) { + int res = 0; + if (p == NULL) { + res = strcmp(cb->what, req->uri) == 0; + } else { + res = ((strncmp(cb->what, req->uri, offset) == 0) && + (cb->what[offset] == '\0')); + } + + if (res) + return (cb); + } + + return (NULL); +} + +static void +evhttp_handle_request(struct evhttp_request *req, void *arg) +{ + struct evhttp *http = arg; + struct evhttp_cb *cb = NULL; + + if (req->uri == NULL) { + evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request"); + return; + } + + if ((cb = evhttp_dispatch_callback(&http->callbacks, req)) != NULL) { + (*cb->cb)(req, cb->cbarg); + return; + } + + /* Generic call back */ + if (http->gencb) { + (*http->gencb)(req, http->gencbarg); + return; + } else { + /* We need to send a 404 here */ +#define ERR_FORMAT "" \ + "404 Not Found" \ + "" \ + "

Not Found

" \ + "

The requested URL %s was not found on this server.

"\ + "\n" + + char *escaped_html = evhttp_htmlescape(req->uri); + struct evbuffer *buf = evbuffer_new(); + + evhttp_response_code(req, HTTP_NOTFOUND, "Not Found"); + + evbuffer_add_printf(buf, ERR_FORMAT, escaped_html); + + free(escaped_html); + + evhttp_send_page(req, buf); + + evbuffer_free(buf); +#undef ERR_FORMAT + } +} + +static void +accept_socket(int fd, short what, void *arg) +{ + struct evhttp *http = arg; + struct sockaddr_storage ss; + socklen_t addrlen = sizeof(ss); + int nfd; + + if ((nfd = accept(fd, (struct sockaddr *)&ss, &addrlen)) == -1) { + if (errno != EAGAIN && errno != EINTR) + event_warn("%s: bad accept", __func__); + return; + } + if (evutil_make_socket_nonblocking(nfd) < 0) + return; + + evhttp_get_request(http, nfd, (struct sockaddr *)&ss, addrlen); +} + +int +evhttp_bind_socket(struct evhttp *http, const char *address, u_short port) +{ + int fd; + int res; + + if ((fd = bind_socket(address, port, 1 /*reuse*/)) == -1) + return (-1); + + if (listen(fd, 128) == -1) { + event_warn("%s: listen", __func__); + EVUTIL_CLOSESOCKET(fd); + return (-1); + } + + res = evhttp_accept_socket(http, fd); + + if (res != -1) + event_debug(("Bound to port %d - Awaiting connections ... ", + port)); + + return (res); +} + +int +evhttp_accept_socket(struct evhttp *http, int fd) +{ + struct evhttp_bound_socket *bound; + struct event *ev; + int res; + + bound = malloc(sizeof(struct evhttp_bound_socket)); + if (bound == NULL) + return (-1); + + ev = &bound->bind_ev; + + /* Schedule the socket for accepting */ + event_set(ev, fd, EV_READ | EV_PERSIST, accept_socket, http); + EVHTTP_BASE_SET(http, ev); + + res = event_add(ev, NULL); + + if (res == -1) { + free(bound); + return (-1); + } + + TAILQ_INSERT_TAIL(&http->sockets, bound, next); + + return (0); +} + +static struct evhttp* +evhttp_new_object(void) +{ + struct evhttp *http = NULL; + + if ((http = calloc(1, sizeof(struct evhttp))) == NULL) { + event_warn("%s: calloc", __func__); + return (NULL); + } + + http->timeout = -1; + + TAILQ_INIT(&http->sockets); + TAILQ_INIT(&http->callbacks); + TAILQ_INIT(&http->connections); + + return (http); +} + +struct evhttp * +evhttp_new(struct event_base *base) +{ + struct evhttp *http = evhttp_new_object(); + + http->base = base; + + return (http); +} + +/* + * Start a web server on the specified address and port. + */ + +struct evhttp * +evhttp_start(const char *address, u_short port) +{ + struct evhttp *http = evhttp_new_object(); + + if (evhttp_bind_socket(http, address, port) == -1) { + free(http); + return (NULL); + } + + return (http); +} + +void +evhttp_free(struct evhttp* http) +{ + struct evhttp_cb *http_cb; + struct evhttp_connection *evcon; + struct evhttp_bound_socket *bound; + int fd; + + /* Remove the accepting part */ + while ((bound = TAILQ_FIRST(&http->sockets)) != NULL) { + TAILQ_REMOVE(&http->sockets, bound, next); + + fd = bound->bind_ev.ev_fd; + event_del(&bound->bind_ev); + EVUTIL_CLOSESOCKET(fd); + + free(bound); + } + + while ((evcon = TAILQ_FIRST(&http->connections)) != NULL) { + /* evhttp_connection_free removes the connection */ + evhttp_connection_free(evcon); + } + + while ((http_cb = TAILQ_FIRST(&http->callbacks)) != NULL) { + TAILQ_REMOVE(&http->callbacks, http_cb, next); + free(http_cb->what); + free(http_cb); + } + + free(http); +} + +void +evhttp_set_timeout(struct evhttp* http, int timeout_in_secs) +{ + http->timeout = timeout_in_secs; +} + +void +evhttp_set_cb(struct evhttp *http, const char *uri, + void (*cb)(struct evhttp_request *, void *), void *cbarg) +{ + struct evhttp_cb *http_cb; + + if ((http_cb = calloc(1, sizeof(struct evhttp_cb))) == NULL) + event_err(1, "%s: calloc", __func__); + + http_cb->what = strdup(uri); + http_cb->cb = cb; + http_cb->cbarg = cbarg; + + TAILQ_INSERT_TAIL(&http->callbacks, http_cb, next); +} + +int +evhttp_del_cb(struct evhttp *http, const char *uri) +{ + struct evhttp_cb *http_cb; + + TAILQ_FOREACH(http_cb, &http->callbacks, next) { + if (strcmp(http_cb->what, uri) == 0) + break; + } + if (http_cb == NULL) + return (-1); + + TAILQ_REMOVE(&http->callbacks, http_cb, next); + free(http_cb->what); + free(http_cb); + + return (0); +} + +void +evhttp_set_gencb(struct evhttp *http, + void (*cb)(struct evhttp_request *, void *), void *cbarg) +{ + http->gencb = cb; + http->gencbarg = cbarg; +} + +/* + * Request related functions + */ + +struct evhttp_request * +evhttp_request_new(void (*cb)(struct evhttp_request *, void *), void *arg) +{ + struct evhttp_request *req = NULL; + + /* Allocate request structure */ + if ((req = calloc(1, sizeof(struct evhttp_request))) == NULL) { + event_warn("%s: calloc", __func__); + goto error; + } + + req->kind = EVHTTP_RESPONSE; + req->input_headers = calloc(1, sizeof(struct evkeyvalq)); + if (req->input_headers == NULL) { + event_warn("%s: calloc", __func__); + goto error; + } + TAILQ_INIT(req->input_headers); + + req->output_headers = calloc(1, sizeof(struct evkeyvalq)); + if (req->output_headers == NULL) { + event_warn("%s: calloc", __func__); + goto error; + } + TAILQ_INIT(req->output_headers); + + if ((req->input_buffer = evbuffer_new()) == NULL) { + event_warn("%s: evbuffer_new", __func__); + goto error; + } + + if ((req->output_buffer = evbuffer_new()) == NULL) { + event_warn("%s: evbuffer_new", __func__); + goto error; + } + + req->cb = cb; + req->cb_arg = arg; + + return (req); + + error: + if (req != NULL) + evhttp_request_free(req); + return (NULL); +} + +void +evhttp_request_free(struct evhttp_request *req) +{ + if (req->remote_host != NULL) + free(req->remote_host); + if (req->uri != NULL) + free(req->uri); + if (req->response_code_line != NULL) + free(req->response_code_line); + + evhttp_clear_headers(req->input_headers); + free(req->input_headers); + + evhttp_clear_headers(req->output_headers); + free(req->output_headers); + + if (req->input_buffer != NULL) + evbuffer_free(req->input_buffer); + + if (req->output_buffer != NULL) + evbuffer_free(req->output_buffer); + + free(req); +} + +void +evhttp_request_set_chunked_cb(struct evhttp_request *req, + void (*cb)(struct evhttp_request *, void *)) +{ + req->chunk_cb = cb; +} + +/* + * Allows for inspection of the request URI + */ + +const char * +evhttp_request_uri(struct evhttp_request *req) { + if (req->uri == NULL) + event_debug(("%s: request %p has no uri\n", __func__, req)); + return (req->uri); +} + +/* + * Takes a file descriptor to read a request from. + * The callback is executed once the whole request has been read. + */ + +static struct evhttp_connection* +evhttp_get_request_connection( + struct evhttp* http, + int fd, struct sockaddr *sa, socklen_t salen) +{ + struct evhttp_connection *evcon; + char *hostname = NULL, *portname = NULL; + + name_from_addr(sa, salen, &hostname, &portname); + if (hostname == NULL || portname == NULL) { + if (hostname) free(hostname); + if (portname) free(portname); + return (NULL); + } + + event_debug(("%s: new request from %s:%s on %d\n", + __func__, hostname, portname, fd)); + + /* we need a connection object to put the http request on */ + evcon = evhttp_connection_new(hostname, atoi(portname)); + free(hostname); + free(portname); + if (evcon == NULL) + return (NULL); + + /* associate the base if we have one*/ + evhttp_connection_set_base(evcon, http->base); + + evcon->flags |= EVHTTP_CON_INCOMING; + evcon->state = EVCON_READING_FIRSTLINE; + + evcon->fd = fd; + + return (evcon); +} + +static int +evhttp_associate_new_request_with_connection(struct evhttp_connection *evcon) +{ + struct evhttp *http = evcon->http_server; + struct evhttp_request *req; + if ((req = evhttp_request_new(evhttp_handle_request, http)) == NULL) + return (-1); + + req->evcon = evcon; /* the request ends up owning the connection */ + req->flags |= EVHTTP_REQ_OWN_CONNECTION; + + TAILQ_INSERT_TAIL(&evcon->requests, req, next); + + req->kind = EVHTTP_REQUEST; + + if ((req->remote_host = strdup(evcon->address)) == NULL) + event_err(1, "%s: strdup", __func__); + req->remote_port = evcon->port; + + evhttp_start_read(evcon); + + return (0); +} + +void +evhttp_get_request(struct evhttp *http, int fd, + struct sockaddr *sa, socklen_t salen) +{ + struct evhttp_connection *evcon; + + evcon = evhttp_get_request_connection(http, fd, sa, salen); + if (evcon == NULL) + return; + + /* the timeout can be used by the server to close idle connections */ + if (http->timeout != -1) + evhttp_connection_set_timeout(evcon, http->timeout); + + /* + * if we want to accept more than one request on a connection, + * we need to know which http server it belongs to. + */ + evcon->http_server = http; + TAILQ_INSERT_TAIL(&http->connections, evcon, next); + + if (evhttp_associate_new_request_with_connection(evcon) == -1) + evhttp_connection_free(evcon); +} + + +/* + * Network helper functions that we do not want to export to the rest of + * the world. + */ +#if 0 /* Unused */ +static struct addrinfo * +addr_from_name(char *address) +{ +#ifdef HAVE_GETADDRINFO + struct addrinfo ai, *aitop; + int ai_result; + + memset(&ai, 0, sizeof(ai)); + ai.ai_family = AF_INET; + ai.ai_socktype = SOCK_RAW; + ai.ai_flags = 0; + if ((ai_result = getaddrinfo(address, NULL, &ai, &aitop)) != 0) { + if ( ai_result == EAI_SYSTEM ) + event_warn("getaddrinfo"); + else + event_warnx("getaddrinfo: %s", gai_strerror(ai_result)); + } + + return (aitop); +#else + assert(0); + return NULL; /* XXXXX Use gethostbyname, if this function is ever used. */ +#endif +} +#endif + +static void +name_from_addr(struct sockaddr *sa, socklen_t salen, + char **phost, char **pport) +{ + char ntop[NI_MAXHOST]; + char strport[NI_MAXSERV]; + int ni_result; + +#ifdef HAVE_GETNAMEINFO + ni_result = getnameinfo(sa, salen, + ntop, sizeof(ntop), strport, sizeof(strport), + NI_NUMERICHOST|NI_NUMERICSERV); + + if (ni_result != 0) { + if (ni_result == EAI_SYSTEM) + event_err(1, "getnameinfo failed"); + else + event_errx(1, "getnameinfo failed: %s", gai_strerror(ni_result)); + return; + } +#else + ni_result = fake_getnameinfo(sa, salen, + ntop, sizeof(ntop), strport, sizeof(strport), + NI_NUMERICHOST|NI_NUMERICSERV); + if (ni_result != 0) + return; +#endif + *phost = strdup(ntop); + *pport = strdup(strport); +} + +/* Create a non-blocking socket and bind it */ +/* todo: rename this function */ +static int +bind_socket_ai(struct addrinfo *ai, int reuse) +{ + int fd, on = 1, r; + int serrno; + + /* Create listen socket */ + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd == -1) { + event_warn("socket"); + return (-1); + } + + if (evutil_make_socket_nonblocking(fd) < 0) + goto out; + +#ifndef WIN32 + if (fcntl(fd, F_SETFD, 1) == -1) { + event_warn("fcntl(F_SETFD)"); + goto out; + } +#endif + + setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on)); + if (reuse) { + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, + (void *)&on, sizeof(on)); + } + + if (ai != NULL) { + r = bind(fd, ai->ai_addr, ai->ai_addrlen); + if (r == -1) + goto out; + } + + return (fd); + + out: + serrno = EVUTIL_SOCKET_ERROR(); + EVUTIL_CLOSESOCKET(fd); + EVUTIL_SET_SOCKET_ERROR(serrno); + return (-1); +} + +static struct addrinfo * +make_addrinfo(const char *address, u_short port) +{ + struct addrinfo *aitop = NULL; + +#ifdef HAVE_GETADDRINFO + struct addrinfo ai; + char strport[NI_MAXSERV]; + int ai_result; + + memset(&ai, 0, sizeof(ai)); + ai.ai_family = AF_INET; + ai.ai_socktype = SOCK_STREAM; + ai.ai_flags = AI_PASSIVE; /* turn NULL host name into INADDR_ANY */ + evutil_snprintf(strport, sizeof(strport), "%d", port); + if ((ai_result = getaddrinfo(address, strport, &ai, &aitop)) != 0) { + if ( ai_result == EAI_SYSTEM ) + event_warn("getaddrinfo"); + else + event_warnx("getaddrinfo: %s", gai_strerror(ai_result)); + return (NULL); + } +#else + static int cur; + static struct addrinfo ai[2]; /* We will be returning the address of some of this memory so it has to last even after this call. */ + if (++cur == 2) cur = 0; /* allow calling this function twice */ + + if (fake_getaddrinfo(address, &ai[cur]) < 0) { + event_warn("fake_getaddrinfo"); + return (NULL); + } + aitop = &ai[cur]; + ((struct sockaddr_in *) aitop->ai_addr)->sin_port = htons(port); +#endif + + return (aitop); +} + +static int +bind_socket(const char *address, u_short port, int reuse) +{ + int fd; + struct addrinfo *aitop = NULL; + + /* just create an unbound socket */ + if (address == NULL && port == 0) + return bind_socket_ai(NULL, 0); + + aitop = make_addrinfo(address, port); + + if (aitop == NULL) + return (-1); + + fd = bind_socket_ai(aitop, reuse); + +#ifdef HAVE_GETADDRINFO + freeaddrinfo(aitop); +#else + fake_freeaddrinfo(aitop); +#endif + + return (fd); +} + +static int +socket_connect(int fd, const char *address, unsigned short port) +{ + struct addrinfo *ai = make_addrinfo(address, port); + int res = -1; + + if (ai == NULL) { + event_debug(("%s: make_addrinfo: \"%s:%d\"", + __func__, address, port)); + return (-1); + } + + if (connect(fd, ai->ai_addr, ai->ai_addrlen) == -1) { +#ifdef WIN32 + int tmp_error = WSAGetLastError(); + if (tmp_error != WSAEWOULDBLOCK && tmp_error != WSAEINVAL && + tmp_error != WSAEINPROGRESS) { + goto out; + } +#else + if (errno != EINPROGRESS) { + goto out; + } +#endif + } + + /* everything is fine */ + res = 0; + +out: +#ifdef HAVE_GETADDRINFO + freeaddrinfo(ai); +#else + fake_freeaddrinfo(ai); +#endif + + return (res); +} diff --git a/third_party/libevent/install-sh b/third_party/libevent/install-sh new file mode 100644 index 0000000000..89fc9b098b --- /dev/null +++ b/third_party/libevent/install-sh @@ -0,0 +1,238 @@ +#! /bin/sh +# +# install - install a program, script, or datafile +# This comes from X11R5. +# +# Calling this script install-sh is preferred over install.sh, to prevent +# `make' implicit rules from creating a file called install from it +# when there is no Makefile. +# +# This script is compatible with the BSD install script, but was written +# from scratch. +# + + +# set DOITPROG to echo to test this script + +# Don't use :- since 4.3BSD and earlier shells don't like it. +doit="${DOITPROG-}" + + +# put in absolute paths if you don't have them in your path; or use env. vars. + +mvprog="${MVPROG-mv}" +cpprog="${CPPROG-cp}" +chmodprog="${CHMODPROG-chmod}" +chownprog="${CHOWNPROG-chown}" +chgrpprog="${CHGRPPROG-chgrp}" +stripprog="${STRIPPROG-strip}" +rmprog="${RMPROG-rm}" +mkdirprog="${MKDIRPROG-mkdir}" + +tranformbasename="" +transform_arg="" +instcmd="$mvprog" +chmodcmd="$chmodprog 0755" +chowncmd="" +chgrpcmd="" +stripcmd="" +rmcmd="$rmprog -f" +mvcmd="$mvprog" +src="" +dst="" +dir_arg="" + +while [ x"$1" != x ]; do + case $1 in + -c) instcmd="$cpprog" + shift + continue;; + + -d) dir_arg=true + shift + continue;; + + -m) chmodcmd="$chmodprog $2" + shift + shift + continue;; + + -o) chowncmd="$chownprog $2" + shift + shift + continue;; + + -g) chgrpcmd="$chgrpprog $2" + shift + shift + continue;; + + -s) stripcmd="$stripprog" + shift + continue;; + + -t=*) transformarg=`echo $1 | sed 's/-t=//'` + shift + continue;; + + -b=*) transformbasename=`echo $1 | sed 's/-b=//'` + shift + continue;; + + *) if [ x"$src" = x ] + then + src=$1 + else + # this colon is to work around a 386BSD /bin/sh bug + : + dst=$1 + fi + shift + continue;; + esac +done + +if [ x"$src" = x ] +then + echo "install: no input file specified" + exit 1 +else + true +fi + +if [ x"$dir_arg" != x ]; then + dst=$src + src="" + + if [ -d $dst ]; then + instcmd=: + else + instcmd=mkdir + fi +else + +# Waiting for this to be detected by the "$instcmd $src $dsttmp" command +# might cause directories to be created, which would be especially bad +# if $src (and thus $dsttmp) contains '*'. + + if [ -f $src -o -d $src ] + then + true + else + echo "install: $src does not exist" + exit 1 + fi + + if [ x"$dst" = x ] + then + echo "install: no destination specified" + exit 1 + else + true + fi + +# If destination is a directory, append the input filename; if your system +# does not like double slashes in filenames, you may need to add some logic + + if [ -d $dst ] + then + dst="$dst"/`basename $src` + else + true + fi +fi + +## this sed command emulates the dirname command +dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` + +# Make sure that the destination directory exists. +# this part is taken from Noah Friedman's mkinstalldirs script + +# Skip lots of stat calls in the usual case. +if [ ! -d "$dstdir" ]; then +defaultIFS=' +' +IFS="${IFS-${defaultIFS}}" + +oIFS="${IFS}" +# Some sh's can't handle IFS=/ for some reason. +IFS='%' +set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'` +IFS="${oIFS}" + +pathcomp='' + +while [ $# -ne 0 ] ; do + pathcomp="${pathcomp}${1}" + shift + + if [ ! -d "${pathcomp}" ] ; + then + $mkdirprog "${pathcomp}" + else + true + fi + + pathcomp="${pathcomp}/" +done +fi + +if [ x"$dir_arg" != x ] +then + $doit $instcmd $dst && + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi +else + +# If we're going to rename the final executable, determine the name now. + + if [ x"$transformarg" = x ] + then + dstfile=`basename $dst` + else + dstfile=`basename $dst $transformbasename | + sed $transformarg`$transformbasename + fi + +# don't allow the sed command to completely eliminate the filename + + if [ x"$dstfile" = x ] + then + dstfile=`basename $dst` + else + true + fi + +# Make a temp file name in the proper directory. + + dsttmp=$dstdir/#inst.$$# + +# Move or copy the file name to the temp name + + $doit $instcmd $src $dsttmp && + + trap "rm -f ${dsttmp}" 0 && + +# and set any options; do chmod last to preserve setuid bits + +# If any of these fail, we abort the whole thing. If we want to +# ignore errors from any of these, just make sure not to ignore +# errors from the above "$doit $instcmd $src $dsttmp" command. + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi && + +# Now rename the file to the real destination. + + $doit $rmcmd -f $dstdir/$dstfile && + $doit $mvcmd $dsttmp $dstdir/$dstfile + +fi && + + +exit 0 diff --git a/third_party/libevent/kqueue.c b/third_party/libevent/kqueue.c new file mode 100644 index 0000000000..556b73c0a9 --- /dev/null +++ b/third_party/libevent/kqueue.c @@ -0,0 +1,450 @@ +/* $OpenBSD: kqueue.c,v 1.5 2002/07/10 14:41:31 art Exp $ */ + +/* + * Copyright 2000-2002 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define _GNU_SOURCE 1 + +#include +#ifdef HAVE_SYS_TIME_H +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_INTTYPES_H +#include +#endif + +/* Some platforms apparently define the udata field of struct kevent as + * intptr_t, whereas others define it as void*. There doesn't seem to be an + * easy way to tell them apart via autoconf, so we need to use OS macros. */ +#if defined(HAVE_INTTYPES_H) && !defined(__OpenBSD__) && !defined(__FreeBSD__) && !defined(__darwin__) && !defined(__APPLE__) +#define PTR_TO_UDATA(x) ((intptr_t)(x)) +#else +#define PTR_TO_UDATA(x) (x) +#endif + +#include "event.h" +#include "event-internal.h" +#include "log.h" + +#define EVLIST_X_KQINKERNEL 0x1000 + +#define NEVENT 64 + +struct kqop { + struct kevent *changes; + int nchanges; + struct kevent *events; + struct event_list evsigevents[NSIG]; + int nevents; + int kq; + pid_t pid; +}; + +static void *kq_init (struct event_base *); +static int kq_add (void *, struct event *); +static int kq_del (void *, struct event *); +static int kq_dispatch (struct event_base *, void *, struct timeval *); +static int kq_insert (struct kqop *, struct kevent *); +static void kq_dealloc (struct event_base *, void *); + +const struct eventop kqops = { + "kqueue", + kq_init, + kq_add, + kq_del, + kq_dispatch, + kq_dealloc, + 1 /* need reinit */ +}; + +static void * +kq_init(struct event_base *base) +{ + int i, kq; + struct kqop *kqueueop; + + /* Disable kqueue when this environment variable is set */ + if (evutil_getenv("EVENT_NOKQUEUE")) + return (NULL); + + if (!(kqueueop = calloc(1, sizeof(struct kqop)))) + return (NULL); + + /* Initalize the kernel queue */ + + if ((kq = kqueue()) == -1) { + event_warn("kqueue"); + free (kqueueop); + return (NULL); + } + + kqueueop->kq = kq; + + kqueueop->pid = getpid(); + + /* Initalize fields */ + kqueueop->changes = malloc(NEVENT * sizeof(struct kevent)); + if (kqueueop->changes == NULL) { + free (kqueueop); + return (NULL); + } + kqueueop->events = malloc(NEVENT * sizeof(struct kevent)); + if (kqueueop->events == NULL) { + free (kqueueop->changes); + free (kqueueop); + return (NULL); + } + kqueueop->nevents = NEVENT; + + /* we need to keep track of multiple events per signal */ + for (i = 0; i < NSIG; ++i) { + TAILQ_INIT(&kqueueop->evsigevents[i]); + } + + /* Check for Mac OS X kqueue bug. */ + kqueueop->changes[0].ident = -1; + kqueueop->changes[0].filter = EVFILT_READ; + kqueueop->changes[0].flags = EV_ADD; + /* + * If kqueue works, then kevent will succeed, and it will + * stick an error in events[0]. If kqueue is broken, then + * kevent will fail. + */ + if (kevent(kq, + kqueueop->changes, 1, kqueueop->events, NEVENT, NULL) != 1 || + kqueueop->events[0].ident != -1 || + kqueueop->events[0].flags != EV_ERROR) { + event_warn("%s: detected broken kqueue; not using.", __func__); + free(kqueueop->changes); + free(kqueueop->events); + free(kqueueop); + close(kq); + return (NULL); + } + + return (kqueueop); +} + +static int +kq_insert(struct kqop *kqop, struct kevent *kev) +{ + int nevents = kqop->nevents; + + if (kqop->nchanges == nevents) { + struct kevent *newchange; + struct kevent *newresult; + + nevents *= 2; + + newchange = realloc(kqop->changes, + nevents * sizeof(struct kevent)); + if (newchange == NULL) { + event_warn("%s: malloc", __func__); + return (-1); + } + kqop->changes = newchange; + + newresult = realloc(kqop->events, + nevents * sizeof(struct kevent)); + + /* + * If we fail, we don't have to worry about freeing, + * the next realloc will pick it up. + */ + if (newresult == NULL) { + event_warn("%s: malloc", __func__); + return (-1); + } + kqop->events = newresult; + + kqop->nevents = nevents; + } + + memcpy(&kqop->changes[kqop->nchanges++], kev, sizeof(struct kevent)); + + event_debug(("%s: fd %d %s%s", + __func__, (int)kev->ident, + kev->filter == EVFILT_READ ? "EVFILT_READ" : "EVFILT_WRITE", + kev->flags == EV_DELETE ? " (del)" : "")); + + return (0); +} + +static void +kq_sighandler(int sig) +{ + /* Do nothing here */ +} + +static int +kq_dispatch(struct event_base *base, void *arg, struct timeval *tv) +{ + struct kqop *kqop = arg; + struct kevent *changes = kqop->changes; + struct kevent *events = kqop->events; + struct event *ev; + struct timespec ts, *ts_p = NULL; + int i, res; + + if (tv != NULL) { + TIMEVAL_TO_TIMESPEC(tv, &ts); + ts_p = &ts; + } + + res = kevent(kqop->kq, changes, kqop->nchanges, + events, kqop->nevents, ts_p); + kqop->nchanges = 0; + if (res == -1) { + if (errno != EINTR) { + event_warn("kevent"); + return (-1); + } + + return (0); + } + + event_debug(("%s: kevent reports %d", __func__, res)); + + for (i = 0; i < res; i++) { + int which = 0; + + if (events[i].flags & EV_ERROR) { + /* + * Error messages that can happen, when a delete fails. + * EBADF happens when the file discriptor has been + * closed, + * ENOENT when the file discriptor was closed and + * then reopened. + * EINVAL for some reasons not understood; EINVAL + * should not be returned ever; but FreeBSD does :-\ + * An error is also indicated when a callback deletes + * an event we are still processing. In that case + * the data field is set to ENOENT. + */ + if (events[i].data == EBADF || + events[i].data == EINVAL || + events[i].data == ENOENT) + continue; + errno = events[i].data; + return (-1); + } + + if (events[i].filter == EVFILT_READ) { + which |= EV_READ; + } else if (events[i].filter == EVFILT_WRITE) { + which |= EV_WRITE; + } else if (events[i].filter == EVFILT_SIGNAL) { + which |= EV_SIGNAL; + } + + if (!which) + continue; + + if (events[i].filter == EVFILT_SIGNAL) { + struct event_list *head = + (struct event_list *)events[i].udata; + TAILQ_FOREACH(ev, head, ev_signal_next) { + event_active(ev, which, events[i].data); + } + } else { + ev = (struct event *)events[i].udata; + + if (!(ev->ev_events & EV_PERSIST)) + ev->ev_flags &= ~EVLIST_X_KQINKERNEL; + + event_active(ev, which, 1); + } + } + + return (0); +} + + +static int +kq_add(void *arg, struct event *ev) +{ + struct kqop *kqop = arg; + struct kevent kev; + + if (ev->ev_events & EV_SIGNAL) { + int nsignal = EVENT_SIGNAL(ev); + + assert(nsignal >= 0 && nsignal < NSIG); + if (TAILQ_EMPTY(&kqop->evsigevents[nsignal])) { + struct timespec timeout = { 0, 0 }; + + memset(&kev, 0, sizeof(kev)); + kev.ident = nsignal; + kev.filter = EVFILT_SIGNAL; + kev.flags = EV_ADD; + kev.udata = PTR_TO_UDATA(&kqop->evsigevents[nsignal]); + + /* Be ready for the signal if it is sent any + * time between now and the next call to + * kq_dispatch. */ + if (kevent(kqop->kq, &kev, 1, NULL, 0, &timeout) == -1) + return (-1); + + if (_evsignal_set_handler(ev->ev_base, nsignal, + kq_sighandler) == -1) + return (-1); + } + + TAILQ_INSERT_TAIL(&kqop->evsigevents[nsignal], ev, + ev_signal_next); + ev->ev_flags |= EVLIST_X_KQINKERNEL; + return (0); + } + + if (ev->ev_events & EV_READ) { + memset(&kev, 0, sizeof(kev)); + kev.ident = ev->ev_fd; + kev.filter = EVFILT_READ; +#ifdef NOTE_EOF + /* Make it behave like select() and poll() */ + kev.fflags = NOTE_EOF; +#endif + kev.flags = EV_ADD; + if (!(ev->ev_events & EV_PERSIST)) + kev.flags |= EV_ONESHOT; + kev.udata = PTR_TO_UDATA(ev); + + if (kq_insert(kqop, &kev) == -1) + return (-1); + + ev->ev_flags |= EVLIST_X_KQINKERNEL; + } + + if (ev->ev_events & EV_WRITE) { + memset(&kev, 0, sizeof(kev)); + kev.ident = ev->ev_fd; + kev.filter = EVFILT_WRITE; + kev.flags = EV_ADD; + if (!(ev->ev_events & EV_PERSIST)) + kev.flags |= EV_ONESHOT; + kev.udata = PTR_TO_UDATA(ev); + + if (kq_insert(kqop, &kev) == -1) + return (-1); + + ev->ev_flags |= EVLIST_X_KQINKERNEL; + } + + return (0); +} + +static int +kq_del(void *arg, struct event *ev) +{ + struct kqop *kqop = arg; + struct kevent kev; + + if (!(ev->ev_flags & EVLIST_X_KQINKERNEL)) + return (0); + + if (ev->ev_events & EV_SIGNAL) { + int nsignal = EVENT_SIGNAL(ev); + struct timespec timeout = { 0, 0 }; + + assert(nsignal >= 0 && nsignal < NSIG); + TAILQ_REMOVE(&kqop->evsigevents[nsignal], ev, ev_signal_next); + if (TAILQ_EMPTY(&kqop->evsigevents[nsignal])) { + memset(&kev, 0, sizeof(kev)); + kev.ident = nsignal; + kev.filter = EVFILT_SIGNAL; + kev.flags = EV_DELETE; + + /* Because we insert signal events + * immediately, we need to delete them + * immediately, too */ + if (kevent(kqop->kq, &kev, 1, NULL, 0, &timeout) == -1) + return (-1); + + if (_evsignal_restore_handler(ev->ev_base, + nsignal) == -1) + return (-1); + } + + ev->ev_flags &= ~EVLIST_X_KQINKERNEL; + return (0); + } + + if (ev->ev_events & EV_READ) { + memset(&kev, 0, sizeof(kev)); + kev.ident = ev->ev_fd; + kev.filter = EVFILT_READ; + kev.flags = EV_DELETE; + + if (kq_insert(kqop, &kev) == -1) + return (-1); + + ev->ev_flags &= ~EVLIST_X_KQINKERNEL; + } + + if (ev->ev_events & EV_WRITE) { + memset(&kev, 0, sizeof(kev)); + kev.ident = ev->ev_fd; + kev.filter = EVFILT_WRITE; + kev.flags = EV_DELETE; + + if (kq_insert(kqop, &kev) == -1) + return (-1); + + ev->ev_flags &= ~EVLIST_X_KQINKERNEL; + } + + return (0); +} + +static void +kq_dealloc(struct event_base *base, void *arg) +{ + struct kqop *kqop = arg; + + if (kqop->changes) + free(kqop->changes); + if (kqop->events) + free(kqop->events); + if (kqop->kq >= 0 && kqop->pid == getpid()) + close(kqop->kq); + memset(kqop, 0, sizeof(struct kqop)); + free(kqop); +} diff --git a/third_party/libevent/libevent.gyp b/third_party/libevent/libevent.gyp new file mode 100644 index 0000000000..27ac1f9c58 --- /dev/null +++ b/third_party/libevent/libevent.gyp @@ -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. + +{ + 'targets': [ + { + 'target_name': 'libevent', + 'product_name': 'event', + 'type': 'static_library', + 'toolsets': ['host', 'target'], + 'sources': [ + 'buffer.c', + 'evbuffer.c', + 'evdns.c', + 'event.c', + 'event_tagging.c', + 'evrpc.c', + 'evutil.c', + 'http.c', + 'log.c', + 'poll.c', + 'select.c', + 'signal.c', + 'strlcpy.c', + ], + 'defines': [ + 'HAVE_CONFIG_H', + ], + 'conditions': [ + # libevent has platform-specific implementation files. Since its + # native build uses autoconf, platform-specific config.h files are + # provided and live in platform-specific directories. + [ 'OS == "linux" or (OS == "android" and _toolset == "host")', { + 'sources': [ 'epoll.c', 'epoll_sub.c' ], + 'include_dirs': [ 'linux' ], + 'link_settings': { + 'libraries': [ + # We need rt for clock_gettime(). + # TODO(port) Maybe on FreeBSD as well? + '-lrt', + ], + }, + }], + [ 'OS == "android" and _toolset == "target"', { + # On android, epoll_create(), epoll_ctl(), epoll_wait() and + # clock_gettime() are all in libc.so, so no need to add + # epoll_sub.c and link librt. + 'sources': [ 'epoll.c' ], + 'include_dirs': [ 'android' ], + }], + [ 'OS == "mac" or OS == "ios" or os_bsd==1', { + 'sources': [ 'kqueue.c' ], + 'include_dirs': [ 'mac' ] + }], + [ 'OS == "solaris"', { + 'sources': [ 'devpoll.c', 'evport.c' ], + 'include_dirs': [ 'solaris' ] + }], + ], + }, + ], +} diff --git a/third_party/libevent/linux/config.h b/third_party/libevent/linux/config.h new file mode 100644 index 0000000000..c01ceb536a --- /dev/null +++ b/third_party/libevent/linux/config.h @@ -0,0 +1,266 @@ +/* config.h. Generated from config.h.in by configure. */ +/* config.h.in. Generated from configure.in by autoheader. */ + +/* Define if clock_gettime is available in libc */ +#define DNS_USE_CPU_CLOCK_FOR_ID 1 + +/* Define is no secure id variant is available */ +/* #undef DNS_USE_GETTIMEOFDAY_FOR_ID */ + +/* Define to 1 if you have the `clock_gettime' function. */ +#define HAVE_CLOCK_GETTIME 1 + +/* Define if /dev/poll is available */ +/* #undef HAVE_DEVPOLL */ + +/* Define to 1 if you have the header file. */ +#define HAVE_DLFCN_H 1 + +/* Define if your system supports the epoll system calls */ +#define HAVE_EPOLL 1 + +/* Define to 1 if you have the `epoll_ctl' function. */ +#define HAVE_EPOLL_CTL 1 + +/* Define if your system supports event ports */ +/* #undef HAVE_EVENT_PORTS */ + +/* Define to 1 if you have the `fcntl' function. */ +#define HAVE_FCNTL 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_FCNTL_H 1 + +/* Define to 1 if the system has the type `fd_mask'. */ +#define HAVE_FD_MASK 1 + +/* Define to 1 if you have the `getaddrinfo' function. */ +#define HAVE_GETADDRINFO 1 + +/* Define to 1 if you have the `getegid' function. */ +#define HAVE_GETEGID 1 + +/* Define to 1 if you have the `geteuid' function. */ +#define HAVE_GETEUID 1 + +/* Define to 1 if you have the `getnameinfo' function. */ +#define HAVE_GETNAMEINFO 1 + +/* Define to 1 if you have the `gettimeofday' function. */ +#define HAVE_GETTIMEOFDAY 1 + +/* Define to 1 if you have the `inet_ntop' function. */ +#define HAVE_INET_NTOP 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the `issetugid' function. */ +/* #undef HAVE_ISSETUGID */ + +/* Define to 1 if you have the `kqueue' function. */ +/* #undef HAVE_KQUEUE */ + +/* Define to 1 if you have the `nsl' library (-lnsl). */ +#define HAVE_LIBNSL 1 + +/* Define to 1 if you have the `resolv' library (-lresolv). */ +#define HAVE_LIBRESOLV 1 + +/* Define to 1 if you have the `rt' library (-lrt). */ +#define HAVE_LIBRT 1 + +/* Define to 1 if you have the `socket' library (-lsocket). */ +/* #undef HAVE_LIBSOCKET */ + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_NETINET_IN6_H */ + +/* Define to 1 if you have the `poll' function. */ +#define HAVE_POLL 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_POLL_H 1 + +/* Define to 1 if you have the `port_create' function. */ +/* #undef HAVE_PORT_CREATE */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_PORT_H */ + +/* Define to 1 if you have the `select' function. */ +#define HAVE_SELECT 1 + +/* Define if F_SETFD is defined in */ +#define HAVE_SETFD 1 + +/* Define to 1 if you have the `sigaction' function. */ +#define HAVE_SIGACTION 1 + +/* Define to 1 if you have the `signal' function. */ +#define HAVE_SIGNAL 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SIGNAL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDARG_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the `strlcpy' function. */ +/* #undef HAVE_STRLCPY */ + +/* Define to 1 if you have the `strsep' function. */ +#define HAVE_STRSEP 1 + +/* Define to 1 if you have the `strtok_r' function. */ +#define HAVE_STRTOK_R 1 + +/* Define to 1 if you have the `strtoll' function. */ +#define HAVE_STRTOLL 1 + +/* Define to 1 if the system has the type `struct in6_addr'. */ +#define HAVE_STRUCT_IN6_ADDR 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_DEVPOLL_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_EPOLL_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_EVENT_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_IOCTL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_PARAM_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_QUEUE_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SELECT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SOCKET_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define if TAILQ_FOREACH is defined in */ +#define HAVE_TAILQFOREACH 1 + +/* Define if timeradd is defined in */ +#define HAVE_TIMERADD 1 + +/* Define if timerclear is defined in */ +#define HAVE_TIMERCLEAR 1 + +/* Define if timercmp is defined in */ +#define HAVE_TIMERCMP 1 + +/* Define if timerisset is defined in */ +#define HAVE_TIMERISSET 1 + +/* Define to 1 if the system has the type `uint16_t'. */ +#define HAVE_UINT16_T 1 + +/* Define to 1 if the system has the type `uint32_t'. */ +#define HAVE_UINT32_T 1 + +/* Define to 1 if the system has the type `uint64_t'. */ +#define HAVE_UINT64_T 1 + +/* Define to 1 if the system has the type `uint8_t'. */ +#define HAVE_UINT8_T 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to 1 if you have the `vasprintf' function. */ +#define HAVE_VASPRINTF 1 + +/* Define if kqueue works correctly with pipes */ +/* #undef HAVE_WORKING_KQUEUE */ + +/* Name of package */ +#define PACKAGE "libevent" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "" + +/* The size of `int', as computed by sizeof. */ +#define SIZEOF_INT 4 + +/* The size of `long', as computed by sizeof. */ +#define SIZEOF_LONG 8 + +/* The size of `long long', as computed by sizeof. */ +#define SIZEOF_LONG_LONG 8 + +/* The size of `short', as computed by sizeof. */ +#define SIZEOF_SHORT 2 + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Define to 1 if you can safely include both and . */ +#define TIME_WITH_SYS_TIME 1 + +/* Version number of package */ +#define VERSION "1.4.13-stable" + +/* Define to appropriate substitue if compiler doesnt have __func__ */ +/* #undef __func__ */ + +/* Define to empty if `const' does not conform to ANSI C. */ +/* #undef const */ + +/* Define to `__inline__' or `__inline' if that's what the C compiler + calls it, or to nothing if 'inline' is not supported under any name. */ +#ifndef __cplusplus +/* #undef inline */ +#endif + +/* Define to `int' if does not define. */ +/* #undef pid_t */ + +/* Define to `unsigned int' if does not define. */ +/* #undef size_t */ + +/* Define to unsigned int if you dont have it */ +/* #undef socklen_t */ diff --git a/third_party/libevent/linux/event-config.h b/third_party/libevent/linux/event-config.h new file mode 100644 index 0000000000..c8a643195f --- /dev/null +++ b/third_party/libevent/linux/event-config.h @@ -0,0 +1,274 @@ +/* event-config.h + * Generated by autoconf; post-processed by libevent. + * Do not edit this file. + * Do not rely on macros in this file existing in later versions. + */ +#ifndef _EVENT_CONFIG_H_ +#define _EVENT_CONFIG_H_ +/* config.h. Generated from config.h.in by configure. */ +/* config.h.in. Generated from configure.in by autoheader. */ + +/* Define if clock_gettime is available in libc */ +#define _EVENT_DNS_USE_CPU_CLOCK_FOR_ID 1 + +/* Define is no secure id variant is available */ +/* #undef _EVENT_DNS_USE_GETTIMEOFDAY_FOR_ID */ + +/* Define to 1 if you have the `clock_gettime' function. */ +#define _EVENT_HAVE_CLOCK_GETTIME 1 + +/* Define if /dev/poll is available */ +/* #undef _EVENT_HAVE_DEVPOLL */ + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_DLFCN_H 1 + +/* Define if your system supports the epoll system calls */ +#define _EVENT_HAVE_EPOLL 1 + +/* Define to 1 if you have the `epoll_ctl' function. */ +#define _EVENT_HAVE_EPOLL_CTL 1 + +/* Define if your system supports event ports */ +/* #undef _EVENT_HAVE_EVENT_PORTS */ + +/* Define to 1 if you have the `fcntl' function. */ +#define _EVENT_HAVE_FCNTL 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_FCNTL_H 1 + +/* Define to 1 if the system has the type `fd_mask'. */ +#define _EVENT_HAVE_FD_MASK 1 + +/* Define to 1 if you have the `getaddrinfo' function. */ +#define _EVENT_HAVE_GETADDRINFO 1 + +/* Define to 1 if you have the `getegid' function. */ +#define _EVENT_HAVE_GETEGID 1 + +/* Define to 1 if you have the `geteuid' function. */ +#define _EVENT_HAVE_GETEUID 1 + +/* Define to 1 if you have the `getnameinfo' function. */ +#define _EVENT_HAVE_GETNAMEINFO 1 + +/* Define to 1 if you have the `gettimeofday' function. */ +#define _EVENT_HAVE_GETTIMEOFDAY 1 + +/* Define to 1 if you have the `inet_ntop' function. */ +#define _EVENT_HAVE_INET_NTOP 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the `issetugid' function. */ +/* #undef _EVENT_HAVE_ISSETUGID */ + +/* Define to 1 if you have the `kqueue' function. */ +/* #undef _EVENT_HAVE_KQUEUE */ + +/* Define to 1 if you have the `nsl' library (-lnsl). */ +#define _EVENT_HAVE_LIBNSL 1 + +/* Define to 1 if you have the `resolv' library (-lresolv). */ +#define _EVENT_HAVE_LIBRESOLV 1 + +/* Define to 1 if you have the `rt' library (-lrt). */ +#define _EVENT_HAVE_LIBRT 1 + +/* Define to 1 if you have the `socket' library (-lsocket). */ +/* #undef _EVENT_HAVE_LIBSOCKET */ + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_MEMORY_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef _EVENT_HAVE_NETINET_IN6_H */ + +/* Define to 1 if you have the `poll' function. */ +#define _EVENT_HAVE_POLL 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_POLL_H 1 + +/* Define to 1 if you have the `port_create' function. */ +/* #undef _EVENT_HAVE_PORT_CREATE */ + +/* Define to 1 if you have the header file. */ +/* #undef _EVENT_HAVE_PORT_H */ + +/* Define to 1 if you have the `select' function. */ +#define _EVENT_HAVE_SELECT 1 + +/* Define if F_SETFD is defined in */ +#define _EVENT_HAVE_SETFD 1 + +/* Define to 1 if you have the `sigaction' function. */ +#define _EVENT_HAVE_SIGACTION 1 + +/* Define to 1 if you have the `signal' function. */ +#define _EVENT_HAVE_SIGNAL 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SIGNAL_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_STDARG_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_STRING_H 1 + +/* Define to 1 if you have the `strlcpy' function. */ +/* #undef _EVENT_HAVE_STRLCPY */ + +/* Define to 1 if you have the `strsep' function. */ +#define _EVENT_HAVE_STRSEP 1 + +/* Define to 1 if you have the `strtok_r' function. */ +#define _EVENT_HAVE_STRTOK_R 1 + +/* Define to 1 if you have the `strtoll' function. */ +#define _EVENT_HAVE_STRTOLL 1 + +/* Define to 1 if the system has the type `struct in6_addr'. */ +#define _EVENT_HAVE_STRUCT_IN6_ADDR 1 + +/* Define to 1 if you have the header file. */ +/* #undef _EVENT_HAVE_SYS_DEVPOLL_H */ + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_EPOLL_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef _EVENT_HAVE_SYS_EVENT_H */ + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_IOCTL_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_PARAM_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_QUEUE_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_SELECT_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_SOCKET_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_TYPES_H 1 + +/* Define if TAILQ_FOREACH is defined in */ +#define _EVENT_HAVE_TAILQFOREACH 1 + +/* Define if timeradd is defined in */ +#define _EVENT_HAVE_TIMERADD 1 + +/* Define if timerclear is defined in */ +#define _EVENT_HAVE_TIMERCLEAR 1 + +/* Define if timercmp is defined in */ +#define _EVENT_HAVE_TIMERCMP 1 + +/* Define if timerisset is defined in */ +#define _EVENT_HAVE_TIMERISSET 1 + +/* Define to 1 if the system has the type `uint16_t'. */ +#define _EVENT_HAVE_UINT16_T 1 + +/* Define to 1 if the system has the type `uint32_t'. */ +#define _EVENT_HAVE_UINT32_T 1 + +/* Define to 1 if the system has the type `uint64_t'. */ +#define _EVENT_HAVE_UINT64_T 1 + +/* Define to 1 if the system has the type `uint8_t'. */ +#define _EVENT_HAVE_UINT8_T 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_UNISTD_H 1 + +/* Define to 1 if you have the `vasprintf' function. */ +#define _EVENT_HAVE_VASPRINTF 1 + +/* Define if kqueue works correctly with pipes */ +/* #undef _EVENT_HAVE_WORKING_KQUEUE */ + +/* Name of package */ +#define _EVENT_PACKAGE "libevent" + +/* Define to the address where bug reports for this package should be sent. */ +#define _EVENT_PACKAGE_BUGREPORT "" + +/* Define to the full name of this package. */ +#define _EVENT_PACKAGE_NAME "" + +/* Define to the full name and version of this package. */ +#define _EVENT_PACKAGE_STRING "" + +/* Define to the one symbol short name of this package. */ +#define _EVENT_PACKAGE_TARNAME "" + +/* Define to the version of this package. */ +#define _EVENT_PACKAGE_VERSION "" + +/* The size of `int', as computed by sizeof. */ +#define _EVENT_SIZEOF_INT 4 + +/* The size of `long', as computed by sizeof. */ +#define _EVENT_SIZEOF_LONG 8 + +/* The size of `long long', as computed by sizeof. */ +#define _EVENT_SIZEOF_LONG_LONG 8 + +/* The size of `short', as computed by sizeof. */ +#define _EVENT_SIZEOF_SHORT 2 + +/* Define to 1 if you have the ANSI C header files. */ +#define _EVENT_STDC_HEADERS 1 + +/* Define to 1 if you can safely include both and . */ +#define _EVENT_TIME_WITH_SYS_TIME 1 + +/* Version number of package */ +#define _EVENT_VERSION "1.4.13-stable" + +/* Define to appropriate substitue if compiler doesnt have __func__ */ +/* #undef _EVENT___func__ */ + +/* Define to empty if `const' does not conform to ANSI C. */ +/* #undef _EVENT_const */ + +/* Define to `__inline__' or `__inline' if that's what the C compiler + calls it, or to nothing if 'inline' is not supported under any name. */ +#ifndef _EVENT___cplusplus +/* #undef _EVENT_inline */ +#endif + +/* Define to `int' if does not define. */ +/* #undef _EVENT_pid_t */ + +/* Define to `unsigned int' if does not define. */ +/* #undef _EVENT_size_t */ + +/* Define to unsigned int if you dont have it */ +/* #undef _EVENT_socklen_t */ +#endif diff --git a/third_party/libevent/log.c b/third_party/libevent/log.c new file mode 100644 index 0000000000..48ebb2691c --- /dev/null +++ b/third_party/libevent/log.c @@ -0,0 +1,187 @@ +/* $OpenBSD: err.c,v 1.2 2002/06/25 15:50:15 mickey Exp $ */ + +/* + * log.c + * + * Based on err.c, which was adapted from OpenBSD libc *err* *warn* code. + * + * Copyright (c) 2005 Nick Mathewson + * + * Copyright (c) 2000 Dug Song + * + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of the University 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 REGENTS 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 REGENTS 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#undef WIN32_LEAN_AND_MEAN +#endif +#include +#ifdef HAVE_SYS_TIME_H +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include "event.h" + +#include "log.h" +#include "evutil.h" + +static void _warn_helper(int severity, int log_errno, const char *fmt, + va_list ap); +static void event_log(int severity, const char *msg); + +void +event_err(int eval, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + _warn_helper(_EVENT_LOG_ERR, errno, fmt, ap); + va_end(ap); + exit(eval); +} + +void +event_warn(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + _warn_helper(_EVENT_LOG_WARN, errno, fmt, ap); + va_end(ap); +} + +void +event_errx(int eval, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + _warn_helper(_EVENT_LOG_ERR, -1, fmt, ap); + va_end(ap); + exit(eval); +} + +void +event_warnx(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + _warn_helper(_EVENT_LOG_WARN, -1, fmt, ap); + va_end(ap); +} + +void +event_msgx(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + _warn_helper(_EVENT_LOG_MSG, -1, fmt, ap); + va_end(ap); +} + +void +_event_debugx(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + _warn_helper(_EVENT_LOG_DEBUG, -1, fmt, ap); + va_end(ap); +} + +static void +_warn_helper(int severity, int log_errno, const char *fmt, va_list ap) +{ + char buf[1024]; + size_t len; + + if (fmt != NULL) + evutil_vsnprintf(buf, sizeof(buf), fmt, ap); + else + buf[0] = '\0'; + + if (log_errno >= 0) { + len = strlen(buf); + if (len < sizeof(buf) - 3) { + evutil_snprintf(buf + len, sizeof(buf) - len, ": %s", + strerror(log_errno)); + } + } + + event_log(severity, buf); +} + +static event_log_cb log_fn = NULL; + +void +event_set_log_callback(event_log_cb cb) +{ + log_fn = cb; +} + +static void +event_log(int severity, const char *msg) +{ + if (log_fn) + log_fn(severity, msg); + else { + const char *severity_str; + switch (severity) { + case _EVENT_LOG_DEBUG: + severity_str = "debug"; + break; + case _EVENT_LOG_MSG: + severity_str = "msg"; + break; + case _EVENT_LOG_WARN: + severity_str = "warn"; + break; + case _EVENT_LOG_ERR: + severity_str = "err"; + break; + default: + severity_str = "???"; + break; + } + (void)fprintf(stderr, "[%s] %s\n", severity_str, msg); + } +} diff --git a/third_party/libevent/log.h b/third_party/libevent/log.h new file mode 100644 index 0000000000..7bc6632b8d --- /dev/null +++ b/third_party/libevent/log.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2000-2004 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef _LOG_H_ +#define _LOG_H_ + +#ifdef __GNUC__ +#define EV_CHECK_FMT(a,b) __attribute__((format(printf, a, b))) +#else +#define EV_CHECK_FMT(a,b) +#endif + +void event_err(int eval, const char *fmt, ...) EV_CHECK_FMT(2,3); +void event_warn(const char *fmt, ...) EV_CHECK_FMT(1,2); +void event_errx(int eval, const char *fmt, ...) EV_CHECK_FMT(2,3); +void event_warnx(const char *fmt, ...) EV_CHECK_FMT(1,2); +void event_msgx(const char *fmt, ...) EV_CHECK_FMT(1,2); +void _event_debugx(const char *fmt, ...) EV_CHECK_FMT(1,2); + +#ifdef USE_DEBUG +#define event_debug(x) _event_debugx x +#else +#define event_debug(x) do {;} while (0) +#endif + +#undef EV_CHECK_FMT + +#endif diff --git a/third_party/libevent/ltmain.sh b/third_party/libevent/ltmain.sh new file mode 100644 index 0000000000..27d498a080 --- /dev/null +++ b/third_party/libevent/ltmain.sh @@ -0,0 +1,6956 @@ +# ltmain.sh - Provide generalized library-building support services. +# NOTE: Changing this file will not affect anything until you rerun configure. +# +# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2003, 2004, 2005, 2006, +# 2007, 2008 Free Software Foundation, Inc. +# Originally by Gordon Matzigkeit , 1996 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +basename="s,^.*/,,g" + +# Work around backward compatibility issue on IRIX 6.5. On IRIX 6.4+, sh +# is ksh but when the shell is invoked as "sh" and the current value of +# the _XPG environment variable is not equal to 1 (one), the special +# positional parameter $0, within a function call, is the name of the +# function. +progpath="$0" + +# The name of this program: +progname=`echo "$progpath" | $SED $basename` +modename="$progname" + +# Global variables: +EXIT_SUCCESS=0 +EXIT_FAILURE=1 + +PROGRAM=ltmain.sh +PACKAGE=libtool +VERSION=1.5.26 +TIMESTAMP=" (1.1220.2.492 2008/01/30 06:40:56)" + +# Be Bourne compatible (taken from Autoconf:_AS_BOURNE_COMPATIBLE). +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then + emulate sh + NULLCMD=: + # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in *posix*) set -o posix;; esac +fi +BIN_SH=xpg4; export BIN_SH # for Tru64 +DUALCASE=1; export DUALCASE # for MKS sh + +# Check that we have a working $echo. +if test "X$1" = X--no-reexec; then + # Discard the --no-reexec flag, and continue. + shift +elif test "X$1" = X--fallback-echo; then + # Avoid inline document here, it may be left over + : +elif test "X`($echo '\t') 2>/dev/null`" = 'X\t'; then + # Yippee, $echo works! + : +else + # Restart under the correct shell, and then maybe $echo will work. + exec $SHELL "$progpath" --no-reexec ${1+"$@"} +fi + +if test "X$1" = X--fallback-echo; then + # used as fallback echo + shift + cat <&2 + $echo "Fatal configuration error. See the $PACKAGE docs for more information." 1>&2 + exit $EXIT_FAILURE +fi + +# Global variables. +mode=$default_mode +nonopt= +prev= +prevopt= +run= +show="$echo" +show_help= +execute_dlfiles= +duplicate_deps=no +preserve_args= +lo2o="s/\\.lo\$/.${objext}/" +o2lo="s/\\.${objext}\$/.lo/" +extracted_archives= +extracted_serial=0 + +##################################### +# Shell function definitions: +# This seems to be the best place for them + +# func_mktempdir [string] +# Make a temporary directory that won't clash with other running +# libtool processes, and avoids race conditions if possible. If +# given, STRING is the basename for that directory. +func_mktempdir () +{ + my_template="${TMPDIR-/tmp}/${1-$progname}" + + if test "$run" = ":"; then + # Return a directory name, but don't create it in dry-run mode + my_tmpdir="${my_template}-$$" + else + + # If mktemp works, use that first and foremost + my_tmpdir=`mktemp -d "${my_template}-XXXXXXXX" 2>/dev/null` + + if test ! -d "$my_tmpdir"; then + # Failing that, at least try and use $RANDOM to avoid a race + my_tmpdir="${my_template}-${RANDOM-0}$$" + + save_mktempdir_umask=`umask` + umask 0077 + $mkdir "$my_tmpdir" + umask $save_mktempdir_umask + fi + + # If we're not in dry-run mode, bomb out on failure + test -d "$my_tmpdir" || { + $echo "cannot create temporary directory \`$my_tmpdir'" 1>&2 + exit $EXIT_FAILURE + } + fi + + $echo "X$my_tmpdir" | $Xsed +} + + +# func_win32_libid arg +# return the library type of file 'arg' +# +# Need a lot of goo to handle *both* DLLs and import libs +# Has to be a shell function in order to 'eat' the argument +# that is supplied when $file_magic_command is called. +func_win32_libid () +{ + win32_libid_type="unknown" + win32_fileres=`file -L $1 2>/dev/null` + case $win32_fileres in + *ar\ archive\ import\ library*) # definitely import + win32_libid_type="x86 archive import" + ;; + *ar\ archive*) # could be an import, or static + if eval $OBJDUMP -f $1 | $SED -e '10q' 2>/dev/null | \ + $EGREP -e 'file format pe-i386(.*architecture: i386)?' >/dev/null ; then + win32_nmres=`eval $NM -f posix -A $1 | \ + $SED -n -e '1,100{ + / I /{ + s,.*,import, + p + q + } + }'` + case $win32_nmres in + import*) win32_libid_type="x86 archive import";; + *) win32_libid_type="x86 archive static";; + esac + fi + ;; + *DLL*) + win32_libid_type="x86 DLL" + ;; + *executable*) # but shell scripts are "executable" too... + case $win32_fileres in + *MS\ Windows\ PE\ Intel*) + win32_libid_type="x86 DLL" + ;; + esac + ;; + esac + $echo $win32_libid_type +} + + +# func_infer_tag arg +# Infer tagged configuration to use if any are available and +# if one wasn't chosen via the "--tag" command line option. +# Only attempt this if the compiler in the base compile +# command doesn't match the default compiler. +# arg is usually of the form 'gcc ...' +func_infer_tag () +{ + if test -n "$available_tags" && test -z "$tagname"; then + CC_quoted= + for arg in $CC; do + case $arg in + *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"") + arg="\"$arg\"" + ;; + esac + CC_quoted="$CC_quoted $arg" + done + case $@ in + # Blanks in the command may have been stripped by the calling shell, + # but not from the CC environment variable when configure was run. + " $CC "* | "$CC "* | " `$echo $CC` "* | "`$echo $CC` "* | " $CC_quoted"* | "$CC_quoted "* | " `$echo $CC_quoted` "* | "`$echo $CC_quoted` "*) ;; + # Blanks at the start of $base_compile will cause this to fail + # if we don't check for them as well. + *) + for z in $available_tags; do + if grep "^# ### BEGIN LIBTOOL TAG CONFIG: $z$" < "$progpath" > /dev/null; then + # Evaluate the configuration. + eval "`${SED} -n -e '/^# ### BEGIN LIBTOOL TAG CONFIG: '$z'$/,/^# ### END LIBTOOL TAG CONFIG: '$z'$/p' < $progpath`" + CC_quoted= + for arg in $CC; do + # Double-quote args containing other shell metacharacters. + case $arg in + *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"") + arg="\"$arg\"" + ;; + esac + CC_quoted="$CC_quoted $arg" + done + case "$@ " in + " $CC "* | "$CC "* | " `$echo $CC` "* | "`$echo $CC` "* | " $CC_quoted"* | "$CC_quoted "* | " `$echo $CC_quoted` "* | "`$echo $CC_quoted` "*) + # The compiler in the base compile command matches + # the one in the tagged configuration. + # Assume this is the tagged configuration we want. + tagname=$z + break + ;; + esac + fi + done + # If $tagname still isn't set, then no tagged configuration + # was found and let the user know that the "--tag" command + # line option must be used. + if test -z "$tagname"; then + $echo "$modename: unable to infer tagged configuration" + $echo "$modename: specify a tag with \`--tag'" 1>&2 + exit $EXIT_FAILURE +# else +# $echo "$modename: using $tagname tagged configuration" + fi + ;; + esac + fi +} + + +# func_extract_an_archive dir oldlib +func_extract_an_archive () +{ + f_ex_an_ar_dir="$1"; shift + f_ex_an_ar_oldlib="$1" + + $show "(cd $f_ex_an_ar_dir && $AR x $f_ex_an_ar_oldlib)" + $run eval "(cd \$f_ex_an_ar_dir && $AR x \$f_ex_an_ar_oldlib)" || exit $? + if ($AR t "$f_ex_an_ar_oldlib" | sort | sort -uc >/dev/null 2>&1); then + : + else + $echo "$modename: ERROR: object name conflicts: $f_ex_an_ar_dir/$f_ex_an_ar_oldlib" 1>&2 + exit $EXIT_FAILURE + fi +} + +# func_extract_archives gentop oldlib ... +func_extract_archives () +{ + my_gentop="$1"; shift + my_oldlibs=${1+"$@"} + my_oldobjs="" + my_xlib="" + my_xabs="" + my_xdir="" + my_status="" + + $show "${rm}r $my_gentop" + $run ${rm}r "$my_gentop" + $show "$mkdir $my_gentop" + $run $mkdir "$my_gentop" + my_status=$? + if test "$my_status" -ne 0 && test ! -d "$my_gentop"; then + exit $my_status + fi + + for my_xlib in $my_oldlibs; do + # Extract the objects. + case $my_xlib in + [\\/]* | [A-Za-z]:[\\/]*) my_xabs="$my_xlib" ;; + *) my_xabs=`pwd`"/$my_xlib" ;; + esac + my_xlib=`$echo "X$my_xlib" | $Xsed -e 's%^.*/%%'` + my_xlib_u=$my_xlib + while :; do + case " $extracted_archives " in + *" $my_xlib_u "*) + extracted_serial=`expr $extracted_serial + 1` + my_xlib_u=lt$extracted_serial-$my_xlib ;; + *) break ;; + esac + done + extracted_archives="$extracted_archives $my_xlib_u" + my_xdir="$my_gentop/$my_xlib_u" + + $show "${rm}r $my_xdir" + $run ${rm}r "$my_xdir" + $show "$mkdir $my_xdir" + $run $mkdir "$my_xdir" + exit_status=$? + if test "$exit_status" -ne 0 && test ! -d "$my_xdir"; then + exit $exit_status + fi + case $host in + *-darwin*) + $show "Extracting $my_xabs" + # Do not bother doing anything if just a dry run + if test -z "$run"; then + darwin_orig_dir=`pwd` + cd $my_xdir || exit $? + darwin_archive=$my_xabs + darwin_curdir=`pwd` + darwin_base_archive=`$echo "X$darwin_archive" | $Xsed -e 's%^.*/%%'` + darwin_arches=`lipo -info "$darwin_archive" 2>/dev/null | $EGREP Architectures 2>/dev/null` + if test -n "$darwin_arches"; then + darwin_arches=`echo "$darwin_arches" | $SED -e 's/.*are://'` + darwin_arch= + $show "$darwin_base_archive has multiple architectures $darwin_arches" + for darwin_arch in $darwin_arches ; do + mkdir -p "unfat-$$/${darwin_base_archive}-${darwin_arch}" + lipo -thin $darwin_arch -output "unfat-$$/${darwin_base_archive}-${darwin_arch}/${darwin_base_archive}" "${darwin_archive}" + cd "unfat-$$/${darwin_base_archive}-${darwin_arch}" + func_extract_an_archive "`pwd`" "${darwin_base_archive}" + cd "$darwin_curdir" + $rm "unfat-$$/${darwin_base_archive}-${darwin_arch}/${darwin_base_archive}" + done # $darwin_arches + ## Okay now we have a bunch of thin objects, gotta fatten them up :) + darwin_filelist=`find unfat-$$ -type f -name \*.o -print -o -name \*.lo -print| xargs basename | sort -u | $NL2SP` + darwin_file= + darwin_files= + for darwin_file in $darwin_filelist; do + darwin_files=`find unfat-$$ -name $darwin_file -print | $NL2SP` + lipo -create -output "$darwin_file" $darwin_files + done # $darwin_filelist + ${rm}r unfat-$$ + cd "$darwin_orig_dir" + else + cd "$darwin_orig_dir" + func_extract_an_archive "$my_xdir" "$my_xabs" + fi # $darwin_arches + fi # $run + ;; + *) + func_extract_an_archive "$my_xdir" "$my_xabs" + ;; + esac + my_oldobjs="$my_oldobjs "`find $my_xdir -name \*.$objext -print -o -name \*.lo -print | $NL2SP` + done + func_extract_archives_result="$my_oldobjs" +} +# End of Shell function definitions +##################################### + +# Darwin sucks +eval std_shrext=\"$shrext_cmds\" + +disable_libs=no + +# Parse our command line options once, thoroughly. +while test "$#" -gt 0 +do + arg="$1" + shift + + case $arg in + -*=*) optarg=`$echo "X$arg" | $Xsed -e 's/[-_a-zA-Z0-9]*=//'` ;; + *) optarg= ;; + esac + + # If the previous option needs an argument, assign it. + if test -n "$prev"; then + case $prev in + execute_dlfiles) + execute_dlfiles="$execute_dlfiles $arg" + ;; + tag) + tagname="$arg" + preserve_args="${preserve_args}=$arg" + + # Check whether tagname contains only valid characters + case $tagname in + *[!-_A-Za-z0-9,/]*) + $echo "$progname: invalid tag name: $tagname" 1>&2 + exit $EXIT_FAILURE + ;; + esac + + case $tagname in + CC) + # Don't test for the "default" C tag, as we know, it's there, but + # not specially marked. + ;; + *) + if grep "^# ### BEGIN LIBTOOL TAG CONFIG: $tagname$" < "$progpath" > /dev/null; then + taglist="$taglist $tagname" + # Evaluate the configuration. + eval "`${SED} -n -e '/^# ### BEGIN LIBTOOL TAG CONFIG: '$tagname'$/,/^# ### END LIBTOOL TAG CONFIG: '$tagname'$/p' < $progpath`" + else + $echo "$progname: ignoring unknown tag $tagname" 1>&2 + fi + ;; + esac + ;; + *) + eval "$prev=\$arg" + ;; + esac + + prev= + prevopt= + continue + fi + + # Have we seen a non-optional argument yet? + case $arg in + --help) + show_help=yes + ;; + + --version) + echo "\ +$PROGRAM (GNU $PACKAGE) $VERSION$TIMESTAMP + +Copyright (C) 2008 Free Software Foundation, Inc. +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + exit $? + ;; + + --config) + ${SED} -e '1,/^# ### BEGIN LIBTOOL CONFIG/d' -e '/^# ### END LIBTOOL CONFIG/,$d' $progpath + # Now print the configurations for the tags. + for tagname in $taglist; do + ${SED} -n -e "/^# ### BEGIN LIBTOOL TAG CONFIG: $tagname$/,/^# ### END LIBTOOL TAG CONFIG: $tagname$/p" < "$progpath" + done + exit $? + ;; + + --debug) + $echo "$progname: enabling shell trace mode" + set -x + preserve_args="$preserve_args $arg" + ;; + + --dry-run | -n) + run=: + ;; + + --features) + $echo "host: $host" + if test "$build_libtool_libs" = yes; then + $echo "enable shared libraries" + else + $echo "disable shared libraries" + fi + if test "$build_old_libs" = yes; then + $echo "enable static libraries" + else + $echo "disable static libraries" + fi + exit $? + ;; + + --finish) mode="finish" ;; + + --mode) prevopt="--mode" prev=mode ;; + --mode=*) mode="$optarg" ;; + + --preserve-dup-deps) duplicate_deps="yes" ;; + + --quiet | --silent) + show=: + preserve_args="$preserve_args $arg" + ;; + + --tag) + prevopt="--tag" + prev=tag + preserve_args="$preserve_args --tag" + ;; + --tag=*) + set tag "$optarg" ${1+"$@"} + shift + prev=tag + preserve_args="$preserve_args --tag" + ;; + + -dlopen) + prevopt="-dlopen" + prev=execute_dlfiles + ;; + + -*) + $echo "$modename: unrecognized option \`$arg'" 1>&2 + $echo "$help" 1>&2 + exit $EXIT_FAILURE + ;; + + *) + nonopt="$arg" + break + ;; + esac +done + +if test -n "$prevopt"; then + $echo "$modename: option \`$prevopt' requires an argument" 1>&2 + $echo "$help" 1>&2 + exit $EXIT_FAILURE +fi + +case $disable_libs in +no) + ;; +shared) + build_libtool_libs=no + build_old_libs=yes + ;; +static) + build_old_libs=`case $build_libtool_libs in yes) echo no;; *) echo yes;; esac` + ;; +esac + +# If this variable is set in any of the actions, the command in it +# will be execed at the end. This prevents here-documents from being +# left over by shells. +exec_cmd= + +if test -z "$show_help"; then + + # Infer the operation mode. + if test -z "$mode"; then + $echo "*** Warning: inferring the mode of operation is deprecated." 1>&2 + $echo "*** Future versions of Libtool will require --mode=MODE be specified." 1>&2 + case $nonopt in + *cc | cc* | *++ | gcc* | *-gcc* | g++* | xlc*) + mode=link + for arg + do + case $arg in + -c) + mode=compile + break + ;; + esac + done + ;; + *db | *dbx | *strace | *truss) + mode=execute + ;; + *install*|cp|mv) + mode=install + ;; + *rm) + mode=uninstall + ;; + *) + # If we have no mode, but dlfiles were specified, then do execute mode. + test -n "$execute_dlfiles" && mode=execute + + # Just use the default operation mode. + if test -z "$mode"; then + if test -n "$nonopt"; then + $echo "$modename: warning: cannot infer operation mode from \`$nonopt'" 1>&2 + else + $echo "$modename: warning: cannot infer operation mode without MODE-ARGS" 1>&2 + fi + fi + ;; + esac + fi + + # Only execute mode is allowed to have -dlopen flags. + if test -n "$execute_dlfiles" && test "$mode" != execute; then + $echo "$modename: unrecognized option \`-dlopen'" 1>&2 + $echo "$help" 1>&2 + exit $EXIT_FAILURE + fi + + # Change the help message to a mode-specific one. + generic_help="$help" + help="Try \`$modename --help --mode=$mode' for more information." + + # These modes are in order of execution frequency so that they run quickly. + case $mode in + # libtool compile mode + compile) + modename="$modename: compile" + # Get the compilation command and the source file. + base_compile= + srcfile="$nonopt" # always keep a non-empty value in "srcfile" + suppress_opt=yes + suppress_output= + arg_mode=normal + libobj= + later= + + for arg + do + case $arg_mode in + arg ) + # do not "continue". Instead, add this to base_compile + lastarg="$arg" + arg_mode=normal + ;; + + target ) + libobj="$arg" + arg_mode=normal + continue + ;; + + normal ) + # Accept any command-line options. + case $arg in + -o) + if test -n "$libobj" ; then + $echo "$modename: you cannot specify \`-o' more than once" 1>&2 + exit $EXIT_FAILURE + fi + arg_mode=target + continue + ;; + + -static | -prefer-pic | -prefer-non-pic) + later="$later $arg" + continue + ;; + + -no-suppress) + suppress_opt=no + continue + ;; + + -Xcompiler) + arg_mode=arg # the next one goes into the "base_compile" arg list + continue # The current "srcfile" will either be retained or + ;; # replaced later. I would guess that would be a bug. + + -Wc,*) + args=`$echo "X$arg" | $Xsed -e "s/^-Wc,//"` + lastarg= + save_ifs="$IFS"; IFS=',' + for arg in $args; do + IFS="$save_ifs" + + # Double-quote args containing other shell metacharacters. + # Many Bourne shells cannot handle close brackets correctly + # in scan sets, so we specify it separately. + case $arg in + *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"") + arg="\"$arg\"" + ;; + esac + lastarg="$lastarg $arg" + done + IFS="$save_ifs" + lastarg=`$echo "X$lastarg" | $Xsed -e "s/^ //"` + + # Add the arguments to base_compile. + base_compile="$base_compile $lastarg" + continue + ;; + + * ) + # Accept the current argument as the source file. + # The previous "srcfile" becomes the current argument. + # + lastarg="$srcfile" + srcfile="$arg" + ;; + esac # case $arg + ;; + esac # case $arg_mode + + # Aesthetically quote the previous argument. + lastarg=`$echo "X$lastarg" | $Xsed -e "$sed_quote_subst"` + + case $lastarg in + # Double-quote args containing other shell metacharacters. + # Many Bourne shells cannot handle close brackets correctly + # in scan sets, and some SunOS ksh mistreat backslash-escaping + # in scan sets (worked around with variable expansion), + # and furthermore cannot handle '|' '&' '(' ')' in scan sets + # at all, so we specify them separately. + *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"") + lastarg="\"$lastarg\"" + ;; + esac + + base_compile="$base_compile $lastarg" + done # for arg + + case $arg_mode in + arg) + $echo "$modename: you must specify an argument for -Xcompile" + exit $EXIT_FAILURE + ;; + target) + $echo "$modename: you must specify a target with \`-o'" 1>&2 + exit $EXIT_FAILURE + ;; + *) + # Get the name of the library object. + [ -z "$libobj" ] && libobj=`$echo "X$srcfile" | $Xsed -e 's%^.*/%%'` + ;; + esac + + # Recognize several different file suffixes. + # If the user specifies -o file.o, it is replaced with file.lo + xform='[cCFSifmso]' + case $libobj in + *.ada) xform=ada ;; + *.adb) xform=adb ;; + *.ads) xform=ads ;; + *.asm) xform=asm ;; + *.c++) xform=c++ ;; + *.cc) xform=cc ;; + *.ii) xform=ii ;; + *.class) xform=class ;; + *.cpp) xform=cpp ;; + *.cxx) xform=cxx ;; + *.[fF][09]?) xform=[fF][09]. ;; + *.for) xform=for ;; + *.java) xform=java ;; + *.obj) xform=obj ;; + *.sx) xform=sx ;; + esac + + libobj=`$echo "X$libobj" | $Xsed -e "s/\.$xform$/.lo/"` + + case $libobj in + *.lo) obj=`$echo "X$libobj" | $Xsed -e "$lo2o"` ;; + *) + $echo "$modename: cannot determine name of library object from \`$libobj'" 1>&2 + exit $EXIT_FAILURE + ;; + esac + + func_infer_tag $base_compile + + for arg in $later; do + case $arg in + -static) + build_old_libs=yes + continue + ;; + + -prefer-pic) + pic_mode=yes + continue + ;; + + -prefer-non-pic) + pic_mode=no + continue + ;; + esac + done + + qlibobj=`$echo "X$libobj" | $Xsed -e "$sed_quote_subst"` + case $qlibobj in + *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"") + qlibobj="\"$qlibobj\"" ;; + esac + test "X$libobj" != "X$qlibobj" \ + && $echo "X$libobj" | grep '[]~#^*{};<>?"'"'"' &()|`$[]' \ + && $echo "$modename: libobj name \`$libobj' may not contain shell special characters." + objname=`$echo "X$obj" | $Xsed -e 's%^.*/%%'` + xdir=`$echo "X$obj" | $Xsed -e 's%/[^/]*$%%'` + if test "X$xdir" = "X$obj"; then + xdir= + else + xdir=$xdir/ + fi + lobj=${xdir}$objdir/$objname + + if test -z "$base_compile"; then + $echo "$modename: you must specify a compilation command" 1>&2 + $echo "$help" 1>&2 + exit $EXIT_FAILURE + fi + + # Delete any leftover library objects. + if test "$build_old_libs" = yes; then + removelist="$obj $lobj $libobj ${libobj}T" + else + removelist="$lobj $libobj ${libobj}T" + fi + + $run $rm $removelist + trap "$run $rm $removelist; exit $EXIT_FAILURE" 1 2 15 + + # On Cygwin there's no "real" PIC flag so we must build both object types + case $host_os in + cygwin* | mingw* | pw32* | os2*) + pic_mode=default + ;; + esac + if test "$pic_mode" = no && test "$deplibs_check_method" != pass_all; then + # non-PIC code in shared libraries is not supported + pic_mode=default + fi + + # Calculate the filename of the output object if compiler does + # not support -o with -c + if test "$compiler_c_o" = no; then + output_obj=`$echo "X$srcfile" | $Xsed -e 's%^.*/%%' -e 's%\.[^.]*$%%'`.${objext} + lockfile="$output_obj.lock" + removelist="$removelist $output_obj $lockfile" + trap "$run $rm $removelist; exit $EXIT_FAILURE" 1 2 15 + else + output_obj= + need_locks=no + lockfile= + fi + + # Lock this critical section if it is needed + # We use this script file to make the link, it avoids creating a new file + if test "$need_locks" = yes; then + until $run ln "$progpath" "$lockfile" 2>/dev/null; do + $show "Waiting for $lockfile to be removed" + sleep 2 + done + elif test "$need_locks" = warn; then + if test -f "$lockfile"; then + $echo "\ +*** ERROR, $lockfile exists and contains: +`cat $lockfile 2>/dev/null` + +This indicates that another process is trying to use the same +temporary object file, and libtool could not work around it because +your compiler does not support \`-c' and \`-o' together. If you +repeat this compilation, it may succeed, by chance, but you had better +avoid parallel builds (make -j) in this platform, or get a better +compiler." + + $run $rm $removelist + exit $EXIT_FAILURE + fi + $echo "$srcfile" > "$lockfile" + fi + + if test -n "$fix_srcfile_path"; then + eval srcfile=\"$fix_srcfile_path\" + fi + qsrcfile=`$echo "X$srcfile" | $Xsed -e "$sed_quote_subst"` + case $qsrcfile in + *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"") + qsrcfile="\"$qsrcfile\"" ;; + esac + + $run $rm "$libobj" "${libobj}T" + + # Create a libtool object file (analogous to a ".la" file), + # but don't create it if we're doing a dry run. + test -z "$run" && cat > ${libobj}T </dev/null`" != "X$srcfile"; then + $echo "\ +*** ERROR, $lockfile contains: +`cat $lockfile 2>/dev/null` + +but it should contain: +$srcfile + +This indicates that another process is trying to use the same +temporary object file, and libtool could not work around it because +your compiler does not support \`-c' and \`-o' together. If you +repeat this compilation, it may succeed, by chance, but you had better +avoid parallel builds (make -j) in this platform, or get a better +compiler." + + $run $rm $removelist + exit $EXIT_FAILURE + fi + + # Just move the object if needed, then go on to compile the next one + if test -n "$output_obj" && test "X$output_obj" != "X$lobj"; then + $show "$mv $output_obj $lobj" + if $run $mv $output_obj $lobj; then : + else + error=$? + $run $rm $removelist + exit $error + fi + fi + + # Append the name of the PIC object to the libtool object file. + test -z "$run" && cat >> ${libobj}T <> ${libobj}T </dev/null`" != "X$srcfile"; then + $echo "\ +*** ERROR, $lockfile contains: +`cat $lockfile 2>/dev/null` + +but it should contain: +$srcfile + +This indicates that another process is trying to use the same +temporary object file, and libtool could not work around it because +your compiler does not support \`-c' and \`-o' together. If you +repeat this compilation, it may succeed, by chance, but you had better +avoid parallel builds (make -j) in this platform, or get a better +compiler." + + $run $rm $removelist + exit $EXIT_FAILURE + fi + + # Just move the object if needed + if test -n "$output_obj" && test "X$output_obj" != "X$obj"; then + $show "$mv $output_obj $obj" + if $run $mv $output_obj $obj; then : + else + error=$? + $run $rm $removelist + exit $error + fi + fi + + # Append the name of the non-PIC object the libtool object file. + # Only append if the libtool object file exists. + test -z "$run" && cat >> ${libobj}T <> ${libobj}T <&2 + fi + if test -n "$link_static_flag"; then + dlopen_self=$dlopen_self_static + fi + prefer_static_libs=yes + ;; + -static) + if test -z "$pic_flag" && test -n "$link_static_flag"; then + dlopen_self=$dlopen_self_static + fi + prefer_static_libs=built + ;; + -static-libtool-libs) + if test -z "$pic_flag" && test -n "$link_static_flag"; then + dlopen_self=$dlopen_self_static + fi + prefer_static_libs=yes + ;; + esac + build_libtool_libs=no + build_old_libs=yes + break + ;; + esac + done + + # See if our shared archives depend on static archives. + test -n "$old_archive_from_new_cmds" && build_old_libs=yes + + # Go through the arguments, transforming them on the way. + while test "$#" -gt 0; do + arg="$1" + shift + case $arg in + *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"") + qarg=\"`$echo "X$arg" | $Xsed -e "$sed_quote_subst"`\" ### testsuite: skip nested quoting test + ;; + *) qarg=$arg ;; + esac + libtool_args="$libtool_args $qarg" + + # If the previous option needs an argument, assign it. + if test -n "$prev"; then + case $prev in + output) + compile_command="$compile_command @OUTPUT@" + finalize_command="$finalize_command @OUTPUT@" + ;; + esac + + case $prev in + dlfiles|dlprefiles) + if test "$preload" = no; then + # Add the symbol object into the linking commands. + compile_command="$compile_command @SYMFILE@" + finalize_command="$finalize_command @SYMFILE@" + preload=yes + fi + case $arg in + *.la | *.lo) ;; # We handle these cases below. + force) + if test "$dlself" = no; then + dlself=needless + export_dynamic=yes + fi + prev= + continue + ;; + self) + if test "$prev" = dlprefiles; then + dlself=yes + elif test "$prev" = dlfiles && test "$dlopen_self" != yes; then + dlself=yes + else + dlself=needless + export_dynamic=yes + fi + prev= + continue + ;; + *) + if test "$prev" = dlfiles; then + dlfiles="$dlfiles $arg" + else + dlprefiles="$dlprefiles $arg" + fi + prev= + continue + ;; + esac + ;; + expsyms) + export_symbols="$arg" + if test ! -f "$arg"; then + $echo "$modename: symbol file \`$arg' does not exist" + exit $EXIT_FAILURE + fi + prev= + continue + ;; + expsyms_regex) + export_symbols_regex="$arg" + prev= + continue + ;; + inst_prefix) + inst_prefix_dir="$arg" + prev= + continue + ;; + precious_regex) + precious_files_regex="$arg" + prev= + continue + ;; + release) + release="-$arg" + prev= + continue + ;; + objectlist) + if test -f "$arg"; then + save_arg=$arg + moreargs= + for fil in `cat $save_arg` + do +# moreargs="$moreargs $fil" + arg=$fil + # A libtool-controlled object. + + # Check to see that this really is a libtool object. + if (${SED} -e '2q' $arg | grep "^# Generated by .*$PACKAGE") >/dev/null 2>&1; then + pic_object= + non_pic_object= + + # Read the .lo file + # If there is no directory component, then add one. + case $arg in + */* | *\\*) . $arg ;; + *) . ./$arg ;; + esac + + if test -z "$pic_object" || \ + test -z "$non_pic_object" || + test "$pic_object" = none && \ + test "$non_pic_object" = none; then + $echo "$modename: cannot find name of object for \`$arg'" 1>&2 + exit $EXIT_FAILURE + fi + + # Extract subdirectory from the argument. + xdir=`$echo "X$arg" | $Xsed -e 's%/[^/]*$%%'` + if test "X$xdir" = "X$arg"; then + xdir= + else + xdir="$xdir/" + fi + + if test "$pic_object" != none; then + # Prepend the subdirectory the object is found in. + pic_object="$xdir$pic_object" + + if test "$prev" = dlfiles; then + if test "$build_libtool_libs" = yes && test "$dlopen_support" = yes; then + dlfiles="$dlfiles $pic_object" + prev= + continue + else + # If libtool objects are unsupported, then we need to preload. + prev=dlprefiles + fi + fi + + # CHECK ME: I think I busted this. -Ossama + if test "$prev" = dlprefiles; then + # Preload the old-style object. + dlprefiles="$dlprefiles $pic_object" + prev= + fi + + # A PIC object. + libobjs="$libobjs $pic_object" + arg="$pic_object" + fi + + # Non-PIC object. + if test "$non_pic_object" != none; then + # Prepend the subdirectory the object is found in. + non_pic_object="$xdir$non_pic_object" + + # A standard non-PIC object + non_pic_objects="$non_pic_objects $non_pic_object" + if test -z "$pic_object" || test "$pic_object" = none ; then + arg="$non_pic_object" + fi + else + # If the PIC object exists, use it instead. + # $xdir was prepended to $pic_object above. + non_pic_object="$pic_object" + non_pic_objects="$non_pic_objects $non_pic_object" + fi + else + # Only an error if not doing a dry-run. + if test -z "$run"; then + $echo "$modename: \`$arg' is not a valid libtool object" 1>&2 + exit $EXIT_FAILURE + else + # Dry-run case. + + # Extract subdirectory from the argument. + xdir=`$echo "X$arg" | $Xsed -e 's%/[^/]*$%%'` + if test "X$xdir" = "X$arg"; then + xdir= + else + xdir="$xdir/" + fi + + pic_object=`$echo "X${xdir}${objdir}/${arg}" | $Xsed -e "$lo2o"` + non_pic_object=`$echo "X${xdir}${arg}" | $Xsed -e "$lo2o"` + libobjs="$libobjs $pic_object" + non_pic_objects="$non_pic_objects $non_pic_object" + fi + fi + done + else + $echo "$modename: link input file \`$save_arg' does not exist" + exit $EXIT_FAILURE + fi + arg=$save_arg + prev= + continue + ;; + rpath | xrpath) + # We need an absolute path. + case $arg in + [\\/]* | [A-Za-z]:[\\/]*) ;; + *) + $echo "$modename: only absolute run-paths are allowed" 1>&2 + exit $EXIT_FAILURE + ;; + esac + if test "$prev" = rpath; then + case "$rpath " in + *" $arg "*) ;; + *) rpath="$rpath $arg" ;; + esac + else + case "$xrpath " in + *" $arg "*) ;; + *) xrpath="$xrpath $arg" ;; + esac + fi + prev= + continue + ;; + xcompiler) + compiler_flags="$compiler_flags $qarg" + prev= + compile_command="$compile_command $qarg" + finalize_command="$finalize_command $qarg" + continue + ;; + xlinker) + linker_flags="$linker_flags $qarg" + compiler_flags="$compiler_flags $wl$qarg" + prev= + compile_command="$compile_command $wl$qarg" + finalize_command="$finalize_command $wl$qarg" + continue + ;; + xcclinker) + linker_flags="$linker_flags $qarg" + compiler_flags="$compiler_flags $qarg" + prev= + compile_command="$compile_command $qarg" + finalize_command="$finalize_command $qarg" + continue + ;; + shrext) + shrext_cmds="$arg" + prev= + continue + ;; + darwin_framework|darwin_framework_skip) + test "$prev" = "darwin_framework" && compiler_flags="$compiler_flags $arg" + compile_command="$compile_command $arg" + finalize_command="$finalize_command $arg" + prev= + continue + ;; + *) + eval "$prev=\"\$arg\"" + prev= + continue + ;; + esac + fi # test -n "$prev" + + prevarg="$arg" + + case $arg in + -all-static) + if test -n "$link_static_flag"; then + compile_command="$compile_command $link_static_flag" + finalize_command="$finalize_command $link_static_flag" + fi + continue + ;; + + -allow-undefined) + # FIXME: remove this flag sometime in the future. + $echo "$modename: \`-allow-undefined' is deprecated because it is the default" 1>&2 + continue + ;; + + -avoid-version) + avoid_version=yes + continue + ;; + + -dlopen) + prev=dlfiles + continue + ;; + + -dlpreopen) + prev=dlprefiles + continue + ;; + + -export-dynamic) + export_dynamic=yes + continue + ;; + + -export-symbols | -export-symbols-regex) + if test -n "$export_symbols" || test -n "$export_symbols_regex"; then + $echo "$modename: more than one -exported-symbols argument is not allowed" + exit $EXIT_FAILURE + fi + if test "X$arg" = "X-export-symbols"; then + prev=expsyms + else + prev=expsyms_regex + fi + continue + ;; + + -framework|-arch|-isysroot) + case " $CC " in + *" ${arg} ${1} "* | *" ${arg} ${1} "*) + prev=darwin_framework_skip ;; + *) compiler_flags="$compiler_flags $arg" + prev=darwin_framework ;; + esac + compile_command="$compile_command $arg" + finalize_command="$finalize_command $arg" + continue + ;; + + -inst-prefix-dir) + prev=inst_prefix + continue + ;; + + # The native IRIX linker understands -LANG:*, -LIST:* and -LNO:* + # so, if we see these flags be careful not to treat them like -L + -L[A-Z][A-Z]*:*) + case $with_gcc/$host in + no/*-*-irix* | /*-*-irix*) + compile_command="$compile_command $arg" + finalize_command="$finalize_command $arg" + ;; + esac + continue + ;; + + -L*) + dir=`$echo "X$arg" | $Xsed -e 's/^-L//'` + # We need an absolute path. + case $dir in + [\\/]* | [A-Za-z]:[\\/]*) ;; + *) + absdir=`cd "$dir" && pwd` + if test -z "$absdir"; then + $echo "$modename: cannot determine absolute directory name of \`$dir'" 1>&2 + absdir="$dir" + notinst_path="$notinst_path $dir" + fi + dir="$absdir" + ;; + esac + case "$deplibs " in + *" -L$dir "*) ;; + *) + deplibs="$deplibs -L$dir" + lib_search_path="$lib_search_path $dir" + ;; + esac + case $host in + *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2*) + testbindir=`$echo "X$dir" | $Xsed -e 's*/lib$*/bin*'` + case :$dllsearchpath: in + *":$dir:"*) ;; + *) dllsearchpath="$dllsearchpath:$dir";; + esac + case :$dllsearchpath: in + *":$testbindir:"*) ;; + *) dllsearchpath="$dllsearchpath:$testbindir";; + esac + ;; + esac + continue + ;; + + -l*) + if test "X$arg" = "X-lc" || test "X$arg" = "X-lm"; then + case $host in + *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-beos*) + # These systems don't actually have a C or math library (as such) + continue + ;; + *-*-os2*) + # These systems don't actually have a C library (as such) + test "X$arg" = "X-lc" && continue + ;; + *-*-openbsd* | *-*-freebsd* | *-*-dragonfly*) + # Do not include libc due to us having libc/libc_r. + test "X$arg" = "X-lc" && continue + ;; + *-*-rhapsody* | *-*-darwin1.[012]) + # Rhapsody C and math libraries are in the System framework + deplibs="$deplibs -framework System" + continue + ;; + *-*-sco3.2v5* | *-*-sco5v6*) + # Causes problems with __ctype + test "X$arg" = "X-lc" && continue + ;; + *-*-sysv4.2uw2* | *-*-sysv5* | *-*-unixware* | *-*-OpenUNIX*) + # Compiler inserts libc in the correct place for threads to work + test "X$arg" = "X-lc" && continue + ;; + esac + elif test "X$arg" = "X-lc_r"; then + case $host in + *-*-openbsd* | *-*-freebsd* | *-*-dragonfly*) + # Do not include libc_r directly, use -pthread flag. + continue + ;; + esac + fi + deplibs="$deplibs $arg" + continue + ;; + + # Tru64 UNIX uses -model [arg] to determine the layout of C++ + # classes, name mangling, and exception handling. + -model) + compile_command="$compile_command $arg" + compiler_flags="$compiler_flags $arg" + finalize_command="$finalize_command $arg" + prev=xcompiler + continue + ;; + + -mt|-mthreads|-kthread|-Kthread|-pthread|-pthreads|--thread-safe|-threads) + compiler_flags="$compiler_flags $arg" + compile_command="$compile_command $arg" + finalize_command="$finalize_command $arg" + continue + ;; + + -multi_module) + single_module="${wl}-multi_module" + continue + ;; + + -module) + module=yes + continue + ;; + + # -64, -mips[0-9] enable 64-bit mode on the SGI compiler + # -r[0-9][0-9]* specifies the processor on the SGI compiler + # -xarch=*, -xtarget=* enable 64-bit mode on the Sun compiler + # +DA*, +DD* enable 64-bit mode on the HP compiler + # -q* pass through compiler args for the IBM compiler + # -m* pass through architecture-specific compiler args for GCC + # -m*, -t[45]*, -txscale* pass through architecture-specific + # compiler args for GCC + # -p, -pg, --coverage, -fprofile-* pass through profiling flag for GCC + # -F/path gives path to uninstalled frameworks, gcc on darwin + # @file GCC response files + -64|-mips[0-9]|-r[0-9][0-9]*|-xarch=*|-xtarget=*|+DA*|+DD*|-q*|-m*| \ + -t[45]*|-txscale*|-p|-pg|--coverage|-fprofile-*|-F*|@*) + + # Unknown arguments in both finalize_command and compile_command need + # to be aesthetically quoted because they are evaled later. + arg=`$echo "X$arg" | $Xsed -e "$sed_quote_subst"` + case $arg in + *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"") + arg="\"$arg\"" + ;; + esac + compile_command="$compile_command $arg" + finalize_command="$finalize_command $arg" + compiler_flags="$compiler_flags $arg" + continue + ;; + + -shrext) + prev=shrext + continue + ;; + + -no-fast-install) + fast_install=no + continue + ;; + + -no-install) + case $host in + *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-*-darwin*) + # The PATH hackery in wrapper scripts is required on Windows + # and Darwin in order for the loader to find any dlls it needs. + $echo "$modename: warning: \`-no-install' is ignored for $host" 1>&2 + $echo "$modename: warning: assuming \`-no-fast-install' instead" 1>&2 + fast_install=no + ;; + *) no_install=yes ;; + esac + continue + ;; + + -no-undefined) + allow_undefined=no + continue + ;; + + -objectlist) + prev=objectlist + continue + ;; + + -o) prev=output ;; + + -precious-files-regex) + prev=precious_regex + continue + ;; + + -release) + prev=release + continue + ;; + + -rpath) + prev=rpath + continue + ;; + + -R) + prev=xrpath + continue + ;; + + -R*) + dir=`$echo "X$arg" | $Xsed -e 's/^-R//'` + # We need an absolute path. + case $dir in + [\\/]* | [A-Za-z]:[\\/]*) ;; + *) + $echo "$modename: only absolute run-paths are allowed" 1>&2 + exit $EXIT_FAILURE + ;; + esac + case "$xrpath " in + *" $dir "*) ;; + *) xrpath="$xrpath $dir" ;; + esac + continue + ;; + + -static | -static-libtool-libs) + # The effects of -static are defined in a previous loop. + # We used to do the same as -all-static on platforms that + # didn't have a PIC flag, but the assumption that the effects + # would be equivalent was wrong. It would break on at least + # Digital Unix and AIX. + continue + ;; + + -thread-safe) + thread_safe=yes + continue + ;; + + -version-info) + prev=vinfo + continue + ;; + -version-number) + prev=vinfo + vinfo_number=yes + continue + ;; + + -Wc,*) + args=`$echo "X$arg" | $Xsed -e "$sed_quote_subst" -e 's/^-Wc,//'` + arg= + save_ifs="$IFS"; IFS=',' + for flag in $args; do + IFS="$save_ifs" + case $flag in + *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"") + flag="\"$flag\"" + ;; + esac + arg="$arg $wl$flag" + compiler_flags="$compiler_flags $flag" + done + IFS="$save_ifs" + arg=`$echo "X$arg" | $Xsed -e "s/^ //"` + ;; + + -Wl,*) + args=`$echo "X$arg" | $Xsed -e "$sed_quote_subst" -e 's/^-Wl,//'` + arg= + save_ifs="$IFS"; IFS=',' + for flag in $args; do + IFS="$save_ifs" + case $flag in + *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"") + flag="\"$flag\"" + ;; + esac + arg="$arg $wl$flag" + compiler_flags="$compiler_flags $wl$flag" + linker_flags="$linker_flags $flag" + done + IFS="$save_ifs" + arg=`$echo "X$arg" | $Xsed -e "s/^ //"` + ;; + + -Xcompiler) + prev=xcompiler + continue + ;; + + -Xlinker) + prev=xlinker + continue + ;; + + -XCClinker) + prev=xcclinker + continue + ;; + + # Some other compiler flag. + -* | +*) + # Unknown arguments in both finalize_command and compile_command need + # to be aesthetically quoted because they are evaled later. + arg=`$echo "X$arg" | $Xsed -e "$sed_quote_subst"` + case $arg in + *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"") + arg="\"$arg\"" + ;; + esac + ;; + + *.$objext) + # A standard object. + objs="$objs $arg" + ;; + + *.lo) + # A libtool-controlled object. + + # Check to see that this really is a libtool object. + if (${SED} -e '2q' $arg | grep "^# Generated by .*$PACKAGE") >/dev/null 2>&1; then + pic_object= + non_pic_object= + + # Read the .lo file + # If there is no directory component, then add one. + case $arg in + */* | *\\*) . $arg ;; + *) . ./$arg ;; + esac + + if test -z "$pic_object" || \ + test -z "$non_pic_object" || + test "$pic_object" = none && \ + test "$non_pic_object" = none; then + $echo "$modename: cannot find name of object for \`$arg'" 1>&2 + exit $EXIT_FAILURE + fi + + # Extract subdirectory from the argument. + xdir=`$echo "X$arg" | $Xsed -e 's%/[^/]*$%%'` + if test "X$xdir" = "X$arg"; then + xdir= + else + xdir="$xdir/" + fi + + if test "$pic_object" != none; then + # Prepend the subdirectory the object is found in. + pic_object="$xdir$pic_object" + + if test "$prev" = dlfiles; then + if test "$build_libtool_libs" = yes && test "$dlopen_support" = yes; then + dlfiles="$dlfiles $pic_object" + prev= + continue + else + # If libtool objects are unsupported, then we need to preload. + prev=dlprefiles + fi + fi + + # CHECK ME: I think I busted this. -Ossama + if test "$prev" = dlprefiles; then + # Preload the old-style object. + dlprefiles="$dlprefiles $pic_object" + prev= + fi + + # A PIC object. + libobjs="$libobjs $pic_object" + arg="$pic_object" + fi + + # Non-PIC object. + if test "$non_pic_object" != none; then + # Prepend the subdirectory the object is found in. + non_pic_object="$xdir$non_pic_object" + + # A standard non-PIC object + non_pic_objects="$non_pic_objects $non_pic_object" + if test -z "$pic_object" || test "$pic_object" = none ; then + arg="$non_pic_object" + fi + else + # If the PIC object exists, use it instead. + # $xdir was prepended to $pic_object above. + non_pic_object="$pic_object" + non_pic_objects="$non_pic_objects $non_pic_object" + fi + else + # Only an error if not doing a dry-run. + if test -z "$run"; then + $echo "$modename: \`$arg' is not a valid libtool object" 1>&2 + exit $EXIT_FAILURE + else + # Dry-run case. + + # Extract subdirectory from the argument. + xdir=`$echo "X$arg" | $Xsed -e 's%/[^/]*$%%'` + if test "X$xdir" = "X$arg"; then + xdir= + else + xdir="$xdir/" + fi + + pic_object=`$echo "X${xdir}${objdir}/${arg}" | $Xsed -e "$lo2o"` + non_pic_object=`$echo "X${xdir}${arg}" | $Xsed -e "$lo2o"` + libobjs="$libobjs $pic_object" + non_pic_objects="$non_pic_objects $non_pic_object" + fi + fi + ;; + + *.$libext) + # An archive. + deplibs="$deplibs $arg" + old_deplibs="$old_deplibs $arg" + continue + ;; + + *.la) + # A libtool-controlled library. + + if test "$prev" = dlfiles; then + # This library was specified with -dlopen. + dlfiles="$dlfiles $arg" + prev= + elif test "$prev" = dlprefiles; then + # The library was specified with -dlpreopen. + dlprefiles="$dlprefiles $arg" + prev= + else + deplibs="$deplibs $arg" + fi + continue + ;; + + # Some other compiler argument. + *) + # Unknown arguments in both finalize_command and compile_command need + # to be aesthetically quoted because they are evaled later. + arg=`$echo "X$arg" | $Xsed -e "$sed_quote_subst"` + case $arg in + *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"") + arg="\"$arg\"" + ;; + esac + ;; + esac # arg + + # Now actually substitute the argument into the commands. + if test -n "$arg"; then + compile_command="$compile_command $arg" + finalize_command="$finalize_command $arg" + fi + done # argument parsing loop + + if test -n "$prev"; then + $echo "$modename: the \`$prevarg' option requires an argument" 1>&2 + $echo "$help" 1>&2 + exit $EXIT_FAILURE + fi + + if test "$export_dynamic" = yes && test -n "$export_dynamic_flag_spec"; then + eval arg=\"$export_dynamic_flag_spec\" + compile_command="$compile_command $arg" + finalize_command="$finalize_command $arg" + fi + + oldlibs= + # calculate the name of the file, without its directory + outputname=`$echo "X$output" | $Xsed -e 's%^.*/%%'` + libobjs_save="$libobjs" + + if test -n "$shlibpath_var"; then + # get the directories listed in $shlibpath_var + eval shlib_search_path=\`\$echo \"X\${$shlibpath_var}\" \| \$Xsed -e \'s/:/ /g\'\` + else + shlib_search_path= + fi + eval sys_lib_search_path=\"$sys_lib_search_path_spec\" + eval sys_lib_dlsearch_path=\"$sys_lib_dlsearch_path_spec\" + + output_objdir=`$echo "X$output" | $Xsed -e 's%/[^/]*$%%'` + if test "X$output_objdir" = "X$output"; then + output_objdir="$objdir" + else + output_objdir="$output_objdir/$objdir" + fi + # Create the object directory. + if test ! -d "$output_objdir"; then + $show "$mkdir $output_objdir" + $run $mkdir $output_objdir + exit_status=$? + if test "$exit_status" -ne 0 && test ! -d "$output_objdir"; then + exit $exit_status + fi + fi + + # Determine the type of output + case $output in + "") + $echo "$modename: you must specify an output file" 1>&2 + $echo "$help" 1>&2 + exit $EXIT_FAILURE + ;; + *.$libext) linkmode=oldlib ;; + *.lo | *.$objext) linkmode=obj ;; + *.la) linkmode=lib ;; + *) linkmode=prog ;; # Anything else should be a program. + esac + + case $host in + *cygwin* | *mingw* | *pw32*) + # don't eliminate duplications in $postdeps and $predeps + duplicate_compiler_generated_deps=yes + ;; + *) + duplicate_compiler_generated_deps=$duplicate_deps + ;; + esac + specialdeplibs= + + libs= + # Find all interdependent deplibs by searching for libraries + # that are linked more than once (e.g. -la -lb -la) + for deplib in $deplibs; do + if test "X$duplicate_deps" = "Xyes" ; then + case "$libs " in + *" $deplib "*) specialdeplibs="$specialdeplibs $deplib" ;; + esac + fi + libs="$libs $deplib" + done + + if test "$linkmode" = lib; then + libs="$predeps $libs $compiler_lib_search_path $postdeps" + + # Compute libraries that are listed more than once in $predeps + # $postdeps and mark them as special (i.e., whose duplicates are + # not to be eliminated). + pre_post_deps= + if test "X$duplicate_compiler_generated_deps" = "Xyes" ; then + for pre_post_dep in $predeps $postdeps; do + case "$pre_post_deps " in + *" $pre_post_dep "*) specialdeplibs="$specialdeplibs $pre_post_deps" ;; + esac + pre_post_deps="$pre_post_deps $pre_post_dep" + done + fi + pre_post_deps= + fi + + deplibs= + newdependency_libs= + newlib_search_path= + need_relink=no # whether we're linking any uninstalled libtool libraries + notinst_deplibs= # not-installed libtool libraries + case $linkmode in + lib) + passes="conv link" + for file in $dlfiles $dlprefiles; do + case $file in + *.la) ;; + *) + $echo "$modename: libraries can \`-dlopen' only libtool libraries: $file" 1>&2 + exit $EXIT_FAILURE + ;; + esac + done + ;; + prog) + compile_deplibs= + finalize_deplibs= + alldeplibs=no + newdlfiles= + newdlprefiles= + passes="conv scan dlopen dlpreopen link" + ;; + *) passes="conv" + ;; + esac + for pass in $passes; do + if test "$linkmode,$pass" = "lib,link" || + test "$linkmode,$pass" = "prog,scan"; then + libs="$deplibs" + deplibs= + fi + if test "$linkmode" = prog; then + case $pass in + dlopen) libs="$dlfiles" ;; + dlpreopen) libs="$dlprefiles" ;; + link) libs="$deplibs %DEPLIBS% $dependency_libs" ;; + esac + fi + if test "$pass" = dlopen; then + # Collect dlpreopened libraries + save_deplibs="$deplibs" + deplibs= + fi + for deplib in $libs; do + lib= + found=no + case $deplib in + -mt|-mthreads|-kthread|-Kthread|-pthread|-pthreads|--thread-safe|-threads) + if test "$linkmode,$pass" = "prog,link"; then + compile_deplibs="$deplib $compile_deplibs" + finalize_deplibs="$deplib $finalize_deplibs" + else + compiler_flags="$compiler_flags $deplib" + fi + continue + ;; + -l*) + if test "$linkmode" != lib && test "$linkmode" != prog; then + $echo "$modename: warning: \`-l' is ignored for archives/objects" 1>&2 + continue + fi + name=`$echo "X$deplib" | $Xsed -e 's/^-l//'` + if test "$linkmode" = lib; then + searchdirs="$newlib_search_path $lib_search_path $compiler_lib_search_dirs $sys_lib_search_path $shlib_search_path" + else + searchdirs="$newlib_search_path $lib_search_path $sys_lib_search_path $shlib_search_path" + fi + for searchdir in $searchdirs; do + for search_ext in .la $std_shrext .so .a; do + # Search the libtool library + lib="$searchdir/lib${name}${search_ext}" + if test -f "$lib"; then + if test "$search_ext" = ".la"; then + found=yes + else + found=no + fi + break 2 + fi + done + done + if test "$found" != yes; then + # deplib doesn't seem to be a libtool library + if test "$linkmode,$pass" = "prog,link"; then + compile_deplibs="$deplib $compile_deplibs" + finalize_deplibs="$deplib $finalize_deplibs" + else + deplibs="$deplib $deplibs" + test "$linkmode" = lib && newdependency_libs="$deplib $newdependency_libs" + fi + continue + else # deplib is a libtool library + # If $allow_libtool_libs_with_static_runtimes && $deplib is a stdlib, + # We need to do some special things here, and not later. + if test "X$allow_libtool_libs_with_static_runtimes" = "Xyes" ; then + case " $predeps $postdeps " in + *" $deplib "*) + if (${SED} -e '2q' $lib | + grep "^# Generated by .*$PACKAGE") >/dev/null 2>&1; then + library_names= + old_library= + case $lib in + */* | *\\*) . $lib ;; + *) . ./$lib ;; + esac + for l in $old_library $library_names; do + ll="$l" + done + if test "X$ll" = "X$old_library" ; then # only static version available + found=no + ladir=`$echo "X$lib" | $Xsed -e 's%/[^/]*$%%'` + test "X$ladir" = "X$lib" && ladir="." + lib=$ladir/$old_library + if test "$linkmode,$pass" = "prog,link"; then + compile_deplibs="$deplib $compile_deplibs" + finalize_deplibs="$deplib $finalize_deplibs" + else + deplibs="$deplib $deplibs" + test "$linkmode" = lib && newdependency_libs="$deplib $newdependency_libs" + fi + continue + fi + fi + ;; + *) ;; + esac + fi + fi + ;; # -l + -L*) + case $linkmode in + lib) + deplibs="$deplib $deplibs" + test "$pass" = conv && continue + newdependency_libs="$deplib $newdependency_libs" + newlib_search_path="$newlib_search_path "`$echo "X$deplib" | $Xsed -e 's/^-L//'` + ;; + prog) + if test "$pass" = conv; then + deplibs="$deplib $deplibs" + continue + fi + if test "$pass" = scan; then + deplibs="$deplib $deplibs" + else + compile_deplibs="$deplib $compile_deplibs" + finalize_deplibs="$deplib $finalize_deplibs" + fi + newlib_search_path="$newlib_search_path "`$echo "X$deplib" | $Xsed -e 's/^-L//'` + ;; + *) + $echo "$modename: warning: \`-L' is ignored for archives/objects" 1>&2 + ;; + esac # linkmode + continue + ;; # -L + -R*) + if test "$pass" = link; then + dir=`$echo "X$deplib" | $Xsed -e 's/^-R//'` + # Make sure the xrpath contains only unique directories. + case "$xrpath " in + *" $dir "*) ;; + *) xrpath="$xrpath $dir" ;; + esac + fi + deplibs="$deplib $deplibs" + continue + ;; + *.la) lib="$deplib" ;; + *.$libext) + if test "$pass" = conv; then + deplibs="$deplib $deplibs" + continue + fi + case $linkmode in + lib) + valid_a_lib=no + case $deplibs_check_method in + match_pattern*) + set dummy $deplibs_check_method + match_pattern_regex=`expr "$deplibs_check_method" : "$2 \(.*\)"` + if eval $echo \"$deplib\" 2>/dev/null \ + | $SED 10q \ + | $EGREP "$match_pattern_regex" > /dev/null; then + valid_a_lib=yes + fi + ;; + pass_all) + valid_a_lib=yes + ;; + esac + if test "$valid_a_lib" != yes; then + $echo + $echo "*** Warning: Trying to link with static lib archive $deplib." + $echo "*** I have the capability to make that library automatically link in when" + $echo "*** you link to this library. But I can only do this if you have a" + $echo "*** shared version of the library, which you do not appear to have" + $echo "*** because the file extensions .$libext of this argument makes me believe" + $echo "*** that it is just a static archive that I should not used here." + else + $echo + $echo "*** Warning: Linking the shared library $output against the" + $echo "*** static library $deplib is not portable!" + deplibs="$deplib $deplibs" + fi + continue + ;; + prog) + if test "$pass" != link; then + deplibs="$deplib $deplibs" + else + compile_deplibs="$deplib $compile_deplibs" + finalize_deplibs="$deplib $finalize_deplibs" + fi + continue + ;; + esac # linkmode + ;; # *.$libext + *.lo | *.$objext) + if test "$pass" = conv; then + deplibs="$deplib $deplibs" + elif test "$linkmode" = prog; then + if test "$pass" = dlpreopen || test "$dlopen_support" != yes || test "$build_libtool_libs" = no; then + # If there is no dlopen support or we're linking statically, + # we need to preload. + newdlprefiles="$newdlprefiles $deplib" + compile_deplibs="$deplib $compile_deplibs" + finalize_deplibs="$deplib $finalize_deplibs" + else + newdlfiles="$newdlfiles $deplib" + fi + fi + continue + ;; + %DEPLIBS%) + alldeplibs=yes + continue + ;; + esac # case $deplib + if test "$found" = yes || test -f "$lib"; then : + else + $echo "$modename: cannot find the library \`$lib' or unhandled argument \`$deplib'" 1>&2 + exit $EXIT_FAILURE + fi + + # Check to see that this really is a libtool archive. + if (${SED} -e '2q' $lib | grep "^# Generated by .*$PACKAGE") >/dev/null 2>&1; then : + else + $echo "$modename: \`$lib' is not a valid libtool archive" 1>&2 + exit $EXIT_FAILURE + fi + + ladir=`$echo "X$lib" | $Xsed -e 's%/[^/]*$%%'` + test "X$ladir" = "X$lib" && ladir="." + + dlname= + dlopen= + dlpreopen= + libdir= + library_names= + old_library= + # If the library was installed with an old release of libtool, + # it will not redefine variables installed, or shouldnotlink + installed=yes + shouldnotlink=no + avoidtemprpath= + + + # Read the .la file + case $lib in + */* | *\\*) . $lib ;; + *) . ./$lib ;; + esac + + if test "$linkmode,$pass" = "lib,link" || + test "$linkmode,$pass" = "prog,scan" || + { test "$linkmode" != prog && test "$linkmode" != lib; }; then + test -n "$dlopen" && dlfiles="$dlfiles $dlopen" + test -n "$dlpreopen" && dlprefiles="$dlprefiles $dlpreopen" + fi + + if test "$pass" = conv; then + # Only check for convenience libraries + deplibs="$lib $deplibs" + if test -z "$libdir"; then + if test -z "$old_library"; then + $echo "$modename: cannot find name of link library for \`$lib'" 1>&2 + exit $EXIT_FAILURE + fi + # It is a libtool convenience library, so add in its objects. + convenience="$convenience $ladir/$objdir/$old_library" + old_convenience="$old_convenience $ladir/$objdir/$old_library" + tmp_libs= + for deplib in $dependency_libs; do + deplibs="$deplib $deplibs" + if test "X$duplicate_deps" = "Xyes" ; then + case "$tmp_libs " in + *" $deplib "*) specialdeplibs="$specialdeplibs $deplib" ;; + esac + fi + tmp_libs="$tmp_libs $deplib" + done + elif test "$linkmode" != prog && test "$linkmode" != lib; then + $echo "$modename: \`$lib' is not a convenience library" 1>&2 + exit $EXIT_FAILURE + fi + continue + fi # $pass = conv + + + # Get the name of the library we link against. + linklib= + for l in $old_library $library_names; do + linklib="$l" + done + if test -z "$linklib"; then + $echo "$modename: cannot find name of link library for \`$lib'" 1>&2 + exit $EXIT_FAILURE + fi + + # This library was specified with -dlopen. + if test "$pass" = dlopen; then + if test -z "$libdir"; then + $echo "$modename: cannot -dlopen a convenience library: \`$lib'" 1>&2 + exit $EXIT_FAILURE + fi + if test -z "$dlname" || + test "$dlopen_support" != yes || + test "$build_libtool_libs" = no; then + # If there is no dlname, no dlopen support or we're linking + # statically, we need to preload. We also need to preload any + # dependent libraries so libltdl's deplib preloader doesn't + # bomb out in the load deplibs phase. + dlprefiles="$dlprefiles $lib $dependency_libs" + else + newdlfiles="$newdlfiles $lib" + fi + continue + fi # $pass = dlopen + + # We need an absolute path. + case $ladir in + [\\/]* | [A-Za-z]:[\\/]*) abs_ladir="$ladir" ;; + *) + abs_ladir=`cd "$ladir" && pwd` + if test -z "$abs_ladir"; then + $echo "$modename: warning: cannot determine absolute directory name of \`$ladir'" 1>&2 + $echo "$modename: passing it literally to the linker, although it might fail" 1>&2 + abs_ladir="$ladir" + fi + ;; + esac + laname=`$echo "X$lib" | $Xsed -e 's%^.*/%%'` + + # Find the relevant object directory and library name. + if test "X$installed" = Xyes; then + if test ! -f "$libdir/$linklib" && test -f "$abs_ladir/$linklib"; then + $echo "$modename: warning: library \`$lib' was moved." 1>&2 + dir="$ladir" + absdir="$abs_ladir" + libdir="$abs_ladir" + else + dir="$libdir" + absdir="$libdir" + fi + test "X$hardcode_automatic" = Xyes && avoidtemprpath=yes + else + if test ! -f "$ladir/$objdir/$linklib" && test -f "$abs_ladir/$linklib"; then + dir="$ladir" + absdir="$abs_ladir" + # Remove this search path later + notinst_path="$notinst_path $abs_ladir" + else + dir="$ladir/$objdir" + absdir="$abs_ladir/$objdir" + # Remove this search path later + notinst_path="$notinst_path $abs_ladir" + fi + fi # $installed = yes + name=`$echo "X$laname" | $Xsed -e 's/\.la$//' -e 's/^lib//'` + + # This library was specified with -dlpreopen. + if test "$pass" = dlpreopen; then + if test -z "$libdir"; then + $echo "$modename: cannot -dlpreopen a convenience library: \`$lib'" 1>&2 + exit $EXIT_FAILURE + fi + # Prefer using a static library (so that no silly _DYNAMIC symbols + # are required to link). + if test -n "$old_library"; then + newdlprefiles="$newdlprefiles $dir/$old_library" + # Otherwise, use the dlname, so that lt_dlopen finds it. + elif test -n "$dlname"; then + newdlprefiles="$newdlprefiles $dir/$dlname" + else + newdlprefiles="$newdlprefiles $dir/$linklib" + fi + fi # $pass = dlpreopen + + if test -z "$libdir"; then + # Link the convenience library + if test "$linkmode" = lib; then + deplibs="$dir/$old_library $deplibs" + elif test "$linkmode,$pass" = "prog,link"; then + compile_deplibs="$dir/$old_library $compile_deplibs" + finalize_deplibs="$dir/$old_library $finalize_deplibs" + else + deplibs="$lib $deplibs" # used for prog,scan pass + fi + continue + fi + + + if test "$linkmode" = prog && test "$pass" != link; then + newlib_search_path="$newlib_search_path $ladir" + deplibs="$lib $deplibs" + + linkalldeplibs=no + if test "$link_all_deplibs" != no || test -z "$library_names" || + test "$build_libtool_libs" = no; then + linkalldeplibs=yes + fi + + tmp_libs= + for deplib in $dependency_libs; do + case $deplib in + -L*) newlib_search_path="$newlib_search_path "`$echo "X$deplib" | $Xsed -e 's/^-L//'`;; ### testsuite: skip nested quoting test + esac + # Need to link against all dependency_libs? + if test "$linkalldeplibs" = yes; then + deplibs="$deplib $deplibs" + else + # Need to hardcode shared library paths + # or/and link against static libraries + newdependency_libs="$deplib $newdependency_libs" + fi + if test "X$duplicate_deps" = "Xyes" ; then + case "$tmp_libs " in + *" $deplib "*) specialdeplibs="$specialdeplibs $deplib" ;; + esac + fi + tmp_libs="$tmp_libs $deplib" + done # for deplib + continue + fi # $linkmode = prog... + + if test "$linkmode,$pass" = "prog,link"; then + if test -n "$library_names" && + { { test "$prefer_static_libs" = no || + test "$prefer_static_libs,$installed" = "built,yes"; } || + test -z "$old_library"; }; then + # We need to hardcode the library path + if test -n "$shlibpath_var" && test -z "$avoidtemprpath" ; then + # Make sure the rpath contains only unique directories. + case "$temp_rpath " in + *" $dir "*) ;; + *" $absdir "*) ;; + *) temp_rpath="$temp_rpath $absdir" ;; + esac + fi + + # Hardcode the library path. + # Skip directories that are in the system default run-time + # search path. + case " $sys_lib_dlsearch_path " in + *" $absdir "*) ;; + *) + case "$compile_rpath " in + *" $absdir "*) ;; + *) compile_rpath="$compile_rpath $absdir" + esac + ;; + esac + case " $sys_lib_dlsearch_path " in + *" $libdir "*) ;; + *) + case "$finalize_rpath " in + *" $libdir "*) ;; + *) finalize_rpath="$finalize_rpath $libdir" + esac + ;; + esac + fi # $linkmode,$pass = prog,link... + + if test "$alldeplibs" = yes && + { test "$deplibs_check_method" = pass_all || + { test "$build_libtool_libs" = yes && + test -n "$library_names"; }; }; then + # We only need to search for static libraries + continue + fi + fi + + link_static=no # Whether the deplib will be linked statically + use_static_libs=$prefer_static_libs + if test "$use_static_libs" = built && test "$installed" = yes ; then + use_static_libs=no + fi + if test -n "$library_names" && + { test "$use_static_libs" = no || test -z "$old_library"; }; then + if test "$installed" = no; then + notinst_deplibs="$notinst_deplibs $lib" + need_relink=yes + fi + # This is a shared library + + # Warn about portability, can't link against -module's on + # some systems (darwin) + if test "$shouldnotlink" = yes && test "$pass" = link ; then + $echo + if test "$linkmode" = prog; then + $echo "*** Warning: Linking the executable $output against the loadable module" + else + $echo "*** Warning: Linking the shared library $output against the loadable module" + fi + $echo "*** $linklib is not portable!" + fi + if test "$linkmode" = lib && + test "$hardcode_into_libs" = yes; then + # Hardcode the library path. + # Skip directories that are in the system default run-time + # search path. + case " $sys_lib_dlsearch_path " in + *" $absdir "*) ;; + *) + case "$compile_rpath " in + *" $absdir "*) ;; + *) compile_rpath="$compile_rpath $absdir" + esac + ;; + esac + case " $sys_lib_dlsearch_path " in + *" $libdir "*) ;; + *) + case "$finalize_rpath " in + *" $libdir "*) ;; + *) finalize_rpath="$finalize_rpath $libdir" + esac + ;; + esac + fi + + if test -n "$old_archive_from_expsyms_cmds"; then + # figure out the soname + set dummy $library_names + realname="$2" + shift; shift + libname=`eval \\$echo \"$libname_spec\"` + # use dlname if we got it. it's perfectly good, no? + if test -n "$dlname"; then + soname="$dlname" + elif test -n "$soname_spec"; then + # bleh windows + case $host in + *cygwin* | mingw*) + major=`expr $current - $age` + versuffix="-$major" + ;; + esac + eval soname=\"$soname_spec\" + else + soname="$realname" + fi + + # Make a new name for the extract_expsyms_cmds to use + soroot="$soname" + soname=`$echo $soroot | ${SED} -e 's/^.*\///'` + newlib="libimp-`$echo $soname | ${SED} 's/^lib//;s/\.dll$//'`.a" + + # If the library has no export list, then create one now + if test -f "$output_objdir/$soname-def"; then : + else + $show "extracting exported symbol list from \`$soname'" + save_ifs="$IFS"; IFS='~' + cmds=$extract_expsyms_cmds + for cmd in $cmds; do + IFS="$save_ifs" + eval cmd=\"$cmd\" + $show "$cmd" + $run eval "$cmd" || exit $? + done + IFS="$save_ifs" + fi + + # Create $newlib + if test -f "$output_objdir/$newlib"; then :; else + $show "generating import library for \`$soname'" + save_ifs="$IFS"; IFS='~' + cmds=$old_archive_from_expsyms_cmds + for cmd in $cmds; do + IFS="$save_ifs" + eval cmd=\"$cmd\" + $show "$cmd" + $run eval "$cmd" || exit $? + done + IFS="$save_ifs" + fi + # make sure the library variables are pointing to the new library + dir=$output_objdir + linklib=$newlib + fi # test -n "$old_archive_from_expsyms_cmds" + + if test "$linkmode" = prog || test "$mode" != relink; then + add_shlibpath= + add_dir= + add= + lib_linked=yes + case $hardcode_action in + immediate | unsupported) + if test "$hardcode_direct" = no; then + add="$dir/$linklib" + case $host in + *-*-sco3.2v5.0.[024]*) add_dir="-L$dir" ;; + *-*-sysv4*uw2*) add_dir="-L$dir" ;; + *-*-sysv5OpenUNIX* | *-*-sysv5UnixWare7.[01].[10]* | \ + *-*-unixware7*) add_dir="-L$dir" ;; + *-*-darwin* ) + # if the lib is a module then we can not link against + # it, someone is ignoring the new warnings I added + if /usr/bin/file -L $add 2> /dev/null | + $EGREP ": [^:]* bundle" >/dev/null ; then + $echo "** Warning, lib $linklib is a module, not a shared library" + if test -z "$old_library" ; then + $echo + $echo "** And there doesn't seem to be a static archive available" + $echo "** The link will probably fail, sorry" + else + add="$dir/$old_library" + fi + fi + esac + elif test "$hardcode_minus_L" = no; then + case $host in + *-*-sunos*) add_shlibpath="$dir" ;; + esac + add_dir="-L$dir" + add="-l$name" + elif test "$hardcode_shlibpath_var" = no; then + add_shlibpath="$dir" + add="-l$name" + else + lib_linked=no + fi + ;; + relink) + if test "$hardcode_direct" = yes; then + add="$dir/$linklib" + elif test "$hardcode_minus_L" = yes; then + add_dir="-L$dir" + # Try looking first in the location we're being installed to. + if test -n "$inst_prefix_dir"; then + case $libdir in + [\\/]*) + add_dir="$add_dir -L$inst_prefix_dir$libdir" + ;; + esac + fi + add="-l$name" + elif test "$hardcode_shlibpath_var" = yes; then + add_shlibpath="$dir" + add="-l$name" + else + lib_linked=no + fi + ;; + *) lib_linked=no ;; + esac + + if test "$lib_linked" != yes; then + $echo "$modename: configuration error: unsupported hardcode properties" + exit $EXIT_FAILURE + fi + + if test -n "$add_shlibpath"; then + case :$compile_shlibpath: in + *":$add_shlibpath:"*) ;; + *) compile_shlibpath="$compile_shlibpath$add_shlibpath:" ;; + esac + fi + if test "$linkmode" = prog; then + test -n "$add_dir" && compile_deplibs="$add_dir $compile_deplibs" + test -n "$add" && compile_deplibs="$add $compile_deplibs" + else + test -n "$add_dir" && deplibs="$add_dir $deplibs" + test -n "$add" && deplibs="$add $deplibs" + if test "$hardcode_direct" != yes && \ + test "$hardcode_minus_L" != yes && \ + test "$hardcode_shlibpath_var" = yes; then + case :$finalize_shlibpath: in + *":$libdir:"*) ;; + *) finalize_shlibpath="$finalize_shlibpath$libdir:" ;; + esac + fi + fi + fi + + if test "$linkmode" = prog || test "$mode" = relink; then + add_shlibpath= + add_dir= + add= + # Finalize command for both is simple: just hardcode it. + if test "$hardcode_direct" = yes; then + add="$libdir/$linklib" + elif test "$hardcode_minus_L" = yes; then + add_dir="-L$libdir" + add="-l$name" + elif test "$hardcode_shlibpath_var" = yes; then + case :$finalize_shlibpath: in + *":$libdir:"*) ;; + *) finalize_shlibpath="$finalize_shlibpath$libdir:" ;; + esac + add="-l$name" + elif test "$hardcode_automatic" = yes; then + if test -n "$inst_prefix_dir" && + test -f "$inst_prefix_dir$libdir/$linklib" ; then + add="$inst_prefix_dir$libdir/$linklib" + else + add="$libdir/$linklib" + fi + else + # We cannot seem to hardcode it, guess we'll fake it. + add_dir="-L$libdir" + # Try looking first in the location we're being installed to. + if test -n "$inst_prefix_dir"; then + case $libdir in + [\\/]*) + add_dir="$add_dir -L$inst_prefix_dir$libdir" + ;; + esac + fi + add="-l$name" + fi + + if test "$linkmode" = prog; then + test -n "$add_dir" && finalize_deplibs="$add_dir $finalize_deplibs" + test -n "$add" && finalize_deplibs="$add $finalize_deplibs" + else + test -n "$add_dir" && deplibs="$add_dir $deplibs" + test -n "$add" && deplibs="$add $deplibs" + fi + fi + elif test "$linkmode" = prog; then + # Here we assume that one of hardcode_direct or hardcode_minus_L + # is not unsupported. This is valid on all known static and + # shared platforms. + if test "$hardcode_direct" != unsupported; then + test -n "$old_library" && linklib="$old_library" + compile_deplibs="$dir/$linklib $compile_deplibs" + finalize_deplibs="$dir/$linklib $finalize_deplibs" + else + compile_deplibs="-l$name -L$dir $compile_deplibs" + finalize_deplibs="-l$name -L$dir $finalize_deplibs" + fi + elif test "$build_libtool_libs" = yes; then + # Not a shared library + if test "$deplibs_check_method" != pass_all; then + # We're trying link a shared library against a static one + # but the system doesn't support it. + + # Just print a warning and add the library to dependency_libs so + # that the program can be linked against the static library. + $echo + $echo "*** Warning: This system can not link to static lib archive $lib." + $echo "*** I have the capability to make that library automatically link in when" + $echo "*** you link to this library. But I can only do this if you have a" + $echo "*** shared version of the library, which you do not appear to have." + if test "$module" = yes; then + $echo "*** But as you try to build a module library, libtool will still create " + $echo "*** a static module, that should work as long as the dlopening application" + $echo "*** is linked with the -dlopen flag to resolve symbols at runtime." + if test -z "$global_symbol_pipe"; then + $echo + $echo "*** However, this would only work if libtool was able to extract symbol" + $echo "*** lists from a program, using \`nm' or equivalent, but libtool could" + $echo "*** not find such a program. So, this module is probably useless." + $echo "*** \`nm' from GNU binutils and a full rebuild may help." + fi + if test "$build_old_libs" = no; then + build_libtool_libs=module + build_old_libs=yes + else + build_libtool_libs=no + fi + fi + else + deplibs="$dir/$old_library $deplibs" + link_static=yes + fi + fi # link shared/static library? + + if test "$linkmode" = lib; then + if test -n "$dependency_libs" && + { test "$hardcode_into_libs" != yes || + test "$build_old_libs" = yes || + test "$link_static" = yes; }; then + # Extract -R from dependency_libs + temp_deplibs= + for libdir in $dependency_libs; do + case $libdir in + -R*) temp_xrpath=`$echo "X$libdir" | $Xsed -e 's/^-R//'` + case " $xrpath " in + *" $temp_xrpath "*) ;; + *) xrpath="$xrpath $temp_xrpath";; + esac;; + *) temp_deplibs="$temp_deplibs $libdir";; + esac + done + dependency_libs="$temp_deplibs" + fi + + newlib_search_path="$newlib_search_path $absdir" + # Link against this library + test "$link_static" = no && newdependency_libs="$abs_ladir/$laname $newdependency_libs" + # ... and its dependency_libs + tmp_libs= + for deplib in $dependency_libs; do + newdependency_libs="$deplib $newdependency_libs" + if test "X$duplicate_deps" = "Xyes" ; then + case "$tmp_libs " in + *" $deplib "*) specialdeplibs="$specialdeplibs $deplib" ;; + esac + fi + tmp_libs="$tmp_libs $deplib" + done + + if test "$link_all_deplibs" != no; then + # Add the search paths of all dependency libraries + for deplib in $dependency_libs; do + case $deplib in + -L*) path="$deplib" ;; + *.la) + dir=`$echo "X$deplib" | $Xsed -e 's%/[^/]*$%%'` + test "X$dir" = "X$deplib" && dir="." + # We need an absolute path. + case $dir in + [\\/]* | [A-Za-z]:[\\/]*) absdir="$dir" ;; + *) + absdir=`cd "$dir" && pwd` + if test -z "$absdir"; then + $echo "$modename: warning: cannot determine absolute directory name of \`$dir'" 1>&2 + absdir="$dir" + fi + ;; + esac + if grep "^installed=no" $deplib > /dev/null; then + path="$absdir/$objdir" + else + eval libdir=`${SED} -n -e 's/^libdir=\(.*\)$/\1/p' $deplib` + if test -z "$libdir"; then + $echo "$modename: \`$deplib' is not a valid libtool archive" 1>&2 + exit $EXIT_FAILURE + fi + if test "$absdir" != "$libdir"; then + $echo "$modename: warning: \`$deplib' seems to be moved" 1>&2 + fi + path="$absdir" + fi + depdepl= + case $host in + *-*-darwin*) + # we do not want to link against static libs, + # but need to link against shared + eval deplibrary_names=`${SED} -n -e 's/^library_names=\(.*\)$/\1/p' $deplib` + eval deplibdir=`${SED} -n -e 's/^libdir=\(.*\)$/\1/p' $deplib` + if test -n "$deplibrary_names" ; then + for tmp in $deplibrary_names ; do + depdepl=$tmp + done + if test -f "$deplibdir/$depdepl" ; then + depdepl="$deplibdir/$depdepl" + elif test -f "$path/$depdepl" ; then + depdepl="$path/$depdepl" + else + # Can't find it, oh well... + depdepl= + fi + # do not add paths which are already there + case " $newlib_search_path " in + *" $path "*) ;; + *) newlib_search_path="$newlib_search_path $path";; + esac + fi + path="" + ;; + *) + path="-L$path" + ;; + esac + ;; + -l*) + case $host in + *-*-darwin*) + # Again, we only want to link against shared libraries + eval tmp_libs=`$echo "X$deplib" | $Xsed -e "s,^\-l,,"` + for tmp in $newlib_search_path ; do + if test -f "$tmp/lib$tmp_libs.dylib" ; then + eval depdepl="$tmp/lib$tmp_libs.dylib" + break + fi + done + path="" + ;; + *) continue ;; + esac + ;; + *) continue ;; + esac + case " $deplibs " in + *" $path "*) ;; + *) deplibs="$path $deplibs" ;; + esac + case " $deplibs " in + *" $depdepl "*) ;; + *) deplibs="$depdepl $deplibs" ;; + esac + done + fi # link_all_deplibs != no + fi # linkmode = lib + done # for deplib in $libs + dependency_libs="$newdependency_libs" + if test "$pass" = dlpreopen; then + # Link the dlpreopened libraries before other libraries + for deplib in $save_deplibs; do + deplibs="$deplib $deplibs" + done + fi + if test "$pass" != dlopen; then + if test "$pass" != conv; then + # Make sure lib_search_path contains only unique directories. + lib_search_path= + for dir in $newlib_search_path; do + case "$lib_search_path " in + *" $dir "*) ;; + *) lib_search_path="$lib_search_path $dir" ;; + esac + done + newlib_search_path= + fi + + if test "$linkmode,$pass" != "prog,link"; then + vars="deplibs" + else + vars="compile_deplibs finalize_deplibs" + fi + for var in $vars dependency_libs; do + # Add libraries to $var in reverse order + eval tmp_libs=\"\$$var\" + new_libs= + for deplib in $tmp_libs; do + # FIXME: Pedantically, this is the right thing to do, so + # that some nasty dependency loop isn't accidentally + # broken: + #new_libs="$deplib $new_libs" + # Pragmatically, this seems to cause very few problems in + # practice: + case $deplib in + -L*) new_libs="$deplib $new_libs" ;; + -R*) ;; + *) + # And here is the reason: when a library appears more + # than once as an explicit dependence of a library, or + # is implicitly linked in more than once by the + # compiler, it is considered special, and multiple + # occurrences thereof are not removed. Compare this + # with having the same library being listed as a + # dependency of multiple other libraries: in this case, + # we know (pedantically, we assume) the library does not + # need to be listed more than once, so we keep only the + # last copy. This is not always right, but it is rare + # enough that we require users that really mean to play + # such unportable linking tricks to link the library + # using -Wl,-lname, so that libtool does not consider it + # for duplicate removal. + case " $specialdeplibs " in + *" $deplib "*) new_libs="$deplib $new_libs" ;; + *) + case " $new_libs " in + *" $deplib "*) ;; + *) new_libs="$deplib $new_libs" ;; + esac + ;; + esac + ;; + esac + done + tmp_libs= + for deplib in $new_libs; do + case $deplib in + -L*) + case " $tmp_libs " in + *" $deplib "*) ;; + *) tmp_libs="$tmp_libs $deplib" ;; + esac + ;; + *) tmp_libs="$tmp_libs $deplib" ;; + esac + done + eval $var=\"$tmp_libs\" + done # for var + fi + # Last step: remove runtime libs from dependency_libs + # (they stay in deplibs) + tmp_libs= + for i in $dependency_libs ; do + case " $predeps $postdeps $compiler_lib_search_path " in + *" $i "*) + i="" + ;; + esac + if test -n "$i" ; then + tmp_libs="$tmp_libs $i" + fi + done + dependency_libs=$tmp_libs + done # for pass + if test "$linkmode" = prog; then + dlfiles="$newdlfiles" + dlprefiles="$newdlprefiles" + fi + + case $linkmode in + oldlib) + case " $deplibs" in + *\ -l* | *\ -L*) + $echo "$modename: warning: \`-l' and \`-L' are ignored for archives" 1>&2 ;; + esac + + if test -n "$dlfiles$dlprefiles" || test "$dlself" != no; then + $echo "$modename: warning: \`-dlopen' is ignored for archives" 1>&2 + fi + + if test -n "$rpath"; then + $echo "$modename: warning: \`-rpath' is ignored for archives" 1>&2 + fi + + if test -n "$xrpath"; then + $echo "$modename: warning: \`-R' is ignored for archives" 1>&2 + fi + + if test -n "$vinfo"; then + $echo "$modename: warning: \`-version-info/-version-number' is ignored for archives" 1>&2 + fi + + if test -n "$release"; then + $echo "$modename: warning: \`-release' is ignored for archives" 1>&2 + fi + + if test -n "$export_symbols" || test -n "$export_symbols_regex"; then + $echo "$modename: warning: \`-export-symbols' is ignored for archives" 1>&2 + fi + + # Now set the variables for building old libraries. + build_libtool_libs=no + oldlibs="$output" + objs="$objs$old_deplibs" + ;; + + lib) + # Make sure we only generate libraries of the form `libNAME.la'. + case $outputname in + lib*) + name=`$echo "X$outputname" | $Xsed -e 's/\.la$//' -e 's/^lib//'` + eval shared_ext=\"$shrext_cmds\" + eval libname=\"$libname_spec\" + ;; + *) + if test "$module" = no; then + $echo "$modename: libtool library \`$output' must begin with \`lib'" 1>&2 + $echo "$help" 1>&2 + exit $EXIT_FAILURE + fi + if test "$need_lib_prefix" != no; then + # Add the "lib" prefix for modules if required + name=`$echo "X$outputname" | $Xsed -e 's/\.la$//'` + eval shared_ext=\"$shrext_cmds\" + eval libname=\"$libname_spec\" + else + libname=`$echo "X$outputname" | $Xsed -e 's/\.la$//'` + fi + ;; + esac + + if test -n "$objs"; then + if test "$deplibs_check_method" != pass_all; then + $echo "$modename: cannot build libtool library \`$output' from non-libtool objects on this host:$objs" 2>&1 + exit $EXIT_FAILURE + else + $echo + $echo "*** Warning: Linking the shared library $output against the non-libtool" + $echo "*** objects $objs is not portable!" + libobjs="$libobjs $objs" + fi + fi + + if test "$dlself" != no; then + $echo "$modename: warning: \`-dlopen self' is ignored for libtool libraries" 1>&2 + fi + + set dummy $rpath + if test "$#" -gt 2; then + $echo "$modename: warning: ignoring multiple \`-rpath's for a libtool library" 1>&2 + fi + install_libdir="$2" + + oldlibs= + if test -z "$rpath"; then + if test "$build_libtool_libs" = yes; then + # Building a libtool convenience library. + # Some compilers have problems with a `.al' extension so + # convenience libraries should have the same extension an + # archive normally would. + oldlibs="$output_objdir/$libname.$libext $oldlibs" + build_libtool_libs=convenience + build_old_libs=yes + fi + + if test -n "$vinfo"; then + $echo "$modename: warning: \`-version-info/-version-number' is ignored for convenience libraries" 1>&2 + fi + + if test -n "$release"; then + $echo "$modename: warning: \`-release' is ignored for convenience libraries" 1>&2 + fi + else + + # Parse the version information argument. + save_ifs="$IFS"; IFS=':' + set dummy $vinfo 0 0 0 + IFS="$save_ifs" + + if test -n "$8"; then + $echo "$modename: too many parameters to \`-version-info'" 1>&2 + $echo "$help" 1>&2 + exit $EXIT_FAILURE + fi + + # convert absolute version numbers to libtool ages + # this retains compatibility with .la files and attempts + # to make the code below a bit more comprehensible + + case $vinfo_number in + yes) + number_major="$2" + number_minor="$3" + number_revision="$4" + # + # There are really only two kinds -- those that + # use the current revision as the major version + # and those that subtract age and use age as + # a minor version. But, then there is irix + # which has an extra 1 added just for fun + # + case $version_type in + darwin|linux|osf|windows|none) + current=`expr $number_major + $number_minor` + age="$number_minor" + revision="$number_revision" + ;; + freebsd-aout|freebsd-elf|sunos) + current="$number_major" + revision="$number_minor" + age="0" + ;; + irix|nonstopux) + current=`expr $number_major + $number_minor` + age="$number_minor" + revision="$number_minor" + lt_irix_increment=no + ;; + esac + ;; + no) + current="$2" + revision="$3" + age="$4" + ;; + esac + + # Check that each of the things are valid numbers. + case $current in + 0|[1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-9][0-9][0-9][0-9][0-9]) ;; + *) + $echo "$modename: CURRENT \`$current' must be a nonnegative integer" 1>&2 + $echo "$modename: \`$vinfo' is not valid version information" 1>&2 + exit $EXIT_FAILURE + ;; + esac + + case $revision in + 0|[1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-9][0-9][0-9][0-9][0-9]) ;; + *) + $echo "$modename: REVISION \`$revision' must be a nonnegative integer" 1>&2 + $echo "$modename: \`$vinfo' is not valid version information" 1>&2 + exit $EXIT_FAILURE + ;; + esac + + case $age in + 0|[1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-9][0-9][0-9][0-9][0-9]) ;; + *) + $echo "$modename: AGE \`$age' must be a nonnegative integer" 1>&2 + $echo "$modename: \`$vinfo' is not valid version information" 1>&2 + exit $EXIT_FAILURE + ;; + esac + + if test "$age" -gt "$current"; then + $echo "$modename: AGE \`$age' is greater than the current interface number \`$current'" 1>&2 + $echo "$modename: \`$vinfo' is not valid version information" 1>&2 + exit $EXIT_FAILURE + fi + + # Calculate the version variables. + major= + versuffix= + verstring= + case $version_type in + none) ;; + + darwin) + # Like Linux, but with the current version available in + # verstring for coding it into the library header + major=.`expr $current - $age` + versuffix="$major.$age.$revision" + # Darwin ld doesn't like 0 for these options... + minor_current=`expr $current + 1` + xlcverstring="${wl}-compatibility_version ${wl}$minor_current ${wl}-current_version ${wl}$minor_current.$revision" + verstring="-compatibility_version $minor_current -current_version $minor_current.$revision" + ;; + + freebsd-aout) + major=".$current" + versuffix=".$current.$revision"; + ;; + + freebsd-elf) + major=".$current" + versuffix=".$current"; + ;; + + irix | nonstopux) + if test "X$lt_irix_increment" = "Xno"; then + major=`expr $current - $age` + else + major=`expr $current - $age + 1` + fi + case $version_type in + nonstopux) verstring_prefix=nonstopux ;; + *) verstring_prefix=sgi ;; + esac + verstring="$verstring_prefix$major.$revision" + + # Add in all the interfaces that we are compatible with. + loop=$revision + while test "$loop" -ne 0; do + iface=`expr $revision - $loop` + loop=`expr $loop - 1` + verstring="$verstring_prefix$major.$iface:$verstring" + done + + # Before this point, $major must not contain `.'. + major=.$major + versuffix="$major.$revision" + ;; + + linux) + major=.`expr $current - $age` + versuffix="$major.$age.$revision" + ;; + + osf) + major=.`expr $current - $age` + versuffix=".$current.$age.$revision" + verstring="$current.$age.$revision" + + # Add in all the interfaces that we are compatible with. + loop=$age + while test "$loop" -ne 0; do + iface=`expr $current - $loop` + loop=`expr $loop - 1` + verstring="$verstring:${iface}.0" + done + + # Make executables depend on our current version. + verstring="$verstring:${current}.0" + ;; + + sunos) + major=".$current" + versuffix=".$current.$revision" + ;; + + windows) + # Use '-' rather than '.', since we only want one + # extension on DOS 8.3 filesystems. + major=`expr $current - $age` + versuffix="-$major" + ;; + + *) + $echo "$modename: unknown library version type \`$version_type'" 1>&2 + $echo "Fatal configuration error. See the $PACKAGE docs for more information." 1>&2 + exit $EXIT_FAILURE + ;; + esac + + # Clear the version info if we defaulted, and they specified a release. + if test -z "$vinfo" && test -n "$release"; then + major= + case $version_type in + darwin) + # we can't check for "0.0" in archive_cmds due to quoting + # problems, so we reset it completely + verstring= + ;; + *) + verstring="0.0" + ;; + esac + if test "$need_version" = no; then + versuffix= + else + versuffix=".0.0" + fi + fi + + # Remove version info from name if versioning should be avoided + if test "$avoid_version" = yes && test "$need_version" = no; then + major= + versuffix= + verstring="" + fi + + # Check to see if the archive will have undefined symbols. + if test "$allow_undefined" = yes; then + if test "$allow_undefined_flag" = unsupported; then + $echo "$modename: warning: undefined symbols not allowed in $host shared libraries" 1>&2 + build_libtool_libs=no + build_old_libs=yes + fi + else + # Don't allow undefined symbols. + allow_undefined_flag="$no_undefined_flag" + fi + fi + + if test "$mode" != relink; then + # Remove our outputs, but don't remove object files since they + # may have been created when compiling PIC objects. + removelist= + tempremovelist=`$echo "$output_objdir/*"` + for p in $tempremovelist; do + case $p in + *.$objext) + ;; + $output_objdir/$outputname | $output_objdir/$libname.* | $output_objdir/${libname}${release}.*) + if test "X$precious_files_regex" != "X"; then + if echo $p | $EGREP -e "$precious_files_regex" >/dev/null 2>&1 + then + continue + fi + fi + removelist="$removelist $p" + ;; + *) ;; + esac + done + if test -n "$removelist"; then + $show "${rm}r $removelist" + $run ${rm}r $removelist + fi + fi + + # Now set the variables for building old libraries. + if test "$build_old_libs" = yes && test "$build_libtool_libs" != convenience ; then + oldlibs="$oldlibs $output_objdir/$libname.$libext" + + # Transform .lo files to .o files. + oldobjs="$objs "`$echo "X$libobjs" | $SP2NL | $Xsed -e '/\.'${libext}'$/d' -e "$lo2o" | $NL2SP` + fi + + # Eliminate all temporary directories. + #for path in $notinst_path; do + # lib_search_path=`$echo "$lib_search_path " | ${SED} -e "s% $path % %g"` + # deplibs=`$echo "$deplibs " | ${SED} -e "s% -L$path % %g"` + # dependency_libs=`$echo "$dependency_libs " | ${SED} -e "s% -L$path % %g"` + #done + + if test -n "$xrpath"; then + # If the user specified any rpath flags, then add them. + temp_xrpath= + for libdir in $xrpath; do + temp_xrpath="$temp_xrpath -R$libdir" + case "$finalize_rpath " in + *" $libdir "*) ;; + *) finalize_rpath="$finalize_rpath $libdir" ;; + esac + done + if test "$hardcode_into_libs" != yes || test "$build_old_libs" = yes; then + dependency_libs="$temp_xrpath $dependency_libs" + fi + fi + + # Make sure dlfiles contains only unique files that won't be dlpreopened + old_dlfiles="$dlfiles" + dlfiles= + for lib in $old_dlfiles; do + case " $dlprefiles $dlfiles " in + *" $lib "*) ;; + *) dlfiles="$dlfiles $lib" ;; + esac + done + + # Make sure dlprefiles contains only unique files + old_dlprefiles="$dlprefiles" + dlprefiles= + for lib in $old_dlprefiles; do + case "$dlprefiles " in + *" $lib "*) ;; + *) dlprefiles="$dlprefiles $lib" ;; + esac + done + + if test "$build_libtool_libs" = yes; then + if test -n "$rpath"; then + case $host in + *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-*-beos*) + # these systems don't actually have a c library (as such)! + ;; + *-*-rhapsody* | *-*-darwin1.[012]) + # Rhapsody C library is in the System framework + deplibs="$deplibs -framework System" + ;; + *-*-netbsd*) + # Don't link with libc until the a.out ld.so is fixed. + ;; + *-*-openbsd* | *-*-freebsd* | *-*-dragonfly*) + # Do not include libc due to us having libc/libc_r. + ;; + *-*-sco3.2v5* | *-*-sco5v6*) + # Causes problems with __ctype + ;; + *-*-sysv4.2uw2* | *-*-sysv5* | *-*-unixware* | *-*-OpenUNIX*) + # Compiler inserts libc in the correct place for threads to work + ;; + *) + # Add libc to deplibs on all other systems if necessary. + if test "$build_libtool_need_lc" = "yes"; then + deplibs="$deplibs -lc" + fi + ;; + esac + fi + + # Transform deplibs into only deplibs that can be linked in shared. + name_save=$name + libname_save=$libname + release_save=$release + versuffix_save=$versuffix + major_save=$major + # I'm not sure if I'm treating the release correctly. I think + # release should show up in the -l (ie -lgmp5) so we don't want to + # add it in twice. Is that correct? + release="" + versuffix="" + major="" + newdeplibs= + droppeddeps=no + case $deplibs_check_method in + pass_all) + # Don't check for shared/static. Everything works. + # This might be a little naive. We might want to check + # whether the library exists or not. But this is on + # osf3 & osf4 and I'm not really sure... Just + # implementing what was already the behavior. + newdeplibs=$deplibs + ;; + test_compile) + # This code stresses the "libraries are programs" paradigm to its + # limits. Maybe even breaks it. We compile a program, linking it + # against the deplibs as a proxy for the library. Then we can check + # whether they linked in statically or dynamically with ldd. + $rm conftest.c + cat > conftest.c </dev/null` + for potent_lib in $potential_libs; do + # Follow soft links. + if ls -lLd "$potent_lib" 2>/dev/null \ + | grep " -> " >/dev/null; then + continue + fi + # The statement above tries to avoid entering an + # endless loop below, in case of cyclic links. + # We might still enter an endless loop, since a link + # loop can be closed while we follow links, + # but so what? + potlib="$potent_lib" + while test -h "$potlib" 2>/dev/null; do + potliblink=`ls -ld $potlib | ${SED} 's/.* -> //'` + case $potliblink in + [\\/]* | [A-Za-z]:[\\/]*) potlib="$potliblink";; + *) potlib=`$echo "X$potlib" | $Xsed -e 's,[^/]*$,,'`"$potliblink";; + esac + done + if eval $file_magic_cmd \"\$potlib\" 2>/dev/null \ + | ${SED} 10q \ + | $EGREP "$file_magic_regex" > /dev/null; then + newdeplibs="$newdeplibs $a_deplib" + a_deplib="" + break 2 + fi + done + done + fi + if test -n "$a_deplib" ; then + droppeddeps=yes + $echo + $echo "*** Warning: linker path does not have real file for library $a_deplib." + $echo "*** I have the capability to make that library automatically link in when" + $echo "*** you link to this library. But I can only do this if you have a" + $echo "*** shared version of the library, which you do not appear to have" + $echo "*** because I did check the linker path looking for a file starting" + if test -z "$potlib" ; then + $echo "*** with $libname but no candidates were found. (...for file magic test)" + else + $echo "*** with $libname and none of the candidates passed a file format test" + $echo "*** using a file magic. Last file checked: $potlib" + fi + fi + else + # Add a -L argument. + newdeplibs="$newdeplibs $a_deplib" + fi + done # Gone through all deplibs. + ;; + match_pattern*) + set dummy $deplibs_check_method + match_pattern_regex=`expr "$deplibs_check_method" : "$2 \(.*\)"` + for a_deplib in $deplibs; do + name=`expr $a_deplib : '-l\(.*\)'` + # If $name is empty we are operating on a -L argument. + if test -n "$name" && test "$name" != "0"; then + if test "X$allow_libtool_libs_with_static_runtimes" = "Xyes" ; then + case " $predeps $postdeps " in + *" $a_deplib "*) + newdeplibs="$newdeplibs $a_deplib" + a_deplib="" + ;; + esac + fi + if test -n "$a_deplib" ; then + libname=`eval \\$echo \"$libname_spec\"` + for i in $lib_search_path $sys_lib_search_path $shlib_search_path; do + potential_libs=`ls $i/$libname[.-]* 2>/dev/null` + for potent_lib in $potential_libs; do + potlib="$potent_lib" # see symlink-check above in file_magic test + if eval $echo \"$potent_lib\" 2>/dev/null \ + | ${SED} 10q \ + | $EGREP "$match_pattern_regex" > /dev/null; then + newdeplibs="$newdeplibs $a_deplib" + a_deplib="" + break 2 + fi + done + done + fi + if test -n "$a_deplib" ; then + droppeddeps=yes + $echo + $echo "*** Warning: linker path does not have real file for library $a_deplib." + $echo "*** I have the capability to make that library automatically link in when" + $echo "*** you link to this library. But I can only do this if you have a" + $echo "*** shared version of the library, which you do not appear to have" + $echo "*** because I did check the linker path looking for a file starting" + if test -z "$potlib" ; then + $echo "*** with $libname but no candidates were found. (...for regex pattern test)" + else + $echo "*** with $libname and none of the candidates passed a file format test" + $echo "*** using a regex pattern. Last file checked: $potlib" + fi + fi + else + # Add a -L argument. + newdeplibs="$newdeplibs $a_deplib" + fi + done # Gone through all deplibs. + ;; + none | unknown | *) + newdeplibs="" + tmp_deplibs=`$echo "X $deplibs" | $Xsed -e 's/ -lc$//' \ + -e 's/ -[LR][^ ]*//g'` + if test "X$allow_libtool_libs_with_static_runtimes" = "Xyes" ; then + for i in $predeps $postdeps ; do + # can't use Xsed below, because $i might contain '/' + tmp_deplibs=`$echo "X $tmp_deplibs" | ${SED} -e "1s,^X,," -e "s,$i,,"` + done + fi + if $echo "X $tmp_deplibs" | $Xsed -e 's/[ ]//g' \ + | grep . >/dev/null; then + $echo + if test "X$deplibs_check_method" = "Xnone"; then + $echo "*** Warning: inter-library dependencies are not supported in this platform." + else + $echo "*** Warning: inter-library dependencies are not known to be supported." + fi + $echo "*** All declared inter-library dependencies are being dropped." + droppeddeps=yes + fi + ;; + esac + versuffix=$versuffix_save + major=$major_save + release=$release_save + libname=$libname_save + name=$name_save + + case $host in + *-*-rhapsody* | *-*-darwin1.[012]) + # On Rhapsody replace the C library is the System framework + newdeplibs=`$echo "X $newdeplibs" | $Xsed -e 's/ -lc / -framework System /'` + ;; + esac + + if test "$droppeddeps" = yes; then + if test "$module" = yes; then + $echo + $echo "*** Warning: libtool could not satisfy all declared inter-library" + $echo "*** dependencies of module $libname. Therefore, libtool will create" + $echo "*** a static module, that should work as long as the dlopening" + $echo "*** application is linked with the -dlopen flag." + if test -z "$global_symbol_pipe"; then + $echo + $echo "*** However, this would only work if libtool was able to extract symbol" + $echo "*** lists from a program, using \`nm' or equivalent, but libtool could" + $echo "*** not find such a program. So, this module is probably useless." + $echo "*** \`nm' from GNU binutils and a full rebuild may help." + fi + if test "$build_old_libs" = no; then + oldlibs="$output_objdir/$libname.$libext" + build_libtool_libs=module + build_old_libs=yes + else + build_libtool_libs=no + fi + else + $echo "*** The inter-library dependencies that have been dropped here will be" + $echo "*** automatically added whenever a program is linked with this library" + $echo "*** or is declared to -dlopen it." + + if test "$allow_undefined" = no; then + $echo + $echo "*** Since this library must not contain undefined symbols," + $echo "*** because either the platform does not support them or" + $echo "*** it was explicitly requested with -no-undefined," + $echo "*** libtool will only create a static version of it." + if test "$build_old_libs" = no; then + oldlibs="$output_objdir/$libname.$libext" + build_libtool_libs=module + build_old_libs=yes + else + build_libtool_libs=no + fi + fi + fi + fi + # Done checking deplibs! + deplibs=$newdeplibs + fi + + + # move library search paths that coincide with paths to not yet + # installed libraries to the beginning of the library search list + new_libs= + for path in $notinst_path; do + case " $new_libs " in + *" -L$path/$objdir "*) ;; + *) + case " $deplibs " in + *" -L$path/$objdir "*) + new_libs="$new_libs -L$path/$objdir" ;; + esac + ;; + esac + done + for deplib in $deplibs; do + case $deplib in + -L*) + case " $new_libs " in + *" $deplib "*) ;; + *) new_libs="$new_libs $deplib" ;; + esac + ;; + *) new_libs="$new_libs $deplib" ;; + esac + done + deplibs="$new_libs" + + + # All the library-specific variables (install_libdir is set above). + library_names= + old_library= + dlname= + + # Test again, we may have decided not to build it any more + if test "$build_libtool_libs" = yes; then + if test "$hardcode_into_libs" = yes; then + # Hardcode the library paths + hardcode_libdirs= + dep_rpath= + rpath="$finalize_rpath" + test "$mode" != relink && rpath="$compile_rpath$rpath" + for libdir in $rpath; do + if test -n "$hardcode_libdir_flag_spec"; then + if test -n "$hardcode_libdir_separator"; then + if test -z "$hardcode_libdirs"; then + hardcode_libdirs="$libdir" + else + # Just accumulate the unique libdirs. + case $hardcode_libdir_separator$hardcode_libdirs$hardcode_libdir_separator in + *"$hardcode_libdir_separator$libdir$hardcode_libdir_separator"*) + ;; + *) + hardcode_libdirs="$hardcode_libdirs$hardcode_libdir_separator$libdir" + ;; + esac + fi + else + eval flag=\"$hardcode_libdir_flag_spec\" + dep_rpath="$dep_rpath $flag" + fi + elif test -n "$runpath_var"; then + case "$perm_rpath " in + *" $libdir "*) ;; + *) perm_rpath="$perm_rpath $libdir" ;; + esac + fi + done + # Substitute the hardcoded libdirs into the rpath. + if test -n "$hardcode_libdir_separator" && + test -n "$hardcode_libdirs"; then + libdir="$hardcode_libdirs" + if test -n "$hardcode_libdir_flag_spec_ld"; then + case $archive_cmds in + *\$LD*) eval dep_rpath=\"$hardcode_libdir_flag_spec_ld\" ;; + *) eval dep_rpath=\"$hardcode_libdir_flag_spec\" ;; + esac + else + eval dep_rpath=\"$hardcode_libdir_flag_spec\" + fi + fi + if test -n "$runpath_var" && test -n "$perm_rpath"; then + # We should set the runpath_var. + rpath= + for dir in $perm_rpath; do + rpath="$rpath$dir:" + done + eval "$runpath_var='$rpath\$$runpath_var'; export $runpath_var" + fi + test -n "$dep_rpath" && deplibs="$dep_rpath $deplibs" + fi + + shlibpath="$finalize_shlibpath" + test "$mode" != relink && shlibpath="$compile_shlibpath$shlibpath" + if test -n "$shlibpath"; then + eval "$shlibpath_var='$shlibpath\$$shlibpath_var'; export $shlibpath_var" + fi + + # Get the real and link names of the library. + eval shared_ext=\"$shrext_cmds\" + eval library_names=\"$library_names_spec\" + set dummy $library_names + realname="$2" + shift; shift + + if test -n "$soname_spec"; then + eval soname=\"$soname_spec\" + else + soname="$realname" + fi + if test -z "$dlname"; then + dlname=$soname + fi + + lib="$output_objdir/$realname" + linknames= + for link + do + linknames="$linknames $link" + done + + # Use standard objects if they are pic + test -z "$pic_flag" && libobjs=`$echo "X$libobjs" | $SP2NL | $Xsed -e "$lo2o" | $NL2SP` + + # Prepare the list of exported symbols + if test -z "$export_symbols"; then + if test "$always_export_symbols" = yes || test -n "$export_symbols_regex"; then + $show "generating symbol list for \`$libname.la'" + export_symbols="$output_objdir/$libname.exp" + $run $rm $export_symbols + cmds=$export_symbols_cmds + save_ifs="$IFS"; IFS='~' + for cmd in $cmds; do + IFS="$save_ifs" + eval cmd=\"$cmd\" + if len=`expr "X$cmd" : ".*"` && + test "$len" -le "$max_cmd_len" || test "$max_cmd_len" -le -1; then + $show "$cmd" + $run eval "$cmd" || exit $? + skipped_export=false + else + # The command line is too long to execute in one step. + $show "using reloadable object file for export list..." + skipped_export=: + # Break out early, otherwise skipped_export may be + # set to false by a later but shorter cmd. + break + fi + done + IFS="$save_ifs" + if test -n "$export_symbols_regex"; then + $show "$EGREP -e \"$export_symbols_regex\" \"$export_symbols\" > \"${export_symbols}T\"" + $run eval '$EGREP -e "$export_symbols_regex" "$export_symbols" > "${export_symbols}T"' + $show "$mv \"${export_symbols}T\" \"$export_symbols\"" + $run eval '$mv "${export_symbols}T" "$export_symbols"' + fi + fi + fi + + if test -n "$export_symbols" && test -n "$include_expsyms"; then + $run eval '$echo "X$include_expsyms" | $SP2NL >> "$export_symbols"' + fi + + tmp_deplibs= + for test_deplib in $deplibs; do + case " $convenience " in + *" $test_deplib "*) ;; + *) + tmp_deplibs="$tmp_deplibs $test_deplib" + ;; + esac + done + deplibs="$tmp_deplibs" + + if test -n "$convenience"; then + if test -n "$whole_archive_flag_spec"; then + save_libobjs=$libobjs + eval libobjs=\"\$libobjs $whole_archive_flag_spec\" + else + gentop="$output_objdir/${outputname}x" + generated="$generated $gentop" + + func_extract_archives $gentop $convenience + libobjs="$libobjs $func_extract_archives_result" + fi + fi + + if test "$thread_safe" = yes && test -n "$thread_safe_flag_spec"; then + eval flag=\"$thread_safe_flag_spec\" + linker_flags="$linker_flags $flag" + fi + + # Make a backup of the uninstalled library when relinking + if test "$mode" = relink; then + $run eval '(cd $output_objdir && $rm ${realname}U && $mv $realname ${realname}U)' || exit $? + fi + + # Do each of the archive commands. + if test "$module" = yes && test -n "$module_cmds" ; then + if test -n "$export_symbols" && test -n "$module_expsym_cmds"; then + eval test_cmds=\"$module_expsym_cmds\" + cmds=$module_expsym_cmds + else + eval test_cmds=\"$module_cmds\" + cmds=$module_cmds + fi + else + if test -n "$export_symbols" && test -n "$archive_expsym_cmds"; then + eval test_cmds=\"$archive_expsym_cmds\" + cmds=$archive_expsym_cmds + else + eval test_cmds=\"$archive_cmds\" + cmds=$archive_cmds + fi + fi + + if test "X$skipped_export" != "X:" && + len=`expr "X$test_cmds" : ".*" 2>/dev/null` && + test "$len" -le "$max_cmd_len" || test "$max_cmd_len" -le -1; then + : + else + # The command line is too long to link in one step, link piecewise. + $echo "creating reloadable object files..." + + # Save the value of $output and $libobjs because we want to + # use them later. If we have whole_archive_flag_spec, we + # want to use save_libobjs as it was before + # whole_archive_flag_spec was expanded, because we can't + # assume the linker understands whole_archive_flag_spec. + # This may have to be revisited, in case too many + # convenience libraries get linked in and end up exceeding + # the spec. + if test -z "$convenience" || test -z "$whole_archive_flag_spec"; then + save_libobjs=$libobjs + fi + save_output=$output + output_la=`$echo "X$output" | $Xsed -e "$basename"` + + # Clear the reloadable object creation command queue and + # initialize k to one. + test_cmds= + concat_cmds= + objlist= + delfiles= + last_robj= + k=1 + output=$output_objdir/$output_la-${k}.$objext + # Loop over the list of objects to be linked. + for obj in $save_libobjs + do + eval test_cmds=\"$reload_cmds $objlist $last_robj\" + if test "X$objlist" = X || + { len=`expr "X$test_cmds" : ".*" 2>/dev/null` && + test "$len" -le "$max_cmd_len"; }; then + objlist="$objlist $obj" + else + # The command $test_cmds is almost too long, add a + # command to the queue. + if test "$k" -eq 1 ; then + # The first file doesn't have a previous command to add. + eval concat_cmds=\"$reload_cmds $objlist $last_robj\" + else + # All subsequent reloadable object files will link in + # the last one created. + eval concat_cmds=\"\$concat_cmds~$reload_cmds $objlist $last_robj\" + fi + last_robj=$output_objdir/$output_la-${k}.$objext + k=`expr $k + 1` + output=$output_objdir/$output_la-${k}.$objext + objlist=$obj + len=1 + fi + done + # Handle the remaining objects by creating one last + # reloadable object file. All subsequent reloadable object + # files will link in the last one created. + test -z "$concat_cmds" || concat_cmds=$concat_cmds~ + eval concat_cmds=\"\${concat_cmds}$reload_cmds $objlist $last_robj\" + + if ${skipped_export-false}; then + $show "generating symbol list for \`$libname.la'" + export_symbols="$output_objdir/$libname.exp" + $run $rm $export_symbols + libobjs=$output + # Append the command to create the export file. + eval concat_cmds=\"\$concat_cmds~$export_symbols_cmds\" + fi + + # Set up a command to remove the reloadable object files + # after they are used. + i=0 + while test "$i" -lt "$k" + do + i=`expr $i + 1` + delfiles="$delfiles $output_objdir/$output_la-${i}.$objext" + done + + $echo "creating a temporary reloadable object file: $output" + + # Loop through the commands generated above and execute them. + save_ifs="$IFS"; IFS='~' + for cmd in $concat_cmds; do + IFS="$save_ifs" + $show "$cmd" + $run eval "$cmd" || exit $? + done + IFS="$save_ifs" + + libobjs=$output + # Restore the value of output. + output=$save_output + + if test -n "$convenience" && test -n "$whole_archive_flag_spec"; then + eval libobjs=\"\$libobjs $whole_archive_flag_spec\" + fi + # Expand the library linking commands again to reset the + # value of $libobjs for piecewise linking. + + # Do each of the archive commands. + if test "$module" = yes && test -n "$module_cmds" ; then + if test -n "$export_symbols" && test -n "$module_expsym_cmds"; then + cmds=$module_expsym_cmds + else + cmds=$module_cmds + fi + else + if test -n "$export_symbols" && test -n "$archive_expsym_cmds"; then + cmds=$archive_expsym_cmds + else + cmds=$archive_cmds + fi + fi + + # Append the command to remove the reloadable object files + # to the just-reset $cmds. + eval cmds=\"\$cmds~\$rm $delfiles\" + fi + save_ifs="$IFS"; IFS='~' + for cmd in $cmds; do + IFS="$save_ifs" + eval cmd=\"$cmd\" + $show "$cmd" + $run eval "$cmd" || { + lt_exit=$? + + # Restore the uninstalled library and exit + if test "$mode" = relink; then + $run eval '(cd $output_objdir && $rm ${realname}T && $mv ${realname}U $realname)' + fi + + exit $lt_exit + } + done + IFS="$save_ifs" + + # Restore the uninstalled library and exit + if test "$mode" = relink; then + $run eval '(cd $output_objdir && $rm ${realname}T && $mv $realname ${realname}T && $mv "$realname"U $realname)' || exit $? + + if test -n "$convenience"; then + if test -z "$whole_archive_flag_spec"; then + $show "${rm}r $gentop" + $run ${rm}r "$gentop" + fi + fi + + exit $EXIT_SUCCESS + fi + + # Create links to the real library. + for linkname in $linknames; do + if test "$realname" != "$linkname"; then + $show "(cd $output_objdir && $rm $linkname && $LN_S $realname $linkname)" + $run eval '(cd $output_objdir && $rm $linkname && $LN_S $realname $linkname)' || exit $? + fi + done + + # If -module or -export-dynamic was specified, set the dlname. + if test "$module" = yes || test "$export_dynamic" = yes; then + # On all known operating systems, these are identical. + dlname="$soname" + fi + fi + ;; + + obj) + case " $deplibs" in + *\ -l* | *\ -L*) + $echo "$modename: warning: \`-l' and \`-L' are ignored for objects" 1>&2 ;; + esac + + if test -n "$dlfiles$dlprefiles" || test "$dlself" != no; then + $echo "$modename: warning: \`-dlopen' is ignored for objects" 1>&2 + fi + + if test -n "$rpath"; then + $echo "$modename: warning: \`-rpath' is ignored for objects" 1>&2 + fi + + if test -n "$xrpath"; then + $echo "$modename: warning: \`-R' is ignored for objects" 1>&2 + fi + + if test -n "$vinfo"; then + $echo "$modename: warning: \`-version-info' is ignored for objects" 1>&2 + fi + + if test -n "$release"; then + $echo "$modename: warning: \`-release' is ignored for objects" 1>&2 + fi + + case $output in + *.lo) + if test -n "$objs$old_deplibs"; then + $echo "$modename: cannot build library object \`$output' from non-libtool objects" 1>&2 + exit $EXIT_FAILURE + fi + libobj="$output" + obj=`$echo "X$output" | $Xsed -e "$lo2o"` + ;; + *) + libobj= + obj="$output" + ;; + esac + + # Delete the old objects. + $run $rm $obj $libobj + + # Objects from convenience libraries. This assumes + # single-version convenience libraries. Whenever we create + # different ones for PIC/non-PIC, this we'll have to duplicate + # the extraction. + reload_conv_objs= + gentop= + # reload_cmds runs $LD directly, so let us get rid of + # -Wl from whole_archive_flag_spec and hope we can get by with + # turning comma into space.. + wl= + + if test -n "$convenience"; then + if test -n "$whole_archive_flag_spec"; then + eval tmp_whole_archive_flags=\"$whole_archive_flag_spec\" + reload_conv_objs=$reload_objs\ `$echo "X$tmp_whole_archive_flags" | $Xsed -e 's|,| |g'` + else + gentop="$output_objdir/${obj}x" + generated="$generated $gentop" + + func_extract_archives $gentop $convenience + reload_conv_objs="$reload_objs $func_extract_archives_result" + fi + fi + + # Create the old-style object. + reload_objs="$objs$old_deplibs "`$echo "X$libobjs" | $SP2NL | $Xsed -e '/\.'${libext}$'/d' -e '/\.lib$/d' -e "$lo2o" | $NL2SP`" $reload_conv_objs" ### testsuite: skip nested quoting test + + output="$obj" + cmds=$reload_cmds + save_ifs="$IFS"; IFS='~' + for cmd in $cmds; do + IFS="$save_ifs" + eval cmd=\"$cmd\" + $show "$cmd" + $run eval "$cmd" || exit $? + done + IFS="$save_ifs" + + # Exit if we aren't doing a library object file. + if test -z "$libobj"; then + if test -n "$gentop"; then + $show "${rm}r $gentop" + $run ${rm}r $gentop + fi + + exit $EXIT_SUCCESS + fi + + if test "$build_libtool_libs" != yes; then + if test -n "$gentop"; then + $show "${rm}r $gentop" + $run ${rm}r $gentop + fi + + # Create an invalid libtool object if no PIC, so that we don't + # accidentally link it into a program. + # $show "echo timestamp > $libobj" + # $run eval "echo timestamp > $libobj" || exit $? + exit $EXIT_SUCCESS + fi + + if test -n "$pic_flag" || test "$pic_mode" != default; then + # Only do commands if we really have different PIC objects. + reload_objs="$libobjs $reload_conv_objs" + output="$libobj" + cmds=$reload_cmds + save_ifs="$IFS"; IFS='~' + for cmd in $cmds; do + IFS="$save_ifs" + eval cmd=\"$cmd\" + $show "$cmd" + $run eval "$cmd" || exit $? + done + IFS="$save_ifs" + fi + + if test -n "$gentop"; then + $show "${rm}r $gentop" + $run ${rm}r $gentop + fi + + exit $EXIT_SUCCESS + ;; + + prog) + case $host in + *cygwin*) output=`$echo $output | ${SED} -e 's,.exe$,,;s,$,.exe,'` ;; + esac + if test -n "$vinfo"; then + $echo "$modename: warning: \`-version-info' is ignored for programs" 1>&2 + fi + + if test -n "$release"; then + $echo "$modename: warning: \`-release' is ignored for programs" 1>&2 + fi + + if test "$preload" = yes; then + if test "$dlopen_support" = unknown && test "$dlopen_self" = unknown && + test "$dlopen_self_static" = unknown; then + $echo "$modename: warning: \`AC_LIBTOOL_DLOPEN' not used. Assuming no dlopen support." + fi + fi + + case $host in + *-*-rhapsody* | *-*-darwin1.[012]) + # On Rhapsody replace the C library is the System framework + compile_deplibs=`$echo "X $compile_deplibs" | $Xsed -e 's/ -lc / -framework System /'` + finalize_deplibs=`$echo "X $finalize_deplibs" | $Xsed -e 's/ -lc / -framework System /'` + ;; + esac + + case $host in + *darwin*) + # Don't allow lazy linking, it breaks C++ global constructors + if test "$tagname" = CXX ; then + compile_command="$compile_command ${wl}-bind_at_load" + finalize_command="$finalize_command ${wl}-bind_at_load" + fi + ;; + esac + + + # move library search paths that coincide with paths to not yet + # installed libraries to the beginning of the library search list + new_libs= + for path in $notinst_path; do + case " $new_libs " in + *" -L$path/$objdir "*) ;; + *) + case " $compile_deplibs " in + *" -L$path/$objdir "*) + new_libs="$new_libs -L$path/$objdir" ;; + esac + ;; + esac + done + for deplib in $compile_deplibs; do + case $deplib in + -L*) + case " $new_libs " in + *" $deplib "*) ;; + *) new_libs="$new_libs $deplib" ;; + esac + ;; + *) new_libs="$new_libs $deplib" ;; + esac + done + compile_deplibs="$new_libs" + + + compile_command="$compile_command $compile_deplibs" + finalize_command="$finalize_command $finalize_deplibs" + + if test -n "$rpath$xrpath"; then + # If the user specified any rpath flags, then add them. + for libdir in $rpath $xrpath; do + # This is the magic to use -rpath. + case "$finalize_rpath " in + *" $libdir "*) ;; + *) finalize_rpath="$finalize_rpath $libdir" ;; + esac + done + fi + + # Now hardcode the library paths + rpath= + hardcode_libdirs= + for libdir in $compile_rpath $finalize_rpath; do + if test -n "$hardcode_libdir_flag_spec"; then + if test -n "$hardcode_libdir_separator"; then + if test -z "$hardcode_libdirs"; then + hardcode_libdirs="$libdir" + else + # Just accumulate the unique libdirs. + case $hardcode_libdir_separator$hardcode_libdirs$hardcode_libdir_separator in + *"$hardcode_libdir_separator$libdir$hardcode_libdir_separator"*) + ;; + *) + hardcode_libdirs="$hardcode_libdirs$hardcode_libdir_separator$libdir" + ;; + esac + fi + else + eval flag=\"$hardcode_libdir_flag_spec\" + rpath="$rpath $flag" + fi + elif test -n "$runpath_var"; then + case "$perm_rpath " in + *" $libdir "*) ;; + *) perm_rpath="$perm_rpath $libdir" ;; + esac + fi + case $host in + *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2*) + testbindir=`$echo "X$libdir" | $Xsed -e 's*/lib$*/bin*'` + case :$dllsearchpath: in + *":$libdir:"*) ;; + *) dllsearchpath="$dllsearchpath:$libdir";; + esac + case :$dllsearchpath: in + *":$testbindir:"*) ;; + *) dllsearchpath="$dllsearchpath:$testbindir";; + esac + ;; + esac + done + # Substitute the hardcoded libdirs into the rpath. + if test -n "$hardcode_libdir_separator" && + test -n "$hardcode_libdirs"; then + libdir="$hardcode_libdirs" + eval rpath=\" $hardcode_libdir_flag_spec\" + fi + compile_rpath="$rpath" + + rpath= + hardcode_libdirs= + for libdir in $finalize_rpath; do + if test -n "$hardcode_libdir_flag_spec"; then + if test -n "$hardcode_libdir_separator"; then + if test -z "$hardcode_libdirs"; then + hardcode_libdirs="$libdir" + else + # Just accumulate the unique libdirs. + case $hardcode_libdir_separator$hardcode_libdirs$hardcode_libdir_separator in + *"$hardcode_libdir_separator$libdir$hardcode_libdir_separator"*) + ;; + *) + hardcode_libdirs="$hardcode_libdirs$hardcode_libdir_separator$libdir" + ;; + esac + fi + else + eval flag=\"$hardcode_libdir_flag_spec\" + rpath="$rpath $flag" + fi + elif test -n "$runpath_var"; then + case "$finalize_perm_rpath " in + *" $libdir "*) ;; + *) finalize_perm_rpath="$finalize_perm_rpath $libdir" ;; + esac + fi + done + # Substitute the hardcoded libdirs into the rpath. + if test -n "$hardcode_libdir_separator" && + test -n "$hardcode_libdirs"; then + libdir="$hardcode_libdirs" + eval rpath=\" $hardcode_libdir_flag_spec\" + fi + finalize_rpath="$rpath" + + if test -n "$libobjs" && test "$build_old_libs" = yes; then + # Transform all the library objects into standard objects. + compile_command=`$echo "X$compile_command" | $SP2NL | $Xsed -e "$lo2o" | $NL2SP` + finalize_command=`$echo "X$finalize_command" | $SP2NL | $Xsed -e "$lo2o" | $NL2SP` + fi + + dlsyms= + if test -n "$dlfiles$dlprefiles" || test "$dlself" != no; then + if test -n "$NM" && test -n "$global_symbol_pipe"; then + dlsyms="${outputname}S.c" + else + $echo "$modename: not configured to extract global symbols from dlpreopened files" 1>&2 + fi + fi + + if test -n "$dlsyms"; then + case $dlsyms in + "") ;; + *.c) + # Discover the nlist of each of the dlfiles. + nlist="$output_objdir/${outputname}.nm" + + $show "$rm $nlist ${nlist}S ${nlist}T" + $run $rm "$nlist" "${nlist}S" "${nlist}T" + + # Parse the name list into a source file. + $show "creating $output_objdir/$dlsyms" + + test -z "$run" && $echo > "$output_objdir/$dlsyms" "\ +/* $dlsyms - symbol resolution table for \`$outputname' dlsym emulation. */ +/* Generated by $PROGRAM - GNU $PACKAGE $VERSION$TIMESTAMP */ + +#ifdef __cplusplus +extern \"C\" { +#endif + +/* Prevent the only kind of declaration conflicts we can make. */ +#define lt_preloaded_symbols some_other_symbol + +/* External symbol declarations for the compiler. */\ +" + + if test "$dlself" = yes; then + $show "generating symbol list for \`$output'" + + test -z "$run" && $echo ': @PROGRAM@ ' > "$nlist" + + # Add our own program objects to the symbol list. + progfiles=`$echo "X$objs$old_deplibs" | $SP2NL | $Xsed -e "$lo2o" | $NL2SP` + for arg in $progfiles; do + $show "extracting global C symbols from \`$arg'" + $run eval "$NM $arg | $global_symbol_pipe >> '$nlist'" + done + + if test -n "$exclude_expsyms"; then + $run eval '$EGREP -v " ($exclude_expsyms)$" "$nlist" > "$nlist"T' + $run eval '$mv "$nlist"T "$nlist"' + fi + + if test -n "$export_symbols_regex"; then + $run eval '$EGREP -e "$export_symbols_regex" "$nlist" > "$nlist"T' + $run eval '$mv "$nlist"T "$nlist"' + fi + + # Prepare the list of exported symbols + if test -z "$export_symbols"; then + export_symbols="$output_objdir/$outputname.exp" + $run $rm $export_symbols + $run eval "${SED} -n -e '/^: @PROGRAM@ $/d' -e 's/^.* \(.*\)$/\1/p' "'< "$nlist" > "$export_symbols"' + case $host in + *cygwin* | *mingw* ) + $run eval "echo EXPORTS "'> "$output_objdir/$outputname.def"' + $run eval 'cat "$export_symbols" >> "$output_objdir/$outputname.def"' + ;; + esac + else + $run eval "${SED} -e 's/\([].[*^$]\)/\\\\\1/g' -e 's/^/ /' -e 's/$/$/'"' < "$export_symbols" > "$output_objdir/$outputname.exp"' + $run eval 'grep -f "$output_objdir/$outputname.exp" < "$nlist" > "$nlist"T' + $run eval 'mv "$nlist"T "$nlist"' + case $host in + *cygwin* | *mingw* ) + $run eval "echo EXPORTS "'> "$output_objdir/$outputname.def"' + $run eval 'cat "$nlist" >> "$output_objdir/$outputname.def"' + ;; + esac + fi + fi + + for arg in $dlprefiles; do + $show "extracting global C symbols from \`$arg'" + name=`$echo "$arg" | ${SED} -e 's%^.*/%%'` + $run eval '$echo ": $name " >> "$nlist"' + $run eval "$NM $arg | $global_symbol_pipe >> '$nlist'" + done + + if test -z "$run"; then + # Make sure we have at least an empty file. + test -f "$nlist" || : > "$nlist" + + if test -n "$exclude_expsyms"; then + $EGREP -v " ($exclude_expsyms)$" "$nlist" > "$nlist"T + $mv "$nlist"T "$nlist" + fi + + # Try sorting and uniquifying the output. + if grep -v "^: " < "$nlist" | + if sort -k 3 /dev/null 2>&1; then + sort -k 3 + else + sort +2 + fi | + uniq > "$nlist"S; then + : + else + grep -v "^: " < "$nlist" > "$nlist"S + fi + + if test -f "$nlist"S; then + eval "$global_symbol_to_cdecl"' < "$nlist"S >> "$output_objdir/$dlsyms"' + else + $echo '/* NONE */' >> "$output_objdir/$dlsyms" + fi + + $echo >> "$output_objdir/$dlsyms" "\ + +#undef lt_preloaded_symbols + +#if defined (__STDC__) && __STDC__ +# define lt_ptr void * +#else +# define lt_ptr char * +# define const +#endif + +/* The mapping between symbol names and symbols. */ +" + + case $host in + *cygwin* | *mingw* ) + $echo >> "$output_objdir/$dlsyms" "\ +/* DATA imports from DLLs on WIN32 can't be const, because + runtime relocations are performed -- see ld's documentation + on pseudo-relocs */ +struct { +" + ;; + * ) + $echo >> "$output_objdir/$dlsyms" "\ +const struct { +" + ;; + esac + + + $echo >> "$output_objdir/$dlsyms" "\ + const char *name; + lt_ptr address; +} +lt_preloaded_symbols[] = +{\ +" + + eval "$global_symbol_to_c_name_address" < "$nlist" >> "$output_objdir/$dlsyms" + + $echo >> "$output_objdir/$dlsyms" "\ + {0, (lt_ptr) 0} +}; + +/* This works around a problem in FreeBSD linker */ +#ifdef FREEBSD_WORKAROUND +static const void *lt_preloaded_setup() { + return lt_preloaded_symbols; +} +#endif + +#ifdef __cplusplus +} +#endif\ +" + fi + + pic_flag_for_symtable= + case $host in + # compiling the symbol table file with pic_flag works around + # a FreeBSD bug that causes programs to crash when -lm is + # linked before any other PIC object. But we must not use + # pic_flag when linking with -static. The problem exists in + # FreeBSD 2.2.6 and is fixed in FreeBSD 3.1. + *-*-freebsd2*|*-*-freebsd3.0*|*-*-freebsdelf3.0*) + case "$compile_command " in + *" -static "*) ;; + *) pic_flag_for_symtable=" $pic_flag -DFREEBSD_WORKAROUND";; + esac;; + *-*-hpux*) + case "$compile_command " in + *" -static "*) ;; + *) pic_flag_for_symtable=" $pic_flag";; + esac + esac + + # Now compile the dynamic symbol file. + $show "(cd $output_objdir && $LTCC $LTCFLAGS -c$no_builtin_flag$pic_flag_for_symtable \"$dlsyms\")" + $run eval '(cd $output_objdir && $LTCC $LTCFLAGS -c$no_builtin_flag$pic_flag_for_symtable "$dlsyms")' || exit $? + + # Clean up the generated files. + $show "$rm $output_objdir/$dlsyms $nlist ${nlist}S ${nlist}T" + $run $rm "$output_objdir/$dlsyms" "$nlist" "${nlist}S" "${nlist}T" + + # Transform the symbol file into the correct name. + case $host in + *cygwin* | *mingw* ) + if test -f "$output_objdir/${outputname}.def" ; then + compile_command=`$echo "X$compile_command" | $SP2NL | $Xsed -e "s%@SYMFILE@%$output_objdir/${outputname}.def $output_objdir/${outputname}S.${objext}%" | $NL2SP` + finalize_command=`$echo "X$finalize_command" | $SP2NL | $Xsed -e "s%@SYMFILE@%$output_objdir/${outputname}.def $output_objdir/${outputname}S.${objext}%" | $NL2SP` + else + compile_command=`$echo "X$compile_command" | $SP2NL | $Xsed -e "s%@SYMFILE@%$output_objdir/${outputname}S.${objext}%" | $NL2SP` + finalize_command=`$echo "X$finalize_command" | $SP2NL | $Xsed -e "s%@SYMFILE@%$output_objdir/${outputname}S.${objext}%" | $NL2SP` + fi + ;; + * ) + compile_command=`$echo "X$compile_command" | $SP2NL | $Xsed -e "s%@SYMFILE@%$output_objdir/${outputname}S.${objext}%" | $NL2SP` + finalize_command=`$echo "X$finalize_command" | $SP2NL | $Xsed -e "s%@SYMFILE@%$output_objdir/${outputname}S.${objext}%" | $NL2SP` + ;; + esac + ;; + *) + $echo "$modename: unknown suffix for \`$dlsyms'" 1>&2 + exit $EXIT_FAILURE + ;; + esac + else + # We keep going just in case the user didn't refer to + # lt_preloaded_symbols. The linker will fail if global_symbol_pipe + # really was required. + + # Nullify the symbol file. + compile_command=`$echo "X$compile_command" | $SP2NL | $Xsed -e "s% @SYMFILE@%%" | $NL2SP` + finalize_command=`$echo "X$finalize_command" | $SP2NL | $Xsed -e "s% @SYMFILE@%%" | $NL2SP` + fi + + if test "$need_relink" = no || test "$build_libtool_libs" != yes; then + # Replace the output file specification. + compile_command=`$echo "X$compile_command" | $SP2NL | $Xsed -e 's%@OUTPUT@%'"$output"'%g' | $NL2SP` + link_command="$compile_command$compile_rpath" + + # We have no uninstalled library dependencies, so finalize right now. + $show "$link_command" + $run eval "$link_command" + exit_status=$? + + # Delete the generated files. + if test -n "$dlsyms"; then + $show "$rm $output_objdir/${outputname}S.${objext}" + $run $rm "$output_objdir/${outputname}S.${objext}" + fi + + exit $exit_status + fi + + if test -n "$shlibpath_var"; then + # We should set the shlibpath_var + rpath= + for dir in $temp_rpath; do + case $dir in + [\\/]* | [A-Za-z]:[\\/]*) + # Absolute path. + rpath="$rpath$dir:" + ;; + *) + # Relative path: add a thisdir entry. + rpath="$rpath\$thisdir/$dir:" + ;; + esac + done + temp_rpath="$rpath" + fi + + if test -n "$compile_shlibpath$finalize_shlibpath"; then + compile_command="$shlibpath_var=\"$compile_shlibpath$finalize_shlibpath\$$shlibpath_var\" $compile_command" + fi + if test -n "$finalize_shlibpath"; then + finalize_command="$shlibpath_var=\"$finalize_shlibpath\$$shlibpath_var\" $finalize_command" + fi + + compile_var= + finalize_var= + if test -n "$runpath_var"; then + if test -n "$perm_rpath"; then + # We should set the runpath_var. + rpath= + for dir in $perm_rpath; do + rpath="$rpath$dir:" + done + compile_var="$runpath_var=\"$rpath\$$runpath_var\" " + fi + if test -n "$finalize_perm_rpath"; then + # We should set the runpath_var. + rpath= + for dir in $finalize_perm_rpath; do + rpath="$rpath$dir:" + done + finalize_var="$runpath_var=\"$rpath\$$runpath_var\" " + fi + fi + + if test "$no_install" = yes; then + # We don't need to create a wrapper script. + link_command="$compile_var$compile_command$compile_rpath" + # Replace the output file specification. + link_command=`$echo "X$link_command" | $Xsed -e 's%@OUTPUT@%'"$output"'%g'` + # Delete the old output file. + $run $rm $output + # Link the executable and exit + $show "$link_command" + $run eval "$link_command" || exit $? + exit $EXIT_SUCCESS + fi + + if test "$hardcode_action" = relink; then + # Fast installation is not supported + link_command="$compile_var$compile_command$compile_rpath" + relink_command="$finalize_var$finalize_command$finalize_rpath" + + $echo "$modename: warning: this platform does not like uninstalled shared libraries" 1>&2 + $echo "$modename: \`$output' will be relinked during installation" 1>&2 + else + if test "$fast_install" != no; then + link_command="$finalize_var$compile_command$finalize_rpath" + if test "$fast_install" = yes; then + relink_command=`$echo "X$compile_var$compile_command$compile_rpath" | $SP2NL | $Xsed -e 's%@OUTPUT@%\$progdir/\$file%g' | $NL2SP` + else + # fast_install is set to needless + relink_command= + fi + else + link_command="$compile_var$compile_command$compile_rpath" + relink_command="$finalize_var$finalize_command$finalize_rpath" + fi + fi + + # Replace the output file specification. + link_command=`$echo "X$link_command" | $Xsed -e 's%@OUTPUT@%'"$output_objdir/$outputname"'%g'` + + # Delete the old output files. + $run $rm $output $output_objdir/$outputname $output_objdir/lt-$outputname + + $show "$link_command" + $run eval "$link_command" || exit $? + + # Now create the wrapper script. + $show "creating $output" + + # Quote the relink command for shipping. + if test -n "$relink_command"; then + # Preserve any variables that may affect compiler behavior + for var in $variables_saved_for_relink; do + if eval test -z \"\${$var+set}\"; then + relink_command="{ test -z \"\${$var+set}\" || unset $var || { $var=; export $var; }; }; $relink_command" + elif eval var_value=\$$var; test -z "$var_value"; then + relink_command="$var=; export $var; $relink_command" + else + var_value=`$echo "X$var_value" | $Xsed -e "$sed_quote_subst"` + relink_command="$var=\"$var_value\"; export $var; $relink_command" + fi + done + relink_command="(cd `pwd`; $relink_command)" + relink_command=`$echo "X$relink_command" | $SP2NL | $Xsed -e "$sed_quote_subst" | $NL2SP` + fi + + # Quote $echo for shipping. + if test "X$echo" = "X$SHELL $progpath --fallback-echo"; then + case $progpath in + [\\/]* | [A-Za-z]:[\\/]*) qecho="$SHELL $progpath --fallback-echo";; + *) qecho="$SHELL `pwd`/$progpath --fallback-echo";; + esac + qecho=`$echo "X$qecho" | $Xsed -e "$sed_quote_subst"` + else + qecho=`$echo "X$echo" | $Xsed -e "$sed_quote_subst"` + fi + + # Only actually do things if our run command is non-null. + if test -z "$run"; then + # win32 will think the script is a binary if it has + # a .exe suffix, so we strip it off here. + case $output in + *.exe) output=`$echo $output|${SED} 's,.exe$,,'` ;; + esac + # test for cygwin because mv fails w/o .exe extensions + case $host in + *cygwin*) + exeext=.exe + outputname=`$echo $outputname|${SED} 's,.exe$,,'` ;; + *) exeext= ;; + esac + case $host in + *cygwin* | *mingw* ) + output_name=`basename $output` + output_path=`dirname $output` + cwrappersource="$output_path/$objdir/lt-$output_name.c" + cwrapper="$output_path/$output_name.exe" + $rm $cwrappersource $cwrapper + trap "$rm $cwrappersource $cwrapper; exit $EXIT_FAILURE" 1 2 15 + + cat > $cwrappersource <> $cwrappersource<<"EOF" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(PATH_MAX) +# define LT_PATHMAX PATH_MAX +#elif defined(MAXPATHLEN) +# define LT_PATHMAX MAXPATHLEN +#else +# define LT_PATHMAX 1024 +#endif + +#ifndef DIR_SEPARATOR +# define DIR_SEPARATOR '/' +# define PATH_SEPARATOR ':' +#endif + +#if defined (_WIN32) || defined (__MSDOS__) || defined (__DJGPP__) || \ + defined (__OS2__) +# define HAVE_DOS_BASED_FILE_SYSTEM +# ifndef DIR_SEPARATOR_2 +# define DIR_SEPARATOR_2 '\\' +# endif +# ifndef PATH_SEPARATOR_2 +# define PATH_SEPARATOR_2 ';' +# endif +#endif + +#ifndef DIR_SEPARATOR_2 +# define IS_DIR_SEPARATOR(ch) ((ch) == DIR_SEPARATOR) +#else /* DIR_SEPARATOR_2 */ +# define IS_DIR_SEPARATOR(ch) \ + (((ch) == DIR_SEPARATOR) || ((ch) == DIR_SEPARATOR_2)) +#endif /* DIR_SEPARATOR_2 */ + +#ifndef PATH_SEPARATOR_2 +# define IS_PATH_SEPARATOR(ch) ((ch) == PATH_SEPARATOR) +#else /* PATH_SEPARATOR_2 */ +# define IS_PATH_SEPARATOR(ch) ((ch) == PATH_SEPARATOR_2) +#endif /* PATH_SEPARATOR_2 */ + +#define XMALLOC(type, num) ((type *) xmalloc ((num) * sizeof(type))) +#define XFREE(stale) do { \ + if (stale) { free ((void *) stale); stale = 0; } \ +} while (0) + +/* -DDEBUG is fairly common in CFLAGS. */ +#undef DEBUG +#if defined DEBUGWRAPPER +# define DEBUG(format, ...) fprintf(stderr, format, __VA_ARGS__) +#else +# define DEBUG(format, ...) +#endif + +const char *program_name = NULL; + +void * xmalloc (size_t num); +char * xstrdup (const char *string); +const char * base_name (const char *name); +char * find_executable(const char *wrapper); +int check_executable(const char *path); +char * strendzap(char *str, const char *pat); +void lt_fatal (const char *message, ...); + +int +main (int argc, char *argv[]) +{ + char **newargz; + int i; + + program_name = (char *) xstrdup (base_name (argv[0])); + DEBUG("(main) argv[0] : %s\n",argv[0]); + DEBUG("(main) program_name : %s\n",program_name); + newargz = XMALLOC(char *, argc+2); +EOF + + cat >> $cwrappersource <> $cwrappersource <<"EOF" + newargz[1] = find_executable(argv[0]); + if (newargz[1] == NULL) + lt_fatal("Couldn't find %s", argv[0]); + DEBUG("(main) found exe at : %s\n",newargz[1]); + /* we know the script has the same name, without the .exe */ + /* so make sure newargz[1] doesn't end in .exe */ + strendzap(newargz[1],".exe"); + for (i = 1; i < argc; i++) + newargz[i+1] = xstrdup(argv[i]); + newargz[argc+1] = NULL; + + for (i=0; i> $cwrappersource <> $cwrappersource <> $cwrappersource <<"EOF" + return 127; +} + +void * +xmalloc (size_t num) +{ + void * p = (void *) malloc (num); + if (!p) + lt_fatal ("Memory exhausted"); + + return p; +} + +char * +xstrdup (const char *string) +{ + return string ? strcpy ((char *) xmalloc (strlen (string) + 1), string) : NULL +; +} + +const char * +base_name (const char *name) +{ + const char *base; + +#if defined (HAVE_DOS_BASED_FILE_SYSTEM) + /* Skip over the disk name in MSDOS pathnames. */ + if (isalpha ((unsigned char)name[0]) && name[1] == ':') + name += 2; +#endif + + for (base = name; *name; name++) + if (IS_DIR_SEPARATOR (*name)) + base = name + 1; + return base; +} + +int +check_executable(const char * path) +{ + struct stat st; + + DEBUG("(check_executable) : %s\n", path ? (*path ? path : "EMPTY!") : "NULL!"); + if ((!path) || (!*path)) + return 0; + + if ((stat (path, &st) >= 0) && + ( + /* MinGW & native WIN32 do not support S_IXOTH or S_IXGRP */ +#if defined (S_IXOTH) + ((st.st_mode & S_IXOTH) == S_IXOTH) || +#endif +#if defined (S_IXGRP) + ((st.st_mode & S_IXGRP) == S_IXGRP) || +#endif + ((st.st_mode & S_IXUSR) == S_IXUSR)) + ) + return 1; + else + return 0; +} + +/* Searches for the full path of the wrapper. Returns + newly allocated full path name if found, NULL otherwise */ +char * +find_executable (const char* wrapper) +{ + int has_slash = 0; + const char* p; + const char* p_next; + /* static buffer for getcwd */ + char tmp[LT_PATHMAX + 1]; + int tmp_len; + char* concat_name; + + DEBUG("(find_executable) : %s\n", wrapper ? (*wrapper ? wrapper : "EMPTY!") : "NULL!"); + + if ((wrapper == NULL) || (*wrapper == '\0')) + return NULL; + + /* Absolute path? */ +#if defined (HAVE_DOS_BASED_FILE_SYSTEM) + if (isalpha ((unsigned char)wrapper[0]) && wrapper[1] == ':') + { + concat_name = xstrdup (wrapper); + if (check_executable(concat_name)) + return concat_name; + XFREE(concat_name); + } + else + { +#endif + if (IS_DIR_SEPARATOR (wrapper[0])) + { + concat_name = xstrdup (wrapper); + if (check_executable(concat_name)) + return concat_name; + XFREE(concat_name); + } +#if defined (HAVE_DOS_BASED_FILE_SYSTEM) + } +#endif + + for (p = wrapper; *p; p++) + if (*p == '/') + { + has_slash = 1; + break; + } + if (!has_slash) + { + /* no slashes; search PATH */ + const char* path = getenv ("PATH"); + if (path != NULL) + { + for (p = path; *p; p = p_next) + { + const char* q; + size_t p_len; + for (q = p; *q; q++) + if (IS_PATH_SEPARATOR(*q)) + break; + p_len = q - p; + p_next = (*q == '\0' ? q : q + 1); + if (p_len == 0) + { + /* empty path: current directory */ + if (getcwd (tmp, LT_PATHMAX) == NULL) + lt_fatal ("getcwd failed"); + tmp_len = strlen(tmp); + concat_name = XMALLOC(char, tmp_len + 1 + strlen(wrapper) + 1); + memcpy (concat_name, tmp, tmp_len); + concat_name[tmp_len] = '/'; + strcpy (concat_name + tmp_len + 1, wrapper); + } + else + { + concat_name = XMALLOC(char, p_len + 1 + strlen(wrapper) + 1); + memcpy (concat_name, p, p_len); + concat_name[p_len] = '/'; + strcpy (concat_name + p_len + 1, wrapper); + } + if (check_executable(concat_name)) + return concat_name; + XFREE(concat_name); + } + } + /* not found in PATH; assume curdir */ + } + /* Relative path | not found in path: prepend cwd */ + if (getcwd (tmp, LT_PATHMAX) == NULL) + lt_fatal ("getcwd failed"); + tmp_len = strlen(tmp); + concat_name = XMALLOC(char, tmp_len + 1 + strlen(wrapper) + 1); + memcpy (concat_name, tmp, tmp_len); + concat_name[tmp_len] = '/'; + strcpy (concat_name + tmp_len + 1, wrapper); + + if (check_executable(concat_name)) + return concat_name; + XFREE(concat_name); + return NULL; +} + +char * +strendzap(char *str, const char *pat) +{ + size_t len, patlen; + + assert(str != NULL); + assert(pat != NULL); + + len = strlen(str); + patlen = strlen(pat); + + if (patlen <= len) + { + str += len - patlen; + if (strcmp(str, pat) == 0) + *str = '\0'; + } + return str; +} + +static void +lt_error_core (int exit_status, const char * mode, + const char * message, va_list ap) +{ + fprintf (stderr, "%s: %s: ", program_name, mode); + vfprintf (stderr, message, ap); + fprintf (stderr, ".\n"); + + if (exit_status >= 0) + exit (exit_status); +} + +void +lt_fatal (const char *message, ...) +{ + va_list ap; + va_start (ap, message); + lt_error_core (EXIT_FAILURE, "FATAL", message, ap); + va_end (ap); +} +EOF + # we should really use a build-platform specific compiler + # here, but OTOH, the wrappers (shell script and this C one) + # are only useful if you want to execute the "real" binary. + # Since the "real" binary is built for $host, then this + # wrapper might as well be built for $host, too. + $run $LTCC $LTCFLAGS -s -o $cwrapper $cwrappersource + ;; + esac + $rm $output + trap "$rm $output; exit $EXIT_FAILURE" 1 2 15 + + $echo > $output "\ +#! $SHELL + +# $output - temporary wrapper script for $objdir/$outputname +# Generated by $PROGRAM - GNU $PACKAGE $VERSION$TIMESTAMP +# +# The $output program cannot be directly executed until all the libtool +# libraries that it depends on are installed. +# +# This wrapper script should never be moved out of the build directory. +# If it is, it will not operate correctly. + +# Sed substitution that helps us do robust quoting. It backslashifies +# metacharacters that are still active within double-quoted strings. +Xsed='${SED} -e 1s/^X//' +sed_quote_subst='$sed_quote_subst' + +# Be Bourne compatible (taken from Autoconf:_AS_BOURNE_COMPATIBLE). +if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then + emulate sh + NULLCMD=: + # Zsh 3.x and 4.x performs word splitting on \${1+\"\$@\"}, which + # is contrary to our usage. Disable this feature. + alias -g '\${1+\"\$@\"}'='\"\$@\"' + setopt NO_GLOB_SUBST +else + case \`(set -o) 2>/dev/null\` in *posix*) set -o posix;; esac +fi +BIN_SH=xpg4; export BIN_SH # for Tru64 +DUALCASE=1; export DUALCASE # for MKS sh + +# The HP-UX ksh and POSIX shell print the target directory to stdout +# if CDPATH is set. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +relink_command=\"$relink_command\" + +# This environment variable determines our operation mode. +if test \"\$libtool_install_magic\" = \"$magic\"; then + # install mode needs the following variable: + notinst_deplibs='$notinst_deplibs' +else + # When we are sourced in execute mode, \$file and \$echo are already set. + if test \"\$libtool_execute_magic\" != \"$magic\"; then + echo=\"$qecho\" + file=\"\$0\" + # Make sure echo works. + if test \"X\$1\" = X--no-reexec; then + # Discard the --no-reexec flag, and continue. + shift + elif test \"X\`(\$echo '\t') 2>/dev/null\`\" = 'X\t'; then + # Yippee, \$echo works! + : + else + # Restart under the correct shell, and then maybe \$echo will work. + exec $SHELL \"\$0\" --no-reexec \${1+\"\$@\"} + fi + fi\ +" + $echo >> $output "\ + + # Find the directory that this script lives in. + thisdir=\`\$echo \"X\$file\" | \$Xsed -e 's%/[^/]*$%%'\` + test \"x\$thisdir\" = \"x\$file\" && thisdir=. + + # Follow symbolic links until we get to the real thisdir. + file=\`ls -ld \"\$file\" | ${SED} -n 's/.*-> //p'\` + while test -n \"\$file\"; do + destdir=\`\$echo \"X\$file\" | \$Xsed -e 's%/[^/]*\$%%'\` + + # If there was a directory component, then change thisdir. + if test \"x\$destdir\" != \"x\$file\"; then + case \"\$destdir\" in + [\\\\/]* | [A-Za-z]:[\\\\/]*) thisdir=\"\$destdir\" ;; + *) thisdir=\"\$thisdir/\$destdir\" ;; + esac + fi + + file=\`\$echo \"X\$file\" | \$Xsed -e 's%^.*/%%'\` + file=\`ls -ld \"\$thisdir/\$file\" | ${SED} -n 's/.*-> //p'\` + done + + # Try to get the absolute directory name. + absdir=\`cd \"\$thisdir\" && pwd\` + test -n \"\$absdir\" && thisdir=\"\$absdir\" +" + + if test "$fast_install" = yes; then + $echo >> $output "\ + program=lt-'$outputname'$exeext + progdir=\"\$thisdir/$objdir\" + + if test ! -f \"\$progdir/\$program\" || \\ + { file=\`ls -1dt \"\$progdir/\$program\" \"\$progdir/../\$program\" 2>/dev/null | ${SED} 1q\`; \\ + test \"X\$file\" != \"X\$progdir/\$program\"; }; then + + file=\"\$\$-\$program\" + + if test ! -d \"\$progdir\"; then + $mkdir \"\$progdir\" + else + $rm \"\$progdir/\$file\" + fi" + + $echo >> $output "\ + + # relink executable if necessary + if test -n \"\$relink_command\"; then + if relink_command_output=\`eval \$relink_command 2>&1\`; then : + else + $echo \"\$relink_command_output\" >&2 + $rm \"\$progdir/\$file\" + exit $EXIT_FAILURE + fi + fi + + $mv \"\$progdir/\$file\" \"\$progdir/\$program\" 2>/dev/null || + { $rm \"\$progdir/\$program\"; + $mv \"\$progdir/\$file\" \"\$progdir/\$program\"; } + $rm \"\$progdir/\$file\" + fi" + else + $echo >> $output "\ + program='$outputname' + progdir=\"\$thisdir/$objdir\" +" + fi + + $echo >> $output "\ + + if test -f \"\$progdir/\$program\"; then" + + # Export our shlibpath_var if we have one. + if test "$shlibpath_overrides_runpath" = yes && test -n "$shlibpath_var" && test -n "$temp_rpath"; then + $echo >> $output "\ + # Add our own library path to $shlibpath_var + $shlibpath_var=\"$temp_rpath\$$shlibpath_var\" + + # Some systems cannot cope with colon-terminated $shlibpath_var + # The second colon is a workaround for a bug in BeOS R4 sed + $shlibpath_var=\`\$echo \"X\$$shlibpath_var\" | \$Xsed -e 's/::*\$//'\` + + export $shlibpath_var +" + fi + + # fixup the dll searchpath if we need to. + if test -n "$dllsearchpath"; then + $echo >> $output "\ + # Add the dll search path components to the executable PATH + PATH=$dllsearchpath:\$PATH +" + fi + + $echo >> $output "\ + if test \"\$libtool_execute_magic\" != \"$magic\"; then + # Run the actual program with our arguments. +" + case $host in + # Backslashes separate directories on plain windows + *-*-mingw | *-*-os2*) + $echo >> $output "\ + exec \"\$progdir\\\\\$program\" \${1+\"\$@\"} +" + ;; + + *) + $echo >> $output "\ + exec \"\$progdir/\$program\" \${1+\"\$@\"} +" + ;; + esac + $echo >> $output "\ + \$echo \"\$0: cannot exec \$program \$*\" + exit $EXIT_FAILURE + fi + else + # The program doesn't exist. + \$echo \"\$0: error: \\\`\$progdir/\$program' does not exist\" 1>&2 + \$echo \"This script is just a wrapper for \$program.\" 1>&2 + $echo \"See the $PACKAGE documentation for more information.\" 1>&2 + exit $EXIT_FAILURE + fi +fi\ +" + chmod +x $output + fi + exit $EXIT_SUCCESS + ;; + esac + + # See if we need to build an old-fashioned archive. + for oldlib in $oldlibs; do + + if test "$build_libtool_libs" = convenience; then + oldobjs="$libobjs_save" + addlibs="$convenience" + build_libtool_libs=no + else + if test "$build_libtool_libs" = module; then + oldobjs="$libobjs_save" + build_libtool_libs=no + else + oldobjs="$old_deplibs $non_pic_objects" + fi + addlibs="$old_convenience" + fi + + if test -n "$addlibs"; then + gentop="$output_objdir/${outputname}x" + generated="$generated $gentop" + + func_extract_archives $gentop $addlibs + oldobjs="$oldobjs $func_extract_archives_result" + fi + + # Do each command in the archive commands. + if test -n "$old_archive_from_new_cmds" && test "$build_libtool_libs" = yes; then + cmds=$old_archive_from_new_cmds + else + # POSIX demands no paths to be encoded in archives. We have + # to avoid creating archives with duplicate basenames if we + # might have to extract them afterwards, e.g., when creating a + # static archive out of a convenience library, or when linking + # the entirety of a libtool archive into another (currently + # not supported by libtool). + if (for obj in $oldobjs + do + $echo "X$obj" | $Xsed -e 's%^.*/%%' + done | sort | sort -uc >/dev/null 2>&1); then + : + else + $echo "copying selected object files to avoid basename conflicts..." + + if test -z "$gentop"; then + gentop="$output_objdir/${outputname}x" + generated="$generated $gentop" + + $show "${rm}r $gentop" + $run ${rm}r "$gentop" + $show "$mkdir $gentop" + $run $mkdir "$gentop" + exit_status=$? + if test "$exit_status" -ne 0 && test ! -d "$gentop"; then + exit $exit_status + fi + fi + + save_oldobjs=$oldobjs + oldobjs= + counter=1 + for obj in $save_oldobjs + do + objbase=`$echo "X$obj" | $Xsed -e 's%^.*/%%'` + case " $oldobjs " in + " ") oldobjs=$obj ;; + *[\ /]"$objbase "*) + while :; do + # Make sure we don't pick an alternate name that also + # overlaps. + newobj=lt$counter-$objbase + counter=`expr $counter + 1` + case " $oldobjs " in + *[\ /]"$newobj "*) ;; + *) if test ! -f "$gentop/$newobj"; then break; fi ;; + esac + done + $show "ln $obj $gentop/$newobj || cp $obj $gentop/$newobj" + $run ln "$obj" "$gentop/$newobj" || + $run cp "$obj" "$gentop/$newobj" + oldobjs="$oldobjs $gentop/$newobj" + ;; + *) oldobjs="$oldobjs $obj" ;; + esac + done + fi + + eval cmds=\"$old_archive_cmds\" + + if len=`expr "X$cmds" : ".*"` && + test "$len" -le "$max_cmd_len" || test "$max_cmd_len" -le -1; then + cmds=$old_archive_cmds + else + # the command line is too long to link in one step, link in parts + $echo "using piecewise archive linking..." + save_RANLIB=$RANLIB + RANLIB=: + objlist= + concat_cmds= + save_oldobjs=$oldobjs + + # Is there a better way of finding the last object in the list? + for obj in $save_oldobjs + do + last_oldobj=$obj + done + for obj in $save_oldobjs + do + oldobjs="$objlist $obj" + objlist="$objlist $obj" + eval test_cmds=\"$old_archive_cmds\" + if len=`expr "X$test_cmds" : ".*" 2>/dev/null` && + test "$len" -le "$max_cmd_len"; then + : + else + # the above command should be used before it gets too long + oldobjs=$objlist + if test "$obj" = "$last_oldobj" ; then + RANLIB=$save_RANLIB + fi + test -z "$concat_cmds" || concat_cmds=$concat_cmds~ + eval concat_cmds=\"\${concat_cmds}$old_archive_cmds\" + objlist= + fi + done + RANLIB=$save_RANLIB + oldobjs=$objlist + if test "X$oldobjs" = "X" ; then + eval cmds=\"\$concat_cmds\" + else + eval cmds=\"\$concat_cmds~\$old_archive_cmds\" + fi + fi + fi + save_ifs="$IFS"; IFS='~' + for cmd in $cmds; do + eval cmd=\"$cmd\" + IFS="$save_ifs" + $show "$cmd" + $run eval "$cmd" || exit $? + done + IFS="$save_ifs" + done + + if test -n "$generated"; then + $show "${rm}r$generated" + $run ${rm}r$generated + fi + + # Now create the libtool archive. + case $output in + *.la) + old_library= + test "$build_old_libs" = yes && old_library="$libname.$libext" + $show "creating $output" + + # Preserve any variables that may affect compiler behavior + for var in $variables_saved_for_relink; do + if eval test -z \"\${$var+set}\"; then + relink_command="{ test -z \"\${$var+set}\" || unset $var || { $var=; export $var; }; }; $relink_command" + elif eval var_value=\$$var; test -z "$var_value"; then + relink_command="$var=; export $var; $relink_command" + else + var_value=`$echo "X$var_value" | $Xsed -e "$sed_quote_subst"` + relink_command="$var=\"$var_value\"; export $var; $relink_command" + fi + done + # Quote the link command for shipping. + relink_command="(cd `pwd`; $SHELL $progpath $preserve_args --mode=relink $libtool_args @inst_prefix_dir@)" + relink_command=`$echo "X$relink_command" | $SP2NL | $Xsed -e "$sed_quote_subst" | $NL2SP` + if test "$hardcode_automatic" = yes ; then + relink_command= + fi + + + # Only create the output if not a dry run. + if test -z "$run"; then + for installed in no yes; do + if test "$installed" = yes; then + if test -z "$install_libdir"; then + break + fi + output="$output_objdir/$outputname"i + # Replace all uninstalled libtool libraries with the installed ones + newdependency_libs= + for deplib in $dependency_libs; do + case $deplib in + *.la) + name=`$echo "X$deplib" | $Xsed -e 's%^.*/%%'` + eval libdir=`${SED} -n -e 's/^libdir=\(.*\)$/\1/p' $deplib` + if test -z "$libdir"; then + $echo "$modename: \`$deplib' is not a valid libtool archive" 1>&2 + exit $EXIT_FAILURE + fi + newdependency_libs="$newdependency_libs $libdir/$name" + ;; + *) newdependency_libs="$newdependency_libs $deplib" ;; + esac + done + dependency_libs="$newdependency_libs" + newdlfiles= + for lib in $dlfiles; do + name=`$echo "X$lib" | $Xsed -e 's%^.*/%%'` + eval libdir=`${SED} -n -e 's/^libdir=\(.*\)$/\1/p' $lib` + if test -z "$libdir"; then + $echo "$modename: \`$lib' is not a valid libtool archive" 1>&2 + exit $EXIT_FAILURE + fi + newdlfiles="$newdlfiles $libdir/$name" + done + dlfiles="$newdlfiles" + newdlprefiles= + for lib in $dlprefiles; do + name=`$echo "X$lib" | $Xsed -e 's%^.*/%%'` + eval libdir=`${SED} -n -e 's/^libdir=\(.*\)$/\1/p' $lib` + if test -z "$libdir"; then + $echo "$modename: \`$lib' is not a valid libtool archive" 1>&2 + exit $EXIT_FAILURE + fi + newdlprefiles="$newdlprefiles $libdir/$name" + done + dlprefiles="$newdlprefiles" + else + newdlfiles= + for lib in $dlfiles; do + case $lib in + [\\/]* | [A-Za-z]:[\\/]*) abs="$lib" ;; + *) abs=`pwd`"/$lib" ;; + esac + newdlfiles="$newdlfiles $abs" + done + dlfiles="$newdlfiles" + newdlprefiles= + for lib in $dlprefiles; do + case $lib in + [\\/]* | [A-Za-z]:[\\/]*) abs="$lib" ;; + *) abs=`pwd`"/$lib" ;; + esac + newdlprefiles="$newdlprefiles $abs" + done + dlprefiles="$newdlprefiles" + fi + $rm $output + # place dlname in correct position for cygwin + tdlname=$dlname + case $host,$output,$installed,$module,$dlname in + *cygwin*,*lai,yes,no,*.dll | *mingw*,*lai,yes,no,*.dll) tdlname=../bin/$dlname ;; + esac + $echo > $output "\ +# $outputname - a libtool library file +# Generated by $PROGRAM - GNU $PACKAGE $VERSION$TIMESTAMP +# +# Please DO NOT delete this file! +# It is necessary for linking the library. + +# The name that we can dlopen(3). +dlname='$tdlname' + +# Names of this library. +library_names='$library_names' + +# The name of the static archive. +old_library='$old_library' + +# Libraries that this one depends upon. +dependency_libs='$dependency_libs' + +# Version information for $libname. +current=$current +age=$age +revision=$revision + +# Is this an already installed library? +installed=$installed + +# Should we warn about portability when linking against -modules? +shouldnotlink=$module + +# Files to dlopen/dlpreopen +dlopen='$dlfiles' +dlpreopen='$dlprefiles' + +# Directory that this library needs to be installed in: +libdir='$install_libdir'" + if test "$installed" = no && test "$need_relink" = yes; then + $echo >> $output "\ +relink_command=\"$relink_command\"" + fi + done + fi + + # Do a symbolic link so that the libtool archive can be found in + # LD_LIBRARY_PATH before the program is installed. + $show "(cd $output_objdir && $rm $outputname && $LN_S ../$outputname $outputname)" + $run eval '(cd $output_objdir && $rm $outputname && $LN_S ../$outputname $outputname)' || exit $? + ;; + esac + exit $EXIT_SUCCESS + ;; + + # libtool install mode + install) + modename="$modename: install" + + # There may be an optional sh(1) argument at the beginning of + # install_prog (especially on Windows NT). + if test "$nonopt" = "$SHELL" || test "$nonopt" = /bin/sh || + # Allow the use of GNU shtool's install command. + $echo "X$nonopt" | grep shtool > /dev/null; then + # Aesthetically quote it. + arg=`$echo "X$nonopt" | $Xsed -e "$sed_quote_subst"` + case $arg in + *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"") + arg="\"$arg\"" + ;; + esac + install_prog="$arg " + arg="$1" + shift + else + install_prog= + arg=$nonopt + fi + + # The real first argument should be the name of the installation program. + # Aesthetically quote it. + arg=`$echo "X$arg" | $Xsed -e "$sed_quote_subst"` + case $arg in + *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"") + arg="\"$arg\"" + ;; + esac + install_prog="$install_prog$arg" + + # We need to accept at least all the BSD install flags. + dest= + files= + opts= + prev= + install_type= + isdir=no + stripme= + for arg + do + if test -n "$dest"; then + files="$files $dest" + dest=$arg + continue + fi + + case $arg in + -d) isdir=yes ;; + -f) + case " $install_prog " in + *[\\\ /]cp\ *) ;; + *) prev=$arg ;; + esac + ;; + -g | -m | -o) prev=$arg ;; + -s) + stripme=" -s" + continue + ;; + -*) + ;; + *) + # If the previous option needed an argument, then skip it. + if test -n "$prev"; then + prev= + else + dest=$arg + continue + fi + ;; + esac + + # Aesthetically quote the argument. + arg=`$echo "X$arg" | $Xsed -e "$sed_quote_subst"` + case $arg in + *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"") + arg="\"$arg\"" + ;; + esac + install_prog="$install_prog $arg" + done + + if test -z "$install_prog"; then + $echo "$modename: you must specify an install program" 1>&2 + $echo "$help" 1>&2 + exit $EXIT_FAILURE + fi + + if test -n "$prev"; then + $echo "$modename: the \`$prev' option requires an argument" 1>&2 + $echo "$help" 1>&2 + exit $EXIT_FAILURE + fi + + if test -z "$files"; then + if test -z "$dest"; then + $echo "$modename: no file or destination specified" 1>&2 + else + $echo "$modename: you must specify a destination" 1>&2 + fi + $echo "$help" 1>&2 + exit $EXIT_FAILURE + fi + + # Strip any trailing slash from the destination. + dest=`$echo "X$dest" | $Xsed -e 's%/$%%'` + + # Check to see that the destination is a directory. + test -d "$dest" && isdir=yes + if test "$isdir" = yes; then + destdir="$dest" + destname= + else + destdir=`$echo "X$dest" | $Xsed -e 's%/[^/]*$%%'` + test "X$destdir" = "X$dest" && destdir=. + destname=`$echo "X$dest" | $Xsed -e 's%^.*/%%'` + + # Not a directory, so check to see that there is only one file specified. + set dummy $files + if test "$#" -gt 2; then + $echo "$modename: \`$dest' is not a directory" 1>&2 + $echo "$help" 1>&2 + exit $EXIT_FAILURE + fi + fi + case $destdir in + [\\/]* | [A-Za-z]:[\\/]*) ;; + *) + for file in $files; do + case $file in + *.lo) ;; + *) + $echo "$modename: \`$destdir' must be an absolute directory name" 1>&2 + $echo "$help" 1>&2 + exit $EXIT_FAILURE + ;; + esac + done + ;; + esac + + # This variable tells wrapper scripts just to set variables rather + # than running their programs. + libtool_install_magic="$magic" + + staticlibs= + future_libdirs= + current_libdirs= + for file in $files; do + + # Do each installation. + case $file in + *.$libext) + # Do the static libraries later. + staticlibs="$staticlibs $file" + ;; + + *.la) + # Check to see that this really is a libtool archive. + if (${SED} -e '2q' $file | grep "^# Generated by .*$PACKAGE") >/dev/null 2>&1; then : + else + $echo "$modename: \`$file' is not a valid libtool archive" 1>&2 + $echo "$help" 1>&2 + exit $EXIT_FAILURE + fi + + library_names= + old_library= + relink_command= + # If there is no directory component, then add one. + case $file in + */* | *\\*) . $file ;; + *) . ./$file ;; + esac + + # Add the libdir to current_libdirs if it is the destination. + if test "X$destdir" = "X$libdir"; then + case "$current_libdirs " in + *" $libdir "*) ;; + *) current_libdirs="$current_libdirs $libdir" ;; + esac + else + # Note the libdir as a future libdir. + case "$future_libdirs " in + *" $libdir "*) ;; + *) future_libdirs="$future_libdirs $libdir" ;; + esac + fi + + dir=`$echo "X$file" | $Xsed -e 's%/[^/]*$%%'`/ + test "X$dir" = "X$file/" && dir= + dir="$dir$objdir" + + if test -n "$relink_command"; then + # Determine the prefix the user has applied to our future dir. + inst_prefix_dir=`$echo "$destdir" | $SED "s%$libdir\$%%"` + + # Don't allow the user to place us outside of our expected + # location b/c this prevents finding dependent libraries that + # are installed to the same prefix. + # At present, this check doesn't affect windows .dll's that + # are installed into $libdir/../bin (currently, that works fine) + # but it's something to keep an eye on. + if test "$inst_prefix_dir" = "$destdir"; then + $echo "$modename: error: cannot install \`$file' to a directory not ending in $libdir" 1>&2 + exit $EXIT_FAILURE + fi + + if test -n "$inst_prefix_dir"; then + # Stick the inst_prefix_dir data into the link command. + relink_command=`$echo "$relink_command" | $SP2NL | $SED "s%@inst_prefix_dir@%-inst-prefix-dir $inst_prefix_dir%" | $NL2SP` + else + relink_command=`$echo "$relink_command" | $SP2NL | $SED "s%@inst_prefix_dir@%%" | $NL2SP` + fi + + $echo "$modename: warning: relinking \`$file'" 1>&2 + $show "$relink_command" + if $run eval "$relink_command"; then : + else + $echo "$modename: error: relink \`$file' with the above command before installing it" 1>&2 + exit $EXIT_FAILURE + fi + fi + + # See the names of the shared library. + set dummy $library_names + if test -n "$2"; then + realname="$2" + shift + shift + + srcname="$realname" + test -n "$relink_command" && srcname="$realname"T + + # Install the shared library and build the symlinks. + $show "$install_prog $dir/$srcname $destdir/$realname" + $run eval "$install_prog $dir/$srcname $destdir/$realname" || exit $? + if test -n "$stripme" && test -n "$striplib"; then + $show "$striplib $destdir/$realname" + $run eval "$striplib $destdir/$realname" || exit $? + fi + + if test "$#" -gt 0; then + # Delete the old symlinks, and create new ones. + # Try `ln -sf' first, because the `ln' binary might depend on + # the symlink we replace! Solaris /bin/ln does not understand -f, + # so we also need to try rm && ln -s. + for linkname + do + if test "$linkname" != "$realname"; then + $show "(cd $destdir && { $LN_S -f $realname $linkname || { $rm $linkname && $LN_S $realname $linkname; }; })" + $run eval "(cd $destdir && { $LN_S -f $realname $linkname || { $rm $linkname && $LN_S $realname $linkname; }; })" + fi + done + fi + + # Do each command in the postinstall commands. + lib="$destdir/$realname" + cmds=$postinstall_cmds + save_ifs="$IFS"; IFS='~' + for cmd in $cmds; do + IFS="$save_ifs" + eval cmd=\"$cmd\" + $show "$cmd" + $run eval "$cmd" || { + lt_exit=$? + + # Restore the uninstalled library and exit + if test "$mode" = relink; then + $run eval '(cd $output_objdir && $rm ${realname}T && $mv ${realname}U $realname)' + fi + + exit $lt_exit + } + done + IFS="$save_ifs" + fi + + # Install the pseudo-library for information purposes. + name=`$echo "X$file" | $Xsed -e 's%^.*/%%'` + instname="$dir/$name"i + $show "$install_prog $instname $destdir/$name" + $run eval "$install_prog $instname $destdir/$name" || exit $? + + # Maybe install the static library, too. + test -n "$old_library" && staticlibs="$staticlibs $dir/$old_library" + ;; + + *.lo) + # Install (i.e. copy) a libtool object. + + # Figure out destination file name, if it wasn't already specified. + if test -n "$destname"; then + destfile="$destdir/$destname" + else + destfile=`$echo "X$file" | $Xsed -e 's%^.*/%%'` + destfile="$destdir/$destfile" + fi + + # Deduce the name of the destination old-style object file. + case $destfile in + *.lo) + staticdest=`$echo "X$destfile" | $Xsed -e "$lo2o"` + ;; + *.$objext) + staticdest="$destfile" + destfile= + ;; + *) + $echo "$modename: cannot copy a libtool object to \`$destfile'" 1>&2 + $echo "$help" 1>&2 + exit $EXIT_FAILURE + ;; + esac + + # Install the libtool object if requested. + if test -n "$destfile"; then + $show "$install_prog $file $destfile" + $run eval "$install_prog $file $destfile" || exit $? + fi + + # Install the old object if enabled. + if test "$build_old_libs" = yes; then + # Deduce the name of the old-style object file. + staticobj=`$echo "X$file" | $Xsed -e "$lo2o"` + + $show "$install_prog $staticobj $staticdest" + $run eval "$install_prog \$staticobj \$staticdest" || exit $? + fi + exit $EXIT_SUCCESS + ;; + + *) + # Figure out destination file name, if it wasn't already specified. + if test -n "$destname"; then + destfile="$destdir/$destname" + else + destfile=`$echo "X$file" | $Xsed -e 's%^.*/%%'` + destfile="$destdir/$destfile" + fi + + # If the file is missing, and there is a .exe on the end, strip it + # because it is most likely a libtool script we actually want to + # install + stripped_ext="" + case $file in + *.exe) + if test ! -f "$file"; then + file=`$echo $file|${SED} 's,.exe$,,'` + stripped_ext=".exe" + fi + ;; + esac + + # Do a test to see if this is really a libtool program. + case $host in + *cygwin*|*mingw*) + wrapper=`$echo $file | ${SED} -e 's,.exe$,,'` + ;; + *) + wrapper=$file + ;; + esac + if (${SED} -e '4q' $wrapper | grep "^# Generated by .*$PACKAGE")>/dev/null 2>&1; then + notinst_deplibs= + relink_command= + + # Note that it is not necessary on cygwin/mingw to append a dot to + # foo even if both foo and FILE.exe exist: automatic-append-.exe + # behavior happens only for exec(3), not for open(2)! Also, sourcing + # `FILE.' does not work on cygwin managed mounts. + # + # If there is no directory component, then add one. + case $wrapper in + */* | *\\*) . ${wrapper} ;; + *) . ./${wrapper} ;; + esac + + # Check the variables that should have been set. + if test -z "$notinst_deplibs"; then + $echo "$modename: invalid libtool wrapper script \`$wrapper'" 1>&2 + exit $EXIT_FAILURE + fi + + finalize=yes + for lib in $notinst_deplibs; do + # Check to see that each library is installed. + libdir= + if test -f "$lib"; then + # If there is no directory component, then add one. + case $lib in + */* | *\\*) . $lib ;; + *) . ./$lib ;; + esac + fi + libfile="$libdir/"`$echo "X$lib" | $Xsed -e 's%^.*/%%g'` ### testsuite: skip nested quoting test + if test -n "$libdir" && test ! -f "$libfile"; then + $echo "$modename: warning: \`$lib' has not been installed in \`$libdir'" 1>&2 + finalize=no + fi + done + + relink_command= + # Note that it is not necessary on cygwin/mingw to append a dot to + # foo even if both foo and FILE.exe exist: automatic-append-.exe + # behavior happens only for exec(3), not for open(2)! Also, sourcing + # `FILE.' does not work on cygwin managed mounts. + # + # If there is no directory component, then add one. + case $wrapper in + */* | *\\*) . ${wrapper} ;; + *) . ./${wrapper} ;; + esac + + outputname= + if test "$fast_install" = no && test -n "$relink_command"; then + if test "$finalize" = yes && test -z "$run"; then + tmpdir=`func_mktempdir` + file=`$echo "X$file$stripped_ext" | $Xsed -e 's%^.*/%%'` + outputname="$tmpdir/$file" + # Replace the output file specification. + relink_command=`$echo "X$relink_command" | $SP2NL | $Xsed -e 's%@OUTPUT@%'"$outputname"'%g' | $NL2SP` + + $show "$relink_command" + if $run eval "$relink_command"; then : + else + $echo "$modename: error: relink \`$file' with the above command before installing it" 1>&2 + ${rm}r "$tmpdir" + continue + fi + file="$outputname" + else + $echo "$modename: warning: cannot relink \`$file'" 1>&2 + fi + else + # Install the binary that we compiled earlier. + file=`$echo "X$file$stripped_ext" | $Xsed -e "s%\([^/]*\)$%$objdir/\1%"` + fi + fi + + # remove .exe since cygwin /usr/bin/install will append another + # one anyway + case $install_prog,$host in + */usr/bin/install*,*cygwin*) + case $file:$destfile in + *.exe:*.exe) + # this is ok + ;; + *.exe:*) + destfile=$destfile.exe + ;; + *:*.exe) + destfile=`$echo $destfile | ${SED} -e 's,.exe$,,'` + ;; + esac + ;; + esac + $show "$install_prog$stripme $file $destfile" + $run eval "$install_prog\$stripme \$file \$destfile" || exit $? + test -n "$outputname" && ${rm}r "$tmpdir" + ;; + esac + done + + for file in $staticlibs; do + name=`$echo "X$file" | $Xsed -e 's%^.*/%%'` + + # Set up the ranlib parameters. + oldlib="$destdir/$name" + + $show "$install_prog $file $oldlib" + $run eval "$install_prog \$file \$oldlib" || exit $? + + if test -n "$stripme" && test -n "$old_striplib"; then + $show "$old_striplib $oldlib" + $run eval "$old_striplib $oldlib" || exit $? + fi + + # Do each command in the postinstall commands. + cmds=$old_postinstall_cmds + save_ifs="$IFS"; IFS='~' + for cmd in $cmds; do + IFS="$save_ifs" + eval cmd=\"$cmd\" + $show "$cmd" + $run eval "$cmd" || exit $? + done + IFS="$save_ifs" + done + + if test -n "$future_libdirs"; then + $echo "$modename: warning: remember to run \`$progname --finish$future_libdirs'" 1>&2 + fi + + if test -n "$current_libdirs"; then + # Maybe just do a dry run. + test -n "$run" && current_libdirs=" -n$current_libdirs" + exec_cmd='$SHELL $progpath $preserve_args --finish$current_libdirs' + else + exit $EXIT_SUCCESS + fi + ;; + + # libtool finish mode + finish) + modename="$modename: finish" + libdirs="$nonopt" + admincmds= + + if test -n "$finish_cmds$finish_eval" && test -n "$libdirs"; then + for dir + do + libdirs="$libdirs $dir" + done + + for libdir in $libdirs; do + if test -n "$finish_cmds"; then + # Do each command in the finish commands. + cmds=$finish_cmds + save_ifs="$IFS"; IFS='~' + for cmd in $cmds; do + IFS="$save_ifs" + eval cmd=\"$cmd\" + $show "$cmd" + $run eval "$cmd" || admincmds="$admincmds + $cmd" + done + IFS="$save_ifs" + fi + if test -n "$finish_eval"; then + # Do the single finish_eval. + eval cmds=\"$finish_eval\" + $run eval "$cmds" || admincmds="$admincmds + $cmds" + fi + done + fi + + # Exit here if they wanted silent mode. + test "$show" = : && exit $EXIT_SUCCESS + + $echo "X----------------------------------------------------------------------" | $Xsed + $echo "Libraries have been installed in:" + for libdir in $libdirs; do + $echo " $libdir" + done + $echo + $echo "If you ever happen to want to link against installed libraries" + $echo "in a given directory, LIBDIR, you must either use libtool, and" + $echo "specify the full pathname of the library, or use the \`-LLIBDIR'" + $echo "flag during linking and do at least one of the following:" + if test -n "$shlibpath_var"; then + $echo " - add LIBDIR to the \`$shlibpath_var' environment variable" + $echo " during execution" + fi + if test -n "$runpath_var"; then + $echo " - add LIBDIR to the \`$runpath_var' environment variable" + $echo " during linking" + fi + if test -n "$hardcode_libdir_flag_spec"; then + libdir=LIBDIR + eval flag=\"$hardcode_libdir_flag_spec\" + + $echo " - use the \`$flag' linker flag" + fi + if test -n "$admincmds"; then + $echo " - have your system administrator run these commands:$admincmds" + fi + if test -f /etc/ld.so.conf; then + $echo " - have your system administrator add LIBDIR to \`/etc/ld.so.conf'" + fi + $echo + $echo "See any operating system documentation about shared libraries for" + $echo "more information, such as the ld(1) and ld.so(8) manual pages." + $echo "X----------------------------------------------------------------------" | $Xsed + exit $EXIT_SUCCESS + ;; + + # libtool execute mode + execute) + modename="$modename: execute" + + # The first argument is the command name. + cmd="$nonopt" + if test -z "$cmd"; then + $echo "$modename: you must specify a COMMAND" 1>&2 + $echo "$help" + exit $EXIT_FAILURE + fi + + # Handle -dlopen flags immediately. + for file in $execute_dlfiles; do + if test ! -f "$file"; then + $echo "$modename: \`$file' is not a file" 1>&2 + $echo "$help" 1>&2 + exit $EXIT_FAILURE + fi + + dir= + case $file in + *.la) + # Check to see that this really is a libtool archive. + if (${SED} -e '2q' $file | grep "^# Generated by .*$PACKAGE") >/dev/null 2>&1; then : + else + $echo "$modename: \`$lib' is not a valid libtool archive" 1>&2 + $echo "$help" 1>&2 + exit $EXIT_FAILURE + fi + + # Read the libtool library. + dlname= + library_names= + + # If there is no directory component, then add one. + case $file in + */* | *\\*) . $file ;; + *) . ./$file ;; + esac + + # Skip this library if it cannot be dlopened. + if test -z "$dlname"; then + # Warn if it was a shared library. + test -n "$library_names" && $echo "$modename: warning: \`$file' was not linked with \`-export-dynamic'" + continue + fi + + dir=`$echo "X$file" | $Xsed -e 's%/[^/]*$%%'` + test "X$dir" = "X$file" && dir=. + + if test -f "$dir/$objdir/$dlname"; then + dir="$dir/$objdir" + else + if test ! -f "$dir/$dlname"; then + $echo "$modename: cannot find \`$dlname' in \`$dir' or \`$dir/$objdir'" 1>&2 + exit $EXIT_FAILURE + fi + fi + ;; + + *.lo) + # Just add the directory containing the .lo file. + dir=`$echo "X$file" | $Xsed -e 's%/[^/]*$%%'` + test "X$dir" = "X$file" && dir=. + ;; + + *) + $echo "$modename: warning \`-dlopen' is ignored for non-libtool libraries and objects" 1>&2 + continue + ;; + esac + + # Get the absolute pathname. + absdir=`cd "$dir" && pwd` + test -n "$absdir" && dir="$absdir" + + # Now add the directory to shlibpath_var. + if eval "test -z \"\$$shlibpath_var\""; then + eval "$shlibpath_var=\"\$dir\"" + else + eval "$shlibpath_var=\"\$dir:\$$shlibpath_var\"" + fi + done + + # This variable tells wrapper scripts just to set shlibpath_var + # rather than running their programs. + libtool_execute_magic="$magic" + + # Check if any of the arguments is a wrapper script. + args= + for file + do + case $file in + -*) ;; + *) + # Do a test to see if this is really a libtool program. + if (${SED} -e '4q' $file | grep "^# Generated by .*$PACKAGE") >/dev/null 2>&1; then + # If there is no directory component, then add one. + case $file in + */* | *\\*) . $file ;; + *) . ./$file ;; + esac + + # Transform arg to wrapped name. + file="$progdir/$program" + fi + ;; + esac + # Quote arguments (to preserve shell metacharacters). + file=`$echo "X$file" | $Xsed -e "$sed_quote_subst"` + args="$args \"$file\"" + done + + if test -z "$run"; then + if test -n "$shlibpath_var"; then + # Export the shlibpath_var. + eval "export $shlibpath_var" + fi + + # Restore saved environment variables + for lt_var in LANG LANGUAGE LC_ALL LC_CTYPE LC_COLLATE LC_MESSAGES + do + eval "if test \"\${save_$lt_var+set}\" = set; then + $lt_var=\$save_$lt_var; export $lt_var + fi" + done + + # Now prepare to actually exec the command. + exec_cmd="\$cmd$args" + else + # Display what would be done. + if test -n "$shlibpath_var"; then + eval "\$echo \"\$shlibpath_var=\$$shlibpath_var\"" + $echo "export $shlibpath_var" + fi + $echo "$cmd$args" + exit $EXIT_SUCCESS + fi + ;; + + # libtool clean and uninstall mode + clean | uninstall) + modename="$modename: $mode" + rm="$nonopt" + files= + rmforce= + exit_status=0 + + # This variable tells wrapper scripts just to set variables rather + # than running their programs. + libtool_install_magic="$magic" + + for arg + do + case $arg in + -f) rm="$rm $arg"; rmforce=yes ;; + -*) rm="$rm $arg" ;; + *) files="$files $arg" ;; + esac + done + + if test -z "$rm"; then + $echo "$modename: you must specify an RM program" 1>&2 + $echo "$help" 1>&2 + exit $EXIT_FAILURE + fi + + rmdirs= + + origobjdir="$objdir" + for file in $files; do + dir=`$echo "X$file" | $Xsed -e 's%/[^/]*$%%'` + if test "X$dir" = "X$file"; then + dir=. + objdir="$origobjdir" + else + objdir="$dir/$origobjdir" + fi + name=`$echo "X$file" | $Xsed -e 's%^.*/%%'` + test "$mode" = uninstall && objdir="$dir" + + # Remember objdir for removal later, being careful to avoid duplicates + if test "$mode" = clean; then + case " $rmdirs " in + *" $objdir "*) ;; + *) rmdirs="$rmdirs $objdir" ;; + esac + fi + + # Don't error if the file doesn't exist and rm -f was used. + if (test -L "$file") >/dev/null 2>&1 \ + || (test -h "$file") >/dev/null 2>&1 \ + || test -f "$file"; then + : + elif test -d "$file"; then + exit_status=1 + continue + elif test "$rmforce" = yes; then + continue + fi + + rmfiles="$file" + + case $name in + *.la) + # Possibly a libtool archive, so verify it. + if (${SED} -e '2q' $file | grep "^# Generated by .*$PACKAGE") >/dev/null 2>&1; then + . $dir/$name + + # Delete the libtool libraries and symlinks. + for n in $library_names; do + rmfiles="$rmfiles $objdir/$n" + done + test -n "$old_library" && rmfiles="$rmfiles $objdir/$old_library" + + case "$mode" in + clean) + case " $library_names " in + # " " in the beginning catches empty $dlname + *" $dlname "*) ;; + *) rmfiles="$rmfiles $objdir/$dlname" ;; + esac + test -n "$libdir" && rmfiles="$rmfiles $objdir/$name $objdir/${name}i" + ;; + uninstall) + if test -n "$library_names"; then + # Do each command in the postuninstall commands. + cmds=$postuninstall_cmds + save_ifs="$IFS"; IFS='~' + for cmd in $cmds; do + IFS="$save_ifs" + eval cmd=\"$cmd\" + $show "$cmd" + $run eval "$cmd" + if test "$?" -ne 0 && test "$rmforce" != yes; then + exit_status=1 + fi + done + IFS="$save_ifs" + fi + + if test -n "$old_library"; then + # Do each command in the old_postuninstall commands. + cmds=$old_postuninstall_cmds + save_ifs="$IFS"; IFS='~' + for cmd in $cmds; do + IFS="$save_ifs" + eval cmd=\"$cmd\" + $show "$cmd" + $run eval "$cmd" + if test "$?" -ne 0 && test "$rmforce" != yes; then + exit_status=1 + fi + done + IFS="$save_ifs" + fi + # FIXME: should reinstall the best remaining shared library. + ;; + esac + fi + ;; + + *.lo) + # Possibly a libtool object, so verify it. + if (${SED} -e '2q' $file | grep "^# Generated by .*$PACKAGE") >/dev/null 2>&1; then + + # Read the .lo file + . $dir/$name + + # Add PIC object to the list of files to remove. + if test -n "$pic_object" \ + && test "$pic_object" != none; then + rmfiles="$rmfiles $dir/$pic_object" + fi + + # Add non-PIC object to the list of files to remove. + if test -n "$non_pic_object" \ + && test "$non_pic_object" != none; then + rmfiles="$rmfiles $dir/$non_pic_object" + fi + fi + ;; + + *) + if test "$mode" = clean ; then + noexename=$name + case $file in + *.exe) + file=`$echo $file|${SED} 's,.exe$,,'` + noexename=`$echo $name|${SED} 's,.exe$,,'` + # $file with .exe has already been added to rmfiles, + # add $file without .exe + rmfiles="$rmfiles $file" + ;; + esac + # Do a test to see if this is a libtool program. + if (${SED} -e '4q' $file | grep "^# Generated by .*$PACKAGE") >/dev/null 2>&1; then + relink_command= + . $dir/$noexename + + # note $name still contains .exe if it was in $file originally + # as does the version of $file that was added into $rmfiles + rmfiles="$rmfiles $objdir/$name $objdir/${name}S.${objext}" + if test "$fast_install" = yes && test -n "$relink_command"; then + rmfiles="$rmfiles $objdir/lt-$name" + fi + if test "X$noexename" != "X$name" ; then + rmfiles="$rmfiles $objdir/lt-${noexename}.c" + fi + fi + fi + ;; + esac + $show "$rm $rmfiles" + $run $rm $rmfiles || exit_status=1 + done + objdir="$origobjdir" + + # Try to remove the ${objdir}s in the directories where we deleted files + for dir in $rmdirs; do + if test -d "$dir"; then + $show "rmdir $dir" + $run rmdir $dir >/dev/null 2>&1 + fi + done + + exit $exit_status + ;; + + "") + $echo "$modename: you must specify a MODE" 1>&2 + $echo "$generic_help" 1>&2 + exit $EXIT_FAILURE + ;; + esac + + if test -z "$exec_cmd"; then + $echo "$modename: invalid operation mode \`$mode'" 1>&2 + $echo "$generic_help" 1>&2 + exit $EXIT_FAILURE + fi +fi # test -z "$show_help" + +if test -n "$exec_cmd"; then + eval exec $exec_cmd + exit $EXIT_FAILURE +fi + +# We need to display help for each of the modes. +case $mode in +"") $echo \ +"Usage: $modename [OPTION]... [MODE-ARG]... + +Provide generalized library-building support services. + + --config show all configuration variables + --debug enable verbose shell tracing +-n, --dry-run display commands without modifying any files + --features display basic configuration information and exit + --finish same as \`--mode=finish' + --help display this help message and exit + --mode=MODE use operation mode MODE [default=inferred from MODE-ARGS] + --quiet same as \`--silent' + --silent don't print informational messages + --tag=TAG use configuration variables from tag TAG + --version print version information + +MODE must be one of the following: + + clean remove files from the build directory + compile compile a source file into a libtool object + execute automatically set library path, then run a program + finish complete the installation of libtool libraries + install install libraries or executables + link create a library or an executable + uninstall remove libraries from an installed directory + +MODE-ARGS vary depending on the MODE. Try \`$modename --help --mode=MODE' for +a more detailed description of MODE. + +Report bugs to ." + exit $EXIT_SUCCESS + ;; + +clean) + $echo \ +"Usage: $modename [OPTION]... --mode=clean RM [RM-OPTION]... FILE... + +Remove files from the build directory. + +RM is the name of the program to use to delete files associated with each FILE +(typically \`/bin/rm'). RM-OPTIONS are options (such as \`-f') to be passed +to RM. + +If FILE is a libtool library, object or program, all the files associated +with it are deleted. Otherwise, only FILE itself is deleted using RM." + ;; + +compile) + $echo \ +"Usage: $modename [OPTION]... --mode=compile COMPILE-COMMAND... SOURCEFILE + +Compile a source file into a libtool library object. + +This mode accepts the following additional options: + + -o OUTPUT-FILE set the output file name to OUTPUT-FILE + -prefer-pic try to building PIC objects only + -prefer-non-pic try to building non-PIC objects only + -static always build a \`.o' file suitable for static linking + +COMPILE-COMMAND is a command to be used in creating a \`standard' object file +from the given SOURCEFILE. + +The output file name is determined by removing the directory component from +SOURCEFILE, then substituting the C source code suffix \`.c' with the +library object suffix, \`.lo'." + ;; + +execute) + $echo \ +"Usage: $modename [OPTION]... --mode=execute COMMAND [ARGS]... + +Automatically set library path, then run a program. + +This mode accepts the following additional options: + + -dlopen FILE add the directory containing FILE to the library path + +This mode sets the library path environment variable according to \`-dlopen' +flags. + +If any of the ARGS are libtool executable wrappers, then they are translated +into their corresponding uninstalled binary, and any of their required library +directories are added to the library path. + +Then, COMMAND is executed, with ARGS as arguments." + ;; + +finish) + $echo \ +"Usage: $modename [OPTION]... --mode=finish [LIBDIR]... + +Complete the installation of libtool libraries. + +Each LIBDIR is a directory that contains libtool libraries. + +The commands that this mode executes may require superuser privileges. Use +the \`--dry-run' option if you just want to see what would be executed." + ;; + +install) + $echo \ +"Usage: $modename [OPTION]... --mode=install INSTALL-COMMAND... + +Install executables or libraries. + +INSTALL-COMMAND is the installation command. The first component should be +either the \`install' or \`cp' program. + +The rest of the components are interpreted as arguments to that command (only +BSD-compatible install options are recognized)." + ;; + +link) + $echo \ +"Usage: $modename [OPTION]... --mode=link LINK-COMMAND... + +Link object files or libraries together to form another library, or to +create an executable program. + +LINK-COMMAND is a command using the C compiler that you would use to create +a program from several object files. + +The following components of LINK-COMMAND are treated specially: + + -all-static do not do any dynamic linking at all + -avoid-version do not add a version suffix if possible + -dlopen FILE \`-dlpreopen' FILE if it cannot be dlopened at runtime + -dlpreopen FILE link in FILE and add its symbols to lt_preloaded_symbols + -export-dynamic allow symbols from OUTPUT-FILE to be resolved with dlsym(3) + -export-symbols SYMFILE + try to export only the symbols listed in SYMFILE + -export-symbols-regex REGEX + try to export only the symbols matching REGEX + -LLIBDIR search LIBDIR for required installed libraries + -lNAME OUTPUT-FILE requires the installed library libNAME + -module build a library that can dlopened + -no-fast-install disable the fast-install mode + -no-install link a not-installable executable + -no-undefined declare that a library does not refer to external symbols + -o OUTPUT-FILE create OUTPUT-FILE from the specified objects + -objectlist FILE Use a list of object files found in FILE to specify objects + -precious-files-regex REGEX + don't remove output files matching REGEX + -release RELEASE specify package release information + -rpath LIBDIR the created library will eventually be installed in LIBDIR + -R[ ]LIBDIR add LIBDIR to the runtime path of programs and libraries + -static do not do any dynamic linking of uninstalled libtool libraries + -static-libtool-libs + do not do any dynamic linking of libtool libraries + -version-info CURRENT[:REVISION[:AGE]] + specify library version info [each variable defaults to 0] + +All other options (arguments beginning with \`-') are ignored. + +Every other argument is treated as a filename. Files ending in \`.la' are +treated as uninstalled libtool libraries, other files are standard or library +object files. + +If the OUTPUT-FILE ends in \`.la', then a libtool library is created, +only library objects (\`.lo' files) may be specified, and \`-rpath' is +required, except when creating a convenience library. + +If OUTPUT-FILE ends in \`.a' or \`.lib', then a standard library is created +using \`ar' and \`ranlib', or on Windows using \`lib'. + +If OUTPUT-FILE ends in \`.lo' or \`.${objext}', then a reloadable object file +is created, otherwise an executable program is created." + ;; + +uninstall) + $echo \ +"Usage: $modename [OPTION]... --mode=uninstall RM [RM-OPTION]... FILE... + +Remove libraries from an installation directory. + +RM is the name of the program to use to delete files associated with each FILE +(typically \`/bin/rm'). RM-OPTIONS are options (such as \`-f') to be passed +to RM. + +If FILE is a libtool library, all the files associated with it are deleted. +Otherwise, only FILE itself is deleted using RM." + ;; + +*) + $echo "$modename: invalid operation mode \`$mode'" 1>&2 + $echo "$help" 1>&2 + exit $EXIT_FAILURE + ;; +esac + +$echo +$echo "Try \`$modename --help' for more information about other modes." + +exit $? + +# The TAGs below are defined such that we never get into a situation +# in which we disable both kinds of libraries. Given conflicting +# choices, we go for a static library, that is the most portable, +# since we can't tell whether shared libraries were disabled because +# the user asked for that or because the platform doesn't support +# them. This is particularly important on AIX, because we don't +# support having both static and shared libraries enabled at the same +# time on that platform, so we default to a shared-only configuration. +# If a disable-shared tag is given, we'll fallback to a static-only +# configuration. But we'll never go from static-only to shared-only. + +# ### BEGIN LIBTOOL TAG CONFIG: disable-shared +disable_libs=shared +# ### END LIBTOOL TAG CONFIG: disable-shared + +# ### BEGIN LIBTOOL TAG CONFIG: disable-static +disable_libs=static +# ### END LIBTOOL TAG CONFIG: disable-static + +# Local Variables: +# mode:shell-script +# sh-indentation:2 +# End: diff --git a/third_party/libevent/mac/config.h b/third_party/libevent/mac/config.h new file mode 100644 index 0000000000..f73f0c6329 --- /dev/null +++ b/third_party/libevent/mac/config.h @@ -0,0 +1,266 @@ +/* config.h. Generated from config.h.in by configure. */ +/* config.h.in. Generated from configure.in by autoheader. */ + +/* Define if clock_gettime is available in libc */ +/* #undef DNS_USE_CPU_CLOCK_FOR_ID */ + +/* Define is no secure id variant is available */ +#define DNS_USE_GETTIMEOFDAY_FOR_ID 1 + +/* Define to 1 if you have the `clock_gettime' function. */ +/* #undef HAVE_CLOCK_GETTIME */ + +/* Define if /dev/poll is available */ +/* #undef HAVE_DEVPOLL */ + +/* Define to 1 if you have the header file. */ +#define HAVE_DLFCN_H 1 + +/* Define if your system supports the epoll system calls */ +/* #undef HAVE_EPOLL */ + +/* Define to 1 if you have the `epoll_ctl' function. */ +/* #undef HAVE_EPOLL_CTL */ + +/* Define if your system supports event ports */ +/* #undef HAVE_EVENT_PORTS */ + +/* Define to 1 if you have the `fcntl' function. */ +#define HAVE_FCNTL 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_FCNTL_H 1 + +/* Define to 1 if the system has the type `fd_mask'. */ +#define HAVE_FD_MASK 1 + +/* Define to 1 if you have the `getaddrinfo' function. */ +#define HAVE_GETADDRINFO 1 + +/* Define to 1 if you have the `getegid' function. */ +#define HAVE_GETEGID 1 + +/* Define to 1 if you have the `geteuid' function. */ +#define HAVE_GETEUID 1 + +/* Define to 1 if you have the `getnameinfo' function. */ +#define HAVE_GETNAMEINFO 1 + +/* Define to 1 if you have the `gettimeofday' function. */ +#define HAVE_GETTIMEOFDAY 1 + +/* Define to 1 if you have the `inet_ntop' function. */ +#define HAVE_INET_NTOP 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the `issetugid' function. */ +#define HAVE_ISSETUGID 1 + +/* Define to 1 if you have the `kqueue' function. */ +#define HAVE_KQUEUE 1 + +/* Define to 1 if you have the `nsl' library (-lnsl). */ +/* #undef HAVE_LIBNSL */ + +/* Define to 1 if you have the `resolv' library (-lresolv). */ +#define HAVE_LIBRESOLV 1 + +/* Define to 1 if you have the `rt' library (-lrt). */ +/* #undef HAVE_LIBRT */ + +/* Define to 1 if you have the `socket' library (-lsocket). */ +/* #undef HAVE_LIBSOCKET */ + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_NETINET_IN6_H */ + +/* Define to 1 if you have the `poll' function. */ +#define HAVE_POLL 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_POLL_H 1 + +/* Define to 1 if you have the `port_create' function. */ +/* #undef HAVE_PORT_CREATE */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_PORT_H */ + +/* Define to 1 if you have the `select' function. */ +#define HAVE_SELECT 1 + +/* Define if F_SETFD is defined in */ +#define HAVE_SETFD 1 + +/* Define to 1 if you have the `sigaction' function. */ +#define HAVE_SIGACTION 1 + +/* Define to 1 if you have the `signal' function. */ +#define HAVE_SIGNAL 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SIGNAL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDARG_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the `strlcpy' function. */ +#define HAVE_STRLCPY 1 + +/* Define to 1 if you have the `strsep' function. */ +#define HAVE_STRSEP 1 + +/* Define to 1 if you have the `strtok_r' function. */ +#define HAVE_STRTOK_R 1 + +/* Define to 1 if you have the `strtoll' function. */ +#define HAVE_STRTOLL 1 + +/* Define to 1 if the system has the type `struct in6_addr'. */ +#define HAVE_STRUCT_IN6_ADDR 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_DEVPOLL_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_EPOLL_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_EVENT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_IOCTL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_PARAM_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_QUEUE_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SELECT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SOCKET_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define if TAILQ_FOREACH is defined in */ +#define HAVE_TAILQFOREACH 1 + +/* Define if timeradd is defined in */ +#define HAVE_TIMERADD 1 + +/* Define if timerclear is defined in */ +#define HAVE_TIMERCLEAR 1 + +/* Define if timercmp is defined in */ +#define HAVE_TIMERCMP 1 + +/* Define if timerisset is defined in */ +#define HAVE_TIMERISSET 1 + +/* Define to 1 if the system has the type `uint16_t'. */ +#define HAVE_UINT16_T 1 + +/* Define to 1 if the system has the type `uint32_t'. */ +#define HAVE_UINT32_T 1 + +/* Define to 1 if the system has the type `uint64_t'. */ +#define HAVE_UINT64_T 1 + +/* Define to 1 if the system has the type `uint8_t'. */ +#define HAVE_UINT8_T 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to 1 if you have the `vasprintf' function. */ +#define HAVE_VASPRINTF 1 + +/* Define if kqueue works correctly with pipes */ +#define HAVE_WORKING_KQUEUE 1 + +/* Name of package */ +#define PACKAGE "libevent" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "" + +/* The size of `int', as computed by sizeof. */ +#define SIZEOF_INT 4 + +/* The size of `long', as computed by sizeof. */ +#define SIZEOF_LONG 4 + +/* The size of `long long', as computed by sizeof. */ +#define SIZEOF_LONG_LONG 8 + +/* The size of `short', as computed by sizeof. */ +#define SIZEOF_SHORT 2 + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Define to 1 if you can safely include both and . */ +#define TIME_WITH_SYS_TIME 1 + +/* Version number of package */ +#define VERSION "1.4.13-stable" + +/* Define to appropriate substitue if compiler doesnt have __func__ */ +/* #undef __func__ */ + +/* Define to empty if `const' does not conform to ANSI C. */ +/* #undef const */ + +/* Define to `__inline__' or `__inline' if that's what the C compiler + calls it, or to nothing if 'inline' is not supported under any name. */ +#ifndef __cplusplus +/* #undef inline */ +#endif + +/* Define to `int' if does not define. */ +/* #undef pid_t */ + +/* Define to `unsigned int' if does not define. */ +/* #undef size_t */ + +/* Define to unsigned int if you dont have it */ +/* #undef socklen_t */ diff --git a/third_party/libevent/mac/event-config.h b/third_party/libevent/mac/event-config.h new file mode 100644 index 0000000000..4af575a5da --- /dev/null +++ b/third_party/libevent/mac/event-config.h @@ -0,0 +1,274 @@ +/* event-config.h + * Generated by autoconf; post-processed by libevent. + * Do not edit this file. + * Do not rely on macros in this file existing in later versions. + */ +#ifndef _EVENT_CONFIG_H_ +#define _EVENT_CONFIG_H_ +/* config.h. Generated from config.h.in by configure. */ +/* config.h.in. Generated from configure.in by autoheader. */ + +/* Define if clock_gettime is available in libc */ +/* #undef _EVENT_DNS_USE_CPU_CLOCK_FOR_ID */ + +/* Define is no secure id variant is available */ +#define _EVENT_DNS_USE_GETTIMEOFDAY_FOR_ID 1 + +/* Define to 1 if you have the `clock_gettime' function. */ +/* #undef _EVENT_HAVE_CLOCK_GETTIME */ + +/* Define if /dev/poll is available */ +/* #undef _EVENT_HAVE_DEVPOLL */ + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_DLFCN_H 1 + +/* Define if your system supports the epoll system calls */ +/* #undef _EVENT_HAVE_EPOLL */ + +/* Define to 1 if you have the `epoll_ctl' function. */ +/* #undef _EVENT_HAVE_EPOLL_CTL */ + +/* Define if your system supports event ports */ +/* #undef _EVENT_HAVE_EVENT_PORTS */ + +/* Define to 1 if you have the `fcntl' function. */ +#define _EVENT_HAVE_FCNTL 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_FCNTL_H 1 + +/* Define to 1 if the system has the type `fd_mask'. */ +#define _EVENT_HAVE_FD_MASK 1 + +/* Define to 1 if you have the `getaddrinfo' function. */ +#define _EVENT_HAVE_GETADDRINFO 1 + +/* Define to 1 if you have the `getegid' function. */ +#define _EVENT_HAVE_GETEGID 1 + +/* Define to 1 if you have the `geteuid' function. */ +#define _EVENT_HAVE_GETEUID 1 + +/* Define to 1 if you have the `getnameinfo' function. */ +#define _EVENT_HAVE_GETNAMEINFO 1 + +/* Define to 1 if you have the `gettimeofday' function. */ +#define _EVENT_HAVE_GETTIMEOFDAY 1 + +/* Define to 1 if you have the `inet_ntop' function. */ +#define _EVENT_HAVE_INET_NTOP 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the `issetugid' function. */ +#define _EVENT_HAVE_ISSETUGID 1 + +/* Define to 1 if you have the `kqueue' function. */ +#define _EVENT_HAVE_KQUEUE 1 + +/* Define to 1 if you have the `nsl' library (-lnsl). */ +/* #undef _EVENT_HAVE_LIBNSL */ + +/* Define to 1 if you have the `resolv' library (-lresolv). */ +#define _EVENT_HAVE_LIBRESOLV 1 + +/* Define to 1 if you have the `rt' library (-lrt). */ +/* #undef _EVENT_HAVE_LIBRT */ + +/* Define to 1 if you have the `socket' library (-lsocket). */ +/* #undef _EVENT_HAVE_LIBSOCKET */ + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_MEMORY_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef _EVENT_HAVE_NETINET_IN6_H */ + +/* Define to 1 if you have the `poll' function. */ +#define _EVENT_HAVE_POLL 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_POLL_H 1 + +/* Define to 1 if you have the `port_create' function. */ +/* #undef _EVENT_HAVE_PORT_CREATE */ + +/* Define to 1 if you have the header file. */ +/* #undef _EVENT_HAVE_PORT_H */ + +/* Define to 1 if you have the `select' function. */ +#define _EVENT_HAVE_SELECT 1 + +/* Define if F_SETFD is defined in */ +#define _EVENT_HAVE_SETFD 1 + +/* Define to 1 if you have the `sigaction' function. */ +#define _EVENT_HAVE_SIGACTION 1 + +/* Define to 1 if you have the `signal' function. */ +#define _EVENT_HAVE_SIGNAL 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SIGNAL_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_STDARG_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_STRING_H 1 + +/* Define to 1 if you have the `strlcpy' function. */ +#define _EVENT_HAVE_STRLCPY 1 + +/* Define to 1 if you have the `strsep' function. */ +#define _EVENT_HAVE_STRSEP 1 + +/* Define to 1 if you have the `strtok_r' function. */ +#define _EVENT_HAVE_STRTOK_R 1 + +/* Define to 1 if you have the `strtoll' function. */ +#define _EVENT_HAVE_STRTOLL 1 + +/* Define to 1 if the system has the type `struct in6_addr'. */ +#define _EVENT_HAVE_STRUCT_IN6_ADDR 1 + +/* Define to 1 if you have the header file. */ +/* #undef _EVENT_HAVE_SYS_DEVPOLL_H */ + +/* Define to 1 if you have the header file. */ +/* #undef _EVENT_HAVE_SYS_EPOLL_H */ + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_EVENT_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_IOCTL_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_PARAM_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_QUEUE_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_SELECT_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_SOCKET_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_TYPES_H 1 + +/* Define if TAILQ_FOREACH is defined in */ +#define _EVENT_HAVE_TAILQFOREACH 1 + +/* Define if timeradd is defined in */ +#define _EVENT_HAVE_TIMERADD 1 + +/* Define if timerclear is defined in */ +#define _EVENT_HAVE_TIMERCLEAR 1 + +/* Define if timercmp is defined in */ +#define _EVENT_HAVE_TIMERCMP 1 + +/* Define if timerisset is defined in */ +#define _EVENT_HAVE_TIMERISSET 1 + +/* Define to 1 if the system has the type `uint16_t'. */ +#define _EVENT_HAVE_UINT16_T 1 + +/* Define to 1 if the system has the type `uint32_t'. */ +#define _EVENT_HAVE_UINT32_T 1 + +/* Define to 1 if the system has the type `uint64_t'. */ +#define _EVENT_HAVE_UINT64_T 1 + +/* Define to 1 if the system has the type `uint8_t'. */ +#define _EVENT_HAVE_UINT8_T 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_UNISTD_H 1 + +/* Define to 1 if you have the `vasprintf' function. */ +#define _EVENT_HAVE_VASPRINTF 1 + +/* Define if kqueue works correctly with pipes */ +#define _EVENT_HAVE_WORKING_KQUEUE 1 + +/* Name of package */ +#define _EVENT_PACKAGE "libevent" + +/* Define to the address where bug reports for this package should be sent. */ +#define _EVENT_PACKAGE_BUGREPORT "" + +/* Define to the full name of this package. */ +#define _EVENT_PACKAGE_NAME "" + +/* Define to the full name and version of this package. */ +#define _EVENT_PACKAGE_STRING "" + +/* Define to the one symbol short name of this package. */ +#define _EVENT_PACKAGE_TARNAME "" + +/* Define to the version of this package. */ +#define _EVENT_PACKAGE_VERSION "" + +/* The size of `int', as computed by sizeof. */ +#define _EVENT_SIZEOF_INT 4 + +/* The size of `long', as computed by sizeof. */ +#define _EVENT_SIZEOF_LONG 4 + +/* The size of `long long', as computed by sizeof. */ +#define _EVENT_SIZEOF_LONG_LONG 8 + +/* The size of `short', as computed by sizeof. */ +#define _EVENT_SIZEOF_SHORT 2 + +/* Define to 1 if you have the ANSI C header files. */ +#define _EVENT_STDC_HEADERS 1 + +/* Define to 1 if you can safely include both and . */ +#define _EVENT_TIME_WITH_SYS_TIME 1 + +/* Version number of package */ +#define _EVENT_VERSION "1.4.13-stable" + +/* Define to appropriate substitue if compiler doesnt have __func__ */ +/* #undef _EVENT___func__ */ + +/* Define to empty if `const' does not conform to ANSI C. */ +/* #undef _EVENT_const */ + +/* Define to `__inline__' or `__inline' if that's what the C compiler + calls it, or to nothing if 'inline' is not supported under any name. */ +#ifndef _EVENT___cplusplus +/* #undef _EVENT_inline */ +#endif + +/* Define to `int' if does not define. */ +/* #undef _EVENT_pid_t */ + +/* Define to `unsigned int' if does not define. */ +/* #undef _EVENT_size_t */ + +/* Define to unsigned int if you dont have it */ +/* #undef _EVENT_socklen_t */ +#endif diff --git a/third_party/libevent/min_heap.h b/third_party/libevent/min_heap.h new file mode 100644 index 0000000000..4fc83c01e7 --- /dev/null +++ b/third_party/libevent/min_heap.h @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2006 Maxim Yegorushkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef _MIN_HEAP_H_ +#define _MIN_HEAP_H_ + +#include "event.h" +#include "evutil.h" + +typedef struct min_heap +{ + struct event** p; + unsigned n, a; +} min_heap_t; + +static inline void min_heap_ctor(min_heap_t* s); +static inline void min_heap_dtor(min_heap_t* s); +static inline void min_heap_elem_init(struct event* e); +static inline int min_heap_elem_greater(struct event *a, struct event *b); +static inline int min_heap_empty(min_heap_t* s); +static inline unsigned min_heap_size(min_heap_t* s); +static inline struct event* min_heap_top(min_heap_t* s); +static inline int min_heap_reserve(min_heap_t* s, unsigned n); +static inline int min_heap_push(min_heap_t* s, struct event* e); +static inline struct event* min_heap_pop(min_heap_t* s); +static inline int min_heap_erase(min_heap_t* s, struct event* e); +static inline void min_heap_shift_up_(min_heap_t* s, unsigned hole_index, struct event* e); +static inline void min_heap_shift_down_(min_heap_t* s, unsigned hole_index, struct event* e); + +int min_heap_elem_greater(struct event *a, struct event *b) +{ + return evutil_timercmp(&a->ev_timeout, &b->ev_timeout, >); +} + +void min_heap_ctor(min_heap_t* s) { s->p = 0; s->n = 0; s->a = 0; } +void min_heap_dtor(min_heap_t* s) { free(s->p); } +void min_heap_elem_init(struct event* e) { e->min_heap_idx = -1; } +int min_heap_empty(min_heap_t* s) { return 0u == s->n; } +unsigned min_heap_size(min_heap_t* s) { return s->n; } +struct event* min_heap_top(min_heap_t* s) { return s->n ? *s->p : 0; } + +int min_heap_push(min_heap_t* s, struct event* e) +{ + if(min_heap_reserve(s, s->n + 1)) + return -1; + min_heap_shift_up_(s, s->n++, e); + return 0; +} + +struct event* min_heap_pop(min_heap_t* s) +{ + if(s->n) + { + struct event* e = *s->p; + min_heap_shift_down_(s, 0u, s->p[--s->n]); + e->min_heap_idx = -1; + return e; + } + return 0; +} + +int min_heap_erase(min_heap_t* s, struct event* e) +{ + if(((unsigned int)-1) != e->min_heap_idx) + { + struct event *last = s->p[--s->n]; + unsigned parent = (e->min_heap_idx - 1) / 2; + /* we replace e with the last element in the heap. We might need to + shift it upward if it is less than its parent, or downward if it is + greater than one or both its children. Since the children are known + to be less than the parent, it can't need to shift both up and + down. */ + if (e->min_heap_idx > 0 && min_heap_elem_greater(s->p[parent], last)) + min_heap_shift_up_(s, e->min_heap_idx, last); + else + min_heap_shift_down_(s, e->min_heap_idx, last); + e->min_heap_idx = -1; + return 0; + } + return -1; +} + +int min_heap_reserve(min_heap_t* s, unsigned n) +{ + if(s->a < n) + { + struct event** p; + unsigned a = s->a ? s->a * 2 : 8; + if(a < n) + a = n; + if(!(p = (struct event**)realloc(s->p, a * sizeof *p))) + return -1; + s->p = p; + s->a = a; + } + return 0; +} + +void min_heap_shift_up_(min_heap_t* s, unsigned hole_index, struct event* e) +{ + unsigned parent = (hole_index - 1) / 2; + while(hole_index && min_heap_elem_greater(s->p[parent], e)) + { + (s->p[hole_index] = s->p[parent])->min_heap_idx = hole_index; + hole_index = parent; + parent = (hole_index - 1) / 2; + } + (s->p[hole_index] = e)->min_heap_idx = hole_index; +} + +void min_heap_shift_down_(min_heap_t* s, unsigned hole_index, struct event* e) +{ + unsigned min_child = 2 * (hole_index + 1); + while(min_child <= s->n) + { + min_child -= min_child == s->n || min_heap_elem_greater(s->p[min_child], s->p[min_child - 1]); + if(!(min_heap_elem_greater(e, s->p[min_child]))) + break; + (s->p[hole_index] = s->p[min_child])->min_heap_idx = hole_index; + hole_index = min_child; + min_child = 2 * (hole_index + 1); + } + min_heap_shift_up_(s, hole_index, e); +} + +#endif /* _MIN_HEAP_H_ */ diff --git a/third_party/libevent/missing b/third_party/libevent/missing new file mode 100644 index 0000000000..e7ef83a1c2 --- /dev/null +++ b/third_party/libevent/missing @@ -0,0 +1,360 @@ +#! /bin/sh +# Common stub for a few missing GNU programs while installing. + +scriptversion=2003-09-02.23 + +# Copyright (C) 1996, 1997, 1999, 2000, 2002, 2003 +# Free Software Foundation, Inc. +# Originally by Fran,cois Pinard , 1996. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +# 02111-1307, USA. + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +if test $# -eq 0; then + echo 1>&2 "Try \`$0 --help' for more information" + exit 1 +fi + +run=: + +# In the cases where this matters, `missing' is being run in the +# srcdir already. +if test -f configure.ac; then + configure_ac=configure.ac +else + configure_ac=configure.in +fi + +msg="missing on your system" + +case "$1" in +--run) + # Try to run requested program, and just exit if it succeeds. + run= + shift + "$@" && exit 0 + # Exit code 63 means version mismatch. This often happens + # when the user try to use an ancient version of a tool on + # a file that requires a minimum version. In this case we + # we should proceed has if the program had been absent, or + # if --run hadn't been passed. + if test $? = 63; then + run=: + msg="probably too old" + fi + ;; +esac + +# If it does not exist, or fails to run (possibly an outdated version), +# try to emulate it. +case "$1" in + + -h|--h|--he|--hel|--help) + echo "\ +$0 [OPTION]... PROGRAM [ARGUMENT]... + +Handle \`PROGRAM [ARGUMENT]...' for when PROGRAM is missing, or return an +error status if there is no known handling for PROGRAM. + +Options: + -h, --help display this help and exit + -v, --version output version information and exit + --run try to run the given command, and emulate it if it fails + +Supported PROGRAM values: + aclocal touch file \`aclocal.m4' + autoconf touch file \`configure' + autoheader touch file \`config.h.in' + automake touch all \`Makefile.in' files + bison create \`y.tab.[ch]', if possible, from existing .[ch] + flex create \`lex.yy.c', if possible, from existing .c + help2man touch the output file + lex create \`lex.yy.c', if possible, from existing .c + makeinfo touch the output file + tar try tar, gnutar, gtar, then tar without non-portable flags + yacc create \`y.tab.[ch]', if possible, from existing .[ch] + +Send bug reports to ." + ;; + + -v|--v|--ve|--ver|--vers|--versi|--versio|--version) + echo "missing $scriptversion (GNU Automake)" + ;; + + -*) + echo 1>&2 "$0: Unknown \`$1' option" + echo 1>&2 "Try \`$0 --help' for more information" + exit 1 + ;; + + aclocal*) + if test -z "$run" && ($1 --version) > /dev/null 2>&1; then + # We have it, but it failed. + exit 1 + fi + + echo 1>&2 "\ +WARNING: \`$1' is $msg. You should only need it if + you modified \`acinclude.m4' or \`${configure_ac}'. You might want + to install the \`Automake' and \`Perl' packages. Grab them from + any GNU archive site." + touch aclocal.m4 + ;; + + autoconf) + if test -z "$run" && ($1 --version) > /dev/null 2>&1; then + # We have it, but it failed. + exit 1 + fi + + echo 1>&2 "\ +WARNING: \`$1' is $msg. You should only need it if + you modified \`${configure_ac}'. You might want to install the + \`Autoconf' and \`GNU m4' packages. Grab them from any GNU + archive site." + touch configure + ;; + + autoheader) + if test -z "$run" && ($1 --version) > /dev/null 2>&1; then + # We have it, but it failed. + exit 1 + fi + + echo 1>&2 "\ +WARNING: \`$1' is $msg. You should only need it if + you modified \`acconfig.h' or \`${configure_ac}'. You might want + to install the \`Autoconf' and \`GNU m4' packages. Grab them + from any GNU archive site." + files=`sed -n 's/^[ ]*A[CM]_CONFIG_HEADER(\([^)]*\)).*/\1/p' ${configure_ac}` + test -z "$files" && files="config.h" + touch_files= + for f in $files; do + case "$f" in + *:*) touch_files="$touch_files "`echo "$f" | + sed -e 's/^[^:]*://' -e 's/:.*//'`;; + *) touch_files="$touch_files $f.in";; + esac + done + touch $touch_files + ;; + + automake*) + if test -z "$run" && ($1 --version) > /dev/null 2>&1; then + # We have it, but it failed. + exit 1 + fi + + echo 1>&2 "\ +WARNING: \`$1' is $msg. You should only need it if + you modified \`Makefile.am', \`acinclude.m4' or \`${configure_ac}'. + You might want to install the \`Automake' and \`Perl' packages. + Grab them from any GNU archive site." + find . -type f -name Makefile.am -print | + sed 's/\.am$/.in/' | + while read f; do touch "$f"; done + ;; + + autom4te) + if test -z "$run" && ($1 --version) > /dev/null 2>&1; then + # We have it, but it failed. + exit 1 + fi + + echo 1>&2 "\ +WARNING: \`$1' is needed, but is $msg. + You might have modified some files without having the + proper tools for further handling them. + You can get \`$1' as part of \`Autoconf' from any GNU + archive site." + + file=`echo "$*" | sed -n 's/.*--output[ =]*\([^ ]*\).*/\1/p'` + test -z "$file" && file=`echo "$*" | sed -n 's/.*-o[ ]*\([^ ]*\).*/\1/p'` + if test -f "$file"; then + touch $file + else + test -z "$file" || exec >$file + echo "#! /bin/sh" + echo "# Created by GNU Automake missing as a replacement of" + echo "# $ $@" + echo "exit 0" + chmod +x $file + exit 1 + fi + ;; + + bison|yacc) + echo 1>&2 "\ +WARNING: \`$1' $msg. You should only need it if + you modified a \`.y' file. You may need the \`Bison' package + in order for those modifications to take effect. You can get + \`Bison' from any GNU archive site." + rm -f y.tab.c y.tab.h + if [ $# -ne 1 ]; then + eval LASTARG="\${$#}" + case "$LASTARG" in + *.y) + SRCFILE=`echo "$LASTARG" | sed 's/y$/c/'` + if [ -f "$SRCFILE" ]; then + cp "$SRCFILE" y.tab.c + fi + SRCFILE=`echo "$LASTARG" | sed 's/y$/h/'` + if [ -f "$SRCFILE" ]; then + cp "$SRCFILE" y.tab.h + fi + ;; + esac + fi + if [ ! -f y.tab.h ]; then + echo >y.tab.h + fi + if [ ! -f y.tab.c ]; then + echo 'main() { return 0; }' >y.tab.c + fi + ;; + + lex|flex) + echo 1>&2 "\ +WARNING: \`$1' is $msg. You should only need it if + you modified a \`.l' file. You may need the \`Flex' package + in order for those modifications to take effect. You can get + \`Flex' from any GNU archive site." + rm -f lex.yy.c + if [ $# -ne 1 ]; then + eval LASTARG="\${$#}" + case "$LASTARG" in + *.l) + SRCFILE=`echo "$LASTARG" | sed 's/l$/c/'` + if [ -f "$SRCFILE" ]; then + cp "$SRCFILE" lex.yy.c + fi + ;; + esac + fi + if [ ! -f lex.yy.c ]; then + echo 'main() { return 0; }' >lex.yy.c + fi + ;; + + help2man) + if test -z "$run" && ($1 --version) > /dev/null 2>&1; then + # We have it, but it failed. + exit 1 + fi + + echo 1>&2 "\ +WARNING: \`$1' is $msg. You should only need it if + you modified a dependency of a manual page. You may need the + \`Help2man' package in order for those modifications to take + effect. You can get \`Help2man' from any GNU archive site." + + file=`echo "$*" | sed -n 's/.*-o \([^ ]*\).*/\1/p'` + if test -z "$file"; then + file=`echo "$*" | sed -n 's/.*--output=\([^ ]*\).*/\1/p'` + fi + if [ -f "$file" ]; then + touch $file + else + test -z "$file" || exec >$file + echo ".ab help2man is required to generate this page" + exit 1 + fi + ;; + + makeinfo) + if test -z "$run" && (makeinfo --version) > /dev/null 2>&1; then + # We have makeinfo, but it failed. + exit 1 + fi + + echo 1>&2 "\ +WARNING: \`$1' is $msg. You should only need it if + you modified a \`.texi' or \`.texinfo' file, or any other file + indirectly affecting the aspect of the manual. The spurious + call might also be the consequence of using a buggy \`make' (AIX, + DU, IRIX). You might want to install the \`Texinfo' package or + the \`GNU make' package. Grab either from any GNU archive site." + file=`echo "$*" | sed -n 's/.*-o \([^ ]*\).*/\1/p'` + if test -z "$file"; then + file=`echo "$*" | sed 's/.* \([^ ]*\) *$/\1/'` + file=`sed -n '/^@setfilename/ { s/.* \([^ ]*\) *$/\1/; p; q; }' $file` + fi + touch $file + ;; + + tar) + shift + if test -n "$run"; then + echo 1>&2 "ERROR: \`tar' requires --run" + exit 1 + fi + + # We have already tried tar in the generic part. + # Look for gnutar/gtar before invocation to avoid ugly error + # messages. + if (gnutar --version > /dev/null 2>&1); then + gnutar "$@" && exit 0 + fi + if (gtar --version > /dev/null 2>&1); then + gtar "$@" && exit 0 + fi + firstarg="$1" + if shift; then + case "$firstarg" in + *o*) + firstarg=`echo "$firstarg" | sed s/o//` + tar "$firstarg" "$@" && exit 0 + ;; + esac + case "$firstarg" in + *h*) + firstarg=`echo "$firstarg" | sed s/h//` + tar "$firstarg" "$@" && exit 0 + ;; + esac + fi + + echo 1>&2 "\ +WARNING: I can't seem to be able to run \`tar' with the given arguments. + You may want to install GNU tar or Free paxutils, or check the + command line arguments." + exit 1 + ;; + + *) + echo 1>&2 "\ +WARNING: \`$1' is needed, and is $msg. + You might have modified some files without having the + proper tools for further handling them. Check the \`README' file, + it often tells you about the needed prerequisites for installing + this package. You may also peek at any GNU archive site, in case + some other package would contain this missing \`$1' program." + exit 1 + ;; +esac + +exit 0 + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-end: "$" +# End: diff --git a/third_party/libevent/mkinstalldirs b/third_party/libevent/mkinstalldirs new file mode 100644 index 0000000000..56d6671096 --- /dev/null +++ b/third_party/libevent/mkinstalldirs @@ -0,0 +1,40 @@ +#! /bin/sh +# mkinstalldirs --- make directory hierarchy +# Author: Noah Friedman +# Created: 1993-05-16 +# Public domain + +# $Id: mkinstalldirs 11 2002-04-09 17:52:23Z nprovos $ + +errstatus=0 + +for file +do + set fnord `echo ":$file" | sed -ne 's/^:\//#/;s/^://;s/\// /g;s/^#/\//;p'` + shift + + pathcomp= + for d + do + pathcomp="$pathcomp$d" + case "$pathcomp" in + -* ) pathcomp=./$pathcomp ;; + esac + + if test ! -d "$pathcomp"; then + echo "mkdir $pathcomp" + + mkdir "$pathcomp" || lasterr=$? + + if test ! -d "$pathcomp"; then + errstatus=$lasterr + fi + fi + + pathcomp="$pathcomp/" + done +done + +exit $errstatus + +# mkinstalldirs ends here diff --git a/third_party/libevent/poll.c b/third_party/libevent/poll.c new file mode 100644 index 0000000000..2aa245b371 --- /dev/null +++ b/third_party/libevent/poll.c @@ -0,0 +1,379 @@ +/* $OpenBSD: poll.c,v 1.2 2002/06/25 15:50:15 mickey Exp $ */ + +/* + * Copyright 2000-2003 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#ifdef HAVE_SYS_TIME_H +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CHECK_INVARIANTS +#include +#endif + +#include "event.h" +#include "event-internal.h" +#include "evsignal.h" +#include "log.h" + +struct pollop { + int event_count; /* Highest number alloc */ + int nfds; /* Size of event_* */ + int fd_count; /* Size of idxplus1_by_fd */ + struct pollfd *event_set; + struct event **event_r_back; + struct event **event_w_back; + int *idxplus1_by_fd; /* Index into event_set by fd; we add 1 so + * that 0 (which is easy to memset) can mean + * "no entry." */ +}; + +static void *poll_init (struct event_base *); +static int poll_add (void *, struct event *); +static int poll_del (void *, struct event *); +static int poll_dispatch (struct event_base *, void *, struct timeval *); +static void poll_dealloc (struct event_base *, void *); + +const struct eventop pollops = { + "poll", + poll_init, + poll_add, + poll_del, + poll_dispatch, + poll_dealloc, + 0 +}; + +static void * +poll_init(struct event_base *base) +{ + struct pollop *pollop; + + /* Disable poll when this environment variable is set */ + if (evutil_getenv("EVENT_NOPOLL")) + return (NULL); + + if (!(pollop = calloc(1, sizeof(struct pollop)))) + return (NULL); + + evsignal_init(base); + + return (pollop); +} + +#ifdef CHECK_INVARIANTS +static void +poll_check_ok(struct pollop *pop) +{ + int i, idx; + struct event *ev; + + for (i = 0; i < pop->fd_count; ++i) { + idx = pop->idxplus1_by_fd[i]-1; + if (idx < 0) + continue; + assert(pop->event_set[idx].fd == i); + if (pop->event_set[idx].events & POLLIN) { + ev = pop->event_r_back[idx]; + assert(ev); + assert(ev->ev_events & EV_READ); + assert(ev->ev_fd == i); + } + if (pop->event_set[idx].events & POLLOUT) { + ev = pop->event_w_back[idx]; + assert(ev); + assert(ev->ev_events & EV_WRITE); + assert(ev->ev_fd == i); + } + } + for (i = 0; i < pop->nfds; ++i) { + struct pollfd *pfd = &pop->event_set[i]; + assert(pop->idxplus1_by_fd[pfd->fd] == i+1); + } +} +#else +#define poll_check_ok(pop) +#endif + +static int +poll_dispatch(struct event_base *base, void *arg, struct timeval *tv) +{ + int res, i, j, msec = -1, nfds; + struct pollop *pop = arg; + + poll_check_ok(pop); + + if (tv != NULL) + msec = tv->tv_sec * 1000 + (tv->tv_usec + 999) / 1000; + + nfds = pop->nfds; + res = poll(pop->event_set, nfds, msec); + + if (res == -1) { + if (errno != EINTR) { + event_warn("poll"); + return (-1); + } + + evsignal_process(base); + return (0); + } else if (base->sig.evsignal_caught) { + evsignal_process(base); + } + + event_debug(("%s: poll reports %d", __func__, res)); + + if (res == 0 || nfds == 0) + return (0); + + i = random() % nfds; + for (j = 0; j < nfds; j++) { + struct event *r_ev = NULL, *w_ev = NULL; + int what; + if (++i == nfds) + i = 0; + what = pop->event_set[i].revents; + + if (!what) + continue; + + res = 0; + + /* If the file gets closed notify */ + if (what & (POLLHUP|POLLERR)) + what |= POLLIN|POLLOUT; + if (what & POLLIN) { + res |= EV_READ; + r_ev = pop->event_r_back[i]; + } + if (what & POLLOUT) { + res |= EV_WRITE; + w_ev = pop->event_w_back[i]; + } + if (res == 0) + continue; + + if (r_ev && (res & r_ev->ev_events)) { + event_active(r_ev, res & r_ev->ev_events, 1); + } + if (w_ev && w_ev != r_ev && (res & w_ev->ev_events)) { + event_active(w_ev, res & w_ev->ev_events, 1); + } + } + + return (0); +} + +static int +poll_add(void *arg, struct event *ev) +{ + struct pollop *pop = arg; + struct pollfd *pfd = NULL; + int i; + + if (ev->ev_events & EV_SIGNAL) + return (evsignal_add(ev)); + if (!(ev->ev_events & (EV_READ|EV_WRITE))) + return (0); + + poll_check_ok(pop); + if (pop->nfds + 1 >= pop->event_count) { + struct pollfd *tmp_event_set; + struct event **tmp_event_r_back; + struct event **tmp_event_w_back; + int tmp_event_count; + + if (pop->event_count < 32) + tmp_event_count = 32; + else + tmp_event_count = pop->event_count * 2; + + /* We need more file descriptors */ + tmp_event_set = realloc(pop->event_set, + tmp_event_count * sizeof(struct pollfd)); + if (tmp_event_set == NULL) { + event_warn("realloc"); + return (-1); + } + pop->event_set = tmp_event_set; + + tmp_event_r_back = realloc(pop->event_r_back, + tmp_event_count * sizeof(struct event *)); + if (tmp_event_r_back == NULL) { + /* event_set overallocated; that's okay. */ + event_warn("realloc"); + return (-1); + } + pop->event_r_back = tmp_event_r_back; + + tmp_event_w_back = realloc(pop->event_w_back, + tmp_event_count * sizeof(struct event *)); + if (tmp_event_w_back == NULL) { + /* event_set and event_r_back overallocated; that's + * okay. */ + event_warn("realloc"); + return (-1); + } + pop->event_w_back = tmp_event_w_back; + + pop->event_count = tmp_event_count; + } + if (ev->ev_fd >= pop->fd_count) { + int *tmp_idxplus1_by_fd; + int new_count; + if (pop->fd_count < 32) + new_count = 32; + else + new_count = pop->fd_count * 2; + while (new_count <= ev->ev_fd) + new_count *= 2; + tmp_idxplus1_by_fd = + realloc(pop->idxplus1_by_fd, new_count * sizeof(int)); + if (tmp_idxplus1_by_fd == NULL) { + event_warn("realloc"); + return (-1); + } + pop->idxplus1_by_fd = tmp_idxplus1_by_fd; + memset(pop->idxplus1_by_fd + pop->fd_count, + 0, sizeof(int)*(new_count - pop->fd_count)); + pop->fd_count = new_count; + } + + i = pop->idxplus1_by_fd[ev->ev_fd] - 1; + if (i >= 0) { + pfd = &pop->event_set[i]; + } else { + i = pop->nfds++; + pfd = &pop->event_set[i]; + pfd->events = 0; + pfd->fd = ev->ev_fd; + pop->event_w_back[i] = pop->event_r_back[i] = NULL; + pop->idxplus1_by_fd[ev->ev_fd] = i + 1; + } + + pfd->revents = 0; + if (ev->ev_events & EV_WRITE) { + pfd->events |= POLLOUT; + pop->event_w_back[i] = ev; + } + if (ev->ev_events & EV_READ) { + pfd->events |= POLLIN; + pop->event_r_back[i] = ev; + } + poll_check_ok(pop); + + return (0); +} + +/* + * Nothing to be done here. + */ + +static int +poll_del(void *arg, struct event *ev) +{ + struct pollop *pop = arg; + struct pollfd *pfd = NULL; + int i; + + if (ev->ev_events & EV_SIGNAL) + return (evsignal_del(ev)); + + if (!(ev->ev_events & (EV_READ|EV_WRITE))) + return (0); + + poll_check_ok(pop); + i = pop->idxplus1_by_fd[ev->ev_fd] - 1; + if (i < 0) + return (-1); + + /* Do we still want to read or write? */ + pfd = &pop->event_set[i]; + if (ev->ev_events & EV_READ) { + pfd->events &= ~POLLIN; + pop->event_r_back[i] = NULL; + } + if (ev->ev_events & EV_WRITE) { + pfd->events &= ~POLLOUT; + pop->event_w_back[i] = NULL; + } + poll_check_ok(pop); + if (pfd->events) + /* Another event cares about that fd. */ + return (0); + + /* Okay, so we aren't interested in that fd anymore. */ + pop->idxplus1_by_fd[ev->ev_fd] = 0; + + --pop->nfds; + if (i != pop->nfds) { + /* + * Shift the last pollfd down into the now-unoccupied + * position. + */ + memcpy(&pop->event_set[i], &pop->event_set[pop->nfds], + sizeof(struct pollfd)); + pop->event_r_back[i] = pop->event_r_back[pop->nfds]; + pop->event_w_back[i] = pop->event_w_back[pop->nfds]; + pop->idxplus1_by_fd[pop->event_set[i].fd] = i + 1; + } + + poll_check_ok(pop); + return (0); +} + +static void +poll_dealloc(struct event_base *base, void *arg) +{ + struct pollop *pop = arg; + + evsignal_dealloc(base); + if (pop->event_set) + free(pop->event_set); + if (pop->event_r_back) + free(pop->event_r_back); + if (pop->event_w_back) + free(pop->event_w_back); + if (pop->idxplus1_by_fd) + free(pop->idxplus1_by_fd); + + memset(pop, 0, sizeof(struct pollop)); + free(pop); +} diff --git a/third_party/libevent/sample/Makefile.am b/third_party/libevent/sample/Makefile.am new file mode 100644 index 0000000000..2f4e26e2f3 --- /dev/null +++ b/third_party/libevent/sample/Makefile.am @@ -0,0 +1,14 @@ +AUTOMAKE_OPTIONS = foreign no-dependencies + +LDADD = ../libevent.la +AM_CFLAGS = -I$(top_srcdir) -I$(top_srcdir)/compat + +noinst_PROGRAMS = event-test time-test signal-test + +event_test_sources = event-test.c +time_test_sources = time-test.c +signal_test_sources = signal-test.c + +verify: + +DISTCLEANFILES = *~ diff --git a/third_party/libevent/sample/Makefile.in b/third_party/libevent/sample/Makefile.in new file mode 100644 index 0000000000..793752ad98 --- /dev/null +++ b/third_party/libevent/sample/Makefile.in @@ -0,0 +1,442 @@ +# Makefile.in generated by automake 1.10.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +noinst_PROGRAMS = event-test$(EXEEXT) time-test$(EXEEXT) \ + signal-test$(EXEEXT) +subdir = sample +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.in +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +PROGRAMS = $(noinst_PROGRAMS) +event_test_SOURCES = event-test.c +event_test_OBJECTS = event-test.$(OBJEXT) +event_test_LDADD = $(LDADD) +event_test_DEPENDENCIES = ../libevent.la +signal_test_SOURCES = signal-test.c +signal_test_OBJECTS = signal-test.$(OBJEXT) +signal_test_LDADD = $(LDADD) +signal_test_DEPENDENCIES = ../libevent.la +time_test_SOURCES = time-test.c +time_test_OBJECTS = time-test.$(OBJEXT) +time_test_LDADD = $(LDADD) +time_test_DEPENDENCIES = ../libevent.la +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = +am__depfiles_maybe = +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ + --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +CCLD = $(CC) +LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ + --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ + $(LDFLAGS) -o $@ +SOURCES = event-test.c signal-test.c time-test.c +DIST_SOURCES = event-test.c signal-test.c time-test.c +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DSYMUTIL = @DSYMUTIL@ +ECHO = @ECHO@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +F77 = @F77@ +FFLAGS = @FFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIBTOOL_DEPS = @LIBTOOL_DEPS@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +NMEDIT = @NMEDIT@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_F77 = @ac_ct_F77@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AUTOMAKE_OPTIONS = foreign no-dependencies +LDADD = ../libevent.la +AM_CFLAGS = -I$(top_srcdir) -I$(top_srcdir)/compat +event_test_sources = event-test.c +time_test_sources = time-test.c +signal_test_sources = signal-test.c +DISTCLEANFILES = *~ +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh \ + && exit 0; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign sample/Makefile'; \ + cd $(top_srcdir) && \ + $(AUTOMAKE) --foreign sample/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +clean-noinstPROGRAMS: + @list='$(noinst_PROGRAMS)'; for p in $$list; do \ + f=`echo $$p|sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f $$p $$f"; \ + rm -f $$p $$f ; \ + done +event-test$(EXEEXT): $(event_test_OBJECTS) $(event_test_DEPENDENCIES) + @rm -f event-test$(EXEEXT) + $(LINK) $(event_test_OBJECTS) $(event_test_LDADD) $(LIBS) +signal-test$(EXEEXT): $(signal_test_OBJECTS) $(signal_test_DEPENDENCIES) + @rm -f signal-test$(EXEEXT) + $(LINK) $(signal_test_OBJECTS) $(signal_test_LDADD) $(LIBS) +time-test$(EXEEXT): $(time_test_OBJECTS) $(time_test_DEPENDENCIES) + @rm -f time-test$(EXEEXT) + $(LINK) $(time_test_OBJECTS) $(time_test_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +.c.o: + $(COMPILE) -c $< + +.c.obj: + $(COMPILE) -c `$(CYGPATH_W) '$<'` + +.c.lo: + $(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonemtpy = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$tags $$unique; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$tags$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$tags $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && cd $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) $$here + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \ + fi; \ + cp -pR $$d/$$file $(distdir)$$dir || exit 1; \ + else \ + test -f $(distdir)/$$file \ + || cp -p $$d/$$file $(distdir)/$$file \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-noinstPROGRAMS \ + mostlyclean-am + +distclean: distclean-am + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-exec-am: + +install-html: install-html-am + +install-info: install-info-am + +install-man: + +install-pdf: install-pdf-am + +install-ps: install-ps-am + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \ + clean-libtool clean-noinstPROGRAMS ctags distclean \ + distclean-compile distclean-generic distclean-libtool \ + distclean-tags distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ + pdf pdf-am ps ps-am tags uninstall uninstall-am + + +verify: +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/third_party/libevent/sample/event-test.c b/third_party/libevent/sample/event-test.c new file mode 100644 index 0000000000..2c6cb93864 --- /dev/null +++ b/third_party/libevent/sample/event-test.c @@ -0,0 +1,139 @@ +/* + * Compile with: + * cc -I/usr/local/include -o event-test event-test.c -L/usr/local/lib -levent + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#ifndef WIN32 +#include +#include +#include +#else +#include +#endif +#include +#include +#include +#include +#include + +#include + +static void +fifo_read(int fd, short event, void *arg) +{ + char buf[255]; + int len; + struct event *ev = arg; +#ifdef WIN32 + DWORD dwBytesRead; +#endif + + /* Reschedule this event */ + event_add(ev, NULL); + + fprintf(stderr, "fifo_read called with fd: %d, event: %d, arg: %p\n", + fd, event, arg); +#ifdef WIN32 + len = ReadFile((HANDLE)fd, buf, sizeof(buf) - 1, &dwBytesRead, NULL); + + // Check for end of file. + if(len && dwBytesRead == 0) { + fprintf(stderr, "End Of File"); + event_del(ev); + return; + } + + buf[dwBytesRead] = '\0'; +#else + len = read(fd, buf, sizeof(buf) - 1); + + if (len == -1) { + perror("read"); + return; + } else if (len == 0) { + fprintf(stderr, "Connection closed\n"); + return; + } + + buf[len] = '\0'; +#endif + fprintf(stdout, "Read: %s\n", buf); +} + +int +main (int argc, char **argv) +{ + struct event evfifo; +#ifdef WIN32 + HANDLE socket; + // Open a file. + socket = CreateFile("test.txt", // open File + GENERIC_READ, // open for reading + 0, // do not share + NULL, // no security + OPEN_EXISTING, // existing file only + FILE_ATTRIBUTE_NORMAL, // normal file + NULL); // no attr. template + + if(socket == INVALID_HANDLE_VALUE) + return 1; + +#else + struct stat st; + const char *fifo = "event.fifo"; + int socket; + + if (lstat (fifo, &st) == 0) { + if ((st.st_mode & S_IFMT) == S_IFREG) { + errno = EEXIST; + perror("lstat"); + exit (1); + } + } + + unlink (fifo); + if (mkfifo (fifo, 0600) == -1) { + perror("mkfifo"); + exit (1); + } + + /* Linux pipes are broken, we need O_RDWR instead of O_RDONLY */ +#ifdef __linux + socket = open (fifo, O_RDWR | O_NONBLOCK, 0); +#else + socket = open (fifo, O_RDONLY | O_NONBLOCK, 0); +#endif + + if (socket == -1) { + perror("open"); + exit (1); + } + + fprintf(stderr, "Write data to %s\n", fifo); +#endif + /* Initalize the event library */ + event_init(); + + /* Initalize one event */ +#ifdef WIN32 + event_set(&evfifo, (int)socket, EV_READ, fifo_read, &evfifo); +#else + event_set(&evfifo, socket, EV_READ, fifo_read, &evfifo); +#endif + + /* Add it to the active events, without a timeout */ + event_add(&evfifo, NULL); + + event_dispatch(); +#ifdef WIN32 + CloseHandle(socket); +#endif + return (0); +} + diff --git a/third_party/libevent/sample/signal-test.c b/third_party/libevent/sample/signal-test.c new file mode 100644 index 0000000000..9a131cb50c --- /dev/null +++ b/third_party/libevent/sample/signal-test.c @@ -0,0 +1,63 @@ +/* + * Compile with: + * cc -I/usr/local/include -o signal-test \ + * signal-test.c -L/usr/local/lib -levent + */ + +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#ifndef WIN32 +#include +#include +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include + +#include + +int called = 0; + +static void +signal_cb(int fd, short event, void *arg) +{ + struct event *signal = arg; + + printf("%s: got signal %d\n", __func__, EVENT_SIGNAL(signal)); + + if (called >= 2) + event_del(signal); + + called++; +} + +int +main (int argc, char **argv) +{ + struct event signal_int; + + /* Initalize the event library */ + event_init(); + + /* Initalize one event */ + event_set(&signal_int, SIGINT, EV_SIGNAL|EV_PERSIST, signal_cb, + &signal_int); + + event_add(&signal_int, NULL); + + event_dispatch(); + + return (0); +} + diff --git a/third_party/libevent/sample/time-test.c b/third_party/libevent/sample/time-test.c new file mode 100644 index 0000000000..069d4f8f78 --- /dev/null +++ b/third_party/libevent/sample/time-test.c @@ -0,0 +1,70 @@ +/* + * Compile with: + * cc -I/usr/local/include -o time-test time-test.c -L/usr/local/lib -levent + */ + +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#ifndef WIN32 +#include +#include +#endif +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif +#include +#include +#include +#include +#include + +#include +#include + +int lasttime; + +static void +timeout_cb(int fd, short event, void *arg) +{ + struct timeval tv; + struct event *timeout = arg; + int newtime = time(NULL); + + printf("%s: called at %d: %d\n", __func__, newtime, + newtime - lasttime); + lasttime = newtime; + + evutil_timerclear(&tv); + tv.tv_sec = 2; + event_add(timeout, &tv); +} + +int +main (int argc, char **argv) +{ + struct event timeout; + struct timeval tv; + + /* Initalize the event library */ + event_init(); + + /* Initalize one event */ + evtimer_set(&timeout, timeout_cb, &timeout); + + evutil_timerclear(&tv); + tv.tv_sec = 2; + event_add(&timeout, &tv); + + lasttime = time(NULL); + + event_dispatch(); + + return (0); +} + diff --git a/third_party/libevent/select.c b/third_party/libevent/select.c new file mode 100644 index 0000000000..3f73331317 --- /dev/null +++ b/third_party/libevent/select.c @@ -0,0 +1,364 @@ +/* $OpenBSD: select.c,v 1.2 2002/06/25 15:50:15 mickey Exp $ */ + +/* + * Copyright 2000-2002 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#ifdef HAVE_SYS_TIME_H +#include +#else +#include +#endif +#ifdef HAVE_SYS_SELECT_H +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#ifdef CHECK_INVARIANTS +#include +#endif + +#include "event.h" +#include "evutil.h" +#include "event-internal.h" +#include "evsignal.h" +#include "log.h" + +#ifndef howmany +#define howmany(x, y) (((x)+((y)-1))/(y)) +#endif + +#ifndef _EVENT_HAVE_FD_MASK +/* This type is mandatory, but Android doesn't define it. */ +#undef NFDBITS +#define NFDBITS (sizeof(long)*8) +typedef unsigned long fd_mask; +#endif + +struct selectop { + int event_fds; /* Highest fd in fd set */ + int event_fdsz; + fd_set *event_readset_in; + fd_set *event_writeset_in; + fd_set *event_readset_out; + fd_set *event_writeset_out; + struct event **event_r_by_fd; + struct event **event_w_by_fd; +}; + +static void *select_init (struct event_base *); +static int select_add (void *, struct event *); +static int select_del (void *, struct event *); +static int select_dispatch (struct event_base *, void *, struct timeval *); +static void select_dealloc (struct event_base *, void *); + +const struct eventop selectops = { + "select", + select_init, + select_add, + select_del, + select_dispatch, + select_dealloc, + 0 +}; + +static int select_resize(struct selectop *sop, int fdsz); + +static void * +select_init(struct event_base *base) +{ + struct selectop *sop; + + /* Disable select when this environment variable is set */ + if (evutil_getenv("EVENT_NOSELECT")) + return (NULL); + + if (!(sop = calloc(1, sizeof(struct selectop)))) + return (NULL); + + select_resize(sop, howmany(32 + 1, NFDBITS)*sizeof(fd_mask)); + + evsignal_init(base); + + return (sop); +} + +#ifdef CHECK_INVARIANTS +static void +check_selectop(struct selectop *sop) +{ + int i; + for (i = 0; i <= sop->event_fds; ++i) { + if (FD_ISSET(i, sop->event_readset_in)) { + assert(sop->event_r_by_fd[i]); + assert(sop->event_r_by_fd[i]->ev_events & EV_READ); + assert(sop->event_r_by_fd[i]->ev_fd == i); + } else { + assert(! sop->event_r_by_fd[i]); + } + if (FD_ISSET(i, sop->event_writeset_in)) { + assert(sop->event_w_by_fd[i]); + assert(sop->event_w_by_fd[i]->ev_events & EV_WRITE); + assert(sop->event_w_by_fd[i]->ev_fd == i); + } else { + assert(! sop->event_w_by_fd[i]); + } + } + +} +#else +#define check_selectop(sop) do { (void) sop; } while (0) +#endif + +static int +select_dispatch(struct event_base *base, void *arg, struct timeval *tv) +{ + int res, i, j; + struct selectop *sop = arg; + + check_selectop(sop); + + memcpy(sop->event_readset_out, sop->event_readset_in, + sop->event_fdsz); + memcpy(sop->event_writeset_out, sop->event_writeset_in, + sop->event_fdsz); + + res = select(sop->event_fds + 1, sop->event_readset_out, + sop->event_writeset_out, NULL, tv); + + check_selectop(sop); + + if (res == -1) { + if (errno != EINTR) { + event_warn("select"); + return (-1); + } + + evsignal_process(base); + return (0); + } else if (base->sig.evsignal_caught) { + evsignal_process(base); + } + + event_debug(("%s: select reports %d", __func__, res)); + + check_selectop(sop); + i = random() % (sop->event_fds+1); + for (j = 0; j <= sop->event_fds; ++j) { + struct event *r_ev = NULL, *w_ev = NULL; + if (++i >= sop->event_fds+1) + i = 0; + + res = 0; + if (FD_ISSET(i, sop->event_readset_out)) { + r_ev = sop->event_r_by_fd[i]; + res |= EV_READ; + } + if (FD_ISSET(i, sop->event_writeset_out)) { + w_ev = sop->event_w_by_fd[i]; + res |= EV_WRITE; + } + if (r_ev && (res & r_ev->ev_events)) { + event_active(r_ev, res & r_ev->ev_events, 1); + } + if (w_ev && w_ev != r_ev && (res & w_ev->ev_events)) { + event_active(w_ev, res & w_ev->ev_events, 1); + } + } + check_selectop(sop); + + return (0); +} + + +static int +select_resize(struct selectop *sop, int fdsz) +{ + int n_events, n_events_old; + + fd_set *readset_in = NULL; + fd_set *writeset_in = NULL; + fd_set *readset_out = NULL; + fd_set *writeset_out = NULL; + struct event **r_by_fd = NULL; + struct event **w_by_fd = NULL; + + n_events = (fdsz/sizeof(fd_mask)) * NFDBITS; + n_events_old = (sop->event_fdsz/sizeof(fd_mask)) * NFDBITS; + + if (sop->event_readset_in) + check_selectop(sop); + + if ((readset_in = realloc(sop->event_readset_in, fdsz)) == NULL) + goto error; + sop->event_readset_in = readset_in; + if ((readset_out = realloc(sop->event_readset_out, fdsz)) == NULL) + goto error; + sop->event_readset_out = readset_out; + if ((writeset_in = realloc(sop->event_writeset_in, fdsz)) == NULL) + goto error; + sop->event_writeset_in = writeset_in; + if ((writeset_out = realloc(sop->event_writeset_out, fdsz)) == NULL) + goto error; + sop->event_writeset_out = writeset_out; + if ((r_by_fd = realloc(sop->event_r_by_fd, + n_events*sizeof(struct event*))) == NULL) + goto error; + sop->event_r_by_fd = r_by_fd; + if ((w_by_fd = realloc(sop->event_w_by_fd, + n_events * sizeof(struct event*))) == NULL) + goto error; + sop->event_w_by_fd = w_by_fd; + + memset((char *)sop->event_readset_in + sop->event_fdsz, 0, + fdsz - sop->event_fdsz); + memset((char *)sop->event_writeset_in + sop->event_fdsz, 0, + fdsz - sop->event_fdsz); + memset(sop->event_r_by_fd + n_events_old, 0, + (n_events-n_events_old) * sizeof(struct event*)); + memset(sop->event_w_by_fd + n_events_old, 0, + (n_events-n_events_old) * sizeof(struct event*)); + + sop->event_fdsz = fdsz; + check_selectop(sop); + + return (0); + + error: + event_warn("malloc"); + return (-1); +} + + +static int +select_add(void *arg, struct event *ev) +{ + struct selectop *sop = arg; + + if (ev->ev_events & EV_SIGNAL) + return (evsignal_add(ev)); + + check_selectop(sop); + /* + * Keep track of the highest fd, so that we can calculate the size + * of the fd_sets for select(2) + */ + if (sop->event_fds < ev->ev_fd) { + int fdsz = sop->event_fdsz; + + if (fdsz < sizeof(fd_mask)) + fdsz = sizeof(fd_mask); + + while (fdsz < + (howmany(ev->ev_fd + 1, NFDBITS) * sizeof(fd_mask))) + fdsz *= 2; + + if (fdsz != sop->event_fdsz) { + if (select_resize(sop, fdsz)) { + check_selectop(sop); + return (-1); + } + } + + sop->event_fds = ev->ev_fd; + } + + if (ev->ev_events & EV_READ) { + FD_SET(ev->ev_fd, sop->event_readset_in); + sop->event_r_by_fd[ev->ev_fd] = ev; + } + if (ev->ev_events & EV_WRITE) { + FD_SET(ev->ev_fd, sop->event_writeset_in); + sop->event_w_by_fd[ev->ev_fd] = ev; + } + check_selectop(sop); + + return (0); +} + +/* + * Nothing to be done here. + */ + +static int +select_del(void *arg, struct event *ev) +{ + struct selectop *sop = arg; + + check_selectop(sop); + if (ev->ev_events & EV_SIGNAL) + return (evsignal_del(ev)); + + if (sop->event_fds < ev->ev_fd) { + check_selectop(sop); + return (0); + } + + if (ev->ev_events & EV_READ) { + FD_CLR(ev->ev_fd, sop->event_readset_in); + sop->event_r_by_fd[ev->ev_fd] = NULL; + } + + if (ev->ev_events & EV_WRITE) { + FD_CLR(ev->ev_fd, sop->event_writeset_in); + sop->event_w_by_fd[ev->ev_fd] = NULL; + } + + check_selectop(sop); + return (0); +} + +static void +select_dealloc(struct event_base *base, void *arg) +{ + struct selectop *sop = arg; + + evsignal_dealloc(base); + if (sop->event_readset_in) + free(sop->event_readset_in); + if (sop->event_writeset_in) + free(sop->event_writeset_in); + if (sop->event_readset_out) + free(sop->event_readset_out); + if (sop->event_writeset_out) + free(sop->event_writeset_out); + if (sop->event_r_by_fd) + free(sop->event_r_by_fd); + if (sop->event_w_by_fd) + free(sop->event_w_by_fd); + + memset(sop, 0, sizeof(struct selectop)); + free(sop); +} diff --git a/third_party/libevent/signal.c b/third_party/libevent/signal.c new file mode 100644 index 0000000000..74fa23f688 --- /dev/null +++ b/third_party/libevent/signal.c @@ -0,0 +1,357 @@ +/* $OpenBSD: select.c,v 1.2 2002/06/25 15:50:15 mickey Exp $ */ + +/* + * Copyright 2000-2002 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#undef WIN32_LEAN_AND_MEAN +#endif +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif +#include +#ifdef HAVE_SYS_SOCKET_H +#include +#endif +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#ifdef HAVE_FCNTL_H +#include +#endif +#include + +#include "event.h" +#include "event-internal.h" +#include "evsignal.h" +#include "evutil.h" +#include "log.h" + +struct event_base *evsignal_base = NULL; + +static void evsignal_handler(int sig); + +/* Callback for when the signal handler write a byte to our signaling socket */ +static void +evsignal_cb(int fd, short what, void *arg) +{ + static char signals[1]; +#ifdef WIN32 + SSIZE_T n; +#else + ssize_t n; +#endif + + n = recv(fd, signals, sizeof(signals), 0); + if (n == -1) + event_err(1, "%s: read", __func__); +} + +#ifdef HAVE_SETFD +#define FD_CLOSEONEXEC(x) do { \ + if (fcntl(x, F_SETFD, 1) == -1) \ + event_warn("fcntl(%d, F_SETFD)", x); \ +} while (0) +#else +#define FD_CLOSEONEXEC(x) +#endif + +int +evsignal_init(struct event_base *base) +{ + int i; + + /* + * Our signal handler is going to write to one end of the socket + * pair to wake up our event loop. The event loop then scans for + * signals that got delivered. + */ + if (evutil_socketpair( + AF_UNIX, SOCK_STREAM, 0, base->sig.ev_signal_pair) == -1) { +#ifdef WIN32 + /* Make this nonfatal on win32, where sometimes people + have localhost firewalled. */ + event_warn("%s: socketpair", __func__); +#else + event_err(1, "%s: socketpair", __func__); +#endif + return -1; + } + + FD_CLOSEONEXEC(base->sig.ev_signal_pair[0]); + FD_CLOSEONEXEC(base->sig.ev_signal_pair[1]); + base->sig.sh_old = NULL; + base->sig.sh_old_max = 0; + base->sig.evsignal_caught = 0; + memset(&base->sig.evsigcaught, 0, sizeof(sig_atomic_t)*NSIG); + /* initialize the queues for all events */ + for (i = 0; i < NSIG; ++i) + TAILQ_INIT(&base->sig.evsigevents[i]); + + evutil_make_socket_nonblocking(base->sig.ev_signal_pair[0]); + + event_set(&base->sig.ev_signal, base->sig.ev_signal_pair[1], + EV_READ | EV_PERSIST, evsignal_cb, &base->sig.ev_signal); + base->sig.ev_signal.ev_base = base; + base->sig.ev_signal.ev_flags |= EVLIST_INTERNAL; + + return 0; +} + +/* Helper: set the signal handler for evsignal to handler in base, so that + * we can restore the original handler when we clear the current one. */ +int +_evsignal_set_handler(struct event_base *base, + int evsignal, void (*handler)(int)) +{ +#ifdef HAVE_SIGACTION + struct sigaction sa; +#else + ev_sighandler_t sh; +#endif + struct evsignal_info *sig = &base->sig; + void *p; + + /* + * resize saved signal handler array up to the highest signal number. + * a dynamic array is used to keep footprint on the low side. + */ + if (evsignal >= sig->sh_old_max) { + int new_max = evsignal + 1; + event_debug(("%s: evsignal (%d) >= sh_old_max (%d), resizing", + __func__, evsignal, sig->sh_old_max)); + p = realloc(sig->sh_old, new_max * sizeof(*sig->sh_old)); + if (p == NULL) { + event_warn("realloc"); + return (-1); + } + + memset((char *)p + sig->sh_old_max * sizeof(*sig->sh_old), + 0, (new_max - sig->sh_old_max) * sizeof(*sig->sh_old)); + + sig->sh_old_max = new_max; + sig->sh_old = p; + } + + /* allocate space for previous handler out of dynamic array */ + sig->sh_old[evsignal] = malloc(sizeof *sig->sh_old[evsignal]); + if (sig->sh_old[evsignal] == NULL) { + event_warn("malloc"); + return (-1); + } + + /* save previous handler and setup new handler */ +#ifdef HAVE_SIGACTION + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = handler; + sa.sa_flags |= SA_RESTART; + sigfillset(&sa.sa_mask); + + if (sigaction(evsignal, &sa, sig->sh_old[evsignal]) == -1) { + event_warn("sigaction"); + free(sig->sh_old[evsignal]); + return (-1); + } +#else + if ((sh = signal(evsignal, handler)) == SIG_ERR) { + event_warn("signal"); + free(sig->sh_old[evsignal]); + return (-1); + } + *sig->sh_old[evsignal] = sh; +#endif + + return (0); +} + +int +evsignal_add(struct event *ev) +{ + int evsignal; + struct event_base *base = ev->ev_base; + struct evsignal_info *sig = &ev->ev_base->sig; + + if (ev->ev_events & (EV_READ|EV_WRITE)) + event_errx(1, "%s: EV_SIGNAL incompatible use", __func__); + evsignal = EVENT_SIGNAL(ev); + assert(evsignal >= 0 && evsignal < NSIG); + if (TAILQ_EMPTY(&sig->evsigevents[evsignal])) { + event_debug(("%s: %p: changing signal handler", __func__, ev)); + if (_evsignal_set_handler( + base, evsignal, evsignal_handler) == -1) + return (-1); + + /* catch signals if they happen quickly */ + evsignal_base = base; + + if (!sig->ev_signal_added) { + if (event_add(&sig->ev_signal, NULL)) + return (-1); + sig->ev_signal_added = 1; + } + } + + /* multiple events may listen to the same signal */ + TAILQ_INSERT_TAIL(&sig->evsigevents[evsignal], ev, ev_signal_next); + + return (0); +} + +int +_evsignal_restore_handler(struct event_base *base, int evsignal) +{ + int ret = 0; + struct evsignal_info *sig = &base->sig; +#ifdef HAVE_SIGACTION + struct sigaction *sh; +#else + ev_sighandler_t *sh; +#endif + + /* restore previous handler */ + sh = sig->sh_old[evsignal]; + sig->sh_old[evsignal] = NULL; +#ifdef HAVE_SIGACTION + if (sigaction(evsignal, sh, NULL) == -1) { + event_warn("sigaction"); + ret = -1; + } +#else + if (signal(evsignal, *sh) == SIG_ERR) { + event_warn("signal"); + ret = -1; + } +#endif + free(sh); + + return ret; +} + +int +evsignal_del(struct event *ev) +{ + struct event_base *base = ev->ev_base; + struct evsignal_info *sig = &base->sig; + int evsignal = EVENT_SIGNAL(ev); + + assert(evsignal >= 0 && evsignal < NSIG); + + /* multiple events may listen to the same signal */ + TAILQ_REMOVE(&sig->evsigevents[evsignal], ev, ev_signal_next); + + if (!TAILQ_EMPTY(&sig->evsigevents[evsignal])) + return (0); + + event_debug(("%s: %p: restoring signal handler", __func__, ev)); + + return (_evsignal_restore_handler(ev->ev_base, EVENT_SIGNAL(ev))); +} + +static void +evsignal_handler(int sig) +{ + int save_errno = errno; + + if (evsignal_base == NULL) { + event_warn( + "%s: received signal %d, but have no base configured", + __func__, sig); + return; + } + + evsignal_base->sig.evsigcaught[sig]++; + evsignal_base->sig.evsignal_caught = 1; + +#ifndef HAVE_SIGACTION + signal(sig, evsignal_handler); +#endif + + /* Wake up our notification mechanism */ + send(evsignal_base->sig.ev_signal_pair[0], "a", 1, 0); + errno = save_errno; +} + +void +evsignal_process(struct event_base *base) +{ + struct evsignal_info *sig = &base->sig; + struct event *ev, *next_ev; + sig_atomic_t ncalls; + int i; + + base->sig.evsignal_caught = 0; + for (i = 1; i < NSIG; ++i) { + ncalls = sig->evsigcaught[i]; + if (ncalls == 0) + continue; + sig->evsigcaught[i] -= ncalls; + + for (ev = TAILQ_FIRST(&sig->evsigevents[i]); + ev != NULL; ev = next_ev) { + next_ev = TAILQ_NEXT(ev, ev_signal_next); + if (!(ev->ev_events & EV_PERSIST)) + event_del(ev); + event_active(ev, EV_SIGNAL, ncalls); + } + + } +} + +void +evsignal_dealloc(struct event_base *base) +{ + int i = 0; + if (base->sig.ev_signal_added) { + event_del(&base->sig.ev_signal); + base->sig.ev_signal_added = 0; + } + for (i = 0; i < NSIG; ++i) { + if (i < base->sig.sh_old_max && base->sig.sh_old[i] != NULL) + _evsignal_restore_handler(base, i); + } + + EVUTIL_CLOSESOCKET(base->sig.ev_signal_pair[0]); + base->sig.ev_signal_pair[0] = -1; + EVUTIL_CLOSESOCKET(base->sig.ev_signal_pair[1]); + base->sig.ev_signal_pair[1] = -1; + base->sig.sh_old_max = 0; + + /* per index frees are handled in evsignal_del() */ + free(base->sig.sh_old); +} diff --git a/third_party/libevent/solaris/config.h b/third_party/libevent/solaris/config.h new file mode 100644 index 0000000000..4dd40eb36e --- /dev/null +++ b/third_party/libevent/solaris/config.h @@ -0,0 +1,266 @@ +/* config.h. Generated from config.h.in by configure. */ +/* config.h.in. Generated from configure.in by autoheader. */ + +/* Define if clock_gettime is available in libc */ +#define DNS_USE_CPU_CLOCK_FOR_ID 1 + +/* Define is no secure id variant is available */ +/* #undef DNS_USE_GETTIMEOFDAY_FOR_ID */ + +/* Define to 1 if you have the `clock_gettime' function. */ +#define HAVE_CLOCK_GETTIME 1 + +/* Define if /dev/poll is available */ +#define HAVE_DEVPOLL 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_DLFCN_H 1 + +/* Define if your system supports the epoll system calls */ +/* #undef HAVE_EPOLL */ + +/* Define to 1 if you have the `epoll_ctl' function. */ +/* #undef HAVE_EPOLL_CTL */ + +/* Define if your system supports event ports */ +#define HAVE_EVENT_PORTS 1 + +/* Define to 1 if you have the `fcntl' function. */ +#define HAVE_FCNTL 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_FCNTL_H 1 + +/* Define to 1 if the system has the type `fd_mask'. */ +#define HAVE_FD_MASK 1 + +/* Define to 1 if you have the `getaddrinfo' function. */ +#define HAVE_GETADDRINFO 1 + +/* Define to 1 if you have the `getegid' function. */ +#define HAVE_GETEGID 1 + +/* Define to 1 if you have the `geteuid' function. */ +#define HAVE_GETEUID 1 + +/* Define to 1 if you have the `getnameinfo' function. */ +#define HAVE_GETNAMEINFO 1 + +/* Define to 1 if you have the `gettimeofday' function. */ +#define HAVE_GETTIMEOFDAY 1 + +/* Define to 1 if you have the `inet_ntop' function. */ +#define HAVE_INET_NTOP 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the `issetugid' function. */ +#define HAVE_ISSETUGID 1 + +/* Define to 1 if you have the `kqueue' function. */ +/* #undef HAVE_KQUEUE */ + +/* Define to 1 if you have the `nsl' library (-lnsl). */ +#define HAVE_LIBNSL 1 + +/* Define to 1 if you have the `resolv' library (-lresolv). */ +#define HAVE_LIBRESOLV 1 + +/* Define to 1 if you have the `rt' library (-lrt). */ +#define HAVE_LIBRT 1 + +/* Define to 1 if you have the `socket' library (-lsocket). */ +#define HAVE_LIBSOCKET 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_NETINET_IN6_H */ + +/* Define to 1 if you have the `poll' function. */ +#define HAVE_POLL 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_POLL_H 1 + +/* Define to 1 if you have the `port_create' function. */ +#define HAVE_PORT_CREATE 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_PORT_H 1 + +/* Define to 1 if you have the `select' function. */ +#define HAVE_SELECT 1 + +/* Define if F_SETFD is defined in */ +#define HAVE_SETFD 1 + +/* Define to 1 if you have the `sigaction' function. */ +#define HAVE_SIGACTION 1 + +/* Define to 1 if you have the `signal' function. */ +#define HAVE_SIGNAL 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SIGNAL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDARG_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the `strlcpy' function. */ +#define HAVE_STRLCPY 1 + +/* Define to 1 if you have the `strsep' function. */ +#define HAVE_STRSEP 1 + +/* Define to 1 if you have the `strtok_r' function. */ +#define HAVE_STRTOK_R 1 + +/* Define to 1 if you have the `strtoll' function. */ +#define HAVE_STRTOLL 1 + +/* Define to 1 if the system has the type `struct in6_addr'. */ +#define HAVE_STRUCT_IN6_ADDR 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_DEVPOLL_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_EPOLL_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_EVENT_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_IOCTL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_PARAM_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_QUEUE_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SELECT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SOCKET_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define if TAILQ_FOREACH is defined in */ +#define HAVE_TAILQFOREACH 1 + +/* Define if timeradd is defined in */ +#define HAVE_TIMERADD 1 + +/* Define if timerclear is defined in */ +#define HAVE_TIMERCLEAR 1 + +/* Define if timercmp is defined in */ +#define HAVE_TIMERCMP 1 + +/* Define if timerisset is defined in */ +#define HAVE_TIMERISSET 1 + +/* Define to 1 if the system has the type `uint16_t'. */ +#define HAVE_UINT16_T 1 + +/* Define to 1 if the system has the type `uint32_t'. */ +#define HAVE_UINT32_T 1 + +/* Define to 1 if the system has the type `uint64_t'. */ +#define HAVE_UINT64_T 1 + +/* Define to 1 if the system has the type `uint8_t'. */ +#define HAVE_UINT8_T 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to 1 if you have the `vasprintf' function. */ +#define HAVE_VASPRINTF 1 + +/* Define if kqueue works correctly with pipes */ +/* #undef HAVE_WORKING_KQUEUE */ + +/* Name of package */ +#define PACKAGE "libevent" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "" + +/* The size of `int', as computed by sizeof. */ +#define SIZEOF_INT 4 + +/* The size of `long', as computed by sizeof. */ +#define SIZEOF_LONG 4 + +/* The size of `long long', as computed by sizeof. */ +#define SIZEOF_LONG_LONG 8 + +/* The size of `short', as computed by sizeof. */ +#define SIZEOF_SHORT 2 + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Define to 1 if you can safely include both and . */ +#define TIME_WITH_SYS_TIME 1 + +/* Version number of package */ +#define VERSION "1.4.13-stable" + +/* Define to appropriate substitue if compiler doesnt have __func__ */ +/* #undef __func__ */ + +/* Define to empty if `const' does not conform to ANSI C. */ +/* #undef const */ + +/* Define to `__inline__' or `__inline' if that's what the C compiler + calls it, or to nothing if 'inline' is not supported under any name. */ +#ifndef __cplusplus +/* #undef inline */ +#endif + +/* Define to `int' if does not define. */ +/* #undef pid_t */ + +/* Define to `unsigned int' if does not define. */ +/* #undef size_t */ + +/* Define to unsigned int if you dont have it */ +/* #undef socklen_t */ diff --git a/third_party/libevent/solaris/event-config.h b/third_party/libevent/solaris/event-config.h new file mode 100644 index 0000000000..c5fe16082a --- /dev/null +++ b/third_party/libevent/solaris/event-config.h @@ -0,0 +1,274 @@ +/* event-config.h + * Generated by autoconf; post-processed by libevent. + * Do not edit this file. + * Do not rely on macros in this file existing in later versions. + */ +#ifndef _EVENT_CONFIG_H_ +#define _EVENT_CONFIG_H_ +/* config.h. Generated from config.h.in by configure. */ +/* config.h.in. Generated from configure.in by autoheader. */ + +/* Define if clock_gettime is available in libc */ +#define _EVENT_DNS_USE_CPU_CLOCK_FOR_ID 1 + +/* Define is no secure id variant is available */ +/* #undef _EVENT_DNS_USE_GETTIMEOFDAY_FOR_ID */ + +/* Define to 1 if you have the `clock_gettime' function. */ +#define _EVENT_HAVE_CLOCK_GETTIME 1 + +/* Define if /dev/poll is available */ +#define _EVENT_HAVE_DEVPOLL 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_DLFCN_H 1 + +/* Define if your system supports the epoll system calls */ +/* #undef _EVENT_HAVE_EPOLL */ + +/* Define to 1 if you have the `epoll_ctl' function. */ +/* #undef _EVENT_HAVE_EPOLL_CTL */ + +/* Define if your system supports event ports */ +#define _EVENT_HAVE_EVENT_PORTS 1 + +/* Define to 1 if you have the `fcntl' function. */ +#define _EVENT_HAVE_FCNTL 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_FCNTL_H 1 + +/* Define to 1 if the system has the type `fd_mask'. */ +#define _EVENT_HAVE_FD_MASK 1 + +/* Define to 1 if you have the `getaddrinfo' function. */ +#define _EVENT_HAVE_GETADDRINFO 1 + +/* Define to 1 if you have the `getegid' function. */ +#define _EVENT_HAVE_GETEGID 1 + +/* Define to 1 if you have the `geteuid' function. */ +#define _EVENT_HAVE_GETEUID 1 + +/* Define to 1 if you have the `getnameinfo' function. */ +#define _EVENT_HAVE_GETNAMEINFO 1 + +/* Define to 1 if you have the `gettimeofday' function. */ +#define _EVENT_HAVE_GETTIMEOFDAY 1 + +/* Define to 1 if you have the `inet_ntop' function. */ +#define _EVENT_HAVE_INET_NTOP 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the `issetugid' function. */ +#define _EVENT_HAVE_ISSETUGID 1 + +/* Define to 1 if you have the `kqueue' function. */ +/* #undef _EVENT_HAVE_KQUEUE */ + +/* Define to 1 if you have the `nsl' library (-lnsl). */ +#define _EVENT_HAVE_LIBNSL 1 + +/* Define to 1 if you have the `resolv' library (-lresolv). */ +#define _EVENT_HAVE_LIBRESOLV 1 + +/* Define to 1 if you have the `rt' library (-lrt). */ +#define _EVENT_HAVE_LIBRT 1 + +/* Define to 1 if you have the `socket' library (-lsocket). */ +#define _EVENT_HAVE_LIBSOCKET 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_MEMORY_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef _EVENT_HAVE_NETINET_IN6_H */ + +/* Define to 1 if you have the `poll' function. */ +#define _EVENT_HAVE_POLL 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_POLL_H 1 + +/* Define to 1 if you have the `port_create' function. */ +#define _EVENT_HAVE_PORT_CREATE 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_PORT_H 1 + +/* Define to 1 if you have the `select' function. */ +#define _EVENT_HAVE_SELECT 1 + +/* Define if F_SETFD is defined in */ +#define _EVENT_HAVE_SETFD 1 + +/* Define to 1 if you have the `sigaction' function. */ +#define _EVENT_HAVE_SIGACTION 1 + +/* Define to 1 if you have the `signal' function. */ +#define _EVENT_HAVE_SIGNAL 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SIGNAL_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_STDARG_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_STRING_H 1 + +/* Define to 1 if you have the `strlcpy' function. */ +#define _EVENT_HAVE_STRLCPY 1 + +/* Define to 1 if you have the `strsep' function. */ +#define _EVENT_HAVE_STRSEP 1 + +/* Define to 1 if you have the `strtok_r' function. */ +#define _EVENT_HAVE_STRTOK_R 1 + +/* Define to 1 if you have the `strtoll' function. */ +#define _EVENT_HAVE_STRTOLL 1 + +/* Define to 1 if the system has the type `struct in6_addr'. */ +#define _EVENT_HAVE_STRUCT_IN6_ADDR 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_DEVPOLL_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef _EVENT_HAVE_SYS_EPOLL_H */ + +/* Define to 1 if you have the header file. */ +/* #undef _EVENT_HAVE_SYS_EVENT_H */ + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_IOCTL_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_PARAM_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_QUEUE_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_SELECT_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_SOCKET_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_TYPES_H 1 + +/* Define if TAILQ_FOREACH is defined in */ +#define _EVENT_HAVE_TAILQFOREACH 1 + +/* Define if timeradd is defined in */ +#define _EVENT_HAVE_TIMERADD 1 + +/* Define if timerclear is defined in */ +#define _EVENT_HAVE_TIMERCLEAR 1 + +/* Define if timercmp is defined in */ +#define _EVENT_HAVE_TIMERCMP 1 + +/* Define if timerisset is defined in */ +#define _EVENT_HAVE_TIMERISSET 1 + +/* Define to 1 if the system has the type `uint16_t'. */ +#define _EVENT_HAVE_UINT16_T 1 + +/* Define to 1 if the system has the type `uint32_t'. */ +#define _EVENT_HAVE_UINT32_T 1 + +/* Define to 1 if the system has the type `uint64_t'. */ +#define _EVENT_HAVE_UINT64_T 1 + +/* Define to 1 if the system has the type `uint8_t'. */ +#define _EVENT_HAVE_UINT8_T 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_UNISTD_H 1 + +/* Define to 1 if you have the `vasprintf' function. */ +#define _EVENT_HAVE_VASPRINTF 1 + +/* Define if kqueue works correctly with pipes */ +/* #undef _EVENT_HAVE_WORKING_KQUEUE */ + +/* Name of package */ +#define _EVENT_PACKAGE "libevent" + +/* Define to the address where bug reports for this package should be sent. */ +#define _EVENT_PACKAGE_BUGREPORT "" + +/* Define to the full name of this package. */ +#define _EVENT_PACKAGE_NAME "" + +/* Define to the full name and version of this package. */ +#define _EVENT_PACKAGE_STRING "" + +/* Define to the one symbol short name of this package. */ +#define _EVENT_PACKAGE_TARNAME "" + +/* Define to the version of this package. */ +#define _EVENT_PACKAGE_VERSION "" + +/* The size of `int', as computed by sizeof. */ +#define _EVENT_SIZEOF_INT 4 + +/* The size of `long', as computed by sizeof. */ +#define _EVENT_SIZEOF_LONG 4 + +/* The size of `long long', as computed by sizeof. */ +#define _EVENT_SIZEOF_LONG_LONG 8 + +/* The size of `short', as computed by sizeof. */ +#define _EVENT_SIZEOF_SHORT 2 + +/* Define to 1 if you have the ANSI C header files. */ +#define _EVENT_STDC_HEADERS 1 + +/* Define to 1 if you can safely include both and . */ +#define _EVENT_TIME_WITH_SYS_TIME 1 + +/* Version number of package */ +#define _EVENT_VERSION "1.4.13-stable" + +/* Define to appropriate substitue if compiler doesnt have __func__ */ +/* #undef _EVENT___func__ */ + +/* Define to empty if `const' does not conform to ANSI C. */ +/* #undef _EVENT_const */ + +/* Define to `__inline__' or `__inline' if that's what the C compiler + calls it, or to nothing if 'inline' is not supported under any name. */ +#ifndef _EVENT___cplusplus +/* #undef _EVENT_inline */ +#endif + +/* Define to `int' if does not define. */ +/* #undef _EVENT_pid_t */ + +/* Define to `unsigned int' if does not define. */ +/* #undef _EVENT_size_t */ + +/* Define to unsigned int if you dont have it */ +/* #undef _EVENT_socklen_t */ +#endif diff --git a/third_party/libevent/strlcpy-internal.h b/third_party/libevent/strlcpy-internal.h new file mode 100644 index 0000000000..22b5f61d45 --- /dev/null +++ b/third_party/libevent/strlcpy-internal.h @@ -0,0 +1,23 @@ +#ifndef _STRLCPY_INTERNAL_H_ +#define _STRLCPY_INTERNAL_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#ifndef HAVE_STRLCPY +#include +size_t _event_strlcpy(char *dst, const char *src, size_t siz); +#define strlcpy _event_strlcpy +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/third_party/libevent/strlcpy.c b/third_party/libevent/strlcpy.c new file mode 100644 index 0000000000..5d194527c8 --- /dev/null +++ b/third_party/libevent/strlcpy.c @@ -0,0 +1,76 @@ +/* $OpenBSD: strlcpy.c,v 1.5 2001/05/13 15:40:16 deraadt Exp $ */ + +/* + * Copyright (c) 1998 Todd C. Miller + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if defined(LIBC_SCCS) && !defined(lint) +static char *rcsid = "$OpenBSD: strlcpy.c,v 1.5 2001/05/13 15:40:16 deraadt Exp $"; +#endif /* LIBC_SCCS and not lint */ + +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#ifndef HAVE_STRLCPY +#include "strlcpy-internal.h" + +/* + * Copy src to string dst of size siz. At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + */ +size_t +_event_strlcpy(dst, src, siz) + char *dst; + const char *src; + size_t siz; +{ + register char *d = dst; + register const char *s = src; + register size_t n = siz; + + /* Copy as many bytes as will fit */ + if (n != 0 && --n != 0) { + do { + if ((*d++ = *s++) == 0) + break; + } while (--n != 0); + } + + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++) + ; + } + + return(s - src - 1); /* count does not include NUL */ +} +#endif diff --git a/third_party/libevent/test/Makefile.am b/third_party/libevent/test/Makefile.am new file mode 100644 index 0000000000..3558d02fd5 --- /dev/null +++ b/third_party/libevent/test/Makefile.am @@ -0,0 +1,35 @@ +AUTOMAKE_OPTIONS = foreign no-dependencies + +AM_CFLAGS = -I$(top_srcdir) -I$(top_srcdir)/compat + +EXTRA_DIST = regress.rpc regress.gen.h regress.gen.c + +noinst_PROGRAMS = test-init test-eof test-weof test-time regress bench + +BUILT_SOURCES = regress.gen.c regress.gen.h +test_init_SOURCES = test-init.c +test_init_LDADD = ../libevent_core.la +test_eof_SOURCES = test-eof.c +test_eof_LDADD = ../libevent_core.la +test_weof_SOURCES = test-weof.c +test_weof_LDADD = ../libevent_core.la +test_time_SOURCES = test-time.c +test_time_LDADD = ../libevent_core.la +regress_SOURCES = regress.c regress.h regress_http.c regress_dns.c \ + regress_rpc.c \ + regress.gen.c regress.gen.h +regress_LDADD = ../libevent.la +bench_SOURCES = bench.c +bench_LDADD = ../libevent.la + +regress.gen.c regress.gen.h: regress.rpc $(top_srcdir)/event_rpcgen.py + $(top_srcdir)/event_rpcgen.py $(srcdir)/regress.rpc || echo "No Python installed" + +DISTCLEANFILES = *~ + +test: test-init test-eof test-weof test-time regress + +verify: test + @$(srcdir)/test.sh + +bench test-init test-eof test-weof test-time: ../libevent.la diff --git a/third_party/libevent/test/Makefile.in b/third_party/libevent/test/Makefile.in new file mode 100644 index 0000000000..c2d5b31ad4 --- /dev/null +++ b/third_party/libevent/test/Makefile.in @@ -0,0 +1,487 @@ +# Makefile.in generated by automake 1.10.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +noinst_PROGRAMS = test-init$(EXEEXT) test-eof$(EXEEXT) \ + test-weof$(EXEEXT) test-time$(EXEEXT) regress$(EXEEXT) \ + bench$(EXEEXT) +subdir = test +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.in +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +PROGRAMS = $(noinst_PROGRAMS) +am_bench_OBJECTS = bench.$(OBJEXT) +bench_OBJECTS = $(am_bench_OBJECTS) +bench_DEPENDENCIES = ../libevent.la +am_regress_OBJECTS = regress.$(OBJEXT) regress_http.$(OBJEXT) \ + regress_dns.$(OBJEXT) regress_rpc.$(OBJEXT) \ + regress.gen.$(OBJEXT) +regress_OBJECTS = $(am_regress_OBJECTS) +regress_DEPENDENCIES = ../libevent.la +am_test_eof_OBJECTS = test-eof.$(OBJEXT) +test_eof_OBJECTS = $(am_test_eof_OBJECTS) +test_eof_DEPENDENCIES = ../libevent_core.la +am_test_init_OBJECTS = test-init.$(OBJEXT) +test_init_OBJECTS = $(am_test_init_OBJECTS) +test_init_DEPENDENCIES = ../libevent_core.la +am_test_time_OBJECTS = test-time.$(OBJEXT) +test_time_OBJECTS = $(am_test_time_OBJECTS) +test_time_DEPENDENCIES = ../libevent_core.la +am_test_weof_OBJECTS = test-weof.$(OBJEXT) +test_weof_OBJECTS = $(am_test_weof_OBJECTS) +test_weof_DEPENDENCIES = ../libevent_core.la +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = +am__depfiles_maybe = +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ + --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +CCLD = $(CC) +LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ + --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ + $(LDFLAGS) -o $@ +SOURCES = $(bench_SOURCES) $(regress_SOURCES) $(test_eof_SOURCES) \ + $(test_init_SOURCES) $(test_time_SOURCES) $(test_weof_SOURCES) +DIST_SOURCES = $(bench_SOURCES) $(regress_SOURCES) $(test_eof_SOURCES) \ + $(test_init_SOURCES) $(test_time_SOURCES) $(test_weof_SOURCES) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DSYMUTIL = @DSYMUTIL@ +ECHO = @ECHO@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +F77 = @F77@ +FFLAGS = @FFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIBTOOL_DEPS = @LIBTOOL_DEPS@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +NMEDIT = @NMEDIT@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_F77 = @ac_ct_F77@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AUTOMAKE_OPTIONS = foreign no-dependencies +AM_CFLAGS = -I$(top_srcdir) -I$(top_srcdir)/compat +EXTRA_DIST = regress.rpc regress.gen.h regress.gen.c +BUILT_SOURCES = regress.gen.c regress.gen.h +test_init_SOURCES = test-init.c +test_init_LDADD = ../libevent_core.la +test_eof_SOURCES = test-eof.c +test_eof_LDADD = ../libevent_core.la +test_weof_SOURCES = test-weof.c +test_weof_LDADD = ../libevent_core.la +test_time_SOURCES = test-time.c +test_time_LDADD = ../libevent_core.la +regress_SOURCES = regress.c regress.h regress_http.c regress_dns.c \ + regress_rpc.c \ + regress.gen.c regress.gen.h + +regress_LDADD = ../libevent.la +bench_SOURCES = bench.c +bench_LDADD = ../libevent.la +DISTCLEANFILES = *~ +all: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh \ + && exit 0; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign test/Makefile'; \ + cd $(top_srcdir) && \ + $(AUTOMAKE) --foreign test/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +clean-noinstPROGRAMS: + @list='$(noinst_PROGRAMS)'; for p in $$list; do \ + f=`echo $$p|sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f $$p $$f"; \ + rm -f $$p $$f ; \ + done +bench$(EXEEXT): $(bench_OBJECTS) $(bench_DEPENDENCIES) + @rm -f bench$(EXEEXT) + $(LINK) $(bench_OBJECTS) $(bench_LDADD) $(LIBS) +regress$(EXEEXT): $(regress_OBJECTS) $(regress_DEPENDENCIES) + @rm -f regress$(EXEEXT) + $(LINK) $(regress_OBJECTS) $(regress_LDADD) $(LIBS) +test-eof$(EXEEXT): $(test_eof_OBJECTS) $(test_eof_DEPENDENCIES) + @rm -f test-eof$(EXEEXT) + $(LINK) $(test_eof_OBJECTS) $(test_eof_LDADD) $(LIBS) +test-init$(EXEEXT): $(test_init_OBJECTS) $(test_init_DEPENDENCIES) + @rm -f test-init$(EXEEXT) + $(LINK) $(test_init_OBJECTS) $(test_init_LDADD) $(LIBS) +test-time$(EXEEXT): $(test_time_OBJECTS) $(test_time_DEPENDENCIES) + @rm -f test-time$(EXEEXT) + $(LINK) $(test_time_OBJECTS) $(test_time_LDADD) $(LIBS) +test-weof$(EXEEXT): $(test_weof_OBJECTS) $(test_weof_DEPENDENCIES) + @rm -f test-weof$(EXEEXT) + $(LINK) $(test_weof_OBJECTS) $(test_weof_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +.c.o: + $(COMPILE) -c $< + +.c.obj: + $(COMPILE) -c `$(CYGPATH_W) '$<'` + +.c.lo: + $(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonemtpy = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$tags $$unique; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$tags$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$tags $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && cd $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) $$here + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \ + fi; \ + cp -pR $$d/$$file $(distdir)$$dir || exit 1; \ + else \ + test -f $(distdir)/$$file \ + || cp -p $$d/$$file $(distdir)/$$file \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) check-am +all-am: Makefile $(PROGRAMS) +installdirs: +install: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." + -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES) +clean: clean-am + +clean-am: clean-generic clean-libtool clean-noinstPROGRAMS \ + mostlyclean-am + +distclean: distclean-am + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-exec-am: + +install-html: install-html-am + +install-info: install-info-am + +install-man: + +install-pdf: install-pdf-am + +install-ps: install-ps-am + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \ + clean-libtool clean-noinstPROGRAMS ctags distclean \ + distclean-compile distclean-generic distclean-libtool \ + distclean-tags distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ + pdf pdf-am ps ps-am tags uninstall uninstall-am + + +regress.gen.c regress.gen.h: regress.rpc $(top_srcdir)/event_rpcgen.py + $(top_srcdir)/event_rpcgen.py $(srcdir)/regress.rpc || echo "No Python installed" + +test: test-init test-eof test-weof test-time regress + +verify: test + @$(srcdir)/test.sh + +bench test-init test-eof test-weof test-time: ../libevent.la +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/third_party/libevent/test/bench.c b/third_party/libevent/test/bench.c new file mode 100644 index 0000000000..c976932fa8 --- /dev/null +++ b/third_party/libevent/test/bench.c @@ -0,0 +1,188 @@ +/* + * Copyright 2003 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * + * Mon 03/10/2003 - Modified by Davide Libenzi + * + * Added chain event propagation to improve the sensitivity of + * the measure respect to the event loop efficency. + * + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#ifdef WIN32 +#include +#else +#include +#include +#include +#endif +#include +#include +#include +#include +#include +#include + +#include +#include + + +static int count, writes, fired; +static int *pipes; +static int num_pipes, num_active, num_writes; +static struct event *events; + +static void +read_cb(int fd, short which, void *arg) +{ + long idx = (long) arg, widx = idx + 1; + u_char ch; + + count += read(fd, &ch, sizeof(ch)); + if (writes) { + if (widx >= num_pipes) + widx -= num_pipes; + write(pipes[2 * widx + 1], "e", 1); + writes--; + fired++; + } +} + +static struct timeval * +run_once(void) +{ + int *cp, space; + long i; + static struct timeval ts, te; + + for (cp = pipes, i = 0; i < num_pipes; i++, cp += 2) { + event_del(&events[i]); + event_set(&events[i], cp[0], EV_READ | EV_PERSIST, read_cb, (void *) i); + event_add(&events[i], NULL); + } + + event_loop(EVLOOP_ONCE | EVLOOP_NONBLOCK); + + fired = 0; + space = num_pipes / num_active; + space = space * 2; + for (i = 0; i < num_active; i++, fired++) + write(pipes[i * space + 1], "e", 1); + + count = 0; + writes = num_writes; + { int xcount = 0; + gettimeofday(&ts, NULL); + do { + event_loop(EVLOOP_ONCE | EVLOOP_NONBLOCK); + xcount++; + } while (count != fired); + gettimeofday(&te, NULL); + + if (xcount != count) fprintf(stderr, "Xcount: %d, Rcount: %d\n", xcount, count); + } + + evutil_timersub(&te, &ts, &te); + + return (&te); +} + +int +main (int argc, char **argv) +{ +#ifndef WIN32 + struct rlimit rl; +#endif + int i, c; + struct timeval *tv; + int *cp; + + num_pipes = 100; + num_active = 1; + num_writes = num_pipes; + while ((c = getopt(argc, argv, "n:a:w:")) != -1) { + switch (c) { + case 'n': + num_pipes = atoi(optarg); + break; + case 'a': + num_active = atoi(optarg); + break; + case 'w': + num_writes = atoi(optarg); + break; + default: + fprintf(stderr, "Illegal argument \"%c\"\n", c); + exit(1); + } + } + +#ifndef WIN32 + rl.rlim_cur = rl.rlim_max = num_pipes * 2 + 50; + if (setrlimit(RLIMIT_NOFILE, &rl) == -1) { + perror("setrlimit"); + exit(1); + } +#endif + + events = calloc(num_pipes, sizeof(struct event)); + pipes = calloc(num_pipes * 2, sizeof(int)); + if (events == NULL || pipes == NULL) { + perror("malloc"); + exit(1); + } + + event_init(); + + for (cp = pipes, i = 0; i < num_pipes; i++, cp += 2) { +#ifdef USE_PIPES + if (pipe(cp) == -1) { +#else + if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, cp) == -1) { +#endif + perror("pipe"); + exit(1); + } + } + + for (i = 0; i < 25; i++) { + tv = run_once(); + if (tv == NULL) + exit(1); + fprintf(stdout, "%ld\n", + tv->tv_sec * 1000000L + tv->tv_usec); + } + + exit(0); +} diff --git a/third_party/libevent/test/regress.c b/third_party/libevent/test/regress.c new file mode 100644 index 0000000000..0b7517d3aa --- /dev/null +++ b/third_party/libevent/test/regress.c @@ -0,0 +1,1703 @@ +/* + * Copyright (c) 2003, 2004 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef WIN32 +#include +#include +#endif + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif +#include +#ifndef WIN32 +#include +#include +#include +#include +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +#include "event.h" +#include "evutil.h" +#include "event-internal.h" +#include "log.h" + +#include "regress.h" +#ifndef WIN32 +#include "regress.gen.h" +#endif + +int pair[2]; +int test_ok; +static int called; +static char wbuf[4096]; +static char rbuf[4096]; +static int woff; +static int roff; +static int usepersist; +static struct timeval tset; +static struct timeval tcalled; +static struct event_base *global_base; + +#define TEST1 "this is a test" +#define SECONDS 1 + +#ifndef SHUT_WR +#define SHUT_WR 1 +#endif + +#ifdef WIN32 +#define write(fd,buf,len) send((fd),(buf),(len),0) +#define read(fd,buf,len) recv((fd),(buf),(len),0) +#endif + +static void +simple_read_cb(int fd, short event, void *arg) +{ + char buf[256]; + int len; + + if (arg == NULL) + return; + + len = read(fd, buf, sizeof(buf)); + + if (len) { + if (!called) { + if (event_add(arg, NULL) == -1) + exit(1); + } + } else if (called == 1) + test_ok = 1; + + called++; +} + +static void +simple_write_cb(int fd, short event, void *arg) +{ + int len; + + if (arg == NULL) + return; + + len = write(fd, TEST1, strlen(TEST1) + 1); + if (len == -1) + test_ok = 0; + else + test_ok = 1; +} + +static void +multiple_write_cb(int fd, short event, void *arg) +{ + struct event *ev = arg; + int len; + + len = 128; + if (woff + len >= sizeof(wbuf)) + len = sizeof(wbuf) - woff; + + len = write(fd, wbuf + woff, len); + if (len == -1) { + fprintf(stderr, "%s: write\n", __func__); + if (usepersist) + event_del(ev); + return; + } + + woff += len; + + if (woff >= sizeof(wbuf)) { + shutdown(fd, SHUT_WR); + if (usepersist) + event_del(ev); + return; + } + + if (!usepersist) { + if (event_add(ev, NULL) == -1) + exit(1); + } +} + +static void +multiple_read_cb(int fd, short event, void *arg) +{ + struct event *ev = arg; + int len; + + len = read(fd, rbuf + roff, sizeof(rbuf) - roff); + if (len == -1) + fprintf(stderr, "%s: read\n", __func__); + if (len <= 0) { + if (usepersist) + event_del(ev); + return; + } + + roff += len; + if (!usepersist) { + if (event_add(ev, NULL) == -1) + exit(1); + } +} + +static void +timeout_cb(int fd, short event, void *arg) +{ + struct timeval tv; + int diff; + + evutil_gettimeofday(&tcalled, NULL); + if (evutil_timercmp(&tcalled, &tset, >)) + evutil_timersub(&tcalled, &tset, &tv); + else + evutil_timersub(&tset, &tcalled, &tv); + + diff = tv.tv_sec*1000 + tv.tv_usec/1000 - SECONDS * 1000; + if (diff < 0) + diff = -diff; + + if (diff < 100) + test_ok = 1; +} + +#ifndef WIN32 +static void +signal_cb_sa(int sig) +{ + test_ok = 2; +} + +static void +signal_cb(int fd, short event, void *arg) +{ + struct event *ev = arg; + + signal_del(ev); + test_ok = 1; +} +#endif + +struct both { + struct event ev; + int nread; +}; + +static void +combined_read_cb(int fd, short event, void *arg) +{ + struct both *both = arg; + char buf[128]; + int len; + + len = read(fd, buf, sizeof(buf)); + if (len == -1) + fprintf(stderr, "%s: read\n", __func__); + if (len <= 0) + return; + + both->nread += len; + if (event_add(&both->ev, NULL) == -1) + exit(1); +} + +static void +combined_write_cb(int fd, short event, void *arg) +{ + struct both *both = arg; + char buf[128]; + int len; + + len = sizeof(buf); + if (len > both->nread) + len = both->nread; + + len = write(fd, buf, len); + if (len == -1) + fprintf(stderr, "%s: write\n", __func__); + if (len <= 0) { + shutdown(fd, SHUT_WR); + return; + } + + both->nread -= len; + if (event_add(&both->ev, NULL) == -1) + exit(1); +} + +/* Test infrastructure */ + +static int +setup_test(const char *name) +{ + + fprintf(stdout, "%s", name); + + if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1) { + fprintf(stderr, "%s: socketpair\n", __func__); + exit(1); + } + +#ifdef HAVE_FCNTL + if (fcntl(pair[0], F_SETFL, O_NONBLOCK) == -1) + fprintf(stderr, "fcntl(O_NONBLOCK)"); + + if (fcntl(pair[1], F_SETFL, O_NONBLOCK) == -1) + fprintf(stderr, "fcntl(O_NONBLOCK)"); +#endif + + test_ok = 0; + called = 0; + return (0); +} + +static int +cleanup_test(void) +{ +#ifndef WIN32 + close(pair[0]); + close(pair[1]); +#else + CloseHandle((HANDLE)pair[0]); + CloseHandle((HANDLE)pair[1]); +#endif + if (test_ok) + fprintf(stdout, "OK\n"); + else { + fprintf(stdout, "FAILED\n"); + exit(1); + } + test_ok = 0; + return (0); +} + +static void +test_registerfds(void) +{ + int i, j; + int pair[2]; + struct event read_evs[512]; + struct event write_evs[512]; + + struct event_base *base = event_base_new(); + + fprintf(stdout, "Testing register fds: "); + + for (i = 0; i < 512; ++i) { + if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1) { + /* run up to the limit of file descriptors */ + break; + } + event_set(&read_evs[i], pair[0], + EV_READ|EV_PERSIST, simple_read_cb, NULL); + event_base_set(base, &read_evs[i]); + event_add(&read_evs[i], NULL); + event_set(&write_evs[i], pair[1], + EV_WRITE|EV_PERSIST, simple_write_cb, NULL); + event_base_set(base, &write_evs[i]); + event_add(&write_evs[i], NULL); + + /* just loop once */ + event_base_loop(base, EVLOOP_ONCE); + } + + /* now delete everything */ + for (j = 0; j < i; ++j) { + event_del(&read_evs[j]); + event_del(&write_evs[j]); +#ifndef WIN32 + close(read_evs[j].ev_fd); + close(write_evs[j].ev_fd); +#else + CloseHandle((HANDLE)read_evs[j].ev_fd); + CloseHandle((HANDLE)write_evs[j].ev_fd); +#endif + + /* just loop once */ + event_base_loop(base, EVLOOP_ONCE); + } + + event_base_free(base); + + fprintf(stdout, "OK\n"); +} + +static void +test_simpleread(void) +{ + struct event ev; + + /* Very simple read test */ + setup_test("Simple read: "); + + write(pair[0], TEST1, strlen(TEST1)+1); + shutdown(pair[0], SHUT_WR); + + event_set(&ev, pair[1], EV_READ, simple_read_cb, &ev); + if (event_add(&ev, NULL) == -1) + exit(1); + event_dispatch(); + + cleanup_test(); +} + +static void +test_simplewrite(void) +{ + struct event ev; + + /* Very simple write test */ + setup_test("Simple write: "); + + event_set(&ev, pair[0], EV_WRITE, simple_write_cb, &ev); + if (event_add(&ev, NULL) == -1) + exit(1); + event_dispatch(); + + cleanup_test(); +} + +static void +test_multiple(void) +{ + struct event ev, ev2; + int i; + + /* Multiple read and write test */ + setup_test("Multiple read/write: "); + memset(rbuf, 0, sizeof(rbuf)); + for (i = 0; i < sizeof(wbuf); i++) + wbuf[i] = i; + + roff = woff = 0; + usepersist = 0; + + event_set(&ev, pair[0], EV_WRITE, multiple_write_cb, &ev); + if (event_add(&ev, NULL) == -1) + exit(1); + event_set(&ev2, pair[1], EV_READ, multiple_read_cb, &ev2); + if (event_add(&ev2, NULL) == -1) + exit(1); + event_dispatch(); + + if (roff == woff) + test_ok = memcmp(rbuf, wbuf, sizeof(wbuf)) == 0; + + cleanup_test(); +} + +static void +test_persistent(void) +{ + struct event ev, ev2; + int i; + + /* Multiple read and write test with persist */ + setup_test("Persist read/write: "); + memset(rbuf, 0, sizeof(rbuf)); + for (i = 0; i < sizeof(wbuf); i++) + wbuf[i] = i; + + roff = woff = 0; + usepersist = 1; + + event_set(&ev, pair[0], EV_WRITE|EV_PERSIST, multiple_write_cb, &ev); + if (event_add(&ev, NULL) == -1) + exit(1); + event_set(&ev2, pair[1], EV_READ|EV_PERSIST, multiple_read_cb, &ev2); + if (event_add(&ev2, NULL) == -1) + exit(1); + event_dispatch(); + + if (roff == woff) + test_ok = memcmp(rbuf, wbuf, sizeof(wbuf)) == 0; + + cleanup_test(); +} + +static void +test_combined(void) +{ + struct both r1, r2, w1, w2; + + setup_test("Combined read/write: "); + memset(&r1, 0, sizeof(r1)); + memset(&r2, 0, sizeof(r2)); + memset(&w1, 0, sizeof(w1)); + memset(&w2, 0, sizeof(w2)); + + w1.nread = 4096; + w2.nread = 8192; + + event_set(&r1.ev, pair[0], EV_READ, combined_read_cb, &r1); + event_set(&w1.ev, pair[0], EV_WRITE, combined_write_cb, &w1); + event_set(&r2.ev, pair[1], EV_READ, combined_read_cb, &r2); + event_set(&w2.ev, pair[1], EV_WRITE, combined_write_cb, &w2); + if (event_add(&r1.ev, NULL) == -1) + exit(1); + if (event_add(&w1.ev, NULL)) + exit(1); + if (event_add(&r2.ev, NULL)) + exit(1); + if (event_add(&w2.ev, NULL)) + exit(1); + + event_dispatch(); + + if (r1.nread == 8192 && r2.nread == 4096) + test_ok = 1; + + cleanup_test(); +} + +static void +test_simpletimeout(void) +{ + struct timeval tv; + struct event ev; + + setup_test("Simple timeout: "); + + tv.tv_usec = 0; + tv.tv_sec = SECONDS; + evtimer_set(&ev, timeout_cb, NULL); + evtimer_add(&ev, &tv); + + evutil_gettimeofday(&tset, NULL); + event_dispatch(); + + cleanup_test(); +} + +#ifndef WIN32 +extern struct event_base *current_base; + +static void +child_signal_cb(int fd, short event, void *arg) +{ + struct timeval tv; + int *pint = arg; + + *pint = 1; + + tv.tv_usec = 500000; + tv.tv_sec = 0; + event_loopexit(&tv); +} + +static void +test_fork(void) +{ + int status, got_sigchld = 0; + struct event ev, sig_ev; + pid_t pid; + + setup_test("After fork: "); + + write(pair[0], TEST1, strlen(TEST1)+1); + + event_set(&ev, pair[1], EV_READ, simple_read_cb, &ev); + if (event_add(&ev, NULL) == -1) + exit(1); + + signal_set(&sig_ev, SIGCHLD, child_signal_cb, &got_sigchld); + signal_add(&sig_ev, NULL); + + if ((pid = fork()) == 0) { + /* in the child */ + if (event_reinit(current_base) == -1) { + fprintf(stderr, "FAILED (reinit)\n"); + exit(1); + } + + signal_del(&sig_ev); + + called = 0; + + event_dispatch(); + + /* we do not send an EOF; simple_read_cb requires an EOF + * to set test_ok. we just verify that the callback was + * called. */ + exit(test_ok != 0 || called != 2 ? -2 : 76); + } + + /* wait for the child to read the data */ + sleep(1); + + write(pair[0], TEST1, strlen(TEST1)+1); + + if (waitpid(pid, &status, 0) == -1) { + fprintf(stderr, "FAILED (fork)\n"); + exit(1); + } + + if (WEXITSTATUS(status) != 76) { + fprintf(stderr, "FAILED (exit): %d\n", WEXITSTATUS(status)); + exit(1); + } + + /* test that the current event loop still works */ + write(pair[0], TEST1, strlen(TEST1)+1); + shutdown(pair[0], SHUT_WR); + + event_dispatch(); + + if (!got_sigchld) { + fprintf(stdout, "FAILED (sigchld)\n"); + exit(1); + } + + signal_del(&sig_ev); + + cleanup_test(); +} + +static void +test_simplesignal(void) +{ + struct event ev; + struct itimerval itv; + + setup_test("Simple signal: "); + signal_set(&ev, SIGALRM, signal_cb, &ev); + signal_add(&ev, NULL); + /* find bugs in which operations are re-ordered */ + signal_del(&ev); + signal_add(&ev, NULL); + + memset(&itv, 0, sizeof(itv)); + itv.it_value.tv_sec = 1; + if (setitimer(ITIMER_REAL, &itv, NULL) == -1) + goto skip_simplesignal; + + event_dispatch(); + skip_simplesignal: + if (signal_del(&ev) == -1) + test_ok = 0; + + cleanup_test(); +} + +static void +test_multiplesignal(void) +{ + struct event ev_one, ev_two; + struct itimerval itv; + + setup_test("Multiple signal: "); + + signal_set(&ev_one, SIGALRM, signal_cb, &ev_one); + signal_add(&ev_one, NULL); + + signal_set(&ev_two, SIGALRM, signal_cb, &ev_two); + signal_add(&ev_two, NULL); + + memset(&itv, 0, sizeof(itv)); + itv.it_value.tv_sec = 1; + if (setitimer(ITIMER_REAL, &itv, NULL) == -1) + goto skip_simplesignal; + + event_dispatch(); + + skip_simplesignal: + if (signal_del(&ev_one) == -1) + test_ok = 0; + if (signal_del(&ev_two) == -1) + test_ok = 0; + + cleanup_test(); +} + +static void +test_immediatesignal(void) +{ + struct event ev; + + test_ok = 0; + printf("Immediate signal: "); + signal_set(&ev, SIGUSR1, signal_cb, &ev); + signal_add(&ev, NULL); + raise(SIGUSR1); + event_loop(EVLOOP_NONBLOCK); + signal_del(&ev); + cleanup_test(); +} + +static void +test_signal_dealloc(void) +{ + /* make sure that signal_event is event_del'ed and pipe closed */ + struct event ev; + struct event_base *base = event_init(); + printf("Signal dealloc: "); + signal_set(&ev, SIGUSR1, signal_cb, &ev); + signal_add(&ev, NULL); + signal_del(&ev); + event_base_free(base); + /* If we got here without asserting, we're fine. */ + test_ok = 1; + cleanup_test(); +} + +static void +test_signal_pipeloss(void) +{ + /* make sure that the base1 pipe is closed correctly. */ + struct event_base *base1, *base2; + int pipe1; + test_ok = 0; + printf("Signal pipeloss: "); + base1 = event_init(); + pipe1 = base1->sig.ev_signal_pair[0]; + base2 = event_init(); + event_base_free(base2); + event_base_free(base1); + if (close(pipe1) != -1 || errno!=EBADF) { + /* fd must be closed, so second close gives -1, EBADF */ + printf("signal pipe not closed. "); + test_ok = 0; + } else { + test_ok = 1; + } + cleanup_test(); +} + +/* + * make two bases to catch signals, use both of them. this only works + * for event mechanisms that use our signal pipe trick. kqueue handles + * signals internally, and all interested kqueues get all the signals. + */ +static void +test_signal_switchbase(void) +{ + struct event ev1, ev2; + struct event_base *base1, *base2; + int is_kqueue; + test_ok = 0; + printf("Signal switchbase: "); + base1 = event_init(); + base2 = event_init(); + is_kqueue = !strcmp(event_get_method(),"kqueue"); + signal_set(&ev1, SIGUSR1, signal_cb, &ev1); + signal_set(&ev2, SIGUSR1, signal_cb, &ev2); + if (event_base_set(base1, &ev1) || + event_base_set(base2, &ev2) || + event_add(&ev1, NULL) || + event_add(&ev2, NULL)) { + fprintf(stderr, "%s: cannot set base, add\n", __func__); + exit(1); + } + + test_ok = 0; + /* can handle signal before loop is called */ + raise(SIGUSR1); + event_base_loop(base2, EVLOOP_NONBLOCK); + if (is_kqueue) { + if (!test_ok) + goto done; + test_ok = 0; + } + event_base_loop(base1, EVLOOP_NONBLOCK); + if (test_ok && !is_kqueue) { + test_ok = 0; + + /* set base1 to handle signals */ + event_base_loop(base1, EVLOOP_NONBLOCK); + raise(SIGUSR1); + event_base_loop(base1, EVLOOP_NONBLOCK); + event_base_loop(base2, EVLOOP_NONBLOCK); + } + done: + event_base_free(base1); + event_base_free(base2); + cleanup_test(); +} + +/* + * assert that a signal event removed from the event queue really is + * removed - with no possibility of it's parent handler being fired. + */ +static void +test_signal_assert(void) +{ + struct event ev; + struct event_base *base = event_init(); + test_ok = 0; + printf("Signal handler assert: "); + /* use SIGCONT so we don't kill ourselves when we signal to nowhere */ + signal_set(&ev, SIGCONT, signal_cb, &ev); + signal_add(&ev, NULL); + /* + * if signal_del() fails to reset the handler, it's current handler + * will still point to evsignal_handler(). + */ + signal_del(&ev); + + raise(SIGCONT); + /* only way to verify we were in evsignal_handler() */ + if (base->sig.evsignal_caught) + test_ok = 0; + else + test_ok = 1; + + event_base_free(base); + cleanup_test(); + return; +} + +/* + * assert that we restore our previous signal handler properly. + */ +static void +test_signal_restore(void) +{ + struct event ev; + struct event_base *base = event_init(); +#ifdef HAVE_SIGACTION + struct sigaction sa; +#endif + + test_ok = 0; + printf("Signal handler restore: "); +#ifdef HAVE_SIGACTION + sa.sa_handler = signal_cb_sa; + sa.sa_flags = 0x0; + sigemptyset(&sa.sa_mask); + if (sigaction(SIGUSR1, &sa, NULL) == -1) + goto out; +#else + if (signal(SIGUSR1, signal_cb_sa) == SIG_ERR) + goto out; +#endif + signal_set(&ev, SIGUSR1, signal_cb, &ev); + signal_add(&ev, NULL); + signal_del(&ev); + + raise(SIGUSR1); + /* 1 == signal_cb, 2 == signal_cb_sa, we want our previous handler */ + if (test_ok != 2) + test_ok = 0; +out: + event_base_free(base); + cleanup_test(); + return; +} + +static void +signal_cb_swp(int sig, short event, void *arg) +{ + called++; + if (called < 5) + raise(sig); + else + event_loopexit(NULL); +} +static void +timeout_cb_swp(int fd, short event, void *arg) +{ + if (called == -1) { + struct timeval tv = {5, 0}; + + called = 0; + evtimer_add((struct event *)arg, &tv); + raise(SIGUSR1); + return; + } + test_ok = 0; + event_loopexit(NULL); +} + +static void +test_signal_while_processing(void) +{ + struct event_base *base = event_init(); + struct event ev, ev_timer; + struct timeval tv = {0, 0}; + + setup_test("Receiving a signal while processing other signal: "); + + called = -1; + test_ok = 1; + signal_set(&ev, SIGUSR1, signal_cb_swp, NULL); + signal_add(&ev, NULL); + evtimer_set(&ev_timer, timeout_cb_swp, &ev_timer); + evtimer_add(&ev_timer, &tv); + event_dispatch(); + + event_base_free(base); + cleanup_test(); + return; +} +#endif + +static void +test_free_active_base(void) +{ + struct event_base *base1; + struct event ev1; + setup_test("Free active base: "); + base1 = event_init(); + event_set(&ev1, pair[1], EV_READ, simple_read_cb, &ev1); + event_base_set(base1, &ev1); + event_add(&ev1, NULL); + /* event_del(&ev1); */ + event_base_free(base1); + test_ok = 1; + cleanup_test(); +} + +static void +test_event_base_new(void) +{ + struct event_base *base; + struct event ev1; + setup_test("Event base new: "); + + write(pair[0], TEST1, strlen(TEST1)+1); + shutdown(pair[0], SHUT_WR); + + base = event_base_new(); + event_set(&ev1, pair[1], EV_READ, simple_read_cb, &ev1); + event_base_set(base, &ev1); + event_add(&ev1, NULL); + + event_base_dispatch(base); + + event_base_free(base); + test_ok = 1; + cleanup_test(); +} + +static void +test_loopexit(void) +{ + struct timeval tv, tv_start, tv_end; + struct event ev; + + setup_test("Loop exit: "); + + tv.tv_usec = 0; + tv.tv_sec = 60*60*24; + evtimer_set(&ev, timeout_cb, NULL); + evtimer_add(&ev, &tv); + + tv.tv_usec = 0; + tv.tv_sec = 1; + event_loopexit(&tv); + + evutil_gettimeofday(&tv_start, NULL); + event_dispatch(); + evutil_gettimeofday(&tv_end, NULL); + evutil_timersub(&tv_end, &tv_start, &tv_end); + + evtimer_del(&ev); + + if (tv.tv_sec < 2) + test_ok = 1; + + cleanup_test(); +} + +static void +test_loopexit_multiple(void) +{ + struct timeval tv; + struct event_base *base; + + setup_test("Loop Multiple exit: "); + + base = event_base_new(); + + tv.tv_usec = 0; + tv.tv_sec = 1; + event_base_loopexit(base, &tv); + + tv.tv_usec = 0; + tv.tv_sec = 2; + event_base_loopexit(base, &tv); + + event_base_dispatch(base); + + event_base_free(base); + + test_ok = 1; + + cleanup_test(); +} + +static void +break_cb(int fd, short events, void *arg) +{ + test_ok = 1; + event_loopbreak(); +} + +static void +fail_cb(int fd, short events, void *arg) +{ + test_ok = 0; +} + +static void +test_loopbreak(void) +{ + struct event ev1, ev2; + struct timeval tv; + + setup_test("Loop break: "); + + tv.tv_sec = 0; + tv.tv_usec = 0; + evtimer_set(&ev1, break_cb, NULL); + evtimer_add(&ev1, &tv); + evtimer_set(&ev2, fail_cb, NULL); + evtimer_add(&ev2, &tv); + + event_dispatch(); + + evtimer_del(&ev1); + evtimer_del(&ev2); + + cleanup_test(); +} + +static void +test_evbuffer(void) { + + struct evbuffer *evb = evbuffer_new(); + setup_test("Testing Evbuffer: "); + + evbuffer_add_printf(evb, "%s/%d", "hello", 1); + + if (EVBUFFER_LENGTH(evb) == 7 && + strcmp((char*)EVBUFFER_DATA(evb), "hello/1") == 0) + test_ok = 1; + + evbuffer_free(evb); + + cleanup_test(); +} + +static void +test_evbuffer_find(void) +{ + u_char* p; + const char* test1 = "1234567890\r\n"; + const char* test2 = "1234567890\r"; +#define EVBUFFER_INITIAL_LENGTH 256 + char test3[EVBUFFER_INITIAL_LENGTH]; + unsigned int i; + struct evbuffer * buf = evbuffer_new(); + + /* make sure evbuffer_find doesn't match past the end of the buffer */ + fprintf(stdout, "Testing evbuffer_find 1: "); + evbuffer_add(buf, (u_char*)test1, strlen(test1)); + evbuffer_drain(buf, strlen(test1)); + evbuffer_add(buf, (u_char*)test2, strlen(test2)); + p = evbuffer_find(buf, (u_char*)"\r\n", 2); + if (p == NULL) { + fprintf(stdout, "OK\n"); + } else { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + /* + * drain the buffer and do another find; in r309 this would + * read past the allocated buffer causing a valgrind error. + */ + fprintf(stdout, "Testing evbuffer_find 2: "); + evbuffer_drain(buf, strlen(test2)); + for (i = 0; i < EVBUFFER_INITIAL_LENGTH; ++i) + test3[i] = 'a'; + test3[EVBUFFER_INITIAL_LENGTH - 1] = 'x'; + evbuffer_add(buf, (u_char *)test3, EVBUFFER_INITIAL_LENGTH); + p = evbuffer_find(buf, (u_char *)"xy", 2); + if (p == NULL) { + printf("OK\n"); + } else { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + /* simple test for match at end of allocated buffer */ + fprintf(stdout, "Testing evbuffer_find 3: "); + p = evbuffer_find(buf, (u_char *)"ax", 2); + if (p != NULL && strncmp((char*)p, "ax", 2) == 0) { + printf("OK\n"); + } else { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + evbuffer_free(buf); +} + +/* + * simple bufferevent test + */ + +static void +readcb(struct bufferevent *bev, void *arg) +{ + if (EVBUFFER_LENGTH(bev->input) == 8333) { + bufferevent_disable(bev, EV_READ); + test_ok++; + } +} + +static void +writecb(struct bufferevent *bev, void *arg) +{ + if (EVBUFFER_LENGTH(bev->output) == 0) + test_ok++; +} + +static void +errorcb(struct bufferevent *bev, short what, void *arg) +{ + test_ok = -2; +} + +static void +test_bufferevent(void) +{ + struct bufferevent *bev1, *bev2; + char buffer[8333]; + int i; + + setup_test("Bufferevent: "); + + bev1 = bufferevent_new(pair[0], readcb, writecb, errorcb, NULL); + bev2 = bufferevent_new(pair[1], readcb, writecb, errorcb, NULL); + + bufferevent_disable(bev1, EV_READ); + bufferevent_enable(bev2, EV_READ); + + for (i = 0; i < sizeof(buffer); i++) + buffer[i] = i; + + bufferevent_write(bev1, buffer, sizeof(buffer)); + + event_dispatch(); + + bufferevent_free(bev1); + bufferevent_free(bev2); + + if (test_ok != 2) + test_ok = 0; + + cleanup_test(); +} + +/* + * test watermarks and bufferevent + */ + +static void +wm_readcb(struct bufferevent *bev, void *arg) +{ + int len = EVBUFFER_LENGTH(bev->input); + static int nread; + + assert(len >= 10 && len <= 20); + + evbuffer_drain(bev->input, len); + + nread += len; + if (nread == 65000) { + bufferevent_disable(bev, EV_READ); + test_ok++; + } +} + +static void +wm_writecb(struct bufferevent *bev, void *arg) +{ + if (EVBUFFER_LENGTH(bev->output) == 0) + test_ok++; +} + +static void +wm_errorcb(struct bufferevent *bev, short what, void *arg) +{ + test_ok = -2; +} + +static void +test_bufferevent_watermarks(void) +{ + struct bufferevent *bev1, *bev2; + char buffer[65000]; + int i; + + setup_test("Bufferevent Watermarks: "); + + bev1 = bufferevent_new(pair[0], NULL, wm_writecb, wm_errorcb, NULL); + bev2 = bufferevent_new(pair[1], wm_readcb, NULL, wm_errorcb, NULL); + + bufferevent_disable(bev1, EV_READ); + bufferevent_enable(bev2, EV_READ); + + for (i = 0; i < sizeof(buffer); i++) + buffer[i] = i; + + bufferevent_write(bev1, buffer, sizeof(buffer)); + + /* limit the reading on the receiving bufferevent */ + bufferevent_setwatermark(bev2, EV_READ, 10, 20); + + event_dispatch(); + + bufferevent_free(bev1); + bufferevent_free(bev2); + + if (test_ok != 2) + test_ok = 0; + + cleanup_test(); +} + +struct test_pri_event { + struct event ev; + int count; +}; + +static void +test_priorities_cb(int fd, short what, void *arg) +{ + struct test_pri_event *pri = arg; + struct timeval tv; + + if (pri->count == 3) { + event_loopexit(NULL); + return; + } + + pri->count++; + + evutil_timerclear(&tv); + event_add(&pri->ev, &tv); +} + +static void +test_priorities(int npriorities) +{ + char buf[32]; + struct test_pri_event one, two; + struct timeval tv; + + evutil_snprintf(buf, sizeof(buf), "Testing Priorities %d: ", npriorities); + setup_test(buf); + + event_base_priority_init(global_base, npriorities); + + memset(&one, 0, sizeof(one)); + memset(&two, 0, sizeof(two)); + + timeout_set(&one.ev, test_priorities_cb, &one); + if (event_priority_set(&one.ev, 0) == -1) { + fprintf(stderr, "%s: failed to set priority", __func__); + exit(1); + } + + timeout_set(&two.ev, test_priorities_cb, &two); + if (event_priority_set(&two.ev, npriorities - 1) == -1) { + fprintf(stderr, "%s: failed to set priority", __func__); + exit(1); + } + + evutil_timerclear(&tv); + + if (event_add(&one.ev, &tv) == -1) + exit(1); + if (event_add(&two.ev, &tv) == -1) + exit(1); + + event_dispatch(); + + event_del(&one.ev); + event_del(&two.ev); + + if (npriorities == 1) { + if (one.count == 3 && two.count == 3) + test_ok = 1; + } else if (npriorities == 2) { + /* Two is called once because event_loopexit is priority 1 */ + if (one.count == 3 && two.count == 1) + test_ok = 1; + } else { + if (one.count == 3 && two.count == 0) + test_ok = 1; + } + + cleanup_test(); +} + +static void +test_multiple_cb(int fd, short event, void *arg) +{ + if (event & EV_READ) + test_ok |= 1; + else if (event & EV_WRITE) + test_ok |= 2; +} + +static void +test_multiple_events_for_same_fd(void) +{ + struct event e1, e2; + + setup_test("Multiple events for same fd: "); + + event_set(&e1, pair[0], EV_READ, test_multiple_cb, NULL); + event_add(&e1, NULL); + event_set(&e2, pair[0], EV_WRITE, test_multiple_cb, NULL); + event_add(&e2, NULL); + event_loop(EVLOOP_ONCE); + event_del(&e2); + write(pair[1], TEST1, strlen(TEST1)+1); + event_loop(EVLOOP_ONCE); + event_del(&e1); + + if (test_ok != 3) + test_ok = 0; + + cleanup_test(); +} + +int evtag_decode_int(uint32_t *pnumber, struct evbuffer *evbuf); +int evtag_encode_tag(struct evbuffer *evbuf, uint32_t number); +int evtag_decode_tag(uint32_t *pnumber, struct evbuffer *evbuf); + +static void +read_once_cb(int fd, short event, void *arg) +{ + char buf[256]; + int len; + + len = read(fd, buf, sizeof(buf)); + + if (called) { + test_ok = 0; + } else if (len) { + /* Assumes global pair[0] can be used for writing */ + write(pair[0], TEST1, strlen(TEST1)+1); + test_ok = 1; + } + + called++; +} + +static void +test_want_only_once(void) +{ + struct event ev; + struct timeval tv; + + /* Very simple read test */ + setup_test("Want read only once: "); + + write(pair[0], TEST1, strlen(TEST1)+1); + + /* Setup the loop termination */ + evutil_timerclear(&tv); + tv.tv_sec = 1; + event_loopexit(&tv); + + event_set(&ev, pair[1], EV_READ, read_once_cb, &ev); + if (event_add(&ev, NULL) == -1) + exit(1); + event_dispatch(); + + cleanup_test(); +} + +#define TEST_MAX_INT 6 + +static void +evtag_int_test(void) +{ + struct evbuffer *tmp = evbuffer_new(); + uint32_t integers[TEST_MAX_INT] = { + 0xaf0, 0x1000, 0x1, 0xdeadbeef, 0x00, 0xbef000 + }; + uint32_t integer; + int i; + + for (i = 0; i < TEST_MAX_INT; i++) { + int oldlen, newlen; + oldlen = EVBUFFER_LENGTH(tmp); + encode_int(tmp, integers[i]); + newlen = EVBUFFER_LENGTH(tmp); + fprintf(stdout, "\t\tencoded 0x%08x with %d bytes\n", + integers[i], newlen - oldlen); + } + + for (i = 0; i < TEST_MAX_INT; i++) { + if (evtag_decode_int(&integer, tmp) == -1) { + fprintf(stderr, "decode %d failed", i); + exit(1); + } + if (integer != integers[i]) { + fprintf(stderr, "got %x, wanted %x", + integer, integers[i]); + exit(1); + } + } + + if (EVBUFFER_LENGTH(tmp) != 0) { + fprintf(stderr, "trailing data"); + exit(1); + } + evbuffer_free(tmp); + + fprintf(stdout, "\t%s: OK\n", __func__); +} + +static void +evtag_fuzz(void) +{ + u_char buffer[4096]; + struct evbuffer *tmp = evbuffer_new(); + struct timeval tv; + int i, j; + + int not_failed = 0; + for (j = 0; j < 100; j++) { + for (i = 0; i < sizeof(buffer); i++) + buffer[i] = rand(); + evbuffer_drain(tmp, -1); + evbuffer_add(tmp, buffer, sizeof(buffer)); + + if (evtag_unmarshal_timeval(tmp, 0, &tv) != -1) + not_failed++; + } + + /* The majority of decodes should fail */ + if (not_failed >= 10) { + fprintf(stderr, "evtag_unmarshal should have failed"); + exit(1); + } + + /* Now insert some corruption into the tag length field */ + evbuffer_drain(tmp, -1); + evutil_timerclear(&tv); + tv.tv_sec = 1; + evtag_marshal_timeval(tmp, 0, &tv); + evbuffer_add(tmp, buffer, sizeof(buffer)); + + EVBUFFER_DATA(tmp)[1] = 0xff; + if (evtag_unmarshal_timeval(tmp, 0, &tv) != -1) { + fprintf(stderr, "evtag_unmarshal_timeval should have failed"); + exit(1); + } + + evbuffer_free(tmp); + + fprintf(stdout, "\t%s: OK\n", __func__); +} + +static void +evtag_tag_encoding(void) +{ + struct evbuffer *tmp = evbuffer_new(); + uint32_t integers[TEST_MAX_INT] = { + 0xaf0, 0x1000, 0x1, 0xdeadbeef, 0x00, 0xbef000 + }; + uint32_t integer; + int i; + + for (i = 0; i < TEST_MAX_INT; i++) { + int oldlen, newlen; + oldlen = EVBUFFER_LENGTH(tmp); + evtag_encode_tag(tmp, integers[i]); + newlen = EVBUFFER_LENGTH(tmp); + fprintf(stdout, "\t\tencoded 0x%08x with %d bytes\n", + integers[i], newlen - oldlen); + } + + for (i = 0; i < TEST_MAX_INT; i++) { + if (evtag_decode_tag(&integer, tmp) == -1) { + fprintf(stderr, "decode %d failed", i); + exit(1); + } + if (integer != integers[i]) { + fprintf(stderr, "got %x, wanted %x", + integer, integers[i]); + exit(1); + } + } + + if (EVBUFFER_LENGTH(tmp) != 0) { + fprintf(stderr, "trailing data"); + exit(1); + } + evbuffer_free(tmp); + + fprintf(stdout, "\t%s: OK\n", __func__); +} + +static void +evtag_test(void) +{ + fprintf(stdout, "Testing Tagging:\n"); + + evtag_init(); + evtag_int_test(); + evtag_fuzz(); + + evtag_tag_encoding(); + + fprintf(stdout, "OK\n"); +} + +#ifndef WIN32 +static void +rpc_test(void) +{ + struct msg *msg, *msg2; + struct kill *attack; + struct run *run; + struct evbuffer *tmp = evbuffer_new(); + struct timeval tv_start, tv_end; + uint32_t tag; + int i; + + fprintf(stdout, "Testing RPC: "); + + msg = msg_new(); + EVTAG_ASSIGN(msg, from_name, "niels"); + EVTAG_ASSIGN(msg, to_name, "phoenix"); + + if (EVTAG_GET(msg, attack, &attack) == -1) { + fprintf(stderr, "Failed to set kill message.\n"); + exit(1); + } + + EVTAG_ASSIGN(attack, weapon, "feather"); + EVTAG_ASSIGN(attack, action, "tickle"); + + evutil_gettimeofday(&tv_start, NULL); + for (i = 0; i < 1000; ++i) { + run = EVTAG_ADD(msg, run); + if (run == NULL) { + fprintf(stderr, "Failed to add run message.\n"); + exit(1); + } + EVTAG_ASSIGN(run, how, "very fast but with some data in it"); + EVTAG_ASSIGN(run, fixed_bytes, + (unsigned char*)"012345678901234567890123"); + } + + if (msg_complete(msg) == -1) { + fprintf(stderr, "Failed to make complete message.\n"); + exit(1); + } + + evtag_marshal_msg(tmp, 0xdeaf, msg); + + if (evtag_peek(tmp, &tag) == -1) { + fprintf(stderr, "Failed to peak tag.\n"); + exit (1); + } + + if (tag != 0xdeaf) { + fprintf(stderr, "Got incorrect tag: %0x.\n", tag); + exit (1); + } + + msg2 = msg_new(); + if (evtag_unmarshal_msg(tmp, 0xdeaf, msg2) == -1) { + fprintf(stderr, "Failed to unmarshal message.\n"); + exit(1); + } + + evutil_gettimeofday(&tv_end, NULL); + evutil_timersub(&tv_end, &tv_start, &tv_end); + fprintf(stderr, "(%.1f us/add) ", + (float)tv_end.tv_sec/(float)i * 1000000.0 + + tv_end.tv_usec / (float)i); + + if (!EVTAG_HAS(msg2, from_name) || + !EVTAG_HAS(msg2, to_name) || + !EVTAG_HAS(msg2, attack)) { + fprintf(stderr, "Missing data structures.\n"); + exit(1); + } + + if (EVTAG_LEN(msg2, run) != i) { + fprintf(stderr, "Wrong number of run messages.\n"); + exit(1); + } + + msg_free(msg); + msg_free(msg2); + + evbuffer_free(tmp); + + fprintf(stdout, "OK\n"); +} +#endif + +static void +test_evutil_strtoll(void) +{ + const char *s; + char *endptr; + setup_test("evutil_stroll: "); + test_ok = 0; + + if (evutil_strtoll("5000000000", NULL, 10) != ((ev_int64_t)5000000)*1000) + goto err; + if (evutil_strtoll("-5000000000", NULL, 10) != ((ev_int64_t)5000000)*-1000) + goto err; + s = " 99999stuff"; + if (evutil_strtoll(s, &endptr, 10) != (ev_int64_t)99999) + goto err; + if (endptr != s+6) + goto err; + if (evutil_strtoll("foo", NULL, 10) != 0) + goto err; + + test_ok = 1; + err: + cleanup_test(); +} + + +int +main (int argc, char **argv) +{ +#ifdef WIN32 + WORD wVersionRequested; + WSADATA wsaData; + int err; + + wVersionRequested = MAKEWORD( 2, 2 ); + + err = WSAStartup( wVersionRequested, &wsaData ); +#endif + +#ifndef WIN32 + if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) + return (1); +#endif + setvbuf(stdout, NULL, _IONBF, 0); + + /* Initalize the event library */ + global_base = event_init(); + + test_registerfds(); + + test_evutil_strtoll(); + + /* use the global event base and need to be called first */ + test_priorities(1); + test_priorities(2); + test_priorities(3); + + test_evbuffer(); + test_evbuffer_find(); + + test_bufferevent(); + test_bufferevent_watermarks(); + + test_free_active_base(); + + test_event_base_new(); + + http_suite(); + +#ifndef WIN32 + rpc_suite(); +#endif + + dns_suite(); + +#ifndef WIN32 + test_fork(); +#endif + + test_simpleread(); + + test_simplewrite(); + + test_multiple(); + + test_persistent(); + + test_combined(); + + test_simpletimeout(); +#ifndef WIN32 + test_simplesignal(); + test_multiplesignal(); + test_immediatesignal(); +#endif + test_loopexit(); + test_loopbreak(); + + test_loopexit_multiple(); + + test_multiple_events_for_same_fd(); + + test_want_only_once(); + + evtag_test(); + +#ifndef WIN32 + rpc_test(); + + test_signal_dealloc(); + test_signal_pipeloss(); + test_signal_switchbase(); + test_signal_restore(); + test_signal_assert(); + test_signal_while_processing(); +#endif + + return (0); +} + diff --git a/third_party/libevent/test/regress.gen.c b/third_party/libevent/test/regress.gen.c new file mode 100644 index 0000000000..0918fc0efd --- /dev/null +++ b/third_party/libevent/test/regress.gen.c @@ -0,0 +1,878 @@ +/* + * Automatically generated from ./regress.rpc + * by event_rpcgen.py/0.1. DO NOT EDIT THIS FILE. + */ + +#include +#ifdef _EVENT_HAVE_SYS_TIME_H +#include +#endif +#include +#include +#include +#define EVENT_NO_STRUCT +#include + +#ifdef _EVENT___func__ +#define __func__ _EVENT___func__ +#endif + +#include "./regress.gen.h" + +void event_err(int eval, const char *fmt, ...); +void event_warn(const char *fmt, ...); +void event_errx(int eval, const char *fmt, ...); +void event_warnx(const char *fmt, ...); + + +/* + * Implementation of msg + */ + +static struct msg_access_ __msg_base = { + msg_from_name_assign, + msg_from_name_get, + msg_to_name_assign, + msg_to_name_get, + msg_attack_assign, + msg_attack_get, + msg_run_assign, + msg_run_get, + msg_run_add, +}; + +struct msg * +msg_new(void) +{ + struct msg *tmp; + if ((tmp = malloc(sizeof(struct msg))) == NULL) { + event_warn("%s: malloc", __func__); + return (NULL); + } + tmp->base = &__msg_base; + + tmp->from_name_data = NULL; + tmp->from_name_set = 0; + + tmp->to_name_data = NULL; + tmp->to_name_set = 0; + + tmp->attack_data = NULL; + tmp->attack_set = 0; + + tmp->run_data = NULL; + tmp->run_length = 0; + tmp->run_num_allocated = 0; + tmp->run_set = 0; + + return (tmp); +} + + + + +struct run * +msg_run_add(struct msg *msg) +{ + if (++msg->run_length >= msg->run_num_allocated) { + int tobe_allocated = msg->run_num_allocated; + struct run ** new_data = NULL; + tobe_allocated = !tobe_allocated ? 1 : tobe_allocated << 1; + new_data = (struct run **) realloc(msg->run_data, + tobe_allocated * sizeof(struct run *)); + if (new_data == NULL) + goto error; + msg->run_data = new_data; + msg->run_num_allocated = tobe_allocated; + } + msg->run_data[msg->run_length - 1] = run_new(); + if (msg->run_data[msg->run_length - 1] == NULL) + goto error; + msg->run_set = 1; + return (msg->run_data[msg->run_length - 1]); +error: + --msg->run_length; + return (NULL); +} + + +int +msg_from_name_assign(struct msg *msg, + const char * value) +{ + if (msg->from_name_data != NULL) + free(msg->from_name_data); + if ((msg->from_name_data = strdup(value)) == NULL) + return (-1); + msg->from_name_set = 1; + return (0); +} + +int +msg_to_name_assign(struct msg *msg, + const char * value) +{ + if (msg->to_name_data != NULL) + free(msg->to_name_data); + if ((msg->to_name_data = strdup(value)) == NULL) + return (-1); + msg->to_name_set = 1; + return (0); +} + +int +msg_attack_assign(struct msg *msg, + const struct kill* value) +{ + struct evbuffer *tmp = NULL; + if (msg->attack_set) { + kill_clear(msg->attack_data); + msg->attack_set = 0; + } else { + msg->attack_data = kill_new(); + if (msg->attack_data == NULL) { + event_warn("%s: kill_new()", __func__); + goto error; + } + } + if ((tmp = evbuffer_new()) == NULL) { + event_warn("%s: evbuffer_new()", __func__); + goto error; + } + kill_marshal(tmp, value); + if (kill_unmarshal(msg->attack_data, tmp) == -1) { + event_warnx("%s: kill_unmarshal", __func__); + goto error; + } + msg->attack_set = 1; + evbuffer_free(tmp); + return (0); + error: + if (tmp != NULL) + evbuffer_free(tmp); + if (msg->attack_data != NULL) { + kill_free(msg->attack_data); + msg->attack_data = NULL; + } + return (-1); +} + +int +msg_run_assign(struct msg *msg, int off, + const struct run * value) +{ + struct evbuffer *tmp = NULL; + if (!msg->run_set || off < 0 || off >= msg->run_length) + return (-1); + run_clear(msg->run_data[off]); + if ((tmp = evbuffer_new()) == NULL) { + event_warn("%s: evbuffer_new()", __func__); + goto error; + } + run_marshal(tmp, value); + if (run_unmarshal(msg->run_data[off], tmp) == -1) { + event_warnx("%s: run_unmarshal", __func__); + goto error; + } + evbuffer_free(tmp); + return (0); +error: + if (tmp != NULL) + evbuffer_free(tmp); + run_clear(msg->run_data[off]); + return (-1); +} + +int +msg_from_name_get(struct msg *msg, char * *value) +{ + if (msg->from_name_set != 1) + return (-1); + *value = msg->from_name_data; + return (0); +} + +int +msg_to_name_get(struct msg *msg, char * *value) +{ + if (msg->to_name_set != 1) + return (-1); + *value = msg->to_name_data; + return (0); +} + +int +msg_attack_get(struct msg *msg, struct kill* *value) +{ + if (msg->attack_set != 1) { + msg->attack_data = kill_new(); + if (msg->attack_data == NULL) + return (-1); + msg->attack_set = 1; + } + *value = msg->attack_data; + return (0); +} + +int +msg_run_get(struct msg *msg, int offset, + struct run * *value) +{ + if (!msg->run_set || offset < 0 || offset >= msg->run_length) + return (-1); + *value = msg->run_data[offset]; + return (0); +} + +void +msg_clear(struct msg *tmp) +{ + if (tmp->from_name_set == 1) { + free (tmp->from_name_data); + tmp->from_name_data = NULL; + tmp->from_name_set = 0; + } + if (tmp->to_name_set == 1) { + free (tmp->to_name_data); + tmp->to_name_data = NULL; + tmp->to_name_set = 0; + } + if (tmp->attack_set == 1) { + kill_free(tmp->attack_data); + tmp->attack_data = NULL; + tmp->attack_set = 0; + } + if (tmp->run_set == 1) { + int i; + for (i = 0; i < tmp->run_length; ++i) { + run_free(tmp->run_data[i]); + } + free(tmp->run_data); + tmp->run_data = NULL; + tmp->run_set = 0; + tmp->run_length = 0; + tmp->run_num_allocated = 0; + } +} + +void +msg_free(struct msg *tmp) +{ + if (tmp->from_name_data != NULL) + free (tmp->from_name_data); + if (tmp->to_name_data != NULL) + free (tmp->to_name_data); + if (tmp->attack_data != NULL) + kill_free(tmp->attack_data); + if (tmp->run_data != NULL) { + int i; + for (i = 0; i < tmp->run_length; ++i) { + run_free(tmp->run_data[i]); + tmp->run_data[i] = NULL; + } + free(tmp->run_data); + tmp->run_data = NULL; + tmp->run_length = 0; + tmp->run_num_allocated = 0; + } + free(tmp); +} + +void +msg_marshal(struct evbuffer *evbuf, const struct msg *tmp){ + evtag_marshal_string(evbuf, MSG_FROM_NAME, tmp->from_name_data); + evtag_marshal_string(evbuf, MSG_TO_NAME, tmp->to_name_data); + if (tmp->attack_set) { + evtag_marshal_kill(evbuf, MSG_ATTACK, tmp->attack_data); + } + { + int i; + for (i = 0; i < tmp->run_length; ++i) { + evtag_marshal_run(evbuf, MSG_RUN, tmp->run_data[i]); + } + } +} + +int +msg_unmarshal(struct msg *tmp, struct evbuffer *evbuf) +{ + ev_uint32_t tag; + while (EVBUFFER_LENGTH(evbuf) > 0) { + if (evtag_peek(evbuf, &tag) == -1) + return (-1); + switch (tag) { + + case MSG_FROM_NAME: + + if (tmp->from_name_set) + return (-1); + if (evtag_unmarshal_string(evbuf, MSG_FROM_NAME, &tmp->from_name_data) == -1) { + event_warnx("%s: failed to unmarshal from_name", __func__); + return (-1); + } + tmp->from_name_set = 1; + break; + + case MSG_TO_NAME: + + if (tmp->to_name_set) + return (-1); + if (evtag_unmarshal_string(evbuf, MSG_TO_NAME, &tmp->to_name_data) == -1) { + event_warnx("%s: failed to unmarshal to_name", __func__); + return (-1); + } + tmp->to_name_set = 1; + break; + + case MSG_ATTACK: + + if (tmp->attack_set) + return (-1); + tmp->attack_data = kill_new(); + if (tmp->attack_data == NULL) + return (-1); + if (evtag_unmarshal_kill(evbuf, MSG_ATTACK, tmp->attack_data) == -1) { + event_warnx("%s: failed to unmarshal attack", __func__); + return (-1); + } + tmp->attack_set = 1; + break; + + case MSG_RUN: + + if (msg_run_add(tmp) == NULL) + return (-1); + if (evtag_unmarshal_run(evbuf, MSG_RUN, + tmp->run_data[tmp->run_length - 1]) == -1) { + --tmp->run_length; + event_warnx("%s: failed to unmarshal run", __func__); + return (-1); + } + tmp->run_set = 1; + break; + + default: + return -1; + } + } + + if (msg_complete(tmp) == -1) + return (-1); + return (0); +} + +int +msg_complete(struct msg *msg) +{ + if (!msg->from_name_set) + return (-1); + if (!msg->to_name_set) + return (-1); + if (msg->attack_set && kill_complete(msg->attack_data) == -1) + return (-1); + { + int i; + for (i = 0; i < msg->run_length; ++i) { + if (run_complete(msg->run_data[i]) == -1) + return (-1); + } + } + return (0); +} + +int +evtag_unmarshal_msg(struct evbuffer *evbuf, ev_uint32_t need_tag, struct msg *msg) +{ + ev_uint32_t tag; + int res = -1; + + struct evbuffer *tmp = evbuffer_new(); + + if (evtag_unmarshal(evbuf, &tag, tmp) == -1 || tag != need_tag) + goto error; + + if (msg_unmarshal(msg, tmp) == -1) + goto error; + + res = 0; + + error: + evbuffer_free(tmp); + return (res); +} + +void +evtag_marshal_msg(struct evbuffer *evbuf, ev_uint32_t tag, const struct msg *msg) +{ + struct evbuffer *_buf = evbuffer_new(); + assert(_buf != NULL); + evbuffer_drain(_buf, -1); + msg_marshal(_buf, msg); + evtag_marshal(evbuf, tag, EVBUFFER_DATA(_buf), EVBUFFER_LENGTH(_buf)); + evbuffer_free(_buf); +} + +/* + * Implementation of kill + */ + +static struct kill_access_ __kill_base = { + kill_weapon_assign, + kill_weapon_get, + kill_action_assign, + kill_action_get, + kill_how_often_assign, + kill_how_often_get, +}; + +struct kill * +kill_new(void) +{ + struct kill *tmp; + if ((tmp = malloc(sizeof(struct kill))) == NULL) { + event_warn("%s: malloc", __func__); + return (NULL); + } + tmp->base = &__kill_base; + + tmp->weapon_data = NULL; + tmp->weapon_set = 0; + + tmp->action_data = NULL; + tmp->action_set = 0; + + tmp->how_often_data = 0; + tmp->how_often_set = 0; + + return (tmp); +} + + + + +int +kill_weapon_assign(struct kill *msg, + const char * value) +{ + if (msg->weapon_data != NULL) + free(msg->weapon_data); + if ((msg->weapon_data = strdup(value)) == NULL) + return (-1); + msg->weapon_set = 1; + return (0); +} + +int +kill_action_assign(struct kill *msg, + const char * value) +{ + if (msg->action_data != NULL) + free(msg->action_data); + if ((msg->action_data = strdup(value)) == NULL) + return (-1); + msg->action_set = 1; + return (0); +} + +int +kill_how_often_assign(struct kill *msg, const ev_uint32_t value) +{ + msg->how_often_set = 1; + msg->how_often_data = value; + return (0); +} + +int +kill_weapon_get(struct kill *msg, char * *value) +{ + if (msg->weapon_set != 1) + return (-1); + *value = msg->weapon_data; + return (0); +} + +int +kill_action_get(struct kill *msg, char * *value) +{ + if (msg->action_set != 1) + return (-1); + *value = msg->action_data; + return (0); +} + +int +kill_how_often_get(struct kill *msg, ev_uint32_t *value) +{ + if (msg->how_often_set != 1) + return (-1); + *value = msg->how_often_data; + return (0); +} + +void +kill_clear(struct kill *tmp) +{ + if (tmp->weapon_set == 1) { + free (tmp->weapon_data); + tmp->weapon_data = NULL; + tmp->weapon_set = 0; + } + if (tmp->action_set == 1) { + free (tmp->action_data); + tmp->action_data = NULL; + tmp->action_set = 0; + } + tmp->how_often_set = 0; +} + +void +kill_free(struct kill *tmp) +{ + if (tmp->weapon_data != NULL) + free (tmp->weapon_data); + if (tmp->action_data != NULL) + free (tmp->action_data); + free(tmp); +} + +void +kill_marshal(struct evbuffer *evbuf, const struct kill *tmp){ + evtag_marshal_string(evbuf, KILL_WEAPON, tmp->weapon_data); + evtag_marshal_string(evbuf, KILL_ACTION, tmp->action_data); + if (tmp->how_often_set) { + evtag_marshal_int(evbuf, KILL_HOW_OFTEN, tmp->how_often_data); + } +} + +int +kill_unmarshal(struct kill *tmp, struct evbuffer *evbuf) +{ + ev_uint32_t tag; + while (EVBUFFER_LENGTH(evbuf) > 0) { + if (evtag_peek(evbuf, &tag) == -1) + return (-1); + switch (tag) { + + case KILL_WEAPON: + + if (tmp->weapon_set) + return (-1); + if (evtag_unmarshal_string(evbuf, KILL_WEAPON, &tmp->weapon_data) == -1) { + event_warnx("%s: failed to unmarshal weapon", __func__); + return (-1); + } + tmp->weapon_set = 1; + break; + + case KILL_ACTION: + + if (tmp->action_set) + return (-1); + if (evtag_unmarshal_string(evbuf, KILL_ACTION, &tmp->action_data) == -1) { + event_warnx("%s: failed to unmarshal action", __func__); + return (-1); + } + tmp->action_set = 1; + break; + + case KILL_HOW_OFTEN: + + if (tmp->how_often_set) + return (-1); + if (evtag_unmarshal_int(evbuf, KILL_HOW_OFTEN, &tmp->how_often_data) == -1) { + event_warnx("%s: failed to unmarshal how_often", __func__); + return (-1); + } + tmp->how_often_set = 1; + break; + + default: + return -1; + } + } + + if (kill_complete(tmp) == -1) + return (-1); + return (0); +} + +int +kill_complete(struct kill *msg) +{ + if (!msg->weapon_set) + return (-1); + if (!msg->action_set) + return (-1); + return (0); +} + +int +evtag_unmarshal_kill(struct evbuffer *evbuf, ev_uint32_t need_tag, struct kill *msg) +{ + ev_uint32_t tag; + int res = -1; + + struct evbuffer *tmp = evbuffer_new(); + + if (evtag_unmarshal(evbuf, &tag, tmp) == -1 || tag != need_tag) + goto error; + + if (kill_unmarshal(msg, tmp) == -1) + goto error; + + res = 0; + + error: + evbuffer_free(tmp); + return (res); +} + +void +evtag_marshal_kill(struct evbuffer *evbuf, ev_uint32_t tag, const struct kill *msg) +{ + struct evbuffer *_buf = evbuffer_new(); + assert(_buf != NULL); + evbuffer_drain(_buf, -1); + kill_marshal(_buf, msg); + evtag_marshal(evbuf, tag, EVBUFFER_DATA(_buf), EVBUFFER_LENGTH(_buf)); + evbuffer_free(_buf); +} + +/* + * Implementation of run + */ + +static struct run_access_ __run_base = { + run_how_assign, + run_how_get, + run_some_bytes_assign, + run_some_bytes_get, + run_fixed_bytes_assign, + run_fixed_bytes_get, +}; + +struct run * +run_new(void) +{ + struct run *tmp; + if ((tmp = malloc(sizeof(struct run))) == NULL) { + event_warn("%s: malloc", __func__); + return (NULL); + } + tmp->base = &__run_base; + + tmp->how_data = NULL; + tmp->how_set = 0; + + tmp->some_bytes_data = NULL; + tmp->some_bytes_length = 0; + tmp->some_bytes_set = 0; + + memset(tmp->fixed_bytes_data, 0, sizeof(tmp->fixed_bytes_data)); + tmp->fixed_bytes_set = 0; + + return (tmp); +} + + + + +int +run_how_assign(struct run *msg, + const char * value) +{ + if (msg->how_data != NULL) + free(msg->how_data); + if ((msg->how_data = strdup(value)) == NULL) + return (-1); + msg->how_set = 1; + return (0); +} + +int +run_some_bytes_assign(struct run *msg, const ev_uint8_t * value, ev_uint32_t len) +{ + if (msg->some_bytes_data != NULL) + free (msg->some_bytes_data); + msg->some_bytes_data = malloc(len); + if (msg->some_bytes_data == NULL) + return (-1); + msg->some_bytes_set = 1; + msg->some_bytes_length = len; + memcpy(msg->some_bytes_data, value, len); + return (0); +} + +int +run_fixed_bytes_assign(struct run *msg, const ev_uint8_t *value) +{ + msg->fixed_bytes_set = 1; + memcpy(msg->fixed_bytes_data, value, 24); + return (0); +} + +int +run_how_get(struct run *msg, char * *value) +{ + if (msg->how_set != 1) + return (-1); + *value = msg->how_data; + return (0); +} + +int +run_some_bytes_get(struct run *msg, ev_uint8_t * *value, ev_uint32_t *plen) +{ + if (msg->some_bytes_set != 1) + return (-1); + *value = msg->some_bytes_data; + *plen = msg->some_bytes_length; + return (0); +} + +int +run_fixed_bytes_get(struct run *msg, ev_uint8_t **value) +{ + if (msg->fixed_bytes_set != 1) + return (-1); + *value = msg->fixed_bytes_data; + return (0); +} + +void +run_clear(struct run *tmp) +{ + if (tmp->how_set == 1) { + free (tmp->how_data); + tmp->how_data = NULL; + tmp->how_set = 0; + } + if (tmp->some_bytes_set == 1) { + free (tmp->some_bytes_data); + tmp->some_bytes_data = NULL; + tmp->some_bytes_length = 0; + tmp->some_bytes_set = 0; + } + tmp->fixed_bytes_set = 0; + memset(tmp->fixed_bytes_data, 0, sizeof(tmp->fixed_bytes_data)); +} + +void +run_free(struct run *tmp) +{ + if (tmp->how_data != NULL) + free (tmp->how_data); + if (tmp->some_bytes_data != NULL) + free (tmp->some_bytes_data); + free(tmp); +} + +void +run_marshal(struct evbuffer *evbuf, const struct run *tmp){ + evtag_marshal_string(evbuf, RUN_HOW, tmp->how_data); + if (tmp->some_bytes_set) { + evtag_marshal(evbuf, RUN_SOME_BYTES, tmp->some_bytes_data, tmp->some_bytes_length); + } + evtag_marshal(evbuf, RUN_FIXED_BYTES, tmp->fixed_bytes_data, sizeof(tmp->fixed_bytes_data)); +} + +int +run_unmarshal(struct run *tmp, struct evbuffer *evbuf) +{ + ev_uint32_t tag; + while (EVBUFFER_LENGTH(evbuf) > 0) { + if (evtag_peek(evbuf, &tag) == -1) + return (-1); + switch (tag) { + + case RUN_HOW: + + if (tmp->how_set) + return (-1); + if (evtag_unmarshal_string(evbuf, RUN_HOW, &tmp->how_data) == -1) { + event_warnx("%s: failed to unmarshal how", __func__); + return (-1); + } + tmp->how_set = 1; + break; + + case RUN_SOME_BYTES: + + if (tmp->some_bytes_set) + return (-1); + if (evtag_payload_length(evbuf, &tmp->some_bytes_length) == -1) + return (-1); + if (tmp->some_bytes_length > EVBUFFER_LENGTH(evbuf)) + return (-1); + if ((tmp->some_bytes_data = malloc(tmp->some_bytes_length)) == NULL) + return (-1); + if (evtag_unmarshal_fixed(evbuf, RUN_SOME_BYTES, tmp->some_bytes_data, tmp->some_bytes_length) == -1) { + event_warnx("%s: failed to unmarshal some_bytes", __func__); + return (-1); + } + tmp->some_bytes_set = 1; + break; + + case RUN_FIXED_BYTES: + + if (tmp->fixed_bytes_set) + return (-1); + if (evtag_unmarshal_fixed(evbuf, RUN_FIXED_BYTES, tmp->fixed_bytes_data, sizeof(tmp->fixed_bytes_data)) == -1) { + event_warnx("%s: failed to unmarshal fixed_bytes", __func__); + return (-1); + } + tmp->fixed_bytes_set = 1; + break; + + default: + return -1; + } + } + + if (run_complete(tmp) == -1) + return (-1); + return (0); +} + +int +run_complete(struct run *msg) +{ + if (!msg->how_set) + return (-1); + if (!msg->fixed_bytes_set) + return (-1); + return (0); +} + +int +evtag_unmarshal_run(struct evbuffer *evbuf, ev_uint32_t need_tag, struct run *msg) +{ + ev_uint32_t tag; + int res = -1; + + struct evbuffer *tmp = evbuffer_new(); + + if (evtag_unmarshal(evbuf, &tag, tmp) == -1 || tag != need_tag) + goto error; + + if (run_unmarshal(msg, tmp) == -1) + goto error; + + res = 0; + + error: + evbuffer_free(tmp); + return (res); +} + +void +evtag_marshal_run(struct evbuffer *evbuf, ev_uint32_t tag, const struct run *msg) +{ + struct evbuffer *_buf = evbuffer_new(); + assert(_buf != NULL); + evbuffer_drain(_buf, -1); + run_marshal(_buf, msg); + evtag_marshal(evbuf, tag, EVBUFFER_DATA(_buf), EVBUFFER_LENGTH(_buf)); + evbuffer_free(_buf); +} + diff --git a/third_party/libevent/test/regress.gen.h b/third_party/libevent/test/regress.gen.h new file mode 100644 index 0000000000..b1feacd9eb --- /dev/null +++ b/third_party/libevent/test/regress.gen.h @@ -0,0 +1,183 @@ +/* + * Automatically generated from ./regress.rpc + */ + +#ifndef ___REGRESS_RPC_ +#define ___REGRESS_RPC_ + +#include +#ifdef _EVENT_HAVE_STDINT_H +#include +#endif +#define EVTAG_HAS(msg, member) ((msg)->member##_set == 1) +#ifdef __GNUC__ +#define EVTAG_ASSIGN(msg, member, args...) (*(msg)->base->member##_assign)(msg, ## args) +#define EVTAG_GET(msg, member, args...) (*(msg)->base->member##_get)(msg, ## args) +#else +#define EVTAG_ASSIGN(msg, member, ...) (*(msg)->base->member##_assign)(msg, ## __VA_ARGS__) +#define EVTAG_GET(msg, member, ...) (*(msg)->base->member##_get)(msg, ## __VA_ARGS__) +#endif +#define EVTAG_ADD(msg, member) (*(msg)->base->member##_add)(msg) +#define EVTAG_LEN(msg, member) ((msg)->member##_length) + +struct msg; +struct kill; +struct run; + +/* Tag definition for msg */ +enum msg_ { + MSG_FROM_NAME=1, + MSG_TO_NAME=2, + MSG_ATTACK=3, + MSG_RUN=4, + MSG_MAX_TAGS +}; + +/* Structure declaration for msg */ +struct msg_access_ { + int (*from_name_assign)(struct msg *, const char *); + int (*from_name_get)(struct msg *, char * *); + int (*to_name_assign)(struct msg *, const char *); + int (*to_name_get)(struct msg *, char * *); + int (*attack_assign)(struct msg *, const struct kill*); + int (*attack_get)(struct msg *, struct kill* *); + int (*run_assign)(struct msg *, int, const struct run *); + int (*run_get)(struct msg *, int, struct run * *); + struct run * (*run_add)(struct msg *); +}; + +struct msg { + struct msg_access_ *base; + + char *from_name_data; + char *to_name_data; + struct kill* attack_data; + struct run **run_data; + int run_length; + int run_num_allocated; + + ev_uint8_t from_name_set; + ev_uint8_t to_name_set; + ev_uint8_t attack_set; + ev_uint8_t run_set; +}; + +struct msg *msg_new(void); +void msg_free(struct msg *); +void msg_clear(struct msg *); +void msg_marshal(struct evbuffer *, const struct msg *); +int msg_unmarshal(struct msg *, struct evbuffer *); +int msg_complete(struct msg *); +void evtag_marshal_msg(struct evbuffer *, ev_uint32_t, + const struct msg *); +int evtag_unmarshal_msg(struct evbuffer *, ev_uint32_t, + struct msg *); +int msg_from_name_assign(struct msg *, const char *); +int msg_from_name_get(struct msg *, char * *); +int msg_to_name_assign(struct msg *, const char *); +int msg_to_name_get(struct msg *, char * *); +int msg_attack_assign(struct msg *, const struct kill*); +int msg_attack_get(struct msg *, struct kill* *); +int msg_run_assign(struct msg *, int, const struct run *); +int msg_run_get(struct msg *, int, struct run * *); +struct run * msg_run_add(struct msg *); +/* --- msg done --- */ + +/* Tag definition for kill */ +enum kill_ { + KILL_WEAPON=65825, + KILL_ACTION=2, + KILL_HOW_OFTEN=3, + KILL_MAX_TAGS +}; + +/* Structure declaration for kill */ +struct kill_access_ { + int (*weapon_assign)(struct kill *, const char *); + int (*weapon_get)(struct kill *, char * *); + int (*action_assign)(struct kill *, const char *); + int (*action_get)(struct kill *, char * *); + int (*how_often_assign)(struct kill *, const ev_uint32_t); + int (*how_often_get)(struct kill *, ev_uint32_t *); +}; + +struct kill { + struct kill_access_ *base; + + char *weapon_data; + char *action_data; + ev_uint32_t how_often_data; + + ev_uint8_t weapon_set; + ev_uint8_t action_set; + ev_uint8_t how_often_set; +}; + +struct kill *kill_new(void); +void kill_free(struct kill *); +void kill_clear(struct kill *); +void kill_marshal(struct evbuffer *, const struct kill *); +int kill_unmarshal(struct kill *, struct evbuffer *); +int kill_complete(struct kill *); +void evtag_marshal_kill(struct evbuffer *, ev_uint32_t, + const struct kill *); +int evtag_unmarshal_kill(struct evbuffer *, ev_uint32_t, + struct kill *); +int kill_weapon_assign(struct kill *, const char *); +int kill_weapon_get(struct kill *, char * *); +int kill_action_assign(struct kill *, const char *); +int kill_action_get(struct kill *, char * *); +int kill_how_often_assign(struct kill *, const ev_uint32_t); +int kill_how_often_get(struct kill *, ev_uint32_t *); +/* --- kill done --- */ + +/* Tag definition for run */ +enum run_ { + RUN_HOW=1, + RUN_SOME_BYTES=2, + RUN_FIXED_BYTES=3, + RUN_MAX_TAGS +}; + +/* Structure declaration for run */ +struct run_access_ { + int (*how_assign)(struct run *, const char *); + int (*how_get)(struct run *, char * *); + int (*some_bytes_assign)(struct run *, const ev_uint8_t *, ev_uint32_t); + int (*some_bytes_get)(struct run *, ev_uint8_t * *, ev_uint32_t *); + int (*fixed_bytes_assign)(struct run *, const ev_uint8_t *); + int (*fixed_bytes_get)(struct run *, ev_uint8_t **); +}; + +struct run { + struct run_access_ *base; + + char *how_data; + ev_uint8_t *some_bytes_data; + ev_uint32_t some_bytes_length; + ev_uint8_t fixed_bytes_data[24]; + + ev_uint8_t how_set; + ev_uint8_t some_bytes_set; + ev_uint8_t fixed_bytes_set; +}; + +struct run *run_new(void); +void run_free(struct run *); +void run_clear(struct run *); +void run_marshal(struct evbuffer *, const struct run *); +int run_unmarshal(struct run *, struct evbuffer *); +int run_complete(struct run *); +void evtag_marshal_run(struct evbuffer *, ev_uint32_t, + const struct run *); +int evtag_unmarshal_run(struct evbuffer *, ev_uint32_t, + struct run *); +int run_how_assign(struct run *, const char *); +int run_how_get(struct run *, char * *); +int run_some_bytes_assign(struct run *, const ev_uint8_t *, ev_uint32_t); +int run_some_bytes_get(struct run *, ev_uint8_t * *, ev_uint32_t *); +int run_fixed_bytes_assign(struct run *, const ev_uint8_t *); +int run_fixed_bytes_get(struct run *, ev_uint8_t **); +/* --- run done --- */ + +#endif /* ___REGRESS_RPC_ */ diff --git a/third_party/libevent/test/regress.h b/third_party/libevent/test/regress.h new file mode 100644 index 0000000000..4060ff5c6a --- /dev/null +++ b/third_party/libevent/test/regress.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2000-2004 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef _REGRESS_H_ +#define _REGRESS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +void http_suite(void); +void http_basic_test(void); + +void rpc_suite(void); + +void dns_suite(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _REGRESS_H_ */ diff --git a/third_party/libevent/test/regress.rpc b/third_party/libevent/test/regress.rpc new file mode 100644 index 0000000000..65ca95de4c --- /dev/null +++ b/third_party/libevent/test/regress.rpc @@ -0,0 +1,20 @@ +/* tests data packing and unpacking */ + +struct msg { + string from_name = 1; + string to_name = 2; + optional struct[kill] attack = 3; + array struct[run] run = 4; +} + +struct kill { + string weapon = 0x10121; + string action = 2; + optional int how_often = 3; +} + +struct run { + string how = 1; + optional bytes some_bytes = 2; + bytes fixed_bytes[24] = 3; +} diff --git a/third_party/libevent/test/regress_dns.c b/third_party/libevent/test/regress_dns.c new file mode 100644 index 0000000000..129cdad498 --- /dev/null +++ b/third_party/libevent/test/regress_dns.c @@ -0,0 +1,376 @@ +/* + * Copyright (c) 2003-2006 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef WIN32 +#include +#include +#endif + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif +#include +#ifndef WIN32 +#include +#include +#include +#include +#include +#endif +#ifdef HAVE_NETINET_IN6_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#include +#include +#include +#include +#include + +#include "event.h" +#include "evdns.h" +#include "log.h" + +static int dns_ok = 0; +static int dns_err = 0; + +void dns_suite(void); + +static void +dns_gethostbyname_cb(int result, char type, int count, int ttl, + void *addresses, void *arg) +{ + dns_ok = dns_err = 0; + + if (result == DNS_ERR_TIMEOUT) { + fprintf(stdout, "[Timed out] "); + dns_err = result; + goto out; + } + + if (result != DNS_ERR_NONE) { + fprintf(stdout, "[Error code %d] ", result); + goto out; + } + + fprintf(stderr, "type: %d, count: %d, ttl: %d: ", type, count, ttl); + + switch (type) { + case DNS_IPv6_AAAA: { +#if defined(HAVE_STRUCT_IN6_ADDR) && defined(HAVE_INET_NTOP) && defined(INET6_ADDRSTRLEN) + struct in6_addr *in6_addrs = addresses; + char buf[INET6_ADDRSTRLEN+1]; + int i; + /* a resolution that's not valid does not help */ + if (ttl < 0) + goto out; + for (i = 0; i < count; ++i) { + const char *b = inet_ntop(AF_INET6, &in6_addrs[i], buf,sizeof(buf)); + if (b) + fprintf(stderr, "%s ", b); + else + fprintf(stderr, "%s ", strerror(errno)); + } +#endif + break; + } + case DNS_IPv4_A: { + struct in_addr *in_addrs = addresses; + int i; + /* a resolution that's not valid does not help */ + if (ttl < 0) + goto out; + for (i = 0; i < count; ++i) + fprintf(stderr, "%s ", inet_ntoa(in_addrs[i])); + break; + } + case DNS_PTR: + /* may get at most one PTR */ + if (count != 1) + goto out; + + fprintf(stderr, "%s ", *(char **)addresses); + break; + default: + goto out; + } + + dns_ok = type; + +out: + event_loopexit(NULL); +} + +static void +dns_gethostbyname(void) +{ + fprintf(stdout, "Simple DNS resolve: "); + dns_ok = 0; + evdns_resolve_ipv4("www.monkey.org", 0, dns_gethostbyname_cb, NULL); + event_dispatch(); + + if (dns_ok == DNS_IPv4_A) { + fprintf(stdout, "OK\n"); + } else { + fprintf(stdout, "FAILED\n"); + exit(1); + } +} + +static void +dns_gethostbyname6(void) +{ + fprintf(stdout, "IPv6 DNS resolve: "); + dns_ok = 0; + evdns_resolve_ipv6("www.ietf.org", 0, dns_gethostbyname_cb, NULL); + event_dispatch(); + + if (dns_ok == DNS_IPv6_AAAA) { + fprintf(stdout, "OK\n"); + } else if (!dns_ok && dns_err == DNS_ERR_TIMEOUT) { + fprintf(stdout, "SKIPPED\n"); + } else { + fprintf(stdout, "FAILED (%d)\n", dns_ok); + exit(1); + } +} + +static void +dns_gethostbyaddr(void) +{ + struct in_addr in; + in.s_addr = htonl(0x7f000001ul); /* 127.0.0.1 */ + fprintf(stdout, "Simple reverse DNS resolve: "); + dns_ok = 0; + evdns_resolve_reverse(&in, 0, dns_gethostbyname_cb, NULL); + event_dispatch(); + + if (dns_ok == DNS_PTR) { + fprintf(stdout, "OK\n"); + } else { + fprintf(stdout, "FAILED\n"); + exit(1); + } +} + +static int n_server_responses = 0; + +static void +dns_server_request_cb(struct evdns_server_request *req, void *data) +{ + int i, r; + const char TEST_ARPA[] = "11.11.168.192.in-addr.arpa"; + for (i = 0; i < req->nquestions; ++i) { + struct in_addr ans; + ans.s_addr = htonl(0xc0a80b0bUL); /* 192.168.11.11 */ + if (req->questions[i]->type == EVDNS_TYPE_A && + req->questions[i]->dns_question_class == EVDNS_CLASS_INET && + !strcmp(req->questions[i]->name, "zz.example.com")) { + r = evdns_server_request_add_a_reply(req, "zz.example.com", + 1, &ans.s_addr, 12345); + if (r<0) + dns_ok = 0; + } else if (req->questions[i]->type == EVDNS_TYPE_AAAA && + req->questions[i]->dns_question_class == EVDNS_CLASS_INET && + !strcmp(req->questions[i]->name, "zz.example.com")) { + char addr6[17] = "abcdefghijklmnop"; + r = evdns_server_request_add_aaaa_reply(req, "zz.example.com", + 1, addr6, 123); + if (r<0) + dns_ok = 0; + } else if (req->questions[i]->type == EVDNS_TYPE_PTR && + req->questions[i]->dns_question_class == EVDNS_CLASS_INET && + !strcmp(req->questions[i]->name, TEST_ARPA)) { + r = evdns_server_request_add_ptr_reply(req, NULL, TEST_ARPA, + "ZZ.EXAMPLE.COM", 54321); + if (r<0) + dns_ok = 0; + } else { + fprintf(stdout, "Unexpected question %d %d \"%s\" ", + req->questions[i]->type, + req->questions[i]->dns_question_class, + req->questions[i]->name); + dns_ok = 0; + } + } + r = evdns_server_request_respond(req, 0); + if (r<0) { + fprintf(stdout, "Couldn't send reply. "); + dns_ok = 0; + } +} + +static void +dns_server_gethostbyname_cb(int result, char type, int count, int ttl, + void *addresses, void *arg) +{ + if (result != DNS_ERR_NONE) { + fprintf(stdout, "Unexpected result %d. ", result); + dns_ok = 0; + goto out; + } + if (count != 1) { + fprintf(stdout, "Unexpected answer count %d. ", count); + dns_ok = 0; + goto out; + } + switch (type) { + case DNS_IPv4_A: { + struct in_addr *in_addrs = addresses; + if (in_addrs[0].s_addr != htonl(0xc0a80b0bUL) || ttl != 12345) { + fprintf(stdout, "Bad IPv4 response \"%s\" %d. ", + inet_ntoa(in_addrs[0]), ttl); + dns_ok = 0; + goto out; + } + break; + } + case DNS_IPv6_AAAA: { +#if defined (HAVE_STRUCT_IN6_ADDR) && defined(HAVE_INET_NTOP) && defined(INET6_ADDRSTRLEN) + struct in6_addr *in6_addrs = addresses; + char buf[INET6_ADDRSTRLEN+1]; + if (memcmp(&in6_addrs[0].s6_addr, "abcdefghijklmnop", 16) + || ttl != 123) { + const char *b = inet_ntop(AF_INET6, &in6_addrs[0],buf,sizeof(buf)); + fprintf(stdout, "Bad IPv6 response \"%s\" %d. ", b, ttl); + dns_ok = 0; + goto out; + } +#endif + break; + } + case DNS_PTR: { + char **addrs = addresses; + if (strcmp(addrs[0], "ZZ.EXAMPLE.COM") || ttl != 54321) { + fprintf(stdout, "Bad PTR response \"%s\" %d. ", + addrs[0], ttl); + dns_ok = 0; + goto out; + } + break; + } + default: + fprintf(stdout, "Bad response type %d. ", type); + dns_ok = 0; + } + + out: + if (++n_server_responses == 3) { + event_loopexit(NULL); + } +} + +static void +dns_server(void) +{ + int sock; + struct sockaddr_in my_addr; + struct evdns_server_port *port; + struct in_addr resolve_addr; + + dns_ok = 1; + fprintf(stdout, "DNS server support: "); + + /* Add ourself as the only nameserver, and make sure we really are + * the only nameserver. */ + evdns_nameserver_ip_add("127.0.0.1:35353"); + if (evdns_count_nameservers() != 1) { + fprintf(stdout, "Couldn't set up.\n"); + exit(1); + } + + /* Now configure a nameserver port. */ + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock == -1) { + perror("socket"); + exit(1); + } +#ifdef WIN32 + { + u_long nonblocking = 1; + ioctlsocket(sock, FIONBIO, &nonblocking); + } +#else + fcntl(sock, F_SETFL, O_NONBLOCK); +#endif + memset(&my_addr, 0, sizeof(my_addr)); + my_addr.sin_family = AF_INET; + my_addr.sin_port = htons(35353); + my_addr.sin_addr.s_addr = htonl(0x7f000001UL); + if (bind(sock, (struct sockaddr*)&my_addr, sizeof(my_addr)) < 0) { + perror("bind"); + exit (1); + } + port = evdns_add_server_port(sock, 0, dns_server_request_cb, NULL); + + /* Send two queries. */ + evdns_resolve_ipv4("zz.example.com", DNS_QUERY_NO_SEARCH, + dns_server_gethostbyname_cb, NULL); + evdns_resolve_ipv6("zz.example.com", DNS_QUERY_NO_SEARCH, + dns_server_gethostbyname_cb, NULL); + resolve_addr.s_addr = htonl(0xc0a80b0bUL); /* 192.168.11.11 */ + evdns_resolve_reverse(&resolve_addr, 0, + dns_server_gethostbyname_cb, NULL); + + event_dispatch(); + + if (dns_ok) { + fprintf(stdout, "OK\n"); + } else { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + evdns_close_server_port(port); + evdns_shutdown(0); /* remove ourself as nameserver. */ +#ifdef WIN32 + closesocket(sock); +#else + close(sock); +#endif +} + +void +dns_suite(void) +{ + dns_server(); /* Do this before we call evdns_init. */ + + evdns_init(); + dns_gethostbyname(); + dns_gethostbyname6(); + dns_gethostbyaddr(); + + evdns_shutdown(0); +} diff --git a/third_party/libevent/test/regress_http.c b/third_party/libevent/test/regress_http.c new file mode 100644 index 0000000000..1e2a1eb062 --- /dev/null +++ b/third_party/libevent/test/regress_http.c @@ -0,0 +1,1476 @@ +/* + * Copyright (c) 2003-2006 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef WIN32 +#include +#include +#endif + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif +#include +#ifndef WIN32 +#include +#include +#include +#include +#endif +#include +#include +#include +#include +#include + +#include "event.h" +#include "evhttp.h" +#include "log.h" +#include "http-internal.h" + +extern int pair[]; +extern int test_ok; + +static struct evhttp *http; +/* set if a test needs to call loopexit on a base */ +static struct event_base *base; + +void http_suite(void); + +void http_basic_cb(struct evhttp_request *req, void *arg); +static void http_chunked_cb(struct evhttp_request *req, void *arg); +void http_post_cb(struct evhttp_request *req, void *arg); +void http_dispatcher_cb(struct evhttp_request *req, void *arg); +static void http_large_delay_cb(struct evhttp_request *req, void *arg); + +static struct evhttp * +http_setup(short *pport, struct event_base *base) +{ + int i; + struct evhttp *myhttp; + short port = -1; + + /* Try a few different ports */ + myhttp = evhttp_new(base); + for (i = 0; i < 50; ++i) { + if (evhttp_bind_socket(myhttp, "127.0.0.1", 8080 + i) != -1) { + port = 8080 + i; + break; + } + } + + if (port == -1) + event_errx(1, "Could not start web server"); + + /* Register a callback for certain types of requests */ + evhttp_set_cb(myhttp, "/test", http_basic_cb, NULL); + evhttp_set_cb(myhttp, "/chunked", http_chunked_cb, NULL); + evhttp_set_cb(myhttp, "/postit", http_post_cb, NULL); + evhttp_set_cb(myhttp, "/largedelay", http_large_delay_cb, NULL); + evhttp_set_cb(myhttp, "/", http_dispatcher_cb, NULL); + + *pport = port; + return (myhttp); +} + +#ifndef NI_MAXSERV +#define NI_MAXSERV 1024 +#endif + +static int +http_connect(const char *address, u_short port) +{ + /* Stupid code for connecting */ +#ifdef WIN32 + struct hostent *he; + struct sockaddr_in sin; +#else + struct addrinfo ai, *aitop; + char strport[NI_MAXSERV]; +#endif + struct sockaddr *sa; + int slen; + int fd; + +#ifdef WIN32 + if (!(he = gethostbyname(address))) { + event_warn("gethostbyname"); + } + memcpy(&sin.sin_addr, he->h_addr_list[0], he->h_length); + sin.sin_family = AF_INET; + sin.sin_port = htons(port); + slen = sizeof(struct sockaddr_in); + sa = (struct sockaddr*)&sin; +#else + memset(&ai, 0, sizeof (ai)); + ai.ai_family = AF_INET; + ai.ai_socktype = SOCK_STREAM; + snprintf(strport, sizeof (strport), "%d", port); + if (getaddrinfo(address, strport, &ai, &aitop) != 0) { + event_warn("getaddrinfo"); + return (-1); + } + sa = aitop->ai_addr; + slen = aitop->ai_addrlen; +#endif + + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd == -1) + event_err(1, "socket failed"); + + if (connect(fd, sa, slen) == -1) + event_err(1, "connect failed"); + +#ifndef WIN32 + freeaddrinfo(aitop); +#endif + + return (fd); +} + +static void +http_readcb(struct bufferevent *bev, void *arg) +{ + const char *what = "This is funny"; + + event_debug(("%s: %s\n", __func__, EVBUFFER_DATA(bev->input))); + + if (evbuffer_find(bev->input, + (const unsigned char*) what, strlen(what)) != NULL) { + struct evhttp_request *req = evhttp_request_new(NULL, NULL); + enum message_read_status done; + + req->kind = EVHTTP_RESPONSE; + done = evhttp_parse_firstline(req, bev->input); + if (done != ALL_DATA_READ) + goto out; + + done = evhttp_parse_headers(req, bev->input); + if (done != ALL_DATA_READ) + goto out; + + if (done == 1 && + evhttp_find_header(req->input_headers, + "Content-Type") != NULL) + test_ok++; + + out: + evhttp_request_free(req); + bufferevent_disable(bev, EV_READ); + if (base) + event_base_loopexit(base, NULL); + else + event_loopexit(NULL); + } +} + +static void +http_writecb(struct bufferevent *bev, void *arg) +{ + if (EVBUFFER_LENGTH(bev->output) == 0) { + /* enable reading of the reply */ + bufferevent_enable(bev, EV_READ); + test_ok++; + } +} + +static void +http_errorcb(struct bufferevent *bev, short what, void *arg) +{ + test_ok = -2; + event_loopexit(NULL); +} + +void +http_basic_cb(struct evhttp_request *req, void *arg) +{ + struct evbuffer *evb = evbuffer_new(); + int empty = evhttp_find_header(req->input_headers, "Empty") != NULL; + event_debug(("%s: called\n", __func__)); + evbuffer_add_printf(evb, "This is funny"); + + /* For multi-line headers test */ + { + const char *multi = + evhttp_find_header(req->input_headers,"X-multi"); + if (multi) { + if (strcmp("END", multi + strlen(multi) - 3) == 0) + test_ok++; + if (evhttp_find_header(req->input_headers, "X-Last")) + test_ok++; + } + } + + /* injecting a bad content-length */ + if (evhttp_find_header(req->input_headers, "X-Negative")) + evhttp_add_header(req->output_headers, + "Content-Length", "-100"); + + /* allow sending of an empty reply */ + evhttp_send_reply(req, HTTP_OK, "Everything is fine", + !empty ? evb : NULL); + + evbuffer_free(evb); +} + +static char const* const CHUNKS[] = { + "This is funny", + "but not hilarious.", + "bwv 1052" +}; + +struct chunk_req_state { + struct evhttp_request *req; + int i; +}; + +static void +http_chunked_trickle_cb(int fd, short events, void *arg) +{ + struct evbuffer *evb = evbuffer_new(); + struct chunk_req_state *state = arg; + struct timeval when = { 0, 0 }; + + evbuffer_add_printf(evb, "%s", CHUNKS[state->i]); + evhttp_send_reply_chunk(state->req, evb); + evbuffer_free(evb); + + if (++state->i < sizeof(CHUNKS)/sizeof(CHUNKS[0])) { + event_once(-1, EV_TIMEOUT, + http_chunked_trickle_cb, state, &when); + } else { + evhttp_send_reply_end(state->req); + free(state); + } +} + +static void +http_chunked_cb(struct evhttp_request *req, void *arg) +{ + struct timeval when = { 0, 0 }; + struct chunk_req_state *state = malloc(sizeof(struct chunk_req_state)); + event_debug(("%s: called\n", __func__)); + + memset(state, 0, sizeof(struct chunk_req_state)); + state->req = req; + + /* generate a chunked reply */ + evhttp_send_reply_start(req, HTTP_OK, "Everything is fine"); + + /* but trickle it across several iterations to ensure we're not + * assuming it comes all at once */ + event_once(-1, EV_TIMEOUT, http_chunked_trickle_cb, state, &when); +} + +static void +http_complete_write(int fd, short what, void *arg) +{ + struct bufferevent *bev = arg; + const char *http_request = "host\r\n" + "Connection: close\r\n" + "\r\n"; + bufferevent_write(bev, http_request, strlen(http_request)); +} + +static void +http_basic_test(void) +{ + struct timeval tv; + struct bufferevent *bev; + int fd; + const char *http_request; + short port = -1; + + test_ok = 0; + fprintf(stdout, "Testing Basic HTTP Server: "); + + http = http_setup(&port, NULL); + + /* bind to a second socket */ + if (evhttp_bind_socket(http, "127.0.0.1", port + 1) == -1) { + fprintf(stdout, "FAILED (bind)\n"); + exit(1); + } + + fd = http_connect("127.0.0.1", port); + + /* Stupid thing to send a request */ + bev = bufferevent_new(fd, http_readcb, http_writecb, + http_errorcb, NULL); + + /* first half of the http request */ + http_request = + "GET /test HTTP/1.1\r\n" + "Host: some"; + + bufferevent_write(bev, http_request, strlen(http_request)); + timerclear(&tv); + tv.tv_usec = 10000; + event_once(-1, EV_TIMEOUT, http_complete_write, bev, &tv); + + event_dispatch(); + + if (test_ok != 3) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + /* connect to the second port */ + bufferevent_free(bev); + EVUTIL_CLOSESOCKET(fd); + + fd = http_connect("127.0.0.1", port + 1); + + /* Stupid thing to send a request */ + bev = bufferevent_new(fd, http_readcb, http_writecb, + http_errorcb, NULL); + + http_request = + "GET /test HTTP/1.1\r\n" + "Host: somehost\r\n" + "Connection: close\r\n" + "\r\n"; + + bufferevent_write(bev, http_request, strlen(http_request)); + + event_dispatch(); + + bufferevent_free(bev); + EVUTIL_CLOSESOCKET(fd); + + evhttp_free(http); + + if (test_ok != 5) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + fprintf(stdout, "OK\n"); +} + +static struct evhttp_connection *delayed_client; + +static void +http_delay_reply(int fd, short what, void *arg) +{ + struct evhttp_request *req = arg; + + evhttp_send_reply(req, HTTP_OK, "Everything is fine", NULL); + + ++test_ok; +} + +static void +http_large_delay_cb(struct evhttp_request *req, void *arg) +{ + struct timeval tv; + timerclear(&tv); + tv.tv_sec = 3; + + event_once(-1, EV_TIMEOUT, http_delay_reply, req, &tv); + + /* here we close the client connection which will cause an EOF */ + evhttp_connection_fail(delayed_client, EVCON_HTTP_EOF); +} + +void http_request_done(struct evhttp_request *, void *); +void http_request_empty_done(struct evhttp_request *, void *); + +static void +http_connection_test(int persistent) +{ + short port = -1; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + + test_ok = 0; + fprintf(stdout, "Testing Request Connection Pipeline %s: ", + persistent ? "(persistent)" : ""); + + http = http_setup(&port, NULL); + + evcon = evhttp_connection_new("127.0.0.1", port); + if (evcon == NULL) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + /* + * At this point, we want to schedule a request to the HTTP + * server using our make request method. + */ + + req = evhttp_request_new(http_request_done, NULL); + + /* Add the information that we care about */ + evhttp_add_header(req->output_headers, "Host", "somehost"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + event_dispatch(); + + if (test_ok != 1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + /* try to make another request over the same connection */ + test_ok = 0; + + req = evhttp_request_new(http_request_done, NULL); + + /* Add the information that we care about */ + evhttp_add_header(req->output_headers, "Host", "somehost"); + + /* + * if our connections are not supposed to be persistent; request + * a close from the server. + */ + if (!persistent) + evhttp_add_header(req->output_headers, "Connection", "close"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + event_dispatch(); + + /* make another request: request empty reply */ + test_ok = 0; + + req = evhttp_request_new(http_request_empty_done, NULL); + + /* Add the information that we care about */ + evhttp_add_header(req->output_headers, "Empty", "itis"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + event_dispatch(); + + if (test_ok != 1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + evhttp_connection_free(evcon); + evhttp_free(http); + + fprintf(stdout, "OK\n"); +} + +void +http_request_done(struct evhttp_request *req, void *arg) +{ + const char *what = "This is funny"; + + if (req->response_code != HTTP_OK) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (evhttp_find_header(req->input_headers, "Content-Type") == NULL) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (EVBUFFER_LENGTH(req->input_buffer) != strlen(what)) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (memcmp(EVBUFFER_DATA(req->input_buffer), what, strlen(what)) != 0) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + test_ok = 1; + event_loopexit(NULL); +} + +/* test date header and content length */ + +void +http_request_empty_done(struct evhttp_request *req, void *arg) +{ + if (req->response_code != HTTP_OK) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (evhttp_find_header(req->input_headers, "Date") == NULL) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + + if (evhttp_find_header(req->input_headers, "Content-Length") == NULL) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (strcmp(evhttp_find_header(req->input_headers, "Content-Length"), + "0")) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (EVBUFFER_LENGTH(req->input_buffer) != 0) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + test_ok = 1; + event_loopexit(NULL); +} + +/* + * HTTP DISPATCHER test + */ + +void +http_dispatcher_cb(struct evhttp_request *req, void *arg) +{ + + struct evbuffer *evb = evbuffer_new(); + event_debug(("%s: called\n", __func__)); + evbuffer_add_printf(evb, "DISPATCHER_TEST"); + + evhttp_send_reply(req, HTTP_OK, "Everything is fine", evb); + + evbuffer_free(evb); +} + +static void +http_dispatcher_test_done(struct evhttp_request *req, void *arg) +{ + const char *what = "DISPATCHER_TEST"; + + if (req->response_code != HTTP_OK) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (evhttp_find_header(req->input_headers, "Content-Type") == NULL) { + fprintf(stderr, "FAILED (content type)\n"); + exit(1); + } + + if (EVBUFFER_LENGTH(req->input_buffer) != strlen(what)) { + fprintf(stderr, "FAILED (length %zu vs %zu)\n", + EVBUFFER_LENGTH(req->input_buffer), strlen(what)); + exit(1); + } + + if (memcmp(EVBUFFER_DATA(req->input_buffer), what, strlen(what)) != 0) { + fprintf(stderr, "FAILED (data)\n"); + exit(1); + } + + test_ok = 1; + event_loopexit(NULL); +} + +static void +http_dispatcher_test(void) +{ + short port = -1; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + + test_ok = 0; + fprintf(stdout, "Testing HTTP Dispatcher: "); + + http = http_setup(&port, NULL); + + evcon = evhttp_connection_new("127.0.0.1", port); + if (evcon == NULL) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + /* also bind to local host */ + evhttp_connection_set_local_address(evcon, "127.0.0.1"); + + /* + * At this point, we want to schedule an HTTP GET request + * server using our make request method. + */ + + req = evhttp_request_new(http_dispatcher_test_done, NULL); + if (req == NULL) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + /* Add the information that we care about */ + evhttp_add_header(req->output_headers, "Host", "somehost"); + + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/?arg=val") == -1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + event_dispatch(); + + evhttp_connection_free(evcon); + evhttp_free(http); + + if (test_ok != 1) { + fprintf(stdout, "FAILED: %d\n", test_ok); + exit(1); + } + + fprintf(stdout, "OK\n"); +} + +/* + * HTTP POST test. + */ + +void http_postrequest_done(struct evhttp_request *, void *); + +#define POST_DATA "Okay. Not really printf" + +static void +http_post_test(void) +{ + short port = -1; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + + test_ok = 0; + fprintf(stdout, "Testing HTTP POST Request: "); + + http = http_setup(&port, NULL); + + evcon = evhttp_connection_new("127.0.0.1", port); + if (evcon == NULL) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + /* + * At this point, we want to schedule an HTTP POST request + * server using our make request method. + */ + + req = evhttp_request_new(http_postrequest_done, NULL); + if (req == NULL) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + /* Add the information that we care about */ + evhttp_add_header(req->output_headers, "Host", "somehost"); + evbuffer_add_printf(req->output_buffer, POST_DATA); + + if (evhttp_make_request(evcon, req, EVHTTP_REQ_POST, "/postit") == -1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + event_dispatch(); + + evhttp_connection_free(evcon); + evhttp_free(http); + + if (test_ok != 1) { + fprintf(stdout, "FAILED: %d\n", test_ok); + exit(1); + } + + fprintf(stdout, "OK\n"); +} + +void +http_post_cb(struct evhttp_request *req, void *arg) +{ + struct evbuffer *evb; + event_debug(("%s: called\n", __func__)); + + /* Yes, we are expecting a post request */ + if (req->type != EVHTTP_REQ_POST) { + fprintf(stdout, "FAILED (post type)\n"); + exit(1); + } + + if (EVBUFFER_LENGTH(req->input_buffer) != strlen(POST_DATA)) { + fprintf(stdout, "FAILED (length: %zu vs %zu)\n", + EVBUFFER_LENGTH(req->input_buffer), strlen(POST_DATA)); + exit(1); + } + + if (memcmp(EVBUFFER_DATA(req->input_buffer), POST_DATA, + strlen(POST_DATA))) { + fprintf(stdout, "FAILED (data)\n"); + fprintf(stdout, "Got :%s\n", EVBUFFER_DATA(req->input_buffer)); + fprintf(stdout, "Want:%s\n", POST_DATA); + exit(1); + } + + evb = evbuffer_new(); + evbuffer_add_printf(evb, "This is funny"); + + evhttp_send_reply(req, HTTP_OK, "Everything is fine", evb); + + evbuffer_free(evb); +} + +void +http_postrequest_done(struct evhttp_request *req, void *arg) +{ + const char *what = "This is funny"; + + if (req == NULL) { + fprintf(stderr, "FAILED (timeout)\n"); + exit(1); + } + + if (req->response_code != HTTP_OK) { + + fprintf(stderr, "FAILED (response code)\n"); + exit(1); + } + + if (evhttp_find_header(req->input_headers, "Content-Type") == NULL) { + fprintf(stderr, "FAILED (content type)\n"); + exit(1); + } + + if (EVBUFFER_LENGTH(req->input_buffer) != strlen(what)) { + fprintf(stderr, "FAILED (length %zu vs %zu)\n", + EVBUFFER_LENGTH(req->input_buffer), strlen(what)); + exit(1); + } + + if (memcmp(EVBUFFER_DATA(req->input_buffer), what, strlen(what)) != 0) { + fprintf(stderr, "FAILED (data)\n"); + exit(1); + } + + test_ok = 1; + event_loopexit(NULL); +} + +static void +http_failure_readcb(struct bufferevent *bev, void *arg) +{ + const char *what = "400 Bad Request"; + if (evbuffer_find(bev->input, (const unsigned char*) what, strlen(what)) != NULL) { + test_ok = 2; + bufferevent_disable(bev, EV_READ); + event_loopexit(NULL); + } +} + +/* + * Testing that the HTTP server can deal with a malformed request. + */ +static void +http_failure_test(void) +{ + struct bufferevent *bev; + int fd; + const char *http_request; + short port = -1; + + test_ok = 0; + fprintf(stdout, "Testing Bad HTTP Request: "); + + http = http_setup(&port, NULL); + + fd = http_connect("127.0.0.1", port); + + /* Stupid thing to send a request */ + bev = bufferevent_new(fd, http_failure_readcb, http_writecb, + http_errorcb, NULL); + + http_request = "illegal request\r\n"; + + bufferevent_write(bev, http_request, strlen(http_request)); + + event_dispatch(); + + bufferevent_free(bev); + EVUTIL_CLOSESOCKET(fd); + + evhttp_free(http); + + if (test_ok != 2) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + fprintf(stdout, "OK\n"); +} + +static void +close_detect_done(struct evhttp_request *req, void *arg) +{ + struct timeval tv; + if (req == NULL || req->response_code != HTTP_OK) { + + fprintf(stderr, "FAILED\n"); + exit(1); + } + + test_ok = 1; + + timerclear(&tv); + tv.tv_sec = 3; /* longer than the http time out */ + + event_loopexit(&tv); +} + +static void +close_detect_launch(int fd, short what, void *arg) +{ + struct evhttp_connection *evcon = arg; + struct evhttp_request *req; + + req = evhttp_request_new(close_detect_done, NULL); + + /* Add the information that we care about */ + evhttp_add_header(req->output_headers, "Host", "somehost"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } +} + +static void +close_detect_cb(struct evhttp_request *req, void *arg) +{ + struct evhttp_connection *evcon = arg; + struct timeval tv; + + if (req != NULL && req->response_code != HTTP_OK) { + + fprintf(stderr, "FAILED\n"); + exit(1); + } + + timerclear(&tv); + tv.tv_sec = 3; /* longer than the http time out */ + + /* launch a new request on the persistent connection in 6 seconds */ + event_once(-1, EV_TIMEOUT, close_detect_launch, evcon, &tv); +} + + +static void +http_close_detection(int with_delay) +{ + short port = -1; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + + test_ok = 0; + fprintf(stdout, "Testing Connection Close Detection%s: ", + with_delay ? " (with delay)" : ""); + + http = http_setup(&port, NULL); + + /* 2 second timeout */ + evhttp_set_timeout(http, 2); + + evcon = evhttp_connection_new("127.0.0.1", port); + if (evcon == NULL) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + delayed_client = evcon; + + /* + * At this point, we want to schedule a request to the HTTP + * server using our make request method. + */ + + req = evhttp_request_new(close_detect_cb, evcon); + + /* Add the information that we care about */ + evhttp_add_header(req->output_headers, "Host", "somehost"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, + req, EVHTTP_REQ_GET, with_delay ? "/largedelay" : "/test") == -1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + event_dispatch(); + + if (test_ok != 1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + /* at this point, the http server should have no connection */ + if (TAILQ_FIRST(&http->connections) != NULL) { + fprintf(stdout, "FAILED (left connections)\n"); + exit(1); + } + + evhttp_connection_free(evcon); + evhttp_free(http); + + fprintf(stdout, "OK\n"); +} + +static void +http_highport_test(void) +{ + int i = -1; + struct evhttp *myhttp = NULL; + + fprintf(stdout, "Testing HTTP Server with high port: "); + + /* Try a few different ports */ + for (i = 0; i < 50; ++i) { + myhttp = evhttp_start("127.0.0.1", 65535 - i); + if (myhttp != NULL) { + fprintf(stdout, "OK\n"); + evhttp_free(myhttp); + return; + } + } + + fprintf(stdout, "FAILED\n"); + exit(1); +} + +static void +http_bad_header_test(void) +{ + struct evkeyvalq headers; + + fprintf(stdout, "Testing HTTP Header filtering: "); + + TAILQ_INIT(&headers); + + if (evhttp_add_header(&headers, "One", "Two") != 0) + goto fail; + + if (evhttp_add_header(&headers, "One\r", "Two") != -1) + goto fail; + if (evhttp_add_header(&headers, "One", "Two") != 0) + goto fail; + if (evhttp_add_header(&headers, "One", "Two\r\n Three") != 0) + goto fail; + if (evhttp_add_header(&headers, "One\r", "Two") != -1) + goto fail; + if (evhttp_add_header(&headers, "One\n", "Two") != -1) + goto fail; + if (evhttp_add_header(&headers, "One", "Two\r") != -1) + goto fail; + if (evhttp_add_header(&headers, "One", "Two\n") != -1) + goto fail; + + evhttp_clear_headers(&headers); + + fprintf(stdout, "OK\n"); + return; +fail: + fprintf(stdout, "FAILED\n"); + exit(1); +} + +static int validate_header( + const struct evkeyvalq* headers, + const char *key, const char *value) +{ + const char *real_val = evhttp_find_header(headers, key); + if (real_val == NULL) + return (-1); + if (strcmp(real_val, value) != 0) + return (-1); + return (0); +} + +static void +http_parse_query_test(void) +{ + struct evkeyvalq headers; + + fprintf(stdout, "Testing HTTP query parsing: "); + + TAILQ_INIT(&headers); + + evhttp_parse_query("http://www.test.com/?q=test", &headers); + if (validate_header(&headers, "q", "test") != 0) + goto fail; + evhttp_clear_headers(&headers); + + evhttp_parse_query("http://www.test.com/?q=test&foo=bar", &headers); + if (validate_header(&headers, "q", "test") != 0) + goto fail; + if (validate_header(&headers, "foo", "bar") != 0) + goto fail; + evhttp_clear_headers(&headers); + + evhttp_parse_query("http://www.test.com/?q=test+foo", &headers); + if (validate_header(&headers, "q", "test foo") != 0) + goto fail; + evhttp_clear_headers(&headers); + + evhttp_parse_query("http://www.test.com/?q=test%0Afoo", &headers); + if (validate_header(&headers, "q", "test\nfoo") != 0) + goto fail; + evhttp_clear_headers(&headers); + + evhttp_parse_query("http://www.test.com/?q=test%0Dfoo", &headers); + if (validate_header(&headers, "q", "test\rfoo") != 0) + goto fail; + evhttp_clear_headers(&headers); + + fprintf(stdout, "OK\n"); + return; +fail: + fprintf(stdout, "FAILED\n"); + exit(1); +} + +static void +http_base_test(void) +{ + struct bufferevent *bev; + int fd; + const char *http_request; + short port = -1; + + test_ok = 0; + fprintf(stdout, "Testing HTTP Server Event Base: "); + + base = event_init(); + + /* + * create another bogus base - which is being used by all subsequen + * tests - yuck! + */ + event_init(); + + http = http_setup(&port, base); + + fd = http_connect("127.0.0.1", port); + + /* Stupid thing to send a request */ + bev = bufferevent_new(fd, http_readcb, http_writecb, + http_errorcb, NULL); + bufferevent_base_set(base, bev); + + http_request = + "GET /test HTTP/1.1\r\n" + "Host: somehost\r\n" + "Connection: close\r\n" + "\r\n"; + + bufferevent_write(bev, http_request, strlen(http_request)); + + event_base_dispatch(base); + + bufferevent_free(bev); + EVUTIL_CLOSESOCKET(fd); + + evhttp_free(http); + + event_base_free(base); + base = NULL; + + if (test_ok != 2) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + fprintf(stdout, "OK\n"); +} + +/* + * the server is going to reply with chunked data. + */ + +static void +http_chunked_readcb(struct bufferevent *bev, void *arg) +{ + /* nothing here */ +} + +static void +http_chunked_errorcb(struct bufferevent *bev, short what, void *arg) +{ + if (!test_ok) + goto out; + + test_ok = -1; + + if ((what & EVBUFFER_EOF) != 0) { + struct evhttp_request *req = evhttp_request_new(NULL, NULL); + const char *header; + enum message_read_status done; + + req->kind = EVHTTP_RESPONSE; + done = evhttp_parse_firstline(req, EVBUFFER_INPUT(bev)); + if (done != ALL_DATA_READ) + goto out; + + done = evhttp_parse_headers(req, EVBUFFER_INPUT(bev)); + if (done != ALL_DATA_READ) + goto out; + + header = evhttp_find_header(req->input_headers, "Transfer-Encoding"); + if (header == NULL || strcmp(header, "chunked")) + goto out; + + header = evhttp_find_header(req->input_headers, "Connection"); + if (header == NULL || strcmp(header, "close")) + goto out; + + header = evbuffer_readline(EVBUFFER_INPUT(bev)); + if (header == NULL) + goto out; + /* 13 chars */ + if (strcmp(header, "d")) + goto out; + free((char*)header); + + if (strncmp((char *)EVBUFFER_DATA(EVBUFFER_INPUT(bev)), + "This is funny", 13)) + goto out; + + evbuffer_drain(EVBUFFER_INPUT(bev), 13 + 2); + + header = evbuffer_readline(EVBUFFER_INPUT(bev)); + if (header == NULL) + goto out; + /* 18 chars */ + if (strcmp(header, "12")) + goto out; + free((char *)header); + + if (strncmp((char *)EVBUFFER_DATA(EVBUFFER_INPUT(bev)), + "but not hilarious.", 18)) + goto out; + + evbuffer_drain(EVBUFFER_INPUT(bev), 18 + 2); + + header = evbuffer_readline(EVBUFFER_INPUT(bev)); + if (header == NULL) + goto out; + /* 8 chars */ + if (strcmp(header, "8")) + goto out; + free((char *)header); + + if (strncmp((char *)EVBUFFER_DATA(EVBUFFER_INPUT(bev)), + "bwv 1052.", 8)) + goto out; + + evbuffer_drain(EVBUFFER_INPUT(bev), 8 + 2); + + header = evbuffer_readline(EVBUFFER_INPUT(bev)); + if (header == NULL) + goto out; + /* 0 chars */ + if (strcmp(header, "0")) + goto out; + free((char *)header); + + test_ok = 2; + } + +out: + event_loopexit(NULL); +} + +static void +http_chunked_writecb(struct bufferevent *bev, void *arg) +{ + if (EVBUFFER_LENGTH(EVBUFFER_OUTPUT(bev)) == 0) { + /* enable reading of the reply */ + bufferevent_enable(bev, EV_READ); + test_ok++; + } +} + +static void +http_chunked_request_done(struct evhttp_request *req, void *arg) +{ + if (req->response_code != HTTP_OK) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (evhttp_find_header(req->input_headers, + "Transfer-Encoding") == NULL) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (EVBUFFER_LENGTH(req->input_buffer) != 13 + 18 + 8) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (strncmp((char *)EVBUFFER_DATA(req->input_buffer), + "This is funnybut not hilarious.bwv 1052", + 13 + 18 + 8)) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + test_ok = 1; + event_loopexit(NULL); +} + +static void +http_chunked_test(void) +{ + struct bufferevent *bev; + int fd; + const char *http_request; + short port = -1; + struct timeval tv_start, tv_end; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + int i; + + test_ok = 0; + fprintf(stdout, "Testing Chunked HTTP Reply: "); + + http = http_setup(&port, NULL); + + fd = http_connect("127.0.0.1", port); + + /* Stupid thing to send a request */ + bev = bufferevent_new(fd, + http_chunked_readcb, http_chunked_writecb, + http_chunked_errorcb, NULL); + + http_request = + "GET /chunked HTTP/1.1\r\n" + "Host: somehost\r\n" + "Connection: close\r\n" + "\r\n"; + + bufferevent_write(bev, http_request, strlen(http_request)); + + evutil_gettimeofday(&tv_start, NULL); + + event_dispatch(); + + evutil_gettimeofday(&tv_end, NULL); + evutil_timersub(&tv_end, &tv_start, &tv_end); + + if (tv_end.tv_sec >= 1) { + fprintf(stdout, "FAILED (time)\n"); + exit (1); + } + + + if (test_ok != 2) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + /* now try again with the regular connection object */ + evcon = evhttp_connection_new("127.0.0.1", port); + if (evcon == NULL) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + /* make two requests to check the keepalive behavior */ + for (i = 0; i < 2; i++) { + test_ok = 0; + req = evhttp_request_new(http_chunked_request_done, NULL); + + /* Add the information that we care about */ + evhttp_add_header(req->output_headers, "Host", "somehost"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, + EVHTTP_REQ_GET, "/chunked") == -1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + event_dispatch(); + + if (test_ok != 1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + } + + evhttp_connection_free(evcon); + evhttp_free(http); + + fprintf(stdout, "OK\n"); +} + +static void +http_multi_line_header_test(void) +{ + struct bufferevent *bev; + int fd; + const char *http_start_request; + short port = -1; + + test_ok = 0; + fprintf(stdout, "Testing HTTP Server with multi line: "); + + http = http_setup(&port, NULL); + + fd = http_connect("127.0.0.1", port); + + /* Stupid thing to send a request */ + bev = bufferevent_new(fd, http_readcb, http_writecb, + http_errorcb, NULL); + + http_start_request = + "GET /test HTTP/1.1\r\n" + "Host: somehost\r\n" + "Connection: close\r\n" + "X-Multi: aaaaaaaa\r\n" + " a\r\n" + "\tEND\r\n" + "X-Last: last\r\n" + "\r\n"; + + bufferevent_write(bev, http_start_request, strlen(http_start_request)); + + event_dispatch(); + + bufferevent_free(bev); + EVUTIL_CLOSESOCKET(fd); + + evhttp_free(http); + + if (test_ok != 4) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + fprintf(stdout, "OK\n"); +} + +static void +http_request_bad(struct evhttp_request *req, void *arg) +{ + if (req != NULL) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + test_ok = 1; + event_loopexit(NULL); +} + +static void +http_negative_content_length_test(void) +{ + short port = -1; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + + test_ok = 0; + fprintf(stdout, "Testing HTTP Negative Content Length: "); + + http = http_setup(&port, NULL); + + evcon = evhttp_connection_new("127.0.0.1", port); + if (evcon == NULL) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + /* + * At this point, we want to schedule a request to the HTTP + * server using our make request method. + */ + + req = evhttp_request_new(http_request_bad, NULL); + + /* Cause the response to have a negative content-length */ + evhttp_add_header(req->output_headers, "X-Negative", "makeitso"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + event_dispatch(); + + evhttp_free(http); + + if (test_ok != 1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + fprintf(stdout, "OK\n"); +} + +void +http_suite(void) +{ + http_base_test(); + http_bad_header_test(); + http_parse_query_test(); + http_basic_test(); + http_connection_test(0 /* not-persistent */); + http_connection_test(1 /* persistent */); + http_close_detection(0 /* with delay */); + http_close_detection(1 /* with delay */); + http_post_test(); + http_failure_test(); + http_highport_test(); + http_dispatcher_test(); + + http_multi_line_header_test(); + http_negative_content_length_test(); + + http_chunked_test(); +} diff --git a/third_party/libevent/test/regress_rpc.c b/third_party/libevent/test/regress_rpc.c new file mode 100644 index 0000000000..760934766a --- /dev/null +++ b/third_party/libevent/test/regress_rpc.c @@ -0,0 +1,631 @@ +/* + * Copyright (c) 2003-2006 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef WIN32 +#include +#include +#endif + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif +#include +#ifndef WIN32 +#include +#include +#include +#include +#endif +#include +#include +#include +#include +#include +#include + +#include "event.h" +#include "evhttp.h" +#include "log.h" +#include "evrpc.h" + +#include "regress.gen.h" + +void rpc_suite(void); + +extern int test_ok; + +static struct evhttp * +http_setup(short *pport) +{ + int i; + struct evhttp *myhttp; + short port = -1; + + /* Try a few different ports */ + for (i = 0; i < 50; ++i) { + myhttp = evhttp_start("127.0.0.1", 8080 + i); + if (myhttp != NULL) { + port = 8080 + i; + break; + } + } + + if (port == -1) + event_errx(1, "Could not start web server"); + + *pport = port; + return (myhttp); +} + +EVRPC_HEADER(Message, msg, kill); +EVRPC_HEADER(NeverReply, msg, kill); + +EVRPC_GENERATE(Message, msg, kill); +EVRPC_GENERATE(NeverReply, msg, kill); + +static int need_input_hook = 0; +static int need_output_hook = 0; + +static void +MessageCb(EVRPC_STRUCT(Message)* rpc, void *arg) +{ + struct kill* kill_reply = rpc->reply; + + if (need_input_hook) { + struct evhttp_request* req = EVRPC_REQUEST_HTTP(rpc); + const char *header = evhttp_find_header( + req->input_headers, "X-Hook"); + assert(strcmp(header, "input") == 0); + } + + /* we just want to fill in some non-sense */ + EVTAG_ASSIGN(kill_reply, weapon, "dagger"); + EVTAG_ASSIGN(kill_reply, action, "wave around like an idiot"); + + /* no reply to the RPC */ + EVRPC_REQUEST_DONE(rpc); +} + +static EVRPC_STRUCT(NeverReply) *saved_rpc; + +static void +NeverReplyCb(EVRPC_STRUCT(NeverReply)* rpc, void *arg) +{ + test_ok += 1; + saved_rpc = rpc; +} + +static void +rpc_setup(struct evhttp **phttp, short *pport, struct evrpc_base **pbase) +{ + short port; + struct evhttp *http = NULL; + struct evrpc_base *base = NULL; + + http = http_setup(&port); + base = evrpc_init(http); + + EVRPC_REGISTER(base, Message, msg, kill, MessageCb, NULL); + EVRPC_REGISTER(base, NeverReply, msg, kill, NeverReplyCb, NULL); + + *phttp = http; + *pport = port; + *pbase = base; + + need_input_hook = 0; + need_output_hook = 0; +} + +static void +rpc_teardown(struct evrpc_base *base) +{ + assert(EVRPC_UNREGISTER(base, Message) == 0); + assert(EVRPC_UNREGISTER(base, NeverReply) == 0); + + evrpc_free(base); +} + +static void +rpc_postrequest_failure(struct evhttp_request *req, void *arg) +{ + if (req->response_code != HTTP_SERVUNAVAIL) { + + fprintf(stderr, "FAILED (response code)\n"); + exit(1); + } + + test_ok = 1; + event_loopexit(NULL); +} + +/* + * Test a malformed payload submitted as an RPC + */ + +static void +rpc_basic_test(void) +{ + short port; + struct evhttp *http = NULL; + struct evrpc_base *base = NULL; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + + fprintf(stdout, "Testing Basic RPC Support: "); + + rpc_setup(&http, &port, &base); + + evcon = evhttp_connection_new("127.0.0.1", port); + if (evcon == NULL) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + /* + * At this point, we want to schedule an HTTP POST request + * server using our make request method. + */ + + req = evhttp_request_new(rpc_postrequest_failure, NULL); + if (req == NULL) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + /* Add the information that we care about */ + evhttp_add_header(req->output_headers, "Host", "somehost"); + evbuffer_add_printf(req->output_buffer, "Some Nonsense"); + + if (evhttp_make_request(evcon, req, + EVHTTP_REQ_POST, + "/.rpc.Message") == -1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + test_ok = 0; + + event_dispatch(); + + evhttp_connection_free(evcon); + + rpc_teardown(base); + + if (test_ok != 1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + fprintf(stdout, "OK\n"); + + evhttp_free(http); +} + +static void +rpc_postrequest_done(struct evhttp_request *req, void *arg) +{ + struct kill* kill_reply = NULL; + + if (req->response_code != HTTP_OK) { + + fprintf(stderr, "FAILED (response code)\n"); + exit(1); + } + + kill_reply = kill_new(); + + if ((kill_unmarshal(kill_reply, req->input_buffer)) == -1) { + fprintf(stderr, "FAILED (unmarshal)\n"); + exit(1); + } + + kill_free(kill_reply); + + test_ok = 1; + event_loopexit(NULL); +} + +static void +rpc_basic_message(void) +{ + short port; + struct evhttp *http = NULL; + struct evrpc_base *base = NULL; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + struct msg *msg; + + fprintf(stdout, "Testing Good RPC Post: "); + + rpc_setup(&http, &port, &base); + + evcon = evhttp_connection_new("127.0.0.1", port); + if (evcon == NULL) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + /* + * At this point, we want to schedule an HTTP POST request + * server using our make request method. + */ + + req = evhttp_request_new(rpc_postrequest_done, NULL); + if (req == NULL) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + /* Add the information that we care about */ + evhttp_add_header(req->output_headers, "Host", "somehost"); + + /* set up the basic message */ + msg = msg_new(); + EVTAG_ASSIGN(msg, from_name, "niels"); + EVTAG_ASSIGN(msg, to_name, "tester"); + msg_marshal(req->output_buffer, msg); + msg_free(msg); + + if (evhttp_make_request(evcon, req, + EVHTTP_REQ_POST, + "/.rpc.Message") == -1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + test_ok = 0; + + event_dispatch(); + + evhttp_connection_free(evcon); + + rpc_teardown(base); + + if (test_ok != 1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + fprintf(stdout, "OK\n"); + + evhttp_free(http); +} + +static struct evrpc_pool * +rpc_pool_with_connection(short port) +{ + struct evhttp_connection *evcon; + struct evrpc_pool *pool; + + pool = evrpc_pool_new(NULL); + assert(pool != NULL); + + evcon = evhttp_connection_new("127.0.0.1", port); + assert(evcon != NULL); + + evrpc_pool_add_connection(pool, evcon); + + return (pool); +} + +static void +GotKillCb(struct evrpc_status *status, + struct msg *msg, struct kill *kill, void *arg) +{ + char *weapon; + char *action; + + if (need_output_hook) { + struct evhttp_request *req = status->http_req; + const char *header = evhttp_find_header( + req->input_headers, "X-Pool-Hook"); + assert(strcmp(header, "ran") == 0); + } + + if (status->error != EVRPC_STATUS_ERR_NONE) + goto done; + + if (EVTAG_GET(kill, weapon, &weapon) == -1) { + fprintf(stderr, "get weapon\n"); + goto done; + } + if (EVTAG_GET(kill, action, &action) == -1) { + fprintf(stderr, "get action\n"); + goto done; + } + + if (strcmp(weapon, "dagger")) + goto done; + + if (strcmp(action, "wave around like an idiot")) + goto done; + + test_ok += 1; + +done: + event_loopexit(NULL); +} + +static void +GotKillCbTwo(struct evrpc_status *status, + struct msg *msg, struct kill *kill, void *arg) +{ + char *weapon; + char *action; + + if (status->error != EVRPC_STATUS_ERR_NONE) + goto done; + + if (EVTAG_GET(kill, weapon, &weapon) == -1) { + fprintf(stderr, "get weapon\n"); + goto done; + } + if (EVTAG_GET(kill, action, &action) == -1) { + fprintf(stderr, "get action\n"); + goto done; + } + + if (strcmp(weapon, "dagger")) + goto done; + + if (strcmp(action, "wave around like an idiot")) + goto done; + + test_ok += 1; + +done: + if (test_ok == 2) + event_loopexit(NULL); +} + +static int +rpc_hook_add_header(struct evhttp_request *req, + struct evbuffer *evbuf, void *arg) +{ + const char *hook_type = arg; + if (strcmp("input", hook_type) == 0) + evhttp_add_header(req->input_headers, "X-Hook", hook_type); + else + evhttp_add_header(req->output_headers, "X-Hook", hook_type); + return (0); +} + +static int +rpc_hook_remove_header(struct evhttp_request *req, + struct evbuffer *evbuf, void *arg) +{ + const char *header = evhttp_find_header(req->input_headers, "X-Hook"); + assert(header != NULL); + assert(strcmp(header, arg) == 0); + evhttp_remove_header(req->input_headers, "X-Hook"); + evhttp_add_header(req->input_headers, "X-Pool-Hook", "ran"); + + return (0); +} + +static void +rpc_basic_client(void) +{ + short port; + struct evhttp *http = NULL; + struct evrpc_base *base = NULL; + struct evrpc_pool *pool = NULL; + struct msg *msg; + struct kill *kill; + + fprintf(stdout, "Testing RPC Client: "); + + rpc_setup(&http, &port, &base); + + need_input_hook = 1; + need_output_hook = 1; + + assert(evrpc_add_hook(base, EVRPC_INPUT, rpc_hook_add_header, (void*)"input") + != NULL); + assert(evrpc_add_hook(base, EVRPC_OUTPUT, rpc_hook_add_header, (void*)"output") + != NULL); + + pool = rpc_pool_with_connection(port); + + assert(evrpc_add_hook(pool, EVRPC_INPUT, rpc_hook_remove_header, (void*)"output")); + + /* set up the basic message */ + msg = msg_new(); + EVTAG_ASSIGN(msg, from_name, "niels"); + EVTAG_ASSIGN(msg, to_name, "tester"); + + kill = kill_new(); + + EVRPC_MAKE_REQUEST(Message, pool, msg, kill, GotKillCb, NULL); + + test_ok = 0; + + event_dispatch(); + + if (test_ok != 1) { + fprintf(stdout, "FAILED (1)\n"); + exit(1); + } + + /* we do it twice to make sure that reuse works correctly */ + kill_clear(kill); + + EVRPC_MAKE_REQUEST(Message, pool, msg, kill, GotKillCb, NULL); + + event_dispatch(); + + rpc_teardown(base); + + if (test_ok != 2) { + fprintf(stdout, "FAILED (2)\n"); + exit(1); + } + + fprintf(stdout, "OK\n"); + + msg_free(msg); + kill_free(kill); + + evrpc_pool_free(pool); + evhttp_free(http); +} + +/* + * We are testing that the second requests gets send over the same + * connection after the first RPCs completes. + */ +static void +rpc_basic_queued_client(void) +{ + short port; + struct evhttp *http = NULL; + struct evrpc_base *base = NULL; + struct evrpc_pool *pool = NULL; + struct msg *msg; + struct kill *kill_one, *kill_two; + + fprintf(stdout, "Testing RPC (Queued) Client: "); + + rpc_setup(&http, &port, &base); + + pool = rpc_pool_with_connection(port); + + /* set up the basic message */ + msg = msg_new(); + EVTAG_ASSIGN(msg, from_name, "niels"); + EVTAG_ASSIGN(msg, to_name, "tester"); + + kill_one = kill_new(); + kill_two = kill_new(); + + EVRPC_MAKE_REQUEST(Message, pool, msg, kill_one, GotKillCbTwo, NULL); + EVRPC_MAKE_REQUEST(Message, pool, msg, kill_two, GotKillCb, NULL); + + test_ok = 0; + + event_dispatch(); + + rpc_teardown(base); + + if (test_ok != 2) { + fprintf(stdout, "FAILED (1)\n"); + exit(1); + } + + fprintf(stdout, "OK\n"); + + msg_free(msg); + kill_free(kill_one); + kill_free(kill_two); + + evrpc_pool_free(pool); + evhttp_free(http); +} + +static void +GotErrorCb(struct evrpc_status *status, + struct msg *msg, struct kill *kill, void *arg) +{ + if (status->error != EVRPC_STATUS_ERR_TIMEOUT) + goto done; + + /* should never be complete but just to check */ + if (kill_complete(kill) == 0) + goto done; + + test_ok += 1; + +done: + event_loopexit(NULL); +} + +static void +rpc_client_timeout(void) +{ + short port; + struct evhttp *http = NULL; + struct evrpc_base *base = NULL; + struct evrpc_pool *pool = NULL; + struct msg *msg; + struct kill *kill; + + fprintf(stdout, "Testing RPC Client Timeout: "); + + rpc_setup(&http, &port, &base); + + pool = rpc_pool_with_connection(port); + + /* set the timeout to 5 seconds */ + evrpc_pool_set_timeout(pool, 5); + + /* set up the basic message */ + msg = msg_new(); + EVTAG_ASSIGN(msg, from_name, "niels"); + EVTAG_ASSIGN(msg, to_name, "tester"); + + kill = kill_new(); + + EVRPC_MAKE_REQUEST(NeverReply, pool, msg, kill, GotErrorCb, NULL); + + test_ok = 0; + + event_dispatch(); + + /* free the saved RPC structure up */ + EVRPC_REQUEST_DONE(saved_rpc); + + rpc_teardown(base); + + if (test_ok != 2) { + fprintf(stdout, "FAILED (1)\n"); + exit(1); + } + + fprintf(stdout, "OK\n"); + + msg_free(msg); + kill_free(kill); + + evrpc_pool_free(pool); + evhttp_free(http); +} + +void +rpc_suite(void) +{ + rpc_basic_test(); + rpc_basic_message(); + rpc_basic_client(); + rpc_basic_queued_client(); + rpc_client_timeout(); +} diff --git a/third_party/libevent/test/test-eof.c b/third_party/libevent/test/test-eof.c new file mode 100644 index 0000000000..3264a7b7ce --- /dev/null +++ b/third_party/libevent/test/test-eof.c @@ -0,0 +1,86 @@ +/* + * Compile with: + * cc -I/usr/local/include -o time-test time-test.c -L/usr/local/lib -levent + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + + +#ifdef WIN32 +#include +#endif +#include +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif +#ifdef HAVE_SYS_SOCKET_H +#include +#endif +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#include + +#include +#include + +int test_okay = 1; +int called = 0; + +static void +read_cb(int fd, short event, void *arg) +{ + char buf[256]; + int len; + + len = recv(fd, buf, sizeof(buf), 0); + + printf("%s: read %d%s\n", __func__, + len, len ? "" : " - means EOF"); + + if (len) { + if (!called) + event_add(arg, NULL); + } else if (called == 1) + test_okay = 0; + + called++; +} + +#ifndef SHUT_WR +#define SHUT_WR 1 +#endif + +int +main (int argc, char **argv) +{ + struct event ev; + const char *test = "test string"; + int pair[2]; + + if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1) + return (1); + + + send(pair[0], test, strlen(test)+1, 0); + shutdown(pair[0], SHUT_WR); + + /* Initalize the event library */ + event_init(); + + /* Initalize one event */ + event_set(&ev, pair[1], EV_READ, read_cb, &ev); + + event_add(&ev, NULL); + + event_dispatch(); + + return (test_okay); +} + diff --git a/third_party/libevent/test/test-init.c b/third_party/libevent/test/test-init.c new file mode 100644 index 0000000000..d60aa36bd5 --- /dev/null +++ b/third_party/libevent/test/test-init.c @@ -0,0 +1,40 @@ +/* + * Compile with: + * cc -I/usr/local/include -o time-test time-test.c -L/usr/local/lib -levent + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef WIN32 +#include +#endif + +#include +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif +#ifdef HAVE_SYS_SOCKET_H +#include +#endif +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#include + +#include + +int +main(int argc, char **argv) +{ + /* Initalize the event library */ + event_init(); + + return (0); +} + diff --git a/third_party/libevent/test/test-time.c b/third_party/libevent/test/test-time.c new file mode 100644 index 0000000000..703bc32b57 --- /dev/null +++ b/third_party/libevent/test/test-time.c @@ -0,0 +1,89 @@ +/* + * Compile with: + * cc -I/usr/local/include -o time-test time-test.c -L/usr/local/lib -levent + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef WIN32 +#include +#endif + +#include +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#include + +#include + +int called = 0; + +#define NEVENT 20000 + +struct event *ev[NEVENT]; + +static int +rand_int(int n) +{ +#ifdef WIN32 + return (int)(rand() * n); +#else + return (int)(random() % n); +#endif +} + +static void +time_cb(int fd, short event, void *arg) +{ + struct timeval tv; + int i, j; + + called++; + + if (called < 10*NEVENT) { + for (i = 0; i < 10; i++) { + j = rand_int(NEVENT); + tv.tv_sec = 0; + tv.tv_usec = rand_int(50000); + if (tv.tv_usec % 2) + evtimer_add(ev[j], &tv); + else + evtimer_del(ev[j]); + } + } +} + +int +main (int argc, char **argv) +{ + struct timeval tv; + int i; + + /* Initalize the event library */ + event_init(); + + for (i = 0; i < NEVENT; i++) { + ev[i] = malloc(sizeof(struct event)); + + /* Initalize one event */ + evtimer_set(ev[i], time_cb, ev[i]); + tv.tv_sec = 0; + tv.tv_usec = rand_int(50000); + evtimer_add(ev[i], &tv); + } + + event_dispatch(); + + return (called < NEVENT); +} + diff --git a/third_party/libevent/test/test-weof.c b/third_party/libevent/test/test-weof.c new file mode 100644 index 0000000000..7fd6c8b1f9 --- /dev/null +++ b/third_party/libevent/test/test-weof.c @@ -0,0 +1,84 @@ +/* + * Compile with: + * cc -I/usr/local/include -o time-test time-test.c -L/usr/local/lib -levent + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + + +#ifdef WIN32 +#include +#endif +#include +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif +#ifdef HAVE_SYS_SOCKET_H +#include +#endif +#include +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#include + +#include +#include + +int pair[2]; +int test_okay = 1; +int called = 0; + +static void +write_cb(int fd, short event, void *arg) +{ + const char *test = "test string"; + int len; + + len = send(fd, test, strlen(test) + 1, 0); + + printf("%s: write %d%s\n", __func__, + len, len ? "" : " - means EOF"); + + if (len > 0) { + if (!called) + event_add(arg, NULL); + EVUTIL_CLOSESOCKET(pair[0]); + } else if (called == 1) + test_okay = 0; + + called++; +} + +int +main (int argc, char **argv) +{ + struct event ev; + +#ifndef WIN32 + if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) + return (1); +#endif + + if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1) + return (1); + + /* Initalize the event library */ + event_init(); + + /* Initalize one event */ + event_set(&ev, pair[1], EV_WRITE, write_cb, &ev); + + event_add(&ev, NULL); + + event_dispatch(); + + return (test_okay); +} + diff --git a/third_party/libevent/test/test.sh b/third_party/libevent/test/test.sh new file mode 100644 index 0000000000..506a1988c3 --- /dev/null +++ b/third_party/libevent/test/test.sh @@ -0,0 +1,91 @@ +#!/bin/sh + +setup () { + EVENT_NOKQUEUE=yes; export EVENT_NOKQUEUE + EVENT_NODEVPOLL=yes; export EVENT_NODEVPOLL + EVENT_NOPOLL=yes; export EVENT_NOPOLL + EVENT_NOSELECT=yes; export EVENT_NOSELECT + EVENT_NOEPOLL=yes; export EVENT_NOEPOLL + EVENT_NOEVPORT=yes; export EVENT_NOEVPORT +} + +test () { + if ./test-init 2>/dev/null ; + then + true + else + echo Skipping test + return + fi + +echo -n " test-eof: " +if ./test-eof >/dev/null ; +then + echo OKAY ; +else + echo FAILED ; +fi +echo -n " test-weof: " +if ./test-weof >/dev/null ; +then + echo OKAY ; +else + echo FAILED ; +fi +echo -n " test-time: " +if ./test-time >/dev/null ; +then + echo OKAY ; +else + echo FAILED ; +fi +echo -n " regress: " +if ./regress >/dev/null ; +then + echo OKAY ; +else + echo FAILED ; +fi +} + +echo "Running tests:" + +# Need to do this by hand? +setup +unset EVENT_NOKQUEUE +export EVENT_NOKQUEUE +echo "KQUEUE" +test + +setup +unset EVENT_NODEVPOLL +export EVENT_NODEVPOLL +echo "DEVPOLL" +test + +setup +unset EVENT_NOPOLL +export EVENT_NOPOLL +echo "POLL" +test + +setup +unset EVENT_NOSELECT +export EVENT_NOSELECT +echo "SELECT" +test + +setup +unset EVENT_NOEPOLL +export EVENT_NOEPOLL +echo "EPOLL" +test + +setup +unset EVENT_NOEVPORT +export EVENT_NOEVPORT +echo "EVPORT" +test + + + diff --git a/third_party/modp_b64/LICENSE b/third_party/modp_b64/LICENSE new file mode 100644 index 0000000000..55af76f3eb --- /dev/null +++ b/third_party/modp_b64/LICENSE @@ -0,0 +1,33 @@ + * MODP_B64 - High performance base64 encoder/decoder + * Version 1.3 -- 17-Mar-2006 + * http://modp.com/release/base64 + * + * Copyright (c) 2005, 2006 Nick Galbreath -- nickg [at] modp [dot] com + * 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 the modp.com 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/third_party/modp_b64/OWNERS b/third_party/modp_b64/OWNERS new file mode 100644 index 0000000000..fcfec1df97 --- /dev/null +++ b/third_party/modp_b64/OWNERS @@ -0,0 +1,2 @@ +jschuh@chromium.org +rsleevi@chromium.org diff --git a/third_party/modp_b64/README.chromium b/third_party/modp_b64/README.chromium new file mode 100644 index 0000000000..26eed1e80c --- /dev/null +++ b/third_party/modp_b64/README.chromium @@ -0,0 +1,22 @@ +Name: modp base64 decoder +Short Name: stringencoders +URL: http://code.google.com/p/stringencoders/ +Version: unknown +License: BSD +Security Critical: yes + +Description: +The modp_b64.c file was modified to remove the inclusion of modp's config.h +and to fix compilation errors that occur under VC8. The file was renamed +modp_b64.cc to force it to be compiled as C++ so that the inclusion of +basictypes.h could be possible. + +The file modp_b64_data.h was generated by modp_b64_gen.c (under Linux), +which is not included in this directory. The resulting header was +modified to not include when COMPILER_MSVC is defined (since +that header file does not exist under VC8), but instead in that case to +include "base/basictypes.h" and provide the required typedefs for +uint8_t and uint32_t using uint8 and uint32. + +The modp_b64.cc and modp_b64.h files were modified to make them safe on +64-bit systems. \ No newline at end of file diff --git a/third_party/modp_b64/modp_b64.cc b/third_party/modp_b64/modp_b64.cc new file mode 100644 index 0000000000..e5f6cf1024 --- /dev/null +++ b/third_party/modp_b64/modp_b64.cc @@ -0,0 +1,265 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*- */ +/* vi: set expandtab shiftwidth=4 tabstop=4: */ +/** + * \file + *
+ * MODP_B64 - High performance base64 encoder/decoder
+ * Version 1.3 -- 17-Mar-2006
+ * http://modp.com/release/base64
+ *
+ * Copyright © 2005, 2006  Nick Galbreath -- nickg [at] modp [dot] com
+ * 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 the modp.com nor the names of its
+ *   contributors may be used to endorse or promote products derived from
+ *   this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This is the standard "new" BSD license:
+ * http://www.opensource.org/licenses/bsd-license.php
+ * 
+ */ + +/* public header */ +#include "modp_b64.h" + +/* + * If you are ripping this out of the library, comment out the next + * line and uncomment the next lines as approrpiate + */ +//#include "config.h" + +/* if on motoral, sun, ibm; uncomment this */ +/* #define WORDS_BIGENDIAN 1 */ +/* else for Intel, Amd; uncomment this */ +/* #undef WORDS_BIGENDIAN */ + +#include "modp_b64_data.h" + +#define BADCHAR 0x01FFFFFF + +/** + * you can control if we use padding by commenting out this + * next line. However, I highly recommend you use padding and not + * using it should only be for compatability with a 3rd party. + * Also, 'no padding' is not tested! + */ +#define DOPAD 1 + +/* + * if we aren't doing padding + * set the pad character to NULL + */ +#ifndef DOPAD +#undef CHARPAD +#define CHARPAD '\0' +#endif + +size_t modp_b64_encode(char* dest, const char* str, size_t len) +{ + size_t i = 0; + uint8_t* p = (uint8_t*) dest; + + /* unsigned here is important! */ + uint8_t t1, t2, t3; + + if (len > 2) { + for (; i < len - 2; i += 3) { + t1 = str[i]; t2 = str[i+1]; t3 = str[i+2]; + *p++ = e0[t1]; + *p++ = e1[((t1 & 0x03) << 4) | ((t2 >> 4) & 0x0F)]; + *p++ = e1[((t2 & 0x0F) << 2) | ((t3 >> 6) & 0x03)]; + *p++ = e2[t3]; + } + } + + switch (len - i) { + case 0: + break; + case 1: + t1 = str[i]; + *p++ = e0[t1]; + *p++ = e1[(t1 & 0x03) << 4]; + *p++ = CHARPAD; + *p++ = CHARPAD; + break; + default: /* case 2 */ + t1 = str[i]; t2 = str[i+1]; + *p++ = e0[t1]; + *p++ = e1[((t1 & 0x03) << 4) | ((t2 >> 4) & 0x0F)]; + *p++ = e2[(t2 & 0x0F) << 2]; + *p++ = CHARPAD; + } + + *p = '\0'; + return p - (uint8_t*)dest; +} + +#ifdef WORDS_BIGENDIAN /* BIG ENDIAN -- SUN / IBM / MOTOROLA */ +int modp_b64_decode(char* dest, const char* src, int len) +{ + if (len == 0) return 0; + +#ifdef DOPAD + /* if padding is used, then the message must be at least + 4 chars and be a multiple of 4. + there can be at most 2 pad chars at the end */ + if (len < 4 || (len % 4 != 0)) return MODP_B64_ERROR; + if (src[len-1] == CHARPAD) { + len--; + if (src[len -1] == CHARPAD) { + len--; + } + } +#endif /* DOPAD */ + + size_t i; + int leftover = len % 4; + size_t chunks = (leftover == 0) ? len / 4 - 1 : len /4; + + uint8_t* p = (uint8_t*) dest; + uint32_t x = 0; + uint32_t* destInt = (uint32_t*) p; + uint32_t* srcInt = (uint32_t*) src; + uint32_t y = *srcInt++; + for (i = 0; i < chunks; ++i) { + x = d0[y >> 24 & 0xff] | d1[y >> 16 & 0xff] | + d2[y >> 8 & 0xff] | d3[y & 0xff]; + + if (x >= BADCHAR) return MODP_B64_ERROR; + *destInt = x << 8; + p += 3; + destInt = (uint32_t*)p; + y = *srcInt++; + } + + switch (leftover) { + case 0: + x = d0[y >> 24 & 0xff] | d1[y >> 16 & 0xff] | + d2[y >> 8 & 0xff] | d3[y & 0xff]; + if (x >= BADCHAR) return MODP_B64_ERROR; + *p++ = ((uint8_t*)&x)[1]; + *p++ = ((uint8_t*)&x)[2]; + *p = ((uint8_t*)&x)[3]; + return (chunks+1)*3; + case 1: + x = d3[y >> 24]; + *p = (uint8_t)x; + break; + case 2: + x = d3[y >> 24] *64 + d3[(y >> 16) & 0xff]; + *p = (uint8_t)(x >> 4); + break; + default: /* case 3 */ + x = (d3[y >> 24] *64 + d3[(y >> 16) & 0xff])*64 + + d3[(y >> 8) & 0xff]; + *p++ = (uint8_t) (x >> 10); + *p = (uint8_t) (x >> 2); + break; + } + + if (x >= BADCHAR) return MODP_B64_ERROR; + return 3*chunks + (6*leftover)/8; +} + +#else /* LITTLE ENDIAN -- INTEL AND FRIENDS */ + +size_t modp_b64_decode(char* dest, const char* src, size_t len) +{ + if (len == 0) return 0; + +#ifdef DOPAD + /* + * if padding is used, then the message must be at least + * 4 chars and be a multiple of 4 + */ + if (len < 4 || (len % 4 != 0)) return MODP_B64_ERROR; /* error */ + /* there can be at most 2 pad chars at the end */ + if (src[len-1] == CHARPAD) { + len--; + if (src[len -1] == CHARPAD) { + len--; + } + } +#endif + + size_t i; + int leftover = len % 4; + size_t chunks = (leftover == 0) ? len / 4 - 1 : len /4; + + uint8_t* p = (uint8_t*)dest; + uint32_t x = 0; + uint32_t* destInt = (uint32_t*) p; + uint32_t* srcInt = (uint32_t*) src; + uint32_t y = *srcInt++; + for (i = 0; i < chunks; ++i) { + x = d0[y & 0xff] | + d1[(y >> 8) & 0xff] | + d2[(y >> 16) & 0xff] | + d3[(y >> 24) & 0xff]; + + if (x >= BADCHAR) return MODP_B64_ERROR; + *destInt = x ; + p += 3; + destInt = (uint32_t*)p; + y = *srcInt++;} + + + switch (leftover) { + case 0: + x = d0[y & 0xff] | + d1[(y >> 8) & 0xff] | + d2[(y >> 16) & 0xff] | + d3[(y >> 24) & 0xff]; + + if (x >= BADCHAR) return MODP_B64_ERROR; + *p++ = ((uint8_t*)(&x))[0]; + *p++ = ((uint8_t*)(&x))[1]; + *p = ((uint8_t*)(&x))[2]; + return (chunks+1)*3; + break; + case 1: /* with padding this is an impossible case */ + x = d0[y & 0xff]; + *p = *((uint8_t*)(&x)); // i.e. first char/byte in int + break; + case 2: // * case 2, 1 output byte */ + x = d0[y & 0xff] | d1[y >> 8 & 0xff]; + *p = *((uint8_t*)(&x)); // i.e. first char + break; + default: /* case 3, 2 output bytes */ + x = d0[y & 0xff] | + d1[y >> 8 & 0xff ] | + d2[y >> 16 & 0xff]; /* 0x3c */ + *p++ = ((uint8_t*)(&x))[0]; + *p = ((uint8_t*)(&x))[1]; + break; + } + + if (x >= BADCHAR) return MODP_B64_ERROR; + + return 3*chunks + (6*leftover)/8; +} + +#endif /* if bigendian / else / endif */ diff --git a/third_party/modp_b64/modp_b64.gyp b/third_party/modp_b64/modp_b64.gyp new file mode 100644 index 0000000000..baed111858 --- /dev/null +++ b/third_party/modp_b64/modp_b64.gyp @@ -0,0 +1,21 @@ +# 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. + +{ + 'targets': [ + { + 'target_name': 'modp_b64', + 'type': 'static_library', + 'toolsets': ['host', 'target'], + 'sources': [ + 'modp_b64.cc', + 'modp_b64.h', + 'modp_b64_data.h', + ], + 'include_dirs': [ + '../..', + ], + }, + ], +} diff --git a/third_party/modp_b64/modp_b64.h b/third_party/modp_b64/modp_b64.h new file mode 100644 index 0000000000..3270e5fdf1 --- /dev/null +++ b/third_party/modp_b64/modp_b64.h @@ -0,0 +1,171 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*- */ +/* vi: set expandtab shiftwidth=4 tabstop=4: */ + +/** + * \file + *
+ * High performance base64 encoder / decoder
+ * Version 1.3 -- 17-Mar-2006
+ *
+ * Copyright © 2005, 2006, Nick Galbreath -- nickg [at] modp [dot] com
+ * All rights reserved.
+ *
+ * http://modp.com/release/base64
+ *
+ * Released under bsd license.  See modp_b64.c for details.
+ * 
+ * + * The default implementation is the standard b64 encoding with padding. + * It's easy to change this to use "URL safe" characters and to remove + * padding. See the modp_b64.c source code for details. + * + */ + +#ifndef MODP_B64 +#define MODP_B64 + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Encode a raw binary string into base 64. + * src contains the bytes + * len contains the number of bytes in the src + * dest should be allocated by the caller to contain + * at least modp_b64_encode_len(len) bytes (see below) + * This will contain the null-terminated b64 encoded result + * returns length of the destination string plus the ending null byte + * i.e. the result will be equal to strlen(dest) + 1 + * + * Example + * + * \code + * char* src = ...; + * int srclen = ...; //the length of number of bytes in src + * char* dest = (char*) malloc(modp_b64_encode_len); + * int len = modp_b64_encode(dest, src, sourcelen); + * if (len == -1) { + * printf("Error\n"); + * } else { + * printf("b64 = %s\n", dest); + * } + * \endcode + * + */ +size_t modp_b64_encode(char* dest, const char* str, size_t len); + +/** + * Decode a base64 encoded string + * + * src should contain exactly len bytes of b64 characters. + * if src contains -any- non-base characters (such as white + * space, -1 is returned. + * + * dest should be allocated by the caller to contain at least + * len * 3 / 4 bytes. + * + * Returns the length (strlen) of the output, or -1 if unable to + * decode + * + * \code + * char* src = ...; + * int srclen = ...; // or if you don't know use strlen(src) + * char* dest = (char*) malloc(modp_b64_decode_len(srclen)); + * int len = modp_b64_decode(dest, src, sourcelen); + * if (len == -1) { error } + * \endcode + */ +size_t modp_b64_decode(char* dest, const char* src, size_t len); + +/** + * Given a source string of length len, this returns the amount of + * memory the destination string should have. + * + * remember, this is integer math + * 3 bytes turn into 4 chars + * ceiling[len / 3] * 4 + 1 + * + * +1 is for any extra null. + */ +#define modp_b64_encode_len(A) ((A+2)/3 * 4 + 1) + +/** + * Given a base64 string of length len, + * this returns the amount of memory required for output string + * It maybe be more than the actual number of bytes written. + * NOTE: remember this is integer math + * this allocates a bit more memory than traditional versions of b64 + * decode 4 chars turn into 3 bytes + * floor[len * 3/4] + 2 + */ +#define modp_b64_decode_len(A) (A / 4 * 3 + 2) + +/** + * Will return the strlen of the output from encoding. + * This may be less than the required number of bytes allocated. + * + * This allows you to 'deserialized' a struct + * \code + * char* b64encoded = "..."; + * int len = strlen(b64encoded); + * + * struct datastuff foo; + * if (modp_b64_encode_strlen(sizeof(struct datastuff)) != len) { + * // wrong size + * return false; + * } else { + * // safe to do; + * if (modp_b64_decode((char*) &foo, b64encoded, len) == -1) { + * // bad characters + * return false; + * } + * } + * // foo is filled out now + * \endcode + */ +#define modp_b64_encode_strlen(A) ((A + 2)/ 3 * 4) + +#define MODP_B64_ERROR ((size_t)-1) + +#ifdef __cplusplus +} + +#include + +inline std::string& modp_b64_encode(std::string& s) +{ + std::string x(modp_b64_encode_len(s.size()), '\0'); + size_t d = modp_b64_encode(const_cast(x.data()), s.data(), (int)s.size()); + x.erase(d, std::string::npos); + s.swap(x); + return s; +} + +/** + * base 64 decode a string (self-modifing) + * On failure, the string is empty. + * + * This function is for C++ only (duh) + * + * \param[in,out] s the string to be decoded + * \return a reference to the input string + */ +inline std::string& modp_b64_decode(std::string& s) +{ + std::string x(modp_b64_decode_len(s.size()), '\0'); + size_t d = modp_b64_decode(const_cast(x.data()), s.data(), (int)s.size()); + if (d == MODP_B64_ERROR) { + x.clear(); + } else { + x.erase(d, std::string::npos); + } + s.swap(x); + return s; +} + +#endif /* __cplusplus */ + +#endif /* MODP_B64 */ diff --git a/third_party/modp_b64/modp_b64_data.h b/third_party/modp_b64/modp_b64_data.h new file mode 100644 index 0000000000..aca6f0fb0e --- /dev/null +++ b/third_party/modp_b64/modp_b64_data.h @@ -0,0 +1,491 @@ +#include "build/build_config.h" +#if !defined(COMPILER_MSVC) +#include +#else +// VC8 doesn't have stdint.h. On the other hand, some compilers don't like +// the below code, because basictypes.h itself includes stdint.h and the +// typedefs below can cause conflicts. +#include "base/basictypes.h" +typedef uint8 uint8_t; +typedef uint32 uint32_t; +#endif + +#define CHAR62 '+' +#define CHAR63 '/' +#define CHARPAD '=' +static const char e0[256] = { + 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'C', 'C', + 'C', 'C', 'D', 'D', 'D', 'D', 'E', 'E', 'E', 'E', + 'F', 'F', 'F', 'F', 'G', 'G', 'G', 'G', 'H', 'H', + 'H', 'H', 'I', 'I', 'I', 'I', 'J', 'J', 'J', 'J', + 'K', 'K', 'K', 'K', 'L', 'L', 'L', 'L', 'M', 'M', + 'M', 'M', 'N', 'N', 'N', 'N', 'O', 'O', 'O', 'O', + 'P', 'P', 'P', 'P', 'Q', 'Q', 'Q', 'Q', 'R', 'R', + 'R', 'R', 'S', 'S', 'S', 'S', 'T', 'T', 'T', 'T', + 'U', 'U', 'U', 'U', 'V', 'V', 'V', 'V', 'W', 'W', + 'W', 'W', 'X', 'X', 'X', 'X', 'Y', 'Y', 'Y', 'Y', + 'Z', 'Z', 'Z', 'Z', 'a', 'a', 'a', 'a', 'b', 'b', + 'b', 'b', 'c', 'c', 'c', 'c', 'd', 'd', 'd', 'd', + 'e', 'e', 'e', 'e', 'f', 'f', 'f', 'f', 'g', 'g', + 'g', 'g', 'h', 'h', 'h', 'h', 'i', 'i', 'i', 'i', + 'j', 'j', 'j', 'j', 'k', 'k', 'k', 'k', 'l', 'l', + 'l', 'l', 'm', 'm', 'm', 'm', 'n', 'n', 'n', 'n', + 'o', 'o', 'o', 'o', 'p', 'p', 'p', 'p', 'q', 'q', + 'q', 'q', 'r', 'r', 'r', 'r', 's', 's', 's', 's', + 't', 't', 't', 't', 'u', 'u', 'u', 'u', 'v', 'v', + 'v', 'v', 'w', 'w', 'w', 'w', 'x', 'x', 'x', 'x', + 'y', 'y', 'y', 'y', 'z', 'z', 'z', 'z', '0', '0', + '0', '0', '1', '1', '1', '1', '2', '2', '2', '2', + '3', '3', '3', '3', '4', '4', '4', '4', '5', '5', + '5', '5', '6', '6', '6', '6', '7', '7', '7', '7', + '8', '8', '8', '8', '9', '9', '9', '9', '+', '+', + '+', '+', '/', '/', '/', '/' +}; + +static const char e1[256] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', + 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', + 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', + 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', '+', '/', 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/', 'A', 'B', + 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', + 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', + 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '+', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', + 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', + 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', + 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', '+', '/' +}; + +static const char e2[256] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', + 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', + 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', + 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', '+', '/', 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/', 'A', 'B', + 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', + 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', + 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '+', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', + 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', + 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', + 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', '+', '/' +}; + + + +#ifdef WORDS_BIGENDIAN + + +/* SPECIAL DECODE TABLES FOR BIG ENDIAN (IBM/MOTOROLA/SUN) CPUS */ + +static const uint32_t d0[256] = { +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x00f80000, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00fc0000, +0x00d00000, 0x00d40000, 0x00d80000, 0x00dc0000, 0x00e00000, 0x00e40000, +0x00e80000, 0x00ec0000, 0x00f00000, 0x00f40000, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, +0x00040000, 0x00080000, 0x000c0000, 0x00100000, 0x00140000, 0x00180000, +0x001c0000, 0x00200000, 0x00240000, 0x00280000, 0x002c0000, 0x00300000, +0x00340000, 0x00380000, 0x003c0000, 0x00400000, 0x00440000, 0x00480000, +0x004c0000, 0x00500000, 0x00540000, 0x00580000, 0x005c0000, 0x00600000, +0x00640000, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x00680000, 0x006c0000, 0x00700000, 0x00740000, 0x00780000, +0x007c0000, 0x00800000, 0x00840000, 0x00880000, 0x008c0000, 0x00900000, +0x00940000, 0x00980000, 0x009c0000, 0x00a00000, 0x00a40000, 0x00a80000, +0x00ac0000, 0x00b00000, 0x00b40000, 0x00b80000, 0x00bc0000, 0x00c00000, +0x00c40000, 0x00c80000, 0x00cc0000, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff +}; + + +static const uint32_t d1[256] = { +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x0003e000, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x0003f000, +0x00034000, 0x00035000, 0x00036000, 0x00037000, 0x00038000, 0x00039000, +0x0003a000, 0x0003b000, 0x0003c000, 0x0003d000, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, +0x00001000, 0x00002000, 0x00003000, 0x00004000, 0x00005000, 0x00006000, +0x00007000, 0x00008000, 0x00009000, 0x0000a000, 0x0000b000, 0x0000c000, +0x0000d000, 0x0000e000, 0x0000f000, 0x00010000, 0x00011000, 0x00012000, +0x00013000, 0x00014000, 0x00015000, 0x00016000, 0x00017000, 0x00018000, +0x00019000, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x0001a000, 0x0001b000, 0x0001c000, 0x0001d000, 0x0001e000, +0x0001f000, 0x00020000, 0x00021000, 0x00022000, 0x00023000, 0x00024000, +0x00025000, 0x00026000, 0x00027000, 0x00028000, 0x00029000, 0x0002a000, +0x0002b000, 0x0002c000, 0x0002d000, 0x0002e000, 0x0002f000, 0x00030000, +0x00031000, 0x00032000, 0x00033000, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff +}; + + +static const uint32_t d2[256] = { +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x00000f80, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000fc0, +0x00000d00, 0x00000d40, 0x00000d80, 0x00000dc0, 0x00000e00, 0x00000e40, +0x00000e80, 0x00000ec0, 0x00000f00, 0x00000f40, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, +0x00000040, 0x00000080, 0x000000c0, 0x00000100, 0x00000140, 0x00000180, +0x000001c0, 0x00000200, 0x00000240, 0x00000280, 0x000002c0, 0x00000300, +0x00000340, 0x00000380, 0x000003c0, 0x00000400, 0x00000440, 0x00000480, +0x000004c0, 0x00000500, 0x00000540, 0x00000580, 0x000005c0, 0x00000600, +0x00000640, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x00000680, 0x000006c0, 0x00000700, 0x00000740, 0x00000780, +0x000007c0, 0x00000800, 0x00000840, 0x00000880, 0x000008c0, 0x00000900, +0x00000940, 0x00000980, 0x000009c0, 0x00000a00, 0x00000a40, 0x00000a80, +0x00000ac0, 0x00000b00, 0x00000b40, 0x00000b80, 0x00000bc0, 0x00000c00, +0x00000c40, 0x00000c80, 0x00000cc0, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff +}; + + +static const uint32_t d3[256] = { +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x0000003e, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x0000003f, +0x00000034, 0x00000035, 0x00000036, 0x00000037, 0x00000038, 0x00000039, +0x0000003a, 0x0000003b, 0x0000003c, 0x0000003d, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, +0x00000001, 0x00000002, 0x00000003, 0x00000004, 0x00000005, 0x00000006, +0x00000007, 0x00000008, 0x00000009, 0x0000000a, 0x0000000b, 0x0000000c, +0x0000000d, 0x0000000e, 0x0000000f, 0x00000010, 0x00000011, 0x00000012, +0x00000013, 0x00000014, 0x00000015, 0x00000016, 0x00000017, 0x00000018, +0x00000019, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x0000001a, 0x0000001b, 0x0000001c, 0x0000001d, 0x0000001e, +0x0000001f, 0x00000020, 0x00000021, 0x00000022, 0x00000023, 0x00000024, +0x00000025, 0x00000026, 0x00000027, 0x00000028, 0x00000029, 0x0000002a, +0x0000002b, 0x0000002c, 0x0000002d, 0x0000002e, 0x0000002f, 0x00000030, +0x00000031, 0x00000032, 0x00000033, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff +}; + + +#else + + +/* SPECIAL DECODE TABLES FOR LITTLE ENDIAN (INTEL) CPUS */ + +static const uint32_t d0[256] = { +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x000000f8, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x000000fc, +0x000000d0, 0x000000d4, 0x000000d8, 0x000000dc, 0x000000e0, 0x000000e4, +0x000000e8, 0x000000ec, 0x000000f0, 0x000000f4, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, +0x00000004, 0x00000008, 0x0000000c, 0x00000010, 0x00000014, 0x00000018, +0x0000001c, 0x00000020, 0x00000024, 0x00000028, 0x0000002c, 0x00000030, +0x00000034, 0x00000038, 0x0000003c, 0x00000040, 0x00000044, 0x00000048, +0x0000004c, 0x00000050, 0x00000054, 0x00000058, 0x0000005c, 0x00000060, +0x00000064, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x00000068, 0x0000006c, 0x00000070, 0x00000074, 0x00000078, +0x0000007c, 0x00000080, 0x00000084, 0x00000088, 0x0000008c, 0x00000090, +0x00000094, 0x00000098, 0x0000009c, 0x000000a0, 0x000000a4, 0x000000a8, +0x000000ac, 0x000000b0, 0x000000b4, 0x000000b8, 0x000000bc, 0x000000c0, +0x000000c4, 0x000000c8, 0x000000cc, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff +}; + + +static const uint32_t d1[256] = { +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x0000e003, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x0000f003, +0x00004003, 0x00005003, 0x00006003, 0x00007003, 0x00008003, 0x00009003, +0x0000a003, 0x0000b003, 0x0000c003, 0x0000d003, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, +0x00001000, 0x00002000, 0x00003000, 0x00004000, 0x00005000, 0x00006000, +0x00007000, 0x00008000, 0x00009000, 0x0000a000, 0x0000b000, 0x0000c000, +0x0000d000, 0x0000e000, 0x0000f000, 0x00000001, 0x00001001, 0x00002001, +0x00003001, 0x00004001, 0x00005001, 0x00006001, 0x00007001, 0x00008001, +0x00009001, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x0000a001, 0x0000b001, 0x0000c001, 0x0000d001, 0x0000e001, +0x0000f001, 0x00000002, 0x00001002, 0x00002002, 0x00003002, 0x00004002, +0x00005002, 0x00006002, 0x00007002, 0x00008002, 0x00009002, 0x0000a002, +0x0000b002, 0x0000c002, 0x0000d002, 0x0000e002, 0x0000f002, 0x00000003, +0x00001003, 0x00002003, 0x00003003, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff +}; + + +static const uint32_t d2[256] = { +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x00800f00, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00c00f00, +0x00000d00, 0x00400d00, 0x00800d00, 0x00c00d00, 0x00000e00, 0x00400e00, +0x00800e00, 0x00c00e00, 0x00000f00, 0x00400f00, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, +0x00400000, 0x00800000, 0x00c00000, 0x00000100, 0x00400100, 0x00800100, +0x00c00100, 0x00000200, 0x00400200, 0x00800200, 0x00c00200, 0x00000300, +0x00400300, 0x00800300, 0x00c00300, 0x00000400, 0x00400400, 0x00800400, +0x00c00400, 0x00000500, 0x00400500, 0x00800500, 0x00c00500, 0x00000600, +0x00400600, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x00800600, 0x00c00600, 0x00000700, 0x00400700, 0x00800700, +0x00c00700, 0x00000800, 0x00400800, 0x00800800, 0x00c00800, 0x00000900, +0x00400900, 0x00800900, 0x00c00900, 0x00000a00, 0x00400a00, 0x00800a00, +0x00c00a00, 0x00000b00, 0x00400b00, 0x00800b00, 0x00c00b00, 0x00000c00, +0x00400c00, 0x00800c00, 0x00c00c00, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff +}; + + +static const uint32_t d3[256] = { +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x003e0000, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x003f0000, +0x00340000, 0x00350000, 0x00360000, 0x00370000, 0x00380000, 0x00390000, +0x003a0000, 0x003b0000, 0x003c0000, 0x003d0000, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, +0x00010000, 0x00020000, 0x00030000, 0x00040000, 0x00050000, 0x00060000, +0x00070000, 0x00080000, 0x00090000, 0x000a0000, 0x000b0000, 0x000c0000, +0x000d0000, 0x000e0000, 0x000f0000, 0x00100000, 0x00110000, 0x00120000, +0x00130000, 0x00140000, 0x00150000, 0x00160000, 0x00170000, 0x00180000, +0x00190000, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x001a0000, 0x001b0000, 0x001c0000, 0x001d0000, 0x001e0000, +0x001f0000, 0x00200000, 0x00210000, 0x00220000, 0x00230000, 0x00240000, +0x00250000, 0x00260000, 0x00270000, 0x00280000, 0x00290000, 0x002a0000, +0x002b0000, 0x002c0000, 0x002d0000, 0x002e0000, 0x002f0000, 0x00300000, +0x00310000, 0x00320000, 0x00330000, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff +}; + + +#endif diff --git a/third_party/openssl b/third_party/openssl new file mode 160000 index 0000000000..8f54aac19a --- /dev/null +++ b/third_party/openssl @@ -0,0 +1 @@ +Subproject commit 8f54aac19a36d72ea630c813cae51c81a3cc0d78 diff --git a/tools/DEPS b/tools/DEPS new file mode 100644 index 0000000000..dc04e3dfc6 --- /dev/null +++ b/tools/DEPS @@ -0,0 +1,6 @@ +# checkdeps.py shouldn't check include paths for files in these dirs: +skip_child_includes = [ + "clang", + "gyp", + "traceline", +] diff --git a/tools/OWNERS b/tools/OWNERS new file mode 100644 index 0000000000..4cd808f040 --- /dev/null +++ b/tools/OWNERS @@ -0,0 +1,3 @@ +* + +per-file bisect-builds.py=rsesek@chromium.org diff --git a/tools/PRESUBMIT.py b/tools/PRESUBMIT.py new file mode 100644 index 0000000000..01572314c0 --- /dev/null +++ b/tools/PRESUBMIT.py @@ -0,0 +1,48 @@ +# 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. + +"""Top-level presubmit script for bisect trybot. + +See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts for +details on the presubmit API built into gcl. +""" + +import imp + +def _ExamineBisectConfigFile(input_api, output_api): + for f in input_api.AffectedFiles(): + if not f.LocalPath().endswith('run-bisect-perf-regression.cfg'): + continue + + try: + cfg_file = imp.load_source('config', 'run-bisect-perf-regression.cfg') + + for k, v in cfg_file.config.iteritems(): + if v: + return f.LocalPath() + except (IOError, AttributeError, TypeError): + return f.LocalPath() + + return None + +def _CheckNoChangesToBisectConfigFile(input_api, output_api): + results = _ExamineBisectConfigFile(input_api, output_api) + if results: + return [output_api.PresubmitError( + 'The bisection config file should only contain a config dict with ' + 'empty fields. Changes to this file should never be submitted.', + items=[results])] + + return [] + +def CommonChecks(input_api, output_api): + results = [] + results.extend(_CheckNoChangesToBisectConfigFile(input_api, output_api)) + return results + +def CheckChangeOnUpload(input_api, output_api): + return CommonChecks(input_api, output_api) + +def CheckChangeOnCommit(input_api, output_api): + return CommonChecks(input_api, output_api) diff --git a/tools/android/OWNERS b/tools/android/OWNERS new file mode 100644 index 0000000000..e911c595c5 --- /dev/null +++ b/tools/android/OWNERS @@ -0,0 +1,6 @@ +bulach@chromium.org +digit@chromium.org +michaelbai@chromium.org +pliard@chromium.org +wangxianzhu@chromium.org +yfriedman@chromium.org diff --git a/tools/android/adb_reboot/adb_reboot.c b/tools/android/adb_reboot/adb_reboot.c new file mode 100644 index 0000000000..48c5cbe58f --- /dev/null +++ b/tools/android/adb_reboot/adb_reboot.c @@ -0,0 +1,43 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include +#include +#include +#include + +int main(int argc, char ** argv) { + int i = fork(); + struct stat ft; + time_t ct; + + if (i < 0) { + printf("fork error"); + return 1; + } + if (i > 0) + return 0; + + /* child (daemon) continues */ + int j; + for (j = 0; j < getdtablesize(); j++) + close(j); + + setsid(); /* obtain a new process group */ + + while (1) { + sleep(120); + + stat("/sdcard/host_heartbeat", &ft); + time(&ct); + if (ct - ft.st_mtime > 120) { + /* File was not touched for some time. */ + system("su -c reboot"); + } + } + + return 0; +} diff --git a/tools/android/adb_reboot/adb_reboot.gyp b/tools/android/adb_reboot/adb_reboot.gyp new file mode 100644 index 0000000000..85134b9906 --- /dev/null +++ b/tools/android/adb_reboot/adb_reboot.gyp @@ -0,0 +1,14 @@ +# 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. +{ + 'targets': [ + { + 'target_name': 'adb_reboot', + 'type': 'executable', + 'sources': [ + 'adb_reboot.c', + ], + }, + ], +} diff --git a/tools/android/android_tools.gyp b/tools/android/android_tools.gyp new file mode 100644 index 0000000000..c38cd70d58 --- /dev/null +++ b/tools/android/android_tools.gyp @@ -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. + +{ + 'targets': [ + # Intermediate target grouping the android tools needed to run native + # unittests and instrumentation test apks. + { + 'target_name': 'android_tools', + 'type': 'none', + 'dependencies': [ + 'fake_dns/fake_dns.gyp:fake_dns', + 'forwarder2/forwarder.gyp:forwarder2', + 'md5sum/md5sum.gyp:md5sum', + 'adb_reboot/adb_reboot.gyp:adb_reboot', + ], + }, + { + 'target_name': 'memdump', + 'type': 'none', + 'dependencies': [ + 'memdump/memdump.gyp:memdump', + ], + } + ], +} diff --git a/tools/android/asan/asanwrapper.sh b/tools/android/asan/asanwrapper.sh new file mode 100755 index 0000000000..2392d2ca44 --- /dev/null +++ b/tools/android/asan/asanwrapper.sh @@ -0,0 +1,9 @@ +#!/system/bin/sh +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +ASAN_OPTIONS=debug=1,verbosity=1,strict_memcmp=0 \ +LD_LIBRARY_PATH=/data/local/tmp/asan:$LD_LIBRARY_PATH \ +LD_PRELOAD=libclang_rt.asan-arm-android.so \ +exec $@ diff --git a/tools/android/common/adb_connection.cc b/tools/android/common/adb_connection.cc new file mode 100644 index 0000000000..91c25fed20 --- /dev/null +++ b/tools/android/common/adb_connection.cc @@ -0,0 +1,107 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "tools/android/common/adb_connection.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "tools/android/common/net.h" + +namespace tools { +namespace { + +void CloseSocket(int fd) { + if (fd >= 0) { + int old_errno = errno; + (void) HANDLE_EINTR(close(fd)); + errno = old_errno; + } +} + +} // namespace + +int ConnectAdbHostSocket(const char* forward_to) { + // ADB port forward request format: HHHHtcp:port:address. + // HHHH is the hexidecimal length of the "tcp:port:address" part. + const size_t kBufferMaxLength = 30; + const size_t kLengthOfLength = 4; + const size_t kAddressMaxLength = kBufferMaxLength - kLengthOfLength; + + const char kAddressPrefix[] = { 't', 'c', 'p', ':' }; + size_t address_length = arraysize(kAddressPrefix) + strlen(forward_to); + if (address_length > kBufferMaxLength - kLengthOfLength) { + LOG(ERROR) << "Forward to address is too long: " << forward_to; + return -1; + } + + char request[kBufferMaxLength]; + memcpy(request + kLengthOfLength, kAddressPrefix, arraysize(kAddressPrefix)); + memcpy(request + kLengthOfLength + arraysize(kAddressPrefix), + forward_to, strlen(forward_to)); + + char length_buffer[kLengthOfLength + 1]; + snprintf(length_buffer, arraysize(length_buffer), "%04X", + static_cast(address_length)); + memcpy(request, length_buffer, kLengthOfLength); + + int host_socket = socket(AF_INET, SOCK_STREAM, 0); + if (host_socket < 0) { + LOG(ERROR) << "Failed to create adb socket: " << strerror(errno); + return -1; + } + + DisableNagle(host_socket); + + const int kAdbPort = 5037; + sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr.sin_port = htons(kAdbPort); + if (HANDLE_EINTR(connect(host_socket, reinterpret_cast(&addr), + sizeof(addr))) < 0) { + LOG(ERROR) << "Failed to connect adb socket: " << strerror(errno); + CloseSocket(host_socket); + return -1; + } + + size_t bytes_remaining = address_length + kLengthOfLength; + size_t bytes_sent = 0; + while (bytes_remaining > 0) { + int ret = HANDLE_EINTR(send(host_socket, request + bytes_sent, + bytes_remaining, 0)); + if (ret < 0) { + LOG(ERROR) << "Failed to send request: " << strerror(errno); + CloseSocket(host_socket); + return -1; + } + + bytes_sent += ret; + bytes_remaining -= ret; + } + + const size_t kAdbStatusLength = 4; + char response[kBufferMaxLength]; + int response_length = HANDLE_EINTR(recv(host_socket, response, + kBufferMaxLength, 0)); + if (response_length < kAdbStatusLength || + strncmp("OKAY", response, kAdbStatusLength) != 0) { + LOG(ERROR) << "Bad response from ADB: length: " << response_length + << " data: " << DumpBinary(response, response_length); + CloseSocket(host_socket); + return -1; + } + + return host_socket; +} + +} // namespace tools diff --git a/tools/android/common/adb_connection.h b/tools/android/common/adb_connection.h new file mode 100644 index 0000000000..3fa0fb396f --- /dev/null +++ b/tools/android/common/adb_connection.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 TOOLS_ANDROID_COMMON_ADB_CONNECTION_H_ +#define TOOLS_ANDROID_COMMON_ADB_CONNECTION_H_ + +namespace tools { + +// Creates a socket that can forward to a host socket through ADB. +// The format of forward_to is :. +// Returns the socket handle, or -1 on any error. +int ConnectAdbHostSocket(const char* forward_to); + +} // namespace tools + +#endif // TOOLS_ANDROID_COMMON_ADB_CONNECTION_H_ + diff --git a/tools/android/common/common.gyp b/tools/android/common/common.gyp new file mode 100644 index 0000000000..8622625844 --- /dev/null +++ b/tools/android/common/common.gyp @@ -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. + +{ + 'targets': [ + { + 'target_name': 'android_tools_common', + 'type': 'static_library', + 'toolsets': ['host', 'target'], + 'include_dirs': [ + '..', + '../../..', + ], + 'sources': [ + 'adb_connection.cc', + 'adb_connection.h', + 'daemon.cc', + 'daemon.h', + 'net.cc', + 'net.h', + ], + }, + ], +} + diff --git a/tools/android/common/daemon.cc b/tools/android/common/daemon.cc new file mode 100644 index 0000000000..9eafdbce05 --- /dev/null +++ b/tools/android/common/daemon.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 "tools/android/common/daemon.h" + +#include +#include +#include +#include + +#include "base/command_line.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" + +namespace { + +const char kNoSpawnDaemon[] = "D"; + +int g_exit_status = 0; + +void Exit(int unused) { + _exit(g_exit_status); +} + +void CloseFileDescriptor(int fd) { + int old_errno = errno; + (void) HANDLE_EINTR(close(fd)); + errno = old_errno; +} + +} // namespace + +namespace tools { + +bool HasHelpSwitch(const CommandLine& command_line) { + return command_line.HasSwitch("h") || command_line.HasSwitch("help"); +} + +bool HasNoSpawnDaemonSwitch(const CommandLine& command_line) { + return command_line.HasSwitch(kNoSpawnDaemon); +} + +void ShowHelp(const char* program, + const char* extra_title, + const char* extra_descriptions) { + printf("Usage: %s [-%s] %s\n" + " -%s stops from spawning a daemon process\n%s", + program, kNoSpawnDaemon, extra_title, kNoSpawnDaemon, + extra_descriptions); +} + +void SpawnDaemon(int exit_status) { + g_exit_status = exit_status; + signal(SIGUSR1, Exit); + + if (fork()) { + // In parent process. + sleep(10); // Wait for the child process to finish setsid(). + NOTREACHED(); + } + + // In child process. + setsid(); // Detach the child process from its parent. + kill(getppid(), SIGUSR1); // Inform the parent process to exit. + + // Close the standard input and outputs, otherwise the process may block + // adbd when the shell exits. + // Comment out these lines if you want to see outputs for debugging. + CloseFileDescriptor(0); + CloseFileDescriptor(1); + CloseFileDescriptor(2); +} + +} // namespace tools diff --git a/tools/android/common/daemon.h b/tools/android/common/daemon.h new file mode 100644 index 0000000000..0c1640107d --- /dev/null +++ b/tools/android/common/daemon.h @@ -0,0 +1,28 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef TOOLS_ANDROID_COMMON_DAEMON_H_ +#define TOOLS_ANDROID_COMMON_DAEMON_H_ + +class CommandLine; + +namespace tools { + +bool HasHelpSwitch(const CommandLine& command_line); + +bool HasNoSpawnDaemonSwitch(const CommandLine& command_line); + +void ShowHelp(const char* program, + const char* extra_title, + const char* extra_descriptions); + +// Spawns a daemon process and exits the current process with exit_status. +// Any code executed after this function returns will be executed in the +// spawned daemon process. +void SpawnDaemon(int exit_status); + +} // namespace tools + +#endif // TOOLS_ANDROID_COMMON_DAEMON_H_ + diff --git a/tools/android/common/net.cc b/tools/android/common/net.cc new file mode 100644 index 0000000000..3b9ef15bc3 --- /dev/null +++ b/tools/android/common/net.cc @@ -0,0 +1,40 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "tools/android/common/net.h" + +#include +#include +#include +#include + +#include "base/strings/stringprintf.h" + +namespace tools { + +int DisableNagle(int socket) { + int on = 1; + return setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)); +} + +int DeferAccept(int socket) { + int on = 1; + return setsockopt(socket, IPPROTO_TCP, TCP_DEFER_ACCEPT, &on, sizeof(on)); +} + +std::string DumpBinary(const char* buffer, size_t length) { + std::string result = "["; + for (int i = 0; i < length; ++i) { + base::StringAppendF(&result, "%02x,", + static_cast(buffer[i])); + } + + if (length) + result.erase(result.length() - 1); + + return result + "]"; +} + +} // namespace tools + diff --git a/tools/android/common/net.h b/tools/android/common/net.h new file mode 100644 index 0000000000..e3619548ab --- /dev/null +++ b/tools/android/common/net.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 TOOLS_ANDROID_COMMON_NET_H_ +#define TOOLS_ANDROID_COMMON_NET_H_ + +#include + +namespace tools { + +// DisableNagle can improve TCP transmission performance. Both Chrome net stack +// and adb tool use it. +int DisableNagle(int socket); + +// Wake up listener only when data arrive. +int DeferAccept(int socket); + +// Dumps a binary buffer into a string in a human-readable format. +std::string DumpBinary(const char* buffer, size_t length); + +} // namespace tools + +#endif // TOOLS_ANDROID_COMMON_NET_H_ + diff --git a/tools/android/device_stats_monitor/device_stats_monitor.cc b/tools/android/device_stats_monitor/device_stats_monitor.cc new file mode 100644 index 0000000000..6388cdaf43 --- /dev/null +++ b/tools/android/device_stats_monitor/device_stats_monitor.cc @@ -0,0 +1,108 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Dumps CPU and IO stats to a file at a regular interval. +// +// Output may be post processed by host to get top/iotop style information. + +#include +#include + +#include +#include +#include + +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/strings/string_split.h" + +namespace { + +const char kIOStatsPath[] = "/proc/diskstats"; +const char kCPUStatsPath[] = "/proc/stat"; + +class DeviceStatsMonitor { + public: + explicit DeviceStatsMonitor(const std::string& out_path) + : out_path_(out_path), + record_(true) { + CHECK(!out_path_.empty()); + samples_.reserve(1024 * 1024); + } + + // Records stats continuously at |hz| cycles per second util + // StopRecordingAndDumpStats() is called. + // + // Yes, this buffers everything in memory, so it cannot be used for extended + // durations without OOM. But that beats writing during the trace which + // would affect the results. + void Start(int hz) { + const int sample_interval = 1000000 / hz; + const base::FilePath io_stats_path(kIOStatsPath); + const base::FilePath cpu_stats_path(kCPUStatsPath); + std::string out; + while (record_) { + out.clear(); + CHECK(file_util::ReadFileToString(io_stats_path, &out)); + CHECK(file_util::ReadFileToString(cpu_stats_path, &out)); + samples_.push_back(out); + usleep(sample_interval); + } + } + + // Stops recording and saves samples to file. + void StopAndDumpStats() { + record_ = false; + usleep(250 * 1000); + std::ofstream out_stream; + out_stream.open(out_path_.value().c_str(), std::ios::out); + for (std::vector::const_iterator i = samples_.begin(); + i != samples_.end(); ++i) { + out_stream << i->c_str() << std::endl; + } + out_stream.close(); + } + + private: + const base::FilePath out_path_; + std::vector samples_; + bool record_; + + DISALLOW_COPY_AND_ASSIGN(DeviceStatsMonitor); +}; + +DeviceStatsMonitor* g_device_stats_monitor = NULL; + +void SigTermHandler(int unused) { + printf("Stopping device stats monitor\n"); + g_device_stats_monitor->StopAndDumpStats(); +} + +} // namespace + +int main(int argc, char** argv) { + const int kDefaultHz = 20; + + CommandLine command_line(argc, argv); + CommandLine::StringVector args = command_line.GetArgs(); + if (command_line.HasSwitch("h") || command_line.HasSwitch("help") || + args.size() != 1) { + printf("Usage: %s OUTPUT_FILE\n" + " --hz=HZ Number of samples/second. default=%d\n", + argv[0], kDefaultHz); + return 1; + } + + int hz = command_line.HasSwitch("hz") ? + atoi(command_line.GetSwitchValueNative("hz").c_str()) : + kDefaultHz; + + printf("Starting device stats monitor\n"); + g_device_stats_monitor = new DeviceStatsMonitor(args[0]); + signal(SIGTERM, SigTermHandler); + g_device_stats_monitor->Start(hz); + delete g_device_stats_monitor; + + return 0; +} diff --git a/tools/android/device_stats_monitor/device_stats_monitor.gyp b/tools/android/device_stats_monitor/device_stats_monitor.gyp new file mode 100644 index 0000000000..f9bd9e8e4b --- /dev/null +++ b/tools/android/device_stats_monitor/device_stats_monitor.gyp @@ -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. + +{ + 'targets': [ + { + 'target_name': 'device_stats_monitor', + 'type': 'none', + 'dependencies': [ + 'device_stats_monitor_symbols', + ], + 'actions': [ + { + 'action_name': 'strip_device_stats_monitor', + 'inputs': ['<(PRODUCT_DIR)/device_stats_monitor_symbols'], + 'outputs': ['<(PRODUCT_DIR)/device_stats_monitor'], + 'action': [ + '<(android_strip)', + '--strip-unneeded', + '<@(_inputs)', + '-o', + '<@(_outputs)', + ], + }, + ], + }, { + 'target_name': 'device_stats_monitor_symbols', + 'type': 'executable', + 'dependencies': [ + '../../../base/base.gyp:base', + ], + 'include_dirs': [ + '../../..', + ], + 'sources': [ + 'device_stats_monitor.cc', + ], + }, + ], +} diff --git a/tools/android/fake_dns/DEPS b/tools/android/fake_dns/DEPS new file mode 100644 index 0000000000..8fa9d48d88 --- /dev/null +++ b/tools/android/fake_dns/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+net", +] diff --git a/tools/android/fake_dns/fake_dns.cc b/tools/android/fake_dns/fake_dns.cc new file mode 100644 index 0000000000..9a055bfd37 --- /dev/null +++ b/tools/android/fake_dns/fake_dns.cc @@ -0,0 +1,238 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "base/basictypes.h" +#include "base/command_line.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/safe_strerror_posix.h" +#include "net/base/big_endian.h" +#include "net/base/net_util.h" +#include "net/dns/dns_protocol.h" +#include "tools/android/common/daemon.h" +#include "tools/android/common/net.h" + +namespace { + +// Mininum request size: 1 question containing 1 QNAME, 1 TYPE and 1 CLASS. +const size_t kMinRequestSize = sizeof(net::dns_protocol::Header) + 6; + +// The name reference in the answer pointing to the name in the query. +// Its format is: highest two bits set to 1, then the offset of the name +// which just follows the header. +const uint16 kPointerToQueryName = + static_cast(0xc000 | sizeof(net::dns_protocol::Header)); + +const uint32 kTTL = 86400; // One day. + +void PError(const char* msg) { + int current_errno = errno; + LOG(ERROR) << "ERROR: " << msg << ": " << safe_strerror(current_errno); +} + +void SendTo(int sockfd, const void* buf, size_t len, int flags, + const sockaddr* dest_addr, socklen_t addrlen) { + if (HANDLE_EINTR(sendto(sockfd, buf, len, flags, dest_addr, addrlen)) == -1) + PError("sendto()"); +} + +void CloseFileDescriptor(int fd) { + int old_errno = errno; + (void) HANDLE_EINTR(close(fd)); + errno = old_errno; +} + +void SendRefusedResponse(int sock, const sockaddr_in& client_addr, uint16 id) { + net::dns_protocol::Header response; + response.id = htons(id); + response.flags = htons(net::dns_protocol::kFlagResponse | + net::dns_protocol::kFlagAA | + net::dns_protocol::kFlagRD | + net::dns_protocol::kFlagRA | + net::dns_protocol::kRcodeREFUSED); + response.qdcount = 0; + response.ancount = 0; + response.nscount = 0; + response.arcount = 0; + SendTo(sock, &response, sizeof(response), 0, + reinterpret_cast(&client_addr), sizeof(client_addr)); +} + +void SendResponse(int sock, const sockaddr_in& client_addr, uint16 id, + uint16 qtype, const char* question, size_t question_length) { + net::dns_protocol::Header header; + header.id = htons(id); + header.flags = htons(net::dns_protocol::kFlagResponse | + net::dns_protocol::kFlagAA | + net::dns_protocol::kFlagRD | + net::dns_protocol::kFlagRA | + net::dns_protocol::kRcodeNOERROR); + header.qdcount = htons(1); + header.ancount = htons(1); + header.nscount = 0; + header.arcount = 0; + + // Size of RDATA which is a IPv4 or IPv6 address. + size_t rdata_size = qtype == net::dns_protocol::kTypeA ? + net::kIPv4AddressSize : net::kIPv6AddressSize; + + // Size of the whole response which contains the header, the question and + // the answer. 12 is the sum of sizes of the compressed name reference, TYPE, + // CLASS, TTL and RDLENGTH. + size_t response_size = sizeof(header) + question_length + 12 + rdata_size; + + if (response_size > net::dns_protocol::kMaxUDPSize) { + LOG(ERROR) << "Response is too large: " << response_size; + SendRefusedResponse(sock, client_addr, id); + return; + } + + char response[net::dns_protocol::kMaxUDPSize]; + net::BigEndianWriter writer(response, arraysize(response)); + writer.WriteBytes(&header, sizeof(header)); + + // Repeat the question in the response. Some clients (e.g. ping) needs this. + writer.WriteBytes(question, question_length); + + // Construct the answer. + writer.WriteU16(kPointerToQueryName); + writer.WriteU16(qtype); + writer.WriteU16(net::dns_protocol::kClassIN); + writer.WriteU32(kTTL); + writer.WriteU16(rdata_size); + if (qtype == net::dns_protocol::kTypeA) + writer.WriteU32(INADDR_LOOPBACK); + else + writer.WriteBytes(&in6addr_loopback, sizeof(in6_addr)); + DCHECK(writer.ptr() - response == response_size); + + SendTo(sock, response, response_size, 0, + reinterpret_cast(&client_addr), sizeof(client_addr)); +} + +void HandleRequest(int sock, const char* request, size_t size, + const sockaddr_in& client_addr) { + if (size < kMinRequestSize) { + LOG(ERROR) << "Request is too small " << size + << "\n" << tools::DumpBinary(request, size); + return; + } + + net::BigEndianReader reader(request, size); + net::dns_protocol::Header header; + reader.ReadBytes(&header, sizeof(header)); + uint16 id = ntohs(header.id); + uint16 flags = ntohs(header.flags); + uint16 qdcount = ntohs(header.qdcount); + uint16 ancount = ntohs(header.ancount); + uint16 nscount = ntohs(header.nscount); + uint16 arcount = ntohs(header.arcount); + + const uint16 kAllowedFlags = 0x07ff; + if ((flags & ~kAllowedFlags) || + qdcount != 1 || ancount || nscount || arcount) { + LOG(ERROR) << "Unsupported request: FLAGS=" << flags + << " QDCOUNT=" << qdcount + << " ANCOUNT=" << ancount + << " NSCOUNT=" << nscount + << " ARCOUNT=" << arcount + << "\n" << tools::DumpBinary(request, size); + SendRefusedResponse(sock, client_addr, id); + return; + } + + // request[size - 5] should be the end of the QNAME (a zero byte). + // We don't care about the validity of QNAME because we don't parse it. + const char* qname_end = &request[size - 5]; + if (*qname_end) { + LOG(ERROR) << "Error parsing QNAME\n" << tools::DumpBinary(request, size); + SendRefusedResponse(sock, client_addr, id); + return; + } + + reader.Skip(qname_end - reader.ptr() + 1); + + uint16 qtype; + uint16 qclass; + reader.ReadU16(&qtype); + reader.ReadU16(&qclass); + if ((qtype != net::dns_protocol::kTypeA && + qtype != net::dns_protocol::kTypeAAAA) || + qclass != net::dns_protocol::kClassIN) { + LOG(ERROR) << "Unsupported query: QTYPE=" << qtype << " QCLASS=" << qclass + << "\n" << tools::DumpBinary(request, size); + SendRefusedResponse(sock, client_addr, id); + return; + } + + SendResponse(sock, client_addr, id, qtype, + request + sizeof(header), size - sizeof(header)); +} + +} // namespace + +int main(int argc, char** argv) { + printf("Fake DNS server\n"); + + CommandLine command_line(argc, argv); + if (tools::HasHelpSwitch(command_line) || command_line.GetArgs().size()) { + tools::ShowHelp(argv[0], "", ""); + return 0; + } + + int sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + PError("create socket"); + return 1; + } + + sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr.sin_port = htons(53); + int reuse_addr = 1; + if (HANDLE_EINTR(bind(sock, reinterpret_cast(&addr), + sizeof(addr))) < 0) { + PError("server bind"); + CloseFileDescriptor(sock); + return 1; + } + + if (!tools::HasNoSpawnDaemonSwitch(command_line)) + tools::SpawnDaemon(0); + + while (true) { + sockaddr_in client_addr; + socklen_t client_addr_len = sizeof(client_addr); + char request[net::dns_protocol::kMaxUDPSize]; + int size = HANDLE_EINTR(recvfrom(sock, request, sizeof(request), + MSG_WAITALL, + reinterpret_cast(&client_addr), + &client_addr_len)); + if (size < 0) { + // Unrecoverable error, can only exit. + LOG(ERROR) << "Failed to receive a request: " << strerror(errno); + CloseFileDescriptor(sock); + return 1; + } + + if (size > 0) + HandleRequest(sock, request, size, client_addr); + } +} + diff --git a/tools/android/fake_dns/fake_dns.gyp b/tools/android/fake_dns/fake_dns.gyp new file mode 100644 index 0000000000..0edacfadf0 --- /dev/null +++ b/tools/android/fake_dns/fake_dns.gyp @@ -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. + +{ + 'targets': [ + { + 'target_name': 'fake_dns', + 'type': 'none', + 'dependencies': [ + 'fake_dns_symbols', + ], + 'actions': [ + { + 'action_name': 'strip_fake_dns', + 'inputs': ['<(PRODUCT_DIR)/fake_dns_symbols'], + 'outputs': ['<(PRODUCT_DIR)/fake_dns'], + 'action': [ + '<(android_strip)', + '--strip-unneeded', + '<@(_inputs)', + '-o', + '<@(_outputs)', + ], + }, + ], + }, { + 'target_name': 'fake_dns_symbols', + 'type': 'executable', + 'dependencies': [ + '../../../base/base.gyp:base', + '../../../net/net.gyp:net', + '../common/common.gyp:android_tools_common', + ], + 'include_dirs': [ + '../../..', + ], + 'sources': [ + 'fake_dns.cc', + ], + }, + ], +} + diff --git a/tools/android/find_unused_resources.py b/tools/android/find_unused_resources.py new file mode 100755 index 0000000000..da86e73415 --- /dev/null +++ b/tools/android/find_unused_resources.py @@ -0,0 +1,126 @@ +#!/usr/bin/python +# 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. + +"""Lists unused Java strings and other resources.""" + +import optparse +import re +import subprocess +import sys + + +def GetApkResources(apk_path): + """Returns the types and names of resources packaged in an APK. + + Args: + apk_path: path to the APK. + + Returns: + The resources in the APK as a list of tuples (type, name). Example: + [('drawable', 'arrow'), ('layout', 'month_picker'), ...] + """ + p = subprocess.Popen( + ['aapt', 'dump', 'resources', apk_path], + stdout=subprocess.PIPE) + dump_out, _ = p.communicate() + assert p.returncode == 0, 'aapt dump failed' + matches = re.finditer( + r'^\s+spec resource 0x[0-9a-fA-F]+ [\w.]+:(?P\w+)/(?P\w+)', + dump_out, re.MULTILINE) + return [m.group('type', 'name') for m in matches] + + +def GetUsedResources(source_paths, resource_types): + """Returns the types and names of resources used in Java or resource files. + + Args: + source_paths: a list of files or folders collectively containing all the + Java files, resource files, and the AndroidManifest.xml. + resource_types: a list of resource types to look for. Example: + ['string', 'drawable'] + + Returns: + The resources referenced by the Java and resource files as a list of tuples + (type, name). Example: + [('drawable', 'app_icon'), ('layout', 'month_picker'), ...] + """ + type_regex = '|'.join(map(re.escape, resource_types)) + patterns = [r'@(())(%s)/(\w+)' % type_regex, + r'\b((\w+\.)*)R\.(%s)\.(\w+)' % type_regex] + resources = [] + for pattern in patterns: + p = subprocess.Popen( + ['grep', '-REIhoe', pattern] + source_paths, + stdout=subprocess.PIPE) + grep_out, grep_err = p.communicate() + # Check stderr instead of return code, since return code is 1 when no + # matches are found. + assert not grep_err, 'grep failed' + matches = re.finditer(pattern, grep_out) + for match in matches: + package = match.group(1) + if package == 'android.': + continue + type_ = match.group(3) + name = match.group(4) + resources.append((type_, name)) + return resources + + +def FormatResources(resources): + """Formats a list of resources for printing. + + Args: + resources: a list of resources, given as (type, name) tuples. + """ + return '\n'.join(['%-12s %s' % (t, n) for t, n in sorted(resources)]) + + +def ParseArgs(args): + usage = 'usage: %prog [-v] APK_PATH SOURCE_PATH...' + parser = optparse.OptionParser(usage=usage) + parser.add_option('-v', help='Show verbose output', action='store_true') + options, args = parser.parse_args(args=args) + if len(args) < 2: + parser.error('must provide APK_PATH and SOURCE_PATH arguments') + return options.v, args[0], args[1:] + + +def main(args=None): + verbose, apk_path, source_paths = ParseArgs(args) + apk_resources = GetApkResources(apk_path) + resource_types = list(set([r[0] for r in apk_resources])) + used_resources = GetUsedResources(source_paths, resource_types) + unused_resources = set(apk_resources) - set(used_resources) + undefined_resources = set(used_resources) - set(apk_resources) + + # aapt dump fails silently. Notify the user if things look wrong. + if not apk_resources: + print >> sys.stderr, ( + 'Warning: No resources found in the APK. Did you provide the correct ' + 'APK path?') + if not used_resources: + print >> sys.stderr, ( + 'Warning: No resources references from Java or resource files. Did you ' + 'provide the correct source paths?') + if undefined_resources: + print >> sys.stderr, ( + 'Warning: found %d "undefined" resources that are referenced by Java ' + 'files or by other resources, but are not in the APK. Run with -v to ' + 'see them.' % len(undefined_resources)) + + if verbose: + print '%d undefined resources:' % len(undefined_resources) + print FormatResources(undefined_resources), '\n' + print '%d resources packaged into the APK:' % len(apk_resources) + print FormatResources(apk_resources), '\n' + print '%d used resources:' % len(used_resources) + print FormatResources(used_resources), '\n' + print '%d unused resources:' % len(unused_resources) + print FormatResources(unused_resources) + + +if __name__ == '__main__': + main() diff --git a/tools/android/findbugs_plugin/README b/tools/android/findbugs_plugin/README new file mode 100644 index 0000000000..3ba3f53085 --- /dev/null +++ b/tools/android/findbugs_plugin/README @@ -0,0 +1,15 @@ +This is the FindBugs plugin for chrome on android. + +Currently it detects: +- synchronized method +- synchronized 'this' + +We don't want the synchronized method and synchronized 'this' to be +used, the exception is the synchronized method defined in Android +API. + +The plugin jar file was prebuilt and checked in, to rebuild the +plugin, you need ant, and run below command, the new jar file will +be in lib directory. + +ant install diff --git a/tools/android/findbugs_plugin/build.xml b/tools/android/findbugs_plugin/build.xml new file mode 100644 index 0000000000..09ee13c41f --- /dev/null +++ b/tools/android/findbugs_plugin/build.xml @@ -0,0 +1,48 @@ + + + + + + + Build findbugs_plugin for Chromium Android + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/android/findbugs_plugin/findbugs.xml b/tools/android/findbugs_plugin/findbugs.xml new file mode 100644 index 0000000000..43b1f3417d --- /dev/null +++ b/tools/android/findbugs_plugin/findbugs.xml @@ -0,0 +1,18 @@ + + + + + + + + + + diff --git a/tools/android/findbugs_plugin/findbugs_plugin.gyp b/tools/android/findbugs_plugin/findbugs_plugin.gyp new file mode 100644 index 0000000000..16d06e6c25 --- /dev/null +++ b/tools/android/findbugs_plugin/findbugs_plugin.gyp @@ -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. + +{ + 'targets': [ + { + 'target_name': 'findbugs_plugin_test', + 'type': 'none', + 'variables': { + 'java_in_dir': 'test/java/', + }, + 'includes': [ '../../../build/java.gypi' ], + } + ] +} diff --git a/tools/android/findbugs_plugin/lib/chromiumPlugin.jar b/tools/android/findbugs_plugin/lib/chromiumPlugin.jar new file mode 100644 index 0000000000..6ccf61b430 Binary files /dev/null and b/tools/android/findbugs_plugin/lib/chromiumPlugin.jar differ diff --git a/tools/android/findbugs_plugin/messages.xml b/tools/android/findbugs_plugin/messages.xml new file mode 100644 index 0000000000..aea983bc24 --- /dev/null +++ b/tools/android/findbugs_plugin/messages.xml @@ -0,0 +1,56 @@ + + + + + + + + Chromium FindBugs Plugin +
Adds style checks enforced in the chromium project.
+
+ + +
+ +
+ +
+ + + Shouldn't use synchronized(this) + Shouldn't use synchronized(this), please narrow down the synchronization scope. +
+Shouldn't use synchronized(this), please narrow down the synchronization scope.

+]]> +
+
+ + +
+ +
+ +
+ + + Shouldn't use synchronized method + Shouldn't use synchronized method, please narrow down the synchronization scope. +
+Shouldn't use synchronized method, please narrow down the synchronization scope.

+]]> +
+
+ + CHROMIUM +
diff --git a/tools/android/findbugs_plugin/src/org/chromium/tools/findbugs/plugin/SynchronizedMethodDetector.java b/tools/android/findbugs_plugin/src/org/chromium/tools/findbugs/plugin/SynchronizedMethodDetector.java new file mode 100644 index 0000000000..7a879f6644 --- /dev/null +++ b/tools/android/findbugs_plugin/src/org/chromium/tools/findbugs/plugin/SynchronizedMethodDetector.java @@ -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. + +package org.chromium.tools.findbugs.plugin; + +import edu.umd.cs.findbugs.BugInstance; +import edu.umd.cs.findbugs.BugReporter; +import edu.umd.cs.findbugs.bcel.OpcodeStackDetector; + +import org.apache.bcel.classfile.Code; + +/** + * This class detects the synchronized method. + */ +public class SynchronizedMethodDetector extends OpcodeStackDetector { + + private BugReporter mBugReporter; + + public SynchronizedMethodDetector(BugReporter bugReporter) { + this.mBugReporter = bugReporter; + } + + @Override + public void visit(Code code) { + if (getMethod().isSynchronized()) { + mBugReporter.reportBug(new BugInstance(this, "CHROMIUM_SYNCHRONIZED_METHOD", + NORMAL_PRIORITY) + .addClassAndMethod(this) + .addSourceLine(this)); + } + super.visit(code); + } + + @Override + public void sawOpcode(int arg0) { + } +} diff --git a/tools/android/findbugs_plugin/src/org/chromium/tools/findbugs/plugin/SynchronizedThisDetector.java b/tools/android/findbugs_plugin/src/org/chromium/tools/findbugs/plugin/SynchronizedThisDetector.java new file mode 100644 index 0000000000..330431ba7c --- /dev/null +++ b/tools/android/findbugs_plugin/src/org/chromium/tools/findbugs/plugin/SynchronizedThisDetector.java @@ -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. + +package org.chromium.tools.findbugs.plugin; + +import org.apache.bcel.classfile.Code; + +import edu.umd.cs.findbugs.BugInstance; +import edu.umd.cs.findbugs.BugReporter; +import edu.umd.cs.findbugs.bcel.OpcodeStackDetector; + +/** + * This class detects the synchronized(this). + * + * The pattern of byte code of synchronized(this) is + * aload_0 # Load the 'this' pointer on top of stack + * dup # Duplicate the 'this' pointer + * astore_x # Store this for late use, it might be astore. + * monitorenter + */ +public class SynchronizedThisDetector extends OpcodeStackDetector { + private final int PATTERN[] = {ALOAD_0, DUP, 0xff, 0xff, MONITORENTER}; + + private int mStep = 0; + private BugReporter mBugReporter; + + public SynchronizedThisDetector(BugReporter bugReporter) { + mBugReporter = bugReporter; + } + + @Override + public void visit(Code code) { + mStep = 0; + super.visit(code); + } + + @Override + public void sawOpcode(int seen) { + if (PATTERN[mStep] == seen) { + mStep++; + if (mStep == PATTERN.length) { + mBugReporter.reportBug(new BugInstance(this, "CHROMIUM_SYNCHRONIZED_THIS", + NORMAL_PRIORITY) + .addClassAndMethod(this) + .addSourceLine(this)); + mStep = 0; + return; + } + } else if (mStep == 2) { + // This could be astore_x + switch (seen) { + case ASTORE_0: + case ASTORE_1: + case ASTORE_2: + case ASTORE_3: + mStep += 2; + break; + case ASTORE: + mStep++; + break; + default: + mStep = 0; + break; + } + } else if (mStep == 3) { + // Could be any byte following the ASTORE. + mStep++; + } else { + mStep = 0; + } + } +} diff --git a/tools/android/findbugs_plugin/test/expected_result.txt b/tools/android/findbugs_plugin/test/expected_result.txt new file mode 100644 index 0000000000..076b00703c --- /dev/null +++ b/tools/android/findbugs_plugin/test/expected_result.txt @@ -0,0 +1,3 @@ +M C CSM: Shouldn't use synchronized method, please narrow down the synchronization scope. At SimpleSynchronizedMethod.java +M C CSM: Shouldn't use synchronized method, please narrow down the synchronization scope. At SimpleSynchronizedStaticMethod.java +M C CST: Shouldn't use synchronized(this), please narrow down the synchronization scope. At SimpleSynchronizedThis.java diff --git a/tools/android/findbugs_plugin/test/java/src/org/chromium/tools/findbugs/plugin/SimpleSynchronizedMethod.java b/tools/android/findbugs_plugin/test/java/src/org/chromium/tools/findbugs/plugin/SimpleSynchronizedMethod.java new file mode 100644 index 0000000000..c04a35cb02 --- /dev/null +++ b/tools/android/findbugs_plugin/test/java/src/org/chromium/tools/findbugs/plugin/SimpleSynchronizedMethod.java @@ -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. + +package org.chromium.tools.findbugs.plugin; + +/** + * This class has synchronized method and is used to test + * SynchronizedMethodDetector. + */ +class SimpleSynchronizedMethod { + private int i = 0; + synchronized void synchronizedMethod() { + i++; + } +} diff --git a/tools/android/findbugs_plugin/test/java/src/org/chromium/tools/findbugs/plugin/SimpleSynchronizedStaticMethod.java b/tools/android/findbugs_plugin/test/java/src/org/chromium/tools/findbugs/plugin/SimpleSynchronizedStaticMethod.java new file mode 100644 index 0000000000..0af6582fa0 --- /dev/null +++ b/tools/android/findbugs_plugin/test/java/src/org/chromium/tools/findbugs/plugin/SimpleSynchronizedStaticMethod.java @@ -0,0 +1,15 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.tools.findbugs.plugin; + +/** + * This class is used to test SynchronizedMethodDetector + */ +class SimpleSynchronizedStaticMethod { + private static int i = 0; + synchronized static void synchronizedStaticMethod() { + i++; + } +} diff --git a/tools/android/findbugs_plugin/test/java/src/org/chromium/tools/findbugs/plugin/SimpleSynchronizedThis.java b/tools/android/findbugs_plugin/test/java/src/org/chromium/tools/findbugs/plugin/SimpleSynchronizedThis.java new file mode 100644 index 0000000000..2c7e6fdfb4 --- /dev/null +++ b/tools/android/findbugs_plugin/test/java/src/org/chromium/tools/findbugs/plugin/SimpleSynchronizedThis.java @@ -0,0 +1,19 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.tools.findbugs.plugin; + +/** + * This class has synchronized(this) statement and is used to test + * SynchronizedThisDetector. + */ +class SimpleSynchronizedThis { + private int i = 0; + + void synchronizedThis() { + synchronized(this) { + i++; + } + } +} diff --git a/tools/android/findbugs_plugin/test/run_findbugs_plugin_tests.py b/tools/android/findbugs_plugin/test/run_findbugs_plugin_tests.py new file mode 100755 index 0000000000..c2e1531484 --- /dev/null +++ b/tools/android/findbugs_plugin/test/run_findbugs_plugin_tests.py @@ -0,0 +1,44 @@ +#!/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 is used to test the findbugs plugin, it calls +# build/android/pylib/utils/findbugs.py to analyze the classes in +# org.chromium.tools.findbugs.plugin package, and expects to get the same +# issue with those in expected_result.txt. +# +# Useful command line: +# --rebaseline to generate the expected_result.txt, please make sure don't +# remove the expected result of exsting tests. + + +import optparse +import os +import sys + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), + '..', '..', '..', '..', + 'build', 'android'))) + +from pylib import constants +from pylib.utils import findbugs + + +def main(argv): + parser = findbugs.GetCommonParser() + + options, _ = parser.parse_args() + + if not options.known_bugs: + options.known_bugs = os.path.join(constants.DIR_SOURCE_ROOT, 'tools', + 'android', 'findbugs_plugin', 'test', + 'expected_result.txt') + if not options.only_analyze: + options.only_analyze = 'org.chromium.tools.findbugs.plugin.*' + + return findbugs.Run(options) + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/tools/android/forwarder/forwarder.cc b/tools/android/forwarder/forwarder.cc new file mode 100644 index 0000000000..e77c8065ff --- /dev/null +++ b/tools/android/forwarder/forwarder.cc @@ -0,0 +1,426 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "base/command_line.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "tools/android/common/adb_connection.h" +#include "tools/android/common/daemon.h" +#include "tools/android/common/net.h" + +namespace { + +const pthread_t kInvalidThread = static_cast(-1); +volatile bool g_killed = false; + +void CloseSocket(int fd) { + if (fd >= 0) { + int old_errno = errno; + (void) HANDLE_EINTR(close(fd)); + errno = old_errno; + } +} + +class Buffer { + public: + Buffer() + : bytes_read_(0), + write_offset_(0) { + } + + bool CanRead() { + return bytes_read_ == 0; + } + + bool CanWrite() { + return write_offset_ < bytes_read_; + } + + int Read(int fd) { + int ret = -1; + if (CanRead()) { + ret = HANDLE_EINTR(read(fd, buffer_, kBufferSize)); + if (ret > 0) + bytes_read_ = ret; + } + return ret; + } + + int Write(int fd) { + int ret = -1; + if (CanWrite()) { + ret = HANDLE_EINTR(write(fd, buffer_ + write_offset_, + bytes_read_ - write_offset_)); + if (ret > 0) { + write_offset_ += ret; + if (write_offset_ == bytes_read_) { + write_offset_ = 0; + bytes_read_ = 0; + } + } + } + return ret; + } + + private: + // A big buffer to let our file-over-http bridge work more like real file. + static const int kBufferSize = 1024 * 128; + int bytes_read_; + int write_offset_; + char buffer_[kBufferSize]; + + DISALLOW_COPY_AND_ASSIGN(Buffer); +}; + +class Server; + +struct ForwarderThreadInfo { + ForwarderThreadInfo(Server* a_server, int a_forwarder_index) + : server(a_server), + forwarder_index(a_forwarder_index) { + } + Server* server; + int forwarder_index; +}; + +struct ForwarderInfo { + time_t start_time; + int socket1; + time_t socket1_last_byte_time; + size_t socket1_bytes; + int socket2; + time_t socket2_last_byte_time; + size_t socket2_bytes; +}; + +class Server { + public: + Server() + : thread_(kInvalidThread), + socket_(-1) { + memset(forward_to_, 0, sizeof(forward_to_)); + memset(&forwarders_, 0, sizeof(forwarders_)); + } + + int GetFreeForwarderIndex() { + for (int i = 0; i < kMaxForwarders; i++) { + if (forwarders_[i].start_time == 0) + return i; + } + return -1; + } + + void DisposeForwarderInfo(int index) { + forwarders_[index].start_time = 0; + } + + ForwarderInfo* GetForwarderInfo(int index) { + return &forwarders_[index]; + } + + void DumpInformation() { + LOG(INFO) << "Server information: " << forward_to_; + LOG(INFO) << "No.: age up(bytes,idle) down(bytes,idle)"; + int count = 0; + time_t now = time(NULL); + for (int i = 0; i < kMaxForwarders; i++) { + const ForwarderInfo& info = forwarders_[i]; + if (info.start_time) { + count++; + LOG(INFO) << count << ": " << now - info.start_time << " up(" + << info.socket1_bytes << "," + << now - info.socket1_last_byte_time << " down(" + << info.socket2_bytes << "," + << now - info.socket2_last_byte_time << ")"; + } + } + } + + void Shutdown() { + if (socket_ >= 0) + shutdown(socket_, SHUT_RDWR); + } + + bool InitSocket(const char* arg); + + void StartThread() { + pthread_create(&thread_, NULL, ServerThread, this); + } + + void JoinThread() { + if (thread_ != kInvalidThread) + pthread_join(thread_, NULL); + } + + private: + static void* ServerThread(void* arg); + + // There are 3 kinds of threads that will access the array: + // 1. Server thread will get a free ForwarderInfo and initialize it; + // 2. Forwarder threads will dispose the ForwarderInfo when it finishes; + // 3. Main thread will iterate and print the forwarders. + // Using an array is not optimal, but can avoid locks or other complex + // inter-thread communication. + static const int kMaxForwarders = 512; + ForwarderInfo forwarders_[kMaxForwarders]; + + pthread_t thread_; + int socket_; + char forward_to_[40]; + + DISALLOW_COPY_AND_ASSIGN(Server); +}; + +// Forwards all outputs from one socket to another socket. +void* ForwarderThread(void* arg) { + ForwarderThreadInfo* thread_info = + reinterpret_cast(arg); + Server* server = thread_info->server; + int index = thread_info->forwarder_index; + delete thread_info; + ForwarderInfo* info = server->GetForwarderInfo(index); + int socket1 = info->socket1; + int socket2 = info->socket2; + int nfds = socket1 > socket2 ? socket1 + 1 : socket2 + 1; + fd_set read_fds; + fd_set write_fds; + Buffer buffer1; + Buffer buffer2; + + while (!g_killed) { + FD_ZERO(&read_fds); + if (buffer1.CanRead()) + FD_SET(socket1, &read_fds); + if (buffer2.CanRead()) + FD_SET(socket2, &read_fds); + + FD_ZERO(&write_fds); + if (buffer1.CanWrite()) + FD_SET(socket2, &write_fds); + if (buffer2.CanWrite()) + FD_SET(socket1, &write_fds); + + if (HANDLE_EINTR(select(nfds, &read_fds, &write_fds, NULL, NULL)) <= 0) { + LOG(ERROR) << "Select error: " << strerror(errno); + break; + } + + int now = time(NULL); + if (FD_ISSET(socket1, &read_fds)) { + info->socket1_last_byte_time = now; + int bytes = buffer1.Read(socket1); + if (bytes <= 0) + break; + info->socket1_bytes += bytes; + } + if (FD_ISSET(socket2, &read_fds)) { + info->socket2_last_byte_time = now; + int bytes = buffer2.Read(socket2); + if (bytes <= 0) + break; + info->socket2_bytes += bytes; + } + if (FD_ISSET(socket1, &write_fds)) { + if (buffer2.Write(socket1) <= 0) + break; + } + if (FD_ISSET(socket2, &write_fds)) { + if (buffer1.Write(socket2) <= 0) + break; + } + } + + CloseSocket(socket1); + CloseSocket(socket2); + server->DisposeForwarderInfo(index); + return NULL; +} + +// Listens to a server socket. On incoming request, forward it to the host. +// static +void* Server::ServerThread(void* arg) { + Server* server = reinterpret_cast(arg); + while (!g_killed) { + int forwarder_index = server->GetFreeForwarderIndex(); + if (forwarder_index < 0) { + LOG(ERROR) << "Too many forwarders"; + continue; + } + + struct sockaddr_in addr; + socklen_t addr_len = sizeof(addr); + int socket = HANDLE_EINTR(accept(server->socket_, + reinterpret_cast(&addr), + &addr_len)); + if (socket < 0) { + LOG(ERROR) << "Failed to accept: " << strerror(errno); + break; + } + tools::DisableNagle(socket); + + int host_socket = tools::ConnectAdbHostSocket(server->forward_to_); + if (host_socket >= 0) { + // Set NONBLOCK flag because we use select(). + fcntl(socket, F_SETFL, fcntl(socket, F_GETFL) | O_NONBLOCK); + fcntl(host_socket, F_SETFL, fcntl(host_socket, F_GETFL) | O_NONBLOCK); + + ForwarderInfo* forwarder_info = server->GetForwarderInfo(forwarder_index); + time_t now = time(NULL); + forwarder_info->start_time = now; + forwarder_info->socket1 = socket; + forwarder_info->socket1_last_byte_time = now; + forwarder_info->socket1_bytes = 0; + forwarder_info->socket2 = host_socket; + forwarder_info->socket2_last_byte_time = now; + forwarder_info->socket2_bytes = 0; + + pthread_t thread; + pthread_create(&thread, NULL, ForwarderThread, + new ForwarderThreadInfo(server, forwarder_index)); + } else { + // Close the unused client socket which is failed to connect to host. + CloseSocket(socket); + } + } + + CloseSocket(server->socket_); + server->socket_ = -1; + return NULL; +} + +// Format of arg: [::] +bool Server::InitSocket(const char* arg) { + char* endptr; + int local_port = static_cast(strtol(arg, &endptr, 10)); + if (local_port < 0) + return false; + + if (*endptr != ':') { + snprintf(forward_to_, sizeof(forward_to_), "%d:127.0.0.1", local_port); + } else { + strncpy(forward_to_, endptr + 1, sizeof(forward_to_) - 1); + } + + socket_ = socket(AF_INET, SOCK_STREAM, 0); + if (socket_ < 0) { + perror("server socket"); + return false; + } + tools::DisableNagle(socket_); + + sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr.sin_port = htons(local_port); + int reuse_addr = 1; + setsockopt(socket_, SOL_SOCKET, SO_REUSEADDR, + &reuse_addr, sizeof(reuse_addr)); + tools::DeferAccept(socket_); + if (HANDLE_EINTR(bind(socket_, reinterpret_cast(&addr), + sizeof(addr))) < 0 || + HANDLE_EINTR(listen(socket_, 5)) < 0) { + perror("server bind"); + CloseSocket(socket_); + socket_ = -1; + return false; + } + + if (local_port == 0) { + socklen_t addrlen = sizeof(addr); + if (getsockname(socket_, reinterpret_cast(&addr), &addrlen) + != 0) { + perror("get listen address"); + CloseSocket(socket_); + socket_ = -1; + return false; + } + local_port = ntohs(addr.sin_port); + } + + printf("Forwarding device port %d to host %s\n", local_port, forward_to_); + return true; +} + +int g_server_count = 0; +Server* g_servers = NULL; + +void KillHandler(int unused) { + g_killed = true; + for (int i = 0; i < g_server_count; i++) + g_servers[i].Shutdown(); +} + +void DumpInformation(int unused) { + for (int i = 0; i < g_server_count; i++) + g_servers[i].DumpInformation(); +} + +} // namespace + +int main(int argc, char** argv) { + printf("Android device to host TCP forwarder\n"); + printf("Like 'adb forward' but in the reverse direction\n"); + + CommandLine command_line(argc, argv); + CommandLine::StringVector server_args = command_line.GetArgs(); + if (tools::HasHelpSwitch(command_line) || server_args.empty()) { + tools::ShowHelp( + argv[0], + "[::] ...", + " default is \n" + " default is 127.0.0.1\n" + "If is 0, a port will by dynamically allocated.\n"); + return 0; + } + + g_servers = new Server[server_args.size()]; + g_server_count = 0; + int failed_count = 0; + for (size_t i = 0; i < server_args.size(); i++) { + if (!g_servers[g_server_count].InitSocket(server_args[i].c_str())) { + printf("Couldn't start forwarder server for port spec: %s\n", + server_args[i].c_str()); + ++failed_count; + } else { + ++g_server_count; + } + } + + if (g_server_count == 0) { + printf("No forwarder servers could be started. Exiting.\n"); + delete [] g_servers; + return failed_count; + } + + if (!tools::HasNoSpawnDaemonSwitch(command_line)) + tools::SpawnDaemon(failed_count); + + signal(SIGTERM, KillHandler); + signal(SIGUSR2, DumpInformation); + + for (int i = 0; i < g_server_count; i++) + g_servers[i].StartThread(); + for (int i = 0; i < g_server_count; i++) + g_servers[i].JoinThread(); + g_server_count = 0; + delete [] g_servers; + + return 0; +} + diff --git a/tools/android/forwarder/forwarder.gyp b/tools/android/forwarder/forwarder.gyp new file mode 100644 index 0000000000..1df518b1bb --- /dev/null +++ b/tools/android/forwarder/forwarder.gyp @@ -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. + +{ + 'targets': [ + { + 'target_name': 'forwarder', + 'type': 'none', + 'dependencies': [ + 'forwarder_symbols', + ], + 'actions': [ + { + 'action_name': 'strip_forwarder', + 'inputs': ['<(PRODUCT_DIR)/forwarder_symbols'], + 'outputs': ['<(PRODUCT_DIR)/forwarder'], + 'action': [ + '<(android_strip)', + '--strip-unneeded', + '<@(_inputs)', + '-o', + '<@(_outputs)', + ], + }, + ], + }, { + 'target_name': 'forwarder_symbols', + 'type': 'executable', + 'dependencies': [ + '../../../base/base.gyp:base', + '../common/common.gyp:android_tools_common', + ], + 'include_dirs': [ + '../../..', + ], + 'sources': [ + 'forwarder.cc', + ], + }, + ], +} + diff --git a/tools/android/forwarder2/command.cc b/tools/android/forwarder2/command.cc new file mode 100644 index 0000000000..9b0aa24cfb --- /dev/null +++ b/tools/android/forwarder2/command.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 "tools/android/forwarder2/command.h" + +#include +#include +#include +#include + +#include "base/logging.h" +#include "base/safe_strerror_posix.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_piece.h" +#include "tools/android/forwarder2/socket.h" + +using base::StringPiece; + +namespace { + + +// Command format: +// : +// +// Where: +// is a 5-chars zero-padded ASCII decimal integer +// matching the target port for the command (e.g. +// '08080' for port 8080) +// is a 3-char zero-padded ASCII decimal integer +// matching a command::Type value (e.g. 002 for +// ACK). +// The column (:) is used as a separator for easier reading. +const int kPortStringSize = 5; +const int kCommandTypeStringSize = 2; +// Command string size also includes the ':' separator char. +const int kCommandStringSize = kPortStringSize + kCommandTypeStringSize + 1; + +} // namespace + +namespace forwarder2 { + +bool ReadCommand(Socket* socket, + int* port_out, + command::Type* command_type_out) { + char command_buffer[kCommandStringSize + 1]; + // To make logging easier. + command_buffer[kCommandStringSize] = '\0'; + + int bytes_read = socket->ReadNumBytes(command_buffer, kCommandStringSize); + if (bytes_read != kCommandStringSize) { + if (bytes_read < 0) + LOG(ERROR) << "Read() error: " << safe_strerror(errno); + else if (!bytes_read) + LOG(ERROR) << "Read() error, endpoint was unexpectedly closed."; + else + LOG(ERROR) << "Read() error, not enough data received from the socket."; + return false; + } + + StringPiece port_str(command_buffer, kPortStringSize); + if (!StringToInt(port_str, port_out)) { + LOG(ERROR) << "Could not parse the command port string: " + << port_str; + return false; + } + + StringPiece command_type_str( + &command_buffer[kPortStringSize + 1], kCommandTypeStringSize); + int command_type; + if (!StringToInt(command_type_str, &command_type)) { + LOG(ERROR) << "Could not parse the command type string: " + << command_type_str; + return false; + } + *command_type_out = static_cast(command_type); + return true; +} + +bool SendCommand(command::Type command, int port, Socket* socket) { + char buffer[kCommandStringSize + 1]; + int len = snprintf(buffer, sizeof(buffer), "%05d:%02d", port, command); + CHECK_EQ(len, kCommandStringSize); + // Write the full command minus the leading \0 char. + return socket->WriteNumBytes(buffer, len) == len; +} + +bool ReceivedCommand(command::Type command, Socket* socket) { + int port; + command::Type received_command; + if (!ReadCommand(socket, &port, &received_command)) + return false; + return received_command == command; +} + +} // namespace forwarder diff --git a/tools/android/forwarder2/command.h b/tools/android/forwarder2/command.h new file mode 100644 index 0000000000..8e222ef7bb --- /dev/null +++ b/tools/android/forwarder2/command.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 TOOLS_ANDROID_FORWARDER2_COMMAND_H_ +#define TOOLS_ANDROID_FORWARDER2_COMMAND_H_ + +#include "base/basictypes.h" + +namespace forwarder2 { + +class Socket; + +namespace command { + +enum Type { + ACCEPT_ERROR = 0, + ACCEPT_SUCCESS, + ACK, + ADB_DATA_SOCKET_ERROR, + ADB_DATA_SOCKET_SUCCESS, + BIND_ERROR, + BIND_SUCCESS, + DATA_CONNECTION, + HOST_SERVER_ERROR, + HOST_SERVER_SUCCESS, + KILL_ALL_LISTENERS, + LISTEN, + UNLISTEN, + UNLISTEN_ERROR, + UNLISTEN_SUCCESS, +}; + +} // namespace command + +bool ReadCommand(Socket* socket, + int* port_out, + command::Type* command_type_out); + +// Helper function to read the command from the |socket| and return true if the +// |command| is equal to the given command parameter. +bool ReceivedCommand(command::Type command, Socket* socket); + +bool SendCommand(command::Type command, int port, Socket* socket); + +} // namespace forwarder + +#endif // TOOLS_ANDROID_FORWARDER2_COMMAND_H_ diff --git a/tools/android/forwarder2/common.cc b/tools/android/forwarder2/common.cc new file mode 100644 index 0000000000..c97ed8056b --- /dev/null +++ b/tools/android/forwarder2/common.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 "tools/android/forwarder2/common.h" + +#include +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/safe_strerror_posix.h" + +namespace forwarder2 { + +void PError(const char* msg) { + LOG(ERROR) << msg << ": " << safe_strerror(errno); +} + +void CloseFD(int fd) { + const int errno_copy = errno; + if (HANDLE_EINTR(close(fd)) < 0) { + PError("close"); + errno = errno_copy; + } +} + +} // namespace forwarder2 diff --git a/tools/android/forwarder2/common.h b/tools/android/forwarder2/common.h new file mode 100644 index 0000000000..43de57b160 --- /dev/null +++ b/tools/android/forwarder2/common.h @@ -0,0 +1,89 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Common helper functions/classes used both in the host and device forwarder. + +#ifndef TOOLS_ANDROID_FORWARDER2_COMMON_H_ +#define TOOLS_ANDROID_FORWARDER2_COMMON_H_ + +#include +#include +#include + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" + +// Preserving errno for Close() is important because the function is very often +// used in cleanup code, after an error occurred, and it is very easy to pass an +// invalid file descriptor to close() in this context, or more rarely, a +// spurious signal might make close() return -1 + setting errno to EINTR, +// masking the real reason for the original error. This leads to very unpleasant +// debugging sessions. +#define PRESERVE_ERRNO_HANDLE_EINTR(Func) \ + do { \ + int local_errno = errno; \ + (void) HANDLE_EINTR(Func); \ + errno = local_errno; \ + } while (false); + +// Wrapper around RAW_LOG() which is signal-safe. The only purpose of this macro +// is to avoid documenting uses of RawLog(). +#define SIGNAL_SAFE_LOG(Level, Msg) \ + RAW_LOG(Level, Msg); + +namespace forwarder2 { + +// Note that the two following functions are not signal-safe. + +// Chromium logging-aware implementation of libc's perror(). +void PError(const char* msg); + +// Closes the provided file descriptor and logs an error if it failed. +void CloseFD(int fd); + +// Helps build a formatted C-string allocated in a fixed-size array. This is +// useful in signal handlers where base::StringPrintf() can't be used safely +// (due to its use of LOG()). +template +class FixedSizeStringBuilder { + public: + FixedSizeStringBuilder() { + Reset(); + } + + const char* buffer() const { return buffer_; } + + void Reset() { + buffer_[0] = 0; + write_ptr_ = buffer_; + } + + // Returns the number of bytes appended to the underlying buffer or -1 if it + // failed. + int Append(const char* format, ...) PRINTF_FORMAT(/* + 1 for 'this' */ 2, 3) { + if (write_ptr_ >= buffer_ + BufferSize) + return -1; + va_list ap; + va_start(ap, format); + const int bytes_written = vsnprintf( + write_ptr_, BufferSize - (write_ptr_ - buffer_), format, ap); + va_end(ap); + if (bytes_written > 0) + write_ptr_ += bytes_written; + return bytes_written; + } + + private: + char* write_ptr_; + char buffer_[BufferSize]; + + COMPILE_ASSERT(BufferSize >= 1, Size_of_buffer_must_be_at_least_one); + DISALLOW_COPY_AND_ASSIGN(FixedSizeStringBuilder); +}; + +} // namespace forwarder2 + +#endif // TOOLS_ANDROID_FORWARDER2_COMMON_H_ diff --git a/tools/android/forwarder2/daemon.cc b/tools/android/forwarder2/daemon.cc new file mode 100644 index 0000000000..0ca89d1a5f --- /dev/null +++ b/tools/android/forwarder2/daemon.cc @@ -0,0 +1,288 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "tools/android/forwarder2/daemon.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "base/basictypes.h" +#include "base/file_util.h" +#include "base/files/file_path.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/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "tools/android/forwarder2/common.h" +#include "tools/android/forwarder2/socket.h" + +namespace forwarder2 { +namespace { + +const int kBufferSize = 256; + +// Timeout constant used for polling when connecting to the daemon's Unix Domain +// Socket and also when waiting for its death when it is killed. +const int kNumTries = 100; +const int kIdleTimeMSec = 20; + +void InitLoggingForDaemon(const std::string& log_file) { + logging::LoggingSettings settings; + settings.logging_dest = + log_file.empty() ? + logging::LOG_TO_SYSTEM_DEBUG_LOG : logging::LOG_TO_FILE; + settings.log_file = log_file.c_str(); + settings.lock_log = logging::DONT_LOCK_LOG_FILE; + settings.dcheck_state = + logging::ENABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS; + CHECK(logging::InitLogging(settings)); +} + +bool RunServerAcceptLoop(const std::string& welcome_message, + Socket* server_socket, + Daemon::ServerDelegate* server_delegate) { + bool failed = false; + for (;;) { + scoped_ptr client_socket(new Socket()); + if (!server_socket->Accept(client_socket.get())) { + if (server_socket->DidReceiveEvent()) + break; + PError("Accept()"); + failed = true; + break; + } + if (!client_socket->Write(welcome_message.c_str(), + welcome_message.length() + 1)) { + PError("Write()"); + failed = true; + continue; + } + server_delegate->OnClientConnected(client_socket.Pass()); + } + return !failed; +} + +void SigChildHandler(int signal_number) { + DCHECK_EQ(signal_number, SIGCHLD); + int status; + pid_t child_pid = waitpid(-1 /* any child */, &status, WNOHANG); + if (child_pid < 0) { + PError("waitpid"); + return; + } + if (child_pid == 0) + return; + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) + return; + // Avoid using StringAppendF() since it's unsafe in a signal handler due to + // its use of LOG(). + FixedSizeStringBuilder<256> string_builder; + string_builder.Append("Daemon (pid=%d) died unexpectedly with ", child_pid); + if (WIFEXITED(status)) + string_builder.Append("status %d.", WEXITSTATUS(status)); + else if (WIFSIGNALED(status)) + string_builder.Append("signal %d.", WTERMSIG(status)); + else + string_builder.Append("unknown reason."); + SIGNAL_SAFE_LOG(ERROR, string_builder.buffer()); +} + +// Note that 0 is written to |lock_owner_pid| in case the file is not locked. +bool GetFileLockOwnerPid(int fd, pid_t* lock_owner_pid) { + struct flock lock_info = {}; + lock_info.l_type = F_WRLCK; + lock_info.l_whence = SEEK_CUR; + const int ret = HANDLE_EINTR(fcntl(fd, F_GETLK, &lock_info)); + if (ret < 0) { + if (errno == EBADF) { + // Assume that the provided file descriptor corresponding to the PID file + // was valid until the daemon removed this file. + *lock_owner_pid = 0; + return true; + } + PError("fcntl"); + return false; + } + if (lock_info.l_type == F_UNLCK) { + *lock_owner_pid = 0; + return true; + } + CHECK_EQ(F_WRLCK /* exclusive lock */, lock_info.l_type); + *lock_owner_pid = lock_info.l_pid; + return true; +} + +scoped_ptr ConnectToUnixDomainSocket( + const std::string& socket_name, + int tries_count, + int idle_time_msec, + const std::string& expected_welcome_message) { + for (int i = 0; i < tries_count; ++i) { + scoped_ptr socket(new Socket()); + if (!socket->ConnectUnix(socket_name)) { + if (idle_time_msec) + usleep(idle_time_msec * 1000); + continue; + } + char buf[kBufferSize]; + DCHECK(expected_welcome_message.length() + 1 <= sizeof(buf)); + memset(buf, 0, sizeof(buf)); + if (socket->Read(buf, expected_welcome_message.length() + 1) < 0) { + perror("read"); + continue; + } + if (expected_welcome_message != buf) { + LOG(ERROR) << "Unexpected message read from daemon: " << buf; + break; + } + return socket.Pass(); + } + return scoped_ptr(); +} + +} // namespace + +Daemon::Daemon(const std::string& log_file_path, + const std::string& identifier, + ClientDelegate* client_delegate, + ServerDelegate* server_delegate, + GetExitNotifierFDCallback get_exit_fd_callback) + : log_file_path_(log_file_path), + identifier_(identifier), + client_delegate_(client_delegate), + server_delegate_(server_delegate), + get_exit_fd_callback_(get_exit_fd_callback) { + DCHECK(client_delegate_); + DCHECK(server_delegate_); + DCHECK(get_exit_fd_callback_); +} + +Daemon::~Daemon() {} + +bool Daemon::SpawnIfNeeded() { + const int kSingleTry = 1; + const int kNoIdleTime = 0; + scoped_ptr client_socket = ConnectToUnixDomainSocket( + identifier_, kSingleTry, kNoIdleTime, identifier_); + if (!client_socket) { + switch (fork()) { + case -1: + PError("fork()"); + return false; + // Child. + case 0: { + if (setsid() < 0) { // Detach the child process from its parent. + PError("setsid()"); + exit(1); + } + InitLoggingForDaemon(log_file_path_); + CloseFD(STDIN_FILENO); + CloseFD(STDOUT_FILENO); + CloseFD(STDERR_FILENO); + const int null_fd = open("/dev/null", O_RDWR); + CHECK_EQ(null_fd, STDIN_FILENO); + CHECK_EQ(dup(null_fd), STDOUT_FILENO); + CHECK_EQ(dup(null_fd), STDERR_FILENO); + Socket command_socket; + if (!command_socket.BindUnix(identifier_)) { + scoped_ptr client_socket = ConnectToUnixDomainSocket( + identifier_, kSingleTry, kNoIdleTime, identifier_); + if (client_socket.get()) { + // The daemon was spawned by a concurrent process. + exit(0); + } + PError("bind()"); + exit(1); + } + server_delegate_->Init(); + command_socket.AddEventFd(get_exit_fd_callback_()); + return RunServerAcceptLoop( + identifier_, &command_socket, server_delegate_); + } + default: + break; + } + } + // Parent. + // Install the custom SIGCHLD handler. + sigset_t blocked_signals_set; + if (sigprocmask(0 /* first arg ignored */, NULL, &blocked_signals_set) < 0) { + PError("sigprocmask()"); + return false; + } + struct sigaction old_action; + struct sigaction new_action; + memset(&new_action, 0, sizeof(new_action)); + new_action.sa_handler = SigChildHandler; + new_action.sa_flags = SA_NOCLDSTOP; + sigemptyset(&new_action.sa_mask); + if (sigaction(SIGCHLD, &new_action, &old_action) < 0) { + PError("sigaction()"); + return false; + } + // Connect to the daemon's Unix Domain Socket. + bool failed = false; + if (!client_socket) { + client_socket = ConnectToUnixDomainSocket( + identifier_, kNumTries, kIdleTimeMSec, identifier_); + if (!client_socket) { + LOG(ERROR) << "Could not connect to daemon's Unix Daemon socket"; + failed = true; + } + } + if (!failed) + client_delegate_->OnDaemonReady(client_socket.get()); + // Restore the previous signal action for SIGCHLD. + if (sigaction(SIGCHLD, &old_action, NULL) < 0) { + PError("sigaction"); + failed = true; + } + return !failed; +} + +bool Daemon::Kill() { + pid_t daemon_pid = Socket::GetUnixDomainSocketProcessOwner(identifier_); + if (daemon_pid < 0) + return true; // No daemon running. + if (kill(daemon_pid, SIGTERM) < 0) { + if (errno == ESRCH /* invalid PID */) + // The daemon exited for some reason (e.g. kill by a process other than + // us) right before the call to kill() above. + return true; + PError("kill"); + return false; + } + for (int i = 0; i < kNumTries; ++i) { + const pid_t previous_pid = daemon_pid; + daemon_pid = Socket::GetUnixDomainSocketProcessOwner(identifier_); + if (daemon_pid < 0) + return true; + // Since we are polling we might not see the 'daemon exited' event if + // another daemon was spawned during our idle period. + if (daemon_pid != previous_pid) { + LOG(WARNING) << "Daemon (pid=" << previous_pid + << ") was successfully killed but a new daemon (pid=" + << daemon_pid << ") seems to be running now."; + return true; + } + usleep(kIdleTimeMSec * 1000); + } + LOG(ERROR) << "Timed out while killing daemon. " + "It might still be tearing down."; + return false; +} + +} // namespace forwarder2 diff --git a/tools/android/forwarder2/daemon.h b/tools/android/forwarder2/daemon.h new file mode 100644 index 0000000000..4b05ea423c --- /dev/null +++ b/tools/android/forwarder2/daemon.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 TOOLS_ANDROID_FORWARDER2_DAEMON_H_ +#define TOOLS_ANDROID_FORWARDER2_DAEMON_H_ + +#include + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" + +namespace forwarder2 { + +class Socket; + +// Provides a way to spawn a daemon and communicate with it. +class Daemon { + public: + // Callback used by the daemon to shutdown properly. See pipe_notifier.h for + // more details. + typedef int (*GetExitNotifierFDCallback)(); + + class ClientDelegate { + public: + virtual ~ClientDelegate() {} + + // Called after the daemon is ready to receive commands. + virtual void OnDaemonReady(Socket* daemon_socket) = 0; + }; + + class ServerDelegate { + public: + virtual ~ServerDelegate() {} + + // Called after the daemon bound its Unix Domain Socket. This can be used to + // setup signal handlers or perform global initialization. + virtual void Init() = 0; + + virtual void OnClientConnected(scoped_ptr client_socket) = 0; + }; + + // |identifier| should be a unique string identifier. It is used to + // bind/connect the underlying Unix Domain Socket. + // Note that this class does not take ownership of |client_delegate| and + // |server_delegate|. + Daemon(const std::string& log_file_path, + const std::string& identifier, + ClientDelegate* client_delegate, + ServerDelegate* server_delegate, + GetExitNotifierFDCallback get_exit_fd_callback); + + ~Daemon(); + + // Returns whether the daemon was successfully spawned. Note that this does + // not necessarily mean that the current process was forked in case the daemon + // is already running. + bool SpawnIfNeeded(); + + // Kills the daemon and blocks until it exited. Returns whether it succeeded. + bool Kill(); + + private: + const std::string log_file_path_; + const std::string identifier_; + ClientDelegate* const client_delegate_; + ServerDelegate* const server_delegate_; + const GetExitNotifierFDCallback get_exit_fd_callback_; + + DISALLOW_COPY_AND_ASSIGN(Daemon); +}; + +} // namespace forwarder2 + +#endif // TOOLS_ANDROID_FORWARDER2_DAEMON_H_ diff --git a/tools/android/forwarder2/device_controller.cc b/tools/android/forwarder2/device_controller.cc new file mode 100644 index 0000000000..87d0e17143 --- /dev/null +++ b/tools/android/forwarder2/device_controller.cc @@ -0,0 +1,154 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "tools/android/forwarder2/device_controller.h" + +#include + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/single_thread_task_runner.h" +#include "tools/android/forwarder2/command.h" +#include "tools/android/forwarder2/device_listener.h" +#include "tools/android/forwarder2/socket.h" + +namespace forwarder2 { + +// static +scoped_ptr DeviceController::Create( + const std::string& adb_unix_socket, + int exit_notifier_fd) { + scoped_ptr device_controller; + scoped_ptr host_socket(new Socket()); + if (!host_socket->BindUnix(adb_unix_socket)) { + PLOG(ERROR) << "Could not BindAndListen DeviceController socket on port " + << adb_unix_socket << ": "; + return device_controller.Pass(); + } + LOG(INFO) << "Listening on Unix Domain Socket " << adb_unix_socket; + device_controller.reset( + new DeviceController(host_socket.Pass(), exit_notifier_fd)); + return device_controller.Pass(); +} + +DeviceController::~DeviceController() { + DCHECK(construction_task_runner_->RunsTasksOnCurrentThread()); +} + +void DeviceController::Start() { + AcceptHostCommandSoon(); +} + +DeviceController::DeviceController(scoped_ptr host_socket, + int exit_notifier_fd) + : host_socket_(host_socket.Pass()), + exit_notifier_fd_(exit_notifier_fd), + construction_task_runner_(base::MessageLoopProxy::current()), + weak_ptr_factory_(this) { + host_socket_->AddEventFd(exit_notifier_fd); +} + +void DeviceController::AcceptHostCommandSoon() { + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&DeviceController::AcceptHostCommandInternal, + base::Unretained(this))); +} + +void DeviceController::AcceptHostCommandInternal() { + scoped_ptr socket(new Socket); + if (!host_socket_->Accept(socket.get())) { + if (!host_socket_->DidReceiveEvent()) + PLOG(ERROR) << "Could not Accept DeviceController socket"; + else + LOG(INFO) << "Received exit notification"; + return; + } + base::ScopedClosureRunner accept_next_client( + base::Bind(&DeviceController::AcceptHostCommandSoon, + base::Unretained(this))); + // So that |socket| doesn't block on read if it has notifications. + socket->AddEventFd(exit_notifier_fd_); + int port; + command::Type command; + if (!ReadCommand(socket.get(), &port, &command)) { + LOG(ERROR) << "Invalid command received."; + return; + } + const ListenersMap::iterator listener_it = listeners_.find(port); + DeviceListener* const listener = listener_it == listeners_.end() + ? static_cast(NULL) : listener_it->second.get(); + switch (command) { + case command::LISTEN: { + if (listener != NULL) { + LOG(WARNING) << "Already forwarding port " << port + << ". Attempting to restart the listener.\n"; + // Note that this deletes the listener object. + listeners_.erase(listener_it); + } + scoped_ptr new_listener( + DeviceListener::Create( + socket.Pass(), port, base::Bind(&DeviceController::DeleteListener, + weak_ptr_factory_.GetWeakPtr()))); + if (!new_listener) + return; + new_listener->Start(); + // |port| can be zero, to allow dynamically allocated port, so instead, we + // call DeviceListener::listener_port() to retrieve the currently + // allocated port to this new listener. + const int listener_port = new_listener->listener_port(); + listeners_.insert( + std::make_pair(listener_port, + linked_ptr(new_listener.release()))); + LOG(INFO) << "Forwarding device port " << listener_port << " to host."; + break; + } + case command::DATA_CONNECTION: + if (listener == NULL) { + LOG(ERROR) << "Data Connection command received, but " + << "listener has not been set up yet for port " << port; + // After this point it is assumed that, once we close our Adb Data + // socket, the Adb forwarder command will propagate the closing of + // sockets all the way to the host side. + break; + } + listener->SetAdbDataSocket(socket.Pass()); + break; + case command::UNLISTEN: + if (!listener) { + SendCommand(command::UNLISTEN_ERROR, port, socket.get()); + break; + } + listeners_.erase(listener_it); + SendCommand(command::UNLISTEN_SUCCESS, port, socket.get()); + break; + default: + // TODO(felipeg): add a KillAllListeners command. + LOG(ERROR) << "Invalid command received. Port: " << port + << " Command: " << command; + } +} + +// static +void DeviceController::DeleteListener( + const base::WeakPtr& device_controller_ptr, + int listener_port) { + DeviceController* const controller = device_controller_ptr.get(); + if (!controller) + return; + DCHECK(controller->construction_task_runner_->RunsTasksOnCurrentThread()); + const ListenersMap::iterator listener_it = controller->listeners_.find( + listener_port); + if (listener_it == controller->listeners_.end()) + return; + const linked_ptr listener = listener_it->second; + // Note that the listener is removed from the map before it gets destroyed in + // case its destructor would access the map. + controller->listeners_.erase(listener_it); +} + +} // namespace forwarder diff --git a/tools/android/forwarder2/device_controller.h b/tools/android/forwarder2/device_controller.h new file mode 100644 index 0000000000..3daedb3688 --- /dev/null +++ b/tools/android/forwarder2/device_controller.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 TOOLS_ANDROID_FORWARDER2_DEVICE_CONTROLLER_H_ +#define TOOLS_ANDROID_FORWARDER2_DEVICE_CONTROLLER_H_ + +#include + +#include "base/basictypes.h" +#include "base/containers/hash_tables.h" +#include "base/memory/linked_ptr.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "tools/android/forwarder2/socket.h" + +namespace base { +class SingleThreadTaskRunner; +} // namespace base + +namespace forwarder2 { + +class DeviceListener; + +// There is a single DeviceController per device_forwarder process, and it is in +// charge of managing all active redirections on the device side (one +// DeviceListener each). +class DeviceController { + public: + static scoped_ptr Create(const std::string& adb_unix_socket, + int exit_notifier_fd); + ~DeviceController(); + + void Start(); + + private: + typedef base::hash_map< + int /* port */, linked_ptr > ListenersMap; + + DeviceController(scoped_ptr host_socket, int exit_notifier_fd); + + void AcceptHostCommandSoon(); + void AcceptHostCommandInternal(); + + // Note that this can end up being called after the DeviceController is + // destroyed which is why a weak pointer is used. + static void DeleteListener( + const base::WeakPtr& device_controller_ptr, + int listener_port); + + const scoped_ptr host_socket_; + // Used to notify the controller to exit. + const int exit_notifier_fd_; + // Lets ensure DeviceListener instances are deleted on the thread they were + // created on. + const scoped_refptr construction_task_runner_; + base::WeakPtrFactory weak_ptr_factory_; + ListenersMap listeners_; + + DISALLOW_COPY_AND_ASSIGN(DeviceController); +}; + +} // namespace forwarder + +#endif // TOOLS_ANDROID_FORWARDER2_DEVICE_CONTROLLER_H_ diff --git a/tools/android/forwarder2/device_forwarder_main.cc b/tools/android/forwarder2/device_forwarder_main.cc new file mode 100644 index 0000000000..cad46f465a --- /dev/null +++ b/tools/android/forwarder2/device_forwarder_main.cc @@ -0,0 +1,169 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include +#include + +#include "base/at_exit.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/strings/string_piece.h" +#include "base/strings/stringprintf.h" +#include "base/threading/thread.h" +#include "tools/android/forwarder2/common.h" +#include "tools/android/forwarder2/daemon.h" +#include "tools/android/forwarder2/device_controller.h" +#include "tools/android/forwarder2/pipe_notifier.h" + +namespace forwarder2 { +namespace { + +// Leaky global instance, accessed from the signal handler. +forwarder2::PipeNotifier* g_notifier = NULL; + +const int kBufSize = 256; + +const char kUnixDomainSocketPath[] = "chrome_device_forwarder"; +const char kDaemonIdentifier[] = "chrome_device_forwarder_daemon"; + +void KillHandler(int /* unused */) { + CHECK(g_notifier); + if (!g_notifier->Notify()) + exit(1); +} + +// Lets the daemon fetch the exit notifier file descriptor. +int GetExitNotifierFD() { + DCHECK(g_notifier); + return g_notifier->receiver_fd(); +} + +class ServerDelegate : public Daemon::ServerDelegate { + public: + ServerDelegate() : initialized_(false) {} + + virtual ~ServerDelegate() { + if (!controller_thread_.get()) + return; + // The DeviceController instance, if any, is constructed on the controller + // thread. Make sure that it gets deleted on that same thread. Note that + // DeleteSoon() is not used here since it would imply reading |controller_| + // from the main thread while it's set on the internal thread. + controller_thread_->message_loop_proxy()->PostTask( + FROM_HERE, + base::Bind(&ServerDelegate::DeleteControllerOnInternalThread, + base::Unretained(this))); + } + + void DeleteControllerOnInternalThread() { + DCHECK( + controller_thread_->message_loop_proxy()->RunsTasksOnCurrentThread()); + controller_.reset(); + } + + // Daemon::ServerDelegate: + virtual void Init() OVERRIDE { + DCHECK(!g_notifier); + g_notifier = new forwarder2::PipeNotifier(); + signal(SIGTERM, KillHandler); + signal(SIGINT, KillHandler); + controller_thread_.reset(new base::Thread("controller_thread")); + controller_thread_->Start(); + } + + virtual void OnClientConnected(scoped_ptr client_socket) OVERRIDE { + if (initialized_) { + client_socket->WriteString("OK"); + return; + } + controller_thread_->message_loop()->PostTask( + FROM_HERE, + base::Bind(&ServerDelegate::StartController, base::Unretained(this), + GetExitNotifierFD(), base::Passed(&client_socket))); + initialized_ = true; + } + + private: + void StartController(int exit_notifier_fd, scoped_ptr client_socket) { + DCHECK(!controller_.get()); + scoped_ptr controller( + DeviceController::Create(kUnixDomainSocketPath, exit_notifier_fd)); + if (!controller.get()) { + client_socket->WriteString( + base::StringPrintf("ERROR: Could not initialize device controller " + "with ADB socket path: %s", + kUnixDomainSocketPath)); + return; + } + controller_.swap(controller); + controller_->Start(); + client_socket->WriteString("OK"); + client_socket->Close(); + } + + scoped_ptr controller_; + scoped_ptr controller_thread_; + bool initialized_; +}; + +class ClientDelegate : public Daemon::ClientDelegate { + public: + ClientDelegate() : has_failed_(false) {} + + bool has_failed() const { return has_failed_; } + + // Daemon::ClientDelegate: + virtual void OnDaemonReady(Socket* daemon_socket) OVERRIDE { + char buf[kBufSize]; + const int bytes_read = daemon_socket->Read( + buf, sizeof(buf) - 1 /* leave space for null terminator */); + CHECK_GT(bytes_read, 0); + DCHECK(bytes_read < sizeof(buf)); + buf[bytes_read] = 0; + base::StringPiece msg(buf, bytes_read); + if (msg.starts_with("ERROR")) { + LOG(ERROR) << msg; + has_failed_ = true; + return; + } + } + + private: + bool has_failed_; +}; + +int RunDeviceForwarder(int argc, char** argv) { + CommandLine::Init(argc, argv); // Needed by logging. + const bool kill_server = CommandLine::ForCurrentProcess()->HasSwitch( + "kill-server"); + if ((kill_server && argc != 2) || (!kill_server && argc != 1)) { + std::cerr << "Usage: device_forwarder [--kill-server]" << std::endl; + return 1; + } + base::AtExitManager at_exit_manager; // Used by base::Thread. + ClientDelegate client_delegate; + ServerDelegate daemon_delegate; + const char kLogFilePath[] = ""; // Log to logcat. + Daemon daemon(kLogFilePath, kDaemonIdentifier, &client_delegate, + &daemon_delegate, &GetExitNotifierFD); + + if (kill_server) + return !daemon.Kill(); + + if (!daemon.SpawnIfNeeded()) + return 1; + return client_delegate.has_failed(); +} + +} // namespace +} // namespace forwarder2 + +int main(int argc, char** argv) { + return forwarder2::RunDeviceForwarder(argc, argv); +} diff --git a/tools/android/forwarder2/device_listener.cc b/tools/android/forwarder2/device_listener.cc new file mode 100644 index 0000000000..1819a8a6ed --- /dev/null +++ b/tools/android/forwarder2/device_listener.cc @@ -0,0 +1,148 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "tools/android/forwarder2/device_listener.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/single_thread_task_runner.h" +#include "tools/android/forwarder2/command.h" +#include "tools/android/forwarder2/forwarder.h" +#include "tools/android/forwarder2/socket.h" + +namespace forwarder2 { + +// static +scoped_ptr DeviceListener::Create( + scoped_ptr host_socket, + int listener_port, + const DeleteCallback& delete_callback) { + scoped_ptr listener_socket(new Socket()); + scoped_ptr device_listener; + if (!listener_socket->BindTcp("", listener_port)) { + LOG(ERROR) << "Device could not bind and listen to local port " + << listener_port; + SendCommand(command::BIND_ERROR, listener_port, host_socket.get()); + return device_listener.Pass(); + } + // In case the |listener_port_| was zero, GetPort() will return the + // currently (non-zero) allocated port for this socket. + listener_port = listener_socket->GetPort(); + SendCommand(command::BIND_SUCCESS, listener_port, host_socket.get()); + device_listener.reset( + new DeviceListener( + scoped_ptr(new PipeNotifier()), listener_socket.Pass(), + host_socket.Pass(), listener_port, delete_callback)); + return device_listener.Pass(); +} + +DeviceListener::~DeviceListener() { + DCHECK(deletion_task_runner_->RunsTasksOnCurrentThread()); + exit_notifier_->Notify(); +} + +void DeviceListener::Start() { + thread_.Start(); + AcceptNextClientSoon(); +} + +void DeviceListener::SetAdbDataSocket(scoped_ptr adb_data_socket) { + thread_.message_loop_proxy()->PostTask( + FROM_HERE, + base::Bind(&DeviceListener::OnAdbDataSocketReceivedOnInternalThread, + base::Unretained(this), base::Passed(&adb_data_socket))); +} + +DeviceListener::DeviceListener(scoped_ptr pipe_notifier, + scoped_ptr listener_socket, + scoped_ptr host_socket, + int port, + const DeleteCallback& delete_callback) + : exit_notifier_(pipe_notifier.Pass()), + listener_socket_(listener_socket.Pass()), + host_socket_(host_socket.Pass()), + listener_port_(port), + delete_callback_(delete_callback), + deletion_task_runner_(base::MessageLoopProxy::current()), + thread_("DeviceListener") { + CHECK(host_socket_.get()); + DCHECK(deletion_task_runner_.get()); + DCHECK(exit_notifier_.get()); + host_socket_->AddEventFd(exit_notifier_->receiver_fd()); + listener_socket_->AddEventFd(exit_notifier_->receiver_fd()); +} + +void DeviceListener::AcceptNextClientSoon() { + thread_.message_loop_proxy()->PostTask( + FROM_HERE, + base::Bind(&DeviceListener::AcceptClientOnInternalThread, + base::Unretained(this))); +} + +void DeviceListener::AcceptClientOnInternalThread() { + device_data_socket_.reset(new Socket()); + if (!listener_socket_->Accept(device_data_socket_.get())) { + if (listener_socket_->DidReceiveEvent()) { + LOG(INFO) << "Received exit notification, stopped accepting clients."; + SelfDelete(); + return; + } + LOG(WARNING) << "Could not Accept in ListenerSocket."; + SendCommand(command::ACCEPT_ERROR, listener_port_, host_socket_.get()); + SelfDelete(); + return; + } + SendCommand(command::ACCEPT_SUCCESS, listener_port_, host_socket_.get()); + if (!ReceivedCommand(command::HOST_SERVER_SUCCESS, + host_socket_.get())) { + SendCommand(command::ACK, listener_port_, host_socket_.get()); + LOG(ERROR) << "Host could not connect to server."; + device_data_socket_->Close(); + if (host_socket_->has_error()) { + LOG(ERROR) << "Adb Control connection lost. " + << "Listener port: " << listener_port_; + SelfDelete(); + return; + } + // It can continue if the host forwarder could not connect to the host + // server but the control connection is still alive (no errors). The device + // acknowledged that (above), and it can re-try later. + AcceptNextClientSoon(); + return; + } +} + +void DeviceListener::OnAdbDataSocketReceivedOnInternalThread( + scoped_ptr adb_data_socket) { + adb_data_socket_.swap(adb_data_socket); + SendCommand(command::ADB_DATA_SOCKET_SUCCESS, listener_port_, + host_socket_.get()); + CHECK(adb_data_socket_.get()); + StartForwarder(device_data_socket_.Pass(), adb_data_socket_.Pass()); + AcceptNextClientSoon(); +} + +void DeviceListener::SelfDelete() { + if (!deletion_task_runner_->RunsTasksOnCurrentThread()) { + deletion_task_runner_->PostTask( + FROM_HERE, + base::Bind(&DeviceListener::SelfDeleteOnDeletionTaskRunner, + delete_callback_, listener_port_)); + return; + } + SelfDeleteOnDeletionTaskRunner(delete_callback_, listener_port_); +} + +// static +void DeviceListener::SelfDeleteOnDeletionTaskRunner( + const DeleteCallback& delete_callback, + int listener_port) { + delete_callback.Run(listener_port); +} + +} // namespace forwarder diff --git a/tools/android/forwarder2/device_listener.h b/tools/android/forwarder2/device_listener.h new file mode 100644 index 0000000000..2a69823196 --- /dev/null +++ b/tools/android/forwarder2/device_listener.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 TOOLS_ANDROID_FORWARDER2_DEVICE_LISTENER_H_ +#define TOOLS_ANDROID_FORWARDER2_DEVICE_LISTENER_H_ + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/thread.h" +#include "tools/android/forwarder2/pipe_notifier.h" +#include "tools/android/forwarder2/socket.h" + +namespace base { +class SingleThreadTaskRunner; +} // namespace base + +namespace forwarder2 { + +class Forwarder; + +// A DeviceListener instance is used in the device_forwarder program to bind to +// a specific device-side |port| and wait for client connections. When a +// connection happens, it informs the corresponding HostController instance +// running on the host, through |host_socket|. Then the class expects a call to +// its SetAdbDataSocket() method (performed by the device controller) once the +// host opened a new connection to the device. When this happens, a new internal +// Forwarder instance is started. +// Note that instances of this class are owned by the device controller which +// creates and destroys them on the same thread. In case an internal error +// happens on the DeviceListener's internal thread, the DeviceListener +// can also self-delete by executing the user-provided callback on the thread +// the DeviceListener was created on. +// Note that the DeviceListener's destructor joins its internal thread (i.e. +// waits for its completion) which means that the internal thread is guaranteed +// not to be running anymore once the object is deleted. +class DeviceListener { + public: + // Callback that is used for self-deletion as a way to let the device + // controller perform some additional cleanup work (e.g. removing the device + // listener instance from its internal map before deleting it). + typedef base::Callback DeleteCallback; + + static scoped_ptr Create( + scoped_ptr host_socket, + int port, + const DeleteCallback& delete_callback); + + ~DeviceListener(); + + void Start(); + + void SetAdbDataSocket(scoped_ptr adb_data_socket); + + int listener_port() const { return listener_port_; } + + private: + DeviceListener(scoped_ptr pipe_notifier, + scoped_ptr listener_socket, + scoped_ptr host_socket, + int port, + const DeleteCallback& delete_callback); + + // Pushes an AcceptClientOnInternalThread() task to the internal thread's + // message queue in order to wait for a new client soon. + void AcceptNextClientSoon(); + + void AcceptClientOnInternalThread(); + + void OnAdbDataSocketReceivedOnInternalThread( + scoped_ptr adb_data_socket); + + void SelfDelete(); + + // Note that this can be called after the DeviceListener instance gets deleted + // which is why this method is static. + static void SelfDeleteOnDeletionTaskRunner( + const DeleteCallback& delete_callback, + int listener_port); + + // Used for the listener thread to be notified on destruction. We have one + // notifier per Listener thread since each Listener thread may be requested to + // exit for different reasons independently from each other and independent + // from the main program, ex. when the host requests to forward/listen the + // same port again. Both the |host_socket_| and |listener_socket_| + // must share the same receiver file descriptor from |exit_notifier_| and it + // is set in the constructor. + const scoped_ptr exit_notifier_; + // The local device listener socket for accepting connections from the local + // port (listener_port_). + const scoped_ptr listener_socket_; + // The listener socket for sending control commands. + const scoped_ptr host_socket_; + scoped_ptr device_data_socket_; + // This is the adb connection to transport the actual data, used for creating + // the forwarder. Ownership transferred to the Forwarder. + scoped_ptr adb_data_socket_; + const int listener_port_; + const DeleteCallback delete_callback_; + // Task runner used for deletion set at construction time (i.e. the object is + // deleted on the same thread it is created on). + scoped_refptr deletion_task_runner_; + base::Thread thread_; + + DISALLOW_COPY_AND_ASSIGN(DeviceListener); +}; + +} // namespace forwarder + +#endif // TOOLS_ANDROID_FORWARDER2_DEVICE_LISTENER_H_ diff --git a/tools/android/forwarder2/forwarder.cc b/tools/android/forwarder2/forwarder.cc new file mode 100644 index 0000000000..df4c29cf9f --- /dev/null +++ b/tools/android/forwarder2/forwarder.cc @@ -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. + +#include "tools/android/forwarder2/forwarder.h" + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/posix/eintr_wrapper.h" +#include "base/single_thread_task_runner.h" +#include "tools/android/forwarder2/socket.h" + +namespace forwarder2 { +namespace { + +// Helper class to buffer reads and writes from one socket to another. +class BufferedCopier { + public: + // Does NOT own the pointers. + BufferedCopier(Socket* socket_from, + Socket* socket_to) + : socket_from_(socket_from), + socket_to_(socket_to), + bytes_read_(0), + write_offset_(0) { + } + + bool AddToReadSet(fd_set* read_fds) { + if (bytes_read_ == 0) + return socket_from_->AddFdToSet(read_fds); + return false; + } + + bool AddToWriteSet(fd_set* write_fds) { + if (write_offset_ < bytes_read_) + return socket_to_->AddFdToSet(write_fds); + return false; + } + + bool TryRead(const fd_set& read_fds) { + if (!socket_from_->IsFdInSet(read_fds)) + return false; + if (bytes_read_ != 0) // Can't read. + return false; + int ret = socket_from_->Read(buffer_, kBufferSize); + if (ret > 0) { + bytes_read_ = ret; + return true; + } + return false; + } + + bool TryWrite(const fd_set& write_fds) { + if (!socket_to_->IsFdInSet(write_fds)) + return false; + if (write_offset_ >= bytes_read_) // Nothing to write. + return false; + int ret = socket_to_->Write(buffer_ + write_offset_, + bytes_read_ - write_offset_); + if (ret > 0) { + write_offset_ += ret; + if (write_offset_ == bytes_read_) { + write_offset_ = 0; + bytes_read_ = 0; + } + return true; + } + return false; + } + + private: + // Not owned. + Socket* socket_from_; + Socket* socket_to_; + + // A big buffer to let our file-over-http bridge work more like real file. + static const int kBufferSize = 1024 * 128; + int bytes_read_; + int write_offset_; + char buffer_[kBufferSize]; + + DISALLOW_COPY_AND_ASSIGN(BufferedCopier); +}; + +// Internal class that wraps a helper thread to forward traffic between +// |socket1| and |socket2|. After creating a new instance, call its Start() +// method to launch operations. Thread stops automatically if one of the socket +// disconnects, but ensures that all buffered writes to the other, still alive, +// socket, are written first. When this happens, the instance will delete itself +// automatically. +// Note that the instance will always be destroyed on the same thread that +// created it. +class Forwarder { + public: + Forwarder(scoped_ptr socket1, scoped_ptr socket2) + : socket1_(socket1.Pass()), + socket2_(socket2.Pass()), + destructor_runner_(base::MessageLoopProxy::current()), + thread_("ForwarderThread") { + } + + void Start() { + thread_.Start(); + thread_.message_loop_proxy()->PostTask( + FROM_HERE, + base::Bind(&Forwarder::ThreadHandler, base::Unretained(this))); + } + + private: + void ThreadHandler() { + const int nfds = Socket::GetHighestFileDescriptor(*socket1_, *socket2_) + 1; + fd_set read_fds; + fd_set write_fds; + + // Copy from socket1 to socket2 + BufferedCopier buffer1(socket1_.get(), socket2_.get()); + // Copy from socket2 to socket1 + BufferedCopier buffer2(socket2_.get(), socket1_.get()); + + bool run = true; + while (run) { + FD_ZERO(&read_fds); + FD_ZERO(&write_fds); + + buffer1.AddToReadSet(&read_fds); + buffer2.AddToReadSet(&read_fds); + buffer1.AddToWriteSet(&write_fds); + buffer2.AddToWriteSet(&write_fds); + + if (HANDLE_EINTR(select(nfds, &read_fds, &write_fds, NULL, NULL)) <= 0) { + PLOG(ERROR) << "select"; + break; + } + // When a socket in the read set closes the connection, select() returns + // with that socket descriptor set as "ready to read". When we call + // TryRead() below, it will return false, but the while loop will continue + // to run until all the write operations are finished, to make sure the + // buffers are completely flushed out. + + // Keep running while we have some operation to do. + run = buffer1.TryRead(read_fds); + run = run || buffer2.TryRead(read_fds); + run = run || buffer1.TryWrite(write_fds); + run = run || buffer2.TryWrite(write_fds); + } + + // Note that the thread that |destruction_runner_| runs tasks on could be + // temporarily blocked on I/O (e.g. select()) therefore it is safer to close + // the sockets now rather than relying on the destructor. + socket1_.reset(); + socket2_.reset(); + + // Note that base::Thread must be destroyed on the thread it was created on. + destructor_runner_->DeleteSoon(FROM_HERE, this); + } + + scoped_ptr socket1_; + scoped_ptr socket2_; + scoped_refptr destructor_runner_; + base::Thread thread_; +}; + +} // namespace + +void StartForwarder(scoped_ptr socket1, scoped_ptr socket2) { + (new Forwarder(socket1.Pass(), socket2.Pass()))->Start(); +} + +} // namespace forwarder2 diff --git a/tools/android/forwarder2/forwarder.gyp b/tools/android/forwarder2/forwarder.gyp new file mode 100644 index 0000000000..fdc19aa4ce --- /dev/null +++ b/tools/android/forwarder2/forwarder.gyp @@ -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. + +{ + 'targets': [ + { + 'target_name': 'forwarder2', + 'type': 'none', + 'dependencies': [ + 'device_forwarder', + 'host_forwarder#host', + ], + # For the component build, ensure dependent shared libraries are stripped + # and put alongside forwarder to simplify pushing to the device. + 'variables': { + 'output_dir': '<(PRODUCT_DIR)/forwarder_dist/', + 'native_binary': '<(PRODUCT_DIR)/device_forwarder', + }, + 'includes': ['../../../build/android/native_app_dependencies.gypi'], + }, + { + 'target_name': 'device_forwarder', + 'type': 'executable', + 'toolsets': ['target'], + 'dependencies': [ + '../../../base/base.gyp:base', + '../common/common.gyp:android_tools_common', + ], + 'include_dirs': [ + '../../..', + ], + 'conditions': [ + # Warning: A PIE tool cannot run on ICS 4.0.4, so only + # build it as position-independent when ASAN + # is activated. See b/6587214 for details. + [ 'asan==1', { + 'cflags': [ + '-fPIE', + ], + 'ldflags': [ + '-pie', + ], + }], + ], + 'sources': [ + 'command.cc', + 'common.cc', + 'daemon.cc', + 'device_controller.cc', + 'device_forwarder_main.cc', + 'device_listener.cc', + 'forwarder.cc', + 'pipe_notifier.cc', + 'socket.cc', + ], + }, + { + 'target_name': 'host_forwarder', + 'type': 'executable', + 'toolsets': ['host'], + 'dependencies': [ + '../../../base/base.gyp:base', + '../common/common.gyp:android_tools_common', + ], + 'include_dirs': [ + '../../..', + ], + 'sources': [ + 'command.cc', + 'common.cc', + 'daemon.cc', + 'forwarder.cc', + 'host_controller.cc', + 'host_forwarder_main.cc', + 'pipe_notifier.cc', + 'socket.cc', + # TODO(pliard): Remove this. This is needed to avoid undefined + # references at link time. + '../../../base/message_loop/message_pump_glib.cc', + '../../../base/message_loop/message_pump_gtk.cc', + ], + }, + ], +} diff --git a/tools/android/forwarder2/forwarder.h b/tools/android/forwarder2/forwarder.h new file mode 100644 index 0000000000..651b5e80f7 --- /dev/null +++ b/tools/android/forwarder2/forwarder.h @@ -0,0 +1,19 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef TOOLS_ANDROID_FORWARDER2_FORWARDER_H_ +#define TOOLS_ANDROID_FORWARDER2_FORWARDER_H_ + +#include "base/memory/scoped_ptr.h" +#include "base/threading/thread.h" + +namespace forwarder2 { + +class Socket; + +void StartForwarder(scoped_ptr socket1, scoped_ptr socket2); + +} // namespace forwarder2 + +#endif // TOOLS_ANDROID_FORWARDER2_FORWARDER_H_ diff --git a/tools/android/forwarder2/host_controller.cc b/tools/android/forwarder2/host_controller.cc new file mode 100644 index 0000000000..1588e7291b --- /dev/null +++ b/tools/android/forwarder2/host_controller.cc @@ -0,0 +1,186 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "tools/android/forwarder2/host_controller.h" + +#include + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "tools/android/forwarder2/command.h" +#include "tools/android/forwarder2/forwarder.h" +#include "tools/android/forwarder2/socket.h" + +namespace forwarder2 { + +// static +scoped_ptr HostController::Create( + int device_port, + int host_port, + int adb_port, + int exit_notifier_fd, + const DeletionCallback& deletion_callback) { + scoped_ptr host_controller; + scoped_ptr delete_controller_notifier(new PipeNotifier()); + scoped_ptr adb_control_socket(new Socket()); + adb_control_socket->AddEventFd(exit_notifier_fd); + adb_control_socket->AddEventFd(delete_controller_notifier->receiver_fd()); + if (!adb_control_socket->ConnectTcp(std::string(), adb_port)) { + LOG(ERROR) << "Could not connect HostController socket on port: " + << adb_port; + return host_controller.Pass(); + } + // Send the command to the device start listening to the "device_forward_port" + bool send_command_success = SendCommand( + command::LISTEN, device_port, adb_control_socket.get()); + CHECK(send_command_success); + int device_port_allocated; + command::Type command; + if (!ReadCommand( + adb_control_socket.get(), &device_port_allocated, &command) || + command != command::BIND_SUCCESS) { + LOG(ERROR) << "Device binding error using port " << device_port; + return host_controller.Pass(); + } + host_controller.reset( + new HostController( + device_port_allocated, host_port, adb_port, exit_notifier_fd, + deletion_callback, adb_control_socket.Pass(), + delete_controller_notifier.Pass())); + return host_controller.Pass(); +} + +HostController::~HostController() { + DCHECK(deletion_task_runner_->RunsTasksOnCurrentThread()); + delete_controller_notifier_->Notify(); + // Note that the Forwarder instance (that also received a delete notification) + // might still be running on its own thread at this point. This is not a + // problem since it will self-delete once the socket that it is operating on + // is closed. +} + +void HostController::Start() { + thread_.Start(); + ReadNextCommandSoon(); +} + +HostController::HostController( + int device_port, + int host_port, + int adb_port, + int exit_notifier_fd, + const DeletionCallback& deletion_callback, + scoped_ptr adb_control_socket, + scoped_ptr delete_controller_notifier) + : device_port_(device_port), + host_port_(host_port), + adb_port_(adb_port), + global_exit_notifier_fd_(exit_notifier_fd), + deletion_callback_(deletion_callback), + adb_control_socket_(adb_control_socket.Pass()), + delete_controller_notifier_(delete_controller_notifier.Pass()), + deletion_task_runner_(base::MessageLoopProxy::current()), + thread_("HostControllerThread") { +} + +void HostController::ReadNextCommandSoon() { + thread_.message_loop_proxy()->PostTask( + FROM_HERE, + base::Bind(&HostController::ReadCommandOnInternalThread, + base::Unretained(this))); +} + +void HostController::ReadCommandOnInternalThread() { + if (!ReceivedCommand(command::ACCEPT_SUCCESS, adb_control_socket_.get())) { + SelfDelete(); + return; + } + // Try to connect to host server. + scoped_ptr host_server_data_socket(CreateSocket()); + if (!host_server_data_socket->ConnectTcp(std::string(), host_port_)) { + LOG(ERROR) << "Could not Connect HostServerData socket on port: " + << host_port_; + SendCommand( + command::HOST_SERVER_ERROR, device_port_, adb_control_socket_.get()); + if (ReceivedCommand(command::ACK, adb_control_socket_.get())) { + // It can continue if the host forwarder could not connect to the host + // server but the device acknowledged that, so that the device could + // re-try later. + ReadNextCommandSoon(); + return; + } + SelfDelete(); + return; + } + SendCommand( + command::HOST_SERVER_SUCCESS, device_port_, adb_control_socket_.get()); + StartForwarder(host_server_data_socket.Pass()); + ReadNextCommandSoon(); +} + +void HostController::StartForwarder( + scoped_ptr host_server_data_socket) { + scoped_ptr adb_data_socket(CreateSocket()); + if (!adb_data_socket->ConnectTcp("", adb_port_)) { + LOG(ERROR) << "Could not connect AdbDataSocket on port: " << adb_port_; + SelfDelete(); + return; + } + // Open the Adb data connection, and send a command with the + // |device_forward_port| as a way for the device to identify the connection. + SendCommand(command::DATA_CONNECTION, device_port_, adb_data_socket.get()); + + // Check that the device received the new Adb Data Connection. Note that this + // check is done through the |adb_control_socket_| that is handled in the + // DeviceListener thread just after the call to WaitForAdbDataSocket(). + if (!ReceivedCommand(command::ADB_DATA_SOCKET_SUCCESS, + adb_control_socket_.get())) { + LOG(ERROR) << "Device could not handle the new Adb Data Connection."; + SelfDelete(); + return; + } + forwarder2::StartForwarder( + host_server_data_socket.Pass(), adb_data_socket.Pass()); +} + +scoped_ptr HostController::CreateSocket() { + scoped_ptr socket(new Socket()); + socket->AddEventFd(global_exit_notifier_fd_); + socket->AddEventFd(delete_controller_notifier_->receiver_fd()); + return socket.Pass(); +} + +void HostController::SelfDelete() { + scoped_ptr self_deleter(this); + deletion_task_runner_->PostTask( + FROM_HERE, + base::Bind(&HostController::SelfDeleteOnDeletionTaskRunner, + deletion_callback_, base::Passed(&self_deleter))); + // Tell the device to delete its corresponding controller instance before we + // self-delete. + Socket socket; + if (!socket.ConnectTcp("", adb_port_)) { + LOG(ERROR) << "Could not connect to device on port " << adb_port_; + return; + } + if (!SendCommand(command::UNLISTEN, device_port_, &socket)) { + LOG(ERROR) << "Could not send unmap command for port " << device_port_; + return; + } + if (!ReceivedCommand(command::UNLISTEN_SUCCESS, &socket)) { + LOG(ERROR) << "Unamp command failed for port " << device_port_; + return; + } +} + +// static +void HostController::SelfDeleteOnDeletionTaskRunner( + const DeletionCallback& deletion_callback, + scoped_ptr controller) { + deletion_callback.Run(controller.Pass()); +} + +} // namespace forwarder2 diff --git a/tools/android/forwarder2/host_controller.h b/tools/android/forwarder2/host_controller.h new file mode 100644 index 0000000000..aaedb945e8 --- /dev/null +++ b/tools/android/forwarder2/host_controller.h @@ -0,0 +1,99 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef TOOLS_ANDROID_FORWARDER2_HOST_CONTROLLER_H_ +#define TOOLS_ANDROID_FORWARDER2_HOST_CONTROLLER_H_ + +#include + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/thread.h" +#include "tools/android/forwarder2/pipe_notifier.h" +#include "tools/android/forwarder2/socket.h" + +namespace forwarder2 { + +// This class partners with DeviceController and has the same lifetime and +// threading characteristics as DeviceListener. In a nutshell, this class +// operates on its own thread and is destroyed on the thread it was constructed +// on. The class' deletion can happen in two different ways: +// - Its destructor was called by its owner (HostControllersManager). +// - Its internal thread requested self-deletion after an error happened. In +// this case the owner (HostControllersManager) is notified on the +// construction thread through the provided DeletionCallback invoked with the +// HostController instance. When this callback is invoked, it's up to the +// owner to delete the instance. +class HostController { + public: + // Callback used for self-deletion that lets the client perform some cleanup + // work before deleting the HostController instance. + typedef base::Callback)> DeletionCallback; + + // If |device_port| is zero then a dynamic port is allocated (and retrievable + // through device_port() below). + static scoped_ptr Create( + int device_port, + int host_port, + int adb_port, + int exit_notifier_fd, + const DeletionCallback& deletion_callback); + + ~HostController(); + + // Starts the internal controller thread. + void Start(); + + int adb_port() const { return adb_port_; } + + int device_port() const { return device_port_; } + + private: + HostController(int device_port, + int host_port, + int adb_port, + int exit_notifier_fd, + const DeletionCallback& deletion_callback, + scoped_ptr adb_control_socket, + scoped_ptr delete_controller_notifier); + + void ReadNextCommandSoon(); + void ReadCommandOnInternalThread(); + + void StartForwarder(scoped_ptr host_server_data_socket); + + // Helper method that creates a socket and adds the appropriate event file + // descriptors. + scoped_ptr CreateSocket(); + + void SelfDelete(); + + static void SelfDeleteOnDeletionTaskRunner( + const DeletionCallback& deletion_callback, + scoped_ptr controller); + + const int device_port_; + const int host_port_; + const int adb_port_; + // Used to notify the controller when the process is killed. + const int global_exit_notifier_fd_; + // Used to let the client delete the instance in case an error happened. + const DeletionCallback deletion_callback_; + scoped_ptr adb_control_socket_; + scoped_ptr delete_controller_notifier_; + // Used to cancel the pending blocking IO operations when the host controller + // instance is deleted. + // Task runner used for deletion set at construction time (i.e. the object is + // deleted on the same thread it is created on). + const scoped_refptr deletion_task_runner_; + base::Thread thread_; + + DISALLOW_COPY_AND_ASSIGN(HostController); +}; + +} // namespace forwarder2 + +#endif // TOOLS_ANDROID_FORWARDER2_HOST_CONTROLLER_H_ diff --git a/tools/android/forwarder2/host_forwarder_main.cc b/tools/android/forwarder2/host_forwarder_main.cc new file mode 100644 index 0000000000..1071aa7dd4 --- /dev/null +++ b/tools/android/forwarder2/host_forwarder_main.cc @@ -0,0 +1,414 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "base/at_exit.h" +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/compiler_specific.h" +#include "base/containers/hash_tables.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/memory/linked_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/memory/weak_ptr.h" +#include "base/pickle.h" +#include "base/posix/eintr_wrapper.h" +#include "base/safe_strerror_posix.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_piece.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/task_runner.h" +#include "base/threading/thread.h" +#include "tools/android/forwarder2/common.h" +#include "tools/android/forwarder2/daemon.h" +#include "tools/android/forwarder2/host_controller.h" +#include "tools/android/forwarder2/pipe_notifier.h" +#include "tools/android/forwarder2/socket.h" + +namespace forwarder2 { +namespace { + +const char kLogFilePath[] = "/tmp/host_forwarder_log"; +const char kDaemonIdentifier[] = "chrome_host_forwarder_daemon"; + +const char kKillServerCommand[] = "kill-server"; +const char kForwardCommand[] = "forward"; + +const int kBufSize = 256; + +// Needs to be global to be able to be accessed from the signal handler. +PipeNotifier* g_notifier = NULL; + +// Lets the daemon fetch the exit notifier file descriptor. +int GetExitNotifierFD() { + DCHECK(g_notifier); + return g_notifier->receiver_fd(); +} + +void KillHandler(int signal_number) { + char buf[kBufSize]; + if (signal_number != SIGTERM && signal_number != SIGINT) { + snprintf(buf, sizeof(buf), "Ignoring unexpected signal %d.", signal_number); + SIGNAL_SAFE_LOG(WARNING, buf); + return; + } + snprintf(buf, sizeof(buf), "Received signal %d.", signal_number); + SIGNAL_SAFE_LOG(WARNING, buf); + static int s_kill_handler_count = 0; + CHECK(g_notifier); + // If for some reason the forwarder get stuck in any socket waiting forever, + // we can send a SIGKILL or SIGINT three times to force it die + // (non-nicely). This is useful when debugging. + ++s_kill_handler_count; + if (!g_notifier->Notify() || s_kill_handler_count > 2) + exit(1); +} + +// Manages HostController instances. There is one HostController instance for +// each connection being forwarded. Note that forwarding can happen with many +// devices (identified with a serial id). +class HostControllersManager { + public: + HostControllersManager() + : weak_ptr_factory_(this), + controllers_(new HostControllerMap()), + has_failed_(false) { + } + + ~HostControllersManager() { + if (!thread_.get()) + return; + // Delete the controllers on the thread they were created on. + thread_->message_loop_proxy()->DeleteSoon( + FROM_HERE, controllers_.release()); + } + + void HandleRequest(const std::string& device_serial, + int device_port, + int host_port, + scoped_ptr client_socket) { + // Lazy initialize so that the CLI process doesn't get this thread created. + InitOnce(); + thread_->message_loop_proxy()->PostTask( + FROM_HERE, + base::Bind( + &HostControllersManager::HandleRequestOnInternalThread, + base::Unretained(this), device_serial, device_port, host_port, + base::Passed(&client_socket))); + } + + bool has_failed() const { return has_failed_; } + + private: + typedef base::hash_map< + std::string, linked_ptr > HostControllerMap; + + static std::string MakeHostControllerMapKey(int adb_port, int device_port) { + return base::StringPrintf("%d:%d", adb_port, device_port); + } + + void InitOnce() { + if (thread_.get()) + return; + at_exit_manager_.reset(new base::AtExitManager()); + thread_.reset(new base::Thread("HostControllersManagerThread")); + thread_->Start(); + } + + // Invoked when a HostController instance reports an error (e.g. due to a + // device connectivity issue). Note that this could be called after the + // controller manager was destroyed which is why a weak pointer is used. + static void DeleteHostController( + const base::WeakPtr& manager_ptr, + scoped_ptr host_controller) { + HostController* const controller = host_controller.release(); + HostControllersManager* const manager = manager_ptr.get(); + if (!manager) { + // Note that |controller| is not leaked in this case since the host + // controllers manager owns the controllers. If the manager was deleted + // then all the controllers (including |controller|) were also deleted. + return; + } + DCHECK(manager->thread_->message_loop_proxy()->RunsTasksOnCurrentThread()); + // Note that this will delete |controller| which is owned by the map. + manager->controllers_->erase( + MakeHostControllerMapKey(controller->adb_port(), + controller->device_port())); + } + + void HandleRequestOnInternalThread(const std::string& device_serial, + int device_port, + int host_port, + scoped_ptr client_socket) { + const int adb_port = GetAdbPortForDevice(device_serial); + if (adb_port < 0) { + SendMessage( + "ERROR: could not get adb port for device. You might need to add " + "'adb' to your PATH or provide the device serial id.", + client_socket.get()); + return; + } + if (device_port < 0) { + // Remove the previously created host controller. + const std::string controller_key = MakeHostControllerMapKey( + adb_port, -device_port); + const HostControllerMap::size_type removed_elements = controllers_->erase( + controller_key); + SendMessage( + !removed_elements ? "ERROR: could not unmap port" : "OK", + client_socket.get()); + return; + } + if (host_port < 0) { + SendMessage("ERROR: missing host port", client_socket.get()); + return; + } + const bool use_dynamic_port_allocation = device_port == 0; + if (!use_dynamic_port_allocation) { + const std::string controller_key = MakeHostControllerMapKey( + adb_port, device_port); + if (controllers_->find(controller_key) != controllers_->end()) { + LOG(INFO) << "Already forwarding device port " << device_port + << " to host port " << host_port; + SendMessage(base::StringPrintf("%d:%d", device_port, host_port), + client_socket.get()); + return; + } + } + // Create a new host controller. + scoped_ptr host_controller( + HostController::Create( + device_port, host_port, adb_port, GetExitNotifierFD(), + base::Bind(&HostControllersManager::DeleteHostController, + weak_ptr_factory_.GetWeakPtr()))); + if (!host_controller.get()) { + has_failed_ = true; + SendMessage("ERROR: Connection to device failed.", client_socket.get()); + return; + } + // Get the current allocated port. + device_port = host_controller->device_port(); + LOG(INFO) << "Forwarding device port " << device_port << " to host port " + << host_port; + const std::string msg = base::StringPrintf("%d:%d", device_port, host_port); + if (!SendMessage(msg, client_socket.get())) + return; + host_controller->Start(); + controllers_->insert( + std::make_pair(MakeHostControllerMapKey(adb_port, device_port), + linked_ptr(host_controller.release()))); + } + + int GetAdbPortForDevice(const std::string& device_serial) { + base::hash_map::const_iterator it = + device_serial_to_adb_port_map_.find(device_serial); + if (it != device_serial_to_adb_port_map_.end()) + return it->second; + Socket bind_socket; + CHECK(bind_socket.BindTcp("127.0.0.1", 0)); + const int port = bind_socket.GetPort(); + bind_socket.Close(); + const std::string serial_part = device_serial.empty() ? + std::string() : std::string("-s ") + device_serial; + const std::string command = base::StringPrintf( + "adb %s forward tcp:%d localabstract:chrome_device_forwarder", + device_serial.empty() ? "" : serial_part.c_str(), + port); + LOG(INFO) << command; + const int ret = system(command.c_str()); + if (ret < 0 || !WIFEXITED(ret) || WEXITSTATUS(ret) != 0) + return -1; + device_serial_to_adb_port_map_[device_serial] = port; + return port; + } + + bool SendMessage(const std::string& msg, Socket* client_socket) { + bool result = client_socket->WriteString(msg); + DCHECK(result); + if (!result) + has_failed_ = true; + return result; + } + + base::WeakPtrFactory weak_ptr_factory_; + base::hash_map device_serial_to_adb_port_map_; + scoped_ptr controllers_; + bool has_failed_; + scoped_ptr at_exit_manager_; // Needed by base::Thread. + scoped_ptr thread_; +}; + +class ServerDelegate : public Daemon::ServerDelegate { + public: + ServerDelegate() : has_failed_(false) {} + + bool has_failed() const { + return has_failed_ || controllers_manager_.has_failed(); + } + + // Daemon::ServerDelegate: + virtual void Init() OVERRIDE { + LOG(INFO) << "Starting host process daemon (pid=" << getpid() << ")"; + DCHECK(!g_notifier); + g_notifier = new PipeNotifier(); + signal(SIGTERM, KillHandler); + signal(SIGINT, KillHandler); + } + + virtual void OnClientConnected(scoped_ptr client_socket) OVERRIDE { + char buf[kBufSize]; + const int bytes_read = client_socket->Read(buf, sizeof(buf)); + if (bytes_read <= 0) { + if (client_socket->DidReceiveEvent()) + return; + PError("Read()"); + has_failed_ = true; + return; + } + const Pickle command_pickle(buf, bytes_read); + PickleIterator pickle_it(command_pickle); + std::string device_serial; + CHECK(pickle_it.ReadString(&device_serial)); + int device_port; + if (!pickle_it.ReadInt(&device_port)) { + client_socket->WriteString("ERROR: missing device port"); + return; + } + int host_port; + if (!pickle_it.ReadInt(&host_port)) + host_port = -1; + controllers_manager_.HandleRequest( + device_serial, device_port, host_port, client_socket.Pass()); + } + + private: + bool has_failed_; + HostControllersManager controllers_manager_; + + DISALLOW_COPY_AND_ASSIGN(ServerDelegate); +}; + +class ClientDelegate : public Daemon::ClientDelegate { + public: + ClientDelegate(const Pickle& command_pickle) + : command_pickle_(command_pickle), + has_failed_(false) { + } + + bool has_failed() const { return has_failed_; } + + // Daemon::ClientDelegate: + virtual void OnDaemonReady(Socket* daemon_socket) OVERRIDE { + // Send the forward command to the daemon. + CHECK_EQ(command_pickle_.size(), + daemon_socket->WriteNumBytes(command_pickle_.data(), + command_pickle_.size())); + char buf[kBufSize]; + const int bytes_read = daemon_socket->Read( + buf, sizeof(buf) - 1 /* leave space for null terminator */); + CHECK_GT(bytes_read, 0); + DCHECK(bytes_read < sizeof(buf)); + buf[bytes_read] = 0; + base::StringPiece msg(buf, bytes_read); + if (msg.starts_with("ERROR")) { + LOG(ERROR) << msg; + has_failed_ = true; + return; + } + printf("%s\n", buf); + } + + private: + const Pickle command_pickle_; + bool has_failed_; +}; + +void ExitWithUsage() { + std::cerr << "Usage: host_forwarder [options]\n\n" + "Options:\n" + " --serial-id=[0-9A-Z]{16}]\n" + " --map DEVICE_PORT HOST_PORT\n" + " --unmap DEVICE_PORT\n" + " --kill-server\n"; + exit(1); +} + +int PortToInt(const std::string& s) { + int value; + // Note that 0 is a valid port (used for dynamic port allocation). + if (!base::StringToInt(s, &value) || value < 0 || + value > std::numeric_limits::max()) { + LOG(ERROR) << "Could not convert string " << s << " to port"; + ExitWithUsage(); + } + return value; +} + +int RunHostForwarder(int argc, char** argv) { + CommandLine::Init(argc, argv); + const CommandLine& cmd_line = *CommandLine::ForCurrentProcess(); + bool kill_server = false; + + Pickle pickle; + pickle.WriteString( + cmd_line.HasSwitch("serial-id") ? + cmd_line.GetSwitchValueASCII("serial-id") : std::string()); + + const std::vector args = cmd_line.GetArgs(); + if (cmd_line.HasSwitch("kill-server")) { + kill_server = true; + } else if (cmd_line.HasSwitch("unmap")) { + if (args.size() != 1) + ExitWithUsage(); + // Note the minus sign below. + pickle.WriteInt(-PortToInt(args[0])); + } else if (cmd_line.HasSwitch("map")) { + if (args.size() != 2) + ExitWithUsage(); + pickle.WriteInt(PortToInt(args[0])); + pickle.WriteInt(PortToInt(args[1])); + } else { + ExitWithUsage(); + } + + if (kill_server && args.size() > 0) + ExitWithUsage(); + + ClientDelegate client_delegate(pickle); + ServerDelegate daemon_delegate; + Daemon daemon( + kLogFilePath, kDaemonIdentifier, &client_delegate, &daemon_delegate, + &GetExitNotifierFD); + + if (kill_server) + return !daemon.Kill(); + if (!daemon.SpawnIfNeeded()) + return 1; + + return client_delegate.has_failed() || daemon_delegate.has_failed(); +} + +} // namespace +} // namespace forwarder2 + +int main(int argc, char** argv) { + return forwarder2::RunHostForwarder(argc, argv); +} diff --git a/tools/android/forwarder2/pipe_notifier.cc b/tools/android/forwarder2/pipe_notifier.cc new file mode 100644 index 0000000000..3aba18b0d0 --- /dev/null +++ b/tools/android/forwarder2/pipe_notifier.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 "tools/android/forwarder2/pipe_notifier.h" + +#include +#include +#include +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/safe_strerror_posix.h" + +namespace forwarder2 { + +PipeNotifier::PipeNotifier() { + int pipe_fd[2]; + int ret = pipe(pipe_fd); + CHECK_EQ(0, ret); + receiver_fd_ = pipe_fd[0]; + sender_fd_ = pipe_fd[1]; + fcntl(sender_fd_, F_SETFL, O_NONBLOCK); +} + +PipeNotifier::~PipeNotifier() { + (void) HANDLE_EINTR(close(receiver_fd_)); + (void) HANDLE_EINTR(close(sender_fd_)); +} + +bool PipeNotifier::Notify() { + CHECK_NE(-1, sender_fd_); + errno = 0; + int ret = HANDLE_EINTR(write(sender_fd_, "1", 1)); + if (ret < 0) { + LOG(WARNING) << "Error while notifying pipe. " << safe_strerror(errno); + return false; + } + return true; +} + +} // namespace forwarder diff --git a/tools/android/forwarder2/pipe_notifier.h b/tools/android/forwarder2/pipe_notifier.h new file mode 100644 index 0000000000..efe303b2c1 --- /dev/null +++ b/tools/android/forwarder2/pipe_notifier.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 TOOLS_ANDROID_FORWARDER2_PIPE_NOTIFIER_H_ +#define TOOLS_ANDROID_FORWARDER2_PIPE_NOTIFIER_H_ + +#include "base/basictypes.h" + +namespace forwarder2 { + +// Helper class used to create a unix pipe that sends notifications to the +// |receiver_fd_| file descriptor when called |Notify()|. This should be used +// by the main thread to notify other threads that it must exit. +// The |receiver_fd_| can be put into a fd_set and used in a select together +// with a socket waiting to accept or read. +class PipeNotifier { + public: + PipeNotifier(); + ~PipeNotifier(); + + bool Notify(); + + int receiver_fd() const { return receiver_fd_; } + + private: + int sender_fd_; + int receiver_fd_; + + DISALLOW_COPY_AND_ASSIGN(PipeNotifier); +}; + +} // namespace forwarder + +#endif // TOOLS_ANDROID_FORWARDER2_PIPE_NOTIFIER_H_ diff --git a/tools/android/forwarder2/socket.cc b/tools/android/forwarder2/socket.cc new file mode 100644 index 0000000000..9564e98634 --- /dev/null +++ b/tools/android/forwarder2/socket.cc @@ -0,0 +1,422 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "tools/android/forwarder2/socket.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/safe_strerror_posix.h" +#include "tools/android/common/net.h" +#include "tools/android/forwarder2/common.h" + +namespace { +const int kNoTimeout = -1; +const int kConnectTimeOut = 10; // Seconds. + +bool FamilyIsTCP(int family) { + return family == AF_INET || family == AF_INET6; +} +} // namespace + +namespace forwarder2 { + +bool Socket::BindUnix(const std::string& path) { + errno = 0; + if (!InitUnixSocket(path) || !BindAndListen()) { + Close(); + return false; + } + return true; +} + +bool Socket::BindTcp(const std::string& host, int port) { + errno = 0; + if (!InitTcpSocket(host, port) || !BindAndListen()) { + Close(); + return false; + } + return true; +} + +bool Socket::ConnectUnix(const std::string& path) { + errno = 0; + if (!InitUnixSocket(path) || !Connect()) { + Close(); + return false; + } + return true; +} + +bool Socket::ConnectTcp(const std::string& host, int port) { + errno = 0; + if (!InitTcpSocket(host, port) || !Connect()) { + Close(); + return false; + } + return true; +} + +Socket::Socket() + : socket_(-1), + port_(0), + socket_error_(false), + family_(AF_INET), + addr_ptr_(reinterpret_cast(&addr_.addr4)), + addr_len_(sizeof(sockaddr)) { + memset(&addr_, 0, sizeof(addr_)); +} + +Socket::~Socket() { + Close(); +} + +void Socket::Shutdown() { + if (!IsClosed()) { + PRESERVE_ERRNO_HANDLE_EINTR(shutdown(socket_, SHUT_RDWR)); + } +} + +void Socket::Close() { + if (!IsClosed()) { + CloseFD(socket_); + socket_ = -1; + } +} + +bool Socket::InitSocketInternal() { + socket_ = socket(family_, SOCK_STREAM, 0); + if (socket_ < 0) + return false; + tools::DisableNagle(socket_); + int reuse_addr = 1; + setsockopt(socket_, SOL_SOCKET, SO_REUSEADDR, + &reuse_addr, sizeof(reuse_addr)); + return true; +} + +bool Socket::InitUnixSocket(const std::string& path) { + static const size_t kPathMax = sizeof(addr_.addr_un.sun_path); + // For abstract sockets we need one extra byte for the leading zero. + if (path.size() + 2 /* '\0' */ > kPathMax) { + LOG(ERROR) << "The provided path is too big to create a unix " + << "domain socket: " << path; + return false; + } + family_ = PF_UNIX; + addr_.addr_un.sun_family = family_; + // Copied from net/socket/unix_domain_socket_posix.cc + // Convert the path given into abstract socket name. It must start with + // the '\0' character, so we are adding it. |addr_len| must specify the + // length of the structure exactly, as potentially the socket name may + // have '\0' characters embedded (although we don't support this). + // Note that addr_.addr_un.sun_path is already zero initialized. + memcpy(addr_.addr_un.sun_path + 1, path.c_str(), path.size()); + addr_len_ = path.size() + offsetof(struct sockaddr_un, sun_path) + 1; + addr_ptr_ = reinterpret_cast(&addr_.addr_un); + return InitSocketInternal(); +} + +bool Socket::InitTcpSocket(const std::string& host, int port) { + port_ = port; + if (host.empty()) { + // Use localhost: INADDR_LOOPBACK + family_ = AF_INET; + addr_.addr4.sin_family = family_; + addr_.addr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + } else if (!Resolve(host)) { + return false; + } + CHECK(FamilyIsTCP(family_)) << "Invalid socket family."; + if (family_ == AF_INET) { + addr_.addr4.sin_port = htons(port_); + addr_ptr_ = reinterpret_cast(&addr_.addr4); + addr_len_ = sizeof(addr_.addr4); + } else if (family_ == AF_INET6) { + addr_.addr6.sin6_port = htons(port_); + addr_ptr_ = reinterpret_cast(&addr_.addr6); + addr_len_ = sizeof(addr_.addr6); + } + return InitSocketInternal(); +} + +bool Socket::BindAndListen() { + errno = 0; + if (HANDLE_EINTR(bind(socket_, addr_ptr_, addr_len_)) < 0 || + HANDLE_EINTR(listen(socket_, SOMAXCONN)) < 0) { + SetSocketError(); + return false; + } + if (port_ == 0 && FamilyIsTCP(family_)) { + SockAddr addr; + memset(&addr, 0, sizeof(addr)); + socklen_t addrlen = 0; + sockaddr* addr_ptr = NULL; + uint16* port_ptr = NULL; + if (family_ == AF_INET) { + addr_ptr = reinterpret_cast(&addr.addr4); + port_ptr = &addr.addr4.sin_port; + addrlen = sizeof(addr.addr4); + } else if (family_ == AF_INET6) { + addr_ptr = reinterpret_cast(&addr.addr6); + port_ptr = &addr.addr6.sin6_port; + addrlen = sizeof(addr.addr6); + } + errno = 0; + if (getsockname(socket_, addr_ptr, &addrlen) != 0) { + LOG(ERROR) << "getsockname error: " << safe_strerror(errno);; + SetSocketError(); + return false; + } + port_ = ntohs(*port_ptr); + } + return true; +} + +bool Socket::Accept(Socket* new_socket) { + DCHECK(new_socket != NULL); + if (!WaitForEvent(READ, kNoTimeout)) { + SetSocketError(); + return false; + } + errno = 0; + int new_socket_fd = HANDLE_EINTR(accept(socket_, NULL, NULL)); + if (new_socket_fd < 0) { + SetSocketError(); + return false; + } + + tools::DisableNagle(new_socket_fd); + new_socket->socket_ = new_socket_fd; + return true; +} + +bool Socket::Connect() { + // Set non-block because we use select for connect. + const int kFlags = fcntl(socket_, F_GETFL); + DCHECK(!(kFlags & O_NONBLOCK)); + fcntl(socket_, F_SETFL, kFlags | O_NONBLOCK); + errno = 0; + if (HANDLE_EINTR(connect(socket_, addr_ptr_, addr_len_)) < 0 && + errno != EINPROGRESS) { + SetSocketError(); + PRESERVE_ERRNO_HANDLE_EINTR(fcntl(socket_, F_SETFL, kFlags)); + return false; + } + // Wait for connection to complete, or receive a notification. + if (!WaitForEvent(WRITE, kConnectTimeOut)) { + SetSocketError(); + PRESERVE_ERRNO_HANDLE_EINTR(fcntl(socket_, F_SETFL, kFlags)); + return false; + } + int socket_errno; + socklen_t opt_len = sizeof(socket_errno); + if (getsockopt(socket_, SOL_SOCKET, SO_ERROR, &socket_errno, &opt_len) < 0) { + LOG(ERROR) << "getsockopt(): " << safe_strerror(errno); + SetSocketError(); + PRESERVE_ERRNO_HANDLE_EINTR(fcntl(socket_, F_SETFL, kFlags)); + return false; + } + if (socket_errno != 0) { + LOG(ERROR) << "Could not connect to host: " << safe_strerror(socket_errno); + SetSocketError(); + PRESERVE_ERRNO_HANDLE_EINTR(fcntl(socket_, F_SETFL, kFlags)); + return false; + } + fcntl(socket_, F_SETFL, kFlags); + return true; +} + +bool Socket::Resolve(const std::string& host) { + struct addrinfo hints; + struct addrinfo* res; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags |= AI_CANONNAME; + + int errcode = getaddrinfo(host.c_str(), NULL, &hints, &res); + if (errcode != 0) { + SetSocketError(); + freeaddrinfo(res); + return false; + } + family_ = res->ai_family; + switch (res->ai_family) { + case AF_INET: + memcpy(&addr_.addr4, + reinterpret_cast(res->ai_addr), + sizeof(sockaddr_in)); + break; + case AF_INET6: + memcpy(&addr_.addr6, + reinterpret_cast(res->ai_addr), + sizeof(sockaddr_in6)); + break; + } + freeaddrinfo(res); + return true; +} + +int Socket::GetPort() { + if (!FamilyIsTCP(family_)) { + LOG(ERROR) << "Can't call GetPort() on an unix domain socket."; + return 0; + } + return port_; +} + +bool Socket::IsFdInSet(const fd_set& fds) const { + if (IsClosed()) + return false; + return FD_ISSET(socket_, &fds); +} + +bool Socket::AddFdToSet(fd_set* fds) const { + if (IsClosed()) + return false; + FD_SET(socket_, fds); + return true; +} + +int Socket::ReadNumBytes(void* buffer, size_t num_bytes) { + int bytes_read = 0; + int ret = 1; + while (bytes_read < num_bytes && ret > 0) { + ret = Read(static_cast(buffer) + bytes_read, num_bytes - bytes_read); + if (ret >= 0) + bytes_read += ret; + } + return bytes_read; +} + +void Socket::SetSocketError() { + socket_error_ = true; + // We never use non-blocking socket. + DCHECK(errno != EAGAIN && errno != EWOULDBLOCK); + Close(); +} + +int Socket::Read(void* buffer, size_t buffer_size) { + if (!WaitForEvent(READ, kNoTimeout)) { + SetSocketError(); + return 0; + } + int ret = HANDLE_EINTR(read(socket_, buffer, buffer_size)); + if (ret < 0) + SetSocketError(); + return ret; +} + +int Socket::Write(const void* buffer, size_t count) { + int ret = HANDLE_EINTR(send(socket_, buffer, count, MSG_NOSIGNAL)); + if (ret < 0) + SetSocketError(); + return ret; +} + +int Socket::WriteString(const std::string& buffer) { + return WriteNumBytes(buffer.c_str(), buffer.size()); +} + +void Socket::AddEventFd(int event_fd) { + Event event; + event.fd = event_fd; + event.was_fired = false; + events_.push_back(event); +} + +bool Socket::DidReceiveEventOnFd(int fd) const { + for (size_t i = 0; i < events_.size(); ++i) + if (events_[i].fd == fd) + return events_[i].was_fired; + return false; +} + +bool Socket::DidReceiveEvent() const { + for (size_t i = 0; i < events_.size(); ++i) + if (events_[i].was_fired) + return true; + return false; +} + +int Socket::WriteNumBytes(const void* buffer, size_t num_bytes) { + int bytes_written = 0; + int ret = 1; + while (bytes_written < num_bytes && ret > 0) { + ret = Write(static_cast(buffer) + bytes_written, + num_bytes - bytes_written); + if (ret >= 0) + bytes_written += ret; + } + return bytes_written; +} + +bool Socket::WaitForEvent(EventType type, int timeout_secs) { + if (events_.empty() || socket_ == -1) + return true; + fd_set read_fds; + fd_set write_fds; + FD_ZERO(&read_fds); + FD_ZERO(&write_fds); + if (type == READ) + FD_SET(socket_, &read_fds); + else + FD_SET(socket_, &write_fds); + for (size_t i = 0; i < events_.size(); ++i) + FD_SET(events_[i].fd, &read_fds); + timeval tv = {}; + timeval* tv_ptr = NULL; + if (timeout_secs > 0) { + tv.tv_sec = timeout_secs; + tv.tv_usec = 0; + tv_ptr = &tv; + } + int max_fd = socket_; + for (size_t i = 0; i < events_.size(); ++i) + if (events_[i].fd > max_fd) + max_fd = events_[i].fd; + if (HANDLE_EINTR( + select(max_fd + 1, &read_fds, &write_fds, NULL, tv_ptr)) <= 0) { + return false; + } + bool event_was_fired = false; + for (size_t i = 0; i < events_.size(); ++i) { + if (FD_ISSET(events_[i].fd, &read_fds)) { + events_[i].was_fired = true; + event_was_fired = true; + } + } + return !event_was_fired; +} + +// static +int Socket::GetHighestFileDescriptor(const Socket& s1, const Socket& s2) { + return std::max(s1.socket_, s2.socket_); +} + +// static +pid_t Socket::GetUnixDomainSocketProcessOwner(const std::string& path) { + Socket socket; + if (!socket.ConnectUnix(path)) + return -1; + ucred ucred; + socklen_t len = sizeof(ucred); + if (getsockopt(socket.socket_, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == -1) { + CHECK_NE(ENOPROTOOPT, errno); + return -1; + } + return ucred.pid; +} + +} // namespace forwarder2 diff --git a/tools/android/forwarder2/socket.h b/tools/android/forwarder2/socket.h new file mode 100644 index 0000000000..b86fd9e7aa --- /dev/null +++ b/tools/android/forwarder2/socket.h @@ -0,0 +1,145 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef TOOLS_ANDROID_FORWARDER2_SOCKET_H_ +#define TOOLS_ANDROID_FORWARDER2_SOCKET_H_ + +#include +#include +#include +#include + +#include +#include + +#include "base/basictypes.h" + +namespace forwarder2 { + +// Wrapper class around unix socket api. Can be used to create, bind or +// connect to both Unix domain sockets and TCP sockets. +// TODO(pliard): Split this class into TCPSocket and UnixDomainSocket. +class Socket { + public: + Socket(); + ~Socket(); + + bool BindUnix(const std::string& path); + bool BindTcp(const std::string& host, int port); + bool ConnectUnix(const std::string& path); + bool ConnectTcp(const std::string& host, int port); + + // Just a wrapper around unix socket shutdown(), see man 2 shutdown. + void Shutdown(); + + // Just a wrapper around unix socket close(), see man 2 close. + void Close(); + bool IsClosed() const { return socket_ < 0; } + + bool Accept(Socket* new_socket); + + // Returns the port allocated to this socket or zero on error. + int GetPort(); + + bool IsFdInSet(const fd_set& fds) const; + bool AddFdToSet(fd_set* fds) const; + + // Just a wrapper around unix read() function. + // Reads up to buffer_size, but may read less then buffer_size. + // Returns the number of bytes read. + int Read(void* buffer, size_t buffer_size); + + // Same as Read(), just a wrapper around write(). + int Write(const void* buffer, size_t count); + + // Calls Read() multiple times until num_bytes is written to the provided + // buffer. No bounds checking is performed. + // Returns number of bytes read, which can be different from num_bytes in case + // of errror. + int ReadNumBytes(void* buffer, size_t num_bytes); + + // Calls Write() multiple times until num_bytes is written. No bounds checking + // is performed. Returns number of bytes written, which can be different from + // num_bytes in case of errror. + int WriteNumBytes(const void* buffer, size_t num_bytes); + + // Calls WriteNumBytes for the given std::string. Note that the null + // terminator is not written to the socket. + int WriteString(const std::string& buffer); + + bool has_error() const { return socket_error_; } + + // |event_fd| must be a valid pipe file descriptor created from the + // PipeNotifier and must live (not be closed) at least as long as this socket + // is alive. + void AddEventFd(int event_fd); + + // Returns whether Accept() or Connect() was interrupted because the socket + // received an external event fired through the provided fd. + bool DidReceiveEventOnFd(int fd) const; + + bool DidReceiveEvent() const; + + static int GetHighestFileDescriptor(const Socket& s1, const Socket& s2); + + static pid_t GetUnixDomainSocketProcessOwner(const std::string& path); + + private: + enum EventType { + READ, + WRITE + }; + + union SockAddr { + // IPv4 sockaddr + sockaddr_in addr4; + // IPv6 sockaddr + sockaddr_in6 addr6; + // Unix Domain sockaddr + sockaddr_un addr_un; + }; + + struct Event { + int fd; + bool was_fired; + }; + + // If |host| is empty, use localhost. + bool InitTcpSocket(const std::string& host, int port); + bool InitUnixSocket(const std::string& path); + bool BindAndListen(); + bool Connect(); + + bool Resolve(const std::string& host); + bool InitSocketInternal(); + void SetSocketError(); + + // Waits until either the Socket or the |exit_notifier_fd_| has received an + // event. + bool WaitForEvent(EventType type, int timeout_secs); + + int socket_; + int port_; + bool socket_error_; + + // Family of the socket (PF_INET, PF_INET6 or PF_UNIX). + int family_; + + SockAddr addr_; + + // Points to one of the members of the above union depending on the family. + sockaddr* addr_ptr_; + // Length of one of the members of the above union depending on the family. + socklen_t addr_len_; + + // Used to listen for external events (e.g. process received a SIGTERM) while + // blocking on I/O operations. + std::vector events_; + + DISALLOW_COPY_AND_ASSIGN(Socket); +}; + +} // namespace forwarder + +#endif // TOOLS_ANDROID_FORWARDER2_SOCKET_H_ diff --git a/tools/android/md5sum/md5sum.cc b/tools/android/md5sum/md5sum.cc new file mode 100644 index 0000000000..bc4ca071b8 --- /dev/null +++ b/tools/android/md5sum/md5sum.cc @@ -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. + +// Md5sum implementation for Android. This version handles files as well as +// directories. Its output is sorted by file path. + +#include +#include +#include +#include + +#include "base/file_util.h" +#include "base/files/file_enumerator.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/md5.h" + +namespace { + +const int kBufferSize = 1024; + +// Returns whether |path|'s MD5 was successfully written to |digest_string|. +bool MD5Sum(const char* path, std::string* digest_string) { + std::ifstream stream(path); + if (!stream.good()) { + LOG(ERROR) << "Could not open file " << path; + return false; + } + base::MD5Context ctx; + base::MD5Init(&ctx); + char buf[kBufferSize]; + while (stream.good()) { + std::streamsize bytes_read = stream.readsome(buf, sizeof(buf)); + if (bytes_read == 0) + break; + base::MD5Update(&ctx, base::StringPiece(buf, bytes_read)); + } + if (stream.fail()) { + LOG(ERROR) << "Error reading file " << path; + return false; + } + base::MD5Digest digest; + base::MD5Final(&digest, &ctx); + *digest_string = base::MD5DigestToBase16(digest); + return true; +} + +// Returns the set of all files contained in |files|. This handles directories +// by walking them recursively. Excludes, .svn directories and file under them. +std::set MakeFileSet(const char** files) { + const std::string svn_dir_component = FILE_PATH_LITERAL("/.svn/"); + std::set file_set; + for (const char** file = files; *file; ++file) { + base::FilePath file_path(*file); + if (base::DirectoryExists(file_path)) { + base::FileEnumerator file_enumerator( + file_path, true /* recurse */, base::FileEnumerator::FILES); + for (base::FilePath child, empty; + (child = file_enumerator.Next()) != empty; ) { + // If the path contains /.svn/, ignore it. + if (child.value().find(svn_dir_component) == std::string::npos) { + child = base::MakeAbsoluteFilePath(child); + file_set.insert(child.value()); + } + } + } else { + file_set.insert(*file); + } + } + return file_set; +} + +} // namespace + +int main(int argc, const char* argv[]) { + if (argc < 2) { + LOG(ERROR) << "Usage: md5sum ..."; + return 1; + } + const std::set files = MakeFileSet(argv + 1); + bool failed = false; + std::string digest; + for (std::set::const_iterator it = files.begin(); + it != files.end(); ++it) { + if (!MD5Sum(it->c_str(), &digest)) + failed = true; + base::FilePath file_path(*it); + std::cout << digest << " " + << base::MakeAbsoluteFilePath(file_path).value() << std::endl; + } + return failed; +} diff --git a/tools/android/md5sum/md5sum.gyp b/tools/android/md5sum/md5sum.gyp new file mode 100644 index 0000000000..2c4ccc84dd --- /dev/null +++ b/tools/android/md5sum/md5sum.gyp @@ -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. + +{ + 'targets': [ + { + 'target_name': 'md5sum', + 'type': 'none', + 'dependencies': [ + 'md5sum_stripped_device_bin', + 'md5sum_bin_host#host', + ], + # For the component build, ensure dependent shared libraries are stripped + # and put alongside md5sum to simplify pushing to the device. + 'variables': { + 'output_dir': '<(PRODUCT_DIR)/md5sum_dist/', + 'native_binary': '<(PRODUCT_DIR)/md5sum_bin', + }, + 'includes': ['../../../build/android/native_app_dependencies.gypi'], + }, + { + 'target_name': 'md5sum_device_bin', + 'type': 'executable', + 'dependencies': [ + '../../../base/base.gyp:base', + ], + 'include_dirs': [ + '../../..', + ], + 'sources': [ + 'md5sum.cc', + ], + 'conditions': [ + [ 'order_profiling!=0 and OS=="android"', { + 'dependencies': [ '../../../tools/cygprofile/cygprofile.gyp:cygprofile', ], + }], + ], + }, + { + 'target_name': 'md5sum_stripped_device_bin', + 'type': 'none', + 'dependencies': [ + 'md5sum_device_bin', + ], + 'actions': [ + { + 'action_name': 'strip_md5sum_device_bin', + 'inputs': ['<(PRODUCT_DIR)/md5sum_device_bin'], + 'outputs': ['<(PRODUCT_DIR)/md5sum_bin'], + 'action': [ + '<(android_strip)', + '--strip-unneeded', + '<@(_inputs)', + '-o', + '<@(_outputs)', + ], + }, + ], + }, + # Same binary but for the host rather than the device. + { + 'target_name': 'md5sum_bin_host', + 'toolsets': ['host'], + 'type': 'executable', + 'dependencies': [ + '../../../base/base.gyp:base', + ], + 'include_dirs': [ + '../../..', + ], + 'sources': [ + 'md5sum.cc', + ], + }, + ], +} diff --git a/tools/android/memdump/memdump.cc b/tools/android/memdump/memdump.cc new file mode 100644 index 0000000000..26035b29b0 --- /dev/null +++ b/tools/android/memdump/memdump.cc @@ -0,0 +1,555 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "base/base64.h" +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/containers/hash_tables.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_piece.h" +#include "base/strings/string_split.h" +#include "base/strings/stringprintf.h" + +namespace { + +class BitSet { + public: + void resize(size_t nbits) { + data_.resize((nbits + 7) / 8); + } + + void set(uint32 bit) { + const uint32 byte_idx = bit / 8; + CHECK(byte_idx < data_.size()); + data_[byte_idx] |= (1 << (bit & 7)); + } + + std::string AsB64String() const { + std::string bits(&data_[0], data_.size()); + std::string b64_string; + base::Base64Encode(bits, &b64_string); + return b64_string; + } + + private: + std::vector data_; +}; + +// An entry in /proc//pagemap. +struct PageMapEntry { + uint64 page_frame_number : 55; + uint unused : 8; + uint present : 1; +}; + +// Describes a memory page. +struct PageInfo { + int64 page_frame_number; // Physical page id, also known as PFN. + int64 flags; + int32 times_mapped; +}; + +struct MemoryMap { + std::string name; + std::string flags; + uint start_address; + uint end_address; + uint offset; + int private_count; + int unevictable_private_count; + int other_shared_count; + int unevictable_other_shared_count; + // app_shared_counts[i] contains the number of pages mapped in i+2 processes + // (only among the processes that are being analyzed). + std::vector app_shared_counts; + std::vector committed_pages; + // committed_pages_bits is a bitset reflecting the present bit for all the + // virtual pages of the mapping. + BitSet committed_pages_bits; +}; + +struct ProcessMemory { + pid_t pid; + std::vector memory_maps; +}; + +bool PageIsUnevictable(const PageInfo& page_info) { + // These constants are taken from kernel-page-flags.h. + const int KPF_DIRTY = 4; // Note that only file-mapped pages can be DIRTY. + const int KPF_ANON = 12; // Anonymous pages are dirty per definition. + const int KPF_UNEVICTABLE = 18; + const int KPF_MLOCKED = 33; + + return (page_info.flags & ((1ll << KPF_DIRTY) | + (1ll << KPF_ANON) | + (1ll << KPF_UNEVICTABLE) | + (1ll << KPF_MLOCKED))) ? + true : false; +} + +// Number of times a physical page is mapped in a process. +typedef base::hash_map PFNMap; + +// Parses lines from /proc//maps, e.g.: +// 401e7000-401f5000 r-xp 00000000 103:02 158 /system/bin/linker +bool ParseMemoryMapLine(const std::string& line, + std::vector* tokens, + MemoryMap* memory_map) { + tokens->clear(); + base::SplitString(line, ' ', tokens); + if (tokens->size() < 2) + return false; + const int addr_len = 8; + const std::string& addr_range = tokens->at(0); + if (addr_range.length() != addr_len + 1 + addr_len) + return false; + uint64 tmp = 0; + if (!base::HexStringToUInt64( + base::StringPiece( + addr_range.begin(), addr_range.begin() + addr_len), + &tmp)) { + return false; + } + memory_map->start_address = static_cast(tmp); + const int end_addr_start_pos = addr_len + 1; + if (!base::HexStringToUInt64( + base::StringPiece( + addr_range.begin() + end_addr_start_pos, + addr_range.begin() + end_addr_start_pos + addr_len), + &tmp)) { + return false; + } + memory_map->end_address = static_cast(tmp); + if (tokens->at(1).size() != strlen("rwxp")) + return false; + memory_map->flags.swap(tokens->at(1)); + if (!base::HexStringToUInt64(tokens->at(2), &tmp)) + return false; + memory_map->offset = static_cast(tmp); + memory_map->committed_pages_bits.resize( + (memory_map->end_address - memory_map->start_address) / PAGE_SIZE); + const int map_name_index = 5; + if (tokens->size() >= map_name_index + 1) { + for (std::vector::const_iterator it = + tokens->begin() + map_name_index; it != tokens->end(); ++it) { + if (!it->empty()) { + if (!memory_map->name.empty()) + memory_map->name.append(" "); + memory_map->name.append(*it); + } + } + } + return true; +} + +// Reads sizeof(T) bytes from file |fd| at |offset|. +template +bool ReadFromFileAtOffset(int fd, off_t offset, T* value) { + if (lseek64(fd, offset * sizeof(*value), SEEK_SET) < 0) { + PLOG(ERROR) << "lseek"; + return false; + } + ssize_t bytes = read(fd, value, sizeof(*value)); + if (bytes != sizeof(*value) && bytes != 0) { + PLOG(ERROR) << "read"; + return false; + } + return true; +} + +// Fills |process_maps| in with the process memory maps identified by |pid|. +bool GetProcessMaps(pid_t pid, std::vector* process_maps) { + std::ifstream maps_file(base::StringPrintf("/proc/%d/maps", pid).c_str()); + if (!maps_file.good()) { + PLOG(ERROR) << "open"; + return false; + } + std::string line; + std::vector tokens; + while (std::getline(maps_file, line) && !line.empty()) { + MemoryMap memory_map = {}; + if (!ParseMemoryMapLine(line, &tokens, &memory_map)) { + LOG(ERROR) << "Could not parse line: " << line; + return false; + } + process_maps->push_back(memory_map); + } + return true; +} + +// Fills |committed_pages| in with the set of committed pages contained in the +// provided memory map. +bool GetPagesForMemoryMap(int pagemap_fd, + const MemoryMap& memory_map, + std::vector* committed_pages, + BitSet* committed_pages_bits) { + for (uint addr = memory_map.start_address, page_index = 0; + addr < memory_map.end_address; + addr += PAGE_SIZE, ++page_index) { + DCHECK_EQ(0, addr % PAGE_SIZE); + PageMapEntry page_map_entry = {}; + COMPILE_ASSERT(sizeof(PageMapEntry) == sizeof(uint64), unexpected_size); + const off64_t offset = addr / PAGE_SIZE; + if (!ReadFromFileAtOffset(pagemap_fd, offset, &page_map_entry)) + return false; + if (page_map_entry.present) { // Ignore non-committed pages. + if (page_map_entry.page_frame_number == 0) + continue; + PageInfo page_info = {}; + page_info.page_frame_number = page_map_entry.page_frame_number; + committed_pages->push_back(page_info); + committed_pages_bits->set(page_index); + } + } + return true; +} + +// Fills |committed_pages| with mapping count and flags information gathered +// looking-up /proc/kpagecount and /proc/kpageflags. +bool SetPagesInfo(int pagecount_fd, + int pageflags_fd, + std::vector* pages) { + for (std::vector::iterator it = pages->begin(); + it != pages->end(); ++it) { + PageInfo* const page_info = &*it; + + int64 times_mapped; + if (!ReadFromFileAtOffset( + pagecount_fd, page_info->page_frame_number, ×_mapped)) { + return false; + } + DCHECK(times_mapped <= std::numeric_limits::max()); + page_info->times_mapped = static_cast(times_mapped); + + int64 page_flags; + if (!ReadFromFileAtOffset( + pageflags_fd, page_info->page_frame_number, &page_flags)) { + return false; + } + page_info->flags = page_flags; + } + return true; +} + +// Fills in the provided vector of Page Frame Number maps. This lets +// ClassifyPages() know how many times each page is mapped in the processes. +void FillPFNMaps(const std::vector& processes_memory, + std::vector* pfn_maps) { + int current_process_index = 0; + for (std::vector::const_iterator it = processes_memory.begin(); + it != processes_memory.end(); ++it, ++current_process_index) { + const std::vector& memory_maps = it->memory_maps; + for (std::vector::const_iterator it = memory_maps.begin(); + it != memory_maps.end(); ++it) { + const std::vector& pages = it->committed_pages; + for (std::vector::const_iterator it = pages.begin(); + it != pages.end(); ++it) { + const PageInfo& page_info = *it; + PFNMap* const pfn_map = &(*pfn_maps)[current_process_index]; + const std::pair result = pfn_map->insert( + std::make_pair(page_info.page_frame_number, 0)); + ++result.first->second; + } + } + } +} + +// Sets the private_count/app_shared_counts/other_shared_count fields of the +// provided memory maps for each process. +void ClassifyPages(std::vector* processes_memory) { + std::vector pfn_maps(processes_memory->size()); + FillPFNMaps(*processes_memory, &pfn_maps); + // Hash set keeping track of the physical pages mapped in a single process so + // that they can be counted only once. + std::hash_set physical_pages_mapped_in_process; + + for (std::vector::iterator it = processes_memory->begin(); + it != processes_memory->end(); ++it) { + std::vector* const memory_maps = &it->memory_maps; + physical_pages_mapped_in_process.clear(); + for (std::vector::iterator it = memory_maps->begin(); + it != memory_maps->end(); ++it) { + MemoryMap* const memory_map = &*it; + const size_t processes_count = processes_memory->size(); + memory_map->app_shared_counts.resize(processes_count - 1, 0); + const std::vector& pages = memory_map->committed_pages; + for (std::vector::const_iterator it = pages.begin(); + it != pages.end(); ++it) { + const PageInfo& page_info = *it; + if (page_info.times_mapped == 1) { + ++memory_map->private_count; + if (PageIsUnevictable(page_info)) + ++memory_map->unevictable_private_count; + continue; + } + const uint64 page_frame_number = page_info.page_frame_number; + const std::pair::iterator, bool> result = + physical_pages_mapped_in_process.insert(page_frame_number); + const bool did_insert = result.second; + if (!did_insert) { + // This physical page (mapped multiple times in the same process) was + // already counted. + continue; + } + // See if the current physical page is also mapped in the other + // processes that are being analyzed. + int times_mapped = 0; + int mapped_in_processes_count = 0; + for (std::vector::const_iterator it = pfn_maps.begin(); + it != pfn_maps.end(); ++it) { + const PFNMap& pfn_map = *it; + const PFNMap::const_iterator found_it = pfn_map.find( + page_frame_number); + if (found_it == pfn_map.end()) + continue; + ++mapped_in_processes_count; + times_mapped += found_it->second; + } + if (times_mapped == page_info.times_mapped) { + // The physical page is only mapped in the processes that are being + // analyzed. + if (mapped_in_processes_count > 1) { + // The physical page is mapped in multiple processes. + ++memory_map->app_shared_counts[mapped_in_processes_count - 2]; + } else { + // The physical page is mapped multiple times in the same process. + ++memory_map->private_count; + if (PageIsUnevictable(page_info)) + ++memory_map->unevictable_private_count; + } + } else { + ++memory_map->other_shared_count; + if (PageIsUnevictable(page_info)) + ++memory_map->unevictable_other_shared_count; + + } + } + } + } +} + +void AppendAppSharedField(const std::vector& app_shared_counts, + std::string* out) { + out->append("["); + for (std::vector::const_iterator it = app_shared_counts.begin(); + it != app_shared_counts.end(); ++it) { + out->append(base::IntToString(*it * PAGE_SIZE)); + if (it + 1 != app_shared_counts.end()) + out->append(","); + } + out->append("]"); +} + +void DumpProcessesMemoryMaps( + const std::vector& processes_memory) { + std::string buf; + std::string app_shared_buf; + for (std::vector::const_iterator it = processes_memory.begin(); + it != processes_memory.end(); ++it) { + const ProcessMemory& process_memory = *it; + std::cout << "[ PID=" << process_memory.pid << "]" << '\n'; + const std::vector& memory_maps = process_memory.memory_maps; + for (std::vector::const_iterator it = memory_maps.begin(); + it != memory_maps.end(); ++it) { + const MemoryMap& memory_map = *it; + app_shared_buf.clear(); + AppendAppSharedField(memory_map.app_shared_counts, &app_shared_buf); + base::SStringPrintf( + &buf, + "%x-%x %s private_unevictable=%d private=%d shared_app=%s " + "shared_other_unevictable=%d shared_other=%d %s\n", + memory_map.start_address, + memory_map.end_address, memory_map.flags.c_str(), + memory_map.unevictable_private_count * PAGE_SIZE, + memory_map.private_count * PAGE_SIZE, + app_shared_buf.c_str(), + memory_map.unevictable_other_shared_count * PAGE_SIZE, + memory_map.other_shared_count * PAGE_SIZE, + memory_map.name.c_str()); + std::cout << buf; + } + } +} + +void DumpProcessesMemoryMapsInShortFormat( + const std::vector& processes_memory) { + const int KB_PER_PAGE = PAGE_SIZE >> 10; + std::vector totals_app_shared(processes_memory.size()); + std::string buf; + std::cout << "pid\tprivate\t\tshared_app\tshared_other (KB)\n"; + for (std::vector::const_iterator it = processes_memory.begin(); + it != processes_memory.end(); ++it) { + const ProcessMemory& process_memory = *it; + std::fill(totals_app_shared.begin(), totals_app_shared.end(), 0); + int total_private = 0, total_other_shared = 0; + const std::vector& memory_maps = process_memory.memory_maps; + for (std::vector::const_iterator it = memory_maps.begin(); + it != memory_maps.end(); ++it) { + const MemoryMap& memory_map = *it; + total_private += memory_map.private_count; + for (size_t i = 0; i < memory_map.app_shared_counts.size(); ++i) + totals_app_shared[i] += memory_map.app_shared_counts[i]; + total_other_shared += memory_map.other_shared_count; + } + double total_app_shared = 0; + for (size_t i = 0; i < totals_app_shared.size(); ++i) + total_app_shared += static_cast(totals_app_shared[i]) / (i + 2); + base::SStringPrintf( + &buf, "%d\t%d\t\t%d\t\t%d\n", + process_memory.pid, + total_private * KB_PER_PAGE, + static_cast(total_app_shared) * KB_PER_PAGE, + total_other_shared * KB_PER_PAGE); + std::cout << buf; + } +} + +void DumpProcessesMemoryMapsInExtendedFormat( + const std::vector& processes_memory) { + std::string buf; + std::string app_shared_buf; + for (std::vector::const_iterator it = processes_memory.begin(); + it != processes_memory.end(); ++it) { + const ProcessMemory& process_memory = *it; + std::cout << "[ PID=" << process_memory.pid << "]" << '\n'; + const std::vector& memory_maps = process_memory.memory_maps; + for (std::vector::const_iterator it = memory_maps.begin(); + it != memory_maps.end(); ++it) { + const MemoryMap& memory_map = *it; + app_shared_buf.clear(); + AppendAppSharedField(memory_map.app_shared_counts, &app_shared_buf); + base::SStringPrintf( + &buf, + "%x-%x %s %x private_unevictable=%d private=%d shared_app=%s " + "shared_other_unevictable=%d shared_other=%d \"%s\" [%s]\n", + memory_map.start_address, + memory_map.end_address, + memory_map.flags.c_str(), + memory_map.offset, + memory_map.unevictable_private_count * PAGE_SIZE, + memory_map.private_count * PAGE_SIZE, + app_shared_buf.c_str(), + memory_map.unevictable_other_shared_count * PAGE_SIZE, + memory_map.other_shared_count * PAGE_SIZE, + memory_map.name.c_str(), + memory_map.committed_pages_bits.AsB64String().c_str()); + std::cout << buf; + } + } +} + +bool CollectProcessMemoryInformation(int page_count_fd, + int page_flags_fd, + ProcessMemory* process_memory) { + const pid_t pid = process_memory->pid; + int pagemap_fd = open( + base::StringPrintf("/proc/%d/pagemap", pid).c_str(), O_RDONLY); + if (pagemap_fd < 0) { + PLOG(ERROR) << "open"; + return false; + } + file_util::ScopedFD auto_closer(&pagemap_fd); + std::vector* const process_maps = &process_memory->memory_maps; + if (!GetProcessMaps(pid, process_maps)) + return false; + for (std::vector::iterator it = process_maps->begin(); + it != process_maps->end(); ++it) { + std::vector* const committed_pages = &it->committed_pages; + BitSet* const pages_bits = &it->committed_pages_bits; + GetPagesForMemoryMap(pagemap_fd, *it, committed_pages, pages_bits); + SetPagesInfo(page_count_fd, page_flags_fd, committed_pages); + } + return true; +} + +void KillAll(const std::vector& pids, int signal_number) { + for (std::vector::const_iterator it = pids.begin(); it != pids.end(); + ++it) { + kill(*it, signal_number); + } +} + +} // namespace + +int main(int argc, char** argv) { + if (argc == 1) { + LOG(ERROR) << "Usage: " << argv[0] << " [-a|-x] ... "; + return EXIT_FAILURE; + } + const bool short_output = !strncmp(argv[1], "-a", 2); + const bool extended_output = !strncmp(argv[1], "-x", 2); + if (short_output || extended_output) { + if (argc == 2) { + LOG(ERROR) << "Usage: " << argv[0] << " [-a|-x] ... "; + return EXIT_FAILURE; + } + ++argv; + } + std::vector pids; + for (const char* const* ptr = argv + 1; *ptr; ++ptr) { + pid_t pid; + if (!base::StringToInt(*ptr, &pid)) + return EXIT_FAILURE; + pids.push_back(pid); + } + + std::vector processes_memory(pids.size()); + { + int page_count_fd = open("/proc/kpagecount", O_RDONLY); + if (page_count_fd < 0) { + PLOG(ERROR) << "open /proc/kpagecount"; + return EXIT_FAILURE; + } + + int page_flags_fd = open("/proc/kpageflags", O_RDONLY); + if (page_flags_fd < 0) { + PLOG(ERROR) << "open /proc/kpageflags"; + return EXIT_FAILURE; + } + + file_util::ScopedFD page_count_fd_closer(&page_count_fd); + file_util::ScopedFD page_flags_fd_closer(&page_flags_fd); + + base::ScopedClosureRunner auto_resume_processes( + base::Bind(&KillAll, pids, SIGCONT)); + KillAll(pids, SIGSTOP); + for (std::vector::const_iterator it = pids.begin(); it != pids.end(); + ++it) { + ProcessMemory* const process_memory = + &processes_memory[it - pids.begin()]; + process_memory->pid = *it; + if (!CollectProcessMemoryInformation(page_count_fd, + page_flags_fd, + process_memory)) + return EXIT_FAILURE; + } + } + + ClassifyPages(&processes_memory); + if (short_output) + DumpProcessesMemoryMapsInShortFormat(processes_memory); + else if (extended_output) + DumpProcessesMemoryMapsInExtendedFormat(processes_memory); + else + DumpProcessesMemoryMaps(processes_memory); + return EXIT_SUCCESS; +} diff --git a/tools/android/memdump/memdump.gyp b/tools/android/memdump/memdump.gyp new file mode 100644 index 0000000000..faa24d0737 --- /dev/null +++ b/tools/android/memdump/memdump.gyp @@ -0,0 +1,31 @@ +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'targets': [ + { + 'target_name': 'memdump', + 'type': 'executable', + 'dependencies': [ + '../../../base/base.gyp:base', + ], + 'conditions': [ + # Warning: A PIE tool cannot run on ICS 4.0.4, so only + # build it as position-independent when ASAN + # is activated. See b/6587214 for details. + [ 'asan==1', { + 'cflags': [ + '-fPIE', + ], + 'ldflags': [ + '-pie', + ], + }], + ], + 'sources': [ + 'memdump.cc', + ], + }, + ], +} diff --git a/tools/android/memdump/memreport.py b/tools/android/memdump/memreport.py new file mode 100755 index 0000000000..1db1cb6b5e --- /dev/null +++ b/tools/android/memdump/memreport.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python +# +# 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. + +import re +import sys + +from sets import Set + + +_ENTRIES = [ + ('Total', '.* r... .*'), + ('Read-only', '.* r--. .*'), + ('Read-write', '.* rw.. .*'), + ('Executable', '.* ..x. .*'), + ('Anonymous total', '.* .... .* .*other=[0-9]+ ($|.*chromium:.*)'), + ('Anonymous read-write', '.* rw.. .* .*other=[0-9]+ ($|.*chromium:.*)'), + ('Anonymous executable (JIT\'ed code)', '.* ..x. .* shared_other=[0-9]+ $'), + ('File total', '.* .... .* /.*'), + ('File read-write', '.* rw.. .* /.*'), + ('File executable', '.* ..x. .* /.*'), + ('chromium mmap', '.* r... .*chromium:.*'), + ('chromium TransferBuffer', '.* r... .*chromium:.*CreateTransferBuffer.*'), + ('Galaxy Nexus GL driver', '.* r... .*pvrsrvkm.*'), + ('Dalvik', '.* rw.. .* /.*dalvik.*'), + ('Dalvik heap', '.* rw.. .* /.*dalvik-heap.*'), + ('Native heap (jemalloc)', '.* r... .* /.*jemalloc.*'), + ('System heap', '.* r... .* \\[heap\\]'), + ('Ashmem', '.* rw.. .* /dev/ashmem .*'), + ('libchromeview.so total', '.* r... .* /.*libchromeview.so'), + ('libchromeview.so read-only', '.* r--. .* /.*libchromeview.so'), + ('libchromeview.so read-write', '.* rw-. .* /.*libchromeview.so'), + ('libchromeview.so executable', '.* r.x. .* /.*libchromeview.so'), +] + + +def _CollectMemoryStats(memdump, region_filters): + processes = [] + mem_usage_for_regions = None + regexps = {} + for region_filter in region_filters: + regexps[region_filter] = re.compile(region_filter) + for line in memdump: + if 'PID=' in line: + mem_usage_for_regions = {} + processes.append(mem_usage_for_regions) + continue + matched_regions = Set([]) + for region_filter in region_filters: + if regexps[region_filter].match(line.rstrip('\r\n')): + matched_regions.add(region_filter) + if not region_filter in mem_usage_for_regions: + mem_usage_for_regions[region_filter] = { + 'private_unevictable': 0, + 'private': 0, + 'shared_app': 0.0, + 'shared_other_unevictable': 0, + 'shared_other': 0, + } + for matched_region in matched_regions: + mem_usage = mem_usage_for_regions[matched_region] + for key in mem_usage: + for token in line.split(' '): + if (key + '=') in token: + field = token.split('=')[1] + if key != 'shared_app': + mem_usage[key] += int(field) + else: # shared_app=[\d,\d...] + array = eval(field) + for i in xrange(len(array)): + mem_usage[key] += float(array[i]) / (i + 2) + break + return processes + + +def _ConvertMemoryField(field): + return str(field / (1024.0 * 1024)) + + +def _DumpCSV(processes_stats): + total_map = {} + i = 0 + for process in processes_stats: + i += 1 + print (',Process ' + str(i) + ',private,private_unevictable,shared_app,' + + 'shared_other,shared_other_unevictable,') + for (k, v) in _ENTRIES: + if not v in process: + print ',' + k + ',0,0,0,0,' + continue + if not v in total_map: + total_map[v] = {'resident':0, 'unevictable':0} + total_map[v]['resident'] += (process[v]['private'] + + process[v]['shared_app']) + total_map[v]['unevictable'] += process[v]['private_unevictable'] + print ( + ',' + k + ',' + + _ConvertMemoryField(process[v]['private']) + ',' + + _ConvertMemoryField(process[v]['private_unevictable']) + ',' + + _ConvertMemoryField(process[v]['shared_app']) + ',' + + _ConvertMemoryField(process[v]['shared_other']) + ',' + + _ConvertMemoryField(process[v]['shared_other_unevictable']) + ',' + ) + print '' + + for (k, v) in _ENTRIES: + if not v in total_map: + print ',' + k + ',0,0,' + continue + print (',' + k + ',' + _ConvertMemoryField(total_map[v]['resident']) + ',' + + _ConvertMemoryField(total_map[v]['unevictable']) + ',') + print '' + + +def main(argv): + _DumpCSV(_CollectMemoryStats(sys.stdin, [value for (key, value) in _ENTRIES])) + + +if __name__ == '__main__': + main(sys.argv) diff --git a/tools/android/memdump/memsymbols.py b/tools/android/memdump/memsymbols.py new file mode 100755 index 0000000000..372196303b --- /dev/null +++ b/tools/android/memdump/memsymbols.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python +# +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import base64 +import os +import sys +import re + +from optparse import OptionParser + +"""Extracts the list of resident symbols of a library loaded in a process. + +This scripts combines the extended output of memdump for a given process +(obtained through memdump -x PID) and the symbol table of a .so loaded in that +process (obtained through nm -C lib-with-symbols.so), filtering out only those +symbols that, at the time of the snapshot, were resident in memory (that are, +the symbols which start address belongs to a mapped page of the .so which was +resident at the time of the snapshot). +The aim is to perform a "code coverage"-like profiling of a binary, intersecting +run-time information (list of resident pages) and debug symbols. +""" + +_PAGE_SIZE = 4096 + + +def _TestBit(word, bit): + assert(bit >= 0 and bit < 8) + return not not ((word >> bit) & 1) + + +def _HexAddr(addr): + return hex(addr)[2:].zfill(8) + + +def _GetResidentPagesSet(memdump_contents, lib_name, verbose): + """Parses the memdump output and extracts the resident page set for lib_name. + Args: + memdump_contents: Array of strings (lines) of a memdump output. + lib_name: A string containing the name of the library.so to be matched. + verbose: Print a verbose header for each mapping matched. + + Returns: + A set of resident pages (the key is the page index) for all the + mappings matching .*lib_name. + """ + resident_pages = set() + MAP_RX = re.compile( + r'^([0-9a-f]+)-([0-9a-f]+) ([\w-]+) ([0-9a-f]+) .* "(.*)" \[(.*)\]$') + for line in memdump_contents: + line = line.rstrip('\r\n') + if line.startswith('[ PID'): + continue + + r = MAP_RX.match(line) + if not r: + sys.stderr.write('Skipping %s from %s\n' % (line, memdump_file)) + continue + + map_start = int(r.group(1), 16) + map_end = int(r.group(2), 16) + prot = r.group(3) + offset = int(r.group(4), 16) + assert(offset % _PAGE_SIZE == 0) + lib = r.group(5) + enc_bitmap = r.group(6) + + if not lib.endswith(lib_name): + continue + + bitmap = base64.b64decode(enc_bitmap) + map_pages_count = (map_end - map_start + 1) / _PAGE_SIZE + bitmap_pages_count = len(bitmap) * 8 + + if verbose: + print 'Found %s: mapped %d pages in mode %s @ offset %s.' % ( + lib, map_pages_count, prot, _HexAddr(offset)) + print ' Map range in the process VA: [%s - %s]. Len: %s' % ( + _HexAddr(map_start), + _HexAddr(map_end), + _HexAddr(map_pages_count * _PAGE_SIZE)) + print ' Corresponding addresses in the binary: [%s - %s]. Len: %s' % ( + _HexAddr(offset), + _HexAddr(offset + map_end - map_start), + _HexAddr(map_pages_count * _PAGE_SIZE)) + print ' Bitmap: %d pages' % bitmap_pages_count + print '' + + assert(bitmap_pages_count >= map_pages_count) + for i in xrange(map_pages_count): + bitmap_idx = i / 8 + bitmap_off = i % 8 + if (bitmap_idx < len(bitmap) and + _TestBit(ord(bitmap[bitmap_idx]), bitmap_off)): + resident_pages.add(offset / _PAGE_SIZE + i) + return resident_pages + + +def main(argv): + NM_RX = re.compile(r'^([0-9a-f]+)\s+.*$') + + parser = OptionParser() + parser.add_option("-r", "--reverse", + action="store_true", dest="reverse", default=False, + help="Print out non present symbols") + parser.add_option("-v", "--verbose", + action="store_true", dest="verbose", default=False, + help="Print out verbose debug information.") + + (options, args) = parser.parse_args() + + if len(args) != 3: + print 'Usage: %s [-v] memdump.file nm.file library.so' % ( + os.path.basename(argv[0])) + return 1 + + memdump_file = args[0] + nm_file = args[1] + lib_name = args[2] + + if memdump_file == '-': + memdump_contents = sys.stdin.readlines() + else: + memdump_contents = open(memdump_file, 'r').readlines() + resident_pages = _GetResidentPagesSet(memdump_contents, + lib_name, + options.verbose) + + # Process the nm symbol table, filtering out the resident symbols. + nm_fh = open(nm_file, 'r') + for line in nm_fh: + line = line.rstrip('\r\n') + # Skip undefined symbols (lines with no address). + if line.startswith(' '): + continue + + r = NM_RX.match(line) + if not r: + sys.stderr.write('Skipping %s from %s\n' % (line, nm_file)) + continue + + sym_addr = int(r.group(1), 16) + sym_page = sym_addr / _PAGE_SIZE + last_sym_matched = (sym_page in resident_pages) + if (sym_page in resident_pages) != options.reverse: + print line + return 0 + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/tools/android/remove_strings.py b/tools/android/remove_strings.py new file mode 100755 index 0000000000..b8c4807d88 --- /dev/null +++ b/tools/android/remove_strings.py @@ -0,0 +1,49 @@ +#!/usr/bin/python +# 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. + +"""Remove strings by name from a GRD file.""" + +import optparse +import re +import sys + + +def RemoveStrings(grd_path, string_names): + """Removes strings with the given names from a GRD file. Overwrites the file. + + Args: + grd_path: path to the GRD file. + string_names: a list of string names to be removed. + """ + with open(grd_path, 'r') as f: + grd = f.read() + names_pattern = '|'.join(map(re.escape, string_names)) + pattern = r']*name="(%s)".*?\s*' % names_pattern + grd = re.sub(pattern, '', grd, flags=re.DOTALL) + with open(grd_path, 'w') as f: + f.write(grd) + + +def ParseArgs(args): + usage = 'usage: %prog GRD_PATH...' + parser = optparse.OptionParser( + usage=usage, description='Remove strings from GRD files. Reads string ' + 'names from stdin, and removes strings with those names from the listed ' + 'GRD files.') + options, args = parser.parse_args(args=args) + if not args: + parser.error('must provide GRD_PATH argument(s)') + return args + + +def main(args=None): + grd_paths = ParseArgs(args) + strings_to_remove = filter(None, map(str.strip, sys.stdin.readlines())) + for grd_path in grd_paths: + RemoveStrings(grd_path, strings_to_remove) + + +if __name__ == '__main__': + main() diff --git a/tools/bash-completion b/tools/bash-completion new file mode 100644 index 0000000000..19172dab14 --- /dev/null +++ b/tools/bash-completion @@ -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. + +# Flag completion rule for bash. +# To load in your shell, "source path/to/this/file". + +chrome_source=$(cd $(dirname $BASH_SOURCE)/.. && pwd) + +_chrome_flag() { + local cur targets + cur="${COMP_WORDS[COMP_CWORD]}" + targets=$(cd $chrome_source; \ + git ls-files '*switches*' | \ + xargs sed -ne 's/^[^/]*"\([^" /]\{1,\}\)".*/--\1/p') + COMPREPLY=($(compgen -W "$targets" -- "$cur")) + return 0 +} + +complete -F _chrome_flag google-chrome +complete -F _chrome_flag chrome +if [ $(uname) = "Darwin" ] +then + complete -F _chrome_flag Chromium +fi diff --git a/tools/bisect-builds.py b/tools/bisect-builds.py new file mode 100755 index 0000000000..d9f394736b --- /dev/null +++ b/tools/bisect-builds.py @@ -0,0 +1,718 @@ +#!/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. + +"""Snapshot Build Bisect Tool + +This script bisects a snapshot archive using binary search. It starts at +a bad revision (it will try to guess HEAD) and asks for a last known-good +revision. It will then binary search across this revision range by downloading, +unzipping, and opening Chromium for you. After testing the specific revision, +it will ask you whether it is good or bad before continuing the search. +""" + +# The root URL for storage. +BASE_URL = 'http://commondatastorage.googleapis.com/chromium-browser-snapshots' + +# The root URL for official builds. +OFFICIAL_BASE_URL = 'http://master.chrome.corp.google.com/official_builds' + +# Changelogs URL. +CHANGELOG_URL = 'http://build.chromium.org/f/chromium/' \ + 'perf/dashboard/ui/changelog.html?' \ + 'url=/trunk/src&range=%d%%3A%d' + +# Official Changelogs URL. +OFFICIAL_CHANGELOG_URL = 'http://omahaproxy.appspot.com/'\ + 'changelog?old_version=%s&new_version=%s' + +# DEPS file URL. +DEPS_FILE= 'http://src.chromium.org/viewvc/chrome/trunk/src/DEPS?revision=%d' +# Blink Changelogs URL. +BLINK_CHANGELOG_URL = 'http://build.chromium.org/f/chromium/' \ + 'perf/dashboard/ui/changelog_blink.html?' \ + 'url=/trunk&range=%d%%3A%d' + +DONE_MESSAGE_GOOD_MIN = 'You are probably looking for a change made after %s ' \ + '(known good), but no later than %s (first known bad).' +DONE_MESSAGE_GOOD_MAX = 'You are probably looking for a change made after %s ' \ + '(known bad), but no later than %s (first known good).' + +############################################################################### + +import math +import optparse +import os +import pipes +import re +import shutil +import subprocess +import sys +import tempfile +import threading +import urllib +from distutils.version import LooseVersion +from xml.etree import ElementTree +import zipfile + + +class PathContext(object): + """A PathContext is used to carry the information used to construct URLs and + paths when dealing with the storage server and archives.""" + def __init__(self, platform, good_revision, bad_revision, is_official): + super(PathContext, self).__init__() + # Store off the input parameters. + self.platform = platform # What's passed in to the '-a/--archive' option. + self.good_revision = good_revision + self.bad_revision = bad_revision + self.is_official = is_official + + # The name of the ZIP file in a revision directory on the server. + self.archive_name = None + + # Set some internal members: + # _listing_platform_dir = Directory that holds revisions. Ends with a '/'. + # _archive_extract_dir = Uncompressed directory in the archive_name file. + # _binary_name = The name of the executable to run. + if self.platform in ('linux', 'linux64', 'linux-arm'): + self._binary_name = 'chrome' + elif self.platform == 'mac': + self.archive_name = 'chrome-mac.zip' + self._archive_extract_dir = 'chrome-mac' + elif self.platform == 'win': + self.archive_name = 'chrome-win32.zip' + self._archive_extract_dir = 'chrome-win32' + self._binary_name = 'chrome.exe' + else: + raise Exception('Invalid platform: %s' % self.platform) + + if is_official: + if self.platform == 'linux': + self._listing_platform_dir = 'lucid32bit/' + self.archive_name = 'chrome-lucid32bit.zip' + self._archive_extract_dir = 'chrome-lucid32bit' + elif self.platform == 'linux64': + self._listing_platform_dir = 'lucid64bit/' + self.archive_name = 'chrome-lucid64bit.zip' + self._archive_extract_dir = 'chrome-lucid64bit' + elif self.platform == 'mac': + self._listing_platform_dir = 'mac/' + self._binary_name = 'Google Chrome.app/Contents/MacOS/Google Chrome' + elif self.platform == 'win': + self._listing_platform_dir = 'win/' + else: + if self.platform in ('linux', 'linux64', 'linux-arm'): + self.archive_name = 'chrome-linux.zip' + self._archive_extract_dir = 'chrome-linux' + if self.platform == 'linux': + self._listing_platform_dir = 'Linux/' + elif self.platform == 'linux64': + self._listing_platform_dir = 'Linux_x64/' + elif self.platform == 'linux-arm': + self._listing_platform_dir = 'Linux_ARM_Cross-Compile/' + elif self.platform == 'mac': + self._listing_platform_dir = 'Mac/' + self._binary_name = 'Chromium.app/Contents/MacOS/Chromium' + elif self.platform == 'win': + self._listing_platform_dir = 'Win/' + + def GetListingURL(self, marker=None): + """Returns the URL for a directory listing, with an optional marker.""" + marker_param = '' + if marker: + marker_param = '&marker=' + str(marker) + return BASE_URL + '/?delimiter=/&prefix=' + self._listing_platform_dir + \ + marker_param + + def GetDownloadURL(self, revision): + """Gets the download URL for a build archive of a specific revision.""" + if self.is_official: + return "%s/%s/%s%s" % ( + OFFICIAL_BASE_URL, revision, self._listing_platform_dir, + self.archive_name) + else: + return "%s/%s%s/%s" % ( + BASE_URL, self._listing_platform_dir, revision, self.archive_name) + + def GetLastChangeURL(self): + """Returns a URL to the LAST_CHANGE file.""" + return BASE_URL + '/' + self._listing_platform_dir + 'LAST_CHANGE' + + def GetLaunchPath(self): + """Returns a relative path (presumably from the archive extraction location) + that is used to run the executable.""" + return os.path.join(self._archive_extract_dir, self._binary_name) + + def ParseDirectoryIndex(self): + """Parses the Google Storage directory listing into a list of revision + numbers.""" + + def _FetchAndParse(url): + """Fetches a URL and returns a 2-Tuple of ([revisions], next-marker). If + next-marker is not None, then the listing is a partial listing and another + fetch should be performed with next-marker being the marker= GET + parameter.""" + handle = urllib.urlopen(url) + document = ElementTree.parse(handle) + + # All nodes in the tree are namespaced. Get the root's tag name to extract + # the namespace. Etree does namespaces as |{namespace}tag|. + root_tag = document.getroot().tag + end_ns_pos = root_tag.find('}') + if end_ns_pos == -1: + raise Exception("Could not locate end namespace for directory index") + namespace = root_tag[:end_ns_pos + 1] + + # Find the prefix (_listing_platform_dir) and whether or not the list is + # truncated. + prefix_len = len(document.find(namespace + 'Prefix').text) + next_marker = None + is_truncated = document.find(namespace + 'IsTruncated') + if is_truncated is not None and is_truncated.text.lower() == 'true': + next_marker = document.find(namespace + 'NextMarker').text + + # Get a list of all the revisions. + all_prefixes = document.findall(namespace + 'CommonPrefixes/' + + namespace + 'Prefix') + # The nodes have content of the form of + # |_listing_platform_dir/revision/|. Strip off the platform dir and the + # trailing slash to just have a number. + revisions = [] + for prefix in all_prefixes: + revnum = prefix.text[prefix_len:-1] + try: + revnum = int(revnum) + revisions.append(revnum) + except ValueError: + pass + return (revisions, next_marker) + + # Fetch the first list of revisions. + (revisions, next_marker) = _FetchAndParse(self.GetListingURL()) + + # If the result list was truncated, refetch with the next marker. Do this + # until an entire directory listing is done. + while next_marker: + next_url = self.GetListingURL(next_marker) + (new_revisions, next_marker) = _FetchAndParse(next_url) + revisions.extend(new_revisions) + return revisions + + def GetRevList(self): + """Gets the list of revision numbers between self.good_revision and + self.bad_revision.""" + # Download the revlist and filter for just the range between good and bad. + minrev = min(self.good_revision, self.bad_revision) + maxrev = max(self.good_revision, self.bad_revision) + revlist = map(int, self.ParseDirectoryIndex()) + revlist = [x for x in revlist if x >= int(minrev) and x <= int(maxrev)] + revlist.sort() + return revlist + + def GetOfficialBuildsList(self): + """Gets the list of official build numbers between self.good_revision and + self.bad_revision.""" + # Download the revlist and filter for just the range between good and bad. + minrev = min(self.good_revision, self.bad_revision) + maxrev = max(self.good_revision, self.bad_revision) + handle = urllib.urlopen(OFFICIAL_BASE_URL) + dirindex = handle.read() + handle.close() + build_numbers = re.findall(r'
', dirindex) + final_list = [] + i = 0 + parsed_build_numbers = [LooseVersion(x) for x in build_numbers] + for build_number in sorted(parsed_build_numbers): + path = OFFICIAL_BASE_URL + '/' + str(build_number) + '/' + \ + self._listing_platform_dir + self.archive_name + i = i + 1 + try: + connection = urllib.urlopen(path) + connection.close() + if build_number > maxrev: + break + if build_number >= minrev: + final_list.append(str(build_number)) + except urllib.HTTPError, e: + pass + return final_list + +def UnzipFilenameToDir(filename, dir): + """Unzip |filename| to directory |dir|.""" + cwd = os.getcwd() + if not os.path.isabs(filename): + filename = os.path.join(cwd, filename) + zf = zipfile.ZipFile(filename) + # Make base. + if not os.path.isdir(dir): + os.mkdir(dir) + os.chdir(dir) + # Extract files. + for info in zf.infolist(): + name = info.filename + if name.endswith('/'): # dir + if not os.path.isdir(name): + os.makedirs(name) + else: # file + dir = os.path.dirname(name) + if not os.path.isdir(dir): + os.makedirs(dir) + out = open(name, 'wb') + out.write(zf.read(name)) + out.close() + # Set permissions. Permission info in external_attr is shifted 16 bits. + os.chmod(name, info.external_attr >> 16L) + os.chdir(cwd) + + +def FetchRevision(context, rev, filename, quit_event=None, progress_event=None): + """Downloads and unzips revision |rev|. + @param context A PathContext instance. + @param rev The Chromium revision number/tag to download. + @param filename The destination for the downloaded file. + @param quit_event A threading.Event which will be set by the master thread to + indicate that the download should be aborted. + @param progress_event A threading.Event which will be set by the master thread + to indicate that the progress of the download should be + displayed. + """ + def ReportHook(blocknum, blocksize, totalsize): + if quit_event and quit_event.isSet(): + raise RuntimeError("Aborting download of revision %s" % str(rev)) + if progress_event and progress_event.isSet(): + size = blocknum * blocksize + if totalsize == -1: # Total size not known. + progress = "Received %d bytes" % size + else: + size = min(totalsize, size) + progress = "Received %d of %d bytes, %.2f%%" % ( + size, totalsize, 100.0 * size / totalsize) + # Send a \r to let all progress messages use just one line of output. + sys.stdout.write("\r" + progress) + sys.stdout.flush() + + download_url = context.GetDownloadURL(rev) + try: + urllib.urlretrieve(download_url, filename, ReportHook) + if progress_event and progress_event.isSet(): + print + except RuntimeError, e: + pass + + +def RunRevision(context, revision, zipfile, profile, num_runs, command, args): + """Given a zipped revision, unzip it and run the test.""" + print "Trying revision %s..." % str(revision) + + # Create a temp directory and unzip the revision into it. + cwd = os.getcwd() + tempdir = tempfile.mkdtemp(prefix='bisect_tmp') + UnzipFilenameToDir(zipfile, tempdir) + os.chdir(tempdir) + + # Run the build as many times as specified. + testargs = ['--user-data-dir=%s' % profile] + args + # The sandbox must be run as root on Official Chrome, so bypass it. + if context.is_official and context.platform.startswith('linux'): + testargs.append('--no-sandbox') + + runcommand = [] + for token in command.split(): + if token == "%a": + runcommand.extend(testargs) + else: + runcommand.append( \ + token.replace('%p', context.GetLaunchPath()) \ + .replace('%s', ' '.join(testargs))) + + for i in range(0, num_runs): + subproc = subprocess.Popen(runcommand, + bufsize=-1, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + (stdout, stderr) = subproc.communicate() + + os.chdir(cwd) + try: + shutil.rmtree(tempdir, True) + except Exception, e: + pass + + return (subproc.returncode, stdout, stderr) + + +def AskIsGoodBuild(rev, official_builds, status, stdout, stderr): + """Ask the user whether build |rev| is good or bad.""" + # Loop until we get a response that we can parse. + while True: + response = raw_input('Revision %s is [(g)ood/(b)ad/(u)nknown/(q)uit]: ' % + str(rev)) + if response and response in ('g', 'b', 'u'): + return response + if response and response == 'q': + raise SystemExit() + + +class DownloadJob(object): + """DownloadJob represents a task to download a given Chromium revision.""" + def __init__(self, context, name, rev, zipfile): + super(DownloadJob, self).__init__() + # Store off the input parameters. + self.context = context + self.name = name + self.rev = rev + self.zipfile = zipfile + self.quit_event = threading.Event() + self.progress_event = threading.Event() + + def Start(self): + """Starts the download.""" + fetchargs = (self.context, + self.rev, + self.zipfile, + self.quit_event, + self.progress_event) + self.thread = threading.Thread(target=FetchRevision, + name=self.name, + args=fetchargs) + self.thread.start() + + def Stop(self): + """Stops the download which must have been started previously.""" + self.quit_event.set() + self.thread.join() + os.unlink(self.zipfile) + + def WaitFor(self): + """Prints a message and waits for the download to complete. The download + must have been started previously.""" + print "Downloading revision %s..." % str(self.rev) + self.progress_event.set() # Display progress of download. + self.thread.join() + + +def Bisect(platform, + official_builds, + good_rev=0, + bad_rev=0, + num_runs=1, + command="%p %a", + try_args=(), + profile=None, + evaluate=AskIsGoodBuild): + """Given known good and known bad revisions, run a binary search on all + archived revisions to determine the last known good revision. + + @param platform Which build to download/run ('mac', 'win', 'linux64', etc.). + @param official_builds Specify build type (Chromium or Official build). + @param good_rev Number/tag of the known good revision. + @param bad_rev Number/tag of the known bad revision. + @param num_runs Number of times to run each build for asking good/bad. + @param try_args A tuple of arguments to pass to the test application. + @param profile The name of the user profile to run with. + @param evaluate A function which returns 'g' if the argument build is good, + 'b' if it's bad or 'u' if unknown. + + Threading is used to fetch Chromium revisions in the background, speeding up + the user's experience. For example, suppose the bounds of the search are + good_rev=0, bad_rev=100. The first revision to be checked is 50. Depending on + whether revision 50 is good or bad, the next revision to check will be either + 25 or 75. So, while revision 50 is being checked, the script will download + revisions 25 and 75 in the background. Once the good/bad verdict on rev 50 is + known: + + - If rev 50 is good, the download of rev 25 is cancelled, and the next test + is run on rev 75. + + - If rev 50 is bad, the download of rev 75 is cancelled, and the next test + is run on rev 25. + """ + + if not profile: + profile = 'profile' + + context = PathContext(platform, good_rev, bad_rev, official_builds) + cwd = os.getcwd() + + + + print "Downloading list of known revisions..." + _GetDownloadPath = lambda rev: os.path.join(cwd, + '%s-%s' % (str(rev), context.archive_name)) + if official_builds: + revlist = context.GetOfficialBuildsList() + else: + revlist = context.GetRevList() + + # Get a list of revisions to bisect across. + if len(revlist) < 2: # Don't have enough builds to bisect. + msg = 'We don\'t have enough builds to bisect. revlist: %s' % revlist + raise RuntimeError(msg) + + # Figure out our bookends and first pivot point; fetch the pivot revision. + minrev = 0 + maxrev = len(revlist) - 1 + pivot = maxrev / 2 + rev = revlist[pivot] + zipfile = _GetDownloadPath(rev) + fetch = DownloadJob(context, 'initial_fetch', rev, zipfile) + fetch.Start() + fetch.WaitFor() + + # Binary search time! + while fetch and fetch.zipfile and maxrev - minrev > 1: + if bad_rev < good_rev: + min_str, max_str = "bad", "good" + else: + min_str, max_str = "good", "bad" + print 'Bisecting range [%s (%s), %s (%s)].' % (revlist[minrev], min_str, \ + revlist[maxrev], max_str) + + # Pre-fetch next two possible pivots + # - down_pivot is the next revision to check if the current revision turns + # out to be bad. + # - up_pivot is the next revision to check if the current revision turns + # out to be good. + down_pivot = int((pivot - minrev) / 2) + minrev + down_fetch = None + if down_pivot != pivot and down_pivot != minrev: + down_rev = revlist[down_pivot] + down_fetch = DownloadJob(context, 'down_fetch', down_rev, + _GetDownloadPath(down_rev)) + down_fetch.Start() + + up_pivot = int((maxrev - pivot) / 2) + pivot + up_fetch = None + if up_pivot != pivot and up_pivot != maxrev: + up_rev = revlist[up_pivot] + up_fetch = DownloadJob(context, 'up_fetch', up_rev, + _GetDownloadPath(up_rev)) + up_fetch.Start() + + # Run test on the pivot revision. + status = None + stdout = None + stderr = None + try: + (status, stdout, stderr) = RunRevision(context, + rev, + fetch.zipfile, + profile, + num_runs, + command, + try_args) + except Exception, e: + print >>sys.stderr, e + fetch.Stop() + fetch = None + + # Call the evaluate function to see if the current revision is good or bad. + # On that basis, kill one of the background downloads and complete the + # other, as described in the comments above. + try: + answer = evaluate(rev, official_builds, status, stdout, stderr) + if answer == 'g' and good_rev < bad_rev or \ + answer == 'b' and bad_rev < good_rev: + minrev = pivot + if down_fetch: + down_fetch.Stop() # Kill the download of the older revision. + if up_fetch: + up_fetch.WaitFor() + pivot = up_pivot + fetch = up_fetch + elif answer == 'b' and good_rev < bad_rev or \ + answer == 'g' and bad_rev < good_rev: + maxrev = pivot + if up_fetch: + up_fetch.Stop() # Kill the download of the newer revision. + if down_fetch: + down_fetch.WaitFor() + pivot = down_pivot + fetch = down_fetch + elif answer == 'u': + # Nuke the revision from the revlist and choose a new pivot. + revlist.pop(pivot) + maxrev -= 1 # Assumes maxrev >= pivot. + + if maxrev - minrev > 1: + # Alternate between using down_pivot or up_pivot for the new pivot + # point, without affecting the range. Do this instead of setting the + # pivot to the midpoint of the new range because adjacent revisions + # are likely affected by the same issue that caused the (u)nknown + # response. + if up_fetch and down_fetch: + fetch = [up_fetch, down_fetch][len(revlist) % 2] + elif up_fetch: + fetch = up_fetch + else: + fetch = down_fetch + fetch.WaitFor() + if fetch == up_fetch: + pivot = up_pivot - 1 # Subtracts 1 because revlist was resized. + else: + pivot = down_pivot + zipfile = fetch.zipfile + + if down_fetch and fetch != down_fetch: + down_fetch.Stop() + if up_fetch and fetch != up_fetch: + up_fetch.Stop() + else: + assert False, "Unexpected return value from evaluate(): " + answer + except SystemExit: + print "Cleaning up..." + for f in [_GetDownloadPath(revlist[down_pivot]), + _GetDownloadPath(revlist[up_pivot])]: + try: + os.unlink(f) + except OSError: + pass + sys.exit(0) + + rev = revlist[pivot] + + return (revlist[minrev], revlist[maxrev]) + + +def GetBlinkRevisionForChromiumRevision(rev): + """Returns the blink revision that was in chromium's DEPS file at + chromium revision |rev|.""" + # . doesn't match newlines without re.DOTALL, so this is safe. + blink_re = re.compile(r'webkit_revision.:\D*(\d+)') + url = urllib.urlopen(DEPS_FILE % rev) + m = blink_re.search(url.read()) + url.close() + if m: + return int(m.group(1)) + else: + raise Exception('Could not get blink revision for cr rev %d' % rev) + + +def GetChromiumRevision(url): + """Returns the chromium revision read from given URL.""" + try: + # Location of the latest build revision number + return int(urllib.urlopen(url).read()) + except Exception, e: + print('Could not determine latest revision. This could be bad...') + return 999999999 + + +def main(): + usage = ('%prog [options] [-- chromium-options]\n' + 'Perform binary search on the snapshot builds to find a minimal\n' + 'range of revisions where a behavior change happened. The\n' + 'behaviors are described as "good" and "bad".\n' + 'It is NOT assumed that the behavior of the later revision is\n' + 'the bad one.\n' + '\n' + 'Revision numbers should use\n' + ' Official versions (e.g. 1.0.1000.0) for official builds. (-o)\n' + ' SVN revisions (e.g. 123456) for chromium builds, from trunk.\n' + ' Use base_trunk_revision from http://omahaproxy.appspot.com/\n' + ' for earlier revs.\n' + ' Chrome\'s about: build number and omahaproxy branch_revision\n' + ' are incorrect, they are from branches.\n' + '\n' + 'Tip: add "-- --no-first-run" to bypass the first run prompts.') + parser = optparse.OptionParser(usage=usage) + # Strangely, the default help output doesn't include the choice list. + choices = ['mac', 'win', 'linux', 'linux64', 'linux-arm'] + # linux-chromiumos lacks a continuous archive http://crbug.com/78158 + parser.add_option('-a', '--archive', + choices = choices, + help = 'The buildbot archive to bisect [%s].' % + '|'.join(choices)) + parser.add_option('-o', action="store_true", dest='official_builds', + help = 'Bisect across official ' + + 'Chrome builds (internal only) instead of ' + + 'Chromium archives.') + parser.add_option('-b', '--bad', type = 'str', + help = 'A bad revision to start bisection. ' + + 'May be earlier or later than the good revision. ' + + 'Default is HEAD.') + parser.add_option('-g', '--good', type = 'str', + help = 'A good revision to start bisection. ' + + 'May be earlier or later than the bad revision. ' + + 'Default is 0.') + parser.add_option('-p', '--profile', '--user-data-dir', type = 'str', + help = 'Profile to use; this will not reset every run. ' + + 'Defaults to a clean profile.', default = 'profile') + parser.add_option('-t', '--times', type = 'int', + help = 'Number of times to run each build before asking ' + + 'if it\'s good or bad. Temporary profiles are reused.', + default = 1) + parser.add_option('-c', '--command', type = 'str', + help = 'Command to execute. %p and %a refer to Chrome ' + + 'executable and specified extra arguments respectively. ' + + 'Use %s to specify all extra arguments as one string. ' + + 'Defaults to "%p %a". Note that any extra paths ' + + 'specified should be absolute.', + default = '%p %a'); + (opts, args) = parser.parse_args() + + if opts.archive is None: + print 'Error: missing required parameter: --archive' + print + parser.print_help() + return 1 + + # Create the context. Initialize 0 for the revisions as they are set below. + context = PathContext(opts.archive, 0, 0, opts.official_builds) + # Pick a starting point, try to get HEAD for this. + if opts.bad: + bad_rev = opts.bad + else: + bad_rev = '999.0.0.0' + if not opts.official_builds: + bad_rev = GetChromiumRevision(context.GetLastChangeURL()) + + # Find out when we were good. + if opts.good: + good_rev = opts.good + else: + good_rev = '0.0.0.0' if opts.official_builds else 0 + + if opts.official_builds: + good_rev = LooseVersion(good_rev) + bad_rev = LooseVersion(bad_rev) + else: + good_rev = int(good_rev) + bad_rev = int(bad_rev) + + if opts.times < 1: + print('Number of times to run (%d) must be greater than or equal to 1.' % + opts.times) + parser.print_help() + return 1 + + (min_chromium_rev, max_chromium_rev) = Bisect( + opts.archive, opts.official_builds, good_rev, bad_rev, opts.times, + opts.command, args, opts.profile) + + # Get corresponding blink revisions. + try: + min_blink_rev = GetBlinkRevisionForChromiumRevision(min_chromium_rev) + max_blink_rev = GetBlinkRevisionForChromiumRevision(max_chromium_rev) + except Exception, e: + # Silently ignore the failure. + min_blink_rev, max_blink_rev = 0, 0 + + # We're done. Let the user know the results in an official manner. + if good_rev > bad_rev: + print DONE_MESSAGE_GOOD_MAX % (str(min_chromium_rev), str(max_chromium_rev)) + else: + print DONE_MESSAGE_GOOD_MIN % (str(min_chromium_rev), str(max_chromium_rev)) + + if min_blink_rev != max_blink_rev: + print 'BLINK CHANGELOG URL:' + print ' ' + BLINK_CHANGELOG_URL % (max_blink_rev, min_blink_rev) + print 'CHANGELOG URL:' + if opts.official_builds: + print OFFICIAL_CHANGELOG_URL % (min_chromium_rev, max_chromium_rev) + else: + print ' ' + CHANGELOG_URL % (min_chromium_rev, max_chromium_rev) + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/bisect-manual-test.py b/tools/bisect-manual-test.py new file mode 100755 index 0000000000..62aa072c03 --- /dev/null +++ b/tools/bisect-manual-test.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Simple script which asks user to manually check result of bisection. + +Typically used as by the run-bisect-manual-test.py script. +""" + +import os +import sys + +sys.path.append(os.path.join(os.path.dirname(__file__), 'telemetry')) +from telemetry.core import browser_finder +from telemetry.core import browser_options + + +def _StartManualTest(options): + """Start browser then ask the user whether build is good or bad.""" + browser_to_create = browser_finder.FindBrowser(options) + print 'Starting browser: %s.' % options.browser_type + with browser_to_create.Create() as browser: + + # Loop until we get a response that we can parse. + while True: + sys.stderr.write('Revision is [(g)ood/(b)ad]: ') + response = raw_input() + if response and response in ('g', 'b'): + if response in ('g'): + print "RESULT manual_test: manual_test= 1" + else: + print "RESULT manual_test: manual_test= 0" + break + + browser.Close() + + +def main(): + usage = ('%prog [options]\n' + 'Starts browser with an optional url and asks user whether ' + 'revision is good or bad.\n') + + options = browser_options.BrowserOptions() + parser = options.CreateParser(usage) + options, args = parser.parse_args() + + _StartManualTest(options) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/bisect-perf-regression.py b/tools/bisect-perf-regression.py new file mode 100755 index 0000000000..4947ece657 --- /dev/null +++ b/tools/bisect-perf-regression.py @@ -0,0 +1,2456 @@ +#!/usr/bin/env python +# 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. + +"""Performance Test Bisect Tool + +This script bisects a series of changelists using binary search. It starts at +a bad revision where a performance metric has regressed, and asks for a last +known-good revision. It will then binary search across this revision range by +syncing, building, and running a performance test. If the change is +suspected to occur as a result of WebKit/V8 changes, the script will +further bisect changes to those depots and attempt to narrow down the revision +range. + + +An example usage (using svn cl's): + +./tools/bisect-perf-regression.py -c\ +"out/Release/performance_ui_tests --gtest_filter=ShutdownTest.SimpleUserQuit"\ +-g 168222 -b 168232 -m shutdown/simple-user-quit + +Be aware that if you're using the git workflow and specify an svn revision, +the script will attempt to find the git SHA1 where svn changes up to that +revision were merged in. + + +An example usage (using git hashes): + +./tools/bisect-perf-regression.py -c\ +"out/Release/performance_ui_tests --gtest_filter=ShutdownTest.SimpleUserQuit"\ +-g 1f6e67861535121c5c819c16a666f2436c207e7b\ +-b b732f23b4f81c382db0b23b9035f3dadc7d925bb\ +-m shutdown/simple-user-quit + +""" + +import errno +import imp +import math +import optparse +import os +import re +import shlex +import shutil +import subprocess +import sys +import threading +import time + +import bisect_utils + + +# The additional repositories that might need to be bisected. +# If the repository has any dependant repositories (such as skia/src needs +# skia/include and skia/gyp to be updated), specify them in the 'depends' +# so that they're synced appropriately. +# Format is: +# src: path to the working directory. +# recurse: True if this repositry will get bisected. +# depends: A list of other repositories that are actually part of the same +# repository in svn. +# svn: Needed for git workflow to resolve hashes to svn revisions. +# from: Parent depot that must be bisected before this is bisected. +DEPOT_DEPS_NAME = { + 'chromium' : { + "src" : "src/", + "recurse" : True, + "depends" : None, + "from" : 'cros' + }, + 'webkit' : { + "src" : "src/third_party/WebKit", + "recurse" : True, + "depends" : None, + "from" : 'chromium' + }, + 'v8' : { + "src" : "src/v8", + "recurse" : True, + "depends" : None, + "build_with": 'v8_bleeding_edge', + "from" : 'chromium', + "custom_deps": bisect_utils.GCLIENT_CUSTOM_DEPS_V8 + }, + 'v8_bleeding_edge' : { + "src" : "src/v8_bleeding_edge", + "recurse" : False, + "depends" : None, + "svn": "https://v8.googlecode.com/svn/branches/bleeding_edge", + "from" : 'chromium' + }, + 'skia/src' : { + "src" : "src/third_party/skia/src", + "recurse" : True, + "svn" : "http://skia.googlecode.com/svn/trunk/src", + "depends" : ['skia/include', 'skia/gyp'], + "from" : 'chromium' + }, + 'skia/include' : { + "src" : "src/third_party/skia/include", + "recurse" : False, + "svn" : "http://skia.googlecode.com/svn/trunk/include", + "depends" : None, + "from" : 'chromium' + }, + 'skia/gyp' : { + "src" : "src/third_party/skia/gyp", + "recurse" : False, + "svn" : "http://skia.googlecode.com/svn/trunk/gyp", + "depends" : None, + "from" : 'chromium' + } +} + +DEPOT_NAMES = DEPOT_DEPS_NAME.keys() +CROS_SDK_PATH = os.path.join('..', 'cros', 'chromite', 'bin', 'cros_sdk') +CROS_VERSION_PATTERN = 'new version number from %s' +CROS_CHROMEOS_PATTERN = 'chromeos-base/chromeos-chrome' +CROS_TEST_KEY_PATH = os.path.join('..', 'cros', 'chromite', 'ssh_keys', + 'testing_rsa') +CROS_SCRIPT_KEY_PATH = os.path.join('..', 'cros', 'src', 'scripts', + 'mod_for_test_scripts', 'ssh_keys', + 'testing_rsa') + +BUILD_RESULT_SUCCEED = 0 +BUILD_RESULT_FAIL = 1 +BUILD_RESULT_SKIPPED = 2 + +def CalculateTruncatedMean(data_set, truncate_percent): + """Calculates the truncated mean of a set of values. + + Args: + data_set: Set of values to use in calculation. + truncate_percent: The % from the upper/lower portions of the data set to + discard, expressed as a value in [0, 1]. + + Returns: + The truncated mean as a float. + """ + if len(data_set) > 2: + data_set = sorted(data_set) + + discard_num_float = len(data_set) * truncate_percent + discard_num_int = int(math.floor(discard_num_float)) + kept_weight = len(data_set) - discard_num_float * 2 + + data_set = data_set[discard_num_int:len(data_set)-discard_num_int] + + weight_left = 1.0 - (discard_num_float - discard_num_int) + + if weight_left < 1: + # If the % to discard leaves a fractional portion, need to weight those + # values. + unweighted_vals = data_set[1:len(data_set)-1] + weighted_vals = [data_set[0], data_set[len(data_set)-1]] + weighted_vals = [w * weight_left for w in weighted_vals] + data_set = weighted_vals + unweighted_vals + else: + kept_weight = len(data_set) + + truncated_mean = reduce(lambda x, y: float(x) + float(y), + data_set) / kept_weight + + return truncated_mean + + +def CalculateStandardDeviation(v): + if len(v) == 1: + return 0.0 + + mean = CalculateTruncatedMean(v, 0.0) + variances = [float(x) - mean for x in v] + variances = [x * x for x in variances] + variance = reduce(lambda x, y: float(x) + float(y), variances) / (len(v) - 1) + std_dev = math.sqrt(variance) + + return std_dev + + +def IsStringFloat(string_to_check): + """Checks whether or not the given string can be converted to a floating + point number. + + Args: + string_to_check: Input string to check if it can be converted to a float. + + Returns: + True if the string can be converted to a float. + """ + try: + float(string_to_check) + + return True + except ValueError: + return False + + +def IsStringInt(string_to_check): + """Checks whether or not the given string can be converted to a integer. + + Args: + string_to_check: Input string to check if it can be converted to an int. + + Returns: + True if the string can be converted to an int. + """ + try: + int(string_to_check) + + return True + except ValueError: + return False + + +def IsWindows(): + """Checks whether or not the script is running on Windows. + + Returns: + True if running on Windows. + """ + return os.name == 'nt' + + +def RunProcess(command): + """Run an arbitrary command. If output from the call is needed, use + RunProcessAndRetrieveOutput instead. + + Args: + command: A list containing the command and args to execute. + + Returns: + The return code of the call. + """ + # On Windows, use shell=True to get PATH interpretation. + shell = IsWindows() + return subprocess.call(command, shell=shell) + + +def RunProcessAndRetrieveOutput(command): + """Run an arbitrary command, returning its output and return code. Since + output is collected via communicate(), there will be no output until the + call terminates. If you need output while the program runs (ie. so + that the buildbot doesn't terminate the script), consider RunProcess(). + + Args: + command: A list containing the command and args to execute. + print_output: Optional parameter to write output to stdout as it's + being collected. + + Returns: + A tuple of the output and return code. + """ + # On Windows, use shell=True to get PATH interpretation. + shell = IsWindows() + proc = subprocess.Popen(command, + shell=shell, + stdout=subprocess.PIPE) + + (output, _) = proc.communicate() + + return (output, proc.returncode) + + +def RunGit(command): + """Run a git subcommand, returning its output and return code. + + Args: + command: A list containing the args to git. + + Returns: + A tuple of the output and return code. + """ + command = ['git'] + command + + return RunProcessAndRetrieveOutput(command) + + +def CheckRunGit(command): + """Run a git subcommand, returning its output and return code. Asserts if + the return code of the call is non-zero. + + Args: + command: A list containing the args to git. + + Returns: + A tuple of the output and return code. + """ + (output, return_code) = RunGit(command) + + assert not return_code, 'An error occurred while running'\ + ' "git %s"' % ' '.join(command) + return output + + +def BuildWithMake(threads, targets): + cmd = ['make', 'BUILDTYPE=Release'] + + if threads: + cmd.append('-j%d' % threads) + + cmd += targets + + return_code = RunProcess(cmd) + + return not return_code + + +def BuildWithNinja(threads, targets): + cmd = ['ninja', '-C', os.path.join('out', 'Release')] + + if threads: + cmd.append('-j%d' % threads) + + cmd += targets + + return_code = RunProcess(cmd) + + return not return_code + + +def BuildWithVisualStudio(targets): + path_to_devenv = os.path.abspath( + os.path.join(os.environ['VS100COMNTOOLS'], '..', 'IDE', 'devenv.com')) + path_to_sln = os.path.join(os.getcwd(), 'chrome', 'chrome.sln') + cmd = [path_to_devenv, '/build', 'Release', path_to_sln] + + for t in targets: + cmd.extend(['/Project', t]) + + return_code = RunProcess(cmd) + + return not return_code + + +class Builder(object): + """Builder is used by the bisect script to build relevant targets and deploy. + """ + def Build(self, depot, opts): + raise NotImplementedError() + + +class DesktopBuilder(Builder): + """DesktopBuilder is used to build Chromium on linux/mac/windows.""" + def Build(self, depot, opts): + """Builds chrome and performance_ui_tests using options passed into + the script. + + Args: + depot: Current depot being bisected. + opts: The options parsed from the command line. + + Returns: + True if build was successful. + """ + targets = ['chrome', 'performance_ui_tests'] + + threads = None + if opts.use_goma: + threads = 64 + + build_success = False + if opts.build_preference == 'make': + build_success = BuildWithMake(threads, targets) + elif opts.build_preference == 'ninja': + if IsWindows(): + targets = [t + '.exe' for t in targets] + build_success = BuildWithNinja(threads, targets) + elif opts.build_preference == 'msvs': + assert IsWindows(), 'msvs is only supported on Windows.' + build_success = BuildWithVisualStudio(targets) + else: + assert False, 'No build system defined.' + return build_success + + +class AndroidBuilder(Builder): + """AndroidBuilder is used to build on android.""" + def InstallAPK(self, opts): + """Installs apk to device. + + Args: + opts: The options parsed from the command line. + + Returns: + True if successful. + """ + path_to_tool = os.path.join('build', 'android', 'adb_install_apk.py') + cmd = [path_to_tool, '--apk', 'ChromiumTestShell.apk', '--apk_package', + 'org.chromium.chrome.testshell', '--release'] + return_code = RunProcess(cmd) + + return not return_code + + def Build(self, depot, opts): + """Builds the android content shell and other necessary tools using options + passed into the script. + + Args: + depot: Current depot being bisected. + opts: The options parsed from the command line. + + Returns: + True if build was successful. + """ + targets = ['chromium_testshell', 'forwarder2', 'md5sum'] + threads = None + if opts.use_goma: + threads = 64 + + build_success = False + if opts.build_preference == 'ninja': + build_success = BuildWithNinja(threads, targets) + else: + assert False, 'No build system defined.' + + if build_success: + build_success = self.InstallAPK(opts) + + return build_success + + +class CrosBuilder(Builder): + """CrosBuilder is used to build and image ChromeOS/Chromium when cros is the + target platform.""" + def ImageToTarget(self, opts): + """Installs latest image to target specified by opts.cros_remote_ip. + + Args: + opts: Program options containing cros_board and cros_remote_ip. + + Returns: + True if successful. + """ + try: + # Keys will most likely be set to 0640 after wiping the chroot. + os.chmod(CROS_SCRIPT_KEY_PATH, 0600) + os.chmod(CROS_TEST_KEY_PATH, 0600) + cmd = [CROS_SDK_PATH, '--', './bin/cros_image_to_target.py', + '--remote=%s' % opts.cros_remote_ip, + '--board=%s' % opts.cros_board, '--test', '--verbose'] + + return_code = RunProcess(cmd) + return not return_code + except OSError, e: + return False + + def BuildPackages(self, opts, depot): + """Builds packages for cros. + + Args: + opts: Program options containing cros_board. + depot: The depot being bisected. + + Returns: + True if successful. + """ + cmd = [CROS_SDK_PATH] + + if depot != 'cros': + path_to_chrome = os.path.join(os.getcwd(), '..') + cmd += ['--chrome_root=%s' % path_to_chrome] + + cmd += ['--'] + + if depot != 'cros': + cmd += ['CHROME_ORIGIN=LOCAL_SOURCE'] + + cmd += ['BUILDTYPE=Release', './build_packages', + '--board=%s' % opts.cros_board] + return_code = RunProcess(cmd) + + return not return_code + + def BuildImage(self, opts, depot): + """Builds test image for cros. + + Args: + opts: Program options containing cros_board. + depot: The depot being bisected. + + Returns: + True if successful. + """ + cmd = [CROS_SDK_PATH] + + if depot != 'cros': + path_to_chrome = os.path.join(os.getcwd(), '..') + cmd += ['--chrome_root=%s' % path_to_chrome] + + cmd += ['--'] + + if depot != 'cros': + cmd += ['CHROME_ORIGIN=LOCAL_SOURCE'] + + cmd += ['BUILDTYPE=Release', '--', './build_image', + '--board=%s' % opts.cros_board, 'test'] + + return_code = RunProcess(cmd) + + return not return_code + + def Build(self, depot, opts): + """Builds targets using options passed into the script. + + Args: + depot: Current depot being bisected. + opts: The options parsed from the command line. + + Returns: + True if build was successful. + """ + if self.BuildPackages(opts, depot): + if self.BuildImage(opts, depot): + return self.ImageToTarget(opts) + return False + + +class SourceControl(object): + """SourceControl is an abstraction over the underlying source control + system used for chromium. For now only git is supported, but in the + future, the svn workflow could be added as well.""" + def __init__(self): + super(SourceControl, self).__init__() + + def SyncToRevisionWithGClient(self, revision): + """Uses gclient to sync to the specified revision. + + ie. gclient sync --revision + + Args: + revision: The git SHA1 or svn CL (depending on workflow). + + Returns: + The return code of the call. + """ + return bisect_utils.RunGClient(['sync', '--revision', + revision, '--verbose', '--nohooks', '--reset', '--force']) + + def SyncToRevisionWithRepo(self, timestamp): + """Uses repo to sync all the underlying git depots to the specified + time. + + Args: + timestamp: The unix timestamp to sync to. + + Returns: + The return code of the call. + """ + return bisect_utils.RunRepoSyncAtTimestamp(timestamp) + + +class GitSourceControl(SourceControl): + """GitSourceControl is used to query the underlying source control. """ + def __init__(self, opts): + super(GitSourceControl, self).__init__() + self.opts = opts + + def IsGit(self): + return True + + def GetRevisionList(self, revision_range_end, revision_range_start): + """Retrieves a list of revisions between |revision_range_start| and + |revision_range_end|. + + Args: + revision_range_end: The SHA1 for the end of the range. + revision_range_start: The SHA1 for the beginning of the range. + + Returns: + A list of the revisions between |revision_range_start| and + |revision_range_end| (inclusive). + """ + revision_range = '%s..%s' % (revision_range_start, revision_range_end) + cmd = ['log', '--format=%H', '-10000', '--first-parent', revision_range] + log_output = CheckRunGit(cmd) + + revision_hash_list = log_output.split() + revision_hash_list.append(revision_range_start) + + return revision_hash_list + + def SyncToRevision(self, revision, sync_client=None): + """Syncs to the specified revision. + + Args: + revision: The revision to sync to. + use_gclient: Specifies whether or not we should sync using gclient or + just use source control directly. + + Returns: + True if successful. + """ + + if not sync_client: + results = RunGit(['checkout', revision])[1] + elif sync_client == 'gclient': + results = self.SyncToRevisionWithGClient(revision) + elif sync_client == 'repo': + results = self.SyncToRevisionWithRepo(revision) + + return not results + + def ResolveToRevision(self, revision_to_check, depot, search): + """If an SVN revision is supplied, try to resolve it to a git SHA1. + + Args: + revision_to_check: The user supplied revision string that may need to be + resolved to a git SHA1. + depot: The depot the revision_to_check is from. + search: The number of changelists to try if the first fails to resolve + to a git hash. If the value is negative, the function will search + backwards chronologically, otherwise it will search forward. + + Returns: + A string containing a git SHA1 hash, otherwise None. + """ + if depot != 'cros': + if not IsStringInt(revision_to_check): + return revision_to_check + + depot_svn = 'svn://svn.chromium.org/chrome/trunk/src' + + if depot != 'chromium': + depot_svn = DEPOT_DEPS_NAME[depot]['svn'] + + svn_revision = int(revision_to_check) + git_revision = None + + if search > 0: + search_range = xrange(svn_revision, svn_revision + search, 1) + else: + search_range = xrange(svn_revision, svn_revision + search, -1) + + for i in search_range: + svn_pattern = 'git-svn-id: %s@%d' % (depot_svn, i) + cmd = ['log', '--format=%H', '-1', '--grep', svn_pattern, + 'origin/master'] + + (log_output, return_code) = RunGit(cmd) + + assert not return_code, 'An error occurred while running'\ + ' "git %s"' % ' '.join(cmd) + + if not return_code: + log_output = log_output.strip() + + if log_output: + git_revision = log_output + + break + + return git_revision + else: + if IsStringInt(revision_to_check): + return int(revision_to_check) + else: + cwd = os.getcwd() + os.chdir(os.path.join(os.getcwd(), 'src', 'third_party', + 'chromiumos-overlay')) + pattern = CROS_VERSION_PATTERN % revision_to_check + cmd = ['log', '--format=%ct', '-1', '--grep', pattern] + + git_revision = None + + log_output = CheckRunGit(cmd) + if log_output: + git_revision = log_output + git_revision = int(log_output.strip()) + os.chdir(cwd) + + return git_revision + + def IsInProperBranch(self): + """Confirms they're in the master branch for performing the bisection. + This is needed or gclient will fail to sync properly. + + Returns: + True if the current branch on src is 'master' + """ + cmd = ['rev-parse', '--abbrev-ref', 'HEAD'] + log_output = CheckRunGit(cmd) + log_output = log_output.strip() + + return log_output == "master" + + def SVNFindRev(self, revision): + """Maps directly to the 'git svn find-rev' command. + + Args: + revision: The git SHA1 to use. + + Returns: + An integer changelist #, otherwise None. + """ + + cmd = ['svn', 'find-rev', revision] + + output = CheckRunGit(cmd) + svn_revision = output.strip() + + if IsStringInt(svn_revision): + return int(svn_revision) + + return None + + def QueryRevisionInfo(self, revision): + """Gathers information on a particular revision, such as author's name, + email, subject, and date. + + Args: + revision: Revision you want to gather information on. + Returns: + A dict in the following format: + { + 'author': %s, + 'email': %s, + 'date': %s, + 'subject': %s, + } + """ + commit_info = {} + + formats = ['%cN', '%cE', '%s', '%cD'] + targets = ['author', 'email', 'subject', 'date'] + + for i in xrange(len(formats)): + cmd = ['log', '--format=%s' % formats[i], '-1', revision] + output = CheckRunGit(cmd) + commit_info[targets[i]] = output.rstrip() + + return commit_info + + def CheckoutFileAtRevision(self, file_name, revision): + """Performs a checkout on a file at the given revision. + + Returns: + True if successful. + """ + return not RunGit(['checkout', revision, file_name])[1] + + def RevertFileToHead(self, file_name): + """Unstages a file and returns it to HEAD. + + Returns: + True if successful. + """ + # Reset doesn't seem to return 0 on success. + RunGit(['reset', 'HEAD', bisect_utils.FILE_DEPS_GIT]) + + return not RunGit(['checkout', bisect_utils.FILE_DEPS_GIT])[1] + + def QueryFileRevisionHistory(self, filename, revision_start, revision_end): + """Returns a list of commits that modified this file. + + Args: + filename: Name of file. + revision_start: Start of revision range. + revision_end: End of revision range. + + Returns: + Returns a list of commits that touched this file. + """ + cmd = ['log', '--format=%H', '%s~1..%s' % (revision_start, revision_end), + filename] + output = CheckRunGit(cmd) + + return [o for o in output.split('\n') if o] + +class BisectPerformanceMetrics(object): + """BisectPerformanceMetrics performs a bisection against a list of range + of revisions to narrow down where performance regressions may have + occurred.""" + + def __init__(self, source_control, opts): + super(BisectPerformanceMetrics, self).__init__() + + self.opts = opts + self.source_control = source_control + self.src_cwd = os.getcwd() + self.cros_cwd = os.path.join(os.getcwd(), '..', 'cros') + self.depot_cwd = {} + self.cleanup_commands = [] + self.warnings = [] + self.builder = None + + if opts.target_platform == 'cros': + self.builder = CrosBuilder() + elif opts.target_platform == 'android': + self.builder = AndroidBuilder() + else: + self.builder = DesktopBuilder() + + # This always starts true since the script grabs latest first. + self.was_blink = True + + for d in DEPOT_NAMES: + # The working directory of each depot is just the path to the depot, but + # since we're already in 'src', we can skip that part. + + self.depot_cwd[d] = self.src_cwd + DEPOT_DEPS_NAME[d]['src'][3:] + + def PerformCleanup(self): + """Performs cleanup when script is finished.""" + os.chdir(self.src_cwd) + for c in self.cleanup_commands: + if c[0] == 'mv': + shutil.move(c[1], c[2]) + else: + assert False, 'Invalid cleanup command.' + + def GetRevisionList(self, depot, bad_revision, good_revision): + """Retrieves a list of all the commits between the bad revision and + last known good revision.""" + + revision_work_list = [] + + if depot == 'cros': + revision_range_start = good_revision + revision_range_end = bad_revision + + cwd = os.getcwd() + self.ChangeToDepotWorkingDirectory('cros') + + # Print the commit timestamps for every commit in the revision time + # range. We'll sort them and bisect by that. There is a remote chance that + # 2 (or more) commits will share the exact same timestamp, but it's + # probably safe to ignore that case. + cmd = ['repo', 'forall', '-c', + 'git log --format=%%ct --before=%d --after=%d' % ( + revision_range_end, revision_range_start)] + (output, return_code) = RunProcessAndRetrieveOutput(cmd) + + assert not return_code, 'An error occurred while running'\ + ' "%s"' % ' '.join(cmd) + + os.chdir(cwd) + + revision_work_list = list(set( + [int(o) for o in output.split('\n') if IsStringInt(o)])) + revision_work_list = sorted(revision_work_list, reverse=True) + else: + revision_work_list = self.source_control.GetRevisionList(bad_revision, + good_revision) + + return revision_work_list + + def Get3rdPartyRevisionsFromCurrentRevision(self, depot): + """Parses the DEPS file to determine WebKit/v8/etc... versions. + + Returns: + A dict in the format {depot:revision} if successful, otherwise None. + """ + + cwd = os.getcwd() + self.ChangeToDepotWorkingDirectory(depot) + + results = {} + + if depot == 'chromium': + locals = {'Var': lambda _: locals["vars"][_], + 'From': lambda *args: None} + execfile(bisect_utils.FILE_DEPS_GIT, {}, locals) + + os.chdir(cwd) + + rxp = re.compile(".git@(?P[a-fA-F0-9]+)") + + for d in DEPOT_NAMES: + if DEPOT_DEPS_NAME[d]['recurse'] and\ + DEPOT_DEPS_NAME[d]['from'] == depot: + if locals['deps'].has_key(DEPOT_DEPS_NAME[d]['src']): + re_results = rxp.search(locals['deps'][DEPOT_DEPS_NAME[d]['src']]) + + if re_results: + results[d] = re_results.group('revision') + else: + return None + else: + return None + elif depot == 'cros': + cmd = [CROS_SDK_PATH, '--', 'portageq-%s' % self.opts.cros_board, + 'best_visible', '/build/%s' % self.opts.cros_board, 'ebuild', + CROS_CHROMEOS_PATTERN] + (output, return_code) = RunProcessAndRetrieveOutput(cmd) + + assert not return_code, 'An error occurred while running'\ + ' "%s"' % ' '.join(cmd) + + if len(output) > CROS_CHROMEOS_PATTERN: + output = output[len(CROS_CHROMEOS_PATTERN):] + + if len(output) > 1: + output = output.split('_')[0] + + if len(output) > 3: + contents = output.split('.') + + version = contents[2] + + if contents[3] != '0': + warningText = 'Chrome version: %s.%s but using %s.0 to bisect.' %\ + (version, contents[3], version) + if not warningText in self.warnings: + self.warnings.append(warningText) + + cwd = os.getcwd() + self.ChangeToDepotWorkingDirectory('chromium') + return_code = CheckRunGit(['log', '-1', '--format=%H', + '--author=chrome-release@google.com', '--grep=to %s' % version, + 'origin/master']) + os.chdir(cwd) + + results['chromium'] = output.strip() + + return results + + def BuildCurrentRevision(self, depot): + """Builds chrome and performance_ui_tests on the current revision. + + Returns: + True if the build was successful. + """ + if self.opts.debug_ignore_build: + return True + + cwd = os.getcwd() + os.chdir(self.src_cwd) + + build_success = self.builder.Build(depot, self.opts) + + os.chdir(cwd) + + return build_success + + def RunGClientHooks(self): + """Runs gclient with runhooks command. + + Returns: + True if gclient reports no errors. + """ + + if self.opts.debug_ignore_build: + return True + + return not bisect_utils.RunGClient(['runhooks']) + + def TryParseHistogramValuesFromOutput(self, metric, text): + """Attempts to parse a metric in the format HISTOGRAM . + + Args: + metric: The metric as a list of [, ] strings. + text: The text to parse the metric values from. + + Returns: + A list of floating point numbers found. + """ + metric_formatted = 'HISTOGRAM %s: %s= ' % (metric[0], metric[1]) + + text_lines = text.split('\n') + values_list = [] + + for current_line in text_lines: + if metric_formatted in current_line: + current_line = current_line[len(metric_formatted):] + + try: + histogram_values = eval(current_line) + + for b in histogram_values['buckets']: + average_for_bucket = float(b['high'] + b['low']) * 0.5 + # Extends the list with N-elements with the average for that bucket. + values_list.extend([average_for_bucket] * b['count']) + except: + pass + + return values_list + + def TryParseResultValuesFromOutput(self, metric, text): + """Attempts to parse a metric in the format RESULT . + + Args: + metric: The metric as a list of [, ] strings. + text: The text to parse the metric values from. + + Returns: + A list of floating point numbers found. + """ + # Format is: RESULT : = + metric_formatted = re.escape('RESULT %s: %s=' % (metric[0], metric[1])) + + text_lines = text.split('\n') + values_list = [] + + for current_line in text_lines: + # Parse the output from the performance test for the metric we're + # interested in. + metric_re = metric_formatted +\ + "(\s)*(?P[0-9]+(\.[0-9]*)?)" + metric_re = re.compile(metric_re) + regex_results = metric_re.search(current_line) + + if not regex_results is None: + values_list += [regex_results.group('values')] + else: + metric_re = metric_formatted +\ + "(\s)*\[(\s)*(?P[0-9,.]+)\]" + metric_re = re.compile(metric_re) + regex_results = metric_re.search(current_line) + + if not regex_results is None: + metric_values = regex_results.group('values') + + values_list += metric_values.split(',') + + values_list = [float(v) for v in values_list if IsStringFloat(v)] + + # If the metric is times/t, we need to sum the timings in order to get + # similar regression results as the try-bots. + + if metric == ['times', 't']: + if values_list: + values_list = [reduce(lambda x, y: float(x) + float(y), values_list)] + + return values_list + + def ParseMetricValuesFromOutput(self, metric, text): + """Parses output from performance_ui_tests and retrieves the results for + a given metric. + + Args: + metric: The metric as a list of [, ] strings. + text: The text to parse the metric values from. + + Returns: + A list of floating point numbers found. + """ + metric_values = self.TryParseResultValuesFromOutput(metric, text) + + if not metric_values: + metric_values = self.TryParseHistogramValuesFromOutput(metric, text) + + return metric_values + + def RunPerformanceTestAndParseResults(self, command_to_run, metric): + """Runs a performance test on the current revision by executing the + 'command_to_run' and parses the results. + + Args: + command_to_run: The command to be run to execute the performance test. + metric: The metric to parse out from the results of the performance test. + + Returns: + On success, it will return a tuple of the average value of the metric, + and a success code of 0. + """ + + if self.opts.debug_ignore_perf_test: + return ({'mean': 0.0, 'std_dev': 0.0}, 0) + + if IsWindows(): + command_to_run = command_to_run.replace('/', r'\\') + + args = shlex.split(command_to_run) + + # If running a telemetry test for cros, insert the remote ip, and + # identity parameters. + if self.opts.target_platform == 'cros': + if 'tools/perf/run_' in args[0]: + args.append('--remote=%s' % self.opts.cros_remote_ip) + args.append('--identity=%s' % CROS_TEST_KEY_PATH) + + cwd = os.getcwd() + os.chdir(self.src_cwd) + + start_time = time.time() + + metric_values = [] + for i in xrange(self.opts.repeat_test_count): + # Can ignore the return code since if the tests fail, it won't return 0. + try: + (output, return_code) = RunProcessAndRetrieveOutput(args) + except OSError, e: + if e.errno == errno.ENOENT: + err_text = ("Something went wrong running the performance test. " + "Please review the command line:\n\n") + if 'src/' in ' '.join(args): + err_text += ("Check that you haven't accidentally specified a path " + "with src/ in the command.\n\n") + err_text += ' '.join(args) + err_text += '\n' + + return (err_text, -1) + raise + + if self.opts.output_buildbot_annotations: + print output + + metric_values += self.ParseMetricValuesFromOutput(metric, output) + + elapsed_minutes = (time.time() - start_time) / 60.0 + + if elapsed_minutes >= self.opts.repeat_test_max_time or not metric_values: + break + + os.chdir(cwd) + + # Need to get the average value if there were multiple values. + if metric_values: + truncated_mean = CalculateTruncatedMean(metric_values, + self.opts.truncate_percent) + standard_dev = CalculateStandardDeviation(metric_values) + + values = { + 'mean': truncated_mean, + 'std_dev': standard_dev, + } + + print 'Results of performance test: %12f %12f' % ( + truncated_mean, standard_dev) + print + return (values, 0) + else: + return ('Invalid metric specified, or no values returned from ' + 'performance test.', -1) + + def FindAllRevisionsToSync(self, revision, depot): + """Finds all dependant revisions and depots that need to be synced for a + given revision. This is only useful in the git workflow, as an svn depot + may be split into multiple mirrors. + + ie. skia is broken up into 3 git mirrors over skia/src, skia/gyp, and + skia/include. To sync skia/src properly, one has to find the proper + revisions in skia/gyp and skia/include. + + Args: + revision: The revision to sync to. + depot: The depot in use at the moment (probably skia). + + Returns: + A list of [depot, revision] pairs that need to be synced. + """ + revisions_to_sync = [[depot, revision]] + + is_base = (depot == 'chromium') or (depot == 'cros') + + # Some SVN depots were split into multiple git depots, so we need to + # figure out for each mirror which git revision to grab. There's no + # guarantee that the SVN revision will exist for each of the dependant + # depots, so we have to grep the git logs and grab the next earlier one. + if not is_base and\ + DEPOT_DEPS_NAME[depot]['depends'] and\ + self.source_control.IsGit(): + svn_rev = self.source_control.SVNFindRev(revision) + + for d in DEPOT_DEPS_NAME[depot]['depends']: + self.ChangeToDepotWorkingDirectory(d) + + dependant_rev = self.source_control.ResolveToRevision(svn_rev, d, -1000) + + if dependant_rev: + revisions_to_sync.append([d, dependant_rev]) + + num_resolved = len(revisions_to_sync) + num_needed = len(DEPOT_DEPS_NAME[depot]['depends']) + + self.ChangeToDepotWorkingDirectory(depot) + + if not ((num_resolved - 1) == num_needed): + return None + + return revisions_to_sync + + def PerformPreBuildCleanup(self): + """Performs necessary cleanup between runs.""" + print 'Cleaning up between runs.' + print + + # Having these pyc files around between runs can confuse the + # perf tests and cause them to crash. + for (path, dir, files) in os.walk(self.src_cwd): + for cur_file in files: + if cur_file.endswith('.pyc'): + path_to_file = os.path.join(path, cur_file) + os.remove(path_to_file) + + def PerformWebkitDirectoryCleanup(self, revision): + """If the script is switching between Blink and WebKit during bisect, + its faster to just delete the directory rather than leave it up to git + to sync. + + Returns: + True if successful. + """ + if not self.source_control.CheckoutFileAtRevision( + bisect_utils.FILE_DEPS_GIT, revision): + return False + + cwd = os.getcwd() + os.chdir(self.src_cwd) + + is_blink = bisect_utils.IsDepsFileBlink() + + os.chdir(cwd) + + if not self.source_control.RevertFileToHead( + bisect_utils.FILE_DEPS_GIT): + return False + + if self.was_blink != is_blink: + self.was_blink = is_blink + return bisect_utils.RemoveThirdPartyWebkitDirectory() + return True + + def PerformCrosChrootCleanup(self): + """Deletes the chroot. + + Returns: + True if successful. + """ + cwd = os.getcwd() + self.ChangeToDepotWorkingDirectory('cros') + cmd = [CROS_SDK_PATH, '--delete'] + return_code = RunProcess(cmd) + os.chdir(cwd) + return not return_code + + def CreateCrosChroot(self): + """Creates a new chroot. + + Returns: + True if successful. + """ + cwd = os.getcwd() + self.ChangeToDepotWorkingDirectory('cros') + cmd = [CROS_SDK_PATH, '--create'] + return_code = RunProcess(cmd) + os.chdir(cwd) + return not return_code + + def PerformPreSyncCleanup(self, revision, depot): + """Performs any necessary cleanup before syncing. + + Returns: + True if successful. + """ + if depot == 'chromium': + return self.PerformWebkitDirectoryCleanup(revision) + elif depot == 'cros': + return self.PerformCrosChrootCleanup() + return True + + def RunPostSync(self, depot): + """Performs any work after syncing. + + Returns: + True if successful. + """ + if self.opts.target_platform == 'android': + cwd = os.getcwd() + os.chdir(os.path.join(self.src_cwd, '..')) + if not bisect_utils.SetupAndroidBuildEnvironment(self.opts): + return False + os.chdir(cwd) + + if depot == 'cros': + return self.CreateCrosChroot() + else: + return self.RunGClientHooks() + return True + + def ShouldSkipRevision(self, depot, revision): + """Some commits can be safely skipped (such as a DEPS roll), since the tool + is git based those changes would have no effect. + + Args: + depot: The depot being bisected. + revision: Current revision we're synced to. + + Returns: + True if we should skip building/testing this revision. + """ + if depot == 'chromium': + if self.source_control.IsGit(): + cmd = ['diff-tree', '--no-commit-id', '--name-only', '-r', revision] + output = CheckRunGit(cmd) + + files = output.splitlines() + + if len(files) == 1 and files[0] == 'DEPS': + return True + + return False + + def SyncBuildAndRunRevision(self, revision, depot, command_to_run, metric, + skippable=False): + """Performs a full sync/build/run of the specified revision. + + Args: + revision: The revision to sync to. + depot: The depot that's being used at the moment (src, webkit, etc.) + command_to_run: The command to execute the performance test. + metric: The performance metric being tested. + + Returns: + On success, a tuple containing the results of the performance test. + Otherwise, a tuple with the error message. + """ + sync_client = None + if depot == 'chromium': + sync_client = 'gclient' + elif depot == 'cros': + sync_client = 'repo' + + revisions_to_sync = self.FindAllRevisionsToSync(revision, depot) + + if not revisions_to_sync: + return ('Failed to resolve dependant depots.', BUILD_RESULT_FAIL) + + if not self.PerformPreSyncCleanup(revision, depot): + return ('Failed to perform pre-sync cleanup.', BUILD_RESULT_FAIL) + + success = True + + if not self.opts.debug_ignore_sync: + for r in revisions_to_sync: + self.ChangeToDepotWorkingDirectory(r[0]) + + if sync_client: + self.PerformPreBuildCleanup() + + if not self.source_control.SyncToRevision(r[1], sync_client): + success = False + + break + + if success: + success = self.RunPostSync(depot) + + if success: + if skippable and self.ShouldSkipRevision(depot, revision): + return ('Skipped revision: [%s]' % str(revision), + BUILD_RESULT_SKIPPED) + + if self.BuildCurrentRevision(depot): + results = self.RunPerformanceTestAndParseResults(command_to_run, + metric) + + if results[1] == 0 and sync_client: + external_revisions = self.Get3rdPartyRevisionsFromCurrentRevision( + depot) + + if external_revisions: + return (results[0], results[1], external_revisions) + else: + return ('Failed to parse DEPS file for external revisions.', + BUILD_RESULT_FAIL) + else: + return results + else: + return ('Failed to build revision: [%s]' % (str(revision, )), + BUILD_RESULT_FAIL) + else: + return ('Failed to run [gclient runhooks].', BUILD_RESULT_FAIL) + else: + return ('Failed to sync revision: [%s]' % (str(revision, )), + BUILD_RESULT_FAIL) + + def CheckIfRunPassed(self, current_value, known_good_value, known_bad_value): + """Given known good and bad values, decide if the current_value passed + or failed. + + Args: + current_value: The value of the metric being checked. + known_bad_value: The reference value for a "failed" run. + known_good_value: The reference value for a "passed" run. + + Returns: + True if the current_value is closer to the known_good_value than the + known_bad_value. + """ + dist_to_good_value = abs(current_value['mean'] - known_good_value['mean']) + dist_to_bad_value = abs(current_value['mean'] - known_bad_value['mean']) + + return dist_to_good_value < dist_to_bad_value + + def ChangeToDepotWorkingDirectory(self, depot_name): + """Given a depot, changes to the appropriate working directory. + + Args: + depot_name: The name of the depot (see DEPOT_NAMES). + """ + if depot_name == 'chromium': + os.chdir(self.src_cwd) + elif depot_name == 'cros': + os.chdir(self.cros_cwd) + elif depot_name in DEPOT_NAMES: + os.chdir(self.depot_cwd[depot_name]) + else: + assert False, 'Unknown depot [ %s ] encountered. Possibly a new one'\ + ' was added without proper support?' %\ + (depot_name,) + + def PrepareToBisectOnDepot(self, + current_depot, + end_revision, + start_revision, + previous_depot, + previous_revision): + """Changes to the appropriate directory and gathers a list of revisions + to bisect between |start_revision| and |end_revision|. + + Args: + current_depot: The depot we want to bisect. + end_revision: End of the revision range. + start_revision: Start of the revision range. + previous_depot: The depot we were previously bisecting. + previous_revision: The last revision we synced to on |previous_depot|. + + Returns: + A list containing the revisions between |start_revision| and + |end_revision| inclusive. + """ + # Change into working directory of external library to run + # subsequent commands. + old_cwd = os.getcwd() + os.chdir(self.depot_cwd[current_depot]) + + # V8 (and possibly others) is merged in periodically. Bisecting + # this directory directly won't give much good info. + if DEPOT_DEPS_NAME[current_depot].has_key('build_with'): + if (DEPOT_DEPS_NAME[current_depot].has_key('custom_deps') and + previous_depot == 'chromium'): + config_path = os.path.join(self.src_cwd, '..') + if bisect_utils.RunGClientAndCreateConfig(self.opts, + DEPOT_DEPS_NAME[current_depot]['custom_deps'], cwd=config_path): + return [] + if bisect_utils.RunGClient( + ['sync', '--revision', previous_revision], cwd=self.src_cwd): + return [] + + new_depot = DEPOT_DEPS_NAME[current_depot]['build_with'] + + svn_start_revision = self.source_control.SVNFindRev(start_revision) + svn_end_revision = self.source_control.SVNFindRev(end_revision) + os.chdir(self.depot_cwd[new_depot]) + + start_revision = self.source_control.ResolveToRevision( + svn_start_revision, new_depot, -1000) + end_revision = self.source_control.ResolveToRevision( + svn_end_revision, new_depot, -1000) + + old_name = DEPOT_DEPS_NAME[current_depot]['src'][4:] + new_name = DEPOT_DEPS_NAME[new_depot]['src'][4:] + + os.chdir(self.src_cwd) + + shutil.move(old_name, old_name + '.bak') + shutil.move(new_name, old_name) + os.chdir(self.depot_cwd[current_depot]) + + self.cleanup_commands.append(['mv', old_name, new_name]) + self.cleanup_commands.append(['mv', old_name + '.bak', old_name]) + + os.chdir(self.depot_cwd[current_depot]) + + depot_revision_list = self.GetRevisionList(current_depot, + end_revision, + start_revision) + + os.chdir(old_cwd) + + return depot_revision_list + + def GatherReferenceValues(self, good_rev, bad_rev, cmd, metric, target_depot): + """Gathers reference values by running the performance tests on the + known good and bad revisions. + + Args: + good_rev: The last known good revision where the performance regression + has not occurred yet. + bad_rev: A revision where the performance regression has already occurred. + cmd: The command to execute the performance test. + metric: The metric being tested for regression. + + Returns: + A tuple with the results of building and running each revision. + """ + bad_run_results = self.SyncBuildAndRunRevision(bad_rev, + target_depot, + cmd, + metric) + + good_run_results = None + + if not bad_run_results[1]: + good_run_results = self.SyncBuildAndRunRevision(good_rev, + target_depot, + cmd, + metric) + + return (bad_run_results, good_run_results) + + def AddRevisionsIntoRevisionData(self, revisions, depot, sort, revision_data): + """Adds new revisions to the revision_data dict and initializes them. + + Args: + revisions: List of revisions to add. + depot: Depot that's currently in use (src, webkit, etc...) + sort: Sorting key for displaying revisions. + revision_data: A dict to add the new revisions into. Existing revisions + will have their sort keys offset. + """ + + num_depot_revisions = len(revisions) + + for k, v in revision_data.iteritems(): + if v['sort'] > sort: + v['sort'] += num_depot_revisions + + for i in xrange(num_depot_revisions): + r = revisions[i] + + revision_data[r] = {'revision' : r, + 'depot' : depot, + 'value' : None, + 'passed' : '?', + 'sort' : i + sort + 1} + + def PrintRevisionsToBisectMessage(self, revision_list, depot): + if self.opts.output_buildbot_annotations: + step_name = 'Bisection Range: [%s - %s]' % ( + revision_list[len(revision_list)-1], revision_list[0]) + bisect_utils.OutputAnnotationStepStart(step_name) + + print + print 'Revisions to bisect on [%s]:' % depot + for revision_id in revision_list: + print ' -> %s' % (revision_id, ) + print + + if self.opts.output_buildbot_annotations: + bisect_utils.OutputAnnotationStepClosed() + + def NudgeRevisionsIfDEPSChange(self, bad_revision, good_revision): + """Checks to see if changes to DEPS file occurred, and that the revision + range also includes the change to .DEPS.git. If it doesn't, attempts to + expand the revision range to include it. + + Args: + bad_rev: First known bad revision. + good_revision: Last known good revision. + + Returns: + A tuple with the new bad and good revisions. + """ + if self.source_control.IsGit() and self.opts.target_platform == 'chromium': + changes_to_deps = self.source_control.QueryFileRevisionHistory( + 'DEPS', good_revision, bad_revision) + + if changes_to_deps: + # DEPS file was changed, search from the oldest change to DEPS file to + # bad_revision to see if there are matching .DEPS.git changes. + oldest_deps_change = changes_to_deps[-1] + changes_to_gitdeps = self.source_control.QueryFileRevisionHistory( + bisect_utils.FILE_DEPS_GIT, oldest_deps_change, bad_revision) + + if len(changes_to_deps) != len(changes_to_gitdeps): + # Grab the timestamp of the last DEPS change + cmd = ['log', '--format=%ct', '-1', changes_to_deps[0]] + output = CheckRunGit(cmd) + commit_time = int(output) + + # Try looking for a commit that touches the .DEPS.git file in the + # next 15 minutes after the DEPS file change. + cmd = ['log', '--format=%H', '-1', + '--before=%d' % (commit_time + 900), '--after=%d' % commit_time, + 'origin/master', bisect_utils.FILE_DEPS_GIT] + output = CheckRunGit(cmd) + output = output.strip() + if output: + self.warnings.append('Detected change to DEPS and modified ' + 'revision range to include change to .DEPS.git') + return (output, good_revision) + else: + self.warnings.append('Detected change to DEPS but couldn\'t find ' + 'matching change to .DEPS.git') + return (bad_revision, good_revision) + + def CheckIfRevisionsInProperOrder(self, + target_depot, + good_revision, + bad_revision): + """Checks that |good_revision| is an earlier revision than |bad_revision|. + + Args: + good_revision: Number/tag of the known good revision. + bad_revision: Number/tag of the known bad revision. + + Returns: + True if the revisions are in the proper order (good earlier than bad). + """ + if self.source_control.IsGit() and target_depot != 'cros': + cmd = ['log', '--format=%ct', '-1', good_revision] + output = CheckRunGit(cmd) + good_commit_time = int(output) + + cmd = ['log', '--format=%ct', '-1', bad_revision] + output = CheckRunGit(cmd) + bad_commit_time = int(output) + + return good_commit_time <= bad_commit_time + else: + # Cros/svn use integers + return int(good_revision) <= int(bad_revision) + + def Run(self, command_to_run, bad_revision_in, good_revision_in, metric): + """Given known good and bad revisions, run a binary search on all + intermediate revisions to determine the CL where the performance regression + occurred. + + Args: + command_to_run: Specify the command to execute the performance test. + good_revision: Number/tag of the known good revision. + bad_revision: Number/tag of the known bad revision. + metric: The performance metric to monitor. + + Returns: + A dict with 2 members, 'revision_data' and 'error'. On success, + 'revision_data' will contain a dict mapping revision ids to + data about that revision. Each piece of revision data consists of a + dict with the following keys: + + 'passed': Represents whether the performance test was successful at + that revision. Possible values include: 1 (passed), 0 (failed), + '?' (skipped), 'F' (build failed). + 'depot': The depot that this revision is from (ie. WebKit) + 'external': If the revision is a 'src' revision, 'external' contains + the revisions of each of the external libraries. + 'sort': A sort value for sorting the dict in order of commits. + + For example: + { + 'error':None, + 'revision_data': + { + 'CL #1': + { + 'passed':False, + 'depot':'chromium', + 'external':None, + 'sort':0 + } + } + } + + If an error occurred, the 'error' field will contain the message and + 'revision_data' will be empty. + """ + + results = {'revision_data' : {}, + 'error' : None} + + # Choose depot to bisect first + target_depot = 'chromium' + if self.opts.target_platform == 'cros': + target_depot = 'cros' + + cwd = os.getcwd() + self.ChangeToDepotWorkingDirectory(target_depot) + + # If they passed SVN CL's, etc... we can try match them to git SHA1's. + bad_revision = self.source_control.ResolveToRevision(bad_revision_in, + target_depot, 100) + good_revision = self.source_control.ResolveToRevision(good_revision_in, + target_depot, -100) + + os.chdir(cwd) + + + if bad_revision is None: + results['error'] = 'Could\'t resolve [%s] to SHA1.' % (bad_revision_in,) + return results + + if good_revision is None: + results['error'] = 'Could\'t resolve [%s] to SHA1.' % (good_revision_in,) + return results + + # Check that they didn't accidentally swap good and bad revisions. + if not self.CheckIfRevisionsInProperOrder( + target_depot, good_revision, bad_revision): + results['error'] = 'bad_revision < good_revision, did you swap these '\ + 'by mistake?' + return results + + (bad_revision, good_revision) = self.NudgeRevisionsIfDEPSChange( + bad_revision, good_revision) + + if self.opts.output_buildbot_annotations: + bisect_utils.OutputAnnotationStepStart('Gathering Revisions') + + print 'Gathering revision range for bisection.' + + # Retrieve a list of revisions to do bisection on. + src_revision_list = self.GetRevisionList(target_depot, + bad_revision, + good_revision) + + if self.opts.output_buildbot_annotations: + bisect_utils.OutputAnnotationStepClosed() + + if src_revision_list: + # revision_data will store information about a revision such as the + # depot it came from, the webkit/V8 revision at that time, + # performance timing, build state, etc... + revision_data = results['revision_data'] + + # revision_list is the list we're binary searching through at the moment. + revision_list = [] + + sort_key_ids = 0 + + for current_revision_id in src_revision_list: + sort_key_ids += 1 + + revision_data[current_revision_id] = {'value' : None, + 'passed' : '?', + 'depot' : target_depot, + 'external' : None, + 'sort' : sort_key_ids} + revision_list.append(current_revision_id) + + min_revision = 0 + max_revision = len(revision_list) - 1 + + self.PrintRevisionsToBisectMessage(revision_list, target_depot) + + if self.opts.output_buildbot_annotations: + bisect_utils.OutputAnnotationStepStart('Gathering Reference Values') + + print 'Gathering reference values for bisection.' + + # Perform the performance tests on the good and bad revisions, to get + # reference values. + (bad_results, good_results) = self.GatherReferenceValues(good_revision, + bad_revision, + command_to_run, + metric, + target_depot) + + if self.opts.output_buildbot_annotations: + bisect_utils.OutputAnnotationStepClosed() + + if bad_results[1]: + results['error'] = bad_results[0] + return results + + if good_results[1]: + results['error'] = good_results[0] + return results + + + # We need these reference values to determine if later runs should be + # classified as pass or fail. + known_bad_value = bad_results[0] + known_good_value = good_results[0] + + # Can just mark the good and bad revisions explicitly here since we + # already know the results. + bad_revision_data = revision_data[revision_list[0]] + bad_revision_data['external'] = bad_results[2] + bad_revision_data['passed'] = 0 + bad_revision_data['value'] = known_bad_value + + good_revision_data = revision_data[revision_list[max_revision]] + good_revision_data['external'] = good_results[2] + good_revision_data['passed'] = 1 + good_revision_data['value'] = known_good_value + + next_revision_depot = target_depot + + while True: + if not revision_list: + break + + min_revision_data = revision_data[revision_list[min_revision]] + max_revision_data = revision_data[revision_list[max_revision]] + + if max_revision - min_revision <= 1: + if min_revision_data['passed'] == '?': + next_revision_index = min_revision + elif max_revision_data['passed'] == '?': + next_revision_index = max_revision + elif min_revision_data['depot'] == 'chromium' or\ + min_revision_data['depot'] == 'cros': + # If there were changes to any of the external libraries we track, + # should bisect the changes there as well. + external_depot = None + + for current_depot in DEPOT_NAMES: + if DEPOT_DEPS_NAME[current_depot]["recurse"] and\ + DEPOT_DEPS_NAME[current_depot]['from'] ==\ + min_revision_data['depot']: + if min_revision_data['external'][current_depot] !=\ + max_revision_data['external'][current_depot]: + external_depot = current_depot + break + + # If there was no change in any of the external depots, the search + # is over. + if not external_depot: + break + + previous_revision = revision_list[min_revision] + + earliest_revision = max_revision_data['external'][external_depot] + latest_revision = min_revision_data['external'][external_depot] + + new_revision_list = self.PrepareToBisectOnDepot(external_depot, + latest_revision, + earliest_revision, + next_revision_depot, + previous_revision) + + if not new_revision_list: + results['error'] = 'An error occurred attempting to retrieve'\ + ' revision range: [%s..%s]' %\ + (depot_rev_range[1], depot_rev_range[0]) + return results + + self.AddRevisionsIntoRevisionData(new_revision_list, + external_depot, + min_revision_data['sort'], + revision_data) + + # Reset the bisection and perform it on the newly inserted + # changelists. + revision_list = new_revision_list + min_revision = 0 + max_revision = len(revision_list) - 1 + sort_key_ids += len(revision_list) + + print 'Regression in metric:%s appears to be the result of changes'\ + ' in [%s].' % (metric, external_depot) + + self.PrintRevisionsToBisectMessage(revision_list, external_depot) + + continue + else: + break + else: + next_revision_index = int((max_revision - min_revision) / 2) +\ + min_revision + + next_revision_id = revision_list[next_revision_index] + next_revision_data = revision_data[next_revision_id] + next_revision_depot = next_revision_data['depot'] + + self.ChangeToDepotWorkingDirectory(next_revision_depot) + + if self.opts.output_buildbot_annotations: + step_name = 'Working on [%s]' % next_revision_id + bisect_utils.OutputAnnotationStepStart(step_name) + + print 'Working on revision: [%s]' % next_revision_id + + run_results = self.SyncBuildAndRunRevision(next_revision_id, + next_revision_depot, + command_to_run, + metric, skippable=True) + + # If the build is successful, check whether or not the metric + # had regressed. + if not run_results[1]: + if len(run_results) > 2: + next_revision_data['external'] = run_results[2] + + passed_regression = self.CheckIfRunPassed(run_results[0], + known_good_value, + known_bad_value) + + next_revision_data['passed'] = passed_regression + next_revision_data['value'] = run_results[0] + + if passed_regression: + max_revision = next_revision_index + else: + min_revision = next_revision_index + else: + if run_results[1] == BUILD_RESULT_SKIPPED: + next_revision_data['passed'] = 'Skipped' + elif run_results[1] == BUILD_RESULT_FAIL: + next_revision_data['passed'] = 'Failed' + + print run_results[0] + + # If the build is broken, remove it and redo search. + revision_list.pop(next_revision_index) + + max_revision -= 1 + + if self.opts.output_buildbot_annotations: + bisect_utils.OutputAnnotationStepClosed() + else: + # Weren't able to sync and retrieve the revision range. + results['error'] = 'An error occurred attempting to retrieve revision '\ + 'range: [%s..%s]' % (good_revision, bad_revision) + + return results + + def FormatAndPrintResults(self, bisect_results): + """Prints the results from a bisection run in a readable format. + + Args + bisect_results: The results from a bisection test run. + """ + revision_data = bisect_results['revision_data'] + revision_data_sorted = sorted(revision_data.iteritems(), + key = lambda x: x[1]['sort']) + + if self.opts.output_buildbot_annotations: + bisect_utils.OutputAnnotationStepStart('Results') + + print + print 'Full results of bisection:' + for current_id, current_data in revision_data_sorted: + build_status = current_data['passed'] + + if type(build_status) is bool: + build_status = int(build_status) + + print ' %8s %40s %s' % (current_data['depot'], + current_id, build_status) + print + + print + print 'Tested commits:' + for current_id, current_data in revision_data_sorted: + if current_data['value']: + print ' %8s %40s %12f %12f' % ( + current_data['depot'], current_id, + current_data['value']['mean'], current_data['value']['std_dev']) + print + + # Find range where it possibly broke. + first_working_revision = None + last_broken_revision = None + last_broken_revision_index = -1 + + for i in xrange(len(revision_data_sorted)): + k, v = revision_data_sorted[i] + if v['passed'] == 1: + if not first_working_revision: + first_working_revision = k + + if not v['passed']: + last_broken_revision = k + last_broken_revision_index = i + + if last_broken_revision != None and first_working_revision != None: + print 'Results: Regression may have occurred in range:' + print ' -> First Bad Revision: [%40s] [%s]' %\ + (last_broken_revision, + revision_data[last_broken_revision]['depot']) + print ' -> Last Good Revision: [%40s] [%s]' %\ + (first_working_revision, + revision_data[first_working_revision]['depot']) + + cwd = os.getcwd() + self.ChangeToDepotWorkingDirectory( + revision_data[last_broken_revision]['depot']) + + if revision_data[last_broken_revision]['depot'] == 'cros': + # Want to get a list of all the commits and what depots they belong + # to so that we can grab info about each. + cmd = ['repo', 'forall', '-c', + 'pwd ; git log --pretty=oneline --before=%d --after=%d' % ( + last_broken_revision, first_working_revision + 1)] + (output, return_code) = RunProcessAndRetrieveOutput(cmd) + + changes = [] + + assert not return_code, 'An error occurred while running'\ + ' "%s"' % ' '.join(cmd) + + last_depot = None + cwd = os.getcwd() + for l in output.split('\n'): + if l: + # Output will be in form: + # /path_to_depot + # /path_to_other_depot + # + # /path_again + # + # etc. + if l[0] == '/': + last_depot = l + else: + contents = l.split(' ') + if len(contents) > 1: + changes.append([last_depot, contents[0]]) + + print + for c in changes: + os.chdir(c[0]) + info = self.source_control.QueryRevisionInfo(c[1]) + + print + print 'Commit : %s' % c[1] + print 'Author : %s' % info['author'] + print 'Email : %s' % info['email'] + print 'Date : %s' % info['date'] + print 'Subject : %s' % info['subject'] + print + else: + multiple_commits = 0 + for i in xrange(last_broken_revision_index, len(revision_data_sorted)): + k, v = revision_data_sorted[i] + if k == first_working_revision: + break + + self.ChangeToDepotWorkingDirectory(v['depot']) + + info = self.source_control.QueryRevisionInfo(k) + + print + print 'Commit : %s' % k + print 'Author : %s' % info['author'] + print 'Email : %s' % info['email'] + print 'Date : %s' % info['date'] + print 'Subject : %s' % info['subject'] + + multiple_commits += 1 + if multiple_commits > 1: + self.warnings.append('Due to build errors, regression range could' + ' not be narrowed down to a single commit.') + print + os.chdir(cwd) + + # Give a warning if the values were very close together + good_std_dev = revision_data[first_working_revision]['value']['std_dev'] + good_mean = revision_data[first_working_revision]['value']['mean'] + bad_mean = revision_data[last_broken_revision]['value']['mean'] + + # A standard deviation of 0 could indicate either insufficient runs + # or a test that consistently returns the same value. + if good_std_dev > 0: + deviations = math.fabs(bad_mean - good_mean) / good_std_dev + + if deviations < 1.5: + self.warnings.append('Regression was less than 1.5 standard ' + 'deviations from "good" value. Results may not be accurate.') + elif self.opts.repeat_test_count == 1: + self.warnings.append('Tests were only set to run once. This ' + 'may be insufficient to get meaningful results.') + + # Check for any other possible regression ranges + prev_revision_data = revision_data_sorted[0][1] + prev_revision_id = revision_data_sorted[0][0] + possible_regressions = [] + for current_id, current_data in revision_data_sorted: + if current_data['value']: + prev_mean = prev_revision_data['value']['mean'] + cur_mean = current_data['value']['mean'] + + if good_std_dev: + deviations = math.fabs(prev_mean - cur_mean) / good_std_dev + else: + deviations = None + + if good_mean: + percent_change = (prev_mean - cur_mean) / good_mean + + # If the "good" valuse are supposed to be higher than the "bad" + # values (ie. scores), flip the sign of the percent change so that + # a positive value always represents a regression. + if bad_mean < good_mean: + percent_change *= -1.0 + else: + percent_change = None + + if deviations >= 1.5 or percent_change > 0.01: + if current_id != first_working_revision: + possible_regressions.append( + [current_id, prev_revision_id, percent_change, deviations]) + prev_revision_data = current_data + prev_revision_id = current_id + + if possible_regressions: + print + print 'Other regressions may have occurred:' + print + for p in possible_regressions: + current_id = p[0] + percent_change = p[2] + deviations = p[3] + current_data = revision_data[current_id] + previous_id = p[1] + previous_data = revision_data[previous_id] + + if deviations is None: + deviations = 'N/A' + else: + deviations = '%.2f' % deviations + + if percent_change is None: + percent_change = 0 + + print ' %8s %s [%.2f%%, %s x std.dev]' % ( + previous_data['depot'], previous_id, 100 * percent_change, + deviations) + print ' %8s %s' % ( + current_data['depot'], current_id) + print + + if self.warnings: + print + print 'The following warnings were generated:' + print + for w in self.warnings: + print ' - %s' % w + print + + if self.opts.output_buildbot_annotations: + bisect_utils.OutputAnnotationStepClosed() + + +def DetermineAndCreateSourceControl(opts): + """Attempts to determine the underlying source control workflow and returns + a SourceControl object. + + Returns: + An instance of a SourceControl object, or None if the current workflow + is unsupported. + """ + + (output, return_code) = RunGit(['rev-parse', '--is-inside-work-tree']) + + if output.strip() == 'true': + return GitSourceControl(opts) + + return None + + +def SetNinjaBuildSystemDefault(): + """Makes ninja the default build system to be used by + the bisection script.""" + gyp_var = os.getenv('GYP_GENERATORS') + + if not gyp_var or not 'ninja' in gyp_var: + if gyp_var: + os.environ['GYP_GENERATORS'] = gyp_var + ',ninja' + else: + os.environ['GYP_GENERATORS'] = 'ninja' + + if IsWindows(): + os.environ['GYP_DEFINES'] = 'component=shared_library '\ + 'incremental_chrome_dll=1 disable_nacl=1 fastbuild=1 '\ + 'chromium_win_pch=0' + + +def SetMakeBuildSystemDefault(): + """Makes make the default build system to be used by + the bisection script.""" + os.environ['GYP_GENERATORS'] = 'make' + + +def CheckPlatformSupported(opts): + """Checks that this platform and build system are supported. + + Args: + opts: The options parsed from the command line. + + Returns: + True if the platform and build system are supported. + """ + # Haven't tested the script out on any other platforms yet. + supported = ['posix', 'nt'] + if not os.name in supported: + print "Sorry, this platform isn't supported yet." + print + return False + + if IsWindows(): + if not opts.build_preference: + opts.build_preference = 'msvs' + + if opts.build_preference == 'msvs': + if not os.getenv('VS100COMNTOOLS'): + print 'Error: Path to visual studio could not be determined.' + print + return False + elif opts.build_preference == 'ninja': + SetNinjaBuildSystemDefault() + else: + assert False, 'Error: %s build not supported' % opts.build_preference + else: + if not opts.build_preference: + if 'ninja' in os.getenv('GYP_GENERATORS'): + opts.build_preference = 'ninja' + else: + opts.build_preference = 'make' + + if opts.build_preference == 'ninja': + SetNinjaBuildSystemDefault() + elif opts.build_preference == 'make': + SetMakeBuildSystemDefault() + elif opts.build_preference != 'make': + assert False, 'Error: %s build not supported' % opts.build_preference + + bisect_utils.RunGClient(['runhooks']) + + return True + + +def RmTreeAndMkDir(path_to_dir): + """Removes the directory tree specified, and then creates an empty + directory in the same location. + + Args: + path_to_dir: Path to the directory tree. + + Returns: + True if successful, False if an error occurred. + """ + try: + if os.path.exists(path_to_dir): + shutil.rmtree(path_to_dir) + except OSError, e: + if e.errno != errno.ENOENT: + return False + + try: + os.makedirs(path_to_dir) + except OSError, e: + if e.errno != errno.EEXIST: + return False + + return True + + +def RemoveBuildFiles(): + """Removes build files from previous runs.""" + if RmTreeAndMkDir(os.path.join('out', 'Release')): + if RmTreeAndMkDir(os.path.join('build', 'Release')): + return True + return False + + +def main(): + + usage = ('%prog [options] [-- chromium-options]\n' + 'Perform binary search on revision history to find a minimal ' + 'range of revisions where a peformance metric regressed.\n') + + parser = optparse.OptionParser(usage=usage) + + parser.add_option('-c', '--command', + type='str', + help='A command to execute your performance test at' + + ' each point in the bisection.') + parser.add_option('-b', '--bad_revision', + type='str', + help='A bad revision to start bisection. ' + + 'Must be later than good revision. May be either a git' + + ' or svn revision.') + parser.add_option('-g', '--good_revision', + type='str', + help='A revision to start bisection where performance' + + ' test is known to pass. Must be earlier than the ' + + 'bad revision. May be either a git or svn revision.') + parser.add_option('-m', '--metric', + type='str', + help='The desired metric to bisect on. For example ' + + '"vm_rss_final_b/vm_rss_f_b"') + parser.add_option('-w', '--working_directory', + type='str', + help='Path to the working directory where the script will ' + 'do an initial checkout of the chromium depot. The ' + 'files will be placed in a subdirectory "bisect" under ' + 'working_directory and that will be used to perform the ' + 'bisection. This parameter is optional, if it is not ' + 'supplied, the script will work from the current depot.') + parser.add_option('-r', '--repeat_test_count', + type='int', + default=20, + help='The number of times to repeat the performance test. ' + 'Values will be clamped to range [1, 100]. ' + 'Default value is 20.') + parser.add_option('--repeat_test_max_time', + type='int', + default=20, + help='The maximum time (in minutes) to take running the ' + 'performance tests. The script will run the performance ' + 'tests according to --repeat_test_count, so long as it ' + 'doesn\'t exceed --repeat_test_max_time. Values will be ' + 'clamped to range [1, 60].' + 'Default value is 20.') + parser.add_option('-t', '--truncate_percent', + type='int', + default=25, + help='The highest/lowest % are discarded to form a ' + 'truncated mean. Values will be clamped to range [0, 25]. ' + 'Default value is 25 (highest/lowest 25% will be ' + 'discarded).') + parser.add_option('--build_preference', + type='choice', + choices=['msvs', 'ninja', 'make'], + help='The preferred build system to use. On linux/mac ' + 'the options are make/ninja. On Windows, the options ' + 'are msvs/ninja.') + parser.add_option('--target_platform', + type='choice', + choices=['chromium', 'cros', 'android'], + default='chromium', + help='The target platform. Choices are "chromium" (current ' + 'platform), "cros", or "android". If you specify something ' + 'other than "chromium", you must be properly set up to ' + 'build that platform.') + parser.add_option('--cros_board', + type='str', + help='The cros board type to build.') + parser.add_option('--cros_remote_ip', + type='str', + help='The remote machine to image to.') + parser.add_option('--use_goma', + action="store_true", + help='Add a bunch of extra threads for goma.') + parser.add_option('--output_buildbot_annotations', + action="store_true", + help='Add extra annotation output for buildbot.') + parser.add_option('--debug_ignore_build', + action="store_true", + help='DEBUG: Don\'t perform builds.') + parser.add_option('--debug_ignore_sync', + action="store_true", + help='DEBUG: Don\'t perform syncs.') + parser.add_option('--debug_ignore_perf_test', + action="store_true", + help='DEBUG: Don\'t perform performance tests.') + (opts, args) = parser.parse_args() + + if not opts.command: + print 'Error: missing required parameter: --command' + print + parser.print_help() + return 1 + + if not opts.good_revision: + print 'Error: missing required parameter: --good_revision' + print + parser.print_help() + return 1 + + if not opts.bad_revision: + print 'Error: missing required parameter: --bad_revision' + print + parser.print_help() + return 1 + + if not opts.metric: + print 'Error: missing required parameter: --metric' + print + parser.print_help() + return 1 + + if opts.target_platform == 'cros': + # Run sudo up front to make sure credentials are cached for later. + print 'Sudo is required to build cros:' + print + RunProcess(['sudo', 'true']) + + if not opts.cros_board: + print 'Error: missing required parameter: --cros_board' + print + parser.print_help() + return 1 + + if not opts.cros_remote_ip: + print 'Error: missing required parameter: --cros_remote_ip' + print + parser.print_help() + return 1 + + if not opts.working_directory: + print 'Error: missing required parameter: --working_directory' + print + parser.print_help() + return 1 + + opts.repeat_test_count = min(max(opts.repeat_test_count, 1), 100) + opts.repeat_test_max_time = min(max(opts.repeat_test_max_time, 1), 60) + opts.truncate_percent = min(max(opts.truncate_percent, 0), 25) + opts.truncate_percent = opts.truncate_percent / 100.0 + + metric_values = opts.metric.split('/') + if len(metric_values) != 2: + print "Invalid metric specified: [%s]" % (opts.metric,) + print + return 1 + + if opts.working_directory: + if bisect_utils.CreateBisectDirectoryAndSetupDepot(opts): + return 1 + + if not bisect_utils.SetupPlatformBuildEnvironment(opts): + print 'Error: Failed to set platform environment.' + print + return 1 + + os.chdir(os.path.join(os.getcwd(), 'src')) + + if not RemoveBuildFiles(): + print "Something went wrong removing the build files." + print + return 1 + + if not CheckPlatformSupported(opts): + return 1 + + # Check what source control method they're using. Only support git workflow + # at the moment. + source_control = DetermineAndCreateSourceControl(opts) + + if not source_control: + print "Sorry, only the git workflow is supported at the moment." + print + return 1 + + # gClient sync seems to fail if you're not in master branch. + if not source_control.IsInProperBranch() and not opts.debug_ignore_sync: + print "You must switch to master branch to run bisection." + print + return 1 + + bisect_test = BisectPerformanceMetrics(source_control, opts) + try: + bisect_results = bisect_test.Run(opts.command, + opts.bad_revision, + opts.good_revision, + metric_values) + if not(bisect_results['error']): + bisect_test.FormatAndPrintResults(bisect_results) + finally: + bisect_test.PerformCleanup() + + if not(bisect_results['error']): + return 0 + else: + print 'Error: ' + bisect_results['error'] + print + return 1 + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/bisect_test.py b/tools/bisect_test.py new file mode 100644 index 0000000000..b970f84e36 --- /dev/null +++ b/tools/bisect_test.py @@ -0,0 +1,53 @@ +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import unittest + +bisect_builds = __import__('bisect-builds') + + +class BisectTest(unittest.TestCase): + + patched = [] + max_rev = 10000 + + def monkey_patch(self, obj, name, new): + self.patched.append((obj, name, getattr(obj, name))) + setattr(obj, name, new) + + def clear_patching(self): + for obj, name, old in self.patched: + setattr(obj, name, old) + self.patched = [] + + def setUp(self): + self.monkey_patch(bisect_builds.DownloadJob, 'Start', lambda *args: None) + self.monkey_patch(bisect_builds.DownloadJob, 'Stop', lambda *args: None) + self.monkey_patch(bisect_builds.DownloadJob, 'WaitFor', lambda *args: None) + self.monkey_patch(bisect_builds, 'RunRevision', lambda *args: (0, "", "")) + self.monkey_patch(bisect_builds.PathContext, 'ParseDirectoryIndex', + lambda *args: range(self.max_rev)) + + def tearDown(self): + self.clear_patching() + + def bisect(self, good_rev, bad_rev, evaluate): + return bisect_builds.Bisect(good_rev=good_rev, + bad_rev=bad_rev, + evaluate=evaluate, + num_runs=1, + official_builds=False, + platform='linux', + profile=None, + try_args=()) + + def testBisectConsistentAnswer(self): + self.assertEqual(self.bisect(1000, 100, lambda *args: 'g'), (100, 101)) + self.assertEqual(self.bisect(100, 1000, lambda *args: 'b'), (100, 101)) + self.assertEqual(self.bisect(2000, 200, lambda *args: 'b'), (1999, 2000)) + self.assertEqual(self.bisect(200, 2000, lambda *args: 'g'), (1999, 2000)) + + +if __name__ == '__main__': + unittest.main() diff --git a/tools/bisect_utils.py b/tools/bisect_utils.py new file mode 100644 index 0000000000..37869df791 --- /dev/null +++ b/tools/bisect_utils.py @@ -0,0 +1,402 @@ +# 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. + +"""Set of operations/utilities related to checking out the depot, and +outputting annotations on the buildbot waterfall. These are intended to be +used by the bisection scripts.""" + +import errno +import os +import shutil +import subprocess +import sys + +GCLIENT_SPEC_DATA = [ + { "name" : "src", + "url" : "https://chromium.googlesource.com/chromium/src.git", + "deps_file" : ".DEPS.git", + "managed" : True, + "custom_deps" : { + "src/data/page_cycler": "https://chrome-internal.googlesource.com/" + "chrome/data/page_cycler/.git", + "src/data/dom_perf": "https://chrome-internal.googlesource.com/" + "chrome/data/dom_perf/.git", + "src/data/mach_ports": "https://chrome-internal.googlesource.com/" + "chrome/data/mach_ports/.git", + "src/tools/perf/data": "https://chrome-internal.googlesource.com/" + "chrome/tools/perf/data/.git", + "src/third_party/adobe/flash/binaries/ppapi/linux": + "https://chrome-internal.googlesource.com/" + "chrome/deps/adobe/flash/binaries/ppapi/linux/.git", + "src/third_party/adobe/flash/binaries/ppapi/linux_x64": + "https://chrome-internal.googlesource.com/" + "chrome/deps/adobe/flash/binaries/ppapi/linux_x64/.git", + "src/third_party/adobe/flash/binaries/ppapi/mac": + "https://chrome-internal.googlesource.com/" + "chrome/deps/adobe/flash/binaries/ppapi/mac/.git", + "src/third_party/adobe/flash/binaries/ppapi/mac_64": + "https://chrome-internal.googlesource.com/" + "chrome/deps/adobe/flash/binaries/ppapi/mac_64/.git", + "src/third_party/adobe/flash/binaries/ppapi/win": + "https://chrome-internal.googlesource.com/" + "chrome/deps/adobe/flash/binaries/ppapi/win/.git", + "src/third_party/adobe/flash/binaries/ppapi/win_x64": + "https://chrome-internal.googlesource.com/" + "chrome/deps/adobe/flash/binaries/ppapi/win_x64/.git", + }, + "safesync_url": "", + }, +] +GCLIENT_SPEC_ANDROID = "\ntarget_os = ['android']" +GCLIENT_CUSTOM_DEPS_V8 = {"src/v8_bleeding_edge": "git://github.com/v8/v8.git"} +FILE_DEPS_GIT = '.DEPS.git' + +REPO_PARAMS = [ + 'https://chrome-internal.googlesource.com/chromeos/manifest-internal/', + '--repo-url', + 'https://git.chromium.org/external/repo.git' +] + +REPO_SYNC_COMMAND = 'git checkout -f $(git rev-list --max-count=1 '\ + '--before=%d remotes/m/master)' + +ORIGINAL_ENV = {} + +def OutputAnnotationStepStart(name): + """Outputs appropriate annotation to signal the start of a step to + a trybot. + + Args: + name: The name of the step. + """ + print + print '@@@SEED_STEP %s@@@' % name + print '@@@STEP_CURSOR %s@@@' % name + print '@@@STEP_STARTED@@@' + print + sys.stdout.flush() + + +def OutputAnnotationStepClosed(): + """Outputs appropriate annotation to signal the closing of a step to + a trybot.""" + print + print '@@@STEP_CLOSED@@@' + print + sys.stdout.flush() + + +def CreateAndChangeToSourceDirectory(working_directory): + """Creates a directory 'bisect' as a subdirectory of 'working_directory'. If + the function is successful, the current working directory will change to that + of the new 'bisect' directory. + + Returns: + True if the directory was successfully created (or already existed). + """ + cwd = os.getcwd() + os.chdir(working_directory) + try: + os.mkdir('bisect') + except OSError, e: + if e.errno != errno.EEXIST: + return False + os.chdir('bisect') + return True + + +def SubprocessCall(cmd, cwd=None): + """Runs a subprocess with specified parameters. + + Args: + params: A list of parameters to pass to gclient. + cwd: Working directory to run from. + + Returns: + The return code of the call. + """ + if os.name == 'nt': + # "HOME" isn't normally defined on windows, but is needed + # for git to find the user's .netrc file. + if not os.getenv('HOME'): + os.environ['HOME'] = os.environ['USERPROFILE'] + shell = os.name == 'nt' + return subprocess.call(cmd, shell=shell, cwd=cwd) + + +def RunGClient(params, cwd=None): + """Runs gclient with the specified parameters. + + Args: + params: A list of parameters to pass to gclient. + cwd: Working directory to run from. + + Returns: + The return code of the call. + """ + cmd = ['gclient'] + params + + return SubprocessCall(cmd, cwd=cwd) + + +def RunRepo(params): + """Runs cros repo command with specified parameters. + + Args: + params: A list of parameters to pass to gclient. + + Returns: + The return code of the call. + """ + cmd = ['repo'] + params + + return SubprocessCall(cmd) + + +def RunRepoSyncAtTimestamp(timestamp): + """Syncs all git depots to the timestamp specified using repo forall. + + Args: + params: Unix timestamp to sync to. + + Returns: + The return code of the call. + """ + repo_sync = REPO_SYNC_COMMAND % timestamp + cmd = ['forall', '-c', REPO_SYNC_COMMAND % timestamp] + return RunRepo(cmd) + + +def RunGClientAndCreateConfig(opts, custom_deps=None, cwd=None): + """Runs gclient and creates a config containing both src and src-internal. + + Args: + opts: The options parsed from the command line through parse_args(). + custom_deps: A dictionary of additional dependencies to add to .gclient. + cwd: Working directory to run from. + + Returns: + The return code of the call. + """ + spec = GCLIENT_SPEC_DATA + + if custom_deps: + for k, v in custom_deps.iteritems(): + spec[0]['custom_deps'][k] = v + + # Cannot have newlines in string on windows + spec = 'solutions =' + str(spec) + spec = ''.join([l for l in spec.splitlines()]) + + if opts.target_platform == 'android': + spec += GCLIENT_SPEC_ANDROID + + return_code = RunGClient( + ['config', '--spec=%s' % spec, '--git-deps'], cwd=cwd) + return return_code + + +def IsDepsFileBlink(): + """Reads .DEPS.git and returns whether or not we're using blink. + + Returns: + True if blink, false if webkit. + """ + locals = {'Var': lambda _: locals["vars"][_], + 'From': lambda *args: None} + execfile(FILE_DEPS_GIT, {}, locals) + return 'blink.git' in locals['vars']['webkit_url'] + + +def RemoveThirdPartyWebkitDirectory(): + """Removes third_party/WebKit. + + Returns: + True on success. + """ + try: + path_to_dir = os.path.join(os.getcwd(), 'third_party', 'WebKit') + if os.path.exists(path_to_dir): + shutil.rmtree(path_to_dir) + except OSError, e: + if e.errno != errno.ENOENT: + return False + return True + + +def RunGClientAndSync(cwd=None): + """Runs gclient and does a normal sync. + + Args: + cwd: Working directory to run from. + + Returns: + The return code of the call. + """ + params = ['sync', '--verbose', '--nohooks', '--reset', '--force'] + return RunGClient(params, cwd=cwd) + + +def SetupGitDepot(opts): + """Sets up the depot for the bisection. The depot will be located in a + subdirectory called 'bisect'. + + Args: + opts: The options parsed from the command line through parse_args(). + + Returns: + True if gclient successfully created the config file and did a sync, False + otherwise. + """ + name = 'Setting up Bisection Depot' + + if opts.output_buildbot_annotations: + OutputAnnotationStepStart(name) + + passed = False + + if not RunGClientAndCreateConfig(opts): + passed_deps_check = True + if os.path.isfile(os.path.join('src', FILE_DEPS_GIT)): + cwd = os.getcwd() + os.chdir('src') + if not IsDepsFileBlink(): + passed_deps_check = RemoveThirdPartyWebkitDirectory() + else: + passed_deps_check = True + os.chdir(cwd) + + if passed_deps_check: + RunGClient(['revert']) + if not RunGClientAndSync(): + passed = True + + if opts.output_buildbot_annotations: + print + OutputAnnotationStepClosed() + + return passed + + +def SetupCrosRepo(): + """Sets up cros repo for bisecting chromeos. + + Returns: + Returns 0 on success. + """ + cwd = os.getcwd() + try: + os.mkdir('cros') + except OSError, e: + if e.errno != errno.EEXIST: + return False + os.chdir('cros') + + cmd = ['init', '-u'] + REPO_PARAMS + + passed = False + + if not RunRepo(cmd): + if not RunRepo(['sync']): + passed = True + os.chdir(cwd) + + return passed + + +def CopyAndSaveOriginalEnvironmentVars(): + """Makes a copy of the current environment variables.""" + # TODO: Waiting on crbug.com/255689, will remove this after. + vars_to_remove = [] + for k, v in os.environ.iteritems(): + if 'ANDROID' in k: + vars_to_remove.append(k) + vars_to_remove.append('CHROME_SRC') + vars_to_remove.append('CHROMIUM_GYP_FILE') + vars_to_remove.append('GYP_CROSSCOMPILE') + vars_to_remove.append('GYP_DEFINES') + vars_to_remove.append('GYP_GENERATORS') + vars_to_remove.append('GYP_GENERATOR_FLAGS') + vars_to_remove.append('OBJCOPY') + for k in vars_to_remove: + if os.environ.has_key(k): + del os.environ[k] + + global ORIGINAL_ENV + ORIGINAL_ENV = os.environ.copy() + + +def SetupAndroidBuildEnvironment(opts): + """Sets up the android build environment. + + Args: + opts: The options parsed from the command line through parse_args(). + path_to_file: Path to the bisect script's directory. + + Returns: + True if successful. + """ + + # Revert the environment variables back to default before setting them up + # with envsetup.sh. + env_vars = os.environ.copy() + for k, _ in env_vars.iteritems(): + del os.environ[k] + for k, v in ORIGINAL_ENV.iteritems(): + os.environ[k] = v + + path_to_file = os.path.join('build', 'android', 'envsetup.sh') + proc = subprocess.Popen(['bash', '-c', 'source %s && env' % path_to_file], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd='src') + (out, _) = proc.communicate() + + for line in out.splitlines(): + (k, _, v) = line.partition('=') + os.environ[k] = v + return not proc.returncode + + +def SetupPlatformBuildEnvironment(opts): + """Performs any platform specific setup. + + Args: + opts: The options parsed from the command line through parse_args(). + path_to_file: Path to the bisect script's directory. + + Returns: + True if successful. + """ + if opts.target_platform == 'android': + CopyAndSaveOriginalEnvironmentVars() + return SetupAndroidBuildEnvironment(opts) + elif opts.target_platform == 'cros': + return SetupCrosRepo() + + return True + + +def CreateBisectDirectoryAndSetupDepot(opts): + """Sets up a subdirectory 'bisect' and then retrieves a copy of the depot + there using gclient. + + Args: + opts: The options parsed from the command line through parse_args(). + reset: Whether to reset any changes to the depot. + + Returns: + Returns 0 on success, otherwise 1. + """ + if not CreateAndChangeToSourceDirectory(opts.working_directory): + print 'Error: Could not create bisect directory.' + print + return 1 + + if not SetupGitDepot(opts): + print 'Error: Failed to grab source.' + print + return 1 + + return 0 diff --git a/tools/checkbins/checkbins.py b/tools/checkbins/checkbins.py new file mode 100755 index 0000000000..e166eec551 --- /dev/null +++ b/tools/checkbins/checkbins.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python +# Copyright (c) 2011 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Makes sure that all EXE and DLL files in the provided directory were built +correctly. + +In essense it runs a subset of BinScope tests ensuring that binaries have +/NXCOMPAT, /DYNAMICBASE and /SAFESEH. +""" + +import os +import optparse +import sys + +# Find /third_party/pefile based on current directory and script path. +sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', + 'third_party', 'pefile')) +import pefile + +PE_FILE_EXTENSIONS = ['.exe', '.dll'] +DYNAMICBASE_FLAG = 0x0040 +NXCOMPAT_FLAG = 0x0100 +NO_SEH_FLAG = 0x0400 +MACHINE_TYPE_AMD64 = 0x8664 + +# Please do not add your file here without confirming that it indeed doesn't +# require /NXCOMPAT and /DYNAMICBASE. Contact cpu@chromium.org or your local +# Windows guru for advice. +EXCLUDED_FILES = ['chrome_frame_mini_installer.exe', + 'mini_installer.exe', + 'wow_helper.exe', + 'xinput1_3.dll' # Microsoft DirectX redistributable. + ] + +def IsPEFile(path): + return (os.path.isfile(path) and + os.path.splitext(path)[1].lower() in PE_FILE_EXTENSIONS and + os.path.basename(path) not in EXCLUDED_FILES) + +def main(options, args): + directory = args[0] + pe_total = 0 + pe_passed = 0 + + for file in os.listdir(directory): + path = os.path.abspath(os.path.join(directory, file)) + if not IsPEFile(path): + continue + pe = pefile.PE(path, fast_load=True) + pe.parse_data_directories(directories=[ + pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG']]) + pe_total = pe_total + 1 + success = True + + # Check for /DYNAMICBASE. + if pe.OPTIONAL_HEADER.DllCharacteristics & DYNAMICBASE_FLAG: + if options.verbose: + print "Checking %s for /DYNAMICBASE... PASS" % path + else: + success = False + print "Checking %s for /DYNAMICBASE... FAIL" % path + + # Check for /NXCOMPAT. + if pe.OPTIONAL_HEADER.DllCharacteristics & NXCOMPAT_FLAG: + if options.verbose: + print "Checking %s for /NXCOMPAT... PASS" % path + else: + success = False + print "Checking %s for /NXCOMPAT... FAIL" % path + + # Check for /SAFESEH. Binaries should meet one of the following + # criteria: + # 1) Have no SEH table as indicated by the DLL characteristics + # 2) Have a LOAD_CONFIG section containing a valid SEH table + # 3) Be a 64-bit binary, in which case /SAFESEH isn't required + # + # Refer to the following MSDN article for more information: + # http://msdn.microsoft.com/en-us/library/9a89h429.aspx + if (pe.OPTIONAL_HEADER.DllCharacteristics & NO_SEH_FLAG or + (hasattr(pe, "DIRECTORY_ENTRY_LOAD_CONFIG") and + pe.DIRECTORY_ENTRY_LOAD_CONFIG.struct.SEHandlerCount > 0 and + pe.DIRECTORY_ENTRY_LOAD_CONFIG.struct.SEHandlerTable != 0) or + pe.FILE_HEADER.Machine == MACHINE_TYPE_AMD64): + if options.verbose: + print "Checking %s for /SAFESEH... PASS" % path + else: + success = False + print "Checking %s for /SAFESEH... FAIL" % path + + # Update tally. + if success: + pe_passed = pe_passed + 1 + + print "Result: %d files found, %d files passed" % (pe_total, pe_passed) + if pe_passed != pe_total: + sys.exit(1) + +if __name__ == '__main__': + usage = "Usage: %prog [options] DIRECTORY" + option_parser = optparse.OptionParser(usage=usage) + option_parser.add_option("-v", "--verbose", action="store_true", + default=False, help="Print debug logging") + options, args = option_parser.parse_args() + if not args: + option_parser.print_help() + sys.exit(0) + main(options, args) diff --git a/tools/checkdeps/DEPS b/tools/checkdeps/DEPS new file mode 100644 index 0000000000..7a57b0bcc8 --- /dev/null +++ b/tools/checkdeps/DEPS @@ -0,0 +1,3 @@ +skip_child_includes = [ + "testdata", +] diff --git a/tools/checkdeps/PRESUBMIT.py b/tools/checkdeps/PRESUBMIT.py new file mode 100644 index 0000000000..10ef63212f --- /dev/null +++ b/tools/checkdeps/PRESUBMIT.py @@ -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. + +"""Presubmit script for checkdeps tool. +""" + + +def CheckChange(input_api, output_api): + results = [] + results.extend(input_api.canned_checks.RunUnitTests( + input_api, output_api, + [input_api.os_path.join(input_api.PresubmitLocalPath(), + 'checkdeps_test.py')])) + return results + + +# Mandatory entrypoint. +def CheckChangeOnUpload(input_api, output_api): + return CheckChange(input_api, output_api) + + +# Mandatory entrypoint. +def CheckChangeOnCommit(input_api, output_api): + return CheckChange(input_api, output_api) diff --git a/tools/checkdeps/checkdeps.py b/tools/checkdeps/checkdeps.py new file mode 100755 index 0000000000..5bfde13b4d --- /dev/null +++ b/tools/checkdeps/checkdeps.py @@ -0,0 +1,526 @@ +#!/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. + +"""Makes sure that files include headers from allowed directories. + +Checks DEPS files in the source tree for rules, and applies those rules to +"#include" commands in source files. Any source file including something not +permitted by the DEPS files will fail. + +The format of the deps file: + +First you have the normal module-level deps. These are the ones used by +gclient. An example would be: + + deps = { + "base":"http://foo.bar/trunk/base" + } + +DEPS files not in the top-level of a module won't need this. Then you +have any additional include rules. You can add (using "+") or subtract +(using "-") from the previously specified rules (including +module-level deps). You can also specify a path that is allowed for +now but that we intend to remove, using "!"; this is treated the same +as "+" when check_deps is run by our bots, but a presubmit step will +show a warning if you add a new include of a file that is only allowed +by "!". + +Note that for .java files, there is currently no difference between +"+" and "!", even in the presubmit step. + + include_rules = { + # Code should be able to use base (it's specified in the module-level + # deps above), but nothing in "base/evil" because it's evil. + "-base/evil", + + # But this one subdirectory of evil is OK. + "+base/evil/not", + + # And it can include files from this other directory even though there is + # no deps rule for it. + "+tools/crime_fighter", + + # This dependency is allowed for now but work is ongoing to remove it, + # so you shouldn't add further dependencies on it. + "!base/evil/ok_for_now.h", + } + +If you have certain include rules that should only be applied for some +files within this directory and subdirectories, you can write a +section named specific_include_rules that is a hash map of regular +expressions to the list of rules that should apply to files matching +them. Note that such rules will always be applied before the rules +from 'include_rules' have been applied, but the order in which rules +associated with different regular expressions is applied is arbitrary. + + specific_include_rules = { + ".*_(unit|browser|api)test\.cc": [ + "+libraries/testsupport", + ], + } + +DEPS files may be placed anywhere in the tree. Each one applies to all +subdirectories, where there may be more DEPS files that provide additions or +subtractions for their own sub-trees. + +There is an implicit rule for the current directory (where the DEPS file lives) +and all of its subdirectories. This prevents you from having to explicitly +allow the current directory everywhere. This implicit rule is applied first, +so you can modify or remove it using the normal include rules. + +The rules are processed in order. This means you can explicitly allow a higher +directory and then take away permissions from sub-parts, or the reverse. + +Note that all directory separators must be slashes (Unix-style) and not +backslashes. All directories should be relative to the source root and use +only lowercase. +""" + +import os +import optparse +import re +import subprocess +import sys +import copy + +import cpp_checker +import java_checker +import results +from rules import Rule, Rules + + +# Variable name used in the DEPS file to add or subtract include files from +# the module-level deps. +INCLUDE_RULES_VAR_NAME = 'include_rules' + +# Variable name used in the DEPS file to add or subtract include files +# from module-level deps specific to files whose basename (last +# component of path) matches a given regular expression. +SPECIFIC_INCLUDE_RULES_VAR_NAME = 'specific_include_rules' + +# Optionally present in the DEPS file to list subdirectories which should not +# be checked. This allows us to skip third party code, for example. +SKIP_SUBDIRS_VAR_NAME = 'skip_child_includes' + + +def NormalizePath(path): + """Returns a path normalized to how we write DEPS rules and compare paths. + """ + return path.lower().replace('\\', '/') + + +def _IsTestFile(filename): + """Does a rudimentary check to try to skip test files; this could be + improved but is good enough for now. + """ + return re.match('(test|mock|dummy)_.*|.*_[a-z]*test\.(cc|mm|java)', filename) + + +class DepsChecker(object): + """Parses include_rules from DEPS files and can verify files in the + source tree against them. + """ + + def __init__(self, + base_directory=None, + verbose=False, + being_tested=False, + ignore_temp_rules=False, + skip_tests=False): + """Creates a new DepsChecker. + + Args: + base_directory: OS-compatible path to root of checkout, e.g. C:\chr\src. + verbose: Set to true for debug output. + being_tested: Set to true to ignore the DEPS file at tools/checkdeps/DEPS. + """ + self.base_directory = base_directory + self.verbose = verbose + self._under_test = being_tested + self._ignore_temp_rules = ignore_temp_rules + self._skip_tests = skip_tests + + if not base_directory: + self.base_directory = os.path.abspath( + os.path.join(os.path.abspath(os.path.dirname(__file__)), '..', '..')) + + self.results_formatter = results.NormalResultsFormatter(verbose) + + self.git_source_directories = set() + self._AddGitSourceDirectories() + + # Map of normalized directory paths to rules to use for those + # directories, or None for directories that should be skipped. + self.directory_rules = {} + self._ApplyDirectoryRulesAndSkipSubdirs(Rules(), self.base_directory) + + def Report(self): + """Prints a report of results, and returns an exit code for the process.""" + if self.results_formatter.GetResults(): + self.results_formatter.PrintResults() + return 1 + print '\nSUCCESS\n' + return 0 + + def _ApplyRules(self, existing_rules, includes, specific_includes, cur_dir): + """Applies the given include rules, returning the new rules. + + Args: + existing_rules: A set of existing rules that will be combined. + include: The list of rules from the "include_rules" section of DEPS. + specific_includes: E.g. {'.*_unittest\.cc': ['+foo', '-blat']} rules + from the "specific_include_rules" section of DEPS. + cur_dir: The current directory, normalized path. We will create an + implicit rule that allows inclusion from this directory. + + Returns: A new set of rules combining the existing_rules with the other + arguments. + """ + rules = copy.deepcopy(existing_rules) + + # First apply the implicit "allow" rule for the current directory. + if cur_dir.startswith( + NormalizePath(os.path.normpath(self.base_directory))): + relative_dir = cur_dir[len(self.base_directory) + 1:] + + source = relative_dir + if len(source) == 0: + source = 'top level' # Make the help string a little more meaningful. + rules.AddRule('+' + relative_dir, 'Default rule for ' + source) + else: + raise Exception('Internal error: base directory is not at the beginning' + + ' for\n %s and base dir\n %s' % + (cur_dir, self.base_directory)) + + def ApplyOneRule(rule_str, dependee_regexp=None): + """Deduces a sensible description for the rule being added, and + adds the rule with its description to |rules|. + + If we are ignoring temporary rules, this function does nothing + for rules beginning with the Rule.TEMP_ALLOW character. + """ + if self._ignore_temp_rules and rule_str.startswith(Rule.TEMP_ALLOW): + return + + rule_block_name = 'include_rules' + if dependee_regexp: + rule_block_name = 'specific_include_rules' + if not relative_dir: + rule_description = 'the top level %s' % rule_block_name + else: + rule_description = relative_dir + "'s %s" % rule_block_name + rules.AddRule(rule_str, rule_description, dependee_regexp) + + # Apply the additional explicit rules. + for (_, rule_str) in enumerate(includes): + ApplyOneRule(rule_str) + + # Finally, apply the specific rules. + for regexp, specific_rules in specific_includes.iteritems(): + for rule_str in specific_rules: + ApplyOneRule(rule_str, regexp) + + return rules + + def _ApplyDirectoryRules(self, existing_rules, dir_name): + """Combines rules from the existing rules and the new directory. + + Any directory can contain a DEPS file. Toplevel DEPS files can contain + module dependencies which are used by gclient. We use these, along with + additional include rules and implicit rules for the given directory, to + come up with a combined set of rules to apply for the directory. + + Args: + existing_rules: The rules for the parent directory. We'll add-on to these. + dir_name: The directory name that the deps file may live in (if + it exists). This will also be used to generate the + implicit rules. This is a non-normalized path. + + Returns: A tuple containing: (1) the combined set of rules to apply to the + sub-tree, and (2) a list of all subdirectories that should NOT be + checked, as specified in the DEPS file (if any). + """ + norm_dir_name = NormalizePath(dir_name) + + # Check for a .svn directory in this directory or check this directory is + # contained in git source direcotries. This will tell us if it's a source + # directory and should be checked. + if not (os.path.exists(os.path.join(dir_name, ".svn")) or + (norm_dir_name in self.git_source_directories)): + return (None, []) + + # Check the DEPS file in this directory. + if self.verbose: + print 'Applying rules from', dir_name + def FromImpl(_unused, _unused2): + pass # NOP function so "From" doesn't fail. + + def FileImpl(_unused): + pass # NOP function so "File" doesn't fail. + + class _VarImpl: + def __init__(self, local_scope): + self._local_scope = local_scope + + def Lookup(self, var_name): + """Implements the Var syntax.""" + if var_name in self._local_scope.get('vars', {}): + return self._local_scope['vars'][var_name] + raise Exception('Var is not defined: %s' % var_name) + + local_scope = {} + global_scope = { + 'File': FileImpl, + 'From': FromImpl, + 'Var': _VarImpl(local_scope).Lookup, + } + deps_file = os.path.join(dir_name, 'DEPS') + + # The second conditional here is to disregard the + # tools/checkdeps/DEPS file while running tests. This DEPS file + # has a skip_child_includes for 'testdata' which is necessary for + # running production tests, since there are intentional DEPS + # violations under the testdata directory. On the other hand when + # running tests, we absolutely need to verify the contents of that + # directory to trigger those intended violations and see that they + # are handled correctly. + if os.path.isfile(deps_file) and ( + not self._under_test or not os.path.split(dir_name)[1] == 'checkdeps'): + execfile(deps_file, global_scope, local_scope) + elif self.verbose: + print ' No deps file found in', dir_name + + # Even if a DEPS file does not exist we still invoke ApplyRules + # to apply the implicit "allow" rule for the current directory + include_rules = local_scope.get(INCLUDE_RULES_VAR_NAME, []) + specific_include_rules = local_scope.get(SPECIFIC_INCLUDE_RULES_VAR_NAME, + {}) + skip_subdirs = local_scope.get(SKIP_SUBDIRS_VAR_NAME, []) + + return (self._ApplyRules(existing_rules, include_rules, + specific_include_rules, norm_dir_name), + skip_subdirs) + + def _ApplyDirectoryRulesAndSkipSubdirs(self, parent_rules, dir_path): + """Given |parent_rules| and a subdirectory |dir_path| from the + directory that owns the |parent_rules|, add |dir_path|'s rules to + |self.directory_rules|, and add None entries for any of its + subdirectories that should be skipped. + """ + directory_rules, excluded_subdirs = self._ApplyDirectoryRules(parent_rules, + dir_path) + self.directory_rules[NormalizePath(dir_path)] = directory_rules + for subdir in excluded_subdirs: + self.directory_rules[NormalizePath( + os.path.normpath(os.path.join(dir_path, subdir)))] = None + + def GetDirectoryRules(self, dir_path): + """Returns a Rules object to use for the given directory, or None + if the given directory should be skipped. This takes care of + first building rules for parent directories (up to + self.base_directory) if needed. + + Args: + dir_path: A real (non-normalized) path to the directory you want + rules for. + """ + norm_dir_path = NormalizePath(dir_path) + + if not norm_dir_path.startswith( + NormalizePath(os.path.normpath(self.base_directory))): + dir_path = os.path.join(self.base_directory, dir_path) + norm_dir_path = NormalizePath(dir_path) + + parent_dir = os.path.dirname(dir_path) + parent_rules = None + if not norm_dir_path in self.directory_rules: + parent_rules = self.GetDirectoryRules(parent_dir) + + # We need to check for an entry for our dir_path again, in case we + # are at a path e.g. A/B/C where A/B/DEPS specifies the C + # subdirectory to be skipped; in this case, the invocation to + # GetDirectoryRules(parent_dir) has already filled in an entry for + # A/B/C. + if not norm_dir_path in self.directory_rules: + if not parent_rules: + # If the parent directory should be skipped, then the current + # directory should also be skipped. + self.directory_rules[norm_dir_path] = None + else: + self._ApplyDirectoryRulesAndSkipSubdirs(parent_rules, dir_path) + return self.directory_rules[norm_dir_path] + + def CheckDirectory(self, start_dir): + """Checks all relevant source files in the specified directory and + its subdirectories for compliance with DEPS rules throughout the + tree (starting at |self.base_directory|). |start_dir| must be a + subdirectory of |self.base_directory|. + + On completion, self.results_formatter has the results of + processing, and calling Report() will print a report of results. + """ + java = java_checker.JavaChecker(self.base_directory, self.verbose) + cpp = cpp_checker.CppChecker(self.verbose) + checkers = dict( + (extension, checker) + for checker in [java, cpp] for extension in checker.EXTENSIONS) + self._CheckDirectoryImpl(checkers, start_dir) + + def _CheckDirectoryImpl(self, checkers, dir_name): + rules = self.GetDirectoryRules(dir_name) + if rules == None: + return + + # Collect a list of all files and directories to check. + files_to_check = [] + dirs_to_check = [] + contents = os.listdir(dir_name) + for cur in contents: + full_name = os.path.join(dir_name, cur) + if os.path.isdir(full_name): + dirs_to_check.append(full_name) + elif os.path.splitext(full_name)[1] in checkers: + if not self._skip_tests or not _IsTestFile(cur): + files_to_check.append(full_name) + + # First check all files in this directory. + for cur in files_to_check: + checker = checkers[os.path.splitext(cur)[1]] + file_status = checker.CheckFile(rules, cur) + if file_status.HasViolations(): + self.results_formatter.AddError(file_status) + + # Next recurse into the subdirectories. + for cur in dirs_to_check: + self._CheckDirectoryImpl(checkers, cur) + + def CheckAddedCppIncludes(self, added_includes): + """This is used from PRESUBMIT.py to check new #include statements added in + the change being presubmit checked. + + Args: + added_includes: ((file_path, (include_line, include_line, ...), ...) + + Return: + A list of tuples, (bad_file_path, rule_type, rule_description) + where rule_type is one of Rule.DISALLOW or Rule.TEMP_ALLOW and + rule_description is human-readable. Empty if no problems. + """ + cpp = cpp_checker.CppChecker(self.verbose) + problems = [] + for file_path, include_lines in added_includes: + if not cpp.IsCppFile(file_path): + pass + rules_for_file = self.GetDirectoryRules(os.path.dirname(file_path)) + if rules_for_file: + for line in include_lines: + is_include, violation = cpp.CheckLine( + rules_for_file, line, file_path, True) + if violation: + rule_type = violation.violated_rule.allow + if rule_type != Rule.ALLOW: + violation_text = results.NormalResultsFormatter.FormatViolation( + violation, self.verbose) + problems.append((file_path, rule_type, violation_text)) + return problems + + def _AddGitSourceDirectories(self): + """Adds any directories containing sources managed by git to + self.git_source_directories. + """ + if not os.path.exists(os.path.join(self.base_directory, '.git')): + return + + popen_out = os.popen('cd %s && git ls-files --full-name .' % + subprocess.list2cmdline([self.base_directory])) + for line in popen_out.readlines(): + dir_name = os.path.join(self.base_directory, os.path.dirname(line)) + # Add the directory as well as all the parent directories. Use + # forward slashes and lower case to normalize paths. + while dir_name != self.base_directory: + self.git_source_directories.add(NormalizePath(dir_name)) + dir_name = os.path.dirname(dir_name) + self.git_source_directories.add(NormalizePath(self.base_directory)) + + +def PrintUsage(): + print """Usage: python checkdeps.py [--root ] [tocheck] + + --root ROOT Specifies the repository root. This defaults to "../../.." + relative to the script file. This will be correct given the + normal location of the script in "/tools/checkdeps". + + --(others) There are a few lesser-used options; run with --help to show them. + + tocheck Specifies the directory, relative to root, to check. This defaults + to "." so it checks everything. + +Examples: + python checkdeps.py + python checkdeps.py --root c:\\source chrome""" + + +def main(): + option_parser = optparse.OptionParser() + option_parser.add_option( + '', '--root', + default='', dest='base_directory', + help='Specifies the repository root. This defaults ' + 'to "../../.." relative to the script file, which ' + 'will normally be the repository root.') + option_parser.add_option( + '', '--ignore-temp-rules', + action='store_true', dest='ignore_temp_rules', default=False, + help='Ignore !-prefixed (temporary) rules.') + option_parser.add_option( + '', '--generate-temp-rules', + action='store_true', dest='generate_temp_rules', default=False, + help='Print rules to temporarily allow files that fail ' + 'dependency checking.') + option_parser.add_option( + '', '--count-violations', + action='store_true', dest='count_violations', default=False, + help='Count #includes in violation of intended rules.') + option_parser.add_option( + '', '--skip-tests', + action='store_true', dest='skip_tests', default=False, + help='Skip checking test files (best effort).') + option_parser.add_option( + '-v', '--verbose', + action='store_true', default=False, + help='Print debug logging') + options, args = option_parser.parse_args() + + deps_checker = DepsChecker(options.base_directory, + verbose=options.verbose, + ignore_temp_rules=options.ignore_temp_rules, + skip_tests=options.skip_tests) + + # Figure out which directory we have to check. + start_dir = deps_checker.base_directory + if len(args) == 1: + # Directory specified. Start here. It's supposed to be relative to the + # base directory. + start_dir = os.path.abspath( + os.path.join(deps_checker.base_directory, args[0])) + elif len(args) >= 2 or (options.generate_temp_rules and + options.count_violations): + # More than one argument, or incompatible flags, we don't handle this. + PrintUsage() + return 1 + + print 'Using base directory:', deps_checker.base_directory + print 'Checking:', start_dir + + if options.generate_temp_rules: + deps_checker.results_formatter = results.TemporaryRulesFormatter() + elif options.count_violations: + deps_checker.results_formatter = results.CountViolationsFormatter() + deps_checker.CheckDirectory(start_dir) + return deps_checker.Report() + + +if '__main__' == __name__: + sys.exit(main()) diff --git a/tools/checkdeps/checkdeps_test.py b/tools/checkdeps/checkdeps_test.py new file mode 100755 index 0000000000..e8835a5ca8 --- /dev/null +++ b/tools/checkdeps/checkdeps_test.py @@ -0,0 +1,177 @@ +#!/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 checkdeps. +""" + +import os +import unittest + + +import checkdeps +import results + + +class CheckDepsTest(unittest.TestCase): + + def setUp(self): + self.deps_checker = checkdeps.DepsChecker(being_tested=True) + + def ImplTestRegularCheckDepsRun(self, ignore_temp_rules, skip_tests): + self.deps_checker._ignore_temp_rules = ignore_temp_rules + self.deps_checker._skip_tests = skip_tests + self.deps_checker.CheckDirectory( + os.path.join(self.deps_checker.base_directory, + 'tools/checkdeps/testdata')) + + problems = self.deps_checker.results_formatter.GetResults() + if skip_tests: + self.failUnlessEqual(3, len(problems)) + else: + self.failUnlessEqual(4, len(problems)) + + def VerifySubstringsInProblems(key_path, substrings_in_sequence): + """Finds the problem in |problems| that contains |key_path|, + then verifies that each of |substrings_in_sequence| occurs in + that problem, in the order they appear in + |substrings_in_sequence|. + """ + found = False + key_path = os.path.normpath(key_path) + for problem in problems: + index = problem.find(key_path) + if index != -1: + for substring in substrings_in_sequence: + index = problem.find(substring, index + 1) + self.failUnless(index != -1, '%s in %s' % (substring, problem)) + found = True + break + if not found: + self.fail('Found no problem for file %s' % key_path) + + if ignore_temp_rules: + VerifySubstringsInProblems('testdata/allowed/test.h', + ['-tools/checkdeps/testdata/disallowed', + 'temporarily_allowed.h', + '-third_party/explicitly_disallowed', + 'Because of no rule applying']) + else: + VerifySubstringsInProblems('testdata/allowed/test.h', + ['-tools/checkdeps/testdata/disallowed', + '-third_party/explicitly_disallowed', + 'Because of no rule applying']) + + VerifySubstringsInProblems('testdata/disallowed/test.h', + ['-third_party/explicitly_disallowed', + 'Because of no rule applying', + 'Because of no rule applying']) + VerifySubstringsInProblems('disallowed/allowed/test.h', + ['-third_party/explicitly_disallowed', + 'Because of no rule applying', + 'Because of no rule applying']) + + if not skip_tests: + VerifySubstringsInProblems('allowed/not_a_test.cc', + ['-tools/checkdeps/testdata/disallowed']) + + def testRegularCheckDepsRun(self): + self.ImplTestRegularCheckDepsRun(False, False) + + def testRegularCheckDepsRunIgnoringTempRules(self): + self.ImplTestRegularCheckDepsRun(True, False) + + def testRegularCheckDepsRunSkipTests(self): + self.ImplTestRegularCheckDepsRun(False, True) + + def testRegularCheckDepsRunIgnoringTempRulesSkipTests(self): + self.ImplTestRegularCheckDepsRun(True, True) + + def CountViolations(self, ignore_temp_rules): + self.deps_checker._ignore_temp_rules = ignore_temp_rules + self.deps_checker.results_formatter = results.CountViolationsFormatter() + self.deps_checker.CheckDirectory( + os.path.join(self.deps_checker.base_directory, + 'tools/checkdeps/testdata')) + return self.deps_checker.results_formatter.GetResults() + + def testCountViolations(self): + self.failUnlessEqual('10', self.CountViolations(False)) + + def testCountViolationsIgnoringTempRules(self): + self.failUnlessEqual('11', self.CountViolations(True)) + + def testTempRulesGenerator(self): + self.deps_checker.results_formatter = results.TemporaryRulesFormatter() + self.deps_checker.CheckDirectory( + os.path.join(self.deps_checker.base_directory, + 'tools/checkdeps/testdata/allowed')) + temp_rules = self.deps_checker.results_formatter.GetResults() + expected = [u' "!third_party/explicitly_disallowed/bad.h",', + u' "!third_party/no_rule/bad.h",', + u' "!tools/checkdeps/testdata/disallowed/bad.h",', + u' "!tools/checkdeps/testdata/disallowed/teststuff/bad.h",'] + self.failUnlessEqual(expected, temp_rules) + + def testCheckAddedIncludesAllGood(self): + problems = self.deps_checker.CheckAddedCppIncludes( + [['tools/checkdeps/testdata/allowed/test.cc', + ['#include "tools/checkdeps/testdata/allowed/good.h"', + '#include "tools/checkdeps/testdata/disallowed/allowed/good.h"'] + ]]) + self.failIf(problems) + + def testCheckAddedIncludesManyGarbageLines(self): + garbage_lines = ["My name is Sam%d\n" % num for num in range(50)] + problems = self.deps_checker.CheckAddedCppIncludes( + [['tools/checkdeps/testdata/allowed/test.cc', garbage_lines]]) + self.failIf(problems) + + def testCheckAddedIncludesNoRule(self): + problems = self.deps_checker.CheckAddedCppIncludes( + [['tools/checkdeps/testdata/allowed/test.cc', + ['#include "no_rule_for_this/nogood.h"'] + ]]) + self.failUnless(problems) + + def testCheckAddedIncludesSkippedDirectory(self): + problems = self.deps_checker.CheckAddedCppIncludes( + [['tools/checkdeps/testdata/disallowed/allowed/skipped/test.cc', + ['#include "whatever/whocares.h"'] + ]]) + self.failIf(problems) + + def testCheckAddedIncludesTempAllowed(self): + problems = self.deps_checker.CheckAddedCppIncludes( + [['tools/checkdeps/testdata/allowed/test.cc', + ['#include "tools/checkdeps/testdata/disallowed/temporarily_allowed.h"'] + ]]) + self.failUnless(problems) + + def testCopyIsDeep(self): + # Regression test for a bug where we were making shallow copies of + # Rules objects and therefore all Rules objects shared the same + # dictionary for specific rules. + # + # The first pair should bring in a rule from testdata/allowed/DEPS + # into that global dictionary that allows the + # temp_allowed_for_tests.h file to be included in files ending + # with _unittest.cc, and the second pair should completely fail + # once the bug is fixed, but succeed (with a temporary allowance) + # if the bug is in place. + problems = self.deps_checker.CheckAddedCppIncludes( + [['tools/checkdeps/testdata/allowed/test.cc', + ['#include "tools/checkdeps/testdata/disallowed/temporarily_allowed.h"'] + ], + ['tools/checkdeps/testdata/disallowed/foo_unittest.cc', + ['#include "tools/checkdeps/testdata/bongo/temp_allowed_for_tests.h"'] + ]]) + # With the bug in place, there would be two problems reported, and + # the second would be for foo_unittest.cc. + self.failUnless(len(problems) == 1) + self.failUnless(problems[0][0].endswith('/test.cc')) + + +if __name__ == '__main__': + unittest.main() diff --git a/tools/checkdeps/cpp_checker.py b/tools/checkdeps/cpp_checker.py new file mode 100644 index 0000000000..9bcd14d75c --- /dev/null +++ b/tools/checkdeps/cpp_checker.py @@ -0,0 +1,113 @@ +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Checks C++ and Objective-C files for illegal includes.""" + +import codecs +import os +import re + +import results +from rules import Rule, MessageRule + + +class CppChecker(object): + + EXTENSIONS = [ + '.h', + '.cc', + '.cpp', + '.m', + '.mm', + ] + + # The maximum number of non-include lines we can see before giving up. + _MAX_UNINTERESTING_LINES = 50 + + # The maximum line length, this is to be efficient in the case of very long + # lines (which can't be #includes). + _MAX_LINE_LENGTH = 128 + + # This regular expression will be used to extract filenames from include + # statements. + _EXTRACT_INCLUDE_PATH = re.compile( + '[ \t]*#[ \t]*(?:include|import)[ \t]+"(.*)"') + + def __init__(self, verbose): + self._verbose = verbose + + def CheckLine(self, rules, line, dependee_path, fail_on_temp_allow=False): + """Checks the given line with the given rule set. + + Returns a tuple (is_include, dependency_violation) where + is_include is True only if the line is an #include or #import + statement, and dependency_violation is an instance of + results.DependencyViolation if the line violates a rule, or None + if it does not. + """ + found_item = self._EXTRACT_INCLUDE_PATH.match(line) + if not found_item: + return False, None # Not a match + + include_path = found_item.group(1) + + if '\\' in include_path: + return True, results.DependencyViolation( + include_path, + MessageRule('Include paths may not include backslashes.'), + rules) + + if '/' not in include_path: + # Don't fail when no directory is specified. We may want to be more + # strict about this in the future. + if self._verbose: + print ' WARNING: directory specified with no path: ' + include_path + return True, None + + rule = rules.RuleApplyingTo(include_path, dependee_path) + if (rule.allow == Rule.DISALLOW or + (fail_on_temp_allow and rule.allow == Rule.TEMP_ALLOW)): + return True, results.DependencyViolation(include_path, rule, rules) + return True, None + + def CheckFile(self, rules, filepath): + if self._verbose: + print 'Checking: ' + filepath + + dependee_status = results.DependeeStatus(filepath) + ret_val = '' # We'll collect the error messages in here + last_include = 0 + with codecs.open(filepath, encoding='utf-8') as f: + in_if0 = 0 + for line_num, line in enumerate(f): + if line_num - last_include > self._MAX_UNINTERESTING_LINES: + break + + line = line.strip() + + # Check to see if we're at / inside a #if 0 block + if line.startswith('#if 0'): + in_if0 += 1 + continue + if in_if0 > 0: + if line.startswith('#if'): + in_if0 += 1 + elif line.startswith('#endif'): + in_if0 -= 1 + continue + + is_include, violation = self.CheckLine(rules, line, filepath) + if is_include: + last_include = line_num + if violation: + dependee_status.AddViolation(violation) + + return dependee_status + + @staticmethod + def IsCppFile(file_path): + """Returns True iff the given path ends in one of the extensions + handled by this checker. + """ + return os.path.splitext(file_path)[1] in CppChecker.EXTENSIONS diff --git a/tools/checkdeps/java_checker.py b/tools/checkdeps/java_checker.py new file mode 100644 index 0000000000..670eac2164 --- /dev/null +++ b/tools/checkdeps/java_checker.py @@ -0,0 +1,107 @@ +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Checks Java files for illegal imports.""" + +import codecs +import os +import re + +import results +from rules import Rule + + +class JavaChecker(object): + """Import checker for Java files. + + The CheckFile method uses real filesystem paths, but Java imports work in + terms of package names. To deal with this, we have an extra "prescan" pass + that reads all the .java files and builds a mapping of class name -> filepath. + In CheckFile, we convert each import statement into a real filepath, and check + that against the rules in the DEPS files. + + Note that in Java you can always use classes in the same directory without an + explicit import statement, so these imports can't be blocked with DEPS files. + But that shouldn't be a problem, because same-package imports are pretty much + always correct by definition. (If we find a case where this is *not* correct, + it probably means the package is too big and needs to be split up.) + + Properties: + _classmap: dict of fully-qualified Java class name -> filepath + """ + + EXTENSIONS = ['.java'] + + def __init__(self, base_directory, verbose): + self._base_directory = base_directory + self._verbose = verbose + self._classmap = {} + self._PrescanFiles() + + def _PrescanFiles(self): + for root, dirs, files in os.walk(self._base_directory): + # Skip unwanted subdirectories. TODO(husky): it would be better to do + # this via the skip_child_includes flag in DEPS files. Maybe hoist this + # prescan logic into checkdeps.py itself? + for d in dirs: + # Skip hidden directories. + if d.startswith('.'): + dirs.remove(d) + # Skip the "out" directory, as dealing with generated files is awkward. + # We don't want paths like "out/Release/lib.java" in our DEPS files. + # TODO(husky): We need some way of determining the "real" path to + # a generated file -- i.e., where it would be in source control if + # it weren't generated. + if d == 'out': + dirs.remove(d) + # Skip third-party directories. + if d == 'third_party': + dirs.remove(d) + for f in files: + if f.endswith('.java'): + self._PrescanFile(os.path.join(root, f)) + + def _PrescanFile(self, filepath): + if self._verbose: + print 'Prescanning: ' + filepath + with codecs.open(filepath, encoding='utf-8') as f: + short_class_name, _ = os.path.splitext(os.path.basename(filepath)) + for line in f: + for package in re.findall('^package ([\w\.]+);', line): + full_class_name = package + '.' + short_class_name + if full_class_name in self._classmap: + print 'WARNING: multiple definitions of %s:' % full_class_name + print ' ' + filepath + print ' ' + self._classmap[full_class_name] + print + else: + self._classmap[full_class_name] = filepath + return + print 'WARNING: no package definition found in %s' % filepath + + def CheckFile(self, rules, filepath): + if self._verbose: + print 'Checking: ' + filepath + + dependee_status = results.DependeeStatus(filepath) + with codecs.open(filepath, encoding='utf-8') as f: + for line in f: + for clazz in re.findall('^import\s+(?:static\s+)?([\w\.]+)\s*;', line): + if clazz not in self._classmap: + # Importing a class from outside the Chromium tree. That's fine -- + # it's probably a Java or Android system class. + continue + include_path = os.path.relpath( + self._classmap[clazz], self._base_directory) + # Convert Windows paths to Unix style, as used in DEPS files. + include_path = include_path.replace(os.path.sep, '/') + rule = rules.RuleApplyingTo(include_path, filepath) + if rule.allow == Rule.DISALLOW: + dependee_status.AddViolation( + results.DependencyViolation(include_path, rule, rules)) + if '{' in line: + # This is code, so we're finished reading imports for this file. + break + + return dependee_status diff --git a/tools/checkdeps/results.py b/tools/checkdeps/results.py new file mode 100644 index 0000000000..8f9c189854 --- /dev/null +++ b/tools/checkdeps/results.py @@ -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. + + +"""Results object and results formatters for checkdeps tool.""" + + +class DependencyViolation(object): + """A single dependency violation.""" + + def __init__(self, include_path, violated_rule, rules): + # The include or import path that is in violation of a rule. + self.include_path = include_path + + # The violated rule. + self.violated_rule = violated_rule + + # The set of rules containing self.violated_rule. + self.rules = rules + + +class DependeeStatus(object): + """Results object for a dependee file.""" + + def __init__(self, dependee_path): + # Path of the file whose nonconforming dependencies are listed in + # self.violations. + self.dependee_path = dependee_path + + # List of DependencyViolation objects that apply to the dependee + # file. May be empty. + self.violations = [] + + def AddViolation(self, violation): + """Adds a violation.""" + self.violations.append(violation) + + def HasViolations(self): + """Returns True if this dependee is violating one or more rules.""" + return not not self.violations + + +class ResultsFormatter(object): + """Base class for results formatters.""" + + def AddError(self, dependee_status): + """Add a formatted result to |self.results| for |dependee_status|, + which is guaranteed to return True for + |dependee_status.HasViolations|. + """ + raise NotImplementedError() + + def GetResults(self): + """Returns the results. May be overridden e.g. to process the + results that have been accumulated. + """ + raise NotImplementedError() + + def PrintResults(self): + """Prints the results to stdout.""" + raise NotImplementedError() + + +class NormalResultsFormatter(ResultsFormatter): + """A results formatting object that produces the classical, + detailed, human-readable output of the checkdeps tool. + """ + + def __init__(self, verbose): + self.results = [] + self.verbose = verbose + + def AddError(self, dependee_status): + lines = [] + lines.append('\nERROR in %s' % dependee_status.dependee_path) + for violation in dependee_status.violations: + lines.append(self.FormatViolation(violation, self.verbose)) + self.results.append('\n'.join(lines)) + + @staticmethod + def FormatViolation(violation, verbose=False): + lines = [] + if verbose: + lines.append(' For %s' % violation.rules) + lines.append( + ' Illegal include: "%s"\n Because of %s' % + (violation.include_path, str(violation.violated_rule))) + return '\n'.join(lines) + + def GetResults(self): + return self.results + + def PrintResults(self): + for result in self.results: + print result + if self.results: + print '\nFAILED\n' + + +class TemporaryRulesFormatter(ResultsFormatter): + """A results formatter that produces a single line per nonconforming + include. The combined output is suitable for directly pasting into a + DEPS file as a list of temporary-allow rules. + """ + + def __init__(self): + self.violations = set() + + def AddError(self, dependee_status): + for violation in dependee_status.violations: + self.violations.add(violation.include_path) + + def GetResults(self): + return [' "!%s",' % path for path in sorted(self.violations)] + + def PrintResults(self): + for result in self.GetResults(): + print result + + +class CountViolationsFormatter(ResultsFormatter): + """A results formatter that produces a number, the count of #include + statements that are in violation of the dependency rules. + + Note that you normally want to instantiate DepsChecker with + ignore_temp_rules=True when you use this formatter. + """ + + def __init__(self): + self.count = 0 + + def AddError(self, dependee_status): + self.count += len(dependee_status.violations) + + def GetResults(self): + return '%d' % self.count + + def PrintResults(self): + print self.count diff --git a/tools/checkdeps/rules.py b/tools/checkdeps/rules.py new file mode 100644 index 0000000000..09d718c723 --- /dev/null +++ b/tools/checkdeps/rules.py @@ -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. + +"""Base classes to represent dependency rules, used by checkdeps.py""" + + +import os +import re + + +class Rule(object): + """Specifies a single rule for an include, which can be one of + ALLOW, DISALLOW and TEMP_ALLOW. + """ + + # These are the prefixes used to indicate each type of rule. These + # are also used as values for self.allow to indicate which type of + # rule this is. + ALLOW = '+' + DISALLOW = '-' + TEMP_ALLOW = '!' + + def __init__(self, allow, directory, source): + self.allow = allow + self._dir = directory + self._source = source + + def __str__(self): + return '"%s%s" from %s.' % (self.allow, self._dir, self._source) + + def ParentOrMatch(self, other): + """Returns true if the input string is an exact match or is a parent + of the current rule. For example, the input "foo" would match "foo/bar".""" + return self._dir == other or self._dir.startswith(other + '/') + + def ChildOrMatch(self, other): + """Returns true if the input string would be covered by this rule. For + example, the input "foo/bar" would match the rule "foo".""" + return self._dir == other or other.startswith(self._dir + '/') + + +class MessageRule(Rule): + """A rule that has a simple message as the reason for failing, + unrelated to directory or source. + """ + + def __init__(self, reason): + super(MessageRule, self).__init__(Rule.DISALLOW, '', '') + self._reason = reason + + def __str__(self): + return self._reason + + +def ParseRuleString(rule_string, source): + """Returns a tuple of a character indicating what type of rule this + is, and a string holding the path the rule applies to. + """ + if not rule_string: + raise Exception('The rule string "%s" is empty\nin %s' % + (rule_string, source)) + + if not rule_string[0] in [Rule.ALLOW, Rule.DISALLOW, Rule.TEMP_ALLOW]: + raise Exception( + 'The rule string "%s" does not begin with a "+", "-" or "!".' % + rule_string) + + return (rule_string[0], rule_string[1:]) + + +class Rules(object): + """Sets of rules for files in a directory. + + By default, rules are added to the set of rules applicable to all + dependee files in the directory. Rules may also be added that apply + only to dependee files whose filename (last component of their path) + matches a given regular expression; hence there is one additional + set of rules per unique regular expression. + """ + + def __init__(self): + """Initializes the current rules with an empty rule list for all + files. + """ + # We keep the general rules out of the specific rules dictionary, + # as we need to always process them last. + self._general_rules = [] + + # Keys are regular expression strings, values are arrays of rules + # that apply to dependee files whose basename matches the regular + # expression. These are applied before the general rules, but + # their internal order is arbitrary. + self._specific_rules = {} + + def __str__(self): + result = ['Rules = {\n (apply to all files): [\n%s\n ],' % '\n'.join( + ' %s' % x for x in self._general_rules)] + for regexp, rules in self._specific_rules.iteritems(): + result.append(' (limited to files matching %s): [\n%s\n ]' % ( + regexp, '\n'.join(' %s' % x for x in rules))) + result.append(' }') + return '\n'.join(result) + + def AddRule(self, rule_string, source, dependee_regexp=None): + """Adds a rule for the given rule string. + + Args: + rule_string: The include_rule string read from the DEPS file to apply. + source: A string representing the location of that string (filename, etc.) + so that we can give meaningful errors. + dependee_regexp: The rule will only be applied to dependee files + whose filename (last component of their path) + matches the expression. None to match all + dependee files. + """ + (rule_type, rule_dir) = ParseRuleString(rule_string, source) + + if not dependee_regexp: + rules_to_update = self._general_rules + else: + if dependee_regexp in self._specific_rules: + rules_to_update = self._specific_rules[dependee_regexp] + else: + rules_to_update = [] + + # Remove any existing rules or sub-rules that apply. For example, if we're + # passed "foo", we should remove "foo", "foo/bar", but not "foobar". + rules_to_update = [x for x in rules_to_update + if not x.ParentOrMatch(rule_dir)] + rules_to_update.insert(0, Rule(rule_type, rule_dir, source)) + + if not dependee_regexp: + self._general_rules = rules_to_update + else: + self._specific_rules[dependee_regexp] = rules_to_update + + def RuleApplyingTo(self, include_path, dependee_path): + """Returns the rule that applies to |include_path| for a dependee + file located at |dependee_path|. + """ + dependee_filename = os.path.basename(dependee_path) + for regexp, specific_rules in self._specific_rules.iteritems(): + if re.match(regexp, dependee_filename): + for rule in specific_rules: + if rule.ChildOrMatch(include_path): + return rule + for rule in self._general_rules: + if rule.ChildOrMatch(include_path): + return rule + return MessageRule('no rule applying.') diff --git a/tools/checkdeps/testdata/DEPS b/tools/checkdeps/testdata/DEPS new file mode 100644 index 0000000000..f0657f5bc3 --- /dev/null +++ b/tools/checkdeps/testdata/DEPS @@ -0,0 +1,8 @@ +include_rules = [ + "-tools/checkdeps/testdata/disallowed", + "+tools/checkdeps/testdata/allowed", + "-third_party/explicitly_disallowed", +] +skip_child_includes = [ + "checkdeps_test", +] diff --git a/tools/checkdeps/testdata/allowed/DEPS b/tools/checkdeps/testdata/allowed/DEPS new file mode 100644 index 0000000000..8fb09053c1 --- /dev/null +++ b/tools/checkdeps/testdata/allowed/DEPS @@ -0,0 +1,12 @@ +include_rules = [ + "+tools/checkdeps/testdata/disallowed/allowed", + "!tools/checkdeps/testdata/disallowed/temporarily_allowed.h", + "+third_party/allowed_may_use", +] + +specific_include_rules = { + ".*_unittest\.cc": [ + "+tools/checkdeps/testdata/disallowed/teststuff", + "!tools/checkdeps/testdata/bongo/temp_allowed_for_tests.h", + ] +} diff --git a/tools/checkdeps/testdata/allowed/foo_unittest.cc b/tools/checkdeps/testdata/allowed/foo_unittest.cc new file mode 100644 index 0000000000..754f295eb5 --- /dev/null +++ b/tools/checkdeps/testdata/allowed/foo_unittest.cc @@ -0,0 +1,5 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "tools/checkdeps/testdata/disallowed/teststuff/good.h" diff --git a/tools/checkdeps/testdata/allowed/not_a_test.cc b/tools/checkdeps/testdata/allowed/not_a_test.cc new file mode 100644 index 0000000000..7b2a105d53 --- /dev/null +++ b/tools/checkdeps/testdata/allowed/not_a_test.cc @@ -0,0 +1,5 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "tools/checkdeps/testdata/disallowed/teststuff/bad.h" diff --git a/tools/checkdeps/testdata/allowed/test.h b/tools/checkdeps/testdata/allowed/test.h new file mode 100644 index 0000000000..ea33090050 --- /dev/null +++ b/tools/checkdeps/testdata/allowed/test.h @@ -0,0 +1,11 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "tools/checkdeps/testdata/allowed/good.h" +#include "tools/checkdeps/testdata/disallowed/bad.h" +#include "tools/checkdeps/testdata/disallowed/allowed/good.h" +#include "tools/checkdeps/testdata/disallowed/temporarily_allowed.h" +#include "third_party/explicitly_disallowed/bad.h" +#include "third_party/allowed_may_use/good.h" +#include "third_party/no_rule/bad.h" diff --git a/tools/checkdeps/testdata/checkdeps_test/DEPS b/tools/checkdeps/testdata/checkdeps_test/DEPS new file mode 100644 index 0000000000..91a9b990c3 --- /dev/null +++ b/tools/checkdeps/testdata/checkdeps_test/DEPS @@ -0,0 +1,5 @@ +include_rules = [ + "-disallowed", + "+allowed", + "-third_party/explicitly_disallowed", +] diff --git a/tools/checkdeps/testdata/checkdeps_test/allowed/DEPS b/tools/checkdeps/testdata/checkdeps_test/allowed/DEPS new file mode 100644 index 0000000000..14aa4d4516 --- /dev/null +++ b/tools/checkdeps/testdata/checkdeps_test/allowed/DEPS @@ -0,0 +1,11 @@ +include_rules = [ + "+disallowed/allowed", + "!disallowed/temporarily_allowed.h", + "+third_party/allowed_may_use", +] + +specific_include_rules = { + ".*_unittest\.cc": [ + "+disallowed/teststuff", + ] +} diff --git a/tools/checkdeps/testdata/checkdeps_test/allowed/foo_unittest.cc b/tools/checkdeps/testdata/checkdeps_test/allowed/foo_unittest.cc new file mode 100644 index 0000000000..1a507eca4e --- /dev/null +++ b/tools/checkdeps/testdata/checkdeps_test/allowed/foo_unittest.cc @@ -0,0 +1,5 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "disallowed/teststuff/good.h" diff --git a/tools/checkdeps/testdata/checkdeps_test/allowed/not_a_test.cc b/tools/checkdeps/testdata/checkdeps_test/allowed/not_a_test.cc new file mode 100644 index 0000000000..4278d64763 --- /dev/null +++ b/tools/checkdeps/testdata/checkdeps_test/allowed/not_a_test.cc @@ -0,0 +1,5 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "disallowed/teststuff/bad.h" diff --git a/tools/checkdeps/testdata/checkdeps_test/allowed/test.h b/tools/checkdeps/testdata/checkdeps_test/allowed/test.h new file mode 100644 index 0000000000..2dbd7a38b4 --- /dev/null +++ b/tools/checkdeps/testdata/checkdeps_test/allowed/test.h @@ -0,0 +1,11 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "allowed/good.h" +#include "disallowed/bad.h" +#include "disallowed/allowed/good.h" +#include "disallowed/temporarily_allowed.h" +#include "third_party/explicitly_disallowed/bad.h" +#include "third_party/allowed_may_use/good.h" +#include "third_party/no_rule/bad.h" diff --git a/tools/checkdeps/testdata/checkdeps_test/disallowed/allowed/DEPS b/tools/checkdeps/testdata/checkdeps_test/disallowed/allowed/DEPS new file mode 100644 index 0000000000..2be72b8018 --- /dev/null +++ b/tools/checkdeps/testdata/checkdeps_test/disallowed/allowed/DEPS @@ -0,0 +1,3 @@ +skip_child_includes = [ + "skipped", +] diff --git a/tools/checkdeps/testdata/checkdeps_test/disallowed/allowed/skipped/test.h b/tools/checkdeps/testdata/checkdeps_test/disallowed/allowed/skipped/test.h new file mode 100644 index 0000000000..80105968e2 --- /dev/null +++ b/tools/checkdeps/testdata/checkdeps_test/disallowed/allowed/skipped/test.h @@ -0,0 +1,5 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "whatever/whocares/ok.h" diff --git a/tools/checkdeps/testdata/checkdeps_test/disallowed/allowed/test.h b/tools/checkdeps/testdata/checkdeps_test/disallowed/allowed/test.h new file mode 100644 index 0000000000..aa5013d25d --- /dev/null +++ b/tools/checkdeps/testdata/checkdeps_test/disallowed/allowed/test.h @@ -0,0 +1,11 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "allowed/good.h" +// Always allowed to include self and parents. +#include "disallowed/good.h" +#include "disallowed/allowed/good.h" +#include "third_party/explicitly_disallowed/bad.h" +#include "third_party/allowed_may_use/bad.h" +#include "third_party/no_rule/bad.h" diff --git a/tools/checkdeps/testdata/checkdeps_test/disallowed/test.h b/tools/checkdeps/testdata/checkdeps_test/disallowed/test.h new file mode 100644 index 0000000000..5520a68c8f --- /dev/null +++ b/tools/checkdeps/testdata/checkdeps_test/disallowed/test.h @@ -0,0 +1,12 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "allowed/good.h" +// Always allowed to include self. +#include "disallowed/good.h" +#include "disallowed/allowed/good.h" +#include "third_party/explicitly_disallowed/bad.h" +// Only allowed for code under allowed/. +#include "third_party/allowed_may_use/bad.h" +#include "third_party/no_rule/bad.h" diff --git a/tools/checkdeps/testdata/disallowed/allowed/DEPS b/tools/checkdeps/testdata/disallowed/allowed/DEPS new file mode 100644 index 0000000000..2be72b8018 --- /dev/null +++ b/tools/checkdeps/testdata/disallowed/allowed/DEPS @@ -0,0 +1,3 @@ +skip_child_includes = [ + "skipped", +] diff --git a/tools/checkdeps/testdata/disallowed/allowed/skipped/test.h b/tools/checkdeps/testdata/disallowed/allowed/skipped/test.h new file mode 100644 index 0000000000..80105968e2 --- /dev/null +++ b/tools/checkdeps/testdata/disallowed/allowed/skipped/test.h @@ -0,0 +1,5 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "whatever/whocares/ok.h" diff --git a/tools/checkdeps/testdata/disallowed/allowed/test.h b/tools/checkdeps/testdata/disallowed/allowed/test.h new file mode 100644 index 0000000000..f7dc822c4b --- /dev/null +++ b/tools/checkdeps/testdata/disallowed/allowed/test.h @@ -0,0 +1,11 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "tools/checkdeps/testdata/allowed/good.h" +// Always allowed to include self and parents. +#include "tools/checkdeps/testdata/disallowed/good.h" +#include "tools/checkdeps/testdata/disallowed/allowed/good.h" +#include "third_party/explicitly_disallowed/bad.h" +#include "third_party/allowed_may_use/bad.h" +#include "third_party/no_rule/bad.h" diff --git a/tools/checkdeps/testdata/disallowed/foo_unittest.cc b/tools/checkdeps/testdata/disallowed/foo_unittest.cc new file mode 100644 index 0000000000..144c05eb36 --- /dev/null +++ b/tools/checkdeps/testdata/disallowed/foo_unittest.cc @@ -0,0 +1,10 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Not allowed for code under disallowed/ but temporarily allowed +// specifically for test code under allowed/. This regression tests a +// bug where we were taking shallow copies of rules when generating +// rules for subdirectories, so all rule objects were getting the same +// dictionary for specific rules. +#include "tools/checkdeps/testdata/disallowed/temp_allowed_for_tests.h" diff --git a/tools/checkdeps/testdata/disallowed/test.h b/tools/checkdeps/testdata/disallowed/test.h new file mode 100644 index 0000000000..d0128e3a96 --- /dev/null +++ b/tools/checkdeps/testdata/disallowed/test.h @@ -0,0 +1,12 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "tools/checkdeps/testdata/allowed/good.h" +// Always allowed to include self. +#include "tools/checkdeps/testdata/disallowed/good.h" +#include "tools/checkdeps/testdata/disallowed/allowed/good.h" +#include "third_party/explicitly_disallowed/bad.h" +// Only allowed for code under allowed/. +#include "third_party/allowed_may_use/bad.h" +#include "third_party/no_rule/bad.h" diff --git a/tools/checklicenses/OWNERS b/tools/checklicenses/OWNERS new file mode 100644 index 0000000000..2abfa66e8a --- /dev/null +++ b/tools/checklicenses/OWNERS @@ -0,0 +1,3 @@ +set noparent +phajdan.jr@chromium.org +thestig@chromium.org diff --git a/tools/checklicenses/checklicenses.py b/tools/checklicenses/checklicenses.py new file mode 100755 index 0000000000..25dedc2974 --- /dev/null +++ b/tools/checklicenses/checklicenses.py @@ -0,0 +1,548 @@ +#!/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. + +"""Makes sure that all files contain proper licensing information.""" + + +import optparse +import os.path +import subprocess +import sys + + +def PrintUsage(): + print """Usage: python checklicenses.py [--root ] [tocheck] + --root Specifies the repository root. This defaults to "../.." relative + to the script file. This will be correct given the normal location + of the script in "/tools/checklicenses". + + --ignore-suppressions Ignores path-specific license whitelist. Useful when + trying to remove a suppression/whitelist entry. + + tocheck Specifies the directory, relative to root, to check. This defaults + to "." so it checks everything. + +Examples: + python checklicenses.py + python checklicenses.py --root ~/chromium/src third_party""" + + +WHITELISTED_LICENSES = [ + 'Apache (v2.0)', + 'Apache (v2.0) BSD (2 clause)', + 'Apache (v2.0) GPL (v2)', + 'Apple MIT', # https://fedoraproject.org/wiki/Licensing/Apple_MIT_License + 'APSL (v2)', + 'APSL (v2) BSD (4 clause)', + 'BSD', + 'BSD (2 clause)', + 'BSD (2 clause) ISC', + 'BSD (2 clause) MIT/X11 (BSD like)', + 'BSD (3 clause)', + 'BSD (3 clause) GPL (v2)', + 'BSD (3 clause) ISC', + 'BSD (3 clause) LGPL (v2 or later)', + 'BSD (3 clause) LGPL (v2.1 or later)', + 'BSD (3 clause) MIT/X11 (BSD like)', + 'BSD (4 clause)', + 'BSD-like', + + # TODO(phajdan.jr): Make licensecheck not print BSD-like twice. + 'BSD-like MIT/X11 (BSD like)', + + 'BSL (v1.0)', + 'GPL (v2) LGPL (v2.1 or later)', + 'GPL (v2 or later) with Bison parser exception', + 'GPL (v2 or later) with libtool exception', + 'GPL (v3 or later) with Bison parser exception', + 'GPL with Bison parser exception', + 'ISC', + 'LGPL (unversioned/unknown version)', + 'LGPL (v2)', + 'LGPL (v2 or later)', + 'LGPL (v2.1)', + 'LGPL (v2.1 or later)', + 'LGPL (v3 or later)', + 'MIT/X11 (BSD like)', + 'MPL (v1.0) LGPL (v2 or later)', + 'MPL (v1.1)', + 'MPL (v1.1) BSD (3 clause) GPL (v2) LGPL (v2.1 or later)', + 'MPL (v1.1) BSD (3 clause) LGPL (v2.1 or later)', + 'MPL (v1.1) BSD-like', + 'MPL (v1.1) BSD-like GPL (unversioned/unknown version)', + 'MPL (v1.1) BSD-like GPL (v2) LGPL (v2.1 or later)', + 'MPL (v1.1) GPL (v2)', + 'MPL (v1.1) GPL (v2) LGPL (v2 or later)', + 'MPL (v1.1) GPL (v2) LGPL (v2.1 or later)', + 'MPL (v1.1) GPL (unversioned/unknown version)', + 'MPL (v1.1) LGPL (v2 or later)', + 'MPL (v1.1) LGPL (v2.1 or later)', + 'MPL (v2.0)', + 'Ms-PL', + 'Public domain', + 'Public domain BSD', + 'Public domain BSD (3 clause)', + 'Public domain BSD-like', + 'Public domain LGPL (v2.1 or later)', + 'libpng', + 'zlib/libpng', + 'SGI Free Software License B', + 'University of Illinois/NCSA Open Source License (BSD like)', +] + + +PATH_SPECIFIC_WHITELISTED_LICENSES = { + 'base/hash.cc': [ # http://crbug.com/98100 + 'UNKNOWN', + ], + 'base/third_party/icu': [ # http://crbug.com/98087 + 'UNKNOWN', + ], + + # http://code.google.com/p/google-breakpad/issues/detail?id=450 + 'breakpad/src': [ + 'UNKNOWN', + ], + + 'chrome/common/extensions/docs/examples': [ # http://crbug.com/98092 + 'UNKNOWN', + ], + 'chrome/test/data/gpu/vt': [ + 'UNKNOWN', + ], + 'chrome/test/data/layout_tests/LayoutTests': [ + 'UNKNOWN', + ], + 'courgette/third_party/bsdiff_create.cc': [ # http://crbug.com/98095 + 'UNKNOWN', + ], + 'data/mozilla_js_tests': [ + 'UNKNOWN', + ], + 'data/page_cycler': [ + 'UNKNOWN', + 'GPL (v2 or later)', + ], + 'data/tab_switching': [ + 'UNKNOWN', + ], + 'native_client': [ # http://crbug.com/98099 + 'UNKNOWN', + ], + 'native_client/toolchain': [ + 'BSD GPL (v2 or later)', + 'BSD (2 clause) GPL (v2 or later)', + 'BSD (3 clause) GPL (v2 or later)', + 'BSL (v1.0) GPL', + 'BSL (v1.0) GPL (v3.1)', + 'GPL', + 'GPL (unversioned/unknown version)', + 'GPL (v2)', + 'GPL (v2 or later)', + 'GPL (v3.1)', + 'GPL (v3 or later)', + ], + 'net/tools/spdyshark': [ + 'GPL (v2 or later)', + 'UNKNOWN', + ], + 'third_party/WebKit': [ + 'UNKNOWN', + ], + 'third_party/WebKit/Websites/webkit.org/blog/wp-content/plugins/' + 'akismet/akismet.php': [ + 'GPL (v2 or later)' + ], + 'third_party/WebKit/Source/JavaScriptCore/tests/mozilla': [ + 'GPL', + 'GPL (v2 or later)', + 'GPL (unversioned/unknown version)', + ], + 'third_party/active_doc': [ # http://crbug.com/98113 + 'UNKNOWN', + ], + + # http://code.google.com/p/angleproject/issues/detail?id=217 + 'third_party/angle': [ + 'UNKNOWN', + ], + + 'third_party/bsdiff/mbsdiff.cc': [ + 'UNKNOWN', + ], + 'third_party/bzip2': [ + 'UNKNOWN', + ], + + # http://crbug.com/222828 + # http://bugs.python.org/issue17514 + 'third_party/chromite/third_party/argparse.py': [ + 'UNKNOWN', + ], + + # Not used. http://crbug.com/156020 + # Using third_party/cros_dbus_cplusplus/cros_dbus_cplusplus.gyp instead. + 'third_party/cros_dbus_cplusplus/source/autogen.sh': [ + 'UNKNOWN', + ], + # Included in the source tree but not built. http://crbug.com/156020 + 'third_party/cros_dbus_cplusplus/source/examples': [ + 'UNKNOWN', + ], + 'third_party/devscripts': [ + 'GPL (v2 or later)', + ], + 'third_party/expat/files/lib': [ # http://crbug.com/98121 + 'UNKNOWN', + ], + 'third_party/ffmpeg': [ + 'GPL', + 'GPL (v2)', + 'GPL (v2 or later)', + 'UNKNOWN', # http://crbug.com/98123 + ], + 'third_party/findbugs/doc': [ # http://crbug.com/157206 + 'UNKNOWN', + ], + 'third_party/freetype2': [ # http://crbug.com/177319 + 'UNKNOWN', + ], + 'third_party/gles2_book': [ # http://crbug.com/98130 + 'UNKNOWN', + ], + 'third_party/gles2_conform/GTF_ES': [ # http://crbug.com/98131 + 'UNKNOWN', + ], + 'third_party/harfbuzz': [ # http://crbug.com/98133 + 'UNKNOWN', + ], + 'third_party/hunspell': [ # http://crbug.com/98134 + 'UNKNOWN', + ], + 'third_party/hyphen/hyphen.tex': [ # http://crbug.com/157375 + 'UNKNOWN', + ], + 'third_party/iccjpeg': [ # http://crbug.com/98137 + 'UNKNOWN', + ], + 'third_party/icu': [ # http://crbug.com/98301 + 'UNKNOWN', + ], + 'third_party/jemalloc': [ # http://crbug.com/98302 + 'UNKNOWN', + ], + 'third_party/JSON': [ + 'Perl', # Build-only. + # License missing upstream on 3 minor files. + 'UNKNOWN', # https://rt.cpan.org/Public/Bug/Display.html?id=85915 + ], + 'third_party/lcov': [ # http://crbug.com/98304 + 'UNKNOWN', + ], + 'third_party/lcov/contrib/galaxy/genflat.pl': [ + 'GPL (v2 or later)', + ], + 'third_party/lcov-1.9/contrib/galaxy/genflat.pl': [ + 'GPL (v2 or later)', + ], + 'third_party/libevent': [ # http://crbug.com/98309 + 'UNKNOWN', + ], + 'third_party/libjingle/source/talk': [ # http://crbug.com/98310 + 'UNKNOWN', + ], + 'third_party/libjingle/source_internal/talk': [ # http://crbug.com/98310 + 'UNKNOWN', + ], + 'third_party/libjpeg': [ # http://crbug.com/98313 + 'UNKNOWN', + ], + 'third_party/libjpeg_turbo': [ # http://crbug.com/98314 + 'UNKNOWN', + ], + 'third_party/libpng': [ # http://crbug.com/98318 + 'UNKNOWN', + ], + + # The following files lack license headers, but are trivial. + 'third_party/libusb/src/libusb/os/poll_posix.h': [ + 'UNKNOWN', + ], + 'third_party/libusb/src/libusb/version.h': [ + 'UNKNOWN', + ], + 'third_party/libusb/src/autogen.sh': [ + 'UNKNOWN', + ], + 'third_party/libusb/src/config.h': [ + 'UNKNOWN', + ], + 'third_party/libusb/src/msvc/config.h': [ + 'UNKNOWN', + ], + + 'third_party/libvpx/source': [ # http://crbug.com/98319 + 'UNKNOWN', + ], + 'third_party/libvpx/source/libvpx/examples/includes': [ + 'GPL (v2 or later)', + ], + 'third_party/libxml': [ + 'UNKNOWN', + ], + 'third_party/libxslt': [ + 'UNKNOWN', + ], + 'third_party/lzma_sdk': [ + 'UNKNOWN', + ], + 'third_party/mesa/src': [ + 'GPL (v2)', + 'GPL (v3 or later)', + 'MIT/X11 (BSD like) GPL (v3 or later) with Bison parser exception', + 'UNKNOWN', # http://crbug.com/98450 + ], + 'third_party/modp_b64': [ + 'UNKNOWN', + ], + 'third_party/npapi/npspy/extern/java': [ + 'GPL (unversioned/unknown version)', + ], + 'third_party/openmax_dl/dl' : [ + 'Khronos Group', + ], + 'third_party/openssl': [ # http://crbug.com/98451 + 'UNKNOWN', + ], + 'third_party/ots/tools/ttf-checksum.py': [ # http://code.google.com/p/ots/issues/detail?id=2 + 'UNKNOWN', + ], + 'third_party/molokocacao': [ # http://crbug.com/98453 + 'UNKNOWN', + ], + 'third_party/npapi/npspy': [ + 'UNKNOWN', + ], + 'third_party/ocmock/OCMock': [ # http://crbug.com/98454 + 'UNKNOWN', + ], + 'third_party/ply/__init__.py': [ + 'UNKNOWN', + ], + 'third_party/protobuf': [ # http://crbug.com/98455 + 'UNKNOWN', + ], + + # http://crbug.com/222831 + # https://bitbucket.org/eliben/pyelftools/issue/12 + 'third_party/pyelftools': [ + 'UNKNOWN', + ], + + 'third_party/pylib': [ + 'UNKNOWN', + ], + 'third_party/scons-2.0.1/engine/SCons': [ # http://crbug.com/98462 + 'UNKNOWN', + ], + 'third_party/simplejson': [ + 'UNKNOWN', + ], + 'third_party/skia': [ # http://crbug.com/98463 + 'UNKNOWN', + ], + 'third_party/snappy/src': [ # http://crbug.com/98464 + 'UNKNOWN', + ], + 'third_party/smhasher/src': [ # http://crbug.com/98465 + 'UNKNOWN', + ], + 'third_party/speech-dispatcher/libspeechd.h': [ + 'GPL (v2 or later)', + ], + 'third_party/sqlite': [ + 'UNKNOWN', + ], + 'third_party/swig/Lib/linkruntime.c': [ # http://crbug.com/98585 + 'UNKNOWN', + ], + 'third_party/talloc': [ + 'GPL (v3 or later)', + 'UNKNOWN', # http://crbug.com/98588 + ], + 'third_party/tcmalloc': [ + 'UNKNOWN', # http://crbug.com/98589 + ], + 'third_party/tlslite': [ + 'UNKNOWN', + ], + 'third_party/webdriver': [ # http://crbug.com/98590 + 'UNKNOWN', + ], + 'third_party/webrtc': [ # http://crbug.com/98592 + 'UNKNOWN', + ], + 'third_party/xdg-utils': [ # http://crbug.com/98593 + 'UNKNOWN', + ], + 'third_party/yasm/source': [ # http://crbug.com/98594 + 'UNKNOWN', + ], + 'third_party/zlib/contrib/minizip': [ + 'UNKNOWN', + ], + 'third_party/zlib/trees.h': [ + 'UNKNOWN', + ], + 'tools/dromaeo_benchmark_runner/dromaeo_benchmark_runner.py': [ + 'UNKNOWN', + ], + 'tools/emacs': [ # http://crbug.com/98595 + 'UNKNOWN', + ], + 'tools/grit/grit/node/custom/__init__.py': [ + 'UNKNOWN', + ], + 'tools/gyp/test': [ + 'UNKNOWN', + ], + 'tools/histograms': [ + 'UNKNOWN', + ], + 'tools/memory_watcher': [ + 'UNKNOWN', + ], + 'tools/playback_benchmark': [ + 'UNKNOWN', + ], + 'tools/python/google/__init__.py': [ + 'UNKNOWN', + ], + 'tools/site_compare': [ + 'UNKNOWN', + ], + 'tools/stats_viewer/Properties/AssemblyInfo.cs': [ + 'UNKNOWN', + ], + 'tools/symsrc/pefile.py': [ + 'UNKNOWN', + ], + 'v8/test/cctest': [ # http://crbug.com/98597 + 'UNKNOWN', + ], + 'webkit/data/ico_decoder': [ + 'UNKNOWN', + ], +} + + +def check_licenses(options, args): + # Figure out which directory we have to check. + if len(args) == 0: + # No directory to check specified, use the repository root. + start_dir = options.base_directory + elif len(args) == 1: + # Directory specified. Start here. It's supposed to be relative to the + # base directory. + start_dir = os.path.abspath(os.path.join(options.base_directory, args[0])) + else: + # More than one argument, we don't handle this. + PrintUsage() + return 1 + + print "Using base directory:", options.base_directory + print "Checking:", start_dir + print + + licensecheck_path = os.path.abspath(os.path.join(options.base_directory, + 'third_party', + 'devscripts', + 'licensecheck.pl')) + + licensecheck = subprocess.Popen([licensecheck_path, + '-l', '100', + '-r', start_dir], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = licensecheck.communicate() + if options.verbose: + print '----------- licensecheck stdout -----------' + print stdout + print '--------- end licensecheck stdout ---------' + if licensecheck.returncode != 0 or stderr: + print '----------- licensecheck stderr -----------' + print stderr + print '--------- end licensecheck stderr ---------' + print "\nFAILED\n" + return 1 + + success = True + for line in stdout.splitlines(): + filename, license = line.split(':', 1) + filename = os.path.relpath(filename.strip(), options.base_directory) + + # All files in the build output directory are generated one way or another. + # There's no need to check them. + if filename.startswith('out/') or filename.startswith('sconsbuild/'): + continue + + # For now we're just interested in the license. + license = license.replace('*No copyright*', '').strip() + + # Skip generated files. + if 'GENERATED FILE' in license: + continue + + if license in WHITELISTED_LICENSES: + continue + + if not options.ignore_suppressions: + found_path_specific = False + for prefix in PATH_SPECIFIC_WHITELISTED_LICENSES: + if (filename.startswith(prefix) and + license in PATH_SPECIFIC_WHITELISTED_LICENSES[prefix]): + found_path_specific = True + break + if found_path_specific: + continue + + print "'%s' has non-whitelisted license '%s'" % (filename, license) + success = False + + if success: + print "\nSUCCESS\n" + return 0 + else: + print "\nFAILED\n" + print "Please read", + print "http://www.chromium.org/developers/adding-3rd-party-libraries" + print "for more info how to handle the failure." + print + print "Please respect OWNERS of checklicenses.py. Changes violating" + print "this requirement may be reverted." + return 1 + + +def main(): + default_root = os.path.abspath( + os.path.join(os.path.dirname(__file__), '..', '..')) + option_parser = optparse.OptionParser() + option_parser.add_option('--root', default=default_root, + dest='base_directory', + help='Specifies the repository root. This defaults ' + 'to "../.." relative to the script file, which ' + 'will normally be the repository root.') + option_parser.add_option('-v', '--verbose', action='store_true', + default=False, help='Print debug logging') + option_parser.add_option('--ignore-suppressions', + action='store_true', + default=False, + help='Ignore path-specific license whitelist.') + options, args = option_parser.parse_args() + return check_licenses(options, args) + + +if '__main__' == __name__: + sys.exit(main()) diff --git a/tools/checkperms/OWNERS b/tools/checkperms/OWNERS new file mode 100644 index 0000000000..1967bf567e --- /dev/null +++ b/tools/checkperms/OWNERS @@ -0,0 +1 @@ +thestig@chromium.org diff --git a/tools/checkperms/PRESUBMIT.py b/tools/checkperms/PRESUBMIT.py new file mode 100644 index 0000000000..c2f93567da --- /dev/null +++ b/tools/checkperms/PRESUBMIT.py @@ -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. + +"""Top-level presubmit script for checkperms. + +See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts for +details on the presubmit API built into gcl. +""" + + +def CommonChecks(input_api, output_api): + output = [] + output.extend(input_api.canned_checks.RunPylint(input_api, output_api)) + # Run it like if it were a unit test. + output.extend( + input_api.canned_checks.RunUnitTests( + input_api, output_api, ['./checkperms.py'])) + return output + + +def CheckChangeOnUpload(input_api, output_api): + return CommonChecks(input_api, output_api) + + +def CheckChangeOnCommit(input_api, output_api): + return CommonChecks(input_api, output_api) diff --git a/tools/checkperms/checkperms.py b/tools/checkperms/checkperms.py new file mode 100755 index 0000000000..f07103e379 --- /dev/null +++ b/tools/checkperms/checkperms.py @@ -0,0 +1,508 @@ +#!/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. + +"""Makes sure files have the right permissions. + +Some developers have broken SCM configurations that flip the svn:executable +permission on for no good reason. Unix developers who run ls --color will then +see .cc files in green and get confused. + +- For file extensions that must be executable, add it to EXECUTABLE_EXTENSIONS. +- For file extensions that must not be executable, add it to + NOT_EXECUTABLE_EXTENSIONS. +- To ignore all the files inside a directory, add it to IGNORED_PATHS. +- For file base name with ambiguous state and that should not be checked for + shebang, add it to IGNORED_FILENAMES. + +Any file not matching the above will be opened and looked if it has a shebang. +It this doesn't match the executable bit on the file, the file will be flagged. + +Note that all directory separators must be slashes (Unix-style) and not +backslashes. All directories should be relative to the source root and all +file paths should be only lowercase. +""" + +import logging +import optparse +import os +import stat +import subprocess +import sys + +#### USER EDITABLE SECTION STARTS HERE #### + +# Files with these extensions must have executable bit set. +EXECUTABLE_EXTENSIONS = ( + 'bat', + 'dll', + 'dylib', + 'exe', +) + +# These files must have executable bit set. +EXECUTABLE_PATHS = ( + # TODO(maruel): Detect ELF files. + 'chrome/installer/mac/sign_app.sh.in', + 'chrome/installer/mac/sign_versioned_dir.sh.in', +) + +# These files must not have the executable bit set. This is mainly a performance +# optimization as these files are not checked for shebang. The list was +# partially generated from: +# git ls-files | grep "\\." | sed 's/.*\.//' | sort | uniq -c | sort -b -g +NON_EXECUTABLE_EXTENSIONS = ( + '1', + '3ds', + 'S', + 'am', + 'applescript', + 'asm', + 'c', + 'cc', + 'cfg', + 'chromium', + 'cpp', + 'crx', + 'cs', + 'css', + 'cur', + 'def', + 'der', + 'expected', + 'gif', + 'grd', + 'gyp', + 'gypi', + 'h', + 'hh', + 'htm', + 'html', + 'hyph', + 'ico', + 'idl', + 'java', + 'jpg', + 'js', + 'json', + 'm', + 'm4', + 'mm', + 'mms', + 'mock-http-headers', + 'nmf', + 'onc', + 'pat', + 'patch', + 'pdf', + 'pem', + 'plist', + 'png', + 'proto', + 'rc', + 'rfx', + 'rgs', + 'rules', + 'spec', + 'sql', + 'srpc', + 'svg', + 'tcl', + 'test', + 'tga', + 'txt', + 'vcproj', + 'vsprops', + 'webm', + 'word', + 'xib', + 'xml', + 'xtb', + 'zip', +) + +# File names that are always whitelisted. (These are all autoconf spew.) +IGNORED_FILENAMES = ( + 'config.guess', + 'config.sub', + 'configure', + 'depcomp', + 'install-sh', + 'missing', + 'mkinstalldirs', + 'naclsdk', + 'scons', +) + +# File paths starting with one of these will be ignored as well. +# Please consider fixing your file permissions, rather than adding to this list. +IGNORED_PATHS = ( + # TODO(maruel): Detect ELF files. + 'chrome/test/data/extensions/uitest/plugins/plugin.plugin/contents/' + 'macos/testnetscapeplugin', + 'chrome/test/data/extensions/uitest/plugins_private/plugin.plugin/contents/' + 'macos/testnetscapeplugin', + 'chrome/installer/mac/sign_app.sh.in', + 'chrome/installer/mac/sign_versioned_dir.sh.in', + 'native_client_sdk/src/build_tools/sdk_tools/third_party/', + 'out/', + # TODO(maruel): Fix these. + 'third_party/android_testrunner/', + 'third_party/bintrees/', + 'third_party/closure_linter/', + 'third_party/devscripts/licensecheck.pl.vanilla', + 'third_party/hyphen/', + 'third_party/jemalloc/', + 'third_party/lcov-1.9/contrib/galaxy/conglomerate_functions.pl', + 'third_party/lcov-1.9/contrib/galaxy/gen_makefile.sh', + 'third_party/lcov/contrib/galaxy/conglomerate_functions.pl', + 'third_party/lcov/contrib/galaxy/gen_makefile.sh', + 'third_party/libevent/autogen.sh', + 'third_party/libevent/test/test.sh', + 'third_party/libxml/linux/xml2-config', + 'third_party/libxml/src/ltmain.sh', + 'third_party/mesa/', + 'third_party/protobuf/', + 'third_party/python_gflags/gflags.py', + 'third_party/sqlite/', + 'third_party/talloc/script/mksyms.sh', + 'third_party/tcmalloc/', + 'third_party/tlslite/setup.py', +) + +#### USER EDITABLE SECTION ENDS HERE #### + +assert set(EXECUTABLE_EXTENSIONS) & set(NON_EXECUTABLE_EXTENSIONS) == set() + + +def capture(cmd, cwd): + """Returns the output of a command. + + Ignores the error code or stderr. + """ + logging.debug('%s; cwd=%s' % (' '.join(cmd), cwd)) + env = os.environ.copy() + env['LANGUAGE'] = 'en_US.UTF-8' + p = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd, env=env) + return p.communicate()[0] + + +def get_svn_info(dir_path): + """Returns svn meta-data for a svn checkout.""" + if not os.path.isdir(dir_path): + return {} + out = capture(['svn', 'info', '.', '--non-interactive'], dir_path) + return dict(l.split(': ', 1) for l in out.splitlines() if l) + + +def get_svn_url(dir_path): + return get_svn_info(dir_path).get('URL') + + +def get_svn_root(dir_path): + """Returns the svn checkout root or None.""" + svn_url = get_svn_url(dir_path) + if not svn_url: + return None + logging.info('svn url: %s' % svn_url) + while True: + parent = os.path.dirname(dir_path) + if parent == dir_path: + return None + svn_url = svn_url.rsplit('/', 1)[0] + if svn_url != get_svn_url(parent): + return dir_path + dir_path = parent + + +def get_git_root(dir_path): + """Returns the git checkout root or None.""" + root = capture(['git', 'rev-parse', '--show-toplevel'], dir_path).strip() + if root: + return root + + +def is_ignored(rel_path): + """Returns True if rel_path is in our whitelist of files to ignore.""" + rel_path = rel_path.lower() + return ( + os.path.basename(rel_path) in IGNORED_FILENAMES or + rel_path.startswith(IGNORED_PATHS)) + + +def must_be_executable(rel_path): + """The file name represents a file type that must have the executable bit + set. + """ + return ( + os.path.splitext(rel_path)[1][1:].lower() in EXECUTABLE_EXTENSIONS or + rel_path in EXECUTABLE_PATHS) + + +def must_not_be_executable(rel_path): + """The file name represents a file type that must not have the executable + bit set. + """ + return os.path.splitext(rel_path)[1][1:].lower() in NON_EXECUTABLE_EXTENSIONS + + +def has_executable_bit(full_path): + """Returns if any executable bit is set.""" + permission = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH + return bool(permission & os.stat(full_path).st_mode) + + +def has_shebang(full_path): + """Returns if the file starts with #!/. + + file_path is the absolute path to the file. + """ + with open(full_path, 'rb') as f: + return f.read(3) == '#!/' + +def check_file(full_path, bare_output): + """Checks file_path's permissions and returns an error if it is + inconsistent. + + It is assumed that the file is not ignored by is_ignored(). + + If the file name is matched with must_be_executable() or + must_not_be_executable(), only its executable bit is checked. + Otherwise, the 3 first bytes of the file are read to verify if it has a + shebang and compares this with the executable bit on the file. + """ + try: + bit = has_executable_bit(full_path) + except OSError: + # It's faster to catch exception than call os.path.islink(). Chromium + # tree happens to have invalid symlinks under + # third_party/openssl/openssl/test/. + return None + + if must_be_executable(full_path): + if not bit: + if bare_output: + return full_path + return '%s: Must have executable bit set' % full_path + return + if must_not_be_executable(full_path): + if bit: + if bare_output: + return full_path + return '%s: Must not have executable bit set' % full_path + return + + # For the others, it depends on the shebang. + shebang = has_shebang(full_path) + if bit != shebang: + if bare_output: + return full_path + if bit: + return '%s: Has executable bit but not shebang' % full_path + else: + return '%s: Has shebang but not executable bit' % full_path + + +def check_files(root, files, bare_output): + errors = [] + for file_path in files: + if is_ignored(file_path): + continue + + full_file_path = os.path.join(root, file_path) + + error = check_file(full_file_path, bare_output) + if error: + errors.append(error) + + return errors + +class ApiBase(object): + def __init__(self, root_dir, bare_output): + self.root_dir = root_dir + self.bare_output = bare_output + self.count = 0 + self.count_shebang = 0 + + def check_file(self, rel_path): + logging.debug('check_file(%s)' % rel_path) + self.count += 1 + + if (not must_be_executable(rel_path) and + not must_not_be_executable(rel_path)): + self.count_shebang += 1 + + full_path = os.path.join(self.root_dir, rel_path) + return check_file(full_path, self.bare_output) + + def check_dir(self, rel_path): + return self.check(rel_path) + + def check(self, start_dir): + """Check the files in start_dir, recursively check its subdirectories.""" + errors = [] + items = self.list_dir(start_dir) + logging.info('check(%s) -> %d' % (start_dir, len(items))) + for item in items: + full_path = os.path.join(self.root_dir, start_dir, item) + rel_path = full_path[len(self.root_dir) + 1:] + if is_ignored(rel_path): + continue + if os.path.isdir(full_path): + # Depth first. + errors.extend(self.check_dir(rel_path)) + else: + error = self.check_file(rel_path) + if error: + errors.append(error) + return errors + + def list_dir(self, start_dir): + """Lists all the files and directory inside start_dir.""" + return sorted( + x for x in os.listdir(os.path.join(self.root_dir, start_dir)) + if not x.startswith('.') + ) + + +class ApiSvnQuick(ApiBase): + """Returns all files in svn-versioned directories, independent of the fact if + they are versionned. + + Uses svn info in each directory to determine which directories should be + crawled. + """ + def __init__(self, *args): + super(ApiSvnQuick, self).__init__(*args) + self.url = get_svn_url(self.root_dir) + + def check_dir(self, rel_path): + url = self.url + '/' + rel_path + if get_svn_url(os.path.join(self.root_dir, rel_path)) != url: + return [] + return super(ApiSvnQuick, self).check_dir(rel_path) + + +class ApiAllFilesAtOnceBase(ApiBase): + _files = None + + def list_dir(self, start_dir): + """Lists all the files and directory inside start_dir.""" + if self._files is None: + self._files = sorted(self._get_all_files()) + if not self.bare_output: + print 'Found %s files' % len(self._files) + start_dir = start_dir[len(self.root_dir) + 1:] + return [ + x[len(start_dir):] for x in self._files if x.startswith(start_dir) + ] + + def _get_all_files(self): + """Lists all the files and directory inside self._root_dir.""" + raise NotImplementedError() + + +class ApiSvn(ApiAllFilesAtOnceBase): + """Returns all the subversion controlled files. + + Warning: svn ls is abnormally slow. + """ + def _get_all_files(self): + cmd = ['svn', 'ls', '--non-interactive', '--recursive'] + return ( + x for x in capture(cmd, self.root_dir).splitlines() + if not x.endswith(os.path.sep)) + + +class ApiGit(ApiAllFilesAtOnceBase): + def _get_all_files(self): + return capture(['git', 'ls-files'], cwd=self.root_dir).splitlines() + + +def get_scm(dir_path, bare): + """Returns a properly configured ApiBase instance.""" + cwd = os.getcwd() + root = get_svn_root(dir_path or cwd) + if root: + if not bare: + print('Found subversion checkout at %s' % root) + return ApiSvnQuick(dir_path or root, bare) + root = get_git_root(dir_path or cwd) + if root: + if not bare: + print('Found git repository at %s' % root) + return ApiGit(dir_path or root, bare) + + # Returns a non-scm aware checker. + if not bare: + print('Failed to determine the SCM for %s' % dir_path) + return ApiBase(dir_path or cwd, bare) + + +def main(): + usage = """Usage: python %prog [--root ] [tocheck] + tocheck Specifies the directory, relative to root, to check. This defaults + to "." so it checks everything. + +Examples: + python %prog + python %prog --root /path/to/source chrome""" + + parser = optparse.OptionParser(usage=usage) + parser.add_option( + '--root', + help='Specifies the repository root. This defaults ' + 'to the checkout repository root') + parser.add_option( + '-v', '--verbose', action='count', default=0, help='Print debug logging') + parser.add_option( + '--bare', + action='store_true', + default=False, + help='Prints the bare filename triggering the checks') + parser.add_option( + '--file', action='append', dest='files', + help='Specifics a list of files to check the permissions of. Only these ' + 'files will be checked') + options, args = parser.parse_args() + + levels = [logging.ERROR, logging.INFO, logging.DEBUG] + logging.basicConfig(level=levels[min(len(levels) - 1, options.verbose)]) + + if len(args) > 1: + parser.error('Too many arguments used') + + if options.root: + options.root = os.path.abspath(options.root) + + if options.files: + errors = check_files(options.root, options.files, options.bare) + print '\n'.join(errors) + return bool(errors) + + api = get_scm(options.root, options.bare) + if args: + start_dir = args[0] + else: + start_dir = api.root_dir + + errors = api.check(start_dir) + + if not options.bare: + print 'Processed %s files, %d files where tested for shebang' % ( + api.count, api.count_shebang) + + if errors: + if not options.bare: + print '\nFAILED\n' + print '\n'.join(errors) + return 1 + if not options.bare: + print '\nSUCCESS\n' + return 0 + + +if '__main__' == __name__: + sys.exit(main()) diff --git a/tools/clang/OWNERS b/tools/clang/OWNERS new file mode 100644 index 0000000000..d86ef9424a --- /dev/null +++ b/tools/clang/OWNERS @@ -0,0 +1,2 @@ +hans@chromium.org +thakis@chromium.org diff --git a/tools/clang/empty_string/EmptyStringConverter.cpp b/tools/clang/empty_string/EmptyStringConverter.cpp new file mode 100644 index 0000000000..421f964bbd --- /dev/null +++ b/tools/clang/empty_string/EmptyStringConverter.cpp @@ -0,0 +1,200 @@ +// 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 implements a Clang tool to convert all instances of std::string("") to +// std::string(). The latter is more efficient (as std::string doesn't have to +// take a copy of an empty string) and generates fewer instructions as well. It +// should be run using the tools/clang/scripts/run_tool.py helper. + +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Support/CommandLine.h" + +using clang::ast_matchers::MatchFinder; +using clang::ast_matchers::argumentCountIs; +using clang::ast_matchers::bindTemporaryExpr; +using clang::ast_matchers::constructorDecl; +using clang::ast_matchers::constructExpr; +using clang::ast_matchers::defaultArgExpr; +using clang::ast_matchers::expr; +using clang::ast_matchers::forEach; +using clang::ast_matchers::has; +using clang::ast_matchers::hasArgument; +using clang::ast_matchers::hasDeclaration; +using clang::ast_matchers::hasName; +using clang::ast_matchers::id; +using clang::ast_matchers::methodDecl; +using clang::ast_matchers::newExpr; +using clang::ast_matchers::ofClass; +using clang::ast_matchers::stringLiteral; +using clang::ast_matchers::varDecl; +using clang::tooling::CommonOptionsParser; +using clang::tooling::Replacement; +using clang::tooling::Replacements; + +namespace { + +// Handles replacements for stack and heap-allocated instances, e.g.: +// std::string a(""); +// std::string* b = new std::string(""); +class ConstructorCallback : public MatchFinder::MatchCallback { + public: + ConstructorCallback(Replacements* replacements) + : replacements_(replacements) {} + + virtual void run(const MatchFinder::MatchResult& result) LLVM_OVERRIDE; + + private: + Replacements* const replacements_; +}; + +// Handles replacements for invocations of std::string("") in an initializer +// list. +class InitializerCallback : public MatchFinder::MatchCallback { + public: + InitializerCallback(Replacements* replacements) + : replacements_(replacements) {} + + virtual void run(const MatchFinder::MatchResult& result) LLVM_OVERRIDE; + + private: + Replacements* const replacements_; +}; + +// Handles replacements for invocations of std::string("") in a temporary +// context, e.g. FunctionThatTakesString(std::string("")). Note that this +// handles implicits construction of std::string as well. +class TemporaryCallback : public MatchFinder::MatchCallback { + public: + TemporaryCallback(Replacements* replacements) : replacements_(replacements) {} + + virtual void run(const MatchFinder::MatchResult& result) LLVM_OVERRIDE; + + private: + Replacements* const replacements_; +}; + +class EmptyStringConverter { + public: + explicit EmptyStringConverter(Replacements* replacements) + : constructor_callback_(replacements), + initializer_callback_(replacements), + temporary_callback_(replacements) {} + + void SetupMatchers(MatchFinder* match_finder); + + private: + ConstructorCallback constructor_callback_; + InitializerCallback initializer_callback_; + TemporaryCallback temporary_callback_; +}; + +void EmptyStringConverter::SetupMatchers(MatchFinder* match_finder) { + const clang::ast_matchers::StatementMatcher& constructor_call = + id("call", + constructExpr( + hasDeclaration(methodDecl(ofClass(hasName("std::basic_string")))), + argumentCountIs(2), + hasArgument(0, id("literal", stringLiteral())), + hasArgument(1, defaultArgExpr()))); + + // Note that expr(has()) in the matcher is significant; the Clang AST wraps + // calls to the std::string constructor with exprWithCleanups nodes. Without + // the expr(has()) matcher, the first and last rules would not match anything! + match_finder->addMatcher(varDecl(forEach(expr(has(constructor_call)))), + &constructor_callback_); + match_finder->addMatcher(newExpr(has(constructor_call)), + &constructor_callback_); + match_finder->addMatcher(bindTemporaryExpr(has(constructor_call)), + &temporary_callback_); + match_finder->addMatcher( + constructorDecl(forEach(expr(has(constructor_call)))), + &initializer_callback_); +} + +void ConstructorCallback::run(const MatchFinder::MatchResult& result) { + const clang::StringLiteral* literal = + result.Nodes.getNodeAs("literal"); + if (literal->getLength() > 0) + return; + + const clang::CXXConstructExpr* call = + result.Nodes.getNodeAs("call"); + clang::CharSourceRange range = + clang::CharSourceRange::getTokenRange(call->getParenRange()); + replacements_->insert(Replacement(*result.SourceManager, range, "")); +} + +void InitializerCallback::run(const MatchFinder::MatchResult& result) { + const clang::StringLiteral* literal = + result.Nodes.getNodeAs("literal"); + if (literal->getLength() > 0) + return; + + const clang::CXXConstructExpr* call = + result.Nodes.getNodeAs("call"); + replacements_->insert(Replacement(*result.SourceManager, call, "")); +} + +void TemporaryCallback::run(const MatchFinder::MatchResult& result) { + const clang::StringLiteral* literal = + result.Nodes.getNodeAs("literal"); + if (literal->getLength() > 0) + return; + + const clang::CXXConstructExpr* call = + result.Nodes.getNodeAs("call"); + // Differentiate between explicit and implicit calls to std::string's + // constructor. An implicitly generated constructor won't have a valid + // source range for the parenthesis. We do this because the matched expression + // for |call| in the explicit case doesn't include the closing parenthesis. + clang::SourceRange range = call->getParenRange(); + if (range.isValid()) { + replacements_->insert(Replacement(*result.SourceManager, literal, "")); + } else { + replacements_->insert( + Replacement(*result.SourceManager, + call, + literal->isWide() ? "std::wstring()" : "std::string()")); + } +} + +} // namespace + +static llvm::cl::extrahelp common_help(CommonOptionsParser::HelpMessage); + +int main(int argc, const char* argv[]) { + CommonOptionsParser options(argc, argv); + clang::tooling::ClangTool tool(options.getCompilations(), + options.getSourcePathList()); + + Replacements replacements; + EmptyStringConverter converter(&replacements); + MatchFinder match_finder; + converter.SetupMatchers(&match_finder); + + int result = + tool.run(clang::tooling::newFrontendActionFactory(&match_finder)); + if (result != 0) + return result; + + // Each replacement line should have the following format: + // r:::: + // Only the field can contain embedded ":" characters. + // TODO(dcheng): Use a more clever serialization. + llvm::outs() << "==== BEGIN EDITS ====\n"; + for (Replacements::const_iterator it = replacements.begin(); + it != replacements.end(); ++it) { + llvm::outs() << "r:" << it->getFilePath() << ":" << it->getOffset() << ":" + << it->getLength() << ":" << it->getReplacementText() << "\n"; + } + llvm::outs() << "==== END EDITS ====\n"; + + return 0; +} diff --git a/tools/clang/empty_string/Makefile b/tools/clang/empty_string/Makefile new file mode 100644 index 0000000000..d44641216e --- /dev/null +++ b/tools/clang/empty_string/Makefile @@ -0,0 +1,23 @@ +# 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 Makefile requires the LLVM build system. In order to build this tool, +# please run tools/clang/scripts/build_tool.py. + +CLANG_LEVEL := ../.. + +TOOLNAME = empty_string + +NO_INSTALL = 1 + +include $(CLANG_LEVEL)/../../Makefile.config + +LINK_COMPONENTS := $(TARGETS_TO_BUILD) asmparser bitreader support mc +USEDLIBS = clangTooling.a clangFrontend.a clangSerialization.a clangDriver.a \ + clangRewriteFrontend.a clangRewriteCore.a clangParse.a clangSema.a \ + clangAnalysis.a clangAST.a clangASTMatchers.a clangEdit.a \ + clangLex.a clangBasic.a + +include $(CLANG_LEVEL)/Makefile + diff --git a/tools/clang/empty_string/tests/test-expected.cc b/tools/clang/empty_string/tests/test-expected.cc new file mode 100644 index 0000000000..7fd9613424 --- /dev/null +++ b/tools/clang/empty_string/tests/test-expected.cc @@ -0,0 +1,45 @@ +// 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. +// +// Test file for the empty string clang tool. + +#include + +// Tests for std::string declarations. +void TestDeclarations() { std::string a, b("abc"), c; } + +// Tests for std::string allocated with new. +void TestNew() { + std::string* a = new std::string, + *b = new std::string("abc"), + *c = new std::string, + *d = new std::string(); +} + +// Tests for std::string construction in initializer lists. +class TestInitializers { + public: + TestInitializers() {} + TestInitializers(bool) {} + TestInitializers(double) : b("cat"), c() {} + + private: + std::string a; + std::string b; + std::string c; +}; + +// Tests for temporary std::strings. +void TestTemporaries(const std::string& reference_argument, + const std::string value_argument) { + TestTemporaries(std::string(), std::string()); + TestTemporaries(std::string(), std::string()); +} + +// Tests for temporary std::wstrings. +void TestWideTemporaries(const std::wstring& reference_argument, + const std::wstring value_argument) { + TestWideTemporaries(std::wstring(), std::wstring()); + TestWideTemporaries(std::wstring(), std::wstring()); +} diff --git a/tools/clang/empty_string/tests/test-original.cc b/tools/clang/empty_string/tests/test-original.cc new file mode 100644 index 0000000000..2edb896cc6 --- /dev/null +++ b/tools/clang/empty_string/tests/test-original.cc @@ -0,0 +1,46 @@ +// 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. +// +// Test file for the empty string clang tool. + +#include + +// Tests for std::string declarations. +void TestDeclarations() { std::string a(""), b("abc"), c(""); } + +// Tests for std::string allocated with new. +void TestNew() { + std::string* a = new std::string(""), + *b = new std::string("abc"), + *c = new std::string(""), + *d = new std::string(); +} + +// Tests for std::string construction in initializer lists. +class TestInitializers { + public: + TestInitializers() : a("") {} + TestInitializers(bool) : a(""), b("") {} + TestInitializers(double) : a(""), b("cat"), c() {} + + private: + std::string a; + std::string b; + std::string c; +}; + +// Tests for temporary std::strings. +void TestTemporaries(const std::string& reference_argument, + const std::string value_argument) { + TestTemporaries("", ""); + TestTemporaries(std::string(""), std::string("")); +} + +// Tests for temporary std::wstrings. +void TestWideTemporaries(const std::wstring& reference_argument, + const std::wstring value_argument) { + TestWideTemporaries(L"", L""); + TestWideTemporaries(std::wstring(L""), std::wstring(L"")); +} + diff --git a/tools/clang/plugins/ChromeClassTester.cpp b/tools/clang/plugins/ChromeClassTester.cpp new file mode 100644 index 0000000000..ee8452dbc1 --- /dev/null +++ b/tools/clang/plugins/ChromeClassTester.cpp @@ -0,0 +1,303 @@ +// Copyright (c) 2012 The Chromium 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 general interface for filtering and only acting on classes in Chromium C++ +// code. + +#include "ChromeClassTester.h" + +#include + +#include "clang/AST/AST.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/SourceManager.h" + +using namespace clang; + +namespace { + +bool starts_with(const std::string& one, const std::string& two) { + return one.compare(0, two.size(), two) == 0; +} + +std::string lstrip(const std::string& one, const std::string& two) { + if (starts_with(one, two)) + return one.substr(two.size()); + return one; +} + +bool ends_with(const std::string& one, const std::string& two) { + if (two.size() > one.size()) + return false; + + return one.compare(one.size() - two.size(), two.size(), two) == 0; +} + +} // namespace + +ChromeClassTester::ChromeClassTester(CompilerInstance& instance, + bool check_url_directory) + : instance_(instance), + diagnostic_(instance.getDiagnostics()), + check_url_directory_(check_url_directory) { + BuildBannedLists(); +} + +ChromeClassTester::~ChromeClassTester() {} + +void ChromeClassTester::HandleTagDeclDefinition(TagDecl* tag) { + pending_class_decls_.push_back(tag); +} + +bool ChromeClassTester::HandleTopLevelDecl(DeclGroupRef group_ref) { + for (size_t i = 0; i < pending_class_decls_.size(); ++i) + CheckTag(pending_class_decls_[i]); + pending_class_decls_.clear(); + + return true; // true means continue parsing. +} + +void ChromeClassTester::CheckTag(TagDecl* tag) { + // We handle class types here where we have semantic information. We can only + // check structs/classes/enums here, but we get a bunch of nice semantic + // information instead of just parsing information. + + if (CXXRecordDecl* record = dyn_cast(tag)) { + // If this is a POD or a class template or a type dependent on a + // templated class, assume there's no ctor/dtor/virtual method + // optimization that we can do. + if (record->isPOD() || + record->getDescribedClassTemplate() || + record->getTemplateSpecializationKind() || + record->isDependentType()) + return; + + if (InBannedNamespace(record)) + return; + + SourceLocation record_location = record->getInnerLocStart(); + if (InBannedDirectory(record_location)) + return; + + // We sadly need to maintain a blacklist of types that violate these + // rules, but do so for good reason or due to limitations of this + // checker (i.e., we don't handle extern templates very well). + std::string base_name = record->getNameAsString(); + if (IsIgnoredType(base_name)) + return; + + // We ignore all classes that end with "Matcher" because they're probably + // GMock artifacts. + if (ends_with(base_name, "Matcher")) + return; + + CheckChromeClass(record_location, record); + } +} + +void ChromeClassTester::emitWarning(SourceLocation loc, + const char* raw_error) { + FullSourceLoc full(loc, instance().getSourceManager()); + std::string err; + err = "[chromium-style] "; + err += raw_error; + DiagnosticsEngine::Level level = + diagnostic().getWarningsAsErrors() ? + DiagnosticsEngine::Error : + DiagnosticsEngine::Warning; + unsigned id = diagnostic().getCustomDiagID(level, err); + DiagnosticBuilder builder = diagnostic().Report(full, id); +} + +bool ChromeClassTester::InBannedNamespace(const Decl* record) { + std::string n = GetNamespace(record); + if (!n.empty()) { + return std::find(banned_namespaces_.begin(), banned_namespaces_.end(), n) + != banned_namespaces_.end(); + } + + return false; +} + +std::string ChromeClassTester::GetNamespace(const Decl* record) { + return GetNamespaceImpl(record->getDeclContext(), ""); +} + +bool ChromeClassTester::InImplementationFile(SourceLocation record_location) { + std::string filename; + if (!GetFilename(record_location, &filename)) + return false; + + if (ends_with(filename, ".cc") || ends_with(filename, ".cpp") || + ends_with(filename, ".mm")) { + return true; + } + + return false; +} + +void ChromeClassTester::BuildBannedLists() { + banned_namespaces_.push_back("std"); + banned_namespaces_.push_back("__gnu_cxx"); + banned_namespaces_.push_back("WebKit"); + banned_namespaces_.push_back("WebTestRunner"); + + banned_directories_.push_back("third_party/"); + banned_directories_.push_back("native_client/"); + banned_directories_.push_back("breakpad/"); + banned_directories_.push_back("courgette/"); + banned_directories_.push_back("pdf/"); + banned_directories_.push_back("ppapi/"); + banned_directories_.push_back("usr/"); + banned_directories_.push_back("testing/"); + banned_directories_.push_back("v8/"); + banned_directories_.push_back("dart/"); + banned_directories_.push_back("sdch/"); + banned_directories_.push_back("icu4c/"); + banned_directories_.push_back("frameworks/"); + + if (!check_url_directory_) + banned_directories_.push_back("url/"); + + // Don't check autogenerated headers. + // Make puts them below $(builddir_name)/.../gen and geni. + // Ninja puts them below OUTPUT_DIR/.../gen + // Xcode has a fixed output directory for everything. + banned_directories_.push_back("gen/"); + banned_directories_.push_back("geni/"); + banned_directories_.push_back("xcodebuild/"); + + // You are standing in a mazy of twisty dependencies, all resolved by + // putting everything in the header. + banned_directories_.push_back("automation/"); + + // Don't check system headers. + banned_directories_.push_back("/Developer/"); + + // Used in really low level threading code that probably shouldn't be out of + // lined. + ignored_record_names_.insert("ThreadLocalBoolean"); + + // A complicated pickle derived struct that is all packed integers. + ignored_record_names_.insert("Header"); + + // Part of the GPU system that uses multiple included header + // weirdness. Never getting this right. + ignored_record_names_.insert("Validators"); + + // Has a UNIT_TEST only constructor. Isn't *terribly* complex... + ignored_record_names_.insert("AutocompleteController"); + ignored_record_names_.insert("HistoryURLProvider"); + + // Because of chrome frame + ignored_record_names_.insert("ReliabilityTestSuite"); + + // Used over in the net unittests. A large enough bundle of integers with 1 + // non-pod class member. Probably harmless. + ignored_record_names_.insert("MockTransaction"); + + // Used heavily in ui_unittests and once in views_unittests. Fixing this + // isn't worth the overhead of an additional library. + ignored_record_names_.insert("TestAnimationDelegate"); + + // Part of our public interface that nacl and friends use. (Arguably, this + // should mean that this is a higher priority but fixing this looks hard.) + ignored_record_names_.insert("PluginVersionInfo"); + + // Measured performance improvement on cc_perftests. See + // https://codereview.chromium.org/11299290/ + ignored_record_names_.insert("QuadF"); +} + +std::string ChromeClassTester::GetNamespaceImpl(const DeclContext* context, + const std::string& candidate) { + switch (context->getDeclKind()) { + case Decl::TranslationUnit: { + return candidate; + } + case Decl::Namespace: { + const NamespaceDecl* decl = dyn_cast(context); + std::string name_str; + llvm::raw_string_ostream OS(name_str); + if (decl->isAnonymousNamespace()) + OS << ""; + else + OS << *decl; + return GetNamespaceImpl(context->getParent(), + OS.str()); + } + default: { + return GetNamespaceImpl(context->getParent(), candidate); + } + } +} + +bool ChromeClassTester::InBannedDirectory(SourceLocation loc) { + std::string filename; + if (!GetFilename(loc, &filename)) { + // If the filename cannot be determined, simply treat this as a banned + // location, instead of going through the full lookup process. + return true; + } + + // We need to special case scratch space; which is where clang does its + // macro expansion. We explicitly want to allow people to do otherwise bad + // things through macros that were defined due to third party libraries. + if (filename == "") + return true; + + // Don't complain about autogenerated protobuf files. + if (ends_with(filename, ".pb.h")) { + return true; + } + + // We need to munge the paths so that they are relative to the repository + // srcroot. We first resolve the symlinktastic relative path and then + // remove our known srcroot from it if needed. + char resolvedPath[MAXPATHLEN]; + if (realpath(filename.c_str(), resolvedPath)) { + filename = resolvedPath; + } + + // On linux, chrome is often checked out to /usr/local/google. Due to the + // "usr" rule in banned_directories_, all diagnostics would be suppressed + // in that case. As a workaround, strip that prefix. + filename = lstrip(filename, "/usr/local/google"); + + for (std::vector::const_iterator it = + banned_directories_.begin(); + it != banned_directories_.end(); ++it) { + // If we can find any of the banned path components in this path, then + // this file is rejected. + size_t index = filename.find(*it); + if (index != std::string::npos) { + bool matches_full_dir_name = index == 0 || filename[index - 1] == '/'; + if ((*it)[0] == '/') + matches_full_dir_name = true; + if (matches_full_dir_name) + return true; + } + } + + return false; +} + +bool ChromeClassTester::IsIgnoredType(const std::string& base_name) { + return ignored_record_names_.find(base_name) != ignored_record_names_.end(); +} + +bool ChromeClassTester::GetFilename(SourceLocation loc, + std::string* filename) { + const SourceManager& source_manager = instance_.getSourceManager(); + SourceLocation spelling_location = source_manager.getSpellingLoc(loc); + PresumedLoc ploc = source_manager.getPresumedLoc(spelling_location); + if (ploc.isInvalid()) { + // If we're in an invalid location, we're looking at things that aren't + // actually stated in the source. + return false; + } + + *filename = ploc.getFilename(); + return true; +} diff --git a/tools/clang/plugins/ChromeClassTester.h b/tools/clang/plugins/ChromeClassTester.h new file mode 100644 index 0000000000..541341de53 --- /dev/null +++ b/tools/clang/plugins/ChromeClassTester.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 TOOLS_CLANG_PLUGINS_CHROMECLASSTESTER_H_ +#define TOOLS_CLANG_PLUGINS_CHROMECLASSTESTER_H_ + +#include +#include + +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/TypeLoc.h" +#include "clang/Frontend/CompilerInstance.h" + +// A class on top of ASTConsumer that forwards classes defined in Chromium +// headers to subclasses which implement CheckChromeClass(). +class ChromeClassTester : public clang::ASTConsumer { + public: + explicit ChromeClassTester(clang::CompilerInstance& instance, + bool check_url_directory); + virtual ~ChromeClassTester(); + + // clang::ASTConsumer: + virtual void HandleTagDeclDefinition(clang::TagDecl* tag); + virtual bool HandleTopLevelDecl(clang::DeclGroupRef group_ref); + + protected: + clang::CompilerInstance& instance() { return instance_; } + clang::DiagnosticsEngine& diagnostic() { return diagnostic_; } + + // Emits a simple warning; this shouldn't be used if you require printf-style + // printing. + void emitWarning(clang::SourceLocation loc, const char* error); + + // Utility method for subclasses to check if this class is in a banned + // namespace. + bool InBannedNamespace(const clang::Decl* record); + + // Utility method for subclasses to determine the namespace of the + // specified record, if any. Unnamed namespaces will be identified as + // "". + std::string GetNamespace(const clang::Decl* record); + + // Utility method for subclasses to check if this class is within an + // implementation (.cc, .cpp, .mm) file. + bool InImplementationFile(clang::SourceLocation location); + + private: + void BuildBannedLists(); + + void CheckTag(clang::TagDecl*); + + // Filtered versions of tags that are only called with things defined in + // chrome header files. + virtual void CheckChromeClass(clang::SourceLocation record_location, + clang::CXXRecordDecl* record) = 0; + + // Utility methods used for filtering out non-chrome classes (and ones we + // deliberately ignore) in HandleTagDeclDefinition(). + std::string GetNamespaceImpl(const clang::DeclContext* context, + const std::string& candidate); + bool InBannedDirectory(clang::SourceLocation loc); + bool IsIgnoredType(const std::string& base_name); + + // Attempts to determine the filename for the given SourceLocation. + // Returns false if the filename could not be determined. + bool GetFilename(clang::SourceLocation loc, std::string* filename); + + clang::CompilerInstance& instance_; + clang::DiagnosticsEngine& diagnostic_; + + // List of banned namespaces. + std::vector banned_namespaces_; + + // List of banned directories. + std::vector banned_directories_; + + // List of types that we don't check. + std::set ignored_record_names_; + + // List of decls to check once the current top-level decl is parsed. + std::vector pending_class_decls_; + + // TODO(tfarina): Remove once url/ directory compiles without warnings. + bool check_url_directory_; +}; + +#endif // TOOLS_CLANG_PLUGINS_CHROMECLASSTESTER_H_ diff --git a/tools/clang/plugins/FindBadConstructs.cpp b/tools/clang/plugins/FindBadConstructs.cpp new file mode 100644 index 0000000000..9697c5f211 --- /dev/null +++ b/tools/clang/plugins/FindBadConstructs.cpp @@ -0,0 +1,668 @@ +// Copyright (c) 2012 The Chromium 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 a bunch of recurring problems in the Chromium C++ code. +// +// Checks that are implemented: +// - Constructors/Destructors should not be inlined if they are of a complex +// class type. +// - Missing "virtual" keywords on methods that should be virtual. +// - Non-annotated overriding virtual methods. +// - Virtual methods with nonempty implementations in their headers. +// - Classes that derive from base::RefCounted / base::RefCountedThreadSafe +// should have protected or private destructors. + +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/AST.h" +#include "clang/AST/Attr.h" +#include "clang/AST/CXXInheritance.h" +#include "clang/AST/TypeLoc.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendPluginRegistry.h" +#include "clang/Lex/Lexer.h" +#include "llvm/Support/raw_ostream.h" + +#include "ChromeClassTester.h" + +using namespace clang; + +namespace { + +const char kMethodRequiresOverride[] = + "[chromium-style] Overriding method must be marked with OVERRIDE."; +const char kMethodRequiresVirtual[] = + "[chromium-style] Overriding method must have \"virtual\" keyword."; +const char kNoExplicitDtor[] = + "[chromium-style] Classes that are ref-counted should have explicit " + "destructors that are declared protected or private."; +const char kPublicDtor[] = + "[chromium-style] Classes that are ref-counted should have " + "destructors that are declared protected or private."; +const char kProtectedNonVirtualDtor[] = + "[chromium-style] Classes that are ref-counted and have non-private " + "destructors should declare their destructor virtual."; +const char kNoteInheritance[] = + "[chromium-style] %0 inherits from %1 here"; +const char kNoteImplicitDtor[] = + "[chromium-style] No explicit destructor for %0 defined"; +const char kNotePublicDtor[] = + "[chromium-style] Public destructor declared here"; +const char kNoteProtectedNonVirtualDtor[] = + "[chromium-style] Protected non-virtual destructor declared here"; + +bool TypeHasNonTrivialDtor(const Type* type) { + if (const CXXRecordDecl* cxx_r = type->getPointeeCXXRecordDecl()) + return cxx_r->hasTrivialDestructor(); + + return false; +} + +// Returns the underlying Type for |type| by expanding typedefs and removing +// any namespace qualifiers. This is similar to desugaring, except that for +// ElaboratedTypes, desugar will unwrap too much. +const Type* UnwrapType(const Type* type) { + if (const ElaboratedType* elaborated = dyn_cast(type)) + return UnwrapType(elaborated->getNamedType().getTypePtr()); + if (const TypedefType* typedefed = dyn_cast(type)) + return UnwrapType(typedefed->desugar().getTypePtr()); + return type; +} + +// Searches for constructs that we know we don't want in the Chromium code base. +class FindBadConstructsConsumer : public ChromeClassTester { + public: + FindBadConstructsConsumer(CompilerInstance& instance, + bool check_base_classes, + bool check_virtuals_in_implementations, + bool check_url_directory) + : ChromeClassTester(instance, check_url_directory), + check_base_classes_(check_base_classes), + check_virtuals_in_implementations_(check_virtuals_in_implementations) { + // Register warning/error messages. + diag_method_requires_override_ = diagnostic().getCustomDiagID( + getErrorLevel(), kMethodRequiresOverride); + diag_method_requires_virtual_ = diagnostic().getCustomDiagID( + getErrorLevel(), kMethodRequiresVirtual); + diag_no_explicit_dtor_ = diagnostic().getCustomDiagID( + getErrorLevel(), kNoExplicitDtor); + diag_public_dtor_ = diagnostic().getCustomDiagID( + getErrorLevel(), kPublicDtor); + diag_protected_non_virtual_dtor_ = diagnostic().getCustomDiagID( + getErrorLevel(), kProtectedNonVirtualDtor); + + // Registers notes to make it easier to interpret warnings. + diag_note_inheritance_ = diagnostic().getCustomDiagID( + DiagnosticsEngine::Note, kNoteInheritance); + diag_note_implicit_dtor_ = diagnostic().getCustomDiagID( + DiagnosticsEngine::Note, kNoteImplicitDtor); + diag_note_public_dtor_ = diagnostic().getCustomDiagID( + DiagnosticsEngine::Note, kNotePublicDtor); + diag_note_protected_non_virtual_dtor_ = diagnostic().getCustomDiagID( + DiagnosticsEngine::Note, kNoteProtectedNonVirtualDtor); + } + + virtual void CheckChromeClass(SourceLocation record_location, + CXXRecordDecl* record) { + bool implementation_file = InImplementationFile(record_location); + + if (!implementation_file) { + // Only check for "heavy" constructors/destructors in header files; + // within implementation files, there is no performance cost. + CheckCtorDtorWeight(record_location, record); + } + + if (!implementation_file || check_virtuals_in_implementations_) { + bool warn_on_inline_bodies = !implementation_file; + + // Check that all virtual methods are marked accordingly with both + // virtual and OVERRIDE. + CheckVirtualMethods(record_location, record, warn_on_inline_bodies); + } + + CheckRefCountedDtors(record_location, record); + } + + private: + // The type of problematic ref-counting pattern that was encountered. + enum RefcountIssue { + None, + ImplicitDestructor, + PublicDestructor + }; + + bool check_base_classes_; + bool check_virtuals_in_implementations_; + + unsigned diag_method_requires_override_; + unsigned diag_method_requires_virtual_; + unsigned diag_no_explicit_dtor_; + unsigned diag_public_dtor_; + unsigned diag_protected_non_virtual_dtor_; + unsigned diag_note_inheritance_; + unsigned diag_note_implicit_dtor_; + unsigned diag_note_public_dtor_; + unsigned diag_note_protected_non_virtual_dtor_; + + // Prints errors if the constructor/destructor weight is too heavy. + void CheckCtorDtorWeight(SourceLocation record_location, + CXXRecordDecl* record) { + // We don't handle anonymous structs. If this record doesn't have a + // name, it's of the form: + // + // struct { + // ... + // } name_; + if (record->getIdentifier() == NULL) + return; + + // Count the number of templated base classes as a feature of whether the + // destructor can be inlined. + int templated_base_classes = 0; + for (CXXRecordDecl::base_class_const_iterator it = record->bases_begin(); + it != record->bases_end(); ++it) { + if (it->getTypeSourceInfo()->getTypeLoc().getTypeLocClass() == + TypeLoc::TemplateSpecialization) { + ++templated_base_classes; + } + } + + // Count the number of trivial and non-trivial member variables. + int trivial_member = 0; + int non_trivial_member = 0; + int templated_non_trivial_member = 0; + for (RecordDecl::field_iterator it = record->field_begin(); + it != record->field_end(); ++it) { + CountType(it->getType().getTypePtr(), + &trivial_member, + &non_trivial_member, + &templated_non_trivial_member); + } + + // Check to see if we need to ban inlined/synthesized constructors. Note + // that the cutoffs here are kind of arbitrary. Scores over 10 break. + int dtor_score = 0; + // Deriving from a templated base class shouldn't be enough to trigger + // the ctor warning, but if you do *anything* else, it should. + // + // TODO(erg): This is motivated by templated base classes that don't have + // any data members. Somehow detect when templated base classes have data + // members and treat them differently. + dtor_score += templated_base_classes * 9; + // Instantiating a template is an insta-hit. + dtor_score += templated_non_trivial_member * 10; + // The fourth normal class member should trigger the warning. + dtor_score += non_trivial_member * 3; + + int ctor_score = dtor_score; + // You should be able to have 9 ints before we warn you. + ctor_score += trivial_member; + + if (ctor_score >= 10) { + if (!record->hasUserDeclaredConstructor()) { + emitWarning(record_location, + "Complex class/struct needs an explicit out-of-line " + "constructor."); + } else { + // Iterate across all the constructors in this file and yell if we + // find one that tries to be inline. + for (CXXRecordDecl::ctor_iterator it = record->ctor_begin(); + it != record->ctor_end(); ++it) { + if (it->hasInlineBody()) { + if (it->isCopyConstructor() && + !record->hasUserDeclaredCopyConstructor()) { + emitWarning(record_location, + "Complex class/struct needs an explicit out-of-line " + "copy constructor."); + } else { + emitWarning(it->getInnerLocStart(), + "Complex constructor has an inlined body."); + } + } + } + } + } + + // The destructor side is equivalent except that we don't check for + // trivial members; 20 ints don't need a destructor. + if (dtor_score >= 10 && !record->hasTrivialDestructor()) { + if (!record->hasUserDeclaredDestructor()) { + emitWarning( + record_location, + "Complex class/struct needs an explicit out-of-line " + "destructor."); + } else if (CXXDestructorDecl* dtor = record->getDestructor()) { + if (dtor->hasInlineBody()) { + emitWarning(dtor->getInnerLocStart(), + "Complex destructor has an inline body."); + } + } + } + } + + void CheckVirtualMethod(const CXXMethodDecl* method, + bool warn_on_inline_bodies) { + if (!method->isVirtual()) + return; + + if (!method->isVirtualAsWritten()) { + SourceLocation loc = method->getTypeSpecStartLoc(); + if (isa(method)) + loc = method->getInnerLocStart(); + SourceManager& manager = instance().getSourceManager(); + FullSourceLoc full_loc(loc, manager); + SourceLocation spelling_loc = manager.getSpellingLoc(loc); + diagnostic().Report(full_loc, diag_method_requires_virtual_) + << FixItHint::CreateInsertion(spelling_loc, "virtual "); + } + + // Virtual methods should not have inline definitions beyond "{}". This + // only matters for header files. + if (warn_on_inline_bodies && method->hasBody() && + method->hasInlineBody()) { + if (CompoundStmt* cs = dyn_cast(method->getBody())) { + if (cs->size()) { + emitWarning( + cs->getLBracLoc(), + "virtual methods with non-empty bodies shouldn't be " + "declared inline."); + } + } + } + } + + bool InTestingNamespace(const Decl* record) { + return GetNamespace(record).find("testing") != std::string::npos; + } + + bool IsMethodInBannedOrTestingNamespace(const CXXMethodDecl* method) { + if (InBannedNamespace(method)) + return true; + for (CXXMethodDecl::method_iterator i = method->begin_overridden_methods(); + i != method->end_overridden_methods(); + ++i) { + const CXXMethodDecl* overridden = *i; + if (IsMethodInBannedOrTestingNamespace(overridden) || + InTestingNamespace(overridden)) { + return true; + } + } + + return false; + } + + void CheckOverriddenMethod(const CXXMethodDecl* method) { + if (!method->size_overridden_methods() || method->getAttr()) + return; + + if (isa(method) || method->isPure()) + return; + + if (IsMethodInBannedOrTestingNamespace(method)) + return; + + SourceManager& manager = instance().getSourceManager(); + SourceRange type_info_range = + method->getTypeSourceInfo()->getTypeLoc().getSourceRange(); + FullSourceLoc loc(type_info_range.getBegin(), manager); + + // Build the FixIt insertion point after the end of the method definition, + // including any const-qualifiers and attributes, and before the opening + // of the l-curly-brace (if inline) or the semi-color (if a declaration). + SourceLocation spelling_end = + manager.getSpellingLoc(type_info_range.getEnd()); + if (spelling_end.isValid()) { + SourceLocation token_end = Lexer::getLocForEndOfToken( + spelling_end, 0, manager, LangOptions()); + diagnostic().Report(token_end, diag_method_requires_override_) + << FixItHint::CreateInsertion(token_end, " OVERRIDE"); + } else { + diagnostic().Report(loc, diag_method_requires_override_); + } + } + + // Makes sure there is a "virtual" keyword on virtual methods. + // + // Gmock objects trigger these for each MOCK_BLAH() macro used. So we have a + // trick to get around that. If a class has member variables whose types are + // in the "testing" namespace (which is how gmock works behind the scenes), + // there's a really high chance we won't care about these errors + void CheckVirtualMethods(SourceLocation record_location, + CXXRecordDecl* record, + bool warn_on_inline_bodies) { + for (CXXRecordDecl::field_iterator it = record->field_begin(); + it != record->field_end(); ++it) { + CXXRecordDecl* record_type = + it->getTypeSourceInfo()->getTypeLoc().getTypePtr()-> + getAsCXXRecordDecl(); + if (record_type) { + if (InTestingNamespace(record_type)) { + return; + } + } + } + + for (CXXRecordDecl::method_iterator it = record->method_begin(); + it != record->method_end(); ++it) { + if (it->isCopyAssignmentOperator() || isa(*it)) { + // Ignore constructors and assignment operators. + } else if (isa(*it) && + !record->hasUserDeclaredDestructor()) { + // Ignore non-user-declared destructors. + } else { + CheckVirtualMethod(*it, warn_on_inline_bodies); + CheckOverriddenMethod(*it); + } + } + } + + void CountType(const Type* type, + int* trivial_member, + int* non_trivial_member, + int* templated_non_trivial_member) { + switch (type->getTypeClass()) { + case Type::Record: { + // Simplifying; the whole class isn't trivial if the dtor is, but + // we use this as a signal about complexity. + if (TypeHasNonTrivialDtor(type)) + (*trivial_member)++; + else + (*non_trivial_member)++; + break; + } + case Type::TemplateSpecialization: { + TemplateName name = + dyn_cast(type)->getTemplateName(); + bool whitelisted_template = false; + + // HACK: I'm at a loss about how to get the syntax checker to get + // whether a template is exterened or not. For the first pass here, + // just do retarded string comparisons. + if (TemplateDecl* decl = name.getAsTemplateDecl()) { + std::string base_name = decl->getNameAsString(); + if (base_name == "basic_string") + whitelisted_template = true; + } + + if (whitelisted_template) + (*non_trivial_member)++; + else + (*templated_non_trivial_member)++; + break; + } + case Type::Elaborated: { + CountType( + dyn_cast(type)->getNamedType().getTypePtr(), + trivial_member, non_trivial_member, templated_non_trivial_member); + break; + } + case Type::Typedef: { + while (const TypedefType* TT = dyn_cast(type)) { + type = TT->getDecl()->getUnderlyingType().getTypePtr(); + } + CountType(type, trivial_member, non_trivial_member, + templated_non_trivial_member); + break; + } + default: { + // Stupid assumption: anything we see that isn't the above is one of + // the 20 integer types. + (*trivial_member)++; + break; + } + } + } + + // Check |record| for issues that are problematic for ref-counted types. + // Note that |record| may not be a ref-counted type, but a base class for + // a type that is. + // If there are issues, update |loc| with the SourceLocation of the issue + // and returns appropriately, or returns None if there are no issues. + static RefcountIssue CheckRecordForRefcountIssue( + const CXXRecordDecl* record, + SourceLocation &loc) { + if (!record->hasUserDeclaredDestructor()) { + loc = record->getLocation(); + return ImplicitDestructor; + } + + if (CXXDestructorDecl* dtor = record->getDestructor()) { + if (dtor->getAccess() == AS_public) { + loc = dtor->getInnerLocStart(); + return PublicDestructor; + } + } + + return None; + } + + // Adds either a warning or error, based on the current handling of + // -Werror. + DiagnosticsEngine::Level getErrorLevel() { + return diagnostic().getWarningsAsErrors() ? + DiagnosticsEngine::Error : DiagnosticsEngine::Warning; + } + + // Returns true if |base| specifies one of the Chromium reference counted + // classes (base::RefCounted / base::RefCountedThreadSafe). + static bool IsRefCountedCallback(const CXXBaseSpecifier* base, + CXXBasePath& path, + void* user_data) { + FindBadConstructsConsumer* self = + static_cast(user_data); + + const TemplateSpecializationType* base_type = + dyn_cast( + UnwrapType(base->getType().getTypePtr())); + if (!base_type) { + // Base-most definition is not a template, so this cannot derive from + // base::RefCounted. However, it may still be possible to use with a + // scoped_refptr<> and support ref-counting, so this is not a perfect + // guarantee of safety. + return false; + } + + TemplateName name = base_type->getTemplateName(); + if (TemplateDecl* decl = name.getAsTemplateDecl()) { + std::string base_name = decl->getNameAsString(); + + // Check for both base::RefCounted and base::RefCountedThreadSafe. + if (base_name.compare(0, 10, "RefCounted") == 0 && + self->GetNamespace(decl) == "base") { + return true; + } + } + + return false; + } + + // Returns true if |base| specifies a class that has a public destructor, + // either explicitly or implicitly. + static bool HasPublicDtorCallback(const CXXBaseSpecifier* base, + CXXBasePath& path, + void* user_data) { + // Only examine paths that have public inheritance, as they are the + // only ones which will result in the destructor potentially being + // exposed. This check is largely redundant, as Chromium code should be + // exclusively using public inheritance. + if (path.Access != AS_public) + return false; + + CXXRecordDecl* record = dyn_cast( + base->getType()->getAs()->getDecl()); + SourceLocation unused; + return None != CheckRecordForRefcountIssue(record, unused); + } + + // Outputs a C++ inheritance chain as a diagnostic aid. + void PrintInheritanceChain(const CXXBasePath& path) { + for (CXXBasePath::const_iterator it = path.begin(); it != path.end(); + ++it) { + diagnostic().Report(it->Base->getLocStart(), diag_note_inheritance_) + << it->Class << it->Base->getType(); + } + } + + unsigned DiagnosticForIssue(RefcountIssue issue) { + switch (issue) { + case ImplicitDestructor: + return diag_no_explicit_dtor_; + case PublicDestructor: + return diag_public_dtor_; + case None: + assert(false && "Do not call DiagnosticForIssue with issue None"); + return 0; + } + } + + // Check |record| to determine if it has any problematic refcounting + // issues and, if so, print them as warnings/errors based on the current + // value of getErrorLevel(). + // + // If |record| is a C++ class, and if it inherits from one of the Chromium + // ref-counting classes (base::RefCounted / base::RefCountedThreadSafe), + // ensure that there are no public destructors in the class hierarchy. This + // is to guard against accidentally stack-allocating a RefCounted class or + // sticking it in a non-ref-counted container (like scoped_ptr<>). + void CheckRefCountedDtors(SourceLocation record_location, + CXXRecordDecl* record) { + // Skip anonymous structs. + if (record->getIdentifier() == NULL) + return; + + // Determine if the current type is even ref-counted. + CXXBasePaths refcounted_path; + if (!record->lookupInBases( + &FindBadConstructsConsumer::IsRefCountedCallback, this, + refcounted_path)) { + return; // Class does not derive from a ref-counted base class. + } + + // Easy check: Check to see if the current type is problematic. + SourceLocation loc; + RefcountIssue issue = CheckRecordForRefcountIssue(record, loc); + if (issue != None) { + diagnostic().Report(loc, DiagnosticForIssue(issue)); + PrintInheritanceChain(refcounted_path.front()); + return; + } + if (CXXDestructorDecl* dtor = + refcounted_path.begin()->back().Class->getDestructor()) { + if (dtor->getAccess() == AS_protected && + !dtor->isVirtual()) { + loc = dtor->getInnerLocStart(); + diagnostic().Report(loc, diag_protected_non_virtual_dtor_); + return; + } + } + + // Long check: Check all possible base classes for problematic + // destructors. This checks for situations involving multiple + // inheritance, where the ref-counted class may be implementing an + // interface that has a public or implicit destructor. + // + // struct SomeInterface { + // virtual void DoFoo(); + // }; + // + // struct RefCountedInterface + // : public base::RefCounted, + // public SomeInterface { + // private: + // friend class base::Refcounted; + // virtual ~RefCountedInterface() {} + // }; + // + // While RefCountedInterface is "safe", in that its destructor is + // private, it's possible to do the following "unsafe" code: + // scoped_refptr some_class( + // new RefCountedInterface); + // // Calls SomeInterface::~SomeInterface(), which is unsafe. + // delete static_cast(some_class.get()); + if (!check_base_classes_) + return; + + // Find all public destructors. This will record the class hierarchy + // that leads to the public destructor in |dtor_paths|. + CXXBasePaths dtor_paths; + if (!record->lookupInBases( + &FindBadConstructsConsumer::HasPublicDtorCallback, this, + dtor_paths)) { + return; + } + + for (CXXBasePaths::const_paths_iterator it = dtor_paths.begin(); + it != dtor_paths.end(); ++it) { + // The record with the problem will always be the last record + // in the path, since it is the record that stopped the search. + const CXXRecordDecl* problem_record = dyn_cast( + it->back().Base->getType()->getAs()->getDecl()); + + issue = CheckRecordForRefcountIssue(problem_record, loc); + + if (issue == ImplicitDestructor) { + diagnostic().Report(record_location, diag_no_explicit_dtor_); + PrintInheritanceChain(refcounted_path.front()); + diagnostic().Report(loc, diag_note_implicit_dtor_) << problem_record; + PrintInheritanceChain(*it); + } else if (issue == PublicDestructor) { + diagnostic().Report(record_location, diag_public_dtor_); + PrintInheritanceChain(refcounted_path.front()); + diagnostic().Report(loc, diag_note_public_dtor_); + PrintInheritanceChain(*it); + } + } + } +}; + +class FindBadConstructsAction : public PluginASTAction { + public: + FindBadConstructsAction() + : check_base_classes_(false), + check_virtuals_in_implementations_(true), + check_url_directory_(false) { + } + + protected: + // Overridden from PluginASTAction: + virtual ASTConsumer* CreateASTConsumer(CompilerInstance& instance, + llvm::StringRef ref) { + return new FindBadConstructsConsumer( + instance, check_base_classes_, check_virtuals_in_implementations_, + check_url_directory_); + } + + virtual bool ParseArgs(const CompilerInstance& instance, + const std::vector& args) { + bool parsed = true; + + for (size_t i = 0; i < args.size() && parsed; ++i) { + if (args[i] == "skip-virtuals-in-implementations") { + // TODO(rsleevi): Remove this once http://crbug.com/115047 is fixed. + check_virtuals_in_implementations_ = false; + } else if (args[i] == "check-base-classes") { + // TODO(rsleevi): Remove this once http://crbug.com/123295 is fixed. + check_base_classes_ = true; + } else if (args[i] == "check-url-directory") { + // TODO(tfarina): Remove this once http://crbug.com/229660 is fixed. + check_url_directory_ = true; + } else { + parsed = false; + llvm::errs() << "Unknown clang plugin argument: " << args[i] << "\n"; + } + } + + return parsed; + } + + private: + bool check_base_classes_; + bool check_virtuals_in_implementations_; + bool check_url_directory_; +}; + +} // namespace + +static FrontendPluginRegistry::Add +X("find-bad-constructs", "Finds bad C++ constructs"); diff --git a/tools/clang/plugins/Makefile b/tools/clang/plugins/Makefile new file mode 100644 index 0000000000..0cfec71159 --- /dev/null +++ b/tools/clang/plugins/Makefile @@ -0,0 +1,19 @@ +# This file requires the clang build system, at least for now. So to use this +# Makefile, you should execute the following commands to copy this directory +# into a clang checkout: +# +# cp -R third_party/llvm/tools/clang/tools/chrome-plugin +# cd third_party/llvm/tools/clang/tools/chrome-plugin +# make + +CLANG_LEVEL := ../.. +LIBRARYNAME = FindBadConstructs + +LINK_LIBS_IN_SHARED = 0 +SHARED_LIBRARY = 1 + +include $(CLANG_LEVEL)/Makefile + +ifeq ($(OS),Darwin) + LDFLAGS=-Wl,-undefined,dynamic_lookup +endif diff --git a/tools/clang/plugins/OWNERS b/tools/clang/plugins/OWNERS new file mode 100644 index 0000000000..4733a4f06b --- /dev/null +++ b/tools/clang/plugins/OWNERS @@ -0,0 +1 @@ +erg@chromium.org diff --git a/tools/clang/plugins/README.chromium b/tools/clang/plugins/README.chromium new file mode 100644 index 0000000000..a2ce0ff557 --- /dev/null +++ b/tools/clang/plugins/README.chromium @@ -0,0 +1,4 @@ +Documentation for this code is: + +- http://code.google.com/p/chromium/wiki/Clang +- http://code.google.com/p/chromium/wiki/WritingClangPlugins diff --git a/tools/clang/plugins/tests/base_refcounted.cpp b/tools/clang/plugins/tests/base_refcounted.cpp new file mode 100644 index 0000000000..698bf7b952 --- /dev/null +++ b/tools/clang/plugins/tests/base_refcounted.cpp @@ -0,0 +1,79 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base_refcounted.h" + +#include + +namespace { + +// Unsafe; should error. +class AnonymousDerivedProtectedToPublicInImpl + : public ProtectedRefCountedVirtualDtorInHeader { + public: + AnonymousDerivedProtectedToPublicInImpl() {} + virtual ~AnonymousDerivedProtectedToPublicInImpl() {} +}; + +// Unsafe; but we should only warn on the base class. +class AnonymousDerivedProtectedOnDerived + : public ProtectedRefCountedDtorInHeader { + protected: + ~AnonymousDerivedProtectedOnDerived() {} +}; + +} // namespace + +// Unsafe; should error. +class PublicRefCountedDtorInImpl + : public base::RefCounted { + public: + PublicRefCountedDtorInImpl() {} + ~PublicRefCountedDtorInImpl() {} + + private: + friend class base::RefCounted; +}; + +class Foo { + public: + class BarInterface { + protected: + virtual ~BarInterface() {} + }; + + typedef base::RefCounted RefCountedBar; + typedef RefCountedBar AnotherTypedef; +}; + +class Baz { + public: + typedef typename Foo::AnotherTypedef MyLocalTypedef; +}; + +// Unsafe; should error. +class UnsafeTypedefChainInImpl : public Baz::MyLocalTypedef { + public: + UnsafeTypedefChainInImpl() {} + ~UnsafeTypedefChainInImpl() {} +}; + +int main() { + PublicRefCountedDtorInHeader bad; + PublicRefCountedDtorInImpl also_bad; + + ProtectedRefCountedDtorInHeader* even_badder = NULL; + PrivateRefCountedDtorInHeader* private_ok = NULL; + + DerivedProtectedToPublicInHeader still_bad; + PublicRefCountedThreadSafeDtorInHeader another_bad_variation; + AnonymousDerivedProtectedToPublicInImpl and_this_is_bad_too; + ImplicitDerivedProtectedToPublicInHeader bad_yet_again; + UnsafeTypedefChainInImpl and_again_this_is_bad; + + WebKitPublicDtorInHeader ignored; + WebKitDerivedPublicDtorInHeader still_ignored; + + return 0; +} diff --git a/tools/clang/plugins/tests/base_refcounted.flags b/tools/clang/plugins/tests/base_refcounted.flags new file mode 100644 index 0000000000..5cf5d10b2e --- /dev/null +++ b/tools/clang/plugins/tests/base_refcounted.flags @@ -0,0 +1 @@ +-Xclang -plugin-arg-find-bad-constructs -Xclang check-base-classes diff --git a/tools/clang/plugins/tests/base_refcounted.h b/tools/clang/plugins/tests/base_refcounted.h new file mode 100644 index 0000000000..4b4077c5a4 --- /dev/null +++ b/tools/clang/plugins/tests/base_refcounted.h @@ -0,0 +1,223 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_REFCOUNTED_H_ +#define BASE_REFCOUNTED_H_ + +namespace base { + +template +class RefCounted { + public: + RefCounted() {} + protected: + ~RefCounted() {} +}; + +template +class RefCountedThreadSafe { + public: + RefCountedThreadSafe() {} + protected: + ~RefCountedThreadSafe() {} +}; + +} // namespace base + +// Ignore classes whose inheritance tree ends in WebKit's RefCounted base +// class. Though prone to error, this pattern is very prevalent in WebKit +// code, so do not issue any warnings. +namespace WebKit { + +template +class RefCounted { + public: + RefCounted() {} + ~RefCounted() {} +}; + +} // namespace WebKit + +// Unsafe; should error. +class PublicRefCountedDtorInHeader + : public base::RefCounted { + public: + PublicRefCountedDtorInHeader() {} + ~PublicRefCountedDtorInHeader() {} + + private: + friend class base::RefCounted; +}; + +// Unsafe; should error. +class PublicRefCountedThreadSafeDtorInHeader + : public base::RefCountedThreadSafe< + PublicRefCountedThreadSafeDtorInHeader> { + public: + PublicRefCountedThreadSafeDtorInHeader() {} + ~PublicRefCountedThreadSafeDtorInHeader() {} + + private: + friend class base::RefCountedThreadSafe< + PublicRefCountedThreadSafeDtorInHeader>; +}; + +// Unsafe; should error. +class ProtectedRefCountedDtorInHeader + : public base::RefCounted { + public: + ProtectedRefCountedDtorInHeader() {} + + protected: + ~ProtectedRefCountedDtorInHeader() {} + + private: + friend class base::RefCounted; +}; + +// Safe; should not have errors +class ProtectedRefCountedVirtualDtorInHeader + : public base::RefCounted { + public: + ProtectedRefCountedVirtualDtorInHeader() {} + + protected: + virtual ~ProtectedRefCountedVirtualDtorInHeader() {} + + private: + friend class base::RefCounted; +}; + + +// Safe; should not have errors. +class PrivateRefCountedDtorInHeader + : public base::RefCounted { + public: + PrivateRefCountedDtorInHeader() {} + + private: + ~PrivateRefCountedDtorInHeader() {} + friend class base::RefCounted; +}; + +// Unsafe; A grandchild class ends up exposing their parent and grandparent's +// destructors. +class DerivedProtectedToPublicInHeader + : public ProtectedRefCountedVirtualDtorInHeader { + public: + DerivedProtectedToPublicInHeader() {} + virtual ~DerivedProtectedToPublicInHeader() {} +}; + +// Unsafe; A grandchild ends up implicitly exposing their parent and +// grantparent's destructors. +class ImplicitDerivedProtectedToPublicInHeader + : public ProtectedRefCountedVirtualDtorInHeader { + public: + ImplicitDerivedProtectedToPublicInHeader() {} +}; + +// Unsafe-but-ignored; should not have errors. +class WebKitPublicDtorInHeader + : public WebKit::RefCounted { + public: + WebKitPublicDtorInHeader() {} + ~WebKitPublicDtorInHeader() {} +}; + +// Unsafe-but-ignored; should not have errors. +class WebKitDerivedPublicDtorInHeader + : public WebKitPublicDtorInHeader { + public: + WebKitDerivedPublicDtorInHeader() {} + ~WebKitDerivedPublicDtorInHeader() {} +}; + +class APublicInterface { + public: + virtual ~APublicInterface() {} + virtual void DoFoo() = 0; +}; + +// Unsafe. "ImplementsAPublicInterface* foo" can be deleted via +// "delete (APublicInterface*)foo;". +class ImplementsAPublicInterface + : public APublicInterface, + public base::RefCounted { + public: + virtual void DoFoo() override {} + + protected: + virtual ~ImplementsAPublicInterface() {} + + private: + friend class base::RefCounted; +}; + +class AnImplicitInterface { + public: + virtual void DoBar() {} +}; + +// Unsafe. +class ImplementsAnImplicitInterface + : public AnImplicitInterface, + public base::RefCounted { + public: + virtual void DoBar() override {} + + private: + friend class base::RefCounted; + ~ImplementsAnImplicitInterface() {} +}; + +// Safe. Private inheritance does not expose the base destructor. +class PrivatelyImplementsAPublicInterface + : private APublicInterface, + public base::RefCounted { + public: + virtual void DoFoo() override {} + + private: + friend class base::RefCounted; + virtual ~PrivatelyImplementsAPublicInterface() {} +}; + +// Unsafe. +class BaseInterface { + public: + virtual ~BaseInterface() {} + virtual void DoFoo() {} +}; +class DerivedInterface : public BaseInterface { + protected: + virtual ~DerivedInterface() {} +}; +class SomeOtherInterface { + public: + virtual ~SomeOtherInterface() {} + virtual void DoBar() {} +}; +class RefcountedType : public base::RefCounted { + protected: + ~RefcountedType() {} + private: + friend class base::RefCounted; +}; +class UnsafeInheritanceChain + : public DerivedInterface, + public SomeOtherInterface, + public RefcountedType { + public: + // DerivedInterface + virtual void DoFoo() override {} + + // SomeOtherInterface + virtual void DoBar() override {} + + protected: + virtual ~UnsafeInheritanceChain() {} +}; + +#endif // BASE_REFCOUNTED_H_ diff --git a/tools/clang/plugins/tests/base_refcounted.txt b/tools/clang/plugins/tests/base_refcounted.txt new file mode 100644 index 0000000000..20c0cdf42f --- /dev/null +++ b/tools/clang/plugins/tests/base_refcounted.txt @@ -0,0 +1,87 @@ +In file included from base_refcounted.cpp:5: +./base_refcounted.h:47:3: warning: [chromium-style] Classes that are ref-counted should have destructors that are declared protected or private. + ~PublicRefCountedDtorInHeader() {} + ^ +./base_refcounted.h:44:7: note: [chromium-style] 'PublicRefCountedDtorInHeader' inherits from 'base::RefCounted' here + : public base::RefCounted { + ^ +./base_refcounted.h:59:3: warning: [chromium-style] Classes that are ref-counted should have destructors that are declared protected or private. + ~PublicRefCountedThreadSafeDtorInHeader() {} + ^ +./base_refcounted.h:55:7: note: [chromium-style] 'PublicRefCountedThreadSafeDtorInHeader' inherits from 'base::RefCountedThreadSafe' here + : public base::RefCountedThreadSafe< + ^ +./base_refcounted.h:73:3: warning: [chromium-style] Classes that are ref-counted and have non-private destructors should declare their destructor virtual. + ~ProtectedRefCountedDtorInHeader() {} + ^ +./base_refcounted.h:110:3: warning: [chromium-style] Classes that are ref-counted should have destructors that are declared protected or private. + virtual ~DerivedProtectedToPublicInHeader() {} + ^ +./base_refcounted.h:107:7: note: [chromium-style] 'DerivedProtectedToPublicInHeader' inherits from 'ProtectedRefCountedVirtualDtorInHeader' here + : public ProtectedRefCountedVirtualDtorInHeader { + ^ +./base_refcounted.h:81:7: note: [chromium-style] 'ProtectedRefCountedVirtualDtorInHeader' inherits from 'base::RefCounted' here + : public base::RefCounted { + ^ +./base_refcounted.h:115:7: warning: [chromium-style] Classes that are ref-counted should have explicit destructors that are declared protected or private. +class ImplicitDerivedProtectedToPublicInHeader + ^ +./base_refcounted.h:116:7: note: [chromium-style] 'ImplicitDerivedProtectedToPublicInHeader' inherits from 'ProtectedRefCountedVirtualDtorInHeader' here + : public ProtectedRefCountedVirtualDtorInHeader { + ^ +./base_refcounted.h:81:7: note: [chromium-style] 'ProtectedRefCountedVirtualDtorInHeader' inherits from 'base::RefCounted' here + : public base::RefCounted { + ^ +./base_refcounted.h:145:1: warning: [chromium-style] Classes that are ref-counted should have destructors that are declared protected or private. +class ImplementsAPublicInterface +^ +./base_refcounted.h:147:7: note: [chromium-style] 'ImplementsAPublicInterface' inherits from 'base::RefCounted' here + public base::RefCounted { + ^ +./base_refcounted.h:139:3: note: [chromium-style] Public destructor declared here + virtual ~APublicInterface() {} + ^ +./base_refcounted.h:146:7: note: [chromium-style] 'ImplementsAPublicInterface' inherits from 'APublicInterface' here + : public APublicInterface, + ^ +./base_refcounted.h:164:1: warning: [chromium-style] Classes that are ref-counted should have explicit destructors that are declared protected or private. +class ImplementsAnImplicitInterface +^ +./base_refcounted.h:166:7: note: [chromium-style] 'ImplementsAnImplicitInterface' inherits from 'base::RefCounted' here + public base::RefCounted { + ^ +./base_refcounted.h:158:7: note: [chromium-style] No explicit destructor for 'AnImplicitInterface' defined +class AnImplicitInterface { + ^ +./base_refcounted.h:165:7: note: [chromium-style] 'ImplementsAnImplicitInterface' inherits from 'AnImplicitInterface' here + : public AnImplicitInterface, + ^ +./base_refcounted.h:204:3: warning: [chromium-style] Classes that are ref-counted and have non-private destructors should declare their destructor virtual. + ~RefcountedType() {} + ^ +./base_refcounted.h:204:3: warning: [chromium-style] Classes that are ref-counted and have non-private destructors should declare their destructor virtual. +base_refcounted.cpp:16:3: warning: [chromium-style] Classes that are ref-counted should have destructors that are declared protected or private. + virtual ~AnonymousDerivedProtectedToPublicInImpl() {} + ^ +base_refcounted.cpp:13:7: note: [chromium-style] 'AnonymousDerivedProtectedToPublicInImpl' inherits from 'ProtectedRefCountedVirtualDtorInHeader' here + : public ProtectedRefCountedVirtualDtorInHeader { + ^ +./base_refcounted.h:81:7: note: [chromium-style] 'ProtectedRefCountedVirtualDtorInHeader' inherits from 'base::RefCounted' here + : public base::RefCounted { + ^ +./base_refcounted.h:73:3: warning: [chromium-style] Classes that are ref-counted and have non-private destructors should declare their destructor virtual. + ~ProtectedRefCountedDtorInHeader() {} + ^ +base_refcounted.cpp:33:3: warning: [chromium-style] Classes that are ref-counted should have destructors that are declared protected or private. + ~PublicRefCountedDtorInImpl() {} + ^ +base_refcounted.cpp:30:7: note: [chromium-style] 'PublicRefCountedDtorInImpl' inherits from 'base::RefCounted' here + : public base::RefCounted { + ^ +base_refcounted.cpp:59:3: warning: [chromium-style] Classes that are ref-counted should have destructors that are declared protected or private. + ~UnsafeTypedefChainInImpl() {} + ^ +base_refcounted.cpp:56:34: note: [chromium-style] 'UnsafeTypedefChainInImpl' inherits from 'Baz::MyLocalTypedef' (aka 'RefCounted') here +class UnsafeTypedefChainInImpl : public Baz::MyLocalTypedef { + ^ +13 warnings generated. diff --git a/tools/clang/plugins/tests/inline_copy_ctor.cpp b/tools/clang/plugins/tests/inline_copy_ctor.cpp new file mode 100644 index 0000000000..dcd90020c5 --- /dev/null +++ b/tools/clang/plugins/tests/inline_copy_ctor.cpp @@ -0,0 +1,5 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "inline_copy_ctor.h" diff --git a/tools/clang/plugins/tests/inline_copy_ctor.h b/tools/clang/plugins/tests/inline_copy_ctor.h new file mode 100644 index 0000000000..619a18392b --- /dev/null +++ b/tools/clang/plugins/tests/inline_copy_ctor.h @@ -0,0 +1,12 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +struct C { + C(); + ~C(); + + static C foo() { return C(); } + + int a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p , q, r, s, t, u, v, w, x; +}; diff --git a/tools/clang/plugins/tests/inline_copy_ctor.txt b/tools/clang/plugins/tests/inline_copy_ctor.txt new file mode 100644 index 0000000000..bc4bd8911e --- /dev/null +++ b/tools/clang/plugins/tests/inline_copy_ctor.txt @@ -0,0 +1,5 @@ +In file included from inline_copy_ctor.cpp:5: +./inline_copy_ctor.h:5:1: warning: [chromium-style] Complex class/struct needs an explicit out-of-line copy constructor. +struct C { +^ +1 warning generated. diff --git a/tools/clang/plugins/tests/inline_ctor.cpp b/tools/clang/plugins/tests/inline_ctor.cpp new file mode 100644 index 0000000000..6a751fb405 --- /dev/null +++ b/tools/clang/plugins/tests/inline_ctor.cpp @@ -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 "inline_ctor.h" + +#include +#include + +// We don't warn on classes that are in CPP files. +class InlineInCPPOK { + public: + InlineInCPPOK() {} + ~InlineInCPPOK() {} + + private: + std::vector one_; + std::vector two_; +}; + +int main() { + InlineInCPPOK one; + InlineCtorsArentOKInHeader two; + return 0; +} diff --git a/tools/clang/plugins/tests/inline_ctor.h b/tools/clang/plugins/tests/inline_ctor.h new file mode 100644 index 0000000000..d053b2f57d --- /dev/null +++ b/tools/clang/plugins/tests/inline_ctor.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 INLINE_CTOR_H_ +#define INLINE_CTOR_H_ + +#include +#include + +class InlineCtorsArentOKInHeader { + public: + InlineCtorsArentOKInHeader() {} + ~InlineCtorsArentOKInHeader() {} + + private: + std::vector one_; + std::vector two_; +}; + +#endif // INLINE_CTOR_H_ diff --git a/tools/clang/plugins/tests/inline_ctor.txt b/tools/clang/plugins/tests/inline_ctor.txt new file mode 100644 index 0000000000..caa0cb4e3b --- /dev/null +++ b/tools/clang/plugins/tests/inline_ctor.txt @@ -0,0 +1,8 @@ +In file included from inline_ctor.cpp:5: +./inline_ctor.h:13:3: warning: [chromium-style] Complex constructor has an inlined body. + InlineCtorsArentOKInHeader() {} + ^ +./inline_ctor.h:14:3: warning: [chromium-style] Complex destructor has an inline body. + ~InlineCtorsArentOKInHeader() {} + ^ +2 warnings generated. diff --git a/tools/clang/plugins/tests/missing_ctor.cpp b/tools/clang/plugins/tests/missing_ctor.cpp new file mode 100644 index 0000000000..8ee2fb2ac8 --- /dev/null +++ b/tools/clang/plugins/tests/missing_ctor.cpp @@ -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 "missing_ctor.h" + +#include +#include + +// We don't warn on classes that use default ctors in cpp files. +class MissingInCPPOK { + public: + + private: + std::vector one_; + std::vector two_; +}; + +int main() { + MissingInCPPOK one; + MissingCtorsArentOKInHeader two; + return 0; +} diff --git a/tools/clang/plugins/tests/missing_ctor.h b/tools/clang/plugins/tests/missing_ctor.h new file mode 100644 index 0000000000..1050457a1a --- /dev/null +++ b/tools/clang/plugins/tests/missing_ctor.h @@ -0,0 +1,19 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MISSING_CTOR_H_ +#define MISSING_CTOR_H_ + +#include +#include + +class MissingCtorsArentOKInHeader { + public: + + private: + std::vector one_; + std::vector two_; +}; + +#endif // MISSING_CTOR_H_ diff --git a/tools/clang/plugins/tests/missing_ctor.txt b/tools/clang/plugins/tests/missing_ctor.txt new file mode 100644 index 0000000000..301449c4ac --- /dev/null +++ b/tools/clang/plugins/tests/missing_ctor.txt @@ -0,0 +1,6 @@ +In file included from missing_ctor.cpp:5: +./missing_ctor.h:11:1: warning: [chromium-style] Complex class/struct needs an explicit out-of-line constructor. +class MissingCtorsArentOKInHeader { +^ +./missing_ctor.h:11:1: warning: [chromium-style] Complex class/struct needs an explicit out-of-line destructor. +2 warnings generated. diff --git a/tools/clang/plugins/tests/nested_class_inline_ctor.cpp b/tools/clang/plugins/tests/nested_class_inline_ctor.cpp new file mode 100644 index 0000000000..aa90a95eb3 --- /dev/null +++ b/tools/clang/plugins/tests/nested_class_inline_ctor.cpp @@ -0,0 +1,5 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "nested_class_inline_ctor.h" diff --git a/tools/clang/plugins/tests/nested_class_inline_ctor.h b/tools/clang/plugins/tests/nested_class_inline_ctor.h new file mode 100644 index 0000000000..01cfea9232 --- /dev/null +++ b/tools/clang/plugins/tests/nested_class_inline_ctor.h @@ -0,0 +1,22 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NESTED_CLASS_INLINE_CTOR_H_ +#define NESTED_CLASS_INLINE_CTOR_H_ + +#include +#include + +// See crbug.com/136863. + +class Foo { + class Bar { + Bar() {} + ~Bar() {} + + std::vector a; + }; +}; + +#endif // NESTED_CLASS_INLINE_CTOR_H_ diff --git a/tools/clang/plugins/tests/nested_class_inline_ctor.txt b/tools/clang/plugins/tests/nested_class_inline_ctor.txt new file mode 100644 index 0000000000..39bd6e1dce --- /dev/null +++ b/tools/clang/plugins/tests/nested_class_inline_ctor.txt @@ -0,0 +1,8 @@ +In file included from nested_class_inline_ctor.cpp:5: +./nested_class_inline_ctor.h:15:5: warning: [chromium-style] Complex constructor has an inlined body. + Bar() {} + ^ +./nested_class_inline_ctor.h:16:5: warning: [chromium-style] Complex destructor has an inline body. + ~Bar() {} + ^ +2 warnings generated. diff --git a/tools/clang/plugins/tests/overridden_methods.cpp b/tools/clang/plugins/tests/overridden_methods.cpp new file mode 100644 index 0000000000..648b907ca3 --- /dev/null +++ b/tools/clang/plugins/tests/overridden_methods.cpp @@ -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 "overridden_methods.h" + +// Fill in the implementations +void DerivedClass::SomeMethod() {} +void DerivedClass::SomeOtherMethod() {} +void DerivedClass::WebKitModifiedSomething() {} + +class ImplementationInterimClass : public BaseClass { + public: + // Should not warn about pure virtual methods. + virtual void SomeMethod() = 0; +}; + +class ImplementationDerivedClass : public ImplementationInterimClass, + public webkit_glue::WebKitObserverImpl { + public: + // Should not warn about destructors. + virtual ~ImplementationDerivedClass() {} + // Should warn. + virtual void SomeMethod(); + // Should not warn if marked as override. + virtual void SomeOtherMethod() override; + // Should not warn for inline implementations in implementation files. + virtual void SomeInlineMethod() {} + // Should not warn if overriding a method whose origin is WebKit. + virtual void WebKitModifiedSomething(); + // Should warn with the insertion point after the const. + virtual void SomeConstMethod() const {} + // Should warn with the insertion point after the throw spec. + virtual void SomeMethodWithExceptionSpec() throw() {} + // Should warn with the insertion point after both the const and the throw + // specifiers. + virtual void SomeConstMethodWithExceptionSpec() const throw(int) {} + // Should warn even if overridden method isn't pure. + virtual void SomeNonPureBaseMethod() {} + // Should warn and place correctly even when there is a comment. + virtual void SomeMethodWithComment(); // This is a comment. + // Should warn and place correctly even if there is a comment and body. + virtual void SomeMethodWithCommentAndBody() {} // This is a comment. +}; + +int main() { + DerivedClass something; + ImplementationDerivedClass something_else; +} diff --git a/tools/clang/plugins/tests/overridden_methods.h b/tools/clang/plugins/tests/overridden_methods.h new file mode 100644 index 0000000000..cec9c0533d --- /dev/null +++ b/tools/clang/plugins/tests/overridden_methods.h @@ -0,0 +1,70 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef OVERRIDDEN_METHODS_H_ +#define OVERRIDDEN_METHODS_H_ + +// Should warn about overriding of methods. +class BaseClass { + public: + virtual ~BaseClass() {} + virtual void SomeMethod() = 0; + virtual void SomeOtherMethod() = 0; + virtual void SomeInlineMethod() = 0; + virtual void SomeConstMethod() const = 0; + virtual void SomeMethodWithExceptionSpec() throw() = 0; + virtual void SomeConstMethodWithExceptionSpec() const throw(int) = 0; + virtual void SomeNonPureBaseMethod() {} + virtual void SomeMethodWithComment() = 0; + virtual void SomeMethodWithCommentAndBody() = 0; +}; + +class InterimClass : public BaseClass { + // Should not warn about pure virtual methods. + virtual void SomeMethod() = 0; +}; + +namespace WebKit { +class WebKitObserver { + public: + virtual void WebKitModifiedSomething() {}; +}; +} // namespace WebKit + +namespace webkit_glue { +class WebKitObserverImpl : WebKit::WebKitObserver { + public: + virtual void WebKitModifiedSomething() {}; +}; +} // namespace webkit_glue + +class DerivedClass : public InterimClass, + public webkit_glue::WebKitObserverImpl { + public: + // Should not warn about destructors. + virtual ~DerivedClass() {} + // Should warn. + virtual void SomeMethod(); + // Should not warn if marked as override. + virtual void SomeOtherMethod() override; + // Should warn for inline implementations. + virtual void SomeInlineMethod() {} + // Should not warn if overriding a method whose origin is WebKit. + virtual void WebKitModifiedSomething(); + // Should warn with the insertion point after the const. + virtual void SomeConstMethod() const {} + // Should warn with the insertion point after the throw spec. + virtual void SomeMethodWithExceptionSpec() throw() {} + // Should warn with the insertion point after both the const and the throw + // specifiers. + virtual void SomeConstMethodWithExceptionSpec() const throw(int) {} + // Should warn even if overridden method isn't pure. + virtual void SomeNonPureBaseMethod() {} + // Should warn and place correctly even when there is a comment. + virtual void SomeMethodWithComment(); // This is a comment. + // Should warn and place correctly even if there is a comment and body. + virtual void SomeMethodWithCommentAndBody() {} // This is a comment. +}; + +#endif // OVERRIDDEN_METHODS_H_ diff --git a/tools/clang/plugins/tests/overridden_methods.txt b/tools/clang/plugins/tests/overridden_methods.txt new file mode 100644 index 0000000000..67349d23a8 --- /dev/null +++ b/tools/clang/plugins/tests/overridden_methods.txt @@ -0,0 +1,66 @@ +In file included from overridden_methods.cpp:5: +./overridden_methods.h:48:28: warning: [chromium-style] Overriding method must be marked with OVERRIDE. + virtual void SomeMethod(); + ^ + OVERRIDE +./overridden_methods.h:52:34: warning: [chromium-style] Overriding method must be marked with OVERRIDE. + virtual void SomeInlineMethod() {} + ^ + OVERRIDE +./overridden_methods.h:56:39: warning: [chromium-style] Overriding method must be marked with OVERRIDE. + virtual void SomeConstMethod() const {} + ^ + OVERRIDE +./overridden_methods.h:58:53: warning: [chromium-style] Overriding method must be marked with OVERRIDE. + virtual void SomeMethodWithExceptionSpec() throw() {} + ^ + OVERRIDE +./overridden_methods.h:61:67: warning: [chromium-style] Overriding method must be marked with OVERRIDE. + virtual void SomeConstMethodWithExceptionSpec() const throw(int) {} + ^ + OVERRIDE +./overridden_methods.h:63:39: warning: [chromium-style] Overriding method must be marked with OVERRIDE. + virtual void SomeNonPureBaseMethod() {} + ^ + OVERRIDE +./overridden_methods.h:65:39: warning: [chromium-style] Overriding method must be marked with OVERRIDE. + virtual void SomeMethodWithComment(); // This is a comment. + ^ + OVERRIDE +./overridden_methods.h:67:46: warning: [chromium-style] Overriding method must be marked with OVERRIDE. + virtual void SomeMethodWithCommentAndBody() {} // This is a comment. + ^ + OVERRIDE +overridden_methods.cpp:24:28: warning: [chromium-style] Overriding method must be marked with OVERRIDE. + virtual void SomeMethod(); + ^ + OVERRIDE +overridden_methods.cpp:28:34: warning: [chromium-style] Overriding method must be marked with OVERRIDE. + virtual void SomeInlineMethod() {} + ^ + OVERRIDE +overridden_methods.cpp:32:39: warning: [chromium-style] Overriding method must be marked with OVERRIDE. + virtual void SomeConstMethod() const {} + ^ + OVERRIDE +overridden_methods.cpp:34:53: warning: [chromium-style] Overriding method must be marked with OVERRIDE. + virtual void SomeMethodWithExceptionSpec() throw() {} + ^ + OVERRIDE +overridden_methods.cpp:37:67: warning: [chromium-style] Overriding method must be marked with OVERRIDE. + virtual void SomeConstMethodWithExceptionSpec() const throw(int) {} + ^ + OVERRIDE +overridden_methods.cpp:39:39: warning: [chromium-style] Overriding method must be marked with OVERRIDE. + virtual void SomeNonPureBaseMethod() {} + ^ + OVERRIDE +overridden_methods.cpp:41:39: warning: [chromium-style] Overriding method must be marked with OVERRIDE. + virtual void SomeMethodWithComment(); // This is a comment. + ^ + OVERRIDE +overridden_methods.cpp:43:46: warning: [chromium-style] Overriding method must be marked with OVERRIDE. + virtual void SomeMethodWithCommentAndBody() {} // This is a comment. + ^ + OVERRIDE +16 warnings generated. diff --git a/tools/clang/plugins/tests/test.sh b/tools/clang/plugins/tests/test.sh new file mode 100755 index 0000000000..cf3acc3afa --- /dev/null +++ b/tools/clang/plugins/tests/test.sh @@ -0,0 +1,76 @@ +#!/bin/bash +# +# Copyright (c) 2011 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# +# Hacky, primitive testing: This runs the style plugin for a set of input files +# and compares the output with golden result files. + +E_BADARGS=65 +E_FAILEDTEST=1 + +failed_any_test= + +# Prints usage information. +usage() { + echo "Usage: $(basename "${0}")" \ + "" + echo "" + echo " Runs all the libFindBadConstructs unit tests" + echo "" +} + +# Runs a single test case. +do_testcase() { + local flags="" + if [ -e "${3}" ]; then + flags="$(cat "${3}")" + fi + local output="$("${CLANG_DIR}"/bin/clang -c -Wno-c++11-extensions \ + -Xclang -load -Xclang "${CLANG_DIR}"/lib/libFindBadConstructs.${LIB} \ + -Xclang -add-plugin -Xclang find-bad-constructs ${flags} ${1} 2>&1)" + local diffout="$(echo "${output}" | diff - "${2}")" + if [ "${diffout}" = "" ]; then + echo "PASS: ${1}" + else + failed_any_test=yes + echo "FAIL: ${1}" + echo "Output of compiler:" + echo "${output}" + echo "Expected output:" + cat "${2}" + echo + fi +} + +# Validate input to the script. +if [[ -z "${1}" ]]; then + usage + exit ${E_BADARGS} +elif [[ ! -d "${1}" ]]; then + echo "${1} is not a directory." + usage + exit ${E_BADARGS} +else + export CLANG_DIR="${PWD}/${1}" + echo "Using clang directory ${CLANG_DIR}..." + + # The golden files assume that the cwd is this directory. To make the script + # work no matter what the cwd is, explicitly cd to there. + cd "$(dirname "${0}")" + + if [ "$(uname -s)" = "Linux" ]; then + export LIB=so + elif [ "$(uname -s)" = "Darwin" ]; then + export LIB=dylib + fi +fi + +for input in *.cpp; do + do_testcase "${input}" "${input%cpp}txt" "${input%cpp}flags" +done + +if [[ "${failed_any_test}" ]]; then + exit ${E_FAILEDTEST} +fi diff --git a/tools/clang/plugins/tests/virtual_methods.cpp b/tools/clang/plugins/tests/virtual_methods.cpp new file mode 100644 index 0000000000..a07cbe4875 --- /dev/null +++ b/tools/clang/plugins/tests/virtual_methods.cpp @@ -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 "virtual_methods.h" + +// Shouldn't warn about method usage in the implementation file. +class VirtualMethodsInImplementation { + public: + virtual void MethodIsAbstract() = 0; + virtual void MethodHasNoArguments(); + virtual void MethodHasEmptyDefaultImpl() {} + virtual bool ComplainAboutThis() { return true; } +}; + +// Stubs to fill in the abstract method +class ConcreteVirtualMethodsInHeaders : public VirtualMethodsInHeaders { + public: + virtual void MethodIsAbstract() override {} +}; + +class ConcreteVirtualMethodsInImplementation + : public VirtualMethodsInImplementation { + public: + virtual void MethodIsAbstract() override {} +}; + +// Fill in the implementations +void VirtualMethodsInHeaders::MethodHasNoArguments() {} +void WarnOnMissingVirtual::MethodHasNoArguments() {} +void VirtualMethodsInImplementation::MethodHasNoArguments() {} + +int main() { + ConcreteVirtualMethodsInHeaders one; + ConcreteVirtualMethodsInImplementation two; +} diff --git a/tools/clang/plugins/tests/virtual_methods.h b/tools/clang/plugins/tests/virtual_methods.h new file mode 100644 index 0000000000..d9fbf96ed3 --- /dev/null +++ b/tools/clang/plugins/tests/virtual_methods.h @@ -0,0 +1,39 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VIRTUAL_METHODS_H_ +#define VIRTUAL_METHODS_H_ + +// Should warn about virtual method usage. +class VirtualMethodsInHeaders { + public: + // Don't complain about these. + virtual void MethodIsAbstract() = 0; + virtual void MethodHasNoArguments(); + virtual void MethodHasEmptyDefaultImpl() {} + + // But complain about this: + virtual bool ComplainAboutThis() { return true; } +}; + +// Complain on missing 'virtual' keyword in overrides. +class WarnOnMissingVirtual : public VirtualMethodsInHeaders { + public: + void MethodHasNoArguments() override; +}; + +// Don't complain about things in a 'testing' namespace. +namespace testing { +struct TestStruct {}; +} // namespace testing + +class VirtualMethodsInHeadersTesting : public VirtualMethodsInHeaders { + public: + // Don't complain about no virtual testing methods. + void MethodHasNoArguments(); + private: + testing::TestStruct tester_; +}; + +#endif // VIRTUAL_METHODS_H_ diff --git a/tools/clang/plugins/tests/virtual_methods.txt b/tools/clang/plugins/tests/virtual_methods.txt new file mode 100644 index 0000000000..3e484cd5fe --- /dev/null +++ b/tools/clang/plugins/tests/virtual_methods.txt @@ -0,0 +1,9 @@ +In file included from virtual_methods.cpp:5: +./virtual_methods.h:17:36: warning: [chromium-style] virtual methods with non-empty bodies shouldn't be declared inline. + virtual bool ComplainAboutThis() { return true; } + ^ +./virtual_methods.h:23:3: warning: [chromium-style] Overriding method must have "virtual" keyword. + void MethodHasNoArguments() override; + ^ + virtual +2 warnings generated. diff --git a/tools/clang/rewrite_scoped_array/Makefile b/tools/clang/rewrite_scoped_array/Makefile new file mode 100644 index 0000000000..d79329cd56 --- /dev/null +++ b/tools/clang/rewrite_scoped_array/Makefile @@ -0,0 +1,23 @@ +# 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 Makefile requires the LLVM build system. In order to build this tool, +# please run tools/clang/scripts/build_tool.py. + +CLANG_LEVEL := ../.. + +TOOLNAME = rewrite_scoped_array + +NO_INSTALL = 1 + +include $(CLANG_LEVEL)/../../Makefile.config + +LINK_COMPONENTS := $(TARGETS_TO_BUILD) asmparser bitreader support mc +USEDLIBS = clangTooling.a clangFrontend.a clangSerialization.a clangDriver.a \ + clangRewriteFrontend.a clangRewriteCore.a clangParse.a clangSema.a \ + clangAnalysis.a clangAST.a clangASTMatchers.a clangEdit.a \ + clangLex.a clangBasic.a + +include $(CLANG_LEVEL)/Makefile + diff --git a/tools/clang/rewrite_scoped_array/RewriteScopedArray.cpp b/tools/clang/rewrite_scoped_array/RewriteScopedArray.cpp new file mode 100644 index 0000000000..a09d5358ed --- /dev/null +++ b/tools/clang/rewrite_scoped_array/RewriteScopedArray.cpp @@ -0,0 +1,96 @@ +// 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 implements a Clang tool to rewrite all instances of scoped_array to +// scoped_ptr. The former is being deprecated in favor of the latter, to +// allow for an eventual transition from scoped_ptr to unique_ptr. + +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Support/CommandLine.h" + +using clang::ast_matchers::MatchFinder; +using clang::ast_matchers::hasDeclaration; +using clang::ast_matchers::hasName; +using clang::ast_matchers::id; +using clang::ast_matchers::loc; +using clang::ast_matchers::qualType; +using clang::ast_matchers::recordDecl; +using clang::tooling::CommonOptionsParser; +using clang::tooling::Replacement; +using clang::tooling::Replacements; +using llvm::StringRef; + +namespace { + +class RewriterCallback : public MatchFinder::MatchCallback { + public: + RewriterCallback(Replacements* replacements) : replacements_(replacements) {} + virtual void run(const MatchFinder::MatchResult& result) LLVM_OVERRIDE; + + private: + Replacements* const replacements_; +}; + +void RewriterCallback::run(const MatchFinder::MatchResult& result) { + const clang::TypeLoc type_location = + *result.Nodes.getNodeAs("loc"); + clang::CharSourceRange range = clang::CharSourceRange::getTokenRange( + result.SourceManager->getSpellingLoc(type_location.getLocStart()), + result.SourceManager->getSpellingLoc(type_location.getLocEnd())); + // TODO(dcheng): Log an error? + if (!range.isValid()) + return; + std::string replacement_text = clang::Lexer::getSourceText( + range, *result.SourceManager, result.Context->getLangOpts()); + // TODO(dcheng): Log errors? + if (!StringRef(replacement_text).startswith("scoped_array<") || + !StringRef(replacement_text).endswith(">")) + return; + replacement_text.replace(strlen("scoped_"), strlen("array"), "ptr"); + replacement_text.insert(replacement_text.size() - 1, "[]"); + replacements_->insert( + Replacement(*result.SourceManager, range, replacement_text)); +} + +} // namespace + +static llvm::cl::extrahelp common_help(CommonOptionsParser::HelpMessage); + +int main(int argc, const char* argv[]) { + CommonOptionsParser options(argc, argv); + clang::tooling::ClangTool tool(options.getCompilations(), + options.getSourcePathList()); + + Replacements replacements; + RewriterCallback callback(&replacements); + MatchFinder match_finder; + match_finder.addMatcher( + id("loc", + loc(qualType(hasDeclaration(recordDecl(hasName("::scoped_array")))))), + &callback); + + int result = + tool.run(clang::tooling::newFrontendActionFactory(&match_finder)); + if (result != 0) + return result; + + // Serialization format is documented in tools/clang/scripts/run_tool.py + llvm::outs() << "==== BEGIN EDITS ====\n"; + for (Replacements::const_iterator it = replacements.begin(); + it != replacements.end(); ++it) { + llvm::outs() << "r:" << it->getFilePath() << ":" << it->getOffset() << ":" + << it->getLength() << ":" << it->getReplacementText() << "\n"; + } + llvm::outs() << "==== END EDITS ====\n"; + + return 0; +} diff --git a/tools/clang/rewrite_scoped_array/tests/test-expected.cc b/tools/clang/rewrite_scoped_array/tests/test-expected.cc new file mode 100644 index 0000000000..62c66ef1ef --- /dev/null +++ b/tools/clang/rewrite_scoped_array/tests/test-expected.cc @@ -0,0 +1,21 @@ +// 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. + +template class scoped_array { + private: + T* data_; +}; + +class TestClass { + private: + scoped_ptr test_field_; +}; + +scoped_ptr TestFunction(scoped_ptr x, scoped_ptr) { + scoped_ptr(scoped_array test, scoped_array)[]> y; + scoped_ptr(*function_pointer)(scoped_ptr test, + scoped_ptr); + scoped_ptr test_variable; +} + diff --git a/tools/clang/rewrite_scoped_array/tests/test-original.cc b/tools/clang/rewrite_scoped_array/tests/test-original.cc new file mode 100644 index 0000000000..e0408ca750 --- /dev/null +++ b/tools/clang/rewrite_scoped_array/tests/test-original.cc @@ -0,0 +1,21 @@ +// 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. + +template class scoped_array { + private: + T* data_; +}; + +class TestClass { + private: + scoped_array test_field_; +}; + +scoped_array TestFunction(scoped_array x, scoped_array) { + scoped_array(scoped_array test, scoped_array)> y; + scoped_array(*function_pointer)(scoped_array test, + scoped_array); + scoped_array test_variable; +} + diff --git a/tools/clang/rewrite_scoped_ptr_ctor_null/Makefile b/tools/clang/rewrite_scoped_ptr_ctor_null/Makefile new file mode 100644 index 0000000000..f807d89c9a --- /dev/null +++ b/tools/clang/rewrite_scoped_ptr_ctor_null/Makefile @@ -0,0 +1,23 @@ +# 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 Makefile requires the LLVM build system. In order to build this tool, +# please run tools/clang/scripts/build_tool.py. + +CLANG_LEVEL := ../.. + +TOOLNAME = rewrite_scoped_ptr_ctor_null + +NO_INSTALL = 1 + +include $(CLANG_LEVEL)/../../Makefile.config + +LINK_COMPONENTS := $(TARGETS_TO_BUILD) asmparser bitreader support mc +USEDLIBS = clangTooling.a clangFrontend.a clangSerialization.a clangDriver.a \ + clangRewriteFrontend.a clangRewriteCore.a clangParse.a clangSema.a \ + clangAnalysis.a clangAST.a clangASTMatchers.a clangEdit.a \ + clangLex.a clangBasic.a + +include $(CLANG_LEVEL)/Makefile + diff --git a/tools/clang/rewrite_scoped_ptr_ctor_null/RewriteScopedPtrCtorNull.cpp b/tools/clang/rewrite_scoped_ptr_ctor_null/RewriteScopedPtrCtorNull.cpp new file mode 100644 index 0000000000..26813eba48 --- /dev/null +++ b/tools/clang/rewrite_scoped_ptr_ctor_null/RewriteScopedPtrCtorNull.cpp @@ -0,0 +1,198 @@ +// 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 implements a Clang tool to convert all instances of std::string("") to +// std::string(). The latter is more efficient (as std::string doesn't have to +// take a copy of an empty string) and generates fewer instructions as well. It +// should be run using the tools/clang/scripts/run_tool.py helper. + +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Support/CommandLine.h" + +using clang::ast_matchers::MatchFinder; +using clang::ast_matchers::argumentCountIs; +using clang::ast_matchers::bindTemporaryExpr; +using clang::ast_matchers::constructorDecl; +using clang::ast_matchers::constructExpr; +using clang::ast_matchers::defaultArgExpr; +using clang::ast_matchers::expr; +using clang::ast_matchers::forEach; +using clang::ast_matchers::has; +using clang::ast_matchers::hasArgument; +using clang::ast_matchers::hasDeclaration; +using clang::ast_matchers::matchesName; +using clang::ast_matchers::id; +using clang::ast_matchers::methodDecl; +using clang::ast_matchers::newExpr; +using clang::ast_matchers::ofClass; +using clang::ast_matchers::unless; +using clang::ast_matchers::varDecl; +using clang::tooling::CommonOptionsParser; +using clang::tooling::Replacement; +using clang::tooling::Replacements; + +namespace { + +bool IsNullConstant(const clang::Expr& expr, clang::ASTContext* context) { + return expr.isNullPointerConstant(*context, + clang::Expr::NPC_ValueDependentIsNotNull) != + clang::Expr::NPCK_NotNull; +} + +// Handles replacements for stack and heap-allocated instances, e.g.: +// scoped_ptr a(NULL); +// scoped_ptr* b = new scoped_ptr(NULL); +// ...though the latter should be pretty rare. +class ConstructorCallback : public MatchFinder::MatchCallback { + public: + ConstructorCallback(Replacements* replacements) + : replacements_(replacements) {} + + virtual void run(const MatchFinder::MatchResult& result) LLVM_OVERRIDE; + + private: + Replacements* const replacements_; +}; + +// Handles replacements for invocations of scoped_ptr(NULL) in an initializer +// list. +class InitializerCallback : public MatchFinder::MatchCallback { + public: + InitializerCallback(Replacements* replacements) + : replacements_(replacements) {} + + virtual void run(const MatchFinder::MatchResult& result) LLVM_OVERRIDE; + + private: + Replacements* const replacements_; +}; + +// Handles replacements for invocations of scoped_ptr(NULL) in a temporary +// context, e.g. return scoped_ptr(NULL). +class TemporaryCallback : public MatchFinder::MatchCallback { + public: + TemporaryCallback(Replacements* replacements) : replacements_(replacements) {} + + virtual void run(const MatchFinder::MatchResult& result) LLVM_OVERRIDE; + + private: + Replacements* const replacements_; +}; + +class EmptyStringConverter { + public: + explicit EmptyStringConverter(Replacements* replacements) + : constructor_callback_(replacements), + initializer_callback_(replacements), + temporary_callback_(replacements) {} + + void SetupMatchers(MatchFinder* match_finder); + + private: + ConstructorCallback constructor_callback_; + InitializerCallback initializer_callback_; + TemporaryCallback temporary_callback_; +}; + +void EmptyStringConverter::SetupMatchers(MatchFinder* match_finder) { + const char kPattern[] = "^::(scoped_ptr|scoped_ptr_malloc)$"; + const clang::ast_matchers::StatementMatcher& constructor_call = id( + "call", + constructExpr(hasDeclaration(methodDecl(ofClass(matchesName(kPattern)))), + argumentCountIs(1), + hasArgument(0, id("arg", expr())), + unless(hasArgument(0, defaultArgExpr())))); + + match_finder->addMatcher(varDecl(forEach(constructor_call)), + &constructor_callback_); + match_finder->addMatcher(newExpr(has(constructor_call)), + &constructor_callback_); + match_finder->addMatcher(bindTemporaryExpr(has(constructor_call)), + &temporary_callback_); + match_finder->addMatcher(constructorDecl(forEach(constructor_call)), + &initializer_callback_); +} + +void ConstructorCallback::run(const MatchFinder::MatchResult& result) { + const clang::Expr* arg = result.Nodes.getNodeAs("arg"); + if (!IsNullConstant(*arg, result.Context)) + return; + + const clang::CXXConstructExpr* call = + result.Nodes.getNodeAs("call"); + clang::CharSourceRange range = + clang::CharSourceRange::getTokenRange(call->getParenRange()); + replacements_->insert(Replacement(*result.SourceManager, range, "")); +} + +void InitializerCallback::run(const MatchFinder::MatchResult& result) { + const clang::Expr* arg = result.Nodes.getNodeAs("arg"); + if (!IsNullConstant(*arg, result.Context)) + return; + + const clang::CXXConstructExpr* call = + result.Nodes.getNodeAs("call"); + replacements_->insert(Replacement(*result.SourceManager, call, "")); +} + +void TemporaryCallback::run(const MatchFinder::MatchResult& result) { + const clang::Expr* arg = result.Nodes.getNodeAs("arg"); + if (!IsNullConstant(*arg, result.Context)) + return; + + // TODO(dcheng): File a bug with clang. There should be an easier way to do + // this replacement, but getTokenRange(call->getParenRange()) and the obvious + // (but incorrect) arg both don't work. The former is presumably just buggy, + // while the latter probably has to do with the fact that NULL is actually a + // macro which expands to a built-in. + clang::SourceRange range = arg->getSourceRange(); + clang::SourceRange expansion_range( + result.SourceManager->getExpansionLoc(range.getBegin()), + result.SourceManager->getExpansionLoc(range.getEnd())); + replacements_->insert( + Replacement(*result.SourceManager, + clang::CharSourceRange::getTokenRange(expansion_range), + "")); +} + +} // namespace + +static llvm::cl::extrahelp common_help(CommonOptionsParser::HelpMessage); + +int main(int argc, const char* argv[]) { + CommonOptionsParser options(argc, argv); + clang::tooling::ClangTool tool(options.getCompilations(), + options.getSourcePathList()); + + Replacements replacements; + EmptyStringConverter converter(&replacements); + MatchFinder match_finder; + converter.SetupMatchers(&match_finder); + + int result = + tool.run(clang::tooling::newFrontendActionFactory(&match_finder)); + if (result != 0) + return result; + + // Each replacement line should have the following format: + // r:::: + // Only the field can contain embedded ":" characters. + // TODO(dcheng): Use a more clever serialization. + llvm::outs() << "==== BEGIN EDITS ====\n"; + for (Replacements::const_iterator it = replacements.begin(); + it != replacements.end(); + ++it) { + llvm::outs() << "r:" << it->getFilePath() << ":" << it->getOffset() << ":" + << it->getLength() << ":" << it->getReplacementText() << "\n"; + } + llvm::outs() << "==== END EDITS ====\n"; + + return 0; +} diff --git a/tools/clang/rewrite_scoped_ptr_ctor_null/tests/test-expected.cc b/tools/clang/rewrite_scoped_ptr_ctor_null/tests/test-expected.cc new file mode 100644 index 0000000000..8cdb486234 --- /dev/null +++ b/tools/clang/rewrite_scoped_ptr_ctor_null/tests/test-expected.cc @@ -0,0 +1,37 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/scoped_ptr.h" + +void TestDeclarations() { + scoped_ptr a, b(new int), c; + scoped_ptr_malloc d; +} + +void TestNew() { + scoped_ptr* a = new scoped_ptr, *b = new scoped_ptr(new int), + *c = new scoped_ptr; +} + +class TestInitializers { + public: + TestInitializers() {} + TestInitializers(bool) {} + TestInitializers(double) + : b(new int), c(), f(static_cast(malloc(sizeof(int)))) {} + + private: + scoped_ptr a; + scoped_ptr b; + scoped_ptr c; + scoped_ptr_malloc d; + scoped_ptr_malloc e; + scoped_ptr_malloc f; +}; + +scoped_ptr TestTemporaries(scoped_ptr a, scoped_ptr b) { + scoped_ptr c = + TestTemporaries(scoped_ptr(), scoped_ptr(new int)); + return scoped_ptr(); +} diff --git a/tools/clang/rewrite_scoped_ptr_ctor_null/tests/test-original.cc b/tools/clang/rewrite_scoped_ptr_ctor_null/tests/test-original.cc new file mode 100644 index 0000000000..25c59e0f0d --- /dev/null +++ b/tools/clang/rewrite_scoped_ptr_ctor_null/tests/test-original.cc @@ -0,0 +1,39 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/scoped_ptr.h" + +void TestDeclarations() { + scoped_ptr a(NULL), b(new int), c(NULL); + scoped_ptr_malloc d(NULL); +} + +void TestNew() { + scoped_ptr* a = new scoped_ptr(NULL), + *b = new scoped_ptr(new int), + *c = new scoped_ptr(NULL); +} + +class TestInitializers { + public: + TestInitializers() : a(NULL) {} + TestInitializers(bool) : a(NULL), b(NULL), e(NULL) {} + TestInitializers(double) + : a(NULL), b(new int), c(), f(static_cast(malloc(sizeof(int)))) {} + + private: + scoped_ptr a; + scoped_ptr b; + scoped_ptr c; + scoped_ptr_malloc d; + scoped_ptr_malloc e; + scoped_ptr_malloc f; +}; + +scoped_ptr TestTemporaries(scoped_ptr a, scoped_ptr b) { + scoped_ptr c = + TestTemporaries(scoped_ptr(NULL), scoped_ptr(new int)); + return scoped_ptr(NULL); +} + diff --git a/tools/clang/scripts/package.sh b/tools/clang/scripts/package.sh new file mode 100755 index 0000000000..582125b52f --- /dev/null +++ b/tools/clang/scripts/package.sh @@ -0,0 +1,98 @@ +#!/bin/bash +# Copyright (c) 2012 The Chromium 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 will check out llvm and clang, and then package the results up +# to a tgz file. + +THIS_DIR="$(dirname "${0}")" +LLVM_DIR="${THIS_DIR}/../../../third_party/llvm" +LLVM_BOOTSTRAP_DIR="${THIS_DIR}/../../../third_party/llvm-bootstrap" +LLVM_BUILD_DIR="${THIS_DIR}/../../../third_party/llvm-build" +LLVM_BIN_DIR="${LLVM_BUILD_DIR}/Release+Asserts/bin" +LLVM_LIB_DIR="${LLVM_BUILD_DIR}/Release+Asserts/lib" + +echo "Diff in llvm:" | tee buildlog.txt +svn stat "${LLVM_DIR}" 2>&1 | tee -a buildlog.txt +svn diff "${LLVM_DIR}" 2>&1 | tee -a buildlog.txt +echo "Diff in llvm/tools/clang:" | tee -a buildlog.txt +svn stat "${LLVM_DIR}/tools/clang" 2>&1 | tee -a buildlog.txt +svn diff "${LLVM_DIR}/tools/clang" 2>&1 | tee -a buildlog.txt +echo "Diff in llvm/projects/compiler-rt:" | tee -a buildlog.txt +svn stat "${LLVM_DIR}/projects/compiler-rt" 2>&1 | tee -a buildlog.txt +svn diff "${LLVM_DIR}/projects/compiler-rt" 2>&1 | tee -a buildlog.txt + +echo "Starting build" | tee -a buildlog.txt + +set -ex + +# Do a clobber build. +rm -rf "${LLVM_BOOTSTRAP_DIR}" +rm -rf "${LLVM_BUILD_DIR}" +"${THIS_DIR}"/update.sh --run-tests --bootstrap --force-local-build 2>&1 | \ + tee -a buildlog.txt + +R=$("${LLVM_BIN_DIR}/clang" --version | \ + sed -ne 's/clang version .*(trunk \([0-9]*\))/\1/p') + +PDIR=clang-$R +rm -rf $PDIR +mkdir $PDIR +mkdir $PDIR/bin +mkdir $PDIR/lib + +if [ "$(uname -s)" = "Darwin" ]; then + SO_EXT="dylib" +else + SO_EXT="so" +fi + +# Copy buildlog over. +cp buildlog.txt $PDIR/ + +# Copy clang into pdir, symlink clang++ to it. +cp "${LLVM_BIN_DIR}/clang" $PDIR/bin/ +(cd $PDIR/bin && ln -sf clang clang++ && cd -) +cp "${LLVM_BIN_DIR}/llvm-symbolizer" $PDIR/bin/ + +# Copy plugins. Some of the dylibs are pretty big, so copy only the ones we +# care about. +cp "${LLVM_LIB_DIR}/libFindBadConstructs.${SO_EXT}" $PDIR/lib +cp "${LLVM_LIB_DIR}/libprofile_rt.${SO_EXT}" $PDIR/lib + +# Copy built-in headers (lib/clang/3.2/include). +# libcompiler-rt puts all kinds of libraries there too, but we want only ASan. +if [ "$(uname -s)" = "Darwin" ]; then + # Keep only Release+Asserts/lib/clang/3.2/lib/darwin/libclang_rt.asan_osx.a + find "${LLVM_LIB_DIR}/clang" -type f -path '*lib/darwin*' | grep -v asan | \ + xargs rm + # Fix LC_ID_DYLIB for the ASan dynamic library to be relative to + # @executable_path. + # TODO(glider): this is transitional. We'll need to fix the dylib name + # either in our build system, or in Clang. See also http://crbug.com/170629. + ASAN_DYLIB_NAME=libclang_rt.asan_osx_dynamic.dylib + ASAN_DYLIB=$(find "${LLVM_LIB_DIR}/clang" -type f -path "*${ASAN_DYLIB_NAME}") + install_name_tool -id @executable_path/${ASAN_DYLIB_NAME} "${ASAN_DYLIB}" +else + # Keep only + # Release+Asserts/lib/clang/3.2/lib/linux/libclang_rt.{a,t,m}san-x86_64.a + # TODO(thakis): Make sure the 32bit version of ASan runtime is kept too once + # that's built. TSan and MSan runtimes exist only for 64 bits. + find "${LLVM_LIB_DIR}/clang" -type f -path '*lib/linux*' | \ + grep -v "asan\|tsan\|msan" | xargs rm +fi + +cp -R "${LLVM_LIB_DIR}/clang" $PDIR/lib + +tar zcf $PDIR.tgz -C $PDIR bin lib buildlog.txt + +if [ "$(uname -s)" = "Darwin" ]; then + PLATFORM=Mac +else + PLATFORM=Linux_x64 +fi + +echo To upload, run: +echo gsutil cp -a public-read $PDIR.tgz \ + gs://chromium-browser-clang/$PLATFORM/$PDIR.tgz diff --git a/tools/clang/scripts/plugin_flags.sh b/tools/clang/scripts/plugin_flags.sh new file mode 100755 index 0000000000..0d8dea566b --- /dev/null +++ b/tools/clang/scripts/plugin_flags.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# Copyright (c) 2012 The Chromium 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 returns the flags that should be used when GYP_DEFINES contains +# clang_use_chrome_plugins. The flags are stored in a script so that they can +# be changed on the bots without requiring a master restart. + +THIS_ABS_DIR=$(cd $(dirname $0) && echo $PWD) +CLANG_LIB_PATH=$THIS_ABS_DIR/../../../third_party/llvm-build/Release+Asserts/lib + +if uname -s | grep -q Darwin; then + LIBSUFFIX=dylib +else + LIBSUFFIX=so +fi + +echo -Xclang -load -Xclang $CLANG_LIB_PATH/libFindBadConstructs.$LIBSUFFIX \ + -Xclang -add-plugin -Xclang find-bad-constructs diff --git a/tools/clang/scripts/run_tool.py b/tools/clang/scripts/run_tool.py new file mode 100755 index 0000000000..903235d43d --- /dev/null +++ b/tools/clang/scripts/run_tool.py @@ -0,0 +1,303 @@ +#!/usr/bin/env python +# 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. + +"""Wrapper script to help run clang tools across Chromium code. + +How to use this tool: +If you want to run the tool across all Chromium code: +run_tool.py + +If you only want to run the tool across just chrome/browser and content/browser: +run_tool.py chrome/browser content/browser + +Please see https://code.google.com/p/chromium/wiki/ClangToolRefactoring for more +information, which documents the entire automated refactoring flow in Chromium. + +Why use this tool: +The clang tool implementation doesn't take advantage of multiple cores, and if +it fails mysteriously in the middle, all the generated replacements will be +lost. + +Unfortunately, if the work is simply sharded across multiple cores by running +multiple RefactoringTools, problems arise when they attempt to rewrite a file at +the same time. To work around that, clang tools that are run using this tool +should output edits to stdout in the following format: + +==== BEGIN EDITS ==== +r:::: +r:::: +...etc... +==== END EDITS ==== + +Any generated edits are applied once the clang tool has finished running +across Chromium, regardless of whether some instances failed or not. +""" + +import collections +import functools +import multiprocessing +import os.path +import subprocess +import sys + + +Edit = collections.namedtuple( + 'Edit', ('edit_type', 'offset', 'length', 'replacement')) + + +def _GetFilesFromGit(paths = None): + """Gets the list of files in the git repository. + + Args: + paths: Prefix filter for the returned paths. May contain multiple entries. + """ + args = ['git', 'ls-files'] + if paths: + args.extend(paths) + command = subprocess.Popen(args, stdout=subprocess.PIPE) + output, _ = command.communicate() + return output.splitlines() + + +def _ExtractEditsFromStdout(build_directory, stdout): + """Extracts generated list of edits from the tool's stdout. + + The expected format is documented at the top of this file. + + Args: + build_directory: Directory that contains the compile database. Used to + normalize the filenames. + stdout: The stdout from running the clang tool. + + Returns: + A dictionary mapping filenames to the associated edits. + """ + lines = stdout.splitlines() + start_index = lines.index('==== BEGIN EDITS ====') + end_index = lines.index('==== END EDITS ====') + edits = collections.defaultdict(list) + for line in lines[start_index + 1:end_index]: + try: + edit_type, path, offset, length, replacement = line.split(':', 4) + # Normalize the file path emitted by the clang tool to be relative to the + # current working directory. + path = os.path.relpath(os.path.join(build_directory, path)) + edits[path].append(Edit(edit_type, int(offset), int(length), replacement)) + except ValueError: + print 'Unable to parse edit: %s' % line + return edits + + +def _ExecuteTool(toolname, build_directory, filename): + """Executes the tool. + + This is defined outside the class so it can be pickled for the multiprocessing + module. + + Args: + toolname: Path to the tool to execute. + build_directory: Directory that contains the compile database. + filename: The file to run the tool over. + + Returns: + A dictionary that must contain the key "status" and a boolean value + associated with it. + + If status is True, then the generated edits are stored with the key "edits" + in the dictionary. + + Otherwise, the filename and the output from stderr are associated with the + keys "filename" and "stderr" respectively. + """ + command = subprocess.Popen((toolname, '-p', build_directory, filename), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = command.communicate() + if command.returncode != 0: + return {'status': False, 'filename': filename, 'stderr': stderr} + else: + return {'status': True, + 'edits': _ExtractEditsFromStdout(build_directory, stdout)} + + +class _CompilerDispatcher(object): + """Multiprocessing controller for running clang tools in parallel.""" + + def __init__(self, toolname, build_directory, filenames): + """Initializer method. + + Args: + toolname: Path to the tool to execute. + build_directory: Directory that contains the compile database. + filenames: The files to run the tool over. + """ + self.__toolname = toolname + self.__build_directory = build_directory + self.__filenames = filenames + self.__success_count = 0 + self.__failed_count = 0 + self.__edits = collections.defaultdict(list) + + @property + def edits(self): + return self.__edits + + @property + def failed_count(self): + return self.__failed_count + + def Run(self): + """Does the grunt work.""" + pool = multiprocessing.Pool() + result_iterator = pool.imap_unordered( + functools.partial(_ExecuteTool, self.__toolname, + self.__build_directory), + self.__filenames) + for result in result_iterator: + self.__ProcessResult(result) + sys.stdout.write('\n') + sys.stdout.flush() + + def __ProcessResult(self, result): + """Handles result processing. + + Args: + result: The result dictionary returned by _ExecuteTool. + """ + if result['status']: + self.__success_count += 1 + for k, v in result['edits'].iteritems(): + self.__edits[k].extend(v) + else: + self.__failed_count += 1 + sys.stdout.write('\nFailed to process %s\n' % result['filename']) + sys.stdout.write(result['stderr']) + sys.stdout.write('\n') + percentage = ( + float(self.__success_count + self.__failed_count) / + len(self.__filenames)) * 100 + sys.stdout.write('Succeeded: %d, Failed: %d [%.2f%%]\r' % ( + self.__success_count, self.__failed_count, percentage)) + sys.stdout.flush() + + +def _ApplyEdits(edits, clang_format_diff_path): + """Apply the generated edits. + + Args: + edits: A dict mapping filenames to Edit instances that apply to that file. + clang_format_diff_path: Path to the clang-format-diff.py helper to help + automatically reformat diffs to avoid style violations. Pass None if the + clang-format step should be skipped. + """ + edit_count = 0 + for k, v in edits.iteritems(): + # Sort the edits and iterate through them in reverse order. Sorting allows + # duplicate edits to be quickly skipped, while reversing means that + # subsequent edits don't need to have their offsets updated with each edit + # applied. + v.sort() + last_edit = None + with open(k, 'rb+') as f: + contents = bytearray(f.read()) + for edit in reversed(v): + if edit == last_edit: + continue + last_edit = edit + contents[edit.offset:edit.offset + edit.length] = edit.replacement + if not edit.replacement: + _ExtendDeletionIfElementIsInList(contents, edit.offset) + edit_count += 1 + f.seek(0) + f.truncate() + f.write(contents) + if clang_format_diff_path: + if subprocess.call('git diff -U0 %s | python %s -style=Chromium' % ( + k, clang_format_diff_path), shell=True) != 0: + print 'clang-format failed for %s' % k + print 'Applied %d edits to %d files' % (edit_count, len(edits)) + + +_WHITESPACE_BYTES = frozenset((ord('\t'), ord('\n'), ord('\r'), ord(' '))) + + +def _ExtendDeletionIfElementIsInList(contents, offset): + """Extends the range of a deletion if the deleted element was part of a list. + + This rewriter helper makes it easy for refactoring tools to remove elements + from a list. Even if a matcher callback knows that it is removing an element + from a list, it may not have enough information to accurately remove the list + element; for example, another matcher callback may end up removing an adjacent + list element, or all the list elements may end up being removed. + + With this helper, refactoring tools can simply remove the list element and not + worry about having to include the comma in the replacement. + + Args: + contents: A bytearray with the deletion already applied. + offset: The offset in the bytearray where the deleted range used to be. + """ + char_before = char_after = None + left_trim_count = 0 + for byte in reversed(contents[:offset]): + left_trim_count += 1 + if byte in _WHITESPACE_BYTES: + continue + if byte in (ord(','), ord(':'), ord('('), ord('{')): + char_before = chr(byte) + break + + right_trim_count = 0 + for byte in contents[offset:]: + right_trim_count += 1 + if byte in _WHITESPACE_BYTES: + continue + if byte == ord(','): + char_after = chr(byte) + break + + if char_before: + if char_after: + del contents[offset:offset + right_trim_count] + elif char_before in (',', ':'): + del contents[offset - left_trim_count:offset] + + +def main(argv): + if len(argv) < 2: + print 'Usage: run_tool.py ...' + print ' is the clang tool that should be run.' + print ' is the directory that contains the compile database' + print ' ... can be used to filter what files are edited' + return 1 + + clang_format_diff_path = os.path.join( + os.path.dirname(os.path.realpath(__file__)), + '../../../third_party/llvm/tools/clang/tools/clang-format', + 'clang-format-diff.py') + # TODO(dcheng): Allow this to be controlled with a flag as well. + if not os.path.isfile(clang_format_diff_path): + clang_format_diff_path = None + + filenames = frozenset(_GetFilesFromGit(argv[2:])) + # Filter out files that aren't C/C++/Obj-C/Obj-C++. + extensions = frozenset(('.c', '.cc', '.m', '.mm')) + dispatcher = _CompilerDispatcher(argv[0], argv[1], + [f for f in filenames + if os.path.splitext(f)[1] in extensions]) + dispatcher.Run() + # Filter out edits to files that aren't in the git repository, since it's not + # useful to modify files that aren't under source control--typically, these + # are generated files or files in a git submodule that's not part of Chromium. + _ApplyEdits({k : v for k, v in dispatcher.edits.iteritems() + if k in filenames}, + clang_format_diff_path) + if dispatcher.failed_count != 0: + return 2 + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/tools/clang/scripts/test_tool.py b/tools/clang/scripts/test_tool.py new file mode 100755 index 0000000000..a99e9f48ae --- /dev/null +++ b/tools/clang/scripts/test_tool.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python +# 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. + +"""Test harness for chromium clang tools.""" + +import difflib +import glob +import json +import os +import os.path +import subprocess +import shutil +import sys + + +def _GenerateCompileCommands(files, include_paths): + """Returns a JSON string containing a compilation database for the input.""" + include_path_flags = ''.join('-I %s' % include_path + for include_path in include_paths) + return json.dumps([{'directory': '.', + 'command': 'clang++ -fsyntax-only %s -c %s' % ( + include_path_flags, f), + 'file': f} for f in files], indent=2) + + +def _NumberOfTestsToString(tests): + """Returns an English describing the number of tests.""" + return "%d test%s" % (tests, 's' if tests != 1 else '') + + +def main(argv): + if len(argv) < 1: + print 'Usage: test_tool.py ' + print ' is the clang tool to be tested.' + sys.exit(1) + + tool_to_test = argv[0] + tools_clang_scripts_directory = os.path.dirname(os.path.realpath(__file__)) + tools_clang_directory = os.path.dirname(tools_clang_scripts_directory) + test_directory_for_tool = os.path.join( + tools_clang_directory, tool_to_test, 'tests') + compile_database = os.path.join(test_directory_for_tool, + 'compile_commands.json') + source_files = glob.glob(os.path.join(test_directory_for_tool, + '*-original.cc')) + actual_files = ['-'.join([source_file.rsplit('-', 2)[0], 'actual.cc']) + for source_file in source_files] + expected_files = ['-'.join([source_file.rsplit('-', 2)[0], 'expected.cc']) + for source_file in source_files] + include_paths = [] + include_paths.append( + os.path.realpath(os.path.join(tools_clang_directory, '../..'))) + + try: + # Set up the test environment. + for source, actual in zip(source_files, actual_files): + shutil.copyfile(source, actual) + # Stage the test files in the git index. If they aren't staged, then + # run_tools.py will skip them when applying replacements. + args = ['git', 'add'] + args.extend(actual_files) + subprocess.check_call(args) + # Generate a temporary compilation database to run the tool over. + with open(compile_database, 'w') as f: + f.write(_GenerateCompileCommands(actual_files, include_paths)) + + args = ['python', + os.path.join(tools_clang_scripts_directory, 'run_tool.py'), + tool_to_test, + test_directory_for_tool] + args.extend(actual_files) + run_tool = subprocess.Popen(args, stdout=subprocess.PIPE) + stdout, _ = run_tool.communicate() + if run_tool.returncode != 0: + print 'run_tool failed:\n%s' % stdout + sys.exit(1) + + passed = 0 + failed = 0 + for expected, actual in zip(expected_files, actual_files): + print '[ RUN ] %s' % os.path.relpath(actual) + expected_output = actual_output = None + with open(expected, 'r') as f: + expected_output = f.readlines() + with open(actual, 'r') as f: + actual_output = f.readlines() + if actual_output != expected_output: + print '[ FAILED ] %s' % os.path.relpath(actual) + failed += 1 + for line in difflib.unified_diff(expected_output, actual_output, + fromfile=os.path.relpath(expected), + tofile=os.path.relpath(actual)): + sys.stdout.write(line) + # Don't clean up the file on failure, so the results can be referenced + # more easily. + continue + print '[ OK ] %s' % os.path.relpath(actual) + passed += 1 + os.remove(actual) + + if failed == 0: + os.remove(compile_database) + + print '[==========] %s ran.' % _NumberOfTestsToString(len(source_files)) + if passed > 0: + print '[ PASSED ] %s.' % _NumberOfTestsToString(passed) + if failed > 0: + print '[ FAILED ] %s.' % _NumberOfTestsToString(failed) + finally: + # No matter what, unstage the git changes we made earlier to avoid polluting + # the index. + args = ['git', 'reset', '--quiet', 'HEAD'] + args.extend(actual_files) + subprocess.call(args) + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/tools/clang/scripts/update.py b/tools/clang/scripts/update.py new file mode 100755 index 0000000000..bdc781f715 --- /dev/null +++ b/tools/clang/scripts/update.py @@ -0,0 +1,34 @@ +#!/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. + +"""Windows can't run .sh files, so this is a small python wrapper around +update.sh. +""" + +import os +import subprocess +import sys + + +def main(): + if sys.platform in ['win32', 'cygwin']: + return 0 + + # This script is called by gclient. gclient opens its hooks subprocesses with + # (stdout=subprocess.PIPE, stderr=subprocess.STDOUT) and then does custom + # output processing that breaks printing '\r' characters for single-line + # updating status messages as printed by curl and wget. + # Work around this by setting stderr of the update.sh process to stdin (!): + # gclient doesn't redirect stdin, and while stdin itself is read-only, a + # dup()ed sys.stdin is writable, try + # fd2 = os.dup(sys.stdin.fileno()); os.write(fd2, 'hi') + # TODO: Fix gclient instead, http://crbug.com/95350 + return subprocess.call( + [os.path.join(os.path.dirname(__file__), 'update.sh')] + sys.argv[1:], + stderr=os.fdopen(os.dup(sys.stdin.fileno()))) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/clang/scripts/update.sh b/tools/clang/scripts/update.sh new file mode 100755 index 0000000000..ea6f2b21b3 --- /dev/null +++ b/tools/clang/scripts/update.sh @@ -0,0 +1,350 @@ +#!/usr/bin/env bash +# Copyright (c) 2012 The Chromium 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 will check out llvm and clang into third_party/llvm and build it. + +# Do NOT CHANGE this if you don't know what you're doing -- see +# https://code.google.com/p/chromium/wiki/UpdatingClang +# Reverting problematic clang rolls is safe, though. +CLANG_REVISION=186332 + +THIS_DIR="$(dirname "${0}")" +LLVM_DIR="${THIS_DIR}/../../../third_party/llvm" +LLVM_BUILD_DIR="${LLVM_DIR}/../llvm-build" +LLVM_BOOTSTRAP_DIR="${LLVM_DIR}/../llvm-bootstrap" +CLANG_DIR="${LLVM_DIR}/tools/clang" +CLANG_TOOLS_EXTRA_DIR="${CLANG_DIR}/tools/extra" +COMPILER_RT_DIR="${LLVM_DIR}/projects/compiler-rt" +ANDROID_NDK_DIR="${LLVM_DIR}/../android_tools/ndk" +STAMP_FILE="${LLVM_BUILD_DIR}/cr_build_revision" + +# ${A:-a} returns $A if it's set, a else. +LLVM_REPO_URL=${LLVM_URL:-https://llvm.org/svn/llvm-project} + +# Die if any command dies. +set -e + +OS="$(uname -s)" + +# Parse command line options. +force_local_build= +mac_only= +run_tests= +bootstrap= +with_android=yes +chrome_tools="plugins" + +if [[ "${OS}" = "Darwin" ]]; then + with_android= +fi + +while [[ $# > 0 ]]; do + case $1 in + --bootstrap) + bootstrap=yes + ;; + --force-local-build) + force_local_build=yes + ;; + --mac-only) + mac_only=yes + ;; + --run-tests) + run_tests=yes + ;; + --without-android) + with_android= + ;; + --with-chrome-tools) + shift + if [[ $# == 0 ]]; then + echo "--with-chrome-tools requires an argument." + exit 1 + fi + chrome_tools=$1 + ;; + --help) + echo "usage: $0 [--force-local-build] [--mac-only] [--run-tests] " + echo "--bootstrap: First build clang with CC, then with itself." + echo "--force-local-build: Don't try to download prebuilt binaries." + echo "--mac-only: Do initial download only on Mac systems." + echo "--run-tests: Run tests after building. Only for local builds." + echo "--without-android: Don't build ASan Android runtime library." + echo "--with-chrome-tools: Select which chrome tools to build." \ + "Defaults to plugins." + echo " Example: --with-chrome-tools 'plugins empty-string'" + echo + exit 1 + ;; + esac + shift +done + +# --mac-only prevents the initial download on non-mac systems, but if clang has +# already been downloaded in the past, this script keeps it up to date even if +# --mac-only is passed in and the system isn't a mac. People who don't like this +# can just delete their third_party/llvm-build directory. +if [[ -n "$mac_only" ]] && [[ "${OS}" != "Darwin" ]] && + [[ ! ( "$GYP_DEFINES" =~ .*(clang|tsan|asan)=1.* ) ]] && + ! [[ -d "${LLVM_BUILD_DIR}" ]]; then + exit 0 +fi + +# Xcode and clang don't get along when predictive compilation is enabled. +# http://crbug.com/96315 +if [[ "${OS}" = "Darwin" ]] && xcodebuild -version | grep -q 'Xcode 3.2' ; then + XCONF=com.apple.Xcode + if [[ "${GYP_GENERATORS}" != "make" ]] && \ + [ "$(defaults read "${XCONF}" EnablePredictiveCompilation)" != "0" ]; then + echo + echo " HEARKEN!" + echo "You're using Xcode3 and you have 'Predictive Compilation' enabled." + echo "This does not work well with clang (http://crbug.com/96315)." + echo "Disable it in Preferences->Building (lower right), or run" + echo " defaults write ${XCONF} EnablePredictiveCompilation -boolean NO" + echo "while Xcode is not running." + echo + fi + + SUB_VERSION=$(xcodebuild -version | sed -Ene 's/Xcode 3\.2\.([0-9]+)/\1/p') + if [[ "${SUB_VERSION}" < 6 ]]; then + echo + echo " YOUR LD IS BUGGY!" + echo "Please upgrade Xcode to at least 3.2.6." + echo + fi +fi + + +# Check if there's anything to be done, exit early if not. +if [[ -f "${STAMP_FILE}" ]]; then + PREVIOUSLY_BUILT_REVISON=$(cat "${STAMP_FILE}") + if [[ -z "$force_local_build" ]] && \ + [[ "${PREVIOUSLY_BUILT_REVISON}" = "${CLANG_REVISION}" ]]; then + echo "Clang already at ${CLANG_REVISION}" + exit 0 + fi +fi +# To always force a new build if someone interrupts their build half way. +rm -f "${STAMP_FILE}" + +# Clobber pch files, since they only work with the compiler version that +# created them. Also clobber .o files, to make sure everything will be built +# with the new compiler. +if [[ "${OS}" = "Darwin" ]]; then + XCODEBUILD_DIR="${THIS_DIR}/../../../xcodebuild" + + # Xcode groups .o files by project first, configuration second. + if [[ -d "${XCODEBUILD_DIR}" ]]; then + echo "Clobbering .o files for Xcode build" + find "${XCODEBUILD_DIR}" -name '*.o' -exec rm {} + + fi +fi + +MAKE_DIR="${THIS_DIR}/../../../out" + +for CONFIG in Debug Release; do + if [[ -d "${MAKE_DIR}/${CONFIG}/obj.target" || + -d "${MAKE_DIR}/${CONFIG}/obj.host" ]]; then + echo "Clobbering ${CONFIG} PCH and .o files for make build" + if [[ -d "${MAKE_DIR}/${CONFIG}/obj.target" ]]; then + find "${MAKE_DIR}/${CONFIG}/obj.target" -name '*.gch' -exec rm {} + + find "${MAKE_DIR}/${CONFIG}/obj.target" -name '*.o' -exec rm {} + + fi + if [[ -d "${MAKE_DIR}/${CONFIG}/obj.host" ]]; then + find "${MAKE_DIR}/${CONFIG}/obj.host" -name '*.o' -exec rm {} + + fi + fi + + # ninja puts its output below ${MAKE_DIR} as well. + if [[ -d "${MAKE_DIR}/${CONFIG}/obj" ]]; then + echo "Clobbering ${CONFIG} PCH and .o files for ninja build" + find "${MAKE_DIR}/${CONFIG}/obj" -name '*.gch' -exec rm {} + + find "${MAKE_DIR}/${CONFIG}/obj" -name '*.o' -exec rm {} + + find "${MAKE_DIR}/${CONFIG}/obj" -name '*.o.d' -exec rm {} + + fi + + if [[ "${OS}" = "Darwin" ]]; then + if [[ -d "${XCODEBUILD_DIR}/${CONFIG}/SharedPrecompiledHeaders" ]]; then + echo "Clobbering ${CONFIG} PCH files for Xcode build" + rm -rf "${XCODEBUILD_DIR}/${CONFIG}/SharedPrecompiledHeaders" + fi + fi +done + +# Clobber NaCl toolchain stamp files, see http://crbug.com/159793 +if [[ -d "${MAKE_DIR}" ]]; then + find "${MAKE_DIR}" -name 'stamp.untar' -exec rm {} + +fi +if [[ "${OS}" = "Darwin" ]]; then + if [[ -d "${XCODEBUILD_DIR}" ]]; then + find "${XCODEBUILD_DIR}" -name 'stamp.untar' -exec rm {} + + fi +fi + +if [[ -z "$force_local_build" ]]; then + # Check if there's a prebuilt binary and if so just fetch that. That's faster, + # and goma relies on having matching binary hashes on client and server too. + CDS_URL=https://commondatastorage.googleapis.com/chromium-browser-clang + CDS_FILE="clang-${CLANG_REVISION}.tgz" + CDS_OUT_DIR=$(mktemp -d -t clang_download.XXXXXX) + CDS_OUTPUT="${CDS_OUT_DIR}/${CDS_FILE}" + if [ "${OS}" = "Linux" ]; then + CDS_FULL_URL="${CDS_URL}/Linux_x64/${CDS_FILE}" + elif [ "${OS}" = "Darwin" ]; then + CDS_FULL_URL="${CDS_URL}/Mac/${CDS_FILE}" + fi + echo Trying to download prebuilt clang + if which curl > /dev/null; then + curl -L --fail "${CDS_FULL_URL}" -o "${CDS_OUTPUT}" || \ + rm -rf "${CDS_OUT_DIR}" + elif which wget > /dev/null; then + wget "${CDS_FULL_URL}" -O "${CDS_OUTPUT}" || rm -rf "${CDS_OUT_DIR}" + else + echo "Neither curl nor wget found. Please install one of these." + exit 1 + fi + if [ -f "${CDS_OUTPUT}" ]; then + rm -rf "${LLVM_BUILD_DIR}/Release+Asserts" + mkdir -p "${LLVM_BUILD_DIR}/Release+Asserts" + tar -xzf "${CDS_OUTPUT}" -C "${LLVM_BUILD_DIR}/Release+Asserts" + echo clang "${CLANG_REVISION}" unpacked + echo "${CLANG_REVISION}" > "${STAMP_FILE}" + rm -rf "${CDS_OUT_DIR}" + exit 0 + else + echo Did not find prebuilt clang at r"${CLANG_REVISION}", building + fi +fi + +if [[ -n "${with_android}" ]] && ! [[ -d "${ANDROID_NDK_DIR}" ]]; then + echo "Android NDK not found at ${ANDROID_NDK_DIR}" + echo "The Android NDK is needed to build a Clang whose -fsanitize=address" + echo "works on Android. See " + echo "http://code.google.com/p/chromium/wiki/AndroidBuildInstructions for how" + echo "to install the NDK, or pass --without-android." + exit 1 +fi + +echo Getting LLVM r"${CLANG_REVISION}" in "${LLVM_DIR}" +if ! svn co --force "${LLVM_REPO_URL}/llvm/trunk@${CLANG_REVISION}" \ + "${LLVM_DIR}"; then + echo Checkout failed, retrying + rm -rf "${LLVM_DIR}" + svn co --force "${LLVM_REPO_URL}/llvm/trunk@${CLANG_REVISION}" "${LLVM_DIR}" +fi + +echo Getting clang r"${CLANG_REVISION}" in "${CLANG_DIR}" +svn co --force "${LLVM_REPO_URL}/cfe/trunk@${CLANG_REVISION}" "${CLANG_DIR}" + +echo Getting compiler-rt r"${CLANG_REVISION}" in "${COMPILER_RT_DIR}" +svn co --force "${LLVM_REPO_URL}/compiler-rt/trunk@${CLANG_REVISION}" \ + "${COMPILER_RT_DIR}" + +# Echo all commands. +set -x + +NUM_JOBS=3 +if [[ "${OS}" = "Linux" ]]; then + NUM_JOBS="$(grep -c "^processor" /proc/cpuinfo)" +elif [ "${OS}" = "Darwin" ]; then + NUM_JOBS="$(sysctl -n hw.ncpu)" +fi + +# Build bootstrap clang if requested. +if [[ -n "${bootstrap}" ]]; then + echo "Building bootstrap compiler" + mkdir -p "${LLVM_BOOTSTRAP_DIR}" + cd "${LLVM_BOOTSTRAP_DIR}" + if [[ ! -f ./config.status ]]; then + # The bootstrap compiler only needs to be able to build the real compiler, + # so it needs no cross-compiler output support. In general, the host + # compiler should be as similar to the final compiler as possible, so do + # keep --disable-threads & co. + ../llvm/configure \ + --enable-optimized \ + --enable-targets=host-only \ + --disable-threads \ + --disable-pthreads \ + --without-llvmgcc \ + --without-llvmgxx + fi + MACOSX_DEPLOYMENT_TARGET=10.5 make -j"${NUM_JOBS}" + if [[ -n "${run_tests}" ]]; then + make check-all + fi + cd - + export CC="${PWD}/${LLVM_BOOTSTRAP_DIR}/Release+Asserts/bin/clang" + export CXX="${PWD}/${LLVM_BOOTSTRAP_DIR}/Release+Asserts/bin/clang++" + echo "Building final compiler" +fi + +# Build clang (in a separate directory). +# The clang bots have this path hardcoded in built/scripts/slave/compile.py, +# so if you change it you also need to change these links. +mkdir -p "${LLVM_BUILD_DIR}" +cd "${LLVM_BUILD_DIR}" +if [[ ! -f ./config.status ]]; then + ../llvm/configure \ + --enable-optimized \ + --disable-threads \ + --disable-pthreads \ + --without-llvmgcc \ + --without-llvmgxx +fi + +MACOSX_DEPLOYMENT_TARGET=10.5 make -j"${NUM_JOBS}" +STRIP_FLAGS= +if [ "${OS}" = "Darwin" ]; then + # See http://crbug.com/256342 + STRIP_FLAGS=-x +fi +strip ${STRIP_FLAGS} Release+Asserts/bin/clang +cd - + +if [[ -n "${with_android}" ]]; then + # Make a standalone Android toolchain. + ${ANDROID_NDK_DIR}/build/tools/make-standalone-toolchain.sh \ + --platform=android-14 \ + --install-dir="${LLVM_BUILD_DIR}/android-toolchain" \ + --system=linux-x86_64 \ + --stl=stlport + + # Build ASan runtime for Android. + # Note: LLVM_ANDROID_TOOLCHAIN_DIR is not relative to PWD, but to where we + # build the runtime, i.e. third_party/llvm/projects/compiler-rt. + cd "${LLVM_BUILD_DIR}" + make -C tools/clang/runtime/ \ + LLVM_ANDROID_TOOLCHAIN_DIR="../../../llvm-build/android-toolchain" + cd - +fi + +# Build Chrome-specific clang tools. Paths in this list should be relative to +# tools/clang. +# For each tool directory, copy it into the clang tree and use clang's build +# system to compile it. +for CHROME_TOOL_DIR in ${chrome_tools}; do + TOOL_SRC_DIR="${THIS_DIR}/../${CHROME_TOOL_DIR}" + TOOL_DST_DIR="${LLVM_DIR}/tools/clang/tools/chrome-${CHROME_TOOL_DIR}" + TOOL_BUILD_DIR="${LLVM_BUILD_DIR}/tools/clang/tools/chrome-${CHROME_TOOL_DIR}" + rm -rf "${TOOL_DST_DIR}" + cp -R "${TOOL_SRC_DIR}" "${TOOL_DST_DIR}" + rm -rf "${TOOL_BUILD_DIR}" + mkdir -p "${TOOL_BUILD_DIR}" + cp "${TOOL_SRC_DIR}/Makefile" "${TOOL_BUILD_DIR}" + MACOSX_DEPLOYMENT_TARGET=10.5 make -j"${NUM_JOBS}" -C "${TOOL_BUILD_DIR}" +done + +if [[ -n "$run_tests" ]]; then + # Run a few tests. + PLUGIN_SRC_DIR="${THIS_DIR}/../plugins" + "${PLUGIN_SRC_DIR}/tests/test.sh" "${LLVM_BUILD_DIR}/Release+Asserts" + cd "${LLVM_BUILD_DIR}" + make check-all + cd - +fi + +# After everything is done, log success for this revision. +echo "${CLANG_REVISION}" > "${STAMP_FILE}" diff --git a/tools/code_coverage/coverage.py b/tools/code_coverage/coverage.py new file mode 100755 index 0000000000..a1496d7418 --- /dev/null +++ b/tools/code_coverage/coverage.py @@ -0,0 +1,359 @@ +#!/bin/env python +# Copyright (c) 2011 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + + +"""Module to setup and generate code coverage data + +This module first sets up the environment for code coverage, instruments the +binaries, runs the tests and collects the code coverage data. + + +Usage: + coverage.py --upload= + --revision= + --src_root= + [--tools_path=] +""" + +import logging +import optparse +import os +import shutil +import subprocess +import sys +import tempfile + +import google.logging_utils +import google.process_utils as proc + + +# The list of binaries that will be instrumented for code coverage +# TODO(niranjan): Re-enable instrumentation of chrome.exe and chrome.dll once we +# resolve the issue where vsinstr.exe is confused while reading symbols. +windows_binaries = [#'chrome.exe', + #'chrome.dll', + 'unit_tests.exe', + 'automated_ui_tests.exe', + 'installer_util_unittests.exe', + 'ipc_tests.exe', + 'memory_test.exe', + 'page_cycler_tests.exe', + 'perf_tests.exe', + 'reliability_tests.exe', + 'security_tests.dll', + 'startup_tests.exe', + 'tab_switching_test.exe', + 'test_shell.exe'] + +# The list of [tests, args] that will be run. +# Failing tests have been commented out. +# TODO(niranjan): Need to add layout tests that excercise the test shell. +windows_tests = [ + ['unit_tests.exe', ''], +# ['automated_ui_tests.exe', ''], + ['installer_util_unittests.exe', ''], + ['ipc_tests.exe', ''], + ['page_cycler_tests.exe', '--gtest_filter=*File --no-sandbox'], + ['reliability_tests.exe', '--no-sandbox'], + ['startup_tests.exe', '--no-sandbox'], + ['tab_switching_test.exe', '--no-sandbox'], + ] + + +def IsWindows(): + """Checks if the current platform is Windows. + """ + return sys.platform[:3] == 'win' + + +class Coverage(object): + """Class to set up and generate code coverage. + + This class contains methods that are useful to set up the environment for + code coverage. + + Attributes: + instrumented: A boolean indicating if all the binaries have been + instrumented. + """ + + def __init__(self, + revision, + src_path = None, + tools_path = None, + archive=None): + """Init method for the Coverage class. + + Args: + revision: Revision number of the Chromium source tree. + src_path: Location of the Chromium source base. + tools_path: Location of the Visual Studio Team Tools. (Win32 only) + archive: Archive location for the intermediate .coverage results. + """ + google.logging_utils.config_root() + self.revision = revision + self.instrumented = False + self.tools_path = tools_path + self.src_path = src_path + self._dir = tempfile.mkdtemp() + self._archive = archive + + def SetUp(self, binaries): + """Set up the platform specific environment and instrument the binaries for + coverage. + + This method sets up the environment, instruments all the compiled binaries + and sets up the code coverage counters. + + Args: + binaries: List of binaries that need to be instrumented. + + Returns: + True on success. + False on error. + """ + if self.instrumented: + logging.error('Binaries already instrumented') + return False + if IsWindows(): + # Stop all previous instance of VSPerfMon counters + counters_command = ('%s -shutdown' % + (os.path.join(self.tools_path, 'vsperfcmd.exe'))) + (retcode, output) = proc.RunCommandFull(counters_command, + collect_output=True) + # TODO(niranjan): Add a check that to verify that the binaries were built + # using the /PROFILE linker flag. + if self.tools_path == None: + logging.error('Could not locate Visual Studio Team Server tools') + return False + # Remove trailing slashes + self.tools_path = self.tools_path.rstrip('\\') + # Add this to the env PATH. + os.environ['PATH'] = os.environ['PATH'] + ';' + self.tools_path + instrument_command = '%s /COVERAGE ' % (os.path.join(self.tools_path, + 'vsinstr.exe')) + for binary in binaries: + logging.info('binary = %s' % (binary)) + logging.info('instrument_command = %s' % (instrument_command)) + # Instrument each binary in the list + binary = os.path.join(self.src_path, 'chrome', 'Release', binary) + (retcode, output) = proc.RunCommandFull(instrument_command + binary, + collect_output=True) + # Check if the file has been instrumented correctly. + if output.pop().rfind('Successfully instrumented') == -1: + logging.error('Error instrumenting %s' % (binary)) + return False + # We are now ready to run tests and measure code coverage. + self.instrumented = True + return True + + def TearDown(self): + """Tear down method. + + This method shuts down the counters, and cleans up all the intermediate + artifacts. + """ + if self.instrumented == False: + return + + if IsWindows(): + # Stop counters + counters_command = ('%s -shutdown' % + (os.path.join(self.tools_path, 'vsperfcmd.exe'))) + (retcode, output) = proc.RunCommandFull(counters_command, + collect_output=True) + logging.info('Counters shut down: %s' % (output)) + # TODO(niranjan): Revert the instrumented binaries to their original + # versions. + else: + return + if self._archive: + shutil.copytree(self._dir, os.path.join(self._archive, self.revision)) + logging.info('Archived the .coverage files') + # Delete all the temp files and folders + if self._dir != None: + shutil.rmtree(self._dir, ignore_errors=True) + logging.info('Cleaned up temporary files and folders') + # Reset the instrumented flag. + self.instrumented = False + + def RunTest(self, src_root, test): + """Run tests and collect the .coverage file + + Args: + src_root: Path to the root of the source. + test: Path to the test to be run. + + Returns: + Path of the intermediate .coverage file on success. + None on error. + """ + # Generate the intermediate file name for the coverage results + test_name = os.path.split(test[0])[1].strip('.exe') + # test_command = binary + args + test_command = '%s %s' % (os.path.join(src_root, + 'chrome', + 'Release', + test[0]), + test[1]) + + coverage_file = os.path.join(self._dir, '%s_win32_%s.coverage' % + (test_name, self.revision)) + logging.info('.coverage file for test %s: %s' % (test_name, coverage_file)) + + # After all the binaries have been instrumented, we start the counters. + counters_command = ('%s -start:coverage -output:%s' % + (os.path.join(self.tools_path, 'vsperfcmd.exe'), + coverage_file)) + # Here we use subprocess.call() instead of the RunCommandFull because the + # VSPerfCmd spawns another process before terminating and this confuses + # the subprocess.Popen() used by RunCommandFull. + retcode = subprocess.call(counters_command) + + # Run the test binary + logging.info('Executing test %s: ' % test_command) + (retcode, output) = proc.RunCommandFull(test_command, collect_output=True) + if retcode != 0: # Return error if the tests fail + logging.error('One or more tests failed in %s.' % test_command) + return None + + # Stop the counters + counters_command = ('%s -shutdown' % + (os.path.join(self.tools_path, 'vsperfcmd.exe'))) + (retcode, output) = proc.RunCommandFull(counters_command, + collect_output=True) + logging.info('Counters shut down: %s' % (output)) + # Return the intermediate .coverage file + return coverage_file + + def Upload(self, list_coverage, upload_path, sym_path=None, src_root=None): + """Upload the results to the dashboard. + + This method uploads the coverage data to a dashboard where it will be + processed. On Windows, this method will first convert the .coverage file to + the lcov format. This method needs to be called before the TearDown method. + + Args: + list_coverage: The list of coverage data files to consoliate and upload. + upload_path: Destination where the coverage data will be processed. + sym_path: Symbol path for the build (Win32 only) + src_root: Root folder of the source tree (Win32 only) + + Returns: + True on success. + False on failure. + """ + if upload_path == None: + logging.info('Upload path not specified. Will not convert to LCOV') + return True + + if IsWindows(): + # Stop counters + counters_command = ('%s -shutdown' % + (os.path.join(self.tools_path, 'vsperfcmd.exe'))) + (retcode, output) = proc.RunCommandFull(counters_command, + collect_output=True) + logging.info('Counters shut down: %s' % (output)) + lcov_file = os.path.join(upload_path, 'chrome_win32_%s.lcov' % + (self.revision)) + lcov = open(lcov_file, 'w') + for coverage_file in list_coverage: + # Convert the intermediate .coverage file to lcov format + if self.tools_path == None: + logging.error('Lcov converter tool not found') + return False + self.tools_path = self.tools_path.rstrip('\\') + convert_command = ('%s -sym_path=%s -src_root=%s %s' % + (os.path.join(self.tools_path, + 'coverage_analyzer.exe'), + sym_path, + src_root, + coverage_file)) + (retcode, output) = proc.RunCommandFull(convert_command, + collect_output=True) + # TODO(niranjan): Fix this to check for the correct return code. +# if output != 0: +# logging.error('Conversion to LCOV failed. Exiting.') + tmp_lcov_file = coverage_file + '.lcov' + logging.info('Conversion to lcov complete for %s' % (coverage_file)) + # Now append this .lcov file to the cumulative lcov file + logging.info('Consolidating LCOV file: %s' % (tmp_lcov_file)) + tmp_lcov = open(tmp_lcov_file, 'r') + lcov.write(tmp_lcov.read()) + tmp_lcov.close() + lcov.close() + logging.info('LCOV file uploaded to %s' % (upload_path)) + + +def main(): + # Command line parsing + parser = optparse.OptionParser() + # Path where the .coverage to .lcov converter tools are stored. + parser.add_option('-t', + '--tools_path', + dest='tools_path', + default=None, + help='Location of the coverage tools (windows only)') + parser.add_option('-u', + '--upload', + dest='upload_path', + default=None, + help='Location where the results should be uploaded') + # We need the revision number so that we can generate the output file of the + # format chrome__.lcov + parser.add_option('-r', + '--revision', + dest='revision', + default=None, + help='Revision number of the Chromium source repo') + # Root of the source tree. Needed for converting the generated .coverage file + # on Windows to the open source lcov format. + parser.add_option('-s', + '--src_root', + dest='src_root', + default=None, + help='Root of the source repository') + parser.add_option('-a', + '--archive', + dest='archive', + default=None, + help='Archive location of the intermediate .coverage data') + + (options, args) = parser.parse_args() + + if options.revision == None: + parser.error('Revision number not specified') + if options.src_root == None: + parser.error('Source root not specified') + + if IsWindows(): + # Initialize coverage + cov = Coverage(options.revision, + options.src_root, + options.tools_path, + options.archive) + list_coverage = [] + # Instrument the binaries + if cov.SetUp(windows_binaries): + # Run all the tests + for test in windows_tests: + coverage = cov.RunTest(options.src_root, test) + if coverage == None: # Indicate failure to the buildbots. + return 1 + # Collect the intermediate file + list_coverage.append(coverage) + else: + logging.error('Error during instrumentation.') + sys.exit(1) + + cov.Upload(list_coverage, + options.upload_path, + os.path.join(options.src_root, 'chrome', 'Release'), + options.src_root) + cov.TearDown() + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/code_coverage/coverage_posix.py b/tools/code_coverage/coverage_posix.py new file mode 100755 index 0000000000..f4fa56caa1 --- /dev/null +++ b/tools/code_coverage/coverage_posix.py @@ -0,0 +1,1266 @@ +#!/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. + +"""Generate and process code coverage. + +TODO(jrg): rename this from coverage_posix.py to coverage_all.py! + +Written for and tested on Mac, Linux, and Windows. To use this script +to generate coverage numbers, please run from within a gyp-generated +project. + +All platforms, to set up coverage: + cd ...../chromium ; src/tools/gyp/gyp_dogfood -Dcoverage=1 src/build/all.gyp + +Run coverage on... +Mac: + ( cd src/chrome ; xcodebuild -configuration Debug -target coverage ) +Linux: + ( cd src/chrome ; hammer coverage ) + # In particular, don't try and run 'coverage' from src/build + + +--directory=DIR: specify directory that contains gcda files, and where + a "coverage" directory will be created containing the output html. + Example name: ..../chromium/src/xcodebuild/Debug. + If not specified (e.g. buildbot) we will try and figure it out based on + other options (e.g. --target and --build-dir; see below). + +--genhtml: generate html output. If not specified only lcov is generated. + +--all_unittests: if present, run all files named *_unittests that we + can find. + +--fast_test: make the tests run real fast (just for testing) + +--strict: if a test fails, we continue happily. --strict will cause + us to die immediately. + +--trim=False: by default we trim away tests known to be problematic on + specific platforms. If set to false we do NOT trim out tests. + +--xvfb=True: By default we use Xvfb to make sure DISPLAY is valid + (Linux only). if set to False, do not use Xvfb. TODO(jrg): convert + this script from the compile stage of a builder to a + RunPythonCommandInBuildDir() command to avoid the need for this + step. + +--timeout=SECS: if a subprocess doesn't have output within SECS, + assume it's a hang. Kill it and give up. + +--bundles=BUNDLEFILE: a file containing a python list of coverage + bundles to be eval'd. Example contents of the bundlefile: + ['../base/base.gyp:base_unittests'] + This is used as part of the coverage bot. + If no other bundlefile-finding args are used (--target, + --build-dir), this is assumed to be an absolute path. + If those args are used, find BUNDLEFILE in a way consistent with + other scripts launched by buildbot. Example of another script + launched by buildbot: + http://src.chromium.org/viewvc/chrome/trunk/tools/buildbot/scripts/slave/runtest.py + +--target=NAME: specify the build target (e.g. 'Debug' or 'Release'). + This is used by buildbot scripts to help us find the output directory. + Must be used with --build-dir. + +--build-dir=DIR: According to buildbot comments, this is the name of + the directory within the buildbot working directory in which the + solution, Debug, and Release directories are found. + It's usually "src/build", but on mac it's $DIR/../xcodebuild and on + Linux it's $DIR/out. + This is used by buildbot scripts to help us find the output directory. + Must be used with --target. + +--no_exclusions: Do NOT use the exclusion list. This script keeps a + list of tests known to be problematic under coverage. For example, + ProcessUtilTest.SpawnChild will crash inside __gcov_fork() when + using the MacOS 10.6 SDK. Use of --no_exclusions prevents the use + of this exclusion list. + +--dont-clear-coverage-data: Normally we clear coverage data from + previous runs. If this arg is used we do NOT clear the coverage + data. + +Strings after all options are considered tests to run. Test names +have all text before a ':' stripped to help with gyp compatibility. +For example, ../base/base.gyp:base_unittests is interpreted as a test +named "base_unittests". +""" + +import glob +import logging +import optparse +import os +import Queue +import re +import shutil +import signal +import subprocess +import sys +import tempfile +import threading +import time +import traceback + +"""Global list of child PIDs to kill when we die.""" +gChildPIDs = [] + +"""Exclusion list. Format is + { platform: { testname: (exclusion1, exclusion2, ... ), ... } } + + Platform is a match for sys.platform and can be a list. + Matching code does an 'if sys.platform in (the key):'. + Similarly, matching does an 'if testname in thefulltestname:' + + The Chromium convention has traditionally been to place the + exclusion list in a distinct file. Unlike valgrind (which has + frequent changes when things break and are fixed), the expectation + here is that exclusions remain relatively constant (e.g. OS bugs). + If that changes, revisit the decision to place inclusions in this + script. + + Details: + ProcessUtilTest.SpawnChild: chokes in __gcov_fork on 10.6 + IPCFuzzingTest.MsgBadPayloadArgs: ditto + PanelBrowserNavigatorTest.NavigateFromCrashedPanel: Fails on coverage bot. + WebGLConformanceTests.conformance_attribs_gl_enable_vertex_attrib: Fails + with timeout (45000 ms) exceeded error. crbug.com/143248 + WebGLConformanceTests.conformance_attribs_gl_disabled_vertex_attrib: + ditto. + WebGLConformanceTests.conformance_attribs_gl_vertex_attrib_zero_issues: + ditto. + WebGLConformanceTests.conformance_attribs_gl_vertex_attrib: ditto. + WebGLConformanceTests.conformance_attribs_gl_vertexattribpointer_offsets: + ditto. + WebGLConformanceTests.conformance_attribs_gl_vertexattribpointer: ditto. + WebGLConformanceTests.conformance_buffers_buffer_bind_test: After + disabling WebGLConformanceTests specified above, this test fails when run + on local machine. + WebGLConformanceTests.conformance_buffers_buffer_data_array_buffer: ditto. + WebGLConformanceTests.conformance_buffers_index_validation_copies_indices: + ditto. + WebGLConformanceTests. + conformance_buffers_index_validation_crash_with_buffer_sub_data: ditto. + WebGLConformanceTests. + conformance_buffers_index_validation_verifies_too_many_indices: ditto. + WebGLConformanceTests. + conformance_buffers_index_validation_with_resized_buffer: ditto. + WebGLConformanceTests.conformance_canvas_buffer_offscreen_test: ditto. + WebGLConformanceTests.conformance_canvas_buffer_preserve_test: ditto. + WebGLConformanceTests.conformance_canvas_canvas_test: ditto. + WebGLConformanceTests.conformance_canvas_canvas_zero_size: ditto. + WebGLConformanceTests. + conformance_canvas_drawingbuffer_static_canvas_test: ditto. + WebGLConformanceTests.conformance_canvas_drawingbuffer_test: ditto. + PageCycler*.*: Fails on coverage bot with "Missing test directory + /....../slave/coverage-dbg-linux/build/src/data/page_cycler/moz" error. + *FrameRateCompositingTest.*: Fails with + "FATAL:chrome_content_browser_client.cc(893)] Check failed: + command_line->HasSwitch(switches::kEnableStatsTable)." + *FrameRateNoVsyncCanvasInternalTest.*: ditto. + *FrameRateGpuCanvasInternalTest.*: ditto. + IndexedDBTest.Perf: Fails with 'Timeout reached in WaitUntilCookieValue' + error. + TwoClientPasswordsSyncTest.DeleteAll: Fails on coverage bot. + MigrationTwoClientTest.MigrationHellWithoutNigori: Fails with timeout + (45000 ms) exceeded error. + TwoClientSessionsSyncTest.DeleteActiveSession: ditto. + MultipleClientSessionsSyncTest.EncryptedAndChanged: ditto. + MigrationSingleClientTest.AllTypesIndividuallyTriggerNotification: ditto. + *OldPanelResizeBrowserTest.*: crbug.com/143247 + *OldPanelDragBrowserTest.*: ditto. + *OldPanelBrowserTest.*: ditto. + *OldPanelAndDesktopNotificationTest.*: ditto. + *OldDockedPanelBrowserTest.*: ditto. + *OldDetachedPanelBrowserTest.*: ditto. + PanelDragBrowserTest.AttachWithSqueeze: ditto. + *PanelBrowserTest.*: ditto. + *DockedPanelBrowserTest.*: ditto. + *DetachedPanelBrowserTest.*: ditto. + AutomatedUITest.TheOneAndOnlyTest: crbug.com/143419 + AutomatedUITestBase.DragOut: ditto + +""" +gTestExclusions = { + 'darwin2': { 'base_unittests': ('ProcessUtilTest.SpawnChild',), + 'ipc_tests': ('IPCFuzzingTest.MsgBadPayloadArgs',), }, + 'linux2': { + 'gpu_tests': + ('WebGLConformanceTests.conformance_attribs_gl_enable_vertex_attrib', + 'WebGLConformanceTests.' + 'conformance_attribs_gl_disabled_vertex_attrib', + 'WebGLConformanceTests.' + 'conformance_attribs_gl_vertex_attrib_zero_issues', + 'WebGLConformanceTests.conformance_attribs_gl_vertex_attrib', + 'WebGLConformanceTests.' + 'conformance_attribs_gl_vertexattribpointer_offsets', + 'WebGLConformanceTests.conformance_attribs_gl_vertexattribpointer', + 'WebGLConformanceTests.conformance_buffers_buffer_bind_test', + 'WebGLConformanceTests.' + 'conformance_buffers_buffer_data_array_buffer', + 'WebGLConformanceTests.' + 'conformance_buffers_index_validation_copies_indices', + 'WebGLConformanceTests.' + 'conformance_buffers_index_validation_crash_with_buffer_sub_data', + 'WebGLConformanceTests.' + 'conformance_buffers_index_validation_verifies_too_many_indices', + 'WebGLConformanceTests.' + 'conformance_buffers_index_validation_with_resized_buffer', + 'WebGLConformanceTests.conformance_canvas_buffer_offscreen_test', + 'WebGLConformanceTests.conformance_canvas_buffer_preserve_test', + 'WebGLConformanceTests.conformance_canvas_canvas_test', + 'WebGLConformanceTests.conformance_canvas_canvas_zero_size', + 'WebGLConformanceTests.' + 'conformance_canvas_drawingbuffer_static_canvas_test', + 'WebGLConformanceTests.conformance_canvas_drawingbuffer_test',), + 'performance_ui_tests': + ('*PageCycler*.*', + '*FrameRateCompositingTest.*', + '*FrameRateNoVsyncCanvasInternalTest.*', + '*FrameRateGpuCanvasInternalTest.*', + 'IndexedDBTest.Perf',), + 'sync_integration_tests': + ('TwoClientPasswordsSyncTest.DeleteAll', + 'MigrationTwoClientTest.MigrationHellWithoutNigori', + 'TwoClientSessionsSyncTest.DeleteActiveSession', + 'MultipleClientSessionsSyncTest.EncryptedAndChanged', + 'MigrationSingleClientTest.' + 'AllTypesIndividuallyTriggerNotification',), + 'interactive_ui_tests': + ('*OldPanelResizeBrowserTest.*', + '*OldPanelDragBrowserTest.*', + '*OldPanelBrowserTest.*', + '*OldPanelAndDesktopNotificationTest.*', + '*OldDockedPanelBrowserTest.*', + '*OldDetachedPanelBrowserTest.*', + 'PanelDragBrowserTest.AttachWithSqueeze', + '*PanelBrowserTest.*', + '*DockedPanelBrowserTest.*', + '*DetachedPanelBrowserTest.*',), + 'automated_ui_tests': + ('AutomatedUITest.TheOneAndOnlyTest', + 'AutomatedUITestBase.DragOut',), }, +} + +"""Since random tests are failing/hanging on coverage bot, we are enabling + tests feature by feature. crbug.com/159748 +""" +gTestInclusions = { + 'linux2': { + 'browser_tests': + (# 'src/chrome/browser/downloads' + 'SavePageBrowserTest.*', + 'SavePageAsMHTMLBrowserTest.*', + 'DownloadQueryTest.*', + 'DownloadDangerPromptTest.*', + 'DownloadTest.*', + # 'src/chrome/browser/net' + 'CookiePolicyBrowserTest.*', + 'FtpBrowserTest.*', + 'LoadTimingObserverTest.*', + 'PredictorBrowserTest.*', + 'ProxyBrowserTest.*', + # 'src/chrome/browser/extensions' + 'Extension*.*', + 'WindowOpenPanelDisabledTest.*', + 'WindowOpenPanelTest.*', + 'WebstoreStandalone*.*', + 'CommandLineWebstoreInstall.*', + 'WebViewTest.*', + 'RequirementsCheckerBrowserTest.*', + 'ProcessManagementTest.*', + 'PlatformAppBrowserTest.*', + 'PlatformAppDevToolsBrowserTest.*', + 'LazyBackgroundPageApiTest.*', + 'IsolatedAppTest.*', + 'PanelMessagingTest.*', + 'GeolocationApiTest.*', + 'ClipboardApiTest.*', + 'ExecuteScriptApiTest.*', + 'CalculatorBrowserTest.*', + 'ChromeAppAPITest.*', + 'AppApiTest.*', + 'BlockedAppApiTest.*', + 'AppBackgroundPageApiTest.*', + 'WebNavigationApiTest.*', + 'UsbApiTest.*', + 'TabCaptureApiTest.*', + 'SystemInfo*.*', + 'SyncFileSystemApiTest.*', + 'SocketApiTest.*', + 'SerialApiTest.*', + 'RecordApiTest.*', + 'PushMessagingApiTest.*', + 'ProxySettingsApiTest.*', + 'ExperimentalApiTest.*', + 'OmniboxApiTest.*', + 'OffscreenTabsApiTest.*', + 'NotificationApiTest.*', + 'MediaGalleriesPrivateApiTest.*', + 'PlatformAppMediaGalleriesBrowserTest.*', + 'GetAuthTokenFunctionTest.*', + 'LaunchWebAuthFlowFunctionTest.*', + 'FileSystemApiTest.*', + 'ScriptBadgeApiTest.*', + 'PageAsBrowserActionApiTest.*', + 'PageActionApiTest.*', + 'BrowserActionApiTest.*', + 'DownloadExtensionTest.*', + 'DnsApiTest.*', + 'DeclarativeApiTest.*', + 'BluetoothApiTest.*', + 'AllUrlsApiTest.*', + # 'src/chrome/browser/nacl_host' + 'nacl_host.*', + # 'src/chrome/browser/automation' + 'AutomationMiscBrowserTest.*', + # 'src/chrome/browser/autofill' + 'FormStructureBrowserTest.*', + 'AutofillPopupViewBrowserTest.*', + 'AutofillTest.*', + # 'src/chrome/browser/autocomplete' + 'AutocompleteBrowserTest.*', + # 'src/chrome/browser/captive_portal' + 'CaptivePortalBrowserTest.*', + # 'src/chrome/browser/geolocation' + 'GeolocationAccessTokenStoreTest.*', + 'GeolocationBrowserTest.*', + # 'src/chrome/browser/nacl_host' + 'NaClGdbTest.*', + # 'src/chrome/browser/devtools' + 'DevToolsSanityTest.*', + 'DevToolsExtensionTest.*', + 'DevToolsExperimentalExtensionTest.*', + 'WorkerDevToolsSanityTest.*', + # 'src/chrome/browser/first_run' + 'FirstRunBrowserTest.*', + # 'src/chrome/browser/importer' + 'ToolbarImporterUtilsTest.*', + # 'src/chrome/browser/page_cycler' + 'PageCyclerBrowserTest.*', + 'PageCyclerCachedBrowserTest.*', + # 'src/chrome/browser/performance_monitor' + 'PerformanceMonitorBrowserTest.*', + 'PerformanceMonitorUncleanExitBrowserTest.*', + 'PerformanceMonitorSessionRestoreBrowserTest.*', + # 'src/chrome/browser/prerender' + 'PrerenderBrowserTest.*', + 'PrerenderBrowserTestWithNaCl.*', + 'PrerenderBrowserTestWithExtensions.*', + 'PrefetchBrowserTest.*', + 'PrefetchBrowserTestNoPrefetching.*', ), + }, +} + + +def TerminateSignalHandler(sig, stack): + """When killed, try and kill our child processes.""" + signal.signal(sig, signal.SIG_DFL) + for pid in gChildPIDs: + if 'kill' in os.__all__: # POSIX + os.kill(pid, sig) + else: + subprocess.call(['taskkill.exe', '/PID', str(pid)]) + sys.exit(0) + + +class RunTooLongException(Exception): + """Thrown when a command runs too long without output.""" + pass + +class BadUserInput(Exception): + """Thrown when arguments from the user are incorrectly formatted.""" + pass + + +class RunProgramThread(threading.Thread): + """A thread to run a subprocess. + + We want to print the output of our subprocess in real time, but also + want a timeout if there has been no output for a certain amount of + time. Normal techniques (e.g. loop in select()) aren't cross + platform enough. the function seems simple: "print output of child, kill it + if there is no output by timeout. But it was tricky to get this right + in a x-platform way (see warnings about deadlock on the python + subprocess doc page). + + """ + # Constants in our queue + PROGRESS = 0 + DONE = 1 + + def __init__(self, cmd): + super(RunProgramThread, self).__init__() + self._cmd = cmd + self._process = None + self._queue = Queue.Queue() + self._retcode = None + + def run(self): + if sys.platform in ('win32', 'cygwin'): + return self._run_windows() + else: + self._run_posix() + + def _run_windows(self): + # We need to save stdout to a temporary file because of a bug on the + # windows implementation of python which can deadlock while waiting + # for the IO to complete while writing to the PIPE and the pipe waiting + # on us and us waiting on the child process. + stdout_file = tempfile.TemporaryFile() + try: + self._process = subprocess.Popen(self._cmd, + stdin=subprocess.PIPE, + stdout=stdout_file, + stderr=subprocess.STDOUT) + gChildPIDs.append(self._process.pid) + try: + # To make sure that the buildbot don't kill us if we run too long + # without any activity on the console output, we look for progress in + # the length of the temporary file and we print what was accumulated so + # far to the output console to make the buildbot know we are making some + # progress. + previous_tell = 0 + # We will poll the process until we get a non-None return code. + self._retcode = None + while self._retcode is None: + self._retcode = self._process.poll() + current_tell = stdout_file.tell() + if current_tell > previous_tell: + # Report progress to our main thread so we don't timeout. + self._queue.put(RunProgramThread.PROGRESS) + # And print what was accumulated to far. + stdout_file.seek(previous_tell) + print stdout_file.read(current_tell - previous_tell), + previous_tell = current_tell + # Don't be selfish, let other threads do stuff while we wait for + # the process to complete. + time.sleep(0.5) + # OK, the child process has exited, let's print its output to our + # console to create debugging logs in case they get to be needed. + stdout_file.flush() + stdout_file.seek(previous_tell) + print stdout_file.read(stdout_file.tell() - previous_tell) + except IOError, e: + logging.exception('%s', e) + pass + finally: + stdout_file.close() + + # If we get here the process is done. + gChildPIDs.remove(self._process.pid) + self._queue.put(RunProgramThread.DONE) + + def _run_posix(self): + """No deadlock problem so use the simple answer. The windows solution + appears to add extra buffering which we don't want on other platforms.""" + self._process = subprocess.Popen(self._cmd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + gChildPIDs.append(self._process.pid) + try: + while True: + line = self._process.stdout.readline() + if not line: # EOF + break + print line, + self._queue.put(RunProgramThread.PROGRESS, True) + except IOError: + pass + # If we get here the process is done. + gChildPIDs.remove(self._process.pid) + self._queue.put(RunProgramThread.DONE) + + def stop(self): + self.kill() + + def kill(self): + """Kill our running process if needed. Wait for kill to complete. + + Should be called in the PARENT thread; we do not self-kill. + Returns the return code of the process. + Safe to call even if the process is dead. + """ + if not self._process: + return self.retcode() + if 'kill' in os.__all__: # POSIX + os.kill(self._process.pid, signal.SIGKILL) + else: + subprocess.call(['taskkill.exe', '/PID', str(self._process.pid)]) + return self.retcode() + + def retcode(self): + """Return the return value of the subprocess. + + Waits for process to die but does NOT kill it explicitly. + """ + if self._retcode == None: # must be none, not 0/False + self._retcode = self._process.wait() + return self._retcode + + def RunUntilCompletion(self, timeout): + """Run thread until completion or timeout (in seconds). + + Start the thread. Let it run until completion, or until we've + spent TIMEOUT without seeing output. On timeout throw + RunTooLongException. + """ + self.start() + while True: + try: + x = self._queue.get(True, timeout) + if x == RunProgramThread.DONE: + return self.retcode() + except Queue.Empty, e: # timed out + logging.info('TIMEOUT (%d seconds exceeded with no output): killing' % + timeout) + self.kill() + raise RunTooLongException() + + +class Coverage(object): + """Doitall class for code coverage.""" + + def __init__(self, options, args): + super(Coverage, self).__init__() + logging.basicConfig(level=logging.DEBUG) + self.directory = options.directory + self.options = options + self.args = args + self.ConfirmDirectory() + self.directory_parent = os.path.dirname(self.directory) + self.output_directory = os.path.join(self.directory, 'coverage') + if not os.path.exists(self.output_directory): + os.mkdir(self.output_directory) + # The "final" lcov-format file + self.coverage_info_file = os.path.join(self.directory, 'coverage.info') + # If needed, an intermediate VSTS-format file + self.vsts_output = os.path.join(self.directory, 'coverage.vsts') + # Needed for Windows. + self.src_root = options.src_root + self.FindPrograms() + self.ConfirmPlatformAndPaths() + self.tests = [] # This can be a list of strings, lists or both. + self.xvfb_pid = 0 + self.test_files = [] # List of files with test specifications. + self.test_filters = {} # Mapping from testname->--gtest_filter arg. + logging.info('self.directory: ' + self.directory) + logging.info('self.directory_parent: ' + self.directory_parent) + + def FindInPath(self, program): + """Find program in our path. Return abs path to it, or None.""" + if not 'PATH' in os.environ: + logging.fatal('No PATH environment variable?') + sys.exit(1) + paths = os.environ['PATH'].split(os.pathsep) + for path in paths: + fullpath = os.path.join(path, program) + if os.path.exists(fullpath): + return fullpath + return None + + def FindPrograms(self): + """Find programs we may want to run.""" + if self.IsPosix(): + self.lcov_directory = os.path.join(sys.path[0], + '../../third_party/lcov/bin') + self.lcov = os.path.join(self.lcov_directory, 'lcov') + self.mcov = os.path.join(self.lcov_directory, 'mcov') + self.genhtml = os.path.join(self.lcov_directory, 'genhtml') + self.programs = [self.lcov, self.mcov, self.genhtml] + else: + # Hack to get the buildbot working. + os.environ['PATH'] += r';c:\coverage\coverage_analyzer' + os.environ['PATH'] += r';c:\coverage\performance_tools' + # (end hack) + commands = ['vsperfcmd.exe', 'vsinstr.exe', 'coverage_analyzer.exe'] + self.perf = self.FindInPath('vsperfcmd.exe') + self.instrument = self.FindInPath('vsinstr.exe') + self.analyzer = self.FindInPath('coverage_analyzer.exe') + if not self.perf or not self.instrument or not self.analyzer: + logging.fatal('Could not find Win performance commands.') + logging.fatal('Commands needed in PATH: ' + str(commands)) + sys.exit(1) + self.programs = [self.perf, self.instrument, self.analyzer] + + def PlatformBuildPrefix(self): + """Return a platform specific build directory prefix. + + This prefix is prepended to the build target (Debug, Release) to + identify output as relative to the build directory. + These values are specific to Chromium's use of gyp. + """ + if self.IsMac(): + return '../xcodebuild' + if self.IsWindows(): + return '' + else: # Linux + return '../out' # assumes make, unlike runtest.py + + def ConfirmDirectory(self): + """Confirm correctness of self.directory. + + If it exists, happiness. If not, try and figure it out in a + manner similar to FindBundlesFile(). The 'figure it out' case + happens with buildbot where the directory isn't specified + explicitly. + """ + if (not self.directory and + not (self.options.target and self.options.build_dir)): + logging.fatal('Must use --directory or (--target and --build-dir)') + sys.exit(1) + + if not self.directory: + self.directory = os.path.join(self.options.build_dir, + self.PlatformBuildPrefix(), + self.options.target) + + if os.path.exists(self.directory): + logging.info('Directory: ' + self.directory) + return + else: + logging.fatal('Directory ' + + self.directory + ' doesn\'t exist') + sys.exit(1) + + + def FindBundlesFile(self): + """Find the bundlesfile. + + The 'bundles' file can be either absolute path, or (if we are run + from buildbot) we need to find it based on other hints (--target, + --build-dir, etc). + """ + # If no bundle file, no problem! + if not self.options.bundles: + return + # If true, we're buildbot. Form a path. + # Else assume absolute. + if self.options.target and self.options.build_dir: + fullpath = os.path.join(self.options.build_dir, + self.PlatformBuildPrefix(), + self.options.target, + self.options.bundles) + self.options.bundles = fullpath + + if os.path.exists(self.options.bundles): + logging.info('BundlesFile: ' + self.options.bundles) + return + else: + logging.fatal('bundlefile ' + + self.options.bundles + ' doesn\'t exist') + sys.exit(1) + + + def FindTests(self): + """Find unit tests to run; set self.tests to this list. + + Assume all non-option items in the arg list are tests to be run. + """ + # Before we begin, find the bundles file if not an absolute path. + self.FindBundlesFile() + + # Small tests: can be run in the "chromium" directory. + # If asked, run all we can find. + if self.options.all_unittests: + self.tests += glob.glob(os.path.join(self.directory, '*_unittests')) + self.tests += glob.glob(os.path.join(self.directory, '*unit_tests')) + elif self.options.all_browsertests: + # Run all tests in browser_tests and content_browsertests. + self.tests += glob.glob(os.path.join(self.directory, 'browser_tests')) + self.tests += glob.glob(os.path.join(self.directory, + 'content_browsertests')) + + # Tests can come in as args directly, indirectly (through a file + # of test lists) or as a file of bundles. + all_testnames = self.args[:] # Copy since we might modify + + for test_file in self.options.test_files: + f = open(test_file) + for line in f: + line = re.sub(r"#.*$", "", line) + line = re.sub(r"\s*", "", line) + if re.match("\s*$"): + continue + all_testnames.append(line) + f.close() + + tests_from_bundles = None + if self.options.bundles: + try: + tests_from_bundles = eval(open(self.options.bundles).read()) + except IOError: + logging.fatal('IO error in bundle file ' + + self.options.bundles + ' (doesn\'t exist?)') + except (NameError, SyntaxError): + logging.fatal('Parse or syntax error in bundle file ' + + self.options.bundles) + if hasattr(tests_from_bundles, '__iter__'): + all_testnames += tests_from_bundles + else: + logging.fatal('Fatal error with bundle file; could not get list from' + + self.options.bundles) + sys.exit(1) + + # If told explicit tests, run those (after stripping the name as + # appropriate) + for testname in all_testnames: + mo = re.search(r"(.*)\[(.*)\]$", testname) + gtest_filter = None + if mo: + gtest_filter = mo.group(2) + testname = mo.group(1) + if ':' in testname: + testname = testname.split(':')[1] + # We need 'pyautolib' to run pyauto tests and 'pyautolib' itself is not an + # executable. So skip this test from adding into coverage_bundles.py. + if testname == 'pyautolib': + continue + self.tests += [os.path.join(self.directory, testname)] + if gtest_filter: + self.test_filters[testname] = gtest_filter + + # Add 'src/test/functional/pyauto_functional.py' to self.tests. + # This file with '-v --suite=CODE_COVERAGE' arguments runs all pyauto tests. + # Pyauto tests are failing randomly on coverage bots. So excluding them. + # self.tests += [['src/chrome/test/functional/pyauto_functional.py', + # '-v', + # '--suite=CODE_COVERAGE']] + + # Medium tests? + # Not sure all of these work yet (e.g. page_cycler_tests) + # self.tests += glob.glob(os.path.join(self.directory, '*_tests')) + + # If needed, append .exe to tests since vsinstr.exe likes it that + # way. + if self.IsWindows(): + for ind in range(len(self.tests)): + test = self.tests[ind] + test_exe = test + '.exe' + if not test.endswith('.exe') and os.path.exists(test_exe): + self.tests[ind] = test_exe + + def TrimTests(self): + """Trim specific tests for each platform.""" + if self.IsWindows(): + return + # TODO(jrg): remove when not needed + inclusion = ['unit_tests'] + keep = [] + for test in self.tests: + for i in inclusion: + if i in test: + keep.append(test) + self.tests = keep + logging.info('After trimming tests we have ' + ' '.join(self.tests)) + return + if self.IsLinux(): + # self.tests = filter(lambda t: t.endswith('base_unittests'), self.tests) + return + if self.IsMac(): + exclusion = ['automated_ui_tests'] + punted = [] + for test in self.tests: + for e in exclusion: + if test.endswith(e): + punted.append(test) + self.tests = filter(lambda t: t not in punted, self.tests) + if punted: + logging.info('Tests trimmed out: ' + str(punted)) + + def ConfirmPlatformAndPaths(self): + """Confirm OS and paths (e.g. lcov).""" + for program in self.programs: + if not os.path.exists(program): + logging.fatal('Program missing: ' + program) + sys.exit(1) + + def Run(self, cmdlist, ignore_error=False, ignore_retcode=None, + explanation=None): + """Run the command list; exit fatally on error. + + Args: + cmdlist: a list of commands (e.g. to pass to subprocess.call) + ignore_error: if True log an error; if False then exit. + ignore_retcode: if retcode is non-zero, exit unless we ignore. + + Returns: process return code. + Throws: RunTooLongException if the process does not produce output + within TIMEOUT seconds; timeout is specified as a command line + option to the Coverage class and is set on init. + """ + logging.info('Running ' + str(cmdlist)) + t = RunProgramThread(cmdlist) + retcode = t.RunUntilCompletion(self.options.timeout) + + if retcode: + if ignore_error or retcode == ignore_retcode: + logging.warning('COVERAGE: %s unhappy but errors ignored %s' % + (str(cmdlist), explanation or '')) + else: + logging.fatal('COVERAGE: %s failed; return code: %d' % + (str(cmdlist), retcode)) + sys.exit(retcode) + return retcode + + def IsPosix(self): + """Return True if we are POSIX.""" + return self.IsMac() or self.IsLinux() + + def IsMac(self): + return sys.platform == 'darwin' + + def IsLinux(self): + return sys.platform.startswith('linux') + + def IsWindows(self): + """Return True if we are Windows.""" + return sys.platform in ('win32', 'cygwin') + + def ClearData(self): + """Clear old gcda files and old coverage info files.""" + if self.options.dont_clear_coverage_data: + print 'Clearing of coverage data NOT performed.' + return + print 'Clearing coverage data from previous runs.' + if os.path.exists(self.coverage_info_file): + os.remove(self.coverage_info_file) + if self.IsPosix(): + subprocess.call([self.lcov, + '--directory', self.directory_parent, + '--zerocounters']) + shutil.rmtree(os.path.join(self.directory, 'coverage')) + if self.options.all_unittests: + if os.path.exists(os.path.join(self.directory, 'unittests_coverage')): + shutil.rmtree(os.path.join(self.directory, 'unittests_coverage')) + elif self.options.all_browsertests: + if os.path.exists(os.path.join(self.directory, + 'browsertests_coverage')): + shutil.rmtree(os.path.join(self.directory, 'browsertests_coverage')) + else: + if os.path.exists(os.path.join(self.directory, 'total_coverage')): + shutil.rmtree(os.path.join(self.directory, 'total_coverage')) + + def BeforeRunOneTest(self, testname): + """Do things before running each test.""" + if not self.IsWindows(): + return + # Stop old counters if needed + cmdlist = [self.perf, '-shutdown'] + self.Run(cmdlist, ignore_error=True) + # Instrument binaries + for fulltest in self.tests: + if os.path.exists(fulltest): + # See http://support.microsoft.com/kb/939818 for details on args + cmdlist = [self.instrument, '/d:ignorecverr', '/COVERAGE', fulltest] + self.Run(cmdlist, ignore_retcode=4, + explanation='OK with a multiple-instrument') + # Start new counters + cmdlist = [self.perf, '-start:coverage', '-output:' + self.vsts_output] + self.Run(cmdlist) + + def BeforeRunAllTests(self): + """Called right before we run all tests.""" + if self.IsLinux() and self.options.xvfb: + self.StartXvfb() + + def GtestFilter(self, fulltest, excl=None): + """Return a --gtest_filter=BLAH for this test. + + Args: + fulltest: full name of test executable + exclusions: the exclusions list. Only set in a unit test; + else uses gTestExclusions. + Returns: + String of the form '--gtest_filter=BLAH', or None. + """ + positive_gfilter_list = [] + negative_gfilter_list = [] + + # Exclude all flaky, failing, disabled and maybe tests; + # they don't count for code coverage. + negative_gfilter_list += ('*.FLAKY_*', '*.FAILS_*', + '*.DISABLED_*', '*.MAYBE_*') + + if not self.options.no_exclusions: + exclusions = excl or gTestExclusions + excldict = exclusions.get(sys.platform) + if excldict: + for test in excldict.keys(): + # example: if base_unittests in ../blah/blah/base_unittests.exe + if test in fulltest: + negative_gfilter_list += excldict[test] + + inclusions = gTestInclusions + include_dict = inclusions.get(sys.platform) + if include_dict: + for test in include_dict.keys(): + if test in fulltest: + positive_gfilter_list += include_dict[test] + + fulltest_basename = os.path.basename(fulltest) + if fulltest_basename in self.test_filters: + specific_test_filters = self.test_filters[fulltest_basename].split('-') + if len(specific_test_filters) > 2: + logging.error('Multiple "-" symbols in filter list: %s' % + self.test_filters[fulltest_basename]) + raise BadUserInput() + if len(specific_test_filters) == 2: + # Remove trailing ':' + specific_test_filters[0] = specific_test_filters[0][:-1] + + if specific_test_filters[0]: # Test for no positive filters. + positive_gfilter_list += specific_test_filters[0].split(':') + if len(specific_test_filters) > 1: + negative_gfilter_list += specific_test_filters[1].split(':') + + if not positive_gfilter_list and not negative_gfilter_list: + return None + + result = '--gtest_filter=' + if positive_gfilter_list: + result += ':'.join(positive_gfilter_list) + if negative_gfilter_list: + if positive_gfilter_list: result += ':' + result += '-' + ':'.join(negative_gfilter_list) + return result + + def RunTests(self): + """Run all unit tests and generate appropriate lcov files.""" + self.BeforeRunAllTests() + for fulltest in self.tests: + if type(fulltest) is str: + if not os.path.exists(fulltest): + logging.info(fulltest + ' does not exist') + if self.options.strict: + sys.exit(2) + else: + logging.info('%s path exists' % fulltest) + cmdlist = [fulltest, '--gtest_print_time'] + + # If asked, make this REAL fast for testing. + if self.options.fast_test: + logging.info('Running as a FAST test for testing') + # cmdlist.append('--gtest_filter=RenderWidgetHost*') + # cmdlist.append('--gtest_filter=CommandLine*') + cmdlist.append('--gtest_filter=C*') + + # Possibly add a test-specific --gtest_filter + filter = self.GtestFilter(fulltest) + if filter: + cmdlist.append(filter) + elif type(fulltest) is list: + cmdlist = fulltest + + self.BeforeRunOneTest(fulltest) + logging.info('Running test ' + str(cmdlist)) + try: + retcode = self.Run(cmdlist, ignore_retcode=True) + except SystemExit: # e.g. sys.exit() was called somewhere in here + raise + except: # can't "except WindowsError" since script runs on non-Windows + logging.info('EXCEPTION while running a unit test') + logging.info(traceback.format_exc()) + retcode = 999 + self.AfterRunOneTest(fulltest) + + if retcode: + logging.info('COVERAGE: test %s failed; return code: %d.' % + (fulltest, retcode)) + if self.options.strict: + logging.fatal('Test failure is fatal.') + sys.exit(retcode) + self.AfterRunAllTests() + + def AfterRunOneTest(self, testname): + """Do things right after running each test.""" + if not self.IsWindows(): + return + # Stop counters + cmdlist = [self.perf, '-shutdown'] + self.Run(cmdlist) + full_output = self.vsts_output + '.coverage' + shutil.move(full_output, self.vsts_output) + # generate lcov! + self.GenerateLcovWindows(testname) + + def AfterRunAllTests(self): + """Do things right after running ALL tests.""" + # On POSIX we can do it all at once without running out of memory. + # This contrasts with Windows where we must do it after each test. + if self.IsPosix(): + self.GenerateLcovPosix() + # Only on Linux do we have the Xvfb step. + if self.IsLinux() and self.options.xvfb: + self.StopXvfb() + + def StartXvfb(self): + """Start Xvfb and set an appropriate DISPLAY environment. Linux only. + + Copied from http://src.chromium.org/viewvc/chrome/trunk/tools/buildbot/ + scripts/slave/slave_utils.py?view=markup + with some simplifications (e.g. no need to use xdisplaycheck, save + pid in var not file, etc) + """ + logging.info('Xvfb: starting') + proc = subprocess.Popen(["Xvfb", ":9", "-screen", "0", "1024x768x24", + "-ac"], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + self.xvfb_pid = proc.pid + if not self.xvfb_pid: + logging.info('Could not start Xvfb') + return + os.environ['DISPLAY'] = ":9" + # Now confirm, giving a chance for it to start if needed. + logging.info('Xvfb: confirming') + for test in range(10): + proc = subprocess.Popen('xdpyinfo >/dev/null', shell=True) + pid, retcode = os.waitpid(proc.pid, 0) + if retcode == 0: + break + time.sleep(0.5) + if retcode != 0: + logging.info('Warning: could not confirm Xvfb happiness') + else: + logging.info('Xvfb: OK') + + def StopXvfb(self): + """Stop Xvfb if needed. Linux only.""" + if self.xvfb_pid: + logging.info('Xvfb: killing') + try: + os.kill(self.xvfb_pid, signal.SIGKILL) + except: + pass + del os.environ['DISPLAY'] + self.xvfb_pid = 0 + + def CopyCoverageFileToDestination(self, coverage_folder): + coverage_dir = os.path.join(self.directory, coverage_folder) + if not os.path.exists(coverage_dir): + os.makedirs(coverage_dir) + shutil.copyfile(self.coverage_info_file, os.path.join(coverage_dir, + 'coverage.info')) + + def GenerateLcovPosix(self): + """Convert profile data to lcov on Mac or Linux.""" + start_dir = os.getcwd() + logging.info('GenerateLcovPosix: start_dir=' + start_dir) + if self.IsLinux(): + # With Linux/make (e.g. the coverage_run target), the current + # directory for this command is .../build/src/chrome but we need + # to be in .../build/src for the relative path of source files + # to be correct. However, when run from buildbot, the current + # directory is .../build. Accommodate. + # On Mac source files are compiled with abs paths so this isn't + # a problem. + # This is a bit of a hack. The best answer is to require this + # script be run in a specific directory for all cases (from + # Makefile or from buildbot). + if start_dir.endswith('chrome'): + logging.info('coverage_posix.py: doing a "cd .." ' + 'to accomodate Linux/make PWD') + os.chdir('..') + elif start_dir.endswith('build'): + logging.info('coverage_posix.py: doing a "cd src" ' + 'to accomodate buildbot PWD') + os.chdir('src') + else: + logging.info('coverage_posix.py: NOT changing directory.') + elif self.IsMac(): + pass + + command = [self.mcov, + '--directory', + os.path.join(start_dir, self.directory_parent), + '--output', + os.path.join(start_dir, self.coverage_info_file)] + logging.info('Assembly command: ' + ' '.join(command)) + retcode = subprocess.call(command) + if retcode: + logging.fatal('COVERAGE: %s failed; return code: %d' % + (command[0], retcode)) + if self.options.strict: + sys.exit(retcode) + if self.IsLinux(): + os.chdir(start_dir) + + # Copy the unittests coverage information to a different folder. + if self.options.all_unittests: + self.CopyCoverageFileToDestination('unittests_coverage') + elif self.options.all_browsertests: + # Save browsertests only coverage information. + self.CopyCoverageFileToDestination('browsertests_coverage') + else: + # Save the overall coverage information. + self.CopyCoverageFileToDestination('total_coverage') + + if not os.path.exists(self.coverage_info_file): + logging.fatal('%s was not created. Coverage run failed.' % + self.coverage_info_file) + sys.exit(1) + + def GenerateLcovWindows(self, testname=None): + """Convert VSTS format to lcov. Appends coverage data to sum file.""" + lcov_file = self.vsts_output + '.lcov' + if os.path.exists(lcov_file): + os.remove(lcov_file) + # generates the file (self.vsts_output + ".lcov") + + cmdlist = [self.analyzer, + '-sym_path=' + self.directory, + '-src_root=' + self.src_root, + '-noxml', + self.vsts_output] + self.Run(cmdlist) + if not os.path.exists(lcov_file): + logging.fatal('Output file %s not created' % lcov_file) + sys.exit(1) + logging.info('Appending lcov for test %s to %s' % + (testname, self.coverage_info_file)) + size_before = 0 + if os.path.exists(self.coverage_info_file): + size_before = os.stat(self.coverage_info_file).st_size + src = open(lcov_file, 'r') + dst = open(self.coverage_info_file, 'a') + dst.write(src.read()) + src.close() + dst.close() + size_after = os.stat(self.coverage_info_file).st_size + logging.info('Lcov file growth for %s: %d --> %d' % + (self.coverage_info_file, size_before, size_after)) + + def GenerateHtml(self): + """Convert lcov to html.""" + # TODO(jrg): This isn't happy when run with unit_tests since V8 has a + # different "base" so V8 includes can't be found in ".". Fix. + command = [self.genhtml, + self.coverage_info_file, + '--output-directory', + self.output_directory] + print >>sys.stderr, 'html generation command: ' + ' '.join(command) + retcode = subprocess.call(command) + if retcode: + logging.fatal('COVERAGE: %s failed; return code: %d' % + (command[0], retcode)) + if self.options.strict: + sys.exit(retcode) + +def CoverageOptionParser(): + """Return an optparse.OptionParser() suitable for Coverage object creation.""" + parser = optparse.OptionParser() + parser.add_option('-d', + '--directory', + dest='directory', + default=None, + help='Directory of unit test files') + parser.add_option('-a', + '--all_unittests', + dest='all_unittests', + default=False, + help='Run all tests we can find (*_unittests)') + parser.add_option('-b', + '--all_browsertests', + dest='all_browsertests', + default=False, + help='Run all tests in browser_tests ' + 'and content_browsertests') + parser.add_option('-g', + '--genhtml', + dest='genhtml', + default=False, + help='Generate html from lcov output') + parser.add_option('-f', + '--fast_test', + dest='fast_test', + default=False, + help='Make the tests run REAL fast by doing little.') + parser.add_option('-s', + '--strict', + dest='strict', + default=False, + help='Be strict and die on test failure.') + parser.add_option('-S', + '--src_root', + dest='src_root', + default='.', + help='Source root (only used on Windows)') + parser.add_option('-t', + '--trim', + dest='trim', + default=True, + help='Trim out tests? Default True.') + parser.add_option('-x', + '--xvfb', + dest='xvfb', + default=True, + help='Use Xvfb for tests? Default True.') + parser.add_option('-T', + '--timeout', + dest='timeout', + default=5.0 * 60.0, + type="int", + help='Timeout before bailing if a subprocess has no output.' + ' Default is 5min (Buildbot is 10min.)') + parser.add_option('-B', + '--bundles', + dest='bundles', + default=None, + help='Filename of bundles for coverage.') + parser.add_option('--build-dir', + dest='build_dir', + default=None, + help=('Working directory for buildbot build.' + 'used for finding bundlefile.')) + parser.add_option('--target', + dest='target', + default=None, + help=('Buildbot build target; ' + 'used for finding bundlefile (e.g. Debug)')) + parser.add_option('--no_exclusions', + dest='no_exclusions', + default=None, + help=('Disable the exclusion list.')) + parser.add_option('--dont-clear-coverage-data', + dest='dont_clear_coverage_data', + default=False, + action='store_true', + help=('Turn off clearing of cov data from a prev run')) + parser.add_option('-F', + '--test-file', + dest="test_files", + default=[], + action='append', + help=('Specify a file from which tests to be run will ' + + 'be extracted')) + return parser + + +def main(): + # Print out the args to help someone do it by hand if needed + print >>sys.stderr, sys.argv + + # Try and clean up nice if we're killed by buildbot, Ctrl-C, ... + signal.signal(signal.SIGINT, TerminateSignalHandler) + signal.signal(signal.SIGTERM, TerminateSignalHandler) + + parser = CoverageOptionParser() + (options, args) = parser.parse_args() + if options.all_unittests and options.all_browsertests: + print 'Error! Can not have all_unittests and all_browsertests together!' + sys.exit(1) + coverage = Coverage(options, args) + coverage.ClearData() + coverage.FindTests() + if options.trim: + coverage.TrimTests() + coverage.RunTests() + if options.genhtml: + coverage.GenerateHtml() + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/code_coverage/coverage_posix_unittest.py b/tools/code_coverage/coverage_posix_unittest.py new file mode 100755 index 0000000000..3164cd3359 --- /dev/null +++ b/tools/code_coverage/coverage_posix_unittest.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python +# Copyright (c) 2010 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Unit tests for coverage_posix.py. + +Run a single test with a command such as: + ./coverage_posix_unittest.py CoveragePosixTest.testFindTestsAsArgs + +Waring that running a single test like that may interfere with the arg +parsing tests, since coverage_posix.py uses optparse.OptionParser() +which references globals. +""" + +import coverage_posix as coverage +import os +import sys +import tempfile +import unittest + +class CoveragePosixTest(unittest.TestCase): + + + def setUp(self): + self.parseArgs() + self.sample_test_names = ['zippy_tests', '../base/base.gyp:base_unittests'] + + def confirmSampleTestsArePresent(self, tests): + """Confirm the tests in self.sample_test_names are in some form in 'tests'. + + The Coverage object can munge them (e.g. add .exe to the end as needed. + Helper function for arg parsing, bundle file tests. + + Args: + tests: the parsed tests from a Coverage object. + """ + for simple_test_name in ('zippy_tests', 'base_unittests'): + found = False + for item in tests: + if simple_test_name in item: + found = True + break + self.assertTrue(found) + for not_test_name in ('kablammo', 'not_a_unittest'): + found = False + for item in tests: + if not_test_name in item: + found = True + break + self.assertFalse(found) + + def parseArgs(self): + """Setup and process arg parsing.""" + self.parser = coverage.CoverageOptionParser() + (self.options, self.args) = self.parser.parse_args() + self.options.directory = '.' + + def testSanity(self): + """Sanity check we're able to actually run the tests. + + Simply creating a Coverage instance checks a few things (e.g. on + Windows that the coverage tools can be found).""" + c = coverage.Coverage(self.options, self.args) + + def testRunBasicProcess(self): + """Test a simple run of a subprocess.""" + c = coverage.Coverage(self.options, self.args) + for code in range(2): + retcode = c.Run([sys.executable, '-u', '-c', + 'import sys; sys.exit(%d)' % code], + ignore_error=True) + self.assertEqual(code, retcode) + + def testRunSlowProcess(self): + """Test program which prints slowly but doesn't hit our timeout. + + Overall runtime is longer than the timeout but output lines + trickle in keeping things alive. + """ + self.options.timeout = 2.5 + c = coverage.Coverage(self.options, self.args) + slowscript = ('import sys, time\n' + 'for x in range(10):\n' + ' time.sleep(0.5)\n' + ' print "hi mom"\n' + 'sys.exit(0)\n') + retcode = c.Run([sys.executable, '-u', '-c', slowscript]) + self.assertEqual(0, retcode) + + def testRunExcessivelySlowProcess(self): + """Test program which DOES hit our timeout. + + Initial lines should print but quickly it takes too long and + should be killed. + """ + self.options.timeout = 2.5 + c = coverage.Coverage(self.options, self.args) + slowscript = ('import time\n' + 'for x in range(1,10):\n' + ' print "sleeping for %d" % x\n' + ' time.sleep(x)\n') + self.assertRaises(Exception, + c.Run, + [sys.executable, '-u', '-c', slowscript]) + + def testFindTestsAsArgs(self): + """Test finding of tests passed as args.""" + self.args += '--' + self.args += self.sample_test_names + c = coverage.Coverage(self.options, self.args) + c.FindTests() + self.confirmSampleTestsArePresent(c.tests) + + def testFindTestsFromBundleFile(self): + """Test finding of tests from a bundlefile.""" + (fd, filename) = tempfile.mkstemp() + f = os.fdopen(fd, 'w') + f.write(str(self.sample_test_names)) + f.close() + self.options.bundles = filename + c = coverage.Coverage(self.options, self.args) + c.FindTests() + self.confirmSampleTestsArePresent(c.tests) + os.unlink(filename) + + def testExclusionList(self): + """Test the gtest_filter exclusion list.""" + c = coverage.Coverage(self.options, self.args) + self.assertFalse(c.GtestFilter('doesnotexist_test')) + fake_exclusions = { sys.platform: { 'foobar': + ('a','b'), + 'doesnotexist_test': + ('Evil.Crash','Naughty.Test') } } + self.assertFalse(c.GtestFilter('barfoo')) + filter = c.GtestFilter('doesnotexist_test', fake_exclusions) + self.assertEquals('--gtest_filter=-Evil.Crash:-Naughty.Test', filter) + + + +if __name__ == '__main__': + unittest.main() diff --git a/tools/code_coverage/croc.css b/tools/code_coverage/croc.css new file mode 100644 index 0000000000..071822dafa --- /dev/null +++ b/tools/code_coverage/croc.css @@ -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. + */ + +/* + * croc.css - styles for croc HTML output + */ + +body { + font-family:arial; +} + +table { + border-collapse:collapse; + border-width:0px; + border-style:solid; +} + +thead { + background-color:#C0C0E0; + text-align:center; +} + +td { + padding-right:10px; + padding-left:10px; + font-size:small; + border-width:1px; + border-style:solid; + border-color:black; +} + +td.secdesc { + text-align:center; + font-size:medium; + font-weight:bold; + border-width:0px; + border-style:none; + padding-top:10px; + padding-bottom:5px; +} + +td.section { + background-color:#D0D0F0; + text-align:center; +} + +td.stat { + text-align:center; +} + +td.number { + text-align:right; +} + +td.graph { + /* Hide the dummy character */ + color:#FFFFFF; + padding-left:6px; +} + +td.high_pct { + text-align:right; + background-color:#B0FFB0; +} +td.mid_pct { + text-align:right; + background-color:#FFFF90; +} +td.low_pct { + text-align:right; + background-color:#FFB0B0; +} + + +span.missing { + background-color:#FFB0B0; +} +span.instr { + background-color:#FFFF90; +} +span.covered { + background-color:#B0FFB0; +} + +span.g_missing { + background-color:#FF4040; +} +span.g_instr { + background-color:#FFFF00; +} +span.g_covered { + background-color:#40FF40; +} + +p.time { + padding-top:10px; + font-size:small; + font-style:italic; +} diff --git a/tools/code_coverage/croc.py b/tools/code_coverage/croc.py new file mode 100755 index 0000000000..1b9908a5f8 --- /dev/null +++ b/tools/code_coverage/croc.py @@ -0,0 +1,722 @@ +#!/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. + +"""Crocodile - compute coverage numbers for Chrome coverage dashboard.""" + +import optparse +import os +import platform +import re +import sys +import croc_html +import croc_scan + + +class CrocError(Exception): + """Coverage error.""" + + +class CrocStatError(CrocError): + """Error evaluating coverage stat.""" + +#------------------------------------------------------------------------------ + + +class CoverageStats(dict): + """Coverage statistics.""" + + # Default dictionary values for this stat. + DEFAULTS = { 'files_covered': 0, + 'files_instrumented': 0, + 'files_executable': 0, + 'lines_covered': 0, + 'lines_instrumented': 0, + 'lines_executable': 0 } + + def Add(self, coverage_stats): + """Adds a contribution from another coverage stats dict. + + Args: + coverage_stats: Statistics to add to this one. + """ + for k, v in coverage_stats.iteritems(): + if k in self: + self[k] += v + else: + self[k] = v + + def AddDefaults(self): + """Add some default stats which might be assumed present. + + Do not clobber if already present. Adds resilience when evaling a + croc file which expects certain stats to exist.""" + for k, v in self.DEFAULTS.iteritems(): + if not k in self: + self[k] = v + +#------------------------------------------------------------------------------ + + +class CoveredFile(object): + """Information about a single covered file.""" + + def __init__(self, filename, **kwargs): + """Constructor. + + Args: + filename: Full path to file, '/'-delimited. + kwargs: Keyword args are attributes for file. + """ + self.filename = filename + self.attrs = dict(kwargs) + + # Move these to attrs? + self.local_path = None # Local path to file + self.in_lcov = False # Is file instrumented? + + # No coverage data for file yet + self.lines = {} # line_no -> None=executable, 0=instrumented, 1=covered + self.stats = CoverageStats() + + def UpdateCoverage(self): + """Updates the coverage summary based on covered lines.""" + exe = instr = cov = 0 + for l in self.lines.itervalues(): + exe += 1 + if l is not None: + instr += 1 + if l == 1: + cov += 1 + + # Add stats that always exist + self.stats = CoverageStats(lines_executable=exe, + lines_instrumented=instr, + lines_covered=cov, + files_executable=1) + + # Add conditional stats + if cov: + self.stats['files_covered'] = 1 + if instr or self.in_lcov: + self.stats['files_instrumented'] = 1 + +#------------------------------------------------------------------------------ + + +class CoveredDir(object): + """Information about a directory containing covered files.""" + + def __init__(self, dirpath): + """Constructor. + + Args: + dirpath: Full path of directory, '/'-delimited. + """ + self.dirpath = dirpath + + # List of covered files directly in this dir, indexed by filename (not + # full path) + self.files = {} + + # List of subdirs, indexed by filename (not full path) + self.subdirs = {} + + # Dict of CoverageStats objects summarizing all children, indexed by group + self.stats_by_group = {'all': CoverageStats()} + # TODO: by language + + def GetTree(self, indent=''): + """Recursively gets stats for the directory and its children. + + Args: + indent: indent prefix string. + + Returns: + The tree as a string. + """ + dest = [] + + # Compile all groupstats + groupstats = [] + for group in sorted(self.stats_by_group): + s = self.stats_by_group[group] + if not s.get('lines_executable'): + continue # Skip groups with no executable lines + groupstats.append('%s:%d/%d/%d' % ( + group, s.get('lines_covered', 0), + s.get('lines_instrumented', 0), + s.get('lines_executable', 0))) + + outline = '%s%-30s %s' % (indent, + os.path.split(self.dirpath)[1] + '/', + ' '.join(groupstats)) + dest.append(outline.rstrip()) + + for d in sorted(self.subdirs): + dest.append(self.subdirs[d].GetTree(indent=indent + ' ')) + + return '\n'.join(dest) + +#------------------------------------------------------------------------------ + + +class Coverage(object): + """Code coverage for a group of files.""" + + def __init__(self): + """Constructor.""" + self.files = {} # Map filename --> CoverageFile + self.root_dirs = [] # (root, altname) + self.rules = [] # (regexp, dict of RHS attrs) + self.tree = CoveredDir('') + self.print_stats = [] # Dicts of args to PrintStat() + + # Functions which need to be replaced for unit testing + self.add_files_walk = os.walk # Walk function for AddFiles() + self.scan_file = croc_scan.ScanFile # Source scanner for AddFiles() + + def CleanupFilename(self, filename): + """Cleans up a filename. + + Args: + filename: Input filename. + + Returns: + The cleaned up filename. + + Changes all path separators to '/'. + Makes relative paths (those starting with '../' or './' absolute. + Replaces all instances of root dirs with alternate names. + """ + # Change path separators + filename = filename.replace('\\', '/') + + # Windows doesn't care about case sensitivity. + if platform.system() in ['Windows', 'Microsoft']: + filename = filename.lower() + + # If path is relative, make it absolute + # TODO: Perhaps we should default to relative instead, and only understand + # absolute to be files starting with '\', '/', or '[A-Za-z]:'? + if filename.split('/')[0] in ('.', '..'): + filename = os.path.abspath(filename).replace('\\', '/') + + # Replace alternate roots + for root, alt_name in self.root_dirs: + # Windows doesn't care about case sensitivity. + if platform.system() in ['Windows', 'Microsoft']: + root = root.lower() + filename = re.sub('^' + re.escape(root) + '(?=(/|$))', + alt_name, filename) + return filename + + def ClassifyFile(self, filename): + """Applies rules to a filename, to see if we care about it. + + Args: + filename: Input filename. + + Returns: + A dict of attributes for the file, accumulated from the right hand sides + of rules which fired. + """ + attrs = {} + + # Process all rules + for regexp, rhs_dict in self.rules: + if regexp.match(filename): + attrs.update(rhs_dict) + + return attrs + # TODO: Files can belong to multiple groups? + # (test/source) + # (mac/pc/win) + # (media_test/all_tests) + # (small/med/large) + # How to handle that? + + def AddRoot(self, root_path, alt_name='_'): + """Adds a root directory. + + Args: + root_path: Root directory to add. + alt_name: If specified, name of root dir. Otherwise, defaults to '_'. + + Raises: + ValueError: alt_name was blank. + """ + # Alt name must not be blank. If it were, there wouldn't be a way to + # reverse-resolve from a root-replaced path back to the local path, since + # '' would always match the beginning of the candidate filename, resulting + # in an infinite loop. + if not alt_name: + raise ValueError('AddRoot alt_name must not be blank.') + + # Clean up root path based on existing rules + self.root_dirs.append([self.CleanupFilename(root_path), alt_name]) + + def AddRule(self, path_regexp, **kwargs): + """Adds a rule. + + Args: + path_regexp: Regular expression to match for filenames. These are + matched after root directory replacement. + kwargs: Keyword arguments are attributes to set if the rule applies. + + Keyword arguments currently supported: + include: If True, includes matches; if False, excludes matches. Ignored + if None. + group: If not None, sets group to apply to matches. + language: If not None, sets file language to apply to matches. + """ + + # Compile regexp ahead of time + self.rules.append([re.compile(path_regexp), dict(kwargs)]) + + def GetCoveredFile(self, filename, add=False): + """Gets the CoveredFile object for the filename. + + Args: + filename: Name of file to find. + add: If True, will add the file if it's not present. This applies the + transformations from AddRoot() and AddRule(), and only adds the file + if a rule includes it, and it has a group and language. + + Returns: + The matching CoveredFile object, or None if not present. + """ + # Clean filename + filename = self.CleanupFilename(filename) + + # Check for existing match + if filename in self.files: + return self.files[filename] + + # File isn't one we know about. If we can't add it, give up. + if not add: + return None + + # Check rules to see if file can be added. Files must be included and + # have a group and language. + attrs = self.ClassifyFile(filename) + if not (attrs.get('include') + and attrs.get('group') + and attrs.get('language')): + return None + + # Add the file + f = CoveredFile(filename, **attrs) + self.files[filename] = f + + # Return the newly covered file + return f + + def RemoveCoveredFile(self, cov_file): + """Removes the file from the covered file list. + + Args: + cov_file: A file object returned by GetCoveredFile(). + """ + self.files.pop(cov_file.filename) + + def ParseLcovData(self, lcov_data): + """Adds coverage from LCOV-formatted data. + + Args: + lcov_data: An iterable returning lines of data in LCOV format. For + example, a file or list of strings. + """ + cov_file = None + cov_lines = None + for line in lcov_data: + line = line.strip() + if line.startswith('SF:'): + # Start of data for a new file; payload is filename + cov_file = self.GetCoveredFile(line[3:], add=True) + if cov_file: + cov_lines = cov_file.lines + cov_file.in_lcov = True # File was instrumented + elif not cov_file: + # Inside data for a file we don't care about - so skip it + pass + elif line.startswith('DA:'): + # Data point - that is, an executable line in current file + line_no, is_covered = map(int, line[3:].split(',')) + if is_covered: + # Line is covered + cov_lines[line_no] = 1 + elif cov_lines.get(line_no) != 1: + # Line is not covered, so track it as uncovered + cov_lines[line_no] = 0 + elif line == 'end_of_record': + cov_file.UpdateCoverage() + cov_file = None + # (else ignore other line types) + + def ParseLcovFile(self, input_filename): + """Adds coverage data from a .lcov file. + + Args: + input_filename: Input filename. + """ + # TODO: All manner of error checking + lcov_file = None + try: + lcov_file = open(input_filename, 'rt') + self.ParseLcovData(lcov_file) + finally: + if lcov_file: + lcov_file.close() + + def GetStat(self, stat, group='all', default=None): + """Gets a statistic from the coverage object. + + Args: + stat: Statistic to get. May also be an evaluatable python expression, + using the stats. For example, 'stat1 - stat2'. + group: File group to match; if 'all', matches all groups. + default: Value to return if there was an error evaluating the stat. For + example, if the stat does not exist. If None, raises + CrocStatError. + + Returns: + The evaluated stat, or None if error. + + Raises: + CrocStatError: Error evaluating stat. + """ + # TODO: specify a subdir to get the stat from, then walk the tree to + # print the stats from just that subdir + + # Make sure the group exists + if group not in self.tree.stats_by_group: + if default is None: + raise CrocStatError('Group %r not found.' % group) + else: + return default + + stats = self.tree.stats_by_group[group] + # Unit tests use real dicts, not CoverageStats objects, + # so we can't AddDefaults() on them. + if group == 'all' and hasattr(stats, 'AddDefaults'): + stats.AddDefaults() + try: + return eval(stat, {'__builtins__': {'S': self.GetStat}}, stats) + except Exception, e: + if default is None: + raise CrocStatError('Error evaluating stat %r: %s' % (stat, e)) + else: + return default + + def PrintStat(self, stat, format=None, outfile=sys.stdout, **kwargs): + """Prints a statistic from the coverage object. + + Args: + stat: Statistic to get. May also be an evaluatable python expression, + using the stats. For example, 'stat1 - stat2'. + format: Format string to use when printing stat. If None, prints the + stat and its evaluation. + outfile: File stream to output stat to; defaults to stdout. + kwargs: Additional args to pass to GetStat(). + """ + s = self.GetStat(stat, **kwargs) + if format is None: + outfile.write('GetStat(%r) = %s\n' % (stat, s)) + else: + outfile.write(format % s + '\n') + + def AddFiles(self, src_dir): + """Adds files to coverage information. + + LCOV files only contains files which are compiled and instrumented as part + of running coverage. This function finds missing files and adds them. + + Args: + src_dir: Directory on disk at which to start search. May be a relative + path on disk starting with '.' or '..', or an absolute path, or a + path relative to an alt_name for one of the roots + (for example, '_/src'). If the alt_name matches more than one root, + all matches will be attempted. + + Note that dirs not underneath one of the root dirs and covered by an + inclusion rule will be ignored. + """ + # Check for root dir alt_names in the path and replace with the actual + # root dirs, then recurse. + found_root = False + for root, alt_name in self.root_dirs: + replaced_root = re.sub('^' + re.escape(alt_name) + '(?=(/|$))', root, + src_dir) + if replaced_root != src_dir: + found_root = True + self.AddFiles(replaced_root) + if found_root: + return # Replaced an alt_name with a root_dir, so already recursed. + + for (dirpath, dirnames, filenames) in self.add_files_walk(src_dir): + # Make a copy of the dirnames list so we can modify the original to + # prune subdirs we don't need to walk. + for d in list(dirnames): + # Add trailing '/' to directory names so dir-based regexps can match + # '/' instead of needing to specify '(/|$)'. + dpath = self.CleanupFilename(dirpath + '/' + d) + '/' + attrs = self.ClassifyFile(dpath) + if not attrs.get('include'): + # Directory has been excluded, so don't traverse it + # TODO: Document the slight weirdness caused by this: If you + # AddFiles('./A'), and the rules include 'A/B/C/D' but not 'A/B', + # then it won't recurse into './A/B' so won't find './A/B/C/D'. + # Workarounds are to AddFiles('./A/B/C/D') or AddFiles('./A/B/C'). + # The latter works because it explicitly walks the contents of the + # path passed to AddFiles(), so it finds './A/B/C/D'. + dirnames.remove(d) + + for f in filenames: + local_path = dirpath + '/' + f + + covf = self.GetCoveredFile(local_path, add=True) + if not covf: + continue + + # Save where we found the file, for generating line-by-line HTML output + covf.local_path = local_path + + if covf.in_lcov: + # File already instrumented and doesn't need to be scanned + continue + + if not covf.attrs.get('add_if_missing', 1): + # Not allowed to add the file + self.RemoveCoveredFile(covf) + continue + + # Scan file to find potentially-executable lines + lines = self.scan_file(covf.local_path, covf.attrs.get('language')) + if lines: + for l in lines: + covf.lines[l] = None + covf.UpdateCoverage() + else: + # File has no executable lines, so don't count it + self.RemoveCoveredFile(covf) + + def AddConfig(self, config_data, lcov_queue=None, addfiles_queue=None): + """Adds JSON-ish config data. + + Args: + config_data: Config data string. + lcov_queue: If not None, object to append lcov_files to instead of + parsing them immediately. + addfiles_queue: If not None, object to append add_files to instead of + processing them immediately. + """ + # TODO: All manner of error checking + cfg = eval(config_data, {'__builtins__': {}}, {}) + + for rootdict in cfg.get('roots', []): + self.AddRoot(rootdict['root'], alt_name=rootdict.get('altname', '_')) + + for ruledict in cfg.get('rules', []): + regexp = ruledict.pop('regexp') + self.AddRule(regexp, **ruledict) + + for add_lcov in cfg.get('lcov_files', []): + if lcov_queue is not None: + lcov_queue.append(add_lcov) + else: + self.ParseLcovFile(add_lcov) + + for add_path in cfg.get('add_files', []): + if addfiles_queue is not None: + addfiles_queue.append(add_path) + else: + self.AddFiles(add_path) + + self.print_stats += cfg.get('print_stats', []) + + def ParseConfig(self, filename, **kwargs): + """Parses a configuration file. + + Args: + filename: Config filename. + kwargs: Additional parameters to pass to AddConfig(). + """ + # TODO: All manner of error checking + f = None + try: + f = open(filename, 'rt') + # Need to strip CR's from CRLF-terminated lines or posix systems can't + # eval the data. + config_data = f.read().replace('\r\n', '\n') + # TODO: some sort of include syntax. + # + # Needs to be done at string-time rather than at eval()-time, so that + # it's possible to include parts of dicts. Path from a file to its + # include should be relative to the dir containing the file. + # + # Or perhaps it could be done after eval. In that case, there'd be an + # 'include' section with a list of files to include. Those would be + # eval()'d and recursively pre- or post-merged with the including file. + # + # Or maybe just don't worry about it, since multiple configs can be + # specified on the command line. + self.AddConfig(config_data, **kwargs) + finally: + if f: + f.close() + + def UpdateTreeStats(self): + """Recalculates the tree stats from the currently covered files. + + Also calculates coverage summary for files. + """ + self.tree = CoveredDir('') + for cov_file in self.files.itervalues(): + # Add the file to the tree + fdirs = cov_file.filename.split('/') + parent = self.tree + ancestors = [parent] + for d in fdirs[:-1]: + if d not in parent.subdirs: + if parent.dirpath: + parent.subdirs[d] = CoveredDir(parent.dirpath + '/' + d) + else: + parent.subdirs[d] = CoveredDir(d) + parent = parent.subdirs[d] + ancestors.append(parent) + # Final subdir actually contains the file + parent.files[fdirs[-1]] = cov_file + + # Now add file's contribution to coverage by dir + for a in ancestors: + # Add to 'all' group + a.stats_by_group['all'].Add(cov_file.stats) + + # Add to group file belongs to + group = cov_file.attrs.get('group') + if group not in a.stats_by_group: + a.stats_by_group[group] = CoverageStats() + cbyg = a.stats_by_group[group] + cbyg.Add(cov_file.stats) + + def PrintTree(self): + """Prints the tree stats.""" + # Print the tree + print 'Lines of code coverage by directory:' + print self.tree.GetTree() + +#------------------------------------------------------------------------------ + + +def Main(argv): + """Main routine. + + Args: + argv: list of arguments + + Returns: + exit code, 0 for normal exit. + """ + # Parse args + parser = optparse.OptionParser() + parser.add_option( + '-i', '--input', dest='inputs', type='string', action='append', + metavar='FILE', + help='read LCOV input from FILE') + parser.add_option( + '-r', '--root', dest='roots', type='string', action='append', + metavar='ROOT[=ALTNAME]', + help='add ROOT directory, optionally map in coverage results as ALTNAME') + parser.add_option( + '-c', '--config', dest='configs', type='string', action='append', + metavar='FILE', + help='read settings from configuration FILE') + parser.add_option( + '-a', '--addfiles', dest='addfiles', type='string', action='append', + metavar='PATH', + help='add files from PATH to coverage data') + parser.add_option( + '-t', '--tree', dest='tree', action='store_true', + help='print tree of code coverage by group') + parser.add_option( + '-u', '--uninstrumented', dest='uninstrumented', action='store_true', + help='list uninstrumented files') + parser.add_option( + '-m', '--html', dest='html_out', type='string', metavar='PATH', + help='write HTML output to PATH') + parser.add_option( + '-b', '--base_url', dest='base_url', type='string', metavar='URL', + help='include URL in base tag of HTML output') + + parser.set_defaults( + inputs=[], + roots=[], + configs=[], + addfiles=[], + tree=False, + html_out=None, + ) + + options = parser.parse_args(args=argv)[0] + + cov = Coverage() + + # Set root directories for coverage + for root_opt in options.roots: + if '=' in root_opt: + cov.AddRoot(*root_opt.split('=')) + else: + cov.AddRoot(root_opt) + + # Read config files + for config_file in options.configs: + cov.ParseConfig(config_file, lcov_queue=options.inputs, + addfiles_queue=options.addfiles) + + # Parse lcov files + for input_filename in options.inputs: + cov.ParseLcovFile(input_filename) + + # Add missing files + for add_path in options.addfiles: + cov.AddFiles(add_path) + + # Print help if no files specified + if not cov.files: + print 'No covered files found.' + parser.print_help() + return 1 + + # Update tree stats + cov.UpdateTreeStats() + + # Print uninstrumented filenames + if options.uninstrumented: + print 'Uninstrumented files:' + for f in sorted(cov.files): + covf = cov.files[f] + if not covf.in_lcov: + print ' %-6s %-6s %s' % (covf.attrs.get('group'), + covf.attrs.get('language'), f) + + # Print tree stats + if options.tree: + cov.PrintTree() + + # Print stats + for ps_args in cov.print_stats: + cov.PrintStat(**ps_args) + + # Generate HTML + if options.html_out: + html = croc_html.CrocHtml(cov, options.html_out, options.base_url) + html.Write() + + # Normal exit + return 0 + + +if __name__ == '__main__': + sys.exit(Main(sys.argv)) diff --git a/tools/code_coverage/croc_html.py b/tools/code_coverage/croc_html.py new file mode 100644 index 0000000000..7866f472f7 --- /dev/null +++ b/tools/code_coverage/croc_html.py @@ -0,0 +1,451 @@ +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Crocodile HTML output.""" + +import os +import shutil +import time +import xml.dom + + +class CrocHtmlError(Exception): + """Coverage HTML error.""" + + +class HtmlElement(object): + """Node in a HTML file.""" + + def __init__(self, doc, element): + """Constructor. + + Args: + doc: XML document object. + element: XML element. + """ + self.doc = doc + self.element = element + + def E(self, name, **kwargs): + """Adds a child element. + + Args: + name: Name of element. + kwargs: Attributes for element. To use an attribute which is a python + reserved word (i.e. 'class'), prefix the attribute name with 'e_'. + + Returns: + The child element. + """ + he = HtmlElement(self.doc, self.doc.createElement(name)) + element = he.element + self.element.appendChild(element) + + for k, v in kwargs.iteritems(): + if k.startswith('e_'): + # Remove prefix + element.setAttribute(k[2:], str(v)) + else: + element.setAttribute(k, str(v)) + + return he + + def Text(self, text): + """Adds a text node. + + Args: + text: Text to add. + + Returns: + self. + """ + t = self.doc.createTextNode(str(text)) + self.element.appendChild(t) + return self + + +class HtmlFile(object): + """HTML file.""" + + def __init__(self, xml_impl, filename): + """Constructor. + + Args: + xml_impl: DOMImplementation to use to create document. + filename: Path to file. + """ + self.xml_impl = xml_impl + doctype = xml_impl.createDocumentType( + 'HTML', '-//W3C//DTD HTML 4.01//EN', + 'http://www.w3.org/TR/html4/strict.dtd') + self.doc = xml_impl.createDocument(None, 'html', doctype) + self.filename = filename + + # Create head and body elements + root = HtmlElement(self.doc, self.doc.documentElement) + self.head = root.E('head') + self.body = root.E('body') + + def Write(self, cleanup=True): + """Writes the file. + + Args: + cleanup: If True, calls unlink() on the internal xml document. This + frees up memory, but means that you can't use this file for anything + else. + """ + f = open(self.filename, 'wt') + self.doc.writexml(f, encoding='UTF-8') + f.close() + + if cleanup: + self.doc.unlink() + # Prevent future uses of the doc now that we've unlinked it + self.doc = None + +#------------------------------------------------------------------------------ + +COV_TYPE_STRING = {None: 'm', 0: 'i', 1: 'E', 2: ' '} +COV_TYPE_CLASS = {None: 'missing', 0: 'instr', 1: 'covered', 2: ''} + + +class CrocHtml(object): + """Crocodile HTML output class.""" + + def __init__(self, cov, output_root, base_url=None): + """Constructor.""" + self.cov = cov + self.output_root = output_root + self.base_url = base_url + self.xml_impl = xml.dom.getDOMImplementation() + self.time_string = 'Coverage information generated %s.' % time.asctime() + + def CreateHtmlDoc(self, filename, title): + """Creates a new HTML document. + + Args: + filename: Filename to write to, relative to self.output_root. + title: Title of page + + Returns: + The document. + """ + f = HtmlFile(self.xml_impl, self.output_root + '/' + filename) + + f.head.E('title').Text(title) + + if self.base_url: + css_href = self.base_url + 'croc.css' + base_href = self.base_url + os.path.dirname(filename) + if not base_href.endswith('/'): + base_href += '/' + f.head.E('base', href=base_href) + else: + css_href = '../' * (len(filename.split('/')) - 1) + 'croc.css' + + f.head.E('link', rel='stylesheet', type='text/css', href=css_href) + + return f + + def AddCaptionForFile(self, body, path): + """Adds a caption for the file, with links to each parent dir. + + Args: + body: Body elemement. + path: Path to file. + """ + # This is slightly different that for subdir, because it needs to have a + # link to the current directory's index.html. + hdr = body.E('h2') + hdr.Text('Coverage for ') + dirs = [''] + path.split('/') + num_dirs = len(dirs) + for i in range(num_dirs - 1): + hdr.E('a', href=( + '../' * (num_dirs - i - 2) + 'index.html')).Text(dirs[i] + '/') + hdr.Text(dirs[-1]) + + def AddCaptionForSubdir(self, body, path): + """Adds a caption for the subdir, with links to each parent dir. + + Args: + body: Body elemement. + path: Path to subdir. + """ + # Link to parent dirs + hdr = body.E('h2') + hdr.Text('Coverage for ') + dirs = [''] + path.split('/') + num_dirs = len(dirs) + for i in range(num_dirs - 1): + hdr.E('a', href=( + '../' * (num_dirs - i - 1) + 'index.html')).Text(dirs[i] + '/') + hdr.Text(dirs[-1] + '/') + + def AddSectionHeader(self, table, caption, itemtype, is_file=False): + """Adds a section header to the coverage table. + + Args: + table: Table to add rows to. + caption: Caption for section, if not None. + itemtype: Type of items in this section, if not None. + is_file: Are items in this section files? + """ + + if caption is not None: + table.E('tr').E('th', e_class='secdesc', colspan=8).Text(caption) + + sec_hdr = table.E('tr') + + if itemtype is not None: + sec_hdr.E('th', e_class='section').Text(itemtype) + + sec_hdr.E('th', e_class='section').Text('Coverage') + sec_hdr.E('th', e_class='section', colspan=3).Text( + 'Lines executed / instrumented / missing') + + graph = sec_hdr.E('th', e_class='section') + graph.E('span', style='color:#00FF00').Text('exe') + graph.Text(' / ') + graph.E('span', style='color:#FFFF00').Text('inst') + graph.Text(' / ') + graph.E('span', style='color:#FF0000').Text('miss') + + if is_file: + sec_hdr.E('th', e_class='section').Text('Language') + sec_hdr.E('th', e_class='section').Text('Group') + else: + sec_hdr.E('th', e_class='section', colspan=2) + + def AddItem(self, table, itemname, stats, attrs, link=None): + """Adds a bar graph to the element. This is a series of elements. + + Args: + table: Table to add item to. + itemname: Name of item. + stats: Stats object. + attrs: Attributes dictionary; if None, no attributes will be printed. + link: Destination for itemname hyperlink, if not None. + """ + row = table.E('tr') + + # Add item name + if itemname is not None: + item_elem = row.E('td') + if link is not None: + item_elem = item_elem.E('a', href=link) + item_elem.Text(itemname) + + # Get stats + stat_exe = stats.get('lines_executable', 0) + stat_ins = stats.get('lines_instrumented', 0) + stat_cov = stats.get('lines_covered', 0) + + percent = row.E('td') + + # Add text + row.E('td', e_class='number').Text(stat_cov) + row.E('td', e_class='number').Text(stat_ins) + row.E('td', e_class='number').Text(stat_exe - stat_ins) + + # Add percent and graph; only fill in if there's something in there + graph = row.E('td', e_class='graph', width=100) + if stat_exe: + percent_cov = 100.0 * stat_cov / stat_exe + percent_ins = 100.0 * stat_ins / stat_exe + + # Color percent based on thresholds + percent.Text('%.1f%%' % percent_cov) + if percent_cov >= 80: + percent.element.setAttribute('class', 'high_pct') + elif percent_cov >= 60: + percent.element.setAttribute('class', 'mid_pct') + else: + percent.element.setAttribute('class', 'low_pct') + + # Graphs use integer values + percent_cov = int(percent_cov) + percent_ins = int(percent_ins) + + graph.Text('.') + graph.E('span', style='padding-left:%dpx' % percent_cov, + e_class='g_covered') + graph.E('span', style='padding-left:%dpx' % (percent_ins - percent_cov), + e_class='g_instr') + graph.E('span', style='padding-left:%dpx' % (100 - percent_ins), + e_class='g_missing') + + if attrs: + row.E('td', e_class='stat').Text(attrs.get('language')) + row.E('td', e_class='stat').Text(attrs.get('group')) + else: + row.E('td', colspan=2) + + def WriteFile(self, cov_file): + """Writes the HTML for a file. + + Args: + cov_file: croc.CoveredFile to write. + """ + print ' ' + cov_file.filename + title = 'Coverage for ' + cov_file.filename + + f = self.CreateHtmlDoc(cov_file.filename + '.html', title) + body = f.body + + # Write header section + self.AddCaptionForFile(body, cov_file.filename) + + # Summary for this file + table = body.E('table') + self.AddSectionHeader(table, None, None, is_file=True) + self.AddItem(table, None, cov_file.stats, cov_file.attrs) + + body.E('h2').Text('Line-by-line coverage:') + + # Print line-by-line coverage + if cov_file.local_path: + code_table = body.E('table').E('tr').E('td').E('pre') + + flines = open(cov_file.local_path, 'rt') + lineno = 0 + + for line in flines: + lineno += 1 + line_cov = cov_file.lines.get(lineno, 2) + e_class = COV_TYPE_CLASS.get(line_cov) + + code_table.E('span', e_class=e_class).Text('%4d %s : %s\n' % ( + lineno, + COV_TYPE_STRING.get(line_cov), + line.rstrip() + )) + + else: + body.Text('Line-by-line coverage not available. Make sure the directory' + ' containing this file has been scanned via ') + body.E('B').Text('add_files') + body.Text(' in a configuration file, or the ') + body.E('B').Text('--addfiles') + body.Text(' command line option.') + + # TODO: if file doesn't have a local path, try to find it by + # reverse-mapping roots and searching for the file. + + body.E('p', e_class='time').Text(self.time_string) + f.Write() + + def WriteSubdir(self, cov_dir): + """Writes the index.html for a subdirectory. + + Args: + cov_dir: croc.CoveredDir to write. + """ + print ' ' + cov_dir.dirpath + '/' + + # Create the subdir if it doesn't already exist + subdir = self.output_root + '/' + cov_dir.dirpath + if not os.path.exists(subdir): + os.mkdir(subdir) + + if cov_dir.dirpath: + title = 'Coverage for ' + cov_dir.dirpath + '/' + f = self.CreateHtmlDoc(cov_dir.dirpath + '/index.html', title) + else: + title = 'Coverage summary' + f = self.CreateHtmlDoc('index.html', title) + + body = f.body + + dirs = [''] + cov_dir.dirpath.split('/') + num_dirs = len(dirs) + sort_jsfile = '../' * (num_dirs - 1) + 'sorttable.js' + script = body.E('script', src=sort_jsfile) + body.E('/script') + + # Write header section + if cov_dir.dirpath: + self.AddCaptionForSubdir(body, cov_dir.dirpath) + else: + body.E('h2').Text(title) + + table = body.E('table', e_class='sortable') + table.E('h3').Text('Coverage by Group') + # Coverage by group + self.AddSectionHeader(table, None, 'Group') + + for group in sorted(cov_dir.stats_by_group): + self.AddItem(table, group, cov_dir.stats_by_group[group], None) + + # List subdirs + if cov_dir.subdirs: + table = body.E('table', e_class='sortable') + table.E('h3').Text('Subdirectories') + self.AddSectionHeader(table, None, 'Subdirectory') + + for d in sorted(cov_dir.subdirs): + self.AddItem(table, d + '/', cov_dir.subdirs[d].stats_by_group['all'], + None, link=d + '/index.html') + + # List files + if cov_dir.files: + table = body.E('table', e_class='sortable') + table.E('h3').Text('Files in This Directory') + self.AddSectionHeader(table, None, 'Filename', + is_file=True) + + for filename in sorted(cov_dir.files): + cov_file = cov_dir.files[filename] + self.AddItem(table, filename, cov_file.stats, cov_file.attrs, + link=filename + '.html') + + body.E('p', e_class='time').Text(self.time_string) + f.Write() + + def WriteRoot(self): + """Writes the files in the output root.""" + # Find ourselves + src_dir = os.path.split(self.WriteRoot.func_code.co_filename)[0] + + # Files to copy into output root + copy_files = ['croc.css'] + # Third_party files to copy into output root + third_party_files = ['sorttable.js'] + + # Copy files from our directory into the output directory + for copy_file in copy_files: + print ' Copying %s' % copy_file + shutil.copyfile(os.path.join(src_dir, copy_file), + os.path.join(self.output_root, copy_file)) + # Copy third party files from third_party directory into + # the output directory + src_dir = os.path.join(src_dir, 'third_party') + for third_party_file in third_party_files: + print ' Copying %s' % third_party_file + shutil.copyfile(os.path.join(src_dir, third_party_file), + os.path.join(self.output_root, third_party_file)) + + def Write(self): + """Writes HTML output.""" + + print 'Writing HTML to %s...' % self.output_root + + # Loop through the tree and write subdirs, breadth-first + # TODO: switch to depth-first and sort values - makes nicer output? + todo = [self.cov.tree] + while todo: + cov_dir = todo.pop(0) + + # Append subdirs to todo list + todo += cov_dir.subdirs.values() + + # Write this subdir + self.WriteSubdir(cov_dir) + + # Write files in this subdir + for cov_file in cov_dir.files.itervalues(): + self.WriteFile(cov_file) + + # Write files in root directory + self.WriteRoot() diff --git a/tools/code_coverage/croc_scan.py b/tools/code_coverage/croc_scan.py new file mode 100644 index 0000000000..8d0e2e8df2 --- /dev/null +++ b/tools/code_coverage/croc_scan.py @@ -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. + +"""Crocodile source scanners.""" + + +import re + + +class Scanner(object): + """Generic source scanner.""" + + def __init__(self): + """Constructor.""" + + self.re_token = re.compile('#') + self.comment_to_eol = ['#'] + self.comment_start = None + self.comment_end = None + + def ScanLines(self, lines): + """Scans the lines for executable statements. + + Args: + lines: Iterator returning source lines. + + Returns: + An array of line numbers which are executable. + """ + exe_lines = [] + lineno = 0 + + in_string = None + in_comment = None + comment_index = None + + for line in lines: + lineno += 1 + in_string_at_start = in_string + + for t in self.re_token.finditer(line): + tokenstr = t.groups()[0] + + if in_comment: + # Inside a multi-line comment, so look for end token + if tokenstr == in_comment: + in_comment = None + # Replace comment with spaces + line = (line[:comment_index] + + ' ' * (t.end(0) - comment_index) + + line[t.end(0):]) + + elif in_string: + # Inside a string, so look for end token + if tokenstr == in_string: + in_string = None + + elif tokenstr in self.comment_to_eol: + # Single-line comment, so truncate line at start of token + line = line[:t.start(0)] + break + + elif tokenstr == self.comment_start: + # Multi-line comment start - end token is comment_end + in_comment = self.comment_end + comment_index = t.start(0) + + else: + # Starting a string - end token is same as start + in_string = tokenstr + + # If still in comment at end of line, remove comment + if in_comment: + line = line[:comment_index] + # Next line, delete from the beginnine + comment_index = 0 + + # If line-sans-comments is not empty, claim it may be executable + if line.strip() or in_string_at_start: + exe_lines.append(lineno) + + # Return executable lines + return exe_lines + + def Scan(self, filename): + """Reads the file and scans its lines. + + Args: + filename: Path to file to scan. + + Returns: + An array of line numbers which are executable. + """ + + # TODO: All manner of error checking + f = None + try: + f = open(filename, 'rt') + return self.ScanLines(f) + finally: + if f: + f.close() + + +class PythonScanner(Scanner): + """Python source scanner.""" + + def __init__(self): + """Constructor.""" + Scanner.__init__(self) + + # TODO: This breaks for strings ending in more than 2 backslashes. Need + # a pattern which counts only an odd number of backslashes, so the last + # one thus escapes the quote. + self.re_token = re.compile(r'(#|\'\'\'|"""|(?_.lcov. + This method parses the file name and then sets up the correct folder + hierarchy for the coverage data and then runs genhtml to get the actual HTML + formatted coverage data. + + Args: + lcov_path: Path of the lcov data file. + dash_root: Root location of the dashboard. + + Returns: + Code coverage percentage on sucess. + None on failure. + """ + # Parse the LCOV file name. + filename = os.path.basename(lcov_path).split('.')[0] + buffer = filename.split('_') + dash_root = dash_root.rstrip('/') # Remove trailing '/' + + # Set up correct folder hierarchy in the dashboard root + # TODO(niranjan): Check the formatting using a regexp + if len(buffer) >= 3: # Check if filename has right formatting + platform = buffer[len(buffer) - 2] + revision = buffer[len(buffer) - 1] + if os.path.exists(os.path.join(dash_root, platform)) == False: + os.mkdir(os.path.join(dash_root, platform)) + output_dir = os.path.join(dash_root, platform, revision) + os.mkdir(output_dir) + else: + # TODO(niranjan): Add failure logging here. + return None # File not formatted correctly + + # Run genhtml + os.system('/usr/bin/genhtml -o %s %s' % (output_dir, lcov_path)) + # TODO(niranjan): Check the exit status of the genhtml command. + # TODO(niranjan): Parse the stdout and return coverage percentage. + CleanPathNames(output_dir) + return 'dummy' # TODO(niranjan): Return actual percentage. + + +def CleanWin32Lcov(lcov_path, src_root): + """Cleanup the lcov data generated on Windows. + + This method fixes up the paths inside the lcov file from the Win32 specific + paths to the actual paths of the mounted CIFS share. The lcov files generated + on Windows have the following format: + + SF:c:\chrome_src\src\skia\sgl\skscan_antihair.cpp + DA:97,0 + DA:106,0 + DA:107,0 + DA:109,0 + ... + end_of_record + + This method changes the source-file (SF) lines to a format compatible with + genhtml on Linux by fixing paths. This method also removes references to + certain dynamically generated files to be excluded from the code ceverage. + + Args: + lcov_path: Path of the Win32 lcov file to be cleaned. + src_root: Location of the source and symbols dir. + Returns: + None + """ + strip_flag = False + lcov = open(lcov_path, 'r') + loc_csv_file = open(lcov_path + '.csv', 'w') + (tmpfile_id, tmpfile_name) = tempfile.mkstemp() + tmpfile = open(tmpfile_name, 'w') + src_root = src_root.rstrip('/') # Remove trailing '/' + for line in lcov: + if line.startswith('SF'): + # We want to exclude certain auto-generated files otherwise genhtml will + # fail to convert lcov to HTML. + for exp in win32_srcs_exclude: + if line.rfind(exp) != -1: + strip_flag = True # Indicates that we want to remove this section + + # Now we normalize the paths + # e.g. Change SF:c:\foo\src\... to SF:/chrome_src/... + parse_buffer = line.split(':') + buffer = '%s:%s%s' % (parse_buffer[0], + src_root, + parse_buffer[2]) + buffer = buffer.replace('\\', '/') + line = buffer.replace('\r', '') + + # We want an accurate count of the lines of code in a given file so that + # we can estimate the code coverage perscentage accurately. We use a + # third party script cloc.pl which gives that count and then just parse + # its command line output to filter out the other unnecessary data. + # TODO(niranjan): Find out a better way of doing this. + buffer = buffer.lstrip('SF:') + file_for_loc = buffer.replace('\r\n', '') + # TODO(niranjan): Add a check to see if cloc is present on the machine. + command = ["perl", + "cloc.pl", + file_for_loc] + output = subprocess.Popen(command, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT).communicate()[0] + if output.rfind('error:'): + return None + + tmp_buf1 = output.split('=') + tmp_buf2 = tmp_buf1[len(tmp_buf1) - 2].split('x')[0].split(' ') + loc = tmp_buf2[len(tmp_buf2) - 2] + loc_csv_file.write('%s,%s\r\n' % (file_for_loc, loc)) + + # Write to the temp file if the section to write is valid + if strip_flag == False: + # Also write this to the 'clean' LCOV file + tmpfile.write('%s' % (line)) + + # Reset the strip flag + if line.endswith('end_of_record'): + strip_flag = False + + # Close the files and replace the lcov file by the 'clean' tmpfile + tmpfile.close() + lcov.close() + loc_csv_file.close() + shutil.move(tmpfile_name, lcov_path) + + +def ParseCoverageDataForDashboard(lcov_path): + """Parse code coverage data into coverage results per source node. + + Use lcov and linecount data to create a map of source nodes to + corresponding total and tested line counts. + + Args: + lcov_path: File path to lcov coverage data. + + Returns: + List of strings with comma separated source node and coverage. + """ + results = {} + linecount_path = lcov_path + '.csv' + assert(os.path.exists(linecount_path), + 'linecount csv does not exist at: %s' % linecount_path) + csv_file = open(linecount_path, 'r') + linecounts = csv_file.readlines() + csv_file.close() + lcov_file = open(lcov_path, 'r') + srcfile_index = 0 + for line in lcov_file: + line = line.strip() + + # Set the current srcfile name for a new src file declaration. + if line[:len('SF:')] == 'SF:': + instrumented_set = {} + executed_set = {} + srcfile_name = line[len('SF:'):] + + # Mark coverage data points hashlist style for the current src file. + if line[:len('DA:')] == 'DA:': + line_info = line[len('DA:'):].split(',') + assert(len(line_info) == 2, 'DA: line format unexpected - %s' % line) + (line_num, line_was_executed) = line_info + instrumented_set[line_num] = True + # line_was_executed is '0' or '1' + if int(line_was_executed): + executed_set[line_num] = True + + # Update results for the current src file at record end. + if line == 'end_of_record': + instrumented = len(instrumented_set.keys()) + executed = len(executed_set.keys()) + parent_directory = srcfile_name[:srcfile_name.rfind('/') + 1] + linecount_point = linecounts[srcfile_index].strip().split(',') + assert(len(linecount_point) == 2, + 'lintcount format unexpected - %s' % linecounts[srcfile_index]) + (linecount_path, linecount_count) = linecount_point + srcfile_index += 1 + + # Sanity check that path names in the lcov and linecount are lined up. + if linecount_path[-10:] != srcfile_name[-10:]: + print 'NAME MISMATCH: %s :: %s' % (srcfile_name, linecount_path) + if instrumented > int(linecount_count): + linecount_count = instrumented + + # Keep counts the same way that it is done in the genhtml utility. + # Count the coverage of a file towards the file, + # the parent directory, and the source root. + AddResults(results, srcfile_name, int(linecount_count), executed) + AddResults(results, parent_directory, int(linecount_count), executed) + AddResults(results, '/', instrumented, executed) + + lcov_file.close() + keys = results.keys() + keys.sort() + # The first key (sorted) will be the base directory '/' + # but its full path may be '/mnt/chrome_src/src/' + # using this offset will ignore the part '/mnt/chrome_src/src'. + # Offset is the last '/' that isn't the last character for the + # first directory name in results (position 1 in keys). + offset = len(keys[1][:keys[1][:-1].rfind('/')]) + lines = [] + for key in keys: + if len(key) > offset: + node_path = key[offset:] + else: + node_path = key + (total, covered) = results[key] + percent = float(covered) * 100 / total + lines.append('%s,%.2f' % (node_path, percent)) + return lines + + +def AddResults(results, location, lines_total, lines_executed): + """Add resulting line tallies to a location's total. + + Args: + results: Map of node location to corresponding coverage data. + location: Source node string. + lines_total: Number of lines to add to the total count for this node. + lines_executed: Number of lines to add to the executed count for this node. + """ + if results.has_key(location): + (i, e) = results[location] + results[location] = (i + lines_total, e + lines_executed) + else: + results[location] = (lines_total, lines_executed) + + +def PostResultsToDashboard(lcov_path, results, post_url): + """Post coverage results to coverage dashboard. + + Args: + lcov_path: File path for lcov data in the expected format: + __.coverage.lcov + results: string list in the appropriate posting format. + """ + project_platform_cl = lcov_path.split('.')[0].split('_') + assert(len(project_platform_cl) == 3, + 'lcov_path not in expected format: %s' % lcov_path) + (project, platform, cl_string) = project_platform_cl + project_name = '%s-%s' % (project, platform) + url = '%s/newdata.do?project=%s&cl=%s' % (post_url, project_name, cl_string) + + # Send POSTs of POST_CHUNK_SIZE lines of the result set until + # there is no more data and last_loop is set to True. + last_loop = False + cur_line = 0 + while not last_loop: + body = '\n'.join(results[cur_line:cur_line + POST_CHUNK_SIZE]) + cur_line += POST_CHUNK_SIZE + last_loop = (cur_line >= len(results)) + req = urllib2.Request('%s&last=%s' % (url, str(last_loop)), body) + req.add_header('Content-Type', 'text/plain') + SendPost(req) + + +# Global counter for the current number of request failures. +num_fails = 0 + +def SendPost(req): + """Execute a post request and retry for up to MAX_FAILURES. + + Args: + req: A urllib2 request object. + + Raises: + URLError: If urlopen throws after too many retries. + HTTPError: If urlopen throws after too many retries. + """ + global num_fails + try: + urllib2.urlopen(req) + # Reset failure count. + num_fails = 0 + except (urllib2.URLError, urllib2.HTTPError): + num_fails += 1 + if num_fails < MAX_FAILURES: + print 'fail, retrying (%d)' % num_fails + time.sleep(5) + SendPost(req) + else: + print 'POST request exceeded allowed retries.' + raise + + +def main(): + if not sys.platform.startswith('linux'): + print 'This script is supported only on Linux' + return 0 + + # Command line parsing + parser = optparse.OptionParser() + parser.add_option('-p', + '--platform', + dest='platform', + default=None, + help=('Platform that the locv file was generated on. Must' + 'be one of {win32, linux2, linux3, macosx}')) + parser.add_option('-s', + '--source', + dest='src_dir', + default=None, + help='Path to the source code and symbols') + parser.add_option('-d', + '--dash_root', + dest='dash_root', + default=None, + help='Root directory for the dashboard') + parser.add_option('-l', + '--lcov', + dest='lcov_path', + default=None, + help='Location of the LCOV file to process') + parser.add_option('-u', + '--post_url', + dest='post_url', + default=None, + help='Base URL of the coverage dashboard') + (options, args) = parser.parse_args() + + if options.platform == None: + parser.error('Platform not specified') + if options.lcov_path == None: + parser.error('lcov file path not specified') + if options.src_dir == None: + parser.error('Source directory not specified') + if options.dash_root == None: + parser.error('Dashboard root not specified') + if options.post_url == None: + parser.error('Post URL not specified') + if options.platform == 'win32': + CleanWin32Lcov(options.lcov_path, options.src_dir) + percent = GenerateHtml(options.lcov_path, options.dash_root) + if percent == None: + # TODO(niranjan): Add logging. + print 'Failed to generate code coverage' + return 1 + else: + # TODO(niranjan): Do something with the code coverage numbers + pass + else: + print 'Unsupported platform' + return 1 + + # Prep coverage results for dashboard and post new set. + parsed_data = ParseCoverageDataForDashboard(options.lcov_path) + PostResultsToDashboard(options.lcov_path, parsed_data, options.post_url) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/code_coverage/third_party/README.chromium b/tools/code_coverage/third_party/README.chromium new file mode 100644 index 0000000000..a4925525c8 --- /dev/null +++ b/tools/code_coverage/third_party/README.chromium @@ -0,0 +1,11 @@ +Name: SortTable +Short Name: sorttable.js +URL: http://www.kryogenix.org/code/browser/sorttable/ +Version: 2 +Date: 7th April 2007 +License: Licenced as X11: http://www.kryogenix.org/code/browser/licence.html + +Description: +Add to your HTML +Add class="sortable" to any table you'd like to make sortable +Click on the headers to sort diff --git a/tools/code_coverage/third_party/sorttable.js b/tools/code_coverage/third_party/sorttable.js new file mode 100644 index 0000000000..16ef551497 --- /dev/null +++ b/tools/code_coverage/third_party/sorttable.js @@ -0,0 +1,494 @@ +/* + SortTable + version 2 + 7th April 2007 + Stuart Langridge, http://www.kryogenix.org/code/browser/sorttable/ + + Instructions: + Download this file + Add to your HTML + Add class="sortable" to any table you'd like to make sortable + Click on the headers to sort + + Thanks to many, many people for contributions and suggestions. + Licenced as X11: http://www.kryogenix.org/code/browser/licence.html + This basically means: do what you want with it. +*/ + + +var stIsIE = /*@cc_on!@*/false; + +sorttable = { + init: function() { + // quit if this function has already been called + if (arguments.callee.done) return; + // flag this function so we don't do the same thing twice + arguments.callee.done = true; + // kill the timer + if (_timer) clearInterval(_timer); + + if (!document.createElement || !document.getElementsByTagName) return; + + sorttable.DATE_RE = /^(\d\d?)[\/\.-](\d\d?)[\/\.-]((\d\d)?\d\d)$/; + + forEach(document.getElementsByTagName('table'), function(table) { + if (table.className.search(/\bsortable\b/) != -1) { + sorttable.makeSortable(table); + } + }); + + }, + + makeSortable: function(table) { + if (table.getElementsByTagName('thead').length == 0) { + // table doesn't have a tHead. Since it should have, create one and + // put the first table row in it. + the = document.createElement('thead'); + the.appendChild(table.rows[0]); + table.insertBefore(the,table.firstChild); + } + // Safari doesn't support table.tHead, sigh + if (table.tHead == null) table.tHead = table.getElementsByTagName('thead')[0]; + + if (table.tHead.rows.length != 1) return; // can't cope with two header rows + + // Sorttable v1 put rows with a class of "sortbottom" at the bottom (as + // "total" rows, for example). This is B&R, since what you're supposed + // to do is put them in a tfoot. So, if there are sortbottom rows, + // for backwards compatibility, move them to tfoot (creating it if needed). + sortbottomrows = []; + for (var i=0; i5' : ' ▴'; + this.appendChild(sortrevind); + return; + } + if (this.className.search(/\bsorttable_sorted_reverse\b/) != -1) { + // if we're already sorted by this column in reverse, just + // re-reverse the table, which is quicker + sorttable.reverse(this.sorttable_tbody); + this.className = this.className.replace('sorttable_sorted_reverse', + 'sorttable_sorted'); + this.removeChild(document.getElementById('sorttable_sortrevind')); + sortfwdind = document.createElement('span'); + sortfwdind.id = "sorttable_sortfwdind"; + sortfwdind.innerHTML = stIsIE ? ' 6' : ' ▾'; + this.appendChild(sortfwdind); + return; + } + + // remove sorttable_sorted classes + theadrow = this.parentNode; + forEach(theadrow.childNodes, function(cell) { + if (cell.nodeType == 1) { // an element + cell.className = cell.className.replace('sorttable_sorted_reverse',''); + cell.className = cell.className.replace('sorttable_sorted',''); + } + }); + sortfwdind = document.getElementById('sorttable_sortfwdind'); + if (sortfwdind) { sortfwdind.parentNode.removeChild(sortfwdind); } + sortrevind = document.getElementById('sorttable_sortrevind'); + if (sortrevind) { sortrevind.parentNode.removeChild(sortrevind); } + + this.className += ' sorttable_sorted'; + sortfwdind = document.createElement('span'); + sortfwdind.id = "sorttable_sortfwdind"; + sortfwdind.innerHTML = stIsIE ? ' 6' : ' ▾'; + this.appendChild(sortfwdind); + + // build an array to sort. This is a Schwartzian transform thing, + // i.e., we "decorate" each row with the actual sort key, + // sort based on the sort keys, and then put the rows back in order + // which is a lot faster because you only do getInnerText once per row + row_array = []; + col = this.sorttable_columnindex; + rows = this.sorttable_tbody.rows; + for (var j=0; j 12) { + // definitely dd/mm + return sorttable.sort_ddmm; + } else if (second > 12) { + return sorttable.sort_mmdd; + } else { + // looks like a date, but we can't tell which, so assume + // that it's dd/mm (English imperialism!) and keep looking + sortfn = sorttable.sort_ddmm; + } + } + } + } + return sortfn; + }, + + getInnerText: function(node) { + // gets the text we want to use for sorting for a cell. + // strips leading and trailing whitespace. + // this is *not* a generic getInnerText function; it's special to sorttable. + // for example, you can override the cell text with a customkey attribute. + // it also gets .value for fields. + + if (!node) return ""; + + hasInputs = (typeof node.getElementsByTagName == 'function') && + node.getElementsByTagName('input').length; + + if (node.getAttribute("sorttable_customkey") != null) { + return node.getAttribute("sorttable_customkey"); + } + else if (typeof node.textContent != 'undefined' && !hasInputs) { + return node.textContent.replace(/^\s+|\s+$/g, ''); + } + else if (typeof node.innerText != 'undefined' && !hasInputs) { + return node.innerText.replace(/^\s+|\s+$/g, ''); + } + else if (typeof node.text != 'undefined' && !hasInputs) { + return node.text.replace(/^\s+|\s+$/g, ''); + } + else { + switch (node.nodeType) { + case 3: + if (node.nodeName.toLowerCase() == 'input') { + return node.value.replace(/^\s+|\s+$/g, ''); + } + case 4: + return node.nodeValue.replace(/^\s+|\s+$/g, ''); + break; + case 1: + case 11: + var innerText = ''; + for (var i = 0; i < node.childNodes.length; i++) { + innerText += sorttable.getInnerText(node.childNodes[i]); + } + return innerText.replace(/^\s+|\s+$/g, ''); + break; + default: + return ''; + } + } + }, + + reverse: function(tbody) { + // reverse the rows in a tbody + newrows = []; + for (var i=0; i=0; i--) { + tbody.appendChild(newrows[i]); + } + delete newrows; + }, + + /* sort functions + each sort function takes two parameters, a and b + you are comparing a[0] and b[0] */ + sort_numeric: function(a,b) { + aa = parseFloat(a[0].replace(/[^0-9.-]/g,'')); + if (isNaN(aa)) aa = 0; + bb = parseFloat(b[0].replace(/[^0-9.-]/g,'')); + if (isNaN(bb)) bb = 0; + return aa-bb; + }, + sort_alpha: function(a,b) { + if (a[0]==b[0]) return 0; + if (a[0] 0 ) { + var q = list[i]; list[i] = list[i+1]; list[i+1] = q; + swap = true; + } + } // for + t--; + + if (!swap) break; + + for(var i = t; i > b; --i) { + if ( comp_func(list[i], list[i-1]) < 0 ) { + var q = list[i]; list[i] = list[i-1]; list[i-1] = q; + swap = true; + } + } // for + b++; + + } // while(swap) + } +} + +/* ****************************************************************** + Supporting functions: bundled here to avoid depending on a library + ****************************************************************** */ + +// Dean Edwards/Matthias Miller/John Resig + +/* for Mozilla/Opera9 */ +if (document.addEventListener) { + document.addEventListener("DOMContentLoaded", sorttable.init, false); +} + +/* for Internet Explorer */ +/*@cc_on @*/ +/*@if (@_win32) + document.write(" + + + +
+
+ + +""" + +def _GenerateGraph(json_data, policy): + legends = list(json_data['policies'][policy]['legends']) + legends = ['second'] + legends[legends.index('FROM_HERE_FOR_TOTAL') + 1: + legends.index('UNTIL_HERE_FOR_TOTAL')] + data = [] + for snapshot in json_data['policies'][policy]['snapshots']: + data.append([0] * len(legends)) + for k, v in snapshot.iteritems(): + if k in legends: + data[-1][legends.index(k)] = v + print Template(_HTML_TEMPLATE).safe_substitute( + {'JSON_ARRAY': json.dumps([legends] + data)}) + + +def main(argv): + _GenerateGraph(json.load(file(argv[1], 'r')), argv[2]) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) + + diff --git a/tools/deep_memory_profiler/lib/__init__.py b/tools/deep_memory_profiler/lib/__init__.py new file mode 100644 index 0000000000..9228df89b0 --- /dev/null +++ b/tools/deep_memory_profiler/lib/__init__.py @@ -0,0 +1,3 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. diff --git a/tools/deep_memory_profiler/lib/bucket.py b/tools/deep_memory_profiler/lib/bucket.py new file mode 100644 index 0000000000..51af5b2ddd --- /dev/null +++ b/tools/deep_memory_profiler/lib/bucket.py @@ -0,0 +1,191 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import logging +import os + +from lib.symbol import FUNCTION_SYMBOLS, SOURCEFILE_SYMBOLS, TYPEINFO_SYMBOLS + + +LOGGER = logging.getLogger('dmprof') + +# Indexes in dumped heap profile dumps. +VIRTUAL, COMMITTED, ALLOC_COUNT, FREE_COUNT, _, BUCKET_ID = range(6) + + +class Bucket(object): + """Represents a bucket, which is a unit of memory block classification.""" + + def __init__(self, stacktrace, allocator_type, typeinfo, typeinfo_name): + self._stacktrace = stacktrace + self._allocator_type = allocator_type + self._typeinfo = typeinfo + self._typeinfo_name = typeinfo_name + + self._symbolized_stackfunction = stacktrace + self._symbolized_joined_stackfunction = '' + self._symbolized_stacksourcefile = stacktrace + self._symbolized_joined_stacksourcefile = '' + self._symbolized_typeinfo = typeinfo_name + + self.component_cache = '' + + def __str__(self): + result = [] + result.append(self._allocator_type) + if self._symbolized_typeinfo == 'no typeinfo': + result.append('tno_typeinfo') + else: + result.append('t' + self._symbolized_typeinfo) + result.append('n' + self._typeinfo_name) + result.extend(['%s(@%s)' % (function, sourcefile) + for function, sourcefile + in zip(self._symbolized_stackfunction, + self._symbolized_stacksourcefile)]) + return ' '.join(result) + + def symbolize(self, symbol_mapping_cache): + """Makes a symbolized stacktrace and typeinfo with |symbol_mapping_cache|. + + Args: + symbol_mapping_cache: A SymbolMappingCache object. + """ + # TODO(dmikurube): Fill explicitly with numbers if symbol not found. + self._symbolized_stackfunction = [ + symbol_mapping_cache.lookup(FUNCTION_SYMBOLS, address) + for address in self._stacktrace] + self._symbolized_joined_stackfunction = ' '.join( + self._symbolized_stackfunction) + self._symbolized_stacksourcefile = [ + symbol_mapping_cache.lookup(SOURCEFILE_SYMBOLS, address) + for address in self._stacktrace] + self._symbolized_joined_stacksourcefile = ' '.join( + self._symbolized_stacksourcefile) + if not self._typeinfo: + self._symbolized_typeinfo = 'no typeinfo' + else: + self._symbolized_typeinfo = symbol_mapping_cache.lookup( + TYPEINFO_SYMBOLS, self._typeinfo) + if not self._symbolized_typeinfo: + self._symbolized_typeinfo = 'no typeinfo' + + def clear_component_cache(self): + self.component_cache = '' + + @property + def stacktrace(self): + return self._stacktrace + + @property + def allocator_type(self): + return self._allocator_type + + @property + def typeinfo(self): + return self._typeinfo + + @property + def typeinfo_name(self): + return self._typeinfo_name + + @property + def symbolized_stackfunction(self): + return self._symbolized_stackfunction + + @property + def symbolized_joined_stackfunction(self): + return self._symbolized_joined_stackfunction + + @property + def symbolized_stacksourcefile(self): + return self._symbolized_stacksourcefile + + @property + def symbolized_joined_stacksourcefile(self): + return self._symbolized_joined_stacksourcefile + + @property + def symbolized_typeinfo(self): + return self._symbolized_typeinfo + + +class BucketSet(object): + """Represents a set of bucket.""" + def __init__(self): + self._buckets = {} + self._code_addresses = set() + self._typeinfo_addresses = set() + + def load(self, prefix): + """Loads all related bucket files. + + Args: + prefix: A prefix string for bucket file names. + """ + LOGGER.info('Loading bucket files.') + + n = 0 + skipped = 0 + while True: + path = '%s.%04d.buckets' % (prefix, n) + if not os.path.exists(path) or not os.stat(path).st_size: + if skipped > 10: + break + n += 1 + skipped += 1 + continue + LOGGER.info(' %s' % path) + with open(path, 'r') as f: + self._load_file(f) + n += 1 + skipped = 0 + + def _load_file(self, bucket_f): + for line in bucket_f: + words = line.split() + typeinfo = None + typeinfo_name = '' + stacktrace_begin = 2 + for index, word in enumerate(words): + if index < 2: + continue + if word[0] == 't': + typeinfo = int(word[1:], 16) + self._typeinfo_addresses.add(typeinfo) + elif word[0] == 'n': + typeinfo_name = word[1:] + else: + stacktrace_begin = index + break + stacktrace = [int(address, 16) for address in words[stacktrace_begin:]] + for frame in stacktrace: + self._code_addresses.add(frame) + self._buckets[int(words[0])] = Bucket( + stacktrace, words[1], typeinfo, typeinfo_name) + + def __iter__(self): + for bucket_id, bucket_content in self._buckets.iteritems(): + yield bucket_id, bucket_content + + def __getitem__(self, bucket_id): + return self._buckets[bucket_id] + + def get(self, bucket_id): + return self._buckets.get(bucket_id) + + def symbolize(self, symbol_mapping_cache): + for bucket_content in self._buckets.itervalues(): + bucket_content.symbolize(symbol_mapping_cache) + + def clear_component_cache(self): + for bucket_content in self._buckets.itervalues(): + bucket_content.clear_component_cache() + + def iter_addresses(self, symbol_type): + if symbol_type in [FUNCTION_SYMBOLS, SOURCEFILE_SYMBOLS]: + for function in self._code_addresses: + yield function + else: + for function in self._typeinfo_addresses: + yield function diff --git a/tools/deep_memory_profiler/lib/dump.py b/tools/deep_memory_profiler/lib/dump.py new file mode 100644 index 0000000000..115979e0f6 --- /dev/null +++ b/tools/deep_memory_profiler/lib/dump.py @@ -0,0 +1,487 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import copy +import datetime +import logging +import os +import re +import time + +from lib.bucket import BUCKET_ID +from lib.exceptions import EmptyDumpException, InvalidDumpException +from lib.exceptions import ObsoleteDumpVersionException, ParsingException +from lib.pageframe import PageFrame +from lib.range_dict import ExclusiveRangeDict +from lib.symbol import proc_maps + + +LOGGER = logging.getLogger('dmprof') + + +# Heap Profile Dump versions + +# DUMP_DEEP_[1-4] are obsolete. +# DUMP_DEEP_2+ distinct mmap regions and malloc chunks. +# DUMP_DEEP_3+ don't include allocation functions in their stack dumps. +# DUMP_DEEP_4+ support comments with '#' and global stats "nonprofiled-*". +# DUMP_DEEP_[1-2] should be processed by POLICY_DEEP_1. +# DUMP_DEEP_[3-4] should be processed by POLICY_DEEP_2 or POLICY_DEEP_3. +DUMP_DEEP_1 = 'DUMP_DEEP_1' +DUMP_DEEP_2 = 'DUMP_DEEP_2' +DUMP_DEEP_3 = 'DUMP_DEEP_3' +DUMP_DEEP_4 = 'DUMP_DEEP_4' + +DUMP_DEEP_OBSOLETE = (DUMP_DEEP_1, DUMP_DEEP_2, DUMP_DEEP_3, DUMP_DEEP_4) + +# DUMP_DEEP_5 doesn't separate sections for malloc and mmap. +# malloc and mmap are identified in bucket files. +# DUMP_DEEP_5 should be processed by POLICY_DEEP_4. +DUMP_DEEP_5 = 'DUMP_DEEP_5' + +# DUMP_DEEP_6 adds a mmap list to DUMP_DEEP_5. +DUMP_DEEP_6 = 'DUMP_DEEP_6' + + +class Dump(object): + """Represents a heap profile dump.""" + + _PATH_PATTERN = re.compile(r'^(.*)\.([0-9]+)\.([0-9]+)\.heap$') + + _HOOK_PATTERN = re.compile( + r'^ ([ \(])([a-f0-9]+)([ \)])-([ \(])([a-f0-9]+)([ \)])\s+' + r'(hooked|unhooked)\s+(.+)$', re.IGNORECASE) + + _HOOKED_PATTERN = re.compile(r'(?P.+ )?(?P[0-9]+) / ' + '(?P[0-9]+) @ (?P[0-9]+)') + _UNHOOKED_PATTERN = re.compile(r'(?P.+ )?(?P[0-9]+) / ' + '(?P[0-9]+)') + + _OLD_HOOKED_PATTERN = re.compile(r'(?P.+) @ (?P[0-9]+)') + _OLD_UNHOOKED_PATTERN = re.compile(r'(?P.+) (?P[0-9]+)') + + _TIME_PATTERN_FORMAT = re.compile( + r'^Time: ([0-9]+/[0-9]+/[0-9]+ [0-9]+:[0-9]+:[0-9]+)(\.[0-9]+)?') + _TIME_PATTERN_SECONDS = re.compile(r'^Time: ([0-9]+)$') + + def __init__(self, path, modified_time): + self._path = path + matched = self._PATH_PATTERN.match(path) + self._pid = int(matched.group(2)) + self._count = int(matched.group(3)) + self._time = modified_time + self._map = {} + self._procmaps = ExclusiveRangeDict(ProcMapsEntryAttribute) + self._stacktrace_lines = [] + self._global_stats = {} # used only in apply_policy + + self._run_id = '' + self._pagesize = 4096 + self._pageframe_length = 0 + self._pageframe_encoding = '' + self._has_pagecount = False + + self._version = '' + self._lines = [] + + @property + def path(self): + return self._path + + @property + def count(self): + return self._count + + @property + def time(self): + return self._time + + @property + def iter_map(self): + for region in sorted(self._map.iteritems()): + yield region[0], region[1] + + def iter_procmaps(self): + for begin, end, attr in self._map.iter_range(): + yield begin, end, attr + + @property + def iter_stacktrace(self): + for line in self._stacktrace_lines: + yield line + + def global_stat(self, name): + return self._global_stats[name] + + @property + def run_id(self): + return self._run_id + + @property + def pagesize(self): + return self._pagesize + + @property + def pageframe_length(self): + return self._pageframe_length + + @property + def pageframe_encoding(self): + return self._pageframe_encoding + + @property + def has_pagecount(self): + return self._has_pagecount + + @staticmethod + def load(path, log_header='Loading a heap profile dump: '): + """Loads a heap profile dump. + + Args: + path: A file path string to load. + log_header: A preceding string for log messages. + + Returns: + A loaded Dump object. + + Raises: + ParsingException for invalid heap profile dumps. + """ + dump = Dump(path, os.stat(path).st_mtime) + with open(path, 'r') as f: + dump.load_file(f, log_header) + return dump + + def load_file(self, f, log_header): + self._lines = [line for line in f + if line and not line.startswith('#')] + + try: + self._version, ln = self._parse_version() + self._parse_meta_information() + if self._version == DUMP_DEEP_6: + self._parse_mmap_list() + self._parse_global_stats() + self._extract_stacktrace_lines(ln) + except EmptyDumpException: + LOGGER.info('%s%s ...ignored an empty dump.' % (log_header, self._path)) + except ParsingException, e: + LOGGER.error('%s%s ...error %s' % (log_header, self._path, e)) + raise + else: + LOGGER.info('%s%s (version:%s)' % (log_header, self._path, self._version)) + + def _parse_version(self): + """Parses a version string in self._lines. + + Returns: + A pair of (a string representing a version of the stacktrace dump, + and an integer indicating a line number next to the version string). + + Raises: + ParsingException for invalid dump versions. + """ + version = '' + + # Skip until an identifiable line. + headers = ('STACKTRACES:\n', 'MMAP_STACKTRACES:\n', 'heap profile: ') + if not self._lines: + raise EmptyDumpException('Empty heap dump file.') + (ln, found) = skip_while( + 0, len(self._lines), + lambda n: not self._lines[n].startswith(headers)) + if not found: + raise InvalidDumpException('No version header.') + + # Identify a version. + if self._lines[ln].startswith('heap profile: '): + version = self._lines[ln][13:].strip() + if version in (DUMP_DEEP_5, DUMP_DEEP_6): + (ln, _) = skip_while( + ln, len(self._lines), + lambda n: self._lines[n] != 'STACKTRACES:\n') + elif version in DUMP_DEEP_OBSOLETE: + raise ObsoleteDumpVersionException(version) + else: + raise InvalidDumpException('Invalid version: %s' % version) + elif self._lines[ln] == 'STACKTRACES:\n': + raise ObsoleteDumpVersionException(DUMP_DEEP_1) + elif self._lines[ln] == 'MMAP_STACKTRACES:\n': + raise ObsoleteDumpVersionException(DUMP_DEEP_2) + + return (version, ln) + + def _parse_global_stats(self): + """Parses lines in self._lines as global stats.""" + (ln, _) = skip_while( + 0, len(self._lines), + lambda n: self._lines[n] != 'GLOBAL_STATS:\n') + + global_stat_names = [ + 'total', 'absent', 'file-exec', 'file-nonexec', 'anonymous', 'stack', + 'other', 'nonprofiled-absent', 'nonprofiled-anonymous', + 'nonprofiled-file-exec', 'nonprofiled-file-nonexec', + 'nonprofiled-stack', 'nonprofiled-other', + 'profiled-mmap', 'profiled-malloc'] + + for prefix in global_stat_names: + (ln, _) = skip_while( + ln, len(self._lines), + lambda n: self._lines[n].split()[0] != prefix) + words = self._lines[ln].split() + self._global_stats[prefix + '_virtual'] = int(words[-2]) + self._global_stats[prefix + '_committed'] = int(words[-1]) + + def _parse_meta_information(self): + """Parses lines in self._lines for meta information.""" + (ln, found) = skip_while( + 0, len(self._lines), + lambda n: self._lines[n] != 'META:\n') + if not found: + return + ln += 1 + + while True: + if self._lines[ln].startswith('Time:'): + matched_seconds = self._TIME_PATTERN_SECONDS.match(self._lines[ln]) + matched_format = self._TIME_PATTERN_FORMAT.match(self._lines[ln]) + if matched_format: + self._time = time.mktime(datetime.datetime.strptime( + matched_format.group(1), '%Y/%m/%d %H:%M:%S').timetuple()) + if matched_format.group(2): + self._time += float(matched_format.group(2)[1:]) / 1000.0 + elif matched_seconds: + self._time = float(matched_seconds.group(1)) + elif self._lines[ln].startswith('Reason:'): + pass # Nothing to do for 'Reason:' + elif self._lines[ln].startswith('PageSize: '): + self._pagesize = int(self._lines[ln][10:]) + elif self._lines[ln].startswith('CommandLine:'): + pass + elif (self._lines[ln].startswith('PageFrame: ') or + self._lines[ln].startswith('PFN: ')): + if self._lines[ln].startswith('PageFrame: '): + words = self._lines[ln][11:].split(',') + else: + words = self._lines[ln][5:].split(',') + for word in words: + if word == '24': + self._pageframe_length = 24 + elif word == 'Base64': + self._pageframe_encoding = 'base64' + elif word == 'PageCount': + self._has_pagecount = True + elif self._lines[ln].startswith('RunID: '): + self._run_id = self._lines[ln][7:].strip() + elif (self._lines[ln].startswith('MMAP_LIST:') or + self._lines[ln].startswith('GLOBAL_STATS:')): + # Skip until "MMAP_LIST:" or "GLOBAL_STATS" is found. + break + else: + pass + ln += 1 + + def _parse_mmap_list(self): + """Parses lines in self._lines as a mmap list.""" + (ln, found) = skip_while( + 0, len(self._lines), + lambda n: self._lines[n] != 'MMAP_LIST:\n') + if not found: + return {} + + ln += 1 + self._map = {} + current_vma = {} + pageframe_list = [] + while True: + entry = proc_maps.ProcMaps.parse_line(self._lines[ln]) + if entry: + current_vma = {} + for _, _, attr in self._procmaps.iter_range(entry.begin, entry.end): + for key, value in entry.as_dict().iteritems(): + attr[key] = value + current_vma[key] = value + ln += 1 + continue + + if self._lines[ln].startswith(' PF: '): + for pageframe in self._lines[ln][5:].split(): + pageframe_list.append(PageFrame.parse(pageframe, self._pagesize)) + ln += 1 + continue + + matched = self._HOOK_PATTERN.match(self._lines[ln]) + if not matched: + break + # 2: starting address + # 5: end address + # 7: hooked or unhooked + # 8: additional information + if matched.group(7) == 'hooked': + submatched = self._HOOKED_PATTERN.match(matched.group(8)) + if not submatched: + submatched = self._OLD_HOOKED_PATTERN.match(matched.group(8)) + elif matched.group(7) == 'unhooked': + submatched = self._UNHOOKED_PATTERN.match(matched.group(8)) + if not submatched: + submatched = self._OLD_UNHOOKED_PATTERN.match(matched.group(8)) + else: + assert matched.group(7) in ['hooked', 'unhooked'] + + submatched_dict = submatched.groupdict() + region_info = { 'vma': current_vma } + if submatched_dict.get('TYPE'): + region_info['type'] = submatched_dict['TYPE'].strip() + if submatched_dict.get('COMMITTED'): + region_info['committed'] = int(submatched_dict['COMMITTED']) + if submatched_dict.get('RESERVED'): + region_info['reserved'] = int(submatched_dict['RESERVED']) + if submatched_dict.get('BUCKETID'): + region_info['bucket_id'] = int(submatched_dict['BUCKETID']) + + if matched.group(1) == '(': + start = current_vma['begin'] + else: + start = int(matched.group(2), 16) + if matched.group(4) == '(': + end = current_vma['end'] + else: + end = int(matched.group(5), 16) + + if pageframe_list and pageframe_list[0].start_truncated: + pageframe_list[0].set_size( + pageframe_list[0].size - start % self._pagesize) + if pageframe_list and pageframe_list[-1].end_truncated: + pageframe_list[-1].set_size( + pageframe_list[-1].size - (self._pagesize - end % self._pagesize)) + region_info['pageframe'] = pageframe_list + pageframe_list = [] + + self._map[(start, end)] = (matched.group(7), region_info) + ln += 1 + + def _extract_stacktrace_lines(self, line_number): + """Extracts the position of stacktrace lines. + + Valid stacktrace lines are stored into self._stacktrace_lines. + + Args: + line_number: A line number to start parsing in lines. + + Raises: + ParsingException for invalid dump versions. + """ + if self._version in (DUMP_DEEP_5, DUMP_DEEP_6): + (line_number, _) = skip_while( + line_number, len(self._lines), + lambda n: not self._lines[n].split()[0].isdigit()) + stacktrace_start = line_number + (line_number, _) = skip_while( + line_number, len(self._lines), + lambda n: self._check_stacktrace_line(self._lines[n])) + self._stacktrace_lines = self._lines[stacktrace_start:line_number] + + elif self._version in DUMP_DEEP_OBSOLETE: + raise ObsoleteDumpVersionException(self._version) + + else: + raise InvalidDumpException('Invalid version: %s' % self._version) + + @staticmethod + def _check_stacktrace_line(stacktrace_line): + """Checks if a given stacktrace_line is valid as stacktrace. + + Args: + stacktrace_line: A string to be checked. + + Returns: + True if the given stacktrace_line is valid. + """ + words = stacktrace_line.split() + if len(words) < BUCKET_ID + 1: + return False + if words[BUCKET_ID - 1] != '@': + return False + return True + + +class DumpList(object): + """Represents a sequence of heap profile dumps.""" + + def __init__(self, dump_list): + self._dump_list = dump_list + + @staticmethod + def load(path_list): + LOGGER.info('Loading heap dump profiles.') + dump_list = [] + for path in path_list: + dump_list.append(Dump.load(path, ' ')) + return DumpList(dump_list) + + def __len__(self): + return len(self._dump_list) + + def __iter__(self): + for dump in self._dump_list: + yield dump + + def __getitem__(self, index): + return self._dump_list[index] + + +class ProcMapsEntryAttribute(ExclusiveRangeDict.RangeAttribute): + """Represents an entry of /proc/maps in range_dict.ExclusiveRangeDict.""" + _DUMMY_ENTRY = proc_maps.ProcMapsEntry( + 0, # begin + 0, # end + '-', # readable + '-', # writable + '-', # executable + '-', # private + 0, # offset + '00', # major + '00', # minor + 0, # inode + '' # name + ) + + def __init__(self): + super(ProcMapsEntryAttribute, self).__init__() + self._entry = self._DUMMY_ENTRY.as_dict() + + def __str__(self): + return str(self._entry) + + def __repr__(self): + return 'ProcMapsEntryAttribute' + str(self._entry) + + def __getitem__(self, key): + return self._entry[key] + + def __setitem__(self, key, value): + if key not in self._entry: + raise KeyError(key) + self._entry[key] = value + + def copy(self): + new_entry = ProcMapsEntryAttribute() + for key, value in self._entry.iteritems(): + new_entry[key] = copy.deepcopy(value) + return new_entry + + +def skip_while(index, max_index, skipping_condition): + """Increments |index| until |skipping_condition|(|index|) is False. + + Returns: + A pair of an integer indicating a line number after skipped, and a + boolean value which is True if found a line which skipping_condition + is False for. + """ + while skipping_condition(index): + index += 1 + if index >= max_index: + return index, False + return index, True diff --git a/tools/deep_memory_profiler/lib/exceptions.py b/tools/deep_memory_profiler/lib/exceptions.py new file mode 100644 index 0000000000..2c68af72cc --- /dev/null +++ b/tools/deep_memory_profiler/lib/exceptions.py @@ -0,0 +1,22 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +class EmptyDumpException(Exception): + def __str__(self): + return repr(self.args[0]) + + +class ParsingException(Exception): + def __str__(self): + return repr(self.args[0]) + + +class InvalidDumpException(ParsingException): + def __str__(self): + return "invalid heap profile dump: %s" % repr(self.args[0]) + + +class ObsoleteDumpVersionException(ParsingException): + def __str__(self): + return "obsolete heap profile dump version: %s" % repr(self.args[0]) diff --git a/tools/deep_memory_profiler/lib/ordered_dict.py b/tools/deep_memory_profiler/lib/ordered_dict.py new file mode 100644 index 0000000000..f7d053bd34 --- /dev/null +++ b/tools/deep_memory_profiler/lib/ordered_dict.py @@ -0,0 +1,19 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# TODO(dmikurube): Remove this file once Python 2.7 is required. + +import os +import sys + +try: + from collections import OrderedDict # pylint: disable=E0611,W0611 +except ImportError: + _BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + _SIMPLEJSON_PATH = os.path.join(_BASE_PATH, + os.pardir, + os.pardir, + 'third_party') + sys.path.insert(0, _SIMPLEJSON_PATH) + from simplejson import OrderedDict # pylint: disable=W0611 diff --git a/tools/deep_memory_profiler/lib/pageframe.py b/tools/deep_memory_profiler/lib/pageframe.py new file mode 100644 index 0000000000..8722243baf --- /dev/null +++ b/tools/deep_memory_profiler/lib/pageframe.py @@ -0,0 +1,163 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import logging +import os +import re +import struct + + +LOGGER = logging.getLogger('dmprof') + + +class PageFrame(object): + """Represents a pageframe and maybe its shared count.""" + def __init__(self, pfn, size, pagecount, start_truncated, end_truncated): + self._pfn = pfn + self._size = size + self._pagecount = pagecount + self._start_truncated = start_truncated + self._end_truncated = end_truncated + + def __str__(self): + result = str() + if self._start_truncated: + result += '<' + result += '%06x#%d' % (self._pfn, self._pagecount) + if self._end_truncated: + result += '>' + return result + + def __repr__(self): + return str(self) + + @staticmethod + def parse(encoded_pfn, size): + start = 0 + end = len(encoded_pfn) + end_truncated = False + if encoded_pfn.endswith('>'): + end = len(encoded_pfn) - 1 + end_truncated = True + pagecount_found = encoded_pfn.find('#') + pagecount = None + if pagecount_found >= 0: + encoded_pagecount = 'AAA' + encoded_pfn[pagecount_found+1 : end] + pagecount = struct.unpack( + '>I', '\x00' + encoded_pagecount.decode('base64'))[0] + end = pagecount_found + start_truncated = False + if encoded_pfn.startswith('<'): + start = 1 + start_truncated = True + + pfn = struct.unpack( + '>I', '\x00' + (encoded_pfn[start:end]).decode('base64'))[0] + + return PageFrame(pfn, size, pagecount, start_truncated, end_truncated) + + @property + def pfn(self): + return self._pfn + + @property + def size(self): + return self._size + + def set_size(self, size): + self._size = size + + @property + def pagecount(self): + return self._pagecount + + @property + def start_truncated(self): + return self._start_truncated + + @property + def end_truncated(self): + return self._end_truncated + + +class PFNCounts(object): + """Represents counts of PFNs in a process.""" + + _PATH_PATTERN = re.compile(r'^(.*)\.([0-9]+)\.([0-9]+)\.heap$') + + def __init__(self, path, modified_time): + matched = self._PATH_PATTERN.match(path) + if matched: + self._pid = int(matched.group(2)) + else: + self._pid = 0 + self._command_line = '' + self._pagesize = 4096 + self._path = path + self._pfn_meta = '' + self._pfnset = {} + self._reason = '' + self._time = modified_time + + @staticmethod + def load(path, log_header='Loading PFNs from a heap profile dump: '): + pfnset = PFNCounts(path, float(os.stat(path).st_mtime)) + LOGGER.info('%s%s' % (log_header, path)) + + with open(path, 'r') as pfnset_f: + pfnset.load_file(pfnset_f) + + return pfnset + + @property + def path(self): + return self._path + + @property + def pid(self): + return self._pid + + @property + def time(self): + return self._time + + @property + def reason(self): + return self._reason + + @property + def iter_pfn(self): + for pfn, count in self._pfnset.iteritems(): + yield pfn, count + + def load_file(self, pfnset_f): + prev_pfn_end_truncated = None + for line in pfnset_f: + line = line.strip() + if line.startswith('GLOBAL_STATS:') or line.startswith('STACKTRACES:'): + break + elif line.startswith('PF: '): + for encoded_pfn in line[3:].split(): + page_frame = PageFrame.parse(encoded_pfn, self._pagesize) + if page_frame.start_truncated and ( + not prev_pfn_end_truncated or + prev_pfn_end_truncated != page_frame.pfn): + LOGGER.error('Broken page frame number: %s.' % encoded_pfn) + self._pfnset[page_frame.pfn] = self._pfnset.get(page_frame.pfn, 0) + 1 + if page_frame.end_truncated: + prev_pfn_end_truncated = page_frame.pfn + else: + prev_pfn_end_truncated = None + elif line.startswith('PageSize: '): + self._pagesize = int(line[10:]) + elif line.startswith('PFN: '): + self._pfn_meta = line[5:] + elif line.startswith('PageFrame: '): + self._pfn_meta = line[11:] + elif line.startswith('Time: '): + self._time = float(line[6:]) + elif line.startswith('CommandLine: '): + self._command_line = line[13:] + elif line.startswith('Reason: '): + self._reason = line[8:] diff --git a/tools/deep_memory_profiler/lib/policy.py b/tools/deep_memory_profiler/lib/policy.py new file mode 100644 index 0000000000..d7a38977eb --- /dev/null +++ b/tools/deep_memory_profiler/lib/policy.py @@ -0,0 +1,404 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import json +import logging +import os +import re + + +LOGGER = logging.getLogger('dmprof') + +BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +POLICIES_JSON_PATH = os.path.join(BASE_PATH, 'policies.json') + +# Heap Profile Policy versions + +# POLICY_DEEP_1 DOES NOT include allocation_type columns. +# mmap regions are distincted w/ mmap frames in the pattern column. +POLICY_DEEP_1 = 'POLICY_DEEP_1' + +# POLICY_DEEP_2 DOES include allocation_type columns. +# mmap regions are distincted w/ the allocation_type column. +POLICY_DEEP_2 = 'POLICY_DEEP_2' + +# POLICY_DEEP_3 is in JSON format. +POLICY_DEEP_3 = 'POLICY_DEEP_3' + +# POLICY_DEEP_3 contains typeinfo. +POLICY_DEEP_4 = 'POLICY_DEEP_4' + + +class Rule(object): + """Represents one matching rule in a policy file.""" + + def __init__(self, + name, + allocator_type, + stackfunction_pattern=None, + stacksourcefile_pattern=None, + typeinfo_pattern=None, + mappedpathname_pattern=None, + mappedpermission_pattern=None, + sharedwith=None): + self._name = name + self._allocator_type = allocator_type + + self._stackfunction_pattern = None + if stackfunction_pattern: + self._stackfunction_pattern = re.compile( + stackfunction_pattern + r'\Z') + + self._stacksourcefile_pattern = None + if stacksourcefile_pattern: + self._stacksourcefile_pattern = re.compile( + stacksourcefile_pattern + r'\Z') + + self._typeinfo_pattern = None + if typeinfo_pattern: + self._typeinfo_pattern = re.compile(typeinfo_pattern + r'\Z') + + self._mappedpathname_pattern = None + if mappedpathname_pattern: + self._mappedpathname_pattern = re.compile(mappedpathname_pattern + r'\Z') + + self._mappedpermission_pattern = None + if mappedpermission_pattern: + self._mappedpermission_pattern = re.compile( + mappedpermission_pattern + r'\Z') + + self._sharedwith = [] + if sharedwith: + self._sharedwith = sharedwith + + @property + def name(self): + return self._name + + @property + def allocator_type(self): + return self._allocator_type + + @property + def stackfunction_pattern(self): + return self._stackfunction_pattern + + @property + def stacksourcefile_pattern(self): + return self._stacksourcefile_pattern + + @property + def typeinfo_pattern(self): + return self._typeinfo_pattern + + @property + def mappedpathname_pattern(self): + return self._mappedpathname_pattern + + @property + def mappedpermission_pattern(self): + return self._mappedpermission_pattern + + @property + def sharedwith(self): + return self._sharedwith + + +class Policy(object): + """Represents a policy, a content of a policy file.""" + + def __init__(self, rules, version, components): + self._rules = rules + self._version = version + self._components = components + + @property + def rules(self): + return self._rules + + @property + def version(self): + return self._version + + @property + def components(self): + return self._components + + def find_rule(self, component_name): + """Finds a rule whose name is |component_name|. """ + for rule in self._rules: + if rule.name == component_name: + return rule + return None + + def find_malloc(self, bucket): + """Finds a matching component name which a given |bucket| belongs to. + + Args: + bucket: A Bucket object to be searched for. + + Returns: + A string representing a component name. + """ + assert not bucket or bucket.allocator_type == 'malloc' + + if not bucket: + return 'no-bucket' + if bucket.component_cache: + return bucket.component_cache + + stackfunction = bucket.symbolized_joined_stackfunction + stacksourcefile = bucket.symbolized_joined_stacksourcefile + typeinfo = bucket.symbolized_typeinfo + if typeinfo.startswith('0x'): + typeinfo = bucket.typeinfo_name + + for rule in self._rules: + if (rule.allocator_type == 'malloc' and + (not rule.stackfunction_pattern or + rule.stackfunction_pattern.match(stackfunction)) and + (not rule.stacksourcefile_pattern or + rule.stacksourcefile_pattern.match(stacksourcefile)) and + (not rule.typeinfo_pattern or rule.typeinfo_pattern.match(typeinfo))): + bucket.component_cache = rule.name + return rule.name + + assert False + + def find_mmap(self, region, bucket_set, + pageframe=None, group_pfn_counts=None): + """Finds a matching component which a given mmap |region| belongs to. + + It uses |bucket_set| to match with backtraces. If |pageframe| is given, + it considers memory sharing among processes. + + NOTE: Don't use Bucket's |component_cache| for mmap regions because they're + classified not only with bucket information (mappedpathname for example). + + Args: + region: A tuple representing a memory region. + bucket_set: A BucketSet object to look up backtraces. + pageframe: A PageFrame object representing a pageframe maybe including + a pagecount. + group_pfn_counts: A dict mapping a PFN to the number of times the + the pageframe is mapped by the known "group (Chrome)" processes. + + Returns: + A string representing a component name. + """ + assert region[0] == 'hooked' + bucket = bucket_set.get(region[1]['bucket_id']) + assert not bucket or bucket.allocator_type == 'mmap' + + if not bucket: + return 'no-bucket', None + + stackfunction = bucket.symbolized_joined_stackfunction + stacksourcefile = bucket.symbolized_joined_stacksourcefile + sharedwith = self._categorize_pageframe(pageframe, group_pfn_counts) + + for rule in self._rules: + if (rule.allocator_type == 'mmap' and + (not rule.stackfunction_pattern or + rule.stackfunction_pattern.match(stackfunction)) and + (not rule.stacksourcefile_pattern or + rule.stacksourcefile_pattern.match(stacksourcefile)) and + (not rule.mappedpathname_pattern or + rule.mappedpathname_pattern.match(region[1]['vma']['name'])) and + (not rule.mappedpermission_pattern or + rule.mappedpermission_pattern.match( + region[1]['vma']['readable'] + + region[1]['vma']['writable'] + + region[1]['vma']['executable'] + + region[1]['vma']['private'])) and + (not rule.sharedwith or + not pageframe or sharedwith in rule.sharedwith)): + return rule.name, bucket + + assert False + + def find_unhooked(self, region, pageframe=None, group_pfn_counts=None): + """Finds a matching component which a given unhooked |region| belongs to. + + If |pageframe| is given, it considers memory sharing among processes. + + Args: + region: A tuple representing a memory region. + pageframe: A PageFrame object representing a pageframe maybe including + a pagecount. + group_pfn_counts: A dict mapping a PFN to the number of times the + the pageframe is mapped by the known "group (Chrome)" processes. + + Returns: + A string representing a component name. + """ + assert region[0] == 'unhooked' + sharedwith = self._categorize_pageframe(pageframe, group_pfn_counts) + + for rule in self._rules: + if (rule.allocator_type == 'unhooked' and + (not rule.mappedpathname_pattern or + rule.mappedpathname_pattern.match(region[1]['vma']['name'])) and + (not rule.mappedpermission_pattern or + rule.mappedpermission_pattern.match( + region[1]['vma']['readable'] + + region[1]['vma']['writable'] + + region[1]['vma']['executable'] + + region[1]['vma']['private'])) and + (not rule.sharedwith or + not pageframe or sharedwith in rule.sharedwith)): + return rule.name + + assert False + + @staticmethod + def load(filename, filetype): + """Loads a policy file of |filename| in a |format|. + + Args: + filename: A filename to be loaded. + filetype: A string to specify a type of the file. Only 'json' is + supported for now. + + Returns: + A loaded Policy object. + """ + with open(os.path.join(BASE_PATH, filename)) as policy_f: + return Policy.parse(policy_f, filetype) + + @staticmethod + def parse(policy_f, filetype): + """Parses a policy file content in a |format|. + + Args: + policy_f: An IO object to be loaded. + filetype: A string to specify a type of the file. Only 'json' is + supported for now. + + Returns: + A loaded Policy object. + """ + if filetype == 'json': + return Policy._parse_json(policy_f) + else: + return None + + @staticmethod + def _parse_json(policy_f): + """Parses policy file in json format. + + A policy file contains component's names and their stacktrace pattern + written in regular expression. Those patterns are matched against each + symbols of each stacktraces in the order written in the policy file + + Args: + policy_f: A File/IO object to read. + + Returns: + A loaded policy object. + """ + policy = json.load(policy_f) + + rules = [] + for rule in policy['rules']: + stackfunction = rule.get('stackfunction') or rule.get('stacktrace') + stacksourcefile = rule.get('stacksourcefile') + rules.append(Rule( + rule['name'], + rule['allocator'], # allocator_type + stackfunction, + stacksourcefile, + rule['typeinfo'] if 'typeinfo' in rule else None, + rule.get('mappedpathname'), + rule.get('mappedpermission'), + rule.get('sharedwith'))) + + return Policy(rules, policy['version'], policy['components']) + + @staticmethod + def _categorize_pageframe(pageframe, group_pfn_counts): + """Categorizes a pageframe based on its sharing status. + + Returns: + 'private' if |pageframe| is not shared with other processes. 'group' + if |pageframe| is shared only with group (Chrome-related) processes. + 'others' if |pageframe| is shared with non-group processes. + """ + if not pageframe: + return 'private' + + if pageframe.pagecount: + if pageframe.pagecount == 1: + return 'private' + elif pageframe.pagecount <= group_pfn_counts.get(pageframe.pfn, 0) + 1: + return 'group' + else: + return 'others' + else: + if pageframe.pfn in group_pfn_counts: + return 'group' + else: + return 'private' + + +class PolicySet(object): + """Represents a set of policies.""" + + def __init__(self, policy_directory): + self._policy_directory = policy_directory + + @staticmethod + def load(labels=None): + """Loads a set of policies via the "default policy directory". + + The "default policy directory" contains pairs of policies and their labels. + For example, a policy "policy.l0.json" is labeled "l0" in the default + policy directory "policies.json". + + All policies in the directory are loaded by default. Policies can be + limited by |labels|. + + Args: + labels: An array that contains policy labels to be loaded. + + Returns: + A PolicySet object. + """ + default_policy_directory = PolicySet._load_default_policy_directory() + if labels: + specified_policy_directory = {} + for label in labels: + if label in default_policy_directory: + specified_policy_directory[label] = default_policy_directory[label] + # TODO(dmikurube): Load an un-labeled policy file. + return PolicySet._load_policies(specified_policy_directory) + else: + return PolicySet._load_policies(default_policy_directory) + + def __len__(self): + return len(self._policy_directory) + + def __iter__(self): + for label in self._policy_directory: + yield label + + def __getitem__(self, label): + return self._policy_directory[label] + + @staticmethod + def _load_default_policy_directory(): + with open(POLICIES_JSON_PATH, mode='r') as policies_f: + default_policy_directory = json.load(policies_f) + return default_policy_directory + + @staticmethod + def _load_policies(directory): + LOGGER.info('Loading policy files.') + policies = {} + for label in directory: + LOGGER.info(' %s: %s' % (label, directory[label]['file'])) + loaded = Policy.load(directory[label]['file'], directory[label]['format']) + if loaded: + policies[label] = loaded + return PolicySet(policies) diff --git a/tools/deep_memory_profiler/lib/range_dict.py b/tools/deep_memory_profiler/lib/range_dict.py new file mode 100644 index 0000000000..565789d561 --- /dev/null +++ b/tools/deep_memory_profiler/lib/range_dict.py @@ -0,0 +1,144 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import os +import sys + +_BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +_BINTREES_PATH = os.path.join( + _BASE_PATH, os.pardir, os.pardir, 'third_party', 'bintrees') +sys.path.insert(0, _BINTREES_PATH) + +from bintrees import FastRBTree # pylint: disable=F0401 + + +class ExclusiveRangeDict(object): + """A class like dict whose key is a range [begin, end) of integers. + + It has an attribute for each range of integers, for example: + [10, 20) => Attribute(0), + [20, 40) => Attribute(1), + [40, 50) => Attribute(2), + ... + + An instance of this class is accessed only via iter_range(begin, end). + The instance is accessed as follows: + + 1) If the given range [begin, end) is not covered by the instance, + the range is newly created and iterated. + + 2) If the given range [begin, end) exactly covers ranges in the instance, + the ranges are iterated. + (See test_set() in tests/range_dict_tests.py.) + + 3) If the given range [begin, end) starts at and/or ends at a mid-point of + an existing range, the existing range is split by the given range, and + ranges in the given range are iterated. For example, consider a case that + [25, 45) is given to an instance of [20, 30), [30, 40), [40, 50). In this + case, [20, 30) is split into [20, 25) and [25, 30), and [40, 50) into + [40, 45) and [45, 50). Then, [25, 30), [30, 40), [40, 45) are iterated. + (See test_split() in tests/range_dict_tests.py.) + + 4) If the given range [begin, end) includes non-existing ranges in an + instance, the gaps are filled with new ranges, and all ranges are iterated. + For example, consider a case that [25, 50) is given to an instance of + [30, 35) and [40, 45). In this case, [25, 30), [35, 40) and [45, 50) are + created in the instance, and then [25, 30), [30, 35), [35, 40), [40, 45) + and [45, 50) are iterated. + (See test_fill() in tests/range_dict_tests.py.) + """ + class RangeAttribute(object): + def __init__(self): + pass + + def __str__(self): + return '' + + def __repr__(self): + return '' + + def copy(self): # pylint: disable=R0201 + return ExclusiveRangeDict.RangeAttribute() + + def __init__(self, attr=RangeAttribute): + self._tree = FastRBTree() + self._attr = attr + + def iter_range(self, begin=None, end=None): + if not begin: + begin = self._tree.min_key() + if not end: + end = self._tree.max_item()[1][0] + + # Assume that self._tree has at least one element. + if self._tree.is_empty(): + self._tree[begin] = (end, self._attr()) + + # Create a beginning range (border) + try: + bound_begin, bound_value = self._tree.floor_item(begin) + bound_end = bound_value[0] + if begin >= bound_end: + # Create a blank range. + try: + new_end, _ = self._tree.succ_item(bound_begin) + except KeyError: + new_end = end + self._tree[begin] = (min(end, new_end), self._attr()) + elif bound_begin < begin and begin < bound_end: + # Split the existing range. + new_end = bound_value[0] + new_value = bound_value[1] + self._tree[bound_begin] = (begin, new_value.copy()) + self._tree[begin] = (new_end, new_value.copy()) + else: # bound_begin == begin + # Do nothing (just saying it clearly since this part is confusing) + pass + except KeyError: # begin is less than the smallest element. + # Create a blank range. + # Note that we can assume self._tree has at least one element. + self._tree[begin] = (min(end, self._tree.min_key()), self._attr()) + + # Create an ending range (border) + try: + bound_begin, bound_value = self._tree.floor_item(end) + bound_end = bound_value[0] + if end > bound_end: + # Create a blank range. + new_begin = bound_end + self._tree[new_begin] = (end, self._attr()) + elif bound_begin < end and end < bound_end: + # Split the existing range. + new_end = bound_value[0] + new_value = bound_value[1] + self._tree[bound_begin] = (end, new_value.copy()) + self._tree[end] = (new_end, new_value.copy()) + else: # bound_begin == begin + # Do nothing (just saying it clearly since this part is confusing) + pass + except KeyError: # end is less than the smallest element. + # It must not happen. A blank range [begin,end) has already been created + # even if [begin,end) is less than the smallest range. + # Do nothing (just saying it clearly since this part is confusing) + raise + + missing_ranges = [] + + prev_end = None + for range_begin, range_value in self._tree.itemslice(begin, end): + range_end = range_value[0] + # Note that we can assume that we have a range beginning with |begin| + # and a range ending with |end| (they may be the same range). + if prev_end and prev_end != range_begin: + missing_ranges.append((prev_end, range_begin)) + prev_end = range_end + + for missing_begin, missing_end in missing_ranges: + self._tree[missing_begin] = (missing_end, self._attr()) + + for range_begin, range_value in self._tree.itemslice(begin, end): + yield range_begin, range_value[0], range_value[1] + + def __str__(self): + return str(self._tree) diff --git a/tools/deep_memory_profiler/lib/sorter.py b/tools/deep_memory_profiler/lib/sorter.py new file mode 100644 index 0000000000..64e0851e17 --- /dev/null +++ b/tools/deep_memory_profiler/lib/sorter.py @@ -0,0 +1,470 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import cStringIO +import json +import logging +import os +import re + + +LOGGER = logging.getLogger('dmprof') + +BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +DEFAULT_SORTERS = [ + os.path.join(BASE_PATH, 'sorter.malloc-component.json'), + os.path.join(BASE_PATH, 'sorter.malloc-type.json'), + os.path.join(BASE_PATH, 'sorter.vm-map.json'), + os.path.join(BASE_PATH, 'sorter.vm-sharing.json'), + ] + +DEFAULT_TEMPLATES = os.path.join(BASE_PATH, 'templates.json') + + +class Unit(object): + """Represents a minimum unit of memory usage categorization. + + It is supposed to be inherited for some different spaces like the entire + virtual memory and malloc arena. Such different spaces are called "worlds" + in dmprof. (For example, the "vm" world and the "malloc" world.) + """ + def __init__(self, unit_id, size): + self._unit_id = unit_id + self._size = size + + @property + def unit_id(self): + return self._unit_id + + @property + def size(self): + return self._size + + +class VMUnit(Unit): + """Represents a Unit for a memory region on virtual memory.""" + def __init__(self, unit_id, committed, reserved, mmap, region, + pageframe=None, group_pfn_counts=None): + super(VMUnit, self).__init__(unit_id, committed) + self._reserved = reserved + self._mmap = mmap + self._region = region + self._pageframe = pageframe + self._group_pfn_counts = group_pfn_counts + + @property + def committed(self): + return self._size + + @property + def reserved(self): + return self._reserved + + @property + def mmap(self): + return self._mmap + + @property + def region(self): + return self._region + + @property + def pageframe(self): + return self._pageframe + + @property + def group_pfn_counts(self): + return self._group_pfn_counts + + +class MMapUnit(VMUnit): + """Represents a Unit for a mmap'ed region.""" + def __init__(self, unit_id, committed, reserved, region, bucket_set, + pageframe=None, group_pfn_counts=None): + super(MMapUnit, self).__init__(unit_id, committed, reserved, True, + region, pageframe, group_pfn_counts) + self._bucket_set = bucket_set + + def __repr__(self): + return str(self.region) + + @property + def bucket_set(self): + return self._bucket_set + + +class UnhookedUnit(VMUnit): + """Represents a Unit for a non-mmap'ed memory region on virtual memory.""" + def __init__(self, unit_id, committed, reserved, region, + pageframe=None, group_pfn_counts=None): + super(UnhookedUnit, self).__init__(unit_id, committed, reserved, False, + region, pageframe, group_pfn_counts) + + def __repr__(self): + return str(self.region) + + +class MallocUnit(Unit): + """Represents a Unit for a malloc'ed memory block.""" + def __init__(self, unit_id, size, alloc_count, free_count, bucket): + super(MallocUnit, self).__init__(unit_id, size) + self._bucket = bucket + self._alloc_count = alloc_count + self._free_count = free_count + + def __repr__(self): + return str(self.bucket) + + @property + def bucket(self): + return self._bucket + + @property + def alloc_count(self): + return self._alloc_count + + @property + def free_count(self): + return self._free_count + + +class UnitSet(object): + """Represents an iterable set of Units.""" + def __init__(self, world): + self._units = {} + self._world = world + + def __repr__(self): + return str(self._units) + + def __iter__(self): + for unit_id in sorted(self._units): + yield self._units[unit_id] + + def append(self, unit, overwrite=False): + if not overwrite and unit.unit_id in self._units: + LOGGER.error('The unit id=%s already exists.' % str(unit.unit_id)) + self._units[unit.unit_id] = unit + + +class AbstractRule(object): + """An abstract class for rules to be matched with units.""" + def __init__(self, dct): + self._name = dct['name'] + self._hidden = dct.get('hidden', False) + self._subs = dct.get('subs', []) + + def match(self, unit): + raise NotImplementedError() + + @property + def name(self): + return self._name + + @property + def hidden(self): + return self._hidden + + def iter_subs(self): + for sub in self._subs: + yield sub + + +class VMRule(AbstractRule): + """Represents a Rule to match with virtual memory regions.""" + def __init__(self, dct): + super(VMRule, self).__init__(dct) + self._backtrace_function = dct.get('backtrace_function', None) + if self._backtrace_function: + self._backtrace_function = re.compile(self._backtrace_function) + self._backtrace_sourcefile = dct.get('backtrace_sourcefile', None) + if self._backtrace_sourcefile: + self._backtrace_sourcefile = re.compile(self._backtrace_sourcefile) + self._mmap = dct.get('mmap', None) + self._sharedwith = dct.get('sharedwith', []) + self._mapped_pathname = dct.get('mapped_pathname', None) + if self._mapped_pathname: + self._mapped_pathname = re.compile(self._mapped_pathname) + self._mapped_permission = dct.get('mapped_permission', None) + if self._mapped_permission: + self._mapped_permission = re.compile(self._mapped_permission) + + def __repr__(self): + result = cStringIO.StringIO() + result.write('{"%s"=>' % self._name) + attributes = [] + attributes.append('mmap: %s' % self._mmap) + if self._backtrace_function: + attributes.append('backtrace_function: "%s"' % + self._backtrace_function.pattern) + if self._sharedwith: + attributes.append('sharedwith: "%s"' % self._sharedwith) + if self._mapped_pathname: + attributes.append('mapped_pathname: "%s"' % self._mapped_pathname.pattern) + if self._mapped_permission: + attributes.append('mapped_permission: "%s"' % + self._mapped_permission.pattern) + result.write('%s}' % ', '.join(attributes)) + return result.getvalue() + + def match(self, unit): + if unit.mmap: + assert unit.region[0] == 'hooked' + bucket = unit.bucket_set.get(unit.region[1]['bucket_id']) + assert bucket + assert bucket.allocator_type == 'mmap' + + stackfunction = bucket.symbolized_joined_stackfunction + stacksourcefile = bucket.symbolized_joined_stacksourcefile + + # TODO(dmikurube): Support shared memory. + sharedwith = None + + if self._mmap == False: # (self._mmap == None) should go through. + return False + if (self._backtrace_function and + not self._backtrace_function.match(stackfunction)): + return False + if (self._backtrace_sourcefile and + not self._backtrace_sourcefile.match(stacksourcefile)): + return False + if (self._mapped_pathname and + not self._mapped_pathname.match(unit.region[1]['vma']['name'])): + return False + if (self._mapped_permission and + not self._mapped_permission.match( + unit.region[1]['vma']['readable'] + + unit.region[1]['vma']['writable'] + + unit.region[1]['vma']['executable'] + + unit.region[1]['vma']['private'])): + return False + if (self._sharedwith and + unit.pageframe and sharedwith not in self._sharedwith): + return False + + return True + + else: + assert unit.region[0] == 'unhooked' + + # TODO(dmikurube): Support shared memory. + sharedwith = None + + if self._mmap == True: # (self._mmap == None) should go through. + return False + if (self._mapped_pathname and + not self._mapped_pathname.match(unit.region[1]['vma']['name'])): + return False + if (self._mapped_permission and + not self._mapped_permission.match( + unit.region[1]['vma']['readable'] + + unit.region[1]['vma']['writable'] + + unit.region[1]['vma']['executable'] + + unit.region[1]['vma']['private'])): + return False + if (self._sharedwith and + unit.pageframe and sharedwith not in self._sharedwith): + return False + + return True + + +class MallocRule(AbstractRule): + """Represents a Rule to match with malloc'ed blocks.""" + def __init__(self, dct): + super(MallocRule, self).__init__(dct) + self._backtrace_function = dct.get('backtrace_function', None) + if self._backtrace_function: + self._backtrace_function = re.compile(self._backtrace_function) + self._backtrace_sourcefile = dct.get('backtrace_sourcefile', None) + if self._backtrace_sourcefile: + self._backtrace_sourcefile = re.compile(self._backtrace_sourcefile) + self._typeinfo = dct.get('typeinfo', None) + if self._typeinfo: + self._typeinfo = re.compile(self._typeinfo) + + def __repr__(self): + result = cStringIO.StringIO() + result.write('{"%s"=>' % self._name) + attributes = [] + if self._backtrace_function: + attributes.append('backtrace_function: "%s"' % self._backtrace_function) + if self._typeinfo: + attributes.append('typeinfo: "%s"' % self._typeinfo) + result.write('%s}' % ', '.join(attributes)) + return result.getvalue() + + def match(self, unit): + assert unit.bucket.allocator_type == 'malloc' + + stackfunction = unit.bucket.symbolized_joined_stackfunction + stacksourcefile = unit.bucket.symbolized_joined_stacksourcefile + typeinfo = unit.bucket.symbolized_typeinfo + if typeinfo.startswith('0x'): + typeinfo = unit.bucket.typeinfo_name + + return ((not self._backtrace_function or + self._backtrace_function.match(stackfunction)) and + (not self._backtrace_sourcefile or + self._backtrace_sourcefile.match(stacksourcefile)) and + (not self._typeinfo or self._typeinfo.match(typeinfo))) + + +class NoBucketMallocRule(MallocRule): + """Represents a Rule that small ignorable units match with.""" + def __init__(self): + super(NoBucketMallocRule, self).__init__({'name': 'tc-no-bucket'}) + self._no_bucket = True + + @property + def no_bucket(self): + return self._no_bucket + + +class AbstractSorter(object): + """An abstract class for classifying Units with a set of Rules.""" + def __init__(self, dct): + self._type = 'sorter' + self._version = dct['version'] + self._world = dct['world'] + self._name = dct['name'] + self._root = dct.get('root', False) + self._order = dct['order'] + + self._rules = [] + for rule in dct['rules']: + if dct['world'] == 'vm': + self._rules.append(VMRule(rule)) + elif dct['world'] == 'malloc': + self._rules.append(MallocRule(rule)) + else: + LOGGER.error('Unknown sorter world type') + + def __repr__(self): + result = cStringIO.StringIO() + result.write('world=%s' % self._world) + result.write('order=%s' % self._order) + result.write('rules:') + for rule in self._rules: + result.write(' %s' % rule) + return result.getvalue() + + @staticmethod + def load(filename): + with open(filename) as sorter_f: + sorter_dict = json.load(sorter_f) + if sorter_dict['world'] == 'vm': + return VMSorter(sorter_dict) + elif sorter_dict['world'] == 'malloc': + return MallocSorter(sorter_dict) + else: + LOGGER.error('Unknown sorter world type') + return None + + @property + def world(self): + return self._world + + @property + def name(self): + return self._name + + @property + def root(self): + return self._root + + def find(self, unit): + raise NotImplementedError() + + def find_rule(self, name): + """Finds a rule whose name is |name|. """ + for rule in self._rules: + if rule.name == name: + return rule + return None + + +class VMSorter(AbstractSorter): + """Represents a Sorter for memory regions on virtual memory.""" + def __init__(self, dct): + assert dct['world'] == 'vm' + super(VMSorter, self).__init__(dct) + + def find(self, unit): + for rule in self._rules: + if rule.match(unit): + return rule + assert False + + +class MallocSorter(AbstractSorter): + """Represents a Sorter for malloc'ed blocks.""" + def __init__(self, dct): + assert dct['world'] == 'malloc' + super(MallocSorter, self).__init__(dct) + self._no_bucket_rule = NoBucketMallocRule() + + def find(self, unit): + if not unit.bucket: + return self._no_bucket_rule + assert unit.bucket.allocator_type == 'malloc' + + if unit.bucket.component_cache: + return unit.bucket.component_cache + + for rule in self._rules: + if rule.match(unit): + unit.bucket.component_cache = rule + return rule + assert False + + +class SorterTemplates(object): + """Represents a template for sorters.""" + def __init__(self, dct): + self._dict = dct + + def as_dict(self): + return self._dict + + @staticmethod + def load(filename): + with open(filename) as templates_f: + templates_dict = json.load(templates_f) + return SorterTemplates(templates_dict) + + +class SorterSet(object): + """Represents an iterable set of Sorters.""" + def __init__(self, additional=None, default=None): + if not additional: + additional = [] + if not default: + default = DEFAULT_SORTERS + self._sorters = {} + for filename in default + additional: + sorter = AbstractSorter.load(filename) + if sorter.world not in self._sorters: + self._sorters[sorter.world] = [] + self._sorters[sorter.world].append(sorter) + self._templates = SorterTemplates.load(DEFAULT_TEMPLATES) + + def __repr__(self): + result = cStringIO.StringIO() + result.write(self._sorters) + return result.getvalue() + + def __iter__(self): + for sorters in self._sorters.itervalues(): + for sorter in sorters: + yield sorter + + def iter_world(self, world): + for sorter in self._sorters.get(world, []): + yield sorter + + @property + def templates(self): + return self._templates diff --git a/tools/deep_memory_profiler/lib/subcommand.py b/tools/deep_memory_profiler/lib/subcommand.py new file mode 100644 index 0000000000..25416f6ee3 --- /dev/null +++ b/tools/deep_memory_profiler/lib/subcommand.py @@ -0,0 +1,160 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import logging +import optparse +import os +import re + +from lib.bucket import BucketSet +from lib.dump import Dump, DumpList +from lib.symbol import SymbolDataSources, SymbolMappingCache, SymbolFinder +from lib.symbol import proc_maps +from lib.symbol import FUNCTION_SYMBOLS, SOURCEFILE_SYMBOLS, TYPEINFO_SYMBOLS + + +LOGGER = logging.getLogger('dmprof') + +BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +CHROME_SRC_PATH = os.path.join(BASE_PATH, os.pardir, os.pardir) + + +class SubCommand(object): + """Subclasses are a subcommand for this executable. + + See COMMANDS in main() in dmprof.py. + """ + _DEVICE_BINDIRS = ['/data/data/', '/data/app-lib/', '/data/local/tmp'] + + def __init__(self, usage): + self._parser = optparse.OptionParser(usage) + + @staticmethod + def load_basic_files( + dump_path, multiple, no_dump=False, alternative_dirs=None): + prefix = SubCommand._find_prefix(dump_path) + # If the target process is estimated to be working on Android, converts + # a path in the Android device to a path estimated to be corresponding in + # the host. Use --alternative-dirs to specify the conversion manually. + if not alternative_dirs: + alternative_dirs = SubCommand._estimate_alternative_dirs(prefix) + if alternative_dirs: + for device, host in alternative_dirs.iteritems(): + LOGGER.info('Assuming %s on device as %s on host' % (device, host)) + symbol_data_sources = SymbolDataSources(prefix, alternative_dirs) + symbol_data_sources.prepare() + bucket_set = BucketSet() + bucket_set.load(prefix) + if not no_dump: + if multiple: + dump_list = DumpList.load(SubCommand._find_all_dumps(dump_path)) + else: + dump = Dump.load(dump_path) + symbol_mapping_cache = SymbolMappingCache() + with open(prefix + '.cache.function', 'a+') as cache_f: + symbol_mapping_cache.update( + FUNCTION_SYMBOLS, bucket_set, + SymbolFinder(FUNCTION_SYMBOLS, symbol_data_sources), cache_f) + with open(prefix + '.cache.typeinfo', 'a+') as cache_f: + symbol_mapping_cache.update( + TYPEINFO_SYMBOLS, bucket_set, + SymbolFinder(TYPEINFO_SYMBOLS, symbol_data_sources), cache_f) + with open(prefix + '.cache.sourcefile', 'a+') as cache_f: + symbol_mapping_cache.update( + SOURCEFILE_SYMBOLS, bucket_set, + SymbolFinder(SOURCEFILE_SYMBOLS, symbol_data_sources), cache_f) + bucket_set.symbolize(symbol_mapping_cache) + if no_dump: + return bucket_set + elif multiple: + return (bucket_set, dump_list) + else: + return (bucket_set, dump) + + @staticmethod + def _find_prefix(path): + return re.sub('\.[0-9][0-9][0-9][0-9]\.heap', '', path) + + @staticmethod + def _estimate_alternative_dirs(prefix): + """Estimates a path in host from a corresponding path in target device. + + For Android, dmprof.py should find symbol information from binaries in + the host instead of the Android device because dmprof.py doesn't run on + the Android device. This method estimates a path in the host + corresponding to a path in the Android device. + + Returns: + A dict that maps a path in the Android device to a path in the host. + If a file in SubCommand._DEVICE_BINDIRS is found in /proc/maps, it + assumes the process was running on Android and maps the path to + "out/Debug/lib" in the Chromium directory. An empty dict is returned + unless Android. + """ + device_lib_path_candidates = set() + + with open(prefix + '.maps') as maps_f: + maps = proc_maps.ProcMaps.load(maps_f) + for entry in maps: + name = entry.as_dict()['name'] + if any([base_dir in name for base_dir in SubCommand._DEVICE_BINDIRS]): + device_lib_path_candidates.add(os.path.dirname(name)) + + if len(device_lib_path_candidates) == 1: + return {device_lib_path_candidates.pop(): os.path.join( + CHROME_SRC_PATH, 'out', 'Debug', 'lib')} + else: + return {} + + @staticmethod + def _find_all_dumps(dump_path): + prefix = SubCommand._find_prefix(dump_path) + dump_path_list = [dump_path] + + n = int(dump_path[len(dump_path) - 9 : len(dump_path) - 5]) + n += 1 + skipped = 0 + while True: + p = '%s.%04d.heap' % (prefix, n) + if os.path.exists(p) and os.stat(p).st_size: + dump_path_list.append(p) + else: + if skipped > 10: + break + skipped += 1 + n += 1 + + return dump_path_list + + @staticmethod + def _find_all_buckets(dump_path): + prefix = SubCommand._find_prefix(dump_path) + bucket_path_list = [] + + n = 0 + while True: + path = '%s.%04d.buckets' % (prefix, n) + if not os.path.exists(path): + if n > 10: + break + n += 1 + continue + bucket_path_list.append(path) + n += 1 + + return bucket_path_list + + def _parse_args(self, sys_argv, required): + options, args = self._parser.parse_args(sys_argv) + if len(args) < required + 1: + self._parser.error('needs %d argument(s).\n' % required) + return None + return (options, args) + + @staticmethod + def _parse_policy_list(options_policy): + if options_policy: + return options_policy.split(',') + else: + return None diff --git a/tools/deep_memory_profiler/lib/symbol.py b/tools/deep_memory_profiler/lib/symbol.py new file mode 100644 index 0000000000..897d4098d5 --- /dev/null +++ b/tools/deep_memory_profiler/lib/symbol.py @@ -0,0 +1,189 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import logging +import os +import sys + +_BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +_FIND_RUNTIME_SYMBOLS_PATH = os.path.join(_BASE_PATH, + os.pardir, + 'find_runtime_symbols') +sys.path.append(_FIND_RUNTIME_SYMBOLS_PATH) + +import find_runtime_symbols +import prepare_symbol_info +import proc_maps # pylint: disable=W0611 + +LOGGER = logging.getLogger('dmprof') + +FUNCTION_SYMBOLS = find_runtime_symbols.FUNCTION_SYMBOLS +SOURCEFILE_SYMBOLS = find_runtime_symbols.SOURCEFILE_SYMBOLS +TYPEINFO_SYMBOLS = find_runtime_symbols.TYPEINFO_SYMBOLS + + +class SymbolDataSources(object): + """Manages symbol data sources in a process. + + The symbol data sources consist of maps (/proc//maps), nm, readelf and + so on. They are collected into a directory '|prefix|.symmap' from the binary + files by 'prepare()' with tools/find_runtime_symbols/prepare_symbol_info.py. + + Binaries are not mandatory to profile. The prepared data sources work in + place of the binary even if the binary has been overwritten with another + binary. + + Note that loading the symbol data sources takes a long time. They are often + very big. So, the 'dmprof' profiler is designed to use 'SymbolMappingCache' + which caches actually used symbols. + """ + def __init__(self, prefix, alternative_dirs=None): + self._prefix = prefix + self._prepared_symbol_data_sources_path = None + self._loaded_symbol_data_sources = None + self._alternative_dirs = alternative_dirs or {} + + def prepare(self): + """Prepares symbol data sources by extracting mapping from a binary. + + The prepared symbol data sources are stored in a directory. The directory + name is stored in |self._prepared_symbol_data_sources_path|. + + Returns: + True if succeeded. + """ + LOGGER.info('Preparing symbol mapping...') + self._prepared_symbol_data_sources_path, used_tempdir = ( + prepare_symbol_info.prepare_symbol_info( + self._prefix + '.maps', + output_dir_path=self._prefix + '.symmap', + alternative_dirs=self._alternative_dirs, + use_tempdir=True, + use_source_file_name=True)) + if self._prepared_symbol_data_sources_path: + LOGGER.info(' Prepared symbol mapping.') + if used_tempdir: + LOGGER.warn(' Using a temporary directory for symbol mapping.') + LOGGER.warn(' Delete it by yourself.') + LOGGER.warn(' Or, move the directory by yourself to use it later.') + return True + else: + LOGGER.warn(' Failed to prepare symbol mapping.') + return False + + def get(self): + """Returns the prepared symbol data sources. + + Returns: + The prepared symbol data sources. None if failed. + """ + if not self._prepared_symbol_data_sources_path and not self.prepare(): + return None + if not self._loaded_symbol_data_sources: + LOGGER.info('Loading symbol mapping...') + self._loaded_symbol_data_sources = ( + find_runtime_symbols.RuntimeSymbolsInProcess.load( + self._prepared_symbol_data_sources_path)) + return self._loaded_symbol_data_sources + + def path(self): + """Returns the path of the prepared symbol data sources if possible.""" + if not self._prepared_symbol_data_sources_path and not self.prepare(): + return None + return self._prepared_symbol_data_sources_path + + +class SymbolFinder(object): + """Finds corresponding symbols from addresses. + + This class does only 'find()' symbols from a specified |address_list|. + It is introduced to make a finder mockable. + """ + def __init__(self, symbol_type, symbol_data_sources): + self._symbol_type = symbol_type + self._symbol_data_sources = symbol_data_sources + + def find(self, address_list): + return find_runtime_symbols.find_runtime_symbols( + self._symbol_type, self._symbol_data_sources.get(), address_list) + + +class SymbolMappingCache(object): + """Caches mapping from actually used addresses to symbols. + + 'update()' updates the cache from the original symbol data sources via + 'SymbolFinder'. Symbols can be looked up by the method 'lookup()'. + """ + def __init__(self): + self._symbol_mapping_caches = { + FUNCTION_SYMBOLS: {}, + SOURCEFILE_SYMBOLS: {}, + TYPEINFO_SYMBOLS: {}, + } + + def update(self, symbol_type, bucket_set, symbol_finder, cache_f): + """Updates symbol mapping cache on memory and in a symbol cache file. + + It reads cached symbol mapping from a symbol cache file |cache_f| if it + exists. Unresolved addresses are then resolved and added to the cache + both on memory and in the symbol cache file with using 'SymbolFinder'. + + A cache file is formatted as follows: +
+
+
+ ... + + Args: + symbol_type: A type of symbols to update. It should be one of + FUNCTION_SYMBOLS, SOURCEFILE_SYMBOLS and TYPEINFO_SYMBOLS. + bucket_set: A BucketSet object. + symbol_finder: A SymbolFinder object to find symbols. + cache_f: A readable and writable IO object of the symbol cache file. + """ + cache_f.seek(0, os.SEEK_SET) + self._load(cache_f, symbol_type) + + unresolved_addresses = sorted( + address for address in bucket_set.iter_addresses(symbol_type) + if address not in self._symbol_mapping_caches[symbol_type]) + + if not unresolved_addresses: + LOGGER.info('No need to resolve any more addresses.') + return + + cache_f.seek(0, os.SEEK_END) + LOGGER.info('Loading %d unresolved addresses.' % + len(unresolved_addresses)) + symbol_dict = symbol_finder.find(unresolved_addresses) + + for address, symbol in symbol_dict.iteritems(): + stripped_symbol = symbol.strip() or '?' + self._symbol_mapping_caches[symbol_type][address] = stripped_symbol + cache_f.write('%x %s\n' % (address, stripped_symbol)) + + def lookup(self, symbol_type, address): + """Looks up a symbol for a given |address|. + + Args: + symbol_type: A type of symbols to update. It should be one of + FUNCTION_SYMBOLS, SOURCEFILE_SYMBOLS and TYPEINFO_SYMBOLS. + address: An integer that represents an address. + + Returns: + A string that represents a symbol. + """ + return self._symbol_mapping_caches[symbol_type].get(address) + + def _load(self, cache_f, symbol_type): + try: + for line in cache_f: + items = line.rstrip().split(None, 1) + if len(items) == 1: + items.append('??') + self._symbol_mapping_caches[symbol_type][int(items[0], 16)] = items[1] + LOGGER.info('Loaded %d entries from symbol cache.' % + len(self._symbol_mapping_caches[symbol_type])) + except IOError as e: + LOGGER.info('The symbol cache file is invalid: %s' % e) diff --git a/tools/deep_memory_profiler/policies.json b/tools/deep_memory_profiler/policies.json new file mode 100644 index 0000000000..775045a8c2 --- /dev/null +++ b/tools/deep_memory_profiler/policies.json @@ -0,0 +1,34 @@ +{ + "android.browser": { + "file": "policy.android.browser.json", + "format": "json" + }, + "android.renderer": { + "file": "policy.android.renderer.json", + "format": "json" + }, + "android.webview": { + "file": "policy.android.webview.json", + "format": "json" + }, + "sourcefile": { + "file": "policy.sourcefile.json", + "format": "json" + }, + "l0": { + "file": "policy.l0.json", + "format": "json" + }, + "l1": { + "file": "policy.l1.json", + "format": "json" + }, + "l2": { + "file": "policy.l2.json", + "format": "json" + }, + "t0": { + "file": "policy.t0.json", + "format": "json" + } +} \ No newline at end of file diff --git a/tools/deep_memory_profiler/policy.android.browser.json b/tools/deep_memory_profiler/policy.android.browser.json new file mode 100644 index 0000000000..e34fee7253 --- /dev/null +++ b/tools/deep_memory_profiler/policy.android.browser.json @@ -0,0 +1,301 @@ +{ + "components": [ + "second", + "mmap-profiler", + "mmap-type-profiler", + "mmap-tcmalloc", + "FROM_HERE_FOR_TOTAL", + "mustbezero", + "unhooked-absent", + "unhooked-ashmem-dalvik-heap", + "unhooked-ashmem-dalvik-LinearAlloc", + "unhooked-ashmem-dalvik-aux-structure", + "unhooked-ashmem-dalvik-bitmap", + "unhooked-ashmem-dalvik-other", + "unhooked-pvrsrvkm", + "unhooked-system-dex", + "unhooked-chrome-dex", + "unhooked-other-ashmem", + "unhooked-anonymous", + "unhooked-file-exec-lib-chrome", + "unhooked-file-exec", + "unhooked-file-nonexec-lib-chrome", + "unhooked-file-nonexec", + "unhooked-stack", + "unhooked-other", + "no-bucket", + "mmap-gpu-transferbuffer", + "mmap-gpu-ringbuffer", + "mmap-catch-all", + "tc-disk_cache-backing", + "tc-disk_cache-other", + "tc-sqlite3MemAlloc", + "tc-angle", + "tc-crypto", + "tc-net-iobuffer", + "tc-stl-string", + "tc-stl-rbtree", + "tc-stl-vector", + "tc-stl-hashtable", + "tc-stl-node", + "tc-catch-all", + "tc-unused", + "UNTIL_HERE_FOR_TOTAL", + "total-exclude-profiler", + "total", + "absent", + "anonymous", + "file-exec", + "file-nonexec", + "stack", + "other", + "mmap-total-log", + "mmap-no-log", + "mmap-total-record", + "other-total-log", + "tc-total-log", + "tc-no-log", + "tc-total-record", + "tc-total" + ], + "rules": [ + { + "name": "second", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "mmap-profiler", + "stacktrace": ".*(ProfilerMalloc|MemoryRegionMap::).*", + "allocator": "mmap" + }, + { + "name": "mmap-type-profiler", + "stacktrace": ".*(TypeProfilerMalloc).*", + "allocator": "mmap" + }, + { + "name": "mmap-tcmalloc", + "stacktrace": ".*(DoAllocWithArena|SbrkSysAllocator::Alloc|MmapSysAllocator::Alloc|LowLevelAlloc::Alloc|LowLevelAlloc::AllocWithArena).*", + "allocator": "mmap" + }, + { + "name": "FROM_HERE_FOR_TOTAL", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "mustbezero", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "unhooked-absent", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "unhooked-ashmem-dalvik-heap", + "mappedpathname": "/dev/ashmem/dalvik-heap.*", + "allocator": "unhooked" + }, + { + "name": "unhooked-ashmem-dalvik-LinearAlloc", + "mappedpathname": "/dev/ashmem/dalvik-LinearAlloc.*", + "allocator": "unhooked" + }, + { + "name": "unhooked-ashmem-dalvik-aux-structure", + "mappedpathname": "/dev/ashmem/dalvik-aux-structure.*", + "allocator": "unhooked" + }, + { + "name": "unhooked-ashmem-dalvik-bitmap", + "mappedpathname": "/dev/ashmem/dalvik-bitmap.*", + "allocator": "unhooked" + }, + { + "name": "unhooked-ashmem-dalvik-other", + "mappedpathname": "/dev/ashmem/dalvik.*", + "allocator": "unhooked" + }, + { + "name": "unhooked-pvrsrvkm", + "mappedpathname": "/dev/pvrsrvkm.*", + "allocator": "unhooked" + }, + { + "name": "unhooked-system-dex", + "mappedpathname": "/data/dalvik-cache/system.*.dex.*", + "allocator": "unhooked" + }, + { + "name": "unhooked-chrome-dex", + "mappedpathname": "^/.*?(chrome|content).*?apk@classes.dex", + "allocator": "unhooked" + }, + { + "name": "unhooked-other-ashmem", + "mappedpathname": "/dev/ashmem/.*", + "allocator": "unhooked" + }, + { + "name": "unhooked-anonymous", + "mappedpathname": "^$", + "allocator": "unhooked" + }, + { + "name": "unhooked-file-exec-lib-chrome", + "mappedpathname": "^/.*?(chromeview|content).*", + "mappedpermission": "..x.", + "allocator": "unhooked" + }, + { + "name": "unhooked-file-exec", + "mappedpathname": "^/.*", + "mappedpermission": "..x.", + "allocator": "unhooked" + }, + { + "name": "unhooked-file-nonexec-lib-chrome", + "mappedpathname": "^/.*?(chromeview|content).*", + "allocator": "unhooked" + }, + { + "name": "unhooked-file-nonexec", + "mappedpathname": "^/.*", + "allocator": "unhooked" + }, + { + "name": "unhooked-stack", + "mappedpathname": ".stack.", + "allocator": "unhooked" + }, + { + "name": "unhooked-other", + "mappedpathname": ".*", + "allocator": "unhooked" + }, + { + "name": "mmap-gpu-transferbuffer", + "stacktrace": ".*gpu::TransferBufferManager::RegisterTransferBuffer.*", + "allocator": "mmap" + }, + { + "name": "mmap-gpu-ringbuffer", + "stacktrace": ".*gpu::CommandBufferHelper::AllocateRingBuffer.*", + "allocator": "mmap" + }, + { + "name": "mmap-catch-all", + "stacktrace": ".*", + "allocator": "mmap" + }, + { + "name": "tc-disk_cache-backing", + "stacktrace": ".*disk_cache::BackendImpl::InitBackingStore.*", + "allocator": "malloc" + }, + { + "name": "tc-disk_cache-other", + "stacktrace": ".*disk_cache::.*", + "allocator": "malloc" + }, + { + "name": "tc-sqlite3MemAlloc", + "stacktrace": ".*sqlite3MemMalloc.*", + "allocator": "malloc" + }, + { + "name": "tc-angle", + "stacktrace": ".*TPoolAllocator::allocate.*", + "allocator": "malloc" + }, + { + "name": "tc-crypto", + "stacktrace": ".*(CRYPTO_malloc|CRYPTO_realloc).*", + "allocator": "malloc" + }, + { + "name": "tc-net-iobuffer", + "stacktrace": ".*net::IOBuffer::IOBuffer.*", + "allocator": "malloc" + }, + { + "name": "tc-stl-string", + "stacktrace": ".*std::basic_string::.*", + "allocator": "malloc" + }, + { + "name": "tc-stl-rbtree", + "stacktrace": ".*std::priv::_Rb_tree::.*", + "allocator": "malloc" + }, + { + "name": "tc-stl-vector", + "stacktrace": ".*std::priv::_Impl_vector::.*", + "allocator": "malloc" + }, + { + "name": "tc-stl-hashtable", + "stacktrace": ".*std::hashtable::.*", + "allocator": "malloc" + }, + { + "name": "tc-stl-node", + "stacktrace": ".*std::priv::_Impl_vector::.*", + "allocator": "malloc" + }, + { + "name": "tc-catch-all", + "stacktrace": ".*", + "allocator": "malloc" + }, + { + "name": "UNTIL_HERE_FOR_TOTAL", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "total-exclude-profiler", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "total", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "absent", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "anonymous", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "file-exec", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "file-nonexec", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "stack", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "other", + "stacktrace": "optional", + "allocator": "optional" + } + ], + "version": "POLICY_DEEP_3" +} diff --git a/tools/deep_memory_profiler/policy.android.renderer.json b/tools/deep_memory_profiler/policy.android.renderer.json new file mode 100644 index 0000000000..c039c6f97a --- /dev/null +++ b/tools/deep_memory_profiler/policy.android.renderer.json @@ -0,0 +1,579 @@ +{ + "components": [ + "second", + "mmap-profiler", + "mmap-type-profiler", + "mmap-tcmalloc", + "FROM_HERE_FOR_TOTAL", + "mustbezero", + "unhooked-absent", + "unhooked-ashmem-dalvik-heap", + "unhooked-ashmem-dalvik-LinearAlloc", + "unhooked-ashmem-dalvik-aux-structure", + "unhooked-ashmem-dalvik-bitmap", + "unhooked-ashmem-dalvik-other", + "unhooked-pvrsrvkm", + "unhooked-system-dex", + "unhooked-chrome-dex", + "unhooked-other-ashmem", + "unhooked-anonymous", + "unhooked-file-exec-lib-chrome", + "unhooked-file-exec", + "unhooked-file-nonexec-lib-chrome", + "unhooked-file-nonexec", + "unhooked-stack", + "unhooked-other", + "no-bucket", + "mmap-v8-heap-newspace", + "mmap-v8-heap-coderange", + "mmap-v8-heap-pagedspace", + "mmap-v8-other", + "mmap-gpu-mappedmemorymanager", + "mmap-gpu-command-ringbuffer", + "mmap-gpu-transfer-ringbuffer", + "mmap-gpu-gles2-createbuffer", + "mmap-skia-font", + "mmap-catch-all", + "tc-webcore-fontcache", + "tc-skia", + "tc-renderobject", + "tc-renderstyle", + "tc-webcore-sharedbuf", + "tc-webcore-XHRcreate", + "tc-webcore-XHRreceived", + "tc-webcore-docwriter-add", + "tc-webcore-node-and-doc", + "tc-webcore-node-factory", + "tc-webcore-element-wrapper", + "tc-webcore-stylepropertyset", + "tc-webcore-style-createsheet", + "tc-webcore-cachedresource", + "tc-webcore-script-execute", + "tc-webcore-events-related", + "tc-webcore-document-write", + "tc-webcore-node-create-renderer", + "tc-webcore-image-frame-generator", + "tc-webcore-render-catch-all", + "tc-webcore-setInnerHTML-except-node", + "tc-wtf-StringImpl-user-catch-all", + "tc-wtf-HashTable-user-catch-all", + "tc-webcore-everything-create", + "tc-webkit-from-v8-catch-all", + "tc-webkit-catch-all", + "tc-v8-catch-all", + "tc-toplevel-string", + "tc-std-treemap", + "tc-std-hashmap", + "tc-std-vector", + "tc-std-other", + "tc-catch-all", + "tc-unused", + "UNTIL_HERE_FOR_TOTAL", + "total-exclude-profiler", + "total", + "absent", + "anonymous", + "file-exec", + "file-nonexec", + "stack", + "other", + "mmap-total-log", + "mmap-no-log", + "mmap-total-record", + "other-total-log", + "tc-total-log", + "tc-no-log", + "tc-total-record", + "tc-total" + ], + "rules": [ + { + "name": "second", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "mmap-profiler", + "stacktrace": ".*(ProfilerMalloc|MemoryRegionMap::).*", + "allocator": "mmap" + }, + { + "name": "mmap-type-profiler", + "stacktrace": ".*(TypeProfilerMalloc).*", + "allocator": "mmap" + }, + { + "name": "mmap-tcmalloc", + "stacktrace": ".*(DoAllocWithArena|SbrkSysAllocator::Alloc|MmapSysAllocator::Alloc|LowLevelAlloc::Alloc|LowLevelAlloc::AllocWithArena).*", + "allocator": "mmap" + }, + { + "name": "FROM_HERE_FOR_TOTAL", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "mustbezero", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "unhooked-absent", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "unhooked-ashmem-dalvik-heap", + "mappedpathname": "/dev/ashmem/dalvik-heap.*", + "allocator": "unhooked" + }, + { + "name": "unhooked-ashmem-dalvik-LinearAlloc", + "mappedpathname": "/dev/ashmem/dalvik-LinearAlloc.*", + "allocator": "unhooked" + }, + { + "name": "unhooked-ashmem-dalvik-aux-structure", + "mappedpathname": "/dev/ashmem/dalvik-aux-structure.*", + "allocator": "unhooked" + }, + { + "name": "unhooked-ashmem-dalvik-bitmap", + "mappedpathname": "/dev/ashmem/dalvik-bitmap.*", + "allocator": "unhooked" + }, + { + "name": "unhooked-ashmem-dalvik-other", + "mappedpathname": "/dev/ashmem/dalvik.*", + "allocator": "unhooked" + }, + { + "name": "unhooked-pvrsrvkm", + "mappedpathname": "/dev/pvrsrvkm.*", + "allocator": "unhooked" + }, + { + "name": "unhooked-system-dex", + "mappedpathname": "/data/dalvik-cache/system.*.dex.*", + "allocator": "unhooked" + }, + { + "name": "unhooked-chrome-dex", + "mappedpathname": "^/.*?(chrome|content).*?apk@classes.dex", + "allocator": "unhooked" + }, + { + "name": "unhooked-other-ashmem", + "mappedpathname": "/dev/ashmem/.*", + "allocator": "unhooked" + }, + { + "name": "unhooked-anonymous", + "mappedpathname": "^$", + "allocator": "unhooked" + }, + { + "name": "unhooked-file-exec-lib-chrome", + "mappedpathname": "^/.*?(chromeview|content).*", + "mappedpermission": "..x.", + "allocator": "unhooked" + }, + { + "name": "unhooked-file-exec", + "mappedpathname": "^/.*", + "mappedpermission": "..x.", + "allocator": "unhooked" + }, + { + "name": "unhooked-file-nonexec-lib-chrome", + "mappedpathname": "^/.*?(chromeview|content).*", + "allocator": "unhooked" + }, + { + "name": "unhooked-file-nonexec", + "mappedpathname": "^/.*", + "allocator": "unhooked" + }, + { + "name": "unhooked-stack", + "mappedpathname": ".stack.", + "allocator": "unhooked" + }, + { + "name": "unhooked-other", + "mappedpathname": ".*", + "allocator": "unhooked" + }, + { + "name": "mmap-v8-heap-newspace", + "stacktrace": ".*v8::internal::NewSpace::SetUp.*", + "allocator": "mmap" + }, + { + "name": "mmap-v8-heap-coderange", + "stacktrace": ".*v8::internal::CodeRange::SetUp.*", + "allocator": "mmap" + }, + { + "name": "mmap-v8-heap-pagedspace", + "stacktrace": ".*v8::internal::PagedSpace::AllocateRaw.*", + "allocator": "mmap" + }, + { + "name": "mmap-v8-other", + "stacktrace": ".*v8::.*", + "allocator": "mmap" + }, + { + "name": "mmap-gpu-mappedmemorymanager", + "stacktrace": ".*gpu::MappedMemoryManager::Alloc.*", + "allocator": "mmap" + }, + { + "name": "mmap-gpu-command-ringbuffer", + "stacktrace": ".*gpu::CommandBufferHelper::AllocateRingBuffer.*", + "allocator": "mmap" + }, + { + "name": "mmap-gpu-transfer-ringbuffer", + "stacktrace": ".*gpu::TransferBuffer::AllocateRingBuffer.*", + "allocator": "mmap" + }, + { + "name": "mmap-gpu-gles2-createbuffer", + "stacktrace": ".*gpu::gles2::BufferTracker::CreateBuffer.*", + "allocator": "mmap" + }, + { + "name": "mmap-skia-font", + "stacktrace": ".*SkTypeface::openStream.*", + "allocator": "mmap" + }, + { + "name": "mmap-catch-all", + "stacktrace": ".*", + "allocator": "mmap" + }, + { + "name": "tc-webcore-fontcache", + "stacktrace": ".*WebCore::FontCache::getCachedFontData.*", + "allocator": "malloc" + }, + { + "name": "tc-skia", + "stacktrace": ".* Sk[A-Za-z_]+::.*", + "allocator": "malloc" + }, + { + "name": "tc-renderobject", + "stacktrace": ".*WebCore::RenderArena::allocate.*", + "allocator": "malloc" + }, + { + "name": "tc-renderstyle", + "stacktrace": ".*WebCore::RenderStyle::create.*", + "allocator": "malloc" + }, + { + "name": "tc-renderstyle", + "stacktrace": ".*WebCore::RenderStyle::clone.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-sharedbuf", + "stacktrace": ".*WebCore::SharedBuffer::create.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-sharedbuf", + "stacktrace": ".*WebCore::SharedBuffer::buffer.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-sharedbuf", + "stacktrace": ".*WebCore::SharedBuffer::append.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-XHRcreate", + "stacktrace": ".*WebCore::XMLHttpRequest::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-XHRreceived", + "stacktrace": ".*WebCore::XMLHttpRequest::didReceiveData.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-docwriter-add", + "stacktrace": ".*WebCore::DocumentWriter::addData.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-node-and-doc", + "stacktrace": ".*WebCore::HTML[a-zA-Z0-9_]*Element::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-node-and-doc", + "stacktrace": ".*WebCore::Text::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-node-and-doc", + "stacktrace": ".*WebCore::Comment::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-node-and-doc", + "stacktrace": ".*WebCore::HTMLDocument::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-node-and-doc", + "stacktrace": ".*WebCore::CSSStyleRule::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-node-and-doc", + "stacktrace": ".*WebCore::Attribute::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-node-and-doc", + "stacktrace": ".*WebCore::DOMWindow::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-node-factory", + "stacktrace": ".*WebCore::HTML[a-zA-Z0-9_]*Factory::create[a-zA-Z0-9_]*Element.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-element-wrapper", + "stacktrace": ".*WebCore::createHTML[a-zA-Z0-9_]*ElementWrapper.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-stylepropertyset", + "stacktrace": ".*WebCore::StylePropertySet::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-style-createsheet", + "stacktrace": ".*WebCore::StyleElement::createSheet.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-cachedresource", + "stacktrace": ".*WebCore::CachedResource::data .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-cachedresource", + "stacktrace": ".*WebCore::CachedResource::load .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-script-execute", + "stacktrace": ".*WebCore::ScriptElement::execute.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-events-related", + "stacktrace": ".*WebCore::createAttributeEventListener.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-events-related", + "stacktrace": ".*WebCore::V8LazyEventListener::create.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-events-related", + "stacktrace": ".*WebCore::V8EventListener::create.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-events-related", + "stacktrace": ".*WebCore::Event::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-events-related", + "stacktrace": ".*WebCore::EventListener::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-document-write", + "stacktrace": ".*WebCore::Document::write.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-node-create-renderer", + "stacktrace": ".*WebCore::Node::createRendererIfNeeded.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-image-frame-generator", + "stacktrace": ".*WebCore::ImageFrameGenerator.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-render-catch-all", + "stacktrace": ".*WebCore::RenderLayer.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-render-catch-all", + "stacktrace": ".*WebCore::RenderBlock.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-render-catch-all", + "stacktrace": ".*WebCore::RenderWidget.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-render-catch-all", + "stacktrace": ".*WebCore::RenderView.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-render-catch-all", + "stacktrace": ".*WebCore::RenderViewImpl.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-render-catch-all", + "stacktrace": ".*WebCore::RenderStyle.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-render-catch-all", + "stacktrace": ".*WebCore::RenderText.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-render-catch-all", + "stacktrace": ".* RendererMain .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-setInnerHTML-except-node", + "stacktrace": ".*WebCore::HTMLElement::setInnerHTML.*", + "allocator": "malloc" + }, + { + "name": "tc-wtf-StringImpl-user-catch-all", + "stacktrace": ".*WTF::StringImpl::create .*", + "allocator": "malloc" + }, + { + "name": "tc-wtf-StringImpl-user-catch-all", + "stacktrace": ".*WTF::StringImpl::createUninitialized.*", + "allocator": "malloc" + }, + { + "name": "tc-wtf-HashTable-user-catch-all", + "stacktrace": ".*WTF::HashTable::allocateTable.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-everything-create", + "stacktrace": ".*WebCore::[a-zA-Z0-9_]*::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webkit-from-v8-catch-all", + "stacktrace": ".*(WTF::|WebCore::|WebKit::).*v8::.*", + "allocator": "malloc" + }, + { + "name": "tc-webkit-catch-all", + "stacktrace": ".*(WTF::|WebCore::|WebKit::).*", + "allocator": "malloc" + }, + { + "name": "tc-v8-catch-all", + "stacktrace": ".*v8::.*", + "allocator": "malloc" + }, + { + "name": "tc-toplevel-string", + "stacktrace": "std::basic_string::_Rep::_S_create", + "allocator": "malloc" + }, + { + "name": "tc-std-treemap", + "stacktrace": ".*::allocate std::(_Rb_tree|__1::__tree).*", + "allocator": "malloc" + }, + { + "name": "tc-std-hashmap", + "stacktrace": ".*(std::vector::reserve __gnu_cxx::hashtable|::allocate std::_Hashtable|::allocate std::__1::__hash_table).*", + "allocator": "malloc" + }, + { + "name": "tc-std-vector", + "stacktrace": ".*std::(_Vector_base::_M_allocate|__1::vector::allocate).*", + "allocator": "malloc" + }, + { + "name": "tc-std-other", + "stacktrace": ".*(__gnu_cxx::new_allocator::allocate|std::__1::allocator::allocate).*", + "allocator": "malloc" + }, + { + "name": "tc-catch-all", + "stacktrace": ".*", + "allocator": "malloc" + }, + { + "name": "UNTIL_HERE_FOR_TOTAL", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "total-exclude-profiler", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "total", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "absent", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "anonymous", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "file-exec", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "file-nonexec", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "stack", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "other", + "stacktrace": "optional", + "allocator": "optional" + } + ], + "version": "POLICY_DEEP_3" +} diff --git a/tools/deep_memory_profiler/policy.android.webview.json b/tools/deep_memory_profiler/policy.android.webview.json new file mode 100644 index 0000000000..d3611ce843 --- /dev/null +++ b/tools/deep_memory_profiler/policy.android.webview.json @@ -0,0 +1,639 @@ +{ + "components": [ + "second", + "mmap-profiler", + "mmap-type-profiler", + "mmap-tcmalloc", + "FROM_HERE_FOR_TOTAL", + "mustbezero", + "unhooked-absent", + "unhooked-ashmem-dalvik-heap", + "unhooked-ashmem-dalvik-LinearAlloc", + "unhooked-ashmem-dalvik-aux-structure", + "unhooked-ashmem-dalvik-bitmap", + "unhooked-ashmem-dalvik-other", + "unhooked-pvrsrvkm", + "unhooked-system-dex", + "unhooked-chrome-dex", + "unhooked-other-ashmem", + "unhooked-anonymous", + "unhooked-file-exec-lib-chrome", + "unhooked-file-exec", + "unhooked-file-nonexec-lib-chrome", + "unhooked-file-nonexec", + "unhooked-stack", + "unhooked-other", + "no-bucket", + "mmap-v8-heap-newspace", + "mmap-v8-heap-coderange", + "mmap-v8-heap-pagedspace", + "mmap-v8-other", + "mmap-gpu-mappedmemorymanager", + "mmap-gpu-command-ringbuffer", + "mmap-gpu-transfer-ringbuffer", + "mmap-gpu-gles2-createbuffer", + "mmap-skia-font", + "mmap-catch-all", + "tc-disk_cache-backing", + "tc-disk_cache-other", + "tc-sqlite3MemAlloc", + "tc-angle", + "tc-crypto", + "tc-net-iobuffer", + "tc-stl-string", + "tc-stl-rbtree", + "tc-stl-vector", + "tc-stl-hashtable", + "tc-stl-node", + "tc-webcore-fontcache", + "tc-skia", + "tc-renderobject", + "tc-renderstyle", + "tc-webcore-sharedbuf", + "tc-webcore-XHRcreate", + "tc-webcore-XHRreceived", + "tc-webcore-docwriter-add", + "tc-webcore-node-and-doc", + "tc-webcore-node-factory", + "tc-webcore-element-wrapper", + "tc-webcore-stylepropertyset", + "tc-webcore-style-createsheet", + "tc-webcore-cachedresource", + "tc-webcore-script-execute", + "tc-webcore-events-related", + "tc-webcore-document-write", + "tc-webcore-node-create-renderer", + "tc-webcore-render-catch-all", + "tc-webcore-setInnerHTML-except-node", + "tc-wtf-StringImpl-user-catch-all", + "tc-wtf-HashTable-user-catch-all", + "tc-webcore-everything-create", + "tc-webkit-from-v8-catch-all", + "tc-webkit-catch-all", + "tc-v8-catch-all", + "tc-toplevel-string", + "tc-std-treemap", + "tc-std-hashmap", + "tc-std-vector", + "tc-std-other", + "tc-catch-all", + "tc-unused", + "UNTIL_HERE_FOR_TOTAL", + "total-exclude-profiler", + "total", + "absent", + "anonymous", + "file-exec", + "file-nonexec", + "stack", + "other", + "mmap-total-log", + "mmap-no-log", + "mmap-total-record", + "other-total-log", + "tc-total-log", + "tc-no-log", + "tc-total-record", + "tc-total" + ], + "rules": [ + { + "name": "second", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "mmap-profiler", + "stacktrace": ".*(ProfilerMalloc|MemoryRegionMap::).*", + "allocator": "mmap" + }, + { + "name": "mmap-type-profiler", + "stacktrace": ".*(TypeProfilerMalloc).*", + "allocator": "mmap" + }, + { + "name": "mmap-tcmalloc", + "stacktrace": ".*(DoAllocWithArena|SbrkSysAllocator::Alloc|MmapSysAllocator::Alloc|LowLevelAlloc::Alloc|LowLevelAlloc::AllocWithArena).*", + "allocator": "mmap" + }, + { + "name": "FROM_HERE_FOR_TOTAL", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "mustbezero", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "unhooked-absent", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "unhooked-ashmem-dalvik-heap", + "mappedpathname": "/dev/ashmem/dalvik-heap.*", + "allocator": "unhooked" + }, + { + "name": "unhooked-ashmem-dalvik-LinearAlloc", + "mappedpathname": "/dev/ashmem/dalvik-LinearAlloc.*", + "allocator": "unhooked" + }, + { + "name": "unhooked-ashmem-dalvik-aux-structure", + "mappedpathname": "/dev/ashmem/dalvik-aux-structure.*", + "allocator": "unhooked" + }, + { + "name": "unhooked-ashmem-dalvik-bitmap", + "mappedpathname": "/dev/ashmem/dalvik-bitmap.*", + "allocator": "unhooked" + }, + { + "name": "unhooked-ashmem-dalvik-other", + "mappedpathname": "/dev/ashmem/dalvik.*", + "allocator": "unhooked" + }, + { + "name": "unhooked-pvrsrvkm", + "mappedpathname": "/dev/pvrsrvkm.*", + "allocator": "unhooked" + }, + { + "name": "unhooked-system-dex", + "mappedpathname": "/data/dalvik-cache/system.*.dex.*", + "allocator": "unhooked" + }, + { + "name": "unhooked-chrome-dex", + "mappedpathname": "^/.*?(chrome|content).*?apk@classes.dex", + "allocator": "unhooked" + }, + { + "name": "unhooked-other-ashmem", + "mappedpathname": "/dev/ashmem/.*", + "allocator": "unhooked" + }, + { + "name": "unhooked-anonymous", + "mappedpathname": "^$", + "allocator": "unhooked" + }, + { + "name": "unhooked-file-exec-lib-chrome", + "mappedpathname": "^/.*?(chromeview|content).*", + "mappedpermission": "..x.", + "allocator": "unhooked" + }, + { + "name": "unhooked-file-exec", + "mappedpathname": "^/.*", + "mappedpermission": "..x.", + "allocator": "unhooked" + }, + { + "name": "unhooked-file-nonexec-lib-chrome", + "mappedpathname": "^/.*?(chromeview|content).*", + "allocator": "unhooked" + }, + { + "name": "unhooked-file-nonexec", + "mappedpathname": "^/.*", + "allocator": "unhooked" + }, + { + "name": "unhooked-stack", + "mappedpathname": ".stack.", + "allocator": "unhooked" + }, + { + "name": "unhooked-other", + "mappedpathname": ".*", + "allocator": "unhooked" + }, + { + "name": "mmap-v8-heap-newspace", + "stacktrace": ".*v8::internal::NewSpace::SetUp.*", + "allocator": "mmap" + }, + { + "name": "mmap-v8-heap-coderange", + "stacktrace": ".*v8::internal::CodeRange::SetUp.*", + "allocator": "mmap" + }, + { + "name": "mmap-v8-heap-pagedspace", + "stacktrace": ".*v8::internal::PagedSpace::AllocateRaw.*", + "allocator": "mmap" + }, + { + "name": "mmap-v8-other", + "stacktrace": ".*v8::.*", + "allocator": "mmap" + }, + { + "name": "mmap-gpu-mappedmemorymanager", + "stacktrace": ".*gpu::MappedMemoryManager::Alloc.*", + "allocator": "mmap" + }, + { + "name": "mmap-gpu-command-ringbuffer", + "stacktrace": ".*gpu::CommandBufferHelper::AllocateRingBuffer.*", + "allocator": "mmap" + }, + { + "name": "mmap-gpu-transfer-ringbuffer", + "stacktrace": ".*gpu::TransferBuffer::AllocateRingBuffer.*", + "allocator": "mmap" + }, + { + "name": "mmap-gpu-gles2-createbuffer", + "stacktrace": ".*gpu::gles2::BufferTracker::CreateBuffer.*", + "allocator": "mmap" + }, + { + "name": "mmap-skia-font", + "stacktrace": ".*SkTypeface::openStream.*", + "allocator": "mmap" + }, + { + "name": "mmap-catch-all", + "stacktrace": ".*", + "allocator": "mmap" + }, + { + "name": "tc-disk_cache-backing", + "stacktrace": ".*disk_cache::BackendImpl::InitBackingStore.*", + "allocator": "malloc" + }, + { + "name": "tc-disk_cache-other", + "stacktrace": ".*disk_cache::.*", + "allocator": "malloc" + }, + { + "name": "tc-sqlite3MemAlloc", + "stacktrace": ".*sqlite3MemMalloc.*", + "allocator": "malloc" + }, + { + "name": "tc-angle", + "stacktrace": ".*TPoolAllocator::allocate.*", + "allocator": "malloc" + }, + { + "name": "tc-crypto", + "stacktrace": ".*(CRYPTO_malloc|CRYPTO_realloc).*", + "allocator": "malloc" + }, + { + "name": "tc-net-iobuffer", + "stacktrace": ".*net::IOBuffer::IOBuffer.*", + "allocator": "malloc" + }, + { + "name": "tc-stl-string", + "stacktrace": ".*std::basic_string::.*", + "allocator": "malloc" + }, + { + "name": "tc-stl-rbtree", + "stacktrace": ".*std::priv::_Rb_tree::.*", + "allocator": "malloc" + }, + { + "name": "tc-stl-vector", + "stacktrace": ".*std::priv::_Impl_vector::.*", + "allocator": "malloc" + }, + { + "name": "tc-stl-hashtable", + "stacktrace": ".*std::hashtable::.*", + "allocator": "malloc" + }, + { + "name": "tc-stl-node", + "stacktrace": ".*std::priv::_Impl_vector::.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-fontcache", + "stacktrace": ".*WebCore::FontCache::getCachedFontData.*", + "allocator": "malloc" + }, + { + "name": "tc-skia", + "stacktrace": ".* Sk[A-Za-z_]+::.*", + "allocator": "malloc" + }, + { + "name": "tc-renderobject", + "stacktrace": ".*WebCore::RenderArena::allocate.*", + "allocator": "malloc" + }, + { + "name": "tc-renderstyle", + "stacktrace": ".*WebCore::RenderStyle::create.*", + "allocator": "malloc" + }, + { + "name": "tc-renderstyle", + "stacktrace": ".*WebCore::RenderStyle::clone.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-sharedbuf", + "stacktrace": ".*WebCore::SharedBuffer::create.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-sharedbuf", + "stacktrace": ".*WebCore::SharedBuffer::buffer.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-sharedbuf", + "stacktrace": ".*WebCore::SharedBuffer::append.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-XHRcreate", + "stacktrace": ".*WebCore::XMLHttpRequest::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-XHRreceived", + "stacktrace": ".*WebCore::XMLHttpRequest::didReceiveData.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-docwriter-add", + "stacktrace": ".*WebCore::DocumentWriter::addData.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-node-and-doc", + "stacktrace": ".*WebCore::HTML[a-zA-Z0-9_]*Element::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-node-and-doc", + "stacktrace": ".*WebCore::Text::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-node-and-doc", + "stacktrace": ".*WebCore::Comment::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-node-and-doc", + "stacktrace": ".*WebCore::HTMLDocument::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-node-and-doc", + "stacktrace": ".*WebCore::CSSStyleRule::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-node-and-doc", + "stacktrace": ".*WebCore::Attribute::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-node-and-doc", + "stacktrace": ".*WebCore::DOMWindow::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-node-factory", + "stacktrace": ".*WebCore::HTML[a-zA-Z0-9_]*Factory::create[a-zA-Z0-9_]*Element.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-element-wrapper", + "stacktrace": ".*WebCore::createHTML[a-zA-Z0-9_]*ElementWrapper.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-stylepropertyset", + "stacktrace": ".*WebCore::StylePropertySet::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-style-createsheet", + "stacktrace": ".*WebCore::StyleElement::createSheet.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-cachedresource", + "stacktrace": ".*WebCore::CachedResource::data .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-cachedresource", + "stacktrace": ".*WebCore::CachedResource::load .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-script-execute", + "stacktrace": ".*WebCore::ScriptElement::execute.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-events-related", + "stacktrace": ".*WebCore::createAttributeEventListener.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-events-related", + "stacktrace": ".*WebCore::V8LazyEventListener::create.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-events-related", + "stacktrace": ".*WebCore::V8EventListener::create.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-events-related", + "stacktrace": ".*WebCore::Event::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-events-related", + "stacktrace": ".*WebCore::EventListener::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-document-write", + "stacktrace": ".*WebCore::Document::write.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-node-create-renderer", + "stacktrace": ".*WebCore::Node::createRendererIfNeeded.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-render-catch-all", + "stacktrace": ".*WebCore::RenderLayer.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-render-catch-all", + "stacktrace": ".*WebCore::RenderBlock.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-render-catch-all", + "stacktrace": ".*WebCore::RenderWidget.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-render-catch-all", + "stacktrace": ".*WebCore::RenderView.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-render-catch-all", + "stacktrace": ".*WebCore::RenderViewImpl.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-render-catch-all", + "stacktrace": ".*WebCore::RenderStyle.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-render-catch-all", + "stacktrace": ".*WebCore::RenderText.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-render-catch-all", + "stacktrace": ".* RendererMain .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-setInnerHTML-except-node", + "stacktrace": ".*WebCore::HTMLElement::setInnerHTML.*", + "allocator": "malloc" + }, + { + "name": "tc-wtf-StringImpl-user-catch-all", + "stacktrace": ".*WTF::StringImpl::create .*", + "allocator": "malloc" + }, + { + "name": "tc-wtf-StringImpl-user-catch-all", + "stacktrace": ".*WTF::StringImpl::createUninitialized.*", + "allocator": "malloc" + }, + { + "name": "tc-wtf-HashTable-user-catch-all", + "stacktrace": ".*WTF::HashTable::allocateTable.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-everything-create", + "stacktrace": ".*WebCore::[a-zA-Z0-9_]*::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webkit-from-v8-catch-all", + "stacktrace": ".*(WTF::|WebCore::|WebKit::).*v8::.*", + "allocator": "malloc" + }, + { + "name": "tc-webkit-catch-all", + "stacktrace": ".*(WTF::|WebCore::|WebKit::).*", + "allocator": "malloc" + }, + { + "name": "tc-v8-catch-all", + "stacktrace": ".*v8::.*", + "allocator": "malloc" + }, + { + "name": "tc-toplevel-string", + "stacktrace": "std::basic_string::_Rep::_S_create", + "allocator": "malloc" + }, + { + "name": "tc-std-treemap", + "stacktrace": ".*::allocate std::(_Rb_tree|__1::__tree).*", + "allocator": "malloc" + }, + { + "name": "tc-std-hashmap", + "stacktrace": ".*(std::vector::reserve __gnu_cxx::hashtable|::allocate std::_Hashtable|::allocate std::__1::__hash_table).*", + "allocator": "malloc" + }, + { + "name": "tc-std-vector", + "stacktrace": ".*std::(_Vector_base::_M_allocate|__1::vector::allocate).*", + "allocator": "malloc" + }, + { + "name": "tc-std-other", + "stacktrace": ".*(__gnu_cxx::new_allocator::allocate|std::__1::allocator::allocate).*", + "allocator": "malloc" + }, + { + "name": "tc-catch-all", + "stacktrace": ".*", + "allocator": "malloc" + }, + { + "name": "UNTIL_HERE_FOR_TOTAL", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "total-exclude-profiler", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "total", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "absent", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "anonymous", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "file-exec", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "file-nonexec", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "stack", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "other", + "stacktrace": "optional", + "allocator": "optional" + } + ], + "version": "POLICY_DEEP_3" +} diff --git a/tools/deep_memory_profiler/policy.l0.json b/tools/deep_memory_profiler/policy.l0.json new file mode 100644 index 0000000000..14eeb9f3ec --- /dev/null +++ b/tools/deep_memory_profiler/policy.l0.json @@ -0,0 +1,162 @@ +{ + "components": [ + "second", + "mmap-profiler", + "mmap-type-profiler", + "mmap-tcmalloc", + "FROM_HERE_FOR_TOTAL", + "mustbezero", + "unhooked-absent", + "unhooked-anonymous", + "unhooked-file-exec", + "unhooked-file-nonexec", + "unhooked-stack", + "unhooked-other", + "no-bucket", + "mmap-v8", + "mmap-catch-all", + "tc-used-all", + "tc-unused", + "UNTIL_HERE_FOR_TOTAL", + "total-exclude-profiler", + "total", + "absent", + "anonymous", + "file-exec", + "file-nonexec", + "stack", + "other", + "mmap-total-log", + "mmap-no-log", + "mmap-total-record", + "other-total-log", + "tc-total-log", + "tc-no-log", + "tc-total-record", + "tc-total" + ], + "rules": [ + { + "name": "second", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "mmap-profiler", + "stacktrace": ".*(ProfilerMalloc|MemoryRegionMap::).*", + "allocator": "mmap" + }, + { + "name": "mmap-type-profiler", + "stacktrace": ".*(TypeProfilerMalloc).*", + "allocator": "mmap" + }, + { + "name": "mmap-tcmalloc", + "stacktrace": ".*(DoAllocWithArena|SbrkSysAllocator::Alloc|MmapSysAllocator::Alloc|LowLevelAlloc::Alloc|LowLevelAlloc::AllocWithArena).*", + "allocator": "mmap" + }, + { + "name": "FROM_HERE_FOR_TOTAL", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "mustbezero", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "unhooked-absent", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "unhooked-anonymous", + "mappedpathname": "^$", + "allocator": "unhooked" + }, + { + "name": "unhooked-file-exec", + "mappedpathname": "^/.*", + "mappedpermission": "..x.", + "allocator": "unhooked" + }, + { + "name": "unhooked-file-nonexec", + "mappedpathname": "^/.*", + "allocator": "unhooked" + }, + { + "name": "unhooked-stack", + "mappedpathname": ".stack.", + "allocator": "unhooked" + }, + { + "name": "unhooked-other", + "mappedpathname": ".*", + "allocator": "unhooked" + }, + { + "name": "mmap-v8", + "stacktrace": ".*v8::.*", + "allocator": "mmap" + }, + { + "name": "mmap-catch-all", + "stacktrace": ".*", + "allocator": "mmap" + }, + { + "name": "tc-used-all", + "stacktrace": ".*", + "allocator": "malloc" + }, + { + "name": "UNTIL_HERE_FOR_TOTAL", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "total-exclude-profiler", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "total", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "absent", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "anonymous", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "file-exec", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "file-nonexec", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "stack", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "other", + "stacktrace": "optional", + "allocator": "optional" + } + ], + "version": "POLICY_DEEP_3" +} diff --git a/tools/deep_memory_profiler/policy.l1.json b/tools/deep_memory_profiler/policy.l1.json new file mode 100644 index 0000000000..599c540693 --- /dev/null +++ b/tools/deep_memory_profiler/policy.l1.json @@ -0,0 +1,204 @@ +{ + "components": [ + "second", + "mmap-profiler", + "mmap-type-profiler", + "mmap-tcmalloc", + "FROM_HERE_FOR_TOTAL", + "mustbezero", + "unhooked-absent", + "unhooked-anonymous", + "unhooked-file-exec", + "unhooked-file-nonexec", + "unhooked-stack", + "unhooked-other", + "no-bucket", + "mmap-v8-heap-newspace", + "mmap-v8-heap-coderange", + "mmap-v8-heap-pagedspace", + "mmap-v8-other", + "mmap-catch-all", + "tc-v8", + "tc-skia", + "tc-webkit-catch-all", + "tc-unknown-string", + "tc-catch-all", + "tc-unused", + "UNTIL_HERE_FOR_TOTAL", + "total-exclude-profiler", + "total", + "absent", + "anonymous", + "file-exec", + "file-nonexec", + "stack", + "other", + "mmap-total-log", + "mmap-no-log", + "mmap-total-record", + "other-total-log", + "tc-total-log", + "tc-no-log", + "tc-total-record", + "tc-total" + ], + "rules": [ + { + "name": "second", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "mmap-profiler", + "stacktrace": ".*(ProfilerMalloc|MemoryRegionMap::).*", + "allocator": "mmap" + }, + { + "name": "mmap-type-profiler", + "stacktrace": ".*(TypeProfilerMalloc).*", + "allocator": "mmap" + }, + { + "name": "mmap-tcmalloc", + "stacktrace": ".*(DoAllocWithArena|SbrkSysAllocator::Alloc|MmapSysAllocator::Alloc|LowLevelAlloc::Alloc|LowLevelAlloc::AllocWithArena).*", + "allocator": "mmap" + }, + { + "name": "FROM_HERE_FOR_TOTAL", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "mustbezero", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "unhooked-absent", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "unhooked-anonymous", + "mappedpathname": "^$", + "allocator": "unhooked" + }, + { + "name": "unhooked-file-exec", + "mappedpathname": "^/.*", + "mappedpermission": "..x.", + "allocator": "unhooked" + }, + { + "name": "unhooked-file-nonexec", + "mappedpathname": "^/.*", + "allocator": "unhooked" + }, + { + "name": "unhooked-stack", + "mappedpathname": ".stack.", + "allocator": "unhooked" + }, + { + "name": "unhooked-other", + "mappedpathname": ".*", + "allocator": "unhooked" + }, + { + "name": "mmap-v8-heap-newspace", + "stacktrace": ".*v8::internal::NewSpace::SetUp.*", + "allocator": "mmap" + }, + { + "name": "mmap-v8-heap-coderange", + "stacktrace": ".*v8::internal::CodeRange::SetUp.*", + "allocator": "mmap" + }, + { + "name": "mmap-v8-heap-pagedspace", + "stacktrace": ".*v8::internal::PagedSpace::AllocateRaw.*", + "allocator": "mmap" + }, + { + "name": "mmap-v8-other", + "stacktrace": ".*v8::.*", + "allocator": "mmap" + }, + { + "name": "mmap-catch-all", + "stacktrace": ".*", + "allocator": "mmap" + }, + { + "name": "tc-v8", + "stacktrace": ".*v8::.*", + "allocator": "malloc" + }, + { + "name": "tc-skia", + "stacktrace": ".*Sk[A-Za-z_]+::.*", + "allocator": "malloc" + }, + { + "name": "tc-webkit-catch-all", + "stacktrace": ".*(WTF::|WebCore::|WebKit::).*", + "allocator": "malloc" + }, + { + "name": "tc-unknown-string", + "stacktrace": ".*std::basic_string::_Rep::_S_create.*", + "allocator": "malloc" + }, + { + "name": "tc-catch-all", + "stacktrace": ".*", + "allocator": "malloc" + }, + { + "name": "UNTIL_HERE_FOR_TOTAL", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "total-exclude-profiler", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "total", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "absent", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "anonymous", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "file-exec", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "file-nonexec", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "stack", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "other", + "stacktrace": "optional", + "allocator": "optional" + } + ], + "version": "POLICY_DEEP_3" +} diff --git a/tools/deep_memory_profiler/policy.l2.json b/tools/deep_memory_profiler/policy.l2.json new file mode 100644 index 0000000000..f0a007b133 --- /dev/null +++ b/tools/deep_memory_profiler/policy.l2.json @@ -0,0 +1,490 @@ +{ + "components": [ + "second", + "mmap-profiler", + "mmap-type-profiler", + "mmap-tcmalloc", + "FROM_HERE_FOR_TOTAL", + "mustbezero", + "unhooked-absent", + "unhooked-anonymous", + "unhooked-file-exec", + "unhooked-file-nonexec-others", + "unhooked-file-nonexec-group", + "unhooked-file-nonexec", + "unhooked-stack", + "unhooked-other", + "no-bucket", + "mmap-v8-heap-newspace", + "mmap-v8-heap-coderange", + "mmap-v8-heap-pagedspace", + "mmap-v8-other", + "mmap-catch-all", + "tc-webcore-fontcache", + "tc-skia", + "tc-renderobject", + "tc-renderstyle", + "tc-webcore-sharedbuf", + "tc-webcore-XHRcreate", + "tc-webcore-XHRreceived", + "tc-webcore-docwriter-add", + "tc-webcore-node-and-doc", + "tc-webcore-node-factory", + "tc-webcore-element-wrapper", + "tc-webcore-stylepropertyset", + "tc-webcore-style-createsheet", + "tc-webcore-cachedresource", + "tc-webcore-script-execute", + "tc-webcore-events-related", + "tc-webcore-document-write", + "tc-webcore-node-create-renderer", + "tc-webcore-render-catch-all", + "tc-webcore-setInnerHTML-except-node", + "tc-wtf-StringImpl-user-catch-all", + "tc-wtf-HashTable-user-catch-all", + "tc-webcore-everything-create", + "tc-webkit-from-v8-catch-all", + "tc-webkit-catch-all", + "tc-v8-catch-all", + "tc-toplevel-string", + "tc-std-treemap", + "tc-std-hashmap", + "tc-std-vector", + "tc-std-other", + "tc-catch-all", + "tc-unused", + "UNTIL_HERE_FOR_TOTAL", + "total-exclude-profiler", + "total", + "absent", + "anonymous", + "file-exec", + "file-nonexec", + "stack", + "other", + "mmap-total-log", + "mmap-no-log", + "mmap-total-record", + "other-total-log", + "tc-total-log", + "tc-no-log", + "tc-total-record", + "tc-total" + ], + "rules": [ + { + "name": "second", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "mmap-profiler", + "stacktrace": ".*(ProfilerMalloc|MemoryRegionMap::).*", + "allocator": "mmap" + }, + { + "name": "mmap-type-profiler", + "stacktrace": ".*(TypeProfilerMalloc).*", + "allocator": "mmap" + }, + { + "name": "mmap-tcmalloc", + "stacktrace": ".*(DoAllocWithArena|SbrkSysAllocator::Alloc|MmapSysAllocator::Alloc|LowLevelAlloc::Alloc|LowLevelAlloc::AllocWithArena).*", + "allocator": "mmap" + }, + { + "name": "FROM_HERE_FOR_TOTAL", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "mustbezero", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "unhooked-absent", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "unhooked-anonymous", + "mappedpathname": "^$", + "allocator": "unhooked" + }, + { + "name": "unhooked-file-exec", + "mappedpathname": "^/.*", + "mappedpermission": "..x.", + "allocator": "unhooked" + }, + { + "name": "unhooked-file-nonexec-others", + "mappedpathname": "^/.*", + "allocator": "unhooked", + "sharedwith": ["others"] + }, + { + "name": "unhooked-file-nonexec-group", + "mappedpathname": "^/.*", + "allocator": "unhooked", + "sharedwith": ["group"] + }, + { + "name": "unhooked-file-nonexec", + "mappedpathname": "^/.*", + "allocator": "unhooked" + }, + { + "name": "unhooked-stack", + "mappedpathname": ".stack.", + "allocator": "unhooked" + }, + { + "name": "unhooked-other", + "mappedpathname": ".*", + "allocator": "unhooked" + }, + { + "name": "mmap-v8-heap-newspace", + "stacktrace": ".*v8::internal::NewSpace::SetUp.*", + "allocator": "mmap" + }, + { + "name": "mmap-v8-heap-coderange", + "stacktrace": ".*v8::internal::CodeRange::SetUp.*", + "allocator": "mmap" + }, + { + "name": "mmap-v8-heap-pagedspace", + "stacktrace": ".*v8::internal::PagedSpace::AllocateRaw.*", + "allocator": "mmap" + }, + { + "name": "mmap-v8-other", + "stacktrace": ".*v8::.*", + "allocator": "mmap" + }, + { + "name": "mmap-catch-all", + "stacktrace": ".*", + "allocator": "mmap" + }, + { + "name": "tc-webcore-fontcache", + "stacktrace": ".*WebCore::FontCache::getCachedFontData.*", + "allocator": "malloc" + }, + { + "name": "tc-skia", + "stacktrace": ".* Sk[A-Za-z_]+::.*", + "allocator": "malloc" + }, + { + "name": "tc-renderobject", + "stacktrace": ".*WebCore::RenderArena::allocate.*", + "allocator": "malloc" + }, + { + "name": "tc-renderstyle", + "stacktrace": ".*WebCore::RenderStyle::create.*", + "allocator": "malloc" + }, + { + "name": "tc-renderstyle", + "stacktrace": ".*WebCore::RenderStyle::clone.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-sharedbuf", + "stacktrace": ".*WebCore::SharedBuffer::create.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-sharedbuf", + "stacktrace": ".*WebCore::SharedBuffer::buffer.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-sharedbuf", + "stacktrace": ".*WebCore::SharedBuffer::append.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-XHRcreate", + "stacktrace": ".*WebCore::XMLHttpRequest::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-XHRreceived", + "stacktrace": ".*WebCore::XMLHttpRequest::didReceiveData.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-docwriter-add", + "stacktrace": ".*WebCore::DocumentWriter::addData.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-node-and-doc", + "stacktrace": ".*WebCore::HTML[a-zA-Z0-9_]*Element::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-node-and-doc", + "stacktrace": ".*WebCore::Text::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-node-and-doc", + "stacktrace": ".*WebCore::Comment::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-node-and-doc", + "stacktrace": ".*WebCore::HTMLDocument::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-node-and-doc", + "stacktrace": ".*WebCore::CSSStyleRule::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-node-and-doc", + "stacktrace": ".*WebCore::Attribute::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-node-and-doc", + "stacktrace": ".*WebCore::DOMWindow::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-node-factory", + "stacktrace": ".*WebCore::HTML[a-zA-Z0-9_]*Factory::create[a-zA-Z0-9_]*Element.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-element-wrapper", + "stacktrace": ".*WebCore::createHTML[a-zA-Z0-9_]*ElementWrapper.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-stylepropertyset", + "stacktrace": ".*WebCore::StylePropertySet::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-style-createsheet", + "stacktrace": ".*WebCore::StyleElement::createSheet.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-cachedresource", + "stacktrace": ".*WebCore::CachedResource::data .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-cachedresource", + "stacktrace": ".*WebCore::CachedResource::load .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-script-execute", + "stacktrace": ".*WebCore::ScriptElement::execute.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-events-related", + "stacktrace": ".*WebCore::createAttributeEventListener.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-events-related", + "stacktrace": ".*WebCore::V8LazyEventListener::create.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-events-related", + "stacktrace": ".*WebCore::V8EventListener::create.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-events-related", + "stacktrace": ".*WebCore::Event::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-events-related", + "stacktrace": ".*WebCore::EventListener::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-document-write", + "stacktrace": ".*WebCore::Document::write.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-node-create-renderer", + "stacktrace": ".*WebCore::Node::createRendererIfNeeded.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-render-catch-all", + "stacktrace": ".*WebCore::RenderLayer.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-render-catch-all", + "stacktrace": ".*WebCore::RenderBlock.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-render-catch-all", + "stacktrace": ".*WebCore::RenderWidget.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-render-catch-all", + "stacktrace": ".*WebCore::RenderView.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-render-catch-all", + "stacktrace": ".*WebCore::RenderViewImpl.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-render-catch-all", + "stacktrace": ".*WebCore::RenderStyle.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-render-catch-all", + "stacktrace": ".*WebCore::RenderText.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-render-catch-all", + "stacktrace": ".* RendererMain .*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-setInnerHTML-except-node", + "stacktrace": ".*WebCore::HTMLElement::setInnerHTML.*", + "allocator": "malloc" + }, + { + "name": "tc-wtf-StringImpl-user-catch-all", + "stacktrace": ".*WTF::StringImpl::create .*", + "allocator": "malloc" + }, + { + "name": "tc-wtf-StringImpl-user-catch-all", + "stacktrace": ".*WTF::StringImpl::createUninitialized.*", + "allocator": "malloc" + }, + { + "name": "tc-wtf-HashTable-user-catch-all", + "stacktrace": ".*WTF::HashTable::allocateTable.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore-everything-create", + "stacktrace": ".*WebCore::[a-zA-Z0-9_]*::create .*", + "allocator": "malloc" + }, + { + "name": "tc-webkit-from-v8-catch-all", + "stacktrace": ".*(WTF::|WebCore::|WebKit::).*v8::.*", + "allocator": "malloc" + }, + { + "name": "tc-webkit-catch-all", + "stacktrace": ".*(WTF::|WebCore::|WebKit::).*", + "allocator": "malloc" + }, + { + "name": "tc-v8-catch-all", + "stacktrace": ".*v8::.*", + "allocator": "malloc" + }, + { + "name": "tc-toplevel-string", + "stacktrace": "std::basic_string::_Rep::_S_create", + "allocator": "malloc" + }, + { + "name": "tc-std-treemap", + "stacktrace": ".*::allocate std::(_Rb_tree|__1::__tree).*", + "allocator": "malloc" + }, + { + "name": "tc-std-hashmap", + "stacktrace": ".*(std::vector::reserve __gnu_cxx::hashtable|::allocate std::_Hashtable|::allocate std::__1::__hash_table).*", + "allocator": "malloc" + }, + { + "name": "tc-std-vector", + "stacktrace": ".*std::(_Vector_base::_M_allocate|__1::vector::allocate).*", + "allocator": "malloc" + }, + { + "name": "tc-std-other", + "stacktrace": ".*(__gnu_cxx::new_allocator::allocate|std::__1::allocator::allocate).*", + "allocator": "malloc" + }, + { + "name": "tc-catch-all", + "stacktrace": ".*", + "allocator": "malloc" + }, + { + "name": "UNTIL_HERE_FOR_TOTAL", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "total-exclude-profiler", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "total", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "absent", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "anonymous", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "file-exec", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "file-nonexec", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "stack", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "other", + "stacktrace": "optional", + "allocator": "optional" + } + ], + "version": "POLICY_DEEP_3" +} diff --git a/tools/deep_memory_profiler/policy.sourcefile.json b/tools/deep_memory_profiler/policy.sourcefile.json new file mode 100644 index 0000000000..7a037e5973 --- /dev/null +++ b/tools/deep_memory_profiler/policy.sourcefile.json @@ -0,0 +1,186 @@ +{ + "components": [ + "second", + "mmap-profiler", + "mmap-type-profiler", + "mmap-tcmalloc", + "FROM_HERE_FOR_TOTAL", + "mustbezero", + "unhooked-absent", + "unhooked-anonymous", + "unhooked-file-exec", + "unhooked-file-nonexec", + "unhooked-stack", + "unhooked-other", + "no-bucket", + "mmap-v8", + "mmap-catch-all", + "tc-v8", + "tc-skia", + "tc-webcore", + "tc-webkit", + "tc-catch-all", + "tc-unused", + "UNTIL_HERE_FOR_TOTAL", + "total-exclude-profiler", + "total", + "absent", + "anonymous", + "file-exec", + "file-nonexec", + "stack", + "other", + "mmap-total-log", + "mmap-no-log", + "mmap-total-record", + "other-total-log", + "tc-total-log", + "tc-no-log", + "tc-total-record", + "tc-total" + ], + "rules": [ + { + "name": "second", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "mmap-profiler", + "stacktrace": ".*(ProfilerMalloc|MemoryRegionMap::).*", + "allocator": "mmap" + }, + { + "name": "mmap-type-profiler", + "stacktrace": ".*(TypeProfilerMalloc).*", + "allocator": "mmap" + }, + { + "name": "mmap-tcmalloc", + "stacktrace": ".*(DoAllocWithArena|SbrkSysAllocator::Alloc|MmapSysAllocator::Alloc|LowLevelAlloc::Alloc|LowLevelAlloc::AllocWithArena).*", + "allocator": "mmap" + }, + { + "name": "FROM_HERE_FOR_TOTAL", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "mustbezero", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "unhooked-absent", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "unhooked-anonymous", + "mappedpathname": "^$", + "allocator": "unhooked" + }, + { + "name": "unhooked-file-exec", + "mappedpathname": "^/.*", + "mappedpermission": "..x.", + "allocator": "unhooked" + }, + { + "name": "unhooked-file-nonexec", + "mappedpathname": "^/.*", + "allocator": "unhooked" + }, + { + "name": "unhooked-stack", + "mappedpathname": ".stack.", + "allocator": "unhooked" + }, + { + "name": "unhooked-other", + "mappedpathname": ".*", + "allocator": "unhooked" + }, + { + "name": "mmap-v8", + "stacksourcefile": ".*\\.\\./\\.\\./v8/src/.*", + "allocator": "mmap" + }, + { + "name": "mmap-catch-all", + "stacksourcefile": ".*", + "allocator": "mmap" + }, + { + "name": "tc-v8", + "stacksourcefile": ".*\\.\\./\\.\\./v8/src/.*", + "allocator": "malloc" + }, + { + "name": "tc-skia", + "stacksourcefile": ".*\\.\\./\\.\\./third_party/skia/src/.*", + "allocator": "malloc" + }, + { + "name": "tc-webcore", + "stacksourcefile": ".*\\.\\./\\.\\./third_party/WebKit/Source/WebCore/.*", + "allocator": "malloc" + }, + { + "name": "tc-webkit", + "stacksourcefile": ".*\\.\\./\\.\\./third_party/WebKit/Source/.*", + "allocator": "malloc" + }, + { + "name": "tc-catch-all", + "stacksourcefile": ".*", + "allocator": "malloc" + }, + { + "name": "UNTIL_HERE_FOR_TOTAL", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "total-exclude-profiler", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "total", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "absent", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "anonymous", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "file-exec", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "file-nonexec", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "stack", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "other", + "stacktrace": "optional", + "allocator": "optional" + } + ], + "version": "POLICY_DEEP_3" +} diff --git a/tools/deep_memory_profiler/policy.t0.json b/tools/deep_memory_profiler/policy.t0.json new file mode 100644 index 0000000000..fd716babe0 --- /dev/null +++ b/tools/deep_memory_profiler/policy.t0.json @@ -0,0 +1,205 @@ +{ + "components": [ + "second", + "mmap-profiler", + "mmap-type-profiler", + "mmap-tcmalloc", + "FROM_HERE_FOR_TOTAL", + "mustbezero", + "unhooked-absent", + "unhooked-anonymous", + "unhooked-file-exec", + "unhooked-file-nonexec", + "unhooked-stack", + "unhooked-other", + "no-bucket", + "mmap-v8", + "mmap-catch-all", + "tc-std-string", + "tc-WTF-String", + "tc-no-typeinfo-StringImpl", + "tc-Skia", + "tc-WebCore-Style", + "tc-no-typeinfo-other", + "tc-other", + "tc-unused", + "UNTIL_HERE_FOR_TOTAL", + "total-exclude-profiler", + "total", + "absent", + "anonymous", + "file-exec", + "file-nonexec", + "stack", + "other", + "mmap-total-log", + "mmap-no-log", + "mmap-total-record", + "other-total-log", + "tc-total-log", + "tc-no-log", + "tc-total-record", + "tc-total" + ], + "rules": [ + { + "name": "second", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "mmap-profiler", + "stacktrace": ".*(ProfilerMalloc|MemoryRegionMap::).*", + "allocator": "mmap" + }, + { + "name": "mmap-type-profiler", + "stacktrace": ".*(TypeProfilerMalloc).*", + "allocator": "mmap" + }, + { + "name": "mmap-tcmalloc", + "stacktrace": ".*(DoAllocWithArena|SbrkSysAllocator::Alloc|MmapSysAllocator::Alloc|LowLevelAlloc::Alloc|LowLevelAlloc::AllocWithArena).*", + "allocator": "mmap" + }, + { + "name": "FROM_HERE_FOR_TOTAL", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "mustbezero", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "unhooked-absent", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "unhooked-anonymous", + "mappedpathname": "^$", + "allocator": "unhooked" + }, + { + "name": "unhooked-file-exec", + "mappedpathname": "^/.*", + "mappedpermission": "..x.", + "allocator": "unhooked" + }, + { + "name": "unhooked-file-nonexec", + "mappedpathname": "^/.*", + "allocator": "unhooked" + }, + { + "name": "unhooked-stack", + "mappedpathname": ".stack.", + "allocator": "unhooked" + }, + { + "name": "unhooked-other", + "mappedpathname": ".*", + "allocator": "unhooked" + }, + { + "name": "mmap-v8", + "stacktrace": ".*v8::.*", + "allocator": "mmap" + }, + { + "name": "mmap-catch-all", + "stacktrace": ".*", + "allocator": "mmap" + }, + { + "name": "tc-std-string", + "stacktrace": ".*", + "typeinfo": "std::basic_string.*", + "allocator": "malloc" + }, + { + "name": "tc-WTF-String", + "stacktrace": ".*", + "typeinfo": "WTF::String.*", + "allocator": "malloc" + }, + { + "name": "tc-no-typeinfo-StringImpl", + "stacktrace": ".*WTF::StringImpl::getData16SlowCase.*", + "typeinfo": "no typeinfo", + "allocator": "malloc" + }, + { + "name": "tc-Skia", + "stacktrace": ".*", + "typeinfo": "(skia::|SkGlyph).*", + "allocator": "malloc" + }, + { + "name": "tc-WebCore-Style", + "stacktrace": ".*", + "typeinfo": "WebCore::Style.*", + "allocator": "malloc" + }, + { + "name": "tc-no-typeinfo-other", + "stacktrace": ".*", + "typeinfo": "no typeinfo", + "allocator": "malloc" + }, + { + "name": "tc-other", + "stacktrace": ".*", + "typeinfo": ".*", + "allocator": "malloc" + }, + { + "name": "UNTIL_HERE_FOR_TOTAL", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "total-exclude-profiler", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "total", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "absent", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "anonymous", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "file-exec", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "file-nonexec", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "stack", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "other", + "stacktrace": "optional", + "allocator": "optional" + } + ], + "version": "POLICY_DEEP_4" +} diff --git a/tools/deep_memory_profiler/sorter.malloc-component.json b/tools/deep_memory_profiler/sorter.malloc-component.json new file mode 100644 index 0000000000..c5c809507a --- /dev/null +++ b/tools/deep_memory_profiler/sorter.malloc-component.json @@ -0,0 +1,261 @@ +{ + "type": "sorter", + "version": 1, + "world": "malloc", + "name": "component", + "order": { + "preset1": [ + "tc-webcore-fontcache", + "tc-skia", + "tc-renderobject", + "tc-renderstyle", + "tc-webcore-sharedbuf", + "tc-webcore-XHRcreate", + "tc-webcore-XHRreceived", + "tc-webcore-docwriter-add", + "tc-webcore-node-and-doc", + "tc-webcore-node-factory", + "tc-webcore-element-wrapper", + "tc-webcore-stylepropertyset", + "tc-webcore-style-createsheet", + "tc-webcore-cachedresource", + "tc-webcore-script-execute", + "tc-webcore-events-related", + "tc-webcore-document-write", + "tc-webcore-node-create-renderer", + "tc-webcore-render-catch-all", + "tc-webcore-setInnerHTML-except-node", + "tc-wtf-StringImpl-user-catch-all", + "tc-wtf-HashTable-user-catch-all", + "tc-webcore-everything-create", + "tc-webkit-from-v8-catch-all", + "tc-webkit-catch-all", + "tc-v8-catch-all", + "tc-toplevel-string", + "tc-std-treemap", + "tc-std-hashmap", + "tc-std-vector", + "tc-std-other", + "tc-catch-all", + "tc-unused" + ] + }, + "rules": [ + { + "name": "tc-webcore-fontcache", + "backtrace_function": ".*WebCore::FontCache::getCachedFontData.*" + }, + { + "name": "tc-skia", + "backtrace_function": ".* Sk[A-Za-z_]+::.*" + }, + { + "name": "tc-renderobject", + "backtrace_function": ".*WebCore::RenderArena::allocate.*" + }, + { + "name": "tc-renderstyle", + "backtrace_function": ".*WebCore::RenderStyle::create.*" + }, + { + "name": "tc-renderstyle", + "backtrace_function": ".*WebCore::RenderStyle::clone.*" + }, + { + "name": "tc-webcore-sharedbuf", + "backtrace_function": ".*WebCore::SharedBuffer::create.*" + }, + { + "name": "tc-webcore-sharedbuf", + "backtrace_function": ".*WebCore::SharedBuffer::buffer.*" + }, + { + "name": "tc-webcore-sharedbuf", + "backtrace_function": ".*WebCore::SharedBuffer::append.*" + }, + { + "name": "tc-webcore-XHRcreate", + "backtrace_function": ".*WebCore::XMLHttpRequest::create .*" + }, + { + "name": "tc-webcore-XHRreceived", + "backtrace_function": ".*WebCore::XMLHttpRequest::didReceiveData.*" + }, + { + "name": "tc-webcore-docwriter-add", + "backtrace_function": ".*WebCore::DocumentWriter::addData.*" + }, + { + "name": "tc-webcore-node-and-doc", + "backtrace_function": ".*WebCore::HTML[a-zA-Z0-9_]*Element::create .*" + }, + { + "name": "tc-webcore-node-and-doc", + "backtrace_function": ".*WebCore::Text::create .*" + }, + { + "name": "tc-webcore-node-and-doc", + "backtrace_function": ".*WebCore::Comment::create .*" + }, + { + "name": "tc-webcore-node-and-doc", + "backtrace_function": ".*WebCore::HTMLDocument::create .*" + }, + { + "name": "tc-webcore-node-and-doc", + "backtrace_function": ".*WebCore::CSSStyleRule::create .*" + }, + { + "name": "tc-webcore-node-and-doc", + "backtrace_function": ".*WebCore::Attribute::create .*" + }, + { + "name": "tc-webcore-node-and-doc", + "backtrace_function": ".*WebCore::DOMWindow::create .*" + }, + { + "name": "tc-webcore-node-factory", + "backtrace_function": ".*WebCore::HTML[a-zA-Z0-9_]*Factory::create[a-zA-Z0-9_]*Element.*" + }, + { + "name": "tc-webcore-element-wrapper", + "backtrace_function": ".*WebCore::createHTML[a-zA-Z0-9_]*ElementWrapper.*" + }, + { + "name": "tc-webcore-stylepropertyset", + "backtrace_function": ".*WebCore::StylePropertySet::create .*" + }, + { + "name": "tc-webcore-style-createsheet", + "backtrace_function": ".*WebCore::StyleElement::createSheet.*" + }, + { + "name": "tc-webcore-cachedresource", + "backtrace_function": ".*WebCore::CachedResource::data .*" + }, + { + "name": "tc-webcore-cachedresource", + "backtrace_function": ".*WebCore::CachedResource::load .*" + }, + { + "name": "tc-webcore-script-execute", + "backtrace_function": ".*WebCore::ScriptElement::execute.*" + }, + { + "name": "tc-webcore-events-related", + "backtrace_function": ".*WebCore::createAttributeEventListener.*" + }, + { + "name": "tc-webcore-events-related", + "backtrace_function": ".*WebCore::V8LazyEventListener::create.*" + }, + { + "name": "tc-webcore-events-related", + "backtrace_function": ".*WebCore::V8EventListener::create.*" + }, + { + "name": "tc-webcore-events-related", + "backtrace_function": ".*WebCore::Event::create .*" + }, + { + "name": "tc-webcore-events-related", + "backtrace_function": ".*WebCore::EventListener::create .*" + }, + { + "name": "tc-webcore-document-write", + "backtrace_function": ".*WebCore::Document::write.*" + }, + { + "name": "tc-webcore-node-create-renderer", + "backtrace_function": ".*WebCore::Node::createRendererIfNeeded.*" + }, + { + "name": "tc-webcore-render-catch-all", + "backtrace_function": ".*WebCore::RenderLayer.*" + }, + { + "name": "tc-webcore-render-catch-all", + "backtrace_function": ".*WebCore::RenderBlock.*" + }, + { + "name": "tc-webcore-render-catch-all", + "backtrace_function": ".*WebCore::RenderWidget.*" + }, + { + "name": "tc-webcore-render-catch-all", + "backtrace_function": ".*WebCore::RenderView.*" + }, + { + "name": "tc-webcore-render-catch-all", + "backtrace_function": ".*WebCore::RenderViewImpl.*" + }, + { + "name": "tc-webcore-render-catch-all", + "backtrace_function": ".*WebCore::RenderStyle.*" + }, + { + "name": "tc-webcore-render-catch-all", + "backtrace_function": ".*WebCore::RenderText.*" + }, + { + "name": "tc-webcore-render-catch-all", + "backtrace_function": ".* RendererMain .*" + }, + { + "name": "tc-webcore-setInnerHTML-except-node", + "backtrace_function": ".*WebCore::HTMLElement::setInnerHTML.*" + }, + { + "name": "tc-wtf-StringImpl-user-catch-all", + "backtrace_function": ".*WTF::StringImpl::create .*" + }, + { + "name": "tc-wtf-StringImpl-user-catch-all", + "backtrace_function": ".*WTF::StringImpl::createUninitialized.*" + }, + { + "name": "tc-wtf-HashTable-user-catch-all", + "backtrace_function": ".*WTF::HashTable::allocateTable.*" + }, + { + "name": "tc-webcore-everything-create", + "backtrace_function": ".*WebCore::[a-zA-Z0-9_]*::create .*" + }, + { + "name": "tc-webkit-from-v8-catch-all", + "backtrace_function": ".*(WTF::|WebCore::|WebKit::).*v8::.*" + }, + { + "name": "tc-webkit-catch-all", + "backtrace_function": ".*(WTF::|WebCore::|WebKit::).*" + }, + { + "name": "tc-v8-catch-all", + "backtrace_function": ".*v8::.*" + }, + { + "name": "tc-toplevel-string", + "backtrace_function": "std::basic_string::_Rep::_S_create" + }, + { + "name": "tc-std-treemap", + "backtrace_function": ".*::allocate std::(_Rb_tree|__1::__tree).*" + }, + { + "name": "tc-std-hashmap", + "backtrace_function": ".*(std::vector::reserve __gnu_cxx::hashtable|::allocate std::_Hashtable|::allocate std::__1::__hash_table).*" + }, + { + "name": "tc-std-vector", + "backtrace_function": ".*std::(_Vector_base::_M_allocate|__1::vector::allocate).*" + }, + { + "name": "tc-std-other", + "backtrace_function": ".*(__gnu_cxx::new_allocator::allocate|std::__1::allocator::allocate).*" + }, + { + "name": "tc-catch-all", + "backtrace_function": ".*" + } + ] +} diff --git a/tools/deep_memory_profiler/sorter.malloc-type.json b/tools/deep_memory_profiler/sorter.malloc-type.json new file mode 100644 index 0000000000..cc8ee65c6a --- /dev/null +++ b/tools/deep_memory_profiler/sorter.malloc-type.json @@ -0,0 +1,38 @@ +{ + "type": "sorter", + "version": 1, + "world": "malloc", + "name": "type", + "order": {}, + "rules": [ + { + "name": "tc-std-string", + "typeinfo": "std::basic_string.*" + }, + { + "name": "tc-WTF-String", + "typeinfo": "WTF::String.*" + }, + { + "name": "tc-no-typeinfo-StringImpl", + "backtrace_function": ".*WTF::StringImpl::getData16SlowCase.*", + "typeinfo": "no typeinfo" + }, + { + "name": "tc-Skia", + "typeinfo": "(skia::|SkGlyph).*" + }, + { + "name": "tc-WebCore-Style", + "typeinfo": "WebCore::Style.*" + }, + { + "name": "tc-no-typeinfo-other", + "typeinfo": "no typeinfo" + }, + { + "name": "tc-other", + "typeinfo": ".*" + } + ] +} diff --git a/tools/deep_memory_profiler/sorter.vm-map.json b/tools/deep_memory_profiler/sorter.vm-map.json new file mode 100644 index 0000000000..7fc4d27785 --- /dev/null +++ b/tools/deep_memory_profiler/sorter.vm-map.json @@ -0,0 +1,107 @@ +{ + "type": "sorter", + "version": 1, + "world": "vm", + "name": "map", + "root": true, + "order": {}, + "rules": [ + { + "name": "mmap-profiler", + "backtrace_function": ".*(ProfilerMalloc|MemoryRegionMap::).*", + "mmap": true, + "hidden": true + }, + { + "name": "mmap-type-profiler", + "backtrace_function": ".*(TypeProfilerMalloc).*", + "mmap": true, + "hidden": true + }, + { + "name": "unhooked-anonymous", + "mapped_pathname": "^$", + "mmap": false + }, + { + "name": "unhooked-file-exec", + "mapped_pathname": "^/.*", + "mapped_permission": "..x.", + "mmap": false + }, + { + "name": "unhooked-file-nonexec", + "mapped_pathname": "^/.*", + "mmap": false + }, + { + "name": "unhooked-stack", + "mapped_pathname": ".stack.", + "mmap": false + }, + { + "name": "unhooked-other", + "mapped_pathname": ".*", + "mmap": false + }, + { + "name": "mmap-tcmalloc", + "backtrace_function": ".*(DoAllocWithArena|SbrkSysAllocator::Alloc|MmapSysAllocator::Alloc|LowLevelAlloc::Alloc|LowLevelAlloc::AllocWithArena).*", + "subs": [ + [ "malloc", "component" ], + [ "malloc", "type" ] + ], + "mmap": true + }, + { + "name": "mmap-v8-heap-newspace", + "backtrace_function": ".*v8::internal::NewSpace::SetUp.*", + "mmap": true + }, + { + "name": "mmap-v8-heap-coderange", + "backtrace_function": ".*v8::internal::CodeRange::SetUp.*", + "mmap": true + }, + { + "name": "mmap-v8-heap-pagedspace", + "backtrace_function": ".*v8::internal::PagedSpace::AllocateRaw.*", + "mmap": true + }, + { + "name": "mmap-v8-other", + "backtrace_function": ".*v8::.*", + "mmap": true + }, + { + "name": "mmap-gpu-mappedmemorymanager", + "backtrace_function": ".*gpu::MappedMemoryManager::Alloc.*", + "mmap": true + }, + { + "name": "mmap-gpu-command-ringbuffer", + "backtrace_function": ".*gpu::CommandBufferHelper::AllocateRingBuffer.*", + "mmap": true + }, + { + "name": "mmap-gpu-transfer-ringbuffer", + "backtrace_function": ".*gpu::TransferBuffer::AllocateRingBuffer.*", + "mmap": true + }, + { + "name": "mmap-gpu-gles2-createbuffer", + "backtrace_function": ".*gpu::gles2::BufferTracker::CreateBuffer.*", + "mmap": true + }, + { + "name": "mmap-skia-font", + "backtrace_function": ".*SkTypeface::openStream.*", + "mmap": true + }, + { + "name": "mmap-catch-all", + "backtrace_function": ".*", + "mmap": true + } + ] +} diff --git a/tools/deep_memory_profiler/sorter.vm-sharing.json b/tools/deep_memory_profiler/sorter.vm-sharing.json new file mode 100644 index 0000000000..4cb52a61f3 --- /dev/null +++ b/tools/deep_memory_profiler/sorter.vm-sharing.json @@ -0,0 +1,25 @@ +{ + "type": "sorter", + "version": 1, + "world": "vm", + "name": "sharing", + "root": true, + "order": {}, + "rules": [ + { + "name": "others", + "sharedwith": ["others"] + }, + { + "name": "chrome", + "sharedwith": ["group"] + }, + { + "name": "private", + "sharedwith": ["private"] + }, + { + "name": "any" + } + ] +} diff --git a/tools/deep_memory_profiler/subcommands/__init__.py b/tools/deep_memory_profiler/subcommands/__init__.py new file mode 100644 index 0000000000..4fb29d0c37 --- /dev/null +++ b/tools/deep_memory_profiler/subcommands/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +from subcommands.buckets import BucketsCommand +from subcommands.cat import CatCommand +from subcommands.expand import ExpandCommand +from subcommands.map import MapCommand +from subcommands.policies import CSVCommand +from subcommands.policies import JSONCommand +from subcommands.policies import ListCommand +from subcommands.pprof import PProfCommand +from subcommands.stacktrace import StacktraceCommand +from subcommands.upload import UploadCommand diff --git a/tools/deep_memory_profiler/subcommands/buckets.py b/tools/deep_memory_profiler/subcommands/buckets.py new file mode 100644 index 0000000000..4ea8640ca9 --- /dev/null +++ b/tools/deep_memory_profiler/subcommands/buckets.py @@ -0,0 +1,35 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import logging +import sys + +from lib.subcommand import SubCommand + + +LOGGER = logging.getLogger('dmprof') + + +class BucketsCommand(SubCommand): + def __init__(self): + super(BucketsCommand, self).__init__('Usage: %prog buckets ') + + def do(self, sys_argv, out=sys.stdout): + _, args = self._parse_args(sys_argv, 1) + dump_path = args[1] + bucket_set = SubCommand.load_basic_files(dump_path, True, True) + + BucketsCommand._output(bucket_set, out) + return 0 + + @staticmethod + def _output(bucket_set, out): + """Prints all buckets with resolving symbols. + + Args: + bucket_set: A BucketSet object. + out: An IO object to output. + """ + for bucket_id, bucket in sorted(bucket_set): + out.write('%d: %s\n' % (bucket_id, bucket)) diff --git a/tools/deep_memory_profiler/subcommands/cat.py b/tools/deep_memory_profiler/subcommands/cat.py new file mode 100644 index 0000000000..1e9e5b39d8 --- /dev/null +++ b/tools/deep_memory_profiler/subcommands/cat.py @@ -0,0 +1,179 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import json +import logging +import sys + +from lib.bucket import BUCKET_ID, COMMITTED, ALLOC_COUNT, FREE_COUNT +from lib.ordered_dict import OrderedDict +from lib.subcommand import SubCommand +from lib.sorter import MallocUnit, MMapUnit, SorterSet, UnhookedUnit, UnitSet + + +LOGGER = logging.getLogger('dmprof') + + +class CatCommand(SubCommand): + def __init__(self): + super(CatCommand, self).__init__('Usage: %prog cat ') + self._parser.add_option('--alternative-dirs', dest='alternative_dirs', + metavar='/path/on/target@/path/on/host[:...]', + help='Read files in /path/on/host/ instead of ' + 'files in /path/on/target/.') + self._parser.add_option('--indent', dest='indent', action='store_true', + help='Indent the output.') + + def do(self, sys_argv): + options, args = self._parse_args(sys_argv, 1) + dump_path = args[1] + # TODO(dmikurube): Support shared memory. + alternative_dirs_dict = {} + if options.alternative_dirs: + for alternative_dir_pair in options.alternative_dirs.split(':'): + target_path, host_path = alternative_dir_pair.split('@', 1) + alternative_dirs_dict[target_path] = host_path + (bucket_set, dumps) = SubCommand.load_basic_files( + dump_path, True, alternative_dirs=alternative_dirs_dict) + + # Load all sorters. + sorters = SorterSet() + + json_root = OrderedDict() + json_root['version'] = 1 + json_root['run_id'] = None + for dump in dumps: + if json_root['run_id'] and json_root['run_id'] != dump.run_id: + LOGGER.error('Inconsistent heap profile dumps.') + json_root['run_id'] = '' + break + json_root['run_id'] = dump.run_id + json_root['roots'] = [] + for sorter in sorters: + if sorter.root: + json_root['roots'].append([sorter.world, sorter.name]) + json_root['default_template'] = 'l2' + json_root['templates'] = sorters.templates.as_dict() + + json_root['snapshots'] = [] + + for dump in dumps: + json_root['snapshots'].append( + self._fill_snapshot(dump, bucket_set, sorters)) + + if options.indent: + json.dump(json_root, sys.stdout, indent=2) + else: + json.dump(json_root, sys.stdout) + print '' + + @staticmethod + def _fill_snapshot(dump, bucket_set, sorters): + root = OrderedDict() + root['time'] = dump.time + root['worlds'] = OrderedDict() + root['worlds']['vm'] = CatCommand._fill_world( + dump, bucket_set, sorters, 'vm') + root['worlds']['malloc'] = CatCommand._fill_world( + dump, bucket_set, sorters, 'malloc') + return root + + @staticmethod + def _fill_world(dump, bucket_set, sorters, world): + root = OrderedDict() + + root['name'] = world + if world == 'vm': + root['unit_fields'] = ['size', 'reserved'] + elif world == 'malloc': + root['unit_fields'] = ['size', 'alloc_count', 'free_count'] + + # Make { vm | malloc } units with their sizes. + root['units'] = OrderedDict() + unit_set = UnitSet(world) + if world == 'vm': + for unit in CatCommand._iterate_vm_unit(dump, None, bucket_set): + unit_set.append(unit) + for unit in unit_set: + root['units'][unit.unit_id] = [unit.committed, unit.reserved] + elif world == 'malloc': + for unit in CatCommand._iterate_malloc_unit(dump, bucket_set): + unit_set.append(unit) + for unit in unit_set: + root['units'][unit.unit_id] = [ + unit.size, unit.alloc_count, unit.free_count] + + # Iterate for { vm | malloc } sorters. + root['breakdown'] = OrderedDict() + for sorter in sorters.iter_world(world): + breakdown = OrderedDict() + for unit in unit_set: + found = sorter.find(unit) + if found.name not in breakdown: + category = OrderedDict() + category['name'] = found.name + category['color'] = 'random' + subs = [] + for sub_world, sub_breakdown in found.iter_subs(): + subs.append([sub_world, sub_breakdown]) + if subs: + category['subs'] = subs + if found.hidden: + category['hidden'] = True + category['units'] = [] + breakdown[found.name] = category + breakdown[found.name]['units'].append(unit.unit_id) + root['breakdown'][sorter.name] = breakdown + + return root + + @staticmethod + def _iterate_vm_unit(dump, pfn_dict, bucket_set): + unit_id = 0 + for _, region in dump.iter_map: + unit_id += 1 + if region[0] == 'unhooked': + if pfn_dict and dump.pageframe_length: + for pageframe in region[1]['pageframe']: + yield UnhookedUnit(unit_id, pageframe.size, pageframe.size, + region, pageframe, pfn_dict) + else: + yield UnhookedUnit(unit_id, + int(region[1]['committed']), + int(region[1]['reserved']), + region) + elif region[0] == 'hooked': + if pfn_dict and dump.pageframe_length: + for pageframe in region[1]['pageframe']: + yield MMapUnit(unit_id, + pageframe.size, + pageframe.size, + region, bucket_set, pageframe, pfn_dict) + else: + yield MMapUnit(unit_id, + int(region[1]['committed']), + int(region[1]['reserved']), + region, + bucket_set) + else: + LOGGER.error('Unrecognized mapping status: %s' % region[0]) + + @staticmethod + def _iterate_malloc_unit(dump, bucket_set): + for line in dump.iter_stacktrace: + words = line.split() + bucket = bucket_set.get(int(words[BUCKET_ID])) + if bucket and bucket.allocator_type == 'malloc': + yield MallocUnit(int(words[BUCKET_ID]), + int(words[COMMITTED]), + int(words[ALLOC_COUNT]), + int(words[FREE_COUNT]), + bucket) + elif not bucket: + # 'Not-found' buckets are all assumed as malloc buckets. + yield MallocUnit(int(words[BUCKET_ID]), + int(words[COMMITTED]), + int(words[ALLOC_COUNT]), + int(words[FREE_COUNT]), + None) diff --git a/tools/deep_memory_profiler/subcommands/expand.py b/tools/deep_memory_profiler/subcommands/expand.py new file mode 100644 index 0000000000..4058a00180 --- /dev/null +++ b/tools/deep_memory_profiler/subcommands/expand.py @@ -0,0 +1,104 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import logging +import sys + +from lib.bucket import BUCKET_ID, COMMITTED, ALLOC_COUNT, FREE_COUNT +from lib.policy import PolicySet +from lib.subcommand import SubCommand + + +LOGGER = logging.getLogger('dmprof') + + +class ExpandCommand(SubCommand): + def __init__(self): + super(ExpandCommand, self).__init__( + 'Usage: %prog expand ') + + def do(self, sys_argv): + _, args = self._parse_args(sys_argv, 4) + dump_path = args[1] + target_policy = args[2] + component_name = args[3] + depth = args[4] + (bucket_set, dump) = SubCommand.load_basic_files(dump_path, False) + policy_set = PolicySet.load(SubCommand._parse_policy_list(target_policy)) + + ExpandCommand._output(dump, policy_set[target_policy], bucket_set, + component_name, int(depth), sys.stdout) + return 0 + + @staticmethod + def _output(dump, policy, bucket_set, component_name, depth, out): + """Prints all stacktraces in a given component of given depth. + + Args: + dump: A Dump object. + policy: A Policy object. + bucket_set: A BucketSet object. + component_name: A name of component for filtering. + depth: An integer representing depth to be printed. + out: An IO object to output. + """ + sizes = {} + + ExpandCommand._accumulate( + dump, policy, bucket_set, component_name, depth, sizes) + + sorted_sizes_list = sorted( + sizes.iteritems(), key=(lambda x: x[1]), reverse=True) + total = 0 + # TODO(dmikurube): Better formatting. + for size_pair in sorted_sizes_list: + out.write('%10d %s\n' % (size_pair[1], size_pair[0])) + total += size_pair[1] + LOGGER.info('total: %d\n' % total) + + @staticmethod + def _add_size(precedence, bucket, depth, committed, sizes): + stacktrace_sequence = precedence + for function, sourcefile in zip( + bucket.symbolized_stackfunction[ + 0 : min(len(bucket.symbolized_stackfunction), 1 + depth)], + bucket.symbolized_stacksourcefile[ + 0 : min(len(bucket.symbolized_stacksourcefile), 1 + depth)]): + stacktrace_sequence += '%s(@%s) ' % (function, sourcefile) + if not stacktrace_sequence in sizes: + sizes[stacktrace_sequence] = 0 + sizes[stacktrace_sequence] += committed + + @staticmethod + def _accumulate(dump, policy, bucket_set, component_name, depth, sizes): + rule = policy.find_rule(component_name) + if not rule: + pass + elif rule.allocator_type == 'malloc': + for line in dump.iter_stacktrace: + words = line.split() + bucket = bucket_set.get(int(words[BUCKET_ID])) + if not bucket or bucket.allocator_type == 'malloc': + component_match = policy.find_malloc(bucket) + elif bucket.allocator_type == 'mmap': + continue + else: + assert False + if component_match == component_name: + precedence = '' + precedence += '(alloc=%d) ' % int(words[ALLOC_COUNT]) + precedence += '(free=%d) ' % int(words[FREE_COUNT]) + if bucket.typeinfo: + precedence += '(type=%s) ' % bucket.symbolized_typeinfo + precedence += '(type.name=%s) ' % bucket.typeinfo_name + ExpandCommand._add_size(precedence, bucket, depth, + int(words[COMMITTED]), sizes) + elif rule.allocator_type == 'mmap': + for _, region in dump.iter_map: + if region[0] != 'hooked': + continue + component_match, bucket = policy.find_mmap(region, bucket_set) + if component_match == component_name: + ExpandCommand._add_size('', bucket, depth, + region[1]['committed'], sizes) diff --git a/tools/deep_memory_profiler/subcommands/map.py b/tools/deep_memory_profiler/subcommands/map.py new file mode 100644 index 0000000000..2237d6f200 --- /dev/null +++ b/tools/deep_memory_profiler/subcommands/map.py @@ -0,0 +1,102 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import copy +import logging +import sys + +from lib.range_dict import ExclusiveRangeDict +from lib.policy import PolicySet +from lib.subcommand import SubCommand + + +LOGGER = logging.getLogger('dmprof') + + +class MapCommand(SubCommand): + def __init__(self): + super(MapCommand, self).__init__('Usage: %prog map ') + + def do(self, sys_argv, out=sys.stdout): + _, args = self._parse_args(sys_argv, 2) + dump_path = args[1] + target_policy = args[2] + (bucket_set, dumps) = SubCommand.load_basic_files(dump_path, True) + policy_set = PolicySet.load(SubCommand._parse_policy_list(target_policy)) + + MapCommand._output(dumps, bucket_set, policy_set[target_policy], out) + return 0 + + @staticmethod + def _output(dumps, bucket_set, policy, out): + """Prints all stacktraces in a given component of given depth. + + Args: + dumps: A list of Dump objects. + bucket_set: A BucketSet object. + policy: A Policy object. + out: An IO object to output. + """ + max_dump_count = 0 + range_dict = ExclusiveRangeDict(ListAttribute) + for dump in dumps: + max_dump_count = max(max_dump_count, dump.count) + for key, value in dump.iter_map: + for begin, end, attr in range_dict.iter_range(key[0], key[1]): + attr[dump.count] = value + + max_dump_count_digit = len(str(max_dump_count)) + for begin, end, attr in range_dict.iter_range(): + out.write('%x-%x\n' % (begin, end)) + if len(attr) < max_dump_count: + attr[max_dump_count] = None + for index, value in enumerate(attr[1:]): + out.write(' #%0*d: ' % (max_dump_count_digit, index + 1)) + if not value: + out.write('None\n') + elif value[0] == 'hooked': + component_match, _ = policy.find_mmap(value, bucket_set) + out.write('%s @ %d\n' % (component_match, value[1]['bucket_id'])) + else: + component_match = policy.find_unhooked(value) + region_info = value[1] + size = region_info['committed'] + out.write('%s [%d bytes] %s%s%s%s %s\n' % ( + component_match, size, value[1]['vma']['readable'], + value[1]['vma']['writable'], value[1]['vma']['executable'], + value[1]['vma']['private'], value[1]['vma']['name'])) + + +class ListAttribute(ExclusiveRangeDict.RangeAttribute): + """Represents a list for an attribute in range_dict.ExclusiveRangeDict.""" + def __init__(self): + super(ListAttribute, self).__init__() + self._list = [] + + def __str__(self): + return str(self._list) + + def __repr__(self): + return 'ListAttribute' + str(self._list) + + def __len__(self): + return len(self._list) + + def __iter__(self): + for x in self._list: + yield x + + def __getitem__(self, index): + return self._list[index] + + def __setitem__(self, index, value): + if index >= len(self._list): + self._list.extend([None] * (index + 1 - len(self._list))) + self._list[index] = value + + def copy(self): + new_list = ListAttribute() + for index, item in enumerate(self._list): + new_list[index] = copy.deepcopy(item) + return new_list diff --git a/tools/deep_memory_profiler/subcommands/policies.py b/tools/deep_memory_profiler/subcommands/policies.py new file mode 100644 index 0000000000..182959b10f --- /dev/null +++ b/tools/deep_memory_profiler/subcommands/policies.py @@ -0,0 +1,375 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import datetime +import json +import logging +import sys + +from lib.bucket import BUCKET_ID, COMMITTED +from lib.pageframe import PFNCounts +from lib.policy import PolicySet +from lib.subcommand import SubCommand + + +LOGGER = logging.getLogger('dmprof') + + +class PolicyCommands(SubCommand): + def __init__(self, command): + super(PolicyCommands, self).__init__( + 'Usage: %%prog %s [-p POLICY] [shared-first-dumps...]' % + command) + self._parser.add_option('-p', '--policy', type='string', dest='policy', + help='profile with POLICY', metavar='POLICY') + self._parser.add_option('--alternative-dirs', dest='alternative_dirs', + metavar='/path/on/target@/path/on/host[:...]', + help='Read files in /path/on/host/ instead of ' + 'files in /path/on/target/.') + + def _set_up(self, sys_argv): + options, args = self._parse_args(sys_argv, 1) + dump_path = args[1] + shared_first_dump_paths = args[2:] + alternative_dirs_dict = {} + if options.alternative_dirs: + for alternative_dir_pair in options.alternative_dirs.split(':'): + target_path, host_path = alternative_dir_pair.split('@', 1) + alternative_dirs_dict[target_path] = host_path + (bucket_set, dumps) = SubCommand.load_basic_files( + dump_path, True, alternative_dirs=alternative_dirs_dict) + + pfn_counts_dict = {} + for shared_first_dump_path in shared_first_dump_paths: + shared_dumps = SubCommand._find_all_dumps(shared_first_dump_path) + for shared_dump in shared_dumps: + pfn_counts = PFNCounts.load(shared_dump) + if pfn_counts.pid not in pfn_counts_dict: + pfn_counts_dict[pfn_counts.pid] = [] + pfn_counts_dict[pfn_counts.pid].append(pfn_counts) + + policy_set = PolicySet.load(SubCommand._parse_policy_list(options.policy)) + return policy_set, dumps, pfn_counts_dict, bucket_set + + @staticmethod + def _apply_policy(dump, pfn_counts_dict, policy, bucket_set, first_dump_time): + """Aggregates the total memory size of each component. + + Iterate through all stacktraces and attribute them to one of the components + based on the policy. It is important to apply policy in right order. + + Args: + dump: A Dump object. + pfn_counts_dict: A dict mapping a pid to a list of PFNCounts. + policy: A Policy object. + bucket_set: A BucketSet object. + first_dump_time: An integer representing time when the first dump is + dumped. + + Returns: + A dict mapping components and their corresponding sizes. + """ + LOGGER.info(' %s' % dump.path) + all_pfn_dict = {} + if pfn_counts_dict: + LOGGER.info(' shared with...') + for pid, pfnset_list in pfn_counts_dict.iteritems(): + closest_pfnset_index = None + closest_pfnset_difference = 1024.0 + for index, pfnset in enumerate(pfnset_list): + time_difference = pfnset.time - dump.time + if time_difference >= 3.0: + break + elif ((time_difference < 0.0 and pfnset.reason != 'Exiting') or + (0.0 <= time_difference and time_difference < 3.0)): + closest_pfnset_index = index + closest_pfnset_difference = time_difference + elif time_difference < 0.0 and pfnset.reason == 'Exiting': + closest_pfnset_index = None + break + if closest_pfnset_index: + for pfn, count in pfnset_list[closest_pfnset_index].iter_pfn: + all_pfn_dict[pfn] = all_pfn_dict.get(pfn, 0) + count + LOGGER.info(' %s (time difference = %f)' % + (pfnset_list[closest_pfnset_index].path, + closest_pfnset_difference)) + else: + LOGGER.info(' (no match with pid:%d)' % pid) + + sizes = dict((c, 0) for c in policy.components) + + PolicyCommands._accumulate_malloc(dump, policy, bucket_set, sizes) + verify_global_stats = PolicyCommands._accumulate_maps( + dump, all_pfn_dict, policy, bucket_set, sizes) + + # TODO(dmikurube): Remove the verifying code when GLOBAL_STATS is removed. + # http://crbug.com/245603. + for verify_key, verify_value in verify_global_stats.iteritems(): + dump_value = dump.global_stat('%s_committed' % verify_key) + if dump_value != verify_value: + LOGGER.warn('%25s: %12d != %d (%d)' % ( + verify_key, dump_value, verify_value, dump_value - verify_value)) + + sizes['mmap-no-log'] = ( + dump.global_stat('profiled-mmap_committed') - + sizes['mmap-total-log']) + sizes['mmap-total-record'] = dump.global_stat('profiled-mmap_committed') + sizes['mmap-total-record-vm'] = dump.global_stat('profiled-mmap_virtual') + + sizes['tc-no-log'] = ( + dump.global_stat('profiled-malloc_committed') - + sizes['tc-total-log']) + sizes['tc-total-record'] = dump.global_stat('profiled-malloc_committed') + sizes['tc-unused'] = ( + sizes['mmap-tcmalloc'] - + dump.global_stat('profiled-malloc_committed')) + if sizes['tc-unused'] < 0: + LOGGER.warn(' Assuming tc-unused=0 as it is negative: %d (bytes)' % + sizes['tc-unused']) + sizes['tc-unused'] = 0 + sizes['tc-total'] = sizes['mmap-tcmalloc'] + + # TODO(dmikurube): global_stat will be deprecated. + # See http://crbug.com/245603. + for key, value in { + 'total': 'total_committed', + 'filemapped': 'file_committed', + 'absent': 'absent_committed', + 'file-exec': 'file-exec_committed', + 'file-nonexec': 'file-nonexec_committed', + 'anonymous': 'anonymous_committed', + 'stack': 'stack_committed', + 'other': 'other_committed', + 'unhooked-absent': 'nonprofiled-absent_committed', + 'total-vm': 'total_virtual', + 'filemapped-vm': 'file_virtual', + 'anonymous-vm': 'anonymous_virtual', + 'other-vm': 'other_virtual' }.iteritems(): + if key in sizes: + sizes[key] = dump.global_stat(value) + + if 'mustbezero' in sizes: + removed_list = ( + 'profiled-mmap_committed', + 'nonprofiled-absent_committed', + 'nonprofiled-anonymous_committed', + 'nonprofiled-file-exec_committed', + 'nonprofiled-file-nonexec_committed', + 'nonprofiled-stack_committed', + 'nonprofiled-other_committed') + sizes['mustbezero'] = ( + dump.global_stat('total_committed') - + sum(dump.global_stat(removed) for removed in removed_list)) + if 'total-exclude-profiler' in sizes: + sizes['total-exclude-profiler'] = ( + dump.global_stat('total_committed') - + (sizes['mmap-profiler'] + sizes['mmap-type-profiler'])) + if 'hour' in sizes: + sizes['hour'] = (dump.time - first_dump_time) / 60.0 / 60.0 + if 'minute' in sizes: + sizes['minute'] = (dump.time - first_dump_time) / 60.0 + if 'second' in sizes: + sizes['second'] = dump.time - first_dump_time + + return sizes + + @staticmethod + def _accumulate_malloc(dump, policy, bucket_set, sizes): + for line in dump.iter_stacktrace: + words = line.split() + bucket = bucket_set.get(int(words[BUCKET_ID])) + if not bucket or bucket.allocator_type == 'malloc': + component_match = policy.find_malloc(bucket) + elif bucket.allocator_type == 'mmap': + continue + else: + assert False + sizes[component_match] += int(words[COMMITTED]) + + assert not component_match.startswith('mmap-') + if component_match.startswith('tc-'): + sizes['tc-total-log'] += int(words[COMMITTED]) + else: + sizes['other-total-log'] += int(words[COMMITTED]) + + @staticmethod + def _accumulate_maps(dump, pfn_dict, policy, bucket_set, sizes): + # TODO(dmikurube): Remove the dict when GLOBAL_STATS is removed. + # http://crbug.com/245603. + global_stats = { + 'total': 0, + 'file-exec': 0, + 'file-nonexec': 0, + 'anonymous': 0, + 'stack': 0, + 'other': 0, + 'nonprofiled-file-exec': 0, + 'nonprofiled-file-nonexec': 0, + 'nonprofiled-anonymous': 0, + 'nonprofiled-stack': 0, + 'nonprofiled-other': 0, + 'profiled-mmap': 0, + } + + for key, value in dump.iter_map: + # TODO(dmikurube): Remove the subtotal code when GLOBAL_STATS is removed. + # It's temporary verification code for transition described in + # http://crbug.com/245603. + committed = 0 + if 'committed' in value[1]: + committed = value[1]['committed'] + global_stats['total'] += committed + key = 'other' + name = value[1]['vma']['name'] + if name.startswith('/'): + if value[1]['vma']['executable'] == 'x': + key = 'file-exec' + else: + key = 'file-nonexec' + elif name == '[stack]': + key = 'stack' + elif name == '': + key = 'anonymous' + global_stats[key] += committed + if value[0] == 'unhooked': + global_stats['nonprofiled-' + key] += committed + if value[0] == 'hooked': + global_stats['profiled-mmap'] += committed + + if value[0] == 'unhooked': + if pfn_dict and dump.pageframe_length: + for pageframe in value[1]['pageframe']: + component_match = policy.find_unhooked(value, pageframe, pfn_dict) + sizes[component_match] += pageframe.size + else: + component_match = policy.find_unhooked(value) + sizes[component_match] += int(value[1]['committed']) + elif value[0] == 'hooked': + if pfn_dict and dump.pageframe_length: + for pageframe in value[1]['pageframe']: + component_match, _ = policy.find_mmap( + value, bucket_set, pageframe, pfn_dict) + sizes[component_match] += pageframe.size + assert not component_match.startswith('tc-') + if component_match.startswith('mmap-'): + sizes['mmap-total-log'] += pageframe.size + else: + sizes['other-total-log'] += pageframe.size + else: + component_match, _ = policy.find_mmap(value, bucket_set) + sizes[component_match] += int(value[1]['committed']) + if component_match.startswith('mmap-'): + sizes['mmap-total-log'] += int(value[1]['committed']) + else: + sizes['other-total-log'] += int(value[1]['committed']) + else: + LOGGER.error('Unrecognized mapping status: %s' % value[0]) + + return global_stats + + +class CSVCommand(PolicyCommands): + def __init__(self): + super(CSVCommand, self).__init__('csv') + + def do(self, sys_argv): + policy_set, dumps, pfn_counts_dict, bucket_set = self._set_up(sys_argv) + return CSVCommand._output( + policy_set, dumps, pfn_counts_dict, bucket_set, sys.stdout) + + @staticmethod + def _output(policy_set, dumps, pfn_counts_dict, bucket_set, out): + max_components = 0 + for label in policy_set: + max_components = max(max_components, len(policy_set[label].components)) + + for label in sorted(policy_set): + components = policy_set[label].components + if len(policy_set) > 1: + out.write('%s%s\n' % (label, ',' * (max_components - 1))) + out.write('%s%s\n' % ( + ','.join(components), ',' * (max_components - len(components)))) + + LOGGER.info('Applying a policy %s to...' % label) + for dump in dumps: + component_sizes = PolicyCommands._apply_policy( + dump, pfn_counts_dict, policy_set[label], bucket_set, dumps[0].time) + s = [] + for c in components: + if c in ('hour', 'minute', 'second'): + s.append('%05.5f' % (component_sizes[c])) + else: + s.append('%05.5f' % (component_sizes[c] / 1024.0 / 1024.0)) + out.write('%s%s\n' % ( + ','.join(s), ',' * (max_components - len(components)))) + + bucket_set.clear_component_cache() + + return 0 + + +class JSONCommand(PolicyCommands): + def __init__(self): + super(JSONCommand, self).__init__('json') + + def do(self, sys_argv): + policy_set, dumps, pfn_counts_dict, bucket_set = self._set_up(sys_argv) + return JSONCommand._output( + policy_set, dumps, pfn_counts_dict, bucket_set, sys.stdout) + + @staticmethod + def _output(policy_set, dumps, pfn_counts_dict, bucket_set, out): + json_base = { + 'version': 'JSON_DEEP_2', + 'policies': {}, + } + + for label in sorted(policy_set): + json_base['policies'][label] = { + 'legends': policy_set[label].components, + 'snapshots': [], + } + + LOGGER.info('Applying a policy %s to...' % label) + for dump in dumps: + component_sizes = PolicyCommands._apply_policy( + dump, pfn_counts_dict, policy_set[label], bucket_set, dumps[0].time) + component_sizes['dump_path'] = dump.path + component_sizes['dump_time'] = datetime.datetime.fromtimestamp( + dump.time).strftime('%Y-%m-%d %H:%M:%S') + json_base['policies'][label]['snapshots'].append(component_sizes) + + bucket_set.clear_component_cache() + + json.dump(json_base, out, indent=2, sort_keys=True) + + return 0 + + +class ListCommand(PolicyCommands): + def __init__(self): + super(ListCommand, self).__init__('list') + + def do(self, sys_argv): + policy_set, dumps, pfn_counts_dict, bucket_set = self._set_up(sys_argv) + return ListCommand._output( + policy_set, dumps, pfn_counts_dict, bucket_set, sys.stdout) + + @staticmethod + def _output(policy_set, dumps, pfn_counts_dict, bucket_set, out): + for label in sorted(policy_set): + LOGGER.info('Applying a policy %s to...' % label) + for dump in dumps: + component_sizes = PolicyCommands._apply_policy( + dump, pfn_counts_dict, policy_set[label], bucket_set, dump.time) + out.write('%s for %s:\n' % (label, dump.path)) + for c in policy_set[label].components: + if c in ['hour', 'minute', 'second']: + out.write('%40s %12.3f\n' % (c, component_sizes[c])) + else: + out.write('%40s %12d\n' % (c, component_sizes[c])) + + bucket_set.clear_component_cache() + + return 0 diff --git a/tools/deep_memory_profiler/subcommands/pprof.py b/tools/deep_memory_profiler/subcommands/pprof.py new file mode 100644 index 0000000000..506d811711 --- /dev/null +++ b/tools/deep_memory_profiler/subcommands/pprof.py @@ -0,0 +1,161 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import logging +import sys + +from lib.bucket import BUCKET_ID, COMMITTED, ALLOC_COUNT, FREE_COUNT +from lib.policy import PolicySet +from lib.subcommand import SubCommand + + +LOGGER = logging.getLogger('dmprof') + + +class PProfCommand(SubCommand): + def __init__(self): + super(PProfCommand, self).__init__( + 'Usage: %prog pprof [-c COMPONENT] ') + self._parser.add_option('-c', '--component', type='string', + dest='component', + help='restrict to COMPONENT', metavar='COMPONENT') + + def do(self, sys_argv): + options, args = self._parse_args(sys_argv, 2) + + dump_path = args[1] + target_policy = args[2] + component = options.component + + (bucket_set, dump) = SubCommand.load_basic_files(dump_path, False) + policy_set = PolicySet.load(SubCommand._parse_policy_list(target_policy)) + + with open(SubCommand._find_prefix(dump_path) + '.maps', 'r') as maps_f: + maps_lines = maps_f.readlines() + PProfCommand._output( + dump, policy_set[target_policy], bucket_set, maps_lines, component, + sys.stdout) + + return 0 + + @staticmethod + def _output(dump, policy, bucket_set, maps_lines, component_name, out): + """Converts the heap profile dump so it can be processed by pprof. + + Args: + dump: A Dump object. + policy: A Policy object. + bucket_set: A BucketSet object. + maps_lines: A list of strings containing /proc/.../maps. + component_name: A name of component for filtering. + out: An IO object to output. + """ + out.write('heap profile: ') + com_committed, com_allocs = PProfCommand._accumulate( + dump, policy, bucket_set, component_name) + + out.write('%6d: %8s [%6d: %8s] @ heapprofile\n' % ( + com_allocs, com_committed, com_allocs, com_committed)) + + PProfCommand._output_stacktrace_lines( + dump, policy, bucket_set, component_name, out) + + out.write('MAPPED_LIBRARIES:\n') + for line in maps_lines: + out.write(line) + + @staticmethod + def _accumulate(dump, policy, bucket_set, component_name): + """Accumulates size of committed chunks and the number of allocated chunks. + + Args: + dump: A Dump object. + policy: A Policy object. + bucket_set: A BucketSet object. + component_name: A name of component for filtering. + + Returns: + Two integers which are the accumulated size of committed regions and the + number of allocated chunks, respectively. + """ + com_committed = 0 + com_allocs = 0 + + for _, region in dump.iter_map: + if region[0] != 'hooked': + continue + component_match, bucket = policy.find_mmap(region, bucket_set) + + if (component_name and component_name != component_match) or ( + region[1]['committed'] == 0): + continue + + com_committed += region[1]['committed'] + com_allocs += 1 + + for line in dump.iter_stacktrace: + words = line.split() + bucket = bucket_set.get(int(words[BUCKET_ID])) + if not bucket or bucket.allocator_type == 'malloc': + component_match = policy.find_malloc(bucket) + elif bucket.allocator_type == 'mmap': + continue + else: + assert False + if (not bucket or + (component_name and component_name != component_match)): + continue + + com_committed += int(words[COMMITTED]) + com_allocs += int(words[ALLOC_COUNT]) - int(words[FREE_COUNT]) + + return com_committed, com_allocs + + @staticmethod + def _output_stacktrace_lines(dump, policy, bucket_set, component_name, out): + """Prints information of stacktrace lines for pprof. + + Args: + dump: A Dump object. + policy: A Policy object. + bucket_set: A BucketSet object. + component_name: A name of component for filtering. + out: An IO object to output. + """ + for _, region in dump.iter_map: + if region[0] != 'hooked': + continue + component_match, bucket = policy.find_mmap(region, bucket_set) + + if (component_name and component_name != component_match) or ( + region[1]['committed'] == 0): + continue + + out.write(' 1: %8s [ 1: %8s] @' % ( + region[1]['committed'], region[1]['committed'])) + for address in bucket.stacktrace: + out.write(' 0x%016x' % address) + out.write('\n') + + for line in dump.iter_stacktrace: + words = line.split() + bucket = bucket_set.get(int(words[BUCKET_ID])) + if not bucket or bucket.allocator_type == 'malloc': + component_match = policy.find_malloc(bucket) + elif bucket.allocator_type == 'mmap': + continue + else: + assert False + if (not bucket or + (component_name and component_name != component_match)): + continue + + out.write('%6d: %8s [%6d: %8s] @' % ( + int(words[ALLOC_COUNT]) - int(words[FREE_COUNT]), + words[COMMITTED], + int(words[ALLOC_COUNT]) - int(words[FREE_COUNT]), + words[COMMITTED])) + for address in bucket.stacktrace: + out.write(' 0x%016x' % address) + out.write('\n') diff --git a/tools/deep_memory_profiler/subcommands/stacktrace.py b/tools/deep_memory_profiler/subcommands/stacktrace.py new file mode 100644 index 0000000000..72b850981a --- /dev/null +++ b/tools/deep_memory_profiler/subcommands/stacktrace.py @@ -0,0 +1,41 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import sys + +from lib.bucket import BUCKET_ID +from lib.subcommand import SubCommand + + +class StacktraceCommand(SubCommand): + def __init__(self): + super(StacktraceCommand, self).__init__( + 'Usage: %prog stacktrace ') + + def do(self, sys_argv): + _, args = self._parse_args(sys_argv, 1) + dump_path = args[1] + (bucket_set, dump) = SubCommand.load_basic_files(dump_path, False) + + StacktraceCommand._output(dump, bucket_set, sys.stdout) + return 0 + + @staticmethod + def _output(dump, bucket_set, out): + """Outputs a given stacktrace. + + Args: + bucket_set: A BucketSet object. + out: A file object to output. + """ + for line in dump.iter_stacktrace: + words = line.split() + bucket = bucket_set.get(int(words[BUCKET_ID])) + if not bucket: + continue + for i in range(0, BUCKET_ID - 1): + out.write(words[i] + ' ') + for frame in bucket.symbolized_stackfunction: + out.write(frame + ' ') + out.write('\n') diff --git a/tools/deep_memory_profiler/subcommands/upload.py b/tools/deep_memory_profiler/subcommands/upload.py new file mode 100644 index 0000000000..de34a8a715 --- /dev/null +++ b/tools/deep_memory_profiler/subcommands/upload.py @@ -0,0 +1,79 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import logging +import os +import subprocess +import tempfile +import zipfile + +from lib.subcommand import SubCommand +from lib.symbol import SymbolDataSources + + +LOGGER = logging.getLogger('dmprof') + + +class UploadCommand(SubCommand): + def __init__(self): + super(UploadCommand, self).__init__( + 'Usage: %prog upload [--gsutil path/to/gsutil] ' + ' ') + self._parser.add_option('--gsutil', default='gsutil', + help='path to GSUTIL', metavar='GSUTIL') + + def do(self, sys_argv): + options, args = self._parse_args(sys_argv, 2) + dump_path = args[1] + gs_path = args[2] + + dump_files = SubCommand._find_all_dumps(dump_path) + bucket_files = SubCommand._find_all_buckets(dump_path) + prefix = SubCommand._find_prefix(dump_path) + symbol_data_sources = SymbolDataSources(prefix) + symbol_data_sources.prepare() + symbol_path = symbol_data_sources.path() + + handle_zip, filename_zip = tempfile.mkstemp('.zip', 'dmprof') + os.close(handle_zip) + + try: + file_zip = zipfile.ZipFile(filename_zip, 'w', zipfile.ZIP_DEFLATED) + for filename in dump_files: + file_zip.write(filename, os.path.basename(os.path.abspath(filename))) + for filename in bucket_files: + file_zip.write(filename, os.path.basename(os.path.abspath(filename))) + + symbol_basename = os.path.basename(os.path.abspath(symbol_path)) + for filename in os.listdir(symbol_path): + if not filename.startswith('.'): + file_zip.write(os.path.join(symbol_path, filename), + os.path.join(symbol_basename, os.path.basename( + os.path.abspath(filename)))) + file_zip.close() + + returncode = UploadCommand._run_gsutil( + options.gsutil, 'cp', '-a', 'public-read', filename_zip, gs_path) + finally: + os.remove(filename_zip) + + return returncode + + @staticmethod + def _run_gsutil(gsutil, *args): + """Run gsutil as a subprocess. + + Args: + *args: Arguments to pass to gsutil. The first argument should be an + operation such as ls, cp or cat. + Returns: + The return code from the process. + """ + command = [gsutil] + list(args) + LOGGER.info("Running: %s", command) + + try: + return subprocess.call(command) + except OSError, e: + LOGGER.error('Error to run gsutil: %s', e) diff --git a/tools/deep_memory_profiler/templates.json b/tools/deep_memory_profiler/templates.json new file mode 100644 index 0000000000..b4d9a4eb60 --- /dev/null +++ b/tools/deep_memory_profiler/templates.json @@ -0,0 +1,12 @@ +{ + "l2": ["vm", "map", { + "mmap-tcmalloc": ["malloc", "component", {}] + }], + "details": ["vm", "map", { + "mmap-tcmalloc": ["malloc", "component", { + "webkit": ["malloc", "webkit-details", {}], + "skia": ["malloc", "skia-details", {}] + }], + "mmap-v8-heap": ["javascript", "type", {}] + }] +} diff --git a/tools/deep_memory_profiler/tests/data/.gitignore b/tools/deep_memory_profiler/tests/data/.gitignore new file mode 100644 index 0000000000..ca08f425bd --- /dev/null +++ b/tools/deep_memory_profiler/tests/data/.gitignore @@ -0,0 +1 @@ +heap.01234.cache.* diff --git a/tools/deep_memory_profiler/tests/data/heap.01234.0001.buckets b/tools/deep_memory_profiler/tests/data/heap.01234.0001.buckets new file mode 100644 index 0000000000..c7de4bdbcf --- /dev/null +++ b/tools/deep_memory_profiler/tests/data/heap.01234.0001.buckets @@ -0,0 +1,5 @@ +4 mmap t0x0 nno_typeinfo 0x7f6306ff0571 0x7f630c5d52da 0x7f6306fdad6c 0x7f6306ffc365 0x7f6306ffc4cb 0x7f6306fdbc77 0x7f6306fdadd0 0x7f6306fde66e 0x7f6306fe2294 0x7f630c5d56a6 0x7f630b0e1c3c 0x7f6308e3d455 0x7f6308e3d311 0x7f6308e3d29d 0x7f6308e3cff7 0x7f6308e3cb70 0x7f6308e3aa88 0x7f6308e3a30d 0x7f6308e3a9ee 0x7f630985c298 0x7f63087cd903 0x7f63087c858d 0x7f63087c3f15 0x7f6306dbf057 0x7f6306deee40 0x7f6309c5bc11 0x7f6309c5dcd3 0x7f6309c53644 0x7f6309c563e7 0x7f6309c55dfb 0x7f6309c53271 0x7f6309c5386f +2 malloc t0x7f630ff7e718 nN7WebCore8RuleDataE 0x7f630b0e1c3c 0x7f6308a255e2 0x7f6308a25341 0x7f6308a24f75 0x7f6308a24592 0x7f6308a244b9 0x7f6308a24475 0x7f63089c6204 0x7f63089c660a 0x7f63084a5961 0x7f6308d5a7fc 0x7f6308dcdf99 0x7f630b88a4e8 0x7f630b88a3fe 0x7f6308d3dd77 0x7f6307514142 0x7f63075169f1 0x7f63074d019d 0x7f63074ceafe 0x7f63074ca99b 0x7f63074c9d0b 0x7f63074a3331 0x7f63074a1927 0x7f63074a10c5 0x7f63074a3ae3 0x7f6308d4df27 0x7f63086cdb28 0x7f63086b2969 0x7f6306db7a37 0x7f6306e48c5e 0x7f63086b2a50 0x7f63086b2f7b +1 malloc t0x0 nno_typeinfo 0x7f6307289164 0x7f6307288f89 0x7f6307288f10 0x7f6307288a98 0x7f63072887a5 0x7f63072884c0 0x7f6307286613 0x7f6307284cbc 0x7f6307285022 0x7f63072358a4 0x7f6307235845 0x7f63091b2836 0x7f63091b1d98 0x7f63090935bb 0x7f63069a33e6 0x7f63069a8eda 0x7f63069a8e31 0x7f63069a8dcc 0x7f6305c6eade 0x7f630720564e 0x7f630720592b 0x7f6307205ad5 0x7f630720f418 0x7f63072050c6 0x7f6307204f75 0x7f6307236682 0x7f6307204811 0x7f6309cb5190 0x7f6309c39b8a 0x7f6309c39ea4 0x7f6309c3ae5d 0x7f6309c392d4 +3 malloc t0x7f630feeb3a0 nN7WebCore17CSSPrimitiveValueE 0x7f630b0e1c3c 0x7f6306e67075 0x7f630844b318 0x7f630846fc1e 0x7f63083eab7a 0x7f63098737df 0x7f63083e71c7 0x7f630850ed0c 0x7f630850ec92 0x7f63089c63bf 0x7f63089c650b 0x7f63089c5e1d 0x7f63089c5bf6 0x7f63084a4e42 0x7f6308d5b114 0x7f6306e2423b 0x7f6308dcad80 0x7f6308e2e1e4 0x7f6308dca1ed 0x7f6308dca245 0x7f63075141d7 0x7f6307513ff1 0x7f63075147fd 0x7f63074d4158 0x7f63074cec74 0x7f63074ca9b7 0x7f63074c9d0b 0x7f63074a3331 0x7f63074a1927 0x7f63074a10c5 0x7f63074a0f3d 0x7f63074a3c73 +5 malloc t0x0 nno_typeinfo 0x7f62fd455a89 0x7f62fd4575e3 0x7fff9439ce50 diff --git a/tools/deep_memory_profiler/tests/data/heap.01234.0001.heap b/tools/deep_memory_profiler/tests/data/heap.01234.0001.heap new file mode 100644 index 0000000000..1c1d28a5ce --- /dev/null +++ b/tools/deep_memory_profiler/tests/data/heap.01234.0001.heap @@ -0,0 +1,25 @@ +heap profile: DUMP_DEEP_6 +GLOBAL_STATS: + virtual committed + total 1022775296 64831488 + file-exec 207237120 32600064 + file-nonexec 194805760 10862592 + anonymous 620589056 21233664 + stack 139264 131072 + other 4096 4096 + nonprofiled-total 414846427 47091700 + nonprofiled-absent 0 0 + nonprofiled-anonymous 23650304 3751936 + nonprofiled-file-exec 207237120 32600064 + nonprofiled-file-nonexec 183815643 10604532 + nonprofiled-stack 139264 131072 + nonprofiled-other 4096 4096 + profiled-mmap 607928869 17727500 + profiled-malloc 1935377 1873937 +STACKTRACES: + virtual committed + 65536 65536 1 0 @ 4 + 32 32 1 0 @ 1 + 48 48 1 0 @ 3 + 8288 8288 109 0 @ 2 + 72 72 1 0 @ 5 diff --git a/tools/deep_memory_profiler/tests/data/heap.01234.0002.buckets b/tools/deep_memory_profiler/tests/data/heap.01234.0002.buckets new file mode 100644 index 0000000000..9a4c71d777 --- /dev/null +++ b/tools/deep_memory_profiler/tests/data/heap.01234.0002.buckets @@ -0,0 +1,2 @@ +6 malloc t0x0 nno_typeinfo 0x7f630b0e1c3c 0x7f630b0e1ba5 0x7f6308e1d21b 0x7f6308e1d157 0x7f6308e1e2e0 0x7f6308e1dff9 0x7f6308e1db4c 0x7f6308e17cd0 0x7f6308e13370 0x7f6308de1b18 0x7f6308e12f9f 0x7f6308e12f2f 0x7f630978f482 0x7f630978e785 0x12e93499fa0f 0x12e93499f038 0x12e93499ee28 0x12e934925d44 0x12e934907177 0x7f630a88841d 0x7f630a887f04 0x7f630a7ea908 0x7f6308342a17 0x7f63083427a6 0x7f6308390b75 0x7f630897225b 0x7f6308971f0f 0x7f6308de27da 0x7f6308de2379 0x7f6308e13d89 0x7f6308ea5011 0x7f6308ea52ec +7 mmap t0x0 nno_typeinfo 0x7f6306ff0571 0x7f630c5d52da 0x7f630763cbac 0x7f630763cae7 0x7f630763c51a 0x7f630763c34b 0x7f630b91ba67 0x7f63072f33d4 0x7f630aa68d19 0x7f630aa61012 0x7f630aa581e2 0x7f630aa579d9 0x7f630aa5745a 0x7f630aa56fc4 0x7f630aa56ddf 0x7f630aa56d45 0x7f630aa5625b 0x7f630aa55f0d 0x7f630aa55156 0x7f630aa5558f 0x7f630aa584cc 0x7f630aa579d9 0x7f630aa5745a 0x7f630aa56fc4 0x7f630aa56ddf 0x7f630aa56d45 0x7f630aa5625b 0x7f630aa55f0d 0x7f630aa55156 0x7f630aa5488c 0x7f630aa4d56d 0x7f630aa4e0e4 diff --git a/tools/deep_memory_profiler/tests/data/heap.01234.0002.heap b/tools/deep_memory_profiler/tests/data/heap.01234.0002.heap new file mode 100644 index 0000000000..800913983b --- /dev/null +++ b/tools/deep_memory_profiler/tests/data/heap.01234.0002.heap @@ -0,0 +1,28 @@ +heap profile: DUMP_DEEP_6 +GLOBAL_STATS: + virtual committed + total 1067888640 101031936 + file-exec 207237120 35569664 + file-nonexec 192380928 8441856 + anonymous 666476544 55226368 + stack 1789952 1789952 + other 4096 4096 + nonprofiled-total 416234971 50352116 + nonprofiled-absent 0 0 + nonprofiled-anonymous 25812992 4808704 + nonprofiled-file-exec 207237120 35569664 + nonprofiled-file-nonexec 181390811 8179700 + nonprofiled-stack 1789952 1789952 + nonprofiled-other 4096 4096 + profiled-mmap 651653669 50679820 + profiled-malloc 3054552 3025880 + unhooked-arena 64128 64128 +STACKTRACES: + virtual committed + 65536 65536 1 0 @ 4 + 32 32 1 0 @ 1 + 48 48 1 0 @ 3 + 8288 8288 109 0 @ 2 + 72 72 1 0 @ 5 + 32768 32768 7 6 @ 7 + 29 29 1 0 @ 6 diff --git a/tools/deep_memory_profiler/tests/data/heap.01234.maps b/tools/deep_memory_profiler/tests/data/heap.01234.maps new file mode 100644 index 0000000000..a2555247d1 --- /dev/null +++ b/tools/deep_memory_profiler/tests/data/heap.01234.maps @@ -0,0 +1,410 @@ +114147900000-114147935000 rw-p 00000000 00:00 0 +12e93464c000-12e934700000 ---p 00000000 00:00 0 +12e934700000-12e934705000 rw-p 00000000 00:00 0 +12e934705000-12e934706000 ---p 00000000 00:00 0 +12e934706000-12e934707000 rwxp 00000000 00:00 0 +12e934707000-12e934800000 ---p 00000000 00:00 0 +12e934800000-12e934805000 rw-p 00000000 00:00 0 +12e934805000-12e934806000 ---p 00000000 00:00 0 +12e934806000-12e934807000 rwxp 00000000 00:00 0 +12e934807000-12e934900000 ---p 00000000 00:00 0 +12e934900000-12e934905000 rw-p 00000000 00:00 0 +12e934905000-12e934906000 ---p 00000000 00:00 0 +12e934906000-12e9349ff000 rwxp 00000000 00:00 0 +12e9349ff000-12e95464c000 ---p 00000000 00:00 0 +16b0892d7000-16b0892d8000 r-xp 00000000 00:00 0 +19fc4e858000-19fc4e859000 r-xp 00000000 00:00 0 +1ae6500da000-1ae6503ea000 rw-p 00000000 00:00 0 +1c8ad47d9000-1c8ad47da000 r-xp 00000000 00:00 0 +1cca7ce6a000-1cca7ce80000 ---p 00000000 00:00 0 +1cca7ce80000-1cca7cea0000 rw-p 00000000 00:00 0 +1cca7cea0000-1cca7ceca000 ---p 00000000 00:00 0 +2195f26d4000-2195f26d5000 r-xp 00000000 00:00 0 +21c091300000-21c091325000 rw-p 00000000 00:00 0 +277034700000-277034800000 rw-p 00000000 00:00 0 +280bd8000000-280bd8f00000 ---p 00000000 00:00 0 +280bd8f00000-280bd9000000 rw-p 00000000 00:00 0 +280bd9000000-280bda000000 ---p 00000000 00:00 0 +2bb801c00000-2bb801c85000 rw-p 00000000 00:00 0 +30713ff00000-30713ff25000 rw-p 00000000 00:00 0 +31278c7c9000-31278c7ca000 r-xp 00000000 00:00 0 +3190d6f35000-3190d6f36000 rw-p 00000000 00:00 0 +3190d6f36000-3190d7135000 ---p 00000000 00:00 0 +7f62f5a2b000-7f62f607b000 rw-p 00000000 00:00 0 +7f62f607b000-7f62f6301000 rw-- 00000000 00:00 888963090 /SYSV00000000 (deleted) +7f62f6301000-7f62f6311000 rw-p 00000000 00:00 0 +7f62f6311000-7f62f6362000 r--- 00000000 00:00 1323029 /usr/share/fonts/truetype/msttcorefonts/Times_New_Roman.ttf +7f62f6362000-7f62f64d2000 rw-p 00000000 00:00 0 +7f62f64fe000-7f62f6542000 r--- 00000000 00:00 1323020 /usr/share/fonts/truetype/msttcorefonts/Arial.ttf +7f62f6542000-7f62f65b2000 rw-p 00000000 00:00 0 +7f62f65b2000-7f62f6804000 rw-- 00000000 00:00 888930318 /SYSV00000000 (deleted) +7f62f6804000-7f62f6984000 rw-p 00000000 00:00 0 +7f62f6984000-7f62f69a4000 r--- 00000000 00:00 14645040 /run/shm/.org.chromium.Chromium.KmCcUm (deleted) +7f62f69a4000-7f62f6a44000 rw-p 00000000 00:00 0 +7f62f6a44000-7f62f6a45000 ---p 00000000 00:00 0 +7f62f6a45000-7f62f6a55000 rw-p 00000000 00:00 0 +7f62f6a55000-7f62f6a56000 ---p 00000000 00:00 0 +7f62f6a56000-7f62f6af6000 rw-p 00000000 00:00 0 +7f62f6af6000-7f62f6af7000 ---p 00000000 00:00 0 +7f62f6af7000-7f62f72f7000 rw-p 00000000 00:00 0 +7f62f72f7000-7f62f72f8000 ---p 00000000 00:00 0 +7f62f72f8000-7f62f7af8000 rw-p 00000000 00:00 0 +7f62f7af8000-7f62f7bd6000 r-xp 00000000 00:00 6311146 /home/user/chromium/src/out/Debug/libppGoogleNaClPluginChrome.so +7f62f7bd6000-7f62f7bd7000 ---p 000de000 00:00 6311146 /home/user/chromium/src/out/Debug/libppGoogleNaClPluginChrome.so +7f62f7bd7000-7f62f7bda000 r--p 000de000 00:00 6311146 /home/user/chromium/src/out/Debug/libppGoogleNaClPluginChrome.so +7f62f7bda000-7f62f7bdc000 rw-p 000e1000 00:00 6311146 /home/user/chromium/src/out/Debug/libppGoogleNaClPluginChrome.so +7f62f7bdc000-7f62f7c3e000 r-xp 00000000 00:00 422206 /usr/lib/x86_64-linux-gnu/nss/libfreebl3.so +7f62f7c3e000-7f62f7e3d000 ---p 00062000 00:00 422206 /usr/lib/x86_64-linux-gnu/nss/libfreebl3.so +7f62f7e3d000-7f62f7e3f000 r--p 00061000 00:00 422206 /usr/lib/x86_64-linux-gnu/nss/libfreebl3.so +7f62f7e3f000-7f62f7e40000 rw-p 00063000 00:00 422206 /usr/lib/x86_64-linux-gnu/nss/libfreebl3.so +7f62f7e40000-7f62f7e44000 rw-p 00000000 00:00 0 +7f62f7e44000-7f62f7ee2000 r-xp 00000000 00:00 419056 /usr/lib/x86_64-linux-gnu/libsqlite3.so.0.8.6 +7f62f7ee2000-7f62f80e2000 ---p 0009e000 00:00 419056 /usr/lib/x86_64-linux-gnu/libsqlite3.so.0.8.6 +7f62f80e2000-7f62f80e4000 r--p 0009e000 00:00 419056 /usr/lib/x86_64-linux-gnu/libsqlite3.so.0.8.6 +7f62f80e4000-7f62f80e6000 rw-p 000a0000 00:00 419056 /usr/lib/x86_64-linux-gnu/libsqlite3.so.0.8.6 +7f62f80e6000-7f62f80e7000 rw-p 00000000 00:00 0 +7f62f80f6000-7f62f8106000 rw-p 00000000 00:00 0 +7f62f8106000-7f62f813e000 r-xp 00000000 00:00 422227 /usr/lib/x86_64-linux-gnu/nss/libsoftokn3.so +7f62f813e000-7f62f833d000 ---p 00038000 00:00 422227 /usr/lib/x86_64-linux-gnu/nss/libsoftokn3.so +7f62f833d000-7f62f833f000 r--p 00037000 00:00 422227 /usr/lib/x86_64-linux-gnu/nss/libsoftokn3.so +7f62f833f000-7f62f8340000 rw-p 00039000 00:00 422227 /usr/lib/x86_64-linux-gnu/nss/libsoftokn3.so +7f62f8340000-7f62f84fd000 r-xp 00000000 00:00 6311921 /home/user/chromium/src/out/Debug/libffmpegsumo.so +7f62f84fd000-7f62f84fe000 ---p 001bd000 00:00 6311921 /home/user/chromium/src/out/Debug/libffmpegsumo.so +7f62f84fe000-7f62f850e000 r--p 001bd000 00:00 6311921 /home/user/chromium/src/out/Debug/libffmpegsumo.so +7f62f850e000-7f62f8510000 rw-p 001cd000 00:00 6311921 /home/user/chromium/src/out/Debug/libffmpegsumo.so +7f62f8510000-7f62f85b3000 rw-p 00000000 00:00 0 +7f62f85b3000-7f62f8ad7000 r--- 00000000 00:00 6968058 /home/user/chromium/src/out/Debug/resources.pak +7f62f8ad7000-7f62f8b01000 r--- 00000000 00:00 6966120 /home/user/chromium/src/out/Debug/locales/en-US.pak +7f62f8b01000-7f62f8bd6000 r--- 00000000 00:00 6966021 /home/user/chromium/src/out/Debug/chrome_100_percent.pak +7f62f8bd6000-7f62f8f7d000 r--- 00000000 00:00 6966014 /home/user/chromium/src/out/Debug/chrome.pak +7f62f8f7d000-7f62f8fce000 rw-p 00000000 00:00 0 +7f62f8fce000-7f62f9d21000 r--p 00000000 00:00 795151 /usr/lib/locale/locale-archive +7f62f9d21000-7f62fa621000 rw-p 00000000 00:00 0 +7f62fa621000-7f62fa626000 r-xp 00000000 00:00 418995 /usr/lib/x86_64-linux-gnu/libXdmcp.so.6.0.0 +7f62fa626000-7f62fa825000 ---p 00005000 00:00 418995 /usr/lib/x86_64-linux-gnu/libXdmcp.so.6.0.0 +7f62fa825000-7f62fa826000 r--p 00004000 00:00 418995 /usr/lib/x86_64-linux-gnu/libXdmcp.so.6.0.0 +7f62fa826000-7f62fa827000 rw-p 00005000 00:00 418995 /usr/lib/x86_64-linux-gnu/libXdmcp.so.6.0.0 +7f62fa827000-7f62fa829000 r-xp 00000000 00:00 419161 /usr/lib/x86_64-linux-gnu/libXau.so.6.0.0 +7f62fa829000-7f62faa28000 ---p 00002000 00:00 419161 /usr/lib/x86_64-linux-gnu/libXau.so.6.0.0 +7f62faa28000-7f62faa29000 r--p 00001000 00:00 419161 /usr/lib/x86_64-linux-gnu/libXau.so.6.0.0 +7f62faa29000-7f62faa2a000 rw-p 00002000 00:00 419161 /usr/lib/x86_64-linux-gnu/libXau.so.6.0.0 +7f62faa2a000-7f62faa2d000 r-xp 00000000 00:00 262419 /lib/x86_64-linux-gnu/libgpg-error.so.0.8.0 +7f62faa2d000-7f62fac2c000 ---p 00003000 00:00 262419 /lib/x86_64-linux-gnu/libgpg-error.so.0.8.0 +7f62fac2c000-7f62fac2d000 r--p 00002000 00:00 262419 /lib/x86_64-linux-gnu/libgpg-error.so.0.8.0 +7f62fac2d000-7f62fac2e000 rw-p 00003000 00:00 262419 /lib/x86_64-linux-gnu/libgpg-error.so.0.8.0 +7f62fac2e000-7f62fac3f000 r-xp 00000000 00:00 419811 /usr/lib/x86_64-linux-gnu/libp11-kit.so.0.0.0 +7f62fac3f000-7f62fae3e000 ---p 00011000 00:00 419811 /usr/lib/x86_64-linux-gnu/libp11-kit.so.0.0.0 +7f62fae3e000-7f62fae3f000 r--p 00010000 00:00 419811 /usr/lib/x86_64-linux-gnu/libp11-kit.so.0.0.0 +7f62fae3f000-7f62fae40000 rw-p 00011000 00:00 419811 /usr/lib/x86_64-linux-gnu/libp11-kit.so.0.0.0 +7f62fae40000-7f62fae50000 r-xp 00000000 00:00 420094 /usr/lib/x86_64-linux-gnu/libtasn1.so.3.1.12 +7f62fae50000-7f62fb04f000 ---p 00010000 00:00 420094 /usr/lib/x86_64-linux-gnu/libtasn1.so.3.1.12 +7f62fb04f000-7f62fb050000 r--p 0000f000 00:00 420094 /usr/lib/x86_64-linux-gnu/libtasn1.so.3.1.12 +7f62fb050000-7f62fb051000 rw-p 00010000 00:00 420094 /usr/lib/x86_64-linux-gnu/libtasn1.so.3.1.12 +7f62fb051000-7f62fb054000 r-xp 00000000 00:00 262413 /lib/x86_64-linux-gnu/libkeyutils.so.1.4 +7f62fb054000-7f62fb253000 ---p 00003000 00:00 262413 /lib/x86_64-linux-gnu/libkeyutils.so.1.4 +7f62fb253000-7f62fb254000 r--p 00002000 00:00 262413 /lib/x86_64-linux-gnu/libkeyutils.so.1.4 +7f62fb254000-7f62fb255000 rw-p 00003000 00:00 262413 /lib/x86_64-linux-gnu/libkeyutils.so.1.4 +7f62fb255000-7f62fb25c000 r-xp 00000000 00:00 418378 /usr/lib/x86_64-linux-gnu/libkrb5support.so.0.1 +7f62fb25c000-7f62fb45b000 ---p 00007000 00:00 418378 /usr/lib/x86_64-linux-gnu/libkrb5support.so.0.1 +7f62fb45b000-7f62fb45c000 r--p 00006000 00:00 418378 /usr/lib/x86_64-linux-gnu/libkrb5support.so.0.1 +7f62fb45c000-7f62fb45d000 rw-p 00007000 00:00 418378 /usr/lib/x86_64-linux-gnu/libkrb5support.so.0.1 +7f62fb45d000-7f62fb46d000 r-xp 00000000 00:00 419369 /usr/lib/x86_64-linux-gnu/libavahi-client.so.3.2.9 +7f62fb46d000-7f62fb66c000 ---p 00010000 00:00 419369 /usr/lib/x86_64-linux-gnu/libavahi-client.so.3.2.9 +7f62fb66c000-7f62fb66d000 r--p 0000f000 00:00 419369 /usr/lib/x86_64-linux-gnu/libavahi-client.so.3.2.9 +7f62fb66d000-7f62fb66e000 rw-p 00010000 00:00 419369 /usr/lib/x86_64-linux-gnu/libavahi-client.so.3.2.9 +7f62fb66e000-7f62fb679000 r-xp 00000000 00:00 420318 /usr/lib/x86_64-linux-gnu/libavahi-common.so.3.5.3 +7f62fb679000-7f62fb878000 ---p 0000b000 00:00 420318 /usr/lib/x86_64-linux-gnu/libavahi-common.so.3.5.3 +7f62fb878000-7f62fb879000 r--p 0000a000 00:00 420318 /usr/lib/x86_64-linux-gnu/libavahi-common.so.3.5.3 +7f62fb879000-7f62fb87a000 rw-p 0000b000 00:00 420318 /usr/lib/x86_64-linux-gnu/libavahi-common.so.3.5.3 +7f62fb87a000-7f62fb89f000 r-xp 00000000 00:00 420145 /usr/lib/x86_64-linux-gnu/libdbus-glib-1.so.2.2.2 +7f62fb89f000-7f62fba9f000 ---p 00025000 00:00 420145 /usr/lib/x86_64-linux-gnu/libdbus-glib-1.so.2.2.2 +7f62fba9f000-7f62fbaa0000 r--p 00025000 00:00 420145 /usr/lib/x86_64-linux-gnu/libdbus-glib-1.so.2.2.2 +7f62fbaa0000-7f62fbaa1000 rw-p 00026000 00:00 420145 /usr/lib/x86_64-linux-gnu/libdbus-glib-1.so.2.2.2 +7f62fbaa1000-7f62fbaa9000 r-xp 00000000 00:00 401299 /usr/lib/x86_64-linux-gnu/libxcb-render.so.0.0.0 +7f62fbaa9000-7f62fbca9000 ---p 00008000 00:00 401299 /usr/lib/x86_64-linux-gnu/libxcb-render.so.0.0.0 +7f62fbca9000-7f62fbcaa000 r--p 00008000 00:00 401299 /usr/lib/x86_64-linux-gnu/libxcb-render.so.0.0.0 +7f62fbcaa000-7f62fbcab000 rw-p 00009000 00:00 401299 /usr/lib/x86_64-linux-gnu/libxcb-render.so.0.0.0 +7f62fbcab000-7f62fbcad000 r-xp 00000000 00:00 396366 /usr/lib/x86_64-linux-gnu/libxcb-shm.so.0.0.0 +7f62fbcad000-7f62fbeac000 ---p 00002000 00:00 396366 /usr/lib/x86_64-linux-gnu/libxcb-shm.so.0.0.0 +7f62fbeac000-7f62fbead000 r--p 00001000 00:00 396366 /usr/lib/x86_64-linux-gnu/libxcb-shm.so.0.0.0 +7f62fbead000-7f62fbeae000 rw-p 00002000 00:00 396366 /usr/lib/x86_64-linux-gnu/libxcb-shm.so.0.0.0 +7f62fbeae000-7f62fbed4000 r-xp 00000000 00:00 262376 /lib/x86_64-linux-gnu/libpng12.so.0.46.0 +7f62fbed4000-7f62fc0d4000 ---p 00026000 00:00 262376 /lib/x86_64-linux-gnu/libpng12.so.0.46.0 +7f62fc0d4000-7f62fc0d5000 r--p 00026000 00:00 262376 /lib/x86_64-linux-gnu/libpng12.so.0.46.0 +7f62fc0d5000-7f62fc0d6000 rw-p 00027000 00:00 262376 /lib/x86_64-linux-gnu/libpng12.so.0.46.0 +7f62fc0d6000-7f62fc157000 r-xp 00000000 00:00 419692 /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.24.4 +7f62fc157000-7f62fc356000 ---p 00081000 00:00 419692 /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.24.4 +7f62fc356000-7f62fc35c000 r--p 00080000 00:00 419692 /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.24.4 +7f62fc35c000-7f62fc35d000 rw-p 00086000 00:00 419692 /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.24.4 +7f62fc35d000-7f62fc37a000 r-xp 00000000 00:00 262459 /lib/x86_64-linux-gnu/libselinux.so.1 +7f62fc37a000-7f62fc579000 ---p 0001d000 00:00 262459 /lib/x86_64-linux-gnu/libselinux.so.1 +7f62fc579000-7f62fc57a000 r--p 0001c000 00:00 262459 /lib/x86_64-linux-gnu/libselinux.so.1 +7f62fc57a000-7f62fc57b000 rw-p 0001d000 00:00 262459 /lib/x86_64-linux-gnu/libselinux.so.1 +7f62fc57b000-7f62fc57c000 rw-p 00000000 00:00 0 +7f62fc57c000-7f62fc57e000 r-xp 00000000 00:00 419344 /usr/lib/x86_64-linux-gnu/libXinerama.so.1.0.0 +7f62fc57e000-7f62fc77d000 ---p 00002000 00:00 419344 /usr/lib/x86_64-linux-gnu/libXinerama.so.1.0.0 +7f62fc77d000-7f62fc77e000 r--p 00001000 00:00 419344 /usr/lib/x86_64-linux-gnu/libXinerama.so.1.0.0 +7f62fc77e000-7f62fc77f000 rw-p 00002000 00:00 419344 /usr/lib/x86_64-linux-gnu/libXinerama.so.1.0.0 +7f62fc77f000-7f62fc7bb000 r-xp 00000000 00:00 262390 /lib/x86_64-linux-gnu/libpcre.so.3.12.1 +7f62fc7bb000-7f62fc9ba000 ---p 0003c000 00:00 262390 /lib/x86_64-linux-gnu/libpcre.so.3.12.1 +7f62fc9ba000-7f62fc9bb000 r--p 0003b000 00:00 262390 /lib/x86_64-linux-gnu/libpcre.so.3.12.1 +7f62fc9bb000-7f62fc9bc000 rw-p 0003c000 00:00 262390 /lib/x86_64-linux-gnu/libpcre.so.3.12.1 +7f62fc9bc000-7f62fc9c3000 r-xp 00000000 00:00 419528 /usr/lib/x86_64-linux-gnu/libffi.so.6.0.0 +7f62fc9c3000-7f62fcbc2000 ---p 00007000 00:00 419528 /usr/lib/x86_64-linux-gnu/libffi.so.6.0.0 +7f62fcbc2000-7f62fcbc3000 r--p 00006000 00:00 419528 /usr/lib/x86_64-linux-gnu/libffi.so.6.0.0 +7f62fcbc3000-7f62fcbc4000 rw-p 00007000 00:00 419528 /usr/lib/x86_64-linux-gnu/libffi.so.6.0.0 +7f62fcbc4000-7f62fcbe1000 r-xp 00000000 00:00 400302 /usr/lib/x86_64-linux-gnu/libxcb.so.1.1.0 +7f62fcbe1000-7f62fcde0000 ---p 0001d000 00:00 400302 /usr/lib/x86_64-linux-gnu/libxcb.so.1.1.0 +7f62fcde0000-7f62fcde1000 r--p 0001c000 00:00 400302 /usr/lib/x86_64-linux-gnu/libxcb.so.1.1.0 +7f62fcde1000-7f62fcde2000 rw-p 0001d000 00:00 400302 /usr/lib/x86_64-linux-gnu/libxcb.so.1.1.0 +7f62fcde2000-7f62fcf97000 r-xp 00000000 00:00 263480 /lib/x86_64-linux-gnu/libc-2.15.so +7f62fcf97000-7f62fd196000 ---p 001b5000 00:00 263480 /lib/x86_64-linux-gnu/libc-2.15.so +7f62fd196000-7f62fd19a000 r--p 001b4000 00:00 263480 /lib/x86_64-linux-gnu/libc-2.15.so +7f62fd19a000-7f62fd19c000 rw-p 001b8000 00:00 263480 /lib/x86_64-linux-gnu/libc-2.15.so +7f62fd19c000-7f62fd1a1000 rw-p 00000000 00:00 0 +7f62fd1a1000-7f62fd1b6000 r-xp 00000000 00:00 262366 /lib/x86_64-linux-gnu/libgcc_s.so.1 +7f62fd1b6000-7f62fd3b5000 ---p 00015000 00:00 262366 /lib/x86_64-linux-gnu/libgcc_s.so.1 +7f62fd3b5000-7f62fd3b6000 r--p 00014000 00:00 262366 /lib/x86_64-linux-gnu/libgcc_s.so.1 +7f62fd3b6000-7f62fd3b7000 rw-p 00015000 00:00 262366 /lib/x86_64-linux-gnu/libgcc_s.so.1 +7f62fd3b7000-7f62fd499000 r-xp 00000000 00:00 419083 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.16 +7f62fd499000-7f62fd698000 ---p 000e2000 00:00 419083 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.16 +7f62fd698000-7f62fd6a0000 r--p 000e1000 00:00 419083 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.16 +7f62fd6a0000-7f62fd6a2000 rw-p 000e9000 00:00 419083 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.16 +7f62fd6a2000-7f62fd6b7000 rw-p 00000000 00:00 0 +7f62fd6b7000-7f62fd6c3000 r-xp 00000000 00:00 262197 /lib/x86_64-linux-gnu/libudev.so.0.13.0 +7f62fd6c3000-7f62fd8c2000 ---p 0000c000 00:00 262197 /lib/x86_64-linux-gnu/libudev.so.0.13.0 +7f62fd8c2000-7f62fd8c3000 r--p 0000b000 00:00 262197 /lib/x86_64-linux-gnu/libudev.so.0.13.0 +7f62fd8c3000-7f62fd8c4000 rw-p 0000c000 00:00 262197 /lib/x86_64-linux-gnu/libudev.so.0.13.0 +7f62fd8c4000-7f62fd8eb000 r-xp 00000000 00:00 262435 /lib/x86_64-linux-gnu/libexpat.so.1.5.2 +7f62fd8eb000-7f62fdaeb000 ---p 00027000 00:00 262435 /lib/x86_64-linux-gnu/libexpat.so.1.5.2 +7f62fdaeb000-7f62fdaed000 r--p 00027000 00:00 262435 /lib/x86_64-linux-gnu/libexpat.so.1.5.2 +7f62fdaed000-7f62fdaee000 rw-p 00029000 00:00 262435 /lib/x86_64-linux-gnu/libexpat.so.1.5.2 +7f62fdaee000-7f62fdafd000 r-xp 00000000 00:00 262446 /lib/x86_64-linux-gnu/libbz2.so.1.0.4 +7f62fdafd000-7f62fdcfc000 ---p 0000f000 00:00 262446 /lib/x86_64-linux-gnu/libbz2.so.1.0.4 +7f62fdcfc000-7f62fdcfd000 r--p 0000e000 00:00 262446 /lib/x86_64-linux-gnu/libbz2.so.1.0.4 +7f62fdcfd000-7f62fdcfe000 rw-p 0000f000 00:00 262446 /lib/x86_64-linux-gnu/libbz2.so.1.0.4 +7f62fdcfe000-7f62fddf9000 r-xp 00000000 00:00 296562 /lib/x86_64-linux-gnu/libm-2.15.so +7f62fddf9000-7f62fdff8000 ---p 000fb000 00:00 296562 /lib/x86_64-linux-gnu/libm-2.15.so +7f62fdff8000-7f62fdff9000 r--p 000fa000 00:00 296562 /lib/x86_64-linux-gnu/libm-2.15.so +7f62fdff9000-7f62fdffa000 rw-p 000fb000 00:00 296562 /lib/x86_64-linux-gnu/libm-2.15.so +7f62fdffa000-7f62fe003000 r-xp 00000000 00:00 296563 /lib/x86_64-linux-gnu/libcrypt-2.15.so +7f62fe003000-7f62fe203000 ---p 00009000 00:00 296563 /lib/x86_64-linux-gnu/libcrypt-2.15.so +7f62fe203000-7f62fe204000 r--p 00009000 00:00 296563 /lib/x86_64-linux-gnu/libcrypt-2.15.so +7f62fe204000-7f62fe205000 rw-p 0000a000 00:00 296563 /lib/x86_64-linux-gnu/libcrypt-2.15.so +7f62fe205000-7f62fe233000 rw-p 00000000 00:00 0 +7f62fe233000-7f62fe249000 r-xp 00000000 00:00 262537 /lib/x86_64-linux-gnu/libz.so.1.2.3.4 +7f62fe249000-7f62fe448000 ---p 00016000 00:00 262537 /lib/x86_64-linux-gnu/libz.so.1.2.3.4 +7f62fe448000-7f62fe449000 r--p 00015000 00:00 262537 /lib/x86_64-linux-gnu/libz.so.1.2.3.4 +7f62fe449000-7f62fe44a000 rw-p 00016000 00:00 262537 /lib/x86_64-linux-gnu/libz.so.1.2.3.4 +7f62fe44a000-7f62fe4c4000 r-xp 00000000 00:00 262361 /lib/x86_64-linux-gnu/libgcrypt.so.11.7.0 +7f62fe4c4000-7f62fe6c4000 ---p 0007a000 00:00 262361 /lib/x86_64-linux-gnu/libgcrypt.so.11.7.0 +7f62fe6c4000-7f62fe6c5000 r--p 0007a000 00:00 262361 /lib/x86_64-linux-gnu/libgcrypt.so.11.7.0 +7f62fe6c5000-7f62fe6c8000 rw-p 0007b000 00:00 262361 /lib/x86_64-linux-gnu/libgcrypt.so.11.7.0 +7f62fe6c8000-7f62fe77c000 r-xp 00000000 00:00 419320 /usr/lib/x86_64-linux-gnu/libgnutls.so.26.21.8 +7f62fe77c000-7f62fe97c000 ---p 000b4000 00:00 419320 /usr/lib/x86_64-linux-gnu/libgnutls.so.26.21.8 +7f62fe97c000-7f62fe982000 r--p 000b4000 00:00 419320 /usr/lib/x86_64-linux-gnu/libgnutls.so.26.21.8 +7f62fe982000-7f62fe983000 rw-p 000ba000 00:00 419320 /usr/lib/x86_64-linux-gnu/libgnutls.so.26.21.8 +7f62fe983000-7f62fe984000 rw-p 00000000 00:00 0 +7f62fe984000-7f62fe987000 r-xp 00000000 00:00 262402 /lib/x86_64-linux-gnu/libcom_err.so.2.1 +7f62fe987000-7f62feb86000 ---p 00003000 00:00 262402 /lib/x86_64-linux-gnu/libcom_err.so.2.1 +7f62feb86000-7f62feb87000 r--p 00002000 00:00 262402 /lib/x86_64-linux-gnu/libcom_err.so.2.1 +7f62feb87000-7f62feb88000 rw-p 00003000 00:00 262402 /lib/x86_64-linux-gnu/libcom_err.so.2.1 +7f62feb88000-7f62febad000 r-xp 00000000 00:00 419096 /usr/lib/x86_64-linux-gnu/libk5crypto.so.3.1 +7f62febad000-7f62fedad000 ---p 00025000 00:00 419096 /usr/lib/x86_64-linux-gnu/libk5crypto.so.3.1 +7f62fedad000-7f62fedae000 r--p 00025000 00:00 419096 /usr/lib/x86_64-linux-gnu/libk5crypto.so.3.1 +7f62fedae000-7f62fedaf000 rw-p 00026000 00:00 419096 /usr/lib/x86_64-linux-gnu/libk5crypto.so.3.1 +7f62fedaf000-7f62fedb0000 rw-p 00000000 00:00 0 +7f62fedb0000-7f62fee74000 r-xp 00000000 00:00 420204 /usr/lib/x86_64-linux-gnu/libkrb5.so.3.3 +7f62fee74000-7f62ff073000 ---p 000c4000 00:00 420204 /usr/lib/x86_64-linux-gnu/libkrb5.so.3.3 +7f62ff073000-7f62ff07d000 r--p 000c3000 00:00 420204 /usr/lib/x86_64-linux-gnu/libkrb5.so.3.3 +7f62ff07d000-7f62ff07e000 rw-p 000cd000 00:00 420204 /usr/lib/x86_64-linux-gnu/libkrb5.so.3.3 +7f62ff07e000-7f62ff0b9000 r-xp 00000000 00:00 420086 /usr/lib/x86_64-linux-gnu/libgssapi_krb5.so.2.2 +7f62ff0b9000-7f62ff2b9000 ---p 0003b000 00:00 420086 /usr/lib/x86_64-linux-gnu/libgssapi_krb5.so.2.2 +7f62ff2b9000-7f62ff2ba000 r--p 0003b000 00:00 420086 /usr/lib/x86_64-linux-gnu/libgssapi_krb5.so.2.2 +7f62ff2ba000-7f62ff2bc000 rw-p 0003c000 00:00 420086 /usr/lib/x86_64-linux-gnu/libgssapi_krb5.so.2.2 +7f62ff2bc000-7f62ff30c000 r-xp 00000000 00:00 444357 /usr/lib/x86_64-linux-gnu/libcups.so.2 +7f62ff30c000-7f62ff50b000 ---p 00050000 00:00 444357 /usr/lib/x86_64-linux-gnu/libcups.so.2 +7f62ff50b000-7f62ff50f000 r--p 0004f000 00:00 444357 /usr/lib/x86_64-linux-gnu/libcups.so.2 +7f62ff50f000-7f62ff510000 rw-p 00053000 00:00 444357 /usr/lib/x86_64-linux-gnu/libcups.so.2 +7f62ff510000-7f62ff515000 r-xp 00000000 00:00 419317 /usr/lib/x86_64-linux-gnu/libXfixes.so.3.1.0 +7f62ff515000-7f62ff714000 ---p 00005000 00:00 419317 /usr/lib/x86_64-linux-gnu/libXfixes.so.3.1.0 +7f62ff714000-7f62ff715000 r--p 00004000 00:00 419317 /usr/lib/x86_64-linux-gnu/libXfixes.so.3.1.0 +7f62ff715000-7f62ff716000 rw-p 00005000 00:00 419317 /usr/lib/x86_64-linux-gnu/libXfixes.so.3.1.0 +7f62ff716000-7f62ff718000 r-xp 00000000 00:00 419868 /usr/lib/x86_64-linux-gnu/libXdamage.so.1.1.0 +7f62ff718000-7f62ff917000 ---p 00002000 00:00 419868 /usr/lib/x86_64-linux-gnu/libXdamage.so.1.1.0 +7f62ff917000-7f62ff918000 r--p 00001000 00:00 419868 /usr/lib/x86_64-linux-gnu/libXdamage.so.1.1.0 +7f62ff918000-7f62ff919000 rw-p 00002000 00:00 419868 /usr/lib/x86_64-linux-gnu/libXdamage.so.1.1.0 +7f62ff919000-7f62ff9ff000 r-xp 00000000 00:00 419887 /usr/lib/x86_64-linux-gnu/libasound.so.2.0.0 +7f62ff9ff000-7f62ffbff000 ---p 000e6000 00:00 419887 /usr/lib/x86_64-linux-gnu/libasound.so.2.0.0 +7f62ffbff000-7f62ffc05000 r--p 000e6000 00:00 419887 /usr/lib/x86_64-linux-gnu/libasound.so.2.0.0 +7f62ffc05000-7f62ffc06000 rw-p 000ec000 00:00 419887 /usr/lib/x86_64-linux-gnu/libasound.so.2.0.0 +7f62ffc06000-7f62ffc08000 r-xp 00000000 00:00 419816 /usr/lib/x86_64-linux-gnu/libXcomposite.so.1.0.0 +7f62ffc08000-7f62ffe07000 ---p 00002000 00:00 419816 /usr/lib/x86_64-linux-gnu/libXcomposite.so.1.0.0 +7f62ffe07000-7f62ffe08000 r--p 00001000 00:00 419816 /usr/lib/x86_64-linux-gnu/libXcomposite.so.1.0.0 +7f62ffe08000-7f62ffe09000 rw-p 00002000 00:00 419816 /usr/lib/x86_64-linux-gnu/libXcomposite.so.1.0.0 +7f62ffe09000-7f62ffe21000 r-xp 00000000 00:00 263482 /lib/x86_64-linux-gnu/libpthread-2.15.so +7f62ffe21000-7f6300020000 ---p 00018000 00:00 263482 /lib/x86_64-linux-gnu/libpthread-2.15.so +7f6300020000-7f6300021000 r--p 00017000 00:00 263482 /lib/x86_64-linux-gnu/libpthread-2.15.so +7f6300021000-7f6300022000 rw-p 00018000 00:00 263482 /lib/x86_64-linux-gnu/libpthread-2.15.so +7f6300022000-7f6300026000 rw-p 00000000 00:00 0 +7f6300026000-7f6300068000 r-xp 00000000 00:00 262204 /lib/x86_64-linux-gnu/libdbus-1.so.3.5.8 +7f6300068000-7f6300268000 ---p 00042000 00:00 262204 /lib/x86_64-linux-gnu/libdbus-1.so.3.5.8 +7f6300268000-7f6300269000 r--p 00042000 00:00 262204 /lib/x86_64-linux-gnu/libdbus-1.so.3.5.8 +7f6300269000-7f630026a000 rw-p 00043000 00:00 262204 /lib/x86_64-linux-gnu/libdbus-1.so.3.5.8 +7f630026a000-7f6300282000 r-xp 00000000 00:00 289099 /lib/x86_64-linux-gnu/libresolv-2.15.so +7f6300282000-7f6300482000 ---p 00018000 00:00 289099 /lib/x86_64-linux-gnu/libresolv-2.15.so +7f6300482000-7f6300483000 r--p 00018000 00:00 289099 /lib/x86_64-linux-gnu/libresolv-2.15.so +7f6300483000-7f6300484000 rw-p 00019000 00:00 289099 /lib/x86_64-linux-gnu/libresolv-2.15.so +7f6300484000-7f6300486000 rw-p 00000000 00:00 0 +7f6300486000-7f63004b3000 r-xp 00000000 00:00 419655 /usr/lib/x86_64-linux-gnu/libgconf-2.so.4.1.5 +7f63004b3000-7f63006b2000 ---p 0002d000 00:00 419655 /usr/lib/x86_64-linux-gnu/libgconf-2.so.4.1.5 +7f63006b2000-7f63006b3000 r--p 0002c000 00:00 419655 /usr/lib/x86_64-linux-gnu/libgconf-2.so.4.1.5 +7f63006b3000-7f63006b4000 rw-p 0002d000 00:00 419655 /usr/lib/x86_64-linux-gnu/libgconf-2.so.4.1.5 +7f63006b4000-7f63006ee000 r-xp 00000000 00:00 422225 /usr/lib/x86_64-linux-gnu/libnspr4.so +7f63006ee000-7f63008ee000 ---p 0003a000 00:00 422225 /usr/lib/x86_64-linux-gnu/libnspr4.so +7f63008ee000-7f63008ef000 r--p 0003a000 00:00 422225 /usr/lib/x86_64-linux-gnu/libnspr4.so +7f63008ef000-7f63008f1000 rw-p 0003b000 00:00 422225 /usr/lib/x86_64-linux-gnu/libnspr4.so +7f63008f1000-7f63008f3000 rw-p 00000000 00:00 0 +7f63008f3000-7f63008f7000 r-xp 00000000 00:00 422220 /usr/lib/x86_64-linux-gnu/libplc4.so +7f63008f7000-7f6300af6000 ---p 00004000 00:00 422220 /usr/lib/x86_64-linux-gnu/libplc4.so +7f6300af6000-7f6300af7000 r--p 00003000 00:00 422220 /usr/lib/x86_64-linux-gnu/libplc4.so +7f6300af7000-7f6300af8000 rw-p 00004000 00:00 422220 /usr/lib/x86_64-linux-gnu/libplc4.so +7f6300af8000-7f6300afb000 r-xp 00000000 00:00 422218 /usr/lib/x86_64-linux-gnu/libplds4.so +7f6300afb000-7f6300cfa000 ---p 00003000 00:00 422218 /usr/lib/x86_64-linux-gnu/libplds4.so +7f6300cfa000-7f6300cfb000 r--p 00002000 00:00 422218 /usr/lib/x86_64-linux-gnu/libplds4.so +7f6300cfb000-7f6300cfc000 rw-p 00003000 00:00 422218 /usr/lib/x86_64-linux-gnu/libplds4.so +7f6300cfc000-7f6300d1f000 r-xp 00000000 00:00 422231 /usr/lib/x86_64-linux-gnu/libsmime3.so +7f6300d1f000-7f6300f1f000 ---p 00023000 00:00 422231 /usr/lib/x86_64-linux-gnu/libsmime3.so +7f6300f1f000-7f6300f22000 r--p 00023000 00:00 422231 /usr/lib/x86_64-linux-gnu/libsmime3.so +7f6300f22000-7f6300f23000 rw-p 00026000 00:00 422231 /usr/lib/x86_64-linux-gnu/libsmime3.so +7f6300f23000-7f6300f44000 r-xp 00000000 00:00 420737 /usr/lib/x86_64-linux-gnu/libnssutil3.so +7f6300f44000-7f6301143000 ---p 00021000 00:00 420737 /usr/lib/x86_64-linux-gnu/libnssutil3.so +7f6301143000-7f6301149000 r--p 00020000 00:00 420737 /usr/lib/x86_64-linux-gnu/libnssutil3.so +7f6301149000-7f630114a000 rw-p 00026000 00:00 420737 /usr/lib/x86_64-linux-gnu/libnssutil3.so +7f630114a000-7f6301249000 r-xp 00000000 00:00 420704 /usr/lib/x86_64-linux-gnu/libnss3.so +7f6301249000-7f6301448000 ---p 000ff000 00:00 420704 /usr/lib/x86_64-linux-gnu/libnss3.so +7f6301448000-7f630144d000 r--p 000fe000 00:00 420704 /usr/lib/x86_64-linux-gnu/libnss3.so +7f630144d000-7f630144f000 rw-p 00103000 00:00 420704 /usr/lib/x86_64-linux-gnu/libnss3.so +7f630144f000-7f6301451000 rw-p 00000000 00:00 0 +7f6301451000-7f6301485000 r-xp 00000000 00:00 419748 /usr/lib/x86_64-linux-gnu/libfontconfig.so.1.4.4 +7f6301485000-7f6301685000 ---p 00034000 00:00 419748 /usr/lib/x86_64-linux-gnu/libfontconfig.so.1.4.4 +7f6301685000-7f6301686000 r--p 00034000 00:00 419748 /usr/lib/x86_64-linux-gnu/libfontconfig.so.1.4.4 +7f6301686000-7f6301687000 rw-p 00035000 00:00 419748 /usr/lib/x86_64-linux-gnu/libfontconfig.so.1.4.4 +7f6301687000-7f630171d000 r-xp 00000000 00:00 420767 /usr/lib/x86_64-linux-gnu/libfreetype.so.6.8.0 +7f630171d000-7f630191c000 ---p 00096000 00:00 420767 /usr/lib/x86_64-linux-gnu/libfreetype.so.6.8.0 +7f630191c000-7f6301922000 r--p 00095000 00:00 420767 /usr/lib/x86_64-linux-gnu/libfreetype.so.6.8.0 +7f6301922000-7f6301923000 rw-p 0009b000 00:00 420767 /usr/lib/x86_64-linux-gnu/libfreetype.so.6.8.0 +7f6301923000-7f6301969000 r-xp 00000000 00:00 419045 /usr/lib/x86_64-linux-gnu/libpango-1.0.so.0.3000.0 +7f6301969000-7f6301b69000 ---p 00046000 00:00 419045 /usr/lib/x86_64-linux-gnu/libpango-1.0.so.0.3000.0 +7f6301b69000-7f6301b6b000 r--p 00046000 00:00 419045 /usr/lib/x86_64-linux-gnu/libpango-1.0.so.0.3000.0 +7f6301b6b000-7f6301b6c000 rw-p 00048000 00:00 419045 /usr/lib/x86_64-linux-gnu/libpango-1.0.so.0.3000.0 +7f6301b6c000-7f6301c25000 r-xp 00000000 00:00 418996 /usr/lib/x86_64-linux-gnu/libcairo.so.2.11000.2 +7f6301c25000-7f6301e24000 ---p 000b9000 00:00 418996 /usr/lib/x86_64-linux-gnu/libcairo.so.2.11000.2 +7f6301e24000-7f6301e26000 r--p 000b8000 00:00 418996 /usr/lib/x86_64-linux-gnu/libcairo.so.2.11000.2 +7f6301e26000-7f6301e27000 rw-p 000ba000 00:00 418996 /usr/lib/x86_64-linux-gnu/libcairo.so.2.11000.2 +7f6301e27000-7f6301e2a000 rw-p 00000000 00:00 0 +7f6301e2a000-7f6301e48000 r-xp 00000000 00:00 419133 /usr/lib/x86_64-linux-gnu/libgdk_pixbuf-2.0.so.0.2600.1 +7f6301e48000-7f6302048000 ---p 0001e000 00:00 419133 /usr/lib/x86_64-linux-gnu/libgdk_pixbuf-2.0.so.0.2600.1 +7f6302048000-7f6302049000 r--p 0001e000 00:00 419133 /usr/lib/x86_64-linux-gnu/libgdk_pixbuf-2.0.so.0.2600.1 +7f6302049000-7f630204a000 rw-p 0001f000 00:00 419133 /usr/lib/x86_64-linux-gnu/libgdk_pixbuf-2.0.so.0.2600.1 +7f630204a000-7f6302055000 r-xp 00000000 00:00 419820 /usr/lib/x86_64-linux-gnu/libpangocairo-1.0.so.0.3000.0 +7f6302055000-7f6302254000 ---p 0000b000 00:00 419820 /usr/lib/x86_64-linux-gnu/libpangocairo-1.0.so.0.3000.0 +7f6302254000-7f6302255000 r--p 0000a000 00:00 419820 /usr/lib/x86_64-linux-gnu/libpangocairo-1.0.so.0.3000.0 +7f6302255000-7f6302256000 rw-p 0000b000 00:00 419820 /usr/lib/x86_64-linux-gnu/libpangocairo-1.0.so.0.3000.0 +7f6302256000-7f630227f000 r-xp 00000000 00:00 419157 /usr/lib/x86_64-linux-gnu/libpangoft2-1.0.so.0.3000.0 +7f630227f000-7f630247e000 ---p 00029000 00:00 419157 /usr/lib/x86_64-linux-gnu/libpangoft2-1.0.so.0.3000.0 +7f630247e000-7f630247f000 r--p 00028000 00:00 419157 /usr/lib/x86_64-linux-gnu/libpangoft2-1.0.so.0.3000.0 +7f630247f000-7f6302480000 rw-p 00029000 00:00 419157 /usr/lib/x86_64-linux-gnu/libpangoft2-1.0.so.0.3000.0 +7f6302480000-7f63025c8000 r-xp 00000000 00:00 419294 /usr/lib/x86_64-linux-gnu/libgio-2.0.so.0.3200.3 +7f63025c8000-7f63027c7000 ---p 00148000 00:00 419294 /usr/lib/x86_64-linux-gnu/libgio-2.0.so.0.3200.3 +7f63027c7000-7f63027cb000 r--p 00147000 00:00 419294 /usr/lib/x86_64-linux-gnu/libgio-2.0.so.0.3200.3 +7f63027cb000-7f63027cd000 rw-p 0014b000 00:00 419294 /usr/lib/x86_64-linux-gnu/libgio-2.0.so.0.3200.3 +7f63027cd000-7f63027cf000 rw-p 00000000 00:00 0 +7f63027cf000-7f63027ee000 r-xp 00000000 00:00 420188 /usr/lib/x86_64-linux-gnu/libatk-1.0.so.0.20409.1 +7f63027ee000-7f63029ee000 ---p 0001f000 00:00 420188 /usr/lib/x86_64-linux-gnu/libatk-1.0.so.0.20409.1 +7f63029ee000-7f63029f0000 r--p 0001f000 00:00 420188 /usr/lib/x86_64-linux-gnu/libatk-1.0.so.0.20409.1 +7f63029f0000-7f63029f1000 rw-p 00021000 00:00 420188 /usr/lib/x86_64-linux-gnu/libatk-1.0.so.0.20409.1 +7f63029f1000-7f6302a9e000 r-xp 00000000 00:00 419013 /usr/lib/x86_64-linux-gnu/libgdk-x11-2.0.so.0.2400.10 +7f6302a9e000-7f6302c9d000 ---p 000ad000 00:00 419013 /usr/lib/x86_64-linux-gnu/libgdk-x11-2.0.so.0.2400.10 +7f6302c9d000-7f6302ca1000 r--p 000ac000 00:00 419013 /usr/lib/x86_64-linux-gnu/libgdk-x11-2.0.so.0.2400.10 +7f6302ca1000-7f6302ca3000 rw-p 000b0000 00:00 419013 /usr/lib/x86_64-linux-gnu/libgdk-x11-2.0.so.0.2400.10 +7f6302ca3000-7f63030d0000 r-xp 00000000 00:00 419385 /usr/lib/x86_64-linux-gnu/libgtk-x11-2.0.so.0.2400.10 +7f63030d0000-7f63032d0000 ---p 0042d000 00:00 419385 /usr/lib/x86_64-linux-gnu/libgtk-x11-2.0.so.0.2400.10 +7f63032d0000-7f63032d7000 r--p 0042d000 00:00 419385 /usr/lib/x86_64-linux-gnu/libgtk-x11-2.0.so.0.2400.10 +7f63032d7000-7f63032db000 rw-p 00434000 00:00 419385 /usr/lib/x86_64-linux-gnu/libgtk-x11-2.0.so.0.2400.10 +7f63032db000-7f63032dd000 rw-p 00000000 00:00 0 +7f63032dd000-7f63032eb000 r-xp 00000000 00:00 419600 /usr/lib/x86_64-linux-gnu/libXi.so.6.1.0 +7f63032eb000-7f63034ea000 ---p 0000e000 00:00 419600 /usr/lib/x86_64-linux-gnu/libXi.so.6.1.0 +7f63034ea000-7f63034eb000 r--p 0000d000 00:00 419600 /usr/lib/x86_64-linux-gnu/libXi.so.6.1.0 +7f63034eb000-7f63034ec000 rw-p 0000e000 00:00 419600 /usr/lib/x86_64-linux-gnu/libXi.so.6.1.0 +7f63034ec000-7f63035de000 r-xp 00000000 00:00 262368 /lib/x86_64-linux-gnu/libglib-2.0.so.0.3200.3 +7f63035de000-7f63037de000 ---p 000f2000 00:00 262368 /lib/x86_64-linux-gnu/libglib-2.0.so.0.3200.3 +7f63037de000-7f63037df000 r--p 000f2000 00:00 262368 /lib/x86_64-linux-gnu/libglib-2.0.so.0.3200.3 +7f63037df000-7f63037e0000 rw-p 000f3000 00:00 262368 /lib/x86_64-linux-gnu/libglib-2.0.so.0.3200.3 +7f63037e0000-7f63037e1000 rw-p 00000000 00:00 0 +7f63037e1000-7f63037e2000 r-xp 00000000 00:00 419135 /usr/lib/x86_64-linux-gnu/libgthread-2.0.so.0.3200.3 +7f63037e2000-7f63039e1000 ---p 00001000 00:00 419135 /usr/lib/x86_64-linux-gnu/libgthread-2.0.so.0.3200.3 +7f63039e1000-7f63039e2000 r--p 00000000 00:00 419135 /usr/lib/x86_64-linux-gnu/libgthread-2.0.so.0.3200.3 +7f63039e2000-7f63039e3000 rw-p 00001000 00:00 419135 /usr/lib/x86_64-linux-gnu/libgthread-2.0.so.0.3200.3 +7f63039e3000-7f6303a30000 r-xp 00000000 00:00 419601 /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.3200.3 +7f6303a30000-7f6303c30000 ---p 0004d000 00:00 419601 /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.3200.3 +7f6303c30000-7f6303c31000 r--p 0004d000 00:00 419601 /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.3200.3 +7f6303c31000-7f6303c32000 rw-p 0004e000 00:00 419601 /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.3200.3 +7f6303c32000-7f6303c35000 r-xp 00000000 00:00 420150 /usr/lib/x86_64-linux-gnu/libgmodule-2.0.so.0.3200.3 +7f6303c35000-7f6303e34000 ---p 00003000 00:00 420150 /usr/lib/x86_64-linux-gnu/libgmodule-2.0.so.0.3200.3 +7f6303e34000-7f6303e35000 r--p 00002000 00:00 420150 /usr/lib/x86_64-linux-gnu/libgmodule-2.0.so.0.3200.3 +7f6303e35000-7f6303e36000 rw-p 00003000 00:00 420150 /usr/lib/x86_64-linux-gnu/libgmodule-2.0.so.0.3200.3 +7f6303e36000-7f6303e38000 r-xp 00000000 00:00 263479 /lib/x86_64-linux-gnu/libdl-2.15.so +7f6303e38000-7f6304038000 ---p 00002000 00:00 263479 /lib/x86_64-linux-gnu/libdl-2.15.so +7f6304038000-7f6304039000 r--p 00002000 00:00 263479 /lib/x86_64-linux-gnu/libdl-2.15.so +7f6304039000-7f630403a000 rw-p 00003000 00:00 263479 /lib/x86_64-linux-gnu/libdl-2.15.so +7f630403a000-7f6304041000 r-xp 00000000 00:00 265235 /lib/x86_64-linux-gnu/librt-2.15.so +7f6304041000-7f6304240000 ---p 00007000 00:00 265235 /lib/x86_64-linux-gnu/librt-2.15.so +7f6304240000-7f6304241000 r--p 00006000 00:00 265235 /lib/x86_64-linux-gnu/librt-2.15.so +7f6304241000-7f6304242000 rw-p 00007000 00:00 265235 /lib/x86_64-linux-gnu/librt-2.15.so +7f6304242000-7f6304252000 r-xp 00000000 00:00 419652 /usr/lib/x86_64-linux-gnu/libXext.so.6.4.0 +7f6304252000-7f6304451000 ---p 00010000 00:00 419652 /usr/lib/x86_64-linux-gnu/libXext.so.6.4.0 +7f6304451000-7f6304452000 r--p 0000f000 00:00 419652 /usr/lib/x86_64-linux-gnu/libXext.so.6.4.0 +7f6304452000-7f6304453000 rw-p 00010000 00:00 419652 /usr/lib/x86_64-linux-gnu/libXext.so.6.4.0 +7f6304453000-7f6304455000 r-xp 00000000 00:00 420132 /usr/lib/x86_64-linux-gnu/libXss.so.1.0.0 +7f6304455000-7f6304655000 ---p 00002000 00:00 420132 /usr/lib/x86_64-linux-gnu/libXss.so.1.0.0 +7f6304655000-7f6304656000 r--p 00002000 00:00 420132 /usr/lib/x86_64-linux-gnu/libXss.so.1.0.0 +7f6304656000-7f6304657000 rw-p 00003000 00:00 420132 /usr/lib/x86_64-linux-gnu/libXss.so.1.0.0 +7f6304657000-7f6304660000 r-xp 00000000 00:00 420211 /usr/lib/x86_64-linux-gnu/libXrender.so.1.3.0 +7f6304660000-7f630485f000 ---p 00009000 00:00 420211 /usr/lib/x86_64-linux-gnu/libXrender.so.1.3.0 +7f630485f000-7f6304860000 r--p 00008000 00:00 420211 /usr/lib/x86_64-linux-gnu/libXrender.so.1.3.0 +7f6304860000-7f6304861000 rw-p 00009000 00:00 420211 /usr/lib/x86_64-linux-gnu/libXrender.so.1.3.0 +7f6304861000-7f6304869000 r-xp 00000000 00:00 419998 /usr/lib/x86_64-linux-gnu/libXrandr.so.2.2.0 +7f6304869000-7f6304a68000 ---p 00008000 00:00 419998 /usr/lib/x86_64-linux-gnu/libXrandr.so.2.2.0 +7f6304a68000-7f6304a69000 r--p 00007000 00:00 419998 /usr/lib/x86_64-linux-gnu/libXrandr.so.2.2.0 +7f6304a69000-7f6304a6a000 rw-p 00008000 00:00 419998 /usr/lib/x86_64-linux-gnu/libXrandr.so.2.2.0 +7f6304a6a000-7f6304a73000 r-xp 00000000 00:00 419293 /usr/lib/x86_64-linux-gnu/libXcursor.so.1.0.2 +7f6304a73000-7f6304c72000 ---p 00009000 00:00 419293 /usr/lib/x86_64-linux-gnu/libXcursor.so.1.0.2 +7f6304c72000-7f6304c73000 r--p 00008000 00:00 419293 /usr/lib/x86_64-linux-gnu/libXcursor.so.1.0.2 +7f6304c73000-7f6304c74000 rw-p 00009000 00:00 419293 /usr/lib/x86_64-linux-gnu/libXcursor.so.1.0.2 +7f6304c74000-7f6304da3000 r-xp 00000000 00:00 419233 /usr/lib/x86_64-linux-gnu/libX11.so.6.3.0 +7f6304da3000-7f6304fa3000 ---p 0012f000 00:00 419233 /usr/lib/x86_64-linux-gnu/libX11.so.6.3.0 +7f6304fa3000-7f6304fa4000 r--p 0012f000 00:00 419233 /usr/lib/x86_64-linux-gnu/libX11.so.6.3.0 +7f6304fa4000-7f6304fa8000 rw-p 00130000 00:00 419233 /usr/lib/x86_64-linux-gnu/libX11.so.6.3.0 +7f6304fa8000-7f6304fca000 r-xp 00000000 00:00 297771 /lib/x86_64-linux-gnu/ld-2.15.so +7f6304fcd000-7f6304fce000 r--- 00000000 00:00 14846136 /run/shm/.org.chromium.Chromium.zsFhgx (deleted) +7f6304fce000-7f6304fdf000 rw-p 00000000 00:00 0 +7f6304fdf000-7f6305107000 rw-p 00000000 00:00 0 +7f6305107000-7f6305108000 ---p 00000000 00:00 0 +7f6305108000-7f6305127000 rw-p 00000000 00:00 0 +7f6305127000-7f6305128000 ---p 00000000 00:00 0 +7f6305128000-7f6305147000 rw-p 00000000 00:00 0 +7f6305147000-7f6305148000 ---p 00000000 00:00 0 +7f6305148000-7f6305167000 rw-p 00000000 00:00 0 +7f6305167000-7f6305168000 ---p 00000000 00:00 0 +7f6305168000-7f63051b1000 rw-p 00000000 00:00 0 +7f63051b1000-7f63051ca000 rw-p 00000000 00:00 0 +7f63051ca000-7f63051cb000 r--p 00022000 00:00 297771 /lib/x86_64-linux-gnu/ld-2.15.so +7f63051cb000-7f63051cd000 rw-p 00023000 00:00 297771 /lib/x86_64-linux-gnu/ld-2.15.so +7f63051cd000-7f630fd06000 r-xp 00000000 00:00 6308662 /home/user/chromium/src/out/Debug/chrome +7f630fd06000-7f631010d000 r--p 0ab38000 00:00 6308662 /home/user/chromium/src/out/Debug/chrome +7f631010d000-7f631014a000 rw-p 0af3f000 00:00 6308662 /home/user/chromium/src/out/Debug/chrome +7f631014a000-7f63101c2000 rw-p 00000000 00:00 0 +7fff9437f000-7fff943a1000 rw-p 00000000 00:00 0 [stack] +7fff943ff000-7fff94400000 r-xp 00000000 00:00 0 [vdso] +ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] diff --git a/tools/deep_memory_profiler/tests/data/heap.01234.symmap/chrome.abcdef.nm b/tools/deep_memory_profiler/tests/data/heap.01234.symmap/chrome.abcdef.nm new file mode 100644 index 0000000000..0f61c8ed9d --- /dev/null +++ b/tools/deep_memory_profiler/tests/data/heap.01234.symmap/chrome.abcdef.nm @@ -0,0 +1,914 @@ +0000000000aa1820 t WebKit::WebSize::WebSize(gfx::Size const&)<0000000000aa1820> +0000000000aa1850 t webkit_glue::WebPreferences::WebPreferences(webkit_glue::WebPreferences const&)<0000000000aa1850> +0000000000aa1880 t WebKit::WebURLRequest::WebURLRequest(WebKit::WebURL const&)<0000000000aa1880> +0000000000aa18b0 t WebKit::WebURLRequest::~WebURLRequest()<0000000000aa18b0> +0000000000aa18d0 t base::Callback::RunnableType, base::internal::FunctorTraits::RunType, void (base::internal::CallbackParamTraits >::StorageType)>::UnboundRunType> base::Bind >(void (printing::PrepareFrameAndViewForPrint::*)(), base::WeakPtr const&)<0000000000aa18d0> +0000000000aa1980 t base::WeakPtrFactory::GetWeakPtr()<0000000000aa1980> +0000000000aa1a90 t base::WeakPtr::~WeakPtr()<0000000000aa1a90> +0000000000aa1ab0 t base::Callback::Run() const<0000000000aa1ab0> +0000000000aa1af0 t content::RenderViewObserverTracker::RenderViewObserverTracker(content::RenderView const*)<0000000000aa1af0> +0000000000aa1b40 t scoped_ptr >::scoped_ptr()<0000000000aa1b40> +0000000000aa1b60 t scoped_ptr >::scoped_ptr()<0000000000aa1b60> +0000000000aa1b80 t scoped_ptr >::~scoped_ptr()<0000000000aa1b80> +0000000000aa1ba0 t scoped_ptr >::~scoped_ptr()<0000000000aa1ba0> +0000000000aa1bc0 t content::RenderViewObserverTracker::~RenderViewObserverTracker()<0000000000aa1bc0> +0000000000aa1c00 t ChromeViewHostMsg_CancelPrerenderForPrinting::ChromeViewHostMsg_CancelPrerenderForPrinting(int)<0000000000aa1c00> +0000000000aa1c30 t bool IPC::Message::Dispatch(IPC::Message const*, printing::PrintWebViewHelper*, printing::PrintWebViewHelper*, void (printing::PrintWebViewHelper::*)())<0000000000aa1c30> +0000000000aa1cd0 t bool PrintMsg_InitiatePrintPreview::Dispatch(IPC::Message const*, printing::PrintWebViewHelper*, printing::PrintWebViewHelper*, void (printing::PrintWebViewHelper::*)(bool))<0000000000aa1cd0> +0000000000aa1d60 t bool PrintMsg_PrintPreview::Dispatch(IPC::Message const*, printing::PrintWebViewHelper*, printing::PrintWebViewHelper*, void (printing::PrintWebViewHelper::*)(base::DictionaryValue const&))<0000000000aa1d60> +00000000017d6130 t IPC::ChannelProxy::Context::OnMessageReceived(IPC::Message const&)<00000000017d6130> +00000000017d6190 t IPC::ChannelProxy::Context::OnMessageReceivedNoFilter(IPC::Message const&)<00000000017d6190> +00000000017d6270 t IPC::ChannelProxy::Context::OnDispatchMessage(IPC::Message const&)<00000000017d6270> +00000000017d6450 t IPC::ChannelProxy::Context::OnChannelConnected(int)<00000000017d6450> +00000000017d65e0 t IPC::ChannelProxy::Context::OnAddFilter()<00000000017d65e0> +00000000017dbaf0 t base::internal::FunctorTraits::RunnableType base::internal::MakeRunnable(void (IPC::ChannelProxy::Context::* const&)(IPC::Message const&))<00000000017dbaf0> +00000000017dbb30 t base::internal::RunnableAdapter::RunnableAdapter(void (IPC::ChannelProxy::Context::*)(IPC::Message const&))<00000000017dbb30> +00000000017dbb80 t base::internal::RunnableAdapter::RunnableAdapter(void (IPC::ChannelProxy::Context::*)(IPC::Message const&))<00000000017dbb80> +00000000017dbbc0 t base::internal::BindState, void (IPC::ChannelProxy::Context*, IPC::Message const&), void (IPC::ChannelProxy::Context*, IPC::Message)>::BindState(base::internal::RunnableAdapter const&, IPC::ChannelProxy::Context* const&, IPC::Message const&)<00000000017dbbc0> +00000000017dbc50 t base::internal::BindState, void (IPC::ChannelProxy::Context*, IPC::Message const&), void (IPC::ChannelProxy::Context*, IPC::Message)>::~BindState()<00000000017dbc50> +00000000017dbc70 t base::internal::BindState, void (IPC::ChannelProxy::Context*, IPC::Message const&), void (IPC::ChannelProxy::Context*, IPC::Message)>::~BindState()<00000000017dbc70> +00000000017dbca0 t base::internal::BindState, void (IPC::ChannelProxy::Context*, IPC::Message const&), void (IPC::ChannelProxy::Context*, IPC::Message)>::~BindState()<00000000017dbca0> +00000000017dbd00 t base::Callback::Callback, void (IPC::ChannelProxy::Context*, IPC::Message const&), void (IPC::ChannelProxy::Context*, IPC::Message)>(base::internal::BindState, void (IPC::ChannelProxy::Context*, IPC::Message const&), void (IPC::ChannelProxy::Context*, IPC::Message)>*)<00000000017dbd00> +00000000017dbd50 t base::internal::Invoker<2, base::internal::BindState, void (IPC::ChannelProxy::Context*, IPC::Message const&), void (IPC::ChannelProxy::Context*, IPC::Message)>, void (IPC::ChannelProxy::Context*, IPC::Message const&)>::Run(base::internal::BindStateBase*)<00000000017dbd50> +00000000017dbde0 t base::internal::UnwrapTraits::Unwrap(IPC::Message const&)<00000000017dbde0> +00000000017dbdf0 t base::internal::InvokeHelper, void (IPC::ChannelProxy::Context* const&, IPC::Message const&)>::MakeItSo(base::internal::RunnableAdapter, IPC::ChannelProxy::Context* const&, IPC::Message const&)<00000000017dbdf0> +00000000017dbe40 t IPC::Message const& base::internal::CallbackForward(IPC::Message const&)<00000000017dbe40> +00000000017dbe50 t base::internal::RunnableAdapter::Run(IPC::ChannelProxy::Context*, IPC::Message const&)<00000000017dbe50> +00000000017dbee0 t std::vector, std::allocator > >::~vector()<00000000017dbee0> +00000000017dbf30 t std::_Vector_base, std::allocator > >::~_Vector_base()<00000000017dbf30> +00000000017dbf70 t std::_Vector_base, std::allocator > >::_Vector_impl::~_Vector_impl()<00000000017dbf70> +00000000017dbf90 t std::_Vector_base, std::allocator > >::_Vector_impl::~_Vector_impl()<00000000017dbf90> +00000000017dbfb0 t std::allocator >::~allocator()<00000000017dbfb0> +00000000017dbfd0 t __gnu_cxx::new_allocator >::~new_allocator()<00000000017dbfd0> +00000000017dbfe0 t std::vector, std::allocator > >::vector()<00000000017dbfe0> +00000000017dc000 t std::_Vector_base, std::allocator > >::_Vector_base()<00000000017dc000> +00000000017dc020 t std::_Vector_base, std::allocator > >::_Vector_impl::_Vector_impl()<00000000017dc020> +00000000017dc040 t std::_Vector_base, std::allocator > >::_Vector_impl::_Vector_impl()<00000000017dc040> +00000000017dc080 t std::allocator >::allocator()<00000000017dc080> +00000000017dc0a0 t __gnu_cxx::new_allocator >::new_allocator()<00000000017dc0a0> +00000000017dc0b0 t scoped_refptr::scoped_refptr(base::SingleThreadTaskRunner*)<00000000017dc0b0> +0000000001bea890 t WebKit::WebFrameImpl::dispatchWillSendRequest(WebKit::WebURLRequest&)<0000000001bea890> +0000000001bea960 t WebKit::WebFrameImpl::createAssociatedURLLoader(WebKit::WebURLLoaderOptions const&)<0000000001bea960> +0000000001bea9f0 t WebKit::WebFrameImpl::commitDocumentData(char const*, unsigned long)<0000000001bea9f0> +0000000001beaa40 t WebKit::WebFrameImpl::unloadListenerCount() const<0000000001beaa40> +0000000001beaa80 t WebKit::WebFrameImpl::isProcessingUserGesture() const<0000000001beaa80> +0000000001beaaa0 t WebKit::WebFrameImpl::consumeUserGesture() const<0000000001beaaa0> +0000000001beaac0 t WebKit::WebFrameImpl::willSuppressOpenerInNewFrame() const<0000000001beaac0> +0000000001beab00 t WebKit::WebFrameImpl::replaceSelection(WebKit::WebString const&)<0000000001beab00> +0000000001beab80 t WebKit::WebFrameImpl::insertText(WebKit::WebString const&)<0000000001beab80> +0000000001beac60 t WebKit::WebFrameImpl::setMarkedText(WebKit::WebString const&, unsigned int, unsigned int)<0000000001beac60> +0000000001beacf0 t WebKit::WebFrameImpl::unmarkText()<0000000001beacf0> +0000000001bead30 t WebKit::WebFrameImpl::hasMarkedText() const<0000000001bead30> +0000000001bf1da0 t WebKit::generateFrameIdentifier()<0000000001bf1da0> +0000000001bf1dc0 t WebKit::WebFrameImpl::~WebFrameImpl()<0000000001bf1dc0> +0000000001bf1df0 t non-virtual thunk to WebKit::WebFrameImpl::~WebFrameImpl()<0000000001bf1df0> +0000000001bf1e20 t WebKit::WebFrameImpl::~WebFrameImpl() +0000000001bf1f50 t non-virtual thunk to WebKit::WebFrameImpl::~WebFrameImpl()<0000000001bf1f50> +0000000001bf1f80 t WebKit::WebFrameImpl::setWebCoreFrame(WebCore::Frame*)<0000000001bf1f80> +0000000001bf2010 t WebKit::WebFrameImpl::initializeAsMainFrame(WebCore::Page*)<0000000001bf2010> +0000000001bf20c0 t WebKit::WebFrameImpl::createChildFrame(WebCore::FrameLoadRequest const&, WebCore::HTMLFrameOwnerElement*)<0000000001bf20c0> +0000000001c21ca0 t WebKit::pageGroupLoadDeferrerStack()<0000000001c21ca0> +0000000001c21d10 t WebKit::WebView::didExitModalLoop()<0000000001c21d10> +0000000001c21de0 t WebKit::WebViewImpl::initializeMainFrame(WebKit::WebFrameClient*)<0000000001c21de0> +0000000001c21e60 t WebKit::WebViewImpl::initializeHelperPluginFrame(WebKit::WebFrameClient*)<0000000001c21e60> +0000000001c21eb0 t WebKit::WebViewImpl::setAutofillClient(WebKit::WebAutofillClient*)<0000000001c21eb0> +0000000001c21ed0 t WebKit::WebViewImpl::setDevToolsAgentClient(WebKit::WebDevToolsAgentClient*)<0000000001c21ed0> +0000000001c21f90 t WebKit::WebViewImpl::setPermissionClient(WebKit::WebPermissionClient*)<0000000001c21f90> +0000000001c21fd0 t WebKit::WebViewImpl::setPrerendererClient(WebKit::WebPrerendererClient*)<0000000001c21fd0> +0000000001c22050 t WebKit::WebViewImpl::setSpellCheckClient(WebKit::WebSpellCheckClient*)<0000000001c22050> +0000000001c22070 t WebKit::WebViewImpl::addTextFieldDecoratorClient(WebKit::WebTextFieldDecoratorClient*)<0000000001c22070> +0000000001c56f90 t WTF::Vector::remove(unsigned long)<0000000001c56f90> +0000000001c57060 t WTF::Vector::operator[](unsigned long)<0000000001c57060> +0000000001c57090 t WTF::Vector::size() const<0000000001c57090> +0000000001c570a0 t WebKit::WebViewImpl::autofillClient()<0000000001c570a0> +0000000001c570b0 t WebKit::AutofillPopupMenuClient::getTextField() const<0000000001c570b0> +0000000001c570e0 t WebCore::PopupMenuStyle::PopupMenuStyle(WebCore::PopupMenuStyle const&)<0000000001c570e0> +0000000001c57110 t WTF::OwnPtr::operator*() const<0000000001c57110> +0000000001c57190 t WTF::PassRefPtr::operator->() const<0000000001c57190> +0000000001c571a0 t WebKit::WebViewImpl::autofillPopupDidHide()<0000000001c571a0> +0000000001c571c0 t WTF::Vector::operator[](unsigned long) const<0000000001c571c0> +0000000001c571f0 t WTF::RefPtr::operator->() const<0000000001c571f0> +0000000001c57200 t WebCore::Document::styleResolver()<0000000001c57200> +0000000001c57260 t WebCore::StyleResolver::fontSelector() const<0000000001c57260> +0000000001c57290 t WTF::RefPtr::operator=(WebCore::HTMLInputElement*)<0000000001c57290> +0000000001c572e0 t WebKit::WebVector::WebVector(unsigned long)<0000000001c572e0> +0000000001c57310 t WebKit::WebVector::operator[](unsigned long)<0000000001c57310> +0000000001c57380 t WebCore::FontDescription::FontDescription()<0000000001c57380> +0000000001c573a0 t WebCore::FontDescription::setComputedSize(float)<0000000001c573a0> +0000000001c573e0 t WebCore::FontDescription::computedSize() const<0000000001c573e0> +0000000001c573f0 t WTF::PassRefPtr::PassRefPtr(WebCore::FontSelector*)<0000000001c573f0> +0000000001c57420 t WTF::PassRefPtr::~PassRefPtr()<0000000001c57420> +0000000001c57440 t WTF::OwnPtr::operator=(WTF::PassOwnPtr const&)<0000000001c57440> +0000000001c574f0 t WTF::PassOwnPtr WTF::adoptPtr(WebCore::PopupMenuStyle*)<0000000001c574f0> +0000000001c57520 t WebCore::PopupMenuStyle::PopupMenuStyle(WebCore::Color const&, WebCore::Color const&, WebCore::Font const&, bool, bool, WebCore::Length, WebCore::TextDirection, bool, WebCore::PopupMenuStyle::PopupMenuType)<0000000001c57520> +0000000001c7b970 t WebKit::FrameLoaderClientImpl::postProgressEstimateChangedNotification()<0000000001c7b970> +0000000001c7ba20 t WebKit::FrameLoaderClientImpl::postProgressFinishedNotification()<0000000001c7ba20> +0000000001c7ba90 t WebKit::FrameLoaderClientImpl::setMainFrameDocumentReady(bool)<0000000001c7ba90> +0000000001c7baa0 t WebKit::FrameLoaderClientImpl::startDownload(WebCore::ResourceRequest const&, WTF::String const&)<0000000001c7baa0> +0000000001c7bb60 t WebKit::FrameLoaderClientImpl::willChangeTitle(WebCore::DocumentLoader*)<0000000001c7bb60> +0000000001c7bb70 t WebKit::FrameLoaderClientImpl::didChangeTitle(WebCore::DocumentLoader*)<0000000001c7bb70> +0000000001c7bb80 t WebKit::FrameLoaderClientImpl::committedLoad(WebCore::DocumentLoader*, char const*, int)<0000000001c7bb80> +0000000001c7bde0 t WebKit::FrameLoaderClientImpl::finishedLoading(WebCore::DocumentLoader*)<0000000001c7bde0> +0000000001c7be60 t WebKit::FrameLoaderClientImpl::updateGlobalHistory()<0000000001c7be60> +0000000001c7be70 t WebKit::FrameLoaderClientImpl::updateGlobalHistoryRedirectLinks()<0000000001c7be70> +0000000001c7be80 t WebKit::FrameLoaderClientImpl::shouldGoToHistoryItem(WebCore::HistoryItem*) const<0000000001c7be80> +0000000001c99d90 t WebCore::PlainTextRange::PlainTextRange(unsigned int, unsigned int)<0000000001c99d90> +0000000001c99dc0 t WebCore::Node::computedStyle(WebCore::PseudoId)<0000000001c99dc0> +0000000001c99df0 t WTF::PassRefPtr WebCore::CSSPrimitiveValue::create(WebCore::EDisplay)<0000000001c99df0> +0000000001c99e60 t WebCore::RenderStyle::display() const<0000000001c99e60> +0000000001c99e80 t WTF::PassRefPtr::operator->() const<0000000001c99e80> +0000000001c99e90 t WTF::PassRefPtr::~PassRefPtr()<0000000001c99e90> +0000000001c99eb0 t WebKit::WebVector::swap(WebKit::WebVector&)<0000000001c99eb0> +0000000001c99f10 t WebKit::WebPrivatePtr::WebPrivatePtr(WTF::PassRefPtr const&)<0000000001c99f10> +0000000001c99f40 t WebKit::WebPrivatePtr::operator=(WTF::PassRefPtr const&)<0000000001c99f40> +0000000001c99f80 t WebKit::WebPrivatePtr::assign(WebCore::AccessibilityObject*)<0000000001c99f80> +0000000001c99fd0 t WTF::PassRefPtr::leakRef() const<0000000001c99fd0> +0000000001c99ff0 t WebKit::WebPrivatePtr::WebPrivatePtr(WTF::PassRefPtr const&)<0000000001c99ff0> +0000000001c9a020 t WTF::PassRefPtr WTF::adoptRef(WebCore::CSSPrimitiveValue*)<0000000001c9a020> +0000000001c9a060 t WTF::RefCounted::operator new(unsigned long)<0000000001c9a060> +0000000001c9a0a0 t WebCore::CSSPrimitiveValue::CSSPrimitiveValue(WebCore::EDisplay)<0000000001c9a0a0> +0000000001c9a0d0 t WebCore::CSSPrimitiveValue::CSSPrimitiveValue(WebCore::EDisplay)<0000000001c9a0d0> +0000000001c9a320 t WebCore::CSSValue::CSSValue(WebCore::CSSValue::ClassType, bool)<0000000001c9a320> +0000000001e0dbc0 t MmapHook(void const*, void const*, unsigned long, int, int, int, long)<0000000001e0dbc0> +0000000001e0dc40 t MremapHook(void const*, void const*, unsigned long, unsigned long, int, void const*)<0000000001e0dc40> +0000000001e0dcb0 t MunmapHook(void const*, unsigned long)<0000000001e0dcb0> +0000000001e0dd00 t SbrkHook(void const*, long)<0000000001e0dd00> +0000000001e0dd50 t ProfilerMalloc(unsigned long)<0000000001e0dd50> +0000000001e0dd80 t ProfilerFree(void*)<0000000001e0dd80> +0000000001e0dda0 t NewHook(void const*, unsigned long)<0000000001e0dda0> +0000000001e0dde0 T DeleteHook(void const*)<0000000001e0dde0> +0000000001e0de10 T IterateAllocatedObjects<0000000001e0de10> +0000000001e0de90 T IsHeapProfilerRunning<0000000001e0de90> +0000000001e0dee0 t HeapProfilerStop<0000000001e0dee0> +0000000001e0ec00 t RecordAlloc(void const*, unsigned long, int)<0000000001e0ec00> +0000000001e0eca0 t MMapProfilerMalloc(unsigned long)<0000000001e0eca0> +0000000001e0ecd0 t MMapProfilerFree(void*)<0000000001e0ecd0> +0000000001e0ecf0 t __cxx_global_var_init<0000000001e0ecf0> +0000000001e0ed60 t __cxx_global_var_init1<0000000001e0ed60> +0000000001e0edd0 t __cxx_global_var_init3<0000000001e0edd0> +0000000001e0ee40 t __cxx_global_var_init5<0000000001e0ee40> +0000000001e0eeb0 t __cxx_global_var_init7<0000000001e0eeb0> +0000000001e0ef20 t __cxx_global_var_init10<0000000001e0ef20> +0000000001e113a0 T MallocHook_AddSbrkHook<0000000001e113a0> +0000000001e113e0 T MallocHook_RemoveSbrkHook<0000000001e113e0> +0000000001e11420 T MallocHook_SetNewHook<0000000001e11420> +0000000001e11460 T MallocHook_SetDeleteHook<0000000001e11460> +0000000001e114a0 T MallocHook_SetPreMmapHook<0000000001e114a0> +0000000001e114e0 T MallocHook_SetMmapHook<0000000001e114e0> +0000000001e11520 T MallocHook_SetMunmapHook<0000000001e11520> +0000000001e11560 T MallocHook_SetMremapHook<0000000001e11560> +0000000001e115a0 T MallocHook_SetPreSbrkHook<0000000001e115a0> +0000000001e115e0 T MallocHook_SetSbrkHook<0000000001e115e0> +0000000001e11620 T MallocHook::InvokeNewHookSlow(void const*, unsigned long)<0000000001e11620> +0000000001e11690 T MallocHook::InvokeDeleteHookSlow(void const*)<0000000001e11690> +0000000001e11700 T MallocHook::InvokePreMmapHookSlow(void const*, unsigned long, int, int, int, long)<0000000001e11700> +0000000001e11790 T MallocHook::InvokeMmapHookSlow(void const*, void const*, unsigned long, int, int, int, long)<0000000001e11790> +0000000001e11830 T MallocHook::InvokeMmapReplacementSlow(void const*, unsigned long, int, int, int, long, void**)<0000000001e11830> +0000000001e118d0 T MallocHook::InvokeMunmapHookSlow(void const*, unsigned long)<0000000001e118d0> +0000000001e11940 T MallocHook::InvokeMunmapReplacementSlow(void const*, unsigned long, int*)<0000000001e11940> +0000000001e14fc0 t MallocHook::GetSbrkHook()<0000000001e14fc0> +0000000001e14fe0 t base::internal::AtomicPtr::Get() const<0000000001e14fe0> +0000000001e15000 t base::internal::HookList::empty() const<0000000001e15000> +0000000001e15030 t MallocHook::GetPreSbrkHook()<0000000001e15030> +0000000001e15050 t base::internal::AtomicPtr::Get() const<0000000001e15050> +0000000001e15070 t base::internal::HookList::empty() const<0000000001e15070> +0000000001e150a0 t MallocHook::GetMremapHook()<0000000001e150a0> +0000000001e150c0 t base::internal::AtomicPtr::Get() const<0000000001e150c0> +0000000001e150e0 t MallocHook::GetMunmapHook()<0000000001e150e0> +0000000001e15100 t base::internal::AtomicPtr::Get() const<0000000001e15100> +0000000001e15120 t base::internal::HookList::empty() const<0000000001e15120> +0000000001e15150 t MallocHook::GetMmapHook()<0000000001e15150> +0000000001e15170 t base::internal::AtomicPtr::Get() const<0000000001e15170> +0000000001e15190 t base::internal::HookList::empty() const<0000000001e15190> +0000000001e151c0 t MallocHook::GetPreMmapHook()<0000000001e151c0> +0000000001e151e0 t base::internal::AtomicPtr::Get() const<0000000001e151e0> +0000000001e15200 t MallocHook::RemovePreMmapHook(void (*)(void const*, unsigned long, int, int, int, long))<0000000001e15200> +0000000001e15230 t MallocHook::RemovePreSbrkHook(void (*)(long))<0000000001e15230> +0000000001e15260 t MallocHook::InvokeNewHook(void const*, unsigned long)<0000000001e15260> +0000000001e152c0 T MallocHook::GetNewHook()<0000000001e152c0> +0000000001e152e0 T perftools_pthread_key_create(unsigned int*, void (*)(void*))<0000000001e152e0> +0000000001e15380 T perftools_pthread_getspecific(unsigned int)<0000000001e15380> +0000000001e153d0 T perftools_pthread_setspecific(unsigned int, void*)<0000000001e153d0> +0000000001e15430 T perftools_pthread_once(int*, void (*)())<0000000001e15430> +0000000001e154b0 T MemoryRegionMap::Init(int)<0000000001e154b0> +0000000001e233b0 T DoAllocWithArena(unsigned long, LowLevelAlloc::Arena*)<0000000001e233b0> +0000000001e237b0 T LowLevelAlloc::DefaultArena()<0000000001e237b0> +0000000001e237c0 t LowLevelAlloc::GetSizeOfUnhookedArena()<0000000001e237c0> +0000000001e237e0 t RoundUp(long, long)<0000000001e237e0> +0000000001e23820 t LLA_SkiplistLevels(unsigned long, unsigned long, bool)<0000000001e23820> +0000000001e2f0c0 T HeapProfileTable::DeallocateBucketTable(HeapProfileTable::Bucket**)<0000000001e2f0c0> +0000000001e2f190 T HeapProfileTable::DeallocateAllocationMap(AddressMap*)<0000000001e2f190> +0000000001e2f1f0 T HeapProfileTable::GetBucket(int, void const* const*, HeapProfileTable::Bucket**, int*)<0000000001e2f1f0> +0000000001e2f440 T HeapProfileTable::GetCallerStackTrace(int, void**)<0000000001e2f440> +0000000001e2f480 T HeapProfileTable::RecordAlloc(void const*, unsigned long, int, void const* const*)<0000000001e2f480> +0000000001e2f560 T HeapProfileTable::RecordFree(void const*)<0000000001e2f560> +0000000001e2f5f0 T HeapProfileTable::FindAlloc(void const*, unsigned long*) const<0000000001e2f5f0> +0000000001e2f650 T HeapProfileTable::FindAllocDetails(void const*, HeapProfileTable::AllocInfo*) const<0000000001e2f650> +0000000002037650 t MessageLoop::PostNonNestableDelayedTask(tracked_objects::Location const&, base::Callback const&, base::TimeDelta)<0000000002037650> +00000000020377f0 t MessageLoop::Run()<00000000020377f0> +0000000002037820 t MessageLoop::RunUntilIdle()<0000000002037820> +0000000002037850 t MessageLoop::QuitWhenIdle()<0000000002037850> +0000000002037990 t MessageLoop::QuitNow()<0000000002037990> +0000000002037ae0 t MessageLoop::IsType(MessageLoop::Type) const<0000000002037ae0> +0000000002037b00 t MessageLoop::QuitWhenIdleClosure()<0000000002037b00> +00000000020380d0 t MessageLoop::StartHistogrammer()<00000000020380d0> +0000000002038230 t MessageLoop::ProcessNextDelayedNonNestableTask()<0000000002038230> +00000000020388e0 t MessageLoop::DeferOrRunPendingTask(base::PendingTask const&)<00000000020388e0> +0000000002042120 t void base::subtle::DeleteHelperInternal::DeleteViaSequencedTaskRunner(MessageLoop*, tracked_objects::Location const&, base::MessageLoopProxyImpl const*)<0000000002042120> +0000000002042160 t base::DeleteHelper::DoDelete(void const*)<0000000002042160> +00000000020421a0 t base::ThreadRestrictions::ScopedAllowSingleton::~ScopedAllowSingleton()<00000000020421a0> +00000000020421d0 t base::ThreadRestrictions::ScopedAllowSingleton::ScopedAllowSingleton()<00000000020421d0> +0000000002042200 t base::MessagePump::MessagePump()<0000000002042200> +0000000002042240 t base::MessagePump::~MessagePump()<0000000002042240> +0000000002042270 t base::MessagePump::~MessagePump() +00000000020422a0 t base::RefCountedThreadSafe >::RefCountedThreadSafe()<00000000020422a0> +00000000020422c0 t base::RefCountedThreadSafe >::~RefCountedThreadSafe()<00000000020422c0> +00000000020422e0 t base::MessagePumpDefault::MessagePumpDefault() +0000000002042350 t base::MessagePumpDefault::Run(base::MessagePump::Delegate*)<0000000002042350> +0000000002042620 t base::MessagePumpDefault::Quit()<0000000002042620> +0000000002042630 t base::MessagePumpDefault::ScheduleWork()<0000000002042630> +0000000002042660 t base::MessagePumpDefault::ScheduleDelayedWork(base::TimeTicks const&)<0000000002042660> +00000000020426b0 t base::MessagePumpDefault::~MessagePumpDefault()<00000000020426b0> +00000000020426d0 t base::MessagePumpDefault::~MessagePumpDefault()<00000000020426d0> +0000000002042700 t base::MessagePumpDefault::~MessagePumpDefault()<0000000002042700> +0000000002068570 t base::FileDescriptorTableInjection::FileDescriptorTableInjection()<0000000002068570> +0000000002068590 t base::FileDescriptorTableInjection::~FileDescriptorTableInjection()<0000000002068590> +00000000020685b0 t base::FileDescriptorTableInjection::~FileDescriptorTableInjection()<00000000020685b0> +00000000020685d0 t base::InjectionDelegate::~InjectionDelegate()<00000000020685d0> +00000000020685e0 t base::FileDescriptorTableInjection::FileDescriptorTableInjection()<00000000020685e0> +0000000002068620 t base::InjectionDelegate::InjectionDelegate()<0000000002068620> +0000000002068640 t base::InjectionDelegate::~InjectionDelegate()<0000000002068640> +0000000002068660 t base::InjectionDelegate::~InjectionDelegate()<0000000002068660> +0000000002068690 t base::InjectionArc::InjectionArc(int, int, bool)<0000000002068690> +00000000020686d0 t base::DirReaderLinux::~DirReaderLinux()<00000000020686d0> +0000000002068780 t base::DirReaderLinux::DirReaderLinux(char const*)<0000000002068780> +00000000020687f0 t tracked_objects::ScopedProfile::ScopedProfile(tracked_objects::Location const&) +0000000002068830 t tracked_objects::ScopedProfile::~ScopedProfile() +0000000002068850 t tracked_objects::ScopedProfile::StopClockAndTally()<0000000002068850> +00000000020688c0 t tracked_objects::SetAlternateTimeSource(unsigned int (*)(), tracked_objects::TimeSourceType)<00000000020688c0> +0000000002068970 t tracked_objects::GetAlternateTimeSource()<0000000002068970> +0000000002068980 t tracked_objects::GetTimeSourceType()<0000000002068980> +0000000002068990 t std::basic_string, std::allocator >* logging::CheckEQImpl(unsigned int (* const&)(), unsigned int (* const&)(), char const*)<0000000002068990> +00000000020689f0 t std::basic_string, std::allocator >* logging::MakeCheckOpString(unsigned int (* const&)(), unsigned int (* const&)(), char const*)<00000000020689f0> +0000000002068b30 t base::RandInt(int, int)<0000000002068b30> +0000000002069470 t (anonymous namespace)::URandomFd::URandomFd() +0000000002069540 t base::RunLoop::RunLoop() +00000000020695b0 t base::RunLoop::RunLoop(base::MessagePumpDispatcher*) +0000000002069620 t base::RunLoop::~RunLoop() +0000000002069650 t base::RunLoop::Run()<0000000002069650> +00000000020696a0 t base::RunLoop::BeforeRun()<00000000020696a0> +00000000020697f0 t base::RunLoop::AfterRun()<00000000020697f0> +0000000002069850 t base::RunLoop::RunUntilIdle()<0000000002069850> +0000000002069870 t base::RunLoop::Quit()<0000000002069870> +00000000020698c0 t base::RunLoop::QuitClosure()<00000000020698c0> +0000000002069930 t base::WeakPtrFactory::WeakPtrFactory(base::RunLoop*)<0000000002069930> +0000000002069960 t base::WeakPtrFactory::~WeakPtrFactory()<0000000002069960> +0000000002069980 t base::Callback::RunnableType, base::internal::FunctorTraits::RunType, void (base::internal::CallbackParamTraits >::StorageType)>::UnboundRunType> base::Bind >(void (base::RunLoop::*)(), base::WeakPtr const&)<0000000002069980> +00000000020b7a00 t tracked_objects::ThreadData::SnapshotAllExecutedTasks(bool, tracked_objects::ProcessDataSnapshot*, std::map, std::allocator > >*)<00000000020b7a00> +00000000020b7a70 t tracked_objects::ThreadData::TallyABirth(tracked_objects::Location const&)<00000000020b7a70> +00000000020b7ba0 t tracked_objects::ThreadData::TallyADeath(tracked_objects::Births const&, int, int)<00000000020b7ba0> +00000000020b7cf0 t tracked_objects::ThreadData::TallyABirthIfActive(tracked_objects::Location const&)<00000000020b7cf0> +00000000020b7d60 t tracked_objects::ThreadData::TrackingStatus()<00000000020b7d60> +00000000020b7d80 t tracked_objects::ThreadData::TallyRunOnNamedThreadIfTracking(base::TrackingInfo const&, tracked_objects::TrackedTime const&, tracked_objects::TrackedTime const&)<00000000020b7d80> +00000000020b7eb0 t tracked_objects::ThreadData::TallyRunOnWorkerThreadIfTracking(tracked_objects::Births const*, tracked_objects::TrackedTime const&, tracked_objects::TrackedTime const&, tracked_objects::TrackedTime const&)<00000000020b7eb0> +00000000020b7f80 t tracked_objects::ThreadData::TallyRunInAScopedRegionIfTracking(tracked_objects::Births const*, tracked_objects::TrackedTime const&, tracked_objects::TrackedTime const&)<00000000020b7f80> +00000000020b9330 t std::_Rb_tree_iterator >::operator->() const<00000000020b9330> +00000000020b9360 t std::map, std::allocator > >::operator[](tracked_objects::Location const&)<00000000020b9360> +00000000020b9450 t std::map, std::allocator > >::find(tracked_objects::Births const* const&)<00000000020b9450> +00000000020b9480 t std::_Rb_tree_iterator >::operator!=(std::_Rb_tree_iterator > const&) const<00000000020b9480> +00000000020b94b0 t std::map, std::allocator > >::end()<00000000020b94b0> +00000000020b94e0 t std::_Rb_tree_iterator >::operator->() const<00000000020b94e0> +00000000020b9510 t std::map, std::allocator > >::operator[](tracked_objects::Births const* const&)<00000000020b9510> +00000000020b9640 t std::_Rb_tree_const_iterator >::_Rb_tree_const_iterator(std::_Rb_tree_iterator > const&)<00000000020b9640> +00000000020b9670 t std::map, std::allocator > >::begin()<00000000020b9670> +00000000020b96a0 t std::_Rb_tree_const_iterator >::operator!=(std::_Rb_tree_const_iterator > const&) const<00000000020b96a0> +00000000020b96d0 t std::_Rb_tree_const_iterator >::operator->() const<00000000020b96d0> +00000000020b9700 t std::map, std::allocator > >::operator[](tracked_objects::BirthOnThread const* const&)<00000000020b9700> +00000000020b97e0 t std::_Rb_tree_const_iterator >::operator++()<00000000020b97e0> +00000000020b9810 t std::_Rb_tree_const_iterator >::_Rb_tree_const_iterator(std::_Rb_tree_iterator > const&)<00000000020b9810> +00000000020b9840 t std::map, std::allocator > >::begin()<00000000020b9840> +00000000020b9870 t std::_Rb_tree_const_iterator >::operator!=(std::_Rb_tree_const_iterator > const&) const<00000000020b9870> +00000000020b98a0 t std::_Rb_tree_const_iterator >::operator->() const<00000000020b98a0> +00000000020b98d0 t std::_Rb_tree_const_iterator >::operator++()<00000000020b98d0> +00000000020b9900 t std::_Rb_tree_iterator >::operator++()<00000000020b9900> +00000000020bb1d0 t std::_Rb_tree, std::_Select1st >, std::less, std::allocator > >::_S_value(std::_Rb_tree_node_base const*)<00000000020bb1d0> +00000000020bb1f0 t std::_Rb_tree, std::_Select1st >, std::less, std::allocator > >::key_comp() const<00000000020bb1f0> +00000000020bb200 W std::_Rb_tree, std::_Select1st >, std::less, std::allocator > >::lower_bound(tracked_objects::BirthOnThread const* const&)<00000000020bb200> +00000000020bb250 W std::_Rb_tree, std::_Select1st >, std::less, std::allocator > >::_M_lower_bound(std::_Rb_tree_node >*, std::_Rb_tree_node >*, tracked_objects::BirthOnThread const* const&)<00000000020bb250> +00000000020bb2f0 t std::pair const* std::__addressof const>(std::pair const&)<00000000020bb2f0> +00000000020bb300 t std::_Rb_tree_const_iterator >::_Rb_tree_const_iterator(std::_Rb_tree_iterator > const&)<00000000020bb300> +00000000020bb320 t std::_Rb_tree, std::_Select1st >, std::less, std::allocator > >::begin()<00000000020bb320> +00000000020bb360 t std::_Rb_tree_iterator >::_Rb_tree_iterator(std::_Rb_tree_node >*)<00000000020bb360> +00000000020bb390 t std::_Rb_tree_iterator >::_Rb_tree_iterator(std::_Rb_tree_node >*)<00000000020bb390> +00000000020bb3b0 t std::map, std::allocator > >::lower_bound(tracked_objects::Births const* const&)<00000000020bb3b0> +00000000020bb3e0 t std::_Rb_tree_iterator >::operator==(std::_Rb_tree_iterator > const&) const<00000000020bb3e0> +00000000020bb410 t std::map, std::allocator > >::key_comp() const<00000000020bb410> +00000000020bb430 t std::less::operator()(tracked_objects::Births const* const&, tracked_objects::Births const* const&) const<00000000020bb430> +00000000020bb460 t std::_Rb_tree_iterator >::operator*() const<00000000020bb460> +00000000020bb480 t std::map, std::allocator > >::insert(std::_Rb_tree_iterator >, std::pair const&)<00000000020bb480> +00000000020bb4d0 t std::pair::pair(tracked_objects::Births const* const&, tracked_objects::DeathData const&)<00000000020bb4d0> +00000000020bb500 W std::pair::pair(tracked_objects::Births const* const&, tracked_objects::DeathData const&)<00000000020bb500> +00000000020bb550 t std::_Rb_tree, std::_Select1st >, std::less, std::allocator > >::_M_insert_unique_(std::_Rb_tree_const_iterator >, std::pair const&)<00000000020bb550> +0000000002126100 t google::protobuf::RepeatedPtrField::RepeatedPtrField()<0000000002126100> +0000000002126120 t google::protobuf::RepeatedField::CopyArray(int*, int const*, int)<0000000002126120> +0000000002126160 t google::protobuf::RepeatedField::~RepeatedField()<0000000002126160> +00000000021261e0 t google::protobuf::RepeatedField::RepeatedField()<00000000021261e0> +0000000002126210 t void google::protobuf::internal::RepeatedPtrFieldBase::Clear::TypeHandler>()<0000000002126210> +0000000002126280 t google::protobuf::internal::GenericTypeHandler::Clear(chrome_variations::Study_Experiment*)<0000000002126280> +00000000021262a0 t chrome_variations::Study::set_has_default_experiment_name()<00000000021262a0> +00000000021262c0 t chrome_variations::Study::set_has_name()<00000000021262c0> +00000000021262e0 t chrome_variations::Study::set_has_filter()<00000000021262e0> +0000000002126300 t google::protobuf::RepeatedPtrField::Add()<0000000002126300> +0000000002126320 t chrome_variations::Study::set_has_consistency()<0000000002126320> +0000000002126340 t chrome_variations::Study_Filter::set_has_max_version()<0000000002126340> +0000000002126360 t chrome_variations::Study_Filter::set_has_min_version()<0000000002126360> +0000000002126380 t chrome_variations::Study_Experiment::set_has_name()<0000000002126380> +00000000021263a0 t __op_new_intercept__(void*, unsigned long, std::type_info const&)<00000000021263a0> +00000000021263e0 t __op_delete_intercept__(void*, unsigned long, std::type_info const&)<00000000021263e0> +0000000002126420 t base::type_profiler::InterceptFunctions::SetFunctions(void* (*)(void*, unsigned long, std::type_info const&), void* (*)(void*, unsigned long, std::type_info const&))<0000000002126420> +00000000021264c0 t (anonymous namespace)::NopIntercept(void*, unsigned long, std::type_info const&)<00000000021264c0> +00000000021264e0 t base::type_profiler::InterceptFunctions::ResetFunctions()<00000000021264e0> +0000000002126500 t base::type_profiler::InterceptFunctions::IsAvailable()<0000000002126500> +0000000002126540 t LibSpeechdLoader::LibSpeechdLoader() +0000000002126550 t LibSpeechdLoader::~LibSpeechdLoader() +0000000002126580 t LibSpeechdLoader::CleanUp(bool)<0000000002126580> +0000000002126610 t LibSpeechdLoader::Load(std::basic_string, std::allocator > const&)<0000000002126610> +00000000022d3c90 t WebCore::HTMLDocumentParser::stopBackgroundParser()<00000000022d3c90> +00000000022d3dd0 t WebCore::HTMLDocumentParser::stopParsing()<00000000022d3dd0> +00000000022d3e30 t WebCore::HTMLDocumentParser::prepareToStopParsing()<00000000022d3e30> +00000000022d4000 t WebCore::HTMLDocumentParser::pumpTokenizerIfPossible(WebCore::HTMLDocumentParser::SynchronousMode)<00000000022d4000> +00000000022d40d0 t WebCore::HTMLDocumentParser::attemptToRunDeferredScriptsAndEnd()<00000000022d40d0> +00000000022d4200 t WebCore::HTMLDocumentParser::isParsingFragment() const<00000000022d4200> +00000000022d4230 t WebCore::HTMLDocumentParser::processingData() const<00000000022d4230> +00000000022d62a0 t WebCore::HTMLDocumentParser::constructTreeFromHTMLToken(WebCore::HTMLToken&)<00000000022d62a0> +00000000022d63d0 t WebCore::HTMLDocumentParser::hasInsertionPoint()<00000000022d63d0> +00000000022d6450 t WebCore::HTMLDocumentParser::insert(WebCore::SegmentedString const&)<00000000022d6450> +00000000022d6c30 t WebCore::HTMLDocumentParser::attemptToEnd()<00000000022d6c30> +00000000022d6c80 t WebCore::HTMLDocumentParser::finish()<00000000022d6c80> +00000000022fca50 t WebCore::HTMLTreeBuilder::~HTMLTreeBuilder() +00000000022fcad0 t WebCore::HTMLTreeBuilder::detach()<00000000022fcad0> +00000000022fcb00 t WebCore::HTMLTreeBuilder::FragmentParsingContext::FragmentParsingContext() +00000000022fcb30 t WebCore::HTMLTreeBuilder::FragmentParsingContext::FragmentParsingContext(WebCore::DocumentFragment*, WebCore::Element*, WebCore::FragmentScriptingPermission) +00000000022fcbd0 t WebCore::HTMLTreeBuilder::FragmentParsingContext::~FragmentParsingContext() +00000000022fcbe0 t WebCore::HTMLTreeBuilder::takeScriptToProcess(WTF::TextPosition&)<00000000022fcbe0> +00000000022fccb0 t WebCore::HTMLTreeBuilder::constructTree(WebCore::AtomicHTMLToken*)<00000000022fccb0> +00000000022fce40 t WebCore::HTMLTreeBuilder::processTokenInForeignContent(WebCore::AtomicHTMLToken*)<00000000022fce40> +00000000022fd890 t WebCore::HTMLTreeBuilder::processToken(WebCore::AtomicHTMLToken*)<00000000022fd890> +00000000022fd9e0 t WebCore::HTMLTreeBuilder::processDoctypeToken(WebCore::AtomicHTMLToken*)<00000000022fd9e0> +00000000022fdad0 t WebCore::HTMLTreeBuilder::processStartTag(WebCore::AtomicHTMLToken*)<00000000022fdad0> +0000000002301910 t WebCore::HTMLTreeBuilder::processComment(WebCore::AtomicHTMLToken*)<0000000002301910> +0000000002301a70 t WebCore::HTMLTreeBuilder::processCharacter(WebCore::AtomicHTMLToken*)<0000000002301a70> +0000000002301b10 t WebCore::HTMLTreeBuilder::processEndOfFile(WebCore::AtomicHTMLToken*)<0000000002301b10> +0000000002306f60 t WebCore::HTMLTreeBuilder::processTableEndTagForInTable()<0000000002306f60> +0000000002307050 t WebCore::HTMLTreeBuilder::defaultForInitial()<0000000002307050> +00000000023070d0 t WebCore::HTMLTreeBuilder::defaultForBeforeHTML()<00000000023070d0> +0000000002307180 t WebCore::HTMLTreeBuilder::defaultForBeforeHead()<0000000002307180> +0000000002307210 t WebCore::HTMLTreeBuilder::defaultForInHead()<0000000002307210> +00000000023072a0 t WebCore::HTMLTreeBuilder::defaultForAfterHead()<00000000023072a0> +0000000002307330 t WebCore::HTMLTreeBuilder::processCaptionEndTagForInCaption()<0000000002307330> +0000000002346e20 t WebCore::HTMLConstructionSite::shouldFosterParent() const<0000000002346e20> +0000000002346e90 t WebCore::HTMLConstructionSite::fosterParent(WTF::PassRefPtr)<0000000002346e90> +0000000002346f60 t WebCore::HTMLConstructionSite::executeQueuedTasks()<0000000002346f60> +0000000002347030 t WebCore::executeTask(WebCore::HTMLConstructionSiteTask&)<0000000002347030> +0000000002347240 t WebCore::HTMLConstructionSite::HTMLConstructionSite(WebCore::Document*, unsigned int) +00000000023479f0 t WebCore::HTMLConstructionSite::setDefaultCompatibilityMode()<00000000023479f0> +0000000002347a50 t WebCore::HTMLConstructionSite::setCompatibilityMode(WebCore::Document::CompatibilityMode)<0000000002347a50> +0000000002347a90 t WebCore::HTMLConstructionSite::setCompatibilityModeFromDoctype(WTF::String const&, WTF::String const&, WTF::String const&)<0000000002347a90> +0000000002349a20 t WebCore::HTMLConstructionSite::findFosterSite(WebCore::HTMLConstructionSiteTask&)<0000000002349a20> +0000000002349c20 t WebCore::isAllWhitespace(WTF::String const&)<0000000002349c20> +0000000002349c40 t WebCore::HTMLConstructionSite::createElementFromSavedToken(WebCore::HTMLStackItem*)<0000000002349c40> +000000000246f060 t std::_Vector_base >::_Vector_impl::~_Vector_impl()<000000000246f060> +000000000246f080 t std::_Vector_base >::_Vector_impl::~_Vector_impl()<000000000246f080> +000000000246f0a0 t std::allocator::~allocator()<000000000246f0a0> +000000000246f0c0 t __gnu_cxx::new_allocator::~new_allocator()<000000000246f0c0> +000000000246f0d0 t std::pair::~pair()<000000000246f0d0> +000000000246f100 t std::pair::~pair()<000000000246f100> +000000000246f130 t std::map, std::allocator > >::~map()<000000000246f130> +000000000246f150 t std::_Rb_tree, std::_Select1st >, std::less, std::allocator > >::~_Rb_tree()<000000000246f150> +000000000246f170 t std::_Rb_tree, std::_Select1st >, std::less, std::allocator > >::~_Rb_tree()<000000000246f170> +000000000246f1b0 t std::_Rb_tree, std::_Select1st >, std::less, std::allocator > >::_Rb_tree_impl, false>::~_Rb_tree_impl()<000000000246f1b0> +000000000246f1d0 t std::_Rb_tree, std::_Select1st >, std::less, std::allocator > >::_Rb_tree_impl, false>::~_Rb_tree_impl()<000000000246f1d0> +000000000246f1f0 t std::allocator > >::~allocator()<000000000246f1f0> +000000000246f210 t __gnu_cxx::new_allocator > >::~new_allocator()<000000000246f210> +000000000246f220 t gpu::gles2::CachedProgramInfoManager::ProgramInfo::UniformInfo::~UniformInfo()<000000000246f220> +000000000246f260 t gpu::gles2::CachedProgramInfoManager::ProgramInfo::VertexAttribInfo::~VertexAttribInfo()<000000000246f260> +000000000246f290 t gpu::gles2::CachedProgramInfoManager::ProgramInfo::VertexAttribInfo::VertexAttribInfo(int, unsigned int, std::basic_string, std::allocator > const&, int)<000000000246f290> +000000000246f2e0 t InsertType(void*, unsigned long, std::type_info const&)<000000000246f2e0> +000000000246f360 t (anonymous namespace)::InitializeTypeProfilerMemory()<000000000246f360> +000000000246f450 t AddressMap<(anonymous namespace)::ObjectInfo>::Insert(void const*, (anonymous namespace)::ObjectInfo)<000000000246f450> +000000000246f5f0 t EraseType(void*)<000000000246f5f0> +000000000246fd30 t global constructors keyed to a<000000000246fd30> +000000000246fd40 t GURL::GURL() +000000000246fd80 t GURL::GURL(GURL const&) +00000000031755b0 t WebCore::ScriptController::clearForOutOfMemory()<00000000031755b0> +00000000031755d0 t WebCore::ScriptController::clearForClose()<00000000031755d0> +0000000003175640 t WebCore::ScriptController::updateSecurityOrigin()<0000000003175640> +0000000003175670 t WebCore::ScriptController::updatePlatformScriptObjects()<0000000003175670> +00000000031756d0 t WebCore::ScriptController::processingUserGesture()<00000000031756d0> +00000000031756e0 t WebCore::ScriptController::callFunction(v8::Handle, v8::Handle, int, v8::Handle*)<00000000031756e0> +00000000031757d0 t WebCore::ScriptController::callFunctionWithInstrumentation(WebCore::ScriptExecutionContext*, v8::Handle, v8::Handle, int, v8::Handle*)<00000000031757d0> +0000000003175a80 t WebCore::resourceInfo(v8::Handle, WTF::String&, int&)<0000000003175a80> +00000000031c3b90 t WebCore::V8LazyEventListener::prepareListenerObject(WebCore::ScriptExecutionContext*)<00000000031c3b90> +000000000321a0c0 t WebCore::CSSParser::parseSheet(WebCore::StyleSheetContents*, WTF::String const&, int, WTF::Vector, 0ul>*)<000000000321a0c0> +000000000321a220 t WebCore::CSSParser::parseRule(WebCore::StyleSheetContents*, WTF::String const&)<000000000321a220> +000000000321a2b0 t WebCore::CSSParser::parseKeyframeRule(WebCore::StyleSheetContents*, WTF::String const&)<000000000321a2b0> +000000000321a330 t WebCore::CSSParser::parseFontFaceValue(WTF::AtomicString const&)<000000000321a330> +000000000321a450 t WebCore::CSSParser::parseValue(WebCore::StylePropertySet*, WebCore::CSSPropertyID, WTF::String const&, bool, WebCore::CSSParserMode, WebCore::StyleSheetContents*)<000000000321a450> +000000000327e070 t void WTF::Vector::insert(unsigned long, WebCore::CSSParserValue const&)<000000000327e070> +000000000327e220 t WTF::Vector::remove(unsigned long)<000000000327e220> +000000000327e2f0 t WebCore::CSSPrimitiveValue::createIdentifier(int)<000000000327e2f0> +000000000327e360 t WebCore::CSSPrimitiveValue::setPrimitiveType(unsigned short)<000000000327e360> +000000000327e3b0 t WTF::PassRefPtr::PassRefPtr(WTF::PassRefPtr const&)<000000000327e3b0> +000000000327e3e0 t WebCore::CSSFunctionValue::create(WebCore::CSSParserFunction*)<000000000327e3e0> +000000000327e450 t WTF::PassRefPtr::~PassRefPtr()<000000000327e450> +000000000327e470 t WebCore::CSSPrimitiveValue::create(double, WebCore::CSSPrimitiveValue::UnitTypes)<000000000327e470> +000000000327e4f0 t WTF::OwnPtr::OwnPtr(WTF::PassOwnPtr const&)<000000000327e4f0> +000000000327e520 t WTF::PassOwnPtr WTF::adoptPtr(WebCore::CSSSelector*)<000000000327e520> +000000000327e550 t WebCore::CSSSelector* WTF::fastNew()<000000000327e550> +000000000327e610 t WTF::PassOwnPtr::~PassOwnPtr()<000000000327e610> +00000000032a2b80 t WebCore::CSSValuePool::createIdentifierValue(int)<00000000032a2b80> +00000000032a2c70 t WebCore::CSSValuePool::createColorValue(unsigned int)<00000000032a2c70> +00000000032a2e20 t WebCore::CSSValuePool::createValue(double, WebCore::CSSPrimitiveValue::UnitTypes)<00000000032a2e20> +00000000032d7bb0 t WebCore::StylePropertyShorthand::StylePropertyShorthand(WebCore::CSSPropertyID const*, WebCore::StylePropertyShorthand const**, unsigned int)<00000000032d7bb0> +00000000032d7bf0 t WebCore::StylePropertyShorthand::StylePropertyShorthand()<00000000032d7bf0> +00000000032d7c10 t WebCore::StylePropertyShorthand::StylePropertyShorthand()<00000000032d7c10> +00000000032d7c40 t WebCore::StylePropertyShorthand::StylePropertyShorthand(WebCore::CSSPropertyID const*, WebCore::StylePropertyShorthand const**, unsigned int)<00000000032d7c40> +00000000032d7c80 t WebCore::StyleResolver::StyleResolver(WebCore::Document*, bool) +00000000032d8ae0 t WebCore::StyleResolver::appendAuthorStyleSheets(unsigned int, WTF::Vector, 0ul> const&)<00000000032d8ae0> +00000000032d8c10 t WebCore::StyleResolver::pushParentElement(WebCore::Element*)<00000000032d8c10> +0000000003341c70 t WebCore::StyleSheetContents::parseString(WTF::String const&)<0000000003341c70> +0000000003341ca0 t WebCore::StyleSheetContents::parseStringAtLine(WTF::String const&, int)<0000000003341ca0> +0000000003341d50 t WebCore::StyleSheetContents::isLoading() const<0000000003341d50> +0000000003341e00 t WebCore::StyleSheetContents::checkLoaded()<0000000003341e00> +0000000003341f90 t WebCore::StyleSheetContents::parentStyleSheet() const<0000000003341f90> +00000000034e56b0 t WebCore::DocumentLoader::commitData(char const*, unsigned long)<00000000034e56b0> +00000000034e5980 t WebCore::DocumentLoader::commitLoad(char const*, int)<00000000034e5980> +00000000034e5a90 t WebCore::DocumentLoader::documentURL() const<00000000034e5a90> +00000000034e5b60 t WebCore::DocumentLoader::isMultipartReplacingLoad() const<00000000034e5b60> +00000000034e5bb0 t WebCore::DocumentLoader::reportMemoryUsage(WTF::MemoryObjectInfo*) const<00000000034e5bb0> +00000000035008b0 t WebCore::canReferToParentFrameEncoding(WebCore::Frame const*, WebCore::Frame const*)<00000000035008b0> +0000000003500930 t WebCore::DocumentWriter::reportDataReceived()<0000000003500930> +0000000003500a20 t WebCore::DocumentWriter::addData(char const*, unsigned long)<0000000003500a20> +0000000003500b30 t WebCore::DocumentWriter::setEncoding(WTF::String const&, bool)<0000000003500b30> +0000000003500b80 t WebCore::DocumentWriter::setDocumentWasLoadedAsPartOfNavigation()<0000000003500b80> +0000000003500c40 t WTF::RefPtr::RefPtr()<0000000003500c40> +0000000003500c60 t WTF::RefPtr::operator=(WebCore::TextResourceDecoder*)<0000000003500c60> +0000000003500cb0 t WebCore::PluginDocument::create(WebCore::Frame*, WebCore::KURL const&)<0000000003500cb0> +0000000003500d30 t WTF::PassRefPtr::~PassRefPtr()<0000000003500d30> +0000000003500d50 t WTF::PassRefPtr::PassRefPtr(WTF::PassRefPtr const&)<0000000003500d50> +0000000003500d80 t WebCore::PlaceholderDocument::create(WebCore::Frame*, WebCore::KURL const&)<0000000003500d80> +0000000003500e00 t WTF::PassRefPtr::~PassRefPtr()<0000000003500e00> +0000000003500e20 t WTF::RefPtr& WTF::RefPtr::operator=(WTF::PassRefPtr const&)<0000000003500e20> +00000000035f6c90 t WTF::PassRefPtr::get() const<00000000035f6c90> +00000000035f6ca0 t bool WTF::operator!=(WTF::RefPtr const&, WTF::RefPtr const&)<00000000035f6ca0> +00000000035f6ce0 t WebCore::Page::shouldSuppressScrollbarAnimations() const<00000000035f6ce0> +00000000035f6d00 t WebCore::ElementTraversal::firstWithin(WebCore::Node const*)<00000000035f6d00> +00000000035f6d20 t WebCore::FocusCandidate::FocusCandidate()<00000000035f6d20> +00000000035f6d40 t WebCore::FocusCandidate::isNull() const<00000000035f6d40> +00000000035f6d60 t WTF::PassOwnPtr::PassOwnPtr(WebCore::FocusController*)<00000000035f6d60> +00000000035f6d90 t WTF::PassOwnPtr::PassOwnPtr(WebCore::FocusController*)<00000000035f6d90> +00000000035f6db0 t WebCore::maxDistance()<00000000035f6db0> +00000000035f6dc0 t WebCore::FocusCandidate::FocusCandidate()<00000000035f6dc0> +00000000035f6e40 t WebCore::Element* WebCore::ElementTraversal::firstElementWithinTemplate(WebCore::Node const*)<00000000035f6e40> +00000000035f6eb0 t WebCore::Frame::create(WebCore::Page*, WebCore::HTMLFrameOwnerElement*, WebCore::FrameLoaderClient*)<00000000035f6eb0> +00000000035f6fa0 t WebCore::Frame::~Frame() +00000000035f71b0 t WebCore::Frame::setView(WTF::PassRefPtr)<00000000035f71b0> +00000000035fb390 t WebCore::parentFromOwnerElement(WebCore::HTMLFrameOwnerElement*)<00000000035fb390> +00000000035fb3e0 t WebCore::parentPageZoomFactor(WebCore::Frame*)<00000000035fb3e0> +00000000035fb450 t WebCore::parentTextZoomFactor(WebCore::Frame*)<00000000035fb450> +00000000035fb4c0 t __cxx_global_var_init<00000000035fb4c0> +00000000035fb510 t global constructors keyed to a<00000000035fb510> +00000000035fb520 t WTF::RefCounted::operator new(unsigned long)<00000000035fb520> +00000000035fb560 t WebCore::Frame::Frame(WebCore::Page*, WebCore::HTMLFrameOwnerElement*, WebCore::FrameLoaderClient*)<00000000035fb560> +00000000035fb5a0 t WTF::HashSet, WTF::HashTraits >::end() const<00000000035fb5a0> +00000000035fb5f0 t WTF::HashSet, WTF::HashTraits >::begin() const<00000000035fb5f0> +00000000035fb640 t bool WTF::operator!=, WTF::HashTraits, WTF::HashTraits >, WebCore::FrameDestructionObserver*>(WTF::HashTableConstIteratorAdapter, WTF::HashTraits, WTF::HashTraits >, WebCore::FrameDestructionObserver*> const&, WTF::HashTableConstIteratorAdapter, WTF::HashTraits, WTF::HashTraits >, WebCore::FrameDestructionObserver*> const&)<00000000035fb640> +00000000035fb670 t WTF::HashTableConstIteratorAdapter, WTF::HashTraits, WTF::HashTraits >, WebCore::FrameDestructionObserver*>::operator*() const<00000000035fb670> +00000000035fb690 t WTF::HashTableConstIteratorAdapter, WTF::HashTraits, WTF::HashTraits >, WebCore::FrameDestructionObserver*>::operator++()<00000000035fb690> +00000000035fb6c0 t WTF::HashTableConstIteratorAdapter, WTF::HashTraits, WTF::HashTraits >, WebCore::FrameDestructionObserver*>::~HashTableConstIteratorAdapter()<00000000035fb6c0> +00000000035fb6e0 t WTF::HashSet, WTF::HashTraits >::~HashSet()<00000000035fb6e0> +00000000035fb700 t WTF::RefCounted::~RefCounted()<00000000035fb700> +00000000035fb720 t WTF::HashSet, WTF::HashTraits >::add(WebCore::FrameDestructionObserver* const&)<00000000035fb720> +00000000035fb750 t WTF::HashTableAddResult, WTF::HashTraits, WTF::HashTraits > >::~HashTableAddResult()<00000000035fb750> +00000000035fb770 t WTF::HashSet, WTF::HashTraits >::remove(WebCore::FrameDestructionObserver* const&)<00000000035fb770> +00000000035fb7c0 t WTF::PassRefPtr::operator!() const<00000000035fb7c0> +00000000035fb7e0 t WTF::RefPtr::operator=(WTF::PassRefPtr const&)<00000000035fb7e0> +00000000035fb830 t WTF::PassRefPtr::operator!() const<00000000035fb830> +00000000035fb850 t WTF::PassRefPtr::operator->() const<00000000035fb850> +00000000035fb860 t WTF::RefPtr::operator=(WTF::PassRefPtr const&)<00000000035fb860> +0000000003600630 t WTF::PassOwnPtr::PassOwnPtr(WebCore::RegularExpression*)<0000000003600630> +0000000003600650 t WTF::HashTableAddResult, WTF::HashTraits, WTF::HashTraits > >::~HashTableAddResult()<0000000003600650> +0000000003600670 t WTF::HashSet, WTF::HashTraits >::~HashSet()<0000000003600670> +0000000003600690 t WTF::HashTable, WTF::HashTraits, WTF::HashTraits >::~HashTable()<0000000003600690> +00000000036006b0 t WTF::HashTable, WTF::HashTraits, WTF::HashTraits >::~HashTable()<00000000036006b0> +0000000003600710 t WTF::HashTableConstIteratorAdapter, WTF::HashTraits, WTF::HashTraits >, WebCore::FrameDestructionObserver*>::~HashTableConstIteratorAdapter()<0000000003600710> +0000000003600730 t WebCore::Frame::Frame(WebCore::Page*, WebCore::HTMLFrameOwnerElement*, WebCore::FrameLoaderClient*)<0000000003600730> +00000000036009a0 t WTF::RefCounted::RefCounted()<00000000036009a0> +00000000036009c0 t WTF::HashSet, WTF::HashTraits >::HashSet()<00000000036009c0> +00000000036009e0 t WebCore::FrameTree::FrameTree(WebCore::Frame*, WebCore::Frame*)<00000000036009e0> +0000000003600a10 t WebCore::Page::incrementSubframeCount()<0000000003600a10> +0000000003600a30 t WebCore::FrameTree::FrameTree(WebCore::Frame*, WebCore::Frame*)<0000000003600a30> +0000000003600ad0 t WTF::HashSet, WTF::HashTraits >::HashSet()<0000000003600ad0> +0000000003600af0 t WTF::HashTable, WTF::HashTraits, WTF::HashTraits >::HashTable()<0000000003600af0> +0000000003600b10 t WTF::HashTable, WTF::HashTraits, WTF::HashTraits >::HashTable()<0000000003600b10> +0000000003600bc0 t WebCore::FrameDestructionObserver::FrameDestructionObserver(WebCore::Frame*) +00000000037a4cf0 t WebCore::V8AbstractEventListener::handleEvent(WebCore::ScriptExecutionContext*, WebCore::Event*)<00000000037a4cf0> +00000000037a4f80 t WebCore::V8AbstractEventListener::invokeEventHandler(WebCore::ScriptExecutionContext*, WebCore::Event*, v8::Handle)<00000000037a4f80> +00000000037f8920 t WTF::RefPtr >::operator!() const<00000000037f8920> +00000000037f8940 t WTF::RefPtr >::operator->() const<00000000037f8940> +00000000037f8950 t WebCore::SVGPropertyTearOff::propertyReference()<00000000037f8950> +00000000037f8960 t WebCore::SVGAnimatedPropertyTearOff* WebCore::SVGAnimatedProperty::lookupWrapper >(WebCore::SVGCursorElement*, WebCore::SVGPropertyInfo const*)<00000000037f8960> +00000000037f8a00 t WTF::HashSet, WTF::HashTraits >::~HashSet()<00000000037f8a00> +00000000037f8a20 t WTF::HashTable, WTF::HashTraits, WTF::HashTraits >::~HashTable()<00000000037f8a20> +00000000037f8a40 t WTF::HashTable, WTF::HashTraits, WTF::HashTraits >::~HashTable()<00000000037f8a40> +00000000037f8aa0 t WTF::HashTableConstIteratorAdapter, WTF::HashTraits, WTF::HashTraits >, WebCore::SVGElement*>::~HashTableConstIteratorAdapter()<00000000037f8aa0> +00000000037f8ac0 t WTF::HashSet, WTF::HashTraits >::HashSet()<00000000037f8ac0> +00000000037f8ae0 t WTF::HashTable, WTF::HashTraits, WTF::HashTraits >::HashTable()<00000000037f8ae0> +00000000037f8b00 t WTF::HashTable, WTF::HashTraits, WTF::HashTraits >::HashTable()<00000000037f8b00> +00000000037f8bb0 t WebCore::CSSDefaultStyleSheets::initDefaultStyle(WebCore::Element*)<00000000037f8bb0> +00000000037f8c10 t WebCore::elementCanUseSimpleDefaultStyle(WebCore::Element*)<00000000037f8c10> +00000000037f8cf0 t WebCore::CSSDefaultStyleSheets::loadSimpleDefaultStyle()<00000000037f8cf0> +00000000037f8e70 t WebCore::CSSDefaultStyleSheets::loadFullDefaultStyle()<00000000037f8e70> +0000000003857250 t WebCore::RuleSet::addRulesFromSheet(WebCore::StyleSheetContents*, WebCore::MediaQueryEvaluator const&, WebCore::StyleResolver*, WebCore::ContainerNode const*)<0000000003857250> +0000000003857480 t WebCore::RuleSet::shrinkToFit()<0000000003857480> +0000000003857520 t WebCore::shrinkMapVectorsToFit(WTF::HashMap >, WTF::PtrHash, WTF::HashTraits, WTF::HashTraits > > >&)<0000000003857520> +00000000038575c0 t WebCore::isCommonAttributeSelectorAttribute(WebCore::QualifiedName const&)<00000000038575c0> +0000000003857610 t WebCore::selectorListContainsUncommonAttributeSelector(WebCore::CSSSelector const*)<0000000003857610> +00000000038576d0 t WTF::MemoryClassInfo::MemoryClassInfo(WTF::MemoryObjectInfo*, WebCore::RuleData const*, char const*, unsigned long)<00000000038576d0> +0000000003857710 t WTF::MemoryClassInfo::MemoryClassInfo(WTF::MemoryObjectInfo*, WebCore::RuleSet const*, char const*, unsigned long)<0000000003857710> +0000000003857750 t void WTF::MemoryClassInfo::addMember >, WTF::PtrHash, WTF::HashTraits, WTF::HashTraits > > > >(WTF::HashMap >, WTF::PtrHash, WTF::HashTraits, WTF::HashTraits > > > const&, char const*)<0000000003857750> +0000000003857da0 t WebCore::RuleSet::RuleSetSelectorPair::RuleSetSelectorPair(WebCore::CSSSelector const*, WTF::PassOwnPtr)<0000000003857da0> +0000000003857dd0 t WebCore::StyleResolver::ruleSets()<0000000003857dd0> +0000000003857de0 t WebCore::CSSSelectorList::indexOfNextSelectorAfter(unsigned long) const<0000000003857de0> +0000000003857e60 t WTF::PassRefPtr::PassRefPtr(WebCore::StyleRuleKeyframes*)<0000000003857e60> +0000000003857e90 t WebCore::StyleRuleBase::isHostRule() const<0000000003857e90> +0000000003857ec0 t WebCore::StyleResolver::addHostRule(WebCore::StyleRuleHost*, bool, WebCore::ContainerNode const*)<0000000003857ec0> +0000000003857f10 t WebCore::StyleSheetContents::importRules() const<0000000003857f10> +0000000003857f30 t WebCore::StyleSheetContents::childRules() const<0000000003857f30> +0000000003857f50 t WTF::Vector::shrinkToFit()<0000000003857f50> +0000000003857f80 t WTF::Vector::shrinkToFit()<0000000003857f80> +0000000003857fb0 t WTF::Vector::shrinkCapacity(unsigned long)<0000000003857fb0> +0000000003858110 t WTF::VectorBufferBase::shouldReallocateBuffer(unsigned long) const<0000000003858110> +0000000003858150 t WTF::VectorBufferBase::reallocateBuffer(unsigned long)<0000000003858150> +0000000003858250 t WTF::VectorBuffer::restoreInlineBufferIfNeeded()<0000000003858250> +0000000003858260 t WTF::Vector::shrinkCapacity(unsigned long)<0000000003858260> +0000000003b70db0 t WebCore::ContainerNode::~ContainerNode()<0000000003b70db0> +0000000003b70de0 t WebCore::ContainerNode::~ContainerNode() +0000000003b70e90 t WebCore::ContainerNode::insertBefore(WTF::PassRefPtr, WebCore::Node*, int&, bool)<0000000003b70e90> +0000000003b80ce0 t void std::make_heap(WTF::String*, WTF::String*, bool (*)(WTF::String const&, WTF::String const&))<0000000003b80ce0> +0000000003b80e10 t WebCore::DecodedDataDocumentParser::DecodedDataDocumentParser(WebCore::Document*)<0000000003b80e10> +0000000003b80e60 t WebCore::DecodedDataDocumentParser::appendBytes(WebCore::DocumentWriter*, char const*, unsigned long)<0000000003b80e60> +0000000003b80f70 t WebCore::DecodedDataDocumentParser::flush(WebCore::DocumentWriter*)<0000000003b80f70> +0000000003b81050 t WebCore::DecodedDataDocumentParser::~DecodedDataDocumentParser()<0000000003b81050> +0000000003b81070 t WebCore::DecodedDataDocumentParser::~DecodedDataDocumentParser()<0000000003b81070> +0000000003b810a0 t WebCore::DeviceMotionController::DeviceMotionController(WebCore::DeviceMotionClient*) +0000000003b81160 t WebCore::DeviceMotionController::deviceMotionClient()<0000000003b81160> +0000000003b81180 t WebCore::DeviceMotionController::create(WebCore::DeviceMotionClient*)<0000000003b81180> +0000000003b811f0 t WebCore::DeviceMotionController::didChangeDeviceMotion(WebCore::DeviceMotionData*)<0000000003b811f0> +0000000003b8d570 t WebCore::Document::haveStylesheetsLoaded() const<0000000003b8d570> +0000000003b8d5d0 t WebCore::Document::body() const<0000000003b8d5d0> +0000000003b8d6c0 t WebCore::Document::styleForElementIgnoringPendingStylesheets(WebCore::Element*)<0000000003b8d6c0> +0000000003b8d850 t WebCore::Document::styleForPage(int)<0000000003b8d850> +0000000003b8d8d0 t WebCore::Document::isPageBoxVisible(int)<0000000003b8d8d0> +0000000003b8d950 t WebCore::Document::pageSizeAndMarginsInPixels(int, WebCore::IntSize&, int&, int&, int&, int&)<0000000003b8d950> +0000000003b8e000 t WebCore::Document::setIsViewSource(bool)<0000000003b8e000> +0000000003b8e080 t WebCore::Document::createStyleResolver()<0000000003b8e080> +0000000003b8e160 t WebCore::Document::attach()<0000000003b8e160> +0000000003b8e400 t WebCore::Document::topDocument() const<0000000003b8e400> +0000000003bfd150 t WebCore::Element::setSavedLayerScrollOffset(WebCore::IntSize const&)<0000000003bfd150> +0000000003bfd1c0 t WebCore::Element::createRendererIfNeeded()<0000000003bfd1c0> +0000000003bfd200 t WebCore::Element::attach()<0000000003bfd200> +0000000003bfd3f0 t WebCore::Element::isInCanvasSubtree() const<0000000003bfd3f0> +0000000003bfd450 t WebCore::Element::setIsInCanvasSubtree(bool)<0000000003bfd450> +0000000003bfd490 t WebCore::Element::updatePseudoElement(WebCore::PseudoId, WebCore::Node::StyleChange)<0000000003bfd490> +0000000003bfdc90 t WebCore::Element::styleForRenderer()<0000000003bfdc90> +0000000003bfddc0 t WebCore::Element::recalcStyle(WebCore::Node::StyleChange)<0000000003bfddc0> +0000000003c00cd0 t WebCore::Element::outerText()<0000000003c00cd0> +0000000003c00d00 t WebCore::Element::title() const<0000000003c00d00> +0000000003c00d30 t WebCore::Element::setPseudo(WTF::AtomicString const&)<0000000003c00d30> +0000000003c00d60 t WebCore::Element::minimumSizeForResizing() const<0000000003c00d60> +0000000003c00de0 t WebCore::Element::setMinimumSizeForResizing(WebCore::LayoutSize const&)<0000000003c00de0> +0000000003c00e80 t WebCore::Element::computedStyle(WebCore::PseudoId)<0000000003c00e80> +0000000003c01000 t WebCore::Element::setStyleAffectedByEmpty()<0000000003c01000> +0000000003c01030 t WebCore::Element::setChildrenAffectedByHover(bool)<0000000003c01030> +0000000003c01090 t WebCore::Element::setChildrenAffectedByActive(bool)<0000000003c01090> +0000000003c010f0 t WebCore::Element::setChildrenAffectedByDrag(bool)<0000000003c010f0> +0000000003c01150 t WebCore::Element::setChildrenAffectedByFirstChildRules()<0000000003c01150> +0000000003c01180 t WebCore::Element::setChildrenAffectedByLastChildRules()<0000000003c01180> +0000000003c011b0 t WebCore::Element::setChildrenAffectedByDirectAdjacentRules()<0000000003c011b0> +0000000003c011e0 t WebCore::Element::setChildrenAffectedByForwardPositionalRules()<0000000003c011e0> +0000000003c01210 t WebCore::Element::setChildrenAffectedByBackwardPositionalRules()<0000000003c01210> +0000000003c01240 t WebCore::Element::setChildIndex(unsigned int)<0000000003c01240> +0000000003c14980 t WebCore::shadowOfParent(WebCore::Node const*)<0000000003c14980> +0000000003c14a10 t WebCore::EventTargetData::EventTargetData() +0000000003c14a40 t WebCore::EventTargetData::~EventTargetData() +0000000003c14a80 t WebCore::EventTarget::~EventTarget()<0000000003c14a80> +0000000003c14ab0 t WebCore::EventTarget::~EventTarget() +0000000003c14ac0 t WebCore::EventTarget::toNode()<0000000003c14ac0> +0000000003c14ad0 t WebCore::EventTarget::toDOMWindow()<0000000003c14ad0> +0000000003c14ae0 t WebCore::EventTarget::addEventListener(WTF::AtomicString const&, WTF::PassRefPtr, bool)<0000000003c14ae0> +0000000003c14b80 t WebCore::EventTarget::removeEventListener(WTF::AtomicString const&, WebCore::EventListener*, bool)<0000000003c14b80> +0000000003c14d20 t WebCore::EventTarget::setAttributeEventListener(WTF::AtomicString const&, WTF::PassRefPtr)<0000000003c14d20> +0000000003c14de0 t WebCore::EventTarget::clearAttributeEventListener(WTF::AtomicString const&)<0000000003c14de0> +0000000003c15520 t WebCore::EventTarget::uncaughtExceptionInEventHandler()<0000000003c15520> +0000000003c15530 t WebCore::prefixedType(WebCore::Event const*)<0000000003c15530> +0000000003c155c0 t WebCore::EventTarget::fireEventListeners(WebCore::Event*, WebCore::EventTargetData*, WTF::Vector&)<0000000003c155c0> +0000000003c45e70 t WebCore::Node::mutationObserverRegistry()<0000000003c45e70> +0000000003c45ee0 t WebCore::Node::addEventListener(WTF::AtomicString const&, WTF::PassRefPtr, bool)<0000000003c45ee0> +0000000003c45f50 t WebCore::tryAddEventListener(WebCore::Node*, WTF::AtomicString const&, WTF::PassRefPtr, bool)<0000000003c45f50> +0000000003c46070 t WebCore::Node::removeEventListener(WTF::AtomicString const&, WebCore::EventListener*, bool)<0000000003c46070> +0000000003c460b0 t WebCore::tryRemoveEventListener(WebCore::Node*, WTF::AtomicString const&, WebCore::EventListener*, bool)<0000000003c460b0> +0000000003c461a0 t WebCore::Node::eventTargetData()<0000000003c461a0> +0000000003c46210 t WebCore::eventTargetDataMap()<0000000003c46210> +0000000003c46d10 t WebCore::Node::handleLocalEvents(WebCore::Event*)<0000000003c46d10> +0000000003c46da0 t WebCore::Node::dispatchScopedEvent(WTF::PassRefPtr)<0000000003c46da0> +0000000003c46e00 t WebCore::Node::dispatchScopedEventDispatchMediator(WTF::PassRefPtr)<0000000003c46e00> +0000000003c46e40 t WebCore::Node::dispatchEvent(WTF::PassRefPtr)<0000000003c46e40> +0000000003c46f30 t WebCore::Node::dispatchSubtreeModifiedEvent()<0000000003c46f30> +0000000003c4aa30 t WTF::Vector, 0ul>::size() const<0000000003c4aa30> +0000000003c4aa40 t WebCore::Document::addMutationObserverTypes(unsigned char)<0000000003c4aa40> +0000000003c4aa70 t WTF::Vector, 0ul>::at(unsigned long)<0000000003c4aa70> +0000000003c4ab20 t WTF::OwnPtr::operator->() const<0000000003c4ab20> +0000000003c4aba0 t WebCore::MutationObserverRegistration::mutationTypes() const<0000000003c4aba0> +0000000003c4abc0 t WTF::HashMap, WTF::PtrHash, WTF::HashTraits, WTF::HashTraits > >::get(WebCore::Node* const&) const<0000000003c4abc0> +0000000003c4ac30 t WebCore::Node::setHasEventTargetData(bool)<0000000003c4ac30> +0000000003c4ac60 t WebCore::EventTargetData::operator new(unsigned long)<0000000003c4ac60> +0000000003c4aca0 t WTF::HashMap, WTF::PtrHash, WTF::HashTraits, WTF::HashTraits > >::set(WebCore::Node* const&, WTF::PassOwnPtr)<0000000003c4aca0> +0000000003c4ad40 t WTF::PassOwnPtr WTF::adoptPtr(WebCore::EventTargetData*)<0000000003c4ad40> +0000000003c4ad70 t WTF::HashTableAddResult >, WTF::KeyValuePairKeyExtractor > >, WTF::PtrHash, WTF::HashMapValueTraits, WTF::HashTraits > >, WTF::HashTraits > >::~HashTableAddResult()<0000000003c4ad70> +0000000003c4ad90 t WTF::PassOwnPtr::~PassOwnPtr()<0000000003c4ad90> +0000000003c4adb0 t WTF::HashMap, WTF::PtrHash, WTF::HashTraits, WTF::HashTraits > >::remove(WebCore::Node* const&)<0000000003c4adb0> +0000000003c4ae00 t WebCore::NodeRareData::mutationObserverData()<0000000003c4ae00> +0000000003c4ae30 t WebCore::NodeRareData::ensureMutationObserverData()<0000000003c4ae30> +0000000003c4aeb0 t WTF::Vector, 0ul>::operator[](unsigned long)<0000000003c4aeb0> +0000000003c4aee0 t WebCore::MutationObserverRegistration::observer() const<0000000003c4aee0> +0000000003c4af00 t WTF::OwnPtr::get() const<0000000003c4af00> +0000000003c4af10 t void WTF::Vector, 0ul>::append >(WTF::PassOwnPtr const&)<0000000003c4af10> +0000000003c4afc0 t WTF::PassRefPtr::PassRefPtr(WebCore::MutationObserver*)<0000000003c4afc0> +0000000003c4fe80 t WTF::HashTable >, WTF::KeyValuePairKeyExtractor > >, WTF::PtrHash, WTF::HashMapValueTraits, WTF::HashTraits > >, WTF::HashTraits >::internalCheckTableConsistency()<0000000003c4fe80> +0000000003c4fe90 t WTF::HashTable >, WTF::KeyValuePairKeyExtractor > >, WTF::PtrHash, WTF::HashMapValueTraits, WTF::HashTraits > >, WTF::HashTraits >::removeWithoutEntryConsistencyCheck(WTF::HashTableIterator >, WTF::KeyValuePairKeyExtractor > >, WTF::PtrHash, WTF::HashMapValueTraits, WTF::HashTraits > >, WTF::HashTraits >)<0000000003c4fe90> +0000000003c4ff00 t WTF::HashTable >, WTF::KeyValuePairKeyExtractor > >, WTF::PtrHash, WTF::HashMapValueTraits, WTF::HashTraits > >, WTF::HashTraits >::removeAndInvalidateWithoutEntryConsistencyCheck(WTF::KeyValuePair >*)<0000000003c4ff00> +0000000003c4ff40 t WTF::HashTable >, WTF::KeyValuePairKeyExtractor > >, WTF::PtrHash, WTF::HashMapValueTraits, WTF::HashTraits > >, WTF::HashTraits >::invalidateIterators()<0000000003c4ff40> +0000000003c4fff0 t WTF::HashTable >, WTF::KeyValuePairKeyExtractor > >, WTF::PtrHash, WTF::HashMapValueTraits, WTF::HashTraits > >, WTF::HashTraits >::remove(WTF::KeyValuePair >*)<0000000003c4fff0> +0000000003c50060 t WTF::HashTable >, WTF::KeyValuePairKeyExtractor > >, WTF::PtrHash, WTF::HashMapValueTraits, WTF::HashTraits > >, WTF::HashTraits >::deleteBucket(WTF::KeyValuePair >&)<0000000003c50060> +0000000003c50090 t WTF::HashTable >, WTF::KeyValuePairKeyExtractor > >, WTF::PtrHash, WTF::HashMapValueTraits, WTF::HashTraits > >, WTF::HashTraits >::shouldShrink() const<0000000003c50090> +0000000003c500e0 t WTF::HashTable >, WTF::KeyValuePairKeyExtractor > >, WTF::PtrHash, WTF::HashMapValueTraits, WTF::HashTraits > >, WTF::HashTraits >::shrink()<0000000003c500e0> +0000000003c50110 t WTF::HashTable >, WTF::KeyValuePairKeyExtractor > >, WTF::PtrHash, WTF::HashMapValueTraits, WTF::HashTraits > >, WTF::HashTraits >::rehash(int)<0000000003c50110> +0000000003c501f0 t WTF::HashTable >, WTF::KeyValuePairKeyExtractor > >, WTF::PtrHash, WTF::HashMapValueTraits, WTF::HashTraits > >, WTF::HashTraits >::internalCheckTableConsistencyExceptSize()<0000000003c501f0> +0000000003c50200 t WTF::HashTable >, WTF::KeyValuePairKeyExtractor > >, WTF::PtrHash, WTF::HashMapValueTraits, WTF::HashTraits > >, WTF::HashTraits >::allocateTable(int)<0000000003c50200> +0000000003c50230 t WTF::HashTable >, WTF::KeyValuePairKeyExtractor > >, WTF::PtrHash, WTF::HashMapValueTraits, WTF::HashTraits > >, WTF::HashTraits >::isEmptyOrDeletedBucket(WTF::KeyValuePair > const&)<0000000003c50230> +0000000003c50270 t WTF::HashTable >, WTF::KeyValuePairKeyExtractor > >, WTF::PtrHash, WTF::HashMapValueTraits, WTF::HashTraits > >, WTF::HashTraits >::reinsert(WTF::KeyValuePair >&)<0000000003c50270> +0000000003c50400 t WTF::HashTable >, WTF::KeyValuePairKeyExtractor > >, WTF::PtrHash, WTF::HashMapValueTraits, WTF::HashTraits > >, WTF::HashTraits >::deallocateTable(WTF::KeyValuePair >*, int)<0000000003c50400> +0000000003c50ab0 t WTF::PassOwnPtr::~PassOwnPtr()<0000000003c50ab0> +0000000003c50ad0 t WTF::PassOwnPtr::PassOwnPtr(WebCore::EventTargetData*)<0000000003c50ad0> +0000000003c50b00 t WTF::PassOwnPtr::PassOwnPtr(WebCore::EventTargetData*)<0000000003c50b00> +0000000003c50b20 t WTF::HashMap, WTF::PtrHash, WTF::HashTraits, WTF::HashTraits > >::inlineAdd(WebCore::Node* const&, WTF::PassOwnPtr&)<0000000003c50b20> +0000000003c50b60 t WTF::HashTraits >::store(WTF::PassOwnPtr, WTF::OwnPtr&)<0000000003c50b60> +0000000003c50b90 t WTF::PassOwnPtr::PassOwnPtr(WTF::PassOwnPtr const&)<0000000003c50b90> +0000000003c50bc0 t WTF::HashTableIterator >, WTF::KeyValuePairKeyExtractor > >, WTF::PtrHash, WTF::HashMapValueTraits, WTF::HashTraits > >, WTF::HashTraits >::operator->() const<0000000003c50bc0> +0000000003c50be0 t WTF::HashTableIterator >, WTF::KeyValuePairKeyExtractor > >, WTF::PtrHash, WTF::HashMapValueTraits, WTF::HashTraits > >, WTF::HashTraits >::get() const<0000000003c50be0> +0000000003c50c00 t WTF::HashTableConstIterator >, WTF::KeyValuePairKeyExtractor > >, WTF::PtrHash, WTF::HashMapValueTraits, WTF::HashTraits > >, WTF::HashTraits >::get() const<0000000003c50c00> +0000000003c50c30 t WTF::HashTableConstIterator >, WTF::KeyValuePairKeyExtractor > >, WTF::PtrHash, WTF::HashMapValueTraits, WTF::HashTraits > >, WTF::HashTraits >::checkValidity() const<0000000003c50c30> +0000000003c50ca0 t WTF::PassOwnPtr::PassOwnPtr(WTF::PassOwnPtr const&)<0000000003c50ca0> +0000000003c50cd0 t WTF::PassOwnPtr::leakPtr() const<0000000003c50cd0> +0000000003c50cf0 t WTF::OwnPtr::operator=(WTF::PassOwnPtr const&)<0000000003c50cf0> +0000000003c50da0 t WTF::HashTableAddResult >, WTF::KeyValuePairKeyExtractor > >, WTF::PtrHash, WTF::HashMapValueTraits, WTF::HashTraits > >, WTF::HashTraits > > WTF::HashTable >, WTF::KeyValuePairKeyExtractor > >, WTF::PtrHash, WTF::HashMapValueTraits, WTF::HashTraits > >, WTF::HashTraits >::add, WTF::HashTraits > >, WTF::PtrHash >, WebCore::Node*, WTF::PassOwnPtr >(WebCore::Node* const&, WTF::PassOwnPtr const&)<0000000003c50da0> +0000000003c61050 t WebCore::NodeRenderingContext::isOnEncapsulationBoundary() const<0000000003c61050> +0000000003c610d0 t WebCore::NodeRenderingContext::isOnUpperEncapsulationBoundary() const<0000000003c610d0> +0000000003c61130 t WebCore::NodeRenderingContext::createRendererForElementIfNeeded()<0000000003c61130> +0000000003c61460 t WebCore::NodeRenderingContext::createRendererForTextIfNeeded()<0000000003c61460> +0000000003c6d0d0 t WebCore::toRenderQuote(WebCore::RenderObject*)<0000000003c6d0d0> +0000000003c6d160 t WebCore::PseudoElement::~PseudoElement()<0000000003c6d160> +0000000003c6d180 t WebCore::PseudoElement::~PseudoElement()<0000000003c6d180> +0000000003c6d1b0 t WebCore::PseudoElement::canContainRangeEndPoint() const<0000000003c6d1b0> +0000000003c6d1c0 t WebCore::PseudoElement::canStartSelection() const<0000000003c6d1c0> +0000000003c6d1d0 t WebCore::PseudoElement::customPseudoId() const<0000000003c6d1d0> +0000000003c6d1e0 t WebCore::PseudoElement::~PseudoElement()<0000000003c6d1e0> +0000000003c6d200 t WebCore::QualifiedName::QualifiedName(WTF::AtomicString const&, WTF::AtomicString const&, WTF::AtomicString const&) +0000000003c6d350 t WebCore::QualifiedName::~QualifiedName() +0000000003c6d370 t WebCore::QualifiedName::deref()<0000000003c6d370> +0000000003c6d3f0 t WebCore::QualifiedName::QualifiedNameImpl::~QualifiedNameImpl() +0000000003c6d480 t WebCore::QualifiedName::toString() const<0000000003c6d480> +0000000003c6d570 t WebCore::QualifiedName::init()<0000000003c6d570> +0000000003c6d5f0 t WebCore::nullQName()<0000000003c6d5f0> +0000000003c6db30 t WTF::RefCounted::~RefCounted()<0000000003c6db30> +0000000003c6db50 t WebCore::QualifiedName::operator new(unsigned long, void*)<0000000003c6db50> +0000000003c6db60 t WTF::AtomicString::upper() const<0000000003c6db60> +0000000003c6dbd0 t WTF::MemoryClassInfo::MemoryClassInfo(WTF::MemoryObjectInfo*, WebCore::QualifiedName const*, char const*, unsigned long)<0000000003c6dbd0> +0000000003c6dc10 t void WTF::MemoryClassInfo::addMember(WebCore::QualifiedName::QualifiedNameImpl* const&, char const*)<0000000003c6dc10> +0000000003c6dc60 t WTF::MemoryClassInfo::MemoryClassInfo(WTF::MemoryObjectInfo*, WebCore::QualifiedName::QualifiedNameImpl const*, char const*, unsigned long)<0000000003c6dc60> +0000000003c6dca0 t WTF::AtomicString::AtomicString(char const*, unsigned int, WTF::AtomicString::ConstructFromLiteralTag)<0000000003c6dca0> +0000000003c6dcd0 t WTF::MemoryClassInfo::MemoryClassInfo(WTF::MemoryObjectInfo*, WebCore::QualifiedName::QualifiedNameImpl const*, char const*, unsigned long)<0000000003c6dcd0> +0000000003c6f990 t WTF::HashTableIterator::get() const<0000000003c6f990> +0000000003c6f9b0 t WTF::HashTableConstIterator::get() const<0000000003c6f9b0> +0000000003c6f9e0 t WTF::HashTableConstIterator::checkValidity() const<0000000003c6f9e0> +0000000003c6fa50 t WTF::HashTableAddResult > WTF::HashTable::addPassingHashCode, WebCore::QualifiedNameComponents, WebCore::QualifiedNameComponents>(WebCore::QualifiedNameComponents const&, WebCore::QualifiedNameComponents const&)<0000000003c6fa50> +0000000003c6fd00 t void WTF::HashTable::checkKey, WebCore::QualifiedNameComponents>(WebCore::QualifiedNameComponents const&)<0000000003c6fd00> +0000000003c6fd40 t WTF::HashTable::expand()<0000000003c6fd40> +0000000003c6fdc0 t std::pair, unsigned int> WTF::HashTable::fullLookupForWriting, WebCore::QualifiedNameComponents>(WebCore::QualifiedNameComponents const&)<0000000003c6fdc0> +0000000003cd7d20 t WTF::PassRefPtr::PassRefPtr(WebCore::CustomElementConstructor*, bool)<0000000003cd7d20> +0000000003cd7d60 t WTF::PassRefPtr::PassRefPtr(WebCore::CustomElementConstructor*, bool)<0000000003cd7d60> +0000000003cd7d80 t WebCore::EventContext::EventContext(WTF::PassRefPtr, WTF::PassRefPtr, WTF::PassRefPtr) +0000000003cd7ed0 t WebCore::EventContext::~EventContext()<0000000003cd7ed0> +0000000003cd7f00 t WebCore::EventContext::~EventContext() +0000000003cd7f60 t WebCore::EventContext::handleLocalEvents(WebCore::Event*) const<0000000003cd7f60> +0000000003cd8020 t WebCore::EventContext::isMouseOrFocusEventContext() const<0000000003cd8020> +0000000003cd8030 t WebCore::MouseOrFocusEventContext::MouseOrFocusEventContext(WTF::PassRefPtr, WTF::PassRefPtr, WTF::PassRefPtr) +0000000003cd80e0 t WebCore::MouseOrFocusEventContext::~MouseOrFocusEventContext()<0000000003cd80e0> +0000000003cd8110 t WebCore::MouseOrFocusEventContext::~MouseOrFocusEventContext() +0000000003cd8160 t WebCore::MouseOrFocusEventContext::handleLocalEvents(WebCore::Event*) const<0000000003cd8160> +0000000003cd8300 t WebCore::MouseOrFocusEventContext::isMouseOrFocusEventContext() const<0000000003cd8300> +0000000003cd8310 t WebCore::MouseEvent::setRelatedTarget(WTF::PassRefPtr)<0000000003cd8310> +0000000003ec6560 t content::ChildThread::OnMessageReceived(IPC::Message const&)<0000000003ec6560> +0000000003fe4db0 t content::ResourceDispatcher::IsResourceDispatcherMessage(IPC::Message const&)<0000000003fe4db0> +0000000003fe4e20 t content::ResourceDispatcher::GetPendingRequestInfo(int)<0000000003fe4e20> +0000000003fe4ec0 t content::ResourceDispatcher::ReleaseResourcesInDataMessage(IPC::Message const&)<0000000003fe4ec0> +0000000003fe4ff0 t content::ResourceDispatcher::FlushDeferredMessages(int)<0000000003fe4ff0> +0000000003fe59c0 t content::ResourceDispatcher::OnUploadProgress(IPC::Message const&, int, long, long)<0000000003fe59c0> +0000000003fe5aa0 t content::ResourceDispatcher::OnReceivedResponse(int, content::ResourceResponseHead const&)<0000000003fe5aa0> +00000000045c1560 t WebCore::NodeV8Internal::insertBeforeMethodCallback(v8::Arguments const&)<00000000045c1560> +00000000045c1590 t WebCore::NodeV8Internal::replaceChildMethodCallback(v8::Arguments const&)<00000000045c1590> +00000000045c15c0 t WebCore::NodeV8Internal::removeChildMethodCallback(v8::Arguments const&)<00000000045c15c0> +00000000045c15f0 t WebCore::NodeV8Internal::appendChildMethodCallback(v8::Arguments const&)<00000000045c15f0> +00000000045c1620 t WebCore::NodeV8Internal::hasChildNodesMethodCallback(v8::Arguments const&)<00000000045c1620> +00000000045c1650 t WebCore::NodeV8Internal::cloneNodeMethodCallback(v8::Arguments const&)<00000000045c1650> +00000000045c1680 t WebCore::NodeV8Internal::normalizeMethodCallback(v8::Arguments const&)<00000000045c1680> +00000000045c16b0 t WebCore::NodeV8Internal::isSupportedMethodCallback(v8::Arguments const&)<00000000045c16b0> +00000000045c16e0 t WebCore::NodeV8Internal::lookupPrefixMethodCallback(v8::Arguments const&)<00000000045c16e0> +00000000045c1710 t WebCore::NodeV8Internal::isDefaultNamespaceMethodCallback(v8::Arguments const&)<00000000045c1710> +00000000045c1740 t WebCore::NodeV8Internal::lookupNamespaceURIMethodCallback(v8::Arguments const&)<00000000045c1740> +00000000045c1770 t WebCore::NodeV8Internal::addEventListenerMethodCallback(v8::Arguments const&)<00000000045c1770> +00000000045c17a0 t WebCore::NodeV8Internal::removeEventListenerMethodCallback(v8::Arguments const&)<00000000045c17a0> +00000000045c17d0 t WebCore::NodeV8Internal::removeEventListenerMethod(v8::Arguments const&)<00000000045c17d0> +00000000045c2500 t WebCore::NodeV8Internal::lookupNamespaceURIMethod(v8::Arguments const&)<00000000045c2500> +0000000004a6c010 t sigslot::_signal_base4::_signal_base4()<0000000004a6c010> +0000000004a6c070 t sigslot::signal4::~signal4()<0000000004a6c070> +0000000004a6c0a0 t non-virtual thunk to sigslot::signal4::~signal4()<0000000004a6c0a0> +0000000004a6c0d0 t std::list*, std::allocator*> >::list()<0000000004a6c0d0> +0000000004a6c0f0 t std::list*, std::allocator*> >::list()<0000000004a6c0f0> +0000000004a6c110 t std::_List_base*, std::allocator*> >::_List_base()<0000000004a6c110> +0000000004a6c140 t std::_List_base*, std::allocator*> >::_List_impl::_List_impl()<0000000004a6c140> +0000000004a6c160 t std::_List_base*, std::allocator*> >::_M_init()<0000000004a6c160> +0000000004a6c180 t std::_List_base*, std::allocator*> >::_List_impl::_List_impl()<0000000004a6c180> +0000000004a6c1c0 t std::allocator*> >::allocator()<0000000004a6c1c0> +0000000004a6c1e0 t __gnu_cxx::new_allocator*> >::new_allocator()<0000000004a6c1e0> +0000000004a6c1f0 t talk_base::PacketSocketFactory::~PacketSocketFactory()<0000000004a6c1f0> +0000000004a6c210 t talk_base::PacketSocketFactory::~PacketSocketFactory()<0000000004a6c210> +0000000004a6c240 t content::ContentMain(int, char const**, content::ContentMainDelegate*)<0000000004a6c240> +0000000004a6c310 t scoped_ptr >::scoped_ptr(content::ContentMainRunner*)<0000000004a6c310> +0000000004a6c340 t scoped_ptr >::operator->() const<0000000004a6c340> +0000000004a6c3a0 t scoped_ptr >::~scoped_ptr()<0000000004a6c3a0> +0000000004a6c3c0 t base::internal::scoped_ptr_impl >::get() const<0000000004a6c3c0> +0000000004a6c3d0 t scoped_ptr >::scoped_ptr(content::ContentMainRunner*)<0000000004a6c3d0> +0000000004a6c400 t base::internal::scoped_ptr_impl >::scoped_ptr_impl(content::ContentMainRunner*)<0000000004a6c400> +0000000004a6c430 t base::internal::scoped_ptr_impl >::scoped_ptr_impl(content::ContentMainRunner*)<0000000004a6c430> +0000000004a6c460 t base::internal::scoped_ptr_impl >::Data::Data(content::ContentMainRunner*)<0000000004a6c460> +0000000004a6c490 t base::internal::scoped_ptr_impl >::Data::Data(content::ContentMainRunner*)<0000000004a6c490> +0000000004a6c4d0 t base::DefaultDeleter::DefaultDeleter()<0000000004a6c4d0> +0000000004a6c4e0 t scoped_ptr >::~scoped_ptr()<0000000004a6c4e0> +0000000004a6c500 t base::internal::scoped_ptr_impl >::~scoped_ptr_impl()<0000000004a6c500> +0000000004a6c520 t base::internal::scoped_ptr_impl >::~scoped_ptr_impl()<0000000004a6c520> +0000000004a6c560 t base::DefaultDeleter::operator()(content::ContentMainRunner*) const<0000000004a6c560> +0000000004a6c5a0 t content::SetupSignalHandlers()<0000000004a6c5a0> +0000000004a6cca0 t content::InitializeStatsTable(CommandLine const&)<0000000004a6cca0> +0000000004a6cda0 t content::RunNamedProcessTypeMain(std::basic_string, std::allocator > const&, content::MainFunctionParams const&, content::ContentMainDelegate*)<0000000004a6cda0> +0000000004a6dc80 t content::ContentMainRunnerImpl::Run()<0000000004a6dc80> +0000000004a6de90 t content::ContentMainRunnerImpl::Shutdown()<0000000004a6de90> +0000000004a6e0e0 t scoped_ptr >::reset(base::AtExitManager*)<0000000004a6e0e0> +0000000004a6e110 t base::internal::scoped_ptr_impl >::reset(base::AtExitManager*)<0000000004a6e110> +0000000004a864e0 t content::RenderThreadImpl::OnCreateNewView(ViewMsg_New_Params const&)<0000000004a864e0> +0000000004a88b20 t scoped_ptr >::reset(media::AudioHardwareConfig*)<0000000004a88b20> +0000000004a88b50 t scoped_refptr::operator->() const<0000000004a88b50> +0000000004a88bb0 t scoped_ptr >::get() const<0000000004a88bb0> +0000000004a88bd0 t content::ChildProcess::io_message_loop()<0000000004a88bd0> +0000000004a88c00 t scoped_ptr >::operator->() const<0000000004a88c00> +0000000004a88c60 t scoped_ptr >::operator->() const<0000000004a88c60> +0000000004a88cc0 t bool ViewMsg_SetZoomLevelForCurrentURL::Dispatch, std::allocator > const&, double)>(IPC::Message const*, content::RenderThreadImpl*, content::RenderThreadImpl*, void (content::RenderThreadImpl::*)(std::basic_string, std::allocator > const&, double))<0000000004a88cc0> +0000000004a88d70 t bool ViewMsg_New::Dispatch(IPC::Message const*, content::RenderThreadImpl*, content::RenderThreadImpl*, void (content::RenderThreadImpl::*)(ViewMsg_New_Params const&))<0000000004a88d70> +0000000004a88e40 t bool ViewMsg_PurgePluginListCache::Dispatch(IPC::Message const*, content::RenderThreadImpl*, content::RenderThreadImpl*, void (content::RenderThreadImpl::*)(bool))<0000000004a88e40> +0000000004a88ed0 t bool ViewMsg_NetworkStateChanged::Dispatch(IPC::Message const*, content::RenderThreadImpl*, content::RenderThreadImpl*, void (content::RenderThreadImpl::*)(bool))<0000000004a88ed0> +0000000004a88f60 t bool ViewMsg_TempCrashWithData::Dispatch(IPC::Message const*, content::RenderThreadImpl*, content::RenderThreadImpl*, void (content::RenderThreadImpl::*)(GURL const&))<0000000004a88f60> +0000000004a89030 t base::RefCountedData::RefCountedData(int const&)<0000000004a89030> +0000000004a89060 t scoped_ptr >::get() const<0000000004a89060> +0000000004a89080 t scoped_ptr >::reset(content::MediaStreamDependencyFactory*)<0000000004a89080> +0000000004a890b0 t scoped_refptr::operator content::VideoCaptureImplManager*() const<0000000004a890b0> +0000000004a890c0 t content::RendererWebKitPlatformSupportImpl::set_plugin_refresh_allowed(bool)<0000000004a890c0> +0000000004a890e0 t base::internal::scoped_ptr_impl >::reset(content::MediaStreamDependencyFactory*)<0000000004a890e0> +0000000004a89640 t base::DefaultDeleter::operator()(content::AudioRendererMixerManager*) const<0000000004a89640> +0000000004a896b0 t base::internal::scoped_ptr_impl >::get() const<0000000004a896b0> +0000000004a896c0 t base::internal::scoped_ptr_impl >::get() const<0000000004a896c0> +0000000004a896d0 t base::internal::scoped_ptr_impl >::reset(content::WebGraphicsContext3DCommandBufferImpl*)<0000000004a896d0> +0000000004a909f0 t content::RenderView::ForEach(content::RenderViewVisitor*)<0000000004a909f0> +0000000004a90ad0 t content::RenderViewImpl::Create(int, content::RendererPreferences const&, webkit_glue::WebPreferences const&, base::RefCountedData*, int, int, long, std::basic_string > const&, bool, bool, int, WebKit::WebScreenInfo const&, AccessibilityMode)<0000000004a90ad0> +0000000004a90d10 t content::RenderViewImpl::InstallCreateHook(content::RenderViewImpl* (*)(content::RenderViewImplParams*))<0000000004a90d10> +0000000004a90dd0 t content::RenderViewImpl::AddObserver(content::RenderViewObserver*)<0000000004a90dd0> +0000000004a90e10 t content::RenderViewImpl::RemoveObserver(content::RenderViewObserver*)<0000000004a90e10> +0000000004a90e60 t content::RenderViewImpl::PluginCrashed(base::FilePath const&, int)<0000000004a90e60> +0000000004a90ef0 t content::RenderViewImpl::RegisterPluginDelegate(content::WebPluginDelegateProxy*)<0000000004a90ef0> +0000000004a90f80 t content::RenderViewImpl::UnregisterPluginDelegate(content::WebPluginDelegateProxy*)<0000000004a90f80> +0000000004a90fc0 t content::RenderViewImpl::GetPluginInfo(GURL const&, GURL const&, std::basic_string, std::allocator > const&, webkit::WebPluginInfo*, std::basic_string, std::allocator >*)<0000000004a90fc0> +0000000004ae8260 t content::HandleRendererErrorTestParameters(CommandLine const&)<0000000004ae8260> +0000000004ae8450 t content::RendererMessageLoopObserver::RendererMessageLoopObserver()<0000000004ae8450> +0000000004ae8470 t scoped_ptr >::scoped_ptr()<0000000004ae8470> +000000000561d6c0 t v8::Function::Call(v8::Handle, int, v8::Handle*)<000000000561d6c0> +000000000561daa0 t v8::Function::SetName(v8::Handle)<000000000561daa0> +000000000561dba0 t v8::Function::GetName() const<000000000561dba0> +00000000056bac10 t v8::internal::Double::AsUint64() const<00000000056bac10> +00000000056bac20 t v8::internal::Double::IsDenormal() const<00000000056bac20> +00000000056bac60 t v8::internal::Double::Double(double)<00000000056bac60> +00000000056bac90 t v8::internal::double_to_uint64(double)<00000000056bac90> +00000000056bacb0 t v8::internal::StackGuard::StackGuard() +00000000056bace0 t v8::internal::Execution::Call(v8::internal::Handle, v8::internal::Handle, int, v8::internal::Handle*, bool*, bool)<00000000056bace0> +00000000056baf20 t v8::internal::Execution::TryGetFunctionDelegate(v8::internal::Handle, bool*)<00000000056baf20> +00000000056bb110 t v8::internal::Execution::ToObject(v8::internal::Handle, bool*)<00000000056bb110> +00000000056bb200 t v8::internal::Invoke(bool, v8::internal::Handle, v8::internal::Handle, int, v8::internal::Handle*, bool*)<00000000056bb200> +00000000058802e0 t v8::internal::Parser::ParseFunctionDeclaration(v8::internal::ZoneList >*, bool*)<00000000058802e0> +0000000005880520 t v8::internal::Parser::ParseVariableStatement(v8::internal::Parser::VariableDeclarationContext, v8::internal::ZoneList >*, bool*)<0000000005880520> +00000000058805d0 t v8::internal::Parser::ParseImportDeclaration(bool*)<00000000058805d0> +0000000005880fd0 t v8::internal::Parser::ParseStatement(v8::internal::ZoneList >*, bool*)<0000000005880fd0> +0000000005888100 t v8::internal::Parser::ParseAssignmentExpression(bool, bool*)<0000000005888100> +0000000005888e60 t v8::internal::Parser::NewThrowReferenceError(v8::internal::Handle)<0000000005888e60> +0000000005888ed0 t v8::internal::Parser::ParseConditionalExpression(bool, bool*)<0000000005888ed0> +0000000005889060 t v8::internal::Parser::CheckStrictModeLValue(v8::internal::Expression*, char const*, bool*)<0000000005889060> +0000000005889180 t v8::internal::Parser::MarkAsLValue(v8::internal::Expression*)<0000000005889180> +00000000058891f0 t v8::internal::Parser::ParseBinaryExpression(int, bool, bool*)<00000000058891f0> +0000000005889d60 t v8::internal::Precedence(v8::internal::Token::Value, bool)<0000000005889d60> +0000000005889db0 t v8::internal::Parser::ParsePostfixExpression(bool*)<0000000005889db0> +0000000005889f50 t v8::internal::Parser::ParseLeftHandSideExpression(bool*)<0000000005889f50> +0000000005893d30 t v8::internal::Interface::MakeModule(bool*)<0000000005893d30> +0000000005893d80 t v8::internal::Interface::Freeze(bool*)<0000000005893d80> +0000000005893df0 t v8::internal::AstNodeFactory::NewModuleLiteral(v8::internal::Block*, v8::internal::Interface*)<0000000005893df0> +0000000005893e80 t v8::internal::AstNodeFactory::NewModulePath(v8::internal::Module*, v8::internal::Handle)<0000000005893e80> +0000000005893f20 t v8::internal::Interface::Add(v8::internal::Handle, v8::internal::Interface*, v8::internal::Zone*, bool*)<0000000005893f20> +0000000005893f90 t v8::internal::VariableProxy* v8::internal::Scope::NewUnresolved(v8::internal::AstNodeFactory*, v8::internal::Handle, v8::internal::Interface*, int)<0000000005893f90> +0000000005894050 t v8::internal::Interface::NewModule(v8::internal::Zone*)<0000000005894050> +00000000058940b0 t v8::internal::AstNodeFactory::NewModuleVariable(v8::internal::VariableProxy*)<00000000058940b0> +0000000005894150 t void v8::internal::USE >(v8::internal::Handle)<0000000005894150> +0000000005894160 t v8::internal::Parser::Consume(v8::internal::Token::Value)<0000000005894160> +00000000058941d0 t v8::internal::List, v8::internal::ZoneAllocationPolicy>::operator[](int) const<00000000058941d0> +0000000005894280 t v8::internal::Interface::NewUnknown(v8::internal::Zone*)<0000000005894280> +00000000058942e0 t v8::internal::AstNodeFactory::NewImportDeclaration(v8::internal::VariableProxy*, v8::internal::Module*, v8::internal::Scope*)<00000000058942e0> +000000000589ba70 t v8::internal::ImportDeclaration::ImportDeclaration(v8::internal::VariableProxy*, v8::internal::Module*, v8::internal::Scope*)<000000000589ba70> +000000000589bab0 t v8::internal::ImportDeclaration::ImportDeclaration(v8::internal::VariableProxy*, v8::internal::Module*, v8::internal::Scope*)<000000000589bab0> +000000000589bb20 t v8::internal::ModuleVariable::ModuleVariable(v8::internal::VariableProxy*)<000000000589bb20> +000000000589bb50 t v8::internal::ModuleVariable::ModuleVariable(v8::internal::VariableProxy*)<000000000589bb50> +000000000589bbc0 t v8::internal::Module::Module(v8::internal::Interface*, v8::internal::Block*)<000000000589bbc0> +000000000589bc20 t v8::internal::Module::~Module()<000000000589bc20> +000000000589bc40 t v8::internal::Module::~Module()<000000000589bc40> +000000000589bc80 t v8::internal::Module::~Module()<000000000589bc80> +000000000589bca0 t v8::internal::Scope::already_resolved()<000000000589bca0> +000000000589bcc0 t v8::internal::AstNodeFactory::NewVariableProxy(v8::internal::Handle, bool, v8::internal::Interface*, int)<000000000589bcc0> +000000000589bd80 t v8::internal::ZoneList::Add(v8::internal::VariableProxy* const&, v8::internal::Zone*)<000000000589bd80> +000000000589bde0 t v8::internal::List::Add(v8::internal::VariableProxy* const&, v8::internal::ZoneAllocationPolicy)<000000000589bde0> +000000000589be50 t v8::internal::List::ResizeAdd(v8::internal::VariableProxy* const&, v8::internal::ZoneAllocationPolicy)<000000000589be50> +000000000589be90 t v8::internal::List::ResizeAddInternal(v8::internal::VariableProxy* const&, v8::internal::ZoneAllocationPolicy)<000000000589be90> +000000000589bf40 t v8::internal::List::Resize(int, v8::internal::ZoneAllocationPolicy)<000000000589bf40> +000000000589c000 t v8::internal::List::NewData(int, v8::internal::ZoneAllocationPolicy)<000000000589c000> +0000000005f14a50 t WTFAnnotateBenignRaceSized<0000000005f14a50> +0000000005f14a70 t WTFAnnotateHappensBefore<0000000005f14a70> +0000000005f14a90 t WTFAnnotateHappensAfter<0000000005f14a90> +0000000005f14ab0 t WTF::fastMallocForbid()<0000000005f14ab0> +0000000005f14af0 t WTF::initializeIsForbiddenKey()<0000000005f14af0> +0000000005f14b20 t WTF::fastMallocAllow()<0000000005f14b20> +0000000005f14b60 t WTF::Internal::fastMallocMatchFailed(void*)<0000000005f14b60> +0000000005f14b90 t WTF::fastZeroedMalloc(unsigned long)<0000000005f14b90> +0000000005f14bd0 t WTF::fastMalloc(unsigned long)<0000000005f14bd0> +0000000005f14c80 t WTF::fastStrDup(char const*)<0000000005f14c80> +0000000005f14cd0 t WTF::tryFastZeroedMalloc(unsigned long)<0000000005f14cd0> +0000000005f14d70 t WTF::tryFastMalloc(unsigned long)<0000000005f14d70> +0000000005f14e00 t WTF::fastMallocGoodSize(unsigned long)<0000000005f14e00> +0000000005f14e10 t WTF::isForbidden()<0000000005f14e10> +0000000005f14e60 t WTF::tryFastCalloc(unsigned long, unsigned long)<0000000005f14e60> +00000000066bd110 t WTF::RefPtr::RefPtr(WTF::PassRefPtr const&)<00000000066bd110> +00000000066bd140 t WTF::PassRefPtr::PassRefPtr(WebCore::HTMLTextAreaElement*, bool)<00000000066bd140> +00000000066bd180 t WTF::PassRefPtr::PassRefPtr(WebCore::HTMLTextAreaElement*, bool)<00000000066bd180> +00000000066bd1a0 t WebCore::HTMLTextAreaElement::~HTMLTextAreaElement()<00000000066bd1a0> +00000000066bd200 t WebCore::HTMLTitleElement::create(WebCore::QualifiedName const&, WebCore::Document*)<00000000066bd200> +00000000066bd280 t WebCore::HTMLTitleElement::insertedInto(WebCore::ContainerNode*)<00000000066bd280> +00000000066bd310 t WebCore::HTMLTitleElement::removedFrom(WebCore::ContainerNode*)<00000000066bd310> +00000000066bd390 t WebCore::HTMLTitleElement::childrenChanged(bool, WebCore::Node*, WebCore::Node*, int)<00000000066bd390> +00000000066bd4b0 t WebCore::HTMLTitleElement::textWithDirection()<00000000066bd4b0> +00000000066bd5a0 t WebCore::HTMLTitleElement::text() const<00000000066bd5a0> +00000000066bd670 t WebCore::HTMLTitleElement::setText(WTF::String const&)<00000000066bd670> +000000000674ea00 t close_display<000000000674ea00> +000000000674ea30 t base::type_profiler::NewInterceptForTCMalloc(void*, unsigned long, std::type_info const&)<000000000674ea30> +000000000674ea80 t base::type_profiler::DeleteInterceptForTCMalloc(void*, unsigned long, std::type_info const&)<000000000674ea80> +000000000674eac0 t JSC::Yarr::byteCompile(JSC::Yarr::YarrPattern&, WTF::BumpPointerAllocator*)<000000000674eac0> +000000000674eb10 t JSC::Yarr::interpret(JSC::Yarr::BytecodePattern*, WTF::String const&, unsigned int, unsigned int*)<000000000674eb10> +000000000674ec20 t JSC::Yarr::interpret(JSC::Yarr::BytecodePattern*, unsigned char const*, unsigned int, unsigned int, unsigned int*)<000000000674ec20> +000000000674ec70 t JSC::Yarr::interpret(JSC::Yarr::BytecodePattern*, unsigned short const*, unsigned int, unsigned int, unsigned int*)<000000000674ec70> +000000000674ecc0 t bool WTF::isInBounds(unsigned int)<000000000674ecc0> +000000000674ece0 t bool WTF::safeEquals(unsigned int, int)<000000000674ece0> +000000000674ed10 t JSC::Yarr::ByteCompiler::ByteCompiler(JSC::Yarr::YarrPattern&)<000000000674ed10> +000000000674ed40 t JSC::Yarr::ByteCompiler::compile(WTF::BumpPointerAllocator*)<000000000674ed40> +0000000007408020 T mremap<0000000007408020> +0000000007408240 T sbrk<0000000007408240> +000000000740827a T LowLevelAlloc::AllocWithArena(unsigned long, LowLevelAlloc::Arena*)<000000000740827a> +0000000007408312 T LowLevelAlloc::Free(void*)<0000000007408312> +0000000007408462 T LowLevelAlloc::Alloc(unsigned long)<0000000007408462> +00000000074084b1 T __stop_malloc_hook<00000000074084b1> +00000000074084c0 T tc_new +0000000007408500 T tc_delete +0000000007408530 T tc_newarray +0000000007408570 T tc_deletearray +00000000074085a0 T tc_new_nothrow diff --git a/tools/deep_memory_profiler/tests/data/heap.01234.symmap/chrome.uvwxyz.readelf-e b/tools/deep_memory_profiler/tests/data/heap.01234.symmap/chrome.uvwxyz.readelf-e new file mode 100644 index 0000000000..37bd98ea0c --- /dev/null +++ b/tools/deep_memory_profiler/tests/data/heap.01234.symmap/chrome.uvwxyz.readelf-e @@ -0,0 +1,101 @@ +ELF Header: + Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 + Class: ELF64 + Data: 2's complement, little endian + Version: 1 (current) + OS/ABI: UNIX - System V + ABI Version: 0 + Type: DYN (Shared object file) + Machine: Advanced Micro Devices X86-64 + Version: 0x1 + Entry point address: 0x9fbcc0 + Start of program headers: 64 (bytes into file) + Start of section headers: 418040784 (bytes into file) + Flags: 0x0 + Size of this header: 64 (bytes) + Size of program headers: 56 (bytes) + Number of program headers: 10 + Size of section headers: 64 (bytes) + Number of section headers: 47 + Section header string table index: 46 + +Section Headers: + [Nr] Name Type Address Off Size ES Flg Lk Inf Al + [ 0] NULL 0000000000000000 000000 000000 00 0 0 0 + [ 1] .interp PROGBITS 0000000000000270 000270 00001c 00 A 0 0 1 + [ 2] .note.ABI-tag NOTE 000000000000028c 00028c 000020 00 A 0 0 4 + [ 3] .note.gnu.build-id NOTE 00000000000002ac 0002ac 000024 00 A 0 0 4 + [ 4] .dynsym DYNSYM 00000000000002d0 0002d0 00fb70 18 A 5 1 8 + [ 5] .dynstr STRTAB 000000000000fe40 00fe40 00e678 00 A 0 0 1 + [ 6] .gnu.hash GNU_HASH 000000000001e4b8 01e4b8 0001d0 00 A 4 0 8 + [ 7] .gnu.version VERSYM 000000000001e688 01e688 0014f4 02 A 4 0 2 + [ 8] .gnu.version_r VERNEED 000000000001fb7c 01fb7c 0003b0 00 A 5 13 4 + [ 9] .rela.dyn RELA 000000000001ff30 01ff30 9c3210 18 A 4 0 8 + [10] .rela.plt RELA 00000000009e3140 9e3140 00ed30 18 AI 4 12 8 + [11] .init PROGBITS 00000000009f1e70 9f1e70 000018 00 AX 0 0 4 + [12] .plt PROGBITS 00000000009f1e88 9f1e88 009e30 10 AX 0 0 8 + [13] .text PROGBITS 00000000009fbcc0 9fbcc0 6a0c238 00 AX 0 0 16 + [14] malloc_hook PROGBITS 0000000007407f00 7407f00 0005b1 00 AX 0 0 16 + [15] google_malloc PROGBITS 00000000074084c0 74084c0 0005a2 00 AX 0 0 16 + [16] .fini PROGBITS 0000000007408a64 7408a64 00000e 00 AX 0 0 4 + [17] .rodata PROGBITS 0000000007408a80 7408a80 18388f0 00 A 0 0 64 + [18] .gcc_except_table PROGBITS 0000000008c41370 8c41370 0014f4 00 A 0 0 4 + [19] .eh_frame PROGBITS 0000000008c42868 8c42868 1887a54 00 A 0 0 8 + [20] .eh_frame_hdr PROGBITS 000000000a4ca2bc a4ca2bc 66e33c 00 A 0 0 4 + [21] .tbss NOBITS 000000000ab39720 ab38720 000028 00 WAT 0 0 8 + [22] .data.rel.ro.local PROGBITS 000000000ab39720 ab38720 1907e0 00 WA 0 0 16 + [23] .ctors PROGBITS 000000000acc9f00 acc8f00 0001f8 00 WA 0 0 8 + [24] .dtors PROGBITS 000000000acca0f8 acc90f8 000010 00 WA 0 0 8 + [25] .jcr PROGBITS 000000000acca108 acc9108 000008 00 WA 0 0 8 + [26] .data.rel.ro PROGBITS 000000000acca110 acc9110 257380 00 WA 0 0 16 + [27] .dynamic DYNAMIC 000000000af21490 af20490 000520 10 WA 5 0 8 + [28] .got PROGBITS 000000000af219b0 af209b0 01e638 00 WA 0 0 8 + [29] .got.plt PROGBITS 000000000af3ffe8 af3efe8 004f28 00 WA 0 0 8 + [30] .data PROGBITS 000000000af44f10 af43f10 0372b8 00 WA 0 0 16 + [31] .bss NOBITS 000000000af7c1d0 af7b1d0 07895a 00 WA 0 0 16 + [32] .comment PROGBITS 0000000000000000 af7b1c8 00002b 01 MS 0 0 1 + [33] .debug_info PROGBITS 0000000000000000 af7b1f3 2fd3510 00 0 0 1 + [34] .debug_abbrev PROGBITS 0000000000000000 df4e703 156ae4 00 0 0 1 + [35] .debug_aranges PROGBITS 0000000000000000 e0a51e7 000120 00 0 0 1 + [36] .debug_macinfo PROGBITS 0000000000000000 e0a5307 000000 00 0 0 1 + [37] .debug_line PROGBITS 0000000000000000 e0a5307 2e73466 00 0 0 1 + [38] .debug_loc PROGBITS 0000000000000000 10f1876d 000934 00 0 0 1 + [39] .debug_pubtypes PROGBITS 0000000000000000 10f190a1 000000 00 0 0 1 + [40] .debug_str PROGBITS 0000000000000000 10f190a1 fd980a 01 MS 0 0 1 + [41] .debug_ranges PROGBITS 0000000000000000 11ef28ab 08d950 00 0 0 1 + [42] .debug_frame PROGBITS 0000000000000000 11f80200 000058 00 0 0 8 + [43] .note.gnu.gold-version NOTE 0000000000000000 11f80258 00001c 00 0 0 4 + [44] .symtab SYMTAB 0000000000000000 11f80278 1c2e928 18 45 1175494 8 + [45] .strtab STRTAB 0000000000000000 13baeba0 52fde3d 00 0 0 1 + [46] .shstrtab STRTAB 0000000000000000 18eac9dd 0001ec 00 0 0 1 +Key to Flags: + W (write), A (alloc), X (execute), M (merge), S (strings), l (large) + I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown) + O (extra OS processing required) o (OS specific), p (processor specific) + +Program Headers: + Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align + PHDR 0x000040 0x0000000000000040 0x0000000000000040 0x000230 0x000230 R 0x8 + INTERP 0x000270 0x0000000000000270 0x0000000000000270 0x00001c 0x00001c R 0x1 + [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] + LOAD 0x000000 0x0000000000000000 0x0000000000000000 0xab385f8 0xab385f8 R E 0x1000 + LOAD 0xab38720 0x000000000ab39720 0x000000000ab39720 0x442aa8 0x4bb40a RW 0x1000 + DYNAMIC 0xaf20490 0x000000000af21490 0x000000000af21490 0x000520 0x000520 RW 0x8 + NOTE 0x00028c 0x000000000000028c 0x000000000000028c 0x000044 0x000044 R 0x4 + GNU_EH_FRAME 0xa4ca2bc 0x000000000a4ca2bc 0x000000000a4ca2bc 0x66e33c 0x66e33c R 0x4 + GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0 + TLS 0xab38720 0x000000000ab39720 0x000000000ab39720 0x000000 0x000028 R 0x8 + GNU_RELRO 0xab38720 0x000000000ab39720 0x000000000ab39720 0x4068e0 0x4068e0 RW 0x10 + + Section to Segment mapping: + Segment Sections... + 00 + 01 .interp + 02 .interp .note.ABI-tag .note.gnu.build-id .dynsym .dynstr .gnu.hash .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text malloc_hook google_malloc .fini .rodata .gcc_except_table .eh_frame .eh_frame_hdr + 03 .data.rel.ro.local .ctors .dtors .jcr .data.rel.ro .dynamic .got .got.plt .data .bss + 04 .dynamic + 05 .note.ABI-tag .note.gnu.build-id + 06 .eh_frame_hdr + 07 + 08 .tbss + 09 .data.rel.ro.local .ctors .dtors .jcr .data.rel.ro .dynamic .got diff --git a/tools/deep_memory_profiler/tests/data/heap.01234.symmap/files.json b/tools/deep_memory_profiler/tests/data/heap.01234.symmap/files.json new file mode 100644 index 0000000000..64d3449242 --- /dev/null +++ b/tools/deep_memory_profiler/tests/data/heap.01234.symmap/files.json @@ -0,0 +1,12 @@ +{ + "/home/user/chromium/src/out/Debug/chrome": { + "nm": { + "file": "chrome.abcdef.nm", + "format": "bsd", + "mangled": false + }, + "readelf-e": { + "file": "chrome.uvwxyz.readelf-e" + } + } +} \ No newline at end of file diff --git a/tools/deep_memory_profiler/tests/data/heap.01234.symmap/maps b/tools/deep_memory_profiler/tests/data/heap.01234.symmap/maps new file mode 100644 index 0000000000..a2555247d1 --- /dev/null +++ b/tools/deep_memory_profiler/tests/data/heap.01234.symmap/maps @@ -0,0 +1,410 @@ +114147900000-114147935000 rw-p 00000000 00:00 0 +12e93464c000-12e934700000 ---p 00000000 00:00 0 +12e934700000-12e934705000 rw-p 00000000 00:00 0 +12e934705000-12e934706000 ---p 00000000 00:00 0 +12e934706000-12e934707000 rwxp 00000000 00:00 0 +12e934707000-12e934800000 ---p 00000000 00:00 0 +12e934800000-12e934805000 rw-p 00000000 00:00 0 +12e934805000-12e934806000 ---p 00000000 00:00 0 +12e934806000-12e934807000 rwxp 00000000 00:00 0 +12e934807000-12e934900000 ---p 00000000 00:00 0 +12e934900000-12e934905000 rw-p 00000000 00:00 0 +12e934905000-12e934906000 ---p 00000000 00:00 0 +12e934906000-12e9349ff000 rwxp 00000000 00:00 0 +12e9349ff000-12e95464c000 ---p 00000000 00:00 0 +16b0892d7000-16b0892d8000 r-xp 00000000 00:00 0 +19fc4e858000-19fc4e859000 r-xp 00000000 00:00 0 +1ae6500da000-1ae6503ea000 rw-p 00000000 00:00 0 +1c8ad47d9000-1c8ad47da000 r-xp 00000000 00:00 0 +1cca7ce6a000-1cca7ce80000 ---p 00000000 00:00 0 +1cca7ce80000-1cca7cea0000 rw-p 00000000 00:00 0 +1cca7cea0000-1cca7ceca000 ---p 00000000 00:00 0 +2195f26d4000-2195f26d5000 r-xp 00000000 00:00 0 +21c091300000-21c091325000 rw-p 00000000 00:00 0 +277034700000-277034800000 rw-p 00000000 00:00 0 +280bd8000000-280bd8f00000 ---p 00000000 00:00 0 +280bd8f00000-280bd9000000 rw-p 00000000 00:00 0 +280bd9000000-280bda000000 ---p 00000000 00:00 0 +2bb801c00000-2bb801c85000 rw-p 00000000 00:00 0 +30713ff00000-30713ff25000 rw-p 00000000 00:00 0 +31278c7c9000-31278c7ca000 r-xp 00000000 00:00 0 +3190d6f35000-3190d6f36000 rw-p 00000000 00:00 0 +3190d6f36000-3190d7135000 ---p 00000000 00:00 0 +7f62f5a2b000-7f62f607b000 rw-p 00000000 00:00 0 +7f62f607b000-7f62f6301000 rw-- 00000000 00:00 888963090 /SYSV00000000 (deleted) +7f62f6301000-7f62f6311000 rw-p 00000000 00:00 0 +7f62f6311000-7f62f6362000 r--- 00000000 00:00 1323029 /usr/share/fonts/truetype/msttcorefonts/Times_New_Roman.ttf +7f62f6362000-7f62f64d2000 rw-p 00000000 00:00 0 +7f62f64fe000-7f62f6542000 r--- 00000000 00:00 1323020 /usr/share/fonts/truetype/msttcorefonts/Arial.ttf +7f62f6542000-7f62f65b2000 rw-p 00000000 00:00 0 +7f62f65b2000-7f62f6804000 rw-- 00000000 00:00 888930318 /SYSV00000000 (deleted) +7f62f6804000-7f62f6984000 rw-p 00000000 00:00 0 +7f62f6984000-7f62f69a4000 r--- 00000000 00:00 14645040 /run/shm/.org.chromium.Chromium.KmCcUm (deleted) +7f62f69a4000-7f62f6a44000 rw-p 00000000 00:00 0 +7f62f6a44000-7f62f6a45000 ---p 00000000 00:00 0 +7f62f6a45000-7f62f6a55000 rw-p 00000000 00:00 0 +7f62f6a55000-7f62f6a56000 ---p 00000000 00:00 0 +7f62f6a56000-7f62f6af6000 rw-p 00000000 00:00 0 +7f62f6af6000-7f62f6af7000 ---p 00000000 00:00 0 +7f62f6af7000-7f62f72f7000 rw-p 00000000 00:00 0 +7f62f72f7000-7f62f72f8000 ---p 00000000 00:00 0 +7f62f72f8000-7f62f7af8000 rw-p 00000000 00:00 0 +7f62f7af8000-7f62f7bd6000 r-xp 00000000 00:00 6311146 /home/user/chromium/src/out/Debug/libppGoogleNaClPluginChrome.so +7f62f7bd6000-7f62f7bd7000 ---p 000de000 00:00 6311146 /home/user/chromium/src/out/Debug/libppGoogleNaClPluginChrome.so +7f62f7bd7000-7f62f7bda000 r--p 000de000 00:00 6311146 /home/user/chromium/src/out/Debug/libppGoogleNaClPluginChrome.so +7f62f7bda000-7f62f7bdc000 rw-p 000e1000 00:00 6311146 /home/user/chromium/src/out/Debug/libppGoogleNaClPluginChrome.so +7f62f7bdc000-7f62f7c3e000 r-xp 00000000 00:00 422206 /usr/lib/x86_64-linux-gnu/nss/libfreebl3.so +7f62f7c3e000-7f62f7e3d000 ---p 00062000 00:00 422206 /usr/lib/x86_64-linux-gnu/nss/libfreebl3.so +7f62f7e3d000-7f62f7e3f000 r--p 00061000 00:00 422206 /usr/lib/x86_64-linux-gnu/nss/libfreebl3.so +7f62f7e3f000-7f62f7e40000 rw-p 00063000 00:00 422206 /usr/lib/x86_64-linux-gnu/nss/libfreebl3.so +7f62f7e40000-7f62f7e44000 rw-p 00000000 00:00 0 +7f62f7e44000-7f62f7ee2000 r-xp 00000000 00:00 419056 /usr/lib/x86_64-linux-gnu/libsqlite3.so.0.8.6 +7f62f7ee2000-7f62f80e2000 ---p 0009e000 00:00 419056 /usr/lib/x86_64-linux-gnu/libsqlite3.so.0.8.6 +7f62f80e2000-7f62f80e4000 r--p 0009e000 00:00 419056 /usr/lib/x86_64-linux-gnu/libsqlite3.so.0.8.6 +7f62f80e4000-7f62f80e6000 rw-p 000a0000 00:00 419056 /usr/lib/x86_64-linux-gnu/libsqlite3.so.0.8.6 +7f62f80e6000-7f62f80e7000 rw-p 00000000 00:00 0 +7f62f80f6000-7f62f8106000 rw-p 00000000 00:00 0 +7f62f8106000-7f62f813e000 r-xp 00000000 00:00 422227 /usr/lib/x86_64-linux-gnu/nss/libsoftokn3.so +7f62f813e000-7f62f833d000 ---p 00038000 00:00 422227 /usr/lib/x86_64-linux-gnu/nss/libsoftokn3.so +7f62f833d000-7f62f833f000 r--p 00037000 00:00 422227 /usr/lib/x86_64-linux-gnu/nss/libsoftokn3.so +7f62f833f000-7f62f8340000 rw-p 00039000 00:00 422227 /usr/lib/x86_64-linux-gnu/nss/libsoftokn3.so +7f62f8340000-7f62f84fd000 r-xp 00000000 00:00 6311921 /home/user/chromium/src/out/Debug/libffmpegsumo.so +7f62f84fd000-7f62f84fe000 ---p 001bd000 00:00 6311921 /home/user/chromium/src/out/Debug/libffmpegsumo.so +7f62f84fe000-7f62f850e000 r--p 001bd000 00:00 6311921 /home/user/chromium/src/out/Debug/libffmpegsumo.so +7f62f850e000-7f62f8510000 rw-p 001cd000 00:00 6311921 /home/user/chromium/src/out/Debug/libffmpegsumo.so +7f62f8510000-7f62f85b3000 rw-p 00000000 00:00 0 +7f62f85b3000-7f62f8ad7000 r--- 00000000 00:00 6968058 /home/user/chromium/src/out/Debug/resources.pak +7f62f8ad7000-7f62f8b01000 r--- 00000000 00:00 6966120 /home/user/chromium/src/out/Debug/locales/en-US.pak +7f62f8b01000-7f62f8bd6000 r--- 00000000 00:00 6966021 /home/user/chromium/src/out/Debug/chrome_100_percent.pak +7f62f8bd6000-7f62f8f7d000 r--- 00000000 00:00 6966014 /home/user/chromium/src/out/Debug/chrome.pak +7f62f8f7d000-7f62f8fce000 rw-p 00000000 00:00 0 +7f62f8fce000-7f62f9d21000 r--p 00000000 00:00 795151 /usr/lib/locale/locale-archive +7f62f9d21000-7f62fa621000 rw-p 00000000 00:00 0 +7f62fa621000-7f62fa626000 r-xp 00000000 00:00 418995 /usr/lib/x86_64-linux-gnu/libXdmcp.so.6.0.0 +7f62fa626000-7f62fa825000 ---p 00005000 00:00 418995 /usr/lib/x86_64-linux-gnu/libXdmcp.so.6.0.0 +7f62fa825000-7f62fa826000 r--p 00004000 00:00 418995 /usr/lib/x86_64-linux-gnu/libXdmcp.so.6.0.0 +7f62fa826000-7f62fa827000 rw-p 00005000 00:00 418995 /usr/lib/x86_64-linux-gnu/libXdmcp.so.6.0.0 +7f62fa827000-7f62fa829000 r-xp 00000000 00:00 419161 /usr/lib/x86_64-linux-gnu/libXau.so.6.0.0 +7f62fa829000-7f62faa28000 ---p 00002000 00:00 419161 /usr/lib/x86_64-linux-gnu/libXau.so.6.0.0 +7f62faa28000-7f62faa29000 r--p 00001000 00:00 419161 /usr/lib/x86_64-linux-gnu/libXau.so.6.0.0 +7f62faa29000-7f62faa2a000 rw-p 00002000 00:00 419161 /usr/lib/x86_64-linux-gnu/libXau.so.6.0.0 +7f62faa2a000-7f62faa2d000 r-xp 00000000 00:00 262419 /lib/x86_64-linux-gnu/libgpg-error.so.0.8.0 +7f62faa2d000-7f62fac2c000 ---p 00003000 00:00 262419 /lib/x86_64-linux-gnu/libgpg-error.so.0.8.0 +7f62fac2c000-7f62fac2d000 r--p 00002000 00:00 262419 /lib/x86_64-linux-gnu/libgpg-error.so.0.8.0 +7f62fac2d000-7f62fac2e000 rw-p 00003000 00:00 262419 /lib/x86_64-linux-gnu/libgpg-error.so.0.8.0 +7f62fac2e000-7f62fac3f000 r-xp 00000000 00:00 419811 /usr/lib/x86_64-linux-gnu/libp11-kit.so.0.0.0 +7f62fac3f000-7f62fae3e000 ---p 00011000 00:00 419811 /usr/lib/x86_64-linux-gnu/libp11-kit.so.0.0.0 +7f62fae3e000-7f62fae3f000 r--p 00010000 00:00 419811 /usr/lib/x86_64-linux-gnu/libp11-kit.so.0.0.0 +7f62fae3f000-7f62fae40000 rw-p 00011000 00:00 419811 /usr/lib/x86_64-linux-gnu/libp11-kit.so.0.0.0 +7f62fae40000-7f62fae50000 r-xp 00000000 00:00 420094 /usr/lib/x86_64-linux-gnu/libtasn1.so.3.1.12 +7f62fae50000-7f62fb04f000 ---p 00010000 00:00 420094 /usr/lib/x86_64-linux-gnu/libtasn1.so.3.1.12 +7f62fb04f000-7f62fb050000 r--p 0000f000 00:00 420094 /usr/lib/x86_64-linux-gnu/libtasn1.so.3.1.12 +7f62fb050000-7f62fb051000 rw-p 00010000 00:00 420094 /usr/lib/x86_64-linux-gnu/libtasn1.so.3.1.12 +7f62fb051000-7f62fb054000 r-xp 00000000 00:00 262413 /lib/x86_64-linux-gnu/libkeyutils.so.1.4 +7f62fb054000-7f62fb253000 ---p 00003000 00:00 262413 /lib/x86_64-linux-gnu/libkeyutils.so.1.4 +7f62fb253000-7f62fb254000 r--p 00002000 00:00 262413 /lib/x86_64-linux-gnu/libkeyutils.so.1.4 +7f62fb254000-7f62fb255000 rw-p 00003000 00:00 262413 /lib/x86_64-linux-gnu/libkeyutils.so.1.4 +7f62fb255000-7f62fb25c000 r-xp 00000000 00:00 418378 /usr/lib/x86_64-linux-gnu/libkrb5support.so.0.1 +7f62fb25c000-7f62fb45b000 ---p 00007000 00:00 418378 /usr/lib/x86_64-linux-gnu/libkrb5support.so.0.1 +7f62fb45b000-7f62fb45c000 r--p 00006000 00:00 418378 /usr/lib/x86_64-linux-gnu/libkrb5support.so.0.1 +7f62fb45c000-7f62fb45d000 rw-p 00007000 00:00 418378 /usr/lib/x86_64-linux-gnu/libkrb5support.so.0.1 +7f62fb45d000-7f62fb46d000 r-xp 00000000 00:00 419369 /usr/lib/x86_64-linux-gnu/libavahi-client.so.3.2.9 +7f62fb46d000-7f62fb66c000 ---p 00010000 00:00 419369 /usr/lib/x86_64-linux-gnu/libavahi-client.so.3.2.9 +7f62fb66c000-7f62fb66d000 r--p 0000f000 00:00 419369 /usr/lib/x86_64-linux-gnu/libavahi-client.so.3.2.9 +7f62fb66d000-7f62fb66e000 rw-p 00010000 00:00 419369 /usr/lib/x86_64-linux-gnu/libavahi-client.so.3.2.9 +7f62fb66e000-7f62fb679000 r-xp 00000000 00:00 420318 /usr/lib/x86_64-linux-gnu/libavahi-common.so.3.5.3 +7f62fb679000-7f62fb878000 ---p 0000b000 00:00 420318 /usr/lib/x86_64-linux-gnu/libavahi-common.so.3.5.3 +7f62fb878000-7f62fb879000 r--p 0000a000 00:00 420318 /usr/lib/x86_64-linux-gnu/libavahi-common.so.3.5.3 +7f62fb879000-7f62fb87a000 rw-p 0000b000 00:00 420318 /usr/lib/x86_64-linux-gnu/libavahi-common.so.3.5.3 +7f62fb87a000-7f62fb89f000 r-xp 00000000 00:00 420145 /usr/lib/x86_64-linux-gnu/libdbus-glib-1.so.2.2.2 +7f62fb89f000-7f62fba9f000 ---p 00025000 00:00 420145 /usr/lib/x86_64-linux-gnu/libdbus-glib-1.so.2.2.2 +7f62fba9f000-7f62fbaa0000 r--p 00025000 00:00 420145 /usr/lib/x86_64-linux-gnu/libdbus-glib-1.so.2.2.2 +7f62fbaa0000-7f62fbaa1000 rw-p 00026000 00:00 420145 /usr/lib/x86_64-linux-gnu/libdbus-glib-1.so.2.2.2 +7f62fbaa1000-7f62fbaa9000 r-xp 00000000 00:00 401299 /usr/lib/x86_64-linux-gnu/libxcb-render.so.0.0.0 +7f62fbaa9000-7f62fbca9000 ---p 00008000 00:00 401299 /usr/lib/x86_64-linux-gnu/libxcb-render.so.0.0.0 +7f62fbca9000-7f62fbcaa000 r--p 00008000 00:00 401299 /usr/lib/x86_64-linux-gnu/libxcb-render.so.0.0.0 +7f62fbcaa000-7f62fbcab000 rw-p 00009000 00:00 401299 /usr/lib/x86_64-linux-gnu/libxcb-render.so.0.0.0 +7f62fbcab000-7f62fbcad000 r-xp 00000000 00:00 396366 /usr/lib/x86_64-linux-gnu/libxcb-shm.so.0.0.0 +7f62fbcad000-7f62fbeac000 ---p 00002000 00:00 396366 /usr/lib/x86_64-linux-gnu/libxcb-shm.so.0.0.0 +7f62fbeac000-7f62fbead000 r--p 00001000 00:00 396366 /usr/lib/x86_64-linux-gnu/libxcb-shm.so.0.0.0 +7f62fbead000-7f62fbeae000 rw-p 00002000 00:00 396366 /usr/lib/x86_64-linux-gnu/libxcb-shm.so.0.0.0 +7f62fbeae000-7f62fbed4000 r-xp 00000000 00:00 262376 /lib/x86_64-linux-gnu/libpng12.so.0.46.0 +7f62fbed4000-7f62fc0d4000 ---p 00026000 00:00 262376 /lib/x86_64-linux-gnu/libpng12.so.0.46.0 +7f62fc0d4000-7f62fc0d5000 r--p 00026000 00:00 262376 /lib/x86_64-linux-gnu/libpng12.so.0.46.0 +7f62fc0d5000-7f62fc0d6000 rw-p 00027000 00:00 262376 /lib/x86_64-linux-gnu/libpng12.so.0.46.0 +7f62fc0d6000-7f62fc157000 r-xp 00000000 00:00 419692 /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.24.4 +7f62fc157000-7f62fc356000 ---p 00081000 00:00 419692 /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.24.4 +7f62fc356000-7f62fc35c000 r--p 00080000 00:00 419692 /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.24.4 +7f62fc35c000-7f62fc35d000 rw-p 00086000 00:00 419692 /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.24.4 +7f62fc35d000-7f62fc37a000 r-xp 00000000 00:00 262459 /lib/x86_64-linux-gnu/libselinux.so.1 +7f62fc37a000-7f62fc579000 ---p 0001d000 00:00 262459 /lib/x86_64-linux-gnu/libselinux.so.1 +7f62fc579000-7f62fc57a000 r--p 0001c000 00:00 262459 /lib/x86_64-linux-gnu/libselinux.so.1 +7f62fc57a000-7f62fc57b000 rw-p 0001d000 00:00 262459 /lib/x86_64-linux-gnu/libselinux.so.1 +7f62fc57b000-7f62fc57c000 rw-p 00000000 00:00 0 +7f62fc57c000-7f62fc57e000 r-xp 00000000 00:00 419344 /usr/lib/x86_64-linux-gnu/libXinerama.so.1.0.0 +7f62fc57e000-7f62fc77d000 ---p 00002000 00:00 419344 /usr/lib/x86_64-linux-gnu/libXinerama.so.1.0.0 +7f62fc77d000-7f62fc77e000 r--p 00001000 00:00 419344 /usr/lib/x86_64-linux-gnu/libXinerama.so.1.0.0 +7f62fc77e000-7f62fc77f000 rw-p 00002000 00:00 419344 /usr/lib/x86_64-linux-gnu/libXinerama.so.1.0.0 +7f62fc77f000-7f62fc7bb000 r-xp 00000000 00:00 262390 /lib/x86_64-linux-gnu/libpcre.so.3.12.1 +7f62fc7bb000-7f62fc9ba000 ---p 0003c000 00:00 262390 /lib/x86_64-linux-gnu/libpcre.so.3.12.1 +7f62fc9ba000-7f62fc9bb000 r--p 0003b000 00:00 262390 /lib/x86_64-linux-gnu/libpcre.so.3.12.1 +7f62fc9bb000-7f62fc9bc000 rw-p 0003c000 00:00 262390 /lib/x86_64-linux-gnu/libpcre.so.3.12.1 +7f62fc9bc000-7f62fc9c3000 r-xp 00000000 00:00 419528 /usr/lib/x86_64-linux-gnu/libffi.so.6.0.0 +7f62fc9c3000-7f62fcbc2000 ---p 00007000 00:00 419528 /usr/lib/x86_64-linux-gnu/libffi.so.6.0.0 +7f62fcbc2000-7f62fcbc3000 r--p 00006000 00:00 419528 /usr/lib/x86_64-linux-gnu/libffi.so.6.0.0 +7f62fcbc3000-7f62fcbc4000 rw-p 00007000 00:00 419528 /usr/lib/x86_64-linux-gnu/libffi.so.6.0.0 +7f62fcbc4000-7f62fcbe1000 r-xp 00000000 00:00 400302 /usr/lib/x86_64-linux-gnu/libxcb.so.1.1.0 +7f62fcbe1000-7f62fcde0000 ---p 0001d000 00:00 400302 /usr/lib/x86_64-linux-gnu/libxcb.so.1.1.0 +7f62fcde0000-7f62fcde1000 r--p 0001c000 00:00 400302 /usr/lib/x86_64-linux-gnu/libxcb.so.1.1.0 +7f62fcde1000-7f62fcde2000 rw-p 0001d000 00:00 400302 /usr/lib/x86_64-linux-gnu/libxcb.so.1.1.0 +7f62fcde2000-7f62fcf97000 r-xp 00000000 00:00 263480 /lib/x86_64-linux-gnu/libc-2.15.so +7f62fcf97000-7f62fd196000 ---p 001b5000 00:00 263480 /lib/x86_64-linux-gnu/libc-2.15.so +7f62fd196000-7f62fd19a000 r--p 001b4000 00:00 263480 /lib/x86_64-linux-gnu/libc-2.15.so +7f62fd19a000-7f62fd19c000 rw-p 001b8000 00:00 263480 /lib/x86_64-linux-gnu/libc-2.15.so +7f62fd19c000-7f62fd1a1000 rw-p 00000000 00:00 0 +7f62fd1a1000-7f62fd1b6000 r-xp 00000000 00:00 262366 /lib/x86_64-linux-gnu/libgcc_s.so.1 +7f62fd1b6000-7f62fd3b5000 ---p 00015000 00:00 262366 /lib/x86_64-linux-gnu/libgcc_s.so.1 +7f62fd3b5000-7f62fd3b6000 r--p 00014000 00:00 262366 /lib/x86_64-linux-gnu/libgcc_s.so.1 +7f62fd3b6000-7f62fd3b7000 rw-p 00015000 00:00 262366 /lib/x86_64-linux-gnu/libgcc_s.so.1 +7f62fd3b7000-7f62fd499000 r-xp 00000000 00:00 419083 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.16 +7f62fd499000-7f62fd698000 ---p 000e2000 00:00 419083 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.16 +7f62fd698000-7f62fd6a0000 r--p 000e1000 00:00 419083 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.16 +7f62fd6a0000-7f62fd6a2000 rw-p 000e9000 00:00 419083 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.16 +7f62fd6a2000-7f62fd6b7000 rw-p 00000000 00:00 0 +7f62fd6b7000-7f62fd6c3000 r-xp 00000000 00:00 262197 /lib/x86_64-linux-gnu/libudev.so.0.13.0 +7f62fd6c3000-7f62fd8c2000 ---p 0000c000 00:00 262197 /lib/x86_64-linux-gnu/libudev.so.0.13.0 +7f62fd8c2000-7f62fd8c3000 r--p 0000b000 00:00 262197 /lib/x86_64-linux-gnu/libudev.so.0.13.0 +7f62fd8c3000-7f62fd8c4000 rw-p 0000c000 00:00 262197 /lib/x86_64-linux-gnu/libudev.so.0.13.0 +7f62fd8c4000-7f62fd8eb000 r-xp 00000000 00:00 262435 /lib/x86_64-linux-gnu/libexpat.so.1.5.2 +7f62fd8eb000-7f62fdaeb000 ---p 00027000 00:00 262435 /lib/x86_64-linux-gnu/libexpat.so.1.5.2 +7f62fdaeb000-7f62fdaed000 r--p 00027000 00:00 262435 /lib/x86_64-linux-gnu/libexpat.so.1.5.2 +7f62fdaed000-7f62fdaee000 rw-p 00029000 00:00 262435 /lib/x86_64-linux-gnu/libexpat.so.1.5.2 +7f62fdaee000-7f62fdafd000 r-xp 00000000 00:00 262446 /lib/x86_64-linux-gnu/libbz2.so.1.0.4 +7f62fdafd000-7f62fdcfc000 ---p 0000f000 00:00 262446 /lib/x86_64-linux-gnu/libbz2.so.1.0.4 +7f62fdcfc000-7f62fdcfd000 r--p 0000e000 00:00 262446 /lib/x86_64-linux-gnu/libbz2.so.1.0.4 +7f62fdcfd000-7f62fdcfe000 rw-p 0000f000 00:00 262446 /lib/x86_64-linux-gnu/libbz2.so.1.0.4 +7f62fdcfe000-7f62fddf9000 r-xp 00000000 00:00 296562 /lib/x86_64-linux-gnu/libm-2.15.so +7f62fddf9000-7f62fdff8000 ---p 000fb000 00:00 296562 /lib/x86_64-linux-gnu/libm-2.15.so +7f62fdff8000-7f62fdff9000 r--p 000fa000 00:00 296562 /lib/x86_64-linux-gnu/libm-2.15.so +7f62fdff9000-7f62fdffa000 rw-p 000fb000 00:00 296562 /lib/x86_64-linux-gnu/libm-2.15.so +7f62fdffa000-7f62fe003000 r-xp 00000000 00:00 296563 /lib/x86_64-linux-gnu/libcrypt-2.15.so +7f62fe003000-7f62fe203000 ---p 00009000 00:00 296563 /lib/x86_64-linux-gnu/libcrypt-2.15.so +7f62fe203000-7f62fe204000 r--p 00009000 00:00 296563 /lib/x86_64-linux-gnu/libcrypt-2.15.so +7f62fe204000-7f62fe205000 rw-p 0000a000 00:00 296563 /lib/x86_64-linux-gnu/libcrypt-2.15.so +7f62fe205000-7f62fe233000 rw-p 00000000 00:00 0 +7f62fe233000-7f62fe249000 r-xp 00000000 00:00 262537 /lib/x86_64-linux-gnu/libz.so.1.2.3.4 +7f62fe249000-7f62fe448000 ---p 00016000 00:00 262537 /lib/x86_64-linux-gnu/libz.so.1.2.3.4 +7f62fe448000-7f62fe449000 r--p 00015000 00:00 262537 /lib/x86_64-linux-gnu/libz.so.1.2.3.4 +7f62fe449000-7f62fe44a000 rw-p 00016000 00:00 262537 /lib/x86_64-linux-gnu/libz.so.1.2.3.4 +7f62fe44a000-7f62fe4c4000 r-xp 00000000 00:00 262361 /lib/x86_64-linux-gnu/libgcrypt.so.11.7.0 +7f62fe4c4000-7f62fe6c4000 ---p 0007a000 00:00 262361 /lib/x86_64-linux-gnu/libgcrypt.so.11.7.0 +7f62fe6c4000-7f62fe6c5000 r--p 0007a000 00:00 262361 /lib/x86_64-linux-gnu/libgcrypt.so.11.7.0 +7f62fe6c5000-7f62fe6c8000 rw-p 0007b000 00:00 262361 /lib/x86_64-linux-gnu/libgcrypt.so.11.7.0 +7f62fe6c8000-7f62fe77c000 r-xp 00000000 00:00 419320 /usr/lib/x86_64-linux-gnu/libgnutls.so.26.21.8 +7f62fe77c000-7f62fe97c000 ---p 000b4000 00:00 419320 /usr/lib/x86_64-linux-gnu/libgnutls.so.26.21.8 +7f62fe97c000-7f62fe982000 r--p 000b4000 00:00 419320 /usr/lib/x86_64-linux-gnu/libgnutls.so.26.21.8 +7f62fe982000-7f62fe983000 rw-p 000ba000 00:00 419320 /usr/lib/x86_64-linux-gnu/libgnutls.so.26.21.8 +7f62fe983000-7f62fe984000 rw-p 00000000 00:00 0 +7f62fe984000-7f62fe987000 r-xp 00000000 00:00 262402 /lib/x86_64-linux-gnu/libcom_err.so.2.1 +7f62fe987000-7f62feb86000 ---p 00003000 00:00 262402 /lib/x86_64-linux-gnu/libcom_err.so.2.1 +7f62feb86000-7f62feb87000 r--p 00002000 00:00 262402 /lib/x86_64-linux-gnu/libcom_err.so.2.1 +7f62feb87000-7f62feb88000 rw-p 00003000 00:00 262402 /lib/x86_64-linux-gnu/libcom_err.so.2.1 +7f62feb88000-7f62febad000 r-xp 00000000 00:00 419096 /usr/lib/x86_64-linux-gnu/libk5crypto.so.3.1 +7f62febad000-7f62fedad000 ---p 00025000 00:00 419096 /usr/lib/x86_64-linux-gnu/libk5crypto.so.3.1 +7f62fedad000-7f62fedae000 r--p 00025000 00:00 419096 /usr/lib/x86_64-linux-gnu/libk5crypto.so.3.1 +7f62fedae000-7f62fedaf000 rw-p 00026000 00:00 419096 /usr/lib/x86_64-linux-gnu/libk5crypto.so.3.1 +7f62fedaf000-7f62fedb0000 rw-p 00000000 00:00 0 +7f62fedb0000-7f62fee74000 r-xp 00000000 00:00 420204 /usr/lib/x86_64-linux-gnu/libkrb5.so.3.3 +7f62fee74000-7f62ff073000 ---p 000c4000 00:00 420204 /usr/lib/x86_64-linux-gnu/libkrb5.so.3.3 +7f62ff073000-7f62ff07d000 r--p 000c3000 00:00 420204 /usr/lib/x86_64-linux-gnu/libkrb5.so.3.3 +7f62ff07d000-7f62ff07e000 rw-p 000cd000 00:00 420204 /usr/lib/x86_64-linux-gnu/libkrb5.so.3.3 +7f62ff07e000-7f62ff0b9000 r-xp 00000000 00:00 420086 /usr/lib/x86_64-linux-gnu/libgssapi_krb5.so.2.2 +7f62ff0b9000-7f62ff2b9000 ---p 0003b000 00:00 420086 /usr/lib/x86_64-linux-gnu/libgssapi_krb5.so.2.2 +7f62ff2b9000-7f62ff2ba000 r--p 0003b000 00:00 420086 /usr/lib/x86_64-linux-gnu/libgssapi_krb5.so.2.2 +7f62ff2ba000-7f62ff2bc000 rw-p 0003c000 00:00 420086 /usr/lib/x86_64-linux-gnu/libgssapi_krb5.so.2.2 +7f62ff2bc000-7f62ff30c000 r-xp 00000000 00:00 444357 /usr/lib/x86_64-linux-gnu/libcups.so.2 +7f62ff30c000-7f62ff50b000 ---p 00050000 00:00 444357 /usr/lib/x86_64-linux-gnu/libcups.so.2 +7f62ff50b000-7f62ff50f000 r--p 0004f000 00:00 444357 /usr/lib/x86_64-linux-gnu/libcups.so.2 +7f62ff50f000-7f62ff510000 rw-p 00053000 00:00 444357 /usr/lib/x86_64-linux-gnu/libcups.so.2 +7f62ff510000-7f62ff515000 r-xp 00000000 00:00 419317 /usr/lib/x86_64-linux-gnu/libXfixes.so.3.1.0 +7f62ff515000-7f62ff714000 ---p 00005000 00:00 419317 /usr/lib/x86_64-linux-gnu/libXfixes.so.3.1.0 +7f62ff714000-7f62ff715000 r--p 00004000 00:00 419317 /usr/lib/x86_64-linux-gnu/libXfixes.so.3.1.0 +7f62ff715000-7f62ff716000 rw-p 00005000 00:00 419317 /usr/lib/x86_64-linux-gnu/libXfixes.so.3.1.0 +7f62ff716000-7f62ff718000 r-xp 00000000 00:00 419868 /usr/lib/x86_64-linux-gnu/libXdamage.so.1.1.0 +7f62ff718000-7f62ff917000 ---p 00002000 00:00 419868 /usr/lib/x86_64-linux-gnu/libXdamage.so.1.1.0 +7f62ff917000-7f62ff918000 r--p 00001000 00:00 419868 /usr/lib/x86_64-linux-gnu/libXdamage.so.1.1.0 +7f62ff918000-7f62ff919000 rw-p 00002000 00:00 419868 /usr/lib/x86_64-linux-gnu/libXdamage.so.1.1.0 +7f62ff919000-7f62ff9ff000 r-xp 00000000 00:00 419887 /usr/lib/x86_64-linux-gnu/libasound.so.2.0.0 +7f62ff9ff000-7f62ffbff000 ---p 000e6000 00:00 419887 /usr/lib/x86_64-linux-gnu/libasound.so.2.0.0 +7f62ffbff000-7f62ffc05000 r--p 000e6000 00:00 419887 /usr/lib/x86_64-linux-gnu/libasound.so.2.0.0 +7f62ffc05000-7f62ffc06000 rw-p 000ec000 00:00 419887 /usr/lib/x86_64-linux-gnu/libasound.so.2.0.0 +7f62ffc06000-7f62ffc08000 r-xp 00000000 00:00 419816 /usr/lib/x86_64-linux-gnu/libXcomposite.so.1.0.0 +7f62ffc08000-7f62ffe07000 ---p 00002000 00:00 419816 /usr/lib/x86_64-linux-gnu/libXcomposite.so.1.0.0 +7f62ffe07000-7f62ffe08000 r--p 00001000 00:00 419816 /usr/lib/x86_64-linux-gnu/libXcomposite.so.1.0.0 +7f62ffe08000-7f62ffe09000 rw-p 00002000 00:00 419816 /usr/lib/x86_64-linux-gnu/libXcomposite.so.1.0.0 +7f62ffe09000-7f62ffe21000 r-xp 00000000 00:00 263482 /lib/x86_64-linux-gnu/libpthread-2.15.so +7f62ffe21000-7f6300020000 ---p 00018000 00:00 263482 /lib/x86_64-linux-gnu/libpthread-2.15.so +7f6300020000-7f6300021000 r--p 00017000 00:00 263482 /lib/x86_64-linux-gnu/libpthread-2.15.so +7f6300021000-7f6300022000 rw-p 00018000 00:00 263482 /lib/x86_64-linux-gnu/libpthread-2.15.so +7f6300022000-7f6300026000 rw-p 00000000 00:00 0 +7f6300026000-7f6300068000 r-xp 00000000 00:00 262204 /lib/x86_64-linux-gnu/libdbus-1.so.3.5.8 +7f6300068000-7f6300268000 ---p 00042000 00:00 262204 /lib/x86_64-linux-gnu/libdbus-1.so.3.5.8 +7f6300268000-7f6300269000 r--p 00042000 00:00 262204 /lib/x86_64-linux-gnu/libdbus-1.so.3.5.8 +7f6300269000-7f630026a000 rw-p 00043000 00:00 262204 /lib/x86_64-linux-gnu/libdbus-1.so.3.5.8 +7f630026a000-7f6300282000 r-xp 00000000 00:00 289099 /lib/x86_64-linux-gnu/libresolv-2.15.so +7f6300282000-7f6300482000 ---p 00018000 00:00 289099 /lib/x86_64-linux-gnu/libresolv-2.15.so +7f6300482000-7f6300483000 r--p 00018000 00:00 289099 /lib/x86_64-linux-gnu/libresolv-2.15.so +7f6300483000-7f6300484000 rw-p 00019000 00:00 289099 /lib/x86_64-linux-gnu/libresolv-2.15.so +7f6300484000-7f6300486000 rw-p 00000000 00:00 0 +7f6300486000-7f63004b3000 r-xp 00000000 00:00 419655 /usr/lib/x86_64-linux-gnu/libgconf-2.so.4.1.5 +7f63004b3000-7f63006b2000 ---p 0002d000 00:00 419655 /usr/lib/x86_64-linux-gnu/libgconf-2.so.4.1.5 +7f63006b2000-7f63006b3000 r--p 0002c000 00:00 419655 /usr/lib/x86_64-linux-gnu/libgconf-2.so.4.1.5 +7f63006b3000-7f63006b4000 rw-p 0002d000 00:00 419655 /usr/lib/x86_64-linux-gnu/libgconf-2.so.4.1.5 +7f63006b4000-7f63006ee000 r-xp 00000000 00:00 422225 /usr/lib/x86_64-linux-gnu/libnspr4.so +7f63006ee000-7f63008ee000 ---p 0003a000 00:00 422225 /usr/lib/x86_64-linux-gnu/libnspr4.so +7f63008ee000-7f63008ef000 r--p 0003a000 00:00 422225 /usr/lib/x86_64-linux-gnu/libnspr4.so +7f63008ef000-7f63008f1000 rw-p 0003b000 00:00 422225 /usr/lib/x86_64-linux-gnu/libnspr4.so +7f63008f1000-7f63008f3000 rw-p 00000000 00:00 0 +7f63008f3000-7f63008f7000 r-xp 00000000 00:00 422220 /usr/lib/x86_64-linux-gnu/libplc4.so +7f63008f7000-7f6300af6000 ---p 00004000 00:00 422220 /usr/lib/x86_64-linux-gnu/libplc4.so +7f6300af6000-7f6300af7000 r--p 00003000 00:00 422220 /usr/lib/x86_64-linux-gnu/libplc4.so +7f6300af7000-7f6300af8000 rw-p 00004000 00:00 422220 /usr/lib/x86_64-linux-gnu/libplc4.so +7f6300af8000-7f6300afb000 r-xp 00000000 00:00 422218 /usr/lib/x86_64-linux-gnu/libplds4.so +7f6300afb000-7f6300cfa000 ---p 00003000 00:00 422218 /usr/lib/x86_64-linux-gnu/libplds4.so +7f6300cfa000-7f6300cfb000 r--p 00002000 00:00 422218 /usr/lib/x86_64-linux-gnu/libplds4.so +7f6300cfb000-7f6300cfc000 rw-p 00003000 00:00 422218 /usr/lib/x86_64-linux-gnu/libplds4.so +7f6300cfc000-7f6300d1f000 r-xp 00000000 00:00 422231 /usr/lib/x86_64-linux-gnu/libsmime3.so +7f6300d1f000-7f6300f1f000 ---p 00023000 00:00 422231 /usr/lib/x86_64-linux-gnu/libsmime3.so +7f6300f1f000-7f6300f22000 r--p 00023000 00:00 422231 /usr/lib/x86_64-linux-gnu/libsmime3.so +7f6300f22000-7f6300f23000 rw-p 00026000 00:00 422231 /usr/lib/x86_64-linux-gnu/libsmime3.so +7f6300f23000-7f6300f44000 r-xp 00000000 00:00 420737 /usr/lib/x86_64-linux-gnu/libnssutil3.so +7f6300f44000-7f6301143000 ---p 00021000 00:00 420737 /usr/lib/x86_64-linux-gnu/libnssutil3.so +7f6301143000-7f6301149000 r--p 00020000 00:00 420737 /usr/lib/x86_64-linux-gnu/libnssutil3.so +7f6301149000-7f630114a000 rw-p 00026000 00:00 420737 /usr/lib/x86_64-linux-gnu/libnssutil3.so +7f630114a000-7f6301249000 r-xp 00000000 00:00 420704 /usr/lib/x86_64-linux-gnu/libnss3.so +7f6301249000-7f6301448000 ---p 000ff000 00:00 420704 /usr/lib/x86_64-linux-gnu/libnss3.so +7f6301448000-7f630144d000 r--p 000fe000 00:00 420704 /usr/lib/x86_64-linux-gnu/libnss3.so +7f630144d000-7f630144f000 rw-p 00103000 00:00 420704 /usr/lib/x86_64-linux-gnu/libnss3.so +7f630144f000-7f6301451000 rw-p 00000000 00:00 0 +7f6301451000-7f6301485000 r-xp 00000000 00:00 419748 /usr/lib/x86_64-linux-gnu/libfontconfig.so.1.4.4 +7f6301485000-7f6301685000 ---p 00034000 00:00 419748 /usr/lib/x86_64-linux-gnu/libfontconfig.so.1.4.4 +7f6301685000-7f6301686000 r--p 00034000 00:00 419748 /usr/lib/x86_64-linux-gnu/libfontconfig.so.1.4.4 +7f6301686000-7f6301687000 rw-p 00035000 00:00 419748 /usr/lib/x86_64-linux-gnu/libfontconfig.so.1.4.4 +7f6301687000-7f630171d000 r-xp 00000000 00:00 420767 /usr/lib/x86_64-linux-gnu/libfreetype.so.6.8.0 +7f630171d000-7f630191c000 ---p 00096000 00:00 420767 /usr/lib/x86_64-linux-gnu/libfreetype.so.6.8.0 +7f630191c000-7f6301922000 r--p 00095000 00:00 420767 /usr/lib/x86_64-linux-gnu/libfreetype.so.6.8.0 +7f6301922000-7f6301923000 rw-p 0009b000 00:00 420767 /usr/lib/x86_64-linux-gnu/libfreetype.so.6.8.0 +7f6301923000-7f6301969000 r-xp 00000000 00:00 419045 /usr/lib/x86_64-linux-gnu/libpango-1.0.so.0.3000.0 +7f6301969000-7f6301b69000 ---p 00046000 00:00 419045 /usr/lib/x86_64-linux-gnu/libpango-1.0.so.0.3000.0 +7f6301b69000-7f6301b6b000 r--p 00046000 00:00 419045 /usr/lib/x86_64-linux-gnu/libpango-1.0.so.0.3000.0 +7f6301b6b000-7f6301b6c000 rw-p 00048000 00:00 419045 /usr/lib/x86_64-linux-gnu/libpango-1.0.so.0.3000.0 +7f6301b6c000-7f6301c25000 r-xp 00000000 00:00 418996 /usr/lib/x86_64-linux-gnu/libcairo.so.2.11000.2 +7f6301c25000-7f6301e24000 ---p 000b9000 00:00 418996 /usr/lib/x86_64-linux-gnu/libcairo.so.2.11000.2 +7f6301e24000-7f6301e26000 r--p 000b8000 00:00 418996 /usr/lib/x86_64-linux-gnu/libcairo.so.2.11000.2 +7f6301e26000-7f6301e27000 rw-p 000ba000 00:00 418996 /usr/lib/x86_64-linux-gnu/libcairo.so.2.11000.2 +7f6301e27000-7f6301e2a000 rw-p 00000000 00:00 0 +7f6301e2a000-7f6301e48000 r-xp 00000000 00:00 419133 /usr/lib/x86_64-linux-gnu/libgdk_pixbuf-2.0.so.0.2600.1 +7f6301e48000-7f6302048000 ---p 0001e000 00:00 419133 /usr/lib/x86_64-linux-gnu/libgdk_pixbuf-2.0.so.0.2600.1 +7f6302048000-7f6302049000 r--p 0001e000 00:00 419133 /usr/lib/x86_64-linux-gnu/libgdk_pixbuf-2.0.so.0.2600.1 +7f6302049000-7f630204a000 rw-p 0001f000 00:00 419133 /usr/lib/x86_64-linux-gnu/libgdk_pixbuf-2.0.so.0.2600.1 +7f630204a000-7f6302055000 r-xp 00000000 00:00 419820 /usr/lib/x86_64-linux-gnu/libpangocairo-1.0.so.0.3000.0 +7f6302055000-7f6302254000 ---p 0000b000 00:00 419820 /usr/lib/x86_64-linux-gnu/libpangocairo-1.0.so.0.3000.0 +7f6302254000-7f6302255000 r--p 0000a000 00:00 419820 /usr/lib/x86_64-linux-gnu/libpangocairo-1.0.so.0.3000.0 +7f6302255000-7f6302256000 rw-p 0000b000 00:00 419820 /usr/lib/x86_64-linux-gnu/libpangocairo-1.0.so.0.3000.0 +7f6302256000-7f630227f000 r-xp 00000000 00:00 419157 /usr/lib/x86_64-linux-gnu/libpangoft2-1.0.so.0.3000.0 +7f630227f000-7f630247e000 ---p 00029000 00:00 419157 /usr/lib/x86_64-linux-gnu/libpangoft2-1.0.so.0.3000.0 +7f630247e000-7f630247f000 r--p 00028000 00:00 419157 /usr/lib/x86_64-linux-gnu/libpangoft2-1.0.so.0.3000.0 +7f630247f000-7f6302480000 rw-p 00029000 00:00 419157 /usr/lib/x86_64-linux-gnu/libpangoft2-1.0.so.0.3000.0 +7f6302480000-7f63025c8000 r-xp 00000000 00:00 419294 /usr/lib/x86_64-linux-gnu/libgio-2.0.so.0.3200.3 +7f63025c8000-7f63027c7000 ---p 00148000 00:00 419294 /usr/lib/x86_64-linux-gnu/libgio-2.0.so.0.3200.3 +7f63027c7000-7f63027cb000 r--p 00147000 00:00 419294 /usr/lib/x86_64-linux-gnu/libgio-2.0.so.0.3200.3 +7f63027cb000-7f63027cd000 rw-p 0014b000 00:00 419294 /usr/lib/x86_64-linux-gnu/libgio-2.0.so.0.3200.3 +7f63027cd000-7f63027cf000 rw-p 00000000 00:00 0 +7f63027cf000-7f63027ee000 r-xp 00000000 00:00 420188 /usr/lib/x86_64-linux-gnu/libatk-1.0.so.0.20409.1 +7f63027ee000-7f63029ee000 ---p 0001f000 00:00 420188 /usr/lib/x86_64-linux-gnu/libatk-1.0.so.0.20409.1 +7f63029ee000-7f63029f0000 r--p 0001f000 00:00 420188 /usr/lib/x86_64-linux-gnu/libatk-1.0.so.0.20409.1 +7f63029f0000-7f63029f1000 rw-p 00021000 00:00 420188 /usr/lib/x86_64-linux-gnu/libatk-1.0.so.0.20409.1 +7f63029f1000-7f6302a9e000 r-xp 00000000 00:00 419013 /usr/lib/x86_64-linux-gnu/libgdk-x11-2.0.so.0.2400.10 +7f6302a9e000-7f6302c9d000 ---p 000ad000 00:00 419013 /usr/lib/x86_64-linux-gnu/libgdk-x11-2.0.so.0.2400.10 +7f6302c9d000-7f6302ca1000 r--p 000ac000 00:00 419013 /usr/lib/x86_64-linux-gnu/libgdk-x11-2.0.so.0.2400.10 +7f6302ca1000-7f6302ca3000 rw-p 000b0000 00:00 419013 /usr/lib/x86_64-linux-gnu/libgdk-x11-2.0.so.0.2400.10 +7f6302ca3000-7f63030d0000 r-xp 00000000 00:00 419385 /usr/lib/x86_64-linux-gnu/libgtk-x11-2.0.so.0.2400.10 +7f63030d0000-7f63032d0000 ---p 0042d000 00:00 419385 /usr/lib/x86_64-linux-gnu/libgtk-x11-2.0.so.0.2400.10 +7f63032d0000-7f63032d7000 r--p 0042d000 00:00 419385 /usr/lib/x86_64-linux-gnu/libgtk-x11-2.0.so.0.2400.10 +7f63032d7000-7f63032db000 rw-p 00434000 00:00 419385 /usr/lib/x86_64-linux-gnu/libgtk-x11-2.0.so.0.2400.10 +7f63032db000-7f63032dd000 rw-p 00000000 00:00 0 +7f63032dd000-7f63032eb000 r-xp 00000000 00:00 419600 /usr/lib/x86_64-linux-gnu/libXi.so.6.1.0 +7f63032eb000-7f63034ea000 ---p 0000e000 00:00 419600 /usr/lib/x86_64-linux-gnu/libXi.so.6.1.0 +7f63034ea000-7f63034eb000 r--p 0000d000 00:00 419600 /usr/lib/x86_64-linux-gnu/libXi.so.6.1.0 +7f63034eb000-7f63034ec000 rw-p 0000e000 00:00 419600 /usr/lib/x86_64-linux-gnu/libXi.so.6.1.0 +7f63034ec000-7f63035de000 r-xp 00000000 00:00 262368 /lib/x86_64-linux-gnu/libglib-2.0.so.0.3200.3 +7f63035de000-7f63037de000 ---p 000f2000 00:00 262368 /lib/x86_64-linux-gnu/libglib-2.0.so.0.3200.3 +7f63037de000-7f63037df000 r--p 000f2000 00:00 262368 /lib/x86_64-linux-gnu/libglib-2.0.so.0.3200.3 +7f63037df000-7f63037e0000 rw-p 000f3000 00:00 262368 /lib/x86_64-linux-gnu/libglib-2.0.so.0.3200.3 +7f63037e0000-7f63037e1000 rw-p 00000000 00:00 0 +7f63037e1000-7f63037e2000 r-xp 00000000 00:00 419135 /usr/lib/x86_64-linux-gnu/libgthread-2.0.so.0.3200.3 +7f63037e2000-7f63039e1000 ---p 00001000 00:00 419135 /usr/lib/x86_64-linux-gnu/libgthread-2.0.so.0.3200.3 +7f63039e1000-7f63039e2000 r--p 00000000 00:00 419135 /usr/lib/x86_64-linux-gnu/libgthread-2.0.so.0.3200.3 +7f63039e2000-7f63039e3000 rw-p 00001000 00:00 419135 /usr/lib/x86_64-linux-gnu/libgthread-2.0.so.0.3200.3 +7f63039e3000-7f6303a30000 r-xp 00000000 00:00 419601 /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.3200.3 +7f6303a30000-7f6303c30000 ---p 0004d000 00:00 419601 /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.3200.3 +7f6303c30000-7f6303c31000 r--p 0004d000 00:00 419601 /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.3200.3 +7f6303c31000-7f6303c32000 rw-p 0004e000 00:00 419601 /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.3200.3 +7f6303c32000-7f6303c35000 r-xp 00000000 00:00 420150 /usr/lib/x86_64-linux-gnu/libgmodule-2.0.so.0.3200.3 +7f6303c35000-7f6303e34000 ---p 00003000 00:00 420150 /usr/lib/x86_64-linux-gnu/libgmodule-2.0.so.0.3200.3 +7f6303e34000-7f6303e35000 r--p 00002000 00:00 420150 /usr/lib/x86_64-linux-gnu/libgmodule-2.0.so.0.3200.3 +7f6303e35000-7f6303e36000 rw-p 00003000 00:00 420150 /usr/lib/x86_64-linux-gnu/libgmodule-2.0.so.0.3200.3 +7f6303e36000-7f6303e38000 r-xp 00000000 00:00 263479 /lib/x86_64-linux-gnu/libdl-2.15.so +7f6303e38000-7f6304038000 ---p 00002000 00:00 263479 /lib/x86_64-linux-gnu/libdl-2.15.so +7f6304038000-7f6304039000 r--p 00002000 00:00 263479 /lib/x86_64-linux-gnu/libdl-2.15.so +7f6304039000-7f630403a000 rw-p 00003000 00:00 263479 /lib/x86_64-linux-gnu/libdl-2.15.so +7f630403a000-7f6304041000 r-xp 00000000 00:00 265235 /lib/x86_64-linux-gnu/librt-2.15.so +7f6304041000-7f6304240000 ---p 00007000 00:00 265235 /lib/x86_64-linux-gnu/librt-2.15.so +7f6304240000-7f6304241000 r--p 00006000 00:00 265235 /lib/x86_64-linux-gnu/librt-2.15.so +7f6304241000-7f6304242000 rw-p 00007000 00:00 265235 /lib/x86_64-linux-gnu/librt-2.15.so +7f6304242000-7f6304252000 r-xp 00000000 00:00 419652 /usr/lib/x86_64-linux-gnu/libXext.so.6.4.0 +7f6304252000-7f6304451000 ---p 00010000 00:00 419652 /usr/lib/x86_64-linux-gnu/libXext.so.6.4.0 +7f6304451000-7f6304452000 r--p 0000f000 00:00 419652 /usr/lib/x86_64-linux-gnu/libXext.so.6.4.0 +7f6304452000-7f6304453000 rw-p 00010000 00:00 419652 /usr/lib/x86_64-linux-gnu/libXext.so.6.4.0 +7f6304453000-7f6304455000 r-xp 00000000 00:00 420132 /usr/lib/x86_64-linux-gnu/libXss.so.1.0.0 +7f6304455000-7f6304655000 ---p 00002000 00:00 420132 /usr/lib/x86_64-linux-gnu/libXss.so.1.0.0 +7f6304655000-7f6304656000 r--p 00002000 00:00 420132 /usr/lib/x86_64-linux-gnu/libXss.so.1.0.0 +7f6304656000-7f6304657000 rw-p 00003000 00:00 420132 /usr/lib/x86_64-linux-gnu/libXss.so.1.0.0 +7f6304657000-7f6304660000 r-xp 00000000 00:00 420211 /usr/lib/x86_64-linux-gnu/libXrender.so.1.3.0 +7f6304660000-7f630485f000 ---p 00009000 00:00 420211 /usr/lib/x86_64-linux-gnu/libXrender.so.1.3.0 +7f630485f000-7f6304860000 r--p 00008000 00:00 420211 /usr/lib/x86_64-linux-gnu/libXrender.so.1.3.0 +7f6304860000-7f6304861000 rw-p 00009000 00:00 420211 /usr/lib/x86_64-linux-gnu/libXrender.so.1.3.0 +7f6304861000-7f6304869000 r-xp 00000000 00:00 419998 /usr/lib/x86_64-linux-gnu/libXrandr.so.2.2.0 +7f6304869000-7f6304a68000 ---p 00008000 00:00 419998 /usr/lib/x86_64-linux-gnu/libXrandr.so.2.2.0 +7f6304a68000-7f6304a69000 r--p 00007000 00:00 419998 /usr/lib/x86_64-linux-gnu/libXrandr.so.2.2.0 +7f6304a69000-7f6304a6a000 rw-p 00008000 00:00 419998 /usr/lib/x86_64-linux-gnu/libXrandr.so.2.2.0 +7f6304a6a000-7f6304a73000 r-xp 00000000 00:00 419293 /usr/lib/x86_64-linux-gnu/libXcursor.so.1.0.2 +7f6304a73000-7f6304c72000 ---p 00009000 00:00 419293 /usr/lib/x86_64-linux-gnu/libXcursor.so.1.0.2 +7f6304c72000-7f6304c73000 r--p 00008000 00:00 419293 /usr/lib/x86_64-linux-gnu/libXcursor.so.1.0.2 +7f6304c73000-7f6304c74000 rw-p 00009000 00:00 419293 /usr/lib/x86_64-linux-gnu/libXcursor.so.1.0.2 +7f6304c74000-7f6304da3000 r-xp 00000000 00:00 419233 /usr/lib/x86_64-linux-gnu/libX11.so.6.3.0 +7f6304da3000-7f6304fa3000 ---p 0012f000 00:00 419233 /usr/lib/x86_64-linux-gnu/libX11.so.6.3.0 +7f6304fa3000-7f6304fa4000 r--p 0012f000 00:00 419233 /usr/lib/x86_64-linux-gnu/libX11.so.6.3.0 +7f6304fa4000-7f6304fa8000 rw-p 00130000 00:00 419233 /usr/lib/x86_64-linux-gnu/libX11.so.6.3.0 +7f6304fa8000-7f6304fca000 r-xp 00000000 00:00 297771 /lib/x86_64-linux-gnu/ld-2.15.so +7f6304fcd000-7f6304fce000 r--- 00000000 00:00 14846136 /run/shm/.org.chromium.Chromium.zsFhgx (deleted) +7f6304fce000-7f6304fdf000 rw-p 00000000 00:00 0 +7f6304fdf000-7f6305107000 rw-p 00000000 00:00 0 +7f6305107000-7f6305108000 ---p 00000000 00:00 0 +7f6305108000-7f6305127000 rw-p 00000000 00:00 0 +7f6305127000-7f6305128000 ---p 00000000 00:00 0 +7f6305128000-7f6305147000 rw-p 00000000 00:00 0 +7f6305147000-7f6305148000 ---p 00000000 00:00 0 +7f6305148000-7f6305167000 rw-p 00000000 00:00 0 +7f6305167000-7f6305168000 ---p 00000000 00:00 0 +7f6305168000-7f63051b1000 rw-p 00000000 00:00 0 +7f63051b1000-7f63051ca000 rw-p 00000000 00:00 0 +7f63051ca000-7f63051cb000 r--p 00022000 00:00 297771 /lib/x86_64-linux-gnu/ld-2.15.so +7f63051cb000-7f63051cd000 rw-p 00023000 00:00 297771 /lib/x86_64-linux-gnu/ld-2.15.so +7f63051cd000-7f630fd06000 r-xp 00000000 00:00 6308662 /home/user/chromium/src/out/Debug/chrome +7f630fd06000-7f631010d000 r--p 0ab38000 00:00 6308662 /home/user/chromium/src/out/Debug/chrome +7f631010d000-7f631014a000 rw-p 0af3f000 00:00 6308662 /home/user/chromium/src/out/Debug/chrome +7f631014a000-7f63101c2000 rw-p 00000000 00:00 0 +7fff9437f000-7fff943a1000 rw-p 00000000 00:00 0 [stack] +7fff943ff000-7fff94400000 r-xp 00000000 00:00 0 [vdso] +ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] diff --git a/tools/deep_memory_profiler/tests/dmprof_test.py b/tools/deep_memory_profiler/tests/dmprof_test.py new file mode 100755 index 0000000000..68d19256ae --- /dev/null +++ b/tools/deep_memory_profiler/tests/dmprof_test.py @@ -0,0 +1,220 @@ +#!/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. + +import cStringIO +import logging +import os +import sys +import textwrap +import unittest + +BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.append(BASE_PATH) + +from lib.bucket import Bucket +from lib.ordered_dict import OrderedDict +from lib.policy import Policy +from lib.symbol import SymbolMappingCache +from lib.symbol import FUNCTION_SYMBOLS, SOURCEFILE_SYMBOLS, TYPEINFO_SYMBOLS + +import subcommands + + +class SymbolMappingCacheTest(unittest.TestCase): + class MockBucketSet(object): + def __init__(self, addresses): + self._addresses = addresses + + def iter_addresses(self, symbol_type): # pylint: disable=W0613 + for address in self._addresses: + yield address + + class MockSymbolFinder(object): + def __init__(self, mapping): + self._mapping = mapping + + def find(self, address_list): + result = OrderedDict() + for address in address_list: + result[address] = self._mapping[address] + return result + + _TEST_FUNCTION_CACHE = textwrap.dedent("""\ + 1 0x0000000000000001 + 7fc33eebcaa4 __gnu_cxx::new_allocator::allocate + 7fc33ef69242 void DispatchToMethod + """) + + _EXPECTED_TEST_FUNCTION_CACHE = textwrap.dedent("""\ + 1 0x0000000000000001 + 7fc33eebcaa4 __gnu_cxx::new_allocator::allocate + 7fc33ef69242 void DispatchToMethod + 2 0x0000000000000002 + 7fc33ef7bc3e std::map::operator[] + 7fc34411f9d5 WTF::RefCounted::operator new + """) + + _TEST_FUNCTION_ADDRESS_LIST1 = [ + 0x1, 0x7fc33eebcaa4, 0x7fc33ef69242] + + _TEST_FUNCTION_ADDRESS_LIST2 = [ + 0x1, 0x2, 0x7fc33eebcaa4, 0x7fc33ef69242, 0x7fc33ef7bc3e, 0x7fc34411f9d5] + + _TEST_FUNCTION_DICT = { + 0x1: '0x0000000000000001', + 0x2: '0x0000000000000002', + 0x7fc33eebcaa4: '__gnu_cxx::new_allocator::allocate', + 0x7fc33ef69242: 'void DispatchToMethod', + 0x7fc33ef7bc3e: 'std::map::operator[]', + 0x7fc34411f9d5: 'WTF::RefCounted::operator new', + } + + def test_update(self): + symbol_mapping_cache = SymbolMappingCache() + cache_f = cStringIO.StringIO() + cache_f.write(self._TEST_FUNCTION_CACHE) + + # No update from self._TEST_FUNCTION_CACHE + symbol_mapping_cache.update( + FUNCTION_SYMBOLS, + self.MockBucketSet(self._TEST_FUNCTION_ADDRESS_LIST1), + self.MockSymbolFinder(self._TEST_FUNCTION_DICT), cache_f) + for address in self._TEST_FUNCTION_ADDRESS_LIST1: + self.assertEqual(self._TEST_FUNCTION_DICT[address], + symbol_mapping_cache.lookup(FUNCTION_SYMBOLS, address)) + self.assertEqual(self._TEST_FUNCTION_CACHE, cache_f.getvalue()) + + # Update to self._TEST_FUNCTION_ADDRESS_LIST2 + symbol_mapping_cache.update( + FUNCTION_SYMBOLS, + self.MockBucketSet(self._TEST_FUNCTION_ADDRESS_LIST2), + self.MockSymbolFinder(self._TEST_FUNCTION_DICT), cache_f) + for address in self._TEST_FUNCTION_ADDRESS_LIST2: + self.assertEqual(self._TEST_FUNCTION_DICT[address], + symbol_mapping_cache.lookup(FUNCTION_SYMBOLS, address)) + self.assertEqual(self._EXPECTED_TEST_FUNCTION_CACHE, cache_f.getvalue()) + + +class PolicyTest(unittest.TestCase): + class MockSymbolMappingCache(object): + def __init__(self): + self._symbol_caches = { + FUNCTION_SYMBOLS: {}, + SOURCEFILE_SYMBOLS: {}, + TYPEINFO_SYMBOLS: {}, + } + + def add(self, symbol_type, address, symbol): + self._symbol_caches[symbol_type][address] = symbol + + def lookup(self, symbol_type, address): + symbol = self._symbol_caches[symbol_type].get(address) + return symbol if symbol else '0x%016x' % address + + _TEST_POLICY = textwrap.dedent("""\ + { + "components": [ + "second", + "mmap-v8", + "malloc-v8", + "malloc-WebKit", + "mmap-catch-all", + "malloc-catch-all" + ], + "rules": [ + { + "name": "second", + "stacktrace": "optional", + "allocator": "optional" + }, + { + "name": "mmap-v8", + "stacktrace": ".*v8::.*", + "allocator": "mmap" + }, + { + "name": "malloc-v8", + "stacktrace": ".*v8::.*", + "allocator": "malloc" + }, + { + "name": "malloc-WebKit", + "stacktrace": ".*WebKit::.*", + "allocator": "malloc" + }, + { + "name": "mmap-catch-all", + "stacktrace": ".*", + "allocator": "mmap" + }, + { + "name": "malloc-catch-all", + "stacktrace": ".*", + "allocator": "malloc" + } + ], + "version": "POLICY_DEEP_3" + } + """) + + def test_load(self): + policy = Policy.parse(cStringIO.StringIO(self._TEST_POLICY), 'json') + self.assertTrue(policy) + self.assertEqual('POLICY_DEEP_3', policy.version) + + def test_find(self): + policy = Policy.parse(cStringIO.StringIO(self._TEST_POLICY), 'json') + self.assertTrue(policy) + + symbol_mapping_cache = self.MockSymbolMappingCache() + symbol_mapping_cache.add(FUNCTION_SYMBOLS, 0x1212, 'v8::create') + symbol_mapping_cache.add(FUNCTION_SYMBOLS, 0x1381, 'WebKit::create') + + bucket1 = Bucket([0x1212, 0x013], 'malloc', 0x29492, '_Z') + bucket1.symbolize(symbol_mapping_cache) + bucket2 = Bucket([0x18242, 0x1381], 'malloc', 0x9492, '_Z') + bucket2.symbolize(symbol_mapping_cache) + bucket3 = Bucket([0x18242, 0x181], 'malloc', 0x949, '_Z') + bucket3.symbolize(symbol_mapping_cache) + + self.assertEqual('malloc-v8', policy.find_malloc(bucket1)) + self.assertEqual('malloc-WebKit', policy.find_malloc(bucket2)) + self.assertEqual('malloc-catch-all', policy.find_malloc(bucket3)) + + +class BucketsCommandTest(unittest.TestCase): + def test(self): + BUCKETS_PATH = os.path.join(BASE_PATH, 'tests', 'output', 'buckets') + with open(BUCKETS_PATH) as output_f: + expected = output_f.read() + + out = cStringIO.StringIO() + + HEAP_PATH = os.path.join(BASE_PATH, 'tests', 'data', 'heap.01234.0001.heap') + subcommand = subcommands.BucketsCommand() + returncode = subcommand.do(['buckets', HEAP_PATH], out) + self.assertEqual(0, returncode) + self.assertEqual(expected, out.getvalue()) + + +class UploadCommandTest(unittest.TestCase): + def test(self): + MOCK_GSUTIL_PATH = os.path.join(BASE_PATH, 'tests', 'mock_gsutil.py') + HEAP_PATH = os.path.join(BASE_PATH, 'tests', 'data', 'heap.01234.0001.heap') + subcommand = subcommands.UploadCommand() + returncode = subcommand.do([ + 'upload', + '--gsutil', + MOCK_GSUTIL_PATH, + HEAP_PATH, + 'gs://test-storage/']) + self.assertEqual(0, returncode) + + +if __name__ == '__main__': + logging.basicConfig( + level=logging.DEBUG if '-v' in sys.argv else logging.ERROR, + format='%(levelname)5s %(filename)15s(%(lineno)3d): %(message)s') + unittest.main() diff --git a/tools/deep_memory_profiler/tests/mock_gsutil.py b/tools/deep_memory_profiler/tests/mock_gsutil.py new file mode 100755 index 0000000000..acacdcff3b --- /dev/null +++ b/tools/deep_memory_profiler/tests/mock_gsutil.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# 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. + +import os +import re +import sys +import zipfile + + +def main(): + ZIP_PATTERN = re.compile('dmprof......\.zip') + + assert len(sys.argv) == 6 + assert sys.argv[1] == 'cp' + assert sys.argv[2] == '-a' + assert sys.argv[3] == 'public-read' + assert ZIP_PATTERN.match(os.path.basename(sys.argv[4])) + assert sys.argv[5] == 'gs://test-storage/' + + zip_file = zipfile.ZipFile(sys.argv[4], 'r') + + expected_nameset = set(['heap.01234.0001.heap', + 'heap.01234.0002.heap', + 'heap.01234.0001.buckets', + 'heap.01234.0002.buckets', + 'heap.01234.symmap/maps', + 'heap.01234.symmap/chrome.uvwxyz.readelf-e', + 'heap.01234.symmap/chrome.abcdef.nm', + 'heap.01234.symmap/files.json']) + assert set(zip_file.namelist()) == expected_nameset + + heap_1 = zip_file.getinfo('heap.01234.0001.heap') + assert heap_1.CRC == 763099253 + assert heap_1.file_size == 1107 + + buckets_1 = zip_file.getinfo('heap.01234.0001.buckets') + assert buckets_1.CRC == 2632528901 + assert buckets_1.file_size == 2146 + + nm_chrome = zip_file.getinfo('heap.01234.symmap/chrome.abcdef.nm') + assert nm_chrome.CRC == 2717882373 + assert nm_chrome.file_size == 131049 + + zip_file.close() + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/deep_memory_profiler/tests/output/buckets b/tools/deep_memory_profiler/tests/output/buckets new file mode 100644 index 0000000000..7f18ffee01 --- /dev/null +++ b/tools/deep_memory_profiler/tests/output/buckets @@ -0,0 +1,7 @@ +1: malloc tno_typeinfo nno_typeinfo std::_Rb_tree::_M_insert_unique_(@?) std::_Rb_tree::_M_insert_unique_(@?) std::_Rb_tree::_M_insert_unique_(@?) std::_Rb_tree::_M_insert_unique_(@?) std::_Rb_tree::_M_insert_unique_(@?) std::map::insert(@?) std::map::operator[](@?) tracked_objects::ThreadData::TallyADeath(@?) tracked_objects::ThreadData::TallyRunInAScopedRegionIfTracking(@?) tracked_objects::ScopedProfile::StopClockAndTally(@?) tracked_objects::ScopedProfile::~ScopedProfile(@?) content::ResourceDispatcher::FlushDeferredMessages(@?) content::ChildThread::OnMessageReceived(@?) content::ChildThread::OnMessageReceived(@?) IPC::ChannelProxy::Context::OnDispatchMessage(@?) base::internal::RunnableAdapter::Run(@?) base::internal::InvokeHelper::MakeItSo(@?) base::internal::Invoker::Run(@?) base::Callback::Run(@?) MessageLoop::ProcessNextDelayedNonNestableTask(@?) MessageLoop::DeferOrRunPendingTask(@?) MessageLoop::DeferOrRunPendingTask(@?) base::MessagePumpDefault::Run(@?) MessageLoop::QuitWhenIdleClosure(@?) MessageLoop::QuitWhenIdleClosure(@?) base::RunLoop::Run(@?) MessageLoop::Run(@?) content::RenderViewImpl::GetPluginInfo(@?) content::SetupSignalHandlers(@?) content::RunNamedProcessTypeMain(@?) content::ContentMainRunnerImpl::Run(@?) content::ContentMain(@?) +2: malloc t0x00007f630ff7e718 nN7WebCore8RuleDataE WTF::fastMalloc(@?) WTF::Vector::shrinkCapacity(@?) WTF::Vector::shrinkCapacity(@?) WTF::Vector::shrinkToFit(@?) WebCore::shrinkMapVectorsToFit(@?) WebCore::RuleSet::shrinkToFit(@?) WebCore::RuleSet::addRulesFromSheet(@?) WebCore::CSSDefaultStyleSheets::loadFullDefaultStyle(@?) WebCore::CSSDefaultStyleSheets::loadFullDefaultStyle(@?) WebCore::StyleResolver::StyleResolver(@?) WebCore::Document::styleForElementIgnoringPendingStylesheets(@?) WebCore::Element::computedStyle(@?) WebCore::HTMLTitleElement::textWithDirection(@?) WebCore::HTMLTitleElement::childrenChanged(@?) WTF::Vector::shrinkCapacity(@?) WebCore::executeTask(@?) WebCore::HTMLConstructionSite::setCompatibilityModeFromDoctype(@?) WebCore::HTMLTreeBuilder::processEndOfFile(@?) WebCore::HTMLTreeBuilder::processCharacter(@?) WebCore::HTMLTreeBuilder::processToken(@?) WebCore::HTMLTreeBuilder::constructTree(@?) WebCore::HTMLDocumentParser::constructTreeFromHTMLToken(@?) WebCore::HTMLDocumentParser::processingData(@?) WebCore::HTMLDocumentParser::pumpTokenizerIfPossible(@?) WebCore::HTMLDocumentParser::insert(@?) WebCore::DecodedDataDocumentParser::appendBytes(@?) WebCore::DocumentWriter::addData(@?) WebCore::DocumentLoader::commitData(@?) WebKit::WebFrameImpl::commitDocumentData(@?) WebKit::FrameLoaderClientImpl::committedLoad(@?) WebCore::DocumentLoader::commitLoad(@?) WebCore::DocumentLoader::reportMemoryUsage(@?) +3: malloc t0x00007f630feeb3a0 nN7WebCore17CSSPrimitiveValueE WTF::fastMalloc(@?) WTF::RefCounted::operator new(@?) WebCore::CSSPrimitiveValue::createIdentifier(@?) WebCore::CSSValuePool::createIdentifierValue(@?) WebCore::CSSParser::parseValue(@?) WebCore::NodeV8Internal::lookupNamespaceURIMethod(@?) WebCore::CSSParser::parseSheet(@?) WebCore::StyleSheetContents::parseStringAtLine(@?) WebCore::StyleSheetContents::parseString(@?) WebCore::CSSDefaultStyleSheets::loadFullDefaultStyle(@?) WebCore::CSSDefaultStyleSheets::loadFullDefaultStyle(@?) WebCore::CSSDefaultStyleSheets::loadSimpleDefaultStyle(@?) WebCore::CSSDefaultStyleSheets::initDefaultStyle(@?) WebCore::StyleResolver::StyleResolver(@?) WebCore::Document::createStyleResolver(@?) WebCore::Document::styleResolver(@?) WebCore::Element::styleForRenderer(@?) WebCore::NodeRenderingContext::createRendererForElementIfNeeded(@?) WebCore::Element::createRendererIfNeeded(@?) WebCore::Element::attach(@?) WebCore::executeTask(@?) WebCore::HTMLConstructionSite::executeQueuedTasks(@?) WebCore::HTMLConstructionSite::HTMLConstructionSite(@?) WebCore::HTMLTreeBuilder::defaultForBeforeHTML(@?) WebCore::HTMLTreeBuilder::processEndOfFile(@?) WebCore::HTMLTreeBuilder::processToken(@?) WebCore::HTMLTreeBuilder::constructTree(@?) WebCore::HTMLDocumentParser::constructTreeFromHTMLToken(@?) WebCore::HTMLDocumentParser::processingData(@?) WebCore::HTMLDocumentParser::pumpTokenizerIfPossible(@?) WebCore::HTMLDocumentParser::prepareToStopParsing(@?) WebCore::HTMLDocumentParser::attemptToEnd(@?) +4: mmap tno_typeinfo nno_typeinfo DoAllocWithArena(@?) LowLevelAlloc::AllocWithArena(@?) ProfilerMalloc(@?) HeapProfileTable::GetBucket(@?) HeapProfileTable::RecordAlloc(@?) RecordAlloc(@?) NewHook(@?) MallocHook::InvokeNewHookSlow(@?) MallocHook::InvokeNewHook(@?) tc_new_nothrow(@?) WTF::fastMalloc(@?) WTF::HashTable::fullLookupForWriting(@?) WTF::HashTable::fullLookupForWriting(@?) WTF::HashTable::fullLookupForWriting(@?) WTF::HashTable::fullLookupForWriting(@?) WTF::HashTable::addPassingHashCode(@?) WebCore::nullQName(@?) WebCore::QualifiedName::QualifiedName(@?) WebCore::nullQName(@?) WebCore::NodeV8Internal::lookupNamespaceURIMethod(@?) WebCore::Frame::Frame(@?) WebCore::Frame::Frame(@?) WebCore::Frame::create(@?) WebKit::WebFrameImpl::initializeAsMainFrame(@?) WebKit::WebViewImpl::initializeMainFrame(@?) base::internal::scoped_ptr_impl::reset(@?) content::RenderViewImpl::Create(@?) content::RenderThreadImpl::OnCreateNewView(@?) base::internal::scoped_ptr_impl::reset(@?) ViewMsg_New::Dispatch(@?) base::internal::scoped_ptr_impl::reset(@?) content::RenderThreadImpl::OnCreateNewView(@?) +5: malloc tno_typeinfo nno_typeinfo 0x00007f62fd455a89(@?) 0x00007f62fd4575e3(@?) 0x00007fff9439ce50(@?) +6: malloc tno_typeinfo nno_typeinfo WTF::fastMalloc(@?) WTF::fastZeroedMalloc(@?) WTF::HashTable::allocateTable(@?) WTF::HashTable::rehash(@?) WTF::HashTable::add(@?) WTF::HashTable::add(@?) WTF::HashMap::inlineAdd(@?) WTF::HashMap::set(@?) WebCore::eventTargetDataMap(@?) WebCore::EventTarget::addEventListener(@?) WebCore::tryAddEventListener(@?) WebCore::Node::addEventListener(@?) WebCore::NodeV8Internal::removeEventListenerMethod(@?) WebCore::NodeV8Internal::addEventListenerMethodCallback(@?) 0x000012e93499fa0f(@?) 0x000012e93499f038(@?) 0x000012e93499ee28(@?) 0x000012e934925d44(@?) 0x000012e934907177(@?) v8::internal::Invoke(@?) v8::internal::Execution::Call(@?) v8::Function::Call(@?) WebCore::ScriptController::callFunctionWithInstrumentation(@?) WebCore::ScriptController::callFunction(@?) WebCore::resourceInfo(@?) WebCore::V8AbstractEventListener::invokeEventHandler(@?) WebCore::V8AbstractEventListener::handleEvent(@?) WebCore::EventTarget::fireEventListeners(@?) WebCore::EventTarget::clearAttributeEventListener(@?) WebCore::Node::handleLocalEvents(@?) WebCore::EventContext::handleLocalEvents(@?) WebCore::MouseOrFocusEventContext::handleLocalEvents(@?) +7: mmap tno_typeinfo nno_typeinfo DoAllocWithArena(@?) LowLevelAlloc::AllocWithArena(@?) EraseType(@?) EraseType(@?) AddressMap::Insert(@?) InsertType(@?) base::type_profiler::NewInterceptForTCMalloc(@?) __op_new_intercept__(@?) v8::internal::AstNodeFactory::NewVariableProxy(@?) v8::internal::Scope::NewUnresolved(@?) v8::internal::Parser::ParseLeftHandSideExpression(@?) v8::internal::Parser::ParseLeftHandSideExpression(@?) v8::internal::Parser::ParseLeftHandSideExpression(@?) v8::internal::Parser::ParseLeftHandSideExpression(@?) v8::internal::Parser::ParsePostfixExpression(@?) v8::internal::Parser::ParseBinaryExpression(@?) v8::internal::Parser::ParseBinaryExpression(@?) v8::internal::Parser::ParseConditionalExpression(@?) v8::internal::Parser::ParseAssignmentExpression(@?) v8::internal::Parser::ParseAssignmentExpression(@?) v8::internal::Parser::ParseLeftHandSideExpression(@?) v8::internal::Parser::ParseLeftHandSideExpression(@?) v8::internal::Parser::ParseLeftHandSideExpression(@?) v8::internal::Parser::ParseLeftHandSideExpression(@?) v8::internal::Parser::ParsePostfixExpression(@?) v8::internal::Parser::ParseBinaryExpression(@?) v8::internal::Parser::ParseBinaryExpression(@?) v8::internal::Parser::ParseConditionalExpression(@?) v8::internal::Parser::ParseAssignmentExpression(@?) v8::internal::Parser::ParseStatement(@?) v8::internal::Parser::ParseVariableStatement(@?) v8::internal::Parser::ParseStatement(@?) diff --git a/tools/deep_memory_profiler/tests/range_dict_tests.py b/tools/deep_memory_profiler/tests/range_dict_tests.py new file mode 100755 index 0000000000..3bc2c139ac --- /dev/null +++ b/tools/deep_memory_profiler/tests/range_dict_tests.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python +# 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. + +import logging +import os +import sys +import unittest + +BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.append(BASE_PATH) + +from lib.range_dict import ExclusiveRangeDict + + +class ExclusiveRangeDictTest(unittest.TestCase): + class TestAttribute(ExclusiveRangeDict.RangeAttribute): + def __init__(self): + super(ExclusiveRangeDictTest.TestAttribute, self).__init__() + self._value = 0 + + def __str__(self): + return str(self._value) + + def __repr__(self): + return '' % self._value + + def get(self): + return self._value + + def set(self, new_value): + self._value = new_value + + def copy(self): # pylint: disable=R0201 + new_attr = ExclusiveRangeDictTest.TestAttribute() + new_attr.set(self._value) + return new_attr + + def test_init(self): + ranges = ExclusiveRangeDict(self.TestAttribute) + + result = [] + for begin, end, attr in ranges.iter_range(20, 40): + result.append({'begin': begin, 'end':end, 'attr':attr.get()}) + expected = [ + {'begin': 20, 'end': 40, 'attr': 0}, + ] + self.assertEqual(expected, result) + + def test_norange(self): + ranges = ExclusiveRangeDict(self.TestAttribute) + + result = [] + for begin, end, attr in ranges.iter_range(20, 20): + result.append({'begin': begin, 'end':end, 'attr':attr.get()}) + expected = [] + self.assertEqual(expected, result) + + def test_set(self): + ranges = ExclusiveRangeDict(self.TestAttribute) + for begin, end, attr in ranges.iter_range(20, 30): + attr.set(12) + for begin, end, attr in ranges.iter_range(30, 40): + attr.set(52) + + result = [] + for begin, end, attr in ranges.iter_range(20, 40): + result.append({'begin': begin, 'end':end, 'attr':attr.get()}) + expected = [ + {'begin': 20, 'end': 30, 'attr': 12}, + {'begin': 30, 'end': 40, 'attr': 52}, + ] + self.assertEqual(expected, result) + + def test_split(self): + ranges = ExclusiveRangeDict(self.TestAttribute) + for begin, end, attr in ranges.iter_range(20, 30): + attr.set(1000) + for begin, end, attr in ranges.iter_range(30, 40): + attr.set(2345) + for begin, end, attr in ranges.iter_range(40, 50): + attr.set(3579) + + result1 = [] + for begin, end, attr in ranges.iter_range(25, 45): + result1.append({'begin': begin, 'end':end, 'attr':attr.get()}) + expected1 = [ + {'begin': 25, 'end': 30, 'attr': 1000}, + {'begin': 30, 'end': 40, 'attr': 2345}, + {'begin': 40, 'end': 45, 'attr': 3579}, + ] + self.assertEqual(expected1, result1) + + result2 = [] + for begin, end, attr in ranges.iter_range(20, 50): + result2.append({'begin': begin, 'end':end, 'attr':attr.get()}) + expected2 = [ + {'begin': 20, 'end': 25, 'attr': 1000}, + {'begin': 25, 'end': 30, 'attr': 1000}, + {'begin': 30, 'end': 40, 'attr': 2345}, + {'begin': 40, 'end': 45, 'attr': 3579}, + {'begin': 45, 'end': 50, 'attr': 3579}, + ] + self.assertEqual(expected2, result2) + + def test_fill(self): + ranges = ExclusiveRangeDict(self.TestAttribute) + for begin, end, attr in ranges.iter_range(30, 35): + attr.set(12345) + for begin, end, attr in ranges.iter_range(40, 45): + attr.set(97531) + + result = [] + for begin, end, attr in ranges.iter_range(25, 50): + result.append({'begin': begin, 'end':end, 'attr':attr.get()}) + expected = [ + {'begin': 25, 'end': 30, 'attr': 0}, + {'begin': 30, 'end': 35, 'attr': 12345}, + {'begin': 35, 'end': 40, 'attr': 0}, + {'begin': 40, 'end': 45, 'attr': 97531}, + {'begin': 45, 'end': 50, 'attr': 0}, + ] + self.assertEqual(expected, result) + + +if __name__ == '__main__': + logging.basicConfig( + level=logging.DEBUG if '-v' in sys.argv else logging.ERROR, + format='%(levelname)5s %(filename)15s(%(lineno)3d): %(message)s') + unittest.main() diff --git a/tools/deep_memory_profiler/visualizer/main.css b/tools/deep_memory_profiler/visualizer/main.css new file mode 100644 index 0000000000..5d9ed53f48 --- /dev/null +++ b/tools/deep_memory_profiler/visualizer/main.css @@ -0,0 +1,9 @@ +/* Copyright 2013 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. */ + +.plot-container { + width: 1240px; + height: 720px; + margin: 30px auto 30px auto; +} \ No newline at end of file diff --git a/tools/deep_memory_profiler/visualizer/main.html b/tools/deep_memory_profiler/visualizer/main.html new file mode 100644 index 0000000000..fdd7874591 --- /dev/null +++ b/tools/deep_memory_profiler/visualizer/main.html @@ -0,0 +1,17 @@ + + + + + + + + + + +

Deep Memory Profiler Visulaizer

+
+ diff --git a/tools/deep_memory_profiler/visualizer/main.js b/tools/deep_memory_profiler/visualizer/main.js new file mode 100644 index 0000000000..8c4dff84c4 --- /dev/null +++ b/tools/deep_memory_profiler/visualizer/main.js @@ -0,0 +1,160 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * This class provides data access interface for dump file profiler + * @constructor + */ +var Profiler = function(jsonData) { + this._jsonData = jsonData; +}; + +/** + * Get units of a snapshot in a world. + * Exception will be thrown when no world of given name exists. + * @param {string} worldName + * @param {number} snapshotIndex + * @return {Object.} + */ +Profiler.prototype.getUnits = function(worldName, snapshotIndex) { + var snapshot = this._jsonData.snapshots[snapshotIndex]; + if (!snapshot.worlds[worldName]) + throw 'no world ' + worldName + ' in snapshot ' + index; + + // Return units. + var world = snapshot.worlds[worldName]; + var units = {}; + for (var unitName in world.units) + units[unitName] = world.units[unitName][0]; + return units; +}; + +/** + * Get first-level breakdowns of a snapshot in a world. + * Exception will be thrown when no world of given name exists. + * @param {string} worldName + * @param {number} snapshotIndex + * @return {Object.} + */ +Profiler.prototype.getBreakdowns = function(worldName, snapshotIndex) { + var snapshot = this._jsonData.snapshots[snapshotIndex]; + if (!snapshot.worlds[worldName]) + throw 'no world ' + worldName + ' in snapshot ' + index; + + // Return breakdowns. + // TODO(junjianx): handle breakdown with arbitrary-level structure. + return snapshot.worlds[worldName].breakdown; +}; + +/** + * Get categories from fixed hard-coded worlds and breakdowns temporarily. + * TODO(junjianx): remove the hard-code and support general cases. + * @return {Array.} + */ +Profiler.prototype.getCategories = function() { + var categories = []; + var snapshotNum = this._jsonData.snapshots.length; + + for (var snapshotIndex = 0; snapshotIndex < snapshotNum; ++snapshotIndex) { + // Initial categories object for one snapshot. + categories.push({}); + + // Handle breakdowns in malloc world. + var mallocBreakdown = this.getBreakdowns('malloc', snapshotIndex); + var mallocUnits = this.getUnits('malloc', snapshotIndex); + if (!mallocBreakdown['component']) + throw 'no breakdown ' + 'component' + ' in snapshot ' + snapshotIndex; + + var componentBreakdown = mallocBreakdown['component']; + var componentMemory = 0; + Object.keys(componentBreakdown).forEach(function(breakdownName) { + var breakdown = componentBreakdown[breakdownName]; + var memory = breakdown.units.reduce(function(previous, current) { + return previous + mallocUnits[current]; + }, 0); + componentMemory += memory; + + if (componentBreakdown['hidden'] === true) + return; + else + categories[snapshotIndex][breakdownName] = memory; + }); + + // Handle breakdowns in vm world. + var vmBreakdown = this.getBreakdowns('vm', snapshotIndex); + var vmUnits = this.getUnits('vm', snapshotIndex); + if (!vmBreakdown['map']) + throw 'no breakdown ' + 'map' + ' in snapshot ' + snapshotIndex; + + var mapBreakdown = vmBreakdown['map']; + + Object.keys(mapBreakdown).forEach(function(breakdownName) { + var breakdown = mapBreakdown[breakdownName]; + var memory = breakdown.units.reduce(function(previous, current) { + return previous + vmUnits[current]; + }, 0); + + if (vmBreakdown['hidden'] === true) + return; + else if (breakdownName === 'mmap-tcmalloc') + categories[snapshotIndex]['tc-unused'] = memory - componentMemory; + else + categories[snapshotIndex][breakdownName] = memory; + }); + } + + return categories; +}; + +/** + * Generate lines for flot plotting. + * @param {Array.} categories + * @return {Array.} + */ +var generateLines = function(categories) { + var lines = {}; + var snapshotNum = categories.length; + + // Initialize lines with all zero. + categories.forEach(function(categories) { + Object.keys(categories).forEach(function(breakdownName) { + if (lines[breakdownName]) + return; + lines[breakdownName] = []; + for (var i = 0; i < snapshotNum; ++i) + lines[breakdownName].push([i, 0]); + }); + }); + + // Assignment lines with values of categories. + categories.forEach(function(categories, index) { + Object.keys(categories).forEach(function(breakdownName) { + lines[breakdownName][index] = [index, categories[breakdownName]]; + }); + }); + + return Object.keys(lines).map(function(breakdownName) { + return { + label: breakdownName, + data: lines[breakdownName] + }; + }); +}; + +$(function() { + // Read original data and plot. + $.getJSON('data/result.json', function(jsonData) { + var profiler = new Profiler(jsonData); + var categories = profiler.getCategories(); + var lines = generateLines(categories); + + // Plot stack graph. + $.plot('#plot', lines, { + series: { + stack: true, + lines: { show: true, fill: true } + } + }); + }); +}); \ No newline at end of file diff --git a/tools/deps2git b/tools/deps2git new file mode 160000 index 0000000000..92b6fca498 --- /dev/null +++ b/tools/deps2git @@ -0,0 +1 @@ +Subproject commit 92b6fca498aa9d9cca70e80b25a4c41313a537f0 diff --git a/tools/diagnose-me.py b/tools/diagnose-me.py new file mode 100755 index 0000000000..bbd9429cb1 --- /dev/null +++ b/tools/diagnose-me.py @@ -0,0 +1,95 @@ +#!/usr/bin/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. + +"""Diagnose some common system configuration problems on Linux, and +suggest fixes.""" + +import os +import subprocess +import sys + +all_checks = [] + +def Check(name): + """Decorator that defines a diagnostic check.""" + def wrap(func): + all_checks.append((name, func)) + return func + return wrap + + +@Check("/usr/bin/ld is not gold") +def CheckSystemLd(): + proc = subprocess.Popen(['/usr/bin/ld', '-v'], stdout=subprocess.PIPE) + stdout = proc.communicate()[0] + if 'GNU gold' in stdout: + return ("When /usr/bin/ld is gold, system updates can silently\n" + "corrupt your graphics drivers.\n" + "Try 'sudo apt-get remove binutils-gold'.\n") + return None + + +@Check("random lds are not in the $PATH") +def CheckPathLd(): + proc = subprocess.Popen(['which', '-a', 'ld'], stdout=subprocess.PIPE) + stdout = proc.communicate()[0] + instances = stdout.split() + if len(instances) > 1: + return ("You have multiple 'ld' binaries in your $PATH:\n" + + '\n'.join(' - ' + i for i in instances) + "\n" + "You should delete all of them but your system one.\n" + "gold is hooked into your build via gyp.\n") + return None + + +@Check("/usr/bin/ld doesn't point to gold") +def CheckLocalGold(): + # Check /usr/bin/ld* symlinks. + for path in ('ld.bfd', 'ld'): + path = '/usr/bin/' + path + try: + target = os.readlink(path) + except OSError, e: + if e.errno == 2: + continue # No such file + if e.errno == 22: + continue # Not a symlink + raise + if '/usr/local/gold' in target: + return ("%s is a symlink into /usr/local/gold.\n" + "It's difficult to make a recommendation, because you\n" + "probably set this up yourself. But you should make\n" + "/usr/bin/ld be the standard linker, which you likely\n" + "renamed /usr/bin/ld.bfd or something like that.\n" % path) + + return None + + +@Check("random ninja binaries are not in the $PATH") +def CheckPathNinja(): + proc = subprocess.Popen(['which', 'ninja'], stdout=subprocess.PIPE) + stdout = proc.communicate()[0] + if not 'depot_tools' in stdout: + return ("The ninja binary in your path isn't from depot_tools:\n" + + " " + stdout + + "Remove custom ninjas from your path so that the one\n" + "in depot_tools is used.\n") + return None + + +def RunChecks(): + for name, check in all_checks: + sys.stdout.write("* Checking %s: " % name) + sys.stdout.flush() + error = check() + if not error: + print "ok" + else: + print "FAIL" + print error + + +if __name__ == '__main__': + RunChecks() diff --git a/tools/dromaeo_benchmark_runner/dromaeo_benchmark_runner.py b/tools/dromaeo_benchmark_runner/dromaeo_benchmark_runner.py new file mode 100755 index 0000000000..5c4be81c37 --- /dev/null +++ b/tools/dromaeo_benchmark_runner/dromaeo_benchmark_runner.py @@ -0,0 +1,266 @@ +#!/usr/bin/env python +# Copyright (c) 2011 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Dromaeo benchmark automation script. + +Script runs dromaeo tests in browsers specified by --browser switch and saves +results to a spreadsheet on docs.google.com. + +Prerequisites: +1. Install Google Data APIs Python Client Library from + http://code.google.com/p/gdata-python-client. +2. Checkout Dromaeo benchmark from + http://src.chromium.org/svn/trunk/src/chrome/test/data/dromaeo and provide + local path to it in --dromaeo_home switch. +3. Create a spreadsheet at http://docs.google.com and specify its name in + --spreadsheet switch + +Benchmark results are presented in the following format: +browser | date time +test 1 name|m11|...|m1n|test 1 average mean| |e11|...|e1n|test 1 average error +test 2 name|m21|...|m2n|test 2 average mean| |e21|...|e2n|test 2 average error +... + +Here mij is mean run/s in individual dromaeo test i during benchmark run j, +eij is error in individual dromaeo test i during benchmark run j. + +Example usage: +dromaeo_benchmark_runner.py -b "E:\chromium\src\chrome\Release\chrome.exe" + -b "C:\Program Files (x86)\Safari\safari.exe" + -b "C:\Program Files (x86)\Opera 10.50 pre-alpha\opera.exe" -n 1 + -d "E:\chromium\src\chrome\test\data\dromaeo" -f dom -e example@gmail.com + +""" + +import getpass +import json +import os +import re +import subprocess +import time +import urlparse +from optparse import OptionParser +from BaseHTTPServer import HTTPServer +import SimpleHTTPServer +import gdata.spreadsheet.service + +max_spreadsheet_columns = 20 +test_props = ['mean', 'error'] + + +def ParseArguments(): + parser = OptionParser() + parser.add_option("-b", "--browser", + action="append", dest="browsers", + help="list of browsers to test") + parser.add_option("-n", "--run_count", dest="run_count", type="int", + default=5, help="number of runs") + parser.add_option("-d", "--dromaeo_home", dest="dromaeo_home", + help="directory with your dromaeo files") + parser.add_option("-p", "--port", dest="port", type="int", + default=8080, help="http server port") + parser.add_option("-f", "--filter", dest="filter", + default="dom", help="dromaeo suite filter") + parser.add_option("-e", "--email", dest="email", + help="your google docs account") + parser.add_option("-s", "--spreadsheet", dest="spreadsheet_title", + default="dromaeo", + help="your google docs spreadsheet name") + + options = parser.parse_args()[0] + + if not options.dromaeo_home: + raise Exception('please specify dromaeo_home') + + return options + + +def KillProcessByName(process_name): + process = subprocess.Popen('wmic process get processid, executablepath', + stdout=subprocess.PIPE) + stdout = str(process.communicate()[0]) + match = re.search(re.escape(process_name) + '\s+(\d+)', stdout) + if match: + pid = match.group(1) + subprocess.call('taskkill /pid %s' % pid) + + +class SpreadsheetWriter(object): + "Utility class for storing benchmarking results in Google spreadsheets." + + def __init__(self, email, spreadsheet_title): + '''Login to google docs and search for spreadsheet''' + + self.token_file = os.path.expanduser("~/.dromaeo_bot_auth_token") + self.gd_client = gdata.spreadsheet.service.SpreadsheetsService() + + authenticated = False + if os.path.exists(self.token_file): + token = '' + try: + file = open(self.token_file, 'r') + token = file.read() + file.close() + self.gd_client.SetClientLoginToken(token) + self.gd_client.GetSpreadsheetsFeed() + authenticated = True + except (IOError, gdata.service.RequestError): + pass + if not authenticated: + self.gd_client.email = email + self.gd_client.password = getpass.getpass('Password for %s: ' % email) + self.gd_client.source = 'python robot for dromaeo' + self.gd_client.ProgrammaticLogin() + token = self.gd_client.GetClientLoginToken() + try: + file = open(self.token_file, 'w') + file.write(token) + file.close() + except (IOError): + pass + os.chmod(self.token_file, 0600) + + # Search for the spreadsheet with title = spreadsheet_title. + spreadsheet_feed = self.gd_client.GetSpreadsheetsFeed() + for spreadsheet in spreadsheet_feed.entry: + if spreadsheet.title.text == spreadsheet_title: + self.spreadsheet_key = spreadsheet.id.text.rsplit('/', 1)[1] + if not self.spreadsheet_key: + raise Exception('Spreadsheet %s not found' % spreadsheet_title) + + # Get the key of the first worksheet in spreadsheet. + worksheet_feed = self.gd_client.GetWorksheetsFeed(self.spreadsheet_key) + self.worksheet_key = worksheet_feed.entry[0].id.text.rsplit('/', 1)[1] + + def _InsertRow(self, row): + row = dict([('c' + str(i), row[i]) for i in xrange(len(row))]) + self.gd_client.InsertRow(row, self.spreadsheet_key, self.worksheet_key) + + def _InsertBlankRow(self): + self._InsertRow('-' * self.columns_count) + + def PrepareSpreadsheet(self, run_count): + """Update cells in worksheet topmost row with service information. + + Calculate column count corresponding to run_count and create worksheet + column titles [c0, c1, ...] in the topmost row to speed up spreadsheet + updates (it allows to insert a whole row with a single request) + """ + + # Calculate the number of columns we need to present all test results. + self.columns_count = (run_count + 2) * len(test_props) + if self.columns_count > max_spreadsheet_columns: + # Google spreadsheet has just max_spreadsheet_columns columns. + max_run_count = max_spreadsheet_columns / len(test_props) - 2 + raise Exception('maximum run count is %i' % max_run_count) + # Create worksheet column titles [c0, c1, ..., cn]. + for i in xrange(self.columns_count): + self.gd_client.UpdateCell(1, i + 1, 'c' + str(i), self.spreadsheet_key, + self.worksheet_key) + + def WriteColumnTitles(self, run_count): + "Create titles for test results (mean 1, mean 2, ..., average mean, ...)" + row = [] + for prop in test_props: + row.append('') + for i in xrange(run_count): + row.append('%s %i' % (prop, i + 1)) + row.append('average ' + prop) + self._InsertRow(row) + + def WriteBrowserBenchmarkTitle(self, browser_name): + "Create browser benchmark title (browser name, date time)" + self._InsertBlankRow() + self._InsertRow([browser_name, time.strftime('%d.%m.%Y %H:%M:%S')]) + + def WriteBrowserBenchmarkResults(self, test_name, test_data): + "Insert a row with single test results" + row = [] + for prop in test_props: + if not row: + row.append(test_name) + else: + row.append('') + row.extend([str(x) for x in test_data[prop]]) + row.append(str(sum(test_data[prop]) / len(test_data[prop]))) + self._InsertRow(row) + + +class DromaeoHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): + + def do_POST(self): + self.send_response(200) + self.end_headers() + self.wfile.write("POST OK.

"); + length = int(self.headers.getheader('content-length')) + parameters = urlparse.parse_qs(self.rfile.read(length)) + self.server.got_post = True + self.server.post_data = parameters['data'] + + +class BenchmarkResults(object): + "Storage class for dromaeo benchmark results" + + def __init__(self): + self.data = {} + + def ProcessBrowserPostData(self, data): + "Convert dromaeo test results in internal format" + tests = json.loads(data[0]) + for test in tests: + test_name = test['name'] + if test_name not in self.data: + # Test is encountered for the first time. + self.data[test_name] = dict([(prop, []) for prop in test_props]) + # Append current run results. + for prop in test_props: + value = -1 + if prop in test: value = test[prop] # workaround for Opera 10.5 + self.data[test_name][prop].append(value) + + +def main(): + options = ParseArguments() + + # Start sever with dromaeo. + os.chdir(options.dromaeo_home) + server = HTTPServer(('', options.port), DromaeoHandler) + + # Open and prepare spreadsheet on google docs. + spreadsheet_writer = SpreadsheetWriter(options.email, + options.spreadsheet_title) + spreadsheet_writer.PrepareSpreadsheet(options.run_count) + spreadsheet_writer.WriteColumnTitles(options.run_count) + + for browser in options.browsers: + browser_name = os.path.splitext(os.path.basename(browser))[0] + spreadsheet_writer.WriteBrowserBenchmarkTitle(browser_name) + benchmark_results = BenchmarkResults() + for run_number in xrange(options.run_count): + print '%s run %i' % (browser_name, run_number + 1) + # Run browser. + test_page = 'http://localhost:%i/index.html?%s&automated&post_json' % ( + options.port, options.filter) + browser_process = subprocess.Popen('%s "%s"' % (browser, test_page)) + server.got_post = False + server.post_data = None + # Wait until POST request from browser. + while not server.got_post: + server.handle_request() + benchmark_results.ProcessBrowserPostData(server.post_data) + # Kill browser. + KillProcessByName(browser) + browser_process.wait() + + # Insert test results into spreadsheet. + for (test_name, test_data) in benchmark_results.data.iteritems(): + spreadsheet_writer.WriteBrowserBenchmarkResults(test_name, test_data) + + server.socket.close() + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/emacs/chrome-filetypes.el b/tools/emacs/chrome-filetypes.el new file mode 100644 index 0000000000..14fc6bbaef --- /dev/null +++ b/tools/emacs/chrome-filetypes.el @@ -0,0 +1,16 @@ +; To get syntax highlighting and tab settings for gyp(i) files, add the +; following to init.el: +; (setq-default chrome-root "/path/to/chrome/src/") +; (add-to-list 'load-path (concat chrome-root "tools/emacs")) +; (require 'chrome-filetypes) + +(define-derived-mode gyp-mode python-mode "Gyp" + "Major mode for editing Generate Your Project files." + (setq indent-tabs-mode nil + tab-width 2 + python-indent 2)) + +(add-to-list 'auto-mode-alist '("\\.gyp$" . gyp-mode)) +(add-to-list 'auto-mode-alist '("\\.gypi$" . gyp-mode)) + +(provide 'chrome-filetypes) diff --git a/tools/emacs/flymake-chromium.el b/tools/emacs/flymake-chromium.el new file mode 100644 index 0000000000..8adb0dbe56 --- /dev/null +++ b/tools/emacs/flymake-chromium.el @@ -0,0 +1,122 @@ +;; Copyright (c) 2011 The Chromium Authors. All rights reserved. +;; Use of this source code is governed by a BSD-style license that can be +;; found in the LICENSE file. + +;; Set up flymake for use with chromium code. Uses ninja (since none of the +;; other chromium build systems have latency that allows interactive use). +;; +;; Requires a modern emacs (GNU Emacs >= 23) and that gyp has already generated +;; the build.ninja file(s). See defcustoms below for settable knobs. + + +(require 'flymake) + +(defcustom cr-flymake-ninja-build-file "out/Debug/build.ninja" + "Relative path from chromium's src/ directory to the + build.ninja file to use.") + +(defcustom cr-flymake-ninja-executable "ninja" + "Ninja executable location; either in $PATH or explicitly given.") + +(defun cr-flymake-absbufferpath () + "Return the absolute path to the current buffer, or nil if the + current buffer has no path." + (when buffer-file-truename + (expand-file-name buffer-file-truename))) + +(defun cr-flymake-chromium-src () + "Return chromium's src/ directory, or nil on failure." + (let ((srcdir (locate-dominating-file + (cr-flymake-absbufferpath) cr-flymake-ninja-build-file))) + (when srcdir (expand-file-name srcdir)))) + +(defun cr-flymake-string-prefix-p (prefix str) + "Return non-nil if PREFIX is a prefix of STR (23.2 has string-prefix-p but + that's case insensitive and also 23.1 doesn't have it)." + (string= prefix (substring str 0 (length prefix)))) + +(defun cr-flymake-current-file-name () + "Return the relative path from chromium's src/ directory to the + file backing the current buffer or nil if it doesn't look like + we're under chromium/src/." + (when (and (cr-flymake-chromium-src) + (cr-flymake-string-prefix-p + (cr-flymake-chromium-src) (cr-flymake-absbufferpath))) + (substring (cr-flymake-absbufferpath) (length (cr-flymake-chromium-src))))) + +(defun cr-flymake-from-build-to-src-root () + "Return a path fragment for getting from the build.ninja file to src/." + (replace-regexp-in-string + "[^/]+" ".." + (substring + (file-name-directory + (file-truename (or (and (cr-flymake-string-prefix-p + "/" cr-flymake-ninja-build-file) + cr-flymake-ninja-build-file) + (concat (cr-flymake-chromium-src) + cr-flymake-ninja-build-file)))) + (length (cr-flymake-chromium-src))))) + +(defun cr-flymake-getfname (file-name-from-error-message) + "Strip cruft from the passed-in filename to help flymake find the real file." + (file-name-nondirectory file-name-from-error-message)) + +(defun cr-flymake-ninja-command-line () + "Return the command-line for running ninja, as a list of strings, or nil if + we're not during a save" + (unless (buffer-modified-p) + (list cr-flymake-ninja-executable + (list "-C" + (concat (cr-flymake-chromium-src) + (file-name-directory cr-flymake-ninja-build-file)) + (concat (cr-flymake-from-build-to-src-root) + (cr-flymake-current-file-name) "^"))))) + +(defun cr-flymake-kick-off-check-after-save () + "Kick off a syntax check after file save, if flymake-mode is on." + (when flymake-mode (flymake-start-syntax-check))) + +(defadvice next-error (around cr-flymake-next-error activate) + "If flymake has something to say, let it say it; otherwise + revert to normal next-error behavior." + (if (not flymake-err-info) + (condition-case msg + ad-do-it + (error (message "%s" (prin1-to-string msg)))) + (flymake-goto-next-error) + ;; copy/pasted from flymake-display-err-menu-for-current-line because I + ;; couldn't find a way to have it tell me what the relevant error for this + ;; line was in a single call: + (let* ((line-no (flymake-current-line-no)) + (line-err-info-list + (nth 0 (flymake-find-err-info flymake-err-info line-no))) + (menu-data (flymake-make-err-menu-data line-no line-err-info-list))) + (prin1 (car (car (car (cdr menu-data)))) t)))) + +(defun cr-flymake-find-file () + "Enable flymake, but only if it makes sense, and immediately + disable timer-based execution." + (when (and (not flymake-mode) + (not buffer-read-only) + (cr-flymake-current-file-name)) + ;; Since flymake-allowed-file-name-masks requires static regexps to match + ;; against, can't use cr-flymake-chromium-src here. Instead we add a + ;; generic regexp, but only to a buffer-local version of the variable. + (set (make-local-variable 'flymake-allowed-file-name-masks) + (list (list "\\.c\\(\\|c\\|pp\\)" + 'cr-flymake-ninja-command-line + 'ignore + 'cr-flymake-getfname))) + (flymake-find-file-hook) + (if flymake-mode + (cancel-timer flymake-timer) + (kill-local-variable 'flymake-allowed-file-name-masks)))) + +(add-hook 'find-file-hook 'cr-flymake-find-file 'append) +(add-hook 'after-save-hook 'cr-flymake-kick-off-check-after-save) + +;; Show flymake infrastructure ERRORs in hopes of fixing them. Set to 3 for +;; DEBUG-level output from flymake.el. +(setq flymake-log-level 0) + +(provide 'flymake-chromium) diff --git a/tools/emacs/trybot-linux.txt b/tools/emacs/trybot-linux.txt new file mode 100644 index 0000000000..ad3ada8de5 --- /dev/null +++ b/tools/emacs/trybot-linux.txt @@ -0,0 +1,6 @@ +A snippet of Linux trybot output (note UTF-8 quotes). + + AR(target) out/Debug/obj.target/printing/libprinting.a +app/l10n_util_unittest.cc: In member function ‘virtual void L10nUtilTest_TruncateString_Test::TestBody()’: +app/l10n_util_unittest.cc:67: error: invalid initialization of reference of type ‘const string16&’ from expression of type ‘std::wstring’ +./app/l10n_util.h:166: error: in passing argument 1 of ‘string16 l10n_util::TruncateString(const string16&, size_t)’ diff --git a/tools/emacs/trybot-mac.txt b/tools/emacs/trybot-mac.txt new file mode 100644 index 0000000000..87d7f26212 --- /dev/null +++ b/tools/emacs/trybot-mac.txt @@ -0,0 +1,1985 @@ +/b/build/third_party/zope/__init__.py:19: UserWarning: Module twisted was already imported from /b/build/third_party/twisted_8_1/twisted/__init__.pyc, but /System/Library/Frameworks/Python.framework/Versions/2.6/Extras/lib/python is being added to sys.path + import pkg_resources +/b/build/third_party/zope/__init__.py:19: UserWarning: Module zope was already imported from /b/build/third_party/zope/__init__.pyc, but /System/Library/Frameworks/Python.framework/Versions/2.6/Extras/lib/python is being added to sys.path + import pkg_resources + +pump xcodebuild -configuration Debug -project all.xcodeproj -buildhostsfile /b/build/scripts/slave/mac_distcc_hosts/golo_mini-10_6 +__________Using distcc-pump from /usr/bin +__________Using 1 distcc server in pump mode + +=== BUILD AGGREGATE TARGET app_resources OF PROJECT app WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD AGGREGATE TARGET app_strings OF PROJECT app WITH CONFIGURATION Debug === +Check dependencies +PhaseScriptExecution "Rule \"grit\"" ../xcodebuild/app.build/Debug/app_strings.build/Script-3555EADE2A4F7996024F949F.sh + cd /b/build/slave/mac/build/src/app + /bin/sh -c /b/build/slave/mac/build/src/app/../xcodebuild/app.build/Debug/app_strings.build/Script-3555EADE2A4F7996024F949F.sh + +make: Nothing to be done for `all'. + +=== BUILD AGGREGATE TARGET app_base Support OF PROJECT app WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET app_base OF PROJECT app WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET test_support_base OF PROJECT base WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET protobuf_full_do_not_use OF PROJECT protobuf WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET protobuf_lite OF PROJECT protobuf WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET protoc OF PROJECT protobuf WITH CONFIGURATION Debug === +Check dependencies +PhaseScriptExecution "Postbuild \"Strip If Needed\"" ../../xcodebuild/protobuf.build/Debug/protoc.build/Script-7AC9923F7CDD2087661C69AB.sh + cd /b/build/slave/mac/build/src/third_party/protobuf + /bin/sh -c /b/build/slave/mac/build/src/third_party/protobuf/../../xcodebuild/protobuf.build/Debug/protoc.build/Script-7AC9923F7CDD2087661C69AB.sh + + +=== BUILD AGGREGATE TARGET sync_proto OF PROJECT sync_proto WITH CONFIGURATION Debug === +Check dependencies +PhaseScriptExecution "Rule \"genproto\"" ../../../../xcodebuild/sync_proto.build/Debug/sync_proto.build/Script-237DAA9DBF3380A9B7228EAF.sh + cd /b/build/slave/mac/build/src/chrome/browser/sync/protocol + /bin/sh -c /b/build/slave/mac/build/src/chrome/browser/sync/protocol/../../../../xcodebuild/sync_proto.build/Debug/sync_proto.build/Script-237DAA9DBF3380A9B7228EAF.sh + +make: Nothing to be done for `all'. + +=== BUILD AGGREGATE TARGET device_management_proto OF PROJECT device_management_proto WITH CONFIGURATION Debug === +Check dependencies +PhaseScriptExecution "Rule \"genproto\"" ../../../../xcodebuild/device_management_proto.build/Debug/device_management_proto.build/Script-7D8C1925B70AD3724821C80A.sh + cd /b/build/slave/mac/build/src/chrome/browser/policy/proto + /bin/sh -c /b/build/slave/mac/build/src/chrome/browser/policy/proto/../../../../xcodebuild/device_management_proto.build/Debug/device_management_proto.build/Script-7D8C1925B70AD3724821C80A.sh + +make: Nothing to be done for `all'. + +=== BUILD AGGREGATE TARGET py_proto OF PROJECT protobuf WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET net_test_support OF PROJECT net WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET skia OF PROJECT skia WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET gmock OF PROJECT gmock WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET gtest OF PROJECT gtest WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET icui18n OF PROJECT icu WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET icuuc OF PROJECT icu WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET libpng OF PROJECT libpng WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET zlib OF PROJECT zlib WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET libjpeg OF PROJECT libjpeg WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET base OF PROJECT base WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET modp_b64 OF PROJECT modp_b64 WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET dynamic_annotations OF PROJECT dynamic_annotations WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET nss OF PROJECT nss WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET nspr OF PROJECT nss WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET sqlite OF PROJECT sqlite WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET icudata OF PROJECT icu WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET libevent OF PROJECT libevent WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET base_i18n OF PROJECT base WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD AGGREGATE TARGET gfx_resources OF PROJECT gfx WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET gfx OF PROJECT gfx WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET skia_opts OF PROJECT skia WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD AGGREGATE TARGET net_resources OF PROJECT net WITH CONFIGURATION Debug === +Check dependencies +PhaseScriptExecution "Rule \"grit\"" ../xcodebuild/net.build/Debug/net_resources.build/Script-D3210C5A91652E9B8F9DF7BD.sh + cd /b/build/slave/mac/build/src/net + /bin/sh -c /b/build/slave/mac/build/src/net/../xcodebuild/net.build/Debug/net_resources.build/Script-D3210C5A91652E9B8F9DF7BD.sh + +make: Nothing to be done for `all'. + +=== BUILD AGGREGATE TARGET js2c OF PROJECT v8 WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET v8_nosnapshot OF PROJECT v8 WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET v8_base OF PROJECT v8 WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET mksnapshot OF PROJECT v8 WITH CONFIGURATION Debug === +Check dependencies +PhaseScriptExecution "Postbuild \"Strip If Needed\"" ../../../xcodebuild/v8.build/Debug/mksnapshot.build/Script-759699424E9CDB8D5A56B17D.sh + cd /b/build/slave/mac/build/src/v8/tools/gyp + /bin/sh -c /b/build/slave/mac/build/src/v8/tools/gyp/../../../xcodebuild/v8.build/Debug/mksnapshot.build/Script-759699424E9CDB8D5A56B17D.sh + + +=== BUILD AGGREGATE TARGET v8_snapshot Support OF PROJECT v8 WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET v8_snapshot OF PROJECT v8 WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD AGGREGATE TARGET v8 OF PROJECT v8 WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET net OF PROJECT net WITH CONFIGURATION Debug === +Check dependencies +Distributed-CompileC ../xcodebuild/net.build/Debug/net.build/Objects-normal/i386/spdy_session.o spdy/spdy_session.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/net + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS localhost + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNO_NSPR_10_SUPPORT -DNSS_USE_STATIC_LIBS -DUSE_UTIL_DIRECTLY -DNSS_PLATFORM_CLIENT_AUTH -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/net/../xcodebuild/Debug -I/b/build/slave/mac/build/src/net/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I../sdch/open-vcdiff/src -I../third_party/zlib -I/b/build/slave/mac/build/src/net/../xcodebuild/DerivedSources/Debug/net -I../v8/include -I../third_party/nss/mozilla/nsprpub/pr/include -I../third_party/nss/mozilla/nsprpub/lib/ds -I../third_party/nss/mozilla/nsprpub/lib/libc/include -I../third_party/nss/mozilla/security/nss/lib/base -I../third_party/nss/mozilla/security/nss/lib/certdb -I../third_party/nss/mozilla/security/nss/lib/certhigh -I../third_party/nss/mozilla/security/nss/lib/cryptohi -I../third_party/nss/mozilla/security/nss/lib/dev -I../third_party/nss/mozilla/security/nss/lib/freebl -I../third_party/nss/mozilla/security/nss/lib/freebl/ecl -I../third_party/nss/mozilla/security/nss/lib/nss -I../third_party/nss/mozilla/security/nss/lib/pk11wrap -I../third_party/nss/mozilla/security/nss/lib/pkcs7 -I../third_party/nss/mozilla/security/nss/lib/pki -I../third_party/nss/mozilla/security/nss/lib/smime -I../third_party/nss/mozilla/security/nss/lib/softoken -I../third_party/nss/mozilla/security/nss/lib/util -Ithird_party/nss/ssl -I/b/build/slave/mac/build/src/net/../xcodebuild/net.build/Debug/net.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/net/../xcodebuild/net.build/Debug/net.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/net/spdy/spdy_session.cc -o /b/build/slave/mac/build/src/net/../xcodebuild/net.build/Debug/net.build/Objects-normal/i386/spdy_session.o + +Libtool ../xcodebuild/Debug/libnet.a normal i386 + cd /b/build/slave/mac/build/src/net + setenv DISTCC_HOSTS "distcc4.golo.chromium.org:3632,lzo,cpp/0 distcc7.golo.chromium.org:3632,lzo,cpp/0 distcc8.golo.chromium.org:3632,lzo,cpp/0 distcc10.golo.chromium.org:3632,lzo,cpp/0 distcc6.golo.chromium.org:3632,lzo,cpp/0 distcc3.golo.chromium.org:3632,lzo,cpp/0 distcc2.golo.chromium.org:3632,lzo,cpp/0 distcc1.golo.chromium.org:3632,lzo,cpp/0 distcc9.golo.chromium.org:3632,lzo,cpp/0 distcc5.golo.chromium.org:3632,lzo,cpp/0" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv MACOSX_DEPLOYMENT_TARGET 10.5 + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/libtool -static -arch_only i386 -syslibroot /Developer/SDKs/MacOSX10.5.sdk -L/b/build/slave/mac/build/src/net/../xcodebuild/Debug -filelist /b/build/slave/mac/build/src/net/../xcodebuild/net.build/Debug/net.build/Objects-normal/i386/net.LinkFileList -o /b/build/slave/mac/build/src/net/../xcodebuild/Debug/libnet.a + + +=== BUILD NATIVE TARGET googleurl OF PROJECT googleurl WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET sdch OF PROJECT sdch WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET ssl_false_start_blacklist_process OF PROJECT net WITH CONFIGURATION Debug === +Check dependencies +PhaseScriptExecution "Postbuild \"Strip If Needed\"" ../xcodebuild/net.build/Debug/ssl_false_start_blacklist_process.build/Script-5AC333953C6BB680BA591B7D.sh + cd /b/build/slave/mac/build/src/net + /bin/sh -c /b/build/slave/mac/build/src/net/../xcodebuild/net.build/Debug/ssl_false_start_blacklist_process.build/Script-5AC333953C6BB680BA591B7D.sh + + +=== BUILD AGGREGATE TARGET net_base Support OF PROJECT net WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET net_base OF PROJECT net WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD AGGREGATE TARGET ssl_host_info Support OF PROJECT net WITH CONFIGURATION Debug === +Check dependencies +PhaseScriptExecution "Rule \"genproto\"" "../xcodebuild/net.build/Debug/ssl_host_info Support.build/Script-2ECF060B764178F116E30CEF.sh" + cd /b/build/slave/mac/build/src/net + /bin/sh -c "\"/b/build/slave/mac/build/src/net/../xcodebuild/net.build/Debug/ssl_host_info Support.build/Script-2ECF060B764178F116E30CEF.sh\"" + +make: Nothing to be done for `all'. + +=== BUILD NATIVE TARGET ssl_host_info OF PROJECT net WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET ssl OF PROJECT ssl WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET app_unittests OF PROJECT app WITH CONFIGURATION Debug === +Check dependencies +Ld ../xcodebuild/Debug/app_unittests normal i386 + cd /b/build/slave/mac/build/src/app + setenv DISTCC_HOSTS "distcc4.golo.chromium.org:3632,lzo,cpp/0 distcc7.golo.chromium.org:3632,lzo,cpp/0 distcc8.golo.chromium.org:3632,lzo,cpp/0 distcc10.golo.chromium.org:3632,lzo,cpp/0 distcc6.golo.chromium.org:3632,lzo,cpp/0 distcc3.golo.chromium.org:3632,lzo,cpp/0 distcc2.golo.chromium.org:3632,lzo,cpp/0 distcc1.golo.chromium.org:3632,lzo,cpp/0 distcc9.golo.chromium.org:3632,lzo,cpp/0 distcc5.golo.chromium.org:3632,lzo,cpp/0" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv MACOSX_DEPLOYMENT_TARGET 10.5 + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/g++-4.2 -arch i386 -isysroot /Developer/SDKs/MacOSX10.5.sdk -L/b/build/slave/mac/build/src/app/../xcodebuild/Debug -F/b/build/slave/mac/build/src/app/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -filelist /b/build/slave/mac/build/src/app/../xcodebuild/app.build/Debug/app_unittests.build/Objects-normal/i386/app_unittests.LinkFileList -mmacosx-version-min=10.5 -Wl,-search_paths_first -lapp_base /b/build/slave/mac/build/src/xcodebuild/Debug/libtest_support_base.a /b/build/slave/mac/build/src/xcodebuild/Debug/libnet_test_support.a /b/build/slave/mac/build/src/xcodebuild/Debug/libskia.a /b/build/slave/mac/build/src/xcodebuild/Debug/libgmock.a /b/build/slave/mac/build/src/xcodebuild/Debug/libgtest.a /b/build/slave/mac/build/src/xcodebuild/Debug/libicui18n.a /b/build/slave/mac/build/src/xcodebuild/Debug/libicuuc.a /b/build/slave/mac/build/src/xcodebuild/Debug/libpng.a /b/build/slave/mac/build/src/xcodebuild/Debug/libchrome_zlib.a /b/build/slave/mac/build/src/xcodebuild/Debug/libjpeg.a /b/build/slave/mac/build/src/xcodebuild/Debug/libbase.a /b/build/slave/mac/build/src/xcodebuild/Debug/libmodp_b64.a /b/build/slave/mac/build/src/xcodebuild/Debug/libdynamic_annotations.a /b/build/slave/mac/build/src/xcodebuild/Debug/libnss.a /b/build/slave/mac/build/src/xcodebuild/Debug/libnspr.a /b/build/slave/mac/build/src/xcodebuild/Debug/libsqlite3.a /b/build/slave/mac/build/src/xcodebuild/Debug/libicudata.a /b/build/slave/mac/build/src/xcodebuild/Debug/libevent.a /b/build/slave/mac/build/src/xcodebuild/Debug/libbase_i18n.a /b/build/slave/mac/build/src/xcodebuild/Debug/libgfx.a /b/build/slave/mac/build/src/xcodebuild/Debug/libskia_opts.a /b/build/slave/mac/build/src/xcodebuild/Debug/libnet.a /b/build/slave/mac/build/src/xcodebuild/Debug/libgoogleurl.a /b/build/slave/mac/build/src/xcodebuild/Debug/libsdch.a /b/build/slave/mac/build/src/xcodebuild/Debug/libnet_base.a /b/build/slave/mac/build/src/xcodebuild/Debug/libssl_host_info.a /b/build/slave/mac/build/src/xcodebuild/Debug/libprotobuf_lite.a /b/build/slave/mac/build/src/xcodebuild/Debug/libv8_snapshot.a /b/build/slave/mac/build/src/xcodebuild/Debug/libv8_base.a /b/build/slave/mac/build/src/xcodebuild/Debug/libssl.a -framework OpenGL -framework AppKit -framework Carbon -framework CoreFoundation -framework Foundation -framework IOKit -framework Security -framework SystemConfiguration -lresolv -o /b/build/slave/mac/build/src/app/../xcodebuild/Debug/app_unittests + +PhaseScriptExecution "Postbuild \"Strip If Needed\"" ../xcodebuild/app.build/Debug/app_unittests.build/Script-07E688F6B96AA35A01000844.sh + cd /b/build/slave/mac/build/src/app + /bin/sh -c /b/build/slave/mac/build/src/app/../xcodebuild/app.build/Debug/app_unittests.build/Script-07E688F6B96AA35A01000844.sh + + +=== BUILD NATIVE TARGET base_unittests OF PROJECT base WITH CONFIGURATION Debug === +Check dependencies +PhaseScriptExecution "Postbuild \"Strip If Needed\"" ../xcodebuild/base.build/Debug/base_unittests.build/Script-25445A6E87471CA82F7A2A04.sh + cd /b/build/slave/mac/build/src/base + /bin/sh -c /b/build/slave/mac/build/src/base/../xcodebuild/base.build/Debug/base_unittests.build/Script-25445A6E87471CA82F7A2A04.sh + + +=== BUILD NATIVE TARGET test_support_perf OF PROJECT base WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD AGGREGATE TARGET common_constants Support OF PROJECT chrome WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET common_constants OF PROJECT chrome WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD AGGREGATE TARGET cacheinvalidation_proto OF PROJECT cacheinvalidation WITH CONFIGURATION Debug === +Check dependencies +PhaseScriptExecution "Rule \"genproto\"" ../../xcodebuild/cacheinvalidation.build/Debug/cacheinvalidation_proto.build/Script-F6284ABD289942E92747985A.sh + cd /b/build/slave/mac/build/src/third_party/cacheinvalidation + /bin/sh -c /b/build/slave/mac/build/src/third_party/cacheinvalidation/../../xcodebuild/cacheinvalidation.build/Debug/cacheinvalidation_proto.build/Script-F6284ABD289942E92747985A.sh + +make: Nothing to be done for `all'. + +=== BUILD NATIVE TARGET cacheinvalidation OF PROJECT cacheinvalidation WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET sync_notifier OF PROJECT chrome WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET notifier OF PROJECT jingle WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET libjingle OF PROJECT libjingle WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD AGGREGATE TARGET sync_proto_cpp OF PROJECT sync_proto WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET sync OF PROJECT chrome WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD AGGREGATE TARGET chrome_resources OF PROJECT chrome WITH CONFIGURATION Debug === +Check dependencies +PhaseScriptExecution "Rule \"grit\"" ../xcodebuild/chrome.build/Debug/chrome_resources.build/Script-9CA0E2FB3E57937086B90B01.sh + cd /b/build/slave/mac/build/src/chrome + /bin/sh -c /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/chrome_resources.build/Script-9CA0E2FB3E57937086B90B01.sh + +make: Nothing to be done for `all'. + +=== BUILD AGGREGATE TARGET chrome_strings OF PROJECT chrome WITH CONFIGURATION Debug === +Check dependencies +PhaseScriptExecution "Rule \"grit\"" ../xcodebuild/chrome.build/Debug/chrome_strings.build/Script-E0610D9F312AEECB2F90C4E3.sh + cd /b/build/slave/mac/build/src/chrome + /bin/sh -c /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/chrome_strings.build/Script-E0610D9F312AEECB2F90C4E3.sh + +make: Nothing to be done for `all'. + +=== BUILD AGGREGATE TARGET theme_resources OF PROJECT chrome WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD AGGREGATE TARGET npapi OF PROJECT npapi WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD AGGREGATE TARGET ppapi_c OF PROJECT ppapi WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD AGGREGATE TARGET webkit_resources OF PROJECT webkit_support WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD AGGREGATE TARGET webkit_strings OF PROJECT webkit_support WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD AGGREGATE TARGET webkit_user_agent Support OF PROJECT webkit_support WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET webkit_user_agent OF PROJECT webkit_support WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET glue OF PROJECT webkit_support WITH CONFIGURATION Debug === +Check dependencies +Distributed-CompileC ../../xcodebuild/webkit_support.build/Debug/glue.build/Objects-normal/i386/context_menu.o ../glue/context_menu.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/webkit/support + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS localhost + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/webkit/support/../../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/webkit/support/../../xcodebuild/Debug/include -I../../third_party/icu/public/common -I../../third_party/icu/public/i18n -I/b/build/slave/mac/build/src/webkit/support/../../xcodebuild/webkit_support.build/DerivedSources/Debug -I/b/build/slave/mac/build/src/webkit/support/../../xcodebuild/DerivedSources/Debug/webkit -I../../gpu -I../.. -I../../third_party -I../../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/webkit/support/../../xcodebuild/DerivedSources/Debug/app -I../../skia/config -I../../third_party/skia/include/config -I../../third_party/skia/include/core -I../../third_party/skia/include/effects -I../../skia/ext -I../../third_party/npapi -I../../third_party/npapi/bindings -I/b/build/slave/mac/build/src/webkit/support/../../xcodebuild/webkit_support.build/Debug/glue.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/webkit/support/../../xcodebuild/webkit_support.build/Debug/glue.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/webkit/support/../glue/context_menu.cc -o /b/build/slave/mac/build/src/webkit/support/../../xcodebuild/webkit_support.build/Debug/glue.build/Objects-normal/i386/context_menu.o + +Libtool ../../xcodebuild/Debug/libglue.a normal i386 + cd /b/build/slave/mac/build/src/webkit/support + setenv DISTCC_HOSTS "distcc4.golo.chromium.org:3632,lzo,cpp/0 distcc7.golo.chromium.org:3632,lzo,cpp/0 distcc8.golo.chromium.org:3632,lzo,cpp/0 distcc10.golo.chromium.org:3632,lzo,cpp/0 distcc6.golo.chromium.org:3632,lzo,cpp/0 distcc3.golo.chromium.org:3632,lzo,cpp/0 distcc2.golo.chromium.org:3632,lzo,cpp/0 distcc1.golo.chromium.org:3632,lzo,cpp/0 distcc9.golo.chromium.org:3632,lzo,cpp/0 distcc5.golo.chromium.org:3632,lzo,cpp/0" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv MACOSX_DEPLOYMENT_TARGET 10.5 + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/libtool -static -arch_only i386 -syslibroot /Developer/SDKs/MacOSX10.5.sdk -L/b/build/slave/mac/build/src/webkit/support/../../xcodebuild/Debug -filelist /b/build/slave/mac/build/src/webkit/support/../../xcodebuild/webkit_support.build/Debug/glue.build/Objects-normal/i386/glue.LinkFileList -o /b/build/slave/mac/build/src/webkit/support/../../xcodebuild/Debug/libglue.a + + +=== BUILD NATIVE TARGET common OF PROJECT chrome WITH CONFIGURATION Debug === +Check dependencies +Distributed-CompileC ../xcodebuild/chrome.build/Debug/common.build/Objects-normal/i386/appcache_backend_proxy.o common/appcache/appcache_backend_proxy.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DLIBXML_STATIC -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I../gpu -I.. -I../third_party -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/sqlite -I../third_party/zlib -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/common/appcache/appcache_backend_proxy.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/Objects-normal/i386/appcache_backend_proxy.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/common.build/Objects-normal/i386/web_database_observer_impl.o common/web_database_observer_impl.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DLIBXML_STATIC -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I../gpu -I.. -I../third_party -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/sqlite -I../third_party/zlib -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/common/web_database_observer_impl.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/Objects-normal/i386/web_database_observer_impl.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/common.build/Objects-normal/i386/database_util.o common/database_util.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DLIBXML_STATIC -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I../gpu -I.. -I../third_party -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/sqlite -I../third_party/zlib -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/common/database_util.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/Objects-normal/i386/database_util.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/common.build/Objects-normal/i386/appcache_dispatcher.o common/appcache/appcache_dispatcher.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DLIBXML_STATIC -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I../gpu -I.. -I../third_party -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/sqlite -I../third_party/zlib -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/common/appcache/appcache_dispatcher.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/Objects-normal/i386/appcache_dispatcher.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/common.build/Objects-normal/i386/db_message_filter.o common/db_message_filter.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DLIBXML_STATIC -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I../gpu -I.. -I../third_party -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/sqlite -I../third_party/zlib -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/common/db_message_filter.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/Objects-normal/i386/db_message_filter.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/common.build/Objects-normal/i386/file_system_dispatcher.o common/file_system/file_system_dispatcher.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DLIBXML_STATIC -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I../gpu -I.. -I../third_party -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/sqlite -I../third_party/zlib -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/common/file_system/file_system_dispatcher.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/Objects-normal/i386/file_system_dispatcher.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/common.build/Objects-normal/i386/extension_localization_peer.o common/extensions/extension_localization_peer.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DLIBXML_STATIC -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I../gpu -I.. -I../third_party -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/sqlite -I../third_party/zlib -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/common/extensions/extension_localization_peer.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/Objects-normal/i386/extension_localization_peer.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/common.build/Objects-normal/i386/render_messages.o common/render_messages.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DLIBXML_STATIC -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I../gpu -I.. -I../third_party -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/sqlite -I../third_party/zlib -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/common/render_messages.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/Objects-normal/i386/render_messages.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/common.build/Objects-normal/i386/socket_stream_dispatcher.o common/socket_stream_dispatcher.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DLIBXML_STATIC -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I../gpu -I.. -I../third_party -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/sqlite -I../third_party/zlib -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/common/socket_stream_dispatcher.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/Objects-normal/i386/socket_stream_dispatcher.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/common.build/Objects-normal/i386/render_messages_params.o common/render_messages_params.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DLIBXML_STATIC -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I../gpu -I.. -I../third_party -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/sqlite -I../third_party/zlib -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/common/render_messages_params.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/Objects-normal/i386/render_messages_params.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/common.build/Objects-normal/i386/resource_dispatcher.o common/resource_dispatcher.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DLIBXML_STATIC -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I../gpu -I.. -I../third_party -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/sqlite -I../third_party/zlib -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/common/resource_dispatcher.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/Objects-normal/i386/resource_dispatcher.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/common.build/Objects-normal/i386/webblobregistry_impl.o common/webblobregistry_impl.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DLIBXML_STATIC -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I../gpu -I.. -I../third_party -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/sqlite -I../third_party/zlib -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/common/webblobregistry_impl.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/Objects-normal/i386/webblobregistry_impl.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/common.build/Objects-normal/i386/logging_chrome.o common/logging_chrome.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DLIBXML_STATIC -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I../gpu -I.. -I../third_party -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/sqlite -I../third_party/zlib -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/common/logging_chrome.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/Objects-normal/i386/logging_chrome.o + +Libtool ../xcodebuild/Debug/libcommon.a normal i386 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_HOSTS "distcc4.golo.chromium.org:3632,lzo,cpp/0 distcc7.golo.chromium.org:3632,lzo,cpp/0 distcc8.golo.chromium.org:3632,lzo,cpp/0 distcc10.golo.chromium.org:3632,lzo,cpp/0 distcc6.golo.chromium.org:3632,lzo,cpp/0 distcc3.golo.chromium.org:3632,lzo,cpp/0 distcc2.golo.chromium.org:3632,lzo,cpp/0 distcc1.golo.chromium.org:3632,lzo,cpp/0 distcc9.golo.chromium.org:3632,lzo,cpp/0 distcc5.golo.chromium.org:3632,lzo,cpp/0" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv MACOSX_DEPLOYMENT_TARGET 10.5 + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/libtool -static -arch_only i386 -syslibroot /Developer/SDKs/MacOSX10.5.sdk -L/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -filelist /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/common.build/Objects-normal/i386/common.LinkFileList -o /b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/libcommon.a + + +=== BUILD NATIVE TARGET common_net OF PROJECT chrome WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD AGGREGATE TARGET default_plugin_resources OF PROJECT default_plugin WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET default_plugin OF PROJECT default_plugin WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET libxml OF PROJECT libxml WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET ipc OF PROJECT ipc WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET printing OF PROJECT printing WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET bzip2 OF PROJECT bzip2 WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET appcache OF PROJECT webkit_support WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET blob OF PROJECT webkit_support WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET gles2_implementation OF PROJECT gpu WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET gles2_cmd_helper OF PROJECT gpu WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET command_buffer_client OF PROJECT gpu WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET command_buffer_common OF PROJECT gpu WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET ppapi_shared_impl OF PROJECT ppapi WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET gpu_plugin OF PROJECT gpu WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET command_buffer_service OF PROJECT gpu WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET translator_glsl OF PROJECT build_angle WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET translator_common OF PROJECT build_angle WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD AGGREGATE TARGET libvpx_include OF PROJECT libvpx WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD AGGREGATE TARGET chromotocol_proto OF PROJECT chromotocol WITH CONFIGURATION Debug === +Check dependencies +PhaseScriptExecution "Rule \"genproto\"" ../../xcodebuild/chromotocol.build/Debug/chromotocol_proto.build/Script-FC778EC6151CD2300601907F.sh + cd /b/build/slave/mac/build/src/remoting/proto + /bin/sh -c /b/build/slave/mac/build/src/remoting/proto/../../xcodebuild/chromotocol.build/Debug/chromotocol_proto.build/Script-FC778EC6151CD2300601907F.sh + +make: Nothing to be done for `all'. + +=== BUILD NATIVE TARGET chromotocol_proto_lib OF PROJECT chromotocol WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD AGGREGATE TARGET trace_proto OF PROJECT trace WITH CONFIGURATION Debug === +Check dependencies +PhaseScriptExecution "Rule \"genproto\"" ../../xcodebuild/trace.build/Debug/trace_proto.build/Script-1767794B043163548BD577A7.sh + cd /b/build/slave/mac/build/src/remoting/proto + /bin/sh -c /b/build/slave/mac/build/src/remoting/proto/../../xcodebuild/trace.build/Debug/trace_proto.build/Script-1767794B043163548BD577A7.sh + +make: Nothing to be done for `all'. + +=== BUILD NATIVE TARGET trace_proto_lib OF PROJECT trace WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET chromoting_base OF PROJECT remoting WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET chromoting_plugin OF PROJECT remoting WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD AGGREGATE TARGET config_sources OF PROJECT yasm WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET genmacro OF PROJECT yasm WITH CONFIGURATION Debug === +Check dependencies +PhaseScriptExecution "Postbuild \"Strip If Needed\"" ../../xcodebuild/yasm.build/Debug/genmacro.build/Script-4C7889A20E677CE959D733D8.sh + cd /b/build/slave/mac/build/src/third_party/yasm + /bin/sh -c /b/build/slave/mac/build/src/third_party/yasm/../../xcodebuild/yasm.build/Debug/genmacro.build/Script-4C7889A20E677CE959D733D8.sh + + +=== BUILD NATIVE TARGET genmodule OF PROJECT yasm WITH CONFIGURATION Debug === +Check dependencies +PhaseScriptExecution "Postbuild \"Strip If Needed\"" ../../xcodebuild/yasm.build/Debug/genmodule.build/Script-4889D2DFD6E02E6FD4E5DD23.sh + cd /b/build/slave/mac/build/src/third_party/yasm + /bin/sh -c /b/build/slave/mac/build/src/third_party/yasm/../../xcodebuild/yasm.build/Debug/genmodule.build/Script-4889D2DFD6E02E6FD4E5DD23.sh + + +=== BUILD NATIVE TARGET genperf_libs OF PROJECT yasm WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET genperf OF PROJECT yasm WITH CONFIGURATION Debug === +Check dependencies +PhaseScriptExecution "Postbuild \"Strip If Needed\"" ../../xcodebuild/yasm.build/Debug/genperf.build/Script-1D9F9DCA8B66106271900FD2.sh + cd /b/build/slave/mac/build/src/third_party/yasm + /bin/sh -c /b/build/slave/mac/build/src/third_party/yasm/../../xcodebuild/yasm.build/Debug/genperf.build/Script-1D9F9DCA8B66106271900FD2.sh + + +=== BUILD NATIVE TARGET genversion OF PROJECT yasm WITH CONFIGURATION Debug === +Check dependencies +PhaseScriptExecution "Postbuild \"Strip If Needed\"" ../../xcodebuild/yasm.build/Debug/genversion.build/Script-86C7D43B641DD7FA312A063A.sh + cd /b/build/slave/mac/build/src/third_party/yasm + /bin/sh -c /b/build/slave/mac/build/src/third_party/yasm/../../xcodebuild/yasm.build/Debug/genversion.build/Script-86C7D43B641DD7FA312A063A.sh + + +=== BUILD AGGREGATE TARGET generate_files OF PROJECT yasm WITH CONFIGURATION Debug === +Check dependencies +PhaseScriptExecution "Rule \"generate_gperf\"" ../../xcodebuild/yasm.build/Debug/generate_files.build/Script-17643966799E5C48ACBBC76C.sh + cd /b/build/slave/mac/build/src/third_party/yasm + /bin/sh -c /b/build/slave/mac/build/src/third_party/yasm/../../xcodebuild/yasm.build/Debug/generate_files.build/Script-17643966799E5C48ACBBC76C.sh + +make: Nothing to be done for `all'. + +=== BUILD NATIVE TARGET genstring OF PROJECT yasm WITH CONFIGURATION Debug === +Check dependencies +PhaseScriptExecution "Postbuild \"Strip If Needed\"" ../../xcodebuild/yasm.build/Debug/genstring.build/Script-8C8DB269C91395EE620901FA.sh + cd /b/build/slave/mac/build/src/third_party/yasm + /bin/sh -c /b/build/slave/mac/build/src/third_party/yasm/../../xcodebuild/yasm.build/Debug/genstring.build/Script-8C8DB269C91395EE620901FA.sh + + +=== BUILD NATIVE TARGET re2c OF PROJECT yasm WITH CONFIGURATION Debug === +Check dependencies +PhaseScriptExecution "Postbuild \"Strip If Needed\"" ../../xcodebuild/yasm.build/Debug/re2c.build/Script-80CBF3376FF074DC264D0801.sh + cd /b/build/slave/mac/build/src/third_party/yasm + /bin/sh -c /b/build/slave/mac/build/src/third_party/yasm/../../xcodebuild/yasm.build/Debug/re2c.build/Script-80CBF3376FF074DC264D0801.sh + + +=== BUILD AGGREGATE TARGET yasm Support OF PROJECT yasm WITH CONFIGURATION Debug === +Check dependencies +PhaseScriptExecution "Rule \"generate_gperf\"" "../../xcodebuild/yasm.build/Debug/yasm Support.build/Script-01D2B874063C3CD35D7BB021.sh" + cd /b/build/slave/mac/build/src/third_party/yasm + /bin/sh -c "\"/b/build/slave/mac/build/src/third_party/yasm/../../xcodebuild/yasm.build/Debug/yasm Support.build/Script-01D2B874063C3CD35D7BB021.sh\"" + +make: Nothing to be done for `all'. +PhaseScriptExecution "Rule \"generate_re2c\"" "../../xcodebuild/yasm.build/Debug/yasm Support.build/Script-78F98A3A8E7B6F2F3A9E27E4.sh" + cd /b/build/slave/mac/build/src/third_party/yasm + /bin/sh -c "\"/b/build/slave/mac/build/src/third_party/yasm/../../xcodebuild/yasm.build/Debug/yasm Support.build/Script-78F98A3A8E7B6F2F3A9E27E4.sh\"" + +make: Nothing to be done for `all'. + +=== BUILD NATIVE TARGET yasm OF PROJECT yasm WITH CONFIGURATION Debug === +Check dependencies +PhaseScriptExecution "Postbuild \"Strip If Needed\"" ../../xcodebuild/yasm.build/Debug/yasm.build/Script-553C58D6C5752DBCAC66E158.sh + cd /b/build/slave/mac/build/src/third_party/yasm + /bin/sh -c /b/build/slave/mac/build/src/third_party/yasm/../../xcodebuild/yasm.build/Debug/yasm.build/Script-553C58D6C5752DBCAC66E158.sh + + +=== BUILD AGGREGATE TARGET assemble_ffmpeg_asm OF PROJECT ffmpeg WITH CONFIGURATION Debug === +Check dependencies +PhaseScriptExecution "Rule \"assemble\"" ../../xcodebuild/ffmpeg.build/Debug/assemble_ffmpeg_asm.build/Script-CC0AEB08A28175BFCADB3230.sh + cd /b/build/slave/mac/build/src/third_party/ffmpeg + /bin/sh -c /b/build/slave/mac/build/src/third_party/ffmpeg/../../xcodebuild/ffmpeg.build/Debug/assemble_ffmpeg_asm.build/Script-CC0AEB08A28175BFCADB3230.sh + +make: Nothing to be done for `all'. + +=== BUILD AGGREGATE TARGET make_ffmpeg_asm_lib OF PROJECT ffmpeg WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD AGGREGATE TARGET ffmpegsumo Support OF PROJECT ffmpeg WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET ffmpegsumo OF PROJECT ffmpeg WITH CONFIGURATION Debug === +Check dependencies +PhaseScriptExecution "Postbuild \"Strip If Needed\"" ../../xcodebuild/ffmpeg.build/Debug/ffmpegsumo.build/Script-5F4EE13F45F454D1CDFA2E81.sh + cd /b/build/slave/mac/build/src/third_party/ffmpeg + /bin/sh -c /b/build/slave/mac/build/src/third_party/ffmpeg/../../xcodebuild/ffmpeg.build/Debug/ffmpegsumo.build/Script-5F4EE13F45F454D1CDFA2E81.sh + + +=== BUILD NATIVE TARGET ffmpegsumo_nolink OF PROJECT ffmpeg WITH CONFIGURATION Debug === +Check dependencies +PhaseScriptExecution "Postbuild \"Strip If Needed\"" ../../xcodebuild/ffmpeg.build/Debug/ffmpegsumo_nolink.build/Script-5FDA933F15D46D3AB0B441AC.sh + cd /b/build/slave/mac/build/src/third_party/ffmpeg + /bin/sh -c /b/build/slave/mac/build/src/third_party/ffmpeg/../../xcodebuild/ffmpeg.build/Debug/ffmpegsumo_nolink.build/Script-5FDA933F15D46D3AB0B441AC.sh + + +=== BUILD AGGREGATE TARGET ffmpeg Support OF PROJECT ffmpeg WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET ffmpeg OF PROJECT ffmpeg WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET media OF PROJECT media WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET chromoting_jingle_glue OF PROJECT remoting WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET expat OF PROJECT expat WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET libjingle_p2p OF PROJECT libjingle WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET libsrtp OF PROJECT libsrtp WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET chromoting_client OF PROJECT remoting WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET chromoting_protocol OF PROJECT remoting WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET ppapi_cpp_objects OF PROJECT ppapi WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET sync_listen_notifications OF PROJECT sync_tools WITH CONFIGURATION Debug === +Check dependencies +Ld ../../../../xcodebuild/Debug/sync_listen_notifications normal i386 + cd /b/build/slave/mac/build/src/chrome/browser/sync/tools + setenv DISTCC_HOSTS "distcc4.golo.chromium.org:3632,lzo,cpp/0 distcc7.golo.chromium.org:3632,lzo,cpp/0 distcc8.golo.chromium.org:3632,lzo,cpp/0 distcc10.golo.chromium.org:3632,lzo,cpp/0 distcc6.golo.chromium.org:3632,lzo,cpp/0 distcc3.golo.chromium.org:3632,lzo,cpp/0 distcc2.golo.chromium.org:3632,lzo,cpp/0 distcc1.golo.chromium.org:3632,lzo,cpp/0 distcc9.golo.chromium.org:3632,lzo,cpp/0 distcc5.golo.chromium.org:3632,lzo,cpp/0" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv MACOSX_DEPLOYMENT_TARGET 10.5 + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/g++-4.2 -arch i386 -isysroot /Developer/SDKs/MacOSX10.5.sdk -L/b/build/slave/mac/build/src/chrome/browser/sync/tools/../../../../xcodebuild/Debug -F/b/build/slave/mac/build/src/chrome/browser/sync/tools/../../../../xcodebuild/Debug -filelist /b/build/slave/mac/build/src/chrome/browser/sync/tools/../../../../xcodebuild/sync_tools.build/Debug/sync_listen_notifications.build/Objects-normal/i386/sync_listen_notifications.LinkFileList -mmacosx-version-min=10.5 -Wl,-search_paths_first /b/build/slave/mac/build/src/xcodebuild/Debug/libbase.a /b/build/slave/mac/build/src/xcodebuild/Debug/libcommon_constants.a /b/build/slave/mac/build/src/xcodebuild/Debug/libsync_notifier.a /b/build/slave/mac/build/src/xcodebuild/Debug/libnotifier.a /b/build/slave/mac/build/src/xcodebuild/Debug/libjingle.a /b/build/slave/mac/build/src/xcodebuild/Debug/libmodp_b64.a /b/build/slave/mac/build/src/xcodebuild/Debug/libdynamic_annotations.a /b/build/slave/mac/build/src/xcodebuild/Debug/libnss.a /b/build/slave/mac/build/src/xcodebuild/Debug/libnspr.a /b/build/slave/mac/build/src/xcodebuild/Debug/libsqlite3.a /b/build/slave/mac/build/src/xcodebuild/Debug/libicui18n.a /b/build/slave/mac/build/src/xcodebuild/Debug/libicuuc.a /b/build/slave/mac/build/src/xcodebuild/Debug/libicudata.a /b/build/slave/mac/build/src/xcodebuild/Debug/libevent.a /b/build/slave/mac/build/src/xcodebuild/Debug/libsync.a /b/build/slave/mac/build/src/xcodebuild/Debug/libcommon.a /b/build/slave/mac/build/src/xcodebuild/Debug/libcommon_net.a /b/build/slave/mac/build/src/xcodebuild/Debug/libapp_base.a /b/build/slave/mac/build/src/xcodebuild/Debug/libbase_i18n.a /b/build/slave/mac/build/src/xcodebuild/Debug/libgfx.a /b/build/slave/mac/build/src/xcodebuild/Debug/libskia.a /b/build/slave/mac/build/src/xcodebuild/Debug/libskia_opts.a /b/build/slave/mac/build/src/xcodebuild/Debug/libpng.a /b/build/slave/mac/build/src/xcodebuild/Debug/libchrome_zlib.a /b/build/slave/mac/build/src/xcodebuild/Debug/libjpeg.a /b/build/slave/mac/build/src/xcodebuild/Debug/libnet.a /b/build/slave/mac/build/src/xcodebuild/Debug/libgoogleurl.a /b/build/slave/mac/build/src/xcodebuild/Debug/libsdch.a /b/build/slave/mac/build/src/xcodebuild/Debug/libnet_base.a /b/build/slave/mac/build/src/xcodebuild/Debug/libssl_host_info.a /b/build/slave/mac/build/src/xcodebuild/Debug/libprotobuf_lite.a /b/build/slave/mac/build/src/xcodebuild/Debug/libv8_snapshot.a /b/build/slave/mac/build/src/xcodebuild/Debug/libv8_base.a /b/build/slave/mac/build/src/xcodebuild/Debug/libssl.a /b/build/slave/mac/build/src/xcodebuild/Debug/libdefault_plugin.a /b/build/slave/mac/build/src/xcodebuild/Debug/libxml2.a /b/build/slave/mac/build/src/xcodebuild/Debug/libipc.a /b/build/slave/mac/build/src/xcodebuild/Debug/libprinting.a /b/build/slave/mac/build/src/xcodebuild/Debug/libchrome_bz2.a /b/build/slave/mac/build/src/xcodebuild/Debug/libappcache.a /b/build/slave/mac/build/src/xcodebuild/Debug/libblob.a /b/build/slave/mac/build/src/xcodebuild/Debug/libglue.a /b/build/slave/mac/build/src/xcodebuild/Debug/libgles2_implementation.a /b/build/slave/mac/build/src/xcodebuild/Debug/libgles2_cmd_helper.a /b/build/slave/mac/build/src/xcodebuild/Debug/libcommand_buffer_client.a /b/build/slave/mac/build/src/xcodebuild/Debug/libcommand_buffer_common.a /b/build/slave/mac/build/src/xcodebuild/Debug/libppapi_shared_impl.a /b/build/slave/mac/build/src/xcodebuild/Debug/libwebkit_user_agent.a /b/build/slave/mac/build/src/xcodebuild/Debug/libgpu_plugin.a /b/build/slave/mac/build/src/xcodebuild/Debug/libcommand_buffer_service.a /b/build/slave/mac/build/src/xcodebuild/Debug/libtranslator_glsl.a /b/build/slave/mac/build/src/xcodebuild/Debug/libtranslator_common.a /b/build/slave/mac/build/src/xcodebuild/Debug/libchromoting_plugin.a /b/build/slave/mac/build/src/xcodebuild/Debug/libchromoting_base.a /b/build/slave/mac/build/src/xcodebuild/Debug/libmedia.a /b/build/slave/mac/build/src/xcodebuild/Debug/libffmpeg.a /b/build/slave/mac/build/src/xcodebuild/Debug/libchromoting_jingle_glue.a /b/build/slave/mac/build/src/xcodebuild/Debug/libexpat.a /b/build/slave/mac/build/src/xcodebuild/Debug/libjingle_p2p.a /b/build/slave/mac/build/src/xcodebuild/Debug/libsrtp.a /b/build/slave/mac/build/src/xcodebuild/Debug/libchromotocol_proto_lib.a /b/build/slave/mac/build/src/xcodebuild/Debug/libtrace_proto_lib.a /b/build/slave/mac/build/src/xcodebuild/Debug/libchromoting_client.a /b/build/slave/mac/build/src/xcodebuild/Debug/libchromoting_protocol.a /b/build/slave/mac/build/src/xcodebuild/Debug/libppapi_cpp_objects.a /b/build/slave/mac/build/src/xcodebuild/Debug/libcacheinvalidation.a -framework AppKit -framework Carbon -framework CoreFoundation -framework Foundation -framework IOKit -framework Security -framework OpenGL -framework SystemConfiguration -lresolv -lcups -framework QuartzCore -framework AudioToolbox -framework CoreAudio -o /b/build/slave/mac/build/src/chrome/browser/sync/tools/../../../../xcodebuild/Debug/sync_listen_notifications + +PhaseScriptExecution "Postbuild \"Strip If Needed\"" ../../../../xcodebuild/sync_tools.build/Debug/sync_listen_notifications.build/Script-577A6588CCA38D4E2CF91DDA.sh + cd /b/build/slave/mac/build/src/chrome/browser/sync/tools + /bin/sh -c /b/build/slave/mac/build/src/chrome/browser/sync/tools/../../../../xcodebuild/sync_tools.build/Debug/sync_listen_notifications.build/Script-577A6588CCA38D4E2CF91DDA.sh + + +=== BUILD AGGREGATE TARGET platform_locale_settings OF PROJECT chrome WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD AGGREGATE TARGET chrome_extra_resources OF PROJECT chrome WITH CONFIGURATION Debug === +Check dependencies +PhaseScriptExecution "Rule \"grit\"" ../xcodebuild/chrome.build/Debug/chrome_extra_resources.build/Script-3DF0B99B7073177A35D87914.sh + cd /b/build/slave/mac/build/src/chrome + /bin/sh -c /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/chrome_extra_resources.build/Script-3DF0B99B7073177A35D87914.sh + +make: Nothing to be done for `all'. + +=== BUILD AGGREGATE TARGET default_extensions OF PROJECT chrome WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET debugger OF PROJECT chrome WITH CONFIGURATION Debug === +Check dependencies +Distributed-CompileC ../xcodebuild/chrome.build/Debug/debugger.build/Objects-normal/i386/debugger_remote_service.o browser/debugger/debugger_remote_service.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/debugger.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/debugger.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/debugger/debugger_remote_service.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/debugger.build/Objects-normal/i386/debugger_remote_service.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/debugger.build/Objects-normal/i386/extension_ports_remote_service.o browser/debugger/extension_ports_remote_service.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/debugger.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/debugger.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/debugger/extension_ports_remote_service.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/debugger.build/Objects-normal/i386/extension_ports_remote_service.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/debugger.build/Objects-normal/i386/devtools_window.o browser/debugger/devtools_window.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/debugger.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/debugger.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/debugger/devtools_window.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/debugger.build/Objects-normal/i386/devtools_window.o + +Libtool ../xcodebuild/Debug/libdebugger.a normal i386 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_HOSTS "distcc4.golo.chromium.org:3632,lzo,cpp/0 distcc7.golo.chromium.org:3632,lzo,cpp/0 distcc8.golo.chromium.org:3632,lzo,cpp/0 distcc10.golo.chromium.org:3632,lzo,cpp/0 distcc6.golo.chromium.org:3632,lzo,cpp/0 distcc3.golo.chromium.org:3632,lzo,cpp/0 distcc2.golo.chromium.org:3632,lzo,cpp/0 distcc1.golo.chromium.org:3632,lzo,cpp/0 distcc9.golo.chromium.org:3632,lzo,cpp/0 distcc5.golo.chromium.org:3632,lzo,cpp/0" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv MACOSX_DEPLOYMENT_TARGET 10.5 + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/libtool -static -arch_only i386 -syslibroot /Developer/SDKs/MacOSX10.5.sdk -L/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -filelist /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/debugger.build/Objects-normal/i386/debugger.LinkFileList -o /b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/libdebugger.a + + +=== BUILD NATIVE TARGET plugin OF PROJECT chrome WITH CONFIGURATION Debug === +Check dependencies +Distributed-CompileC ../xcodebuild/chrome.build/Debug/plugin.build/Objects-normal/i386/plugin_thread.o plugin/plugin_thread.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS localhost + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../gpu -I.. -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/plugin.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/plugin.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/plugin/plugin_thread.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/plugin.build/Objects-normal/i386/plugin_thread.o + +Libtool ../xcodebuild/Debug/libplugin.a normal i386 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_HOSTS "distcc4.golo.chromium.org:3632,lzo,cpp/0 distcc7.golo.chromium.org:3632,lzo,cpp/0 distcc8.golo.chromium.org:3632,lzo,cpp/0 distcc10.golo.chromium.org:3632,lzo,cpp/0 distcc6.golo.chromium.org:3632,lzo,cpp/0 distcc3.golo.chromium.org:3632,lzo,cpp/0 distcc2.golo.chromium.org:3632,lzo,cpp/0 distcc1.golo.chromium.org:3632,lzo,cpp/0 distcc9.golo.chromium.org:3632,lzo,cpp/0 distcc5.golo.chromium.org:3632,lzo,cpp/0" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv MACOSX_DEPLOYMENT_TARGET 10.5 + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/libtool -static -arch_only i386 -syslibroot /Developer/SDKs/MacOSX10.5.sdk -L/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -filelist /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/plugin.build/Objects-normal/i386/plugin.LinkFileList -o /b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/libplugin.a + + +=== BUILD NATIVE TARGET utility OF PROJECT chrome WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET profile_import OF PROJECT chrome WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET chrome_gpu OF PROJECT chrome WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET ppapi_plugin OF PROJECT chrome WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET worker OF PROJECT chrome WITH CONFIGURATION Debug === +Check dependencies +Distributed-CompileC ../xcodebuild/chrome.build/Debug/worker.build/Objects-normal/i386/worker_webkitclient_impl.o worker/worker_webkitclient_impl.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS localhost + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I.. -I../gpu -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/worker.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/worker.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/worker/worker_webkitclient_impl.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/worker.build/Objects-normal/i386/worker_webkitclient_impl.o + +Libtool ../xcodebuild/Debug/libworker.a normal i386 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_HOSTS "distcc4.golo.chromium.org:3632,lzo,cpp/0 distcc7.golo.chromium.org:3632,lzo,cpp/0 distcc8.golo.chromium.org:3632,lzo,cpp/0 distcc10.golo.chromium.org:3632,lzo,cpp/0 distcc6.golo.chromium.org:3632,lzo,cpp/0 distcc3.golo.chromium.org:3632,lzo,cpp/0 distcc2.golo.chromium.org:3632,lzo,cpp/0 distcc1.golo.chromium.org:3632,lzo,cpp/0 distcc9.golo.chromium.org:3632,lzo,cpp/0 distcc5.golo.chromium.org:3632,lzo,cpp/0" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv MACOSX_DEPLOYMENT_TARGET 10.5 + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/libtool -static -arch_only i386 -syslibroot /Developer/SDKs/MacOSX10.5.sdk -L/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -filelist /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/worker.build/Objects-normal/i386/worker.LinkFileList -o /b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/libworker.a + + +=== BUILD NATIVE TARGET syncapi OF PROJECT chrome WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET service OF PROJECT chrome WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD AGGREGATE TARGET device_management_proto_cpp OF PROJECT device_management_proto WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD AGGREGATE TARGET safe_browsing_csd_proto OF PROJECT chrome WITH CONFIGURATION Debug === +Check dependencies +PhaseScriptExecution "Rule \"genproto\"" ../xcodebuild/chrome.build/Debug/safe_browsing_csd_proto.build/Script-A64480A7A7F73B68D5D0EBFB.sh + cd /b/build/slave/mac/build/src/chrome + /bin/sh -c /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/safe_browsing_csd_proto.build/Script-A64480A7A7F73B68D5D0EBFB.sh + +make: Nothing to be done for `all'. + +=== BUILD AGGREGATE TARGET safe_browsing_report_proto OF PROJECT chrome WITH CONFIGURATION Debug === +Check dependencies +PhaseScriptExecution "Rule \"genproto\"" ../xcodebuild/chrome.build/Debug/safe_browsing_report_proto.build/Script-6B102C7C9773962E1B26CF1B.sh + cd /b/build/slave/mac/build/src/chrome + /bin/sh -c /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/safe_browsing_report_proto.build/Script-6B102C7C9773962E1B26CF1B.sh + +make: Nothing to be done for `all'. + +=== BUILD AGGREGATE TARGET userfeedback_proto OF PROJECT chrome WITH CONFIGURATION Debug === +Check dependencies +PhaseScriptExecution "Rule \"genproto\"" ../xcodebuild/chrome.build/Debug/userfeedback_proto.build/Script-E9D8DD8102F83FCBC3F0768B.sh + cd /b/build/slave/mac/build/src/chrome + /bin/sh -c /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/userfeedback_proto.build/Script-E9D8DD8102F83FCBC3F0768B.sh + +make: Nothing to be done for `all'. + +=== BUILD AGGREGATE TARGET browser Support OF PROJECT chrome WITH CONFIGURATION Debug === +Check dependencies + +=== BUILD NATIVE TARGET browser OF PROJECT chrome WITH CONFIGURATION Debug === +Check dependencies +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/appcache_dispatcher_host.o browser/appcache/appcache_dispatcher_host.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/appcache/appcache_dispatcher_host.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/appcache_dispatcher_host.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/appcache_frontend_proxy.o browser/appcache/appcache_frontend_proxy.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/appcache/appcache_frontend_proxy.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/appcache_frontend_proxy.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/dom_ui.o browser/dom_ui/dom_ui.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/dom_ui/dom_ui.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/dom_ui.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/extension_port_container.o browser/automation/extension_port_container.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/automation/extension_port_container.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/extension_port_container.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/browser_about_handler.o browser/browser_about_handler.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/browser_about_handler.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/browser_about_handler.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/browser_main.o browser/browser_main.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/browser_main.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/browser_main.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/devtools_ui.o browser/dom_ui/devtools_ui.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/dom_ui/devtools_ui.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/devtools_ui.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/dispatcher_host.o browser/device_orientation/dispatcher_host.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/device_orientation/dispatcher_host.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/dispatcher_host.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/extension_browser_actions_api.o browser/extensions/extension_browser_actions_api.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/extensions/extension_browser_actions_api.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/extension_browser_actions_api.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/dom_storage_dispatcher_host.o browser/in_process_webkit/dom_storage_dispatcher_host.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/in_process_webkit/dom_storage_dispatcher_host.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/dom_storage_dispatcher_host.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/indexed_db_callbacks.o browser/in_process_webkit/indexed_db_callbacks.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/in_process_webkit/indexed_db_callbacks.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/indexed_db_callbacks.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/extension_menu_manager.o browser/extensions/extension_menu_manager.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/extensions/extension_menu_manager.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/extension_menu_manager.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/browser_process_impl.o browser/browser_process_impl.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/browser_process_impl.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/browser_process_impl.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/instant_loader.o browser/instant/instant_loader.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/instant/instant_loader.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/instant_loader.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/extension_event_router.o browser/extensions/extension_event_router.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/extensions/extension_event_router.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/extension_event_router.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/memory_purger.o browser/memory_purger.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/memory_purger.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/memory_purger.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/extension_message_service.o browser/extensions/extension_message_service.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/extensions/extension_message_service.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/extension_message_service.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/extension_sidebar_api.o browser/extensions/extension_sidebar_api.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/extensions/extension_sidebar_api.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/extension_sidebar_api.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/extension_process_manager.o browser/extensions/extension_process_manager.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/extensions/extension_process_manager.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/extension_process_manager.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/extension_host.o browser/extensions/extension_host.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/extensions/extension_host.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/extension_host.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/histogram_synchronizer.o browser/metrics/histogram_synchronizer.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/metrics/histogram_synchronizer.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/histogram_synchronizer.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/dom_storage_area.o browser/in_process_webkit/dom_storage_area.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/in_process_webkit/dom_storage_area.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/dom_storage_area.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/indexed_db_dispatcher_host.o browser/in_process_webkit/indexed_db_dispatcher_host.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/in_process_webkit/indexed_db_dispatcher_host.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/indexed_db_dispatcher_host.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/extension_page_actions_module.o browser/extensions/extension_page_actions_module.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/extensions/extension_page_actions_module.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/extension_page_actions_module.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/mime_registry_dispatcher.o browser/mime_registry_dispatcher.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/mime_registry_dispatcher.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/mime_registry_dispatcher.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/file_system_dispatcher_host.o browser/file_system/file_system_dispatcher_host.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/file_system/file_system_dispatcher_host.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/file_system_dispatcher_host.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/geolocation_dispatcher_host_old.o browser/geolocation/geolocation_dispatcher_host_old.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/geolocation/geolocation_dispatcher_host_old.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/geolocation_dispatcher_host_old.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/balloon_host.o browser/notifications/balloon_host.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/notifications/balloon_host.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/balloon_host.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/gpu_process_host.o browser/gpu_process_host.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/gpu_process_host.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/gpu_process_host.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/notification_object_proxy.o browser/notifications/notification_object_proxy.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/notifications/notification_object_proxy.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/notification_object_proxy.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/extension_function_dispatcher.o browser/extensions/extension_function_dispatcher.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/extensions/extension_function_dispatcher.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/extension_function_dispatcher.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/geolocation_permission_context.o browser/geolocation/geolocation_permission_context.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/geolocation/geolocation_permission_context.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/geolocation_permission_context.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/plugin_process_host.o browser/plugin_process_host.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/plugin_process_host.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/plugin_process_host.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/desktop_notification_service.o browser/notifications/desktop_notification_service.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/notifications/desktop_notification_service.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/desktop_notification_service.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/ppapi_plugin_process_host.o browser/ppapi_plugin_process_host.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/ppapi_plugin_process_host.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/ppapi_plugin_process_host.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/web_cache_manager.o browser/renderer_host/web_cache_manager.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/renderer_host/web_cache_manager.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/web_cache_manager.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/plugin_service.o browser/plugin_service.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/plugin_service.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/plugin_service.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/nacl_process_host.o browser/nacl_host/nacl_process_host.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/nacl_host/nacl_process_host.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/nacl_process_host.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/metrics_service.o browser/metrics/metrics_service.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/metrics/metrics_service.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/metrics_service.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/profile.o browser/profiles/profile.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/profiles/profile.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/profile.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/render_view_host_delegate.o browser/renderer_host/render_view_host_delegate.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/renderer_host/render_view_host_delegate.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/render_view_host_delegate.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/async_resource_handler.o browser/renderer_host/async_resource_handler.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/renderer_host/async_resource_handler.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/async_resource_handler.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/audio_renderer_host.o browser/renderer_host/audio_renderer_host.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/renderer_host/audio_renderer_host.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/audio_renderer_host.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/task_manager_resource_providers.o browser/task_manager/task_manager_resource_providers.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/task_manager/task_manager_resource_providers.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/task_manager_resource_providers.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/render_widget_host.o browser/renderer_host/render_widget_host.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/renderer_host/render_widget_host.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/render_widget_host.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/render_view_host.o browser/renderer_host/render_view_host.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/renderer_host/render_view_host.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/render_view_host.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/profile_impl.o browser/profiles/profile_impl.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/profiles/profile_impl.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/profile_impl.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/sync_resource_handler.o browser/renderer_host/sync_resource_handler.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/renderer_host/sync_resource_handler.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/sync_resource_handler.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/socket_stream_host.o browser/renderer_host/socket_stream_host.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/renderer_host/socket_stream_host.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/socket_stream_host.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/speech_input_dispatcher_host.o browser/speech/speech_input_dispatcher_host.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/speech/speech_input_dispatcher_host.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/speech_input_dispatcher_host.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/file_utilities_dispatcher_host.o browser/renderer_host/file_utilities_dispatcher_host.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/renderer_host/file_utilities_dispatcher_host.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/file_utilities_dispatcher_host.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/render_widget_host_view_mac.o browser/renderer_host/render_widget_host_view_mac.mm normal i386 objective-c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x objective-c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/renderer_host/render_widget_host_view_mac.mm -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/render_widget_host_view_mac.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/database_dispatcher_host.o browser/renderer_host/database_dispatcher_host.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/renderer_host/database_dispatcher_host.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/database_dispatcher_host.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/browser_render_process_host.o browser/renderer_host/browser_render_process_host.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/renderer_host/browser_render_process_host.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/browser_render_process_host.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/search_provider_install_state_dispatcher_host.o browser/search_engines/search_provider_install_state_dispatcher_host.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/search_engines/search_provider_install_state_dispatcher_host.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/search_provider_install_state_dispatcher_host.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/render_view_context_menu.o browser/tab_contents/render_view_context_menu.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/tab_contents/render_view_context_menu.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/render_view_context_menu.o + +distcc[1299] ERROR: compile /b/build/slave/mac/build/src/chrome/browser/tab_contents/render_view_context_menu.cc on distcc4.golo.chromium.org:3632,lzo,cpp/18 failed +distcc[1299] (dcc_build_somewhere) Warning: remote compilation of '/b/build/slave/mac/build/src/chrome/browser/tab_contents/render_view_context_menu.cc' failed, retrying locally +distcc[1299] Warning: failed to distribute /b/build/slave/mac/build/src/chrome/browser/tab_contents/render_view_context_menu.cc to distcc4.golo.chromium.org:3632,lzo,cpp/18, running locally instead +/b/build/slave/mac/build/src/chrome/browser/tab_contents/render_view_context_menu.cc: In member function 'string16 RenderViewContextMenu::PrintableSelectionText()': +/b/build/slave/mac/build/src/chrome/browser/tab_contents/render_view_context_menu.cc:1436: error: invalid initialization of reference of type 'const std::wstring&' from expression of type 'string16' +../app/l10n_util.h:166: error: in passing argument 1 of 'std::wstring l10n_util::TruncateString(const std::wstring&, size_t)' +distcc[1299] ERROR: compile /b/build/slave/mac/build/src/chrome/browser/tab_contents/render_view_context_menu.cc on localhost failed +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/render_view_host_manager.o browser/tab_contents/render_view_host_manager.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/tab_contents/render_view_host_manager.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/render_view_host_manager.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/blob_dispatcher_host.o browser/renderer_host/blob_dispatcher_host.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/renderer_host/blob_dispatcher_host.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/blob_dispatcher_host.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/tab_contents_view_mac.o browser/tab_contents/tab_contents_view_mac.mm normal i386 objective-c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x objective-c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/tab_contents/tab_contents_view_mac.mm -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/tab_contents_view_mac.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/worker_service.o browser/worker_host/worker_service.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/worker_host/worker_service.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/worker_service.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/pepper_file_message_filter.o browser/renderer_host/pepper_file_message_filter.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/renderer_host/pepper_file_message_filter.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/pepper_file_message_filter.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/worker_process_host.o browser/worker_host/worker_process_host.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/worker_host/worker_process_host.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/worker_process_host.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/socket_stream_dispatcher_host.o browser/renderer_host/socket_stream_dispatcher_host.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/renderer_host/socket_stream_dispatcher_host.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/socket_stream_dispatcher_host.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/tab_contents.o browser/tab_contents/tab_contents.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/tab_contents/tab_contents.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/tab_contents.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/render_view_context_menu_mac.o browser/tab_contents/render_view_context_menu_mac.mm normal i386 objective-c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x objective-c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/tab_contents/render_view_context_menu_mac.mm -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/render_view_context_menu_mac.o + +distcc[1301] ERROR: compile /b/build/slave/mac/build/src/chrome/browser/tab_contents/render_view_context_menu_mac.mm on distcc9.golo.chromium.org:3632,lzo,cpp/18 failed +distcc[1301] (dcc_build_somewhere) Warning: remote compilation of '/b/build/slave/mac/build/src/chrome/browser/tab_contents/render_view_context_menu_mac.mm' failed, retrying locally +distcc[1301] Warning: failed to distribute /b/build/slave/mac/build/src/chrome/browser/tab_contents/render_view_context_menu_mac.mm to distcc9.golo.chromium.org:3632,lzo,cpp/18, running locally instead +/b/build/slave/mac/build/src/chrome/browser/tab_contents/render_view_context_menu_mac.mm: In member function 'virtual void RenderViewContextMenuMac::LookUpInDictionary()': +/b/build/slave/mac/build/src/chrome/browser/tab_contents/render_view_context_menu_mac.mm:78: error: invalid initialization of reference of type 'const std::wstring&' from expression of type 'string16' +../base/sys_string_conversions.h:68: error: in passing argument 1 of 'NSString* base::SysWideToNSString(const std::wstring&)' +distcc[1301] ERROR: compile /b/build/slave/mac/build/src/chrome/browser/tab_contents/render_view_context_menu_mac.mm on localhost failed +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/resource_dispatcher_host.o browser/renderer_host/resource_dispatcher_host.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/renderer_host/resource_dispatcher_host.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/resource_dispatcher_host.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/resource_message_filter.o browser/renderer_host/resource_message_filter.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DCHROME_V8 -DGOOGLE_PROTOBUF_NO_RTTI -DXML_STATIC -DFEATURE_ENABLE_SSL -DFEATURE_ENABLE_VOICEMAIL -DEXPAT_RELATIVE_PATH -DOSX -DPOSIX -DLIBXML_STATIC -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/DerivedSources/Debug -I../third_party/apple -I../third_party/GTM -I../third_party/GTM/AppKit -I../third_party/GTM/Foundation -I../third_party/GTM/DebugUtils -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_resources -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_locale_settings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app/app_strings -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I../third_party/bzip2 -I../third_party/expat/files/lib -I../third_party/libjingle/overrides -I../third_party/libjingle/source -I../third_party/expat/files -I../third_party/libxml/mac/include -I../third_party/libxml/src/include -I../third_party/npapi -I../third_party/npapi/bindings -I../third_party/speex/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/browser/renderer_host/resource_message_filter.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/browser.build/Objects-normal/i386/resource_message_filter.o + + +=== BUILD AGGREGATE TARGET safe_browsing_proto OF PROJECT chrome WITH CONFIGURATION Debug === +Check dependencies +PhaseScriptExecution "Rule \"genproto\"" ../xcodebuild/chrome.build/Debug/safe_browsing_proto.build/Script-04EB001E8DD6896C5E67BFBB.sh + cd /b/build/slave/mac/build/src/chrome + /bin/sh -c /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/safe_browsing_proto.build/Script-04EB001E8DD6896C5E67BFBB.sh + +make: Nothing to be done for `all'. + +=== BUILD NATIVE TARGET renderer OF PROJECT chrome WITH CONFIGURATION Debug === +Check dependencies +Distributed-CompileC ../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/autofill_helper.o renderer/autofill_helper.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DGOOGLE_PROTOBUF_NO_RTTI -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -DCLD_WINDOWS -DCOMPILER_GCC -D__STDC_CONSTANT_MACROS -DNACL_BLOCK_SHIFT=5 -DNACL_BLOCK_SIZE=32 -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I../third_party/cld -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/ffmpeg -I../third_party/ffmpeg/config -I../third_party/ffmpeg/patched-ffmpeg-mt -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/renderer/autofill_helper.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/autofill_helper.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/dom_automation_controller.o renderer/automation/dom_automation_controller.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DGOOGLE_PROTOBUF_NO_RTTI -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -DCLD_WINDOWS -DCOMPILER_GCC -D__STDC_CONSTANT_MACROS -DNACL_BLOCK_SHIFT=5 -DNACL_BLOCK_SIZE=32 -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I../third_party/cld -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/ffmpeg -I../third_party/ffmpeg/config -I../third_party/ffmpeg/patched-ffmpeg-mt -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/renderer/automation/dom_automation_controller.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/dom_automation_controller.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/chrome_app_bindings.o renderer/extensions/chrome_app_bindings.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DGOOGLE_PROTOBUF_NO_RTTI -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -DCLD_WINDOWS -DCOMPILER_GCC -D__STDC_CONSTANT_MACROS -DNACL_BLOCK_SHIFT=5 -DNACL_BLOCK_SIZE=32 -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I../third_party/cld -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/ffmpeg -I../third_party/ffmpeg/config -I../third_party/ffmpeg/patched-ffmpeg-mt -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/renderer/extensions/chrome_app_bindings.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/chrome_app_bindings.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/password_autocomplete_manager.o renderer/password_autocomplete_manager.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DGOOGLE_PROTOBUF_NO_RTTI -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -DCLD_WINDOWS -DCOMPILER_GCC -D__STDC_CONSTANT_MACROS -DNACL_BLOCK_SHIFT=5 -DNACL_BLOCK_SIZE=32 -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I../third_party/cld -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/ffmpeg -I../third_party/ffmpeg/config -I../third_party/ffmpeg/patched-ffmpeg-mt -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/renderer/password_autocomplete_manager.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/password_autocomplete_manager.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/bindings_utils.o renderer/extensions/bindings_utils.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DGOOGLE_PROTOBUF_NO_RTTI -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -DCLD_WINDOWS -DCOMPILER_GCC -D__STDC_CONSTANT_MACROS -DNACL_BLOCK_SHIFT=5 -DNACL_BLOCK_SIZE=32 -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I../third_party/cld -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/ffmpeg -I../third_party/ffmpeg/config -I../third_party/ffmpeg/patched-ffmpeg-mt -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/renderer/extensions/bindings_utils.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/bindings_utils.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/renderer_net_predictor.o renderer/net/renderer_net_predictor.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DGOOGLE_PROTOBUF_NO_RTTI -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -DCLD_WINDOWS -DCOMPILER_GCC -D__STDC_CONSTANT_MACROS -DNACL_BLOCK_SHIFT=5 -DNACL_BLOCK_SIZE=32 -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I../third_party/cld -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/ffmpeg -I../third_party/ffmpeg/config -I../third_party/ffmpeg/patched-ffmpeg-mt -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/renderer/net/renderer_net_predictor.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/renderer_net_predictor.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/audio_renderer_impl.o renderer/media/audio_renderer_impl.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DGOOGLE_PROTOBUF_NO_RTTI -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -DCLD_WINDOWS -DCOMPILER_GCC -D__STDC_CONSTANT_MACROS -DNACL_BLOCK_SHIFT=5 -DNACL_BLOCK_SIZE=32 -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I../third_party/cld -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/ffmpeg -I../third_party/ffmpeg/config -I../third_party/ffmpeg/patched-ffmpeg-mt -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/renderer/media/audio_renderer_impl.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/audio_renderer_impl.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/cookie_message_filter.o renderer/cookie_message_filter.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DGOOGLE_PROTOBUF_NO_RTTI -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -DCLD_WINDOWS -DCOMPILER_GCC -D__STDC_CONSTANT_MACROS -DNACL_BLOCK_SHIFT=5 -DNACL_BLOCK_SIZE=32 -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I../third_party/cld -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/ffmpeg -I../third_party/ffmpeg/config -I../third_party/ffmpeg/patched-ffmpeg-mt -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/renderer/cookie_message_filter.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/cookie_message_filter.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/event_bindings.o renderer/extensions/event_bindings.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DGOOGLE_PROTOBUF_NO_RTTI -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -DCLD_WINDOWS -DCOMPILER_GCC -D__STDC_CONSTANT_MACROS -DNACL_BLOCK_SHIFT=5 -DNACL_BLOCK_SIZE=32 -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I../third_party/cld -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/ffmpeg -I../third_party/ffmpeg/config -I../third_party/ffmpeg/patched-ffmpeg-mt -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/renderer/extensions/event_bindings.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/event_bindings.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/external_popup_menu.o renderer/external_popup_menu.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DGOOGLE_PROTOBUF_NO_RTTI -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -DCLD_WINDOWS -DCOMPILER_GCC -D__STDC_CONSTANT_MACROS -DNACL_BLOCK_SHIFT=5 -DNACL_BLOCK_SIZE=32 -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I../third_party/cld -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/ffmpeg -I../third_party/ffmpeg/config -I../third_party/ffmpeg/patched-ffmpeg-mt -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/renderer/external_popup_menu.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/external_popup_menu.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/renderer_extension_bindings.o renderer/extensions/renderer_extension_bindings.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DGOOGLE_PROTOBUF_NO_RTTI -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -DCLD_WINDOWS -DCOMPILER_GCC -D__STDC_CONSTANT_MACROS -DNACL_BLOCK_SHIFT=5 -DNACL_BLOCK_SIZE=32 -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I../third_party/cld -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/ffmpeg -I../third_party/ffmpeg/config -I../third_party/ffmpeg/patched-ffmpeg-mt -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/renderer/extensions/renderer_extension_bindings.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/renderer_extension_bindings.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/extension_process_bindings.o renderer/extensions/extension_process_bindings.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DGOOGLE_PROTOBUF_NO_RTTI -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -DCLD_WINDOWS -DCOMPILER_GCC -D__STDC_CONSTANT_MACROS -DNACL_BLOCK_SHIFT=5 -DNACL_BLOCK_SIZE=32 -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I../third_party/cld -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/ffmpeg -I../third_party/ffmpeg/config -I../third_party/ffmpeg/patched-ffmpeg-mt -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/renderer/extensions/extension_process_bindings.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/extension_process_bindings.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/device_orientation_dispatcher.o renderer/device_orientation_dispatcher.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DGOOGLE_PROTOBUF_NO_RTTI -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -DCLD_WINDOWS -DCOMPILER_GCC -D__STDC_CONSTANT_MACROS -DNACL_BLOCK_SHIFT=5 -DNACL_BLOCK_SIZE=32 -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I../third_party/cld -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/ffmpeg -I../third_party/ffmpeg/config -I../third_party/ffmpeg/patched-ffmpeg-mt -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/renderer/device_orientation_dispatcher.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/device_orientation_dispatcher.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/audio_message_filter.o renderer/audio_message_filter.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DGOOGLE_PROTOBUF_NO_RTTI -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -DCLD_WINDOWS -DCOMPILER_GCC -D__STDC_CONSTANT_MACROS -DNACL_BLOCK_SHIFT=5 -DNACL_BLOCK_SIZE=32 -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I../third_party/cld -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/ffmpeg -I../third_party/ffmpeg/config -I../third_party/ffmpeg/patched-ffmpeg-mt -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/renderer/audio_message_filter.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/audio_message_filter.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/blocked_plugin.o renderer/blocked_plugin.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DGOOGLE_PROTOBUF_NO_RTTI -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -DCLD_WINDOWS -DCOMPILER_GCC -D__STDC_CONSTANT_MACROS -DNACL_BLOCK_SHIFT=5 -DNACL_BLOCK_SIZE=32 -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I../third_party/cld -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/ffmpeg -I../third_party/ffmpeg/config -I../third_party/ffmpeg/patched-ffmpeg-mt -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/renderer/blocked_plugin.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/blocked_plugin.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/page_load_histograms.o renderer/page_load_histograms.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DGOOGLE_PROTOBUF_NO_RTTI -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -DCLD_WINDOWS -DCOMPILER_GCC -D__STDC_CONSTANT_MACROS -DNACL_BLOCK_SHIFT=5 -DNACL_BLOCK_SIZE=32 -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I../third_party/cld -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/ffmpeg -I../third_party/ffmpeg/config -I../third_party/ffmpeg/patched-ffmpeg-mt -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/renderer/page_load_histograms.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/page_load_histograms.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/devtools_agent.o renderer/devtools_agent.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DGOOGLE_PROTOBUF_NO_RTTI -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -DCLD_WINDOWS -DCOMPILER_GCC -D__STDC_CONSTANT_MACROS -DNACL_BLOCK_SHIFT=5 -DNACL_BLOCK_SIZE=32 -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I../third_party/cld -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/ffmpeg -I../third_party/ffmpeg/config -I../third_party/ffmpeg/patched-ffmpeg-mt -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/renderer/devtools_agent.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/devtools_agent.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/devtools_agent_filter.o renderer/devtools_agent_filter.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DGOOGLE_PROTOBUF_NO_RTTI -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -DCLD_WINDOWS -DCOMPILER_GCC -D__STDC_CONSTANT_MACROS -DNACL_BLOCK_SHIFT=5 -DNACL_BLOCK_SIZE=32 -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I../third_party/cld -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/ffmpeg -I../third_party/ffmpeg/config -I../third_party/ffmpeg/patched-ffmpeg-mt -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/renderer/devtools_agent_filter.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/devtools_agent_filter.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/dom_ui_bindings.o renderer/dom_ui_bindings.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DGOOGLE_PROTOBUF_NO_RTTI -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -DCLD_WINDOWS -DCOMPILER_GCC -D__STDC_CONSTANT_MACROS -DNACL_BLOCK_SHIFT=5 -DNACL_BLOCK_SIZE=32 -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I../third_party/cld -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/ffmpeg -I../third_party/ffmpeg/config -I../third_party/ffmpeg/patched-ffmpeg-mt -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/renderer/dom_ui_bindings.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/dom_ui_bindings.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/devtools_client.o renderer/devtools_client.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DGOOGLE_PROTOBUF_NO_RTTI -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -DCLD_WINDOWS -DCOMPILER_GCC -D__STDC_CONSTANT_MACROS -DNACL_BLOCK_SHIFT=5 -DNACL_BLOCK_SIZE=32 -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I../third_party/cld -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/ffmpeg -I../third_party/ffmpeg/config -I../third_party/ffmpeg/patched-ffmpeg-mt -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/renderer/devtools_client.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/devtools_client.o + +Distributed-CompileC ../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/indexed_db_dispatcher.o renderer/indexed_db_dispatcher.cc normal i386 c++ com.apple.compilers.gcc.4_2 + cd /b/build/slave/mac/build/src/chrome + setenv DISTCC_COMPILER "gcc version 4.2.1 (Apple Inc. build 5659)" + setenv DISTCC_HOSTS "--randomize distcc5.golo.chromium.org:3632,lzo,cpp/18 distcc9.golo.chromium.org:3632,lzo,cpp/18 distcc1.golo.chromium.org:3632,lzo,cpp/18 distcc2.golo.chromium.org:3632,lzo,cpp/18 distcc3.golo.chromium.org:3632,lzo,cpp/18 distcc6.golo.chromium.org:3632,lzo,cpp/18 distcc10.golo.chromium.org:3632,lzo,cpp/18 distcc7.golo.chromium.org:3632,lzo,cpp/18 distcc8.golo.chromium.org:3632,lzo,cpp/18 distcc4.golo.chromium.org:3632,lzo,cpp/18" + setenv DISTCC_SYSTEM "10.6.4 (10F2025, i386)" + setenv INCLUDE_SERVER_DIR /tmp/distcc-pump.C2DZDO + setenv INCLUDE_SERVER_PID 983 + setenv INCLUDE_SERVER_PORT /tmp/distcc-pump.C2DZDO/socket + setenv LANG en_US.US-ASCII + setenv PATH "/usr/bin:/Developer/usr/bin:/usr/bin:/b/build/../depot_tools:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" + /Developer/usr/bin/distcc /Developer/usr/bin/gcc-4.2 -x c++ -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fno-exceptions -fno-rtti -O0 -Werror -Wnewline-eof -DCHROMIUM_BUILD -DENABLE_REMOTING=1 -DENABLE_GPU=1 -DNACL_WINDOWS=0 -DNACL_LINUX=0 -DNACL_OSX=1 -DNACL_TARGET_SUBARCH=32 -DNACL_BUILD_SUBARCH=32 -DGOOGLE_PROTOBUF_NO_RTTI -DHUNSPELL_STATIC -DHUNSPELL_CHROME_CLIENT -DUSE_HUNSPELL -DCLD_WINDOWS -DCOMPILER_GCC -D__STDC_CONSTANT_MACROS -DNACL_BLOCK_SHIFT=5 -DNACL_BLOCK_SIZE=32 -D__STDC_FORMAT_MACROS -DDYNAMIC_ANNOTATIONS_ENABLED=1 -D_DEBUG -isysroot /Developer/SDKs/MacOSX10.5.sdk -fvisibility=hidden -fvisibility-inlines-hidden -fno-threadsafe-statics -mmacosx-version-min=10.5 -gdwarf-2 -Wall -Wendif-labels -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fpch-preprocess -F/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug -F/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks -I/b/build/slave/mac/build/src/chrome/../xcodebuild/Debug/include -I../third_party/icu/public/common -I../third_party/icu/public/i18n -I.. -I../third_party/cld -I../gpu -I../third_party -I../third_party/mesa/MesaLib/include -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/app -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/chrome -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/protoc_out -I../third_party/protobuf -I../third_party/protobuf/src -I../skia/config -I../third_party/skia/include/config -I../third_party/skia/include/core -I../third_party/skia/include/effects -I../skia/ext -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/ffmpeg -I../third_party/ffmpeg/config -I../third_party/ffmpeg/patched-ffmpeg-mt -I../third_party/npapi -I../third_party/npapi/bindings -I/b/build/slave/mac/build/src/chrome/../xcodebuild/DerivedSources/Debug/webkit -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources/i386 -I/b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/DerivedSources -g1 -c /b/build/slave/mac/build/src/chrome/renderer/indexed_db_dispatcher.cc -o /b/build/slave/mac/build/src/chrome/../xcodebuild/chrome.build/Debug/renderer.build/Objects-normal/i386/indexed_db_dispatcher.o + diff --git a/tools/emacs/trybot-windows.txt b/tools/emacs/trybot-windows.txt new file mode 100644 index 0000000000..b3ee27fced --- /dev/null +++ b/tools/emacs/trybot-windows.txt @@ -0,0 +1,72 @@ +This file contains sample trybot output from a Windows trybot run. +It contains a warning and an error but has otherwise been shortened +for length. + +"C:\Program Files (x86)\Xoreax\IncrediBuild\BuildConsole.exe" e:\b\build\slave\win\build\src\build\all.sln "/Cfg=Debug|Win32" + +----------------------------------------------------------------- +IncrediBuild Console 3.60 Internal (build 1156) +Copyright (C) 2001-2010 Xoreax Software Ltd. All rights reserved. +----------------------------------------------------------------- +--------------------Configuration: toolband_proxy_lib - Debug|Win32------------ +Compiling... +toolband_p.c +toolband_proxy.cc +toolband_dlldata.c +Creating library... + +toolband_proxy_lib - 0 error(s), 0 warning(s) +--------------------Configuration: webcore_bindings - Debug|Win32-------------- +Compiling... +CSSGrammar.cpp +e:\b\build\slave\win\build\src\third_party\webkit\javascriptcore\wtf\text\StringImpl.h(90) : warning C4355: 'this' : used in base member initializer list with a gratuitous backslash \ for testing +e:\b\build\slave\win\build\src\third_party\webkit\webcore\dom\ViewportArguments.h(78) : warning C4305: 'initializing' : truncation from '' to 'bool' +e:\b\build\slave\win\build\src\build\Debug\obj\global_intermediate\webkit\CSSGrammar.cpp(1930) : warning C4065: switch statement contains 'default' but no 'case' labels +V8DerivedSources1.cpp +--------------------Configuration: run_testserver - Debug|Win32---------------- +Compiling... +run_testserver.cc +Linking... +Embedding manifest... +Embedding manifest... (rc.exe) +Microsoft (R) Windows (R) Resource Compiler Version 6.1.6723.1 + +Copyright (C) Microsoft Corporation. All rights reserved. + +Embedding manifest... (link.exe) + +run_testserver - 0 error(s), 0 warning(s) +--------------------Configuration: browser - Debug|Win32----------------------- +Compiling... +bookmark_manager_resources_map.cc +theme_resources_map.cc +shared_resources_map.cc +process_singleton_win.cc +e:\b\build\slave\win\build\src\chrome\browser\process_singleton_win.cc(95) : error C2664: 'PathService::Get' : cannot convert parameter 2 from 'std::wstring *' to 'FilePath *' + Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast +gpu_process_host.cc +ntp_background_util.cc + +browser - 6 error(s), 0 warning(s) + +1 build system warning(s): + - PDB instance limit is enabled + +---------------------- Done ---------------------- + + Build: 244 succeeded, 1 failed, 233 up-to-date, 42 skipped + + +We also see weird paths with mixed slashes like this: + +--------------------Configuration: inspector_protocol_sources - Debug|Win32---- +--------------------Configuration: installer_util_nacl_win64 - Debug|x64------- +Compiling... +google_chrome_distribution_dummy.cc +helper.cc +installation_state.cc +self_reg_work_item.cc +..\chrome/installer/util/work_item.h(67) : error C2514: 'FilePath' : class has no constructors + ..\chrome/installer/util/work_item.h(26) : see declaration of 'FilePath' +delete_tree_work_item.cc +delete_reg_key_work_item.cc diff --git a/tools/emacs/trybot.el b/tools/emacs/trybot.el new file mode 100644 index 0000000000..970ffc085d --- /dev/null +++ b/tools/emacs/trybot.el @@ -0,0 +1,176 @@ +; To use this, +; 1) Add to init.el: +; (setq-default chrome-root "/path/to/chrome/src/") +; (add-to-list 'load-path (concat chrome-root "tools/emacs")) +; (require 'trybot) +; 2) Run on trybot output: +; M-x trybot +; +; To hack on this, +; M-x eval-buffer +; M-x trybot-test-win or M-x trybot-test-mac + +(defvar chrome-root nil + "Path to the src/ directory of your Chrome checkout.") + +(defun get-chrome-root () + (or chrome-root default-directory)) + +; Hunt down from the top, case correcting each path component as needed. +; Currently does not keep a cache. Returns nil if no matching file can be +; figured out. +(defun case-corrected-filename (filename) + (save-match-data + (let ((path-components (split-string filename "/")) + (corrected-path (file-name-as-directory (get-chrome-root)))) + (mapc + (function + (lambda (elt) + (if corrected-path + (let ((next-component + (car (member-ignore-case + elt (directory-files corrected-path))))) + (setq corrected-path + (and next-component + (file-name-as-directory + (concat corrected-path next-component)))))))) + path-components) + (if corrected-path + (file-relative-name (directory-file-name corrected-path) + (get-chrome-root)) + nil)))) + +(defun trybot-fixup-win () + "Fix up Windows-specific output." + + ; Fix Windows paths ("d:\...\src\"). + (save-excursion + ; This regexp is subtle and rather hard to read. :~( + ; Use regexp-builder when making changes to it. + (while (re-search-forward + (concat + ; First part: path leader, either of the form + ; e:\...src\ or ..\ + "\\(^.:\\\\.*\\\\src\\\\\\|\\.\\.\\\\\\)" + ; Second part: path, followed by error message marker. + "\\(.*?\\)[(:]") nil t) + (replace-match "" nil t nil 1) + ; Line now looks like: + ; foo\bar\baz.cc error message here + ; We want to fixup backslashes in path into forward slashes, + ; without modifying the error message - by matching up to the + ; first colon above (which will be just beyond the end of the + ; filename) we can use the end of the match as a limit. + (subst-char-in-region (point) (match-end 0) ?\\ ?/) + ; See if we can correct the file name casing. + (let ((filename (buffer-substring (match-beginning 2) (match-end 2)))) + (if (and (not (file-exists-p filename)) + (setq filename (case-corrected-filename filename))) + (replace-match filename t t nil 2)))))) + +(defun trybot-fixup-maclin () + "Fix up Mac/Linux output." + (save-excursion + (while (re-search-forward "^/b/build/[^ ]*/src/" nil t) + (replace-match "")))) + +(defun trybot-fixup (type-hint) + "Parse and fixup the contents of the current buffer as trybot output." + + ; XXX is there something I should so so this stuff doesn't end up on the + ; undo stack? + + ;; Fixup paths. + (cd (get-chrome-root)) + + (goto-char (point-min)) + + ;; Fix up path references. + (cond ((eq type-hint 'win) (trybot-fixup-win)) + ((eq type-hint 'mac) (trybot-fixup-maclin)) + ((eq type-hint 'linux) (trybot-fixup-maclin)) + (t (trybot-fixup-win) (trybot-fixup-maclin))) + + (compilation-mode)) + +(defun trybot-get-new-buffer () + "Get a new clean buffer for trybot output." + ; Use trybot-buffer-name if available; otherwise, "*trybot*". + (let ((buffer-name (if (boundp 'trybot-buffer-name) + trybot-buffer-name + "*trybot*"))) + (let ((old (get-buffer buffer-name))) + (when old (kill-buffer old))) + (get-buffer-create buffer-name))) + +(defun trybot-fetch (type-hint url) + "Fetch a URL and postprocess it as trybot output." + + (let ((on-fetch-completion + (lambda (process state) + (switch-to-buffer (process-buffer process)) + (when (equal state "finished\n") + (trybot-fixup (process-get process 'type-hint))))) + (command (concat "curl -s " (shell-quote-argument url) + ; Pipe it through the output shortener. + (cond + ((eq type-hint 'win) + (concat " | " (get-chrome-root) + "build/sanitize-win-build-log.sh")) + ((eq type-hint 'mac) + (concat " | " (get-chrome-root) + "build/sanitize-mac-build-log.sh")))))) + + ; Start up the subprocess. + (let* ((coding-system-for-read 'utf-8-dos) + (buffer (trybot-get-new-buffer)) + (process (start-process-shell-command "curl" buffer command))) + ; Attach the type hint to the process so we can get it back when + ; the process completes. + (process-put process 'type-hint type-hint) + (set-process-query-on-exit-flag process nil) + (set-process-sentinel process on-fetch-completion)))) + +(defun trybot-test (type-hint filename) + "Load the given test data filename and do the trybot parse on it." + + (let ((trybot-buffer-name "*trybot-test*") + (url (concat "file://" (get-chrome-root) "tools/emacs/" filename))) + (trybot-fetch type-hint url))) + +(defun trybot-test-win () + "Load the Windows test data and do the trybot parse on it." + (interactive) + (trybot-test 'win "trybot-windows.txt")) +(defun trybot-test-mac () + "Load the Mac test data and do the trybot parse on it." + (interactive) + (trybot-test 'mac "trybot-mac.txt")) +(defun trybot-test-linux () + "Load the Linux test data and do the trybot parse on it." + (interactive) + (trybot-test 'linux "trybot-linux.txt")) + +(defun trybot (url) + "Fetch a trybot URL and fix up the output into a compilation-mode buffer." + (interactive "sURL to trybot stdout (leave empty to use clipboard): ") + + ;; Yank URL from clipboard if necessary. + (when (= (length url) 0) + (with-temp-buffer + (clipboard-yank) + (setq url (buffer-string)))) + + ;; Append /text to the URL to get plain text output in the common + ;; case of getting a URL to the HTML build log. + (when (equal "stdio" (car (last (split-string url "/")))) + (setq url (concat url "/text"))) + + (let ((type-hint (cond ((string-match "/[Ww]in" url) 'win) + ((string-match "/mac/" url) 'mac) + ; Match /linux, /linux_view, etc. + ((string-match "/linux" url) 'linux) + (t 'unknown)))) + (trybot-fetch type-hint url))) + +(provide 'trybot) diff --git a/tools/export_tarball/export_tarball.py b/tools/export_tarball/export_tarball.py new file mode 100755 index 0000000000..36a98268ff --- /dev/null +++ b/tools/export_tarball/export_tarball.py @@ -0,0 +1,172 @@ +#!/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 tool creates a tarball with all the sources, but without .svn directories. + +It can also remove files which are not strictly required for build, so that +the resulting tarball can be reasonably small (last time it was ~110 MB). + +Example usage: + +export_tarball.py /foo/bar + +The above will create file /foo/bar.tar.bz2. +""" + +import optparse +import os +import subprocess +import sys +import tarfile + + +NONESSENTIAL_DIRS = ( + 'breakpad/src/processor/testdata', + 'chrome/browser/resources/tracing/tests', + 'chrome/common/extensions/docs', + 'chrome/tools/test/reference_build', + 'courgette/testdata', + 'data', + 'native_client/src/trusted/service_runtime/testdata', + 'src/chrome/test/data', + 'o3d/documentation', + 'o3d/samples', + 'o3d/tests', + 'ppapi/examples', + 'ppapi/native_client/tests', + 'third_party/angle/samples/gles2_book', + 'third_party/findbugs', + 'third_party/hunspell_dictionaries', + 'third_party/hunspell/tests', + 'third_party/lighttpd', + 'third_party/sqlite/src/test', + 'third_party/sqlite/test', + 'third_party/vc_80', + 'third_party/xdg-utils/tests', + 'third_party/yasm/source/patched-yasm/modules/arch/x86/tests', + 'third_party/yasm/source/patched-yasm/modules/dbgfmts/dwarf2/tests', + 'third_party/yasm/source/patched-yasm/modules/objfmts/bin/tests', + 'third_party/yasm/source/patched-yasm/modules/objfmts/coff/tests', + 'third_party/yasm/source/patched-yasm/modules/objfmts/elf/tests', + 'third_party/yasm/source/patched-yasm/modules/objfmts/macho/tests', + 'third_party/yasm/source/patched-yasm/modules/objfmts/rdf/tests', + 'third_party/yasm/source/patched-yasm/modules/objfmts/win32/tests', + 'third_party/yasm/source/patched-yasm/modules/objfmts/win64/tests', + 'third_party/yasm/source/patched-yasm/modules/objfmts/xdf/tests', + 'third_party/WebKit/LayoutTests', + 'third_party/WebKit/Source/JavaScriptCore/tests', + 'third_party/WebKit/Source/WebCore/ChangeLog', + 'third_party/WebKit/Source/WebKit2', + 'third_party/WebKit/Tools/Scripts', + 'tools/gyp/test', + 'v8/test', + 'webkit/data/layout_tests', + 'webkit/tools/test/reference_build', +) + +TESTDIRS = ( + 'chrome/test/data', + 'content/test/data', + 'media/test/data', + 'net/data', +) + +PRUNEDDIRS = ( + 'courgette', +) + + +def GetSourceDirectory(): + return os.path.realpath( + os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src')) + + +# Workaround lack of the exclude parameter in add method in python-2.4. +# TODO(phajdan.jr): remove the workaround when it's not needed on the bot. +class MyTarFile(tarfile.TarFile): + def set_remove_nonessential_files(self, remove): + self.__remove_nonessential_files = remove + + def add(self, name, arcname=None, recursive=True, exclude=None, filter=None): + head, tail = os.path.split(name) + if tail in ('.svn', '.git'): + return + + if self.__remove_nonessential_files: + # WebKit change logs take quite a lot of space. This saves ~10 MB + # in a bzip2-compressed tarball. + if 'ChangeLog' in name: + return + + # Remove contents of non-essential directories, but preserve gyp files, + # so that build/gyp_chromium can work. + for nonessential_dir in (NONESSENTIAL_DIRS + TESTDIRS + PRUNEDDIRS): + dir_path = os.path.join(GetSourceDirectory(), nonessential_dir) + if (name.startswith(dir_path) and + os.path.isfile(name) and + 'gyp' not in name): + return + + tarfile.TarFile.add(self, name, arcname=arcname, recursive=recursive) + + +def main(argv): + parser = optparse.OptionParser() + parser.add_option("--basename") + parser.add_option("--remove-nonessential-files", + dest="remove_nonessential_files", + action="store_true", default=False) + parser.add_option("--test-data", action="store_true") + # TODO(phajdan.jr): Remove --xz option when it's not needed for compatibility. + parser.add_option("--xz", action="store_true") + + options, args = parser.parse_args(argv) + + if len(args) != 1: + print 'You must provide only one argument: output file name' + print '(without .tar.xz extension).' + return 1 + + if not os.path.exists(GetSourceDirectory()): + print 'Cannot find the src directory ' + GetSourceDirectory() + return 1 + + # These two commands are from src/DEPS; please keep them in sync. + if subprocess.call(['python', 'build/util/lastchange.py', '-o', + 'build/util/LASTCHANGE'], cwd=GetSourceDirectory()) != 0: + print 'Could not run build/util/lastchange.py to update LASTCHANGE.' + return 1 + if subprocess.call(['python', 'build/util/lastchange.py', '-s', + 'third_party/WebKit', '-o', + 'build/util/LASTCHANGE.blink'], + cwd=GetSourceDirectory()) != 0: + print 'Could not run build/util/lastchange.py to update LASTCHANGE.blink.' + return 1 + + output_fullname = args[0] + '.tar' + output_basename = options.basename or os.path.basename(args[0]) + + archive = MyTarFile.open(output_fullname, 'w') + archive.set_remove_nonessential_files(options.remove_nonessential_files) + try: + if options.test_data: + for directory in TESTDIRS: + archive.add(os.path.join(GetSourceDirectory(), directory), + arcname=os.path.join(output_basename, directory)) + else: + archive.add(GetSourceDirectory(), arcname=output_basename) + finally: + archive.close() + + if subprocess.call(['xz', '-9', output_fullname]) != 0: + print 'xz -9 failed!' + return 1 + + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) diff --git a/tools/export_tarball/export_v8_tarball.py b/tools/export_tarball/export_v8_tarball.py new file mode 100755 index 0000000000..30767b6287 --- /dev/null +++ b/tools/export_tarball/export_v8_tarball.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python +# Copyright (c) 2011 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Creates a tarball with V8 sources, but without .svn directories. + +This allows easy packaging of V8, synchronized with browser releases. + +Example usage: + +export_v8_tarball.py /foo/bar + +The above will create file /foo/bar/v8-VERSION.tar.bz2 if it doesn't exist. +""" + +import optparse +import os +import re +import subprocess +import sys +import tarfile + +_V8_MAJOR_VERSION_PATTERN = re.compile(r'#define\s+MAJOR_VERSION\s+(.*)') +_V8_MINOR_VERSION_PATTERN = re.compile(r'#define\s+MINOR_VERSION\s+(.*)') +_V8_BUILD_NUMBER_PATTERN = re.compile(r'#define\s+BUILD_NUMBER\s+(.*)') +_V8_PATCH_LEVEL_PATTERN = re.compile(r'#define\s+PATCH_LEVEL\s+(.*)') + +_V8_PATTERNS = [ + _V8_MAJOR_VERSION_PATTERN, + _V8_MINOR_VERSION_PATTERN, + _V8_BUILD_NUMBER_PATTERN, + _V8_PATCH_LEVEL_PATTERN] + + +def GetV8Version(v8_directory): + """ + Returns version number as string based on the string + contents of version.cc file. + """ + with open(os.path.join(v8_directory, 'src', 'version.cc')) as version_file: + version_contents = version_file.read() + + version_components = [] + for pattern in _V8_PATTERNS: + version_components.append(pattern.search(version_contents).group(1).strip()) + + if version_components[len(version_components) - 1] == '0': + version_components.pop() + + return '.'.join(version_components) + + +def GetSourceDirectory(): + return os.path.realpath( + os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src')) + + +def GetV8Directory(): + return os.path.join(GetSourceDirectory(), 'v8') + + +# Workaround lack of the exclude parameter in add method in python-2.4. +# TODO(phajdan.jr): remove the workaround when it's not needed on the bot. +class MyTarFile(tarfile.TarFile): + def add(self, name, arcname=None, recursive=True, exclude=None, filter=None): + head, tail = os.path.split(name) + if tail in ('.svn', '.git'): + return + + tarfile.TarFile.add(self, name, arcname=arcname, recursive=recursive) + + +def main(argv): + parser = optparse.OptionParser() + options, args = parser.parse_args(argv) + + if len(args) != 1: + print 'You must provide only one argument: output file directory' + return 1 + + v8_directory = GetV8Directory() + if not os.path.exists(v8_directory): + print 'Cannot find the v8 directory.' + return 1 + + v8_version = GetV8Version(v8_directory) + print 'Packaging V8 version %s...' % v8_version + output_basename = 'v8-%s' % v8_version + output_fullname = os.path.join(args[0], output_basename + '.tar.bz2') + + if os.path.exists(output_fullname): + print 'Already packaged, exiting.' + return 0 + + subprocess.check_call(["make", "dependencies"], cwd=v8_directory) + + archive = MyTarFile.open(output_fullname, 'w:bz2') + try: + archive.add(v8_directory, arcname=output_basename) + finally: + archive.close() + + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/tools/find_depot_tools.py b/tools/find_depot_tools.py new file mode 100644 index 0000000000..0fa151b162 --- /dev/null +++ b/tools/find_depot_tools.py @@ -0,0 +1,40 @@ +# Copyright (c) 2011 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Small utility function to find depot_tools and add it to the python path. + +Will throw an ImportError exception if depot_tools can't be found since it +imports breakpad. +""" + +import os +import sys + +def add_depot_tools_to_path(): + """Search for depot_tools and add it to sys.path.""" + # First look if depot_tools is already in PYTHONPATH. + for i in sys.path: + if i.rstrip(os.sep).endswith('depot_tools'): + return i + # Then look if depot_tools is in PATH, common case. + for i in os.environ['PATH'].split(os.pathsep): + if i.rstrip(os.sep).endswith('depot_tools'): + sys.path.append(i.rstrip(os.sep)) + return i + # Rare case, it's not even in PATH, look upward up to root. + root_dir = os.path.dirname(os.path.abspath(__file__)) + previous_dir = os.path.abspath(__file__) + while root_dir and root_dir != previous_dir: + if os.path.isfile(os.path.join(root_dir, 'depot_tools', 'breakpad.py')): + i = os.path.join(root_dir, 'depot_tools') + sys.path.append(i) + return i + previous_dir = root_dir + root_dir = os.path.dirname(root_dir) + print >> sys.stderr, 'Failed to find depot_tools' + return None + +add_depot_tools_to_path() + +# pylint: disable=W0611 +import breakpad diff --git a/tools/find_runtime_symbols/OWNERS b/tools/find_runtime_symbols/OWNERS new file mode 100644 index 0000000000..aeea00ec3e --- /dev/null +++ b/tools/find_runtime_symbols/OWNERS @@ -0,0 +1 @@ +dmikurube@chromium.org diff --git a/tools/find_runtime_symbols/PRESUBMIT.py b/tools/find_runtime_symbols/PRESUBMIT.py new file mode 100644 index 0000000000..8d6889ce3f --- /dev/null +++ b/tools/find_runtime_symbols/PRESUBMIT.py @@ -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. + +"""Top-level presubmit script for find_runtime_symbols. + +See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts for +details on the presubmit API built into gcl. +""" + + +def CommonChecks(input_api, output_api): + import sys + def join(*args): + return input_api.os_path.join(input_api.PresubmitLocalPath(), *args) + + output = [] + sys_path_backup = sys.path + try: + sys.path = [ + join('..', 'find_runtime_symbols'), + ] + sys.path + output.extend(input_api.canned_checks.RunPylint(input_api, output_api)) + finally: + sys.path = sys_path_backup + + output.extend( + input_api.canned_checks.RunUnitTestsInDirectory( + input_api, output_api, + input_api.os_path.join(input_api.PresubmitLocalPath(), 'tests'), + whitelist=[r'.+_test\.py$'])) + + if input_api.is_committing: + output.extend(input_api.canned_checks.PanProjectChecks(input_api, + output_api, + owners_check=False)) + return output + + +def CheckChangeOnUpload(input_api, output_api): + return CommonChecks(input_api, output_api) + + +def CheckChangeOnCommit(input_api, output_api): + return CommonChecks(input_api, output_api) diff --git a/tools/find_runtime_symbols/README b/tools/find_runtime_symbols/README new file mode 100644 index 0000000000..ee5c2ac88c --- /dev/null +++ b/tools/find_runtime_symbols/README @@ -0,0 +1,24 @@ +This script maps runtime addresses to symbol names. It is robust over +Address Space Layout Randomization (ASLR) since it uses runtime addresses with +runtime mapping information (/proc/.../maps). +Like 'pprof --symbols' in gperftools . + + +Step 1: Prepare symbol information. + +It is required to collect symbol information before mapping runtime addresses +to symbol names. + +./prepare_symbol_info.py /path/to/maps [/another/path/to/symbol_info_dir] + +The required 'maps' file is /proc/.../maps of the process at runtime. + + +Step 2: Find symbols. + +./find_runtime_symbols.py /path/to/symbol_info_dir < addresses.txt + +'symbol_info_dir' is the result of the Step 1. +The stdin should be a list of hex addresses to map, one per line. + +The results will be printed to stdout like 'pprof --symbols'. diff --git a/tools/find_runtime_symbols/find_runtime_symbols.py b/tools/find_runtime_symbols/find_runtime_symbols.py new file mode 100755 index 0000000000..bed9e800b1 --- /dev/null +++ b/tools/find_runtime_symbols/find_runtime_symbols.py @@ -0,0 +1,207 @@ +#!/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. +"""Find symbols in a binary corresponding to given runtime virtual addresses. + +Note that source file names are treated as symbols in this script while they +are actually not. +""" + +import json +import logging +import os +import sys + +from static_symbols import StaticSymbolsInFile +from proc_maps import ProcMaps + +try: + from collections import OrderedDict # pylint: disable=E0611 +except ImportError: + BASE_PATH = os.path.dirname(os.path.abspath(__file__)) + SIMPLEJSON_PATH = os.path.join(BASE_PATH, os.pardir, os.pardir, 'third_party') + sys.path.insert(0, SIMPLEJSON_PATH) + from simplejson import OrderedDict + + +FUNCTION_SYMBOLS = 0 +SOURCEFILE_SYMBOLS = 1 +TYPEINFO_SYMBOLS = 2 + +_MAPS_FILENAME = 'maps' +_FILES_FILENAME = 'files.json' + + +class RuntimeSymbolsInProcess(object): + def __init__(self): + self._maps = None + self._static_symbols_in_filse = {} + + def find_procedure(self, runtime_address): + for vma in self._maps.iter(ProcMaps.executable): + if vma.begin <= runtime_address < vma.end: + static_symbols = self._static_symbols_in_filse.get(vma.name) + if static_symbols: + return static_symbols.find_procedure_by_runtime_address( + runtime_address, vma) + else: + return None + return None + + def find_sourcefile(self, runtime_address): + for vma in self._maps.iter(ProcMaps.executable): + if vma.begin <= runtime_address < vma.end: + static_symbols = self._static_symbols_in_filse.get(vma.name) + if static_symbols: + return static_symbols.find_sourcefile_by_runtime_address( + runtime_address, vma) + else: + return None + return None + + def find_typeinfo(self, runtime_address): + for vma in self._maps.iter(ProcMaps.constants): + if vma.begin <= runtime_address < vma.end: + static_symbols = self._static_symbols_in_filse.get(vma.name) + if static_symbols: + return static_symbols.find_typeinfo_by_runtime_address( + runtime_address, vma) + else: + return None + return None + + @staticmethod + def load(prepared_data_dir): + symbols_in_process = RuntimeSymbolsInProcess() + + with open(os.path.join(prepared_data_dir, _MAPS_FILENAME), mode='r') as f: + symbols_in_process._maps = ProcMaps.load(f) + with open(os.path.join(prepared_data_dir, _FILES_FILENAME), mode='r') as f: + files = json.load(f) + + # pylint: disable=W0212 + for vma in symbols_in_process._maps.iter(ProcMaps.executable_and_constants): + file_entry = files.get(vma.name) + if not file_entry: + continue + + static_symbols = StaticSymbolsInFile(vma.name) + + nm_entry = file_entry.get('nm') + if nm_entry and nm_entry['format'] == 'bsd': + with open(os.path.join(prepared_data_dir, nm_entry['file']), 'r') as f: + static_symbols.load_nm_bsd(f, nm_entry['mangled']) + + readelf_entry = file_entry.get('readelf-e') + if readelf_entry: + with open(os.path.join(prepared_data_dir, readelf_entry['file']), + 'r') as f: + static_symbols.load_readelf_ew(f) + + decodedline_file_entry = file_entry.get('readelf-debug-decodedline-file') + if decodedline_file_entry: + with open(os.path.join(prepared_data_dir, + decodedline_file_entry['file']), 'r') as f: + static_symbols.load_readelf_debug_decodedline_file(f) + + symbols_in_process._static_symbols_in_filse[vma.name] = static_symbols + + return symbols_in_process + + +def _find_runtime_function_symbols(symbols_in_process, addresses): + result = OrderedDict() + for address in addresses: + if isinstance(address, basestring): + address = int(address, 16) + found = symbols_in_process.find_procedure(address) + if found: + result[address] = found.name + else: + result[address] = '0x%016x' % address + return result + + +def _find_runtime_sourcefile_symbols(symbols_in_process, addresses): + result = OrderedDict() + for address in addresses: + if isinstance(address, basestring): + address = int(address, 16) + found = symbols_in_process.find_sourcefile(address) + if found: + result[address] = found + else: + result[address] = '' + return result + + +def _find_runtime_typeinfo_symbols(symbols_in_process, addresses): + result = OrderedDict() + for address in addresses: + if isinstance(address, basestring): + address = int(address, 16) + if address == 0: + result[address] = 'no typeinfo' + else: + found = symbols_in_process.find_typeinfo(address) + if found: + if found.startswith('typeinfo for '): + result[address] = found[13:] + else: + result[address] = found + else: + result[address] = '0x%016x' % address + return result + + +_INTERNAL_FINDERS = { + FUNCTION_SYMBOLS: _find_runtime_function_symbols, + SOURCEFILE_SYMBOLS: _find_runtime_sourcefile_symbols, + TYPEINFO_SYMBOLS: _find_runtime_typeinfo_symbols, + } + + +def find_runtime_symbols(symbol_type, symbols_in_process, addresses): + return _INTERNAL_FINDERS[symbol_type](symbols_in_process, addresses) + + +def main(): + # FIX: Accept only .pre data + if len(sys.argv) < 2: + sys.stderr.write("""Usage: +%s /path/to/prepared_data_dir/ < addresses.txt +""" % sys.argv[0]) + return 1 + + log = logging.getLogger('find_runtime_symbols') + log.setLevel(logging.WARN) + handler = logging.StreamHandler() + handler.setLevel(logging.WARN) + formatter = logging.Formatter('%(message)s') + handler.setFormatter(formatter) + log.addHandler(handler) + + prepared_data_dir = sys.argv[1] + if not os.path.exists(prepared_data_dir): + log.warn("Nothing found: %s" % prepared_data_dir) + return 1 + if not os.path.isdir(prepared_data_dir): + log.warn("Not a directory: %s" % prepared_data_dir) + return 1 + + symbols_in_process = RuntimeSymbolsInProcess.load(prepared_data_dir) + symbols_dict = find_runtime_symbols(FUNCTION_SYMBOLS, + symbols_in_process, + sys.stdin) + for address, symbol in symbols_dict: + if symbol: + print '%016x %s' % (address, symbol) + else: + print '%016x' % address + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/find_runtime_symbols/prepare_symbol_info.py b/tools/find_runtime_symbols/prepare_symbol_info.py new file mode 100755 index 0000000000..d5503881a1 --- /dev/null +++ b/tools/find_runtime_symbols/prepare_symbol_info.py @@ -0,0 +1,226 @@ +#!/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. + +import hashlib +import json +import logging +import os +import re +import shutil +import subprocess +import sys +import tempfile + +from proc_maps import ProcMaps + + +BASE_PATH = os.path.dirname(os.path.abspath(__file__)) +REDUCE_DEBUGLINE_PATH = os.path.join(BASE_PATH, 'reduce_debugline.py') +LOGGER = logging.getLogger('prepare_symbol_info') + + +def _dump_command_result(command, output_dir_path, basename, suffix): + handle_out, filename_out = tempfile.mkstemp( + suffix=suffix, prefix=basename + '.', dir=output_dir_path) + handle_err, filename_err = tempfile.mkstemp( + suffix=suffix + '.err', prefix=basename + '.', dir=output_dir_path) + error = False + try: + subprocess.check_call( + command, stdout=handle_out, stderr=handle_err, shell=True) + except (OSError, subprocess.CalledProcessError): + error = True + finally: + os.close(handle_err) + os.close(handle_out) + + if os.path.exists(filename_err): + if LOGGER.getEffectiveLevel() <= logging.DEBUG: + with open(filename_err, 'r') as f: + for line in f: + LOGGER.debug(line.rstrip()) + os.remove(filename_err) + + if os.path.exists(filename_out) and ( + os.path.getsize(filename_out) == 0 or error): + os.remove(filename_out) + return None + + if not os.path.exists(filename_out): + return None + + return filename_out + + +def prepare_symbol_info(maps_path, + output_dir_path=None, + alternative_dirs=None, + use_tempdir=False, + use_source_file_name=False): + """Prepares (collects) symbol information files for find_runtime_symbols. + + 1) If |output_dir_path| is specified, it tries collecting symbol information + files in the given directory |output_dir_path|. + 1-a) If |output_dir_path| doesn't exist, create the directory and use it. + 1-b) If |output_dir_path| is an empty directory, use it. + 1-c) If |output_dir_path| is a directory which has 'files.json', assumes that + files are already collected and just ignores it. + 1-d) Otherwise, depends on |use_tempdir|. + + 2) If |output_dir_path| is not specified, it tries to create a new directory + depending on 'maps_path'. + + If it cannot create a new directory, creates a temporary directory depending + on |use_tempdir|. If |use_tempdir| is False, returns None. + + Args: + maps_path: A path to a file which contains '/proc//maps'. + alternative_dirs: A mapping from a directory '/path/on/target' where the + target process runs to a directory '/path/on/host' where the script + reads the binary. Considered to be used for Android binaries. + output_dir_path: A path to a directory where files are prepared. + use_tempdir: If True, it creates a temporary directory when it cannot + create a new directory. + use_source_file_name: If True, it adds reduced result of 'readelf -wL' + to find source file names. + + Returns: + A pair of a path to the prepared directory and a boolean representing + if it created a temporary directory or not. + """ + alternative_dirs = alternative_dirs or {} + if not output_dir_path: + matched = re.match('^(.*)\.maps$', os.path.basename(maps_path)) + if matched: + output_dir_path = matched.group(1) + '.pre' + if not output_dir_path: + matched = re.match('^/proc/(.*)/maps$', os.path.realpath(maps_path)) + if matched: + output_dir_path = matched.group(1) + '.pre' + if not output_dir_path: + output_dir_path = os.path.basename(maps_path) + '.pre' + # TODO(dmikurube): Find another candidate for output_dir_path. + + used_tempdir = False + LOGGER.info('Data for profiling will be collected in "%s".' % output_dir_path) + if os.path.exists(output_dir_path): + if os.path.isdir(output_dir_path) and not os.listdir(output_dir_path): + LOGGER.warn('Using an empty existing directory "%s".' % output_dir_path) + else: + LOGGER.warn('A file or a directory exists at "%s".' % output_dir_path) + if os.path.exists(os.path.join(output_dir_path, 'files.json')): + LOGGER.warn('Using the existing directory "%s".' % output_dir_path) + return output_dir_path, used_tempdir + else: + if use_tempdir: + output_dir_path = tempfile.mkdtemp() + used_tempdir = True + LOGGER.warn('Using a temporary directory "%s".' % output_dir_path) + else: + LOGGER.warn('The directory "%s" is not available.' % output_dir_path) + return None, used_tempdir + else: + LOGGER.info('Creating a new directory "%s".' % output_dir_path) + try: + os.mkdir(output_dir_path) + except OSError: + LOGGER.warn('A directory "%s" cannot be created.' % output_dir_path) + if use_tempdir: + output_dir_path = tempfile.mkdtemp() + used_tempdir = True + LOGGER.warn('Using a temporary directory "%s".' % output_dir_path) + else: + LOGGER.warn('The directory "%s" is not available.' % output_dir_path) + return None, used_tempdir + + shutil.copyfile(maps_path, os.path.join(output_dir_path, 'maps')) + + with open(maps_path, mode='r') as f: + maps = ProcMaps.load(f) + + LOGGER.debug('Listing up symbols.') + files = {} + for entry in maps.iter(ProcMaps.executable): + LOGGER.debug(' %016x-%016x +%06x %s' % ( + entry.begin, entry.end, entry.offset, entry.name)) + binary_path = entry.name + for target_path, host_path in alternative_dirs.iteritems(): + if entry.name.startswith(target_path): + binary_path = entry.name.replace(target_path, host_path, 1) + nm_filename = _dump_command_result( + 'nm -n --format bsd %s | c++filt' % binary_path, + output_dir_path, os.path.basename(binary_path), '.nm') + if not nm_filename: + continue + readelf_e_filename = _dump_command_result( + 'readelf -eW %s' % binary_path, + output_dir_path, os.path.basename(binary_path), '.readelf-e') + if not readelf_e_filename: + continue + readelf_debug_decodedline_file = None + if use_source_file_name: + readelf_debug_decodedline_file = _dump_command_result( + 'readelf -wL %s | %s' % (binary_path, REDUCE_DEBUGLINE_PATH), + output_dir_path, os.path.basename(binary_path), '.readelf-wL') + + files[entry.name] = {} + files[entry.name]['nm'] = { + 'file': os.path.basename(nm_filename), + 'format': 'bsd', + 'mangled': False} + files[entry.name]['readelf-e'] = { + 'file': os.path.basename(readelf_e_filename)} + if readelf_debug_decodedline_file: + files[entry.name]['readelf-debug-decodedline-file'] = { + 'file': os.path.basename(readelf_debug_decodedline_file)} + + files[entry.name]['size'] = os.stat(binary_path).st_size + + with open(binary_path, 'rb') as entry_f: + md5 = hashlib.md5() + sha1 = hashlib.sha1() + chunk = entry_f.read(1024 * 1024) + while chunk: + md5.update(chunk) + sha1.update(chunk) + chunk = entry_f.read(1024 * 1024) + files[entry.name]['sha1'] = sha1.hexdigest() + files[entry.name]['md5'] = md5.hexdigest() + + with open(os.path.join(output_dir_path, 'files.json'), 'w') as f: + json.dump(files, f, indent=2, sort_keys=True) + + LOGGER.info('Collected symbol information at "%s".' % output_dir_path) + return output_dir_path, used_tempdir + + +def main(): + if not sys.platform.startswith('linux'): + sys.stderr.write('This script work only on Linux.') + return 1 + + LOGGER.setLevel(logging.DEBUG) + handler = logging.StreamHandler() + handler.setLevel(logging.INFO) + formatter = logging.Formatter('%(message)s') + handler.setFormatter(formatter) + LOGGER.addHandler(handler) + + # TODO(dmikurube): Specify |alternative_dirs| from command line. + if len(sys.argv) < 2: + sys.stderr.write("""Usage: +%s /path/to/maps [/path/to/output_data_dir/] +""" % sys.argv[0]) + return 1 + elif len(sys.argv) == 2: + result, _ = prepare_symbol_info(sys.argv[1]) + else: + result, _ = prepare_symbol_info(sys.argv[1], sys.argv[2]) + + return not result + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/find_runtime_symbols/proc_maps.py b/tools/find_runtime_symbols/proc_maps.py new file mode 100644 index 0000000000..2d917b3212 --- /dev/null +++ b/tools/find_runtime_symbols/proc_maps.py @@ -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. + +import re + + +_MAPS_PATTERN = re.compile( + r'^([a-f0-9]+)-([a-f0-9]+)\s+(.)(.)(.)(.)\s+([a-f0-9]+)\s+(\S+):(\S+)\s+' + r'(\d+)\s*(.*)$', re.IGNORECASE) + + +class ProcMapsEntry(object): + """A class representing one line in /proc/.../maps.""" + + def __init__( + self, begin, end, readable, writable, executable, private, offset, + major, minor, inode, name): + self.begin = begin + self.end = end + self.readable = readable + self.writable = writable + self.executable = executable + self.private = private + self.offset = offset + self.major = major + self.minor = minor + self.inode = inode + self.name = name + + def as_dict(self): + return { + 'begin': self.begin, + 'end': self.end, + 'readable': self.readable, + 'writable': self.writable, + 'executable': self.executable, + 'private': self.private, + 'offset': self.offset, + 'major': self.major, + 'minor': self.minor, + 'inode': self.inode, + 'name': self.name, + } + + +class ProcMaps(object): + """A class representing contents in /proc/.../maps.""" + + def __init__(self): + self._sorted_indexes = [] + self._dictionary = {} + self._sorted = True + + def iter(self, condition): + if not self._sorted: + self._sorted_indexes.sort() + self._sorted = True + for index in self._sorted_indexes: + if not condition or condition(self._dictionary[index]): + yield self._dictionary[index] + + def __iter__(self): + if not self._sorted: + self._sorted_indexes.sort() + self._sorted = True + for index in self._sorted_indexes: + yield self._dictionary[index] + + @staticmethod + def load(f): + table = ProcMaps() + for line in f: + table.append_line(line) + return table + + def append_line(self, line): + entry = self.parse_line(line) + if entry: + self._append_entry(entry) + + @staticmethod + def parse_line(line): + matched = _MAPS_PATTERN.match(line) + if matched: + return ProcMapsEntry( # pylint: disable=W0212 + int(matched.group(1), 16), # begin + int(matched.group(2), 16), # end + matched.group(3), # readable + matched.group(4), # writable + matched.group(5), # executable + matched.group(6), # private + int(matched.group(7), 16), # offset + matched.group(8), # major + matched.group(9), # minor + int(matched.group(10), 10), # inode + matched.group(11) # name + ) + else: + return None + + @staticmethod + def constants(entry): + return (entry.writable == '-' and entry.executable == '-' and re.match( + '\S+(\.(so|dll|dylib|bundle)|chrome)((\.\d+)+\w*(\.\d+){0,3})?', + entry.name)) + + @staticmethod + def executable(entry): + return (entry.executable == 'x' and re.match( + '\S+(\.(so|dll|dylib|bundle)|chrome)((\.\d+)+\w*(\.\d+){0,3})?', + entry.name)) + + @staticmethod + def executable_and_constants(entry): + return (((entry.writable == '-' and entry.executable == '-') or + entry.executable == 'x') and re.match( + '\S+(\.(so|dll|dylib|bundle)|chrome)((\.\d+)+\w*(\.\d+){0,3})?', + entry.name)) + + def _append_entry(self, entry): + if self._sorted_indexes and self._sorted_indexes[-1] > entry.begin: + self._sorted = False + self._sorted_indexes.append(entry.begin) + self._dictionary[entry.begin] = entry diff --git a/tools/find_runtime_symbols/reduce_debugline.py b/tools/find_runtime_symbols/reduce_debugline.py new file mode 100755 index 0000000000..75c8c8578d --- /dev/null +++ b/tools/find_runtime_symbols/reduce_debugline.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +# 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. +"""Reduces result of 'readelf -wL' to just a list of starting addresses. + +It lists up all addresses where the corresponding source files change. The +list is sorted in ascending order. See tests/reduce_debugline_test.py for +examples. + +This script assumes that the result of 'readelf -wL' ends with an empty line. + +Note: the option '-wL' has the same meaning with '--debug-dump=decodedline'. +""" + +import re +import sys + + +_FILENAME_PATTERN = re.compile('(CU: |)(.+)\:') + + +def reduce_decoded_debugline(input_file): + filename = '' + starting_dict = {} + started = False + + for line in input_file: + line = line.strip() + unpacked = line.split(None, 2) + + if len(unpacked) == 3 and unpacked[2].startswith('0x'): + if not started and filename: + started = True + starting_dict[int(unpacked[2], 16)] = filename + else: + started = False + if line.endswith(':'): + matched = _FILENAME_PATTERN.match(line) + if matched: + filename = matched.group(2) + + starting_list = [] + prev_filename = '' + for address in sorted(starting_dict): + curr_filename = starting_dict[address] + if prev_filename != curr_filename: + starting_list.append((address, starting_dict[address])) + prev_filename = curr_filename + return starting_list + + +def main(): + if len(sys.argv) != 1: + print >> sys.stderr, 'Unsupported arguments' + return 1 + + starting_list = reduce_decoded_debugline(sys.stdin) + bits64 = starting_list[-1][0] > 0xffffffff + for address, filename in starting_list: + if bits64: + print '%016x %s' % (address, filename) + else: + print '%08x %s' % (address, filename) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/find_runtime_symbols/static_symbols.py b/tools/find_runtime_symbols/static_symbols.py new file mode 100644 index 0000000000..cd57bacd99 --- /dev/null +++ b/tools/find_runtime_symbols/static_symbols.py @@ -0,0 +1,277 @@ +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import bisect +import re + + +_ARGUMENT_TYPE_PATTERN = re.compile('\([^()]*\)(\s*const)?') +_TEMPLATE_ARGUMENT_PATTERN = re.compile('<[^<>]*>') +_LEADING_TYPE_PATTERN = re.compile('^.*\s+(\w+::)') +_READELF_SECTION_HEADER_PATTER = re.compile( + '^\s*\[\s*(Nr|\d+)\]\s+(|\S+)\s+([A-Z_]+)\s+([0-9a-f]+)\s+' + '([0-9a-f]+)\s+([0-9a-f]+)\s+([0-9]+)\s+([WAXMSILGxOop]*)\s+' + '([0-9]+)\s+([0-9]+)\s+([0-9]+)') + + +class ParsingException(Exception): + def __str__(self): + return repr(self.args[0]) + + +class AddressMapping(object): + def __init__(self): + self._symbol_map = {} + + def append(self, start, entry): + self._symbol_map[start] = entry + + def find(self, address): + return self._symbol_map.get(address) + + +class RangeAddressMapping(AddressMapping): + def __init__(self): + super(RangeAddressMapping, self).__init__() + self._sorted_start_list = [] + self._is_sorted = True + + def append(self, start, entry): + if self._sorted_start_list: + if self._sorted_start_list[-1] > start: + self._is_sorted = False + elif self._sorted_start_list[-1] == start: + return + self._sorted_start_list.append(start) + self._symbol_map[start] = entry + + def find(self, address): + if not self._sorted_start_list: + return None + if not self._is_sorted: + self._sorted_start_list.sort() + self._is_sorted = True + found_index = bisect.bisect_left(self._sorted_start_list, address) + found_start_address = self._sorted_start_list[found_index - 1] + return self._symbol_map[found_start_address] + + +class Procedure(object): + """A class for a procedure symbol and an address range for the symbol.""" + + def __init__(self, start, end, name): + self.start = start + self.end = end + self.name = name + + def __eq__(self, other): + return (self.start == other.start and + self.end == other.end and + self.name == other.name) + + def __ne__(self, other): + return not self.__eq__(other) + + def __str__(self): + return '%x-%x: %s' % (self.start, self.end, self.name) + + +class ElfSection(object): + """A class for an elf section header.""" + + def __init__( + self, number, name, stype, address, offset, size, es, flg, lk, inf, al): + self.number = number + self.name = name + self.stype = stype + self.address = address + self.offset = offset + self.size = size + self.es = es + self.flg = flg + self.lk = lk + self.inf = inf + self.al = al + + def __eq__(self, other): + return (self.number == other.number and + self.name == other.name and + self.stype == other.stype and + self.address == other.address and + self.offset == other.offset and + self.size == other.size and + self.es == other.es and + self.flg == other.flg and + self.lk == other.lk and + self.inf == other.inf and + self.al == other.al) + + def __ne__(self, other): + return not self.__eq__(other) + + def __str__(self): + return '%x+%x(%x) %s' % (self.address, self.size, self.offset, self.name) + + +class StaticSymbolsInFile(object): + """Represents static symbol information in a binary file.""" + + def __init__(self, my_name): + self.my_name = my_name + self._elf_sections = [] + self._procedures = RangeAddressMapping() + self._sourcefiles = RangeAddressMapping() + self._typeinfos = AddressMapping() + + def _append_elf_section(self, elf_section): + self._elf_sections.append(elf_section) + + def _append_procedure(self, start, procedure): + self._procedures.append(start, procedure) + + def _append_sourcefile(self, start, sourcefile): + self._sourcefiles.append(start, sourcefile) + + def _append_typeinfo(self, start, typeinfo): + self._typeinfos.append(start, typeinfo) + + def _find_symbol_by_runtime_address(self, address, vma, target): + if not (vma.begin <= address < vma.end): + return None + + if vma.name != self.my_name: + return None + + file_offset = address - (vma.begin - vma.offset) + elf_address = None + for section in self._elf_sections: + if section.offset <= file_offset < (section.offset + section.size): + elf_address = section.address + file_offset - section.offset + if not elf_address: + return None + + return target.find(elf_address) + + def find_procedure_by_runtime_address(self, address, vma): + return self._find_symbol_by_runtime_address(address, vma, self._procedures) + + def find_sourcefile_by_runtime_address(self, address, vma): + return self._find_symbol_by_runtime_address(address, vma, self._sourcefiles) + + def find_typeinfo_by_runtime_address(self, address, vma): + return self._find_symbol_by_runtime_address(address, vma, self._typeinfos) + + def load_readelf_ew(self, f): + found_header = False + for line in f: + if line.rstrip() == 'Section Headers:': + found_header = True + break + if not found_header: + return None + + for line in f: + line = line.rstrip() + matched = _READELF_SECTION_HEADER_PATTER.match(line) + if matched: + self._append_elf_section(ElfSection( + int(matched.group(1), 10), # number + matched.group(2), # name + matched.group(3), # stype + int(matched.group(4), 16), # address + int(matched.group(5), 16), # offset + int(matched.group(6), 16), # size + matched.group(7), # es + matched.group(8), # flg + matched.group(9), # lk + matched.group(10), # inf + matched.group(11) # al + )) + else: + if line in ('Key to Flags:', 'Program Headers:'): + break + + def load_readelf_debug_decodedline_file(self, input_file): + for line in input_file: + splitted = line.rstrip().split(None, 2) + self._append_sourcefile(int(splitted[0], 16), splitted[1]) + + @staticmethod + def _parse_nm_bsd_line(line): + if line[8] == ' ': + return line[0:8], line[9], line[11:] + elif line[16] == ' ': + return line[0:16], line[17], line[19:] + raise ParsingException('Invalid nm output.') + + @staticmethod + def _get_short_function_name(function): + while True: + function, number = _ARGUMENT_TYPE_PATTERN.subn('', function) + if not number: + break + while True: + function, number = _TEMPLATE_ARGUMENT_PATTERN.subn('', function) + if not number: + break + return _LEADING_TYPE_PATTERN.sub('\g<1>', function) + + def load_nm_bsd(self, f, mangled=False): + last_start = 0 + routine = '' + + for line in f: + line = line.rstrip() + sym_value, sym_type, sym_name = self._parse_nm_bsd_line(line) + + if sym_value[0] == ' ': + continue + + start_val = int(sym_value, 16) + + if (sym_type in ('r', 'R', 'D', 'U', 'd', 'V') and + (not mangled and sym_name.startswith('typeinfo'))): + self._append_typeinfo(start_val, sym_name) + + # It's possible for two symbols to share the same address, if + # one is a zero-length variable (like __start_google_malloc) or + # one symbol is a weak alias to another (like __libc_malloc). + # In such cases, we want to ignore all values except for the + # actual symbol, which in nm-speak has type "T". The logic + # below does this, though it's a bit tricky: what happens when + # we have a series of lines with the same address, is the first + # one gets queued up to be processed. However, it won't + # *actually* be processed until later, when we read a line with + # a different address. That means that as long as we're reading + # lines with the same address, we have a chance to replace that + # item in the queue, which we do whenever we see a 'T' entry -- + # that is, a line with type 'T'. If we never see a 'T' entry, + # we'll just go ahead and process the first entry (which never + # got touched in the queue), and ignore the others. + if start_val == last_start and (sym_type == 't' or sym_type == 'T'): + # We are the 'T' symbol at this address, replace previous symbol. + routine = sym_name + continue + elif start_val == last_start: + # We're not the 'T' symbol at this address, so ignore us. + continue + + # Tag this routine with the starting address in case the image + # has multiple occurrences of this routine. We use a syntax + # that resembles template paramters that are automatically + # stripped out by ShortFunctionName() + sym_name += "<%016x>" % start_val + + if not mangled: + routine = self._get_short_function_name(routine) + self._append_procedure( + last_start, Procedure(last_start, start_val, routine)) + + last_start = start_val + routine = sym_name + + if not mangled: + routine = self._get_short_function_name(routine) + self._append_procedure( + last_start, Procedure(last_start, last_start, routine)) diff --git a/tools/find_runtime_symbols/tests/proc_maps_test.py b/tools/find_runtime_symbols/tests/proc_maps_test.py new file mode 100755 index 0000000000..502f252c55 --- /dev/null +++ b/tools/find_runtime_symbols/tests/proc_maps_test.py @@ -0,0 +1,110 @@ +#!/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. + +import cStringIO +import logging +import os +import sys +import unittest + +ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.insert(0, ROOT_DIR) + +from proc_maps import ProcMaps + + +class ProcMapsTest(unittest.TestCase): + _TEST_PROCMAPS = '\n'.join([ + '00000000-00001000 r--p 00000000 fc:00 0', + '0080b000-0080c000 r-xp 0020b000 fc:00 2231329' + ' /usr/bin/some', + '0080c000-0080f000 ---p 0020c000 fc:00 2231329' + ' /usr/bin/some', + '0100a000-0100c000 r-xp 0120a000 fc:00 22381' + ' /usr/bin/chrome', + '0100c000-0100f000 ---p 0120c000 fc:00 22381' + ' /usr/bin/chrome', + '0237d000-02a9b000 rw-p 00000000 00:00 0' + ' [heap]', + '7fb920e6d000-7fb920e85000 r-xp 00000000 fc:00 263482' + ' /lib/x86_64-linux-gnu/libpthread-2.15.so', + '7fb920e85000-7fb921084000 ---p 00018000 fc:00 263482' + ' /lib/x86_64-linux-gnu/libpthread-2.15.so', + '7fb9225f4000-7fb922654000 rw-s 00000000 00:04 19660808' + ' /SYSV00000000 (deleted)', + 'ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0' + ' [vsyscall]', + ]) + + _EXPECTED = [ + (0x0, 0x1000, 'r', '-', '-', 'p', 0x0, 'fc', '00', 0, ''), + (0x80b000, 0x80c000, 'r', '-', 'x', 'p', 0x20b000, + 'fc', '00', 2231329, '/usr/bin/some'), + (0x80c000, 0x80f000, '-', '-', '-', 'p', 0x20c000, + 'fc', '00', 2231329, '/usr/bin/some'), + (0x100a000, 0x100c000, 'r', '-', 'x', 'p', 0x120a000, + 'fc', '00', 22381, '/usr/bin/chrome'), + (0x100c000, 0x100f000, '-', '-', '-', 'p', 0x120c000, + 'fc', '00', 22381, '/usr/bin/chrome'), + (0x237d000, 0x2a9b000, 'r', 'w', '-', 'p', 0x0, + '00', '00', 0, '[heap]'), + (0x7fb920e6d000, 0x7fb920e85000, 'r', '-', 'x', 'p', 0x0, + 'fc', '00', 263482, '/lib/x86_64-linux-gnu/libpthread-2.15.so'), + (0x7fb920e85000, 0x7fb921084000, '-', '-', '-', 'p', 0x18000, + 'fc', '00', 263482, '/lib/x86_64-linux-gnu/libpthread-2.15.so'), + (0x7fb9225f4000, 0x7fb922654000, 'r', 'w', '-', 's', 0x0, + '00', '04', 19660808, '/SYSV00000000 (deleted)'), + (0xffffffffff600000, 0xffffffffff601000, 'r', '-', 'x', 'p', 0x0, + '00', '00', 0, '[vsyscall]'), + ] + + @staticmethod + def _expected_as_dict(index): + return { + 'begin': ProcMapsTest._EXPECTED[index][0], + 'end': ProcMapsTest._EXPECTED[index][1], + 'readable': ProcMapsTest._EXPECTED[index][2], + 'writable': ProcMapsTest._EXPECTED[index][3], + 'executable': ProcMapsTest._EXPECTED[index][4], + 'private': ProcMapsTest._EXPECTED[index][5], + 'offset': ProcMapsTest._EXPECTED[index][6], + 'major': ProcMapsTest._EXPECTED[index][7], + 'minor': ProcMapsTest._EXPECTED[index][8], + 'inode': ProcMapsTest._EXPECTED[index][9], + 'name': ProcMapsTest._EXPECTED[index][10], + } + + def test_load(self): + maps = ProcMaps.load(cStringIO.StringIO(self._TEST_PROCMAPS)) + for index, entry in enumerate(maps): + self.assertEqual(entry.as_dict(), self._expected_as_dict(index)) + + def test_constants(self): + maps = ProcMaps.load(cStringIO.StringIO(self._TEST_PROCMAPS)) + selected = [4, 7] + for index, entry in enumerate(maps.iter(ProcMaps.constants)): + self.assertEqual(entry.as_dict(), + self._expected_as_dict(selected[index])) + + def test_executable(self): + maps = ProcMaps.load(cStringIO.StringIO(self._TEST_PROCMAPS)) + selected = [3, 6] + for index, entry in enumerate(maps.iter(ProcMaps.executable)): + self.assertEqual(entry.as_dict(), + self._expected_as_dict(selected[index])) + + def test_executable_and_constants(self): + maps = ProcMaps.load(cStringIO.StringIO(self._TEST_PROCMAPS)) + selected = [3, 4, 6, 7] + for index, entry in enumerate(maps.iter(ProcMaps.executable_and_constants)): + self.assertEqual(entry.as_dict(), + self._expected_as_dict(selected[index])) + + +if __name__ == '__main__': + logging.basicConfig( + level=logging.DEBUG if '-v' in sys.argv else logging.ERROR, + format='%(levelname)5s %(filename)15s(%(lineno)3d): %(message)s') + unittest.main() diff --git a/tools/find_runtime_symbols/tests/reduce_debugline_test.py b/tools/find_runtime_symbols/tests/reduce_debugline_test.py new file mode 100755 index 0000000000..1e3a21afc2 --- /dev/null +++ b/tools/find_runtime_symbols/tests/reduce_debugline_test.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python +# 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. + +import cStringIO +import logging +import os +import sys +import textwrap +import unittest + +ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.insert(0, ROOT_DIR) + +import reduce_debugline + + +class ReduceDebuglineTest(unittest.TestCase): + _DECODED_DEBUGLINE = textwrap.dedent("""\ + Decoded dump of debug contents of section .debug_line: + + CU: ../../chrome/service/service_main.cc: + File name Line number Starting address + service_main.cc 21 0xa41210 + + service_main.cc 24 0xa4141f + service_main.cc 30 0xa4142b + service_main.cc 31 0xa4143e + + ../../base/message_loop.h: + message_loop.h 550 0xa41300 + + message_loop.h 551 0xa41310 + + ../../base/logging.h: + logging.h 246 0xa41710 + + logging.h 247 0xa41726 + + ../../base/logging.h: + logging.h 846 0xa3fd90 + + logging.h 846 0xa3fda0 + + """) + + _EXPECTED_REDUCED_DEBUGLINE = [ + (0xa3fd90, '../../base/logging.h'), + (0xa41210, '../../chrome/service/service_main.cc'), + (0xa41300, '../../base/message_loop.h'), + (0xa4141f, '../../chrome/service/service_main.cc'), + (0xa41710, '../../base/logging.h'), + ] + + def test(self): + ranges_dict = reduce_debugline.reduce_decoded_debugline( + cStringIO.StringIO(self._DECODED_DEBUGLINE)) + self.assertEqual(self._EXPECTED_REDUCED_DEBUGLINE, ranges_dict) + + +if __name__ == '__main__': + logging.basicConfig( + level=logging.DEBUG if '-v' in sys.argv else logging.ERROR, + format='%(levelname)5s %(filename)15s(%(lineno)3d): %(message)s') + unittest.main() diff --git a/tools/flakiness/find_flakiness.py b/tools/flakiness/find_flakiness.py new file mode 100755 index 0000000000..21629e4a7a --- /dev/null +++ b/tools/flakiness/find_flakiness.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python +# Copyright (c) 2011 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Contains two functions that run different test cases and the same test +case in parallel repeatedly to identify flaky tests. +""" + + +import os +import re +import subprocess +import time + + +# Defaults for FindShardingFlakiness(). +FF_DATA_SUFFIX = '_flakies' +FF_SLEEP_INTERVAL = 10.0 +FF_NUM_ITERATIONS = 100 +FF_SUPERVISOR_ARGS = ['-r3', '--random-seed'] + +# Defaults for FindUnaryFlakiness(). +FF_OUTPUT_SUFFIX = '_purges' +FF_NUM_PROCS = 20 +FF_NUM_REPEATS = 10 +FF_TIMEOUT = 600 + + +def FindShardingFlakiness(test_path, data_path, supervisor_args): + """Finds flaky test cases by sharding and running a test for the specified + number of times. The data file is read at the beginning of each run to find + the last known counts and is overwritten at the end of each run with the new + counts. There is an optional sleep interval between each run so the script can + be killed without losing the data, useful for overnight (or weekend!) runs. + """ + + failed_tests = {} + # Read a previously written data file. + if os.path.exists(data_path): + data_file = open(data_path, 'r') + num_runs = int(data_file.readline().split(' ')[0]) + num_passes = int(data_file.readline().split(' ')[0]) + for line in data_file: + if line: + split_line = line.split(' -> ') + failed_tests[split_line[0]] = int(split_line[1]) + data_file.close() + # No data file found. + else: + num_runs = 0 + num_passes = 0 + + log_lines = False + args = ['python', '../sharding_supervisor/sharding_supervisor.py'] + args.extend(supervisor_args + [test_path]) + proc = subprocess.Popen(args, stderr=subprocess.PIPE) + + # Shard the test and collect failures. + while True: + line = proc.stderr.readline() + if not line: + if proc.poll() is not None: + break + continue + print line.rstrip() + if log_lines: + line = line.rstrip() + if line in failed_tests: + failed_tests[line] += 1 + else: + failed_tests[line] = 1 + elif line.find('FAILED TESTS:') >= 0: + log_lines = True + num_runs += 1 + if proc.returncode == 0: + num_passes += 1 + + # Write the data file and print results. + data_file = open(data_path, 'w') + print '%i runs' % num_runs + data_file.write('%i runs\n' % num_runs) + print '%i passes' % num_passes + data_file.write('%i passes\n' % num_passes) + for (test, count) in failed_tests.iteritems(): + print '%s -> %i' % (test, count) + data_file.write('%s -> %i\n' % (test, count)) + data_file.close() + + +def FindUnaryFlakiness(test_path, output_path, num_procs, num_repeats, timeout): + """Runs all the test cases in a given test in parallel with itself, to get at + those that hold on to shared resources. The idea is that if a test uses a + unary resource, then running many instances of this test will purge out some + of them as failures or timeouts. + """ + + test_name_regex = r'((\w+/)?\w+\.\w+(/\d+)?)' + test_start = re.compile('\[\s+RUN\s+\] ' + test_name_regex) + test_list = [] + + # Run the test to discover all the test cases. + proc = subprocess.Popen([test_path], stdout=subprocess.PIPE) + while True: + line = proc.stdout.readline() + if not line: + if proc.poll() is not None: + break + continue + print line.rstrip() + results = test_start.search(line) + if results: + test_list.append(results.group(1)) + + failures = [] + index = 0 + total = len(test_list) + + # Run each test case in parallel with itself. + for test_name in test_list: + num_fails = 0 + num_terminated = 0 + procs = [] + args = [test_path, '--gtest_filter=' + test_name, + '--gtest_repeat=%i' % num_repeats] + while len(procs) < num_procs: + procs.append(subprocess.Popen(args)) + seconds = 0 + while procs: + for proc in procs: + if proc.poll() is not None: + if proc.returncode != 0: + ++num_fails + procs.remove(proc) + # Timeout exceeded, kill the remaining processes and make a note. + if seconds > timeout: + num_fails += len(procs) + num_terminated = len(procs) + while procs: + procs.pop().terminate() + time.sleep(1.0) + seconds += 1 + if num_fails: + line = '%s: %i failed' % (test_name, num_fails) + if num_terminated: + line += ' (%i terminated)' % num_terminated + failures.append(line) + print '%s (%i / %i): %i failed' % (test_name, index, total, num_fails) + index += 1 + time.sleep(1.0) + + # Print the results and write the data file. + print failures + data_file = open(output_path, 'w') + for line in failures: + data_file.write(line + '\n') + data_file.close() + + +def main(): + if not args: + parser.error('You must specify a path to test!') + if not os.path.exists(args[0]): + parser.error('%s does not exist!' % args[0]) + + data_path = os.path.basename(args[0]) + FF_DATA_SUFFIX + output_path = os.path.basename(args[0]) + FF_OUTPUT_SUFFIX + + for i in range(FF_NUM_ITERATIONS): + FindShardingFlakiness(args[0], data_path, FF_SUPERVISOR_ARGS) + print 'That was just iteration %i of %i.' % (i + 1, FF_NUM_ITERATIONS) + time.sleep(FF_SLEEP_INTERVAL) + + FindUnaryFlakiness( + args[0], output_path, FF_NUM_PROCS, FF_NUM_REPEATS, FF_TIMEOUT) + + +if __name__ == '__main__': + main() diff --git a/tools/gdb/gdb_chrome.py b/tools/gdb/gdb_chrome.py new file mode 100644 index 0000000000..8173fa15a5 --- /dev/null +++ b/tools/gdb/gdb_chrome.py @@ -0,0 +1,296 @@ +# Copyright (c) 2011 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""GDB support for Chrome types. + +Add this to your gdb by amending your ~/.gdbinit as follows: + python + import sys + sys.path.insert(0, "/path/to/tools/gdb/") + import gdb_chrome + end + +This module relies on the WebKit gdb module already existing in +your Python path. + +Use + (gdb) p /r any_variable +to print |any_variable| without using any printers. +""" + +import datetime +import gdb +import webkit + +# When debugging this module, set the below variable to True, and then use +# (gdb) python del sys.modules['gdb_chrome'] +# (gdb) python import gdb_chrome +# to reload. +_DEBUGGING = False + + +pp_set = gdb.printing.RegexpCollectionPrettyPrinter("chromium") + + +def typed_ptr(ptr): + """Prints a pointer along with its exact type. + + By default, gdb would print just the address, which takes more + steps to interpret. + """ + # Returning this as a cast expression surrounded by parentheses + # makes it easier to cut+paste inside of gdb. + return '((%s)%s)' % (ptr.dynamic_type, ptr) + + +class Printer(object): + def __init__(self, val): + self.val = val + + +class StringPrinter(Printer): + def display_hint(self): + return 'string' + + +class String16Printer(StringPrinter): + def to_string(self): + return webkit.ustring_to_string(self.val['_M_dataplus']['_M_p']) +pp_set.add_printer( + 'string16', + '^string16|std::basic_string<(unsigned short|char16|base::char16).*>$', + String16Printer); + + +class GURLPrinter(StringPrinter): + def to_string(self): + return self.val['spec_'] +pp_set.add_printer('GURL', '^GURL$', GURLPrinter) + + +class FilePathPrinter(StringPrinter): + def to_string(self): + return self.val['path_']['_M_dataplus']['_M_p'] +pp_set.add_printer('FilePath', '^FilePath$', FilePathPrinter) + + +class SizePrinter(Printer): + def to_string(self): + return '%sx%s' % (self.val['width_'], self.val['height_']) +pp_set.add_printer('gfx::Size', '^gfx::(Size|SizeF|SizeBase<.*>)$', SizePrinter) + + +class PointPrinter(Printer): + def to_string(self): + return '%s,%s' % (self.val['x_'], self.val['y_']) +pp_set.add_printer('gfx::Point', '^gfx::(Point|PointF|PointBase<.*>)$', + PointPrinter) + + +class RectPrinter(Printer): + def to_string(self): + return '%s %s' % (self.val['origin_'], self.val['size_']) +pp_set.add_printer('gfx::Rect', '^gfx::(Rect|RectF|RectBase<.*>)$', + RectPrinter) + + +class SmartPtrPrinter(Printer): + def to_string(self): + return '%s%s' % (self.typename, typed_ptr(self.ptr())) + + +class ScopedRefPtrPrinter(SmartPtrPrinter): + typename = 'scoped_refptr' + def ptr(self): + return self.val['ptr_'] +pp_set.add_printer('scoped_refptr', '^scoped_refptr<.*>$', ScopedRefPtrPrinter) + + +class LinkedPtrPrinter(SmartPtrPrinter): + typename = 'linked_ptr' + def ptr(self): + return self.val['value_'] +pp_set.add_printer('linked_ptr', '^linked_ptr<.*>$', LinkedPtrPrinter) + + +class WeakPtrPrinter(SmartPtrPrinter): + typename = 'base::WeakPtr' + def ptr(self): + flag = ScopedRefPtrPrinter(self.val['ref_']['flag_']).ptr() + if flag and flag['is_valid_']: + return self.val['ptr_'] + return gdb.Value(0).cast(self.val['ptr_'].type) +pp_set.add_printer('base::WeakPtr', '^base::WeakPtr<.*>$', WeakPtrPrinter) + + +class CallbackPrinter(Printer): + """Callbacks provide no usable information so reduce the space they take.""" + def to_string(self): + return '...' +pp_set.add_printer('base::Callback', '^base::Callback<.*>$', CallbackPrinter) + + +class LocationPrinter(Printer): + def to_string(self): + return '%s()@%s:%s' % (self.val['function_name_'].string(), + self.val['file_name_'].string(), + self.val['line_number_']) +pp_set.add_printer('tracked_objects::Location', '^tracked_objects::Location$', + LocationPrinter) + + +class LockPrinter(Printer): + def to_string(self): + try: + if self.val['owned_by_thread_']: + return 'Locked by thread %s' % self.val['owning_thread_id_'] + else: + return 'Unlocked' + except gdb.error: + return 'Unknown state' +pp_set.add_printer('base::Lock', '^base::Lock$', LockPrinter) + + +class TimeDeltaPrinter(object): + def __init__(self, val): + self._timedelta = datetime.timedelta(microseconds=int(val['delta_'])) + + def timedelta(self): + return self._timedelta + + def to_string(self): + return str(self._timedelta) +pp_set.add_printer('base::TimeDelta', '^base::TimeDelta$', TimeDeltaPrinter) + + +class TimeTicksPrinter(TimeDeltaPrinter): + def __init__(self, val): + self._timedelta = datetime.timedelta(microseconds=int(val['ticks_'])) +pp_set.add_printer('base::TimeTicks', '^base::TimeTicks$', TimeTicksPrinter) + + +class TimePrinter(object): + def __init__(self, val): + timet_offset = gdb.parse_and_eval( + 'base::Time::kTimeTToMicrosecondsOffset') + self._datetime = (datetime.datetime.fromtimestamp(0) + + datetime.timedelta(microseconds= + int(val['us_'] - timet_offset))) + + def datetime(self): + return self._datetime + + def to_string(self): + return str(self._datetime) +pp_set.add_printer('base::Time', '^base::Time$', TimePrinter) + + +class IpcMessagePrinter(Printer): + def header(self): + return self.val['header_'].cast( + gdb.lookup_type('IPC::Message::Header').pointer()) + + def to_string(self): + message_type = self.header()['type'] + return '%s of kind %s line %s' % ( + self.val.dynamic_type, + (message_type >> 16).cast(gdb.lookup_type('IPCMessageStart')), + message_type & 0xffff) + + def children(self): + yield ('header_', self.header().dereference()) + yield ('capacity_', self.val['capacity_']) + yield ('variable_buffer_offset_', self.val['variable_buffer_offset_']) + for field in self.val.type.fields(): + if field.is_base_class: + continue + yield (field.name, self.val[field.name]) +pp_set.add_printer('IPC::Message', '^IPC::Message$', IpcMessagePrinter) + + +class NotificationRegistrarPrinter(Printer): + def to_string(self): + try: + registrations = self.val['registered_'] + vector_finish = registrations['_M_impl']['_M_finish'] + vector_start = registrations['_M_impl']['_M_start'] + if vector_start == vector_finish: + return 'Not watching notifications' + if vector_start.dereference().type.sizeof == 0: + # Incomplete type: b/8242773 + return 'Watching some notifications' + return ('Watching %s notifications; ' + 'print %s->registered_ for details') % ( + int(vector_finish - vector_start), + typed_ptr(self.val.address)) + except gdb.error: + return 'NotificationRegistrar' +pp_set.add_printer('content::NotificationRegistrar', + '^content::NotificationRegistrar$', + NotificationRegistrarPrinter) + + +class SiteInstanceImplPrinter(object): + def __init__(self, val): + self.val = val.cast(val.dynamic_type) + + def to_string(self): + return 'SiteInstanceImpl@%s for %s' % ( + self.val.address, self.val['site_']) + + def children(self): + yield ('id_', self.val['id_']) + yield ('has_site_', self.val['has_site_']) + if self.val['browsing_instance_']['ptr_']: + yield ('browsing_instance_', self.val['browsing_instance_']['ptr_']) + if self.val['process_']: + yield ('process_', typed_ptr(self.val['process_'])) + if self.val['render_process_host_factory_']: + yield ('render_process_host_factory_', + self.val['render_process_host_factory_']) +pp_set.add_printer('content::SiteInstanceImpl', '^content::SiteInstanceImpl$', + SiteInstanceImplPrinter) + + +class RenderProcessHostImplPrinter(object): + def __init__(self, val): + self.val = val.cast(val.dynamic_type) + + def to_string(self): + pid = '' + try: + child_process_launcher_ptr = ( + self.val['child_process_launcher_']['impl_']['data_']['ptr']) + if child_process_launcher_ptr: + context = (child_process_launcher_ptr['context_']['ptr_']) + if context: + pid = ' PID %s' % str(context['process_']['process_']) + except gdb.error: + # The definition of the Context type may not be available. + # b/8242773 + pass + return 'RenderProcessHostImpl@%s%s' % (self.val.address, pid) + + def children(self): + yield ('id_', self.val['id_']) + yield ('render_widget_hosts_', + self.val['render_widget_hosts_']['data_']) + yield ('fast_shutdown_started_', self.val['fast_shutdown_started_']) + yield ('deleting_soon_', self.val['deleting_soon_']) + yield ('pending_views_', self.val['pending_views_']) + yield ('visible_widgets_', self.val['visible_widgets_']) + yield ('backgrounded_', self.val['backgrounded_']) + yield ('widget_helper_', self.val['widget_helper_']) + yield ('is_initialized_', self.val['is_initialized_']) + yield ('browser_context_', typed_ptr(self.val['browser_context_'])) + yield ('sudden_termination_allowed_', + self.val['sudden_termination_allowed_']) + yield ('ignore_input_events_', self.val['ignore_input_events_']) + yield ('is_guest_', self.val['is_guest_']) +pp_set.add_printer('content::RenderProcessHostImpl', + '^content::RenderProcessHostImpl$', + RenderProcessHostImplPrinter) + + +gdb.printing.register_pretty_printer(gdb, pp_set, replace=_DEBUGGING) diff --git a/tools/gen_keyboard_overlay_data/gen_keyboard_overlay_data.py b/tools/gen_keyboard_overlay_data/gen_keyboard_overlay_data.py new file mode 100755 index 0000000000..8531241f28 --- /dev/null +++ b/tools/gen_keyboard_overlay_data/gen_keyboard_overlay_data.py @@ -0,0 +1,529 @@ +#!/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. + +"""Generate keyboard layout and hotkey data for the keyboard overlay. + +This script fetches data from the keyboard layout and hotkey data spreadsheet, +and output the data depending on the option. + + --cc: Rewrites a part of C++ code in + chrome/browser/chromeos/webui/keyboard_overlay_ui.cc + + --grd: Rewrites a part of grd messages in + chrome/app/generated_resources.grd + + --js: Rewrites the entire JavaScript code in + chrome/browser/resources/keyboard_overlay/keyboard_overlay_data.js + +These options can be specified at the same time. + +e.g. +python gen_keyboard_overlay_data.py --cc --grd --js + +The output directory of the generated files can be changed with --outdir. + +e.g. (This will generate tmp/keyboard_overlay.js) +python gen_keyboard_overlay_data.py --outdir=tmp --js +""" + +import cStringIO +import datetime +import gdata.spreadsheet.service +import getpass +import json +import optparse +import os +import re +import sys + +MODIFIER_SHIFT = 1 << 0 +MODIFIER_CTRL = 1 << 1 +MODIFIER_ALT = 1 << 2 + +KEYBOARD_GLYPH_SPREADSHEET_KEY = '0Ao3KldW9piwEdExLbGR6TmZ2RU9aUjFCMmVxWkVqVmc' +HOTKEY_SPREADSHEET_KEY = '0AqzoqbAMLyEPdE1RQXdodk1qVkFyTWtQbUxROVM1cXc' +CC_OUTDIR = 'chrome/browser/ui/webui/chromeos' +CC_FILENAME = 'keyboard_overlay_ui.cc' +GRD_OUTDIR = 'chrome/app' +GRD_FILENAME = 'chromeos_strings.grdp' +JS_OUTDIR = 'chrome/browser/resources/chromeos' +JS_FILENAME = 'keyboard_overlay_data.js' +CC_START = r'IDS_KEYBOARD_OVERLAY_INSTRUCTIONS_HIDE },' +CC_END = r'};' +GRD_START = r' ' +GRD_END = r' ' + +LABEL_MAP = { + 'glyph_arrow_down': 'down', + 'glyph_arrow_left': 'left', + 'glyph_arrow_right': 'right', + 'glyph_arrow_up': 'up', + 'glyph_back': 'back', + 'glyph_backspace': 'backspace', + 'glyph_brightness_down': 'bright down', + 'glyph_brightness_up': 'bright up', + 'glyph_enter': 'enter', + 'glyph_forward': 'forward', + 'glyph_fullscreen': 'full screen', + # Kana/Eisu key on Japanese keyboard + 'glyph_ime': u'\u304b\u306a\u0020\u002f\u0020\u82f1\u6570', + 'glyph_lock': 'lock', + 'glyph_overview': 'switch window', + 'glyph_power': 'power', + 'glyph_right': 'right', + 'glyph_reload': 'reload', + 'glyph_search': 'search', + 'glyph_shift': 'shift', + 'glyph_tab': 'tab', + 'glyph_tools': 'tools', + 'glyph_volume_down': 'vol. down', + 'glyph_volume_mute': 'mute', + 'glyph_volume_up': 'vol. up', +}; + +INPUT_METHOD_ID_TO_OVERLAY_ID = { + 'm17n:ar:kbd': 'ar', + 'm17n:fa:isiri': 'ar', + 'm17n:hi:itrans': 'hi', + 'm17n:th:kesmanee': 'th', + 'm17n:th:pattachote': 'th', + 'm17n:th:tis820': 'th', + 'm17n:vi:tcvn': 'vi', + 'm17n:vi:telex': 'vi', + 'm17n:vi:viqr': 'vi', + 'm17n:vi:vni': 'vi', + 'm17n:zh:cangjie': 'zh_TW', + 'm17n:zh:quick': 'zh_TW', + 'mozc': 'en_US', + 'mozc-chewing': 'zh_TW', + 'mozc-dv': 'en_US_dvorak', + 'mozc-hangul': 'ko', + 'mozc-jp': 'ja', + 'pinyin': 'zh_CN', + 'pinyin-dv': 'en_US_dvorak', + 'xkb:be::fra': 'fr', + 'xkb:be::ger': 'de', + 'xkb:be::nld': 'nl', + 'xkb:bg::bul': 'bg', + 'xkb:bg:phonetic:bul': 'bg', + 'xkb:br::por': 'pt_BR', + 'xkb:ca::fra': 'fr_CA', + 'xkb:ca:eng:eng': 'ca', + 'xkb:ch::ger': 'de', + 'xkb:ch:fr:fra': 'fr', + 'xkb:cz::cze': 'cs', + 'xkb:de::ger': 'de', + 'xkb:de:neo:ger': 'de_neo', + 'xkb:dk::dan': 'da', + 'xkb:ee::est': 'et', + 'xkb:es::spa': 'es', + 'xkb:es:cat:cat': 'ca', + 'xkb:fi::fin': 'fi', + 'xkb:fr::fra': 'fr', + 'xkb:gb:dvorak:eng': 'en_GB_dvorak', + 'xkb:gb:extd:eng': 'en_GB', + 'xkb:gr::gre': 'el', + 'xkb:hr::scr': 'hr', + 'xkb:hu::hun': 'hu', + 'xkb:il::heb': 'iw', + 'xkb:it::ita': 'it', + 'xkb:jp::jpn': 'ja', + 'xkb:kr:kr104:kor': 'ko', + 'xkb:latam::spa': 'es_419', + 'xkb:lt::lit': 'lt', + 'xkb:lv:apostrophe:lav': 'lv', + 'xkb:no::nob': 'no', + 'xkb:pl::pol': 'pl', + 'xkb:pt::por': 'pt_PT', + 'xkb:ro::rum': 'ro', + 'xkb:rs::srp': 'sr', + 'xkb:ru::rus': 'ru', + 'xkb:ru:phonetic:rus': 'ru', + 'xkb:se::swe': 'sv', + 'xkb:si::slv': 'sl', + 'xkb:sk::slo': 'sk', + 'xkb:tr::tur': 'tr', + 'xkb:ua::ukr': 'uk', + 'xkb:us::eng': 'en_US', + 'xkb:us:altgr-intl:eng': 'en_US_altgr_intl', + 'xkb:us:colemak:eng': 'en_US_colemak', + 'xkb:us:dvorak:eng': 'en_US_dvorak', + 'xkb:us:intl:eng': 'en_US_intl', + 'zinnia-japanese': 'ja', +} + +# The file was first generated in 2012 and we have a policy of not updating +# copyright dates. +COPYRIGHT_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. + +// This is a generated file but may contain local modifications. See +// src/tools/gen_keyboard_overlay_data/gen_keyboard_overlay_data.py --help +""" + +# A snippet for grd file +GRD_SNIPPET_TEMPLATE=""" + %s + +""" + +# A snippet for C++ file +CC_SNIPPET_TEMPLATE=""" { "%s", %s }, +""" + + +def SplitBehavior(behavior): + """Splits the behavior to compose a message or i18n-content value. + + Examples: + 'Activate last tab' => ['Activate', 'last', 'tab'] + 'Close tab' => ['Close', 'tab'] + """ + return [x for x in re.split('[ ()"-.,]', behavior) if len(x) > 0] + + +def ToMessageName(behavior): + """Composes a message name for grd file. + + Examples: + 'Activate last tab' => IDS_KEYBOARD_OVERLAY_ACTIVATE_LAST_TAB + 'Close tab' => IDS_KEYBOARD_OVERLAY_CLOSE_TAB + """ + segments = [segment.upper() for segment in SplitBehavior(behavior)] + return 'IDS_KEYBOARD_OVERLAY_' + ('_'.join(segments)) + + +def ToMessageDesc(description): + """Composes a message description for grd file.""" + message_desc = 'The text in the keyboard overlay to explain the shortcut' + if description: + message_desc = '%s (%s).' % (message_desc, description) + else: + message_desc += '.' + return message_desc + + +def Toi18nContent(behavior): + """Composes a i18n-content value for HTML/JavaScript files. + + Examples: + 'Activate last tab' => keyboardOverlayActivateLastTab + 'Close tab' => keyboardOverlayCloseTab + """ + segments = [segment.lower() for segment in SplitBehavior(behavior)] + result = 'keyboardOverlay' + for segment in segments: + result += segment[0].upper() + segment[1:] + return result + + +def ToKeys(hotkey): + """Converts the action value to shortcut keys used from JavaScript. + + Examples: + 'Ctrl - 9' => '9<>CTRL' + 'Ctrl - Shift - Tab' => 'tab<>CTRL<>SHIFT' + """ + values = hotkey.split(' - ') + modifiers = sorted(value.upper() for value in values + if value in ['Shift', 'Ctrl', 'Alt', 'Search']) + keycode = [value.lower() for value in values + if value not in ['Shift', 'Ctrl', 'Alt', 'Search']] + # The keys which are highlighted even without modifier keys. + base_keys = ['backspace', 'power'] + if not modifiers and (keycode and keycode[0] not in base_keys): + return None + return '<>'.join(keycode + modifiers) + + +def ParseOptions(): + """Parses the input arguemnts and returns options.""" + # default_username = os.getusername() + '@google.com'; + default_username = '%s@google.com' % os.environ.get('USER') + parser = optparse.OptionParser() + parser.add_option('--key', dest='key', + help='The key of the spreadsheet (required).') + parser.add_option('--username', dest='username', + default=default_username, + help='Your user name (default: %s).' % default_username) + parser.add_option('--password', dest='password', + help='Your password.') + parser.add_option('--account_type', default='GOOGLE', dest='account_type', + help='Account type used for gdata login (default: GOOGLE)') + parser.add_option('--js', dest='js', default=False, action='store_true', + help='Output js file.') + parser.add_option('--grd', dest='grd', default=False, action='store_true', + help='Output resource file.') + parser.add_option('--cc', dest='cc', default=False, action='store_true', + help='Output cc file.') + parser.add_option('--outdir', dest='outdir', default=None, + help='Specify the directory files are generated.') + (options, unused_args) = parser.parse_args() + + if not options.username.endswith('google.com'): + print 'google.com account is necessary to use this script.' + sys.exit(-1) + + if (not (options.js or options.grd or options.cc)): + print 'Either --js, --grd, or --cc needs to be specified.' + sys.exit(-1) + + # Get the password from the terminal, if needed. + if not options.password: + options.password = getpass.getpass( + 'Application specific password for %s: ' % options.username) + return options + + +def InitClient(options): + """Initializes the spreadsheet client.""" + client = gdata.spreadsheet.service.SpreadsheetsService() + client.email = options.username + client.password = options.password + client.source = 'Spread Sheet' + client.account_type = options.account_type + print 'Logging in as %s (%s)' % (client.email, client.account_type) + client.ProgrammaticLogin() + return client + + +def PrintDiffs(message, lhs, rhs): + """Prints the differences between |lhs| and |rhs|.""" + dif = set(lhs).difference(rhs) + if dif: + print message, ', '.join(dif) + + +def FetchSpreadsheetFeeds(client, key, sheets, cols): + """Fetch feeds from the spreadsheet. + + Args: + client: A spreadsheet client to be used for fetching data. + key: A key string of the spreadsheet to be fetched. + sheets: A list of the sheet names to read data from. + cols: A list of columns to read data from. + """ + worksheets_feed = client.GetWorksheetsFeed(key) + print 'Fetching data from the worksheet: %s' % worksheets_feed.title.text + worksheets_data = {} + titles = [] + for entry in worksheets_feed.entry: + worksheet_id = entry.id.text.split('/')[-1] + list_feed = client.GetListFeed(key, worksheet_id) + list_data = [] + # Hack to deal with sheet names like 'sv (Copy of fl)' + title = list_feed.title.text.split('(')[0].strip() + titles.append(title) + if title not in sheets: + continue + print 'Reading data from the sheet: %s' % list_feed.title.text + for i, entry in enumerate(list_feed.entry): + line_data = {} + for k in entry.custom: + if (k not in cols) or (not entry.custom[k].text): + continue + line_data[k] = entry.custom[k].text + list_data.append(line_data) + worksheets_data[title] = list_data + PrintDiffs('Exist only on the spreadsheet: ', titles, sheets) + PrintDiffs('Specified but do not exist on the spreadsheet: ', sheets, titles) + return worksheets_data + + +def FetchKeyboardGlyphData(client): + """Fetches the keyboard glyph data from the spreadsheet.""" + glyph_cols = ['scancode', 'p0', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', + 'p8', 'p9', 'label', 'format', 'notes'] + keyboard_glyph_data = FetchSpreadsheetFeeds( + client, KEYBOARD_GLYPH_SPREADSHEET_KEY, + INPUT_METHOD_ID_TO_OVERLAY_ID.values(), glyph_cols) + ret = {} + for lang in keyboard_glyph_data: + ret[lang] = {} + keys = {} + for line in keyboard_glyph_data[lang]: + scancode = line.get('scancode') + if (not scancode) and line.get('notes'): + ret[lang]['layoutName'] = line['notes'] + continue + del line['scancode'] + if 'notes' in line: + del line['notes'] + if 'label' in line: + line['label'] = LABEL_MAP.get(line['label'], line['label']) + keys[scancode] = line + # Add a label to space key + if '39' not in keys: + keys['39'] = {'label': 'space'} + ret[lang]['keys'] = keys + return ret + + +def FetchLayoutsData(client): + """Fetches the keyboard glyph data from the spreadsheet.""" + layout_names = ['U_layout', 'J_layout', 'E_layout', 'B_layout'] + cols = ['scancode', 'x', 'y', 'w', 'h'] + layouts = FetchSpreadsheetFeeds(client, KEYBOARD_GLYPH_SPREADSHEET_KEY, + layout_names, cols) + ret = {} + for layout_name, layout in layouts.items(): + ret[layout_name[0]] = [] + for row in layout: + line = [] + for col in cols: + value = row.get(col) + if not value: + line.append('') + else: + if col != 'scancode': + value = float(value) + line.append(value) + ret[layout_name[0]].append(line) + return ret + + +def FetchHotkeyData(client): + """Fetches the hotkey data from the spreadsheet.""" + hotkey_sheet = ['Cross Platform Behaviors'] + hotkey_cols = ['behavior', 'context', 'kind', 'actionctrlctrlcmdonmac', + 'chromeos', 'descriptionfortranslation'] + hotkey_data = FetchSpreadsheetFeeds(client, HOTKEY_SPREADSHEET_KEY, + hotkey_sheet, hotkey_cols) + action_to_id = {} + id_to_behavior = {} + # (behavior, action) + result = [] + for line in hotkey_data['Cross Platform Behaviors']: + if (not line.get('chromeos')) or (line.get('kind') != 'Key'): + continue + action = ToKeys(line['actionctrlctrlcmdonmac']) + if not action: + continue + behavior = line['behavior'].strip() + description = line.get('descriptionfortranslation') + result.append((behavior, action, description)) + return result + + +def UniqueBehaviors(hotkey_data): + """Retrieves a sorted list of unique behaviors from |hotkey_data|.""" + return sorted(set((behavior, description) for (behavior, _, description) + in hotkey_data), + cmp=lambda x, y: cmp(ToMessageName(x[0]), ToMessageName(y[0]))) + + +def GetPath(path_from_src): + """Returns the absolute path of the specified path.""" + path = os.path.join(os.path.dirname(__file__), '../..', path_from_src) + if not os.path.isfile(path): + print 'WARNING: %s does not exist. Maybe moved or renamed?' % path + return path + + +def OutputFile(outpath, snippet): + """Output the snippet into the specified path.""" + out = file(outpath, 'w') + out.write(COPYRIGHT_HEADER + '\n') + out.write(snippet) + print 'Output ' + os.path.normpath(outpath) + + +def RewriteFile(start, end, original_dir, original_filename, snippet, + outdir=None): + """Replaces a part of the specified file with snippet and outputs it.""" + original_path = GetPath(os.path.join(original_dir, original_filename)) + original = file(original_path, 'r') + original_content = original.read() + original.close() + if outdir: + outpath = os.path.join(outdir, original_filename) + else: + outpath = original_path + out = file(outpath, 'w') + rx = re.compile(r'%s\n.*?%s\n' % (re.escape(start), re.escape(end)), + re.DOTALL) + new_content = re.sub(rx, '%s\n%s%s\n' % (start, snippet, end), + original_content) + out.write(new_content) + out.close() + print 'Output ' + os.path.normpath(outpath) + + +def OutputJson(keyboard_glyph_data, hotkey_data, layouts, var_name, outdir): + """Outputs the keyboard overlay data as a JSON file.""" + action_to_id = {} + for (behavior, action, _) in hotkey_data: + i18nContent = Toi18nContent(behavior) + action_to_id[action] = i18nContent + data = {'keyboardGlyph': keyboard_glyph_data, + 'shortcut': action_to_id, + 'layouts': layouts, + 'inputMethodIdToOverlayId': INPUT_METHOD_ID_TO_OVERLAY_ID} + + if not outdir: + outdir = JS_OUTDIR + outpath = GetPath(os.path.join(outdir, JS_FILENAME)) + json_data = json.dumps(data, sort_keys=True, indent=2) + # Remove redundant spaces after ',' + json_data = json_data.replace(', \n', ',\n') + # Replace double quotes with single quotes to avoid lint warnings. + json_data = json_data.replace('\"', '\'') + snippet = 'var %s = %s;\n' % (var_name, json_data) + OutputFile(outpath, snippet) + + +def OutputGrd(hotkey_data, outdir): + """Outputs a part of messages in the grd file.""" + snippet = cStringIO.StringIO() + for (behavior, description) in UniqueBehaviors(hotkey_data): + # Do not generate message for 'Show wrench menu'. It is handled manually + # based on branding. + if behavior == 'Show wrench menu': + continue + snippet.write(GRD_SNIPPET_TEMPLATE % + (ToMessageName(behavior), ToMessageDesc(description), + behavior)) + + RewriteFile(GRD_START, GRD_END, GRD_OUTDIR, GRD_FILENAME, snippet.getvalue(), + outdir) + + +def OutputCC(hotkey_data, outdir): + """Outputs a part of code in the C++ file.""" + snippet = cStringIO.StringIO() + for (behavior, _) in UniqueBehaviors(hotkey_data): + message_name = ToMessageName(behavior) + output = CC_SNIPPET_TEMPLATE % (Toi18nContent(behavior), message_name) + # Break the line if the line is longer than 80 characters + if len(output) > 80: + output = output.replace(' ' + message_name, '\n %s' % message_name) + snippet.write(output) + + RewriteFile(CC_START, CC_END, CC_OUTDIR, CC_FILENAME, snippet.getvalue(), + outdir) + + +def main(): + options = ParseOptions() + client = InitClient(options) + hotkey_data = FetchHotkeyData(client) + + if options.js: + keyboard_glyph_data = FetchKeyboardGlyphData(client) + + if options.js: + layouts = FetchLayoutsData(client) + OutputJson(keyboard_glyph_data, hotkey_data, layouts, 'keyboardOverlayData', + options.outdir) + if options.grd: + OutputGrd(hotkey_data, options.outdir) + if options.cc: + OutputCC(hotkey_data, options.outdir) + + +if __name__ == '__main__': + main() diff --git a/tools/generate_library_loader/generate_library_loader.py b/tools/generate_library_loader/generate_library_loader.py new file mode 100755 index 0000000000..866e005547 --- /dev/null +++ b/tools/generate_library_loader/generate_library_loader.py @@ -0,0 +1,256 @@ +#!/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. + +""" +Creates a library loader (a header and implementation file), +which is a wrapper for dlopen or direct linking with given library. + +The loader makes it possible to have the same client code for both cases, +and also makes it easier to write code using dlopen (and also provides +a standard way to do so, and limits the ugliness just to generated files). + +For more info refer to http://crbug.com/162733 . +""" + + +import optparse +import os.path +import re +import sys + + +HEADER_TEMPLATE = """// This is generated file. Do not modify directly. +// Path to the code generator: %(generator_path)s . + +#ifndef %(unique_prefix)s +#define %(unique_prefix)s + +%(wrapped_header_include)s + +#include + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#if defined(%(unique_prefix)s_DLOPEN) +#include "base/native_library.h" +#endif + +class %(class_name)s { + public: + %(class_name)s(); + ~%(class_name)s(); + + bool Load(const std::string& library_name) WARN_UNUSED_RESULT; + + bool loaded() const { return loaded_; } + +%(member_decls)s + + private: + void CleanUp(bool unload); + +#if defined(%(unique_prefix)s_DLOPEN) + base::NativeLibrary library_; +#endif + + bool loaded_; + + DISALLOW_COPY_AND_ASSIGN(%(class_name)s); +}; + +#endif // %(unique_prefix)s +""" + + +HEADER_MEMBER_TEMPLATE = """ typeof(&::%(function_name)s) %(function_name)s; +""" + + +IMPL_TEMPLATE = """// This is generated file. Do not modify directly. +// Path to the code generator: %(generator_path)s . + +#include "%(generated_header_name)s" + +// Put these sanity checks here so that they fire at most once +// (to avoid cluttering the build output). +#if !defined(%(unique_prefix)s_DLOPEN) && !defined(%(unique_prefix)s_DT_NEEDED) +#error neither %(unique_prefix)s_DLOPEN nor %(unique_prefix)s_DT_NEEDED defined +#endif +#if defined(%(unique_prefix)s_DLOPEN) && defined(%(unique_prefix)s_DT_NEEDED) +#error both %(unique_prefix)s_DLOPEN and %(unique_prefix)s_DT_NEEDED defined +#endif + +#include "base/files/file_path.h" +#include "base/logging.h" + +%(class_name)s::%(class_name)s() : loaded_(false) { +} + +%(class_name)s::~%(class_name)s() { + CleanUp(loaded_); +} + +bool %(class_name)s::Load(const std::string& library_name) { + if (loaded_) { + NOTREACHED(); + return false; + } + +#if defined(%(unique_prefix)s_DLOPEN) + library_ = base::LoadNativeLibrary(base::FilePath(library_name), NULL); + if (!library_) + return false; +#endif + +%(member_init)s + + loaded_ = true; + return true; +} + +void %(class_name)s::CleanUp(bool unload) { +#if defined(%(unique_prefix)s_DLOPEN) + if (unload) { + base::UnloadNativeLibrary(library_); + library_ = NULL; + } +#endif + loaded_ = false; +%(member_cleanup)s +} +""" + +IMPL_MEMBER_INIT_TEMPLATE = """ +#if defined(%(unique_prefix)s_DLOPEN) + %(function_name)s = + reinterpret_cast%(function_name)s)>( + base::GetFunctionPointerFromNativeLibrary( + library_, "%(function_name)s")); +#endif +#if defined(%(unique_prefix)s_DT_NEEDED) + %(function_name)s = &::%(function_name)s; +#endif + if (!%(function_name)s) { + CleanUp(true); + return false; + } +""" + +IMPL_MEMBER_CLEANUP_TEMPLATE = """ %(function_name)s = NULL; +""" + +def main(): + parser = optparse.OptionParser() + parser.add_option('--name') + parser.add_option('--output-cc') + parser.add_option('--output-h') + parser.add_option('--header') + + parser.add_option('--bundled-header') + parser.add_option('--use-extern-c', action='store_true', default=False) + parser.add_option('--link-directly', type=int, default=0) + + options, args = parser.parse_args() + + if not options.name: + parser.error('Missing --name parameter') + if not options.output_cc: + parser.error('Missing --output-cc parameter') + if not options.output_h: + parser.error('Missing --output-h parameter') + if not options.header: + parser.error('Missing --header paramater') + if not args: + parser.error('No function names specified') + + # Make sure we are always dealing with paths relative to source tree root + # to avoid issues caused by different relative path roots. + source_tree_root = os.path.abspath( + os.path.join(os.path.dirname(__file__), '..', '..')) + options.output_cc = os.path.relpath(options.output_cc, source_tree_root) + options.output_h = os.path.relpath(options.output_h, source_tree_root) + + # Create a unique prefix, e.g. for header guards. + # Stick a known string at the beginning to ensure this doesn't begin + # with an underscore, which is reserved for the C++ implementation. + unique_prefix = ('LIBRARY_LOADER_' + + re.sub(r'[\W]', '_', options.output_h).upper()) + + member_decls = [] + member_init = [] + member_cleanup = [] + for fn in args: + member_decls.append(HEADER_MEMBER_TEMPLATE % { + 'function_name': fn, + 'unique_prefix': unique_prefix + }) + member_init.append(IMPL_MEMBER_INIT_TEMPLATE % { + 'function_name': fn, + 'unique_prefix': unique_prefix + }) + member_cleanup.append(IMPL_MEMBER_CLEANUP_TEMPLATE % { + 'function_name': fn, + 'unique_prefix': unique_prefix + }) + + header = options.header + if options.link_directly == 0 and options.bundled_header: + header = options.bundled_header + wrapped_header_include = '#include %s\n' % header + + # Some libraries (e.g. libpci) have headers that cannot be included + # without extern "C", otherwise they cause the link to fail. + # TODO(phajdan.jr): This is a workaround for broken headers. Remove it. + if options.use_extern_c: + wrapped_header_include = 'extern "C" {\n%s\n}\n' % wrapped_header_include + + # It seems cleaner just to have a single #define here and #ifdefs in bunch + # of places, rather than having a different set of templates, duplicating + # or complicating more code. + if options.link_directly == 0: + wrapped_header_include += '#define %s_DLOPEN\n' % unique_prefix + elif options.link_directly == 1: + wrapped_header_include += '#define %s_DT_NEEDED\n' % unique_prefix + else: + parser.error('Invalid value for --link-directly. Should be 0 or 1.') + + # Make it easier for people to find the code generator just in case. + # Doing it this way is more maintainable, because it's going to work + # even if file gets moved without updating the contents. + generator_path = os.path.relpath(__file__, source_tree_root) + + header_contents = HEADER_TEMPLATE % { + 'generator_path': generator_path, + 'unique_prefix': unique_prefix, + 'wrapped_header_include': wrapped_header_include, + 'class_name': options.name, + 'member_decls': ''.join(member_decls), + } + + impl_contents = IMPL_TEMPLATE % { + 'generator_path': generator_path, + 'unique_prefix': unique_prefix, + 'generated_header_name': options.output_h, + 'class_name': options.name, + 'member_init': ''.join(member_init), + 'member_cleanup': ''.join(member_cleanup), + } + + header_file = open(os.path.join(source_tree_root, options.output_h), 'w') + try: + header_file.write(header_contents) + finally: + header_file.close() + + impl_file = open(os.path.join(source_tree_root, options.output_cc), 'w') + try: + impl_file.write(impl_contents) + finally: + impl_file.close() + + return 0 + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/generate_shim_headers/generate_shim_headers.py b/tools/generate_shim_headers/generate_shim_headers.py new file mode 100755 index 0000000000..81c531b2e6 --- /dev/null +++ b/tools/generate_shim_headers/generate_shim_headers.py @@ -0,0 +1,107 @@ +#!/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. + +""" +Generates shim headers that mirror the directory structure of bundled headers, +but just forward to the system ones. + +This allows seamless compilation against system headers with no changes +to our source code. +""" + + +import optparse +import os.path +import sys + + +SHIM_TEMPLATE = """ +#if defined(OFFICIAL_BUILD) +#error shim headers must not be used in official builds! +#endif +""" + + +def GeneratorMain(argv): + parser = optparse.OptionParser() + parser.add_option('--headers-root', action='append') + parser.add_option('--define', action='append') + parser.add_option('--output-directory') + parser.add_option('--prefix', default='') + parser.add_option('--use-include-next', action='store_true') + parser.add_option('--outputs', action='store_true') + parser.add_option('--generate', action='store_true') + + options, args = parser.parse_args(argv) + + if not options.headers_root: + parser.error('Missing --headers-root parameter.') + if not options.output_directory: + parser.error('Missing --output-directory parameter.') + if not args: + parser.error('Missing arguments - header file names.') + + source_tree_root = os.path.abspath( + os.path.join(os.path.dirname(__file__), '..', '..')) + + for root in options.headers_root: + target_directory = os.path.join( + options.output_directory, + os.path.relpath(root, source_tree_root)) + if options.generate and not os.path.exists(target_directory): + os.makedirs(target_directory) + + for header_spec in args: + if ';' in header_spec: + (header_filename, + include_before, + include_after) = header_spec.split(';', 2) + else: + header_filename = header_spec + include_before = '' + include_after = '' + if options.outputs: + yield os.path.join(target_directory, header_filename) + if options.generate: + with open(os.path.join(target_directory, header_filename), 'w') as f: + f.write(SHIM_TEMPLATE) + + if options.define: + for define in options.define: + key, value = define.split('=', 1) + # This non-standard push_macro extension is supported + # by compilers we support (GCC, clang). + f.write('#pragma push_macro("%s")\n' % key) + f.write('#undef %s\n' % key) + f.write('#define %s %s\n' % (key, value)) + + if include_before: + for header in include_before.split(':'): + f.write('#include %s\n' % header) + + include_target = options.prefix + header_filename + if options.use_include_next: + f.write('#include_next <%s>\n' % include_target) + else: + f.write('#include <%s>\n' % include_target) + + if include_after: + for header in include_after.split(':'): + f.write('#include %s\n' % header) + + if options.define: + for define in options.define: + key, value = define.split('=', 1) + # This non-standard pop_macro extension is supported + # by compilers we support (GCC, clang). + f.write('#pragma pop_macro("%s")\n' % key) + + +def DoMain(argv): + return '\n'.join(GeneratorMain(argv)) + + +if __name__ == '__main__': + DoMain(sys.argv[1:]) diff --git a/tools/generate_stubs/generate_stubs.py b/tools/generate_stubs/generate_stubs.py new file mode 100755 index 0000000000..27f8d5a05d --- /dev/null +++ b/tools/generate_stubs/generate_stubs.py @@ -0,0 +1,1131 @@ +#!/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. + +"""Creates windows and posix stub files for a given set of signatures. + +For libraries that need to be loaded outside of the standard executable startup +path mechanism, stub files need to be generated for the wanted functions. In +windows, this is done via "def" files and the delay load mechanism. On a posix +system, a set of stub functions need to be generated that dispatch to functions +found via dlsym. + +This script takes a set of files, where each file is a list of C-style +signatures (one signature per line). The output is either a windows def file, +or a header + implementation file of stubs suitable for use in a posix system. + +This script also handles varidiac functions, e.g. +void printf(const char* s, ...); + +TODO(hclam): Fix the situation for varidiac functions. +Stub for the above function will be generated and inside the stub function it +is translated to: +void printf(const char* s, ...) { + printf_ptr(s, (void*)arg1); +} + +Only one argument from the varidiac arguments is used and it will be used as +type void*. +""" + +__author__ = 'ajwong@chromium.org (Albert J. Wong)' + +import optparse +import os +import re +import string +import subprocess +import sys + + +class Error(Exception): + pass + + +class BadSignatureError(Error): + pass + + +class SubprocessError(Error): + def __init__(self, message, error_code): + Error.__init__(self) + self.message = message + self.error_code = error_code + + def __str__(self): + return 'Failed with code %s: %s' % (self.message, repr(self.error_code)) + + +# Regular expression used to parse function signatures in the input files. +# The regex is built around identifying the "identifier" for the function name. +# We consider the identifier to be the string that follows these constraints: +# +# 1) Starts with [_a-ZA-Z] (C++ spec 2.10). +# 2) Continues with [_a-ZA-Z0-9] (C++ spec 2.10). +# 3) Preceeds an opening parenthesis by 0 or more whitespace chars. +# +# From that, all preceeding characters are considered the return value. +# Trailing characters should have a substring matching the form (.*). That +# is considered the arguments. +SIGNATURE_REGEX = re.compile('(?P.+?)' + '(?P[_a-zA-Z][_a-zA-Z0-9]+)\s*' + '\((?P.*?)\)') + +# Used for generating C++ identifiers. +INVALID_C_IDENT_CHARS = re.compile('[^_a-zA-Z0-9]') + +# Constants defning the supported file types options. +FILE_TYPE_WIN_X86 = 'windows_lib' +FILE_TYPE_WIN_X64 = 'windows_lib_x64' +FILE_TYPE_POSIX_STUB = 'posix_stubs' +FILE_TYPE_WIN_DEF = 'windows_def' + +# Template for generating a stub function definition. Includes a forward +# declaration marking the symbol as weak. This template takes the following +# named parameters. +# return_type: The return type. +# name: The name of the function. +# params: The parameters to the function. +# return_prefix: 'return ' if this function is not void. '' otherwise. +# arg_list: The arguments used to call the stub function. +STUB_FUNCTION_DEFINITION = ( + """extern %(return_type)s %(name)s(%(params)s) __attribute__((weak)); +%(return_type)s %(name)s(%(params)s) { + %(return_prefix)s%(name)s_ptr(%(arg_list)s); +}""") + +# Template for generating a variadic stub function definition with return +# value. +# Includes a forward declaration marking the symbol as weak. +# This template takes the following named parameters. +# return_type: The return type. +# name: The name of the function. +# params: The parameters to the function. +# arg_list: The arguments used to call the stub function without the +# variadic argument. +# last_named_arg: Name of the last named argument before the variadic +# argument. +VARIADIC_STUB_FUNCTION_DEFINITION = ( + """extern %(return_type)s %(name)s(%(params)s) __attribute__((weak)); +%(return_type)s %(name)s(%(params)s) { + va_list args___; + va_start(args___, %(last_named_arg)s); + %(return_type)s ret___ = %(name)s_ptr(%(arg_list)s, va_arg(args___, void*)); + va_end(args___); + return ret___; +}""") + +# Template for generating a variadic stub function definition without +# return value. +# Includes a forward declaration marking the symbol as weak. +# This template takes the following named parameters. +# name: The name of the function. +# params: The parameters to the function. +# arg_list: The arguments used to call the stub function without the +# variadic argument. +# last_named_arg: Name of the last named argument before the variadic +# argument. +VOID_VARIADIC_STUB_FUNCTION_DEFINITION = ( + """extern void %(name)s(%(params)s) __attribute__((weak)); +void %(name)s(%(params)s) { + va_list args___; + va_start(args___, %(last_named_arg)s); + %(name)s_ptr(%(arg_list)s, va_arg(args___, void*)); + va_end(args___); +}""") + +# Template for the preamble for the stub header file with the header guards, +# standard set of includes, and namespace opener. This template takes the +# following named parameters: +# guard_name: The macro to use as the header guard. +# namespace: The namespace for the stub functions. +STUB_HEADER_PREAMBLE = """// This is generated file. Do not modify directly. + +#ifndef %(guard_name)s +#define %(guard_name)s + +#include +#include +#include + +#include "base/logging.h" + +namespace %(namespace)s { +""" + +# Template for the end of the stub header. This closes the namespace and the +# header guards. This template takes the following named parameters: +# guard_name: The macro to use as the header guard. +# namespace: The namespace for the stub functions. +STUB_HEADER_CLOSER = """} // namespace %(namespace)s + +#endif // %(guard_name)s +""" + +# The standard includes needed for the stub implementation file. Takes one +# string substition with the path to the associated stub header file. +IMPLEMENTATION_PREAMBLE = """// This is generated file. Do not modify directly. + +#include "%s" + +#include // For NULL. +#include // For dysym, dlopen. + +#include +#include +""" + +# The start and end templates for the enum definitions used by the Umbrella +# initializer. +UMBRELLA_ENUM_START = """// Enum and typedef for umbrella initializer. +enum StubModules { +""" +UMBRELLA_ENUM_END = """ kNumStubModules +}; + +""" + +# Start and end of the extern "C" section for the implementation contents. +IMPLEMENTATION_CONTENTS_C_START = """extern "C" { + +""" +IMPLEMENTATION_CONTENTS_C_END = """ +} // extern "C" + + +""" + +# Templates for the start and end of a namespace. Takes one parameter, the +# namespace name. +NAMESPACE_START = """namespace %s { + +""" +NAMESPACE_END = """} // namespace %s + +""" + +# Comment to include before the section declaring all the function pointers +# used by the stub functions. +FUNCTION_POINTER_SECTION_COMMENT = ( + """// Static pointers that will hold the location of the real function +// implementations after the module has been loaded. +""") + +# Template for the module initialization check function. This template +# takes two parameteres: the function name, and the conditional used to +# verify the module's initialization. +MODULE_INITIALIZATION_CHECK_FUNCTION = ( + """// Returns true if all stubs have been properly initialized. +bool %s() { + if (%s) { + return true; + } else { + return false; + } +} + +""") + +# Template for the line that initialize the stub pointer. This template takes +# the following named parameters: +# name: The name of the function. +# return_type: The return type. +# params: The parameters to the function. +STUB_POINTER_INITIALIZER = """ %(name)s_ptr = + reinterpret_cast<%(return_type)s (*)(%(parameters)s)>( + dlsym(module, "%(name)s")); + VLOG_IF(1, !%(name)s_ptr) << "Couldn't load %(name)s, dlerror() says:\\n" + << dlerror(); +""" + +# Template for module initializer function start and end. This template takes +# one parameter which is the initializer function name. +MODULE_INITIALIZE_START = """// Initializes the module stubs. +void %s(void* module) { +""" +MODULE_INITIALIZE_END = """} + +""" + +# Template for module uninitializer function start and end. This template +# takes one parameter which is the initializer function name. +MODULE_UNINITIALIZE_START = ( + """// Uninitialize the module stubs. Reset pointers to NULL. +void %s() { +""") +MODULE_UNINITIALIZE_END = """} + +""" + + +# Open namespace and add typedef for internal data structures used by the +# umbrella initializer. +UMBRELLA_INITIALIZER_START = """namespace %s { +typedef std::map StubHandleMap; +""" + +# Function close DSOs on error and clean up dangling references. +UMBRELLA_INITIALIZER_CLEANUP_FUNCTION = ( + """static void CloseLibraries(StubHandleMap* stub_handles) { + for (StubHandleMap::const_iterator it = stub_handles->begin(); + it != stub_handles->end(); + ++it) { + dlclose(it->second); + } + + stub_handles->clear(); +} +""") + +# Function to initialize each DSO for the given paths. +UMBRELLA_INITIALIZER_INITIALIZE_FUNCTION_START = ( + """bool InitializeStubs(const StubPathMap& path_map) { + StubHandleMap opened_libraries; + for (int i = 0; i < kNumStubModules; ++i) { + StubModules cur_module = static_cast(i); + // If a module is missing, we fail. + StubPathMap::const_iterator it = path_map.find(cur_module); + if (it == path_map.end()) { + CloseLibraries(&opened_libraries); + return false; + } + + // Otherwise, attempt to dlopen the library. + const std::vector& paths = it->second; + bool module_opened = false; + for (std::vector::const_iterator dso_path = paths.begin(); + !module_opened && dso_path != paths.end(); + ++dso_path) { + void* handle = dlopen(dso_path->c_str(), RTLD_LAZY); + if (handle != NULL) { + module_opened = true; + opened_libraries[cur_module] = handle; + } else { + VLOG(1) << "dlopen(" << dso_path->c_str() << ") failed, " + << "dlerror() says:\\n" << dlerror(); + } + } + + if (!module_opened) { + CloseLibraries(&opened_libraries); + return false; + } + } +""") + +# Template to generate code to check if each module initializer correctly +# completed, and cleanup on failures. This template takes the following +# named parameters. +# conditional: The conditional expression for successful initialization. +# uninitializers: The statements needed to uninitialize the modules. +UMBRELLA_INITIALIZER_CHECK_AND_CLEANUP = ( + """ // Check that each module is initialized correctly. + // Close all previously opened libraries on failure. + if (%(conditional)s) { + %(uninitializers)s; + CloseLibraries(&opened_libraries); + return false; + } + + return true; +} +""") + +# Template for Initialize, Unininitialize, and IsInitialized functions for each +# module. This template takes the following named parameters: +# initialize: Name of the Initialize function. +# uninitialize: Name of the Uninitialize function. +# is_initialized: Name of the IsInitialized function. +MODULE_FUNCTION_PROTOTYPES = """bool %(is_initialized)s(); +void %(initialize)s(void* module); +void %(uninitialize)s(); + +""" + +# Template for umbrella initializer declaration and associated datatypes. +UMBRELLA_INITIALIZER_PROTOTYPE = ( + """typedef std::map > StubPathMap; + +// Umbrella initializer for all the modules in this stub file. +bool InitializeStubs(const StubPathMap& path_map); +""") + + +def ExtractModuleName(infile_path): + """Infers the module name from the input file path. + + The input filename is supposed to be in the form "ModuleName.sigs". + This function splits the filename from the extention on that basename of + the path and returns that as the module name. + + Args: + infile_path: String holding the path to the input file. + + Returns: + The module name as a string. + """ + basename = os.path.basename(infile_path) + + # This loop continously removes suffixes of the filename separated by a "." + # character. + while 1: + new_basename = os.path.splitext(basename)[0] + if basename == new_basename: + break + else: + basename = new_basename + return basename + + +def ParseSignatures(infile): + """Parses function signatures in the input file. + + This function parses a file of signatures into a list of dictionaries that + represent the function signatures in the input file. Each dictionary has + the following keys: + return_type: A string with the return type. + name: A string with the name of the function. + params: A list of each function parameter declaration (type + name) + + The format of the input file is one C-style function signature per line, no + trailing semicolon. Empty lines are allowed. An empty line is a line that + consists purely of whitespace. Lines that begin with a # are considered + comment lines and are ignored. + + We assume that "int foo(void)" is the same as "int foo()", which is not + true in C where "int foo()" is equivalent to "int foo(...)". Our generated + code is C++, and we do not handle varargs, so this is a case that can be + ignored for now. + + Args: + infile: File object holding a text file of function signatures. + + Returns: + A list of dictionaries, where each dictionary represents one function + signature. + + Raises: + BadSignatureError: A line could not be parsed as a signature. + """ + signatures = [] + for line in infile: + line = line.strip() + if line and line[0] != '#': + m = SIGNATURE_REGEX.match(line) + if m is None: + raise BadSignatureError('Unparsable line: %s' % line) + signatures.append( + {'return_type': m.group('return_type').strip(), + 'name': m.group('name').strip(), + 'params': [arg.strip() for arg in m.group('params').split(',')]}) + return signatures + + +def WriteWindowsDefFile(module_name, signatures, outfile): + """Writes a windows def file to the given output file object. + + The def file format is basically a list of function names. Generation is + simple. After outputting the LIBRARY and EXPORTS lines, print out each + function name, one to a line, preceeded by 2 spaces. + + Args: + module_name: The name of the module we are writing a stub for. + signatures: The list of signature hashes, as produced by ParseSignatures, + to create stubs for. + outfile: File handle to populate with definitions. + """ + outfile.write('LIBRARY %s\n' % module_name) + outfile.write('EXPORTS\n') + + for sig in signatures: + outfile.write(' %s\n' % sig['name']) + + +def QuietRun(args, filter=None, write_to=sys.stdout): + """Invoke |args| as command via subprocess.Popen, filtering lines starting + with |filter|.""" + popen = subprocess.Popen(args, stdout=subprocess.PIPE) + out, _ = popen.communicate() + for line in out.splitlines(): + if not filter or not line.startswith(filter): + write_to.write(line + '\n') + return popen.returncode + + +def CreateWindowsLib(module_name, signatures, intermediate_dir, outdir_path, + machine): + """Creates a windows library file. + + Calling this function will create a lib file in the outdir_path that exports + the signatures passed into the object. A temporary def file will be created + in the intermediate_dir. + + Args: + module_name: The name of the module we are writing a stub for. + signatures: The list of signature hashes, as produced by ParseSignatures, + to create stubs for. + intermediate_dir: The directory where the generated .def files should go. + outdir_path: The directory where generated .lib files should go. + machine: String holding the machine type, 'X86' or 'X64'. + + Raises: + SubprocessError: If invoking the windows "lib" tool fails, this is raised + with the error code. + """ + def_file_path = os.path.join(intermediate_dir, + module_name + '.def') + lib_file_path = os.path.join(outdir_path, + module_name + '.lib') + outfile = open(def_file_path, 'w') + try: + WriteWindowsDefFile(module_name, signatures, outfile) + finally: + outfile.close() + + # Invoke the "lib" program on Windows to create stub .lib files for the + # generated definitions. These .lib files can then be used during + # delayloading of the dynamic libraries. + ret = QuietRun(['lib', '/nologo', + '/machine:' + machine, + '/def:' + def_file_path, + '/out:' + lib_file_path], + filter=' Creating library') + if ret != 0: + raise SubprocessError( + 'Failed creating %s for %s' % (lib_file_path, def_file_path), + ret) + + +class PosixStubWriter(object): + """Creates a file of stub functions for a library that is opened via dlopen. + + Windows provides a function in their compiler known as delay loading, which + effectively generates a set of stub functions for a dynamic library that + delays loading of the dynamic library/resolution of the symbols until one of + the needed functions are accessed. + + In posix, RTLD_LAZY does something similar with DSOs. This is the default + link mode for DSOs. However, even though the symbol is not resolved until + first usage, the DSO must be present at load time of the main binary. + + To simulate the windows delay load procedure, we need to create a set of + stub functions that allow for correct linkage of the main binary, but + dispatch to the dynamically resolved symbol when the module is initialized. + + This class takes a list of function signatures, and generates a set of stub + functions plus initialization code for them. + """ + + def __init__(self, module_name, signatures): + """Initializes PosixStubWriter for this set of signatures and module_name. + + Args: + module_name: The name of the module we are writing a stub for. + signatures: The list of signature hashes, as produced by ParseSignatures, + to create stubs for. + """ + self.signatures = signatures + self.module_name = module_name + + @classmethod + def CStyleIdentifier(cls, identifier): + """Generates a C style identifier. + + The module_name has all invalid identifier characters removed (anything + that's not [_a-zA-Z0-9]) and is run through string.capwords to try + and approximate camel case. + + Args: + identifier: The string with the module name to turn to C-style. + + Returns: + A string that can be used as part of a C identifier. + """ + return string.capwords(re.sub(INVALID_C_IDENT_CHARS, '', identifier)) + + @classmethod + def EnumName(cls, module_name): + """Gets the enum name for the module. + + Takes the module name and creates a suitable enum name. The module_name + is munged to be a valid C identifier then prefixed with the string + "kModule" to generate a Google style enum name. + + Args: + module_name: The name of the module to generate an enum name for. + + Returns: + A string with the name of the enum value representing this module. + """ + return 'kModule%s' % PosixStubWriter.CStyleIdentifier(module_name) + + @classmethod + def IsInitializedName(cls, module_name): + """Gets the name of function that checks initialization of this module. + + The name is in the format IsModuleInitialized. Where "Module" is replaced + with the module name, munged to be a valid C identifier. + + Args: + module_name: The name of the module to generate the function name for. + + Returns: + A string with the name of the initialization check function. + """ + return 'Is%sInitialized' % PosixStubWriter.CStyleIdentifier(module_name) + + @classmethod + def InitializeModuleName(cls, module_name): + """Gets the name of the function that initializes this module. + + The name is in the format InitializeModule. Where "Module" is replaced + with the module name, munged to be a valid C identifier. + + Args: + module_name: The name of the module to generate the function name for. + + Returns: + A string with the name of the initialization function. + """ + return 'Initialize%s' % PosixStubWriter.CStyleIdentifier(module_name) + + @classmethod + def UninitializeModuleName(cls, module_name): + """Gets the name of the function that uninitializes this module. + + The name is in the format UninitializeModule. Where "Module" is replaced + with the module name, munged to be a valid C identifier. + + Args: + module_name: The name of the module to generate the function name for. + + Returns: + A string with the name of the uninitialization function. + """ + return 'Uninitialize%s' % PosixStubWriter.CStyleIdentifier(module_name) + + @classmethod + def StubFunctionPointer(cls, signature): + """Generates a function pointer declaration for the given signature. + + Args: + signature: A signature hash, as produced by ParseSignatures, + representating the function signature. + + Returns: + A string with the declaration of the function pointer for the signature. + """ + return 'static %s (*%s_ptr)(%s) = NULL;' % (signature['return_type'], + signature['name'], + ', '.join(signature['params'])) + + @classmethod + def StubFunction(cls, signature): + """Generates a stub function definition for the given signature. + + The function definitions are created with __attribute__((weak)) so that + they may be overridden by a real static link or mock versions to be used + when testing. + + Args: + signature: A signature hash, as produced by ParseSignatures, + representating the function signature. + + Returns: + A string with the stub function definition. + """ + return_prefix = '' + if signature['return_type'] != 'void': + return_prefix = 'return ' + + # Generate the argument list. + arguments = [re.split('[\*& ]', arg)[-1].strip() for arg in + signature['params']] + arg_list = ', '.join(arguments) + if arg_list == 'void': + arg_list = '' + + if arg_list != '' and len(arguments) > 1 and arguments[-1] == '...': + # If the last argment is ... then this is a variadic function. + if return_prefix != '': + return VARIADIC_STUB_FUNCTION_DEFINITION % { + 'return_type': signature['return_type'], + 'name': signature['name'], + 'params': ', '.join(signature['params']), + 'arg_list': ', '.join(arguments[0:-1]), + 'last_named_arg': arguments[-2]} + else: + return VOID_VARIADIC_STUB_FUNCTION_DEFINITION % { + 'name': signature['name'], + 'params': ', '.join(signature['params']), + 'arg_list': ', '.join(arguments[0:-1]), + 'last_named_arg': arguments[-2]} + else: + # This is a regular function. + return STUB_FUNCTION_DEFINITION % { + 'return_type': signature['return_type'], + 'name': signature['name'], + 'params': ', '.join(signature['params']), + 'return_prefix': return_prefix, + 'arg_list': arg_list} + + @classmethod + def WriteImplementationPreamble(cls, header_path, outfile): + """Write the necessary includes for the implementation file. + + Args: + header_path: The path to the header file. + outfile: The file handle to populate. + """ + outfile.write(IMPLEMENTATION_PREAMBLE % header_path) + + @classmethod + def WriteUmbrellaInitializer(cls, module_names, namespace, outfile): + """Writes a single function that will open + initialize each module. + + This intializer will take in an stl map of that lists the correct + dlopen target for each module. The map type is + std::map> which matches one module + to a list of paths to try in dlopen. + + This function is an all-or-nothing function. If any module fails to load, + all other modules are dlclosed, and the function returns. Though it is + not enforced, this function should only be called once. + + Args: + module_names: A list with the names of the modules in this stub file. + namespace: The namespace these functions should be in. + outfile: The file handle to populate with pointer definitions. + """ + outfile.write(UMBRELLA_INITIALIZER_START % namespace) + outfile.write(UMBRELLA_INITIALIZER_CLEANUP_FUNCTION) + + # Create the initializaiton function that calls all module initializers, + # checks if they succeeded, and backs out module loads on an error. + outfile.write(UMBRELLA_INITIALIZER_INITIALIZE_FUNCTION_START) + outfile.write( + '\n // Initialize each module if we have not already failed.\n') + for module in module_names: + outfile.write(' %s(opened_libraries[%s]);\n' % + (PosixStubWriter.InitializeModuleName(module), + PosixStubWriter.EnumName(module))) + outfile.write('\n') + + # Output code to check the initialization status, clean up on error. + initializer_checks = ['!%s()' % PosixStubWriter.IsInitializedName(name) + for name in module_names] + uninitializers = ['%s()' % PosixStubWriter.UninitializeModuleName(name) + for name in module_names] + outfile.write(UMBRELLA_INITIALIZER_CHECK_AND_CLEANUP % { + 'conditional': ' ||\n '.join(initializer_checks), + 'uninitializers': ';\n '.join(uninitializers)}) + outfile.write('\n} // namespace %s\n' % namespace) + + @classmethod + def WriteHeaderContents(cls, module_names, namespace, header_guard, outfile): + """Writes a header file for the stub file generated for module_names. + + The header file exposes the following: + 1) An enum, StubModules, listing with an entry for each enum. + 2) A typedef for a StubPathMap allowing for specification of paths to + search for each module. + 3) The IsInitialized/Initialize/Uninitialize functions for each module. + 4) An umbrella initialize function for all modules. + + Args: + module_names: A list with the names of each module in this stub file. + namespace: The namespace these functions should be in. + header_guard: The macro to use as our header guard. + outfile: The output handle to populate. + """ + outfile.write(STUB_HEADER_PREAMBLE % + {'guard_name': header_guard, 'namespace': namespace}) + + # Generate the Initializer protoypes for each module. + outfile.write('// Individual module initializer functions.\n') + for name in module_names: + outfile.write(MODULE_FUNCTION_PROTOTYPES % { + 'is_initialized': PosixStubWriter.IsInitializedName(name), + 'initialize': PosixStubWriter.InitializeModuleName(name), + 'uninitialize': PosixStubWriter.UninitializeModuleName(name)}) + + # Generate the enum for umbrella initializer. + outfile.write(UMBRELLA_ENUM_START) + outfile.write(' %s = 0,\n' % PosixStubWriter.EnumName(module_names[0])) + for name in module_names[1:]: + outfile.write(' %s,\n' % PosixStubWriter.EnumName(name)) + outfile.write(UMBRELLA_ENUM_END) + + outfile.write(UMBRELLA_INITIALIZER_PROTOTYPE) + outfile.write(STUB_HEADER_CLOSER % { + 'namespace': namespace, 'guard_name': + header_guard}) + + def WriteImplementationContents(self, namespace, outfile): + """Given a file handle, write out the stub definitions for this module. + + Args: + namespace: The namespace these functions should be in. + outfile: The file handle to populate. + """ + outfile.write(IMPLEMENTATION_CONTENTS_C_START) + self.WriteFunctionPointers(outfile) + self.WriteStubFunctions(outfile) + outfile.write(IMPLEMENTATION_CONTENTS_C_END) + + outfile.write(NAMESPACE_START % namespace) + self.WriteModuleInitializeFunctions(outfile) + outfile.write(NAMESPACE_END % namespace) + + def WriteFunctionPointers(self, outfile): + """Write the function pointer declarations needed by the stubs. + + We need function pointers to hold the actual location of the function + implementation returned by dlsym. This function outputs a pointer + definition for each signature in the module. + + Pointers will be named with the following pattern "FuntionName_ptr". + + Args: + outfile: The file handle to populate with pointer definitions. + """ + outfile.write(FUNCTION_POINTER_SECTION_COMMENT) + + for sig in self.signatures: + outfile.write('%s\n' % PosixStubWriter.StubFunctionPointer(sig)) + outfile.write('\n') + + def WriteStubFunctions(self, outfile): + """Write the function stubs to handle dispatching to real implementations. + + Functions that have a return type other than void will look as follows: + + ReturnType FunctionName(A a) { + return FunctionName_ptr(a); + } + + Functions with a return type of void will look as follows: + + void FunctionName(A a) { + FunctionName_ptr(a); + } + + Args: + outfile: The file handle to populate. + """ + outfile.write('// Stubs that dispatch to the real implementations.\n') + for sig in self.signatures: + outfile.write('%s\n' % PosixStubWriter.StubFunction(sig)) + + def WriteModuleInitializeFunctions(self, outfile): + """Write functions to initialize/query initlialization of the module. + + This creates 2 functions IsModuleInitialized and InitializeModule where + "Module" is replaced with the module name, first letter capitalized. + + The InitializeModule function takes a handle that is retrieved from dlopen + and attempts to assign each function pointer above via dlsym. + + The IsModuleInitialized returns true if none of the required functions + pointers are NULL. + + Args: + outfile: The file handle to populate. + """ + ptr_names = ['%s_ptr' % sig['name'] for sig in self.signatures] + + # Construct the conditional expression to check the initialization of + # all the function pointers above. It should generate a conjuntion + # with each pointer on its own line, indented by six spaces to match + # the indentation level of MODULE_INITIALIZATION_CHECK_FUNCTION. + initialization_conditional = ' &&\n '.join(ptr_names) + + outfile.write(MODULE_INITIALIZATION_CHECK_FUNCTION % ( + PosixStubWriter.IsInitializedName(self.module_name), + initialization_conditional)) + + # Create function that initializes the module. + outfile.write(MODULE_INITIALIZE_START % + PosixStubWriter.InitializeModuleName(self.module_name)) + for sig in self.signatures: + outfile.write(STUB_POINTER_INITIALIZER % { + 'name': sig['name'], + 'return_type': sig['return_type'], + 'parameters': ', '.join(sig['params'])}) + outfile.write(MODULE_INITIALIZE_END) + + # Create function that uninitializes the module (sets all pointers to + # NULL). + outfile.write(MODULE_UNINITIALIZE_START % + PosixStubWriter.UninitializeModuleName(self.module_name)) + for sig in self.signatures: + outfile.write(' %s_ptr = NULL;\n' % sig['name']) + outfile.write(MODULE_UNINITIALIZE_END) + + +def CreateOptionParser(): + """Creates an OptionParser for the configuration options of script. + + Returns: + A OptionParser object. + """ + parser = optparse.OptionParser(usage='usage: %prog [options] input') + parser.add_option('-o', + '--output', + dest='out_dir', + default=None, + help='Output location.') + parser.add_option('-i', + '--intermediate_dir', + dest='intermediate_dir', + default=None, + help=('Location of intermediate files. Ignored for %s type' + % FILE_TYPE_WIN_DEF)) + parser.add_option('-t', + '--type', + dest='type', + default=None, + help=('Type of file. Valid types are "%s" or "%s" or "%s" ' + 'or "%s"' % + (FILE_TYPE_POSIX_STUB, FILE_TYPE_WIN_X86, + FILE_TYPE_WIN_X64, FILE_TYPE_WIN_DEF))) + parser.add_option('-s', + '--stubfile_name', + dest='stubfile_name', + default=None, + help=('Name of posix_stubs output file. Only valid with ' + '%s type.' % FILE_TYPE_POSIX_STUB)) + parser.add_option('-p', + '--path_from_source', + dest='path_from_source', + default=None, + help=('The relative path from the project root that the ' + 'generated file should consider itself part of (eg. ' + 'third_party/ffmpeg). This is used to generate the ' + 'header guard and namespace for our initializer ' + 'functions and does NOT affect the physical output ' + 'location of the file like -o does. Ignored for ' + '%s and %s types.' % + (FILE_TYPE_WIN_X86, FILE_TYPE_WIN_X64))) + parser.add_option('-e', + '--extra_stub_header', + dest='extra_stub_header', + default=None, + help=('File to insert after the system includes in the ' + 'generated stub implemenation file. Ignored for ' + '%s and %s types.' % + (FILE_TYPE_WIN_X86, FILE_TYPE_WIN_X64))) + parser.add_option('-m', + '--module_name', + dest='module_name', + default=None, + help=('Name of output DLL or LIB for DEF creation using ' + '%s type.' % FILE_TYPE_WIN_DEF)) + + return parser + + +def ParseOptions(): + """Parses the options and terminates program if they are not sane. + + Returns: + The pair (optparse.OptionValues, [string]), that is the output of + a successful call to parser.parse_args(). + """ + parser = CreateOptionParser() + options, args = parser.parse_args() + + if not args: + parser.error('No inputs specified') + + if options.out_dir is None: + parser.error('Output location not specified') + + if (options.type not in + [FILE_TYPE_WIN_X86, FILE_TYPE_WIN_X64, FILE_TYPE_POSIX_STUB, + FILE_TYPE_WIN_DEF]): + parser.error('Invalid output file type: %s' % options.type) + + if options.type == FILE_TYPE_POSIX_STUB: + if options.stubfile_name is None: + parser.error('Output file name needed for %s' % FILE_TYPE_POSIX_STUB) + if options.path_from_source is None: + parser.error('Path from source needed for %s' % FILE_TYPE_POSIX_STUB) + + if options.type == FILE_TYPE_WIN_DEF: + if options.module_name is None: + parser.error('Module name needed for %s' % FILE_TYPE_WIN_DEF) + + return options, args + + +def EnsureDirExists(dir): + """Creates a directory. Does not use the more obvious 'if not exists: create' + to avoid race with other invocations of the same code, which will error out + on makedirs if another invocation has succeeded in creating the directory + since the existence check.""" + try: + os.makedirs(dir) + except: + if not os.path.isdir(dir): + raise + + +def CreateOutputDirectories(options): + """Creates the intermediate and final output directories. + + Given the parsed options, create the intermediate and final output + directories if they do not exist. Returns the paths to both directories + as a pair. + + Args: + options: An OptionParser.OptionValues object with the parsed options. + + Returns: + The pair (out_dir, intermediate_dir), both of which are strings. + """ + out_dir = os.path.normpath(options.out_dir) + intermediate_dir = os.path.normpath(options.intermediate_dir) + if intermediate_dir is None: + intermediate_dir = out_dir + + EnsureDirExists(out_dir) + EnsureDirExists(intermediate_dir) + + return out_dir, intermediate_dir + + +def CreateWindowsLibForSigFiles(sig_files, out_dir, intermediate_dir, machine): + """For each signature file, create a windows lib. + + Args: + sig_files: Array of strings with the paths to each signature file. + out_dir: String holding path to directory where the generated libs go. + intermediate_dir: String holding path to directory generated intermdiate + artifacts. + machine: String holding the machine type, 'X86' or 'X64'. + """ + for input_path in sig_files: + infile = open(input_path, 'r') + try: + signatures = ParseSignatures(infile) + module_name = ExtractModuleName(os.path.basename(input_path)) + CreateWindowsLib(module_name, signatures, intermediate_dir, out_dir, + machine) + finally: + infile.close() + + +def CreateWindowsDefForSigFiles(sig_files, out_dir, module_name): + """For all signature files, create a single windows def file. + + Args: + sig_files: Array of strings with the paths to each signature file. + out_dir: String holding path to directory where the generated def goes. + module_name: Name of the output DLL or LIB which will link in the def file. + """ + signatures = [] + for input_path in sig_files: + infile = open(input_path, 'r') + try: + signatures += ParseSignatures(infile) + finally: + infile.close() + + def_file_path = os.path.join( + out_dir, os.path.splitext(os.path.basename(module_name))[0] + '.def') + outfile = open(def_file_path, 'w') + + try: + WriteWindowsDefFile(module_name, signatures, outfile) + finally: + outfile.close() + + +def CreatePosixStubsForSigFiles(sig_files, stub_name, out_dir, + intermediate_dir, path_from_source, + extra_stub_header): + """Create a posix stub library with a module for each signature file. + + Args: + sig_files: Array of strings with the paths to each signature file. + stub_name: String with the basename of the generated stub file. + out_dir: String holding path to directory for the .h files. + intermediate_dir: String holding path to directory for the .cc files. + path_from_source: String with relative path of generated files from the + project root. + extra_stub_header: String with path to file of extra lines to insert + into the generated header for the stub library. + """ + header_base_name = stub_name + '.h' + header_path = os.path.join(out_dir, header_base_name) + impl_path = os.path.join(intermediate_dir, stub_name + '.cc') + + module_names = [ExtractModuleName(path) for path in sig_files] + namespace = path_from_source.replace('/', '_').lower() + header_guard = '%s_' % namespace.upper() + header_include_path = os.path.join(path_from_source, header_base_name) + + # First create the implementation file. + impl_file = open(impl_path, 'w') + try: + # Open the file, and create the preamble which consists of a file + # header plus any necessary includes. + PosixStubWriter.WriteImplementationPreamble(header_include_path, + impl_file) + if extra_stub_header is not None: + extra_header_file = open(extra_stub_header, 'r') + try: + impl_file.write('\n') + for line in extra_header_file: + impl_file.write(line) + impl_file.write('\n') + finally: + extra_header_file.close() + + # For each signature file, generate the stub population functions + # for that file. Each file represents one module. + for input_path in sig_files: + name = ExtractModuleName(input_path) + infile = open(input_path, 'r') + try: + signatures = ParseSignatures(infile) + finally: + infile.close() + writer = PosixStubWriter(name, signatures) + writer.WriteImplementationContents(namespace, impl_file) + + # Lastly, output the umbrella function for the file. + PosixStubWriter.WriteUmbrellaInitializer(module_names, namespace, + impl_file) + finally: + impl_file.close() + + # Then create the associated header file. + header_file = open(header_path, 'w') + try: + PosixStubWriter.WriteHeaderContents(module_names, namespace, + header_guard, header_file) + finally: + header_file.close() + + +def main(): + options, args = ParseOptions() + out_dir, intermediate_dir = CreateOutputDirectories(options) + + if options.type == FILE_TYPE_WIN_X86: + CreateWindowsLibForSigFiles(args, out_dir, intermediate_dir, 'X86') + elif options.type == FILE_TYPE_WIN_X64: + CreateWindowsLibForSigFiles(args, out_dir, intermediate_dir, 'X64') + elif options.type == FILE_TYPE_POSIX_STUB: + CreatePosixStubsForSigFiles(args, options.stubfile_name, out_dir, + intermediate_dir, options.path_from_source, + options.extra_stub_header) + elif options.type == FILE_TYPE_WIN_DEF: + CreateWindowsDefForSigFiles(args, out_dir, options.module_name) + + +if __name__ == '__main__': + main() diff --git a/tools/generate_stubs/generate_stubs_unittest.py b/tools/generate_stubs/generate_stubs_unittest.py new file mode 100755 index 0000000000..13a2bc15e5 --- /dev/null +++ b/tools/generate_stubs/generate_stubs_unittest.py @@ -0,0 +1,297 @@ +#!/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. + +"""Unittest for the generate_stubs.py. + +Since generate_stubs.py is a code generator, it is hard to do a very good +test. Instead of creating a golden-file test, which might be flakey, this +test elects instead to verify that various components "exist" within the +generated file as a sanity check. In particular, there is a simple hit +test to make sure that umbrella functions, etc., do try and include every +function they are responsible for invoking. Missing an invocation is quite +easily missed. + +There is no attempt to verify ordering of different components, or whether +or not those components are going to parse incorrectly because of prior +errors or positioning. Most of that should be caught really fast anyways +during any attempt to use a badly behaving script. +""" + +import generate_stubs as gs +import re +import StringIO +import sys +import unittest + + +def _MakeSignature(return_type, name, params): + return {'return_type': return_type, + 'name': name, + 'params': params} + + +SIMPLE_SIGNATURES = [ + ('int foo(int a)', _MakeSignature('int', 'foo', ['int a'])), + ('int bar(int a, double b)', _MakeSignature('int', 'bar', + ['int a', 'double b'])), + ('int baz(void)', _MakeSignature('int', 'baz', ['void'])), + ('void quux(void)', _MakeSignature('void', 'quux', ['void'])), + ('void waldo(void);', _MakeSignature('void', 'waldo', ['void'])), + ('int corge(void);', _MakeSignature('int', 'corge', ['void'])), + ] + +TRICKY_SIGNATURES = [ + ('const struct name *foo(int a, struct Test* b); ', + _MakeSignature('const struct name *', + 'foo', + ['int a', 'struct Test* b'])), + ('const struct name &foo(int a, struct Test* b);', + _MakeSignature('const struct name &', + 'foo', + ['int a', 'struct Test* b'])), + ('const struct name &_foo(int a, struct Test* b);', + _MakeSignature('const struct name &', + '_foo', + ['int a', 'struct Test* b'])), + ('struct name const * const _foo(int a, struct Test* b) ' + '__attribute__((inline));', + _MakeSignature('struct name const * const', + '_foo', + ['int a', 'struct Test* b'])) + ] + +INVALID_SIGNATURES = ['I am bad', 'Seriously bad(', ';;;'] + + +class GenerateStubModuleFunctionsUnittest(unittest.TestCase): + def testExtractModuleName(self): + self.assertEqual('somefile-2', gs.ExtractModuleName('somefile-2.ext')) + + def testParseSignatures_EmptyFile(self): + # Empty file just generates empty signatures. + infile = StringIO.StringIO() + signatures = gs.ParseSignatures(infile) + self.assertEqual(0, len(signatures)) + + def testParseSignatures_SimpleSignatures(self): + file_contents = '\n'.join([x[0] for x in SIMPLE_SIGNATURES]) + infile = StringIO.StringIO(file_contents) + signatures = gs.ParseSignatures(infile) + self.assertEqual(len(SIMPLE_SIGNATURES), len(signatures)) + + # We assume signatures are in order. + for i in xrange(len(SIMPLE_SIGNATURES)): + self.assertEqual(SIMPLE_SIGNATURES[i][1], signatures[i], + msg='Expected %s\nActual %s\nFor %s' % + (SIMPLE_SIGNATURES[i][1], + signatures[i], + SIMPLE_SIGNATURES[i][0])) + + def testParseSignatures_TrickySignatures(self): + file_contents = '\n'.join([x[0] for x in TRICKY_SIGNATURES]) + infile = StringIO.StringIO(file_contents) + signatures = gs.ParseSignatures(infile) + self.assertEqual(len(TRICKY_SIGNATURES), len(signatures)) + + # We assume signatures are in order. + for i in xrange(len(TRICKY_SIGNATURES)): + self.assertEqual(TRICKY_SIGNATURES[i][1], signatures[i], + msg='Expected %s\nActual %s\nFor %s' % + (TRICKY_SIGNATURES[i][1], + signatures[i], + TRICKY_SIGNATURES[i][0])) + + def testParseSignatures_InvalidSignatures(self): + for i in INVALID_SIGNATURES: + infile = StringIO.StringIO(i) + self.assertRaises(gs.BadSignatureError, gs.ParseSignatures, infile) + + def testParseSignatures_CommentsIgnored(self): + my_sigs = [] + my_sigs.append('# a comment') + my_sigs.append(SIMPLE_SIGNATURES[0][0]) + my_sigs.append('# another comment') + my_sigs.append(SIMPLE_SIGNATURES[0][0]) + my_sigs.append('# a third comment') + my_sigs.append(SIMPLE_SIGNATURES[0][0]) + + file_contents = '\n'.join(my_sigs) + infile = StringIO.StringIO(file_contents) + signatures = gs.ParseSignatures(infile) + self.assertEqual(3, len(signatures)) + + +class WindowsLibUnittest(unittest.TestCase): + def testWriteWindowsDefFile(self): + module_name = 'my_module-1' + signatures = [sig[1] for sig in SIMPLE_SIGNATURES] + outfile = StringIO.StringIO() + gs.WriteWindowsDefFile(module_name, signatures, outfile) + contents = outfile.getvalue() + + # Check that the file header is correct. + self.assertTrue(contents.startswith("""LIBRARY %s +EXPORTS +""" % module_name)) + + # Check that the signatures were exported. + for sig in signatures: + pattern = '\n %s\n' % sig['name'] + self.assertTrue(re.search(pattern, contents), + msg='Expected match of "%s" in %s' % (pattern, contents)) + + def testQuietRun(self): + output = StringIO.StringIO() + gs.QuietRun([sys.executable, + '-c', 'print "line 1 and suffix\\nline 2"'], + write_to=output) + self.assertEqual('line 1 and suffix\nline 2\n', output.getvalue()) + + output = StringIO.StringIO() + gs.QuietRun([sys.executable, + '-c', 'print "line 1 and suffix\\nline 2"'], + filter='line 1', write_to=output) + self.assertEqual('line 2\n', output.getvalue()) + + +class PosixStubWriterUnittest(unittest.TestCase): + def setUp(self): + self.module_name = 'my_module-1' + self.signatures = [sig[1] for sig in SIMPLE_SIGNATURES] + self.out_dir = 'out_dir' + self.writer = gs.PosixStubWriter(self.module_name, self.signatures) + + def testEnumName(self): + self.assertEqual('kModuleMy_module1', + gs.PosixStubWriter.EnumName(self.module_name)) + + def testIsInitializedName(self): + self.assertEqual('IsMy_module1Initialized', + gs.PosixStubWriter.IsInitializedName(self.module_name)) + + def testInitializeModuleName(self): + self.assertEqual( + 'InitializeMy_module1', + gs.PosixStubWriter.InitializeModuleName(self.module_name)) + + def testUninitializeModuleName(self): + self.assertEqual( + 'UninitializeMy_module1', + gs.PosixStubWriter.UninitializeModuleName(self.module_name)) + + def testStubFunctionPointer(self): + self.assertEqual( + 'static int (*foo_ptr)(int a) = NULL;', + gs.PosixStubWriter.StubFunctionPointer(SIMPLE_SIGNATURES[0][1])) + + def testStubFunction(self): + # Test for a signature with a return value and a parameter. + self.assertEqual("""extern int foo(int a) __attribute__((weak)); +int foo(int a) { + return foo_ptr(a); +}""", gs.PosixStubWriter.StubFunction(SIMPLE_SIGNATURES[0][1])) + + # Test for a signature with a void return value and no parameters. + self.assertEqual("""extern void waldo(void) __attribute__((weak)); +void waldo(void) { + waldo_ptr(); +}""", gs.PosixStubWriter.StubFunction(SIMPLE_SIGNATURES[4][1])) + + def testWriteImplemenationContents(self): + outfile = StringIO.StringIO() + self.writer.WriteImplementationContents('my_namespace', outfile) + contents = outfile.getvalue() + + # Verify namespace exists somewhere. + self.assertTrue(contents.find('namespace my_namespace {') != -1) + + # Verify that each signature has an _ptr and a function call in the file. + # Check that the signatures were exported. + for sig in self.signatures: + decl = gs.PosixStubWriter.StubFunctionPointer(sig) + self.assertTrue(contents.find(decl) != -1, + msg='Expected "%s" in %s' % (decl, contents)) + + # Verify that each signature has an stub function generated for it. + for sig in self.signatures: + decl = gs.PosixStubWriter.StubFunction(sig) + self.assertTrue(contents.find(decl) != -1, + msg='Expected "%s" in %s' % (decl, contents)) + + # Find module initializer functions. Make sure all 3 exist. + decl = gs.PosixStubWriter.InitializeModuleName(self.module_name) + self.assertTrue(contents.find(decl) != -1, + msg='Expected "%s" in %s' % (decl, contents)) + decl = gs.PosixStubWriter.UninitializeModuleName(self.module_name) + self.assertTrue(contents.find(decl) != -1, + msg='Expected "%s" in %s' % (decl, contents)) + decl = gs.PosixStubWriter.IsInitializedName(self.module_name) + self.assertTrue(contents.find(decl) != -1, + msg='Expected "%s" in %s' % (decl, contents)) + + def testWriteHeaderContents(self): + # Data for header generation. + module_names = ['oneModule', 'twoModule'] + + # Make the header. + outfile = StringIO.StringIO() + self.writer.WriteHeaderContents(module_names, 'my_namespace', 'GUARD_', + outfile) + contents = outfile.getvalue() + + # Check for namespace and header guard. + self.assertTrue(contents.find('namespace my_namespace {') != -1) + self.assertTrue(contents.find('#ifndef GUARD_') != -1) + + # Check for umbrella initializer. + self.assertTrue(contents.find('InitializeStubs(') != -1) + + # Check per-module declarations. + for name in module_names: + # Check for enums. + decl = gs.PosixStubWriter.EnumName(name) + self.assertTrue(contents.find(decl) != -1, + msg='Expected "%s" in %s' % (decl, contents)) + + # Check for module initializer functions. + decl = gs.PosixStubWriter.IsInitializedName(name) + self.assertTrue(contents.find(decl) != -1, + msg='Expected "%s" in %s' % (decl, contents)) + decl = gs.PosixStubWriter.InitializeModuleName(name) + self.assertTrue(contents.find(decl) != -1, + msg='Expected "%s" in %s' % (decl, contents)) + decl = gs.PosixStubWriter.UninitializeModuleName(name) + self.assertTrue(contents.find(decl) != -1, + msg='Expected "%s" in %s' % (decl, contents)) + + def testWriteUmbrellaInitializer(self): + # Data for header generation. + module_names = ['oneModule', 'twoModule'] + + # Make the header. + outfile = StringIO.StringIO() + self.writer.WriteUmbrellaInitializer(module_names, 'my_namespace', outfile) + contents = outfile.getvalue() + + # Check for umbrella initializer declaration. + self.assertTrue(contents.find('bool InitializeStubs(') != -1) + + # If the umbrella initializer is correctly written, each module will have + # its initializer called, checked, and uninitialized on failure. Sanity + # check that here. + for name in module_names: + # Check for module initializer functions. + decl = gs.PosixStubWriter.IsInitializedName(name) + self.assertTrue(contents.find(decl) != -1, + msg='Expected "%s" in %s' % (decl, contents)) + decl = gs.PosixStubWriter.InitializeModuleName(name) + self.assertTrue(contents.find(decl) != -1, + msg='Expected "%s" in %s' % (decl, contents)) + decl = gs.PosixStubWriter.UninitializeModuleName(name) + self.assertTrue(contents.find(decl) != -1, + msg='Expected "%s" in %s' % (decl, contents)) + +if __name__ == '__main__': + unittest.main() diff --git a/tools/git/README b/tools/git/README new file mode 100644 index 0000000000..7f8e363dee --- /dev/null +++ b/tools/git/README @@ -0,0 +1,16 @@ +This directory contains some helpful Git tools. + +post-checkout and post-merge +============================ +These hooks warn you about DEPS modifications so you will remember +to run "gclient sync". + +To install these Git hooks, create symlinks like so: + ln -s $(pwd)/post-checkout $(git rev-parse --git-dir)/hooks + ln -s $(pwd)/post-merge $(git rev-parse --git-dir)/hooks + + +git-graph +========= +Create a graph of the recent history of occurences of a grep +expression in the project. diff --git a/tools/git/for-all-touched-files.py b/tools/git/for-all-touched-files.py new file mode 100755 index 0000000000..a7e784ade3 --- /dev/null +++ b/tools/git/for-all-touched-files.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python +# Copyright (c) 2011 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" + Invokes the specified (quoted) command for all files modified + between the current git branch and the specified branch or commit. + + The special token [[FILENAME]] (or whatever you choose using the -t + flag) is replaced with each of the filenames of new or modified files. + + Deleted files are not included. Neither are untracked files. + +Synopsis: + %prog [-b BRANCH] [-d] [-x EXTENSIONS|-c] [-t TOKEN] QUOTED_COMMAND + +Examples: + %prog -x gyp,gypi "tools/format_xml.py [[FILENAME]]" + %prog -c "tools/sort-headers.py [[FILENAME]]" + %prog -t "~~BINGO~~" "echo I modified ~~BINGO~~" +""" + +import optparse +import os +import subprocess +import sys + + +# List of C++-like source file extensions. +_CPP_EXTENSIONS = ('h', 'hh', 'hpp', 'c', 'cc', 'cpp', 'cxx', 'mm',) + + +def GitShell(args, ignore_return=False): + """A shell invocation suitable for communicating with git. Returns + output as list of lines, raises exception on error. + """ + job = subprocess.Popen(args, + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + (out, err) = job.communicate() + if job.returncode != 0 and not ignore_return: + print out + raise Exception("Error %d running command %s" % ( + job.returncode, args)) + return out.split('\n') + + +def FilenamesFromGit(branch_name, extensions): + """Provides a list of all new and modified files listed by [git diff + branch_name] where branch_name can be blank to get a diff of the + workspace. + + Excludes deleted files. + + If extensions is not an empty list, include only files with one of + the extensions on the list. + """ + lines = GitShell('git diff --stat=600,500 %s' % branch_name) + filenames = [] + for line in lines: + line = line.lstrip() + # Avoid summary line, and files that have been deleted (no plus). + if line.find('|') != -1 and line.find('+') != -1: + filename = line.split()[0] + if filename: + filename = filename.rstrip() + ext = filename.rsplit('.')[-1] + if not extensions or ext in extensions: + filenames.append(filename) + return filenames + + +def ForAllTouchedFiles(branch_name, extensions, token, command): + """For each new or modified file output by [git diff branch_name], + run command with token replaced with the filename. If extensions is + not empty, do this only for files with one of the extensions in that + list. + """ + filenames = FilenamesFromGit(branch_name, extensions) + for filename in filenames: + os.system(command.replace(token, filename)) + + +def main(): + parser = optparse.OptionParser(usage=__doc__) + parser.add_option('-x', '--extensions', default='', dest='extensions', + help='Limits to files with given extensions ' + '(comma-separated).') + parser.add_option('-c', '--cpp', default=False, action='store_true', + dest='cpp_only', + help='Runs your command only on C++-like source files.') + parser.add_option('-t', '--token', default='[[FILENAME]]', dest='token', + help='Sets the token to be replaced for each file ' + 'in your command (default [[FILENAME]]).') + parser.add_option('-b', '--branch', default='origin/master', dest='branch', + help='Sets what to diff to (default origin/master). Set ' + 'to empty to diff workspace against HEAD.') + opts, args = parser.parse_args() + + if not args: + parser.print_help() + sys.exit(1) + + extensions = opts.extensions + if opts.cpp_only: + extensions = _CPP_EXTENSIONS + + ForAllTouchedFiles(opts.branch, extensions, opts.token, args[0]) + + +if __name__ == '__main__': + main() diff --git a/tools/git/git-diff-ide.py b/tools/git/git-diff-ide.py new file mode 100755 index 0000000000..405d270eba --- /dev/null +++ b/tools/git/git-diff-ide.py @@ -0,0 +1,93 @@ +#!/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. + +""" + Invokes git diff [args...] and inserts file:line in front of each line of diff + output where possible. + + This is useful from an IDE that allows you to double-click lines that begin + with file:line to open and jump to that point in the file. + +Synopsis: + %prog [git diff args...] + +Examples: + %prog + %prog HEAD +""" + +import subprocess +import sys + + +def GitShell(args, ignore_return=False): + """A shell invocation suitable for communicating with git. Returns + output as list of lines, raises exception on error. + """ + job = subprocess.Popen(args, + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + (out, err) = job.communicate() + if job.returncode != 0 and not ignore_return: + print out + raise Exception("Error %d running command %s" % ( + job.returncode, args)) + return out.split('\n') + + +def PrintGitDiff(extra_args): + """Outputs git diff extra_args with file:line inserted into relevant lines.""" + current_file = ''; + line_num = 0; + lines = GitShell('git diff %s' % ' '.join(extra_args)) + for line in lines: + # Pass-through lines: + # diff --git a/file.c b/file.c + # index 0e38c2d..8cd69ae 100644 + # --- a/file.c + if (line.startswith('diff ') or + line.startswith('index ') or + line.startswith('--- ')): + print line + continue + + # Get the filename from the +++ line: + # +++ b/file.c + if line.startswith('+++ '): + # Filename might be /dev/null or a/file or b/file. + # Skip the first two characters unless it starts with /. + current_file = line[4:] if line[4] == '/' else line[6:] + print line + continue + + # Update line number from the @@ lines: + # @@ -41,9 +41,9 @@ def MyFunc(): + # ^^ + if line.startswith('@@ '): + _, old_nr, new_nr, _ = line.split(' ', 3) + line_num = int(new_nr.split(',')[0]) + print line + continue + print current_file + ':' + repr(line_num) + ':' + line + + # Increment line number for lines that start with ' ' or '+': + # @@ -41,4 +41,4 @@ def MyFunc(): + # file.c:41: // existing code + # file.c:42: // existing code + # file.c:43:-// deleted code + # file.c:43:-// deleted code + # file.c:43:+// inserted code + # file.c:44:+// inserted code + if line.startswith(' ') or line.startswith('+'): + line_num += 1 + + +def main(): + PrintGitDiff(sys.argv[1:]) + + +if __name__ == '__main__': + main() diff --git a/tools/git/git-utils.sh b/tools/git/git-utils.sh new file mode 100755 index 0000000000..608d27aa26 --- /dev/null +++ b/tools/git/git-utils.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +TPUT=$(which tput 2>/dev/null) +if test -x "$TPUT" && $TPUT setaf 1 >/dev/null ; then + RED="$($TPUT setaf 1)" + NORMAL="$($TPUT op)" +else + RED= + NORMAL= +fi + +warn() { + echo "${RED}WARNING:${NORMAL} $@" +} diff --git a/tools/git/graph.sh b/tools/git/graph.sh new file mode 100755 index 0000000000..800a52b86c --- /dev/null +++ b/tools/git/graph.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# 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. + +about="Given a grep expression, creates a graph of occurrences of that +expression in the recent history of the tree. + +Prerequisites: git and GNU R (apt-get install r-base). +" + +set -e + +target="$1" + +if [ -z $target ]; then + echo "usage: $0 " + echo + echo "$about" + exit 1 +fi + +datafile=$(mktemp -t tmp.XXXXXXXXXX) +trap "rm -f $datafile" EXIT + +echo 'ago count' > $datafile +for ago in {90..0}; do + commit=$(git rev-list -1 --until="$ago days ago" origin/trunk) + git checkout -q -f $commit + count=$(git grep -E "$target" -- '*.cc' '*.h' '*.m' '*.mm' | wc -l) + echo "-$ago $count" >> $datafile + echo -n '.' +done + +R CMD BATCH <(cat <"])' + replacement: '\1chrome/browser/ui/browser/browser.h\3' + file_globs: ['*.cc', '*.h', '*.m', '*.mm'] + + Returns the list of files modified. + + Raises an exception on error. + """ + # Posix extended regular expressions do not reliably support the "\s" + # shorthand. + posix_ere_original = re.sub(r"\\s", "[[:space:]]", original) + if sys.platform == 'win32': + posix_ere_original = posix_ere_original.replace('"', '""') + out, err = subprocess.Popen( + ['git', 'grep', '-E', '--name-only', posix_ere_original, + '--'] + file_globs, + stdout=subprocess.PIPE, + shell=_USE_SHELL).communicate() + referees = out.splitlines() + + for referee in referees: + with open(referee) as f: + original_contents = f.read() + contents = re.sub(original, replacement, original_contents) + if contents == original_contents: + raise Exception('No change in file %s although matched in grep' % + referee) + with open(referee, 'wb') as f: + f.write(contents) + + return referees + + +def main(): + parser = optparse.OptionParser(usage=''' +(1) %prog REGEXP REPLACEMENT +REGEXP uses full Python regexp syntax. REPLACEMENT can use back-references. + +(2) %prog -i + should contain a list (in Python syntax) of +[REGEXP, REPLACEMENT, [GLOBS]] lists, e.g.: +[ + [r"(foo|bar)", r"\1baz", ["*.cc", "*.h"]], + ["54", "42"], +] +As shown above, [GLOBS] can be omitted for a given search-replace list, in which +case the corresponding search-replace will use the globs specified on the +command line.''') + parser.add_option('-d', action='store_true', + dest='use_default_glob', + help='Perform the change on C++ and Objective-C(++) source ' + 'and header files.') + parser.add_option('-f', action='store_true', + dest='force_unsafe_run', + help='Perform the run even if there are uncommitted local ' + 'changes.') + parser.add_option('-g', action='append', + type='string', + default=[], + metavar="", + dest='user_supplied_globs', + help='Perform the change on the specified glob. Can be ' + 'specified multiple times, in which case the globs are ' + 'unioned.') + parser.add_option('-i', "--input_file", + type='string', + action='store', + default='', + metavar="", + dest='input_filename', + help='Read arguments from rather than the command ' + 'line. NOTE: To be sure of regular expressions being ' + 'interpreted correctly, use raw strings.') + opts, args = parser.parse_args() + if opts.use_default_glob and opts.user_supplied_globs: + print '"-d" and "-g" cannot be used together' + parser.print_help() + return 1 + + from_file = opts.input_filename != "" + if (from_file and len(args) != 0) or (not from_file and len(args) != 2): + parser.print_help() + return 1 + + if not opts.force_unsafe_run: + out, err = subprocess.Popen(['git', 'status', '--porcelain'], + stdout=subprocess.PIPE, + shell=_USE_SHELL).communicate() + if out: + print 'ERROR: This tool does not print any confirmation prompts,' + print 'so you should only run it with a clean staging area and cache' + print 'so that reverting a bad find/replace is as easy as running' + print ' git checkout -- .' + print '' + print 'To override this safeguard, pass the -f flag.' + return 1 + + global_file_globs = ['*.*'] + if opts.use_default_glob: + global_file_globs = ['*.cc', '*.h', '*.m', '*.mm'] + elif opts.user_supplied_globs: + global_file_globs = opts.user_supplied_globs + + # Construct list of search-replace tasks. + search_replace_tasks = [] + if opts.input_filename == '': + original = args[0] + replacement = args[1] + search_replace_tasks.append([original, replacement, global_file_globs]) + else: + f = open(opts.input_filename) + search_replace_tasks = eval("".join(f.readlines())) + for task in search_replace_tasks: + if len(task) == 2: + task.append(global_file_globs) + f.close() + + for (original, replacement, file_globs) in search_replace_tasks: + print 'File globs: %s' % file_globs + print 'Original: %s' % original + print 'Replacement: %s' % replacement + MultiFileFindReplace(original, replacement, file_globs) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/git/move_source_file.bat b/tools/git/move_source_file.bat new file mode 100755 index 0000000000..bc3d7978e0 --- /dev/null +++ b/tools/git/move_source_file.bat @@ -0,0 +1,6 @@ +@echo off +setlocal +:: This is required with cygwin only. +PATH=%~dp0;%PATH% +set PYTHONDONTWRITEBYTECODE=1 +call python "%~dp0move_source_file.py" %* diff --git a/tools/git/move_source_file.py b/tools/git/move_source_file.py new file mode 100755 index 0000000000..1a953f3c42 --- /dev/null +++ b/tools/git/move_source_file.py @@ -0,0 +1,191 @@ +#!/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. + +"""Moves C++ files to a new location, updating any include paths that +point to them, and re-ordering headers as needed. If multiple source +files are specified, the destination must be a directory (and must end +in a slash). Updates include guards in moved header files. Assumes +Chromium coding style. + +Attempts to update paths used in .gyp(i) files, but does not reorder +or restructure .gyp(i) files in any way. + +Updates full-path references to files in // comments in source files. + +Must run in a git checkout, as it relies on git grep for a fast way to +find files that reference the moved file. +""" + + +import optparse +import os +import re +import subprocess +import sys + +import mffr + +if __name__ == '__main__': + # Need to add the directory containing sort-headers.py to the Python + # classpath. + sys.path.append(os.path.abspath(os.path.join(sys.path[0], '..'))) +sort_headers = __import__('sort-headers') + + +HANDLED_EXTENSIONS = ['.cc', '.mm', '.h', '.hh'] + + +def IsHandledFile(path): + return os.path.splitext(path)[1] in HANDLED_EXTENSIONS + +def MakeDestinationPath(from_path, to_path): + """Given the from and to paths, return a correct destination path. + + The initial destination path may either a full path or a directory, + in which case the path must end with /. Also does basic sanity + checks. + """ + if not IsHandledFile(from_path): + raise Exception('Only intended to move individual source files. (%s)' % + from_path) + dest_extension = os.path.splitext(to_path)[1] + if dest_extension not in HANDLED_EXTENSIONS: + if to_path.endswith('/') or to_path.endswith('\\'): + to_path += os.path.basename(from_path) + else: + raise Exception('Destination must be either full path or end with /.') + return to_path + + +def MoveFile(from_path, to_path): + """Performs a git mv command to move a file from |from_path| to |to_path|. + """ + if not os.system('git mv %s %s' % (from_path, to_path)) == 0: + raise Exception('Fatal: Failed to run git mv command.') + + +def UpdatePostMove(from_path, to_path): + """Given a file that has moved from |from_path| to |to_path|, + updates the moved file's include guard to match the new path and + updates all references to the file in other source files. Also tries + to update references in .gyp(i) files using a heuristic. + """ + # Include paths always use forward slashes. + from_path = from_path.replace('\\', '/') + to_path = to_path.replace('\\', '/') + + if os.path.splitext(from_path)[1] in ['.h', '.hh']: + UpdateIncludeGuard(from_path, to_path) + + # Update include/import references. + files_with_changed_includes = mffr.MultiFileFindReplace( + r'(#(include|import)\s*["<])%s([>"])' % re.escape(from_path), + r'\1%s\3' % to_path, + ['*.cc', '*.h', '*.m', '*.mm']) + + # Reorder headers in files that changed. + for changed_file in files_with_changed_includes: + def AlwaysConfirm(a, b): return True + sort_headers.FixFileWithConfirmFunction(changed_file, AlwaysConfirm, True) + + # Update comments; only supports // comments, which are primarily + # used in our code. + # + # This work takes a bit of time. If this script starts feeling too + # slow, one good way to speed it up is to make the comment handling + # optional under a flag. + mffr.MultiFileFindReplace( + r'(//.*)%s' % re.escape(from_path), + r'\1%s' % to_path, + ['*.cc', '*.h', '*.m', '*.mm']) + + # Update references in .gyp(i) files. + def PathMinusFirstComponent(path): + """foo/bar/baz -> bar/baz""" + parts = re.split(r"[/\\]", path, 1) + if len(parts) == 2: + return parts[1] + else: + return parts[0] + mffr.MultiFileFindReplace( + r'([\'"])%s([\'"])' % re.escape(PathMinusFirstComponent(from_path)), + r'\1%s\2' % PathMinusFirstComponent(to_path), + ['*.gyp*']) + + +def MakeIncludeGuardName(path_from_root): + """Returns an include guard name given a path from root.""" + guard = path_from_root.replace('/', '_') + guard = guard.replace('\\', '_') + guard = guard.replace('.', '_') + guard += '_' + return guard.upper() + + +def UpdateIncludeGuard(old_path, new_path): + """Updates the include guard in a file now residing at |new_path|, + previously residing at |old_path|, with an up-to-date include guard. + + Prints a warning if the update could not be completed successfully (e.g., + because the old include guard was not formatted correctly per Chromium style). + """ + old_guard = MakeIncludeGuardName(old_path) + new_guard = MakeIncludeGuardName(new_path) + + with open(new_path) as f: + contents = f.read() + + new_contents = contents.replace(old_guard, new_guard) + # The file should now have three instances of the new guard: two at the top + # of the file plus one at the bottom for the comment on the #endif. + if new_contents.count(new_guard) != 3: + print ('WARNING: Could not successfully update include guard; perhaps ' + 'old guard is not per style guide? You will have to update the ' + 'include guard manually. (%s)' % new_path) + + with open(new_path, 'w') as f: + f.write(new_contents) + + +def main(): + if not os.path.isdir('.git'): + print 'Fatal: You must run from the root of a git checkout.' + return 1 + + parser = optparse.OptionParser(usage='%prog FROM_PATH... TO_PATH') + parser.add_option('--already_moved', action='store_true', + dest='already_moved', + help='Causes the script to skip moving the file.') + parser.add_option('--no_error_for_non_source_file', action='store_false', + default='True', + dest='error_for_non_source_file', + help='Causes the script to simply print a warning on ' + 'encountering a non-source file rather than raising an ' + 'error.') + opts, args = parser.parse_args() + + if len(args) < 2: + parser.print_help() + return 1 + + if len(args) > 2 and not args[-1].endswith('/'): + print 'Target %s is not a directory.' % args[-1] + print + parser.print_help() + return 1 + + for from_path in args[:len(args)-1]: + if not opts.error_for_non_source_file and not IsHandledFile(from_path): + print '%s does not appear to be a source file, skipping' % (from_path) + continue + to_path = MakeDestinationPath(from_path, args[-1]) + if not opts.already_moved: + MoveFile(from_path, to_path) + UpdatePostMove(from_path, to_path) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/git/post-checkout b/tools/git/post-checkout new file mode 100755 index 0000000000..452eb48eb4 --- /dev/null +++ b/tools/git/post-checkout @@ -0,0 +1,22 @@ +#!/bin/bash +# 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. + +script=$(readlink $0) +source $(dirname ${script:-$0})/git-utils.sh + +old_ref=$1 # Previous HEAD. +new_ref=$2 # Current HEAD. +branch_switch=$3 # Whether we switched branches. + +if [ $old_ref == $new_ref ]; then + if ! git diff-index --quiet HEAD $(git rev-parse --show-cdup)DEPS; then + warn "DEPS has local modifications; do you need to re-run gclient sync?" + fi +else + if git diff-tree $old_ref $new_ref | grep -qs $'\tDEPS$'; then + warn "DEPS has changed; you probably need to re-run gclient sync." + fi +fi + diff --git a/tools/git/post-merge b/tools/git/post-merge new file mode 100755 index 0000000000..8b774ce659 --- /dev/null +++ b/tools/git/post-merge @@ -0,0 +1,12 @@ +#!/bin/bash +# 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. + +script=$(readlink $0) +source $(dirname ${script:-$0})/git-utils.sh + +if git diff-tree ORIG_HEAD HEAD | grep -qs $'\tDEPS$'; then + warn "DEPS has changed; you probably need to re-run gclient sync." +fi + diff --git a/tools/git/update-copyrights.sh b/tools/git/update-copyrights.sh new file mode 100755 index 0000000000..ac69bd53e6 --- /dev/null +++ b/tools/git/update-copyrights.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +echo 'Updating copyrights is no longer necessary.' +echo 'See https://groups.google.com/a/chromium.org/d/msg/chromium-dev/8p4JKV76kig/OiFYFjuZ6nAJ' diff --git a/tools/gn/BUILD.gn b/tools/gn/BUILD.gn new file mode 100644 index 0000000000..17be36c0f4 --- /dev/null +++ b/tools/gn/BUILD.gn @@ -0,0 +1,174 @@ +static_library("gn_lib") { + sources = [ + "binary_target_generator.cc", + "binary_target_generator.h", + "build_settings.cc", + "build_settings.h", + "command_desc.cc", + "command_gen.cc", + "command_help.cc", + "commands.cc", + "commands.h", + "config.cc", + "config.h", + "config_values.cc", + "config_values.h", + "config_values_extractors.cc", + "config_values_extractors.h", + "config_values_generator.cc", + "config_values_generator.h", + "copy_target_generator.cc", + "copy_target_generator.h", + "err.cc", + "err.h", + "escape.cc", + "escape.h", + "file_template.cc", + "file_template.h", + "filesystem_utils.cc", + "filesystem_utils.h", + "functions.cc", + "functions.h", + "functions_target.cc", + "function_exec_script.cc", + "function_process_file_template.cc", + "function_read_file.cc", + "function_set_default_toolchain.cc", + "function_template.cc", + "function_toolchain.cc", + "function_write_file.cc", + "group_target_generator.cc", + "group_target_generator.h", + "import_manager.cc", + "import_manager.h", + "input_conversion.cc", + "input_conversion.h", + "input_file.cc", + "input_file.h", + "input_file_manager.cc", + "input_file_manager.h", + "item.cc", + "item.h", + "item_node.cc", + "item_node.h", + "item_tree.cc", + "item_tree.h", + "label.cc", + "label.h", + "location.h", + "ninja_binary_target_writer.cc", + "ninja_binary_target_writer.h", + "ninja_build_writer.cc", + "ninja_build_writer.h", + "ninja_copy_target_writer.cc", + "ninja_copy_target_writer.h", + "ninja_group_target_writer.cc", + "ninja_group_target_writer.h", + "ninja_helper.cc", + "ninja_helper.h", + "ninja_script_target_writer.cc", + "ninja_script_target_writer.h", + "ninja_target_writer.cc", + "ninja_target_writer.h", + "ninja_toolchain_writer.cc", + "ninja_toolchain_writer.h", + "ninja_writer.cc", + "ninja_writer.h", + "operators.cc", + "operators.h", + "output_file.h", + "parse_tree.cc", + "parse_tree.h", + "parser.cc", + "parser.h", + "path_output.cc", + "path_output.h", + "pattern.cc", + "pattern.h", + "scheduler.cc", + "scheduler.h", + "scope.cc", + "scope.h", + "scope_per_file_provider.cc", + "scope_per_file_provider.h", + "script_target_generator.cc", + "script_target_generator.h", + "script_values.cc", + "script_values.h", + "settings.cc", + "settings.h", + "setup.cc", + "setup.h", + "source_dir.cc", + "source_dir.h", + "source_file.cc", + "source_file.h", + "standard_out.cc", + "standard_out.h", + "string_utils.cc", + "string_utils.h", + "target.cc", + "target.h", + "target_generator.cc", + "target_generator.h", + "target_manager.cc", + "target_manager.h", + "token.cc", + "token.h", + "tokenizer.cc", + "tokenizer.h", + "toolchain.cc", + "toolchain.h", + "toolchain_manager.cc", + "toolchain_manager.h", + "value.cc", + "value.h", + "value_extractors.cc", + "value_extractors.h", + "variables.cc", + "variables.h", + ] + deps = [ + "//base", + "//base/third_party/dynamic_annotations", + ] +} + +executable("gn") { + sources = [ + "gn_main.cc", + ] + deps = [ + ":gn_lib", + ] +} + +test("gn_unittests") { + sources = [ + "escape_unittest.cc", + "file_template_unittest.cc", + "filesystem_utils_unittest.cc", + "input_conversion_unittest.cc", + "label_unittest.cc", + "ninja_helper_unittest.cc", + "parser_unittest.cc", + "path_output_unittest.cc", + "pattern_unittest.cc", + "source_dir_unittest.cc", + "string_utils_unittest.cc", + "target_generator_unittest.cc", + "target_manager_unittest.cc", + "tokenizer_unittest.cc", + ] + deps = [ + ":gn_lib", + "//base:run_all_unittests", + "//base:test_support_base", + "//testing:gtest", + ] +} + +executable("generate_test_gn_data") { + sources = [ "generate_test_gn_data.cc" ] + deps = [ "//base" ] +} diff --git a/tools/gn/OWNERS b/tools/gn/OWNERS new file mode 100644 index 0000000000..06fefbf4ec --- /dev/null +++ b/tools/gn/OWNERS @@ -0,0 +1 @@ +brettw@chromium.org diff --git a/tools/gn/README.txt b/tools/gn/README.txt new file mode 100644 index 0000000000..0a637bf477 --- /dev/null +++ b/tools/gn/README.txt @@ -0,0 +1,7 @@ +GN "Generate Ninja" + +This tool is an experimental metabuildsystem. It is not currently in a state +where it is ready for public consumption. + +It is not currently used in the build and there are currently no plans to +replace GYP. diff --git a/tools/gn/binary_target_generator.cc b/tools/gn/binary_target_generator.cc new file mode 100644 index 0000000000..d7e488a5a6 --- /dev/null +++ b/tools/gn/binary_target_generator.cc @@ -0,0 +1,36 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "tools/gn/binary_target_generator.h" + +#include "tools/gn/config_values_generator.h" +#include "tools/gn/err.h" + +BinaryTargetGenerator::BinaryTargetGenerator(Target* target, + Scope* scope, + const Token& function_token, + Target::OutputType type, + Err* err) + : TargetGenerator(target, scope, function_token, err), + output_type_(type) { +} + +BinaryTargetGenerator::~BinaryTargetGenerator() { +} + +void BinaryTargetGenerator::DoRun() { + target_->set_output_type(output_type_); + + FillSources(); + FillConfigs(); + + // Config values (compiler flags, etc.) set directly on this target. + ConfigValuesGenerator gen(&target_->config_values(), scope_, + function_token_, input_directory_, err_); + gen.Run(); + if (err_->has_error()) + return; + + SetToolchainDependency(); +} diff --git a/tools/gn/binary_target_generator.h b/tools/gn/binary_target_generator.h new file mode 100644 index 0000000000..dacb4974bd --- /dev/null +++ b/tools/gn/binary_target_generator.h @@ -0,0 +1,32 @@ +// 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 TOOLS_GN_BINARY_TARGET_GENERATOR_H_ +#define TOOLS_GN_BINARY_TARGET_GENERATOR_H_ + +#include "base/compiler_specific.h" +#include "tools/gn/target_generator.h" + +// Populates a Target with the values from a binary rule (executable, shared +// library, or static library). +class BinaryTargetGenerator : public TargetGenerator { + public: + BinaryTargetGenerator(Target* target, + Scope* scope, + const Token& function_token, + Target::OutputType type, + Err* err); + virtual ~BinaryTargetGenerator(); + + protected: + virtual void DoRun() OVERRIDE; + + private: + Target::OutputType output_type_; + + DISALLOW_COPY_AND_ASSIGN(BinaryTargetGenerator); +}; + +#endif // TOOLS_GN_BINARY_TARGET_GENERATOR_H_ + diff --git a/tools/gn/build_settings.cc b/tools/gn/build_settings.cc new file mode 100644 index 0000000000..09b4c99589 --- /dev/null +++ b/tools/gn/build_settings.cc @@ -0,0 +1,44 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "tools/gn/build_settings.h" + +#include "tools/gn/filesystem_utils.h" + +BuildSettings::BuildSettings() + : item_tree_(), + target_manager_(this), + toolchain_manager_(this) { +} + +BuildSettings::~BuildSettings() { +} + +void BuildSettings::SetSecondarySourcePath(const SourceDir& d) { + secondary_source_path_ = GetFullPath(d); +} + +void BuildSettings::SetBuildDir(const SourceDir& d) { + build_dir_ = d; + build_to_source_dir_string_ = InvertDir(d); +} + +base::FilePath BuildSettings::GetFullPath(const SourceFile& file) const { + return file.Resolve(root_path_); +} + +base::FilePath BuildSettings::GetFullPath(const SourceDir& dir) const { + return dir.Resolve(root_path_); +} + +base::FilePath BuildSettings::GetFullPathSecondary( + const SourceFile& file) const { + return file.Resolve(secondary_source_path_); +} + +base::FilePath BuildSettings::GetFullPathSecondary( + const SourceDir& dir) const { + return dir.Resolve(secondary_source_path_); +} + diff --git a/tools/gn/build_settings.h b/tools/gn/build_settings.h new file mode 100644 index 0000000000..fe0426674b --- /dev/null +++ b/tools/gn/build_settings.h @@ -0,0 +1,110 @@ +// 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 TOOLS_GN_BUILD_SETTINGS_H_ +#define TOOLS_GN_BUILD_SETTINGS_H_ + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/files/file_path.h" +#include "tools/gn/item_tree.h" +#include "tools/gn/scope.h" +#include "tools/gn/source_dir.h" +#include "tools/gn/source_file.h" +#include "tools/gn/target_manager.h" +#include "tools/gn/toolchain_manager.h" + +// Settings for one build, which is one toplevel output directory. There +// may be multiple Settings objects that refer to this, one for each toolchain. +class BuildSettings { + public: + typedef base::Callback TargetResolvedCallback; + + BuildSettings(); + ~BuildSettings(); + + // Absolute path of the source root on the local system. Everything is + // relative to this. + const base::FilePath& root_path() const { return root_path_; } + void set_root_path(const base::FilePath& r) { root_path_ = r; } + + // When nonempty, specifies a parallel directory higherarchy in which to + // search for buildfiles if they're not found in the root higherarchy. This + // allows us to keep buildfiles in a separate tree during development. + const base::FilePath& secondary_source_path() const { + return secondary_source_path_; + } + void SetSecondarySourcePath(const SourceDir& d); + + // Path of the python executable to run scripts with. + base::FilePath python_path() const { return python_path_; } + void set_python_path(const base::FilePath& p) { python_path_ = p; } + + const SourceFile& build_config_file() const { return build_config_file_; } + void set_build_config_file(const SourceFile& f) { build_config_file_ = f; } + + // The build directory is the root of all output files. The default toolchain + // files go into here, and non-default toolchains will have separate + // toolchain-specific root directories inside this. + const SourceDir& build_dir() const { return build_dir_; } + void SetBuildDir(const SourceDir& dir); + + // The inverse of relative_build_dir, ending with a separator. + // Example: relative_build_dir_ = "out/Debug/" this will be "../../" + const std::string& build_to_source_dir_string() const { + return build_to_source_dir_string_; + } + + // These accessors do not return const objects since the resulting objects + // are threadsafe. In this setting, we use constness primarily to ensure + // that the Settings object is used in a threadsafe manner. Although this + // violates the concept of logical constness, that's less important in our + // application, and actually implementing this in a way that preserves + // logical constness is cumbersome. + ItemTree& item_tree() const { return item_tree_; } + TargetManager& target_manager() const { return target_manager_; } + ToolchainManager& toolchain_manager() const { return toolchain_manager_; } + + // Returns the full absolute OS path cooresponding to the given file in the + // root source tree. + base::FilePath GetFullPath(const SourceFile& file) const; + base::FilePath GetFullPath(const SourceDir& dir) const; + + // Returns the absolute OS path inside the secondary source path. Will return + // an empty FilePath if the secondary source path is empty. When loading a + // buildfile, the GetFullPath should always be consulted first. + base::FilePath GetFullPathSecondary(const SourceFile& file) const; + base::FilePath GetFullPathSecondary(const SourceDir& dir) const; + + // This is the callback to execute when a target is marked resolved. If we + // don't need to do anything, this will be null. When a target is resolved, + // this callback should be posted to the scheduler pool so the work is + // distributed properly. + const TargetResolvedCallback& target_resolved_callback() const { + return target_resolved_callback_; + } + void set_target_resolved_callback(const TargetResolvedCallback& cb) { + target_resolved_callback_ = cb; + } + + private: + base::FilePath root_path_; + base::FilePath secondary_source_path_; + base::FilePath python_path_; + + SourceFile build_config_file_; + SourceDir build_dir_; + std::string build_to_source_dir_string_; + + TargetResolvedCallback target_resolved_callback_; + + // See getters above. + mutable ItemTree item_tree_; + mutable TargetManager target_manager_; + mutable ToolchainManager toolchain_manager_; + + DISALLOW_COPY_AND_ASSIGN(BuildSettings); +}; + +#endif // TOOLS_GN_BUILD_SETTINGS_H_ diff --git a/tools/gn/command_desc.cc b/tools/gn/command_desc.cc new file mode 100644 index 0000000000..dd78b84b7e --- /dev/null +++ b/tools/gn/command_desc.cc @@ -0,0 +1,344 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include + +#include "base/command_line.h" +#include "tools/gn/commands.h" +#include "tools/gn/config.h" +#include "tools/gn/config_values_extractors.h" +#include "tools/gn/item.h" +#include "tools/gn/item_node.h" +#include "tools/gn/label.h" +#include "tools/gn/setup.h" +#include "tools/gn/standard_out.h" +#include "tools/gn/target.h" + +namespace commands { + +namespace { + +struct CompareTargetLabel { + bool operator()(const Target* a, const Target* b) const { + return a->label() < b->label(); + } +}; + +void RecursiveCollectDeps(const Target* target, std::set

p+?g(=e*8_%3Z+5L%/G+28/7$6A3?8F3A @HY`(ca+ig1sp.{x7‚†1Œ;š 6¡§=¥¯;¨±>«¹8¯½;±Â9µÅ<­ÁP®ÂQM]B                             #'0PT\cfsber^^nYXh[Xf]Zi`[j`[jiewtpƒxuƒsqrs~qr}uuuuuvuvsr{qpylmxklwiiwiiwihqihqfgkfgkhfmigniiokkrqrtyz|‚‰{xpV`HGQ9FS1CQ/@N3G1;E/691!$ (/7[`=^c@hk?mpDquGtxJvzL{Q~…NƒŠR’Kƒ”M•M•M‚’N‚’N‡”M†“LC~?~‹B~‹B|Š?y‡<~–.ƒš2ˆ©+‹­.—¶2œ»6£²BŸ®? ‘`I:8/E*Y{>Y{:Z{:Z{=>A*8;$0<9F=KP_&mv5W`ej%ns.v|-|‚3‹’4•›=ž¦8£«<§²8©µ:°¾1µÃ5´È:µÊ;žµVDFA?F>AIA@K@?J??I9>H87>/8?09F4L0?I1@J2AF7>C5570 #!"*3<š®=œ°@ž¤H£G™‰g@14:?'D8)dK;wCBˆ10w+m5Aƒ{”® ºÓÁÊÉÁÊɼ·ª¤Ÿ’¶´§²¯¢·¼§Æ˶¾Å©˜Ÿƒƒ•s }“·ˆˆ¬~ƒ¤qz›hogSeL[kxfv„]o‘\nPgˆKbƒG^}D\z@Yz?Xx=Xz=Xz:Z{:Z{]ƒB`‡AdŒDfŽBiDk“Sx„n“Ÿ©’Œ¨‘•£žy–žy”œv’št“›u•ž}•ž}“‚‰Œ{„†…‡‚ƒ‡„‚…ƒ„‡~„‡~†ˆˆŠŒu€—s}•d}žeŸ\z ZyŸVu™Rq•Op–Mm“Lj“Ki’IfHeŽHeŽHeŽJeŽGcŒE`‰E`‰DaŒDaŒBaŒEdŽBiHo–R|œ^ˆ§o‘¥s•¨|“™}”šŒ›ŠšŽŽ˜‰—‡Ž•†”…ŠŠŒ’†Ž”ˆ–“Œ‘Ž‡‘ˆ’Ž‡–„‚‘ˆ—yˆ—yŒ˜v‹—u‰“tˆ’sˆyˆy‹ƒŒ„ˆŒŽ…‰Šu€•t”m„™r‰žx’œ{–Ÿ„™–—“)4)4'3'3'2'2'2'2'3'3&2&2&2&2'2'2'1'1)2)2'0'0'0'0'0'0'0'0'0'0'0'0*3*3*3*3)1)1)1*2!+3"+3"*2!*2!*2!*2!*1"*1"*0$*0$*0$*0$*0$*0$)/#)/#'/#$- %-#%-#%-#%-#%-#%-#%-%%-%$,"#+!!,!!,! * ))))(''(())(())))))))))))))))))()((('((((' &%!%!%!%!#!#!!#!#!#!#"!"!%$&%$'$&)'-,'/.*63(:7,B?+FD0QP0XW7aa?hhFhnIntOjxUftR\lWXhSWeWYgY\g\`j`gnagnahpXlt\ovZovZs|TqzRq}Kq}Kt€IuKv„Iy†Kz‰P}‹R€ŽW€ŽW‹R}ŠQxˆJw‡Iv…ErAiw@es=]k@Xg;S^?Q\JR:EN8AK5>H0:E,6A-1<(,8$)4 '/%-&,(/NO6npWefFacCchCkqKgpN-6$( !' !' (&+(=9ZV4snFlg?`\:_Z9TO=QK:YNTu|uh‡h[{aX€aX€XS}ZUcZ‰\S‚dZ…e\†gd€~|—pqzcem^fb^fbdjrnu}QYh/8F &,6FQ^nXeuVavWbwTe~Yj‚ZgŒ1>c"-[,8f.;r4Ax7Az7Az2@w0=t+>i';e%5^#3\'2T"-P*.D%)?.10587GJ3GJ38D1=9HAPck*_h'Z_ci#ou&qw)~…'‹’4–ž/¥6¡­2§²8¬¹,°¾1¯Ä5¯Ä5¢¹Z‘¨I) +$$"                        #'UW`ijsgfqYXcZVb]Ye`[jb^lc_pfcsll|wv†ssƒqp€rqssƒst„tu…vx†x{ˆx|†x|†x|†tw‚otns€lq|jozjnwhltijshirggpfeobbgdeicdfgiksrtzy{†~nslNXACM7?N0:I+7F/5C-5B05B02D19K8AUGQfW\qccxjw‚wuuttkqriqsgrthmrcmrcjog\aY9?<"($"("(''#+.HPSq~l‚}€Šk€Šk…k„ŽjmnW-./,&=?:CE@@G?DKC@K@?J?K9=K6=K6?M1>L0?I1@J2AF7>C535.  #+7@AS\]olƒ’€ˆ™t„•p‡–d„“`‚”S{LxEgp3ML&-- + &:?/JU5OY:V`5V`5^a1ac3dc6dc6gf;fe:gj>koCntAtyFv…Ez‰I|FI„K„K„S†’U†”P†”P†•O…”MGƒ“Iˆ›BŠCŽ¤:‘¨>˜¬;˜¬;™ŸCš D’‚`E59">>&C5%`B3n87~/.t*lP\ž©Ã¤½×ÂÌʹÂÁ“‚…t­«žÄµÇÌ·ÅʵÃÊ®˜ŸƒzŒi‡šw‹¯€‰®‰ªw±~Œž…|Žtcs€dt[mZlPgˆKbƒG^}D\z@Yz?Xx;Wy;Wy9Yz9Yz;W|;W|:X~]ƒB`‡AdŒDfŽDk“Fm”Y~Št™¥©’‹§’¡}žy”œv”œv“›u”œv•ž}•ž}Ž’‡‹z„†…‡‚ƒ‡„‚…ƒ‚…|‚…|†ˆˆŠŒx‚™t~–d}žd}ž]{¡\z Xw›Ut˜No•Ll’Ki’Ki’JhIfGdGdE`‰E`‰E`‰E`‰DaŒDaŒBaŒEdŽAhIp—U~ž_‰©o‘¥p“¦{’—|“™ˆ—Œ‰™Ž˜‰—‡Ž•†”…‰~‰~Œ’†‘˜‹–“Œ‘Ž‡‘ˆ’Ž‡–„„”Š˜zˆ—yŠ•t‰”s‰“tˆ’sˆyˆy‹ƒŠŽ‚†ŠŒ…‡s~“t”r‰žw£z•ž}— ‚˜”—“'3'3)4)4'3&2)4)4)4)4'3'3'3'3'2'2'1'1&0&0&0&0)1&/'0'0'0'0)2)2)2)2*3*3)1)1)1)1)1*3)1*3*3*3)1)1*2!*2!*1"*1"+2#+2#+3"+3"*2!*2!'/ '/ '.!'.!%.!%.!$.$.#/!#/!"- #/!$- $- "+"+ + + + +))**))))******((()**))))**'())) ) ("("' &&%%%%%# # # # $!$!%% & "(#$*'%+(*-*+.+/2)36,==-AB2OM2VT9bb9ii@npGtvMozRozRbrX_nU\i\]j]cm^cm^gnYhpZmrOotRs|Fu~Hw„CvƒAv‡;v‡;~Š=‹>„‘A…’B†—C‡™D„”Bƒ“Aƒ‘FD|Hy‰DsGmyAgsHdqF]eHZaEX^DU\BPTAMQ>KOJ, ""!!"&&@9]W:smGgb<\[=VU8OK:PM;WMO|rtym‹fZwaVƒbW„_X‡_X‡_X‡\U„^Yƒc^‡ie†}y™z}ˆfitaki`jhZgiivxixˆ]l|QauScwVavVavVf{[kUg‚Wi„P^~%3S)4_0;f1?=FJ0:>%6>5=BJJRV]!V]!_g hq)v~)~†1‹•-– 8›©0Ÿ¬4¦³/ª¶3®¾*²Â-­Ã7®Ä8yH0  !""!                         67@dfokjs]\eXVd[Xf^\j`^lb_mfdrilyqtvx†qtnq|ps~su‚qtty†uz‡z|‰x{ˆw|‰w|‰t{Šqw†lv‚isftguƒhqen}fnxdlv`fp]bmbaj``ibajbajjjqqqxyy€{{‚ekaHNDAL3>H08H-5E)3F01C.3H49M:DWFSfUjxhqox|wswrosiloemqeosgjqijqierndqmdnjV`\7B>)&"#!"%0*ITNv‚k€Œvƒg‚ŒfˆŽh†‹fmgQ1+!205<=B??DCBEA@B666+++&)'1417?4>F;BI;CK0n,,u%%n$4xjz¾—®Ã§½ÒÃÉÊ»Á‚z~[SV……‡–•—®¸¦¸Ã±±¼¡šŽŸz’m{£o‡°|„±o”Á£Ä|š¼t–uw€_dkem€Tg†ObGZ€CV|?Wz>Vy>Vy>Vy?Wz?WzknEruLq}Tq}Tfu\brX]j]\i\bl\bl\gnYhpZqvSotRu~HvIx†Dw„Cx‰={‹@„C†’E‰—GŠ˜H‹œGŠ›F†—D„”B…“H„’G}ŽIxˆCr~En{BfrGbnC]eHZaEY_EU\BQUBNR@LP=IM:@I5u6@y4?x2=t/:q)8j#2d!0[,W!,O*L *D)B#(3 &0'(&./,?C)DH.;C,3 3;3:B8>E6@G8DK6EM7AK5@J4?G6:B1.-) */5ANJ]jfv†k…•zŒœr†–m‹—eˆ•c‡–VMv}E`g/>>#!!() >A2NQCUZ>Y]A_d:bg>lk7lk7hf/gd.ba/_],\]*cd1cg;mpDr|BtDzƒAzƒAxA{ƒD†I…‹O‡’Qˆ“R‹˜O‹˜O†•O‰˜Q‹œN‹œNŽ¢FŽ¢F‘¡C‹›=”œP–Q€rYeW>I0KL4N7)g<.k''p!"j;Kv…É–­Â¢¸Î¼ÂýÃŹ±´˜”}|‰ˆŠ—¢–¡Ÿ«˜¤‰œ­ˆ•¦ƒ«wƒ«w}ªh„±o™ºs¦Ç€œ¥„‡oem€bi|Sf…L_~GZ€EX~?Wz?Wz?Wz?Wz>Vy>VyF39B.4=)19(-5$+1%&- %-$,,/IL7mpT`cG[eA`jF\kKZiI-   !!#'(C>a\=qmEb^5XW7UU5TSq4=t4=t1:o,5j+6f%0`".U+R)N&L%C"A"8"8"1/%''587;>404*,.13 9>PBM`Q]pajwjo}otxslpkmpgloemqeosghogkrjhtrfrpfpn`jhZf^_kbfuefueeq]juaŒj€ŽlŠd‚Œf†‰f‚_ZRA# +%$" "(,/52769>=>B<@D?BG8CH9?C0@D2@C58;-('# &25;KTNfoi~ŒoŠ˜zŒšq‡”k‰—a†”]‡—R€Ku|D]e-89!! !44+EH9RTF[_EZ^D^d6ah9ll5ll5kj1hf.bb-`_+\Z/_^3af5gl;ktVy?Wz?Wz?Wz]ƒ:]…>`ˆCeFhHnŒHnŒo–„¤ª“©‘¥— }“y™w™wŽ˜y‘œ|˜ž„“šŽƒ‰‰}‡‡……†„†~„|ƒƒ|ƒƒ|zƒ‹~†Žm|™n}›a|ža|ž\y¢XvžUr›Qo—Jj“HhFfŽEdCeAdŒDb‹Db‹Ca‰Ca‰B_ˆB_ˆB_ŠC`‹A`‹EdŽ@m‘JvšZ†¡a¨s‘£q¡‚•’‚•’Š•ˆ‰”‡Š’†Š’†”…Š€Š‘}‰|Œ”‘š‡Ž™…Ž™…šŒ™€›wŒ™t‹–vŠ•u‰’wˆ‘vŠ}Š}†‰††‰†‚~Žo~•tƒ™oŒœu’¢‚š¤‚š¤‹››ˆ˜—•’‹“*6)5)5)5)4)4*5*5)4)4)4)4)4)4'2'2'1'1)2)2'1'1&/&/&/&/'0'0)2)2)2'1)2)2)2)2*3*3*3*3*3*3)1)1)1)1)1)1*1"*1"*1"+2#*2!*2!*2!*2!*2!)1)0!)0!%/ %/ %0%0"- "- "- "- $- $- "+"+ + + + +)),,*)))******((****))))**(())((("("' &&%%%%%# # # # $!$!!&%!&%!&%"'&$*'',)*-*,/,02-461:90?=4KG1RN8[Y7b`>ki@sqHszRx~Vs_q\it`dp\eoYclVjpQjpQowDu}J{„E€ŠK…•L‹›R›W›W’œ]’œ]” Z” Z’£UŸQ‹MˆšI„•Gƒ”F{Lt†Eq|MmyJdoM_jI[dLX`HW]JSZGPVCLS@IOF39B.4=)19(-5$+1%&- %-#+)+;>)fiMceIU_;[eA[jJ^nN+?,    !&'GA"e_@ojBa]4VV6UU5WU?YXAYOQi`aqi‡c[zWOyZS}VR€VR€`W†f]Œf\h^c[ˆ_Wƒjh„mj†^cnRXbO[bT_g[dr`hwcm€jt‡hvdsŒ\o‡\o‡Rd…?Qs)5\,8_06g29j19l3:n19q19q,5j,5j+6f%0`".U+R(M%J$B"A"8"8#2"1"$$%''36,47.-/,.38?D!PP\]&gl gl ty y~$‚Ž!‹—*”£›ª& ³¥·#¤º§½£¼6¨Á;—¨nIZ        + +          "') '+3X\djjqddka_febjgcolhtjgupm{ut‚qqghxikzjm}jm}hl|gkzdjyio~nrnrmtƒpv…qvˆek|\`vVZpX]q\atdfzeg{di|ej~_h{^gz]ev]eveo{lv‚qu~hltcembclfhjijljkoppurswqrv_e[EJ@;H66C11C05G48KBG8DI;IM:GK9?B457)'&"!#*8:ANWQjsm„’u‹™|‹™p‡”kˆ–`‡•^‡—R}ŽIpx@Y`(12 '(<=3GJpK:m-.m()iRaš„“̧Ȧƥ»—Ÿµ”—¢—š¥–ŸŸ…Žƒ—„“§”™ªƒ—©‚–¬r™¯u˜¸sœ¼w–¸n’´j˜½jšÀl”±h®e„—{|sZeuT_oGY{DVw?Wz?Wz>Vy?Wz?Wz?Wz]ƒ<^†?a‰EgEgHnŒLqp—‚¢¨’§Ž¤Œ— }“y™w™wŽ˜y‘œ|—ƒ’˜~Œ‹Š…†„…†„€…}„|ƒƒ|‚‚{xˆ{„Œm|™n}›a|ža|žZx¡XvžUr›Pm–Jj“HhEdEdAdŒ@c‹Db‹Db‹Ca‰Ca‰B_ˆB_ˆC`‹B_ŠBaŒEdŽAn’Mzž]ˆ£a¨r¢r¢”‘”‘‡“†‡“†ˆ‘„‹”‡”…‹‚‰|Š‘}Ž—ƒ”‰˜„˜„šŒ™€Žšv‹—sŠ•u‰“t‰’wˆ‘v‰Œ{‰Œ{†‰††‰†~Ž~Žp–w†u’¢z—¦‚š¤™£‹››ˆ˜—•’‹“'5'5(6(6)4)4*5*5*5'3)4)4)4)4'2'2'0'0)1)1'/'/&.&.&.&.&.'/)2)2'1'1)1)1)1)1)1)1)1)1*2!*2!*2!*2!*2!*2!*2!*2!*1"*1"*1"*1"*1"*1"*1"*1"*1"*1")0!)0!'/#%.!%-#%-#$- $- $- $- $- $- #,#, + +**,,,,)))))*))**))****((((((((((((& & & %&%%%$!$!"!"!# # $!$! %" %""($#)&$*'',)).++1..2/252873=<8HC6MIv‚E€ˆGˆO•]“šc˜l˜l”•q•–r–šn•™m’ža›^‹˜V†”RQ}‹NvUr|QmuVhpQciQagOVaOS]LU]LR[IPVCMTAIO"JORX%dh!ps,dkmtw~ ˆ)Ž˜#– +›¬#ž¯& ´¥¸!©»-°Â4ª½Q£¶J;E   +         # -#&3+54B`^ljgufclb_ifdkhfmggpkjsnrosƒej~]bu]dxah{el‚cidj{chzbj{dl}jojokpƒlq…ca}ZXsZStZSt\Tqc[xd`zhd}if}li€ghzeew_cr\`o^eskq€ty†ko|ehsehsfgkfgkkinmkpmnrnotjniPTO:C67@31@47F:8NACZMVh\cuinxns}rrqoomkpokqqlptjmpg`kedoietretrdpnamk`mb`mb`m[cp^p~bzˆm„nŒjŠd€‹e†‰fz|ZG=1" %"&!!%)-23589;@ADB9EC:A>:740(#$!%!%-9=FR_Tmzo…”t‹šzŒœnˆ—i‡—[‡—[Š˜T|ŠFouBX]*.-!!..'>>7FG>KLBUXC[]H`c>dgBhh:gg9ll5hh2eb3a_/]\/]\/_b2dg6gj>jnBjjAllCqnFusJxxH}~N}Gz}D‰J„‹L‰’OŽ—TšRšRŽœQŽœQŽ›TœV‘šY•TyVskHqdbl_]^JjYEfE:nC8l]i—‹—Ř¦Æ—¥Å¦¹–Ÿ²—œ§“™£š–›”•“š‡‘˜„’¡u—¥z›°l¡µržµd´c§½iª¿lªÅo©Än–´[–´[©e’¬hyƒt`iZKXuLYv>U{Vy?Wz>Vy>Vy]ƒ?_‡Bb‹DiDiKsŠOwŽwš’ƒ¥•¦Š‘¡…“œ{“œ{˜yŽ—xŽ™w‘œ{—ž—z‹Œ‚Š‹ƒ‡„„€|‚~ƒ~ƒ~{€}‚dyšh}ž]¢\}¡Zx¡VsœRp˜Ol•HhEdCcŒCcŒBb‹Bb‹Aa‰@`ˆCaˆCaˆB_Š@\‡A_A_?cBfGq–Q| bˆ¤g©y’™uŽ–ƒ–Œ”Šˆ‘„‡ƒŠˆ’Š‰Ž††‹ƒ†{ˆ‘}›• …Ž™~Ž™~œ|šzŒ—x‹–v‹‘wŠvŠŽ{ŠŽ{…Š€…Š€‚‡‹…‰p€”q•nˆ˜uŸ˜¤ƒš§Š™¢‰˜¡˜›Œ”˜Ž–”‹’‘+:+:+:+:,7,7*5*5+6*5)4)4)4)4&1&1'0'0)1)1'/'/&.&.&.&.&.&.'1'1'1'1)1)1)1)1)1)1)1)1*2!*2!*2!*2!*2!*2!*2!*2!*1"*1"*1"*1"*1"*1"+2#*1"*1"*1"*1"*1"%.!%.!%-#%-#$- $- $- $- $- $- #,#, + +!,!,,,,,**)))*))))))******((((((((((& & & %&%%%$!$!"!"!# # $!$! %" %"!&#"($$*'',)).++1..2/252873=<8HC6MIHN;DK7BH7AF6;A06<+07(,3%*1"%,'-%*&*(,AC(ehL]`BY]?V_C]fIQaL/#$'')F@!d^?mc>e[6^W6`Y8`XAaZBaVMi^Uwl~pew]Xvb\{kbŠlcŒnak]k]nah[i\c]…UOwOQnUVt]crW^lXamYbn]ev_fw]g~U_vHWt:If7Be9Dg9Cj:Dk9>k9>k7;j7;j28k05i24c13b)4^'2\&0U",Q',M#(I'>&="5!4 2 2!,!,# $-/(>A:?G*8@#EKKQZ]y}6u|%dkry{‚#…‹•“£™© œ¯¢µ£µ(©»-¦¸Lª½QŽ™s0;    +  " -(*8==JRR_dapdapfclifpliqliqnnwuu~nrcfv]bu[_s[bu_fy_e{`f|bgydj{ck|dl}fl}johl€OTgQNjQNjSLmWQr[Sp`Yvea{jf€khif}hi{jk}dhw`dsW^l]crlq}otjnxhkvghmghmljokinklpklpptn`d_>G:8A40?38G;;REG]PZl`dvjq|qoyoomkomkqqlqqlosiloe\g`_jdetretreqocomanccodbo]an\lz_x†k‡’p‚l}ˆaŠd„‡drtR>4(# %"%)#&*"*+-9:5<>9DB9BA8>;71.*#!&&*2@DMXeZs€uˆ˜x†•u‡–h…•g‡—[‡—[…“OzˆCmr?OU"+*!!//(??8FG>KLBRT?Y[F]`;`c>hh:gg9ij3gg1b`1`^.^]0^]0_b2dg6dhVy?Wz>Vy>Vy]ƒ?_‡Bb‹DiEj’Jr‰Rz‘{•„¦Ÿ•¦Š‘¡…“œ{“œ{˜yŽ—xŽ™w‘œ{˜Ÿƒ‘˜|Š‹‰‰€‚…ƒ„‚~€|ƒ~‚†ƒˆ•†’fz›fz›\}¡[| Zx¡VsœRp˜Ol•GgEdCcŒBb‹Bb‹Bb‹@`ˆ@`ˆCaˆCaˆB_ŠC`‹@^‹@^‹?cDh’Hs—T~£d‹§i«x˜v—‚•‹”Šˆ‘„†‚‰Ž†ˆ…ˆ…†‹ƒ‡|‡|’žƒ• …š˜}Œ˜yšzŒ—x‹–v‹‘wŠv‰z‰z†‹†‹…‰~„‡o“tƒ˜ry”¤ƒš§‚™¥‰˜¡‰˜¡—šŒ”˜•“Š‘-8,7-8-8-8-8,7+5+6+6*5*5)4)4)3'2'1'1'1'1'0'0&/&/&0&0&0&0'1'1'1'1'0'0'0'0*3*3*3*3)1)1*2!*2!*2!*2!*2!*2!*1"*1"*1"*1"*1"*1"*1"*1"*0$)/#*0$*0$%.!%.!%-#%-#%.!$- $- $- #,#,#,#, + + + +,,,,,,))))))''))*****!*!) ) ))(())(('''&$!# $"$"$!$!"!"!# # $!$!$! %"!&#"($$*'',)).++1..2216557989;B@:GE>QP7WV=__=ffDmpDquIw~O|ƒT{„\w€Xq{WmvSmrHmrHv}E‰QƒŒV†Z†‘d‰”g‡Žt‡Žt‡Š|ŠŒ~‰Œ{†Šyˆn„Ši‹Yz‡Uw‡Pu…No~SkyNepVbmR\hRZfOVaOS]LR[IOWFLS@IOD3;A05;*06%.4#,1!(.(, %)&)&)/2Y\EceIX[?W\@\aEZfO7C- # &%*G@$f`Cmb@f[9aZ9aZ9c]>c]>cYFj`M}pt{nri`tjaui`}lbneˆi_ƒfZ…fZ…eZŽg\`W`WWT€IFrJLiJLiDF\,-D((D--H21N65S>Bf?CgBGmDJp@Is@Is;Fo7Bl3=i3=i07f/6e/2_.1^00Y.-V-,Q,+P)*G')F%)?#'=!$6 2/.,,++&"%0-0@D3FI9FJ%FJ%IL\_&„Š;rx*ipqx…$…$–%•ž-›¦'¨) ±&¤¶*ªº3¯¿8§ºS°H'2  # '&4==JE@SJFYTPb_[nhcsnhyljvrp|st„qsƒ]buV[nV]rU\qS]pW`sW_vW_v]dxah{cj~ah{dg€^azDCaCB`OHkNGjULmVMn[Ro`Vsb\td^vkcyoh~sm~uo€ro}khwdep]^icgqtw‚vv‚oo{mnriinkinljokhonlsrrpopnLTI8@61@49H=;REI`S`qei{os}rlvkonjpokqqjqqjmpggjaWb\^icbrtetvaqq^nmbojbojdqecodqdzˆm‡“o‚ŽjŠd~‰c„‚epoQ9,*$!!""$%!"$ ) #'%)-$%)! $./4:;?9:>76;('+!$*'.4BIO_k[wƒtˆ–t‡•s‰–g‡•e‡šU…˜R‚Mu‚@fjVy?Wz>Vy>Vy;W|]ƒ@`ˆCcŒChEj’KqTz–}ž›ƒ¤¡•§‡’¤ƒ“žwštŽšq›rŽœq’¡uš¡…—z‰‰€‡ˆ‚…ƒ„‚~€|€„‚†ƒˆ•€„‘l}œl}œ_|ž^{Zx¡VsœQo—Nk”GgEdCcŒAa‰Aa‰Aa‰Aa‰@`ˆCaˆCaˆ@\‡A]‰@a‡Abˆ=dDk”Jv˜V£i§l‘«x˜x˜ƒ“Œ€‰†Ž„…‚‡‹††Š……‰ƒ‚††{Š“€’Ÿ€’Ÿ€ŽœxŽœxŒšvŒšvŒ˜v‹—u‹’vŠ‘uŠŽ{‹|†ˆˆ†ˆˆz‚“z‚“r„˜wŠ|‘¤–©†™¥…—¤Œ– ‹•Ÿ˜˜Š––Œ”‘Š’Ž0:".9 .9 .9 .9 .9 .9 ,7+6+6*5*5)4)4)3'2'1'1'1'1'0'0&/&/&0&0&0&0'1'1'1'1'0'0'0'0)1)1)1)1)1)1*2!*2!*2!*2!*2!*2!*1"*1"*1"*1"*1"*1"*1"*1"*0$)/#)/#)/#%.!%.!%-#%-#%.!%.!$- $- #,"+#,#, + + + +,,**,,))))))''))****&&(((((())(('''&$!# $"$"$!$!"!"!# # $!$!$! %"!&#"($$*'',)).++1..2216557989;@>7EBD3;A05;*06%.4#,1!(.(, %)$($(*-FI2ehL^aEY]A[`DYeNKW@""%%*HB%e_Bmb@f[9`Y8`Y8`[<`[#&7"4!0..."-"-(%(-),15$=@0MQ,JM(HK59QWlr$y'musxzƒ‹–%–¡"¨)ž°$¡²'£³,ªº3£¶N¨»TŽ™w'2  ''3IJUggt[[iFBTA=PPL_]Ylkevpk|wuzx„hjyYZjTXlTXlTZpRXnPYlR\nRZrQYqY`s]dx_fyah{TWo.2J54R=QUB[]A^aEed@ca=gg9gg9ge5ge5ee5ee5ieff?hg@eiBdh@ff?ee>hgVy?Wz>Vy>Vy;W|]ƒ@`ˆCcŒDiFk“JpŒY›‚¢Ÿƒ¤¡”¦†‘£‚“žwšt›r‘œt‘ t–¤yš¡…—z‰‰€‡ˆ‚…ƒ„‚„‚~ƒ~ƒ‡‚ƒˆ•€„‘k|›k|›_|ž^{XvžUr›Ol•Lj“GgCcŒCcŒAa‰Aa‰Aa‰Aa‰@`ˆCaˆCaˆA]‰B_Š?_…Bc‰?eŽDk”MxšZ…§i§i§v—x˜‘Šˆ…‚„Œ†Š…†Š……‰ƒ…‰ƒˆ‘}–‚”¡’Ÿ€ŽœxŽœxŠ™tŠ™t‹—uŠ•tŒ“wŠ‘u‹|‹|†ˆˆ†ˆˆz‚“z‚“u‡›}£–©‚—ª…—¤…—¤—¡Œ– Ž™šŠ––Œ”‘Š’Ž1>0=.;.;09090909+:*9*8*8)7)7(5(5'3'3'3'3'2'2&0&0&0'1'1'1&/&/&/&/'/'/)1)1*2!*2!)1)1)1)1)1)1)1)1*2!*2!*2!*2!)1)1(1"(1"(1"(1"'/#'/#%.!%.!%.!%.!%.!%.!$.$.$.$.!,!,!,!, + + + +**))++**))(((())))))**(()(((((('&&&&&&$!$!$!$!# # "!"!"!"!# $!!&#"($#)&%+().++1.*2/.6246878:===@@@HEALIEXT9^Z?cg;ilAryDy€K„T„†V|…VzƒTt}Gs|FtF|‰N}ŒQ€T|‡f€‹i~…v{‚tyu{wz‚qz‚q{‚my€jv€cpz]nyXkvTirXgpV^hY\fW[eUWaRT^MP[IRYEOUBLRAIO>GL;CI8=C2=C29>-4:)/4&-2#-2#(-%,$+' ($)/5$]_LbdQ[\C[\CWeJZhL&3! %%+IA)e]EncBi]=g]=g]=f^?g_@c\Hh`MrhivwvkxshunfwqizzoŽ|r‘wk”rfofœf]“\U‹XR‡XW‚NMyFGlCDh77T+*G/,J40N;7W@<]@CdEGiFKqHMsALt?Kr:Gp8En5Bm2>j/:d+6_.1W*-T+-Q)*O+)I+)I))E((D*(<)&;&'7#%5$$2""/&".$ ,%"+&#,+(/2/666622266(GG9QP0HH(GL HMiuu(pxqyu}#~…+Š’(’š0›¤1œ¥2ž­.¡±1¨µ=®»C«·c™¦R&+!:>Bdclsr{plxa]iC?R72EA:U[Tnrl„{vvsˆa^sSVjWYmPUiQVjPYlR\nRZrRZrNXoNXoT\tYay_a~EFc0*P5/T=7];5ZGBRX[o{dw‰™r‰™r‡•e…“c†•Z†•Z‚ŒSpzBVZ9,0/.#?>:FEAEF=GH?JQ=LS@QUBUYG^]D`_Fdc>dc>fc=ebchCbgBcj@_gigCooFwwNx~K~„Q†ˆQˆ‹S‰‹VŽZŽ`‡†Y‚~SƒT„€V…W€c~`xvnzypˆ…Œ•”œ’–žŸ¥®­³»¯¶µ£«©ž›—‡„€}n~‚o…‘oŠ•t£j“§m›±bž´fª¸m¯½r´¾x°ºt¢±j¡°i ·l¡¸n™°e†SyƒdalMSXrQUp@SyBU{;Wy=Xz;U|>W~>Y‚B]†?a‰CeGjJm’Nr…a†™‚¥–‚¥–”¤‚¡~žužu’™v”›x”žz˜¡~™Ÿ‡Ž”|‡‡‚‡‡‚ƒ†…‚…„‚ƒ€‚ƒ€ƒ…~‡‰‚…ˆ“‚…lzšlzš`y ^xŸTsžOn™Nm˜Ml—Hg’EdŽCcBaŒ?a‹>_ŠB_ŠB_ŠCaˆCaˆC`‹C`‹B_ŠC`‹BfKo™T{_…¨k£k£}“}“‡‘‰…††Œ€…‹…‡‚…‡‚‰„‹ƒ…“~Š˜ƒ {ŽŸzŽžuŽžuŽ˜yŽ˜yŽ—x•v‹‘wŠv‡‚‡‚„‡‹„‡‹s‚”v„–yŽ¡“§„˜§„˜§‰—¤‰—¤‹—£‰• ˜˜˜˜Ž•Œ’2?1>0<0<2: 2: 0909+:*9*8*8)7)7(5(5)4'3'3'3'2'2&0&0&0'1'1'1&/&/&/&/'/'/)1)1*2!*2!)1)1)1)1)1)1)1)1*2!*2!*2!*2!)1)1(1"(1"(1"(1"'/#'/#%.!%.!%.!%.!$- $- $.$.$.$.!,!,!,!, + + + +**))++**))(((())))))**((((((((('&&&&&&$!$!$!$!# # "!"!"!"!# $!!&#"($#)&%+().++1.*2/.6246878:<<<===FC?IFBUP6ZU:_b6dhGL;DJ9AF6=C2:?/6<+27&/4&-2#,1"). $+$+ ( ($)'-KL:bdQ]_E[\CSaE[iMCQ<"%$*B:"aZBqeEl`@ja@ja@jcDjcDibNjcOmcevw{p}tivrj|yqƒ„z™„z™uj“ti‘qgj`–_YŽ\U‹VUDCn78]=>b00M('E.+I1.L51Q>:[?AcDFhCInDJp>Jq>Jq:Gp9Fo4Al2>j0;e-8a/2X-0V+-Q)*O,*J)(H((D((D,*?,*?(*9&'7'&4$$2'#/($0(%/)&030841922222288*77)?>XW7gl.di+p|#wƒ*|…(t} u}#u}#„Œ"‹“)”*š£0šª*ž­.£±8¬¹@«·c«·c‡Œw!& ! "EFOilpux|edm[Zc`\hZVbB>Q1-?70JMF`ke}rl„][pPNcOQeQSgPUiUZmQZmQZmV]uT\tKVmMWnX`xS[s?@^*+I0*P<6[5/T94Y?4ZF;aPCiQDkPDfRGhTLi[Sph]vpf~zn}€tƒx‡€v†}typ}rmwmgqro}{y‡prygiokhokhoignhfmfehfehfhaSVO8J>@RFE^PZsem}ql{pjujitinslptmntjmsi^f^NUMKZUXgb\osatxapt\ko[li[li^n`brdr~a|‰k‡‘mƒi‡h‡h€c]]@%"##&&)()($$"##&''. '  %&&&'&()&&& %2>DGX]ap|f„y‰™r‡–o…“c…“cˆ˜\‡—[‹Rlv=KP/'+ $#76+BAGH?HN;KR>NR@SWD[[B]\Cb`fg>kkBqwDx~K„L‚…N†‡SˆŠU†…W~Q}yO|Q…W…W~`~`zyp{zq„‚‰‰†Ž‹—¡¥®µ¼Ä¹¿Ç±¹·¥¬«•’Ž†ƒ~‚o‰z›zšx¢h¢h³d¤ºk©·lª¸m±»u³½w¬ºt¤²l›²h¢¹oš±gŒ£Y}ˆhr|]RVqSXrBU{BU{:Ux:Ux:T{=V}?Z„B]†?a‰CeGjJm’PuˆeŠ…§™€£”’¢Žž|žužu’™v”›x”žz˜¡~—œ…Œ’z†…†…‚…„‚…„‚ƒ€€€~ƒ…~‡‰‚…ˆ“‚…lzšlzš`y ^xŸTsžOn™Lk•Hg’Gf‘CcCcBaŒ?a‹>_ŠB_ŠB_ŠCaˆCaˆC`‹C`‹C`‹EbEi“Ko™V|Ÿ`†©k£k£}“}“…†„Ž…†Œ€…‹…‡‚…‡‚‰…Œ„…“~Š˜ƒ {ŽŸzŽžuŽžuŽ˜yŽ˜yŽ—x•v‹‘wŠv‡‚‡‚„‡‹„‡‹v„–{Šœ|‘¤–©„˜§„˜§‰—¤‰—¤‹—£‰• ˜˜˜˜Ž•Œ’2?2?3?"2>!1=@AD3;@16;-5:+05'/4&-2#). ',%,") ('!&"'69)\`O`aJ[[EV\E_dM\eQ08%!!'B;!aZ@wlIqfCng?le>leDpiHqlYqlYoe`}sn†{‚}rxzo|ƒx…Žƒ¢ˆ}œtj›qh™kgœieš]]’XYKOz;?i39]:@d15R$(E,+I/.L33N77S>>^ABbAGkAGk[4-I1*MG@cHBcMFgNKiNKiLMfPQjPTjUYoSXqV\tV[vV[vTZyV[zFIh03R,)L+(K4,V;3]7.];2a:1[B9cG?eE=bI?cMCg_Rmj\xxfy}k~my‚nz‚p~†s‚„u€r~{q{ukvuoy€{…wvlktkgqifpifpheobdkcel`ebSXUDPIGRKTa[eslpxunvrkqnnspsunsunpukjoeV_RMVIHXSSb][ms_rx_lsZhoZkhZkh]m_brdr}b~‰n…ŽmƒŒj‚Šf„‹h}}bWW<"$ #%'&''%'(%%(%%,$$+!"$%$)*+ 0++ -).;GQIakb{‰g‰—uˆšl…–h†—`…•^‹™\„’U}ƒPjp=CD&'( +(%970DE;FG>FI9IL;JQ=JQ=MQ>NR@VW@XYB[\@]]Ad`jg?hf>ee^\:_^:a_;ca?ca?ghCqrN}{S}{SzW…\x\ulPrmSxsYzv[z`€|hƒ~j{}kn‡}’ˆ¢¤¤µ¸·½À¸´¶¯§ªš”—‡Ž‹~Š}ŠŠv‡‡r†’i‡“jˆ™Yˆ™Y˜¦Z§µj­»n­»n«¸q°¼v²½|­·v¢³l®g¢·k•ª^†”d‚`^cgW\`ERwFSxU{:T{;U|=XB]†AdŒEgGjJm’Qv‰gŒŸŠ¥—†¡”“£€Ÿ}žužu”u•žv“žv™¤|”™„‡Œw„‚€…ƒ‚…ƒ‚…ƒƒ…€‚„ƒ…~‡‰‚…ˆ“‚…ixœixœ^y¢ZužTsžOn™Ji”Hg’EdŽCcBaŒA`‹?a‹?a‹C`‹C`‹CaˆCaˆDaŒDaŒCa‰EcŒDiLq˜VŸ^ˆ§o¡lŸ}“}“„Ž…‚‹ƒ†‹ƒ†‹ƒ…‡‚…‡‚‹|†€‰™}‹›ŽŸz {ŽžutŒ™t‹—s”zŒ“x‹Ž~‹Ž~‡‰‰†ˆˆz†‘x„p‰—z“¡ƒ—¦…™¨†™¥…—¤‰—¤‰—¤Š–¢ˆ”ŸŒ——Œ——Ž•‹‘Œ5B#5B#4@#2>!2="2="1=BC?EGBKK;LM=TY1Y]6af5gl;qtCwyIw‚G~‰N…ŽJ…ŽJzŒBxŠ@x‰Iz‹Kv„[v„[uiuisxpsxptzptzpuymtxlsximrclqbin`ej[dhZ`gZ[aUY`SW^QOYJLUFKQ@HM=EK:CI8@E4,+=+(6)&4'%3'%3)%1+'3,)22.853:4193082/6006&&-($"#*'?<ik x{/y„ |‡$„’+†”.|ƒ%v}z~ ‚†(Œ“.“š5˜ 6œ¥;¦§R¬­X¥«qª°vŠ &!379nrstvxkmohhhhhhkgnidlhcrd_mTLi7/L*#F>6ZB<]93TC?^JFeIJcLMfNRhTXnTZrU[sV[vY]xLRq-2Q @14T.,N0.PE>h>7a7.];2a=3]>4_A9_D;aKBeWMqhZuoa||j}~l‚nzmy€n|o}ƒs„u€t|r}{u€{…||…sr{lirheokgqfcl`bh`bh\b_Z_\JUONYR]kdkyrqyvmuqkqnnspsuntvomsidj`MVIKTGGVRRa\Xkq_rx_lsZho[liYjg^n`cses~d~‰nƒŒjˆg…Œi‚Šfzz^LL1"  %#&%')))()"%##*&&-&!"$$'', 0 %2 %2',97;5EF=FG>FI9IL;JQ=JQ=LP=LP=RS]]Ac_;c_;f`;hc=jg?jg?hh?eeW~?Z„E`‰AdŒEgEhŽKn“Puˆh Š¥—†¡”“£€Ÿ}žužu“œt“œt” wš¥}“˜ƒŠy†…ƒ…ƒ‚…ƒ‚…ƒƒ…€‚„ƒ…~‡‰‚…ˆ“‚…ixœixœ_z£[vŸQp›Ml—Ih“Gf‘BaŒBaŒBaŒA`‹?a‹?a‹C`‹C`‹CaˆCaˆDaŒDaŒDb‹HeŽEj’MršVŸ^ˆ§o¡lŸ}“}“„Ž…‚‹ƒ†‹ƒ†‹ƒ‡ˆƒ‡ˆƒ…Š“„‚‚ {žyŽžuŒœrŒ™t‹—s”zŒ“x‹Ž~‹Ž~‡‰‰†ˆˆx„}‰•x‘Ÿ~—¥…™¨…™¨†™¥…—¤ˆ•£ˆ•£‰• †’žŠ––‰”•Ž•‹‘Œ7D"5C!7C$9F&6D'3B$2A#1@"/<@=@C@EI?GJ@MR=RWA[^;adAmoArtFw}C}‚I‹E…I|Ž>zŒ;zŽ?zŽ?w‡Pw‡Pu€`s}^qxiqxiptnrvqptnptnjpkgmgdkdbia`h]]e[]cWY`SU[ORYMOUIKRFHP?CK:CI8AF6>D3;A08>24;.3:-.5(,3%+2#*1"'/ "+ (&%%%#';>-bbM__K[[E\]FZbJ_gO2<&$94WR:xkMxkMyqL|tN{uXx\vo€unynn~sr…z€‰~„†}‘‡šƒ‚§wvš]a’Z]ah—_f”T_‹R\ˆEPz=Hr-8Z$F %D!'F&(E)*G*+I.0M35R:i-=h/>i*9d(4[$/V).O(-N+-H*,G,+@,+@-)<,(;*+6''3''0&%/%#/&%0-*341:52;52;109109**8""/.." '$/)MG8tq7zw<~ƒ%‰/‡Ž-„Œ+{(z~'„„2‹‹9‘‘?——Ež G¦©O£¨\ª¯c¤«z‹“b !$(*qtqsvspomkjhkhknkmniqmholjxljxQNj52N($D-)IE>_81R=:XJFeKLeNNgOOhPQjPSlPSlMNl;=Z%%GA#D&#H&!D72UB9c>4_6*Z>2b?4a?4aC:]MCg\NjdVrs\ru^t~cugy†jz…hxhwhwƒo{‡s…w€…w€vƒ„y†€z†wq}miufbnfepcam\]hZ[fZ\c[]dP[UU`Ygrls~xs|tjtlnrmnrmruksvlipa]dUDP@DP@BVQMa[Tio[pvWjpVio\mkZkh`ldhtlx‚jŒsƒi‰e„Œf†ŽhvwbAA-!" $$'()()++)(+("#,)*,#$& %&(!$/&0'1+58BMP^jXu‚pˆ™mŠ›pŠ—i…’e‰”`Š•aˆ’U„Px|Ncg986"'%11*??8FD=KHBMLAMLAKN>LO?KP:KP:NQRR@WVDZ[=]]@b`>ca?heAheAhfBigCeaDa]@[\@[\@\\mlLklIffDmlHomIvoNzsRi_HaW?f^NphWvp_~xgƒƒh„„i…gŽ–q”š{£„¨¬’¬°—­¯–«­“£¦ˆž¡ƒ—˜z—˜z—›t‘•nšf’Ÿj”¤_”¤_–£T–£T˜¦V¡®^«¸qª·p¨²s®¸y­¸}¦±v °r›«m“£l‡˜azkaiSRYlQXkDWvDWv>WwA[{@^€C`‚@c‹DfŽFmKq”W{‰s—¥Ž¦”‡Ÿ–¡’ž|’œx‘šw™s™s’œv™£}‘•‚‰z‡†„†…ƒ„„‚„‚„ƒ†}‡Š€…Š—€„‘kxškxšWuœSr˜On™Lk•Hg’Gf‘DeAc?a‹>_ŠC`‹C`‹B_ŠB_ŠD^ŠD^Š@_Š@_Š=a‹AdŽDl‘Px[‚£b‰ªsŽ£oŠŸ’‘‚‡€‹…ƒ‰ƒƒ‰ƒ‚Š…ˆ‚ƒ’y‰˜ŸŽž}¡uŽŸsŽ›rŒšq’˜w’˜wŒ“w‹’vŒŒ‚†ŽƒŒx„”‹›•¤ƒ—¦Š—§Š—§Œ—§‹•¦‰’¥ˆ‘¤†’ž†’ž—šŒ–™•’Œ‘Ž8E#7D"7C$5B#2A#3B$2A#1@"1?$.; .; ,:,8",8"*6 *6 )5)5(4(4*4*4)1)1'/'/)1)1'1'1'1'1'0'0'0'0)1)1*3*3*3*3*3*3*3*3)1)1)1)1*2!*2!*2!*2!&3!&3!%/ %/ %/ %/ $.$.$.$.#-#-#-#-#-#-!*",",",",!*****((''((''''''((((((((((&%$$%%%%%%%%%%$!$!&"&"%%%%%% & "(##)&$*'%+((-**0+.4.0543879:<;=?;><>A>CF=EI?LQeg9ln@qw=w}C}†A‹E}?x‹:vŠD3=C2:?/7=13:-17+.5(+2#*1"'/ %,!*'%$###'*.OO:ccNWXAVW@T]E]eMPZD(3.OJ1reG~qS}uO~vQx\„~b…yr‚wp{oo{oo{pvŠ……|neyjia`„[_dh™SZ‰NU„PZ†DNz=Hrd/>i/>i-=h+:e+6]%1X(-N%)K'*E&)D((<'';+':,(;*+6''3''0&%/%#/)'30-763=74>52;109109))6""///%"*$!(-&)#?<b_$€…'€…'~†%‹“2ˆŒ5|){{)€/ŠŠ8”•CšDž GŸ¤X¡¦Z¤sŸ¦utzp +dhjuyvjmjhfdjigqnqtpsvqyvqywt‚pm{PMi97S-)I'#C<5V>7X74RGDbJKdLMfNNgNNgILe36O"#@ "?##E&&H&#H?*%H61T6,V3*T6*Z8-\=2_>3`I?cWMqeXsi[vrZps\rx^pw\n{_oeueuhw€lx„p}ƒt}s||q~€u‚}vƒwq}njvhdpdbn`_jZ[fWXcY[aZ\cS_XYd^kvpo{tpzqmvnnrmptnrukosiel^RZKBN>CO?@TNMa[ShmVkqUgmVio\mkZkh`ldhtl{†mŒsƒi‰e†Žh…grr]99$ " !&&)*)*++,,+,% !*')++,.$%)%%( #.$,6)1;8BENX[lxfŒzŽŸsŠ›pŠ—i‰–hŠ•a‰”`Œ•X‹NrwH[_11/(&22,>>7FD=KHBMLAMLALO?NRAMR=KP:LN9MO:PO=RR@XY;]]@b`>ecAheAjfBjhDigCeaDa]@ZZ?XX=ZY9baBfgBcd@__=XX6SR3RQ1hhFooMqoKpnJunMtmLf[DeZCjbQqiX|ve€{i……jˆˆl‰’l™s–œ}šŸ¡¥‹¥©§©£¤‹›ž€•˜zŸ ‚¡¢„¡¥~ž¢{¢¯{«¸„¯À{«»w¬¹j£°a›¨Xš§W ¬f§³m¨²s¦¯p¨³x©´y£³u›«mŒœežgŠ‘|t{fX_rV]qGZyGZyB\|E^~C`‚Fd†AdŒEgFmKq”[Žvš©§•ˆ Ž–¡’ž|’œx‘šw’št’št”Ÿy˜¢|”‡‹y‡†„‡†„„„‚~‚~ƒ†}‡Š€„‰–~ƒkxškxšYxžWuœNm˜Ji”Hg’Cc@bŒ@bŒAc@bŒC`‹A]‰B_ŠB_ŠD^ŠD^Š@_Š@_Š>bŒBfGo“RzŸ[‚£b‰ªr¡r¡’~‚‡€‹…ƒ‰ƒƒ‰ƒ‚Š…ƒŒ††–|ƒ €Ÿ¡uŽŸs‘Ÿvžu’˜w’˜w•x‹’vŒ‹€Œ„„|‰™„‘¡ƒ—¦„˜§Š—§Š—§Œ—§‹•¦‹•¨‰’¥†’ž†’ž—šŒ–™•’Œ‘Ž=>@@@BBHIEKMHUSDXWG_^>ddDlo?ruDy€Cy€Cx…=x…=t†6t†6qƒ@qƒ@p{Up{Ujqbjqbjnigkfgkffjebhcagb^f^[bZZbWV^TU\MRZKMTEJRCIPBFM>BI<@F:AF7=B4;@19=/3:-3:-06*,3&*1"*1"&.$+ ('%%$$ &"(78(\]MWZERT?SU@\_J]bO;?,#!:8)aQDvfXl_‚pcsnˆ|wƒy{~uvpksmhoe`ngbqcg}MQgOXt`i…m¢tˆ©Vo˜B[…5Lz'>l1Cm1Cm$0U'L"(G#)H%'D%'D#%@#%@'*E13N5:Y8>]0:_-7\$5[)9`*9d,;f+:e*9d'3Z$/V%*I"(G&)B$'@((<((<();'(:&'7#%5$$2##1&%/+*352;:6@:6@74>32;109#'4#/"1"1#/!.# ."-, &L?!maCzw5~<‚†/…Š3‚†1~‚-‚ƒ6ŒŽ@E––Kœ›R¡¡X¤¡f¤¡f ŸSR3  KLJ{|yrqokjhlhhsnowtwyvyzy‚~‡zzˆYYfMHd<7S.&I$@,&K4.S,'H0,M:9WHGeNMjA@^# C5"!E''K%$I*)M/,Q,(M)&K)&K/(P4-U6/Y92\@6ZI?c\LgfVrr]ss^ttZpnTkoUlpWmqQkpPjwXo{]tzau~ey}h|k~|l{k€vk}tj|plxhdp_^i[YeWXcVWbY[aWZ`Xab^ghqxysyzrttrttuvsrrppslnqi_fYJQDBL=DM>ARMM_ZSdjVflZho^kr[kkYiiamfjuny…oƒxi~‹f†Žh…gtr\?>'/'$+# $!%'')'))+,-..-))##*,,3126""'!%!'!''.408=ERG]j^t‡b†št¢qŠlŒ—h‹–gŠ•a‰”`†’Y{‡OimHIL''&%#21/=;9DB9HG>LM=MN>NP=PR@RRBPQAMN>LM=NK>OM?RR@TSA[ZC_]GeaFhcHjeDjeDhaGd]C[WAYU?QT8\^Ccj@fnCfi=`c7\`2Y]/Z_.fk:qtBswDuvDqr?ec:jg?pqLwxS}\ƒ†aˆcŒ‘g™j”n— q™¢sœ¥v¢«|¡§{¢v™Ÿs›¡u©¬‚¶¸¶¸‘´µŽ±³…¯°ƒ®°{®°{°¶o´ºr°ºg¥¯[žªX ¬Z£¯k£¯k¤±v§´y¢®uœd†’Y•¡h–n‚‹cmmfhha_gq]eoUcwWeyTh}Ui~Sm†Tn‡Ur‡Wu‰j†‹‚ž£’§‘Œ¡‹–¢~’žz’œv’œv”œv•w—¡{š¥~Ž’€‡‹y†…ƒ‡†„„„‚„‚„ƒ…€‡ˆƒ‚†•|€ew›ew›TsžOn™Ml—Ih“FeBaŒ@bŒ@bŒ?a‹>_ŠA]‰C`‹A]‰A]‰?^‰?^‰>_Š>_Š?cCg‘Gq–P{Ÿ`†¢eŒ¨tŽšs™…‘‚‹ƒŒ†‚Š…ƒˆ…ƒˆ…Œ†’„‰š|ŽŸ“¥}¢{‘ tžs“žv’u–x–xŒ‘|Šy†ˆˆ‡‰‰z„}†’~†•¥†˜§†˜§‹•¦‹•¦Œ”¥Š’¢†£†£…‘†’ž™œ—šŽ”‘‹?O&?O&=>@@?AADFAIJFRQAWVF]]=_^>eh8kn=ou9qx;q~6t8r„3r„3k}:k}:juOjuOel^el^eiddhcdhcbf`^e_]d^[bZW_WV^TS[PPWINUFKSDHO@FM>CKC5;@19=/6;-28,17+,3&,3&*1"'/ %,#*%$$$$$ & &+,KK;_bMUXCOR=VYDY]JTXE&%+)F6)\L?n\Ozh[zniupƒy{|rtniqhck[WeRM\AE[9=SFNjaj†k€¡v‹¬g©`x¢Ph–AX‡FX‚@R},8]*O!'F#)H%'D%'D#%@#%@$&A+-H-2Q49X-7\)3X#4Z$5[&6a(7b&6a%4_%1X!-T%*I"(G$'@ $<%$9##8%%7&&9%&6"$3$$2'&4+*321:;8A<9B:6@:6@21:-,6!&3$1#2#2!.#/# . ,/#0$4( +3& ROvs2~ƒ,‚†/…‰4‡Š5‚ƒ6…‡9‰‰>ŒB–•L˜˜Oœ™^Ÿœb––va :9;|}zpqohfdjigrmnzuv|y{|‚‹uu~TTbMM[MHd>9U+$G#?%D'!F($D'#C$#@))F&%C5=# C"!E#"F%$I&%J-*O-*O-*O52W;4\?9`E>hIAkTJnUKodTom]yt_up[pnTkjQgpWmtZppPjjJdlNepQhnUioVjp\ozfyxh}tdyodvqfxplxhdp_^i[YeWXcVWbVY_UW^V__cklpwxntvnqpnqprrppqonqinqi[aUCJ=@J;EN?BSNK]XP`gRbi[ip^krZjjZjjepjmxrz†p…‘{„l„l‹“m‡jzxbTSFKWdYlyn“mŠx¢qŠlŒ—h‹–g‹–bŠ•aˆ”\{‡OknIKN**( '%#753BA8GF=HI9LM=OQ>NP=NO?MN>MN>LM=MJ=NK>NM;ON_Š=^‰A]‰@\‡A]‰A]‰?^‰?^‰>_Š>_Š?cCg‘Hs—S}¡a‡£g©tŽšrŒ˜ƒŒŽŠ‚Š…‚Š…ƒˆ…„‰†„‚‰”‡ŽŸ’£…“¥}¢{“¢w‘ t” w’u’—y–x’}Œ‘|†ˆˆ…‡‡z„€Š–‚’¡‡–¦†˜§†˜§‹•¦Š”¥‹“¤‹“¤†£†£…‘†’ž—šŠ•˜“ˆŽ‹EU+EU+@N*?M)=?AA?AAEGGKJFNNISWDUYG\b<^d>el;gn=kt8mv9kv5kv5cr9ds:boIerKbiZ`hY`e^bf_`d_^b]]bZ\aYX]VUZRTZNQXKNVEJRAHO@GN?CJ=AG;?E9?E9;B57=17=13:-06*06**2((0%'-#%+!$* '$$$$$$%& %38)Y]JV[HLS@PVCQ_HYgQ5C5$* -3)G=:\ROiZ^vfkwoyrjtdbn\ZfPTcKO^JMX?:B?:B;9>:8=21:*)2 &0%/%2%2#.#.!$/ #.##*&&-)%*'#($! 0.\Y,urE~„6~„6|…(ˆ‘4‘—GŠ@†‰Dˆ‹F‹‰Y”’cŒŠorpU-1% +)*(uvswuujhhlhgqmkvuxwwy~~„…sqUR`KKXLLYKIcB@Y/,J# >!?"@% C&!D# C!A @?#"F$#H$$F&&H&$M%#L%#J1/V=6^B;cH=eMAj^KnbOrgRndOluXoz^ux]quYnqRitVmyZsz[twVttSrnNokLliFi^<_T@ZgSmvf{tdyjcrmetllzeesY\gSVaRW]SX^TY_RW]RZ]fnqpwxjqrlnpmoqnrmptnnuhiocJRACK:=LMM?NK@OLANICKGAGE>@>7;93FD=GGAQQJZXOZXO]YH[XGf[Mf[Ma[J\WE[\C`bIlrEsyLv‚Ev‚Ew‰;z‹=};};~Ž<{‹9;~Œ:}‰<‹>‹L†QŽ–^—_‘œa—¡g—¥h–¤f—£d–¢b“žc“žc˜jš m £w¢¦z©§ƒ¬«‡«¬«¬¥¨x¤§v¨­tª°vª²q¨°o¤°e¢­c¡­`Ÿ¬_ž©až©aš¤lŸ©q¡ªt›¤ošfŽ˜e”œo•h„ˆj}cyzjwxhtwnvypqywqywnx}mw|m{€o~‚“‰ŒŸ–•§€“¥}• z‘›u‘›u‘›u‘œt” w— }˜¡~‘~ˆŠw„†„†‚…ƒ€ƒ€|ƒ{|ƒ{…€…‰ƒ†šw{cvœatšUp›Sm™Jk•Fh’Ef‘CdŽ@bŒ@bŒ@_Š@_ŠB_ŠA]‰A]‰A]‰>]‡>]‡?a‹?a‹>bŒDh’Ks˜W¤g‹¡k¤xŽ‘w…‘Š‚‡„Š…„Š…„Š…†Œ‡ˆ“Œ–…”¡˜¦„™¤|—¢y™¡{— z•œy“šw—z•x‡‚…Š€{…Š~‡ŒzŠ˜€ƒ˜¤„™¥‰–¦‰–¦Œ“¦‹’¥Š¦ˆ¤ƒ¢ƒ¢›„“ž‹˜šˆ•˜Ž”‘‹FV-FV-CR-BQ,>N,=M*9I(8G'9I(7F&7D+5A)2>(0<&/<#/<#,8"+7!+7!(4)1)1*2!*2!*2!*2!)1)1&.&.&.&.'0'0'0'0)1)1)1)1)1)1)1)1+0+0)/)/%0%0%0%0%/ %/ %/ '0!'0!$.$.$.$.$.$.$.#.#.#.#.#.#.#.#.!. -****++))))**''&&&&&&%'%%$%&&&&&&%%$$$$# # $!$!# # $!$!%%%%%%%%!&%!&%#('$)(%*)).-).-+0/.101333554767999;;>@@ACCIHCMLHPTARVCV\6W]7[c2`h6bk.dn1cn,cn,^m4_n5^kD`mG]dU\cT[_X[_XY]X[_YX]VW\TTXQPUMOUIMSGJRAGO>GN?BI;@F:>D8P^G[jSSaS*#(:0-QFC_PUcTXd\gf^i_^iXWbKO^LP`PS^FIT;@M@DQZd~ktŽVk‘QfŒJc‘Jc‘Oh’=U3Bf /T%,F$>!";!";! 7 62 4"=#>!C A#D%E$I'M*L!,O!*K(H#+C!)A (9%6 $3 $3%/#.%(3(+643>86B?:B@$!D"!E"!E&&H++M,*S0-V97^<;bCaQ/RA-GUA[l\pp`ujcrkdskkyccqWZeQT_QV\RW]RW]RW]Zbfrz}u{|ipqmoqnprqupnrmmtg]cWAI8?G6>NGDTMF[ZPed^osZkoWijYkmevfrƒr…•s‡—uŽœlnkkŽ–i‡b~„cszYeeY[ZODB@.,*"&!%'')**+****&($ *+'1547-,.&)()++0526;8CPE_lau‡f…—vŽ¢t¤u‘¢pŒkšmšm™g‹•d€‘Zv†OjyGZi6]f>bkCfoLfoLfjSVZBEH8@D3DD6II…‘D‡“Fˆ’SŽ˜Y—_“šc“žc™¤i–¤f–¤f—£d—£d”Ÿd”Ÿd•›h–œišr›Ÿs¤£~¦¥¨©}¨©}£¦u¢¤t£©o¤ªp¤¬k¤¬k¡¬a©^ª\ª\¨`¨`—¡hŸ©q¤­w¢«vŸªvž©uŸ§{œ¤w–™{’t€q{|lz}s{~uu|{u|{s}‚s}‚s…uƒˆ”ŠŠ“”¦~¢{”Ÿyštštšt›r’u— }˜¡~Œ{ˆŠw„†„†‚…ƒ€ƒ€|ƒ{|ƒ{€„‚††šw{cvœ`s™Up›Sm™Gi“Fh’Ef‘CdŽ@bŒ?a‹?^‰?^‰A]‰A]‰A]‰A]‰>]‡>]‡>_Š?a‹>bŒDh’NvšY¦hŒ¢jŽ£xŽ‘xŽ‘„‰Œ†„Š……‹††Œ‡‹‘ŒŒ–…’œ‹–¤‚–¤‚™¤|—¢y™¡{— z•œy“šw—z•x‡‚…Š€{…ŠˆŽ€‡—¤„™¥„™¥‰–¦ˆ•¥‹’¥Š‘¤ˆ¤‡Ž£ƒ¢ƒ¢›„“žŒ™›Š–™Ž”‘‹GX,FW+FV-EU+@R+?Q*=M*:K(9I'8H&7E)6C(3?)2>(1>%0=$.9%.9%+7")4 +3"*2!+3"+3")1)1)1)1)1)1'/'/'0'0'0'0'0'0'0'0'0'0'0'0'0'0'0'0$0$0$0$0$/%0'1%0%/ $.$.$.$.$.$.$.#-#-#-#-#,#,"+"+ - -+*******))))''&&&&%%%%%%$%&&&&&&%%$$$$$$$$$$$$%%%%%$%$!&%!&%"'&%*)%+(',)).+*0,-0/.10022244476799>@@BECCGDGMAHNBMPBMPBRVD8;B59?37=17=14;.06*-4'+1%*0$%.!$- #,!*%%%%##%%%%%'-3 PVC[]JRSATUE]^N`f\GLB" )(-76;IEHPLOTNQYSVSRWOMRNJPSOT[TSYSRVPMRMIUW`ghq\eYc}GZ€J]ƒE^…=V}7Kn(<_(A 4/.)'##!!""$',"1!%5&*9+.?04E37F48G68F7:G<>D<>D7:>37;97<=<@@CB>CB>C@# C$!D# C @=9<#A61T<7Z:4U:4UQIh_WvODcKA`UFdZKi^Lf]KddPjdPjoVjqYlwWquVpqQkqQkoQmsVqvRswStvQunImdBja?gW7^F'N6%EA/PTG`cVoe_pe_pefqabmSYcNS^LTYOV\SX^SX^glrz~„uv{mnronqrqsprmprmqwkQXK?OHFVOGZ^PdgXjkZln`qeewks†a|jˆe¤l•«e•«e—¬b–«a•¨c£^‘¡_‡–Ty…Lo|C`cGKN2/*&'""&"&"*#+**+)**''!)&".,*/0/40220226;8AGDP_Fl{b”c£r™¦q–£o˜Ÿn–lŽ›gŽ›gšd‰•^‡‘R}‡Hpx@gn6cg9im?lsBhp?goB;QVOZ[J\]MdbLhgPkkKjjJjsDoyJz†G~‹K“>„•@‚š7‚š7„œ4…5‡Ÿ7Š¡9Š 8‰Ÿ7Šž;Ž¢?‘¡J”£M“ž\“ž\–Ÿg˜¢j›¤p›¤pœ tšr›žtœŸuŸ {Ÿ {žzžz  ~  ~¡¤z¡¤z¢£w¢£w¢¦q¡¥pŸ§hž¦gŸªbŸªb©^©^œ¥bœ¥b§jŸ¨k §o §o¦p˜¡k— j–Ÿi—¡g– f™`ˆ”\‰a‰aŠjŠj†‰r†‰r…u‡wŽœz•£€—¤v”¡t”Ÿp’nœlœl›n”Ÿr— x–Ÿw‹‹{ˆ‰y„…ƒƒ„ƒƒƒƒ|‚||‚|‚‚…ƒ…›w{‘cvœ_r˜TošRl˜Gi“Ef‘Ef‘CdŽ@bŒ?a‹?^‰?^‰@\‡=Z…@\‡A]‰?^‰?^‰=^‰?a‹=dDk”NxZ„¨h jŽ¡zŽzŽ…ŽˆƒŒ†„‰†…Š‡„ˆ‡“Šƒ‘ ‡—¦•£˜¢|—¡{—Ÿ{–žz•›|“˜z•~“{ˆ…†‹ƒ€‰Š’…• ‰š¥ˆ˜¥†–£‰“¤‰“¤ˆ‘¤ˆ‘¤…Ž£…Ž£€Ž¡€Ž¡›‚“žŠ–™‡”—Ž”‘‹HY-GX,FV-FV-BT-@R+?O-=M*%0=$/:&.9%+7"*5!+3"*2!+3"+3")1)1)1)1)1)1'/'/'0'0'0'0'0'0'0'0'0'0'0'0'0'0'0'0$0$0$0$0$/%0'1%0%/ $.$.$.$.$.$.$.#-#-#-#-#,#,"+"+ - -+*******))))''&&&&%%%%$$$$%%%%&&%%$$$$$$$$$$$$%%%%%$%% $$ $$!&%$)($*'%+(',)).+,..-0/.10022244587:<<;>=@C@ADACJ=FL@IK=KM?MQ7NR8QV3RW4RX3RX3R[9T]25Ru;Nr/=V 2('#" $(!0'+:58J@CTCGVDHXDFTCEREGNDFLBFJ?BFB@EB@EC?DB>CB>C@;7<109+*3 #."-!.!.#-$.(4 )5%*6$(5&'7&'7%&1%&1+'1+'13*)1)(6*5)A8XOso)†ƒ=D’GŠˆXspA/)("iib}}vnngoohnklommrpuyw|uu|VV]OP[RR^SS`TTbSUdXYiUUkUUk97S&#?# C# C @>9:=#A83V=8[>7XA;\`Xwi`MCbMCbRCaPB_VD]ZHbcOicOinUipWksTmpPjlMflMfjLhkNirMoqLmlGjkFi];cZ8`K,SA!H3!B:(IM@YaTme_p]WhcdodepUZePU`PW]PW]QV\X]csw}z~„stxppuonqsrtprmprmiocCJ=8@/;C2:H8>M=?OHFVOH[_ObfXjk]npi{ons{Žiƒ–q¢j‘¦o™®i™®iœ°gœ°g–©d•¨c–¥c‘¡_ˆ”\|‰PorV]_DB=9+%"#($)#+#+++****'' ($ ,)(-205022244396DIFYhOp€f‰œk’¥u›¨t™¦q˜Ÿn–lœhœhœeŠ–`†QyƒDmt]‡>]‡=^‰>_Š@fHn˜P{Ÿ[…ªjŽ¡k£zŽzŽ„‡‚Š……Š‡„‰†…’‰ˆ”‹Ÿ†’¡ˆ—¦•£—¡{—¡{—Ÿ{–žz”š{“˜z•~•~‰Ž†ˆ…Š’‡—‡˜£‰š¥‡—¤†–£ˆ’¢ˆ’¢ˆ‘¤ˆ‘¤…Ž£…Ž£€Ž¡€Ž¡›‚“ž‹˜šˆ•˜Ž”‘‹M_0K]/J\.HZ,FV-FV-@U->R+".=$,;")8!*9"*6 (4)3")3"%0(2!(2!(2!'1'1%0%0%0%0%0%0%2%2%2%2'0'0'0'0%1%1%1$0$0$0$0$0%1'2%1%1#.#.#.#.$.$.",",$/$/#.#.#.#.",",!*!*!* ( ( (((&&%%%%%%%%%%&%%%$$$$##$$$$$$$$$$$$$$%%%%$$&&%%%%%%!'""(#$+%$+%'-''-').+).+*0,,2/252596:=::=::?>=AA>CB@EDGHCGHCGHCGHCHKAHKAILBILBHNDKQGMQJKPIKRJKRJKSHIRGIQIHPHHPFEMBEJ@EJ@BH>@E;@F:>D8>E6=D@>IBLe'5N ' +%   ! "(*.8>ALLLYKKXLJQJHOKGLKGLMFLLEKGAEGAEF?EF?EB>C>:?:;?9:>56?01:'+3"&/!.!.#-#-$.'1!&3$(5%*6%*6#*6'/;&+8',9)&4*'5+(/(&-/),0*-/&-$9'A/"J6.Q=5H959*&(!pidyzumojqmkvrprorqnq{x{xWUaQP[RQaTScUXeXZgVYkUXiVWnABY-/L#$BAA@=<99?& E/)O9-O:.PK7XP<\S@^ZGe]HfZDc[C`\EafJfiNinPknPkjIgjIglKjkJhmLkmLkiGhcAb]hp=PY*:C`i:ktEer7hv;mv7pz;n|8n|8l}6q‚;z‚Cw~@eq:OZ$BH#JP+KS;KS;P]EZgNfoMgpNoxKs{O|ƒK}„LJ‚’N‰›JŠœL‰ ?‰ ?Š£6‹¤7Š¥1Š¥1¥4¥4Œ¥:Š¢8‡›?ˆœ@Š˜KžQ‘•g”˜j››…Ÿ ‰¢¡–¤£˜«©›­«ž­«ž­«ž²«Ÿ´¬ µ¯ž²­œ³°ž²®±°’®¬®¯Š©ª…©«{¥¨x¡§hž£ež¤cž¤cž¦`ž¦`Ÿ¦c¤`˜ž]—\’˜X’˜X‘™Z‘™Z‘˜\‘˜\‘™Z˜Y‘›V”žXŸX’¡Z“¢[“¢[‘œ_›^Žša’Ÿf—£k–¢j•¡h•¡h–Ÿg”žf“e“e“›j˜ŸnšŸu”™pˆ‹v…‡r‚|‚|€€~‚{~‚{~~~‚‚‚{‚œqy“`qœ]ošRl˜Pj•Fh’DeCdŽ@bŒ@bŒ?a‹?^‰>]‡@[†?Z…>X„>X„<\†<\†=^‰>_Š=h‘Ep™S}]†¦pŽ pŽ {Ž|Ž†Œˆ„‰†…ˆ…ˆ‹ˆ‰”‡Œ—Š’¤ƒ•§‡™¥—£— }— }•ž“›|‘—}Ž•{“‚“‚‡Ž‡ŽƒŽ•Š•œ‰š¥‰š¥‹–¤‰”¢Š“¦ˆ‘¤†£†£ƒŽ£ƒŽ£~Ž¢}¡{š€“ Œ˜ˆ”™Ž“’‹N`2M_0K]/K]/HX/GW.CW/@U->R,:?>=AACE@CE@EGBGHCEI?HKAHKAHKAFKAHNDINFHMEGNFFMEFNDEMBDKCAIAAI??G=AG=?D:JC=E>8D?4IC9LH9OK;UNMXRQTWbfit\j…O]x;SxAB:?;7<56;459/09+-6#'0#+!.!.#-$.%/'1!&3$(5$(5&+8)0<+2?+/<',9)&4)&4-*2-*2/),-(+4+#0' 9'8%5!7#-4&!kc^~wqlmiikfrnmtpoqnqyvy|y‚`]gRQ\US_VUeVUe[^k^`m\_pBEW-/E&(>')F$%CAA??>=> @#C'!F0%G:.PL8YR>_[Hf_Lj_Ih`JiaIfaIfiNiiNikNikNijIgjIgkJhlKjlKjiHfcAb^<]Y8]T3X@%L: F2E1D, B4)JD=SMF\MIZIFVPQ\bcnZ`jOT_QX^QX^X]aqw{ƒ„‰|}‚tuyrswuuutttstqpqoJRC4;-4A/8E37G4]‡?Z…?Z…>X„?Z…<\†<\†=^‰>_Š>i’HtœX‚¢a‹«q¡nŸ}}„‰†ƒˆ……ˆ…ˆ‹ˆŠ•ˆ›Ž”¦†”¦†—£–¢~— }— }”œ}’š{–|Ž•{Ž’“‚†Ž‰Š•œ˜ ˆ™¤†—¢Š•£‹–¤ˆ‘¤†£†£†£ƒŽ£ƒŽ£~Ž¢}¡{š€“ ‹—œ‡“˜’‘ŠŽŽQc5Pb4N`2K]/J[1J[1EY2CW/@U->R+;M*?AA@BB@BB@FC@FCAGDBHEDIFDIFBGGBGGBGG@ED@GA?E@=B?:B:8?7:@68=39?49?48>25/5=.1;+/8).7(+5&(1$)2%%.!"+!) (&$$$%#&&&&%%&%$$''%.!FOBhe\[WNVULWVMY`Xah`*3: * -,)2;8AJAGI@FF@8D>6C>1B=0CA4DB5GA>QLHVQRfbbll~bcuQ^P]EVz?Pt=LjBQn=I\#5#   # !*01:IKTZWaZWa_Xe_XeVT`US_WT]OKUKIPDBI>;6=;6=64;53:)*3)*3(+6(+6$1#/#-#-$.%/&0&0!&3%*6&+8).;-4C.5D,0?&*9(*9'(8)'3)'3)&0)&0($0'#/'"*#&"##$0+,ojk{ysolfhignommlomlowu|{x€dclVU^TU`ST_SUdWXhVWnJLb&)D/9%#C''I%%G C?==@""DA@)D.$J8*MB4VR>_XDe_Fh^Eg]Ac_DfcHhcHhlOjlOjoMinLhjJfkKgmMinNjnNjjJfcC_a@\Y?^N3S:%H5 C+D'A'>.&E:9JBBREJPGLREQVUafXcjMX`RWdOT`^bj|€ˆƒ„‰yz~uv{tuytrwsqvtsvvuxeik@DF,8/2>55B7;H=?OHDTMH\VYmhlrq†x›r‡£zŽ¨q‘ªt”­w•¯x˜²y™³z¯xš­v›¬lš«k—«c•©a”©]“¨\‹œW€KrzM_g;A=4-)!$ '"%$ ,$ ,!!.!!. * * *')+..%(.79@\bVu|oŽn}™ªx—¨u˜£o—¢n–¡m—¢n–¢j–¢jŽšaˆ”\}‰Lmx;eo6dm5fo9fo9gp:fo9fr;gs=kt9ow=oy:mv7ov7rz;y‚?~‡C~†E‚‹J†JŒ–P™U™Uˆ”P‡“O“Q„–S„–Q…˜R‰šS‰šS¢V¢V‘¤Q“¦S’¦Q¡LŒ D¡EŠA‡›?ˆ™?…•<ˆA„Œ>‚‚KƒƒL€wi€wivpz‚|†’‘œ¬ª¶±²½µµÁ»¸À¾¼Ã¾¼Ã¾¼Ã¿Â¿ÀÃÀÃÈÁÅÉÂÇÊÀÅÉ¿ÀÅ°¿Ä¯Âŧ½Á£¾¼˜¹¸“®¯Š¤¥Ÿx›vœ¡q™žm•œg“šdŽ“c’bŒdŠa‡†`‡†`†„`ˆ‡cˆ‰^ˆ‰^‰Œ\Š]‰’\‹”^“žc– f“ _[Ž V¢Y’£^”¤_•¥`”¤_–¤_”¢^–¤_–¤_”¢^–¤_˜£h“žcmŠj†ˆu†ˆu‚„v‚„v~„s~„s€|ƒ…€v~–qy‘]q›Zn˜Ml—Ih“Fh’CdŽAc?a‹>_Š=^‰?^‰>]‡;Z…;Z…;Z…;Z…<\†<\†=^‰>_ŠDk”OuŸ_„¢gŒ«s‘œr›‚•‹~’ˆƒ‚‹€ƒ‹€…‚Š˜ƒ’ ‹”§„”§„— …”ƒ—ƒ•œ‚’š{˜yŽ•{”z‹ƒŒ„„Œ‰‘”ˆ”Ÿ™¥Š˜¥‡”¢‰”¢‰”¢ˆ‘¤ˆ‘¤„¤„¤Ž¥Ž¥|‹¡}Œ£{Ž˜~‘œŠ——‡••Œ’ŠŠRd6Qc5Pb4Pb4N^5M]4G\4DX0BV.@U-%-<#-;$*9"*6 )5)3")3"(2!'1'1'1%0%0%0%0$/$/$/$/#/#/#/#/%.%.$-$-#.#.#.#.$/$/#.#.#.#."-"-".".".".!,!,"-"-!*",#-!*!*!*!*!*!*!* ( (''(&&&%%$$##""""""""!!""!!  """!""####$$##$$&&&&%&''("(" )# )##)$#)$#)$#)$$+%(.)(.)*0+.2-.2-1502612852855:77<9:<<;>=>@@>@@>C@>C@>C@?EA?EA?EAAFEAFE>CB<@@:A;:A;8>:7<98?75<45;17<25;15;13:-28,3:+18)/8)+5&*4%(1"%.!'/##,!* ( (&&$$%%&&&&%%&%$%%' (.6*\XOd`WZXOTRIPXP^f^W`h'/#*$ */,6A8>JAGIB:E?7EA4C>1?NIJZUVcdvop‚ao‘Yg‰@Ru0Ae:IfFVsO[m3?Q#  %()2DENYZc_\eb_ic]ia[gYXcRQ\IFOA=G96=.,3+(/%"*'"*)$,-*2-*2'(1()2),7(+6!&3$1$.$.$.%/&0&0#'4$(5%*6(-:+1@*0?(,<&*9(*9'(8)'3)'3)&0)&0+'3($0*%-"%#$<8=pklytupmgljdlmjpqopprvuxzwebj\[dVU^YYeVWbXYiMO^(*@-04;%#C&&H%%G C?=?$$F##E C C.$J4*PC5WK=_T@a[GhcJl^EgW<^W<^_EddIipRnpRnqNjnLhjJfkKgnNjmMinNjjJfhGceEa`FeV;[B-P;&I,E+D) ?1)G:9JEDTJOUGLR@LQNZ_YdkNYaNR_PUbfjr~‚‹‚†ww|tuystxsqvtrwsrtpprrvxptvEQI0=47D9=J?:JC>NGK_Zcwqp…vx†¢y‹¦}“¬v“¬v•¯x—°z™³z˜²y˜«s–¨q–§g“¤d”§`—«c”©]”©]‘¢]†–QyTjrEXTL;7/(#++'./+72.:.-;//<,*6*(4%#/)')! 0#"2#%,79@Y`Sx~ršz–£ƒ˜©v—¨u™¤p˜£o–¡m•Ÿl” g” gŽšaƒWv‚Egs5dm5gq9fo9fo9gp:gp:gs=ht>mu;mu;oy:oy:t|=v}>{…A~‡C€ˆG…LŠ”O—Q™UŒ˜T‘œX›W‰›YŒž[ŒŸY‹XŽŸXŽŸX‘¤X‘¤X“¦S’¥R’¦Q¡L¡EŒ D…™=ƒ–:„”;€7„Œ>ˆ9J~~Hypbypbrmwwq{‘›²°¼·¸Ã¸¹Ä¿ÇÄÂÉÄÂÉÄÂÉÅÈÅÆÉÆÈÍÅÇÌÄÉÌÃÈËÁÉ͸ÇÌ·ÊίÈË­ÆÄ ÁÀœ²³¦§‚£¥~¤¦Ÿ¤s•šj’˜c‘—b•dŽ“cŠa‡‹_†…_„ƒ\‚]€\‚ƒX‚ƒX‚…U„†V‚‹U„Wˆ’Xš`“ _œZ‰›R‰›RX‘¢]’£^”¤_–¤_”¢^–¤_–¤_”¢^–¤_—¡g’b‘oŒl†ˆu†ˆu„†x„†x~„s~„s€|ƒ…€u}•ow[ošXl–Ji”Ih“CdŽAc@bŒ?a‹?a‹>_Š?^‰>]‡;Z…;Z…>]‡>]‡>]‡>]‡=^‰AcFl•Pv `…¤e‹©s‘œpš“‰}†ŠŠ„Œ‡…›‡“¡Œ“¦ƒ‘£€–Ÿ„”ƒ”›“š‘™z˜y”z”z‹ƒ“‡‰‘”‹“—‰• ‹—£‡”¢†“¡†’ †’ †£†£ƒŽ£ƒŽ£€¤€¤{Š |‹¡{Ž˜~‘œŠ——‡••“Ž‹‘ŒWf8Wf8Ve7Sc5Qb7Qb7I^4EZ0CV1BU0?R/99@:9?<:@=:@=9?<9C;6@79?49?47>65<45<45<42:008.09,09,,6'/8)0:*.7(+3)*2((0%%-#$- $- #, ())((&&&&&&%%%%%%&&&&$$+#?DIA>H@6?;2=91=91@<3HC=UPJbZ]skoel‚bh~BVu0Cb2Ie=SoFTm>Le#/=!*89DWV`^]g^\h_^i^\hVT`DCS87G+(6*#"#% ) $,%)1(-:+/<%,:&5%2%2'3#*6")5#*6%,9'/;(1?(1?+2F$+?")<#*>%)8#&6((4''3''0''0%*."(+!%&)-.]a\|€{}|qpodifbmjfnroqtqusxwv{]ZaVSZVU^YYbXZgY[iEIZ. )+47 ="#@A##E!#E A>$$F''K$#H'#J'#J5)M>2VMpx>r{@x€F}…K}‡H‰J†“L‹—Q‹šSœVŸX‘ Y’¡Z“¢[”¤[”¤[“¢[“¢[‘Ÿ[‘Ÿ[‘¢]Ÿ[Žœ]›[™\‹—ZŽ–WŒ“T†ŽM‚‹JƒN}Kzt^rlVn^jqbml`‚od†^`ˆ_aŠˆ˜± ±É¬ºÇ­»È»ÃÆÀÈËÂÉÈÃÊÉÇÈÊÈÊÌÈÊÌÈÊÌÅËÆÄÊÅÅË¿ÅË¿ÇͺÇͺÇȱÀÁª·¶Ÿ­«•¯«•¦¢Œ–|‰o‹hŠŽghŒŽg†„i„‚gƒ|m€zk{spzrnxoi|rmyqgyqgzvfzvf€~cd‰Žd”jŠ”\„ŽVˆ•S[“¤d“¤d–£a”¢`–£a–£a˜¢b™£d˜¢b“^–e•d‹hŠŽgˆŒlˆŒl†‡n†‡n„‚€‡†„u}•nvŽZmšYl™Li”If‘@aŽ@aŽ@aŽ@aŽ@^‹?]Š?]Š?]Š>]‡;Z…?^‰?^‰<]‡<]‡;_‰>bŒGn•S{¢d‰£hŒ¦v•x‘–ƒ’‰„…Œ}…Œ}ƒ‡”„‘Ÿˆ”£Œ‘£€Ž¡~“ž’}‘œ|™z“—}•{Œ}‘~Œ’†Ž”ˆŒ”˜—š‰• Š–¢ˆ“¡†’ ˆ’ž‡‘†‡Ÿ‚¢‚¢}‹¤|Š£{Š {Š {š’Ÿ†•™„’—‰‘†Ž‹Wf8Wf8Wf8Wf8Rd8Rd8K`6I^4FZ4BU0@S0=P-9K+7I(4D(1A&1A'/>%.<&-;$+6$+6$*5#)3"'1'1'1'1$/$/$/$/&.&.&.&.#.#.#.#.#.#.",",%,%,#)#)!+",",",",",",",",",!*!*!*!*!*!*!*!*!*!*",!*!* ) (''&&&&%%%%%%###!!""!   !!"""""#####$$##$$$$&&$$&&( ( )# )#("("( ( *!!+"%.!%.!%.!(1$*1)*1)+4',5(.7//90.7//902915<46<76<77<98>:9?<9?<8>:8>:8>:7<95>64=55;139/18007/07/07//7,/7,.6*.6*.7(,6',6',6')1')1''/$#+!#,#,#,"+)))(&&&&''&&&%&&'&%%$$&+#KPIji^ZYNRULTXNT[\agi?IS +"'"&*%/=52F>;IB:D>6A=4@<3=9195,?:4HC=RJNjbeciciObBVu;Qm>TqESlESlALZ* $((4GHS_^h]\e^\h`_jTR^<;F! 0+&" "$"*',9).;$*9&5'3'3'3#*6'/;*1=*1=,3@.6E.6E18L+2F%-@$+?&*9$(7)*5((4*)2+*3%*.$(@DFquw|€{rvqmmakj_pmitqmtwuy|yusx^\aWT\[Y`TS\YYb_an>@N((/249: =$$F&&H!#E')K44V33U+*O''K-)P-)P@3WG:^UDiWFk[AeZ@d^@b^@bZ<`[=b`@a]>^bD`hJfqOiqOimNgjJdjJdiIckGfkGfhGenMlqUsmQnS=\E/N<.P7)K2/F:7NDKSHNVDTMDTMCTQEVSR]dQ\cOT__eo~€‡~€‡{{‚uu|sszqqxur{spyhfm]Za[^beimmuxqy|GVS2A>:GCIVR`rkpƒ{}‘~‡›ˆŠ£‹¤‚Š£Š£¥¥‘¥}‹Ÿwˆ™m€‘e€`{‹[}ŽU…–]Ž¥[•¬b–¨^”¦\‹œWJyYmsNed[_^U_bYdg]dh\dh\_dUY^PQROKLJ?CE59:*+6%&1$,/>FJer^Žz–¤y—¥z™¨t•¥p“žo’nkkb‰–[ƒ‘Tx†His;gq9em:gou}Cy‚G~†LŠP†Q‡‘R‹—Q™SŸX’¡Z“¢[”£\–¤^–¤^–¥\–¥\“¢[“¢[‘Ÿ[žYŠ›V‰™U†”T‡–V†’U†’UŠ’S‰‘R…L‡F{}HvxDpiTpiTfVbpall`‚od†[^†`b‹¡¹¢³Ì®¼Ê³ÁξÇÊÂÊÍÄËÊÄËÊÇÈÊÈÊÌÈÊÌÇÈÊÆÌÇÅËÆÆÌÀÅË¿ÇͺÇͺË̵ÆÇ°½¼¥¶µž®ª”–“|‰‚h‰‚h†‹c‰f‹fŠ‹d…ƒhƒ€e€zk~xizrnvnkvlgtjeph^ph^pl]pl]urWrpUv{Q‚‡]‹•]†X~‹J„‘Oˆ™Y“¤d–£a”¢`–£a–£a˜¢b™£d˜¢b“^—f–e’j‹h‰m‰m†‡n†‡n„‚€‰‡…v~–muŒYl™Wj—If‘GcŽ?`>_‹=]Š=]Š@^‹?]Š?]Š?]Š>]‡;Z…>]‡>]‡<]‡<]‡<`ŠCg‘Ip—U|£d‰£hŒ¦u”x‘–ƒ’‰„…Œ}…Œ}…‘‚Š–†“¢‹—¥Ž’¥‚¢“ž‘œ|š{Ž˜y•{“z‘~Ž’€•‰‘˜‹—š˜›‰• ‰• ‰”¢‰”¢Œ•¢‘š¦’š©Œ•£‚¢‚¢|Š£|Š£{Š {Š {š’Ÿ†•™„’—‰‘†Ž‹We:We:Xg;Xg;We:Tc8Na7M`6G\4EY2BU0?S-=P-;M*8J*6H'1A'/>%.<&-;$,7%+6$)3"(2!'1'1%0#.#.#.$/$/%-%-%-%-"-"-!,#.#.#.#.#.%-%-$,$,",",!+!+!+!+!+!+",",!*!*!*!*!*!* ( (!* (&&&&%%%$%%$#$$##!!  !!!!  !!! !!!#####$$$#%%%%%%%$$$$%%' *!'')) *! *!!+"!+"$+#$+#$+#$+#(/'(/')2%)2%)4))4)-7,/9/1803;33:45;55:75:75:75:75;53:4293293.7//9039/16,.5-.5-,4,,4,,4*,4*+4'+4'+5&*4%(1"(1"'/$'/$'/$#+!#,"+"+"+))))&&&&'''''&&&&&&&%& '*1)VSOda]\XVWSRVUSba_Waf-7<  &)*#+6.6F=>J@B@:7=84;:198/<7*?;.E>8XQKfgkqrvalƒ\f}Qc€O`}P`{Qa|R]r,7L + " *42@XVdecq`^l[]lYZj;BQ%3,-(#! !#' ,'(8)+:$(5$(5$*2$*2#,3%.6*3:-6>07C18D2;J5=L3;N.5H*1E%-@&*9%)8((4((4%)1#'0$'+>AE€|z{wvtkpnekhdokgtppwrszx}usx_^hUT]XWaWV`ZYiZYi67I$-/469; A!#E$'H(*L%%G++M;8]63X-'L)#I0(M91WG8_L=dWAg[Fk^AhZ=d\>c\>cX9^W8]Y;^X:\Z<_^@bcEciJhjIghGejIgjIgkIjeCdaAbhHipQqkLlV;[O4TD3SA/P=4H>5I?DJFKQDVHDVHCVLCVLO\_Q]`V[fjoz}…y|‚ww}uu|tt{rryvuytrwab`YZX[[`cdheiomqxmstKQS?MDP_Vk{f}yˆžŒ¡ƒŠ †‡žƒ„›…‚™ƒƒ›ƒ‚š‚…–x‚“u|‰jn{\nx[t~ax„[|‡_…™X’¥e–«_“¨\£T†œM‚“UQ{ˆY|ŠZ|W~‘Z}‘W{ŽU{‰Sv„MmxKhrEZbCS[B6@Ioz?r~>uB{†B‹G„Jˆ•O†”P‰—RŒ›TŸX’Ÿ]–£a–£a–£a” c–¡d“ _’Ÿ]‘¢[ YŽ V‹Tˆ’XƒŽS~ƒZ~ƒZ„€c‚~b€|f{e}ukvndpbii[b\Jr_Nu[OˆXK„HNMS‘G\˜`t±ˆ¡Ã”¬Ï­¼Ç³ÁÍÁÅËÃÈÎÇÈÊÇÈÊÆËÊÅÊɽ¿ÆÌÈÅÍÊÄÌÈÅÌÄÅÌÄÅÌÄÅÌÄÈËÁ¾Â¸Ã°±·¥¦žƒˆ‰nsˆptŽvz†}o†}o…€m†n‡}q…{o}tszqpwjwrdqi^ph]oh\pk`tnbrl`pi]hk^itmgunh~f†ƒk…Y…Y…\Œ”cœe• j‘žc‘žc”¢b•£d’¡až^’žaš]–b—cŒ“d‹’cŠjŠjˆ…z‰~‘vw‰_o‘\lMh’KfBaŒBaŒA`‹A`‹@`ˆ@`ˆ?_‡?_‡<\†>]‡>]‡>]‡=^‰=^‰=a‹Dh’OvX¦gŠ¢kŽ¦w‘{””…’‡€…‹ƒŠ}…’€šˆ˜¤‡™¦ˆ—¥ƒ•£€“ž’}š{Ž˜yŽ•{”z”„”„‘•”˜’– ‘—¡Š•£‰”¢Š— Š— –¡› «¥¨ª¥¡£ž•’ Žœ‰ ‰ vŠ xŒ¡x›{’Ÿˆ˜—…••Š‘‡ŽZi>Zi>Xg;Xg;We:Tc8Na7M`6I]5F[3CV1?S-=P-;M*7I(7I(5D+3B).<&-;$,7%+6$)3"(2!(2!'1%0#.#.#.$/$/$,$,$,$,"-"-"-"-"-"-",",$,$,#+#+ * *!+!+!+!+!+!+",",!* ) ( ( ( (' (''%%&&$$$$&&##$$##!!        !!!!!!!#####"###""$%$$$$&&%%(()) *! *!!+"!+"%-%%-%'.&'.&(/'(/')2%)2%)4)*5*,6+-7,1801801723:4285285285285293172172172-6.,5-.4*-3)+2*+2**1)*1)*2(*2()2%)2%*4%*4%(1"(1"'/$'/$$,"#+!#,"+"+"+))))(&''&%&&&%&&&&&&%& '!( :73_\Xd`_WSRSQOVUSU^dT]c!* +$$#$&'5+-C9;F@<3><3=8+:5(;4.D=7QRWtuynycm„PbM^{IYtWg‚do„ITi!/  "$".?29F4;H5=L5=L3;N/6I+2F'.A'+:&*9''3%&1#'0 $,,04orv„†uwrrqhqogtqmwtpxst~yzzx}_]bXWa[ZcWV`\[da`p>=M%%.4%'D&(E!$C"%D)+M)+M*,N,/P++M**L63X41V0*P.(M3+Q:2XI9`J:aM8]O9^S6]R5\X9^[=bW8]U7\X:\Y;^Y;^[>`^@^`A_eDcfEdfEdkJhlJkeCdaAbbCcdEebCcV;[R8WJ8YF4T?6J?6J;?EBFLDVHDVHCVLCVLLY[Ubd`fpv|†y|‚ru{sszqqxsszsszusxusx^_\YZX]^b]^b\agchnmsthnpQ`W]kbt„o„”€‡~Š …›ƒ™}”~{’|{“{|”|‘s|ow„dgtUfpSlvYvYtV{ŽMŠ]“¨\”©]’¨YŽ¤U‹›]‹›]‹™in’¥m‘¤l£j¢hŽœe‡•^€‹^xƒVmuVfnOX\PX\PW`Shqd|„l——¥|–£{“ soŽ˜mŽ˜mŒ–iŒ–i–e„Ž]z‡Ljw]‡?^‰?^‰=^‰=^‰=a‹Dh’NuœZ‚©hŒ£j¥x‘’x‘’…’‡€‡{…‹†“’Ÿ™¦ˆ—£†–¤‚”¡“ž‘œ|™zŽ˜y–|Ž•{‘—†“™ˆ”˜’•™”– – ‰”¢ˆ“¡ˆ”žŠ— š¦Ÿ£®§¬­©¢¤Ÿ’žŽ‹™}ˆŸ€Š¡vŠ xŒ¡yœz‘†––„””Š‘‡Ž[g<[gN,?=eaa^ZZRPNSQOU\VbhcAQW # $ "%$.%->56J@BH@9.>9.;4,82*A:;`YZllzddrVczMZqLXo]jil„fi‚8BU$""%0HDPlhtmjznk|ah{>EX11-)  #&"-#'1%(3&)4&)4'*5&,6(.8,1J9?N7=L3;N,4G'0C&/B"*;!):#'4#/#/!.XZ\€ƒ{{tvvorrmvuqwtw}z|z‚‚}„ebjTRYYXcWUa[\gabmJK]&0-4"=(+J37V13U57Y;;]??a69X37V81R93T@7X;2T>2V=1U@.VA/WH6_I7`H2_D/[C,VE.XO3^P4_N3[N3[P6\T9`X>d[@g[>`[>`^>abBeiGhiGhjKkjKkbCc^?_aB`]?]Y<\Z=]U=\P8WG7RD4P?7HG?QEPLISOFYJDVHFWTSdamrvv|sv|npvopyrt}rt}rt}uu|ppvY[]UVXY\[Y\[\]_cdfejilqppzjpzj~’lŠxˆžv‰ w„œ}—xx‘wwuz’n{“o„•n‘jzƒgblO`fUfl[pv^mr[nzO~Š_Šœ^‘£e•©`“§]“§]“§]–¨eœ®k±pœ°o ±oœ®k™¬`—ª^‘¢[‹V’T{ŒNp~NmzKr|X{„a‰”k” wœ§x—£s“p›n—l‹”j‹”j‹”j‰”`‚Yv~Mel;\g1bm7cn8`l6eu2kz8n:sƒ?x†D{ˆF~‹J…’Q„”Rˆ—Už\‘¡_’£^’£^”¤_–¦bš§e—¤b—¤b”¢`“Ÿb’ža’œd—_ˆ’Y‰QƒP†“S…’Q„‘O€TpqE^[G^[GYUMUQHJHMGEJNHgF@_3/d?;p>K„LY’FaŒh‚®†¥ÃŒ«Èž´Ã§¾Ì¸ÄɺÆ˸¹ÄŒ˜œ™© ®±³ºÆÈÏÄÌÈÅÍÊÄÌÈÄÌÈÅÌÄÅÌÄÄÊÅÅËÆк˸¢³ŒaˆoDkpQxwXsbywf~wnoxop|rt|rt|sr|srzosxnruiysgwiaye]uj_zpe€od}pf~ufuftgrxlw‚vqxlgh]Hg\GifFokLrxR‡a‹œjŽŸl‘¢hžeŽœ]Žœ]›^›^šaša’˜cŽ•`Ž•`Ž•`ŠŽor……‡€jt‡gqƒWj‹TfˆH`ŒG_‹D_ˆFa‹@^„@^„A^‡A^‡A]‰@\‡@\‡@\‡<]‡>_Š@fGm—NxžZƒªlŽ¡j {’Œ}“‡‘‚ƒŒ}€Š{†€Žž…“¢‰˜¥†•¢‚”Ÿ€”Ÿ€“ž’}œ|šz—‘™—‰’š‹–˜š—™›—¡•Ÿˆ’¢†‘¡†’ž‰• ™¤—¥°£²­ ¥ “˜Œœ–Š™‰ŸˆvŠ¢yŒ¤y‘›{“…••ƒ““Œ‘ŠŽ\i>\i>[g>[g>We:Tc8Pc9Na7M_7I[4GX3EV1AR/?O-=L,8G'4B+0?(-:&,9%*6')5&*4%)3#(2!%0$/$/#.#.#.#.$,$,#+#+ * * * *(( )!*#*#*")")))********''&&&& (&%%$$$$&&''%%####!!     !!!!!!!"##""######$$%%%&%%'&&&( ( *! *!!+"!+"!,!,!,!,$- %.!'/#(1$)1')1'+3),4*,6+-7,-7,.8.08.08./6./6..5-.5-+2*)0()2%)2%)2%)2%+0(+0(+0(+0((0%(0%'/$%-#%-#%-#(0%'/$$,"$,"$,"$,"#+!"* "* "* ****))(())((((''&&''%$$#"#!()'NIJideVUSNMKOVP[a\Rbi.>E +"! +!":02E=:JB?FA6?:/>80=7/;45G@A\\jkky[h~N[rP]tWd{\_x_b{V_r *<  # #.JFRnjvmjzliy]dx3;N20)&   "%) #.#'1&)4'*5),7(.8+0;-3=.4>.6B3:G8>M9?N6=P,4G&/B'0C)1B$,=#'4!. -OT`xy{wxzuunrrlvuq{zu|~{~‚}„qltQOVXV]XWb[YebcnVWb-)008&)D/2Q03R13U=?aAAcCCeGJjADdB<]GAbG>_90QB6ZH<`K9`D2YB0YD1[C-ZB,YC,VC,VH,WN2]N3[M1ZL1WS8^T9`[@g`Be_AcbBecCfhFfjHilMmnNogGhaAb`A_]?]Y<\Y<\W?^U=\K;WG7R>6GC;LCNJISOFYJGZKIZWVfdouxuz~prynpvopyopypqzqs{uu|ppvY[]UVXVXX[]][\^\]_afejonyƒt‰y‰œw‹žyˆžvˆžvšz~–wx‘wuŽsym{“oƒ”m~hwdakN_eTagVmr[jpXamBrTƒ•WŽ¡c”¨^–«a”¨^™­c›­j ±o¡µt¢¶u£µr¡³pŸ²f™¬`˜©b•¦_Žža‰š\ƒ‘a€Ž^ƒiŠ”p›r•¡xš¥v•¡q’œošmŒ–k‹”j‹”j‰“i‰”`‚Yt{Jbj9]h2`l6`l6`l6hx6m}:sƒ?xˆCKƒN…’Q‰–T‹›Y ]”£a–¥c–¦b–¦b–¦b–¦b˜¥c–£a”¢`“ _›^Œ˜[‹•]‰“Z„ŽV‚ŒS|ˆIx…E}ŠH‚M‰Š_‚ƒX~|hsp\gc[]YPOMRZY]ZTs:5S0,a_Z{‡À„Ê{•À‹¥Ñ˜·Ôž½Ú«ÁЫÁиÄɾÊϪ«¶stšªŽ‹›’”šÃÅËÄÌÈÄÌÈÅÍÊÄÌÈÆÍÅÆÍÅÅËƸ¾¹Ÿ‰›gRcg@C:9B98@:7<73NHMibh]ZiQO]RQaUTda_redvcixLRa% )<M8?S/6I(2E(2E,4E'/?$1#'4W[aqv|rrrkkkursvtt{wz}z|{}{}sszUU\TS\WV`WUa_^iaan,,:--236$$?,*J0/O55W??aKEcOJhb\t]WoRL_B;O=1N>2OK:]I9\F4[D2Y?-\<*X;'V=(W>)Z>)ZC*YH/^L1_J/\K1\M3^R9aX?g]Bi^Dj`DhcFkhHjhHjkKnpPsmLkiHfdCabA_^?_^?_`@aZ:[S;XM5R@4JA5L@GHJPQGZKJ\NIZWVfdksvnvyjoumqxov~qwnu}ms{tt{nnu[\^UVXX\YWZX[^[_b`aj]oxkŒl‰–v’¥y¤x¢x vˆ™t…–qƒ“p’oƒ”j…•k‡’lƒgt~a_iL`gS`gSel_el_XdIfrWz\ˆ›j’¨`”ªb”«a—­c™­jœ±n¡µr¢·s¤µu¤µu£±r¢°p«mœªl•¥h”¤f¢aŽŸ_‹a¡f’¢k—§p™£r•Ÿm”n‘škŽšiŽšiŒ˜fŒ˜f‡a}‡WnqL^a;R?Kt‰”½›²Å¡¸Ë¨¿Æ®Ä̼ÈȼÈÈ¿ËÉÀÌÊÂÆÎÁÄͺ½È¯²½µÃɬ¹À³¾À¾ÈËÆÌÈÆÌÈÆÌÈÇÍÊÌÆÐÇÂÌÊÉcY'jU#gO.zR2~OAzTE~[Ny_S~maxrf|ukvukvultxnvznuxmtwn{zq~to†pj‚riƒypŠzk„qa{m[um[us^tydz€lv€lvwbj\HPG84UFBVWGrscƒrˆ”wr‹šnŠ˜f‡–d—c—c—c—c‹™]‹™]Š—\Š—\”j’i~Š‰y~~ƒyz~kr‡el‚Sf…PdƒJc†G`‚D]„D]„B]†B]†@\‡@\‡@\‡@\‡=^‰AcBh’MsœX£`‰«nŽ›mš€—ˆ–‡‡’€ƒ{„{Ž™…“£ˆ”¤‰–£ƒ•¢‚– – –|•œ{’š{‘™z—‚’™„’˜Ž˜ž”™œ –™žŠ’¡Š’¡‡Ÿˆ‘ ˆ•˜Œ™›¥£ª¢«¡¢¤š›ŒŸŒ‡š‡¡‡¡x‹£x‹£y‘›y‘›…••ƒ““Œ‘ŠŽ\j:[i9[g<[gN,>K&0.778#">'%F/.N76Y??aNHgZTsgbyd^vRL_C=PB5SE9VL<^L<^G5]A/W;)W9(V9%S9%S:%V:%VB)XD+ZH.[K0]L2]O6`Q8`S:bZ?e]BiaEjcFkeEhhHjiIlkKnnMllKjhGehGedEedEeaAb[<\TUXcŒ˜£Ì¤»Î©ÀÓ°ÇϳÉÑÁÌÍÁÌÍÀÌʼÉǸ¼Å·»Ä¼¿Ê¿Ã͸ÆÍ»ÈÏÀÊÍÀÊÍÇÍÊÇÍÊÅÊÇÃÈż¶ÀŸ™£|O{Y,X\*nf4wQ1}O.zG8qL=vTGrXLwcWmi]sndorgrultxnvxmtznuvmzyp}xrŠxrŠriƒsj„}n‡vg€wfwfze{}h}†r|„q{ƒnvvaiRD?K=8YYIstd…‘s‰•xr…”i…”b…”bŠ•a—c‹–b—c‹™]‹™]‹™]‹™]”j•kŠ‰y€…{|€ntŠio…Uh‡Sf…Jc†Ib…D]„D]„B]†B]†@\‡@\‡@\‡@\‡=^‰AcCj“NtžY‚¤`‰«mšlŒ™€—ˆ}•††ƒ{ˆ”€“žŠ”¤‰”¤‰•¢‚”¡– – —ž}•œ{’š{’š{—‚’™„”™™Ÿ•—›Ÿ”—›Š’¡Š’¡‡Ÿ‡Ÿ‹˜š’Ÿ¡ §Ÿž¦ž§Ÿ›’“ŽŠ‰›ˆ¢‚‰£x‹£z¥x™z’œ„””‚’‘Œ‘ŠŽ^h>^h>\i>\i>We:Vd9Vd9Sb7O`4K]1IY2GV/BS/?P+=P-9L)7F-4C*1?+/=(,8&)6$(3(3(1"%/ $.$.",",",", ) )('&&&&''''&&''%%''''&&%$$$%$$$!!!!!!"""""!""!!!!!! !!!!""!!!!#%%%'''()) * * !,! * + +-- . . /!0 %/ %/ (1"(1"'/#'/#)2%)2%)2%)2%*1))0()0()0('2%'2%'2%'2%(/'(/'(/'(/'(1$(1$)2%)2%'2%'2%&1$&1$&0%&0%#.#"-""-""-""-""-"&,"&,"%+!%+!!+"!+"!+"!+""(#"(#"(#!'"'#'#'#'#''&&%%%%$$##! ! " $D>Ae`cVQTPKNPPPWWWV`eGQV &-!*;/8QCJVIPSFJNAFJ>@NBDd]^_XYMKKECCBAKEDMRR[kjs\^g ) %)FLPcaa_\]OPT459(! #%&!)%-!(0!(0'.6(/7+19-1@.2A.5D06E,4E.6F'1B#.>7?IQYcpswlptkjlhgjgggooousztqyts}ts}not[[`PSYTV]WW^XX_`ajWYb(*23" ;!:% A($D&#F&#F2,M=6WQKee_yocy_SjVH]M?SO=VR@ZXAbZCeTAdM:]A/W8&M8$Q7#O3#Q3#Q6&V;+[B-`E/bK1cM4eS6bS6bWgaBieElgFkgFkdDgeEhjKkiJjhHigGhhGehGeaAc\<_X9^R3XE2PC0N:8FEBPGNOLSTMYWXdbelrelrgnvjpxor}ps~mqyos{twvkmmch^V\RQURWZXY^Wdiao|a~Œp t“¦z–§t“¤rŸoŸoˆ›q†™o†™o…—n†–mˆ˜oŠ“qƒŒjpv^`fN\dS\dS]eT`iWZhLgtYz\Œžn©g§f“©h”ªi˜¬rš®t›®wŸ²zŸ±| ²}ž°{¯z®n›¬l™ªe›«f›«fœ¬g—ªe–©d—©h—©h—§k–¦j“£e’¢d‘¡_‘¡_Ÿ_‹šZ†\|‡SqxNelB^bJ_cLgmSpw]s€Ys€YzŒW~[†™[ŒŸa“¤d”¥e“¦h“¦h–§g”¥e’£b `¤\‹žW‹šS‰˜Q†”P„’NŽHŒE~ˆI}‡HtyF{€Mx~Kfk8]k&w…A}’1v‹*z“{”ƒ’0x‡%b\D_ZBQPgLKb[m™«Ì±ÃɳÆÌÂÈÉÄÊÌÆÊÌÄÈÉÁ¿Í¹¶Ä¯®¾¼»Ë½ÅÉÀÈËÂËÌÂËÌÅÉÊÆÊÌÉÌËÅÇÇϲб•³X˜i2qX&wc1‚O5€K2}:0x<1y<2x<2xA;wE>zRJvWN{aXsg^yoetrhxrivsjwqkwun{vt†€’}rqƒ}q}ql|l|zcwu_r}hy€j{lo~knn[\J78FECqpnŠ‚†ˆ‡‘‚}†wƒˆq„Šr†Œm‡nˆ’gˆ’g‹”e–g™`™`—c’šg‘•uŽ’q‡†{„ƒxz||wyymq€ko~ZgƒVdL`ƒJ^D]„D]„@[†@[†>[†@\‡<_‹?bBk”Ox¡[ˆ¥^‹¨r’˜nŽ””†’ƒ…|†‘}‹œ~”¥‡˜¥†–£ƒ—ž—ž—ž—ž”œ}’š{’˜~‘—}•†“˜Š—š˜˜œ™Œ•¢Š“Ÿ‘Ÿ€›„“žŸ˜˜¨ ª¥Ÿ¤Ÿ™Ÿ”¡™Ž›†Š ‚†œ{†¤~ˆ¦z¥vŠ¢r‰•{’Ÿƒ”˜’–ˆ•…’[e:[e:Ye:Ye:We:Vd9Tc8Ra5O`4K]1IY2GV/BS/?P+;M*7J'6E,4C*0>).<',8&)6$)4 )4 (1"%/ $.$.",",",", ) )('&&&&&&&&&&&&%%''&&&&$$$$$$##!!!   !!!!!!!!!!!! !!!!""!!!!#%%%'''()) * * !,! * + +-- . . /!0 %/ %/ '0!'0!'/#'/#)2%)2%)2%)2%*1))0((/'(/'&1$&1$&1$'2%)0()0()0((/'(1$(1$)2%)2%'2%'2%&1$%0#&0%&0%#.#"-""-""-""-""-"&,"&,"%+!%+!!+"!+"!+"!+""(#"(#!'"!'"'#'#'#'#''&&%%%%$$##!!)#&OJMhbeTNQJJJQQQNX]U^dGW]!(!'$9,2K=DUHMYLPQEGL@BQJKhabZXXJGHFENHGPKKT``ipqz@BK  #'BGK`]^VTTNOSBCG,/:" "%$&"*$,!(0$*2$*2&,4)-=)-='.=)/>%->!):4>OR\mhoziq{hko`cgedfhgjnnnooousz|zuu~jir\]aTTYPSYTV]XX_[[a]_h35>,+23!:%">($D)%F%"E @(!B5.OJD^VOjTH^H?A "(%$$!)"*"*$,%-"*2%.6$-4(0%+?HNoxygpqjkocdha`ddbggelliqkntkntwu|omtjir^]gTV]PSYPTZSX^XWa[ZcGGU""/ 0 0!9!9%">(&A-(K,'I(&H&#F/(I0)J@;W94P6+E4(CE2PS@^^Fe]EdaGfdIibKn`HlS=eF0X9&R6"N2"P2"P2!Q4$T<([@-`I4eL7hR6fQ5eT9gT9g^@idFofFneEleGigIlhJmeGi`Be[>`[9a[9aX6]V5\S3]Q1ZF.TE-S;.L=1NB@NJHVJQWU\a]dj]dj^dl`gofhuhjxfjrimvjll_ba^eX]cWT^NR[L]iUozfmž|•§w•§w—¨u’£qŸoŸoŒœn‹›mŸo pŽŸsŒqŽ˜t‡‘muzcagO[cQ]eT`m[lxfo`xŠjŒ r’¦w“§m“§m’¦j“¨k”§p“¦o–§t—¨u˜§z™©{˜¥~–£|”u˜p‹”j‹”jŽ›g•¢n˜¨q™©r˜©o—¨nš©nš©n•¥h“£e”¢b’¡a‘¡_Ž[‰—Z‚S€ŒS‹Rw„PvƒN{‰Wƒ‘_‡˜e‰šgŒ¡d£g”¦j•§k–¦j—§k“¦h‘£eŽŸ_‰›Z‰™Uˆ˜Tƒ•Lƒ•Lƒ”F‚“E’@{Ž;{‹9yŠ7xŠ3vˆ1{‰7{‰7v„2t‚0v†-y‰0})‘*|Ž"w‰s€#x†(jt5dn/`a_[\Z`p‹—§Â­ÀƳÆÌÀÈÉÂËÌÄÆÌÁÄÊÀÁ̼¼È¤¼§¯Ç¨³Ãµ¿ÐÃÇÈÄÈÉʼÓÁ²É®€¾yK‰Q&vS)xP1„N0ƒ?2?2=5}<4{<4{;2z;3y;3y81v81v?7xC:{LGvPLzZRvaY}k_usg~rlxtmzxmwl~qm~sp€sm~vq€v‡wˆxl‚th‚v‰}”“†’”ˆ“‘}‚kq‚yxŠ€………‰‰‰x€}t|xw‚uw‚u~„s€…tƒˆqƒˆq‡Žkˆl‰”g‰”gŽ™cšd“—p”mrŒoˆˆt„…p€~w{ysnoxkmv]h}XcxO`}N_|EZ‚G[ƒ@[†@[†?a‰DfŽFr˜Q}£c‰žeŒ¡x‘‹wŠ”}”}‰”{Ž˜€–¢…–¢…”Ÿ€”Ÿ€—ž•€•€•€”œ}“›|“™“™’™–œ•›Ÿ•›ŸŒ’¡Š‘ ƒ ‚ŸŠ––“žž¢©š ¨™¡›ž—’•……š……š}ˆ¢{‡¡xˆ¥z‰¦rˆ l‚šj€zŸ‚’™‚’™‰—‡•\f;\f;Zf;Zf;Vd9Tc8Sb7Q`4N_3M^2IY2GV/DU0@Q,GLhqvhqrajkdeicdhihmnlqnlsnlskntru{gelSQX``i\[dTV]RTZPTZNSYNMVQPZ%%3,/$#5&$=&$=&#?'$@)$G,'I%"E&#F3-N2,M61M.)E(71&AE2PQ>\X@_V>]Z@_^CcX@dU>bK4]D-V8$Q3 L/ N/ N2!Q4$T<([@-`I4eL7hT9hV;kY>kY>k^@i`BleElfFngIlhJmhJmeGi\?aU8ZV5\T2ZT2ZR0WQ1ZM-WF.TE-S;.L;.L<:HB@NGNSQX^Y`fY`fZai^dlcfsegtcgpfjrfihY\[[aUdj^]gXYbSbmYskŠ˜v’ ~•§w•§w–§t’£qŒžnŒžnŒœnŽo p pžrŽŸs™v‹•qw}eagOYbP_gVcp^p}kvˆg„–u¥v’¦w”¨n‘¥k‘¥i’¦j’¥m’¥m–§t–§t”¤v’¢t‘žwœu˜p‹”k„c„c‰–b“ l˜¨q™©r˜©o—¨n–¦j–¦j•¥h“£e”¢b•£d”£a‘¡_Žœ^Š˜[ˆ”\‡“Z„‘\‚[„“`‰—eŠ›iŒk£g¤h”¦j”¦j–¦j•¥i“¦hŒŸa‡˜X„•TJ{‹Gvˆ?r„:t…7z‹={Ž;wŠ7yŠ7{‹9xŠ3w‰2x†4w…3v„2w…3z‹1y‰0{Ž'{Ž'p‚ew mzs€#ku6gq2VVTPQNFVqŸº¬¿Å²Å˾ÇȽÆÇ¿ÁÈ¿ÁÈ¿À˺»Æ©±É¬´Ë£®¾®¸ÉÁÅÇ¿ÃÅ»­Ã‡xa3q\.lU*zZ0N0ƒN0ƒ>1€=0=5}<4{<4{:1y81v81v:2x70u<3t?7xFBpJFtVOr\Uxj^tocyoiutmztj|sizpl}tqys„{Œ‚”Ž„•Œ€–s‰’…œšŽ¤Ÿ’›šœˆŽ‘}‚‚yx‰€ˆˆˆ………u}ys{wvtvt|‚q}ƒr€†n€†n…Œi†jˆ“f‰”gŽ™cšd’–o‘•n‘srˆˆt„…p€~w}vsu~opy^i~\g|PbN_|G[ƒG[ƒ@[†B\‡?a‰EgHt›S¥c‰žeŒ¡wŠwŠ”}€“|Š•|‘œƒ—£†–¢…”Ÿ€”Ÿ€—ž•€•€•€”œ}“›|“™”š‚•›˜Ÿ’”šž‘–šŠ‘ ˆŽƒ ‚ŸŽ™š—¢£ ¨™ž¥—š”—•“‰‰‰‰}ˆ¢}ˆ¢xˆ¥z‰¦o„œf|”f}‹xŽ‘—‘—ˆŽ–…Œ”Yb9Yb9Yb9Yb9Tc8Sb7Ra5P^3K]1J[0HW0FU.CR-BQ,@M-8KIBVG?I7/9$!(!"!"  $"*&"! "   #9EJnquilplkmfehhffmjkliqmkrjjqmmtppvUU\BALdbn``lYYeVU^TS\PQ\GHSPP^AAO,1 6&%<''B''B'"E(#F("H*%J'"E% C'"E(#F& E"B'=.#D@1QH9YQ9]N7[R5\U8^Q8`K2ZE.XB*U5&R0"M.N.N0 P5%U7)X;-]G5eJ7hS:iW>m\Bo]Cp]Bk]Bk_DmbFohIpiJqhIpcDkZ;bW7^Q3]S6_Q2`L-[K-WN0YF-VG.WE/UD.T=-MB3SE@SPL_WZeX[fW]eZai`cnadoadocgq`deRVWUaKamV`rPbuRm}T|c’¢r”¥u–¤y”£x• zŽ™sŒ™sŒ™sŒšqžu pŽžnšm’œo“pŒ–i{€dejNWbP`jXgugssyl‰|”¥y–§{—¨u‘¢p‘¥k£j£j¢h‘¡q’¢r’ŸyœuŒ™sˆ•n„sŠoƒˆr€…p‰u–|™£}›¦€™¤|•¡x•¡q”Ÿp”£j”£j•¥h•¥h”§b“¥`”¤_‘¢]‰žX…šU…žV„T‡žX‹£]Ž¢f¤h‘¥k’¦l“¦o’¥m•¥p“£n i‰™b€Vz‰PrHix>bp9\j3dp0hu5n.r„2sˆ.w3x1z‘2z‹0z‹0yŠ/wˆ,yˆ+{‹-|ˆ.{‡,^hFOU]aj"ho0cj,EH1;?'FR`‚›¨¸Å¯¿ÌºÃË·¿Ç¯µÄµ¼Ë¸½Ïª¯Á¨´Ç«·Éª²Ãµ½Í÷ҵ©ÄŒm§R3lQ-|S0~=0?295~;787~87~;895}<4{:1y:1y7/w;2z;2z:2x:2x?:yB={KCvPH{[Rz`We^zjbhfjh„jm…sv‚‚–ŒŒ¡——§–•¥—¥Šƒ˜¤¤–«ª™ªŸŽž™†•‹}‚}‚‡ƒŠ…€ˆpu{pu{r{zs|{ysysy€qzr‚†q„‰t‡jˆk‰•e˜i’•q“npoŒŒqŠŠo‰…vˆ„t|xyvrllzjjx_c~]b}P`‚O_D]„F_‡BaŒJi”Swš]¤eŠgŒŸs†s††•~‡–Ž™~‘‚˜¢…—¡„”Ÿ€”Ÿ€”Ÿ€“ž•ž•ž”œ}“›|˜„‘š‡—–˜ššœ”–¥‘’¢‡ ‡ ƒ”˜ˆ™—¤™ªž¦¤ž —”œˆ—}‰}‰r‰¥r‰¥p‰¥oˆ¤l‚šdz’c}ˆv›}Ž—™‡Ž“„‹‘Yb9Yb9Yb9Yb9Tc8Sb7Ra5P^3K]1J[0HW0FU.CR-BQ,@M-Eniqb\_YSVRSQRSQS[_]ehUdm+:C "#"" ", (<-6M?HREP`S^xo|f^jRPWPMUTOY]Xbrnzhdp#!4- -(!#"%"*$ ,&& %#"!   + FRWhtyhkojmqkjlkjlommpnnignliqppvppv<…>=„>::7~=5}=5}<4{;2z;2z;2z;3y;3y<7u=8vH@sKCvSJrYPxd]yng„xv’…ƒž‰Œ¥Ž‘ªšš®žž²¤£³¥¥µ¥³Ÿ˜­¥—¬£•©¤”¤œ‹œœˆ’–ƒŒ‘€…‘€……€ˆƒ~…qv|otzr{zs|{ysysy€qzr„o‚†q…g†Žh‡“cŒ—h“n‘lŒŽnoŒŒq‹‹pˆ„tˆ„t‚{}yurr€pp}ch‚af€QaƒO_D]„E^…BaŒLk•X{Ÿ`„§gŒŸeŠrŽ…rŽ…ƒ‘{†•~‘‚—¢ˆš¤‡—¡„”Ÿ€”Ÿ€”Ÿ€“ž•ž•ž”œ}“›|˜„‘š‡˜—šššœ“•¤‘¡‡ †ŽŸ„•™Š›Ÿªžž« ¤¢›š—‘“Œ›“Œ›|‡œ}‰tŠ¦u‹§rŠ¦m†¢j€—]s‹d~ŠtŽš€’š~˜…’ƒŠV_7V_7W`8W`8Ra5Ra5P^3O]2M^2K]1HW0FU.ES/BQ,?L,;H(9F-5A)2?&0=$/;$.:#+7"*5!'1$/#.#.#.!+ * *'&'''&%#$$$$$$$$####!!!! !  !!  !!"#$$&&&''*****++ ,!-!-!-%/ %/ %/ '0!'0!'0!(1"(1"(1"(1"(1"(1"(1"(1"(1"(1"(1"'0!'0!'0!'0!'0!'0!'0!%1!&2"%1!&2"&1$%0#%0#&1$&1$&1$&1$&1$&1$&1$#/!"- "- "- "/"/".".!-!-!, +) + * * ((("("%%&&&&$$$$#### ! #%&"  #FAIpioc]bUSSSQQPSRWZY[adagj8AO +!" $ $ $" +%*>2;F:Creps~b\hUO[RM\PKY`_jts~BFU!%%#! "*/+773?//;,-8 !*"%%#   + ,39qrvmnrkgmminnlqmkpjjqmmtnlsspxieq0,8'[Yecgpaem[^iRU`OSbHL[:@QMRd03G+7!!='%F('G&&H**L1+S1+S.(M("H& E%D!CA9>'A-#G7+O<0T@.V=+R:%O<'Q=)U>*V;*U<+V6(S3$O0 P/O.N2!Q2#U8)[?,_E2eK4eQ:kY@oY@oY>kY>k[Al]DnaDpbFq_Ak]?hW8dQ2^P2cQ4dL4aI1]K2ZK2ZK3YM5[R5\Q4[M2YH.TB0RH5XI>VSIaZUd`[j^`i^`i`ajbcl\bfLRUMYK[gYhwWp_€‘e‘¢w˜£{—¢y”Ÿy’œv™vŽ˜tŒ™sŽ›tžp’¢t’¥mŒŸhŽœeŽœe’Ÿi›d‚‹cjsKWdMdpZn~i{‹vŠœz‘£€”¤v’¢tŽ iŒŸhŠždˆ›a‰—a‰—aƒd†’gˆ”pŽšvŽš}ˆ”w…wŒs}†l„s‹•x”ž›£„˜ “y™v™q™qœj“Ÿm‘¤d‘¤d¦^’¨`¦\Ž¥[Ž§\Ž§\’«a’«a’ªd‘©c’©e“ªg“¨k“¨k“¤s’¢r’uŽšq‹–o…j†cpwT[a@LR1\bCbhIZcB[dC\l7\l7Zk+Zk+Wi'Yj(cr0hx6n€8rƒ&@EKTY_ˆ•¬¦³Ê°µÏ’¬@Cc~ š¤Âlw•Š›³§¸Ð¸½Ð©­ÁŒw¦zf•\@†K.u@.~D2ƒ?>…76}8;}>AƒJJŒEE‡=:>;€=;„=;„:9‚75~66z88|89{66x14x35y,8x.;{;M|Oae{“wŒ¤–ž¯¥¶©¨±§§°­¢¯§œ©® °³¤´²³¬—­¬—­ª•«£Ž¤œ‡š†”œ‡–—Šˆ–‰‡‹„…†~€uw}ru{nx}nx}w|yw|yz}zz}z{}x}z€‡qˆr‰‘fŽ•k‘p‰m‰Šs‰Šs†‹o‡Œp†ˆl‡‰n„…n‚ƒl}yq{wotqyrovdg€ad|Ta†S`…Ge‹Sr˜W~ž_†¦nŸk‰›yŽ„xƒ†”xŠ—|”¡—¤„™¤„”Ÿ€”¡}”¡}’}’}“›|“›|’˜€’˜€’—‰–›Œ˜›š˜›šŽ“žŒ’œ„œ†’žš•–£Ÿ£¨ž¢§œ˜Ÿ•‘˜‰‰›‡ˆšyˆ¡{‰¢y‹¦y‹¦x‰¦q‚Ÿdx•WkˆXvŠi†šy‘›v˜~Œ“{ˆV_7V_7V_7V_7P^3P^3O]2O]2J[0IZ.HW0FU.CR-AP+?L,;H(8E,5A)2?&0=$/;$.:#+7"*5!(2!'1#.#.#.!+ * *'&&''%#%############!!!!    !!  !!"#$$&&&''*** , ,++ ,!-!-!-%/ %/ %/ '0!'0!'0!(1"(1"(1"(1"(1"(1"(1"(1"(1"(1"'0!'0!'0!'0!'0!'0!'0!'0!%1!&2"%1!&2"&1$%0#%0#&1$&1$&1$&1$&1$&1$&1$#/!"- "- "- "/"/".".!-!-!, +** * * ((& & %%&&%%$$$$##""  $%&#! &!(KDJpio_\]SQQNPPQTSTZ]^cg_gvKSb &! $%!  %!0$-I+^D0cM5fO7iV=lV=lU;hU;hX>iZ@k]Al^Bm]?hZ=fV7cQ2^P2cP2cL4aL4aL3[J1YM5[Q8^U8^S6]N3ZJ0V?,OA.QD9RK@YRM\XSbZ[d\^g`aj`ajZ`cNTXP[N\hZk{[w†fˆ™m¡u—¢y” w“žw‘›u—s—s‹˜rŽ›t‘ s’¢t£kŒŸhgŽœežh‹˜b‚‹cjsKWdMgs\r‚m€{ }¢“£užp‹žg‰œd‡š`ƒ—]„‘[Y{‡\|ˆ]€Œh‡“oˆ”wˆ”wˆ“z†xƒŒq…ŽtŒ–y’œ˜ “›|‘šw—sŽ—oŽ—oœj’žl‘¤d‘¤d‘§_‘§_‘¨]‘¨]©^‘ª`“­b“­b“«e“«e”«h”«h“¨k”©m‘¡qŸoŽšqŒ—oŠ”n‚Œf{ƒ_rzVelK[a@pvWpvWYb@NW6Td/Sc.Yj)ar2\n+Wi'Yi'[j(`r*gy1l|1l|1jw/bo'Xc(FQ3<.7DO O[+KQ9NT==89{74y=:<:ƒ:9‚<:ƒ:9‚88|77{78y89{57{14x(5u@LŒat£}¿“¨Àš¯Ç®¶Ç¬´Å±°¹­¬¶®£°ªž¬«œ¬­ž®±œ²°›°¯š¯¯š¯¯š¯ª•«¦‘ ¥Ÿ¡”‘Ž‰Šˆ‚vxsv|nx}nx}w|yw|yz}zz}z|~y|~y}…o~†p‡ŽdŠ’g‹oˆŒlˆ‰rˆ‰r†‹o†‹o‡‰n†ˆl†‡pƒ„m}u€|swu|tqyhkƒdg€Vb‡WcˆJhUs™Y€ _†¦lŠœk‰›yŽ„yŽ„ˆ–{›—¤„˜¥†™¤„”Ÿ€”¡}”¡}’}’}“›|“›|’˜€“™”™‹—œ—š™—š™Ž“ž‰™ƒ›‰• ”¡œ˜¥¡£¨ž£™˜“š‘Œ“†‡™…†˜x‡ {‰¢y‹¦y‹¦t†£j{˜\pTh„XvŠk‰x™u—}‹’x†U]7U]7U]7U]7Q\4Q\4M]4J[1IZ.GX,FU.CS,AP+?M)>J,;G*5D+3B)3B)/>%/;$,8"*5!)4 '2%1$/$/#.!, + +('&&&&%%########!!!!      !!!!!###$$%%'(())*,,,,!/!/!-!-".#/ $.$.'0!'0!'0!(1"(1"(1"(2!(2!(2!(2!(1"(1"(1"(1"'0!'0!'0!'0!'0!'0!(1"(1"(1"(1"(1"(1"&3!&3!&3!&3!&2"&2"&1$&1$&2"&2"#/ #/ "- "- -- + + + + , ,)))()'''&%%%$#""""##% &)'(#"%#+IAPplqa]bNOSMMRPQUWX\]_hghqOXg&5 $!  +$!C:@ujwshu]XfTP^TP^]Xflm}JK[ !#   ")" ,%#/+)5((5""/*+ )"*%!  + +QQXtt{ljoechebjkhoigsjhtsq}gfqEFQ$%079@Z\cbcndepddrbbpW]nPVg9?WLQjIPj$>9&'G/-O.,N/,Q/,Q6,V6,V1)O*"H$C'F."M5)T6,V/%O%F$E) H+"J-#I+ F*G)F,G-H0#L0#L3$O5&R3$O3$O.!O.!O+M,N0!S3$W=)\D0cK7jM:mS9mS9mQ6jP5iQ6jS9mW>kX@mX=jU;hM5bM5bL4aL4aM5bM5bN5^N5^U8]V:^X9^X9^U6]P0WH.TE*PB,RF1VO@YUE_VTb\Yg^]g``i\`bTXZS[Pckar‚b€oŠ™t›w”|’›z—zŒ“wŠ’s‹“t‰–o‹˜rŽœqŒ›p‰œd‹žgžeže‘žcš_„clvL_eToudt„o‚’} } } m‹œj‰š_†˜\†™T”OŠPxƒItzUyY|„e‡h€mƒq…”t†•uƒ‘s†•wŒ—x‘œ|‘yœx‘œt™pŒ™lšmŽ›gœh£b£b¤a¤aŽ¦^§_‘©a‘©a“­b”®c—®i—®i’«i’«i‘§m‘§mŸr‹›m‹—sˆ”p„n‚l}‚dw}^yzcyzcxz^vy]ipVgmSjrUhoSmqQmqQfmEX_7LW0MX2Pb4Ug8Xf6Sa1S^/T_0P^5IW.;J;JSZ7RY6SUGVYKUW`abkTZyfkŠš£Ä‘›¼jzš‡—·p†¤k Ž¦¿œ´Í¬°Í¢¦Ã}j¤R?x=)x;'u.){2-€-.|-.|468:„36~14|44}44}478;ƒ:=…:=…8;37}6789‚'6z,<€9XŒa³’«¾ ¹Ì¸ÆƼÉÉÃÄÉÁÂƶ¿º®¶¹¦·°œ­®š²«˜°«˜°¯œ³±Ÿ´±Ÿ´¶ ³´ž±³›ª¯˜§­¡•¨œ‘¡™†–|€~wzxqx~zw|yw}xw}xx}|x}|z~yy}xxp{‚t‚‰l…ŒpŠ‰y„ƒs†€xˆy„‡p…ˆq…‰o…‰o„‰h„‰h…m…m~|s{zqrnxpmwciciWo`x–b iˆ§tŠ›o…–w||’€‡˜zŒš£‰™¢ˆ—¡„“€“€“€‘›~‘›~“š~“š~‘˜„’™…’˜Ž•š˜›Œ”˜‹’˜Š‘—ˆ˜—š¥š›¦›¢œŸœ–š‹žŠ†™…¤}ƒ¢s‡¤vŠ¦uŠ©t‰¨m‚¡`u”SkŠOg…Uu‰e…™tŒ–u—}Š“z†T\6T\6S[5S[5P[2P[2K\2HX/HY-FW+CS,BR+@N*>L(;G*:F)4C*3B)1A'.=$/;$.:#)4 '2)4 %1$/$/"- + + +('&&&&%%########!!!!      !!!!!##%$%%%'())**,,,,!/!/!-!-".#/ %/ %/ %/ '0!(1"(1"(1"(1"(2!(2!(2!(2!(1"(1"(1"(1"'0!'0!'0!'0!'0!'0!(1"(1"(1"(1"(1"(1"&3!&3!&3!&3!&2"&2"&1$&1$#/ #/ #/ #/ "- "- -- + + + + , ,))((('''&%%%##""""## $$'&'$'*$,&-LHMokp_`dQRWMMRPQURS\VXaX`oX`o+8H!  + F:H}rlguZUdQL[VQ_\^njl|&-<   (%#/$".$".$".*)*+ $,!%-"'#!  )19qqxffmhgkechgelignmlwrp|YXc1/;..:RR^^agacjdep``laanaanTYk%/<#.:#,8"*5!(3(3'2$/#.",!+!+ *(''&%%$#####!!!!       !!###%$%%''()(*+, ., .!/!/"1"1#2%3'2'2%1'2'1'1'1'1)3")3"(2!(2!(1"(1"(1"(1"'0!(1"(1"(1"'0!'0!(1"(1"(1"(1"(1"(1"%1!%1!%1!%1!%1!%1!%0#%0##/!#/!#/!#/!"- "- -- + + + + ,+)(''&&&"$!# "# # "!""!!!! !")((%*+ ,# .&,C$B$A%B)F,!I/!J2$M4&O5'P6%Q4#N2!O5#Q9&Y>+^G1eK5hL9lPqR:nP8lM6lL5kL7hK5fK4eJ3dJ2fJ2fK3gK3gO6cP8dR9aT;dWT3J4?K5F  f^m{s‚aZiYR`YUa]Yeghx=?N  "$''&&$(( )"#,#%-$*%&"*Z^gimvhgjbbd_acdfhcgpgksWXcBCNYYedepbcn]^iZ[fZ[fZ\jY[iMRdQWhOOh;##E$$F'#J'#J'%N)&O*#M)"L)J'G( E'D"?!>,!I7+T:+V:+V4&O,G$@">!= < =!>$A%B%B&C-H0#L2$M4&O7&R7&R6$R8&U>+^C/bH2fK5hL9lN;nS=pRgX:dW9bS6_P2[H/XE,TB*U?(S;(M<)N9/GB7OKFUPKYUVX]_ahqbyƒt‡•qŒšv˜yŽ—xŒ“w‰s‡Žt†s†s†s…n€‰jv‚^p}Xu„Xƒ’fŒdŠ›a›[ˆ—W€YlyEgrRvb„‘v‚ŸvŒœr…˜a„—`„—W‚•T„•PJxzLjl>ebCifFmiQqnVps\tx`ve~‡mŽpƒ‘s‡‘r‰“tŒšqŒšqŒ™lŒ™lœjŒ›i__‹Ÿ\‹Ÿ\‹ [‹ [Š¢ZŒ¥\Ž¤a¥bŽ¢a¡`‹a‹aŠ›i‹œj‹›mŠ™lŒšq‹™p‡“o€Œh{ˆhz‡g{…hwd||hzzfuv_vw`xy]ww\xzZuwWOQ/%'1425,7,7%? -G(2I'9P.BV5EZ9ES7@N3=I2@L6EO7OZAX\IX\ISQQ\ZZQUkad{p~ Ž°€•»–¼‡ž¾’©Ê—¬Ë“©ÇžŸÊik–:&i?*m6'{6'{.*-(€*){---.|.0}+-x+-x//z..y--x//z20z1/x.1z.1z*=€Na¤z˜Æ¯Ü«ÃÍ°ÈÑÀÈÉÂËÌÆËÊÆËÊÆËÊÆËÊÈÆËÄÃÇǶÇÀ¯À¸¢º³µ°œ¶®š´ª—®©–­¬–§¯š«´Ÿ§µ ¨½²½²¶´Œ¯­…™˜x‡‡g}s€‚v|~y{}x~~~}}}y{{y{{w}qw}q~r€ƒu…‚…xuxqo{sq}sw{wz~|‚q}ƒr‚†n‚†nƒ…lƒ…lƒ…lƒ…l€~w~{uoulq|gw‹p€”rˆ rˆ s‰‘p‡}‘~“€Ž €•§‡˜¢…”ž–Ÿ„“œ‚“œ‚’›™~™~‘–‘–•‹’˜Ž’––’––Œ’œŒ’œš˜•žŸ¥›ž¤šš—“–‹™Ž‰—„€ƒœx…£|ˆ¦w‹§w‹§r†£j~›`r“VhŠJf‹IeŠXyf‡u‹•u‹•~ˆ’|†OW2OW2LW0LW0IW.IW.GT+GT+EV*BS'?L*(09#09#+7!*6 (4(4'3%2$0#/!, + ) )'&'%&&####!!  ""#$%%&&''('*+ *!+",",%.&/%.%.&/'0)1)1)1)1)1)1)1)1'0'0)1)1+3"+3"*1"*1"*1"*1"(2!(2!(2!(2!'1'1'1'1'0!'0!'/$'/$'/#'/#'/#'/#'/#'/#%.!%.!$- $- $- $- "-""-" * * "+"+!*!*))))'''&$$#"##""!!!! !$!,"-/,+,0 #5)$5-'8-'8:4E`[jmhwZVbMIUIHRKKTPMUYW^Z_ebgmIV]%,  +,#2oetvl{cYi\UdYR`\Ygjgu!.   +  "$&$&%%% ) "(!#*"$+#%,"*&"&W\`jlsacjddkccjadobepikz`aq]]kddreesbbp\_lWYfWYfXZgX[mWZlMTgEL_79T$&AA> D!"F"!E$#H-)P/+S+'L&#H%A$@( D%A">!=+!E7-P@1X>/V<,S2#J%C$B!><@ A$C%E'F%E*E+ F/"I3&L7'P;*T;*U<+VA-\B.]E0_H4cL6iM7jK7jM:mP;qN:oJ8mH6kG3hE1fD0cD0cH2fH2fM4hM4hO9fP:gV?gV?g\AhZ?eX:dV8aQ6_N3[J/XH-VD-W?(SA*RA*R;(M?+P?4OF;VOKW^Zfrvj‡‹Ž–yŽ–y–x•v‹Ž~‰Œ{…‰v„ˆu„Šp„Špƒ‹l}…fmv[irXt„]€hžeže‹›]†–X|ŠXjyGfuPu„_†”pŽœxŸoˆ›j†˜\„–Z…—T‚”RƒP~‹KvvMmnEnbHlaGl`MsgTpkYvp_wzc~j„ŠpŠv‰•q‰•q‹™p‹™pŒ›iŒ›i‰˜c‰˜cŒžbŠœ`Š]Š]Šž[Šž[Š]Š]Žža_‰˜_…“ZƒŒ]„^ˆŠhˆŠh†l†l…’p‡•s‡•sm€‹i~Šh{„izƒhy€fxew]w]vZw‚\z„ZmwMRX3*/ +$)27")9++;-)B3-G86M71I37I09J1CR9DS:GT;O\CS^?Q\]jCbhGfmLgjbad]`f|ƒ‰Ÿ|•¸x³€œ¼©É—«Ê”§Æ›˜Ê`\.!j4'p3(~2'}/,€.*+*~*(}++{**z-,y-,y-,y-,y+,y+,y*,w,.y!/x+9‚>_“oٸäÂ;ÉÉ¿ÊÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÄÁËÀ¼ÆĮƹ£»·¡¹´Ÿ·°™´¬–°«•­«•­¬˜¤¬˜¤²©”¹°›µ»ˆ°¶ƒ ¬v˜bƒi„ˆn~€{|~yz}zz}z|}zz{xy~vw{ty|r{~u~~„qqxgilm„gqƒis†sxwy~}|‚q}ƒr€‡q€‡q„…s‚„r‚‚u€€rx{xtwupz†yƒv‰›v‰›vŠ‹r†‡€‘€†—†’Ÿ„–¤‰—¢‰”ž†”…’š‚•›ƒ•›ƒ‘—}–|Ž”•‚Ž“‰’˜Ž“˜—’––—–’™˜— •›£™§Ÿœ¢š—–¡‘‹œƒ‡‚†œw„¢x…£s‡¤tˆ¥wŠ©v‰¨u„¢kz—YmRf‰FfŽEdRz‘]…œqŒ“qŒ“z‹yŠŽMU/MU/JT.JT.GT+GT+ES*ES*CT)AR&>K)9BBBC D%$I0,T73[3/T'$I%A%A'C">">!='A3)M?0W@1X;+R2#J'D"? =<@ A$C'F(G(G+ F+ F/"I3&L7'P<,U<+V=,XA-\B.]G3aG3aI4gI4gG4gJ6iK7lM9nM).?14D68RBE^OLdNE]GP7M]CRaHUbIVcJXcCV`A^kDanHfmLhnMileileTZppvŒy’´n‡ª›»©É•¨Ç“¦Å“ÂTQƒ1$l7)r3(~2'}-)~.*+*~*(}()y()y,+x,+x-,y-,y(*w(*w*,w*,w'5~AO˜e…¹†§Û¤ÂͧÆÑÀËÌÀËÌÅÊÉÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÊÇÐÅÂÌÍ·ÏÀªÂ»¦¾·¡¹´ž¸­—±«•­«•­®š§ª–¢«¢´«–´¹†´¹†©´~˜¤m†Šp€„k|~y}zz}zz}z|}z{|yw{tw{tz}s|v||ƒkkrcd{ef}blfo‚putuyyzo|‚q}…o~†pƒpƒpt€€ry|yx{xpz†yƒw‹œuˆštˆŠs‡‰ƒ”„ˆ™‰–¤‰–¤‰–¡ˆ’…“›ƒ’š‚”š‚”š‚–|Ž•{Ž”•‚Ž“‰’˜Ž’––“˜—’™˜—ž™¡–š¢—¤œ˜ž–’Š›Žˆ™‚†œ€„šy†¤x…£s‡¤tˆ¥wŠ©s†¥phw•YmRf‰FfŽEdT|“_‡žr”r”z‹yŠŽIR,IR,GR,FQ+DQ+DQ+BO(BO(AP+>L(:I$8F"9F&7C$5A&3>$3>$0; .:,7)5)5'3'3%2#/".!-!-!- +*))'%&&####!!!!!"$$$$%&&''((( , ,!-!-".#/'0&/&/&/&/'0'0)1)1)1)1)1*3*3)1)1)1)1)1)1)1)1)1)1(2!(2!(2!(2!'1'1'1'1'1'1'0!'0!'/#'/#'/#%.!%.!%.!$- $- $- $- $- $- !,!!,! * * +*)))((&&%&&!!""!!!#$,!(0!$6"40"4 #5$'9(+=+.?25F58J33E/0B<=O[\ndhuPUbIJUGHSIIPNNURUY`cgbloT^a#/4 #%..A9Otm‚lbsdZk_Wf_Wfily8;H      %( !*#%-!"+ ) )!"+!#*!#*#&*#&* %+?DJnqubei]]d^^e^`idfoilyilyacr_`p]]k]]k[]l[]l[]lZ\kZ^o\_pW\p^cwUWr35P>? D DBC&#F(&H:/VQGmWMq-#G&?&?$?#=">!=&@.%HA/WH6^B1X6$K) A&?96< >$B'D( E)!F,!H+ F/"I2%K7'P<,U=,X>-YB.]B.]F2`D/^G1eG1eF3fG4gJ5kK7lL:oL:oL:oH6kD2gB0eA/dA/dE2eE2eM6eO8gV>iW?j\@iX=fU6bR4_L2]J0[F,WE+VB*V?'T?(S?(S?*R=)Q?*O>(NF4PVC_odjƒx~‹ŠŽ‚ŠŽ{†Šwƒ†v‚…t€…rƒ‡tƒ‡tƒpu|ghpZ`gRgnYt‚`ƒ‘on›kšfˆ•a|†`p{UjvYx„g„”q‡—u‡™k‚”fƒ’^‚‘\“Q“QƒŽS}ˆMnnLmmKriTqhSofSh_Lf]Qj`Tkk^xxj„‰t’}Š•tˆ“qˆ•nˆ•n‹›d‹›d‰™b‰™b‰™b‰™b‰›]Šœ^‡›ZŠ]‡›Z†šY†”]V|„ZryOgfOfdNhgWhgWuzd~ƒn}†l~‡mƒŒq„s‚‹pŠo}ˆh~‰jƒŒjƒŒj„‘d…’e…–]†—^†•Z„“Xˆ^‚‰XuyYlpPagVbhWbk^cl_goWaiQR]=WaB_fI`gKceIceIalEbmGhrEisFpvJrwKvv[nnS]]diiotˆ¥•²† ¾©Ç•©Í£Ç‡»AL(?M)P44F56H12D01CIN[afs[\gNOZIIPHHOIMQSVZQ\_[ehamrNZ_(1  "*.0"1IBW}r„lbs]Ve]VeWYfegt'3    #("#,#%-$&/#%-#%-%'0%(.%(.%)-"%)%?,"F?-TE3ZB1X8&M,#D&?!;9:"?'D( E*E*E/"I2%K5%N9)S<+V>-YB.]B.]D/^B.]D.aE/bC/bD0cI4jL8mJ8mK9nH6kE3hD2gB0eA/dA/dC/bC/bI2aN7fR;eV>i[?hV;dU6bR4_L2]J0[F,WE+VA(U>&S?(S?(SA-TB.VC-SA+PA.JI6R]QXpekxwlu„ˆu„ˆu‚…t€„sƒp‚†sƒpy~kmt__fQ_fQiq[uƒa…’pnŽœlœh‹˜c~‰cp{UjvYx„gƒ“p†–tƒ•g‘b€[}ŒX“Q“Q‚R}ˆMuuSppNvnXtkVqgTkaNf]Qf]Qkk^zzmŠy‘–€Ž™w‹—u‡”m‡”m‰™b‰™b‰™b‰™b‰™b‰™b‰›]‰›]‹ž^Š]‡›Z„—W‚Z{‰St|QhpFZYBPN8QP@YXHns]x}hzƒh|…k~‡mŠo‚‹pŠo€Šk‹l…Žm‡o‹˜kŒ™l‹œcžeŸcŽžb‰_†Ž]}‚av{ZsxhsxhluhirehpXaiQR]=[fG`gK_fIbdHbdH_iCdoIfqDisFntHsyLvv[mmR^^eccjbv’€”±…Ÿ½§Å‘¥ÈŽ¢Æ~x²71k3$s:+z1(~0'}('|&%y('|'&{'(w'(w)(v)(v)(v)(v,+x+)w )u'0|C^›v‘Îœ´Ì§¿Ö¾ÉÉÁÌÍÄËÊÄËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÄËÊÃÊÉÇÃϾºÆÁ«Ãº¤¼´ž¸°™´«•­¦‘©¤ŽŸ£žŸšŠ‹’’p‘‘o—¡g›¦k–i„Š^|}r}sxzzxzz{{{{{{x}ux}uyszt€ˆnnwZa{W^xM]}Ue…bm}al|mtspwvyl{n‡h…f€„k€„k~‚v|€tw€€ˆ‰{Š“z‰’w‰„z‹†‰›¡‡–¥‡“¡„’œ†‘›…‘š‘š“š’˜~•~•~•„Ž–…›‰• Ž›¡•œ¢–¢¥—¡£•¡œ›–—”Š™’ˆ—„†œ„†œw…žx‡ tŠ¢tŠ¢tˆ¥tˆ¥s†¥o‚¡fyš`r“Vk‘OdŠCgˆDh‰X}d‰œx“x“‚ŒŽDN(DN(DN(DN(AM)AM)@L(=I%#="<<< >#A(G(G.#L5*S=-T5%L2%I, D77 8 8"<#=$B%C)C* D-"J0$M5%N6&O;*T=-V>-Y?.Z@/]@/]B/_?-]B/_C0aD0eG3hJ5kJ5kG5jG5jD2gB0eA0bA0b@.a@.aD1bG5eN7fP9hT;kQ8gM4dK2aJ1`H/^A-\@,ZA*Y='V='V='V@*VB,YD-VD-VH-VI.WN4XZ@deVfrcsxst}xyq€€r~„l…mx}hrwbhk[\`O]bShm_u€g…wtŽ›r’›q—l€‹ep{UmwX|‡g’m’mƒ•g‘b}‘W|V~‘S~‘S]€Œ\yzcopYtl\tl\rmToiQidRjeSsr`†…s“š~—ž™v‹•qƒ•g‚”f•`•`„—`…˜a…˜a…˜a„™\‡›_ˆœ`…š]ƒ•Wƒ•W}‹[v„TlnLbeBQH@E<4D:>YOSnnguun{}knƒp„…s„†m†‡nƒŒj…Žm†’i‰”kŒ˜m‘r’¢k“£l–¨e–¨e”¥^‘¢[‹a~‚Tww^ww^tq_tq_fhOY[B^aEbdHbfEbfEcgFdhGcjIelKjqGkrHoqAlo?vsRqoMcaaiggeo‡ƒŽ¥‰œ½ŸÁ’¡ÌœÇxr¬3.g,l5&v0'}2*,'~(${(%y(%y&&x&&x&&x&&x+)y'%u#+s"*q,q-C‰c„ªˆ©Ï´ÂǺÈÌÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆÌÈÅÊÇÈ¿ÌÀ·ÄéȽ£Â¶ ¿±›º®›°¨•«¥‘¤Ÿ‹ŸŽ“˜‰Ž‘sŒo‰œdŒŸhƒ”b{ŒYx€ox€oyyyyyywyywyyy}xy}xy€qzrƒ„rntY[}VYzF]„F]„RgTi^lyes€v}|w~}~„s~„s€‚o€‚o~€t}sz‚~‰…Ž‹~ŒŠy‹“‡¡…“¦Š˜£Š”ž†’™„‘˜ƒ‘š‘š˜€˜€˜€—‹–‚Ž™…–‡š¢Œ ¢”¡£•¢Ÿ¢Ÿ›ž””¢›‡‡ …†Ÿ|„£|„£u… v†¡sˆ¢t‰£p‰¥p‰¥n„ g}™bu›]q–Kh“GcŽ?h„Ai…\„c‹”}”Œ~•‰–‚‰–‚BL&BL&BL&BL&=I%=I%=I%!1= ,;+:*8)7)4 '2'3%2%1$0!,!,.-,*)))(%$#!!!""!!      !!!!#%$%%&(((( * *!-"."-"-#.#.$0$0%2%2'2'2)3*4*3*3*3*3*3*3*2!*2!*2!*2!*2!*2!*3*3'0'0'/'/)1)1'0!%/ $.$.%.!'/#%.!%.!%.!%.!$- $- $,"%-##+!#+!#+!#+!#+!#+!"+!*!* ( (&&%$$#""!     ,)*5--?./A%-@")<'.A.5H5Q5@W5@W5;X4:W15R04Q./H,-E<:LON`dep^_jUVZQRWSSSSSSRTVUVXS^eZelOap8JY.$+-'ZOaxme[lYSd_Yjagv;BQ  #  #''%(#$(&'+++2,,3,/5.06).4(-3)+2`bhdgmacjgdmifpfepljvgkz^bqZ\j^`m__r__ra_rb`sZ^r[_s[bu_fyHMa.3G2/F62J)"C$?#D B @ @%"E30S>;Y74R+"C&?$>"< : :;#A)C+!E,!I-"J4#M5%N7'P;*T<+V=,X?-\?-\?-]?-]?-]>+\B-cB-cC.dC.dB0eB0eA/dA/d>-`>-`@.a@.aB/_E2cO8gQ;iT;kQ8gM4dK2aI0_F-]A-\@,ZA*Y='V='V='V=(TA+XE/WE/WI.WI.WL2VM3WRDT_P`eaapklxxj~~p~„lygrwbfkUadT\`O[`Rin`x‚j‡‘ytŒšq“rŒ–k~‰cp{UnyY|‡g~j~j‘b{_|V|V~‘S~‘S‚Ž^‚Ž^€juv_zrawo^toWsnVnhWnhWvvd‰ˆv—ž—žŽ˜tŠ”p‚”f“e€”_€”_ƒ–^„—`…˜a…˜a…š]ˆœ`‡›_„™\ƒ•W€“U}‹[v„TmoM^`>YPHNE>I>BRHLbb[vvoz|iz|i~m€‚o„†m†‡nƒŒj…Žm†’i‰”kŒ˜m‘r“£l”¤m–¨e–¨e•¦_’¤\‘c†Www^vu]ur`pm\]_EbcJceIdfKcgFcgFfjJdhGdkJfmLjqGkrHlo?fi9olKpmLhffomm`k‚yƒš†˜º‰œ½Œ›Æ‰˜Äuo©2,f-n4%u-%{/&|)%|+&}'#x'#x%%w%%w%%w&&x'%u'%u (o"*q 7|Lb§|Ô´Ú·ÆÊ»ÉÎÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆÌÈÅÊÇÈ¿ÌÀ·ÄéȽ£ÂµŸ¾°š¹®›°¨•«¤£š†™”…‰“ƒˆ‘s‘’t¡j¡j‡˜e|[x€ox€ozzzzzzxzzxzzx|wx|wu}nxp€|‚tpuY[}VYzE\ƒE\ƒNd{Sh€]jxcp~pwvt{z{p}ƒr€‚o€‚o~€t}s{ƒ‚Š†Ž‹~ŒŠ|…—‹‘¤ˆ’¥‰–¡ˆ’…’™„—‚‘š‘š˜€˜€˜€—‹–‚Ž™…˜ŸŠ›£ ¢”Ÿ¡“œ™œ˜”—›Š‰—…†Ÿ…†Ÿ|„£|„£u… v†¡sˆ¢vŠ¥rŠ¦n‡£mƒŸd{—atš\o•If‘Eb>gƒFo‹^†g™€—€—Œš…Œš…?I%?I%?I%?I%47B.5H(/B&/D09N6?T6?T3@W1>U1=V/:T-7P*3M(/I(/I02H)+B>=MYXhfep_^iRRYRRYTSXRQURTVWY[W[achnYcm,7A $ $$'_YjvqcZnaXla[nhbu&,=  (*)*+ & %+#(.%)/$+1&-3&-3.5;dgmehnabkcemccqaanggtpp}_evW]nZ^r[_s\`v\`v[ay[ay_c~_c~QRr77W('E21N53U-*M @># C @=>(!B5.OSOhHD^)!>%:;:99=>"B#C*!I/&N,#M+!L,G,G*E'C#?98988 =!>'E+H-!L, K2$M4&O6%Q8'S9)W9)W:*X:*X;+[;+[:,\9+[;)^;)^=)^=)^;)^;)^<*_=+`<+^=,_>-`>-`@.c@.cE2cF4dL7hO:kI4eF1bD/_B-^=-]=-]=*[;(X9'W8&V='VA*YF,WJ0[J0[H/YH0VL4ZJ2VQ9]WGcbQmk`ktjtwsqtpoif`ROISUP[]X_aZmohz„n‰“}Ž™wŒ˜v™s‹“m‰er|Xoz[|‡g|f}g{Ž]z\zSyŒR|ŽP|ŽP‚‹e‚‹e‡„wzxjzqeyocyq`vn]qk\un_||iŽ{™Ÿ…—ƒŠ˜o…“j“^€‘\‚”_ƒ•`€“b”c‚•dƒ–eƒ–e‚•d‚•d”c‚“`€‘^zŠZsƒSlrLdjD_]HZXDQOBQOBZYNji^ywh{zj}~g€iƒi„†mƒŒj„l†n‰’pŒ—qšt“¢p•¤r–§i–§i—¦f’¡a‘c†Www\ssWppUggLabDefHffKggLehJehJdgIfjKekMhmOkqJgnFdf?_`9kkKnmMfe\pneln{uw„„Ž¬Ž˜¶™ÅŠ”Àje¤)$b, r0%v)"z(!y('}'%|'&{%$x#"t$#u&%y$#w$#u#"t(s!.y5TŒj‰Â™´Â§ÂпÅÉÃÈÌÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÅÊÉÄÈÈÅ»ÊÀ¶Æ¿¨Ë¹¢Æ²À¬—º¥œ« –¥›—’‡ˆ€Œ†~‡Žk‰‘mˆœ`Œ¡dŠœg~[|…k|…k}|x}|xxzzwyyw{uuytt|mu}n€~~ursWYvWYvG[ƒDYC^|IdRg{Vk~irxmw|w{uz~y~€t~€tzwy~v{…ƒ‚ŒŠŽŠŒ‡”~‰œ‡“¥„“¥„• …‘‚“›ƒ‘™‘š‘š——–€–€—†›‰™‘› ”Ÿ›ž›˜›–”¢›„†œ„†œ{…{…qƒžr„ŸkƒŸoˆ¤rŠ¦rŠ¦pŠ£k„žd~•b}”`yŽ]v‹Ws…Vr„]z|e‚„{”€‚›‡¡{£~–¤y“¢w(!B2,MFB\>:S&;#8::::?%"E& E'!F(G+"J/%O/%O/!J. I)D&B <77868; =$A)F-!L."M2$M4&O6%Q7&R7(V7(V7(V7(V5%U6&V7)X5(W6$Y6$Y6"W7#X5#X7&Z6$Y9'\9'Z;*\<+^<+^<*_<*_?-]C0aG2cG2cF1bE0aB-^@+\=-]=-]=*[;(X9'W8&V='VA*YF,WG-XH/YJ0[H0VK3YJ2VJ2VI9UO?ZYOZcXchdbd`_USLFD=IJFZ\Wbe^moh{…oŠ”~Ž™wšx’štŒ”n‰eq{Wp{\{…fzŒe|f|^xŠZzS{ŽU}Q}Q‰d…gˆ†x}{n€vj}sgzrayq`un_wqb€m‘‘~™Ÿ…“š‰—nƒ‘h“^€‘\€‘\“^€“b”cƒ–e…—gƒ–e‚•d”c‘a‚“`€‘^|Œ\y‰Yx}XmsNkhTfdO][MXVIVUJ]\Qml\ywh{|e}~g}fƒi€‰hƒŒj…Žm‰’pŠ”n˜r’¡n”£q–§i–§i—¦f’¡aŽ“dƒ‡Yww\mmRhhMeeIefHfgIggLhhMfjKfjKfjKgkMhmOhmOjpIdjCce>ce>poPpoPjh`mlcln{oqpz˜ˆ’±™Å‹•Ágb % ^, r/#u*#{.'~'%|&${'&{$#w#"t$#u%$x#!v#"t"!s(s0=ˆYx° Ø¢½ËªÆÓÃÈÌÃÈÌÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÁÆÅÅ»ÊÀ¶Æªμ¤È²À§’µ –¥šŸ™Ž”“ˆŽˆ€Œ†~„‹h‚Šf|T€”Xˆše€‘\|…k|…k}|x}|xz||y{{w{uuytt|mu}n€~~yww[\zXZwEZ‚G[ƒB]zHb€Rg{Vk~eotirxswrx|w~€t~€t|z{€x{…ƒ‚ŒŠ€ˆ~Š†ƒ–ŸŠ“¥„“¥„• …‘‚“›ƒ‘™‘š‘š——–€–€šˆ“Œ™‘› ”š˜”—Ž‹™Šˆ–„†œ„†œ|‡ž{…qƒžr„Ÿl… oˆ¤rŠ¦p‰¥nˆ¡gše–e–d}’d}’d€’gƒ•pr‘€™…„‰Ž¢}£~˜§{–¤y7C!7C!9D"9D"7D7D6B 6B 4>5@ 5@ 3=.;.;-9-9*9)8%4%4$2%3$2$2%/%/$.$.!- ,**%%&%%$$"""##""""##""!!""####!!    !!!!##$$$%''' ("("(#+#+$,$,%.#,%.%.%-'/'/'/)0!)0!*1"*1",1",1",1",1"+0+0+1+1)1)1)1)1)1)1)1)1'0)1)1'0&.&.&.&.$/$/$/$/$.$.#-#-#,"+#,#,"+"+"+!*++((&&&&%$""    #"*/.7:G?BO!5 .,)9SMYd^j_\eROYPLXMIUQOVSQXVU^XWaYdkYdk4CL' '`Zmrk~`Vld[q^_vQSi *.0!7#(:*/A/8F8AO9BQ6?M)6?4AJbgmmqxjjqffmhhtcdoacqilyghx^_oZ`v[aw[aw]dy]f‚U]zHLn7<]%&M#$J$&J()M,,N--O'&M" G!CA?=;#@&"B'#C2(E-#@$9"7 < <:;"B'!F.#L-"J+J."M7(V9)W4%Q0"M'G$E?:7899;;@!D*H*H/ N2"P2"P3#Q4'U2%R2"P2"P0!O2"P4$T5%U1#S0"Q.!S0$U1$X1$X2%Y2%Y3&Z5'\7*^7*^8(]7'\:+^<.`@-`B.aB.a@-`>+^=)\<([<([9&Y8%X9&Y8%X;&Y>([A)\A)\D+ZD+ZE,\E,\C-ZD/[C-ZF0\I5]Q=eVDeQ?`E;IPN\`ddots€ˆp‹“{—s—s•r‹m~„cpvUo{YwƒazŠa|c|Œ\{‹[|S|S~RŽS}ˆh‚Œm‹‡…„€~„wt}pm{sgxqexpf|tj„‚u•’…š ˆ’˜€ˆ“q‚ly‹]zŒ^w‹]|a‘b“eƒ“l†•nƒ”j‚’i~Žd{‹b~Œc~Œc~b}Œaw†TrOn{MivIcoF`kC]hF`kJciJkqR{{`~~c}}k}}k‚…iƒ†j…‹l‰q‹r“t˜p“œt–¢r–¢rš¢q–lŽ“d„ˆZw{VknIghJghJjjHklIjhMjhMhhOhhOfiMfiMjnMglKfjJfjJ]cD^dFkqRlrTnm]rparnzso{hkŠ„‡¦‘”ÁŽ‘¾c^'"`* o/$s&!y'#z!!x!!x%#z$"y&#u&#u%%w$#u!u #w1z?Y¢w›·³Ï±ÁǵÅÌÃÉÊÄÊÌÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆÈÏÁÄÊſɼƿ­É¼©Å©ºœ­–’ž”œ’’’Ž’„ˆ~‡o‚‹i{Š]z‰[€Žc€Žc~ƒn}‚m}v}vzy{yxzy{twzst|mszlx}}v^_vZ\rL\„KZƒC[yD\zLezQi~]kw`ozntvpwxx}u{€x|~{~}}……Š„„Œš…‘žŠ”Ÿ„–¡†—œ…”š‚“›ƒ‘™—‚—‚–€–€–ƒ–ƒ•’”š–›šŸ™˜œŽ’¡ˆŒ›{‡š‹‹€Œž‘—‘—}Ž—x‰’uˆšw‹œtŒrŠ›o‡“n†’{‹Š{‹Šz“|•ƒ›|…~‘£u’¤v˜¨q—§pš©n­qœ¨vœ¨v7C!7C!7C!7C!6B6B6B 6B 3=3=2<0;-9-9,8,8)8(7(6(6'4$2$2$2%/%/$.$.!- ,**&%%%$$#"""##""""##""!!""####!!      ##$$%%$%'(' ("(#)#+#+$,$,$-$-%.%.%-&.'/'/)0!)0!*1"*1",1",1",1",1"+0+0+1+1)1)1)1)1)1)1)1)1&/'0)1'0&.&.&.&.$/$/$/$/$.$.#-#-"+#,#,#,"+"+"+!*+*(&&%&%$$"! "!*/.7:GCERAH`>F^6@Z4>W=GaCMf=Jf9Gb7A_2<[-8V)3Q&0I!+E"+@(= #7!5"/ ."(6/ $;*/A-3D6?M9BQ?HVGP^R_hcpzlpveiojjqiiodepefqilyilybdsacr]dy_e{bh~\cx=Fb-6R05V16W55\//V,.R./S..P++M$"IE!CA=<:"?&"B'#C,"?(; 649 <<<#C)#I2&O0$M+J0#N:*X<-Z7)T4%Q)J"C?:8899;;@ C'F*H-K.L0!O2"P2%R.!O.L.L/ N3#Q3"S3"S0"Q0"Q.!S, R,T,T0#W0#W0#W1$X2%Y5'\8(]9)^9*\:+^>+^?,_>+^=)\;'Z9&Y9&Y9&Y;'Z;'Z8%X8%X;&Y;&Y?&Z?&ZB)XB)XB)XB)XB,YA+XB,YB,YA-TB.VC1RD3S>5I?6J;9GKIWbgfqvu…uŠ’z‹•qŒ–rŽ“pˆjx^pvUp|Zy„bw‡]y‰`{‹[{‹[|S|S~RŽS|‡g}ˆhŒ‹”Ž…xutr|uh{sg{shzo‡„w–”†™Ÿ‡‘–ˆ“q‚lzŒ^y‹]vŠ[xŒ^~a‘b‚’k…”mƒ”j‚’i~Ždxˆ_w…\y†]{Š^zˆ]}ŒYyˆV{ˆZq~QozRmxOlwUlwUotVjpQttY||a}}k}}ke‚…i‚ˆjˆŽoŒ‘s“t–nŽ—o›k“žo–l’ši‹aƒUuxSptOlmOghJnnLmmKkiNkiNhhOhhOfiMfiMfjJglKeiIcgF_eGhmOotVrxYqo_nm]okwyunqwz™‚…²ˆ‹¸`[™-(f,!p1'v&!y'#z""yu#!x$"y&#u&#u%%w$#u!u"v&?ˆ[u¾†ªÆ•¹Õ²ÂȸÈÎÄÊÌÄÊÌÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÅÇÎÅÇÎÆÁËÅ¿ÉIJμ©Å©º¡•²š–¢›—£ššš™™™–›Œ’—‰™wŠ“q€av†Xu„Xzˆ]|k|k}v}vzy{yxzwzswzsszlt|m||u}}v]^uZ\rHXGW€D\zE]|KdyMf{Zit]kwlrspwxx}u|z|~{~}‰……Š„ƒ†Žœˆ’ ‹• …”Ÿ„˜†•›ƒ“›ƒ‘™—‚—‚–€–€–ƒ–ƒ’—”“˜•–”™“’—ˆŒ›ƒ‡–{‡š}‰œŸ†‘¤‹›¢‹›¢…–Ÿ€’šxŒw‹œtŒs‹œsŠ–x›…••†––‡ Œ‡ Œ‹£„Š¢ƒ’¤v•§y˜¨qšªsš©nš©n›§u›§u6B6B6B6B5A5A4?4?1@ .=.=.=-9+7,8,8)7(6)7(5%3$2$1#0$.$.#,#,+)**&&&&$%&&%%$$####%%####&&%%%%%%$##%$ ##   !!"#$$&&&&&& (!*"(#)#*#*$+#*#+%-%-%-%-&.'/'/)0!)0!)0!)0!,1",1",1",1"+/!+/!,1!,1!)1)1*2!*2!)1)1'0'0'0'0'0'0&.&.&.&.$.$.#-",#-#-#-#-"+"+!*"+!*!*!* (((&&%%$$$#!!!(-*37=?DJCLaHPfAJd;E^=GaCMf>Ni=Mh4Hg-A`*=\&:Y$5R0M,G'C#8!6312.-86I^YgfapZVbQMYMMSMMSOLSSQXUYbZ^g^cpX]j59H+TUgabtSQkZXrZ_x>C\/(=#/?-:JCK[MUfY^k`erjkvoo{feofeoggshhthhukkygkzcfvcfvdhwdgy]`r@C\),D.0R:=^=>i;'">'">% =!8735!=% C"@$C*!I-!L-!L,L- N7(V<-Z;,X:+V/%O'G A<99::?@@C"E#F%H(J*N*N-O,N+K+K)K.#P,!P,!P+ O*N)Q(P+R,T,T+R. U0#W/"V0#W5&Z7'\8)[:+^9'Z:)[:)[:)[5'Y4&X5'Y5'Y3$W3$W4&X4&X6%X6%X9&Y9&Y=(X=(X=(X=(X;)W<*X>*X>*X=*[=*[=+Y<*X:-S:-S=5MOG_gfivux…‰v„ˆu‡ŽmˆnŠŽn‚†fryXkrQnzVv‚^t…Zu†[yŠWyŠW{Q|ŽRQy‰Lw`x€bŒ‰Œ™–˜ˆ€ƒ€x{‚vq€to|qnƒxuŠ…–—™‹’„…ŽtŠoz‡gw„du„du„d{ˆhŒl‚Žq…‘s†“qƒ‘oŒjw„bq{Wq{WvZy„^w…Uy‡WxˆSw‡R{ƒPz‚Nw„Py†Rx…Qt‚MszIryHzY}„\„_„_€‡d…ŒiŒ“p”qŒ“p”qŽ•r—s—rŒ”n†^}„U{Sw~OrvNlpIjjHmmKnkPnkPlkRiiPfiMfiMegMfhOcdK]_E[cDbjKgoPktUrwbot_lmqppugo‡nvŽz‚§y¦a]™0,h+"q+"q%$x%$x##{xwx!"v"#w!%x"&y%t-|:[•nÉ™´Â£¿ÌºÇʼÈËÄÊÌÅÌÍÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÇÈÊÅÆÈÆÀÌ¿¹Å°¬º«¦´§§®¥¥¬¥¨§¦©¨¢§™› ’ ¢‚˜šz‰b~„XwƒXy†[{‚e|ƒgqqz{wwytwytwytt|mw~o}}v€€z\atX]qDZBW}C\AZ|G^}H`~SgvWkzervjv{vy}z~‚x}|z~‚Š†ƒ‹‡„„ˆŽœˆ“¡Œ–¡†• …—œ…”š‚“›ƒ‘™˜€˜€——“‡‘•‰“–š’•™ŠŸˆ‹}‰{†›y‰–}š†š¡—›«‘š©Ÿ¦Šœ¤‡–œ‰“š‡Žš„†’£|”¦~š¬w›­x›°lš®kœ°h›®gŸ­iŸ­ižªkžªk¡§t ¦s¡¤z w3?3?3?3?3?3?2=2=/>+;*9*9,8+7)5)5(6'5(5'4$2#1#0"/$.$.#,#,+)))&&&&$$%%%%$$####%%###%&&%%%%%%%%$##!     """#$$&&& '&& (!*#)#)#*#*$+#*#+%-%-%-%-&.'/'/)0!)0!)0!)0!,1",1",1",1"+/!+/!,1!,1!)1)1*2!*2!)1)1'0'0'0'0'0'0&.&.&.&.$.$.#-",#-#-",","+"+!*!*!*!*!* (('&%%$$###!!&#*/58=C=F[FOdCMf;E^;E^CMf>Ni=Mh9Lk3Fe/Ba+>](:W$5R"0K+F(=&;"5210 0+&!/AS)>4AQKXhV^oYar]anejwpq|ggsdclfeoggshhtkkykkyaeudhwcfvQUd,/A. $<8;SKMoMOqABm;858*(D0+G(#?#:"9648;"@&!D(G*!I."M."M,L- N5&S9)W=/Z:+V3*T+!L"C?99::>?AD!D!D%H'I'J'J*L+M+K+K*L+!N*N+ O,!P+ O(P(P*Q*Q*Q*Q*Q+R. U. U2"W4%Y5'Y7(Z:)[:)[:)[:)[4&X3$W4&X4&X3$W3$W3$W3$W5$W5$W8%X9&Y;&W=(X=(X=(X8&U8&U:&U;'V;(X;(X<*X9(V:-S9,RC;SWOgiikzy{~‚o~‚oˆg‚‰h€„cx|[mtSkrQo{Wt€\rƒWt…ZwˆVwˆVy‹P|ŽR}Ov†HgoPmuV„ƒš—š™‘•y|…yt‚vq}rp„ywŠ…–”—‰‚…ŽtŠo{ˆhx…ev…ev…e{ˆhŒlƒr†’u‡•s…’pŒjw„bpzVisOnxRnxRp~NqOu„PxˆS~‡S}…Ry†R{‰Tz‡Sy†R|ƒRwNrxQx~V}ƒ^‡a€‡d…Œi‹’oŒ“pŒ“p”q”qŽ•rŽ–q‹“m‰a‚ˆZ|ƒTw~OuyRosLrsPooMnkPnkPkjQhhOfiMfiMfhOdfL_aH\]D[cDbjKgoPnvWw|gw|gstxlmq^f}aiw¤y¦a]™73o,$r,$r$#w%$x"!zw! y! y!"v"#w #w!%x(x-;ŠSt¯|× »É§ÂкÇʽÊÌÄÊÌÄÊÌÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÈÊÌÈÊÌÊÃÐÉÂÏþÍÁ¼Ê¾¾Ä¹¹À±³³¬®®«¯¡¡¦˜¡£ƒšœ|“gƒ‰]wƒXy†[{‚e|ƒgqqz{wwytwytwytu}nu}n}}v€€z\atX]qCY~BW}AZ|B[~G^}H`~QetVjydpuhtywz~y|€x}|z~‰…‚Š†„„ˆ’ ‹“¡Œ–¡†• …•›ƒ”š‚’š‚‘™˜€˜€——“‡‘•‰”˜“—‰Œž…‰š}‰{†›y‰–}š†š£™ž­”œ¬“¡¨Œ §‹›¢š¡Ž—¤—¤—©‚™ªƒ¯z¯zœ±n›°lœ°hœ°h ®j ®jŸ«lŸ«l¡§t ¦sŸ¡xšs1>1>0=0=.=.=,<,<+;+;*:*:)7(6)7)7(6(6'5'5#0"/"/"/!-!-++)(''%%%%%%%%&&&&%%&&$$%%((''''''((%%&%####!!  !""!"$$$$((** ) )#*#*#*$+$+$+#,%.%.%.#.$0%1%1'/ '/ )0!)0!+/!,1",1",1"-/!-/!-/!-/!)0!)0!)0!)0!)1)1'/'/'/'/'/'/$/$/$/$/",","+"+!*!*!*!*!)!)!) ( ( ( (&%%$%$##"##!!"!**199@>BSEIZCGb>C]c&:]#7[4P0M-F'@#:"92-00-,*#2RKYgco[WcVR^RNZPOYPOYRQ\RQ\RR_[[i`erhmzmp‚loek|dj{ajx_gv[eqYbn\`jY\gkinrpuffmddkabkdfoiiwllznnziju`erPUb-3= )$5/9SISlKQwFKq;9R?>W-+D&;$9 8 899"@'"E*!I+"J0$M,!I(J)K/#S6*Z>-`;*\7)W1$Q)J"C?<;;>@A C!E#H!H G#I#I#J#J&L&L(M(M(M*P%M%M%M%M)R)R(Q(Q'P'P(Q(Q,!U.#W/%W0&X3'Y3'Y2'V2'V2&X1%W2&X1%W0$U/"T0$U0$U4$T4$T5%U6&V7'W9(X9(X9(X7'W7'W5%U5%U3%U5(W5(V4'U:.RB6ZPM]^[krrr{{{}{n|zmƒk‚€jxx_ppW`bIegMowQt}WsVu„Xw‡RxˆSy‹Py‹P{Kt†DdlGV^9mmm‘‘‘ š¤†‹„}~€yzyt„}w‰€–’‰”…‡Ž€Žs}Šoz‰kw…hu†hv‡iy‡l~Œpƒu„‘v…–x„•wƒ’m{Šeu}^aiJ_bMehSkmRloSoxOqzRu~QyT{‡O‹R}ŒQzŠNxˆJw‡I|‡L|‡L|„Q}…RŠ]„Ža‰”g‹•hŠ—iŠ—iŠ”nŒ—q˜m˜m‹”g‚Š^†W‡Y}‚X{€VvxXoqQmkUljTkkOiiNhhOgfMciJ_eG\dGX_CZcIdmRls]t{fyytxwsqr}klwZf{Wbwbmu€¢__–;;q-(t,'s(%y(%y"!|!{x! y"#w#$x%x t0y7N˜l·ˆ«Ó¨¿Æ®ÄÌÃÊÉÃÊÉÇÉÉÈÊÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÄÊÌÄÊÌÆËÊÆËÊÆËÊÃÇǺ¹»´³¶¶®«¬¤ ž—„“Œx“Žh‹†`}„U†W€…b†c€‚h€‚h|~py|nwzswzsx|ky}l}s€‚vkev`ZkJW{KX|?Y~?Y~E^zH`|RdzVg}_qzar{t|s{~w~z€‚ˆ‰ƒ‰Šƒ‰•…–¡ˆ™¤Œ— …–Ÿ„“œ‚’›‘š‘š™~˜}•‚•‚“Ž‘•Œ“ŸˆœŒ¡ŠŸwŠwŠ}‘€•”£‰“§ž²†œ°ƒ¤°¤°¦­~¨¯€¥±z¥±zŸ±u ²v¡±s °rž°o®n ¯h ¯h¡°i¢±j¤®o¡«l¢©s›¢lžs–—l.<.<.<.<.=.=,<,<+;+;)9)9(6(6(6(6'5'5%4%4$1"/"/"/!- ,++)(''''%%%%%%&&&&%%&&$$%%(((((((())%%%%%%%%!!!      !""""$$$$&&** ) )#*#*#*$+$+$+#,%.%.%.#.$0%1%1'/ '/ )0!)0!+/!,1",1",1"-/!-/!-/!-/!)0!)0!)0!)0!)1)1)1'/'/'/'/'/$/$/$/$/",","+"+!*!*!*!*!) ( ( ( (&&&&%$###""#!!&&-33:7;L=@R6:U27R69X=@_@LlDPpBQuAPt5Kp-Bh+?c)=`#:V5Q"0I+D'>$;2-.-,( '!)3/;TP\b^j[WcNMVTS\NLXOMYNN\RR_VZgZ_l\_p`cu^du^duYapV_nXamS\iWZebepsqvonreelddkegpijspp}ssjkvTU`&+8$)$)408=OS]wU^xMRxINtBGo9=f!J H97^1/V$"I FA?<<77!9*)B)'A#";%:$97!: < < >% C(G) H0$M,!I'I)K0$T6*Z=,_=,_9,Y2%R* K#D>:;;>@@B#H%I!H G!H!H!I!I#I%J'L'L%K'L$L$L%M%M)R)R(Q(Q'P'P'P'P(Q(Q,"S-#T0$U0$U2'V2'V1%W0$U0$U0$U/"T/"T0$U0$U4$T4$T5%U6&V9(X9(X9(X9(X6&V6&V5%U6&V3%U3%U4'U7)WB6ZOBgYVgjfwwwwvvvzxj|zm}|ewv_kjQ[[BOQ7VX>bjDhpJixMn}Qr‚Mv…Qw‰M|ŽR{Kqƒ@[c=6>'''lll˜¢‘‹•‡€„}~„}wˆ€{‰€–’‰”…‡Ž€Žs}Šoz‰kw…hu†hwˆj}Šo€Žs…“w†”x…–x„•w‡•q‚‘l{ƒdiqRY[FY[F_bFceIenFhqIksFqyLx„K|‰P{‹P{‹Pz‹Mz‹M}ˆM}ˆM|„QˆTŠ]Œ_…bˆ“f‡”g‡”g‰“mŠ”nŽ•k–l‹”g†ŽaƒŠ[‚ˆZƒˆ^}‚Xz{[vxXtr\mkUiiNiiNhhOgfM_eG_eG\dG[cF^gLhqWszdzkyytxwsqr}klw\g|YdyZeˆozoo¦DD{*%q-(t(%y'#xzzx! y"#w#$x%x t4~H_©w™Á‹­Õ«ÁÉ®ÄÌÂÉÈÃÊÉÈÊÊÈÊÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÃÉÊÄÊÌÆËÊÆËÊÆËÊÅÊÉÀ¿Áµ´·¯§¤•‘…~jyr_zTƒ}X}„U†W}‚_€…b€‚h€‚h|~py|nwzswzsx|ky}l}s€‚vidu`ZkJW{IUz?Y~?Y~E^zH`|RdzVg}^pxbs|s{~u}€w~z€‡ˆ‚ˆ‰„€™Š–¡ˆ–¡ˆ— …–Ÿ„“œ‚’›‘š‘š™~˜}•‚•‚””˜’Š’ž‡Ž›ŠŸ}‰x‹Ÿ|Ž¢…š˜†›š¤Š‘¦Œœ°ƒœ°ƒ¥²€£¯}¨¯€¨¯€¦²{¥±z¡³w ²v¡±s °rž°ož°o ¯h ¯hŸ®gž­f ªkœ¦g˜ži’˜cŽdˆ‰^-9-9.:.:,;+:,;,;*9*9*9*9(6(6(6(6%4%4%4%4#2!/"0"0 -,,++*) ) ''''%%%&&&'''(('''(())**))))**'&%&%#$$!!""!!    !!   ""!!""# # &&$$&& ) )!*!*#*#*#*#*$+$+#*$+%,%,'-'-(.(.&.&.'/ )0!+/!+/!,1",1"*2!*2!)1)1)0!)0!'/ '/ )1)1&.&.&.'/'/'/#.#.#.#.",","+"+!*' ( (!)!) ( (%%&&&%$##""!!!#$(-.217F39H,1L*/I.1P:=]@IjDMnCRwCRw  %/4:CVT^qW_}QYxNU}NU}ELt3:bEB.-R%$IA?=<872379 5!6$9$9 8";% A#@ >!?%F' H+!N*L*L*L.%T7.]>/a<.`:,\5(W.#P'I@;::???A G!HHHGGHH H H!K!K!K#L$O$O%P%P$M$M%N%N%L%L%L%L%N%N&N&N*!P,"Q1&U1&U0$U/"T/"T/"T/"T/"T/"T0$U0$T/#S2'V3(W5(W5(W7)X7)X5(W5(W1&S/$Q0%R0%R5,T90XME][Skifiurtzypvtk|tj{shuldkc[XONB98=28F;BRIH[SR^]KedRioNpvUw„Px…QxˆJjz[!7T"1H4 ' &$#%%'%#  %54?WUa]ZiMJXGDRMJXNN\NN\NR_RWdQYeT[gT]kV_nY^kcgtqszoqxomtignnktrnxps~sv\`j,/: !*39KUgYcuW_}RZyNU}IPx>Em07_;:$#H"!E@>=<76/036 5!6#8#8!:#<&"B'#C!?!?"C&G,"O,"O*L+!N/&U4+Z:+^;-_;-]7)X/$Q(J A;::??>@D GGGGGGGGG!K!K!K!K!M!M#N$O!K$M$M$M%L%L%L%L#L$M%M%M*!P,"Q/#S0$T0$U+Q, R, R.!S.!S.!S/"T0$T/#S2'V3(W4&V5(W4&V4&V9+[5(W1&S0%R0%R2(U<2[H?gVNfe]utpsvsvtsjxvn|tjtlajaZZQI=43-$#/$*7,2>55D;:HG5QQ>Z`?agFhuAp}IqCjz<]fGdmNbeinqu€€Ž’’Ÿ‘’‰…ŠŒ…Œ…”‘„˜–‰”‹{‡u„s~Œp~Œp{‹o}qƒ‘s†•w‡–x‡–x…šwƒ˜u…šp‡rŠ—i}Š]rtZWY?MGCOIFRMBWQGZXDa_KimHruPwNx€O{†K|‡L|ˆI~‹K‹NŒO€RŽSƒU„’V†Zˆ‘[Š‘bŠ‘bŠ“d–g—c—cŒ•a‰‘^†Ž]…\‚ˆ[|‚V|ZuxSppWmlShgPecM^bJ^bJZ_J`eOho\ovcorjorjnnwnnwbm}^hyOcyNbxG^zKa}ac•^_‘87w32r/*{/*{%$x" uxxxw!v$z#=†[u¾†¥Ä•´Ó¶ÁȺÆÍÅÊÉÅÊÉÈÊÊÉÌËÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÅÊÉÄÈÈÆÇÉÆÇÉÆËÊÅÊÉÆÇÉÂÄƼ¬¸•†’|^lkM[lV`q\fqdZtg^poQtrUv{X„a€ƒe€ƒe~o~oy{oxznx|ix|i|}r‚ƒxoo|``mLYvLYvI]~K`MbzNd{Xgy[i{bnzht€u}€v~‚z€|‚ƒ~‡‚Š…†•~Œ›„•¡„” ƒ— …“œ‚’›‘š‘š‘š—‚–€Œ‘ƒŒ‘ƒ‘“Ž’”ƒ‹šŠ™}Œ—‚œˆ™”‹˜’§‘“¨’Ÿ®‰Ÿ®‰¡®ˆŸ¬…£¬„¢«ƒ ¬ª¡ª‚¡ª‚£¨…£¨…¢¦… ¥„¢¡ƒœ––voŽˆi‰„e†za}qWudUiWHaLB`K@*9*9+:+:):):'8'8*9*9*9)7'5'5'5'5$4#2#2#2#1!. -,,,,+,+ ) ) ((((&&&''''''())))++++++++++++))))(%%%$####"  !!""!!""""" !!!"!!""#!!!!""# # &&$$&& ( (!*!*#)#)")$+$+$+$+%,%,%,%,%,&.&.%,%,&.&.&.&.'/)1'/'/)1)1)1)1&.&.'/ '/ &.&.'/ )0!&.%,#-#-#-#-!*!*"+"+ (' (' (''''&%%%$$#$#"!  '+(05B6;H.9P,6N+6Y.9[3@b:Gi;MqASwCWzDX{@S~=Q{:Nx4Hr3Ei,>b!0G4))#"""$')'(($+@9LYSfYSfPI]TNaYSfSYhTZiV\mV\m^cn^cnhiropyomtkhoihmljonmxqo{qq†ccxACCDHHGGEEJJKKKKKK!I!I!I!I#L#L!KIHI$L%M) O,"Q-$S,"Q)!N'K( L( L*P*P, R.!S+$S-&U,%T,%T0'V0'V4+Z4+Z6*Z5)X2(U/$Q1(P7.VJA\WNhh`htmu}ts{rq}tlwngmebd\XVFTA1?-4*0(4)5*!7.$:3(<;0DD:EODOZVGd`PgkDeiB[_E^bIQQQYYYpry–”‘”‹ˆŠ‰‰Ž}“”‚Œ•{ˆ‘vˆ‘v‡u‡u‡u…Žx…Žxƒ’y„“z…–x…–xŠ˜v‰—u‰›m‰›mŽ¡g‰œc|ˆ]myNWTIIF;G=EI?HICIPJP[ZOih]svXsvXt|Qu}Sx‚Nz…Q}ŽI€K}’F}’F„•I†—Kˆ•Sˆ•S†”V‡•W‰“YŠ•Z‹”^—aŒ“d”eŠ’_ˆ\„Œ[ˆW~ƒZzUsySntOglKcgF^bI[_E[`KdiTfk\hm_jllmpofnxdlv]i{ZfyQbzO_xK[vK[vMX„S]‰@A|==x84}0+u%#s$"r"u$v v v u%z'C…^y»‰§Â—µÐµÁƹÅÊÄÈÉÄÈÉÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÇÈÊÆÇÉÅÅÊÃÄÉÃÄÉÇÈÌÅÉÊÃÇÈ̹ÏÀ®Ã¢v rGp`5jh>rcLogOsk]fo`jri[sj\tqT~z]}‚_„a}€i}€ix|kx|ku€gwi{‡l€‹pz}sptjknrnqueuteutevqevqjxojxoo{sv‚zƒ~~‚|ƒƒ‚ˆ~‡‚Œ–~š”ž†•Ÿ‡”ž†›‚‘š‘š™~™~–€Ž•Ž’„”…”“’‘ƒŒ”…•ƒ’”†–˜¡Š•¦Žš¬„š¬„ ¬ŠŸª‰¢ª¡¨Œ ¥œ¡Œ› ‹™žˆšœ‡šœ‡›˜‹›˜‹š”Œ˜’‰“‹ˆŠ‚†s{}kru^mmUeeL[Z@PT6KP2GW9G^@N*9*9*9*9(9(9'8'8*9*9)7'5'5'5'5'5$4#2#2#2#1!. -,,,,+,+ ) ) ((((&&&''''''())))++++++ , , , , , ,++))()%%$$$###!!""##""##"""#!!!"""!""#!!!!""# # &&$$&& ( (!*!*#)#)#*#*$+$+$+%,%,%,%,%,&.&.%,%,&.&.&.&.&.&.'/'/&.&.&.&.)1)1'/ '/ &.&.&.&.#*#*!*#-#-#-!*!*!*!*'' (''''&&&%$$$####! !   '+(.3@9=J3=U3=U0;^4?a4Ad7Eg;MqASwFZ~FZ~EY„EY„AUb$;.,($"#%$$*,//,&%+0*=D>QRL_e^ragv^es_evbgyejuagqqs{noxliqliqnlqsqvsq}utedyGG\6?M'6$2%0> 3=%/%23CQR[pS\qHMl?Ed5;_6=`6>]/7V= +0 $@"?<=;98868<: 9!:"<#=$?(!B+#I0(M.%M+"J&!K&!K,"Q-$S.%T.%T1&U3(W2%Y3&Z6)[3'Y2&X0$U)!N!E?=;;<>ACCDHHGGFEIIKKKKKK H H H H!K!K!KIHH#J$L(N*!P,"Q) O( L)!N( L( L*P*P)N+Q("P)#Q)#Q)#Q-$S.%T1(W3)X6*Z5)X2(U0%R3*SD;cTLf`Wrrkssltxpo{rqxohpg``XUPHE=-;-*'.&-%1(4'3'3)2-"63)36,7C?0JF7PT-RV/VZAY]CLLLVVVcel}…‘Ž‹ˆŠŽ‹€Ž‹€Ž}‘~‰’wˆ‘v…Žt…Žt„s„sƒwƒwƒ’y„“z‡˜z‡˜zŒ™wŠ˜v‰›mˆšlŒ fŽ¡gŒ˜m‚Žctqf]ZOG=EB8AA;@E>DONCdcXmqRosUowMryOu€Lx‚NJ€K{‘D{‘D‚’G„•I„‘O„‘Oƒ‘T„’U†‘Vˆ’Xˆ‘[‹”^Œ“d”eŒ•a‰‘^†Ž]„Œ[‚‡]}‚Xx}XsySnrRhmL[_ENR8QV@Z_JbgYdhZceekmmdlvcku]i{ZfyQbzO_xK[vK[vISFQ}AB}:;v84},(q&$t$"r$v"uuu!v$y'C…_{½‹¨Ã™¶Ñ¶ÂǸÄÉÄÈÉÆÊÌÅÊÉÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆÇÉÃÅÇÁÂÆÀÁÅÅÅÊÅÅÊÅÉÊÀÄÆŲȤ’§rGpW,U`5jk@tbKndMqi[dn_hqhZsj\rnQwsVz\„a~j~j}€p}€p{†m‰q‚Žs†‘v„‡~„z‚†€ƒ‡v††v††t…€qƒ~u„{u„{w„{{‡~…€€„ƒƒ‚…„‚ˆ~‡‚Ž˜€›‚”ž†’…‘œƒš‘š‘š™~™~–€Ž•Ž’„‘–‡”“’‘†–†–†–˜‰˜š“¤•¦Ž™ªƒš¬„ž©‡Ÿª‰¡¨ŒŸ¦Šœ¡ŒšŸŠ—›†’—‚’”’}Š}‰‚z‚{s{spmebjW__MTXAPM6EO6EQ7FW9NZ51HIC[QLc[Vk^Ynbbhppvrpunlqlmqppunrzos{lpZ^n=GZ::8664<$"B&#F# C% C"@#?%A%A( D-#I4*P3(P2&O)$P)$P+$S/)W/)W2+Z5(Z1%W/"V2%Y6)]3&Z3%\0"Y,"S#K@><<<?CCDDGGGGGGGGGGIIHGDDEEHHGFEDD I$J%K% J#G&J'K'K'K'K'K( L( L(!K(!K)"L)"L*#M*#M-%O.&Q0(U1)V.'O1+S;8TLJe`Yfoiuxnrxnr~orzlnq`jdS]TBW?-B+6(4#3"2!3!3"8"8%;&<&;'<- ;2$@9,>@3EI=EJ>GH?NNDTUN]jcr†{†ƒy„†xx‰||Šƒt‹„vˆŠp‡ˆo‡Šlˆ‹m„‹o†Žq‰’w‰’wˆ’|Š”~Š•|Œ–~šzŠ—xˆšl‡™kŒ¡d£g’¥fŽ¡c‡hw€XZ[RCD:<9;=:W6Bb9Ee6Fh:JlCSuIY{N`‚Se†Nc„K`F[|CWx9Ji.M43.,,,00*,,.00..12!5"6"6!5 2 2**__fppvkinkinghmdeiaemfjrUYh:>M7AT:CV:FX,8J)7I6DV:IY9HXBPdHVjFPgDOfMQnKOlBFhBFhAGc>Ea%+G /7 $?$%C =:744<%#C,)L+(K*%H&!D$@$@$@'C0%K5+Q7+T7+T3-Y0+W.(V/)W2+Z2+Z5(Z/"T,T/"V1$X0#W0"Y/!W*!R%LC?<<<?CCCCGGGGGGGGGGHHFFDDDDHHGFBBDDE!H!F E"G#H%I%I%I%I&J&J'J'J)"L)"L*#M*#M-%O.&Q/'S0(U/(P82YDA]SQlkeqtmzukovlpwhkpaddS]TCL>+A/2(4&2"2"2 2!3"8"8$:$:%:%:*7)6,1.!33'/7+46,;9/?;3BF?NcXcj_ji[[m``pj[tm^uw^vx_y|^|€b}„h€ˆk…Žtˆ‘v†yˆ’|Š•|Š•|šzŠ—xŠœn‡™k‡›_Šžb¢d’¥f”u‹”kxypbcZB>A858C@BTQT`c[df_ahUbiVivPo|V{’NzMxŽFyH~‘J“K‚”K€’HIIJƒ‘M„Q…’R†Xˆ’Y†Z…ŽYˆ’`‡‘_ƒŒ_‰]|ƒ[yXx}ZtyVrxYsy[mmdklcimoeik_hpdmtZlyZlyVf|TczPayM^wIYtHXsGWxDTvDI}05i12t55w+(v'#q$ u$ u" v" v u!v'@‚\v¸Œ¦¿™³ÌµÁƸÄÉÂÈÉÃÉÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÆËÊÄÈÈÃÃÊÁÁÈ»¹Ç·´Â¼½ÆÃÄÍÅÅËÀÀǽ Ç}`‡S*gX.lQ4v[=€XHxZJzcTreWtoahrektj`tj`rtYy{`€…i…Šn…k‰’oˆ™mžr¢p¢p“¥p“¥p•¤r•¤r’¦l’¦l‘¤l¡j‹žgŠeŒœeŒœeŒ˜m‹—l‹“tŠ’s‡s‹•xš{’}”…’š‚’š‚’š‚™~™~™~™~Ž“‚Œ’“‡‘•‰”“’‘‡’“‰”•ŒœŒŽŸŽ•¤†”£…˜¥†–£ƒ˜¡†™¢ˆšŸŠ”™„””†‡„y{xmqkggb^gXa`QZQAVE5JC,I?'D@(GA)HE/GC.FI2FO8LZ>L\AN_CLcFP)7)7)7)7'9'9'9'9&8&8%6%6%4%4$3$3$2#1#1#1#1!."0!.!.!. -,++*)++++,,--.. - - , , , , , ,$.$.$.$.#,#,!.!.!.!.!-!-!-!-))**()!+)((''''&&&&%%$$$$$$%%######""$$#%%%%%$%%%!'"!'"!)!)!)!)#)#)#) '$*$*$+$+#+#+'-'-'-'-&*&*&*&*&- '.!)/#'.!&.&.%,%,%,%,%,%,%,%,$*$*",",",",*(**&&&&%%''%%$%$$####                 ##*,,3/2D58J59T8=W8C_;Fb?HiDMnMVwQ[|QbVg†Se†M_H[|@Rt1B_ ;10--++16000011/*&').55!4"&: *PS^vs}heoffmggnjltghqZcqLUc@L^3?Q.=S/>U0V32G11F%$9115((D00L5/T4.S-&N&G%C%C* D-#G2%K6)P9-V:/W9/\=2_6-\4+Z3(\4)]5*^-"V'P(Q)R)R'R( T&Q&QIE@@;>@@DDDDGGFFIIGGEEDCAAAAEEDDBBCDCCDDBBEEEE" G" G" G" G&"I'#J&"I'#J)%M,(O2&O2&O3,O?8[QIaaYqqhuofswhsrcom\jaQ_O=V:(B*=&865666677 : :!;!;"8"8#:#:)<)<(;)<.:2"=?/FE4LK5HP9MUBJ\IQ\QIcXPhaTjcWli^tqfxyi~on†ˆu‰“vˆ‘u‹—sŒ™t‰™i‡—gŠ›aŠ›aŒ _£b•¤k•¤kŽ—o~‡_dgYDF8@@>QRO[c`\daTeSVhU_sRk^tWtWr‹Pr‹PvRvRy“Mx’K{‘DxAx‰=~CFƒ“Iƒ‘T„’U„’V„’V†“X‡”Y„‘X„‘X€ŽW~ŒUz…XwTt}[oxWmqenrfhongnmbjtais\iw]jxRc{O_xO_xL]vGYtFXsHVvIWwEMy5RE3O=+G7"F6!D8%D:&E;)C=+EC1?C1?L7?O:BV?@ZBD`E<`E<)7)7)7)7'9'9'9'9&8&8%6%6%4%4$3$3$2#1#1#1#1!."0!.!.!. -,++++++++,,--..* * , , , , ,"-$.$.$.$.#,#,!.!.!.!.!-!-!-!- +***()!+)((((''&&&&%%$$$$$$%%########$$$$%%%%$%%%!'"!'"!)!)!)!)#)#)#) '"("(#*#*"*"*&,&,&,&,&*&*&*&*%,&- &- $*&.&.%,%,%,%,%,%,%,%,$*$*",",",", +)**''''%%''%%$%$##!##                !''.22936H69K38S59T2=Y9D`>GhDMnLUvQ[|N^~RcƒObƒM_H[|;No"3P 8/1//++++////002,& $&&-0"5',@PS^or}kgqgdmggnffmegp^`iKSb:CR5AT-9L+;Q)8O*A%<3=U:D\=GZDNaQUpOToIOlMTpORsJLnJLnKMoMQuCGkADd$'G4866J@@U))>24 "8//K44O71W71W1+S)#J+#I,$J,"F,"F3&L5(O4)Q8,U8-Z=2_<3b6-\0&Y0&Y* T* T'P%N%N%N#N&Q'R'R" NHA@;>@@DDDDGGFFIIEECCCCAAAACCDDBBBDCCDDAABCCEEE F" G$G&"I%!H'#J&"I'#J-"J.#L5-PD<`TLdf^vpgtpgtrcom]ibR`RBP=+E3!;)<': 95666677 : :!;!;"8"8#:#:)<)<(;)<-9+6,2-38!58!5:'/<*1?4,C81JB6KD7NK@WTI\]MefVkmZrtaubzƒg~‹f‚Žj„”dƒ“c†—^‡™_‡›Z‰œ[Žd“¡h”uŽ—o‚thj\MNLSTRU]Z[c`UgTWiVfzYo„csŒUqŠTr‹Pr‹PvRvRuItŽHs‰6!D5 C7$B;'F<*D>-FC1?E2AL7?O:BT<>W@A]C9]C9)7)7)7)7'9'9&7&7&8&8%6%6%4%4$3$3$2#1#1#1$2"0!. -!.!. -,--,,,,--- - - - - - +". - -"-"-#/#/#-#-#-#-#-#-!.!."/"/".".!-!-!,!,!,!,!+!+ * *!*!* ) ) ( (''&&&&%%$$$$$$$$$$ % % $ $$!$!&"# %%$$%%& & !'"!'" ( ( ( ( ( ("("("("(#*#*"*"*%)%)%)%)&*&*'+'+%,$*$*%,$+%,%,%,%,%,$*$*$*#)#(#(!*!*!*!*!)!)!)!) ( (&&%%&&%$$$##!!""           "$$*)232;57L69M28P28P0;W6A\@HfDLkJVqP[wP^~R`€O_O_M\~:Jl&B7101-++--++++./0.+*'%&+%>>Komthfmhgkdbgccj``gZ\jDFT.6B:AN8DR4?M(9I&7F%4D%4D.:M8DVEMeQYqUXxLOnJLnSUwMQuDHl;Ae6c80]0&X*!R%N#L"O NMM"P#R'!V'!V"PLGD@@BBCCDDDDEEEEBB@@@@>>==??@@??>?@@AAAABBAA?ABC$ E$ E%!F%!F%!F%!F'C+$G92LHA\ZTgf_spcpn`nn[pcQfP?]<+I.!@+>#?">9666557 89999"8"8#:#:&;&;%:'<':':'6'6+3*2-2,0,0-2-3/52%79,>?5DF'J>'J9&I6$G1$D0#C1'?5*CG4CL7?L7?P7>P7>V7PBNaMUmRZrORrORrQSuUWyORvADh?EhFLoKPqPTv=?a36W00P'(H";$'@&(>%&=)&D ; >#A-&N5.V5-X4,V6-U2)Q.$J-#I/"I- F-H. I0#P>??>?@@AAAAAA>????A B B!C!C!C!C%A*#F:4NIC]ZTge^rm_mi\ifShZG]A0N1 >+>+>">!= :86655666699 7 7"8#:&;&;%:'<':':(7(7,4+3-2+/).+/+1(.(.(.(-,#23)39.9;69D>AMI@TPG[\C`bIioCntHs{Hw€L{…L‹RŠ”\ša‘™m˜l†‹ow|`coRdpSj€TtŠ]y\xŽ[sŒUqŠTq‡[p†ZjUezOcvC_s@\i5S`,Yb,en8mv@t}Gz„JŠP‹QƒŽS„’U†”V„’U†”V„’V‡”YƒW„‘X‚Œ[€ŠX{„bx`u|htzgmvnlumfqtdnq^juZfrM`tI[oFZoCWmCVoCVoDTxAPt4B(>?&'J>'J9&I9&I2%E1$D2(@6+DH#3@"2?/9J.8IAI\U\pRVqMRlKQoPUtQUxMQuPU}JOwPSyQTzORvQUxCDh56Z21V76ZIId;;V1,H61M97S)'B6;%D,&K0*Q4-U<2\6,V*#M'J(G(G(C(C,G9+T=6`@9c90_4+Z&!R LJJ L L"P#R%T+$Z.&Y0(\-%X( TJFAA@@@@AABCGGCA??====::====????>>>>>>>>>>>>>>??????"?($D73LGC]ZTg`ZmcXlaUiUKcE;S2'F)>%@$?;:::8655555566 : :!;"<$<$<$<$<&9&9':&9'8'8'8&7"4!3!1!1!1!1"1"1!0!0$2(60!3:*=B1=H6BO=BSBGZMF`SLfbGmiNuqN}zVƒ]†_‚_z|ZoxOpyQu‚T{ˆZw‰Tw‰Tz‰[v†Xo\k{YdtTZiI[cDU^?KR/EL)QT8\^C_cCglKryOxUx‚N}ˆT„’V†“X†”T†”T†•S†•S„”R†•S„‘XƒW‚Š^€ˆ[wƒav‚`w}ev|dsyoqvlkpogkkZdnXblL[kIXhDZkDZkAVpAVp?Mt/>d-3r+1p)r)r .r*7{/Ev8NFb‚Tp}›“¦±ª¬¬¬®®¶¯©¶¯©¶¬§µ«¦³¨¡³¨¡²©œ±§›¯¦—­¥–­¥–«¢”©Ÿ“¨ž’ª¡“ª¡“¨¢‘¨¢‘¦¤Ž¦¤Ž§¤‹§¤‹¦£ˆ¦£ˆ¤¢‡¢ …¡Ÿ‚¡Ÿ‚Ÿ}›ž{¡y› xš¡r˜Ÿp–Ÿg“e“›a˜]‘š]Ž—[—X‘™Z•\•\’Ÿ]”¢`—§c˜©d™«f›®iœ­mœ­m›«m›«mšªl™©k–ªb’¥^Ž¢Y‰žTˆSˆSˆSˆS‰™VœZ‘œa‘œa‘›j‘›j’šp’šp’—z’—zŽ‘|‡ŠuŠ‚x‡uƒ|v~wqzosukoubllYchOc`G[U9WO2PG%MD"I=!J; I8#H6 F3"G6%I9&I9&I:(I:(I9*A9*A:)@8(?9*A;-CA.?F3CH3BG2AJ4>K5@L2;L2;L1Jj,7S6 * *))****++++++//000-)),0?dhw]^iST_WUZ^\abbhddkGKT/3;/5?)/:%+3$*2+6?2=F"2?(6'7(9>EXT[nTYsTYsSYxSYxSW{PTwKPxMQySV|TW~QUxRVzGIm9;_21V54Y;;V00L.)E3.K30L*(D <9>!A(!I3,T8.Y9/Z0)S&H$C%E%@&A'B0#L4,V70Z8/^4+Z'"S#NKK L L#R%T'!V*#Y.&Y0(\0(\-%X$"PJDBAAAAAABCFFFD@?==<<::<===????=====>====>>====<<<< <&"B50JB>XVOb^Wk]RfZNbND\?5M/%D&;#="<::::9955555566 : :!;"<$<$<$<$<&9&9':&9(9):'8&7"4"4!1 0!1!1!0!0!0"1&4$2+.,/1+1+6%*:(-?1*C6/HC)NI/UQ-\X4_Z9e`?beBbeB^g?bkCfsEjwJi{Fk|HkzLhxJaqN]mKUdDL[;MU6IQ2KR/MT1HJ/CF*QU5`eDjqGnvLr|Iu€L{ˆM‚T‡–Vˆ—W‡–Tƒ“Qƒ“Qƒ“QƒW„‘X„`‚Š^z…cz…cx~fx~frxnrxnlqphml\gpZdnP_oN]mF[lF[lBWqBWqDSy6Ek-3r/6t)7€1?ˆ;IIV›Pg—[q¢b~žo‹«…˜£ «£¥¥£¥¥®§¡®§¡­£ž«¢°¥°¥¯¥™­¤˜«¢”«¢”«¢”ª¡“«¢•ª ”© ‘© ‘¦ ¦ ¤¢Œ¢¡Š¤¡‰£ ˆ¢ …¢ … ƒŸœœœšzšz˜œu—›t–œn–œn”žf’œd“›a˜]™\Ž—[˜Y˜Y‘šY‘šYœZ’Ÿ]’£^—§c–©d™«f›¬l›¬l›«m›«mšªl™©k•©a’¥^¤ZŒ V‰žTˆSˆSˆSŠšXŠšX™_™_™gši˜m˜m‹sˆq‚…p{~ivndnf\ib]f_Y]RVSIMR?IOB&CB&CD"ID"I>"K=!J:$I;%K7&K6%I9&I9&I:(I:(I9*A9*A8(?:)@9*A:+BB/@B/@H3BH3BJ4>J4>L2;L2;J0;L1<)9)9(8(8'9(:'9'9&7&7%5%5#4#4!2!2"0"0#1#1$2$2"0!. . . . . . . . .- - .... / / - -"."."-"- + +#-#-#-#-#-#-$.$.$.$.$.$."."."."."-"-"-"-",",",",#+#+"*"*"*"* ( (''&&&&&&&&&&&&%%&&&&& & & & &"&"& & %%%%&&&&&&&&&&&&"("("("($(%)$($(%)%)%)%)%,%,$*$*$* $* !)!)!*!*!)!) ( (!)!)(''''&''%%$$####""!!! !             %)&"50+>03G46J48N7;Q3]&.M#;.**))--++++----//1/-+'!!.khokho_[`]Y_^\aa`dbajONW"+9,5D,9I#/?%2&3!,:)4C.:F,8C8AO1:H6?!B)"L1*T4+Z4+Z,'X'"S"M LKJ K$O&Q( T+"V.&Y2)]2)],'X&!R"LG@A?>>?AB @ @"A=<::876669:99777788998888::::999999998"=/,H<9UPJbUOgQJ_MF\C>U71H)$@!888:97766555556779 :"I:?M>BQEETIHWOKYQNYOQ[RSTNQRLOLEKA;@?8>?8>@=B659<;>LLN[ZObaVhjUoq\ryXz`„[ˆ“_‰–[‡”Yƒ“Qƒ“Q„”Rƒ“Qƒ‘T„’UY€ŽW~ˆ]}†\y‚ay‚aszdpxblsejqb`ld]i`VbiS^eN]mL[kJ[sIZr>RnAUrUd}\k„j{†s„Ž‡–““›š¢—Ÿ§–Ÿ§–¥¦–¦§—¬©—¬©—®§š®§š¯¦—­¥–«¥–«¥–«¤˜«¤˜©¢•©¢•¨¡”§ “¢ž‘¢ž‘ ’ ’Ÿœ‘Ÿœ‘¡›ŠŸ™ˆŸ˜‰Ÿ˜‰Ÿš†œ˜„›—œ˜‚™™~™™~™›{—™x—šu—šu”›l“šk‘šd™cŒ•`‹”^‹˜VŠ—U‹™UŒšV›S”ŸV’ \’ \—£d˜¤eš¦i¨kš¨o›ªq›ªqš¨o”¦j’¤h¢d‹ž_‰Z‡œY…™V†›X†›X…™VŠ™Y‹šZ›]Žœ^ˆ—^‰˜_Ž“d‹a„~fwqYnV_bKSS:NM4HH,GF+FI-JF*HF,PC*N8#H;%K9%L8#K7&K6%I3(G3(G3(G3(G5)D5)D:+D9)C9)C:+D8(?7&>;,>;,>>-=>-=B/@F3CI4CI4CH5>F2DZV]rV[vY]x^`}XZwVXuWYvWYvUVtSRpVUsVYzSUwXY}UW{35Y./S0/R22T74R74R75P75P+,K?<:A#"F%#L.,U30\+(SGB?>>?!D)"L/&U3)X,'X% Q#N"M"MK#N#N%P&Q)!U-%X/'Z/'Z-(Y+&W&"P"LAA?>>?AB @ @ @ @<<<;876699887777888877779999999999997 ;-*F86RKF]OIaLE[H@V@:R50G(#?455687766555556779 :"56B8:=7:=7:9386/56/56/572:<7>:8=538658=;,>>-=>-=C0AG4DJ5DJ5DK8BK8BL8EL8E(7(7)8)8(7*9(7(7'9'9%6%6%6#5"4"4#2#2#2#2#1"0!/ ... / /".".".". /!0 / / 0 0 ../ / / /".".".". , ,!.!.!-!-".".$.$.$.$.$.$.$.$.$-$-$-$-$,$,$,$, ) )( ) ( ( ('''&&$$%%''''''&&$$$&#'"#'""& "& & & & & &&&& % %&&%%$$$$%%&&&& ( ( ( ( ( ( ( ("("("(#)#( #( ))!)!)!)!) ( ( ( ((''''&%%$$##""""                !'.#"2)&;20E04J7;Q6;S6;S5Y8?^9A_:Ab;Bc9Cc4>^3:T")D2,)))*+-++**,,--.-+,+,YW^khodadgdfcb`a_]dbgQOT*-/<09N>G\3E`+=X0E$:&3+80;)4 '6&-b8:^8;[7:Y58U7;X:9W98U00P$%D;:?AG++T52^2/Z(#MC<<;;;"?&H-%O,#P,#P#N L L LII J"L$Q*$W*%U+&W.'X/(Y,$U' Q# JEA?>?>? @ @ "? "?"="=";";";764443344644444446677664466444438)'B41M@@VJI`FCXA?T<7N4.F'"<355555555554456777 8 8 8!:!:!:!:!:!:";";"<".C>.C>.C?/DD2EG6HH7JH7JJ:HJ:HK;IJ:H)8)8)8)8(7*9(7(7'9'9&7%6%6#5"4"4#2#2#2#2#1"0!/ . / / / /".".".".- / / / 0 0 ../ / / /".".".". , ,!.!.!-!-".".$.$.$.$.$.$.$.$.$-$-$-$-$,$,$,#+ ) ) )( ('''''&&$$%%''''''&&$$$&#'"#'""& "& & & & & &&&& % %&&%%$$$$%%&&&& ( ( ( ( ( ( ( ("("("(#)#( #( ))!)!)!)!) ( ( ( ((''''&%%$$##""""             #(! 0$#3+)>52G48N8Y8?^9A_:Ab:Ab6?`09Z.5O"=-,)))*++++)+++.+++*+,QRbmkra_fgdfdad_][hfdWUZ! $!#1/1?1:OAI_b9<\?>?? @ "? "? #> #>#&>#&> $< 9 95554344644444446666664466444446'$@30L=:NHE[\CfhOszP}…[„‘X‰•]ˆ—U†•S‡–Tˆ—U‰”Wˆ“V†Z…ŽYŒX~‹W€Œ\€Œ\}ˆ`|‡_wƒau€_v|kw}l{…zƒ‚‡“†™‹”žŽ—¡’ž£“¢‘£Ÿ¥’¡¥’¢¦“£§•£§•¥§•¥§•§£”¦¢“¤£‘¤£‘¦ £žŒ¡™œ”Š™Š–ˆ–Œ‰•‹ˆ•‰‹–ŠŒ–ŒŽ—…‡Š€‚†Œ•Œ’˜——–”ˆ‘‰}…znvznvy|ˆƒ†™™’¡¡š¢ª˜Ÿ§–¡©‘¡©‘£ª¢©¦‡š¢ƒ™¡{— z•žo‘šk˜bŠ–`Š–WŠ–W˜OŽ™PŒ—L˜NŽšV’Y”œb•žc d’¡f¡g¡g¡l¡lŠŸi‰h‰žg†›c…š]…š]†™[‡š\Š›V‰™U‰™U‰™UŽšVŽšV‹—Z‰”W…\ˆWxc|s^vbhmY^[AZJ/H@)E?'D=)J;'G<&E<&E=&G=&G9'G9'G8%H8%H8%H8%H8'E8'E8'C:*E;+G<,H<-F=.GB2FB2FB2FC3HG6HJ8KJ8KJ8KK;IK;IK;IJ:H*9*9)7)7)7)7(6(6&6&6%5%5#4#4"2!1#0#0#0#0#1"0"0!/!/!/ . .".#/#/#/"/"/"/"/ 0 0 .. - - - - + + * * , , , ,!-!-!-".$-$-$-$-$-$-$-$-!, +* +",",!+!+ * * *)(()'&&%%$$%%%%$$$$##&&&& & & & & & & & & &&&&&&&&$$&&&%&'''''&&&& ( ( (&"&"&#( #( #( #( ))!)!)!)!)))((('''&%%%$$####"!             "(+" 3'%7/+C74K<;R=Lg?MhA`>>^;<\5:Y-2Q#&F?B=C$!K('R/.Y0+U&!KA<7787;> C"E#I!HI J M!N$Q$Q#N% Q)$T+&W/(Y0)Z-&W,$U&#O#K FB>=>?9!;"; $< $<"%=$(>%)?$&:!$8 #7 43310111.0000//0011111000111122%#<10I87NED[DAV>;P97J32D'&=2--....////2233443344 7 7 7 7!:!:!:!:"<"<"GXOWfdWrob…]‡f˜b‰•^ˆ•Z‡”Y‰“Y†‘VŠ”\ˆ’YŒV€‹U‹RU„ŽV„ŽV€Œ[€Œ[]„’bŒ˜m‘r–Ÿw›¤|ž£€ ¥‚ž¢Šž¢Š £Œ¡¤¥¤’¥¤’¦¥“¦¥“§£”¨¤•¨ –£›‘¤•‘Ÿ‘ŒžŠŽ™†‰‘{Ž‹uˆƒiˆ|byZ†tUrQ‚rQ‚rY†t\ˆsa‰tcŠsi†|r‚|–…~˜‚{—rk‡`^xge~sx„‡‹˜’¢›šª£¡ª›£­ž¡­–£¯™¥¯™¢«• ©Ÿ¨¤ƒš ”œv™s•kŠe‡Œ\…ŠYˆŒYˆŒYŠZ‘\Œ–W—X’œ]“^‘g’žhšmšmŽšqŽšqŒšqŒšq‰˜k‰˜k…–d„•c…˜a…˜aˆ™[ˆ™[„™T„™Tˆ˜Tˆ˜T‰˜X‰˜Xˆ—U‡–Tˆ’S‹LTtsHmXS[F@L/AJ-@?(C>'B:%D:%D<%F<%F9'G9'G8%H8%H6$G8%H6%H6%H9+H;-K;.I=/J=0H>1JA2KB3LD5ND5NF6MI8OH7NH7NJ8KH7JH7JH7J(6(6)7)7+:+:(6(6&6&6%5%5#4#4"2!1#0#0#0#0#1"0"0!/!/ . . .".#/$0#/"/"/"/"/ 0 0 .. - - - -",", * *++ , ,!-!-!-!-$-$-$-$-$-$-$-$-!, +* +!+!+ * *)())((('&&%%$%%%%%$$$$##&&&& & & & & %%& & '&&&&&$$$$$$&%&'''''&&&&&&&&"&"&#( #( #( #( ))!)!)!)!)))((('''&%%%$$##"""!           "%*.$#5)':1.E96M=BXJcfvlplolojg~YUmOKeYTnSMlRLjUQrZVv\]|[[{QRvHJnEFkCDhCDh?Ae>A`>A`ABb@A`7=\17V,0O$'G DBCD! K('R/*T(#MB;777699>ADEH J!N"P$Q$Q#N#N% Q)$T-&W.'X.'X-&W*&R&#O#!H FA?>?9!;!:";"%=#&>$(>#'=$&:#%9!$8!53332110.0000////00001000//0033 7-+D76MBAXB@U=:O97J32D)(?#"9/-....//..0002003344 7 7 7 7!:!:!:!:"<"<"<"<";";!: 8"8"8"8"8&7&7&7&7)5)5)3)3+2+2+2+2+2+2+2*0(/(/(/(/!1!1"2"2&4&4$7%8!=!= < <#=#=#;$<$<%='=(?+@+@,=,=, 7, 76-5F(G='F<%F<%F6%E9'G9&I9&I8%H9&I9)K9)K=.L?0N?1L@2N@3LA4MD5NF6OF6OF6OH7NH7NF6MF6MG6HG6HG6HG6H)8)8)8)8(6(6(6(6%5%5%5%5#4#4"2!1!1!1%0%0"0!.!/!/#/#/#/#/".".#/"."/"/"/"/ 0 0 , . - --- , ,+++++ ,!-!-!-!-$-$-$,$,$+$+#*#*!* ) ) )))))((('''''&&%%$$$$#$$$$$##%%%%& & & & !&#!&# %" %" ' '&&&&$&&&&&%%%%&&&&&&&&&&#$&& ' ' ( ( ( (!)!)!)!) ( ( ( ('&%%%$$$##!!""!      + + + + + +       &'+ 0##8))>30G:7N<:S<:S79T35P,1P,1P06U17V35Y24X/3W(,P#?1 ) ) '))****)*++-++&&KMShjqa]Yc`\fc_b_[_Zab]e$!/')%6*&7&(5.0=08I6>O8E\@Md=Nf8Ib0H.G 0B!2C'0<8BNcgtglymn~noFIaGJcWSqZWuXRwYSxZYyZYyXYy[[{PWxIPqDKnCImBHl=Dg<@d<@d=Cb=Cb:=]9<\.6U%-K $H?@CE$!K+%Q+%Q E=:95556:=ACI"!L#!O$"P%!O&"P#M#M#M$ N("P-&U/*V0+W.-T-,S*)M&%J"!EC<:9: "8 "8!$6#&7&'7'(8%%7"#5%'4#&3!#2!#2 !1 !1 0..,..--------..,,--,,++))1*(<1.E:7N@>Q<:L86I53E,+='%7/')*))'***++++*+--001 222 8!:!:";!;!;!;"<#=#="<"<"<"< : :"7"7"7"7$7$7$4$4'5'5*2*2*3*3*0*0(1(1'0'0$1$1$1$1"4"4 4"5 7 7"; 8#;#;"<$>$<$<%=&?'A'A)A)A)=)=-=3%BA6CTHUe_Urla{~Y‚…`‰“Z‰“Z‹“`–bŠ“]‰’\†Z…ŽYˆTxMx|N~‚Tƒ‡Yˆ^‡”Y‹™]‹™]Ž›`‘™f“œh–šn–šnšžwšžwœ›}›š|™|™|ž—„Ÿ˜…ž‰—‰‚{ƒˆs{|btYyiLy_BpX:qR4kM0lF)e=%f>&g<&g<&g9*cB4mOLwIFr@Ag==dCBkJIrMQuW[~_izu‘œ–¨¢¥­œ¤¬›¦¬™¥«˜ª¬™©«˜§¦–¢ žš‹˜”…‘{Šˆt‹€r‚wi|qcuj\ogQogQqiQumVzwQ~X„‡T‰ZŒ\Ž‘_‹’c•g•h•h‰’o‰’o…’m„l†’i†’i‡h†gƒ‘_ƒ‘_€[‚‘\…•W„”V„–Q‚•P†•S†•S‡–Tˆ—UžWˆ™Rˆ”TƒP‚{XuoKjRVS:>G*CE(A<$H<$H;&I;&I9(M7&K7)M7)M8*N;,P:.P;0Q=3R>4S@7Q@7Q>6N>6N@6N@6N@6N@6ND6MD6MC5LC5LA6J?4H?4H@5I(7(7)8)8)7)7(6(6%5%5%5%5#4#4"2!1!1!1#.#.!.!.!/!/#/#/#/#/".".". -"/"/"/"/ 0 0 , . - -..!-!-++++++ , , ,!-#,$-"*"*")")#*#* ) ) ) )))))('''%%%$#########$$$$$##%%%%& & & & !&#!&# %" %" ' '&&&&&%&&&&%%%%&&&&&&&&##$&&& ' ' ( ( ( (!)!)!)!) ( ( ( ('&%%%$$$##!!!!       + + + + + +       ()/"$3))>--A30G:7N<:S<:S58S02M(.M(.M,1P/5T24X13W*.R&*M8 0 ) ) ' ' ')))))*+--+, *SYcehnZ\cb_[c`\_\Xeb^c^f-(/#%#1-*:0,=/1?/1?.6F7?P7CZ8E\4E],Bf<@d?Ed>Dc=@_:=]3;Y-5T&*MBA@DF'!N'!N E=:955558<@AH"!L#!O$"P'#Q)$S%!O#M#M#M'!O,%T/*V2,X0.U.-T*)M&%J&%J"!E "?<886 "8 #5!$6%&6&'7%%7%%7%'4%'4%&6#%5"$3!#2 !1 !1 0/000000.---..----,,++)).#!5,)@30G=;M=;M75H32D/-?(&90()))))))*++++*+,,--.01277 8";!;!;!;!;"<"<"<"<"<"$>$<$<%=&?)C)C+C+C)=)=+:-=3(5A6CSNCb]RosNw{VƒU‹•]Š’_Š’_Š“]‰’\†Z…ŽYƒ‹X~‡Sy~Ox|N{QƒU€R„’V‡”Yˆ•Z‰‘^Š’_‘e‘eŒiŒiŒnŒ‹mŠ‡jŠ‡jŠƒpŒ…qŠ}v€rkwbjhS[]BbS9YN1_N1_N0gO2hQ4oP3nG/pE-nA+lA+l>/h@1jA>iA>i==d89_66_88aADhMQu`j{{…–š”š”–ž–ž—Š—Š—™‡”–ƒ‘€‰ˆx„€q{whtr]khTk`Qf[McWI_TF_VAaYCe]EmeMqnGurL{L‚P‚†SˆŒYˆŽ`Š‘b‰‘e‰‘e…k…kiiƒŽf„g†g…Žf‚^‚^Z~ŽY€S’T‚•P‚•Pƒ“Qƒ“Q†•S†•Sˆ™R‹V‹—X…’Rˆ^~xUu]afNRQ4MF)B>'J>'J?*NA,O=,P:)N9+O9+O;,P=.S>3U@4V>4S>4S@7Q@7Q@7O@7O@6N@6N@6N@6ND6MD6MC5LC5LA6JB7K?4H?4H(5(5(5(5(5(5'4'4#4#4%5%5#4#4"2!1!1 0#.#.!.!. . .".".#/#/!/!/"0!/ / / / / . .,+**,,++******)) * *"*"*"*"*")")")")''&&''''''&&%$$$####!!$$##$$$$##%%%%%%& & %" %" %" %" & & %%&&%$$&&&%%%%&&&&&&&&$$$ '&&& ' ( ( ( ( ( ( ( ( ( (&&'&%%$#####!!           + +        %+.!!5'';,+@11F73L=9R;8T97S46S.0M',K',K,1P-2Q14T/2Q,1P"(G3 - ( ( ' '))***+')')% &0^dagli\]T_`V_]W][Ta[^lgj61A "0'#6,+=/-?/2D25F+3D/7H/=O1?Q->U 2H)A,E"3L!2K#/AFQdfl}fl}in€dj{bf|]awORkX[t[]x\^yac~eg‚cg„[_|QXyNUvJPtJPtHNrBHl?Cg<@d=Bc`8:\36W10T&%JEAAGH! KE?:955337:<@FG! K"!L%!O&"P$ N"L#M#M%K(#O,&R0+W0.U0.U+*O)(L''I&&H#$B "?:966!5!5#$6%%7&'7'(8')6),9'*5%(3%(3"%0#'1#'1!$/!$/#/ %2"%2"%2 0/.+++,,..,,++,))),0(&9/-?87G?>N54D21A,,<'&6"!1)'&'''%%''&%&''(+++,-//4 544!8!8"9"9"<"<#=#="<"<"+!>&?%=%?#=$?%A'C)D* D+!E, D, D*=*=,:,:/ 93$=<1>NBP^XNkeZvxX~€`…Œ]Š‘b‹”^Š“]ˆ’Y„ŽVƒŒVŠTƒUw{MtxJuyKx€H‡OƒM†Q…’R…’R…S…S„‹SƒŠR€‚TSzX{vUzrZyqYucbjYWcI[Z?QO7VO7VH4[H4[F5aI8cC7bF9dJ8fE3aD/^D/^C0aE2cG6bF5aA4Z>1X93[>8_>?\LMjdfztvŠ€z†y…†w‚ˆy…†v}‡w~†q{kuxapoXgiN`bGYY:XR3QJ0OH-MI3ML5OUBQ\IXaUPfZVhcHmiNomIsrNwuQ}{W~€Y„†_ƒa‚„b‚‰h…‹jƒŒjƒŒj‚Œf€‹eŠd~‰c~‰c~‰c‰^‰^€Y€Y‘U‚’Vƒ“Qƒ“Q‚’Oƒ“Q‚•P…˜Rˆ™R‡˜Q‰T€‡JxrSmhIbJUT2V>3U>3U@5T@5T@7Q@7Q>6N@7OC8QC8QB7OB7O@7M@7M@7M@7M@6N@6NB7OB7O(5(5(5(5(5(5'4'4#4#4%5%5%5#4!1!1!1 0#.#.!.!. . .".".#/#/!/!/"0!/ / / / / . .,+**,,++******)) * *"*"*"*"*")")") (''&&''''&&&&$$$#""""##$$##$$$$##%%%%& & & & %" %" %" %" & & %%&$%%$$$$%%%%&&&&&&&&$$$&&&& ' ( ( ( ( ( ( ( ( ( ( ( ('&%%$#####!!          + + + +      #'/"$3((<--A44H77L<7Q>:S;8T86R12P.0M',K',K*0O,1P03R.1P(.M$C0 - ( ( ' '))****++*%(.8agqcifV\Y]^U]^U][TcaZrmpD>A !,%!4&"5*(;,+=47I7;L8@Q2:K+9L/=O);Q/E0H5F^K\tAQj,8J9EW^duafwbgy[`r^bx]awX[t[^v_b}bdeg‚fi„ae‚[_|RYzS[|MSvHNrFLoBHlADh=Ae;?a8=^9<]9<]8:\36W43X+*O!JCCFG JE?<:553367:<CEG! K%!O&"P#M"L"L"L%K%K(#O,&R+)P+)P+*O)(L))K''I%'D#$B #>:66 4!5"#5#$6"$3%&6%'4')6),7*.8'*5&)4'*5%(3"%0"%0 %2 %2"%2"%2 0/....,,--,,+++++)*-" 3+*<32C:9J76F10@,,<'&6$#3.(''''%%%%%&%%%&'))+,-.22346!8"9"9"<"<#=#=#=#=#="<#8"7 6 6"6"6$4$4$4$4$1$1&4&4$2$2#0#0#0#0#0#0!0!0!3!3 4$7+";,$<.$A.$A'@&?%?#=%A&B)D*E+!E+!E."F."F*=*=,:,:-7/ 92'4<1>KF;]WMhjJtvU}„U‡Yˆ‘[‹”^Š”\†X„W‚‹U€…V|€RvzLrwHt{Cx€H|†G€ŠK„Q…’R‡U…SƒŠR‰QƒUSzX{vUumVumVq`^n\[jPbeK]X@_V>]N:aJ6^F5aF5aB6aA5`D2`@/]?+Y?+Y>+\@.^?.Z=,X;.T:-S1+S3,T23Q?@^NPdOQeVP\VP\_O[_O[aQX]MT[FPXBLO7FI1AF,=A'9@"@? >; @=#BB,FI3MO=KXFT]QLdXShcHhcHhfBjhDlkGomIqsLvxQwzWx{Xz`†e€‰hƒŒjƒg‚ŒfŠd~‰c}ˆa}ˆa‰^‰^€Y€Y€T‘Uƒ“Qƒ“Q‚’O‚’O”O„–Q‡˜Qˆ™R”W†P{\xrSpXcdLWN8YE.P>0R?1T;1U;1U<0T<0T=1U=1U=2T=2T=3R=3R>5P>5P=5M>6N@6N@6N@6N@6N@7M@7M?6L?6LB7O@6NB7O?5M(5(5'3%2'4'4(5(5#3#3"2"2$2$2#1#1"/!.#0#0"/"/!.!.!/ . / / / / / /!/!/!/---++**)+ * *((''(( )(((((((''&&%%$$&&&&&&%%%#######""#$%%%%$$$$#$%%%%& & '#'#$!$!$!$!$!$!&&$!$!%%$%%%%%$$$$$$&&&&&&&&&&&&&& ( ( (& % %"&"&"& $##$$$$!!""!!!              #,.$4,'<1*D:4N>7QA:UAS+=Q$6J /E)8O5C\>Le>Md?NeS]pZdw^as]`r_bt_btVXlWYmaaujjjk‚fh~Z`}RXuX]~X]~MW~GQxEOtALq?Gl=Dj;@f5;`66]89_66_22[2/Y.,U$$M HCF J! KF?;;::88887:;>A F%#L$!K"!L"!LII#G$H'#J($L)%M+&N-)P,(O**J**J'*E%'B#%@ #>!#9 "833#!4#!4!#2!#2%'4&(5,+4-,6--4++2+.4+.4')/%(.#'0!%- #."- "/ "/!.!. - - -,%%+'++++))().'%7.,>32D96F30@2-;-(6(#3$/ +(%$''&&%$&&&&&'))))*+..0133788 9!:" ;#<#<#<#)@'='?(@)A+C.$C/%D,!@,!@.!@.!@- ?)<*6*6,!5-"6:/:H=H[TGg`SqqO||Z~†U†Ž]Š•X‹—ZŠ’X…S„ŠW€†S|SuxLtwGprBrzFv~K|ˆJŒOƒ”O‚’N„’U‚SƒŽQƒŽQŠP}ˆM{ƒPxMtyOotJklLdeEa]G]ZCYQGUMCODHMCGK=KH:HE6HB2E?-I>,H:*E=-I;)J;)J9&I9&I8&F8&F6*G6*G>+I>+I@+I@+IB)MA'KA%IA%I=!F?"G="H="H8H9 I:!L:!L3!J4"L5&K<-RG7RM1Y@2V?1U=3R;0P90Q90Q;1N;1N?4O?4O?5M?5M<4L=5M=5M>6N@7O>6NA9QA9Q>8O<7N?9Q<7N%2%2$1$1$2$2#1#1"2"2"2!0$2$2"0"0#0"/"/"/!.!. - - .-...... . .----++)*))))''&&''&''')&&&&&%%$$$$&&&&##%%%#""!!###"#$%%%%#####$%%%%& & &"&"$!$!$!$!$!$!&&$!$!%%$%%%%%$$$$$$$$$$$$&&&&&& ( (&&&&"&"& % % $ $##$$$$! ""!!!               &-/% 5-(>70J<6PBW9?W52C30@0,:,'5)$5& 1".)&$''%%%$&&&&&'))))))--//227778!:!:#<#<#<#<";";";"; 7 7 7 7"4"4!1!1!0!0!0!0 . .!0!0".!-".". -!.$4&6&4(!7) ;) ;&;'<(?)@(@)A)A+C,!@.$C,!@,!@.!@.!@.!@+>*6*6,!5-"6/%09.9HA5VOCaa?mmKv~M~†U„S†’UŒ”Zˆ‘V„ŠW‡T~V|SvxHmp@muBpyEs~Ax„G{‹G€K‚S‚S…‘T…‘T„T‚R‰UˆT|WzUvxXrsSokUjfPg_UbZPZPTYOSWIWSFSP@SK$H@#H@#HA%IB&JA'MA'M=$L9 I<"M<"M3!J3!J4%I8*NC3OI9USELXJQc\Hf_LghCfgBgj>fi=faBd^?aZEc]GeaQkgWjn]rvet€ey„ix‰dx‰d|†`|†`{†]{†]‰^‰^ŽZ€Y€R‚T‚Sƒ‘Tƒ‘Tƒ‘T…’R†“SŠ•Q‹–RˆZ„ŠU‚xgzp_lZhcP_N<\C1R>1Y>1Y?1U>/T;0P<1Q90Q8/P:0M:0M=2L=2L=2K=2K<4L=5M>6N@7O@7OB:RB:RA9Q?9Q<7N>8O<7N'4%3#1#1%3$2#1#1"2!1!1!1"0"0"0"0!.!.!.!. , , - - . . / /....-- . .--**))&&&''&$&&&'''''%$$#%%%$$##$$$$####%%##!!!!!!!"####""""$$$$%%$$# # $!$!$!$!$!$!%%&&%%%%%%%%$$$$$$$#$$$$$$&&%% & & %%&&&!( $$%%%%$$##"!!!""             %&0 2'!;0)C9/LA7TI<[L?_H?`C:\:7Z64V13U.0R--O0/R0/R22T;:X77T$&A1,) ' ' '*)))'$ )-.`denodbdX]]P_`RfcXli^lefqikXR\ -94I<>Z?CYEI_CH\:?S4>O:DT/;N'3F+?4CX@Xt3Kg9Pl6Lh8G^ETkY\p_aueewcdvaevhk}ehzcfwef}gidfUWrMRsTYzWZ€WZ€PX~MUzMUxIRu@Il;Dg8@e3;`54]//X.-X,+W3-Y2,X,*S&$MC@DGE>;;::::;;76679<B D!!J""K HGA??B$ E&#H&#H)&K))I**J+-H+-H+-H+-H)+B')? %9 %9 #5!3"4 #5 %2#/! 0#"2%%3%%3#%-'(1)*3)*3%)1#'0!$/!$/!.!. "/ "/!$/"%0#'1"%0"#, !*++)))(((('''"!1*)90-;2/=0,8,(4,(4)%1%!-"*$!  ######$$&&&&&&''(()+..33346 7!8!8!8!8"7"7"7"7"9 7!6!6"4"4$4$4"1!0 . . .-,,-,--,!."2%5&7&7&9&9&;'<(?(?(@'?'?(@)@+A+A+A.!@.!@/"B.!@0"=0"=-!9-!91%41%49(9C2BPEK]QXibUtl`{{Wƒ„_Š\‹‘^ˆZƒ‰T…R|‚NyzOstIolKpmLopKrsOvxO{}T~ŠM‚P‚’N‚’N„’Nƒ‘Mƒ‘Mƒ‘MƒLƒL€ŠK~ˆI{€Iw|DsxAqv>nt?jq;hk?cg;]]=XW7QI?LD:F=>D:ij>gf;ed8`V=^T:]NJdUQcV[k_cjhasqjx|k~q~…i}„h}ˆa{…_~ˆ]~ˆ]~‰Z€Œ\‚Y‚YƒY„ZƒW„‘X‚S‚S…‘T†’Uˆ‘V‡U‡…Z‚Vxm]mcRaQ_SCQC2PB1O=1U<0T;/S;/S80N7.M7/L7/L92L92L93K:4L;5M<7N<7N?9Q?9QA;SA;S@:R?9Q<7N<7N:4L$2$2#1#1#1#1#1#1!1!1 0 0"0!.!."0!.!.!.!. , ,,,--..- - - - ----,,))((&&#&&%%$%%%%%%%%%%%&&&##""####""""####!!!!!!!"##$$""""$$$$%%$$"# $!$!$!$!$!$!%%&&%%%%%%%%$$$$$$$$&&$$$$&&%%%%%%&&&&$$%%%%$$###"!!""               &&.1%9,&@5+H<2OB5TE8XB9[B9[:7Z64V13U.0R11S0/R11S0/R65S21N8/-) )* ')))*,"/4AkoqhlmacWacWaaShhZjg\li^tmnKDE $#3;6LCC^;;V77S66R-1G04J;@TAFZ0;K6@Q3?Q&2E/C*:N0Ie9QmE[x6Lh=LcL[r_aueg{hi{ghzcfwfi{gj|aevcd{giX[vHJeMRsZ_€Z]ƒX[U]‚NV{LTwIRuAJm:Cf:Bg4#D%!F%!F&#H%&F'(H)+F+-H-0K-0K.0F,-D&+?#(;$'9 #5 #5!$6 %2!.! 0! 0""/""/"#,"#,%'0%'0#'0%)1"%0"%0!#1!#1!#1!#1!$/"%0#'1"%0#%-!"++++))((('$$%)$#3,*8.,:3/;.*6,(4+'3)%1($0%"* %&#$#######%%&&&&&&''()..333556!8!8!8!8"7"7"7"7"9!8!6!6"4"4$4$4"1!0 . .----,,--, -!1"2"4"4"6"6$8%:&<'='?&>'?(@(?(?+A+A.!@.!@.!@- ?/!39LAGYREc\PmnIvwR~„Q„ŠWŠ[‡ŽY€†S|‚N{|QyzOwtSqoMijEmnIoqHruLv‚E}‰LJ‚’N„’Nƒ‘Mƒ‘Mƒ‘M…N„M„ŽO‹L€…N~ƒKy~Gw|DtzEtzEquIpsHkkKgfFaYO]UKXNPRHJOBMI5"@5"@4 ?4 ?4 ?5"@7 C7 C8#G8#G:%H:%H:$I:$I6"J6"J4I4I-H-H+ F-#I5)F<0MH9IM?OWLE^SLc`@ebChi=hi=gf;ed8bY?[R8UFBUFBYLP_RVa^Xif`qudvyiyd}„h}ˆay„^{…[}†\~‰Z~‰ZŒXŒX€‹U‚ŽWƒWƒWƒ‘T‚SƒŽQ‚P†ŽTˆ‘V‹`‡…Z€ueukZiYg_N\N=[C2P<0T;/S;/S;/S7.M80N7/L7/L92L92L:4L;5M<7N<7NA;SB8O<7N;5M:4L 1 1"3!2!0!0!1!1 1 1 1 1"0 .- . ,+++++,,.- - - - - - - + .**((')''%'%%$#""""##""""""!!##!!!!!""!!!!!!!!!!"! !!!!""######!!# # # # $$%%%%%%$$%%$$$$$$$$&&&&%%$$$$$$$$&&&&&&%$%%$#$$$$%%$$##!!!!        +     "+-12%:*"?2'F7-L:/N>4S@7XA8Y<6[94Y74Y41V.-R.-R.-R-,Q2,K*$C3/0+)&))&(%%/34koqmn^efVggShiTkiZnm]qkglfc6-A /"4 "8.0FJNi9>Y-4N07R3:O'-C>FW@HY:DT6@Q0?O-=L*:N1AU3E`CVpE]|>VuAMfIUo]_sdfzjiyjiyeixgkzhk}fi{cj~bi|S^uKVmP[wVa}X]|W\{U^Q[|QZ}LTwCOv=Ip:@k6z‚:z‚:z=ysv>kjDff?b[G]VCWJJQDDI7EB0>7&>6%<6!=6!=3?4 @5!B5!B3!B3!B4"C5$D8#G8#G7!I7!I2I2I.H.H+G,H/!L5&Rge`X9XI>RD8MHXHTaR]f^bogkpvew}l{‡c|ˆd}ˆYy…Uz‡Uz‡U}…[†\}…[}…[~‰ZŠ[€ŽW‚Z„’VŽS„T„T†ŽV…ŒU‚ƒZ{|SukZk`P]PWTFMD5NB3L;0Q:.P5,O5,O7-P8.R92N:3O:4N:4N>9N?:O?:OAS?=Q=:O<9N97L74I52G 1 1 10 / / 0 000/- . .,-+*****+++ * +* +* +, +, +, +, +- + ) ) (('(%&%%###!!!""""""""!!""!!     !! !!!!""####!!!!""# # $$$$$$$$$$%%$$$$$$$$&&&&%%$$$$####$$####%#$%$#$$$$%%####!!!!        + +     %+-12$9*"?2'F6,K;0P=3R>5VA8Y=7]<6[95Z52W0/S0/S.-R-,Q+%D7/0/+)*+*(()+1@eikhlmhiXdeUjjUop[rpatsckeb1+'&$/$6#5,-DKMcGLf8=W:A[6=W7=S9@U:BS@HY?JZ:DT*9I,;K*:N6FZ3E`N`{IaD\zLXqQ]wceyln‚jiygfvaeueixehzaevah{_fyMWnOYqOZvQ]xV[zTZyQ[|S\}T\QZ}EQx?Kr?Eo:@k37a*-X&&T#$Q&"P$ N"H"H$%L%&M""KE?<;977677:9:<:::::=??A "D A???@ C""D(%C)&D)&D)&D-*F0.I2.H1-G,/A-0B)/>&-<,,>)*<'(8%&6"%2!#1!#2 !1/ 0 #."%0!#1"%2"%0#'1#'1"%0"%0"%0%'0%'0$&/#%-"#. ,*)**)'((('$##&*$!/+'10-70-7/,63-21+0.*/.*/(",& *"+%"#""$$!!""""""$$'(,,0033441144333566 4 42 4 2 2 1000..----,,//000011$4$4&8&8"8"8#:$;&<'='='=)?)?(>(>*7*7+5+5,3+2,0-2122!35#6?-@QC>_QLhdBsnMxE~‡J„P„P€‰N|„Jy{TvxQwtTroPnkEliCmrArwGqAuƒF~‡J‹NƒŽJ„K„JƒI‚‹J‡FƒG|Ex€?t}v|=z}Ez}EvuNssLogTibNbTTZLLP>LH6D;*A6%<7">6!=4 @4 @4 @4 @3!B3!B3!B3!B5 C5 C4E4E0G0G-G-G+G-J,H.K7*J>1QF?ELEKWTB^[Idc>dc>hf>hf>iaC`X9[MBSE9J:CF5>J:FSDOYQUd\_gm\ntct€\y…a}ˆY}ˆYz‡Ux„RzW{ƒX|„Z|„Zy…Uy…U~ŒU€ŽW‚TŽS‹Q‚R„‹S…ŒU……\‚ƒZtdukZi[b_QXPAZC4M;0Q9-O8.R8.R9/S:0T:3O;4P>7Q?8R@;PAS?=Q?=Q=:O:8M74I52G63H----.....--- , ,!.+****) *))+ ( + + + + + + + ) ''%%&%$ %##""!#!!"!!!!!!!     !!!!!!  !!!!!!""""######$$$$$$&&$$##$$%%%%$$##""!!""""""""!!!$"!"!"!"!###"%%"###  ""           %&.-4 7%@)"C3*K8/P;2T<3U=5Y>6Z>8^=7]:6[95Z21V21V10T,+P81-.*'*,&)%'_b`utdkiZkgVkgVpk`pk`pmpTQT'%87 @03RGHfNOm;>W:=V59T6:U7?W6>V,5H4=P>IY6@Q-9L&2E.=S@Of=Vr6Ok3Mm7PpHVqTb}giklƒcdvbcu^bxX\r\d|_g~Vf|Q`vGVoJXqP[wS^zRWvMSrNUvS[|V[RX~EPzCNw>It:Dp1;i)3a*+]$%W$"P" N!!J##L()M01V*,N!C>98655337899<<::889::?? @ !A !A=<<?"#@#$B#$B%'D))E++F-+D-+D.0F.0F,1E,1E-/C-/C+.?'*<&);$'9$&:#%9 #5 #5 $3 $3#&3#&3#'1#'1''3%&1#$/$%0!!.!!.!!.!!.!!.,+,,+*)'&&&%$#$%*'$--*30-70-76/56/53-22,1-(+*$(&'"#!!    !!"#"$()+---.011113322233322 42 2 2111100....--........1133 6 6"7"7%:&;%:%:':':':':(7(7*7*7&4$2*4*4*4*4,61!;8+=D8JQGI`VXjgOtpX|ƒK„‹S†Q‹L~…NyIxyMstItoNqlKleKleKqoMtrPx}M|PˆL‹N}FzŒB{ˆ={ˆ=y@v~=uwBtvAtrBqo@qp>qp>vxncEg\>[JHQ?>C-?=(98%<7$;2"=2"=1 <1 <0!?0!?1"@1"@1A1A/A/A+C+C+G+G+F+F+F,G/%D6,K?7HG?QQKJXRQ]ZF_]HdeAfgBgc@d`<`Q?YJ8P?BI8;A1?L8^=7];8]:6[43X43X43X%$I22..+)'''(%MPMlomnm]ml\pm\ur`wrgvqfKHJ"")%3(#264 @/2Q9:X>?\?BZAD]:?ZAFa?G_?G_;DW7AT6@Q.8I*6H/;N+;QCRhIt;Eq4=l.7f,-_&'Y'%S'%S((Q'&O'(L./S(*L!C@<655533578<<:<:88888:= @""B""B?=<< = "?"#@$%C''B))E,*C.,E.0F.0F-2F-2F/2F/2F.1C.1C,/A+.?),@(+?'*<'*<&*9&*9(*8(*8'*5%(3''3%&1"#.!!-,,,,*,+,,+*)'&&&%$$#$%!(&#,)&0-*34.46/53-21+00*-,'*)"*$%"!!    !!!"#%&'(+--.011113322233311 4 4 2 2111100....00........1133 6 6"7"7$8%:%:%:':':&8&8&6&6)6)6$2$2(2(2(2(2*4,6- 24'9D:oq=om=mk;gf4hg5jl1mo3pt3tw7vy;wzncEg\>ZIGP>KQFSZTWc]ajlYsucwV{…[}‰Wz‡UzƒTxRu{Vv|Wv{Xv{Xx}Xx}Xy…T‹Y‚ŽVU‚ŒS‚ŒS…‹X†ŒY‡ˆ]„…Y}zaxu]o`jfW`WJcK>V>5R=3P91P>5T?7T@9UA:UA:UA5VC:\E:`F;aA8`?6^<:\;8[=;]<:\;8[;8[12P7.1,*)))*'%@FJrughj\ffRmmYxpfyqgmdj<39 88:<#@.*J>6SJC_TLiXQmKLeIJcIMcJNdDL]:BS2>P*6H&=!-D&:WEYu@Zx5Pn2OlD`~Xj‡Te‚am‚am‚fkbfzah{`gzYcuYcuTdxUeyQ`vM\sJVtGTrMUxJSvIQwHPuINvHLuBBp?@mBAq??o;,-=(*9'&4%%3##1 -)()),+,.**)''&&&%$##$#$*$ ,'#/+&0.(24+14+1-*&-*&*%&&"""#!!!!$$$$#$'(,,--001100//2233111111224422..........//....0011 2 2"4"4#6#6#6#6&7&7$5$5"2"2$4$4"2"2!1!1&2&2&2&2)6*71%;:.EH?ETKQb]JmhTv}E}„L„ŒK„ŒK‚‰M{‚FvwGttDtoNokIjcGhbFfdBpmLuyKw{MwƒFwƒFw„DuBs|Fmv@klAjk?jbFg^BdWBaS?`S<`S6"<2!?2!?/ >/ >,"?,"?-#@-#@+A+A, D, D+F+F*G*G(G(G( E( E+%F/(I72G>9NKDJRKQ\SL`WOd^HhaLibAha@g[A`T:VI@OB9H:CG9BG;JM@PSIX]Tcfc_mjfsx\y~b{†Y}‡ZˆW|†UyYu{VpuYqvZrscstdty]w|`‡Z‚Š^ŒV€‹U‚‹U‚‹U„ŒYƒ‹X†W~‚T{x]rpUh`]_WTTI[K@RD9RC8Q@9S?8R>:S?;U>;R<8O@;P>9NA9Q@7O>6N;3K7/G4,D2)B/'?++++))))*))*)(((('%%& & $ $ $ # +# +# +% +% +% +% +$$$ $ # +# +" " ""!"    !! !!!!!!!!!"! !#$##!#!!"#$#""####!!!!##""   !!!        ! !#""####!!""!!!!!! !!                 +  #- 07!";)#B.(F2)J8/P?6WF=^J@fI?eE<^=;]96Y#$B3..-,')''' "BGKnswil^dgYnnZnnZumcmeZ-$*$!1%"286 ?;&"B73T@9UG?\PIeRKhIJcIJcGKaHLbEM^:BS,8J!-@6)@$8U@Tq,Fd0Jh5Qo;XvQc€Pbam‚ep…fkbfzcj~em€_h{ZdwScwQauQ`vM\sKXuHUsGPsHQtHPuBJp@Dm?Ck::h89f<>n88l56j47m,.e&(X&(X(,W#&Q"H!GB D D?7 211222267;<<<:9662234567:87;; ?#!A'$B'$B(%C*'E*'E,(F-)G/,J10I10I10I10I02H02H23J23J44H32G32G32G01C./A,-=(*9))6'&4$$2""/ , ,*),+++)))''&&&$$##$#"$#'$)'!+-$*1(.-*&-*&+&'(#$#$ !!!!$$%%#$'(,,--..0000//2233111111224421..........//////0011 2"4"4"4#6#6#6#6&7&7&7$5%5%5%5%5$4"2!1!1&2&2(4(4)6*7+5+58/5C:@TOecAim?ptFr}@uCx…Ez†Gw€Ks|FpqEklAjbFe\@bT@]P;ZM6XK4[Q3[Q3`Y1e^7g]=d[:fT@`N:WA@N77;':9%88$>6"<2!?2!?0!?0!?-#@-#@.$A.$A-"C-"C-!E-!E. I. I-"J-"J+"J+"J,$J,$J,&G.'H72G<7MHBGOIN[RK_VNc]Gd^HibAjcBh\CbV=YMCRF:S>:S=9Q=9Q>9N;6L=5M<4L;3K6.F4,D2)B-%=,$<****(')())**(((('&&&'$ # +# +$ ! ! ! ! ! " " # +# +! !       !!!!!!!!!"!!!!!!!!!!"!""!!!!""!!          !!!!!!!!!!""""!!"""!!!"""""  +                  !%0!3$ :)%>0(E3,H7-L;0PA5WG;]J=dK>eE:[0,M3.++*.+*$#IQMt|kfn]fhVhiWll_eeXedf;:<"/:9866 8/%DA6UK?\NB_UIfVJhRLfXRlYZsNNgAI_8AV-;O#0E!7.D";W7Pl1Po*Ih.Km6TuJ_~Lb€Xg}\k‚blenis†hr…blYcuJZnJZnO]xM[vGSsAMn>KpDQuIPz?Eo<>g:9i<7j<7j87k42g*-a(+^%+W!&S !LIF!!J!!HE9 3./123366:=::9:9920//-00133469!!= "?"#@"#@$%C%'D%'D')F)*G,*J-+K.,M.,M30L.+G/,H0.I3/I3/I62K62K32H.-D**?**?*)9('7('7%%5#"2"!1! 0 / /,+*'')'&&%%$#"""!"" "% (" '&#++&','(-*&($ $""!"!%%&&$%'(++++----....000010111111 0 0/....-......... /....00. /"0"0"2#3#3#3&4&4%3%3&6&6%5%5$4"2%5%5&4&4'5'5)7* 8'6(7+!91'?=3BG=MYTGa\PnpGtvM|„Jƒ‹Q„‹L}„E}‚Fv{?qnFnlCg_Id[FbZIf^NhiKmnPsyLtzMv€HuGu{HouBkjDcb<_VA\S>XHHP@@N:DN:DQ:IS+B2$@2$@2%G1#F."F."F+!E+!E,"F,"F,"F,"F,!I,!I, K, K."M1$O-%O-%O.%M.%M.%K.%K/'K.&I4-I92NC;LI@RTJN[QU`[G`[Gdc>feAca=^]9\P@SF6N@BL=@J=HMALQHPVLU\TWd\_miZtpaxzZ|~^ˆY€‰ZˆY|…VvVpyQru^qt]ssftugvxcwydz‚]€ˆc‚Y‚Y‚ŽVƒW†‘VŠ•ZŒ”Z†ŽTƒ‡Y}Sxt^plVf]\YPOLCPF=JC;LB9K>9N:5J:5J94I<4L90H6.F2)B0(@-%=+";'7(((())(%''''&&&&&&$ $ '& & & !  + + +! ! ! ! " "  + + !!!   !"!!!  !!      !!   !!      !!""""!!"""!!!""""# # ""               #&0!3$ :)%>4-I92N<1Q=3RA5WB6XC6]E8_B9bE<^><^A:]?8[40P!=,,**+.-(#"GOLnvrgp^\dShiWjlYkk^kk^GFH/" 386644 85+JG<[THeL@]RFcSGdRLfYSmSTmQRkIQgAI_/%6L-Fb@i68a84b72a94e94e93f:4g53h53h,/c*-a&,X!&S !L KG HFE9 3./1233668;889:9961--,*+//035:#"> "?"#@$%C%'D$%C$%C')F)*G+)I+)I,*J,*J.+G,)E*(D*(D.)C1-G1-G1-G-,C)(?**?**?,,<,,<*)9)(8$#3#"2"!1! 0! 0.+*''')&&%%##""!!"" !# %&""*%&*'#($ $""#!!! ""%%&&$%'(++++----....001110111111 0 0/....-----..... /....00. /"0"0"2"2#3#3%3%3%3%3&6&6&6%5%5$4&6&6(6(6)7)7)7)7)9*:)7* 80&59/?HC6SNB_a8hjAr{@y‚G‰J…ŒM€„H{CtrInlCh`Je\G]UDaYHbcFefHkpDrwKt~EuGw}Jw}JrqKllEf]H_VA\LLVFFO+B>+B4'B6(C5'I4&H0$H0$H.%H.%H0&I0&I0&I0&I0$M0$M0#N0#N1$O2&Q.&Q.&Q0'O0'O0(M0(M1*M0(L2+G80M>6GDa_;^]9_RBXK;OACPBDK>IK>INDLTJRXPT^VZieVmiZrsSxzZzƒTˆY€‰ZˆYzƒ[vVru^ps\tugvvhvxcvxcx€Z~†`ƒŽZ‚Y‚ŽVƒWŠ•Z‹–[˜]•[ŠŽ`ˆ^~g{w`sjif]\TKXIAMD.(B7.K=3P>5T<4S>4W;1U>1XB5[B7_F:cF:cE9b=6^6/W%$B51/---., $381QI9\M=_P@cRAdPEeVKjQOhTSlKTi=F[3?Q*6H"1A/>M4I\:Ob8Oi/F`,Fd5Pn.Fd0HgIXu[kˆem„fn…ju…p{‹mzŠgt„cqƒaoXiPayFUuBPpBJp?GlBFiADh@>e75\;2_<4`;2a90_70a5._6/`;4e31a0-^**Z''W!!QNJIHG< 522233313457779::63/,* &'*+/46! >$#@#$B#$B"#@"#@#%@%'B**J))I'*I'*I(*L')K&&H%%G##E""D'"E)$G(#F(#F'#C'#C*(D,)E+,C+,C.*B)&=(%:$"7#!4!20/*'''''&&%%$###   ""#! #""$((!'' (&&%"!  ""%%&&''((++,,--..0000//000-...///00/...--++,,,,-.,,,+,,,, . . . ."0"0#2#2#2#2$ 3$ 3$4% 5% 5% 5"4"4(!7(!7(!7(!7)"8)"8+";+";)!9( 8)!9)!9,$<-%=7-<@6FKFBUOKfbJmiQwzH€ƒQ€ŒH}ˆDz€?tz9nlCjg?cWI^SEYPH]UMd_NjeSmnPrrUtyOtyOt|IpyEmpDcg;aYCZRG6G@/@;(D8&B5)F7+H5)M6*N3)M3)M0(L1*M1*M2+N2+N2+N/'L/'L0'O1(P0)S0)S0(U0(U3)V3)V2(S2(S2)Q2)Q1*M1*M3*K2)J1)F3,H<2HC9OIALNFPUU@WWC]^:_`;b`<^]9`U@ZO:QEBPCAK=DL>EPEKTIPXNP]STd\Wib]nlXus_x|[|€`|†b}‡c~†`z‚]vz`ty_tuetuevujwvkwxfz|i|†b‚‹h†“_†“_‹–[Œ—\˜]š`‘›[Ž˜YŠ‘Y„‹S€{ZuqOf[XUKHH:HF9F?3I:.E4-B4-B2*@/(>,$:)"8) ;(:(:%6'''()'&&&&( ( %%$ $ $ $ $ ! ! ! ! !    + + + +     + +            !!!!""!!!!###"!!##""!!" "            &* 2%#8.(B2+E:0M@6S@8W@8W?5Y<3V=0W@3YA6^F:cG;dF:cB;c93[-,J =00...-((!$jptu~kenZ_eKcjPgl^hm_]`]),)#.99::41/2- ?M9Na+(?)&='$9#!5" 3 00/+*****''&&%$$$#   "" ""$'' ((!(&(&'&"###"""%%&&''((+++,--..0000//.......//////...--++,,,,-.,,.. . . . . . . . ."0"0#2#2$ 3$ 3%!4%!4$4% 5&!7&!7$"7$"7(!7)"8)"8)"8+#9+#9-%=-%=-%=.&>-%=-%=-%=-%=2(86,;=84HB?UR:_[Cmq>rvCy…A~ŠF‚ˆGz€?trIljAeZLdYJXOGVNF[VDc^LefHijMrwMtyOt|Is{HquIkoCh`JaYC\OMUHFO?OK:JC0L>,H9-K9-K7+O9,P7-P7-P3,O3,O3,O5-P5-P5-P3+Q3+Q2)Q3*S3+U3+U3*W3*W3)V3)V2(S2(S0'O0'O1*M1*M1(I0'H0(E1)F8/E<2HD,$:)"8(!7(:'9$5"4%%$$%%%%####$$##" " " ! ! ! ! !   +          !!""""####$$%$"#$$# # " " $ $ !         '+ 4%"90(E4-I9.M=3RC5WD6XB3W@2V=0W>1XE9]H<`I=aH<`B<]D=^77T%$B--./+)""Y\S|vluZ`iO`kSepWkrjbia15> 1!587631003/"B;.MB2UG7YP@cP@cMEcQIhVQm\WsNUj=CY/9J*5E$1?6DREYhMapI^xNc}Mg…WqPjˆ;Vs:Nk3GdFWpL]vQ[sYd{am‚ht‰ht‹cp‡ZfVb{MYyCOo?Fn;Bj<=h;''B&&A#">!!=%%@'&D'&D+)I+)I**J))I&&H&&H%%G##E C C DC D D C C""B !A ;"$?$$?$$?!763222/.+/..+**+''''$"      #$#!)(&****+*!*( $#!!  ''&&&'((**+,----....////....////-.//.---++++,,--,, /. . . . ."/"/ . ."0"0#2#2%!4%!4&"5%!4%"2$!1%"2'$5$#5$#5)#6+$7+#9+#9,$:,$:-&:-&:.(;.(;.(;.(;.(;.(;-&;-&;/)<5/BA8EKBNXRHa\QmoFvxO}‰C|ˆBxƒ:s~6ko4L:/N;0P7/S81T50S4/R7/S7/S6.R6.R3-R3-R3,T3,T4,V4,V4+X4+X3(W3(W2(U0%R/&N.%M.&I.&I0'H-$F-&B.'D0+@50F?9EF@LPN@RPCZ[=]]@`a<`a0R;,P9+O9,R=0WD8\E9]G:^G:^A;\=6W32P! >//,.)+LMVw{qnqhktYfoTku]pzbkrjNUM %2 #7766321/00#C9,LH8[M=_P@cP@cNFeOGfMHdPLhNUj?E[-7H-7H9FTJXeOcrSgvNc}K_zKf„Hb€1Ki4OlDXt@Tq?Pi@k:;f73_1-Y/*V3-Y7/\91^60^5/]60^71_1/]20^72b83c43e0/a..^((X#$Q!"OE>77;77<??<83257663342320--+.39$$?!!= ;!!=#">$#@&%C&$D)(H&'G%&F$$F$$F##E""DA?AABB@@?=::::652200..,....+,,''''%#"""   !$#!(&$))))+*!*( '%#$#!$ ! &&&&&'((**+,----....//////..00//-...----++++,,--,, /. . ."/"/"/"/"/"/#2#2$ 3$ 3%!4&"5'#6&"5%"2$#5%$6)#6+$7+#9,$:,$:,$:-&:-&:.(;/)/)<.(;6-:=4AJD:SNCad:ilBwƒ=|ˆBŠAz…=wzHor?gaPa[JaVT_TQ[UM]VNddHhhMktEmvGrzFpyEtuJrsHojOfbG_XR[TORIUNERE;SB7O=3R<1Q92U:3V72U50S7/S7/S6.R6.R3-R3-R3,T3,T4,V4,V4+X4+X5)X3(W2(U0%R/&N.%M,%H,%H-$F-$F-&B-&B.)?3.C?9ECRPCXY;Z[=ab=`aZOFWMCUHMVJNWJUYLW[QU_TXaXYe[]g`Zg`Zlj[rpaqucuyf{c}‚dˆgˆgƒi}gwzjuxhxzn{|q…m…m€d„’iŠšc‰™bŠ™Yˆ—WŠ›VŠ›V‰—Rˆ–Q†‹S~ƒKwmVg]ES?EE174&;3%:-#9+"8' 5' 5%3$2"1!0- .$$##!!######$#"""    + + + + + +             ##!!!!###"# $& & # ""# # # # !!  +     +   +-3"7' =*"?-"B0&E5'I6(J4&H7)K9*Q<,SD4[E6]E8_C6]A6]<2X-+K=3/-.%"MQGz}sru`dgRdcQkjXwulrqhWY['07666554413$7:0MF8[I;]P@cRAdMBdK@bF@_LFeQOhPNg7@U.7L6DVM[mWfx\j|Tj€H_tOlŠFc€9Uu5PpH_D[|DVwDVw0?]7 43333565688660033665577< ="#@"#@!%B $@!C A A>>>;<====9864442222//..//00 "/ "/ -,(&&&)('%$$!#  #% !*%&.*#-(",, ,, ,+")'&$""! #$%%)***,,--....-./////0//2222110-..//,++-****) *",$-"-# .$!/$!/&!/&!/'"1'"1'"1'"1'"1'"1'"1(#2(#2(#2(#3(#3)$5)$5)$5+%6+%6,&7*&9*&9*&9*&9*&9*&9,(;,(;+&;+&;-(>-(>,&>,&>,&>+%=+%=+%=80A@8JQKA^XNhp=owD{Š:?9{‡3t{?ou9gdKdaIeWPcUN_UKdZPicKoiQqpJrqKvuNwvOqtJknEgeHbaC\SMZPKNERH?LA9QA9Q>6S<5R;1U;1U5/T5/T4.S3-R2*P2*P3)V3)V4*[4*[3)X3)X0)Z/(Y0)S.&Q-'L,&K,&G+%F,$C+#B,%A,%A-(?1+C73A<7FICAOHGWT@ZXD_^>a`@a`@_^>]TF[RD]PN]PN[OZ]P[]S\^T]_V\bY_g`ag`akdelefshlwmqvtkxvn}~lƒp‡j~…i{€iw}ex|i{l{‚m~†pŒj„n‰˜cŠ™e‡—Z†–X‰•V‰•Vˆ–P…”M†O~…GysIhb7RA=B1-7#47#4.!5,3$2"1!, +,,,,####!!######$#"""    + + + + + +            ##!!!!####$$& & # ""# # # # !!   +    +  (,3!6%:&;(=)>/!C2%G4&H7)K<,S>/V@1XD4[D7^D7^A6]<2X20P$"B3/..#<;I}€wnqhacN_bMjiWvvdtsjfe\+,.- 264665544144*G@6S>0RB4VO?bSCeRGhLAcE?^GA`NMfNMf>G\5>S;J\M[mVdw^m@VlTj€TqŽ=Yw@\|LhˆH_@WwBUvEWx6Ec4C`FRkO[tV`xXbzR`yM[tIRuJSvOS|JOwDHs;?i12_,,Z)+V(*U/*V2,X3-Y3-Y2.\2.\5/]60^6/`6/`20`1._..^,+[))Y&&U!KB;=ACFDGFB@8633224446: <::66443344448:<<!>;>>>=>>;<====9865442222///.//00 "/ "/ -,*(**)('%$$"#  !#% !-("1,&/.#/.#-,#*( '%##" " #$%%))**,,--.....//////022 4 4333320////-,,-++++) *",$-"-# .$!/$!/&!/&!/'"1'"1'"1'"1'"1'"1'"1'"1'"1'"1'!2'!2(#3)$5(#3)$5+%6,&7*&9*&9)$7)$7)$7)$7+':,(;+&;+&;-(>-(>,&>,&>,&>+%=*$<*$<.&75->C=3PJ@[d0fn;q1{Š:‚Ž:„‘=€‡J}ƒGroWolSm_Yk]Ve[QcYOb\De`HmmFqpJvuNttMpsIpsInlOjiKd[V_UPULYSJWJBZD6S<3V;1U60V5/T4.S3-R5,R5,R6+X6+X4*[4*[4+Z4+Z0)Z/(Y0)S.&Q-'L,&K,&G+%F+#B+#B,%A,%A-(?1+C94B=8GICAOHGSQ=XVA\\<`_?a`@a`@cZLaXIcVTcVT^Q\_R]^T]bX`d[af]cf_`habjbdjbdndhrgkqogtsjxzg}~l~…i‡j}ƒkyg{l{l|ƒnˆr‚l„n‡—b‰˜cˆ™[…•W…’R„Q„“L‚‘J‰J}„EwqFlf3UA5WC8XG<[C;Z>5T00M! >.-&"*.)swr|~eoqXmqYlpXotfpughhh... + 12446622001!8>5R;1N>0KF8SQ@^TCaO@`N?_I>^MCbHCaB=[GMeHNfIQgOWmYdyITi,?WUh€Ni„VqKg†EaE]€AZ|8Su2Mp6Mn3Jk2Fc:NkHXsIYtN\wHVqJOpKPqKKtKKtLKvA@l67d01^*,W(*U+(S,)U-*V1-Y41]41]5/\4.Z3,[5/]1/]/-[.,Z,+X+)W'%S!JD>@F#K!KE>CBA;:65323258<?# C# C! @! @:6636644888788<<<<<=;< = =!;!;!7 6 6 62222 2 2 2 2//,,!0!0!.!.!. - *)**)('%$"##""   $&""+&'-)(/+)1+'-($*&$*&$$""!!  %%***+----0000////////22 !3 !3 !3 !3#!5!3!3 2--....--,,++**,,"-"-$!/$!/&!/&!/&!/&!/& 1& 1'!2'!2& 1& 1& 1& 1%0& 1'!2'!2(#3)$5)$5)$5)$7)$7'#6'#6'#6'#6)$7*&9*%:*%:,'<,'<,&>,&>,&>,&>*'>*'>*$<)#;1):<4EIFBURNbi?nvL}Œ>ƒ‘D…™Dƒ—A‘A|‹;v{Bqw=ljHifEdWNaUKeZSj_WmgQqkUpqLpqLruLpsIkjMgeH`YS]UPWMWQGRG5T<4S71W60V5-X1*T0'V0'V4)]6+_3*`4+a5,b3*`2)]0(\1)V0(U,%M,%M.&I,%H,$C-%D.'D.'D.)@1+C61@;6DC>EJELTPFXUJ`]Db_FbdDbdDhbJhbJh]Sh]SfXZfXZfX_i[bi^dl`glaelael`hh\eaYjd\ndapgesplqtpux{m}q„o„o}‚m}‚m|ƒo~…rƒŠv…Œy‚n‚n…”b‚^€Y~‹W~‰N|‡L†HyBuvDhi7_O=M<*5$)1$+.),!,"- *!+**))!!!!!!#####"""  ""     + +        !##""""###"# # $ +$ +# " +$ $ $ $ " +! " " " ! !       +')24!8"9&=) ?.#D3'I:.P;0Q<1R<1R>3UC8YE;ZI>^C;Z>5T00M<--((img{zoqXoqXtx`vybmrckoa999 &114466221004$7/%BB5PIY{;Rs9Pp9Mj?SoFVqHXsJWsERnJOpHLnDDmGGpGFrA@l;;i78e/0\(*U)%Q*&R+(S,)U1-Y1-Y2,X0+W0*X5/]20^.,Z+)W*(V,+X.,Z(-U$L@>CGIC>@BA?=:833357<9= @# C&$D&$D ;86333447776769::9=<;< = =!;!;"8"8!7!75533!3!3!3!3///!0!0!0!.!.!. - *()))('%%$##""  !$ )$%+'&-)(/*&-($+'&*&$&#$#!! % " ##***+----0000..//////222222!3!3"4!3" 3" 3 0...//,,++++++ ,"-# .# .% .% .% .% .& 1& 1& 1& 1%0%0& 1& 1%0& 1'!2'!2'!2(#3)$5)$5'#6'#6)$7)$7'#6'#6'#6'#6*%:*%:+&;+&;+%=+%=+%=+%=)&=)&='"9'"9+#50(9;84IFBX`5dkAr4Ž@…™Dˆ›Fˆ˜H…•E‚ˆN{GsqOolKj]ScVM^SLcXPe_IlfPnoJnoJqtJruLqpRnlOg`Z`YS\Q\WMWMB]G5T94Y83X70Z4,V4+Z6-\8.a9/c6-c7.d7.d5,b3+^2)]1)V1)V0*Q.'O/'K.&I-%D.&E.'D.'D0*B2,D94B<7FC>EJELTPFXUJ`]Db_FdeEdeEicKicKj`Vj`ViZ]iZ]hZaj]ck_fmbhmbfmbfl`hh\e_Wh_Wh]Zi_]kjfkplquwi{}o€…p„o„o}‚m{n|ƒo€†s‚ˆum€Žl„“`€]}ŠUz‡S|‡Lz„J}„ExAtuCjk8eTCSB0:(-/#),),!,!,) *++**  """"$$#"     !         + +   !""" " ####$$& & & & $ $ % % & & # # ! $ $ !  "      +%(./68&=-%D1)G5-L:0M>5RC9VG=ZH>[H>[IBWIBW?\TA_O=^N<\J8[G4WB9[B9[LGcZUqWZsILe;E^FOiL]vQbzI`zBYsBYzKbƒF^ƒ7Nt1Kr?X€9U|7Sz9Tw9Tw:Qr=TuATs>QpAMo@Kn;Ci9Af==f@@i>@k:;f53a.,Z)+V'(S'&Q%$P,#P.&R-$Q-$Q.&R0(U.&Q.&Q*&R*&R,)U2/Z89_,-T?76:><@@EEB@?=:7;>>>>AA!E&"I&"I @<7611545555567799888: :9!9":!9":$8$8$5$5!7!7!4!4!0!0//..!.!.$,$,#+"* *((')''%$##""# #!%!"&"')$%*%&*%&*%&&#$&#$! $" $$((*++,,,,---,,,-///. 2 222002 !322 !3 !322..,,..++++,,,,+ . . . ."/#0#0"/"/$0$0%2%2%2%2&3&3#!4#!4#!4#!4&"5&"5'#6'#6&!7&!7&!7&!7'#8*%:*$<*$<*$<*$<+%=+%=)&;(%:'#;%"9' =' =/$=9/GGGATTMfr:q}D~@…—G‰¢E‡ŸB‡šA€“:~‡Cs|9qiQf^F^PK\MIaUJfZNheLmiQqqOqqOruLqtJnkPigLf\WaWRWK[RFVG<[B7W>6[=4Z91^91^40^51_50e73h91i91i92h5/d5-`4,_3,[3,[/*T-(R0(L/'K.&I.&I0*I0*I2.H2.H72E;7JC=GICMPNHUSLZ]F^bJefFfhGjjJjjJifMifMh`ViaWm`dk_cm`dnaen`gn`gj_jf\gaVh]SdWMjXNk]Zqdaxinmotsw{h|€m€ƒl€ƒl}n}n~„l‚‡p€ŠkƒŽn‚d|‡_{†Yy„WxƒMt€Is‚Bq@qz>js7e^=WP/D5,3#(&(&"'#)!*!*"*!)&&    """"$$#"""             + +  !   " " ####$$& & & & % % % % & & ' ' &&" +" +""    +  %(./47$;*"@3+J80N>5RB8UG=ZJ@]LC_LC_OG]LE[?U{9Sz:T{;W}C_…Gb…@[~@k79d1/].,Z*,W'(S'&Q"!L'K'K( L( L)!N( L)"L)"L%"N%"N'$P0,X>?e66]?758;>DDFDA?>==>AB!A!A#DABB C C=965331444666678::8:8:9866!9":$8$8$5$5"8"8"5"5"1"1!0!0.. - -#+#+"*!)!,))()'('&%####   ! !% !&""(#$)$%(&&'$%%#($"' !&#&$((+**+++),--////00/.0000//-.0000//..,...+++++++++, . ., ."/"/"/"/$0$0%2%2%2%2%2%2" 3" 3!2!2#2#2%!4&"5% 5% 5% 5% 5% 5&!7&!8&!8&!8&!8'"9)#;'$9%#8%"9%"9' =' =.#<1'?993GGA[g.fr:w‰9~@‚›>‚›>ˆ›Bˆ›B„I}†BtlTjbJ_QLXJEYMB`TIb_FheLmmKppNruLruLurWomRkb]h^Y^RbWK[LBaG<[A9_@8^=5a=5a95c:6d95j95j<5m<5m;5j81g7/c6.a4-\3,[/*T.)S0(L/'K/'K/'K0*I0*I2.H2.H95H>9LC=GICMPNHUSLY\E]`IdeEdeEjjJkkKkhPmiQiaWkcYk_cm`dm`dnaen`gn`gj_jf\g]Sd]SdVLiSJfSOgZVn`ddgkkqucuyf~j‚k~‚o~‚o|j}ƒk€Šk€Šk€Œc{†]z…Xv€SuKr}Gs‚Bq@r{?mv9jcB_X7K<36')''%"'!& ) )!) '%% ##"!$$#"""""           # # ""# # % % & & & & % % % % & & ' ' &&# # # #    + +  %&,-04"7)!>4+E90JA5LG;QRFZTI]SG[SG[QJ_KDZ?3UF@_WRpCFa47R0>W9Ha3Db6HeCXwI^}@\|EaDb‚?^}8Tt9Uu4Pu;W|Daƒ=[|5Oo3Mm;OpPr;No9Di6@eACkCEnAAj??h=7d3-Y.-X*)U!%P $O KIHH GF F" G$!K$!K&$M.,U13U+.O"%D635:<EEFFBB?C D!E !E D$!D%"E$ E BA@??;:9833333588889;:;;;7755665!6#7#7#1$2$1 %2 %2 %2 $3"1"1!0/++ -!)!)"* ( *()(%%)'&&%#""   ""!%"%"%&"''#(+%*+%*&$)$"'!(%)())++++++,-// 2 20 2/,./////////0000......+*++****++,, . . . ."/"/ 0 0 0 0!2!2 0 0 0 0 0 0"0"0#2#2$4% 5% 5% 5$4$4$6$6$6$6%7&!8$"7$"7%"9%"9$ :%!;'">)$@,&>71HJK@VXMfu—A‚œ;ƒ<„˜:”5|ƒ@qw4aZ@XQ7XJEYKFYNK`URicTngXomRrpUtuNrtMonNkkKg`ZaZTWN]PGVJ?_I>^B9bE7h@8i?7o?7o>7l>7l;5j81g90d6.a5/]4-\/*T/*T2+N1*M0)J/(I1,H1,H1/C1/C72G;6LB?GHELOKJTPN[\L]^N_cDdgIjnBmpDnpInpInjTokUnf\nf\oe`oe`nbbnbbg`ae^__Ve[Q`PFcLC_EAbKGgRSlYZsbclegprukvyp}‚m„o}ƒk~„l}†l{„i}‡c{„ayƒXt~Tx‚Pq{Iq~Co}Bqz>nw:lk7cc/SJ.A8,)%%$$##!#!#"" ##"!$$#"""""             ### # ""# # % % & & & & % % &&' ' ' ' &&# # # # !  + +  %&+,04 6' =.%@5,GE9OH\|B^~6Rr&Bg5Rv@^€9Wy3Mm.Hh6Kl7Lm;No;No=Gl7AfBDm?Bj::c::c;5a3-Y/.Y+*V$(R$(R"$O KII GEE" G$!K$!K)&O+(R)+M)+M'*I;338;ACFFECC!E $H $H"#H!"F# C!A B??<<<;:983333234699:;:;;;77555545!4!4#1%3$1 %2!&3!&3!%5#&6 $3#2!0/*)!)!)"* ()))(%%''))%$%# #  ! " #!$$ &($),&+,&+'%*'%*%"+$ * /+++,,,,---/ 2 2 2 20 2"1"1!0.////////0000.... / /+*++,,,, . .,, . . . ."/"/" 3" 3" 3" 3!2!2 0 0 0 0 0 0"0"0"0"0#3#3% 5% 5$4$4%7%7%7%7%7&!8$"7$"7%"9%"9'"<%!;'">)$@*$<.)@=?3JK@Yg.ds:l…/w9€š8€š8„˜:”5€†Cv}:jbH_X>WID\MIVLI\ROb\MicTomRvtYtuNuvOtsSqqQkc^g`Z\RbWN]RHgLBaG>fF=eB;lA9j@8p@8p>7l>7l92h70e7/c5-`4-\4-\2-V/*T3,O2+N2,M0)J2-I2-I52G52G94I;6L@=DHELOKJTPN[\L]^N`dFehJkoCorFqsLprKokUplVog]og]oe`oe`nbbmaag`ad]^]TcWN]LC_I?\B=^C?_DE^LMfZ[dabkkndosiv{f{€j~„l}ƒkzƒh{„i}‡c{„a}†\{…[yƒQr|JrDq~Cqz>ox;qq=hh3^U9KB&6)&-!$$##""!!"" \ No newline at end of file diff --git a/media/test/data/bali_640x360_YUY2.yuv b/media/test/data/bali_640x360_YUY2.yuv new file mode 100644 index 0000000000..ecf1a5def6 --- /dev/null +++ b/media/test/data/bali_640x360_YUY2.yuv @@ -0,0 +1 @@ +DEkH€KlLLmM€NnN€PoP€PoPQoSQnS~RoR~RoS~SoR~RrQ~StQ~QuP}OvN}MvL|JwK|JxG}GzF}CzA~Az>€>z:€;w:;u;8r88p88q89q99p89p:€:p9€8p8€:q9€7q67r75s55s44u2€1x1€1y0€0z.€.z--y+~+y)~'y)~)y((|))|(~(}(~(}'~'~(~((~(€'~(€)})€)})€*}*€+}+€,},€*}*€+}+€,},€,},,},,~,+~+}+~+~*~)~)~)})~)})~)})~)}))}((}''}&&}&&{&&{&&|&&|&~&|&~&|&~&|&~&|'~'|&~&|&~&|&~&|&~&|&~&|'~'}'~'}'~'~'~(~(~)~+~,,}-€/|02{2‚4z5ƒ5z7„7z7„8z8…9y9…9y9…9y9…9y9†:y:†9y9…9y9…:y:†8y8†7z6„6}7ƒ7€:‚>‚E|J…QsY†ajh‰ldqˆu^w‰x[xˆw]uˆq_mˆjah‰hdg‰hajˆl_nˆn]o†o[o…o^pƒocp‚oeqshtwhy|e~~`~~`|‚xbrƒlgdƒYlN‚As4€.z,|)€+{+„+z+…,z*…+z+„+z.„4z:B|O{Z}eqnxf~ƒ]†ƒ…Yƒƒ‚X}…w]m…gbX„Hj;‚/t)€%z$$~#~$%%'*{.‚1u78r>CpLSp^€emmti{€f€ƒ|az†uao‡ida‡YgS„OkR‚QnTƒTpQˆSqL„Du=„6y0‚'z%€#€$~$"~"€#~"€"~"€!~!€#~!€"~! ~#%~&(~+|07w=€DpKPmWƒ^jcƒgjl‚rjwxjyxjwujvvjuukuokk_nW€KqF~Aq?}?t@{@u@|@u@|@u?{?u?}?u=~=v:~9v9~9u7~5w5~4v3~1w0~0y/}0x3}9u<}CpNVf]€f[lqXtƒyT{„|Vz„z[v…r^m†dc]‡OhE‡9o2ƒ,y+)€(('~''}'&}((}(‚)})‚)|+ƒ*|-0{1€2{3€6{7~7{8~=|>|?|CxF{FvJ}NtU|\ob|kmp}tjx~zg{}gc€‚d‚€‚fƒ€ƒg€g€h€€f~€}h{€yjzvjt~tmx|{p|€r‚{ƒt†|ˆt‡~‡t…ƒt~~|tz~xsutrtyr€€„q†€…pƒ€ƒoƒ€q|xupftXƒJu=ƒ4u/ƒ.z-€,|0}38{=DxNzZwbvkyspxz|l€~‚h€b€bƒ€ƒd‚€‚c~a}{\yvZww]z€zd|~k„}ˆnŠ|‹t‹}‹tŠ~ŠsŠ~Šrˆ~‡r‡~‡sˆˆuˆ†w€‚yzp„d~XO‚HEƒA}>…<|;‚FÿIÿIÿLÿLÿLÿMÿNÿNÿPÿPÿPÿPÿQÿRÿQÿTÿSÿRÿRÿSÿSÿRÿRÿRÿRÿQÿQÿPÿOÿNÿMÿLÿJÿJÿHÿHÿHÿEÿAÿAÿ@ÿ=ÿ<ÿ;ÿ<ÿ<ÿ:ÿ8ÿ:ÿ:ÿ:ÿ:ÿ:ÿ8ÿ9ÿ9ÿ9ÿ9ÿ:ÿ:ÿ:ÿ9ÿ8ÿ8ÿ8ÿ8ÿ8ÿ7ÿ6ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ2ÿ1ÿ1ÿ1ÿ0ÿ0ÿ.ÿ.ÿ-ÿ-ÿ+ÿ+ÿ*ÿ(ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ'ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ)ÿ+ÿ,ÿ-ÿ/ÿ0ÿ1ÿ1ÿ2ÿ4ÿ5ÿ5ÿ7ÿ8ÿ8ÿ8ÿ8ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ:ÿ:ÿ9ÿ9ÿ9ÿ9ÿ:ÿ:ÿ9ÿ9ÿ9ÿ8ÿ7ÿ8ÿ;ÿ=ÿDÿKÿQÿYÿ_ÿgÿoÿsÿyÿzÿ|ÿ|ÿ|ÿzÿtÿqÿlÿjÿkÿkÿlÿmÿoÿoÿpÿpÿoÿoÿmÿlÿkÿjÿjÿjÿkÿkÿoÿrÿwÿzÿ~ÿÿ€ÿ~ÿ~ÿ~ÿwÿsÿkÿ`ÿUÿHÿ<ÿ2ÿ+ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ+ÿ,ÿ1ÿ7ÿ?ÿJÿVÿaÿmÿwÿ}ÿÿ…ÿ†ÿ…ÿ„ÿÿ{ÿtÿkÿ^ÿPÿ?ÿ3ÿ*ÿ&ÿ$ÿ$ÿ$ÿ%ÿ&ÿ(ÿ,ÿ/ÿ2ÿ5ÿ8ÿ=ÿAÿEÿJÿRÿZÿcÿjÿrÿ|ÿ€ÿÿ€ÿÿzÿuÿnÿdÿZÿSÿOÿMÿTÿbÿpÿfÿVÿNÿEÿ=ÿ6ÿ)ÿ#ÿ$ÿ#ÿ#ÿ#ÿ"ÿ"ÿ#ÿ"ÿ"ÿ"ÿ!ÿ!ÿ#ÿ"ÿ"ÿ!ÿ!ÿ"ÿ$ÿ&ÿ+ÿ-ÿ4ÿ=ÿCÿJÿPÿVÿ\ÿbÿeÿlÿqÿuÿyÿzÿ{ÿzÿwÿuÿvÿvÿuÿuÿuÿsÿkÿaÿVÿKÿEÿBÿ?ÿ?ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ?ÿ?ÿ?ÿ?ÿ>ÿ>ÿ<ÿ:ÿ:ÿ:ÿ7ÿ5ÿ5ÿ4ÿ3ÿ1ÿ0ÿ0ÿ0ÿ1ÿ1ÿ6ÿ<ÿCÿMÿUÿ]ÿfÿlÿqÿtÿyÿ{ÿ|ÿzÿzÿwÿvÿoÿfÿ^ÿRÿGÿ;ÿ3ÿ.ÿ+ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ+ÿ,ÿ.ÿ.ÿ0ÿ0ÿ3ÿ5ÿ6ÿ8ÿ9ÿ;ÿ=ÿAÿBÿDÿGÿKÿOÿXÿ^ÿdÿkÿpÿtÿyÿ}ÿ~ÿ€ÿ‚ÿ‚ÿ„ÿ„ÿ†ÿ†ÿ‡ÿ‡ÿ†ÿ†ÿ„ÿ„ÿ‚ÿ€ÿ~ÿ}ÿ|ÿ{ÿwÿsÿrÿrÿvÿyÿzÿ}ÿÿƒÿ„ÿ…ÿ…ÿ…ÿ…ÿƒÿÿ}ÿ{ÿyÿyÿxÿyÿ~ÿ…ÿ†ÿ†ÿ…ÿ…ÿ‚ÿ‚ÿÿ{ÿuÿmÿbÿRÿDÿ9ÿ2ÿ/ÿ.ÿ.ÿ.ÿ0ÿ3ÿ8ÿ=ÿBÿMÿXÿbÿkÿsÿxÿ|ÿ€ÿ‚ÿÿ€ÿÿÿÿÿÿ€ÿÿ|ÿzÿwÿwÿtÿsÿsÿvÿxÿzÿÿ„ÿˆÿ‹ÿÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‰ÿˆÿˆÿˆÿ‰ÿ‰ÿˆÿ†ÿÿxÿnÿbÿWÿNÿIÿFÿAÿ>ÿ<ÿ;ÿHHmI€KoLLoN€OpO€OpP€PpQQoQRrQ~SsR~RsR~RtR~RtQ~PuP~PvN}MwL}KwJ|JxH|GzE}DzA}@z@€>z=€€C}G‚OwU‡^pbˆjgqˆvc{ˆ}_}ˆ~^{‡x^v„p`n„lbl„kbl„m]n„oZo„mYmƒkXi„g[eƒc_a€^da~dgj~mhr~wf{€c‚‚`‚‚a|ƒvcqƒif^‚QmB€6u.|*~+{*‚*z*…+z+…+z+„+z+„0z5‚={E{Q}_sj~ri{€€_…ˆ\†‚†Z‚ƒ}[x„nbc„ThEƒ6p-&w$€$}%&}'€,{/‚1w4ƒ8t=ƒ>rBƒDpH‚LmU^neoiw€f„‚„cƒƒbz…sbh…`eV…PjN]me~upvxop`{fqy‚ss@…$v!„#{#‚###€#"€"~""~!"~""~#€#~$|$*z.3u9ApG‚MkSƒ[i`ƒehjƒphs‚xh{}h|xhvvhuvivvksolj`nT€MoD@r?}?t?{?u@|@u?|?u?{?u@}>u=}=v<};v:~:u7~5w5~4v2~1w/~0y.}.x2}8u;}BpM~Tg\h]nrVuƒxU|„~W}„|\{…v^q†icc‡VgJ‡>p4ƒ.w+)))(~('}'(}((}(€)})€*|+,|-0{1€1{3€4{5~8{9~=|>{@|AwE}JtM~SoZ~_lf~kjr}vi{~h‚ƒgƒƒe…€†e‡€‡gˆ€†i…€…i„€†ij}|jzwkt~rjp|pmqyupvyyr}{tƒ|ƒt…~…t…t~~~s{~yrz~zp{~p†€‡q‡€†p‚€„o}qyrsj‚\rN>p51s-,w,~.{0}3|7{<|CzMyWy`tizrqx{|m~hd€€c€€€c€b}{`xw\trZoo]r€udx~l„}‰sŠ|ŠuŒ}Œu~sŠ~ŠrŠ~ŠrŠ~Šs‰‰u‰‡w€‚wzk„^~SN„I}F†A{>ˆ;y:…IÿJÿJÿKÿMÿLÿNÿOÿOÿOÿPÿPÿQÿQÿQÿRÿSÿRÿRÿRÿQÿQÿPÿPÿPÿPÿOÿOÿMÿLÿKÿKÿIÿIÿGÿFÿDÿCÿAÿ@ÿ?ÿ>ÿ<ÿ;ÿ;ÿ:ÿ9ÿ9ÿ7ÿ7ÿ9ÿ9ÿ9ÿ:ÿ:ÿ:ÿ9ÿ9ÿ8ÿ:ÿ:ÿ:ÿ9ÿ9ÿ9ÿ9ÿ8ÿ8ÿ7ÿ7ÿ6ÿ6ÿ4ÿ3ÿ3ÿ3ÿ2ÿ1ÿ0ÿ0ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ,ÿ+ÿ*ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ*ÿ+ÿ+ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ(ÿ(ÿ(ÿ(ÿ'ÿ&ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ+ÿ-ÿ/ÿ0ÿ1ÿ2ÿ2ÿ3ÿ5ÿ5ÿ5ÿ4ÿ6ÿ8ÿ8ÿ9ÿ9ÿ9ÿ9ÿ8ÿ8ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ;ÿ:ÿ9ÿ9ÿ7ÿ7ÿ:ÿ:ÿ?ÿDÿJÿRÿYÿ^ÿfÿlÿsÿxÿ|ÿ~ÿ}ÿ|ÿyÿvÿsÿnÿlÿjÿjÿiÿhÿhÿiÿfÿfÿeÿfÿdÿ`ÿ^ÿZÿXÿUÿUÿXÿ\ÿaÿeÿlÿuÿzÿ€ÿ‚ÿ‚ÿ‚ÿÿ~ÿ|ÿvÿmÿbÿWÿIÿ;ÿ4ÿ,ÿ*ÿ,ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ.ÿ2ÿ8ÿAÿNÿXÿcÿoÿyÿ€ÿ†ÿˆÿ‡ÿ‡ÿƒÿ~ÿ}ÿtÿjÿ[ÿMÿ=ÿ0ÿ)ÿ$ÿ$ÿ$ÿ%ÿ+ÿ.ÿ1ÿ6ÿ8ÿ;ÿ?ÿBÿDÿCÿFÿKÿOÿXÿdÿmÿuÿ~ÿƒÿ†ÿ†ÿ‚ÿ}ÿwÿnÿcÿWÿOÿWÿlÿ`ÿiÿlÿpÿkÿdÿwÿÿ^ÿIÿ+ÿ!ÿ$ÿ#ÿ#ÿ#ÿ#ÿ"ÿ"ÿ"ÿ"ÿ!ÿ"ÿ"ÿ"ÿ#ÿ"ÿ%ÿ(ÿ.ÿ3ÿ;ÿ@ÿFÿMÿRÿYÿ_ÿcÿgÿnÿqÿuÿyÿ}ÿ|ÿ{ÿyÿvÿvÿvÿwÿwÿwÿuÿpÿhÿ`ÿTÿKÿDÿBÿ?ÿ?ÿ?ÿ?ÿ@ÿ@ÿ?ÿ?ÿ?ÿ?ÿ>ÿ=ÿ=ÿ=ÿ<ÿ;ÿ:ÿ:ÿ7ÿ5ÿ5ÿ4ÿ1ÿ1ÿ.ÿ/ÿ.ÿ.ÿ2ÿ7ÿ;ÿBÿJÿTÿ\ÿfÿnÿrÿuÿxÿwÿzÿ~ÿ}ÿ|ÿwÿsÿlÿcÿZÿNÿ?ÿ8ÿ1ÿ+ÿ(ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ*ÿ+ÿ-ÿ.ÿ0ÿ1ÿ3ÿ3ÿ3ÿ4ÿ6ÿ7ÿ9ÿ>ÿ>ÿDÿIÿNÿSÿYÿ_ÿaÿiÿmÿtÿxÿ}ÿ€ÿƒÿ„ÿ†ÿ†ÿ‡ÿˆÿ‰ÿˆÿˆÿˆÿ…ÿ…ÿƒÿÿ€ÿ~ÿ|ÿyÿvÿtÿqÿoÿoÿoÿpÿpÿsÿuÿxÿÿƒÿƒÿ„ÿ„ÿƒÿÿ~ÿ~ÿ}ÿ{ÿ|ÿ|ÿ~ÿÿ†ÿ†ÿ†ÿ…ÿ„ÿ‚ÿ€ÿ{ÿvÿoÿfÿXÿIÿ;ÿ3ÿ/ÿ/ÿ0ÿ.ÿ.ÿ0ÿ2ÿ5ÿ:ÿBÿMÿUÿ^ÿgÿpÿwÿ|ÿÿ€ÿÿÿ~ÿ~ÿÿ~ÿ~ÿ|ÿ{ÿxÿvÿtÿqÿmÿlÿlÿnÿrÿvÿ{ÿÿ‡ÿ‰ÿŠÿ‹ÿ‹ÿŒÿŒÿŠÿŠÿŠÿŠÿŠÿŠÿ‹ÿ‹ÿŠÿ…ÿ€ÿwÿjÿ]ÿRÿKÿGÿEÿBÿ?ÿ<ÿ;ÿHImJJoLLqM€NqO€OsO€OsQ€QrR€RsRQtRRuQ~QtP~PuP~OvP~OwL}JxJ}JxH{GyG{E{C}A{@~?{>=y<€;w:€9u:€:s88q88p89p::p:€:p:€:p::q99q8€8s8€8s65s54s43t22u2€1u0€0w0€0y/€/y--y--{,+}))})~(|(~(|((|((|(~(}(~(}(~)})~)~)}))})*}**}*,},,},,},,},},~,~,~,~,~,,~,,~,~,~+~*~*|+~+|*~(~)~)~*~(~)~)~)~)}(~(}'~'|'~'|''}''}'}'~'}'~'~'~'~''~'}'~'|&~&}'~'}'~'~'~'~'~'~(~(~(~(~(~(~*~**~+-}-.|1‚1|1ƒ2z4ƒ5y5„6y7„8z8…8z8…9y9…9y9…9y9‡:y:‡9y8†9y9†9y9†8y7„6{9ƒ:};‚@€E}LƒSvY…`mfˆmgs‡v`y†{]yƒv^w‚s`pkch€eddda_ƒ_^^ƒ^[^„^X_…]VZ…WYT‚R]O~NfO~SmX}_of}pjz€e„ƒa„‚„`„}`x„qelƒ\mO‚Bt8/{+|*‚*{*ƒ*z*…+{+…+{,…-z2ƒ8z?€I{Wva~mmw€€f‡ŠcŠˆa‡‚ƒa{„vdm„_hRƒBo4*t%€%{%€(y.ƒ2u4ƒ9s<ƒ@qCƒDnE‚DmF‚InN‚Uj_‚iiqzi‚†h‡ƒ„e€ƒydr…ie]„Uh\€ipjyqyYsIRsi†quc„R€WzG†!w ˆ!z$#~#~"€"~"€#~""~""~"}&({-€4w;?rE‚KnSƒWj]ƒbhdƒihnthyzj||j{xiwwiwwixwjsolj‚_nTJoD€?s?}?v>|>w?}?v?}?v?}?v>}>v=|=w<|:w9|9w7}5w4~3w2~1x1~/x/}/y0}6u:}@pK}Sg\~e]k€rWv‚wUyƒ{V}…{Zz…z^w†qdg†\hO‡Bp8„1w-‚+}))(~('~'€(}(€'})€(}*€*|+€,{-€/z0€2z3€2{47|9|:}?zD}HtNRrY`od€gjm€rgv€{h€‚h…€†gˆ€ˆgŠŠh‰‰hˆˆj†…jƒ€€j~€}kx~vkt}pll|knj{jnkympozts{{t‚|‚sƒ~ƒu~u€~~r}}}o}}~p‚…p†€†p„€ƒo‚€p‚zrs‚lrbƒRqC‚8r2/t..x.}.z/}2|6z;|BzMwWyasizqqw|zk~}€g€e~€}c€c}€{awu_s‚r[m‚i]i‚iam€qev~|l~„sŠ|ŠuŠ~Št‹~‹s‰~‰s‰~‰t‹~‹u‹~ŠvŠ€…x‚vygƒZ}NH„G~EˆCy@‰>y<‡JÿJÿKÿJÿLÿLÿNÿNÿOÿOÿPÿPÿOÿOÿPÿRÿRÿQÿPÿPÿQÿQÿPÿPÿPÿOÿOÿMÿLÿJÿJÿJÿHÿGÿEÿDÿCÿAÿ@ÿ?ÿ>ÿ=ÿ<ÿ;ÿ;ÿ:ÿ:ÿ:ÿ9ÿ9ÿ8ÿ8ÿ9ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ9ÿ9ÿ8ÿ8ÿ8ÿ8ÿ7ÿ6ÿ5ÿ4ÿ3ÿ3ÿ2ÿ2ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ-ÿ-ÿ,ÿ+ÿ*ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ-ÿ-ÿ.ÿ0ÿ1ÿ0ÿ3ÿ3ÿ3ÿ3ÿ5ÿ6ÿ7ÿ8ÿ8ÿ8ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ:ÿ:ÿ9ÿ8ÿ9ÿ9ÿ8ÿ8ÿ7ÿ7ÿ7ÿ8ÿ:ÿ;ÿ@ÿEÿJÿPÿYÿaÿiÿoÿuÿvÿvÿvÿtÿqÿoÿlÿfÿcÿ_ÿZÿYÿYÿXÿXÿZÿZÿXÿXÿYÿYÿXÿUÿOÿMÿJÿGÿEÿHÿPÿYÿcÿjÿsÿ{ÿÿ‚ÿƒÿƒÿÿ€ÿ}ÿvÿmÿcÿWÿIÿ<ÿ2ÿ.ÿ-ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ,ÿ,ÿ0ÿ7ÿ<ÿFÿSÿ`ÿkÿuÿ~ÿ…ÿ‰ÿŠÿˆÿ‡ÿ„ÿÿyÿrÿdÿWÿGÿ6ÿ-ÿ)ÿ)ÿ)ÿ.ÿ1ÿ5ÿ8ÿ:ÿ?ÿBÿDÿFÿEÿFÿFÿLÿNÿRÿYÿdÿoÿwÿ~ÿƒÿ…ÿ…ÿÿ{ÿtÿkÿ`ÿ\ÿdÿjÿkÿeÿWÿLÿQÿiÿ[ÿOÿNÿVÿ\ÿYÿTÿ&ÿ"ÿ"ÿ$ÿ$ÿ$ÿ#ÿ#ÿ$ÿ"ÿ"ÿ"ÿ$ÿ(ÿ-ÿ4ÿ:ÿ@ÿEÿKÿQÿWÿ]ÿ^ÿbÿhÿlÿqÿvÿyÿzÿ|ÿ|ÿzÿzÿwÿwÿwÿwÿvÿuÿtÿpÿjÿ`ÿSÿIÿCÿAÿ=ÿ=ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ=ÿ<ÿ;ÿ:ÿ9ÿ9ÿ7ÿ5ÿ4ÿ3ÿ2ÿ3ÿ1ÿ1ÿ.ÿ.ÿ/ÿ3ÿ8ÿ?ÿIÿRÿ\ÿfÿmÿpÿtÿwÿzÿ{ÿ{ÿ}ÿ|ÿzÿvÿqÿiÿ_ÿRÿGÿ:ÿ2ÿ.ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ'ÿ(ÿ(ÿ*ÿ)ÿ*ÿ+ÿ+ÿ,ÿ,ÿ-ÿ.ÿ/ÿ1ÿ3ÿ3ÿ6ÿ9ÿ;ÿ@ÿFÿKÿPÿUÿZÿ_ÿdÿhÿmÿqÿvÿzÿ}ÿ‚ÿ„ÿ…ÿ‡ÿ‡ÿˆÿŠÿŠÿŠÿŠÿ‰ÿ‡ÿ…ÿƒÿ€ÿÿzÿxÿvÿtÿoÿkÿhÿgÿeÿeÿgÿjÿmÿsÿ{ÿÿ‚ÿ‚ÿ‚ÿÿ€ÿ~ÿ~ÿ~ÿÿÿÿ€ÿƒÿ†ÿ‡ÿ†ÿ„ÿƒÿ„ÿ€ÿ}ÿxÿrÿiÿ^ÿOÿ@ÿ7ÿ1ÿ.ÿ-ÿ.ÿ.ÿ/ÿ2ÿ2ÿ6ÿ;ÿCÿMÿXÿaÿiÿqÿuÿzÿ}ÿ~ÿ~ÿ~ÿ~ÿ~ÿ}ÿ{ÿzÿyÿwÿsÿmÿmÿkÿgÿgÿgÿjÿnÿqÿxÿÿ„ÿ‡ÿŠÿŠÿŠÿŠÿŠÿ‰ÿ‰ÿˆÿˆÿŠÿ‹ÿ‹ÿ‹ÿˆÿ…ÿ~ÿuÿhÿ[ÿNÿIÿGÿDÿCÿ@ÿ?ÿ@ÿKKnKMoMMnM€OpO€OrP€PrP€PqR€SrQQtPPuP~PvO~OwP|OxM|MyK}KzI}IzH}F{E}D|B~A{?>{==y<€;v;€;v9€9s88q99p::p::p:€:p:€:p::r99r8€8s8€8s76u55u32u11w1€1w1€1x1€1y0€0y/.y--{,,{++{*~*|*~*|**|))|*~*}(~(})~)}*~*~*}**}*+}+,},-}--}--}--},},~,~-~-~,~,-~-,~,~,~,~-~-+~+,~,~*~*~+~+~)~)~)~)~(~(~(~(}(~(}(~(~(~(~((~'''~((~((~(}'~&|&~&}'~'}(~'~'~'~(~(~(~(~(~((~)*~*+~+,},€,|-/|0‚1z2ƒ3{3„4{6„6z7…9z9…8y8†8y8†8y8‡:y:‡8y8†9y8†6y6†5y5„6y7ƒ8|:‚>BG‚NwW„_ng„mcq‚o^om_kh_gbd^XdWTdS„RbU…U`W†WYX†XTV‡VTU„P[KFeB}>p<|AuG{Qv]}fnowea…‚…a‚„b€„{fsƒik^‚PqB€6w0},,{*ƒ+z+…+{*…+{+…,{-„4{9B{O{[}hst~|m„€‰g‹‰b‡‚†b‚„|et„ji]ƒOl>1o+‚+s/‚4t7ƒ9p<ƒ>nBƒCmEƒFlFƒGlGƒIlLƒPlX`mjrm{k„…iƒ|gu„lgbƒ^l`Sw[yNUvTƒEuO…SvJ‚FzDycjndƒZq%€!y$$€$~$‚$~$€#$&)z0€3v8?qEƒLkQ‚Vi[ƒ^h`ƒejimiq€sitwkzzkzyjwvjvwjxxkuqmk‚`oRJqC?r<}}>w>}>w>}>w=}=w;|;w<|:w7|7w8}6w4~3w2~2x1~0y.~/z.}3x7|>sH}Qj[}c`lqXt‚vTx‚zW}„~[|…z`x†tck†agR‡Gn=…4u0ƒ,|)((~((~()})+}+€+},€,|,-{..{/~1{4|6|:y?}EuI€OrR€Wn\alfiinshuxh~€‚h…€‡h‡€‡f‡€ˆf‹‹gŠ‰kˆ…m‚m}{mwsnp|nok{inhxdlbwclgxipnyts{{v€|‚t‚~€s€~€s}~r|€o€}‚o…ˆoˆ€†o…€„mƒ€o}‚wqq‚epY„Ko=ƒ6r1-t..x-~.z1|4{8{=yFzOvXy`ri{qlv||h}}}c}€|a}€|`}yawwdsnak‚jag‚fcc€cdejhq}wn~}„s‡|ŠtŠ~Šr‰~‰q‰~‰qˆ~ˆt‹~‹uŒ~‹u‰ƒw}ƒtze„ZR€J†H{G‰DyB‰@y?ˆJÿIÿLÿLÿMÿLÿPÿNÿOÿOÿPÿPÿPÿPÿOÿPÿQÿQÿPÿPÿPÿPÿOÿOÿOÿMÿMÿMÿKÿJÿJÿHÿFÿDÿCÿBÿ@ÿ?ÿ>ÿ=ÿ=ÿ=ÿ=ÿ<ÿ;ÿ;ÿ:ÿ:ÿ8ÿ8ÿ9ÿ9ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ9ÿ9ÿ8ÿ8ÿ8ÿ8ÿ7ÿ6ÿ5ÿ5ÿ3ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ.ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ-ÿ-ÿ+ÿ+ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ&ÿ%ÿ&ÿ'ÿ'ÿ(ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ-ÿ/ÿ1ÿ1ÿ1ÿ2ÿ3ÿ3ÿ5ÿ5ÿ6ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ9ÿ9ÿ8ÿ8ÿ8ÿ7ÿ5ÿ5ÿ4ÿ5ÿ5ÿ6ÿ6ÿ9ÿ=ÿAÿEÿKÿSÿ^ÿeÿhÿhÿfÿfÿeÿdÿbÿ]ÿZÿUÿRÿSÿRÿQÿRÿRÿRÿVÿVÿWÿWÿVÿSÿPÿMÿGÿAÿ=ÿ8ÿ7ÿ9ÿAÿJÿWÿaÿkÿsÿ}ÿ€ÿ‚ÿ…ÿ‚ÿ‚ÿ€ÿ|ÿvÿoÿeÿYÿGÿ:ÿ2ÿ.ÿ,ÿ*ÿ+ÿ+ÿ+ÿ*ÿ+ÿ+ÿ+ÿ,ÿ1ÿ7ÿ?ÿKÿXÿdÿqÿ|ÿƒÿˆÿŠÿˆÿ‡ÿ†ÿ‚ÿ|ÿuÿmÿbÿRÿAÿ4ÿ/ÿ/ÿ2ÿ6ÿ9ÿ=ÿ?ÿBÿDÿGÿFÿGÿGÿHÿHÿHÿKÿPÿVÿ[ÿeÿmÿuÿ~ÿ‚ÿ„ÿ‚ÿ|ÿxÿnÿcÿ`ÿUÿNÿGÿ:ÿCÿ@ÿ5ÿ9ÿ:ÿ>ÿ@ÿCÿKÿTÿ]ÿSÿ#ÿ#ÿ$ÿ%ÿ%ÿ$ÿ$ÿ$ÿ&ÿ&ÿ*ÿ.ÿ5ÿ;ÿ?ÿGÿLÿQÿXÿZÿ]ÿ`ÿcÿfÿgÿkÿoÿqÿsÿuÿyÿyÿzÿyÿvÿuÿvÿuÿvÿvÿuÿpÿhÿ^ÿQÿHÿAÿ=ÿ<ÿ<ÿ<ÿ<ÿ=ÿ=ÿ=ÿ=ÿ=ÿ=ÿ<ÿ<ÿ;ÿ:ÿ9ÿ8ÿ7ÿ7ÿ7ÿ5ÿ4ÿ3ÿ2ÿ2ÿ0ÿ/ÿ/ÿ.ÿ.ÿ2ÿ8ÿ=ÿFÿRÿZÿcÿkÿqÿvÿuÿwÿ{ÿ}ÿ}ÿ|ÿ|ÿ{ÿtÿlÿbÿWÿKÿ<ÿ4ÿ1ÿ-ÿ)ÿ(ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ.ÿ/ÿ/ÿ2ÿ5ÿ8ÿ=ÿBÿEÿKÿPÿTÿYÿ^ÿcÿhÿmÿpÿsÿvÿ|ÿ~ÿ‚ÿ„ÿ‡ÿ‡ÿ†ÿ‡ÿˆÿˆÿŠÿŠÿŠÿ‰ÿ†ÿƒÿ€ÿ}ÿyÿuÿqÿoÿlÿjÿgÿdÿaÿ_ÿ`ÿcÿgÿjÿnÿtÿyÿ}ÿÿ€ÿ€ÿÿ€ÿ€ÿ€ÿ€ÿƒÿ‚ÿ‚ÿ…ÿˆÿ‰ÿˆÿ†ÿ…ÿ„ÿ€ÿÿ}ÿvÿnÿcÿWÿJÿ<ÿ7ÿ3ÿ.ÿ/ÿ/ÿ.ÿ/ÿ1ÿ5ÿ8ÿ>ÿFÿOÿ[ÿcÿjÿpÿwÿ{ÿ}ÿ~ÿ}ÿ|ÿ|ÿ{ÿzÿxÿuÿrÿoÿjÿgÿfÿcÿbÿ`ÿ`ÿdÿiÿpÿvÿ~ÿ„ÿ‡ÿŠÿŠÿŠÿ‰ÿ‰ÿ‰ÿ‰ÿˆÿŠÿˆÿ‹ÿŒÿ‹ÿˆÿ…ÿ~ÿtÿgÿYÿQÿKÿHÿGÿDÿBÿAÿ@ÿKKnNNnMMmO€OnO€OpP€PqPQsQQtQ~OuP~PuP}PuO}OvL|LwM{LxM}J{H}F|E}CBA??|?={>=y<€~C€JyRYo_bhd€bf`‚`b^ƒ\eZƒWfTƒRfPƒPcQ†S^T‡T\U‡UWV…VUS„PWM‚GaC};m5|1v2{4|;{E{P|\rhqky~cƒ‚aƒƒ€`€ƒ}cwƒphjƒ^nPAu6/{+|**{*„*z(„*z*…+z+„/z7‚<|F}T}`vmxr€€†j‡ˆe‡†eƒ‚fy„pie„YkJ„;o4ƒ3p6ƒ9o;„?oD„FmGƒGlHƒHlIƒIkIƒHkIƒLnQVo_gnq~znj}iumjd`oTUvW€D};~2‚C}Z/|-0zAxQ{EqMLq8‚"w%%~%€%$€$'*|0€4w;ArF‚LnR„Vk[ƒ^h_ƒbhd‚eii‚jij€lkqtkv€xlx€xks€siutittkrmni_oQ€Hq@~xD€HsN€SnX€^jc‚iikƒohsƒug|‚}hƒe„€„e†€†f†ˆg‰‰i‰€‰j‰†n„o|wot~poj|hofycnax^l]w_jcwgkixmpozus{|s}€s}u~}u€|r„{ƒo…|‡o‰Šmˆ†n†ƒpƒ‚€p|‚uol…amT…Fm;ƒ7p4‚0t/€0x.~0x2|6z={BxH{Rs\{dpi|pmx~|i}}d~}a|{_w‚uaq‚oelƒgbdƒ_a\‚\e\\ja}ino{vs}z…u‰}‰uŠ‰rˆ~ˆp‡~‡qˆ~Št‹~‹u~Œw‰„w|‚syg„\}QJ†I{FŠEyCŠAx@ŠLÿLÿNÿNÿMÿMÿOÿOÿOÿOÿPÿPÿPÿQÿQÿQÿRÿQÿPÿPÿPÿPÿOÿMÿLÿLÿKÿLÿJÿHÿHÿFÿDÿBÿ@ÿ@ÿ?ÿ?ÿ>ÿ?ÿ>ÿ=ÿ<ÿ<ÿ<ÿ<ÿ;ÿ;ÿ;ÿ;ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ;ÿ;ÿ;ÿ;ÿ:ÿ:ÿ:ÿ:ÿ8ÿ8ÿ7ÿ7ÿ7ÿ4ÿ4ÿ4ÿ3ÿ2ÿ2ÿ2ÿ1ÿ1ÿ2ÿ2ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ.ÿ.ÿ.ÿ.ÿ,ÿ,ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ(ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ+ÿ,ÿ-ÿ-ÿ.ÿ.ÿ0ÿ0ÿ1ÿ1ÿ2ÿ4ÿ4ÿ5ÿ7ÿ7ÿ8ÿ8ÿ7ÿ7ÿ7ÿ7ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ4ÿ6ÿ9ÿ=ÿBÿIÿNÿUÿWÿVÿWÿZÿ[ÿ[ÿ[ÿZÿZÿVÿUÿSÿSÿSÿSÿTÿUÿUÿWÿXÿVÿUÿRÿOÿIÿCÿ=ÿ6ÿ0ÿ-ÿ-ÿ2ÿ7ÿ@ÿKÿVÿcÿmÿvÿ}ÿ‚ÿ€ÿƒÿÿ€ÿÿzÿsÿmÿcÿVÿIÿ;ÿ2ÿ-ÿ*ÿ*ÿ*ÿ+ÿ*ÿ)ÿ*ÿ+ÿ+ÿ.ÿ2ÿ9ÿDÿQÿ^ÿgÿtÿ}ÿƒÿ‡ÿˆÿ‡ÿ†ÿƒÿÿ|ÿsÿkÿ_ÿQÿCÿ9ÿ7ÿ:ÿ=ÿ?ÿCÿEÿEÿGÿGÿGÿGÿIÿIÿIÿIÿIÿIÿIÿNÿWÿ`ÿjÿsÿyÿÿÿvÿhÿkÿpÿdÿ]ÿ]ÿ[ÿMÿEÿBÿWÿYÿ(ÿ+ÿ4ÿ<ÿ;ÿ7ÿ=ÿDÿKÿ'ÿ%ÿ%ÿ%ÿ%ÿ&ÿ(ÿ*ÿ0ÿ5ÿ:ÿ@ÿGÿLÿRÿWÿ\ÿ]ÿ`ÿbÿdÿdÿgÿeÿeÿfÿiÿlÿpÿsÿvÿvÿvÿsÿsÿuÿtÿuÿuÿrÿmÿfÿ\ÿPÿGÿ@ÿ<ÿ9ÿ9ÿ9ÿ:ÿ;ÿ;ÿ:ÿ;ÿ;ÿ;ÿ;ÿ;ÿ:ÿ9ÿ:ÿ:ÿ7ÿ6ÿ5ÿ3ÿ4ÿ2ÿ2ÿ2ÿ0ÿ/ÿ/ÿ.ÿ/ÿ2ÿ7ÿ<ÿDÿNÿXÿbÿiÿqÿuÿvÿxÿzÿ|ÿ|ÿ|ÿ|ÿyÿsÿnÿfÿ[ÿNÿCÿ8ÿ2ÿ+ÿ,ÿ,ÿ+ÿ+ÿ*ÿ)ÿ+ÿ+ÿ+ÿ,ÿ-ÿ.ÿ.ÿ0ÿ1ÿ3ÿ9ÿ=ÿCÿHÿMÿQÿWÿ\ÿ`ÿfÿjÿnÿqÿvÿzÿ|ÿ~ÿ€ÿ€ÿ‚ÿ„ÿ„ÿ†ÿ†ÿ†ÿ‡ÿˆÿˆÿˆÿ‡ÿ†ÿ„ÿÿ}ÿwÿsÿkÿgÿeÿaÿ`ÿ\ÿ\ÿ\ÿ^ÿbÿeÿhÿkÿnÿpÿtÿzÿ~ÿÿ~ÿ~ÿ}ÿ~ÿÿÿƒÿ†ÿ†ÿ‡ÿ‰ÿ‰ÿ‰ÿˆÿ†ÿ…ÿ…ÿ‚ÿÿ|ÿuÿlÿaÿTÿFÿ=ÿ8ÿ6ÿ4ÿ2ÿ0ÿ0ÿ2ÿ4ÿ9ÿ>ÿFÿLÿUÿ]ÿeÿmÿsÿwÿ|ÿ}ÿ|ÿ}ÿ|ÿzÿwÿuÿrÿmÿjÿeÿbÿ_ÿ[ÿYÿYÿXÿZÿ`ÿjÿmÿvÿ~ÿ…ÿˆÿ‰ÿŠÿ‰ÿˆÿˆÿ‡ÿ‡ÿ‡ÿ‰ÿŒÿ‹ÿŒÿŒÿˆÿ‚ÿ|ÿsÿgÿ]ÿRÿLÿJÿFÿEÿCÿ@ÿ>ÿMMnNNnNNnO€OoO€OpP€PqPQtQQuQ}QuQ}QvO|OwN|NxM{MzKzH{I|G}D|B~A}@}@@}??{??z>=x<€;v<€„@oC„FnG„GlIƒIlJƒJlJƒJlHƒHlFFpEGpM}Wpc|mpw}|n}sokeri`se~dx`}VA}ƒ9r8‚5u4€1x1~4x7}=uD{JsS|Ypa|fmn}ukz}f~|b|{a{xbs‚pej‚gea_fZ‚XiSRmS}Vp^{humywx€z†v‰|Šs‰~ˆqˆ~†p†~‡q‡~‰tŠ~‹v‹~‰v†‚t}‚suhƒ\yR‚M…H}GŠD{CŠAy@ŠMÿMÿNÿNÿPÿPÿOÿOÿOÿOÿPÿPÿQÿRÿQÿQÿQÿQÿNÿNÿMÿMÿMÿMÿLÿLÿJÿHÿGÿEÿBÿAÿ@ÿ?ÿ?ÿ?ÿ@ÿ@ÿ>ÿ=ÿ>ÿ=ÿ;ÿ<ÿ<ÿ<ÿ<ÿ<ÿ:ÿ9ÿ;ÿ;ÿ;ÿ;ÿ:ÿ:ÿ:ÿ:ÿ9ÿ9ÿ:ÿ:ÿ9ÿ8ÿ7ÿ7ÿ7ÿ7ÿ6ÿ5ÿ4ÿ3ÿ2ÿ2ÿ3ÿ3ÿ2ÿ2ÿ1ÿ1ÿ1ÿ0ÿ.ÿ0ÿ1ÿ1ÿ1ÿ/ÿ/ÿ/ÿ-ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ.ÿ/ÿ/ÿ.ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ/ÿ0ÿ1ÿ0ÿ0ÿ1ÿ4ÿ3ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ2ÿ1ÿ0ÿ0ÿ1ÿ4ÿ6ÿ:ÿ>ÿDÿIÿNÿQÿUÿVÿZÿ[ÿ[ÿ\ÿ\ÿ\ÿYÿYÿXÿXÿXÿXÿWÿWÿZÿXÿWÿTÿRÿMÿGÿAÿ9ÿ4ÿ.ÿ*ÿ)ÿ+ÿ-ÿ0ÿ7ÿ@ÿKÿWÿaÿlÿuÿ}ÿÿ‚ÿÿÿ~ÿ}ÿyÿtÿmÿdÿUÿGÿ:ÿ0ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ0ÿ7ÿ=ÿEÿSÿ`ÿjÿwÿ~ÿƒÿ†ÿ†ÿ…ÿ„ÿÿ|ÿwÿoÿhÿ`ÿTÿJÿEÿBÿDÿEÿGÿHÿHÿIÿIÿJÿJÿKÿKÿHÿGÿBÿBÿBÿBÿEÿPÿ[ÿgÿtÿtÿoÿiÿfÿlÿgÿeÿgÿeÿXÿJÿCÿ@ÿGÿ9ÿ1ÿ2ÿ3ÿ,ÿ9ÿ<ÿEÿIÿcÿKÿ%ÿ%ÿ(ÿ*ÿ,ÿ1ÿ8ÿ;ÿAÿGÿLÿRÿVÿYÿ^ÿ_ÿaÿaÿcÿdÿeÿ`ÿ]ÿ\ÿ\ÿ^ÿdÿhÿnÿqÿtÿuÿtÿsÿsÿsÿsÿsÿoÿjÿdÿZÿLÿAÿ=ÿ;ÿ8ÿ8ÿ7ÿ7ÿ8ÿ9ÿ9ÿ8ÿ8ÿ8ÿ8ÿ8ÿ7ÿ7ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ3ÿ2ÿ2ÿ1ÿ/ÿ0ÿ/ÿ.ÿ.ÿ2ÿ6ÿ;ÿDÿNÿYÿbÿiÿpÿuÿvÿxÿyÿzÿ{ÿ|ÿ|ÿ|ÿxÿrÿiÿ_ÿSÿIÿ<ÿ7ÿ1ÿ/ÿ/ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ1ÿ3ÿ7ÿ;ÿAÿFÿKÿOÿUÿ[ÿ_ÿeÿjÿnÿrÿuÿwÿ|ÿ~ÿÿÿÿÿÿÿ‚ÿ‚ÿ„ÿ…ÿ†ÿ…ÿ…ÿ†ÿ†ÿƒÿƒÿÿ~ÿzÿuÿmÿgÿ`ÿ[ÿXÿYÿZÿ\ÿ_ÿbÿeÿhÿkÿlÿoÿqÿtÿwÿ{ÿ{ÿ}ÿ}ÿ|ÿ~ÿ~ÿÿƒÿ†ÿ‰ÿ‰ÿ‰ÿŠÿŠÿˆÿ‡ÿ‡ÿ‡ÿ‡ÿ„ÿ‚ÿ|ÿuÿjÿ_ÿRÿEÿ?ÿ:ÿ9ÿ6ÿ5ÿ5ÿ4ÿ7ÿ;ÿAÿHÿPÿVÿ\ÿcÿjÿsÿxÿ}ÿÿÿ|ÿ|ÿ|ÿzÿwÿsÿmÿhÿ`ÿ[ÿXÿUÿRÿPÿOÿOÿSÿ\ÿfÿmÿwÿÿˆÿ‰ÿ‰ÿ‰ÿˆÿ‡ÿ†ÿ†ÿ‡ÿ‡ÿ‰ÿŠÿŠÿˆÿ‡ÿ…ÿÿ{ÿsÿhÿ^ÿRÿMÿHÿFÿCÿBÿ@ÿ?ÿMMjOOlQQnQ€PoOOpOOqP€PsO~OuP~PvO{NxL|LxK{IzJ{IzG{F}D{B~A|B~@~@??}??{@@z==x=~DzH‚LtQ„UnX„\h^†^e]†^c]†[cY†YaY†Y`X…W^Y†X]Y†T\S„O]IBf=}6r.{+{(z(){*ƒ.{4€;}FzR~^uhqky€€eb‚~^}‚{bvƒpgh‚\mN@s4-{*{)€*{*ƒ){*„*z*…*z.ƒ6{:B{P|\}huq~|n‚€…l…€…gƒ€d}{fuƒnjd„ZkQ„KjF„FiFƒHiHƒIkIƒIkJƒJkJƒImG‚Do>€~ErL}SqY~`nf~mntzk|~g}}d|{aw‚tboƒlfc]fV€SkO~MpI|HsLzQvZwexmxxz‚z‡wŠ}Šr‰ˆp††q‡†sˆ‰u‰‰vˆ~‡t„~syqtj„_xT‚N„HE‰C|A‹?z>‹MÿMÿOÿOÿPÿPÿPÿOÿOÿOÿOÿOÿPÿPÿOÿOÿNÿNÿNÿMÿLÿKÿKÿJÿIÿGÿFÿDÿBÿAÿ@ÿ?ÿ@ÿ@ÿ?ÿ?ÿ?ÿ?ÿ@ÿ@ÿ?ÿ>ÿ=ÿ<ÿ=ÿ=ÿ=ÿ=ÿ<ÿ<ÿ;ÿ;ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ9ÿ9ÿ:ÿ8ÿ7ÿ7ÿ7ÿ6ÿ6ÿ6ÿ5ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ2ÿ2ÿ3ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ0ÿ0ÿ0ÿ/ÿ.ÿ-ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ,ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ'ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ.ÿ.ÿ-ÿ.ÿ/ÿ0ÿ1ÿ0ÿ1ÿ2ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ3ÿ2ÿ1ÿ1ÿ1ÿ0ÿ.ÿ/ÿ2ÿ5ÿ6ÿ9ÿ?ÿEÿJÿNÿTÿXÿZÿ\ÿ_ÿ_ÿ`ÿ`ÿ^ÿ\ÿ[ÿ[ÿ[ÿ[ÿYÿXÿYÿ[ÿXÿVÿQÿLÿEÿ=ÿ6ÿ/ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ,ÿ0ÿ7ÿAÿLÿXÿbÿnÿuÿ}ÿ€ÿÿ€ÿ~ÿ~ÿ}ÿyÿsÿmÿcÿUÿIÿ9ÿ0ÿ*ÿ)ÿ)ÿ)ÿ(ÿ)ÿ*ÿ*ÿ*ÿ*ÿ0ÿ7ÿ>ÿIÿWÿdÿnÿwÿ~ÿƒÿ„ÿ„ÿƒÿÿÿ|ÿwÿqÿkÿaÿXÿPÿIÿGÿGÿHÿHÿIÿJÿJÿJÿJÿKÿJÿEÿ@ÿ<ÿ9ÿ6ÿ9ÿ;ÿEÿSÿ^ÿpÿoÿoÿkÿiÿeÿ`ÿ]ÿYÿSÿMÿIÿJÿLÿSÿ`ÿ`ÿ_ÿ_ÿXÿKÿAÿ8ÿ+ÿ;ÿRÿnÿnÿ5ÿ(ÿ7ÿ<ÿBÿFÿNÿYÿYÿ[ÿ`ÿlÿiÿTÿMÿVÿaÿ[ÿ[ÿaÿRÿPÿPÿUÿZÿaÿiÿpÿuÿuÿtÿsÿtÿrÿpÿoÿnÿhÿ`ÿUÿIÿ?ÿ8ÿ5ÿ6ÿ6ÿ5ÿ6ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ2ÿ2ÿ2ÿ0ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ/ÿ0ÿ4ÿ:ÿCÿMÿVÿ_ÿgÿoÿtÿwÿyÿwÿyÿ{ÿ{ÿ{ÿ|ÿyÿsÿmÿcÿYÿLÿ@ÿ:ÿ6ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ3ÿ4ÿ8ÿ=ÿDÿIÿPÿTÿ\ÿ`ÿfÿiÿmÿrÿuÿyÿ|ÿÿÿ~ÿ}ÿ}ÿ}ÿ}ÿÿ€ÿ€ÿ€ÿÿƒÿƒÿƒÿ‚ÿ‚ÿ‚ÿ‚ÿÿÿÿ|ÿyÿtÿlÿdÿ]ÿXÿWÿYÿ\ÿ^ÿbÿfÿiÿkÿmÿoÿoÿpÿrÿvÿyÿ|ÿzÿ{ÿ{ÿ|ÿ|ÿÿ‚ÿ„ÿˆÿ‹ÿ‹ÿŒÿŒÿ‹ÿ‰ÿ‡ÿ‡ÿ‡ÿ‡ÿƒÿÿ{ÿtÿjÿ^ÿPÿGÿCÿ?ÿ>ÿ=ÿ;ÿ;ÿ>ÿ@ÿCÿJÿQÿXÿ]ÿeÿjÿqÿvÿ{ÿ~ÿ~ÿ}ÿ}ÿ}ÿ{ÿvÿrÿmÿfÿ`ÿYÿRÿMÿJÿEÿCÿCÿHÿQÿ\ÿeÿqÿ|ÿ„ÿˆÿ‡ÿˆÿ‰ÿˆÿ†ÿ†ÿ†ÿ‡ÿˆÿ‰ÿ‰ÿ‰ÿ†ÿ…ÿ‚ÿ~ÿwÿsÿiÿ_ÿTÿMÿGÿDÿBÿ@ÿ@ÿ>ÿNNjOOlOOnN€NoOOpONqP~PsQ}PuN}NvL{LyJ{JzI{H|GzF~CzA~@{@~@}@~?~>}??|?€?{?>z??x>=v=€=u<€€3y*|)){)ƒ*{*„*z*…*z*ƒ/{5<{D~S|`xl}vq|€nƒƒiƒ€€g|ixukoƒgj\„RhK„HiHƒHkIƒKlKƒKlKƒJlH‚EnAnI†Lnc†mih…cim„wlJ„OoKTqZ‚[oY€WnJ~IrK}NtV|`tiqpuwkuwht‚sis‚qkkhm^SoF€{>ˆNÿNÿOÿOÿOÿOÿNÿNÿOÿOÿOÿOÿPÿPÿOÿNÿLÿLÿKÿKÿJÿJÿHÿGÿDÿBÿ@ÿ@ÿ@ÿ>ÿ>ÿ>ÿ>ÿ>ÿ?ÿ?ÿ?ÿ?ÿ?ÿ>ÿ?ÿ?ÿ?ÿ=ÿ=ÿ=ÿ<ÿ<ÿ<ÿ<ÿ<ÿ<ÿ:ÿ:ÿ;ÿ;ÿ:ÿ:ÿ9ÿ9ÿ8ÿ8ÿ8ÿ8ÿ7ÿ7ÿ7ÿ6ÿ5ÿ6ÿ5ÿ5ÿ6ÿ5ÿ3ÿ3ÿ4ÿ4ÿ5ÿ5ÿ3ÿ3ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ0ÿ/ÿ/ÿ/ÿ.ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ0ÿ0ÿ/ÿ/ÿ.ÿ.ÿ.ÿ-ÿ,ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ)ÿ)ÿ)ÿ)ÿ+ÿ+ÿ+ÿ+ÿ*ÿ+ÿ,ÿ-ÿ-ÿ-ÿ.ÿ/ÿ/ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ1ÿ1ÿ0ÿ0ÿ/ÿ/ÿ.ÿ0ÿ1ÿ2ÿ6ÿ:ÿ=ÿCÿGÿOÿTÿYÿ\ÿ^ÿ`ÿaÿbÿcÿcÿaÿ_ÿ_ÿ_ÿ\ÿ[ÿ[ÿZÿZÿZÿUÿNÿKÿCÿ=ÿ5ÿ.ÿ*ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ,ÿ0ÿ7ÿAÿMÿXÿdÿnÿvÿ}ÿÿ€ÿÿ}ÿ~ÿ}ÿyÿsÿlÿaÿTÿEÿ7ÿ-ÿ*ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ-ÿ3ÿ9ÿBÿMÿ[ÿfÿqÿ{ÿ€ÿ‚ÿ„ÿ‚ÿÿ€ÿ}ÿzÿuÿpÿiÿ`ÿWÿPÿMÿJÿJÿIÿKÿKÿKÿJÿIÿFÿAÿ=ÿ8ÿ4ÿ/ÿ-ÿ,ÿ1ÿ;ÿQÿiÿrÿsÿmÿMÿ]ÿaÿWÿUÿOÿNÿPÿMÿGÿBÿNÿVÿbÿ`ÿXÿUÿXÿ]ÿTÿ=ÿ9ÿBÿTÿ^ÿqÿZÿLÿOÿ[ÿmÿ~ÿwÿ€ÿjÿhÿ[ÿYÿbÿmÿpÿwÿtÿdÿ\ÿPÿ@ÿAÿJÿTÿ_ÿkÿsÿuÿwÿvÿvÿtÿsÿsÿqÿkÿhÿ^ÿSÿFÿ<ÿ6ÿ4ÿ2ÿ2ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ4ÿ3ÿ2ÿ2ÿ1ÿ0ÿ/ÿ-ÿ.ÿ.ÿ,ÿ-ÿ-ÿ,ÿ,ÿ-ÿ3ÿ8ÿ@ÿHÿSÿ\ÿhÿoÿsÿwÿxÿxÿyÿyÿzÿ{ÿ{ÿ{ÿtÿnÿiÿ]ÿSÿGÿ>ÿ8ÿ7ÿ6ÿ5ÿ5ÿ5ÿ6ÿ:ÿ?ÿDÿJÿOÿWÿ^ÿdÿiÿoÿtÿxÿzÿ€ÿÿ‚ÿ€ÿÿ~ÿ}ÿ|ÿ|ÿ}ÿ}ÿ~ÿ~ÿÿÿÿÿÿÿÿÿ}ÿ}ÿ~ÿzÿyÿwÿsÿqÿnÿgÿbÿ\ÿYÿYÿ]ÿaÿcÿgÿlÿmÿpÿrÿrÿrÿsÿuÿxÿ{ÿ}ÿ}ÿzÿzÿ{ÿ|ÿÿƒÿˆÿŠÿÿÿÿŽÿŒÿ‡ÿ‡ÿ‡ÿ†ÿ†ÿƒÿÿ{ÿrÿjÿ\ÿRÿNÿHÿFÿFÿFÿEÿGÿIÿKÿOÿWÿ^ÿcÿfÿlÿrÿxÿ|ÿ~ÿ~ÿ~ÿ}ÿ{ÿ{ÿyÿtÿmÿfÿ^ÿXÿOÿGÿAÿ=ÿ9ÿ:ÿ<ÿFÿRÿ\ÿhÿwÿ‚ÿ†ÿˆÿˆÿˆÿˆÿ‡ÿ…ÿ…ÿ‡ÿ‡ÿ‰ÿ‰ÿˆÿ‡ÿ…ÿ‚ÿ€ÿ{ÿvÿrÿkÿaÿUÿMÿEÿAÿ@ÿ>ÿ>ÿ>ÿO€OmN€NmO€OoO€OpPPoO~PpN~MqN}LwK{JxI{H{FzF}FzD}BzB@{@?~?>~>~=~=}>~>}??{@€@y>€=v>€>u==t==s<p:€4r1+u*~*v.~9vPqw€~vxe|`\~G~YzY~UwQRwN€LwMHwQVxY~LzL}\v_|KkG|Ah*yFiL~]iTOn[‚nstoo€€somhqm{lvevpyzv‚tqzjm\|Fn=}KqU|arlspwwkxxivtisƒqjnƒgm]ƒPpD:r3~1w0|0y1{1z1{1y2{2y1|1y1|0x0|1y1}1y/|0y/~-y-}-z-},z-~,z+~,z,}0y2}8x>{GoR|\he~ndrv`w‚w^yƒ{^{ƒ{azƒzcwƒrfk†`kV…Hp@ƒ~GwS€bvn~yyx‡|‰tŠ~ˆs‡†r††t††v‰~ˆx…~„wƒ~t{qwspl„buV„L|FAƒ?~>†:z9†OÿOÿNÿNÿOÿOÿOÿOÿOÿOÿMÿNÿMÿLÿKÿJÿJÿIÿIÿHÿFÿCÿCÿAÿ?ÿ?ÿ=ÿ=ÿ=ÿ=ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ?ÿ?ÿ@ÿ@ÿ>ÿ=ÿ>ÿ>ÿ=ÿ=ÿ>ÿ>ÿ=ÿ=ÿ<ÿ<ÿ<ÿ;ÿ<ÿ<ÿ;ÿ;ÿ;ÿ;ÿ9ÿ8ÿ8ÿ7ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ2ÿ2ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ0ÿ/ÿ.ÿ.ÿ-ÿ-ÿ.ÿ,ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ(ÿ(ÿ(ÿ(ÿ*ÿ*ÿ)ÿ*ÿ,ÿ,ÿ-ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ-ÿ-ÿ/ÿ0ÿ1ÿ4ÿ5ÿ9ÿ?ÿCÿIÿNÿTÿZÿ^ÿ`ÿcÿeÿfÿfÿfÿeÿdÿaÿ`ÿ`ÿ_ÿ[ÿZÿZÿZÿWÿQÿLÿBÿ<ÿ3ÿ-ÿ*ÿ(ÿ'ÿ(ÿ'ÿ&ÿ&ÿ&ÿ'ÿ)ÿ-ÿ1ÿ8ÿCÿMÿYÿcÿmÿwÿ|ÿ~ÿ€ÿÿ}ÿ|ÿ|ÿxÿqÿlÿ_ÿSÿDÿ6ÿ-ÿ*ÿ*ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ*ÿ.ÿ4ÿ9ÿDÿRÿ_ÿjÿrÿ{ÿ~ÿ€ÿ‚ÿÿ€ÿ~ÿ{ÿyÿuÿpÿjÿaÿXÿQÿMÿLÿLÿLÿLÿKÿGÿCÿ>ÿ:ÿ5ÿ/ÿ+ÿ(ÿ'ÿ+ÿ/ÿ5ÿTÿsÿ|ÿ|ÿ{ÿoÿ_ÿVÿHÿFÿVÿVÿQÿUÿ[ÿRÿKÿTÿ\ÿ[ÿjÿRÿIÿZÿdÿeÿZÿGÿBÿFÿKÿOÿJÿXÿfÿ_ÿlÿmÿqÿoÿ€ÿ{ÿlÿgÿlÿ_ÿ[ÿpÿfÿgÿKÿUÿ@ÿHÿWÿdÿnÿsÿwÿwÿxÿxÿxÿwÿvÿsÿnÿgÿ]ÿPÿDÿ:ÿ2ÿ0ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ.ÿ.ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ+ÿ-ÿ.ÿ1ÿ7ÿ>ÿGÿQÿ[ÿeÿnÿtÿxÿxÿxÿxÿyÿzÿ|ÿzÿzÿwÿqÿkÿbÿXÿNÿEÿ?ÿ=ÿ9ÿ8ÿ<ÿ@ÿDÿHÿOÿWÿ^ÿeÿiÿpÿtÿyÿ~ÿ‚ÿƒÿ…ÿ‚ÿ‚ÿ€ÿ€ÿÿÿ|ÿ{ÿ{ÿzÿ{ÿ}ÿ|ÿ~ÿ~ÿ~ÿ€ÿ~ÿ|ÿzÿyÿxÿvÿvÿuÿtÿsÿrÿpÿnÿhÿcÿ_ÿ_ÿ`ÿaÿdÿgÿjÿnÿpÿqÿrÿsÿvÿvÿxÿzÿ}ÿ|ÿ{ÿzÿzÿ{ÿ}ÿÿƒÿˆÿ‹ÿŽÿÿŽÿÿ‰ÿ‡ÿˆÿˆÿˆÿ†ÿƒÿÿ{ÿsÿjÿ_ÿXÿTÿSÿPÿPÿPÿQÿRÿVÿZÿ\ÿbÿfÿjÿoÿqÿvÿzÿ~ÿ~ÿ}ÿ|ÿ|ÿ|ÿyÿvÿnÿhÿ`ÿYÿNÿDÿ<ÿ8ÿ4ÿ4ÿ7ÿ>ÿIÿVÿdÿpÿzÿ‚ÿ‡ÿˆÿˆÿˆÿ‡ÿ…ÿ†ÿ†ÿˆÿ‰ÿ‰ÿ‡ÿ„ÿ„ÿ‚ÿÿ}ÿyÿxÿqÿlÿbÿVÿLÿEÿ@ÿ>ÿ=ÿ:ÿ9ÿO€NmO€NmO€OmN€NoMLoK~MpL}KrJ|HwI{HyE{D|C{B@{?;{;<|<:~<<~<~=~=}>>}>>{?€@y>€>v>€>u==t==s??s==s<€{GrP|Zke~ndqu`x‚x^xƒx^yƒ|a{ƒ{cxƒsfn„gh\„OlG‚Bp?€ÿ>ÿ=ÿ;ÿ;ÿ:ÿ:ÿ:ÿ;ÿ<ÿ<ÿ=ÿ=ÿ>ÿ>ÿ>ÿ>ÿ?ÿ@ÿ>ÿ>ÿ>ÿ>ÿ=ÿ=ÿ=ÿ=ÿ>ÿ>ÿ=ÿ=ÿ<ÿ<ÿ<ÿ<ÿ<ÿ<ÿ;ÿ:ÿ:ÿ9ÿ9ÿ9ÿ7ÿ7ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ7ÿ7ÿ7ÿ7ÿ8ÿ8ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ3ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ0ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ,ÿ,ÿ-ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ+ÿ+ÿ,ÿ,ÿ+ÿ-ÿ/ÿ.ÿ/ÿ.ÿ.ÿ.ÿ-ÿ-ÿ/ÿ/ÿ.ÿ/ÿ.ÿ1ÿ3ÿ7ÿ;ÿBÿGÿLÿRÿVÿ]ÿ_ÿcÿfÿgÿiÿkÿkÿjÿgÿfÿcÿaÿ_ÿ]ÿ[ÿ[ÿYÿWÿRÿIÿCÿ;ÿ4ÿ-ÿ(ÿ'ÿ'ÿ(ÿ&ÿ'ÿ(ÿ'ÿ&ÿ'ÿ(ÿ(ÿ,ÿ1ÿ9ÿBÿNÿXÿcÿmÿvÿzÿ|ÿ~ÿ{ÿ|ÿ|ÿ{ÿwÿrÿjÿ`ÿPÿBÿ5ÿ,ÿ)ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ)ÿ*ÿ/ÿ5ÿ<ÿGÿUÿcÿkÿsÿzÿ~ÿ}ÿ~ÿ}ÿ}ÿ{ÿ{ÿyÿuÿpÿhÿ_ÿVÿQÿMÿJÿIÿIÿEÿAÿ<ÿ7ÿ0ÿ*ÿ&ÿ#ÿ"ÿ ÿ$ÿ*ÿ;ÿ_ÿaÿfÿkÿqÿmÿeÿLÿFÿUÿ[ÿ\ÿVÿTÿXÿ[ÿYÿOÿXÿRÿQÿZÿXÿ^ÿ`ÿgÿiÿbÿRÿ@ÿVÿNÿDÿeÿ~ÿyÿnÿfÿ_ÿVÿ^ÿKÿ:ÿ8ÿ=ÿQÿVÿHÿPÿSÿGÿFÿGÿKÿVÿdÿoÿuÿwÿxÿxÿwÿyÿyÿwÿuÿlÿiÿ\ÿPÿCÿ9ÿ0ÿ-ÿ+ÿ+ÿ+ÿ+ÿ,ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ,ÿ-ÿ1ÿ7ÿ>ÿGÿPÿZÿdÿmÿqÿuÿxÿxÿxÿxÿwÿzÿzÿzÿwÿtÿoÿhÿ^ÿRÿJÿEÿ@ÿCÿFÿKÿPÿWÿ\ÿaÿhÿmÿtÿxÿ}ÿÿ„ÿ…ÿƒÿ‚ÿ‚ÿÿÿ€ÿ~ÿ~ÿ}ÿ}ÿzÿzÿxÿxÿwÿyÿzÿzÿzÿzÿzÿxÿwÿuÿsÿqÿqÿqÿqÿrÿrÿpÿoÿkÿfÿbÿbÿcÿdÿfÿiÿlÿoÿqÿrÿsÿvÿvÿwÿzÿ}ÿ}ÿ|ÿ{ÿzÿzÿ{ÿ}ÿÿ…ÿ‰ÿŒÿÿÿÿÿ‹ÿ‰ÿ‡ÿ‡ÿ‡ÿ†ÿ„ÿ‚ÿ{ÿsÿlÿbÿ^ÿZÿYÿXÿXÿXÿZÿ[ÿ]ÿ`ÿgÿkÿpÿrÿsÿxÿ}ÿ~ÿ~ÿ~ÿ}ÿ|ÿ{ÿ{ÿyÿtÿlÿeÿ[ÿQÿEÿ;ÿ5ÿ2ÿ2ÿ3ÿ9ÿBÿPÿ_ÿjÿvÿÿ†ÿˆÿ‰ÿˆÿˆÿ‡ÿ†ÿ†ÿ‡ÿˆÿˆÿˆÿ‡ÿƒÿ‚ÿÿ{ÿxÿxÿvÿsÿlÿcÿVÿLÿCÿ?ÿ;ÿ9ÿ9ÿ9ÿO€OlO€NlM€MnN€LoK~KoJ}IrH|GuF|FwD{C{B{@~=|<~<};~7}79}98~:~<~<~>~=|>>|>€>{??y>€>v?€?v>>u>>s==r>>r<>q=€;p;€;q9€8q8€7s7€7u7€7v6€6v6€6w56w78z66z66z46z55z55z54z3€2z2€1z0€2z2€2z0~/{.~/{/~,{-},|-|-}-{-~-z-}+z+~,{,~+{+-|-~-|-~.|./|0€.|.€.|.€/|//|..}.€.}.€-}/€-},€-}-€,}+€*}**}*)}))})*}*‚)})‚)|)‚*|*‚(|(‚)|)‚)|)‚)})‚)|)‚(|*‚)|)(|()})‚)})‚)})‚)})‚(}((}(€(}(€)})€)}))})+|++|++{+*|*‚+{+‚+|,ƒ-z-ƒ-z-ƒ.y.ƒ.x.ƒ.x.ƒ/w0„0y0‚2|2€7~;}?FxLƒQrUƒ\m`ƒagc„gcj…j`m„m`i„g_f„c`a…^`[…[`Z„WaSOfG=n7~0x+|((|(‚'|&ƒ&|'ƒ&|&ƒ(|)ƒ)|*ƒ/|4€<}G|T}^vh~rnx€{h|€{`{‚}`|‚yctƒmheƒWoH:u0~)|)|)‚){)ƒ){*ƒ)|)ƒ.|28{BN}\yh~osw|n}|j}€|f|{ez‚xht„mjb…YjR„LkIƒIlF‚Bp;€6s3~+u&}#x!} z#~#z(€;|MUzh}f|c|\}Y}2~@~Zv[WqR]xQPZ|V~Z{UyP{^v[zWt^z`ojyelWw4kOvNjNvoio|rjdbng‚XpZ‚WxM~8{=xGwCtQqWvDp:x6v?zMw[}gup~vpx€xky€ygxxhv‚tknƒim_ƒOpB8r0~*w+}+|,|+}+|+|+|+|*|*{+|-{,},{+}*z*}*|)}+|,},{*}*{+})|+}+{-|-{0{7x>{FrP{Zkc}ldq€wawx`xƒx`xƒzb{ƒ{cxƒvepƒig`ƒVjN‚GnF€HpNRoY€]idigp‚uez‚gƒ‚†d…‚„b€a€b€€b}€}c{€ygv}viu|tht{ujuywnv{vov|wou~rnq~qmp~qnq}ros}qoo}kmhzdncxcndugoislmoqqkrqskvrvoxvzs|z}u}}{uz~zu{}~u|†uŒzs|‘p~‹m‰€‰mˆ€Šoˆ€‡p†‚o}ƒvik…dca…^b\„[e\„\e_„aab„e`iƒnasƒwdwƒ{c}‚}d}}e|{e{ƒzfwƒrfl„ehY…NmBƒ9r4€2y2{4}ÿ;ÿ:ÿ:ÿ:ÿ9ÿ7ÿ8ÿ9ÿ9ÿ9ÿ;ÿ;ÿ;ÿ;ÿ=ÿ>ÿ>ÿ?ÿ?ÿ?ÿ?ÿ>ÿ>ÿ=ÿ=ÿ>ÿ>ÿ>ÿ>ÿ=ÿ=ÿ>ÿ>ÿ=ÿ=ÿ=ÿ=ÿ;ÿ<ÿ;ÿ;ÿ:ÿ9ÿ8ÿ8ÿ7ÿ7ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ7ÿ7ÿ8ÿ9ÿ6ÿ6ÿ6ÿ7ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ3ÿ3ÿ3ÿ1ÿ1ÿ2ÿ1ÿ1ÿ0ÿ0ÿ0ÿ1ÿ0ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ0ÿ0ÿ2ÿ3ÿ7ÿ;ÿ@ÿEÿKÿQÿVÿZÿaÿdÿeÿiÿkÿkÿlÿlÿiÿjÿiÿfÿcÿ`ÿ^ÿ[ÿYÿXÿVÿPÿIÿ@ÿ9ÿ1ÿ-ÿ*ÿ)ÿ(ÿ(ÿ'ÿ&ÿ&ÿ'ÿ&ÿ'ÿ&ÿ&ÿ(ÿ)ÿ-ÿ2ÿ7ÿAÿNÿWÿbÿnÿuÿzÿ{ÿ|ÿ{ÿ{ÿ}ÿ{ÿwÿqÿiÿ^ÿNÿ?ÿ4ÿ+ÿ*ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ0ÿ5ÿ=ÿJÿXÿaÿlÿtÿ{ÿ|ÿ}ÿ~ÿ}ÿ{ÿ{ÿ|ÿzÿuÿoÿfÿ_ÿUÿNÿJÿFÿCÿ=ÿ7ÿ2ÿ,ÿ'ÿ#ÿ!ÿ$ÿ"ÿ"ÿ#ÿ>ÿ`ÿ^ÿRÿVÿ_ÿ^ÿ\ÿUÿRÿRÿUÿZÿ\ÿ]ÿTÿ`ÿ\ÿLÿKÿZÿ_ÿ^ÿgÿSÿJÿ[ÿcÿ`ÿ\ÿVÿFÿWÿKÿRÿgÿ_ÿdÿcÿgÿeÿeÿeÿdÿQÿ4ÿ:ÿHÿEÿCÿBÿ=ÿ/ÿ9ÿCÿPÿ[ÿhÿqÿwÿxÿxÿxÿxÿxÿxÿvÿtÿnÿjÿ_ÿPÿAÿ7ÿ0ÿ*ÿ*ÿ*ÿ*ÿ)ÿ*ÿ+ÿ*ÿ*ÿ+ÿ+ÿ,ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ*ÿ*ÿ+ÿ,ÿ.ÿ2ÿ7ÿ=ÿEÿQÿ[ÿcÿjÿrÿuÿwÿxÿxÿxÿxÿxÿ{ÿ{ÿyÿuÿpÿjÿbÿXÿQÿMÿLÿRÿVÿ\ÿ`ÿfÿlÿsÿwÿ}ÿÿƒÿ„ÿƒÿƒÿ‚ÿÿ€ÿ€ÿÿÿÿ|ÿzÿyÿwÿtÿrÿpÿoÿpÿrÿtÿsÿrÿrÿtÿvÿuÿtÿpÿpÿoÿpÿqÿrÿrÿrÿmÿjÿeÿbÿbÿcÿdÿgÿiÿlÿoÿqÿrÿsÿwÿwÿxÿzÿ}ÿ|ÿ{ÿ{ÿzÿzÿ|ÿ}ÿÿ†ÿŒÿÿÿÿŽÿ‹ÿ‰ÿ‰ÿˆÿ‡ÿˆÿ‰ÿ‡ÿƒÿ}ÿvÿkÿdÿbÿ_ÿ]ÿ[ÿ^ÿ^ÿaÿcÿfÿjÿoÿrÿvÿyÿ|ÿ}ÿ€ÿÿ}ÿ{ÿ{ÿ{ÿ{ÿyÿvÿpÿkÿaÿUÿJÿ=ÿ7ÿ3ÿ1ÿ2ÿ8ÿ>ÿKÿXÿdÿsÿ}ÿ„ÿ‡ÿŠÿŠÿˆÿ‡ÿ†ÿ…ÿ†ÿ‡ÿˆÿˆÿ‡ÿ…ÿÿ~ÿzÿyÿxÿwÿvÿtÿnÿeÿXÿLÿDÿ=ÿ:ÿ8ÿ9ÿ:ÿO€OlM€MlMMlKInH}GqG}GtE|DvD{Ax>|={;|:{9|8}8}7}6}78}:;~;~;~;~;~<{=={=€=z>€=w>€>v>€>u>>t>>r<>r==r==q=€9q4~+t)~$y!}!{#!| /{egz[FxL€dv]€VvSQvUUvRDwN~_}i~R‚B|NXzT€Wz^|V{Yx[{[sXzUqUxSpPxBoHvWo]whn^{fpcftk~^{yEpNzWlb}kgq€tcv€x`x‚x`yƒza{ƒ{dxƒvgrƒnhcƒ[iV‚SkU€Xj^djh€mhs€{b~a„ƒcƒd€dc~e~}gy~wfw|ugrynimyljlxmkkylnm{pos~torqno~nno~qor}rqq}rql|ipeycn`wbnetfohqlnnpolrpumwswqxvzt}z{v{}zvz~yv{|}w‚{‡vŒ{u}rŽ~‹p‰€ˆo‰€‰pˆ€Šrˆ‚ƒm}ƒvgl…eab…_a^„^b_„`bc„f`h„l_oƒt`vƒya|‚b€}c}yczzcy‚vdt‚mggƒ^hO„Dm;‚6r43y3{:}@xO|]vizvx€x‡|‰t‹‰qˆ‡r†‡s‡‡sˆˆu…~ƒt~{tw~yswvrv€tqoƒgqY…MtC„={9€89|>OÿOÿMÿMÿLÿKÿIÿHÿGÿFÿEÿEÿCÿBÿBÿ?ÿ;ÿ:ÿ9ÿ9ÿ8ÿ7ÿ8ÿ7ÿ6ÿ7ÿ8ÿ:ÿ;ÿ;ÿ;ÿ;ÿ;ÿ<ÿ=ÿ=ÿ=ÿ=ÿ=ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ=ÿ=ÿ=ÿ=ÿ<ÿ<ÿ=ÿ=ÿ>ÿ>ÿ=ÿ=ÿ<ÿ<ÿ:ÿ;ÿ:ÿ9ÿ8ÿ8ÿ8ÿ8ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ8ÿ8ÿ6ÿ6ÿ7ÿ8ÿ7ÿ6ÿ5ÿ5ÿ5ÿ5ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ1ÿ1ÿ1ÿ0ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ/ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ,ÿ+ÿ+ÿ,ÿ+ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ(ÿ)ÿ*ÿ*ÿ)ÿ&ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ/ÿ0ÿ1ÿ5ÿ8ÿ<ÿBÿGÿLÿQÿWÿZÿ^ÿcÿgÿiÿlÿmÿpÿqÿpÿqÿmÿkÿgÿcÿ`ÿ^ÿ]ÿZÿWÿTÿOÿHÿ@ÿ9ÿ3ÿ,ÿ)ÿ*ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ)ÿ-ÿ1ÿ8ÿBÿNÿXÿeÿnÿuÿxÿzÿ{ÿ{ÿzÿzÿzÿvÿqÿgÿZÿIÿ=ÿ/ÿ*ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ-ÿ1ÿ7ÿ?ÿMÿXÿdÿoÿwÿzÿ}ÿÿÿ~ÿ~ÿ}ÿ|ÿzÿuÿoÿfÿ[ÿPÿGÿAÿ:ÿ4ÿ/ÿ)ÿ&ÿ"ÿ!ÿ!ÿ!ÿ ÿ!ÿ7ÿYÿgÿhÿZÿ`ÿ]ÿcÿ_ÿHÿMÿWÿTÿRÿOÿQÿ^ÿlÿTÿ5ÿ@ÿIÿKÿGÿSÿ[ÿ^ÿ\ÿWÿOÿFÿEÿEÿBÿHÿKÿJÿVÿ`ÿNÿ]ÿ`ÿ`ÿeÿ[ÿQÿGÿDÿTÿGÿ9ÿ(ÿ(ÿ3ÿ:ÿCÿQÿ\ÿhÿqÿvÿxÿxÿvÿvÿvÿvÿuÿsÿoÿiÿ^ÿOÿ@ÿ6ÿ.ÿ)ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ)ÿ(ÿ'ÿ'ÿ'ÿ'ÿ%ÿ%ÿ'ÿ(ÿ)ÿ*ÿ*ÿ,ÿ/ÿ/ÿ3ÿ8ÿ>ÿEÿQÿWÿbÿkÿqÿtÿvÿxÿwÿwÿwÿyÿ{ÿ{ÿ{ÿwÿuÿnÿiÿaÿ]ÿ\ÿ]ÿ`ÿgÿlÿpÿsÿwÿ}ÿ‚ÿƒÿƒÿ„ÿÿÿÿÿÿ€ÿ~ÿ}ÿ|ÿzÿyÿuÿtÿpÿoÿkÿjÿiÿiÿhÿjÿkÿmÿpÿsÿtÿrÿqÿoÿnÿoÿqÿrÿrÿrÿpÿkÿhÿcÿaÿ`ÿbÿbÿdÿgÿjÿlÿmÿoÿtÿwÿwÿyÿ{ÿ|ÿ{ÿzÿzÿzÿyÿ}ÿ~ÿƒÿˆÿŒÿÿÿÿŽÿ‹ÿ‰ÿˆÿˆÿ‰ÿŠÿŠÿˆÿƒÿ}ÿvÿlÿeÿdÿbÿaÿaÿaÿbÿeÿhÿkÿoÿsÿvÿzÿ{ÿ}ÿ~ÿ~ÿ}ÿ|ÿyÿzÿzÿyÿvÿsÿlÿeÿYÿMÿEÿ;ÿ6ÿ4ÿ3ÿ5ÿ<ÿDÿSÿaÿmÿyÿ‚ÿˆÿ‹ÿŠÿ‰ÿˆÿ‡ÿ†ÿ‡ÿ‡ÿ‡ÿˆÿˆÿ…ÿ‚ÿ}ÿyÿwÿvÿvÿvÿvÿtÿlÿdÿYÿMÿCÿ=ÿ8ÿ9ÿ;ÿ@ÿNMkL~KmJ~GmI}HoG}FqD|Bs@}Bw=}=x:~9y8~7z7~6{6~6|6}7~8~8~9~;~:~:|::z<€>u>>t>>r==q>>q==r>>r<{D€IuK‚QpWƒ[l_‚cgeƒiek„mco„p`p„p^r„m]h„e]aƒ__\ƒZ_XƒWbSLgC}m6/t,}&x#~ }!~!| !}#~O~h|‚ymhzc{abv`_vS~PuS}Xv\}Z|d~TEC‚=D|L~Pz[|`yV|PyV}?{B}EvB|EtF{HvPwQsPvOtZyWw\x_|TuIwOyUq\€Ot,~.|4{9~CzP}\{hxq~urvvluuhuuhvtjnƒhl]ƒNo@4q,~(v'~&|(}(}%}%}'}'}&}&})}){(}(|)}&|%}%|%}%z&|&y&|'w(z*w,y.w1z1v4y8t?yFsQzWoa}ijosevxau‚u`wƒx`yƒzczƒyivƒsjm‚gjd‚cgdhglrgv‚{e‚ƒcƒ‚ac€€d€d}}i{}zhx|uiu{qjoymkkwgkdvckfwhljxmno|rot}tnr~qmm}omp~qmr~qpp}mmk|gmexapaubrbscrfpioinmnopqnuryryuzuzyzvz}ztyyt||vƒ{ˆvŒ|s~ŽpŠnˆ€‡o‡‰pŠŠp‰‚…l}ƒvfk…f`f…dac…cad…f^j„k^m‚o_u‚w_y{a}‚}b}‚}c|zc{{ez‚whq‚khcƒXjM‚An95s2}3x7z?|IwV{dvox{z…v‹}‹r‹€‰p‡€‡r†‡s‰‰tˆ‡v…~€t|~xquuruwrvtqp‚hq[†OsB†ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ=ÿ<ÿ=ÿ=ÿ=ÿ=ÿ;ÿ;ÿ:ÿ:ÿ9ÿ9ÿ9ÿ8ÿ8ÿ8ÿ8ÿ8ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ8ÿ7ÿ7ÿ7ÿ7ÿ7ÿ6ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ1ÿ0ÿ0ÿ/ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ6ÿCÿCÿ4ÿ'ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ*ÿ+ÿ+ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ-ÿ/ÿ/ÿ0ÿ2ÿ5ÿ8ÿ>ÿCÿGÿMÿRÿVÿ[ÿ`ÿdÿfÿiÿkÿmÿoÿqÿqÿpÿpÿoÿkÿfÿcÿ`ÿ]ÿ[ÿXÿUÿTÿOÿFÿ?ÿ8ÿ1ÿ-ÿ*ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ'ÿ)ÿ-ÿ2ÿ8ÿCÿRÿ\ÿfÿpÿuÿyÿ|ÿ|ÿ{ÿ|ÿ|ÿyÿuÿoÿdÿVÿFÿ6ÿ,ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ)ÿ,ÿ3ÿ;ÿCÿQÿ]ÿjÿuÿyÿ|ÿÿÿÿÿÿÿÿzÿuÿmÿ_ÿQÿFÿ<ÿ3ÿ-ÿ*ÿ%ÿ"ÿ!ÿ!ÿ!ÿ ÿ!ÿ5ÿWÿmÿÿyÿ\ÿ^ÿbÿaÿ^ÿYÿXÿJÿKÿPÿZÿbÿ[ÿ]ÿYÿMÿBÿHÿFÿGÿKÿYÿWÿPÿPÿYÿCÿ4ÿ:ÿKÿOÿRÿJÿHÿHÿFÿJÿLÿPÿPÿPÿLÿNÿ\ÿhÿVÿOÿ)ÿ-ÿ5ÿ:ÿDÿOÿ\ÿgÿrÿvÿtÿtÿuÿuÿtÿvÿuÿrÿmÿiÿ]ÿNÿ@ÿ4ÿ+ÿ'ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ(ÿ)ÿ(ÿ(ÿ'ÿ&ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ(ÿ)ÿ*ÿ,ÿ.ÿ0ÿ3ÿ4ÿ5ÿ9ÿ?ÿFÿQÿXÿaÿjÿoÿsÿvÿxÿuÿuÿwÿxÿyÿ{ÿ{ÿzÿxÿvÿrÿmÿjÿiÿiÿoÿrÿwÿ|ÿ€ÿƒÿ„ÿ‚ÿ‚ÿÿÿÿÿ~ÿ}ÿ}ÿzÿxÿwÿuÿsÿpÿpÿlÿiÿeÿaÿaÿbÿfÿhÿkÿnÿqÿrÿtÿtÿqÿpÿnÿoÿpÿqÿqÿpÿoÿmÿiÿeÿbÿaÿaÿbÿcÿcÿeÿfÿhÿjÿnÿrÿuÿyÿxÿzÿ{ÿ{ÿ{ÿzÿyÿyÿ|ÿÿƒÿˆÿŽÿÿÿŽÿÿŠÿ‰ÿ‰ÿ‡ÿˆÿ‰ÿ‰ÿ‡ÿƒÿ}ÿvÿkÿfÿfÿdÿdÿdÿdÿfÿiÿlÿnÿsÿuÿwÿyÿ{ÿ{ÿ{ÿzÿzÿyÿyÿzÿ{ÿyÿvÿpÿjÿbÿWÿIÿ>ÿ8ÿ5ÿ3ÿ4ÿ8ÿ@ÿMÿ\ÿiÿuÿÿ‡ÿ‹ÿ‹ÿ‹ÿ‰ÿ‡ÿ‡ÿ†ÿ‡ÿ‰ÿ‰ÿ‰ÿˆÿ„ÿÿyÿvÿtÿtÿtÿuÿvÿtÿoÿgÿ\ÿQÿEÿ<ÿ9ÿ=ÿCÿJÿONlL~InG~FmF}EoD}Ar?|=u<}9w6}6x6~6y5~4z4~3{4~6|6~5}5~5}7~7{88z:€:y;€€>u=€=t==t>>q??q>>q>>q??q>>q==r<€ŠTwJƒX}d~UA=~O~@}L~T|SP}N~Q~:|;€HxMKvHHsN€JsU‚Yq\„^ob„eph‚hqa|JrBtKuFsDxBwMzNwLxYyfuV~Wv+€,}2{:~EzQ}]{gxq~srsrlrrhsthusjnƒin^ƒOpA4t,~(y%~%|$}$}$~$}$~$}&}&~&}'}(}(}'}&}%}%|'}'z'{(y){+w-y.v/y1u2x4t7x;s@wFpOxVm`{hio}sduwbu‚waxƒxa{ƒ}d}ƒ|ey‚xfv‚seqqdrwgy~g„eƒƒb€€c€€‚e€€~g|€{g{~xjw{ujqzpknykjgwcj_vajcvfihwjklzpns}sot~snqpmooloolp~nlm|klgzclaw_p`u`r_rarcpepfnholnqotpxs{u{v{y|xz}wuxyu}|€v„{‰vŽ|Žt~qŠnˆ€ˆo‰ŠpŠ‰pˆ‚…k|ƒtel…gbf…ebeƒeaeƒf^j‚k^nq\rt_y€za{{dz€yey€yfx€xixsjmgj`‚UkG=o97s6|7y;zD{Rw`zlxxx{†v‹€‹r‹€ˆp‡€‡rˆ‰sŠŠt‰ˆvƒtxuqssottottpq‚gp]„QqE„=w>BzG~NyLÿKÿJÿGÿGÿFÿDÿCÿBÿ@ÿ>ÿ;ÿ9ÿ7ÿ5ÿ5ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ3ÿ4ÿ5ÿ5ÿ6ÿ6ÿ7ÿ9ÿ8ÿ8ÿ:ÿ:ÿ9ÿ:ÿ;ÿ;ÿ=ÿ=ÿ>ÿ>ÿ=ÿ=ÿ>ÿ>ÿ?ÿ?ÿ?ÿ?ÿ?ÿ?ÿ@ÿ>ÿ?ÿ?ÿ>ÿ>ÿ=ÿ=ÿ<ÿ<ÿ<ÿ<ÿ;ÿ:ÿ;ÿ;ÿ<ÿ;ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ9ÿ9ÿ8ÿ8ÿ7ÿ6ÿ7ÿ7ÿ8ÿ8ÿ7ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ3ÿ3ÿ2ÿ2ÿ1ÿ0ÿ.ÿ.ÿ-ÿ-ÿ+ÿ+ÿ+ÿ+ÿ*ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ/ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ&ÿ)ÿ(ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ+ÿ+ÿ+ÿ,ÿ,ÿ+ÿ,ÿ-ÿ,ÿ,ÿ+ÿ,ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ.ÿ/ÿ.ÿ/ÿ0ÿ4ÿ7ÿ:ÿ@ÿEÿIÿPÿVÿ[ÿ`ÿdÿgÿiÿlÿnÿqÿrÿrÿrÿoÿpÿnÿlÿhÿdÿaÿ]ÿ[ÿYÿVÿRÿLÿEÿ<ÿ6ÿ/ÿ+ÿ*ÿ)ÿ)ÿ(ÿ'ÿ&ÿ&ÿ'ÿ'ÿ&ÿ'ÿ'ÿ'ÿ'ÿ+ÿ5ÿ<ÿ/ÿ&ÿ(ÿ-ÿ2ÿ;ÿGÿTÿ_ÿkÿtÿyÿ{ÿ{ÿ{ÿ|ÿ|ÿ|ÿ{ÿuÿnÿaÿQÿBÿ5ÿ+ÿ'ÿ&ÿ&ÿ'ÿ'ÿ'ÿ(ÿ)ÿ-ÿ1ÿ:ÿAÿNÿYÿaÿnÿtÿzÿ~ÿÿ€ÿ€ÿ€ÿÿ‚ÿÿzÿsÿiÿZÿJÿ;ÿ/ÿ(ÿ%ÿ&ÿ%ÿ$ÿ!ÿ!ÿ"ÿ7ÿuÿYÿKÿBÿ<ÿ;ÿKÿOÿVÿaÿTÿ?ÿ1ÿ2ÿ;ÿ@ÿ?ÿEÿBÿGÿJÿAÿ8ÿFÿIÿKÿEÿIÿEÿNÿ[ÿ^ÿ`ÿbÿdÿeÿeÿcÿZÿ>ÿ=ÿIÿCÿEÿBÿ^ÿNÿCÿPÿ[ÿ\ÿJÿ(ÿ+ÿ3ÿ<ÿEÿQÿ]ÿgÿqÿsÿsÿrÿrÿrÿsÿtÿuÿsÿmÿhÿ^ÿOÿ@ÿ6ÿ,ÿ)ÿ%ÿ%ÿ&ÿ&ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ$ÿ%ÿ'ÿ)ÿ+ÿ,ÿ/ÿ0ÿ1ÿ3ÿ4ÿ6ÿ:ÿ>ÿCÿIÿOÿXÿaÿiÿoÿsÿvÿxÿxÿwÿvÿxÿyÿ{ÿ}ÿ~ÿ}ÿ{ÿzÿwÿwÿwÿyÿ}ÿ€ÿƒÿ„ÿ„ÿƒÿƒÿÿÿ€ÿÿ~ÿ|ÿ{ÿzÿxÿuÿsÿoÿoÿlÿgÿbÿ_ÿ^ÿ_ÿaÿcÿgÿjÿlÿoÿrÿsÿsÿuÿtÿqÿpÿoÿoÿoÿoÿpÿnÿoÿkÿeÿbÿaÿ_ÿ^ÿ]ÿ_ÿ`ÿ`ÿdÿdÿgÿlÿqÿtÿwÿxÿyÿ{ÿ|ÿzÿwÿxÿyÿ~ÿÿ†ÿŠÿŽÿŽÿŽÿŒÿ‹ÿˆÿˆÿˆÿˆÿˆÿŠÿŠÿˆÿ‚ÿ{ÿrÿlÿgÿeÿeÿeÿeÿfÿgÿgÿhÿmÿpÿqÿsÿvÿxÿzÿzÿyÿxÿyÿyÿxÿxÿvÿsÿlÿfÿ_ÿQÿGÿ=ÿ:ÿ9ÿ9ÿ:ÿ=ÿFÿTÿbÿmÿyÿƒÿŠÿ‹ÿ‹ÿ‹ÿˆÿˆÿ‡ÿˆÿ‰ÿŠÿŠÿ‰ÿˆÿƒÿÿxÿuÿsÿsÿtÿtÿtÿtÿqÿgÿ]ÿQÿDÿ>ÿAÿHÿLÿPÿLLmI~GmF~EmC}Ao@~>t<~9u6~5v43x22x12z22{33{4~4|55|78}88{9€9x9€:w;€;w<€=v>>v>>u??s??r??p@@p??q??q?>r?>s=€=s=€=t<‚:x;‚;x<‚}DwH€NqRYl_€bie‚hek‚leo‚pessco€obm€mak€gbb€_e\‚YfX‚ShO~Ik@~8s1},{*|)€)|*‚'|'ƒ(|'ƒ'|'ƒ'|'ƒ'|'ƒ&|+ƒ4}-‚'}'‚'}+€.}7}C}OvZ~hop€xhz‚|bz‚za|ƒ|a~ƒygq„flWƒEr7€,z(~'~&|'&|'(},/~4|9BwKUtakpszm}i€f€e„ƒg}ƒvklƒ_lO‚;q-€$v$}#z#~#{$~#%|ÿ>ÿ>ÿ>ÿ?ÿ?ÿ?ÿ?ÿ?ÿ?ÿ@ÿ@ÿ?ÿ?ÿ?ÿ?ÿ@ÿ@ÿ?ÿ>ÿ=ÿ=ÿ=ÿ=ÿ;ÿ<ÿ<ÿ;ÿ;ÿ;ÿ;ÿ:ÿ:ÿ:ÿ:ÿ9ÿ:ÿ:ÿ9ÿ9ÿ9ÿ9ÿ7ÿ6ÿ7ÿ9ÿ8ÿ8ÿ7ÿ8ÿ7ÿ7ÿ7ÿ7ÿ5ÿ4ÿ3ÿ2ÿ2ÿ1ÿ0ÿ.ÿ-ÿ-ÿ-ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ-ÿ-ÿ-ÿ,ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ'ÿ6ÿ1ÿ*ÿ*ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ1ÿ3ÿ7ÿ:ÿAÿFÿMÿSÿXÿ\ÿ`ÿdÿiÿjÿmÿnÿpÿrÿqÿqÿpÿnÿlÿlÿhÿdÿ`ÿ]ÿ[ÿXÿWÿQÿKÿCÿ;ÿ5ÿ.ÿ+ÿ)ÿ(ÿ(ÿ)ÿ)ÿ)ÿ'ÿ&ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ'ÿ%ÿ$ÿ%ÿ'ÿ(ÿ'ÿ*ÿ.ÿ5ÿ?ÿLÿVÿcÿoÿwÿzÿ|ÿ{ÿzÿzÿ|ÿ|ÿzÿtÿlÿ\ÿKÿ<ÿ/ÿ(ÿ'ÿ&ÿ'ÿ&ÿ'ÿ*ÿ+ÿ0ÿ6ÿ:ÿAÿJÿTÿ`ÿjÿrÿyÿ}ÿÿÿÿÿÿƒÿ‚ÿ€ÿyÿqÿbÿRÿ@ÿ1ÿ(ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ'ÿBÿnÿYÿ>ÿ6ÿ;ÿ5ÿ8ÿAÿQÿbÿiÿoÿqÿhÿPÿ=ÿFÿ>ÿ4ÿ6ÿCÿGÿFÿPÿNÿMÿNÿVÿYÿ[ÿ^ÿ`ÿaÿbÿbÿ`ÿ]ÿXÿTÿRÿ5ÿEÿGÿGÿBÿNÿaÿMÿJÿUÿ]ÿKÿ.ÿ1ÿ6ÿ=ÿFÿRÿ]ÿiÿqÿsÿtÿtÿsÿsÿtÿtÿtÿtÿlÿhÿ]ÿOÿAÿ5ÿ-ÿ)ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ%ÿ%ÿ%ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ'ÿ*ÿ+ÿ.ÿ0ÿ0ÿ2ÿ4ÿ5ÿ7ÿ:ÿ<ÿ<ÿAÿEÿKÿQÿYÿaÿhÿoÿsÿuÿyÿxÿwÿvÿxÿ{ÿ~ÿÿÿƒÿ„ÿƒÿ„ÿ„ÿ„ÿ‡ÿ‡ÿ†ÿ…ÿ…ÿ„ÿƒÿƒÿÿ€ÿ€ÿ}ÿ|ÿyÿvÿsÿrÿmÿlÿiÿfÿcÿaÿaÿbÿbÿcÿfÿgÿkÿoÿqÿrÿtÿuÿuÿtÿrÿpÿoÿoÿoÿoÿoÿnÿlÿkÿhÿdÿ`ÿ^ÿ]ÿ\ÿ\ÿZÿZÿ[ÿ\ÿaÿcÿiÿnÿsÿvÿ{ÿ{ÿ}ÿ|ÿyÿwÿyÿ|ÿ€ÿƒÿˆÿ‹ÿŽÿŽÿŽÿŒÿŠÿˆÿˆÿˆÿ‰ÿ‰ÿ‰ÿˆÿ…ÿ€ÿ|ÿsÿiÿeÿdÿdÿaÿ`ÿ_ÿ^ÿ`ÿcÿfÿjÿoÿsÿuÿvÿvÿuÿuÿuÿuÿwÿwÿwÿtÿpÿkÿcÿZÿNÿEÿ>ÿ<ÿ;ÿ;ÿ?ÿFÿQÿ^ÿiÿvÿ€ÿˆÿ‹ÿ‹ÿŠÿ‰ÿˆÿˆÿˆÿ‰ÿŠÿ‹ÿ‰ÿŠÿ‡ÿ€ÿzÿwÿtÿsÿsÿsÿtÿtÿsÿqÿiÿaÿUÿKÿFÿJÿPÿVÿZÿKImG~EnE~Co@};q9~8s6~5t4~2t21u00w//x//z11z33z33z56z8€9y7€8w8€9w;€;u=€>u=>t??s??r@@q@@qA@q@@qAAq@@r?>r==s==t=‚|D~JwL€Tp[_laeiijfmoenoeo€oenkej€jef€bf^\g[XhT€OlG}?q7{0x+z*~)|)€)|*‚2|/ƒ(|(ƒ'|'ƒ'|'ƒ'|'ƒ&{&ƒ&|%‚$}&‚#}(ƒ-}1;}HzU~asm€umy‚|h|‚zbzƒ|c{‚zfuƒnk`ƒPp?€2w*'}%~&(})*~/|3€9y=€AtI‚Sr]€fpovn}k€‚h‚ƒeƒ‚h{ltƒipZƒFp6‚,t&~$x#~#{%~((}[‚k}X„<{6‚<}B}A}IwY€esntqwƒtmV…KnH‚CpADqL€PpM€GpCLnT‚XmZ‚]m`‚`mc‚bpa]uX€TyN}F{>yBzAsCyDpKzZrTwPwLvL{9z1|17z>H{R~^|jxrtqttmusittis‚rkl‚in_‚QpB€5r-~)w%}'{'|'}%}%~&}&~%}%~%}%~&}&|%}&{'|'z(z,x.y.v0x1s3w5t6w:ss@tCrHrMoRt\lbxhho}sgv€xcy‚xax‚ya{‚~d„g‡€ˆgˆ€ˆh‰€‰h‰€ˆh‡€…h…€„iƒ€„i€j}|jy}vmt|pnlzimiydnbw`navakbucjesfiiuljnvqjsztkutkrqko€ojo€olp€omn~lni|emby_l]u[p[tZr[r[s]p]s^nbsgnltrpvwzu{w|z}uz}wtz}}u|†v‰{‹w|ŽtŽ~Œq‰‡o‡€‡pˆ€ˆqˆ€…o‚~kv‚nfh„dbc„bb_ƒ`d\\d`edhkbnsbu€ueu€ufuuhsujuujs€oij€`jYOnD~?r>z>v?wEyKuVybumxvyuˆ|ŒrŠ€‰q‰€‰q‡€‡r‰€ŠuŠ‹wˆ„typvsnrrpttpvuprlpe„VoO„JqMTq[~_pHÿHÿFÿCÿAÿ?ÿ<ÿ9ÿ8ÿ6ÿ5ÿ5ÿ2ÿ2ÿ2ÿ1ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ0ÿ1ÿ2ÿ1ÿ3ÿ3ÿ5ÿ6ÿ7ÿ7ÿ7ÿ8ÿ8ÿ9ÿ;ÿ;ÿ=ÿ>ÿ=ÿ>ÿ?ÿ?ÿ?ÿ?ÿ@ÿ@ÿ@ÿ@ÿAÿ@ÿ@ÿ@ÿAÿAÿAÿAÿ?ÿ>ÿ>ÿ>ÿ=ÿ=ÿ=ÿ=ÿ<ÿ<ÿ<ÿ;ÿ;ÿ;ÿ<ÿ<ÿ;ÿ:ÿ:ÿ:ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ8ÿ8ÿ8ÿ8ÿ7ÿ7ÿ7ÿ7ÿ5ÿ5ÿ4ÿ4ÿ3ÿ2ÿ0ÿ.ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ,ÿ.ÿ/ÿ.ÿ-ÿ-ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ'ÿ(ÿ(ÿ'ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ0ÿ0ÿ0ÿ1ÿ3ÿ6ÿ:ÿAÿFÿLÿSÿWÿ\ÿ`ÿcÿgÿiÿkÿmÿnÿlÿlÿjÿjÿiÿiÿhÿhÿcÿ_ÿ\ÿ[ÿ[ÿVÿSÿLÿCÿ;ÿ4ÿ-ÿ*ÿ*ÿ)ÿ)ÿ)ÿ*ÿ*ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ%ÿ&ÿ&ÿ&ÿ$ÿ&ÿ%ÿ(ÿ+ÿ.ÿ7ÿCÿQÿ]ÿhÿrÿyÿ|ÿ}ÿ{ÿ{ÿ|ÿ|ÿ{ÿvÿoÿfÿWÿDÿ3ÿ)ÿ'ÿ%ÿ&ÿ(ÿ*ÿ.ÿ2ÿ6ÿ:ÿ>ÿCÿJÿRÿ\ÿeÿlÿuÿ{ÿÿ€ÿÿ‚ÿÿƒÿƒÿÿ|ÿyÿpÿaÿOÿ>ÿ0ÿ'ÿ#ÿ#ÿ#ÿ$ÿ%ÿ)ÿDÿKÿFÿ8ÿ?ÿ?ÿ=ÿ>ÿJÿZÿdÿoÿsÿwÿrÿ[ÿTÿNÿAÿ>ÿAÿBÿMÿKÿEÿPÿTÿYÿZÿ[ÿ^ÿ`ÿ`ÿaÿ_ÿ\ÿXÿTÿMÿGÿBÿ:ÿ8ÿMÿBÿ@ÿGÿUÿZÿZÿVÿEÿ,ÿ2ÿ4ÿ7ÿ>ÿJÿTÿ^ÿjÿrÿtÿtÿtÿuÿsÿsÿsÿsÿrÿlÿiÿ^ÿPÿBÿ7ÿ/ÿ+ÿ(ÿ'ÿ'ÿ'ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ&ÿ&ÿ&ÿ'ÿ(ÿ*ÿ+ÿ+ÿ.ÿ0ÿ3ÿ4ÿ5ÿ7ÿ:ÿ;ÿ>ÿ@ÿBÿEÿKÿQÿTÿ\ÿbÿhÿoÿsÿvÿxÿwÿwÿvÿwÿ}ÿ€ÿ„ÿ‡ÿŠÿŒÿÿÿÿÿ‹ÿ‰ÿ‡ÿ†ÿ…ÿ„ÿƒÿ‚ÿ€ÿÿ|ÿ{ÿvÿsÿoÿkÿhÿfÿbÿ_ÿ`ÿbÿbÿaÿcÿeÿeÿhÿkÿlÿpÿqÿsÿtÿuÿtÿrÿqÿoÿoÿnÿnÿoÿmÿnÿlÿiÿeÿbÿ^ÿ\ÿZÿYÿYÿYÿXÿYÿZÿ\ÿ`ÿgÿlÿrÿvÿzÿ{ÿ}ÿ{ÿxÿwÿzÿ}ÿÿ†ÿ‰ÿ‹ÿÿŽÿŒÿŠÿ‰ÿ‡ÿ‡ÿ‡ÿˆÿˆÿˆÿ…ÿ€ÿzÿsÿlÿgÿdÿbÿ`ÿ^ÿ^ÿ\ÿ\ÿ`ÿeÿiÿlÿoÿqÿtÿtÿsÿsÿsÿsÿsÿtÿvÿsÿrÿnÿjÿ`ÿXÿNÿGÿCÿ@ÿ?ÿBÿIÿOÿ[ÿdÿpÿzÿƒÿ‰ÿ‹ÿŠÿ‰ÿ‰ÿ‰ÿ‰ÿ‰ÿŠÿ‹ÿ‹ÿ‹ÿˆÿ„ÿÿyÿuÿrÿrÿsÿtÿtÿuÿuÿsÿmÿdÿ[ÿSÿOÿQÿYÿ`ÿdÿH~EnD~Bo@~=q9~8q45r53q10r1€1s00w..x.€.y/€1y11y2€3x5€6x6€6x9€9v:€:v;€;u=€=t>>s??r??q@@pAApAAp?Ap@@qA@s??s?>t==t<|7~9|9z<|JuU~bpqtpwtnlNmCBn9}7o>FoFPnRWn[ƒ]n^„_ma‚`p^[uW€RxL}F{@{8€5x2TtC{;oGxPlYuXu\u@|0|2~69{@K{V~a|kxsupu€tktsit‚tit‚rklƒhn_ƒQtA6u0,y)}(|'|'~'|%~'|'~&|&~&|$}&|&|'|(z*{,x-y.v0x3v5w7t:w;u=v=tBtDsFsHrOpSmXr^jdvjio{sjv~wfy€yby‚{b‚„d†€Šh€k‘‘l}mŽ|Œl‰|ˆj‡}…l‚~~l~}ny}toq|nqjyhrcx_r^v`p_vaneuelfsgjhsieltkgowqgr|tit~tjs€qkookn€mnn€mmlime|bn^y[n[vYpXtYrXsWtWrYt\o`tfmlwqpvyyt|z}y{w{}{tz}~u|†vˆ|‹v}ŽtŒŠpˆ†o‡€‡p‰€‰q…€‚o}‚wlq‚jffƒcd_ƒ^d^‚\e]^dafdincprbtsds€shr€rjs~tlt~tlr~olg~`kV}NoHzEsCxCxFuKxUs_xgvrv{z…r‹}‹q‹€Šp‰€‰p‰‰sŠ‹v‰‹x‰…w~zsvtpstnuuputorkne„_nWƒVnX^od}ioGÿEÿCÿ@ÿ=ÿ9ÿ8ÿ6ÿ4ÿ3ÿ3ÿ2ÿ2ÿ0ÿ0ÿ0ÿ0ÿ0ÿ.ÿ.ÿ.ÿ.ÿ/ÿ1ÿ1ÿ1ÿ2ÿ3ÿ5ÿ6ÿ6ÿ6ÿ9ÿ9ÿ:ÿ:ÿ;ÿ;ÿ=ÿ=ÿ>ÿ>ÿ?ÿ?ÿ?ÿ?ÿ@ÿ@ÿAÿAÿAÿAÿAÿBÿBÿBÿAÿ@ÿ?ÿ?ÿ?ÿ>ÿ=ÿ=ÿ<ÿ<ÿ=ÿ=ÿ=ÿ=ÿ=ÿ=ÿ<ÿ<ÿ;ÿ;ÿ:ÿ:ÿ:ÿ:ÿ9ÿ9ÿ9ÿ9ÿ:ÿ:ÿ7ÿ7ÿ8ÿ8ÿ7ÿ7ÿ6ÿ6ÿ5ÿ5ÿ3ÿ2ÿ1ÿ0ÿ.ÿ.ÿ-ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ.ÿ-ÿ-ÿ-ÿ.ÿ/ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ,ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ-ÿ-ÿ-ÿ.ÿ.ÿ/ÿ0ÿ.ÿ.ÿ.ÿ.ÿ/ÿ1ÿ1ÿ4ÿ6ÿ9ÿ?ÿDÿKÿPÿTÿXÿ[ÿ_ÿcÿgÿhÿiÿiÿhÿfÿfÿeÿeÿdÿcÿaÿ`ÿ^ÿ^ÿ\ÿ[ÿXÿSÿNÿEÿ=ÿ4ÿ.ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ&ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ+ÿ.ÿ2ÿ<ÿIÿVÿcÿlÿwÿzÿ{ÿzÿyÿyÿ{ÿ|ÿzÿuÿkÿ^ÿNÿ?ÿ2ÿ*ÿ)ÿ)ÿ+ÿ/ÿ3ÿ7ÿ9ÿ>ÿ@ÿEÿKÿOÿWÿaÿhÿpÿxÿ~ÿÿ‚ÿÿ‚ÿ‚ÿƒÿƒÿÿ~ÿwÿnÿ_ÿNÿ=ÿ/ÿ'ÿ#ÿ#ÿ$ÿ%ÿ&ÿBÿuÿwÿDÿ&ÿ)ÿ3ÿ:ÿHÿTÿcÿpÿvÿxÿuÿnÿKÿ;ÿ.ÿ/ÿ2ÿ7ÿ.ÿ;ÿVÿYÿZÿ]ÿ^ÿ`ÿaÿaÿ]ÿ[ÿVÿRÿLÿFÿAÿ9ÿ3ÿ.ÿ-ÿ7ÿ?ÿEÿQÿOÿOÿLÿ_ÿMÿ9ÿ5ÿ5ÿ:ÿAÿJÿUÿbÿlÿuÿwÿvÿuÿuÿuÿuÿtÿuÿtÿlÿhÿ_ÿQÿAÿ6ÿ0ÿ,ÿ)ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ'ÿ&ÿ'ÿ(ÿ*ÿ,ÿ-ÿ.ÿ0ÿ3ÿ5ÿ7ÿ9ÿ<ÿ=ÿ?ÿAÿDÿFÿHÿJÿQÿUÿZÿ`ÿdÿjÿoÿsÿuÿvÿxÿzÿ{ÿ€ÿ…ÿˆÿŒÿŽÿÿ’ÿ“ÿ“ÿ’ÿ‘ÿÿÿ‰ÿˆÿ‡ÿ„ÿ€ÿ~ÿ~ÿyÿvÿrÿlÿiÿeÿaÿ`ÿ]ÿ^ÿ`ÿ_ÿaÿcÿcÿfÿgÿhÿiÿlÿlÿoÿpÿqÿsÿuÿsÿqÿpÿpÿpÿnÿmÿnÿmÿkÿgÿeÿaÿ]ÿZÿYÿWÿVÿWÿTÿSÿTÿTÿYÿ\ÿdÿjÿqÿwÿ{ÿ|ÿ|ÿzÿyÿyÿzÿ~ÿÿ†ÿˆÿ‹ÿÿŽÿŒÿŠÿˆÿ†ÿ‡ÿˆÿˆÿˆÿ…ÿ€ÿzÿuÿpÿfÿcÿ`ÿ^ÿ]ÿ\ÿ]ÿ]ÿ^ÿaÿfÿjÿlÿpÿrÿsÿrÿpÿpÿoÿoÿpÿrÿrÿrÿpÿmÿeÿ]ÿTÿMÿJÿGÿFÿFÿJÿNÿXÿbÿlÿvÿ€ÿˆÿ‹ÿ‹ÿŠÿ‰ÿ‰ÿ‰ÿ‰ÿ‹ÿŠÿ‹ÿŠÿŠÿˆÿ„ÿ}ÿyÿvÿtÿsÿtÿuÿvÿuÿuÿsÿnÿjÿaÿ[ÿYÿ\ÿ`ÿfÿkÿF~CnA~=o:8q67q43r1€1r1.s/0t//u..w.€.x.€0x11y3€4x4€5w6€8w:€:u9€9u;;s==s>>r??q??q@BpAAp@@pBBpAAq@@s??s??t>>t==u==v==v==w<ÿ>ÿ?ÿ?ÿ?ÿ@ÿ@ÿBÿAÿAÿBÿBÿBÿBÿAÿAÿ@ÿ@ÿ?ÿ?ÿ?ÿ?ÿ?ÿ?ÿ=ÿ=ÿ=ÿ=ÿ=ÿ=ÿ=ÿ=ÿ<ÿ<ÿ;ÿ;ÿ;ÿ;ÿ;ÿ;ÿ:ÿ9ÿ9ÿ8ÿ9ÿ9ÿ8ÿ8ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ4ÿ4ÿ1ÿ1ÿ1ÿ/ÿ-ÿ-ÿ-ÿ,ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ*ÿ)ÿ*ÿ*ÿ)ÿ*ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ'ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ(ÿ(ÿ'ÿ(ÿ*ÿ*ÿ+ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ/ÿ.ÿ.ÿ/ÿ/ÿ/ÿ0ÿ0ÿ1ÿ2ÿ6ÿ9ÿ>ÿDÿIÿNÿSÿWÿ[ÿ^ÿ^ÿbÿeÿdÿbÿ`ÿ_ÿ]ÿ[ÿ\ÿ\ÿ]ÿ]ÿ_ÿ`ÿ`ÿ`ÿ]ÿ[ÿVÿOÿFÿ>ÿ6ÿ.ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ(ÿ+ÿ2ÿ8ÿBÿOÿ\ÿiÿqÿvÿyÿ{ÿzÿxÿxÿxÿzÿvÿoÿgÿXÿFÿ8ÿ/ÿ-ÿ-ÿ/ÿ3ÿ8ÿ;ÿ>ÿ@ÿDÿGÿJÿMÿTÿ\ÿdÿlÿvÿ}ÿ€ÿÿÿÿÿÿ‚ÿƒÿÿ}ÿvÿlÿ]ÿJÿ8ÿ+ÿ&ÿ#ÿ"ÿ"ÿ ÿ3ÿLÿ=ÿ"ÿ&ÿ/ÿ4ÿ8ÿDÿRÿaÿnÿtÿwÿxÿZÿ=ÿ$ÿ2ÿ1ÿ?ÿMÿUÿZÿ[ÿ[ÿ_ÿ`ÿaÿ`ÿ]ÿYÿVÿQÿLÿEÿ?ÿ8ÿ0ÿ,ÿ-ÿ/ÿ2ÿ0ÿ.ÿ?ÿMÿQÿRÿTÿMÿIÿIÿ6ÿ5ÿ:ÿAÿMÿZÿcÿmÿuÿwÿwÿwÿvÿuÿwÿwÿtÿsÿpÿiÿ`ÿRÿBÿ9ÿ1ÿ-ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ,ÿ,ÿ-ÿ0ÿ1ÿ3ÿ4ÿ6ÿ8ÿ:ÿ;ÿ?ÿAÿCÿEÿJÿMÿOÿRÿWÿ[ÿ_ÿcÿgÿlÿqÿuÿvÿwÿyÿ{ÿ~ÿƒÿŠÿŽÿ“ÿ–ÿ—ÿ˜ÿ—ÿ–ÿ“ÿ’ÿÿÿŠÿ‡ÿ…ÿÿ}ÿyÿtÿnÿjÿgÿcÿ`ÿ\ÿ[ÿ\ÿ^ÿ`ÿ`ÿaÿcÿfÿfÿfÿgÿjÿkÿlÿnÿpÿpÿsÿvÿuÿsÿqÿqÿoÿoÿoÿlÿlÿjÿfÿdÿ`ÿ]ÿ[ÿWÿVÿUÿSÿRÿQÿPÿQÿRÿSÿ[ÿaÿiÿoÿuÿyÿ|ÿyÿwÿxÿxÿzÿ~ÿ‚ÿ‡ÿ‰ÿŒÿÿÿŒÿŠÿ‰ÿ‰ÿˆÿ‰ÿ‡ÿ†ÿƒÿ}ÿvÿpÿgÿaÿ_ÿ^ÿ]ÿ\ÿZÿZÿ\ÿ_ÿdÿhÿjÿmÿoÿpÿpÿpÿnÿnÿnÿoÿrÿsÿtÿtÿqÿkÿfÿ^ÿVÿPÿNÿLÿKÿMÿRÿXÿbÿkÿtÿ}ÿ„ÿˆÿ‹ÿ‹ÿ‰ÿˆÿ‰ÿ‰ÿ‰ÿŠÿ‰ÿˆÿ‡ÿ‡ÿ…ÿÿ}ÿxÿvÿtÿuÿuÿvÿvÿwÿwÿuÿrÿnÿgÿaÿaÿbÿgÿmÿrÿ@=p;:p8€8q6€5q43r22s00s..u-€-v.€.w.€.w/€1v1€1w2€2w5€6w7€7w:€:v:€:u<s?‚?rA‚ArA‚AqAAqAAqBBq@@r@@r??s??t??t>>u>>v==w==x<;y;;z;;y99z:‚9|9‚8|8‚7z88{6€6{6€6z5€5{5€5{41|1~0|-},|-|,}*{*~*{*~+z++z+}+{+~,{,}+{+,{,,{,,{,€-|-,|,,},,},,},,},,},€)})€(})€)})€(}((}('}'&}'(~(€(~(&}&&}&&|&&|&%~%‚&~&‚&~&&~&'~''~''}'(}((})*}*+|+ƒ+|+ƒ,{,ƒ,{,ƒ,z,‚-z-‚-z-‚.z/‚/y/ƒ0y0ƒ.y.ƒ/x/‚/x/‚2z0€4{8€;A{GKtO‚UnW„\j]ƒ_ibbja€^k\}\n[}[oZ~[p]€]o`‚apa‚`o^‚YnTLrD|;v3{,|*{*‚*{*ƒ*{*ƒ){)ƒ)|(ƒ'|&ƒ'|'ƒ'|'ƒ(|(ƒ'|'ƒ'}'ƒ&}&ƒ&}&‚'}*‚/~5~?~NyXero€vmxyh{‚zdw‚xdw‚tgoƒil[‚Mo>4u1€1w2€6w9>v@AsEƒGqJ‚MoSZobhms€zl€€k‚i‚€ƒeƒ‚„h„ƒkxƒrneƒSo?‚0s(€#w$~.}+|1€0{&€$|&2|2|6~CvQ`rmtpy‚|li†IkJ‡[j[‡YfX…XgY„\k_ƒ`l_ƒ_n]YqU€NuJ}D{={7€0z-ƒ-y,„.z-†-x.ƒ^tS{RmOvNqOuRzIz4|7ÿ?ÿ?ÿ?ÿAÿAÿAÿAÿBÿBÿAÿAÿBÿBÿAÿ@ÿ@ÿ@ÿ?ÿ?ÿ?ÿ?ÿ?ÿ?ÿ>ÿ>ÿ>ÿ>ÿ=ÿ=ÿ=ÿ=ÿ;ÿ;ÿ;ÿ;ÿ;ÿ;ÿ:ÿ:ÿ:ÿ9ÿ9ÿ8ÿ7ÿ8ÿ8ÿ8ÿ8ÿ7ÿ7ÿ7ÿ5ÿ5ÿ4ÿ3ÿ5ÿ3ÿ2ÿ2ÿ/ÿ.ÿ-ÿ,ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ+ÿ+ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ)ÿ)ÿ)ÿ+ÿ*ÿ*ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ(ÿ(ÿ(ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ/ÿ/ÿ/ÿ0ÿ0ÿ/ÿ/ÿ0ÿ0ÿ/ÿ0ÿ3ÿ3ÿ6ÿ:ÿ?ÿDÿIÿMÿSÿVÿZÿ\ÿ]ÿ_ÿaÿaÿ^ÿ\ÿZÿZÿYÿYÿXÿZÿ]ÿ_ÿbÿbÿbÿ`ÿ]ÿXÿRÿIÿ?ÿ6ÿ1ÿ,ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ*ÿ)ÿ)ÿ'ÿ(ÿ(ÿ(ÿ'ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ'ÿ*ÿ-ÿ3ÿ;ÿHÿTÿ_ÿmÿsÿxÿyÿyÿxÿwÿwÿwÿuÿrÿjÿaÿQÿDÿ7ÿ3ÿ3ÿ7ÿ:ÿ<ÿ?ÿAÿCÿBÿEÿHÿLÿOÿVÿ`ÿgÿpÿzÿÿÿÿÿÿÿ€ÿ€ÿƒÿƒÿ}ÿtÿkÿYÿFÿ5ÿ+ÿ&ÿ&ÿ=ÿ6ÿ&ÿ%ÿ%ÿ!ÿ%ÿ+ÿ/ÿ6ÿCÿQÿ`ÿmÿtÿxÿzÿzÿxÿrÿmÿeÿ]ÿ[ÿZÿ\ÿ^ÿ_ÿ`ÿ_ÿ\ÿYÿTÿPÿJÿDÿ=ÿ5ÿ.ÿ,ÿ+ÿ,ÿ-ÿ-ÿ-ÿ-ÿ7ÿuÿXÿQÿNÿLÿJÿSÿ?ÿ/ÿ6ÿ;ÿDÿQÿ[ÿcÿmÿuÿwÿwÿuÿvÿvÿvÿvÿwÿuÿpÿjÿ_ÿRÿDÿ:ÿ3ÿ.ÿ+ÿ+ÿ*ÿ)ÿ*ÿ*ÿ+ÿ*ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ/ÿ0ÿ2ÿ4ÿ6ÿ8ÿ9ÿ<ÿ>ÿ@ÿDÿGÿJÿKÿNÿRÿUÿWÿ[ÿ_ÿcÿgÿkÿnÿsÿtÿvÿxÿ{ÿ~ÿ„ÿ‰ÿÿ”ÿ™ÿ™ÿšÿ—ÿ–ÿ”ÿ“ÿ‘ÿŒÿŠÿˆÿ†ÿÿ}ÿuÿoÿiÿdÿ^ÿ[ÿYÿYÿZÿZÿ[ÿ^ÿ_ÿ_ÿaÿcÿdÿeÿfÿgÿiÿkÿnÿoÿqÿqÿtÿtÿrÿqÿqÿpÿoÿoÿnÿnÿjÿhÿfÿbÿ]ÿXÿWÿTÿSÿRÿOÿNÿNÿMÿLÿNÿQÿWÿaÿhÿpÿwÿzÿzÿzÿzÿzÿ{ÿ{ÿÿ‚ÿ‡ÿ‹ÿÿÿÿÿ‰ÿ‰ÿ‰ÿ‰ÿ‰ÿˆÿ…ÿ€ÿzÿtÿlÿbÿ^ÿ[ÿZÿYÿYÿZÿZÿ]ÿaÿfÿjÿjÿmÿqÿpÿqÿoÿnÿnÿmÿoÿqÿrÿsÿsÿoÿlÿdÿ]ÿXÿSÿPÿPÿSÿUÿZÿaÿkÿsÿ{ÿ‚ÿ‰ÿŠÿŠÿŠÿ‰ÿ‰ÿ‰ÿ‰ÿ‰ÿˆÿ†ÿ…ÿ„ÿƒÿ€ÿÿ|ÿyÿvÿuÿuÿuÿvÿvÿwÿwÿwÿvÿqÿjÿhÿgÿiÿkÿoÿsÿ;€:p8€7p6€6o4€4o32q2€0r00r.€-t.€.v.€.w.€.w/€1v2€2v3€3v56s6€6s9€9t:€;t<‚>s?‚@rB‚BrB‚BqBBqBBqBBq@@r??r?>s>>t??t==u>>u==v<~CGwK‚OpUƒXjZƒ[i_‚`h_^i]€ZlY€YoY€YpZ€^racqc‚dqb‚`p\VnPEr<|6w/{+|*{*‚*{*ƒ*|*ƒ)|)ƒ*|*ƒ)|)ƒ)|)ƒ(|'ƒ(|(ƒ'|'ƒ&}&ƒ'}'ƒ'}'‚'}(‚+~0€9~CzP]ui€royziywewvcw‚xevƒmkeƒWoH‚=r67s9;t>AsCCqDƒFpJ‚MoMSo^fml€un|€€l€j€€f€e‚‚ƒi€ƒznoƒ^pM‚zDP{[~f}owu€wpuvkwwkv‚vlw‚umoƒho`ƒQqE:w3~.|-{,~+z+,{,~,{,~-{,~+{,}-{.}1z2|3y5z6x9y;u>x@tDvGqItKnNsQkTpWj[o^iboekilmpnjrvsiv|xh{~i„‰n‘}—ušzš|˜x—€“w‘’v‹v‡}…v‚zxyvt{mtd|_vZ|XxWzWzWyYzZwZv]t^s_rapdqemfpgljqmhoupgpyrgs~shs€qipoip€oin€mkhhje~aj[|VmTvRnQtPqNsMsLrIsKrMtOpVv_nhzppt{yvxyx{wux}xsz}t„|ˆv|Žw~ŒrŒpŠŠo‰€ˆrˆ€„r€€ypr‚iga‚]dZƒXdXƒXbYZ`]a`f€i`mpar€oco€nenohnqku}vnt|soozjncx]mXtTpRqSsUnYw]ogumrut~w…s‰}Šp‰‰p‰€‰r‰€‰s‰†t„ƒu‚}v~|~v||ytx}uqw~uov€vmwwkw€tjrohighinkq}ul:ÿ9ÿ8ÿ7ÿ6ÿ6ÿ5ÿ4ÿ3ÿ2ÿ1ÿ1ÿ1ÿ1ÿ.ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ1ÿ2ÿ2ÿ3ÿ3ÿ5ÿ6ÿ6ÿ6ÿ9ÿ9ÿ:ÿ;ÿ<ÿ<ÿ=ÿ=ÿ>ÿ>ÿ?ÿ@ÿBÿBÿBÿBÿBÿBÿBÿBÿBÿBÿ@ÿ@ÿ?ÿ?ÿ?ÿ>ÿ>ÿ>ÿ?ÿ?ÿ=ÿ=ÿ<ÿ<ÿ=ÿ=ÿ=ÿ;ÿ:ÿ;ÿ;ÿ:ÿ:ÿ:ÿ9ÿ9ÿ9ÿ9ÿ8ÿ8ÿ9ÿ:ÿ=ÿ>ÿ@ÿ@ÿ?ÿ?ÿ@ÿAÿBÿBÿBÿAÿ>ÿ>ÿ<ÿ8ÿ3ÿ.ÿ+ÿ*ÿ*ÿ*ÿ.ÿ/ÿ0ÿ1ÿ1ÿ1ÿ2ÿ1ÿ1ÿ1ÿ0ÿ/ÿ.ÿ.ÿ.ÿ-ÿ-ÿ+ÿ,ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ*ÿ)ÿ)ÿ*ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ1ÿ3ÿ5ÿ8ÿ<ÿBÿFÿKÿOÿRÿWÿXÿ\ÿ^ÿ^ÿ^ÿ_ÿ]ÿZÿWÿVÿVÿVÿVÿYÿ\ÿaÿcÿcÿdÿbÿaÿ\ÿVÿMÿCÿ;ÿ4ÿ-ÿ+ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ'ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ)ÿ-ÿ4ÿ>ÿLÿXÿgÿpÿuÿxÿxÿwÿvÿuÿvÿwÿtÿqÿhÿZÿPÿBÿ:ÿ:ÿ;ÿ=ÿ?ÿAÿDÿCÿEÿFÿJÿJÿLÿPÿZÿbÿjÿrÿzÿ~ÿ€ÿÿ€ÿ€ÿ€ÿÿ‚ÿ‚ÿÿzÿqÿgÿVÿCÿ4ÿ(ÿ&ÿ#ÿ#ÿ$ÿ2ÿ5ÿ!ÿ#ÿ)ÿ-ÿ5ÿAÿNÿ^ÿlÿuÿzÿ}ÿÿ{ÿxÿpÿlÿfÿ`ÿ^ÿ_ÿ`ÿ_ÿ]ÿYÿRÿNÿIÿBÿ:ÿ5ÿ/ÿ,ÿ+ÿ*ÿ+ÿ,ÿ-ÿ-ÿ.ÿ.ÿ2ÿQÿbÿSÿSÿSÿOÿbÿgÿBÿ7ÿ<ÿFÿSÿ^ÿgÿpÿuÿwÿvÿuÿvÿvÿvÿvÿvÿsÿpÿiÿ]ÿQÿEÿ:ÿ3ÿ.ÿ.ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ,ÿ-ÿ.ÿ/ÿ1ÿ4ÿ6ÿ8ÿ8ÿ:ÿ<ÿ>ÿAÿDÿHÿJÿMÿPÿSÿVÿYÿ\ÿ_ÿcÿdÿhÿmÿqÿsÿtÿwÿwÿ{ÿÿ…ÿŠÿÿ–ÿ—ÿ—ÿ•ÿ”ÿ‘ÿÿŽÿŠÿ‡ÿ†ÿƒÿ€ÿ|ÿwÿmÿgÿ`ÿ[ÿWÿUÿUÿVÿWÿYÿZÿZÿ\ÿ]ÿ_ÿaÿdÿeÿgÿgÿkÿmÿoÿpÿpÿrÿsÿsÿsÿrÿpÿoÿpÿoÿnÿmÿiÿgÿcÿ^ÿYÿUÿRÿPÿNÿMÿLÿKÿKÿIÿGÿIÿMÿTÿ]ÿfÿmÿsÿvÿyÿxÿwÿwÿzÿ|ÿ€ÿ„ÿˆÿŒÿŽÿŽÿÿÿŒÿŠÿŠÿŠÿˆÿˆÿ„ÿÿxÿoÿfÿ^ÿZÿYÿVÿWÿWÿXÿYÿ]ÿaÿhÿjÿmÿmÿqÿpÿoÿnÿoÿoÿpÿqÿuÿvÿtÿsÿoÿjÿbÿ\ÿZÿXÿVÿUÿWÿ]ÿbÿiÿqÿyÿÿ†ÿŠÿ‰ÿ‰ÿ‰ÿ‰ÿ‰ÿ‰ÿ‰ÿ‰ÿ†ÿƒÿ‚ÿÿ~ÿ}ÿ}ÿ|ÿyÿxÿuÿsÿuÿvÿvÿwÿwÿwÿtÿrÿoÿjÿhÿiÿoÿqÿuÿ9€8n7€6o65o44o3€2o1€1p1€0q.€-s.€.u.€.v.€/w/€1u2€3t3€4t56u77u::u:;u==t>>t>‚>s@‚AsBBrCCrCCqBBqBArA@r@@s?>s??t??t??u<=v==v<EzKƒOsTƒWlX„[h[„\g^‚_h_‚\j[ZkXWoYXo\€`ocdoeƒfoc‚^o[TqK}Au9|2|,{*€+{+‚+{+‚+|+ƒ+|+ƒ+|*ƒ)|)ƒ)|)ƒ)|)ƒ)|)ƒ'|'ƒ(}(ƒ(}(ƒ(}(‚&}&‚)},€2}:|GUvb€mqt€vjxxdu€uau€xdv„sik„bmSƒHo=ƒ;p=‚?qA‚CqD‚EpE„FpFƒJpM‚PpW]nf}npw~~o‚k€€€h€€g‚iƒ‚~nw‚mp[ƒIp7‚+t#~#z!#~*-}##~'.|5@xO~\uitqz€}m~‚{jy‚uim„fic„`i_ƒ]m[WnQLrI~Bw9{2}-{+‚,z+ƒ+z,…,y-†/x/†1w1‡5wFƒWpQzKnNvWuVwt@xCqFvIoLuOmRqTkVp[i^nafcogiklporjvvwhx|yh|€k„~‰t{”{–x•€”v’‚tŒ„‹sˆ„s‚€t|{yxrwkzdvZ{TwUzU{TzT|UyVzXvZxZt[t^r_pcqengphllqmjntoioyqhssir€qip€ogq€piq€mij€eia[lXzTlQvPnMtKoHsGqEsDsErFuKqQxZqfylrs|wxxyx|yuw|yt||€uƒ|‡w‹}ŒvŽ~rŒ‹rŠŠrŠ‰sˆ„s~wmn‚fe\ƒX`W‚WbVWbX€[a^€bag€j_lm`q€rco€mfn€nho~pkr|tnszrnoxjnbv\nYrYqWoWu[k`vfolttu|r‚y‡q‰~‰qŠˆq‰€ˆsˆ€‰t‡ƒu€~v}~|x{z|w||ytv~sou€wmv€vlw€xjw€virohmkhlpjs~wk8ÿ7ÿ6ÿ6ÿ5ÿ5ÿ3ÿ3ÿ3ÿ2ÿ1ÿ1ÿ0ÿ.ÿ.ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ1ÿ2ÿ3ÿ4ÿ5ÿ5ÿ6ÿ7ÿ7ÿ9ÿ:ÿ;ÿ<ÿ=ÿ=ÿ>ÿ>ÿ?ÿ?ÿ@ÿAÿBÿBÿCÿCÿCÿCÿBÿBÿBÿAÿ@ÿ@ÿ@ÿ@ÿ?ÿ>ÿ>ÿ>ÿ=ÿ=ÿ=ÿ=ÿ=ÿ=ÿ=ÿ=ÿ<ÿ;ÿ:ÿ;ÿ;ÿ:ÿ:ÿ9ÿ9ÿ9ÿ8ÿ7ÿ9ÿ:ÿ=ÿAÿFÿJÿMÿOÿOÿOÿPÿRÿSÿSÿRÿRÿSÿRÿOÿIÿCÿ9ÿ3ÿ1ÿ2ÿ5ÿ8ÿ;ÿ>ÿ@ÿAÿDÿDÿDÿEÿCÿBÿ?ÿ<ÿ:ÿ9ÿ7ÿ3ÿ0ÿ/ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ1ÿ3ÿ5ÿ7ÿ<ÿ@ÿEÿKÿOÿSÿVÿ[ÿ\ÿ\ÿ]ÿ_ÿ^ÿ]ÿ`ÿ^ÿ\ÿ[ÿZÿZÿZÿ]ÿ`ÿaÿcÿdÿeÿdÿcÿ]ÿYÿPÿGÿ>ÿ7ÿ0ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ&ÿ&ÿ'ÿ+ÿ0ÿ7ÿBÿPÿ\ÿiÿqÿuÿwÿxÿvÿtÿtÿvÿvÿsÿmÿgÿYÿNÿBÿ=ÿ?ÿ@ÿBÿDÿDÿEÿFÿGÿFÿHÿKÿOÿSÿYÿaÿhÿrÿ{ÿ€ÿÿ€ÿÿ€ÿ€ÿ‚ÿƒÿƒÿ€ÿ{ÿsÿcÿPÿ?ÿ0ÿ'ÿ$ÿ"ÿ!ÿ#ÿ"ÿ"ÿ#ÿ'ÿ-ÿ2ÿ@ÿMÿZÿiÿtÿxÿ~ÿ~ÿ{ÿyÿuÿnÿgÿbÿ_ÿ]ÿ[ÿWÿRÿMÿFÿAÿ:ÿ2ÿ-ÿ+ÿ*ÿ*ÿ*ÿ+ÿ,ÿ-ÿ/ÿ0ÿ0ÿ2ÿ4ÿ2ÿLÿWÿKÿIÿIÿ[ÿNÿ6ÿ8ÿ@ÿJÿUÿ`ÿjÿtÿxÿxÿwÿwÿwÿwÿwÿwÿwÿtÿoÿjÿ^ÿOÿEÿ<ÿ5ÿ0ÿ.ÿ.ÿ.ÿ.ÿ-ÿ,ÿ,ÿ,ÿ.ÿ.ÿ-ÿ/ÿ1ÿ2ÿ3ÿ3ÿ5ÿ:ÿ;ÿ=ÿ>ÿAÿDÿHÿJÿMÿPÿRÿVÿXÿ[ÿ^ÿbÿeÿiÿmÿqÿuÿvÿxÿ{ÿ|ÿ}ÿÿ„ÿ‰ÿŽÿ“ÿ“ÿ‘ÿÿÿÿ‹ÿˆÿ†ÿ‚ÿ€ÿ|ÿwÿtÿnÿgÿ`ÿWÿSÿUÿRÿSÿSÿRÿTÿVÿYÿZÿ[ÿ^ÿ_ÿaÿdÿgÿjÿlÿmÿoÿoÿpÿrÿsÿsÿrÿqÿpÿoÿpÿoÿmÿkÿgÿbÿ_ÿYÿUÿQÿOÿMÿKÿHÿFÿDÿCÿAÿAÿCÿHÿOÿYÿcÿlÿrÿwÿxÿwÿwÿxÿ{ÿ}ÿÿ„ÿˆÿ‹ÿŒÿÿŒÿ‹ÿŠÿŠÿŠÿŠÿ‰ÿˆÿ„ÿ~ÿwÿnÿfÿ]ÿYÿYÿXÿVÿWÿYÿ\ÿ_ÿbÿhÿkÿnÿpÿoÿoÿpÿnÿoÿoÿrÿsÿtÿuÿtÿsÿpÿiÿbÿ\ÿYÿYÿYÿ[ÿ]ÿcÿiÿpÿwÿÿ…ÿˆÿ‰ÿˆÿ‰ÿˆÿˆÿ‰ÿ‰ÿˆÿ…ÿ€ÿ~ÿ|ÿ{ÿxÿ{ÿ}ÿ}ÿyÿwÿuÿvÿwÿvÿwÿwÿxÿxÿwÿtÿqÿmÿkÿkÿnÿsÿwÿ66n66o55p33p3€2p1€1r0€/s-€,t..u//v//u02t3€4t6€7t67u78u::u:‚?q?‚@qCCrCCrCCrBBrB@s@>s>?s?>t==t==u==v==v==w<mA‡CiIˆKfM‰PcSŠTbR‰OdN‡JeJ‡IhE†@l=ƒ8o1€/s-~,w-~+z+}+|*},~*}*~)})~*}))}((}''}''~'€&~&&}%€%}%€%~%%~%$~$$~$$~$$~$%~%%~%%~%%~%'~'€'|'‚(|)‚*|*‚+|+ƒ+|+ƒ-{-ƒ-{-ƒ-y.ƒ.y/ƒ/y/ƒ/y/ƒ/y1„/y1ƒ1y2ƒ2{4‚8<}?‚DyI‚PqR„VjZ„[g]…^ea…bccƒcc`‚_d^\i\\m]`pb€apadpe‚coa€]pV€MrD|;w4{.|+{+€*{*‚+{+‚+|+ƒ+|,ƒ+|+ƒ*|*ƒ*|*ƒ)|+ƒ)|)ƒ(|(ƒ'}'ƒ'}'ƒ'}'‚&}&‚(})/}5~?}LxX~fro€rkuueu‚uct‚vcwƒtepƒki_ƒSlHƒAm@‚BpC‚EpFFpF‚GqGƒGqI‚MqMRq[}drm|vr}}m€hf‚‚„iƒ‚‚l}‚tpiƒXqEƒ5s($x"$|#~$}#}#}&,{4@wMZthsqz}m|‚|ly‚ujlƒejcƒ^j\‚WnQLrF~Aw:|2|-{*€*{*‚*z+…+z,†-y.†/x/†.w.‡3wY…TvP}IsJuMuVv=y:}AyKV}_|l~wvyypxuluvlx‚xlv‚smn‚ho]‚OqD€=v6}2}/{/.z.€.{.€-{,€.{.-{/~0{2~3z5}6x8{:vuÿ?ÿ?ÿ@ÿBÿBÿBÿBÿAÿAÿAÿAÿAÿAÿ@ÿ?ÿ=ÿ=ÿ?ÿ>ÿ=ÿ=ÿ=ÿ=ÿ=ÿ=ÿ=ÿ=ÿ<ÿ<ÿ;ÿ;ÿ:ÿ:ÿ:ÿ9ÿ9ÿ9ÿ7ÿ9ÿ:ÿ:ÿ<ÿ=ÿAÿGÿKÿPÿSÿXÿZÿZÿ[ÿ\ÿ^ÿ^ÿ_ÿ_ÿ_ÿaÿ_ÿ\ÿUÿMÿEÿCÿDÿIÿMÿPÿVÿXÿ[ÿ^ÿ_ÿ`ÿbÿ_ÿ_ÿ]ÿYÿVÿRÿNÿGÿBÿ=ÿ6ÿ2ÿ/ÿ.ÿ-ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ)ÿ)ÿ*ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ-ÿ-ÿ-ÿ-ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ/ÿ0ÿ0ÿ0ÿ1ÿ1ÿ2ÿ5ÿ8ÿ=ÿAÿDÿIÿNÿRÿWÿ[ÿ]ÿ^ÿaÿbÿdÿgÿfÿfÿdÿbÿ_ÿ^ÿ^ÿ^ÿ_ÿaÿcÿcÿdÿdÿeÿcÿ_ÿ\ÿRÿJÿAÿ9ÿ1ÿ+ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ)ÿ,ÿ2ÿ;ÿFÿSÿ`ÿkÿrÿuÿwÿuÿuÿuÿuÿwÿvÿqÿnÿgÿYÿNÿGÿCÿBÿDÿFÿGÿFÿGÿFÿFÿFÿHÿIÿIÿLÿTÿ^ÿhÿqÿyÿ~ÿ‚ÿÿÿÿÿ‚ÿƒÿ‚ÿ€ÿxÿoÿ`ÿMÿ<ÿ.ÿ'ÿ#ÿ%ÿ#ÿ$ÿ$ÿ$ÿ(ÿ/ÿ9ÿAÿNÿ\ÿgÿrÿwÿzÿ{ÿ{ÿxÿtÿlÿeÿ_ÿ\ÿUÿQÿKÿCÿ?ÿ8ÿ2ÿ,ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ,ÿ,ÿ-ÿ.ÿ/ÿ/ÿ.ÿ.ÿ2ÿLÿdÿXÿJÿLÿLÿXÿ=ÿ9ÿAÿKÿWÿbÿmÿvÿyÿyÿxÿwÿuÿvÿxÿxÿvÿsÿmÿgÿ]ÿOÿCÿ<ÿ6ÿ2ÿ2ÿ/ÿ1ÿ1ÿ0ÿ/ÿ.ÿ,ÿ.ÿ.ÿ/ÿ1ÿ0ÿ2ÿ3ÿ5ÿ6ÿ8ÿ:ÿ<ÿ@ÿCÿFÿIÿKÿNÿRÿSÿVÿYÿ]ÿ_ÿdÿfÿkÿoÿqÿuÿyÿ|ÿÿ€ÿƒÿƒÿˆÿ‹ÿÿÿÿŽÿŠÿ‰ÿ‡ÿ†ÿ‚ÿÿ|ÿxÿtÿpÿiÿcÿ]ÿWÿSÿQÿOÿOÿOÿPÿPÿRÿUÿVÿYÿ[ÿ\ÿ^ÿ`ÿcÿhÿjÿlÿnÿpÿpÿqÿrÿrÿqÿqÿqÿqÿqÿpÿoÿmÿiÿeÿ_ÿZÿTÿNÿMÿKÿIÿFÿCÿ@ÿ?ÿ;ÿ:ÿ:ÿ<ÿAÿJÿUÿ_ÿhÿoÿvÿwÿwÿuÿwÿyÿ~ÿƒÿ…ÿ‰ÿŠÿŒÿ‹ÿŠÿˆÿˆÿ‡ÿ‡ÿˆÿˆÿ‡ÿƒÿÿxÿqÿgÿ`ÿZÿZÿZÿYÿ\ÿ[ÿ_ÿcÿfÿhÿkÿoÿoÿpÿpÿoÿoÿoÿqÿrÿsÿuÿrÿtÿrÿmÿhÿbÿ]ÿ[ÿZÿ[ÿ^ÿdÿjÿqÿxÿÿ„ÿ‡ÿ‰ÿŠÿˆÿˆÿˆÿˆÿ‰ÿˆÿ‡ÿ‚ÿ~ÿzÿxÿtÿtÿxÿ{ÿ{ÿxÿyÿvÿwÿwÿyÿyÿyÿyÿxÿwÿtÿqÿmÿkÿnÿpÿrÿuÿ77k44l5€5n4€3o32p11q1€1q0€.q.1s22s11s34s5€7t7€7t78s99s::r;;r==r??r@>r@ArB@rCCrDDsBBsA@s?>s>=s>>t==s==t>€>u>€=v;€;v:€:v:€9x9€9y8€7z8€8{9:y;=xB„GvK‡OrRˆVmZ‡Zl[ˆ\j]ˆ^iaˆdgbˆaeb‡`c]‡WbP†NcO†ScXˆ\b_‡abc‰ebj‰jbh‡g`f‡facˆ_d]‰XeVˆQeI„Ah<‚5o2-u+~+{-~+}+}**}*~)}(}(}(}'}'~'}'~&~&'~'&~%%~%$~$€$~$€#~##~#$}$€$}$€$~$%~%%~%%~%%}&'}''|'‚)|)‚+{+‚,{,‚-z-‚.z.‚/y/ƒ/y/ƒ/x0ƒ0z0ƒ/y0ƒ0{1‚2}6€9€=|BFxJ„NsT„VmZ…]f]…acc…e`h„j`k„i`e‚e`c`f__o`aqc€erfdqc‚ap[€WqM}Dw>{7|/{+,{,+{+ƒ,{,ƒ,|,ƒ+|+ƒ,|,ƒ+|+ƒ*|*ƒ*|*ƒ+|+ƒ)|(ƒ)})ƒ(}(ƒ(}(ƒ(}(ƒ&}'‚*}/7~DxQ]ri€rnuufu‚uas‚tcx‚wft‚pkkƒ_jTƒJjCƒDkDƒGmEƒGoFƒGpG‚FrD€FsEFsM}Wtc|nsx~~p‚€‚j‚€‚g‚‚hƒ‚ƒkƒ{nsƒdpSƒAq1€'t$$z%~&|&€'{,€5y8DtN‚Zpgrqv‚wly‚yku‚rkm‚el]‚WmQJqE~?w8|0{+{)~)z)+z+‚*y+„-x-….x.†/w/†0w0†1wC†fxV{RtNtPsVu8x=|CzM€X|c~nuwx€zpxxlwwlxxkv€tlm‚gn]‚PrD€ÿ=ÿ>ÿ=ÿ=ÿ=ÿ<ÿ<ÿ<ÿ<ÿ=ÿ=ÿ=ÿ<ÿ;ÿ;ÿ:ÿ:ÿ9ÿ9ÿ9ÿ8ÿ7ÿ7ÿ8ÿ7ÿ7ÿ9ÿ<ÿ>ÿ@ÿDÿIÿLÿOÿRÿUÿXÿWÿXÿ[ÿ\ÿ_ÿbÿbÿdÿfÿdÿcÿ`ÿ[ÿWÿZÿ^ÿ^ÿcÿeÿgÿgÿiÿkÿkÿmÿiÿkÿkÿiÿgÿeÿcÿ_ÿYÿUÿOÿHÿ@ÿ9ÿ2ÿ-ÿ,ÿ,ÿ*ÿ*ÿ)ÿ*ÿ*ÿ)ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ'ÿ'ÿ'ÿ'ÿ)ÿ)ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ1ÿ1ÿ2ÿ4ÿ8ÿ;ÿ?ÿDÿHÿLÿPÿTÿVÿ^ÿ_ÿbÿdÿeÿiÿjÿkÿmÿlÿlÿiÿfÿeÿcÿaÿ`ÿbÿbÿdÿdÿeÿdÿdÿ^ÿYÿSÿJÿAÿ9ÿ2ÿ.ÿ+ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ&ÿ)ÿ-ÿ4ÿ@ÿMÿYÿdÿnÿsÿvÿuÿuÿtÿuÿwÿxÿwÿsÿoÿfÿYÿOÿHÿDÿDÿFÿFÿFÿFÿGÿFÿDÿAÿ?ÿ>ÿBÿFÿPÿ]ÿjÿtÿ|ÿ‚ÿƒÿ‚ÿ‚ÿ‚ÿ‚ÿƒÿƒÿÿ|ÿvÿjÿZÿFÿ6ÿ+ÿ%ÿ%ÿ&ÿ&ÿ)ÿ.ÿ1ÿ7ÿ>ÿFÿNÿ[ÿgÿrÿvÿwÿxÿxÿuÿrÿmÿcÿXÿRÿKÿDÿ>ÿ8ÿ0ÿ)ÿ)ÿ*ÿ)ÿ)ÿ+ÿ+ÿ+ÿ,ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ0ÿ?ÿbÿaÿQÿLÿOÿLÿEÿ<ÿBÿNÿYÿeÿoÿvÿyÿzÿxÿxÿwÿwÿxÿxÿvÿtÿmÿgÿ]ÿPÿDÿ<ÿ8ÿ4ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ-ÿ.ÿ/ÿ/ÿ0ÿ2ÿ1ÿ3ÿ5ÿ7ÿ9ÿ:ÿ=ÿ@ÿCÿGÿJÿMÿPÿSÿVÿXÿZÿ^ÿaÿcÿgÿlÿpÿrÿvÿ{ÿÿ‚ÿ†ÿ…ÿˆÿŠÿŒÿŒÿÿ‹ÿˆÿ†ÿ…ÿ‚ÿÿ|ÿyÿtÿqÿkÿcÿ^ÿWÿSÿQÿPÿOÿOÿPÿRÿSÿUÿUÿWÿYÿZÿ[ÿ^ÿaÿbÿdÿhÿjÿmÿoÿoÿoÿpÿqÿtÿsÿqÿqÿqÿrÿpÿoÿlÿiÿcÿ\ÿWÿPÿLÿFÿCÿ@ÿ=ÿ<ÿ9ÿ8ÿ6ÿ5ÿ6ÿ8ÿ>ÿJÿUÿ`ÿiÿpÿuÿwÿwÿuÿxÿyÿ€ÿ„ÿ†ÿ‰ÿ‹ÿ‹ÿŠÿˆÿˆÿ‡ÿ…ÿ†ÿ‡ÿ†ÿ†ÿƒÿ€ÿyÿpÿfÿbÿ^ÿ\ÿ\ÿ\ÿ]ÿ^ÿ_ÿbÿfÿiÿnÿpÿqÿrÿrÿqÿrÿrÿtÿuÿvÿvÿvÿtÿqÿkÿeÿaÿ]ÿ\ÿ]ÿ_ÿbÿhÿoÿwÿ~ÿ„ÿ‰ÿŠÿŠÿ‰ÿˆÿˆÿ‰ÿŠÿŠÿ‰ÿ‡ÿ€ÿ|ÿxÿsÿqÿvÿyÿ{ÿzÿzÿxÿwÿwÿvÿxÿxÿxÿxÿxÿwÿuÿrÿoÿmÿmÿnÿqÿsÿ66k54l4€4m3€2n10p12q11q21q11s33s34s44s6€5r7€7r89s9:s99r::r<;r=?r??r@Ar@ArAAr@@r>@s@@s?>s=t==v<;u;~{AuCzHrKxLoNvSlUtWiZr^fbpcegqmfqotlxm~u‚lƒ|†k‡~‰qŠ~‹w‹}Šz‰{†}„zƒ€x}yxtqzk|f{_yX}TuR~OuO€OuR€RsUXtX~ZsZ|]r^{_pbycpexgojxklmynho|pgq~rft€shs€rjr€sip€nin€iid]hVQhK~DkA{>m;y:o7x6p2w2q4v8s?uJwVt`zkvqyvyywx{vsx{{t{„v‡{Šw‹|‹uˆ‡q‡€†q…€†r‡‰r‡„o€xho‚g`bƒ`]^‚]__`]`c^e€h`j€ncq€rerrgs€siu€vluxov}vnsyrmivcl^q]m\n^qbkfrlksryr€q‡y‰q‹~‹qŠŠq‰€ŠsŠ€‹vŠ€†w€{uv}twrxtyxw|x|zxtx~xow€wlx€xly€ykx€wit€qem€kdm~ofr}th6ÿ6ÿ5ÿ4ÿ4ÿ4ÿ3ÿ2ÿ1ÿ0ÿ1ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ6ÿ5ÿ7ÿ7ÿ9ÿ8ÿ9ÿ:ÿ:ÿ:ÿ;ÿ;ÿ<ÿ=ÿ>ÿ?ÿ?ÿ?ÿ?ÿ@ÿAÿ@ÿAÿAÿ@ÿ@ÿAÿ@ÿ?ÿ?ÿ>ÿ>ÿ=ÿ<ÿ<ÿ=ÿ<ÿ<ÿ=ÿ=ÿ<ÿ<ÿ;ÿ;ÿ:ÿ:ÿ9ÿ9ÿ9ÿ8ÿ9ÿ9ÿ6ÿ6ÿ6ÿ5ÿ4ÿ5ÿ8ÿ9ÿ<ÿ?ÿBÿFÿHÿKÿJÿJÿIÿJÿJÿIÿOÿTÿXÿ]ÿ_ÿeÿhÿhÿhÿeÿdÿeÿgÿfÿeÿeÿdÿdÿdÿcÿeÿeÿfÿgÿgÿfÿgÿiÿhÿhÿhÿeÿbÿ[ÿSÿLÿAÿ6ÿ0ÿ*ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ'ÿ&ÿ'ÿ'ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ'ÿ'ÿ'ÿ'ÿ(ÿ)ÿ*ÿ+ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ4ÿ6ÿ9ÿ;ÿAÿGÿLÿQÿRÿXÿZÿ`ÿdÿgÿiÿmÿoÿqÿsÿsÿwÿuÿrÿoÿmÿjÿfÿeÿcÿaÿcÿdÿcÿbÿbÿ`ÿ[ÿUÿOÿFÿ<ÿ5ÿ1ÿ/ÿ.ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ)ÿ,ÿ1ÿ9ÿDÿRÿ_ÿjÿqÿvÿuÿtÿtÿtÿvÿyÿzÿzÿuÿmÿbÿXÿMÿGÿFÿFÿGÿGÿFÿEÿCÿ?ÿ;ÿ8ÿ7ÿ8ÿ<ÿFÿTÿbÿmÿvÿ‚ÿ„ÿƒÿ‚ÿÿÿ‚ÿ‚ÿ‚ÿÿzÿsÿeÿTÿBÿ2ÿ)ÿ'ÿ)ÿ+ÿ0ÿ4ÿ9ÿ?ÿEÿMÿTÿ]ÿhÿqÿwÿxÿwÿwÿrÿoÿiÿ\ÿPÿHÿ@ÿ8ÿ/ÿ+ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ-ÿ.ÿ.ÿ/ÿ/ÿ1ÿ1ÿ2ÿ2ÿ=ÿgÿhÿRÿPÿIÿZÿaÿJÿMÿ[ÿhÿsÿxÿzÿyÿxÿwÿwÿwÿwÿwÿuÿsÿmÿhÿ\ÿOÿEÿ=ÿ8ÿ5ÿ4ÿ3ÿ4ÿ3ÿ2ÿ2ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ4ÿ6ÿ7ÿ9ÿ;ÿ>ÿAÿCÿHÿKÿMÿOÿRÿTÿXÿYÿ]ÿ^ÿcÿgÿlÿpÿtÿxÿ~ÿ‚ÿ…ÿˆÿˆÿŠÿ‹ÿŒÿ‰ÿ‰ÿˆÿ…ÿƒÿ‚ÿ€ÿ}ÿyÿtÿpÿjÿdÿ]ÿXÿVÿTÿUÿSÿUÿXÿXÿZÿ\ÿ]ÿ_ÿ`ÿ`ÿbÿcÿdÿhÿiÿiÿkÿmÿoÿoÿpÿqÿpÿqÿsÿrÿsÿrÿrÿsÿrÿpÿoÿmÿfÿ`ÿZÿTÿMÿCÿ=ÿ:ÿ9ÿ7ÿ5ÿ3ÿ1ÿ0ÿ2ÿ8ÿ@ÿKÿWÿaÿkÿrÿvÿwÿxÿvÿxÿ{ÿÿ„ÿ‡ÿŠÿ‹ÿ‹ÿ‰ÿˆÿ‡ÿ†ÿ‡ÿˆÿ‡ÿˆÿˆÿ…ÿÿxÿoÿgÿbÿ`ÿ`ÿ_ÿ_ÿ_ÿaÿdÿfÿiÿlÿoÿqÿrÿsÿsÿtÿtÿvÿvÿwÿxÿvÿvÿrÿpÿiÿcÿ^ÿ\ÿ[ÿ^ÿbÿgÿoÿwÿ}ÿ„ÿˆÿ‹ÿŒÿ‹ÿŠÿŠÿ‰ÿŠÿŠÿŠÿ‰ÿ„ÿ|ÿxÿsÿpÿpÿsÿyÿ{ÿ{ÿyÿxÿxÿyÿyÿxÿxÿyÿyÿxÿwÿtÿqÿoÿmÿnÿpÿrÿtÿ5‚5m5‚5n4‚4q4‚2q2‚3p2‚2p3‚3q3‚3q34r55s55t66t66t67t9;s;;s;;r<s>‚>r?‚?r?‚?rABr@@r@€@r@€?s>€>t=€=t=€=u>€=u;€;u;€;u:€:w:€:w:€:y9€9y88x77y55z55{4€4{5€5{7:x9~=r>|>r:}9r9};rCInQWj_‚dgh„igh…ggfƒefebca~\fW{TfT{Xf\~\f]~]f]€`daƒdef†hbj‡kamˆj``†VcLƒAj4€-t*~)x*~*z(}(|)}(|'}&}&}&~&}&~%}%~%~%%~%$$€$$€#~"€#~#€#~#€"~$€#~#$~%&~&&~&&}''}'(|(‚*{*‚*{+ƒ,{.ƒ-z-ƒ-z-ƒ/y/ƒ0z0ƒ0{1ƒ2}47<}?„DwK‡PqT†VlY…_eb…gbj…m_o…q]r†v[u†y\x‚w^tƒqbm€ilednd€dqd€csc€br_~YtV|QwF|Az9{2,{,€,{,ƒ,{,ƒ-z-„-z-„,{,„.{.„-{-„-{-„.{.„-{,ƒ-|-ƒ-|-ƒ+}*ƒ*}(ƒ)|)ƒ)|)ƒ)})‚)},2|7~?}Nw]iqq€tkuuetucvweyƒygwƒpggƒ\iQƒIjGƒGmHƒEpG‚EqC€=s74x4}4{9|A{O|_yk}yv€€„o…ƒj‚€h‚ƒh„‚ƒo~‚wpkƒZpHƒ9s.‚*u-‚0u4ƒ:r>…CmJ„QkX…_khƒqjxƒxky‚vlq‚olh\nO€Dq90x*|)){)‚)z)ƒ*z*ƒ*z*ƒ+y,…-y-….x.….x/…/v1‡0x3†;wR„dtT|TrGsNvNrRzQwZ~gxtzu|€zpzylvvjvvktqlnƒgn\ƒOrF>w:|7|6{6€5y5€5z31{10|0€0|00{1~3z5}6y7}:x=}AuC|GtJzMoOyRlSvVjXtYg`sbfhrjhnqslxo}un„|†nŠ~‹q‰~Šs‰~ˆv„}ƒy‚|z|||y|vzp~hwc€\pX€VpW‚WqV‚Ym[‚]ka‚bkcemegkj€kml~nmn}pkq~ris~sfqqdrscsrerriq€tir€riq€nhj€eg_XgQJgB~ÿ>ÿ?ÿ?ÿ?ÿ?ÿ@ÿAÿ@ÿ@ÿ@ÿ@ÿ?ÿ=ÿ=ÿ=ÿ=ÿ=ÿ<ÿ;ÿ<ÿ<ÿ:ÿ:ÿ;ÿ;ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ9ÿ9ÿ8ÿ7ÿ7ÿ6ÿ6ÿ5ÿ5ÿ4ÿ3ÿ3ÿ3ÿ3ÿ1ÿ4ÿ5ÿ6ÿ6ÿ5ÿ3ÿ3ÿ0ÿ0ÿ0ÿ2ÿ9ÿ@ÿHÿQÿYÿ]ÿbÿeÿgÿeÿfÿbÿ_ÿYÿTÿOÿHÿFÿFÿFÿHÿKÿLÿPÿQÿTÿYÿ\ÿaÿfÿkÿmÿpÿnÿlÿeÿYÿLÿ>ÿ1ÿ,ÿ+ÿ+ÿ+ÿ*ÿ)ÿ)ÿ(ÿ(ÿ'ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ"ÿ"ÿ"ÿ#ÿ#ÿ#ÿ#ÿ#ÿ"ÿ$ÿ$ÿ$ÿ$ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ(ÿ(ÿ(ÿ*ÿ*ÿ*ÿ+ÿ,ÿ.ÿ-ÿ-ÿ-ÿ-ÿ/ÿ/ÿ0ÿ0ÿ1ÿ2ÿ4ÿ9ÿ<ÿAÿFÿKÿPÿUÿYÿ\ÿcÿgÿiÿnÿnÿrÿtÿvÿxÿzÿ{ÿ|ÿ{ÿwÿtÿrÿmÿjÿgÿfÿdÿcÿcÿbÿ`ÿ^ÿYÿVÿOÿHÿ@ÿ8ÿ2ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ,ÿ+ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ,ÿ/ÿ5ÿ>ÿKÿYÿeÿnÿsÿuÿuÿtÿuÿvÿwÿyÿyÿxÿtÿjÿ_ÿUÿMÿHÿFÿGÿFÿEÿCÿ@ÿ:ÿ5ÿ1ÿ0ÿ2ÿ4ÿ=ÿJÿZÿgÿvÿ~ÿ„ÿ†ÿ„ÿÿ€ÿ€ÿÿ‚ÿ‚ÿ€ÿyÿqÿaÿNÿ>ÿ3ÿ0ÿ1ÿ3ÿ9ÿ>ÿBÿHÿNÿSÿZÿ_ÿhÿqÿvÿxÿyÿxÿsÿnÿhÿ\ÿLÿ>ÿ4ÿ-ÿ(ÿ)ÿ*ÿ)ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ+ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ/ÿ/ÿ0ÿ1ÿ4ÿ=ÿTÿNÿQÿOÿKÿQÿLÿBÿMÿ]ÿjÿvÿ|ÿ|ÿ{ÿyÿxÿvÿvÿvÿvÿtÿqÿnÿhÿ_ÿRÿFÿ>ÿ:ÿ7ÿ6ÿ6ÿ5ÿ5ÿ3ÿ3ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ2ÿ4ÿ6ÿ7ÿ:ÿ=ÿ@ÿBÿEÿHÿJÿMÿPÿSÿUÿYÿ[ÿ^ÿ`ÿeÿiÿlÿrÿxÿ~ÿ‚ÿ‡ÿŠÿ‹ÿ‹ÿŠÿ‡ÿ‡ÿ…ÿƒÿ‚ÿÿ€ÿ~ÿ{ÿwÿtÿpÿjÿdÿ^ÿ]ÿXÿZÿ[ÿ[ÿ]ÿ`ÿbÿdÿeÿiÿiÿlÿlÿnÿpÿqÿqÿrÿrÿvÿvÿvÿvÿtÿtÿsÿtÿsÿrÿrÿrÿtÿuÿtÿtÿuÿsÿoÿkÿfÿ^ÿXÿPÿFÿ@ÿ;ÿ8ÿ6ÿ5ÿ3ÿ2ÿ3ÿ8ÿ@ÿKÿVÿaÿlÿsÿvÿwÿvÿxÿ{ÿ~ÿ‚ÿ†ÿˆÿ‹ÿ‹ÿ‹ÿŠÿ‰ÿˆÿˆÿˆÿ‰ÿŠÿŠÿ‡ÿ„ÿÿxÿoÿhÿeÿcÿ`ÿ_ÿ_ÿ`ÿcÿeÿfÿjÿnÿrÿsÿuÿtÿtÿuÿvÿwÿyÿyÿyÿwÿuÿqÿlÿeÿ^ÿ]ÿ]ÿ^ÿ`ÿfÿnÿvÿ|ÿ‚ÿˆÿ‹ÿŒÿŒÿŠÿ‹ÿŠÿŠÿ‹ÿ‹ÿ‰ÿ‡ÿÿ|ÿwÿpÿlÿoÿsÿyÿ{ÿ{ÿxÿxÿwÿvÿvÿxÿxÿwÿwÿxÿxÿuÿtÿqÿpÿoÿqÿsÿuÿ6‚6m5‚5n4‚4p4‚4p4‚4p4‚4p5‚5q5‚5q55r55s66s55s66s89s9:r::r;;s;=s=‚=r=‚=q?‚?r?‚?r??r??r?€?s>€>s<€;t<€…BiG†KfO…ShZ…bhjƒqjwƒ{ly‚xlt‚nlhƒ]nM>q3€,x)})){)‚*z*ƒ*z*ƒ,z,ƒ*y,….y.….x.„0w/…0x0‡2x3„OxR€Yt\yRqPsRrTrKyAs[|kvuzt|€zoywlvwjwvjurkmgn^QrF>x9|7}6{6€5y4€3z2€2{2€1|10|0~1{1}1z3}4y5}:x=}>uA|DtGzIqLzPnSxUkWvYh^u_gcsfijrqmwp~uƒn‡|‰p‹~‹oŠ~ˆq…~„t‚~v€}€w}~{vxtqo€jmeah_ƒ_f]ƒ^eaƒbcdƒebhƒiaj‚kdnpgr‚tju€vnw€vkxxhwvdv€vdt€tcrrersit€ujv€vjw€uht€rjmil`\lSHjA€:j7~4j3{1o2x6t@vLxWubzlwt|xzwwv|xsz|t‚|†uŠ}uŒ~‹t‰~ˆrˆ€ˆr‰ŠsŠŠrˆƒn~€weo‚i^bƒ`\_‚^\`€b[dg_h€lbp€ret€tfuulwyozzoy~zpw}tnnyikcu^j]p]m_lcohlqpxpp„uˆpŒ|ŒpŒŠpŒ‹q‹Œs‹€‰u…€ty}rsmyktovtzxwzyzzwsv~unv€vmx€xmx€vkx€ygv€uer€pcquctte6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ8ÿ8ÿ9ÿ9ÿ:ÿ:ÿ:ÿ:ÿ;ÿ;ÿ;ÿ=ÿ=ÿ=ÿ>ÿ>ÿ?ÿ?ÿ?ÿ?ÿ?ÿ?ÿ>ÿ>ÿ>ÿ>ÿ=ÿ=ÿ<ÿ;ÿ;ÿ;ÿ<ÿ<ÿ<ÿ;ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ9ÿ9ÿ8ÿ8ÿ6ÿ6ÿ4ÿ4ÿ4ÿ4ÿ2ÿ2ÿ1ÿ1ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ,ÿ,ÿ+ÿ,ÿ+ÿ+ÿ,ÿ.ÿ0ÿ4ÿ:ÿDÿIÿNÿRÿVÿYÿYÿXÿRÿIÿ>ÿ4ÿ0ÿ.ÿ.ÿ-ÿ,ÿ+ÿ,ÿ.ÿ0ÿ2ÿ7ÿ?ÿGÿPÿZÿcÿjÿqÿuÿwÿsÿkÿaÿUÿHÿ<ÿ7ÿ5ÿ1ÿ/ÿ+ÿ)ÿ(ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ#ÿ#ÿ"ÿ"ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ%ÿ%ÿ%ÿ%ÿ&ÿ'ÿ'ÿ'ÿ(ÿ(ÿ)ÿ*ÿ*ÿ+ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ/ÿ/ÿ0ÿ2ÿ3ÿ5ÿ8ÿ=ÿDÿKÿNÿSÿ[ÿ^ÿcÿfÿhÿlÿnÿoÿtÿwÿxÿzÿ|ÿ{ÿ}ÿyÿxÿwÿsÿpÿlÿiÿhÿeÿcÿbÿ`ÿ]ÿZÿXÿRÿKÿFÿ?ÿ9ÿ2ÿ-ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ0ÿ1ÿ2ÿ4ÿ4ÿ4ÿ3ÿ.ÿ-ÿ,ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ,ÿ-ÿ2ÿ:ÿFÿSÿaÿkÿsÿuÿtÿtÿtÿuÿuÿwÿyÿzÿxÿoÿeÿ]ÿQÿKÿGÿFÿDÿBÿ>ÿ:ÿ5ÿ0ÿ,ÿ*ÿ*ÿ0ÿ6ÿAÿRÿ_ÿmÿyÿÿ…ÿ…ÿ‚ÿ€ÿ€ÿ€ÿ‚ÿ„ÿ‚ÿ~ÿwÿoÿaÿSÿCÿ<ÿ;ÿ>ÿBÿGÿKÿNÿSÿWÿ\ÿdÿjÿqÿwÿ{ÿzÿyÿvÿqÿjÿ^ÿOÿ?ÿ3ÿ,ÿ*ÿ(ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ,ÿ,ÿ,ÿ-ÿ,ÿ,ÿ.ÿ.ÿ.ÿ0ÿ1ÿ1ÿ2ÿ3ÿ?ÿbÿoÿcÿRÿJÿKÿHÿPÿSÿaÿlÿuÿzÿ|ÿzÿyÿwÿvÿwÿwÿvÿuÿrÿnÿhÿ^ÿQÿFÿ>ÿ9ÿ7ÿ6ÿ6ÿ5ÿ4ÿ3ÿ2ÿ2ÿ2ÿ1ÿ1ÿ0ÿ0ÿ1ÿ1ÿ1ÿ3ÿ4ÿ5ÿ7ÿ:ÿ=ÿ>ÿCÿEÿGÿKÿMÿPÿSÿWÿWÿ[ÿ\ÿ_ÿdÿjÿpÿxÿÿƒÿˆÿ‰ÿŠÿŠÿˆÿ†ÿ„ÿ‚ÿÿÿ€ÿ~ÿ}ÿzÿxÿtÿoÿiÿeÿaÿaÿ_ÿ`ÿaÿaÿcÿdÿfÿiÿiÿkÿmÿmÿnÿqÿtÿwÿvÿxÿxÿzÿzÿyÿxÿxÿxÿuÿtÿrÿrÿrÿsÿuÿvÿyÿyÿzÿzÿyÿvÿsÿpÿhÿbÿZÿQÿJÿAÿ;ÿ8ÿ6ÿ5ÿ3ÿ8ÿ@ÿLÿYÿdÿnÿtÿwÿyÿxÿzÿ{ÿ€ÿƒÿ‡ÿ‹ÿŽÿŽÿŒÿŠÿ‰ÿŠÿ‰ÿŠÿŠÿŠÿ‰ÿˆÿƒÿ~ÿwÿnÿgÿbÿbÿaÿ`ÿ_ÿaÿdÿgÿiÿmÿqÿtÿtÿtÿuÿuÿwÿyÿ{ÿ{ÿyÿxÿvÿrÿmÿfÿaÿ^ÿ]ÿ]ÿ`ÿcÿjÿtÿ|ÿÿ†ÿ‹ÿŒÿŒÿ‹ÿŠÿŒÿŒÿÿŒÿ‹ÿ‰ÿ…ÿÿzÿqÿlÿlÿoÿtÿxÿzÿzÿwÿvÿuÿvÿwÿxÿxÿxÿvÿxÿyÿxÿuÿrÿpÿrÿvÿvÿuÿ6‚5l5‚5m4‚4m4‚4m5‚5m5‚5o5‚5n5‚5p5‚5q5‚5q55q77q88p::p:;q;;q<€>q>>r>>s>€=s=€=t;€;u;€;u:€:v;€:v:€9v:€:v9:w;9x88x97x6~5y5~4z3~2{1~1|0~0{/~/{.~.{-~,{+}+|+}+|+|+|*|*|+}/w5};uAGoL~LnOzNnJxCq9x3u-y*x)z)y*z*{*{*{*{*{+|.y4~|AtE|ErH{JoMzPkTxVhWv\h]tbihqonwn}vƒp†|ˆn‰ˆn…ƒn‚~p€}~s~}}u{~ysv€romhfd‚b`b„c`bƒa`cƒd^dƒf]gƒh^klcnofqsjvxlxylz{k}~ygy€xduscr€rgr€sjuwk{~~l|~|mz~}p|xpq€koc‚ZnRImA€:k6~4n4z8qAwMwXud{ows{vyzxy|yu~|u…|ˆv}ŽvŽ}Œu‹€Šs‹€‹s‹‹r‹Šqˆ…o€wgmƒf]b„^Z]\[]__c}fbingrsituiuukvxoyypz~wot{pmlwdj_t]j^o^mcjgpokwq|p…sŠwŒs}ŒqŠ€ŠqŒ€Œs€uŒ‰v„vw}ptkyispuuxwxyxy{wqwwnw€xmw€wlx€vix€zhy€wes€obpreu~uf6ÿ5ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ6ÿ6ÿ6ÿ6ÿ8ÿ8ÿ8ÿ9ÿ:ÿ:ÿ:ÿ;ÿ;ÿ;ÿ<ÿ<ÿ<ÿ<ÿ>ÿ=ÿ>ÿ?ÿ?ÿ?ÿ>ÿ>ÿ=ÿ=ÿ=ÿ=ÿ=ÿ<ÿ<ÿ<ÿ:ÿ:ÿ:ÿ:ÿ<ÿ8ÿ:ÿ:ÿ;ÿ9ÿ9ÿ9ÿ8ÿ9ÿ9ÿ9ÿ8ÿ8ÿ7ÿ6ÿ5ÿ5ÿ4ÿ4ÿ2ÿ2ÿ0ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ-ÿ4ÿ:ÿ=ÿ@ÿCÿAÿ@ÿ:ÿ4ÿ,ÿ+ÿ*ÿ)ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ,ÿ.ÿ2ÿ;ÿGÿPÿ[ÿeÿlÿuÿvÿtÿqÿiÿaÿZÿRÿOÿJÿEÿ@ÿ:ÿ3ÿ,ÿ(ÿ'ÿ'ÿ*ÿ+ÿ+ÿ*ÿ(ÿ&ÿ%ÿ$ÿ$ÿ$ÿ#ÿ"ÿ"ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ&ÿ&ÿ'ÿ'ÿ(ÿ)ÿ)ÿ*ÿ+ÿ,ÿ-ÿ-ÿ.ÿ/ÿ0ÿ0ÿ0ÿ1ÿ4ÿ8ÿ;ÿBÿGÿMÿTÿZÿ`ÿdÿeÿkÿnÿqÿqÿtÿwÿwÿxÿyÿ{ÿyÿvÿtÿsÿpÿmÿkÿhÿhÿdÿbÿ_ÿ]ÿXÿUÿRÿMÿGÿBÿ;ÿ5ÿ1ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ.ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ1ÿ1ÿ3ÿ5ÿ4ÿ5ÿ6ÿ5ÿ2ÿ0ÿ.ÿ-ÿ,ÿ,ÿ,ÿ*ÿ*ÿ,ÿ-ÿ1ÿ8ÿBÿMÿZÿeÿnÿsÿuÿtÿtÿtÿtÿvÿxÿyÿwÿrÿjÿ_ÿVÿLÿGÿEÿBÿ>ÿ9ÿ5ÿ1ÿ-ÿ)ÿ(ÿ)ÿ,ÿ3ÿ:ÿFÿVÿdÿrÿ}ÿÿ„ÿƒÿ€ÿÿÿ€ÿÿ„ÿ„ÿÿ{ÿqÿbÿUÿMÿHÿIÿKÿMÿOÿQÿSÿXÿ\ÿcÿgÿpÿtÿvÿwÿwÿwÿsÿmÿbÿSÿEÿ>ÿ;ÿ,ÿ*ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ.ÿ.ÿ0ÿ2ÿ4ÿ6ÿVÿcÿJÿBÿJÿGÿKÿIÿHÿWÿ`ÿmÿvÿ|ÿ|ÿ{ÿzÿwÿwÿxÿxÿwÿvÿtÿoÿhÿ_ÿRÿHÿ@ÿ:ÿ8ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ2ÿ2ÿ0ÿ1ÿ1ÿ1ÿ1ÿ3ÿ3ÿ3ÿ5ÿ6ÿ8ÿ;ÿ>ÿAÿDÿFÿIÿKÿNÿQÿSÿUÿXÿ\ÿbÿhÿpÿwÿ}ÿƒÿˆÿˆÿˆÿ†ÿ…ÿ‚ÿÿ€ÿÿ~ÿ}ÿ{ÿ|ÿyÿtÿpÿlÿgÿcÿbÿcÿcÿcÿcÿ`ÿcÿdÿeÿfÿhÿiÿjÿmÿoÿpÿqÿrÿtÿwÿyÿ{ÿ|ÿ|ÿzÿzÿxÿuÿsÿrÿrÿrÿsÿvÿyÿ{ÿ~ÿ}ÿ~ÿ€ÿÿÿ|ÿyÿrÿkÿdÿZÿRÿHÿ?ÿ7ÿ2ÿ4ÿ8ÿAÿMÿZÿfÿoÿvÿxÿyÿyÿ{ÿÿÿ…ÿˆÿŽÿÿŽÿŒÿ‹ÿŠÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿŠÿˆÿ…ÿÿwÿnÿgÿbÿ^ÿ\ÿ[ÿ]ÿ_ÿbÿeÿjÿoÿrÿsÿvÿvÿuÿvÿxÿzÿ|ÿ{ÿxÿwÿsÿnÿhÿaÿ^ÿ\ÿ\ÿ^ÿbÿhÿpÿwÿ€ÿ†ÿ‹ÿÿÿŒÿ‹ÿŠÿ‹ÿŒÿ‹ÿŠÿŒÿ‰ÿƒÿ~ÿtÿlÿjÿjÿoÿtÿwÿyÿyÿwÿwÿwÿvÿwÿvÿvÿwÿxÿzÿ{ÿyÿwÿtÿsÿtÿuÿvÿwÿ5‚5k6‚6l7‚7m5‚5m5‚5l5‚6m7‚6n6‚6p7‚7q7‚7q77q88q::p::p:;q;;q==r<=r=€=q>€>q>€>q=€=q<g8†0i,†-j2†3i3†2j2…-l'%q$$v##|#~#}#~#~#~#€#~#€$~$%~%%~%%~''}&‚(}(‚)|)ƒ*{+ƒ,{-ƒ-{-ƒ.z/ƒ/z0ƒ1{47~:~?‚FyM„SsZ„^nd„gij„lgn…pdq„rbvƒwaw‚wbusfrogm~kli~fnb|_p\|[qX{TuRzNyHxD|?w73y0‚-z-ƒ-z-„,z,ƒ-z-ƒ-z-ƒ.z.ƒ/z-ƒ.z0ƒ/{.ƒ.{/ƒ1{1„1{1„2z2…2z2…4{4ƒ6{7ƒ6z4ƒ1z/ƒ-z-„-z-„+{+ƒ+{+ƒ.{15|>~J}Uvd€lrr€tlt€sfsscv‚wcx‚whs‚lja‚WkL‚FnB€As<8w3}/z,|)~'{'*{2€8zCR{a{o~zy€ƒr‚‚‚j€‚fƒ€h„ƒ…l‚ƒ}nw„lj_…UeO‡NbO‡NbQ‡RcU†Xe[„`ef‚jhrwlwwmwtnm‚bpT‚IrN€Ju-})|)|)‚*{*ƒ+z+„-z-„-y.„.y.„/w/…/w/…0x1ƒ6{Q€l}G€7y<}9t?xIrIuJwVza|mzw{u|€{pyxkwyixwivujqƒlnaƒRsG€@x<}9}6}65{54|3~3|3~3}3~1}1~1|1{3~3x4~4x3}6y8y9||AsE}GpJ|MoNzPlRwTj[u`khsqoyq~vƒn‡{ˆnˆ}‡n†„m€n~}p|}zqy~vqrnli€edbaa`‚a`a€aaa€aab~`aa|bbdzeggygkhxjnnyrpsyupxzzmz|zhxvdt€rcr€rfr€tivwky~{oz}~p‚}„s„|‚u~~{rq€kob‚ZnQƒEl<5k4|9oDwPu[uf{pww{yyywy|zt~|‚u†|‰v}v}ŒuŒ~‹sŠ~ŠsŒŒt‹ŠsŠ‡m€yepƒj\e„\Y[Z[Z\a`|dfj~ngs~uku~vkvxly~zo{~{qx}vpq{kldw_h[rYiYmZj`khnsm{p‚tˆrŒzŽs‹~‹q‹Œq‹€‹s€ŒuŒ‰vƒ}vs}krhyjunusyxxyvx{wqwunu€vlw€xjv€xj{€{iz€wes€tbuudv~xb6ÿ6ÿ6ÿ6ÿ7ÿ7ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ7ÿ6ÿ6ÿ6ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ8ÿ8ÿ:ÿ:ÿ:ÿ:ÿ:ÿ;ÿ<ÿ<ÿ=ÿ=ÿ<ÿ=ÿ=ÿ=ÿ>ÿ>ÿ>ÿ>ÿ=ÿ=ÿ<ÿ<ÿ<ÿ<ÿ;ÿ;ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ9ÿ9ÿ9ÿ9ÿ9ÿ8ÿ8ÿ8ÿ7ÿ7ÿ7ÿ6ÿ5ÿ5ÿ5ÿ5ÿ4ÿ3ÿ2ÿ1ÿ0ÿ/ÿ.ÿ.ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ(ÿ*ÿ,ÿ-ÿ0ÿ0ÿ-ÿ*ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ+ÿ-ÿ2ÿ8ÿCÿPÿ\ÿiÿpÿuÿwÿuÿtÿpÿlÿlÿfÿaÿ[ÿTÿKÿEÿ?ÿ:ÿ;ÿ?ÿ@ÿAÿ?ÿ?ÿ7ÿ1ÿ,ÿ%ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ'ÿ'ÿ'ÿ(ÿ(ÿ*ÿ*ÿ*ÿ+ÿ,ÿ-ÿ.ÿ.ÿ.ÿ/ÿ/ÿ0ÿ3ÿ7ÿ:ÿ>ÿCÿJÿPÿWÿ^ÿcÿfÿkÿlÿmÿoÿpÿsÿtÿtÿtÿsÿrÿpÿnÿkÿiÿfÿdÿaÿ]ÿ\ÿZÿXÿSÿPÿMÿIÿDÿ@ÿ;ÿ6ÿ2ÿ/ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ/ÿ0ÿ/ÿ/ÿ/ÿ/ÿ0ÿ1ÿ1ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ5ÿ6ÿ5ÿ2ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ-ÿ0ÿ3ÿ;ÿGÿSÿ`ÿjÿrÿtÿsÿrÿsÿsÿtÿvÿwÿwÿsÿlÿbÿXÿNÿFÿ@ÿ?ÿ9ÿ5ÿ0ÿ,ÿ,ÿ)ÿ'ÿ'ÿ)ÿ.ÿ6ÿ?ÿLÿ[ÿiÿuÿ}ÿ‚ÿ‚ÿ‚ÿ€ÿÿ~ÿÿƒÿ„ÿƒÿ€ÿ}ÿsÿgÿ]ÿUÿOÿOÿPÿUÿUÿWÿZÿ\ÿ^ÿcÿgÿlÿrÿvÿvÿvÿsÿkÿ_ÿRÿPÿGÿBÿ/ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ2ÿ5ÿaÿ†ÿ]ÿ9ÿ8ÿIÿQÿOÿOÿKÿUÿcÿlÿxÿ}ÿ~ÿ}ÿyÿxÿxÿxÿxÿzÿxÿuÿqÿlÿbÿTÿGÿ@ÿ<ÿ9ÿ6ÿ6ÿ5ÿ5ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ4ÿ6ÿ8ÿ8ÿ7ÿ7ÿ5ÿ5ÿ6ÿ7ÿ9ÿ<ÿ>ÿAÿDÿFÿHÿJÿLÿNÿPÿXÿ^ÿgÿoÿvÿ~ÿƒÿ‡ÿˆÿˆÿ…ÿ„ÿ‚ÿ€ÿ~ÿ~ÿ}ÿ{ÿyÿwÿvÿsÿmÿgÿdÿaÿ`ÿ^ÿ]ÿ^ÿ]ÿ\ÿZÿ[ÿZÿ[ÿZÿ\ÿ]ÿ`ÿcÿbÿdÿfÿiÿmÿqÿuÿvÿwÿwÿtÿsÿqÿqÿrÿrÿrÿtÿvÿvÿxÿzÿxÿ|ÿ~ÿÿƒÿ„ÿ‚ÿ~ÿzÿrÿjÿaÿYÿOÿEÿ<ÿ9ÿ;ÿFÿQÿ]ÿhÿqÿxÿzÿzÿzÿ}ÿ€ÿ„ÿ‡ÿŠÿÿÿÿŒÿŒÿ‹ÿŠÿŠÿŒÿŒÿ‹ÿŠÿˆÿ…ÿ‚ÿzÿtÿlÿfÿ^ÿXÿWÿYÿ[ÿbÿhÿmÿrÿtÿtÿtÿuÿvÿxÿyÿzÿ{ÿyÿuÿtÿnÿhÿ`ÿ[ÿZÿYÿXÿ[ÿbÿkÿtÿ|ÿƒÿˆÿŒÿŒÿ‹ÿŠÿŠÿŠÿ‰ÿ‹ÿŒÿÿ‹ÿˆÿ‚ÿ{ÿrÿlÿiÿkÿnÿsÿyÿyÿxÿwÿvÿwÿvÿuÿtÿuÿvÿxÿ{ÿ{ÿzÿwÿtÿsÿuÿvÿuÿwÿ7ƒ7k7ƒ5j7ƒ7j5ƒ5k7‚7l6‚6m7‚6o5‚5q8‚8r6‚7r88q99q89p::p9r>?r<~8w20w.ƒ.y.ƒ-z-ƒ-z-ƒ-z-ƒ-z-ƒ-z-ƒ-z-ƒ-|.ƒ0|0ƒ1{1ƒ1{1ƒ3{3„4z4…2y2…2z1†1z3…4y4…2z2…0z0…0z0….z-…,{.…-{-…-z/ƒ3{9E~P{_gsq€tlssfrsdtvev‚ugs‚njdYlNEpA€=v8~4{.}+~+})'{'‚){.‚3{:G{W}g|pyz~‚sƒ€ƒl€f}‚~e‚ƒh„ƒƒl„zko„ec\…V`T†SaU†VcX…ZfY‚[h\€cmi€oosvou€tnmaoXSt7~>y7~+~,{,‚+z+ƒ,y,ƒ+y,ƒ-x-ƒ.x.„/w0ƒ0w0ƒ1x3„6xUƒw{gI{J|IuTsTtQrOxYwd|pzx}}u}|pyylxzlyzmyyns‚lnc‚UqG@x<~9}6}6€4|4€4|32|2€1}3}4~6x9€=t<=p<~?nJxSt_wj{qxy{{z{vz|~t€|…u‰|Œv~w~ŽtŒ~‹r‹~‹sŒŒtŒŒrŒ~ˆlƒ€}cw‚p[gƒ`Y]‚YV\^_c~jhp€slutmtumwxoz{pz~wrs}qnj{dj]uYgWrWgWm\jdjmmwn~q†v‹tŒ}Œs‹‹pŠŠqŠ€‹t‹~Œv‹ˆx‚{wp|ksixktmusxyxyux|wpuuov€vlw€wkv€wj{€{h{€vet€sdvuet~tf7ÿ7ÿ6ÿ7ÿ7ÿ7ÿ6ÿ6ÿ7ÿ7ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ7ÿ7ÿ7ÿ7ÿ8ÿ8ÿ9ÿ9ÿ:ÿ9ÿ;ÿ;ÿ:ÿ;ÿ;ÿ;ÿ:ÿ;ÿ;ÿ<ÿ>ÿ>ÿ=ÿ=ÿ=ÿ=ÿ<ÿ<ÿ;ÿ;ÿ;ÿ;ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ7ÿ7ÿ6ÿ6ÿ4ÿ4ÿ2ÿ2ÿ1ÿ2ÿ0ÿ/ÿ.ÿ.ÿ-ÿ+ÿ,ÿ,ÿ+ÿ+ÿ+ÿ*ÿ*ÿ)ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ'ÿ(ÿ(ÿ'ÿ&ÿ'ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ)ÿ)ÿ(ÿ'ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ+ÿ.ÿ6ÿ=ÿHÿRÿ]ÿhÿrÿvÿyÿxÿxÿwÿuÿtÿqÿnÿhÿfÿaÿ`ÿaÿcÿdÿfÿfÿdÿ[ÿSÿHÿ:ÿ+ÿ$ÿ$ÿ"ÿ"ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ(ÿ)ÿ)ÿ*ÿ,ÿ,ÿ,ÿ-ÿ.ÿ/ÿ1ÿ1ÿ1ÿ4ÿ7ÿ:ÿAÿFÿMÿSÿYÿ`ÿdÿgÿjÿlÿnÿpÿqÿsÿsÿoÿlÿhÿfÿcÿ`ÿ\ÿ\ÿYÿUÿRÿNÿKÿHÿEÿEÿAÿ>ÿ<ÿ7ÿ5ÿ0ÿ/ÿ.ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ0ÿ1ÿ1ÿ1ÿ1ÿ2ÿ1ÿ3ÿ3ÿ4ÿ4ÿ4ÿ3ÿ3ÿ2ÿ2ÿ4ÿ5ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ0ÿ/ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ/ÿ2ÿ8ÿCÿPÿ\ÿgÿoÿrÿsÿsÿrÿrÿrÿtÿtÿtÿrÿmÿdÿYÿNÿEÿ@ÿ<ÿ7ÿ2ÿ.ÿ+ÿ)ÿ(ÿ'ÿ'ÿ'ÿ+ÿ1ÿ7ÿAÿQÿ`ÿlÿwÿ€ÿƒÿƒÿ€ÿÿ}ÿ~ÿÿƒÿ„ÿ…ÿƒÿ~ÿuÿhÿ`ÿYÿWÿVÿWÿWÿWÿWÿUÿWÿXÿ^ÿfÿmÿtÿuÿuÿrÿhÿZÿXÿLÿ7ÿ@ÿ:ÿ,ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ+ÿ,ÿ,ÿ-ÿ.ÿ.ÿ/ÿ0ÿ0ÿ0ÿ2ÿ2ÿ>ÿXÿ_ÿ\ÿTÿWÿCÿ@ÿNÿUÿUÿXÿfÿpÿxÿ}ÿ}ÿ|ÿyÿyÿxÿzÿ{ÿzÿyÿyÿsÿlÿcÿUÿIÿ@ÿ:ÿ8ÿ6ÿ6ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ8ÿ9ÿ<ÿCÿFÿEÿFÿEÿDÿBÿ>ÿ:ÿ7ÿ7ÿ7ÿ8ÿ;ÿ<ÿ=ÿ@ÿCÿGÿJÿPÿYÿ`ÿiÿrÿyÿÿ…ÿˆÿ‡ÿ†ÿƒÿÿ€ÿ€ÿ~ÿ{ÿzÿzÿxÿvÿtÿnÿiÿcÿ_ÿYÿTÿSÿPÿNÿMÿLÿJÿIÿJÿKÿLÿLÿLÿLÿMÿPÿQÿTÿXÿ]ÿeÿhÿkÿnÿqÿpÿqÿqÿqÿpÿrÿrÿrÿrÿrÿqÿqÿqÿrÿuÿvÿzÿ~ÿ€ÿ‚ÿ‚ÿ|ÿxÿpÿgÿ[ÿTÿIÿAÿBÿKÿTÿ`ÿkÿrÿwÿyÿyÿ{ÿ}ÿ€ÿ…ÿ‰ÿŒÿÿÿÿŽÿŒÿ‹ÿ‹ÿ‹ÿŒÿŒÿŒÿ‹ÿ‹ÿ‰ÿ…ÿÿ{ÿtÿmÿeÿ`ÿ^ÿ^ÿaÿfÿkÿpÿsÿuÿtÿuÿwÿxÿzÿyÿzÿxÿuÿrÿnÿfÿ`ÿZÿVÿWÿUÿXÿ]ÿeÿoÿwÿÿˆÿŠÿŒÿÿ‹ÿ‹ÿŠÿŠÿ‹ÿŒÿŒÿ‹ÿŠÿ†ÿÿwÿoÿhÿgÿjÿmÿsÿyÿyÿxÿwÿvÿuÿvÿvÿvÿvÿwÿyÿ{ÿ{ÿzÿvÿrÿrÿtÿtÿsÿsÿ7ƒ7j6ƒ8i9ƒ8j8ƒ8k6‚9m8‚8n7‚7n7‚7p6‚6p7‚8p78q::q::p;;p;;q<uG~Qr[~cmjoks€vhv‚ugtƒtds…sao‡p^oˆq\rˆu\v‰v]rˆj^e†W`I9g)~"p!}!x"}"}$~$€$~%€$~$‚%~%‚&}&‚&}&‚'|(ƒ)|*ƒ+{,ƒ,{,ƒ,{-ƒ/{0ƒ1{2ƒ3|6‚9~=}CIxO‚Vr\‚amf„ikl„nho„pgs‚qgl~khf{bk\zZpVxRtPwLxJvHyGwCzAw=}:w98w42w/‚.w.‚.w.ƒ-y-ƒ-z-ƒ-z-ƒ.z.ƒ.z.ƒ-z-ƒ-z-ƒ0{0ƒ0{1ƒ2{2„3{3„5y5…5y5…5y5†4y4†5{5†4y4†3y2†2y2†1z0…/z/…/{/…-{.….z0ƒ2{8B|M}X~euosmr€qfqqcq‚sdt‚sgq‚mjeZoOEs>;w6~0},}''|(‚({'‚){+‚.{5?{L~Z{fxt}|tƒ€‚o€}i~‚g‚ƒi…‚…l†ƒ€ky„nhf…]cY†XbX…WfUƒRkR€PnV\rbjsrvqurma|XoY{@u6}<|2},€+{,‚,y-ƒ-y-ƒ-y-ƒ-x-ƒ.x.„/w0…1w0…0v3‚;v[fw^yZv`r[w;oHvToXwVvhzpyz}}u}{pyxlxyly‚yky‚zlt‚mnc‚UqJ@x9~6}5}4€5|5€4|3€2|4~6};x?ErJNiPƒPeR„OeK„Dm>9t6}7y7x7|9v<|>tAzFrKwRsZsctlpsxyo{…o‡~†m†„m€k}m||ozxpuqpmgo_YoS~PqMzGvFyFxExDyEwDyDvDzEuFzGsGzIqJzMpRwYq^rdtgljylfo~qbr€rcsrer€rgo€njk€kml~mqm{oxqvv{|x|z€v{~tolƒbf[ƒPeJ‚FjN|Wsbxlzsyyz{z{v}|t‚|†uŠ|Žv~v~Žs~Œr‹~‹sŒ~‹t‹~‹sŒ~Šn…‚f}v_q‚j]f‚e\efamphstlu~unw~xoyyoyzpw~spn}hmby]iWuRgSpShXn]jgmooyq‚s‰y‹tŒ~‹r‹‹s‹‹t‹€vŒ~Œw‰†w€wul|etfwivouuxyxyuw|vouumv€vlv€vku€wjz€zhz€vft€reqqfq~ph8ÿ8ÿ8ÿ8ÿ9ÿ9ÿ9ÿ9ÿ7ÿ:ÿ8ÿ8ÿ9ÿ9ÿ7ÿ7ÿ6ÿ6ÿ8ÿ8ÿ7ÿ8ÿ:ÿ:ÿ:ÿ:ÿ;ÿ;ÿ;ÿ;ÿ<ÿ<ÿ<ÿ<ÿ<ÿ<ÿ<ÿ<ÿ<ÿ<ÿ<ÿ<ÿ<ÿ<ÿ:ÿ:ÿ:ÿ:ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ7ÿ7ÿ7ÿ7ÿ8ÿ7ÿ5ÿ7ÿ7ÿ4ÿ5ÿ5ÿ5ÿ5ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ+ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ'ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ'ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ(ÿ*ÿ+ÿ0ÿ5ÿ=ÿGÿNÿTÿ]ÿcÿfÿiÿlÿnÿqÿrÿtÿuÿwÿxÿzÿ|ÿ~ÿÿ…ÿ…ÿÿzÿsÿfÿVÿFÿ5ÿ(ÿ!ÿ!ÿ"ÿ"ÿ$ÿ$ÿ$ÿ%ÿ$ÿ$ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ(ÿ)ÿ*ÿ)ÿ,ÿ-ÿ-ÿ-ÿ-ÿ/ÿ0ÿ0ÿ2ÿ3ÿ5ÿ8ÿ;ÿAÿFÿLÿSÿYÿ`ÿeÿhÿjÿlÿmÿnÿoÿqÿnÿhÿcÿ\ÿYÿTÿPÿNÿHÿDÿAÿ?ÿ>ÿ<ÿ;ÿ8ÿ7ÿ6ÿ4ÿ2ÿ0ÿ0ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ2ÿ3ÿ3ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ4ÿ4ÿ4ÿ3ÿ2ÿ2ÿ0ÿ0ÿ0ÿ/ÿ0ÿ/ÿ-ÿ/ÿ.ÿ0ÿ2ÿ8ÿ@ÿLÿVÿcÿoÿrÿrÿqÿrÿoÿpÿqÿrÿqÿqÿmÿeÿZÿOÿEÿ=ÿ8ÿ4ÿ/ÿ+ÿ(ÿ(ÿ)ÿ(ÿ(ÿ)ÿ+ÿ-ÿ3ÿ:ÿEÿVÿdÿpÿzÿ€ÿ‚ÿ€ÿ}ÿ|ÿ}ÿÿ‚ÿ†ÿ‡ÿ†ÿ€ÿ{ÿqÿiÿ`ÿ[ÿXÿWÿUÿQÿQÿOÿLÿOÿTÿ^ÿiÿrÿuÿtÿvÿbÿQÿKÿ?ÿ8ÿ8ÿ2ÿ.ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ/ÿ/ÿ0ÿ0ÿ0ÿ1ÿ2ÿ5ÿMÿkÿ_ÿWÿdÿVÿ8ÿ;ÿPÿWÿWÿdÿqÿzÿ}ÿ}ÿ{ÿyÿxÿxÿyÿyÿyÿ{ÿyÿtÿmÿcÿUÿJÿ@ÿ:ÿ7ÿ7ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ5ÿ9ÿ@ÿGÿMÿSÿXÿ[ÿ\ÿ[ÿYÿUÿNÿBÿ;ÿ8ÿ5ÿ6ÿ6ÿ7ÿ:ÿ>ÿ@ÿFÿOÿVÿ]ÿfÿnÿvÿ}ÿ‚ÿ†ÿ‡ÿ†ÿ„ÿƒÿ€ÿ~ÿ~ÿ~ÿ|ÿ|ÿzÿxÿuÿqÿkÿeÿ^ÿTÿLÿGÿCÿAÿBÿBÿAÿAÿBÿAÿAÿAÿAÿBÿCÿCÿDÿFÿKÿPÿWÿ]ÿ`ÿcÿjÿmÿqÿpÿpÿqÿqÿrÿtÿtÿpÿlÿiÿeÿeÿbÿbÿfÿhÿnÿtÿzÿÿ€ÿ}ÿyÿoÿgÿ_ÿWÿPÿOÿQÿYÿbÿlÿtÿzÿ{ÿ{ÿ}ÿÿ„ÿˆÿ‰ÿŽÿÿÿÿŽÿÿŒÿ‹ÿ‹ÿŒÿÿŒÿ‹ÿ‹ÿ‰ÿˆÿƒÿ~ÿzÿuÿqÿkÿkÿjÿkÿpÿsÿuÿuÿvÿvÿxÿxÿyÿzÿzÿyÿuÿrÿlÿfÿ^ÿUÿRÿPÿRÿRÿWÿ]ÿhÿrÿ|ÿ„ÿ‰ÿŒÿŒÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿÿŒÿŒÿŠÿ…ÿ~ÿwÿmÿeÿcÿjÿpÿvÿyÿyÿwÿuÿuÿuÿvÿvÿvÿvÿuÿvÿxÿxÿxÿvÿsÿrÿpÿqÿpÿnÿ9ƒ9j9ƒ9j9ƒ9j9ƒ9k9‚9m8‚8n8‚8p7‚7p8‚7q7‚7q89q::q::q::q;|J|T}`wkppq€rhooen‚pfq‚php‚mle[oP€Cs;5z1~-*}''{(‚)|(‚)|*‚,z/7zB€P|_zl|wv‚q~k}}iƒjƒ…k…n~‚uim„cf^„ZdUƒQiMIoG~GtH~Pv[~jxq~vst|kmRvBmK|NqF€>w6~0},|,€-z-ƒ,y,‚-y-‚-x-„/x0„/x/…1w0…/v2ƒ5vH~[u[v[vio[y7o6wPqTvVwdyryz}v~zoyykyykz‚zjx‚xks‚mnd‚UsJ@y:~7~5}56|44|4‚6~;|@HrPXh^b`gƒh\f…b]_„VeKƒBn9€6t4|3{5z8{@yFwKySsZ{dqk{rpy{€o„~‡nˆ€…l„€‚m€n}|o~}pzxqvrpj€bpZ€MqG~Cv>y?|>x>}@x?|@w@|@u?~?u?~At@~CrC|GrNzTs[saufkizmfo~qcr€qdq€sfs€qhp‚jjeƒ]lY}UsVyZx]uc~kts|wwzv}{{nrlfdƒ[aX‚UdY}^ng{owuyzx}z}v~{u„{‡vŠ{Žw‘|‘w}t}ŒuŒ}ŒuŽ~t‹Šr‡~…o|i{€{bxxat‚p^q‚qatugvvkv~wowyq{zsywst~oqi|cm[zTiPuOgOqPiUo\kgoqq~t…s‹{ŒsŒt‹€‹s‹€‹tŒwŒx‰„x~€suk|fufwkxquwyzxyvw|touvlv€vlu€ulu€vkw€wiwsdqnfn€nhp}ni9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ8ÿ8ÿ8ÿ8ÿ7ÿ7ÿ8ÿ8ÿ7ÿ7ÿ8ÿ9ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ;ÿ<ÿ<ÿ<ÿ<ÿ<ÿ<ÿ<ÿ=ÿ=ÿ<ÿ;ÿ:ÿ:ÿ:ÿ:ÿ9ÿ9ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ6ÿ6ÿ7ÿ6ÿ5ÿ5ÿ5ÿ5ÿ6ÿ5ÿ5ÿ4ÿ3ÿ1ÿ1ÿ1ÿ0ÿ/ÿ/ÿ.ÿ.ÿ,ÿ,ÿ*ÿ*ÿ*ÿ*ÿ)ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ&ÿ%ÿ%ÿ&ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ'ÿ'ÿ'ÿ&ÿ&ÿ%ÿ(ÿ*ÿ0ÿ5ÿ9ÿ<ÿCÿDÿHÿKÿOÿTÿ\ÿdÿnÿvÿ|ÿ€ÿ„ÿ‡ÿ‰ÿŠÿŠÿ‰ÿ…ÿƒÿ|ÿoÿcÿRÿAÿ.ÿ$ÿ"ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ&ÿ&ÿ'ÿ(ÿ*ÿ+ÿ+ÿ+ÿ-ÿ-ÿ.ÿ.ÿ/ÿ0ÿ1ÿ3ÿ4ÿ6ÿ7ÿ;ÿ?ÿDÿJÿQÿXÿ^ÿdÿgÿjÿlÿoÿnÿmÿmÿhÿ`ÿZÿPÿJÿCÿAÿ=ÿ:ÿ8ÿ6ÿ5ÿ4ÿ4ÿ3ÿ1ÿ1ÿ2ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ1ÿ2ÿ3ÿ3ÿ4ÿ4ÿ5ÿ5ÿ6ÿ6ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ6ÿ6ÿ5ÿ5ÿ3ÿ3ÿ3ÿ2ÿ1ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ1ÿ3ÿ7ÿ=ÿHÿSÿ_ÿjÿpÿsÿsÿpÿnÿmÿnÿoÿrÿoÿlÿgÿ\ÿQÿEÿ:ÿ4ÿ/ÿ+ÿ)ÿ(ÿ'ÿ(ÿ)ÿ(ÿ)ÿ*ÿ+ÿ/ÿ5ÿ<ÿJÿZÿgÿsÿ|ÿÿÿ~ÿ|ÿ|ÿ|ÿ€ÿ‚ÿ„ÿ„ÿƒÿ~ÿxÿpÿgÿ_ÿYÿTÿPÿHÿEÿCÿCÿGÿPÿZÿfÿpÿtÿfÿMÿ>ÿDÿ\ÿ\ÿMÿ?ÿ6ÿ0ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ/ÿ/ÿ/ÿ0ÿ0ÿ.ÿ2ÿ=ÿRÿYÿ^ÿcÿdÿTÿIÿQÿQÿOÿVÿgÿqÿzÿ~ÿ~ÿzÿyÿyÿyÿyÿzÿzÿyÿwÿsÿmÿfÿXÿKÿAÿ:ÿ7ÿ5ÿ5ÿ4ÿ3ÿ4ÿ4ÿ8ÿ=ÿEÿNÿUÿ^ÿfÿlÿqÿrÿpÿlÿeÿ]ÿPÿEÿ<ÿ6ÿ5ÿ3ÿ8ÿ<ÿBÿKÿPÿVÿ`ÿhÿoÿuÿ}ÿÿ…ÿ†ÿ‡ÿ†ÿƒÿÿ€ÿÿ~ÿ}ÿ~ÿ~ÿ|ÿzÿvÿrÿjÿbÿVÿMÿFÿAÿ?ÿ=ÿ>ÿ=ÿ?ÿ<ÿ>ÿ=ÿ=ÿ>ÿ<ÿ=ÿ?ÿ>ÿ?ÿCÿGÿNÿTÿ[ÿaÿeÿiÿoÿpÿpÿqÿpÿqÿsÿqÿqÿoÿiÿcÿ[ÿQÿJÿHÿMÿTÿZÿcÿkÿqÿuÿzÿyÿvÿoÿiÿaÿ]ÿ\ÿ]ÿbÿgÿoÿvÿ{ÿ{ÿ{ÿÿ„ÿ†ÿˆÿ‹ÿÿ‘ÿÿÿŽÿÿŒÿŒÿŒÿŒÿ‹ÿˆÿ‡ÿ„ÿÿzÿyÿzÿzÿ{ÿ{ÿzÿxÿvÿxÿwÿwÿwÿwÿwÿxÿyÿxÿ{ÿzÿyÿwÿsÿmÿfÿ_ÿWÿPÿPÿNÿLÿOÿUÿ^ÿiÿtÿ~ÿ†ÿÿÿÿŒÿ‹ÿ‹ÿ‹ÿ‹ÿŒÿÿÿŒÿ‰ÿÿzÿrÿiÿfÿfÿjÿqÿwÿzÿxÿwÿuÿuÿvÿvÿvÿuÿuÿuÿvÿwÿwÿvÿrÿpÿmÿnÿmÿkÿjÿ8ƒ8j8ƒ8j9ƒ9j9ƒ9k9‚9m8‚7n8‚8p7‚7p8‚8q8‚8q9:q::q::q::q;{<}w=~;u;~ÿDÿJÿRÿXÿ]ÿbÿgÿjÿmÿnÿmÿmÿjÿdÿ]ÿUÿLÿBÿ9ÿ8ÿ7ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ/ÿ0ÿ1ÿ1ÿ2ÿ3ÿ3ÿ3ÿ5ÿ5ÿ5ÿ5ÿ7ÿ7ÿ8ÿ8ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ6ÿ5ÿ5ÿ5ÿ4ÿ3ÿ2ÿ2ÿ1ÿ1ÿ0ÿ1ÿ1ÿ0ÿ0ÿ4ÿ8ÿ;ÿCÿNÿZÿiÿoÿqÿqÿnÿlÿlÿoÿmÿpÿqÿoÿgÿ^ÿSÿGÿ;ÿ2ÿ-ÿ*ÿ)ÿ(ÿ(ÿ)ÿ)ÿ)ÿ+ÿ+ÿ*ÿ,ÿ3ÿ9ÿBÿPÿ_ÿlÿvÿ}ÿÿÿ|ÿzÿzÿ{ÿ~ÿÿ‚ÿ‚ÿÿ{ÿvÿjÿ[ÿSÿLÿEÿ?ÿ:ÿ9ÿ:ÿAÿKÿWÿbÿpÿkÿNÿ9ÿLÿjÿoÿcÿRÿEÿ9ÿ0ÿ-ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ.ÿ/ÿ0ÿ0ÿ1ÿ2ÿ3ÿFÿbÿiÿbÿSÿMÿjÿbÿRÿUÿSÿWÿfÿrÿ{ÿ~ÿ}ÿ{ÿyÿxÿxÿyÿyÿyÿzÿwÿtÿnÿeÿWÿKÿAÿ:ÿ7ÿ4ÿ4ÿ3ÿ3ÿ4ÿ6ÿ<ÿBÿMÿVÿbÿlÿuÿ{ÿÿ€ÿ}ÿuÿlÿbÿUÿIÿ<ÿ7ÿ7ÿ;ÿBÿHÿOÿVÿ]ÿfÿlÿsÿyÿÿ‚ÿ…ÿ†ÿ†ÿƒÿÿÿÿ}ÿ}ÿ~ÿ|ÿ}ÿ~ÿ}ÿ{ÿwÿqÿhÿ_ÿTÿJÿCÿ?ÿ=ÿ<ÿ;ÿ;ÿ;ÿ<ÿ<ÿ;ÿ;ÿ;ÿ<ÿ<ÿ<ÿ<ÿ=ÿBÿGÿNÿVÿ[ÿ`ÿfÿkÿkÿmÿpÿqÿpÿqÿrÿoÿmÿjÿeÿ\ÿRÿJÿ?ÿ7ÿ6ÿ=ÿEÿPÿXÿ`ÿfÿlÿsÿwÿrÿmÿiÿiÿfÿgÿiÿmÿtÿzÿ}ÿ~ÿÿÿ„ÿ‡ÿ‰ÿÿÿ‘ÿÿÿŽÿÿŒÿ‹ÿ‹ÿŠÿˆÿ„ÿ€ÿ{ÿyÿwÿsÿuÿwÿ|ÿ}ÿÿÿÿ~ÿ{ÿ{ÿzÿzÿxÿyÿzÿzÿ|ÿ{ÿyÿtÿmÿgÿ]ÿVÿNÿHÿIÿHÿIÿOÿUÿ`ÿmÿwÿ‚ÿŠÿÿÿÿŒÿ‹ÿ‹ÿŒÿŒÿÿÿŒÿ‹ÿ‡ÿÿxÿmÿeÿbÿhÿlÿrÿwÿxÿyÿwÿvÿvÿvÿvÿvÿvÿvÿuÿvÿwÿwÿvÿsÿoÿmÿhÿhÿgÿeÿ:„:i:„:j:„:j:„9j9‚9l8‚7m7‚7o7‚7q8‚8p8‚8p:9p99p9€9p;€;p::p::p;€;q<€€A~I‚RxVƒ\rb„fkh„kjnƒnknmlg~`qXzPwFx;|7w44w44w4ƒ3w3ƒ3w3ƒ2w1ƒ2w1ƒ0x0ƒ0x0ƒ0y/ƒ.x.ƒ.y.ƒ-y.ƒ-z-ƒ/z/ƒ.z.ƒ0z0ƒ1z1ƒ1z1ƒ3z3ƒ4z5„5{5„5{7„9y9†9y9‡9x9‡9x9ˆ8y8ˆ7y7ˆ6x6‡5x5‡4y4†2y2†1y1†1y1…1z1„4z8ƒ;|@L}W|d€mvp€ppo€nhkmen‚neq‚ohl‚dm\‚NrA5x-~)~)|*‚){*„)|)„*|*ƒ+|,‚1}8€=|K€Z~h|t€{w~€~s}zlyyi|~i€m€‚}nw‚jm_‚RnH€Au<}5}5|7?zI~T|`yj{qtJx?oa{umqgnVƒGr<€4x/}.,{,ƒ.y.„.x.„-x-„.x/„0w0ƒ0x0‚3{;Zz{{zeuHuNu\s\wHsIuRvYvd{qvz}v|nyxlvxkx‚ymy‚xluƒooeƒXtJ€@z8}43|3‚2z1‚3z7€AsH€PsW_oemlrxl}ƒl…€‡k‡€†k‚€l~€~m}~}n~~~o~~p€|pw€qqjƒ_qS€Iu@~;z<{;}:y99x9ÿNÿYÿcÿeÿrÿ{ÿ~ÿ}ÿ{ÿyÿxÿvÿxÿyÿzÿyÿxÿuÿoÿdÿWÿJÿ@ÿ8ÿ4ÿ3ÿ1ÿ1ÿ2ÿ4ÿ8ÿ@ÿHÿPÿ\ÿgÿsÿ|ÿ‚ÿ„ÿƒÿ~ÿuÿlÿ`ÿTÿHÿAÿAÿFÿIÿOÿWÿ[ÿcÿjÿpÿxÿ}ÿƒÿ†ÿ‰ÿ‰ÿ‡ÿ„ÿ‚ÿÿ~ÿ~ÿ}ÿ{ÿ|ÿ~ÿ~ÿ~ÿ~ÿ{ÿwÿqÿhÿ^ÿPÿHÿ@ÿ;ÿ:ÿ9ÿ9ÿ9ÿ9ÿ9ÿ:ÿ:ÿ9ÿ9ÿ9ÿ9ÿ9ÿ:ÿ=ÿAÿHÿPÿYÿ^ÿdÿiÿlÿnÿoÿoÿoÿnÿnÿoÿmÿjÿdÿaÿWÿOÿBÿ6ÿ1ÿ.ÿ/ÿ3ÿ;ÿFÿQÿ[ÿcÿjÿnÿqÿrÿoÿoÿnÿpÿrÿtÿwÿ{ÿ~ÿ~ÿÿ€ÿƒÿ‡ÿŒÿŽÿÿÿÿŽÿÿŠÿŠÿ‹ÿŠÿ‰ÿ†ÿÿzÿrÿkÿeÿbÿeÿmÿrÿxÿ|ÿ~ÿ€ÿ€ÿÿ|ÿ|ÿ{ÿ{ÿ{ÿ{ÿ{ÿ}ÿ{ÿvÿnÿiÿaÿXÿOÿIÿCÿDÿEÿGÿMÿXÿfÿrÿ}ÿ‡ÿŒÿŽÿŽÿ‹ÿ‹ÿŠÿŒÿŒÿŒÿÿÿŒÿ‰ÿƒÿ|ÿqÿjÿbÿbÿgÿlÿtÿyÿyÿyÿwÿuÿuÿuÿuÿuÿuÿuÿwÿwÿvÿtÿsÿpÿmÿkÿiÿfÿeÿbÿ;„;i;„;j;„;k;„;k:‚:l9‚8m8‚7n8‚8o8‚8p8‚8p:8p99p9:p::p:9q::q:€9q9€9r;€:s8€8s88t77t76u54v54x44x54y44y23w44w41x22x0}/z/|0z.|-{+|+z*|*y)|({(|({(|(}'|&|'|&|%|&|&|%|&|&}%|%}%|%{%|%{$|$z$}%y&}&x'}'y'}'x&}&x%|%y&|'y%|%z&|&z&}&{&}&{&~&|&~&|&~&|&~&|%~%|%~%|$~${%~%{$${$${$~$z'~,z1}5x8|;v=~CqKƒWhi‡va€‡…^„‚z`n|[jCy,t#z#|$}$€$~$‚$}%‚&}'‚(})ƒ)}*ƒ,|,„,{-„-z-…/z0…/{1‚3~6€:>|DƒKwQ„Xu\„apf„jmj„jknƒmli€dm`|XrOyFw>x9~6w5€4w54w4‚4w4ƒ4w4ƒ3w3ƒ3w3ƒ2x1ƒ0x0ƒ0y/ƒ/x/ƒ.y-ƒ-y-ƒ-z-ƒ.z.ƒ.z0ƒ0z0ƒ1z1ƒ3z3ƒ4z4ƒ5z5„6y7„7y8„9y9†8y8‡9x9‡9x9ˆ9y8ˆ8y7ˆ7x6‡6x5‡4y4‡3y3†2y2†3y2…2{1„3{6ƒ=}B~I~Tz_€iuqrppngkmeo‚odp‚rgpƒkjcƒWoI€ÿ)ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ&ÿ'ÿ(ÿ)ÿ)ÿ*ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ0ÿ0ÿ1ÿ3ÿ6ÿ9ÿ?ÿDÿJÿNÿUÿ[ÿ_ÿfÿfÿgÿkÿnÿnÿlÿhÿcÿZÿSÿJÿCÿ<ÿ6ÿ6ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ2ÿ1ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ0ÿ0ÿ0ÿ1ÿ1ÿ3ÿ3ÿ3ÿ5ÿ5ÿ5ÿ5ÿ6ÿ7ÿ8ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ8ÿ7ÿ6ÿ7ÿ6ÿ6ÿ5ÿ5ÿ4ÿ3ÿ3ÿ2ÿ2ÿ2ÿ3ÿ1ÿ2ÿ4ÿ7ÿ<ÿAÿIÿRÿ]ÿfÿnÿrÿpÿnÿlÿnÿoÿoÿpÿrÿrÿlÿfÿ[ÿMÿ?ÿ4ÿ,ÿ,ÿ)ÿ+ÿ)ÿ*ÿ+ÿ+ÿ+ÿ*ÿ,ÿ0ÿ5ÿ<ÿFÿUÿaÿlÿvÿ}ÿÿ~ÿ{ÿxÿxÿyÿ|ÿ}ÿÿÿ~ÿ{ÿrÿeÿSÿBÿ7ÿ2ÿ0ÿ3ÿ6ÿ:ÿDÿOÿ[ÿgÿ[ÿEÿRÿuÿtÿoÿjÿ_ÿNÿAÿ6ÿ/ÿ-ÿ+ÿ+ÿ+ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ0ÿ3ÿKÿjÿjÿuÿlÿLÿGÿFÿHÿMÿLÿNÿRÿoÿnÿsÿ{ÿ}ÿ~ÿ{ÿyÿxÿwÿwÿwÿyÿxÿwÿtÿnÿgÿYÿJÿ@ÿ8ÿ4ÿ2ÿ1ÿ2ÿ2ÿ3ÿ9ÿ>ÿHÿUÿbÿmÿyÿ€ÿ„ÿ…ÿ‚ÿ~ÿuÿkÿ`ÿTÿLÿHÿMÿSÿXÿYÿ\ÿdÿmÿtÿ{ÿÿ…ÿ‰ÿ‰ÿŠÿ‡ÿ„ÿ€ÿ€ÿ~ÿ~ÿ~ÿ|ÿ|ÿ}ÿ{ÿÿÿ~ÿ{ÿvÿpÿgÿ\ÿPÿDÿ=ÿ;ÿ7ÿ6ÿ6ÿ6ÿ7ÿ7ÿ7ÿ7ÿ7ÿ6ÿ7ÿ7ÿ6ÿ7ÿ>ÿCÿJÿSÿYÿ_ÿfÿjÿmÿoÿmÿnÿmÿmÿnÿmÿkÿfÿ_ÿYÿPÿEÿ:ÿ2ÿ.ÿ+ÿ+ÿ-ÿ1ÿ8ÿAÿLÿVÿaÿjÿmÿtÿtÿtÿuÿxÿyÿ{ÿ}ÿ~ÿÿÿ€ÿ‚ÿ„ÿˆÿŒÿÿÿÿÿŒÿŒÿ‹ÿŠÿ‹ÿŠÿ‡ÿÿ{ÿsÿgÿ[ÿSÿPÿXÿ`ÿkÿqÿvÿzÿ~ÿÿ~ÿ}ÿ}ÿ}ÿ}ÿ}ÿ|ÿ|ÿzÿwÿrÿkÿcÿZÿQÿGÿBÿ?ÿ?ÿCÿHÿQÿ]ÿjÿwÿ‚ÿ‰ÿÿÿ‹ÿŒÿ‹ÿ‹ÿ‹ÿŒÿŒÿÿŒÿŠÿ†ÿ€ÿxÿnÿfÿ_ÿbÿgÿlÿtÿxÿyÿxÿvÿtÿtÿtÿuÿuÿuÿuÿuÿuÿuÿsÿpÿnÿjÿhÿfÿdÿaÿ]ÿ;ƒ;i;ƒ:j:„:j;„;j:‚9k9‚8l77n88p8‚8p9‚9q98p99q9;q;;q::r:€:q99q98r88s77s65u55u54w55x55y44y33y33y23x55x41y0~0y.}.y.}.z,|+z+|+z*{*{){'{'|'{'|'{&|&|&|%|&|%|%|$|$|%}%|%}$|${$|${$}%y&}%y%~&y'~)x'~&y&|&y'}'y'}'z'|'{'{'y&|&{'|'{'}'{&}&{%}%|%}%|#~%|$~$|$~${#~#{##{""{#~"{$~${#%|&'z,4u>…Nk`ˆqb}‰„^‚ƒyak~Wj<{'t#{#|$}$€$~$‚%}&ƒ'}(ƒ)}*„*|+„+|+ƒ,|,„.|1ƒ2|2‚4~9€=‚B}F„KwQ„Tp[…and…glhƒilmƒnll‚inf`qW|NtGw?z8w7~6w6€5w44w4ƒ4w4ƒ4w4ƒ2w2„2w2„1x1ƒ0x0ƒ0y0ƒ/x/ƒ/z.ƒ-z-ƒ-{-ƒ-{-ƒ.{0ƒ0{0ƒ1{1ƒ2{3ƒ4z5„5y5„5z7…7y7…9y9†9y9‡:x:‡9x9ˆ9x9ˆ8x8ˆ8x8ˆ6x6ˆ5y5‡4y4‡3y3†3y3†2z4…5|8„=~BIQ|Z€cwk€ppq€ojmmfoƒodqƒrevƒqijƒbmQCr6.{)})+|*ƒ+}*ƒ*|,€+~.3€:|?GuS`sjst|€€u€}oy€ykx{k|~m€‚€o{‚spgƒWrF:y31}4|9:zBN{YzezTvKv^qz~tkq‚kj`ƒPoC€7v0},,{,„+y,„,x,„-x-„-x-„.y0„/z3€R}qzzwv_yFwEtL{>r>|LrUxUtetizqt|~t~{qy€xnw€wlxxmyxmuƒoofƒYsJ@y941|21z1‚4z;€AzI|W{cuo{zq„|‡m‡}„m€xnm`nUƒQmQƒSoVƒXp[‚apg‚pmw‚}m…ˆo‰‰mˆ€…oƒ€m~€~n~}o{{q}~~q~q}€|qu€ppfƒ[pOBs<8x6|4}4z56y7~6y6~5y5~4x4~5x7};wByKxSsZz`mg~jhl€lcm€mdl€kem€lgicj]€VlM€Ap8/s,}*y*{,|0x6y?uJsTv^mizmfs}vbw€wcz{k~~r€}€t€|t‚{…uˆ{‹w{xzŒwŒ{Œv‹}‹uŠ}Šu‡}€uzpqeXnK}JkPy\ldvkmrxwm{{}n~}~p~}}q}~}s{~{txutogq^~XmM{Dl?v=k=s@mGoSq_plv{t„w‹yvŽ~‹s‹‹tŠŒvŒŒv€‹x‰€…xutl{dp`ucthrnytuxyxzwst}snssls€sks€uku€tit€qep€ngjfie~cl`{[q;ÿ;ÿ:ÿ;ÿ:ÿ:ÿ;ÿ;ÿ:ÿ9ÿ8ÿ7ÿ7ÿ7ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ9ÿ9ÿ9ÿ8ÿ9ÿ;ÿ;ÿ:ÿ:ÿ9ÿ9ÿ:ÿ:ÿ8ÿ9ÿ8ÿ8ÿ6ÿ6ÿ4ÿ4ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ/ÿ/ÿ-ÿ-ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ*ÿ)ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ%ÿ&ÿ%ÿ&ÿ%ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ'ÿ(ÿ+ÿ+ÿ-ÿ-ÿ+ÿ(ÿ'ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ"ÿ"ÿ#ÿ"ÿ#ÿ#ÿ#ÿ$ÿ%ÿ&ÿ+ÿ1ÿ>ÿMÿ_ÿpÿ}ÿ„ÿ‚ÿyÿkÿWÿ>ÿ'ÿ#ÿ#ÿ%ÿ%ÿ$ÿ$ÿ%ÿ&ÿ'ÿ(ÿ)ÿ*ÿ*ÿ+ÿ,ÿ,ÿ-ÿ.ÿ/ÿ0ÿ2ÿ5ÿ9ÿ?ÿDÿHÿMÿQÿVÿ[ÿ]ÿdÿeÿiÿjÿkÿjÿjÿjÿiÿcÿ[ÿSÿJÿAÿ9ÿ7ÿ5ÿ6ÿ6ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ/ÿ0ÿ0ÿ0ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ0ÿ0ÿ0ÿ1ÿ1ÿ2ÿ3ÿ4ÿ5ÿ6ÿ6ÿ5ÿ7ÿ7ÿ7ÿ8ÿ9ÿ9ÿ9ÿ:ÿ:ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ7ÿ7ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ4ÿ6ÿ:ÿ>ÿCÿIÿQÿ[ÿdÿkÿpÿqÿoÿnÿnÿoÿqÿsÿtÿvÿtÿnÿfÿZÿJÿ<ÿ0ÿ*ÿ*ÿ*ÿ*ÿ+ÿ*ÿ+ÿ,ÿ/ÿ2ÿ6ÿ<ÿBÿKÿTÿ`ÿkÿtÿ|ÿ€ÿÿ~ÿzÿxÿxÿ{ÿ|ÿ~ÿ€ÿ€ÿ|ÿvÿlÿ[ÿKÿ=ÿ5ÿ0ÿ2ÿ5ÿ:ÿBÿMÿWÿdÿPÿSÿgÿvÿuÿrÿmÿcÿSÿDÿ;ÿ2ÿ-ÿ,ÿ,ÿ+ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ-ÿ-ÿ.ÿ0ÿ8ÿlÿzÿuÿuÿbÿNÿEÿDÿ>ÿEÿQÿQÿTÿfÿfÿqÿ|ÿÿ}ÿ{ÿyÿxÿwÿwÿxÿxÿyÿxÿtÿnÿeÿXÿJÿ@ÿ9ÿ4ÿ0ÿ1ÿ/ÿ1ÿ5ÿ;ÿCÿMÿYÿfÿrÿ~ÿ†ÿ‡ÿ‡ÿ†ÿ‚ÿ|ÿpÿdÿYÿVÿVÿXÿZÿ\ÿ]ÿdÿiÿrÿxÿ~ÿ…ÿˆÿ‰ÿ‰ÿ‡ÿƒÿÿÿ~ÿ~ÿ~ÿ|ÿ|ÿ|ÿ}ÿ~ÿÿ~ÿ|ÿyÿtÿoÿfÿ[ÿOÿCÿ:ÿ6ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ5ÿ7ÿ<ÿDÿMÿUÿ\ÿbÿhÿkÿlÿlÿmÿmÿlÿkÿiÿiÿfÿaÿ[ÿTÿIÿ@ÿ3ÿ0ÿ+ÿ)ÿ)ÿ+ÿ0ÿ4ÿ:ÿEÿPÿ\ÿfÿmÿrÿvÿyÿ{ÿ|ÿÿ€ÿÿ‚ÿ‚ÿ‚ÿƒÿƒÿ…ÿ‰ÿ‹ÿŽÿŽÿÿŒÿŠÿ‰ÿŠÿŠÿŠÿˆÿ†ÿÿxÿmÿcÿUÿJÿJÿNÿWÿaÿhÿpÿwÿzÿ}ÿ~ÿ~ÿ}ÿ~ÿ~ÿ~ÿ|ÿ{ÿyÿtÿlÿeÿ^ÿRÿHÿAÿ<ÿ:ÿ>ÿBÿKÿWÿcÿoÿ|ÿ†ÿ‹ÿŒÿÿŒÿ‹ÿŒÿ‹ÿŒÿÿÿŒÿŠÿˆÿƒÿ|ÿuÿjÿbÿ`ÿcÿhÿnÿtÿxÿwÿuÿtÿtÿsÿrÿrÿrÿsÿtÿtÿsÿqÿrÿoÿmÿjÿfÿcÿ`ÿ]ÿ[ÿ;ƒ:i:ƒ;j;ƒ;k;ƒ;k9‚:l8‚7k77l88m8‚7n7‚7p88p88q9:q::q:€:q98q99r87s77v66v55w43x33w33x44y33y3~3y2~2y11y00y/~/y.|-y,|+x*|*y+|+z+{*z({({'{'{'|'{&|&{&|%|%|&|%|%|%|$|$|$z$|$z#|$z$|$z$~%y&'x,€0w3€4v4€2u-~+u)~(u)~)w)~&w&|&x%|%z$|$z$}${$}${#}%|$}$|&~$|#~#|#~#|$~#|$~#|#~"|"~#{"~"{! |$'y)/v:„Jn\‡mdy‡`……zakXgA{)s"{${#}$~%~%&}'ƒ(})ƒ)})„*|+„*|,ƒ.}-„0~2‚59€A„EzJ…NtS…Vp]„_lc…dkf„hkkƒklkƒkljfm_~VqMzDw>x8}6w56w55w44w4ƒ3w3ƒ3w3ƒ2w2„2w2„1x1ƒ0x.ƒ.y/ƒ0x.ƒ-z-ƒ-z-ƒ-{-ƒ.{.ƒ.{0ƒ0{0ƒ1{1ƒ1{2ƒ3z4„6y6„6z7…8z8…9y9†8y9‡:x:‡:x:ˆ9x9ˆ9x9ˆ7x7ˆ6x6ˆ6y5‡5y5‡3y3‡3y3†5|6ƒ7}:‚?~D}IQxZ€cujorqplonfqƒrdsƒtevƒuiqƒkma‚QrB€4w*~*}+|+,~+.€0~36z;ƒAuE…MmV…allƒuo}r‚€o~zk{{j|j‚€l~‚yooarO€Aw71}1|48|A€LyW|dxLvLxjqv~vnq‚lmeƒVqF€;w2}-~+{+ƒ*y+„,x,„-x-„,x-„,y0‚0|I}zrzU|K|^w]}TuPxWuLuMrQsPtirizrr~~s}{rz€ynw€ylyylyxlt‚nnf‚XrJ?y841}1‚/{27{?€F{P{[{hvv{r…|‡s‹}‰s†€srgn]ƒXmXƒYo\ƒ]p`‚epl€rnz€€n„€ˆo‰€‰m†€ƒm€€n~€}o~|p~~p}~~q€q|€yqu€oqeƒYqMAt9€5x2}2|2|3~2{2~3{3~4{4~3z3~4y8|=xExNyVq]{ekjkgm€mcm€nel€lgkihe^kZPoE€:s2+u)}){*{,|/x3y8wEtOw[ocxjir{wey|}e~{€n}‚qƒ|„r„|„t„{†u‰{Œw{xŒ|ŒwŠ}‰v‰}‰v‰}†v…}~vujq`€VlK~GiMzTj^wimrxxp{{}q}s~}}t~}t{~zuwqulds[~OoD{=m:w:n;sBoLpWtgpswu‡x‹{ŒvŒ~‹t‹‹u‹‹v‹‹vŠ~Šwˆ~w}~ssgzar^ubtgroytuvxwzurr}nnqqlr€rks€sks€sir€qhn€ljjfla{^o[xYt;ÿ:ÿ:ÿ;ÿ;ÿ;ÿ;ÿ;ÿ:ÿ9ÿ8ÿ7ÿ7ÿ7ÿ8ÿ8ÿ7ÿ8ÿ7ÿ7ÿ8ÿ8ÿ8ÿ8ÿ9ÿ:ÿ:ÿ:ÿ:ÿ9ÿ7ÿ7ÿ8ÿ8ÿ7ÿ7ÿ7ÿ7ÿ6ÿ5ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ/ÿ.ÿ-ÿ,ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ%ÿ%ÿ&ÿ%ÿ$ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ$ÿ$ÿ$ÿ%ÿ$ÿ&ÿ'ÿ+ÿ0ÿ5ÿ:ÿ=ÿ9ÿ7ÿ2ÿ2ÿ1ÿ0ÿ0ÿ.ÿ,ÿ(ÿ&ÿ$ÿ$ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ#ÿ%ÿ$ÿ$ÿ%ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ#ÿ#ÿ#ÿ"ÿ#ÿ"ÿ#ÿ"ÿ"ÿ"ÿ!ÿ"ÿ%ÿ(ÿ/ÿ9ÿGÿVÿjÿyÿÿ„ÿ|ÿnÿ]ÿJÿ1ÿ$ÿ%ÿ#ÿ$ÿ%ÿ%ÿ&ÿ'ÿ(ÿ)ÿ)ÿ)ÿ*ÿ+ÿ,ÿ-ÿ.ÿ0ÿ3ÿ6ÿ:ÿ@ÿFÿKÿOÿSÿXÿ\ÿ_ÿaÿcÿfÿjÿkÿkÿkÿjÿjÿiÿcÿ\ÿSÿJÿBÿ;ÿ6ÿ4ÿ6ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ1ÿ1ÿ0ÿ/ÿ.ÿ/ÿ/ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ2ÿ3ÿ4ÿ5ÿ6ÿ7ÿ7ÿ8ÿ8ÿ9ÿ9ÿ8ÿ9ÿ:ÿ:ÿ:ÿ:ÿ:ÿ9ÿ8ÿ8ÿ8ÿ7ÿ7ÿ6ÿ6ÿ5ÿ5ÿ5ÿ3ÿ3ÿ5ÿ5ÿ6ÿ6ÿ8ÿ<ÿ@ÿEÿIÿQÿXÿaÿiÿpÿrÿrÿpÿoÿqÿrÿsÿtÿvÿvÿtÿnÿeÿWÿFÿ8ÿ.ÿ-ÿ.ÿ,ÿ+ÿ.ÿ1ÿ5ÿ7ÿ;ÿ@ÿDÿJÿQÿXÿ`ÿjÿsÿ}ÿƒÿƒÿÿÿ|ÿ{ÿ{ÿzÿ}ÿ‚ÿ‚ÿ€ÿ{ÿrÿeÿUÿDÿ8ÿ2ÿ1ÿ4ÿ8ÿAÿIÿTÿcÿKÿFÿiÿvÿvÿrÿlÿfÿWÿHÿ=ÿ2ÿ-ÿ,ÿ,ÿ*ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ1ÿDÿtÿwÿmÿZÿ8ÿOÿOÿMÿSÿWÿQÿRÿRÿOÿcÿjÿsÿ}ÿ€ÿ}ÿ{ÿzÿyÿxÿwÿyÿyÿyÿxÿuÿnÿeÿVÿIÿ>ÿ8ÿ4ÿ3ÿ1ÿ2ÿ5ÿ;ÿAÿIÿSÿ^ÿlÿxÿ‚ÿ‰ÿŠÿ‹ÿŠÿ†ÿÿtÿhÿ`ÿ[ÿ[ÿ\ÿ]ÿ]ÿbÿfÿlÿrÿvÿ|ÿÿ…ÿ†ÿ†ÿ„ÿ‚ÿÿÿ~ÿ}ÿ}ÿ|ÿ|ÿ|ÿ}ÿ~ÿÿ}ÿzÿxÿtÿnÿdÿZÿNÿAÿ9ÿ5ÿ2ÿ1ÿ1ÿ1ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ4ÿ9ÿ>ÿGÿPÿYÿ`ÿgÿjÿmÿmÿmÿmÿmÿlÿlÿkÿhÿcÿ]ÿWÿMÿBÿ7ÿ/ÿ*ÿ)ÿ)ÿ)ÿ+ÿ/ÿ3ÿ:ÿFÿOÿ[ÿcÿiÿpÿvÿ{ÿ~ÿ€ÿƒÿ†ÿ…ÿ†ÿƒÿ„ÿ„ÿ†ÿ‡ÿŠÿŒÿÿÿŒÿŒÿŠÿ‰ÿ‰ÿ‰ÿ‰ÿ†ÿ…ÿ}ÿvÿlÿ`ÿSÿKÿJÿMÿTÿ^ÿhÿpÿvÿ{ÿ}ÿÿÿ~ÿ}ÿ}ÿ|ÿ{ÿzÿvÿpÿjÿbÿVÿKÿCÿ=ÿ:ÿ:ÿ;ÿBÿNÿZÿiÿuÿÿ‰ÿ‹ÿŒÿ‹ÿŒÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‰ÿ†ÿ€ÿxÿpÿeÿ`ÿ^ÿbÿhÿnÿtÿvÿvÿtÿrÿpÿqÿqÿqÿqÿrÿrÿrÿrÿqÿpÿmÿjÿgÿcÿaÿ^ÿZÿXÿ;ƒ9h;ƒ:i:‚;j;‚;l:‚:l89m87m87n88m87o78q99q9€9r9€8r8€8r88r77s66u66w55w2~2v2~2v1~1x1~1x2~2y2~2y10x00y0/{00{/}/{.}.{,{+{*{*{){){){){){)|'{&|%|%|'|'|%}%}&}&}%}%|%}${$}${#}#{%}%y$}$y#|#x$|&x+.w37w;;w:6w77u77u6~5t/}*u%|$w$|$y$|${$~$|#~#{#~#{"~#{#~#{#~#|"~"|"~"|"~"|"~"|!~!|!~!{#~${)-x6ƒBpR†cfs‰~a‚…~as‚ceP|8q'z&z$|$~'}''(‚(}(ƒ*|*„+}+„,}.ƒ/2‚5‚=}C†HwM‡QqT‡Yn]†_kb…dhh„khk…jim‚kihije~^oV|LtDy?z9x76x6‚5x5‚6w6ƒ5w5ƒ3w3ƒ3w3ƒ3x3„2x2„1x1„/x/„/y.ƒ.y-ƒ-z-ƒ-z-ƒ-{-ƒ.{.ƒ/{1ƒ0{0ƒ1{1ƒ1{2ƒ3z4ƒ5z5„6z7…8z8†9y9†8y9‡9y9‡9y9ˆ9y8ˆ7y8ˆ9y9ˆ7y6ˆ4y4‡3y4†3y4‡5z6…7|6‚:~>€@€F}K€PxX`siqrstmrpgp‚qds‚udv‚xfv‚pig‚\pK;t2€-z--}-1}6€9y=„BsE…JmM†Te[†afj‚sm|„q……n€€~j{€zh}~ll‚€nxks[€Jv;3{3|47{>IxS}avJvJvjrv}tor‚omf„XrI€ÿ5ÿ3ÿ4ÿ7ÿ>ÿGÿRÿ^ÿIÿHÿlÿvÿtÿsÿoÿfÿXÿJÿ=ÿ4ÿ-ÿ,ÿ,ÿ*ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ;ÿ`ÿpÿjÿQÿ8ÿ[ÿWÿNÿLÿPÿYÿUÿMÿTÿcÿ`ÿfÿvÿ}ÿ~ÿ}ÿzÿyÿxÿxÿyÿyÿyÿwÿtÿoÿfÿXÿKÿ@ÿ9ÿ6ÿ6ÿ8ÿ9ÿ>ÿEÿLÿSÿ]ÿhÿtÿ~ÿ‡ÿŒÿŽÿÿŠÿ„ÿ|ÿrÿhÿcÿ`ÿ_ÿ^ÿ]ÿ]ÿ^ÿ`ÿdÿjÿoÿvÿ}ÿƒÿ„ÿ‚ÿÿÿ~ÿÿ}ÿ}ÿ}ÿ~ÿÿ~ÿ~ÿ}ÿÿ}ÿyÿvÿrÿmÿcÿYÿKÿ?ÿ7ÿ3ÿ1ÿ0ÿ0ÿ0ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ0ÿ4ÿ:ÿBÿKÿSÿZÿcÿjÿmÿlÿlÿlÿmÿmÿlÿkÿiÿcÿ]ÿWÿPÿEÿ<ÿ3ÿ-ÿ+ÿ)ÿ,ÿ+ÿ-ÿ0ÿ9ÿBÿLÿUÿ]ÿcÿjÿrÿvÿ{ÿÿƒÿ…ÿ†ÿ†ÿ„ÿƒÿ„ÿ„ÿ†ÿ‡ÿŠÿŒÿÿŠÿŠÿ‰ÿˆÿˆÿ†ÿˆÿ‡ÿ‡ÿ„ÿ}ÿvÿlÿ_ÿRÿNÿNÿSÿZÿbÿkÿsÿyÿ|ÿ~ÿ~ÿ}ÿ~ÿ}ÿ|ÿ|ÿ{ÿwÿrÿkÿfÿ\ÿQÿGÿ<ÿ8ÿ8ÿ8ÿ=ÿFÿSÿcÿoÿ{ÿ…ÿŠÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿŒÿŒÿŒÿ‹ÿŠÿ‰ÿ…ÿ|ÿsÿiÿcÿ^ÿ^ÿbÿiÿoÿvÿvÿtÿsÿpÿoÿpÿqÿqÿqÿqÿqÿqÿqÿoÿmÿjÿgÿdÿbÿ^ÿZÿWÿWÿ:ƒ:h<ƒu5}.},{,)z*„,z,…,y,„,y,„-x+ƒ2xLƒexn~a|P|n~|zi{QxOx^u^sNrLuaq^{Vrk~‚s|qz€ymy€yjyylxxkvƒolf‚YnJ?u;;z=‚A|CHyL€TvZbsm}xu|†w‹{ŒzŒ|‰zƒ~zvn‚gpaƒ`p_ƒ]pZXrY}\rbzirp|wt}~q„‚o€~m|€}n}|p}~p~~p~~~q}{rz€trqksc‚WsI=t63y0}/|.{..{-€-{-~,{,}-z/|5ysFuXqdxqs~z‡xŠvŠ|Šu‹}‹tŒ~ŒuŒ~ŒxŒ~Œx‹~ˆx‚~{xq}ft`y\t^tcxjsrzvwwwt|soqplq€rkr€rls€sjq€ojl€jjl€gme}^p\yZrYwYr:ÿ:ÿ<ÿ<ÿ:ÿ:ÿ;ÿ;ÿ;ÿ9ÿ9ÿ9ÿ8ÿ8ÿ8ÿ7ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ7ÿ7ÿ7ÿ6ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ4ÿ4ÿ3ÿ3ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ,ÿ+ÿ+ÿ)ÿ*ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ%ÿ#ÿ#ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ#ÿ"ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ&ÿ&ÿ&ÿ'ÿ)ÿ*ÿ*ÿ+ÿ/ÿ/ÿ/ÿ+ÿ)ÿ&ÿ%ÿ#ÿ#ÿ"ÿ"ÿ"ÿ"ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ#ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ!ÿ ÿ!ÿ!ÿ"ÿ"ÿ"ÿ"ÿ"ÿ#ÿ%ÿ)ÿ/ÿ8ÿEÿWÿfÿtÿÿÿzÿnÿaÿPÿ6ÿ)ÿ)ÿ.ÿ5ÿ9ÿ;ÿ3ÿ*ÿ)ÿ)ÿ*ÿ+ÿ.ÿ1ÿ6ÿ;ÿ@ÿFÿLÿQÿVÿZÿ\ÿaÿdÿcÿfÿiÿhÿhÿiÿiÿhÿgÿcÿ_ÿYÿRÿKÿBÿ<ÿ7ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ0ÿ0ÿ/ÿ/ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ2ÿ3ÿ2ÿ5ÿ6ÿ6ÿ6ÿ6ÿ8ÿ8ÿ8ÿ8ÿ9ÿ9ÿ7ÿ7ÿ8ÿ9ÿ9ÿ8ÿ8ÿ8ÿ7ÿ7ÿ7ÿ7ÿ6ÿ5ÿ6ÿ5ÿ5ÿ6ÿ6ÿ8ÿ:ÿ=ÿAÿDÿHÿIÿLÿQÿWÿ_ÿeÿmÿtÿwÿuÿsÿsÿsÿrÿtÿtÿvÿvÿtÿpÿhÿYÿJÿ>ÿ8ÿ5ÿ9ÿ<ÿAÿDÿIÿNÿRÿVÿYÿ\ÿ_ÿdÿhÿlÿuÿ|ÿ‚ÿˆÿ†ÿ‚ÿwÿnÿkÿyÿ~ÿ|ÿpÿ{ÿ‚ÿ}ÿuÿjÿXÿIÿ:ÿ3ÿ3ÿ7ÿ=ÿEÿOÿ^ÿKÿHÿaÿrÿsÿrÿnÿfÿZÿKÿ>ÿ5ÿ.ÿ,ÿ,ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ4ÿFÿSÿkÿkÿuÿtÿeÿbÿ`ÿaÿZÿNÿBÿSÿfÿkÿzÿÿ}ÿ{ÿzÿyÿyÿyÿyÿyÿxÿxÿsÿmÿfÿ\ÿPÿDÿCÿAÿCÿGÿJÿNÿRÿZÿaÿjÿrÿ|ÿ„ÿ‰ÿ‹ÿŒÿ‰ÿˆÿÿuÿiÿcÿ_ÿ\ÿZÿUÿRÿSÿTÿXÿ`ÿfÿnÿwÿÿƒÿƒÿÿÿ~ÿ|ÿ}ÿ{ÿ{ÿ}ÿ~ÿ~ÿ~ÿ~ÿ~ÿ|ÿyÿvÿuÿqÿkÿbÿVÿIÿ=ÿ6ÿ3ÿ/ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ/ÿ4ÿ<ÿCÿMÿWÿ_ÿgÿjÿmÿnÿlÿkÿlÿlÿlÿiÿeÿbÿ]ÿWÿOÿDÿ9ÿ1ÿ,ÿ+ÿ+ÿ+ÿ/ÿ5ÿ?ÿGÿMÿUÿ\ÿdÿiÿoÿtÿxÿ}ÿ~ÿÿ„ÿ†ÿ†ÿ…ÿƒÿƒÿ„ÿ†ÿ‡ÿŠÿŒÿŒÿŒÿ‰ÿ‰ÿˆÿ‡ÿ‡ÿˆÿˆÿˆÿƒÿ|ÿvÿlÿaÿUÿQÿSÿZÿ`ÿgÿoÿvÿ{ÿ~ÿ~ÿ}ÿ|ÿ{ÿ}ÿ}ÿ|ÿ{ÿuÿpÿiÿaÿTÿIÿ=ÿ7ÿ6ÿ6ÿ9ÿ?ÿJÿ\ÿjÿuÿÿ‰ÿŠÿŠÿŠÿ‹ÿ‹ÿŒÿŒÿŒÿŒÿŒÿŒÿ‹ÿ†ÿÿyÿnÿdÿ[ÿ[ÿ^ÿcÿiÿqÿvÿwÿsÿrÿqÿqÿrÿrÿrÿrÿsÿsÿrÿoÿkÿjÿhÿfÿbÿ]ÿ\ÿ]ÿ^ÿ^ÿ:ƒ:i:ƒ:j;‚;j;‚;l:‚9l9‚8m7‚7o87p8€8q7€7q8€8q7€7r8€7r77s66t4~3t33u33u22x22x1~1y1~1{1~1{0~0z1~1z0~0z0~/{/~/{.},|.}.|,},{,{,{*|)|)|)|)}'|&}&|'}'|&}&|%}%|$}$|%}%{$}${$}$y$}${$|$y#|#y#|#z#|#z#|#z#|$z$}$z%}%z&}&z%}%z&|&y'|%y(}*w'}'w%}$x"}"x"}"x"}"y"~"{"~"{"~"|#~#|!~!}"~"}""|  |  |  z!~!z!~"{$'z+‚4t>…Qmb†ody‡€`|…scgWjB}-t)~1z;?~A7€,*€*~,‚-1‚6€9=ƒCwJ†QsV‡Xm^ˆbgfˆecg‡gcj‡hbi…idh‚fga~]iVzOqLxDuqA„ElJ…NgR†Xd[…^ba…eagƒk`p„vcz€i†…ox€wqu€jrfqvf[{h~s{z€yxoƒ^wM‚=z42~4~9€C~P|[vPxOu_rq{smrkne‚ZqK>w6}/,{*ƒ+z+…+z+†+z+…,z,…-{-„+z+„+w8‚Yxrl}gd}p{dxWuVtTrSu]qh{us{~t}{p{€zly€zjyxkxxjv‚oki‚_mU„MpJƒJsL„MuPƒTtY‚aph‚opv~t†}Šy‹|Š{‡|‚zz}rue]rZ‚VqRMtJ|MtO|Su]{fvr{|u€ƒpn€~m|€|n|}n~~o~~~q}~q{xsx€uqpiqb‚TrI‚>u5€2y.}-}-{--{,+{++{+~*z0{5y>xGyOuY{aoh}kjnlem€kdk€kcl€jeg€ciZ€UmL€Bq8€/u+}+z+|0z4{=xG|NrV}]ob~inn~snw}yp|{r‚z„v‡z‡y…{‚xƒ{„v†{Šx‹zŒz‹z‹zŠ{‰y‡{†y‡zˆxˆ|‡xƒ~|xu€lra€VlRUj\|bmi|qpx||r~|~t~}~w}}~w~~}ux~uvmew^RuE~:r6{6s4w8wAsMy\rl}yu‚{Šy‹w‹}‹uŠ}Šu‹~ŒvŒ~Œx~‹yˆƒz~vyl}`uYxYu^texltsyvwwtu|snqrkq€qkr€rkr€rkr€oklhkgcl`{\l\x^n`wbm;ÿ;ÿ:ÿ:ÿ;ÿ;ÿ;ÿ;ÿ:ÿ9ÿ8ÿ7ÿ7ÿ7ÿ8ÿ7ÿ8ÿ8ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ8ÿ7ÿ7ÿ7ÿ5ÿ4ÿ3ÿ3ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ0ÿ0ÿ.ÿ-ÿ/ÿ/ÿ/ÿ/ÿ-ÿ-ÿ.ÿ+ÿ,ÿ,ÿ,ÿ,ÿ)ÿ(ÿ)ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ%ÿ%ÿ$ÿ#ÿ#ÿ%ÿ&ÿ%ÿ$ÿ#ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ!ÿ!ÿ ÿ ÿ!ÿ!ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ!ÿ"ÿ#ÿ$ÿ)ÿ/ÿ9ÿKÿ]ÿlÿwÿÿ~ÿtÿkÿ\ÿJÿ3ÿ)ÿ,ÿ4ÿ<ÿ?ÿ4ÿ)ÿ)ÿ*ÿ,ÿ/ÿ3ÿ8ÿ<ÿCÿGÿNÿSÿXÿ\ÿaÿdÿfÿgÿhÿhÿhÿjÿiÿgÿdÿaÿ[ÿVÿOÿHÿDÿ>ÿ9ÿ5ÿ5ÿ6ÿ5ÿ5ÿ5ÿ4ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ2ÿ2ÿ1ÿ1ÿ0ÿ0ÿ/ÿ/ÿ.ÿ-ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ/ÿ0ÿ1ÿ1ÿ1ÿ1ÿ2ÿ3ÿ3ÿ4ÿ4ÿ5ÿ6ÿ6ÿ7ÿ7ÿ6ÿ6ÿ7ÿ7ÿ8ÿ8ÿ7ÿ7ÿ8ÿ8ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ6ÿ8ÿ8ÿ:ÿ=ÿ@ÿBÿEÿEÿIÿLÿMÿQÿVÿZÿdÿlÿsÿwÿwÿvÿtÿrÿrÿrÿrÿtÿsÿsÿqÿlÿbÿWÿNÿEÿBÿDÿJÿMÿQÿUÿ[ÿ_ÿdÿfÿiÿkÿpÿsÿvÿzÿ}ÿ€ÿƒÿ‚ÿvÿuÿtÿ|ÿdÿPÿDÿ?ÿ@ÿDÿ^ÿ^ÿXÿoÿhÿJÿ8ÿ0ÿ4ÿ9ÿ?ÿKÿZÿNÿMÿ^ÿlÿrÿrÿmÿeÿZÿKÿ>ÿ6ÿ/ÿ,ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ5ÿMÿ]ÿcÿhÿkÿcÿDÿHÿMÿLÿSÿ`ÿiÿrÿ{ÿÿ}ÿ{ÿyÿyÿwÿxÿyÿyÿyÿxÿuÿqÿkÿcÿ\ÿXÿRÿQÿPÿTÿVÿ[ÿ`ÿfÿnÿuÿ|ÿ‚ÿˆÿ‹ÿ‹ÿŠÿ†ÿÿxÿmÿcÿYÿSÿLÿHÿDÿBÿEÿMÿUÿ^ÿiÿvÿ}ÿ‚ÿƒÿÿÿÿ~ÿ|ÿ|ÿ}ÿ}ÿÿÿ~ÿ~ÿ}ÿ|ÿ{ÿxÿwÿtÿpÿiÿbÿTÿIÿ>ÿ5ÿ2ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ,ÿ1ÿ9ÿ@ÿHÿPÿ[ÿbÿhÿlÿmÿkÿlÿlÿkÿkÿkÿiÿeÿaÿXÿSÿJÿ@ÿ5ÿ/ÿ*ÿ+ÿ-ÿ3ÿ;ÿEÿPÿUÿ]ÿcÿiÿoÿrÿwÿzÿ|ÿ|ÿÿ‚ÿ„ÿ†ÿ†ÿ…ÿ‚ÿƒÿ„ÿ†ÿŠÿ‹ÿŒÿ‹ÿ‹ÿ‰ÿˆÿ‡ÿ†ÿ‡ÿˆÿˆÿ‡ÿƒÿ|ÿuÿiÿ^ÿWÿUÿXÿ_ÿfÿmÿtÿyÿ}ÿ~ÿ~ÿ|ÿ}ÿ}ÿ}ÿ|ÿ|ÿxÿuÿkÿfÿ\ÿPÿBÿ8ÿ4ÿ4ÿ5ÿ9ÿDÿPÿ_ÿoÿ{ÿ…ÿŠÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿŒÿŒÿŒÿŠÿˆÿƒÿ}ÿuÿhÿ^ÿYÿYÿ^ÿeÿlÿsÿwÿwÿuÿsÿrÿrÿqÿqÿsÿsÿsÿsÿrÿoÿlÿhÿeÿbÿ^ÿ^ÿ^ÿ_ÿbÿdÿ<ƒ;i;ƒ;j:‚:l:‚:m9‚8l9‚8m86o77p8€8p7€7p6€6q7€7s65t66u42v1~1v11v11w00x00x/~1y1~1{0~0{/~/{/~/|0~0|/~.|-~-|,},|-},|,}+|,{,|)|)|(|'|&}&|%}%|&}&|%}%|%}%{%}${"}#y#}#y#}#y#}#z#|#y$|$y"|"y"|"y#|#y#|#y#}#z$}$z$}$z$}$z$}$y$}"y"}"y$}$y$}#y"}"y"}"y"}"{"~"{"~"{"~"|"~"|!~!| ~ |  {  {  | z~ z!~"{#€#z'-w5ƒCqV…hks†}b†{ar‚aeR>n.}+w/}7}5}/€*}+€,~.05;‚>|D…JsP†Wm\‡`gcˆfaiˆi`j†jajƒice€ce_}ZiTzOoGwBwx5}.-{+ƒ,z,…,z,†,z,…,z,…,|+‚+€,2‚;|PƒV|Wb}f~QxOy]rIsEqLuVue{qwz~~v~|px€wlx€yjz{k{{jy‚vip‚hic„^l[…YpW„Zq^„cpg„mmrƒyl‡q‰~‹v‹|‰zƒ|{v~kv_QsL~Dv=|8w;z@zM{Yxe|qv{~r„„pn€~n~€}p€rr~~}r|{rzxru€uspirb‚TtI‚=v3€0y-}-|,{,~+{+*{*+{+~-z3{:yBwJyTr\|dnh}kijlej€jek€jfi€gic€_kX€OnF€ÿCÿGÿNÿUÿZÿ^ÿbÿdÿgÿiÿiÿjÿjÿjÿfÿaÿ\ÿVÿQÿJÿDÿ@ÿ;ÿ7ÿ6ÿ6ÿ5ÿ6ÿ6ÿ3ÿ3ÿ4ÿ4ÿ5ÿ5ÿ4ÿ4ÿ4ÿ5ÿ4ÿ4ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ0ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ,ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ/ÿ0ÿ1ÿ1ÿ1ÿ1ÿ2ÿ3ÿ3ÿ3ÿ5ÿ5ÿ4ÿ5ÿ6ÿ7ÿ6ÿ6ÿ7ÿ7ÿ8ÿ8ÿ7ÿ8ÿ8ÿ7ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ7ÿ8ÿ9ÿ=ÿ=ÿ?ÿBÿEÿHÿIÿKÿLÿMÿOÿPÿSÿ[ÿbÿkÿpÿuÿzÿyÿuÿsÿqÿqÿqÿqÿsÿuÿtÿsÿnÿeÿ[ÿTÿPÿQÿUÿZÿ_ÿeÿjÿqÿuÿxÿÿÿÿ€ÿÿƒÿ‡ÿ†ÿ„ÿÿvÿXÿ@ÿPÿZÿRÿPÿ@ÿXÿ`ÿ^ÿdÿlÿNÿJÿAÿ@ÿGÿKÿhÿ˜ÿ“ÿrÿIÿCÿLÿcÿsÿqÿkÿfÿ[ÿKÿ>ÿ5ÿ.ÿ-ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ/ÿHÿnÿ}ÿ„ÿ†ÿ„ÿtÿcÿfÿjÿVÿ[ÿJÿNÿ\ÿXÿeÿqÿzÿ|ÿ}ÿ{ÿxÿwÿzÿzÿyÿ{ÿ{ÿ{ÿzÿxÿvÿoÿgÿaÿ_ÿ]ÿ[ÿ_ÿcÿhÿmÿsÿxÿ}ÿ„ÿ‡ÿŠÿ‹ÿŠÿ‡ÿƒÿ|ÿuÿiÿZÿLÿ@ÿ7ÿ3ÿ3ÿ9ÿCÿPÿ\ÿhÿvÿ}ÿƒÿ†ÿ„ÿÿÿÿ~ÿ~ÿÿ€ÿ€ÿÿÿ~ÿ}ÿzÿyÿxÿxÿwÿsÿpÿiÿbÿTÿIÿ=ÿ3ÿ0ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ-ÿ3ÿ9ÿCÿKÿTÿ\ÿdÿjÿkÿkÿjÿjÿjÿiÿhÿhÿeÿaÿ[ÿVÿMÿBÿ8ÿ1ÿ,ÿ+ÿ0ÿ7ÿBÿKÿSÿ]ÿfÿjÿoÿtÿyÿ}ÿ~ÿ}ÿ}ÿ~ÿÿ„ÿ†ÿ‡ÿ…ÿ„ÿ‚ÿƒÿ…ÿ†ÿ‰ÿ‹ÿ‹ÿ‹ÿ‹ÿˆÿ†ÿ†ÿ†ÿ‡ÿˆÿ‰ÿ‡ÿƒÿ}ÿvÿjÿ_ÿXÿWÿ]ÿeÿmÿqÿyÿ{ÿ}ÿÿÿÿ~ÿ~ÿ~ÿ|ÿ}ÿxÿrÿhÿ^ÿUÿHÿ;ÿ3ÿ1ÿ2ÿ2ÿ;ÿFÿXÿgÿsÿ€ÿ‰ÿŠÿ‹ÿŒÿ‹ÿ‹ÿŒÿ‹ÿ‹ÿ‹ÿ‰ÿˆÿ†ÿƒÿ|ÿtÿkÿcÿ[ÿWÿXÿ_ÿfÿmÿtÿvÿuÿrÿqÿqÿqÿrÿrÿqÿrÿrÿqÿpÿmÿjÿeÿcÿaÿ`ÿaÿaÿdÿeÿfÿ;„;j;„:k:‚:l99m98n77o77p66p55p6€6q55r56t4~2v2~2v2~1v0~0v11w00x//y//y.~.z-~-|-~-|-~/|/}.|.}.|-~-|+~+|+}+|+|)|)|)|*|)|(}(|'}&|&}&|&}%|%}%|$}${$}$z$}$z$}$x$}$x$|#w#|#w#|#x#|#y#}#y#}#y$}$y"}"y#}#z#}#z$}$z"}"z#~#y"~"y#}#y#}#y#~#z#~#z""{""|""|""|!~!{!~!| ~ { ~ { {  {{} {!~!{"~"{"~"{$~(z.€8tG‚Wog…shz‡|ex…nea‚Rk>~-t*~(z(~*~*~,€-05€8~>‚EyL„RsY†\j`†ced†h`i†j`i„h_d_cY|TjNyHqBx>v9w7}6w3€3w5€4w4€4w45w5‚5w5ƒ3w3ƒ4w4ƒ3x3ƒ2x2ƒ1y1„0y0„0z/„.z.„.{.ƒ,{,ƒ-{-ƒ.{-‚-{-‚.{.‚/{0‚0{1‚2{0ƒ2{3ƒ4z4„5z5„5z5„5z5„5z5…6z6…6z6…8z8…7y6†6y6†4z4†7z6…7|9„:<‚>€A€BF|H‚JtJ‚LqN‚LpN‚QpQYobhnptmz{kv€thqqgp‚phr‚thvƒviqƒkid„]eX†Wa[†]dd†jcq…wd|„ƒc‡‡j‡‡lˆ€ˆnˆŠp‹€Œt~jxV~Q}ZVyZ‚\wc‚f|ib\ED€L†QDŠ@|L‹h{w‰myY€G{Zxwsqolpe[sK€=y6}/-|-ƒ,{,…,z,…,z,…+z+…-x/„[x~ƒ€wvƒxw}ƒx{i~izwykvRsIrVr_vXwd{q{|~y~€|qy€xkw€wjy€{j{€|k{€zjw‚okj…enb…`nb„dmi„mlr„yi}‚l†€ˆr‰‹u‰}…{‚}{{q€eyV€Fw;4x0}4{={H|U{axm|xx…s…€„p€o€p€q€€rr}€|r{€xpx€wrwvtrktb‚VuJ‚=w3€.y,~,|-|-~-},,}+~*{*{/y3y:yDwNzXqa{eli~ihiidiidj€ife€bg\€WjQ€Hn>€5r.~-v/{5v>{HtT}\qd}kqo~vq{~~p~}p|}}r~{u„x„z†y†z„z‚x„{‚w…{ˆy‹zŒ{‹zŠzˆ{‡y‡{‡y‡zˆy‰|ˆz…~~zu€js^Zn\`nh{oqs{yr{|}s~}~u}u|v~~|ux~pvh\vSEt8€0s-},v0wÿ9ÿ5ÿ5ÿ4ÿ4ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ3ÿ3ÿ4ÿ4ÿ3ÿ3ÿ2ÿ2ÿ1ÿ1ÿ0ÿ0ÿ0ÿ/ÿ.ÿ.ÿ-ÿ-ÿ,ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ.ÿ0ÿ/ÿ0ÿ0ÿ1ÿ2ÿ0ÿ2ÿ3ÿ3ÿ3ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ7ÿ7ÿ7ÿ5ÿ6ÿ5ÿ6ÿ6ÿ7ÿ9ÿ;ÿ=ÿ>ÿ@ÿAÿBÿEÿGÿIÿKÿLÿMÿMÿMÿOÿPÿSÿVÿ^ÿgÿnÿuÿzÿzÿxÿuÿsÿqÿsÿsÿsÿuÿwÿwÿtÿpÿjÿdÿ_ÿ^ÿ`ÿdÿiÿoÿuÿ~ÿƒÿ‰ÿÿÿÿÿÿÿÿÿÿ‘ÿŒÿpÿVÿIÿIÿWÿ_ÿmÿrÿkÿlÿnÿYÿGÿQÿCÿFÿGÿIÿIÿKÿTÿbÿ^ÿOÿ[ÿtÿnÿmÿiÿcÿ[ÿLÿ>ÿ6ÿ/ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ,ÿ.ÿ>ÿYÿhÿpÿkÿfÿdÿ_ÿnÿkÿPÿOÿ^ÿ^ÿTÿdÿpÿyÿ~ÿ~ÿ|ÿyÿxÿxÿxÿzÿzÿ{ÿ|ÿ{ÿyÿvÿoÿjÿeÿcÿcÿfÿiÿmÿqÿvÿzÿ}ÿÿ…ÿ‡ÿˆÿ‰ÿ‰ÿ…ÿ‚ÿ{ÿqÿeÿTÿDÿ8ÿ2ÿ1ÿ8ÿBÿMÿ[ÿfÿrÿ}ÿ„ÿ„ÿ„ÿ‚ÿÿÿÿÿÿÿ€ÿ€ÿÿÿ}ÿ|ÿ{ÿxÿxÿwÿwÿvÿqÿlÿbÿVÿIÿ<ÿ3ÿ.ÿ,ÿ,ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ*ÿ*ÿ.ÿ5ÿ<ÿGÿNÿXÿaÿfÿiÿiÿiÿiÿiÿiÿiÿhÿeÿ`ÿ\ÿXÿOÿEÿ9ÿ2ÿ/ÿ0ÿ4ÿ<ÿDÿOÿYÿbÿiÿnÿsÿyÿ~ÿ€ÿ~ÿ}ÿ|ÿ}ÿ~ÿÿ„ÿ„ÿ†ÿ†ÿ„ÿ‚ÿƒÿ„ÿ…ÿˆÿŠÿŒÿ‹ÿŠÿˆÿ‡ÿ‡ÿ‡ÿ‡ÿˆÿ‰ÿˆÿ„ÿ~ÿuÿkÿaÿ\ÿ_ÿcÿiÿpÿvÿzÿ{ÿ}ÿ~ÿÿÿÿÿÿÿ|ÿuÿpÿhÿ\ÿOÿ@ÿ5ÿ.ÿ+ÿ+ÿ3ÿ>ÿOÿ_ÿlÿyÿ„ÿ‰ÿŠÿ‹ÿŠÿŠÿŠÿ‹ÿ‹ÿŠÿŠÿŠÿˆÿ…ÿ€ÿyÿrÿkÿ^ÿVÿSÿXÿaÿiÿpÿuÿvÿsÿrÿqÿrÿrÿrÿrÿsÿsÿsÿsÿoÿlÿiÿeÿdÿdÿeÿfÿfÿgÿhÿgÿ:ƒ:j;ƒ;k;‚;l99m87n77o66p65p6€6p5€5q44s42t00v11v11v10v/0w./x..y//y.~.z-~-|-~-|-~-|-}-|,},|,~,},~+}*}*|(|*|)|'|&|&|(}(|(}'|%}%|&}%|&}&{%}%{$}$z#}#y$}$x$}$x$}$y$}$y#}#x#}#y"}"y"}#y#}#y"}"y#}#z"}"z"}"z"}"z#~#z#~"z"}"y!}!y!~!z!~!z!}!y!}!z!~!z!~!z!~!{!~!| ~ { ~ { !{ {{}{ ~ {!~!{ ~!{#~$z'.w8GrWƒfmp…zg|†wcoƒcfT€Cn2,u-€-y01{36{:‚@zE„KwR…UqY†]ha†eci…j`jƒjahacY|SiLzGqBx=x:w6{7v4~4w4€5w5€5w5€4w44w55w5‚4w3ƒ3w3ƒ3x3ƒ3x2ƒ1y1„0y0„/z/„.z.„-{-ƒ,{,ƒ-{-ƒ-{-‚.{.‚/{/‚.{/‚1{1‚1{1ƒ1{1ƒ1z2„4z4„5z5„5z5„5z5…4z4…5z5…5z5…5z4…4{5„5|6„8~<‚<>@€A}B‚F{I‚IvJ‚LtM‚MqN‚OpN‚OpS‚Xn[cnkrpw€xqx€tlsrhr‚qgs‚vgxƒxhxƒuhoƒjff„dbe…hcl†qbw…€b‡ƒŠd€j’€‘n’€’r‘”t”€•v“€Šv^;yBƒXv]‚Wqg‚asg}tpƒVtdƒ\xK‚DCG†O€U†[V‚O}U}i~jzn€lwcZxL€>|7}0,|,ƒ,{,…,z,…+z+…,z,…,{,„6{S„K}N‚V}W‚a{PIxW}QtNyXtRw[vXwe{p{z}}x}~{rx€wkx€yjz€zi{€|i{€{hx‚sjj„fkc„fkfƒjjoƒtiwƒzj}m‚€„s‡‰w‡}…x€}zxq€dyS€Cw72x5}<{E{R|]{jxv|v‚„r‚€n€€€n€p€q€€ss}€|ry€vpv€xrxvtqltd‚WuI‚;w2€-y+}+|,{,~.{-~+{+}){,}/z6z=yGtRz\pb{fkh~ifhheiiei€gge€`iZ€TkM€Co8€1s2~7u;}DrK}Vq^~epk~rpv~|o~~o~~}o{}}s{x„x…}†y…|‚z‚y„{…y†{‰z‹z‹{Šz‰zˆ{‡y‡{‡yˆz‰z‰|ˆ|…~~xu€kr`Ym_}dnk{qrw{|t~|}u~}~v~v|v~{ww~owdYvL=t4€-u+},x5w@{Rta}pt}‡xŠz‹|ŠwŠ}ŠwŠ~ŠyŠ|‰y‰|‰z‡|…{y{phyZ|VvSwWyaui|ourztysro~omqqkq€rhr€ris€rim€kjh€ejffigikihjgek:ÿ:ÿ;ÿ;ÿ;ÿ;ÿ9ÿ9ÿ8ÿ7ÿ7ÿ7ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ2ÿ2ÿ1ÿ1ÿ2ÿ1ÿ0ÿ/ÿ1ÿ2ÿ1ÿ2ÿ2ÿ1ÿ2ÿ2ÿ1ÿ/ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ+ÿ+ÿ)ÿ)ÿ+ÿ+ÿ+ÿ)ÿ*ÿ*ÿ*ÿ)ÿ(ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ&ÿ%ÿ%ÿ%ÿ$ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ#ÿ#ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ"ÿ"ÿ"ÿ#ÿ#ÿ#ÿ"ÿ"ÿ#ÿ#ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ ÿ ÿ ÿ ÿ ÿ!ÿ ÿÿÿÿÿÿ ÿ ÿ!ÿ!ÿ ÿ!ÿ"ÿ#ÿ%ÿ*ÿ2ÿ?ÿOÿ`ÿjÿtÿ}ÿ}ÿwÿlÿ\ÿOÿ@ÿ2ÿ2ÿ2ÿ5ÿ6ÿ8ÿ:ÿ?ÿCÿGÿMÿQÿWÿ[ÿ_ÿbÿfÿhÿiÿjÿgÿbÿ[ÿTÿMÿFÿAÿ=ÿ:ÿ7ÿ6ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ1ÿ1ÿ0ÿ0ÿ/ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ.ÿ/ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ4ÿ5ÿ5ÿ6ÿ8ÿ:ÿ<ÿ=ÿ@ÿBÿCÿDÿFÿHÿJÿJÿKÿLÿNÿNÿOÿOÿNÿOÿSÿTÿYÿ^ÿhÿnÿuÿxÿwÿuÿsÿrÿrÿqÿsÿvÿzÿzÿzÿwÿuÿoÿjÿfÿiÿjÿmÿtÿ{ÿÿˆÿŽÿÿ‘ÿ‘ÿ“ÿ•ÿ•ÿ–ÿ—ÿ—ÿ–ÿ•ÿ‘ÿuÿ^ÿVÿYÿPÿ3ÿ]ÿsÿÿ‚ÿ|ÿwÿjÿ\ÿTÿWÿRÿSÿMÿMÿYÿWÿQÿNÿUÿ^ÿfÿdÿ`ÿYÿMÿAÿ7ÿ.ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ,ÿ2ÿAÿ]ÿaÿPÿZÿ[ÿQÿ]ÿTÿ:ÿBÿEÿJÿTÿ^ÿeÿqÿzÿ}ÿ|ÿ{ÿxÿwÿxÿyÿyÿyÿ{ÿ|ÿ{ÿ{ÿvÿoÿjÿfÿeÿhÿhÿlÿpÿuÿuÿxÿ{ÿ|ÿÿ‚ÿ…ÿ‡ÿ‡ÿ…ÿ€ÿzÿqÿdÿSÿCÿ8ÿ3ÿ7ÿ>ÿIÿVÿaÿoÿyÿÿƒÿ…ÿ‚ÿ€ÿÿÿ€ÿ€ÿ€ÿ€ÿ€ÿÿ~ÿ~ÿ}ÿ|ÿyÿvÿvÿxÿxÿvÿqÿlÿdÿWÿIÿ;ÿ2ÿ-ÿ+ÿ+ÿ,ÿ,ÿ-ÿ,ÿ+ÿ+ÿ+ÿ-ÿ2ÿ9ÿAÿJÿTÿ\ÿbÿfÿhÿiÿhÿhÿiÿiÿiÿgÿdÿ_ÿYÿQÿJÿ@ÿ7ÿ2ÿ5ÿ=ÿBÿJÿSÿ\ÿbÿhÿnÿsÿwÿ{ÿ|ÿ}ÿ{ÿzÿzÿ{ÿÿÿ…ÿ†ÿ†ÿ…ÿƒÿƒÿ„ÿ…ÿ†ÿ‰ÿ‹ÿ‹ÿŠÿ‰ÿˆÿ‡ÿ‡ÿ‡ÿ‰ÿŠÿ‹ÿŠÿ…ÿ~ÿtÿiÿaÿ^ÿ`ÿhÿlÿsÿyÿ|ÿ~ÿ}ÿ~ÿ~ÿÿÿÿÿÿ{ÿwÿoÿdÿYÿJÿ;ÿ4ÿ-ÿ,ÿ/ÿ6ÿCÿXÿdÿtÿ€ÿˆÿ‹ÿ‹ÿŠÿŠÿŠÿŠÿŠÿŠÿ‰ÿˆÿ†ÿ‡ÿ…ÿ€ÿxÿmÿeÿYÿUÿRÿXÿaÿjÿpÿtÿrÿrÿqÿpÿqÿqÿsÿtÿsÿsÿrÿpÿmÿkÿjÿiÿjÿjÿhÿhÿhÿfÿdÿ^ÿ<‚„>uC…HtL†RqT…Xm]‡ahd‡feh„icgcc]|UiPyJoBx=v8u7{6v66v54v3€4v4€4w45w54w4‚5w5‚4w4ƒ3w3ƒ3x3‚3x2‚1y1ƒ/y/ƒ.z.….z.….{.ƒ-{-ƒ-|-ƒ-|-ƒ-{.‚.{.‚.{/‚1{1‚0{0ƒ1{2ƒ4{3„3{3„4{4„5{5„5z5„4z5„5z5…4|4…3|6…8~8‚:€<>@}BDzEGwGƒIuJƒKsLƒMqNƒNqO‚OpN‚PpO‚QnSYnbjpqyqvtpt€sgrqes‚veyƒ|f}ƒygwƒrfm„idj„icn„uczƒb…‹dŽ€h‘€’l˜™p˜~˜s™€šw˜€“wˆq„‚ik„Sjy‚l€mypo‚br]LvEV{^YUQƒO€MƒOPƒRY€a€\}QC|71-~-‚,|,„,{,…,{+†,{,†-z*…-y5‚Z|g~W}Ta|VcxT:vG{LuSyXx^zh{u||~}w}zqz€ymw€wkx€zj{€{k{€xks€lkg‚cmc‚ckf‚iml‚okr€uky€yo}€u~…x…}ƒz€|zxrdxTEw:5y8|AzN{[{g{rx{}vƒ~ƒt‚€pp~€q€€r€s~s{zry€vrv€wrx€wts‚ltcƒVuHƒ;x2-z+}+}+|+~*{*}*{*|+z.z4z;xFyNsVz\nc}fkg~hhg€ggh€hhh€hkd€_mW€OmG€ÿPÿdÿmÿxÿ~ÿ}ÿzÿzÿyÿxÿwÿyÿzÿzÿzÿxÿuÿoÿjÿdÿbÿbÿbÿbÿgÿkÿkÿoÿsÿuÿxÿzÿ}ÿÿÿ‚ÿ‚ÿÿyÿpÿeÿTÿEÿ9ÿ6ÿ:ÿCÿPÿ^ÿjÿvÿÿ„ÿ…ÿƒÿ‚ÿ€ÿÿÿ~ÿÿÿÿÿ~ÿ~ÿ}ÿ{ÿzÿyÿvÿwÿyÿxÿwÿsÿlÿcÿVÿHÿ;ÿ1ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ0ÿ6ÿ=ÿGÿOÿWÿ_ÿfÿhÿiÿgÿgÿgÿhÿhÿgÿfÿbÿ]ÿWÿOÿGÿ?ÿ<ÿ@ÿDÿIÿQÿXÿ\ÿbÿhÿlÿlÿrÿuÿvÿvÿsÿrÿsÿwÿzÿ}ÿ‚ÿ…ÿ‡ÿ†ÿƒÿ„ÿ„ÿ„ÿ‡ÿˆÿ‹ÿÿŒÿ‹ÿŠÿŠÿŠÿ‹ÿ‹ÿŒÿŒÿ‹ÿŠÿ„ÿ~ÿuÿkÿ`ÿ`ÿfÿlÿrÿxÿzÿ|ÿ}ÿ}ÿ~ÿ~ÿÿÿ€ÿÿ~ÿzÿsÿkÿbÿSÿDÿ8ÿ1ÿ,ÿ+ÿ1ÿ;ÿMÿ_ÿnÿ{ÿ…ÿŠÿ‹ÿŒÿ‹ÿ‹ÿ‹ÿŠÿŠÿ‰ÿˆÿˆÿ‡ÿ†ÿÿ|ÿsÿiÿ^ÿSÿPÿRÿ[ÿbÿjÿrÿtÿtÿrÿrÿrÿrÿrÿrÿsÿtÿtÿsÿqÿoÿnÿlÿlÿlÿlÿjÿhÿfÿ`ÿ[ÿVÿ;‚;i9‚9j9‚9j9€8k7€6n6€6o5€3p4€3p22r1/s/~/t.€/u/€2t27s8††BlB„@k=ƒ9m5‚5p4‚2s1‚1v/‚.v.€.v.€-w--y++|*)}))~)})~(}(~'~'~(~'~&~&|%~%|%~%{$~${%~%{%~%z&~&y%~%y$~${%~%{$}$y#}#y#}#y#}#y#}#y#}#y"}"x"}"x"}"w"}"w"}"w"}"x#~#z"~"z"}"z!}!z!}!z!}!z ~ z ~ {~{~{~{~{~{~{{{ { {  {!!{!!{""{#%|&*z3BuNƒ^mh†tf}†z`s…jcb„UfG…AkB†CmH‡LlP‡TkWˆZh]†_db…dbe…fbg‚ebb€\gW~OmGzBs;w8|5w4}5v55v56v6€5v5€4w45w56w6‚5w5‚5w5ƒ4w4ƒ4x4‚2x2‚0y0ƒ0y0ƒ0z/‚.z.‚-{-ƒ-{-ƒ.|.‚-|-‚-|-/|//|01|10~1‚1~2‚1~2ƒ2~2ƒ2~2ƒ2~2ƒ4~2ƒ3~4ƒ3}5‚67‚8€<=>|A‚ByD‚EvE‚EtHƒIsH‚IsKƒLqLƒMpOƒOpM‚NpO‚NpNMqOPrU~^sg~rtvvpt€sjt€sfttfwyg|}hzugo‚lejƒlflƒmfp‚sbz_„Š`f”€•m–˜sš™u—~•w‘~‹v„€p|{n{}k~€n€€{ptkr^€RvNU}OP‚UV‚V€MƒGK†OS†S~T„W€RƒG€;‚4+ƒ.|.„-{,…,{,†-{-†-z,….y.„0z5‚:z=Q{N}QuE{DsOzPvC>{P„k~g~w~|w{xqw€wmy€yky€zky€xlu€tll€ilb[l]^laenigom€ppq€uqw}yu{{~x{{}}wzlbyRFx8}9y={HzT{bzn{yw‚}†v„~ƒrpp~q~r~t}|t{yrx€vrx€yrx€wts€ltb‚VuG‚9u0€+x)})})|)~*|*){)~+z1{8z@wIyRqZ{anf}gii~hgh€hgh€ghg€eka€ZmU€NpF€BoA€EnJPnY€[o_€doh€kom€por~qoo~mno|rpv{zu~y€|ƒx„€…x„~„y„{„z‡z‰zŒzŽ{Žz‹{Šz‹|‹zŒ|Œz||Œ|Š|„~~wr€gobblg}mrt{yvz{{v}|~u~v~w€}x}}xyr€iy]‚NtB‚7t.+v,|3y?vO}btruˆ}‹z‹yŒ|‹w‹~‹x‹}‹yŠ}‰zˆ}‡|†}|y~q{g[wQ|PvRx[|cul|qwuxv{tpsrls€skttkt€tjr€rhp€pfo€lgk€kikfkc\nUPp;ÿ;ÿ:ÿ9ÿ9ÿ9ÿ8ÿ8ÿ7ÿ6ÿ5ÿ5ÿ4ÿ3ÿ3ÿ3ÿ2ÿ2ÿ1ÿ/ÿ/ÿ/ÿ.ÿ/ÿ.ÿ1ÿ6ÿ:ÿ;ÿ@ÿCÿFÿIÿFÿDÿAÿ>ÿ<ÿ;ÿ8ÿ5ÿ5ÿ4ÿ3ÿ1ÿ1ÿ2ÿ0ÿ.ÿ.ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ(ÿ'ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ#ÿ#ÿ"ÿ"ÿ"ÿ"ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ ÿ ÿ ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ ÿÿ ÿ ÿ!ÿ!ÿ"ÿ#ÿ"ÿ"ÿ"ÿ#ÿ%ÿ'ÿ/ÿ:ÿGÿVÿcÿnÿxÿ|ÿwÿrÿjÿaÿSÿHÿHÿKÿNÿRÿVÿZÿ]ÿ`ÿaÿbÿdÿeÿfÿeÿdÿcÿ`ÿ\ÿUÿNÿFÿ>ÿ9ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ1ÿ0ÿ0ÿ0ÿ0ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ/ÿ/ÿ/ÿ0ÿ/ÿ/ÿ0ÿ1ÿ0ÿ1ÿ1ÿ2ÿ3ÿ3ÿ5ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ7ÿ8ÿ9ÿ<ÿ=ÿAÿAÿCÿCÿEÿGÿHÿHÿIÿIÿIÿJÿLÿLÿMÿNÿNÿNÿMÿLÿMÿLÿKÿHÿIÿJÿOÿWÿbÿlÿsÿvÿuÿsÿqÿrÿtÿtÿwÿyÿ{ÿ|ÿzÿuÿoÿlÿiÿhÿfÿgÿgÿlÿpÿyÿ|ÿÿ‡ÿÿÿ“ÿ•ÿ–ÿ–ÿ˜ÿ–ÿ’ÿÿ‰ÿ‚ÿÿyÿzÿ|ÿ~ÿÿ~ÿ~ÿyÿqÿjÿ]ÿUÿUÿRÿRÿNÿNÿLÿNÿIÿDÿGÿOÿPÿNÿJÿMÿPÿJÿDÿ9ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ,ÿ-ÿ-ÿ.ÿ3ÿ=ÿPÿWÿPÿLÿCÿIÿSÿQÿCÿAÿOÿmÿiÿwÿ|ÿzÿxÿwÿwÿxÿyÿyÿzÿxÿwÿtÿsÿlÿgÿ_ÿYÿXÿZÿ`ÿfÿkÿnÿoÿlÿnÿnÿoÿrÿvÿyÿ|ÿ}ÿ{ÿuÿkÿaÿPÿDÿ8ÿ9ÿ@ÿJÿVÿdÿqÿ|ÿÿƒÿ‚ÿ€ÿÿÿÿÿ~ÿÿ~ÿÿÿ~ÿ}ÿ|ÿyÿvÿvÿvÿxÿyÿxÿwÿsÿlÿbÿVÿGÿ9ÿ0ÿ+ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ,ÿ2ÿ9ÿAÿJÿSÿ[ÿbÿfÿgÿhÿiÿhÿhÿhÿgÿfÿbÿ_ÿYÿUÿMÿIÿDÿGÿJÿRÿXÿ^ÿ_ÿbÿeÿjÿlÿlÿmÿlÿkÿiÿiÿlÿpÿuÿyÿ|ÿÿƒÿ„ÿ†ÿ†ÿ„ÿ„ÿ„ÿ‡ÿ‰ÿŒÿŽÿŽÿÿÿ‹ÿ‹ÿ‹ÿ‹ÿŒÿŒÿŒÿŠÿ…ÿ~ÿrÿhÿcÿfÿjÿpÿuÿzÿ{ÿ|ÿ}ÿ~ÿÿÿÿÿ€ÿÿ|ÿwÿqÿhÿ]ÿNÿ@ÿ6ÿ.ÿ+ÿ*ÿ6ÿCÿTÿdÿtÿÿ‡ÿŒÿŒÿŒÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿŠÿ‰ÿˆÿ‡ÿ„ÿÿwÿnÿdÿVÿOÿOÿTÿ[ÿdÿlÿtÿtÿsÿsÿsÿrÿrÿrÿrÿsÿtÿtÿsÿsÿqÿpÿoÿoÿnÿmÿhÿcÿ_ÿWÿNÿIÿ<{?‚?y?„AwC„DwFƒFtHƒHsI„IpJ„JrK„KpK„LnJƒKnKKoM‚LoKJqH~FsC}DuI{Sv^}iuqtrvtks€shs‚uhw‚xiy‚{iz‚uiq‚kif‚dj`_jb€edhp_w{[…ŒaŽ€‘l’–u•~•w•~“xŽ~‡w~qy~ymxzn{€xqs€nvh€az^€[~Y€VT€M„MJ†IJ‡F€C‰G~K‰GCˆH~PˆML…H‚/)€.ƒ-{,„,z+…-{,†-z-…-z-…,y0‚6yFX{Q|QvIzQvU~GzA…={>ˆJ}k‚y~|y{zrw€vmu€wkzzjwulu€rml€dmZ€WlW€Yq^‚gsmcxF@}D}c}kznxqzuxzy~z{}uzj~_xP~Cx9}=zD{O{]{hzu}}v‚~‚t€q~~p~~p~~p~~s}~u{ztw€wqv€vqw€xrx€vtr€ktaSuF8v0,y(}('|'~'{(({)~-z4z;zBuK{Sq\|cmf~gjh€hhi€ihi€iif€cl`€[lXRjL€KhLQjV\k_€cmeini‚ipjiohdmd|doi|nrt{xw{{|‚yƒ~„y„}„y…|…y‡zŠzz{zŽ|ŽzŒ|Œz||Ž{Ž~Œ|ˆ{…tt€koggnm}stv|yv{||s}|}t~}w~~~w}€x}xwo€fvXƒKq?ƒ5s-+w.{8|FwXjuw~„uŠ|ŒzŒx}Œw‹}‹y‹}‹y‹}Š{‰~‡|„~~|u€lybWtP~NuSy^}gvm}txtys|sqq~qmr~rjttjt~uju~ugt€sgpogo€lhf‚`kZQqI}Hv<ÿ<ÿ;ÿ:ÿ9ÿ8ÿ8ÿ7ÿ5ÿ5ÿ5ÿ4ÿ3ÿ3ÿ2ÿ2ÿ1ÿ0ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ0ÿ3ÿ6ÿ:ÿ?ÿDÿHÿLÿRÿRÿPÿNÿMÿKÿIÿHÿFÿDÿDÿCÿ?ÿ@ÿ@ÿ>ÿ<ÿ;ÿ9ÿ9ÿ5ÿ2ÿ-ÿ+ÿ*ÿ)ÿ)ÿ'ÿ&ÿ&ÿ&ÿ&ÿ%ÿ$ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ"ÿ#ÿ#ÿ#ÿ#ÿ#ÿ%ÿ$ÿ$ÿ#ÿ$ÿ$ÿ$ÿ$ÿ"ÿ!ÿ!ÿ!ÿ!ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ ÿ ÿ ÿ ÿ#ÿ"ÿ#ÿ#ÿ"ÿ!ÿ$ÿ&ÿ'ÿ.ÿ8ÿFÿXÿeÿpÿzÿ}ÿ{ÿwÿrÿlÿeÿ`ÿ^ÿ^ÿ]ÿaÿdÿfÿfÿiÿjÿiÿiÿiÿgÿcÿ_ÿ[ÿTÿMÿFÿ>ÿ9ÿ9ÿ9ÿ8ÿ8ÿ6ÿ6ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ8ÿ8ÿ8ÿ:ÿ8ÿ9ÿ9ÿ7ÿ6ÿ6ÿ4ÿ3ÿ3ÿ0ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ0ÿ/ÿ0ÿ/ÿ/ÿ0ÿ0ÿ.ÿ1ÿ4ÿ4ÿ4ÿ5ÿ6ÿ6ÿ6ÿ8ÿ:ÿ:ÿ<ÿ=ÿ>ÿ=ÿ>ÿ@ÿ@ÿAÿBÿBÿDÿDÿFÿFÿFÿFÿHÿIÿJÿIÿIÿIÿKÿKÿKÿKÿLÿLÿLÿLÿKÿKÿKÿKÿLÿKÿLÿJÿHÿGÿCÿBÿ@ÿ?ÿCÿLÿYÿdÿqÿtÿvÿtÿsÿsÿtÿtÿwÿyÿyÿyÿxÿvÿpÿiÿbÿ\ÿXÿWÿZÿ\ÿ`ÿkÿrÿxÿ€ÿ‡ÿŠÿÿ‘ÿ•ÿ•ÿ–ÿ”ÿ’ÿŽÿˆÿÿ|ÿyÿyÿwÿvÿtÿnÿeÿcÿaÿZÿYÿWÿVÿYÿWÿMÿKÿLÿJÿEÿ>ÿ<ÿCÿGÿGÿGÿIÿPÿSÿSÿ\ÿLÿ%ÿ-ÿ.ÿ,ÿ-ÿ,ÿ+ÿ,ÿ,ÿ-ÿ.ÿ-ÿ-ÿ.ÿ4ÿLÿMÿWÿfÿ[ÿSÿTÿ@ÿ<ÿ7ÿ:ÿIÿiÿzÿzÿ{ÿxÿvÿvÿuÿwÿyÿyÿsÿrÿuÿrÿjÿdÿ\ÿXÿZÿ[ÿjÿhÿMÿ=ÿDÿ@ÿ8ÿ]ÿiÿeÿjÿsÿyÿzÿxÿsÿhÿ]ÿLÿ@ÿ;ÿ?ÿGÿTÿ`ÿmÿwÿ€ÿ„ÿƒÿ€ÿÿ~ÿ~ÿ}ÿ}ÿ}ÿ}ÿ}ÿÿ~ÿ}ÿ{ÿyÿwÿvÿvÿvÿwÿxÿxÿvÿqÿjÿaÿSÿFÿ8ÿ1ÿ+ÿ(ÿ(ÿ'ÿ'ÿ'ÿ(ÿ(ÿ*ÿ.ÿ4ÿ=ÿFÿNÿWÿ^ÿeÿgÿhÿgÿhÿiÿiÿiÿiÿfÿcÿaÿ^ÿZÿVÿPÿOÿRÿWÿ[ÿ^ÿaÿeÿgÿhÿhÿiÿfÿbÿ_ÿ^ÿ_ÿcÿiÿmÿtÿxÿ{ÿÿÿ‚ÿ‚ÿ‚ÿ‚ÿ„ÿ†ÿˆÿŒÿŽÿŽÿŽÿÿÿŒÿŒÿÿÿŽÿŽÿÿŠÿ…ÿ|ÿsÿjÿiÿiÿoÿtÿzÿ|ÿ{ÿ|ÿ|ÿ}ÿ~ÿÿÿÿÿ€ÿ}ÿwÿoÿfÿXÿHÿ=ÿ4ÿ-ÿ-ÿ1ÿ;ÿLÿ\ÿnÿ}ÿ„ÿŠÿÿŒÿÿŒÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿŠÿ‰ÿ‡ÿ„ÿ~ÿtÿhÿ^ÿRÿNÿPÿTÿ]ÿeÿlÿrÿuÿsÿsÿrÿrÿsÿsÿtÿtÿtÿuÿuÿuÿuÿtÿqÿpÿmÿhÿcÿ[ÿSÿMÿHÿJÿ;;j:9j8€7k7€7k5€4l3€3o3€2q1€0r1€/r--s..t.€.u.2s6„;q?ˆEkI‰LfRˆSdS‡RcQˆOeOˆMeKˆLgKˆJgIˆIgJˆIhF†DkB†@l=ƒ9m51p.*v)~(x'}(z'~&{%~%}%~%}&~&{&~&{&~&z&~&z%~&z&~&z%}%y%}%y%}$y%}%y$}#y#}$y$}$y#}#y#}#x#}#x$|%x$|%x%}%y%}%y%|%{$}#{"}"z!~!z ~ z ~ z~{~{ ~ {~{{{{{~z~z ~ y ~!{!~ |~| ~ |!~!|!€!}$€&}(€-x5ƒ@rQ†`kl†vd}†c|†xat‡p_lˆk_hˆh`hˆk`k‡kak†kaj„jai‚ebb^fYyRlKvDt?t;z9t99t97t7~7t78u89v8€8v88z9|;~€?x?€>w>~:y6}4}2{2€1{/‚1{1‚0|/€2~1~13}21{34x5ƒ7w7ƒ;t<„~;v:|9x=}IyU~cwo€tru€sks€sgs‚tgw‚why‚ziw‚tkp‚ek\SpP~NoP~Sk\cam‚qVy‚‚Xˆe‘€‘o“~“u’~‘vŒ~†w|rxwnwwopft]Y{VRQ€M„N€V…Q€F†G€FˆC€;‰;€?‹CG‹HK‹M~O‰MO…Q]~@ƒ#}*‚,€-~,…-{,†,{,…-{,…-{-…0z<…|Nu_€ot}€‰x‹||ŒyŒ}‹y‹}‹z‹}‹yŠ}‰{‡~†{„~{{q€fw[QqO~QsYy`zixq}tytyq|qqq~qmr~slstkt~ukw~wiu~tgr€ogl‚gh`ƒXlNƒHtE|Jy:ÿ:ÿ8ÿ8ÿ7ÿ7ÿ7ÿ7ÿ5ÿ4ÿ4ÿ2ÿ2ÿ2ÿ0ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ/ÿ2ÿ4ÿ7ÿ=ÿBÿGÿKÿQÿRÿRÿRÿSÿRÿPÿOÿNÿMÿNÿPÿPÿPÿQÿRÿOÿPÿLÿGÿDÿAÿ=ÿ9ÿ4ÿ0ÿ+ÿ)ÿ(ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ#ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ$ÿ$ÿ%ÿ%ÿ'ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ#ÿ"ÿ"ÿ!ÿ!ÿ ÿ ÿ ÿ ÿÿÿÿÿ ÿ ÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿ ÿ ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ ÿ ÿ!ÿ!ÿ!ÿ"ÿ%ÿ'ÿ)ÿ.ÿ4ÿ>ÿJÿZÿfÿqÿ{ÿÿ€ÿ|ÿyÿwÿtÿuÿsÿsÿqÿpÿoÿoÿpÿpÿoÿoÿjÿgÿdÿ^ÿUÿNÿHÿAÿ<ÿ;ÿ9ÿ9ÿ9ÿ9ÿ8ÿ8ÿ8ÿ8ÿ9ÿ9ÿ8ÿ9ÿ;ÿ;ÿ=ÿBÿFÿIÿJÿMÿNÿKÿIÿCÿ@ÿ8ÿ5ÿ3ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ4ÿ6ÿ7ÿ:ÿ;ÿ=ÿ<ÿ?ÿ@ÿDÿFÿFÿIÿKÿPÿPÿPÿRÿUÿWÿWÿYÿ[ÿZÿ]ÿaÿaÿaÿcÿdÿgÿfÿfÿeÿeÿhÿgÿeÿeÿeÿeÿeÿdÿdÿbÿaÿ_ÿ]ÿ\ÿ[ÿXÿTÿQÿNÿLÿJÿHÿGÿDÿBÿ?ÿ<ÿ:ÿ8ÿ5ÿ5ÿ<ÿDÿRÿ^ÿjÿtÿvÿtÿsÿsÿsÿtÿvÿwÿxÿwÿwÿtÿmÿcÿZÿMÿGÿAÿEÿLÿUÿ^ÿiÿoÿvÿ~ÿ‡ÿŒÿ‘ÿ‘ÿ“ÿ‘ÿ‘ÿ‘ÿÿƒÿ~ÿzÿxÿvÿwÿxÿsÿkÿcÿ]ÿXÿSÿMÿJÿMÿRÿQÿJÿFÿDÿDÿAÿAÿCÿCÿGÿLÿOÿQÿNÿLÿNÿJÿ^ÿiÿLÿ*ÿ&ÿ-ÿ-ÿ,ÿ-ÿ,ÿ,ÿ-ÿ,ÿ-ÿ/ÿ0ÿ7ÿ>ÿ`ÿkÿ_ÿ_ÿKÿ:ÿ9ÿ6ÿMÿaÿqÿyÿ}ÿyÿwÿwÿwÿuÿvÿwÿpÿ_ÿVÿ_ÿhÿiÿeÿaÿ\ÿaÿSÿ=ÿEÿHÿ?ÿKÿXÿ`ÿYÿWÿ[ÿfÿrÿyÿxÿuÿnÿeÿXÿLÿ?ÿ@ÿEÿNÿ\ÿiÿtÿ|ÿÿ‚ÿ€ÿ€ÿÿ}ÿ}ÿ}ÿ}ÿ}ÿ}ÿ|ÿ~ÿ}ÿ|ÿzÿyÿuÿtÿrÿrÿsÿuÿtÿqÿoÿhÿ^ÿRÿEÿ8ÿ0ÿ+ÿ(ÿ(ÿ'ÿ'ÿ&ÿ'ÿ)ÿ,ÿ1ÿ9ÿCÿKÿSÿ[ÿcÿgÿhÿhÿhÿhÿhÿhÿiÿiÿhÿhÿfÿeÿaÿ]ÿ[ÿ[ÿ[ÿ\ÿ_ÿaÿcÿeÿcÿ`ÿ]ÿYÿWÿTÿRÿWÿ^ÿeÿkÿqÿtÿxÿzÿ~ÿÿÿÿÿ‚ÿ…ÿˆÿŒÿŽÿÿÿÿÿÿÿÿŽÿŽÿŽÿÿÿŠÿ†ÿ€ÿxÿpÿmÿnÿtÿxÿzÿ{ÿ|ÿ{ÿ|ÿ}ÿ~ÿ~ÿ~ÿ€ÿÿ€ÿ}ÿwÿqÿgÿZÿKÿ=ÿ4ÿ/ÿ/ÿ3ÿ?ÿRÿcÿrÿ€ÿˆÿ‹ÿŒÿ‹ÿŒÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿŠÿ‰ÿ‡ÿ†ÿÿzÿoÿdÿ[ÿQÿNÿSÿYÿ`ÿjÿqÿsÿsÿsÿsÿrÿpÿrÿsÿtÿuÿuÿvÿwÿwÿwÿwÿsÿpÿmÿeÿ^ÿUÿLÿEÿDÿPÿ88h88j7€7j7€6l6€4n3€1p1€1r1€/r.-r--s,,u,€,x,€.v0€5u9„?pC…GlL†PhQ‡QeQˆQgQˆPfP‰OfQ‰SfT‰TdVŠVdSŠUfSŠSfP‡KgG„Ah<€5l2~-p)~(v'}(y''z%}%{&~&{&~&y&~(y'}'y%~&z&~&z%~%y&~&x&~&x%~%x%}%x%}%x%}%x&}&x$}$x%}&x&|&y&|'y&|%z%|%z$}$z$}$z#}#z"}"z ~ { ~ {~{~{~z~zzzzz~{~{~{ ~ |!~!|!~ |!!|""|"€${$€&z+€/y4‚;sD‚Qn`„jjs…|g…~c|…z_z†z\x‡v_x‡v^v‡u^r…t^r„ran‚ibd}]gUxNlFt?u;s:{;t;:s:9r9€9r9:s:€:w:}=|AyGƒPrTˆYk\‰_g`ˆ^f[†UhM‚Ap6€5x4€5}4€9{;„=x@…CuE„FoI‡MmMˆOmS‰VlVŠZkZ‹\h^Œ_fbŒefd‹gfh‹ifk‹nem‹odr‰scs‰tctˆudvˆucv‰vbw‰wcv‰vct‰udtˆtcrˆp`oˆo`lˆi`gˆd`_ˆ[`V…ObK„GfD‚@k=€8r7~4v3~4y<€GyP€]wi€rrt€tlssgstfv‚xivyjv‚thn‚djVLqA:t>GqR}[ge‚nZu~Y…Œd€‘o“‘u‘~wŠ~…tzqx€vou€wnv€orl‚fv^ƒX}RV‚W€T‚QK„H€D‡BAŠCB‹DD‹ILŠN~T‡SL„M€JƒD‚l}\ƒ9z+-€-~-…,z/…2{/ƒ/{/ƒ1|@ƒ\zrƒmz_‚[{C„9{8‡>}X„f}]|p~|vzxpvvmvulvon]]sb^x`[{X‚Z|Uƒ[}I†^~F…@{Dƒ]xV€MtQbulqvu€rus€pwgYxKBxB{H{Sz_|lzvz}w~s~r}}r}}r}}s}~s}€{rx€vqu€rpq€qpr€spr€prm€hr]QuE8y2+|)}(~(|&€'|()z-}4{ƒ5r2€2v9zD}Vugvu‚€ŠyŒ{Œ{‹x}Œz‹}‹z‹}‹{Š}‰{ˆ}†|~xynbrY‚QmNSq[{bzkyq}r{rwq|qoq}qmq~rlrtmvwkvvjwwhu€qfo„eg]…TmJ„EvH|U}8ÿ8ÿ8ÿ7ÿ7ÿ7ÿ6ÿ5ÿ3ÿ4ÿ3ÿ1ÿ1ÿ/ÿ0ÿ0ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ.ÿ2ÿ6ÿ9ÿ>ÿBÿGÿHÿJÿJÿLÿLÿMÿLÿNÿPÿPÿRÿSÿSÿWÿYÿZÿXÿXÿXÿVÿSÿPÿLÿGÿBÿ:ÿ4ÿ/ÿ+ÿ)ÿ(ÿ'ÿ'ÿ&ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ(ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ'ÿ&ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ"ÿ!ÿ ÿ ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ ÿ!ÿ!ÿ!ÿ ÿ!ÿ"ÿ"ÿ"ÿ#ÿ%ÿ%ÿ(ÿ+ÿ/ÿ2ÿ7ÿ>ÿJÿWÿbÿlÿuÿ}ÿÿ~ÿ|ÿ|ÿ|ÿ|ÿzÿ|ÿzÿzÿyÿwÿwÿuÿuÿsÿmÿgÿ]ÿUÿNÿEÿ?ÿ=ÿ<ÿ;ÿ;ÿ<ÿ<ÿ;ÿ;ÿ;ÿ:ÿ9ÿ;ÿ;ÿ@ÿEÿMÿVÿ^ÿeÿkÿnÿpÿrÿqÿoÿjÿ`ÿQÿCÿ;ÿ:ÿ=ÿ@ÿCÿHÿIÿMÿQÿTÿVÿXÿZÿ[ÿ]ÿcÿdÿgÿiÿkÿmÿnÿpÿrÿrÿqÿtÿtÿuÿwÿyÿzÿyÿxÿ{ÿ|ÿ~ÿ}ÿ~ÿ~ÿÿÿÿÿÿ~ÿ~ÿ}ÿ|ÿ}ÿ}ÿ}ÿ|ÿzÿzÿzÿyÿwÿtÿpÿjÿfÿ_ÿXÿPÿJÿBÿ=ÿ9ÿ4ÿ3ÿ4ÿ8ÿ>ÿFÿRÿ^ÿjÿsÿuÿuÿsÿsÿsÿtÿwÿxÿwÿxÿxÿtÿmÿcÿPÿ@ÿ2ÿ2ÿ8ÿBÿNÿXÿaÿjÿuÿ}ÿ†ÿŒÿÿ‘ÿ’ÿ‘ÿÿÿ‹ÿ…ÿÿ|ÿxÿvÿvÿwÿxÿxÿuÿqÿjÿhÿaÿ^ÿXÿSÿPÿPÿKÿGÿFÿFÿGÿEÿEÿDÿFÿHÿIÿOÿYÿSÿDÿBÿBÿJÿ`ÿhÿ6ÿ)ÿ+ÿ-ÿ-ÿ,ÿ.ÿ.ÿ1ÿ1ÿ;ÿOÿhÿhÿVÿWÿOÿ9ÿ9ÿ9ÿJÿ`ÿeÿSÿ]ÿ{ÿ{ÿyÿuÿuÿuÿtÿoÿhÿ^ÿcÿ_ÿVÿWÿUÿPÿOÿLÿOÿbÿ\ÿ]ÿ]ÿEÿOÿRÿ_ÿgÿsÿmÿtÿpÿ}ÿuÿqÿdÿWÿJÿAÿCÿJÿVÿcÿoÿxÿ€ÿƒÿÿÿ~ÿ}ÿ|ÿ|ÿ}ÿ}ÿ}ÿ}ÿ}ÿ{ÿ{ÿzÿyÿvÿrÿqÿqÿqÿqÿrÿrÿpÿmÿhÿ]ÿQÿDÿ7ÿ0ÿ,ÿ)ÿ(ÿ(ÿ)ÿ'ÿ(ÿ+ÿ.ÿ6ÿ>ÿIÿQÿXÿ`ÿfÿhÿiÿiÿhÿhÿiÿiÿhÿjÿjÿjÿiÿhÿfÿaÿ]ÿ]ÿ]ÿ_ÿaÿaÿ_ÿ]ÿYÿTÿQÿMÿJÿLÿPÿWÿ`ÿhÿmÿrÿwÿzÿ{ÿ~ÿÿÿÿÿ‚ÿ…ÿ‡ÿ‹ÿŽÿÿŽÿŽÿŽÿŽÿŽÿŽÿŽÿÿÿŽÿÿŒÿˆÿ…ÿ~ÿuÿtÿtÿxÿ{ÿ{ÿ|ÿzÿzÿ{ÿ}ÿ~ÿ~ÿ~ÿ€ÿ€ÿÿ~ÿyÿrÿhÿ[ÿMÿAÿ8ÿ3ÿ3ÿ;ÿGÿXÿhÿxÿƒÿŠÿŒÿŒÿ‹ÿ‹ÿŠÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿŠÿˆÿ†ÿÿxÿnÿbÿWÿRÿRÿWÿ_ÿfÿkÿqÿrÿrÿoÿoÿpÿqÿqÿrÿrÿtÿvÿwÿzÿzÿyÿxÿwÿtÿpÿiÿ`ÿTÿKÿGÿJÿ\ÿ99h97j6€5j5€3l3€3m1€/o/€/p.€.p..q,+r++t,€,v+€+v+€.u/3r7‚9o=AlB‚EjG…GjH…IiK†NgN†NgP‡PgU‰WdX‰YdZ‰ZdWŠWeV‰SdQ†IeC‚>g84l.€+p*'r'}'v&~&w&~'x(~(y(}'y(~(y(~(y'~'x'~'x'~'x'~'x'})x%}%x&}'x'}'x(})x(}'x'|&y'|'y'|'z%|%z$}$z$}$z%}%z$}$z"~!{ ~ { ~ {~{~z~zzzzz ~ {~{~z!~!{!~!|!~!|""|!"|$€&|(€*{,/z2€6ww>t>~}A|GxQƒVn_‡ics‡z\}‰ƒ\„ˆƒ]†{bq†chU„HnE…HrK‡OoSˆXl[‰_hcˆhdh‰lam‰n`q‰r_s‰t]u‰w]w‰y]|‰z]{‰{\{‰|\~‡~[‡Z€ˆWˆ‚Wƒ‡ƒX‡‚Wƒ‡„Y…‡„Z„‡„\‚‡]‚†^†^‚‡‚]ƒ‡]‡€_}‡z_u†o_i†bbZ†QgL†Fj@„ƒAnCƒIpR^qh€qpu€uktsjsshv‚vfxƒyfy…xdy‰{e{‹|g^Š/m-„?sI€So^€jdt|a…i’€’r’uŽwŠ~…u€|ry€wpx€vnxynzxpx‚rri€cxW€V~QRƒN€M…IK‡HFˆHEˆF€GˆI€M†V€L‚CAƒD~B†?U‚K€$~*€/€,}/ƒ/~1‚<€`S€K|Q{P\xS€N{9…;};ˆQ}_‚jkzetvzzptsmwqlebo^ZtRƒP|S‚Q‚JJ…F€D„RS|d‚atZ‚JoN€OhW€bfjiih_mWlubUwI~CwE{OzZzhzpz{z}v€~r~}q||q||q||r|{rz€yqw€tpp€oon€noq€rpq€orl€gs]PwC7x0+{)}(~(|('|)+z1{8{BvJzTq[{blg}ihi~ifhhfi€ihi€jhj€iei€gcd‚_]_„_]^ƒ^b_ƒ^eYƒWkP‚LnI€GnG}KrQ{Xubzjup{uxvzzz|y}~yx}zƒ|ˆx‹}Žz}Ž|Ž||||}Ž|~z~{{ˆ|…xxtuurx~{v|||xz|{u}}}w}}~x~€x€}~y|}xvq€hs^ƒQpD„;q4€4v>zK}]tm|v‡€‹zŒ{‹|ŠxŠ}‹y‹}‹yŠ}Š{‰}‰{ˆ}†{€~xxmaqW‚TlT[qa{gzmyq{s{sup|oop}qmr~slrsmuwmz|k{zjw€uio‚hgaƒUlL„IvP|b}8ÿ8ÿ7ÿ6ÿ5ÿ4ÿ4ÿ2ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ+ÿ+ÿ-ÿ/ÿ1ÿ4ÿ8ÿ8ÿ:ÿ=ÿ=ÿ@ÿCÿCÿEÿFÿHÿJÿJÿPÿSÿUÿWÿYÿZÿ[ÿXÿYÿXÿXÿRÿMÿIÿBÿ;ÿ6ÿ2ÿ.ÿ+ÿ(ÿ'ÿ'ÿ'ÿ'ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ)ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ(ÿ'ÿ(ÿ(ÿ'ÿ'ÿ&ÿ%ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ#ÿ#ÿ"ÿ!ÿ ÿ ÿ ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ ÿÿÿÿÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ"ÿ"ÿ!ÿ"ÿ%ÿ'ÿ(ÿ*ÿ+ÿ.ÿ2ÿ5ÿ:ÿ@ÿEÿNÿXÿdÿjÿqÿsÿuÿyÿ{ÿ~ÿ}ÿÿÿÿÿ~ÿ~ÿ|ÿ{ÿyÿyÿuÿlÿdÿ\ÿTÿMÿFÿCÿ>ÿ>ÿ=ÿ<ÿ<ÿ<ÿ=ÿ<ÿ?ÿDÿIÿOÿXÿ`ÿkÿvÿ€ÿ†ÿŠÿŽÿ‘ÿÿÿ‡ÿ}ÿqÿfÿYÿSÿVÿ\ÿ`ÿdÿgÿlÿoÿqÿsÿtÿvÿwÿwÿyÿ{ÿ{ÿ|ÿ}ÿ}ÿÿ~ÿ|ÿ}ÿ}ÿ}ÿ{ÿ{ÿ€ÿ€ÿÿÿ€ÿÿ€ÿ‚ÿ‚ÿ€ÿ‚ÿ‚ÿ‚ÿ‚ÿ„ÿƒÿƒÿƒÿ‚ÿÿƒÿ‚ÿƒÿ…ÿ…ÿ…ÿ‡ÿ†ÿ‡ÿ…ÿƒÿ‚ÿÿ|ÿxÿtÿmÿfÿ`ÿYÿVÿRÿOÿNÿNÿPÿVÿ_ÿiÿrÿtÿtÿtÿsÿsÿsÿtÿvÿvÿwÿÿ‘ÿœÿ ÿžÿ£ÿ ÿzÿ(ÿ3ÿEÿPÿ]ÿgÿuÿÿ‡ÿÿ”ÿ“ÿ‘ÿÿÿÿ‰ÿ†ÿ€ÿ|ÿzÿyÿwÿwÿxÿyÿ{ÿzÿvÿkÿiÿaÿQÿRÿXÿYÿRÿRÿNÿKÿIÿJÿJÿIÿJÿOÿQÿTÿQÿQÿAÿ6ÿDÿAÿ<ÿ;ÿWÿTÿ,ÿ(ÿ1ÿ2ÿ1ÿ<ÿOÿgÿoÿWÿbÿ^ÿYÿTÿMÿ7ÿ<ÿAÿSÿ^ÿdÿqÿuÿwÿxÿwÿvÿtÿvÿrÿdÿYÿOÿRÿ[ÿSÿNÿMÿMÿJÿKÿQÿ_ÿ[ÿZÿZÿJÿSÿ_ÿ^ÿ_ÿ\ÿIÿ[ÿaÿQÿ^ÿlÿaÿVÿHÿGÿJÿRÿ\ÿhÿuÿ}ÿƒÿÿÿ~ÿ|ÿ|ÿ|ÿ|ÿ|ÿ|ÿ|ÿ|ÿ|ÿ{ÿzÿxÿuÿrÿoÿmÿnÿmÿpÿpÿpÿnÿjÿfÿ\ÿOÿCÿ7ÿ0ÿ+ÿ)ÿ(ÿ(ÿ(ÿ'ÿ)ÿ.ÿ4ÿ<ÿGÿMÿUÿ`ÿfÿhÿhÿiÿhÿhÿhÿiÿiÿiÿjÿjÿiÿhÿeÿaÿ]ÿ]ÿ]ÿ\ÿ\ÿYÿVÿQÿLÿIÿEÿBÿBÿFÿLÿSÿZÿbÿjÿpÿuÿvÿzÿ|ÿ}ÿÿÿÿÿ‚ÿ„ÿˆÿ‹ÿÿÿŽÿŽÿÿÿÿÿÿ‘ÿ‘ÿÿÿÿˆÿ„ÿÿ|ÿyÿyÿzÿ{ÿ|ÿ|ÿzÿ{ÿ}ÿ}ÿ}ÿ~ÿÿ€ÿ€ÿ~ÿ|ÿzÿsÿhÿ_ÿRÿFÿ=ÿ6ÿ6ÿ?ÿOÿ`ÿpÿ~ÿ†ÿ‹ÿŒÿŠÿŠÿŠÿ‹ÿ‹ÿ‹ÿŠÿŠÿ‰ÿ‰ÿˆÿ†ÿÿwÿlÿ`ÿVÿUÿVÿ^ÿdÿjÿpÿrÿsÿsÿpÿoÿpÿqÿqÿrÿtÿuÿuÿwÿzÿzÿ{ÿzÿxÿvÿrÿlÿeÿ[ÿQÿNÿZÿiÿ7€7i6€5k33j31l10n//o..p--p--q++q+*s))t**u))v**u(+r--p/1p3€2p5€:m;v=~>w?}B{HyOXp`…ket‡~_‰ˆY”ˆ—Y•†•[”†^†…|br…eea‡chg‰kepˆtbuˆw^zˆ|\}ˆ}[|ˆ|X}‡}W€†€Y~†~X}…}W}…}W{„zV{„{V{ƒ{U}ƒ~S€„T„T„R‚„€S‚…‚V…W…W€…€X„Y„‚Uƒ…„X†…‡V‡‡„X†‡‡[…‡„_€‡|az‡vds‡ogl‡ggd‡_f\†\g_†fikƒsmu€vku€qis‚rgs…tdw‡ƒbšˆ£_¡†¢`¥‡«d®†¨ih‡,m>‚OrZipu€mŠ€‘r•~“w‘~vŽ~Œvˆ~„t€~|q{€ypw€wox€zozzownrkcxOD|T`€XYQLƒM€K„L€Q„R€U‚UP€JBO~F‚E~<ˆ=~:ŠBcƒX€JzVF|0^€y€s~rn}W~>~Q{UQ|:‡;}J‡Y~_‚V€izvƒrspƒvmx‚vlv€rki€UoRƒWw]‚T~HƒJ„JJ„L€RƒT€M~T€[sL[lU€Vg\Ui@‚`jg^l_ntbRuGHyK{Vzazl{v|~w~€s~~|q{~{pyyp{{r{{s|{qz€wps€poo€nnl€lno€ono€nqj€es[OuA€8x0+{*})(|((z)}.z6z>{HuQ{Xob}giijgi€ihi€iji€ikjlkiggi€dcaƒ^aZ„YaW„XbU„OfH€EkA€=n>CqIPqW~]tf}kvqztwxzzy}z~~y~y}‚y…|‡yŒ~z|{Ž{{‘|Ž{~{‘‘z‘‘z‹|‰z…}u|}s||v{}{v{{{v|||w~|~y{€y|}{{{{tr~hn^SlGƒ=n89uByS|dts‚€vˆ€‹z‹{Š|Šx‹}‹y‹}‹z‹}‹{Š}Š{‰}‡y}€vvj‚_nV‚RkW€_pe}nyrzuxt|sqp}oop~qnr~qnv~unv}xk{}}i~~~f|€zewqhjƒclY‚Xub|r{7ÿ7ÿ6ÿ6ÿ4ÿ3ÿ3ÿ1ÿ1ÿ0ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ+ÿ+ÿ+ÿ*ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ'ÿ*ÿ)ÿ)ÿ)ÿ,ÿ+ÿ-ÿ.ÿ1ÿ0ÿ2ÿ5ÿ8ÿ<ÿ=ÿ@ÿCÿEÿIÿMÿQÿUÿVÿWÿXÿ[ÿZÿ[ÿYÿXÿSÿLÿDÿ?ÿ:ÿ6ÿ1ÿ.ÿ-ÿ,ÿ+ÿ+ÿ+ÿ*ÿ)ÿ+ÿ+ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ(ÿ)ÿ(ÿ(ÿ'ÿ&ÿ&ÿ&ÿ%ÿ%ÿ&ÿ&ÿ$ÿ$ÿ$ÿ#ÿ"ÿ"ÿ"ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ!ÿ!ÿ"ÿ"ÿ"ÿ"ÿ$ÿ%ÿ%ÿ&ÿ'ÿ+ÿ-ÿ.ÿ0ÿ4ÿ6ÿ9ÿ<ÿ@ÿHÿNÿUÿZÿ`ÿdÿdÿhÿnÿpÿsÿxÿzÿ|ÿ|ÿ~ÿ}ÿ€ÿ‚ÿ€ÿ€ÿ|ÿzÿtÿnÿhÿaÿ[ÿUÿMÿGÿEÿCÿ@ÿ@ÿDÿIÿNÿVÿ`ÿhÿpÿ|ÿ†ÿÿ”ÿ—ÿ˜ÿ–ÿ–ÿ—ÿ‘ÿŠÿÿwÿmÿkÿnÿrÿsÿuÿwÿ|ÿ~ÿ~ÿ~ÿ~ÿ~ÿ}ÿ}ÿ}ÿ}ÿ}ÿ}ÿ|ÿ|ÿ|ÿ|ÿ|ÿ|ÿzÿzÿzÿ{ÿ{ÿ{ÿ}ÿ~ÿ~ÿÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿÿÿÿÿ€ÿ€ÿÿÿÿ‚ÿƒÿƒÿ„ÿ†ÿ„ÿ…ÿ†ÿ‡ÿ‡ÿ‡ÿ„ÿƒÿÿÿÿ}ÿzÿwÿwÿtÿpÿnÿmÿrÿtÿwÿwÿwÿsÿpÿnÿnÿxÿ„ÿ—ÿ¡ÿŸÿŸÿ¡ÿ¨ÿ¬ÿ°ÿ±ÿ®ÿ’ÿ4ÿ8ÿIÿZÿiÿuÿ‚ÿŒÿ’ÿ•ÿ“ÿ‘ÿÿŽÿŒÿˆÿ„ÿ‚ÿ~ÿ|ÿyÿxÿxÿxÿzÿzÿ{ÿ{ÿyÿrÿbÿNÿCÿPÿSÿSÿ^ÿXÿQÿQÿIÿMÿUÿVÿWÿQÿNÿ>ÿ<ÿMÿJÿCÿ:ÿ:ÿ;ÿ:ÿDÿcÿ^ÿRÿlÿcÿ\ÿpÿiÿfÿƒÿZÿ<ÿPÿ[ÿTÿ=ÿ=ÿ@ÿFÿLÿBÿXÿŽÿ”ÿ{ÿoÿxÿxÿzÿxÿrÿbÿVÿcÿbÿRÿIÿMÿKÿJÿJÿMÿRÿTÿ^ÿ]ÿ[ÿVÿSÿJÿDÿ[ÿhÿgÿdÿ^ÿ`ÿsÿ\ÿQÿKÿJÿKÿXÿcÿnÿyÿ~ÿÿ€ÿ}ÿ|ÿ{ÿ{ÿyÿyÿ{ÿ{ÿ{ÿ{ÿ|ÿzÿyÿuÿrÿoÿmÿmÿlÿlÿoÿoÿoÿnÿjÿeÿ[ÿOÿ@ÿ7ÿ0ÿ+ÿ*ÿ)ÿ(ÿ(ÿ)ÿ+ÿ1ÿ8ÿBÿKÿTÿ\ÿdÿhÿiÿjÿiÿhÿiÿiÿhÿhÿhÿiÿgÿeÿbÿ`ÿ[ÿXÿXÿXÿSÿNÿIÿDÿBÿ>ÿ:ÿ;ÿ?ÿDÿLÿSÿZÿaÿgÿmÿsÿvÿyÿ{ÿ}ÿ~ÿ~ÿ~ÿ~ÿÿ‚ÿ…ÿˆÿÿÿÿÿÿÿÿŽÿÿÿ‘ÿ‘ÿ‘ÿÿŽÿŒÿˆÿ†ÿ€ÿ}ÿ}ÿ}ÿ}ÿ{ÿ{ÿ{ÿ{ÿ|ÿ|ÿ}ÿ}ÿÿ€ÿ}ÿ|ÿzÿwÿrÿiÿ_ÿUÿJÿ@ÿ:ÿ;ÿEÿWÿfÿvÿÿ‰ÿ‹ÿ‹ÿŠÿŠÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿŠÿŠÿ‰ÿ…ÿÿuÿiÿ^ÿVÿRÿ[ÿbÿhÿoÿsÿtÿtÿsÿpÿoÿpÿqÿqÿsÿvÿuÿwÿzÿ{ÿ}ÿ~ÿ€ÿÿ~ÿyÿuÿnÿhÿ`ÿbÿmÿyÿ7€7i5€5k43k12m0/n..o--p,,p,,q++q++r**s*)s))t''t'(t))u))u*~*v)~)u*-u..s0~3r5€9q:‚?nCƒGmM…SkVˆYh[Š]f_Š_d]ˆZcV‡PcJ„Cf?ƒ:j63n1,p,},r+}*t*}+v+|,v+|+u+|+u+|*u*~*u*~)u+~,u*|,u,|,u+|+v*|*v+|+w)|)w*|*y(|'y&|&y&}'z'}'{%}%z$~${#~#{"~!{ ~ { ~ z~zzzzz~z~z~z~ z!!{!!|"~"~#~%~$~%}(~(},/|04{7€9y:€>w@~DvJ~PtQ}TqW}\mcgiinfq‚vcxƒ{`~„_€„]ƒ…ƒ\…}\z…v^o…hba‚XgV€QnK~FqG~KqRXna…gep†z^„‡Š[†“[—†—[—…—]—…’_Œƒ`x„s_s…v_w†y]z†z[{†}Z}„~Y}„~X}…{Y|ƒ|[{ƒ{\|ƒ|^zƒz`zƒz`zƒxayƒzaz„z_}„}^~ƒ~]‚ƒ‚]ƒ‚\‚ƒ‚^‚ƒ`€ƒ‚a€ƒ€bƒ€c„a‚„‚^‚„\„ƒ[‚…Zƒ……Z„„„Z„„†\†„…_‡„‡b„„‚e‚„€h€†}i}†}k}…~iƒ|jx„sgv†…b—‡¢`Ÿ†Ÿcš…e¦‚«g®ƒ¯g®„«dšƒAh5€GqYfvt‚s€”w•~”x‘~wŽ~w‰~†uƒ~r||pz€wox€wnz€{n|{osir`€SyS€R}\~h~b~WTLNW~X€T{R€K|8~5I}Cƒ=|;Š:~7:~>ŠT€h‚f^zD},{`}U|UoxV}RyQ}ZI}<ˆ>K‰UHƒ;V}z~Z€T€a~b‚f|d_}]€Z€as‚iGƒG„JJ†I€L†L€TX€XuPMlI€NeQdfc€`jS€Ro_€cmn€mpN~IsP{^yhzs|{|€y~~u|~{rz~zryzr{{s{{t{ysw€soq€nkl€lkk€lno€ooo€mrj€dt[PwB€7x0+{*})~*|)~)z/{4yÿCÿHÿOÿUÿWÿ\ÿ^ÿ`ÿ^ÿ^ÿ\ÿZÿUÿOÿIÿDÿ@ÿ=ÿ7ÿ3ÿ1ÿ/ÿ-ÿ+ÿ+ÿ,ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ)ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ%ÿ%ÿ$ÿ$ÿ#ÿ#ÿ"ÿ!ÿ ÿ ÿ ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ!ÿ!ÿ!ÿ!ÿ"ÿ"ÿ"ÿ#ÿ$ÿ%ÿ(ÿ(ÿ*ÿ,ÿ/ÿ2ÿ5ÿ7ÿ:ÿ>ÿBÿDÿDÿDÿEÿHÿLÿSÿYÿ^ÿbÿfÿiÿmÿoÿvÿyÿzÿ€ÿ‚ÿ…ÿ‰ÿ‡ÿ„ÿ‚ÿ|ÿyÿvÿpÿiÿdÿ`ÿZÿVÿUÿWÿZÿaÿhÿnÿyÿÿ‰ÿ‹ÿÿ“ÿ–ÿ–ÿ”ÿ”ÿ“ÿÿ‡ÿÿyÿwÿwÿxÿxÿyÿwÿzÿ{ÿ{ÿzÿ{ÿ{ÿyÿxÿyÿyÿyÿxÿxÿuÿuÿtÿtÿrÿrÿrÿsÿtÿuÿvÿvÿwÿwÿzÿzÿ}ÿ}ÿ}ÿ~ÿÿÿÿ~ÿ~ÿ~ÿ|ÿ|ÿ~ÿÿÿÿÿÿ~ÿÿ€ÿÿÿÿ‚ÿ‚ÿ€ÿ€ÿ‚ÿƒÿ†ÿ‡ÿˆÿˆÿˆÿ‰ÿˆÿ†ÿ…ÿ…ÿ…ÿ…ÿ…ÿ‡ÿ…ÿƒÿ€ÿÿ“ÿžÿ ÿÿ›ÿžÿ¡ÿ¦ÿªÿªÿ¨ÿ¦ÿ¥ÿžÿ–ÿTÿ1ÿHÿXÿgÿuÿƒÿŒÿ’ÿ•ÿ”ÿ‘ÿÿÿŒÿ‹ÿˆÿƒÿÿ|ÿ|ÿxÿyÿyÿzÿ|ÿ~ÿ}ÿ|ÿxÿrÿdÿ_ÿVÿWÿaÿdÿbÿ[ÿRÿPÿSÿPÿRÿVÿPÿKÿ7ÿ.ÿ3ÿ8ÿ;ÿ;ÿ8ÿ8ÿ>ÿCÿLÿbÿqÿkÿDÿVÿvÿkÿYÿTÿZÿmÿ_ÿPÿ@ÿ>ÿ?ÿQÿJÿAÿ@ÿBÿ:ÿ9ÿ<ÿ=ÿ<ÿ<ÿ<ÿ=ÿ=ÿGÿOÿQÿeÿ\ÿBÿEÿJÿKÿKÿKÿHÿOÿPÿUÿSÿPÿQÿRÿVÿWÿPÿ=ÿMÿOÿTÿTÿaÿbÿWÿJÿVÿaÿkÿvÿ~ÿÿÿ~ÿzÿzÿzÿzÿyÿ{ÿ{ÿ{ÿ{ÿ{ÿ{ÿyÿwÿsÿpÿmÿlÿlÿkÿlÿoÿoÿpÿnÿjÿdÿ[ÿMÿAÿ6ÿ0ÿ+ÿ+ÿ*ÿ)ÿ+ÿ,ÿ2ÿ7ÿAÿJÿRÿYÿaÿgÿjÿkÿkÿjÿiÿgÿfÿfÿfÿdÿbÿbÿ^ÿZÿVÿSÿOÿLÿGÿBÿ=ÿ7ÿ7ÿ3ÿ3ÿ7ÿ<ÿBÿHÿPÿWÿ^ÿeÿkÿoÿuÿyÿ|ÿ}ÿ}ÿ~ÿ}ÿ}ÿ~ÿÿ‚ÿ…ÿ‰ÿŽÿÿÿÿÿŽÿÿŽÿÿÿ‘ÿ‘ÿ‘ÿÿŽÿÿ‰ÿ†ÿƒÿÿÿ}ÿ|ÿ{ÿ{ÿzÿ{ÿ}ÿ~ÿ~ÿ~ÿ~ÿ}ÿ|ÿzÿxÿuÿoÿhÿaÿYÿPÿEÿ?ÿAÿLÿ]ÿlÿ{ÿ†ÿ‹ÿŒÿŒÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿŠÿ‹ÿ‰ÿ„ÿ~ÿtÿgÿ^ÿYÿYÿaÿhÿoÿvÿvÿtÿsÿpÿoÿnÿpÿqÿqÿrÿvÿuÿwÿzÿ{ÿ~ÿ‚ÿ„ÿ…ÿ„ÿÿ}ÿyÿuÿtÿxÿÿ‡ÿ5€5h5€4j20k0/m.-m-,n-,n++o**q((q((q))r''r''t((u('u''u((u''w('w'(x))y*~*w*~*w,.u/5r:ApF†NkU‡Xg\ˆ^e`ˆac_‰^a[‰XbR‡OcJ…EfA„|>x?|?w@}DvG}LsQWl\`jcggimdp€ucz‚~`ƒ†b‡…†_‡…„\€‡}\y‡u^r†odi„eid„dhf…kdp…x_‚†ˆ_Š…‹]Ž„^”„”\“„‘\‚‹\…‚}\xƒt\uƒw\uƒv^vƒu_u‚vau‚uaw‚tbrrdomek€jgj€hlg~glgekf~fkd€fki€kil€mgpqersdtubu€udutguugt€tiu€vjvwix‚yf|‚}d~‚aƒ‚^ƒ]‚‚€]„\…„…Z†„ˆ\‡„‡_‰„‰`‰„‰dŠ„ŠgŒƒ‹iŒ„‹h‰…“dŸ†›`™ƒ›b £h¤¤m¢{oœ}ši–”]‘„s\3‚DlXguuƒv€”y”~“y“xŽwŒ~ˆvƒ}t}€ypz~yny€ymz‚|n}}ow‚ssi‚cy][|d€d}e€c~X~X~X~Y|V€VzS€Cz62/~7ˆ=}<Ž:|7>{KS}xˆx}]ƒH|P€h|p}jgyX‚U}QQ…@€A‹?<7>ŒBDŠFG‹G€FŽEGŒF€F‹M€TˆF?…B‚B„CM…PN„K‚K…J€I…I€O}V€WtY€ShEEdO6e8FjUUlG~CpX}MuXzd|m{x}~€y~}tz~yry~ysy{rz{s{~{vz~xsv€spo€mmk€klk€lmm€oon€lqh‚btYKv?€4y0,y+},y,}.x1~8s@}GrN|Up\}dlj~lgjjgh€hih€gkf€flc€_l^€ZkU€QhL€HjB€>l:4p/~1v3}6w:~@vD~JuQ~Yt`~hum}swwzzx}z~{~z~|z~~y}‚y‡}‰zŽ~z|{{{|{~{‘{‘‘{z‹~ˆ{„{~z~~}y{|{xz|{x}|}y~|~z}{}yz|yxv|rsn}gmbZlPƒGmB€EtPza|ot}‚†v‹€‹z‹zŠ|Šy‹|‹z‹|‹z‹|‹z‹|Šzˆ~…x}€roh„_gZ„[ic€kps~wtw}vts}pomnnqqmq~rnt~vow}wpy{~rƒ{‡rˆ|‡o…~‚m|o|sˆv3ÿ3ÿ4ÿ2ÿ1ÿ1ÿ/ÿ/ÿ.ÿ,ÿ,ÿ+ÿ+ÿ,ÿ+ÿ+ÿ*ÿ*ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ'ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ,ÿ.ÿ1ÿ9ÿ?ÿDÿLÿOÿVÿZÿ]ÿ_ÿaÿaÿ_ÿZÿXÿXÿUÿQÿKÿGÿBÿ>ÿ7ÿ3ÿ0ÿ.ÿ.ÿ-ÿ.ÿ.ÿ.ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ,ÿ+ÿ+ÿ+ÿ*ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ#ÿ"ÿ ÿ ÿ ÿ ÿÿÿÿÿÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿÿ ÿ ÿ ÿÿ ÿ!ÿ!ÿ"ÿ"ÿ#ÿ#ÿ#ÿ$ÿ$ÿ%ÿ'ÿ'ÿ)ÿ*ÿ-ÿ0ÿ3ÿ7ÿ7ÿ;ÿ<ÿ<ÿ<ÿ<ÿ?ÿCÿGÿKÿPÿVÿYÿ]ÿ]ÿ`ÿdÿfÿjÿmÿrÿwÿ{ÿ€ÿ„ÿˆÿˆÿˆÿ‡ÿ…ÿÿ}ÿ}ÿxÿuÿsÿqÿpÿrÿtÿ|ÿ‚ÿˆÿ‰ÿŠÿÿŽÿÿ“ÿ“ÿ’ÿÿÿŠÿ‚ÿzÿuÿrÿqÿsÿrÿqÿrÿqÿoÿoÿlÿlÿgÿeÿeÿeÿaÿaÿ^ÿ]ÿYÿXÿWÿUÿUÿVÿWÿWÿWÿZÿ\ÿ`ÿaÿbÿdÿhÿhÿiÿiÿjÿjÿjÿjÿiÿhÿhÿjÿjÿiÿhÿiÿkÿnÿoÿrÿtÿwÿzÿ|ÿ~ÿÿ€ÿ‚ÿ„ÿ„ÿ„ÿƒÿƒÿ„ÿ…ÿ†ÿ‡ÿˆÿˆÿŠÿ‹ÿÿÿŒÿŽÿŽÿŽÿ—ÿšÿ˜ÿ”ÿ˜ÿžÿ¢ÿ¡ÿœÿ”ÿˆÿ€ÿ|ÿÿ’ÿÿÿ‚ÿAÿFÿXÿgÿuÿƒÿŒÿ’ÿ”ÿ“ÿÿŽÿŽÿŒÿŠÿ‡ÿƒÿÿ~ÿ|ÿ|ÿ|ÿzÿzÿ{ÿzÿ}ÿ}ÿ{ÿvÿ_ÿaÿ^ÿVÿ_ÿ^ÿdÿdÿZÿWÿVÿ^ÿ_ÿQÿWÿCÿ9ÿ3ÿ2ÿ9ÿ;ÿ;ÿ9ÿ7ÿ8ÿ@ÿHÿEÿ5ÿ=ÿIÿKÿCÿAÿlÿoÿiÿUÿGÿ>ÿ:ÿCÿAÿ=ÿGÿDÿBÿ=ÿ<ÿ;ÿ;ÿ8ÿ8ÿ5ÿ9ÿ9ÿ@ÿTÿNÿFÿ@ÿ?ÿEÿJÿJÿLÿKÿIÿFÿHÿNÿYÿbÿ[ÿUÿMÿFÿJÿUÿWÿNÿEÿKÿJÿEÿWÿNÿUÿ\ÿeÿqÿzÿ€ÿÿ~ÿ{ÿyÿyÿyÿyÿxÿyÿzÿ{ÿ{ÿ{ÿzÿxÿtÿqÿmÿmÿkÿkÿkÿlÿoÿpÿnÿlÿhÿbÿXÿJÿ?ÿ4ÿ/ÿ/ÿ/ÿ0ÿ2ÿ5ÿ9ÿ=ÿDÿJÿSÿZÿ`ÿgÿkÿkÿjÿjÿiÿgÿfÿfÿfÿeÿaÿ_ÿZÿVÿRÿKÿDÿAÿ;ÿ6ÿ0ÿ.ÿ,ÿ/ÿ2ÿ5ÿ<ÿAÿFÿMÿTÿ\ÿcÿiÿnÿsÿwÿzÿ}ÿ~ÿ€ÿ€ÿ€ÿÿÿÿ„ÿˆÿŒÿÿÿÿÿÿÿÿÿÿÿÿ‘ÿ‘ÿ‘ÿÿÿ‹ÿ†ÿ…ÿƒÿ€ÿ}ÿ|ÿzÿzÿzÿ{ÿ}ÿ}ÿ~ÿ~ÿ}ÿ}ÿzÿyÿwÿtÿoÿhÿdÿ\ÿSÿJÿEÿHÿRÿcÿrÿÿ‡ÿÿ‹ÿ‹ÿŠÿŠÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿŒÿ‹ÿŠÿŠÿ…ÿ}ÿrÿeÿ]ÿ\ÿaÿhÿmÿuÿxÿwÿuÿsÿpÿoÿpÿpÿpÿrÿqÿrÿtÿuÿvÿyÿ~ÿƒÿ‡ÿ‰ÿ‰ÿˆÿ…ÿ‚ÿÿƒÿˆÿŒÿŽÿ2€2i0€0k00k..m,+m+*n))n**o**q((q''r''t''s''u'(u((u((t''t''w&'w))x))y*~*x*~*x*~*x*~*x-0v6:q@‚GlN„SiZ†\e\ˆ[c^ˆ_b]‰\a[‰X`VˆS_M„IaC9h3}1n.}.q.}0r.}/r/}.s.}/t/}/t/}/t/|/t/|/t/|.u/|/v-|-x-|,x+|+x+|*x)|)w(|(y(}(z&}&z&~&|%~%|$~#{!~!{!~!{~{z z  z  z!€!z€!z {  {!!{"#{$$|$%}%~%&~()~*+~.}/~3|6~9z<~=x=~=wAEsG€LrP€ToV€XmX~YnX~[n_~clh}oiuze…`Š‚Œ\Š„ˆ[†…ƒ^†€]}…|\z…y\{†}]„…‰_‰…‹_‹„Œ_Ž„‘_‘„‘^‘„‘^Ž‚‡^~‚s^qp^mmejjhh€dmc~`n\|[pZ|XqT{RqP{OrM|JtH|HuG|EuC~DtH€HrIKpM‚RlR‚ViX‚Zh[‚^g`ƒ`e`]f^€\g[€]g\^i_\m[[o]}]na}enj}nmrujx{i~e‚‚d„ƒ…b…ƒ…b†‚†`†‚†_‡‰`‹‡`}‚„a•b—‚•c“€–eœ€¡k¢€™n‰|ny}{opzxgŒ}Œ\‹„‰V\„LeXhtv„wŒ€‘x“~’x}w}‹u‰}‡tƒ€r}}qrbzoqz{zoy‚|l€…}l‚…~riƒ[{W€X}Z€^xd€hvYH|JN€K€=K€G|7C}:€:†;~9Œ5|66|9;|D;}7†?~GAM~S€Svd‚P~?‰<}CC~JŒH€?‹F€8‰J€rƒ}wysƒntV…Tx=…Mj„P‚JƒC…F‚J†HG‡EFˆCD‰GO†XVONvK€FpD€GkG€EkWIpQ}YtS|Sv_zlzt{{{~}w}~zsy~xry~yszzry{s{€zry€votqmnjljklllmn€qpo€mrg€asU€Hu@€6v45v7€9t<€@sE€HqLRnV^mdikl~lkkkkh€fkg€dld€bma€\mW€RnL€En<7o2-t)|)w*|.{2{7{<}CxH~OtX~_rg~jtp}uvwzzy}z~~z€z~€y‚}‡y‹}Žz~‘z|{|{~{~{‘{‘{ŒzŠ‡{„}‚}€{}}{z|||xz||x~|~y~|~z~{}zz|yyw|sso}imd]mVƒMnH€KtVzf|utƒ€ˆvŒ~zz‹|‹yŒ|Œ{‹|Š{Š|Šz‹|‹zˆ~…w|€sof‚^g]‚cii€rrwxvw~utq~pqqqrppqq~rpr~rrr}ssv{|u‚z†x‰{ŠwŠ|ˆt‡~‡qˆ~‹rŽ~t1ÿ1ÿ0ÿ0ÿ1ÿ1ÿ/ÿ,ÿ*ÿ)ÿ*ÿ*ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ&ÿ'ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ-ÿ2ÿ8ÿ?ÿEÿJÿNÿSÿWÿYÿZÿ]ÿ_ÿ`ÿaÿ`ÿ`ÿ_ÿYÿSÿLÿDÿ:ÿ2ÿ0ÿ0ÿ.ÿ/ÿ/ÿ0ÿ0ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ0ÿ0ÿ.ÿ.ÿ.ÿ-ÿ,ÿ,ÿ,ÿ+ÿ)ÿ)ÿ'ÿ'ÿ(ÿ(ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ$ÿ#ÿ!ÿ!ÿ!ÿ!ÿÿÿÿÿÿ ÿ ÿ ÿ ÿ ÿ!ÿ!ÿ ÿ!ÿ!ÿ"ÿ#ÿ#ÿ#ÿ#ÿ$ÿ%ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ&ÿ(ÿ+ÿ,ÿ+ÿ.ÿ0ÿ5ÿ7ÿ;ÿ<ÿ=ÿ@ÿ@ÿEÿIÿJÿMÿOÿSÿTÿTÿUÿTÿUÿUÿWÿ[ÿ^ÿfÿmÿuÿ}ÿ‚ÿ‡ÿŒÿ‹ÿŠÿˆÿ„ÿ„ÿ…ÿ‚ÿÿÿ~ÿÿ„ÿˆÿŠÿŠÿ‹ÿŒÿŒÿÿÿÿ‘ÿ‘ÿ‘ÿŽÿ‡ÿzÿrÿkÿhÿfÿdÿbÿ]ÿ[ÿXÿUÿSÿPÿKÿHÿFÿBÿ?ÿ>ÿ=ÿ;ÿ8ÿ7ÿ7ÿ6ÿ7ÿ8ÿ9ÿ>ÿAÿCÿEÿHÿKÿLÿPÿSÿUÿWÿYÿWÿWÿVÿUÿVÿTÿSÿRÿQÿQÿPÿPÿOÿOÿOÿOÿQÿTÿXÿ[ÿaÿeÿjÿoÿvÿzÿ}ÿÿÿ„ÿ…ÿ…ÿ„ÿ„ÿ…ÿ…ÿ…ÿ…ÿ‡ÿÿpÿÿŒÿÿÿ”ÿ™ÿÿÿ›ÿ—ÿ‹ÿÿ}ÿ~ÿ~ÿ|ÿnÿ|ÿŠÿŒÿ‹ÿsÿLÿYÿgÿvÿ„ÿ‹ÿÿ“ÿ‘ÿÿÿÿ‹ÿ‰ÿ‡ÿ…ÿ‚ÿÿiÿaÿmÿcÿzÿÿ…ÿ‡ÿˆÿÿ‚ÿwÿjÿbÿ\ÿVÿTÿaÿ\ÿFÿCÿHÿQÿRÿHÿRÿbÿ]ÿLÿ:ÿ:ÿ;ÿ6ÿ5ÿ6ÿ5ÿ6ÿ4ÿ9ÿEÿ@ÿ6ÿXÿhÿ_ÿUÿNÿTÿYÿOÿFÿ;ÿ?ÿ<ÿ@ÿAÿEÿIÿ=ÿ4ÿcÿÿ{ÿnÿWÿdÿjÿMÿKÿLÿHÿJÿIÿKÿMÿJÿFÿCÿBÿBÿAÿAÿHÿLÿMÿMÿKÿEÿ=ÿ?ÿ@ÿ@ÿKÿPÿEÿWÿXÿ\ÿ_ÿbÿlÿuÿ|ÿÿ}ÿ}ÿzÿxÿwÿwÿxÿyÿyÿzÿzÿzÿyÿxÿzÿzÿrÿkÿkÿhÿgÿkÿkÿlÿoÿoÿkÿfÿ`ÿUÿJÿ@ÿ:ÿ:ÿ;ÿ?ÿBÿDÿHÿNÿRÿVÿYÿ]ÿbÿgÿkÿmÿmÿiÿiÿhÿhÿfÿfÿdÿ`ÿ]ÿXÿSÿNÿHÿ>ÿ5ÿ/ÿ,ÿ)ÿ)ÿ)ÿ*ÿ.ÿ2ÿ7ÿ<ÿCÿHÿOÿYÿ`ÿeÿkÿsÿuÿwÿzÿ~ÿÿÿÿ€ÿÿÿƒÿ‡ÿ‹ÿŽÿÿ‘ÿÿÿÿÿÿÿÿÿÿ‘ÿ‘ÿÿŽÿ‹ÿˆÿ†ÿ„ÿ‚ÿ€ÿÿ}ÿ|ÿ|ÿ|ÿ}ÿ~ÿ~ÿ~ÿ~ÿ~ÿ}ÿ{ÿxÿvÿrÿmÿiÿeÿ_ÿXÿOÿLÿNÿXÿhÿvÿ„ÿŠÿŒÿŒÿŒÿ‹ÿ‹ÿŒÿŒÿ‹ÿŠÿ‹ÿ‹ÿ‹ÿ‹ÿˆÿ„ÿ{ÿqÿgÿ`ÿaÿgÿoÿuÿzÿzÿwÿuÿrÿqÿqÿqÿpÿpÿqÿrÿrÿrÿrÿrÿtÿyÿ~ÿ„ÿˆÿŠÿŠÿ‰ÿ‹ÿ‹ÿ‹ÿÿÿÿ21j1/l00l/+n++m+*n)(o((p(€(q'€'r'€'t'€'t&&v&&v&%t&&t%&t''t''w((w))w((w)~)x*~*x*~*x*~*x)})w+},w.€4s:@oEƒIjN„SgX†Yb^‰``a‰b^b‰d\e…^`ULdA~6k1{0o/|.p0|0p0|0r0|1r2|2r1{1r2{3s3{1s1{1s1{1s/z/u/z/u.|.u-|,v+|+w*|)x)|)x(|(y'}&{&}&|%~$|"~"z!~!z~{  z z  z!!z! z ‚!y#&y()w('w&€%v#$w%~%|'}$|&})*~+€,/}2~5{9~u:{:u>{BvE{JvQ{StY}_rd}inp~wm}€jƒ‚‡h†…gƒƒ‚dƒƒf„ƒ‡dŠ‚Ža‹‚Žd€—hœ~œn”}qŒŠn„€wkslƒ{or|j{„{e‚†ZiZgou€ƒr‰~u’}x|Žw~‹tŠˆq„„†l‹ƒwop~qnroh‹”f”…‘gŒƒxpkozh€]{Xfwc€]t[€F{GO~Zc~mf{;€M~G:†7~8Œ6~78~65~9BDˆEZ„`T‚O}H…IzJŠ@z@=|?ŽB}N‰Z~c…H~<ƒ2€W€zmw]€MrP‚XvR‚N~J‚MƒN‚J„G‚F‡LMˆF€A‹A€;Œ;€?ŒDGˆI€I…F€I‚J€LJO|O?vX~XrR{buh{ozx|~{~}w|€yswvqv€wrw€vsy€xsz€wrv€ooh€omz‚n~sqljro€ryjixg\uSLrHCoEGqJ‚OpR‚TnW‚Yl[‚`kfhkjkhlkik€jgh€gjf€dla€_n[€VoQ€JqA€8s/~+v(}(|){(({,~3}8|;|BwL~SuZ~aug|mvr{wyzz{{y€z€€z€~y„|†yŠ}y‘}{}{~{{€{€{Ž€{Ž€‹yˆ…yƒ{}{}y}|{z}|}z~|~{~|~{}{|y{|xyu|qrm}jmf€alY‚SmMQr\zl{zu„‹wŒ{Šz‹|‹y‹|‹{Œ|‹{‹{Œz‹}Šzˆ~v|€rqf‚cggijsyr{~zuvvvsstposo~orp~qqr~rpo~nsq|uuzzz…xˆ{ŠzŠxŒ{Œt}‘t‘~t1ÿ/ÿ/ÿ.ÿ-ÿ-ÿ+ÿ*ÿ+ÿ*ÿ*ÿ*ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ&ÿ&ÿ&ÿ&ÿ%ÿ&ÿ&ÿ%ÿ&ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ,ÿ,ÿ,ÿ0ÿ5ÿ9ÿ?ÿEÿIÿMÿSÿYÿ^ÿbÿdÿfÿiÿiÿfÿ^ÿTÿHÿ<ÿ3ÿ1ÿ0ÿ1ÿ/ÿ0ÿ1ÿ1ÿ1ÿ1ÿ2ÿ2ÿ1ÿ1ÿ0ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ/ÿ.ÿ.ÿ-ÿ,ÿ+ÿ+ÿ*ÿ)ÿ)ÿ)ÿ(ÿ(ÿ'ÿ&ÿ&ÿ&ÿ%ÿ$ÿ"ÿ"ÿ!ÿ!ÿÿÿ ÿ ÿÿ ÿ ÿ ÿ!ÿ!ÿ ÿ ÿ"ÿ$ÿ'ÿ,ÿ/ÿ0ÿ0ÿ/ÿ-ÿ+ÿ'ÿ&ÿ%ÿ$ÿ&ÿ&ÿ'ÿ(ÿ*ÿ+ÿ/ÿ3ÿ5ÿ9ÿ=ÿ@ÿCÿFÿIÿKÿOÿPÿPÿQÿRÿRÿTÿVÿXÿZÿ\ÿ]ÿ]ÿ^ÿaÿeÿjÿrÿxÿ€ÿ†ÿŒÿŽÿŒÿ‡ÿ…ÿ…ÿ†ÿˆÿˆÿˆÿ‡ÿ‡ÿ‡ÿ‡ÿˆÿŠÿŠÿŠÿŠÿ‹ÿÿ‘ÿ”ÿ“ÿ“ÿÿ‰ÿ|ÿlÿ\ÿQÿNÿLÿFÿ?ÿ;ÿ9ÿ5ÿ3ÿ1ÿ.ÿ/ÿ0ÿ.ÿ.ÿ.ÿ.ÿ-ÿ/ÿ2ÿ3ÿ3ÿ6ÿ<ÿ>ÿBÿEÿHÿKÿMÿPÿRÿTÿWÿWÿWÿWÿXÿXÿWÿVÿTÿSÿRÿNÿJÿFÿDÿ>ÿ:ÿ4ÿ1ÿ/ÿ.ÿ.ÿ2ÿ8ÿ=ÿBÿIÿOÿUÿ[ÿ`ÿgÿnÿvÿzÿ~ÿ}ÿ€ÿzÿvÿyÿ‚ÿŠÿÿÿÿ‘ÿ™ÿ›ÿ™ÿ•ÿ‘ÿŽÿÿŽÿŽÿÿ‰ÿ{ÿnÿsÿmÿlÿyÿ€ÿŽÿ›ÿ–ÿvÿ~ÿvÿ€ÿÿŽÿ‚ÿzÿƒÿ‹ÿÿÿÿÿ‘ÿ“ÿÿtÿpÿ}ÿÿpÿxÿÿ’ÿ”ÿ‚ÿcÿaÿgÿjÿcÿUÿ\ÿ[ÿ\ÿPÿPÿOÿIÿZÿdÿXÿ9ÿÿMÿKÿ=ÿ8ÿ8ÿ7ÿ8ÿ;ÿ7ÿ6ÿ=ÿ?ÿGÿcÿTÿNÿ7ÿ<ÿ6ÿ8ÿ=ÿFÿDÿAÿAÿCÿKÿWÿ]ÿCÿFÿ<ÿaÿ\ÿbÿeÿ[ÿ@ÿ?ÿJÿDÿNÿZÿVÿPÿHÿFÿFÿHÿCÿ=ÿ>ÿ;ÿ8ÿ=ÿAÿGÿIÿIÿJÿLÿLÿNÿOÿQÿXÿZÿXÿYÿXÿZÿeÿpÿyÿ}ÿ}ÿ|ÿ{ÿxÿwÿuÿvÿtÿyÿxÿsÿsÿvÿpÿfÿJÿLÿ[ÿpÿ†ÿ™ÿÿ`ÿXÿ\ÿSÿGÿKÿ[ÿ^ÿYÿQÿLÿHÿJÿLÿQÿVÿXÿZÿ[ÿ^ÿcÿfÿkÿmÿnÿmÿnÿlÿjÿiÿhÿgÿeÿcÿbÿ`ÿ\ÿTÿMÿGÿ=ÿ4ÿ-ÿ*ÿ(ÿ(ÿ)ÿ(ÿ(ÿ,ÿ2ÿ7ÿ=ÿEÿLÿSÿ[ÿaÿhÿnÿtÿxÿyÿ|ÿÿ€ÿ€ÿÿÿ€ÿÿ„ÿ†ÿŠÿÿ‘ÿÿÿÿÿÿÿÿÿÿÿÿŽÿÿŽÿ‹ÿˆÿ…ÿƒÿÿÿ|ÿ{ÿzÿzÿ}ÿ}ÿ~ÿ~ÿ~ÿ~ÿ}ÿ}ÿ{ÿxÿsÿoÿlÿiÿfÿaÿ[ÿUÿRÿUÿ_ÿmÿ}ÿ‡ÿ‹ÿÿŽÿŽÿ‹ÿ‹ÿ‹ÿ‹ÿŒÿ‹ÿ‹ÿŒÿ‹ÿŠÿˆÿÿ{ÿsÿiÿfÿiÿmÿtÿzÿ{ÿzÿwÿuÿsÿsÿrÿpÿoÿoÿpÿqÿpÿpÿnÿkÿmÿqÿvÿ{ÿÿ†ÿˆÿŠÿŒÿŽÿ‘ÿ‘ÿ‘ÿÿ0/k..m--m+)o*)p((p)(p'&q&&q&&r%€%t%€%t&&t&&t&&t%%t&&t''t((w))w))w))w*~*x*~*x*~*x*~*x+}+x,},x-}-w-~.s06q<€AmF„LjQ‡Xf^‰dag‰l]m†k]e[bN|Bh7z1n1{2p2{2p1|2q2|2q3{3q3{3q3{2r2z2r2z2r2z2r1{0s/{.s-{-t-{-u-{-v+{*w*|*w(|(x'|&z&}&{%~$z$~"y!~ z ~ {  z z!€ z!€ zƒ"z&„+w/†4p<†>i>†>i9ƒ6k0,p)(u'~)y)~)|*.|1€6y9=uBFsG‚JqL‚OqP‚RpT‚TqTƒTqVƒZm\…`gd„ede„ebf„ibq„ua{„€_…ƒŒ]ƒ\‰„‡]…„†[‰„Š[‰„‰\ˆƒˆ^‡ƒˆ]‡ƒ†^ˆƒˆ]ŠƒŒ`ƒ’a“ƒ’aŒˆdz~hgR|EnA{=u9z7{3y10y00{.‚.{/‚-|-‚-~-‚.~.0€4|8€<†;9‹;~@ŒE~<Œ9~8‹:~F‹C~@‰G}?ˆ:}9Š8{7ˆL|@‰A|C‰E}J…U|S„FIƒ_]|S‚ZveƒRyRƒX}9ƒB}h„rzhƒU|I‚F„DA‰BBŒ?:7;@~CGHŒI€KŠM€PˆRR†UX[[zX|`yf{o{z|~z}~|vzwsvwpu€unytn_`nwhobkorntn€a}`J„G}F‰I~N‰N~E„Y~bx\€UoPPoQVqY‚[p]‚`nd‚elimjopiomhkiij€hjh€gke€en`€]pZ€TqL€Ds;€1u*~)y)}){({)|){-{4~9x?}FsN}Uo\}crj|pvu{wy{z}|y€~x€x€€€y„}ˆyy’}{}{~{z€z€Žy€‹y‹€‹yˆ€…yƒ€€z~}{zyyy{zy}{}z}{~{~{~|}{{|y|vys|psl}ine€ak\‚WlUXsbzp}~vˆŒx‹{Š{‹|‹z‹|‹{‹|‹{Œ{Œ{Œ}‹{‡~€u{€sml‚kemrjw{s|€zwxttssroosn~nrp~qqq~npkhsh}lupzv}~x„~ˆxˆ{zw‘|’u}u/ÿ.ÿ.ÿ.ÿ-ÿ+ÿ*ÿ)ÿ(ÿ)ÿ(ÿ'ÿ(ÿ(ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ,ÿ.ÿ2ÿ9ÿ>ÿCÿKÿQÿYÿ`ÿhÿmÿrÿqÿkÿcÿUÿHÿ:ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ1ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ+ÿ*ÿ*ÿ*ÿ(ÿ(ÿ'ÿ&ÿ&ÿ&ÿ%ÿ$ÿ"ÿ!ÿ!ÿ ÿ ÿ ÿ ÿ ÿÿ ÿÿ ÿ ÿ"ÿ%ÿ*ÿ/ÿ3ÿ9ÿ@ÿGÿKÿKÿKÿGÿCÿ>ÿ7ÿ0ÿ+ÿ)ÿ*ÿ,ÿ-ÿ/ÿ1ÿ5ÿ7ÿ=ÿAÿFÿJÿLÿOÿOÿRÿTÿUÿVÿVÿXÿXÿ\ÿ`ÿeÿhÿlÿmÿnÿnÿnÿpÿrÿxÿ}ÿÿ†ÿ‹ÿŒÿŒÿˆÿ†ÿ…ÿ†ÿˆÿ‰ÿŠÿŠÿŠÿ‰ÿ‡ÿ‡ÿ†ÿ„ÿ†ÿ†ÿˆÿ‹ÿŽÿ‘ÿ”ÿÿŒÿ‡ÿ{ÿiÿQÿDÿ<ÿ7ÿ7ÿ3ÿ5ÿ3ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ0ÿ0ÿ3ÿ6ÿ:ÿ>ÿBÿDÿHÿJÿNÿQÿQÿRÿVÿWÿXÿYÿYÿZÿZÿZÿXÿWÿUÿUÿRÿNÿJÿFÿ@ÿ9ÿ5ÿ1ÿ-ÿ,ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ-ÿ0ÿ5ÿ;ÿDÿLÿTÿ[ÿbÿeÿuÿŽÿ”ÿ—ÿ’ÿ”ÿ•ÿ™ÿžÿŸÿ˜ÿyÿkÿoÿmÿŽÿÿ’ÿ‘ÿ’ÿ’ÿ’ÿ¡ÿ¶ÿ»ÿ¹ÿ»ÿ½ÿ½ÿ½ÿ¼ÿ»ÿ»ÿ´ÿ–ÿ‡ÿƒÿˆÿÿvÿpÿiÿhÿjÿkÿ_ÿ]ÿ`ÿvÿwÿyÿ{ÿkÿUÿVÿrÿƒÿnÿUÿZÿjÿmÿjÿlÿlÿ`ÿ]ÿTÿQÿTÿNÿJÿSÿvÿuÿ<ÿ4ÿ1ÿ3ÿ;ÿ<ÿ<ÿ:ÿ@ÿKÿGÿ8ÿ4ÿ7ÿ?ÿFÿDÿLÿEÿ;ÿ<ÿ9ÿ;ÿJÿ<ÿAÿCÿEÿIÿUÿKÿHÿ=ÿMÿiÿeÿZÿVÿJÿVÿOÿ;ÿnÿnÿnÿiÿXÿLÿEÿ@ÿ>ÿ=ÿ>ÿAÿ<ÿ8ÿ:ÿ<ÿ@ÿCÿDÿGÿIÿJÿKÿLÿNÿQÿVÿ[ÿ[ÿXÿ]ÿgÿqÿuÿ|ÿ}ÿxÿxÿtÿsÿvÿvÿmÿUÿsÿmÿlÿdÿ]ÿ^ÿiÿnÿfÿ^ÿCÿDÿNÿPÿQÿJÿDÿHÿ]ÿlÿeÿ`ÿZÿUÿTÿWÿZÿ\ÿ^ÿaÿdÿfÿiÿlÿoÿqÿqÿoÿmÿkÿiÿjÿhÿhÿgÿeÿeÿ`ÿ[ÿXÿQÿJÿBÿ9ÿ0ÿ)ÿ(ÿ)ÿ)ÿ)ÿ*ÿ,ÿ0ÿ8ÿ;ÿAÿIÿOÿWÿ^ÿeÿjÿpÿvÿxÿ{ÿ}ÿÿÿÿ€ÿ€ÿÿ‚ÿ…ÿŠÿŒÿÿ’ÿÿÿÿÿÿÿÿÿŽÿŽÿŒÿŒÿ‹ÿ‹ÿ‰ÿ†ÿƒÿÿÿ}ÿ{ÿyÿzÿ{ÿ{ÿ{ÿ|ÿ}ÿ}ÿ}ÿzÿyÿvÿsÿqÿnÿjÿgÿdÿ`ÿ\ÿWÿWÿ]ÿeÿsÿÿŠÿÿÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿŒÿŒÿŒÿ‹ÿ‡ÿ€ÿ{ÿsÿpÿnÿqÿuÿyÿ}ÿ}ÿ{ÿwÿsÿsÿsÿpÿoÿnÿnÿnÿnÿnÿkÿiÿhÿiÿjÿmÿqÿzÿ€ÿ…ÿˆÿÿÿ‘ÿ’ÿÿÿ--m,,o+~+n(~'o('p''p&~&r&~%r%%s%%s%%u%%u%%u%%u&&u&&u&&v''v((w))w))w*~*w*~*x*~*x*~*x*~*x,~+x,~,x-}-w.}.v-}-s.}2r7€:p?…JjRˆ]chˆm[q†sYod^XzKf=y4o2z2p2z2p1|2r3|4r4{4q4{4q3{3p3z3p4z4q3z2q1z2s3z1s1{/r0{0t.{.u,{,v+{+v)|)x(|'z'|'{&}%z%}#z"~!z!~!z  z z!~"z#$y(„/t5†=mGŠMdR‰V`Y‰[bVˆRdJƒCi<‚6n0.r/0v4‚4u9‚=tCƒFrJƒLpO„RqR„UpU„WnZ„Zl[„\k`„bef†m`r…t[s†qXq†sZu…yY~‡‚W…„ŠX‹ƒ‰V†„…Y…„†Y‰ƒ‹Y‹‚‹]Š‡c…„eƒ€e€‚e†€‰cŒƒc’‚’eŽ‚‹f€og]RkL‚Dq<8w45z4~1~-}-/|/€-}--~.2€2~68z>ƒBuE‚IpJƒLmOƒPkS„SkV„WkY„YjZ„[hZ„ZhYƒWiUƒTiP‚LmHBr=6y1.}+~+€,~,,~,€,~,-1}4€8y?IuN‚TnY„mh‘†™d–…–bŒ‚Ži~šn”|‘r}]s5cr…„k‰h‘”h”Ÿk¸€¾|¼»¿~¿€¾~½€¼~¾¶}³~º¤w„„ˆl†„‰h‚|g_}Rmg|cusprg~wrz{ozlqW~\vd~X~L}[cf‚b~]ƒa~g‡W}H‰G}NŒM}LŠZ~m‡h€J‚:‚9‚I€D…><ˆ8D‰A9‡67Š3}E‹Y~SŠA3‡9~<‡<~I†MBˆD}E‡C}J…V~E‡D~9†M~wp€k}2B€@A€uywn‚gr`‚RyN‚F‚??‡=€>‹><Œ;:Ž>BŽDCŒD€FŒG€F‰H€J‰P€S…WY[}]|gzq~w{xyz|ywv}ttslqrnlm€ojrfmG‚6o@dxaH‚K~K‰L}L‹H}F‰L\€k€pxkhpc]nYƒWoYƒZp_‚aqc€hpikkopjqphn€lgj€hhh€hig€fld€cn`€ZpW€OpG€?s5€-u)~'{'}'~+|,~0|4{:AuDKqRZp^}erm|rvv{yy{z}|x€w~‚wƒw†}‰yŒ}z‘}z}{~{ŽzŽzŽŒzŒ‹z‰‡y…€‚y€€~z|zzyzx{yz{{{|{{|}}{}|y|v|s{pxl{irh|foc~`l\€[mY~^tgzu~w‰€Œy~{‹{‹}‹{‹|‹{‹|‹{Œ{Œ{‹}Š{‡~t{€uhq‚rduyj{€~r}zuxtssrrpprooro~oop~mojgqf}fxl{p}wx}}‚vˆ|Œxy‘{’v|u-ÿ-ÿ,ÿ,ÿ+ÿ)ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ/ÿ1ÿ/ÿ.ÿ2ÿ4ÿ;ÿDÿLÿXÿbÿjÿsÿvÿqÿiÿYÿLÿ=ÿ5ÿ3ÿ3ÿ2ÿ3ÿ2ÿ3ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ4ÿ4ÿ4ÿ3ÿ2ÿ3ÿ3ÿ3ÿ1ÿ/ÿ0ÿ0ÿ0ÿ.ÿ-ÿ-ÿ,ÿ,ÿ)ÿ)ÿ)ÿ)ÿ(ÿ'ÿ&ÿ%ÿ%ÿ#ÿ"ÿ!ÿ!ÿ!ÿ ÿ ÿÿ ÿ!ÿ"ÿ$ÿ(ÿ-ÿ5ÿ>ÿHÿRÿWÿ^ÿdÿeÿgÿfÿaÿYÿQÿGÿAÿ;ÿ5ÿ4ÿ5ÿ7ÿ:ÿ?ÿCÿGÿJÿLÿNÿQÿTÿVÿYÿYÿZÿ[ÿ[ÿ\ÿ`ÿeÿjÿnÿrÿvÿvÿvÿtÿtÿvÿyÿyÿ~ÿ‚ÿ†ÿˆÿˆÿˆÿ„ÿ„ÿ…ÿ†ÿ‡ÿ‰ÿŠÿŠÿ‡ÿ…ÿÿ~ÿzÿyÿyÿzÿÿƒÿŠÿÿ’ÿ‘ÿÿÿ…ÿyÿlÿcÿ\ÿWÿPÿIÿDÿ:ÿ7ÿ1ÿ.ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ0ÿ1ÿ6ÿ8ÿ:ÿ@ÿDÿHÿIÿMÿNÿOÿPÿSÿSÿVÿWÿYÿYÿ[ÿ[ÿZÿZÿXÿXÿUÿRÿPÿJÿEÿ?ÿ9ÿ4ÿ/ÿ-ÿ,ÿ,ÿ+ÿ,ÿ+ÿ,ÿ-ÿ.ÿ0ÿ3ÿ6ÿ<ÿBÿHÿNÿTÿTÿ…ÿžÿ™ÿšÿÿ~ÿkÿyÿnÿ]ÿpÿxÿŠÿ•ÿ˜ÿ•ÿŠÿ{ÿ\ÿ]ÿ„ÿ•ÿ¶ÿ½ÿ¾ÿ½ÿ¼ÿ½ÿ¾ÿ¿ÿ¾ÿ¹ÿ¦ÿlÿsÿ³ÿ¸ÿ°ÿ†ÿ˜ÿÿ¡ÿwÿIÿZÿhÿuÿÿÿzÿ|ÿ{ÿ{ÿ{ÿsÿcÿRÿWÿbÿOÿQÿQÿ_ÿQÿAÿAÿKÿXÿ^ÿQÿNÿLÿIÿOÿLÿQÿXÿ>ÿ8ÿFÿEÿAÿ<ÿ5ÿCÿOÿQÿ9ÿ8ÿ7ÿ<ÿIÿ`ÿJÿLÿPÿNÿUÿOÿCÿDÿBÿRÿLÿJÿTÿBÿFÿ<ÿVÿxÿkÿDÿ>ÿ<ÿ<ÿqÿyÿwÿqÿeÿfÿXÿHÿFÿFÿDÿBÿ@ÿ=ÿ:ÿ9ÿ;ÿ=ÿ@ÿCÿDÿFÿGÿHÿIÿJÿMÿQÿRÿVÿXÿWÿWÿhÿuÿxÿwÿ{ÿyÿyÿvÿsÿnÿ`ÿdÿmÿjÿoÿjÿiÿ_ÿYÿ_ÿOÿJÿKÿKÿHÿDÿSÿeÿkÿlÿnÿnÿmÿkÿfÿaÿ\ÿZÿ]ÿ^ÿbÿbÿeÿgÿlÿmÿmÿoÿoÿoÿmÿkÿjÿhÿhÿhÿfÿeÿcÿbÿ^ÿYÿUÿMÿEÿ=ÿ4ÿ,ÿ)ÿ'ÿ'ÿ'ÿ+ÿ/ÿ3ÿ8ÿ;ÿBÿEÿLÿTÿZÿ`ÿhÿmÿrÿvÿyÿ{ÿ}ÿÿÿÿ~ÿ€ÿ€ÿ‚ÿ…ÿŠÿÿÿ‘ÿÿÿŽÿŽÿÿÿŽÿÿÿÿŒÿŒÿ‹ÿ‰ÿ‡ÿ…ÿ‚ÿ€ÿ~ÿ|ÿzÿyÿxÿyÿ{ÿ{ÿ{ÿ{ÿyÿyÿwÿtÿrÿoÿjÿgÿgÿeÿcÿ`ÿ\ÿ[ÿZÿaÿkÿvÿÿ‰ÿÿŒÿŒÿŒÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿŠÿ‡ÿÿ{ÿuÿqÿtÿwÿ{ÿ}ÿÿ}ÿzÿvÿuÿsÿrÿpÿpÿoÿoÿoÿoÿpÿmÿjÿgÿfÿfÿiÿnÿuÿ{ÿÿ…ÿŒÿÿ‘ÿÿÿŒÿ,,n,+p*~(p'~'r&&t&%t%~%t$~$t$$t$$t$$u$$u$$u$$u$$u$$u&&v''v((w))w))w*~*w*~*x*~*x+}+x-}-x-|-x-|-x-}-w.}.v1}3u1}1s2€3t8…>nG‰Td`‰j\sˆtZqj^[{Me‹6{O‡VzOƒ@zK‚C{FƒC}^~Y}L}Q}A…E~:ˆn~y„M0=B‚przwxpteoh‚^vJG~GD„C@‰:~7‹8~:<~>A~C‹EF‹HH‰JP‡U€U„UX~Q}Xmzv~z{xyx~ssHZrs~pqg~Zqa€fre€qvo_}TL…J~NˆN~L„Of|l€ktl€lom€nmnknh‚dn_ƒ\o]ƒ]p_‚`qc€cpg€imiklm€kkk€jij€hig€fke€dlc€`n]€XpR€KqC€:s1€,v)~'z&}(z,~0z4~;w>€BsG€MpV€^pb~htp|tww{zz|z}~~x~€~w}~wƒw†Šy‘z~z~Ž{Ž~Ž{Žzz‹z‹Šzˆ‡y…€y€}z{}yzw{x{y|{{{}{{z~y{x|u|r|n{jxg{erf|doc~`m^€\n\~dtnzy~ƒxŠ€zŒ~‹{‹{‹}‹{‹|‹{‹|‹{‹{‹{‹}Šy‡~p{€ugs‚udy|j}€~q}yswtstsrpproorpoqo~lqifqc}exi{n{txx{~w„zŒyx‘|w}‹v+ÿ+ÿ*ÿ*ÿ)ÿ'ÿ'ÿ'ÿ$ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ&ÿ&ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ1ÿ3ÿ1ÿ1ÿ2ÿ3ÿ8ÿ>ÿGÿTÿ`ÿjÿrÿuÿsÿiÿZÿJÿ=ÿ4ÿ3ÿ4ÿ3ÿ3ÿ3ÿ5ÿ6ÿ6ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ3ÿ4ÿ3ÿ3ÿ2ÿ1ÿ1ÿ0ÿ1ÿ0ÿ.ÿ-ÿ-ÿ-ÿ,ÿ+ÿ+ÿ*ÿ)ÿ)ÿ'ÿ&ÿ%ÿ#ÿ#ÿ#ÿ"ÿ"ÿÿÿÿÿ!ÿ#ÿ(ÿ-ÿ5ÿ@ÿIÿTÿ^ÿfÿoÿuÿyÿzÿxÿrÿlÿfÿ_ÿUÿQÿMÿGÿEÿGÿJÿIÿJÿNÿOÿPÿTÿUÿXÿ[ÿ^ÿ]ÿ_ÿ_ÿ_ÿaÿeÿkÿnÿoÿtÿvÿvÿuÿsÿsÿtÿuÿyÿ|ÿ€ÿ‚ÿ„ÿ…ÿ„ÿ†ÿ†ÿƒÿ€ÿ~ÿ~ÿ~ÿ{ÿwÿsÿpÿlÿgÿcÿ^ÿaÿgÿqÿÿ‡ÿŽÿ’ÿ‘ÿÿŒÿˆÿ~ÿxÿxÿuÿqÿnÿkÿdÿ^ÿVÿLÿ@ÿ9ÿ1ÿ.ÿ/ÿ1ÿ5ÿ8ÿ<ÿ?ÿBÿDÿHÿJÿKÿMÿNÿQÿRÿSÿTÿVÿWÿXÿYÿZÿZÿXÿVÿUÿTÿRÿNÿIÿDÿ?ÿ9ÿ3ÿ/ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ.ÿ1ÿ4ÿ8ÿ:ÿ?ÿDÿHÿKÿPÿ_ÿ–ÿœÿœÿ¡ÿ¢ÿ£ÿšÿ‡ÿ{ÿÿ‰ÿŽÿŽÿxÿmÿ`ÿyÿuÿ{ÿaÿhÿ|ÿ£ÿ¹ÿ¾ÿ½ÿºÿžÿ™ÿ¬ÿ·ÿ½ÿ½ÿ½ÿ¬ÿ‰ÿ“ÿ°ÿ¥ÿ‡ÿ|ÿ˜ÿŠÿiÿOÿTÿOÿnÿzÿoÿYÿ^ÿdÿlÿmÿlÿeÿjÿ€ÿeÿ]ÿXÿTÿLÿ?ÿ:ÿ9ÿ>ÿEÿIÿTÿRÿMÿKÿKÿKÿTÿMÿKÿEÿAÿBÿ?ÿ>ÿ<ÿCÿEÿ?ÿ=ÿ;ÿ:ÿ;ÿAÿ:ÿ;ÿKÿLÿIÿAÿ@ÿEÿFÿJÿWÿXÿPÿJÿCÿBÿ>ÿzÿVÿ1ÿ2ÿ?ÿbÿ_ÿfÿvÿvÿsÿkÿ^ÿYÿQÿFÿFÿFÿCÿBÿ>ÿ;ÿ9ÿ8ÿ;ÿ=ÿ>ÿBÿDÿGÿHÿKÿLÿOÿWÿ]ÿ[ÿYÿYÿdÿmÿvÿyÿwÿuÿqÿ;ÿQÿsÿdÿ>ÿ[ÿoÿkÿvÿwÿNÿCÿSÿOÿJÿEÿQÿbÿiÿhÿgÿjÿjÿlÿnÿoÿoÿmÿgÿcÿ_ÿ\ÿ\ÿ\ÿ\ÿ^ÿ\ÿ\ÿ^ÿaÿdÿfÿhÿjÿjÿjÿiÿgÿgÿfÿeÿcÿbÿ`ÿ\ÿWÿPÿIÿAÿ8ÿ0ÿ+ÿ(ÿ'ÿ(ÿ+ÿ.ÿ3ÿ8ÿ<ÿ?ÿAÿGÿNÿVÿ]ÿdÿkÿpÿtÿwÿzÿ|ÿ}ÿ~ÿ~ÿ~ÿ}ÿ~ÿÿ‚ÿ…ÿ‹ÿÿ‘ÿÿÿÿŽÿŽÿŽÿŽÿÿÿÿÿŒÿŒÿŠÿˆÿ„ÿ‚ÿÿÿ|ÿzÿxÿwÿxÿyÿ{ÿ{ÿzÿyÿxÿwÿrÿoÿlÿhÿfÿdÿeÿcÿcÿ`ÿ`ÿ^ÿ_ÿhÿpÿ{ÿ…ÿŠÿÿŒÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿŠÿ‡ÿÿ|ÿvÿtÿuÿ{ÿ|ÿ~ÿ}ÿ{ÿyÿuÿtÿsÿrÿpÿpÿoÿoÿpÿoÿnÿkÿgÿfÿcÿeÿiÿnÿpÿwÿ~ÿ„ÿŒÿÿ‘ÿÿŒÿŠÿ)'q))p'~&r%~%r%}%t%%t$~$s#~#t""t""t##w##w##v##v$$t$$t&&u&'u(~(u)~)u)~)w)~*w+~+x,~,x+}+x-}-v-|-w.|.w.|.u.|.s1}4t1}/t23q8„>mG‰Ue_‹j]t‰yXr‚hZ[{Hc;y3m3z3p5{6o5{5o6{6n6{6p6{6p6{6q6{6q6{6q6{6q5y5q5y5q4z4q3z2q1y1r0y.r,z.t-{,t+|*u+}*v'|&w%|$w#~#x"~!x!y!!x#€%x*ƒ1t8†BmM†Wg`‰hcrˆy^}ˆ{`y‡s`o†iacˆ^bZ‰WeT‰SeN‡NgP‡TgSˆSgRˆTfW†Yg[†_g`…`ea…adc„f_i„k[o†rTr…tPs„rOr…rRt…vVy…}W~…‚Z„„„Yƒƒ[€~`{}xet|pllzgrcw_xXvQ{OuR{Yzewq|n†‚ŽhƒŽeƒ‰_€ƒ|]}…|]|‡y`xˆtenˆiib†YjN„Bl;ƒ6p5:q=‚@t@‚DqG„JpL„LnM„NmO„PmRƒTkVƒWkX…ZkX„VlW‚SmR‚OpM€IqD€>t:~6y3~0|..~//..1€3}5‚8y<ƒ?tCƒErK„RoR„{ošmš}¢tžzšz‹wijy€¥|˜~ˆ}…{„}h{Z|T|^|fw|~Ÿy¶~ƒ«~»…¼€±…›¥¯|¤Ž v²Œ£t†‡}w•| |˜r†~Ÿv’€}}^^ƒt€q…_|Y…L{K„S~hƒi}]„i|n†izqˆ^{XŠRz:Š:z;‹=z?‹8{JˆP{O‰F{L‰F|EˆS~KˆEDˆHJ‹LSŒB~8Œ<}?Œ;~:Ž;{=P{MŽS{H‡GzPƒLyA†Dy@„ax]{RwQyAzD‚@|J†h}2‡7};„M}Z]~gyt€uspjq_ƒUwNƒJ~F€B„CC†B~>Š<~<Œ>~@ŒBE‰F€I‰J€OƒPRU[}^Yw\zb{n{v}x}w{ttsqups‚dpQdqov{mK‚?G‰KJ‰GWƒljwihmi€knlkmnnom‚johƒbp^„\pZ„ZrWƒTrR€SrU}Yp\}_nf}hmjjji€gjg€fle€cma€^n\€WoN€Gq?€5s.€(v&}&z*|,{0|5w9~ÿFÿSÿ^ÿiÿtÿxÿsÿhÿ[ÿJÿ;ÿ5ÿ4ÿ4ÿ5ÿ6ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ3ÿ2ÿ1ÿ1ÿ0ÿ1ÿ1ÿ0ÿ-ÿ+ÿ+ÿ*ÿ*ÿ(ÿ'ÿ&ÿ&ÿ%ÿ$ÿ$ÿ"ÿ!ÿ!ÿ!ÿ#ÿ#ÿ$ÿ'ÿ*ÿ1ÿ9ÿCÿMÿWÿbÿjÿrÿvÿyÿxÿvÿpÿqÿlÿhÿeÿdÿ^ÿ\ÿ[ÿYÿWÿZÿ\ÿZÿZÿYÿ[ÿ\ÿ_ÿaÿeÿfÿfÿfÿfÿhÿiÿjÿlÿpÿqÿrÿsÿrÿrÿqÿqÿqÿtÿyÿ}ÿÿÿ€ÿ€ÿ~ÿ}ÿyÿvÿrÿmÿhÿdÿ_ÿ[ÿYÿTÿGÿAÿBÿDÿMÿYÿgÿvÿ„ÿÿÿŽÿ‹ÿ„ÿ}ÿ{ÿ}ÿÿÿ~ÿ~ÿ}ÿzÿvÿsÿkÿdÿYÿNÿCÿ?ÿ?ÿ@ÿCÿDÿFÿIÿKÿLÿLÿMÿNÿOÿPÿQÿTÿVÿWÿYÿXÿUÿRÿSÿNÿMÿMÿIÿDÿ?ÿ:ÿ8ÿ4ÿ2ÿ/ÿ/ÿ/ÿ/ÿ/ÿ1ÿ2ÿ5ÿ8ÿ:ÿ>ÿ@ÿCÿFÿHÿJÿNÿ^ÿ•ÿšÿšÿžÿŸÿ—ÿŒÿwÿWÿ\ÿ‰ÿ¤ÿ§ÿ–ÿ„ÿtÿbÿYÿaÿdÿIÿZÿÿ¾ÿ®ÿ†ÿÿ²ÿµÿ©ÿ®ÿÿnÿqÿ‹ÿ¡ÿšÿ}ÿeÿÿ™ÿŠÿ™ÿ‹ÿgÿaÿgÿfÿ]ÿcÿdÿYÿTÿWÿfÿ\ÿTÿeÿiÿiÿcÿ\ÿbÿRÿ;ÿ<ÿ>ÿDÿ=ÿ<ÿFÿRÿNÿJÿRÿQÿCÿIÿNÿOÿQÿMÿNÿRÿZÿEÿ<ÿ>ÿEÿ=ÿ8ÿ;ÿ:ÿHÿ[ÿfÿLÿKÿPÿCÿCÿBÿGÿ`ÿRÿUÿXÿAÿFÿ=ÿWÿ=ÿ>ÿCÿGÿPÿXÿKÿNÿqÿuÿpÿgÿ_ÿWÿNÿJÿCÿAÿCÿCÿ@ÿ=ÿ<ÿ>ÿ@ÿAÿCÿFÿMÿLÿNÿVÿYÿWÿZÿZÿ[ÿ]ÿ`ÿhÿrÿxÿyÿvÿuÿpÿoÿqÿpÿnÿnÿrÿyÿfÿJÿBÿJÿJÿCÿAÿJÿbÿeÿfÿhÿhÿhÿhÿkÿlÿmÿlÿkÿiÿeÿ_ÿ[ÿYÿXÿTÿOÿMÿJÿLÿKÿPÿYÿ_ÿcÿgÿiÿjÿiÿgÿfÿeÿdÿbÿaÿ]ÿZÿUÿNÿGÿ>ÿ4ÿ+ÿ'ÿ&ÿ(ÿ+ÿ.ÿ0ÿ5ÿ9ÿ>ÿ@ÿEÿKÿRÿXÿbÿgÿnÿsÿwÿzÿ|ÿ{ÿ|ÿ}ÿ}ÿ|ÿ|ÿ~ÿ€ÿ„ÿ‡ÿŒÿÿ‘ÿÿŽÿŽÿÿÿŒÿŒÿÿÿŽÿŽÿÿŒÿŠÿ‡ÿ„ÿ‚ÿ€ÿ~ÿ{ÿyÿxÿwÿwÿxÿxÿxÿyÿyÿvÿsÿpÿjÿfÿbÿ`ÿaÿdÿdÿdÿaÿaÿbÿgÿoÿxÿƒÿ‰ÿŒÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿŠÿŠÿ‰ÿ‡ÿ…ÿ€ÿxÿvÿuÿwÿ{ÿÿÿ~ÿzÿwÿuÿsÿrÿqÿoÿoÿoÿoÿpÿoÿlÿkÿfÿcÿbÿdÿfÿkÿpÿvÿ}ÿ…ÿŒÿ‘ÿÿÿ‹ÿ‰ÿ)(p''s%~$u$~$u#~#u""u"~"t!~!u""v""v""w##w"!v!!v!"v$$v&&v&'v'~(v)~)w)~*w+~+w+~+x,~,x-}-x-}-v.|.v.|.v-|-u-|-s/}1s3}3s12p8„>lH‰Td^‹j\uˆxUrhX]{Lb=y5i6z6o6{5o5{7o6{6p6{6p6{6p6z6o6z6o6z6o6z6o6y6p6y6p5z4p4z4p2y2p1y0p0z0s/{.t,z+t*{)u'|&w'|&w%~$x#~"x""{""y#€&w+ƒ2t:…CoM…Wh`…gcn…o_pƒoao…mal†jbjˆjdh‰gdc‰bd`ˆ^c^ˆ_c_‡aa_‡c_e‡f`h‡l`k†j^i†i]j‡j[l‡nWq†tOs…rMq„oLn…pPq…vRz…~T€‚X|z]xvcqzkkew_r[tWyRsN}JrG€@r>‚@uBFzP}`qt‚ŒkŽƒd‰‚azu]yƒ{^~„]†^€†~a}†yeu†ofe†YgP…IiH„GjG„GlJ„JnJ„JmL„NmN„NmQƒSlUƒVlV‚SmQNoMJqIGtD€@w:€6y4€4}0€0}1€2|3€5{7:y<‚>w@„BuE…GqI…JnP…Sm„„žp˜€œuŸ{˜}’t‡vrYŽQtxx–‹˜x~‡wwf„‡x„_y(Š y}Š½»‚©z„‹}u—¬x®zw[ye“{˜‰‰}>|Z|“v |˜{z{U‡Yz\Ž]ztŽwzm_|\Œ]{ZŒS|VŽm|hŽg|hV|HEz9Œ=zMŒSzFˆ@y>„Tzd„\|`„u|[‡F|L‹N~N‰G}CŠU{A‹=z@Š=|CŠA{77y9DyWSy?ˆ5{C„B{D†Cz;„@yO\yO€>|D…?};‡9}@†J~M€U~S|L}Yvj~ntqjtZ‚OzM‚HB€@…BBˆ?~<Š:~=ŒA~A‹CFˆS€UƒS€]xbbpa`n_^qbzjyszx}x}wzs~ptr€pnkƒHkT„xrv…FF„M‡NF†L€g€j€fudfoeeng€imjkmk€kllfm`‚\nVƒUrQ‚OtG€Ct@~CtD{NrV{]pd|hmi~ikh€gjf€cld€cma€]nZ€UpM€Er<€3t,€(w'})z+|/y2|5u:~?qA€FqL€Rq[driptu}xx{{{||y|€|x|{x}€}x„xˆxzŽŽ{~‹{‹€Œ{Œz‚ŽzŽ‚zŒŠx‡ƒƒy€~y}€{yy~wyx|w{x}x{x}w|v}t}q}l}hzc}_v^|^sc|doc}bna}eoj|qtyy‚}ŠyŒ~|Œ|‹|‹{‹{‹{‹{‹{Œ{Œ{‹{Šy‰}†uƒkxsbuwd{}m}€|sxustrrrqrnnqooqqprljrgcub}eygzlyswxxx†yŒ{‘y}Œv‹‰v(ÿ&ÿ&ÿ&ÿ%ÿ$ÿ$ÿ$ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ!ÿ!ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ!ÿ!ÿ!ÿ!ÿ"ÿ$ÿ$ÿ&ÿ&ÿ&ÿ'ÿ'ÿ(ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ0ÿ2ÿ2ÿ1ÿ2ÿ8ÿ>ÿDÿOÿ[ÿeÿpÿvÿtÿkÿaÿPÿ@ÿ7ÿ7ÿ7ÿ6ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ4ÿ4ÿ4ÿ2ÿ2ÿ3ÿ2ÿ0ÿ0ÿ/ÿ.ÿ,ÿ+ÿ*ÿ)ÿ'ÿ&ÿ'ÿ&ÿ%ÿ$ÿ#ÿ"ÿ"ÿ"ÿ"ÿ"ÿ#ÿ&ÿ+ÿ2ÿ;ÿAÿKÿUÿ\ÿcÿeÿdÿdÿcÿaÿcÿfÿiÿjÿjÿjÿhÿgÿeÿcÿdÿcÿeÿfÿfÿhÿkÿlÿmÿoÿpÿnÿmÿmÿmÿnÿnÿnÿqÿsÿtÿsÿrÿrÿpÿpÿqÿvÿzÿ|ÿzÿzÿzÿwÿuÿpÿkÿfÿ]ÿWÿQÿJÿHÿDÿDÿAÿ@ÿ>ÿ>ÿ=ÿ>ÿBÿJÿZÿlÿ}ÿ‰ÿŒÿ‹ÿ†ÿ|ÿrÿgÿkÿqÿvÿ{ÿ~ÿÿ‚ÿ‚ÿƒÿ€ÿ}ÿ{ÿtÿlÿeÿ[ÿSÿNÿKÿIÿIÿIÿJÿJÿMÿMÿNÿNÿPÿRÿSÿRÿOÿKÿKÿIÿFÿFÿDÿCÿ@ÿ;ÿ8ÿ6ÿ3ÿ3ÿ2ÿ2ÿ5ÿ7ÿ9ÿ;ÿ<ÿ>ÿAÿCÿDÿFÿJÿLÿMÿQÿQÿdÿÿŸÿœÿ ÿœÿ‘ÿƒÿ‚ÿ…ÿcÿQÿfÿ„ÿƒÿ†ÿƒÿƒÿÿŸÿ›ÿZÿ*ÿ#ÿxÿ¼ÿ»ÿ´ÿ¥ÿ‚ÿ]ÿ|ÿœÿ›ÿˆÿbÿVÿkÿŽÿÿ{ÿlÿpÿ–ÿ¤ÿfÿCÿ\ÿ_ÿcÿzÿ’ÿ’ÿvÿmÿdÿbÿ\ÿhÿaÿVÿ\ÿXÿ=ÿ9ÿ?ÿDÿKÿWÿYÿDÿ9ÿBÿBÿcÿ\ÿWÿbÿYÿKÿFÿCÿ@ÿ:ÿLÿTÿ3ÿ8ÿ>ÿIÿJÿ<ÿ4ÿ3ÿ5ÿ>ÿFÿJÿHÿDÿAÿGÿ@ÿEÿ7ÿ@ÿGÿTÿ=ÿ@ÿ<ÿ=ÿ:ÿHÿ]ÿXÿ[ÿ]ÿ?ÿEÿOÿMÿ^ÿ^ÿ_ÿZÿOÿ?ÿ?ÿ@ÿAÿBÿBÿBÿ@ÿ=ÿ>ÿAÿAÿCÿFÿTÿYÿWÿ_ÿcÿeÿcÿbÿaÿbÿgÿlÿtÿxÿwÿtÿsÿlÿ`ÿ\ÿrÿlÿfÿ_ÿTÿMÿCÿLÿ[ÿdÿsÿmÿhÿeÿdÿcÿbÿcÿeÿhÿjÿkÿkÿkÿhÿcÿ]ÿYÿRÿOÿJÿCÿ:ÿ7ÿ6ÿ<ÿDÿMÿVÿ]ÿfÿhÿiÿiÿhÿfÿfÿcÿcÿbÿ`ÿ\ÿXÿSÿKÿBÿ8ÿ0ÿ,ÿ(ÿ'ÿ)ÿ+ÿ/ÿ2ÿ5ÿ:ÿ?ÿBÿGÿLÿRÿ]ÿfÿlÿrÿuÿxÿ{ÿ{ÿ|ÿ|ÿ{ÿ{ÿ{ÿ}ÿ}ÿÿ„ÿˆÿÿÿÿŽÿŽÿÿ‹ÿ‹ÿŒÿŒÿÿÿŽÿŽÿÿŒÿ‰ÿ†ÿƒÿ€ÿÿ|ÿzÿxÿvÿvÿvÿwÿxÿxÿvÿtÿtÿoÿkÿeÿ`ÿ^ÿ]ÿ]ÿ`ÿcÿbÿbÿcÿfÿmÿsÿ{ÿ„ÿŠÿÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿŠÿ‰ÿ†ÿ‚ÿ~ÿxÿsÿuÿwÿ{ÿ}ÿ}ÿ|ÿxÿuÿrÿqÿqÿpÿnÿnÿoÿoÿpÿoÿlÿjÿfÿdÿeÿeÿiÿoÿrÿzÿ‚ÿ‰ÿÿ’ÿÿŒÿ‹ÿ‰ÿ&~%p&~&s$$v$$v$#u!!v ~ v!~!v!!x!!x!!x""x""x##x"!x$$x''v&'w'~(v)~*w+~+x,~,w--w--w-}-x.}.x.|.w-|-w.|.v.|.v0}/u0}1u01q6€;x75z5‚7z8‚7z9ƒƒ?wB„DsF…HqK…LpM…OpP…QnT…ƒn£‚Ÿq¢|Ÿz™v‰…ptˆŒ‹suPuW‘|v{uvŒss{ˆ t—‹@u*Ž#yhº¹…¶²‡¦{…`xd“vxƒ‘ƒiŠ`„}~‡€}rp{cw„x‹ƒOxDfwr’Žyˆ’z’Š|}|{c’a{c“SzR‘Xx\Ry3>zKSx^ŒkwFˆ/y0…5yMƒSxMƒQxJˆIzF‹D|D‹D|WŠ;|8Š8}=ˆ@{7‰3{2Œ5{4Ž8{D‹D};‡>?†B~A„S}@A}F@~=…B~J†RH„V~g€Q}Q|W~@yF@x0OvOMvP@{;€?D€FˆH€BŠ?>Š?@ŠBBŠDHˆQ€YƒZ€_wf€god€dle~fri{nyu{y}x~uxq‚pncƒ]hjƒgnJƒH~S‚N„MY‚e€s|n€itecnc€bmc€cme€eni€inh‚god_oZ„RpLƒGr@€9u2}1v5|:uC|LrV|]pd}fohhlf€ele€dlcao^[pX€QqI€Br7/t*~(w'|)|,{0z4}6v<~BsCHpOWp_€hsm€ruw}yxz{|}|y|€{y{{y{€}y€…yŠyŽyŒ~zŽ}‹z‹€zŽyŽ‚yƒŒz‹‚‡z„‚€y~ƒ}y|yxw~vzw{v{w}w|w}v|u|r}m|i}cy]{Yv\z[s_zaoc}cmd}hnp{xu~zˆ}Œz}‹{Š{‹|‹z‹z‹{‹{‹}Š{Š|Š{Šz‡}„t€€yjtrds‚wg{€|o|€{ux€usrpqpnqnnonopn~mpl~jrhgve}fyizn{qw}z†xy{x~Œx‹~Šw&ÿ%ÿ$ÿ$ÿ#ÿ#ÿ#ÿ!ÿ!ÿ ÿ!ÿ!ÿ ÿ ÿ!ÿ!ÿ ÿ ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ"ÿ"ÿ#ÿ#ÿ$ÿ$ÿ'ÿ'ÿ'ÿ'ÿ(ÿ)ÿ)ÿ*ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ4ÿ:ÿ@ÿKÿWÿbÿjÿqÿrÿoÿfÿYÿNÿDÿ<ÿ7ÿ8ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ3ÿ1ÿ1ÿ1ÿ/ÿ,ÿ,ÿ+ÿ*ÿ)ÿ(ÿ'ÿ'ÿ&ÿ%ÿ%ÿ$ÿ#ÿ"ÿ"ÿ!ÿ"ÿ#ÿ%ÿ(ÿ0ÿ6ÿ>ÿCÿKÿQÿSÿSÿOÿJÿIÿOÿTÿZÿaÿeÿgÿhÿgÿhÿhÿgÿiÿjÿjÿmÿnÿpÿqÿqÿqÿrÿrÿqÿqÿqÿuÿuÿwÿ{ÿ{ÿ{ÿzÿwÿtÿtÿuÿvÿvÿwÿuÿtÿrÿoÿlÿgÿaÿ[ÿSÿKÿDÿ@ÿ>ÿ>ÿ?ÿ>ÿ=ÿ>ÿ<ÿ<ÿ;ÿ<ÿ=ÿAÿGÿRÿcÿxÿ†ÿŠÿ‡ÿ‚ÿuÿcÿOÿJÿOÿXÿbÿkÿrÿzÿÿÿ…ÿ†ÿ‡ÿ…ÿƒÿ€ÿyÿvÿoÿdÿ[ÿSÿOÿKÿKÿLÿLÿMÿMÿMÿMÿHÿFÿCÿAÿ@ÿAÿBÿCÿBÿAÿ=ÿ;ÿ<ÿ:ÿ9ÿ;ÿ>ÿAÿAÿBÿCÿDÿFÿHÿJÿLÿNÿOÿSÿUÿQÿSÿcÿšÿ¡ÿÿžÿ—ÿ‘ÿqÿYÿ~ÿ‡ÿtÿ^ÿMÿdÿ}ÿ{ÿvÿqÿuÿÿ‚ÿ2ÿ*ÿ*ÿMÿµÿ´ÿ±ÿ°ÿ«ÿÿˆÿrÿiÿjÿmÿoÿhÿuÿ|ÿmÿdÿtÿvÿbÿ>ÿ>ÿXÿƒÿ¡ÿ™ÿ–ÿ•ÿŒÿÿuÿfÿ[ÿUÿKÿMÿXÿ_ÿ^ÿGÿKÿBÿFÿZÿUÿDÿ3ÿ2ÿ9ÿ>ÿ5ÿ:ÿNÿIÿIÿCÿ>ÿ;ÿFÿSÿ:ÿ5ÿ8ÿ9ÿ<ÿFÿ;ÿ2ÿ2ÿ2ÿ8ÿ@ÿNÿFÿ=ÿ?ÿ7ÿNÿYÿQÿYÿKÿ9ÿBÿCÿQÿAÿHÿVÿUÿOÿRÿNÿ7ÿ@ÿ2ÿ0ÿIÿKÿYÿ@ÿ6ÿ:ÿ>ÿCÿFÿIÿFÿAÿ?ÿ?ÿAÿAÿCÿGÿHÿUÿ\ÿYÿZÿcÿhÿcÿeÿaÿgÿmÿrÿ{ÿwÿsÿjÿ^ÿgÿgÿ^ÿ^ÿRÿGÿFÿ:ÿ:ÿKÿSÿ_ÿrÿmÿiÿeÿcÿbÿaÿdÿdÿdÿhÿgÿgÿgÿfÿ`ÿ]ÿUÿMÿDÿ=ÿ5ÿ-ÿ,ÿ.ÿ3ÿ<ÿFÿNÿXÿ_ÿdÿfÿgÿfÿeÿeÿcÿaÿ`ÿ`ÿ^ÿ[ÿTÿOÿHÿ?ÿ7ÿ/ÿ*ÿ(ÿ'ÿ)ÿ,ÿ0ÿ6ÿ9ÿ;ÿ?ÿCÿHÿOÿYÿaÿiÿoÿuÿwÿyÿzÿ|ÿ|ÿ|ÿ{ÿ{ÿ{ÿ{ÿ~ÿÿ…ÿŠÿÿŽÿÿŒÿŒÿŒÿ‹ÿ‹ÿÿÿÿÿÿÿŒÿ‰ÿ…ÿ‚ÿÿ}ÿ}ÿzÿyÿwÿuÿvÿwÿxÿwÿwÿuÿtÿqÿlÿfÿaÿ[ÿXÿWÿZÿ_ÿaÿbÿcÿfÿlÿqÿyÿ‚ÿŠÿÿÿŒÿŠÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿŠÿŠÿŠÿŠÿ‡ÿ„ÿ~ÿvÿqÿpÿrÿuÿxÿzÿ|ÿyÿwÿtÿrÿpÿpÿnÿnÿnÿnÿnÿmÿkÿjÿjÿhÿgÿeÿeÿhÿmÿqÿ}ÿ†ÿÿÿÿÿ‹ÿŠÿ‰ÿ&~$v$~$w##v#!v  w !x  z zy  y  x  x!!x!"x$$w%%w''w()w)~)z)~*z+~+z,~,x-~-x.~.x.}.x.}.x-|.w/|/w/|/v.|.v.}.u/}/u0}0s27p<„FkQ‡\efˆk^p…pZiƒb]WM_C}ƒDpH‚KnK‚HlGƒFkI„OiV…[g^…`da…dac…ecg„idj†jdkˆmboˆp`q‡q_sˆr[s‡tXv†wVx‡zU{‡{S|†|Qy…wPv„vOtƒrOppQo€l]i|eg^vVqLsD{?r@?q=ƒ>q=…=r=…=r;…;s;„;v<„?xFPawsƒ‚m‰„‡h‚wfd}Mj;{@pE}NrYcom€xjƒ„gˆ„‡bˆ…‡`……a~…ydu…kd_…VdP…MhL„MkL‚MnLFsC€?u;:t:=uA‚CuCƒCtC„CsC„CrD…DrC…DrD…DqG…JqK…LpN†OpQ…OnQ„RmU†Ump„£p¡~œy™y”ƒwJ†LwnŠtƒluP“Ovq’xvwwupŽ‚sp3t1“0w5’£|®‹§}¨ˆ¤|Š‹€|xj{`’T}YŽW}fj}c{m{lhzSˆ8w?Tvƒ“¥x ‘“z—Ž’|’Œ”{tIz<“^xo’bw[TzN‹L|F‹C~C‹K}RˆD}3†@|Dƒ9{:‚:z8†>zG‰4|3ŠB|KŠK|?Š3}A‰>|?Š7|1‰3{0‹5|9ŠD}C‰><„Pe€`~i€_~;„=~<ˆA~NˆQ~I…E}O„H}MM;<€F}=€C|@?y81}7€?ƒA~@ˆ@~AŠA~@Š@~AŠAHŠIJˆM€M„LR~`hqg~fhZ_lp€ejdƒcfT„K_D„E`J„EhOƒOzJ@„BN‚C~I~d~uymgsdapb€dnd~cne|fnhhnfco^‚XnPƒFp<‚3t+)w)|/y5|>xI{QtY|`qe}gng€emd€dnc€bn`_o\XpU€PqG€>r4-u)~(x(~*{,}0y5}8v:~=sBIqRZqb}jsp}vuv}xxy{{}{y|€{yzzy{€}y€†y‰ŒyyŒ€‹z‹Œz‹€‹zŒŒyŒ‚yƒ‹xˆ‚„x‚y}ƒ|yzwxu~tzu{v{v|v|u|t|r|o}j|e}_yZ{UvW{Yu]{`qa|dmh|pnt{zu„z}{Ž}Œ|Œ|Œ|Œ{Œ{Œ|Š{Š|‹{‹zŠ{‰y‡}ƒr}€tjppfptlvxuz~xuu~rsqqqonqnnommpn~lpk~kriiuf}gxhzmysw~z‡xyzŽxŒ~‹v‰~‰w$ÿ!ÿ#ÿ#ÿ"ÿ!ÿ ÿÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿÿÿÿÿÿÿÿ ÿ ÿ!ÿ"ÿ!ÿ"ÿ$ÿ$ÿ%ÿ%ÿ'ÿ'ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ.ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ0ÿ2ÿ9ÿAÿKÿWÿ`ÿeÿkÿpÿnÿhÿ`ÿUÿLÿFÿ>ÿ;ÿ:ÿ8ÿ6ÿ6ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ4ÿ6ÿ6ÿ6ÿ6ÿ5ÿ3ÿ2ÿ3ÿ3ÿ2ÿ1ÿ/ÿ-ÿ,ÿ,ÿ+ÿ*ÿ(ÿ(ÿ(ÿ(ÿ&ÿ%ÿ%ÿ$ÿ"ÿ"ÿ"ÿ#ÿ#ÿ%ÿ&ÿ+ÿ0ÿ3ÿ9ÿ>ÿ@ÿCÿBÿBÿCÿGÿIÿNÿSÿWÿYÿXÿ[ÿ\ÿ\ÿ]ÿ_ÿaÿaÿdÿgÿiÿkÿmÿpÿqÿtÿsÿtÿuÿxÿyÿyÿ|ÿ|ÿ|ÿ|ÿ|ÿyÿzÿxÿuÿqÿnÿlÿiÿfÿcÿ_ÿXÿPÿGÿBÿ?ÿ>ÿ>ÿ=ÿ=ÿ>ÿ<ÿ=ÿ<ÿ<ÿ:ÿ:ÿ9ÿ:ÿ;ÿ=ÿCÿMÿ^ÿpÿ€ÿˆÿ‰ÿ†ÿ|ÿjÿSÿ7ÿ1ÿ5ÿ=ÿHÿOÿ^ÿjÿuÿ}ÿ„ÿ‡ÿ†ÿ‡ÿ‡ÿ…ÿ‚ÿ~ÿ{ÿvÿpÿiÿ[ÿSÿOÿMÿLÿKÿHÿBÿ?ÿ;ÿ;ÿ;ÿ>ÿAÿEÿFÿGÿGÿIÿIÿJÿJÿJÿJÿJÿJÿJÿJÿLÿMÿNÿOÿQÿRÿRÿRÿRÿSÿWÿVÿvÿ£ÿžÿ›ÿ•ÿŒÿnÿ:ÿIÿcÿ‰ÿˆÿxÿXÿNÿYÿrÿxÿzÿxÿ„ÿ`ÿ8ÿ5ÿ4ÿ.ÿzÿ¤ÿŸÿ¢ÿ¤ÿ–ÿ‰ÿ~ÿmÿdÿXÿNÿBÿ?ÿDÿ]ÿjÿhÿcÿJÿ<ÿEÿQÿ~ÿŸÿžÿ˜ÿ™ÿ¢ÿŸÿ”ÿcÿCÿNÿ|ÿ}ÿvÿmÿdÿ`ÿSÿJÿPÿYÿXÿZÿPÿGÿ<ÿ:ÿFÿGÿ:ÿ;ÿ=ÿEÿAÿ?ÿAÿDÿUÿPÿBÿAÿ7ÿ0ÿ5ÿ=ÿ6ÿ,ÿ0ÿ3ÿ6ÿ:ÿ?ÿUÿbÿYÿVÿNÿ<ÿ;ÿ=ÿDÿ^ÿXÿHÿDÿFÿHÿQÿ]ÿWÿLÿJÿMÿKÿMÿ@ÿ>ÿ@ÿ;ÿ<ÿ?ÿ>ÿ:ÿ;ÿ>ÿ@ÿAÿ@ÿ>ÿ?ÿ?ÿCÿFÿGÿHÿIÿTÿaÿhÿkÿ\ÿFÿNÿNÿ`ÿkÿZÿOÿTÿ]ÿTÿ4ÿ/ÿ@ÿHÿCÿAÿFÿGÿGÿTÿrÿrÿlÿhÿeÿdÿdÿeÿcÿcÿdÿeÿiÿhÿeÿ`ÿ[ÿUÿKÿ@ÿ5ÿ-ÿ(ÿ)ÿ)ÿ/ÿ6ÿ>ÿIÿQÿ[ÿbÿfÿhÿgÿeÿdÿcÿbÿbÿaÿ_ÿ\ÿXÿTÿOÿEÿ=ÿ3ÿ-ÿ)ÿ(ÿ(ÿ*ÿ,ÿ0ÿ5ÿ8ÿ:ÿ=ÿAÿJÿSÿ[ÿbÿjÿpÿvÿvÿxÿyÿ{ÿ{ÿ|ÿ{ÿzÿzÿ{ÿ~ÿÿ†ÿ‰ÿŒÿÿŒÿŒÿ‹ÿ‹ÿ‰ÿ‰ÿŠÿ‹ÿŒÿŒÿŒÿÿ‹ÿˆÿ„ÿÿ~ÿ|ÿ{ÿyÿxÿvÿtÿuÿvÿvÿxÿwÿvÿtÿnÿiÿcÿ\ÿXÿTÿTÿXÿ\ÿ`ÿaÿeÿjÿqÿxÿ~ÿˆÿÿÿŽÿÿŒÿŒÿŒÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿŠÿ‰ÿ…ÿ€ÿyÿoÿlÿlÿnÿpÿvÿwÿzÿxÿuÿrÿqÿoÿnÿnÿnÿnÿmÿmÿmÿmÿmÿkÿjÿjÿjÿhÿjÿoÿvÿ€ÿŠÿŽÿÿŽÿŒÿŠÿ‰ÿ‰ÿ"~!w!~!w ~ x!~ x wwyyzzy!!w!~"w!~"w"#w%%x''z()z)~)z)~*z+~+z,},z-}-y.}.y/}/y.}.x.}.x.}.x0|0w.|.v.|.v.|.v,|/s/~1p5€;mD…NhYˆcah†o^p†k^dƒ^^UN_GCa@|=e:|8h5y3k3y1n4y4o4y4o4y4m4y4m5y5n6y6n6y6p6y5p4y4p3y3p2y/p/y-p,z+r+z)s(z)u'|'v&}&w&}$y$~#x#~"y"€$x&€'x-€1v5€;t:€|~I~Yxm‚~oˆ„Šf†‚€fq\j>{*s+z/v7|?uK~Vrd€nnv€}mƒ‚†e‡ƒ‡_……„^‚…`y†rbiƒ]gS‚LiK‚GmDBq=sCƒGqH†JqM‡NoN†PnR†SmT†SoP†OmO…OlO„OoP…QnR…SoR…SpU†VoV…Vm|‚£qž~˜{z‹jz6„Uya‡{wŒŽ}td“RuL•fvu•}u‚”‰sV–9s7—8u8–Iy”’˜zž‹£|”‰…~}ˆw‚lˆ`‚U…L~F‚I{\…c{\ˆZzGŒAyFJvr”™v•’’z”Žž{žzzZxs‘‰vy’lsi“\uZ”V{]’l~qŽyvjbŠ[TˆU~V‡UzUˆ\zh‹P|C‹?z;ŒFyNIz;Œ0z1Š9{G‰7{-ˆ-}/‡4}8†K~f`~UR~P‚=~>ˆC}JŒA}=>|JŒM{F‹D}MŠN~I‰>~A‰LV…VV€SWƒF:‡=A‡=~:ˆ>~AŠA~CˆEG‡E€A‡MR„Q^SO{Y‚\tIEeRZ]Z€dZY[_T‚Ci:‚>t9‚E|G<}0ÿCÿHÿLÿMÿMÿKÿKÿLÿLÿMÿOÿQÿWÿ[ÿaÿdÿgÿjÿmÿpÿrÿrÿxÿyÿzÿ|ÿÿÿ„ÿ‚ÿ~ÿ{ÿxÿsÿoÿkÿfÿ^ÿZÿWÿSÿMÿFÿ?ÿ:ÿ:ÿ;ÿ;ÿ;ÿ;ÿ;ÿ;ÿ:ÿ:ÿ9ÿ9ÿ8ÿ8ÿ7ÿ7ÿ7ÿ7ÿ:ÿ=ÿDÿRÿeÿyÿ…ÿ‰ÿ†ÿ‚ÿyÿhÿLÿ0ÿ*ÿ)ÿ,ÿ0ÿ9ÿCÿMÿZÿfÿqÿyÿ~ÿ‚ÿ„ÿ„ÿ„ÿ‚ÿÿ€ÿÿvÿmÿbÿUÿKÿFÿAÿ@ÿCÿCÿCÿEÿHÿKÿNÿQÿTÿUÿUÿUÿVÿWÿXÿWÿUÿTÿSÿSÿQÿQÿRÿPÿTÿSÿQÿUÿTÿUÿTÿZÿ‹ÿžÿœÿ—ÿÿÿsÿ,ÿ<ÿXÿqÿ†ÿ}ÿhÿMÿGÿLÿfÿuÿ|ÿuÿJÿ:ÿ;ÿ<ÿ<ÿ:ÿgÿŽÿ‘ÿ–ÿ†ÿwÿrÿtÿmÿeÿYÿPÿPÿTÿZÿUÿQÿQÿIÿFÿGÿGÿfÿŽÿ‹ÿ‡ÿ‹ÿ“ÿ—ÿ•ÿ‰ÿ}ÿ|ÿ]ÿNÿQÿTÿMÿOÿUÿNÿRÿnÿ‰ÿŒÿpÿsÿ…ÿpÿbÿZÿMÿFÿKÿXÿOÿNÿFÿ;ÿGÿOÿ@ÿ2ÿ.ÿ3ÿ7ÿ<ÿ8ÿ/ÿ-ÿ.ÿ.ÿ3ÿ<ÿ?ÿCÿ?ÿKÿVÿJÿMÿIÿ;ÿ7ÿ8ÿ;ÿ;ÿ:ÿ<ÿ>ÿ>ÿAÿ=ÿ8ÿ:ÿ;ÿ<ÿCÿHÿJÿMÿPÿ@ÿAÿDÿAÿ>ÿ?ÿAÿAÿBÿDÿCÿCÿFÿJÿJÿIÿMÿRÿPÿHÿCÿPÿQÿBÿEÿJÿIÿTÿSÿ@ÿ?ÿBÿ@ÿAÿKÿQÿfÿkÿtÿyÿxÿuÿnÿfÿtÿxÿwÿdÿXÿ]ÿTÿ^ÿ^ÿcÿfÿeÿaÿ]ÿUÿFÿ;ÿ1ÿ(ÿ(ÿ'ÿ)ÿ0ÿ7ÿ@ÿKÿUÿ\ÿdÿfÿgÿgÿfÿdÿbÿaÿaÿ`ÿ_ÿ\ÿXÿRÿMÿDÿ;ÿ2ÿ,ÿ)ÿ)ÿ)ÿ+ÿ-ÿ1ÿ5ÿ7ÿ8ÿ<ÿAÿJÿVÿ_ÿfÿlÿqÿvÿxÿyÿzÿzÿ{ÿ{ÿ{ÿzÿyÿzÿ~ÿ€ÿ‡ÿ‹ÿŒÿŒÿ‹ÿŠÿ‰ÿ‰ÿ‰ÿ‰ÿŠÿŠÿ‹ÿ‹ÿ‹ÿ‹ÿŠÿ‡ÿƒÿ€ÿ}ÿ{ÿxÿvÿuÿtÿuÿuÿuÿuÿvÿvÿtÿpÿlÿgÿ_ÿXÿSÿOÿQÿUÿ[ÿ_ÿeÿiÿoÿvÿ|ÿ‚ÿ‰ÿÿŽÿÿ‹ÿ‹ÿ‹ÿ‹ÿŠÿŠÿ‹ÿ‹ÿ‹ÿŒÿˆÿ†ÿÿzÿoÿhÿdÿgÿkÿpÿuÿwÿwÿuÿsÿqÿqÿoÿnÿmÿmÿmÿlÿnÿnÿnÿnÿnÿmÿmÿkÿkÿmÿsÿ|ÿ†ÿŽÿÿŽÿŒÿ‹ÿ‰ÿŠÿŠÿ!~!w ~ w ~ x~xyyyyzzy  w ~!w!~"w#$z&&{'(z()z*}*z+},z+},z,}.z.}.y/}/y/}/y/}.x.}.x.}.x-|-w-|-v/|/w.|-w,|+u,}-t,~1r7=kG…Qg\…dci…ibi†fbd…_aZ„UaQ‚N`IDa@|:g6{5j3z3l3z3n3y3m4y4m4y4n5y5n4y4n4y3n3y3o2y2o1y0o/y.o-z,q,z+r*z)s(|'u&}&v&}&w$~$w$~#x""z#$z$€(x-€0v1€3v2€6t89r>ApD‚FnH‚HmG„JiL„NgQ†UcZ‡[b_‡c_f‡k]n‡r[r‡sXw…{W|„€Y~„€[}„|\y‚w_q€nag~ce]{XiPxMpGvBx=t;~:t::t9:t:‚9t9ƒ9t9ƒ8s8ƒ7t6ƒ6u6‚6v6ƒ7x9>|Kz_rs€„‰kˆ„‚f}‚nhW;o)|)v'|)|-}4z=}IuS~]shooxkƒ‚„g„‚‚h{ƒuiv…xip„aiT„IhE„DjG‡IlJˆMiR‰UhYŠ[h]‰^g]‡YhW‡XkZˆ]nZˆXmV‡VlU†TlS…RnS„TpR„SrS„Vq]ƒbr•x˜~˜{•{Œ}{+ƒ9yN†`w{Žytc•NtK—DuI™PrR›IqAœ=r>œ>u=šŸ5s?œ[ua˜jx”{Žt|eŠ[}N‡R}W‡H|JŠL{=‹?z:‹5z2Š8|9ˆ2|5†5|0†-}*„'}*ƒ1;ƒ?>†T}L‰=|7Œ4{7Ž7|43|95|35~8Š;;‹:9‹8~=‹=~;Œ:}B‹Q}D‰A~?†E€M…G€C…A=„??…BG…IRƒ^€Z]d}T‚V|^‚\rJ‚=nRZuQ€:@€LƒG€E~P€axp€vvsstwtwssvm€ptiesJ{SuAsHv]rcseygmeal[‚SmG‚;r1‚+w&'z*{1{9zByK{Tt]}dqf~gng€fnc€bna€an_€^p[€WqR€JsC€9t2€-w*+{)~,|.~0z4~6x8~=wC|LuW|`ug|mvr|vxx|zzzzz{yz‚{y{zy}yƒ€ˆzŒ‹z‹Š{‰€‰{‰€ˆzˆ€‰z‰‚ŠyŠƒŠyŠƒ‰x†„x€ƒ~w|ƒxxvvyu}uzu{uzu}v{u}u{o}j~e|]~VzQ}OwQzWu^ybrf|kpq{yt}{„z‹{Ž}Ž{Œ~Š|Š}Š|Š|‰z‰{‹|‹}Š{‰}†|‚y~vtmeleekj~nvs{wyw{tvq}opqpponpm~mpn}mpn}pqp}pqn~mqn~lrp{uu~y‡yŽzz|‹zŠ~‰z‰~‰z!ÿ!ÿ!ÿ!ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ ÿÿ ÿ!ÿ"ÿ#ÿ$ÿ&ÿ&ÿ'ÿ(ÿ(ÿ)ÿ*ÿ*ÿ+ÿ,ÿ,ÿ,ÿ,ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ.ÿ,ÿ,ÿ-ÿ-ÿ0ÿ4ÿ7ÿ@ÿFÿRÿ[ÿ^ÿcÿeÿfÿeÿbÿ`ÿ[ÿXÿUÿSÿMÿHÿEÿ=ÿ6ÿ4ÿ3ÿ2ÿ2ÿ2ÿ3ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ4ÿ4ÿ1ÿ0ÿ0ÿ/ÿ-ÿ,ÿ+ÿ+ÿ*ÿ*ÿ(ÿ'ÿ(ÿ(ÿ&ÿ&ÿ%ÿ#ÿ%ÿ$ÿ$ÿ#ÿ#ÿ$ÿ$ÿ$ÿ'ÿ+ÿ,ÿ-ÿ3ÿ7ÿ8ÿ9ÿ=ÿ?ÿ?ÿ@ÿCÿEÿFÿMÿRÿUÿ[ÿ^ÿ`ÿdÿgÿiÿkÿlÿnÿqÿuÿwÿzÿzÿ}ÿ|ÿ|ÿzÿwÿvÿsÿoÿjÿdÿ]ÿWÿPÿIÿCÿAÿ?ÿ>ÿ;ÿ;ÿ8ÿ8ÿ8ÿ8ÿ9ÿ9ÿ8ÿ8ÿ8ÿ8ÿ7ÿ7ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ8ÿ:ÿFÿUÿjÿ{ÿ…ÿ‰ÿ†ÿ‚ÿvÿdÿGÿ-ÿ(ÿ&ÿ(ÿ)ÿ)ÿ,ÿ5ÿ>ÿHÿUÿ]ÿgÿpÿvÿ}ÿ|ÿpÿeÿ_ÿfÿ{ÿxÿpÿeÿZÿOÿNÿPÿVÿZÿbÿiÿlÿnÿoÿoÿpÿlÿfÿ_ÿZÿYÿ[ÿ_ÿ^ÿZÿVÿVÿVÿTÿTÿRÿPÿOÿPÿPÿOÿOÿYÿ™ÿ ÿ—ÿ–ÿ”ÿŒÿÿ3ÿ1ÿ8ÿVÿnÿpÿSÿJÿPÿHÿAÿ<ÿ;ÿ=ÿ@ÿAÿAÿAÿ@ÿ>ÿ7ÿEÿrÿ~ÿsÿeÿdÿaÿXÿZÿRÿKÿNÿNÿJÿKÿMÿLÿFÿGÿGÿJÿLÿYÿuÿ‡ÿ…ÿzÿ{ÿ”ÿŽÿwÿFÿ:ÿAÿBÿCÿAÿAÿ@ÿ>ÿ;ÿ6ÿ/ÿ2ÿVÿ€ÿŒÿ‡ÿvÿbÿ`ÿUÿQÿSÿIÿ=ÿDÿ9ÿ8ÿ8ÿ4ÿ9ÿ?ÿ=ÿ2ÿ.ÿ1ÿ/ÿ.ÿ-ÿ)ÿ<ÿAÿ9ÿ8ÿ<ÿ8ÿ6ÿ3ÿ4ÿ7ÿ;ÿ<ÿAÿCÿ:ÿ4ÿ3ÿ6ÿ6ÿ2ÿ<ÿBÿCÿGÿ<ÿ9ÿ9ÿ;ÿ=ÿFÿFÿBÿ>ÿ8ÿ<ÿ?ÿ@ÿAÿ>ÿAÿEÿCÿBÿIÿIÿYÿQÿjÿuÿMÿPÿiÿpÿrÿaÿIÿAÿ@ÿHÿIÿHÿ=ÿTÿhÿnÿpÿqÿrÿjÿZÿpÿÿfÿlÿ^ÿUÿRÿ@ÿ@ÿLÿUÿ]ÿcÿhÿeÿdÿbÿ[ÿSÿHÿ:ÿ1ÿ+ÿ'ÿ(ÿ,ÿ2ÿ:ÿCÿLÿUÿ]ÿdÿfÿgÿgÿfÿbÿbÿaÿaÿ_ÿ^ÿ\ÿXÿRÿJÿBÿ:ÿ3ÿ,ÿ+ÿ+ÿ)ÿ,ÿ.ÿ0ÿ1ÿ5ÿ8ÿ=ÿEÿNÿWÿ`ÿiÿpÿtÿvÿxÿzÿ{ÿ{ÿ{ÿzÿzÿzÿzÿ}ÿÿƒÿ†ÿŠÿ‹ÿ‹ÿ‰ÿ‰ÿˆÿˆÿˆÿˆÿ‰ÿ‰ÿŠÿŠÿŠÿŠÿ‰ÿ†ÿƒÿÿzÿxÿvÿvÿvÿuÿuÿuÿuÿuÿvÿuÿuÿoÿjÿeÿ]ÿVÿQÿOÿQÿWÿ^ÿbÿgÿlÿrÿzÿÿ‡ÿÿŽÿŽÿŒÿŠÿŠÿŠÿŠÿ‰ÿ‰ÿ‰ÿŠÿ‰ÿ‰ÿ†ÿÿzÿrÿhÿaÿ`ÿbÿgÿnÿsÿvÿvÿsÿqÿoÿoÿoÿoÿnÿoÿoÿnÿmÿnÿpÿoÿoÿnÿmÿnÿnÿtÿzÿƒÿŠÿŽÿŽÿÿ‹ÿŠÿ‰ÿ‰ÿ‰ÿ!~#x#~#x!~x~xyyzzzzy  y y"#y#~$y%~%y()x()x+}+x+},x-}-z,}.y.}.y.|/y.|.y/|.z/|/y-|-x-|-y-|,w,|,w,|,u,|,w-},w,},u.~1q8€@mHƒQiVƒZi^…`eaˆbaaˆa``†]_Z„V_P€JbE~@c:{5f3z3j2y2m3y3o3y3n4y4n3y3n4y3n3y3n3y2n0y/n0y0n.y-q+z+q+{*r({'t'|'u'~%w&}$x%~#z#~${$~${#€#z%€'x*€,x/3t7ƒ;p>„@m?„@lC‡IhMˆVfZˆ]ca‰i^kˆn\n‡o[o†pYs…v\xƒw^y„w_vƒwbu€tcqnfj}ci^zXnQxJtCvw=˜2x6•FzQq~n‰c€]ƒZ„WV‚P€OM„I{HˆFyKŽLwI”IvPšRtW ZtX itxœ|s†”•rŽ’mrC”;r<–;s8™9s9™:u9š7u6™3u/—;uf•~u†–†ysa{SŠN}I…C}EƒK~J‚=2…6~Jˆ;}4…,~3ƒ4/‚+€*„3€?€=€D€E9†5~3Œ4|9Ž6|<ŽB~D<~7Š6~5‰5}4Š3}4‰3}3‰4|5Š7~7‹:|:Œ>|J‰V~I†6~0ƒ,3€8~:€<~E‚?;„=@„F~Z~\Q{CL|[gwb€]rH‚A|HJ†E=‡Y€mm€mvp€rttZwC~l~v}t}†€FxW€`rW|GpIsPs[tfqg|hmg‚bl[„RnH„:s1‚+x)*{/{4{;zDwN|Wt`}dqf~gng€fmd€bma€`o_€^q\€YqR€IsB:s0€*v(€)w*-w11v4~7u9~>uH|SuZ{cwk|quu|xwy|{||{||yz‚yyyyy|€xƒ€‰y‹‹y‹€‰{‰€‡{‡€‡z‡ˆyˆ‚ŠyŠƒŠyŠƒˆy…ƒƒxƒzwxƒwxvuyu|vzv{vzv|u{u}s{m~g~c|\~SzO|NvS{Yu_zcrg{mpt{|uƒ{‰{{Ž}{‹~‰|‰}‰{‰|Š{Š}Š{‹~Š|ˆ}…~€{ymrd‚]o\`sg{o|t{u|u}stqopn~ppo~npn~non~non}npm}mpn~npq~qqv|}v…zŽz{zŒ}‹zŠ~‰z‰~‰z!ÿ#ÿ%ÿ%ÿ"ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ ÿ ÿ!ÿ ÿ!ÿ#ÿ$ÿ%ÿ%ÿ(ÿ)ÿ(ÿ)ÿ+ÿ+ÿ+ÿ,ÿ-ÿ-ÿ,ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ.ÿ/ÿ/ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ*ÿ*ÿ+ÿ+ÿ,ÿ+ÿ,ÿ,ÿ,ÿ,ÿ1ÿ6ÿ?ÿFÿLÿOÿSÿXÿ[ÿ]ÿ^ÿ`ÿ_ÿ`ÿ_ÿ\ÿWÿSÿNÿHÿAÿ:ÿ5ÿ3ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ3ÿ3ÿ4ÿ3ÿ3ÿ3ÿ2ÿ1ÿ/ÿ/ÿ/ÿ0ÿ/ÿ-ÿ,ÿ,ÿ-ÿ+ÿ)ÿ)ÿ'ÿ'ÿ'ÿ&ÿ&ÿ$ÿ$ÿ#ÿ$ÿ%ÿ$ÿ$ÿ%ÿ%ÿ%ÿ'ÿ+ÿ0ÿ5ÿ9ÿ=ÿAÿDÿFÿJÿKÿOÿUÿ[ÿ^ÿcÿfÿjÿnÿrÿrÿsÿtÿvÿuÿwÿwÿwÿtÿsÿqÿoÿmÿkÿiÿfÿ`ÿ\ÿVÿPÿKÿBÿ:ÿ7ÿ5ÿ6ÿ8ÿ8ÿ9ÿ:ÿ:ÿ9ÿ8ÿ6ÿ7ÿ5ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ4ÿ3ÿ3ÿ1ÿ1ÿ1ÿ1ÿ2ÿ3ÿ7ÿBÿVÿjÿyÿ„ÿˆÿ…ÿ‚ÿyÿeÿFÿ,ÿ%ÿ'ÿ%ÿ'ÿ)ÿ*ÿ0ÿ6ÿ;ÿDÿSÿbÿmÿhÿkÿ]ÿdÿxÿqÿxÿyÿxÿyÿrÿsÿrÿwÿÿƒÿ…ÿ…ÿƒÿ†ÿ‚ÿ‚ÿ…ÿ…ÿƒÿÿ}ÿÿÿ~ÿxÿpÿgÿ[ÿQÿOÿLÿLÿGÿGÿEÿCÿCÿCÿAÿzÿžÿ•ÿÿŠÿ„ÿƒÿYÿ.ÿ5ÿAÿ`ÿVÿCÿHÿMÿJÿHÿEÿBÿCÿCÿBÿAÿ@ÿ?ÿ^ÿnÿ^ÿBÿXÿlÿgÿ_ÿXÿSÿRÿNÿOÿMÿKÿGÿHÿMÿMÿKÿMÿLÿTÿVÿWÿVÿXÿZÿoÿ˜ÿ•ÿ„ÿoÿUÿBÿ8ÿ;ÿ:ÿ5ÿ7ÿ6ÿ4ÿ2ÿ1ÿ.ÿ-ÿ-ÿDÿjÿ|ÿ‰ÿŒÿ€ÿeÿTÿNÿJÿDÿAÿIÿJÿ/ÿ)ÿ,ÿ/ÿ3ÿ2ÿ<ÿ:ÿ,ÿ*ÿ1ÿ9ÿ,ÿ'ÿ4ÿ?ÿ9ÿ4ÿ3ÿ4ÿ3ÿ4ÿ1ÿ3ÿ4ÿ0ÿ0ÿ3ÿ3ÿ4ÿ3ÿ3ÿ1ÿ0ÿ0ÿ0ÿ1ÿ4ÿ9ÿ<ÿ:ÿ=ÿCÿLÿDÿ/ÿ)ÿ.ÿ3ÿ7ÿ5ÿBÿMÿ;ÿ?ÿUÿXÿQÿSÿVÿ`ÿmÿZÿ@ÿDÿPÿbÿ]ÿKÿDÿ@ÿBÿ[ÿnÿjÿnÿpÿmÿpÿvÿwÿeÿEÿbÿ¨ÿ’ÿKÿdÿ]ÿLÿBÿJÿQÿ`ÿhÿhÿiÿgÿbÿ\ÿSÿHÿ:ÿ1ÿ+ÿ)ÿ*ÿ.ÿ3ÿ=ÿGÿOÿYÿ`ÿdÿfÿgÿgÿfÿdÿbÿaÿ`ÿ_ÿ^ÿZÿWÿQÿJÿBÿ:ÿ3ÿ2ÿ2ÿ3ÿ3ÿ7ÿ;ÿ;ÿ<ÿ<ÿ@ÿEÿMÿTÿ\ÿeÿmÿsÿwÿyÿyÿ{ÿ|ÿ|ÿ}ÿ{ÿxÿxÿxÿ{ÿ€ÿƒÿ‰ÿ‹ÿŒÿŒÿ‰ÿ‰ÿ‡ÿ‡ÿ‡ÿ‡ÿˆÿˆÿŠÿŠÿŠÿŠÿˆÿ…ÿ‚ÿ~ÿ{ÿyÿwÿvÿtÿsÿvÿvÿvÿvÿuÿuÿsÿmÿhÿbÿZÿQÿNÿOÿSÿYÿ`ÿdÿiÿpÿwÿÿ†ÿ‹ÿÿŽÿŒÿŠÿ‰ÿ‰ÿ‰ÿ‰ÿŠÿŠÿ‹ÿ‹ÿŠÿˆÿƒÿ{ÿrÿhÿ^ÿZÿZÿ^ÿeÿkÿrÿvÿuÿsÿqÿoÿpÿnÿoÿnÿnÿnÿnÿnÿmÿmÿmÿmÿnÿnÿrÿrÿzÿƒÿ‰ÿÿÿÿŒÿ‹ÿ‹ÿ‹ÿŠÿŠÿ#~$y&~'y%~ z~z{{||{{{  {!"y#$y%~%y&~'y(~)z)~)z*}*z*},z.}.z-}.y-|-z.|.z-|.z/|/{/|.z,|,y-|-y,|,w+|+w+|+u+|*x,}+x,}*y*|+x-}/t4€;q?€DnI‚MjQ…VgY†\d]‡_a`‡b_^„]]UN^L~Bb>{7g3y1l2y2m3y3n3y4n3y3o3y3o2y2p1y1p1y1p/y/p.y.p,z,p-{*r*{)t'{'u&|&w&}%w$~#y&%z%%y%)t+‚/q3…9n;†@jEˆHgN‰QeV‰WaXŠ_]f‰j[m‰n[qˆuYx†zZz…y[x„w^u‚rapkck€gge~bk^{YnVzQrMxFuAw;{7u44v46w57y8~7y8€8y6€6w55w44v4‚4v4‚4v4‚3v2‚1w1‚0w0‚0x02{3};Lwb‚up‚„ˆh‰„‡eƒreY€=j)~%p$~%w%€(u.1x:GzS}[{^{Ya{c€f~o~qqwk€isopru„~p‚†„m€†~k|†|ix…yh}…‡fŒ‡Žf‡‘h”†•l“†ŽmŠ†‡j{†lh[ƒLhA>p>~Bw>~<{9};}X}˜€‘|Š‚‡{ƒ‚€|qƒ4}/„=zXŠ[xI’KwM›KwKTtFDuCžEt@>v:˜_{•’|j‹gx`‹exc‹Z{S‰P}LˆP|O‰MzG‹HxJLtM˜RsMMu[žVuKQsTždo–›lƒ˜lm]•QrE’;w2Ž2w47v6’4v1‘.w,*v-Hte|vŒzz‹e~V…]WQƒSwO„Du:‚:~2€4€9~?ƒ<0ƒ0€7‚:€+ƒ(~+„4}4Š6|4Ž4|43~8Š:E†O€D„=€7„54ˆ2/Š--Š0.ˆ0~3‰8}:‹?~>ŠE>†5€/‚1:~?~@~?8~5E‚URY~W}_~m~j}[€L~N}LZymSx]€e|t€u|lsz€€wt€wtty{v|Uƒcy†O{D€S}[uTzAqHvWrfxgqghnh‚cn[„RoG„9q2‚-u+,x2}7y@|JvS|Zta}eqg~gng€fme€clb€am_€^m[€XmTOkJ‚Dk@ƒ?nBƒGoH‚MnO‚PlP‚PmS‚SmX€]ocipq~vuz~zw{}{{{|{€zzx‚yzyyy}ƒx†€ŠyŒ‹yŠ€‰{ˆ€‡{‡€‡z‡‰y‰‚ŠyŠƒŠyŠƒ‡y„ƒ‚x}ƒywwƒwxuuyu|wzw{vzv~v{v~r{l~g~_|X~NyL|OwTzYv`yetlzssxz€y‡{‹}ŽzŒ{Š~‰|‰}‰{‰}Š{ŠŠ{Š‹}†y{p€dtYRpU~[xcxl~tyw{t|ssp}nom~opo~npnnqnlqk}krk}krn}oqo}ts{z„vyz}ŒzŒ~Œz~|Œ~Œ|#ÿ$ÿ&ÿ'ÿ&ÿ"ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ ÿ!ÿ"ÿ#ÿ$ÿ&ÿ&ÿ'ÿ'ÿ(ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ,ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ/ÿ/ÿ/ÿ-ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ)ÿ)ÿ+ÿ+ÿ)ÿ)ÿ*ÿ*ÿ+ÿ*ÿ*ÿ*ÿ+ÿ,ÿ.ÿ3ÿ6ÿ<ÿ@ÿDÿGÿMÿPÿVÿXÿ\ÿ_ÿ`ÿaÿ`ÿ_ÿZÿRÿKÿEÿ=ÿ5ÿ1ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ/ÿ/ÿ.ÿ.ÿ,ÿ,ÿ+ÿ*ÿ*ÿ)ÿ'ÿ'ÿ'ÿ'ÿ&ÿ%ÿ$ÿ%ÿ$ÿ%ÿ(ÿ*ÿ+ÿ/ÿ4ÿ9ÿ?ÿDÿHÿLÿRÿVÿYÿ\ÿ`ÿdÿeÿiÿpÿtÿvÿwÿxÿyÿzÿyÿyÿxÿtÿpÿnÿiÿgÿdÿ`ÿ[ÿWÿQÿNÿJÿHÿCÿ>ÿ8ÿ4ÿ3ÿ1ÿ1ÿ1ÿ2ÿ4ÿ4ÿ5ÿ5ÿ7ÿ6ÿ6ÿ5ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ1ÿ1ÿ1ÿ/ÿ/ÿ-ÿ/ÿ0ÿ1ÿ5ÿCÿXÿlÿ~ÿˆÿŒÿŠÿ†ÿ|ÿjÿQÿ;ÿ&ÿ%ÿ)ÿ4ÿ5ÿ.ÿ0ÿ<ÿKÿNÿGÿGÿGÿOÿNÿNÿRÿMÿQÿTÿWÿVÿUÿZÿhÿrÿuÿyÿvÿvÿuÿwÿ{ÿ„ÿÿ’ÿ’ÿ“ÿ–ÿ–ÿ—ÿ•ÿÿ‰ÿŠÿŽÿ‹ÿ€ÿrÿ[ÿHÿ>ÿ8ÿ8ÿ9ÿ7ÿ5ÿ9ÿ~ÿ–ÿˆÿ‚ÿÿ€ÿÿQÿ=ÿ?ÿQÿ_ÿPÿMÿNÿLÿIÿJÿFÿCÿDÿBÿ@ÿ>ÿ7ÿUÿšÿ’ÿ‚ÿvÿgÿVÿZÿ`ÿYÿPÿMÿRÿNÿHÿEÿEÿJÿHÿKÿNÿNÿNÿYÿYÿSÿHÿJÿSÿÿ’ÿˆÿqÿ]ÿIÿCÿ4ÿ3ÿ7ÿ8ÿ9ÿ9ÿ5ÿ2ÿ-ÿ*ÿ*ÿ*ÿ0ÿJÿ`ÿ|ÿ•ÿ–ÿÿnÿaÿdÿcÿsÿkÿ\ÿXÿSÿMÿDÿ7ÿ3ÿ8ÿDÿHÿIÿ@ÿ7ÿ+ÿ/ÿ3ÿ5ÿ5ÿ3ÿ2ÿ6ÿGÿLÿIÿKÿFÿ6ÿ7ÿ:ÿ8ÿ6ÿ4ÿ:ÿAÿ=ÿ8ÿ/ÿ+ÿ-ÿ7ÿ>ÿAÿMÿ]ÿAÿ2ÿ?ÿCÿHÿBÿHÿGÿ=ÿGÿMÿQÿRÿUÿ^ÿgÿdÿ]ÿUÿVÿUÿ`ÿeÿgÿIÿWÿoÿrÿpÿmÿoÿ„ÿ~ÿhÿiÿbÿ[ÿ\ÿ[ÿYÿMÿDÿIÿ[ÿ`ÿHÿ;ÿFÿOÿfÿfÿiÿjÿfÿaÿ]ÿSÿJÿ<ÿ2ÿ-ÿ,ÿ/ÿ3ÿ8ÿBÿKÿSÿZÿaÿeÿgÿgÿgÿgÿeÿcÿdÿcÿdÿdÿeÿeÿbÿ_ÿ^ÿYÿUÿXÿXÿ[ÿ`ÿeÿhÿiÿlÿlÿnÿnÿpÿpÿsÿtÿwÿzÿ|ÿ|ÿ{ÿzÿzÿzÿxÿwÿxÿxÿzÿ~ÿÿ„ÿŠÿŒÿ‹ÿŠÿ‰ÿˆÿ‡ÿ‡ÿ‡ÿ‡ÿ‰ÿ‰ÿŠÿŠÿŠÿŠÿ‡ÿ„ÿÿ~ÿ{ÿyÿwÿuÿvÿvÿwÿwÿvÿvÿvÿvÿrÿlÿgÿ_ÿWÿMÿKÿNÿTÿYÿaÿfÿnÿuÿ{ÿ‚ÿˆÿŒÿŽÿÿŒÿŠÿ‰ÿ‰ÿ‰ÿ‰ÿŠÿŠÿŠÿŠÿŠÿ‡ÿÿuÿkÿ^ÿSÿMÿQÿYÿdÿmÿsÿvÿsÿrÿpÿnÿoÿqÿoÿnÿnÿnÿnÿlÿkÿkÿkÿkÿkÿmÿnÿsÿ|ÿ…ÿÿ‘ÿÿŒÿŒÿŒÿÿÿŒÿŒÿ!~#z&~'z&~#y ~y~z~z~{~{{y~z ~ z"#z$%z&~'y'~(z'|(z*|+y*|*x*|,{-|/{-|-{-|-{.{.{.|.z0|0z.|,{,|,{*|+y+|*y)|)x*|*x)})w)}*w*}*x)}+x+}+w+},w.2v59r=‚@nEƒJjQ…Vf[‡]b_‡_``‡a_\ƒU_N}Gb;z5g2y2k2x2m2x2o3x3n2x2o2w1p0w0p0y0o.y.p.y.r-y-t-z+t*z*u({(w(|'v'&s&€'s)-q/ƒ3n8…;kB‡HfMŠQdTŠ[`_Šb[f‰iYkˆpWs‡tXw‡y[{†{]z…y^x„u`r‚oblied~`h[}XlR|LqH{Du>y;x9x5{2x1~1x1€0x00x00x03x33x3€3x3€2w2‚2w2‚2w42w22w22w21w0.x--y-€-{.4~Cy>Ž;x72y0)y(Ž)y**x5LwdŽ‚y”Š“{ˆ…}~w€t~uvrcobƒXlLƒKoL‚Bv<€MNF…>}7‡;{:Š7|54~2Œ7}<†<:„>€?„?;…:€7ƒ5€8DE€K;€.1‚12ƒ>~J†Oi…q‚O„/‚=RNLT~Q}R€_~sZ{L‚RzZgzQ]|R€;~=}\}fyY€OxT`u\|^zZ{Ybz^V{P|Q}I€D|@ƒL|J†@}E€N~Ax<|Ht:z1r^|krk€jph‚bp\ƒTqI…;r1ƒ-v-/z5~ÿEÿIÿNÿUÿXÿ]ÿbÿhÿjÿmÿpÿsÿtÿvÿxÿzÿzÿ{ÿyÿxÿuÿtÿqÿoÿjÿfÿaÿ^ÿXÿRÿOÿIÿCÿ=ÿ:ÿ6ÿ2ÿ3ÿ2ÿ1ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ/ÿ0ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ2ÿ4ÿ3ÿ2ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ.ÿ-ÿ,ÿ,ÿ-ÿ.ÿ1ÿ6ÿEÿZÿpÿ‚ÿÿŽÿ‹ÿ‰ÿ€ÿyÿiÿOÿ:ÿCÿXÿWÿQÿRÿKÿ]ÿmÿpÿbÿeÿhÿ]ÿeÿ`ÿYÿdÿcÿRÿKÿEÿ>ÿDÿDÿEÿFÿGÿDÿIÿ\ÿmÿÿŽÿ•ÿ–ÿ–ÿ‘ÿŒÿƒÿŠÿÿÿ„ÿÿ”ÿ™ÿœÿšÿ•ÿŽÿzÿYÿAÿ7ÿ<ÿGÿLÿhÿÿ‰ÿÿÿÿ}ÿrÿnÿnÿgÿXÿLÿOÿMÿDÿDÿBÿ?ÿAÿ?ÿ<ÿ;ÿbÿŒÿŸÿ™ÿ‰ÿ~ÿsÿiÿ^ÿXÿUÿQÿMÿHÿDÿFÿIÿJÿJÿHÿGÿFÿIÿLÿEÿFÿEÿGÿMÿMÿ~ÿˆÿÿyÿmÿFÿ<ÿ7ÿBÿGÿDÿ@ÿ8ÿ0ÿ(ÿ#ÿ#ÿ%ÿ'ÿ)ÿ*ÿ8ÿPÿ`ÿxÿŒÿ“ÿÿ„ÿuÿjÿuÿiÿqÿxÿdÿiÿkÿYÿJÿWÿ^ÿQÿCÿ<ÿ9ÿ7ÿ8ÿ4ÿ0ÿ/ÿ0ÿ2ÿ5ÿ6ÿ6ÿ8ÿ:ÿ8ÿ9ÿGÿOÿGÿ9ÿCÿCÿ7ÿ*ÿ*ÿ+ÿ1ÿNÿhÿkÿmÿdÿ_ÿHÿBÿGÿCÿTÿPÿOÿSÿ\ÿmÿcÿLÿ]ÿfÿZÿMÿGÿ9ÿCÿNÿjÿ[ÿCÿcÿiÿeÿXÿIÿIÿCÿ?ÿ?ÿEÿLÿFÿ7ÿ7ÿ?ÿMÿYÿRÿMÿGÿKÿQÿ[ÿ4ÿOÿgÿkÿiÿjÿhÿbÿ\ÿTÿHÿ>ÿ5ÿ1ÿ1ÿ3ÿ9ÿAÿHÿPÿXÿ_ÿcÿfÿiÿjÿlÿnÿpÿqÿsÿvÿzÿ}ÿ€ÿƒÿ†ÿ‡ÿ…ÿ†ÿ†ÿ†ÿ‡ÿ‰ÿŠÿŠÿ‹ÿ‹ÿŠÿŠÿŠÿŠÿŠÿŠÿ‹ÿ‹ÿŠÿŠÿŠÿŠÿŠÿ‡ÿ†ÿÿÿÿ{ÿzÿ|ÿ€ÿ‚ÿ‡ÿ‹ÿ‹ÿ‹ÿŠÿŠÿˆÿ‡ÿ‡ÿˆÿˆÿˆÿ‰ÿŠÿŠÿ‹ÿ‰ÿ…ÿ‚ÿÿ|ÿzÿxÿwÿwÿwÿwÿxÿxÿwÿwÿuÿtÿqÿkÿeÿ^ÿSÿKÿIÿNÿSÿ\ÿdÿkÿrÿyÿ€ÿ‡ÿŽÿÿÿÿŒÿŠÿ‰ÿ‰ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‡ÿ„ÿ|ÿrÿcÿQÿIÿGÿPÿZÿeÿoÿtÿuÿtÿrÿpÿnÿnÿnÿnÿnÿnÿnÿmÿkÿiÿgÿbÿdÿfÿhÿnÿvÿÿ‹ÿÿ‘ÿÿ‹ÿ‹ÿ‹ÿÿÿÿ‹ÿ~z"~$z$~"{~{~|~|~|~||z~ {!~!{"}#z$}&z'~'z'~({)|*{)|)z+|+y-|-{-|-{-|-{-|-{.{.{.|.{.|-{-|,z,|,z*|+z+|*z*|*z)|)y(}(z)})z)}*y*}*y*}*y)})y*}*w+}+u/}/t48q<‚BnG„NjU‡[daˆd`d‡d^]U_M}Ec9z3g3z1l1z2m2z2n2z0o0y0o1y1o/y/o.y.p.y.q-y-r-z*s+z*t)|(t)~*s,‚/l5ƒ8i<…BdG‡MaPˆT^[ˆ_ZdŠhYm‰pWrˆtWw†yWx…xYx„wZvƒt^rƒpcmkhg€bj^}YmT|OpJ{Fu>z;z5x1{1y0}0y0.y.€/y//y//y//y/.y/1y11y1€1y1€2x2€1x1€1x32x11x1/x..x.-y,+y+€,{./}2|<€Pugƒ|pˆƒŽi‹„Œf„ƒxkr‚wow|s~}uuf|Qz?x?Puc„usy‰\rO‹WsX‹_un‹wvtŠavI‰CxC‹CzD‹F|CˆA|C…D|G‚K}\}q}~z~}}s…Žj‘‚–f•‚Žg‹ƒ“jšƒŸkœ„•h–†•hŒ†yic‡`jn…yk…ƒ–r€ƒz€~|„ŒtŠ‚‚uq}XƒLyM”IuG›CvAž?v=žEtLœVrš—qŸ“rnxŽmkb]n]ZsRIwHG{HŽHzI’HwF—EtFœHtEœsB•FwDBx=Œ3z&Š!{ ˆ!{$‡'z*‰+y>ŠQv`ˆqxƒ†{ƒ„{t€nzi€u~~ztvwpe|b}c†Z{\ŠGz;Žÿ8ÿ-ÿ"ÿ!ÿ#ÿ'ÿ*ÿ+ÿ,ÿ/ÿ:ÿMÿ\ÿjÿwÿ„ÿ‡ÿ‚ÿxÿwÿ}ÿ~ÿ‚ÿzÿuÿjÿ^ÿOÿUÿHÿ<ÿDÿHÿOÿXÿTÿ=ÿ:ÿ@ÿ<ÿ6ÿ7ÿBÿ4ÿ2ÿ>ÿ?ÿCÿFÿZÿUÿ<ÿ>ÿ3ÿ1ÿ5ÿ4ÿ7ÿUÿiÿdÿ\ÿVÿ\ÿ[ÿQÿ@ÿ;ÿCÿaÿjÿhÿ^ÿ\ÿZÿUÿFÿDÿLÿ?ÿAÿMÿXÿHÿVÿhÿ^ÿ\ÿNÿEÿKÿ?ÿGÿ;ÿ?ÿ6ÿ?ÿCÿHÿ\ÿeÿhÿÿxÿoÿNÿLÿ=ÿAÿeÿfÿfÿiÿjÿkÿkÿgÿcÿ^ÿVÿLÿAÿ:ÿ6ÿ7ÿ9ÿ?ÿFÿOÿWÿ`ÿhÿoÿtÿwÿ|ÿÿ‚ÿ…ÿˆÿ‹ÿÿÿ’ÿ’ÿ’ÿÿŽÿŒÿÿ‹ÿ‹ÿŠÿ‹ÿ‹ÿ‹ÿŠÿŠÿ‹ÿ‹ÿ‹ÿ‹ÿŠÿŒÿŒÿŒÿÿÿÿÿÿŽÿÿŽÿŒÿ‹ÿ‰ÿˆÿ†ÿˆÿ‰ÿŒÿÿŒÿ‹ÿŠÿˆÿ‡ÿ‡ÿ‡ÿ‡ÿ‡ÿˆÿ‰ÿŠÿŠÿŠÿ‰ÿ…ÿ‚ÿÿ|ÿ{ÿyÿxÿxÿwÿwÿwÿwÿwÿwÿuÿtÿpÿjÿaÿYÿPÿHÿGÿLÿSÿ\ÿeÿlÿtÿ{ÿƒÿˆÿÿÿŽÿŽÿŒÿŠÿŠÿ‹ÿŠÿŠÿ‹ÿ‹ÿ‹ÿŠÿ‡ÿÿxÿiÿVÿIÿEÿJÿTÿ^ÿjÿrÿtÿuÿsÿrÿpÿnÿnÿnÿnÿnÿnÿoÿmÿkÿhÿeÿbÿbÿcÿhÿqÿyÿ…ÿÿÿÿÿÿŒÿŒÿŽÿŽÿÿŒÿ~y~y~{~{~{~{~{~{zz~!{!~!{"~#{$~%{'~'z(~)z(|)|*|*|,|,{,|-{.|.z,|.z.{.z.{.z.{.|.{-|,|+{*|*{*|+|+|*{*|*{)|){)})z(}(z(}(z(}(z(~){(~({)}){*}'{)})x+}+v-€2t9‚?qG†NlUˆ_fd‡gaj…d_\‚R_K~Bc:|5i5{4l3{1m2z0n0z0o1z0p.y.o/y/q.y-s-z-s.|-u,},t.1o6ƒ8j?†FdJ‡O^Tˆ[Z_ˆdXi‡lWm†pVs‡uVw†vXv„t[r‚o^mi_heba€`h]€YnU|PrL{FuBz;y7z1{0{.~-z-€-z-‚.z-‚-z-.z--y-‚-y-‚.z-‚.z.‚/z/.z/0z00z0/y/0y01y21y12y2/y.+{+,{+*z*){)){,|0:yNdrx‚ƒo„ƒzr|‹u‘Š}ƒ…Š†€ƒ‚|‚}}zzwl‚Xo]‹nlb‘VoOet„‹‚uwˆ|qn‹`sOŒEyAŒFyF‹E{H‹E{FHzJŽL}JA~?‹Sn‚‡‚•r“„iƒ“o˜ƒmš„ŽaŠƒŒ\ƒˆaq‚ŠhŽ‚”l‚”qŽ€„y{~yx{‘qƒqm…‚]{HzJKwEšAw?;u::uJœ}p—š£k§œle”knƒ’~lu‘nmlkpjiwiŽ}•Ž–}’Œxƒ”xu\™Ft>š7t1™3sK˜eok—rinšehaš]lM˜HsD•DwA‘?y?Œ={2‰(|%†'|)†,{,ˆ,{,‰-x:‰Lw]ˆgwp†vw{††t‘‰•x„Š‡‹u‚o‹aQ‹b€VN|WŽUzXŽU{XŠ>~5‚EC{E‚BxD1z6A}:€F~O€K~FC0.;€9,~4„J{Z…S|N…P|\…U}Fƒ={FKxT„LuQ†IvL…QxUƒGzEƒG{J„M}VƒW{H„IxX†ZxM…@xE€O{I~G|8~?~C}8}9|DXynyy}€yys‚Ry4€=zQ~hzg€gwgitj€lthbs^„WtN†Ex=†8};ƒ<~C€LyU_sg€qoy€}m€†n‰‚n‘„’k’„“k•‚”k’ƒ’l‚Žn‹Šp‡€‡r‡‡r‡€ˆpŠ€ŠoŠŠn‰€‰l‰ŠkŠ‰iˆ€‹jŒ€Œj‹iŽjƒŽo‚rƒwŽ’|}Ž€Œ{‰‚ˆz‡†y†‚‡y‡ƒ‰yŠ„‹w‹„Šwˆ„…x‚„x|„{wz„wxw‚wywwzw€wywuzso|g€`Y~O~GzF|L{Tx]{fwm{vw~z„w‰~ŒyŽ{€‹{Š€‰{‡€‰z‰‹z‹‹|Š€†}€w€hxT‚HsE~LwWz`€kvrƒuxu{r|rqp~opmmqnnrnosmjsg€dta~awc{h|qv}€‡uŽ’x|}yŒ~Œ{Ž~Ž{~Œ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ÿ ÿ!ÿ!ÿ$ÿ#ÿ$ÿ%ÿ'ÿ'ÿ(ÿ)ÿ(ÿ)ÿ*ÿ+ÿ,ÿ,ÿ,ÿ-ÿ.ÿ.ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ,ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ(ÿ)ÿ)ÿ+ÿ+ÿ*ÿ+ÿ0ÿ6ÿ>ÿEÿPÿYÿ_ÿgÿkÿiÿcÿ[ÿSÿMÿEÿAÿ=ÿ8ÿ7ÿ6ÿ5ÿ2ÿ1ÿ0ÿ1ÿ0ÿ/ÿ.ÿ/ÿ/ÿ-ÿ-ÿ-ÿ-ÿ+ÿ-ÿ/ÿ1ÿ3ÿ7ÿ<ÿCÿIÿOÿUÿ\ÿaÿeÿjÿmÿpÿsÿsÿsÿsÿtÿrÿpÿoÿmÿjÿeÿbÿ^ÿZÿWÿSÿRÿNÿKÿEÿAÿ;ÿ6ÿ4ÿ1ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ-ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ+ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ1ÿ0ÿ0ÿ0ÿ0ÿ/ÿ.ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ(ÿ)ÿ*ÿ2ÿ@ÿTÿeÿoÿsÿÿŒÿÿÿ…ÿ‚ÿyÿvÿvÿ…ÿ…ÿ~ÿÿ~ÿ\ÿRÿkÿsÿnÿfÿ]ÿoÿ‡ÿÿ|ÿuÿmÿgÿ[ÿWÿPÿEÿEÿJÿRÿKÿEÿDÿCÿEÿIÿKÿGÿGÿJÿ^ÿÿ–ÿ‘ÿ“ÿ”ÿ˜ÿÿ™ÿ‰ÿˆÿŒÿ‹ÿ†ÿrÿwÿ†ÿ‰ÿ‚ÿÿÿ€ÿzÿxÿyÿŽÿ“ÿ”ÿŽÿcÿGÿIÿIÿEÿBÿCÿOÿGÿ8ÿQÿˆÿÿ¦ÿ©ÿ£ÿ^ÿ,ÿXÿ{ÿÿyÿxÿvÿzÿzÿ„ÿ•ÿŸÿ¡ÿ™ÿ“ÿˆÿyÿdÿMÿ>ÿ6ÿ/ÿ8ÿSÿeÿlÿsÿnÿgÿaÿ^ÿUÿKÿAÿ?ÿAÿ?ÿ@ÿ>ÿ8ÿ1ÿ*ÿ.ÿ/ÿ/ÿ/ÿ-ÿ+ÿ*ÿ+ÿ:ÿPÿ[ÿgÿjÿlÿÿ“ÿ¡ÿ˜ÿ–ÿ˜ÿÿ†ÿ{ÿkÿcÿeÿeÿbÿXÿTÿDÿVÿMÿ<ÿ>ÿBÿ?ÿIÿAÿ0ÿ=ÿ;ÿAÿFÿ?ÿGÿNÿ:ÿ.ÿ?ÿVÿDÿ-ÿ5ÿIÿTÿ[ÿJÿAÿJÿ<ÿ<ÿLÿLÿHÿPÿ5ÿ:ÿ<ÿ@ÿFÿWÿVÿFÿEÿEÿOÿNÿ\ÿfÿ_ÿMÿ8ÿ5ÿGÿYÿKÿSÿLÿ6ÿ7ÿRÿYÿ?ÿDÿJÿfÿpÿ_ÿjÿrÿOÿCÿWÿcÿfÿgÿhÿhÿjÿjÿlÿhÿdÿ_ÿXÿOÿFÿ>ÿ<ÿ>ÿBÿMÿVÿ_ÿiÿsÿ}ÿ‚ÿ‡ÿŒÿŽÿ‘ÿ“ÿ”ÿ”ÿ”ÿ”ÿ”ÿ“ÿÿÿ‹ÿˆÿ„ÿÿ~ÿ{ÿyÿyÿ{ÿ~ÿ€ÿ‚ÿÿƒÿ†ÿ‡ÿ†ÿ‡ÿ‡ÿ†ÿ‡ÿ‡ÿ‡ÿ‡ÿ‰ÿ‰ÿŒÿŒÿÿÿŽÿÿÿ’ÿ“ÿ“ÿ‘ÿÿŒÿŠÿˆÿ‡ÿ†ÿ†ÿ‡ÿ‡ÿ‰ÿŠÿ‹ÿ‹ÿŠÿˆÿ…ÿ‚ÿÿ|ÿ{ÿzÿxÿwÿwÿwÿwÿwÿwÿwÿuÿsÿoÿgÿ_ÿUÿJÿFÿEÿKÿRÿ[ÿcÿlÿwÿÿ…ÿŠÿŒÿÿŽÿÿ‹ÿŠÿ‰ÿŠÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿŠÿ†ÿ€ÿuÿfÿSÿIÿJÿRÿ[ÿeÿmÿrÿsÿsÿqÿpÿpÿoÿmÿmÿlÿlÿoÿnÿmÿjÿgÿdÿ`ÿ`ÿcÿiÿsÿ~ÿˆÿÿÿÿÿŒÿŒÿÿŽÿŽÿÿŒÿ~|~|~{~{~{~{~{~{zz!~!z"~"z$~$z%~&z&~'|)~*|(|*|*|,},|,|,|-|,|-|.|.|.{.|.{.|.{.|,{+|+|+{+|+{*}*{)}){)})|)})|)}){'}'{(}({'}'{'~'{'~'{)~){(~){)~(|)~)z)€)x,1v9„@qI‡Rk\ˆddi‡kai…b_\ƒW^QƒK`G‚Cc>=e;9f5}3g3}1h0|0l.|.o.|-p,}-p/~/o4€7l:ƒ?hG†MbTˆZ]`‡c[g‡m[p†r\r…t^s„q^q‚l^jhce€ag\~XkT}OoL|HrE|Au=|9y5|1{0{.~-{.+|+,|+‚+|+‚+|+‚+{+‚+{+‚,{,‚-{-‚,{,‚,z,‚-z-‚-{--{-.z..z..y..y..y//y./{/.{.,{+*{*){))|))(*€+{5ƒFwZƒfw|€~~†ƒƒ~†qb‡p€h…i€o„WyX„erX‰\lgŽgid“_n_qrƒ‹’r„ˆtnlŠgpbŠZuM‹DxF‹J|PŠJ|EŠE|CD~GIK’LL’O‚x‡˜„’w‘ƒ’t—žt—ƒŠjˆƒ`ŽƒŽ]…‚u`}‚ˆiy‚‹sŒ€|{~w}v‰uŽnqtD}LŠKyI˜IwCœ?w>›7uZ™nŸ—¥mª¥iŠ”+o4˜Mpr—n‚—}ny”{r‹’›v¡ s”’Žm†–tm]™Hp;™4t/–9uM“Ysb’goh•cm[•VpT”OtA‘>w?Ž?yAŠB{>ˆ8|1‡3|3‡1{0‡/{-ˆ+y*ˆ+x5ˆLw_ˆhvd‡ks‹˜t¢Œ¤x¤Œ¤¢‹˜ƒ~Œ`ƒVŽhqŽfUŠO}a…VEƒ<‚HWƒ>y2‚z/€&}4GWEƒ2};…G|U†c{VˆC{EˆD}5‡;}F†[|S†<}7„3z7ƒ:yE‚DzHƒB{1‚:{AƒFxa…b{8„<~Y€_X{L~[yPKyS€nzh`yc`{jn}N€V|_]z`fwegxe‚fvijwk€kuh‚euaƒXuQ…HxB‡C|H„NzYbskup‡o‹l‘j’”j””k’‚“k’‚Žn‹‚‡r€‚xtr‚lvfbw`€`yb€dyg€kyoryx}{v||t~~€s„~„s…~…s†‡oˆ€‰kŠ€Šj‹€Žl‘‘n””t“’z~‹~‰|ˆ‡z‡‚†y‡ƒ‰yŠ„‹y‹…Šy‡……x‚…w|…{wz„xxwƒxyx‚wzwwyw‚uzr‚m|f\P|GD{D|J|PxZ~cwm}wv‡wŒƒy‚ŽzŒ‹{Š€Š{‹€‹z‹ŒzŒŒ|‹†}€}u€ftV‚OoP€Wv_|g~oxs€t{ryp|ntp~osnnrnnsnntm€jug€eua~_xdyj}tu€‹v‘~y|Ž}Ž{Ž~Ž|Ž~|~Œ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ÿ!ÿ"ÿ"ÿ$ÿ%ÿ%ÿ&ÿ&ÿ'ÿ)ÿ*ÿ+ÿ(ÿ*ÿ+ÿ,ÿ,ÿ,ÿ-ÿ,ÿ,ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ'ÿ'ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ*ÿ.ÿ3ÿ:ÿBÿLÿTÿ^ÿdÿhÿjÿhÿcÿ^ÿXÿVÿRÿNÿKÿFÿCÿ?ÿ=ÿ:ÿ8ÿ6ÿ3ÿ2ÿ/ÿ.ÿ.ÿ.ÿ/ÿ0ÿ3ÿ6ÿ9ÿ>ÿCÿHÿPÿVÿ]ÿaÿgÿjÿmÿpÿtÿrÿpÿnÿlÿjÿfÿbÿ^ÿ[ÿUÿSÿNÿKÿFÿAÿ=ÿ9ÿ3ÿ1ÿ1ÿ-ÿ,ÿ,ÿ-ÿ-ÿ,ÿ+ÿ+ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ+ÿ5ÿMÿpÿzÿyÿuÿqÿmÿiÿiÿ^ÿ\ÿvÿsÿoÿ}ÿwÿtÿfÿZÿ]ÿbÿTÿKÿTÿbÿuÿÿŒÿ†ÿuÿiÿmÿmÿgÿVÿDÿAÿIÿHÿJÿGÿDÿEÿEÿFÿFÿHÿIÿLÿMÿuÿ–ÿŽÿ‹ÿÿ—ÿžÿ•ÿ‹ÿŒÿ‹ÿŽÿÿŽÿ‹ÿŒÿ’ÿ‡ÿŽÿ‰ÿÿzÿvÿvÿ„ÿ‹ÿ‹ÿ‹ÿzÿKÿGÿHÿFÿFÿEÿAÿ>ÿ5ÿcÿ“ÿ¡ÿ§ÿ§ÿ¥ÿšÿWÿ2ÿ=ÿ?ÿFÿIÿ>ÿAÿjÿ‡ÿšÿ™ÿ’ÿ…ÿ€ÿuÿfÿZÿSÿ;ÿ1ÿ1ÿ>ÿLÿXÿZÿ]ÿ^ÿ`ÿXÿTÿPÿMÿCÿ=ÿ>ÿBÿDÿGÿBÿ?ÿ9ÿ6ÿ6ÿ4ÿ3ÿ1ÿ.ÿ-ÿ)ÿ*ÿ+ÿ5ÿRÿbÿ_ÿ]ÿnÿ€ÿŠÿ“ÿÿŸÿŸÿ›ÿ‹ÿxÿiÿxÿsÿhÿ`ÿ[ÿWÿZÿKÿGÿVÿ`ÿ=ÿ9ÿ4ÿ;ÿAÿGÿFÿHÿ8ÿ9ÿ>ÿHÿ<ÿEÿ>ÿ9ÿ<ÿHÿQÿYÿNÿ<ÿ;ÿ_ÿCÿ=ÿFÿZÿ^ÿEÿ1ÿ.ÿ6ÿ6ÿ<ÿHÿEÿ@ÿ=ÿEÿHÿHÿTÿ^ÿeÿnÿkÿgÿeÿiÿeÿiÿmÿlÿfÿPÿUÿiÿ`ÿfÿlÿ`ÿeÿkÿiÿiÿeÿcÿfÿgÿgÿgÿkÿkÿkÿjÿfÿaÿZÿSÿLÿKÿNÿRÿZÿdÿoÿyÿÿ‰ÿŒÿÿŽÿÿÿ‘ÿ“ÿ’ÿ’ÿÿŽÿŠÿƒÿ{ÿrÿjÿ`ÿXÿRÿJÿFÿEÿEÿGÿKÿLÿRÿ[ÿ_ÿgÿlÿnÿsÿvÿzÿ}ÿ}ÿ~ÿ~ÿ€ÿƒÿ„ÿ…ÿ‡ÿ‡ÿˆÿ‹ÿŒÿŽÿÿ’ÿÿÿÿŒÿŠÿ‰ÿ‡ÿ‡ÿ†ÿ‡ÿ‰ÿŠÿ‹ÿ‹ÿŠÿ‡ÿ…ÿ‚ÿÿ|ÿ{ÿzÿxÿwÿxÿxÿwÿwÿwÿwÿuÿrÿnÿeÿ[ÿOÿEÿAÿAÿGÿQÿ[ÿeÿoÿyÿÿ‡ÿŒÿŽÿÿÿŒÿ‹ÿŠÿŠÿ‹ÿ‹ÿ‹ÿŒÿŒÿŒÿ‹ÿ†ÿ€ÿwÿhÿ\ÿRÿTÿZÿcÿlÿsÿtÿtÿrÿqÿpÿpÿoÿnÿnÿnÿnÿnÿnÿmÿjÿgÿeÿbÿaÿdÿlÿvÿƒÿÿ‘ÿÿÿŽÿŽÿŽÿŽÿŽÿÿÿŒÿ~{~{}{}{||||~{~ {!~!{"~${%~&z&~'z&}'{(}){*|*|+|+|-|.|-|-|-|-{,|-}.{.}.{.}.{.|.{-|,|,}+|+}+}(|*}*|)}(|(}(|(}({(}({'~'|(~(|'~'|&~&|&&z((z((|&&|'){**z/4t;‚DoM†Wk\ˆdgh‡hdg†d`d‡`_[‡Y^W†R^Q†M_IƒE`C‚A`:6d5~2g2~2i3€7h:ƒ>eD„HcN†T_Y‡_]e†hZk…m[n„o]nƒm`j€fba~^gX|UiP}LmJ{Er>{;v7{3y0{/{.|-~,|,+}+,}-+|+‚*}*‚+|+‚+|+‚+|+‚+|+‚+{++{++{+‚+{+‚+{+‚+{+‚,{,,{,,{,€,{,€-z-‚-z-‚-z-‚-z-‚,{,,{,,{+*{*)|)€)~)(€(~+‚0za„}|ƒo€ei‚v€o‚di†^f‡{ƒz‡y‚m†]|f‡`s^Œ|mucjd’llt|m‹ŠŒr“†vŒ…u…†yxuˆd{U‰K}LŠL}GD}EDF‘FJ’MM’J‚i‹”ƒ|ŒŒw“›w‰[{r€…s‰€‡lŒ€f‘”g‘u†}{y|utŠx‘ƒ‘p‚‹t\D‰IzH•HxC™Av<›:rr›šm£–¨i©¦gš”tl0š‹7~g‡Z~3‚-}0>yNAx6€<{JP{R€L|c‚x|wrpzn€kyjiyk€i{i€`zV€h{s€`}Zq~q~m}ih{fexf‚fyg‚hxjmwmlvj‚isdƒat\„YsY„\tc„jtu‚~q†€Šo€k€l€l€oŠq‰€†o~‚tpiƒ`rUƒKvA„>z;„<};„;~:„<>‚B~J€O}W~a{g~oys}vyx|zyz{{{}{€z€}€tƒ~…n‡~ˆiˆ€‰hkq{Œˆ‡|‡‚ˆy‰ƒŠy‹ƒ‹xŠ†‹xˆ††v†~v}‡{wy„wzwƒxyxƒwyw‚w{vƒv|q‚k}d‚Z€Q}F€A{B|H|Rx\~hwq€yv‚‰v…xŽ‚Œz‹€Šz‰€Šz‹‹z‹€ŒzŒ‹{Š€‡}€zv€jq^‚YnZ_tgn}u{v}t|ryp}pvp~nunntnntn~nvmjvf~dvb|azbyn~{v†€w’~{|ŽŽ{|Ž}|Œ}‹}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ!ÿ"ÿ"ÿ$ÿ%ÿ&ÿ&ÿ'ÿ&ÿ'ÿ(ÿ)ÿ*ÿ*ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ-ÿ-ÿ,ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ(ÿ(ÿ(ÿ(ÿ&ÿ&ÿ&ÿ(ÿ*ÿ*ÿ+ÿ/ÿ3ÿ<ÿGÿNÿVÿ]ÿbÿdÿfÿeÿdÿcÿcÿ`ÿ^ÿYÿWÿSÿSÿPÿMÿJÿEÿAÿ;ÿ9ÿ8ÿ8ÿ=ÿ=ÿAÿFÿKÿQÿWÿ]ÿ`ÿbÿhÿjÿkÿiÿhÿhÿeÿaÿ\ÿYÿTÿPÿJÿFÿ>ÿ:ÿ7ÿ3ÿ1ÿ/ÿ/ÿ-ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ-ÿ,ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ)ÿ)ÿ)ÿ)ÿ*ÿ-ÿ3ÿGÿ€ÿzÿqÿzÿwÿtÿwÿiÿiÿkÿgÿjÿuÿ`ÿMÿKÿ[ÿ\ÿeÿgÿeÿbÿdÿ_ÿgÿ{ÿ}ÿ‡ÿ€ÿ“ÿ›ÿ£ÿ£ÿÿÿŠÿ†ÿxÿUÿCÿHÿHÿEÿGÿHÿHÿIÿHÿIÿIÿGÿ`ÿ•ÿÿŠÿŒÿÿ™ÿˆÿ/ÿ9ÿOÿcÿqÿÿˆÿŒÿŽÿŒÿÿ„ÿyÿwÿuÿwÿ‘ÿšÿšÿ˜ÿ•ÿhÿ@ÿGÿFÿEÿAÿ@ÿ=ÿEÿyÿ™ÿ¢ÿ¥ÿªÿ¦ÿÿÿ1ÿ9ÿ;ÿ;ÿ;ÿ9ÿ4ÿ9ÿfÿyÿ{ÿzÿvÿqÿgÿ_ÿXÿPÿLÿGÿGÿHÿIÿJÿNÿPÿNÿPÿRÿNÿJÿIÿFÿ=ÿ4ÿ@ÿNÿNÿKÿEÿCÿAÿ?ÿ:ÿ7ÿ6ÿ4ÿ2ÿ2ÿ4ÿ7ÿ:ÿ7ÿCÿWÿ]ÿ_ÿdÿlÿpÿqÿsÿuÿsÿvÿwÿrÿjÿhÿjÿfÿVÿIÿMÿUÿZÿQÿVÿXÿNÿ1ÿ2ÿ,ÿ9ÿEÿ?ÿ<ÿIÿJÿ2ÿ9ÿ<ÿ8ÿ8ÿ1ÿ/ÿ9ÿKÿDÿ0ÿ0ÿ7ÿ9ÿ4ÿ<ÿ9ÿ=ÿQÿ+ÿ,ÿ1ÿ6ÿ9ÿ2ÿHÿeÿmÿRÿPÿJÿqÿzÿvÿrÿoÿnÿkÿkÿjÿjÿkÿnÿmÿqÿqÿuÿ]ÿJÿmÿrÿnÿiÿgÿfÿfÿfÿfÿgÿhÿiÿkÿkÿlÿmÿmÿlÿjÿiÿjÿkÿpÿvÿ|ÿ„ÿŠÿÿŽÿŒÿŒÿŒÿ‹ÿÿÿ‹ÿ‰ÿ†ÿ€ÿ}ÿvÿmÿeÿ]ÿVÿOÿGÿAÿ>ÿ=ÿ;ÿ<ÿ<ÿ<ÿ>ÿ?ÿBÿFÿMÿVÿ]ÿfÿmÿsÿvÿwÿwÿvÿwÿyÿ|ÿ|ÿ~ÿ€ÿÿ‚ÿ…ÿ†ÿ†ÿ†ÿ‹ÿŒÿŒÿÿÿÿ‹ÿˆÿˆÿˆÿˆÿŠÿ‹ÿ‹ÿŠÿŠÿ‡ÿ†ÿÿ~ÿ}ÿ{ÿyÿwÿxÿxÿxÿwÿwÿvÿuÿtÿnÿjÿcÿ[ÿPÿEÿ@ÿCÿHÿQÿ\ÿhÿqÿ{ÿƒÿ‰ÿÿÿŽÿŒÿ‹ÿŠÿ‰ÿŠÿ‹ÿ‹ÿ‹ÿŒÿŒÿ‹ÿŠÿ‡ÿ€ÿvÿjÿ_ÿ[ÿ]ÿdÿkÿqÿuÿwÿuÿpÿpÿpÿnÿnÿnÿnÿnÿnÿnÿnÿmÿjÿfÿdÿbÿaÿeÿpÿ~ÿˆÿ‘ÿ’ÿÿŽÿŽÿŽÿÿÿŽÿÿÿŒÿ~{~{||{{||{!{#~#{$~${$~&z%~&z(}'{(}){+|+|,|,|,|,|,|-|.|.{.|.}.{.}.{.}-{-}-{,},|+}*|*}*}*|)})|)})|)})|(~({(~({'~'|(~(|(~(|'~&|((|''|((|''|''|(*{)+y0€5t=‚EpL„RlX†^ha†ced†c`eˆd^aˆa^^ˆ]\[‡[\X…T\QƒL^H‚EaEƒCbD„I`M…P_V†[__†b^d†g_i„h_g‚dba^f[SjO|JoEz@t:z6x1|-|-|,|,}+~+}+€+}+€,},€,},‚,},,},,|,‚+}+‚*|*‚*|*‚+|+‚*|*‚*|*‚*|*‚*|*‚*|*‚*|*‚*|*‚+|+,|,,|,‚-|-‚,{,‚-{-‚-{-‚,{,‚+|+*|*+|+,|,)|+€*~*.7}Qƒd~x…d„f…„‚‡…ƒ€„p…pf‡f€i‡b~O†M|L†NzM†Ztg‰ml_ŽThOLi^Žhkp‰jr€„‰|‘¥€¨¡~‚œ~’ƒxUˆ@IŽF€H’IG•IJ–H€H•H€TŒŽŠ‹{‹€”|Œ~@ƒ;~;…<}I„g€}}‡vŽ‹z{|uv|wx”xž…nžƒšnz€BA|FJyC—>v>šVo~›˜hŸ˜£d¦“¦ež•}l.š9r=šy;‚Cu26s<€?u<€9y9€E~C€=9€:};€By<‡?A†C€I„R€Y‚bhpt~wv}vv|w‚yzz‚zy{‚|{~~{ƒw„|„n„~ˆh‰€‹h‹‹pŒ€‹z‹~‰‰z‰ƒ‹vŠ„ŠwŠ†‰wˆˆ…v‡~v|‡zvx…xxx„xxx…vyvƒwzv‚s|o‚j}c€X€N~E€?~B|HRx]€kut}vƒ„ŠvŽ„xŽ‚zŒ€Šz‰ŠzŠ‹zŒ€‹z‹‚Š{‰€†}zw€ho_‚_kbjrq€v|w|w|t}rwq}pun~nsnounnuo~ovmjvf~eva|czfyr~€v‰€‘x~}Ž|}{}Ž|Ž}}Ž}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ!ÿ"ÿ#ÿ$ÿ$ÿ$ÿ&ÿ'ÿ(ÿ(ÿ'ÿ(ÿ)ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ'ÿ*ÿ,ÿ4ÿ;ÿ@ÿGÿNÿRÿVÿYÿ\ÿ^ÿ^ÿ_ÿaÿaÿ`ÿaÿ_ÿ_ÿ^ÿ\ÿZÿVÿSÿQÿPÿPÿPÿTÿWÿZÿ^ÿ_ÿaÿcÿgÿfÿfÿbÿ^ÿ[ÿWÿSÿLÿGÿAÿ;ÿ5ÿ0ÿ-ÿ+ÿ+ÿ*ÿ*ÿ)ÿ+ÿ*ÿ+ÿ,ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ)ÿ+ÿ,ÿ+ÿ0ÿNÿsÿyÿsÿlÿyÿ…ÿˆÿŠÿ„ÿ}ÿ|ÿvÿsÿyÿyÿdÿUÿPÿ[ÿ[ÿKÿ`ÿgÿdÿbÿ]ÿ[ÿVÿ`ÿcÿdÿ~ÿœÿžÿ¯ÿ³ÿ­ÿ«ÿ¤ÿŸÿ—ÿ‚ÿGÿ@ÿFÿIÿLÿMÿMÿLÿIÿDÿBÿGÿÿÿ‰ÿ†ÿ‹ÿ‘ÿŽÿDÿ4ÿ>ÿ>ÿ?ÿEÿ\ÿyÿ†ÿ†ÿ„ÿzÿtÿwÿyÿ{ÿ—ÿœÿ›ÿœÿ™ÿŠÿNÿ?ÿEÿGÿDÿ@ÿ?ÿhÿ“ÿ›ÿ›ÿÿŸÿ£ÿžÿ}ÿ.ÿ6ÿ8ÿ;ÿ:ÿ8ÿ6ÿ1ÿ7ÿSÿ_ÿ_ÿ\ÿ]ÿ[ÿTÿMÿIÿIÿHÿFÿJÿNÿNÿNÿMÿMÿMÿLÿKÿLÿLÿLÿFÿ@ÿJÿRÿSÿVÿGÿDÿHÿIÿFÿDÿ@ÿ<ÿ=ÿBÿFÿBÿ@ÿ>ÿ>ÿDÿTÿXÿZÿZÿ[ÿ_ÿ_ÿeÿhÿnÿpÿnÿeÿ[ÿTÿLÿBÿ;ÿ<ÿ<ÿGÿSÿPÿVÿ]ÿ]ÿZÿ8ÿ-ÿ4ÿ8ÿ5ÿ7ÿ;ÿ7ÿBÿCÿ;ÿ>ÿBÿ0ÿ1ÿ;ÿCÿSÿLÿ8ÿ@ÿ7ÿ6ÿ9ÿ<ÿOÿYÿIÿDÿ:ÿ8ÿXÿuÿtÿ\ÿQÿLÿpÿ{ÿxÿsÿoÿmÿkÿiÿiÿiÿhÿiÿlÿnÿqÿpÿnÿpÿuÿtÿoÿkÿiÿgÿfÿfÿgÿgÿiÿjÿkÿpÿtÿyÿ}ÿÿÿ‚ÿ†ÿˆÿ‹ÿÿŽÿÿÿÿ‹ÿŠÿ‡ÿƒÿ}ÿzÿuÿrÿnÿlÿiÿfÿcÿ`ÿ^ÿ]ÿWÿQÿKÿCÿ@ÿ<ÿ;ÿ;ÿ=ÿ>ÿ?ÿ@ÿ@ÿEÿKÿVÿ^ÿdÿlÿsÿyÿxÿyÿyÿwÿxÿyÿyÿyÿyÿzÿ{ÿ}ÿ€ÿ‚ÿ‚ÿƒÿ„ÿƒÿ…ÿ†ÿ‰ÿŠÿŒÿ‰ÿ‰ÿ‰ÿ‰ÿŠÿŠÿ‹ÿŠÿ‰ÿˆÿ…ÿÿ~ÿ|ÿzÿxÿwÿwÿxÿxÿvÿvÿvÿuÿsÿnÿiÿaÿWÿMÿDÿ?ÿAÿHÿTÿ_ÿlÿvÿ€ÿ‡ÿŒÿŽÿÿŽÿŒÿ‹ÿŠÿ‰ÿŠÿŠÿ‹ÿŒÿ‹ÿ‹ÿŠÿ‰ÿ„ÿ}ÿrÿgÿ`ÿ`ÿhÿmÿuÿwÿyÿxÿvÿrÿqÿpÿnÿnÿnÿoÿnÿnÿnÿnÿlÿiÿhÿgÿdÿeÿjÿvÿ‚ÿ‹ÿ‘ÿÿÿŽÿÿÿÿÿŽÿÿÿŽÿ{{||||||~{!~"{!~"{#~${%~'{&~'{(})|)}*|*}+|,},|,|,{,|-{.|.{.|.{.{.}-{-}.z.~-|,~,|+}*|*}*}*|)})|(}(|(}(|((|((|''}((}((}((}((|''|''|''|''z((z)~)y'~'x,€0w7€}@@~@ŠS€r„ƒ}‚uzo„w|x|“w˜…˜h—„–f“‚asC|H†HyE”AtDšyn•›f›™—`˜š e›—qm-š6r8š9t9š:w9™7w3”>tQ’VrU‘WrVŽRsNJsIŒIrGŽGtJŽMuMŠNuL‰MsNŠJsJ‹HtI‹KuNŒ\seŒ`tUŒEyE‰EzG…L}L…J}F†DxS‹btSCsC‹Bu=ŠOwWˆUxT‡PwK‰GsI‹OtS‹Yw[‹Zw\ŠXwK‰:x:ˆI|LROyKIzRX{K=w:€.u5€?t9€:v8€>yB€:|8€6}.€(z*€,x4€4w(~97~5‡7}?H|<ŒB|>Š;y<†hz}€‚}U€M}_‚„}|}u€rwp€mzk~j{g€gyh€izi€n{t~o|o}v€t|s€p|m|jhyh‚gzh‚jxk‚ows‚xt|€q…‰n€lk‚n‚ŽoŒŠq‡€„q~€vrp€lugfuc_u`‚av_‚]w]‚ZxU…OzH…A}>†<€<‡=€=‡>€?†@€B„E‚O‚W^h€p~v{~}|~{x|xƒz{y„yzy„zz|…~y‚zƒ|„|ƒr‚†j‡€ˆh‰ˆpŒŒ|‹|‰‚‰yŒ…ŒwŠ†‰v‡‡„vˆ~v{‡zvx‡wxw†xxx…vyv…v{uƒqzm‚g}^U€LC€?A}I‚Uxa‚mtxƒtˆ…w„Žy‹zŠ‰z‰‰zŠ‹zŒŠzŠŠ{ˆƒyzoeqa€cnjowu~x~z}x|s~qxq~puo~nun~ovo~mvn~mwk~jvi}hwe{fymyy~†wŒy‘~||Ž}~Ž~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ"ÿ#ÿ"ÿ#ÿ#ÿ$ÿ%ÿ'ÿ&ÿ'ÿ(ÿ)ÿ)ÿ*ÿ+ÿ,ÿ,ÿ-ÿ,ÿ,ÿ,ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ-ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ(ÿ)ÿ.ÿ2ÿ6ÿ:ÿ@ÿCÿGÿIÿLÿMÿSÿUÿWÿXÿ\ÿ_ÿaÿaÿdÿeÿdÿcÿaÿaÿaÿ`ÿaÿdÿeÿcÿaÿ`ÿ]ÿYÿRÿMÿIÿCÿ;ÿ2ÿ-ÿ*ÿ)ÿ(ÿ)ÿ'ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ+ÿ,ÿ?ÿyÿ‰ÿjÿ|ÿ{ÿ|ÿyÿ€ÿŠÿ€ÿuÿrÿ|ÿ}ÿyÿpÿvÿyÿ~ÿvÿjÿ\ÿQÿXÿkÿgÿSÿGÿdÿwÿyÿzÿyÿ‚ÿÿ¨ÿ­ÿªÿ³ÿ¹ÿ´ÿ·ÿ¹ÿ±ÿ›ÿhÿ7ÿIÿHÿGÿFÿFÿCÿCÿCÿ=ÿWÿŽÿ‰ÿÿ}ÿ„ÿ‹ÿ’ÿSÿ4ÿ@ÿ?ÿ=ÿ@ÿBÿNÿpÿƒÿtÿqÿxÿzÿ~ÿÿ”ÿ”ÿ—ÿ—ÿ–ÿxÿHÿHÿLÿFÿBÿGÿÿ˜ÿÿ“ÿŒÿ–ÿŸÿœÿbÿ,ÿ6ÿ9ÿ9ÿ:ÿ:ÿ8ÿ5ÿ2ÿ1ÿ<ÿKÿQÿQÿSÿQÿNÿMÿLÿGÿGÿGÿHÿMÿJÿLÿKÿMÿMÿIÿGÿEÿEÿKÿSÿWÿWÿVÿMÿJÿFÿGÿIÿLÿQÿRÿPÿMÿQÿ^ÿ]ÿPÿMÿCÿ9ÿHÿVÿRÿMÿDÿ=ÿ9ÿ6ÿ6ÿ6ÿ:ÿ9ÿ9ÿ9ÿ5ÿ1ÿ0ÿ:ÿFÿNÿQÿAÿ=ÿAÿDÿDÿ8ÿ?ÿ>ÿ<ÿHÿIÿ>ÿ9ÿ?ÿDÿEÿ9ÿ5ÿ5ÿ3ÿ5ÿ/ÿ$ÿ2ÿ<ÿ=ÿCÿAÿ6ÿ;ÿCÿDÿ>ÿ:ÿ8ÿ7ÿ8ÿEÿNÿMÿEÿMÿhÿoÿ}ÿ|ÿxÿuÿpÿnÿkÿhÿgÿgÿhÿiÿoÿrÿqÿuÿuÿuÿsÿpÿnÿlÿlÿlÿnÿoÿpÿsÿvÿ{ÿ€ÿƒÿ‡ÿÿ‘ÿ’ÿ“ÿ”ÿ’ÿ’ÿÿŽÿŒÿ‰ÿ„ÿ|ÿuÿnÿgÿcÿ`ÿaÿ_ÿ^ÿ^ÿ^ÿ_ÿ^ÿ\ÿ[ÿYÿTÿMÿGÿ?ÿ<ÿ:ÿ;ÿ<ÿ<ÿ=ÿ>ÿ@ÿDÿIÿQÿ[ÿaÿjÿvÿ|ÿÿ€ÿÿ~ÿ{ÿyÿyÿzÿzÿzÿ{ÿ{ÿÿ€ÿƒÿ‡ÿ…ÿƒÿÿ‚ÿ…ÿ†ÿ†ÿˆÿ‰ÿŠÿŠÿŠÿ‹ÿ‹ÿ‹ÿ‹ÿ‰ÿ‡ÿ„ÿ€ÿ}ÿ{ÿyÿxÿwÿwÿxÿxÿvÿvÿuÿsÿpÿlÿfÿ]ÿTÿJÿAÿ>ÿBÿJÿUÿcÿpÿ{ÿ‚ÿ‰ÿÿÿŽÿÿŠÿŠÿ‰ÿ‰ÿ‰ÿŠÿ‹ÿŒÿŠÿŠÿ‰ÿ‡ÿ‚ÿxÿmÿcÿ^ÿbÿiÿpÿuÿxÿyÿvÿsÿpÿpÿoÿoÿnÿlÿmÿnÿnÿoÿmÿkÿjÿhÿgÿfÿgÿpÿ}ÿ‡ÿÿÿÿŽÿŽÿŽÿÿÿÿÿ‹ÿ‹ÿ‹ÿ{{||{{{ { ~!{!~#{$~${$~%{%~&{'~({(})|)}*|+}+|+},|-|-|.|.|.|.}.|.}/{/}-{-}-z-}-|,},|+}*|*}*}*|)})|(}'|'}'|((|((|''}((}((}((}((|((|((|''|''|&&|&~(|'~'{(~(y(~)w-~0v3€6r:€=pAAmGƒKkL„OgR†VbW†\a^†b^b…c^c…c`c…cbaƒaea_f[€WjQ}MnH|Ar;{3x.{)z'|(|({)~)}(€(}*€+}*+~+€*~*€+~+,~-€-~--}--}-‚-}-‚-}-‚-|-‚-|-‚,|+‚+|+,|,‚*|*‚*|*‚*|*‚*|*‚+|+‚+|+‚+|,‚-|-‚-|-‚,|,‚-|-‚+|++|+‚+|+‚*}**}((}()}))€*7„k‘„q‰X†oŒw‰z…‚Š†„Š†„~ˆ€†€†„‡|…n‡v„s‡p„wˆph†c€V„c~}†o{G‰4vXvt|Šzw…†}œ‚£®|·º{»€»|»€¹|­‚Š€B~BŽE}L—J{D™B{C™D{>•F|~‹Œ~‚„}€‚…€‘ƒˆ~=†8|?‹>z>ŽE|C‹a|‚…wys†y{‡~€…~ˆ‚ˆt“„–m–…‰tO€N…IzF‘AtJ—ƒnššžf›—šc›™fš™^m+š6s7š7v9š9x9™7x3•0w-’8wGŽNvO‹OuM‹KtKŠItGŠGuFŠKvK‰MwK‰MvM‡JwH‡FuCˆFtSŠMqKŒNrRŒSuIŠIvI†JyQ†VyW‡[wairYLqJBs9ŒEuVŠSxJ‰Bv>Š@tEŒHt@:v2Œ/v/-w-Œ0w7‹CzEˆ7|2„2}3:~;|>€Cy>€DyM€Kz?€=y9€BzH€9|6€8~6€4|4€7{9€A{C9€4~3†<}AŠ=};‰9}:ˆ:z=†z@‡BwJƒOvX}ctnymwlwi{gwg~ixilzil}t}vu|t~t~qzr€rut‚vqv‚xq|‚€oƒƒˆnŠk’€”j“•j”‘mŒp‰‚„r~urmgrb^s[[v]‚]x^‚^x^]y\\yZ‚XzR„M{H…@;…:€:„:€;„=€=…@€EƒI‚S[f~oy{€‚…{…‚‚|€‚~}}„{zy…zyz…{y{†}x…ƒw…ƒˆy†{…{‚p‚~ƒfƒ†hˆ‚‹r‹Œ~‹|Š„Œy‹‡‰v‡ˆ„v€ˆ{vy‡wvv‡wxw†vxw…vyu…uzu„p{kƒe~\‚RG‚@€?ƒC}K„Yxg„st}…„tŠ…Žw„ŽyŒŠzŠ‰z‰‚‰zŠ‚ŠzŠŠzŠ‚‰{‡u|i]u]€auj}o{u{xx|u}s~qxp~pwn~mwl~mwm~owo~nyl~lwj}iwg{jysy~~ˆw‘y||ŒyŽŒwŒ€uŽ‚sŽ‚Žqÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ ÿ!ÿ!ÿ#ÿ$ÿ$ÿ$ÿ%ÿ%ÿ&ÿ'ÿ(ÿ(ÿ)ÿ)ÿ*ÿ+ÿ+ÿ,ÿ,ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ+ÿ*ÿ,ÿ.ÿ0ÿ1ÿ5ÿ6ÿ;ÿ?ÿ@ÿCÿGÿLÿPÿTÿZÿ]ÿ^ÿ_ÿ`ÿ`ÿ`ÿ`ÿ]ÿ\ÿZÿVÿQÿLÿGÿAÿ:ÿ3ÿ.ÿ+ÿ)ÿ'ÿ(ÿ)ÿ(ÿ(ÿ(ÿ)ÿ)ÿ*ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ+ÿ+ÿ+ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ,ÿGÿoÿhÿWÿYÿqÿuÿ|ÿ‰ÿ†ÿ‡ÿƒÿ‹ÿŒÿƒÿ}ÿ‚ÿÿ€ÿ{ÿqÿuÿlÿpÿmÿXÿEÿdÿsÿjÿRÿGÿ]ÿfÿtÿ‚ÿÿÿ˜ÿ¨ÿµÿ»ÿ»ÿ¼ÿ»ÿºÿµÿžÿ]ÿ7ÿ>ÿAÿEÿBÿCÿCÿ?ÿ@ÿCÿdÿ‘ÿˆÿ~ÿ~ÿÿŠÿ—ÿfÿ1ÿ@ÿ@ÿ>ÿCÿHÿqÿ€ÿxÿxÿzÿÿ~ÿ{ÿzÿÿŒÿ•ÿ—ÿÿ[ÿ@ÿEÿGÿHÿJÿ‚ÿ›ÿÿœÿžÿšÿšÿ”ÿQÿ0ÿ7ÿ:ÿ9ÿ:ÿ9ÿ8ÿ6ÿ3ÿ/ÿ.ÿ0ÿ;ÿEÿMÿOÿNÿMÿKÿIÿGÿGÿGÿEÿKÿMÿKÿKÿJÿHÿEÿFÿCÿDÿXÿQÿLÿNÿSÿTÿHÿIÿIÿKÿMÿUÿ_ÿrÿwÿoÿdÿ]ÿKÿBÿ=ÿMÿ[ÿRÿHÿGÿMÿQÿRÿPÿHÿ?ÿ3ÿ0ÿ/ÿ0ÿ0ÿ0ÿ2ÿ2ÿ/ÿ/ÿ0ÿ3ÿ2ÿ5ÿ;ÿ;ÿFÿ;ÿ;ÿEÿBÿ9ÿ3ÿ8ÿ>ÿAÿ=ÿ7ÿ9ÿ7ÿ9ÿ:ÿ>ÿ>ÿ8ÿ2ÿ3ÿ6ÿ3ÿBÿOÿ=ÿ6ÿ5ÿ8ÿ9ÿ:ÿ<ÿ;ÿ9ÿ=ÿCÿBÿ9ÿ:ÿBÿEÿHÿRÿ]ÿaÿaÿgÿiÿhÿjÿoÿoÿtÿyÿzÿyÿyÿxÿvÿxÿzÿ|ÿ~ÿ€ÿƒÿ‡ÿ‰ÿŒÿŽÿÿ’ÿ“ÿ•ÿ“ÿ“ÿÿÿˆÿƒÿ{ÿsÿkÿcÿ^ÿ]ÿ\ÿ[ÿXÿZÿ\ÿ\ÿ\ÿ]ÿ]ÿ\ÿ\ÿ\ÿYÿVÿRÿKÿEÿ=ÿ;ÿ:ÿ9ÿ:ÿ;ÿ<ÿ<ÿ>ÿEÿJÿRÿ_ÿgÿqÿ|ÿ‚ÿ‡ÿ‡ÿ…ÿƒÿ€ÿÿ|ÿ{ÿzÿ{ÿ|ÿ|ÿÿƒÿ…ÿ‰ÿŠÿˆÿ†ÿƒÿÿÿÿƒÿ†ÿ‰ÿ‹ÿŒÿÿÿŒÿ‹ÿˆÿ†ÿ‚ÿ~ÿzÿxÿvÿvÿwÿwÿvÿvÿvÿuÿtÿtÿoÿiÿdÿ[ÿOÿFÿ@ÿ?ÿEÿMÿZÿfÿtÿ~ÿ…ÿ‹ÿŽÿÿÿŠÿ‰ÿ‰ÿ‰ÿ‰ÿ‰ÿŠÿŠÿŠÿŠÿŠÿˆÿ„ÿ|ÿrÿeÿ\ÿYÿ^ÿgÿnÿsÿwÿvÿrÿqÿpÿpÿpÿnÿmÿlÿmÿmÿoÿnÿnÿmÿlÿjÿiÿhÿmÿvÿÿŒÿÿ‘ÿÿÿŒÿ‹ÿÿ’ÿ“ÿ”ÿ’ÿÿ‹ÿ{{{{zz~z~ z!~"y"~$y$~%z%~&|&}'z'}(z*|*{*|*{+|+{*|-{.|.}.|.}.|.}.|.}.|.~.|.~-|-}-|,|,|+}*|*})~)})~)}(~(}(~(}(~(|(~(|(~(}(~(}(({(({''|''|''|((|''|&&|''|''|'~'{&~&{((|((|++y*}-w1}4s6~8p>BnG‚LiOƒQeUƒVdV‚YeYWhURkN~KnE|@r8{3v){)~'{'~(}(€'})€(}*€*~)*~*,~,,~+-~-,~-.~..}.€.}.€-|-‚-|-‚-|-‚-{-‚-|-‚,|,‚+|+‚,|,‚,|,ƒ+|*ƒ+|+‚+|+‚+|+‚,|,‚-|-ƒ-|-ƒ-|-ƒ-|-ƒ,{-ƒ,|,‚.~.‚0~6‚+~)+~+*|*‚)*‚,‚5ƒY†^‡c‰w‹ˆ^‰k‰~„ˆ‹€„x‹‡ˆ‹‹ˆ‡|‰|…{‰{…q‰mƒvˆox†u€\†A‚V†p„}†u€cˆYz]‰by{ˆŠ{‘„“~£¯~º|¼~»{»º{¸‚§}p~0Š6z=–ByA›DyE›DzDšCzN˜ƒ}Œ†~„}€‚‚’€Ž‚H1†?BˆK~m†~{|‡{x€‰€z†…‰€‰|‡†‰tŽ‡”o•†te€?ƒCzCŽEtP–o—˜œjœ™Ÿe›™™g’š@n3›;r;›:u:›:v9›8v6™3x/”.x0=xJŒLuM‰LtL‰LtJ‰JtG‰IwKŠMxIˆDyC…AzE…D{E†EyX‰[uMŒOsTSuB‹HwIŠKwMŠNv[‹mrm’lpg”foI’?rD[s^ŽRsPVrW‘WqV’Kr@8t2.u*Š)v-Š.x.‰.z/‰.y.ˆ/z1…4|79}8|48{5€2z.€1y;€Ay>€;{;€69€:‚;€<„9€8†1:†:}1ˆ4}4Š7}2‰8~BˆI~G†>7‡5€>ŒCDŒF~C‹>|9‰?xP…Xt[}Yn\vdsmuq|tvv€yw}~~{}}z}~v‚ƒs„„‡qŠ„ŒpŒ„ŒmŽƒŽm’n”€’oŒr‡‚u{€pug]tWXuY€[w\\x]]y\\|\\|\‚\}[‚[}W‚U}PƒJ}D„<~:ƒ9€9ƒ:;‚<<„@GƒK‚S]ƒk|vƒy…ƒ‰y‰„‰y†…ƒy…}z|‡zx{‡|x{‡€x„‡†vŠˆ‹vŠ…†zƒz}€k…d„‡iŒuŒ‚‹yŒ†‰v…‡‚t}‰ztwˆuwu‡vxv‡vxv†vxv‡tyr…m{g…a~YƒO€Eƒ?€=‚EzQ…^uk†vs‚‡†t‹†ŽwŽƒyŠƒ‰z‰ƒ‡y‡ƒˆy‰ƒŠxŠƒŠzŠƒˆ}‚xn~_VzU~[{b{lsxv„vztq}pzonwmkvklum}oxp|pxo|mvk|jwjzo{zw…€Žw‘€‘yŽ{}r‘jiƒi‹…‰iÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ!ÿ"ÿ"ÿ$ÿ$ÿ%ÿ%ÿ&ÿ&ÿ'ÿ'ÿ(ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ*ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ+ÿ,ÿ.ÿ2ÿ5ÿ7ÿ:ÿ>ÿBÿDÿIÿJÿJÿMÿNÿKÿHÿGÿCÿ@ÿ:ÿ2ÿ,ÿ)ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ'ÿ)ÿ)ÿ*ÿ+ÿ*ÿ*ÿ*ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ,ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ4ÿSÿ@ÿ(ÿ+ÿ+ÿ*ÿ*ÿ)ÿ*ÿ2ÿHÿRÿnÿŠÿ‡ÿŠÿqÿiÿmÿ~ÿÿ“ÿ…ÿ…ÿˆÿƒÿ{ÿ{ÿ€ÿÿvÿpÿrÿmÿMÿ?ÿUÿBÿbÿxÿÿxÿ|ÿnÿXÿ_ÿjÿ€ÿŒÿ˜ÿŸÿ¨ÿ²ÿ»ÿ»ÿ»ÿºÿºÿ¦ÿtÿ4ÿ7ÿ<ÿ?ÿ?ÿCÿEÿCÿCÿCÿFÿcÿÿÿÿyÿzÿƒÿÿˆÿDÿ-ÿCÿ\ÿƒÿ~ÿ|ÿ}ÿ‚ÿ…ÿ…ÿˆÿŒÿŒÿŽÿÿ’ÿ’ÿÿzÿIÿFÿAÿFÿLÿÿ–ÿšÿšÿœÿœÿ™ÿ•ÿIÿ5ÿ<ÿ9ÿ:ÿ:ÿ:ÿ:ÿ9ÿ5ÿ4ÿ2ÿ.ÿ+ÿ4ÿEÿLÿOÿNÿNÿKÿIÿJÿLÿLÿMÿKÿGÿLÿPÿRÿPÿKÿDÿAÿEÿ]ÿOÿNÿTÿSÿ?ÿEÿHÿJÿMÿNÿQÿ^ÿbÿ`ÿ^ÿ^ÿMÿKÿRÿ]ÿ]ÿYÿ\ÿ_ÿ\ÿWÿPÿEÿ=ÿ8ÿ6ÿ-ÿ&ÿ'ÿ*ÿ+ÿ+ÿ-ÿ-ÿ-ÿ-ÿ-ÿ0ÿ1ÿ2ÿ5ÿ@ÿAÿ>ÿ<ÿ<ÿFÿGÿHÿBÿFÿ?ÿ<ÿ7ÿ9ÿ7ÿ7ÿ9ÿ3ÿ4ÿ5ÿ6ÿ3ÿ1ÿ/ÿ2ÿ6ÿ1ÿ3ÿ7ÿ:ÿ9ÿ?ÿ<ÿ:ÿ>ÿ<ÿ?ÿEÿKÿJÿGÿAÿ7ÿ=ÿNÿiÿ`ÿHÿ]ÿxÿ|ÿzÿ}ÿÿƒÿ„ÿ…ÿ…ÿ†ÿˆÿ‰ÿŠÿ‹ÿŠÿ‹ÿŒÿŒÿŽÿŽÿÿŽÿŽÿŒÿˆÿ€ÿxÿlÿcÿVÿNÿLÿPÿTÿXÿYÿ[ÿ[ÿ\ÿ\ÿ[ÿ[ÿ[ÿ[ÿ\ÿ\ÿ[ÿYÿWÿUÿPÿJÿCÿ;ÿ:ÿ9ÿ8ÿ9ÿ;ÿ<ÿ?ÿBÿEÿMÿWÿaÿjÿsÿÿ†ÿ‹ÿ‹ÿŠÿˆÿƒÿÿ|ÿ{ÿzÿ{ÿ{ÿ|ÿ€ÿ„ÿˆÿ‹ÿ‹ÿŠÿ‰ÿ…ÿÿ€ÿÿ‚ÿƒÿ†ÿŒÿŒÿŽÿŽÿŒÿ‹ÿˆÿ„ÿÿ|ÿyÿvÿtÿtÿvÿvÿuÿuÿuÿuÿtÿrÿmÿgÿ`ÿXÿKÿAÿ<ÿ=ÿEÿQÿ_ÿlÿxÿƒÿ‰ÿŒÿŽÿŽÿÿŠÿ‰ÿ‰ÿ‰ÿ‰ÿˆÿ‰ÿŠÿŠÿŠÿŠÿ‡ÿÿwÿlÿ]ÿSÿSÿZÿcÿmÿtÿwÿuÿsÿqÿpÿoÿmÿlÿkÿkÿlÿmÿoÿpÿqÿpÿoÿlÿkÿmÿtÿ}ÿ‰ÿÿÿŽÿÿ”ÿÿÿŒÿŠÿ‰ÿ‰ÿˆÿˆÿˆÿ{{{{zz ~ z ~!z!~"y"~$y$~%z$~%{'}(}(}*}*|*|*|*|+|+},|.}.|.}.|.}.|.}/|/}-|-~-|-~-|-},|+|,|,}*|*})~(})~)}'~'}'~'}(~(|(~(|(~(}(~(}''z''z''|''|''|((|('|''|'~'|'~'|'~'{'~'{))|))|)}){)|)z)|)x*|,z/}0x3~5v7:s?ArA~AqB}As={;v6{2x,z){'|&}'|'(|()})€)})€)~*+~+~,~-~,~+~,~,-~,-~..~..}./}//|/‚/|/‚-|-‚-{-‚-|-‚-|-‚-|-‚,|,‚,|,ƒ,|,ƒ,|,‚,|,‚,|,ƒ,|,ƒ-|-ƒ-|-ƒ-|-ƒ-|-ƒ-{-ƒ-|+ƒ+},ƒ/}1‚-}**}**~*+€,A‡X_‹{‡†x‰ƒ~~ŒRJŽT„e‹†Š‹‡‹‡Š‚Œˆ…€Št„tˆw„sˆuƒpˆbƒTˆ>…Lˆ‡uˆt‰€†‡…r…X}_‡ozƒ†{žƒ¥€®~´¹z»€ºz¹€£~i|1‹7z:”™AyAœ@y@œFxC›Dzm‘‘}†‡}€{‚}€€‚Š€‰ƒ;~2†g|ƒ†€w}‰€t…Š‡x‡‡‰‚Š}Ž†p‡k†ŽoƒV}KzDŒDtK•}o’—–j˜˜›fš˜šg“šPn5œ9r:;u<;v;:w9š4w1–.x,‘/x@KuPŒPtL‹JtH‹HvLŠLwM‰PxQ‡QyQƒP|MƒG}:…0y1‰awVŒMuQŒMw4‹=xC‹FwGŒJtV]pb”an^–^nb•bod“dod’doc”`nZ”RoE“‡FO‡R€F…?9„61„4~3…3~4†5~3…53„8€C„F=†@A‡9:‰@€G‹KIŠG}?Š7zQ„avV^ttzƒx†xŠ‹sŠ€ŒrŒ‚Œq‚Œq‹‚Šs‰‚ˆr‡ƒ‰q‰‰s‡€†u…€vw€nwd€VwM€FxFL{SVzY€YzYZzZZ{[[}[[}[‚[}Z‚Z}W‚T}OƒI}A„:~:ƒ9€8ƒ9;‚<=‚AJ€X‚^~aƒlzw„€w†…Šw‹†‹v‰‡„w€ˆ}xz‡zwy‡{x}‡x„‡‡uŠˆ‹v‹ˆˆy†ƒ‚{u}‚h€ƒe‡‚‰jŒxŒ}‹„†x„‰u|‰xuvˆtwtˆuxu‡uxu‡uxu†txq†l{f†]~U„I€@„:=…EyQ‡_tmˆzq…‰‹t‡ŽwŽ…ŒyŠƒˆzˆƒ‡y‡ƒ‡yˆƒŠxŠƒŠz‰ƒ…}~‚wi€\€Q€S}Z„exn†uww…vztr|q}onzlkxklwm~n{o}p{p}nym|kxmyuy‚vˆ{zs’•k’‚Žg‹…‰j†…‡l‡ƒ…jˆ‚ˆjÿÿÿÿÿÿÿÿÿÿÿÿ ÿ ÿ!ÿ!ÿ!ÿ"ÿ"ÿ$ÿ$ÿ%ÿ$ÿ%ÿ'ÿ(ÿ(ÿ*ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ+ÿ+ÿ+ÿ*ÿ*ÿ)ÿ(ÿ)ÿ)ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ)ÿ*ÿ,ÿ-ÿ.ÿ.ÿ1ÿ3ÿ6ÿ7ÿ7ÿ7ÿ6ÿ2ÿ/ÿ,ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ+ÿ+ÿ+ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ/ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ.ÿ-ÿ,ÿ-ÿ,ÿ,ÿ,ÿ*ÿ*ÿ+ÿ+ÿ-ÿ8ÿOÿQÿyÿ‡ÿ€ÿTÿbÿ[ÿOÿJÿMÿiÿ›ÿ™ÿ‰ÿ†ÿÿ‘ÿÿŽÿ|ÿtÿyÿ|ÿzÿ€ÿyÿoÿHÿ?ÿ^ÿxÿxÿuÿ„ÿ†ÿnÿSÿcÿqÿÿ˜ÿ¦ÿ©ÿ¬ÿ°ÿ·ÿºÿµÿšÿNÿ/ÿ9ÿ:ÿ>ÿ>ÿ=ÿ@ÿAÿ>ÿAÿBÿBÿKÿ{ÿŒÿƒÿ}ÿzÿxÿÿ‹ÿÿ@ÿwÿƒÿ}ÿ{ÿÿ‡ÿ‡ÿ‚ÿ†ÿ‹ÿŽÿÿÿ‘ÿ‘ÿ‘ÿ’ÿmÿNÿLÿEÿIÿ|ÿÿ—ÿ–ÿ—ÿ—ÿ–ÿ’ÿUÿ5ÿ9ÿ;ÿ;ÿ<ÿ;ÿ;ÿ9ÿ7ÿ4ÿ3ÿ.ÿ-ÿ,ÿ9ÿIÿOÿSÿVÿWÿWÿWÿWÿVÿSÿQÿYÿ[ÿXÿOÿBÿ3ÿ,ÿ(ÿ,ÿ]ÿ^ÿOÿPÿJÿ,ÿ/ÿ4ÿ9ÿ?ÿTÿ[ÿ`ÿeÿ_ÿ`ÿjÿlÿmÿhÿiÿiÿiÿjÿ`ÿTÿFÿ?ÿ:ÿ:ÿ9ÿ9ÿ2ÿ)ÿ(ÿ,ÿ*ÿ+ÿ+ÿ*ÿ*ÿ*ÿ)ÿ*ÿ-ÿ.ÿ.ÿ2ÿ=ÿIÿKÿMÿPÿNÿXÿOÿIÿLÿHÿAÿFÿMÿQÿVÿNÿBÿ/ÿ2ÿ6ÿ4ÿ/ÿ/ÿ1ÿ4ÿ6ÿ0ÿ/ÿ8ÿKÿ@ÿ;ÿ8ÿ5ÿNÿTÿIÿAÿ9ÿ9ÿ@ÿDÿGÿAÿ9ÿ=ÿQÿ[ÿTÿkÿ‹ÿ‘ÿÿŽÿÿÿ‹ÿŠÿ‰ÿ†ÿ†ÿ„ÿ€ÿ~ÿ}ÿ}ÿ}ÿ|ÿyÿxÿoÿjÿ`ÿVÿLÿFÿDÿGÿKÿQÿTÿXÿXÿWÿXÿXÿXÿYÿYÿYÿYÿZÿZÿZÿZÿVÿRÿLÿFÿAÿ:ÿ:ÿ9ÿ8ÿ9ÿ;ÿ<ÿ?ÿAÿLÿTÿWÿ`ÿlÿwÿ‚ÿ‡ÿŠÿ‹ÿŠÿˆÿƒÿÿ}ÿzÿyÿ{ÿ{ÿ}ÿÿ„ÿ‡ÿ‹ÿŒÿ‹ÿ‰ÿ†ÿ…ÿ‚ÿ€ÿÿ€ÿƒÿ…ÿˆÿ‰ÿŠÿŒÿ‰ÿ‡ÿƒÿ~ÿyÿwÿvÿuÿsÿuÿuÿuÿuÿtÿtÿtÿqÿkÿeÿ\ÿTÿIÿ@ÿ;ÿ>ÿFÿRÿaÿnÿ{ÿ†ÿ‹ÿÿŽÿŽÿ‹ÿ‰ÿˆÿˆÿ‡ÿ‡ÿ‡ÿˆÿŠÿŠÿŠÿ‰ÿ…ÿ}ÿsÿfÿXÿPÿSÿ[ÿgÿqÿvÿwÿuÿsÿrÿpÿoÿnÿlÿkÿlÿmÿmÿnÿpÿqÿpÿpÿnÿmÿqÿyÿÿŒÿ”ÿ—ÿ”ÿÿÿ‹ÿ‹ÿ‡ÿ†ÿ‡ÿ‡ÿˆÿŠÿŠÿ||{{zz ~ y!~!y!~"y#~$y#~$z%~&z(~'|(~)|+}+}+}+},|,|.|.|/|/~/|/~/|//|/-|-~-|-~,|,}-|,}+|*}*|*})~)|)~)|(~(|(~(|(~(|(~(|(~(|(~(|'~'z'~'z&~&{'~'{'~'z(~(z((|''|''{''|''}''}((}((}(~(|)~)|)~*z)|)|*}*~*}*~*|*|,|,z-|-{-|-|+|*})|('}&~&}((}(€(}(€)~)€)~*€*~+€,~,€,~-.~..~./~/.~/€0~0€1~11~1/}//}//|-‚.{.‚/|/‚.|.‚-|-‚-|-‚-|-ƒ-|-ƒ,|,ƒ,|,ƒ,},ƒ,},ƒ-|-ƒ-|-ƒ.|.ƒ.|.ƒ.|.ƒ.|.„.|.„-|-ƒ+|+ƒ+}+ƒ,-0‚J€K‰P…‡ˆbMŒHzE”J|Sh€®„©Š¡…›‹—ƒ Œ˜‹Œ€„‹vƒvŒoƒpŒko‹O:Š;H‰b‡„‡ŠŠ„…~ƒi…V}\‡y|†Ÿ¥§ƒ§{©ƒ³{©~|‡7|29|<”=|<–œ?wBšQyƒ‘Š~‚ˆ{}{„v~€…|w†ƒz|ˆwu|‰ƒr†‹w|ˆ„€Š|Œ†Œo‘ˆ’m“‡‘rƒ{~R}OGuI˜xn™•k“˜”h”˜“h™`n7œ‚D€IPY|b„oyy†‚u†ˆ‰uŒˆŠuˆ‰ƒv€‰}vzŠxvz‰zv}‰ƒu…‰‰vŒ‡‹wŠ†‰wˆ…„yƒ~z€p|‚e‚…eˆ‹q‹~‹€†|ƒ…}yx‡xwvˆswtˆuxuˆuxtˆtxtˆsxpˆj{d†[S…G€?…=}>†HxTˆcspŠr‡ŠŒtŽ‡w‹…‰xˆ„†y†„†y‡„ˆy‰„ŠxŠ„Š{ˆ„ƒ~{„qcƒWQƒW}`…jurˆvuvƒu{r€r}p}o~n{m€m{mm}m~n}q}q|q|mzm{oxty~vŽ|–o”‚ŽjŒƒ‰l‰‚ŠmŠ‚ŒkŒ‚lŒƒŒl‹ƒŠoÿÿÿÿÿÿÿÿÿÿÿ ÿ!ÿ!ÿ!ÿ!ÿ"ÿ#ÿ$ÿ$ÿ$ÿ%ÿ%ÿ&ÿ'ÿ(ÿ)ÿ)ÿ)ÿ)ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ&ÿ'ÿ)ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ*ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ*ÿ+ÿ,ÿ,ÿ,ÿ-ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ/ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ/ÿ0ÿ0ÿ/ÿ/ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ-ÿ.ÿ5ÿVÿIÿhÿ‰ÿÿKÿGÿEÿXÿrÿ†ÿ—ÿ¡ÿ§ÿ­ÿ­ÿ¬ÿ§ÿ¢ÿ›ÿ€ÿ†ÿ‹ÿ}ÿoÿkÿmÿsÿrÿ]ÿ?ÿ?ÿ8ÿ3ÿGÿmÿwÿ‚ÿ~ÿYÿ]ÿpÿ€ÿ’ÿžÿ¤ÿ¡ÿÿŸÿŠÿOÿ4ÿ;ÿ;ÿ:ÿ:ÿ:ÿ8ÿ;ÿ:ÿ<ÿ?ÿ>ÿBÿMÿOÿdÿ‰ÿ…ÿ{ÿyÿwÿyÿ„ÿŽÿ‰ÿ|ÿxÿ~ÿ…ÿÿ€ÿ~ÿˆÿÿˆÿŒÿÿ’ÿÿ‹ÿ‰ÿƒÿSÿIÿHÿOÿpÿ‹ÿ‘ÿ”ÿ”ÿ’ÿ‘ÿŒÿjÿ:ÿ<ÿ;ÿ=ÿ<ÿ=ÿ<ÿ8ÿ5ÿ4ÿ0ÿ0ÿ.ÿ.ÿ[ÿ€ÿ„ÿ}ÿsÿoÿiÿ[ÿWÿUÿTÿZÿ]ÿ]ÿXÿEÿ,ÿ&ÿ)ÿ(ÿ*ÿHÿlÿUÿLÿJÿ/ÿ,ÿ/ÿTÿkÿkÿkÿgÿlÿ{ÿyÿoÿjÿhÿeÿgÿkÿmÿeÿ\ÿRÿJÿEÿAÿ@ÿBÿ?ÿ<ÿ9ÿ7ÿ6ÿ8ÿ6ÿ3ÿ0ÿ*ÿ#ÿ#ÿ$ÿ(ÿ,ÿ-ÿ.ÿ/ÿ8ÿAÿFÿQÿ[ÿ`ÿRÿKÿLÿTÿQÿZÿcÿ_ÿWÿQÿIÿ5ÿ!ÿ+ÿ<ÿ<ÿ1ÿ.ÿ0ÿ0ÿ.ÿ+ÿ*ÿ<ÿvÿ‘ÿÿ”ÿ‡ÿeÿIÿ?ÿJÿPÿPÿQÿNÿ<ÿ7ÿBÿDÿBÿ:ÿCÿWÿhÿŠÿÿŠÿ…ÿ‚ÿ|ÿ{ÿvÿqÿlÿiÿiÿiÿkÿlÿnÿnÿmÿhÿcÿ\ÿSÿJÿFÿFÿIÿMÿQÿUÿYÿYÿXÿXÿYÿYÿZÿZÿZÿZÿYÿYÿYÿXÿVÿQÿKÿEÿ@ÿ:ÿ9ÿ8ÿ:ÿ;ÿ<ÿ=ÿAÿFÿKÿTÿ\ÿdÿqÿzÿ‚ÿ†ÿ‰ÿ‹ÿ‰ÿ‡ÿƒÿ€ÿ|ÿyÿzÿzÿ{ÿÿƒÿ…ÿŠÿŒÿ‹ÿŠÿˆÿ‡ÿ‡ÿ‡ÿ„ÿÿÿÿƒÿ„ÿ…ÿˆÿŠÿ‰ÿ†ÿ‚ÿ}ÿxÿwÿuÿtÿuÿsÿsÿtÿtÿtÿtÿrÿoÿjÿdÿ\ÿRÿGÿ?ÿ>ÿCÿMÿYÿeÿrÿÿ‡ÿŒÿŽÿÿ‹ÿ‰ÿˆÿ‡ÿ‡ÿ†ÿ‡ÿˆÿ‰ÿŠÿŠÿŠÿˆÿƒÿzÿoÿbÿVÿSÿXÿ`ÿjÿsÿyÿwÿuÿrÿqÿpÿoÿnÿmÿmÿmÿmÿmÿnÿpÿqÿpÿoÿmÿmÿ}ÿ”ÿ˜ÿ“ÿÿ‰ÿ†ÿˆÿ‰ÿŒÿŒÿŽÿÿÿ‹ÿ‹ÿŒÿŒÿzzzz  {!!{"~"z"~"z#~$z%~%z%~&z&~'z(~(z'~)z*}*{*}+{,|,{,|,{.|.~.|.~/|/.|--|-~-|-~,|+}*|)}*|*}(|(})~)|)~)|)~)|)~)|)~)|)~)|(~({(~({(~(y)~*y,~)y&~(x'~'z(~(z(({(({''z'(z''}''}((~((~(~(}(~)}(~)})|)~)})*}**|*)|)(|)~)|))|(€(|*€)})(}(‚)})‚(}(‚)~)€*~+€,~.€.~.€.~//~./~/1~11~11~11~11~12}21}11|1‚/{/‚/|/‚/|.‚-|-‚-|-‚-|-ƒ.|.ƒ-|-ƒ-|-ƒ,{-ƒ.{.ƒ.|-ƒ.|.ƒ.|.ƒ.|.ƒ.|.ƒ.|.„.|.„-|-ƒ-|-ƒ-}-ƒ./‚?…]n‚…n}n‹Gz>a}ŽŽšxŸ¨w¦Š¡z£‰£|«Šªyª‹©sŽ‹wsx‘x{|xzbt[‘KtH’CxD‹J‚q‡y†}„oƒ^…j~wˆˆ“ˆœ}™…’|Š†l~DŒ:~=’:}<“=~A”C~C•?{=–™BwHœJvR›myˆ‘zxˆu{v‡|z…‡ŠwzŠvt{Œqwyˆ…€|ƒts}…‘q“…‘s’ƒyh}EŠIuQ˜gm„›k“™Žhˆ—Žj‹—un<š9s:œ†9}=ˆD}Bˆ8{=†Wwo}Šz‡z{xwsoykhzgf{gh|jl}lm|oi{c‚\{SƒK{GF}I€M~RS}XX|XX}XY~ZZZZ[€Y~[X~U‚P~JƒD~>„:9‚89ƒ:€;‚<€AGLSƒ\{g…sw|‡‚t†Š‰sŠŠˆs…‹€t‹zvyŠyvw‰{v‰‚u†‰ŠuŒˆ‹vŠ‡ˆwˆ‡‡y†…†yƒ{ƒ{m‚~‚e„†g†€†q…€ƒ~}|y†vwtˆrws‰twt‰twt‰tws‰oxm‰g{c‡[~Q†G€?†@|F‡Pw\‰hruŠqˆ‹ŽtŽ‡w…‹y‡„†z†„†y‡„ˆy‰„ŠxŠ„‰{‡„‚xƒm`ƒUT„\ydˆnst‰wut„t|q~po|n~m}m€m}mmm~nq}q~p|mzp|†t——l’„i‰ƒ†n†‚‡r‰‹r€pŒ€‹q‰€‹rŽ€’uÿÿÿÿÿÿÿÿ ÿ ÿ!ÿ!ÿ"ÿ"ÿ"ÿ"ÿ!ÿ"ÿ%ÿ%ÿ%ÿ&ÿ&ÿ'ÿ(ÿ(ÿ)ÿ*ÿ*ÿ*ÿ*ÿ+ÿ,ÿ,ÿ,ÿ,ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ+ÿ*ÿ*ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ.ÿ9ÿ+ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ+ÿ-ÿ,ÿ,ÿ/ÿ/ÿ.ÿ/ÿ0ÿ1ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ/ÿEÿtÿzÿeÿCÿRÿBÿXÿÿ¨ÿ¥ÿ¤ÿ£ÿ¦ÿ¨ÿ¥ÿ£ÿ­ÿ¯ÿ®ÿ«ÿ¢ÿ‘ÿÿÿ†ÿ„ÿ…ÿ‰ÿwÿlÿjÿ`ÿeÿ[ÿeÿWÿ_ÿwÿ{ÿ{ÿeÿfÿrÿ{ÿ†ÿŽÿŽÿ‰ÿtÿJÿBÿ:ÿAÿ>ÿ=ÿ=ÿBÿIÿJÿOÿJÿDÿBÿ?ÿ?ÿHÿQÿRÿqÿ‰ÿ{ÿwÿuÿwÿ|ÿ‡ÿ‚ÿsÿqÿyÿ{ÿwÿkÿnÿ‡ÿwÿbÿsÿÿ˜ÿšÿšÿ•ÿ{ÿEÿCÿMÿ^ÿ†ÿ‘ÿŽÿ„ÿwÿŒÿŒÿzÿ@ÿ6ÿ8ÿ;ÿ;ÿ9ÿ6ÿ2ÿ1ÿ1ÿ0ÿ,ÿ8ÿqÿ}ÿyÿwÿuÿuÿkÿ`ÿVÿSÿTÿMÿEÿAÿ:ÿ;ÿ;ÿ<ÿ9ÿ6ÿ4ÿ4ÿ>ÿhÿZÿPÿPÿ3ÿ9ÿfÿjÿqÿtÿÿˆÿƒÿ}ÿzÿrÿoÿqÿsÿlÿcÿfÿtÿxÿoÿfÿeÿ_ÿXÿQÿJÿEÿ@ÿ>ÿ@ÿ=ÿ<ÿ7ÿ*ÿ$ÿ$ÿ$ÿ$ÿ&ÿ-ÿ-ÿ.ÿ,ÿ)ÿ,ÿGÿdÿeÿgÿ^ÿ[ÿ[ÿdÿqÿyÿ…ÿ†ÿ‚ÿ}ÿ{ÿ~ÿwÿaÿ<ÿ0ÿ4ÿ.ÿ+ÿ6ÿFÿFÿDÿ?ÿCÿLÿLÿ^ÿ`ÿ\ÿsÿ|ÿ}ÿ{ÿ[ÿFÿGÿLÿFÿGÿ@ÿ7ÿ?ÿ@ÿ=ÿAÿ[ÿsÿ†ÿyÿpÿnÿjÿhÿgÿgÿgÿgÿhÿjÿlÿnÿmÿmÿkÿfÿ]ÿUÿNÿGÿHÿKÿOÿRÿUÿVÿVÿXÿXÿXÿXÿYÿZÿZÿZÿZÿZÿZÿXÿUÿPÿJÿDÿ>ÿ:ÿ9ÿ8ÿ9ÿ:ÿ;ÿ<ÿBÿGÿNÿVÿ_ÿjÿtÿ}ÿ‚ÿ†ÿ‰ÿŠÿˆÿ„ÿ€ÿ}ÿyÿxÿxÿxÿ|ÿ€ÿ„ÿˆÿŠÿ‹ÿŠÿ‰ÿˆÿ‡ÿ‡ÿ‡ÿ…ÿƒÿ‚ÿ€ÿÿƒÿƒÿ„ÿƒÿ„ÿƒÿ„ÿÿyÿvÿtÿsÿrÿsÿsÿtÿsÿsÿrÿoÿmÿfÿaÿYÿOÿDÿ?ÿAÿGÿRÿ^ÿjÿwÿÿ‰ÿŽÿŽÿŽÿ‹ÿŠÿ‡ÿ†ÿ†ÿ†ÿ‡ÿˆÿ‰ÿŠÿŠÿ‰ÿ‡ÿ€ÿvÿjÿ]ÿTÿTÿ\ÿeÿnÿtÿwÿtÿsÿpÿpÿoÿlÿlÿnÿnÿmÿmÿmÿnÿpÿrÿmÿwÿÿ—ÿ”ÿÿ‹ÿ‰ÿ‡ÿ†ÿ…ÿ†ÿˆÿŠÿ‹ÿ‹ÿ‹ÿ‹ÿÿÿ‘ÿŽÿyy yy!!y""y#~#y#~#y"}#z$}$z%}'z'}'z(}(y)|)y*}*z+},{,|-|-|-}.|.~.|.~/|/€.|-€-}-~,},~,}+}*}*}*~*})~)})~)})~)})~)|)~)|)~)})~)}(~({(~({((|((|'~'z'~'z'~'z(~(z(~(z(~(z((y))z((|''|((}((})~)})~)~)}))}))})€)})€)|))|))|)€)|)€)|))|)(|))|*)~**~++,-.,~-.~.~0~0~1~1~1~12~22~2~2~2~1~21~11}1€1}1€1}10|//|/‚/|/‚.|.‚.|-‚.|.ƒ.|.ƒ-{-ƒ-{-ƒ-z.„.z-„.{.ƒ.{.ƒ/{/‚/{/‚.|.„.|.„.|.„-|-‚-|-ƒ,},ƒ./ƒDm†L{B@zEQ}†¨z­Šªr©‹ t Š¡t¡‹žu¬Š¯v¯Š¥tŠ u¢Š›v‘Ž’s“pv“noa—eon–inh“fte‹p|€…‚‚l„`|nˆvz}Œƒx„‹ywT.{4>~D’>€?•@?”@~@“I}E“DzM”>w:—AvF›OvI˜xx‡}yvˆszy‡zw‚ŠƒrutrtŽpyW‰a€}|wƒmv{„y”†•}˜‚˜€Ž}UˆBwE–doœšl—˜‹ir—…h—~lA™7r7š9u9š8x4™3x2”/w0”.rG”{g‹˜y_p™t]pšo]m™`\p—`s—[j:”Fo@“:q;“;s;“:q8“=r]’\rP‘StA^sjŽnrxŽ‰qŠƒm~’hz•zd}–|dn–_d_”^ii”wjt”mme“arYXxU‹Q|K†G~D„B~>ƒ2'#~"ƒ$}#„*{1‡.z.ˆ+{*†+}Kx^_ha€Th\|Ynaz`km|{hu~sny€zvz~r„j‚T†5ƒ04€>}?~=y9z7w8x>wQxlwkxby]y]wc{svi[sN‚JuFOzP‚I}?ƒ=}@„>yA„[wx}‚|r{l€kzi‚h~gf|fh|jl|mm{nk{c\|VƒN|LI}K€O~S€T}V€V|WW|XX~YY€YY€ZZY€WU‚P€H‚B€=ƒ:€8‚7€:‚::=CI‚P|X„ayk‡vv~Š‚s†‹ŠsŠ‹‡rƒ‹s{‰vuv‰wuy‰}vŠ…u‰Š‹t‹ŠŠu‰ˆ‡wˆˆˆw†Š‡x…€…z‚p€}gƒ€‚dƒƒi„ƒs~z‚wzsˆrxr‰swrŠrwr‰qwpˆpym‰e|^‡X~N…C~?†CzJ‡VuaŠlqyŒ„q‹ŠŽt†ŽwŒ„‰z‡„†{†„†y‡„ˆx‰„ŠxŠ„Š{†„~€tƒg\…T}W‡^vgˆqtvˆvytƒr}om~lmmmmm~mm}o‚m{o}‚|“t•€kŽ„Œl‹„ŠnŠ‚‰pˆƒˆr‹‹t‹~‹uŽ~w~xŽ~{ÿÿÿÿÿ ÿ ÿ ÿ!ÿ!ÿ"ÿ"ÿ#ÿ#ÿ#ÿ#ÿ"ÿ#ÿ%ÿ%ÿ&ÿ&ÿ'ÿ(ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ)ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ.ÿ.ÿ-ÿ/ÿ/ÿ/ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ2ÿ4ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ.ÿ/ÿ=ÿTÿGÿCÿDÿTÿtÿ›ÿ³ÿ²ÿ£ÿ¦ÿ¨ÿ¢ÿ¤ÿ¥ÿ£ÿªÿ§ÿ¥ÿžÿ—ÿ˜ÿ¢ÿ¡ÿ•ÿ“ÿÿŠÿqÿdÿcÿdÿiÿmÿgÿfÿkÿuÿyÿtÿ|ÿ`ÿkÿvÿ}ÿzÿ|ÿjÿ<ÿ,ÿ2ÿ9ÿ?ÿ>ÿ?ÿ@ÿ?ÿ?ÿ?ÿBÿFÿHÿJÿAÿ<ÿ>ÿAÿJÿMÿLÿyÿ‡ÿ}ÿuÿvÿzÿÿ†ÿˆÿxÿlÿ[ÿVÿ\ÿwÿ€ÿ†ÿÿ‘ÿŒÿ‹ÿÿ“ÿÿcÿCÿCÿOÿŒÿ ÿ¡ÿ–ÿ^ÿ‚ÿÿÿ?ÿ7ÿ8ÿ9ÿ7ÿ7ÿ7ÿ4ÿ4ÿ5ÿ6ÿ6ÿMÿ”ÿÿ‰ÿ~ÿqÿÿŸÿ—ÿ•ÿ†ÿÿ~ÿ€ÿPÿ;ÿDÿDÿ<ÿ;ÿ:ÿ<ÿ<ÿ;ÿSÿ\ÿNÿWÿeÿqÿsÿÿ‰ÿˆÿ„ÿ€ÿÿƒÿƒÿ…ÿxÿgÿ`ÿ_ÿ^ÿ`ÿbÿbÿ_ÿaÿbÿ]ÿXÿUÿSÿRÿOÿMÿHÿFÿ<ÿ)ÿ$ÿ#ÿ%ÿ$ÿ%ÿ.ÿ2ÿ/ÿ.ÿ+ÿ+ÿ2ÿNÿ\ÿ_ÿWÿGÿQÿ[ÿ]ÿ]ÿaÿmÿzÿyÿwÿwÿuÿrÿhÿiÿjÿZÿJÿFÿ>ÿ.ÿ6ÿ;ÿ5ÿ+ÿ*ÿ4ÿTÿ‚ÿŠÿÿ‹ÿ‰ÿ~ÿgÿ]ÿSÿNÿPÿVÿNÿLÿMÿ>ÿ8ÿ=ÿ>ÿEÿZÿƒÿ~ÿsÿiÿgÿeÿfÿhÿfÿiÿjÿkÿmÿmÿmÿjÿfÿ_ÿXÿPÿMÿKÿMÿQÿSÿTÿVÿVÿVÿVÿXÿXÿYÿYÿYÿYÿZÿZÿXÿVÿSÿNÿGÿAÿ=ÿ:ÿ:ÿ9ÿ:ÿ:ÿ;ÿ>ÿDÿJÿQÿYÿcÿmÿwÿ~ÿ„ÿˆÿ‰ÿ‰ÿ‡ÿƒÿ}ÿyÿvÿvÿwÿyÿ}ÿÿ…ÿ‰ÿ‹ÿ‹ÿŠÿ‰ÿ‡ÿˆÿˆÿ†ÿ†ÿ„ÿ…ÿÿ€ÿÿƒÿÿÿƒÿ‚ÿÿÿ{ÿuÿtÿsÿsÿtÿtÿsÿsÿrÿqÿpÿmÿeÿ^ÿVÿLÿCÿ?ÿDÿKÿXÿcÿnÿ{ÿ…ÿŒÿŽÿÿŽÿŒÿ‰ÿ‡ÿ†ÿ†ÿ†ÿ‡ÿˆÿ‰ÿŠÿŠÿˆÿ„ÿ|ÿrÿfÿZÿSÿYÿ`ÿkÿsÿwÿvÿtÿpÿpÿoÿnÿlÿmÿnÿnÿlÿlÿmÿmÿrÿŠÿ–ÿ’ÿŒÿŒÿŠÿ‰ÿˆÿŠÿ‹ÿ‰ÿ‡ÿ‡ÿ‡ÿ‰ÿÿŽÿÿŽÿŽÿÿÿŒÿx  x  x!"x""y""y#~#y$~$y$}$z'}'z(}'z(}(z(}*z*}*z+}+z,|,{-|-|.|.}.|.}.|.}-|-~.|.~,},~+}+~,}+}+}+}*~*})~)})~)}*~*})~)|)~)|)~)|)~)|)~){(~({(~({(~({(~(z(~(z'~&z(~(z(~(z'~'z((y((z((|''|'(}('}(~(})~)~)})€)})€)}))}))|)‚)|)‚*|*‚*|*‚*|*‚)|(‚*|*‚*|*‚,~-,~--~..~/0~0}/~.}0~0}2~0|1~1}3~3}3~3~2~2~2~43~33}3€3}3€1}1‚0|0‚0|0‚0|/‚.|.ƒ/|/ƒ.|.ƒ-|-ƒ.{-ƒ-{-ƒ-{.ƒ.{-ƒ.{.ƒ.{.ƒ.{.„.{.„.{.„.{.„.{.„.{.‚-z-ƒ-{+ƒ/|/ƒ>zF‰FwC‘Nxq„z¦Š²w±‡˜rš‹­u®Š±z²†¶|·ƒ¯|¬ˆ£™‚’˜€z–†r~vni—[oo›jodšenh”nnvzt‚ˆ‚}{‚j}f‡rx{{vx’_v1’,z01}:“CD•D€A”>~=“<}9“C{J”Hx:—:w=šHwMšCxR”†y„Ž|yyŠ|x|‹ƒvŽ‹‰vkŒHzQˆT€k€€„’t…˜p“…v‹ƒˆ~}mˆJxC“Dqvœ™n š¡jŒ—si“švm?œ:s;œ;v<›>x?š@v>˜CvB—2pV™c• T •‡O}ž“O—Ÿ˜M„oWpkdU™2o7•?rD”?s;”>DL‚Q|Z„ewoˆyt~Œ„r‡‡r†…rŒ|sw‹vuvŠxu{Š}uŠ†tŠŠ‹t‹ŠŠu‰ˆ‡wˆ‡†w†ˆ…x†……x‚y€|j€}€c‚ƒe€‚n|zw|s„tyr‰rwrŠsvsŠswp‰pwm‰d{]‡T~J…C~A†EzNˆZueŒrq}ŒˆqŠt†wŒ„Šz‰„ˆ{‡„‡yˆ„ˆx‰„‹x‹„‡{ƒ„|~q„c€X„S{Zˆbvk‹stuŠuys„p}n€n~m€n~l€mmk}ml{v{}˜p’k‰‚ŠmŠƒ‹oŠƒ‹sˆ‚†v…†yŠŒ{~|Ž~Ž~~Ž~Œ€ ÿ ÿ ÿ ÿ ÿ ÿ!ÿ"ÿ"ÿ"ÿ"ÿ"ÿ#ÿ#ÿ%ÿ%ÿ%ÿ%ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ*ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ,ÿ+ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ&ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ/ÿ0ÿ0ÿ1ÿ2ÿ3ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ5ÿ5ÿ3ÿ3ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ.ÿ.ÿ.ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ+ÿ.ÿ0ÿ9ÿAÿEÿOÿpÿ†ÿÿ°ÿµÿµÿžÿŒÿªÿ¯ÿ³ÿµÿ¹ÿ¸ÿ³ÿ­ÿ ÿ˜ÿ’ÿ•ÿšÿ—ÿ”ÿ{ÿzÿjÿbÿiÿ`ÿaÿcÿsÿtÿ|ÿ€ÿ†ÿ‘ÿÿ~ÿRÿbÿpÿxÿuÿXÿ,ÿ(ÿ+ÿ1ÿ7ÿ<ÿ@ÿAÿBÿ@ÿ>ÿ>ÿ<ÿ;ÿAÿAÿ<ÿ=ÿ;ÿAÿKÿHÿ9ÿZÿŠÿ†ÿ}ÿ{ÿ{ÿ€ÿ†ÿ‹ÿÿEÿIÿVÿ_ÿxÿŠÿ“ÿ™ÿšÿ—ÿ•ÿ‰ÿtÿqÿQÿDÿEÿWÿˆÿ™ÿÿ–ÿvÿ‚ÿJÿBÿBÿCÿDÿFÿDÿCÿ@ÿ=ÿ=ÿ9ÿ2ÿnÿ©ÿ¥ÿ¢ÿ›ÿÿ~ÿˆÿ…ÿ|ÿyÿgÿ[ÿIÿAÿ7ÿ5ÿ4ÿ8ÿ?ÿ@ÿ;ÿ<ÿ:ÿCÿaÿhÿkÿqÿvÿ„ÿ…ÿ‡ÿˆÿŒÿ’ÿ•ÿ€ÿeÿ`ÿgÿhÿjÿjÿhÿdÿdÿ^ÿMÿ;ÿ=ÿLÿRÿPÿPÿQÿPÿMÿIÿ>ÿ*ÿ"ÿ"ÿ%ÿ'ÿ*ÿ0ÿ5ÿ6ÿ4ÿ2ÿ5ÿEÿQÿPÿRÿFÿ@ÿFÿHÿPÿ[ÿ\ÿ_ÿ[ÿbÿnÿhÿfÿcÿXÿQÿRÿYÿcÿiÿKÿ3ÿ0ÿ1ÿ.ÿ/ÿ2ÿ/ÿ,ÿ-ÿ8ÿoÿ„ÿ€ÿÿ€ÿrÿvÿÿ„ÿ†ÿ†ÿ…ÿxÿMÿ=ÿFÿBÿ?ÿCÿEÿLÿbÿsÿxÿmÿjÿgÿiÿgÿhÿjÿlÿnÿnÿoÿjÿgÿ`ÿYÿRÿOÿMÿNÿQÿTÿUÿVÿVÿVÿVÿVÿXÿZÿZÿ[ÿ\ÿ\ÿZÿYÿVÿSÿMÿFÿ@ÿ<ÿ:ÿ;ÿ;ÿ:ÿ<ÿ@ÿCÿHÿMÿTÿ\ÿgÿpÿzÿÿ„ÿ‡ÿ‡ÿ†ÿƒÿÿ|ÿwÿwÿwÿxÿ{ÿ}ÿÿ†ÿŠÿ‹ÿ‹ÿ‰ÿˆÿ‡ÿˆÿ†ÿ†ÿ‡ÿ†ÿ…ÿ‚ÿ€ÿÿ€ÿ€ÿÿÿ‚ÿ‚ÿ€ÿ}ÿxÿtÿsÿsÿsÿrÿtÿtÿsÿqÿpÿmÿdÿ]ÿSÿGÿAÿAÿGÿPÿ^ÿjÿuÿ€ÿ‰ÿŽÿÿÿÿŒÿŠÿ‰ÿ‰ÿˆÿ‡ÿˆÿˆÿ‰ÿŠÿŠÿ‡ÿƒÿzÿnÿ`ÿUÿVÿ\ÿdÿmÿsÿuÿuÿsÿpÿnÿmÿlÿmÿlÿlÿkÿnÿgÿwÿÿ—ÿ‘ÿÿŒÿŠÿŠÿ‹ÿ‹ÿŠÿŠÿ‡ÿ„ÿ‡ÿŠÿŽÿÿÿÿŽÿŽÿÿÿŽÿ‹ÿ"€!x"€"x!!x""x#~#x#~#x#~#y$~$y$}$y'}'y'}'z(}(z(}*{*}*{+}+|,|,|-|-}.|.}.|.~.|.~-|-},|,~,},,},,|+~+~+~*~)})~)})~)|*~*|)~)|)~)|*~*|*~*|)~){)~){(~(z(~(z(~({(~(y'~&z'~'z'~'z(~(z)~){(~(|(~({'~'{'~'|'~'|(~(|)~)})~)€)|))}))}))})‚){)‚*|*‚*|*‚*|*‚+|*‚+|+‚,|.,}--}..}/~/}0|1~1{1~1{1~1|2~3{3~2{4~4{3~3|4~4|4~4~4~4€3}3€3|3€2}2‚1|1‚0|0‚0|0‚0|0ƒ/|/ƒ.|.ƒ.|.ƒ.{.ƒ.{.ƒ.z.….z.….{.ƒ.{.ƒ.{.ƒ.{.ƒ.{.„.{.„.{.„-{-„-{-ƒ,|,ƒ,}/…’Cs?’rLRsQRuSRwJ‰7z&‡"{"†'{+†,y3ˆ6x5‰9x:‰@zK‡R|OD~7{<}A|I|P{T}WwW|]s`{cqY}\t\€H|=‚7ƒ2†:„G‰W€OŠ6|/…/x21w/|.s.zIn_wgqxz{vy}{v|~t‚€pwkmobqT€>xCHE|EGuEPrP}Sr^|cvg}g|h}k|l|l}m}o~l}ga|]ƒT{O€M}NR}UV}VV}U€WY€Y€YZ€\]€\\€Z€U€R‚L€FƒA>ƒ==ƒ=<‚>€BG€L€P‚W|`…ivr‰zt€Œ„q‡‡q†ƒr~ysv‹xtxŠyt|Š}tŠ†s‰Š‹t‹‰ˆv‡‰†v‡‰†v†Š†w‡‡…w€z€p€|g|c}j€}xx~t‚qys‰rwsŠtut‹rvqŠlxj‰d{]†QE…?~B†HyT‰`rkŒxp‚ŒŠqŠu†Žx‹„Šy‰„ˆyˆ„ˆy‰…‰yˆ…‰x‰…†{‚ƒxm„^V„U{]‰fuoŒttu‰syq…p}n€n}m€j~i‚k}jh|~v–—l‘‚jŠ„‹lŒ„Œp‰ƒ‰s‰ƒ‰v‰‹zŽ}~~}}}€Œ}‰€"ÿ!ÿ!ÿ!ÿ"ÿ"ÿ"ÿ"ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ*ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ*ÿ+ÿ+ÿ,ÿ-ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ2ÿ3ÿ2ÿ3ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ/ÿ?ÿQÿoÿ†ÿ‰ÿ˜ÿ®ÿ¦ÿ¥ÿ²ÿÿpÿ~ÿÿ¦ÿŸÿšÿ¤ÿ¬ÿ©ÿ ÿ™ÿŽÿ…ÿÿ“ÿŠÿˆÿzÿsÿlÿjÿcÿkÿqÿuÿtÿ}ÿ…ÿŒÿŸÿ©ÿ­ÿnÿJÿeÿjÿhÿLÿ*ÿ+ÿ&ÿ,ÿ.ÿ>ÿMÿKÿJÿBÿAÿAÿ?ÿ<ÿ;ÿ>ÿ;ÿ;ÿ=ÿ>ÿDÿHÿFÿFÿwÿÿÿˆÿ~ÿ{ÿ{ÿ}ÿƒÿÿuÿJÿRÿvÿ‡ÿ…ÿ„ÿ€ÿÿ‡ÿÿŽÿyÿrÿNÿFÿCÿKÿsÿÿÿrÿLÿEÿ@ÿ?ÿ=ÿ>ÿ=ÿ<ÿ:ÿ:ÿ5ÿGÿdÿXÿÿ©ÿ›ÿ—ÿƒÿ~ÿ‚ÿ€ÿfÿ[ÿPÿMÿMÿCÿ6ÿ6ÿ4ÿ3ÿ1ÿ2ÿ6ÿ>ÿIÿHÿBÿEÿ`ÿoÿsÿxÿ€ÿˆÿÿ–ÿ“ÿÿsÿqÿkÿkÿjÿkÿlÿpÿsÿpÿjÿXÿ9ÿ.ÿ/ÿ3ÿBÿOÿQÿRÿRÿPÿAÿ-ÿ%ÿ$ÿ'ÿ+ÿ-ÿ0ÿ;ÿ?ÿBÿAÿBÿFÿGÿHÿLÿBÿ8ÿ9ÿ>ÿHÿOÿQÿNÿVÿYÿYÿXÿOÿCÿ3ÿ,ÿ.ÿ1ÿ3ÿ3ÿ4ÿBÿOÿOÿ:ÿ7ÿ6ÿ1ÿ8ÿ;ÿ6ÿNÿ=ÿ<ÿSÿpÿuÿyÿzÿÿuÿbÿ]ÿkÿqÿqÿ…ÿmÿ>ÿLÿEÿ>ÿHÿHÿKÿMÿNÿQÿUÿVÿ_ÿdÿiÿlÿnÿnÿnÿkÿgÿbÿ_ÿVÿQÿOÿOÿSÿVÿVÿVÿVÿUÿWÿYÿYÿYÿZÿ\ÿ]ÿ]ÿ]ÿYÿWÿRÿLÿFÿAÿ@ÿ?ÿ>ÿ?ÿ@ÿBÿEÿJÿNÿRÿ[ÿbÿkÿtÿ{ÿ€ÿ‚ÿ…ÿ…ÿ„ÿÿ}ÿyÿvÿwÿwÿyÿ|ÿÿƒÿ‡ÿŠÿŠÿŠÿˆÿ‡ÿ†ÿ‡ÿ†ÿ†ÿ‡ÿ‡ÿ…ÿ„ÿÿ€ÿÿ~ÿ€ÿ€ÿÿ€ÿÿ~ÿyÿsÿsÿrÿsÿtÿsÿsÿsÿrÿnÿhÿaÿZÿQÿEÿ?ÿBÿKÿXÿcÿnÿyÿ„ÿŠÿÿÿÿŽÿ‹ÿ‰ÿˆÿˆÿˆÿˆÿ‰ÿŠÿ‰ÿˆÿˆÿ…ÿÿwÿlÿ\ÿSÿWÿ_ÿhÿpÿtÿuÿsÿqÿpÿnÿlÿkÿmÿjÿiÿlÿ‚ÿ–ÿ–ÿÿÿ‹ÿ‹ÿÿÿÿ‰ÿ‰ÿ‰ÿŠÿ‹ÿÿ‘ÿÿÿÿÿÿÿÿÿÿŒÿ‰ÿ"~"x#~#x##x"#x%~%x$~$x$~$y$~%y%}%y'}'y'}'z(}(z(}*{*}*{+}+|,|,|-|-}-|-~.|.~.|.~-|-},|,~-}-,},,|+~+~+~*~)})~)}*~*|*~*|*~*|)~)|*~*|)~)|)~){)~({(~(|(~(|'~'{'~'y'~&z(~(z'~'z'~'z'~({(~(|(~(|'~'|'~&|'~'|(~(|)~)})~)~)|)€)})*}**}*‚*{*‚*|*‚*|*‚*|+‚*|*‚*|,‚-|--}..}/~/}0~0}1|0~0|1~1|1~3{2~4z5~5{6~6{5~4|4~4|6~6~5~4€4}4€3|3€2}2‚2|2‚1|1‚1|0‚0|0ƒ/|/ƒ.|.ƒ.|.ƒ.{.ƒ.z.ƒ.{.ƒ.{-ƒ.{-ƒ-{-ƒ.{.ƒ.{.ƒ.{.„-{-„,{,„,{,„-{-ƒ-|-ƒ,|0…Cxe‹€pŽ‹n¡Ž©qšŒp«Œ—lpŽrm‘Œ¥r®Š®s¬‰¬s§‰žw˜…{„‡z“ƒŽt‚Œ€qƒ”}qp–iqf•nor“so{‘ƒq„‹“{©‚®€ŠGy_‰^v]’Gw.‘.{3‹.|-6}G:}>“F~C•B}@–<|>—={<˜u==u=ž;v:œ8t1˜4tZ–jp—¥k“”a€šy\vm[eŸWbLœChB™Am7–4q3’3r4:pC‘KnS’SmS•EnG–OnU—Xk\˜si…—‡g|–€e€–zcq–mcl–ocp—vcw–qgi”Sn4‘-t0Ž0t5HtRTuQHx8Œ-y*Š+z,ˆ.y2‰>wIŠMuN‹OtO‹NwGŠEzF†:}8ƒ:}>€B}J{OO{T~S{I@~-€+‚A…`ƒzˆ›…£}¦„¥{¥„£{”‡~wkˆ]rW‡Fl2ƒ=k@{5n?yYso{suy|zuj\sayqŒ~Žq‡†tJ€C{G€~D†MxZ‰erqŒ}p…Œ‹qŠŽu†xŒ„Šy‰„‰y‡„ˆyˆ…‰y‰…ˆx‡…„{~ƒtg…Z~S‡Wy_Šisr‹tuu‰szp…o}l‚l}k‚l}j€j|„w—•l‘‚ŽkŒ„mƒŽoŒƒŒs‰‚‰w‰€{‘~‘}~}}€}€}Œ}‰#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ%ÿ%ÿ$ÿ$ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ*ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ*ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ)ÿ'ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ*ÿ*ÿ*ÿ,ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ6ÿ6ÿ5ÿ4ÿ4ÿ4ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ.ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ,ÿ0ÿJÿxÿ‘ÿ™ÿ“ÿÿ’ÿ‘ÿ™ÿ£ÿœÿˆÿˆÿÿ©ÿ²ÿ¶ÿªÿÿ˜ÿÿ˜ÿŽÿ„ÿ„ÿÿŠÿsÿ{ÿxÿtÿvÿsÿiÿqÿwÿ|ÿ„ÿŒÿŒÿ—ÿ©ÿ°ÿ£ÿRÿUÿWÿVÿ@ÿ*ÿ+ÿAÿCÿ2ÿ0ÿ0ÿ3ÿ7ÿDÿAÿ@ÿ@ÿ=ÿ?ÿ<ÿ=ÿ=ÿ;ÿ;ÿ?ÿDÿJÿGÿ{ÿvÿjÿÿŽÿ†ÿÿ|ÿzÿÿ†ÿˆÿ`ÿRÿfÿmÿcÿ^ÿ^ÿ^ÿkÿ‚ÿÿ‰ÿ~ÿHÿ?ÿCÿAÿAÿBÿGÿHÿDÿBÿ?ÿ=ÿ>ÿ;ÿ9ÿ5ÿ2ÿ2ÿ5ÿTÿdÿ‚ÿ¥ÿŸÿ€ÿ|ÿvÿiÿ_ÿUÿTÿLÿDÿ?ÿ8ÿ3ÿ4ÿ;ÿ?ÿGÿRÿVÿYÿ[ÿXÿYÿYÿMÿIÿGÿIÿOÿUÿ]ÿiÿzÿŠÿŒÿ†ÿxÿiÿhÿrÿyÿ~ÿÿzÿmÿFÿ.ÿ,ÿ0ÿ3ÿ1ÿ7ÿIÿOÿIÿ<ÿ/ÿ.ÿ-ÿ.ÿ1ÿ2ÿGÿYÿ[ÿZÿ]ÿ^ÿYÿTÿOÿGÿ<ÿ7ÿ6ÿ<ÿAÿFÿJÿMÿNÿKÿ=ÿ2ÿ)ÿ7ÿXÿyÿÿ¦ÿ¶ÿ»ÿ»ÿ»ÿ»ÿºÿ»ÿºÿµÿ®ÿªÿ™ÿhÿ/ÿ1ÿ5ÿ4ÿ0ÿ?ÿXÿkÿuÿ}ÿ€ÿ{ÿwÿ‚ÿ†ÿ‚ÿsÿ|ÿmÿbÿAÿJÿEÿHÿMÿNÿnÿuÿhÿbÿdÿ[ÿVÿVÿWÿ]ÿ]ÿdÿdÿdÿaÿ[ÿUÿTÿSÿUÿVÿUÿVÿVÿTÿUÿXÿYÿYÿZÿ[ÿ]ÿ\ÿ\ÿXÿUÿQÿLÿGÿCÿBÿAÿBÿBÿDÿFÿKÿOÿTÿXÿ_ÿfÿoÿvÿ|ÿÿ‚ÿ„ÿƒÿ€ÿ}ÿyÿvÿuÿwÿxÿ{ÿ|ÿÿ…ÿ‰ÿ‹ÿ‰ÿ‰ÿ‡ÿ†ÿ†ÿ‡ÿ‡ÿ‡ÿ†ÿ†ÿ†ÿ…ÿƒÿ€ÿÿ~ÿ~ÿÿ~ÿ~ÿ~ÿÿzÿuÿqÿqÿrÿsÿsÿsÿsÿpÿlÿfÿ^ÿVÿMÿCÿ@ÿEÿQÿ]ÿgÿsÿ~ÿ†ÿ‹ÿÿŽÿÿŽÿ‹ÿŠÿ‰ÿˆÿˆÿˆÿˆÿ‰ÿ‰ÿ‰ÿ‡ÿƒÿ|ÿsÿfÿYÿTÿ[ÿcÿkÿrÿrÿtÿrÿoÿpÿmÿkÿjÿkÿkÿ‰ÿ˜ÿ”ÿÿ‹ÿ‹ÿŒÿÿŒÿ‹ÿ‹ÿˆÿˆÿ‰ÿÿ‘ÿ’ÿÿŽÿÿÿÿŽÿŽÿÿÿÿÿŒÿ‰ÿ$~#w"~#w%~%w$~%w&~&w%~%w%~%x%~&x'~'y(~(y(~(z(~(z)}+z+}+z,},{,},|.}.~.}.~.}.~.}-~,},,},,~+~+~+~+~++~+}*}*}*}*}*~*}*~*}*~*|)~)|*~*}*~*{)~)z)~)y(~(z(~(z'~'z'~'z&&{&&{&&z&&z''z''z''z''{&&|((|(~(}(~(~)~)~)}))}))}**|**|*+|++|++|+,|,€,|,€-|-/}//}/~1}1}2}2{3}3{4}3{3}4{4}4{45{66|66}66}5~6~6~6~5}54}4€3|32|21|01|0‚0{0ƒ0|/ƒ/}.ƒ-}/ƒ+|.ƒ-|-ƒ.|-ƒ-|-ƒ-|-ƒ,|,ƒ,|,ƒ,|,ƒ,{,„,{,„,{,„,{,ƒ-|-‚-}-‚+|0…[w†Œ—o›’žh“‰hŽk“–lšo §s¬Ž­q¨‘o™Ÿr‹‘y‰…Š{ƒurˆwpvwrz“{tr’xs}q„Šuƒp|…®¥‚X|NˆPwJŽ5x(Ž*|7‹C|/Œ-|0Ž0|07~C•E~=•A|>—<|;—;{9—;z@˜EwK˜Hv|•~us‘jt„ŠtƒŽ}vwzw‹ˆwƒŠZ{_‡d{[€\{]|\}eyw‚y‡~o€?‰@zA•@w?›BvFŸDvBž>u=ž;t98u4š0t0˜;rU˜bn|˜i›–|fp˜sbk›XcUšPhE™9o9•7p<”CoJ”OlT”VjY•Xg[–]f\•`iZ•JmD•DqK•Sp\–im|•h–—ew˜a_ZšuZ…›ˆ[ŠšŠdp˜6p-’0v2Ž6x62x8ŽDxAŽ1x,Ž0x22v1„Z‚z…Ž‚¤ƒ»¾€¾~¿€¾~¾€¾}¾½º|·´q°…ªe}Œ9a(…0m2~4p?zMsd{tvw|x‚}ƒy„~|w|‰uŠ€‡vL€H|H€O„SP‡Z~ƒt€kzjixe}`y]vYzYqZ|]p^|]uYzWzU{U}U{UUU€UVW€W€X€Z~Z€[\\~[Y€US‚M~I…E|D…D{EƒE|G‚J|NRV}[byh„puuˆ}r€ƒp„‚q{swusv‹vsxŒ{s~Œ‚s†‹‹tŒ‹‹vŠŠˆv‡Š‡vˆŠ†u†Š‡u‡Š†u†‰„v„x}y{{{p}}}n~€{vz€vs|sˆtwtŒsvs‹qvoŠjxd‰^}U†KC„B}I‡Tv_‰jqwŽ‚q‡ŒŒr‰w†xŒ„ˆx‰…‡x‡…ˆwˆ†ˆxˆ†ˆx‡†‚{{…re…WS‡[zb‰ktr‹vtvˆqzo…l~kƒi~hk}‰x—€“n‚‹lŠ„ŒnŽ„‹pŠƒ‰r‰ƒ†vˆ‹z‘~‘’}‘~Œ~|Ž|}€Œ}ˆ€&ÿ$ÿ#ÿ$ÿ%ÿ%ÿ$ÿ%ÿ&ÿ&ÿ%ÿ%ÿ%ÿ&ÿ%ÿ&ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ/ÿ/ÿ/ÿ/ÿ1ÿ1ÿ2ÿ2ÿ3ÿ3ÿ4ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ6ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ2ÿ2ÿ1ÿ0ÿ1ÿ0ÿ0ÿ/ÿ0ÿ/ÿ/ÿ.ÿ/ÿ.ÿ8ÿ/ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ+ÿ+ÿ2ÿgÿ„ÿÿÿ ÿœÿ™ÿ™ÿ‘ÿ‘ÿŒÿšÿ£ÿ•ÿ“ÿ¦ÿ¤ÿ¥ÿ¡ÿ£ÿ’ÿŽÿ“ÿŒÿ‰ÿ‹ÿ”ÿ‚ÿyÿyÿ|ÿÿ~ÿyÿwÿtÿvÿ|ÿ{ÿaÿ3ÿÿ›ÿˆÿLÿPÿKÿEÿ/ÿ'ÿ(ÿ,ÿ.ÿ,ÿ,ÿ+ÿ.ÿ1ÿ6ÿ;ÿFÿAÿ?ÿCÿ=ÿ?ÿ<ÿ<ÿ;ÿ<ÿAÿFÿEÿyÿ€ÿsÿmÿoÿÿŒÿ‚ÿ|ÿ{ÿ~ÿ„ÿˆÿrÿSÿZÿRÿUÿWÿYÿfÿvÿ€ÿ€ÿ†ÿ‚ÿUÿ?ÿ?ÿ=ÿ@ÿCÿCÿCÿCÿ=ÿ=ÿ8ÿ6ÿ4ÿ1ÿ0ÿ0ÿ=ÿRÿ_ÿyÿÿÿ{ÿpÿlÿjÿ]ÿJÿJÿAÿ4ÿ5ÿ7ÿ@ÿJÿNÿSÿTÿYÿZÿ[ÿ\ÿ^ÿ_ÿbÿbÿOÿEÿFÿLÿRÿ[ÿiÿ{ÿÿ™ÿšÿ{ÿbÿmÿŒÿ˜ÿ—ÿ˜ÿŽÿ\ÿ-ÿ-ÿ1ÿ3ÿ7ÿ7ÿ5ÿ3ÿ5ÿ3ÿ+ÿ+ÿ1ÿ4ÿ4ÿ9ÿTÿ_ÿeÿmÿxÿ|ÿuÿeÿYÿJÿ>ÿ6ÿ4ÿ@ÿMÿOÿNÿHÿ6ÿ.ÿ,ÿ=ÿHÿWÿvÿŒÿ¦ÿ¹ÿ¾ÿ¾ÿ¿ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¼ÿºÿ·ÿ³ÿ°ÿ®ÿ¥ÿ„ÿ8ÿ.ÿ4ÿ3ÿ4ÿ8ÿUÿhÿgÿ~ÿÿzÿ}ÿ~ÿ‚ÿ‡ÿŒÿ{ÿhÿGÿDÿNÿNÿNÿOÿyÿxÿkÿhÿjÿhÿgÿoÿjÿdÿ_ÿ]ÿ[ÿ\ÿYÿWÿXÿYÿWÿUÿVÿVÿUÿVÿXÿYÿZÿ[ÿ\ÿ]ÿ]ÿ\ÿZÿXÿUÿRÿNÿIÿGÿGÿGÿHÿHÿJÿNÿRÿVÿZÿ^ÿdÿlÿrÿwÿ}ÿ€ÿ„ÿƒÿÿ}ÿzÿuÿuÿvÿxÿzÿ|ÿÿƒÿ‡ÿ‹ÿŒÿ‹ÿŠÿˆÿ‡ÿ‡ÿˆÿ‡ÿ‡ÿ‡ÿ‡ÿ†ÿ†ÿ†ÿ‚ÿ€ÿ}ÿ|ÿ}ÿ~ÿ~ÿ}ÿ}ÿ{ÿwÿsÿsÿtÿtÿsÿsÿqÿoÿjÿdÿ]ÿTÿKÿCÿCÿJÿVÿbÿkÿyÿƒÿŠÿŒÿÿŽÿŽÿ‹ÿŠÿ‰ÿ‡ÿ‡ÿ‡ÿˆÿˆÿˆÿˆÿˆÿ‡ÿ‚ÿzÿpÿcÿYÿTÿ[ÿdÿmÿtÿuÿsÿpÿnÿmÿkÿiÿjÿŒÿ™ÿ’ÿÿŠÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿŠÿˆÿ„ÿ„ÿˆÿÿ“ÿ“ÿ‘ÿÿÿÿÿÿÿŽÿÿÿÿÿŒÿˆÿ(~&w$~%w%~%w%~%w&~&w&~&w'~'x'~'x(~(y)~)y)~)z*~+z*}*z+}+z,},z,},{,},|,},|-}-~-},~+}+,},~,},+}++~+*~*}*}*}*}*}*~*}*~*}*~*|)~)|)~)})~){(~(z)~)y(~(z(~(z'~'z'~'z&&z&&z&&z&&z''z''z''z''{&&|((|(~(}(~(~)~)€)}))}))}**|*+|++|+,|,,|,,|,€-|-€-|-/}/0}0~0}2}2}2{3}3{3}3{3}4z4}4z55{56|55}66}6~6~5~5~5}55}5€3|32|2‚1|10|/‚/{.ƒ.|.ƒ.}.ƒ/}MƒK|0ƒ-|-ƒ-|-ƒ,|,ƒ,|,ƒ,|,ƒ+|+ƒ+|+ƒ+}+„+}+„+}+„+}+ƒ,|+‚,}*‚+}5†hx{ŽŒp ’¢k¡Ÿk›‘šj›‘‰kŽ‘¢m‘…p ‘§m¦¢mœp‰Ž’vˆŒ|‰ƒŽy„…xtvŒ{s}|vyvvoktojwW;|]ˆd~N†M|PˆNy@‹+z'Š)|+‰-|/‹;|1Œ.|02{7“J|D–=}=—>|@˜>z?˜;y;˜?w@™Dvj•tx’ssn‘ut‘‹u€|v|Œv‚Œ…x^ˆX|WT|T}[}jz~}‚x~x‡{„M|>‹@yA“BxC˜GwH˜=v:˜8w4™3w1—.w0•€XtV}owu|{v~|u‚~„s{cq†€nv>€H€GO‡Po‡~€tƒm‚lh‚d…ed‡b€b‡_z[„WtU€VqW~XoX{VtVzVwU|UzW{X{Y}^|`}]~[|Z}Z|Y€XzSNzJ„IxH…HwJƒKxM‚Q{RV}\|`€fxm„utzˆqŽ„pq{vstusv‹wsyŒ}sŒ…s‰‹‹tŒ‹Šv‰Š‡v†Š‡u‡Š‡t†Š‡t†Š†t…‰…u„‡ƒw€€}z}v{|}p~€}tz€wu|sˆswsŒsvs‹qvnŠixc‰Z}P†I~D„G{O‡[uc‹qp{Ž„oŠŒŒsŽ‰Žw†‹x‰„‰xˆ…ˆxˆ…ˆwˆ‡‡wˆ‡‡y†‡€{x†m~b…V~V†]xf‰ntuŠwvs‰qzo…l|m€l}‹y—€“mŽƒ‹k‰ƒŠpŠƒŠrŠƒ‡u†‚„y‚~~|‹{““y‘‚{|Œ|Œ|Ž|‘}Ž‚‹}‡‚'ÿ&ÿ$ÿ%ÿ%ÿ%ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ*ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ,ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ&ÿ&ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ/ÿ/ÿ0ÿ0ÿ0ÿ2ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ6ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ6ÿ6ÿ5ÿ5ÿ3ÿ3ÿ2ÿ2ÿ1ÿ1ÿ0ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ,ÿ)ÿ,ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ+ÿ+ÿ+ÿ+ÿ6ÿfÿ{ÿ‘ÿ£ÿ£ÿ›ÿ™ÿšÿ›ÿ–ÿ‘ÿ…ÿ¢ÿ™ÿÿœÿ§ÿžÿšÿšÿ•ÿÿ“ÿÿ‹ÿ‡ÿŒÿƒÿzÿvÿuÿyÿyÿxÿrÿmÿeÿ\ÿWÿQÿJÿPÿNÿNÿMÿPÿKÿ<ÿ+ÿ'ÿ*ÿ)ÿ,ÿ0ÿ.ÿ+ÿ,ÿ3ÿ4ÿ4ÿ4ÿ5ÿ9ÿ>ÿ>ÿ@ÿ@ÿ?ÿ=ÿ;ÿ=ÿAÿ?ÿ_ÿ‰ÿvÿnÿlÿnÿ…ÿ“ÿ‡ÿ~ÿ{ÿ}ÿƒÿ‹ÿpÿQÿPÿUÿ^ÿdÿqÿ|ÿxÿsÿlÿ}ÿ‰ÿkÿBÿ?ÿCÿBÿCÿFÿDÿ=ÿ:ÿ7ÿ5ÿ2ÿ1ÿ/ÿ0ÿ;ÿMÿZÿtÿzÿ~ÿxÿsÿhÿ\ÿYÿSÿFÿ>ÿ?ÿ:ÿ5ÿ;ÿPÿWÿYÿ_ÿdÿfÿqÿsÿlÿfÿ]ÿXÿWÿVÿLÿLÿQÿYÿaÿoÿ‡ÿ™ÿ ÿ—ÿ›ÿžÿžÿÿ›ÿŒÿaÿ1ÿ3ÿ0ÿ1ÿ4ÿ6ÿ6ÿ7ÿ4ÿ4ÿ2ÿ1ÿ0ÿ3ÿ6ÿIÿ\ÿeÿoÿwÿ|ÿŠÿÿŒÿlÿ:ÿ7ÿ:ÿ;ÿ<ÿ<ÿ>ÿ@ÿ=ÿ7ÿ0ÿ/ÿ7ÿ?ÿEÿfÿ—ÿ¶ÿ¾ÿ½ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ»ÿ»ÿºÿ¶ÿ³ÿ­ÿ§ÿžÿ›ÿ–ÿUÿ0ÿLÿWÿ@ÿ0ÿCÿBÿaÿYÿmÿ€ÿ}ÿ€ÿxÿFÿMÿ~ÿ[ÿ?ÿEÿKÿNÿVÿ`ÿ^ÿUÿRÿJÿIÿIÿJÿLÿMÿRÿQÿMÿKÿJÿOÿSÿWÿWÿUÿVÿTÿWÿYÿVÿTÿ[ÿdÿeÿeÿ^ÿYÿ[ÿ_ÿ]ÿVÿPÿKÿIÿJÿIÿKÿLÿOÿRÿUÿYÿ^ÿcÿhÿoÿvÿ|ÿÿÿƒÿÿ~ÿzÿtÿsÿuÿvÿxÿ{ÿ}ÿÿ…ÿ‰ÿŒÿŒÿŠÿ‰ÿ‡ÿ†ÿ‡ÿ‡ÿ‡ÿ†ÿ‡ÿ†ÿ†ÿ…ÿ†ÿ…ÿƒÿƒÿ€ÿ~ÿ~ÿ~ÿÿ}ÿzÿxÿuÿtÿsÿsÿsÿsÿqÿnÿiÿcÿZÿPÿFÿFÿHÿPÿ\ÿeÿsÿ}ÿ†ÿŒÿŒÿŽÿÿŽÿ‹ÿ‰ÿ‰ÿˆÿ‡ÿ‡ÿˆÿˆÿ‡ÿ‡ÿ†ÿ„ÿÿvÿlÿ_ÿVÿXÿ_ÿgÿpÿuÿvÿsÿrÿpÿkÿnÿˆÿ–ÿ“ÿŽÿ‹ÿ‹ÿ‰ÿŠÿ‹ÿ‡ÿ…ÿ„ÿƒÿ~ÿtÿ~ÿŠÿ‘ÿ‘ÿÿÿÿŒÿŒÿŒÿŒÿÿŽÿÿ‘ÿŽÿÿŠÿ†ÿ'~'x&~&x'~'w'~'w'}'x'}'x(}(y'}'x(}(y(}(y)~*z*~*z)}*z+}+z,},{,},{,},|,},|,},},},}+}+~+}+}*~*~*~,~+~+~*~*~*~*}*~*}*~*}*~*})~)})~)})~)|(~(|(~({)~){(~({(~(y''y&}&w&~&w&~&w&&x&&y%%z%%z&'z''{&&|''|(~(~(~()~))~)€)})€)}*)})*|*‚+}++},,|,,|,€-|--|-€.}0~/}1}0}2}2}2{3}4z3}3z4}4{4}5{5~5z6~6{5~5|6~6}6}6}5}55}55}4€3}31}11{1‚0{/‚/{/‚.|-‚.|.ƒ-|.ƒ.|-ƒ-|-ƒ-|,ƒ,|,ƒ,|,ƒ,|,ƒ+|+ƒ*|)ƒ*|*ƒ*|*ƒ*|*ƒ*|*ƒ*|*‚)}*)6…jz†Ž—rŸ“šl—’j•“šk’‘‘l‰‘m‘›l—”™g›“˜h–’‹kˆŽs“‹Œ{Œ…‹z„„yxzŠ{v}ŒwupbugavPEyKQ}O‰N~O‡Q}L‰J{<Š'{&Š(|)‰+~-‰*|)‹*z,Œ+|/3{1”6{1—9{>™>zA™>z=™?w>˜?vP—‡vu”osn’lrrŒrƒu|~u|Œ„x‚ˆU|ASd}p€yzr…e}Yƒ`}xƒy}\†C|DB{?‘DyH”>x:•7v4–3v1–0w/“:tL“`pƒ“Žn“…lm•anV•ToS•FqB•Br>”8p<•SkX–[da˜h]n™vX›†X™y]w™rcv—gjb—foe—gmi–xi˜›d˜ššb™›—^”œ‘_†nfDœ6p6˜6t7•9u8“6v2’3x44x5Ž3tEŽ`nf‘lhu”|dƒ—Œf‘—ob–2v7”7w65x5Ž6y7‹5y3‹0z0‰4{7‰Bu‡¬ºƒ½~¾€¾~¿€¾~½€¿½¾€¼|º€¸yµ„²l«‹¡U—”’7”~3MKWUŠTh1ƒ9pTjtX~^v}}~w~~€wzysƒ€wtM€E|N€N†eW‰ST‰RP‰U‚Y‰VXˆW€VŠQO‹N€M‹O}W‡U{T‚VsX|[o\z[pTzWqZ{\qbz^tZwZy`ud}dtf]tO‚LuKƒLtLƒMuQƒTwX\|`|f€jwp†vt}Š~r€Ž€q|ryurttrwxr|ŒrƒŒ‡s‹‹Œt‹Š‰vˆŠ‡v†Š‡u‡Š†t†Š‡s†Š†s†Š…t†‰…w‚‚y}w|~p~}t|y~u{vˆtwtŒsuqŒpvm‹gya‰Z|P†FE„IzSˆ^th‹vp†pŠ‹Œs‰Žw…Šy‰†‡x†…†x‡†ˆxˆ†ˆwˆ†‡yƒ‡|{t…j~]†W|Yˆaui‹rrvŒvvtŠqxl…m{‡{˜‘n‘‚Žl‹ƒ‰pˆƒˆs‡‚ƒu‚‚…y|j~n{€„‹v‘…yƒ{ƒŒ{ŒƒŒ{Œƒ{Žƒ{’‚’|‚Š|…ƒ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ)ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ/ÿ0ÿ0ÿ0ÿ0ÿ2ÿ2ÿ2ÿ3ÿ4ÿ4ÿ4ÿ5ÿ5ÿ4ÿ5ÿ5ÿ5ÿ6ÿ6ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ3ÿ3ÿ1ÿ1ÿ1ÿ1ÿ0ÿ/ÿ/ÿ/ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ/ÿ6ÿkÿŒÿ™ÿ™ÿ–ÿœÿ‹ÿÿ˜ÿ’ÿÿ‚ÿ’ÿ•ÿ˜ÿ™ÿ–ÿ—ÿ“ÿŒÿƒÿ„ÿ‚ÿŠÿ†ÿƒÿƒÿˆÿzÿÿzÿtÿnÿiÿaÿjÿcÿKÿ9ÿCÿOÿOÿQÿOÿPÿOÿHÿ8ÿ'ÿ(ÿ(ÿ)ÿ*ÿ*ÿ*ÿ)ÿ*ÿ,ÿ+ÿ.ÿ5ÿ;ÿJÿGÿ9ÿ:ÿ@ÿ@ÿ=ÿ>ÿ<ÿ9ÿ;ÿBÿzÿ‚ÿwÿwÿuÿwÿƒÿ’ÿŠÿÿ~ÿÿ€ÿˆÿqÿXÿaÿhÿvÿ{ÿwÿnÿ\ÿGÿ>ÿHÿZÿeÿWÿEÿCÿEÿCÿ@ÿ@ÿ9ÿ7ÿ3ÿ1ÿ/ÿ0ÿ-ÿ;ÿIÿ`ÿŠÿ›ÿÿŒÿcÿGÿSÿTÿVÿHÿDÿGÿCÿCÿHÿRÿXÿ_ÿhÿjÿnÿ€ÿÿ‘ÿ‰ÿƒÿwÿfÿ^ÿeÿlÿgÿmÿrÿuÿtÿwÿ‚ÿ‡ÿŒÿŽÿ‰ÿ„ÿ~ÿwÿpÿVÿ=ÿ:ÿ9ÿ8ÿ9ÿ8ÿ5ÿ4ÿ3ÿ2ÿ5ÿ8ÿ:ÿYÿtÿtÿuÿxÿÿƒÿ‡ÿÿ‰ÿbÿ6ÿ7ÿ5ÿ4ÿ4ÿ3ÿ0ÿ2ÿ0ÿ.ÿ.ÿ2ÿ6ÿ6ÿEÿqÿŸÿ´ÿ»ÿ»ÿ½ÿ¾ÿ¼ÿ¼ÿ¸ÿ´ÿ¶ÿºÿºÿ¹ÿ³ÿ®ÿ¤ÿ™ÿ‘ÿÿŒÿ†ÿZÿRÿLÿUÿRÿ1ÿ4ÿMÿOÿ=ÿhÿ€ÿÿ€ÿÿ|ÿ|ÿxÿnÿTÿKÿLÿLÿMÿNÿPÿMÿOÿNÿLÿDÿLÿXÿUÿMÿEÿCÿCÿEÿGÿVÿRÿQÿ[ÿYÿSÿYÿWÿcÿeÿeÿmÿkÿhÿhÿfÿmÿoÿhÿ^ÿQÿMÿLÿNÿNÿOÿRÿUÿXÿ\ÿbÿhÿnÿsÿzÿ~ÿ€ÿ‚ÿ‚ÿ~ÿzÿvÿtÿsÿtÿwÿxÿ|ÿÿƒÿ‡ÿ‹ÿŒÿ‹ÿ‰ÿˆÿ‡ÿ†ÿ‡ÿ‡ÿ†ÿ†ÿ†ÿ†ÿ†ÿ‡ÿ†ÿ‡ÿ†ÿ„ÿ‚ÿÿÿ~ÿ~ÿ~ÿ}ÿyÿuÿsÿrÿrÿpÿnÿoÿmÿgÿaÿZÿPÿIÿHÿMÿWÿ`ÿkÿxÿÿ†ÿŠÿÿŽÿŽÿÿŠÿ‰ÿ‡ÿ†ÿ†ÿ†ÿ‡ÿ‡ÿ†ÿ†ÿ„ÿÿ{ÿsÿiÿ\ÿWÿ\ÿcÿkÿrÿwÿwÿqÿpÿlÿƒÿ–ÿ’ÿŽÿ‹ÿŠÿ‹ÿŠÿ‰ÿ‡ÿ„ÿ…ÿÿ|ÿdÿdÿtÿƒÿÿ‘ÿÿÿÿÿŒÿŒÿŒÿŒÿÿŽÿÿ’ÿÿŒÿ‰ÿ†ÿ(~(x(~(x(~(x(~(x'}'x(}(x(}(y(}(x)})y)})y)}*z*}*z*})z*},z,},{,},{+}*{+}+{+}+}*}*}*}*~*})}(~+~*~)~)~)~*~*~)~)})~)})~)})~)})~)})~)}(~(|(~(|'~'{'~'{'~'|'~'{&&z&}&x%~%x%~%x%%x%%y%%z%%z&&z&'{&&{&'{(~(~'~')~))~)€)})€)}))})€*|*+}++}+,|-,|,€.|.€/|/.}/1}0~1}1}1}1{2}3z4}4z5}5{4}5{5~5z6~6{6~6|6~6}5}5}5}55}54}3€2|21|10{0‚0{/‚.{.‚.|-‚-|-ƒ-|-ƒ-|-‚-|-‚,|,‚,|,‚+|+‚+|+‚*|*‚*|*‚*|*‚*|*‚*|*‚*|*‚*|*‚*}0@>…hzŽ˜r“––lš•j‘”–l”•lŒ“—l›“šg–”‹d†”‡f‰’ƒj„q…Ž…yˆ}Š„}zy‡qyq‹pwmŽlxgcxTJzK‹K}MˆN}Q‰N|JŠH|7Š'|(Š({(Š)|)Š+|(‹*z.Œ,{+Ž,z7‘Pz=•8z3—<{:™?z?™=y<—9x;—lv’‚u‚sˆs’s„Ž~t‚t„‹‚yY†it‚xz‚x€jƒW}D„9}7ƒ:}K†a|T‰B{?CyCCyA’8y4“1y/’.y.L€P‚^‚c‚]}_€`xVR{HZ~h€hl€ke[SUƒgRƒG€X†_zZ‚cwiav[gym€l|c}`~Xzk€ly^€SvO‚OuOƒPtPƒRuUVxZ€^~b{g„nwu‹zr}Žp‚‘o}yqvursvrxxr|ŒrƒŒ‡sŠ‹‹tŠŠ‰vˆŠ‡v‡Š‡u‡Š†t†Š†s†Š†s†Š‡tˆ‰‡w…ƒ{w€~€s~v}yuzs‰rurpsoŒmuj‹gy_‰Y|P†I~J†Qy[‰etpyp‚‡pŠŠsŽˆw‹…Šy‰†ˆx‡†‡x†‡‡w‡ˆ‡w‡ˆ„y€‡z|q…f~\†Xz^‰fvnŒutv‹wurˆpx~~–“s‹mŠ‚Šq‹ƒ‰t…‚†uƒƒz{d€^{iƒwvƒ†u„yƒ{ƒ{ƒŽ{Žƒ{Žƒ‘{‘‚zŒƒ‰zˆ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ)ÿ*ÿ,ÿ,ÿ,ÿ,ÿ,ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ(ÿ+ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ-ÿ,ÿ-ÿ,ÿ,ÿ-ÿ-ÿ.ÿ/ÿ1ÿ0ÿ1ÿ1ÿ1ÿ1ÿ2ÿ3ÿ3ÿ3ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ3ÿ3ÿ2ÿ2ÿ1ÿ1ÿ0ÿ0ÿ0ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ,ÿ.ÿ0ÿaÿ†ÿ’ÿÿ”ÿ–ÿÿŽÿ•ÿ•ÿ•ÿ—ÿ›ÿ›ÿ˜ÿ–ÿˆÿ‡ÿ€ÿŽÿÿƒÿƒÿ‹ÿˆÿÿ‚ÿ‹ÿ…ÿwÿyÿwÿvÿrÿmÿeÿ^ÿUÿNÿLÿIÿKÿNÿNÿNÿKÿIÿ:ÿ)ÿ(ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ+ÿ-ÿ-ÿ-ÿ,ÿ/ÿ3ÿ+ÿ0ÿVÿDÿBÿ=ÿ?ÿ@ÿ>ÿ;ÿ=ÿ\ÿ“ÿŒÿ‰ÿ†ÿ†ÿ‹ÿÿ—ÿÿ€ÿ}ÿ‚ÿÿ‡ÿqÿrÿwÿwÿyÿtÿgÿTÿCÿ8ÿ5ÿ4ÿ8ÿLÿZÿOÿDÿAÿFÿDÿDÿ=ÿ5ÿ1ÿ/ÿ/ÿ.ÿ<ÿMÿeÿzÿ•ÿ™ÿsÿ4ÿ@ÿLÿNÿPÿLÿAÿ<ÿ?ÿBÿSÿXÿ]ÿdÿpÿ…ÿ˜ÿžÿÿ–ÿÿtÿuÿmÿ]ÿRÿbÿxÿ|ÿŠÿŸÿšÿœÿ”ÿ{ÿnÿrÿtÿrÿlÿfÿcÿ[ÿYÿPÿDÿAÿ>ÿ=ÿ:ÿ9ÿ7ÿ7ÿ7ÿ9ÿ:ÿMÿvÿ„ÿ€ÿ€ÿ„ÿ‡ÿ…ÿÿ€ÿgÿ<ÿ7ÿ5ÿ6ÿ3ÿ1ÿ/ÿ.ÿ-ÿ/ÿ3ÿ9ÿ;ÿCÿUÿ_ÿfÿkÿsÿ{ÿ‹ÿ•ÿ•ÿŠÿ}ÿwÿ€ÿ‘ÿšÿ›ÿÿ—ÿÿ‹ÿ‡ÿ‡ÿ†ÿ~ÿZÿDÿDÿIÿKÿQÿPÿ<ÿ<ÿMÿmÿ}ÿwÿxÿ~ÿ~ÿlÿ_ÿ‰ÿ‚ÿCÿMÿYÿcÿcÿeÿkÿhÿYÿPÿ\ÿfÿlÿmÿmÿnÿlÿgÿaÿ`ÿXÿPÿJÿMÿ[ÿZÿ]ÿZÿVÿOÿVÿbÿeÿUÿHÿAÿ]ÿdÿWÿQÿQÿOÿPÿPÿPÿRÿVÿXÿZÿ^ÿdÿiÿoÿvÿ{ÿ~ÿÿ‚ÿÿ}ÿyÿvÿsÿuÿwÿxÿzÿ}ÿÿ…ÿ‡ÿŠÿ‹ÿŠÿ‰ÿˆÿ‡ÿ‡ÿ‡ÿ‡ÿ†ÿ†ÿ†ÿ†ÿ†ÿ†ÿ‡ÿ‡ÿˆÿ‡ÿƒÿÿ‚ÿ‚ÿÿ~ÿ|ÿxÿuÿsÿqÿqÿqÿoÿmÿjÿgÿ_ÿXÿOÿIÿLÿSÿ]ÿgÿpÿ{ÿ„ÿˆÿ‹ÿÿŽÿÿ‹ÿŠÿ‰ÿˆÿ‡ÿ†ÿ…ÿ‡ÿˆÿ‡ÿ†ÿ„ÿ€ÿyÿpÿfÿ\ÿ\ÿbÿiÿpÿwÿvÿuÿqÿyÿ”ÿ”ÿÿ‹ÿ‰ÿ‰ÿ‰ÿ‡ÿˆÿ…ÿ„ÿ„ÿzÿdÿ^ÿ`ÿjÿvÿ„ÿÿŽÿÿÿÿŽÿÿÿŒÿŒÿÿŽÿ‘ÿ‘ÿÿŒÿŠÿŒÿ(}(x(}(x(~(z(~(z(}(y(})y)})x)})x*|*x*|*y*}*y*}*z+}+{+}+{+}+}+}+}+}+}+}+}+|+}*|*}*}*~*}*~)~)~(~(~)~)~)~)~)~)})~)})~)~)~)~)~)})~)}(~(}(~'}&~&|&~(z'~'z&~&z&~&{&~&y%~%x%~%x$~$w%~%w%~%y%~%y%~%z%~%z&~&{&~'{'~'}&~'~'~('}(€(}(€)})€)})€*}*€+}+,},,{-,{,-|..|.0}0~0}1}1}0}1}1|1}2{3}3{4~4{5~5z5}6{5~6{5~5|6~5}4}6~5}5~5|4€4|4€2|1€2|0/z/‚.z-‚-{-‚-{-‚-|-‚,|,‚,|,+}+ƒ+}+ƒ+}+ƒ*|*‚+|+‚*|*‚*|*‚+~,‚(~*‚)})‚*}*‚*~*)~))~,„Z{}Ž‹r–”k—Žh‹—k—Œmœ•žkœ•™eœ–•d‰–ŠhŒ”‘n’‘‹r‡ŽŠ{ƒˆ‚€‹„Ž~€†}{xŠvztŒmzdŽ]zREz>ŠB|GŠM}MˆK|H‰H{>‹-{(‹'|)‹*|(‹(|)‹*|-Œ.{,Ž+z()z,‘.y:“CzP–;z?˜=z>™•Jy‘wŠŒ‡t†ŽŒs“˜r•ˆr~Žs‚‹…v‰d~u„x€v‚p€c‚O~>„7}7…7|:†E|V†\|O‹?y>ŽBx?‘@y:4y0.x/:uKgo„’’k—•hl.—6oQ—dpT•HpB•>p>”AmE–[ia˜db{›‘[™X—›‚Zv¡xd{pc~ž”^–˜‹V€ŸšX¤¢Y¢‘ \“—~^r™mchšggd™_jZ™ZmT—OpI—Bq@•>r=•;s9•8t:•4q[—‰kˆ˜wg{—c…˜‚c|˜{ij—Do6–6u4“3w/‘.z./{1‹5|:‰?}Pˆh|}‰~zy‹wwmav_‘byeŒc~d†l€x€€‚ƒt‡†ˆaƒ†J|†{ÿ?ÿ<ÿ:ÿ<ÿ@ÿÿ“ÿŒÿŠÿŽÿ‘ÿ•ÿ–ÿ—ÿŠÿÿ€ÿÿ…ÿŠÿiÿnÿwÿuÿpÿ`ÿLÿBÿ8ÿ8ÿ<ÿDÿZÿfÿ_ÿTÿMÿDÿBÿ@ÿ?ÿ;ÿ5ÿ1ÿ/ÿ/ÿ;ÿHÿ[ÿ}ÿ’ÿ’ÿYÿ2ÿSÿrÿxÿ`ÿLÿEÿAÿ?ÿ@ÿAÿOÿfÿnÿwÿ•ÿŸÿ–ÿ‚ÿsÿ‚ÿ¥ÿ«ÿŸÿ¥ÿ¦ÿ¦ÿ›ÿ—ÿ®ÿ°ÿªÿ¥ÿÿ—ÿ‡ÿ{ÿrÿiÿeÿ`ÿ]ÿZÿVÿRÿNÿKÿEÿAÿ=ÿ=ÿ>ÿ?ÿ?ÿ>ÿ;ÿZÿ…ÿÿ~ÿsÿwÿ~ÿ€ÿ{ÿxÿnÿQÿ8ÿ7ÿ5ÿ3ÿ3ÿ0ÿ0ÿ1ÿ5ÿ8ÿ?ÿBÿVÿvÿŒÿÿÿŽÿ‡ÿ}ÿiÿZÿVÿRÿOÿMÿTÿ[ÿ^ÿ`ÿdÿ`ÿ]ÿ\ÿZÿbÿtÿYÿBÿ?ÿ=ÿBÿ4ÿ=ÿAÿKÿ7ÿKÿeÿgÿnÿzÿ‚ÿ…ÿhÿ&ÿbÿqÿSÿZÿ]ÿdÿkÿlÿfÿhÿcÿbÿhÿoÿpÿpÿpÿkÿgÿdÿ_ÿ_ÿ\ÿZÿXÿYÿYÿYÿZÿTÿTÿ\ÿ^ÿXÿ6ÿ(ÿ1ÿOÿaÿUÿRÿRÿPÿQÿQÿRÿTÿXÿZÿ\ÿ`ÿfÿkÿrÿxÿ}ÿ€ÿƒÿƒÿÿ}ÿzÿvÿsÿuÿxÿyÿ|ÿÿ„ÿ‡ÿŒÿ‹ÿ‹ÿŠÿ‰ÿˆÿ‡ÿ‡ÿ†ÿ†ÿ†ÿ†ÿ‡ÿ†ÿ‡ÿ‡ÿ‡ÿˆÿ‰ÿˆÿ†ÿ†ÿ„ÿ„ÿÿÿ}ÿxÿtÿsÿsÿsÿpÿnÿlÿiÿeÿ^ÿVÿMÿLÿQÿXÿaÿkÿvÿ~ÿ„ÿˆÿŒÿÿÿÿ‰ÿ‡ÿ†ÿ†ÿ†ÿ‡ÿ‡ÿ‡ÿ‡ÿ†ÿ†ÿƒÿÿwÿoÿdÿ^ÿ`ÿgÿnÿsÿwÿtÿtÿŠÿ•ÿ‘ÿ‹ÿ‡ÿ…ÿ†ÿ‡ÿˆÿ†ÿƒÿ…ÿÿiÿcÿcÿbÿfÿqÿ~ÿ‰ÿŒÿÿÿÿŽÿŽÿÿÿŽÿŽÿŽÿŽÿÿ‘ÿÿˆÿŽÿ”ÿ)})x(}(x(}(x)})x)})x)})x)})x*}*x*|*y)|*y*}*z*}*z*}*z*}*z+}+|,},|+}+}*}*}*}*}*}*}+}+~)})~)~)~(~(~(~(~(~(~(~(}(~(}(~(~(~(~'~'}'~'}&~&}'~'}&~&|&~&z&~&z&~%z%~%y%~%x%~%x$~$x$~$y%~%y$~$y$~$y$~%y%~%y&~%{&~&{&~&|&~'}'~'}'}'(}(€(}(€)})€)}*€)}+€,},€,},€,},-}..}.~/|/~/|/}0|1}/|1|1}2{2}2{4}4{5}5z5}5z6}5{4}4|4}4}4}3~3}3~3|3€2|0€1|/€/|/.{.‚-{-‚-{-‚,{,‚,|,‚+|+‚*|*+}+ƒ+}+‚*}*‚*}*‚*}*‚*}*‚*}**~)‚)~)‚'}'‚'}'‚'~''~()+„I}k‹~s–†k„˜‡i‰—…j‚˜‹k˜•’i‹”ŽeŒ–}f‡•‹lŒ”‰sˆ‘ˆv„Ž|€‰€……’…q~t‰y|s‹l}hŠa}S‰LzH‰G|F‰<}@ˆI|G‰E{<‰/{(‰(|)‹*|)‹(|(‹*|*Œ+{,+{-Ž,z--z*,{/“5{4–=z<—7y;”Ly–—wŒs‹’q–•q•“r†€sŒƒv‡Œ‚{r†u€um[K€<~87~D_lr„h|]ˆWvO‘Fu@’Bv?’8w10x-8uD_pr“Œmˆ–PlN˜wjŒ™ƒjo–SlJ”HmA”An>–NkU™ef|›…_ž‹_’ž i­µo²†²k²‰¬[¥ Kœ˜®N³Š®N¤ŸOŽ˜W}sal›egcš^jY™WkX™TlM™HoB•@q@•BrC–Bq@—Cobš‚i†š‚ex™wb|˜}cy˜vio˜[m@˜:r;”;v<7z25{7‹<|@‰E}[‰x}Š†Œ‘„‘€‘…€‚†qj…c\V‚S}MƒGuF„HlH‡L[X†gJs‡oNL‡GgI‡>wG‚8y>EuDGq?HoW~atyv€8uE’t’€wIƒX}V‚QV~ck}b{]}lymq}tq€mh€d`€][€Z~YX~X€Y~Z€W~Z]~\~VE}?L}WYyUQwPQvP‚RvRUwY[{_}chyl†ttx‹|p€‚o‚€o}zpxwqvzq{}s€Œ„s‡‹‹s‹‹Št‰Š‰uˆ‹‡u‡‹†u†‹†s†‹‡s†Œ†s†‹‡tˆ‰Šxˆ€‡|…uƒƒt€~xzwƒsysŠstsŒosnŒlug‹dy\ˆT|M…N|S†[wcŠmqxŽ€p…Ž‰qŒŠŽtˆ‹w‰ˆ‰wˆˆ‡w‡‡‡w‡ˆ†v†‰†v†‰ƒy~ˆw{m†e|`†bwhŠosvŒutqˆ†y“xp‹‚‰qˆ‚‡vˆ†v…ˆxk€ee‰byb‹hrvŒ‚rŒŠw…{ƒ}ƒŽ{Œ„{„Žz„z‚Œzx••m*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ)ÿ)ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ1ÿ0ÿ1ÿ2ÿ2ÿ2ÿ3ÿ3ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ1ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ)ÿ)ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ)ÿ+ÿ<ÿsÿƒÿ‹ÿŠÿ†ÿ‹ÿ‚ÿzÿ~ÿŽÿ–ÿÿŒÿÿ‹ÿzÿ‚ÿ‰ÿ†ÿÿ~ÿnÿvÿ†ÿ„ÿƒÿˆÿÿ}ÿqÿqÿqÿqÿoÿcÿYÿQÿOÿMÿHÿFÿ@ÿAÿJÿEÿCÿ>ÿ0ÿ'ÿ(ÿ)ÿ*ÿ)ÿ(ÿ'ÿ+ÿ*ÿ*ÿ+ÿ,ÿ*ÿ)ÿ*ÿ)ÿ+ÿ+ÿ-ÿ.ÿ1ÿ7ÿ:ÿ@ÿKÿkÿœÿ™ÿ‘ÿÿ‘ÿ–ÿ˜ÿ—ÿ‡ÿ’ÿŽÿƒÿ‚ÿ„ÿ‡ÿˆÿuÿrÿtÿmÿ[ÿIÿCÿCÿCÿJÿWÿeÿoÿkÿ`ÿUÿPÿPÿIÿAÿAÿ@ÿ:ÿ5ÿ/ÿ2ÿCÿ[ÿqÿƒÿyÿNÿcÿ‡ÿÿ‚ÿzÿzÿeÿGÿ=ÿEÿ>ÿNÿVÿPÿ]ÿyÿŠÿ–ÿ¥ÿ±ÿ³ÿµÿ³ÿ°ÿ­ÿ«ÿ¤ÿ›ÿ”ÿªÿ­ÿ¦ÿ¡ÿ¢ÿ–ÿ‡ÿÿtÿnÿjÿdÿaÿ\ÿbÿpÿgÿ\ÿQÿHÿEÿDÿDÿDÿDÿDÿPÿ|ÿ’ÿÿƒÿ{ÿzÿÿ~ÿwÿuÿiÿ_ÿ\ÿYÿ]ÿ\ÿ_ÿPÿ4ÿ8ÿ;ÿ>ÿAÿBÿWÿoÿ‚ÿ…ÿŠÿ‘ÿÿŒÿÿyÿwÿuÿpÿcÿcÿaÿ]ÿ\ÿ[ÿ^ÿaÿfÿmÿpÿfÿBÿAÿ=ÿ@ÿBÿCÿ;ÿLÿAÿIÿGÿ?ÿRÿgÿvÿ€ÿpÿeÿŠÿ“ÿÿ„ÿPÿPÿWÿKÿJÿVÿOÿKÿWÿqÿrÿrÿtÿqÿnÿiÿeÿaÿ^ÿ\ÿ[ÿ[ÿYÿYÿ[ÿ\ÿ\ÿ\ÿ\ÿ\ÿXÿVÿZÿ[ÿXÿUÿUÿSÿQÿPÿPÿRÿRÿUÿXÿ\ÿ_ÿcÿiÿnÿsÿwÿ|ÿ€ÿ‚ÿ‚ÿÿ|ÿyÿvÿwÿxÿ{ÿ|ÿ}ÿ€ÿ„ÿ‡ÿ‹ÿ‹ÿŠÿ‰ÿ‰ÿˆÿ‡ÿ‡ÿ†ÿ†ÿ†ÿ†ÿ†ÿ…ÿ†ÿ†ÿ‡ÿˆÿˆÿˆÿ†ÿ†ÿƒÿƒÿƒÿÿzÿwÿsÿsÿqÿqÿoÿnÿkÿfÿbÿZÿSÿNÿPÿUÿ]ÿeÿqÿ|ÿ‚ÿ‡ÿ‰ÿŒÿŽÿÿ‰ÿˆÿˆÿ‡ÿ†ÿ†ÿ†ÿ†ÿ…ÿ…ÿ…ÿ…ÿƒÿ~ÿwÿmÿdÿcÿgÿiÿrÿwÿrÿ|ÿ’ÿ‘ÿÿ‰ÿŠÿŠÿŠÿ‡ÿ‡ÿ„ÿ‡ÿ…ÿrÿhÿiÿfÿbÿbÿjÿxÿ…ÿÿÿÿÿŽÿŽÿŽÿŽÿŽÿÿÿŽÿÿ‘ÿÿŒÿ‘ÿ˜ÿ•ÿ)})x*}*x*}*x(}*x*}*w*}(w)})z+}+z*|*z)|)z*}*z*}*{*}*|+}+|*}*{*}*|*~*|)~)|)~)}(~(}(~(}(~(}(~(}(~(}(~(~(~(~(~(~(~(~(~(~'~'~(~(}'~'}&~&|&~&|&~&|%~%|&~&{'~%z%~%y%~%y$~$y$~$y$~$y$~$y$~$y$~$y$~%z%~%z&~%{%~&{&~&|&~&}&~&}'~''~'~'~'€'~'€)~)€(~*€,~,€+}+€,},-|.~.|.~/|/}0|0|1|1}1}0|1}2}2}2{2}2z4}4z5}5{5}5{4}4|3}3}2}2}3}3}2|2~0|.€/|.€.|-€-|-‚-|-‚,|+‚+|+‚*|*ƒ)|)ƒ)})ƒ)})ƒ)})ƒ(}'ƒ(}((}()}))}))~)‚'~'‚'~'‚'~'‚''€&(€)~,2}k…‹u“ŠkŒ˜Šhƒ—zh„—Œi–Žj”•gŠ–}i}•†p‚“sugdy~Œ‰|†‰‡Š…Žq…^~_ˆ^~`Œg}d‹Q~Q‡S}UˆM|LˆN|N‰J{FŠE{?Š1{'Š'{)‹)|)‹)})‰(}+Š({)Œ,{*+{*Ž+z,-z,‘,{0’4|7”U|z”‘z•x’Ž”r•—p–‘›nzŠo–ˆr„Ž†s†‰xƒ‡|~~{‚vxqsvsuw}ˆx‹}‚}j†Yw[ZrU–NsG–FtG’Av72w?‘Ntj‘xpl–\i„š•e—š‰c„—‹f‘“tjL”@k>—JkU™LhR™mdŒžh¨±j³‰°l¬Œ¨h£’£\¤“¢RŸ’£Kœš•H’ž‘HŸŒP€žsZmŸkehmfmjbs›jacšZgS˜KoH—HpI–CoB—Nk}š”g˜›Žc‚›`€š}`wšrcišigo˜tmz’zvve{=Š6|8Š:{>‰B{Nˆa}z‡†„‹„ƒzx‚{„{z…s{f†dtjˆgk`‰\a_‰fVg‰jOn‰Z\[Š[vXˆ9€6ƒ:>€M{K€CvF€DpO€`py~|u[}rwŒ~’t„xTƒG~R‚Kƒ6~G‚M}e~l}p~r}ts~q€oke€_€][€[~ZZ~Z€\~\€[~\€]~[~Z~\~ZW|W€UySQwPPvNRvR€UzY~]~_|c‚jwn‡rsvŒ|o€‚n‚€o~zpxŽxpy{q|~sŒ„sˆŒ‹s‹Œ‰tˆŠˆu‡‹‡u‡‹†u†‹…s…‹…s…‹†s†Œ†t‡ˆˆwˆ‡}†w…ƒv€~{y~v„rxqŠqtqotnŽlvg‹cy\‰R{P†T{Y‡_vh‹ro|Žƒp‡ŠrŠuˆŠw‰ˆˆw‡ˆ‡v‡‡‡v‡‰†v†‰…v…‰„y~‰w{m‡f{eˆgwm‹usv‰{w}‘~Œp‰‚‰rŠ‚Šuˆ‡v†ˆx„o~ii†izdŠbueŒorzŒˆrˆ‘w’„Ž{Œƒ{ƒŒzŒ„Œy„zƒ{Ž|–r–€•i)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ)ÿ)ÿ(ÿ*ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ-ÿ.ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ/ÿ/ÿ1ÿ0ÿ1ÿ2ÿ2ÿ2ÿ2ÿ2ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ3ÿ1ÿ0ÿ/ÿ/ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ(ÿ)ÿ/ÿ3ÿNÿ‰ÿÿŒÿˆÿ…ÿ€ÿ|ÿ‚ÿ†ÿ‹ÿ’ÿŒÿ‹ÿˆÿwÿiÿnÿnÿfÿtÿ‚ÿÿ‹ÿ‡ÿŠÿ‹ÿŠÿaÿOÿOÿPÿTÿVÿ_ÿ]ÿOÿMÿOÿNÿKÿJÿKÿHÿGÿEÿ?ÿ1ÿ'ÿ'ÿ)ÿ)ÿ*ÿ#ÿ*ÿ+ÿ*ÿ*ÿ'ÿ&ÿ(ÿ+ÿ+ÿ+ÿ*ÿ+ÿ,ÿ+ÿ+ÿ.ÿAÿnÿ’ÿšÿšÿ”ÿ’ÿ“ÿ•ÿ—ÿ–ÿ•ÿzÿÿ•ÿŽÿˆÿƒÿ†ÿˆÿ‰ÿ‡ÿ†ÿÿ’ÿ”ÿ–ÿ•ÿ’ÿ”ÿ—ÿ—ÿ’ÿ‰ÿuÿRÿPÿRÿXÿRÿMÿGÿEÿJÿCÿ6ÿ4ÿJÿmÿˆÿwÿ{ÿ”ÿ™ÿ›ÿ‘ÿ’ÿŸÿ£ÿŸÿ…ÿhÿiÿZÿOÿ@ÿMÿfÿÿ«ÿ°ÿ°ÿ²ÿ­ÿ¥ÿ ÿ ÿÿ¡ÿ¬ÿ¤ÿŽÿÿ ÿ£ÿ¤ÿ¤ÿ¢ÿžÿ“ÿiÿ[ÿdÿoÿ€ÿvÿqÿnÿfÿ_ÿSÿJÿHÿKÿKÿGÿBÿ@ÿ]ÿŒÿ˜ÿŽÿÿ‚ÿ€ÿxÿuÿxÿxÿqÿvÿ€ÿ‰ÿ„ÿÿsÿPÿ4ÿ6ÿ;ÿ?ÿ?ÿDÿSÿiÿyÿÿ}ÿqÿlÿqÿuÿsÿqÿmÿgÿcÿgÿfÿ_ÿ[ÿ_ÿcÿbÿgÿbÿ\ÿ`ÿsÿeÿBÿ7ÿ;ÿ=ÿ>ÿFÿJÿQÿGÿ;ÿNÿxÿ{ÿdÿ{ÿ‹ÿÿŠÿ‚ÿ\ÿIÿOÿ>ÿ+ÿ@ÿTÿ]ÿdÿqÿqÿrÿsÿsÿoÿlÿfÿaÿ]ÿ\ÿ\ÿ[ÿ[ÿ[ÿ[ÿZÿ[ÿ[ÿ\ÿ\ÿ\ÿ\ÿZÿXÿUÿUÿQÿPÿPÿPÿOÿPÿRÿUÿYÿ]ÿ_ÿcÿhÿlÿrÿvÿyÿ~ÿÿ‚ÿ€ÿ~ÿ|ÿyÿyÿ{ÿ{ÿ|ÿ~ÿÿ†ÿ‰ÿ‹ÿ‹ÿˆÿˆÿ‡ÿ‡ÿ‡ÿ‡ÿ†ÿ†ÿ…ÿ…ÿ…ÿ…ÿ†ÿ†ÿ†ÿ‡ÿˆÿˆÿ‡ÿ†ÿ…ÿƒÿ€ÿ~ÿzÿvÿsÿqÿqÿqÿoÿnÿlÿgÿbÿ[ÿRÿPÿTÿYÿ`ÿiÿsÿ~ÿƒÿ‡ÿ‹ÿŽÿÿÿ‰ÿˆÿ‡ÿ†ÿ†ÿ†ÿ†ÿ†ÿ†ÿ†ÿ…ÿ…ÿƒÿ}ÿwÿmÿfÿgÿjÿqÿuÿ{ÿÿ‘ÿÿŠÿ‹ÿŠÿ‹ÿŠÿ‡ÿ†ÿ†ÿ‡ÿtÿjÿnÿlÿiÿeÿcÿgÿqÿ~ÿ‰ÿÿ’ÿ‘ÿÿŽÿŽÿŽÿÿÿÿŽÿŽÿÿ‘ÿÿ’ÿ—ÿ”ÿ”ÿ*}*x)})x)})x*}+x+}*y*}*y+}+z+}+z*|*z)|){*}*z*}*|*}*|)})|)}){)})|)~)|)~)|)~)}(~(}'~'}(~(}(~((~((~(~(~(~(~(~(~(~(~(~'~'~&~&}&~&}&~&|&~&|&~&|&~%|%~%{%~%z%~%y%~%y$~$x$~$x$~$y$~$y#~$y$~$y$~%z%~%z%~%{%~%{%~%|&~&}%~%}'~''~'~'~'€'~'€(~(€)~)€*~*€)}+,}-,|--|.~0|0}0|0|1|1}1}0|1}2}2}2{2}2{3}3{3}3{3}3{3}3|2}2|1}1}2}3}2|2~/|/€/|.-|--|-‚-|,‚+|+‚*|*‚*|*)|)(}('}''}&'}''~''~''~'&~&'~'‚&~&‚'~'‚'~'‚''‚&(~,ƒ1z;ƒExk|†ƒp‡’…j„—ƒj†˜ƒk…—‹kˆ–‚oƒ•„q‚”‡u„’qyvŽ}‹‰Œ‡Ž~Ž†‚}[‰N|LŒT|XT}G‘W€bŠ[€O‡NN‡K|J‡K{FˆC{=‰/{%‰'}(Š*,ŠImˆ8~#‰(|,Š,|)‹){+,{,Ž.{/Ž.y/2zT€z˜‘ w˜Ž’t’Œ•pš–n–‘m~‘{n‘‘•q‰„q„Žˆt‹Œˆzz†’~”˜y™—x–€—x——y–~…`xVŽXrX—Zq\—UqJ•FtG”Bv:’>sa“o‰•“h•˜—c–ššd¤–¥g¤‘ h““†g“•š^†™__@šOcŒ›¦j®°l¯‰­m¢‘œhž—a —¢WœŠQ¤š¥N¦”¥M ”ŸO™”Y[žÿ2ÿ5ÿ7ÿ<ÿ<ÿAÿFÿRÿ\ÿ]ÿ[ÿ^ÿ`ÿ\ÿVÿYÿYÿTÿOÿSÿTÿWÿXÿZÿ]ÿWÿEÿBÿLÿUÿ_ÿlÿaÿ9ÿ;ÿ6ÿ3ÿ1ÿ=ÿYÿ[ÿeÿWÿVÿpÿ€ÿ†ÿ‡ÿ†ÿƒÿ…ÿcÿIÿJÿEÿ8ÿ=ÿ@ÿ4ÿ=ÿfÿtÿtÿtÿrÿnÿiÿfÿaÿ^ÿ]ÿ[ÿZÿZÿ[ÿ[ÿYÿ[ÿ[ÿ\ÿ]ÿ[ÿ[ÿZÿYÿUÿSÿPÿOÿOÿLÿOÿQÿRÿUÿZÿ^ÿaÿdÿgÿjÿpÿtÿxÿ}ÿÿƒÿ‚ÿ€ÿ|ÿ{ÿ|ÿ|ÿ|ÿ}ÿ€ÿƒÿ†ÿ‰ÿ‰ÿ‰ÿˆÿˆÿ‡ÿ‡ÿ†ÿ†ÿ…ÿ…ÿ…ÿ…ÿ…ÿ…ÿ†ÿ†ÿ†ÿ‡ÿ†ÿ†ÿ‡ÿ…ÿ„ÿ‚ÿÿ|ÿxÿtÿsÿsÿqÿpÿpÿnÿiÿfÿ`ÿXÿRÿQÿWÿ\ÿdÿnÿvÿÿ„ÿˆÿŒÿŽÿÿŒÿŠÿ‰ÿ‡ÿ†ÿ†ÿ†ÿ†ÿ†ÿ‡ÿ‡ÿ†ÿ„ÿƒÿ|ÿwÿmÿiÿiÿmÿuÿŒÿ–ÿÿ‹ÿ‹ÿ‰ÿŠÿ‹ÿˆÿ…ÿ…ÿ‡ÿ|ÿlÿmÿoÿnÿmÿkÿhÿhÿnÿwÿƒÿŒÿÿÿÿŽÿŽÿŽÿŽÿÿÿÿŽÿŽÿŽÿÿ“ÿ—ÿ”ÿ”ÿ’ÿ*|*x*|*x*|*x*|*x*|*y+|+z+|+z*|*z*|*{*|*{)|){*|*{*})|*}*|)})|)})|)~)|(~(|'~'~'~'~'~'~'~(~(~(~(~(~(~(~'~'~(~(}'~'}'~&}&~&|&~&{%~%|$~$}%~%}%~%}%~%}%~&|%~%|%~%{%~%|%~%|$~$|$~$|#~#z$~$z$~$z$~${$~${%~%{%~%{&~&~&~&~$~$}%~%}&~&~&~'~'~'€'~'€(~(€)~)€)}++}+,},~-}.~.|.{.|0{1|1}1|1}1|1|1|3|2|2{1|1{2|3|2|1|2|2}3|3}0}0~0}00}0€.}.-}-‚-}-‚,{,‚,{+‚*}+‚*}*)}))}('~'&~%€&}&€&}&€&~&%~%&~&%~%%}%‚&}&‚'~'‚(~&')~*‚+w7‡Fo[‡um{t|dvm‹qpt”xnu•lml”kol”sv€•œx¢”¤z¡’”|•Ž~Š‹Œ}Š{z^|WY}]a~ba~^Žfglƒp‰_„[…S‚M„HH„B}7‡*{'ˆ'{'‰(|(‰&}'ˆ'}'‰'}'Š&}'Š(|(‹*{+-{/Ž,z5Lydxv‘œt—Ž“r”Ž˜q•”m‘’ƒj~’|k}‘o“Šq†‚q…‰uŒ{|†ƒˆƒŽ’•—€˜|—€—zŠ†rudaqc˜\n[˜_o]˜VrJ—Br@—?q^•’o–”‘h•’e’—e™—h’•lh—[hq™—]¡—¦U¨‘™X}›cž—¥k©’¤i™˜–g•”_—žŽWŽ ›T•œ”V”Ÿ“Y•Ÿ–^“…bWAiCšJ`ažœW—˜‰P{ qYjœbeQ˜GkB˜AjF™HgD@aaŸ_”Ÿ^ˆ\“ž`Œœ…d€—ƒm€“xvto|hŠH/ˆ/|2‰8{;ˆ>|@‡CJ„RT‚WXW„UzT…SuP†NrQ…SqT…WrY†Po:‡+o<ˆHrN‰OwSŠMy@‰Gu?‡9u4„@uWƒJoQ„OgN„[jp‚{s„€‹x‰€„yeƒK|CƒE‚@3‚8}E^}pt}ts~qn~ie`‚]~\‚[~[‚\~\‚[~Z‚Z~[‚Z~Z~[~Z~ZWS€P{NMyNNyO‚PyS€V}Z}^`{c…hwj‰osrŒwo|€l’ƒlƒ’€m~|p{Ž|r~€r‚…rˆ‡s†‹ˆs‡‹†t†Œ†t…Œ„s„„s……r…Œ‡r‡‹‡v‡†‡y†}‡~…v„€‚v€|~x|u†svrqspŽosmjueŒ_zX‰S{R†Vx^‰esnxo‚Ž…pˆŒ‹rŠu‹ˆŠw‰ˆ†v…ˆ†u†‰†u†‰‡u‡Š†v…‰‚x|ˆv{n‡kyk‡ow…€”{‘sŒ‹n‹„‰rŠ„‰v…‚†yŠ~nmˆnzo‹mvoŒnvilrq‘yo„‹ŽtˆŽyŽ…ŒzŒ„z†‹z‹†‹zŽ…z}‘}–p•€”i‘„Žk*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ%ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ.ÿ.ÿ.ÿ.ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ3ÿ2ÿ2ÿ3ÿ3ÿ2ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ*ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ(ÿ(ÿ&ÿ&ÿ%ÿ%ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ$ÿ$ÿ%ÿ%ÿ$ÿ$ÿ&ÿ&ÿ%ÿ&ÿ&ÿ%ÿ&ÿ(ÿ*ÿ*ÿ-ÿ5ÿEÿYÿnÿqÿaÿ`ÿ[ÿXÿ^ÿcÿdÿ\ÿSÿXÿ[ÿ\ÿ]ÿsÿÿ¡ÿ˜ÿ”ÿ”ÿÿ‰ÿŒÿÿŽÿ€ÿ`ÿUÿWÿeÿiÿiÿdÿfÿdÿgÿhÿrÿzÿqÿmÿhÿeÿ]ÿKÿAÿ0ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ'ÿ(ÿ(ÿ*ÿ+ÿ+ÿ,ÿ/ÿ:ÿVÿhÿtÿÿ–ÿÿ“ÿ—ÿ˜ÿ”ÿ’ÿŽÿ~ÿyÿyÿyÿ…ÿ•ÿÿ†ÿ„ÿÿƒÿˆÿŽÿ…ÿ‰ÿÿÿ’ÿ•ÿ—ÿ–ÿŒÿˆÿuÿjÿgÿfÿfÿdÿ`ÿ^ÿ\ÿZÿQÿFÿ=ÿ;ÿ[ÿÿ–ÿ‘ÿŠÿŽÿŒÿˆÿ‰ÿ‚ÿpÿfÿaÿ\ÿeÿÿ£ÿŸÿœÿ˜ÿŽÿÿ•ÿŸÿ¢ÿÿ–ÿ–ÿ’ÿ‘ÿ‰ÿ‡ÿ—ÿ’ÿ“ÿ“ÿ’ÿ‘ÿ‘ÿÿ‘ÿˆÿ{ÿgÿMÿNÿfÿ¦ÿ£ÿ•ÿ‹ÿzÿoÿeÿUÿHÿDÿUÿtÿ‰ÿ€ÿhÿjÿyÿŽÿ—ÿ’ÿ’ÿ”ÿÿ‰ÿ‚ÿÿ€ÿyÿrÿkÿiÿfÿQÿ/ÿ,ÿ.ÿ/ÿ5ÿ8ÿ;ÿ=ÿBÿIÿMÿPÿQÿQÿPÿPÿQÿOÿNÿPÿRÿSÿQÿCÿ0ÿ0ÿ>ÿ?ÿ8ÿ;ÿ>ÿ:ÿ?ÿCÿCÿ=ÿ>ÿ=ÿAÿJÿGÿIÿXÿ[ÿJÿ^ÿkÿÿŠÿ†ÿƒÿuÿXÿFÿIÿRÿDÿ<ÿNÿYÿlÿtÿtÿsÿqÿnÿiÿdÿ_ÿ]ÿ\ÿ[ÿ[ÿ[ÿ[ÿ[ÿZÿZÿ[ÿ\ÿ\ÿZÿXÿYÿUÿRÿOÿNÿMÿNÿNÿNÿOÿSÿVÿYÿ\ÿ`ÿcÿfÿhÿmÿrÿvÿ{ÿÿ€ÿÿÿ€ÿ}ÿ}ÿ~ÿ~ÿÿÿƒÿ…ÿˆÿ‡ÿ‡ÿ‡ÿ‡ÿ…ÿ…ÿ…ÿ…ÿ„ÿƒÿ„ÿ„ÿ…ÿ…ÿ‡ÿ‡ÿ‡ÿ‡ÿ‡ÿ†ÿ‡ÿ…ÿ„ÿ‚ÿ€ÿ}ÿyÿrÿrÿqÿqÿpÿoÿmÿiÿdÿ^ÿXÿUÿUÿXÿ^ÿgÿpÿxÿ‚ÿ…ÿˆÿ‹ÿÿŒÿŠÿŠÿ‰ÿ‡ÿ†ÿ†ÿ†ÿ†ÿ†ÿ‡ÿ‡ÿ†ÿ…ÿ€ÿzÿsÿnÿmÿlÿ€ÿ“ÿ‘ÿ‹ÿŠÿ‰ÿ‰ÿŠÿ‰ÿ‡ÿ…ÿˆÿ‡ÿqÿnÿqÿpÿoÿpÿoÿmÿjÿlÿtÿ{ÿˆÿŽÿÿÿŽÿŒÿŒÿ‹ÿ‹ÿŠÿŠÿÿÿÿ’ÿ–ÿ•ÿ”ÿ“ÿÿŽÿ+|+x+|+x+|+x*|*x*|*y+|+z+|+z*|*z*|*{*|*{)|){)|){)})|(}(|(})|(}(|(~(|(~'|&~&{'~'{'~'~'~(~(~(~(~(~(~(~'~'~'~'(~(&~&~%~%|%~%}%~%~%~%~%~%~%~%~%~%}%~%|%~%|%~%{$~$|$~$}$~$}$~$|#~#z$~$z$~$z$~${$~${$~${$~${$~$|$~$|$~$|$~%|&~&|&~'|'~'~'~'~(~(~(~(~)}++}+,},~-}.~.|.~-|/~1|1}1|1}0|0|0|2|1|1}2|2}1|0|/|1|0|/}0|.}.}.~/}/.|-€-|-,},‚,}+‚+~+‚+~)‚)}))})(}('}&%~%€%~%$~$€$~$€$~$$~$$~$$~$%}%&}&%~'€&&*‚.x4†BnZ‰rdzƒjgb}^yVxW‡Yv\YvOŽCtKQuT‘Zxm“‡{—•†}‡•’~ŽŒ‰‘}Œg|U’R}[“i~b‘d€jhcdjŽt„p‹u‡m†p‡lw…xzgD~%}'&}*…'}(‡'}&ˆ'}'‰(}'Š&})Š(|)‰({*Œ+z.7yEVxftv”Ž’s‘”q–”nm‹’€l’|k‘„m”’o‡‘‚p‚‘€q†‹t‹ƒw•w’“x““v‡„uƒ‘‚qƒ“~px–xnv˜pnb™]pZ˜QpH˜?q?–}o›•gŠ•Šbˆ—„e|™mm\˜_p`—_mc™x`•œ™V¢™¦Wž’ˆYz›“aš›—c’ž“dž‡^s¡†Y ˆZ‘¢•`“ŸŽ`‹¡Š_ŠŸˆ`€|d`T^y¡ªZ£P•œ‡SvŸk`]›LhDšKc]›xU‹¡’S‘Ÿ~V ›]™˜]œ`‰œ‚d|˜xnt“ntfay^ŠV~4‡-~.ˆ)|*ˆ.{3‡8}<†B~F„JMMM~N‚M{NƒOzK‚H{?ƒ3z,ƒ/x7…BqFˆ7l?‰FlFŠ@mK‰OkL‰In>†BsM„It@„LmR„Th_ƒbkt€†p…€wx‚KAƒJ…Z€a…P}Z‚o}p€q}s€s~qn~je_‚]~[‚Z}Z‚Z}Z‚Z}Z‚Z}Z‚Z~Z€Z~X~WT{P€NyMLzLMzMN{RUX}\‚a{c…dwg‰jsqŒuoz~l‚’ƒkƒ’€l}oq~‚r„ˆrˆ‡s‡‹‡s†‹…t„Œ…t…Œƒsƒƒs„…r…Œ†r†‹‡v‡…ˆz‡}ˆ…wƒy~€{€v{sˆqvqpspŽnslhudŒ]yX‰SzVˆ\v`Šhqryn‚Ž†o‰Œ‹rŒŠ‹u‰ˆŠv‰‰‡v†‰†u†‰†t†‰†u†Š…v…‰€xyˆrzn†kww€Œ{xŒ€ŒpŠŠrŠ‚Štˆ‚‡v‹‰}u€l‡n{mŒnxpŒpxp‹mzoŠtx|ˆxŠƒŽ|€Ž€Œ€‹}z’~”s—”h“ƒ’g„j+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ&ÿ&ÿ&ÿ'ÿ%ÿ%ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ)ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ.ÿ.ÿ.ÿ/ÿ0ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ2ÿ1ÿ/ÿ/ÿ.ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ)ÿ(ÿ(ÿ)ÿ(ÿ(ÿ(ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ'ÿ'ÿ,ÿ1ÿ@ÿVÿuÿ|ÿuÿfÿ^ÿWÿYÿfÿdÿWÿQÿLÿEÿIÿVÿUÿ\ÿdÿxÿ{ÿ|ÿŠÿÿŠÿŽÿŽÿ†ÿlÿZÿ]ÿ`ÿkÿgÿiÿkÿnÿlÿbÿhÿoÿpÿkÿoÿpÿsÿhÿmÿrÿÿ|ÿdÿ<ÿÿ)ÿ,ÿ,ÿ)ÿ(ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ*ÿ*ÿ-ÿ;ÿNÿZÿbÿxÿ“ÿŒÿŽÿ•ÿ‘ÿÿŽÿÿ‰ÿ|ÿ…ÿ†ÿ…ÿ†ÿÿ”ÿ‹ÿ„ÿ‚ÿ€ÿÿ…ÿŒÿ„ÿ|ÿ‹ÿ‘ÿÿ’ÿ‘ÿ”ÿšÿ¡ÿÿ—ÿœÿŸÿ˜ÿÿ~ÿhÿ]ÿ\ÿYÿPÿHÿ=ÿuÿœÿ•ÿÿŠÿˆÿÿnÿZÿ`ÿpÿpÿkÿnÿpÿ‚ÿÿ•ÿÿ¦ÿ¡ÿÿˆÿÿŽÿÿŽÿÿ~ÿsÿÿ‚ÿ‰ÿÿÿ„ÿÿ‚ÿ†ÿˆÿ„ÿ~ÿ~ÿpÿhÿƒÿ¥ÿœÿ™ÿ•ÿ‰ÿyÿoÿcÿRÿDÿCÿHÿnÿÿÿŽÿyÿÿšÿÿ›ÿ‘ÿŒÿ‰ÿÿyÿsÿkÿfÿ^ÿ[ÿZÿVÿ@ÿ,ÿ+ÿ(ÿ'ÿ)ÿ)ÿ+ÿ/ÿ4ÿ7ÿ9ÿ<ÿ?ÿ?ÿ?ÿ?ÿ<ÿ8ÿ1ÿ,ÿ*ÿ+ÿ.ÿ3ÿ5ÿEÿUÿ>ÿ6ÿ;ÿ?ÿEÿIÿKÿ?ÿBÿQÿNÿJÿ<ÿLÿLÿNÿUÿVÿ_ÿhÿyÿ‚ÿ„ÿrÿ<ÿ=ÿEÿRÿbÿ[ÿ`ÿoÿpÿqÿsÿsÿqÿnÿjÿeÿ_ÿ\ÿ[ÿZÿZÿZÿZÿZÿZÿZÿZÿ[ÿ\ÿYÿWÿUÿRÿOÿNÿJÿIÿIÿKÿLÿNÿRÿUÿYÿ]ÿ`ÿbÿdÿgÿiÿpÿtÿyÿ}ÿÿƒÿƒÿÿ€ÿ€ÿ€ÿ€ÿÿƒÿ„ÿˆÿŠÿˆÿ‡ÿ‡ÿ†ÿ„ÿ„ÿ„ÿƒÿƒÿƒÿƒÿ„ÿ…ÿ…ÿ†ÿ†ÿ‡ÿ‡ÿˆÿˆÿ‡ÿ…ÿƒÿÿ~ÿzÿvÿrÿqÿqÿpÿpÿnÿlÿhÿdÿ\ÿXÿUÿWÿZÿaÿjÿrÿyÿ‚ÿ†ÿ‰ÿ‹ÿŒÿŒÿŠÿ‰ÿ‡ÿ‡ÿ†ÿ†ÿ†ÿ†ÿ†ÿ†ÿ†ÿ†ÿƒÿ~ÿwÿqÿkÿqÿ‰ÿŒÿŒÿŠÿ‰ÿŠÿ‰ÿˆÿ‡ÿ†ÿˆÿŠÿ{ÿmÿqÿqÿrÿrÿtÿvÿvÿwÿxÿ}ÿ„ÿŠÿ‹ÿÿÿŒÿ‹ÿ‹ÿŒÿÿŽÿÿÿ’ÿ’ÿ“ÿ–ÿ”ÿ“ÿ‘ÿ‘ÿÿŽÿ+|+y+|+y+|+x+|+y+|+y+|+z*|*z*|*z*}*{*}*{*}*{)}){(}(|(}(|(~(|)~)|(~(})~)}'~'}(~'}'~'~'~'~(~(~(~(~(~('~''~'~'~'~&~&}%~%}$~$~$~$~&~&~%~%~%~%~%~%~$~$|$~$|$~${$~${$~$}$~$}$~${#~#{$~$z$~$z#~#|$~$|$~$}$~$}%~%}$~$}$~$}$~%}%~&}&~&}&~&|'~'}'~(})~)|*}+|+}+}+}+~-}.~.{.}0{0}0|0}1|1}0|1|1|1|0}0|1}1|1|1|0|.|-|-}-|-}-|,-|.-|,+|++}+*}*)}++})(}(€'}'€&}&'}&€%~$#~#$~$€$~$€#~#€#~#€#~#€$~$€%}%€%|%&|&~(€+|1†=qUˆrdz†v_sgfUQw]€q}t~c{]~]|QzI‚Jz[‰bzcs|p’ƒ~Š‘†~‡Œ}ˆŒq{S’P|W”l~u“mp‘m€pnmŽm‚smƒoŒj†rˆtŠmƒfŠsr‡~|‹„y`€7€%}(„+}*ˆ+|+‰*~(Š1}9Š-|)‰(|(Š)z.‹;yMZxc…vŒˆsŒŽpŒ‰n†‘‹n‰‘ˆo‡‘n‹‘ˆk’•l‘“ˆo‚’}r}‘}s„’‰rl“HrX›bqf¡wp“Ÿ™qœš¥t¦Ž¬rªŒ¦n™•Škwšam^šZoS˜LpIšjo˜˜˜g—c„˜}fr›[ks›i‰š|gy™zd€š‚Y€‘Q¡™£V——ˆ\Š ˆb† ‡c† yaoŸp`|Ÿ‡b‹Ÿ„c~ „_ž™\Ÿ”X“—‚Yv uV‚¡›R•’Q¢†UzŸq^jœ`fPšCcTœ}SŠ¢‡J€¥sN…¥‘Y˜ž˜\œŒ`†›€fu—nnh’asZWxQ‰N~F‡0~)†(}(…'}(…'|&†'})„*|)ƒ)~(ƒ('ƒ&')++€.}1‚6x8„=nE†JbNˆDa>Š:f=‰ÿ0ÿ'ÿ)ÿ)ÿ)ÿ-ÿ;ÿNÿZÿiÿŒÿ‰ÿ†ÿ‰ÿ‹ÿ‡ÿ†ÿ„ÿ†ÿŠÿ†ÿƒÿ‡ÿ‹ÿ‘ÿÿ˜ÿ—ÿ‹ÿÿ{ÿzÿvÿzÿÿˆÿ`ÿ5ÿOÿYÿfÿqÿxÿ’ÿ¥ÿ°ÿ°ÿ±ÿ©ÿ¡ÿ˜ÿ†ÿnÿcÿ^ÿ\ÿPÿIÿcÿ“ÿ™ÿ“ÿ•ÿ‰ÿ„ÿvÿ]ÿuÿ’ÿ”ÿÿ‹ÿŒÿˆÿ„ÿˆÿ”ÿœÿ ÿ•ÿzÿxÿ~ÿ‚ÿ}ÿxÿtÿqÿÿ’ÿ”ÿ‚ÿmÿtÿ‡ÿ˜ÿ¢ÿªÿ¦ÿ¡ÿ–ÿÿ~ÿˆÿÿÿ‹ÿƒÿ|ÿvÿqÿlÿdÿXÿEÿgÿ…ÿ‰ÿŠÿ…ÿ|ÿÿÿ“ÿ”ÿÿ…ÿ€ÿwÿnÿgÿbÿ]ÿUÿOÿIÿCÿ:ÿ.ÿ(ÿ&ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ(ÿ(ÿ(ÿ(ÿ'ÿ)ÿ*ÿ*ÿ*ÿ*ÿ,ÿ.ÿ1ÿ6ÿ:ÿ=ÿCÿDÿLÿHÿGÿ<ÿ5ÿ9ÿNÿGÿ<ÿ;ÿ@ÿGÿ:ÿ3ÿAÿIÿHÿTÿdÿwÿ‚ÿvÿeÿ\ÿVÿkÿhÿQÿVÿ]ÿfÿqÿrÿsÿrÿqÿmÿiÿdÿ^ÿ[ÿZÿZÿZÿZÿZÿZÿZÿZÿZÿYÿYÿWÿUÿRÿOÿNÿKÿJÿJÿJÿKÿLÿOÿSÿVÿYÿ^ÿ_ÿbÿbÿdÿhÿlÿqÿxÿ}ÿÿƒÿƒÿ‚ÿÿ€ÿ€ÿ‚ÿƒÿ„ÿ‡ÿŠÿŠÿ‡ÿ†ÿ†ÿ…ÿ…ÿ„ÿƒÿ‚ÿÿÿ‚ÿƒÿ…ÿ†ÿ‡ÿˆÿˆÿˆÿ‰ÿˆÿ‡ÿ†ÿ„ÿÿ}ÿyÿuÿrÿpÿpÿoÿoÿmÿjÿfÿbÿ\ÿWÿUÿYÿ_ÿfÿmÿuÿ|ÿ‚ÿˆÿŒÿŒÿ‹ÿ‹ÿ‰ÿ‡ÿ‡ÿ†ÿ†ÿ†ÿ…ÿ…ÿ†ÿ†ÿ†ÿ„ÿÿ{ÿvÿpÿ‚ÿÿ‹ÿ‡ÿ…ÿ‰ÿˆÿˆÿˆÿˆÿ‡ÿ‰ÿŠÿ{ÿyÿÿƒÿ†ÿ‡ÿ‰ÿŠÿ‹ÿŒÿÿÿŽÿŽÿŽÿÿÿÿŒÿÿŒÿŒÿÿŽÿÿ‘ÿ‘ÿ‘ÿ‘ÿ‘ÿÿÿÿÿÿÿ,|,z+|+z+|+y+|+z+|+{+|+|*|*|+|+|*}*}*}*})})})})}'}'|'}'|'~'|'~'|'~'}'~'}'~'}'~'}'~'}'~'}(~(~(~(~(~()~)(~('~'&~&&~&%~$%~&%~%%~%%~%%~%%~%~%~%~$~$}$~$}$~$|#~#|$~$}$~$}$~$|$~$|$~$|$~$|%~%{#~#{$~$|$~$|$~$|$~$|$~$|&~&|&~&{&~&|&~(|)~){)}*z,},|,},}-}-}.}.}.}.}.|.}/|/}.|/|0|0|0|0|/|/|/|/}.|.}-|,~+|+~,|,€,|,€,|,,|+*}))}))}((}('}'€&}&€%}%%}$#~#"~""~"~"~"~"~""~""~"€#~#€$~$€%~%~%€(z+„5sF‰dhyˆxbs†w`|„|ez‚zh{‚vjtjk_€UnE€o3„+r1‚?nP‚[hc…be^„jnj€V|W~ir~d\~[`~l€q}s€qpmi‚d~^‚[}Z‚Z|Z‚Y|Y‚Z|Z‚Y|Y‚Y}YV~TQM~K€J}HH}JKM€P€S}V‚Zz\…^x`‡bvdŠgskŒqov{j€“ƒjƒ“k‘nƒo„†q‰‹rŠŒˆt‡Œ†t…Œ„t„ŒƒrƒŒs€ƒs……r…Œ‡t‰‰ˆxˆ‹|†z‡€…x„€€}~~yƒsyrŠqspŽornlsieuaŒ\wX‰Vw\‰`tfŒmotŽ{n‡pŠŒŒr‹‹‹u‰Š†u†Š†t†Š†t…‹…s†‹‡u…‹ƒv€Š{vp„~zwŽ‹qˆ†t†‡v‡‚†u‡‚ˆtŒ‚…v„‚ˆsŒ‚p„n„jŽ„ŽiŽ„ŽhŽ„hŽ„Œf„ŒgŒ…Œh‹„‹hŒ„hŽ„g„f„Že„c„e„h,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ#ÿ#ÿ$ÿ$ÿ%ÿ%ÿ$ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ(ÿ)ÿ)ÿ)ÿ*ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ/ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ$ÿ$ÿ#ÿ#ÿ"ÿ"ÿ#ÿ#ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ"ÿ"ÿ$ÿ$ÿ%ÿ%ÿ%ÿ*ÿ0ÿDÿbÿxÿwÿqÿvÿ|ÿ~ÿÿ|ÿzÿxÿtÿpÿhÿ\ÿPÿHÿQÿaÿjÿ}ÿ~ÿjÿwÿŠÿ‚ÿƒÿ‚ÿzÿWÿQÿWÿqÿyÿmÿrÿ{ÿqÿqÿqÿuÿyÿtÿwÿuÿ€ÿoÿpÿ~ÿyÿtÿoÿ_ÿuÿrÿbÿmÿŠÿŒÿÿŽÿrÿNÿ3ÿ(ÿ(ÿ(ÿ)ÿ*ÿ*ÿ+ÿ+ÿ+ÿ4ÿGÿTÿ^ÿxÿŠÿ…ÿ€ÿÿÿÿ{ÿpÿtÿxÿ|ÿ}ÿ€ÿ„ÿ„ÿŽÿ—ÿ™ÿ•ÿŒÿ€ÿvÿpÿmÿqÿzÿÿ„ÿWÿ:ÿOÿ]ÿsÿ‡ÿŸÿ­ÿ°ÿ¬ÿ®ÿ§ÿŠÿ]ÿIÿMÿNÿQÿNÿNÿ_ÿÿšÿ˜ÿšÿÿ€ÿjÿPÿ\ÿ…ÿ‰ÿŒÿÿŽÿŠÿˆÿÿ…ÿŽÿ“ÿ{ÿjÿkÿoÿxÿ€ÿ•ÿ§ÿ«ÿ§ÿ£ÿ—ÿ{ÿSÿJÿJÿGÿYÿƒÿ˜ÿÿ„ÿŠÿšÿ—ÿ“ÿˆÿ}ÿsÿoÿnÿnÿoÿfÿPÿAÿ]ÿ‚ÿŽÿŽÿÿ…ÿ{ÿ‚ÿ€ÿ…ÿ€ÿtÿbÿGÿ:ÿ4ÿ3ÿ4ÿ2ÿ.ÿ(ÿ%ÿ%ÿ#ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ%ÿ$ÿ%ÿ%ÿ%ÿ&ÿ%ÿ'ÿ(ÿ*ÿ,ÿ/ÿ0ÿ1ÿ5ÿ?ÿ5ÿ3ÿ/ÿ*ÿ+ÿ.ÿ2ÿ>ÿ^ÿRÿ?ÿ@ÿQÿJÿ<ÿQÿGÿ-ÿ+ÿ4ÿAÿZÿ_ÿRÿTÿIÿOÿtÿrÿ€ÿÿvÿnÿSÿQÿ]ÿhÿpÿrÿqÿpÿmÿiÿdÿ^ÿ[ÿZÿXÿXÿWÿWÿXÿXÿYÿYÿXÿXÿUÿSÿPÿLÿJÿHÿHÿHÿIÿKÿMÿPÿSÿVÿZÿ\ÿ^ÿ`ÿaÿcÿfÿjÿpÿuÿzÿÿƒÿƒÿÿÿÿ‚ÿƒÿ„ÿˆÿŠÿ‹ÿŠÿ‡ÿˆÿ†ÿ…ÿ„ÿƒÿƒÿƒÿƒÿƒÿƒÿ„ÿ‡ÿ„ÿ‡ÿ‰ÿˆÿˆÿ‰ÿ‡ÿ‡ÿ…ÿ„ÿ‚ÿ|ÿwÿsÿrÿqÿpÿoÿnÿlÿiÿeÿaÿ\ÿXÿXÿ\ÿ`ÿhÿoÿvÿ}ÿƒÿ‡ÿŠÿ‹ÿ‹ÿ‰ÿ‡ÿ†ÿ†ÿ†ÿ†ÿ†ÿ…ÿ…ÿ†ÿ†ÿ…ÿ‚ÿ~ÿzÿ|ÿÿŽÿŠÿŠÿ‰ÿ‡ÿ†ÿ‡ÿ‡ÿˆÿŠÿÿŒÿ‹ÿÿ’ÿ”ÿ”ÿ‘ÿÿÿÿŽÿŽÿŽÿŽÿÿÿÿŒÿŒÿ‹ÿ‹ÿŒÿ‹ÿ‹ÿŒÿŒÿŒÿÿÿÿÿŒÿŒÿŒÿŒÿŒÿÿÿ,|,z+|+z+}+y+}+z+|+{*|*|*|*}*|*}*}*|)})|)})}(}(}(~(|'~'|''|''|'~'}'~'}'~'}'~'}(}(}'}'}'~((~()})(}((}('}'&}&€&}&€&~&€'~'€&~&€&~&€&~&€%~%€%~%%~%%~%%~%~#~#}#~#}#~$~$~$~$~$}%~%}#~#|#~$|%~%|%~%|%~%{$~${$~${$~${$~$z&~&z&%{&&{(~({)~){(~*|,~,|,},}-}-}-{-}-{-}.|.}/|/}/{/}.{.}-{-|-{-~-z--z--|,€+|+€*|*€+|+€*}*€*})€)})€)}(€(}(€&}&€&}&&}$$~$~#~#~#~#}!~!~!~!~!~!~!~!"~""~"#~#$~%|%~${(‚/v>‡Wms‡xfq†taz…|b|‚zd{‚zezrhj€fi[€NnIW{i{vˆˆz}’p~€’‚€Œ‚‰i~KŽN}P‘c~n”n€h—i€k”p‚tuƒqŽf„u‹xƒyŒp„oŒs‰vˆk‰e„XŠ`ƒpˆl‚W‡t—…’j‘‚mldgw?~2'~)†)|)‰({*Š5zAŒLxYŒcw‹ˆt‚‹}r}n|’vjn“pks“tlv“rmv“}j„“Žj’•lŒ•†o{’urr’prp‘zt“…uW™Qr`¡rmŠ¤šo®’²n°‹©jœCi=­DnE®DoK©PoP¥[nŠ›lš—žhš—iTš:i=œdcsœ{Y‚›…Yš{\x›yZ‚|]hž_aaŸiax ˆ^–¢TŸ˜œO›š“Z~œUiFœHoOœkj‹˜–b•˜Z’ššX”œ‹ZtŸr_sk_hŸk`oŸadHœFeSœl[x €P{£rTn¡y\yž~d}œckB›4q0—.v,‘*z((|'ˆ&}$‡#}$†$~$†%~%„$~%ƒ&~&‚%$%€'€'€,/€1~0€2{2‚4v7‚3u-‚(x&‚&|)‚*y.‚El_…PaX‡Y^ZˆYbV†Ij,†+o-ƒ9oQ„PjT†Lf\†yk}‚}u‚‡yzSzQO{Xc~p~pq}p€m~h‚c~]‚Z}X‚W|WƒW|WƒX|VƒX|XƒW|WU~Q€O€KJ€GFF€HJLO‚R|UƒY{[…^x_‡`uaŠeriŒnntyi”‚i‚“‚j‚‘‚nƒŽƒp„ˆq‹‹rŠŒˆtˆŒ†t…Œ„t„ŒƒrƒŒƒrƒ„r„‡rˆŒ‰u‰†Šy‰~‰}‰y‡€†zƒ€‚~|~v…syr‹qronqmkshŒeu_‹[wX‰Xv^Šcqjrny~nƒŽˆp‹Šs‰‹‰u‡Š…t…‹…s…‹…s…Œ„t…Œ…s„‹‚v|‡|y‘}‘sˆ‚ˆt†‚…v†‚ˆs‰‚o‚‘nƒ’i’…“e’…c…c„dŒ†ŽeŽ„ŽhŽƒŽiŽ„Žl„nƒo‚pƒoŠƒŒnŒƒŽkŽƒ‹h‹ƒ‹h‹ƒŠf‹ƒ‹f,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ$ÿ$ÿ%ÿ%ÿ#ÿ#ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ&ÿ&ÿ&ÿ%ÿ&ÿ&ÿ(ÿ(ÿ)ÿ)ÿ*ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ%ÿ$ÿ$ÿ$ÿ#ÿ"ÿ#ÿ#ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ"ÿ"ÿ"ÿ#ÿ#ÿ#ÿ%ÿ'ÿ'ÿ,ÿ8ÿLÿkÿtÿrÿrÿwÿ{ÿ|ÿzÿzÿ|ÿyÿxÿqÿlÿbÿVÿNÿJÿ[ÿvÿ‡ÿˆÿxÿuÿƒÿ}ÿ}ÿ€ÿyÿYÿGÿGÿSÿiÿnÿoÿkÿeÿkÿfÿjÿsÿmÿhÿlÿ~ÿÿrÿoÿsÿoÿhÿiÿgÿGÿjÿnÿTÿnÿ”ÿ†ÿqÿhÿfÿtÿ?ÿMÿDÿ)ÿ(ÿ,ÿ*ÿ)ÿ2ÿ<ÿLÿYÿfÿ…ÿ‡ÿÿzÿyÿyÿ{ÿuÿlÿjÿnÿnÿjÿkÿkÿnÿrÿÿ‡ÿŠÿˆÿ‡ÿ…ÿzÿqÿtÿrÿnÿvÿƒÿˆÿeÿ`ÿvÿÿ›ÿªÿ¯ÿ¨ÿwÿiÿQÿBÿ=ÿ=ÿ@ÿPÿYÿZÿ[ÿ‡ÿ”ÿÿÿœÿ…ÿVÿ8ÿAÿSÿgÿwÿ{ÿvÿrÿpÿoÿpÿsÿpÿjÿlÿsÿ~ÿ‰ÿ–ÿ›ÿ—ÿÿŽÿŒÿÿ}ÿTÿ=ÿ>ÿNÿ{ÿÿÿ•ÿ•ÿ—ÿ•ÿŒÿ~ÿcÿSÿ\ÿ`ÿbÿeÿfÿUÿHÿBÿFÿWÿsÿwÿqÿiÿ`ÿjÿpÿuÿuÿSÿ4ÿ0ÿ1ÿ/ÿ,ÿ*ÿ(ÿ(ÿ'ÿ%ÿ$ÿ#ÿ$ÿ$ÿ$ÿ%ÿ%ÿ$ÿ%ÿ%ÿ$ÿ&ÿ(ÿ+ÿ-ÿ.ÿ1ÿ2ÿ4ÿ6ÿ3ÿ2ÿ2ÿ0ÿ.ÿ(ÿ&ÿ%ÿ%ÿ'ÿ)ÿ+ÿ<ÿSÿXÿNÿVÿTÿ\ÿWÿHÿ?ÿ/ÿ2ÿ@ÿJÿOÿRÿXÿRÿ\ÿlÿsÿyÿÿÿvÿmÿYÿMÿWÿnÿrÿqÿqÿmÿhÿcÿ]ÿZÿXÿWÿWÿWÿWÿWÿWÿXÿWÿWÿWÿTÿPÿNÿIÿGÿFÿEÿEÿGÿIÿLÿOÿRÿUÿYÿ[ÿ]ÿ]ÿ_ÿaÿeÿiÿkÿqÿwÿ}ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿƒÿ…ÿ†ÿˆÿ‹ÿ‹ÿ‹ÿ‰ÿ‡ÿ†ÿ…ÿ„ÿ„ÿƒÿƒÿƒÿƒÿ„ÿ„ÿ†ÿ‡ÿ‰ÿ‰ÿ‰ÿŠÿŠÿˆÿˆÿ†ÿƒÿ€ÿ{ÿvÿsÿrÿqÿoÿnÿmÿkÿhÿcÿ^ÿ[ÿXÿZÿ`ÿeÿlÿtÿzÿ~ÿƒÿˆÿ‹ÿŠÿˆÿ‰ÿ‡ÿ…ÿ…ÿ…ÿ…ÿ…ÿ…ÿ„ÿ…ÿ…ÿ„ÿÿ}ÿŠÿÿÿ‰ÿ‡ÿ‡ÿ‡ÿŠÿŒÿŽÿÿ‘ÿ’ÿ’ÿÿÿ‘ÿÿÿŽÿÿŽÿÿÿÿŽÿÿÿŽÿŽÿÿÿÿŒÿŒÿŒÿŒÿŒÿŒÿŒÿÿÿ‘ÿÿÿŽÿÿÿŠÿŒÿ‹ÿ‹ÿ+}+z+}+z+}+y+}+z*|*{*|*|*|*}*|*})})})})})}(}'}'})~)|(~(|('|''|(~(}'~'}'~'}'~'}'}'}'}(}(}()}))})(}((}((}('}'€(}(€'~'€&~&€'}'€&}&€&}&€&}%€&~&%~%%~%%~%~$~$}$~$}$~%~%~%~&~&}&~&}%~%}%~%}%~%}&~&}$~$}%~%}%~%{%~%{%~%{&~&{%&{&'{(~)|)~*|*~+|+~+|-}-}-}-}.~.}.~.}-|-}/|/}-{-}-{-}-{-|-{-~.},,},,|+€*|)€)|)€(|(€)})€(}(€(}(€'}&€&}&€%}%€$}$%}$~#~#~"~"~#~#}!~!~!~!~!~!~!~!!~""~#"~"$€%|(ƒ+u5†Gkaˆseq…sev„{h|ƒyfy‚ye{‚xeusfj€`gU€JoJc|z‰‡zu‘}Žz€{‹|~n‹M}EL}]’h~i”i~j—e€b–d€g”jm’l‚uŽw‚„rƒc‚…‚Šx†oˆi†G†Pˆwƒ_‰E}†ktd‚fr[hvhn|v€Z‚:}+‰+|+Š4z?‹KxWŒ`v„„s~Žvqywmv“sjl”jhk”hjc•dji”fki“nkq”ukw”xo|”€qu’qrn’ntr’xs‚•ql›znŠ¡•m¢™žkw¢alw«Qp=§7r@¢Xqn pphž`pŽšl”™j”˜j`›9l@afoœ†^œr[mœj]iœkapnbjœkcw†`Ÿ˜¤Vœ—ŒMƒ¢}U{Ÿwad›HlA™=oMšrj„œ†b‹›Š]™^†œyeMœ/n3›AmQ™ah_›PiJ›KgU™o`zœrZlžb][ždcp›pkk˜Jr,•/w0’/y+Ž*z(‹(|'‰&}&‡%}$†%}%†%}&„%(ƒ'(„)€+€0‚2~1‚2|1ƒ5z7ƒ4v.„+x*‚){%‚&')€+)+7yA„FnQ„QcU†M^Vˆg`C‡Ah@„JmM„RkS†MgXˆVm_…]yf€k{to€a€KV€i~rr~p€mg‚c~]‚Y}W‚W|WƒV|VƒW|WƒW|WƒW|VS~O€M€HF€DBC€EHK~N‚R{UƒXzZ†]w^‰^u`ŠdrhŒknpvi|”€i€“j‘‚n„Ž†p‡‰q‹‹rŠŒˆt†Œ†t…Œ…t…ŒƒrƒŒ„r„„rƒ†s‡‹ŠvŠ…Šz‰~ˆ~Šy‰†{ƒ€€z}u‡rwqqromqmjshŒdu_‹XvZŠ_ta‹gpoŽum|€n…Ž‰pŒŠsˆ‹ˆu†Š†t…Œ…s…Œ…r…Œ…s…Œƒt‰vˆ€‘|t‹€‡s‡‡rŠŒl„’i’„’g‘…dŽ…eŽ…dŽ…e…hƒlŽƒŒoŒ‚‹uŠŒy}Ž€~‹~‹€‹}Š€Œ}x’q’‚‘l‚m‚ŒlƒnŒƒŒn+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ)ÿ)ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ)ÿ*ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ#ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ%ÿ&ÿ&ÿ'ÿ(ÿ)ÿ)ÿ*ÿ*ÿ*ÿ+ÿ+ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ&ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ#ÿ#ÿ"ÿ"ÿ"ÿ!ÿ"ÿ"ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ"ÿ#ÿ$ÿ$ÿ%ÿ'ÿ*ÿ3ÿCÿ[ÿnÿoÿoÿvÿ{ÿ{ÿwÿrÿuÿwÿvÿwÿuÿtÿgÿ]ÿUÿJÿIÿiÿÿÿˆÿsÿÿ|ÿzÿ|ÿ}ÿbÿIÿGÿPÿ`ÿaÿcÿfÿjÿfÿfÿhÿcÿUÿNÿVÿYÿXÿcÿgÿQÿ]ÿYÿWÿQÿVÿDÿ>ÿcÿoÿ]ÿÿ€ÿ‰ÿdÿWÿgÿ]ÿhÿfÿYÿVÿIÿ9ÿ/ÿ3ÿ>ÿJÿVÿZÿ~ÿ„ÿ}ÿzÿvÿuÿvÿrÿpÿoÿlÿiÿeÿcÿbÿaÿ^ÿ_ÿaÿgÿeÿcÿbÿyÿ}ÿqÿlÿnÿmÿlÿuÿ‚ÿ}ÿsÿˆÿÿ“ÿRÿSÿ[ÿQÿ>ÿ:ÿJÿkÿ„ÿŽÿ…ÿrÿcÿÿÿÿ‰ÿ{ÿ‚ÿpÿLÿQÿkÿvÿÿŠÿyÿnÿiÿfÿbÿeÿjÿiÿoÿyÿ—ÿ¦ÿžÿŽÿzÿvÿqÿqÿkÿMÿ=ÿ:ÿ<ÿKÿcÿuÿÿ…ÿˆÿ—ÿÿ‚ÿpÿDÿ/ÿ,ÿ/ÿ?ÿcÿgÿ^ÿYÿ[ÿhÿ„ÿˆÿÿsÿfÿ]ÿfÿmÿjÿ_ÿFÿ*ÿ-ÿ0ÿ-ÿ+ÿ*ÿ(ÿ'ÿ&ÿ&ÿ&ÿ%ÿ$ÿ%ÿ%ÿ"ÿ#ÿ%ÿ)ÿ)ÿ*ÿ*ÿ/ÿ4ÿ5ÿ6ÿ8ÿ>ÿFÿCÿDÿ<ÿ+ÿ'ÿ(ÿ&ÿ&ÿ&ÿ(ÿ)ÿ)ÿ)ÿ0ÿ7ÿ?ÿIÿYÿ]ÿUÿ]ÿcÿPÿ@ÿEÿLÿFÿMÿWÿkÿ\ÿSÿWÿSÿ^ÿkÿyÿKÿ@ÿ]ÿLÿfÿkÿfÿqÿoÿmÿgÿcÿ]ÿYÿWÿVÿVÿVÿVÿWÿWÿWÿWÿVÿUÿRÿPÿKÿFÿEÿBÿAÿBÿDÿGÿKÿNÿRÿUÿXÿZÿ\ÿ]ÿ^ÿ^ÿaÿfÿjÿoÿuÿ{ÿÿÿ‚ÿ‚ÿƒÿ„ÿ†ÿ‡ÿŠÿ‹ÿ‹ÿŠÿˆÿ‡ÿ…ÿ„ÿ…ÿ…ÿƒÿƒÿ„ÿ„ÿ…ÿ‡ÿ†ÿˆÿŠÿŠÿŠÿ‰ÿ‹ÿŠÿ†ÿ…ÿÿ}ÿyÿtÿpÿrÿqÿoÿmÿmÿjÿhÿcÿ^ÿWÿYÿ_ÿdÿiÿqÿwÿ}ÿ€ÿ…ÿ‹ÿŒÿŠÿˆÿˆÿ†ÿ†ÿ…ÿ…ÿ…ÿ…ÿ…ÿ…ÿ…ÿ…ÿ‚ÿ…ÿÿÿ‹ÿ‰ÿŠÿŠÿŒÿÿÿŽÿÿÿÿŽÿÿŽÿŽÿÿÿ‘ÿ‘ÿÿÿŽÿŒÿ‰ÿ‡ÿ…ÿ…ÿ‡ÿŒÿÿÿÿŒÿ‹ÿ‰ÿ‰ÿ‹ÿŒÿÿ’ÿ“ÿ‘ÿÿÿŽÿÿÿ‘ÿ’ÿÿÿ+}+{+}+{+}+{*}*{*|*{*|*})|)})|)})|)~)}(~'}'}'}'}(~(}'~(}((}((}(~(|(~(|(~(|(~(~'}(~(}(~(}(~)})~)})~(}(~(}(}(}(~(}(€(}'€&~&(~('}'€'}'€&}&€&}&€'~'€%~%€%~%€&~&~&~&&~&&~&~&~&~&&~&&~%~%~%~%~%~%~%~%~&~&}%~%{%~%|&~&|&}&|&}&|&'{(({(~)|(~)|*~*|+~+|-}-}-}-}.|.}-|-}-{-}.{-}-|-}-{-}-{-}-|-},|+*|**|*€*|)€)|)€(|(€(|(€(}(€(~(€&~&€&}&€%}$€#~##~""~"!~! ~ ~ ~ ~ ~ ~ ~ ~!~"~!~!~"~"#$~$„'x0‡Bo\ˆnfl…mbqƒ{e{‚thqrdvƒxdx‚yewodf€\gRDpB}n{…y‰xyk€xŒxy‰qVŒF|H‘Q|b“h{j–m|f˜byU–TyN•DyK”H|H’E}FJ}EŽ>€887Š;‚BŠA…F‡^‡ZJ†ˆ{Œƒpuaƒ~tt‚pznaƒT~K‹H~AŒ;|>ŽJ{PVwyŽƒq}ynt’qmq“qis”sgo–hhe•_j[”ZnW“YoZ”ana•WnH”Iqq•urp“oti“ftk”ws‚—uo|ž‡m[¨OnV«Nr?¦u)’*x,Ž,z*Œ*z(‹'|&‰%}%‡$}#†$}$…#}"„$~&ƒ%&ƒ,€:€BE}E„DyGƒGyB…=y5…)|&ƒ&&‚&&‚()‚'*/}3:wDƒDkJ…WbdŠ`]Z‹VbF‰@l;…@mL†WlXˆZm\‰>tB„[{T€28f‚QN‚]}i‚rp‚mhb~\ƒX}VƒT|TƒU|WƒW{WƒW{WƒU{TƒQ~NJEA@@AC~FJ|MƒQzT…XyY‡Zw[‰\v]Š`schpm‘rlz“|j~“k€“ƒn……oŠŽ‹q‹ˆr‡Œˆt‡‹„t…Œ„t„Œ„r„Œƒrƒ„r††s‡ŠŠwŠ‚‹|Š|Šˆ{‡€„{€}x|sˆqupqqp‘mqljsgau]ŒZv[‹`rdjoqxm}n†Ž‹r‹‹s‹Œˆu†Œ†t…Œ…s…Œ…s…Œ…s…Œ…v‚„Žz’v‹€‡n‹lŽƒhƒgŽ„Žd„g„hŒ„‹k„k„Žn„Œtˆ†zƒƒ€z‚…ˆx‡zƒŒ{‹…Œ|‰†ŠyŠ‚{‘z”€“r‚mŽ‚ŒoŒ€q‘vŽx+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ%ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ(ÿ(ÿ(ÿ)ÿ(ÿ)ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ$ÿ#ÿ#ÿ#ÿ"ÿ"ÿ"ÿ"ÿ!ÿ ÿ ÿ ÿÿÿ ÿ ÿ ÿ ÿ!ÿ"ÿ!ÿ!ÿ!ÿ"ÿ#ÿ$ÿ%ÿ,ÿ<ÿVÿqÿpÿhÿnÿtÿyÿtÿmÿmÿpÿrÿsÿwÿxÿvÿnÿbÿVÿMÿ=ÿ:ÿhÿ†ÿ‡ÿrÿrÿwÿrÿwÿyÿkÿNÿCÿHÿUÿgÿiÿlÿvÿdÿWÿGÿDÿGÿAÿJÿPÿKÿKÿJÿJÿDÿGÿAÿ>ÿ;ÿ;ÿ@ÿFÿ?ÿOÿ]ÿ:ÿkÿ{ÿ…ÿˆÿ‡ÿ„ÿ~ÿtÿcÿTÿMÿIÿEÿAÿ@ÿFÿJÿQÿrÿ‚ÿzÿvÿsÿrÿoÿnÿpÿrÿnÿiÿeÿ_ÿ[ÿYÿOÿNÿTÿ`ÿ^ÿRÿFÿ2ÿGÿjÿmÿnÿjÿhÿhÿnÿvÿ€ÿ|ÿcÿ@ÿKÿNÿNÿEÿHÿoÿÿÿ¢ÿžÿÿjÿ\ÿoÿ…ÿ‹ÿ†ÿyÿ5ÿ1ÿLÿwÿ“ÿÿ‰ÿ€ÿwÿkÿdÿ^ÿ[ÿ^ÿcÿ\ÿfÿ™ÿŸÿ–ÿ‡ÿ|ÿoÿ\ÿaÿlÿ\ÿFÿBÿ@ÿDÿMÿWÿaÿkÿqÿwÿvÿuÿnÿUÿ8ÿ+ÿ-ÿ;ÿRÿhÿtÿ~ÿwÿdÿ^ÿdÿxÿ†ÿ†ÿ„ÿ„ÿ{ÿiÿ_ÿSÿ4ÿ&ÿ)ÿ-ÿ,ÿ*ÿ)ÿ'ÿ'ÿ&ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ#ÿ"ÿ"ÿ#ÿ$ÿ(ÿ/ÿ:ÿBÿDÿCÿ?ÿ:ÿ8ÿ7ÿ6ÿ0ÿ'ÿ'ÿ'ÿ&ÿ'ÿ(ÿ(ÿ'ÿ'ÿ*ÿ1ÿ6ÿ:ÿ=ÿ8ÿGÿQÿXÿ[ÿYÿVÿOÿIÿKÿCÿLÿYÿZÿRÿPÿFÿ6ÿQÿ\ÿMÿVÿjÿYÿJÿYÿrÿsÿpÿnÿhÿbÿ\ÿXÿVÿTÿTÿTÿSÿVÿVÿUÿUÿTÿSÿPÿLÿJÿEÿAÿ@ÿ?ÿ@ÿBÿEÿJÿMÿQÿTÿXÿYÿXÿZÿZÿ\ÿ^ÿaÿeÿjÿqÿwÿ}ÿ~ÿÿ€ÿƒÿ…ÿ†ÿˆÿ‹ÿ‹ÿŠÿ‰ÿ‡ÿ†ÿ„ÿ…ÿ„ÿ„ÿ„ÿ„ÿƒÿƒÿ„ÿ†ÿ†ÿ‡ÿŠÿ‹ÿ‹ÿŠÿ‰ÿ‡ÿ‡ÿ„ÿ€ÿ|ÿvÿsÿqÿqÿqÿpÿmÿlÿjÿgÿaÿ]ÿZÿ]ÿaÿfÿkÿsÿ{ÿ€ÿƒÿ‰ÿ‹ÿ‹ÿ‹ÿ‹ÿˆÿ†ÿ†ÿ…ÿ…ÿ…ÿ…ÿ…ÿ†ÿ†ÿ†ÿŠÿÿ‹ÿ‹ÿŠÿŒÿŽÿŽÿÿŽÿÿÿÿÿÿŒÿŒÿÿŽÿÿÿ‹ÿ‰ÿ…ÿ‚ÿ€ÿ|ÿyÿ{ÿ~ÿ‚ÿ‰ÿÿŽÿŽÿŒÿ‹ÿŠÿŠÿŠÿ‹ÿÿ“ÿ“ÿ’ÿÿŽÿÿ‹ÿÿ’ÿ‘ÿ‘ÿÿŽÿ,},{+}+{+}+{+}*{*|*{*|*}*|*})|)})|)~)}(~'}(})}(}'~'}(~(}''~))~)~)~(~(~)~)|(~(~(}(~(}(~)})~)})~)})~*}*~)}(~(}(€(})€*}(€(}(€)})€)|(€'|'€'|&€'|'€'~'€'~'€'~'€(~(~'~''~''~'~'~'~'~'~'~'~&~&~'~'~&~&~&~&~&~&}&~&{%~%|%~%|%%|''|''{(({(~({)~){*~+{+~+{+}+|,},|-|-}-|-}-{-}-{-}-|-}-{-},{,}+|+}+|**|*)|)€)|)€)|(€'|'€'|'€&}&€&~&€&~%€#~#€$~#€"~""~!"~"!~!!~!~ ~ ~!~!~!~!~!~!~!~!~##|#&x)†7pO‰mfr‡iek„qfvƒsfl‚igjkgoƒqgv‚wctkcc€ViK?r;j€|‹t{yŒrp‰xw‹bKH}Pa|g”_z`—hyW™JxF—DwI–CwH–NwK–LwK“MyE‘C{B‘@|>Ž?|>E@ŠA…J†D†:€<„wv„‡t„‚}zrb„U~MF}D’C}C‘B{FŽLxg„qw‘uns’qmn“lkn”oin–gib–[kX•[oS‘NqN‘CsL‘OrI”9s,–Fsj”jsf”jsh•jto–wsz™Gr< BsI¡IsGmq“˜¢r£“£r¡”rs—ap]–pŽ–†o‡—Co+š=l€˜_„Y{žw^h_e\›YhXšZeS›u]¡›˜UŸ€Tp¤[^P¤Yg\¡Ml;ž:oBœIlRWi[œagd›fejškca™Mi7˜,p7•Ik\•icn™vb~™vfj˜ycŒš\…z]š}ek˜\pJ”,v%(y*Œ*{*‹(z'‰&|%ˆ$}%†%}$…#}#„#}"ƒ"~"ƒ!~&ƒ/~7‚:~<€?~>~=~=~;€8~1(€''€'(€(€'€(&*1€;€=}7‚9xBƒGqI„[e^‡[^\‰GeFˆAlD†KqK‡IrVˆ`oR†@qY„Ruc‚n|^€J‚\sƒtp‚mg‚b~\ƒW}UƒT|SƒT|TƒT{T„T{T„S{RƒN~KGC@??~@A}E‚I{LƒPyT…WxX‡YvZ‰ZvZŠ\s^bpgnlt’xj}“~k€“ƒn†ˆo‰Ž‹q‹Šr‰Œ‡t†‹„t…Œ„t„ŒƒrƒŒƒrƒ„r…ˆtˆˆ‹y‹‹|Š|Šˆ{‡€„{€|ƒvzr‰qupŽpqp‘oqljsgau]ŒZu\‹`phŽnmu{l€…nˆŽ‹r‹Šs‰Œ†t†Œ…t…Œ…s…Œ…r…‹„r††ŠyxŒnŒ‚ŽkŽƒŒiŒƒ‹k‹ƒŒkƒmŒƒm„ŒqŠ„Švˆ†|‚}~„|{yŠyvxyq|€r…‹tŽ‰w†zŒ†Š{Š†ŒyŽ~“|“r’€nƒn‹‚‹t{‘~~Ž…,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ'ÿ(ÿ)ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ(ÿ)ÿ*ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ'ÿ'ÿ'ÿ'ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ#ÿ#ÿ#ÿ#ÿ"ÿ"ÿ!ÿ!ÿ!ÿ!ÿ!ÿ ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ#ÿ"ÿ#ÿ)ÿ0ÿFÿfÿtÿmÿkÿpÿtÿsÿmÿkÿhÿiÿjÿmÿpÿsÿuÿrÿkÿcÿVÿJÿ>ÿ6ÿfÿ—ÿ’ÿÿ~ÿqÿuÿxÿzÿ\ÿCÿHÿQÿeÿkÿ_ÿ]ÿ_ÿPÿDÿFÿIÿLÿGÿMÿQÿOÿMÿJÿJÿDÿBÿAÿ@ÿAÿ?ÿ@ÿBÿDÿCÿCÿFÿ@ÿnÿ„ÿ‰ÿ‡ÿƒÿzÿkÿYÿNÿKÿEÿCÿBÿBÿAÿDÿGÿ_ÿ…ÿxÿvÿtÿqÿnÿjÿiÿkÿjÿgÿaÿ^ÿZÿVÿPÿGÿ>ÿ6ÿ3ÿ;ÿ=ÿ8ÿ6ÿ7ÿVÿmÿgÿgÿfÿfÿgÿoÿwÿqÿFÿ:ÿ7ÿ;ÿ_ÿŒÿ£ÿ¦ÿ¤ÿ¦ÿžÿ‡ÿuÿ`ÿeÿyÿŒÿ†ÿ‰ÿKÿ1ÿ@ÿ„ÿ—ÿ‡ÿƒÿzÿsÿbÿZÿXÿVÿVÿNÿTÿˆÿ›ÿ’ÿ„ÿyÿjÿOÿNÿYÿPÿ;ÿ1ÿ9ÿBÿKÿQÿWÿYÿ\ÿ_ÿcÿfÿeÿ\ÿJÿ6ÿ0ÿ=ÿQÿcÿiÿkÿkÿuÿ|ÿ~ÿ™ÿœÿ’ÿ‚ÿzÿ|ÿwÿdÿTÿ>ÿ*ÿ&ÿ'ÿ)ÿ)ÿ*ÿ(ÿ'ÿ&ÿ%ÿ$ÿ%ÿ%ÿ$ÿ#ÿ#ÿ#ÿ"ÿ"ÿ"ÿ#ÿ*ÿ3ÿ:ÿ=ÿ?ÿAÿ>ÿ>ÿ;ÿ6ÿ3ÿ0ÿ+ÿ&ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ&ÿ)ÿ-ÿ2ÿ3ÿ1ÿ9ÿ<ÿ:ÿDÿUÿ[ÿRÿWÿ\ÿDÿ?ÿDÿQÿTÿUÿ^ÿiÿ]ÿLÿbÿwÿxÿ~ÿgÿDÿXÿkÿtÿoÿmÿfÿbÿ\ÿWÿUÿSÿRÿSÿSÿTÿTÿTÿTÿSÿRÿNÿKÿEÿCÿ@ÿ?ÿ?ÿ@ÿAÿEÿIÿLÿPÿTÿVÿWÿXÿYÿYÿYÿYÿ\ÿ`ÿdÿkÿqÿxÿ}ÿ~ÿ€ÿƒÿ†ÿˆÿ‰ÿ‹ÿ‹ÿˆÿ‡ÿ‡ÿ…ÿ„ÿ…ÿ„ÿ„ÿƒÿƒÿƒÿƒÿ„ÿ…ÿˆÿ‰ÿ‹ÿ‹ÿ‹ÿŠÿŠÿˆÿ„ÿÿÿ{ÿuÿrÿqÿpÿqÿoÿnÿkÿhÿeÿ`ÿ\ÿ[ÿ^ÿcÿiÿmÿvÿ}ÿÿ‡ÿ‰ÿ‹ÿ‹ÿŠÿ‡ÿ†ÿ†ÿ…ÿ…ÿ…ÿ…ÿ…ÿ…ÿ…ÿ‡ÿÿŽÿÿÿŽÿŽÿŒÿ‹ÿ‰ÿ‰ÿ‰ÿŠÿŠÿ‹ÿÿÿ‹ÿ‹ÿˆÿ„ÿÿ|ÿyÿxÿxÿwÿwÿxÿ{ÿ}ÿÿˆÿÿÿŽÿÿÿÿ‹ÿ‹ÿÿÿ“ÿ’ÿÿÿŒÿ‹ÿ‹ÿÿ‘ÿ’ÿÿŽÿŒÿ‹ÿ*|*|*|*|*}*{*}*}*}*|*}*}*|*})|(~)~))~)(~(}(~(})~)})~)})~)~)~)~))~))~)~)~)~)~)~)}*~*~*~*~*~*~+}+}*}*~,},,}+€*|**|*)|))|))})(}((}((}('~'€'~'€)~))~))~)~)~)~(~(~'~'~(~(~'~'~'~'}(~(}&~&}'~'}'~'|&~&z&&|&&|&~&|'~'|''|))|)~*{*~*{)~*z,},{,},|,},|,|,|,|,|.|.}-|-},|,|,|,},|+~*|*~)})})}))})(}((}'&}&&~&€&~&€%~%%~$#~#€"~"€"~!€!~!€ ~ € ~ €!~!~ ~ } ~ }!~!}! }"€$}#‚$y(…/sBˆ^hq‡mdj…oftƒtglƒhifdka€dkiƒnitsbs‚kbb€UjH8s1U~”‘ˆ„v‡n€s‡z~mŒ[|HK~S‘d|a”\x]—ZxL˜DxJ˜OxN˜NvO™RvS™RuK—GwG•E{FI|LA|<>@Œ?‚C‰E„D…W…‡|‡ƒ†y€‚r|gU„J}GC|B“A|B‘@|>@z\‡ry‘sno’pmo“klh“hlh“ela•]mY”TpP‘Hr91v1Ž5v55v5’6t<•itl–itg–cte•htm“}uX—0v2˜Atsš˜q¢—¥r§’¤t••…tm”\rv”qr•‚q‚–`p7š>iw‘`‡ž}[sŸhbV›RkU˜TnT•Meq››[–›…Vy¢n\^¢OeZ¡ajTž5m4›;nG—NkT—Xg\—^da˜bdcš`gY™Fm9˜?mK—Zid–gdf˜gel™pgh˜“d™™b‚˜yes•ik^•Tr?“,u()z)‹(|)‰)}'‰%}%ˆ$~#†#~#…#~#„"~"ƒ!~#‚%}09|<~=|>|?{@|<|8}3~1€/*‚''€(((€(&€&(€-€.€1€347:z<‚JqZƒZcJ…SfO…Cj=†@oI†HoL†PjL‡Zem‡wln…ixd‚EƒW€m‚u~u€ni~c~[X}SƒR{Q„PzP‚SzT„SzS„R{Q‚O}J‚EB€>~=>}@‚B{F‚IzM…NxR†UxV†WwXˆXvW‹WtXŒ]qbhnn‘ulz’|l’‚m…‡o‰Ž‰qŠˆs‡Œ‡s…‹…s„„rƒƒrƒƒrƒ…r†‹ˆu‰‡Šz‹€‹}‰z‰‡z‡„€{„uyrŠqspppo‘lqjfrc_t\Œ]t_‹cpiqlx‘}l‚‡oŠŒqŒŒŠt‰Œ‡s‡Œ…s……r……q…Š…u}Ž|ŽmiŽƒf‹ƒ‰hˆ‚ˆm†‚‰qŠ‚Šr‰‰vˆ‚ƒ~}}y†vvvŒusv‘vqw’yo{’{m‘ƒoŠŽr‰ŽxŽˆy‡Œz‹ƒ|’y’‘qŽ‹n‹„‹rŠx’“~‘}Ž„Œ{‹…*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ(ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ+ÿ*ÿ*ÿ+ÿ+ÿ-ÿ-ÿ,ÿ,ÿ,ÿ+ÿ*ÿ*ÿ+ÿ+ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ+ÿ,ÿ,ÿ,ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ"ÿ"ÿ"ÿ!ÿ!ÿ!ÿ ÿ ÿ ÿ ÿ!ÿ!ÿ ÿ ÿ ÿ ÿ!ÿ!ÿ ÿ"ÿ(ÿ5ÿ%ÿ&ÿ-ÿ?ÿWÿrÿtÿlÿnÿtÿuÿnÿiÿeÿbÿ_ÿ\ÿ_ÿiÿnÿsÿwÿqÿjÿaÿTÿGÿ6ÿ-ÿJÿÿ’ÿ„ÿtÿrÿrÿvÿaÿOÿJÿLÿOÿ]ÿYÿSÿRÿOÿCÿFÿMÿOÿNÿKÿLÿPÿYÿ[ÿPÿHÿHÿIÿFÿHÿLÿPÿNÿBÿAÿ@ÿ@ÿCÿDÿAÿ€ÿˆÿ…ÿ}ÿnÿ_ÿQÿIÿCÿAÿ>ÿ;ÿ=ÿ<ÿ9ÿ;ÿTÿ…ÿ}ÿqÿoÿpÿnÿhÿfÿgÿeÿcÿ_ÿ\ÿVÿUÿPÿDÿ5ÿ3ÿ3ÿ1ÿ1ÿ3ÿ3ÿ5ÿ6ÿMÿpÿlÿhÿfÿeÿcÿlÿtÿtÿ>ÿ,ÿOÿ~ÿ˜ÿ ÿ£ÿ¢ÿ˜ÿŠÿ}ÿcÿZÿwÿ`ÿvÿÿ|ÿrÿ:ÿ6ÿoÿÿÿsÿkÿXÿNÿOÿRÿRÿQÿWÿ†ÿ–ÿ‡ÿwÿmÿcÿXÿTÿVÿZÿUÿBÿ?ÿDÿKÿRÿWÿ[ÿ]ÿ_ÿ`ÿcÿaÿZÿRÿHÿEÿLÿRÿ\ÿbÿdÿgÿkÿpÿyÿ€ÿ™ÿŽÿ}ÿsÿnÿgÿ_ÿZÿUÿ@ÿ,ÿ*ÿ'ÿ(ÿ(ÿ)ÿ)ÿ'ÿ%ÿ%ÿ$ÿ#ÿ#ÿ#ÿ#ÿ!ÿ"ÿ"ÿ!ÿ#ÿ,ÿ7ÿ<ÿ=ÿ>ÿ?ÿ@ÿ?ÿ<ÿ8ÿ5ÿ3ÿ0ÿ+ÿ(ÿ'ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ&ÿ+ÿ.ÿ1ÿ3ÿ5ÿ5ÿ5ÿ7ÿ;ÿCÿMÿOÿBÿFÿUÿZÿ>ÿAÿOÿ\ÿTÿVÿYÿ\ÿbÿMÿSÿ\ÿJÿRÿiÿvÿwÿpÿlÿeÿ_ÿWÿRÿPÿPÿQÿPÿQÿSÿSÿSÿRÿQÿNÿHÿDÿAÿ>ÿ=ÿ>ÿ@ÿAÿEÿIÿMÿOÿSÿUÿVÿWÿWÿWÿVÿVÿWÿ[ÿ_ÿfÿmÿrÿwÿ}ÿÿƒÿ†ÿ‡ÿ‰ÿ‰ÿŠÿˆÿ‡ÿ†ÿ†ÿ…ÿ„ÿ„ÿƒÿƒÿƒÿƒÿƒÿ…ÿ†ÿ‡ÿˆÿŠÿŠÿ‹ÿ‰ÿ‰ÿ‡ÿ‡ÿ„ÿÿyÿtÿrÿqÿpÿpÿoÿlÿjÿfÿcÿ_ÿ\ÿ]ÿ`ÿfÿkÿrÿyÿ~ÿ‚ÿ‡ÿŠÿ‹ÿ‹ÿ‰ÿ‡ÿ‡ÿ‡ÿ…ÿ…ÿ…ÿ…ÿ„ÿ‚ÿ‹ÿ’ÿ‘ÿ‘ÿÿÿŽÿŒÿ‹ÿ‰ÿˆÿˆÿ†ÿˆÿŠÿ‰ÿ†ÿƒÿ}ÿxÿwÿtÿuÿvÿwÿwÿwÿxÿzÿ{ÿ{ÿ€ÿ…ÿŠÿŽÿÿŽÿŽÿÿÿ‹ÿŒÿ‘ÿ‘ÿÿÿŒÿ‰ÿŠÿŠÿ‹ÿÿ”ÿ“ÿÿÿŒÿ‹ÿ*}*z*}*z+}+z*}*{*}*|*}*})})}(}'~'~(~)~)~)~(})~)})~)})~)})~)~)~)~((~))~*~*+~+*~++~++~+,~,+}+~,},€,},,},€,|,,|,*|))|))})(}()}))}))~)€)~)€)~))~))~)€)~)€)~)€)~)€)~)€)~)€)~('~''~'}'~'}'~'|&~&z&&{&&{&~&{'~'{''z((z(~)z+~+z*}*|,},|,},|,},|,|,|,|,|,|,|,|,|,|+|-|-}+|+~*|)~)}))})€(}((}('}&%}%%~%€%~%€$~$$~#%~%~"~"~"~!~ ~ ~~~ ~ ~!~!~ ~ } ~ }!~!} € |"€%z(„,s>‡Vho‡ucn…qcsƒvfo‚jieal]€WmV€]kiƒqgvƒvbrkb`€SiF5t8[‰‚„|‚p…r€s‡l}\Q{F’G|P”TzL–DxA˜CxDšGxM™KxK™JvLšNv]š\uO˜FvD–BzA@|@ŽR}VŽD~=‹A>ŠDƒ>‰>…i‚‡„{v‚m~d[†T}D;|8‘8}8‘7|79zB‘~r~‘rnl’lmj“hod“cp_’XoV“WpV“RrL|;{8~6}4-€&$€&&'€'%€$&€(,€/0€2‚5€6€7€77€~=>~@ƒD|GƒJzM…OxS†UxV‡VwV‰UvTŠUuV‹Yq]cnj‘plv’|m€‘n‡ˆpŠŽŠq‰ˆs‡Œ†s…‹…s„…r„ƒrƒƒrƒŒ…r†‰ˆvˆ…‹zŠ}Š~Š|ŒŠ{‡€ƒ€~~x‡rwp‹qrqppn‘lqjerb^t]Œ]sa‹hontmzm„ˆpŠŠs‰Œˆt†Œ†s†Œ†s†„r„‹„t‡z’p’‚‘fƒŽfŒƒ‹hŠƒ‰k‰‚‰t†‚ˆy…‚~}}z…wwwŒvsuwpw‘wox’yny’zm{’|l€‘†m‹Žs‰ŽyŽ‡z†{Ž~‘~t€ŒpŠƒŠp‰‚‹s’{•~“‚{…zŒ‡*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ(ÿ'ÿ'ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ)ÿ*ÿ*ÿ)ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ#ÿ$ÿ$ÿ"ÿ"ÿ"ÿ!ÿ ÿ ÿÿÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ!ÿ!ÿ ÿ"ÿ"ÿ'ÿ-ÿ<ÿVÿpÿuÿnÿnÿsÿwÿpÿiÿfÿbÿ\ÿTÿPÿPÿ^ÿkÿrÿxÿvÿoÿgÿ]ÿPÿDÿ5ÿ6ÿMÿwÿ†ÿtÿmÿpÿsÿ^ÿLÿTÿEÿDÿLÿHÿEÿAÿ?ÿDÿEÿGÿFÿIÿIÿIÿIÿPÿVÿVÿOÿEÿEÿFÿ?ÿ>ÿ=ÿ;ÿ9ÿDÿ?ÿ?ÿ>ÿ=ÿAÿ@ÿJÿÿ€ÿtÿlÿgÿdÿTÿAÿ;ÿ6ÿ7ÿ6ÿ4ÿ4ÿ6ÿ8ÿbÿ~ÿsÿmÿjÿgÿeÿcÿ_ÿ\ÿQÿLÿNÿMÿIÿEÿ6ÿ*ÿ*ÿ/ÿ.ÿ,ÿ.ÿ/ÿ0ÿ1ÿ8ÿOÿoÿjÿeÿdÿcÿaÿbÿfÿpÿ[ÿcÿ{ÿŠÿÿŒÿ„ÿzÿmÿRÿFÿFÿCÿBÿWÿ~ÿuÿpÿUÿ:ÿvÿŠÿtÿkÿ\ÿBÿEÿGÿLÿKÿRÿkÿvÿuÿiÿ^ÿTÿSÿTÿVÿXÿCÿ<ÿPÿZÿYÿ[ÿZÿ]ÿ`ÿbÿcÿdÿfÿeÿbÿVÿPÿ^ÿgÿiÿgÿlÿxÿlÿ]ÿwÿ“ÿÿ|ÿnÿgÿaÿ_ÿ]ÿVÿUÿRÿ?ÿ+ÿ(ÿ(ÿ'ÿ(ÿ'ÿ&ÿ&ÿ%ÿ%ÿ$ÿ#ÿ#ÿ$ÿ#ÿ"ÿ!ÿ!ÿ!ÿ)ÿ5ÿ<ÿ>ÿ>ÿ?ÿ@ÿAÿ@ÿ?ÿ=ÿ<ÿ8ÿ5ÿ+ÿ$ÿ"ÿ#ÿ#ÿ$ÿ$ÿ#ÿ#ÿ%ÿ&ÿ*ÿ.ÿ1ÿ0ÿ3ÿ7ÿ9ÿ<ÿ;ÿ:ÿ9ÿ8ÿ8ÿ9ÿCÿMÿLÿLÿQÿ[ÿUÿXÿ_ÿaÿWÿEÿIÿ_ÿdÿKÿOÿqÿtÿmÿdÿ]ÿXÿSÿQÿPÿOÿQÿQÿQÿQÿQÿPÿOÿLÿHÿCÿ@ÿ>ÿ=ÿ?ÿAÿCÿFÿIÿNÿOÿSÿTÿUÿVÿVÿUÿTÿTÿUÿWÿ\ÿbÿkÿpÿwÿ|ÿ€ÿƒÿ‡ÿ‰ÿŠÿŠÿ‰ÿˆÿ‡ÿ‡ÿ†ÿ…ÿ„ÿ„ÿ„ÿƒÿƒÿƒÿƒÿ…ÿ†ÿˆÿˆÿ‰ÿ‹ÿŠÿŠÿ‹ÿŠÿˆÿ€ÿ}ÿwÿrÿpÿqÿqÿpÿnÿlÿjÿdÿaÿ^ÿ]ÿ^ÿbÿiÿqÿuÿ{ÿ€ÿ„ÿˆÿŠÿŠÿ‰ÿˆÿ†ÿ†ÿ…ÿ…ÿ…ÿ„ÿ„ÿ…ÿÿ‘ÿÿ’ÿÿÿŽÿ‹ÿŠÿˆÿˆÿˆÿ‰ÿˆÿ‚ÿÿ|ÿwÿwÿvÿvÿuÿvÿvÿwÿwÿwÿxÿxÿzÿ{ÿ}ÿƒÿ‡ÿŒÿŽÿÿŽÿŽÿÿÿŒÿÿ‘ÿŽÿÿŠÿ‰ÿ‰ÿŠÿŒÿ‘ÿ“ÿ•ÿ“ÿÿÿÿŒÿ*~*{*~*{*~*|*~*|)})}*})~)|)})~'~(~)~)~)~)~)~)~)~)~)~)~)~*~*~*~*~))**)~)*~*+~,~*~,~+~+|,~,~,},-}--}-€.}.,|,€,|-€+|*€+|+€*}*+}+*}**}*)})€*}*€+}+€*}*€*}*€)})€)~)€)~)€)~)€)~)€)~))~)'~''~''~'}'~'}''}''}'~'|(~(z))z((z))z**z*}+y+}+y+}+z-}-z,},{,},{,},{,},{+|+|*|)|*}*}*})})})~(}(~(}'~(}(~&}%%}%%~%€%~%€$~$€$~#€"~"}"~"}"~!}~!|~|~|}  } ~ }!€!} #{'„)u9ˆTioˆvcp…nas‚wes‚kheamZTqMJpO]ni‚oit‚sbmec[€NlC7u5I]‚ƒkf„h~q‰S|E“GzC•={B•EyG–CxB™DxE›HxFšGxIšIvIšKvQ™QuN—Fv@•BxB‘CyEŽD{>;|B‹=}=‰>‚?†@„:ƒmƒ{~rƒkf€c…M}@Dz=4{11|23y5;urroi’eme“epb`rZQtKŽNuO‹KvEŠ6x-Š*{*‰-{-‹.{/Œ/y.Ž7w@‘iun”itf“bt`“\v`’jui’irw•„r†•ux“luT“>tF“BtA”KrX–}qx˜jrb˜Bnz™†gt˜feW—@kB•HoJ’LoV’dlg“eh\•TiSšTlV›VoEš4p.š8nV™gh`š`g_—cfe–ded•ggg—gh\—_fi™mfl˜ri{˜rm^˜`l‚˜ˆi|–lhe•_k]”\mW•QpP“Nr>‘+v%&z'‰'|'ˆ'|&ˆ%}%†%~$†$}$…!~"„!~"ƒ&}1€:}=~?{@|@{@{CzBy@{?{>};{4~,|$"€""‚$~$ƒ"~"ƒ$€%ƒ'€*ƒ.€/‚1€5:€=€==}7€1~.+}.‚6{H‚V{X„P{R†YuY‡iri†LwI„b}bZ„L}m†u~kƒc€\€XXZ}R€O{P‚P{P„Q{Q„Q{NƒJ|FƒA~>‚=}>‚@|C„C{F„JzM†PxR†SxR†UwWˆTvSŠSuU‹Wp[Žbnjpmv’|m‚o‡‰q‹Ž‰qˆŒˆr‡Œ‡s†Œ…s„Œ…r…ƒrƒƒr„Œ†u‡‡Šy‹Œ}|‹Š{‹€‰|…€‚{~tˆrwpprpppn‘lqjcs`]t]Œ_qeinp‘ul|ƒm†ˆpŠŽ‹s‰Œˆs†Œ„s„…r…Œ„s„ˆŠy‘u“’h‘ƒŽfŽƒ‹k‰ƒ‰n‰ˆr‡€‰z„~z…yxy‹wswwrw‘vpw’wow“wnv“xny“zm{‘l„‘‰oŽs‰wˆŽy„{yŽ‹q‹‰q‰‚†s‰Žy•~–”{’…ŽzŒˆy‹‡)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ+ÿ,ÿ+ÿ+ÿ+ÿ+ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ(ÿ(ÿ(ÿ'ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ$ÿ$ÿ$ÿ#ÿ#ÿ"ÿ"ÿ"ÿ"ÿ"ÿ!ÿ ÿ ÿÿÿ ÿ ÿÿÿ ÿ ÿ ÿ ÿ!ÿ!ÿ#ÿ%ÿ+ÿ7ÿVÿpÿyÿoÿlÿqÿvÿvÿoÿgÿaÿ\ÿUÿMÿDÿEÿOÿ]ÿgÿnÿqÿsÿkÿbÿWÿJÿ>ÿ4ÿ2ÿ4ÿ`ÿ~ÿhÿgÿmÿoÿJÿFÿCÿ>ÿ:ÿ@ÿCÿGÿEÿDÿDÿEÿDÿGÿHÿHÿIÿIÿFÿKÿOÿQÿTÿXÿdÿnÿnÿqÿoÿkÿcÿUÿRÿUÿ_ÿPÿ7ÿ9ÿWÿxÿqÿjÿgÿ_ÿOÿBÿ@ÿ9ÿ3ÿ2ÿ1ÿ2ÿ3ÿ1ÿ2ÿHÿrÿjÿcÿbÿaÿ_ÿ^ÿYÿQÿLÿIÿMÿNÿMÿGÿ;ÿ.ÿ*ÿ.ÿ1ÿ/ÿ.ÿ/ÿ2ÿ3ÿ7ÿJÿoÿnÿiÿ_ÿ\ÿ[ÿ]ÿbÿmÿsÿwÿ{ÿ|ÿuÿkÿ\ÿAÿ?ÿBÿDÿMÿSÿ`ÿrÿnÿ_ÿ_ÿMÿuÿ„ÿsÿeÿSÿAÿGÿIÿJÿMÿZÿ]ÿZÿXÿVÿTÿSÿUÿRÿ@ÿ0ÿ.ÿ4ÿCÿ_ÿjÿmÿnÿkÿgÿgÿfÿgÿiÿiÿeÿcÿkÿoÿrÿtÿzÿgÿ[ÿWÿ`ÿzÿ{ÿlÿcÿ]ÿYÿXÿYÿRÿPÿMÿJÿ;ÿ*ÿ&ÿ%ÿ&ÿ(ÿ'ÿ'ÿ&ÿ%ÿ%ÿ%ÿ$ÿ#ÿ"ÿ"ÿ"ÿ!ÿ#ÿ-ÿ9ÿ<ÿ>ÿAÿAÿ@ÿ@ÿBÿ@ÿ@ÿ?ÿ>ÿ;ÿ5ÿ(ÿ"ÿ#ÿ#ÿ#ÿ#ÿ$ÿ#ÿ#ÿ$ÿ%ÿ%ÿ%ÿ(ÿ*ÿ.ÿ1ÿ3ÿ7ÿ9ÿ8ÿ6ÿ2ÿ,ÿ+ÿ+ÿ.ÿ<ÿRÿ`ÿZÿ[ÿ_ÿaÿlÿfÿaÿPÿIÿFÿSÿJÿfÿrÿnÿeÿ\ÿWÿ_ÿ^ÿ^ÿYÿOÿPÿPÿPÿPÿNÿKÿHÿDÿAÿ>ÿ=ÿ>ÿ@ÿCÿFÿIÿLÿNÿQÿQÿQÿQÿSÿTÿSÿQÿQÿTÿXÿ\ÿcÿkÿqÿxÿ~ÿ€ÿƒÿ‡ÿŠÿŠÿŠÿ‰ÿˆÿ‡ÿ†ÿ…ÿ…ÿ„ÿ„ÿ„ÿƒÿƒÿƒÿ„ÿ†ÿ‡ÿŠÿ‹ÿ‹ÿŒÿ‹ÿŠÿŠÿ‰ÿ„ÿ€ÿzÿtÿqÿpÿpÿpÿpÿnÿkÿiÿcÿ`ÿ]ÿ]ÿ`ÿfÿlÿsÿvÿ|ÿƒÿ‡ÿŠÿŒÿ‹ÿŠÿˆÿ†ÿ…ÿ…ÿ…ÿ…ÿƒÿ‡ÿŒÿŽÿ’ÿ’ÿÿŽÿ‹ÿ‡ÿ‡ÿ‡ÿˆÿ‰ÿ‡ÿ€ÿ}ÿyÿxÿyÿxÿxÿwÿwÿvÿwÿwÿwÿxÿxÿxÿzÿzÿ{ÿÿ„ÿˆÿÿÿÿŽÿŽÿÿŽÿÿ‘ÿŒÿŠÿ‰ÿˆÿ‡ÿ‰ÿŒÿ“ÿ–ÿ–ÿ“ÿÿŽÿŒÿ‹ÿ‹ÿ(~(|)~)|)~)})~)}(~({(}(|)|)})~)~)~)~)~)~)~)~)~)~)~)~)~)~*~*}*~*}++~++~+~+~+~+~+~+~+~,~,~,~,~-~-}..}.-}-€-}--|-€.|.€,|,€,|,€+}++}++}+*}*+}+€+}+€*}*€,},€,},€+}*€*}*€+}+€*})€*}*€)~))~)(~((~((~(}'~'}''}''}'~'|(~(z)~)z*~*z)~){*~*{*}*{+}+{,},{,},{-}-|,},|,},{,},{*|*{+|*{*}*z)})|)}*~)})~)}&~%}%~&}%}$}$}#~#}%~%}$~"~!~"~#~#"~"!~!!~~ ~ } ~ }  }  }!€!} !z#…'r5‡Qjnˆxdr†ndpƒucuselck]VpLAs=DqP]nf‚ogr‚pbi^cW€Ji<4r1‚1|fƒ|kh„o|k‰EyE”?w9—9x=–BxE—FwF™EwE›DxFšIxEšCvF™CwC—Lwi•‚w’•tšn†ŽxlvŽwmiŒUx_…q‚y}Y…7z?„s~nƒke€[‚M}?ˆ6z21y11z0Ž.z/1x3ŽXrkco`‘^q]Ž\sXŒRuMŠJwI†JzM‚N}N€G~9+~*‚-}/…-|/‡3y43xRrvmavWXvY‘\ua‘mvz“vvp“jsb“Ms<“@q?”Bp]—`pgžboY¢ZqX Rom–km“`kN”HoJ’MpL‘OpR“RmT”TmU—SnQ—Mq=˜3q0˜3o@—Sggšqatžxaz›vco˜kbi—lal˜jam™ray›}dršZjI›ZmPWmffkc˜^l[•XnY“TpM”JqH“Du5'x$Œ&{&‰'|)ˆ(|'‡%}%†%|$†#}"…#} ‚#})7z=>z=~@z@|@z@{AyAz@z@{?}<{3~&| €##‚"~#ƒ%~$ƒ$}#ƒ$}$ƒ%'‚()‚,€-.€*€+*€+*€)+-‚7‚Pƒ\R„Zƒc„d‚b„a}[„\xiƒ[~K[…s~oi_yc€[w@Vx_}S€O{O„O{M„L{H„G|C„@}=ƒ=|>ƒA{B„D{G…LzO†QxQ†QxR†TwRˆQvPŠQuS‹Xq\cnksmy}n€ƒp‡ŽŠqŠ‰qˆŒ‡r…Œ„s„Œ„sƒŒ‚r‚‚r‚ƒrƒ‹…u‡…Šz‹€‹}‹|ŠŠ{Š€‰|…~‚y{sˆptpŽpqp‘opm‘jqhcsaŽ]t^bpgnlsyl}ƒmˆŠpŽ‹s‰Œˆs†Œ†s†„s„Š†vŒz|“l‘fƒ‹i‰ƒ‡n…ƒ…rŠŠ{y{‡{rxxpx‘xpy‘yox’vow“wow“xnx“wkw“zj|‘m†Ž‰qŠw‡ŽzŽ…{’}‘uŒ€Šp‰‚ˆrˆ‚‹tŽ€“|—}•ƒ’z‡ŽxŒ‰‹xŠ‰(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ+ÿ+ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ*ÿ*ÿ)ÿ)ÿ*ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ&ÿ&ÿ&ÿ&ÿ%ÿ$ÿ$ÿ#ÿ#ÿ%ÿ%ÿ$ÿ$ÿ"ÿ"ÿ#ÿ#ÿ"ÿ"ÿ!ÿ!ÿ!ÿÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ!ÿ!ÿ#ÿ$ÿ(ÿ7ÿLÿkÿwÿmÿmÿsÿuÿvÿuÿqÿiÿaÿZÿOÿAÿ:ÿ=ÿDÿOÿ^ÿhÿmÿnÿlÿfÿ^ÿUÿHÿ<ÿ4ÿ3ÿ>ÿtÿvÿhÿgÿnÿcÿAÿCÿ@ÿ:ÿ9ÿ?ÿEÿCÿDÿEÿDÿCÿAÿEÿHÿKÿFÿBÿBÿHÿhÿÿ£ÿªÿªÿ¡ÿ•ÿ‹ÿ|ÿwÿxÿ{ÿhÿWÿgÿ‚ÿ{ÿTÿ/ÿ`ÿoÿlÿeÿ[ÿKÿ=ÿ6ÿ2ÿ2ÿ3ÿ1ÿ/ÿ.ÿ/ÿ1ÿ2ÿ<ÿdÿcÿ_ÿ[ÿYÿXÿVÿSÿPÿLÿKÿKÿKÿKÿOÿMÿJÿBÿ6ÿ1ÿ-ÿ+ÿ-ÿ3ÿ1ÿ+ÿ/ÿZÿfÿ^ÿXÿUÿ]ÿ]ÿ_ÿfÿoÿwÿjÿaÿYÿCÿ=ÿ<ÿFÿaÿcÿbÿ`ÿ]ÿZÿPÿTÿdÿdÿ{ÿdÿUÿGÿHÿMÿPÿQÿRÿTÿUÿUÿUÿQÿOÿHÿ?ÿ4ÿ1ÿ1ÿ;ÿNÿ^ÿmÿwÿtÿrÿwÿxÿuÿrÿqÿqÿqÿpÿrÿsÿtÿuÿ]ÿGÿVÿOÿDÿQÿZÿ^ÿ_ÿ[ÿXÿUÿUÿRÿLÿHÿEÿ?ÿ2ÿ&ÿ$ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ%ÿ%ÿ%ÿ$ÿ#ÿ#ÿ"ÿ#ÿ(ÿ3ÿ<ÿ=ÿ>ÿ?ÿ@ÿ@ÿ@ÿ@ÿAÿAÿ@ÿ@ÿ?ÿ<ÿ2ÿ%ÿÿ ÿ#ÿ#ÿ"ÿ#ÿ&ÿ&ÿ#ÿ#ÿ$ÿ$ÿ&ÿ.ÿ)ÿ)ÿ&ÿ%ÿ&ÿ*ÿ*ÿ*ÿ+ÿ*ÿ+ÿ+ÿ,ÿ.ÿ3ÿFÿJÿ[ÿ_ÿ_ÿXÿ`ÿdÿZÿ_ÿYÿLÿVÿqÿrÿbÿZÿEÿ)ÿ4ÿNÿNÿLÿOÿOÿOÿMÿLÿHÿEÿBÿ?ÿ=ÿ=ÿ>ÿAÿBÿFÿIÿLÿOÿQÿQÿRÿSÿRÿQÿPÿPÿQÿSÿXÿ_ÿdÿlÿsÿyÿ}ÿÿ„ÿ‡ÿŠÿŠÿ‰ÿˆÿ‡ÿ…ÿ„ÿ…ÿ„ÿƒÿ‚ÿ‚ÿ‚ÿ‚ÿƒÿƒÿ†ÿˆÿŠÿ‹ÿ‹ÿ‹ÿŠÿŠÿ‹ÿˆÿƒÿ~ÿzÿtÿpÿpÿpÿpÿoÿmÿiÿgÿbÿ`ÿ]ÿ^ÿbÿhÿnÿtÿyÿ~ÿƒÿ‡ÿ‰ÿŒÿŠÿ‰ÿ‡ÿ…ÿ…ÿ…ÿ…ÿ†ÿŠÿÿ“ÿ‘ÿ’ÿÿÿ‹ÿ‹ÿŒÿŠÿ‡ÿ‹ÿ„ÿyÿwÿyÿzÿyÿxÿxÿyÿxÿwÿwÿwÿwÿvÿwÿwÿwÿxÿzÿ|ÿƒÿ‡ÿÿÿÿÿŽÿŽÿŽÿ‘ÿ“ÿŽÿÿŒÿ‰ÿˆÿ‡ÿ‹ÿ‘ÿ”ÿ–ÿ’ÿ‘ÿÿŽÿŒÿ‹ÿŠÿ)~)}*~*})~)})~)}(~(})~)})~)~)~)~)~)~)~)~*~)~)~)~)~)}*~*}*~*|*~*|+~+}+~+},~,|+~+|+~,~-~-~-~.-~./}/.}..}.€.}.-}.-},-},-}-,},€,},€,},,},-}-€,},€+}+€,},€,},€+}+€+}+€+}+€+}*€+}+€*}*€*}*€)})(}(~(~(~'~'}(~(}(}({)}){*}*{*}*{*}*y*}(z*}*z*}*z*}*z,},{,},{+},|,},|,},|,},|*}){(}){)}){*}*|(}'~&}&~&}%~%}%~%}%~%}%%~%}&~'}*~*|%~$}#~"€"~!€"~"€!~ €    "#} € |!!z%„(t0ˆIjj‰wdm†hdnƒtextesnif\oS~Cs7~6s?HqQ‚^lh„ofp‚lag\eQ~Fl:4x6ƒP~…r„ab…l}_‰AyB•Aw:˜6w?—EvB™BvB›CvC›@wC›HyQ›Jy?—Hy^•Šxž“¥x­Ž¥q”Žm’”jŠjŒqrZˆb€}x‰†wn;„Fur‚k|eZO~@†6y22{23{3‹/{.Œ1y55w>^t`WrVŒUtT‰SwR‡NzL„M|M€K~L~LK{KH|<€/),ƒ/y-‰&x'Š4w_Œbv_[vX[u^dte‘qsn’^sR‘=s;•@p^—_oUZnZ§\lW¬PoG¦PqQ™kp`”PpC‘JrJMrN‘RoX“\m`•]mZ—NpE˜;r3—1q3—EmY—fdp˜v_u›q_p›raq™mbj™laošo`nœn`l›pgK›OlUœGoFžQn[›]m\˜ZnV•UpT”NpJ”FrB’:v-#y#‰$|%‡&}'‡'}%‡%}%…%}$…#}#…$|'ƒ2{9‚;{<>{?~?z@|AzA|A{A{@{@{?}:|/~#}!~ €!"‚"~"ƒ#~&ƒ#}!ƒ"~%ƒ)€+)€)‚++‚*€*++++,*‚,/ƒ/€1ƒDV‡m‚i‡g…f_†fyj†X|RV‚n~gh`zN€+{1}F}O{PƒNzN…NzL…K{G„D|A„>|=ƒ={>ƒAzB…DzG…MzO†PxP†RxR‡QwP‰PvP‰OuR‹Yr`gooum{~n‚…qˆŽ‰q‰‰rˆ‡s…„s„Œ‚rŒ‚q‚qŒƒr„‹†u‰„Šz‹~‹}‹}‹€Š}‰€…€‚~~„yysŠosooqo‘npl‘hqd‘aq`_qadnj’pkv’|k€„mˆ‰q‹‰s‡Œ‡s…„u…‹†v‹~|‘m”’dƒŽfŽ„Žk‹„ŒnƒŒu…|„{syzpx“yox“xow“wox”xox”xmx•xlx•wkx”{j~‘„n‹Œuˆw†ŽyŽ…|’{‘rŒ‹nŠŠq‰€Œx”~—–y“ˆ‘xŽ‰ŒwŒ‰ŠwŠ‰(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ+ÿ,ÿ-ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ.ÿ,ÿ,ÿ-ÿ,ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ(ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ,ÿ,ÿ,ÿ,ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ*ÿ(ÿ)ÿ+ÿ)ÿ)ÿ)ÿ)ÿ(ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ*ÿ0ÿ0ÿ&ÿ#ÿ"ÿ!ÿ"ÿ!ÿ"ÿ"ÿ!ÿ ÿ ÿ ÿ ÿ ÿ!ÿ!ÿ!ÿ!ÿ#ÿ%ÿ&ÿ1ÿFÿdÿyÿoÿgÿkÿrÿxÿvÿvÿrÿjÿaÿWÿJÿ8ÿ0ÿ7ÿAÿJÿTÿ`ÿiÿpÿrÿmÿeÿ[ÿOÿDÿ8ÿ1ÿ<ÿaÿƒÿpÿeÿfÿnÿZÿ@ÿDÿ@ÿ9ÿ6ÿ<ÿCÿDÿAÿAÿCÿCÿBÿDÿEÿHÿEÿKÿdÿƒÿ™ÿ ÿ¤ÿ¬ÿ§ÿÿ•ÿ˜ÿœÿÿ}ÿvÿtÿjÿ…ÿÿ‘ÿ†ÿeÿ3ÿpÿjÿeÿ\ÿRÿCÿ2ÿ.ÿ,ÿ,ÿ*ÿ*ÿ*ÿ+ÿ,ÿ/ÿ0ÿ/ÿ;ÿVÿ\ÿWÿSÿPÿPÿOÿNÿMÿLÿMÿKÿMÿKÿJÿIÿHÿEÿ@ÿ5ÿ.ÿ1ÿ.ÿ)ÿ$ÿ)ÿ:ÿZÿ^ÿaÿbÿcÿaÿ]ÿ^ÿhÿhÿaÿHÿ:ÿBÿUÿZÿWÿ[ÿ`ÿaÿYÿRÿLÿIÿDÿGÿNÿXÿMÿFÿLÿQÿOÿOÿRÿUÿ[ÿaÿ_ÿVÿLÿ@ÿ9ÿ3ÿ0ÿ7ÿJÿ_ÿkÿoÿtÿvÿsÿnÿmÿlÿgÿeÿiÿjÿgÿgÿ_ÿ\ÿZÿGÿZÿNÿIÿHÿRÿ]ÿ^ÿ\ÿZÿVÿUÿRÿJÿFÿCÿ@ÿ5ÿ*ÿ$ÿ$ÿ$ÿ$ÿ'ÿ'ÿ&ÿ'ÿ&ÿ&ÿ&ÿ%ÿ#ÿ#ÿ&ÿ0ÿ8ÿ;ÿ;ÿ<ÿ?ÿ@ÿ?ÿ@ÿAÿAÿAÿAÿ@ÿ@ÿ?ÿ:ÿ/ÿ#ÿ#ÿ%ÿ!ÿ!ÿ"ÿ"ÿ"ÿ"ÿ#ÿ#ÿ%ÿ'ÿ(ÿ*ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ.ÿ/ÿ3ÿ8ÿ@ÿTÿbÿhÿmÿoÿiÿdÿeÿUÿMÿWÿiÿjÿeÿ^ÿ\ÿXÿUÿOÿNÿMÿNÿNÿNÿLÿJÿFÿDÿAÿ>ÿ=ÿ=ÿ>ÿAÿBÿGÿJÿMÿNÿPÿPÿRÿRÿPÿPÿOÿOÿNÿSÿZÿ`ÿhÿpÿuÿ{ÿ~ÿ‚ÿ…ÿˆÿ‰ÿ‰ÿ‰ÿˆÿ‡ÿ…ÿ„ÿ„ÿ‚ÿÿ‚ÿ‚ÿÿÿ‚ÿ…ÿ‡ÿŠÿŠÿ‹ÿ‹ÿ‹ÿ‹ÿŠÿ‰ÿ…ÿÿ}ÿuÿqÿoÿoÿoÿoÿnÿlÿhÿdÿaÿ`ÿ`ÿbÿfÿlÿrÿxÿ~ÿ‚ÿ†ÿ‹ÿŒÿŠÿŠÿˆÿ‡ÿ…ÿ„ÿ†ÿŠÿŽÿ’ÿ’ÿ“ÿ’ÿÿÿÿŽÿÿŽÿŽÿÿ~ÿyÿ{ÿyÿyÿxÿxÿxÿxÿwÿwÿwÿwÿwÿwÿwÿwÿwÿwÿyÿ|ÿ‚ÿ‡ÿÿÿÿ‘ÿ‘ÿŽÿŽÿÿ•ÿÿÿŒÿ‹ÿŠÿŠÿ‹ÿÿ•ÿ—ÿ•ÿ’ÿÿŽÿŒÿŒÿŠÿŠÿ(~(~(~(~)~)~(~(~)~)~)~)~)~*~*~*~*~*~)~)~+~*~+~+~*~*}+~+}+~+|,~,|,~,|+~+|,~,|,~,|,~,}.~.}-}-/}/~.}..}..}..}.€.}.-}-.}..}.,},€.}.€-}--}--}-€,},€,},€,},€,},€,},€,},€,},€,},€+}+€*}*€+}+€(}((}(~(~(~(~(})~)|)}){)}){)}){*}*{+}+y*}*z+}+z+}+z+}+z+}+{+},{,},|,},|,},|,}+|*}*{)}){)}){(}(|'}&}%}%}%}%}%}%}$~$}$~$}$~$}&~&})~(}%~#}"~"~!~!~"~"!~!!!}!!}  }!€"{$„)u-ˆ@kaˆuds†kck„rev„xgxtio€em\~Mq>~0t1:tDPpZƒbkjƒqhr‚igc€XiL€Cp71|E„u…‚„l†g€j†q{V‹AyF•>w9˜9w?™CvD›CvB›BvC›AwCšExD™Iya˜x—•£w¦’¨uªŽ¬rªŒ¡nœ‹™m–Š‡mvŠypzŠ’|Ž|…Žg|…Eid‚luf`|WO|G€G~F€EE€F€E~FJ|L…WzZ‡TxH‰LuRŠSvRŠOwN‡MzL„K|K€K~K|KH{GD|D€D>7ƒ3|.†*{'‰#z%Š>xYŒYvWŽ[v\]u]^t]‘bsU”IqRœYnR§TmW«VnU¬SnM©JpG£@s4›;tE•AuD’IuM’LtL‘QpT’Wo\•\pV–Ir<—7s0•1r=•Pn`•lgp–tcv™tcn™kch™gdf™icišgdeœ_h[›NlUšRoG›HpMœZla›al^˜[oU”SqM“GrD‘@t=1w(‹$z#ˆ#|%‡%~%‡&}&‡&}&…&}$…"}"…+|5ƒ9{:‚<{>>{?~?z@}BzB}A{A}@{@}?}:}/~#~#~$€#"‚"~!ƒ#~#ƒ#}"ƒ"~%ƒ(€(*€*‚++‚+€+**‚*+‚,.ƒ14„8=‡BD‰GTˆf‚g…g„m€i…N‚L‚^‚j}j~d~`{ZTL|LNzM„NzN…NzM…K{G„C|@„>|=ƒ>{?ƒAzD…FzI…MzM†PxP†PxP†PwPˆNvO‰OuU‹Zrbioqxm}nƒ…qˆŽˆqŠˆr‡…sƒƒs‚Œr€ŒqqŒƒs†‰ˆvŠƒŠzŠŠ}‰~ˆ~ˆ~ˆ~„|z…twp‹otooqo‘mpk‘gqe‘cqbbqchnn’ukz’~l‚ˆoŒŒqŠˆs‡‡s†‹†wŠ€{’q’‘j„Ži„iƒl‘„ŽrŽ‚ywyxpy’wox“xnx”xnw”xnx”wnw”wmw•wlw•xkz”}j„‘‰nŒ’u’ˆyŽ‡ŽzŽ€“}“u€ŒoŒ‹q‰ˆv‹€‘{–~—„•y‘ˆx‰‹w‹Š‹wŠŠ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ*ÿ+ÿ+ÿ+ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ.ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ,ÿ-ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ*ÿ+ÿ+ÿ*ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ#ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ%ÿ$ÿ"ÿ#ÿ"ÿ"ÿ!ÿ!ÿ"ÿ"ÿ!ÿ!ÿ!ÿ!ÿ"ÿ"ÿ!ÿ!ÿ#ÿ%ÿ(ÿ1ÿ?ÿ[ÿvÿsÿkÿkÿqÿvÿvÿvÿvÿoÿfÿ\ÿQÿBÿ4ÿ/ÿ5ÿ>ÿHÿUÿ^ÿfÿnÿqÿnÿgÿ_ÿUÿIÿ=ÿ6ÿ7ÿVÿ{ÿ‚ÿlÿfÿiÿqÿTÿ?ÿEÿ>ÿ9ÿ9ÿBÿEÿEÿDÿCÿCÿCÿBÿCÿEÿHÿYÿpÿ‹ÿ—ÿšÿ¤ÿ¬ÿ«ÿªÿ®ÿ£ÿ¡ÿžÿœÿ”ÿrÿ†ÿ‡ÿÿ€ÿˆÿ’ÿ†ÿiÿQÿdÿ_ÿ]ÿ]ÿ\ÿYÿYÿVÿTÿUÿTÿSÿTÿTÿRÿWÿWÿYÿ[ÿSÿNÿMÿNÿKÿLÿKÿLÿKÿJÿIÿIÿJÿHÿEÿEÿBÿAÿ>ÿ:ÿ7ÿ3ÿ-ÿ(ÿ&ÿ'ÿ&ÿCÿWÿPÿPÿWÿ\ÿYÿWÿZÿ`ÿbÿUÿUÿWÿYÿUÿTÿRÿPÿKÿGÿBÿBÿ?ÿ4ÿ3ÿ>ÿHÿCÿEÿIÿJÿLÿOÿQÿUÿXÿWÿQÿFÿ=ÿ7ÿ2ÿ2ÿ>ÿNÿ]ÿhÿpÿxÿxÿqÿkÿiÿiÿfÿfÿfÿgÿhÿgÿXÿDÿ8ÿFÿMÿJÿHÿRÿ^ÿ`ÿaÿ\ÿXÿUÿPÿHÿDÿAÿ=ÿ9ÿ,ÿ%ÿ#ÿ"ÿ#ÿ$ÿ$ÿ%ÿ&ÿ&ÿ&ÿ&ÿ%ÿ#ÿ#ÿ&ÿ0ÿ8ÿ;ÿ<ÿ>ÿ@ÿ?ÿ?ÿ?ÿ@ÿBÿBÿAÿAÿ@ÿ@ÿ=ÿ;ÿ/ÿ#ÿ ÿ ÿ"ÿ"ÿ!ÿ!ÿ#ÿ#ÿ#ÿ"ÿ"ÿ%ÿ'ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ/ÿ0ÿ6ÿ9ÿ?ÿ<ÿ=ÿ<ÿ?ÿDÿKÿQÿ_ÿqÿmÿIÿGÿXÿYÿ_ÿdÿeÿ[ÿSÿVÿQÿNÿNÿNÿNÿNÿMÿLÿHÿEÿ?ÿ=ÿ=ÿ>ÿ?ÿBÿFÿHÿKÿMÿMÿPÿPÿPÿPÿOÿOÿNÿOÿQÿVÿ\ÿbÿiÿqÿzÿ~ÿÿƒÿ…ÿˆÿ‰ÿ‰ÿˆÿ‡ÿ…ÿƒÿƒÿ‚ÿÿ€ÿÿÿÿÿƒÿ†ÿ‰ÿ‹ÿ‹ÿ‹ÿŠÿ‰ÿˆÿˆÿ„ÿ€ÿ|ÿuÿqÿnÿoÿoÿoÿoÿlÿjÿgÿeÿbÿcÿeÿfÿjÿqÿwÿ{ÿ€ÿ„ÿŠÿÿŒÿŠÿ‰ÿˆÿ…ÿ…ÿ‰ÿ’ÿ“ÿ”ÿ‘ÿ‘ÿÿÿŽÿŽÿŽÿÿŽÿÿ†ÿwÿyÿ|ÿyÿyÿzÿxÿxÿwÿwÿxÿxÿxÿwÿwÿvÿvÿwÿwÿxÿzÿ~ÿ…ÿ‹ÿ’ÿ“ÿ’ÿ‘ÿÿÿ’ÿ•ÿ‘ÿÿŒÿ‹ÿŠÿ‡ÿŠÿŒÿ’ÿ•ÿ•ÿ•ÿ‘ÿÿÿŒÿŒÿ‹ÿŠÿ'~'(~((((()~)~)~)~)~)~+~+~+~+~,~,~+~+~+~+~+~*|+~+|,~,},~,|,~,|,~,|-~-|-~-|.~.|-~-~/}//}//}/.}..}..}.€.}.€.}.€.}..}..}.€.}.€-}--}-.}.€-}-€-}-€,},€-}--}--}-+}+,},€+}+€*}*€,},€+}*~*}*~)~)})~)|*~*})~)|)}){*}*{*}*{*}*z*}*{*}*{+}+{+}+{+}+z+}+z,},{,},{+}*{*}*{)}){(}({'|'|'~'|&~%z%~%z%~%|$~$|$~$}$~$}$~$~$~$~&}$#}##~#~"~!~"~"!~!!!~""}#"|%ƒ)u1‡=lX‡scq†jal…perƒrhs‚rlmdnY}NqC}5v/€0w7‚BsMƒVnb…ijoƒpgkeh[€OlC7s5Be„~ˆlˆhf‡l{O@yC–@w8™7wB™GvEœCvCœEuF›Cw@š@xN˜kx—u–•–tž“§tªªt¬¢r£Š£q¤‰¢mŒ‰}nƒ‹‰{‹‚”…šlŠ†z`RƒYlX€WvUTvO€OxP€PwO€OxOO|P~PR|Vƒ[z[†UxP‡IxI†FxE†EyF…E{E‚F|H€H~H~H€EA?‚>~:‚8{5†3y,ˆ'y&‰(y.‹PxZUvSŽUuTSuUŽYt]’brWRoR¨NoPªNqK¥FsA¢=t?žAt7š.v3•=vAAwDFvKMtNRrWYqQ“Fr:•8u3”4u>‘KqW‘ejl”res˜ldh™heg—fef™feg›hfichNš6n@—FpE˜IoX˜^n^–\oX•UpS“KrE’Bs?;u5)z#‰#|#‡#}#‡#}$‡%}%ˆ$}$ˆ#|#†#|)„2|9ƒ={>?z@@z??z@~Az@~@z@}?{?}>}:~.~#!!""ƒ!~!ƒ"}"ƒ"~"ƒ"~$ƒ&++€+)€*‚*€**€**€,‚0€4„6€6…66‡8~9ˆ<~C†F~E„HU‚T€F„G\€R]ye~fx[~W{\{S~MzN‚MxN„NxM„K{GƒD|?ƒ=|=ƒ?{@ƒDzG„HzK†MyM…NyN…NxO†PwOˆOwP‰QtVŠ_qcŒkorwn}nƒŽ…pˆŽŠqˆˆs…„s‚‚r€Œ€q€Œ~p€qŒ„t‡ˆŠyŒƒ|‹Š}‰ˆ|‡€„|€‚y|tˆpvoprpnpn’kpi‘gpe‘epc‘goh‘lms‘xl}‘ƒl‡‹oŽrŒŒŠt…Š†v‡…Ž{’y“€’k‚jƒkŽ…ŽlƒŽrŽ„Œ{z~s‹zqz’xny”yly”ynx“xow”wmw•wmw•xlz•ykw•wk{“k‡Žn“Š“t’‡x†z•|•sŒp‹€‰tˆ€Šy“€—{•†”yŠxŠvŠŒv‰Œ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ$ÿ#ÿ#ÿ#ÿ#ÿ"ÿ!ÿ"ÿ"ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ"ÿ#ÿ'ÿ-ÿ7ÿPÿoÿvÿjÿkÿnÿpÿrÿrÿqÿgÿ`ÿVÿLÿ?ÿ2ÿ,ÿ.ÿ2ÿ<ÿGÿRÿ[ÿfÿmÿoÿoÿiÿ`ÿXÿKÿ?ÿ2ÿ1ÿIÿqÿ}ÿÿlÿgÿjÿlÿLÿ@ÿCÿCÿ:ÿ8ÿ@ÿDÿEÿDÿEÿGÿGÿCÿ?ÿCÿ^ÿ}ÿŒÿ•ÿ˜ÿ’ÿ•ÿšÿ£ÿ®ÿ°ÿ®ÿ°ÿ®ÿ©ÿ«ÿ’ÿtÿvÿŽÿ£ÿ¡ÿ£ÿ–ÿƒÿ`ÿQÿUÿSÿRÿQÿQÿQÿQÿQÿRÿRÿQÿQÿPÿPÿQÿSÿTÿSÿVÿQÿMÿKÿGÿEÿCÿBÿ@ÿAÿ@ÿ?ÿCÿCÿBÿBÿ@ÿ>ÿ>ÿ:ÿ9ÿ6ÿ3ÿ0ÿ*ÿ%ÿ'ÿ*ÿ2ÿRÿ_ÿXÿPÿRÿUÿRÿPÿTÿaÿ[ÿDÿIÿCÿEÿFÿEÿBÿ>ÿ<ÿ>ÿ>ÿ5ÿ/ÿ,ÿ-ÿ:ÿ@ÿAÿEÿJÿLÿMÿNÿNÿPÿOÿCÿ9ÿ7ÿ4ÿ3ÿ:ÿHÿUÿ^ÿgÿlÿmÿlÿiÿhÿfÿfÿfÿeÿ_ÿXÿVÿLÿOÿUÿLÿCÿFÿOÿZÿ^ÿ\ÿZÿVÿSÿOÿHÿCÿ>ÿ<ÿ9ÿ3ÿ'ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ%ÿ%ÿ$ÿ$ÿ#ÿ#ÿ#ÿ)ÿ3ÿ:ÿ=ÿ>ÿ?ÿ@ÿ@ÿ@ÿ?ÿ@ÿ@ÿ@ÿ@ÿ@ÿ?ÿ?ÿ>ÿ:ÿ.ÿ#ÿ!ÿ!ÿ"ÿ"ÿ!ÿ!ÿ"ÿ"ÿ"ÿ"ÿ!ÿ"ÿ$ÿ(ÿ*ÿ*ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ,ÿ/ÿ3ÿ4ÿ4ÿ4ÿ5ÿ6ÿ9ÿ@ÿGÿIÿIÿIÿMÿCÿ@ÿAÿUÿhÿoÿnÿfÿbÿcÿgÿSÿLÿMÿMÿNÿMÿLÿJÿFÿCÿ@ÿ?ÿ?ÿ@ÿAÿDÿGÿHÿKÿMÿMÿNÿNÿOÿPÿRÿTÿTÿTÿWÿYÿ_ÿfÿkÿtÿzÿ}ÿÿƒÿ‡ÿ‰ÿŠÿˆÿˆÿ…ÿ„ÿ‚ÿ‚ÿ€ÿ€ÿ€ÿ~ÿÿ€ÿÿ…ÿˆÿÿÿÿŒÿŠÿ‰ÿˆÿ†ÿƒÿÿyÿtÿpÿoÿoÿoÿnÿnÿlÿjÿgÿeÿdÿeÿhÿiÿoÿvÿ{ÿÿƒÿ‡ÿ‹ÿÿ‹ÿ‰ÿ‰ÿ‡ÿ†ÿ‹ÿ”ÿ‘ÿ’ÿ‘ÿ‘ÿŽÿŽÿÿŽÿŽÿÿŽÿÿÿsÿxÿ{ÿ{ÿyÿyÿyÿxÿxÿxÿxÿwÿwÿwÿwÿwÿwÿwÿxÿxÿyÿ|ÿ‚ÿˆÿŽÿ’ÿ“ÿ’ÿ‘ÿŽÿÿ”ÿ‘ÿÿŒÿŠÿŠÿˆÿ‰ÿŒÿ’ÿ•ÿ—ÿ•ÿ”ÿÿÿÿŒÿÿ‹ÿŠÿ''((((())~)~*~*~+~+~+~+~+~+~,~,~,~,~,~,~+~-~-~-~-~-}-~-|-~-|-~-|.~.|.~.|.~.~/~//}/€0}0€/}/.}..}..}.€.}.€.}.€.}.€.}.€.}.€.}.€.}..}..}.€.}.€.}.€.}.€-}--}--}-,},,},€,},€,},€,},€+}*€*}*)~)~*~*}*~*}*~*|*}*|+}+|*}*{*}*z+}+{*}*{+}+{+}+{+}+z+}+z,}+{*}*{*}*{*}*{)}){'}'{&|&|&~&|&~&}$~$}$~$|$~$|$~%}$~$}$~#}$~$~$}%#}""~"#~"~"~""~!!!~##}%(w*†7mK‰jdy‡p`i…ldmƒphpmke\nQ}Ds9|1v+~+x.€8w@‚KqVƒalj„php„lge\kQGs;3{5Y„{„yŒl‰g€k†n}QŒ@yE•Cw<˜7w?—DvEšEvFœHwG›Bv?šNvo˜…u—•r˜–r‰”r¡¯y¹†º{»‚¸vª…§t’ˆ|u†“}˜‚¢…žqŸ‡as„McUXpX‚VvWVwS€RwS€SxRP|R~RU{U‚UzU„WzP…NzJ„HzF…FzE„E{C„B{B„A|A‚A}Aƒ@~>ƒ>}<ƒ;|:„6{3†.z)ˆ(y+Š*x5ŒTw][vXSwQŽOvR[ta“Js;™?s?CtBœ?u;œ8w8™7w3—/w-“-x9ŽDzI‰KzLˆNyP‰OwO‹QsOŽ?t65v2.v5DsQŽWoY]kd”iih•khk–ffg˜ie\›OgWš8iL˜YpK•GqN”Yp]”\pZ’VqSQrKDt@Žÿ8ÿ>ÿEÿEÿEÿFÿIÿHÿDÿHÿaÿwÿ‰ÿŒÿÿ”ÿ’ÿƒÿ‹ÿ¤ÿ±ÿ¹ÿ»ÿ¼ÿºÿ²ÿ¯ÿ©ÿªÿ¬ÿ®ÿÿ ÿ ÿ¥ÿ—ÿÿ\ÿXÿXÿXÿVÿTÿTÿTÿPÿQÿQÿQÿPÿPÿQÿSÿUÿXÿYÿYÿTÿPÿMÿFÿFÿEÿEÿDÿDÿEÿDÿCÿCÿ@ÿ@ÿAÿ@ÿ>ÿ>ÿ=ÿ;ÿ;ÿ9ÿ5ÿ0ÿ-ÿ+ÿ,ÿ.ÿ7ÿUÿUÿWÿWÿRÿQÿPÿTÿZÿUÿBÿ;ÿ;ÿ>ÿ>ÿ=ÿ;ÿ8ÿ7ÿ5ÿ1ÿ/ÿ/ÿ2ÿ>ÿHÿLÿMÿMÿNÿPÿPÿOÿQÿUÿGÿ(ÿ/ÿ0ÿ.ÿ2ÿ<ÿEÿOÿSÿVÿ^ÿbÿbÿeÿgÿiÿhÿkÿiÿdÿ_ÿQÿQÿTÿOÿOÿWÿZÿ[ÿXÿWÿTÿOÿJÿDÿ@ÿ?ÿ;ÿ9ÿ5ÿ-ÿ%ÿ"ÿ"ÿ$ÿ#ÿ"ÿ#ÿ#ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ"ÿ)ÿ6ÿ>ÿAÿ@ÿAÿAÿAÿAÿAÿBÿAÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ>ÿ/ÿ#ÿ!ÿ ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ!ÿ"ÿ#ÿ#ÿ$ÿ$ÿ(ÿ)ÿ(ÿ)ÿ*ÿ*ÿ+ÿ+ÿ,ÿ/ÿ1ÿ3ÿ5ÿ6ÿ5ÿ3ÿ6ÿ6ÿ8ÿ=ÿ?ÿ>ÿCÿCÿCÿ=ÿIÿ_ÿfÿfÿ\ÿZÿTÿUÿOÿLÿNÿNÿMÿLÿJÿFÿCÿAÿ?ÿ@ÿBÿCÿEÿHÿJÿLÿLÿLÿOÿOÿRÿUÿYÿZÿ_ÿaÿeÿgÿgÿlÿpÿwÿ{ÿ~ÿ€ÿ„ÿ‡ÿ‰ÿ‡ÿ‡ÿ†ÿ„ÿƒÿƒÿ‚ÿÿ€ÿÿ~ÿÿÿ„ÿˆÿÿÿÿŽÿŒÿŠÿ‰ÿ†ÿƒÿÿyÿtÿpÿnÿnÿmÿmÿnÿmÿjÿhÿfÿeÿfÿgÿjÿmÿtÿyÿ~ÿ‚ÿ†ÿ‰ÿŽÿŽÿŒÿŠÿ‡ÿˆÿŽÿ“ÿ‘ÿ‘ÿÿŽÿÿŒÿÿÿ‹ÿ‹ÿ‹ÿŠÿ€ÿuÿtÿzÿ|ÿ{ÿ{ÿzÿxÿxÿxÿxÿwÿwÿwÿwÿwÿvÿvÿvÿxÿxÿyÿ~ÿ…ÿŒÿ’ÿ”ÿ“ÿ‘ÿÿÿ“ÿ‘ÿÿ‹ÿŒÿŠÿˆÿˆÿ‹ÿÿ”ÿ•ÿ–ÿ”ÿ’ÿÿÿÿÿÿ‹ÿŠÿ(~()~)*~*)~))~*~*~*~*~*~*~+}+~+|+~-|-~-|-~-{--}--}.~.|.~.|.~.}-~-}/~.|.~.|.}.|.}0}/}/~/}/~.}..}/.}..}..}..}..}..}./}//}//}//}./}/€/}/€/}/€/}/€-}.€/}.€.}..}.-},,},,},€+}+€,},,},+~+~+~+~+~+}+~+|*~*}*~*}*~*{*~*{*}+z,},|,},|+}+{*}*z*}+z+|+z+|+z*}*{)}){(~'{'~'|&~&}%~%}$$}%%}%%}%%}%%}$$}$~$|$~$}$}$~#}#~#~#}#~#}#~#"~"~"#}$‚'z+†1s=ˆZhxˆrbj…jbq„phphk]€TmH}8s-|(y%~&z)€-|5‚?wI„Tq^„fln„oim‚eh]RnF~;t20{>‚g‡wƒvŽ€kŒbe‡l}vˆKy?Dw@•:w:—EvD™DxFGwFLv`štu˜rˆ–†oŽ•‘p„“‘s£‘±yµ‰¶|º†»{¶ƒ¹}»ƒº~»º®~¦…v¢‡žfŠ†j`Z‚VmR‚RtQ‚SwS€TxPPzN~M~M}RT|RT{UƒUzS…QzN„KzH…FzE„D|C„D|C„C|CƒB}BƒB~Aƒ@~@ƒ>~=ƒ=~=ƒ9|8„4{1†.z.‰.zAŠWyVQxPŽQxPOvMUvV’;v5”;v<—=v:˜8w4—3w2”0y1“9yGM{N‰M}NƒN}N„N|N†QxS‰SvGŒ=v)‹(w/Š5w;ŠCuJŒMpV[n_”bkd“fig—fhc˜ajb™UlV–TrK“PqY“[r[’YrUQtMEwAŒ„AzC‚C{B€@z??z@BzAAzA~A{@~?|=3~%€! ‚ "‚!~!ƒ!~!ƒ!!„!!„!!ƒ""ƒ"&ƒ))ƒ*€*+€+*+ƒ/1ƒ32„4€4„6€8…;€;„:9„=€=BG{MRt[}fob}at\{VzPzTQzNƒNzMƒJ}FƒB{@‚@{B„BzC„F{I…K{M†LzM†MxQ†UvZ‡\t`‰etkŒotpŒpspŒtrwŽ}p€ŽoƒŽ‡p‰Ž‰qˆ†r„„r‚r€r€ŽoŽ„r‡‹Šw†|„Œ}Œ‚‹}‡‚„|„{zwˆruonrmmom’lok’jog’fof’gni’lmq’wl{‘€m‚…nŠŽŽpŽŽŠr‰‰ˆwŒ€’|“q“’k‚ŽoŽƒŽnŽ„Œn‰‚‡w‹ˆyzwxo{”}l}”zly”xmx”wmw•wmw•wlw•wkw•wkw•wjx•zi€’‡l’r“‰’x‘…{“|“t‹€ˆr‹Štˆ‰wŒ€‘}“}•…–y“ŠuŠuŽŠŒt‹Š‹v‰Š(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ/ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ.ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ"ÿ"ÿ"ÿ"ÿ%ÿ(ÿ-ÿ8ÿMÿrÿxÿpÿmÿqÿtÿrÿiÿ_ÿSÿFÿ5ÿ)ÿ'ÿ%ÿ%ÿ&ÿ)ÿ0ÿ9ÿCÿNÿWÿbÿkÿpÿmÿhÿ`ÿYÿOÿ@ÿ2ÿ/ÿ0ÿCÿmÿxÿxÿÿmÿcÿcÿnÿzÿrÿ=ÿAÿ?ÿ:ÿ8ÿAÿCÿBÿEÿAÿGÿ_ÿvÿƒÿ€ÿÿŽÿ‡ÿŽÿ”ÿ“ÿ™ÿ ÿ¤ÿ¯ÿ®ÿ°ÿ¹ÿ¼ÿ¼ÿ¹ÿ³ÿ°ÿµÿ´ÿµÿ©ÿ©ÿ¡ÿÿwÿUÿTÿYÿeÿsÿyÿyÿyÿyÿuÿmÿ^ÿNÿLÿLÿNÿNÿOÿRÿRÿPÿNÿKÿHÿFÿEÿDÿEÿCÿCÿCÿCÿBÿBÿAÿ@ÿ@ÿ@ÿ?ÿ>ÿ=ÿ=ÿ:ÿ9ÿ8ÿ6ÿ3ÿ.ÿ.ÿ,ÿ@ÿSÿPÿOÿNÿNÿKÿKÿMÿQÿRÿ:ÿ6ÿ9ÿ:ÿ8ÿ6ÿ5ÿ2ÿ2ÿ0ÿ2ÿ=ÿKÿPÿOÿNÿNÿNÿNÿNÿNÿPÿUÿUÿTÿTÿ9ÿ2ÿ9ÿ:ÿ;ÿ=ÿAÿFÿRÿWÿZÿ[ÿ_ÿbÿ`ÿaÿ_ÿ\ÿ_ÿYÿSÿNÿKÿOÿXÿZÿYÿXÿSÿPÿJÿ?ÿ<ÿ9ÿ7ÿ5ÿ0ÿ*ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ"ÿ"ÿ#ÿ&ÿ/ÿ;ÿ@ÿAÿCÿBÿ@ÿ?ÿ@ÿAÿAÿAÿBÿBÿAÿ@ÿ?ÿ<ÿ5ÿ)ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ"ÿ"ÿ"ÿ$ÿ&ÿ(ÿ*ÿ*ÿ(ÿ(ÿ)ÿ*ÿ,ÿ/ÿ0ÿ1ÿ2ÿ4ÿ5ÿ4ÿ7ÿ7ÿ5ÿ5ÿ6ÿ9ÿ9ÿ;ÿBÿJÿSÿbÿlÿbÿZÿRÿPÿOÿSÿPÿMÿMÿJÿFÿCÿAÿCÿCÿCÿDÿHÿJÿLÿMÿLÿNÿOÿRÿUÿ[ÿaÿfÿkÿrÿvÿwÿxÿxÿxÿyÿ}ÿ€ÿ€ÿ„ÿ‡ÿ‰ÿ‰ÿ‡ÿ†ÿ„ÿ‚ÿ€ÿÿÿ€ÿ€ÿÿ‚ÿ„ÿ‡ÿŠÿÿŽÿÿŒÿŠÿ‡ÿ„ÿÿ{ÿyÿsÿoÿmÿmÿmÿmÿmÿlÿkÿiÿfÿgÿgÿgÿiÿmÿrÿxÿ|ÿÿ…ÿˆÿ‹ÿÿÿŠÿˆÿ‰ÿÿ’ÿ’ÿÿÿÿÿŽÿŽÿŽÿ‹ÿ‡ÿ‡ÿŒÿÿyÿyÿ{ÿ|ÿ|ÿ|ÿzÿyÿxÿxÿwÿwÿwÿwÿwÿwÿwÿwÿwÿwÿwÿxÿ{ÿ‚ÿ‡ÿÿ“ÿ”ÿ’ÿ‘ÿ‘ÿ“ÿ‘ÿŒÿ‹ÿ‹ÿŠÿˆÿˆÿ‹ÿÿ“ÿ•ÿ•ÿ•ÿ’ÿÿÿÿŽÿŒÿ‹ÿŠÿˆÿ)~)€)~)€*~**~**~*~+~+~+~+~+~,},~-}-~-}-~.}-~.}.~.|.~.|.~.|.~.|.~.}.~.}/~.|/~0|/}/|0}0}/}/~/}/~/}/.}..}..}..}..}..}..}..}..}..}..}..}.€.}.€.}.€.}.€.}.€.}.€.}..}..}..}.-},~,},~,},},},}+~+~+~+~+~+}+~*|*~*|*~*|+~+{+~+{,},z,},|,},{+}+z*}+z+}+z+|+{+|*{*}*{*}*{)~'{&~&|&~&|%~%|$$}%%}%%}%%}%%}$$}$~$|$~$}#~#~#~##}#€#}#€#~##~"#${'ƒ+t4‡Gkiˆvcn‡jaq…vfvƒnicVkF{5q'{$w$~$y%&z)€1y:‚EtP„Zmd…lipƒkhf]lR~Gp:}.w-€4{Uƒs…xxl‹c€a…hw‡~|U:x@’7x6•?xA˜CxD™AxQšuv†™Œs†–’s••“p–”–m“”—oš’›s¥¦w¬Š¹}¾ƒ¼€³ƒ³²³€±~¬¤y¨†¦k”†€`^ƒbmt€~~~€}€|€{x€xutk~`Y|XW{UƒR{RƒO{L…J|G…D|E„F|F„D|D„D|BƒC}Aƒ@~?ƒ?~@ƒ>~=ƒ<~;ƒ:|9„8{7†5z4‡.z-‰/y?‹OxLŒMyLJyIŽGxIŽPxL6w6’9x6”5x3“1x0“/z5‘C{MŒR|RˆN}OƒM}M‚L|LƒMzP…SyWˆ\vVŠFw;‹9w<‰;w=Š@uI‹PrVŽWqZZoZ’Ym[”ZnY•UoP”LsL‘StXZtVRvNŒLwCŠ9z8‹4z0‹,y(‰%|$ˆ$|$‡$|$‡$}$ˆ$}$ˆ$}#‡#}#‡"}"‡#|$‡+{8„@zAƒB{CBzAAzA@{@€AzA>{??|<7~*€!"‚"!‚!~!ƒ!~!ƒ""ƒ""ƒ""ƒ##ƒ"~"ƒ$~&ƒ+€*ƒ)€*ƒ*)ƒ**ƒ*€*ƒ/€2„2€4„4€0ƒ10ƒ/57}<|@{EtJx[kfyjca{YhT}OuP}PN{LƒK|GƒC|AƒB|A„E{F„I{K…L{M…MzM†NxQ†Uv]‰ati‹psxŽ|s}Ž}s{ŒzrzŽ~pŽ‚o†Ž†pˆŽˆq‡‡r„ƒr€r€r€Žrƒ‹†uˆˆ‹x…Ž|Œ‚‹}‰ƒ†|‚„~zy†two‹mrmŽmpl‘mom’koj’ioh’goh’hnj’nmt’yl~‘m†ŠnpŒŠt‰†z’v‘l‚Žk‹ƒnƒp‹ƒˆs†‰|ˆ{yˆyry‘|m}•{l{”zly”xmx”vmv•wmw•wlw•wkw•vkv•vjy•}jƒ’‡m“s“ˆ‘y„‘|“x€Œq‚ŠrŠˆvˆz~”€•{“ˆ•v‘Šu‹uŒ‹tŒŒ‰vˆŒ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ0ÿ0ÿ/ÿ/ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ'ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ#ÿ"ÿ#ÿ%ÿ(ÿ0ÿ=ÿ^ÿyÿsÿiÿnÿvÿvÿqÿgÿZÿLÿ6ÿ(ÿ$ÿ#ÿ$ÿ$ÿ%ÿ&ÿ+ÿ3ÿ;ÿFÿQÿ[ÿeÿnÿnÿjÿaÿZÿMÿAÿ3ÿ-ÿ-ÿ8ÿ\ÿxÿwÿsÿ~ÿpÿaÿcÿhÿtÿ€ÿuÿ@ÿ=ÿ:ÿ5ÿ<ÿ@ÿAÿCÿLÿfÿ†ÿÿÿ”ÿ–ÿ–ÿ˜ÿ™ÿ’ÿ„ÿŒÿ‘ÿ‘ÿšÿ ÿ¤ÿ²ÿ¶ÿ·ÿ´ÿ±ÿ¯ÿ¬ÿ­ÿŸÿ™ÿ–ÿ–ÿ”ÿÿnÿtÿ{ÿzÿvÿwÿvÿwÿtÿsÿsÿsÿtÿqÿoÿmÿjÿhÿdÿbÿ]ÿYÿTÿRÿNÿLÿEÿDÿDÿBÿAÿBÿAÿ@ÿAÿAÿ@ÿ?ÿ=ÿ=ÿ<ÿ;ÿ:ÿ9ÿ8ÿ7ÿ5ÿ4ÿ3ÿ0ÿ-ÿ-ÿ@ÿMÿGÿGÿFÿFÿFÿFÿKÿQÿFÿ6ÿ8ÿ6ÿ5ÿ3ÿ0ÿ/ÿ/ÿ8ÿIÿQÿSÿSÿQÿOÿMÿMÿKÿKÿLÿLÿPÿRÿYÿWÿXÿLÿ;ÿ6ÿ9ÿ<ÿ=ÿCÿJÿMÿRÿTÿVÿTÿTÿTÿUÿVÿQÿMÿMÿSÿXÿYÿVÿSÿOÿLÿIÿBÿ8ÿ1ÿ/ÿ+ÿ%ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ$ÿ$ÿ%ÿ#ÿ"ÿ$ÿ"ÿ"ÿ#ÿ#ÿ&ÿ2ÿ=ÿ?ÿ@ÿAÿAÿCÿCÿAÿ?ÿ=ÿ@ÿBÿBÿ@ÿ?ÿ>ÿ8ÿ,ÿ"ÿ"ÿ"ÿ"ÿ!ÿ!ÿ!ÿ!ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ#ÿ#ÿ"ÿ"ÿ$ÿ$ÿ+ÿ)ÿ*ÿ+ÿ,ÿ*ÿ(ÿ(ÿ(ÿ+ÿ0ÿ/ÿ/ÿ0ÿ-ÿ*ÿ*ÿ1ÿ4ÿ0ÿ3ÿ8ÿ7ÿ9ÿ>ÿOÿWÿaÿWÿQÿZÿYÿQÿPÿMÿKÿIÿFÿCÿ@ÿ@ÿCÿEÿEÿHÿLÿMÿKÿMÿMÿLÿOÿTÿ[ÿcÿjÿsÿzÿÿÿ‚ÿ€ÿ~ÿ~ÿ~ÿÿÿ„ÿ†ÿˆÿ‰ÿ†ÿ†ÿƒÿƒÿÿ€ÿÿ€ÿ€ÿƒÿ†ÿ‡ÿ‰ÿŠÿÿŽÿ‹ÿ‹ÿˆÿ…ÿ‚ÿ{ÿwÿrÿnÿmÿmÿmÿlÿlÿlÿkÿjÿhÿhÿgÿhÿhÿjÿoÿuÿzÿ}ÿÿ†ÿŠÿÿŒÿ‹ÿ‰ÿŠÿÿ’ÿ‘ÿÿŽÿÿÿŽÿÿ‹ÿˆÿ†ÿ†ÿŒÿ†ÿzÿxÿzÿ{ÿ}ÿ{ÿzÿzÿxÿwÿwÿvÿvÿvÿvÿwÿwÿwÿwÿvÿvÿvÿyÿ}ÿƒÿˆÿÿ“ÿ‘ÿÿÿ“ÿ“ÿÿŒÿŒÿÿ‰ÿ‡ÿ‡ÿŒÿ’ÿ”ÿ”ÿ•ÿ“ÿ‘ÿÿÿÿŽÿŒÿ‹ÿ‰ÿˆÿ)~)€)~)€*~**~++~+~+~,~,~,~,~,~-~.~.~.~.|.|.|.|/~/{/~/{.~.{.~.{-~-|/~/|0~0|0~0|0~0|/~/|/}/|/}/|.}.}.}..}..}..}..}..}..}./}/-}-0}0/}//}/~/}/~/}/.}..}./}//}..}..}..}.-},~-}-~-}-|,},|+~+~*~,~+}+|+}+},},|,},|,},|,},|,},|,},|,},z+},z,},{,},{+}+{+}+{*}*{*}){*~)|&~&|&~&{%~%{$$|$$|$$|%%}$$}$$}$~$}#~#}#~#~#~##}#~#}#$~$~#~#~%€)z,…6pNˆsgw‡lbj†reuƒqik€_lO~:p*|$w!|"{"#|$'|+3y=‚HuR‚^nd„kijƒeh]RnH€=u1€,x5‚Hzkƒ}…}Ž€s‹b€c†ht…€}ˆey59y6’8z?•CzB™[y|˜x“•‘u™”žr˜“”pœ“”m‡“Šm’‘r˜Ž¤w£‹©|«ˆ¯³†´}©ƒ£{€{‘~ƒŒq††`t„git€uu€trqro€o€nnnl€l‚i€f‚b`ƒ__ƒ^€\…\€\…XUƒTQƒMJƒHEƒA~=ƒ=~>ƒ=~=ƒ=|<ƒ:}9…8{7†6z5‡2y1ˆ0z.Š.y<ŠKzHŠEyFŠCzBŒBxGRxA6w87w2‘1x1‘1y=L{TŒW}W†S~QƒP~O‚K~K‚K}K‚M{Q„RxS‰NwTŠSvEŠ‰6|/‡.|+ˆ&|%ˆ$}$ˆ$~$‡$~$‡$}$ˆ$}$ˆ$}$‡$}$‡#}#‡#|#‡$|+†8|>…@{@ƒ@z@BzA€>z>?{>?{=€<};5~+‚"!‚!"ƒ"!ƒ""ƒ""ƒ""ƒ!!ƒ##ƒ#}#ƒ$}$ƒ'(ƒ(*ƒ))ƒ(+ƒ./‚,+‚)€*ƒ))ƒ*|-ƒ1|7<{=xCxGsNuWl[uaboyt[b~UgONzMKG}DƒB{@ƒA{EƒHzH„IzK…MzM†MzM†LxO†RvZ‰btk‹vr|Œ€rŽƒs‚€s~|qŒp…Ž‡pˆŽ†r†Ž„rƒƒr€q€€q€„r‡‹ˆvŠ†Œz‚Œ|‹‚ˆ{†ƒƒ{†yys‰otmkqk‘kol’kok’koi’gof’goh’iom‘qmv’{k’m†‘Šor‹Šˆu{’pŽj‹kƒpŒ‚‰t…€„zŠƒ€y|yqy“{m|”{k{•ylx”xlw”vmv”ulu”vkv•vkv•wjw–xiz”~i„“ŠnŽŽ’u‘ˆy‚“}’vŽŽqŽ‚Žs‰‚‰wŠŽ}“|•‚“x”‰’v‘‹v‹vŒ‹vŠˆv‡)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ+ÿ*ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ-ÿ-ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ)ÿ(ÿ'ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ$ÿ(ÿ+ÿ2ÿEÿkÿ|ÿoÿkÿpÿtÿtÿnÿcÿUÿBÿ-ÿ#ÿ"ÿ"ÿ"ÿ"ÿ#ÿ$ÿ'ÿ-ÿ5ÿ=ÿHÿRÿ]ÿfÿjÿjÿbÿXÿMÿBÿ7ÿ0ÿ5ÿGÿiÿ|ÿ~ÿ}ÿvÿ}ÿwÿbÿdÿfÿpÿ€ÿ‚ÿ~ÿHÿ7ÿ7ÿ6ÿ=ÿAÿHÿjÿŠÿ—ÿšÿ–ÿ˜ÿ£ÿ¤ÿ›ÿžÿ›ÿÿ‹ÿŒÿŽÿ˜ÿ¢ÿ¦ÿ­ÿ¬ÿ®ÿ±ÿ­ÿ¦ÿœÿ“ÿ‹ÿˆÿ‹ÿ‹ÿ„ÿˆÿ€ÿkÿoÿoÿqÿqÿpÿmÿmÿkÿjÿkÿjÿiÿiÿhÿdÿbÿeÿ`ÿ_ÿ_ÿZÿYÿXÿXÿXÿWÿTÿQÿSÿRÿSÿRÿRÿPÿGÿ?ÿ=ÿ=ÿ;ÿ:ÿ:ÿ9ÿ8ÿ7ÿ7ÿ6ÿ4ÿ2ÿ2ÿ.ÿ.ÿ.ÿ=ÿDÿBÿAÿCÿBÿBÿDÿIÿTÿ>ÿ6ÿ6ÿ4ÿ0ÿ2ÿ4ÿBÿOÿSÿVÿWÿSÿSÿPÿOÿLÿJÿJÿLÿLÿMÿQÿSÿMÿNÿTÿSÿLÿBÿ?ÿ@ÿBÿDÿIÿMÿNÿMÿMÿJÿDÿDÿEÿGÿHÿFÿDÿDÿCÿEÿFÿCÿ@ÿ9ÿ1ÿ,ÿ+ÿ)ÿ'ÿ&ÿ%ÿ%ÿ$ÿ$ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ#ÿ%ÿ,ÿ6ÿ<ÿ?ÿ?ÿ?ÿ@ÿ?ÿ?ÿ?ÿ@ÿ>ÿ<ÿ:ÿ8ÿ3ÿ*ÿ#ÿ"ÿ"ÿ!ÿ!ÿ"ÿ!ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ&ÿ'ÿ'ÿ'ÿ&ÿ*ÿ,ÿ0ÿ9ÿ<ÿ3ÿ(ÿ(ÿ'ÿ&ÿ'ÿ)ÿ1ÿ=ÿCÿEÿMÿPÿQÿUÿVÿYÿ_ÿpÿ€ÿyÿ^ÿXÿNÿKÿLÿIÿAÿ@ÿBÿBÿCÿFÿGÿIÿKÿMÿNÿNÿNÿMÿNÿOÿUÿ^ÿgÿrÿ{ÿ€ÿÿƒÿ‚ÿ€ÿ~ÿÿÿÿ…ÿ‡ÿ‡ÿ†ÿ…ÿ‚ÿ‚ÿƒÿÿ€ÿ€ÿ‚ÿ‚ÿ†ÿ‰ÿŠÿ‹ÿÿŒÿ‹ÿˆÿ‡ÿƒÿ€ÿ{ÿwÿrÿnÿlÿkÿkÿkÿlÿkÿkÿkÿiÿgÿfÿgÿhÿiÿmÿqÿvÿ{ÿÿÿ†ÿŒÿ‹ÿ‹ÿŠÿŠÿ‘ÿ‘ÿÿÿÿŒÿŒÿÿÿ‹ÿ‡ÿƒÿ†ÿŒÿ†ÿ|ÿzÿyÿzÿ|ÿ|ÿ|ÿ{ÿyÿxÿwÿvÿvÿvÿuÿuÿvÿvÿvÿvÿwÿwÿxÿzÿ~ÿ„ÿŠÿŽÿÿÿŽÿ‘ÿ•ÿÿÿŽÿÿŒÿˆÿ‰ÿŒÿ‘ÿ“ÿ•ÿ“ÿ”ÿ’ÿ‘ÿÿÿŒÿ‹ÿŠÿ‰ÿ‡ÿ†ÿ)~)€)~)€+~+,~,,~,~,~-~-~-~-~-~.~.|.~.|..}/0|/~/{/~/{/~/{/~/{0~0{0~0{0~0|0~0|0~0|/~/|0}0|/}/|.}.|/}/}/}/~.}.~.}.~.}.~.}.~.}.~0}0/}//}//}//}/~.}.~.}.~/}/~.}..}..}./}//}/.}..}-~-}-~,},~,},~,~,}-~+}+}+|+}+},},},},},},},},}-}-|,},|,}-{,},{,},{,},{+~+|+~+|*}*|)})|(~'|'~'|&~&{%~%{%%{%%{$${$$|$$}$$}$~$}!~#}$~$~$~$~$~$~$~$#~$$$~(ƒ.w<‡`l†tbl…qcv„yhqhjZ}Nn5|$t!}!y#}#{!#}%'{.5y<‚GuQ‚[qdƒimeamVJnA9m6ƒ?j]„‚mˆ‚~~zvŽ{~{h€b†bl…~„ˆƒ|t‹1y95{<’@{O•v|‘”›zž‘Ÿx¢¡u¤ qœo™‘nŒ‘q“žv¤¥{¤‰¡} †Ÿ~ž†˜{‘…Šx{t}xwu‚{eŠ‚iekk{l€m„j€k„i€gƒg€hƒf€fƒd€a„[€V„[\„ZX„ZXƒTQƒP€P†O€M†M€M…M€N…QS„RG„A~<ƒ;|9ƒ:{8ƒ9z8…8z6†5y5‡1y/‰0x/‰2yF‰GyEŠEzB‹DyE‹IxRPw6Ž3w32x16zGŒQ{T‰T}U…U~RƒR~O‚M~K‚K}KK{K‚M|MƒPzO†N{O‡LzDˆ6z2ˆ:{?ˆAzE‡FyG‰HxFŠBxC‹CxCŠBy?ŠCzF‰I|J‡H}F†A}<†0}+‡(}'ˆ)z'ˆ'|'ˆ&|&‡%|%‡$}%ˆ$}%ˆ'}'ˆ&|&ˆ&|$ˆ$|%ˆ%|%‡&|*†0|7…;{>ƒ?|=?|@€?{<8|4-~&€#~!‚"#ƒ!"ƒ" ƒ""ƒ""ƒ""ƒ""ƒ""ƒ"#ƒ&&ƒ%&ƒ''ƒ'(ƒ')ƒ4:‚/(&€%‚'~'ƒ+|1;|B}E}JwN|QqSzSlWxXcYzjR~}]P^ogP‚HzGC€@}@‚@{B„EzF…IzK…LzL†NzN†MxN†MvQˆWtcŠmrvŠ}r€‹s~s}}q~Œ‚p‡Ž‰p‰Ž‡r…Ž„r‚‚rqŽƒq„ŒˆsŠˆ‹xƒŒ{‹‚ˆ}‡ƒ†z‚…}yw‡twq‹lskkok‘knk’kok’joh’fof’fog’imm‘qlv’{k’‚k‡‘‹n‹‹s‹†Ž}‘uŽnŽ‚mƒŽnŒ‚Œq‰„u‚€Š}Œ|‡|ty‘yn|”|k}•{kz”xlw”wlu”umu”ulu”ukv•ukv•uju–xiz”i…’‰nŽtˆx’”|uŽ€ŽrŽŒs‰‚ŠxŽ€‘~”{”„”w•‰’uŒŽu‹uŒŠu‰ˆu‡)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ1ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ0ÿ0ÿ0ÿ/ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ$ÿ&ÿ)ÿ3ÿOÿyÿÿqÿpÿwÿ}ÿzÿpÿdÿTÿCÿ-ÿ#ÿ!ÿ"ÿ"ÿ#ÿ#ÿ$ÿ&ÿ(ÿ+ÿ3ÿ<ÿGÿRÿ\ÿbÿfÿdÿ`ÿUÿKÿDÿCÿLÿ`ÿ{ÿŒÿ†ÿzÿwÿvÿzÿ|ÿiÿaÿ`ÿjÿ|ÿ„ÿƒÿˆÿZÿ+ÿ2ÿ6ÿ>ÿUÿzÿÿ›ÿÿ¥ÿ¦ÿ¥ÿ§ÿ¢ÿ™ÿ ÿŸÿ”ÿÿŽÿŽÿ—ÿÿŸÿ¢ÿŸÿÿœÿ”ÿŠÿÿuÿiÿbÿaÿ\ÿnÿ‡ÿvÿ_ÿgÿgÿhÿhÿfÿeÿcÿbÿdÿeÿbÿ_ÿ_ÿ_ÿ]ÿZÿXÿZÿXÿVÿQÿOÿMÿIÿIÿIÿKÿKÿKÿLÿKÿKÿMÿNÿMÿGÿCÿ?ÿ<ÿ:ÿ9ÿ9ÿ8ÿ7ÿ5ÿ5ÿ5ÿ4ÿ1ÿ1ÿ0ÿ.ÿ9ÿHÿEÿBÿEÿDÿFÿFÿJÿOÿFÿ2ÿ2ÿ4ÿ3ÿ7ÿHÿRÿUÿTÿTÿSÿSÿSÿQÿOÿMÿKÿJÿKÿLÿKÿKÿMÿMÿKÿKÿJÿ?ÿ0ÿ%ÿ'ÿ,ÿ2ÿ9ÿ;ÿCÿGÿFÿDÿ@ÿ@ÿAÿ@ÿBÿFÿJÿIÿHÿFÿCÿ@ÿ6ÿ/ÿ*ÿ'ÿ'ÿ'ÿ&ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ%ÿ$ÿ%ÿ'ÿ(ÿ'ÿ'ÿ'ÿ&ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ*ÿ-ÿ3ÿ8ÿ:ÿ=ÿ>ÿ;ÿ6ÿ1ÿ*ÿ$ÿ"ÿ"ÿ%ÿ$ÿ"ÿ#ÿ$ÿ%ÿ!ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ#ÿ#ÿ"ÿ#ÿ$ÿ$ÿ%ÿ%ÿ&ÿ&ÿ'ÿ(ÿ&ÿ'ÿ(ÿ&ÿ'ÿ(ÿ'ÿ&ÿ&ÿ%ÿ'ÿ*ÿ2ÿ6ÿ>ÿDÿGÿMÿNÿPÿSÿZÿcÿnÿÿaÿNÿ^ÿ\ÿIÿCÿAÿ?ÿ?ÿ@ÿBÿFÿHÿIÿKÿLÿLÿNÿNÿMÿNÿJÿOÿSÿ]ÿiÿqÿyÿÿÿÿ~ÿ}ÿ|ÿ}ÿ„ÿ†ÿ‡ÿˆÿ†ÿ…ÿ„ÿ‚ÿ‚ÿÿÿÿ‚ÿ…ÿ‰ÿŠÿŒÿŒÿ‹ÿ‰ÿŠÿ‡ÿ„ÿ€ÿ|ÿwÿsÿnÿmÿlÿkÿkÿkÿkÿkÿkÿjÿhÿfÿfÿfÿfÿhÿlÿqÿvÿ{ÿÿ‚ÿ‡ÿ‹ÿ‹ÿŒÿŒÿÿ’ÿ‘ÿÿÿÿÿŽÿŒÿŠÿ‡ÿƒÿ„ÿŒÿˆÿÿ{ÿvÿxÿyÿzÿzÿ{ÿyÿwÿvÿwÿuÿuÿuÿuÿuÿuÿuÿuÿvÿuÿuÿyÿ{ÿÿ†ÿŒÿŽÿŽÿŽÿŽÿ”ÿ”ÿŽÿÿŽÿÿ‹ÿ‰ÿ‹ÿŽÿ‘ÿ”ÿ”ÿ•ÿ”ÿ’ÿÿŽÿÿÿ‹ÿ‰ÿˆÿ‡ÿ†ÿ*~*€*~*€,~-,~,,~-~.~.~.~.|.~.~.~/~/~/~/~/~0~0{/}/{1}1{/}/{/}/{0~0{0~0{0~0|0~0|0~0|0~0|0~0|1~1|/}/|/}/}/}/}.}0}/}/~/}/~.}.~/}/~.}./}//}//}/.}..}..}..}./}//}//}//}/.}.~.}.~.}-~.}.~.}..}.,},~,},~,},~,},~,},|,},|-}-}-}-},},|-},|-}-}.}.}-|-|,|,|,},|+}+|*~*|)~)|(~({&~&{&~&{%~%{%~%|%~%|%%|$$|%~$|$~$|$~$}%~$}$~$~$~$~$~$%~%%%~%'|+†;vf‡kw…qer…{gƒyik^kM|:o(|#v"}!{"}#}"€#}&€(}*€1{:EwOƒZs`„cpcƒ`pVƒOlQ„\cmˆ}_ˆ„Œi‡x‚xv‘|~}k€`‡^dƒz‚„…ˆ~‹ŽzCŽ+z2<}T’z}—{ŸŽ§z©Žªx©¥užŸq¡šoŽ‹r‰’‰vŽ’z˜‰š{›ˆ‹|…‡~{w…l|g‚hiza‚qk}ƒ„e]‚bwfh„f€dˆb€_†db„b€`„^€]…[€Y…VV†VT†QO†KI†G€G‡E€F‡H€H†I€J†JJ†KM†O~O„H|C„<{9ƒ7z7…6z5‡6y6‡5w3ˆ3w3ˆ/y0ˆCyK‰G{FŠG{GŠCyF‹IxM?w11x2:|GŒR}TˆU~S„S€R‚S~Q‚PNM~L€L~LK}KƒJ}IƒG}F„D}:…,}#†$|$†%|)ˆ-}6‰<|=ˆ>{<ˆ>zA†A{B†F~I…EC„A:„4|,†(z(ˆ'{*ˆ+y+‰*y(‰)z(ˆ'|'ˆ&}&‡&}&‡&|&ˆ'|&ˆ'}'ˆ&}$ˆ&}%‰$}#ˆ#}#†"}%†+}.…2}3ƒ1~-'~""~"ƒ#~$ƒ%"ƒ"#ƒ##ƒ""ƒ""ƒ"#ƒ"~$ƒ%%ƒ&~&‚)~+‚'&&$‚&€&ƒ%%‚''ƒ&&ƒ''ƒ((ƒ)})‚,}.1{8~={EvJ|PmW{`bj|vOu€\LY‚\Zj‚^pG@A~A€?zAƒFyH…IyK†KyK†LxN†MxK†JwL‡PuWˆesm‰vq|‹~r~Œ~t}Œzr~‹…qˆˆq‡†s„ƒrŽrŽs€ŽƒsˆŠŠv‹‡zƒ‹|Šƒ‹|†„…z‡{xwŠstnŽkrk‘kpk’jnj’knj’hng’gog’gmg’hml‘qkv’|i’„k‰’ŠoŠŒv}‘}‘o€ŽlƒŽnŽƒŽpŒ‚‰t…€„{‡€Œ‚„y~‹zry“xmy•{k{•zlx”vmv•umt•ums•uku•vkv–vku–wjv–xj}“€k…‘‹oŽŒŽuŒ‡{”|’sŽŽsŠu‰{‘}“”x•ˆ”u“Š’t‹Œt‹Œ‹t‹ŒŠtˆ‡t†*ÿ*ÿ*ÿ*ÿ,ÿ-ÿ,ÿ,ÿ,ÿ,ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ0ÿ0ÿ1ÿ1ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ)ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ!ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ&ÿ)ÿ,ÿ?ÿqÿ„ÿtÿrÿyÿ€ÿ‚ÿuÿfÿYÿFÿ1ÿ$ÿ#ÿ"ÿ"ÿ"ÿ#ÿ"ÿ#ÿ%ÿ'ÿ*ÿ1ÿ9ÿDÿOÿZÿ`ÿcÿdÿaÿZÿ]ÿgÿqÿ~ÿ†ÿ‰ÿŠÿ„ÿyÿ~ÿÿ€ÿÿjÿ`ÿ\ÿ^ÿvÿ‡ÿÿŒÿŒÿzÿ6ÿ/ÿ5ÿOÿwÿ‹ÿ—ÿ ÿ§ÿ©ÿ©ÿ¨ÿ§ÿ¢ÿœÿœÿ—ÿ‹ÿ‡ÿÿ|ÿ‚ÿŒÿ’ÿ‘ÿŠÿyÿuÿvÿsÿfÿaÿgÿkÿrÿ}ÿuÿ~ÿlÿVÿ_ÿcÿbÿ`ÿ\ÿ\ÿ_ÿ]ÿ\ÿ[ÿ\ÿXÿVÿSÿSÿSÿQÿPÿMÿKÿHÿFÿDÿAÿAÿBÿAÿAÿCÿDÿDÿDÿEÿGÿIÿIÿJÿGÿBÿ<ÿ8ÿ6ÿ6ÿ5ÿ6ÿ6ÿ6ÿ5ÿ3ÿ3ÿ2ÿ2ÿ7ÿKÿOÿGÿGÿGÿGÿJÿGÿHÿLÿ=ÿ0ÿ2ÿ8ÿFÿPÿTÿTÿUÿSÿRÿPÿOÿPÿNÿMÿLÿKÿKÿKÿKÿJÿIÿFÿEÿBÿ8ÿ*ÿ#ÿ#ÿ#ÿ%ÿ&ÿ'ÿ&ÿ)ÿ+ÿ/ÿ:ÿ<ÿ<ÿ@ÿDÿDÿEÿFÿBÿ5ÿ-ÿ*ÿ+ÿ-ÿ-ÿ-ÿ*ÿ+ÿ+ÿ*ÿ)ÿ*ÿ)ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ&ÿ&ÿ%ÿ$ÿ$ÿ$ÿ#ÿ"ÿ#ÿ#ÿ"ÿ$ÿ$ÿ"ÿ"ÿ!ÿ"ÿ"ÿ!ÿ#ÿ#ÿ"ÿ"ÿ"ÿ#ÿ#ÿ#ÿ"ÿ"ÿ"ÿ"ÿ"ÿ#ÿ$ÿ$ÿ)ÿ,ÿ1ÿ5ÿ9ÿ7ÿ5ÿ.ÿ)ÿ'ÿ(ÿ(ÿ%ÿ%ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ(ÿ(ÿ(ÿ*ÿ+ÿ*ÿ,ÿ/ÿ7ÿDÿMÿTÿXÿ[ÿ\ÿ[ÿ^ÿhÿiÿWÿPÿJÿCÿ?ÿ>ÿ?ÿAÿCÿFÿHÿIÿKÿKÿKÿKÿLÿMÿKÿJÿJÿOÿVÿbÿiÿtÿzÿ~ÿ~ÿ~ÿ}ÿzÿÿƒÿ‡ÿ‡ÿ‡ÿ…ÿ„ÿƒÿÿÿÿÿ‚ÿ‡ÿˆÿ‹ÿŒÿÿŽÿ‹ÿŠÿ‰ÿ„ÿ„ÿÿzÿvÿpÿlÿiÿkÿlÿlÿjÿjÿjÿiÿgÿfÿfÿfÿgÿiÿjÿnÿrÿwÿ{ÿÿ†ÿŠÿŠÿŠÿ‹ÿÿÿŽÿŽÿŽÿŒÿÿŽÿÿ‹ÿ‡ÿ„ÿ…ÿ‹ÿˆÿÿ}ÿyÿxÿxÿyÿ{ÿ{ÿ{ÿyÿvÿvÿuÿtÿsÿtÿuÿuÿvÿvÿvÿuÿuÿwÿyÿ~ÿƒÿˆÿ‹ÿÿÿŽÿ’ÿ”ÿÿŽÿŽÿŽÿŒÿ‰ÿ‰ÿÿ‘ÿ“ÿ”ÿ•ÿ”ÿ’ÿÿŽÿŒÿ‹ÿ‹ÿ‹ÿŠÿˆÿ‡ÿ†ÿ*~*+~++~,~-~-~+~,~.~.~/~/}0~0|0~0}0~0|1~1{1~1|1}1{1}1{0}0{1}1{0~0{1~1{0~0|1~1|1~1|1~1|1~1|1~1|1}1|1}1}0}0}/}/}/}/~/}/~.}.~.}/~.}./}//}//}//}/.}..}./}/.}.0}0/}//}/.}.€.}.€.}--}--}..}-,},~,},~,},},},}-}-|-}-|-}-}.}.},},},}.}.}.}.}.}-}-|-}-|,},|+}*|*~*|)~(|(~({&~&{&~&{&~&{%%|%%|%%|$$|%$|$$}#~#}#~#}$~$~$~$~$~$$~$%%}%+{.„@vo†nu„thyƒ€j€pk_~Qm=|+s$|#x#}#}"~#~"#{%({,€0z8CxNƒXu^„ascƒ_p_…kguˆ}^…ˆ‰_‹„‹mƒ|ƒƒ~…‘}|m_‡[€\ƒt}…†{ynŒvw~bx2‘1|Dp…Œ”}Œ¥|¨‹¨z¨Œ¨w£œs™Ž“q„r’wtuw„xuŠgxe‡l{q…j}`eZ{pƒpm]…qby„UqS€WƒY€YŠY€ZˆZV‡W€V‡T€SˆS€NˆOPˆLLˆKIˆFDˆA€?‰=€<‰;€<ˆ?€@ˆA@ˆCEˆF~F†E|C†C{A…ÿjÿÿuÿsÿzÿ€ÿzÿeÿWÿIÿ3ÿ'ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ"ÿ#ÿ$ÿ'ÿ*ÿ-ÿ6ÿAÿLÿVÿ]ÿ`ÿbÿaÿ_ÿnÿ|ÿ…ÿˆÿˆÿŒÿÿƒÿxÿxÿ{ÿ}ÿ}ÿoÿ]ÿZÿ[ÿgÿfÿ_ÿiÿ{ÿƒÿ|ÿHÿ)ÿ>ÿdÿ}ÿŽÿ–ÿŸÿ¤ÿ¦ÿ¨ÿ¦ÿ¢ÿÿ—ÿ“ÿ€ÿuÿ|ÿvÿpÿrÿpÿoÿlÿeÿaÿhÿhÿdÿgÿpÿeÿNÿ^ÿIÿcÿ…ÿYÿMÿQÿSÿTÿUÿSÿUÿUÿSÿRÿRÿPÿNÿHÿIÿKÿJÿJÿGÿEÿBÿ@ÿ=ÿ;ÿ9ÿ8ÿ9ÿ9ÿ9ÿ;ÿ=ÿ<ÿ?ÿBÿCÿCÿEÿEÿBÿBÿ@ÿ<ÿ7ÿ6ÿ4ÿ4ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ4ÿ3ÿ>ÿIÿEÿDÿCÿDÿEÿDÿFÿHÿTÿCÿ6ÿBÿOÿTÿUÿTÿSÿQÿQÿOÿOÿNÿNÿMÿLÿKÿKÿKÿIÿIÿGÿEÿ?ÿ2ÿ$ÿ%ÿ#ÿ$ÿ$ÿ%ÿ&ÿ'ÿ'ÿ(ÿ*ÿ)ÿ4ÿ@ÿBÿBÿ?ÿ7ÿ.ÿ+ÿ-ÿ.ÿ/ÿ,ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ(ÿ(ÿ(ÿ'ÿ%ÿ%ÿ%ÿ%ÿ$ÿ#ÿ#ÿ#ÿ#ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ#ÿ#ÿ"ÿ"ÿ"ÿ#ÿ!ÿ#ÿ"ÿ"ÿ#ÿ#ÿ$ÿ$ÿ#ÿ#ÿ"ÿ%ÿ*ÿ4ÿ?ÿJÿSÿWÿWÿVÿRÿLÿAÿ3ÿ'ÿ$ÿ#ÿ%ÿ&ÿ&ÿ&ÿ&ÿ)ÿ)ÿ,ÿ/ÿ-ÿ,ÿ.ÿ3ÿ6ÿ;ÿ<ÿ?ÿHÿKÿPÿ]ÿlÿlÿgÿ_ÿYÿQÿNÿGÿCÿ?ÿ?ÿ@ÿAÿCÿFÿHÿHÿJÿKÿKÿKÿKÿJÿIÿHÿIÿLÿRÿ]ÿgÿpÿxÿ~ÿÿ€ÿÿÿ‚ÿ…ÿ†ÿ‡ÿ†ÿ…ÿ…ÿ‚ÿ‚ÿÿ€ÿ‚ÿ…ÿ‰ÿ‹ÿŽÿŽÿŽÿŽÿ‹ÿŠÿˆÿ„ÿ€ÿzÿvÿpÿkÿjÿjÿhÿjÿjÿjÿjÿjÿhÿgÿfÿfÿgÿhÿjÿlÿpÿtÿxÿ|ÿ€ÿ…ÿˆÿÿÿ‘ÿÿÿÿÿŒÿŒÿŒÿÿŽÿ‡ÿ„ÿ„ÿˆÿ‰ÿÿ}ÿzÿwÿwÿyÿzÿ{ÿzÿyÿxÿvÿvÿuÿtÿtÿsÿtÿuÿvÿvÿtÿvÿwÿxÿ}ÿÿ…ÿ‹ÿŒÿŒÿŽÿÿ–ÿ‘ÿŽÿŽÿŽÿÿŠÿ‹ÿŽÿÿ’ÿ“ÿ”ÿ•ÿ’ÿÿŽÿÿŒÿ‹ÿŠÿ‰ÿˆÿ‡ÿ†ÿ†ÿ**+++,~..~.~.~-~.~/~/}1~1|0~0|0~0|0~0{0~0{1}1{1}1{0~0{1~1{0~0{1~1{1~1|1~1|1~1{1~0{0~0{/~/{0~0|0~0}0~0}0~0}/}/}/}/}/}/}/}/}.}.}.}.}/}//}//}/.}..}..}..}..}..}./}/.}.-}-.}..}.,}--}-,},~,},~,},},},}-}-}-}-}-|-}.|.}-|-}.|.}.|.}.|.}-}-|-}-|,},|+}+|*~*|)~(|(~({(~({(~'|&~&|&~&{&~&{%$}%%}$$|%$}#~#|#~#|#~#}$~$}%~%~$~$~%%|'€-z3ƒ>te…pu…skv‚|np€amP~=p+|$w#}%|$}#~#~#~###&})-}6€@zKVw\ƒasd†elb‡nd€ˆ…]‰„Š_Œ‚‡nu…w|x{{~o~\†Y€Z„f{c‡gvpzsxt|‘=y-V€s‡“‹š~ Š¥{§‹¥y£ŒŸvšŒ–s†Žzr{“wqq‘juihyjˆezg…j}h‚qw|{€rtTHpQ„Wg~„bgI€OON‰KP‹MSˆRN‰LJ‰HD‰EH‰ED‰DCˆA?ˆ<6Š54Š43‰66‰77ˆ9>ˆB}B†B}A†A{A…@{@…<{9‡6{3‡4z6‡5z6‡5x4‡4x6‡8x=‰FzE‰D{DŠE{EŠCxE‹LwWGw@ŒMySŠU}S†S~Q„PO‚OOM~M€M~LKK€JJ€HF€>1ƒ$$„#~$†%}%†(}(‡'}'‡(}*‡+~4†96…0+†-{.ˆ.x-‰-x.Š.z-ˆ.{-‡.}-ˆ*~*†(|$†#~&ˆ'}*‡'|&‡'|(†)|)†*}'†&}&†$}$…$}#…#}"…"~!†#}#‡#}#‡#~#…"~"…"~#„#~%„$"„!"„!!„""„""…#(„2@LVuaijh‚gcaƒZgP„Bq3ƒ*z$‚$%%ƒ&~&ƒ(}*‚0}5‚:{=€A|=~8{L|\{l|v}u|v~v~sn{ga}ZQM€H‚D}@?|A‚ByD…GzI†IzJ…JxJ…KxK†IyI†IwI‡JuPˆ[sgˆrqz‹sƒŒ‚t€Œr„Œ†q‡Œ‡q…„s„ƒr€‚sŒ…sˆ‹Šu‹†Œ{ƒ{Œƒ‹|‹ƒ‡|ƒ…~zx‡rvnŠjrjŽioi‘joj’joj’ioh’hog’foh’jmj’mlr’vkz”~jƒ’‡nŠx“{“oŽmƒŒo‚pŽƒŠs„‚ƒ{…Š‚…zŠ}sy’vnv“ylz”{jz•xkw”ulu•tls•rlr•tju–vjv–vjv–viw–~i“ˆm‹ŒrŠv’˜|‘t€qŽŒsŠ‚Œv‘}’{”†”v”Š’sŒŽrŒrŠŽˆr‡Ž†r„„r‚*ÿ*ÿ+ÿ+ÿ+ÿ,ÿ.ÿ.ÿ.ÿ.ÿ-ÿ.ÿ0ÿ0ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ,ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ$ÿ%ÿ%ÿ$ÿ$ÿ%ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ%ÿ%ÿ$ÿ$ÿ%ÿ&ÿ)ÿ/ÿ6ÿAÿaÿ~ÿuÿsÿuÿxÿkÿZÿJÿ5ÿ&ÿ"ÿ$ÿ%ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ&ÿ)ÿ-ÿ5ÿ?ÿIÿSÿ\ÿaÿdÿgÿnÿzÿÿ„ÿ†ÿˆÿ†ÿ}ÿkÿpÿwÿvÿxÿzÿrÿ`ÿ]ÿ^ÿdÿgÿuÿzÿ{ÿyÿ}ÿŽÿxÿ/ÿ>ÿhÿzÿŠÿ“ÿ™ÿžÿ£ÿ¢ÿ¡ÿ ÿ›ÿÿ‡ÿ€ÿzÿvÿpÿgÿgÿfÿhÿgÿeÿgÿoÿxÿ|ÿxÿwÿoÿbÿ\ÿWÿgÿ]ÿCÿKÿJÿFÿFÿIÿJÿTÿUÿMÿGÿFÿDÿ@ÿ?ÿBÿAÿ?ÿ@ÿ?ÿ@ÿ>ÿ:ÿ6ÿ3ÿ3ÿ1ÿ0ÿ0ÿ3ÿ4ÿ4ÿ5ÿ9ÿ>ÿ@ÿ@ÿ?ÿ?ÿ?ÿ>ÿ>ÿ>ÿ>ÿ8ÿ4ÿ3ÿ5ÿ5ÿ6ÿ6ÿ6ÿ5ÿ4ÿ6ÿ7ÿ@ÿHÿFÿDÿDÿFÿEÿEÿHÿOÿXÿKÿHÿPÿUÿUÿRÿQÿPÿOÿOÿOÿMÿMÿLÿLÿLÿLÿKÿKÿIÿGÿ?ÿ3ÿ$ÿ$ÿ#ÿ$ÿ%ÿ%ÿ'ÿ'ÿ&ÿ'ÿ(ÿ*ÿ(ÿ'ÿ*ÿ'ÿ(ÿ,ÿ/ÿ.ÿ/ÿ/ÿ/ÿ.ÿ0ÿ.ÿ-ÿ/ÿ.ÿ/ÿ-ÿ'ÿ%ÿ#ÿ#ÿ&ÿ'ÿ*ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ&ÿ'ÿ'ÿ&ÿ%ÿ$ÿ%ÿ$ÿ#ÿ"ÿ"ÿ"ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ"ÿ"ÿ#ÿ#ÿ)ÿ5ÿ2ÿ%ÿ!ÿ!ÿ!ÿ!ÿ"ÿ"ÿ"ÿ"ÿ$ÿ,ÿ8ÿDÿSÿ_ÿkÿtÿxÿtÿoÿhÿ\ÿNÿ?ÿ0ÿ*ÿ%ÿ%ÿ%ÿ'ÿ)ÿ.ÿ0ÿ/ÿ4ÿ<ÿHÿYÿ[ÿPÿuÿ€ÿwÿsÿvÿvÿuÿtÿqÿmÿfÿ]ÿTÿLÿFÿBÿ?ÿAÿAÿCÿEÿGÿIÿIÿJÿJÿJÿKÿKÿIÿIÿHÿHÿJÿPÿ[ÿgÿrÿzÿ€ÿ„ÿ„ÿ‚ÿƒÿƒÿ†ÿˆÿ‡ÿ…ÿ„ÿƒÿ‚ÿÿ‚ÿƒÿ†ÿ‰ÿŠÿŒÿŒÿÿŒÿŠÿ‰ÿ‰ÿ„ÿ€ÿ{ÿvÿpÿkÿiÿiÿiÿiÿiÿiÿjÿiÿiÿhÿhÿgÿgÿjÿkÿkÿoÿsÿxÿzÿ€ÿ…ÿˆÿÿ’ÿ”ÿ‘ÿÿŽÿÿÿŽÿŽÿŽÿŒÿ†ÿƒÿ„ÿ‡ÿ‰ÿƒÿ€ÿ}ÿyÿvÿvÿxÿzÿzÿyÿxÿwÿuÿtÿsÿsÿrÿrÿtÿuÿvÿvÿvÿvÿwÿzÿ~ÿ„ÿŠÿŒÿÿÿŽÿ•ÿ”ÿÿŽÿŽÿŽÿŒÿŠÿŒÿÿ‘ÿ‘ÿ“ÿ”ÿ“ÿ‘ÿÿŒÿŒÿŠÿ‰ÿ‡ÿ†ÿ„ÿ„ÿ„ÿ‚ÿ,*+--,~--~.~.~-~.~1~1}0~0|2~2{3~3{2~2z2~2{2~2{2~2{1~1{1~1{0~0{1~1{1~1|1~1|2~2{0~0{1~2{2~2{1~1|1~1}0~/}0~0}/}/}/}/}/}/}/}/}/}/}/}/}/}/~/}/~/}//}/.}..}..}..}..}--}-.}.-},-}-,},.}.~.}.~,},},},}-}-}-}-}-}-}-}-}-|-}-|-}-|-}.|.}.|.}.|.}-}-|-}-|-}-|*}*|)~)|)~(|(~({'~'{(~(|'~'|&~&{&~&{%%{%%{%%z%$|$~$|$~$|#~#}$~$}$~$~$~%~&'{+€1y:ƒBt\…|pu…qmq‚qnl~Vo?|-r&|$y#~%|$}%~$~###€$&).}2€;zEƒQwZ…bph†mgv‡€Z‚†„Xƒ‚„fz_uO~m‚j~i‰p}p‰s~d…a€`ƒf|s†~wxŒvuy…v‰ˆya3~[Šr€‚ŠŠ€Š˜}žŠŸ{Šœx˜‹‹u††r‚’prg’aua‹ezhˆg{b„^}fo€w{p~orv}xlx€o`v„oe8€D|GB†AAŠ?AŠEEŠDAŠC>Š;=Š<;Š<=‰:8‰53Š21Š1/‰./‰1~1‰1~5‰9}<ˆ=}>ˆ>|>‡>|=‡={=‡;{8‡5z5†6z7†7z6‡8z8‡8z:ˆ<{DˆH{FˆG{E‰FxFŠFwHŒMvTŒNwH‹Q{UˆS~Q†PO†ON„M~M‚M~MLK€KJ€K€FA€5‚&%„$~%†%}%†&}'‡&}'‡)})‡(}&†%|$‡(|+‡-y/ˆ/x.Š.y.‰0{0‡/}/…0€1„/€'„""„#~%…'},†*|(†(|(†'|'†'}'†'}'†%}%…%}%…$}$…#}#†"}"…"}"…"~"…"~#…#~#„&~-„*$„"!„!!„!!„""„%/‚=K|Ziqv€~c„‚^zƒr]g„\eM„>o1ƒ)z'€),04}<€J}T€M|DO{O}_|~y|xv€v|uu~tq|mf|^V~M€FB~?D|AƒByD…GzI†IzI‡JxK‡IxI†HyH†GwG‡HuOˆYrgŠrp|Œƒq†Œ†r…Œ†r†Œ‡q‡Œ‡q…ƒs‚rŒ„s„‹ˆt‰ˆŠxƒŒ{ŒƒŠ{ˆƒ†{……„{~†xxt‰nujŒhqhŽioi‘hoh’joj’ioh’gog’foi’kmm’plt’yk|”n‡ŽŒt‘ƒ”{”r‘lƒŽlƒŽnƒpŠ„ƒv…}‰~‡„‚x~‹{pxwmv“xkz”yjx•wkv”tls•sls•sls•tju–uju–ujv–yj|•‚j†’‰mŒŒŒr†y•{’~s€sŠu‰‚Œz‘~“y“‡’u‘‹rŒŒr‹Ž‰r‡†r‡…rƒŽƒrŽ,ÿ*ÿ+ÿ-ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ/ÿ0ÿ1ÿ1ÿ1ÿ1ÿ2ÿ2ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ2ÿ2ÿ1ÿ1ÿ3ÿ3ÿ3ÿ3ÿ1ÿ1ÿ1ÿ1ÿ0ÿ/ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ,ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ*ÿ*ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ(ÿ.ÿ4ÿ;ÿFÿWÿwÿuÿqÿqÿoÿnÿSÿ7ÿ'ÿ%ÿ%ÿ#ÿ%ÿ%ÿ%ÿ$ÿ#ÿ#ÿ#ÿ$ÿ&ÿ)ÿ-ÿ3ÿ9ÿCÿPÿZÿcÿmÿtÿ{ÿÿ€ÿÿ|ÿuÿbÿ<ÿGÿkÿpÿpÿsÿqÿwÿhÿdÿcÿfÿsÿ|ÿwÿvÿ„ÿ‰ÿÿ‚ÿÿPÿCÿeÿtÿÿ†ÿŽÿ–ÿ™ÿšÿ˜ÿ•ÿ†ÿ„ÿˆÿ†ÿpÿ\ÿ`ÿaÿgÿhÿcÿ`ÿ]ÿcÿlÿyÿ}ÿuÿkÿlÿ{ÿ|ÿ|ÿÿLÿ9ÿBÿCÿ<ÿ;ÿ;ÿ<ÿ@ÿ>ÿ<ÿ<ÿ=ÿ;ÿ9ÿ9ÿ8ÿ7ÿ7ÿ8ÿ6ÿ4ÿ1ÿ/ÿ/ÿ.ÿ/ÿ-ÿ,ÿ-ÿ/ÿ/ÿ/ÿ2ÿ6ÿ9ÿ:ÿ;ÿ>ÿ>ÿ>ÿ=ÿ=ÿ=ÿ=ÿ;ÿ8ÿ7ÿ7ÿ7ÿ7ÿ7ÿ9ÿ9ÿ7ÿ:ÿ<ÿ=ÿDÿGÿFÿDÿDÿFÿEÿEÿHÿLÿPÿNÿHÿNÿPÿPÿOÿNÿMÿLÿMÿMÿMÿMÿLÿJÿKÿJÿIÿGÿCÿ9ÿ&ÿ$ÿ$ÿ%ÿ%ÿ%ÿ&ÿ'ÿ'ÿ(ÿ)ÿ)ÿ(ÿ&ÿ%ÿ$ÿ%ÿ+ÿ.ÿ.ÿ/ÿ.ÿ.ÿ2ÿ2ÿ/ÿ0ÿ3ÿ5ÿ5ÿ/ÿ'ÿ ÿ ÿ!ÿ#ÿ'ÿ,ÿ*ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ#ÿ$ÿ#ÿ#ÿ!ÿ"ÿ"ÿ#ÿ"ÿ#ÿ!ÿ!ÿ!ÿ!ÿ"ÿ"ÿ&ÿ0ÿ?ÿLÿYÿjÿ{ÿ‡ÿŠÿˆÿÿzÿqÿfÿ\ÿMÿAÿ8ÿ4ÿ3ÿ6ÿAÿSÿaÿlÿkÿ[ÿ]ÿbÿgÿyÿ}ÿ‚ÿ€ÿwÿtÿtÿuÿuÿsÿnÿiÿbÿWÿNÿEÿBÿBÿAÿBÿDÿFÿGÿIÿIÿIÿJÿKÿIÿIÿHÿHÿGÿGÿKÿRÿ]ÿiÿsÿ~ÿ…ÿ‡ÿ‡ÿ‡ÿ‡ÿ‡ÿ‡ÿ‡ÿ‡ÿ…ÿƒÿ‚ÿÿ€ÿ…ÿ‡ÿ‰ÿ‰ÿŒÿÿŒÿŠÿ‰ÿˆÿ†ÿƒÿÿ|ÿvÿqÿlÿjÿhÿhÿiÿiÿhÿhÿjÿjÿiÿhÿgÿgÿgÿiÿkÿnÿqÿuÿzÿ}ÿ„ÿˆÿŽÿ”ÿ•ÿ‘ÿÿŽÿÿŒÿÿÿÿŽÿ†ÿ‚ÿ‚ÿ†ÿŠÿ…ÿ€ÿ}ÿyÿxÿvÿwÿyÿzÿyÿxÿwÿvÿsÿsÿsÿsÿsÿsÿtÿuÿuÿuÿvÿwÿyÿ|ÿ‚ÿ†ÿŠÿÿÿŽÿ’ÿ•ÿ’ÿÿÿÿŽÿ‹ÿŠÿŽÿÿÿ’ÿ‘ÿ“ÿÿŽÿŒÿŒÿŠÿˆÿ‡ÿ†ÿ„ÿƒÿ„ÿ‚ÿÿ-~-}-~.}.~.}.~.}//|01|11|22|22}22}2~2|2~1|12{11{2~2z1~1z1~2{1~1{2~2{2~2{3~3{3~3{3~3z3~3z3~3{0~0{0~0}/~/}/}/|/}/|/}/}/{/}.~.|.~.}.~.}.~.}.}.}.}.~.}..}./}/-}-.}-,~,~,~,~,~,~,},~,},~-}-~-}-~-}-~-}-~-}-~-{-~-|-}-|-}-|-}.|.}.|.}-|-}.|/}.|.}.|.}-|-},},|+})|)~){)~){)~(z'~'z&€&{'€'{'~'z&~&z%~%z%~%y%~%y%~%z%%|%%|"€$z$~$|%|%|%%|'~+|0€5y<„IpX„mlxƒkmh‚kkmYn2{$r%|%y$|%€%~%€%%~%~%€&&€(~+~28zAƒMs\†hkuˆ|]~‡|Z{ƒw]p\j;~(s?~f}y~r‡q|t‹z~j‡d€cƒb}m„tx{‰…v…Ž}w{Œzy|Šu}I‰RmŠy€‰‚~‹‰}“‰—{”‹‰wƒŒ„uŽmv_\y`‰a{^‡ayd…c{gƒt}{}{pxkutzxnt‚wbj‚-o9><€8‹6~7‹:~:‹:~8Œ6~7Œ5~5Š3~2Š6~6Š5~1Š0~/‰-~,‰,~,ˆ(~*ˆ-~,ˆ,~/ˆ14‰67‡:~<ˆ<~<ˆ;|;†<{=†9{7‡7z8‡9{9ˆ{:‰?|E‰G{GŠEzDŠEyE‹FxGŒIxOŒEwAŒKyNŠK|L‰M}L‡KK…KKƒKK‚JH€GG‚E=‚+#„%}%†&~&†'~'‡)}*‡,})‡(&‡%~$‡%{,ˆ.z/‰/z/Š1|1‰1€6„8‚8€:ƒ8}0‚& € "ƒ'~-„.,„*&…)}(…)}'†'|'†'~'†&}%…%}%†&}$…$}$„$}$„#}#ƒ#}#ƒ#~#ƒ#~#„#~#…#~#…""†!!…"~"„%~.ƒ=L|]mq€‰eŠ‰^‚„}Yu„m\h„^eT„PmIƒFtHT{bfe}k}e€ezl€syvyyx|yzv{uwt~r~q€k}cY|PH~CA‚A|D„E{G„HzH…IzI†JyJ‡JxJ‡GxG‡FwGˆLuS‰^qi‹toŽ…oŠ‰pˆ‰rˆˆrˆ†r„ƒr‚r‚Œ„s‡ŠˆwŒ†‹{„‹|ˆ‚‰|†……z‚…~yxˆrvnŒjsihph‘hoh‘hoh’hoh’hog’gog’hnj’klp’rkv”{k~‘nˆ‡w”z•~‘n‘ƒŽn„ŽlŽ„mŽ„Šqƒƒx…ˆ€ˆ|ƒ‡€u}Žzpw’wly•zly•zkv•vku”tls•tlt•tmt•tkt–tkt–skv–xi|”ƒj‡ŒpŽ‹ŒtŽ„”z”w~Žs€r‹€ˆu‹€}{Ž„x‘Š‘uŽŠsŒŠrŠŽ‡q‡Ž†q…ƒq€s‹,ÿ-ÿ.ÿ/ÿ.ÿ.ÿ.ÿ.ÿ/ÿ0ÿ0ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ2ÿ1ÿ1ÿ2ÿ1ÿ1ÿ2ÿ2ÿ1ÿ1ÿ1ÿ2ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ,ÿ,ÿ,ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ+ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ%ÿ&ÿ%ÿ%ÿ)ÿ-ÿ3ÿ3ÿ=ÿJÿYÿgÿrÿnÿgÿfÿiÿdÿ@ÿ$ÿ%ÿ%ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ(ÿ*ÿ.ÿ6ÿAÿNÿaÿtÿÿÿ|ÿyÿuÿmÿaÿJÿ4ÿ.ÿ6ÿYÿxÿsÿrÿyÿ{ÿlÿaÿaÿgÿiÿrÿÿ†ÿÿyÿwÿwÿzÿzÿdÿKÿ]ÿpÿxÿyÿ€ÿƒÿŠÿŽÿ’ÿŠÿ‚ÿvÿlÿhÿdÿdÿgÿaÿYÿ\ÿbÿiÿnÿvÿÿ…ÿ|ÿrÿlÿlÿnÿqÿsÿvÿ>ÿ1ÿ4ÿ7ÿ5ÿ4ÿ5ÿ5ÿ5ÿ6ÿ4ÿ3ÿ2ÿ2ÿ2ÿ1ÿ0ÿ2ÿ3ÿ1ÿ/ÿ.ÿ.ÿ,ÿ,ÿ,ÿ,ÿ(ÿ'ÿ)ÿ)ÿ+ÿ,ÿ/ÿ0ÿ2ÿ3ÿ8ÿ9ÿ:ÿ<ÿ<ÿ<ÿ<ÿ<ÿ;ÿ:ÿ8ÿ9ÿ<ÿ=ÿ?ÿYÿbÿHÿBÿ@ÿ>ÿBÿIÿIÿFÿFÿGÿFÿFÿHÿGÿHÿJÿBÿCÿHÿJÿKÿLÿKÿJÿJÿJÿJÿKÿKÿJÿHÿGÿGÿDÿ@ÿ5ÿ&ÿ%ÿ%ÿ&ÿ&ÿ(ÿ*ÿ*ÿ*ÿ+ÿ*ÿ'ÿ%ÿ%ÿ$ÿ%ÿ,ÿ.ÿ/ÿ/ÿ.ÿ0ÿ4ÿ8ÿ=ÿ>ÿ;ÿ=ÿ;ÿ3ÿ'ÿÿÿ ÿ"ÿ*ÿ0ÿ1ÿ2ÿ/ÿ-ÿ,ÿ+ÿ+ÿ)ÿ'ÿ'ÿ)ÿ)ÿ&ÿ%ÿ%ÿ%ÿ%ÿ&ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ"ÿ"ÿ!ÿ!ÿ"ÿ"ÿ'ÿ.ÿ<ÿKÿ^ÿnÿ}ÿˆÿ‹ÿ‹ÿ‡ÿ‚ÿÿyÿuÿpÿkÿfÿ`ÿWÿWÿXÿ]ÿfÿgÿ_ÿ[ÿSÿOÿ]ÿdÿRÿJÿkÿqÿmÿrÿvÿuÿuÿqÿmÿdÿ\ÿRÿJÿCÿBÿCÿDÿEÿGÿIÿIÿIÿIÿHÿHÿHÿHÿGÿGÿFÿGÿLÿSÿ]ÿjÿvÿÿˆÿ‹ÿ‹ÿŠÿ‹ÿŠÿˆÿ‡ÿ†ÿ„ÿ‚ÿ‚ÿÿ‚ÿ…ÿ‡ÿŠÿ‹ÿ‹ÿŒÿˆÿˆÿ‡ÿ‡ÿ„ÿ€ÿzÿtÿqÿmÿkÿiÿhÿhÿhÿhÿjÿjÿjÿjÿhÿgÿgÿgÿiÿjÿmÿqÿtÿyÿ{ÿ~ÿ…ÿÿ“ÿ•ÿ”ÿ‘ÿŽÿÿŒÿÿŽÿÿŽÿ‡ÿ‚ÿÿ…ÿˆÿˆÿ‚ÿÿ|ÿyÿwÿvÿxÿzÿyÿwÿwÿvÿuÿtÿsÿsÿsÿsÿsÿsÿtÿtÿtÿvÿvÿzÿÿ…ÿŠÿŒÿÿÿÿ•ÿ‘ÿÿÿÿÿŠÿ‰ÿ‹ÿÿÿŽÿŽÿÿÿŽÿÿ‹ÿŠÿŠÿˆÿ‡ÿ†ÿ‚ÿÿ€ÿÿƒÿ.~.}.~.}.~.}.~.}00{01{11|22|22}33}3~3|2~2|23{33{3~3z2~2z3~3{3~3{3~3{3~3{3~3{3~3{3~3|3~3|2~2|1~1|0~0|0~0|/}/|/}/|/}/|/{/|/}/|/}/}/}.}.}.}.}.}.}.~.}.~.}.~.}..}-,},,~,~,~,~,~,~,},~-}-~,},~-}-~-}-~-}-~-}-~-{-~-|-}-|-}-|-}.|.}.|.}-|-}.|/}/|/}.|.}-|-},},}+})}*~*|*~*|*~)|(~(|)~){(~'{(~(|(~'|&~%z&~&y%~%y%~%z%%{%%{%€$y%%y&~%z%~%{*~/y1€6u=„MnZ…fko„nkhƒdjc‚fjV+n!|%x$|$|$~$~%%%}&€'~((~){.ƒ6tF†Xko‰‚b„‰€[}…x\oƒfaU‚Ee?€6m9€Txu~p…l||‰|~k…c€be}i„vu…Œ†suusŽu{v‰y|zˆb~Lˆ`€pˆprˆv|ˆ„|ƒ‰{yr‹jyg‹izhŠh|m‡l{k†mxr†yyx‡zy~†‚yyƒmwa‚VxK€K~G}MK~.€0‚3~1‡1~1‰1~1‰2~3Š1~0Š.~.ˆ/~/ˆ-~/‰/~-ˆ-~-‰+~+‰,~,ˆ'~$ˆ)~)ˆ'~(ˆ,,‰03‡5~8ˆ8~:ˆ:}9†:};†:}:‡:};‡:|=ˆ?{?ŠByCŠC|B‰A}D‰F}I‰L|I‰FzHŠGyD‹FxI‹JwHŒ@x>ŒFzH‹J|LŠI~F‰G~I‡JK„JHƒFE‚C@‚9)„$}$†&|$†(|+‡+}+‡+}*‡(~%‡%|%‡&{+ˆ.z0‰0z.‰1~:„=‚@~B„@y=„:w4ƒ(z€€%‚-2€47}85~/€-~.*&~&ƒ%}&„'}'…%}%†%}%…$}$„$}#„%}#ƒ%}$ƒ%~%ƒ#~#„"~"ƒ"~"ƒ##ƒ""ƒ"~#ƒ%~*ƒ4~G}Y~jr}€†e‹Œ^Š„‡\†„ƒ]„~`{„ziwƒmplgwY‚[yW€\zf^zF@yF5v<}`vv||xw~s{s~n|to{g\zS€J}DCC|B„E{G…HzI„IzI†IyI‡IxI‡HxH‡EwHˆMuT‰_qmŒxoƒ‰oŒpŒŒr‹ˆr††r„‚r‚s„Š‡uˆ‡‹y‹…‹{Šƒˆ|ˆ„†{ƒ†y}‡ywtŠotlŒjrhhph‘ioi‘ioi’hoh’hog’gog’ink’nlr’tkx’{n€ŒŠr’~•{•q’‘m„nƒŒmƒŽq‰ƒƒu„|„~ˆ„y~Š~szxpv’ulw•xlx•wlv•ulu”tms•sms•sms•sks–uku–vky–{j€”†k‹pŒŠŽv””}tŽ€ŽsŽ‹t‰ˆx‹ŽŽzŽ†Žv‹sŽr‹‰q‡Ž†q††q„Ž‚s‚Šƒu„„.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ/ÿ.ÿ/ÿ/ÿ/ÿ/ÿ-ÿ-ÿ,ÿ,ÿ+ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ'ÿ&ÿ%ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ'ÿ+ÿ)ÿ'ÿ%ÿ&ÿ(ÿ+ÿ,ÿ/ÿ5ÿ>ÿLÿZÿfÿoÿqÿkÿgÿcÿfÿcÿAÿ"ÿ$ÿ#ÿ%ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ)ÿ)ÿ+ÿ3ÿDÿTÿlÿ…ÿˆÿ„ÿÿ{ÿtÿjÿ_ÿVÿPÿIÿDÿFÿRÿaÿbÿfÿ~ÿzÿnÿcÿaÿhÿkÿ~ÿ‰ÿ‡ÿ{ÿsÿsÿuÿvÿwÿ|ÿyÿOÿQÿgÿlÿjÿkÿoÿuÿsÿtÿkÿgÿjÿkÿlÿoÿtÿ|ÿ}ÿ€ÿ†ÿ‡ÿ„ÿ„ÿÿyÿqÿ^ÿMÿDÿ;ÿ1ÿ2ÿ2ÿ4ÿ-ÿ-ÿ-ÿ.ÿ0ÿ0ÿ1ÿ1ÿ.ÿ/ÿ/ÿ/ÿ,ÿ*ÿ+ÿ+ÿ+ÿ+ÿ-ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ(ÿ%ÿ&ÿ&ÿ'ÿ(ÿ*ÿ+ÿ.ÿ1ÿ3ÿ5ÿ7ÿ8ÿ7ÿ5ÿ6ÿ7ÿ7ÿ7ÿ9ÿ:ÿ:ÿ>ÿBÿDÿFÿHÿJÿFÿFÿFÿFÿHÿJÿLÿMÿKÿJÿIÿHÿKÿKÿJÿGÿ@ÿ@ÿDÿHÿJÿIÿDÿDÿGÿIÿJÿIÿHÿGÿEÿBÿ?ÿ9ÿ+ÿ$ÿ$ÿ%ÿ&ÿ(ÿ+ÿ,ÿ,ÿ,ÿ+ÿ)ÿ&ÿ%ÿ%ÿ%ÿ*ÿ.ÿ0ÿ0ÿ/ÿ6ÿ>ÿ@ÿDÿCÿAÿ?ÿ>ÿ3ÿ(ÿ ÿ!ÿ"ÿ'ÿ0ÿ7ÿ<ÿ<ÿ9ÿ9ÿ8ÿ5ÿ2ÿ.ÿ(ÿ(ÿ'ÿ%ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ#ÿ"ÿ#ÿ$ÿ%ÿ%ÿ%ÿ%ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ$ÿ(ÿ2ÿGÿYÿjÿ|ÿ‡ÿŒÿÿŒÿŒÿ‹ÿˆÿ„ÿ…ÿƒÿ‚ÿÿ{ÿuÿnÿ]ÿ\ÿcÿiÿeÿZÿPÿVÿOÿ?ÿKÿjÿ}ÿ~ÿvÿtÿhÿCÿ`ÿqÿhÿaÿUÿMÿFÿEÿEÿEÿGÿHÿHÿIÿJÿJÿIÿIÿGÿGÿFÿFÿEÿHÿMÿTÿ`ÿnÿxÿƒÿ‰ÿŒÿÿŒÿŒÿ‹ÿˆÿ†ÿ„ÿƒÿ‚ÿ‚ÿ„ÿ…ÿˆÿ‰ÿ‹ÿ‹ÿŠÿ‰ÿ†ÿ…ÿ†ÿƒÿ€ÿ|ÿwÿsÿmÿkÿiÿhÿhÿhÿhÿhÿiÿiÿhÿhÿhÿgÿgÿgÿiÿkÿnÿrÿuÿyÿ{ÿ‚ÿÿ”ÿ”ÿ’ÿ‘ÿÿŽÿŒÿŒÿÿÿŠÿ…ÿÿ€ÿƒÿˆÿˆÿƒÿ€ÿ}ÿzÿxÿvÿuÿwÿxÿxÿwÿvÿuÿuÿtÿsÿsÿsÿsÿsÿsÿsÿuÿuÿvÿyÿ|ÿ‚ÿ‡ÿŒÿŒÿ‹ÿÿ–ÿ“ÿÿÿŒÿŒÿŠÿˆÿ‰ÿÿÿŽÿŽÿŽÿ‘ÿÿÿ‹ÿ‹ÿ‰ÿ‡ÿ†ÿ†ÿƒÿ€ÿ‚ÿƒÿ†ÿ‡ÿ-~-}.~.{/~/{/~-{/~0{1~1{1~1{2~2y2~2{2~2{22{22{23{33{3~3z2~2z3~3z3~3z3~3z4~2z2~2|3~3|3~3{3~3{3~3}2~2}1~1}1~1}1~0}0~0}/|.z/|/z/~/}/~/}.~.}-~-}.~.}.~.}.~.}.~.}.~.~-~+~,~,~,~,~,~,~,~,~,},~,},~,},~-}-~-}-~-}-~-|-}-|-}-|-}-|-}-|-}-|-}.|.}.|.}.|.}.|.~.|.~.|.~,},~,}+~+}+}+}+}*~*|*~*|)~)})~)}(~(})~(}'~&{%~%z%%y%%{&&z&€'y*8wC7v'~&x'~&y)-w06r?…Ll[‡fgo…tgp…igg…bheZm/|$u#|#{$|&$~$&}'~(,y3ƒ=pO…cixˆ…e‹‰‹d……cw„led…`cY…WaRƒNgOXsa~d‚nz{ˆw}n†g€fh|p„‡vŒŽƒsy“kxnr{p‰r}wˆ{}WˆAW†bƒf†aƒc†e~g‡w}nŠa}l‰o}s‡{ƒˆˆ}ˆˆ†zŠ‡ŒxŒˆ‰x~‰kxWDx8‹6y4Š4}4‡4~7‡1*†(~)†/~.‡-~-‡-~-ˆ,~,ˆ*~)‡)~)‡*~*‡*~*†*~(ˆ'~'‡'~'‡%~#‡$~&‡&~'‡'*ˆ,€/ˆ1~3‡4~5‡5€3†33†33†7~8‡8~<ˆB}F‰I{J‹I|JŠJ}H‰I}L‰L|PˆR|Q‰PzO‰QzN‰KzH‹IzH‹CyAByEF{BŠB}C‰G~G†HI…H€F…@€?…9€,…%%†%}'‡(}+ˆ-.‡.-‡*~(‡%~#‡#|)ˆ-z.‰.z1‡:@‚EƒIyE…BvB„=v1‚${€#%+2€:}=‚?y=„:y8ƒ6y42|+€*)%„$~%…$~#‡#}#†%~%„#~"„$~$ƒ%~%ƒ%~%ƒ$~%ƒ&~%ƒ&~'ƒ'~()})€(~'€)~,~2|E{V~gty‡iŽ€b‚`ƒ‹_‰ƒ‰^ˆƒ…f„ƒ€m|ƒpqdƒktoƒjxiƒ[|]ƒh{[‚5zA€dxvx|€w{r€b{cp|ja{XN~FEE|G„IzI…IyJ„JzJ…IzI†GyGˆDxD‡FvG‰KtUŠaomxnƒŽ‰oŒŽŽpŒŒrŠˆr†ƒr‚ŒsƒŠ„t‡‰‹w‹†Œ|Šƒ‰|ˆ‚†|…„…z‚‡€v|ŠxvrŒlsjhqh‘hph‘gog‘ioi“hoh“hog“goi“jnl“oms“vlz’}nŠˆ’v“y”“nƒnƒŒnƒŽoƒˆu€‚€{€„€Š|……‚u~{qyxow“vmx•xlw•vlt”tmt”smr”rmr”sms•sks–uju•ujy•~jƒ’ŠnŒŽŒqˆŽw”~‘|Žu€Žu‹ˆv‡ŠyyŽˆtŒsŽŽ‹rŠŽ‰r†Ž†p…Žƒqƒ‹ƒv…„‡yˆ|-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ0ÿ/ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ(ÿ)ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ(ÿ-ÿFÿZÿLÿ0ÿ0ÿ+ÿ'ÿ)ÿ-ÿ2ÿ8ÿAÿNÿZÿeÿnÿrÿsÿlÿiÿcÿcÿfÿQÿ)ÿ#ÿ#ÿ%ÿ%ÿ$ÿ#ÿ(ÿ(ÿ.ÿ9ÿKÿ`ÿtÿƒÿ‰ÿ‰ÿŠÿŠÿƒÿyÿjÿcÿaÿaÿ`ÿ\ÿXÿVÿUÿYÿhÿuÿvÿsÿxÿrÿhÿfÿiÿoÿ€ÿ†ÿÿuÿnÿnÿnÿoÿpÿwÿyÿYÿ8ÿKÿWÿ\ÿ^ÿ]ÿZÿ[ÿnÿyÿjÿwÿyÿrÿ}ÿ…ÿ‡ÿ‡ÿ‰ÿŠÿŠÿ‰ÿ‚ÿoÿWÿ>ÿ9ÿ4ÿ4ÿ8ÿ?ÿ;ÿ6ÿ9ÿ7ÿ.ÿ'ÿ(ÿ*ÿ,ÿ-ÿ-ÿ,ÿ,ÿ+ÿ*ÿ(ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ&ÿ'ÿ&ÿ%ÿ%ÿ%ÿ#ÿ$ÿ$ÿ#ÿ$ÿ&ÿ%ÿ'ÿ)ÿ,ÿ.ÿ/ÿ1ÿ0ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ4ÿ7ÿ8ÿ>ÿCÿIÿKÿLÿLÿLÿMÿOÿNÿNÿQÿTÿWÿXÿXÿXÿVÿRÿOÿKÿJÿIÿGÿBÿAÿAÿ?ÿ=ÿ?ÿBÿCÿEÿFÿGÿFÿDÿ@ÿ<ÿ5ÿ*ÿ&ÿ&ÿ'ÿ(ÿ)ÿ*ÿ,ÿ,ÿ.ÿ-ÿ*ÿ(ÿ'ÿ&ÿ%ÿ(ÿ-ÿ.ÿ0ÿ0ÿ<ÿCÿEÿIÿIÿEÿBÿ:ÿ+ÿ!ÿ ÿ"ÿ%ÿ0ÿ7ÿ;ÿ;ÿ;ÿ<ÿ;ÿ:ÿ7ÿ4ÿ1ÿ.ÿ,ÿ+ÿ'ÿ#ÿ%ÿ%ÿ#ÿ#ÿ#ÿ$ÿ$ÿ#ÿ"ÿ"ÿ"ÿ!ÿ!ÿ!ÿ#ÿ#ÿ$ÿ$ÿ$ÿ%ÿ&ÿ'ÿ(ÿ)ÿ)ÿ+ÿ,ÿ.ÿ1ÿ8ÿFÿYÿjÿzÿˆÿŽÿŽÿŽÿŽÿŽÿŽÿŒÿŒÿ‹ÿ‡ÿ…ÿÿyÿrÿhÿ^ÿOÿGÿ>ÿ4ÿ0ÿ0ÿ3ÿ1ÿ:ÿAÿMÿmÿyÿuÿvÿvÿsÿoÿkÿcÿ[ÿQÿIÿFÿFÿHÿJÿJÿJÿJÿJÿJÿJÿJÿHÿHÿFÿFÿEÿGÿKÿUÿaÿmÿwÿ‚ÿ‰ÿŒÿÿÿŒÿŠÿ‡ÿ„ÿ‚ÿ‚ÿÿƒÿ„ÿ‡ÿŠÿ‹ÿŒÿŠÿ‰ÿ‡ÿ…ÿ„ÿ‚ÿ€ÿ€ÿ|ÿvÿpÿkÿiÿhÿhÿhÿhÿgÿgÿiÿiÿhÿhÿhÿgÿgÿiÿjÿlÿoÿsÿvÿzÿÿÿ–ÿ‘ÿ’ÿÿŽÿÿŒÿÿŒÿÿ‹ÿ†ÿ€ÿ€ÿÿ„ÿ‰ÿƒÿ€ÿ~ÿ{ÿwÿvÿuÿvÿxÿxÿwÿvÿtÿtÿtÿsÿrÿrÿrÿrÿsÿsÿsÿuÿuÿwÿ{ÿ~ÿƒÿŠÿŒÿÿŽÿ’ÿ”ÿÿÿÿÿ‹ÿˆÿˆÿ‹ÿÿÿŽÿÿÿÿÿŽÿŒÿ‹ÿ‰ÿ†ÿƒÿƒÿƒÿƒÿ…ÿ‡ÿ‡ÿ‰ÿ--}//{/€/{0€0{1~1{1~1{1~1y3~3{3~3{3~3{33{33{33{33{3~3z3~3z4~4z4~4z3~3z3~3z4~4z3~3z3~3{3~3{3~3{2~2{4~4|1~0|0~0{0~0{0}0}0}0}.~.}.~.}.~.}.~.}.~.}.~.}.~.}.~.},~,~,~,~,~,~,~,~,~,~,~,~,},~,},~,},~+}+~,},|-}-|-|-}-|-}-|-}-|-}-|-}-|-}.|.}.|.}.|.}/|/~.|.~-|-~+}+~+}+~+}+}*}*}+~+|*~*|)~)})~)}(~(|(~)|(~(z(~({'~'{(~(z((z(€)y+ƒ?vUƒPu<;v-~)v*.u2:qE„Pj[†dfm†ocr…lfh†dgbƒehgCo#{"w%{&$~%+.x;†Opi‰|h‡ˆ†d‰†‰eˆ…jx‚clX‚Xg^†ab`†_`]…ZbW‚Wke{}{yoˆz|s‡gg€f~m‚tzxyvv•pxom|o‰r~uˆz|\‰36‡KƒV†VƒX†XR„\€n‡tx‰rbŠk~‰}‰zzˆŠ†y}ŒixP@y98z86{58|8Œ9~;‹?5Š(~'‡'~'‡*~,‡*~)‡(~(‡'~%‡%~%‡$~&†%~%…%~%‡$~$†$~$†$~$†"~!†$~%†$&…(€*….,†-/†/€/†0€1†11†49‡9}?ŠE}G‹M|N‹O}OŒO~Q‹T~UŠV}W‰Y~YŠ]}Z‹Y|X‹U{R‹NzJ‹I{I‹G{D‹Bz>Š<{=‰A{B‰A}B‡CB†>8†/~*†'}'‡&}'‡)}*ˆ*}+‡,},‡*~(‡(~(‡%}'ˆ+{/‰0{3‡@€BGƒJ{J…Hv@ƒ2w'}€ &5:€w=‚9x3/|-€-~.€-‚-*ƒ(~&…$~$…#}!„!~!„#~"„!~!ƒ ~ ƒ ~!ƒ"~$ƒ$~%ƒ(~)ƒ)~*.}-},}.{0}4tE~Zmp~h}„eŒ€eŽbŒ‚`ŽƒŽ`‹ƒŠ_‡ƒ}`uƒja`…WiO‡Eq7„.{(„&#‚&{*ƒ6t:…@nN…Ymi…oqt„ttnƒfx^‚SyL€H~G}I‚I|J„JzJ†JzJ…IzI†IyHˆFxF‡EvF‰LtWŠaolxn€ŽˆoŒŽŒpŒrŠ†rƒ‚rŒs„Š†wˆ‡Š{‹ƒ‹}Š‚‡}‡„†{…†ƒy€‰u{‹vsolsjiqh‘hph’gog’hoh“hoh“hog“hoj“kmn“okt“wn{„r’ƒ”z’r‘€Žm‚Œo‹‚Œp‹ƒq‰‚ƒx€€}…ƒ‡x„ˆs}Ž|qxwot’vmx•wlv•vlt”sms”rmr”rmr”rmr•sks–tju•yj}•k‡‘‹oŒs†”z”{~t‹€ŒuŠˆuˆŒ{|ƒwŠ‘s‘rŒŠrŠŽ‰r†…rƒ‹‚tƒ„…xˆx‡{ˆn.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ2ÿIÿXÿKÿ:ÿ-ÿ*ÿ+ÿ/ÿ6ÿ>ÿGÿRÿ^ÿgÿkÿkÿfÿlÿmÿcÿ`ÿcÿiÿfÿ;ÿ"ÿ$ÿ$ÿ%ÿ)ÿ,ÿ5ÿIÿhÿ‚ÿ…ÿ„ÿ…ÿ†ÿ…ÿ|ÿoÿXÿJÿNÿPÿXÿ^ÿbÿbÿ`ÿ`ÿ\ÿZÿcÿ}ÿ~ÿ{ÿ‚ÿuÿjÿjÿhÿkÿmÿoÿuÿxÿrÿmÿnÿnÿrÿuÿwÿ^ÿ1ÿ.ÿ7ÿKÿQÿPÿQÿMÿNÿXÿ^ÿZÿRÿFÿBÿiÿ}ÿ{ÿuÿzÿvÿkÿPÿ;ÿ5ÿ6ÿ:ÿ9ÿ8ÿ7ÿ7ÿ6ÿ8ÿ;ÿ<ÿ;ÿ.ÿ&ÿ%ÿ%ÿ&ÿ'ÿ'ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ#ÿ$ÿ#ÿ#ÿ$ÿ#ÿ#ÿ"ÿ"ÿ"ÿ"ÿ"ÿ!ÿ!ÿ#ÿ#ÿ$ÿ%ÿ&ÿ)ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ0ÿ1ÿ4ÿ7ÿ;ÿ>ÿFÿJÿKÿNÿPÿQÿQÿSÿVÿWÿZÿ[ÿ\ÿYÿ[ÿ]ÿ\ÿZÿYÿUÿSÿOÿNÿNÿKÿIÿGÿDÿBÿ>ÿ>ÿ@ÿ@ÿAÿAÿ?ÿ<ÿ5ÿ/ÿ*ÿ'ÿ'ÿ&ÿ'ÿ(ÿ(ÿ*ÿ+ÿ+ÿ+ÿ*ÿ(ÿ(ÿ(ÿ'ÿ'ÿ+ÿ.ÿ.ÿ4ÿBÿBÿGÿIÿHÿFÿ?ÿ-ÿ#ÿ ÿÿ ÿ)ÿ5ÿ;ÿ=ÿ?ÿ>ÿ7ÿ2ÿ.ÿ+ÿ)ÿ&ÿ$ÿ(ÿ,ÿ,ÿ*ÿ&ÿ$ÿ#ÿ"ÿ#ÿ"ÿ"ÿ!ÿ#ÿ"ÿ!ÿ!ÿ ÿ!ÿ!ÿ#ÿ$ÿ%ÿ'ÿ*ÿ)ÿ*ÿ,ÿ,ÿ0ÿ5ÿ;ÿ9ÿEÿSÿdÿ{ÿÿxÿ|ÿ‰ÿÿŽÿÿŒÿŒÿÿÿ‹ÿŠÿ†ÿÿyÿsÿkÿfÿZÿRÿBÿ9ÿ.ÿ(ÿ(ÿ+ÿ0ÿ9ÿDÿFÿLÿOÿNÿQÿ\ÿkÿkÿiÿcÿWÿOÿJÿIÿIÿJÿLÿJÿJÿJÿJÿIÿIÿHÿGÿEÿEÿEÿFÿLÿWÿaÿnÿyÿÿˆÿŒÿŒÿŒÿ‹ÿ‰ÿ†ÿƒÿ‚ÿÿ…ÿ…ÿ‡ÿŠÿ‹ÿŒÿ‹ÿŠÿ‡ÿ†ÿ„ÿƒÿƒÿÿÿzÿuÿmÿjÿhÿhÿgÿgÿgÿgÿgÿhÿhÿhÿhÿhÿgÿhÿjÿmÿoÿrÿuÿxÿ~ÿ‹ÿ•ÿ’ÿ’ÿÿŽÿÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ…ÿ‚ÿ€ÿ€ÿÿ…ÿ…ÿ‚ÿÿ}ÿzÿxÿwÿuÿvÿxÿwÿvÿuÿtÿsÿsÿrÿrÿrÿrÿrÿrÿsÿsÿsÿuÿxÿ}ÿ„ÿ‰ÿŒÿŽÿÿÿ•ÿ“ÿŽÿŒÿ‹ÿŠÿŠÿˆÿˆÿŒÿŽÿŽÿ‘ÿ‘ÿ‘ÿ‘ÿÿÿ‹ÿŠÿ‡ÿ…ÿ„ÿ„ÿ„ÿ…ÿˆÿ‰ÿ‰ÿŠÿ//{//{00y00y22z22z11z33z33z33z33y33y33z33z44y44y55y55y3~3z3~3z4~4z4~4z4~4{4~4{33{44{4~3{2~0{1~1{0~0{0~0{0~0{.~.}.~.}.~.}.~.}.~.}-~-}-~-}-~-}-}-}-}-}+}+}+}+}+},},},}+}+},},}+}+},},},|,|,|,|,{,},{,},|,}-|-}-|-}-|-}.|.|.|.|.|/~/|/.}.€-}-€+}+},},},~,}*~*}+~+}*~*})~)|*~*|)~){(~(z)~)z'~'z)~+y*~*y,},y+,y-0v9LvTr^ˆ~i‡‰„d‡……h‚wn]€Bs5ˆ=|<‡9}4‡.|*‡)|)‡(|(ˆ({(ˆ(}*ˆ+~+ˆ*}*‡*}*‡(|'ˆ)|*‡.}4†AFH…H|E…Bz7ƒ)| ~"#‚,6=>|<‚9z4‚0{,‚*~('ƒ$€$ƒ'€(„*(…$~#ƒ#~#ƒ$~$„"~!ƒ"}"„"}!ƒ ~!ƒ$~%ƒ'~(‚+~-‚-/€3}6z<|Qkh|nRjpF‚N{‚\ŠŽeŠ‚‹c„aŒƒŠ`ˆƒ†a‡ƒ†`ƒy`t„lef„\lR„Eu7„3w2‡2r4ˆ9n?ŒGhEDbGI]KW[Z‹Yd]‰aoT„IuHK~MIK|LƒI|J„IzI‡HyFˆExD†EwF‰LtW‹`pnxo€ŽˆpŒŽŒqŒŠqˆ…r‚Œ‚rƒ‹…v‡‡ˆy‰…Œ|Œ‚Š~‰ƒˆ|†„…{ƒ‡„xŠuzŒsrlŽirggqggog‘gng‘gnh“hnh“hmh“imj“mko’slx‘xoƒ‹v”|‘~qn‹‚ŠpŠ‚‹p‹‚ˆu…y€‚}‡†ƒu‹r}Žzpy‘vnv“wlx•wlu•tmt”sms”rmq”sms”sms•rms•tiw”|i“‡mŒŽqŽ‹Žx‚–}“yŽ€ŒwŒŠw‰‰y‰{†‘v‘‰‘sŒrŒ‹rˆŽ†t…‹ƒv„†„x†zˆ|‹oŠŠk/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ4ÿ4ÿ4ÿ3ÿ1ÿ1ÿ2ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ*ÿ+ÿ+ÿ+ÿ*ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ+ÿ*ÿ-ÿ.ÿ/ÿ/ÿ/ÿ/ÿ1ÿ1ÿ1ÿ/ÿGÿAÿ1ÿ1ÿ2ÿ8ÿ?ÿHÿPÿ[ÿcÿiÿjÿeÿ[ÿQÿZÿmÿgÿbÿaÿgÿqÿTÿ$ÿ#ÿ)ÿ,ÿ2ÿHÿlÿ„ÿƒÿƒÿÿyÿiÿIÿ.ÿ)ÿ2ÿ4ÿ;ÿDÿJÿRÿXÿZÿ]ÿaÿdÿfÿhÿuÿ‚ÿ‡ÿ†ÿÿqÿiÿgÿfÿhÿlÿmÿkÿiÿiÿkÿlÿoÿqÿvÿ_ÿ,ÿ)ÿ+ÿ.ÿ4ÿFÿHÿEÿGÿFÿ>ÿ6ÿ5ÿIÿFÿ5ÿ3ÿVÿdÿ]ÿHÿ<ÿ5ÿ5ÿ6ÿ8ÿ9ÿ;ÿ;ÿ:ÿ:ÿ9ÿ7ÿ9ÿ:ÿ;ÿ=ÿ-ÿ%ÿ%ÿ#ÿ$ÿ%ÿ%ÿ$ÿ$ÿ#ÿ#ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ ÿ ÿ ÿ ÿ!ÿ!ÿ!ÿ!ÿ ÿ ÿ ÿ ÿ#ÿ$ÿ&ÿ&ÿ%ÿ&ÿ(ÿ*ÿ+ÿ*ÿ*ÿ*ÿ,ÿ.ÿ4ÿ9ÿ;ÿ<ÿCÿHÿNÿOÿRÿRÿRÿSÿSÿTÿUÿWÿ\ÿ]ÿ]ÿ^ÿbÿbÿ`ÿ^ÿYÿVÿXÿYÿXÿXÿWÿWÿUÿMÿIÿCÿ=ÿ9ÿ7ÿ8ÿ7ÿ3ÿ0ÿ,ÿ+ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ*ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ(ÿ*ÿ,ÿ/ÿ5ÿAÿFÿFÿEÿCÿ=ÿ/ÿ#ÿÿ ÿ!ÿ%ÿ-ÿ6ÿ<ÿ?ÿ;ÿ7ÿ1ÿ.ÿ+ÿ)ÿ&ÿ%ÿ#ÿ#ÿ"ÿ#ÿ$ÿ&ÿ#ÿ"ÿ$ÿ)ÿ)ÿ%ÿ%ÿ#ÿ"ÿ#ÿ#ÿ#ÿ"ÿ#ÿ%ÿ'ÿ)ÿ*ÿ,ÿ0ÿ2ÿ2ÿ/ÿ1ÿ=ÿ\ÿuÿ~ÿzÿyÿ|ÿ~ÿ€ÿ€ÿˆÿŠÿŠÿ‹ÿŒÿŠÿ‰ÿ‡ÿ„ÿÿ‚ÿ„ÿƒÿ|ÿwÿpÿiÿcÿ\ÿPÿFÿAÿ:ÿ7ÿ6ÿ8ÿ:ÿ?ÿ?ÿAÿDÿFÿIÿNÿKÿNÿHÿJÿ^ÿeÿSÿUÿZÿHÿFÿJÿJÿJÿJÿJÿGÿFÿEÿDÿDÿHÿOÿXÿbÿoÿzÿ‚ÿ‡ÿ‹ÿŒÿŠÿ‰ÿ†ÿ…ÿ‚ÿ‚ÿ…ÿ‡ÿˆÿˆÿ‹ÿŒÿŒÿ‰ÿˆÿˆÿ‡ÿ‡ÿ…ÿ„ÿÿ}ÿvÿrÿmÿiÿgÿfÿfÿfÿfÿgÿgÿgÿhÿhÿhÿhÿhÿjÿlÿpÿrÿtÿwÿ|ÿˆÿ’ÿ”ÿÿÿÿŒÿ‰ÿˆÿ‰ÿ‰ÿ‰ÿ‡ÿƒÿ€ÿ€ÿÿƒÿ†ÿ‚ÿÿ~ÿ|ÿyÿwÿuÿvÿwÿxÿwÿuÿtÿtÿsÿsÿtÿtÿrÿrÿrÿrÿrÿsÿtÿwÿ|ÿ€ÿ‡ÿÿÿŽÿÿ‘ÿ“ÿÿÿÿ‹ÿ‰ÿ‰ÿ‰ÿ‹ÿŽÿÿÿ‘ÿ‘ÿ‘ÿ‘ÿŽÿŒÿŠÿ‡ÿ…ÿ„ÿ„ÿ…ÿ‡ÿ‰ÿ‰ÿŠÿŠÿˆÿ00{11{22{33{33z22z22y22y33y43y23y44y4~4z4~4y44y44y55y55y5~5y3~3y4~4y4~4y4~4z5~5z55{44{3~3{2~2{3~2{2~2{0~0{0~0{0~.}.~.}.~.}.~.}-~-},~.}.~.}-~-},},},},}+}+}+}+}+},}+},}+}+}+},}*},},},},|,|-|-|-|-|-|-|.|.|-|-|-|-}-|-}.|.~.|.~.|.~.|..}.~-}-~-}-}.}.}-}-~,}+~,~,},~+}*~*|*~*|)~){*~)z)~+z+~-z.~.y0~0y1~2x1}1x00w11w9Bt=€5s7>pEƒNkT†]ef†jbjƒbcU‚KgH„aik…eic†dhn‚qj>|!s(*w6…Qry‡‚k}ƒ~lsWq9|&x(|+|,1{5€3„,1†3~1‰*~1‰D|CŒ8z43x2‘4y7“:z:”:{;“;};7}8:~;<~6Œ(|(ˆ'|&‡%}&†'}'†%}$†"}"†#}#…"}"…!}!…!}!…!~!… ~ … ~ „~„! „"#„$€%„(€*„**„,,„,.†3~6‡7};‰<}BŠG}LO}RŽQ~QŽQ}SŽV}VZ~ZŽ\~[`~ac~aŽ^}\Ž]}^Œ]|YŠ\|^‰W{S‰N{I‰D{<Š7{5‡6{4‡0{.ˆ.{-ˆ,{*‰)z(ˆ)|)ˆ*|*ˆ+|+ˆ+}+ˆ,|+ˆ,|-‡1}7†@DE„D~A„7}+‚!""€#}(.|4€;}>}:4|/*~)&%#ƒ"€"ƒ!€"ƒ$!„%&„)~)ƒ)~&‚$~#ƒ"}$ƒ%}%ƒ%~&ƒ&~*ƒ+~+‚,~.‚/-~.};uKXec‚tYx†wXs…|`„{e„ƒ‡gŠ€ŠeŠ‚Šb‡ƒ„aƒyb|ƒ‚e…ƒƒe|‚xcr‚idb‚\fV…OlBˆ:l>Š:h=‹@dCE^GHYLNUUŒQOVŠZUJ‡H]V†S^_†if\ƒMrI€J€J|K†IyGˆEyCˆDwH‰QtX‹dpoyo€Žˆp‰Ž‹qˆˆr…Œ‚r‚Œƒt„‰‡xˆ†Š{‹„}‚‰~ˆƒ‰{‰„‡y†‰„w‹{tvqrmhqgfpe‘epe’gng“gnh“gnh“hmi“mmm“pkq“tlvqŽ…’z‘wŽ€‹q‹‚Šs‰‚ˆrˆ‚Šu‡…y‚€}~€€„z†‡‚tr}{oy‘xmy“yly•ult•sms”rmr”rmr”rmr”qmq•sms•uiv”{i‚’Šm‘r‡y”|’~ŽvŒŠwŠˆwˆ‰z€‘y‘‡‘u‘ŠtŽŒŽsŒŽŠs‡†v†‡„yˆ|‰}ŠrŠ‹mˆ€‡k0ÿ0ÿ1ÿ1ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ4ÿ3ÿ2ÿ3ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ3ÿ2ÿ3ÿ3ÿ3ÿ3ÿ2ÿ0ÿ0ÿ0ÿ0ÿ0ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ+ÿ+ÿ+ÿ*ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ+ÿ,ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ+ÿ+ÿ+ÿ-ÿ-ÿ/ÿ0ÿ1ÿ2ÿ2ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ>ÿJÿCÿ9ÿBÿLÿTÿ]ÿfÿkÿlÿjÿ`ÿUÿEÿ8ÿHÿiÿiÿdÿbÿiÿtÿaÿ*ÿ#ÿ+ÿ:ÿZÿ|ÿ}ÿxÿwÿcÿ7ÿ%ÿ'ÿ*ÿ)ÿ,ÿ,ÿ0ÿ5ÿ9ÿ@ÿEÿMÿSÿ[ÿfÿrÿxÿ„ÿ’ÿ…ÿyÿxÿsÿeÿ`ÿ`ÿaÿiÿpÿoÿfÿgÿkÿmÿnÿoÿrÿ]ÿ,ÿ(ÿ*ÿ-ÿ1ÿ2ÿ9ÿFÿAÿ@ÿ<ÿ6ÿ*ÿ*ÿ)ÿ(ÿ'ÿ(ÿ,ÿ/ÿ0ÿ2ÿ3ÿ5ÿ9ÿ8ÿ:ÿ:ÿ9ÿ:ÿ;ÿ;ÿ9ÿ7ÿ7ÿ9ÿ<ÿ;ÿ-ÿ(ÿ*ÿ+ÿ,ÿ(ÿ)ÿ)ÿ*ÿ)ÿ(ÿ'ÿ%ÿ#ÿ$ÿ$ÿ"ÿ"ÿ"ÿ"ÿ!ÿ!ÿ ÿ ÿ ÿ ÿÿÿ ÿÿ"ÿ#ÿ$ÿ%ÿ&ÿ)ÿ)ÿ)ÿ*ÿ,ÿ,ÿ,ÿ/ÿ3ÿ6ÿ8ÿ9ÿ<ÿAÿEÿJÿOÿRÿRÿPÿPÿTÿVÿXÿ[ÿ\ÿ\ÿ_ÿ^ÿ`ÿaÿbÿ`ÿ_ÿ_ÿ_ÿ\ÿ[ÿ[ÿ[ÿZÿWÿIÿNÿHÿ<ÿ7ÿ4ÿ2ÿ2ÿ0ÿ/ÿ.ÿ-ÿ,ÿ+ÿ)ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ-ÿ,ÿ-ÿ0ÿ;ÿ@ÿDÿEÿDÿ?ÿ5ÿ&ÿ ÿ!ÿ"ÿ$ÿ)ÿ.ÿ3ÿ9ÿ;ÿ9ÿ5ÿ0ÿ+ÿ'ÿ&ÿ$ÿ#ÿ"ÿ"ÿ"ÿ"ÿ#ÿ#ÿ$ÿ%ÿ(ÿ*ÿ*ÿ*ÿ$ÿ%ÿ#ÿ#ÿ#ÿ$ÿ&ÿ(ÿ'ÿ*ÿ+ÿ+ÿ+ÿ+ÿ*ÿ+ÿ3ÿAÿPÿWÿBÿ=ÿ9ÿ<ÿ:ÿZÿlÿrÿ~ÿ†ÿŠÿŠÿ‰ÿ‰ÿˆÿƒÿ|ÿuÿsÿ{ÿ€ÿ„ÿƒÿ~ÿxÿpÿkÿgÿ_ÿUÿNÿDÿBÿ@ÿ@ÿCÿFÿKÿKÿMÿLÿLÿQÿNÿHÿ?ÿKÿKÿLÿOÿXÿgÿlÿbÿIÿIÿKÿIÿHÿFÿDÿCÿEÿIÿPÿ\ÿeÿpÿyÿ€ÿ…ÿ‡ÿ‰ÿˆÿ‡ÿ„ÿ‚ÿ‚ÿ„ÿ…ÿˆÿ‰ÿŠÿŠÿ‹ÿ‹ÿ‰ÿ‰ÿ‰ÿ‰ÿ‡ÿ†ÿƒÿ€ÿzÿuÿqÿmÿgÿfÿfÿeÿfÿfÿgÿgÿhÿfÿgÿiÿiÿiÿlÿnÿqÿrÿuÿvÿƒÿÿÿŽÿ‹ÿŠÿ‰ÿˆÿ‡ÿ„ÿ…ÿ†ÿ„ÿ„ÿÿÿÿ‚ÿ†ÿ…ÿ‚ÿÿ€ÿ~ÿ{ÿyÿxÿyÿyÿwÿtÿtÿsÿsÿrÿrÿqÿqÿqÿpÿqÿqÿsÿsÿuÿxÿ~ÿ…ÿŒÿÿ‘ÿÿ’ÿ”ÿ’ÿÿ‹ÿ‹ÿŠÿˆÿˆÿ‰ÿŽÿÿÿÿ‘ÿ‘ÿÿŒÿ‹ÿŠÿ‡ÿ†ÿ‡ÿˆÿ‡ÿ‰ÿŠÿ‹ÿŠÿˆÿ‰ÿ„ÿ0€0{1€2{2€2z3€3z22y22y33y33y33y43y34z44z4~4x4~4x55w55w55x55x55y55y55x44x44z44z55{6~6|4~4|4~4|4~2|2~2|1~0{0~0{0}0}/}.}.}.}.}.}-~-}-~-}-~-|,~,|,|,},|,}+}+}+}+}+}+},}-}+}+}+}+},},}+},},|,|-|-|,|,|,|,|,|,|-|-|-|-|-|-~.|.}.|.}-|-}-|-}-}-}-}-}-}-~-}-~-}-~,}+~+~+~+~*|*}*}+}+|*~*{,~,y/~/x1~2x3~3y4~5y6~6y5~6y5~6x7~7w88uD€LrD‚HoR„Yh`…fcm†mai[dP€>j0‚5mV…pli†cgc„jeqHk$~)r8ƒZq~„{px€sr\z.u&z&{)}(€(~+€,€03€9{?ƒFrK‡Xmi†wqy~ƒ‚uŒsz”uydŠ^€\ƒ]€cƒl}pŠh{gŒg~iˆl|o†r{Zˆ-z'ˆ*{-‡5;†9R„H‚=‚:9ƒ2€+…%'‡$&‡&{)‹-z.2y7@x=’9z8”9{:”9}9’8}66~8<~<Œ7|-‰+z-†/|,†,|,….|.†.|,†)|%‡#|$‡#}#†"}"†"~ … ~ …„„~ƒ"~#ƒ#~$„&€'„(€)ƒ)€)ƒ,,„/3†7~3ˆ1}3Š9}>ŽA}HM~PQ~OQ~SŽU~VŽX~Z]}][}[‘a}d‘c~bb}_Ž^|_‹a|b‹\|RˆT|RˆG|;‡7z5ˆ3|3†2{0‡-{.ˆ-z+ˆ+z+‰,{,‰+{+ˆ,{,ˆ.{/ˆ0}0‡3~;…@C‚C„A;„1$‚!‚!%‚'}(ƒ-|2‚6}:€9€3€-€)(€&ƒ%€$ƒ""‚$%‚#€$%())~,-{(%~$~$€$~&‚''€()€**‚)(‚+}/}8~EwTUq=ƒ5s*ƒ'}/€B|X~kv}~ˆnŠ‹i‰ˆd„„}cu„kgh„qkx„jƒ‚e~‚xas‚lce„adYˆRfK‰CeB‹FaFFXIPSQŒSOWŠTNL‡ITP…NSO…ROYˆeQV‰]d]…MyK~J„J{G…ExDˆEwJŠSs]‹fpqyoŽƒo†Ž‡q†…q„Œƒsƒ‰…w‡†Šy‹‚‹}Šƒ‰}‰„‰|‰„‰y‰‡‡w†Š‚u~Œxssorkgpgeod‘fof“gog’goh“goh“ink’mmp“rlt“vmxˆs“‚Ž|ŒuŠˆt‰‚ˆt‡‚„t…‚ƒyƒ„|€~~~ƒ‚†xƒŠs€Ž€q‘|ox’xlz”xlw”tls”sls”rlr”qmq•rmq•qmq•smt•sky”€j†‘Œn’s‘‡’{•|‘€Œw‹Šxˆ‡xŠ€Œ{Ž}ƒŽy‡‘v‘‰tŒrˆŒˆs‡‰ˆw‰€‰|‹t‹ŠmŠ‰m†„n2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ4ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ-ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ-ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ+ÿ+ÿ+ÿ*ÿ*ÿ,ÿ,ÿ+ÿ+ÿ,ÿ-ÿ/ÿ0ÿ3ÿ3ÿ6ÿ7ÿ6ÿ6ÿ8ÿ8ÿ9ÿ9ÿ8ÿ8ÿ8ÿ:ÿ;ÿ;ÿ;ÿ;ÿ>ÿJÿUÿRÿTÿ\ÿcÿjÿrÿmÿcÿWÿJÿ8ÿ.ÿ,ÿ?ÿfÿmÿeÿbÿeÿlÿfÿ;ÿ$ÿ5ÿQÿwÿ|ÿvÿqÿ^ÿ6ÿ&ÿ'ÿ)ÿ)ÿ'ÿ(ÿ)ÿ*ÿ.ÿ3ÿ8ÿ>ÿGÿWÿlÿyÿuÿ|ÿ†ÿŒÿƒÿzÿvÿhÿ^ÿ\ÿ[ÿ^ÿcÿhÿgÿgÿeÿgÿmÿpÿrÿZÿ+ÿ'ÿ*ÿ-ÿ5ÿ<ÿCÿcÿ[ÿBÿ7ÿ9ÿ3ÿ,ÿ,ÿ(ÿ%ÿ(ÿ(ÿ)ÿ*ÿ,ÿ.ÿ5ÿ<ÿ;ÿ5ÿ5ÿ7ÿ8ÿ8ÿ7ÿ8ÿ7ÿ6ÿ5ÿ7ÿ7ÿ:ÿ2ÿ+ÿ+ÿ-ÿ,ÿ+ÿ+ÿ,ÿ-ÿ.ÿ/ÿ,ÿ*ÿ)ÿ(ÿ&ÿ&ÿ$ÿ"ÿ!ÿ ÿÿÿ ÿ ÿÿÿÿÿ"ÿ#ÿ#ÿ$ÿ&ÿ'ÿ(ÿ'ÿ(ÿ(ÿ(ÿ)ÿ-ÿ1ÿ1ÿ0ÿ/ÿ0ÿ5ÿ:ÿ>ÿAÿGÿNÿRÿTÿPÿRÿSÿSÿUÿUÿWÿXÿXÿ[ÿ\ÿ]ÿaÿ_ÿ`ÿ_ÿ_ÿ[ÿ[ÿYÿYÿ[ÿVÿSÿNÿGÿ@ÿ6ÿ7ÿ4ÿ2ÿ1ÿ0ÿ-ÿ-ÿ-ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ1ÿ3ÿ3ÿ3ÿ;ÿ>ÿAÿAÿ?ÿ9ÿ/ÿ$ÿ!ÿ"ÿ(ÿ+ÿ,ÿ/ÿ1ÿ9ÿ<ÿEÿDÿ>ÿ3ÿ+ÿ'ÿ&ÿ$ÿ&ÿ&ÿ#ÿ$ÿ#ÿ&ÿ)ÿ)ÿ(ÿ*ÿ.ÿ.ÿ-ÿ,ÿ)ÿ%ÿ$ÿ%ÿ'ÿ*ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ+ÿ2ÿ6ÿ4ÿ<ÿ1ÿ*ÿ'ÿ'ÿ*ÿ5ÿCÿZÿnÿ~ÿ‰ÿŠÿ‹ÿŠÿ‡ÿƒÿ|ÿqÿjÿeÿiÿqÿxÿÿ‚ÿ‚ÿ}ÿxÿsÿoÿkÿeÿYÿRÿLÿJÿIÿOÿOÿQÿUÿYÿ\ÿ\ÿVÿQÿWÿRÿKÿNÿTÿWÿZÿUÿIÿPÿXÿKÿDÿEÿEÿEÿDÿFÿNÿTÿ]ÿgÿrÿxÿÿ‚ÿ…ÿ…ÿ„ÿƒÿƒÿ…ÿ…ÿ†ÿ‰ÿŠÿ‹ÿ‹ÿ‰ÿˆÿˆÿ‰ÿ‰ÿ‰ÿ‡ÿ‡ÿ†ÿÿ}ÿxÿsÿoÿkÿfÿfÿeÿdÿfÿfÿgÿgÿgÿhÿiÿiÿjÿlÿnÿpÿrÿuÿvÿ}ÿŽÿ“ÿŽÿ‹ÿˆÿ‡ÿ‡ÿ†ÿ†ÿ†ÿ†ÿ…ÿ„ÿ„ÿ€ÿÿ€ÿ…ÿ…ÿƒÿ€ÿÿÿ~ÿ|ÿyÿxÿzÿuÿwÿtÿrÿsÿsÿrÿrÿqÿqÿqÿqÿqÿqÿrÿsÿtÿ{ÿÿˆÿÿ‘ÿÿÿ“ÿ“ÿÿ‹ÿ‹ÿŠÿˆÿ‡ÿ‰ÿÿÿ‘ÿÿ‘ÿ‘ÿ‘ÿÿ‹ÿ‹ÿ‰ÿˆÿˆÿŠÿŠÿŠÿ‹ÿŒÿ‹ÿ‰ÿˆÿ†ÿ‚ÿ3€3z3€3z3€3y3€3y13x44x33y33y22y44y45y55y4~4x3~3w45y55y55y55y55y66y77y55y55z55z55z6~6{6~6|5~5|4~4|3~2|2~2}1~1}1}1}/}.}.}.}-}-}-~-},~,},~,~-~-~-~-~,~,~,},~,},~+}+}+}+}+}+~+}+~,},}-}-},|,|-|-|-|-|-|-|-|-|-|-|-|-|-|-~.|.}.|.}-|-~-|-~.}.~-}-~.}.~.}.~-}-~,}+~,~,~,~,|-}-}-}.|1~2{2~2y5~5z9~;z:~:z;~;z;~ŠA}KŒQ~QR~TŽS~RŽS~SS~T’T~R’U}X“Z~\‘Y}[^}]Ž]}dc|`ŠZ}RˆQ}Q…M}I…8}8„7|2…1{1†/z0ˆ-z-‰-z-‰-z-ˆ-{/ˆ/{3ˆ4}5‡4~8„=>ƒ>‚<ƒ6‚+ƒ#€#„$~&„)|/ƒ1}9€M~`ylprk‚_rR‚Aw3ƒ+|*‚&€""€"€&|-6t4€6p8€8o8€3r0€-w,*{+€+{*€){)€)|)€&|%*+€'%%€'€''(ƒ2}C\|pw|‹o‹Œg‹‡c„ƒ|dpƒch\„`ph„roy„€j„‚‚d}‚zbt„p`k‡h_bˆbZcŠaS`Œ_Kb‹_G^‰]F[…bFm‡h@Y‡[K^‡]JY‡WLP‰@W8ˆOeV…erZƒG~F|F†GwL‰Ut`‹iqsŒzp€qƒ„r„Œ‚sƒ‰„u„††z‰ƒŠ|Š‚‹~‰‡}‡ƒˆ{ˆ…ˆxˆ‡‡vƒ‹s}wrrnpjgqf‘eod‘fnf“fng’hmh“imi“jmm’okr“slt’yoƒ‰v|Œ~ˆt‡‚†s‡†s††t†…y„ƒ}€€~„‚„xŠq€~o}‘|mz’ylz”vlv”slq”rlr”rlr”rmq•pmp•qmq•rms•uj{”„j‰‘Žo‹u‚”|‘y€ŒvŒ‹yˆ‡yˆ€Ž}}…w‹‘tŽtŒ‹Šsˆˆ‡v‰‚‰{‹xŒ~Œp‹€Šm‡‚†pƒs3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ5ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ7ÿ7ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ3ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ/ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ.ÿ.ÿ/ÿ2ÿ4ÿ7ÿ5ÿ8ÿ;ÿ>ÿ?ÿ=ÿ>ÿ>ÿ>ÿ@ÿ@ÿ@ÿAÿAÿAÿBÿBÿCÿDÿFÿJÿLÿSÿVÿYÿZÿjÿvÿnÿ_ÿOÿDÿ5ÿ/ÿ,ÿ-ÿ9ÿZÿtÿiÿeÿdÿjÿlÿZÿ.ÿ2ÿYÿwÿvÿpÿiÿOÿ*ÿ'ÿ)ÿ'ÿ(ÿ&ÿ)ÿ*ÿ)ÿ+ÿ/ÿ5ÿDÿ`ÿwÿsÿjÿnÿ~ÿƒÿzÿtÿoÿqÿfÿ^ÿ\ÿ\ÿ`ÿbÿfÿfÿcÿgÿmÿlÿmÿ\ÿ.ÿ&ÿ&ÿ+ÿ+ÿ0ÿMÿgÿYÿRÿQÿ9ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ-ÿ,ÿ,ÿ-ÿ.ÿ-ÿ-ÿ-ÿ/ÿ/ÿ1ÿ4ÿ5ÿ8ÿ7ÿ5ÿ5ÿ6ÿ8ÿ7ÿ5ÿ@ÿPÿ+ÿ*ÿ-ÿ,ÿ,ÿ+ÿ+ÿ+ÿ*ÿ+ÿ+ÿ+ÿ,ÿ*ÿ(ÿ'ÿ&ÿ$ÿ"ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"ÿ"ÿ"ÿ#ÿ#ÿ$ÿ$ÿ%ÿ%ÿ'ÿ'ÿ(ÿ(ÿ*ÿ/ÿ9ÿ8ÿ<ÿFÿLÿRÿSÿTÿPÿTÿXÿUÿTÿQÿOÿQÿOÿQÿSÿUÿVÿWÿ^ÿaÿbÿ_ÿbÿfÿcÿ]ÿUÿTÿRÿQÿMÿ:ÿ5ÿ;ÿ9ÿ4ÿ0ÿ0ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ/ÿ0ÿ2ÿ5ÿ6ÿ8ÿ4ÿ4ÿ8ÿ9ÿ;ÿ6ÿ0ÿ'ÿ#ÿ#ÿ%ÿ%ÿ+ÿ6ÿFÿ[ÿsÿ€ÿ„ÿ…ÿ†ÿƒÿzÿmÿ`ÿTÿBÿ.ÿ%ÿ$ÿ,ÿ9ÿAÿFÿGÿFÿEÿAÿCÿBÿ<ÿ4ÿ4ÿ4ÿ2ÿ2ÿ2ÿ1ÿ0ÿ0ÿ0ÿ/ÿ-ÿ*ÿ'ÿ$ÿ$ÿ$ÿ$ÿ$ÿ&ÿ(ÿ4ÿHÿ_ÿuÿ„ÿ‹ÿŒÿŒÿ‹ÿŠÿƒÿ{ÿqÿaÿWÿYÿ`ÿjÿsÿyÿ‚ÿ‚ÿƒÿ€ÿ|ÿwÿqÿjÿgÿhÿdÿdÿdÿaÿ`ÿaÿfÿkÿlÿlÿcÿ[ÿWÿXÿWÿ`ÿeÿfÿaÿaÿ[ÿLÿUÿkÿ\ÿJÿEÿFÿKÿOÿXÿbÿiÿsÿzÿ|ÿ€ÿ‚ÿ„ÿ„ÿ„ÿ…ÿ†ÿ†ÿˆÿ‰ÿ‰ÿ‰ÿ‰ÿ‰ÿ‡ÿ‡ÿˆÿ‡ÿ†ÿ„ÿ„ÿÿ}ÿ{ÿwÿrÿnÿjÿfÿeÿeÿdÿfÿfÿfÿfÿfÿfÿiÿiÿjÿmÿoÿrÿsÿuÿ|ÿˆÿ“ÿ‘ÿ‹ÿˆÿ‡ÿ†ÿ…ÿ…ÿ…ÿ…ÿ„ÿƒÿ„ÿƒÿ€ÿ€ÿÿ…ÿ„ÿÿÿ€ÿÿ~ÿ|ÿzÿyÿxÿvÿvÿsÿqÿrÿrÿrÿrÿrÿqÿpÿpÿqÿqÿrÿsÿvÿ}ÿ†ÿ‹ÿÿÿÿÿ•ÿÿÿ‹ÿŒÿŠÿ‡ÿ†ÿŠÿÿÿÿÿ‘ÿ‘ÿÿŽÿŠÿ‡ÿ‡ÿ‡ÿŠÿ‹ÿŠÿ‹ÿŒÿ‰ÿ‰ÿ‡ÿ†ÿ„ÿ‚ÿ22y22y22y33y44w46w55w44w33x44x44w44y55x55x55y44y66z66z66z66z55x66y66z66z66{66{7~6|6}5|4~4|4~4|21}11}1}1}0}/}.~.}-~-}-~-~,~,~,~,}-~-}-~-},~,}+~+|,~,|+},|,},|-},{+},{-}-}-}-},|,|+|,|.|.|-|-|-|-|-|-|-|-}.|.}.|.~-|-~-}--}-.}..}./~/.-}-},|*}+,~,}-~-|/~0{1~2{5~7{8~9{<}>|?}A|B}A|C}D|C}DzC}DxD~DxE~EwE~GvH€KsQ‚Yn_„^j\„cgp„kfZ€Lj?3q/.t-€0uA„irp†lnd…fkmƒsmYƒ-nGƒlnw„uimYh5{&r({)}'{)‚)|*‚,~-1‚;|S‡qwx†lxj}rƒ|s|tvo—myq“m`…]‚[}_a‚c€fˆg€i‰j~l‰m|_‰.z%‰*},‰.~1ˆP€e…]€W„U~Vƒ:}+ƒ'}(…((†(~)„*}-‡.}/‰-{,Š,z-.{0’4{4‘5|31}4Ž4€33ƒK†x‚R}!//|.ƒ-z-…-z,…+|+†*|+†+}*‡(}'‡&~%‡"~‡ ~ †!~†~„~ƒ~‚~‚}ƒ !ƒ!~#„$~$„#~"„&~&…&~(‡,~3ˆ7~=ŠEJŒOSW~SŽS}PR}O‘Q}N’Q}O‘P}R’T~R“Z~`“e}f‘g|hd|dŠZ|N‡>~Q†NY„@~0;|=ƒ7{2‡2{0ˆ0y0ˆ0z1ˆ0z0ˆ1{6‡8{8†6|2†4~5†2~*…&~#…%~&‡(4„J^zp‚p‰‹j‹ŒgƒŽgŽ„‹g„„zjkƒTp1ƒ)u:‚EuM€IqA?nE€FjF€EjA;n:€8p:€:s:€ÿ?ÿAÿBÿDÿEÿFÿGÿGÿGÿGÿGÿGÿHÿHÿIÿIÿIÿKÿJÿNÿUÿ]ÿdÿgÿgÿaÿdÿgÿ[ÿLÿ?ÿ2ÿ/ÿ.ÿ-ÿ0ÿ4ÿTÿpÿkÿiÿdÿhÿrÿuÿJÿ<ÿaÿzÿwÿpÿcÿ=ÿ%ÿ'ÿ&ÿ'ÿ(ÿ)ÿ*ÿ+ÿ-ÿ0ÿAÿ`ÿxÿvÿkÿlÿxÿ~ÿxÿsÿmÿlÿoÿvÿdÿ]ÿ\ÿ[ÿaÿcÿeÿfÿfÿiÿkÿlÿ]ÿ/ÿ&ÿ*ÿ,ÿ-ÿ3ÿSÿgÿ[ÿYÿ[ÿXÿNÿ9ÿ*ÿ'ÿ&ÿ&ÿ'ÿ)ÿ(ÿ-ÿ0ÿ0ÿ/ÿ.ÿ+ÿ+ÿ-ÿ0ÿ1ÿ2ÿ3ÿ1ÿ1ÿ6ÿ9ÿ9ÿIÿeÿ€ÿ‡ÿ6ÿ&ÿ.ÿ0ÿ0ÿ/ÿ1ÿ1ÿ.ÿ.ÿ-ÿ,ÿ+ÿ+ÿ(ÿ'ÿ&ÿ%ÿ$ÿ$ÿ%ÿ%ÿ"ÿ!ÿ"ÿÿÿÿÿÿÿÿÿÿÿ ÿ!ÿ ÿ!ÿ!ÿ ÿ!ÿ#ÿ"ÿ"ÿ'ÿ)ÿ-ÿ/ÿ7ÿ?ÿDÿFÿIÿNÿQÿLÿNÿPÿRÿPÿMÿJÿLÿNÿQÿRÿRÿSÿWÿ]ÿeÿfÿfÿhÿgÿdÿZÿUÿVÿKÿIÿZÿNÿ6ÿ7ÿ=ÿ:ÿ6ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ2ÿ7ÿ8ÿ8ÿ6ÿ4ÿ2ÿ3ÿ0ÿ(ÿ%ÿ%ÿ'ÿ,ÿCÿaÿxÿ‚ÿ‰ÿŽÿŽÿ‹ÿ‰ÿ‹ÿÿÿŽÿÿŽÿÿ…ÿwÿOÿ0ÿ:ÿBÿMÿBÿ>ÿ<ÿ@ÿLÿJÿIÿDÿ?ÿ9ÿ6ÿ7ÿ:ÿ;ÿ<ÿ<ÿ9ÿ9ÿ9ÿ8ÿ-ÿ$ÿ ÿÿ!ÿ$ÿ$ÿ$ÿ*ÿ?ÿSÿiÿ}ÿ‰ÿŽÿŠÿŠÿŒÿŒÿŠÿÿtÿdÿSÿKÿPÿZÿeÿoÿvÿ|ÿ„ÿ†ÿ†ÿ…ÿƒÿ€ÿ€ÿ{ÿzÿwÿuÿqÿsÿxÿzÿyÿxÿtÿuÿfÿaÿdÿkÿjÿpÿqÿoÿnÿgÿ_ÿTÿWÿgÿbÿfÿTÿLÿTÿ\ÿeÿjÿsÿvÿzÿ}ÿÿ€ÿ‚ÿ†ÿ‡ÿˆÿˆÿ‰ÿŠÿˆÿ‡ÿ‡ÿ‡ÿ…ÿ†ÿ†ÿ…ÿ„ÿÿÿ~ÿ|ÿxÿtÿoÿmÿiÿfÿeÿeÿeÿfÿeÿgÿgÿfÿgÿiÿjÿlÿnÿqÿqÿtÿwÿ„ÿ’ÿ‘ÿÿŒÿˆÿ‡ÿ†ÿ‡ÿˆÿ‡ÿ†ÿ„ÿ„ÿ…ÿÿ€ÿ‚ÿ†ÿˆÿÿÿ€ÿÿ€ÿ~ÿ|ÿzÿzÿyÿvÿtÿsÿqÿqÿqÿqÿqÿqÿqÿoÿoÿpÿpÿqÿrÿyÿ€ÿ‰ÿŽÿÿÿ’ÿ•ÿ’ÿŽÿÿ‹ÿŒÿ‰ÿˆÿˆÿŒÿ‘ÿÿÿ‘ÿ‘ÿÿÿŠÿˆÿˆÿ‰ÿŠÿ‹ÿ‹ÿ‹ÿ‹ÿŠÿ†ÿ„ÿƒÿƒÿƒÿƒÿ33x33x44w44w44w45w44w55w55x44x44w55y55x55x55y55y44y45y66y66y45x56y77z66z56z66z6~6z6}6z5~4|4~4|3}3|2}2|1}1}0}/}.~.}-~-}-~-~-~-~-~-}-~-}-~-},~,},~,|,~,|,},|,},|,},{,},{-}-}-}-}-|-|,|,|-|-|,|,|-|-|-|-|-|-}-|-}-|-~-|-~-}--}-.}..}./~///}0~1|0~.|.~.|0~1{3~5{5~9{:~;{?~A{C}D|D}E|G}I|I}J|J}LzK}KxL~LxL~KwL~NuO€SrY‚`ng„mjp„med„\eU€Kh?6o2/t/€/u/ƒ;t_…pph†ekd…jjt…kj@…Ujw†ydsƒicF}(m'z'z){)€(|)*-€3ƒFziˆ{vr†kvn|€ztqmqk•dua’v|kˆ\]}\`|c€d‚c€c†h~jˆj|]‰0z'‰*{+‰-}3ˆX~h‡Z~Y…Y|V…R{J‡>z,‰&|&‡'}'†'+†2~3†/|.ˆ.{,‹,{-Ž.|./}2Ž4>‹FƒPƒa‡xrŠˆ”cw‚n+{1}3z2‚3z4„3|2…1|/…-|+†(|(†%}#…"}#…#~#†#~"† ~„ ~ƒ~‚~‚}ƒƒ~ƒ ~ ƒ!~!ƒ ~!„#~$†&~)‡-~3ˆ:@‰DEŠFH‹H~GŽJ}LN~ON~N‘M~M‘P~O’Q~U”\}`“f|ei|jŽg|d‹[|R‰M}J†Z~e|XFv?~€AhA>g>‚?i<‚2s(‚$|#€%€%€&'.B}Xm}u~kŒ€‹g‹‚‹c‰ƒƒay„ebN†BjH†Rr_„iqp‚xj€‚†cˆ‚ˆ]…ƒ…Z€‚~Uy‚vOu‚tIu„qBt„wf†cG]ƒZM`‡^NbˆfRb‰[Waˆ\PZ‰=MAˆQcd‰]q^‚[}_ye†mur‰vsz‹zq|‹q‚‰…v††‡{‰‚Š‰€ˆ~‡…|…ƒ„y„†‚w‚‰s‹r}zpvsqnlqi‘epe’epe’fnf’fnf’fmg”ikj”nko”sjs’tm{‹Žt”~”|uŒŠsˆˆt‰€‰vˆ‡x…€„}…€€~€‚~‡}‡‚‚w‚po~‘|ny“xly”ums”rmp”qmq”qmp”plp”olo”plp•pku•{k‚’‰kŽrˆ‘x”}‘wŒxˆ†y‡ˆ{Œ€‘z‡‘u‘‹t‹‹ˆv‰„‰yŠ{‹{ŒqŒ}Œm‰ˆn…„t‚€‚y„~†|3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ5ÿ6ÿ6ÿ6ÿ6ÿ4ÿ5ÿ5ÿ6ÿ7ÿ7ÿ6ÿ6ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ4ÿ4ÿ4ÿ3ÿ3ÿ2ÿ2ÿ1ÿ1ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ0ÿ3ÿ3ÿ5ÿ4ÿ0ÿ0ÿ0ÿ2ÿ3ÿ3ÿ7ÿ8ÿ<ÿ>ÿ?ÿCÿEÿFÿEÿHÿJÿMÿNÿNÿNÿOÿPÿPÿOÿPÿPÿPÿOÿQÿRÿRÿUÿZÿaÿhÿnÿoÿsÿpÿbÿRÿCÿ>ÿ7ÿ5ÿ4ÿ3ÿ/ÿ/ÿ1ÿCÿjÿpÿfÿeÿeÿlÿyÿ^ÿNÿtÿ|ÿvÿoÿPÿ+ÿ&ÿ(ÿ*ÿ*ÿ(ÿ(ÿ*ÿ-ÿ2ÿJÿmÿyÿoÿlÿrÿÿxÿnÿlÿgÿbÿdÿoÿpÿbÿ`ÿ^ÿ^ÿaÿcÿaÿbÿeÿiÿhÿ\ÿ0ÿ'ÿ+ÿ,ÿ0ÿ6ÿ_ÿeÿ[ÿ]ÿ]ÿ[ÿPÿOÿLÿ@ÿ.ÿ(ÿ'ÿ&ÿ'ÿ(ÿ.ÿ2ÿ/ÿ-ÿ,ÿ,ÿ*ÿ+ÿ,ÿ,ÿ-ÿ7ÿBÿNÿYÿiÿxÿ„ÿŒÿ–ÿ˜ÿ[ÿÿ-ÿ4ÿ5ÿ6ÿ6ÿ3ÿ2ÿ1ÿ/ÿ-ÿ+ÿ*ÿ)ÿ(ÿ&ÿ#ÿ"ÿ"ÿ"ÿ!ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ ÿ ÿ!ÿ#ÿ$ÿ$ÿ&ÿ(ÿ-ÿ2ÿ6ÿ9ÿ>ÿAÿBÿDÿEÿEÿIÿNÿKÿOÿPÿPÿOÿPÿMÿOÿSÿVÿ\ÿlÿ_ÿaÿoÿlÿjÿcÿ[ÿQÿLÿYÿeÿ_ÿUÿOÿIÿAÿ9ÿ8ÿ8ÿ5ÿ2ÿ2ÿ1ÿ2ÿ5ÿ6ÿ7ÿ7ÿ9ÿ8ÿ6ÿ6ÿ2ÿ,ÿ(ÿ9ÿbÿ‚ÿ‡ÿŒÿÿ“ÿ—ÿ—ÿ”ÿ“ÿ•ÿ–ÿ’ÿÿ‹ÿŠÿÿ’ÿŽÿ{ÿRÿ<ÿFÿIÿJÿJÿKÿNÿSÿVÿXÿXÿ\ÿaÿaÿ^ÿUÿUÿQÿOÿNÿMÿGÿAÿ4ÿ&ÿ#ÿ#ÿ%ÿ&ÿ(ÿ(ÿ.ÿAÿWÿmÿÿÿÿŒÿ‹ÿ‹ÿ‹ÿˆÿÿuÿdÿIÿ9ÿ=ÿJÿWÿ`ÿkÿvÿ}ÿ„ÿ‡ÿ‰ÿ‰ÿˆÿ†ÿ‚ÿ}ÿ~ÿ~ÿtÿjÿaÿdÿaÿ^ÿ`ÿhÿ]ÿaÿYÿUÿWÿaÿfÿaÿWÿZÿaÿ…ÿÿAÿCÿWÿ_ÿ[ÿ`ÿdÿiÿoÿsÿvÿxÿyÿ|ÿÿƒÿ…ÿˆÿˆÿŠÿ‰ÿ‡ÿ†ÿ„ÿƒÿƒÿ„ÿ‚ÿÿÿÿÿ~ÿ|ÿyÿuÿrÿmÿkÿhÿeÿeÿeÿeÿfÿfÿeÿeÿfÿgÿiÿjÿnÿoÿsÿsÿtÿ|ÿÿ”ÿ”ÿ‘ÿÿ‰ÿˆÿˆÿ‰ÿ‰ÿˆÿ†ÿ…ÿ„ÿƒÿ€ÿ€ÿ‚ÿ‡ÿ‡ÿÿÿÿÿÿ~ÿ|ÿyÿyÿwÿuÿsÿrÿpÿpÿpÿqÿqÿpÿpÿoÿoÿpÿpÿqÿvÿ|ÿ„ÿ‹ÿÿÿÿ“ÿ”ÿÿÿŒÿ‹ÿ‰ÿ‡ÿ‡ÿ‰ÿŒÿÿÿÿÿÿÿŒÿˆÿ‰ÿŠÿŠÿ‹ÿŠÿ‰ÿ‰ÿˆÿ‡ÿ…ÿ„ÿ„ÿ„ÿ…ÿ‰ÿ33w44w44v44v5€5w4€4w4€4w4€4w55w44w44w44w55w44w44y55y55x55x66y66y66z77z66z66z66z66y76z6~4z5~5{4~4{3~3|3~3|1}1}0}0}.}.}-}-}-~-},~,}-~-}-~,}-~-}-~-},~,},|,{,|,{,|,{,|,{,|,{,|,|-|-|.|.},|,},|-{,|,{-|-}-|-}.|.}.|.}.|.}-|-}-|--}-.~./~/..~0€1|45z7~7z6~6y7~7y9~9y>~@|A}B|F}G|K}M|M}O{O}Q{Q|S{U}U{T}T{T}TyS}SxS~SvUVs\‚cpi…qkq„qdo‚ec[PeC€Š@BC}EŽM}SR~Q‘NM‘IH‘MQ“U~[“e~j‘o|pl~mŽj}`S~NˆK~`€cUsP€PrN€Iv?€8|7~4ƒ3|3‡3|4†4|7‡8}:†<}<ˆ<|9…4~9‚Z~y‹Šqo’—p–€“p•šlšƒ—j’ƒŒiŠ‚ŠjŒƒ‘j…„^m>ItI}JvH{LvMzRnTZi^ƒ_eb†cbd‡]`Z†^_]†Z`X†TfE‡0s%„#}#%ƒ%€&„&€-„A~S‚i|}xŒ}Žm~ŒhŠ€Še„€|bp‚bcE†/m1†>zJ„Xzelrwk‡‚‹d‚Œ`‰ƒ‡]†ƒ…Y‚ƒ€Vx…jZa†Z\Y…^Z\„WYWWY\ƒ^T^…_Yj…e_XiHƒ‡ˆ,}ŠL@ZŠa_^‡ZtYf„oxr‹stvŠzr|ˆr‚……z‡€‰}‰‡„€…€…ƒƒ{‚„ƒy‡~v~Š~q~Ž}pzxptqqkkqgepd’epe’eod‘ene’gnh”ill”nlp”sks’sn‚ˆ“w“y“p‹p‡‡r‡ˆu†€…z„€ƒ|€€€}ƒ~‰~†„w€o€o~‘|mz’xmv“umq”qmp”pmp”omo”omo“omo•nlo•pjw•}i…’ms…–z“|ŒwŒ€‹x‰€ˆyˆ‹|~‚yˆ‘tŽŠŒuˆˆˆx‰}ŠzŠtŠ~‰o‰‡o‡‚†n…„u„~„}†{4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ7ÿ7ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ7ÿ6ÿ3ÿ4ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ1ÿ1ÿ0ÿ0ÿ/ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ,ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ/ÿ/ÿ1ÿ3ÿ4ÿ4ÿ6ÿ8ÿ;ÿ<ÿ<ÿ<ÿ<ÿ<ÿ;ÿ;ÿ=ÿ@ÿDÿDÿHÿJÿLÿNÿOÿQÿRÿSÿUÿWÿXÿYÿXÿYÿXÿXÿXÿXÿXÿXÿZÿ[ÿ`ÿfÿkÿpÿsÿqÿoÿeÿZÿQÿPÿKÿAÿ8ÿ4ÿ3ÿ3ÿ2ÿ0ÿ8ÿ^ÿqÿgÿdÿhÿnÿvÿuÿxÿƒÿ€ÿxÿaÿ3ÿ(ÿ*ÿ(ÿ*ÿ*ÿ+ÿ,ÿ.ÿ<ÿZÿxÿsÿmÿlÿsÿ~ÿvÿjÿiÿiÿiÿiÿjÿpÿmÿaÿ]ÿ\ÿ^ÿcÿaÿbÿfÿhÿfÿYÿ0ÿ+ÿ.ÿ3ÿ4ÿ?ÿfÿdÿ^ÿ]ÿ^ÿZÿMÿJÿPÿTÿSÿCÿ,ÿ'ÿ&ÿ$ÿ$ÿ&ÿ*ÿ.ÿ*ÿ-ÿ3ÿ1ÿ7ÿAÿKÿWÿhÿpÿ|ÿ‚ÿ‹ÿÿ‡ÿ”ÿšÿÿxÿÿ(ÿ/ÿ2ÿ4ÿ4ÿ3ÿ1ÿ0ÿ/ÿ,ÿ-ÿ-ÿ+ÿ+ÿ)ÿ%ÿ#ÿ!ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ ÿ"ÿ!ÿ#ÿ&ÿ(ÿ*ÿ,ÿ,ÿ-ÿ2ÿ7ÿ9ÿ<ÿ@ÿBÿGÿMÿPÿQÿIÿBÿHÿKÿNÿSÿYÿ_ÿ_ÿeÿjÿgÿlÿnÿiÿ\ÿTÿVÿMÿRÿdÿXÿSÿOÿPÿPÿMÿHÿAÿ8ÿ0ÿ/ÿ1ÿ3ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ<ÿ8ÿ=ÿYÿzÿŠÿ‹ÿ‹ÿŒÿ‘ÿ‘ÿ“ÿ“ÿ‘ÿÿ•ÿ•ÿ—ÿ’ÿÿˆÿˆÿÿÿ‰ÿlÿFÿFÿIÿIÿLÿMÿOÿQÿSÿVÿYÿ\ÿ^ÿ_ÿ_ÿ\ÿ\ÿ[ÿ\ÿ[ÿXÿQÿBÿ,ÿ$ÿ"ÿ#ÿ#ÿ$ÿ$ÿ&ÿ)ÿ7ÿMÿcÿxÿ†ÿ‹ÿ‹ÿ‹ÿ‰ÿˆÿ‚ÿyÿnÿXÿ=ÿ,ÿ)ÿ/ÿ@ÿLÿYÿeÿtÿ~ÿ„ÿ‹ÿŽÿÿŒÿ‹ÿŠÿ‡ÿ‚ÿ€ÿyÿvÿtÿdÿeÿfÿeÿZÿZÿ^ÿjÿjÿgÿ_ÿLÿHÿNÿkÿ…ÿˆÿŽÿtÿSÿPÿLÿQÿYÿgÿnÿsÿuÿuÿwÿ|ÿ€ÿ‚ÿ„ÿ†ÿˆÿˆÿ†ÿ…ÿ„ÿ…ÿ„ÿ€ÿÿ€ÿ~ÿ|ÿ|ÿ}ÿ}ÿ{ÿxÿtÿqÿkÿiÿeÿeÿdÿdÿdÿdÿdÿfÿfÿfÿgÿiÿlÿnÿpÿqÿtÿuÿ‡ÿ•ÿ”ÿ‘ÿŽÿ‹ÿ‰ÿ‡ÿ‡ÿ†ÿ†ÿ…ÿ…ÿ„ÿ„ÿ‚ÿÿ€ÿ‚ÿ‡ÿ…ÿÿÿ€ÿ€ÿÿ~ÿzÿyÿwÿtÿsÿqÿqÿpÿoÿoÿoÿoÿoÿoÿoÿoÿnÿnÿrÿxÿÿŠÿÿÿÿ‘ÿ–ÿ“ÿŽÿÿÿŒÿˆÿ‡ÿ‡ÿ‹ÿŽÿŽÿÿÿŽÿŒÿŠÿ‡ÿˆÿ‰ÿŠÿŠÿŠÿ‰ÿ‰ÿ‡ÿ…ÿ„ÿ„ÿ„ÿ…ÿ…ÿ‡ÿŽÿ44u44u44v44v5€5w4€4w4€4w4€4w44w34w44w44w44w55w44w55w55x55x66y66y66z77z77z77z77z77x8AyC~;y6~6{4~4{4~4|3~2|3}1}0}0}0}/}.}.}.~.},~,}-~-|-~,|-~-|-~-|-~-|-|-}-|-}-|-},|,|-|-|-|-{.|.{-|-{-|-{-|-}-|-}.|.}.|.}.|.}-|-}-|-€-|-€.|..}.-~-/~/1€3~55z8;w=AvABuB@u=~=x@~C{F}H{K}N{P}R|T}U{X}Y{[|[{[}\{]}\{\}Z{[}[z\~\v^_sb€honƒslv„tfq‚gc]TbL€QhU€PnB5r21w1/v>„etn„hpf†hkq†yj~†gyci}>e%x't*y(})})~*0xB†dvyˆosl‡kvqƒ€ukŒh~g’i~i“g~fŽlc‡_€]^€b}b€dg€f†g~V‡3}1‡1}1‰4}G‰f{c‰]z_‰_xY‹FvFNtS‘TuR‘Cy+Œ&|$ˆ#}#‚'6~=6}9?{E‚O|X…`zjˆxu‰…jŠŠaŒ“T–‹›N™…U[|*y,{.€1z0„/z.„.{.ƒ-{,„+{+…*{'…${"…}ƒ~ƒ~~~€}}}‚}‚~‚~ƒ~ƒ"~"ƒ%~'…*+†+~+‡,}/‰3}7:|@ŽJ|KQ}Q‘N~J‘O~L‘N~PS}W^~^^}ac}aa|ZŽV}YŠS}Uƒc^xQ€OqN€NsN€LsHEy>~72|.+|/‚/|0…2|3†4}5„O~s~‡ŠwŒ‚ŠuŠs‘tƒ€vp‚sw€Žu’‚Œp‡‚‡nŠ‚k‹„rlHAsHIwJ|LwNzQvS|SrS~VnW€YjW‚Wf\ƒ^f^ƒ\fX„UkE†,v!‚! €"‚$%ƒ$'ƒ4|Jƒb{w}„}rŠ~Šl‰€†f~ubi„Rf3…(s5…6z>‚DxO€^so€|k‚‰e‚ŽdŒ‚Œ`Šƒ…^‚ƒ€]z„t^n…ecZ„WfVƒXe^d_j€d^e€d_R|LY[|{=‡Œ‰,Ž—1}AoŒfT[ˆVial|o{n„wz}†{„ƒ†‡‡€††…„~ƒ„z‚…€w‰{t|Œ{p{Ž{pyxptoqkhqedpd’cpc’dod’ene”eng•hll•ojq”rit‘yn„–w”x‘€qŠ‚‰q‰‚‰r‡…u„€ƒz„ƒ|€}€‚~ˆ{„„‚t‚€o€n|‘ylv’vmu“smq”pmq”pmp”omo”nmn“nmn•llo•tj{•„i‹n‘Œu“ƒ–z’yŽŒv‹€‰yˆ†z‡~Œ~Ž}ƒxˆŒu‹Š‡x†ƒˆ|ˆx‰Šq‰ˆpˆ€†q†€†u„…|†{…ˆx‚4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ8ÿDÿWÿNÿ9ÿ6ÿ5ÿ4ÿ4ÿ4ÿ3ÿ2ÿ3ÿ1ÿ0ÿ0ÿ0ÿ0ÿ.ÿ.ÿ.ÿ.ÿ-ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ/ÿ/ÿ2ÿ3ÿ5ÿ7ÿ9ÿ;ÿ?ÿDÿDÿEÿFÿEÿBÿAÿCÿEÿIÿKÿNÿQÿSÿTÿVÿXÿ[ÿ]ÿ^ÿ_ÿ`ÿ`ÿ`ÿ`ÿ_ÿ`ÿ`ÿ`ÿ`ÿ`ÿbÿdÿgÿkÿpÿsÿvÿtÿrÿkÿ`ÿWÿMÿHÿDÿHÿMÿKÿAÿ4ÿ1ÿ0ÿ1ÿGÿkÿkÿdÿcÿhÿpÿ}ÿ…ÿ€ÿzÿpÿVÿ0ÿ(ÿ*ÿ(ÿ+ÿ-ÿ1ÿ7ÿJÿiÿuÿmÿkÿhÿoÿzÿoÿjÿhÿhÿiÿiÿeÿcÿeÿhÿaÿ`ÿ_ÿ^ÿbÿdÿeÿeÿfÿUÿ3ÿ1ÿ1ÿ1ÿ2ÿPÿgÿaÿ_ÿcÿ`ÿWÿEÿFÿKÿPÿQÿRÿPÿ?ÿ)ÿ#ÿ#ÿ#ÿ%ÿ1ÿDÿNÿDÿCÿMÿSÿZÿbÿiÿyÿÿ„ÿˆÿ‰ÿÿÿ‘ÿ—ÿ ÿ–ÿ;ÿÿ/ÿ.ÿ0ÿ0ÿ/ÿ/ÿ.ÿ/ÿ.ÿ-ÿ+ÿ+ÿ*ÿ'ÿ$ÿ"ÿ!ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ!ÿ#ÿ%ÿ&ÿ&ÿ*ÿ,ÿ,ÿ-ÿ/ÿ3ÿ7ÿ?ÿDÿEÿKÿMÿNÿKÿIÿJÿMÿOÿQÿSÿWÿZÿ^ÿ^ÿaÿ`ÿbÿ^ÿYÿUÿSÿUÿcÿ]ÿQÿPÿNÿNÿNÿLÿHÿEÿDÿCÿ>ÿ8ÿ3ÿ0ÿ.ÿ,ÿ+ÿ/ÿ/ÿDÿiÿƒÿ„ÿ„ÿˆÿŠÿŒÿÿŽÿˆÿwÿUÿ=ÿAÿYÿxÿÿÿ†ÿ…ÿŠÿŒÿŒÿuÿPÿDÿHÿIÿIÿIÿLÿMÿNÿOÿRÿSÿUÿRÿRÿTÿWÿYÿXÿVÿRÿPÿCÿ.ÿ!ÿ!ÿ ÿ"ÿ$ÿ%ÿ$ÿ&ÿ2ÿEÿ]ÿrÿ‚ÿŠÿŠÿ‰ÿ†ÿƒÿ}ÿsÿfÿRÿ/ÿ&ÿ7ÿ;ÿ?ÿAÿMÿaÿnÿyÿÿ…ÿ‰ÿÿŒÿŒÿ‹ÿ†ÿ‚ÿÿyÿsÿkÿcÿYÿSÿQÿPÿKÿOÿVÿXÿ^ÿ_ÿ^ÿ_ÿvÿ‰ÿÿŽÿ‘ÿ•ÿ™ÿœÿ›ÿŸÿŸÿ—ÿ}ÿWÿUÿ[ÿTÿUÿ[ÿ`ÿcÿpÿ~ÿÿƒÿ„ÿƒÿƒÿ€ÿÿ~ÿ|ÿ{ÿzÿzÿ{ÿ|ÿyÿwÿsÿnÿjÿgÿdÿdÿdÿcÿcÿdÿdÿeÿeÿeÿgÿiÿmÿpÿsÿtÿuÿ~ÿ’ÿ—ÿ”ÿ’ÿŽÿŒÿ‰ÿŠÿŠÿŠÿ†ÿƒÿƒÿ„ÿƒÿÿÿÿƒÿ‰ÿƒÿÿÿÿ€ÿÿ|ÿyÿxÿvÿtÿrÿqÿqÿpÿoÿoÿoÿoÿnÿnÿnÿnÿnÿpÿuÿ|ÿ…ÿŒÿ‘ÿ’ÿÿ”ÿ•ÿÿŽÿŒÿ‹ÿ‰ÿˆÿ†ÿ‡ÿŒÿÿŒÿŒÿ‹ÿ‹ÿ‰ÿˆÿ‡ÿˆÿˆÿ‰ÿŠÿ‰ÿˆÿˆÿ‡ÿ†ÿ†ÿ†ÿ†ÿ†ÿ…ÿˆÿÿ4~4t4~4t34v55v44w44w44w44w44v44v55x55x44w33w44v55v66w55w66y77y66z77z77z66z66z66z7:yGNy<~5z5~5{4~4|4~3|3~1|/~/|0}0{.}.|.}-}-}-{.~.}-~.|.}.},},}-}-}.|.}-|-}-|-}-|-|.|.|-|-|-|.|.|.|.|.|.|.}.|.}.|.}-|-}-{-}-{-~-|-~-|--|-€.~.~.~.~/~/~14}6‚9{;‚>wBƒHsJMqMLsI~GvG}JzL}N|P}S~W}Y|[}^}_|b|b|c|d}d{d}dzc~c{d~dye}eyffvjnrq‚umvƒwgrƒlbcZcQJjCBoB€FsI€IuD€:v1‚0tPƒorf…cob…flr…~j„„~dy€kaW~Ci5|3q6}:rBJoY…opq†jsg‡evn…uƒmki~j’j~h’e}ec~mŠda‚`_}bdd~d…e}S‡0|,ˆ*},ˆ2|[ˆi{dˆaygŠhwWŽItK“MtO”OtO”OvL’9x&‹$~%ƒ(0yA‚VoX„KiH†RnXˆaqg‰rl{‰f‡Š‰`‡ŒŒTŒ”Kš‹žL„‚%c({.{3z2‚0z/„/z.„+z)„*{*„*|(…&|#ƒ }ƒ~ƒ~‚~~~~~~~~‚~‚~!ƒ"~"„$~&†'}*‡-{/ˆ2{4‹:yAŒD|GŒP|TŽY}Y‹Z~[Š]~^‰e~m‰vu‡st‡p~kˆf~`ˆ_}Z‰P}W„f~`vSPnO€OnM€KrIFuD~ByB|C|={:~4{2€1{+‚2|\€~~‚|ƒƒy…‡xŠ‚‰t€ƒmuK‚*}()‚=€c€ƒ€Žv‡„p‡‚ŒnƒvoPƒAqIJuK~KvK}MuO}OuN|NtNKqK~PpSSnONnMKqCƒ.v!!~""‚$~%ƒ%~(ƒ2{Hƒ]|r{ƒ~‰r‡~†lƒ€€d{‚o_b…Mc-ƒ)p+/w6?wM€atl€uo|‚‚i…‚ˆcŒ‚Šc‰ƒˆb…ƒbyƒsckƒfgYƒUjQƒPlR‚]k_‚]d\€b[g}oM{€:’‹•8› FŸŽ¢O¥Œ¤Zœ‘pWŽLƒN…SˆQJˆG@ˆ;}AL{V‹l|~‹‚zƒˆyˆ~vzyryŽzo{Žzoyvqsmqheqddod’dod’dod’ene“emg•jkm•qhs”tju†p–€•z”r‘oŒƒ‹o‹‹o‹‡v‚€‚zƒ‚~€}†€‰|‚‡€u€‚n€’€n}‘xlw’ums“pmo”pmp”pnp”nnn”nnn”mmn”okp•ui~“‡k‘o‘‰w•”}zŽŒw‰€‰x†€†yˆŒŽ{Š†‹vŒ‹‹v‰…‡y‡z‡}ˆtˆˆp‡†q…€…r†€‡x‡~…‚…w„‡Šv‘„4ÿ4ÿ4ÿ4ÿ3ÿ4ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ7ÿ7ÿ6ÿ6ÿ7ÿ7ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ7ÿ:ÿ3ÿ5ÿ5ÿ4ÿ4ÿ4ÿ3ÿ3ÿ1ÿ0ÿ0ÿ0ÿ0ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ-ÿ.ÿ.ÿ-ÿ.ÿ.ÿ-ÿ.ÿ/ÿ/ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ3ÿ5ÿ7ÿ:ÿ<ÿ@ÿDÿIÿMÿOÿRÿRÿPÿMÿJÿLÿNÿQÿSÿUÿXÿZÿ_ÿaÿbÿeÿeÿgÿfÿfÿhÿhÿiÿiÿhÿhÿiÿiÿiÿiÿnÿqÿtÿuÿvÿuÿrÿlÿcÿZÿTÿMÿFÿBÿ@ÿ>ÿ=ÿDÿMÿNÿEÿ?ÿ>ÿ\ÿlÿdÿcÿcÿiÿsÿÿÿ}ÿyÿrÿfÿ\ÿUÿVÿ[ÿ`ÿdÿlÿrÿnÿiÿgÿdÿlÿxÿoÿiÿjÿiÿhÿfÿbÿdÿbÿmÿlÿfÿeÿcÿdÿfÿdÿdÿcÿRÿ0ÿ)ÿ*ÿ*ÿ<ÿgÿiÿdÿfÿeÿgÿOÿLÿOÿMÿNÿNÿMÿNÿNÿKÿ8ÿ(ÿ(ÿ.ÿ6ÿ:ÿKÿ\ÿZÿLÿQÿYÿ]ÿdÿmÿwÿ~ÿ…ÿˆÿ‰ÿ‹ÿŽÿ’ÿ–ÿ›ÿ›ÿhÿÿ(ÿ/ÿ/ÿ.ÿ/ÿ-ÿ+ÿ)ÿ(ÿ(ÿ)ÿ)ÿ(ÿ'ÿ$ÿ!ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ÿ"ÿ"ÿ#ÿ%ÿ%ÿ)ÿ,ÿ/ÿ1ÿ1ÿ2ÿFÿWÿdÿlÿqÿrÿvÿxÿzÿvÿrÿtÿtÿvÿtÿrÿtÿsÿsÿtÿrÿmÿaÿUÿXÿeÿaÿTÿOÿNÿNÿNÿLÿIÿFÿDÿBÿCÿBÿDÿFÿIÿAÿ0ÿ/ÿAÿoÿ‚ÿzÿ{ÿ~ÿƒÿ„ÿ…ÿ}ÿaÿ7ÿ%ÿ(ÿ)ÿ%ÿ2ÿWÿ}ÿŒÿ†ÿƒÿ…ÿŠÿ‰ÿsÿIÿDÿIÿJÿKÿKÿKÿMÿNÿNÿMÿMÿIÿFÿKÿLÿNÿNÿNÿOÿKÿIÿBÿ-ÿ!ÿ!ÿ"ÿ"ÿ$ÿ%ÿ%ÿ(ÿ3ÿIÿ_ÿtÿ‚ÿ‡ÿ‡ÿ„ÿ€ÿ~ÿwÿnÿ^ÿFÿ(ÿ+ÿ3ÿ/ÿ*ÿ3ÿMÿ_ÿiÿqÿxÿ|ÿÿƒÿ†ÿˆÿ‰ÿˆÿ‡ÿÿ}ÿxÿpÿiÿeÿbÿ[ÿUÿ[ÿ\ÿ^ÿ\ÿ]ÿaÿgÿqÿÿŠÿÿ”ÿ™ÿ ÿ›ÿ’ÿ…ÿiÿSÿHÿVÿaÿaÿiÿnÿ\ÿMÿJÿHÿGÿGÿDÿBÿJÿfÿ|ÿ‚ÿÿ|ÿzÿyÿzÿ{ÿzÿyÿxÿuÿrÿmÿhÿdÿdÿdÿdÿdÿdÿdÿdÿeÿeÿeÿgÿjÿmÿqÿsÿuÿwÿÿ—ÿ—ÿ’ÿŽÿŒÿŒÿ‹ÿŒÿŒÿŒÿˆÿ‚ÿ‚ÿƒÿ‚ÿÿ€ÿÿ†ÿˆÿÿÿÿ‚ÿ€ÿ|ÿzÿxÿwÿuÿsÿpÿoÿpÿoÿoÿoÿnÿmÿnÿnÿmÿnÿoÿpÿvÿÿˆÿÿ‘ÿÿÿ”ÿ“ÿ‘ÿŽÿŒÿ‰ÿ‰ÿˆÿ‡ÿŠÿÿÿ‹ÿŠÿŠÿˆÿ‡ÿˆÿˆÿ‡ÿˆÿˆÿˆÿ‡ÿ†ÿ…ÿ†ÿ†ÿ‡ÿ‡ÿ…ÿ…ÿ…ÿŽÿ‘ÿ4€4v4€4v44v44v44v44v44w44w55x55x55x44x54w55w44x55x45y66y66y67y77z66z66z66z55z66z55z63z4~5{5~5|4~3{3~3{3~2|2~2|1}1~0}.~/}.}-}-|-}.}.}.|.}.}.}.}.|.|.|.{.|.|/|/|.|.}.|.}.|.|.|.|.|.|/|/|/|/}.|.}-|-}-|-}-}-}-}-~.|.~-|--|-€-~--~-€0~0€3€6|8;z?ƒCwF„KsNƒRqV‚XqW~SsO}PwQ|T{W|Z|[}^~b}c~f}e~h}i~j}k|k}k{k~kzj~kyllxm€nvrsrs‚smuƒtepƒkcb]fTNnK€DqC@v>€‚.z!€ "~#$}$ƒ%~(ƒ3{Kƒ`|w}†~‰s…‚m~{ft‚lb\…Gh.ƒ&t1){(:{P€[ue‚lqqƒwj{ƒ~hƒƒ…h…ƒ…g„ƒƒe€ƒ{dsƒmdiƒjckƒhca„Zb^„c^gƒkTp‚xD‚„‹:Š?‚XB‘.w0‹;‡M†]‘`ƒn‘q~tfzVŒXyc‰hyi„mymhx^‚RyVƒgxy‡{wx‹ysxŽzqzyoxuqpkqfdqbbod’dod’dod’dne“emg“kkn“qks”sl{’t—~•}rŽ€‹q‹‚ŒoŒŽo‡v‚€ƒ|€€}ˆ€‡{‰‚r‚ƒm’~m|‘ylv’tmr“qmp”omo”ono”mnl”lnl”mmm”ojs•yh‚“ŠkŽrˆx“’}yŒ‹y‰€‰yˆ€‰zŒŽ€Š{Š†‰x‰…ˆyˆ}‡}‡u‡~ˆrˆ€ˆs‡€‡u‡€‡w‡ˆ…x„ˆ„t†‰uƒ4ÿ4ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ5ÿ4ÿ5ÿ5ÿ4ÿ4ÿ5ÿ5ÿ4ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ7ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ3ÿ5ÿ5ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ0ÿ/ÿ.ÿ.ÿ-ÿ.ÿ.ÿ/ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ/ÿ0ÿ/ÿ2ÿ8ÿ;ÿ@ÿDÿHÿLÿQÿVÿYÿ[ÿ[ÿZÿUÿSÿTÿUÿZÿ\ÿ^ÿbÿeÿgÿjÿjÿlÿmÿmÿnÿnÿnÿnÿnÿmÿnÿpÿpÿrÿsÿuÿvÿvÿvÿtÿsÿoÿiÿbÿ]ÿUÿPÿMÿHÿEÿBÿ@ÿ=ÿ9ÿ8ÿ5ÿ6ÿ;ÿCÿRÿfÿeÿcÿeÿiÿsÿyÿuÿxÿvÿtÿtÿuÿxÿvÿsÿoÿlÿfÿbÿbÿeÿbÿiÿ}ÿpÿiÿjÿhÿjÿhÿiÿiÿjÿoÿ{ÿnÿiÿhÿcÿcÿeÿbÿ^ÿNÿ,ÿ*ÿ+ÿ/ÿXÿlÿhÿfÿjÿnÿaÿEÿLÿPÿPÿOÿPÿNÿLÿKÿIÿGÿFÿBÿ?ÿ<ÿ=ÿ;ÿ=ÿEÿ9ÿ<ÿKÿVÿ^ÿfÿnÿrÿ|ÿ„ÿˆÿŠÿŽÿÿ“ÿ–ÿ›ÿžÿ‰ÿ,ÿ!ÿ&ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ#ÿ"ÿ!ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ ÿ!ÿ#ÿ"ÿ'ÿ?ÿZÿfÿgÿdÿcÿdÿeÿgÿrÿxÿxÿuÿtÿvÿtÿsÿrÿoÿmÿkÿkÿlÿlÿnÿpÿpÿrÿvÿ{ÿ}ÿtÿ\ÿPÿLÿNÿNÿLÿHÿFÿEÿCÿBÿCÿDÿHÿSÿaÿmÿrÿzÿ‚ÿ~ÿ{ÿ}ÿ}ÿ}ÿ~ÿyÿ]ÿ,ÿ$ÿ%ÿ&ÿ$ÿ$ÿ/ÿNÿtÿŠÿ†ÿƒÿ„ÿ‡ÿ€ÿZÿ9ÿDÿHÿLÿMÿMÿLÿMÿNÿMÿLÿKÿFÿHÿKÿLÿMÿMÿKÿJÿIÿGÿ>ÿ.ÿ#ÿÿ!ÿ"ÿ"ÿ"ÿ%ÿ(ÿ8ÿPÿfÿzÿ‡ÿ‡ÿ‚ÿ~ÿ~ÿ{ÿtÿiÿXÿ>ÿ+ÿ,ÿ0ÿ,ÿ.ÿEÿQÿZÿ^ÿgÿkÿqÿuÿzÿ|ÿ}ÿ~ÿ‚ÿÿ€ÿÿ|ÿyÿuÿtÿsÿpÿnÿgÿeÿfÿhÿkÿqÿxÿƒÿŠÿŒÿÿ‹ÿÿ\ÿÿ2ÿDÿLÿ\ÿbÿaÿ]ÿnÿsÿmÿ†ÿŸÿ£ÿ©ÿ¦ÿÿ“ÿˆÿ€ÿ‚ÿÿoÿgÿmÿvÿwÿxÿzÿyÿwÿxÿsÿoÿjÿeÿdÿbÿbÿdÿdÿdÿdÿdÿeÿfÿgÿiÿmÿoÿoÿrÿuÿ~ÿ—ÿ™ÿ“ÿÿÿŒÿ‹ÿŠÿŒÿŽÿÿ‡ÿƒÿÿ€ÿÿÿÿÿˆÿ‡ÿÿ‚ÿ‚ÿ‚ÿ€ÿ}ÿ{ÿyÿvÿtÿrÿqÿpÿoÿoÿoÿoÿlÿlÿlÿlÿmÿmÿoÿsÿzÿƒÿÿ‘ÿ‘ÿÿÿ‘ÿÿŒÿŒÿ‹ÿ‰ÿ‰ÿ‡ÿˆÿŒÿŽÿŠÿŠÿ‰ÿ‰ÿˆÿˆÿ‡ÿ‰ÿ‡ÿ‡ÿˆÿ‡ÿ†ÿ†ÿ‡ÿ‡ÿˆÿ†ÿƒÿ‚ÿƒÿ‰ÿÿÿ55u44u44v33v33u44u44u44v44w45w55x44x44x44x44x55x55y77y55y56y66y66y66y66z66|66|66|55|5~5{5~4|2~2|4~4|3~3~2~1}1}1~0}0~0}/|/}.|.}/}.}.|.|.|/}/|/|/|/|/{/|/{/|/{/|/{/|/{.|.{.|.{.|.|/|/|/|/|.|.|.|.|/|-|-|-|.|.}-|-.|.€-~--~--}-€/}0€146:~@„D{H…LwQ…Xr\…^qa`qZ}XtY|Zy\|`}`|d~h}i€j~lmn|oo{oowo€ovq€qtrrtstqwƒwov‚uitƒqem‚ffa€\jW€QpM€JuHDvA?x;9y6€2x1€3v<‚Wtj„cqf„eoo…rkg…hgg‡fhjˆkjjˆimfˆgliˆfof‡esd†`zf„x…som~i’i|f‘e~gh~h}~rŠhfd~ag}a„\{N‡,z*ˆ)z<ˆgzkˆhwiŠlvmStB”KsO˜PqQ™QqO—NqL“ItG’CxB‹@|Bƒ>?u9‚;l5‚4iB…QhZ‰bhg‹lcu‹€[ƒ‡R‰N‘•H—˜EšˆsP} u${'&|'…%}%…$|$…"}"…!}!…~„~…~ƒ~ƒ~‚~~€~€~€~€~€~€~} ƒ }"„7}[†l~i‡ca†`€a†ciˆrz†w~u…u~w…v~t„sr„p~n…n~l†ll„kk‚l€oƒptƒz€wZzLOvNLrK~GuE}CxC}CzC{JyTx`zkzt{€{„y}yy{{zx€uxo‚J|)&&%…&|&‡.}N‚t~ˆzƒ€s……pz„Os7„BzF~J|L|LzM{LyL|LwK}HwE{IxK{KvM|KsJHsFDw>€.}# € €!‚!!ƒ%},…<|Tƒk|~|‰~‡tk}‚yer…gdS†8n"ƒ'x1€8|=LyQZt\ƒ`qf…kmn…qlr…wly…|h~ƒb~‚~b|ƒwcx…wbu…q_q…qao…j`n…sTz„K‡†ŒH‹Š‰PˆŽAi'‰7†E‚Q“[}]˜Q{U™bv}–šs³ˆ¼|»º€¹x±€¢uŽ|~xptx™vww}tz|{z…ztzŒyqvspnkqgcpbbod‘dmd‘cnc’dme“gmi“lkn”qjs”sm„™s—{’s‹pŠ‹oŽ’q„x€~‚€~ˆ…z‹ƒq€m“}mz’xlv“tmr”pmo”omo”nnm”lnl”lml•nll•oit•|i†“Œl‘t…‘z~}{Œ‹z‰€‰y‰Š{Ž~Ž‹z‹ƒ‰yˆ‡{‡vˆ~‡r‡‡q‡€†u††v‰ˆzˆ}„ƒ€t‚Œ†s‰‘uƒ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ4ÿ5ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ2ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ/ÿ/ÿ.ÿ.ÿ/ÿ/ÿ/ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ0ÿ0ÿ1ÿ4ÿ6ÿ:ÿ>ÿAÿHÿLÿPÿVÿ\ÿ`ÿcÿdÿaÿ^ÿ\ÿ]ÿ_ÿaÿbÿfÿhÿiÿlÿnÿpÿpÿqÿoÿoÿoÿoÿoÿqÿqÿrÿsÿuÿwÿxÿxÿvÿtÿsÿoÿkÿfÿaÿ\ÿVÿSÿPÿMÿJÿFÿDÿ@ÿ=ÿ:ÿ7ÿ4ÿ1ÿ1ÿ1ÿGÿjÿeÿaÿcÿiÿlÿXÿKÿRÿUÿTÿTÿNÿJÿKÿWÿhÿfÿaÿ_ÿ\ÿZÿdÿxÿpÿjÿiÿdÿgÿjÿjÿgÿdÿeÿxÿxÿkÿhÿfÿcÿhÿgÿ[ÿJÿ,ÿ'ÿ.ÿPÿnÿiÿhÿlÿoÿjÿEÿEÿLÿPÿSÿTÿRÿQÿOÿMÿIÿEÿCÿ>ÿ<ÿ@ÿEÿ>ÿ;ÿ9ÿ=ÿ9ÿ?ÿTÿVÿ[ÿ`ÿgÿmÿvÿ~ÿ„ÿˆÿŠÿÿÿ’ÿ•ÿ—ÿ˜ÿXÿÿ&ÿ&ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ"ÿ"ÿ!ÿ!ÿ ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿ4ÿZÿkÿjÿeÿ`ÿ`ÿbÿcÿcÿlÿvÿyÿuÿvÿuÿxÿxÿxÿxÿuÿsÿqÿpÿnÿnÿlÿlÿjÿjÿjÿkÿlÿnÿsÿzÿ‚ÿyÿ\ÿOÿNÿLÿKÿGÿEÿCÿBÿBÿEÿJÿRÿaÿnÿtÿÿ€ÿ}ÿyÿwÿwÿsÿpÿ`ÿ9ÿ,ÿ,ÿ(ÿ&ÿ'ÿ(ÿ/ÿOÿsÿ‡ÿ‚ÿ€ÿ„ÿ„ÿvÿIÿ7ÿBÿFÿJÿLÿLÿMÿLÿJÿJÿIÿCÿEÿIÿJÿIÿIÿIÿHÿIÿGÿEÿ?ÿ-ÿ$ÿ#ÿ#ÿ!ÿ"ÿ#ÿ%ÿ,ÿ@ÿXÿmÿ€ÿˆÿ‡ÿ‚ÿ€ÿÿzÿrÿdÿPÿ5ÿ"ÿ"ÿ&ÿ2ÿBÿMÿPÿVÿYÿ\ÿaÿeÿiÿkÿmÿqÿtÿxÿzÿ|ÿ|ÿ|ÿ{ÿ{ÿ|ÿ{ÿwÿtÿtÿtÿrÿpÿtÿxÿ~ÿÿˆÿŒÿ‹ÿˆÿ‚ÿ7ÿ1ÿ<ÿEÿSÿ_ÿVÿQÿMÿnÿ ÿ¶ÿ¾ÿºÿ¶ÿ·ÿ£ÿ«ÿ´ÿ™ÿŽÿ“ÿšÿÿ–ÿÿ€ÿfÿXÿpÿ{ÿyÿvÿrÿmÿgÿcÿcÿbÿcÿcÿcÿcÿcÿcÿdÿeÿhÿjÿmÿoÿqÿsÿuÿŠÿ›ÿ˜ÿ“ÿŽÿŒÿŒÿ‹ÿ‹ÿÿÿŽÿƒÿ‚ÿ‚ÿ‚ÿÿÿ‚ÿƒÿŠÿ…ÿÿ‚ÿ‚ÿ€ÿÿ}ÿzÿwÿuÿtÿrÿpÿoÿoÿoÿnÿmÿlÿlÿlÿlÿmÿmÿpÿuÿ}ÿ‡ÿŽÿ‘ÿÿŽÿ‘ÿÿŽÿÿŒÿ‹ÿ‰ÿ‰ÿ‰ÿŠÿÿŽÿŠÿŠÿ‰ÿˆÿ‡ÿ†ÿ‰ÿ‡ÿ‡ÿ‡ÿ†ÿ…ÿ†ÿˆÿ‰ÿ‡ÿ…ÿÿ‚ÿ†ÿŠÿÿ‘ÿÿ55u44u44v44v44u33u33u44v44w55w44x44x44x44x44x44x66w66w55y56y77y66y66y66z66{66{66{55{5~3{4~4|4~4|4~4|4~4}3~2|1}1|0}0|0}0|0}/|/}/}/}.|/|/{/}/|/|/{/|/{/|/{/|/{/|/{/|/{/|/{/|/{/|/{.|/{/|/|/|.|/|/|.|/|/|.~.|..|.-|-€-~--~--}-€-}/€1358~;ƒ@{E„JwO…Tr[…aqeƒjpiera|]x_|b{d}f|jl{j€mvnntp‚non‚nknƒniqƒrgt‚why‚zixƒwhv‚tfrƒnei‚di`€\nW€TsR€OvLHwFBv@~E€B‚=v8@nA‚Rhd†QdYˆabf‰k^u‹}W‚†P‰‹KŽ’C–Œ—C‘„=X|&y({(%|&†%|$…#}#…"}"…!~!„ ~ …~ƒ~ƒ~‚~ƒ~~~~~~~‚"~E„lo…e€^…\€`†dd‡f~j‡x~z‡w~v‡w}x‡y}{†z}y…x}v…u|r†q|p…q}m„k~k„ki„ij‚lr{€„z[}PLwJ~FtE}CwD}CyD{IyTya{nyt{~}z|vs|st{q€rzZ7}/~0,}(ƒ'{'…2|Q‚u}„y€€~s‚ƒqpƒ=v3‚A}H€K~J~L}M}L|J~IzABzG}IyI}IxJ~IuHIvH€Fz>€,}"€#%~#‚$~%ƒ'}/„C|[q|ƒy‰~…qƒ€k}‚yep…afM†2o ƒ!w%€9yK€PsT‚VnU†Xk\‡^lc‡emg…kml…sjvƒxeyƒ{b{…{c}„~fz„zcx„xbu„vay…|Z†ƒS‡ˆ‰P…‹„XŒ3p3†<‹D~T™[vLŸEqXž”q´½|½€«„–z¨ƒ¤z¬¹w²‘t†}ržw•ny…lw|_vnwx‡wsvoqkgqecpbbob‘cmc‘cnc’cme“gmj“mko”qis”wnŽˆ™w˜x’qŽ‚ŒpŠ‚‹prŠ„y‚€ƒ~ƒ‚‚€‚|…‰€ƒx‹‚pƒ€m“{mx’wlu“smr”pmo”omo”onm”knk”lml•lln•qiv”i‰‘n’‹Žu„‘{|Ž~zŒ‹z‡€‡yŠŒ{~Œ‹|Œ‹|‡y‰~‰t‰€ˆr†€…s…€…wˆ€‰{‰~†ƒx‚Š…s‰‹Žs‘†‘w€5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ6ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ3ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ2ÿ2ÿ2ÿ1ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ/ÿ/ÿ/ÿ/ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ/ÿ0ÿ1ÿ3ÿ5ÿ7ÿ:ÿ=ÿBÿFÿMÿSÿ[ÿaÿeÿjÿmÿjÿfÿcÿbÿdÿeÿhÿkÿkÿjÿmÿnÿnÿpÿnÿnÿnÿoÿpÿqÿsÿvÿxÿ{ÿ{ÿxÿwÿtÿsÿqÿmÿgÿdÿ`ÿ\ÿXÿVÿSÿPÿMÿJÿGÿDÿBÿ>ÿ:ÿ7ÿ4ÿ2ÿ1ÿ3ÿNÿjÿ`ÿ]ÿ`ÿgÿgÿ5ÿ'ÿ*ÿ+ÿ+ÿ,ÿ.ÿ9ÿRÿeÿ_ÿWÿVÿPÿMÿVÿxÿsÿhÿgÿgÿcÿeÿkÿeÿjÿkÿnÿ‚ÿtÿiÿfÿfÿmÿvÿ`ÿCÿ&ÿBÿdÿjÿhÿiÿlÿpÿqÿNÿBÿKÿPÿUÿVÿVÿTÿRÿOÿLÿGÿEÿCÿ?ÿ=ÿ9ÿ:ÿ@ÿJÿJÿ?ÿ9ÿ>ÿEÿ[ÿXÿPÿXÿ`ÿbÿjÿuÿ{ÿÿ„ÿ‰ÿŠÿŽÿ’ÿ’ÿ•ÿ†ÿ-ÿ ÿ(ÿ)ÿ'ÿ'ÿ'ÿ%ÿ%ÿ%ÿ"ÿ"ÿ!ÿ!ÿ ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿ/ÿ]ÿnÿkÿ_ÿ^ÿ`ÿcÿeÿgÿjÿqÿzÿwÿuÿvÿwÿxÿyÿ{ÿ}ÿ}ÿ}ÿ}ÿyÿwÿvÿtÿrÿqÿoÿnÿmÿkÿjÿfÿhÿgÿkÿsÿyÿÿqÿUÿLÿJÿFÿEÿCÿCÿCÿDÿJÿTÿbÿlÿrÿ|ÿzÿrÿpÿpÿqÿnÿnÿmÿaÿEÿ1ÿ/ÿ/ÿ+ÿ+ÿ5ÿUÿwÿ…ÿ~ÿ~ÿÿ€ÿiÿ2ÿ#ÿ5ÿEÿJÿKÿNÿMÿLÿGÿ?ÿ=ÿCÿIÿKÿJÿJÿJÿIÿHÿIÿHÿFÿ<ÿ*ÿ"ÿ#ÿ#ÿ"ÿ%ÿ%ÿ&ÿ0ÿGÿ_ÿwÿˆÿ‰ÿ…ÿƒÿ€ÿ}ÿwÿoÿ`ÿHÿ-ÿ#ÿ%ÿ,ÿBÿPÿTÿWÿWÿWÿYÿZÿZÿ]ÿ\ÿ`ÿdÿfÿkÿqÿtÿvÿxÿyÿyÿ{ÿ}ÿ}ÿ}ÿ}ÿ|ÿxÿzÿ}ÿ~ÿ€ÿƒÿ…ÿ…ÿÿ‚ÿyÿ7ÿ7ÿ;ÿAÿMÿRÿJÿDÿoÿ¦ÿ·ÿ¾ÿ¶ÿˆÿ|ÿ ÿ´ÿºÿ¸ÿ¶ÿ‘ÿ~ÿŠÿ—ÿ–ÿ’ÿ˜ÿÿ‚ÿuÿvÿuÿtÿoÿkÿgÿeÿcÿbÿaÿaÿbÿbÿbÿbÿcÿeÿgÿjÿmÿoÿsÿtÿ|ÿ“ÿ™ÿ—ÿÿÿŒÿŒÿ‹ÿŒÿÿÿ‰ÿƒÿ‚ÿƒÿƒÿ‚ÿ€ÿ€ÿ…ÿ‰ÿ…ÿ‚ÿ‚ÿ‚ÿÿ€ÿ}ÿzÿvÿtÿrÿrÿqÿpÿnÿnÿkÿkÿkÿkÿlÿlÿlÿnÿpÿwÿÿŠÿÿÿÿŽÿŽÿÿŽÿÿŒÿ‹ÿ†ÿ†ÿŠÿÿÿŒÿ‹ÿŒÿ‹ÿ‰ÿŠÿ‰ÿ‡ÿ†ÿ†ÿ…ÿ…ÿ…ÿˆÿ‡ÿ‡ÿƒÿÿ‚ÿ‰ÿÿÿ’ÿÿÿ44u55u43u55u55u44u44u44v44w33w33w53x44x44x55w55w66x55x55x56x56x66x55y66y66z77z77y66y44z44{4~4{3~3z4}4{3}4{3~3{1~1{1}1{1}1{0}0|/{/z/|/{/|/{/|/z0|0|0|0|/|/|.|/{0|0{/|/{/|/{0|0|.|/|/|/{0|0}0|0~/|.~.}-}.}.~.}.~-}--~-.~..~.~/~1~3~4679€<|A‚EyL…RtZ†`nf…knnnoj}gvf|g{i~izi€jwiƒkql„nip„odo„o`r†s_w†x^{…|\y…x]x…w`vƒscnƒihgen_\tZXuSQxOKxIFxB?x:8y62{2/x?‚cwc„^saƒfpjGt&}(y*})|+/|;‡Tseˆ[oW…RsM„NxT„t€wfŒg~h“i|i”i|f”h|l’p}Ž~}m…k|j€hzu‚zwpˆgviŠhxhŠlvpŠntphuCGuM”QsU™WsUšRsR™NrI—FsE”Av>=x;Œ8|5…9€FI‚?u:ƒ9nB„IjP†PfV‡^af‰m[u‹~SŒ„L‡ŽŠGŒ@’‹“Dw%b"|({({(„&|%…%}$„!} „!}„~ƒ~ƒ~ƒ~ƒ~‚~‚~ƒ~ƒ}‚}ƒ""‚B~j„nb„^`†ce†fj†p}w†{}w†t}v…x}w†{||†~}}†~|~†}{z‡xzu†vxw‡tzr‡q{o…j|g…fe„ff„msƒz|ƒiP|JGvF}CuE{CxGzLyUzbzr|xzyu~p€m|n€p{o~o}p{om|aG|2-{,7|V~y~‚w~p€ƒ~pd†0v#ƒ$€*€;‚FH‚J€H?€5€3<C~I|KLzK€KwJHwHEy8%~"€"""ƒ$~$ƒ&}3ƒM{d{}‰uŠ…o„‚‚k}ƒvem…]hC„)t"*|:LzU‚WtZ„]n\ˆ\i[‰ZhYˆTjU‡Zjd…hml„qgt†tdu†wex†~f…€d…~a}„}b~„€_‚…‚Y‚‡~X~ˆ‚_rŠGt9‡@ŠC}KšIuC Mo‰Ÿ©s¶‹½}¶{ƒYƒ€‘©~²yªuŽ~ƒq‹v–l“v¡f£|œcˆurqzr‰oskfrb’bpa‘apa‘bpb‘cnc’dmf“hkk”okq”qjr“~p–ˆ™y”x€o‰Šp‹€p’r‰‚ƒy€}‚€}€‚}ƒ~‰ƒƒwŒ‚p‚‘€m~”zmw”tls”qlo”oko”omn”mml”kmk”lkl•jjm•phx“ƒi‹nŽŠŽvŽƒ|Œ{Œ~ŒyŒŠz‡†x‰{Ž}}zŠ€‰tˆ€ˆr‡€‡s†€†v†€†x‡€…€ƒz€„ƒq†Œr‘‰“u’‘|5ÿ5ÿ5ÿ5ÿ4ÿ3ÿ4ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ7ÿ7ÿ7ÿ7ÿ6ÿ6ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ.ÿ/ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ.ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ/ÿ.ÿ.ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ0ÿ1ÿ3ÿ5ÿ7ÿ8ÿ9ÿ<ÿAÿEÿKÿQÿXÿ^ÿdÿjÿpÿpÿmÿjÿgÿfÿhÿhÿiÿjÿlÿkÿnÿoÿqÿpÿqÿsÿwÿyÿ|ÿ}ÿÿ~ÿ{ÿyÿzÿyÿwÿrÿmÿjÿfÿcÿ_ÿ\ÿ[ÿXÿTÿRÿPÿMÿIÿFÿBÿ?ÿ;ÿ9ÿ6ÿ2ÿ3ÿ0ÿ4ÿ[ÿgÿ`ÿ_ÿbÿhÿWÿ(ÿ%ÿ)ÿ)ÿ,ÿ0ÿ<ÿVÿfÿZÿUÿQÿOÿPÿUÿrÿxÿhÿeÿeÿeÿbÿeÿkÿhÿiÿmÿzÿƒÿsÿkÿjÿiÿoÿ|ÿÿzÿxÿoÿlÿoÿpÿpÿqÿ\ÿAÿHÿNÿPÿSÿUÿTÿRÿOÿLÿGÿCÿ@ÿ>ÿ<ÿ:ÿ9ÿ5ÿ3ÿ2ÿ8ÿCÿGÿ>ÿ1ÿ5ÿ=ÿCÿFÿQÿ[ÿ^ÿeÿnÿuÿ{ÿÿ‚ÿ†ÿˆÿŠÿŒÿ‘ÿ”ÿkÿÿ%ÿ&ÿ&ÿ"ÿ"ÿ#ÿ"ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ÿÿ ÿÿÿ#ÿNÿnÿlÿ`ÿ_ÿeÿdÿdÿiÿrÿwÿzÿ|ÿwÿtÿsÿuÿxÿzÿ{ÿ{ÿ|ÿ}ÿ}ÿ|ÿ|ÿxÿtÿuÿxÿvÿtÿtÿtÿrÿpÿkÿiÿgÿeÿfÿhÿmÿsÿzÿxÿ`ÿKÿHÿDÿCÿDÿDÿHÿMÿXÿfÿrÿxÿwÿrÿqÿnÿmÿoÿnÿnÿqÿrÿoÿmÿkÿ`ÿTÿBÿ?ÿ\ÿzÿÿ|ÿ~ÿ€ÿyÿ[ÿ(ÿ"ÿ"ÿ#ÿ!ÿ*ÿ8ÿAÿAÿ7ÿ/ÿ9ÿ@ÿBÿFÿGÿIÿKÿLÿJÿIÿHÿDÿ6ÿ%ÿ"ÿ"ÿ"ÿ"ÿ$ÿ$ÿ(ÿ:ÿQÿiÿ{ÿˆÿŠÿ…ÿ„ÿ‚ÿÿxÿmÿZÿ?ÿ&ÿ%ÿ1ÿDÿRÿVÿYÿ[ÿ^ÿ_ÿ_ÿZÿYÿWÿTÿSÿYÿ]ÿeÿkÿmÿoÿoÿmÿpÿsÿxÿ|ÿ}ÿÿÿ}ÿÿÿÿ‚ÿ‚ÿÿ|ÿ‚ÿƒÿmÿVÿCÿFÿEÿIÿEÿ@ÿaÿ“ÿ¨ÿ²ÿ·ÿ¸ÿªÿŽÿ|ÿ†ÿ–ÿ•ÿ›ÿ•ÿšÿ”ÿ’ÿ’ÿÿ“ÿ›ÿ¦ÿ•ÿƒÿrÿoÿnÿhÿfÿdÿbÿbÿbÿbÿaÿaÿcÿdÿdÿfÿgÿlÿnÿqÿrÿsÿ„ÿ™ÿ™ÿ”ÿÿŒÿ‰ÿŠÿŠÿŒÿ‘ÿŽÿ‰ÿƒÿ‚ÿ‚ÿÿÿÿ€ÿ„ÿ‡ÿ‚ÿ‚ÿ‚ÿ‚ÿ€ÿ~ÿzÿwÿuÿsÿqÿoÿoÿoÿnÿmÿlÿkÿkÿkÿlÿmÿkÿnÿrÿyÿ„ÿŒÿÿÿÿŽÿŒÿŒÿŒÿ‹ÿŒÿ‰ÿ‡ÿ‡ÿŠÿÿŽÿŽÿÿŒÿŠÿ‰ÿˆÿˆÿ†ÿ†ÿ†ÿ†ÿ†ÿ†ÿ„ÿƒÿÿÿ…ÿ‹ÿÿ–ÿ•ÿ’ÿÿŽÿ65t55t43u45u55u55u55u44v44w33w44w33x33x44x55w44w55w55w66x66x66x55x44y55y66z75z66y66y54y55z4~4z4~4y3}3{3}3{2~1{1~1{1}1{1}1{0}0|0{0z/|/{/|/{/|/z0|0|0|0|/|/|0|0{0|0{.|.{/|/{0|0|/|/|/|/{/|/}0|0~/|.~.}-}.}.~.}.~-}--~-.~.0~0€0~1€3~5689€<~A‚E|I…QvU†[qb‡inpƒtornrj~gxgfwh‚isk„ljo…qev„yay„|c}…e…‚b„_„~^{„z_w‚qem‚jlf€br_€\v[€XxU€RxO€LxJGxC?x<9y73{21y3‚Lwiƒ^t]aqc~as0z$y(})|+0{>‡XrdˆWnS…QrR„TvX„i€rfa}b’c{a”a}i”l}r•p}q“€}uŒj|f…fzh„qz{‡zyy‰uvoŒntpŒrsnNuD’JvN–OuR—RtR™OtK˜JtE–Au>’ÿ;ÿ9ÿ6ÿ5ÿ3ÿ3ÿ2ÿ1ÿ/ÿ2ÿ<ÿ=ÿ4ÿ3ÿ7ÿAÿGÿRÿYÿYÿaÿeÿmÿuÿyÿÿ‚ÿ†ÿ…ÿˆÿÿ‘ÿÿMÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ"ÿ%ÿ(ÿ+ÿ-ÿ%ÿ ÿ7ÿaÿmÿhÿdÿgÿiÿmÿmÿrÿxÿuÿnÿpÿrÿrÿqÿpÿoÿsÿvÿvÿwÿyÿzÿpÿhÿcÿeÿhÿmÿnÿoÿpÿnÿmÿkÿkÿrÿxÿwÿoÿiÿhÿjÿlÿmÿrÿtÿsÿdÿMÿHÿDÿCÿGÿKÿUÿcÿoÿtÿvÿtÿnÿmÿmÿoÿnÿkÿnÿtÿrÿpÿlÿjÿhÿiÿmÿrÿ{ÿƒÿ€ÿ{ÿ~ÿ|ÿsÿKÿ$ÿ!ÿ$ÿ$ÿ"ÿ$ÿ$ÿ$ÿ'ÿ*ÿ+ÿ)ÿ.ÿ:ÿAÿEÿHÿIÿKÿMÿLÿEÿ<ÿ0ÿ&ÿ$ÿ$ÿ#ÿ#ÿ&ÿ'ÿ0ÿDÿYÿqÿ…ÿ‹ÿˆÿ„ÿ‚ÿÿÿwÿhÿTÿ4ÿ%ÿ0ÿBÿLÿRÿYÿZÿ[ÿ^ÿ`ÿ`ÿ_ÿ\ÿWÿUÿSÿTÿYÿ^ÿaÿfÿhÿiÿkÿnÿpÿuÿqÿuÿzÿ|ÿ}ÿ€ÿÿ‚ÿ‚ÿ‚ÿÿ€ÿ„ÿ€ÿnÿ[ÿZÿZÿTÿQÿHÿDÿqÿœÿ¦ÿ¥ÿ£ÿžÿ•ÿ—ÿ˜ÿŠÿ‹ÿ™ÿ—ÿ–ÿ“ÿ–ÿšÿÿ˜ÿ•ÿšÿœÿ“ÿÿ‰ÿ‚ÿjÿeÿdÿaÿbÿbÿaÿbÿbÿbÿcÿcÿeÿgÿhÿkÿpÿpÿsÿvÿ‹ÿšÿ˜ÿ•ÿÿÿŠÿŠÿŠÿÿÿŒÿ†ÿ„ÿƒÿƒÿ€ÿÿÿ~ÿ€ÿƒÿÿ‚ÿÿÿÿ}ÿzÿvÿsÿqÿnÿnÿmÿlÿlÿlÿkÿkÿjÿjÿkÿjÿlÿnÿtÿ~ÿˆÿŒÿŽÿŽÿÿÿŠÿŠÿ‰ÿ‹ÿ‹ÿˆÿ†ÿ‡ÿŒÿ‘ÿÿÿÿŒÿŠÿˆÿ‡ÿ†ÿ†ÿ…ÿ„ÿ„ÿ…ÿ…ÿ‚ÿ‚ÿ‚ÿˆÿÿ“ÿ•ÿ”ÿ”ÿ‘ÿÿÿ5~5t6~6t55u66u64u55u55u44v44x55x44y33y33y34y55w44w55x55x55x55x66y66y66y66y66z66z66z66z66z55z5~4{4~4|3~3{3~3{3~3{2~2{1}1{0}0{1|1{1|1{/|/{/|/{/|0{/|/{0|0{/|/{0|0{0|0{/|/|/|/|/}/|/}/|/}/|/}/|/}/~/}.~.}-}.}.~.}.-}-€-~-.~./~/1~23~57~9:=?C~G„LzS…Xt_†fql„qpwuprotkiug„hoi„oft†{d…†h‡ƒ‡m‡…ˆp‰…ˆm‡„…f„}dzƒwfr‚onlhsdbv^~[y[YyU€SxO€LxJ€GyC€?y;8z62z1€.y/4xSƒdtZƒWs[€ctS|&x%}(|+2zC‡]raŠXoRˆQqV‡YuX†`{r‚i‡c~jj~n‘lk”mk”l~l–c}g‘i{e`{]‡czi†nzqˆuxz‹zunŽ^vOMvN’MwM”NwN•MvL—KxJ•IuE“Bv?‘=y96y5‹2{2‰/{.ˆ.}.…/6‚A€?}7>tG…NjW‰aaUŠ]Zd‹lWt{M‚E‚Ž†?‹‘C•ˆRAp~|}~ƒ~‚ ~ !!ƒ!~!ƒ ~ ƒ~‚~!ƒ%~(…/}4†-~A†em†h€e„h€jƒkn„v|w‡p{i‰kzn‰rzpŠo{nˆozqˆt{tˆu{v‰k}cŒa~aŽagŒj~m‹n~qŠn}lˆi|f‡j{u‡{|s†l}l…jj‚l€n‚pq‚mW~F~C{C{H|NxX|ezp|t}x|qnop~q€n|j|m~rzr€o{l€j|j|h}kyt~}u‚€r|}p}„rqC†#z$ƒ$"#„#"„%%ƒ',ƒ.~*‚*~4‚=BG€H~G‚H|E‚<~/‚%€$€$$$ƒ&~)…2}G„^|u|†~‹s‰€…m‚g€„tcg…Sj1„&u6D}KO|V‚[w\„_p^‡]l_ˆ\hYˆVkT‡TlX†\k`…cma†cne‡ink‡pkp‡mgu†we|†€c…a‚…‚`‚…ƒbƒ†dp‰dpg†c~Z„VQ}O–ut•¦u¥£y‰™|–…“Ž‚€xŽ€’n”™d•‚”^›ƒ]¡ \‘€‘[Žd|~fzbvcŽap`’apb‘apa‘cnc’emg“ill”qjq”ukyr™–}’urŠ‰sŠ€rŠt†…|ƒ~€~~|ƒ†r‚ƒl‚’m{”xmu”qln”mlm”lll”klj”kmk“jmh•klk–mjo•wh€“‰ktŠ…y‹}‰~ˆ{‰‹}ˆ…}…‡xŽ’uŒŒu‹s‰€ˆs†…u……x„„|…}ƒ‚‚vƒŠ‡r‰”t–‡•x”…“|‚}€9ÿ9ÿ9ÿ9ÿ8ÿ8ÿ6ÿ6ÿ7ÿ6ÿ5ÿ5ÿ5ÿ5ÿ3ÿ3ÿ4ÿ4ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ7ÿ6ÿ6ÿ6ÿ6ÿ6ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ1ÿ1ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ/ÿ/ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ0ÿ1ÿ3ÿ5ÿ7ÿ9ÿ:ÿ=ÿ?ÿCÿGÿLÿPÿWÿ\ÿcÿiÿoÿrÿuÿtÿpÿmÿiÿfÿhÿlÿtÿxÿ|ÿÿ„ÿ…ÿ…ÿ…ÿ†ÿˆÿ†ÿ„ÿÿÿ{ÿvÿsÿpÿnÿlÿhÿdÿbÿ^ÿ[ÿZÿWÿTÿQÿNÿKÿIÿHÿCÿ?ÿ;ÿ8ÿ6ÿ2ÿ2ÿ0ÿ/ÿ1ÿCÿbÿ[ÿXÿZÿ`ÿ]ÿ2ÿ%ÿ+ÿ.ÿ0ÿ@ÿZÿ]ÿVÿSÿUÿWÿXÿYÿ`ÿsÿmÿdÿhÿpÿqÿqÿnÿnÿqÿmÿnÿkÿ_ÿ]ÿbÿiÿdÿfÿgÿkÿlÿnÿgÿaÿUÿQÿSÿSÿTÿQÿQÿOÿOÿMÿKÿIÿHÿGÿEÿBÿ?ÿ>ÿ;ÿ7ÿ6ÿ2ÿ1ÿ0ÿ0ÿ/ÿ/ÿ,ÿ-ÿ7ÿFÿEÿ?ÿCÿHÿNÿiÿdÿUÿ_ÿgÿlÿqÿxÿ}ÿ~ÿƒÿ†ÿ‹ÿ‘ÿ•ÿ‰ÿ8ÿÿÿÿÿ ÿ ÿÿ ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿÿ ÿ!ÿ#ÿ"ÿ'ÿ/ÿ8ÿHÿZÿhÿhÿhÿkÿmÿmÿqÿwÿvÿlÿiÿgÿiÿlÿlÿmÿmÿoÿpÿqÿqÿtÿrÿ]ÿ[ÿ[ÿ[ÿ_ÿ`ÿeÿkÿoÿpÿnÿoÿqÿmÿjÿdÿiÿtÿwÿqÿoÿkÿkÿmÿlÿnÿnÿrÿeÿIÿDÿBÿIÿQÿ[ÿhÿqÿwÿuÿnÿnÿpÿpÿpÿmÿfÿiÿrÿrÿpÿnÿkÿlÿjÿiÿqÿ{ÿ„ÿ€ÿzÿ|ÿ{ÿkÿ;ÿ$ÿ&ÿ%ÿ$ÿ%ÿ%ÿ%ÿ#ÿ%ÿ'ÿ,ÿ1ÿ2ÿ+ÿ$ÿ(ÿ6ÿAÿDÿGÿFÿBÿ7ÿ+ÿ#ÿ$ÿ%ÿ$ÿ$ÿ&ÿ*ÿ6ÿMÿcÿzÿ‰ÿ‡ÿ„ÿƒÿÿÿ|ÿrÿeÿLÿ.ÿ&ÿ7ÿEÿKÿOÿSÿYÿYÿ\ÿ^ÿ]ÿ]ÿ[ÿWÿUÿUÿUÿXÿ\ÿ^ÿaÿbÿdÿdÿcÿcÿiÿkÿjÿoÿrÿxÿ|ÿ€ÿÿÿÿÿ‚ÿƒÿ|ÿrÿnÿkÿgÿeÿ`ÿ\ÿ\ÿ|ÿžÿ¤ÿ¢ÿœÿ™ÿ“ÿÿ‰ÿŠÿ‡ÿzÿÿ’ÿ“ÿ—ÿ˜ÿ•ÿ•ÿ™ÿÿ¢ÿŸÿžÿ“ÿ†ÿ}ÿkÿcÿbÿbÿbÿaÿbÿaÿaÿcÿcÿeÿgÿiÿlÿqÿrÿtÿ{ÿ’ÿšÿ–ÿ’ÿÿÿŠÿ‰ÿŠÿÿ‘ÿ‹ÿ…ÿ„ÿ‚ÿÿÿ~ÿÿ‚ÿˆÿ†ÿ€ÿ€ÿ‚ÿÿÿ{ÿxÿuÿpÿnÿmÿlÿlÿlÿjÿjÿkÿkÿjÿkÿjÿjÿmÿqÿxÿ‚ÿ‹ÿÿŒÿ‹ÿŒÿ‹ÿ‰ÿ‡ÿˆÿ‡ÿ‡ÿ…ÿ†ÿ†ÿÿ’ÿÿ‹ÿŠÿ‹ÿ‰ÿˆÿ†ÿ…ÿ„ÿ„ÿ…ÿ…ÿƒÿ‚ÿÿ…ÿ‹ÿ‘ÿ–ÿ•ÿ”ÿ”ÿ’ÿÿÿŒÿ98v99v99v87v77u66u55u54v44w44w44x33x33w33w44w44w44x44x66x66x55y66y66y66y66z66z66z66z65{66{4~4{4~4|4~3{3~3{2~2{2~2{1}1{1}1{1|1{1|1{1|1{/|/{/|/{/|/{-|-{/|/{0|0{0|0{1|1}0|0}0}0|/}/|0}0|/}/|/}/}/}.}.}-.}.€.}.-}-€-~-.~..~/0~13~57~9;~>€@BF‚J}O„TuY…_qe…imp„tlw‚soplqg„gnm…whz„}i€‚ƒmƒƒuƒ…z„‚y‚‚~rz‚vkusip€lnifuc~aw^~[yYVyR€OxM€JxH€FyC€>y:€8y6€3y3€0{//y5‚Yv`ƒWtX‚\ta~Cw&|)z,€0yA‡\s]ŠWpVˆVqY‡YrY‡_ws…qijˆk€mŒqljj’l}n–j|j—d{X“Y{YR|=Š:|>ŒB|FRySWxY‘YvY“VuS“OuO”KvJ•GxF”DzB’@{?<{;9{7Š4|0ˆ/|.‡-}-‡--†/8FKyF„FpC‡Sgr‰c^YŠ`YiŒiVszO}ŽIƒ‡DŒF”‡‹T2€r}!€ ~!ƒ!~"ƒ#~###ƒ"$ƒ$%ƒ$$ƒ""ƒ(~%…5~H†MRˆZdˆj€o‡ot…x}w‡i{c‰ezdŠdyg‰gyg‹kzn‰pzn‰o{j‹R|QX~W[\Ž_€cŒfhŠl€p‰s€u‡tn†j~d…k}y…y~s…pl‚l€m‚l€oƒr€pU~D|C{J|Qx]|mzu|x}r|no~pp}n€i|b|h~qys‚pymm{mm|l|u~}u„€€q|{py…hs7†%~'‚'„%%†'~(†&'…*~.„/~4‚1*‚$%‚-:‚DC‚@€3‚*€$$~$‚$~'„'},†9|Pƒg}|z‡†r„ƒl‚cy„nd_…Al)„(xÿ@ÿBÿDÿHÿLÿRÿVÿ\ÿaÿgÿmÿrÿvÿvÿsÿpÿlÿjÿnÿuÿyÿ{ÿ~ÿ€ÿ€ÿ€ÿÿ€ÿÿÿ|ÿzÿyÿuÿrÿoÿlÿjÿgÿeÿbÿ`ÿ^ÿ[ÿZÿWÿRÿOÿMÿJÿHÿFÿCÿ>ÿ:ÿ8ÿ6ÿ3ÿ3ÿ0ÿ.ÿ.ÿ1ÿIÿbÿ\ÿYÿ[ÿ`ÿTÿ(ÿ(ÿ+ÿ0ÿBÿ[ÿ]ÿWÿUÿUÿWÿWÿXÿ\ÿoÿvÿlÿnÿqÿuÿyÿqÿoÿqÿrÿkÿiÿkÿeÿ]ÿ\ÿZÿLÿ=ÿ=ÿ@ÿHÿNÿTÿXÿYÿ[ÿ[ÿYÿVÿSÿQÿOÿMÿIÿDÿCÿBÿAÿ>ÿ>ÿ=ÿ<ÿ:ÿ8ÿ5ÿ2ÿ0ÿ.ÿ.ÿ.ÿ/ÿ/ÿ1ÿ5ÿ9ÿCÿLÿIÿBÿ2ÿFÿ^ÿgÿ]ÿ^ÿdÿjÿsÿyÿÿ‚ÿ„ÿ†ÿŒÿÿ•ÿŠÿ2ÿÿ!ÿ ÿ"ÿ$ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ$ÿ%ÿ5ÿSÿlÿbÿNÿJÿWÿbÿlÿqÿxÿ{ÿoÿbÿaÿaÿcÿaÿaÿcÿcÿbÿgÿkÿlÿnÿaÿAÿEÿLÿRÿXÿYÿ]ÿbÿcÿdÿgÿmÿqÿuÿuÿwÿsÿmÿiÿiÿtÿ~ÿyÿqÿnÿkÿmÿmÿmÿpÿrÿcÿHÿEÿJÿUÿaÿrÿvÿxÿrÿoÿpÿpÿpÿnÿbÿ_ÿgÿqÿuÿpÿmÿkÿjÿhÿlÿrÿ|ÿƒÿ~ÿ}ÿ}ÿzÿeÿ1ÿ&ÿ&ÿ'ÿ%ÿ%ÿ&ÿ(ÿ(ÿ)ÿ)ÿ&ÿ%ÿ)ÿ2ÿ4ÿ,ÿ&ÿ%ÿ%ÿ.ÿ;ÿ>ÿ1ÿ&ÿ$ÿ%ÿ%ÿ(ÿ'ÿ(ÿ-ÿ?ÿVÿoÿÿˆÿ†ÿ„ÿƒÿ€ÿ€ÿyÿnÿZÿ8ÿ(ÿ0ÿ?ÿGÿJÿNÿQÿTÿYÿ\ÿ]ÿ[ÿ]ÿ]ÿ[ÿ[ÿ[ÿ[ÿ]ÿ^ÿ^ÿ_ÿ`ÿ_ÿ^ÿ]ÿ^ÿbÿcÿbÿcÿgÿjÿoÿvÿyÿ}ÿ}ÿ}ÿ€ÿ€ÿ{ÿuÿtÿtÿrÿsÿqÿoÿpÿ„ÿ”ÿšÿ–ÿÿ’ÿšÿšÿ”ÿÿvÿ{ÿƒÿ‰ÿ‹ÿÿ–ÿ™ÿ›ÿšÿ˜ÿ–ÿ–ÿ—ÿ˜ÿ•ÿƒÿ|ÿoÿ`ÿaÿbÿaÿbÿaÿaÿbÿcÿeÿgÿjÿmÿqÿsÿtÿÿ˜ÿ™ÿ•ÿ’ÿÿŠÿŠÿ‹ÿÿ‘ÿ“ÿŠÿ„ÿƒÿ‚ÿÿ€ÿÿÿƒÿˆÿ…ÿÿÿÿ€ÿ}ÿzÿuÿsÿpÿmÿmÿkÿkÿkÿkÿjÿkÿkÿiÿjÿiÿlÿnÿsÿ|ÿ‡ÿÿÿ‹ÿŒÿŠÿˆÿ†ÿ…ÿ…ÿ…ÿ„ÿ„ÿ‡ÿ‹ÿ‘ÿÿŒÿŒÿ‰ÿ‰ÿˆÿ‡ÿ‡ÿ…ÿ†ÿ†ÿ…ÿ…ÿ„ÿ„ÿˆÿÿ”ÿ•ÿ”ÿ”ÿ”ÿ“ÿ“ÿÿŽÿŒÿ<;s::t:€:u:€:u9~8t8~8u7~7u6~6v44u44u44v33w34w44w33x33x44y55y66y55y55x55x55y66y66y55y6~6z6~6z5~5{4~4{4~4{4~4{3~3z3~3z2}2{2}2{1}1{1}1{0|0|0}0|0{0{/{/{/|/{.|.{.|.{/|/{/|/{/|/{0|0|/|/|0}/|/}/|/}/|/}.|.}.}.}.}-}-|.}..~.-~--~-€-~-€-~.0~12~47~9:}=?AE€G€K‚N~R†Wt]…bmj…piu†wkvƒtlm„lioƒvgx‚{g{€~r~|zz||{{yz€xwv€qto€lrk€iue~czb~_z\~YyW€TxR€OyM€JyE€EyA€=y9€7z7€3z21z-.z/€9y]‚axYƒYu_~au6}&x+€0xC‡[u^ŠYqY‰YqZˆ[r[†_wk„w€qƒn…mp‡y€{xt‘w|o—h{e—fz^“WzTG|<>}BH}MŽRzVZx\‘[uZ’WtU“StP”LuH“DyA‘@z><|<Ž;|:Œ8~7‰6}4‡3~1†1€/…0€1„5€;ƒ?€;€;‚JzM…FrA‡Be]ˆgZ_Š`XdŠkYrŒyR€NƒŒ†IŒŒ‘L—‡ˆ\/w"~&‚#~$„$~%‚&#‚#€!‚$~$ƒ%~#„*~F‚hu„q€d…LAˆJ`‹t|Šy~g‰_|b‰^{_‰ayc‰cyc‹axa‹eyi‹k{T?}CJ}HOTW€Za€aŽ`dŒinŠtƒy†z‚w†t‚q…lo„y{†r~mƒm€mƒm€kƒm€mi€P}IzL|Wwf|v{x|u}q|qr}qp|m~\}^zfpwtƒqymƒkyk€i{k{s}tƒq{‚}sx„as,„'}('…((†'~(‡*})ˆ'}&†'(…-0ƒ0'ƒ((ƒ)~*…/€/…*)†'~&‡(|%†&|0†F}\‚s~ƒwˆˆpƒl€‚€gy„jhUƒ1r'‚5{EK~JL|P€RxTXxZ„\u\†\p\‡[o\ˆ]m]ˆ\l]†`m`ƒ_p`ƒ]n^†^m[†Yo\†`pe†lno…tjw‡yhz‡}i~‡xlrˆsmtˆumw…ust‚v|…€Œƒ‘}“„ |¬„¬}¢€•‚~z{xƒ€‡rŒ€h’‚•_š…ž`¡†b•„”b˜™a’‚ax€dsbz`Œ`ra’aob‘aoc“enh”kkn”rkt“tm…‹—s—|“~rŒ€ŒoŠ‚ŒqŽ‘q’‰vƒƒ~ƒ‚€€€€€„}ˆ}……€u€€o“{lw•vlu•qln•mll•kkj•jmj•kmk“kmk•jmk•ojw•jˆ’m‹‹w‹ƒŠ}ˆ}…„{ƒ€ƒ~ƒ~…}ˆ}Œx~ŽqŒŒoŠ€Šs‰ˆs†…u††|…~…‚ƒw…‰t’Š•u•ˆ”x”‡”y’†’{’Ž~Œ~=ÿ<ÿ;ÿ;ÿ;ÿ;ÿ:ÿ:ÿ9ÿ8ÿ8ÿ8ÿ7ÿ7ÿ6ÿ6ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ5ÿ5ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ0ÿ1ÿ2ÿ4ÿ7ÿ9ÿ:ÿ=ÿ?ÿAÿDÿEÿIÿLÿOÿSÿYÿ^ÿdÿkÿrÿvÿwÿwÿrÿpÿrÿsÿvÿwÿzÿ{ÿzÿ{ÿ{ÿzÿzÿzÿyÿwÿuÿrÿoÿlÿjÿhÿeÿbÿaÿ^ÿ[ÿYÿUÿRÿOÿMÿKÿHÿEÿBÿ?ÿ;ÿ9ÿ7ÿ6ÿ4ÿ1ÿ1ÿ.ÿ.ÿ/ÿ2ÿMÿaÿ[ÿYÿ[ÿbÿOÿ'ÿ+ÿ/ÿ=ÿXÿ`ÿ\ÿ\ÿ\ÿ^ÿ^ÿ`ÿaÿgÿwÿuÿoÿqÿwÿ‚ÿ‚ÿwÿvÿxÿrÿkÿhÿeÿUÿJÿOÿAÿ:ÿ=ÿ@ÿCÿKÿQÿUÿXÿYÿYÿYÿWÿVÿRÿPÿMÿJÿEÿCÿ@ÿ>ÿ=ÿ;ÿ:ÿ:ÿ:ÿ:ÿ8ÿ6ÿ5ÿ3ÿ2ÿ3ÿ3ÿ4ÿ<ÿ=ÿ;ÿ;ÿ=ÿ<ÿ>ÿSÿ]ÿZÿcÿiÿjÿcÿdÿdÿmÿsÿyÿ~ÿ€ÿƒÿˆÿÿ—ÿ—ÿƒÿ+ÿÿ(ÿ'ÿ&ÿ%ÿ&ÿ&ÿ$ÿ#ÿ%ÿ$ÿ!ÿ(ÿOÿnÿxÿiÿ`ÿcÿ^ÿKÿ<ÿAÿTÿnÿtÿdÿYÿ[ÿ]ÿ^ÿbÿbÿbÿfÿeÿ_ÿ`ÿhÿdÿOÿ=ÿ?ÿIÿCÿGÿHÿNÿUÿVÿUÿWÿZÿ`ÿhÿoÿtÿyÿ|ÿ{ÿxÿuÿqÿlÿtÿ|ÿtÿlÿlÿlÿkÿjÿhÿhÿhÿXÿKÿRÿ[ÿmÿwÿvÿqÿpÿqÿrÿqÿpÿeÿWÿ[ÿfÿoÿsÿpÿlÿkÿkÿjÿmÿtÿ}ÿƒÿÿ|ÿ|ÿwÿYÿ)ÿ'ÿ(ÿ(ÿ)ÿ)ÿ*ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ(ÿ*ÿ-ÿ4ÿ.ÿ(ÿ(ÿ)ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ*ÿ)ÿ)ÿ)ÿ3ÿKÿaÿtÿ…ÿˆÿ†ÿÿÿ‚ÿÿxÿfÿLÿ-ÿ,ÿ<ÿGÿJÿKÿLÿNÿQÿRÿVÿXÿYÿZÿZÿ\ÿ\ÿ\ÿ\ÿ^ÿ^ÿ\ÿZÿ^ÿ]ÿZÿ]ÿ_ÿ_ÿ\ÿYÿZÿ\ÿ^ÿbÿiÿoÿsÿtÿwÿyÿwÿqÿnÿpÿuÿuÿuÿuÿvÿwÿ‚ÿ†ÿÿ ÿ³ÿ¶ÿ®ÿ£ÿÿ€ÿ{ÿ„ÿŒÿ‹ÿ‹ÿ‹ÿ”ÿšÿ™ÿšÿžÿ ÿÿ–ÿ”ÿšÿ“ÿ‡ÿ|ÿrÿaÿbÿaÿaÿ`ÿ`ÿ`ÿbÿfÿhÿkÿnÿrÿtÿvÿˆÿ™ÿ•ÿ‘ÿŽÿŒÿŒÿŠÿŒÿŽÿ‘ÿÿ‡ÿ‚ÿ‚ÿ‚ÿ‚ÿ€ÿ~ÿÿ„ÿˆÿ…ÿ€ÿ€ÿ€ÿÿ{ÿwÿtÿqÿpÿmÿmÿlÿkÿjÿjÿjÿkÿkÿkÿkÿkÿmÿrÿwÿ€ÿ‰ÿÿÿ‹ÿ‹ÿˆÿ‡ÿ…ÿ„ÿƒÿƒÿƒÿ†ÿˆÿŒÿÿŽÿŒÿŒÿŠÿŠÿ‰ÿˆÿ†ÿ…ÿ†ÿ†ÿ…ÿ…ÿ…ÿŠÿÿ”ÿ•ÿ•ÿ”ÿ”ÿ”ÿ’ÿ’ÿ’ÿŽÿŒÿ==s>=t<€:z9€7z4€2z2/z.-z-€.z=^y^ƒYvY`va;x(€-y=‡XteŠ`p_‰^n_ˆcqi…iwg…s}}„u‚tƒ|…Š€…y}w•wzu—nxj—_xQ“JxPEz:=|@C|GŒN{QŽVxVWuV‘UsU“RrP”MsI“FvD‘Az@>|<:|:‹:~:ˆ:7†54†4€5…7€9„=€<ƒ<€;ƒ=6ƒ,‚3L†^rfŠg_iŠnUn‰dTb‹jWqŒwW{Œ€T„Œ†RŒ”[—†…g&€!y)~')~'ƒ&%ƒ$#‚"~5qyr€k€e_ƒ]€X†N?Œ>~QR~VŽY}YY|\‹^{bŠbze‹ezeŒeyfŽWzDŽ=|}IK|KMyP€PxQRxUƒWvX„YtZ‡Zp\ˆ]o_‡^n]†]n]†]r\…\s]†^rZ†WqX†Yp[†[q`…ipp‡pnqˆupqˆgsh†mto†stv…xww‚zx‹|®€³€«}žŽy…ƒ„z‚ƒw‚ƒo‚‚‚eŠ…—`œ…œ_›…Ÿb¡…œd–ƒ’b˜‚`‚~lf}`‚_u`‘`pa’`oa“dnh”mkp”rkt“wmŠ‹˜v•|’~rŒ€ŒoŒ‚o’oŽƒw€‚‚€€~€„}ˆ}……t‘€n}”{lw•slq•nlm•llk•kkk•kmk•kmk“lml•kmm”qjx”‚i‰‘o‹Š‹w‹ƒ‡}…}……}ƒ€ƒ~ƒ~‡z‹}uŽ~qŒ‹o‰€ˆqˆ‡u††y†…€…y„†‡sˆ”u–ˆ•v”‡”x”‡“y‘†‘{‘Ž~‹~@ÿ@ÿ?ÿ=ÿ=ÿ=ÿ<ÿ<ÿ;ÿ;ÿ9ÿ9ÿ8ÿ8ÿ7ÿ7ÿ7ÿ7ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ4ÿ4ÿ3ÿ3ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ.ÿ/ÿ/ÿ/ÿ/ÿ-ÿ-ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ/ÿ/ÿ.ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ0ÿ2ÿ4ÿ6ÿ8ÿ:ÿ=ÿ?ÿAÿCÿEÿIÿLÿMÿNÿRÿVÿ[ÿ`ÿgÿlÿpÿvÿxÿxÿtÿrÿtÿvÿvÿvÿwÿwÿuÿuÿvÿvÿuÿtÿsÿnÿmÿkÿgÿfÿeÿ`ÿ_ÿ]ÿXÿUÿQÿNÿLÿJÿGÿDÿBÿ?ÿ=ÿ;ÿ:ÿ8ÿ6ÿ4ÿ1ÿ/ÿ.ÿ-ÿ-ÿ-ÿ2ÿQÿ`ÿ[ÿZÿ^ÿbÿRÿ*ÿ,ÿ9ÿUÿdÿcÿdÿdÿeÿlÿpÿpÿiÿrÿ~ÿzÿ}ÿ‡ÿÿŠÿ|ÿvÿ|ÿuÿnÿfÿ`ÿUÿKÿ=ÿ:ÿ9ÿ<ÿ?ÿAÿFÿHÿMÿRÿSÿTÿSÿTÿSÿRÿPÿMÿJÿGÿEÿBÿ@ÿ>ÿ=ÿ;ÿ:ÿ;ÿ:ÿ7ÿ5ÿ4ÿ4ÿ4ÿ5ÿ6ÿ;ÿ>ÿ=ÿ<ÿ;ÿ;ÿ3ÿ/ÿ+ÿ*ÿ<ÿYÿfÿgÿjÿrÿtÿhÿcÿdÿkÿtÿzÿ~ÿ‚ÿŠÿÿ”ÿ˜ÿ‡ÿ,ÿ"ÿ)ÿ)ÿ'ÿ'ÿ'ÿ#ÿÿ@ÿrÿvÿnÿjÿjÿkÿiÿjÿfÿZÿAÿ8ÿIÿMÿEÿOÿUÿVÿYÿ\ÿaÿcÿdÿeÿgÿ^ÿCÿ4ÿEÿ?ÿAÿQÿKÿEÿHÿHÿIÿLÿNÿSÿ]ÿfÿlÿrÿtÿtÿsÿtÿuÿxÿyÿxÿvÿxÿ}ÿ}ÿuÿmÿjÿlÿhÿfÿfÿcÿaÿWÿZÿiÿuÿwÿsÿpÿrÿsÿtÿpÿhÿVÿTÿXÿaÿlÿrÿnÿjÿkÿiÿjÿnÿuÿ~ÿÿ|ÿÿ}ÿrÿKÿ$ÿ'ÿ)ÿ)ÿ)ÿ(ÿ)ÿ(ÿ(ÿ*ÿ*ÿ*ÿ)ÿ*ÿ'ÿ)ÿ0ÿ3ÿ-ÿ'ÿ'ÿ(ÿ*ÿ*ÿ*ÿ*ÿ-ÿ0ÿ3ÿ3ÿ9ÿGÿ\ÿsÿ…ÿ‡ÿ…ÿ‚ÿ‚ÿÿÿ|ÿrÿ`ÿAÿ+ÿ3ÿBÿJÿKÿKÿMÿPÿPÿPÿPÿRÿTÿVÿYÿYÿYÿZÿ\ÿ_ÿ_ÿ_ÿ]ÿ\ÿ\ÿ[ÿYÿ_ÿ^ÿ[ÿTÿUÿYÿ_ÿ`ÿZÿbÿjÿjÿmÿoÿdÿZÿ`ÿgÿnÿsÿwÿyÿ|ÿ€ÿ…ÿ‘ÿ ÿ¨ÿ©ÿÿ•ÿŽÿ‡ÿ‡ÿ‡ÿ„ÿÿÿÿ‚ÿ‡ÿÿ•ÿœÿ›ÿ™ÿžÿŸÿÿ•ÿ—ÿ‘ÿŠÿ~ÿoÿ_ÿ`ÿ_ÿ_ÿ`ÿ_ÿcÿfÿkÿmÿpÿpÿuÿvÿ‹ÿ˜ÿ•ÿ’ÿÿŒÿŒÿ‹ÿ‹ÿŽÿ“ÿÿ…ÿ‚ÿÿ‚ÿ‚ÿÿ€ÿÿ„ÿˆÿ…ÿÿÿÿ~ÿyÿuÿrÿpÿlÿlÿlÿkÿkÿkÿkÿkÿkÿkÿlÿlÿlÿoÿrÿyÿ‚ÿ‰ÿÿ‹ÿ‹ÿ‹ÿ‡ÿ…ÿ…ÿ…ÿ„ÿ„ÿ†ÿŠÿÿÿÿÿŒÿŠÿ‰ÿˆÿˆÿ‡ÿ†ÿ†ÿ†ÿ…ÿ„ÿˆÿŽÿ“ÿ–ÿ–ÿ•ÿ”ÿ“ÿ“ÿ’ÿÿÿÿŽÿ‹ÿA@rACsB~@t?~>t<~~@€BDFIL€M|QUwX„]qd†hll†qgu†xbt„r^tƒt_uuiu€sssszrt~rr~o~l~j~h}f~d|b_{[Y{VS{PLyJ€HyF€CyB?{>:{87z64z1~/{-~,{,,{-Ay_ƒ]wYƒZv^€bv=)w7†QufŠfri‰kon‡qsu†t|p…t|„‚‚ŠˆŠ{pwm–uvs•luj”_uX“Hv78y9Ž:{<={AE{KPwQŽPuP‘QsP’OsN”OsK”FuB’@y?>{=Œ;};‰98ˆ8~5…43„23…7€=„>€>„<<„8~1†.|.‡.€+ƒ0…JycŠhfhŒqWq‹oVg‹fZmŒs\xŒ}\ƒŒŠY‰“_–„k(!|'+(~'‚"~3suokkmmlƒoo†[}CŒ8}<ŽO~DŽJ}UX|Z‹[|\‹^{^‹[{L9{45|86}EM~I’B~I”J~J“LT\‚c‹g‡iˆm‰p†rŠq…p‰p…u‡x„z…zƒzƒ}…}u…n€h…jg…c~a…a~bƒ\|`~p|z~x}q}qq~r€s|lbzP}PzWx`jvp„kwj„lyj€j|q}yv|q~‚€pqƒCw&„''(ƒ''†(~)‡*})‡)|*ˆ*|)ˆ(|*ˆ-~1„50'(‚(~)‚)}*…,}2…3{4…>{R‚f}zy‡‰n…mƒ‚j}…yfo…]l:„+w9E}I‚M}O‚O{OPyOOwP‚RwRƒVxW„YsZ†[q^‡^p_†`p]†YsX„XtW…]r[…TpS…UqY…erd…_qd†ephˆkq]ˆVu^†fxm…txz„{t‚‡pŒ‚”s‚¡u ƒžu—ƒ“s‹„‹s‹„†o†‚ŠjŠƒŠc‡†‡^‰†^›…šb˜…ež„˜g–‚’fƒiy€dwaz`‰asaapdgni‘lko”sjw’zn’‰˜x’z‘€ŽrŒ‹q‰‚‰pŒ’p‹„xƒ‚€€€~€„|Š|…†u|my“wlt•qlp•okm•kkj•kmk•jmj•jnj•jlj•kjn•si}“…j‹‘qŠ‹‹yŠ‚‡|…~„~„~…}ƒ~†}‹v~ŽsŽŒnŠ€‰oŠ‚Šr‡†t†‚†{…}ƒ„…x‹ˆ’u”ˆ•x•ˆ•y”ˆ’y‘‰y†’{‘‚~ŒBÿAÿAÿ@ÿ?ÿ@ÿ?ÿ>ÿ>ÿ;ÿ;ÿ:ÿ:ÿ:ÿ8ÿ8ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ0ÿ1ÿ1ÿ1ÿ1ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ1ÿ2ÿ3ÿ4ÿ6ÿ8ÿ;ÿ=ÿ@ÿCÿEÿEÿGÿJÿLÿPÿSÿVÿZÿ]ÿcÿgÿlÿqÿuÿuÿqÿqÿqÿpÿpÿrÿrÿpÿpÿqÿpÿmÿmÿlÿjÿhÿeÿcÿaÿ^ÿZÿXÿVÿTÿQÿMÿKÿHÿFÿEÿBÿAÿ>ÿ<ÿ:ÿ7ÿ6ÿ4ÿ2ÿ0ÿ.ÿ,ÿ+ÿ+ÿ+ÿ-ÿ3ÿOÿ`ÿVÿUÿYÿ`ÿWÿ,ÿ2ÿJÿaÿkÿlÿmÿqÿvÿxÿvÿqÿqÿsÿ€ÿÿmÿtÿlÿnÿvÿiÿeÿhÿ^ÿXÿWÿBÿ8ÿ:ÿ:ÿ9ÿ9ÿ:ÿ=ÿAÿHÿNÿPÿMÿKÿLÿNÿOÿOÿNÿLÿHÿCÿ?ÿ<ÿ;ÿ:ÿ8ÿ7ÿ7ÿ8ÿ8ÿ5ÿ4ÿ3ÿ2ÿ5ÿ:ÿ?ÿ@ÿ>ÿ<ÿ<ÿ7ÿ1ÿ/ÿ/ÿ0ÿ/ÿ.ÿ+ÿ5ÿSÿjÿjÿjÿuÿrÿhÿeÿjÿrÿ{ÿ€ÿƒÿ‰ÿ‹ÿÿ’ÿvÿÿ$ÿ(ÿ'ÿ(ÿ&ÿjÿwÿmÿhÿjÿpÿrÿtÿtÿxÿrÿZÿGÿ<ÿ7ÿGÿIÿEÿSÿWÿYÿZÿZÿXÿEÿ6ÿ5ÿ7ÿ:ÿ8ÿ1ÿ:ÿDÿBÿ@ÿBÿDÿHÿIÿQÿ]ÿdÿgÿfÿgÿhÿgÿiÿnÿnÿpÿrÿvÿxÿwÿvÿyÿzÿuÿoÿjÿhÿeÿaÿ_ÿ`ÿaÿ_ÿdÿsÿwÿvÿsÿqÿrÿrÿpÿiÿYÿNÿOÿUÿ`ÿiÿlÿiÿjÿlÿjÿjÿqÿ|ÿÿÿ|ÿ€ÿÿmÿ<ÿ%ÿ'ÿ(ÿ)ÿ)ÿ)ÿ*ÿ+ÿ*ÿ+ÿ*ÿ*ÿ+ÿ+ÿ*ÿ+ÿ+ÿ/ÿ4ÿ7ÿ1ÿ&ÿ'ÿ(ÿ)ÿ)ÿ,ÿ1ÿ8ÿ<ÿIÿ\ÿrÿƒÿŒÿ‰ÿ…ÿ„ÿ‚ÿÿ€ÿwÿjÿVÿ4ÿ,ÿ:ÿDÿIÿMÿOÿOÿPÿRÿQÿOÿNÿOÿPÿRÿUÿYÿZÿ]ÿ^ÿ_ÿaÿ`ÿ]ÿYÿWÿUÿUÿ\ÿ_ÿ]ÿYÿSÿOÿNÿaÿgÿgÿfÿgÿfÿZÿYÿaÿgÿrÿvÿ|ÿ~ÿƒÿ‰ÿŽÿ‘ÿ—ÿ›ÿ›ÿ—ÿÿ‹ÿ’ÿ”ÿ”ÿ‘ÿ˜ÿ ÿ¢ÿžÿšÿ’ÿ‹ÿŠÿ‘ÿ—ÿ˜ÿ–ÿšÿ›ÿ™ÿ’ÿ‡ÿˆÿ‡ÿtÿfÿeÿdÿdÿeÿgÿiÿlÿmÿpÿsÿwÿ~ÿ•ÿ™ÿ“ÿ‘ÿŽÿŒÿ‹ÿŠÿŠÿŽÿ‘ÿŠÿƒÿƒÿƒÿÿÿÿÿÿ„ÿ‰ÿ„ÿÿÿ~ÿ|ÿvÿsÿqÿmÿlÿlÿmÿlÿkÿiÿjÿjÿjÿjÿjÿjÿlÿoÿuÿÿ…ÿ‹ÿŒÿŒÿ‹ÿ‰ÿ‡ÿ…ÿ„ÿ„ÿ…ÿ†ÿ‰ÿÿÿÿŽÿŒÿÿŒÿŠÿŠÿˆÿ†ÿ…ÿ†ÿ„ÿ„ÿ‰ÿÿ”ÿ•ÿ•ÿ•ÿ•ÿ”ÿ”ÿ’ÿÿÿ’ÿ‘ÿÿŒÿFFoEDqD~CtA~?t?~?t<~:u9~9v8~8w7~7w7~7w77v55x33w33w44w44w33x44x55x55x66w66w66x55x55w55y66y5}5y5~5z4~4z3~3z3~3z2~2z2~1z1~1z0~1z1~1{1~0z0}0z/|/z/|/{.|.{.|.{-|-{,|,{,|,{-|-{.|.{/|/}-|-}-}-{-}-}.~.~.~.~.~.~.~.~-}-~.}.~.}..}..~.~.~.~.~//~11~2€3~5€6~;€;~>>@CE€GI€N€Q~TƒWyY…^rc†hkn†nfo…oam„m^n‚ndoopmmzmk~kj~h~g~e~b}a~^|[YzTRzQNzKI{H€EzC€Az>>{;8{66z31z/~.{,},{+~+{,€.y<‚\yX‚SwT‚\w_Ax)ƒ=yT‰fylŠoys†{}z„v€olƒg€i†n{[Šbxq†rŒxog”[oO–SrS”Bu:‘:y;Ž9{97{7Œ;{CŒJyMŽKvH‘FsJ’KsM”LsK”EuB’€B„B€@„><„5|1†1{1‡1|0†0/†,†'}@Š]rf‹lcm‹pZm‹j[m‹v^y‹~_ƒ‹ˆ`ŒŠŒf‘…Or€#}(€&Q€zqkj‚p€uw{„}~`†V~GŒ;~3;}B;}?ŽI|UZ|O5|)6{;9{=@|=;|;>}B‘D}G’IQZƒc‹h†iˆfˆaˆbˆcˆ`Š_‰eŠiˆkˆo‡q†s‡t„sˆr‚q‡q€j…d`…_~^…`~_ƒc|hw}xtt€u€rq€o}dR{M~NzTy_fxh„jym„kyik|r~|~„w€}q€‚pm„@v2„.~*+„++…+~*†**‡*~+ˆ,}-ˆ-},ˆ-~-…08ƒ</‚&~(‚)},ƒ.}.ƒ9|AƒS|g|y}‰q‰l…‚„l‚ƒj~ƒuhd„Hp-ƒ+y:CG‚K|N‚OyP‚RxS‚RyO‚NyNƒOzRƒSxX„[v]†_t_‡_q^‡ZtV…TvQƒZt`ƒcn_…ZmW…TlU…_kg†iji‡ej[‡_nh…npu„zp~„‚n‡ƒŠlƒl‘ƒ–l”„m„m™…¤n¤…¢ož†œlš‡šišˆašˆ\Œ‡Ž]”…”c˜ƒ›g–ƒ‡h~ƒ‹h†‚}olh}k{i…iwkŠmtn‹rqsupw‹‚s—„˜z“x‘€ŽqŒŒpŒ‚p“p‰ƒx‚ƒ€€€~€„~‡|‚‡~s~‘{lw•ulr•oll•lkl•kkj•imk•imi•ili•jkj•mjp•wh“‡kŒ‹sŠ‡‹{‰†}…~„~„„}‰{‹}t’~p€ŒnŒo‰‚‰s‡…w…†€„z††Œw“ˆ•v•ˆ”y”ˆ“z‘ˆy‰y†”{’‚Ž~‹HÿHÿFÿEÿCÿCÿBÿBÿAÿ?ÿ>ÿ>ÿ<ÿ<ÿ:ÿ9ÿ8ÿ8ÿ7ÿ7ÿ7ÿ6ÿ5ÿ5ÿ4ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ4ÿ4ÿ6ÿ6ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ0ÿ1ÿ2ÿ3ÿ5ÿ7ÿ8ÿ:ÿ;ÿ=ÿ?ÿBÿEÿGÿHÿKÿOÿRÿVÿXÿYÿ]ÿbÿeÿgÿiÿkÿkÿkÿiÿiÿjÿjÿiÿiÿiÿhÿhÿfÿeÿdÿbÿ_ÿ^ÿ[ÿWÿUÿSÿPÿNÿLÿJÿHÿFÿCÿAÿ?ÿ=ÿ<ÿ8ÿ8ÿ6ÿ4ÿ2ÿ0ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ2ÿMÿ_ÿVÿQÿWÿ[ÿWÿ,ÿ0ÿ=ÿPÿ^ÿhÿoÿuÿzÿtÿmÿhÿ_ÿWÿQÿJÿZÿrÿ…ÿŽÿ†ÿ€ÿsÿfÿeÿ`ÿIÿ=ÿ9ÿ;ÿ9ÿ9ÿ7ÿ7ÿ8ÿ>ÿCÿIÿHÿEÿEÿFÿHÿIÿHÿGÿCÿ@ÿ<ÿ:ÿ8ÿ5ÿ5ÿ4ÿ5ÿ6ÿ5ÿ3ÿ3ÿ5ÿ7ÿ=ÿCÿDÿBÿBÿ=ÿ9ÿ4ÿ2ÿ2ÿ2ÿ0ÿ1ÿ0ÿ.ÿ.ÿ/ÿ,ÿ+ÿDÿcÿjÿmÿpÿqÿmÿpÿsÿvÿ~ÿ€ÿ…ÿˆÿ‰ÿwÿ ÿ$ÿ"ÿBÿ{ÿqÿhÿjÿoÿvÿyÿ|ÿ‚ÿwÿ\ÿVÿVÿIÿ9ÿ2ÿ5ÿ7ÿ8ÿ7ÿ6ÿ;ÿ8ÿ,ÿ0ÿ5ÿ6ÿ7ÿ9ÿ:ÿ>ÿ>ÿ>ÿEÿHÿLÿQÿTÿZÿ[ÿaÿiÿjÿfÿaÿ^ÿcÿfÿ_ÿZÿ\ÿ_ÿaÿbÿfÿoÿqÿmÿlÿnÿqÿjÿdÿ`ÿ_ÿ^ÿ^ÿ]ÿaÿlÿvÿtÿqÿqÿrÿqÿoÿoÿ`ÿLÿKÿOÿUÿ]ÿcÿeÿkÿmÿjÿjÿnÿuÿ}ÿ†ÿ‚ÿ‚ÿ„ÿÿrÿRÿIÿEÿ<ÿ0ÿ,ÿ,ÿ+ÿ*ÿ+ÿ+ÿ*ÿ+ÿ.ÿ.ÿ,ÿ+ÿ-ÿ-ÿ.ÿ2ÿ:ÿ9ÿ2ÿ)ÿ-ÿ8ÿ>ÿ7ÿ=ÿMÿbÿtÿƒÿŒÿÿ‰ÿ…ÿ„ÿƒÿ‚ÿ€ÿuÿeÿJÿ/ÿ'ÿ0ÿ>ÿFÿJÿKÿNÿQÿPÿPÿOÿOÿNÿMÿNÿNÿOÿRÿXÿ]ÿ]ÿ^ÿ_ÿ^ÿ[ÿWÿTÿSÿWÿ_ÿfÿhÿdÿcÿbÿeÿjÿkÿlÿnÿjÿcÿiÿpÿvÿ|ÿÿ‚ÿ‡ÿ‹ÿŠÿŠÿ‹ÿ‹ÿÿÿÿÿ“ÿšÿ¢ÿ£ÿ ÿšÿ˜ÿ™ÿšÿ—ÿ•ÿ–ÿ–ÿŽÿŠÿŽÿ‘ÿ’ÿ“ÿ“ÿ•ÿÿ‡ÿ„ÿ‚ÿvÿpÿnÿmÿoÿoÿqÿrÿsÿwÿxÿyÿ…ÿ—ÿ–ÿ’ÿÿÿŒÿŒÿÿÿÿ“ÿ‰ÿƒÿ‚ÿƒÿÿÿÿÿÿ„ÿˆÿƒÿ~ÿ~ÿ{ÿwÿuÿrÿpÿmÿlÿlÿjÿiÿiÿhÿiÿiÿiÿiÿjÿjÿmÿpÿxÿÿˆÿÿ‹ÿ‰ÿŠÿˆÿ…ÿ…ÿ„ÿ…ÿ‡ÿ‹ÿÿ“ÿ’ÿÿÿÿŽÿŒÿŠÿ‰ÿˆÿ‡ÿ…ÿ„ÿ„ÿ‰ÿÿ”ÿ•ÿ•ÿ”ÿ“ÿ’ÿ’ÿÿÿÿÿ’ÿÿÿ‰ÿMMoIHqG~FrD~BsC~As@~?v=~€@B€EH€HM€OR~VXxZƒ\p`„bkd…efd…ddb‚che€gpfezef}ec~ba}^[}ZX{URyPOzLJ{HH{EA{A>{;;{8~6|42|1.{+~+{+~+{+~+{,~-{,€}42}35}65|43|27{88{::{9’9{C‘J}N‘PT‘\ƒ_Ž`…^‹e‰jˆh‰e‡`ŠcˆgŠh‰eŠc‹_‰]ŒYŠPP…`Šo„mˆjl†q~k†b}]…\}]ƒ^}\ƒ^|o‚v}qopqr~ql{RLyL|OzSzX~\welxlƒgxi‚m{vz€~†r……lˆ‚†kyƒlmc‚[rS‚D{4-++„++…++‡+~,‡+~+‡+~+‡,~.…4€;„=€033€:~>D|Z|n}sŒ~m€‰kˆ‚ˆm†ƒ„k{uij]k[‚WoLEqA‚DvFHyN‚OzNƒO{NƒL}J‚D}@‚I}LT}Z‚Z|Y„Xx\‡\y[…WxYƒ^uf„lmo„ofp„r_s…s]t…q]s†r]q†s_w…{e‚…ƒh†„‹gƒŒf‹„Šeˆ„ˆgŠ…Œj‘…“m—†šp™†™m”†“k–†˜g˜†–d“†‘`†_Ž†ŽaŽ„’h”„i†ƒ…jŠƒ„m}ƒwsu‚syt€v|w}w€x{wƒzy|ƒŠy•}”~’p‹p‹‹p‹Žo‘qˆ‚‚x‚€‚~‚€~~~}„~ˆ{‰~r|’ynw•tkq•pkn•lkl•jlj•jmi•imi•hlh•kkk•ljq•yiƒ“ŠmŒ‰vˆƒŠ|‡~…~…~…~‡~‰~Œy‘•r’o‚p‚‹qŠˆt†„|ƒ{…ƒ‰wŽ‡”u•‡”x”ˆ“z’‰‘zŠx‰x†’z‚Ž~‹NÿNÿLÿKÿIÿHÿEÿDÿEÿCÿCÿAÿ?ÿ=ÿ<ÿ<ÿ:ÿ9ÿ9ÿ6ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ.ÿ-ÿ-ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ0ÿ0ÿ2ÿ3ÿ4ÿ7ÿ7ÿ9ÿ:ÿ<ÿ>ÿ?ÿAÿCÿGÿJÿMÿQÿSÿUÿUÿVÿXÿ\ÿ\ÿ^ÿ]ÿ]ÿ^ÿ_ÿaÿcÿbÿaÿ`ÿ`ÿ_ÿ`ÿ^ÿ]ÿZÿWÿVÿTÿRÿOÿOÿKÿIÿGÿFÿCÿAÿ?ÿ>ÿ<ÿ;ÿ9ÿ8ÿ5ÿ2ÿ1ÿ.ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ-ÿ,ÿ-ÿ1ÿGÿ_ÿWÿQÿZÿdÿ_ÿ0ÿ(ÿ0ÿ:ÿMÿXÿ[ÿcÿeÿdÿ^ÿXÿYÿZÿRÿJÿNÿlÿzÿtÿpÿoÿoÿrÿbÿQÿAÿ<ÿ5ÿ4ÿ4ÿ2ÿ1ÿ/ÿ1ÿ4ÿ5ÿ5ÿ4ÿ6ÿ7ÿ8ÿ;ÿ<ÿ>ÿ<ÿ:ÿ;ÿ9ÿ6ÿ4ÿ3ÿ3ÿ2ÿ1ÿ5ÿ8ÿ?ÿBÿEÿFÿFÿEÿBÿ>ÿ9ÿ3ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ5ÿ0ÿ/ÿ=ÿZÿiÿnÿnÿyÿ}ÿ{ÿuÿeÿRÿTÿ8ÿ'ÿ/ÿlÿtÿiÿhÿlÿoÿwÿ~ÿ|ÿgÿWÿWÿZÿ[ÿXÿRÿAÿ3ÿ4ÿ6ÿ8ÿ<ÿ3ÿ/ÿ1ÿ6ÿ6ÿ6ÿ:ÿ>ÿ?ÿBÿJÿMÿOÿTÿWÿWÿ[ÿ_ÿ_ÿbÿfÿgÿfÿeÿdÿeÿeÿhÿeÿaÿ[ÿYÿRÿEÿ@ÿQÿfÿjÿjÿkÿpÿiÿ`ÿ[ÿ[ÿ\ÿ\ÿ\ÿeÿyÿzÿpÿpÿqÿsÿqÿpÿbÿJÿHÿKÿNÿPÿUÿ\ÿeÿoÿlÿiÿkÿpÿ{ÿ†ÿˆÿ‰ÿŠÿ‰ÿ‰ÿ…ÿÿyÿpÿdÿ[ÿIÿ6ÿ,ÿ+ÿ+ÿ+ÿ*ÿ+ÿ+ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ,ÿ/ÿ5ÿ>ÿ7ÿ3ÿ5ÿ=ÿBÿQÿiÿzÿˆÿŽÿÿÿ‰ÿˆÿˆÿ†ÿƒÿ}ÿtÿjÿ\ÿ]ÿaÿfÿfÿeÿWÿJÿFÿGÿLÿLÿJÿKÿGÿAÿ.ÿ*ÿ>ÿBÿFÿVÿXÿXÿYÿ\ÿ`ÿ_ÿ]ÿ^ÿeÿmÿrÿsÿsÿtÿvÿyÿyÿxÿxÿwÿvÿxÿzÿ}ÿ‚ÿƒÿ†ÿˆÿÿŒÿŒÿ‹ÿ‹ÿ‰ÿ‰ÿˆÿ‰ÿŒÿÿ“ÿ•ÿ–ÿ–ÿ’ÿ‘ÿ’ÿ“ÿ“ÿ“ÿÿÿÿÿÿÿ‹ÿ’ÿ–ÿ•ÿ”ÿ“ÿ”ÿ‘ÿŒÿ†ÿ{ÿwÿyÿzÿzÿzÿ|ÿ|ÿÿÿ‹ÿ“ÿ“ÿÿŽÿŠÿŠÿŠÿŠÿŒÿÿ‘ÿ…ÿ‚ÿ‚ÿ‚ÿ‚ÿ€ÿ~ÿ~ÿ€ÿ‚ÿˆÿÿ~ÿ{ÿyÿwÿrÿqÿpÿnÿlÿkÿiÿiÿiÿiÿiÿiÿhÿhÿjÿkÿlÿqÿ{ÿ…ÿ‹ÿŒÿ‰ÿ‰ÿ‰ÿ†ÿ…ÿ†ÿ‡ÿ‹ÿŒÿ‘ÿ“ÿ“ÿ’ÿÿÿÿÿ‹ÿŠÿˆÿ†ÿ„ÿƒÿ†ÿŽÿ”ÿ•ÿ•ÿ”ÿ“ÿ’ÿ‘ÿÿÿÿÿÿÿ“ÿ‘ÿŽÿ‹ÿONnNMoK~JpH~FrE~DrC~Bu@~?w>~=v;~;x9~7x76y77y55y55y55y44y44x44x44x44x44x44x44x44x3~3x3~3x3~4y5~4y4~3z3~3z3~3z3~3z2~2z2~2z2~2{1~1{1}1y0}/z/}/z.|.y.|.y-|-y-|-y,|,y,|,{,|,{,|,|,|,|-|-},|-}-}-|-}-|-}-|-}-}-}-~-}-~-~-~-~-~-~-~-~-~.~.~.~.~.}-~.}-~0~0€1~4€4~57~89:€;=€?A€FG€KLOP{QQzS‚VuWƒXqXƒXpXZr[€]x]\~]\\\~ZX}UR|RP{OLzJJ{GE{CA{A?{;9{76{4~3{2~0{,~,{,~,{*~*{,~,{,~,{,-z7€Ux[‚SxU‚]yeO|&|,€4€@‚MSU‚YX€T‚RV‚YƒXUƒR~]~l„mxkŒfrh’iob“XrK‘8v-Œ.z-ˆ+~*†(~(…'~'„'~'…'|*†-|1‡4|8‡<|AˆC|D‡D}E†G~GƒC~@‚A€E‚FG‚GG‚FE‚A;„4}0…0|0†0{1…5z5†7|6†6}6‡5~5…6€3„3„.0ˆIwb‹gjsŒ{a|Œ`xˆXk …$y+k}vl}nq€o€r‚ysƒc~W…\~\†[}^‡\}UŠA}5Œ5|65|30}-/}4D}IF~FŽXdT€QV‚ZŒZ„YŠ^…^Šbˆd‡eŠd‰`Š`‰`‰dŠc‹dŒc‹\W‰TMˆ?:ƒCU‚bŠg€g‡k~g…_|Z…Y|[ƒ]}]ƒn}~ƒwp‚p€rq€q~sX{FEyK|NzQzW~]weƒjxl‚mzr|x}€q‡ic‚Ža‚‰cˆƒdsƒkh^ƒLt4ƒ-~,,„,-†,,†-+†+~+‡+~+…-€1…6€:‚;;€B~LZ}rvŽl‚ŽjŒ„Šk‡‚‡j†ƒi|…seh…`h]…blf„ckb„bjY„GlO‚MoG‚>r9‚=w?9y#/}3~.€GX}Z‚\y_„cvd…crfƒklr„vew…y\y„yVy…zT|…~T}†|T}†€Vƒ†…[†…†dŠ„Œh„jŽ…Œm…Žn…p…p‘…‘q’…’n‘†‘m‘†i†Že†a†`Œ†Œc…f‘…‘h„Œi‹„Ši‹„Šg„ƒ€h„l€„€q€‚€vƒ€„vŒ’rŽmŒ‚Šl‰‚‰l‰‚Œm‚Žo„‚‚y‚€€~~~~‚ˆ{Š~rz’xnv•rkp•pkn•lkk•ili•hmf•hmi•ili•ikk•mis•}h‡“‹mŒ‹‰w‰‚ˆ}†~…~†‰|‹}}’v”’q‘p‚Žq‚‹sŠˆv‡…}‡{ˆ„’w–†•w“‡’y’ˆ‘y‘‰yŠŽwŽ‰w†‘z‚Ž~‹PÿOÿNÿNÿMÿKÿJÿHÿGÿEÿDÿBÿ@ÿ>ÿ>ÿ=ÿ<ÿ;ÿ9ÿ8ÿ7ÿ6ÿ7ÿ7ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ4ÿ5ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ0ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ.ÿ.ÿ/ÿ/ÿ0ÿ3ÿ3ÿ4ÿ5ÿ7ÿ8ÿ9ÿ:ÿ;ÿ=ÿ@ÿDÿEÿIÿJÿLÿNÿMÿOÿOÿPÿQÿRÿRÿRÿUÿWÿYÿZÿYÿXÿYÿXÿXÿWÿVÿTÿQÿOÿNÿMÿLÿJÿIÿHÿFÿDÿBÿ@ÿ>ÿ;ÿ9ÿ7ÿ7ÿ5ÿ3ÿ1ÿ/ÿ.ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ/ÿCÿ_ÿWÿSÿXÿcÿhÿ>ÿ*ÿ-ÿ3ÿ@ÿLÿPÿLÿLÿLÿMÿNÿRÿVÿYÿVÿTÿgÿkÿkÿhÿcÿbÿbÿ^ÿ[ÿJÿ+ÿ(ÿ'ÿ&ÿ%ÿ#ÿ#ÿ!ÿ!ÿ"ÿ"ÿ#ÿ$ÿ&ÿ)ÿ0ÿ9ÿEÿNÿQÿRÿPÿOÿOÿNÿMÿJÿIÿIÿHÿGÿGÿFÿCÿAÿ<ÿ7ÿ1ÿ0ÿ0ÿ0ÿ0ÿ1ÿ5ÿ6ÿ7ÿ6ÿ6ÿ6ÿ4ÿ4ÿ5ÿ5ÿ5ÿ4ÿ1ÿ0ÿ5ÿIÿ^ÿoÿwÿyÿyÿeÿ0ÿ*ÿiÿzÿmÿnÿmÿnÿrÿxÿwÿ\ÿXÿZÿ[ÿ\ÿ]ÿaÿ_ÿ_ÿGÿ6ÿ5ÿ5ÿ3ÿ1ÿ-ÿ.ÿ0ÿ4ÿFÿJÿIÿLÿeÿlÿSÿSÿSÿRÿSÿWÿ]ÿ]ÿaÿcÿbÿ_ÿ\ÿ\ÿ\ÿ]ÿ_ÿ^ÿZÿYÿQÿNÿCÿ:ÿ7ÿ=ÿOÿ`ÿgÿ`ÿiÿjÿ`ÿ\ÿ\ÿ\ÿ[ÿaÿxÿ~ÿuÿrÿpÿsÿqÿqÿlÿLÿBÿEÿIÿMÿQÿWÿ^ÿdÿjÿnÿuÿyÿÿ†ÿ‹ÿÿ’ÿ’ÿ“ÿ“ÿŽÿÿŒÿˆÿ€ÿvÿkÿ[ÿDÿ0ÿ-ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ,ÿ/ÿ4ÿ;ÿ;ÿ=ÿ@ÿNÿbÿvÿˆÿÿ’ÿÿŒÿŠÿˆÿˆÿ‡ÿ‚ÿ{ÿpÿeÿ_ÿDÿFÿcÿiÿeÿaÿ^ÿSÿcÿmÿgÿ_ÿVÿLÿ=ÿ@ÿ;ÿ9ÿ<ÿ@ÿKÿYÿ_ÿaÿcÿeÿfÿhÿnÿrÿvÿyÿzÿ|ÿ|ÿ|ÿ|ÿ}ÿ~ÿÿÿ}ÿ~ÿ€ÿ‚ÿ†ÿ†ÿ†ÿ‰ÿŽÿ“ÿ“ÿ•ÿ–ÿ–ÿ—ÿ—ÿ—ÿ—ÿ—ÿ—ÿ—ÿ•ÿ•ÿ•ÿ“ÿ‘ÿÿŒÿ‹ÿŒÿŒÿŒÿÿŽÿŽÿŒÿŒÿ‰ÿ‡ÿ…ÿ„ÿƒÿ„ÿ…ÿ„ÿƒÿ„ÿ„ÿ„ÿ‚ÿ‚ÿ„ÿ„ÿƒÿ‡ÿŽÿÿÿ‹ÿŠÿŠÿ‰ÿ‰ÿ‰ÿŒÿÿŒÿ‚ÿÿ€ÿ€ÿ€ÿ€ÿ~ÿ~ÿÿ‚ÿ‡ÿ~ÿ|ÿzÿwÿuÿrÿpÿpÿnÿlÿkÿiÿiÿhÿfÿfÿfÿhÿhÿiÿjÿoÿvÿÿˆÿŒÿÿ‰ÿ‰ÿ‡ÿ…ÿ†ÿ…ÿŠÿŒÿ‘ÿ“ÿ”ÿ’ÿÿÿÿŽÿŒÿ‹ÿŠÿŠÿˆÿ‡ÿˆÿÿ”ÿ–ÿ”ÿ“ÿ‘ÿ‘ÿ‘ÿ‘ÿÿÿŽÿŽÿÿÿ’ÿÿŽÿ‹ÿTSmRPmNNoM}KpI}GqF}DtC}Bt>}=u=};v9}:w8~6w7~7y4~6y6~6y5~5y4~4y4~4y4~4y4~4w4~4w44x44x4~4x4~3x3~3w3~3w4~5x4~4x2~2y2~2y3~3z1~1z3~3y2~2y2~2y1~1y0~0{0~/{/~/{.}.{-}-{,},{,},{,},{,},|,},|,|+{+|+{+|+|+|+|,~,}-~-}-~-}-~-}-~-}-~-}-~-}-~-}-~-|-~-|-~-}.~.}.}.~.}.~.~.~0~1~3~3~5~5~7~78~:=@DDF~H€I~K€M€M~M€M~N€N|O€O|PS|TS}T~T}T~S|S~R}R~O|MM|KI|IG{GFzD~@|?~<|<€:}7€5}42|1.{-~,{,},|-}-|,},|,},|,},|,~-|.5|TayVXz_~f|a|0€+}/…6€A„LƒJ‚F‚GH„G}H„M{V„UySƒXi}o…pvcZp\’dqa‘WvCŒ'y%ˆ%|#…!}!„"~"ƒ#~#ƒ$'ƒ)~/ƒ:}K…U~T†R€PƒOO‚O‚N‚J‚JI‚I‚GD‚DC‚A~=„7}3„0|0†0{0…1{4…4|6†7|7†7{;†8|9†56†5€3ƒ5‚60….|0Š7y?ŠE{B‡5},„j}x€n~n‚rqpz€z„]X…Z~\‡_}a†a|`ˆa|OŠ@{74{43{0/|--|26}>A€BŽJ„NQ„WŒX…UŒT†U‹Zˆ]Š_‰_ŠZŠZ‹\Š[‹]Š]‹YŠTŒQ‰MŽG†A<ƒ66€=ŽO€\‰^V‡`~l…b|]…]|]ƒ\}l‚ƒ~‚uq‚q€r€s€p_DzB}FyH|LyOzV~_xcgyr~|{t…~‰iŽ‘c’€“b“”c“ƒbŽƒŒa†ƒbt„gkV„=v/ƒ/..†.€.†.~.‡++‡,+‡-~.‡0~7…<|@E}R}j€r€hƒŽgŒƒ‹hŠƒŠi‰ƒƒi|„ped„cjPƒ=l^ƒglbƒege…heh„hchƒlbo†lebƒOiDƒKpQ€QvY~avfgrkƒnmr…shxƒ{c„€^„W~…R~…~O†P€…~S|…}Y~…ƒ_„…‡lƒ”v˜‚š{žƒ z ƒ z¡…¢z£…¡x£„¢x …s…™p—†”k‡ŒeŒ‡ŒdŒ‡Œb‡‹c‡‡†d‚‡‚dƒ†ƒeƒ†ƒfƒ†‚eƒ†…b…„‡bˆ„ˆb†„…f…ƒ‰hƒŒh‹ƒ‹hŠ„‰hˆ„ˆhˆ„ŒkŽ„‰n‚‚w~~~~€~~~}}€€€†y~Œ{ry”vnt•qko•nkl•lkk•ilh•gnf•ene•glg•ikj•pgw•€iˆ‘ŒpŒŠˆx‰‡~…„‡‹}Ž{“}–s”€’qqs‹‰uŠŠyŠ}Š‹z‘„–w–†”y’‡“y‘‰y‰xŠŽvŠ‹v‡“zƒŽ~‹€UÿTÿSÿSÿPÿOÿOÿMÿKÿIÿFÿFÿCÿBÿ@ÿ>ÿ=ÿ;ÿ:ÿ8ÿ9ÿ7ÿ7ÿ7ÿ6ÿ5ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ3ÿ2ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ0ÿ1ÿ1ÿ1ÿ5ÿ5ÿ7ÿ7ÿ:ÿ<ÿ<ÿ>ÿAÿCÿDÿFÿFÿHÿJÿJÿLÿMÿLÿNÿNÿNÿNÿPÿQÿPÿQÿPÿPÿOÿNÿLÿLÿJÿJÿHÿFÿFÿEÿDÿCÿBÿ@ÿ?ÿ<ÿ:ÿ:ÿ8ÿ6ÿ4ÿ2ÿ0ÿ/ÿ.ÿ-ÿ-ÿ,ÿ+ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ.ÿ.ÿ?ÿaÿ`ÿWÿXÿ_ÿgÿOÿ(ÿ*ÿ.ÿ6ÿBÿMÿJÿGÿIÿEÿDÿHÿKÿMÿTÿWÿ^ÿmÿqÿfÿ_ÿ`ÿdÿeÿ^ÿTÿ9ÿ!ÿ$ÿ"ÿ!ÿ ÿ"ÿ"ÿ"ÿ$ÿ'ÿ,ÿ1ÿ?ÿPÿ[ÿYÿXÿYÿVÿRÿUÿQÿNÿJÿGÿFÿFÿDÿAÿ@ÿ?ÿ<ÿ9ÿ4ÿ0ÿ0ÿ0ÿ0ÿ1ÿ2ÿ4ÿ4ÿ6ÿ6ÿ8ÿ<ÿ>ÿ<ÿ9ÿ5ÿ5ÿ7ÿ7ÿ5ÿ4ÿ4ÿ1ÿ0ÿ/ÿ-ÿ.ÿ+ÿ1ÿeÿvÿmÿkÿoÿqÿpÿwÿ{ÿcÿYÿ[ÿ^ÿ^ÿdÿfÿfÿPÿ>ÿ8ÿ;ÿ8ÿ4ÿ4ÿ2ÿ2ÿ1ÿ0ÿ1ÿ3ÿ4ÿ7ÿ:ÿBÿKÿPÿXÿ[ÿYÿZÿYÿYÿ]ÿ]ÿ]ÿ]ÿZÿZÿ[ÿ\ÿ\ÿYÿTÿPÿMÿIÿ=ÿ8ÿ6ÿ5ÿ6ÿ=ÿKÿSÿTÿQÿYÿhÿeÿ[ÿ]ÿ]ÿaÿwÿƒÿ}ÿvÿtÿuÿtÿsÿqÿRÿ?ÿBÿFÿGÿKÿOÿVÿ_ÿhÿoÿuÿÿˆÿŽÿÿ‘ÿ”ÿ–ÿ—ÿ•ÿ•ÿ“ÿ’ÿŽÿŒÿŒÿ‡ÿ}ÿsÿbÿMÿ5ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ,ÿ,ÿ,ÿ+ÿ+ÿ-ÿ,ÿ2ÿ7ÿKÿ\ÿdÿtÿˆÿ’ÿ‘ÿŽÿÿÿŒÿ‹ÿ‹ÿˆÿ‚ÿyÿlÿcÿbÿcÿZÿ^ÿeÿcÿcÿeÿfÿiÿiÿgÿgÿjÿoÿoÿpÿpÿoÿkÿdÿgÿmÿrÿsÿuÿuÿyÿzÿ€ÿ‚ÿ‚ÿ„ÿ„ÿ‚ÿ€ÿÿ€ÿ€ÿ~ÿ|ÿ{ÿzÿxÿyÿ{ÿ|ÿ}ÿ…ÿÿœÿ£ÿ¦ÿªÿ«ÿ«ÿ«ÿ«ÿ¬ÿ­ÿ®ÿ°ÿ­ÿªÿªÿ©ÿ£ÿŸÿšÿ–ÿÿ‹ÿ‹ÿ‹ÿ‹ÿ‰ÿ‡ÿ„ÿ„ÿÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿÿÿ‚ÿƒÿ‡ÿŠÿ‰ÿ†ÿ„ÿ…ÿ‰ÿ‹ÿŒÿ‹ÿ‹ÿ‰ÿ‰ÿ‰ÿ‰ÿ‰ÿ‹ÿÿˆÿƒÿ€ÿ~ÿ~ÿ|ÿ~ÿ|ÿ|ÿ}ÿ€ÿƒÿ}ÿ{ÿyÿvÿtÿqÿoÿmÿlÿlÿkÿiÿhÿfÿfÿeÿfÿgÿgÿiÿjÿqÿzÿ„ÿŒÿÿ‹ÿŠÿŠÿ…ÿ„ÿ„ÿ‡ÿŒÿ‘ÿ•ÿ•ÿ’ÿ‘ÿÿÿŽÿŒÿŠÿ‰ÿ‰ÿŠÿ‰ÿ‹ÿ‘ÿ”ÿ•ÿ“ÿ“ÿ”ÿ‘ÿÿÿÿÿÿŽÿÿ‹ÿÿ’ÿÿÿŠÿXWmUSmRRoQ}OpM}KpH}IrE}Cs@}>t=};v;}9w9~7w7~7y6~5y5~5y4~4y4~4y3~3y3~3y3~3w3~3w33x22x3~3y3~3y4~4y2~2y2~2x1~1x1~1w1~1w0~0x1~1x0~1z2~0z1~1{0~0{1~1{/~/{.~.{.}-{-}-{,},{+}+{+}+{+}+|*}*|*~*|*~*|)})|*}*|+~+}*~*}+~+}*~+}+~+},~,},~,}-~-},~,}-~-}-~-}.~.}-}.}/}/}/}/~0}1~3~3~3~3~5~6~7~9~9;~=?~A~CE~EFH€II€K~KL~MN~NM~M€M~K€K~J~H~GF~D}DB|CC|B@{A@z>~ƒ7~7„8}8…2|1†0z0…1{2…3{3…4|7†8|;†@{A‡=|8‡8}7‡55…4€4„3€2…0-ƒ,-‚6‚l€w‚l}j€on€nw€zƒh\„\~[…]}`‡a|WŠ:{(Œ.|7Ž;{95{20{03{64{35:?GŽP„UX†WT‡VY‡YŒ^‰^Š]‹\ŠZŠ[Š]Š^Š^ŠZŠTŠRŠQ‡GŒ;„7Ž5€24:F~M‡Q}SƒVzdƒfz]„^|[†f}€„ƒz‚wv‚t€s‚t€vk~K=|B}E|J|QzU~\xkyy~{ˆzp}’j•}–j˜}™i—€•i’‚‘e‚bŒ‚‹`ƒƒycmƒ]mCƒ2|.,ƒ/€/…0~0†..….+…+~-†.~.‡4~Cƒaw{Žs”€’kŽƒjŒƒjŒƒŒh…ƒ€hu„gf`„_ha„aib„aicƒdid…ggg…eee†hen†rcr†vdy†~b…c}…|c|‚~d~‚cƒb†ƒ†`†„ˆ]‡„ƒ\€…Y~…|Y{†xZwˆt^rˆsht†tyt~„¦…¬~¯…±€´ƒ´€´ƒ¶·º»}»€º|´³w²ƒ®s©†¥p…•p…oŽ…‹kˆ…†i‚…k€…~mz†zoy†{p{†{m|†}k„iˆ„Šgˆ„…d…ƒ‡a‰ƒŠc‹ƒŠcŠ„‰cŠ„Šc‰„ŠcŒ„ˆgƒ„€q€‚€x~~z}€}y~€~‚y~‹zqx”ulr•qkn•mkk•jki•ilh•flf•flf•glg•ikj•si|•†kqŒ‡Œy‰}†}„|„~†|Œ}“x•}•r’€uuŒŠs‰ˆuˆ€‰{‰|‚‘y–†•x’‡’y’‡‘y‘‰xŠw‹Œv‹‹vŽ†‘zŒ~Š~YÿXÿWÿWÿUÿTÿSÿPÿNÿMÿIÿHÿEÿCÿAÿ?ÿ>ÿ<ÿ<ÿ:ÿ8ÿ7ÿ7ÿ7ÿ6ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ1ÿ1ÿ0ÿ1ÿ2ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ+ÿ+ÿ+ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ.ÿ/ÿ/ÿ0ÿ0ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ3ÿ6ÿ6ÿ8ÿ:ÿ:ÿ<ÿ=ÿ?ÿ?ÿAÿCÿDÿEÿGÿGÿIÿIÿIÿJÿJÿJÿLÿLÿIÿGÿFÿFÿDÿCÿDÿAÿ@ÿAÿ@ÿ@ÿ>ÿ=ÿ>ÿ<ÿ<ÿ9ÿ8ÿ6ÿ4ÿ5ÿ2ÿ0ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ,ÿ+ÿ,ÿ,ÿ-ÿ/ÿ;ÿZÿaÿZÿUÿYÿeÿdÿ3ÿ&ÿ,ÿ/ÿ9ÿCÿKÿHÿEÿEÿCÿAÿAÿAÿHÿPÿZÿkÿuÿwÿpÿ^ÿPÿUÿ`ÿbÿJÿ(ÿ ÿ!ÿ!ÿ!ÿ!ÿ#ÿ%ÿ+ÿ6ÿNÿ`ÿbÿeÿdÿbÿ_ÿYÿOÿHÿAÿ8ÿ5ÿ0ÿ0ÿ2ÿ7ÿ7ÿ5ÿ6ÿ9ÿ8ÿ4ÿ2ÿ1ÿ1ÿ1ÿ2ÿ3ÿ3ÿ5ÿ6ÿ7ÿ:ÿ>ÿ=ÿ:ÿ8ÿ8ÿ7ÿ5ÿ5ÿ4ÿ4ÿ5ÿ3ÿ3ÿ,ÿ-ÿBÿmÿuÿmÿjÿmÿqÿqÿvÿyÿgÿaÿ\ÿ`ÿ^ÿaÿXÿ:ÿ)ÿ)ÿ-ÿ0ÿ7ÿ:ÿ9ÿ5ÿ2ÿ0ÿ2ÿ8ÿ7ÿ5ÿ5ÿ:ÿ?ÿHÿOÿRÿXÿ[ÿWÿOÿOÿVÿZÿaÿaÿ^ÿ\ÿZÿ[ÿ^ÿ]ÿ^ÿZÿXÿVÿWÿNÿBÿ<ÿ6ÿ5ÿ5ÿ<ÿFÿOÿUÿSÿRÿ^ÿgÿ^ÿZÿ\ÿmÿ‚ÿÿxÿvÿuÿsÿtÿsÿqÿvÿtÿSÿAÿGÿLÿMÿPÿ_ÿsÿ}ÿ„ÿŽÿ’ÿ”ÿ”ÿ–ÿ—ÿ™ÿ˜ÿ“ÿ‘ÿŽÿ‹ÿŒÿÿŒÿŒÿˆÿ~ÿsÿfÿWÿ>ÿ1ÿ4ÿ9ÿ;ÿ;ÿ<ÿ8ÿ6ÿ2ÿ-ÿ*ÿ+ÿ0ÿ2ÿ2ÿCÿ_ÿyÿ‹ÿ“ÿ“ÿ’ÿÿŽÿŒÿ‹ÿŠÿŠÿ…ÿ|ÿoÿbÿ_ÿbÿaÿaÿbÿbÿdÿeÿeÿeÿgÿgÿjÿkÿpÿrÿtÿxÿ}ÿÿÿ€ÿ„ÿƒÿƒÿ…ÿ…ÿ„ÿ…ÿ…ÿˆÿˆÿˆÿ‡ÿ‡ÿƒÿÿ€ÿzÿxÿwÿtÿtÿpÿqÿoÿnÿnÿqÿuÿÿ«ÿ±ÿ²ÿ·ÿ¹ÿ¹ÿ¹ÿ»ÿ¼ÿ¾ÿ½ÿ½ÿ¼ÿ»ÿºÿ¹ÿ·ÿ°ÿ¬ÿ¡ÿ–ÿ”ÿ•ÿÿˆÿ…ÿ„ÿƒÿ‚ÿ~ÿ|ÿyÿwÿvÿuÿvÿvÿvÿwÿyÿ{ÿ~ÿ…ÿˆÿ„ÿÿÿ„ÿˆÿ‰ÿŠÿŠÿ‰ÿŠÿŠÿ‰ÿŠÿ‹ÿ‡ÿ„ÿ‚ÿ€ÿ€ÿ€ÿ€ÿ}ÿ}ÿ~ÿÿÿ|ÿyÿvÿsÿrÿnÿmÿlÿkÿkÿjÿiÿhÿfÿfÿhÿhÿhÿhÿiÿmÿtÿ}ÿ‡ÿŒÿÿ‹ÿŠÿˆÿƒÿƒÿ…ÿˆÿÿ”ÿ”ÿ’ÿ‘ÿÿŽÿÿ‹ÿŠÿˆÿˆÿˆÿ‹ÿÿÿ’ÿ”ÿ’ÿ‘ÿÿÿÿÿÿÿŽÿŽÿ‹ÿŒÿ‹ÿŽÿ‘ÿÿÿ‹ÿ[€[mZ€XmXXnT}QoO}NqL}IrG}DsB}@u?}=v<};w8~8y8~7y5~5y5~5y3~2y3~3y33y33y2~2x2~2x2~2x1~1y22{00{0~1y1~1y1~1y1~1y1~1z/~/z0~0{0~0{1~1{1~0{/~/z0~/z-~/{.~.{-~-z.}-z-}-z+}+z+}*z)})z)})|*}*|)}(|(}(|(~(|)~(|)}*|+}+|+~+|+~+|+~,|,~,|,},},~,}-~-}-~-}.~.}-~-}.}.}0}0}0}0~0}0~0}0}1}2}4~4{5~7{8~8}:~<{<}=}>}A}B~D}D~E~E~FG~GG~GF~EG}D}CC|C~A}A~A}?~=|=~={:~€FO‚TU†WŽWˆTPˆPŽR‰WŒ^‰_Š_‹_‰\Š\‰^Š\‰[‹Z‹ZŠ\‹\ˆX‹K…?Œ<8Ž9~DŠN|T„UzV~Vy[€dza„W|_…v~€ƒ}yƒvtƒp€r„r€jƒj~w‚x|X‚EzI€M{Vcyw~‚zŠy’z”s•z•r–{—q™}”pn‰€„l‡ha‹ƒ‡a|ƒqccƒSpE‚G|L€P|P€P{M€HzC€@8~12~1…4|E‚d}|x€”n“jŒ‚‹l‹‚‹kˆƒ†gƒxfk„`h_„cjbƒciaƒajb…cif…ghh…jgo…sgs…ue{…b„ƒb…„†b‡„ˆb‰„‰aˆ„ˆb†„†cˆƒ†c…ƒ„eƒ„f€†~ey†vds‡pio†hvf…i„m€pŽmyn’˜u­Œ²x³‡¸|¼‚¼}½€½¾¾¾½~¼~»»{»€»x¶ƒ°v§„žvž…–v‹‡€t„€o…€o{…ytx…vys„r~o…r}p…p{r„ryv…wt„„n€„{h~„…d‹‚‹eŠ„‰dŠ„ŠdŠ…‹eŠ…†e„„ƒk„€o€ƒ€r~ƒ~u€ƒy{‹xqw•smp•lkl–lkl–jli–ili–hlf•ili•hkh•ijl•ui€”‰lŒŽ‹tŒ„Š{†}„„z†}Šz’}•w’}r€Žs€‹s‹‚‰u‡ˆxŠŒ{|’‚’y“†‘y‡z†Žz‡ŽxŽŠvŒŒ‹v‹‹‹v‡yŽƒ‹}‰[ÿ[ÿ[ÿ[ÿYÿYÿVÿTÿRÿNÿMÿJÿGÿEÿBÿ@ÿ@ÿ>ÿ=ÿ<ÿ9ÿ9ÿ8ÿ7ÿ5ÿ5ÿ5ÿ5ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ2ÿ2ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ0ÿ0ÿ/ÿ/ÿ.ÿ.ÿ-ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ(ÿ'ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ0ÿ0ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ1ÿ2ÿ4ÿ4ÿ4ÿ6ÿ8ÿ8ÿ9ÿ:ÿ=ÿ>ÿ=ÿ>ÿ?ÿAÿBÿBÿCÿDÿEÿEÿDÿDÿDÿCÿCÿBÿ@ÿ>ÿ>ÿ=ÿ=ÿ=ÿ<ÿ<ÿ;ÿ;ÿ;ÿ:ÿ:ÿ:ÿ7ÿ7ÿ5ÿ2ÿ2ÿ2ÿ2ÿ1ÿ/ÿ/ÿ/ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ.ÿ-ÿ.ÿ.ÿ.ÿ-ÿ,ÿ,ÿ-ÿ-ÿ3ÿSÿhÿZÿWÿYÿ^ÿiÿQÿ#ÿ%ÿ*ÿ,ÿ3ÿ>ÿFÿHÿDÿCÿBÿ@ÿ<ÿAÿIÿRÿfÿpÿpÿkÿ`ÿ[ÿ^ÿ]ÿ]ÿTÿ+ÿÿ!ÿ!ÿ"ÿ%ÿ*ÿ6ÿQÿdÿbÿcÿeÿZÿFÿ0ÿ,ÿ)ÿ%ÿ$ÿ"ÿ#ÿ#ÿ$ÿ$ÿ&ÿ(ÿ*ÿ/ÿ9ÿ;ÿ8ÿ4ÿ4ÿ4ÿ4ÿ7ÿ;ÿ=ÿ=ÿ?ÿBÿBÿEÿ@ÿ;ÿ:ÿ8ÿ6ÿ7ÿ6ÿ6ÿ7ÿ5ÿ0ÿKÿuÿ}ÿtÿkÿiÿmÿqÿvÿzÿuÿaÿ[ÿ_ÿZÿ_ÿgÿKÿ(ÿ(ÿ+ÿ.ÿ-ÿ1ÿ0ÿ3ÿ8ÿ:ÿ7ÿ<ÿFÿEÿ>ÿ;ÿ>ÿ>ÿGÿMÿVÿXÿTÿSÿSÿSÿPÿQÿSÿQÿUÿZÿ^ÿ^ÿ]ÿZÿZÿYÿYÿYÿXÿ^ÿdÿ`ÿOÿCÿBÿ>ÿ?ÿFÿRÿUÿUÿUÿVÿXÿaÿ`ÿ[ÿiÿ€ÿ€ÿ|ÿwÿuÿsÿuÿsÿjÿ`ÿbÿkÿuÿxÿXÿFÿKÿXÿoÿ}ÿ†ÿÿ‘ÿ’ÿ‘ÿ‘ÿ’ÿ’ÿ’ÿÿ‡ÿ€ÿ~ÿzÿzÿÿ‰ÿÿŒÿŠÿƒÿxÿsÿiÿdÿ_ÿbÿfÿfÿfÿbÿ]ÿVÿQÿJÿAÿ8ÿ4ÿ6ÿMÿlÿ„ÿ‘ÿ’ÿ’ÿÿ‹ÿŠÿ‰ÿ‰ÿ‡ÿÿ|ÿrÿdÿbÿ`ÿbÿcÿbÿbÿ`ÿaÿbÿdÿgÿlÿpÿtÿwÿ{ÿ|ÿÿÿ…ÿ‡ÿˆÿ‰ÿŠÿŠÿŠÿŠÿˆÿˆÿ†ÿ…ÿ‚ÿÿ~ÿÿ}ÿ}ÿ}ÿ|ÿxÿsÿnÿjÿfÿfÿ_ÿhÿmÿpÿkÿoÿŸÿ¯ÿ´ÿ¸ÿ»ÿ¾ÿ¾ÿ¾ÿ½ÿ¾ÿ¾ÿ½ÿ¾ÿ½ÿ¼ÿ»ÿ»ÿ»ÿºÿµÿ¬ÿ¦ÿÿ‰ÿzÿzÿ}ÿÿÿ~ÿzÿxÿvÿtÿrÿoÿmÿkÿhÿhÿjÿjÿlÿjÿoÿyÿÿ}ÿvÿ{ÿ‚ÿ‹ÿŠÿ‰ÿŠÿŠÿŠÿ‹ÿŠÿ†ÿ…ÿ„ÿƒÿÿÿÿ~ÿ~ÿ€ÿ„ÿ‚ÿzÿwÿuÿpÿnÿkÿjÿiÿiÿjÿiÿiÿiÿhÿfÿhÿhÿhÿhÿjÿpÿwÿÿ‰ÿŒÿŠÿŒÿŠÿ†ÿ„ÿ„ÿˆÿŒÿ”ÿ—ÿ“ÿ‘ÿÿÿŒÿŠÿ‰ÿˆÿˆÿ‰ÿÿÿ’ÿ“ÿ’ÿ’ÿ’ÿ’ÿ“ÿ—ÿ˜ÿ“ÿŽÿŽÿŒÿŒÿ‹ÿ‹ÿ‹ÿÿÿŽÿ‹ÿ‰ÿ[€[n\€\n[€YnW~VoS}QpN}LqJ}HrF}Ds@}>v=}|>~@}@~A~A~AA~AA~@~?~?~=}>}><|;~;}:~:}:~:|9~9{9~8z6~6z5~5|5~2|2~1{1~1{0}0|0}0|-}-|-}-|.}.|.}.|.}-|-}-|-}-|-|-|-|.}/~8}W‚c~\‚XYcd{@ƒ!z%…)}+†1‚;ƒF„I€Bƒ@~@‚>|=„@zE„U}js‚rxm‹ktiŽhuiexDŠ!y"‡"{#…'.…?^†ie†d}b‡L{3‡*|+‰)}%‡#}!‡#"†"#†&(…+~/…7}9‡6|6†6|6„7{9„={@„B{C†FzH‡GzB‰?z;‰8|7‡6~6…5}4„3~I‚~€y~r‚m|i‚l~q‚s€y€u‚d[„^]„a~a‡D}$ˆ)|+Š+{-.z0Ž4z7:z=9{>J|F<}9=~ELPT„XU‡RRˆRŽNˆMŽOˆNPˆSV‰[‹ZŠX‹ZŠZ‹[ŠVŒT‰ZŒa‰]ŒN‡HŒFƒCCDˆL}RƒUzU{VyV}_z`‚a|r…~|ƒxwƒvtƒv€t‚d€]`g‚l}sƒs}VO{^}s|ƒxŽ{‘t‘zvŽyŒxyŒwˆ}…t}qso€ttvyoŒeŽ‚‹`†‚€_}‚zfxyl{€}i|€zhw‚rik‚fm\VsK€F{K~[{q€‡v”€’oŒmˆ‚‰n‡‚†m…ƒ|kxƒkga„agd„bfaƒcecƒefh„igk„neq…ucy…}b}„c‚„…b‡„ŠdŠ„Šd‰„Šfˆ„‡dˆƒ†b…ƒƒa~„zgv„vnw†vsv…uvs…m{h…b‚\‚_d}a˜ctgœmn‚œ¡p«‘³x¸†»}½ƒ½½¾~½€¶~¾¿}¾½~½}½~½}¼€´|ªˆ …‹s€tŠy€y†yyz…{wz†xzu„rp„k…g‚f‡f‚iˆjƒh†d„e„m„n}w…{uw…wm|„‚k‡ƒ‹iˆƒˆgŠƒ‹e‰ƒ†e‡„„fƒ„„j‚„l€„€q€ƒ„{‚}{ˆwttrnp”lll•klk•jlj”ili”glh•hlh•iki•kjq•|i„”ŠmŠu‚Š|…|„ƒ{ˆ}y•–t”’r€ŽsŒ€Šs‰ˆu‹€‹y‘~“|”…“y’‡“y“…˜|¡~¡€›~’Ž†‹x‹‹‹tŒ‹Œt‡‘yŒ}Š^ÿ^ÿ\ÿ\ÿ[ÿYÿWÿVÿTÿRÿOÿLÿJÿHÿEÿEÿCÿAÿ=ÿ<ÿ:ÿ9ÿ7ÿ6ÿ6ÿ5ÿ4ÿ2ÿ2ÿ2ÿ3ÿ3ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ/ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ'ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ*ÿ*ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ/ÿ/ÿ0ÿ0ÿ1ÿ1ÿ2ÿ2ÿ4ÿ4ÿ5ÿ5ÿ6ÿ6ÿ7ÿ7ÿ9ÿ:ÿ;ÿ<ÿ>ÿ>ÿ>ÿ@ÿ?ÿ?ÿ?ÿ?ÿ?ÿ>ÿ>ÿ>ÿ<ÿ;ÿ:ÿ9ÿ9ÿ9ÿ8ÿ8ÿ8ÿ8ÿ7ÿ7ÿ8ÿ8ÿ6ÿ6ÿ5ÿ5ÿ3ÿ2ÿ2ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ/ÿ-ÿ.ÿ.ÿ-ÿ,ÿ-ÿ-ÿ-ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ/ÿ0ÿ?ÿ_ÿcÿXÿVÿYÿbÿaÿ.ÿ#ÿ'ÿ'ÿ+ÿ.ÿ7ÿCÿGÿFÿBÿBÿ>ÿ;ÿ<ÿDÿXÿvÿ}ÿsÿjÿgÿbÿnÿtÿ]ÿ/ÿ ÿ!ÿ$ÿ'ÿ1ÿHÿhÿmÿmÿlÿ]ÿ7ÿ*ÿ-ÿ)ÿ'ÿ%ÿ#ÿ"ÿ!ÿ#ÿ%ÿ#ÿ$ÿ&ÿ*ÿ-ÿ4ÿ6ÿ6ÿ6ÿ6ÿ6ÿ7ÿ9ÿ=ÿ@ÿDÿFÿHÿHÿGÿCÿ@ÿ<ÿ9ÿ8ÿ5ÿ4ÿ4ÿ1ÿ:ÿsÿ‚ÿuÿoÿnÿqÿsÿtÿyÿyÿbÿ^ÿ`ÿ]ÿaÿgÿIÿ%ÿ%ÿ*ÿ/ÿ9ÿ:ÿ7ÿ8ÿ>ÿ>ÿ?ÿAÿ>ÿ=ÿFÿDÿ?ÿ=ÿ@ÿFÿMÿNÿLÿMÿLÿKÿNÿQÿMÿLÿNÿOÿOÿQÿRÿSÿVÿWÿWÿ[ÿ\ÿVÿTÿUÿWÿUÿNÿKÿKÿGÿEÿEÿHÿNÿUÿUÿVÿVÿ\ÿdÿjÿ}ÿ|ÿvÿuÿsÿuÿuÿuÿuÿaÿ]ÿbÿbÿeÿkÿsÿoÿ_ÿiÿ{ÿ‰ÿÿÿÿ‹ÿˆÿ†ÿ†ÿ‡ÿ„ÿ€ÿxÿkÿhÿmÿtÿrÿxÿ…ÿ‹ÿŒÿŠÿ‡ÿ„ÿ„ÿ†ÿŠÿŽÿÿŒÿ‹ÿ‡ÿÿ{ÿtÿlÿfÿ\ÿ\ÿ_ÿmÿ{ÿ‹ÿ’ÿ‘ÿÿŠÿ‡ÿ‡ÿ…ÿ…ÿ„ÿ}ÿtÿfÿ]ÿ^ÿ_ÿbÿdÿcÿfÿgÿkÿlÿqÿuÿwÿ{ÿÿÿ„ÿ…ÿ‡ÿŠÿŒÿŒÿ‹ÿ‹ÿŠÿ‹ÿŠÿ†ÿ„ÿ€ÿÿ…ÿÿwÿnÿjÿjÿkÿlÿjÿdÿ[ÿWÿTÿQÿRÿUÿSÿWÿ`ÿ‚ÿŸÿ¡ÿªÿ¶ÿ³ÿµÿ»ÿ¸ÿ¸ÿ¼ÿ¾ÿ¼ÿ½ÿ½ÿ¿ÿ½ÿ½ÿ½ÿ½ÿ¼ÿ¹ÿ¦ÿ}ÿiÿmÿqÿtÿuÿvÿvÿwÿvÿuÿrÿoÿnÿkÿiÿdÿiÿpÿoÿpÿkÿfÿlÿtÿsÿjÿeÿkÿpÿuÿvÿÿˆÿˆÿ‹ÿ‹ÿ‡ÿ†ÿ†ÿ…ÿ…ÿ…ÿ…ÿƒÿ‚ÿ‚ÿ€ÿƒÿ‚ÿ}ÿwÿuÿrÿqÿlÿlÿkÿkÿjÿiÿiÿiÿgÿhÿiÿiÿiÿiÿkÿqÿ{ÿ†ÿ‹ÿŒÿ‹ÿ‹ÿŠÿ…ÿÿ„ÿ‰ÿ“ÿ–ÿ”ÿ“ÿ‘ÿÿÿ‹ÿŠÿŠÿ‰ÿÿÿ‘ÿ’ÿ“ÿ“ÿ’ÿ‘ÿ‘ÿ“ÿœÿ£ÿ¤ÿœÿÿŒÿŠÿŒÿ‹ÿŒÿÿŽÿÿŽÿŒÿŠÿ]]n\\o[€YnX~WoT~RpP~NqL~IrG~DsB}?w<};x:}9z8~7z6~4y3~3y2~2y1~1y11y00y1~1y1~1y0~0z0~0z00z00z/~/y/|/y.}.y.}.y/}/z.}.z.}.{.}.{.~.|.~.|-~-|-~-|,|,{,|,z-}-{+|+{*}*{)}({(}(|)}(|&}'|(}(|&}&|'}'|&~&{'~({(}({(}({)}){)}){(}){*}*{*|*{+}+|+~+|,~,|,},|-}-|,}.|-}-|0}0}0}0}1}1}2}2}2}2{2}2{3~4{5~6{7~7|8~;|<}=|=}=|=~=|<~<}<~<}<~;}:~:{:~:{99}88}7~7|6~8|8~8|6~5|5~5|3~3|2~1|0~0|0|0|0|0|0}/|/}/|.|.{-|-{,|,|.|-|-}-|.}.|.}-~-~-~1€Fd‚^€UV]~h~Vx%„&y'†&~(„+ƒ3ƒ?„I€G„A~?„?{<„:|Cƒ^€q~j†jwb‹awp‹s{q‹My#‰y#†'}3…N€m…pq‡nzP‰-y-Š,|)ˆ$|"†"}!†!~"†#"†"€$„)~,„0}4…5}6…6}7…8|:…=|@…D{H†J{H‡GzA‰>y=‰8z7ˆ5|1†1|0†^€xt}qt~uz|~ƒg€Yƒ_]…b~g…U}&ˆ,|)Š-{4Œ=zGŽEzHL{OIzGŽD~EŽIEŽF€ED‚EK‚L‘IƒF’C„E‘J†K‘I†IK†NR†TQˆQR‰UYŠYŒ\‰\ŒU‰RŒT‰Q‹OˆPŒO†KŒHƒF‹EL‡S|VWzU{Yyd€s}{‚w~rƒs~v„v~v„wrƒ_[__€agk~q€t~tz‚}ŒqŒ|pŒzˆt„yƒv‚{ƒr…~py€etc€hyolvm{n…‹f‚‹a‹‚‹aŽ‚“d–•e–‚“dƒ`ˆƒƒb|‚wfollqxq„Žo“‚l‹‚‰m†‚„n„‚„nƒ{jo„`kZƒ_i`ƒ^ibƒgdjƒncq„sdv„|d}ƒ€d†ƒˆd‰ƒ‰cŠƒŒcŽ„‹d‹„‰dˆ„‡f‡„ƒh~„whz„}e|„{ds†fmZ„ZwXƒT|P€M‚W~PGzQ—^rjšln‰•£n¨­tµˆ¹z»ƒ²~Œ…˜œ‡¬~¾ƒ¾}¿¾}¾½~½}¼~½~·‡¢†rYŽcˆil„p‰p„q€t„t€t„tr„qpƒn†k€gŠjo‹noŠpƒpŠm„q„v†m}\ˆ[v`†ermƒzpˆ€Šk‹ˆh…ƒ…e…„…f…„…h……‚i‚…‚i‚ƒ„sƒ€~xyu‰rsolok•jnl”hmh“imi”imh•hmh•hkj•oiu•}h‡”Œn‹‹Šw‹ˆ~„z‚~‡z}”v•’s€s€Žs‹s‹€vŽz“””{’…‘yˆy’†™}£{¢„—zŽƒŒ†‹yŠŠ‹tŒt†yŒ~Š^ÿ^ÿ]ÿ]ÿ[ÿYÿYÿWÿVÿSÿQÿOÿLÿJÿHÿDÿBÿ?ÿ<ÿ;ÿ:ÿ9ÿ8ÿ7ÿ6ÿ4ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ/ÿ0ÿ0ÿ0ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ/ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ*ÿ*ÿ*ÿ*ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ&ÿ'ÿ(ÿ(ÿ'ÿ&ÿ'ÿ'ÿ&ÿ&ÿ&ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ,ÿ,ÿ.ÿ-ÿ.ÿ.ÿ0ÿ0ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ3ÿ4ÿ5ÿ6ÿ7ÿ7ÿ9ÿ:ÿ;ÿ<ÿ<ÿ=ÿ=ÿ=ÿ<ÿ<ÿ;ÿ;ÿ9ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ6ÿ6ÿ5ÿ4ÿ4ÿ4ÿ6ÿ5ÿ3ÿ3ÿ3ÿ3ÿ2ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ.ÿ3ÿPÿgÿYÿRÿXÿbÿeÿFÿ#ÿ%ÿ$ÿ#ÿ$ÿ(ÿ.ÿ;ÿEÿIÿFÿ@ÿ?ÿ>ÿ>ÿHÿcÿpÿnÿcÿeÿkÿhÿkÿfÿ8ÿ!ÿ$ÿ(ÿ1ÿPÿoÿpÿoÿkÿGÿ.ÿ,ÿ)ÿ&ÿ#ÿ"ÿ ÿ!ÿ!ÿ"ÿ#ÿ"ÿ!ÿ#ÿ(ÿ+ÿ-ÿ1ÿ4ÿ6ÿ7ÿ9ÿ:ÿ<ÿ>ÿ?ÿAÿEÿIÿJÿIÿAÿ=ÿ>ÿ>ÿ:ÿ5ÿ0ÿ/ÿ[ÿyÿxÿsÿqÿtÿxÿ|ÿ{ÿ~ÿpÿVÿ\ÿ^ÿ`ÿhÿ^ÿ,ÿ)ÿ,ÿ,ÿ0ÿ:ÿCÿDÿEÿOÿQÿSÿXÿSÿMÿQÿOÿCÿIÿNÿMÿGÿFÿGÿEÿDÿCÿCÿDÿIÿIÿHÿGÿMÿNÿTÿUÿTÿUÿVÿXÿZÿ^ÿ_ÿYÿTÿSÿQÿOÿOÿQÿOÿLÿHÿDÿHÿQÿVÿWÿXÿ[ÿfÿuÿzÿtÿrÿsÿsÿtÿuÿwÿrÿ_ÿ[ÿ\ÿ`ÿ`ÿaÿgÿoÿ|ÿÿ‹ÿÿŒÿŒÿŠÿ‡ÿ„ÿÿÿƒÿ„ÿ€ÿwÿdÿbÿdÿlÿjÿbÿqÿÿ‰ÿŒÿŽÿŒÿÿ’ÿ–ÿ™ÿšÿ™ÿ—ÿ”ÿÿŽÿ‹ÿ‡ÿƒÿÿ}ÿÿ…ÿŠÿÿ‘ÿÿŠÿˆÿ…ÿ„ÿ„ÿƒÿÿ{ÿmÿ^ÿ[ÿ^ÿ^ÿ^ÿeÿiÿnÿrÿwÿzÿ|ÿÿƒÿ‡ÿŠÿŒÿŒÿŒÿŒÿŒÿŒÿŠÿ‰ÿˆÿ…ÿ‚ÿÿÿ{ÿyÿtÿqÿuÿyÿ|ÿvÿvÿlÿdÿ[ÿTÿ^ÿaÿFÿDÿlÿ’ÿšÿ™ÿ§ÿ²ÿ·ÿ¸ÿ¸ÿ¹ÿ¾ÿ¦ÿwÿ™ÿŒÿ‘ÿ»ÿ¾ÿ¾ÿ¿ÿ¾ÿ¾ÿ¾ÿ½ÿ²ÿÿ]ÿRÿWÿ^ÿcÿgÿkÿlÿoÿqÿtÿsÿsÿrÿqÿqÿrÿoÿlÿoÿrÿoÿmÿkÿkÿlÿoÿoÿoÿaÿOÿRÿVÿ`ÿtÿ†ÿˆÿ‹ÿˆÿ…ÿ…ÿ„ÿ…ÿ…ÿ…ÿ„ÿ‚ÿ‚ÿ‚ÿƒÿ„ÿƒÿ€ÿ}ÿxÿtÿpÿmÿlÿkÿiÿhÿhÿhÿiÿiÿiÿhÿhÿhÿkÿpÿwÿ€ÿ‰ÿŒÿ‹ÿŒÿ‹ÿˆÿ„ÿƒÿ‹ÿ’ÿ–ÿ“ÿ’ÿÿÿÿŽÿŒÿ‹ÿŒÿŒÿÿ“ÿ”ÿ•ÿ’ÿ‘ÿ‘ÿÿÿ’ÿ™ÿ ÿŸÿ—ÿÿŒÿ‹ÿ‹ÿ‹ÿÿÿÿÿÿŒÿŠÿ^^l]]n[€ZnY~WoT~SoQ~OqN~JrH~EsC}@v<};w:}9w7~6x6~4y4~3y1~0y1~1y00y00y/~/z.~.z.~.z.~0z/~/z/~/z.~.y.|.y.}.y.}.y.}.z-}-z-}-|,},|-~-~+~+~,~-~,~,~+~+}+~*{*}*{)|){)}){(}'{'})|'}'|(}'|'}'|&}&|&}&|&~&{'~'{'}'{'}'{(}(z(}(z(})z)})z({*{*}*|,},|+}+|,},|,},|-}-|-}.|1}1{1}1{2}2{1}1{3}3{3}3{3~3{5~5{5~6z7~9z8}:|:}:|:~:|8~:}:~:|8~8|8~7{7~7{6}6{6}6{6~6|5~4|4~4|4~3|3~3|3~2|2~2|2~2|1~1|1~1|0}0}0}/}/|/|/|.|/|/}.|-}.}.|-}-|.}-~-~-~-~/6[b‚R€Q€Wa|b€9x"„#z#…"~"ƒ'‚,6ƒB€H„G€C„BBƒ?~P‚g‚bX†Z~]‡g~kˆn{Z‡(z"†%|-…Ff„m~j…f|@ˆ+z,Š'|&ˆ#}!†~ …!~$„%#„#€"„$~(„+}-„/}3…3}6…:|<…=|=…>{A†F{I‡KzC‰?y?‰Az<ˆ2|5†`}wƒr€l€st€xz||u[ƒZ]„]d…g~:†+}+‰,|-‹.|6Œ=|AŽG{OT~XfbŠW€IˆCDŒM‚LŽI‚GEB“@‚A“B‚B”C„G“I…G’H…J‘N…SV†WW‡YŽZ‰Z]‰a]ŠY‹UŠS‹R‰RŒS‰NŒM†H‹D‚E‰M}UXzZ{[yf€s|v‚q}sƒw|x„v|u„vqƒ`[^]`dg~s{ˆs‘~n~o‰~†q„~ƒrƒ„oƒpt€btc€cxii{^ju{‡lŒŽbŽa’•d™šdš‚še—ƒ–e’ƒ‘f‚Œf‰‚‡e†ŠgŒiƒ‹kŠƒˆl‡‚‡k…‚…kƒwlh„ZpTƒXs^ƒbsh‚koo‚si{ƒg„‚†f‰‚ŠdŽ‚ŽdŒƒ‹dŒƒ‹dŠ„‡e‡„ƒe‚…~e}…zgz„zmu„gu\ƒanr„m_n‡gYeˆ^c_…qws~IŠZu™“¬s±Š´u¹…»{»½{¾€¼}»„µ}¬…¸y°„³{¼‚¾~¾¾~¿¾º„§uHŒEšOƒRŸX~\˜`d’k‚o‰pƒp„pƒrƒr„q‚s‚v…vrŠr€x‹tƒlŠg„gŠi†nˆr‡rƒiˆR‚A‡M}W‚oyƒ‡t‹€ˆn…ƒk„ƒ„j„ƒ„jƒƒƒg‚ƒ‚g„„ƒn†ƒ‚y~z‚wzrŠnsllpi‘hoh“hnh”hmh•hmh•ikm•qiz•„i‹’‹pŠˆŒx‹{ˆ~„y…~Žx”}•u“’s‘€‘sŽ‚rŒ‹sŒ€Žw•|—~•‚‘z‘‡z‡z“‚›~ }œ„–€ŽŠˆŠyŠŒŒtŒŒt†yŒ~Š^ÿ]ÿ]ÿ]ÿ[ÿZÿYÿWÿUÿTÿQÿNÿLÿIÿGÿEÿDÿBÿ=ÿ<ÿ:ÿ9ÿ7ÿ6ÿ6ÿ4ÿ3ÿ3ÿ2ÿ1ÿ1ÿ1ÿ0ÿ0ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ0ÿ/ÿ/ÿ.ÿ.ÿ,ÿ,ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ'ÿ&ÿ'ÿ(ÿ'ÿ'ÿ(ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ/ÿ1ÿ1ÿ1ÿ1ÿ2ÿ2ÿ1ÿ1ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ5ÿ5ÿ5ÿ6ÿ7ÿ7ÿ8ÿ8ÿ9ÿ9ÿ9ÿ9ÿ9ÿ8ÿ8ÿ8ÿ7ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ/ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ-ÿ,ÿ,ÿ+ÿ-ÿ-ÿ=ÿbÿ\ÿTÿUÿZÿ_ÿWÿ-ÿÿ!ÿ"ÿ!ÿ#ÿ"ÿ(ÿ2ÿBÿJÿJÿHÿCÿBÿAÿXÿ_ÿOÿLÿOÿWÿ_ÿhÿkÿCÿÿ#ÿ)ÿ9ÿYÿhÿiÿbÿAÿ,ÿ*ÿ&ÿ$ÿ"ÿ ÿÿÿ!ÿ$ÿ$ÿ#ÿ!ÿ!ÿ#ÿ$ÿ'ÿ+ÿ-ÿ0ÿ3ÿ6ÿ9ÿ<ÿ=ÿ=ÿ>ÿAÿDÿHÿIÿGÿCÿAÿ?ÿ8ÿLÿpÿvÿmÿmÿlÿpÿvÿvÿyÿzÿsÿ_ÿYÿ]ÿ\ÿ^ÿeÿIÿ)ÿ-ÿ,ÿ1ÿ1ÿ0ÿ5ÿ9ÿ@ÿHÿOÿWÿaÿkÿhÿWÿJÿGÿJÿNÿNÿJÿEÿAÿ@ÿ>ÿ>ÿ?ÿ?ÿBÿDÿFÿHÿIÿLÿMÿOÿSÿVÿXÿYÿXÿZÿ[ÿ]ÿ^ÿ\ÿXÿXÿWÿWÿTÿOÿNÿJÿEÿDÿGÿQÿWÿYÿZÿeÿrÿvÿsÿvÿzÿyÿxÿuÿwÿrÿ_ÿXÿYÿ\ÿ_ÿdÿkÿxÿƒÿŽÿÿ‘ÿÿŒÿ‰ÿ†ÿƒÿƒÿ‚ÿ„ÿ‚ÿ~ÿqÿbÿaÿcÿfÿgÿ]ÿhÿ{ÿ‡ÿŽÿŽÿÿÿ’ÿ“ÿ˜ÿ™ÿšÿšÿ˜ÿ–ÿ•ÿ’ÿÿÿ‹ÿŒÿÿÿÿÿÿŒÿ‰ÿ‰ÿ‡ÿ‡ÿ†ÿ„ÿ~ÿvÿhÿ^ÿWÿUÿaÿjÿgÿhÿoÿvÿ}ÿ„ÿ‰ÿ‹ÿÿŽÿŒÿÿŒÿ‹ÿ‰ÿ†ÿ„ÿ„ÿÿÿ}ÿ}ÿyÿtÿoÿnÿjÿ]ÿSÿIÿeÿnÿnÿpÿuÿhÿUÿ\ÿjÿLÿoÿ¦ÿ´ÿ¸ÿ»ÿ½ÿ¿ÿ¿ÿ¾ÿ»ÿ´ÿ³ÿ·ÿºÿ»ÿ½ÿ¾ÿ¾ÿ¿ÿ¿ÿ½ÿ»ÿ°ÿ—ÿdÿFÿKÿSÿQÿOÿQÿUÿZÿ^ÿbÿgÿjÿmÿpÿrÿqÿrÿrÿuÿyÿyÿrÿsÿwÿqÿpÿpÿoÿqÿwÿvÿsÿhÿKÿEÿYÿpÿ„ÿˆÿ‹ÿƒÿÿÿ‚ÿ„ÿƒÿ„ÿƒÿƒÿƒÿƒÿ„ÿ…ÿ‡ÿ‚ÿ€ÿ|ÿyÿuÿpÿnÿlÿkÿhÿhÿhÿhÿhÿhÿhÿhÿiÿmÿrÿ{ÿ…ÿ‹ÿŠÿ‰ÿŒÿŠÿ‡ÿ„ÿ‰ÿ’ÿ•ÿ•ÿ’ÿ‘ÿ‘ÿ‘ÿÿÿŒÿŒÿŒÿŽÿ‘ÿ–ÿ–ÿ“ÿ‘ÿ‘ÿÿÿ’ÿ˜ÿÿœÿ™ÿÿŒÿ‹ÿ‹ÿŒÿŒÿŽÿŒÿŽÿŽÿŒÿŒÿŠÿ^‚^n^^n[€ZnZ€XnVSnQOpM~JqJ}GrE}Bv@}>x;}9y6~6x6~4z3~3z1~1y1~1y/~/z.~-z-~-{-~-{.~.{.~.{-~-{.~.{,~,{-|-{.}.|-}-|-},},},}-},},},}+~+~+~+~+~++~+}*~*|*~)|*~*|)~)|)~)|(~'|'~&|&~&|&}&{%}%{%}%{%}%{%~%{$~%{'~&{'~'{(}(z'}&z'~'{)~){(}(z*}*|*}*}*}*}+},|,},{.}.|.}/|0}0|1}1|2}1|1}1{2|2{3|3{3|4z4~4z6~6z5~5{7~7{7~7{8~7}7~7}7}7{7}7{6~6}6~6}6~6{7~7{7}7{6}6{6}6|4}3|3}3|3}3|33|22|2}2}2}2}1~1~1~0~0}00}0/}/}.}.}-|-}-|-},},},},~,~,€+-‚G‚dW‚RU€[€c{Vƒ,x!„"zƒ!~ ƒ"'‚0„<ƒM…R‚O…KG…K€aƒ]€QJ€KM„Yn„c~.„{%…1}R‚d`€V?‚,}&…"}"†"~"†!!„#%„%#…#~"„"}$…(})…,|.„2|4„4|9„:|<„>|?‡A{B‡AzBˆ>y;ˆH{_…t~q‚l€ji€p€u€tƒvv„q`‚Y~\ƒ]^ƒf~_„)~+‡/}0‰3}2Œ5}8Ž8|8?~HŽXi‹l‚_‰UƒMˆM„PŠS…USƒMŽE‚==‚<’<€<“?€C”F‚H•J„L”M†M‘Q†SVˆYY‰YŽW‰X\‰[ŒZ‰YŒXŠX‹T‰PNˆIŽE„CŒDM†S}W[{e€n|nƒp|r„u}v…s}u„vn€g\|X]_h}s~€u~’n‘€k‹‹l‰~‡o‡~†o…‡o…roavaaybeya~lu{ŠlŽe€e“€•h–€™i™€šj˜€—j”‚’eƒcƒ‘c‚Žc‚e‚Žg‹‚Šfˆƒˆd‡ƒƒe~ƒvjlƒbn^‚_vgous€spy€}j‚‡f‹‚ŒefŽ‚ŒeŠ‚ˆe‰‚„b„b}„{cw…ubt…sek…qjo…_jY„pcs…mWkˆlIsˆjVZ†Xu[}WŠusª¸wº„¼}¾¾~¼¸±†ª~µ‡º|¼‚¾|¾½~¾¿»€´ˆ›‹rQ˜KŠT T€QŸMzNŸNzNžSzVš\}`“d€i‹l‚o†o‚p…ps…y~ƒˆ~vˆwƒw†s†s†m‡i‡p‡r†pˆo€aˆBKp„}‰}ˆ~z€v€‚s‚‚‚nƒƒ„l„„„h„„‡jˆƒ…r‚~{{x€u|s‡ovljrh‘hoh“gng•gmh•jjm–sh~•ˆh‹Œq‰…Šzˆ{†~‡x}•t•“sttŽŒsŒ‹uŒ€z”•“z‘†wŽ‡x†–z~ƒ˜}”ƒ…Œ{‰ŠŠwŒŽŽt‹Œ‡t‡xƒŒ|Šƒ[ÿ[ÿ[ÿ[ÿ[ÿZÿYÿWÿVÿSÿQÿOÿMÿJÿHÿEÿDÿBÿ?ÿ=ÿ;ÿ9ÿ7ÿ7ÿ6ÿ4ÿ3ÿ3ÿ1ÿ1ÿ1ÿ1ÿ/ÿ/ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ%ÿ'ÿ&ÿ'ÿ'ÿ(ÿ(ÿ'ÿ&ÿ'ÿ'ÿ)ÿ)ÿ(ÿ(ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ,ÿ,ÿ,ÿ.ÿ.ÿ.ÿ/ÿ0ÿ0ÿ1ÿ1ÿ2ÿ1ÿ1ÿ1ÿ2ÿ2ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ7ÿ7ÿ7ÿ7ÿ8ÿ7ÿ6ÿ6ÿ6ÿ6ÿ6ÿ7ÿ7ÿ7ÿ7ÿ6ÿ6ÿ6ÿ7ÿ7ÿ7ÿ7ÿ6ÿ5ÿ6ÿ6ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ)ÿ*ÿ*ÿ*ÿ0ÿQÿfÿUÿPÿVÿ\ÿbÿ[ÿ-ÿÿÿ!ÿ ÿ ÿ ÿ&ÿ+ÿ9ÿHÿQÿTÿMÿIÿQÿeÿ\ÿNÿNÿPÿSÿeÿtÿKÿ!ÿ$ÿ/ÿNÿaÿYÿUÿKÿ;ÿ'ÿ"ÿ"ÿ"ÿ"ÿ!ÿ!ÿ#ÿ%ÿ%ÿ$ÿ#ÿ#ÿ"ÿ$ÿ'ÿ)ÿ*ÿ,ÿ/ÿ1ÿ4ÿ6ÿ6ÿ8ÿ;ÿ;ÿ;ÿ<ÿ;ÿ7ÿIÿcÿrÿsÿmÿfÿgÿjÿoÿpÿuÿ{ÿwÿmÿaÿZÿYÿ\ÿ^ÿ`ÿdÿ@ÿ-ÿ,ÿ/ÿ0ÿ2ÿ5ÿ8ÿ9ÿ7ÿ3ÿ6ÿAÿRÿ\ÿUÿKÿHÿPÿRÿUÿUÿWÿZÿUÿLÿ@ÿ=ÿ;ÿ:ÿ:ÿ=ÿ?ÿDÿGÿLÿLÿMÿMÿOÿRÿVÿYÿYÿYÿXÿYÿZÿZÿXÿUÿUÿUÿSÿOÿLÿIÿDÿAÿBÿHÿOÿVÿXÿbÿjÿjÿjÿlÿpÿsÿqÿsÿrÿkÿeÿ_ÿYÿZÿ`ÿoÿ{ÿ‡ÿÿ”ÿ’ÿÿ‹ÿ‹ÿ‰ÿ‡ÿ‰ÿ‰ÿ‰ÿ‰ÿ†ÿÿoÿaÿaÿaÿeÿkÿiÿqÿ…ÿÿÿŽÿÿÿ’ÿ’ÿ“ÿ–ÿ–ÿ—ÿ•ÿ”ÿÿŒÿ‹ÿ‹ÿÿÿ‘ÿ‘ÿ‘ÿÿŽÿÿŒÿŠÿˆÿˆÿ‡ÿƒÿ€ÿyÿrÿnÿjÿhÿnÿyÿ~ÿÿ…ÿ…ÿ‰ÿŒÿŽÿŽÿŽÿÿ‹ÿŠÿˆÿ‡ÿ„ÿ€ÿ}ÿzÿwÿuÿvÿrÿrÿrÿoÿnÿpÿqÿrÿnÿoÿqÿmÿkÿhÿfÿYÿSÿ_ÿcÿ|ÿ©ÿ·ÿºÿ½ÿ½ÿ»ÿºÿ¶ÿ¯ÿ¥ÿ°ÿ¸ÿ»ÿ½ÿ¾ÿ¿ÿ»ÿ¶ÿ©ÿ~ÿUÿMÿRÿRÿQÿOÿLÿMÿNÿOÿOÿOÿQÿVÿ[ÿ_ÿcÿgÿkÿpÿqÿqÿrÿtÿ|ÿ|ÿwÿqÿsÿxÿqÿjÿ`ÿmÿvÿxÿwÿnÿ\ÿaÿyÿ„ÿ‰ÿ„ÿ}ÿ}ÿÿÿ€ÿ€ÿÿ‚ÿ„ÿ„ÿ„ÿ…ÿˆÿ†ÿ…ÿƒÿ€ÿ}ÿ{ÿwÿtÿqÿmÿlÿhÿgÿgÿgÿgÿhÿhÿiÿmÿuÿ~ÿˆÿ‹ÿ‹ÿˆÿ‰ÿˆÿ‡ÿ‰ÿ‘ÿ•ÿ“ÿ‘ÿÿÿÿÿŽÿŒÿŒÿ‹ÿÿ‘ÿ”ÿ•ÿ‘ÿ’ÿÿÿÿ’ÿ›ÿžÿšÿ“ÿ‹ÿ‹ÿŒÿŠÿŠÿ‹ÿŒÿŠÿ†ÿŽÿŽÿÿŒÿŠÿZ‚ZnZZnZ€YnY€WnTSnQOpM~JqH}ErD}Bt?}=v;}:w9~7y6~4y3~2y2~0y0~0y/~/z.~-z-~-{-~-{-~-{-~-{-~-{,~,{,~,{,|,{,},|,},|,},},},}+}*}*}(}+~+~+~+~)~)(~(}(~(|(~'|'~'|(~(|(~(|'~&|&~&|%~%|$}$|$}$|$}$}$}$}$~$|#~$|&~&{&~&{&}&{&}&{'~'{(~({)})z)})|*}*{+}+{,},z,},{.}.{/}0{0}0{1}1{2}2{2}2z2|3y3|3y2|3z4~4z4~4z6~6{6~6{6~6{7~7z6~6z6}6{6}7{7}7{7}6{6}6|6}6|7~7|6~6|5~5|4~3|3}3{3}3{3|3{3|3{2}2|1}1|1~0|/~/|/}/}/}/}/}/}.}-}-}-}-}-},},}+}+~,},€+}()5‚_‚bƒSPU\€f|a‚2x„ x…}…"(„*‚7„C„R„T„O‚J„[‚iƒ[‚PK„QZ„pkƒ5| „,~J„c‚]T‚M€G*"}"„"~"„!"„$%„%&„%#„$~$…'},…+|*„*|.„/|/„1|2„6{9„8{4„1{Oƒw|pmg‚d€f‚i€mƒp~pƒw€pƒmc„\~YƒZ}]ƒ^`„Q~1†/~/ˆ2}2‹5}8Œ<};:|8<~=ŽK~EŒ=;‹E„QŒW†VŒX‡ZŒ\…ZŽQ„F>‚;’;€;“;€=”BF•L‚N”N…M“P…P’U‡ZZˆYYˆ[\ˆYUˆQO‰OL‰KJˆHD…CACŒKR†W|`ƒg|gƒh|j„m}o†m}p„md€cb{\~Zzd~sxŽr’’l’€Žk‹‹l‰€ˆm‹€ŒlŒŠn‰ƒqsbv`byh}ryu}}sŒ~‘m€hŽ€g€i‘€’k“€”m“€‘pŒ‚ˆo„‚„n‡‚j‘’i‘h‘‚‘g‚‹fŠƒ‰eˆƒ…dƒ{fyƒxhs‚rjxk„€†k‰€‹gŽgŽ‚g‹f‡‚ƒeƒ€c|ƒ|az„y_w„s]q…p]o…m[p†p]k†j]k†nZp‡qTmˆiReˆjXc…^ec€_vu¥µwº„¼|¾¼~ºƒ¹~µ…£y¬‹­y¸ˆ»~¼¹ƒ±‰“‹e˜J‡L SR¢QzQ¢QyPŸPyOŸOyOžMyMžQzTœZ{^”a~gi‚p‰qr…t‚s‡su‡s€v‡{‚|‡t‚q‰}‚ƒ‰ˆ„‰„ˆpy„€ƒ€†€}}y|}|{}€~yv‚‚qƒ‚ƒm…ƒ†iˆ„…o„„‚s€ƒ}w{‚w}r~p„nxjŠithŽfqg“gng•kko”xg”ˆlŠŒ‰vˆˆ{ˆwˆŒv““t€sttŽsv“{–}–‚‘{‡xŽˆ{—œz—‚ˆ}ˆ‰‹wŠŒ‹uŒŽ‰s„Œ€tŽˆŽxŽ„|‹„\ÿ\ÿ\ÿ\ÿZÿYÿXÿVÿUÿTÿQÿOÿNÿKÿIÿFÿCÿ@ÿ?ÿ=ÿ;ÿ:ÿ9ÿ7ÿ6ÿ4ÿ3ÿ2ÿ2ÿ0ÿ0ÿ0ÿ/ÿ/ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ(ÿ)ÿ)ÿ*ÿ*ÿ)ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ$ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ-ÿ.ÿ/ÿ0ÿ0ÿ0ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ3ÿ5ÿ6ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ7ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ6ÿ7ÿ7ÿ6ÿ6ÿ5ÿ5ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ1ÿ1ÿ1ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ/ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ)ÿ)ÿ'ÿ)ÿ:ÿdÿ`ÿUÿSÿVÿ_ÿiÿdÿ4ÿÿ!ÿ"ÿ#ÿ#ÿ$ÿ%ÿ+ÿ3ÿ>ÿMÿSÿPÿSÿgÿnÿZÿNÿNÿSÿaÿtÿXÿ#ÿ(ÿ>ÿaÿfÿYÿMÿBÿ*ÿ"ÿ"ÿ"ÿ"ÿ!ÿ"ÿ$ÿ%ÿ&ÿ'ÿ%ÿ&ÿ%ÿ%ÿ(ÿ0ÿ/ÿ*ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ+ÿ,ÿ.ÿ,ÿNÿrÿqÿkÿhÿgÿkÿoÿoÿoÿpÿvÿiÿXÿeÿaÿ\ÿZÿZÿYÿUÿXÿ4ÿ.ÿ.ÿ3ÿ6ÿ6ÿ6ÿ7ÿ9ÿ;ÿ7ÿ8ÿ@ÿ?ÿBÿ;ÿ1ÿ9ÿEÿOÿRÿPÿRÿUÿSÿQÿJÿDÿ=ÿ9ÿ9ÿ9ÿ;ÿ=ÿBÿFÿLÿNÿPÿRÿTÿTÿUÿWÿYÿZÿ[ÿ\ÿ\ÿYÿRÿLÿKÿIÿIÿGÿHÿEÿDÿCÿAÿAÿFÿKÿTÿ]ÿdÿdÿeÿhÿkÿlÿkÿmÿjÿ_ÿ`ÿhÿdÿ`ÿhÿwÿ‰ÿÿ’ÿ’ÿ‘ÿŽÿŠÿŠÿ‰ÿŠÿŒÿŒÿ‹ÿŒÿŠÿ†ÿuÿbÿ_ÿdÿkÿvÿ{ÿ‡ÿÿ‘ÿÿÿÿŽÿŽÿŽÿ‘ÿ‘ÿÿŽÿÿ‹ÿˆÿ„ÿ~ÿ~ÿƒÿ‹ÿ‘ÿ’ÿ‘ÿÿŽÿŽÿÿ‹ÿŠÿ‹ÿŠÿˆÿ…ÿ‚ÿ€ÿÿ~ÿ}ÿ€ÿ„ÿ‡ÿ‰ÿ‹ÿŒÿŽÿŽÿŽÿÿÿ‡ÿÿ~ÿxÿuÿqÿmÿmÿrÿsÿpÿpÿqÿoÿnÿmÿlÿkÿlÿoÿnÿnÿnÿcÿYÿ`ÿeÿdÿ`ÿZÿUÿ`ÿžÿ´ÿ¹ÿ»ÿºÿ¸ÿ¸ÿ¸ÿ´ÿ®ÿ°ÿ©ÿ²ÿºÿ¸ÿ¬ÿÿQÿLÿMÿRÿRÿRÿPÿOÿQÿPÿPÿNÿMÿMÿNÿLÿNÿQÿUÿYÿ^ÿcÿhÿlÿnÿrÿqÿpÿrÿvÿxÿÿ†ÿ‡ÿ…ÿzÿŠÿ‘ÿ’ÿÿ‰ÿÿyÿÿ…ÿƒÿzÿxÿ{ÿ{ÿ{ÿ|ÿ}ÿ}ÿÿ€ÿ‚ÿƒÿ…ÿ†ÿ‡ÿ†ÿ…ÿ„ÿ€ÿ}ÿ{ÿzÿwÿsÿoÿmÿjÿhÿgÿgÿgÿhÿkÿpÿzÿƒÿˆÿŠÿˆÿˆÿˆÿ‡ÿ‰ÿÿ”ÿ“ÿÿÿÿÿÿÿŽÿÿÿŽÿ’ÿ•ÿ•ÿ’ÿÿŽÿÿŽÿ“ÿ›ÿœÿšÿ‘ÿÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿŒÿ†ÿÿ}ÿŒÿÿÿŒÿŠÿY‚YoY‚YoY€XnW€UnSRnPNpLKqIFsF~Dv?~=v=~:w8~7x6~4y3~3y2~0y0~0y/}.z-}-z-~,z,~,z,~,{,~,{-~-|,~,|-}-|+}+|,},|+}+|*}*|*}*}*}*~(}(~*~*~)~)~)~)~)~)~)~)~(~(~(~(|(~({'~&{$~&{%~%{%~%{%}%|%}%|$}$}$}$}$}$|"}#|%}%|%}%|&~&{'~'|&}&|(}({)~){)~){*}*|*}+|,},{,}-{-}/{/}0{0~0{0~0{1~2{3~3{2~3z4~4z4~4z5~6z6~6z6~6z6~6z5~5z5~7z6~6z6}6z6}6z7}6{6}6{6}6{7}7{6}6|6}6|5}5|5}4|3}3|3}3z3}3{3}3{2}2|2}2|1}1|0}0|/}/~.}.}/}.|.}.|-}-}-}-}-}-}+}+~,~+€+}(ƒ''ƒ'B„kbƒT‚RW€_h|c‚8x…!z#‡$}$†$~(†)*†6„FƒQ…TƒX„l„i‚Y…P€TƒXh„s€A…)~3ˆF€OˆJ‚<„/€%ƒ!!„  „!~!ƒ"%„&€'…&€$…(~&„(~+ƒ/},„(}'„&{(„&z$„$y!…*zLƒr~n‚m€hhl€m€nƒmpƒr[ƒKh…f~`…\Z„Y~Q…Y~L†*~.‰1}6Š9|9Œ7}88}:7}57}86}32€8ŽD‚KM…KŽK‡MM…HE„B‘>€:’8€8”:€>”AE”LN”Q„T“W…X’V†VX†Z\ˆ]\ˆXPˆMJ‡LJ‡F’E‡GD…EE„DA‚FMWˆ`}a…b|e„h}i…i}k…f~Z]~gwh}jrp}oŽ€l‘€n‰p‰€‰p‰€ŒoŒ€Šlˆ‚Šm‹‚…mw‚dt_~fyp|z{|s‘“n’€k€ŒhŒ€‹h€ŽlŽ€‹p‰€…p‚€u}w„Šu’”p’o‚ŒlŒ‚Œh‚fŒ‚‹cŠƒˆc„cƒ€‚b„€ˆcŠ€Œg€Žh€ŽijŠ„i{‚vho‚gha‚\i_„ceh„k]l„pZq„rXo†oYn†lYl‡nXmˆlYU‡@_M‡Xa_†[eH‚@vYyŒ‡°w¶‡¹{¶„¯{µ‡·{«ˆ¯x±‰­z¶ˆ¶ª‹€…N—O„Q OzQ¢QwS RvRŸSwQŸPyNŸNyLŸOyOŸNyNžSyU›X|\–b~f‘imŒo}qŒt{|‹…}Ž‰–~•‡’€‡‰’ƒ˜ˆš…†ˆ‡„ƒ‡ƒƒv}vƒy|z€zz{z{z~€w‚‚pƒ‚†l‡„…p„„ƒr‚„€t„~yz‚u~q~o†lzkŒjtihoj“lls•|k…’ˆmŠ‹†u†~‰}ŠwŒu“€’t€s€ssŽsx’€••}’‡zˆx“ƒ™|ž|œ‚–}ŽŠ†ŠxŠŠ‹r‹Ž‹qŠŽ„s}Œ|sŒ‡‹w…‹|‰ƒYÿYÿYÿYÿYÿXÿWÿUÿSÿRÿPÿNÿLÿKÿIÿFÿFÿDÿAÿ?ÿ<ÿ<ÿ8ÿ7ÿ6ÿ4ÿ3ÿ3ÿ2ÿ0ÿ0ÿ0ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ$ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ+ÿ,ÿ,ÿ.ÿ.ÿ-ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ2ÿ2ÿ2ÿ3ÿ3ÿ4ÿ5ÿ4ÿ4ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ6ÿ6ÿ6ÿ6ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ0ÿ0ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ,ÿ+ÿ+ÿ+ÿ(ÿ%ÿ#ÿ)ÿHÿmÿaÿYÿWÿWÿ_ÿgÿgÿCÿÿ!ÿ$ÿ%ÿ&ÿ%ÿ%ÿ$ÿ'ÿ/ÿ;ÿJÿPÿ\ÿtÿeÿWÿUÿWÿ_ÿrÿjÿ2ÿ,ÿ,ÿ(ÿ)ÿ%ÿ#ÿ ÿ!ÿ!ÿ ÿ ÿ!ÿ!ÿ#ÿ$ÿ'ÿ)ÿ.ÿ/ÿ,ÿ,ÿ(ÿ&ÿ+ÿ)ÿ(ÿ'ÿ%ÿ$ÿ"ÿ"ÿ$ÿ%ÿWÿtÿmÿoÿlÿlÿnÿoÿkÿmÿrÿrÿFÿ>ÿhÿlÿeÿ\ÿXÿZÿPÿXÿWÿ0ÿ+ÿ1ÿ5ÿ9ÿ:ÿ<ÿ<ÿ;ÿ>ÿ;ÿ5ÿ5ÿ4ÿ6ÿ3ÿ.ÿ3ÿ9ÿDÿIÿHÿHÿFÿFÿEÿDÿBÿAÿ>ÿ:ÿ8ÿ8ÿ:ÿ<ÿ?ÿDÿGÿLÿOÿSÿXÿYÿVÿVÿXÿZÿ[ÿ\ÿXÿVÿPÿJÿKÿKÿHÿGÿFÿFÿFÿGÿGÿGÿFÿCÿIÿPÿXÿ]ÿ`ÿdÿgÿiÿjÿjÿcÿXÿ[ÿcÿiÿnÿwÿ‰ÿÿ‘ÿÿÿŒÿ‹ÿ‰ÿ‰ÿ‹ÿŒÿÿ‹ÿ‡ÿˆÿ‹ÿ…ÿyÿdÿ_ÿfÿtÿÿ‡ÿ’ÿ’ÿ‘ÿŽÿ‹ÿ‹ÿŠÿ‰ÿ‰ÿ†ÿˆÿŠÿŠÿ‰ÿ„ÿ€ÿ}ÿ|ÿÿ†ÿÿ”ÿ”ÿÿÿŒÿ‹ÿ‹ÿ‹ÿŒÿŒÿŠÿ‹ÿ‰ÿ‰ÿˆÿ‡ÿˆÿˆÿˆÿ‰ÿÿŽÿŽÿŽÿÿÿÿŒÿ‡ÿ‚ÿ}ÿuÿhÿXÿLÿIÿOÿRÿTÿ\ÿ`ÿbÿcÿfÿmÿpÿoÿoÿnÿnÿjÿeÿNÿ=ÿRÿ_ÿ^ÿVÿ>ÿ3ÿNÿpÿ¥ÿ´ÿ¶ÿ¯ÿ‹ÿ˜ÿ°ÿ—ÿ¦ÿ²ÿ°ÿ±ÿ ÿ˜ÿoÿMÿOÿTÿ]ÿWÿSÿTÿXÿWÿUÿRÿQÿQÿPÿOÿOÿOÿOÿOÿPÿQÿUÿXÿ[ÿ`ÿhÿqÿ}ÿˆÿÿ“ÿšÿÿ¡ÿ¢ÿžÿ™ÿ™ÿ—ÿ•ÿŽÿŠÿ…ÿÿÿÿÿwÿuÿyÿzÿzÿzÿzÿ{ÿ|ÿ~ÿÿ€ÿÿ…ÿ…ÿƒÿ‚ÿƒÿ‚ÿÿ~ÿ~ÿ}ÿxÿvÿtÿpÿnÿkÿiÿhÿiÿlÿtÿ€ÿ‡ÿŠÿˆÿ…ÿ…ÿ†ÿ‰ÿÿ”ÿ•ÿ’ÿÿÿÿÿÿÿŽÿÿÿÿ“ÿ•ÿ”ÿ‘ÿÿŽÿÿ•ÿžÿŸÿšÿ‘ÿÿÿ‰ÿŠÿŒÿÿŒÿˆÿ‚ÿwÿ}ÿ‹ÿŽÿŒÿŠÿˆÿW‚WoX‚XoW€WnU€TnTSnPNpMKqHEsE~Av?~=v<~;w9~8x5~3y2~2y2~0y/~/y-},z-}-z-~,z+~*z+~+{+~+{+~+{+~+{*}*{*}*{)})|)})|)}*})}))})~)})~(~(~(~(~'~'~'~(~(~(~'~'~'~&|&~&{%~%{%~%{$~${$~${$}$|%}%|$}$|$}$|$}$|%}%|%}%|%}%|&~&{'~'|(}(|(}({(~({)~){)}*{+}+{,},z,}-z-}/z/}/z/}/z0}0z1}2z2}2z4~4z4~5z5~5z6~6z6~6z6~6z6~6z6~6z6~5z5~5z5~5z5~5z5}6z5}6z6}5{5}6{6}6{6}6{6}6{4}3{3}3{3}3y3}3z2}2z2}1{/}1{1}1|/}/|0}0~.}.~.}.}.}.},},},},}+}+},},~+},€*},ƒ,}(„%%…*Kƒm‚b‚XV€W]€d}i‚Kz#‡#y$ˆ${$ˆ$}"‡!€ „&‚2‚?„Fƒk„w„cX…W€U†ev…P|#‡'{'‡%}#‡"~!† !„!!„ ~!ƒ##„%€.…9€@…<~:…/~(„+}+„)}&„%{#„"z#„!|=ƒsp‚kl‚o€n‚mpƒo€sƒk€:…+`…k}i„d}\…[|U‡K{[ˆA|)‰/|4Œ9|:Ž:{=@}@‘=}86}54|2-|149@€DD‚A=ƒ?‘A‚B’AB’?€<’:€9”8€;”=B•EJ•LƒQ”V„V“T…T’V…X‘Y†Z‘V‡TO‡J‘K†L“K„H’H…HH…JK‡JH†DDƒIŽMVŠ^€c†e~e„f~h„e}W‚X}d{m~ts€n‘oŽŒpŠ‰q‰€Šp‹€ŽmŽ€‰i‡‚‡iŠ‚†i}‚ho_~jwv|xŒ}’r€ŽmŠ€‰i‡€…hƒ‚ƒi‚n…€ŠqŒ‡t„v|€‚uˆ€t”‘sŠq‰‚‰oˆ‚‹k‹‹eŠŒbŠ‰aŠ€ŠaŽ€ŽaŽ€cŽ€d€g€ŽlŒ‰o†pz‚mq[‚Nr\‚as\]r^^jZ‚ZeX‚Yd`ƒedjƒmbm„m`e…WaG„=gG„JiK‚NlB?vM}^ƒ•w¯‹²z”ŒRz‡Ž¤w}Žšu³Œ·{ª‰…‚v“\‚MN|R¢XvQŸTuYav]TwUžVvV TvQ PvRžRvPNuOžPqR]qn–~sŒœz¢ˆ£¢„ ƒ›…Ÿ„£†Ÿ†šˆš†˜ˆ’†Œˆ‰‡Š…ˆ†‡~ƒƒ~€x~vƒx{xƒz~z{{z€|~~€wƒ…n„ƒrƒv€‚t~ƒt}ƒ{vwƒu|t€rƒo{l‹luk‘nmy“jˆ‘‹pˆŠ†x…}‡~Šu‘”s”€s€qŽ€ŽssŒŒvŽ€‘z••€‘|…Žy†’{šž|•ƒ‹}ŠˆŠv‹ŒtŒt†Ž}srŽxp†‹t‹†‰y†„WÿWÿWÿWÿUÿUÿTÿTÿRÿQÿPÿNÿLÿJÿHÿEÿDÿAÿ?ÿ=ÿ<ÿ;ÿ9ÿ8ÿ6ÿ5ÿ2ÿ2ÿ2ÿ0ÿ/ÿ/ÿ-ÿ,ÿ,ÿ-ÿ-ÿ+ÿ*ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ-ÿ-ÿ/ÿ/ÿ/ÿ1ÿ1ÿ0ÿ0ÿ1ÿ2ÿ2ÿ2ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ5ÿ6ÿ6ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ1ÿ0ÿ0ÿ1ÿ1ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ)ÿ&ÿ#ÿ(ÿ/ÿMÿmÿ`ÿVÿUÿXÿ^ÿfÿlÿ[ÿ&ÿ"ÿ$ÿ%ÿ#ÿ"ÿ ÿÿÿ!ÿ)ÿ3ÿHÿlÿqÿbÿWÿVÿZÿhÿpÿ2ÿ&ÿ#ÿ$ÿ#ÿ!ÿ!ÿ ÿ!ÿ!ÿ!ÿ ÿ!ÿ$ÿ"ÿ(ÿ.ÿ3ÿ7ÿ8ÿ2ÿ/ÿ+ÿ+ÿ-ÿ*ÿ'ÿ%ÿ#ÿ"ÿ#ÿ-ÿhÿnÿkÿjÿjÿmÿpÿlÿlÿtÿaÿ.ÿ5ÿ_ÿkÿiÿgÿcÿ`ÿbÿJÿ[ÿQÿ+ÿ+ÿ<ÿ>ÿ;ÿ:ÿ=ÿ?ÿDÿ>ÿ7ÿ5ÿ5ÿ9ÿ8ÿ3ÿ0ÿ2ÿ5ÿ8ÿ<ÿ@ÿ=ÿ:ÿ7ÿ:ÿ>ÿ>ÿ>ÿ?ÿ>ÿ=ÿ:ÿ9ÿ7ÿ:ÿ<ÿ?ÿDÿIÿMÿOÿSÿSÿQÿQÿSÿUÿVÿWÿTÿRÿNÿJÿKÿKÿKÿKÿIÿGÿJÿMÿMÿLÿIÿFÿAÿCÿIÿOÿWÿ\ÿaÿcÿfÿfÿcÿYÿZÿeÿpÿzÿ‡ÿŽÿÿŽÿÿ‹ÿˆÿˆÿˆÿŠÿÿŽÿŒÿ‰ÿˆÿ‡ÿ‰ÿ„ÿ}ÿhÿ_ÿlÿyÿ…ÿÿ‘ÿÿ‹ÿˆÿ†ÿ„ÿÿ~ÿ|ÿxÿyÿ~ÿ„ÿ‡ÿ‡ÿ‡ÿ…ÿÿƒÿ‰ÿŽÿ‘ÿÿ‹ÿˆÿ‡ÿ‡ÿˆÿŠÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿ‹ÿŒÿÿÿÿÿÿÿÿÿÿÿ‹ÿ‰ÿ‡ÿ…ÿ~ÿwÿoÿdÿ[ÿmÿmÿ[ÿRÿWÿVÿYÿ`ÿ\ÿXÿXÿYÿ^ÿdÿfÿfÿcÿ\ÿUÿFÿ5ÿ1ÿGÿQÿOÿRÿZÿ]ÿzÿ¢ÿªÿŽÿ_ÿ”ÿ‰ÿsÿŸÿ²ÿµÿ¨ÿvÿVÿIÿIÿLÿOÿOÿOÿSÿVÿVÿRÿOÿTÿUÿTÿUÿTÿRÿQÿQÿRÿQÿNÿMÿaÿ~ÿ–ÿ¥ÿ«ÿ°ÿ¯ÿªÿ§ÿ¡ÿÿœÿžÿžÿÿœÿœÿœÿ˜ÿ“ÿ’ÿÿÿ‡ÿ€ÿyÿwÿxÿxÿzÿzÿ{ÿ{ÿ{ÿ{ÿ|ÿ}ÿÿ‚ÿƒÿ€ÿ€ÿ€ÿ€ÿ€ÿÿ~ÿ~ÿ|ÿzÿyÿwÿtÿrÿoÿmÿnÿqÿzÿƒÿˆÿ‰ÿˆÿ†ÿ†ÿ‰ÿÿ”ÿ•ÿ”ÿÿÿÿŽÿŽÿÿÿŒÿÿÿ’ÿ”ÿ”ÿ‘ÿÿÿ’ÿ˜ÿœÿžÿ™ÿ‘ÿ‹ÿ‰ÿˆÿ‰ÿ‹ÿÿÿ‰ÿ€ÿvÿoÿxÿˆÿŒÿŠÿˆÿ„ÿV‚VpV‚VpTToTRoQOnNLpJHqGEtC}AvA}>v<~:w8~7x5~4x3~3y2~0x/~/x-},y+}+y+}+y*}*y*~*{*~*{*~*{*~*{)~)|)~){)~)})~)}(~((~((~(~(~(~'''''~''~''~'~'~'}&~&|&~%|%%|$$}$~#|#~#{"~"{$~#{$}$}$}$}$}$|%}%|%}%}&}'}'}'}'}'}(}(z(}(z(})z)})y*~*y+~+y,}-y-}.y-}.x0}0x0}0x2}2x2}2z3}4z3~3z5~5z5~6z6~6z6~6y6~6y6~6z6~6z5~5z5~5z5~5z6~6z6~6z6~6z6}6y6}6y6}6z6}6{6}6z4}4z3}3{2|2{1}1{1}1{1}1z0}0|0}/|0}.|/}/}.}-}.}.~-},~+}+~+}+~+}+})}))}*€,}-ƒ-}+…)}%‡$'‡-M†ob‚UT‚W]‚d~l„_z4‡!x$ˆ#y"‡!}ƒ€ ‚!€%ƒ*ƒE‚pƒn…`€Y†Y€`†r}U‡#{%‡"|"†"}!† ~ „ ~ ƒ ~"ƒ$~(„-/…27…6~1†,~-†.}/„,|)ƒ%{$„"{"„Xvƒm€g‚g€lƒlm…uk…O~3…C~aƒh~j…j~h†d{^ˆLz\‹[y5Œ.{:Ž@|?@|@B~B’>~83~5‘:@’B~<’4~3’7~9‘9765‘6€7‘:€:‘<€>’<€<’::“78”;>•CI•OQ•Q„Q•O„N•O„Q•T„U’S…Q’L„L’K„K’L„L’K…KM‡NŽNˆNŽLˆGD†AB„FOƒTŠ\a†ce„c~\[~i|v~‚s‰‹qŽŒrŠ‡t…†s†€ˆpŠ€‰n†€ˆiˆˆhˆƒ„g~‚jnb€py{|‡x}r‹€ˆk„‚g€‚|cw„qgoƒspy|s€ƒs†~‡s„~‡t‰€s€Œq‹ˆo‡ˆm‡‚ˆjŠŠeŠŠdŠ€‹b€b€a’€’cehŒ€ˆmˆ€…q‚€€rx‚sssƒsvrƒqtigujhtiƒircƒWoPQpW[mZVlT‚UlU€OoC€CnT‚SqVYz]~g„eytŽ¤w€t™‰r…£r¯¯z¦|€W˜J}H¡IvM¤LtL¢RtU¡QtO PuP RtU WtW UtQŸRuT OoTŸfkˆ™¦r²Š¹z¼º¸‚°„©ƒ¥††…›‰›…ž‰ … ˆ¡‡Ÿ‡œˆ™…™‡•z†‡w{‚v}{~zz~z~{~{€{z~y|z~t‚ƒ}y}„~|~‚v‚u}ƒ}r|‚|vy‚w|r€q„pzpŠuq}„nŠ‰t…‰ƒy‡zŠ}Žt•€”u’€t€t€tt€Žx’|“|‚|Žƒ‘y–š}›|˜‚“‰ˆŠyˆŽŠsŒŽrŒ†r{rqnwo…‹‰tŠ†ˆy……UÿUÿTÿTÿSÿSÿSÿPÿPÿNÿLÿKÿIÿGÿEÿDÿBÿAÿ@ÿ=ÿ<ÿ;ÿ7ÿ5ÿ7ÿ4ÿ3ÿ3ÿ1ÿ/ÿ/ÿ/ÿ-ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ"ÿ"ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ+ÿ,ÿ-ÿ.ÿ.ÿ.ÿ.ÿ0ÿ0ÿ0ÿ0ÿ2ÿ2ÿ2ÿ2ÿ3ÿ4ÿ4ÿ4ÿ4ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ/ÿ/ÿ.ÿ/ÿ/ÿ.ÿ-ÿ.ÿ.ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ)ÿ(ÿ)ÿ+ÿ+ÿ+ÿ*ÿ(ÿ&ÿ(ÿ+ÿ.ÿ/ÿPÿnÿdÿXÿTÿWÿZÿ^ÿfÿfÿCÿ"ÿÿ"ÿ ÿÿÿÿ ÿ ÿÿ!ÿGÿwÿmÿ^ÿVÿZÿeÿqÿ:ÿ!ÿ#ÿ#ÿ"ÿ!ÿ ÿ ÿ ÿ ÿ ÿ#ÿ&ÿ-ÿ2ÿ1ÿ1ÿ1ÿ,ÿ+ÿ,ÿ-ÿ1ÿ2ÿ.ÿ*ÿ'ÿ$ÿ#ÿ<ÿsÿjÿjÿgÿiÿkÿpÿtÿ_ÿ<ÿ;ÿZÿeÿgÿjÿfÿgÿgÿaÿMÿVÿ_ÿFÿ2ÿ9ÿEÿ?ÿDÿCÿEÿCÿ<ÿ;ÿ7ÿ5ÿ6ÿ>ÿFÿEÿAÿ9ÿ5ÿ5ÿ5ÿ6ÿ4ÿ3ÿ3ÿ2ÿ5ÿ9ÿ9ÿ;ÿ;ÿ<ÿ<ÿ;ÿ:ÿ9ÿ9ÿ<ÿAÿEÿJÿPÿRÿTÿRÿMÿLÿMÿOÿPÿQÿQÿMÿLÿKÿKÿKÿLÿLÿNÿNÿOÿOÿOÿOÿMÿGÿDÿAÿ@ÿBÿGÿKÿRÿYÿaÿeÿcÿ[ÿ^ÿoÿ|ÿˆÿ‹ÿŠÿŒÿŠÿ‡ÿ…ÿ…ÿ†ÿ„ÿ…ÿ„ÿ‚ÿ€ÿ†ÿ†ÿ†ÿ‡ÿƒÿ|ÿjÿcÿqÿ~ÿŒÿÿÿŠÿ†ÿƒÿÿ~ÿyÿpÿgÿgÿmÿsÿvÿ{ÿ~ÿ‚ÿ…ÿ…ÿ‰ÿ‰ÿŒÿ‹ÿ‹ÿ‹ÿˆÿ‰ÿ‡ÿˆÿ‡ÿ‡ÿ‰ÿ‰ÿŠÿŒÿÿÿÿÿÿÿÿŽÿŽÿÿÿ‹ÿ‡ÿ…ÿ‚ÿ€ÿ~ÿvÿrÿpÿpÿpÿqÿsÿtÿqÿoÿcÿ]ÿaÿSÿEÿEÿPÿWÿTÿNÿHÿIÿNÿPÿMÿKÿQÿQÿPÿTÿSÿfÿaÿ`ÿ‘ÿ”ÿ|ÿÿ¥ÿ¨ÿ©ÿ¬ÿªÿŸÿmÿLÿHÿHÿMÿNÿMÿMÿNÿPÿPÿSÿTÿTÿSÿRÿSÿTÿRÿQÿQÿPÿUÿnÿÿªÿ·ÿ»ÿ½ÿ¿ÿ¼ÿºÿµÿ­ÿ¨ÿžÿžÿŸÿžÿžÿ ÿ¤ÿ£ÿŸÿÿŸÿ¡ÿ ÿ™ÿÿ|ÿuÿ{ÿ{ÿzÿzÿ{ÿ{ÿyÿyÿzÿyÿ}ÿÿ€ÿ{ÿ{ÿ|ÿ}ÿÿ~ÿ~ÿ}ÿ}ÿ}ÿ}ÿ{ÿzÿwÿtÿrÿrÿxÿ€ÿ‡ÿŠÿˆÿƒÿ„ÿ‰ÿŒÿ‘ÿ•ÿ“ÿÿÿÿÿÿŒÿŒÿŒÿ‹ÿŒÿÿ’ÿ“ÿÿÿÿ”ÿ˜ÿšÿ˜ÿ”ÿÿ‹ÿ‰ÿ‰ÿ‰ÿŠÿŒÿÿŠÿ„ÿxÿnÿmÿxÿ†ÿ‡ÿˆÿˆÿ…ÿT‚TpR‚RpSQpPOpONnLIpHFqDCtA~?v>~‘=;’:<“@D•J‚M•PS•V‚U•RƒP•O‚N–N‚M”LƒK”KƒK•LƒL•M„N’N…PQ†PO‡OM‡HF…CA„@‘@ƒCHRŠZ€a†a`f~xzƒŠq‹‰s‰†t„„u‚u„‚s|€yq{€„n‡…h„ƒ€e{‚jje€ss‚~ŽuŒo‡€ƒi‚€e~ƒvcl†`l_†drk…ous‚wv|€u‚~…t‡€Šs‰€‰oŠŠm‰‰k‡‚†f†ˆd‰‰cŠ€‹bŒ€dŠŒeŒŒgŒ€ŠkŒ€mŒ€‡o„€q}|sz€wttƒqwqƒqvp„qtq„nr\„9q4ƒ8s9ÿ>ÿ=ÿ?ÿDÿHÿKÿMÿPÿRÿTÿSÿSÿQÿPÿNÿMÿKÿKÿKÿKÿKÿLÿLÿNÿPÿPÿQÿTÿSÿPÿOÿLÿIÿFÿCÿBÿ@ÿ>ÿ?ÿ@ÿGÿQÿUÿ[ÿbÿmÿ|ÿ†ÿŠÿŠÿ‰ÿ‡ÿ„ÿƒÿ‚ÿ‚ÿ‚ÿÿ}ÿvÿqÿuÿÿ‡ÿ…ÿ„ÿ€ÿ{ÿjÿiÿvÿ…ÿÿÿŠÿƒÿ‚ÿ€ÿ~ÿ}ÿxÿmÿaÿ]ÿ`ÿeÿiÿmÿqÿvÿ}ÿÿ„ÿ„ÿ†ÿ‰ÿ‰ÿ‡ÿ‡ÿˆÿ‡ÿ†ÿ†ÿ†ÿ†ÿ†ÿ†ÿ‡ÿ‰ÿŠÿ‹ÿ‰ÿˆÿ†ÿ†ÿ‡ÿˆÿˆÿ‡ÿ‰ÿˆÿ„ÿ~ÿ|ÿ{ÿzÿwÿvÿtÿpÿqÿqÿpÿqÿnÿMÿ)ÿ5ÿ6ÿ8ÿ8ÿ<ÿCÿDÿJÿPÿSÿOÿKÿHÿKÿMÿVÿZÿZÿVÿ^ÿ_ÿlÿ„ÿ’ÿ˜ÿ™ÿžÿ¨ÿªÿ§ÿ¤ÿwÿDÿHÿIÿIÿKÿJÿIÿLÿLÿMÿJÿJÿLÿKÿJÿLÿMÿLÿMÿMÿTÿsÿœÿ¯ÿ¸ÿ¼ÿ¼ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¼ÿ¹ÿ³ÿ­ÿ¤ÿ ÿŸÿÿšÿ™ÿ˜ÿ›ÿÿžÿ¥ÿ¥ÿ¡ÿ›ÿ‹ÿ|ÿzÿ}ÿ{ÿzÿ}ÿ|ÿzÿzÿxÿxÿ{ÿ}ÿÿvÿsÿuÿwÿzÿ{ÿ|ÿ}ÿ}ÿ|ÿ|ÿ|ÿ|ÿ{ÿyÿwÿtÿzÿ‚ÿ‰ÿ‰ÿ†ÿ„ÿ†ÿˆÿÿ–ÿ“ÿÿ‘ÿÿÿŽÿŒÿŒÿ‹ÿ‹ÿÿÿ‘ÿ‘ÿÿÿ“ÿ—ÿ›ÿšÿ–ÿÿŒÿŠÿˆÿ‡ÿ‰ÿŒÿÿÿ‰ÿ‚ÿyÿqÿoÿnÿ{ÿ‡ÿˆÿˆÿ‡ÿ…ÿQ‚QpPPpO€OoM€MoMKnHFrECsBAuA?w;;w9~8w6~6w5~4w3~2w0~/x/~/z-~,z-~+z,~,z*~*z*~*z)~){)~)}(~(}(~(~(~((~(~(~(~(~((~('~''~'(~(~'~'~&~&~&~&~%%%%%%~$$|$~$}$~$}#~#|#~#|#~#|#~#|#}#|$}$|$~$|%~%|%~%}$~%}'}'}'}'}'|(})|){)}*z*}*y+}+y,},y-},y-}.w/~0y1~1y23x22x34x55x55x55x55x44x55y77y66z66z6~6y6~6y5~5y5~5y5~5z5~5|5~5{5~5{5~5{4~4{3~3{3~3{3}3|1}1|1~1{0~0{/}/{/}/{.}.|.}-|,},},}+~,},~+}+~*}*}*}*})})~)})~(|)€)|-‚/|0….|+ˆ*|+ˆ/|3ˆ4€7‡7€B‡c€n†^€S…QS„U€]ƒc}jƒ[y1„x„ z ƒ| ƒ$$„3‚l†r‚b†^[†am†0}†| †"|!…"~"…$(„*,„*},„+}+„+~.„/~0ƒ1~2ƒ/|,„.}`‚o~gƒhgƒi}j…p}h‡d~j†k~h†e}`†`}a†c|bˆ]zV‰H{8Œ2z05z76{8>|@‘<|84~36~41~07AFDB:4~30~.0}1‘3~4‘5~4‘679€<>A‘AB’E‚F“H‚K“N‚O•OQ•SR–P€N–LJ–II•K‚K•MƒM•OƒP’S„SU†SP‡NL†IF†DD„@‘A„A>ƒAEK‹T€a…r€„{‰‰t‰‚ˆs†„y€x€€uzsn€kuv€pˆˆh„‚€fyjkjwq…Œq‹‡l‚€g‚}dz„vek†dn_‰^t_ˆewh…mxs‚yv€…u†€†qˆ€ˆo‡‡k„„j‡…g……e……d……e‡‚†fƒ‚h|ƒ}l~„~q‚‚q„†r†r~€}rz€yuwvuuuqtupv‚knRƒ/p.:w<{>{CwG{KyGxG|HvO}PvQ~XvX€Vs\€`pa‚eri€d}mz‹Š—p“‘œo§©s¦ zm–A{FŸJxI¥LuK¥JtI¥JtI£JuJ¢JuJ¢JtJ¢ItK¡JoS¡lj–™®q·‡¼{½¾~¾€¾~¾€¾~¾€¾~¾€¹€µ„®†¥‰£†¡‰†šŠ™†™‰˜‡˜„‡£v£…Ÿj•ƒ„iz‚~u}€{~{{{€yzx}x€z|~sƒp|tŠuyw‰w~|€{€|y~€~w~‚}x}‚{zyv{zƒ†‰u‰‰…w‚‚‡{Œz‘~•u”‘v€Žvv‹ŠuŠ€‹xŒ|’~‘€‘}“€–~™|™„•~‘€‡Š{‰Šˆv‰ŽŠs‹ŽsŒˆuŽuro‘oln”{k…ˆsˆ…‡x†ƒOÿOÿNÿNÿMÿMÿLÿLÿLÿJÿGÿFÿDÿBÿ@ÿ@ÿ@ÿ=ÿ;ÿ:ÿ:ÿ7ÿ6ÿ6ÿ4ÿ3ÿ2ÿ2ÿ0ÿ/ÿ/ÿ/ÿ-ÿ,ÿ+ÿ+ÿ,ÿ,ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ)ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ.ÿ.ÿ0ÿ0ÿ1ÿ1ÿ3ÿ2ÿ2ÿ2ÿ3ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ)ÿ-ÿ/ÿ2ÿ1ÿ/ÿ-ÿ.ÿ1ÿ4ÿ7ÿ:ÿ=ÿ>ÿ>ÿUÿjÿeÿXÿSÿRÿVÿXÿ_ÿiÿhÿNÿ%ÿÿ ÿÿ ÿ%ÿ(ÿ(ÿ;ÿqÿrÿ]ÿ_ÿaÿkÿbÿ ÿÿÿ"ÿ!ÿ!ÿ!ÿ$ÿ(ÿ*ÿ,ÿ,ÿ.ÿ-ÿ-ÿ-ÿ.ÿ/ÿ0ÿ1ÿ1ÿ0ÿ,ÿUÿrÿfÿcÿdÿhÿkÿpÿnÿjÿhÿgÿeÿfÿdÿaÿaÿbÿcÿdÿfÿYÿ5ÿ1ÿ3ÿ3ÿ3ÿ4ÿ5ÿ9ÿAÿGÿCÿ9ÿ4ÿ4ÿ5ÿ1ÿ1ÿ0ÿ4ÿ>ÿEÿFÿAÿ:ÿ5ÿ1ÿ/ÿ.ÿ0ÿ1ÿ3ÿ5ÿ6ÿ6ÿ7ÿ7ÿ9ÿ<ÿ>ÿBÿBÿCÿEÿFÿJÿJÿLÿLÿLÿNÿOÿQÿSÿOÿMÿLÿIÿIÿJÿJÿLÿMÿPÿRÿTÿTÿUÿSÿOÿMÿJÿHÿFÿDÿCÿAÿAÿAÿ@ÿ>ÿ?ÿBÿOÿ[ÿpÿƒÿŠÿŠÿˆÿ…ÿ‚ÿÿÿÿ€ÿ~ÿzÿqÿhÿlÿtÿ‚ÿ‡ÿ‡ÿ†ÿÿyÿmÿlÿxÿ…ÿ‰ÿ‰ÿ„ÿÿÿ|ÿ{ÿ|ÿvÿkÿbÿ_ÿ\ÿ[ÿ_ÿ`ÿfÿmÿuÿ~ÿˆÿ‡ÿ„ÿ†ÿ†ÿ‡ÿ‡ÿ„ÿ„ÿ…ÿ„ÿ…ÿ…ÿ„ÿ„ÿ…ÿ…ÿƒÿ€ÿ{ÿvÿqÿpÿqÿoÿuÿ|ÿ~ÿ‚ÿ…ÿ‚ÿÿ~ÿ|ÿ{ÿ|ÿ{ÿ{ÿ{ÿyÿxÿyÿ{ÿwÿgÿJÿ@ÿAÿFÿPÿ[ÿ^ÿXÿKÿMÿXÿ\ÿ]ÿ^ÿ\ÿZÿaÿdÿeÿfÿkÿkÿcÿ{ÿ”ÿ‹ÿ›ÿ§ÿ§ÿ¥ÿ™ÿcÿCÿHÿJÿIÿJÿKÿJÿIÿHÿHÿIÿIÿJÿJÿHÿHÿIÿIÿOÿfÿÿªÿ·ÿºÿ¾ÿ¾ÿ½ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿºÿ¶ÿ«ÿ§ÿ£ÿ¡ÿ›ÿ™ÿ™ÿšÿ–ÿ—ÿŸÿ¢ÿ¢ÿœÿŽÿ€ÿ{ÿ{ÿ|ÿ{ÿ{ÿ{ÿzÿxÿxÿyÿ{ÿ}ÿnÿlÿnÿqÿtÿtÿxÿyÿ{ÿ|ÿ}ÿ|ÿ|ÿ|ÿ{ÿzÿyÿ{ÿƒÿŠÿˆÿ„ÿƒÿŠÿŽÿ•ÿ•ÿ“ÿÿÿŽÿŽÿŽÿŠÿ‰ÿŠÿ‹ÿŒÿÿ‘ÿ’ÿ“ÿ—ÿ—ÿ˜ÿ–ÿ‘ÿŒÿŠÿ‰ÿ‡ÿŠÿ‰ÿŠÿ‹ÿÿŠÿ„ÿ}ÿuÿoÿoÿnÿ}ÿ†ÿ‰ÿ‰ÿ‡ÿ†ÿL‚LpLKpK€KpI€IpJGqDBqCAs@>u>;u:8u7~7w5~5w4~2w1~0w0~0w/~.x.~.y,~+y,~,z*~*z*~*z)~){)~)|(~(|(~(}(~(~(~(}(~(})~)(~('~''~''~''~'&~&'~&&&€&%%%%%|$~$}$~$}#~#|#~#|#~#|#~#|$}$|$}$|$~$|%~%|%~&}'~'}(}(}'}'}(}){*}*z*}*z*}+y+},y,}-y-~-y/~/w0~0w1~2w43x33x34x45x55x55x66x55x55y55y55y55y6~6y6~6y5~5y5~5y5~5y5~5z5~5{5~4{4~4{3~3{3~3{3~3{2}2|1}1|1}0{/}/{/}.{.}-{-},|-}-|*}*}+}+~*}*~)}(~(}('}''}'~(}(~(~()~-1|4„4|2ˆ/|2ˆ3|6ˆ:|<ˆB|EˆA}>ˆI}dˆm|\†S~Q…QUƒZ~f‚m{a‚9zƒz"…%{'‡,~,‰H€t‰j‚c‡``†p}F†{†!{ …"|!…%&„+-„/~2„0~.„.~0„0~0ƒ2~2‚1}Lƒr~h‚bcƒe~j„p}p†i}g‡d~d†d}d‡d}c‡e|fˆd{j‰b{EŒ1{25z53z48|:DX]933Ž3~2Ž1~03:E‚KF‚;53Ž.~,Ž/}13~56~7879€;>B‘CD’F‚F“I‚G“J‚J•KL•NO–R€R–R€N–K€I–H€H–JJ•MƒO“S„T‘U†RM‡K‘I…G‘D…C‘B„@’@„@‘Aƒ@@„?F„S‹i„z‚„‚‡{…‚x~y}x|xu€jwc€iwvƒrŠˆl†‚‚jyopmyt…~ˆr†‚m~j|‚|dz„ugf…eqf‡evd‡^w^†azi‚tz€ˆw‡€…r…€…p††i„„i„„i„…fƒ…eƒ‚e€‚zivƒmnb„aveƒeys{w|€}u€‚u€€u|€}sr€m‚h€‚~g~„ykpƒhrd€eyh~i{i€dvW€[s`at`ƒ`tbcpf‚gmj„kmo„htblƒ‹s–ŽŸp§©r¤‘zS™C{F¢KwK¥ItJ¥ItH¥FtG£GuG¢HuJ¢GuG¢HqI¢]k„£r²Œ¹{¼½}¾€¾~¾€¾~¾€¾~¾€¾~¾€¾}½€¸€³…­†¥‰Ÿ†›Šš†˜‰–‡•†‘‡“—…™q™„”g…„zmyz{z{€z€y€xx}yy{~p„jylŒltqrytˆv}y€|€~x|‚zs|‚|u|€z{}|ƒˆx‡…„y„Š|v–~”tw€uŽuŠ‰vŠ‹yŽ~’y’”{˜šz™‚˜€“‚Œ†‹|ŠŠ‰vˆŒ‰sŒŒ‹s‹Ž‹s‡s|tqn’okn‘|mˆ‹‰v‰„ˆ{‡‚KÿKÿKÿIÿJÿJÿHÿHÿGÿHÿFÿDÿBÿAÿ?ÿ=ÿ<ÿ:ÿ:ÿ8ÿ7ÿ7ÿ6ÿ6ÿ3ÿ2ÿ1ÿ0ÿ0ÿ0ÿ0ÿ.ÿ-ÿ-ÿ,ÿ+ÿ,ÿ,ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ'ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ&ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ/ÿ0ÿ0ÿ1ÿ1ÿ2ÿ4ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ1ÿ1ÿ1ÿ0ÿ/ÿ/ÿ/ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ)ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ)ÿ-ÿ1ÿ6ÿ7ÿ5ÿ2ÿ4ÿ:ÿ<ÿ>ÿBÿFÿJÿBÿCÿ@ÿ?ÿVÿkÿbÿWÿQÿPÿSÿ[ÿ_ÿgÿnÿ^ÿ4ÿÿ$ÿ+ÿ,ÿ-ÿ.ÿOÿxÿjÿ_ÿ_ÿ`ÿlÿ4ÿÿÿ ÿ"ÿ$ÿ&ÿ&ÿ)ÿ-ÿ1ÿ2ÿ3ÿ2ÿ2ÿ4ÿ4ÿ4ÿ4ÿ1ÿIÿsÿmÿfÿfÿfÿiÿnÿpÿhÿeÿdÿdÿdÿdÿdÿeÿeÿgÿdÿdÿYÿCÿ7ÿ5ÿ3ÿ4ÿ4ÿ3ÿ7ÿ;ÿ?ÿCÿLÿNÿ7ÿ2ÿ3ÿ3ÿ2ÿ2ÿ1ÿ2ÿ8ÿCÿHÿFÿ=ÿ8ÿ3ÿ0ÿ.ÿ.ÿ/ÿ0ÿ1ÿ5ÿ6ÿ6ÿ6ÿ9ÿ;ÿ<ÿ@ÿBÿDÿFÿFÿGÿFÿGÿHÿHÿIÿMÿPÿPÿQÿNÿLÿKÿIÿHÿHÿHÿHÿJÿNÿPÿSÿTÿPÿMÿKÿIÿGÿDÿCÿAÿ?ÿ@ÿ@ÿCÿDÿCÿAÿAÿHÿYÿjÿtÿ|ÿ€ÿ€ÿ~ÿ}ÿ|ÿ~ÿ|ÿwÿnÿbÿbÿkÿwÿ„ÿŠÿ‰ÿˆÿ„ÿ{ÿoÿmÿyÿ„ÿ‡ÿƒÿÿ}ÿzÿ|ÿ|ÿyÿuÿlÿhÿjÿhÿeÿ`ÿ^ÿ^ÿiÿvÿ…ÿ‹ÿŠÿˆÿ„ÿ„ÿ„ÿ„ÿ„ÿ„ÿ„ÿ„ÿ„ÿ„ÿ†ÿ…ÿƒÿ€ÿ}ÿwÿoÿeÿWÿNÿQÿXÿmÿvÿyÿ{ÿ}ÿÿ€ÿÿ~ÿÿÿƒÿ†ÿ‡ÿ†ÿˆÿˆÿ‡ÿÿ}ÿwÿqÿsÿsÿqÿnÿjÿdÿWÿ_ÿaÿ`ÿ_ÿ_ÿ`ÿeÿeÿgÿhÿlÿoÿgÿcÿgÿ{ÿ•ÿžÿ¥ÿ¥ÿ£ÿ‡ÿJÿEÿKÿJÿIÿHÿFÿHÿGÿGÿGÿGÿGÿGÿGÿIÿHÿFÿLÿnÿšÿ¯ÿ¸ÿ¼ÿ¿ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ½ÿ»ÿ´ÿ¬ÿ¦ÿ¡ÿÿ™ÿ•ÿ‘ÿÿÿ‰ÿ…ÿ„ÿ‹ÿÿ…ÿ{ÿyÿzÿyÿyÿzÿzÿyÿyÿzÿ{ÿ€ÿqÿjÿgÿgÿnÿqÿpÿsÿvÿxÿzÿ|ÿzÿ{ÿ{ÿ}ÿ{ÿ~ÿ…ÿˆÿ‡ÿƒÿ…ÿÿ’ÿ•ÿ’ÿÿÿÿÿÿŒÿŠÿŠÿ‹ÿŒÿÿ”ÿ—ÿ˜ÿšÿ™ÿ—ÿ’ÿŒÿŠÿ‰ÿ‰ÿˆÿ‰ÿ‹ÿ‹ÿ‹ÿ‹ÿŠÿ†ÿÿyÿtÿnÿnÿoÿ}ÿ‡ÿ‰ÿ‰ÿ‰ÿˆÿIIpIIpH€HqG€EqD€DpC€BrB@q?>t<~;t9~8u8~6x6~5w4~3x0~0x0}/v/}-w.}-x-},x,~+{*~){)~){*~*|)~)|(~(}(~(}(~(}'~'})~)})~)})~)~)~)~)~)~(~('~''~'€&~&€%~%%~%%%%%|$$}$$}$~$~$~$~$~$|%~%|%~%{$~${%~%|&~&|%~&}'~(})~)}*~*})~){*~,{+~+{,~,{.~.z.~.z/~/y/~0w1~1x2~2x3~3w4~4w44v64v56w66w66w66y66y66y66x66x44y55y5~4z5~5z6~4{4~4{4~4{3~3{4~2|2~2|2~2|2~2|1~1{0~0{/~/|.~.|.~-},~,}+~+}*~)})~)})~)}'}'~&}&~'~'~(~(~'~&%~%&'~)+€1~6…:}9ˆ7z6‰>z?‰BzH‰LxM‹JyICz?<|P‹b~eˆZ~R…RU‚X€\€_inzR„1v)ˆ)y)Š)})ŠS‚s‡b‚Z‡`€j‡[{ ‡"z$†${&†*|*†*}.„02‚45ƒ4~5ƒ7}7ƒ5~=ƒi~kƒh€h„kk…l|q‡g}d†e}gˆh~hˆc{d‰ezi‰h{^‰G~@ŠB~3Ž3|22|27|FD}@73Ž212~1Ž1|1Ž1}46~6‘:@‘C<;€6.~./~/1~2Ž3~4569;‘<=‘AC‘DE’FF“GE”GH”IL–NN–M€M–K€I–H€H•G€G•IL”NƒP“R„O“L„J“I„G“E‚D“Cƒ@“@ƒ@“BƒD’D„DD†EI†SŽ`„k†t‚x€z‚{z{|vvqwh^y`€jzw„v‹€ŠoŠ‚†n}opn€{sƒ~ƒqƒm|€{h{{f|‚{lsƒkvj†jxh†eub…cxnƒ~xtŠ†qƒ‚mj‚€ƒiƒ€ƒi‚€„g…€ƒgfztlf„^qM…C|D„Vms}w‚zx|‚~x}ƒ~ur‚„o‡‹nŒiŽ‚ŽdŠƒˆb…tlp„pum„mxcƒXu\ƒ_t_ƒ_r`ƒarb‚drfƒgne†ckk†gqdi€ux‹s ¤s ”‚zG™?{G¢IwK¥IuF¦FuF¥FtF¤FtF¤HuE£GrF FlZ ˆl¨“·y¼ƒ¾~¾€¾~¾€¾~¾€¾~¾€¾~¾€¾~¾€¾~½¹‚²…¬‡§Œ¤… Œ…˜ˆ”†‡†‰…„‚s†€‰i€zkyyyx€x€xx€zz~z{zq‚gzehph“msoŒoxu‡{}|€}€}y{‚{x{z{~}„ˆz‡€ƒzŠ|’|–u•‘vŽ€w€uŒ€ŒvŒ€‹v‹~Žx‘€•w˜™zš—“~†‹|Š‹‰x‰Žˆu‰Šs‹‹q‹Ž‡rŽ}ry’rmn•mjn€m†‡‹wŒ~‹}‹xGÿGÿGÿGÿEÿEÿEÿDÿCÿCÿAÿ@ÿ@ÿ?ÿ=ÿ<ÿ:ÿ9ÿ8ÿ7ÿ7ÿ5ÿ5ÿ4ÿ4ÿ3ÿ0ÿ0ÿ0ÿ/ÿ/ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ*ÿ)ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ%ÿ%ÿ&ÿ&ÿ%ÿ&ÿ'ÿ(ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ*ÿ,ÿ+ÿ,ÿ,ÿ-ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ0ÿ1ÿ1ÿ1ÿ2ÿ2ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ4ÿ3ÿ3ÿ4ÿ5ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ4ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ0ÿ0ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ,ÿ+ÿ+ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ&ÿ'ÿ(ÿ*ÿ/ÿ7ÿ<ÿ=ÿ;ÿ8ÿ<ÿBÿHÿJÿLÿLÿJÿIÿEÿDÿ?ÿ=ÿGÿXÿjÿeÿ[ÿXÿXÿXÿYÿ[ÿbÿhÿfÿRÿ*ÿ"ÿ(ÿ)ÿ(ÿZÿtÿdÿ]ÿbÿlÿLÿÿ&ÿ$ÿ&ÿ*ÿ*ÿ+ÿ-ÿ0ÿ3ÿ7ÿ8ÿ:ÿ;ÿ9ÿ8ÿ7ÿfÿjÿgÿiÿkÿjÿoÿpÿhÿcÿfÿgÿgÿgÿhÿfÿgÿiÿlÿWÿAÿ?ÿBÿ7ÿ3ÿ5ÿ3ÿ3ÿ3ÿ7ÿCÿKÿEÿ7ÿ3ÿ1ÿ/ÿ/ÿ/ÿ0ÿ1ÿ2ÿ4ÿ6ÿ9ÿ9ÿ7ÿ9ÿ9ÿ9ÿ5ÿ1ÿ.ÿ.ÿ/ÿ.ÿ/ÿ0ÿ2ÿ5ÿ7ÿ8ÿ9ÿ;ÿ<ÿ>ÿAÿBÿCÿEÿEÿEÿEÿEÿDÿGÿGÿHÿHÿIÿIÿHÿHÿFÿFÿGÿGÿGÿJÿOÿQÿRÿOÿLÿJÿHÿFÿEÿDÿCÿ@ÿ@ÿ@ÿ@ÿCÿEÿEÿEÿEÿGÿHÿPÿ[ÿdÿmÿtÿyÿ{ÿwÿqÿfÿbÿ^ÿ_ÿkÿyÿ†ÿ‹ÿ‰ÿ‹ÿ…ÿ{ÿoÿoÿ{ÿ€ÿ€ÿÿ|ÿ{ÿ{ÿ{ÿ{ÿ}ÿ}ÿyÿpÿoÿlÿjÿiÿfÿfÿqÿÿÿÿ‰ÿ…ÿ‚ÿÿ€ÿ€ÿÿ‚ÿƒÿƒÿƒÿ…ÿ„ÿ‚ÿÿ}ÿzÿtÿgÿZÿTÿKÿHÿPÿcÿtÿvÿvÿyÿ{ÿ}ÿ~ÿÿÿ‚ÿ„ÿ‡ÿ‹ÿÿŽÿŽÿŽÿ‹ÿ‰ÿÿwÿpÿoÿnÿjÿ[ÿ_ÿ`ÿaÿ`ÿ`ÿcÿaÿcÿeÿfÿgÿcÿ^ÿeÿfÿhÿnÿqÿ†ÿšÿÿŸÿÿÿFÿ@ÿFÿGÿHÿGÿHÿEÿEÿEÿEÿEÿFÿEÿEÿDÿFÿOÿtÿžÿ²ÿºÿ½ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ½ÿ¹ÿ²ÿ¬ÿ§ÿ£ÿŸÿÿ˜ÿ“ÿŠÿ…ÿ„ÿ…ÿ†ÿ‹ÿ‹ÿ„ÿ{ÿyÿyÿyÿyÿyÿyÿyÿyÿwÿyÿ}ÿrÿgÿeÿgÿgÿjÿnÿnÿsÿvÿyÿzÿ|ÿ{ÿ{ÿ{ÿzÿÿ…ÿˆÿ‡ÿ…ÿÿ”ÿ•ÿ“ÿÿŽÿŒÿÿÿŒÿŒÿŒÿ‹ÿ‹ÿŽÿ“ÿ–ÿ˜ÿ—ÿ•ÿ‘ÿÿŠÿŠÿŠÿ‰ÿ‰ÿˆÿ‰ÿŠÿŒÿŒÿ‰ÿ†ÿÿ|ÿxÿpÿmÿlÿsÿ‚ÿŠÿŽÿŽÿŽÿŽÿEEqEEqD€DqB€Aq@€@rA€@s>3|15|30|16~AŽ[~R‹52Œ0}/Ž.}.Ž0|13}48~<‘:~9’6€6‘642~-,~--~-Ž/~047‘98’;€=‘=?’A€A“B€B“C€C”DC”C€C–CC–C€C–D€E–D€E•F€F•G€G–JK”N‚Q”L‚I”G‚F”E€E”CA”@?”@ƒC“D…G‘G…F‘F…IG…MŽVƒ_‹gƒo„s‚qg‚S}X€_~b€n}{ˆwŠ€‰r‰‚„p|qqp€{s~~p|{ly€xhyyf}‚}pƒtzo†nzn†kxi…lyvƒ…x’u‡€ƒo€~j€€j€l‚ƒlƒ‚l‚l€€~ky€slhƒap[„WwQƒQzZ‚h{sƒuywƒxvzƒ}u€r‚„r‡ŠpkŽ‚Žfƒ‰e…wlp„ltj„bt]„`sa„btbƒbsaƒcsd‚fsgƒdo^…Yod…fre‚m|r}z†‘wšŽžuš”xy@›ByF£EvD¦HtG¦GtE¥CtD¤FtD¥DtC¤EoJ¢dj’›¬r¸ˆº}½‚¾~¾€¾~¾€¾~¾€¾~¾€¾~¾€¾~¾€½~¼€¶‚²†¬…§Ž¢„Ž›‚–†„‡‚„„‚|‚ƒq…€‰g‡€}j{€{uzz~yx€xw~vwz}s€d{dgqe“fpkltoŠs{wƒx{~{{{{z}|‡€ˆ{†ˆ{x”}”s’u€v€u‹€‹v‹€‹w~y”€–{—”“†‰|‰Šˆxˆ‹†t‡†qŠŽŒqŒŽ‹q†€qŒ{ry‹uqtŠws~ƒˆxŽy}‘q‘€nBÿBÿBÿBÿBÿBÿAÿAÿ@ÿ@ÿ@ÿ?ÿ<ÿ;ÿ:ÿ:ÿ9ÿ7ÿ6ÿ5ÿ5ÿ5ÿ4ÿ3ÿ2ÿ2ÿ2ÿ1ÿ0ÿ/ÿ.ÿ.ÿ.ÿ-ÿ*ÿ*ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ,ÿ,ÿ,ÿ,ÿ,ÿ/ÿ/ÿ.ÿ/ÿ/ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ3ÿ4ÿ4ÿ4ÿ3ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ4ÿ3ÿ3ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ2ÿ1ÿ1ÿ1ÿ1ÿ0ÿ/ÿ.ÿ.ÿ-ÿ.ÿ-ÿ,ÿ,ÿ+ÿ*ÿ*ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ&ÿ&ÿ%ÿ%ÿ%ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ'ÿ(ÿ+ÿ/ÿ:ÿBÿGÿHÿCÿ=ÿFÿNÿOÿPÿNÿJÿJÿEÿBÿ?ÿ=ÿ<ÿ:ÿ6ÿ9ÿMÿhÿiÿ]ÿXÿVÿTÿUÿYÿ]ÿcÿiÿVÿ2ÿ!ÿ$ÿ,ÿ`ÿvÿcÿcÿbÿgÿ1ÿ"ÿ*ÿ,ÿ0ÿ3ÿ2ÿ1ÿ3ÿ7ÿ8ÿ<ÿ8ÿ=ÿiÿqÿjÿhÿjÿhÿiÿoÿlÿfÿgÿjÿjÿgÿjÿnÿnÿ[ÿBÿ9ÿ:ÿ?ÿDÿIÿ;ÿ3ÿ4ÿ4ÿ2ÿ0ÿ1ÿ6ÿ?ÿPÿIÿ3ÿ0ÿ/ÿ/ÿ/ÿ/ÿ2ÿ7ÿ6ÿ7ÿ6ÿ9ÿ<ÿ<ÿ9ÿ8ÿ5ÿ3ÿ/ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ/ÿ1ÿ5ÿ9ÿ:ÿ;ÿ=ÿ=ÿ>ÿ@ÿ@ÿ@ÿ@ÿ>ÿ?ÿAÿ@ÿ?ÿ?ÿ=ÿ>ÿ>ÿ@ÿ?ÿAÿAÿCÿDÿDÿDÿDÿEÿHÿJÿJÿIÿHÿFÿDÿEÿEÿCÿAÿ@ÿ?ÿ@ÿCÿDÿEÿFÿGÿFÿHÿGÿGÿJÿOÿXÿ`ÿfÿcÿVÿIÿOÿ^ÿeÿnÿ|ÿ‰ÿŠÿ‹ÿŠÿ…ÿ|ÿpÿqÿzÿ}ÿ~ÿ}ÿyÿyÿzÿzÿzÿ|ÿÿ€ÿwÿtÿqÿoÿnÿlÿnÿyÿˆÿ’ÿÿ†ÿÿÿ~ÿ~ÿÿ€ÿÿƒÿ„ÿƒÿ‚ÿÿÿ€ÿ~ÿ{ÿxÿrÿiÿeÿaÿ[ÿWÿWÿ]ÿiÿsÿuÿwÿwÿzÿ}ÿÿÿ„ÿ…ÿˆÿŒÿŽÿŽÿŽÿÿ‰ÿ‚ÿxÿpÿgÿcÿ`ÿ`ÿaÿbÿcÿcÿcÿcÿdÿfÿfÿfÿaÿ]ÿ]ÿhÿhÿhÿkÿrÿuÿ€ÿ•ÿžÿ›ÿuÿ<ÿBÿEÿFÿIÿGÿFÿGÿDÿCÿDÿEÿCÿCÿBÿEÿWÿƒÿ¥ÿ´ÿ»ÿ½ÿ½ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿºÿ¶ÿ²ÿ®ÿ©ÿ¢ÿ™ÿ–ÿ‘ÿÿˆÿ„ÿ‚ÿ~ÿ}ÿ{ÿ~ÿ…ÿ~ÿ{ÿ{ÿzÿzÿ{ÿzÿxÿwÿvÿwÿ}ÿwÿgÿeÿfÿgÿeÿjÿlÿoÿpÿsÿuÿyÿ{ÿ{ÿ}ÿ|ÿÿ‡ÿ‡ÿ…ÿŠÿ’ÿ”ÿ”ÿ’ÿÿÿÿÿÿ‹ÿ‹ÿ‹ÿ‹ÿÿ’ÿ”ÿ–ÿ–ÿ‘ÿŒÿ‰ÿ‰ÿ‰ÿ‰ÿˆÿ†ÿ‡ÿ‡ÿŠÿŒÿ‹ÿ‰ÿƒÿÿÿÿÿ€ÿƒÿ‡ÿ‰ÿŒÿÿÿ‘ÿ“ÿ‘ÿ@€@rA€Ar@€@q?€?r=€>s>€x<Œ”<;”;~=•>~>–?~?–@~A–C~C–BA–DF•FG•GF•DC•BB•@?•@?•@‚B•CƒC•D„D“E„E“EƒF’EƒG’JƒQUƒQJEˆGW†f~s€~€ˆvˆˆq‡ƒƒqy‚mrp€wr{}o{€zlyyhy‚zg|€€s„‚~w†q~q…ozo…r{}ƒ‹z“Œv…€€rz{mz~~mmƒ…pƒ‚o~|o}€}o}€|nuqkmimdao`€crb‚iss„vtwƒwx{ƒ|t‚ƒs„‚†sˆ‚‹oŽ‚ŽlŽ„Šk‚…ylp„epb„bsc…dqc…ctc„cucƒctfƒdrcƒcr]‚^si‚jsjƒmyr€s…tzŒŽ›x˜”ry>›AyE¢DuE¦CsC¦EtD¦DuD¤EtD¤BrD¥Kjm¢™l®¶xº„½}¾¾~¾€¾~¾€¾~¾€¾~¾€¾~¾€¾~¾€¾~ºƒ¸µ„®„«‹ •Œ‘€…Ž€Œ€Š€…zƒ~rz€ym~€~n{zwz‚z}y€xx€w}vuz|z}h|eŠhsg“dqejrn‹nxq†t}vy|}}|€}ˆ†|†}Ž}’x‘“uŽv€vŒ€Œw‹€‹wŒ€Œx~“–€”‚‘|Œ‡ˆx‹‰‹xŒ‰x„‹w‡…ˆuŠ‰‹s‰‰…t„‡†y†‡x‰y‹ztm‘i‘‚”g“‚“k@ÿ@ÿ@ÿ@ÿ?ÿ?ÿ?ÿ?ÿ<ÿ<ÿ;ÿ:ÿ9ÿ9ÿ8ÿ8ÿ7ÿ6ÿ6ÿ6ÿ5ÿ3ÿ3ÿ3ÿ2ÿ2ÿ1ÿ1ÿ0ÿ/ÿ.ÿ.ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ*ÿ(ÿ(ÿ(ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ)ÿ*ÿ)ÿ)ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ2ÿ2ÿ2ÿ3ÿ4ÿ4ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ3ÿ4ÿ5ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ1ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ.ÿ-ÿ-ÿ,ÿ-ÿ,ÿ+ÿ+ÿ*ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ&ÿ&ÿ&ÿ%ÿ$ÿ$ÿ&ÿ&ÿ$ÿ$ÿ%ÿ&ÿ$ÿ#ÿ$ÿ$ÿ&ÿ)ÿ-ÿ8ÿEÿOÿTÿRÿMÿKÿSÿXÿVÿSÿOÿKÿGÿCÿ?ÿ;ÿ:ÿ7ÿ3ÿ2ÿ/ÿ.ÿ,ÿ=ÿXÿgÿaÿYÿTÿSÿVÿXÿ[ÿ`ÿgÿbÿBÿ&ÿ$ÿ^ÿoÿaÿeÿeÿVÿ:ÿ8ÿ5ÿ=ÿ@ÿIÿLÿQÿXÿcÿrÿrÿlÿmÿlÿjÿkÿpÿpÿjÿiÿkÿlÿoÿjÿSÿEÿFÿGÿHÿCÿAÿBÿAÿ>ÿ8ÿ4ÿ4ÿ2ÿ0ÿ/ÿ/ÿ3ÿ6ÿ7ÿ6ÿ3ÿ.ÿ-ÿ.ÿ1ÿ1ÿ1ÿ4ÿ9ÿ=ÿ;ÿ7ÿ;ÿBÿDÿ@ÿ=ÿ7ÿ1ÿ/ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ/ÿ1ÿ4ÿ6ÿ7ÿ8ÿ:ÿ;ÿ=ÿ:ÿ8ÿ8ÿ9ÿ<ÿ<ÿ<ÿ;ÿ;ÿ;ÿ:ÿ:ÿ:ÿ=ÿ=ÿ=ÿ>ÿ?ÿAÿBÿCÿCÿDÿDÿEÿDÿCÿAÿ@ÿ@ÿ@ÿAÿAÿ?ÿ?ÿ@ÿ@ÿAÿAÿBÿBÿBÿBÿDÿCÿDÿDÿCÿDÿEÿFÿDÿEÿEÿSÿhÿuÿƒÿ‡ÿ†ÿ‡ÿ…ÿ€ÿtÿmÿqÿxÿxÿzÿzÿzÿyÿyÿyÿzÿ{ÿ|ÿ‰ÿŒÿxÿuÿrÿqÿqÿwÿÿŒÿ’ÿ‹ÿ…ÿ€ÿ{ÿzÿyÿ{ÿ~ÿÿ‚ÿ„ÿƒÿ‚ÿ~ÿyÿwÿxÿzÿyÿzÿwÿxÿpÿnÿlÿmÿmÿlÿhÿmÿtÿwÿwÿxÿ{ÿ}ÿ‚ÿ…ÿ†ÿ†ÿ‡ÿˆÿ‹ÿŠÿ‡ÿÿuÿnÿjÿfÿbÿfÿeÿdÿdÿcÿcÿcÿcÿcÿdÿbÿ`ÿ_ÿfÿlÿoÿlÿjÿpÿxÿyÿÿŽÿ“ÿoÿCÿBÿGÿDÿEÿDÿAÿCÿDÿDÿDÿEÿDÿBÿCÿWÿ…ÿ¦ÿ³ÿ·ÿ¼ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ½ÿ½ÿ¹ÿ¸ÿ²ÿ«ÿ ÿ™ÿ•ÿ–ÿ•ÿ”ÿ‘ÿŽÿŠÿ…ÿ~ÿvÿuÿyÿyÿyÿzÿzÿyÿxÿwÿwÿuÿvÿyÿzÿgÿeÿeÿdÿeÿfÿiÿkÿlÿnÿrÿvÿyÿ}ÿ}ÿ|ÿ„ÿˆÿ†ÿˆÿÿ“ÿ’ÿ‘ÿ‘ÿÿÿÿŒÿŒÿ‹ÿ‹ÿŒÿŒÿ‘ÿ’ÿ‘ÿÿŒÿˆÿˆÿŠÿÿ‘ÿ–ÿ–ÿ’ÿŽÿ‹ÿŠÿ‹ÿŠÿˆÿŒÿÿÿ’ÿ’ÿ’ÿ‘ÿÿ’ÿ‘ÿ“ÿ‘ÿ‘ÿ’ÿ’ÿ?€?q?€?q>€>q=€=r>~;s;~;s97t88t7~6t7~6u4~3u3~2v11w00w.~-w.~.x+}+y+}+y*~+y+}+y*~*x*~*y*~*z*~*z+~+z*~*z*~*z,~,z,~,{,~,{,~,{+~*{*~,{+~({*~*{(~({&~&}'~'}'~'}'~'}'~'}'~'}'~'|(~(|''}&%}&~'~'~'~(~(~)~)~)~)~)~*~*~*}+~,},~,|.~.|.~.}.~.|/~0{/0{00z10z02y22y23y44y55z55z6€6z6€6z5€5z6€6y55y66y55x44x44x44x33y33y3~3z2~1z2~2z2~2z1~1{0~1{0~0{0~/{.}.z-}-{,},{+}+{+~*{)~)|(~(}(~'}'}'}&}&}&~%}$~$~$~&~$~#~#~##~#$}#~$~&}+~6A}LƒVyZŠVxQŒSxXŒYuXTsNKsHDt?Ž>•??•?€?•@@•A‚A”A‚A”AB“B‚B“CC”CB“B€BE€TŠi€v€x‚‚r‚ƒ{ro‚iro€vqvwnx€xkxygz‚ufuvs‡’€ƒyv†t}r…x~‚ƒŒ~Šzƒ€u{xsx~xs|s‚„t…ƒrxrppqtwpuwlvujs„rjs‚ujt‚qjl„kktƒwoxƒxp{‚q…‚†q…‚†q‡‚ˆqˆ‚…p}„uls„plk„foc…eqf…ftf„ducƒctbƒcu`ƒ[u]csgksqnwor‚uy{‹‰wˆ‘pyFšCxC¢EtE¥EsB§AsB§CsD¥ErF¥CnJ¤ji—œ¬rµˆ»z¼‚¾}¿¾~¾€¾~¾€¾~¾€¾~¾€¾~¾€¾~¾€¾~¾€½»¹³…¨€£†¢ ƒ ¡€œ€–z“„Œr„vmuwnxytz‚zzy€w~w€w~vxzz}}h{e‰eqc’fpd‘gqhkunˆrzvƒy~}‚{~}€…}††|‰}}”x“’uŽv€vŒ€Œv‹€‹v‹€{’~‘‚Ž|ŒˆŠxˆŠˆw‹‡y–}›}šv—•t‘€xŽ~w‘~“p•€–j”“d“‚’b’„’c‘„‘e’…‘j’…n<ÿ<ÿ<ÿ<ÿ<ÿ<ÿ;ÿ;ÿ<ÿ9ÿ8ÿ8ÿ8ÿ7ÿ5ÿ5ÿ6ÿ5ÿ6ÿ5ÿ3ÿ2ÿ2ÿ1ÿ1ÿ1ÿ0ÿ0ÿ.ÿ-ÿ-ÿ-ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ*ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ*ÿ*ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ.ÿ.ÿ.ÿ/ÿ.ÿ.ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ0ÿ0ÿ2ÿ2ÿ2ÿ2ÿ3ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ6ÿ6ÿ5ÿ5ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ1ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ.ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ%ÿ)ÿ0ÿ:ÿFÿQÿYÿXÿQÿQÿXÿYÿXÿXÿSÿOÿLÿHÿDÿ?ÿ;ÿ7ÿ5ÿ2ÿ/ÿ.ÿ-ÿ/ÿ*ÿ1ÿHÿeÿiÿYÿSÿSÿVÿXÿYÿ\ÿdÿiÿbÿVÿjÿqÿcÿdÿbÿJÿ8ÿKÿ_ÿeÿgÿgÿnÿtÿlÿhÿjÿlÿmÿpÿpÿkÿmÿlÿ]ÿ=ÿ-ÿ5ÿIÿ[ÿ]ÿTÿOÿMÿEÿGÿGÿ=ÿ7ÿ4ÿ2ÿ3ÿ0ÿ/ÿ,ÿ0ÿ:ÿ=ÿ6ÿ2ÿ1ÿ.ÿ-ÿ-ÿ0ÿ3ÿ6ÿ6ÿ8ÿ:ÿ:ÿ8ÿ9ÿ>ÿAÿEÿBÿ@ÿ9ÿ3ÿ/ÿ-ÿ-ÿ,ÿ,ÿ-ÿ.ÿ0ÿ2ÿ2ÿ2ÿ4ÿ5ÿ5ÿ5ÿ6ÿ7ÿ7ÿ7ÿ8ÿ9ÿ8ÿ9ÿ:ÿ9ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ9ÿ;ÿ;ÿ=ÿ?ÿ@ÿAÿBÿBÿBÿBÿ?ÿ>ÿ?ÿ?ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ?ÿ@ÿ@ÿAÿAÿAÿ?ÿ?ÿ?ÿ@ÿAÿAÿBÿ@ÿBÿAÿJÿ[ÿkÿyÿ{ÿ{ÿ|ÿ}ÿyÿrÿkÿiÿpÿtÿtÿvÿwÿwÿwÿyÿxÿrÿgÿlÿ€ÿ“ÿÿzÿxÿvÿsÿyÿ‚ÿŒÿŽÿˆÿƒÿÿ|ÿyÿyÿyÿ|ÿÿƒÿ…ÿ†ÿ„ÿÿxÿoÿiÿmÿmÿoÿpÿsÿvÿvÿuÿuÿwÿvÿuÿtÿpÿmÿrÿwÿzÿ{ÿÿ„ÿ…ÿ…ÿ†ÿ†ÿ‡ÿ‡ÿ„ÿÿyÿtÿpÿnÿiÿjÿgÿfÿfÿeÿcÿcÿcÿcÿaÿ]ÿZÿ]ÿcÿgÿmÿuÿuÿuÿoÿmÿpÿ†ÿˆÿpÿLÿDÿDÿDÿEÿDÿAÿCÿCÿCÿDÿDÿEÿFÿVÿ€ÿ£ÿ²ÿ¸ÿ»ÿ½ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¼ÿ»ÿ¸ÿ¶ÿµÿ±ÿªÿ¦ÿ£ÿ›ÿ”ÿŽÿƒÿzÿuÿwÿxÿyÿzÿzÿyÿwÿwÿwÿwÿwÿzÿ}ÿhÿeÿdÿcÿdÿeÿgÿhÿiÿmÿqÿtÿzÿ|ÿ{ÿ}ÿ„ÿ…ÿ†ÿ‰ÿ“ÿ”ÿ“ÿ’ÿÿŽÿŽÿÿŒÿŒÿ‹ÿ‹ÿ‹ÿÿÿÿÿŠÿŠÿˆÿˆÿ‹ÿÿ˜ÿÿœÿ™ÿ˜ÿ–ÿ•ÿ–ÿ–ÿ–ÿ—ÿ—ÿ—ÿ•ÿ”ÿ“ÿ“ÿ“ÿ“ÿ’ÿ’ÿ’ÿ‘ÿÿŒÿ<|"…){3ˆGx]Œ_xZ‘RxM’By8’E{E‘;{5‘2{2‘0{/.|/7}H‹L}<‹31Œ/~/.}.Ž3}78~9‘;8‘6~7“<~B”F€C•A<“7~1’/}-‘,|,‘-|/’1|3‘2}4“4|3“5|5“5|5”6}6”6~6•6~8•7|7”7|7”9}9–8}8–7}7–8}8–;}=–>}?•@~@•?~?”?~>•?~>•=~<•=~=•=€=”>€?”@€A”A€A”@€@”>€>”>@”@?“B€IWc‡r€z€wƒvzz„yvq„ju^ƒbul‚qps€unuvjwwgw‚qdc‚Wpn€€˜‚„}ƒy€x„{}„ƒŒ|‹†z‚~u}~ztz}{t|~€u„~…uˆ}‡tƒ|qsbs_‚dwgƒhtl‚nopƒsmuƒxhx‚vgt‚sft„tgt„ujz‚~mƒ‚„m……m…‡p‡ƒ‡nƒƒ{mw„xlu„sno„irg„fve„dtc„bub‚_s^Zt^€fum€twwv~u~p…lxiŠvv†pxQ˜GwF¡FuF¥DsC¨BsC§DsE¥EqA¥Jkd¢’kª”´u¹…½}½€½¾€¾~¾€¾~¾€¾~¾€¾~¾€¾~¾€¾~¾€¾~¾€¾~¾€¾}¾¾~¾€¾~»€°€«¦„~Ž†„wˆzou„wlyƒzqyƒyuywzw€w}vwyz}{l€c‡cud‘dod’fqhŽjum‹rws…{|z‚|}~…}††}‹z“–v’€‘u€Žu€uŒ€‹u‹€‹x~‘{Ž†x‹ŠŠuŠŠ‹wŽ”{—vœ~šnš‚šk˜„šl™ƒ™i—˜g—‚–f•‚”e“„“b”„•b•…’e“…i†‡m:ÿ:ÿ:ÿ:ÿ;ÿ;ÿ:ÿ:ÿ9ÿ9ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ3ÿ1ÿ1ÿ1ÿ0ÿ/ÿ.ÿ.ÿ-ÿ,ÿ+ÿ+ÿ,ÿ,ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ0ÿ2ÿ2ÿ2ÿ2ÿ3ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ,ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ"ÿ"ÿ!ÿ!ÿ"ÿ"ÿ"ÿ"ÿ!ÿ#ÿ$ÿ)ÿ3ÿ>ÿGÿLÿIÿFÿIÿOÿYÿ\ÿ^ÿ]ÿXÿQÿNÿLÿGÿCÿ?ÿ:ÿ7ÿ4ÿ/ÿ+ÿ*ÿ)ÿ+ÿ(ÿ*ÿ+ÿ<ÿYÿeÿ_ÿUÿZÿUÿVÿWÿZÿaÿeÿfÿjÿjÿjÿgÿeÿfÿbÿ`ÿjÿsÿpÿiÿhÿkÿnÿtÿwÿoÿ\ÿ8ÿ'ÿ-ÿ6ÿ/ÿIÿfÿgÿ`ÿ]ÿWÿOÿ7ÿ6ÿJÿCÿ8ÿ5ÿ1ÿ/ÿ/ÿ/ÿ.ÿ.ÿ2ÿ:ÿ9ÿ4ÿ2ÿ1ÿ.ÿ0ÿ1ÿ1ÿ1ÿ5ÿ6ÿ7ÿ;ÿ8ÿ5ÿ7ÿ=ÿBÿEÿEÿCÿ=ÿ8ÿ2ÿ.ÿ+ÿ,ÿ,ÿ-ÿ/ÿ0ÿ2ÿ4ÿ5ÿ4ÿ3ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ4ÿ5ÿ6ÿ6ÿ7ÿ7ÿ9ÿ9ÿ8ÿ8ÿ7ÿ7ÿ7ÿ7ÿ8ÿ8ÿ;ÿ<ÿ=ÿ=ÿ?ÿ?ÿ>ÿ=ÿ=ÿ=ÿ<ÿ<ÿ=ÿ=ÿ=ÿ=ÿ>ÿ?ÿAÿAÿAÿAÿ?ÿ?ÿ>ÿ?ÿ>ÿ>ÿ?ÿAÿIÿTÿ_ÿmÿwÿvÿtÿvÿuÿpÿeÿXÿOÿUÿaÿfÿkÿoÿqÿtÿuÿyÿwÿnÿ[ÿ;ÿ2ÿmÿ–ÿ‹ÿÿ}ÿ{ÿ~ÿ„ÿŒÿ‹ÿ†ÿ‚ÿÿ}ÿzÿzÿ|ÿÿ‚ÿ†ÿ‡ÿˆÿ‡ÿ†ÿ‚ÿxÿiÿYÿYÿ]ÿ`ÿdÿfÿgÿlÿrÿvÿwÿwÿvÿvÿuÿuÿtÿwÿzÿ|ÿÿ‚ÿƒÿƒÿ„ÿ…ÿ…ÿ†ÿƒÿ~ÿzÿyÿzÿuÿrÿoÿmÿgÿdÿdÿcÿbÿ_ÿ_ÿ^ÿ]ÿaÿjÿsÿyÿwÿvÿuÿpÿmÿkÿoÿÿ~ÿYÿDÿGÿFÿEÿBÿBÿBÿCÿDÿEÿEÿAÿNÿsÿ›ÿ¬ÿ¶ÿ¹ÿ¼ÿ½ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ½ÿ¾ÿ¾ÿ¾ÿ¾ÿ½ÿµÿ¬ÿ ÿÿxÿnÿpÿsÿuÿwÿwÿyÿyÿyÿyÿwÿwÿwÿvÿwÿzÿ}ÿkÿcÿcÿbÿdÿdÿfÿhÿjÿmÿqÿtÿzÿ|ÿ|ÿ~ÿ„ÿ…ÿ‡ÿÿ“ÿ“ÿ’ÿ‘ÿÿŽÿÿÿŒÿ‹ÿ‹ÿ‹ÿŽÿ‘ÿÿÿ‹ÿŠÿ‹ÿŽÿ’ÿ“ÿ•ÿ–ÿšÿšÿ›ÿ™ÿšÿšÿšÿ™ÿ™ÿ˜ÿ—ÿ–ÿ•ÿ•ÿ“ÿ“ÿ’ÿ‘ÿ‘ÿŽÿŠÿ…ÿ€ÿ{ÿ99u::u:~9t:~:t8~8t8~8t6~6t6~6t4~4t4~4t2~0t1~1t/~.u.~-u-~,u+~+u*}*v*}*v)~)w)~*w*~*w+~+w+~,w,~+w+~+w,~,w-~-x.~.x.~.y.~.y.~.x-~,z+~,z+~*z+~+{)~)|*~*|)~)|)~)})~(})'|((|((|((~*~*})~)})~)~(~(~(~(~)~*~+~+}+~+,~,-~-.~.}-~-}.~.|/~/z0~0z00z00z11z01z22z2€2y3€3y33z45z5€5z6€6z66y55y55z44z55y33y34y44y2~2y2~2y1~1z1~1{0~.{/~/{0~0|/~/|-~-|.~.|.~-|,~+|+~*|*~)|)~){(~(|'~'}&~&}&~%}%~$}#~"~#~#~"~""~"!~!!~!!~!~"~"~#'09‚C{E‡Az?ŒBzMŽWwZ_t_‘^qW’UqP’KqG‘Cr?Ž9v0‹(y'ˆ(|'‡&|&†&'‡#€*†C€[†_Y…WS†RU†W~Y†\|`†b{c†czd‡c|c†l~wƒq€iƒg€l„p}t…y}h…?}!…#}(‚({9ƒNye‰ixdŽcwc’[wL’4x2‘A{94{20{//{.,|-‹/|1Œ/~0‹11Œ/~15}4Ž1}25}7‘9~8“8~8“=}E”GF•D@”;~5“0},‘+|+‘.|.’/{1‘3|4“4{4“3{3“3{3”3|3”4|4•4|5•6|6–7|7–6|6•7|7•5}5•5}5•7|7•8|8”:};”>~>”=~<•<~<•<~<•<~=•=~<”?~@”@@”AA”@@”>~<“=~=“A~E‘S€_Šisv‚r|r…q{l…d|U„B>„F‚P„X\ƒbxg‚mrs‚tjt‚hfYVsL~f‚Ž~††€‚„‚}‡ƒŒzŠ†w‚€u~~~u}}u~„t‡~ˆt‰|‡r‰}ˆoqm_ƒQuSƒU~Z„]{`„fwj„rpt„tku„ugw„vev„yfyƒzg}ƒg„‚i„„„l„ƒ„lƒƒƒj~„|j|„zku„ump„kph„fud„av_‚]v[\we€ixp€s}us„synˆjti‹grrŽ|xc–GwFœFvF£DtB¥CsC§DsC§DqB¦Rj~¢ n­¶zº„½~½€¾¿€¾~¾€¾~¾€¾~¾€¾~¾€¾~¾€¾~¾€¾~¾€¾~½€½¾¾~¾€¾½µ…¥„Ž‹s„cˆdƒe‡i{n…ssxƒyqzƒzsy‚xywv{wwxy~{n~c†bvbŽdrfesgŒjwl‰oyu†{||‚}}‚}…~ˆ}w•”t“€u€Žu€uŒ€‹w‰€‰z~Žz‹‡Šx‹†Žy’•z—x™™qš€œp›‚šoš™nš‚šo˜ƒ˜q–ƒ–r”…’s’…rŒ‡ˆr…‰€tyŠuxm‹dz9ÿ9ÿ:ÿ:ÿ9ÿ9ÿ9ÿ9ÿ8ÿ8ÿ8ÿ8ÿ6ÿ5ÿ5ÿ5ÿ3ÿ2ÿ2ÿ2ÿ2ÿ0ÿ0ÿ0ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ+ÿ+ÿ)ÿ)ÿ+ÿ+ÿ*ÿ*ÿ)ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ,ÿ-ÿ-ÿ,ÿ+ÿ,ÿ,ÿ+ÿ*ÿ+ÿ+ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ*ÿ(ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ2ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ3ÿ4ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ0ÿ.ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ#ÿ#ÿ"ÿ"ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ"ÿ"ÿ#ÿ'ÿ-ÿ8ÿBÿGÿDÿ>ÿBÿHÿPÿWÿ]ÿ`ÿ_ÿ^ÿ\ÿZÿTÿNÿKÿEÿ?ÿ.ÿ'ÿ&ÿ&ÿ%ÿ(ÿ(ÿ(ÿ'ÿ&ÿ$ÿ$ÿ(ÿ@ÿ]ÿbÿTÿOÿTÿWÿWÿZÿ^ÿ_ÿaÿcÿeÿdÿlÿuÿsÿpÿkÿoÿrÿvÿxÿeÿ;ÿ%ÿ&ÿ(ÿ&ÿ'ÿ=ÿ]ÿiÿiÿeÿcÿ_ÿUÿIÿ,ÿ+ÿ8ÿ6ÿ3ÿ1ÿ0ÿ/ÿ.ÿ-ÿ*ÿ+ÿ,ÿ.ÿ/ÿ0ÿ0ÿ0ÿ0ÿ2ÿ6ÿ7ÿ2ÿ2ÿ3ÿ6ÿ:ÿ:ÿ8ÿ9ÿ>ÿBÿDÿEÿEÿAÿ<ÿ6ÿ1ÿ,ÿ+ÿ+ÿ.ÿ.ÿ.ÿ0ÿ1ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ6ÿ4ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ7ÿ7ÿ:ÿ;ÿ<ÿ=ÿ=ÿ9ÿ:ÿ:ÿ;ÿ;ÿ;ÿ<ÿ=ÿ<ÿ?ÿ@ÿ?ÿ@ÿ?ÿ?ÿCÿ@ÿ>ÿ=ÿ=ÿ?ÿGÿRÿZÿgÿrÿtÿqÿtÿrÿkÿcÿUÿ>ÿ0ÿ2ÿ9ÿ?ÿDÿIÿQÿZÿ`ÿfÿmÿnÿhÿ_ÿeÿhÿrÿ‚ÿ‘ÿ‹ÿ„ÿ‚ÿ‚ÿŠÿŽÿŠÿ†ÿ„ÿ‚ÿ€ÿ€ÿÿÿ„ÿ‡ÿˆÿˆÿŠÿˆÿˆÿŠÿ…ÿzÿmÿVÿMÿOÿPÿTÿWÿ]ÿdÿkÿpÿqÿsÿtÿtÿvÿwÿxÿyÿzÿ|ÿ}ÿ}ÿ~ÿ€ÿ€ÿ‚ÿ„ÿ„ÿ„ÿ‚ÿÿ}ÿ|ÿyÿtÿtÿnÿjÿgÿcÿ`ÿ^ÿ^ÿ\ÿaÿjÿpÿpÿpÿqÿqÿqÿmÿiÿhÿfÿiÿrÿoÿPÿLÿJÿJÿEÿBÿBÿBÿBÿAÿBÿEÿUÿ…ÿ£ÿ°ÿ·ÿ»ÿ½ÿ½ÿ¾ÿ¿ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ½ÿ¼ÿ¼ÿ¼ÿ¾ÿ½ÿ¼ÿ¹ÿ©ÿˆÿhÿYÿ_ÿdÿeÿhÿhÿkÿqÿxÿyÿyÿyÿyÿwÿvÿvÿvÿyÿ~ÿsÿfÿcÿcÿgÿiÿiÿjÿlÿnÿqÿvÿ|ÿ}ÿ~ÿ€ÿ‚ÿ…ÿ‰ÿŽÿ’ÿ‘ÿ’ÿÿŽÿÿÿÿŒÿ‹ÿ‰ÿ‰ÿÿŽÿ‹ÿŠÿŠÿŽÿ‘ÿ”ÿ˜ÿ™ÿ›ÿ›ÿ›ÿ™ÿ™ÿ˜ÿ˜ÿ–ÿ—ÿ—ÿ—ÿ—ÿ–ÿ•ÿ“ÿÿ‰ÿƒÿ~ÿzÿtÿlÿdÿYÿQÿPÿ8~8t9~9t9}9t7}7t8~8t8~7t5~5t5~5t3~2s2~2s2~0u/~.u.~.u.~-u-~,t+~+t+}+v+}+v*~*w*~+w+~+w+~+w+~,w-~-w-~-w.~.w.~.v.~.w.~.w.~.w.~.w-~-w.~.y-~+y,~,{,~+|+~+|+~*|)~)}*~*}*~*|)~)|*~*|*~*|+~)}*~*}*}+~*}(~*}+~+},~+~++~+,~,-~-.~.}-~-}.~.}/~/{0~0{00{/1z11z12z22z22z33z22z33z33y45y44y55y55y33y44z33z45z32z2~2z2~2z0~0{1~1{/~.{/~.{/~.{.~.{.~-{,},{,}+{+}*{*})z)}({(~(|(~&|&~&|%~%}&~%$~##~""~"!~!~!~!~!~!~!~!~!~ }!~"}#&|+4A|F†FxD‹GvIOuUZs_‘brc‘aq_”\qW”SsM‘>v.‹){)ˆ%}$‡$~$‡&~(‡*|(‡(~(ˆ$~*ˆG€]ˆ]€UˆY€]ˆ`{a‡c{cˆh|h…m~s„p€lƒk€m‚qs…w}k‰Nz4‡,y+‡&v'†*wC‡^yiŠbyWŽRxYSxJŽ-y(7z60{00{/Ž-{-Ž*|,0|/1}1Œ1~2Ž1~3Ž8~97~5‘7|:’<}>”<};”;~@”C~E–E~B•?~;•5}0“,{+’,{,‘-{.‘0{1’1{2’3{3”2z2”0z0”2{2–3{3–4{4•4{4•4|4”4|4”5|5•4|2•3{4”6|7”9};”<};”9}7“8}8“8~8•:~;•<|>”=|=”?}?”B}B”B~A”?~<“>~C‘P€[‹f‚qƒu„ss…n|g„_~R…@…2ƒ/ˆ/‚0Š36‰9‚@ˆFƒO„W„_yb„`o[‚^uV€]€t~ƒŽ†„ƒ„{‡‚‹x‰€…u…€„u„€„u„„w†}‡vˆ}ˆt‰ˆrˆˆmŠ€†hylnVƒJ{HƒJƒL‚R‚[‚g{nƒnsoƒpnrƒtjwƒycx‚x`{„}`~„~d~ƒf„€g„ƒi‚„ƒl€„~j{„xkv„rnnƒjpdƒ`r^‚[u]ewh€jzmp€q{o…nxl‰iugŒdudfuk”WwTšPwI CuB£AuB¦BsB¦AoE¦Xj‡Ÿ¤o°Ž¶zºƒ¼~¼¾~¾€¾~¾€¾~¾€¾~¾€¾~¾€¾~¾€¾~¾€½¼»º‚º½‚½~»·…­ˆ…\P‹W”]…`Žd…gƒh†iyj†rswƒxqx‚xvvvywxv|€uy€q|p~r‚syssyst{t}w|}}~~€‚†|Šv‘’v‘Žv€uŒ€Œu‹€ŠwŠ€‹z~€Š{‹„y‚“|—w˜~˜pš€™rš™t˜•w”’w‘‚‘wƒzŽ„Œ|ˆ„€~w‡p‚gˆ`…X‰N…GŠD‡H‹N„8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ7ÿ7ÿ8ÿ8ÿ7ÿ5ÿ5ÿ5ÿ5ÿ5ÿ3ÿ2ÿ2ÿ2ÿ2ÿ0ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ.ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ+ÿ,ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ*ÿ+ÿ+ÿ,ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ0ÿ0ÿ0ÿ2ÿ2ÿ2ÿ0ÿ0ÿ0ÿ0ÿ.ÿ.ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ,ÿ+ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ(ÿ'ÿ)ÿ&ÿ'ÿ&ÿ&ÿ%ÿ%ÿ%ÿ$ÿ$ÿ#ÿ#ÿ"ÿ"ÿ!ÿ!ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ!ÿ!ÿ ÿ ÿ!ÿ#ÿ&ÿ+ÿ4ÿ?ÿHÿJÿJÿKÿNÿPÿSÿZÿ_ÿeÿeÿfÿfÿbÿ^ÿXÿMÿ4ÿ)ÿ+ÿ(ÿ&ÿ$ÿ%ÿ'ÿ&ÿ&ÿ+ÿ-ÿ.ÿ.ÿ+ÿ&ÿ%ÿ*ÿ:ÿKÿWÿgÿlÿjÿkÿmÿnÿkÿuÿrÿmÿmÿoÿsÿuÿwÿlÿSÿIÿ5ÿ2ÿ<ÿ=ÿ1ÿ2ÿLÿcÿdÿZÿSÿLÿMÿMÿGÿ0ÿ%ÿ6ÿ5ÿ/ÿ0ÿ0ÿ.ÿ-ÿ-ÿ-ÿ/ÿ2ÿ0ÿ1ÿ2ÿ1ÿ2ÿ3ÿ6ÿ9ÿ=ÿ;ÿ9ÿ8ÿ8ÿ;ÿ<ÿ=ÿ=ÿ>ÿ@ÿ?ÿ@ÿBÿ@ÿ?ÿ=ÿ9ÿ2ÿ.ÿ,ÿ,ÿ,ÿ-ÿ.ÿ0ÿ1ÿ1ÿ2ÿ3ÿ3ÿ2ÿ2ÿ1ÿ0ÿ1ÿ1ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ2ÿ3ÿ3ÿ5ÿ6ÿ8ÿ:ÿ;ÿ9ÿ8ÿ9ÿ8ÿ8ÿ8ÿ8ÿ7ÿ9ÿ:ÿ;ÿ;ÿ;ÿ<ÿ=ÿ@ÿAÿBÿAÿ?ÿ=ÿ@ÿNÿYÿcÿoÿpÿqÿsÿoÿhÿ\ÿNÿ<ÿ.ÿ-ÿ,ÿ-ÿ/ÿ0ÿ0ÿ0ÿ4ÿ7ÿ:ÿCÿIÿNÿPÿWÿYÿRÿZÿiÿÿ‹ÿ†ÿ…ÿ…ÿ‡ÿˆÿ†ÿ…ÿƒÿƒÿ‚ÿ‚ÿƒÿƒÿ†ÿ‡ÿŠÿŠÿŠÿ‰ÿˆÿ‡ÿ‰ÿŠÿ‡ÿ~ÿoÿ[ÿHÿDÿEÿHÿQÿcÿiÿkÿkÿmÿpÿrÿxÿyÿwÿwÿyÿ{ÿ{ÿ{ÿ|ÿ}ÿ}ÿ~ÿ~ÿÿ‚ÿƒÿ‚ÿÿ}ÿ{ÿyÿuÿrÿnÿjÿeÿ[ÿPÿTÿ\ÿeÿfÿgÿnÿoÿnÿnÿlÿiÿgÿdÿdÿbÿ`ÿXÿRÿPÿFÿDÿBÿBÿAÿAÿAÿBÿDÿXÿˆÿ¥ÿ±ÿ·ÿ¹ÿ¼ÿ¾ÿ½ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¼ÿºÿ¸ÿ·ÿ»ÿ»ÿ½ÿ¹ÿ±ÿ•ÿ\ÿEÿPÿYÿ\ÿ^ÿbÿfÿgÿiÿhÿlÿtÿxÿyÿyÿzÿzÿ|ÿÿ‚ÿ…ÿ‚ÿÿÿ‚ÿ‚ÿ‚ÿ€ÿ~ÿ~ÿ~ÿ~ÿÿÿ€ÿÿ‚ÿ‚ÿ†ÿŒÿŽÿ‘ÿÿÿÿÿÿŒÿŒÿ‹ÿŠÿŠÿÿÿÿŒÿŒÿÿ’ÿ–ÿ—ÿ—ÿ˜ÿ˜ÿ™ÿ™ÿ—ÿ•ÿ“ÿÿŒÿŠÿˆÿ†ÿ„ÿÿyÿsÿgÿ_ÿVÿNÿEÿEÿFÿJÿLÿPÿSÿ8~8t7~7t7}7s7}7s7~6t5~5t5~5t5~5t3~2t2~2t2~0u0~/u/~/t.~-t-~,t,~,t,},u+}+u+~+u*~,u,~,u,~,u,~,u.~.u-~-v/~/v.~.w.~.x0~0x/~/x/~/x.~.x.~.x.~.x-~-z.~.{,~,{,~,{+~+{+~*{+~+|+~+|*~*|*~*|,~,|+~+}+~+|,~,}*~*~,~,~,~,+~-,~,-~-.~.}-~-}.~.}/~/{0~0{00{00z01z11z12z22z22z22z34z33y33y33y33y33y33y33z33z32z0.z0~1z1~1z0~0{0~/{.~.{.~.{.~.{-~-{,~,{+}+{+}*{*}){)}(z&}&{'~'|&~&|%~%|$~$}$~#}#~"}"~!}!~!} ~ ~!~!~ ~ ~!~!~~ } ~!}"%~+3€=|E†IxLŒOvQQtT[t`fskjrfdrc[tH1x,‹/{,‰(|)‰){)‰'{)‰*z,‰,{-Š/{.Š0|1Š1~2Š8~AŠQXŠ`c‰frƒr€o‚or‚t}u„t|e‡QyP‰Ox;‰;wH‰OwJˆOw[Š`xX‹UzRKyKOyF1y$3z5Œ0{0/{-.{-Œ1|6Ž8|55}32~44~69?>=‘;|;“<|@”@|B”A~>•<~?–B~@–@~=—;}4•/{-’-{-‘-{/‘1{1’2{2’2{2”2z2”2z2”2{2”2{2”4{4”3{2”1{1“2{2“3{3”2{1”2{1“1{5“6|7“7|5’6}7“7}7“7}7“8}8“8}8’9}9’:}:’<}=’?}@“=}@‘H}WŒan…q„qr†o€g†\ƒP…>ˆ0ƒ.‹-,‹-€-‹//0€1Ž1€24‚8‹<ƒB‡H„IƒK‚P†Xj†~ƒ|„y†|~…y€ƒuƒ€s€‚t†€†u‡‰w‰Šv‹‰s‡†m‰€‹gŒ‰f‚so\H|BCJ[c€f}f€hxm€rpy€ydwwby‚zbzƒzaz„zb{„|c~„~e~„h„iƒ{kzƒwmuƒsooƒlqe‚_sah|d~_d{m„ovo‡nvl‹hufŒbuaat_[vN—NvHFvC¢BuB¥BtB¦>pD¦Yk‰Ÿ¦q°·z¹ƒ»}½½~¾€¾~¾€¾~¾€¾~¾€¾~¾€¾~¾€¾~¼€»¹ƒ¶µ†·~º„»¹ƒ­‡‚F‰E˜OƒV›\€^”`‚dŒd…g‚i†i{oƒwt{‚{t|q…n‡‡kŠ€Šj‹‹kŠ€Šh‰€‡i†€„i„†i†…n„ƒsƒ€‡tŒ€Žs‘€v€ŽvŒ€ŒuŒ€ŒuŠ€‰y‹€{~€{‘{”z•~—t–•s–€—u—–w”‚Žz‰ƒ…{}ƒx~u…qƒn„cˆV†L‹F†BŒA†@‰C‡H‡MŠM„P‹T‚7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ6ÿ8ÿ7ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ3ÿ2ÿ2ÿ2ÿ2ÿ0ÿ0ÿ/ÿ/ÿ/ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ)ÿ*ÿ,ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ+ÿ+ÿ+ÿ,ÿ,ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ-ÿ-ÿ,ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ0ÿ1ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ1ÿ0ÿ0ÿ1ÿ1ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ+ÿ+ÿ+ÿ+ÿ)ÿ)ÿ)ÿ)ÿ(ÿ'ÿ'ÿ&ÿ'ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ$ÿ#ÿ#ÿ"ÿ"ÿ!ÿ!ÿ ÿ ÿ ÿ ÿ!ÿ!ÿ ÿ ÿ!ÿ!ÿÿ ÿ ÿ!ÿ#ÿ(ÿ.ÿ6ÿ>ÿEÿIÿKÿPÿSÿTÿWÿ_ÿdÿjÿnÿlÿiÿeÿbÿVÿ>ÿ.ÿ-ÿ,ÿ*ÿ*ÿ*ÿ-ÿ-ÿ(ÿ*ÿ*ÿ,ÿ,ÿ,ÿ*ÿ*ÿ-ÿ.ÿ0ÿ1ÿ1ÿ0ÿ.ÿ.ÿ(ÿ(ÿdÿrÿlÿlÿkÿhÿiÿmÿ`ÿIÿLÿNÿPÿDÿCÿNÿRÿQÿYÿ^ÿZÿYÿ]ÿ[ÿUÿUÿTÿRÿ<ÿ'ÿ/ÿ6ÿ8ÿ3ÿ0ÿ.ÿ-ÿ-ÿ1ÿ7ÿ=ÿ<ÿ:ÿ6ÿ3ÿ3ÿ3ÿ5ÿ;ÿ@ÿAÿAÿCÿAÿ?ÿ@ÿBÿBÿAÿ<ÿ:ÿ<ÿ>ÿ=ÿ=ÿ<ÿ:ÿ5ÿ1ÿ.ÿ-ÿ-ÿ-ÿ/ÿ1ÿ1ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ3ÿ3ÿ2ÿ1ÿ/ÿ/ÿ1ÿ1ÿ2ÿ4ÿ4ÿ3ÿ3ÿ4ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ7ÿ7ÿ9ÿ9ÿ:ÿ:ÿ<ÿ=ÿ>ÿ?ÿ>ÿFÿPÿ]ÿkÿrÿnÿoÿlÿfÿ\ÿNÿ=ÿ0ÿ.ÿ,ÿ,ÿ,ÿ,ÿ-ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ1ÿ0ÿ1ÿ3ÿ5ÿ9ÿ;ÿ>ÿAÿKÿ`ÿfÿ`ÿdÿhÿkÿpÿqÿsÿvÿzÿ}ÿƒÿ…ÿ…ÿ‡ÿ‰ÿ‰ÿ‹ÿ‰ÿ‰ÿ†ÿ„ÿ‡ÿŠÿŒÿŒÿ„ÿvÿcÿGÿ?ÿHÿWÿcÿfÿdÿeÿkÿpÿzÿyÿvÿwÿyÿzÿ{ÿyÿxÿxÿxÿzÿ{ÿ|ÿ}ÿ~ÿ}ÿ|ÿ€ÿÿ|ÿzÿvÿsÿrÿoÿoÿpÿlÿkÿnÿkÿkÿoÿoÿoÿlÿjÿhÿfÿbÿaÿbÿ`ÿ\ÿKÿLÿOÿGÿCÿBÿBÿBÿBÿAÿBÿVÿ„ÿ£ÿ®ÿ¶ÿ¹ÿ¼ÿ½ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¼ÿºÿ¸ÿ³ÿ¯ÿµÿ»ÿ»ÿ·ÿ§ÿpÿHÿLÿQÿYÿ\ÿ^ÿbÿdÿgÿjÿjÿjÿmÿsÿ{ÿÿÿ„ÿ‡ÿ‹ÿÿÿÿÿÿÿŽÿŽÿÿ‹ÿˆÿ‡ÿ‡ÿ‡ÿ‡ÿ†ÿ†ÿ…ÿ„ÿˆÿŒÿŽÿÿŽÿŽÿŽÿŒÿŒÿŒÿŒÿŠÿ‰ÿ‹ÿÿÿÿÿÿ‘ÿ“ÿ”ÿ“ÿ•ÿ“ÿ“ÿ”ÿ“ÿŽÿŒÿ†ÿÿuÿlÿdÿ`ÿZÿOÿEÿ@ÿ<ÿ=ÿ>ÿAÿ@ÿCÿHÿLÿNÿOÿRÿ7~7t7~7t7}7s7}7s6}6r5}5s4~4t3~3t3~2u2~2u2~0u1~0u0~0u/~.u.~.v-~,v-~-u-~-u-~-t.~.t.~.s..t..u..u..u11w11w00w0~0v0~0v0~0w0~0w-~-x.~.x-~.y0~.y.~.z-~-z.~.{-~-{-~-{,~,{,~,|,~,|,~,|-~-|,~,},~,},,},,~-}-~,}.~.}.~.}.~-}.~.}.~0~0~0~0|0~0|00{0.{11{11z00y2€2y2€2y1€1{1€1{34{54{33z22z22z22z22z11{1~1z1~1z0}.{0}0{-~-{-~-{-~-|-~-z,~,{+~,{+~+{*~*{*}*{))|(~({(~({&~%|&~%|%~%|$~$|#~#~!~ ~!~!} ~ } ~ } ~ } ~ }~} ~ ~ ~"}$*08ƒ@|EˆHzKŒPwSVwZbwfktonsies^Ot0Ž*z)Œ)|)Š*|*ˆ-|1Š,z,Š,{,Š-{-Š*|%‹#|$Š'},‰0|0Š1{6‰.}Z…v€j„jkƒo~l„hz\‡UxJ‰IvJ‹IwE‹KxR‹OyX‰]zaŒ`yaazZXz_^yZKz/Ž){4A}@‰5}.‰-|0Š:|AŒC}B<}6‘4~469;€?CD‘E~H“D}B”A}B–C}<–7}8–9}9–9|:–8|8–4z0”-z-“,z.‘0z0‘1z1“1z1“2z2”1z1”2z2•2z2”2{2“1{0“0{0’0{0’2{2“1{1“/{/“0{1“0{0“1{1“2{2‘4{4‘4{4‘6{6‘6{6‘8|9‘8|9‘;|=‘==‘@~JV€dŠn‚l…n…j„e…[…M„;Š3/,+Ž,,Ž-~-.~./~/Ž0~0Ž//0022123ƒ6‹@„E‰E‡I‡M‡S‚U†[|a…czhƒo{t‚yyz‚€x†€…tˆ€‰q†€„l……h‡Še‚h‡‚yoeIzG€Vd}ea{cyi|ssy}yjv~vhy~ygzycw‚t`q„v`w„za|ƒ}f}ƒ}g~ƒg}ƒ}h{‚yiv‚smrnrn€o{o}n€m{l…mxn‡jugŒgueŒbtaaub]vO”KwSšPwD BvD¤CsB§CqD§Rnw¡›q®’¶zº„¼}½¼~½½½€¾¾€¾}¾€¾}¾¿¿€¼»»µƒ«ƒ­„´€¸„·‚®†š…o‹_ƒc‘dddgj€jˆkmn„nyr„yq}ƒl…ƒ‡i‰ƒfƒe‚b‚a‘ƒ‘aŽƒŒ_‹„†_‡„†b…ƒ…g†ƒ„m…ƒ‰pƒq‹‚Œrt‹‹u€Œv‹ŠxŒ~}ŽŒ|}‘}‘v“u€vŒv‰„…x€…v~k‡a„U‡M‡E„>‹9„8Ž:„;Œ<„>Š@†@…DˆF‚IŠLLLz7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ0ÿ1ÿ0ÿ0ÿ0ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ,ÿ,ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ.ÿ.ÿ.ÿ-ÿ.ÿ0ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ.ÿ.ÿ.ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ.ÿ/ÿ/ÿ0ÿ0ÿ/ÿ/ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ2ÿ3ÿ3ÿ1ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ/ÿ0ÿ0ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ,ÿ+ÿ*ÿ*ÿ)ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ%ÿ%ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ!ÿ!ÿ!ÿ!ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿÿÿ ÿ ÿ!ÿ$ÿ'ÿ,ÿ4ÿ=ÿCÿFÿGÿHÿKÿQÿUÿZÿaÿfÿhÿlÿkÿiÿeÿZÿBÿ-ÿ(ÿ*ÿ+ÿ+ÿ*ÿ*ÿ(ÿ(ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ'ÿ#ÿ"ÿ%ÿ%ÿ)ÿ,ÿ2ÿ;ÿZÿuÿlÿiÿkÿjÿkÿeÿ[ÿMÿLÿEÿGÿEÿ9ÿ5ÿJÿPÿQÿWÿ`ÿ_ÿ[ÿ_ÿ_ÿZÿZÿ[ÿ_ÿVÿSÿ:ÿ+ÿ/ÿDÿMÿ9ÿ/ÿ/ÿ3ÿ@ÿDÿEÿEÿ@ÿ9ÿ9ÿ:ÿ8ÿ8ÿ<ÿ>ÿ?ÿBÿDÿHÿIÿDÿ?ÿ?ÿ:ÿ:ÿ7ÿ5ÿ5ÿ5ÿ5ÿ8ÿ9ÿ9ÿ8ÿ3ÿ.ÿ-ÿ,ÿ.ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ2ÿ2ÿ1ÿ1ÿ2ÿ2ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ/ÿ/ÿ/ÿ1ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ2ÿ3ÿ3ÿ4ÿ4ÿ4ÿ5ÿ6ÿ6ÿ8ÿ7ÿ9ÿ8ÿ9ÿ9ÿ:ÿAÿNÿYÿhÿmÿmÿjÿeÿ\ÿNÿ>ÿ5ÿ2ÿ0ÿ.ÿ+ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ/ÿ/ÿ0ÿ0ÿ2ÿ2ÿ1ÿ2ÿ2ÿ0ÿ/ÿ0ÿ4ÿ4ÿ6ÿ8ÿ<ÿ@ÿGÿHÿNÿVÿ\ÿdÿiÿoÿuÿyÿ}ÿ€ÿ‚ÿÿ‚ÿƒÿƒÿ„ÿ‡ÿ‹ÿŒÿ‡ÿ|ÿhÿSÿXÿ_ÿdÿbÿdÿoÿwÿxÿwÿvÿvÿyÿyÿwÿvÿpÿnÿnÿoÿtÿwÿzÿ{ÿ}ÿ}ÿÿ~ÿ~ÿ~ÿ}ÿ|ÿzÿyÿvÿtÿrÿqÿqÿpÿnÿnÿmÿlÿjÿfÿdÿbÿaÿaÿ`ÿaÿ`ÿVÿMÿMÿPÿHÿCÿCÿCÿCÿBÿDÿKÿoÿ—ÿ¬ÿ¶ÿ¹ÿ¼ÿ½ÿ¼ÿ¼ÿ¼ÿ¼ÿ½ÿ½ÿ½ÿ½ÿ¼ÿ¼ÿ»ÿ»ÿ¸ÿ·ÿ±ÿ­ÿ¦ÿ¦ÿ«ÿ¯ÿ®ÿ¤ÿ—ÿ€ÿxÿzÿwÿwÿvÿvÿwÿwÿwÿvÿyÿyÿ}ÿ}ÿ€ÿƒÿ…ÿˆÿ‹ÿÿÿ‘ÿ’ÿ‘ÿ’ÿ’ÿ‘ÿ‘ÿŽÿŒÿ‰ÿ‡ÿ†ÿ…ÿ„ÿ„ÿ„ÿ„ÿ„ÿˆÿŒÿŒÿŒÿŒÿŒÿŒÿ‹ÿ‹ÿŒÿ‹ÿ‰ÿŠÿŒÿŒÿÿÿŽÿ’ÿ‘ÿŽÿŽÿŒÿ†ÿ…ÿÿ}ÿxÿnÿgÿZÿOÿEÿ>ÿ;ÿ:ÿ7ÿ8ÿ7ÿ9ÿ<ÿ=ÿ?ÿ@ÿAÿDÿFÿGÿJÿJÿJÿ7~7t7~7t7}7s6}6s6}6r5}5s4~4t3~3t3~2u2~2u3~1u0~/u0~0u/~.u.~.t-~-t-~-t.~.t-~-s-~-s-~-s-/t..t//u00t00v00v00v0~0v1~1v1~1w0~0w0~0x0~0x0~0y/~/y0~0z/~/z/~/{.~.{-~-{-~-{-~-|,~,|,~,|,~,|-~-}-~-}..}..~.}./}-.}.~-}-~.}.~/}/~0~0~/~/|/~/|/~/{//{//{00z//y0€0{0€0{1€1{2€2{21{12{12z22z22{11{10{00|0~0{0~0{0~0|0~0|/~/|.~.|-~-|-~-{,~+{+~+{*~*{)~){)}){)(|'~'|'~'|'~&|%~$|$~#|"~"|!~!~"~"~!~!~ ~ ~ ~ }~}~} ~ } ~ }!~#)/€6>„E|F‰FzF‹IwNUxYŽ`wehujitigtW7v.-y,Š-|)Š*|*ˆ)|)Š(z(Š({(Š*{+Š+|)‹(|'Š'|%ˆ&~*ˆ(~I†p€jƒj€f‚geƒc}P†A{K†MyI‡DvBˆ@w@ˆFxN‰Xyb‹dz\ŽZzb_yWQxR\yYOzD:{3Ž6{=‹2|.Š/}08}?E}F‘B};’>~@‘:88€9:€;‘?H“K}F“?}:•7|5•6{5–4{4–6{7—;{;—8z5•2z/”-z-“/z/“0z0“1z1“1z1“2z2“2z2“/z/“/{/’0{0‘0{0/{//{/‘0{0‘/{/‘.{/‘0{0‘1{1‘1{1‘2{2‘2{21{23{46|67|77|75~9CP‹^€hˆj„h…f…]ˆPƒ?Œ642~1.~,Ž,~,Ž,~,.~/.~.Ž.~.Ž//001€10€22€2Œ00‹0ƒ/‰0„/ˆ/„0ˆ0„2‰6ƒ<‡C‚I†OƒU„\‚bjƒn|rƒvux„{m}„~j„ƒhˆ„hŠƒ€mq‚etabtf}nnv~wkx}vju}unu}tormkh‚gjg„jim„siw„ygz„|g~ƒf~ƒ}f~ƒ~g|ƒyhwƒukvsrt€rxp}p}o{m‚iyf†dudŠat_‹_t_Œat[‘KuI—JvLŸIvE£CsD¥@oA£Glm•n©‘²x¹†ººƒ¼¼ƒºº‚¹‚¸µ„³±…°®‡«~¨‡¦~£†¡~Ÿ… |¤ƒ£| …™{”†‹xŠ…Šxˆ‚ˆw†‚…v…‚„tƒƒ‚o„€o„‚i‚„…f…„‡e‰ƒ‹dŽƒc‘‚’c’‚’b’ƒ‘`ŽƒŒ]‰„‡\‡„ƒ_‚ƒ‚`ƒƒƒcƒƒ†g‰ƒ‰m‹‚‹pŒŒsŒŒuŒ€‹uŠ‰xŠŒzŠŒ{‹w‹‚…w€„{{v†qi‡aƒZˆMˆCˆ=‹<‡=Œ=…=Ž<ƒ:Ž989<Š?ƒ?ˆ?†C…DˆD‚DŠDDD€7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ4ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ3ÿ1ÿ0ÿ/ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ/ÿ0ÿ0ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ2ÿ2ÿ2ÿ1ÿ0ÿ2ÿ1ÿ1ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ+ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ$ÿ$ÿ#ÿ#ÿ"ÿ"ÿ!ÿ!ÿ"ÿ"ÿ!ÿ!ÿ ÿ ÿ ÿ ÿÿÿÿÿ ÿ ÿ ÿ ÿ!ÿ&ÿ)ÿ/ÿ6ÿ>ÿDÿGÿGÿHÿGÿKÿPÿXÿ]ÿ`ÿdÿdÿeÿeÿbÿIÿ-ÿ+ÿ+ÿ)ÿ+ÿ'ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ*ÿ*ÿ*ÿ)ÿ)ÿ'ÿ&ÿ&ÿ'ÿ>ÿhÿfÿgÿfÿgÿdÿcÿNÿ@ÿDÿMÿMÿLÿ>ÿ<ÿJÿNÿIÿWÿ_ÿcÿ_ÿ_ÿ`ÿdÿ`ÿTÿTÿZÿ]ÿaÿOÿGÿBÿ;ÿ4ÿ8ÿ8ÿ7ÿ9ÿ1ÿ1ÿ4ÿ<ÿCÿCÿBÿBÿ?ÿ:ÿ9ÿ9ÿ7ÿ7ÿ8ÿ;ÿFÿLÿMÿEÿ=ÿ7ÿ4ÿ4ÿ3ÿ5ÿ5ÿ6ÿ7ÿ:ÿ<ÿ>ÿ<ÿ8ÿ3ÿ0ÿ-ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ/ÿ/ÿ.ÿ/ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ1ÿ1ÿ1ÿ1ÿ2ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ4ÿ8ÿDÿQÿ^ÿgÿgÿdÿ_ÿUÿCÿ5ÿ4ÿ4ÿ1ÿ0ÿ/ÿ-ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ.ÿ.ÿ.ÿ.ÿ/ÿ0ÿ1ÿ1ÿ0ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ0ÿ0ÿ.ÿ-ÿ.ÿ.ÿ,ÿ.ÿ.ÿ/ÿ3ÿ7ÿ<ÿ@ÿGÿMÿSÿYÿ^ÿdÿhÿlÿpÿsÿxÿ€ÿ…ÿˆÿ‡ÿ€ÿsÿgÿhÿqÿyÿ|ÿ{ÿxÿwÿwÿvÿqÿlÿgÿdÿ\ÿTÿVÿ`ÿgÿmÿrÿwÿxÿzÿ}ÿ~ÿ}ÿ~ÿ}ÿÿ|ÿ}ÿ{ÿyÿxÿvÿuÿtÿsÿrÿqÿoÿmÿjÿfÿbÿaÿ_ÿ_ÿ_ÿ`ÿ]ÿPÿHÿJÿIÿHÿDÿAÿCÿCÿGÿQÿjÿÿ£ÿ­ÿ°ÿ±ÿ±ÿ±ÿ±ÿ­ÿ¬ÿªÿ©ÿ§ÿ¤ÿ¤ÿ¡ÿŸÿžÿœÿ›ÿšÿœÿÿœÿšÿšÿ™ÿ˜ÿ•ÿ’ÿ‘ÿ‘ÿÿÿŽÿÿŒÿŠÿˆÿ†ÿ„ÿƒÿ…ÿ„ÿƒÿ…ÿ…ÿ‡ÿ‰ÿ‹ÿÿŽÿ‘ÿ’ÿ’ÿ’ÿ’ÿ‘ÿÿÿŠÿˆÿ‡ÿƒÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ…ÿˆÿˆÿ‰ÿŠÿŒÿŒÿŒÿŒÿ‰ÿˆÿ‰ÿ†ÿ‡ÿ‡ÿ…ÿ‚ÿ‚ÿ}ÿyÿpÿjÿcÿZÿOÿGÿBÿ;ÿ9ÿ;ÿ;ÿ;ÿ<ÿ=ÿ=ÿ<ÿ<ÿ:ÿ9ÿ:ÿ=ÿ?ÿAÿ@ÿCÿDÿDÿCÿCÿAÿAÿ8~8s7~7s6}6t7}7t6}6t6}6t5}5t3}3t3~2u2~2u2~2u1~0u1~1t0~/t/~/s/~/s-~-s-~-s-~-s.~.s..t..u..u//v//v00v00v00v11v11v0~0v0~0v0~0v0~0v/~/x/~/x//y//y/~/y.~.z-~-{-~-{-~-{,~,{-~-{-~-{,~+{-~-|.~.}.~.}.}.~.}/~/}//}/~.}.~.}.~.~.}.~.}.~.{.~.z.~.{.~.{//z//z0€0z0€0z1€1{2€2{22z11z21{1~1{0~0z0~0{/~/{0~0{0~/|/~/|.~.{-~-{.~.{.~.|-}-|-},|+~+{*~*{*~*{*~)})~)|(~'|'~'}(~&}%~${$~#{$~#|#~"|"~"~!~!~!~!~ ~ ~ ~ }~}~|~~ ~ #~%‚)/„6?†F|H‰H{H‹HxKOyTŽYy[Ž^x_bv`Ww9'y*Š)|'‰(|(‰(|(‰(|)Š(|(Š(|(Š)|+Š+|+Š*|(Š){,Š$|C‡l~`…b€b‚dcƒe}P„;|B…<|:„KzG…>w=‡EyDˆSzd‰az]Œ]yaŽby_cx^’ayb‘`ybTzHD{HV|JŒ>~BŒG}9Œ,}0Ž5};?}B‘G~B’:}7’6~6‘3€3‘6€A‘J}M’G}B”:{5•3{3•5{5•6{7—9{>—?|A–>|:–5z1”.z.“/z/’0z0‘1z1‘2z3‘5z5’1z0’/{/‘/{/0{0.{./{//{//|/‘/|/‘0|00|00|00|00|00|00|01|12|22|23}8ŽC~Q‹^€cˆb‚`ˆXJŠ;€44~3Ž0}/Ž/}/Ž-},Ž,~,,~,,~,-~-/~/Ž0~1Ž11Ž11Ž1€1Œ2€1Œ00Œ0/Œ.€-‹-€-Š-€-Š.€.‰-€-‰.1Š3„;‡?†D„H‡LR‡W|^†dti‡ppu‡wqw„rql‚mosxmw€wjy€vms~prj~as]XsM‚HqQƒZt]ƒdrmƒrnrƒwj}ƒ~g~ƒ~e~ƒ~d}ƒ~d}ƒ|h{ƒymw€vrutvus|p~l€hzf…aw_ˆ`t`‰_s_[tN’LtJœEoE¡HpPŸVo^—jovv †¤¦€¦„¦}¤…£} † |Ÿ†žzœ†›y›†™y—†–z˜†˜y˜…˜x˜„˜v—…—u–…–t•…“t’…’s„q„oŽ„ŒlŠ„ˆh†…ƒg„…‚f†ƒe†††d‡„‰dƒŽc‚’c“‚“e’‚’f‘‚fŽ‚Šb‡‚ƒa‚‚‚a‚‚‚aƒ„d†„†gˆƒˆk‰ƒ‰nŠ‚Št‡‚w~…|{z„v}r„ni‡aƒ\ˆU‡LˆF‹>‰;:†9:„89‚;<ƒ<Ž=ƒ=<ƒ<‰;„:‰<ƒ>‰>†B†B‡A…BˆCƒ@Š@‚@Š?ƒ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ*ÿ+ÿ*ÿ,ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ'ÿ'ÿ'ÿ'ÿ&ÿ%ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ"ÿ"ÿ"ÿ"ÿ!ÿ!ÿ ÿ ÿ ÿ ÿ ÿ ÿÿÿÿÿÿÿ ÿ!ÿ#ÿ%ÿ)ÿ/ÿ4ÿ<ÿBÿGÿHÿIÿIÿIÿPÿUÿVÿWÿZÿYÿZÿWÿFÿ-ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ+ÿ+ÿ,ÿ,ÿ,ÿ)ÿ&ÿ&ÿ=ÿmÿdÿ[ÿ[ÿaÿfÿhÿSÿ>ÿ@ÿ;ÿ7ÿ6ÿ@ÿFÿ=ÿ5ÿ5ÿ6ÿPÿcÿcÿcÿdÿdÿdÿaÿ_ÿ`ÿdÿeÿbÿcÿXÿLÿDÿGÿJÿAÿ;ÿ@ÿAÿ:ÿ1ÿ.ÿ0ÿ2ÿ8ÿAÿDÿEÿ?ÿ6ÿ3ÿ4ÿ0ÿ1ÿ2ÿ:ÿBÿEÿFÿBÿ;ÿ7ÿ4ÿ4ÿ5ÿ5ÿ7ÿ9ÿ;ÿ=ÿ?ÿAÿAÿ>ÿ:ÿ5ÿ1ÿ/ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ2ÿ3ÿ4ÿ4ÿ4ÿ2ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ1ÿ6ÿAÿMÿZÿaÿ]ÿZÿRÿEÿ9ÿ1ÿ2ÿ1ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ/ÿ/ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ2ÿ1ÿ1ÿ2ÿ0ÿ/ÿ.ÿ.ÿ-ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ/ÿ.ÿ.ÿ/ÿ0ÿ0ÿ3ÿ6ÿ:ÿ>ÿDÿIÿMÿSÿVÿ[ÿ^ÿ^ÿ^ÿaÿfÿjÿkÿlÿlÿjÿgÿdÿ]ÿUÿQÿMÿMÿOÿIÿEÿQÿ^ÿfÿjÿmÿpÿuÿ{ÿÿ€ÿÿ|ÿ|ÿ|ÿ|ÿ}ÿ}ÿ{ÿyÿyÿvÿvÿtÿtÿqÿmÿjÿhÿeÿcÿaÿaÿ`ÿ`ÿ`ÿTÿLÿNÿQÿXÿ_ÿkÿsÿ|ÿ‚ÿÿ”ÿ›ÿžÿžÿŸÿŸÿœÿ›ÿÿÿœÿ›ÿ™ÿ™ÿ™ÿ˜ÿ™ÿ˜ÿ—ÿ—ÿ–ÿ–ÿ–ÿ•ÿ•ÿ”ÿ“ÿ“ÿ‘ÿÿÿÿŽÿŽÿŒÿ‹ÿŠÿŠÿ‰ÿ‡ÿ†ÿƒÿƒÿ‚ÿ‚ÿ‚ÿƒÿƒÿ„ÿ‡ÿ‰ÿÿŽÿÿ’ÿ’ÿ’ÿ’ÿ‘ÿÿÿŠÿˆÿ…ÿƒÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ„ÿ„ÿ†ÿ‡ÿ‡ÿ‡ÿ„ÿ‚ÿ}ÿwÿmÿfÿdÿaÿYÿQÿKÿHÿAÿ<ÿ;ÿ;ÿ;ÿ;ÿ;ÿ:ÿ;ÿ<ÿ<ÿ;ÿ<ÿ<ÿ=ÿ=ÿ<ÿ<ÿ:ÿ;ÿ<ÿ=ÿ?ÿ?ÿBÿBÿBÿBÿ@ÿ@ÿ?ÿ@ÿ7~7r6~6r7}8s7}7s6}6t5}5t4}4t2}2t1~1u2~2u3~3u1~0u/~/t/~/t/~/t/~/t-~-s.~.s.~.s/~/s..t//t//u..v00v00v00v11v11w11w1~1w1~1w1~1x1~1x1~1y1~1y00y//y//y..z.~.{-~-{-~-{-~-{-~-{-~-{-~-{-~-|.~.}.~.}/}/~/}/~/}//}/~.}.~.}.~.}.}.~.}.~.}.~.|.~.|.~.|//{//{/€0{/€/{0€0{0€0{22{11{11|0~0|0~0{0~0|/~/|0~0|/}.|.}.|.~-{.~.{-~-|,~,|++|++|+~+}*~*})~({*~(}'~'|'~'|&~&|%~$|#~#{#~"{#~"{"~!{!~!~!~ ~ ~ ~ ~ ~~}~}~|~~!~"%~(ƒ)-…3;ˆA|D‰F{HŠJxLŒOxRSxUŽUxSMx?Ž4y)‹'{'Š'{'‰)|)Š(|(Š(|(Š)|)Š)|)Š+|+Š-}+Š+|)‰*~0†l€lƒ`_‚a€d‚fV„8zA‡Dx<ˆ2x3‡9y@‡DyB†KzE‡Iz_ŠbzaŒf{fb{c_{cd|dgzegzuKz@>{?=}>@}AŒ9{0Ž/{.7{EE{D’@{8“2{1“.|-’/}2‘9}@’B}B”?{;•6{5•4{3•3|6–8|:–<|?–B|B–?{:•6{2”0z1‘1z00z12z34z4Ž5z2Ž2{00{//{.Œ.{.Œ.{/Ž.{.Ž.|..|.-|-Ž.|.Ž/|//|//|/Ž/|/Ž.|.Ž.|.Ž.|.Ž.|.Ž/}3>}IŒW[ŠV€S‰LAŠ7~0Œ/}//|.Ž.}.-}-,~,,~,,~-.~..~/Ž1~0Ž00Ž00Ž11Ž11Ž100€/.€-‹.€-Š,€-Š-€-Š.€.Š0.‰./ˆ/0ˆ/‚0†0ƒ/‡1†5†6‡8„<‡>…A‡C‚E†HM…PT„V~V„X€U‚SN‚E‚C‚C‚FKƒF€?‚D€R[‚a{f‚lwo‚vr}ƒ€jƒg|ƒ|d}ƒ|d|ƒ}f|‚{iy‚xnwwrs€qwomzj|g}ezb„cwaˆcubŒ\s_ŽkvqŒ{wƒ†Šz€’~˜|››y›‚œy„x…zœ†›y›…›y›…›z™…™z˜…—z•„•z•ƒ•{”ƒ”{’…x…y…Žw…Žv„t„‹r‹„‹p‰„ˆl†„…i‚„i„€d„‚c‚†…a‡„‡c‹„ŒeŽ„f‘‚’h’‚‘hŽŒgŠ†f„ƒd‚d‚d‚ƒƒe„ƒ…f‚‚ƒh‚…lw†lu_‰U‚JˆE‡@ˆ?ŠAˆ?‹B†@Ž:„<<ƒ;<‚;<€<<€<<<‹=ƒ<Š<ƒ=Š:„9‰<„<‡=…=†?†B†C‡C…B‡@ƒB‡D„7ÿ7ÿ6ÿ6ÿ7ÿ8ÿ7ÿ7ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ3ÿ1ÿ0ÿ0ÿ/ÿ/ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ0ÿ0ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ(ÿ(ÿ*ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ%ÿ%ÿ$ÿ#ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ!ÿ!ÿ!ÿ ÿ ÿ ÿ ÿ ÿÿÿÿÿÿÿÿ ÿ"ÿ#ÿ&ÿ)ÿ,ÿ0ÿ4ÿ<ÿAÿDÿFÿGÿJÿLÿNÿPÿRÿRÿSÿOÿGÿ7ÿ-ÿ'ÿ'ÿ'ÿ(ÿ'ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ+ÿ+ÿ*ÿ,ÿ+ÿ(ÿ,ÿeÿkÿhÿhÿfÿcÿgÿWÿ7ÿAÿHÿGÿ8ÿ-ÿ/ÿ7ÿ:ÿ=ÿ>ÿDÿ?ÿPÿeÿeÿgÿjÿeÿcÿdÿdÿbÿ_ÿaÿeÿbÿfÿdÿGÿBÿBÿDÿEÿEÿEÿEÿ>ÿ3ÿ/ÿ.ÿ2ÿ7ÿ9ÿAÿEÿ>ÿ3ÿ/ÿ.ÿ-ÿ.ÿ/ÿ2ÿ9ÿ>ÿAÿ?ÿ9ÿ7ÿ6ÿ6ÿ4ÿ5ÿ5ÿ7ÿ8ÿ;ÿ>ÿ@ÿ@ÿ@ÿ>ÿ:ÿ6ÿ1ÿ1ÿ1ÿ0ÿ0ÿ1ÿ2ÿ3ÿ4ÿ4ÿ4ÿ4ÿ2ÿ2ÿ2ÿ1ÿ0ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ1ÿ<ÿFÿSÿVÿRÿNÿIÿ@ÿ6ÿ-ÿ,ÿ,ÿ,ÿ-ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ.ÿ.ÿ.ÿ/ÿ1ÿ0ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ.ÿ-ÿ.ÿ.ÿ-ÿ-ÿ.ÿ/ÿ/ÿ.ÿ0ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ/ÿ0ÿ.ÿ.ÿ/ÿ1ÿ0ÿ4ÿ6ÿ8ÿ=ÿ>ÿ?ÿBÿAÿAÿ>ÿ;ÿ;ÿ;ÿ>ÿBÿBÿ>ÿ?ÿEÿNÿSÿZÿbÿeÿnÿuÿ}ÿÿÿ~ÿ}ÿ}ÿ|ÿ}ÿ~ÿ|ÿ{ÿyÿxÿyÿyÿvÿtÿqÿoÿnÿkÿjÿgÿiÿlÿrÿvÿxÿ|ÿ‚ÿ‹ÿÿ’ÿ–ÿšÿ™ÿšÿœÿÿœÿœÿ›ÿ›ÿ›ÿ›ÿ›ÿ›ÿ›ÿšÿ™ÿ™ÿšÿšÿ˜ÿ—ÿ”ÿ”ÿ”ÿ”ÿ”ÿ”ÿÿŒÿ‰ÿ‰ÿˆÿŠÿÿÿÿÿÿÿŽÿŽÿŒÿŠÿˆÿ‡ÿ„ÿ‚ÿÿ€ÿ€ÿ€ÿ€ÿ~ÿ‚ÿ„ÿˆÿŠÿ‹ÿŒÿÿ‘ÿ‘ÿÿÿŽÿ‹ÿ‡ÿ…ÿ„ÿÿ‚ÿÿÿÿƒÿ„ÿ„ÿÿÿ€ÿzÿrÿiÿ^ÿTÿJÿCÿ?ÿ@ÿ@ÿ?ÿBÿAÿ=ÿ<ÿ<ÿ;ÿ<ÿ<ÿ<ÿ<ÿ;ÿ=ÿ<ÿ<ÿ<ÿ;ÿ;ÿ;ÿ<ÿ;ÿ<ÿ<ÿ=ÿ=ÿ@ÿCÿDÿDÿEÿEÿFÿFÿ6~6s7~7s6~8s6~6s7}7s5}5s5}4s3}3s2~2t2~2t2~1u0~/t.~.s/~/s//t//t/~0s/~/s/~/r.~.s.~.r/~/s//t//t/~/v0~0v0~0w1~1w11w11w11w11w11x11x11y11y/~/z.~/z/~/{/~.{.~.{-~-{,~,|-~-|-~-z-~-z-}-z-}-|-~-}-~.}00~//~/}/~/}/~.}.}.}.}..}.~.}.~.~-~-~-~-}-~-|.~.|.~.|/~/|/~/|/~/|/~/|//{/0{00}0}0|0~0|0~0|/~/|/~/|/}.|.}.|.~-{,~,{,~,|+~+|+~+}+~+}*~*~)~(~)~)}(~(}''}&&}&€%}%€%}$~#|#~"|"~!|"~"|!~!~!~!~ ~ ~~~~ } } |~~~!}$‚&}+….~2‡7~?‰B{HŠIzI‹JyLŒNxOŽPxQQwMHy:Œ,z'‹&{&Š&{'Š(|)Š(|(Š({(‰*|*‰)|)Š*|)Š)|*‰,}-‡]€lƒegdbf€V‚,}<†FyRŠQtJ=t3‹3v7‡;w5†5{:‡R{f‰q{n‹m|e‹^}aŒb~cŒ_}]Ža|f[zROzKKzJŽHzJI|HB{8Ž0|/Ž1z25z@’G{D“:|1’-|-‘-}--~16}<’;};“7{5•5{5•4z4”5{6”8{=–={>•?|@•=|9•7{3’2{10{11{24z4Ž5{54{4Œ4{4‹4{1‹0{.‹.{.‹-{-‹.|.‹.}-Œ-|-Œ-|-Œ-|-Œ.}.Œ/}/Œ.}.Œ-~-Œ.~.Œ-}-Œ-}-Œ,}0Œ9}BŒM}UŠP~L‰F?Š5~-‹,},,},,},,},,},+}+,~-.~..~//~/0~00~00~01~11~1Ž0~/Ž/~/.~...‹..‹/‚/‰/‚/‰0ƒ0‰0ƒ0ˆ0ƒ0ˆ0ƒ0ˆ0ƒ0ˆ.ƒ.ˆ.ƒ.Š.ƒ.Š.‚.‰.ƒ/Š0‚/‰00Š11Œ11Œ3€6Š7€:‰;€;†==†A‚G…KƒP„Wƒ_~hƒruyƒ~nƒ€h}ƒ|d}ƒ~d}„|f{„{i{‚zjv‚vltsoq€orn€oyt}z|‚}ˆ{~z•€—y—€™x™›x››x›‚›x›„šy›ƒ›xš…šxš…˜{š…—}˜…—~—…˜€˜„–€„‹€ƒ’‚’ƒ’ƒ‘„‰ƒ…„ƒƒ‚…’}””y”€•v”“u’sŽ‚Œpˆƒ†lƒƒi€„€e†}a†`†…ˆcŠ…ŒgŽ‚ghŽ€Žj‹Šj†„iƒ€ƒg‚ƒf‚ƒcƒc‚…c~„{ft„mki‡av\ˆPBˆ@‰@†?‹B„@>…<Œ<…<=ƒ=;ƒ;Ž;ƒ;Ž;ƒ;Œ<ƒ;‹<ƒ:‹<ƒ=Š?„?ˆ?„@ˆB…E‡F…F‡G…G…H…G…7ÿ7ÿ7ÿ7ÿ6ÿ8ÿ6ÿ6ÿ7ÿ7ÿ6ÿ5ÿ5ÿ4ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ-ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ/ÿ/ÿ/ÿ.ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ(ÿ'ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ%ÿ$ÿ$ÿ#ÿ#ÿ"ÿ"ÿ!ÿ"ÿ"ÿ!ÿ!ÿ!ÿ!ÿ ÿ ÿÿÿÿÿ ÿ ÿÿÿ!ÿ"ÿ#ÿ&ÿ(ÿ,ÿ0ÿ3ÿ9ÿAÿFÿIÿIÿIÿJÿLÿNÿOÿPÿPÿNÿIÿDÿ4ÿ(ÿ'ÿ&ÿ&ÿ&ÿ'ÿ(ÿ(ÿ(ÿ(ÿ'ÿ)ÿ)ÿ)ÿ*ÿ(ÿ(ÿ(ÿ(ÿ)ÿ-ÿ[ÿnÿdÿgÿeÿ`ÿhÿ[ÿ-ÿ2ÿ>ÿGÿTÿYÿXÿQÿFÿ8ÿ:ÿ<ÿ9ÿ8ÿ?ÿ^ÿlÿtÿrÿlÿhÿjÿeÿ^ÿ`ÿaÿaÿfÿfÿ\ÿSÿRÿOÿLÿNÿOÿNÿLÿLÿHÿ?ÿ3ÿ/ÿ2ÿ3ÿ7ÿ=ÿCÿFÿ?ÿ4ÿ/ÿ-ÿ-ÿ-ÿ,ÿ-ÿ/ÿ4ÿ7ÿ7ÿ7ÿ6ÿ5ÿ5ÿ5ÿ4ÿ6ÿ7ÿ9ÿ<ÿ=ÿ>ÿ@ÿAÿ?ÿ<ÿ9ÿ6ÿ2ÿ1ÿ0ÿ1ÿ1ÿ2ÿ3ÿ4ÿ5ÿ5ÿ5ÿ5ÿ7ÿ7ÿ5ÿ2ÿ2ÿ/ÿ/ÿ/ÿ.ÿ-ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ7ÿAÿJÿRÿOÿJÿFÿ?ÿ5ÿ-ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ-ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ1ÿ3ÿ3ÿ6ÿ6ÿ7ÿ7ÿ8ÿ;ÿ<ÿ?ÿEÿNÿYÿcÿnÿwÿ}ÿÿ€ÿ~ÿÿ€ÿÿ~ÿ}ÿ|ÿzÿyÿ{ÿ{ÿzÿyÿwÿuÿvÿwÿÿ†ÿŠÿÿ“ÿ–ÿ˜ÿ—ÿ—ÿ™ÿ™ÿšÿ›ÿ›ÿ›ÿ›ÿ™ÿ˜ÿ˜ÿ˜ÿ–ÿ”ÿ’ÿŽÿ‹ÿ‰ÿ‰ÿˆÿˆÿ‰ÿŠÿ‹ÿ„ÿ€ÿ†ÿ‹ÿŽÿÿ‰ÿÿrÿrÿzÿ‚ÿ’ÿ™ÿÿ›ÿ›ÿ›ÿ›ÿšÿ–ÿ“ÿÿÿ‹ÿˆÿ„ÿ‚ÿ€ÿ€ÿÿ€ÿ~ÿÿ‚ÿ…ÿ‡ÿˆÿ‰ÿŠÿŠÿŠÿ‹ÿ‹ÿ‰ÿˆÿˆÿ…ÿƒÿƒÿ‚ÿƒÿ‚ÿÿÿÿ‚ÿ‚ÿÿÿ|ÿxÿsÿoÿhÿ`ÿQÿBÿ=ÿ<ÿ>ÿ<ÿ;ÿ;ÿ<ÿ<ÿ<ÿ<ÿ;ÿ;ÿ;ÿ;ÿ;ÿ;ÿ;ÿ=ÿ>ÿ?ÿ?ÿ@ÿBÿBÿBÿCÿEÿGÿGÿGÿHÿHÿHÿGÿ8~8t7~7t7~7t6~6t6}6u5}5u4}4u3}2u2~2v2~2v2~1u1~0t0~0t/~/t/0t00t//s//s/~/r.~.s/~/u/~/u..v..w/~/w/~/w0~0w0~1w11x11x11x11x0~/x.~/x1~1y0~0y/~/y/~.y-~-y.~,y-~-{,~,{,~,|-~-|-~-|,~,|,,|,,}.~.}.~.}/~/~/~/~/}/~/}/~.}.}.}.}.~.}.~.}-~-}.~.}.~-|-~.{.~.{.~.{.~.|.~.|/~/|/~.|//}00}00}0}0|0~0|0~0|0}0|/}/|/}.|.}.|.~-|-~-|,~,|+~+|,~,}+~*}*~)~'~'~'~'}(~(}'&}&%}%~%}$~$}#~"|"~!|"~!|!~!| ~ ~"~"~ ~ ~~~~~ ~ } ~ } ~#‚%}(„)},‡1~5ˆ<~CŠI}JŠI|H‹IzJŒKyNŽMyLLyE?y.'y&‹%{&Š'{(Š(|(Š'|'Š){(‰)|(‰(|(‡({)ˆ*}(†Qmƒc‚ed‚a€c]ƒ.€/†7=‡F{PŠWvXYtMŽ7t2Š9w:†?{<†\|t‡r|rˆp~cŠY~a‹^~[`}fŽh{eŽ]zWSzQQzRQzPŽN{LŽJyCŽ9z3Ž3z/1{8’=zC“A|8’1|-‘*{*+}*,}.‘1}3’5|4“4{5”6z7•8{8–7{9•<{>•@|A•>|=•:{7“5{2‘0{/1{21z3Œ4{5‹5{6‹8{9Š6|4‰3|1‰0|0‰.|-‰.}.‰.},‰,|,Š,|,Š+|+Š,},Š-}-Š-}-Š-},Š,},Š-~-Š-~-Š,},‹5}@‹F}QŠN~I‰E~@ˆ7}.Š(}(‹)})‹)})‹*}*‹*}*‹,},‹-~-‹.~.‹-~-‹.~.‹/~/Œ/~/Œ0~00~01~1Ž1~0Ž0~0/~///////Œ//Œ0‚0Š0‚0‰0ƒ0ˆ0ƒ0ˆ0ƒ0ˆ0ƒ/ˆ.ƒ.ˆ.ƒ-ˆ.€.Š-€-Š..Š.€/Œ/~//~/0001Ž22Ž2€3Ž44Ž5‚56‚;‰IƒWƒcƒmzxƒo„ƒi€ƒg„}g€„~hzƒyixƒzh{„{h{‚{k|€l‡‹nŽ‚’o“ƒ•q•‚•v–‚—v™ƒ™xšƒšx™„šy˜…”{‘‡}Šˆ†€‡|‡u‡oŒl‡h‘f‡f“k„m’p‚qs€{Œ‚„‹‚tŒh}n‹z|‹†˜zŸ~ž~ zž~ w wš€™u”‚‘rŒ‚‰p…„€n|…zk{†{j}†i€…e……†eˆƒ‰iˆ‚ˆm‰‰o‰€‰o†€†mƒ€‚kƒ€ƒi‚‚‚f€€c€ƒ€cƒe€ƒd}…wes‡hm]ŠM|@Š?†<†;Š:…:Œ;…;<ƒ<;ƒ;Ž:ƒ;Ž;‚;Ž>‚@Œ@‚A‹A‚BŠCƒDŠFƒFŠF„H‰G„G‰G…F‡F…F‡6ÿ6ÿ7ÿ7ÿ9ÿ9ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ0ÿ1ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ/ÿ.ÿ/ÿ0ÿ0ÿ/ÿ/ÿ.ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ)ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ$ÿ#ÿ"ÿ"ÿ!ÿ"ÿ!ÿ!ÿ!ÿ ÿ ÿ!ÿ!ÿ ÿ ÿÿÿ ÿÿÿÿ ÿ!ÿ#ÿ%ÿ(ÿ+ÿ+ÿ.ÿ3ÿ7ÿ>ÿEÿJÿJÿHÿEÿDÿFÿGÿIÿKÿJÿGÿCÿ;ÿ+ÿ&ÿ&ÿ&ÿ'ÿ'ÿ(ÿ)ÿ)ÿ'ÿ'ÿ)ÿ(ÿ'ÿ(ÿ(ÿ(ÿ)ÿ+ÿ%ÿOÿiÿbÿdÿeÿ`ÿeÿaÿ0ÿ,ÿ3ÿ8ÿ=ÿGÿNÿUÿYÿXÿPÿ4ÿ/ÿ9ÿAÿEÿHÿlÿtÿtÿtÿoÿ`ÿXÿ`ÿ[ÿZÿ_ÿcÿhÿgÿ`ÿYÿVÿTÿTÿQÿPÿPÿQÿPÿLÿGÿAÿ:ÿ5ÿ3ÿ1ÿ2ÿ7ÿ=ÿ@ÿ:ÿ2ÿ,ÿ*ÿ*ÿ*ÿ)ÿ*ÿ*ÿ-ÿ0ÿ1ÿ2ÿ3ÿ5ÿ7ÿ8ÿ8ÿ8ÿ7ÿ7ÿ9ÿ<ÿ>ÿ?ÿ?ÿ>ÿ=ÿ:ÿ7ÿ5ÿ2ÿ1ÿ1ÿ2ÿ1ÿ3ÿ3ÿ4ÿ6ÿ7ÿ8ÿ7ÿ6ÿ5ÿ4ÿ2ÿ0ÿ0ÿ0ÿ/ÿ.ÿ.ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ,ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ1ÿ<ÿEÿNÿMÿHÿEÿ@ÿ9ÿ4ÿ*ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ+ÿ+ÿ+ÿ,ÿ+ÿ+ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ/ÿ/ÿ0ÿ0ÿ0ÿ1ÿ1ÿ2ÿ2ÿ3ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ:ÿGÿVÿbÿlÿvÿ}ÿÿÿ}ÿÿÿÿ~ÿ~ÿ{ÿvÿsÿvÿzÿ~ÿÿÿ‚ÿ‡ÿŠÿÿÿÿÿ”ÿ”ÿ“ÿ•ÿ–ÿ–ÿ—ÿ—ÿ•ÿ’ÿÿ‹ÿ…ÿÿwÿpÿiÿcÿ[ÿUÿPÿMÿHÿHÿMÿQÿ`ÿ]ÿ_ÿdÿnÿsÿqÿlÿgÿsÿ…ÿ–ÿŸÿ£ÿ¢ÿ¡ÿ¡ÿ¡ÿ¡ÿŸÿÿ›ÿ‘ÿŒÿ‡ÿ„ÿ}ÿyÿtÿrÿsÿsÿxÿ{ÿ~ÿ€ÿ‚ÿƒÿ†ÿ‡ÿ…ÿ…ÿ†ÿ†ÿ†ÿ†ÿƒÿƒÿƒÿ‚ÿ€ÿ€ÿÿ€ÿ€ÿ~ÿ~ÿ~ÿ~ÿÿ‚ÿƒÿ‚ÿÿyÿqÿjÿ]ÿLÿ@ÿ=ÿ=ÿ=ÿ<ÿ;ÿ;ÿ:ÿ<ÿ<ÿ<ÿ;ÿ<ÿ>ÿ>ÿAÿCÿCÿDÿDÿEÿFÿGÿGÿGÿGÿGÿFÿFÿEÿEÿEÿEÿ7~7s7~7s6~6t6~6t5}5u5}5u4}4u3}2u2}2u22u1~0u0~0t00t00t//t0/t//s//s/~/r-~/t/~/u/}/v/~/w.~.w.~.w.~/w0~0w0~0w11x11y11z00z0~/z/~/z.~.y.~.y.~.z.~-z--z--z--{,,{,,|,,|+,|,,|,,|,,}-~-|.~.~/}/~/}/~0~0/~//~/}.~.}.~.},~.|.~.}.~.}-~-|-~-|.~.|.~.|.~.}.~.}.~.},~-}.~.}/~/}/~/|/~/|0~0|0~0|/~/{/~/{/}.}.}.~.~-~-~-~,~,~+~+~+~+}*~(}(~(}'~'}'~'}%~%{&~&{%~%{$~${$~#{#~"|"~!|!~!|!~!| ~ { ~ { ~ {~|~{~}  €%~'ƒ*}+…,}0‡4}9‰@~FŠI}I‹H{EŒByBŽFyGŽGzFEy?5y)&y&Š&z'‰'z(‰(|(‰(|'‰'|(Š({)Š*{*ˆ*|*…T~mƒ`‚b~e‚a~`cƒ1*†56‡6}=†DzIˆPwW‹XuTŒ>u=Œ=v>‰Ž8y1/{1’3{8’<|<“3|,’+|*‘)|))|*,|/1z3‘5z8“9z:”9{:”7{7”7{8”:|>”@|A“A{@‘={:6{42{01{2Œ3|3Š4|5ˆ6}7‡5}3ˆ5}4†2}2‡1}1‡0}.‡.}-‡-|-ˆ,|,ˆ+}+‰+}+‰+}+‰,},‰+~+ˆ,~,ˆ+~+ˆ*~*ˆ)~)ˆ.~8‰>~FŠK~GˆD~Aˆ:~5ˆ.}'ˆ'}(‰'}'‰&}(‰(}(‰)})‰)})‰(})‰+~+‰-~-‰.~/‰.~.Š/~00~10~0Ž0~1Ž2~2Ž1~1Ž1~1Ž/~/Ž//Œ//Œ/€/Œ/€/Š00Š/ƒ/‰1‚1ˆ/ƒ/ˆ.ƒ.‰-ƒ-‰..‰..‰.€.‹./‹/~/Œ1~/00Ž/111Ž23Ž444€422Ž4‚9ŒCƒS…`„k{s„yp„h„ƒj€„i}„|iw„qjo…tlx…}lƒƒgƒƒ…g†„ˆj‰…‰mŽ„ŽoŽ…s†sŽ†wŒ‡†|~ˆw‚o‡hŒ`†X’S…N–L„F˜CD›D€D›E~M˜]{X“RzO‘TzZ’_ygnyˆ”|ž~ ŸyŸ€žxŸ‚žx›ƒ–y‘„Œy†„€w|‡tyo‡iyd‡dve‡ium‡sow†|j~†€j„„l„ƒ„m„„qƒ€‚q‚‚o‚€ok}~j‚~f~‚}c~ƒ~dƒ€d„ƒ€b~„zerˆgpZŠF€>‰<‰;…;Ž<„<Ž>‚<>>?AB€CŽE€FG€G‹F€FŠFFŠFFŠFƒF‰EƒE‰E‚CˆC‚Dˆ6ÿ6ÿ7ÿ7ÿ7ÿ7ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ3ÿ2ÿ2ÿ2ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ-ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ0ÿ0ÿ1ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ,ÿ,ÿ-ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ)ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ%ÿ&ÿ%ÿ%ÿ%ÿ$ÿ$ÿ#ÿ#ÿ#ÿ"ÿ"ÿ!ÿ!ÿ!ÿ!ÿ!ÿ ÿ ÿ ÿ ÿ ÿ ÿÿÿÿÿÿÿ ÿ"ÿ$ÿ'ÿ,ÿ-ÿ/ÿ3ÿ9ÿ<ÿ@ÿFÿIÿIÿGÿBÿ?ÿ?ÿBÿEÿFÿEÿAÿ=ÿ/ÿ(ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ)ÿ)ÿ*ÿ+ÿ.ÿ_ÿkÿaÿaÿbÿ_ÿdÿdÿ5ÿ&ÿ2ÿ9ÿ;ÿ>ÿ>ÿBÿJÿOÿPÿPÿIÿ>ÿ;ÿ<ÿAÿCÿTÿmÿqÿrÿnÿeÿ[ÿYÿcÿaÿeÿkÿmÿkÿhÿeÿ^ÿ]ÿ[ÿYÿVÿRÿPÿSÿRÿOÿMÿJÿEÿ=ÿ3ÿ0ÿ/ÿ2ÿ4ÿ9ÿ9ÿ3ÿ,ÿ+ÿ*ÿ)ÿ)ÿ)ÿ)ÿ+ÿ.ÿ0ÿ1ÿ4ÿ8ÿ9ÿ:ÿ;ÿ<ÿ9ÿ7ÿ7ÿ7ÿ9ÿ=ÿ@ÿBÿBÿAÿ=ÿ:ÿ:ÿ6ÿ5ÿ2ÿ0ÿ0ÿ1ÿ3ÿ3ÿ4ÿ5ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ3ÿ2ÿ1ÿ1ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ-ÿ-ÿ,ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ,ÿ2ÿ:ÿ@ÿHÿHÿCÿ@ÿ<ÿ6ÿ/ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ)ÿ)ÿ)ÿ)ÿ(ÿ)ÿ*ÿ*ÿ+ÿ+ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ/ÿ1ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ/ÿ/ÿ0ÿ0ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ1ÿ1ÿ2ÿ3ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ2ÿ3ÿ5ÿ5ÿ5ÿ5ÿ2ÿ2ÿ2ÿ4ÿ7ÿCÿQÿ^ÿiÿpÿzÿÿ€ÿ€ÿ€ÿÿ}ÿ|ÿzÿvÿpÿoÿqÿuÿyÿ}ÿÿ€ÿÿ€ÿÿÿ‚ÿ‚ÿÿ€ÿ}ÿ}ÿ}ÿ~ÿ{ÿrÿiÿ\ÿTÿLÿIÿIÿKÿLÿOÿNÿLÿJÿHÿHÿIÿKÿQÿQÿOÿKÿIÿKÿTÿ_ÿoÿ†ÿ’ÿ’ÿ“ÿ“ÿ’ÿ’ÿÿŒÿˆÿÿ{ÿsÿmÿeÿ`ÿ\ÿYÿVÿVÿXÿ[ÿbÿeÿiÿpÿsÿvÿ{ÿ~ÿ€ÿÿÿÿÿÿÿÿ€ÿ€ÿÿ~ÿ~ÿ|ÿ{ÿ{ÿ|ÿ}ÿ}ÿ|ÿ|ÿ~ÿ~ÿ€ÿƒÿÿ|ÿwÿoÿdÿWÿFÿ=ÿ=ÿ=ÿ@ÿAÿAÿ?ÿ@ÿ@ÿAÿCÿFÿGÿFÿFÿGÿGÿGÿGÿFÿFÿFÿFÿFÿFÿEÿEÿEÿFÿCÿCÿ6~6u6~6u6~6u5~5u4}4u5}5u4}4u3}2u2}1u00u0~0u/~/t//t00t0~0t1~0t/~/s/~/s/~/t.~-u-~-v.}.v.~.w.~.x.~.x.~.x.~.y/~/y//x//y//z//z.~.{-~-{-~-z-~-z-~-z,~,z,+z++z++{++{**|,,|++|,,|,,|,,}-~-|.~.~.}.~/}/~/~//~//~/~.~.~.~.}-~,|-~.}.~.}-~-|-~-|.~.|.~.|.~.}.~.}-~-}-~/}.~.}.~/}/~/|/~/|/~/|/~/|/~/|.~.|/}.}.}.~-~,~,~,~,~,~+~+~)~)})~(}'~'}'~'}'~'}&~&{%~%{%~${$~#{#~#{#~"|"~!|!~ |!~!|~{ ~ {~{~|~~~ "$‚%~)…,}.‡2}7‰;}@‰C~H‹H}GŒF{A>y>ŽByCŽEzCŽBy:Ž,y'Œ%z%Š&z&‰'z'‰({(‰({)‰'{)ˆ({*‡)|3…e~l^`|_‚]}`‚j?€"‡/4ˆ:~<ˆ@|Bˆ@zCˆHwJ‰Ju?‹8u;ŒAu@Œ{<;{:7{52{1Œ1|1Š2|2‰4}5ˆ6}7‡7}9†7}5…5}3…4}4…2}2…1|3†3}3†0}/‡.},‡,},‡-}-‡-~-ˆ+~+ˆ*~*ˆ+~)ˆ)~)ˆ*~-‰6~<ˆD~J‡A~?‡:~5‡1~*‡'~&ˆ'}'ˆ'}%ˆ%}'ˆ'}&ˆ%}&ˆ'}'ˆ(|*ˆ*|*ˆ*~+‰+~+Š.~/‹.~.‹0~0Œ1~1Œ1~1Ž2~2Ž1~1Ž1~0Ž//Œ..Œ.€.Œ/€/Š/€/Š00‰..Š00Š--‰--‰--‰-€-‰-€-‹-0‹4€4Š5€5Œ32Ž201245676€6333‚3‹4ƒ7Š?ƒN…Z„e{o„vr|„€l„€i~„{hz„xiu…nlk…llo…uhx…{e|„|ez…zgy…xht†slq‡nqm‡lugˆ^~V‰N†J†JŒIƒIKM’LN’NJ“G‚G“HJ”LK’H€FG}K‘N{Zm|{‰}|…{…}„y†z‚vˆqƒjˆb…[‰U†M‰G‹D‡BŒE†GŠO†U…Y†^}_†dte†ipl†qpt…yox„yq}‚rr~}p|{p{{pz‚zn{‚{j|‚}g|ƒ|d{ƒ|d}‚c€ƒb|†tfk‡brU‰Iƒ@…AABŽCCD€EE€FF€FŽG€GG€G‹F€GŠHHŠGGŠFF‰FF‰FFŠGGŠ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ5ÿ5ÿ5ÿ4ÿ2ÿ2ÿ2ÿ1ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ1ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ)ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ$ÿ#ÿ#ÿ"ÿ#ÿ"ÿ"ÿ!ÿ!ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿÿÿÿÿÿÿ ÿ!ÿ#ÿ%ÿ(ÿ+ÿ/ÿ3ÿ8ÿ<ÿBÿEÿGÿIÿHÿFÿCÿAÿ>ÿ>ÿAÿBÿDÿBÿ?ÿ7ÿ)ÿ'ÿ%ÿ%ÿ&ÿ&ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ*ÿ*ÿ+ÿ'ÿ:ÿkÿiÿ^ÿ_ÿ_ÿ]ÿbÿoÿGÿ ÿ-ÿ2ÿ3ÿ8ÿ:ÿDÿGÿKÿFÿEÿHÿGÿ=ÿ>ÿQÿdÿ[ÿDÿOÿjÿlÿmÿgÿjÿiÿeÿgÿkÿmÿpÿqÿnÿiÿeÿfÿ`ÿ\ÿZÿWÿTÿQÿOÿMÿLÿLÿKÿGÿFÿ>ÿ7ÿ1ÿ1ÿ4ÿ3ÿ6ÿ3ÿ.ÿ-ÿ+ÿ)ÿ)ÿ(ÿ(ÿ)ÿ*ÿ+ÿ-ÿ1ÿ3ÿ3ÿ7ÿ9ÿ:ÿ7ÿ6ÿ6ÿ6ÿ7ÿ7ÿ:ÿ=ÿ>ÿ>ÿ>ÿ<ÿ<ÿ;ÿ9ÿ7ÿ5ÿ2ÿ1ÿ1ÿ1ÿ2ÿ3ÿ4ÿ3ÿ5ÿ5ÿ7ÿ9ÿ:ÿ7ÿ6ÿ7ÿ5ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ0ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ(ÿ+ÿ1ÿ9ÿ@ÿFÿCÿ>ÿ:ÿ5ÿ3ÿ.ÿ(ÿ'ÿ'ÿ'ÿ'ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ%ÿ%ÿ%ÿ&ÿ'ÿ)ÿ)ÿ)ÿ*ÿ)ÿ*ÿ,ÿ,ÿ-ÿ.ÿ.ÿ0ÿ1ÿ1ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ0ÿ/ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ1ÿ5ÿ6ÿ8ÿ8ÿ4ÿ3ÿ2ÿ0ÿ2ÿ3ÿ5ÿ6ÿ7ÿ7ÿ8ÿ8ÿ3ÿ3ÿ3ÿ3ÿ2ÿ4ÿ6ÿ?ÿJÿYÿcÿmÿuÿxÿ~ÿÿ€ÿ}ÿ{ÿyÿvÿrÿmÿjÿkÿoÿrÿvÿ{ÿ|ÿ|ÿzÿxÿwÿuÿsÿqÿnÿiÿiÿdÿaÿ\ÿXÿRÿPÿNÿKÿKÿKÿKÿJÿIÿFÿCÿCÿDÿFÿEÿCÿCÿBÿ@ÿAÿDÿOÿZÿ[ÿYÿYÿYÿYÿYÿVÿQÿNÿFÿAÿ=ÿ9ÿ8ÿ7ÿ7ÿ9ÿ?ÿEÿJÿRÿVÿ\ÿ_ÿ_ÿ_ÿaÿcÿeÿiÿmÿpÿqÿvÿzÿ}ÿÿÿ~ÿ|ÿ{ÿzÿzÿzÿzÿ{ÿ{ÿ{ÿ|ÿ|ÿ|ÿ{ÿ{ÿ|ÿ~ÿÿ€ÿÿyÿsÿkÿaÿWÿKÿCÿDÿEÿEÿEÿDÿDÿEÿEÿEÿEÿEÿEÿFÿFÿEÿFÿFÿFÿFÿFÿFÿFÿEÿEÿGÿFÿGÿEÿ6~6v5~4v5~5u6~6u4}4v3}3v3~3u2~2u1~0v2~2v1~1v0~0u0~/t/~/s/~/s/~/s0~0t0~.t.~.t.~.v.~.x-~.w/~/y.~.z-~-z-~-y/~.z.~.z.}.{.}.{.~.{-~-{-~-|,~,|-~-{-~-{--{,,{+~*z*~*z*~*{*~*{))z*+z,,{,,{-~-}-~-}+},}-}-}.}.~/}/~0}0.}..}..}..~.}.~.-~-|,~-|-~-|-~-|-~-}-~-}-~-}.~.}.~.}.~.}.~.}.~.}.~.|.~.|/~/|/~.|..}//}/.~--~-~-~-~-~+~+~+~+})~)|)~(|'~'|&~&|&~&{&~&{$${%%{$#|$$|#"|!!|  ||  |||} ~#$~(…+~-ˆ18‰<D‹GJ‹L~LJ|GBz?A{BC{EFzB=z2&z(Œ'z&‹&z&‰'{'‰'{'‰(z'‰)z)‡)|G‚og~`‚[{^‚a~b€jU~!….{:‹|=‘={=Ž;{9Œ7{5Œ4|3Š0}0‰2~2ˆ2}2‡5}6†89„97ƒ9~9ƒ5~4ƒ4}2„1}0…1}1†0}0†/}/†/}.†+~+„-~*†*~*ˆ*~*ˆ)~)ˆ(~)ˆ-~5ˆ;~@ˆC>‡;€7†3€/‡,€)‡%$ˆ&~&‰%}%‰%}$ˆ%}%‰%}%‰%~&‰'~'‰'~'‰(~)‰*}*Š,}-Š-}-‹.}/0}1Œ2}3Œ2~22~20~00~0.~0Œ/~/Œ/€.‹-€-Š.€.‰-€-‰,€,‰,€,‰,,‰**‰-€-ˆ/€1Š4€6‰8€5‹3€44€2Ž2€34€58€96€666521‚1Š3‚4ˆ=ƒI„V…`zi…rqv„}k€„f~…zgy…vjr…lmj†fkk„ojt„xf|ƒ{c}ƒ{fz„zfw„ugs„qjm„ine„_r[…XvT…Q{M„LJ„G…E„B‡@„?‹=ƒ@‹>ƒ><ƒ<Ž;ƒ;==Œ?„?Œ?…?Œ?†>Ž<‡<Ž9‡:Ž:†:8…9:…:‘9‚:‘ÿ>ÿ>ÿ=ÿ;ÿ9ÿ7ÿ5ÿ3ÿ2ÿ1ÿ1ÿ1ÿ3ÿ3ÿ3ÿ3ÿ3ÿ5ÿ8ÿ8ÿ8ÿ8ÿ7ÿ6ÿ4ÿ2ÿ1ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ.ÿ-ÿ,ÿ,ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ)ÿ)ÿ-ÿ6ÿ;ÿ@ÿ>ÿ:ÿ6ÿ4ÿ1ÿ.ÿ*ÿ&ÿ$ÿ&ÿ&ÿ$ÿ$ÿ%ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ)ÿ)ÿ+ÿ+ÿ,ÿ,ÿ.ÿ.ÿ/ÿ0ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ,ÿ+ÿ,ÿ,ÿ,ÿ-ÿ/ÿ1ÿ1ÿ3ÿ4ÿ4ÿ1ÿ2ÿ3ÿ4ÿ3ÿ4ÿ4ÿ5ÿ6ÿ8ÿ6ÿ6ÿ6ÿ6ÿ6ÿ4ÿ1ÿ1ÿ3ÿ4ÿ4ÿ<ÿFÿRÿ[ÿeÿoÿvÿ{ÿ}ÿ€ÿ}ÿyÿwÿtÿrÿkÿdÿgÿjÿlÿqÿuÿyÿ{ÿ{ÿ|ÿ|ÿ{ÿyÿxÿwÿtÿrÿoÿkÿgÿcÿ`ÿ\ÿWÿVÿTÿQÿNÿJÿFÿAÿ<ÿ:ÿ8ÿ8ÿ8ÿ8ÿ7ÿ6ÿ7ÿ7ÿ:ÿ:ÿ:ÿ:ÿ9ÿ;ÿ;ÿ;ÿ<ÿ=ÿ>ÿ>ÿ<ÿ9ÿ;ÿ;ÿ9ÿ9ÿ;ÿ?ÿEÿJÿOÿSÿ[ÿ^ÿ`ÿ_ÿ`ÿ_ÿ\ÿZÿYÿ[ÿ`ÿeÿjÿqÿvÿzÿ{ÿ{ÿyÿyÿxÿxÿzÿzÿ|ÿ{ÿyÿ{ÿ{ÿ|ÿ|ÿ|ÿ|ÿ}ÿ~ÿÿ}ÿyÿtÿmÿcÿ[ÿNÿEÿFÿFÿEÿDÿCÿDÿCÿBÿBÿBÿCÿCÿCÿCÿDÿEÿFÿGÿGÿIÿIÿHÿHÿFÿGÿFÿ5~4u2~2u4~3u2~2u3}2u2}2u1~1u1~1u0~0v0~0v/~/v/~/u/~/t/~/s.~.s.~.s.~.t/~/t.~.t-~-v-~-w+~+x,~-z-~,z+~-{,~,z,~,y,~,y-}+z*}*z*~+z+~+z,~,|+~+|,~,|,~,|++{++{+~+z*~*z)~){)~){)){)*{++|++|+~+}+~+},},},},}.}.~-}-~-}-.}..}..}..~.~.~.}-~-|-~-|-~-|-~-|-~-}-~-}-~-}-~,}-~-}-~-}-~-}.~.}.~.~/~/~-~-|.~.|.~0}-~-}-}-}-}-}-~-},~,}+~*}*~*}*~*|(~'{'~'|&~&|&~&{&~&{&&|%$|##}##}""|""|  |||||}!"~%‚(~)†-~/‰4;‹A€HŒMPPLŽI|FEzB@{BB{DJ|G8{*Œ*z'‰$z$Š&z(‰'{'‰'|&‰'|$†7~fmc{]‚_zcƒh{hƒl€Z&„+C‰K|JŒN|MŒO{TŠS{K‰IyNˆGx@‰9vNŠaqVŽZrWŽRv]‹d|i‰l}kˆk|qˆn|lˆn|pŠo{bŒ_yegyg‘ewb‘bw_WwSPwL‘HzC’BzA“C|B“?{:’3z0‘2z5’4z.‘,{,‘+{+‘+{+‘,{,‘+{**{+-z/3y57z8’6z5’2{11{36|88|;<{=Ž>{>Œ>{>Œ;|9Š5{5‰3|1ˆ2|3ˆ3|1†0~2‡4~4†2~5„7~7„5}4„2}2…0}0†1}1†2}3…4}3…1~/„-~-†*~*‡*~)‡)~)‡)~(‡(~(‡1~8‡;=†:€6…6€4…0€.…+€)ƒ%~%…%~%‡%~%‡%~%‡&~&‡&~&ˆ&~&ˆ&~&ˆ'~'ˆ(}(ˆ)}*ˆ*}*Š,},Œ-}.‹0}1‹0~0Œ0~0Œ1~1‹1~1‹1~/Œ0~0‹//Š/€/Š.€-‰,€,‰,€+‰*€*‰+€*‰+€+‰+€-ˆ.€0Š1€1‹1€1Œ1€23€3Ž3€22€34€5Ž5€5Ž66766‚6‹4‚4Š5ƒ5†9…A†N„Y‚b…kzq…xp~…j}…xiu…rjn†ime†fqg…jpm…qnv„yf{ƒ{c{„zcz„zcx…xdv…tep†lhi†ghf…cia…]mX…SrM…I{F„D€A„>„:„:ˆ6„7Š7„7Œ6„7Œ7„8Œ9…9Ž<„<Ž=„=Ž<„<=ƒ<:ƒ9‘8€8‘9<@FŒJ„P†U†Z|]†`r`†`m]‡[mUˆStT‡Z}]…dh‚p}v{y{ztzxpy‚yny‚{l{ƒ{j|ƒ}i|ƒ}h{ƒ{f|„}f}…|gx‡tmk‡bx[…O…EƒDŒE€DC€CBAAAŒCC‹CDŠEFŠFHŠHJŠJIŠHFŠFDŠ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ0ÿ0ÿ1ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ,ÿ,ÿ+ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ+ÿ+ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ.ÿ.ÿ-ÿ-ÿ,ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ*ÿ(ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ$ÿ$ÿ$ÿ$ÿ#ÿ"ÿ"ÿ ÿ ÿ ÿ ÿ ÿÿÿÿÿÿÿÿÿÿ ÿ"ÿ#ÿ&ÿ)ÿ)ÿ,ÿ.ÿ2ÿ8ÿ>ÿDÿGÿJÿKÿKÿIÿFÿEÿBÿCÿBÿCÿBÿFÿBÿ0ÿ(ÿ+ÿ'ÿ&ÿ'ÿ&ÿ'ÿ'ÿ'ÿ(ÿ*ÿ%ÿ@ÿpÿmÿbÿbÿ`ÿfÿfÿhÿoÿLÿ#ÿ*ÿ0ÿEÿQÿJÿGÿFÿ@ÿBÿLÿQÿFÿKÿJÿ?ÿ<ÿFÿTÿ[ÿdÿWÿVÿcÿiÿnÿoÿnÿlÿoÿpÿkÿlÿpÿeÿWÿ_ÿjÿjÿhÿiÿcÿaÿ_ÿXÿRÿRÿMÿIÿDÿAÿ>ÿ>ÿ=ÿ<ÿ9ÿ3ÿ/ÿ0ÿ1ÿ1ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ+ÿ,ÿ+ÿ,ÿ,ÿ/ÿ.ÿ3ÿ5ÿ6ÿ5ÿ5ÿ3ÿ1ÿ/ÿ0ÿ5ÿ7ÿ7ÿ8ÿ9ÿ;ÿ<ÿ>ÿ@ÿ@ÿ?ÿ=ÿ:ÿ7ÿ6ÿ3ÿ3ÿ4ÿ3ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ4ÿ4ÿ4ÿ5ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ3ÿ4ÿ3ÿ2ÿ0ÿ-ÿ-ÿ,ÿ*ÿ*ÿ)ÿ)ÿ)ÿ(ÿ&ÿ&ÿ'ÿ*ÿ3ÿ8ÿ:ÿ<ÿ8ÿ6ÿ5ÿ4ÿ3ÿ0ÿ,ÿ*ÿ(ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ(ÿ)ÿ*ÿ*ÿ,ÿ,ÿ-ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ-ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ+ÿ,ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ0ÿ1ÿ2ÿ2ÿ1ÿ2ÿ3ÿ3ÿ3ÿ5ÿ5ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ3ÿ3ÿ4ÿ4ÿ3ÿ7ÿ?ÿKÿTÿ]ÿeÿmÿtÿyÿ~ÿ|ÿvÿrÿpÿnÿlÿgÿbÿeÿgÿjÿoÿuÿxÿ{ÿ{ÿzÿzÿzÿzÿyÿyÿwÿtÿrÿnÿlÿkÿkÿiÿhÿdÿ`ÿ[ÿXÿUÿPÿMÿHÿFÿAÿ;ÿ8ÿ7ÿ7ÿ6ÿ6ÿ6ÿ7ÿ7ÿ7ÿ:ÿ:ÿ;ÿ;ÿ;ÿ;ÿ:ÿ:ÿ8ÿ8ÿ7ÿ7ÿ7ÿ9ÿ<ÿBÿGÿLÿQÿWÿ[ÿ]ÿ_ÿ_ÿ]ÿ[ÿWÿQÿMÿMÿTÿYÿ`ÿgÿpÿtÿwÿzÿzÿwÿwÿxÿyÿyÿzÿzÿyÿ{ÿ|ÿ|ÿ|ÿ{ÿzÿyÿ{ÿ}ÿ}ÿxÿrÿiÿbÿYÿNÿEÿDÿCÿCÿCÿAÿBÿAÿAÿCÿCÿDÿEÿFÿFÿJÿKÿJÿJÿJÿIÿGÿFÿEÿDÿ1}1t3}2t2}2v2}2u1}1t1}1t1~/t.~/t/~.v.~.v.~.v.~.u.~-s-~-s-~-r-~-r,~.s,~,u+~+v*~,v+~+w+~,y+~+z+~*{*~*|*~*|+~+|*~*|+~+}+~+}**},,}*)}**}**}++}**|**|(*|**|**}*+}*~)}*~*}*~*}+~+},~,~,~,~,~,~+~+~-~--~--~-~.~.~.~.~.~.~-~-~.~.~-~-}-~-}-~-}-~-}-~-{-~-{-~-|,~,|-~-}-~-}-~-}.~.}.~.}.~.}.~-~.~.~-~,}-~-}-~-}-~-|-~-},~,~+~+~+~+~)~)}(~(|'~'}&~&|&~&|&~&|&&|%$|$${##|#~"|"~"| ~ |~|}|}|~{~~ ~#$}'…*},ˆ-~.Š26Œ;€@B€FIJŽI}GG|DA{AA{@?~8-}*‹,|(Š'{%‰'z'‰&{(ˆ'{'‡<~pk‚dydƒewgƒjylƒh~=ˆ+/Š3|?ŠZzLŒCyFŒGz<ŠPzRˆNyKˆIwGˆFvLŠQt_fq`Xw_Œg|m‰o~o‡n|p‡q|oˆpzo‰fx_‹ewjhygŽiwffwa]uX’RwN“GxB“=y;”:{;”:{7’4z1‘1z0’-y,‘+{+‘*{*‘){*,{+-{-,{*,{./z01z34{54z20z03{75{89}::}<>}AŒ@~?‹<|:ˆ8{6‡9}7ˆ5}3‡1}/†/}.‡/}/‡1}2…2}4†4}4…4}3…3}3…5~5„4~3„1~/….~-…,~,†*~)†(~*‡)~'‡%~'†'~-†24†9€9„8€5„6‚7‚20‚-+„'%‡%€%‡%$‡$$ˆ$~$‡%~%‡%~%‡&~$‡%}%‡'}(ˆ*}*‰,},Š.}.‹.}.‹.}.‰.}.‹-}-‹-}/‹0~0Œ00‹.~.Š0~.‰.-‰,,‰++‰++‰+~+‰*~*‰++Š,,Š,,Š--Š--‹0111123€3Ž4€4Ž44333‚3‹2‚2‰3ƒ2ˆ1„2ˆ6…:‡B…N‡W‡b}j‡qqx…{fz…ufr…mgm…kof†brb‡eok…qktƒvfz…xfy…zcy…xbx†vdt‡tfo†ldm‡mam‡lcj‡ieg‡ghb†^oZ†VwQ†LE†?…9„8‰6…6‹5„67„77ƒ78ƒ9:„:Ž9„98‚8‘66’5€6’9€>’BFM‚S‚X„\x^†^p^‡]nYˆUrNˆIzH†JƒS…[„cƒkq€wyz€{qx‚ulv‚vkwƒxnwƒwny‚zl{‚}i}ƒzg{„{g{…zhv†pni‡`xX…P‚FƒDŠC€BŽ@@ABCDŒDD‹GH‰HJ‰L~K‰J~H‰G~E‰C~A‰1ÿ1ÿ1ÿ0ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ.ÿ/ÿ/ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ+ÿ)ÿ*ÿ+ÿ+ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ+ÿ+ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ)ÿ(ÿ(ÿ'ÿ'ÿ(ÿ*ÿ*ÿ)ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ.ÿ,ÿ-ÿ.ÿ-ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ$ÿ$ÿ#ÿ"ÿ"ÿ"ÿ ÿ ÿÿÿÿÿÿÿÿÿÿÿ!ÿ$ÿ%ÿ)ÿ*ÿ,ÿ-ÿ.ÿ1ÿ6ÿ;ÿ?ÿCÿEÿGÿJÿJÿIÿHÿEÿBÿBÿAÿ@ÿ9ÿ.ÿ*ÿ+ÿ+ÿ(ÿ'ÿ(ÿ)ÿ(ÿ(ÿ(ÿ*ÿ>ÿkÿmÿfÿcÿfÿkÿmÿoÿgÿ5ÿ&ÿ.ÿ1ÿ0ÿ=ÿXÿXÿKÿNÿKÿJÿLÿMÿRÿSÿNÿDÿFÿFÿPÿQÿhÿiÿeÿaÿfÿlÿtÿoÿlÿkÿnÿnÿkÿnÿlÿ`ÿbÿdÿfÿfÿeÿfÿgÿhÿfÿ_ÿZÿVÿRÿJÿBÿ=ÿ;ÿ:ÿ8ÿ6ÿ6ÿ:ÿ;ÿ8ÿ3ÿ/ÿ-ÿ,ÿ*ÿ(ÿ(ÿ)ÿ*ÿ*ÿ,ÿ-ÿ.ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ1ÿ2ÿ4ÿ6ÿ4ÿ2ÿ2ÿ2ÿ3ÿ5ÿ6ÿ8ÿ9ÿ9ÿ9ÿ<ÿ?ÿ?ÿ>ÿ=ÿ>ÿ<ÿ:ÿ;ÿ9ÿ7ÿ5ÿ3ÿ2ÿ2ÿ1ÿ/ÿ0ÿ1ÿ3ÿ2ÿ3ÿ3ÿ4ÿ4ÿ3ÿ3ÿ3ÿ4ÿ4ÿ3ÿ2ÿ1ÿ/ÿ.ÿ-ÿ,ÿ,ÿ+ÿ*ÿ)ÿ)ÿ)ÿ(ÿ'ÿ&ÿ&ÿ)ÿ,ÿ1ÿ5ÿ:ÿ:ÿ9ÿ9ÿ7ÿ6ÿ6ÿ1ÿ/ÿ,ÿ'ÿ$ÿ%ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ&ÿ&ÿ'ÿ(ÿ*ÿ*ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ0ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ2ÿ2ÿ1ÿ0ÿ/ÿ0ÿ2ÿ3ÿ5ÿ>ÿJÿVÿ^ÿgÿoÿtÿyÿyÿvÿrÿpÿmÿlÿhÿcÿaÿeÿjÿlÿpÿtÿwÿxÿyÿyÿxÿvÿsÿqÿoÿmÿjÿiÿiÿjÿjÿkÿjÿmÿmÿkÿiÿeÿ`ÿZÿSÿKÿDÿ<ÿ8ÿ7ÿ6ÿ6ÿ6ÿ6ÿ6ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ7ÿ6ÿ8ÿ>ÿDÿIÿNÿTÿZÿ\ÿ\ÿ^ÿ^ÿ]ÿUÿQÿJÿFÿBÿGÿOÿXÿaÿiÿoÿtÿxÿxÿxÿvÿtÿtÿuÿvÿvÿuÿuÿyÿ{ÿ{ÿzÿxÿyÿyÿzÿxÿvÿqÿiÿbÿYÿPÿEÿCÿAÿBÿBÿCÿDÿDÿEÿGÿHÿIÿJÿIÿKÿKÿJÿJÿHÿFÿCÿAÿBÿ0}0w0}0w0}0v/}/u0}/v/}/v/~/v0~.v-~-v-~-v+~,u,~,v,~*t,~,t,~,s,~,s,~+t*~*v)~)w*~)w(~)w)~)y)~)z)~*{)~)|*~)|*~*}*~*}+~+~*~*~))}))}((}'(}((}('}((}))})(|((|((}((})~)}*~*}*~*}*~*}*~*~+~+~+~+~+~+~,~,,~,,~,~,~,~,~,~-~-~-~-~-~-~.~.}-~-},~,}-~-}-~-|-~-|,~,|+~+|+~+}*~*}+~+}+~+}+~+}+~+}+~+~+~-~-~-€-~-€,~,}+~*|,~,{+~,},~,~*~*~*~*|(~(|''|''{'%{''{&&z&&z%%{$#|$~#|"~"| ~ |~|||~{~ ~"~%ƒ(})†,}+‰.}1‹4~7Ž>BŽEFŽH~IK}JI|HD{DC{?0{*Œ)z*Š(|&‰(|*‰&{)ˆ(|*†)dpƒgye„exj„j{oW*€(†0|/Œ4zDŽV{\L{K‹HzIŒLyK‹ByI‰RyKˆEx?‰IvY‹_qYŽXp[`vjp|sŠk}jˆj{eŠlyn‹lvg‹_vaŒewgcy_Žaxfhxe‘_u]“YuU”NuG•Bx=–:z8”7z9’=zD?z51y-+z)(z(({(+{,-{-/{/.{.-z-Ž.z/Ž/{3Ž3{4Ž5{5Ž2{1Ž1{3Ž6{77{9;|<Œ<}<‹?|?Š?{?‰>|>‰<|9ˆ8|6ˆ6|5‰3|3ˆ3|3‡4}4†4}4…5~4…2~3…0~0†0~0†0~.†-~.†.~-†,~+†*~)†)~)†(~'†&~'†(,†2€7„:€:„;‚;‚9‚8‚4‚1.‚*ƒ$#„##†##‡"~"‡"~"‡#~#‡$~%‡$}&‡(})ˆ*},ˆ,},ˆ,}-‰.}.‰.}.‰-}-‹,},‹,}-‹-~-‹.~.Š/~.Š/~/‰.~.‰.~.‰-~-‰,~,‰,,‰++‰,~,‰,~,‰,,‰,,‰--Š--‹..Œ//Œ0€10€00000/‚/Œ1‚1‹/.Š/ƒ/Š/ƒ/Š1ƒ5Š;ƒF‡O„\€c…kur…yh{…wet…phm†imh‡eqa‡atg†jqo…rku…wfwƒtaq…q`n†kdi‡hifˆdkdˆdjhˆjfiˆjdlˆjeg‰dj_‰YrQˆH~>‡9†8…7‰6ƒ6‹5ƒ5‹6‚6Œ7‚7Œ5ƒ5Ž4ƒ4445€5’5€5‘5€6‘9€?EL‡QƒWY„[w^…_p]‡ZpS‰MwI‡C€@…I…PƒX…`‚gn‚txy‚ynw‚ukrƒrlsƒtprƒrqrƒupy‚{kyƒyhy„zh|…xjv†pmj…du\…SƒJ‚AŠA€CŒDFEHŒJJ‹JJ‰II‰KJ‰IF‰FC‰A?‰/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ/ÿ.ÿ/ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ,ÿ,ÿ,ÿ,ÿ*ÿ,ÿ,ÿ+ÿ+ÿ)ÿ)ÿ(ÿ*ÿ)ÿ)ÿ(ÿ(ÿ)ÿ(ÿ'ÿ(ÿ(ÿ(ÿ'ÿ(ÿ)ÿ)ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ'ÿ(ÿ(ÿ(ÿ(ÿ&ÿ'ÿ'ÿ(ÿ(ÿ'ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ+ÿ,ÿ,ÿ+ÿ*ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ$ÿ#ÿ$ÿ#ÿ"ÿ"ÿ ÿ ÿÿÿÿÿÿÿÿÿÿ ÿ"ÿ&ÿ)ÿ*ÿ,ÿ.ÿ1ÿ4ÿ8ÿ>ÿCÿEÿFÿIÿJÿKÿKÿJÿJÿIÿFÿFÿFÿ9ÿ+ÿ+ÿ*ÿ*ÿ)ÿ'ÿ&ÿ&ÿ'ÿ(ÿ(ÿ#ÿTÿoÿjÿiÿjÿnÿpÿoÿOÿ,ÿ4ÿ3ÿ.ÿ-ÿ4ÿCÿJÿNÿOÿQÿMÿSÿSÿSÿOÿLÿKÿDÿBÿGÿGÿ[ÿ^ÿUÿKÿ\ÿbÿiÿqÿoÿmÿmÿjÿgÿmÿoÿmÿiÿfÿdÿbÿaÿ`ÿ`ÿ_ÿcÿfÿbÿ^ÿZÿYÿVÿPÿKÿDÿ?ÿ<ÿ<ÿ=ÿ<ÿ<ÿBÿ=ÿ5ÿ3ÿ/ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ+ÿ,ÿ/ÿ/ÿ.ÿ/ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ/ÿ2ÿ4ÿ6ÿ6ÿ3ÿ2ÿ1ÿ1ÿ3ÿ5ÿ6ÿ8ÿ9ÿ;ÿ;ÿ=ÿ?ÿ?ÿ@ÿ@ÿ@ÿ@ÿ?ÿ?ÿ=ÿ<ÿ;ÿ:ÿ9ÿ9ÿ8ÿ8ÿ8ÿ8ÿ7ÿ5ÿ5ÿ4ÿ1ÿ0ÿ.ÿ.ÿ.ÿ.ÿ,ÿ.ÿ-ÿ.ÿ.ÿ-ÿ,ÿ+ÿ*ÿ)ÿ)ÿ)ÿ(ÿ'ÿ'ÿ&ÿ'ÿ(ÿ-ÿ1ÿ4ÿ7ÿ:ÿ;ÿ9ÿ7ÿ6ÿ3ÿ0ÿ,ÿ(ÿ$ÿ#ÿ#ÿ#ÿ#ÿ"ÿ"ÿ"ÿ"ÿ#ÿ#ÿ#ÿ$ÿ%ÿ'ÿ&ÿ'ÿ(ÿ*ÿ,ÿ,ÿ,ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ.ÿ.ÿ0ÿ0ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ1ÿ2ÿ8ÿDÿNÿXÿ`ÿhÿpÿuÿzÿyÿwÿoÿlÿjÿgÿeÿbÿ`ÿaÿgÿnÿqÿsÿsÿuÿqÿoÿmÿiÿeÿdÿbÿ`ÿ[ÿ\ÿ^ÿ`ÿcÿfÿhÿiÿhÿdÿ_ÿYÿPÿGÿ>ÿ9ÿ8ÿ8ÿ6ÿ6ÿ5ÿ5ÿ6ÿ6ÿ7ÿ7ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ4ÿ4ÿ5ÿ6ÿ8ÿ>ÿCÿJÿNÿTÿZÿ\ÿ]ÿ_ÿ]ÿ[ÿWÿPÿJÿEÿDÿDÿJÿQÿZÿbÿhÿpÿsÿwÿxÿvÿtÿrÿpÿqÿqÿqÿrÿrÿuÿzÿ{ÿzÿyÿyÿzÿ{ÿzÿwÿsÿoÿgÿ_ÿXÿMÿFÿEÿCÿGÿHÿIÿJÿJÿJÿJÿIÿIÿIÿIÿIÿFÿDÿAÿ=ÿ<ÿ.}.w.}.w-}-x.}.x.}-w-},w-~.w-~-w,,w))w*~*v*~*v+~(v(~(v(~(u(~(u''t''u''v''w''x'(x((z''{''}((~(())*~*~(~(|(~(})~)}(~(}(~(}'~&|&~&|&&}&&}&€&|&€&|'€'}&€'}))}**}**}++})~)~)~)~)~)~+~+~+~+~+~+~+~,,~*+~+~,~-~,~,~,~,~+~*}+~+}*~,|,~+|,~,},~,}*~*}*~*}+~+|+~+}*~*~+~+~+~+~+~+~*~*~*~*~*~*~+~*~+~,}+~+}+~+|+~+|*~*}*~*|*~*})~)|)){('{&~'{(~(z''y%%x&&y%%z$~$z!~!{ ~ } ~ }||~}~ €$~(„+~,ˆ/|1‹3~7;AŽGLŽMNL~J‘K|JL|KJ|JC{.&{)'{%‹'{'‰'{(‰(z'‡&}M‚qhzcƒiwo…p{iƒ?‚"3‡0}0Œ/|1Ž4}=ŽGRŒZ^ŒX|V‹X{YŠUzMˆIxB‰6w<‹HsbŽcpZYohqtmŽrxrŠq{m‰nzm‰iyi‰jvkŠgvd‹av_Žbw`_w^‘]x\’UyS”UyS•QxM—IwC˜@x@•Cx?“8y:‘9z56z1(y&'{'Ž({(Ž({)Ž){*.{/0{0.{,+|+Ž+|,Œ,}-0}31}01|/Œ0|2Ž5|7Ž8}8:};=}??}?@}@‹A}A‹B|>Š@|@Š@}?‰>}>‰=};ˆ;}8‡5~4†2~/†-~,…-~-…-~,‡-~.‡,~,†+~*†*~)†)~)†(~'†&~&†&%‡&+‡/€2…46„8ƒ8‚4‚4~1‚.€,'‚"€#„##†#~#‡#~#‡$~$ˆ$|$ˆ%}&‡'}(ˆ+|+ˆ,},ˆ-}-‰.}.‰,~,Š+~+Š,},‹-}-‹.}.‰.}.‰.}.‰/}/‰/|/Š.|.Š,~,‰,~,‰-~-ˆ-~-ˆ-~-ˆ.~.ˆ-}-ˆ-}-ˆ--‰--Š..Š//Š00‹00‹1€1‹/€/‹.€.Š/€/Š.€.Š-€-Š.‚.Š.‚.Š0‚1‹7‚?‰IƒS‚\…ewm…shw†wdv†pfj‡hkh‡dq_‡^s^†fql…nlp„pfp„nel„gib†am^ˆ[sW‰TvS‰SuX‰[r`‰ena‰_qZ‹WvMŠE~=†9‡8…6Š6ƒ6Œ5‚5Œ6€6Œ7€7Œ5€5Ž6€65€5‘66‘6~6‘6~69~<ŽAG‰M‚S‚W…Z|\†_v]ˆ\qY‰StN‡H{F…EƒHƒM†T‚]†e‚k~p‚utw‚xmxƒvksƒppm‚nto‚pyq‚stxƒ{mzƒyiy„yi{„zjw…tlq…jtc„[~U‚M‡IHŠIH‹I~J‹I~GŠIG‰H€GŠF€CŠ@€=Š;€9Š.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ)ÿ(ÿ(ÿ(ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ&ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ+ÿ*ÿ*ÿ+ÿ,ÿ-ÿ,ÿ,ÿ+ÿ,ÿ*ÿ*ÿ+ÿ,ÿ,ÿ+ÿ*ÿ*ÿ,ÿ,ÿ,ÿ,ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ*ÿ*ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ"ÿ"ÿ ÿ ÿ ÿ ÿÿÿÿÿÿÿÿ"ÿ%ÿ)ÿ,ÿ/ÿ1ÿ4ÿ7ÿ;ÿ=ÿBÿHÿNÿRÿQÿOÿMÿLÿKÿKÿMÿLÿHÿ7ÿ*ÿ&ÿ&ÿ(ÿ'ÿ&ÿ'ÿ&ÿ&ÿ(ÿ$ÿNÿtÿiÿeÿjÿjÿlÿeÿ2ÿ#ÿ1ÿ2ÿ0ÿ.ÿ3ÿ0ÿ6ÿEÿIÿOÿWÿYÿVÿVÿVÿWÿVÿMÿDÿ;ÿ0ÿ8ÿFÿ^ÿRÿUÿ[ÿaÿkÿjÿrÿuÿqÿmÿpÿrÿnÿjÿiÿgÿgÿdÿbÿ`ÿ]ÿ^ÿ^ÿYÿUÿTÿNÿMÿPÿRÿNÿLÿMÿFÿ?ÿ?ÿAÿ<ÿ7ÿ6ÿ3ÿ5ÿ5ÿ1ÿ+ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ)ÿ*ÿ-ÿ.ÿ/ÿ/ÿ.ÿ-ÿ,ÿ,ÿ+ÿ+ÿ*ÿ+ÿ,ÿ-ÿ.ÿ0ÿ0ÿ/ÿ0ÿ0ÿ3ÿ5ÿ7ÿ7ÿ8ÿ:ÿ:ÿ;ÿ<ÿ>ÿ@ÿ@ÿ@ÿ@ÿAÿAÿBÿBÿBÿAÿAÿAÿ?ÿ=ÿ;ÿ8ÿ7ÿ5ÿ3ÿ1ÿ/ÿ/ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ'ÿ'ÿ&ÿ&ÿ&ÿ%ÿ$ÿ&ÿ(ÿ+ÿ.ÿ0ÿ2ÿ5ÿ4ÿ4ÿ2ÿ/ÿ-ÿ)ÿ%ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ%ÿ%ÿ%ÿ&ÿ'ÿ(ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ.ÿ-ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ/ÿ0ÿ0ÿ0ÿ0ÿ/ÿ.ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ0ÿ1ÿ2ÿ2ÿ;ÿEÿPÿ[ÿaÿiÿnÿvÿyÿuÿpÿmÿjÿiÿeÿ_ÿZÿ]ÿbÿhÿkÿnÿqÿrÿpÿlÿfÿbÿ^ÿYÿUÿQÿNÿLÿOÿOÿSÿXÿYÿWÿUÿPÿJÿBÿ9ÿ7ÿ8ÿ6ÿ6ÿ6ÿ6ÿ6ÿ7ÿ7ÿ8ÿ8ÿ7ÿ7ÿ7ÿ7ÿ8ÿ8ÿ9ÿ9ÿ9ÿ9ÿ:ÿ:ÿ:ÿ;ÿAÿFÿKÿQÿVÿYÿ[ÿ\ÿ]ÿ^ÿZÿUÿQÿKÿGÿFÿHÿMÿRÿXÿaÿhÿlÿpÿuÿwÿyÿyÿwÿrÿmÿmÿoÿoÿnÿoÿrÿvÿ{ÿzÿzÿzÿ{ÿ|ÿ|ÿyÿxÿtÿoÿlÿcÿ\ÿRÿKÿIÿIÿIÿIÿHÿHÿGÿEÿEÿDÿCÿ?ÿ=ÿ;ÿ7ÿ6ÿ.}.x.}.x,}+x-},x,},w-}-w,~,w,~,w+}*w*}*w*~(v'~'v(~&w&~&w&~&w'~'w''v&&w''y''z''{&&{%%{''|''}((~(())(~()~)~(~(~(~'~'~'}'~'}%~%|%~%|%%}%%}%%}%%}&~&}&~&}&&}''}('}''}'~(~)~)~)~)~*~)~*~+~+~+~*~++~++~++~,+~+~+~+~*~*}*~*})~)~+~+~+~*}+~+}*~*}*~*}*~*|*~*}*~*)~))~)~*~*~*~*~*~*~*~*~*~*~*~*}*~*}+~+~+~+~*~*}*~*|*~*|)~){))z)(z(~(z((y((x'%w%%x%%y$~$z"~"{!~!| ~ |||~} ~$€&~)…-~0‰3~7‹;>Œ@€CH€MŽP€QO~N‘N|LL~MK}?Ž-{)‹({(Š'{*Š({'Š&|%‡&}Tvjye‚fxj‚dzg€C#~.ˆ2|1Œ0|.Œ-~/9€HORŒXYŒY^‹d|Z‹TyMŠGw>Š1u<‹HqZŽZnTWn^‘hrjmvq‹qys‰wyv‰qyi‰avaŠfvd_vZYu^‘_wV“PyN’I{H”J{N•M{M–MyI—DwB—@w<”8y5“4z7’7z4‘-y(%{&Ž'{(Ž){)Ž){),{/.{.-{..|.Ž+|)Œ(}(Œ'})Œ)}*Œ-|-‹.|0‹1|4Œ5{66{89{9;{<=|>Ž?|?ŽA}=Œ>}?Œ@~@‹B~B‹A}=Š:}:‰8~6‡6~4‡2~1‡0~/‡/~-‡,~+‡*~*†+~*†)~)†(~(†'~&†%~%†$#‡$$‡$%†)€,….€1ƒ2‚3€4‚/~.+€&€#‚"$…#~#†$~#‡%%ˆ&~&ˆ&}'‡'}(ˆ*|*ˆ*}*ˆ+}+‰+}+‰,|,‰,|,‰-}-‰-}-‰.}-‰.}.‰.}.‰.}.‰/~/ˆ.~-ˆ,~,‰,~+‰-~-ˆ-~-ˆ-~-ˆ-~-ˆ-.ˆ--ˆ--ˆ--‰-.ˆ//ˆ/0‰00‰1€1‰0€0‰1€1Š0€0Š/€.Š0€0Š//Š00Š23Š01‹49ŠA‚J†V„]zf…knr…xgw†qep‡jfe‡cn]‡ZvZ†^xc„gsl„mmo„nhl…gjb†[oV‡SvP‡IG‡GƒHˆJ…MˆM…K‡F‡C…=‰8‚8‹:9Ž8€877888888‘88’:<’<}<’;~;‘;~;<~;?CŒHM‡R„XZ…Zw\†^p[†WpRˆJxI†G€I„L„QƒUƒZƒag„myq„trxƒylxƒvlr‚moo‚nvp‚qzr‚swv‚{p{ƒ{j{ƒ|h}„€g€…{gx…slo…hva„VN‚I…HG‡GD‰DC‰D€AŠ?€;Š:€7Š5€2Š,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ*ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ(ÿ(ÿ*ÿ)ÿ)ÿ)ÿ&ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ'ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ)ÿ)ÿ)ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ*ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ)ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ&ÿ%ÿ$ÿ$ÿ"ÿ"ÿ!ÿ!ÿ ÿ ÿÿÿÿÿÿÿ ÿ$ÿ(ÿ*ÿ-ÿ0ÿ3ÿ7ÿ?ÿCÿDÿEÿHÿIÿJÿLÿMÿOÿMÿMÿLÿJÿBÿ2ÿ'ÿ'ÿ'ÿ'ÿ(ÿ*ÿ)ÿ%ÿ%ÿ$ÿSÿtÿkÿaÿfÿhÿiÿiÿMÿ&ÿ.ÿ1ÿ0ÿ.ÿ.ÿ-ÿ+ÿ/ÿ>ÿMÿWÿPÿUÿVÿYÿ_ÿ_ÿ]ÿ[ÿTÿHÿ>ÿ7ÿCÿQÿ^ÿ^ÿUÿWÿcÿ_ÿ[ÿhÿtÿsÿtÿwÿyÿzÿqÿfÿfÿkÿiÿ`ÿ[ÿZÿ[ÿ\ÿZÿRÿKÿIÿHÿJÿJÿJÿKÿJÿJÿFÿDÿAÿ<ÿ8ÿ7ÿ5ÿ6ÿ5ÿ4ÿ-ÿ(ÿ%ÿ&ÿ'ÿ(ÿ)ÿ)ÿ)ÿ)ÿ+ÿ-ÿ,ÿ,ÿ-ÿ.ÿ.ÿ.ÿ.ÿ*ÿ'ÿ'ÿ&ÿ$ÿ%ÿ(ÿ*ÿ+ÿ-ÿ/ÿ2ÿ5ÿ5ÿ6ÿ8ÿ9ÿ8ÿ8ÿ;ÿ<ÿ<ÿ<ÿ=ÿ=ÿ=ÿ;ÿ:ÿ:ÿ;ÿ>ÿ>ÿ>ÿ<ÿ9ÿ:ÿ:ÿ:ÿ:ÿ8ÿ7ÿ3ÿ2ÿ1ÿ0ÿ0ÿ.ÿ,ÿ+ÿ*ÿ*ÿ*ÿ+ÿ)ÿ)ÿ(ÿ(ÿ&ÿ&ÿ%ÿ%ÿ#ÿ#ÿ$ÿ$ÿ#ÿ#ÿ%ÿ&ÿ*ÿ,ÿ.ÿ1ÿ1ÿ/ÿ.ÿ-ÿ*ÿ%ÿ%ÿ$ÿ#ÿ#ÿ$ÿ$ÿ%ÿ%ÿ&ÿ&ÿ&ÿ'ÿ'ÿ(ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ-ÿ,ÿ,ÿ,ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ1ÿ1ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ2ÿ3ÿ2ÿ3ÿ6ÿ>ÿGÿQÿYÿaÿjÿpÿuÿxÿrÿnÿhÿcÿ^ÿ[ÿVÿYÿ^ÿ`ÿeÿkÿmÿnÿnÿnÿhÿcÿ\ÿVÿSÿNÿHÿDÿAÿ@ÿCÿCÿBÿ@ÿ=ÿ=ÿ:ÿ;ÿ<ÿ;ÿ:ÿ:ÿ:ÿ:ÿ;ÿ;ÿ;ÿ;ÿ;ÿ;ÿ;ÿ;ÿ<ÿ=ÿ=ÿ=ÿ=ÿ=ÿ=ÿ=ÿ>ÿ=ÿ=ÿBÿDÿIÿPÿSÿWÿXÿZÿ\ÿYÿWÿTÿNÿJÿKÿJÿJÿNÿSÿWÿ\ÿdÿgÿkÿqÿtÿxÿyÿxÿvÿrÿoÿmÿqÿrÿrÿrÿtÿyÿ|ÿ{ÿ{ÿ|ÿ€ÿÿƒÿÿ~ÿ}ÿwÿqÿlÿaÿUÿLÿIÿHÿEÿBÿCÿBÿ?ÿ=ÿ;ÿ9ÿ7ÿ6ÿ3ÿ3ÿ+}+x,},x,},w,}+w*}*w*}*u)~)w)~)w(~(w(~&w&~&w&~&w&~%x%~%x%~%x%~%x%%v%%w%%z%%{%%|%%~%%}&&~''~&&~&~'(~((((((}('}'&~&~%~%~%~#}%~%}%%~%%~%~%}%~%|%~%|&~&|&~&}%~&}&~&~&~&~'~)~)~(~'~'*~()~)(~))~)~)~)~)~)~*|*~*|*~+}+~*~)~)~)~)~)~)~)~(~(~(~(~)~))~)}(~(~)~)~)~(~(~(~)~)~)~)})~)}(~(}(~(})~)})})})})})~)})~)|)){**{)){)){)~)y)(y(€(y)€)x(€(x'€'x((w&$y$~%z$~#z"!{!!{||~ ~%‚'~*†/~2‰7:‹@€EŒGFGEF€IKN‘NM‘J}D‘7|,*{(Œ(z(‹){*Š+z$ˆ#}A‚{mx_^ue€dzj_%}+‡1{0Œ3|8Œ/|+Œ+~-‹8DL‚OŽR‚SŽU€Z[}^‹]yQŠJxB‰=wHˆRtWŠYrQRpZRqTburŽsyt‹xy}ˆ|xwˆuws‰ouhŒ`u\YwV‘UyTP{H‘H}I“H}G”G|E•F|J•GzD•Ay>•9y7•6y5“4z3“.x('z''z((z('|)Ž*|+Ž+{++|-/}/Ž.},Œ*}'‹%}"‹$|'Š(|+Š.|0‹4|67{76{67{9Œ={<ŽŒ>€?>>=~>>~??~?<~<=~>‘?}?’?}?“@~@“?~?’?~?‘>~>>=Ž<>ŒBH‰L‚P„TƒVwX…YpZ†WpUˆPvL†K~H…I‚N„R‚U„Y€_„d}h„nwrƒvryzqy‚vpr‚quq‚qyr‚s{s‚vxy~q€‚€j„‚gƒ„…g„…‚e~…yhr‡iq_…Q~G„F…C‚?‰=€=‰;€9‰6€4‰4€3‹3€0‹+ÿ+ÿ+ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ%ÿ&ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ)ÿ(ÿ(ÿ(ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ%ÿ%ÿ%ÿ$ÿ#ÿ#ÿ#ÿ!ÿ!ÿÿÿÿÿÿÿ ÿ%ÿ)ÿ,ÿ/ÿ4ÿ:ÿ=ÿBÿGÿIÿIÿHÿFÿEÿGÿJÿNÿOÿNÿNÿGÿ>ÿ3ÿ)ÿ)ÿ)ÿ)ÿ*ÿ)ÿ(ÿ(ÿ-ÿqÿwÿiÿ`ÿdÿiÿjÿbÿ5ÿ%ÿ.ÿ1ÿ1ÿ2ÿ2ÿ-ÿ*ÿ*ÿ,ÿ5ÿBÿLÿNÿNÿSÿWÿ[ÿ]ÿ\ÿZÿNÿFÿ<ÿ8ÿHÿVÿYÿ[ÿ[ÿSÿ`ÿdÿYÿ[ÿbÿmÿpÿtÿsÿwÿwÿwÿuÿnÿjÿaÿZÿTÿQÿPÿPÿLÿFÿGÿHÿIÿFÿDÿDÿEÿIÿJÿHÿCÿBÿ?ÿ;ÿ9ÿ8ÿ8ÿ5ÿ1ÿ+ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ*ÿ)ÿ'ÿ)ÿ*ÿ+ÿ+ÿ,ÿ.ÿ.ÿ.ÿ,ÿ-ÿ,ÿ(ÿ%ÿ$ÿ$ÿ'ÿ*ÿ.ÿ2ÿ6ÿ8ÿ8ÿ7ÿ6ÿ6ÿ8ÿ:ÿ;ÿ=ÿ<ÿ<ÿ<ÿ;ÿ:ÿ9ÿ6ÿ6ÿ4ÿ6ÿ6ÿ6ÿ5ÿ7ÿ7ÿ;ÿ9ÿ8ÿ9ÿ7ÿ5ÿ2ÿ1ÿ/ÿ/ÿ.ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ*ÿ*ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ&ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ$ÿ%ÿ(ÿ*ÿ.ÿ0ÿ1ÿ/ÿ/ÿ0ÿ-ÿ)ÿ(ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ*ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ1ÿ1ÿ2ÿ2ÿ1ÿ2ÿ3ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ5ÿ5ÿ7ÿ7ÿ7ÿ8ÿ7ÿ7ÿ7ÿ7ÿ8ÿ;ÿ@ÿIÿQÿYÿdÿhÿpÿtÿtÿmÿhÿaÿ[ÿZÿSÿRÿVÿ]ÿ`ÿdÿkÿmÿmÿlÿiÿdÿ^ÿXÿUÿOÿLÿHÿCÿ?ÿ@ÿ@ÿ@ÿAÿAÿAÿ@ÿ@ÿ@ÿAÿAÿAÿ@ÿ@ÿ?ÿ@ÿAÿAÿAÿAÿ@ÿ@ÿ?ÿ?ÿ=ÿ=ÿ>ÿ>ÿ<ÿ;ÿ;ÿ<ÿ?ÿBÿGÿMÿSÿUÿVÿXÿYÿYÿVÿRÿMÿLÿJÿLÿNÿRÿTÿXÿ\ÿaÿfÿkÿqÿsÿvÿwÿzÿyÿwÿtÿrÿqÿqÿtÿvÿxÿxÿ}ÿÿÿÿÿƒÿ…ÿ†ÿ…ÿƒÿÿwÿoÿcÿVÿGÿCÿ@ÿ<ÿ;ÿ:ÿ9ÿ6ÿ4ÿ3ÿ3ÿ2ÿ/ÿ.ÿ*}*x)})x*}*x*}*x)})y(}(x)~)x(~(x'~'w'~&w&~&w&~&w%~$x#~#x$~$y$~$y$$w%%x%%z%%{%%|%%~%%}%%~&&~&&~&~%&~&(&'(('&&'~'~&~&~%~%}%~%}%%~%%~%~%}%~%|%~%|%~%|%~%}$~$}%~&}'~'}'~'}'~'}'~'(~((~((~((~(*~((((~((|(~(}(~)~)~)~)~(~(~(~(~(~(~(~(~'~'}'~'|'~'}(~(}(~(~(~(~(~(~(~(}(~(}(~(}(~(})~)}((}((}(~(})~)|**{**{))x))x))w*)w(€)x)€)w(€(w'€'w((v'%x&~&z%~$z##{""{! | |~"~%‚(~,‡/~4Š;?ŒA€EHIŽGFE€FL€NO€NM~NŽG|7'{'Œ)z*‹*{)ˆ(~(„]€{|m€cueivp~h}@}!„.|2‰0{/Œ.|,Œ*|)Œ+~-‹7AF‚JŽR‚RŽTX^~cŒ^zOŠEyAˆ=xM‡_ufˆfsjnpwqp_YsSŽ`ueŒdxk‹rxxŠywu‹mwjŒdv[VwS’PyO“J{D“E|F“H}F“D|D”D|H”HzG”F{F•FyC–>y:•8y5”2w+‘)x))y+*y+,z,(z('{)+|,-}.Ž-},Œ.},Œ*})Œ'{&Š&{(Š+{.‹5|9Œ8|5Œ4|7Œ9|9<|<Ž={<Ž:{:9{75{55{45{55{56{5Ž3{6Œ6|6Œ2|1Š0|/‰/}/‰.}-ˆ,}/‡.}.‡,}+‡+},‡*~*†*~*†'%†$$†$#†$$†$%„)€,‚.21‚1z2‚1|.+(€%ƒ#%…'~'ˆ&~&ˆ&}'ˆ(}(ˆ(}(‰)}*‰+}+‰+}+‰,},‰,},‰.|.ˆ.}.ˆ-}-ˆ-}-ˆ.}.ˆ.}.ˆ,}-ˆ.}.ˆ,}+‰+}+‰*~*ˆ*~*ˆ++ˆ,,ˆ-~-‡/~.‡..‡..‡//‡..‡//ˆ00ˆ22ˆ32ˆ21‡24‡3~3ˆ4€5ˆ5€5‰6€6‰7€7ˆ8€8ˆ8€8ˆ8€8ˆ7€7‰9€>ˆE‚M…U„]{e…mns…rbm†iab†]j[…WxS…V~\„_|c„gtkƒilj„ije†cm[†XtT„O}L‚G…EDŠB€CC~DC}BC~CB~BA}AA}A‘B}B’B}B“@~@”?~=“<~;‘;~;;9Ž9:Œ;@‰EK…PƒRzW„YsZ…ZpX‡StP†O{L…OQ„S„X„Y\„a€c„h}lƒqysvxy‚{s{‚xru‚suuuyuw{z|v€ƒo„‚i‚ƒƒe…ƒ…cƒ„cz†qhfˆYuHˆA<ƒ9ˆ87‰5€5‰2€1‰1€.‰,€,‰*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ#ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ'ÿ'ÿ(ÿ&ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ%ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ'ÿ%ÿ&ÿ&ÿ&ÿ%ÿ$ÿ#ÿ"ÿ"ÿ!ÿ ÿ ÿÿÿÿ$ÿ&ÿ(ÿ,ÿ.ÿ3ÿ7ÿ;ÿ<ÿ?ÿAÿDÿAÿ@ÿAÿEÿKÿLÿMÿMÿLÿHÿCÿ4ÿ(ÿ(ÿ'ÿ)ÿ)ÿ*ÿ%ÿUÿwÿoÿlÿhÿnÿrÿpÿWÿ&ÿ+ÿ/ÿ3ÿ/ÿ.ÿ.ÿ,ÿ+ÿ*ÿ*ÿ+ÿ8ÿ@ÿKÿOÿRÿRÿUÿVÿVÿYÿ^ÿQÿCÿCÿOÿ^ÿgÿkÿjÿgÿmÿjÿVÿYÿaÿ^ÿZÿWÿWÿcÿoÿrÿtÿpÿlÿnÿiÿ_ÿYÿVÿRÿNÿHÿCÿ@ÿCÿEÿGÿFÿEÿFÿGÿEÿFÿHÿIÿIÿFÿDÿ?ÿ;ÿ9ÿ4ÿ.ÿ*ÿ*ÿ-ÿ*ÿ*ÿ.ÿ1ÿ1ÿ.ÿ+ÿ(ÿ'ÿ)ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ+ÿ,ÿ+ÿ,ÿ)ÿ)ÿ(ÿ)ÿ-ÿ1ÿ6ÿ4ÿ3ÿ4ÿ5ÿ6ÿ8ÿ8ÿ;ÿ:ÿ9ÿ8ÿ8ÿ7ÿ6ÿ4ÿ2ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ0ÿ/ÿ/ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ,ÿ-ÿ-ÿ*ÿ*ÿ*ÿ*ÿ(ÿ&ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ%ÿ(ÿ*ÿ.ÿ1ÿ0ÿ0ÿ2ÿ1ÿ0ÿ.ÿ+ÿ(ÿ&ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ(ÿ(ÿ(ÿ(ÿ)ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ/ÿ/ÿ.ÿ.ÿ,ÿ-ÿ-ÿ-ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ/ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ1ÿ1ÿ2ÿ3ÿ4ÿ3ÿ2ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ5ÿ5ÿ6ÿ6ÿ6ÿ7ÿ7ÿ8ÿ9ÿ9ÿ9ÿ8ÿ8ÿ8ÿ9ÿ9ÿ8ÿ;ÿAÿIÿQÿ[ÿbÿnÿrÿsÿoÿlÿeÿ`ÿ[ÿ[ÿYÿXÿYÿ_ÿcÿgÿiÿkÿjÿjÿhÿeÿ^ÿ[ÿXÿSÿPÿJÿGÿEÿDÿEÿFÿEÿCÿCÿCÿBÿBÿAÿAÿAÿAÿBÿBÿBÿBÿAÿ@ÿ?ÿ=ÿ<ÿ;ÿ:ÿ:ÿ9ÿ9ÿ9ÿ9ÿ:ÿ>ÿEÿHÿNÿRÿUÿWÿ[ÿZÿYÿVÿSÿQÿQÿRÿTÿVÿXÿ[ÿ]ÿ`ÿbÿbÿhÿmÿpÿsÿwÿxÿ|ÿ|ÿzÿxÿuÿsÿvÿxÿ|ÿ|ÿ~ÿ‚ÿ…ÿ„ÿ‚ÿ€ÿ‚ÿ‚ÿÿ~ÿyÿrÿiÿ[ÿJÿ>ÿ8ÿ7ÿ5ÿ4ÿ2ÿ2ÿ0ÿ/ÿ.ÿ-ÿ+ÿ,ÿ*}*z)})z(}(z)})z)})y(}(x)~(x'~'x'~&x&~&x%~%w$~$w$~$x$~$x$$y%%y$$z$$y%~%y%~%{%~%{%~%|&~&~&~&~%~%}'~''~''~''&''&&€&&€''~&%~%%}&%}%~%}%~%}$~$}$~$}$$}%%}%~%}%}%}%~&|&~&|'~&}'~'~(~('~''~%€'~(€'}&&}&&~&&~&'~'&~('~''~''~'~&~&~&~%~%~%~%~%}%~&}&~&}&~&}%~%}&~&}'~'}'~'}'~''~''~''~''(}((|((|)(|(({**z)€)z(€(y)€)x)€(x(€)w)€)u)'x((x(€(w(€(w''x'&y$#x##z!~ {~|~ $~%„*~,ˆ-~1‹46Œ7€:<=Ž;>Ž@BIJL€JJF>|2Ž*{'({)‹(~%†S€y|o‚cwaƒgxs‚o|]$'|/ˆ/{.Œ-{-+{+Ž*|*Ž*},Œ/€BŒKMŽR‚SŽS€QŽP~U[}Z‹LyDŠNwb‰kwo‰ose‹roj_o[hqeasaMuWŽgwlŒoxm‹kvjŒgwc^x]’XxN“GyB”?y@“A|C“E|F“D{D”E|H”I|I•HzF•DyB•@y=•7x/“,w,/w/‘0x3’6y8’2y.‘*y'(z(){)Ž+{,Ž+},Ž-}-Ž.{.Œ+{+‹-z-‹0|0‹0{0Š1{1‹2{36{67z65z44z11z10z1Ž2z2Ž2y2Ž1y0Œ0z.‹/z/Š/{/‰/{/‰.{.ˆ-|-ˆ.|.ˆ/|/ˆ1}1†/}.†,~*…)~)†*~)‡(~'‡&~&‡#%‡$~$†%)„-‚1€3ƒ2}3‚3{3‚0|/,)€&‚%~%„%}&‡'}'‡'}(‰(}(‰*}*‰+}+‰,},‰,},‰,}-ˆ.}.ˆ.}.ˆ.}/ˆ.}.ˆ/}/‰/}/‰.|.‰-|+‰*}*Š+}+Š*~)‰)~+‰+~+‡+~+‡+€,†.€/†/0†11†1€1†2€2†2€2†2€2†2€3†3€3†3€3‡4€4‡4€5‡5€6‡66ˆ66ˆ66ˆ88ˆ77‰99‰88Š87Š77Š?F‡O„Z{c„ijq…u^s‡n\j†ef`…^u[‡Y|X†\{`†eug†hok†loi…dn`…\sW…U}P‚K…H€HŠGFŒEEC}CB}A?~?@~@“B}B•A}A”A|@•?}=’<};:~9Ž8788Œ9<Š@€D†JƒOT„WwY…[r[…YrV†TyV†V~X„Y„[ƒ\ƒ]ƒ_‚dƒd€gƒh€l„pr‚t|x‚|x|{txuvvxxz€}w€‚r„…j‚€f„e~„|by†rei‰ZmIˆ;}5†5†3ƒ1ˆ/€.‰-€,‡++ˆ++ˆ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ(ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ&ÿ%ÿ%ÿ%ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ'ÿ&ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ'ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ%ÿ%ÿ&ÿ&ÿ%ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ&ÿ'ÿ(ÿ(ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ&ÿ#ÿ$ÿ#ÿ#ÿ!ÿ ÿ ÿÿÿ ÿ"ÿ%ÿ(ÿ+ÿ-ÿ0ÿ2ÿ3ÿ3ÿ4ÿ7ÿ:ÿ;ÿ>ÿBÿDÿFÿIÿKÿKÿJÿFÿBÿ6ÿ*ÿ'ÿ)ÿ)ÿ&ÿGÿ|ÿoÿ`ÿ_ÿfÿqÿqÿeÿ7ÿ ÿ,ÿ0ÿ.ÿ-ÿ-ÿ-ÿ+ÿ+ÿ*ÿ*ÿ*ÿ-ÿ=ÿGÿDÿGÿQÿTÿWÿRÿOÿQÿYÿYÿRÿJÿSÿbÿjÿqÿ^ÿoÿvÿbÿeÿoÿhÿaÿ`ÿbÿRÿPÿ\ÿdÿhÿjÿhÿdÿ_ÿ`ÿbÿ^ÿYÿQÿGÿBÿ@ÿ?ÿ@ÿBÿCÿCÿDÿDÿEÿFÿFÿFÿFÿEÿDÿBÿ@ÿ=ÿ7ÿ/ÿ-ÿ/ÿ1ÿ3ÿ4ÿ2ÿ5ÿ4ÿ2ÿ0ÿ+ÿ)ÿ(ÿ(ÿ(ÿ(ÿ*ÿ*ÿ*ÿ,ÿ/ÿ1ÿ2ÿ2ÿ0ÿ0ÿ.ÿ.ÿ-ÿ-ÿ/ÿ/ÿ.ÿ.ÿ/ÿ0ÿ2ÿ2ÿ4ÿ1ÿ1ÿ1ÿ1ÿ0ÿ1ÿ1ÿ0ÿ1ÿ2ÿ2ÿ2ÿ2ÿ1ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ-ÿ.ÿ.ÿ/ÿ/ÿ1ÿ1ÿ/ÿ.ÿ.ÿ,ÿ,ÿ,ÿ*ÿ)ÿ(ÿ'ÿ&ÿ&ÿ$ÿ%ÿ$ÿ$ÿ$ÿ'ÿ+ÿ-ÿ2ÿ5ÿ5ÿ5ÿ4ÿ1ÿ0ÿ-ÿ+ÿ(ÿ&ÿ%ÿ%ÿ&ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ0ÿ0ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ/ÿ-ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ+ÿ,ÿ,ÿ,ÿ,ÿ+ÿ,ÿ.ÿ/ÿ/ÿ0ÿ1ÿ1ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ3ÿ4ÿ3ÿ4ÿ5ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ7ÿ8ÿ7ÿ7ÿ9ÿ9ÿ8ÿ8ÿ8ÿ7ÿ6ÿ6ÿ6ÿ<ÿCÿNÿXÿaÿiÿqÿtÿvÿtÿqÿjÿgÿbÿ`ÿ]ÿ[ÿZÿ]ÿdÿgÿkÿjÿhÿhÿfÿcÿ^ÿYÿVÿTÿPÿKÿHÿGÿFÿEÿDÿCÿBÿAÿAÿAÿBÿBÿBÿBÿBÿBÿAÿ@ÿ?ÿ=ÿ<ÿ;ÿ:ÿ9ÿ7ÿ7ÿ8ÿ8ÿ9ÿ<ÿAÿEÿJÿOÿQÿUÿWÿZÿ[ÿ[ÿ[ÿYÿ[ÿ[ÿZÿ[ÿ\ÿ_ÿaÿcÿcÿeÿfÿfÿhÿkÿnÿqÿtÿxÿ{ÿ|ÿzÿwÿxÿxÿ{ÿÿ€ÿ‚ÿƒÿ„ÿ‚ÿÿ|ÿ{ÿ{ÿyÿuÿqÿgÿ^ÿMÿ=ÿ2ÿ0ÿ0ÿ.ÿ-ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ(}(z(}(z(}(z)})z)})y(}'x'~'x'~'x'~&x&~%x%~%x$~$x$~$y$~$y##z%%z%%z$%y&~%z%~%{&~&{%~%|$~$~%~%~%~%}&~&'~''~''%((''&&''€''€&&~%%~%~%}%~%}$~$}%~%}$$}$$}$~$}%}%}&~&}&~&}&~&}'~&~'~'&~&'~'%~%%}%%}%&~&&~&&~&&~&&~&~&~&~%~&~%~%~%~%~$~$~$~$}$~$}$~$}$~$}$~$}$~$}%~%}%~%}&~&~&~%~&~&~&~&~&&|''}''|''|'(z))y)€)y)€)y*€*x*€)x(€(w)€)u('v''v(€(v'€&v&&v('w$%x#"z"~"z ~ {  ~ ~#ƒ'~)ˆ.~/‹1~2Œ237€:Ž=€@ŽB€CŽF€HŽJ€MLGA|4){(‹*|'…6u~vƒkuk‚jvp€qzj€8€%…-{.Š-{-Œ-{-+|+Œ)})Œ*~0ŒF€CŒB‚I‹QƒSŒQ‚PO€SR~MXzY‹[y`Škx]ŠKtnŒopvŽnoigpd‘^oY‘YqVRsYŽaubev`\y][z[’\zS“KyF”Ay?“>{?“@{C“F{F“E|D“C|E”EzC”BzA”@z<”7x2’.w0‘4w8’7x2“-y1’2y1‘-y,*z)){(Ž){(Ž)|,/|25|54|4Ž2|.Œ.{,Œ.{.Œ-{-Œ/{//{./z//z//z//y00y1Ž3y33z3Œ2z1Š1z1Š.z.Š/{/‰0{0ˆ0{0ˆ.|.ˆ-|-‡0|0‡0}0†0}/†.}-…,},†*~)‡(~'‡&~$‡%~%‡$~$‡$$†(€+ƒ.‚2€4‚55ƒ2~1‚1.+€)€'ƒ%~%…'}'‡(}(‰(})‰+}+‰+}+‰-}-‰-}-‰.}.ˆ.}.ˆ.}.ˆ.}.ˆ0}0ˆ1}1ˆ1}1ˆ2~0‰0~/‰,~,ˆ-~-ˆ-~-ˆ,~,ˆ-~-‡,~,‡,~,†.~.†//†11†1€1†1€1†1€1‡2€2‡1€1‡1€1‡0€1‡2€2‡3€4‡4€4‡55ˆ44ˆ44ˆ56ˆ66‰88‰88Š88Š8~8Š65Š9B‡L‚V~_ƒjns…x_|„z\w…r^k†ggc†`qZ‡X{]†a|d†gvh…hpj…hnd…`s\„Y}VƒQ„LI‹GFE}DC}@’?}?”B}D–C|D—E|C—B|A–@}?“;};‘;~:89::Œ:<Š?€C†HNƒSƒW{Z…\u^„^r_†_u_†_{^†^€_…b‚d„f‚f„ff„cƒbe‡hk†or‚vzz||wzzw{€}x€ƒx‚‚r~k{‚yjv„tgs†nei‡^jQŠAw2‡..„-‡+,ˆ,€-†++‡**‡(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ&ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ&ÿ'ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ&ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ&ÿ'ÿ'ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ)ÿ)ÿ(ÿ(ÿ'ÿ&ÿ&ÿ&ÿ'ÿ&ÿ'ÿ%ÿ$ÿ#ÿ#ÿ"ÿ"ÿ ÿ ÿ ÿ"ÿ"ÿ'ÿ)ÿ,ÿ.ÿ0ÿ1ÿ3ÿ5ÿ8ÿ<ÿBÿCÿDÿDÿFÿJÿLÿOÿLÿGÿAÿ2ÿ(ÿ(ÿ,ÿ,ÿlÿ|ÿkÿkÿqÿrÿnÿlÿAÿÿ#ÿ*ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ+ÿ+ÿ*ÿ)ÿ)ÿ-ÿ/ÿ9ÿFÿLÿQÿPÿPÿPÿPÿKÿLÿPÿWÿ^ÿ`ÿ[ÿbÿ^ÿWÿ_ÿeÿpÿrÿoÿiÿiÿeÿcÿ]ÿ[ÿXÿ]ÿ`ÿaÿaÿ]ÿ\ÿZÿUÿXÿXÿSÿOÿLÿEÿ?ÿ<ÿ=ÿ>ÿ?ÿCÿCÿBÿAÿAÿEÿFÿBÿ@ÿ?ÿAÿBÿAÿ9ÿ0ÿ.ÿ1ÿ5ÿ5ÿ0ÿ-ÿ/ÿ2ÿ1ÿ1ÿ/ÿ.ÿ,ÿ)ÿ)ÿ)ÿ+ÿ+ÿ/ÿ-ÿ0ÿ3ÿ5ÿ8ÿ8ÿ3ÿ0ÿ.ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ-ÿ.ÿ-ÿ-ÿ.ÿ.ÿ-ÿ0ÿ/ÿ0ÿ1ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ.ÿ.ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ.ÿ,ÿ+ÿ+ÿ*ÿ)ÿ(ÿ'ÿ'ÿ&ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ%ÿ)ÿ,ÿ0ÿ2ÿ4ÿ4ÿ2ÿ2ÿ1ÿ/ÿ-ÿ,ÿ(ÿ'ÿ%ÿ%ÿ%ÿ(ÿ(ÿ(ÿ)ÿ+ÿ+ÿ+ÿ+ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ0ÿ1ÿ1ÿ/ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ1ÿ1ÿ0ÿ1ÿ2ÿ2ÿ2ÿ3ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ6ÿ6ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ4ÿ4ÿ4ÿ8ÿ@ÿLÿWÿaÿjÿuÿ|ÿ~ÿ}ÿzÿvÿpÿiÿeÿ_ÿYÿWÿ[ÿ]ÿcÿfÿfÿiÿjÿiÿfÿbÿ\ÿZÿVÿQÿLÿIÿGÿGÿFÿEÿBÿBÿDÿFÿGÿFÿGÿGÿEÿCÿBÿ@ÿ@ÿ?ÿ=ÿ<ÿ;ÿ9ÿ:ÿ:ÿ:ÿ;ÿ=ÿAÿDÿHÿNÿSÿWÿZÿ\ÿ_ÿ_ÿ`ÿ`ÿaÿaÿ`ÿ`ÿaÿcÿeÿgÿgÿgÿfÿcÿ`ÿ`ÿbÿdÿjÿoÿsÿxÿ}ÿ|ÿ|ÿzÿzÿ{ÿ~ÿ€ÿÿ€ÿ€ÿ}ÿxÿvÿtÿrÿqÿmÿhÿ_ÿVÿFÿ6ÿ-ÿ-ÿ-ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ+ÿ+ÿ'~'{(~({(~(y(~(y)~)x(~'x&~&x&~&x&~'y&~%y%~%y$~$y$~$y#~#y$$z%%z&~&z&~&|&&|&&|'~'}&}&}&~&}&~&}&~&}&~&'~'%~%%&((''(('~''~''&~&&~$~&}&~&}%%}%%}%%}%%}%~%~%~%~&~&}'~'}&~&&~&%~%&~&%}%&}&$~$€%~%~%~%$~$%~%%~%~%~$€$~$€$~#~#~#~#~#~"~"~"~""~"!~""~""~"~"~"}#~#}#~#}$~$|#~#|$~%|%~%|%%|%&|&&|&&{'(z((y'€'w)€)x))x))w)€)u)€)u(€(v)€)v)€)u'€'u&€(v(€&v&~'x%~%y$~"y"~ { !~#~#‚'~)‡*}+Š.}0Œ39<@B€FŒJ€MŒN€NŒO€O‰J~FŠA|1Œ(})ˆ*[‚ƒwxlƒmuv€syoP ~ ƒ#|,‡,{,Š,|,Œ,|,,},Œ*|*Œ+~1‹5€:Š@‚H‹O„RŒOƒNKƒHŽKKŽU~aŒd{X‹QxZŒeuiŒgracqkhp[’Xod“an_“_o_‘\q__s\ZvYSwQ‘OzR’RyN“HzB”@y>“’=~<;~;=~=?~?‹AEˆHM„R‚V}Z‚^v`„arc…crb…bua…b{e…de…ff…f‚fƒc„`‚]‡[€\Œd~jŠn~s€uyx{‚{vzzx{~v~€s~yow‚umrƒoinƒlei…cfYˆMq=ˆ.{*…*…*‚+†++‡,+†))†(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ$ÿ$ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ&ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ%ÿ&ÿ&ÿ&ÿ%ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ#ÿ#ÿ#ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ"ÿ#ÿ#ÿ"ÿ"ÿ"ÿ!ÿ!ÿ!ÿ"ÿ"ÿ"ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ"ÿ"ÿ!ÿ!ÿ"ÿ"ÿ"ÿ"ÿ#ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ'ÿ'ÿ'ÿ'ÿ&ÿ$ÿ$ÿ#ÿ!ÿ!ÿ!ÿ!ÿ"ÿ$ÿ'ÿ)ÿ*ÿ+ÿ-ÿ/ÿ1ÿ6ÿ>ÿBÿFÿIÿMÿPÿRÿRÿTÿRÿJÿFÿ?ÿ.ÿ(ÿ)ÿ9ÿzÿrÿiÿnÿwÿwÿqÿTÿ!ÿ ÿ"ÿ&ÿ,ÿ,ÿ+ÿ+ÿ,ÿ+ÿ+ÿ,ÿ,ÿ*ÿ*ÿ*ÿ/ÿ0ÿ5ÿ>ÿFÿIÿLÿLÿIÿJÿLÿMÿLÿXÿbÿcÿYÿEÿHÿSÿTÿ_ÿfÿlÿdÿdÿaÿ_ÿ`ÿbÿiÿkÿeÿ^ÿ^ÿ_ÿ^ÿ]ÿZÿRÿPÿQÿSÿRÿLÿEÿBÿAÿ>ÿ<ÿ8ÿ7ÿ7ÿ8ÿ8ÿ9ÿ8ÿ9ÿ9ÿ9ÿ9ÿ;ÿBÿPÿIÿ1ÿ*ÿ)ÿ,ÿ-ÿ0ÿ2ÿ2ÿ4ÿ2ÿ1ÿ/ÿ0ÿ/ÿ/ÿ0ÿ2ÿ3ÿ4ÿ4ÿ5ÿ2ÿ2ÿ2ÿ3ÿ3ÿ0ÿ-ÿ*ÿ)ÿ*ÿ*ÿ)ÿ+ÿ+ÿ+ÿ-ÿ-ÿ-ÿ-ÿ.ÿ/ÿ/ÿ/ÿ/ÿ0ÿ/ÿ0ÿ1ÿ0ÿ0ÿ0ÿ2ÿ3ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ1ÿ1ÿ0ÿ0ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ/ÿ.ÿ/ÿ-ÿ-ÿ,ÿ+ÿ*ÿ)ÿ(ÿ)ÿ(ÿ&ÿ&ÿ%ÿ%ÿ$ÿ#ÿ$ÿ$ÿ$ÿ'ÿ(ÿ*ÿ-ÿ.ÿ/ÿ0ÿ1ÿ0ÿ0ÿ/ÿ.ÿ,ÿ)ÿ'ÿ(ÿ(ÿ)ÿ(ÿ(ÿ)ÿ*ÿ*ÿ)ÿ+ÿ,ÿ,ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ-ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ,ÿ-ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ.ÿ.ÿ-ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ2ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ3ÿ4ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ6ÿ9ÿ@ÿLÿYÿcÿpÿvÿ{ÿ{ÿ}ÿ}ÿwÿqÿhÿ_ÿVÿPÿQÿWÿ\ÿaÿeÿhÿjÿjÿlÿgÿdÿaÿ\ÿVÿRÿMÿLÿKÿJÿJÿJÿKÿLÿLÿMÿMÿLÿIÿGÿFÿDÿCÿ@ÿ?ÿ=ÿ<ÿ<ÿ<ÿ=ÿ=ÿ?ÿ?ÿCÿGÿHÿMÿRÿVÿYÿ]ÿ_ÿ_ÿcÿdÿdÿeÿbÿdÿdÿeÿeÿfÿfÿfÿfÿcÿ]ÿ]ÿZÿXÿ[ÿaÿfÿlÿpÿsÿyÿzÿ{ÿ{ÿyÿzÿ~ÿ~ÿ}ÿxÿvÿrÿpÿmÿnÿlÿjÿeÿ^ÿTÿCÿ1ÿ+ÿ)ÿ*ÿ)ÿ*ÿ*ÿ+ÿ*ÿ(ÿ(ÿ'~'{'~({)~)y(~'y)~)x(~'x'~'x'~'x&~&y%~%y%~%z%~%z%~%{$~${$$z%%z&}&{&~&|&&}''}'~'}'}'}&~&}&~&}&~&}%~%'~'&~&''''''€''€&&&&''%%&%%%$$%&%%&&%~%%~%%~&}&~&}%~%'~'&~&%~%%~%$~$$~$€"~#~#~#$~$$~$#~#~"~"~"~"~"~"~!~!~!~!~!~ ~!~!!~!!~! ~! ~!~!~!}!~!}~}#~#|"~"|!~"|#~#|$$|$%|%%{%%z&'z&&y(€(w(€(x((w))v)€)u)€)u)€)v)€)v)€)u*€*u*€*v(€(w(€(w&€%x$$x"~"z!!}!$(~)†*}+‰+}.‹/5Œ=€A‹E‚J‰S‚UˆT‚TˆV€Q‰J~EŠ;}*Œ)(ˆT‚u~qƒmvs‚yxte}*~ „"|!‡%{*ˆ,{,Š*|*‹*|+Œ,}.Œ,|+Œ+~+Œ*€3‹=BH‚KŽG‚HŽH‚FŽD€FŽP~_S{FŒKvSŒQtTŽbrgeoijnf^o_\nb’jnd‘ZpX[r\\sZSvP‘TyV’SzQ’L|C“Bz?“;x:“7y5“4y4“5{4’5{6‘8{8’:{A’Ez@8z,Ž)z+Ž,y.3y3’4y4’2y2‘1y45y66z56|76|31|0‘/|/‘,|+‘*|)‘(|((|(){+Ž-z-Ž-y-Ž.y0Ž/y0Ž1x1/y/Œ/y/‹0y0‹0y1Š3z3‰1z2‡2|3†3|3†3|1‡1|0‡/|,‡-|/†.|.„/|-„.|,…-|,…+{+„+|*†)|)†(|&†$}$‡$~$†$~#†$~$…$€'…(+„,,ƒ/0‚2‚2‚1€/‚-€+„*~)‡)})ˆ*}*ˆ*}*ˆ*}*ˆ+|,ˆ.|.ˆ0|0ˆ/|0ˆ/|-‡.|/‡/}/‡/}/‡.}.ˆ.}.ˆ/}/ˆ/}/ˆ.~.‡.~.‡.~.‡-~,†,,‡++‡++‡,,‡--‡..‡..‡//‡/~/ˆ/~/ˆ0~0ˆ/~/ˆ/~/ˆ/~/ˆ//ˆ00ˆ12‰22‰11‰11Š11Š23Š3~3‰4~4Š3~4‹5~7Œ8AŠNY{e‚nhw‚z\{ƒ|Wy…uXo‡ecX‡PtP‡Q}T…Z~b…fyg…jtk…jog…drb„]}X‚R†P€OM~O‘M|N•O{O˜N{N—L{I—H|F–E|D”@|@’?~>=~<Ž>~>Œ>~>‰AE‰I€NƒQ‚U[‚]y]ƒasc…emf…fof…gvf…f{g…g}f…fdƒb€_‚[†U€RŒQ}VŽ^|d‹h~k„r€v|z|wz{v|€zuzxqu‚qnrƒlklƒkgi…ff_ˆXiJˆ:s,†)~+ƒ*„))„)‚)„(‚(„'ÿ'ÿ'ÿ(ÿ)ÿ)ÿ(ÿ'ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ#ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ#ÿ"ÿ"ÿ"ÿ#ÿ#ÿ#ÿ#ÿ"ÿ"ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ ÿ ÿ ÿ ÿ ÿÿ ÿ ÿ ÿ ÿ ÿ!ÿ!ÿ!ÿ ÿ!ÿ!ÿ!ÿ ÿ ÿÿ!ÿ$ÿ$ÿ!ÿ!ÿ!ÿ"ÿ#ÿ#ÿ$ÿ%ÿ$ÿ$ÿ%ÿ%ÿ&ÿ&ÿ&ÿ'ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ(ÿ(ÿ(ÿ(ÿ'ÿ%ÿ$ÿ$ÿ#ÿ#ÿ"ÿ"ÿ#ÿ&ÿ(ÿ)ÿ)ÿ*ÿ+ÿ.ÿ.ÿ4ÿ8ÿ>ÿHÿKÿRÿVÿUÿUÿRÿKÿHÿDÿ9ÿ)ÿ(ÿ,ÿkÿsÿjÿqÿxÿsÿiÿ7ÿÿ$ÿ"ÿ#ÿ'ÿ*ÿ+ÿ+ÿ*ÿ*ÿ)ÿ+ÿ,ÿ.ÿ/ÿ,ÿ*ÿ,ÿ+ÿ5ÿAÿGÿGÿIÿIÿFÿ@ÿ?ÿ;ÿ=ÿFÿRÿJÿ?ÿIÿRÿYÿkÿhÿfÿhÿhÿcÿeÿgÿ\ÿPÿ]ÿgÿaÿXÿTÿWÿXÿZÿZÿVÿQÿUÿSÿMÿMÿJÿCÿCÿ@ÿ=ÿ=ÿ:ÿ7ÿ5ÿ5ÿ5ÿ3ÿ4ÿ6ÿ8ÿ8ÿ<ÿ>ÿ>ÿ>ÿ<ÿ0ÿ)ÿ)ÿ+ÿ-ÿ0ÿ1ÿ4ÿ4ÿ4ÿ3ÿ4ÿ6ÿ8ÿ8ÿ8ÿ7ÿ5ÿ4ÿ3ÿ1ÿ/ÿ-ÿ-ÿ-ÿ,ÿ+ÿ*ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ*ÿ,ÿ.ÿ.ÿ/ÿ0ÿ/ÿ0ÿ1ÿ1ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ1ÿ1ÿ1ÿ3ÿ2ÿ3ÿ4ÿ4ÿ4ÿ5ÿ3ÿ2ÿ0ÿ/ÿ,ÿ+ÿ.ÿ.ÿ/ÿ-ÿ-ÿ-ÿ-ÿ,ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ(ÿ'ÿ'ÿ%ÿ$ÿ&ÿ$ÿ$ÿ$ÿ$ÿ#ÿ&ÿ&ÿ(ÿ*ÿ+ÿ.ÿ1ÿ3ÿ3ÿ2ÿ2ÿ0ÿ/ÿ/ÿ,ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ.ÿ0ÿ0ÿ0ÿ0ÿ/ÿ0ÿ1ÿ1ÿ0ÿ.ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ-ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ2ÿ2ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ3ÿ4ÿ4ÿ5ÿ4ÿ5ÿ7ÿ6ÿ:ÿCÿNÿYÿcÿjÿsÿyÿyÿyÿuÿrÿjÿ_ÿVÿOÿSÿRÿWÿ\ÿbÿgÿmÿkÿlÿkÿiÿeÿbÿ\ÿXÿWÿRÿQÿPÿPÿOÿPÿPÿNÿNÿJÿHÿGÿEÿDÿDÿBÿ@ÿ@ÿ?ÿ?ÿ=ÿ?ÿ?ÿAÿAÿCÿEÿGÿNÿQÿUÿ[ÿ]ÿ^ÿbÿdÿgÿiÿhÿgÿhÿgÿgÿgÿgÿfÿeÿdÿaÿ]ÿXÿRÿOÿNÿOÿRÿYÿaÿgÿlÿpÿtÿxÿ{ÿzÿyÿzÿzÿxÿxÿwÿsÿmÿmÿlÿiÿgÿdÿ\ÿSÿCÿ4ÿ-ÿ*ÿ)ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ \ No newline at end of file diff --git a/media/test/data/bear-1280x720-a_frag-cenc.mp4 b/media/test/data/bear-1280x720-a_frag-cenc.mp4 new file mode 100644 index 0000000000..8feea53a26 Binary files /dev/null and b/media/test/data/bear-1280x720-a_frag-cenc.mp4 differ diff --git a/media/test/data/bear-1280x720-a_frag-cenc_clear-all.mp4 b/media/test/data/bear-1280x720-a_frag-cenc_clear-all.mp4 new file mode 100644 index 0000000000..81cdd5823c Binary files /dev/null and b/media/test/data/bear-1280x720-a_frag-cenc_clear-all.mp4 differ diff --git a/media/test/data/bear-1280x720-av_frag.mp4 b/media/test/data/bear-1280x720-av_frag.mp4 new file mode 100644 index 0000000000..a05e1a0ee1 Binary files /dev/null and b/media/test/data/bear-1280x720-av_frag.mp4 differ diff --git a/media/test/data/bear-1280x720-v_frag-cenc.mp4 b/media/test/data/bear-1280x720-v_frag-cenc.mp4 new file mode 100644 index 0000000000..c4a6ae78c5 Binary files /dev/null and b/media/test/data/bear-1280x720-v_frag-cenc.mp4 differ diff --git a/media/test/data/bear-1280x720-v_frag-cenc_clear-all.mp4 b/media/test/data/bear-1280x720-v_frag-cenc_clear-all.mp4 new file mode 100644 index 0000000000..47c83c4dea Binary files /dev/null and b/media/test/data/bear-1280x720-v_frag-cenc_clear-all.mp4 differ diff --git a/media/test/data/bear-1280x720-zero-stsz-entry.mp4 b/media/test/data/bear-1280x720-zero-stsz-entry.mp4 new file mode 100644 index 0000000000..9d38659ec6 Binary files /dev/null and b/media/test/data/bear-1280x720-zero-stsz-entry.mp4 differ diff --git a/media/test/data/bear-1280x720.mp4 b/media/test/data/bear-1280x720.mp4 new file mode 100644 index 0000000000..b424a0f7d1 Binary files /dev/null and b/media/test/data/bear-1280x720.mp4 differ diff --git a/media/test/data/bear-320x240-16x9-aspect-av_enc-av.webm b/media/test/data/bear-320x240-16x9-aspect-av_enc-av.webm new file mode 100644 index 0000000000..f285f488a7 Binary files /dev/null and b/media/test/data/bear-320x240-16x9-aspect-av_enc-av.webm differ diff --git a/media/test/data/bear-320x240-16x9-aspect.webm b/media/test/data/bear-320x240-16x9-aspect.webm new file mode 100644 index 0000000000..e29e8096cd Binary files /dev/null and b/media/test/data/bear-320x240-16x9-aspect.webm differ diff --git a/media/test/data/bear-320x240-altref.webm b/media/test/data/bear-320x240-altref.webm new file mode 100644 index 0000000000..b1db07a5c8 Binary files /dev/null and b/media/test/data/bear-320x240-altref.webm differ diff --git a/media/test/data/bear-320x240-audio-only.webm b/media/test/data/bear-320x240-audio-only.webm new file mode 100644 index 0000000000..6594754e8e Binary files /dev/null and b/media/test/data/bear-320x240-audio-only.webm differ diff --git a/media/test/data/bear-320x240-av_enc-av.webm b/media/test/data/bear-320x240-av_enc-av.webm new file mode 100644 index 0000000000..e027f917d4 Binary files /dev/null and b/media/test/data/bear-320x240-av_enc-av.webm differ diff --git a/media/test/data/bear-320x240-av_enc-av_clear-1s.webm b/media/test/data/bear-320x240-av_enc-av_clear-1s.webm new file mode 100644 index 0000000000..c2cd41a135 Binary files /dev/null and b/media/test/data/bear-320x240-av_enc-av_clear-1s.webm differ diff --git a/media/test/data/bear-320x240-av_enc-av_clear-all.webm b/media/test/data/bear-320x240-av_enc-av_clear-all.webm new file mode 100644 index 0000000000..9610e6d53f Binary files /dev/null and b/media/test/data/bear-320x240-av_enc-av_clear-all.webm differ diff --git a/media/test/data/bear-320x240-cues-in-front.webm b/media/test/data/bear-320x240-cues-in-front.webm new file mode 100644 index 0000000000..d15f3b2bdd Binary files /dev/null and b/media/test/data/bear-320x240-cues-in-front.webm differ diff --git a/media/test/data/bear-320x240-live.webm b/media/test/data/bear-320x240-live.webm new file mode 100644 index 0000000000..c196c9b634 Binary files /dev/null and b/media/test/data/bear-320x240-live.webm differ diff --git a/media/test/data/bear-320x240-manifest.js b/media/test/data/bear-320x240-manifest.js new file mode 100644 index 0000000000..9e6602a137 --- /dev/null +++ b/media/test/data/bear-320x240-manifest.js @@ -0,0 +1,17 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +{ + duration: 2.744000, + type: 'video/webm; codecs="vp8, vorbis"', + init: { offset: 0, size: 4370}, + media: [ + { offset: 4370, size: 40778, timecode: 0.000000 }, + { offset: 45148, size: 27589, timecode: 0.396000 }, + { offset: 72737, size: 28183, timecode: 0.779000 }, + { offset: 100920, size: 31600, timecode: 1.197000 }, + { offset: 132520, size: 33922, timecode: 1.589000 }, + { offset: 166442, size: 30587, timecode: 1.987000 }, + { offset: 197029, size: 22079, timecode: 2.400000 }, + ] +} diff --git a/media/test/data/bear-320x240-multitrack.webm b/media/test/data/bear-320x240-multitrack.webm new file mode 100644 index 0000000000..987b1935a6 Binary files /dev/null and b/media/test/data/bear-320x240-multitrack.webm differ diff --git a/media/test/data/bear-320x240-video-only.webm b/media/test/data/bear-320x240-video-only.webm new file mode 100644 index 0000000000..a6601f4cbd Binary files /dev/null and b/media/test/data/bear-320x240-video-only.webm differ diff --git a/media/test/data/bear-320x240.webm b/media/test/data/bear-320x240.webm new file mode 100644 index 0000000000..a1b4150b32 Binary files /dev/null and b/media/test/data/bear-320x240.webm differ diff --git a/media/test/data/bear-640x360-a_frag-cenc.mp4 b/media/test/data/bear-640x360-a_frag-cenc.mp4 new file mode 100644 index 0000000000..3bd6988dda Binary files /dev/null and b/media/test/data/bear-640x360-a_frag-cenc.mp4 differ diff --git a/media/test/data/bear-640x360-av_enc-av.webm b/media/test/data/bear-640x360-av_enc-av.webm new file mode 100644 index 0000000000..2054ae0739 Binary files /dev/null and b/media/test/data/bear-640x360-av_enc-av.webm differ diff --git a/media/test/data/bear-640x360-av_frag.mp4 b/media/test/data/bear-640x360-av_frag.mp4 new file mode 100644 index 0000000000..e5a045af7c Binary files /dev/null and b/media/test/data/bear-640x360-av_frag.mp4 differ diff --git a/media/test/data/bear-640x360-manifest.js b/media/test/data/bear-640x360-manifest.js new file mode 100644 index 0000000000..d7787b0bce --- /dev/null +++ b/media/test/data/bear-640x360-manifest.js @@ -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. +{ + duration: 2.740000, + type: 'video/webm; codecs="vp8, vorbis"', + init: { offset: 0, size: 4340}, + media: [ + { offset: 4340, size: 50950, timecode: 0.000000}, + { offset: 55290, size: 18785, timecode: 0.527000}, + { offset: 74075, size: 19810, timecode: 1.014000}, + { offset: 93885, size: 21706, timecode: 1.522000}, + { offset: 115591, size: 20249, timecode: 2.016000}, + { offset: 135840, size: 9946, timecode: 2.515000}, + ] +} diff --git a/media/test/data/bear-640x360-v_frag-cenc.mp4 b/media/test/data/bear-640x360-v_frag-cenc.mp4 new file mode 100644 index 0000000000..eaf1684f1e Binary files /dev/null and b/media/test/data/bear-640x360-v_frag-cenc.mp4 differ diff --git a/media/test/data/bear-640x360.webm b/media/test/data/bear-640x360.webm new file mode 100644 index 0000000000..02ae36c223 Binary files /dev/null and b/media/test/data/bear-640x360.webm differ diff --git a/media/test/data/bear-mpeg2-aac-only_frag.mp4 b/media/test/data/bear-mpeg2-aac-only_frag.mp4 new file mode 100644 index 0000000000..6ce731cec8 Binary files /dev/null and b/media/test/data/bear-mpeg2-aac-only_frag.mp4 differ diff --git a/media/test/data/bear-opus.webm b/media/test/data/bear-opus.webm new file mode 100644 index 0000000000..9f889522bd Binary files /dev/null and b/media/test/data/bear-opus.webm differ diff --git a/media/test/data/bear-vp8-webvtt.webm b/media/test/data/bear-vp8-webvtt.webm new file mode 100644 index 0000000000..c4b4d14ee6 Binary files /dev/null and b/media/test/data/bear-vp8-webvtt.webm differ diff --git a/media/test/data/bear-vp8a.webm b/media/test/data/bear-vp8a.webm new file mode 100644 index 0000000000..7d06257696 Binary files /dev/null and b/media/test/data/bear-vp8a.webm differ diff --git a/media/test/data/bear-vp9-opus.webm b/media/test/data/bear-vp9-opus.webm new file mode 100644 index 0000000000..f7812c2603 Binary files /dev/null and b/media/test/data/bear-vp9-opus.webm differ diff --git a/media/test/data/bear-vp9.webm b/media/test/data/bear-vp9.webm new file mode 100644 index 0000000000..d080589e23 Binary files /dev/null and b/media/test/data/bear-vp9.webm differ diff --git a/media/test/data/bear.ac3 b/media/test/data/bear.ac3 new file mode 100644 index 0000000000..eaf2882ca7 Binary files /dev/null and b/media/test/data/bear.ac3 differ diff --git a/media/test/data/bear.adts b/media/test/data/bear.adts new file mode 100644 index 0000000000..268d521a92 Binary files /dev/null and b/media/test/data/bear.adts differ diff --git a/media/test/data/bear.aiff b/media/test/data/bear.aiff new file mode 100644 index 0000000000..b8d25536a9 Binary files /dev/null and b/media/test/data/bear.aiff differ diff --git a/media/test/data/bear.asf b/media/test/data/bear.asf new file mode 100644 index 0000000000..1b967f7b82 Binary files /dev/null and b/media/test/data/bear.asf differ diff --git a/media/test/data/bear.avi b/media/test/data/bear.avi new file mode 100644 index 0000000000..864cf0cbd2 Binary files /dev/null and b/media/test/data/bear.avi differ diff --git a/media/test/data/bear.eac3 b/media/test/data/bear.eac3 new file mode 100644 index 0000000000..6161b15ef5 Binary files /dev/null and b/media/test/data/bear.eac3 differ diff --git a/media/test/data/bear.flac b/media/test/data/bear.flac new file mode 100644 index 0000000000..6b0286c186 Binary files /dev/null and b/media/test/data/bear.flac differ diff --git a/media/test/data/bear.flv b/media/test/data/bear.flv new file mode 100644 index 0000000000..85c65962da Binary files /dev/null and b/media/test/data/bear.flv differ diff --git a/media/test/data/bear.h261 b/media/test/data/bear.h261 new file mode 100644 index 0000000000..c201363062 Binary files /dev/null and b/media/test/data/bear.h261 differ diff --git a/media/test/data/bear.h263 b/media/test/data/bear.h263 new file mode 100644 index 0000000000..e625badae6 Binary files /dev/null and b/media/test/data/bear.h263 differ diff --git a/media/test/data/bear.m2ts b/media/test/data/bear.m2ts new file mode 100644 index 0000000000..4508dc463e Binary files /dev/null and b/media/test/data/bear.m2ts differ diff --git a/media/test/data/bear.mjpeg b/media/test/data/bear.mjpeg new file mode 100644 index 0000000000..1a4ad2de92 Binary files /dev/null and b/media/test/data/bear.mjpeg differ diff --git a/media/test/data/bear.mpeg b/media/test/data/bear.mpeg new file mode 100644 index 0000000000..4e51b5a157 Binary files /dev/null and b/media/test/data/bear.mpeg differ diff --git a/media/test/data/bear.ogv b/media/test/data/bear.ogv new file mode 100644 index 0000000000..b2e113437b Binary files /dev/null and b/media/test/data/bear.ogv differ diff --git a/media/test/data/bear.rm b/media/test/data/bear.rm new file mode 100644 index 0000000000..894a150f73 Binary files /dev/null and b/media/test/data/bear.rm differ diff --git a/media/test/data/bear.swf b/media/test/data/bear.swf new file mode 100644 index 0000000000..f36cb36ca1 Binary files /dev/null and b/media/test/data/bear.swf differ diff --git a/media/test/data/id3_png_test.mp3 b/media/test/data/id3_png_test.mp3 new file mode 100644 index 0000000000..61efa2c0c3 Binary files /dev/null and b/media/test/data/id3_png_test.mp3 differ diff --git a/media/test/data/id3_test.mp3 b/media/test/data/id3_test.mp3 new file mode 100644 index 0000000000..ddf43b9824 Binary files /dev/null and b/media/test/data/id3_test.mp3 differ diff --git a/media/test/data/midstream_config_change.mp3 b/media/test/data/midstream_config_change.mp3 new file mode 100644 index 0000000000..5bf5a5de6c Binary files /dev/null and b/media/test/data/midstream_config_change.mp3 differ diff --git a/media/test/data/no_audio_video.webm b/media/test/data/no_audio_video.webm new file mode 100644 index 0000000000..924c091af6 Binary files /dev/null and b/media/test/data/no_audio_video.webm differ diff --git a/media/test/data/no_streams.webm b/media/test/data/no_streams.webm new file mode 100644 index 0000000000..e0bc3e8982 Binary files /dev/null and b/media/test/data/no_streams.webm differ diff --git a/media/test/data/nonzero-start-time.webm b/media/test/data/nonzero-start-time.webm new file mode 100644 index 0000000000..9429bc8f65 Binary files /dev/null and b/media/test/data/nonzero-start-time.webm differ diff --git a/media/test/data/rapid_video_change_test.html b/media/test/data/rapid_video_change_test.html new file mode 100644 index 0000000000..81a853bded --- /dev/null +++ b/media/test/data/rapid_video_change_test.html @@ -0,0 +1,47 @@ + + + + HTML 5 Change H.264 Video Test + + + + This test tests the case where in H.264 H/W + decoded videos are added and removed a number of times from the page, + while they are playing.
This should not cause the browser to hang. +

+ * It keeps all state in memory, and there is no difference between apply() and commit(). + */ +public class InMemorySharedPreferences implements SharedPreferences { + + // Guarded on its own monitor. + private final Map mData; + + public InMemorySharedPreferences() { + mData = new HashMap(); + } + + public InMemorySharedPreferences(Map data) { + mData = data; + } + + @Override + public Map getAll() { + synchronized (mData) { + return Collections.unmodifiableMap(mData); + } + } + + @Override + public String getString(String key, String defValue) { + synchronized (mData) { + if (mData.containsKey(key)) { + return (String) mData.get(key); + } + } + return defValue; + } + + @SuppressWarnings("unchecked") + @Override + public Set getStringSet(String key, Set defValues) { + synchronized (mData) { + if (mData.containsKey(key)) { + return Collections.unmodifiableSet((Set) mData.get(key)); + } + } + return defValues; + } + + @Override + public int getInt(String key, int defValue) { + synchronized (mData) { + if (mData.containsKey(key)) { + return (Integer) mData.get(key); + } + } + return defValue; + } + + @Override + public long getLong(String key, long defValue) { + synchronized (mData) { + if (mData.containsKey(key)) { + return (Long) mData.get(key); + } + } + return defValue; + } + + @Override + public float getFloat(String key, float defValue) { + synchronized (mData) { + if (mData.containsKey(key)) { + return (Float) mData.get(key); + } + } + return defValue; + } + + @Override + public boolean getBoolean(String key, boolean defValue) { + synchronized (mData) { + if (mData.containsKey(key)) { + return (Boolean) mData.get(key); + } + } + return defValue; + } + + @Override + public boolean contains(String key) { + synchronized (mData) { + return mData.containsKey(key); + } + } + + @Override + public SharedPreferences.Editor edit() { + return new InMemoryEditor(); + } + + @Override + public void registerOnSharedPreferenceChangeListener( + SharedPreferences.OnSharedPreferenceChangeListener + listener) { + throw new UnsupportedOperationException(); + } + + @Override + public void unregisterOnSharedPreferenceChangeListener( + SharedPreferences.OnSharedPreferenceChangeListener listener) { + throw new UnsupportedOperationException(); + } + + private class InMemoryEditor implements SharedPreferences.Editor { + + // All guarded by |mChanges| + private boolean mClearCalled; + private volatile boolean mApplyCalled; + private final Map mChanges = new HashMap(); + + @Override + public SharedPreferences.Editor putString(String key, String value) { + synchronized (mChanges) { + if (mApplyCalled) throw new IllegalStateException(); + mChanges.put(key, value); + return this; + } + } + + @Override + public SharedPreferences.Editor putStringSet(String key, Set values) { + synchronized (mChanges) { + if (mApplyCalled) throw new IllegalStateException(); + mChanges.put(key, values); + return this; + } + } + + @Override + public SharedPreferences.Editor putInt(String key, int value) { + synchronized (mChanges) { + if (mApplyCalled) throw new IllegalStateException(); + mChanges.put(key, value); + return this; + } + } + + @Override + public SharedPreferences.Editor putLong(String key, long value) { + synchronized (mChanges) { + if (mApplyCalled) throw new IllegalStateException(); + mChanges.put(key, value); + return this; + } + } + + @Override + public SharedPreferences.Editor putFloat(String key, float value) { + synchronized (mChanges) { + if (mApplyCalled) throw new IllegalStateException(); + mChanges.put(key, value); + return this; + } + } + + @Override + public SharedPreferences.Editor putBoolean(String key, boolean value) { + synchronized (mChanges) { + if (mApplyCalled) throw new IllegalStateException(); + mChanges.put(key, value); + return this; + } + } + + @Override + public SharedPreferences.Editor remove(String key) { + synchronized (mChanges) { + if (mApplyCalled) throw new IllegalStateException(); + // Magic value for removes + mChanges.put(key, this); + return this; + } + } + + @Override + public SharedPreferences.Editor clear() { + synchronized (mChanges) { + if (mApplyCalled) throw new IllegalStateException(); + mClearCalled = true; + return this; + } + } + + @Override + public boolean commit() { + apply(); + return true; + } + + @Override + public void apply() { + synchronized (mData) { + synchronized (mChanges) { + if (mApplyCalled) throw new IllegalStateException(); + if (mClearCalled) { + mData.clear(); + } + for (Map.Entry entry : mChanges.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + if (value == this) { + // Special value for removal + mData.remove(key); + } else { + mData.put(key, value); + } + } + // The real shared prefs clears out the temporaries allowing the caller to + // reuse the Editor instance, however this is undocumented behavior and subtle + // to read, so instead we just ban any future use of this instance. + mApplyCalled = true; + } + } + } + } + +} diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/InstrumentationUtils.java b/base/test/android/javatests/src/org/chromium/base/test/util/InstrumentationUtils.java new file mode 100644 index 0000000000..0dc730a739 --- /dev/null +++ b/base/test/android/javatests/src/org/chromium/base/test/util/InstrumentationUtils.java @@ -0,0 +1,32 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.base.test.util; + +import android.app.Instrumentation; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; + +/** + * Utility methods built around the android.app.Instrumentation class. + */ +public final class InstrumentationUtils { + + private InstrumentationUtils() { + } + + public static R runOnMainSyncAndGetResult(Instrumentation instrumentation, + Callable callable) throws Throwable { + FutureTask task = new FutureTask(callable); + instrumentation.runOnMainSync(task); + try { + return task.get(); + } catch (ExecutionException e) { + // Unwrap the cause of the exception and re-throw it. + throw e.getCause(); + } + } +} diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/PhoneOnly.java b/base/test/android/javatests/src/org/chromium/base/test/util/PhoneOnly.java new file mode 100644 index 0000000000..1c82e2046a --- /dev/null +++ b/base/test/android/javatests/src/org/chromium/base/test/util/PhoneOnly.java @@ -0,0 +1,19 @@ +// 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. + +package org.chromium.base.test.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation for specifying that the test should only be run on phones. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface PhoneOnly { + +} diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/ScalableTimeout.java b/base/test/android/javatests/src/org/chromium/base/test/util/ScalableTimeout.java new file mode 100644 index 0000000000..a107b32a91 --- /dev/null +++ b/base/test/android/javatests/src/org/chromium/base/test/util/ScalableTimeout.java @@ -0,0 +1,28 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.base.test.util; + +/** + * Utility class for scaling various timeouts by a common factor. + * For example, to run tests under Valgrind, you might want the following: + * adb shell "echo 20.0 > /data/local/tmp/chrome_timeout_scale" + */ +public class ScalableTimeout { + private static Double sTimeoutScale = null; + private static final String PROPERTY_FILE = "/data/local/tmp/chrome_timeout_scale"; + + public static long ScaleTimeout(long timeout) { + if (sTimeoutScale == null) { + try { + char[] data = TestFileUtil.readUtf8File(PROPERTY_FILE, 32); + sTimeoutScale = Double.parseDouble(new String(data)); + } catch (Exception e) { + // NumberFormatException, FileNotFoundException, IOException + sTimeoutScale = 1.0; + } + } + return (long)(timeout * sTimeoutScale); + } +} diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/TabletOnly.java b/base/test/android/javatests/src/org/chromium/base/test/util/TabletOnly.java new file mode 100644 index 0000000000..36cdf8130c --- /dev/null +++ b/base/test/android/javatests/src/org/chromium/base/test/util/TabletOnly.java @@ -0,0 +1,19 @@ +// 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. + +package org.chromium.base.test.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation for specifying that the test should only be run on tablets. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface TabletOnly { + +} diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/TestFileUtil.java b/base/test/android/javatests/src/org/chromium/base/test/util/TestFileUtil.java new file mode 100644 index 0000000000..1afbc11adb --- /dev/null +++ b/base/test/android/javatests/src/org/chromium/base/test/util/TestFileUtil.java @@ -0,0 +1,77 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.base.test.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import java.util.Arrays; + +/** + * Utility class for dealing with files for test. + */ +public class TestFileUtil { + public static void createNewHtmlFile(String name, String title, String body) + throws IOException { + File file = new File(name); + if (!file.createNewFile()) { + throw new IOException("File \"" + name + "\" already exists"); + } + + Writer writer = null; + try { + writer = new OutputStreamWriter(new FileOutputStream(file), "UTF-8"); + writer.write("" + + "" + title + "" + + "" + + (body != null ? body : "") + + "" + + ""); + } finally { + if (writer != null) { + writer.close(); + } + } + } + + public static void deleteFile(String name) { + File file = new File(name); + file.delete(); + } + + /** + * @param fileName the file to read in. + * @param sizeLimit cap on the file size: will throw an exception if exceeded + * @return Array of chars read from the file + * @throws FileNotFoundException file does not exceed + * @throws IOException error encountered accessing the file + */ + public static char[] readUtf8File(String fileName, int sizeLimit) throws + FileNotFoundException, IOException { + Reader reader = null; + try { + File f = new File(fileName); + if (f.length() > sizeLimit) { + throw new IOException("File " + fileName + " length " + f.length() + + " exceeds limit " + sizeLimit); + } + char[] buffer = new char[(int) f.length()]; + reader = new InputStreamReader(new FileInputStream(f), "UTF-8"); + int charsRead = reader.read(buffer); + // Debug check that we've exhausted the input stream (will fail e.g. if the + // file grew after we inspected its length). + assert !reader.ready(); + return charsRead < buffer.length ? Arrays.copyOfRange(buffer, 0, charsRead) : buffer; + } finally { + if (reader != null) reader.close(); + } + } +} diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/TestThread.java b/base/test/android/javatests/src/org/chromium/base/test/util/TestThread.java new file mode 100644 index 0000000000..11e6afdecd --- /dev/null +++ b/base/test/android/javatests/src/org/chromium/base/test/util/TestThread.java @@ -0,0 +1,143 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.base.test.util; + +import android.os.Handler; +import android.os.Looper; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * This class is usefull when writing instrumentation tests that exercise code that posts tasks + * (to the same thread). + * Since the test code is run in a single thread, the posted tasks are never executed. + * The TestThread class lets you run that code on a specific thread synchronously and flush the + * message loop on that thread. + * + * Example of test using this: + * + * public void testMyAwesomeClass() { + * TestThread testThread = new TestThread(); + * testThread.startAndWaitForReadyState(); + * + * testThread.runOnTestThreadSyncAndProcessPendingTasks(new Runnable() { + * @Override + * public void run() { + * MyAwesomeClass.doStuffAsync(); + * } + * }); + * // Once we get there we know doStuffAsync has been executed and all the tasks it posted. + * assertTrue(MyAwesomeClass.stuffWasDone()); + * } + * + * Notes: + * - this is only for tasks posted to the same thread. Anyway if you were posting to a different + * thread, you'd probably need to set that other thread up. + * - this only supports tasks posted using Handler.post(), it won't work with postDelayed and + * postAtTime. + * - if your test instanciates an object and that object is the one doing the posting of tasks, you + * probably want to instanciate it on the test thread as it might create the Handler it posts + * tasks to in the constructor. + */ + +public class TestThread extends Thread { + private Object mThreadReadyLock; + private AtomicBoolean mThreadReady; + private Handler mMainThreadHandler; + private Handler mTestThreadHandler; + + public TestThread() { + mMainThreadHandler = new Handler(); + // We can't use the AtomicBoolean as the lock or findbugs will freak out... + mThreadReadyLock = new Object(); + mThreadReady = new AtomicBoolean(); + } + + @Override + public void run() { + Looper.prepare(); + mTestThreadHandler = new Handler(); + mTestThreadHandler.post(new Runnable() { + @Override + public void run() { + synchronized (mThreadReadyLock) { + mThreadReady.set(true); + mThreadReadyLock.notify(); + } + } + }); + Looper.loop(); + } + + /** + * Starts this TestThread and blocks until it's ready to accept calls. + */ + public void startAndWaitForReadyState() { + checkOnMainThread(); + start(); + synchronized (mThreadReadyLock) { + try { + // Note the mThreadReady and while are not really needed. + // There are there so findbugs don't report warnings. + while (!mThreadReady.get()) { + mThreadReadyLock.wait(); + } + } catch (InterruptedException ie) { + System.err.println("Error starting TestThread."); + ie.printStackTrace(); + } + } + } + + /** + * Runs the passed Runnable synchronously on the TestThread and returns when all pending + * runnables have been excuted. + * Should be called from the main thread. + */ + public void runOnTestThreadSyncAndProcessPendingTasks(Runnable r) { + checkOnMainThread(); + + runOnTestThreadSync(r); + + // Run another task, when it's done it means all pendings tasks have executed. + runOnTestThreadSync(null); + } + + /** + * Runs the passed Runnable on the test thread and blocks until it has finished executing. + * Should be called from the main thread. + * @param r The runnable to be executed. + */ + public void runOnTestThreadSync(final Runnable r) { + checkOnMainThread(); + final Object lock = new Object(); + // Task executed is not really needed since we are only on one thread, it is here to appease + // findbugs. + final AtomicBoolean taskExecuted = new AtomicBoolean(); + mTestThreadHandler.post(new Runnable() { + @Override + public void run() { + if (r != null) r.run(); + synchronized (lock) { + taskExecuted.set(true); + lock.notify(); + } + } + }); + synchronized (lock) { + try { + while (!taskExecuted.get()) { + lock.wait(); + } + } catch (InterruptedException ie) { + ie.printStackTrace(); + } + } + } + + private void checkOnMainThread() { + assert Looper.myLooper() == mMainThreadHandler.getLooper(); + } +} diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/TimeoutScale.java b/base/test/android/javatests/src/org/chromium/base/test/util/TimeoutScale.java new file mode 100644 index 0000000000..a3b0641cf2 --- /dev/null +++ b/base/test/android/javatests/src/org/chromium/base/test/util/TimeoutScale.java @@ -0,0 +1,22 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.base.test.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation can be used to scale a specific test timeout. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface TimeoutScale { + /** + * @return A number to scale the test timeout. + */ + public int value(); +} diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/UrlUtils.java b/base/test/android/javatests/src/org/chromium/base/test/util/UrlUtils.java new file mode 100644 index 0000000000..767c87256b --- /dev/null +++ b/base/test/android/javatests/src/org/chromium/base/test/util/UrlUtils.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.test.util; + +import org.chromium.base.PathUtils; + +import junit.framework.Assert; + +/** + * Collection of URL utilities. + */ +public class UrlUtils { + private final static String DATA_DIR = "/chrome/test/data/"; + + /** + * Construct a suitable URL for loading a test data file. + * @param path Pathname relative to external/chrome/testing/data + */ + public static String getTestFileUrl(String path) { + return "file://" + PathUtils.getExternalStorageDirectory() + DATA_DIR + path; + } + + /** + * Construct a data:text/html URI for loading from an inline HTML. + * @param html An unencoded HTML + * @return String An URI that contains the given HTML + */ + public static String encodeHtmlDataUri(String html) { + try { + // URLEncoder encodes into application/x-www-form-encoded, so + // ' '->'+' needs to be undone and replaced with ' '->'%20' + // to match the Data URI requirements. + String encoded = + "data:text/html;utf-8," + + java.net.URLEncoder.encode(html, "UTF-8"); + encoded = encoded.replace("+", "%20"); + return encoded; + } catch (java.io.UnsupportedEncodingException e) { + Assert.fail("Unsupported encoding: " + e.getMessage()); + return null; + } + } +} diff --git a/base/test/data/file_util/binary_file.bin b/base/test/data/file_util/binary_file.bin new file mode 100644 index 0000000000..f53cc8283a Binary files /dev/null and b/base/test/data/file_util/binary_file.bin differ diff --git a/base/test/data/file_util/binary_file_diff.bin b/base/test/data/file_util/binary_file_diff.bin new file mode 100644 index 0000000000..103b26d093 Binary files /dev/null and b/base/test/data/file_util/binary_file_diff.bin differ diff --git a/base/test/data/file_util/binary_file_same.bin b/base/test/data/file_util/binary_file_same.bin new file mode 100644 index 0000000000..f53cc8283a Binary files /dev/null and b/base/test/data/file_util/binary_file_same.bin differ diff --git a/base/test/data/file_util/blank_line.txt b/base/test/data/file_util/blank_line.txt new file mode 100644 index 0000000000..88920698cf --- /dev/null +++ b/base/test/data/file_util/blank_line.txt @@ -0,0 +1,3 @@ +The next line is blank. + +But this one isn't. diff --git a/base/test/data/file_util/blank_line_crlf.txt b/base/test/data/file_util/blank_line_crlf.txt new file mode 100644 index 0000000000..3aefe52fec --- /dev/null +++ b/base/test/data/file_util/blank_line_crlf.txt @@ -0,0 +1,3 @@ +The next line is blank. + +But this one isn't. diff --git a/base/test/data/file_util/crlf.txt b/base/test/data/file_util/crlf.txt new file mode 100644 index 0000000000..0e62728660 --- /dev/null +++ b/base/test/data/file_util/crlf.txt @@ -0,0 +1 @@ +This file is the same. diff --git a/base/test/data/file_util/different.txt b/base/test/data/file_util/different.txt new file mode 100644 index 0000000000..5b9f9c4312 --- /dev/null +++ b/base/test/data/file_util/different.txt @@ -0,0 +1 @@ +This file is different. diff --git a/base/test/data/file_util/different_first.txt b/base/test/data/file_util/different_first.txt new file mode 100644 index 0000000000..8661d6646d --- /dev/null +++ b/base/test/data/file_util/different_first.txt @@ -0,0 +1 @@ +this file is the same. diff --git a/base/test/data/file_util/different_last.txt b/base/test/data/file_util/different_last.txt new file mode 100644 index 0000000000..e8b3e5af56 --- /dev/null +++ b/base/test/data/file_util/different_last.txt @@ -0,0 +1 @@ +This file is the same. \ No newline at end of file diff --git a/base/test/data/file_util/empty1.txt b/base/test/data/file_util/empty1.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/base/test/data/file_util/empty2.txt b/base/test/data/file_util/empty2.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/base/test/data/file_util/first1.txt b/base/test/data/file_util/first1.txt new file mode 100644 index 0000000000..2c6e3001b1 --- /dev/null +++ b/base/test/data/file_util/first1.txt @@ -0,0 +1,2 @@ +The first line is the same. +The second line is different. diff --git a/base/test/data/file_util/first2.txt b/base/test/data/file_util/first2.txt new file mode 100644 index 0000000000..e39b5ec292 --- /dev/null +++ b/base/test/data/file_util/first2.txt @@ -0,0 +1,2 @@ +The first line is the same. +The second line is not. diff --git a/base/test/data/file_util/original.txt b/base/test/data/file_util/original.txt new file mode 100644 index 0000000000..4422f5754a --- /dev/null +++ b/base/test/data/file_util/original.txt @@ -0,0 +1 @@ +This file is the same. diff --git a/base/test/data/file_util/same.txt b/base/test/data/file_util/same.txt new file mode 100644 index 0000000000..4422f5754a --- /dev/null +++ b/base/test/data/file_util/same.txt @@ -0,0 +1 @@ +This file is the same. diff --git a/base/test/data/file_util/same_length.txt b/base/test/data/file_util/same_length.txt new file mode 100644 index 0000000000..157405cd19 --- /dev/null +++ b/base/test/data/file_util/same_length.txt @@ -0,0 +1 @@ +This file is not same. diff --git a/base/test/data/file_util/shortened.txt b/base/test/data/file_util/shortened.txt new file mode 100644 index 0000000000..2bee82ca1b --- /dev/null +++ b/base/test/data/file_util/shortened.txt @@ -0,0 +1 @@ +This file is the \ No newline at end of file diff --git a/base/test/data/json/bom_feff.json b/base/test/data/json/bom_feff.json new file mode 100644 index 0000000000..b05ae5083e --- /dev/null +++ b/base/test/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/base/test/data/prefs/invalid.json b/base/test/data/prefs/invalid.json new file mode 100644 index 0000000000..43392a92fb --- /dev/null +++ b/base/test/data/prefs/invalid.json @@ -0,0 +1 @@ +!@#$%^& \ No newline at end of file diff --git a/base/test/data/prefs/read.json b/base/test/data/prefs/read.json new file mode 100644 index 0000000000..ea578a47f4 --- /dev/null +++ b/base/test/data/prefs/read.json @@ -0,0 +1,8 @@ +{ + "homepage": "http://www.cnn.com", + "some_directory": "/usr/local/", + "tabs": { + "new_windows_in_tabs": true, + "max_tabs": 20 + } +} diff --git a/base/test/data/prefs/read.need_empty_value.json b/base/test/data/prefs/read.need_empty_value.json new file mode 100644 index 0000000000..48e1749448 --- /dev/null +++ b/base/test/data/prefs/read.need_empty_value.json @@ -0,0 +1,10 @@ +{ + "list": [ 1 ], + "list_needs_empty_value": [ 2 ], + "dict": { + "dummy": true + }, + "dict_needs_empty_value": { + "dummy": true + } +} diff --git a/base/test/data/prefs/write.golden.json b/base/test/data/prefs/write.golden.json new file mode 100644 index 0000000000..9a5523c730 --- /dev/null +++ b/base/test/data/prefs/write.golden.json @@ -0,0 +1,11 @@ +{ + "homepage": "http://www.cnn.com", + "long_int": { + "pref": "214748364842" + }, + "some_directory": "/usr/sbin/", + "tabs": { + "max_tabs": 10, + "new_windows_in_tabs": false + } +} diff --git a/base/test/data/prefs/write.golden.need_empty_value.json b/base/test/data/prefs/write.golden.need_empty_value.json new file mode 100644 index 0000000000..fa79590f62 --- /dev/null +++ b/base/test/data/prefs/write.golden.need_empty_value.json @@ -0,0 +1,6 @@ +{ + "dict_needs_empty_value": { + + }, + "list_needs_empty_value": [ ] +} diff --git a/base/test/data/serializer_nested_test.json b/base/test/data/serializer_nested_test.json new file mode 100644 index 0000000000..cfea8e86a9 --- /dev/null +++ b/base/test/data/serializer_nested_test.json @@ -0,0 +1,17 @@ +{ + "bool": true, + "dict": { + "bool": true, + "dict": { + "bees": "knees", + "cats": "meow" + }, + "foos": "bar", + "list": [ 3.4, "second", null ] + }, + "int": 42, + "list": [ 1, 2 ], + "null": null, + "real": 3.14, + "string": "hello" +} diff --git a/base/test/data/serializer_test.json b/base/test/data/serializer_test.json new file mode 100644 index 0000000000..446925ea7b --- /dev/null +++ b/base/test/data/serializer_test.json @@ -0,0 +1,8 @@ +{ + "bool": true, + "int": 42, + "list": [ 1, 2 ], + "null": null, + "real": 3.14, + "string": "hello" +} diff --git a/base/test/data/serializer_test_nowhitespace.json b/base/test/data/serializer_test_nowhitespace.json new file mode 100644 index 0000000000..a1afdc5830 --- /dev/null +++ b/base/test/data/serializer_test_nowhitespace.json @@ -0,0 +1 @@ +{"bool":true,"int":42,"list":[1,2],"null":null,"real":3.14,"string":"hello"} \ No newline at end of file diff --git a/base/test/expectations/OWNERS b/base/test/expectations/OWNERS new file mode 100644 index 0000000000..14fce2ae68 --- /dev/null +++ b/base/test/expectations/OWNERS @@ -0,0 +1 @@ +rsesek@chromium.org diff --git a/base/test/expectations/expectation.cc b/base/test/expectations/expectation.cc new file mode 100644 index 0000000000..31a387eba3 --- /dev/null +++ b/base/test/expectations/expectation.cc @@ -0,0 +1,157 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/expectations/expectation.h" + +#include "base/logging.h" + +#if defined(OS_WIN) +#include "base/win/windows_version.h" +#elif defined(OS_MACOSX) && !defined(OS_IOS) +#include "base/mac/mac_util.h" +#elif defined(OS_LINUX) +#include "base/sys_info.h" +#endif + +namespace test_expectations { + +bool ResultFromString(const base::StringPiece& result, Result* out_result) { + if (result == "Failure") + *out_result = RESULT_FAILURE; + else if (result == "Timeout") + *out_result = RESULT_TIMEOUT; + else if (result == "Crash") + *out_result = RESULT_CRASH; + else if (result == "Skip") + *out_result = RESULT_SKIP; + else if (result == "Pass") + *out_result = RESULT_PASS; + else + return false; + + return true; +} + +static bool IsValidPlatform(const Platform* platform) { + const std::string& name = platform->name; + const std::string& variant = platform->variant; + + if (name == "Win") { + if (variant != "" && + variant != "XP" && + variant != "Vista" && + variant != "7" && + variant != "8") { + return false; + } + } else if (name == "Mac") { + if (variant != "" && + variant != "10.6" && + variant != "10.7" && + variant != "10.8") { + return false; + } + } else if (name == "Linux") { + if (variant != "" && + variant != "32" && + variant != "64") { + return false; + } + } else if (name == "ChromeOS") { + // TODO(rsesek): Figure out what ChromeOS needs. + } else if (name == "iOS") { + // TODO(rsesek): Figure out what iOS needs. Probably Device and Simulator. + } else if (name == "Android") { + // TODO(rsesek): Figure out what Android needs. + } else { + return false; + } + + return true; +} + +bool PlatformFromString(const base::StringPiece& modifier, + Platform* out_platform) { + size_t sep = modifier.find('-'); + if (sep == std::string::npos) { + out_platform->name = modifier.as_string(); + out_platform->variant.clear(); + } else { + out_platform->name = modifier.substr(0, sep).as_string(); + out_platform->variant = modifier.substr(sep + 1).as_string(); + } + + return IsValidPlatform(out_platform); +} + +Platform GetCurrentPlatform() { + Platform platform; +#if defined(OS_WIN) + platform.name = "Win"; + base::win::Version version = base::win::GetVersion(); + if (version == base::win::VERSION_XP) + platform.variant = "XP"; + else if (version == base::win::VERSION_VISTA) + platform.variant = "Vista"; + else if (version == base::win::VERSION_WIN7) + platform.variant = "7"; + else if (version == base::win::VERSION_WIN8) + platform.variant = "8"; +#elif defined(OS_IOS) + platform.name = "iOS"; +#elif defined(OS_MACOSX) + platform.name = "Mac"; + if (base::mac::IsOSSnowLeopard()) + platform.variant = "10.6"; + else if (base::mac::IsOSLion()) + platform.variant = "10.7"; + else if (base::mac::IsOSMountainLion()) + platform.variant = "10.8"; +#elif defined(OS_CHROMEOS) + platform.name = "ChromeOS"; +#elif defined(OS_ANDROID) + platform.name = "Android"; +#elif defined(OS_LINUX) + platform.name = "Linux"; + std::string arch = base::SysInfo::OperatingSystemArchitecture(); + if (arch == "x86") + platform.variant = "32"; + else if (arch == "x86_64") + platform.variant = "64"; +#else + NOTREACHED(); +#endif + return platform; +} + +bool ConfigurationFromString(const base::StringPiece& modifier, + Configuration* out_configuration) { + if (modifier == "Debug") + *out_configuration = CONFIGURATION_DEBUG; + else if (modifier == "Release") + *out_configuration = CONFIGURATION_RELEASE; + else + return false; + + return true; +} + +Configuration GetCurrentConfiguration() { +#if NDEBUG + return CONFIGURATION_RELEASE; +#else + return CONFIGURATION_DEBUG; +#endif + NOTREACHED(); + return CONFIGURATION_UNSPECIFIED; +} + +Expectation::Expectation() + : configuration(CONFIGURATION_UNSPECIFIED), + result(RESULT_PASS) { +} + +Expectation::~Expectation() {} + +} // namespace test_expectations diff --git a/base/test/expectations/expectation.h b/base/test/expectations/expectation.h new file mode 100644 index 0000000000..be5a9d7ff1 --- /dev/null +++ b/base/test/expectations/expectation.h @@ -0,0 +1,94 @@ +// 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_TEST_EXPECTATIONS_EXPECTATION_H_ +#define BASE_TEST_EXPECTATIONS_EXPECTATION_H_ + +#include +#include + +#include "base/base_export.h" +#include "base/compiler_specific.h" +#include "base/strings/string_piece.h" + +namespace test_expectations { + +// A Result is the expectation of a test's behavior. +enum Result { + // The test has a failing assertion. + RESULT_FAILURE, + + // The test does not complete within the test runner's alloted duration. + RESULT_TIMEOUT, + + // The test crashes during the course of its execution. + RESULT_CRASH, + + // The test should not be run ever. + RESULT_SKIP, + + // The test passes, used to override a more general expectation. + RESULT_PASS, +}; + +// Converts a text string form of a |result| to its enum value, written to +// |out_result|. Returns true on success and false on error. +bool ResultFromString(const base::StringPiece& result, + Result* out_result) WARN_UNUSED_RESULT; + +// A Platform stores information about the OS environment. +struct Platform { + // The name of the platform. E.g., "Win", or "Mac". + std::string name; + + // The variant of the platform, either an OS version like "XP" or "10.8", or + // "Device" or "Simulator" in the case of mobile. + std::string variant; +}; + +// Converts a text string |modifier| to a Platform struct, written to +// |out_platform|. Returns true on success and false on failure. +bool PlatformFromString(const base::StringPiece& modifier, + Platform* out_platform) WARN_UNUSED_RESULT; + +// Returns the Platform for the currently running binary. +Platform GetCurrentPlatform(); + +// The build configuration. +enum Configuration { + CONFIGURATION_UNSPECIFIED, + CONFIGURATION_DEBUG, + CONFIGURATION_RELEASE, +}; + +// Converts the |modifier| to a Configuration constant, writing the value to +// |out_configuration|. Returns true on success or false on failure. +bool ConfigurationFromString(const base::StringPiece& modifier, + Configuration* out_configuration) WARN_UNUSED_RESULT; + +// Returns the Configuration for the currently running binary. +Configuration GetCurrentConfiguration(); + +// An Expectation is records what the result for a given test name should be on +// the specified platforms and configuration. +struct Expectation { + Expectation(); + ~Expectation(); + + // The name of the test, like FooBarTest.BarIsBaz. + std::string test_name; + + // The set of platforms for which this expectation is applicable. + std::vector platforms; + + // The build configuration. + Configuration configuration; + + // The expected result of this test. + Result result; +}; + +} // namespace test_expectations + +#endif // BASE_TEST_EXPECTATIONS_EXPECTATION_H_ diff --git a/base/test/expectations/expectation_unittest.cc b/base/test/expectations/expectation_unittest.cc new file mode 100644 index 0000000000..8a7af71a97 --- /dev/null +++ b/base/test/expectations/expectation_unittest.cc @@ -0,0 +1,120 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/expectations/expectation.h" + +#include "base/basictypes.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(TestExpectationsFunctionsTest, ResultFromString) { + test_expectations::Result result = test_expectations::RESULT_PASS; + + EXPECT_TRUE(ResultFromString("Failure", &result)); + EXPECT_EQ(test_expectations::RESULT_FAILURE, result); + + EXPECT_TRUE(ResultFromString("Timeout", &result)); + EXPECT_EQ(test_expectations::RESULT_TIMEOUT, result); + + EXPECT_TRUE(ResultFromString("Crash", &result)); + EXPECT_EQ(test_expectations::RESULT_CRASH, result); + + EXPECT_TRUE(ResultFromString("Skip", &result)); + EXPECT_EQ(test_expectations::RESULT_SKIP, result); + + EXPECT_TRUE(ResultFromString("Pass", &result)); + EXPECT_EQ(test_expectations::RESULT_PASS, result); + + // Case sensitive. + EXPECT_FALSE(ResultFromString("failure", &result)); + EXPECT_EQ(test_expectations::RESULT_PASS, result); +} + +TEST(TestExpectationsFunctionsTest, ConfigurationFromString) { + test_expectations::Configuration config = + test_expectations::CONFIGURATION_UNSPECIFIED; + + EXPECT_TRUE(ConfigurationFromString("Debug", &config)); + EXPECT_EQ(test_expectations::CONFIGURATION_DEBUG, config); + + EXPECT_TRUE(ConfigurationFromString("Release", &config)); + EXPECT_EQ(test_expectations::CONFIGURATION_RELEASE, config); + + EXPECT_FALSE(ConfigurationFromString("NotAConfig", &config)); + EXPECT_EQ(test_expectations::CONFIGURATION_RELEASE, config); + + // Case sensitive. + EXPECT_FALSE(ConfigurationFromString("debug", &config)); + EXPECT_EQ(test_expectations::CONFIGURATION_RELEASE, config); +} + +TEST(TestExpectationsFunctionsTest, PlatformFromString) { + test_expectations::Platform platform; + + EXPECT_TRUE(PlatformFromString("Win", &platform)); + EXPECT_EQ("Win", platform.name); + EXPECT_EQ("", platform.variant); + + EXPECT_TRUE(PlatformFromString("Mac-10.6", &platform)); + EXPECT_EQ("Mac", platform.name); + EXPECT_EQ("10.6", platform.variant); + + EXPECT_TRUE(PlatformFromString("ChromeOS", &platform)); + EXPECT_EQ("ChromeOS", platform.name); + EXPECT_EQ("", platform.variant); + + EXPECT_TRUE(PlatformFromString("Linux-", &platform)); + EXPECT_EQ("Linux", platform.name); + EXPECT_EQ("", platform.variant); + + EXPECT_FALSE(PlatformFromString("", &platform)); +} + +TEST(TestExpectationsFunctionsTest, IsValidPlatform) { + const char* kValidPlatforms[] = { + "Win", + "Win-XP", + "Win-Vista", + "Win-7", + "Win-8", + "Mac", + "Mac-10.6", + "Mac-10.7", + "Mac-10.8", + "Linux", + "Linux-32", + "Linux-64", + "ChromeOS", + "iOS", + "Android", + }; + + const char* kInvalidPlatforms[] = { + "Solaris", + "Plan9", + }; + + for (size_t i = 0; i < arraysize(kValidPlatforms); ++i) { + test_expectations::Platform platform; + EXPECT_TRUE(test_expectations::PlatformFromString( + kValidPlatforms[i], &platform)) << kValidPlatforms[i]; + } + + for (size_t i = 0; i < arraysize(kInvalidPlatforms); ++i) { + test_expectations::Platform platform; + EXPECT_FALSE(test_expectations::PlatformFromString( + kInvalidPlatforms[i], &platform)) << kInvalidPlatforms[i]; + } +} + +TEST(TestExpectationsFunctionsTest, CurrentPlatform) { + test_expectations::Platform current = + test_expectations::GetCurrentPlatform(); + EXPECT_FALSE(current.name.empty()); +} + +TEST(TestExpectationsFunctionsTest, CurrentConfiguration) { + test_expectations::Configuration current = + test_expectations::GetCurrentConfiguration(); + EXPECT_NE(test_expectations::CONFIGURATION_UNSPECIFIED, current); +} diff --git a/base/test/expectations/parser.cc b/base/test/expectations/parser.cc new file mode 100644 index 0000000000..c7132e5d48 --- /dev/null +++ b/base/test/expectations/parser.cc @@ -0,0 +1,201 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/expectations/parser.h" + +#include "base/strings/string_util.h" + +namespace test_expectations { + +Parser::Parser(Delegate* delegate, const std::string& input) + : delegate_(delegate), + input_(input), + pos_(NULL), + end_(NULL), + line_number_(0), + data_error_(false) { +} + +Parser::~Parser() { +} + +void Parser::Parse() { + pos_ = &input_[0]; + end_ = pos_ + input_.length(); + + line_number_ = 1; + + StateFuncPtr state = &Parser::Start; + while (state) { + state = (this->*state)(); + } +} + +inline bool Parser::HasNext() { + return pos_ < end_; +} + +Parser::StateFunc Parser::Start() { + // If at the start of a line is whitespace, skip it and arrange to come back + // here. + if (IsAsciiWhitespace(*pos_)) + return SkipWhitespaceAndNewLines(&Parser::Start); + + // Handle comments at the start of lines. + if (*pos_ == '#') + return &Parser::ParseComment; + + // After arranging to come back here from skipping whitespace and comments, + // the parser may be at the end of the input. + if (pos_ >= end_) + return NULL; + + current_ = Expectation(); + data_error_ = false; + + return &Parser::ParseBugURL; +} + +Parser::StateFunc Parser::ParseComment() { + if (*pos_ != '#') + return SyntaxError("Invalid start of comment"); + + do { + ++pos_; + } while (HasNext() && *pos_ != '\n'); + + return &Parser::Start; +} + +Parser::StateFunc Parser::ParseBugURL() { + return SkipWhitespace(ExtractString( + &Parser::BeginModifiers)); +} + +Parser::StateFunc Parser::BeginModifiers() { + if (*pos_ != '[' || !HasNext()) + return SyntaxError("Expected '[' for start of modifiers"); + + ++pos_; + return SkipWhitespace(&Parser::InModifiers); +} + +Parser::StateFunc Parser::InModifiers() { + if (*pos_ == ']') + return &Parser::EndModifiers; + + return ExtractString(SkipWhitespace( + &Parser::SaveModifier)); +} + +Parser::StateFunc Parser::SaveModifier() { + if (extracted_string_.empty()) + return SyntaxError("Invalid modifier list"); + + Configuration config; + if (ConfigurationFromString(extracted_string_, &config)) { + if (current_.configuration != CONFIGURATION_UNSPECIFIED) + DataError("Cannot use more than one configuration modifier"); + else + current_.configuration = config; + } else { + Platform platform; + if (PlatformFromString(extracted_string_, &platform)) + current_.platforms.push_back(platform); + else + DataError("Invalid modifier string"); + } + + return SkipWhitespace(&Parser::InModifiers); +} + +Parser::StateFunc Parser::EndModifiers() { + if (*pos_ != ']' || !HasNext()) + return SyntaxError("Expected ']' for end of modifiers list"); + + ++pos_; + return SkipWhitespace(&Parser::ParseTestName); +} + +Parser::StateFunc Parser::ParseTestName() { + return ExtractString(&Parser::SaveTestName); +} + +Parser::StateFunc Parser::SaveTestName() { + if (extracted_string_.empty()) + return SyntaxError("Invalid test name"); + + current_.test_name = extracted_string_.as_string(); + return SkipWhitespace(&Parser::ParseExpectation); +} + +Parser::StateFunc Parser::ParseExpectation() { + if (*pos_ != '=' || !HasNext()) + return SyntaxError("Expected '=' for expectation result"); + + ++pos_; + return SkipWhitespace(&Parser::ParseExpectationType); +} + +Parser::StateFunc Parser::ParseExpectationType() { + return ExtractString(&Parser::SaveExpectationType); +} + +Parser::StateFunc Parser::SaveExpectationType() { + if (!ResultFromString(extracted_string_, ¤t_.result)) + DataError("Unknown expectation type"); + + return SkipWhitespace(&Parser::End); +} + +Parser::StateFunc Parser::End() { + if (!data_error_) + delegate_->EmitExpectation(current_); + + if (HasNext()) + return SkipWhitespaceAndNewLines(&Parser::Start); + + return NULL; +} + +Parser::StateFunc Parser::ExtractString(StateFunc success) { + const char* start = pos_; + while (!IsAsciiWhitespace(*pos_) && *pos_ != ']' && HasNext()) { + ++pos_; + if (*pos_ == '#') { + return SyntaxError("Unexpected start of comment"); + } + } + extracted_string_ = base::StringPiece(start, pos_ - start); + return success; +} + +Parser::StateFunc Parser::SkipWhitespace(Parser::StateFunc next) { + while ((*pos_ == ' ' || *pos_ == '\t') && HasNext()) { + ++pos_; + } + return next; +} + +Parser::StateFunc Parser::SkipWhitespaceAndNewLines(Parser::StateFunc next) { + while (IsAsciiWhitespace(*pos_) && HasNext()) { + if (*pos_ == '\n') { + ++line_number_; + } + ++pos_; + } + return next; +} + +Parser::StateFunc Parser::SyntaxError(const std::string& message) { + delegate_->OnSyntaxError(message); + return NULL; +} + +void Parser::DataError(const std::string& error) { + data_error_ = true; + delegate_->OnDataError(error); +} + +} // namespace test_expectations diff --git a/base/test/expectations/parser.h b/base/test/expectations/parser.h new file mode 100644 index 0000000000..69a741a852 --- /dev/null +++ b/base/test/expectations/parser.h @@ -0,0 +1,143 @@ +// 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_TEST_EXPECTATIONS_PARSER_H_ +#define BASE_TEST_EXPECTATIONS_PARSER_H_ + +#include + +#include "base/basictypes.h" +#include "base/strings/string_piece.h" +#include "base/test/expectations/expectation.h" + +namespace test_expectations { + +// This is the internal parser for test expectations. It parses an input +// string and reports information to its Delegate as it's processing the +// input. +// +// The input format is documented here: +// https://docs.google.com/a/chromium.org/document/d/1edhMJ5doY_dzfbKNCzeJJ-8XxPrexTbNL2Y_jVvLB8Q/view +// +// Basic format: +// "http://bug/1234 [ OS-Version ] Test.Name = Result" +// +// The parser is implemented as a state machine, with each state returning a +// function pointer to the next state. +class Parser { + public: + // The parser will call these methods on its delegate during a Parse() + // operation. + class Delegate { + public: + // When a well-formed and valid Expectation has been parsed from the input, + // it is reported to the delegate via this method. + virtual void EmitExpectation(const Expectation& expectation) = 0; + + // Called when the input string is not well-formed. Parsing will stop after + // this method is called. + virtual void OnSyntaxError(const std::string& message) = 0; + + // Called when an Expectation has been parsed because it is well-formed but + // contains invalid data (i.e. the modifiers or result are not valid + // keywords). This Expectation will not be reported via EmitExpectation. + virtual void OnDataError(const std::string& message) = 0; + }; + + // Creates a new parser for |input| that will send data to |delegate|. + Parser(Delegate* delegate, const std::string& input); + ~Parser(); + + // Runs the parser of the input string. + void Parse(); + + private: + // This bit of hackery is used to implement a function pointer type that + // returns a pointer to a function of the same signature. Since a definition + // like that is inherently recursive, it's impossible to do: + // type StateFunc(*StateFunc)(StateData*); + // However, this approach works without the need to use void*. Inspired by + // . + struct StateFunc; + typedef StateFunc(Parser::*StateFuncPtr)(); + struct StateFunc { + StateFunc(StateFuncPtr pf) : pf_(pf) {} + operator StateFuncPtr() { + return pf_; + } + StateFuncPtr pf_; + }; + + // Tests whether there is at least one more character at pos_ before end_. + bool HasNext(); + + // The parser state functions. On entry, the parser state is at the beginning + // of the token. Each returns a function pointer to the next state function, + // or NULL to end parsing. On return, the parser is at the beginning of the + // next token. + StateFunc Start(); + StateFunc ParseComment(); + StateFunc ParseBugURL(); + StateFunc BeginModifiers(); + StateFunc InModifiers(); + StateFunc SaveModifier(); + StateFunc EndModifiers(); + StateFunc ParseTestName(); + StateFunc SaveTestName(); + StateFunc ParseExpectation(); + StateFunc ParseExpectationType(); + StateFunc SaveExpectationType(); + StateFunc End(); + + // A state function that collects character data from the current position + // to the next whitespace character. Returns the |success| function when at + // the end of the string, with the data stored in |extracted_string_|. + StateFunc ExtractString(StateFunc success); + + // Function that skips over horizontal whitespace characters and then returns + // the |next| state. + StateFunc SkipWhitespace(StateFunc next); + + // Does the same as SkipWhitespace but includes newlines. + StateFunc SkipWhitespaceAndNewLines(StateFunc next); + + // State function that reports the given syntax error |message| to the + // delegate and then returns NULL, ending the parse loop. + StateFunc SyntaxError(const std::string& message); + + // Function that reports the data |error| to the delegate without stopping + // parsing. + void DataError(const std::string& error); + + // Parser delegate. + Delegate* delegate_; + + // The input string. + std::string input_; + + // Current location in the |input_|. + const char* pos_; + + // Pointer to the end of the |input_|. + const char* end_; + + // Current line number, as updated by SkipWhitespace(). + int line_number_; + + // The character data extracted from |input_| as a result of the + // ExtractString() state. + base::StringPiece extracted_string_; + + // The Expectation object that is currently being processed by the parser. + // Reset in Start(). + Expectation current_; + + // If DataError() has been called during the course of parsing |current_|. + // If true, then |current_| will not be emitted to the Delegate. + bool data_error_; +}; + +} // namespace test_expectations + +#endif // BASE_TEST_EXPECTATIONS_PARSER_H_ diff --git a/base/test/expectations/parser_unittest.cc b/base/test/expectations/parser_unittest.cc new file mode 100644 index 0000000000..1c55a05f16 --- /dev/null +++ b/base/test/expectations/parser_unittest.cc @@ -0,0 +1,209 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/expectations/parser.h" + +#include +#include + +#include "base/compiler_specific.h" +#include "testing/gtest/include/gtest/gtest.h" + +using test_expectations::Parser; + +class TestExpectationParserTest : public testing::Test, + public Parser::Delegate { + public: + virtual void EmitExpectation( + const test_expectations::Expectation& expectation) OVERRIDE { + expectations_.push_back(expectation); + } + + virtual void OnSyntaxError(const std::string& message) OVERRIDE { + syntax_error_ = message; + } + + virtual void OnDataError(const std::string& error) OVERRIDE { + data_errors_.push_back(error); + } + + protected: + std::vector expectations_; + std::string syntax_error_; + std::vector data_errors_; +}; + +TEST_F(TestExpectationParserTest, Basic) { + Parser(this, + "http://crbug.com/1234 [ Win-8 ] DouglasTest.PoopsOk = Timeout"). + Parse(); + EXPECT_TRUE(syntax_error_.empty()); + EXPECT_EQ(0u, data_errors_.size()); + + ASSERT_EQ(1u, expectations_.size()); + EXPECT_EQ("DouglasTest.PoopsOk", expectations_[0].test_name); + EXPECT_EQ(test_expectations::RESULT_TIMEOUT, expectations_[0].result); + EXPECT_EQ(test_expectations::CONFIGURATION_UNSPECIFIED, + expectations_[0].configuration); + + ASSERT_EQ(1u, expectations_[0].platforms.size()); + EXPECT_EQ("Win", expectations_[0].platforms[0].name); + EXPECT_EQ("8", expectations_[0].platforms[0].variant); +} + +TEST_F(TestExpectationParserTest, MultiModifier) { + Parser(this, "BUG [ Win-XP Mac ] OhMy.MeOhMy = Failure").Parse(); + EXPECT_TRUE(syntax_error_.empty()); + EXPECT_EQ(0u, data_errors_.size()); + + ASSERT_EQ(1u, expectations_.size()); + EXPECT_EQ("OhMy.MeOhMy", expectations_[0].test_name); + EXPECT_EQ(test_expectations::RESULT_FAILURE, + expectations_[0].result); + EXPECT_EQ(test_expectations::CONFIGURATION_UNSPECIFIED, + expectations_[0].configuration); + + ASSERT_EQ(2u, expectations_[0].platforms.size()); + + EXPECT_EQ("Win", expectations_[0].platforms[0].name); + EXPECT_EQ("XP", expectations_[0].platforms[0].variant); + + EXPECT_EQ("Mac", expectations_[0].platforms[1].name); + EXPECT_EQ("", expectations_[0].platforms[1].variant); +} + +TEST_F(TestExpectationParserTest, EmptyModifier) { + Parser(this, + "BUG [] First.Test = Failure\n" + "BUG2 [ ] Second.Test = Crash").Parse(); + EXPECT_EQ(0u, data_errors_.size()); + + ASSERT_EQ(2u, expectations_.size()); + + EXPECT_EQ("First.Test", expectations_[0].test_name); + EXPECT_EQ(test_expectations::RESULT_FAILURE, + expectations_[0].result); + EXPECT_EQ(test_expectations::CONFIGURATION_UNSPECIFIED, + expectations_[0].configuration); + EXPECT_EQ(0u, expectations_[0].platforms.size()); + + EXPECT_EQ("Second.Test", expectations_[1].test_name); + EXPECT_EQ(test_expectations::RESULT_CRASH, + expectations_[1].result); + EXPECT_EQ(test_expectations::CONFIGURATION_UNSPECIFIED, + expectations_[1].configuration); + EXPECT_EQ(0u, expectations_[1].platforms.size()); +} + +TEST_F(TestExpectationParserTest, MultiLine) { + Parser(this, + "BUG [ Linux ] Line.First = Failure\n" + "\n" + "# A test comment.\n" + "BUG2 [ Release ] Line.Second = Skip").Parse(); + EXPECT_TRUE(syntax_error_.empty()); + EXPECT_EQ(0u, data_errors_.size()); + + ASSERT_EQ(2u, expectations_.size()); + EXPECT_EQ("Line.First", expectations_[0].test_name); + EXPECT_EQ(test_expectations::RESULT_FAILURE, expectations_[0].result); + EXPECT_EQ(test_expectations::CONFIGURATION_UNSPECIFIED, + expectations_[0].configuration); + + ASSERT_EQ(1u, expectations_[0].platforms.size()); + EXPECT_EQ("Linux", expectations_[0].platforms[0].name); + EXPECT_EQ("", expectations_[0].platforms[0].variant); + + EXPECT_EQ("Line.Second", expectations_[1].test_name); + EXPECT_EQ(test_expectations::RESULT_SKIP, expectations_[1].result); + EXPECT_EQ(test_expectations::CONFIGURATION_RELEASE, + expectations_[1].configuration); + EXPECT_EQ(0u, expectations_[1].platforms.size()); +} + +TEST_F(TestExpectationParserTest, MultiLineWithComments) { + Parser(this, + " # Comment for your thoughts\n" + " \t \n" + "BUG [ Mac-10.8 Debug] Foo=Bar =Skip # Why not another comment?\n" + "BUG2 [Win-XP\tWin-Vista ] Cow.GoesMoo =\tTimeout\n\n").Parse(); + EXPECT_TRUE(syntax_error_.empty()) << syntax_error_; + EXPECT_EQ(0u, data_errors_.size()); + + ASSERT_EQ(2u, expectations_.size()); + EXPECT_EQ("Foo=Bar", expectations_[0].test_name); + EXPECT_EQ(test_expectations::RESULT_SKIP, expectations_[0].result); + EXPECT_EQ(test_expectations::CONFIGURATION_DEBUG, + expectations_[0].configuration); + + ASSERT_EQ(1u, expectations_[0].platforms.size()); + EXPECT_EQ("Mac", expectations_[0].platforms[0].name); + EXPECT_EQ("10.8", expectations_[0].platforms[0].variant); + + EXPECT_EQ("Cow.GoesMoo", expectations_[1].test_name); + EXPECT_EQ(test_expectations::RESULT_TIMEOUT, expectations_[1].result); + EXPECT_EQ(test_expectations::CONFIGURATION_UNSPECIFIED, + expectations_[1].configuration); + + ASSERT_EQ(2u, expectations_[1].platforms.size()); + EXPECT_EQ("Win", expectations_[1].platforms[0].name); + EXPECT_EQ("XP", expectations_[1].platforms[0].variant); + EXPECT_EQ("Win", expectations_[1].platforms[0].name); + EXPECT_EQ("Vista", expectations_[1].platforms[1].variant); +} + +TEST_F(TestExpectationParserTest, WeirdSpaces) { + Parser(this, " BUG [Linux] Weird = Skip ").Parse(); + EXPECT_EQ(1u, expectations_.size()); + EXPECT_TRUE(syntax_error_.empty()); + EXPECT_EQ(0u, data_errors_.size()); +} + +TEST_F(TestExpectationParserTest, SyntaxErrors) { + const char* kErrors[] = { + "Foo [ dfasd", + "Foo [Linux] # This is an illegal comment", + "Foo [Linux] Bar # Another illegal comment.", + "Foo [Linux] Bar = # Another illegal comment.", + "Foo[Linux]Bar=Failure", + "Foo\n[Linux] Bar = Failure", + "Foo [\nLinux] Bar = Failure", + "Foo [Linux\n] Bar = Failure", + "Foo [ Linux ] \n Bar = Failure", + "Foo [ Linux ] Bar =\nFailure", + "Foo [ Linux \n ] Bar =\nFailure", + }; + + for (size_t i = 0; i < arraysize(kErrors); ++i) { + Parser(this, kErrors[i]).Parse(); + EXPECT_FALSE(syntax_error_.empty()) + << "Should have error for #" << i << ": " << kErrors[i]; + syntax_error_.clear(); + } +} + +TEST_F(TestExpectationParserTest, DataErrors) { + const char* kOneError[] = { + "http://crbug.com/1234 [MagicBrowzR] BadModifier = Timeout", + "________ [Linux] BadResult = WhatNow", + "http://wkb.ug/1234 [Debug Release Win-7] MultipleConfigs = Skip", + }; + + for (size_t i = 0; i < arraysize(kOneError); ++i) { + Parser(this, kOneError[i]).Parse(); + EXPECT_EQ(1u, data_errors_.size()) << kOneError[i]; + data_errors_.clear(); + } + + const char* kTwoErrors[] = { + ". [Mac-TurningIntoiOS] BadModifierVariant.BadResult = Foobar", + "1234 [ Debug Release OS/2 ] MultipleConfigs.BadModifier = Pass", + }; + + for (size_t i = 0; i < arraysize(kTwoErrors); ++i) { + Parser(this, kTwoErrors[i]).Parse(); + EXPECT_EQ(2u, data_errors_.size()) << kTwoErrors[i]; + data_errors_.clear(); + } +} diff --git a/base/test/mock_chrome_application_mac.h b/base/test/mock_chrome_application_mac.h new file mode 100644 index 0000000000..ffa3080aec --- /dev/null +++ b/base/test/mock_chrome_application_mac.h @@ -0,0 +1,33 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_MOCK_CHROME_APPLICATION_MAC_H_ +#define BASE_TEST_MOCK_CHROME_APPLICATION_MAC_H_ + +#if defined(__OBJC__) + +#import + +#include "base/mac/scoped_sending_event.h" +#include "base/message_loop/message_pump_mac.h" + +// A basic implementation of CrAppProtocol and +// CrAppControlProtocol. This can be used in tests that need an +// NSApplication and use a runloop, or which need a ScopedSendingEvent +// when handling a nested event loop. +@interface MockCrApp : NSApplication { + @private + BOOL handlingSendEvent_; +} +@end + +#endif + +// To be used to instantiate MockCrApp from C++ code. +namespace mock_cr_app { +void RegisterMockCrApp(); +} // namespace mock_cr_app + +#endif // BASE_TEST_MOCK_CHROME_APPLICATION_MAC_H_ diff --git a/base/test/mock_chrome_application_mac.mm b/base/test/mock_chrome_application_mac.mm new file mode 100644 index 0000000000..089055361d --- /dev/null +++ b/base/test/mock_chrome_application_mac.mm @@ -0,0 +1,44 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/mock_chrome_application_mac.h" + +#include "base/auto_reset.h" +#include "base/logging.h" + +@implementation MockCrApp + ++ (NSApplication*)sharedApplication { + NSApplication* app = [super sharedApplication]; + DCHECK([app conformsToProtocol:@protocol(CrAppControlProtocol)]) + << "Existing NSApp (class " << [[app className] UTF8String] + << ") does not conform to required protocol."; + DCHECK(base::MessagePumpMac::UsingCrApp()) + << "MessagePumpMac::Create() was called before " + << "+[MockCrApp sharedApplication]"; + return app; +} + +- (void)sendEvent:(NSEvent*)event { + base::AutoReset scoper(&handlingSendEvent_, YES); + [super sendEvent:event]; +} + +- (void)setHandlingSendEvent:(BOOL)handlingSendEvent { + handlingSendEvent_ = handlingSendEvent; +} + +- (BOOL)isHandlingSendEvent { + return handlingSendEvent_; +} + +@end + +namespace mock_cr_app { + +void RegisterMockCrApp() { + [MockCrApp sharedApplication]; +} + +} // namespace mock_cr_app diff --git a/base/test/mock_devices_changed_observer.cc b/base/test/mock_devices_changed_observer.cc new file mode 100644 index 0000000000..c05f26a613 --- /dev/null +++ b/base/test/mock_devices_changed_observer.cc @@ -0,0 +1,15 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/mock_devices_changed_observer.h" + +namespace base { + +MockDevicesChangedObserver::MockDevicesChangedObserver() { +} + +MockDevicesChangedObserver::~MockDevicesChangedObserver() { +} + +} // namespace base diff --git a/base/test/mock_devices_changed_observer.h b/base/test/mock_devices_changed_observer.h new file mode 100644 index 0000000000..3ada16b8d4 --- /dev/null +++ b/base/test/mock_devices_changed_observer.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_TEST_MOCK_DEVICES_CHANGED_OBSERVER_H_ +#define BASE_TEST_MOCK_DEVICES_CHANGED_OBSERVER_H_ + +#include + +#include "base/system_monitor/system_monitor.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace base { + +class MockDevicesChangedObserver + : public base::SystemMonitor::DevicesChangedObserver { + public: + MockDevicesChangedObserver(); + ~MockDevicesChangedObserver(); + + MOCK_METHOD1(OnDevicesChanged, + void(base::SystemMonitor::DeviceType device_type)); + + DISALLOW_COPY_AND_ASSIGN(MockDevicesChangedObserver); +}; + +} // namespace base + +#endif // BASE_TEST_MOCK_DEVICES_CHANGED_OBSERVER_H_ diff --git a/base/test/mock_time_provider.cc b/base/test/mock_time_provider.cc new file mode 100644 index 0000000000..9e5547fcd9 --- /dev/null +++ b/base/test/mock_time_provider.cc @@ -0,0 +1,31 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/logging.h" +#include "base/test/mock_time_provider.h" + +using ::testing::DefaultValue; + +namespace base { + +MockTimeProvider* MockTimeProvider::instance_ = NULL; + +MockTimeProvider::MockTimeProvider() { + DCHECK(!instance_) << "Only one instance of MockTimeProvider can exist"; + DCHECK(!DefaultValue is rarely useful. One such use is when A is non-const ref that you +// want filled by the dispatchee, and the tuple is merely a container for that +// output (a "tier"). See MakeRefTuple and its usages. + +struct Tuple0 { + typedef Tuple0 ValueTuple; + typedef Tuple0 RefTuple; + typedef Tuple0 ParamTuple; +}; + +template +struct Tuple1 { + public: + typedef A TypeA; + + Tuple1() {} + explicit Tuple1(typename TupleTraits::ParamType a) : a(a) {} + + A a; +}; + +template +struct Tuple2 { + public: + typedef A TypeA; + typedef B TypeB; + + Tuple2() {} + Tuple2(typename TupleTraits::ParamType a, + typename TupleTraits::ParamType b) + : a(a), b(b) { + } + + A a; + B b; +}; + +template +struct Tuple3 { + public: + typedef A TypeA; + typedef B TypeB; + typedef C TypeC; + + Tuple3() {} + Tuple3(typename TupleTraits::ParamType a, + typename TupleTraits::ParamType b, + typename TupleTraits::ParamType c) + : a(a), b(b), c(c){ + } + + A a; + B b; + C c; +}; + +template +struct Tuple4 { + public: + typedef A TypeA; + typedef B TypeB; + typedef C TypeC; + typedef D TypeD; + + Tuple4() {} + Tuple4(typename TupleTraits::ParamType a, + typename TupleTraits::ParamType b, + typename TupleTraits::ParamType c, + typename TupleTraits::ParamType d) + : a(a), b(b), c(c), d(d) { + } + + A a; + B b; + C c; + D d; +}; + +template +struct Tuple5 { + public: + typedef A TypeA; + typedef B TypeB; + typedef C TypeC; + typedef D TypeD; + typedef E TypeE; + + Tuple5() {} + Tuple5(typename TupleTraits::ParamType a, + typename TupleTraits::ParamType b, + typename TupleTraits::ParamType c, + typename TupleTraits::ParamType d, + typename TupleTraits::ParamType e) + : a(a), b(b), c(c), d(d), e(e) { + } + + A a; + B b; + C c; + D d; + E e; +}; + +template +struct Tuple6 { + public: + typedef A TypeA; + typedef B TypeB; + typedef C TypeC; + typedef D TypeD; + typedef E TypeE; + typedef F TypeF; + + Tuple6() {} + Tuple6(typename TupleTraits::ParamType a, + typename TupleTraits::ParamType b, + typename TupleTraits::ParamType c, + typename TupleTraits::ParamType d, + typename TupleTraits::ParamType e, + typename TupleTraits::ParamType f) + : a(a), b(b), c(c), d(d), e(e), f(f) { + } + + A a; + B b; + C c; + D d; + E e; + F f; +}; + +template +struct Tuple7 { + public: + typedef A TypeA; + typedef B TypeB; + typedef C TypeC; + typedef D TypeD; + typedef E TypeE; + typedef F TypeF; + typedef G TypeG; + + Tuple7() {} + Tuple7(typename TupleTraits::ParamType a, + typename TupleTraits::ParamType b, + typename TupleTraits::ParamType c, + typename TupleTraits::ParamType d, + typename TupleTraits::ParamType e, + typename TupleTraits::ParamType f, + typename TupleTraits::ParamType g) + : a(a), b(b), c(c), d(d), e(e), f(f), g(g) { + } + + A a; + B b; + C c; + D d; + E e; + F f; + G g; +}; + +template +struct Tuple8 { + public: + typedef A TypeA; + typedef B TypeB; + typedef C TypeC; + typedef D TypeD; + typedef E TypeE; + typedef F TypeF; + typedef G TypeG; + typedef H TypeH; + + Tuple8() {} + Tuple8(typename TupleTraits::ParamType a, + typename TupleTraits::ParamType b, + typename TupleTraits::ParamType c, + typename TupleTraits::ParamType d, + typename TupleTraits::ParamType e, + typename TupleTraits::ParamType f, + typename TupleTraits::ParamType g, + typename TupleTraits::ParamType h) + : a(a), b(b), c(c), d(d), e(e), f(f), g(g), h(h) { + } + + A a; + B b; + C c; + D d; + E e; + F f; + G g; + H h; +}; + +// Tuple types ---------------------------------------------------------------- +// +// Allows for selection of ValueTuple/RefTuple/ParamTuple without needing the +// definitions of class types the tuple takes as parameters. + +template <> +struct TupleTypes< Tuple0 > { + typedef Tuple0 ValueTuple; + typedef Tuple0 RefTuple; + typedef Tuple0 ParamTuple; +}; + +template +struct TupleTypes< Tuple1 > { + typedef Tuple1::ValueType> ValueTuple; + typedef Tuple1::RefType> RefTuple; + typedef Tuple1::ParamType> ParamTuple; +}; + +template +struct TupleTypes< Tuple2 > { + typedef Tuple2::ValueType, + typename TupleTraits::ValueType> ValueTuple; +typedef Tuple2::RefType, + typename TupleTraits::RefType> RefTuple; + typedef Tuple2::ParamType, + typename TupleTraits::ParamType> ParamTuple; +}; + +template +struct TupleTypes< Tuple3 > { + typedef Tuple3::ValueType, + typename TupleTraits::ValueType, + typename TupleTraits::ValueType> ValueTuple; +typedef Tuple3::RefType, + typename TupleTraits::RefType, + typename TupleTraits::RefType> RefTuple; + typedef Tuple3::ParamType, + typename TupleTraits::ParamType, + typename TupleTraits::ParamType> ParamTuple; +}; + +template +struct TupleTypes< Tuple4 > { + typedef Tuple4::ValueType, + typename TupleTraits::ValueType, + typename TupleTraits::ValueType, + typename TupleTraits::ValueType> ValueTuple; +typedef Tuple4::RefType, + typename TupleTraits::RefType, + typename TupleTraits::RefType, + typename TupleTraits::RefType> RefTuple; + typedef Tuple4::ParamType, + typename TupleTraits::ParamType, + typename TupleTraits::ParamType, + typename TupleTraits::ParamType> ParamTuple; +}; + +template +struct TupleTypes< Tuple5 > { + typedef Tuple5::ValueType, + typename TupleTraits::ValueType, + typename TupleTraits::ValueType, + typename TupleTraits::ValueType, + typename TupleTraits::ValueType> ValueTuple; +typedef Tuple5::RefType, + typename TupleTraits::RefType, + typename TupleTraits::RefType, + typename TupleTraits::RefType, + typename TupleTraits::RefType> RefTuple; + typedef Tuple5::ParamType, + typename TupleTraits::ParamType, + typename TupleTraits::ParamType, + typename TupleTraits::ParamType, + typename TupleTraits::ParamType> ParamTuple; +}; + +template +struct TupleTypes< Tuple6 > { + typedef Tuple6::ValueType, + typename TupleTraits::ValueType, + typename TupleTraits::ValueType, + typename TupleTraits::ValueType, + typename TupleTraits::ValueType, + typename TupleTraits::ValueType> ValueTuple; +typedef Tuple6::RefType, + typename TupleTraits::RefType, + typename TupleTraits::RefType, + typename TupleTraits::RefType, + typename TupleTraits::RefType, + typename TupleTraits::RefType> RefTuple; + typedef Tuple6::ParamType, + typename TupleTraits::ParamType, + typename TupleTraits::ParamType, + typename TupleTraits::ParamType, + typename TupleTraits::ParamType, + typename TupleTraits::ParamType> ParamTuple; +}; + +template +struct TupleTypes< Tuple7 > { + typedef Tuple7::ValueType, + typename TupleTraits::ValueType, + typename TupleTraits::ValueType, + typename TupleTraits::ValueType, + typename TupleTraits::ValueType, + typename TupleTraits::ValueType, + typename TupleTraits::ValueType> ValueTuple; +typedef Tuple7::RefType, + typename TupleTraits::RefType, + typename TupleTraits::RefType, + typename TupleTraits::RefType, + typename TupleTraits::RefType, + typename TupleTraits::RefType, + typename TupleTraits::RefType> RefTuple; + typedef Tuple7::ParamType, + typename TupleTraits::ParamType, + typename TupleTraits::ParamType, + typename TupleTraits::ParamType, + typename TupleTraits::ParamType, + typename TupleTraits::ParamType, + typename TupleTraits::ParamType> ParamTuple; +}; + +template +struct TupleTypes< Tuple8 > { + typedef Tuple8::ValueType, + typename TupleTraits::ValueType, + typename TupleTraits::ValueType, + typename TupleTraits::ValueType, + typename TupleTraits::ValueType, + typename TupleTraits::ValueType, + typename TupleTraits::ValueType, + typename TupleTraits::ValueType> ValueTuple; +typedef Tuple8::RefType, + typename TupleTraits::RefType, + typename TupleTraits::RefType, + typename TupleTraits::RefType, + typename TupleTraits::RefType, + typename TupleTraits::RefType, + typename TupleTraits::RefType, + typename TupleTraits::RefType> RefTuple; + typedef Tuple8::ParamType, + typename TupleTraits::ParamType, + typename TupleTraits::ParamType, + typename TupleTraits::ParamType, + typename TupleTraits::ParamType, + typename TupleTraits::ParamType, + typename TupleTraits::ParamType, + typename TupleTraits::ParamType> ParamTuple; +}; + +// Tuple creators ------------------------------------------------------------- +// +// Helper functions for constructing tuples while inferring the template +// argument types. + +inline Tuple0 MakeTuple() { + return Tuple0(); +} + +template +inline Tuple1 MakeTuple(const A& a) { + return Tuple1(a); +} + +template +inline Tuple2 MakeTuple(const A& a, const B& b) { + return Tuple2(a, b); +} + +template +inline Tuple3 MakeTuple(const A& a, const B& b, const C& c) { + return Tuple3(a, b, c); +} + +template +inline Tuple4 MakeTuple(const A& a, const B& b, const C& c, + const D& d) { + return Tuple4(a, b, c, d); +} + +template +inline Tuple5 MakeTuple(const A& a, const B& b, const C& c, + const D& d, const E& e) { + return Tuple5(a, b, c, d, e); +} + +template +inline Tuple6 MakeTuple(const A& a, const B& b, const C& c, + const D& d, const E& e, const F& f) { + return Tuple6(a, b, c, d, e, f); +} + +template +inline Tuple7 MakeTuple(const A& a, const B& b, const C& c, + const D& d, const E& e, const F& f, + const G& g) { + return Tuple7(a, b, c, d, e, f, g); +} + +template +inline Tuple8 MakeTuple(const A& a, const B& b, + const C& c, const D& d, + const E& e, const F& f, + const G& g, const H& h) { + return Tuple8(a, b, c, d, e, f, g, h); +} + +// The following set of helpers make what Boost refers to as "Tiers" - a tuple +// of references. + +template +inline Tuple1 MakeRefTuple(A& a) { + return Tuple1(a); +} + +template +inline Tuple2 MakeRefTuple(A& a, B& b) { + return Tuple2(a, b); +} + +template +inline Tuple3 MakeRefTuple(A& a, B& b, C& c) { + return Tuple3(a, b, c); +} + +template +inline Tuple4 MakeRefTuple(A& a, B& b, C& c, D& d) { + return Tuple4(a, b, c, d); +} + +template +inline Tuple5 MakeRefTuple(A& a, B& b, C& c, D& d, E& e) { + return Tuple5(a, b, c, d, e); +} + +template +inline Tuple6 MakeRefTuple(A& a, B& b, C& c, D& d, E& e, + F& f) { + return Tuple6(a, b, c, d, e, f); +} + +template +inline Tuple7 MakeRefTuple(A& a, B& b, C& c, D& d, + E& e, F& f, G& g) { + return Tuple7(a, b, c, d, e, f, g); +} + +template +inline Tuple8 MakeRefTuple(A& a, B& b, C& c, + D& d, E& e, F& f, + G& g, H& h) { + return Tuple8(a, b, c, d, e, f, g, h); +} + +// Dispatchers ---------------------------------------------------------------- +// +// Helper functions that call the given method on an object, with the unpacked +// tuple arguments. Notice that they all have the same number of arguments, +// so you need only write: +// DispatchToMethod(object, &Object::method, args); +// This is very useful for templated dispatchers, since they don't need to know +// what type |args| is. + +// Non-Static Dispatchers with no out params. + +template +inline void DispatchToMethod(ObjT* obj, Method method, const Tuple0& arg) { + (obj->*method)(); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, const A& arg) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(arg)); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, const Tuple1& arg) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(arg.a)); +} + +template +inline void DispatchToMethod(ObjT* obj, + Method method, + const Tuple2& arg) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(arg.a), + base::internal::UnwrapTraits::Unwrap(arg.b)); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple3& arg) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(arg.a), + base::internal::UnwrapTraits::Unwrap(arg.b), + base::internal::UnwrapTraits::Unwrap(arg.c)); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple4& arg) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(arg.a), + base::internal::UnwrapTraits::Unwrap(arg.b), + base::internal::UnwrapTraits::Unwrap(arg.c), + base::internal::UnwrapTraits::Unwrap(arg.d)); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple5& arg) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(arg.a), + base::internal::UnwrapTraits::Unwrap(arg.b), + base::internal::UnwrapTraits::Unwrap(arg.c), + base::internal::UnwrapTraits::Unwrap(arg.d), + base::internal::UnwrapTraits::Unwrap(arg.e)); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple6& arg) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(arg.a), + base::internal::UnwrapTraits::Unwrap(arg.b), + base::internal::UnwrapTraits::Unwrap(arg.c), + base::internal::UnwrapTraits::Unwrap(arg.d), + base::internal::UnwrapTraits::Unwrap(arg.e), + base::internal::UnwrapTraits::Unwrap(arg.f)); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple7& arg) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(arg.a), + base::internal::UnwrapTraits::Unwrap(arg.b), + base::internal::UnwrapTraits::Unwrap(arg.c), + base::internal::UnwrapTraits::Unwrap(arg.d), + base::internal::UnwrapTraits::Unwrap(arg.e), + base::internal::UnwrapTraits::Unwrap(arg.f), + base::internal::UnwrapTraits::Unwrap(arg.g)); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple8& arg) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(arg.a), + base::internal::UnwrapTraits::Unwrap(arg.b), + base::internal::UnwrapTraits::Unwrap(arg.c), + base::internal::UnwrapTraits::Unwrap(arg.d), + base::internal::UnwrapTraits::Unwrap(arg.e), + base::internal::UnwrapTraits::Unwrap(arg.f), + base::internal::UnwrapTraits::Unwrap(arg.g), + base::internal::UnwrapTraits::Unwrap(arg.h)); +} + +// Static Dispatchers with no out params. + +template +inline void DispatchToFunction(Function function, const Tuple0& arg) { + (*function)(); +} + +template +inline void DispatchToFunction(Function function, const A& arg) { + (*function)(arg); +} + +template +inline void DispatchToFunction(Function function, const Tuple1& arg) { + (*function)(base::internal::UnwrapTraits::Unwrap(arg.a)); +} + +template +inline void DispatchToFunction(Function function, const Tuple2& arg) { + (*function)(base::internal::UnwrapTraits::Unwrap(arg.a), + base::internal::UnwrapTraits::Unwrap(arg.b)); +} + +template +inline void DispatchToFunction(Function function, const Tuple3& arg) { + (*function)(base::internal::UnwrapTraits::Unwrap(arg.a), + base::internal::UnwrapTraits::Unwrap(arg.b), + base::internal::UnwrapTraits::Unwrap(arg.c)); +} + +template +inline void DispatchToFunction(Function function, + const Tuple4& arg) { + (*function)(base::internal::UnwrapTraits::Unwrap(arg.a), + base::internal::UnwrapTraits::Unwrap(arg.b), + base::internal::UnwrapTraits::Unwrap(arg.c), + base::internal::UnwrapTraits::Unwrap(arg.d)); +} + +template +inline void DispatchToFunction(Function function, + const Tuple5& arg) { + (*function)(base::internal::UnwrapTraits::Unwrap(arg.a), + base::internal::UnwrapTraits::Unwrap(arg.b), + base::internal::UnwrapTraits::Unwrap(arg.c), + base::internal::UnwrapTraits::Unwrap(arg.d), + base::internal::UnwrapTraits::Unwrap(arg.e)); +} + +template +inline void DispatchToFunction(Function function, + const Tuple6& arg) { + (*function)(base::internal::UnwrapTraits::Unwrap(arg.a), + base::internal::UnwrapTraits::Unwrap(arg.b), + base::internal::UnwrapTraits::Unwrap(arg.c), + base::internal::UnwrapTraits::Unwrap(arg.d), + base::internal::UnwrapTraits::Unwrap(arg.e), + base::internal::UnwrapTraits::Unwrap(arg.f)); +} + +template +inline void DispatchToFunction(Function function, + const Tuple7& arg) { + (*function)(base::internal::UnwrapTraits::Unwrap(arg.a), + base::internal::UnwrapTraits::Unwrap(arg.b), + base::internal::UnwrapTraits::Unwrap(arg.c), + base::internal::UnwrapTraits::Unwrap(arg.d), + base::internal::UnwrapTraits::Unwrap(arg.e), + base::internal::UnwrapTraits::Unwrap(arg.f), + base::internal::UnwrapTraits::Unwrap(arg.g)); +} + +template +inline void DispatchToFunction(Function function, + const Tuple8& arg) { + (*function)(base::internal::UnwrapTraits::Unwrap(arg.a), + base::internal::UnwrapTraits::Unwrap(arg.b), + base::internal::UnwrapTraits::Unwrap(arg.c), + base::internal::UnwrapTraits::Unwrap(arg.d), + base::internal::UnwrapTraits::Unwrap(arg.e), + base::internal::UnwrapTraits::Unwrap(arg.f), + base::internal::UnwrapTraits::Unwrap(arg.g), + base::internal::UnwrapTraits::Unwrap(arg.h)); +} + +// Dispatchers with 0 out param (as a Tuple0). + +template +inline void DispatchToMethod(ObjT* obj, + Method method, + const Tuple0& arg, Tuple0*) { + (obj->*method)(); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, const A& arg, Tuple0*) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(arg)); +} + +template +inline void DispatchToMethod(ObjT* obj, + Method method, + const Tuple1& arg, Tuple0*) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(arg.a)); +} + +template +inline void DispatchToMethod(ObjT* obj, + Method method, + const Tuple2& arg, Tuple0*) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(arg.a), + base::internal::UnwrapTraits::Unwrap(arg.b)); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple3& arg, Tuple0*) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(arg.a), + base::internal::UnwrapTraits::Unwrap(arg.b), + base::internal::UnwrapTraits::Unwrap(arg.c)); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple4& arg, Tuple0*) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(arg.a), + base::internal::UnwrapTraits::Unwrap(arg.b), + base::internal::UnwrapTraits::Unwrap(arg.c), + base::internal::UnwrapTraits::Unwrap(arg.d)); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple5& arg, Tuple0*) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(arg.a), + base::internal::UnwrapTraits::Unwrap(arg.b), + base::internal::UnwrapTraits::Unwrap(arg.c), + base::internal::UnwrapTraits::Unwrap(arg.d), + base::internal::UnwrapTraits::Unwrap(arg.e)); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple6& arg, Tuple0*) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(arg.a), + base::internal::UnwrapTraits::Unwrap(arg.b), + base::internal::UnwrapTraits::Unwrap(arg.c), + base::internal::UnwrapTraits::Unwrap(arg.d), + base::internal::UnwrapTraits::Unwrap(arg.e), + base::internal::UnwrapTraits::Unwrap(arg.f)); +} + +// Dispatchers with 1 out param. + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple0& in, + Tuple1* out) { + (obj->*method)(&out->a); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const InA& in, + Tuple1* out) { + (obj->*method)(in, &out->a); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple1& in, + Tuple1* out) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(in.a), &out->a); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple2& in, + Tuple1* out) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(in.a), + base::internal::UnwrapTraits::Unwrap(in.b), + &out->a); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple3& in, + Tuple1* out) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(in.a), + base::internal::UnwrapTraits::Unwrap(in.b), + base::internal::UnwrapTraits::Unwrap(in.c), + &out->a); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple4& in, + Tuple1* out) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(in.a), + base::internal::UnwrapTraits::Unwrap(in.b), + base::internal::UnwrapTraits::Unwrap(in.c), + base::internal::UnwrapTraits::Unwrap(in.d), + &out->a); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple5& in, + Tuple1* out) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(in.a), + base::internal::UnwrapTraits::Unwrap(in.b), + base::internal::UnwrapTraits::Unwrap(in.c), + base::internal::UnwrapTraits::Unwrap(in.d), + base::internal::UnwrapTraits::Unwrap(in.e), + &out->a); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple6& in, + Tuple1* out) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(in.a), + base::internal::UnwrapTraits::Unwrap(in.b), + base::internal::UnwrapTraits::Unwrap(in.c), + base::internal::UnwrapTraits::Unwrap(in.d), + base::internal::UnwrapTraits::Unwrap(in.e), + base::internal::UnwrapTraits::Unwrap(in.f), + &out->a); +} + +// Dispatchers with 2 out params. + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple0& in, + Tuple2* out) { + (obj->*method)(&out->a, &out->b); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const InA& in, + Tuple2* out) { + (obj->*method)(in, &out->a, &out->b); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple1& in, + Tuple2* out) { + (obj->*method)( + base::internal::UnwrapTraits::Unwrap(in.a), &out->a, &out->b); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple2& in, + Tuple2* out) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(in.a), + base::internal::UnwrapTraits::Unwrap(in.b), + &out->a, + &out->b); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple3& in, + Tuple2* out) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(in.a), + base::internal::UnwrapTraits::Unwrap(in.b), + base::internal::UnwrapTraits::Unwrap(in.c), + &out->a, + &out->b); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple4& in, + Tuple2* out) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(in.a), + base::internal::UnwrapTraits::Unwrap(in.b), + base::internal::UnwrapTraits::Unwrap(in.c), + base::internal::UnwrapTraits::Unwrap(in.d), + &out->a, + &out->b); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple5& in, + Tuple2* out) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(in.a), + base::internal::UnwrapTraits::Unwrap(in.b), + base::internal::UnwrapTraits::Unwrap(in.c), + base::internal::UnwrapTraits::Unwrap(in.d), + base::internal::UnwrapTraits::Unwrap(in.e), + &out->a, + &out->b); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple6& in, + Tuple2* out) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(in.a), + base::internal::UnwrapTraits::Unwrap(in.b), + base::internal::UnwrapTraits::Unwrap(in.c), + base::internal::UnwrapTraits::Unwrap(in.d), + base::internal::UnwrapTraits::Unwrap(in.e), + base::internal::UnwrapTraits::Unwrap(in.f), + &out->a, + &out->b); +} + +// Dispatchers with 3 out params. + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple0& in, + Tuple3* out) { + (obj->*method)(&out->a, &out->b, &out->c); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const InA& in, + Tuple3* out) { + (obj->*method)(in, &out->a, &out->b, &out->c); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple1& in, + Tuple3* out) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(in.a), + &out->a, + &out->b, + &out->c); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple2& in, + Tuple3* out) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(in.a), + base::internal::UnwrapTraits::Unwrap(in.b), + &out->a, + &out->b, + &out->c); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple3& in, + Tuple3* out) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(in.a), + base::internal::UnwrapTraits::Unwrap(in.b), + base::internal::UnwrapTraits::Unwrap(in.c), + &out->a, + &out->b, + &out->c); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple4& in, + Tuple3* out) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(in.a), + base::internal::UnwrapTraits::Unwrap(in.b), + base::internal::UnwrapTraits::Unwrap(in.c), + base::internal::UnwrapTraits::Unwrap(in.d), + &out->a, + &out->b, + &out->c); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple5& in, + Tuple3* out) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(in.a), + base::internal::UnwrapTraits::Unwrap(in.b), + base::internal::UnwrapTraits::Unwrap(in.c), + base::internal::UnwrapTraits::Unwrap(in.d), + base::internal::UnwrapTraits::Unwrap(in.e), + &out->a, + &out->b, + &out->c); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple6& in, + Tuple3* out) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(in.a), + base::internal::UnwrapTraits::Unwrap(in.b), + base::internal::UnwrapTraits::Unwrap(in.c), + base::internal::UnwrapTraits::Unwrap(in.d), + base::internal::UnwrapTraits::Unwrap(in.e), + base::internal::UnwrapTraits::Unwrap(in.f), + &out->a, + &out->b, + &out->c); +} + +// Dispatchers with 4 out params. + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple0& in, + Tuple4* out) { + (obj->*method)(&out->a, &out->b, &out->c, &out->d); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const InA& in, + Tuple4* out) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(in), + &out->a, + &out->b, + &out->c, + &out->d); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple1& in, + Tuple4* out) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(in.a), + &out->a, + &out->b, + &out->c, + &out->d); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple2& in, + Tuple4* out) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(in.a), + base::internal::UnwrapTraits::Unwrap(in.b), + &out->a, + &out->b, + &out->c, + &out->d); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple3& in, + Tuple4* out) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(in.a), + base::internal::UnwrapTraits::Unwrap(in.b), + base::internal::UnwrapTraits::Unwrap(in.c), + &out->a, + &out->b, + &out->c, + &out->d); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple4& in, + Tuple4* out) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(in.a), + base::internal::UnwrapTraits::Unwrap(in.b), + base::internal::UnwrapTraits::Unwrap(in.c), + base::internal::UnwrapTraits::Unwrap(in.d), + &out->a, + &out->b, + &out->c, + &out->d); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple5& in, + Tuple4* out) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(in.a), + base::internal::UnwrapTraits::Unwrap(in.b), + base::internal::UnwrapTraits::Unwrap(in.c), + base::internal::UnwrapTraits::Unwrap(in.d), + base::internal::UnwrapTraits::Unwrap(in.e), + &out->a, + &out->b, + &out->c, + &out->d); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple6& in, + Tuple4* out) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(in.a), + base::internal::UnwrapTraits::Unwrap(in.b), + base::internal::UnwrapTraits::Unwrap(in.c), + base::internal::UnwrapTraits::Unwrap(in.d), + base::internal::UnwrapTraits::Unwrap(in.e), + base::internal::UnwrapTraits::Unwrap(in.f), + &out->a, + &out->b, + &out->c, + &out->d); +} + +// Dispatchers with 5 out params. + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple0& in, + Tuple5* out) { + (obj->*method)(&out->a, &out->b, &out->c, &out->d, &out->e); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const InA& in, + Tuple5* out) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(in), + &out->a, + &out->b, + &out->c, + &out->d, + &out->e); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple1& in, + Tuple5* out) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(in.a), + &out->a, + &out->b, + &out->c, + &out->d, + &out->e); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple2& in, + Tuple5* out) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(in.a), + base::internal::UnwrapTraits::Unwrap(in.b), + &out->a, + &out->b, + &out->c, + &out->d, + &out->e); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple3& in, + Tuple5* out) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(in.a), + base::internal::UnwrapTraits::Unwrap(in.b), + base::internal::UnwrapTraits::Unwrap(in.c), + &out->a, + &out->b, + &out->c, + &out->d, + &out->e); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple4& in, + Tuple5* out) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(in.a), + base::internal::UnwrapTraits::Unwrap(in.b), + base::internal::UnwrapTraits::Unwrap(in.c), + base::internal::UnwrapTraits::Unwrap(in.d), + &out->a, + &out->b, + &out->c, + &out->d, + &out->e); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple5& in, + Tuple5* out) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(in.a), + base::internal::UnwrapTraits::Unwrap(in.b), + base::internal::UnwrapTraits::Unwrap(in.c), + base::internal::UnwrapTraits::Unwrap(in.d), + base::internal::UnwrapTraits::Unwrap(in.e), + &out->a, + &out->b, + &out->c, + &out->d, + &out->e); +} + +template +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple6& in, + Tuple5* out) { + (obj->*method)(base::internal::UnwrapTraits::Unwrap(in.a), + base::internal::UnwrapTraits::Unwrap(in.b), + base::internal::UnwrapTraits::Unwrap(in.c), + base::internal::UnwrapTraits::Unwrap(in.d), + base::internal::UnwrapTraits::Unwrap(in.e), + base::internal::UnwrapTraits::Unwrap(in.f), + &out->a, + &out->b, + &out->c, + &out->d, + &out->e); +} + +#endif // BASE_TUPLE_H__ diff --git a/base/tuple_unittest.cc b/base/tuple_unittest.cc new file mode 100644 index 0000000000..402394cb66 --- /dev/null +++ b/base/tuple_unittest.cc @@ -0,0 +1,128 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/tuple.h" + +#include "base/compiler_specific.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +void DoAdd(int a, int b, int c, int* res) { + *res = a + b + c; +} + +struct Addy { + Addy() { } + void DoAdd(int a, int b, int c, int d, int* res) { + *res = a + b + c + d; + } +}; + +struct Addz { + Addz() { } + void DoAdd(int a, int b, int c, int d, int e, int* res) { + *res = a + b + c + d + e; + } +}; + +} // namespace + +TEST(TupleTest, Basic) { + Tuple0 t0 ALLOW_UNUSED = MakeTuple(); + Tuple1 t1(1); + Tuple2 t2 = MakeTuple(1, static_cast("wee")); + Tuple3 t3(1, 2, 3); + Tuple4 t4(1, 2, 3, &t1.a); + Tuple5 t5(1, 2, 3, 4, &t4.a); + Tuple6 t6(1, 2, 3, 4, 5, &t4.a); + + EXPECT_EQ(1, t1.a); + EXPECT_EQ(1, t2.a); + EXPECT_EQ(1, t3.a); + EXPECT_EQ(2, t3.b); + EXPECT_EQ(3, t3.c); + EXPECT_EQ(1, t4.a); + EXPECT_EQ(2, t4.b); + EXPECT_EQ(3, t4.c); + EXPECT_EQ(1, t5.a); + EXPECT_EQ(2, t5.b); + EXPECT_EQ(3, t5.c); + EXPECT_EQ(4, t5.d); + EXPECT_EQ(1, t6.a); + EXPECT_EQ(2, t6.b); + EXPECT_EQ(3, t6.c); + EXPECT_EQ(4, t6.d); + EXPECT_EQ(5, t6.e); + + EXPECT_EQ(1, t1.a); + DispatchToFunction(&DoAdd, t4); + EXPECT_EQ(6, t1.a); + + int res = 0; + DispatchToFunction(&DoAdd, MakeTuple(9, 8, 7, &res)); + EXPECT_EQ(24, res); + + Addy addy; + EXPECT_EQ(1, t4.a); + DispatchToMethod(&addy, &Addy::DoAdd, t5); + EXPECT_EQ(10, t4.a); + + Addz addz; + EXPECT_EQ(10, t4.a); + DispatchToMethod(&addz, &Addz::DoAdd, t6); + EXPECT_EQ(15, t4.a); +} + +namespace { + +struct CopyLogger { + CopyLogger() { ++TimesConstructed; } + CopyLogger(const CopyLogger& tocopy) { ++TimesConstructed; ++TimesCopied; } + ~CopyLogger() { } + + static int TimesCopied; + static int TimesConstructed; +}; + +void SomeLoggerMethRef(const CopyLogger& logy, const CopyLogger* ptr, bool* b) { + *b = &logy == ptr; +} + +void SomeLoggerMethCopy(CopyLogger logy, const CopyLogger* ptr, bool* b) { + *b = &logy == ptr; +} + +int CopyLogger::TimesCopied = 0; +int CopyLogger::TimesConstructed = 0; + +} // namespace + +TEST(TupleTest, Copying) { + CopyLogger logger; + EXPECT_EQ(0, CopyLogger::TimesCopied); + EXPECT_EQ(1, CopyLogger::TimesConstructed); + + bool res = false; + + // Creating the tuple should copy the class to store internally in the tuple. + Tuple3 tuple(logger, &logger, &res); + tuple.b = &tuple.a; + EXPECT_EQ(2, CopyLogger::TimesConstructed); + EXPECT_EQ(1, CopyLogger::TimesCopied); + + // Our internal Logger and the one passed to the function should be the same. + res = false; + DispatchToFunction(&SomeLoggerMethRef, tuple); + EXPECT_TRUE(res); + EXPECT_EQ(2, CopyLogger::TimesConstructed); + EXPECT_EQ(1, CopyLogger::TimesCopied); + + // Now they should be different, since the function call will make a copy. + res = false; + DispatchToFunction(&SomeLoggerMethCopy, tuple); + EXPECT_FALSE(res); + EXPECT_EQ(3, CopyLogger::TimesConstructed); + EXPECT_EQ(2, CopyLogger::TimesCopied); +} diff --git a/base/value_conversions.cc b/base/value_conversions.cc new file mode 100644 index 0000000000..2fd88f800c --- /dev/null +++ b/base/value_conversions.cc @@ -0,0 +1,46 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/value_conversions.h" + +#include "base/files/file_path.h" +#include "base/strings/string_number_conversions.h" +#include "base/time/time.h" +#include "base/values.h" + +namespace base { + +// |Value| internally stores strings in UTF-8, so we have to convert from the +// system native code to UTF-8 and back. +StringValue* CreateFilePathValue(const FilePath& in_value) { + return new StringValue(in_value.AsUTF8Unsafe()); +} + +bool GetValueAsFilePath(const Value& value, FilePath* file_path) { + std::string str; + if (!value.GetAsString(&str)) + return false; + if (file_path) + *file_path = FilePath::FromUTF8Unsafe(str); + return true; +} + +// |Value| does not support 64-bit integers, and doubles do not have enough +// precision, so we store the 64-bit time value as a string instead. +StringValue* CreateTimeDeltaValue(const TimeDelta& time) { + std::string string_value = base::Int64ToString(time.ToInternalValue()); + return new StringValue(string_value); +} + +bool GetValueAsTimeDelta(const Value& value, TimeDelta* time) { + std::string str; + int64 int_value; + if (!value.GetAsString(&str) || !base::StringToInt64(str, &int_value)) + return false; + if (time) + *time = TimeDelta::FromInternalValue(int_value); + return true; +} + +} // namespace base diff --git a/base/value_conversions.h b/base/value_conversions.h new file mode 100644 index 0000000000..fde9a26929 --- /dev/null +++ b/base/value_conversions.h @@ -0,0 +1,29 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_VALUE_CONVERSIONS_H_ +#define BASE_VALUE_CONVERSIONS_H_ + +// This file contains methods to convert things to a |Value| and back. + +#include "base/base_export.h" + + +namespace base { + +class FilePath; +class TimeDelta; +class StringValue; +class Value; + +// The caller takes ownership of the returned value. +BASE_EXPORT StringValue* CreateFilePathValue(const FilePath& in_value); +BASE_EXPORT bool GetValueAsFilePath(const Value& value, FilePath* file_path); + +BASE_EXPORT StringValue* CreateTimeDeltaValue(const TimeDelta& time); +BASE_EXPORT bool GetValueAsTimeDelta(const Value& value, TimeDelta* time); + +} // namespace + +#endif // BASE_VALUE_CONVERSIONS_H_ diff --git a/base/values.cc b/base/values.cc new file mode 100644 index 0000000000..adfb980139 --- /dev/null +++ b/base/values.cc @@ -0,0 +1,1119 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/values.h" + +#include +#include + +#include "base/float_util.h" +#include "base/json/json_writer.h" +#include "base/logging.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" + +namespace base { + +namespace { + +// Make a deep copy of |node|, but don't include empty lists or dictionaries +// in the copy. It's possible for this function to return NULL and it +// expects |node| to always be non-NULL. +Value* CopyWithoutEmptyChildren(const Value* node) { + DCHECK(node); + switch (node->GetType()) { + case Value::TYPE_LIST: { + const ListValue* list = static_cast(node); + ListValue* copy = new ListValue; + for (ListValue::const_iterator it = list->begin(); it != list->end(); + ++it) { + Value* child_copy = CopyWithoutEmptyChildren(*it); + if (child_copy) + copy->Append(child_copy); + } + if (!copy->empty()) + return copy; + + delete copy; + return NULL; + } + + case Value::TYPE_DICTIONARY: { + const DictionaryValue* dict = static_cast(node); + DictionaryValue* copy = new DictionaryValue; + for (DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) { + Value* child_copy = CopyWithoutEmptyChildren(&it.value()); + if (child_copy) + copy->SetWithoutPathExpansion(it.key(), child_copy); + } + if (!copy->empty()) + return copy; + + delete copy; + return NULL; + } + + default: + // For everything else, just make a copy. + return node->DeepCopy(); + } +} + +// A small functor for comparing Values for std::find_if and similar. +class ValueEquals { + public: + // Pass the value against which all consecutive calls of the () operator will + // compare their argument to. This Value object must not be destroyed while + // the ValueEquals is in use. + explicit ValueEquals(const Value* first) : first_(first) { } + + bool operator ()(const Value* second) const { + return first_->Equals(second); + } + + private: + const Value* first_; +}; + +} // namespace + +Value::~Value() { +} + +// static +Value* Value::CreateNullValue() { + return new Value(TYPE_NULL); +} + +// static +FundamentalValue* Value::CreateBooleanValue(bool in_value) { + return new FundamentalValue(in_value); +} + +// static +FundamentalValue* Value::CreateIntegerValue(int in_value) { + return new FundamentalValue(in_value); +} + +// static +FundamentalValue* Value::CreateDoubleValue(double in_value) { + return new FundamentalValue(in_value); +} + +// static +StringValue* Value::CreateStringValue(const std::string& in_value) { + return new StringValue(in_value); +} + +// static +StringValue* Value::CreateStringValue(const string16& in_value) { + return new StringValue(in_value); +} + +bool Value::GetAsBoolean(bool* out_value) const { + return false; +} + +bool Value::GetAsInteger(int* out_value) const { + return false; +} + +bool Value::GetAsDouble(double* out_value) const { + return false; +} + +bool Value::GetAsString(std::string* out_value) const { + return false; +} + +bool Value::GetAsString(string16* out_value) const { + return false; +} + +bool Value::GetAsList(ListValue** out_value) { + return false; +} + +bool Value::GetAsList(const ListValue** out_value) const { + return false; +} + +bool Value::GetAsDictionary(DictionaryValue** out_value) { + return false; +} + +bool Value::GetAsDictionary(const DictionaryValue** out_value) const { + return false; +} + +Value* Value::DeepCopy() const { + // This method should only be getting called for null Values--all subclasses + // need to provide their own implementation;. + DCHECK(IsType(TYPE_NULL)); + return CreateNullValue(); +} + +bool Value::Equals(const Value* other) const { + // This method should only be getting called for null Values--all subclasses + // need to provide their own implementation;. + DCHECK(IsType(TYPE_NULL)); + return other->IsType(TYPE_NULL); +} + +// static +bool Value::Equals(const Value* a, const Value* b) { + if ((a == NULL) && (b == NULL)) return true; + if ((a == NULL) ^ (b == NULL)) return false; + return a->Equals(b); +} + +Value::Value(Type type) : type_(type) {} + +Value::Value(const Value& that) : type_(that.type_) {} + +Value& Value::operator=(const Value& that) { + type_ = that.type_; + return *this; +} + +///////////////////// FundamentalValue //////////////////// + +FundamentalValue::FundamentalValue(bool in_value) + : Value(TYPE_BOOLEAN), boolean_value_(in_value) { +} + +FundamentalValue::FundamentalValue(int in_value) + : Value(TYPE_INTEGER), integer_value_(in_value) { +} + +FundamentalValue::FundamentalValue(double in_value) + : Value(TYPE_DOUBLE), double_value_(in_value) { + if (!IsFinite(double_value_)) { + NOTREACHED() << "Non-finite (i.e. NaN or positive/negative infinity) " + << "values cannot be represented in JSON"; + double_value_ = 0.0; + } +} + +FundamentalValue::~FundamentalValue() { +} + +bool FundamentalValue::GetAsBoolean(bool* out_value) const { + if (out_value && IsType(TYPE_BOOLEAN)) + *out_value = boolean_value_; + return (IsType(TYPE_BOOLEAN)); +} + +bool FundamentalValue::GetAsInteger(int* out_value) const { + if (out_value && IsType(TYPE_INTEGER)) + *out_value = integer_value_; + return (IsType(TYPE_INTEGER)); +} + +bool FundamentalValue::GetAsDouble(double* out_value) const { + if (out_value && IsType(TYPE_DOUBLE)) + *out_value = double_value_; + else if (out_value && IsType(TYPE_INTEGER)) + *out_value = integer_value_; + return (IsType(TYPE_DOUBLE) || IsType(TYPE_INTEGER)); +} + +FundamentalValue* FundamentalValue::DeepCopy() const { + switch (GetType()) { + case TYPE_BOOLEAN: + return CreateBooleanValue(boolean_value_); + + case TYPE_INTEGER: + return CreateIntegerValue(integer_value_); + + case TYPE_DOUBLE: + return CreateDoubleValue(double_value_); + + default: + NOTREACHED(); + return NULL; + } +} + +bool FundamentalValue::Equals(const Value* other) const { + if (other->GetType() != GetType()) + return false; + + switch (GetType()) { + case TYPE_BOOLEAN: { + bool lhs, rhs; + return GetAsBoolean(&lhs) && other->GetAsBoolean(&rhs) && lhs == rhs; + } + case TYPE_INTEGER: { + int lhs, rhs; + return GetAsInteger(&lhs) && other->GetAsInteger(&rhs) && lhs == rhs; + } + case TYPE_DOUBLE: { + double lhs, rhs; + return GetAsDouble(&lhs) && other->GetAsDouble(&rhs) && lhs == rhs; + } + default: + NOTREACHED(); + return false; + } +} + +///////////////////// StringValue //////////////////// + +StringValue::StringValue(const std::string& in_value) + : Value(TYPE_STRING), + value_(in_value) { + DCHECK(IsStringUTF8(in_value)); +} + +StringValue::StringValue(const string16& in_value) + : Value(TYPE_STRING), + value_(UTF16ToUTF8(in_value)) { +} + +StringValue::~StringValue() { +} + +bool StringValue::GetAsString(std::string* out_value) const { + if (out_value) + *out_value = value_; + return true; +} + +bool StringValue::GetAsString(string16* out_value) const { + if (out_value) + *out_value = UTF8ToUTF16(value_); + return true; +} + +StringValue* StringValue::DeepCopy() const { + return CreateStringValue(value_); +} + +bool StringValue::Equals(const Value* other) const { + if (other->GetType() != GetType()) + return false; + std::string lhs, rhs; + return GetAsString(&lhs) && other->GetAsString(&rhs) && lhs == rhs; +} + +///////////////////// BinaryValue //////////////////// + +BinaryValue::BinaryValue() + : Value(TYPE_BINARY), + size_(0) { +} + +BinaryValue::BinaryValue(scoped_ptr buffer, size_t size) + : Value(TYPE_BINARY), + buffer_(buffer.Pass()), + size_(size) { +} + +BinaryValue::~BinaryValue() { +} + +// static +BinaryValue* BinaryValue::CreateWithCopiedBuffer(const char* buffer, + size_t size) { + char* buffer_copy = new char[size]; + memcpy(buffer_copy, buffer, size); + scoped_ptr scoped_buffer_copy(buffer_copy); + return new BinaryValue(scoped_buffer_copy.Pass(), size); +} + +BinaryValue* BinaryValue::DeepCopy() const { + return CreateWithCopiedBuffer(buffer_.get(), size_); +} + +bool BinaryValue::Equals(const Value* other) const { + if (other->GetType() != GetType()) + return false; + const BinaryValue* other_binary = static_cast(other); + if (other_binary->size_ != size_) + return false; + return !memcmp(GetBuffer(), other_binary->GetBuffer(), size_); +} + +///////////////////// DictionaryValue //////////////////// + +DictionaryValue::DictionaryValue() + : Value(TYPE_DICTIONARY) { +} + +DictionaryValue::~DictionaryValue() { + Clear(); +} + +bool DictionaryValue::GetAsDictionary(DictionaryValue** out_value) { + if (out_value) + *out_value = this; + return true; +} + +bool DictionaryValue::GetAsDictionary(const DictionaryValue** out_value) const { + if (out_value) + *out_value = this; + return true; +} + +bool DictionaryValue::HasKey(const std::string& key) const { + DCHECK(IsStringUTF8(key)); + ValueMap::const_iterator current_entry = dictionary_.find(key); + DCHECK((current_entry == dictionary_.end()) || current_entry->second); + return current_entry != dictionary_.end(); +} + +void DictionaryValue::Clear() { + ValueMap::iterator dict_iterator = dictionary_.begin(); + while (dict_iterator != dictionary_.end()) { + delete dict_iterator->second; + ++dict_iterator; + } + + dictionary_.clear(); +} + +void DictionaryValue::Set(const std::string& path, Value* in_value) { + DCHECK(IsStringUTF8(path)); + DCHECK(in_value); + + std::string current_path(path); + DictionaryValue* current_dictionary = this; + for (size_t delimiter_position = current_path.find('.'); + delimiter_position != std::string::npos; + delimiter_position = current_path.find('.')) { + // Assume that we're indexing into a dictionary. + std::string key(current_path, 0, delimiter_position); + DictionaryValue* child_dictionary = NULL; + if (!current_dictionary->GetDictionary(key, &child_dictionary)) { + child_dictionary = new DictionaryValue; + current_dictionary->SetWithoutPathExpansion(key, child_dictionary); + } + + current_dictionary = child_dictionary; + current_path.erase(0, delimiter_position + 1); + } + + current_dictionary->SetWithoutPathExpansion(current_path, in_value); +} + +void DictionaryValue::SetBoolean(const std::string& path, bool in_value) { + Set(path, CreateBooleanValue(in_value)); +} + +void DictionaryValue::SetInteger(const std::string& path, int in_value) { + Set(path, CreateIntegerValue(in_value)); +} + +void DictionaryValue::SetDouble(const std::string& path, double in_value) { + Set(path, CreateDoubleValue(in_value)); +} + +void DictionaryValue::SetString(const std::string& path, + const std::string& in_value) { + Set(path, CreateStringValue(in_value)); +} + +void DictionaryValue::SetString(const std::string& path, + const string16& in_value) { + Set(path, CreateStringValue(in_value)); +} + +void DictionaryValue::SetWithoutPathExpansion(const std::string& key, + Value* in_value) { + // If there's an existing value here, we need to delete it, because + // we own all our children. + std::pair ins_res = + dictionary_.insert(std::make_pair(key, in_value)); + if (!ins_res.second) { + DCHECK_NE(ins_res.first->second, in_value); // This would be bogus + delete ins_res.first->second; + ins_res.first->second = in_value; + } +} + +void DictionaryValue::SetBooleanWithoutPathExpansion( + const std::string& path, bool in_value) { + SetWithoutPathExpansion(path, CreateBooleanValue(in_value)); +} + +void DictionaryValue::SetIntegerWithoutPathExpansion( + const std::string& path, int in_value) { + SetWithoutPathExpansion(path, CreateIntegerValue(in_value)); +} + +void DictionaryValue::SetDoubleWithoutPathExpansion( + const std::string& path, double in_value) { + SetWithoutPathExpansion(path, CreateDoubleValue(in_value)); +} + +void DictionaryValue::SetStringWithoutPathExpansion( + const std::string& path, const std::string& in_value) { + SetWithoutPathExpansion(path, CreateStringValue(in_value)); +} + +void DictionaryValue::SetStringWithoutPathExpansion( + const std::string& path, const string16& in_value) { + SetWithoutPathExpansion(path, CreateStringValue(in_value)); +} + +bool DictionaryValue::Get( + const std::string& path, const Value** out_value) const { + DCHECK(IsStringUTF8(path)); +// LOG(WARNING) << "\n1\n"; + std::string current_path(path); + const DictionaryValue* current_dictionary = this; +// LOG(WARNING) << "\n2\n"; + for (size_t delimiter_position = current_path.find('.'); + delimiter_position != std::string::npos; + delimiter_position = current_path.find('.')) { + const DictionaryValue* child_dictionary = NULL; + if (!current_dictionary->GetDictionary( + current_path.substr(0, delimiter_position), &child_dictionary)) + return false; + + current_dictionary = child_dictionary; + current_path.erase(0, delimiter_position + 1); + } +// LOG(WARNING) << "\n3\n"; + + return current_dictionary->GetWithoutPathExpansion(current_path, out_value); +} + +bool DictionaryValue::Get(const std::string& path, Value** out_value) { + return static_cast(*this).Get( + path, + const_cast(out_value)); +} + +bool DictionaryValue::GetBoolean(const std::string& path, + bool* bool_value) const { + const Value* value; + if (!Get(path, &value)) + return false; + + return value->GetAsBoolean(bool_value); +} + +bool DictionaryValue::GetInteger(const std::string& path, + int* out_value) const { + const Value* value; + if (!Get(path, &value)) + return false; + + return value->GetAsInteger(out_value); +} + +bool DictionaryValue::GetDouble(const std::string& path, + double* out_value) const { + const Value* value; + if (!Get(path, &value)) + return false; + + return value->GetAsDouble(out_value); +} + +bool DictionaryValue::GetString(const std::string& path, + std::string* out_value) const { + const Value* value; + if (!Get(path, &value)) + return false; + + return value->GetAsString(out_value); +} + +bool DictionaryValue::GetString(const std::string& path, + string16* out_value) const { + const Value* value; + if (!Get(path, &value)) + return false; + + return value->GetAsString(out_value); +} + +bool DictionaryValue::GetStringASCII(const std::string& path, + std::string* out_value) const { + std::string out; + if (!GetString(path, &out)) + return false; + + if (!IsStringASCII(out)) { + NOTREACHED(); + return false; + } + + out_value->assign(out); + return true; +} + +bool DictionaryValue::GetBinary(const std::string& path, + const BinaryValue** out_value) const { + const Value* value; + bool result = Get(path, &value); + if (!result || !value->IsType(TYPE_BINARY)) + return false; + + if (out_value) + *out_value = static_cast(value); + + return true; +} + +bool DictionaryValue::GetBinary(const std::string& path, + BinaryValue** out_value) { + return static_cast(*this).GetBinary( + path, + const_cast(out_value)); +} + +bool DictionaryValue::GetDictionary(const std::string& path, + const DictionaryValue** out_value) const { + const Value* value; + bool result = Get(path, &value); + if (!result || !value->IsType(TYPE_DICTIONARY)) + return false; + + if (out_value) + *out_value = static_cast(value); + + return true; +} + +bool DictionaryValue::GetDictionary(const std::string& path, + DictionaryValue** out_value) { + return static_cast(*this).GetDictionary( + path, + const_cast(out_value)); +} + +bool DictionaryValue::GetList(const std::string& path, + const ListValue** out_value) const { + const Value* value; + bool result = Get(path, &value); + if (!result || !value->IsType(TYPE_LIST)) + return false; + + if (out_value) + *out_value = static_cast(value); + + return true; +} + +bool DictionaryValue::GetList(const std::string& path, ListValue** out_value) { + return static_cast(*this).GetList( + path, + const_cast(out_value)); +} + +bool DictionaryValue::GetWithoutPathExpansion(const std::string& key, + const Value** out_value) const { + DCHECK(IsStringUTF8(key)); + ValueMap::const_iterator entry_iterator = dictionary_.find(key); + if (entry_iterator == dictionary_.end()) + return false; + + const Value* entry = entry_iterator->second; + if (out_value) + *out_value = entry; + return true; +} + +bool DictionaryValue::GetWithoutPathExpansion(const std::string& key, + Value** out_value) { + return static_cast(*this).GetWithoutPathExpansion( + key, + const_cast(out_value)); +} + +bool DictionaryValue::GetBooleanWithoutPathExpansion(const std::string& key, + bool* out_value) const { + const Value* value; + if (!GetWithoutPathExpansion(key, &value)) + return false; + + return value->GetAsBoolean(out_value); +} + +bool DictionaryValue::GetIntegerWithoutPathExpansion(const std::string& key, + int* out_value) const { + const Value* value; + if (!GetWithoutPathExpansion(key, &value)) + return false; + + return value->GetAsInteger(out_value); +} + +bool DictionaryValue::GetDoubleWithoutPathExpansion(const std::string& key, + double* out_value) const { + const Value* value; + if (!GetWithoutPathExpansion(key, &value)) + return false; + + return value->GetAsDouble(out_value); +} + +bool DictionaryValue::GetStringWithoutPathExpansion( + const std::string& key, + std::string* out_value) const { + const Value* value; + if (!GetWithoutPathExpansion(key, &value)) + return false; + + return value->GetAsString(out_value); +} + +bool DictionaryValue::GetStringWithoutPathExpansion(const std::string& key, + string16* out_value) const { + const Value* value; + if (!GetWithoutPathExpansion(key, &value)) + return false; + + return value->GetAsString(out_value); +} + +bool DictionaryValue::GetDictionaryWithoutPathExpansion( + const std::string& key, + const DictionaryValue** out_value) const { + const Value* value; + bool result = GetWithoutPathExpansion(key, &value); + if (!result || !value->IsType(TYPE_DICTIONARY)) + return false; + + if (out_value) + *out_value = static_cast(value); + + return true; +} + +bool DictionaryValue::GetDictionaryWithoutPathExpansion( + const std::string& key, + DictionaryValue** out_value) { + const DictionaryValue& const_this = + static_cast(*this); + return const_this.GetDictionaryWithoutPathExpansion( + key, + const_cast(out_value)); +} + +bool DictionaryValue::GetListWithoutPathExpansion( + const std::string& key, + const ListValue** out_value) const { + const Value* value; + bool result = GetWithoutPathExpansion(key, &value); + if (!result || !value->IsType(TYPE_LIST)) + return false; + + if (out_value) + *out_value = static_cast(value); + + return true; +} + +bool DictionaryValue::GetListWithoutPathExpansion(const std::string& key, + ListValue** out_value) { + return + static_cast(*this).GetListWithoutPathExpansion( + key, + const_cast(out_value)); +} + +bool DictionaryValue::Remove(const std::string& path, + scoped_ptr* out_value) { + DCHECK(IsStringUTF8(path)); + std::string current_path(path); + DictionaryValue* current_dictionary = this; + size_t delimiter_position = current_path.rfind('.'); + if (delimiter_position != std::string::npos) { + if (!GetDictionary(current_path.substr(0, delimiter_position), + ¤t_dictionary)) + return false; + current_path.erase(0, delimiter_position + 1); + } + + return current_dictionary->RemoveWithoutPathExpansion(current_path, + out_value); +} + +bool DictionaryValue::RemoveWithoutPathExpansion(const std::string& key, + scoped_ptr* out_value) { + DCHECK(IsStringUTF8(key)); + ValueMap::iterator entry_iterator = dictionary_.find(key); + if (entry_iterator == dictionary_.end()) + return false; + + Value* entry = entry_iterator->second; + if (out_value) + out_value->reset(entry); + else + delete entry; + dictionary_.erase(entry_iterator); + return true; +} + +DictionaryValue* DictionaryValue::DeepCopyWithoutEmptyChildren() { + Value* copy = CopyWithoutEmptyChildren(this); + return copy ? static_cast(copy) : new DictionaryValue; +} + +void DictionaryValue::MergeDictionary(const DictionaryValue* dictionary) { + for (DictionaryValue::Iterator it(*dictionary); !it.IsAtEnd(); it.Advance()) { + const Value* merge_value = &it.value(); + // Check whether we have to merge dictionaries. + if (merge_value->IsType(Value::TYPE_DICTIONARY)) { + DictionaryValue* sub_dict; + if (GetDictionaryWithoutPathExpansion(it.key(), &sub_dict)) { + sub_dict->MergeDictionary( + static_cast(merge_value)); + continue; + } + } + // All other cases: Make a copy and hook it up. + SetWithoutPathExpansion(it.key(), merge_value->DeepCopy()); + } +} + +void DictionaryValue::Swap(DictionaryValue* other) { + dictionary_.swap(other->dictionary_); +} + +DictionaryValue::Iterator::Iterator(const DictionaryValue& target) + : target_(target), + it_(target.dictionary_.begin()) {} + +DictionaryValue* DictionaryValue::DeepCopy() const { + DictionaryValue* result = new DictionaryValue; + + for (ValueMap::const_iterator current_entry(dictionary_.begin()); + current_entry != dictionary_.end(); ++current_entry) { + result->SetWithoutPathExpansion(current_entry->first, + current_entry->second->DeepCopy()); + } + + return result; +} + +bool DictionaryValue::Equals(const Value* other) const { + if (other->GetType() != GetType()) + return false; + + const DictionaryValue* other_dict = + static_cast(other); + Iterator lhs_it(*this); + Iterator rhs_it(*other_dict); + while (!lhs_it.IsAtEnd() && !rhs_it.IsAtEnd()) { + if (lhs_it.key() != rhs_it.key() || + !lhs_it.value().Equals(&rhs_it.value())) { + return false; + } + lhs_it.Advance(); + rhs_it.Advance(); + } + if (!lhs_it.IsAtEnd() || !rhs_it.IsAtEnd()) + return false; + + return true; +} + +///////////////////// ListValue //////////////////// + +ListValue::ListValue() : Value(TYPE_LIST) { +} + +ListValue::~ListValue() { + Clear(); +} + +void ListValue::Clear() { + for (ValueVector::iterator i(list_.begin()); i != list_.end(); ++i) + delete *i; + list_.clear(); +} + +bool ListValue::Set(size_t index, Value* in_value) { + if (!in_value) + return false; + + if (index >= list_.size()) { + // Pad out any intermediate indexes with null settings + while (index > list_.size()) + Append(CreateNullValue()); + Append(in_value); + } else { + DCHECK(list_[index] != in_value); + delete list_[index]; + list_[index] = in_value; + } + return true; +} + +bool ListValue::Get(size_t index, const Value** out_value) const { + if (index >= list_.size()) + return false; + + if (out_value) + *out_value = list_[index]; + + return true; +} + +bool ListValue::Get(size_t index, Value** out_value) { + return static_cast(*this).Get( + index, + const_cast(out_value)); +} + +bool ListValue::GetBoolean(size_t index, bool* bool_value) const { + const Value* value; + if (!Get(index, &value)) + return false; + + return value->GetAsBoolean(bool_value); +} + +bool ListValue::GetInteger(size_t index, int* out_value) const { + const Value* value; + if (!Get(index, &value)) + return false; + + return value->GetAsInteger(out_value); +} + +bool ListValue::GetDouble(size_t index, double* out_value) const { + const Value* value; + if (!Get(index, &value)) + return false; + + return value->GetAsDouble(out_value); +} + +bool ListValue::GetString(size_t index, std::string* out_value) const { + const Value* value; + if (!Get(index, &value)) + return false; + + return value->GetAsString(out_value); +} + +bool ListValue::GetString(size_t index, string16* out_value) const { + const Value* value; + if (!Get(index, &value)) + return false; + + return value->GetAsString(out_value); +} + +bool ListValue::GetBinary(size_t index, const BinaryValue** out_value) const { + const Value* value; + bool result = Get(index, &value); + if (!result || !value->IsType(TYPE_BINARY)) + return false; + + if (out_value) + *out_value = static_cast(value); + + return true; +} + +bool ListValue::GetBinary(size_t index, BinaryValue** out_value) { + return static_cast(*this).GetBinary( + index, + const_cast(out_value)); +} + +bool ListValue::GetDictionary(size_t index, + const DictionaryValue** out_value) const { + const Value* value; + bool result = Get(index, &value); + if (!result || !value->IsType(TYPE_DICTIONARY)) + return false; + + if (out_value) + *out_value = static_cast(value); + + return true; +} + +bool ListValue::GetDictionary(size_t index, DictionaryValue** out_value) { + return static_cast(*this).GetDictionary( + index, + const_cast(out_value)); +} + +bool ListValue::GetList(size_t index, const ListValue** out_value) const { + const Value* value; + bool result = Get(index, &value); + if (!result || !value->IsType(TYPE_LIST)) + return false; + + if (out_value) + *out_value = static_cast(value); + + return true; +} + +bool ListValue::GetList(size_t index, ListValue** out_value) { + return static_cast(*this).GetList( + index, + const_cast(out_value)); +} + +bool ListValue::Remove(size_t index, scoped_ptr* out_value) { + if (index >= list_.size()) + return false; + + if (out_value) + out_value->reset(list_[index]); + else + delete list_[index]; + + list_.erase(list_.begin() + index); + return true; +} + +bool ListValue::Remove(const Value& value, size_t* index) { + for (ValueVector::iterator i(list_.begin()); i != list_.end(); ++i) { + if ((*i)->Equals(&value)) { + size_t previous_index = i - list_.begin(); + delete *i; + list_.erase(i); + + if (index) + *index = previous_index; + return true; + } + } + return false; +} + +ListValue::iterator ListValue::Erase(iterator iter, + scoped_ptr* out_value) { + if (out_value) + out_value->reset(*iter); + else + delete *iter; + + return list_.erase(iter); +} + +void ListValue::Append(Value* in_value) { + DCHECK(in_value); + list_.push_back(in_value); +} + +void ListValue::AppendBoolean(bool in_value) { + Append(CreateBooleanValue(in_value)); +} + +void ListValue::AppendInteger(int in_value) { + Append(CreateIntegerValue(in_value)); +} + +void ListValue::AppendDouble(double in_value) { + Append(CreateDoubleValue(in_value)); +} + +void ListValue::AppendString(const std::string& in_value) { + Append(CreateStringValue(in_value)); +} + +void ListValue::AppendString(const string16& in_value) { + Append(CreateStringValue(in_value)); +} + +void ListValue::AppendStrings(const std::vector& in_values) { + for (std::vector::const_iterator it = in_values.begin(); + it != in_values.end(); ++it) { + AppendString(*it); + } +} + +void ListValue::AppendStrings(const std::vector& in_values) { + for (std::vector::const_iterator it = in_values.begin(); + it != in_values.end(); ++it) { + AppendString(*it); + } +} + +bool ListValue::AppendIfNotPresent(Value* in_value) { + DCHECK(in_value); + for (ValueVector::const_iterator i(list_.begin()); i != list_.end(); ++i) { + if ((*i)->Equals(in_value)) { + delete in_value; + return false; + } + } + list_.push_back(in_value); + return true; +} + +bool ListValue::Insert(size_t index, Value* in_value) { + DCHECK(in_value); + if (index > list_.size()) + return false; + + list_.insert(list_.begin() + index, in_value); + return true; +} + +ListValue::const_iterator ListValue::Find(const Value& value) const { + return std::find_if(list_.begin(), list_.end(), ValueEquals(&value)); +} + +void ListValue::Swap(ListValue* other) { + list_.swap(other->list_); +} + +bool ListValue::GetAsList(ListValue** out_value) { + if (out_value) + *out_value = this; + return true; +} + +bool ListValue::GetAsList(const ListValue** out_value) const { + if (out_value) + *out_value = this; + return true; +} + +ListValue* ListValue::DeepCopy() const { + ListValue* result = new ListValue; + + for (ValueVector::const_iterator i(list_.begin()); i != list_.end(); ++i) + result->Append((*i)->DeepCopy()); + + return result; +} + +bool ListValue::Equals(const Value* other) const { + if (other->GetType() != GetType()) + return false; + + const ListValue* other_list = + static_cast(other); + const_iterator lhs_it, rhs_it; + for (lhs_it = begin(), rhs_it = other_list->begin(); + lhs_it != end() && rhs_it != other_list->end(); + ++lhs_it, ++rhs_it) { + if (!(*lhs_it)->Equals(*rhs_it)) + return false; + } + if (lhs_it != end() || rhs_it != other_list->end()) + return false; + + return true; +} + +ValueSerializer::~ValueSerializer() { +} + +std::ostream& operator<<(std::ostream& out, const Value& value) { + std::string json; + JSONWriter::WriteWithOptions(&value, + JSONWriter::OPTIONS_PRETTY_PRINT, + &json); + return out << json; +} + +} // namespace base diff --git a/base/values.h b/base/values.h new file mode 100644 index 0000000000..4025c751ee --- /dev/null +++ b/base/values.h @@ -0,0 +1,529 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file specifies a recursive data storage class called Value intended for +// storing settings and other persistable data. +// +// A Value represents something that can be stored in JSON or passed to/from +// JavaScript. As such, it is NOT a generalized variant type, since only the +// types supported by JavaScript/JSON are supported. +// +// IN PARTICULAR this means that there is no support for int64 or unsigned +// numbers. Writing JSON with such types would violate the spec. If you need +// something like this, either use a double or make a string value containing +// the number you want. + +#ifndef BASE_VALUES_H_ +#define BASE_VALUES_H_ + +#include +#include +#include +#include + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string16.h" + +// This file declares "using base::Value", etc. at the bottom, so that +// current code can use these classes without the base namespace. In +// new code, please always use base::Value, etc. or add your own +// "using" declaration. +// http://crbug.com/88666 +namespace base { + +class BinaryValue; +class DictionaryValue; +class FundamentalValue; +class ListValue; +class StringValue; +class Value; + +typedef std::vector ValueVector; +typedef std::map ValueMap; + +// The Value class is the base class for Values. A Value can be instantiated +// via the Create*Value() factory methods, or by directly creating instances of +// the subclasses. +// +// See the file-level comment above for more information. +class BASE_EXPORT Value { + public: + enum Type { + TYPE_NULL = 0, + TYPE_BOOLEAN, + TYPE_INTEGER, + TYPE_DOUBLE, + TYPE_STRING, + TYPE_BINARY, + TYPE_DICTIONARY, + TYPE_LIST + // Note: Do not add more types. See the file-level comment above for why. + }; + + virtual ~Value(); + + static Value* CreateNullValue(); + // DEPRECATED: Do not use the following 5 functions. Instead, use + // new FundamentalValue or new StringValue. + static FundamentalValue* CreateBooleanValue(bool in_value); + static FundamentalValue* CreateIntegerValue(int in_value); + static FundamentalValue* CreateDoubleValue(double in_value); + static StringValue* CreateStringValue(const std::string& in_value); + static StringValue* CreateStringValue(const string16& in_value); + + // Returns the type of the value stored by the current Value object. + // Each type will be implemented by only one subclass of Value, so it's + // safe to use the Type to determine whether you can cast from + // Value* to (Implementing Class)*. Also, a Value object never changes + // its type after construction. + Type GetType() const { return type_; } + + // Returns true if the current object represents a given type. + bool IsType(Type type) const { return type == type_; } + + // These methods allow the convenient retrieval of the contents of the Value. + // If the current object can be converted into the given type, the value is + // returned through the |out_value| parameter and true is returned; + // otherwise, false is returned and |out_value| is unchanged. + virtual bool GetAsBoolean(bool* out_value) const; + virtual bool GetAsInteger(int* out_value) const; + virtual bool GetAsDouble(double* out_value) const; + virtual bool GetAsString(std::string* out_value) const; + virtual bool GetAsString(string16* out_value) const; + virtual bool GetAsList(ListValue** out_value); + virtual bool GetAsList(const ListValue** out_value) const; + virtual bool GetAsDictionary(DictionaryValue** out_value); + virtual bool GetAsDictionary(const DictionaryValue** out_value) const; + // Note: Do not add more types. See the file-level comment above for why. + + // This creates a deep copy of the entire Value tree, and returns a pointer + // to the copy. The caller gets ownership of the copy, of course. + // + // Subclasses return their own type directly in their overrides; + // this works because C++ supports covariant return types. + virtual Value* DeepCopy() const; + + // Compares if two Value objects have equal contents. + virtual bool Equals(const Value* other) const; + + // Compares if two Value objects have equal contents. Can handle NULLs. + // NULLs are considered equal but different from Value::CreateNullValue(). + static bool Equals(const Value* a, const Value* b); + + protected: + // These aren't safe for end-users, but they are useful for subclasses. + explicit Value(Type type); + Value(const Value& that); + Value& operator=(const Value& that); + + private: + Type type_; +}; + +// FundamentalValue represents the simple fundamental types of values. +class BASE_EXPORT FundamentalValue : public Value { + public: + explicit FundamentalValue(bool in_value); + explicit FundamentalValue(int in_value); + explicit FundamentalValue(double in_value); + virtual ~FundamentalValue(); + + // Overridden from Value: + virtual bool GetAsBoolean(bool* out_value) const OVERRIDE; + virtual bool GetAsInteger(int* out_value) const OVERRIDE; + virtual bool GetAsDouble(double* out_value) const OVERRIDE; + virtual FundamentalValue* DeepCopy() const OVERRIDE; + virtual bool Equals(const Value* other) const OVERRIDE; + + private: + union { + bool boolean_value_; + int integer_value_; + double double_value_; + }; +}; + +class BASE_EXPORT StringValue : public Value { + public: + // Initializes a StringValue with a UTF-8 narrow character string. + explicit StringValue(const std::string& in_value); + + // Initializes a StringValue with a string16. + explicit StringValue(const string16& in_value); + + virtual ~StringValue(); + + // Overridden from Value: + virtual bool GetAsString(std::string* out_value) const OVERRIDE; + virtual bool GetAsString(string16* out_value) const OVERRIDE; + virtual StringValue* DeepCopy() const OVERRIDE; + virtual bool Equals(const Value* other) const OVERRIDE; + + private: + std::string value_; +}; + +class BASE_EXPORT BinaryValue: public Value { + public: + // Creates a BinaryValue with a null buffer and size of 0. + BinaryValue(); + + // Creates a BinaryValue, taking ownership of the bytes pointed to by + // |buffer|. + BinaryValue(scoped_ptr buffer, size_t size); + + virtual ~BinaryValue(); + + // For situations where you want to keep ownership of your buffer, this + // factory method creates a new BinaryValue by copying the contents of the + // buffer that's passed in. + static BinaryValue* CreateWithCopiedBuffer(const char* buffer, size_t size); + + size_t GetSize() const { return size_; } + + // May return NULL. + char* GetBuffer() { return buffer_.get(); } + const char* GetBuffer() const { return buffer_.get(); } + + // Overridden from Value: + virtual BinaryValue* DeepCopy() const OVERRIDE; + virtual bool Equals(const Value* other) const OVERRIDE; + + private: + scoped_ptr buffer_; + size_t size_; + + DISALLOW_COPY_AND_ASSIGN(BinaryValue); +}; + +// DictionaryValue provides a key-value dictionary with (optional) "path" +// parsing for recursive access; see the comment at the top of the file. Keys +// are |std::string|s and should be UTF-8 encoded. +class BASE_EXPORT DictionaryValue : public Value { + public: + DictionaryValue(); + virtual ~DictionaryValue(); + + // Overridden from Value: + virtual bool GetAsDictionary(DictionaryValue** out_value) OVERRIDE; + virtual bool GetAsDictionary( + const DictionaryValue** out_value) const OVERRIDE; + + // Returns true if the current dictionary has a value for the given key. + bool HasKey(const std::string& key) const; + + // Returns the number of Values in this dictionary. + size_t size() const { return dictionary_.size(); } + + // Returns whether the dictionary is empty. + bool empty() const { return dictionary_.empty(); } + + // Clears any current contents of this dictionary. + void Clear(); + + // Sets the Value associated with the given path starting from this object. + // A path has the form "" or "..[...]", where "." indexes + // into the next DictionaryValue down. Obviously, "." can't be used + // within a key, but there are no other restrictions on keys. + // If the key at any step of the way doesn't exist, or exists but isn't + // a DictionaryValue, a new DictionaryValue will be created and attached + // to the path in that location. + // Note that the dictionary takes ownership of the value referenced by + // |in_value|, and therefore |in_value| must be non-NULL. + void Set(const std::string& path, Value* in_value); + + // Convenience forms of Set(). These methods will replace any existing + // value at that path, even if it has a different type. + void SetBoolean(const std::string& path, bool in_value); + void SetInteger(const std::string& path, int in_value); + void SetDouble(const std::string& path, double in_value); + void SetString(const std::string& path, const std::string& in_value); + void SetString(const std::string& path, const string16& in_value); + + // Like Set(), but without special treatment of '.'. This allows e.g. URLs to + // be used as paths. + void SetWithoutPathExpansion(const std::string& key, Value* in_value); + + // Convenience forms of SetWithoutPathExpansion(). + void SetBooleanWithoutPathExpansion(const std::string& path, bool in_value); + void SetIntegerWithoutPathExpansion(const std::string& path, int in_value); + void SetDoubleWithoutPathExpansion(const std::string& path, double in_value); + void SetStringWithoutPathExpansion(const std::string& path, + const std::string& in_value); + void SetStringWithoutPathExpansion(const std::string& path, + const string16& in_value); + + // Gets the Value associated with the given path starting from this object. + // A path has the form "" or "..[...]", where "." indexes + // into the next DictionaryValue down. If the path can be resolved + // successfully, the value for the last key in the path will be returned + // through the |out_value| parameter, and the function will return true. + // Otherwise, it will return false and |out_value| will be untouched. + // Note that the dictionary always owns the value that's returned. + bool Get(const std::string& path, const Value** out_value) const; + bool Get(const std::string& path, Value** out_value); + + // These are convenience forms of Get(). The value will be retrieved + // and the return value will be true if the path is valid and the value at + // the end of the path can be returned in the form specified. + bool GetBoolean(const std::string& path, bool* out_value) const; + bool GetInteger(const std::string& path, int* out_value) const; + bool GetDouble(const std::string& path, double* out_value) const; + bool GetString(const std::string& path, std::string* out_value) const; + bool GetString(const std::string& path, string16* out_value) const; + bool GetStringASCII(const std::string& path, std::string* out_value) const; + bool GetBinary(const std::string& path, const BinaryValue** out_value) const; + bool GetBinary(const std::string& path, BinaryValue** out_value); + bool GetDictionary(const std::string& path, + const DictionaryValue** out_value) const; + bool GetDictionary(const std::string& path, DictionaryValue** out_value); + bool GetList(const std::string& path, const ListValue** out_value) const; + bool GetList(const std::string& path, ListValue** out_value); + + // Like Get(), but without special treatment of '.'. This allows e.g. URLs to + // be used as paths. + bool GetWithoutPathExpansion(const std::string& key, + const Value** out_value) const; + bool GetWithoutPathExpansion(const std::string& key, Value** out_value); + bool GetBooleanWithoutPathExpansion(const std::string& key, + bool* out_value) const; + bool GetIntegerWithoutPathExpansion(const std::string& key, + int* out_value) const; + bool GetDoubleWithoutPathExpansion(const std::string& key, + double* out_value) const; + bool GetStringWithoutPathExpansion(const std::string& key, + std::string* out_value) const; + bool GetStringWithoutPathExpansion(const std::string& key, + string16* out_value) const; + bool GetDictionaryWithoutPathExpansion( + const std::string& key, + const DictionaryValue** out_value) const; + bool GetDictionaryWithoutPathExpansion(const std::string& key, + DictionaryValue** out_value); + bool GetListWithoutPathExpansion(const std::string& key, + const ListValue** out_value) const; + bool GetListWithoutPathExpansion(const std::string& key, + ListValue** out_value); + + // Removes the Value with the specified path from this dictionary (or one + // of its child dictionaries, if the path is more than just a local key). + // If |out_value| is non-NULL, the removed Value will be passed out via + // |out_value|. If |out_value| is NULL, the removed value will be deleted. + // This method returns true if |path| is a valid path; otherwise it will + // return false and the DictionaryValue object will be unchanged. + virtual bool Remove(const std::string& path, scoped_ptr* out_value); + + // Like Remove(), but without special treatment of '.'. This allows e.g. URLs + // to be used as paths. + virtual bool RemoveWithoutPathExpansion(const std::string& key, + scoped_ptr* out_value); + + // Makes a copy of |this| but doesn't include empty dictionaries and lists in + // the copy. This never returns NULL, even if |this| itself is empty. + DictionaryValue* DeepCopyWithoutEmptyChildren(); + + // Merge |dictionary| into this dictionary. This is done recursively, i.e. any + // sub-dictionaries will be merged as well. In case of key collisions, the + // passed in dictionary takes precedence and data already present will be + // replaced. Values within |dictionary| are deep-copied, so |dictionary| may + // be freed any time after this call. + void MergeDictionary(const DictionaryValue* dictionary); + + // Swaps contents with the |other| dictionary. + virtual void Swap(DictionaryValue* other); + + // This class provides an iterator over both keys and values in the + // dictionary. It can't be used to modify the dictionary. + class BASE_EXPORT Iterator { + public: + explicit Iterator(const DictionaryValue& target); + + bool IsAtEnd() const { return it_ == target_.dictionary_.end(); } + void Advance() { ++it_; } + + const std::string& key() const { return it_->first; } + const Value& value() const { return *it_->second; } + + private: + const DictionaryValue& target_; + ValueMap::const_iterator it_; + }; + + // Overridden from Value: + virtual DictionaryValue* DeepCopy() const OVERRIDE; + virtual bool Equals(const Value* other) const OVERRIDE; + + private: + ValueMap dictionary_; + + DISALLOW_COPY_AND_ASSIGN(DictionaryValue); +}; + +// This type of Value represents a list of other Value values. +class BASE_EXPORT ListValue : public Value { + public: + typedef ValueVector::iterator iterator; + typedef ValueVector::const_iterator const_iterator; + + ListValue(); + virtual ~ListValue(); + + // Clears the contents of this ListValue + void Clear(); + + // Returns the number of Values in this list. + size_t GetSize() const { return list_.size(); } + + // Returns whether the list is empty. + bool empty() const { return list_.empty(); } + + // Sets the list item at the given index to be the Value specified by + // the value given. If the index beyond the current end of the list, null + // Values will be used to pad out the list. + // Returns true if successful, or false if the index was negative or + // the value is a null pointer. + bool Set(size_t index, Value* in_value); + + // Gets the Value at the given index. Modifies |out_value| (and returns true) + // only if the index falls within the current list range. + // Note that the list always owns the Value passed out via |out_value|. + bool Get(size_t index, const Value** out_value) const; + bool Get(size_t index, Value** out_value); + + // Convenience forms of Get(). Modifies |out_value| (and returns true) + // only if the index is valid and the Value at that index can be returned + // in the specified form. + bool GetBoolean(size_t index, bool* out_value) const; + bool GetInteger(size_t index, int* out_value) const; + bool GetDouble(size_t index, double* out_value) const; + bool GetString(size_t index, std::string* out_value) const; + bool GetString(size_t index, string16* out_value) const; + bool GetBinary(size_t index, const BinaryValue** out_value) const; + bool GetBinary(size_t index, BinaryValue** out_value); + bool GetDictionary(size_t index, const DictionaryValue** out_value) const; + bool GetDictionary(size_t index, DictionaryValue** out_value); + bool GetList(size_t index, const ListValue** out_value) const; + bool GetList(size_t index, ListValue** out_value); + + // Removes the Value with the specified index from this list. + // If |out_value| is non-NULL, the removed Value AND ITS OWNERSHIP will be + // passed out via |out_value|. If |out_value| is NULL, the removed value will + // be deleted. This method returns true if |index| is valid; otherwise + // it will return false and the ListValue object will be unchanged. + virtual bool Remove(size_t index, scoped_ptr* out_value); + + // Removes the first instance of |value| found in the list, if any, and + // deletes it. |index| is the location where |value| was found. Returns false + // if not found. + bool Remove(const Value& value, size_t* index); + + // Removes the element at |iter|. If |out_value| is NULL, the value will be + // deleted, otherwise ownership of the value is passed back to the caller. + // Returns an iterator pointing to the location of the element that + // followed the erased element. + iterator Erase(iterator iter, scoped_ptr* out_value); + + // Appends a Value to the end of the list. + void Append(Value* in_value); + + // Convenience forms of Append. + void AppendBoolean(bool in_value); + void AppendInteger(int in_value); + void AppendDouble(double in_value); + void AppendString(const std::string& in_value); + void AppendString(const string16& in_value); + void AppendStrings(const std::vector& in_values); + void AppendStrings(const std::vector& in_values); + + // Appends a Value if it's not already present. Takes ownership of the + // |in_value|. Returns true if successful, or false if the value was already + // present. If the value was already present the |in_value| is deleted. + bool AppendIfNotPresent(Value* in_value); + + // Insert a Value at index. + // Returns true if successful, or false if the index was out of range. + bool Insert(size_t index, Value* in_value); + + // Searches for the first instance of |value| in the list using the Equals + // method of the Value type. + // Returns a const_iterator to the found item or to end() if none exists. + const_iterator Find(const Value& value) const; + + // Swaps contents with the |other| list. + virtual void Swap(ListValue* other); + + // Iteration. + iterator begin() { return list_.begin(); } + iterator end() { return list_.end(); } + + const_iterator begin() const { return list_.begin(); } + const_iterator end() const { return list_.end(); } + + // Overridden from Value: + virtual bool GetAsList(ListValue** out_value) OVERRIDE; + virtual bool GetAsList(const ListValue** out_value) const OVERRIDE; + virtual ListValue* DeepCopy() const OVERRIDE; + virtual bool Equals(const Value* other) const OVERRIDE; + + private: + ValueVector list_; + + DISALLOW_COPY_AND_ASSIGN(ListValue); +}; + +// This interface is implemented by classes that know how to serialize and +// deserialize Value objects. +class BASE_EXPORT ValueSerializer { + public: + virtual ~ValueSerializer(); + + virtual bool Serialize(const Value& root) = 0; + + // This method deserializes the subclass-specific format into a Value object. + // If the return value is non-NULL, the caller takes ownership of returned + // Value. If the return value is NULL, and if error_code is non-NULL, + // error_code will be set with the underlying error. + // If |error_message| is non-null, it will be filled in with a formatted + // error message including the location of the error if appropriate. + virtual Value* Deserialize(int* error_code, std::string* error_str) = 0; +}; + +// Stream operator so Values can be used in assertion statements. In order that +// gtest uses this operator to print readable output on test failures, we must +// override each specific type. Otherwise, the default template implementation +// is preferred over an upcast. +BASE_EXPORT std::ostream& operator<<(std::ostream& out, const Value& value); + +BASE_EXPORT inline std::ostream& operator<<(std::ostream& out, + const FundamentalValue& value) { + return out << static_cast(value); +} + +BASE_EXPORT inline std::ostream& operator<<(std::ostream& out, + const StringValue& value) { + return out << static_cast(value); +} + +BASE_EXPORT inline std::ostream& operator<<(std::ostream& out, + const DictionaryValue& value) { + return out << static_cast(value); +} + +BASE_EXPORT inline std::ostream& operator<<(std::ostream& out, + const ListValue& value) { + return out << static_cast(value); +} + +} // namespace base + +// http://crbug.com/88666 +using base::DictionaryValue; +using base::ListValue; +using base::StringValue; +using base::Value; + +#endif // BASE_VALUES_H_ diff --git a/base/values_unittest.cc b/base/values_unittest.cc new file mode 100644 index 0000000000..733c485d2b --- /dev/null +++ b/base/values_unittest.cc @@ -0,0 +1,776 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/memory/scoped_ptr.h" +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +TEST(ValuesTest, Basic) { + // Test basic dictionary getting/setting + DictionaryValue settings; + std::string homepage = "http://google.com"; + ASSERT_FALSE(settings.GetString("global.homepage", &homepage)); + ASSERT_EQ(std::string("http://google.com"), homepage); + + ASSERT_FALSE(settings.Get("global", NULL)); + settings.Set("global", new FundamentalValue(true)); + ASSERT_TRUE(settings.Get("global", NULL)); + settings.SetString("global.homepage", "http://scurvy.com"); + ASSERT_TRUE(settings.Get("global", NULL)); + homepage = "http://google.com"; + ASSERT_TRUE(settings.GetString("global.homepage", &homepage)); + ASSERT_EQ(std::string("http://scurvy.com"), homepage); + + // Test storing a dictionary in a list. + ListValue* toolbar_bookmarks; + ASSERT_FALSE( + settings.GetList("global.toolbar.bookmarks", &toolbar_bookmarks)); + + toolbar_bookmarks = new ListValue; + settings.Set("global.toolbar.bookmarks", toolbar_bookmarks); + ASSERT_TRUE(settings.GetList("global.toolbar.bookmarks", &toolbar_bookmarks)); + + DictionaryValue* new_bookmark = new DictionaryValue; + new_bookmark->SetString("name", "Froogle"); + new_bookmark->SetString("url", "http://froogle.com"); + toolbar_bookmarks->Append(new_bookmark); + + ListValue* bookmark_list; + ASSERT_TRUE(settings.GetList("global.toolbar.bookmarks", &bookmark_list)); + DictionaryValue* bookmark; + ASSERT_EQ(1U, bookmark_list->GetSize()); + ASSERT_TRUE(bookmark_list->GetDictionary(0, &bookmark)); + std::string bookmark_name = "Unnamed"; + ASSERT_TRUE(bookmark->GetString("name", &bookmark_name)); + ASSERT_EQ(std::string("Froogle"), bookmark_name); + std::string bookmark_url; + ASSERT_TRUE(bookmark->GetString("url", &bookmark_url)); + ASSERT_EQ(std::string("http://froogle.com"), bookmark_url); +} + +TEST(ValuesTest, List) { + scoped_ptr mixed_list(new ListValue()); + mixed_list->Set(0, new FundamentalValue(true)); + mixed_list->Set(1, new FundamentalValue(42)); + mixed_list->Set(2, new FundamentalValue(88.8)); + mixed_list->Set(3, new StringValue("foo")); + ASSERT_EQ(4u, mixed_list->GetSize()); + + Value *value = NULL; + bool bool_value = false; + int int_value = 0; + double double_value = 0.0; + std::string string_value; + + ASSERT_FALSE(mixed_list->Get(4, &value)); + + ASSERT_FALSE(mixed_list->GetInteger(0, &int_value)); + ASSERT_EQ(0, int_value); + ASSERT_FALSE(mixed_list->GetBoolean(1, &bool_value)); + ASSERT_FALSE(bool_value); + ASSERT_FALSE(mixed_list->GetString(2, &string_value)); + ASSERT_EQ("", string_value); + ASSERT_FALSE(mixed_list->GetInteger(2, &int_value)); + ASSERT_EQ(0, int_value); + ASSERT_FALSE(mixed_list->GetBoolean(3, &bool_value)); + ASSERT_FALSE(bool_value); + + ASSERT_TRUE(mixed_list->GetBoolean(0, &bool_value)); + ASSERT_TRUE(bool_value); + ASSERT_TRUE(mixed_list->GetInteger(1, &int_value)); + ASSERT_EQ(42, int_value); + // implicit conversion from Integer to Double should be possible. + ASSERT_TRUE(mixed_list->GetDouble(1, &double_value)); + ASSERT_EQ(42, double_value); + ASSERT_TRUE(mixed_list->GetDouble(2, &double_value)); + ASSERT_EQ(88.8, double_value); + ASSERT_TRUE(mixed_list->GetString(3, &string_value)); + ASSERT_EQ("foo", string_value); + + // Try searching in the mixed list. + base::FundamentalValue sought_value(42); + base::FundamentalValue not_found_value(false); + + ASSERT_NE(mixed_list->end(), mixed_list->Find(sought_value)); + ASSERT_TRUE((*mixed_list->Find(sought_value))->GetAsInteger(&int_value)); + ASSERT_EQ(42, int_value); + ASSERT_EQ(mixed_list->end(), mixed_list->Find(not_found_value)); +} + +TEST(ValuesTest, BinaryValue) { + // Default constructor creates a BinaryValue with a null buffer and size 0. + scoped_ptr binary(new BinaryValue()); + ASSERT_TRUE(binary.get()); + ASSERT_EQ(NULL, binary->GetBuffer()); + ASSERT_EQ(0U, binary->GetSize()); + + // Test the common case of a non-empty buffer + char* buffer = new char[15]; + binary.reset(new BinaryValue(scoped_ptr(buffer), 15)); + ASSERT_TRUE(binary.get()); + ASSERT_TRUE(binary->GetBuffer()); + ASSERT_EQ(buffer, binary->GetBuffer()); + ASSERT_EQ(15U, binary->GetSize()); + + char stack_buffer[42]; + memset(stack_buffer, '!', 42); + binary.reset(BinaryValue::CreateWithCopiedBuffer(stack_buffer, 42)); + ASSERT_TRUE(binary.get()); + ASSERT_TRUE(binary->GetBuffer()); + ASSERT_NE(stack_buffer, binary->GetBuffer()); + ASSERT_EQ(42U, binary->GetSize()); + ASSERT_EQ(0, memcmp(stack_buffer, binary->GetBuffer(), binary->GetSize())); +} + +TEST(ValuesTest, StringValue) { + // Test overloaded CreateStringValue. + scoped_ptr narrow_value(new StringValue("narrow")); + ASSERT_TRUE(narrow_value.get()); + ASSERT_TRUE(narrow_value->IsType(Value::TYPE_STRING)); + scoped_ptr utf16_value(new StringValue(ASCIIToUTF16("utf16"))); + ASSERT_TRUE(utf16_value.get()); + ASSERT_TRUE(utf16_value->IsType(Value::TYPE_STRING)); + + // Test overloaded GetString. + std::string narrow = "http://google.com"; + string16 utf16 = ASCIIToUTF16("http://google.com"); + ASSERT_TRUE(narrow_value->GetAsString(&narrow)); + ASSERT_TRUE(narrow_value->GetAsString(&utf16)); + ASSERT_EQ(std::string("narrow"), narrow); + ASSERT_EQ(ASCIIToUTF16("narrow"), utf16); + + ASSERT_TRUE(utf16_value->GetAsString(&narrow)); + ASSERT_TRUE(utf16_value->GetAsString(&utf16)); + ASSERT_EQ(std::string("utf16"), narrow); + ASSERT_EQ(ASCIIToUTF16("utf16"), utf16); +} + +// This is a Value object that allows us to tell if it's been +// properly deleted by modifying the value of external flag on destruction. +class DeletionTestValue : public Value { + public: + explicit DeletionTestValue(bool* deletion_flag) : Value(TYPE_NULL) { + Init(deletion_flag); // Separate function so that we can use ASSERT_* + } + + void Init(bool* deletion_flag) { + ASSERT_TRUE(deletion_flag); + deletion_flag_ = deletion_flag; + *deletion_flag_ = false; + } + + virtual ~DeletionTestValue() { + *deletion_flag_ = true; + } + + private: + bool* deletion_flag_; +}; + +TEST(ValuesTest, ListDeletion) { + bool deletion_flag = true; + + { + ListValue list; + list.Append(new DeletionTestValue(&deletion_flag)); + EXPECT_FALSE(deletion_flag); + } + EXPECT_TRUE(deletion_flag); + + { + ListValue list; + list.Append(new DeletionTestValue(&deletion_flag)); + EXPECT_FALSE(deletion_flag); + list.Clear(); + EXPECT_TRUE(deletion_flag); + } + + { + ListValue list; + list.Append(new DeletionTestValue(&deletion_flag)); + EXPECT_FALSE(deletion_flag); + EXPECT_TRUE(list.Set(0, Value::CreateNullValue())); + EXPECT_TRUE(deletion_flag); + } +} + +TEST(ValuesTest, ListRemoval) { + bool deletion_flag = true; + scoped_ptr removed_item; + + { + ListValue list; + list.Append(new DeletionTestValue(&deletion_flag)); + EXPECT_FALSE(deletion_flag); + EXPECT_EQ(1U, list.GetSize()); + EXPECT_FALSE(list.Remove(std::numeric_limits::max(), + &removed_item)); + EXPECT_FALSE(list.Remove(1, &removed_item)); + EXPECT_TRUE(list.Remove(0, &removed_item)); + ASSERT_TRUE(removed_item); + EXPECT_EQ(0U, list.GetSize()); + } + EXPECT_FALSE(deletion_flag); + removed_item.reset(); + EXPECT_TRUE(deletion_flag); + + { + ListValue list; + list.Append(new DeletionTestValue(&deletion_flag)); + EXPECT_FALSE(deletion_flag); + EXPECT_TRUE(list.Remove(0, NULL)); + EXPECT_TRUE(deletion_flag); + EXPECT_EQ(0U, list.GetSize()); + } + + { + ListValue list; + DeletionTestValue* value = new DeletionTestValue(&deletion_flag); + list.Append(value); + EXPECT_FALSE(deletion_flag); + size_t index = 0; + list.Remove(*value, &index); + EXPECT_EQ(0U, index); + EXPECT_TRUE(deletion_flag); + EXPECT_EQ(0U, list.GetSize()); + } +} + +TEST(ValuesTest, DictionaryDeletion) { + std::string key = "test"; + bool deletion_flag = true; + + { + DictionaryValue dict; + dict.Set(key, new DeletionTestValue(&deletion_flag)); + EXPECT_FALSE(deletion_flag); + } + EXPECT_TRUE(deletion_flag); + + { + DictionaryValue dict; + dict.Set(key, new DeletionTestValue(&deletion_flag)); + EXPECT_FALSE(deletion_flag); + dict.Clear(); + EXPECT_TRUE(deletion_flag); + } + + { + DictionaryValue dict; + dict.Set(key, new DeletionTestValue(&deletion_flag)); + EXPECT_FALSE(deletion_flag); + dict.Set(key, Value::CreateNullValue()); + EXPECT_TRUE(deletion_flag); + } +} + +TEST(ValuesTest, DictionaryRemoval) { + std::string key = "test"; + bool deletion_flag = true; + scoped_ptr removed_item; + + { + DictionaryValue dict; + dict.Set(key, new DeletionTestValue(&deletion_flag)); + EXPECT_FALSE(deletion_flag); + EXPECT_TRUE(dict.HasKey(key)); + EXPECT_FALSE(dict.Remove("absent key", &removed_item)); + EXPECT_TRUE(dict.Remove(key, &removed_item)); + EXPECT_FALSE(dict.HasKey(key)); + ASSERT_TRUE(removed_item); + } + EXPECT_FALSE(deletion_flag); + removed_item.reset(); + EXPECT_TRUE(deletion_flag); + + { + DictionaryValue dict; + dict.Set(key, new DeletionTestValue(&deletion_flag)); + EXPECT_FALSE(deletion_flag); + EXPECT_TRUE(dict.HasKey(key)); + EXPECT_TRUE(dict.Remove(key, NULL)); + EXPECT_TRUE(deletion_flag); + EXPECT_FALSE(dict.HasKey(key)); + } +} + +TEST(ValuesTest, DictionaryWithoutPathExpansion) { + DictionaryValue dict; + dict.Set("this.is.expanded", Value::CreateNullValue()); + dict.SetWithoutPathExpansion("this.isnt.expanded", Value::CreateNullValue()); + + EXPECT_FALSE(dict.HasKey("this.is.expanded")); + EXPECT_TRUE(dict.HasKey("this")); + Value* value1; + EXPECT_TRUE(dict.Get("this", &value1)); + DictionaryValue* value2; + ASSERT_TRUE(dict.GetDictionaryWithoutPathExpansion("this", &value2)); + EXPECT_EQ(value1, value2); + EXPECT_EQ(1U, value2->size()); + + EXPECT_TRUE(dict.HasKey("this.isnt.expanded")); + Value* value3; + EXPECT_FALSE(dict.Get("this.isnt.expanded", &value3)); + Value* value4; + ASSERT_TRUE(dict.GetWithoutPathExpansion("this.isnt.expanded", &value4)); + EXPECT_EQ(Value::TYPE_NULL, value4->GetType()); +} + +TEST(ValuesTest, DeepCopy) { + DictionaryValue original_dict; + Value* original_null = Value::CreateNullValue(); + original_dict.Set("null", original_null); + FundamentalValue* original_bool = new FundamentalValue(true); + original_dict.Set("bool", original_bool); + FundamentalValue* original_int = new FundamentalValue(42); + original_dict.Set("int", original_int); + FundamentalValue* original_double = new FundamentalValue(3.14); + original_dict.Set("double", original_double); + StringValue* original_string = new StringValue("hello"); + original_dict.Set("string", original_string); + StringValue* original_string16 = new StringValue(ASCIIToUTF16("hello16")); + original_dict.Set("string16", original_string16); + + scoped_ptr original_buffer(new char[42]); + memset(original_buffer.get(), '!', 42); + BinaryValue* original_binary = new BinaryValue(original_buffer.Pass(), 42); + original_dict.Set("binary", original_binary); + + ListValue* original_list = new ListValue(); + FundamentalValue* original_list_element_0 = new FundamentalValue(0); + original_list->Append(original_list_element_0); + FundamentalValue* original_list_element_1 = new FundamentalValue(1); + original_list->Append(original_list_element_1); + original_dict.Set("list", original_list); + + DictionaryValue* original_nested_dictionary = new DictionaryValue(); + original_nested_dictionary->Set("key", new StringValue("value")); + original_dict.Set("dictionary", original_nested_dictionary); + + scoped_ptr copy_dict(original_dict.DeepCopy()); + ASSERT_TRUE(copy_dict.get()); + ASSERT_NE(copy_dict.get(), &original_dict); + + Value* copy_null = NULL; + ASSERT_TRUE(copy_dict->Get("null", ©_null)); + ASSERT_TRUE(copy_null); + ASSERT_NE(copy_null, original_null); + ASSERT_TRUE(copy_null->IsType(Value::TYPE_NULL)); + + Value* copy_bool = NULL; + ASSERT_TRUE(copy_dict->Get("bool", ©_bool)); + ASSERT_TRUE(copy_bool); + ASSERT_NE(copy_bool, original_bool); + ASSERT_TRUE(copy_bool->IsType(Value::TYPE_BOOLEAN)); + bool copy_bool_value = false; + ASSERT_TRUE(copy_bool->GetAsBoolean(©_bool_value)); + ASSERT_TRUE(copy_bool_value); + + Value* copy_int = NULL; + ASSERT_TRUE(copy_dict->Get("int", ©_int)); + ASSERT_TRUE(copy_int); + ASSERT_NE(copy_int, original_int); + ASSERT_TRUE(copy_int->IsType(Value::TYPE_INTEGER)); + int copy_int_value = 0; + ASSERT_TRUE(copy_int->GetAsInteger(©_int_value)); + ASSERT_EQ(42, copy_int_value); + + Value* copy_double = NULL; + ASSERT_TRUE(copy_dict->Get("double", ©_double)); + ASSERT_TRUE(copy_double); + ASSERT_NE(copy_double, original_double); + ASSERT_TRUE(copy_double->IsType(Value::TYPE_DOUBLE)); + double copy_double_value = 0; + ASSERT_TRUE(copy_double->GetAsDouble(©_double_value)); + ASSERT_EQ(3.14, copy_double_value); + + Value* copy_string = NULL; + ASSERT_TRUE(copy_dict->Get("string", ©_string)); + ASSERT_TRUE(copy_string); + ASSERT_NE(copy_string, original_string); + ASSERT_TRUE(copy_string->IsType(Value::TYPE_STRING)); + std::string copy_string_value; + string16 copy_string16_value; + ASSERT_TRUE(copy_string->GetAsString(©_string_value)); + ASSERT_TRUE(copy_string->GetAsString(©_string16_value)); + ASSERT_EQ(std::string("hello"), copy_string_value); + ASSERT_EQ(ASCIIToUTF16("hello"), copy_string16_value); + + Value* copy_string16 = NULL; + ASSERT_TRUE(copy_dict->Get("string16", ©_string16)); + ASSERT_TRUE(copy_string16); + ASSERT_NE(copy_string16, original_string16); + ASSERT_TRUE(copy_string16->IsType(Value::TYPE_STRING)); + ASSERT_TRUE(copy_string16->GetAsString(©_string_value)); + ASSERT_TRUE(copy_string16->GetAsString(©_string16_value)); + ASSERT_EQ(std::string("hello16"), copy_string_value); + ASSERT_EQ(ASCIIToUTF16("hello16"), copy_string16_value); + + Value* copy_binary = NULL; + ASSERT_TRUE(copy_dict->Get("binary", ©_binary)); + ASSERT_TRUE(copy_binary); + ASSERT_NE(copy_binary, original_binary); + ASSERT_TRUE(copy_binary->IsType(Value::TYPE_BINARY)); + ASSERT_NE(original_binary->GetBuffer(), + static_cast(copy_binary)->GetBuffer()); + ASSERT_EQ(original_binary->GetSize(), + static_cast(copy_binary)->GetSize()); + ASSERT_EQ(0, memcmp(original_binary->GetBuffer(), + static_cast(copy_binary)->GetBuffer(), + original_binary->GetSize())); + + Value* copy_value = NULL; + ASSERT_TRUE(copy_dict->Get("list", ©_value)); + ASSERT_TRUE(copy_value); + ASSERT_NE(copy_value, original_list); + ASSERT_TRUE(copy_value->IsType(Value::TYPE_LIST)); + ListValue* copy_list = NULL; + ASSERT_TRUE(copy_value->GetAsList(©_list)); + ASSERT_TRUE(copy_list); + ASSERT_EQ(2U, copy_list->GetSize()); + + Value* copy_list_element_0; + ASSERT_TRUE(copy_list->Get(0, ©_list_element_0)); + ASSERT_TRUE(copy_list_element_0); + ASSERT_NE(copy_list_element_0, original_list_element_0); + int copy_list_element_0_value; + ASSERT_TRUE(copy_list_element_0->GetAsInteger(©_list_element_0_value)); + ASSERT_EQ(0, copy_list_element_0_value); + + Value* copy_list_element_1; + ASSERT_TRUE(copy_list->Get(1, ©_list_element_1)); + ASSERT_TRUE(copy_list_element_1); + ASSERT_NE(copy_list_element_1, original_list_element_1); + int copy_list_element_1_value; + ASSERT_TRUE(copy_list_element_1->GetAsInteger(©_list_element_1_value)); + ASSERT_EQ(1, copy_list_element_1_value); + + copy_value = NULL; + ASSERT_TRUE(copy_dict->Get("dictionary", ©_value)); + ASSERT_TRUE(copy_value); + ASSERT_NE(copy_value, original_nested_dictionary); + ASSERT_TRUE(copy_value->IsType(Value::TYPE_DICTIONARY)); + DictionaryValue* copy_nested_dictionary = NULL; + ASSERT_TRUE(copy_value->GetAsDictionary(©_nested_dictionary)); + ASSERT_TRUE(copy_nested_dictionary); + EXPECT_TRUE(copy_nested_dictionary->HasKey("key")); +} + +TEST(ValuesTest, Equals) { + Value* null1 = Value::CreateNullValue(); + Value* null2 = Value::CreateNullValue(); + EXPECT_NE(null1, null2); + EXPECT_TRUE(null1->Equals(null2)); + + Value* boolean = new FundamentalValue(false); + EXPECT_FALSE(null1->Equals(boolean)); + delete null1; + delete null2; + delete boolean; + + DictionaryValue dv; + dv.SetBoolean("a", false); + dv.SetInteger("b", 2); + dv.SetDouble("c", 2.5); + dv.SetString("d1", "string"); + dv.SetString("d2", ASCIIToUTF16("http://google.com")); + dv.Set("e", Value::CreateNullValue()); + + scoped_ptr copy; + copy.reset(dv.DeepCopy()); + EXPECT_TRUE(dv.Equals(copy.get())); + + ListValue* list = new ListValue; + list->Append(Value::CreateNullValue()); + list->Append(new DictionaryValue); + dv.Set("f", list); + + EXPECT_FALSE(dv.Equals(copy.get())); + copy->Set("f", list->DeepCopy()); + EXPECT_TRUE(dv.Equals(copy.get())); + + list->Append(new FundamentalValue(true)); + EXPECT_FALSE(dv.Equals(copy.get())); + + // Check if Equals detects differences in only the keys. + copy.reset(dv.DeepCopy()); + EXPECT_TRUE(dv.Equals(copy.get())); + copy->Remove("a", NULL); + copy->SetBoolean("aa", false); + EXPECT_FALSE(dv.Equals(copy.get())); +} + +TEST(ValuesTest, StaticEquals) { + scoped_ptr null1(Value::CreateNullValue()); + scoped_ptr null2(Value::CreateNullValue()); + EXPECT_TRUE(Value::Equals(null1.get(), null2.get())); + EXPECT_TRUE(Value::Equals(NULL, NULL)); + + scoped_ptr i42(new FundamentalValue(42)); + scoped_ptr j42(new FundamentalValue(42)); + scoped_ptr i17(new FundamentalValue(17)); + EXPECT_TRUE(Value::Equals(i42.get(), i42.get())); + EXPECT_TRUE(Value::Equals(j42.get(), i42.get())); + EXPECT_TRUE(Value::Equals(i42.get(), j42.get())); + EXPECT_FALSE(Value::Equals(i42.get(), i17.get())); + EXPECT_FALSE(Value::Equals(i42.get(), NULL)); + EXPECT_FALSE(Value::Equals(NULL, i42.get())); + + // NULL and Value::CreateNullValue() are intentionally different: We need + // support for NULL as a return value for "undefined" without caring for + // ownership of the pointer. + EXPECT_FALSE(Value::Equals(null1.get(), NULL)); + EXPECT_FALSE(Value::Equals(NULL, null1.get())); +} + +TEST(ValuesTest, DeepCopyCovariantReturnTypes) { + DictionaryValue original_dict; + Value* original_null = Value::CreateNullValue(); + original_dict.Set("null", original_null); + FundamentalValue* original_bool = new FundamentalValue(true); + original_dict.Set("bool", original_bool); + FundamentalValue* original_int = new FundamentalValue(42); + original_dict.Set("int", original_int); + FundamentalValue* original_double = new FundamentalValue(3.14); + original_dict.Set("double", original_double); + StringValue* original_string = new StringValue("hello"); + original_dict.Set("string", original_string); + StringValue* original_string16 = new StringValue(ASCIIToUTF16("hello16")); + original_dict.Set("string16", original_string16); + + scoped_ptr original_buffer(new char[42]); + memset(original_buffer.get(), '!', 42); + BinaryValue* original_binary = new BinaryValue(original_buffer.Pass(), 42); + original_dict.Set("binary", original_binary); + + ListValue* original_list = new ListValue(); + FundamentalValue* original_list_element_0 = new FundamentalValue(0); + original_list->Append(original_list_element_0); + FundamentalValue* original_list_element_1 = new FundamentalValue(1); + original_list->Append(original_list_element_1); + original_dict.Set("list", original_list); + + Value* original_dict_value = &original_dict; + Value* original_bool_value = original_bool; + Value* original_int_value = original_int; + Value* original_double_value = original_double; + Value* original_string_value = original_string; + Value* original_string16_value = original_string16; + Value* original_binary_value = original_binary; + Value* original_list_value = original_list; + + scoped_ptr copy_dict_value(original_dict_value->DeepCopy()); + scoped_ptr copy_bool_value(original_bool_value->DeepCopy()); + scoped_ptr copy_int_value(original_int_value->DeepCopy()); + scoped_ptr copy_double_value(original_double_value->DeepCopy()); + scoped_ptr copy_string_value(original_string_value->DeepCopy()); + scoped_ptr copy_string16_value(original_string16_value->DeepCopy()); + scoped_ptr copy_binary_value(original_binary_value->DeepCopy()); + scoped_ptr copy_list_value(original_list_value->DeepCopy()); + + EXPECT_TRUE(original_dict_value->Equals(copy_dict_value.get())); + EXPECT_TRUE(original_bool_value->Equals(copy_bool_value.get())); + EXPECT_TRUE(original_int_value->Equals(copy_int_value.get())); + EXPECT_TRUE(original_double_value->Equals(copy_double_value.get())); + EXPECT_TRUE(original_string_value->Equals(copy_string_value.get())); + EXPECT_TRUE(original_string16_value->Equals(copy_string16_value.get())); + EXPECT_TRUE(original_binary_value->Equals(copy_binary_value.get())); + EXPECT_TRUE(original_list_value->Equals(copy_list_value.get())); +} + +TEST(ValuesTest, RemoveEmptyChildren) { + scoped_ptr root(new DictionaryValue); + // Remove empty lists and dictionaries. + root->Set("empty_dict", new DictionaryValue); + root->Set("empty_list", new ListValue); + root->SetWithoutPathExpansion("a.b.c.d.e", new DictionaryValue); + root.reset(root->DeepCopyWithoutEmptyChildren()); + EXPECT_TRUE(root->empty()); + + // Make sure we don't prune too much. + root->SetBoolean("bool", true); + root->Set("empty_dict", new DictionaryValue); + root->SetString("empty_string", std::string()); + root.reset(root->DeepCopyWithoutEmptyChildren()); + EXPECT_EQ(2U, root->size()); + + // Should do nothing. + root.reset(root->DeepCopyWithoutEmptyChildren()); + EXPECT_EQ(2U, root->size()); + + // Nested test cases. These should all reduce back to the bool and string + // set above. + { + root->Set("a.b.c.d.e", new DictionaryValue); + root.reset(root->DeepCopyWithoutEmptyChildren()); + EXPECT_EQ(2U, root->size()); + } + { + DictionaryValue* inner = new DictionaryValue; + root->Set("dict_with_emtpy_children", inner); + inner->Set("empty_dict", new DictionaryValue); + inner->Set("empty_list", new ListValue); + root.reset(root->DeepCopyWithoutEmptyChildren()); + EXPECT_EQ(2U, root->size()); + } + { + ListValue* inner = new ListValue; + root->Set("list_with_empty_children", inner); + inner->Append(new DictionaryValue); + inner->Append(new ListValue); + root.reset(root->DeepCopyWithoutEmptyChildren()); + EXPECT_EQ(2U, root->size()); + } + + // Nested with siblings. + { + ListValue* inner = new ListValue; + root->Set("list_with_empty_children", inner); + inner->Append(new DictionaryValue); + inner->Append(new ListValue); + DictionaryValue* inner2 = new DictionaryValue; + root->Set("dict_with_empty_children", inner2); + inner2->Set("empty_dict", new DictionaryValue); + inner2->Set("empty_list", new ListValue); + root.reset(root->DeepCopyWithoutEmptyChildren()); + EXPECT_EQ(2U, root->size()); + } + + // Make sure nested values don't get pruned. + { + ListValue* inner = new ListValue; + root->Set("list_with_empty_children", inner); + ListValue* inner2 = new ListValue; + inner->Append(new DictionaryValue); + inner->Append(inner2); + inner2->Append(new StringValue("hello")); + root.reset(root->DeepCopyWithoutEmptyChildren()); + EXPECT_EQ(3U, root->size()); + EXPECT_TRUE(root->GetList("list_with_empty_children", &inner)); + EXPECT_EQ(1U, inner->GetSize()); // Dictionary was pruned. + EXPECT_TRUE(inner->GetList(0, &inner2)); + EXPECT_EQ(1U, inner2->GetSize()); + } +} + +TEST(ValuesTest, MergeDictionary) { + scoped_ptr base(new DictionaryValue); + base->SetString("base_key", "base_key_value_base"); + base->SetString("collide_key", "collide_key_value_base"); + DictionaryValue* base_sub_dict = new DictionaryValue; + base_sub_dict->SetString("sub_base_key", "sub_base_key_value_base"); + base_sub_dict->SetString("sub_collide_key", "sub_collide_key_value_base"); + base->Set("sub_dict_key", base_sub_dict); + + scoped_ptr merge(new DictionaryValue); + merge->SetString("merge_key", "merge_key_value_merge"); + merge->SetString("collide_key", "collide_key_value_merge"); + DictionaryValue* merge_sub_dict = new DictionaryValue; + merge_sub_dict->SetString("sub_merge_key", "sub_merge_key_value_merge"); + merge_sub_dict->SetString("sub_collide_key", "sub_collide_key_value_merge"); + merge->Set("sub_dict_key", merge_sub_dict); + + base->MergeDictionary(merge.get()); + + EXPECT_EQ(4U, base->size()); + std::string base_key_value; + EXPECT_TRUE(base->GetString("base_key", &base_key_value)); + EXPECT_EQ("base_key_value_base", base_key_value); // Base value preserved. + std::string collide_key_value; + EXPECT_TRUE(base->GetString("collide_key", &collide_key_value)); + EXPECT_EQ("collide_key_value_merge", collide_key_value); // Replaced. + std::string merge_key_value; + EXPECT_TRUE(base->GetString("merge_key", &merge_key_value)); + EXPECT_EQ("merge_key_value_merge", merge_key_value); // Merged in. + + DictionaryValue* res_sub_dict; + EXPECT_TRUE(base->GetDictionary("sub_dict_key", &res_sub_dict)); + EXPECT_EQ(3U, res_sub_dict->size()); + std::string sub_base_key_value; + EXPECT_TRUE(res_sub_dict->GetString("sub_base_key", &sub_base_key_value)); + EXPECT_EQ("sub_base_key_value_base", sub_base_key_value); // Preserved. + std::string sub_collide_key_value; + EXPECT_TRUE(res_sub_dict->GetString("sub_collide_key", + &sub_collide_key_value)); + EXPECT_EQ("sub_collide_key_value_merge", sub_collide_key_value); // Replaced. + std::string sub_merge_key_value; + EXPECT_TRUE(res_sub_dict->GetString("sub_merge_key", &sub_merge_key_value)); + EXPECT_EQ("sub_merge_key_value_merge", sub_merge_key_value); // Merged in. +} + +TEST(ValuesTest, MergeDictionaryDeepCopy) { + DictionaryValue* child = new DictionaryValue; + child->SetString("test", "value"); + EXPECT_EQ(1U, child->size()); + + std::string value; + EXPECT_TRUE(child->GetString("test", &value)); + EXPECT_EQ("value", value); + + scoped_ptr base(new DictionaryValue); + base->Set("dict", child); + EXPECT_EQ(1U, base->size()); + + DictionaryValue* ptr; + EXPECT_TRUE(base->GetDictionary("dict", &ptr)); + EXPECT_EQ(child, ptr); + + scoped_ptr merged(new DictionaryValue); + merged->MergeDictionary(base.get()); + EXPECT_EQ(1U, merged->size()); + EXPECT_TRUE(merged->GetDictionary("dict", &ptr)); + EXPECT_NE(child, ptr); + EXPECT_TRUE(ptr->GetString("test", &value)); + EXPECT_EQ("value", value); + + child->SetString("test", "overwrite"); + base.reset(); + EXPECT_TRUE(ptr->GetString("test", &value)); + EXPECT_EQ("value", value); +} + +TEST(ValuesTest, DictionaryIterator) { + DictionaryValue dict; + for (DictionaryValue::Iterator it(dict); !it.IsAtEnd(); it.Advance()) { + ADD_FAILURE(); + } + + StringValue value1("value1"); + dict.Set("key1", value1.DeepCopy()); + bool seen1 = false; + for (DictionaryValue::Iterator it(dict); !it.IsAtEnd(); it.Advance()) { + EXPECT_FALSE(seen1); + EXPECT_EQ("key1", it.key()); + EXPECT_TRUE(value1.Equals(&it.value())); + seen1 = true; + } + EXPECT_TRUE(seen1); + + StringValue value2("value2"); + dict.Set("key2", value2.DeepCopy()); + bool seen2 = seen1 = false; + for (DictionaryValue::Iterator it(dict); !it.IsAtEnd(); it.Advance()) { + if (it.key() == "key1") { + EXPECT_FALSE(seen1); + EXPECT_TRUE(value1.Equals(&it.value())); + seen1 = true; + } else if (it.key() == "key2") { + EXPECT_FALSE(seen2); + EXPECT_TRUE(value2.Equals(&it.value())); + seen2 = true; + } else { + ADD_FAILURE(); + } + } + EXPECT_TRUE(seen1); + EXPECT_TRUE(seen2); +} + +} // namespace base diff --git a/base/version.cc b/base/version.cc new file mode 100644 index 0000000000..70d35c192c --- /dev/null +++ b/base/version.cc @@ -0,0 +1,176 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/version.h" + +#include + +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" + +namespace base { + +namespace { + +// Parses the |numbers| vector representing the different numbers +// inside the version string and constructs a vector of valid integers. It stops +// when it reaches an invalid item (including the wildcard character). |parsed| +// is the resulting integer vector. Function returns true if all numbers were +// parsed successfully, false otherwise. +bool ParseVersionNumbers(const std::string& version_str, + std::vector* parsed) { + std::vector numbers; + SplitString(version_str, '.', &numbers); + if (numbers.empty()) + return false; + + for (std::vector::const_iterator it = numbers.begin(); + it != numbers.end(); ++it) { + int num; + if (!StringToInt(*it, &num)) + return false; + + if (num < 0) + return false; + + const uint16 max = 0xFFFF; + if (num > max) + return false; + + // This throws out things like +3, or 032. + if (IntToString(num) != *it) + return false; + + parsed->push_back(static_cast(num)); + } + return true; +} + +// Compares version components in |components1| with components in +// |components2|. Returns -1, 0 or 1 if |components1| is less than, equal to, +// or greater than |components2|, respectively. +int CompareVersionComponents(const std::vector& components1, + const std::vector& components2) { + const size_t count = std::min(components1.size(), components2.size()); + for (size_t i = 0; i < count; ++i) { + if (components1[i] > components2[i]) + return 1; + if (components1[i] < components2[i]) + return -1; + } + if (components1.size() > components2.size()) { + for (size_t i = count; i < components1.size(); ++i) { + if (components1[i] > 0) + return 1; + } + } else if (components1.size() < components2.size()) { + for (size_t i = count; i < components2.size(); ++i) { + if (components2[i] > 0) + return -1; + } + } + return 0; +} + +} // namespace + +Version::Version() { +} + +Version::~Version() { +} + +Version::Version(const std::string& version_str) { + std::vector parsed; + if (!ParseVersionNumbers(version_str, &parsed)) + return; + + components_.swap(parsed); +} + +bool Version::IsValid() const { + return (!components_.empty()); +} + +// static +bool Version::IsValidWildcardString(const std::string& wildcard_string) { + std::string version_string = wildcard_string; + if (EndsWith(wildcard_string.c_str(), ".*", false)) + version_string = wildcard_string.substr(0, wildcard_string.size() - 2); + + Version version(version_string); + return version.IsValid(); +} + +bool Version::IsOlderThan(const std::string& version_str) const { + Version proposed_ver(version_str); + if (!proposed_ver.IsValid()) + return false; + return (CompareTo(proposed_ver) < 0); +} + +int Version::CompareToWildcardString(const std::string& wildcard_string) const { + DCHECK(IsValid()); + DCHECK(Version::IsValidWildcardString(wildcard_string)); + + // Default behavior if the string doesn't end with a wildcard. + if (!EndsWith(wildcard_string.c_str(), ".*", false)) { + Version version(wildcard_string); + DCHECK(version.IsValid()); + return CompareTo(version); + } + + std::vector parsed; + const bool success = ParseVersionNumbers( + wildcard_string.substr(0, wildcard_string.length() - 2), &parsed); + DCHECK(success); + const int comparison = CompareVersionComponents(components_, parsed); + // If the version is smaller than the wildcard version's |parsed| vector, + // then the wildcard has no effect (e.g. comparing 1.2.3 and 1.3.*) and the + // version is still smaller. Same logic for equality (e.g. comparing 1.2.2 to + // 1.2.2.* is 0 regardless of the wildcard). Under this logic, + // 1.2.0.0.0.0 compared to 1.2.* is 0. + if (comparison == -1 || comparison == 0) + return comparison; + + // Catch the case where the digits of |parsed| are found in |components_|, + // which means that the two are equal since |parsed| has a trailing "*". + // (e.g. 1.2.3 vs. 1.2.* will return 0). All other cases return 1 since + // components is greater (e.g. 3.2.3 vs 1.*). + DCHECK_GT(parsed.size(), 0UL); + const size_t min_num_comp = std::min(components_.size(), parsed.size()); + for (size_t i = 0; i < min_num_comp; ++i) { + if (components_[i] != parsed[i]) + return 1; + } + return 0; +} + +bool Version::Equals(const Version& that) const { + DCHECK(IsValid()); + DCHECK(that.IsValid()); + return (CompareTo(that) == 0); +} + +int Version::CompareTo(const Version& other) const { + DCHECK(IsValid()); + DCHECK(other.IsValid()); + return CompareVersionComponents(components_, other.components_); +} + +const std::string Version::GetString() const { + DCHECK(IsValid()); + std::string version_str; + size_t count = components_.size(); + for (size_t i = 0; i < count - 1; ++i) { + version_str.append(IntToString(components_[i])); + version_str.append("."); + } + version_str.append(IntToString(components_[count - 1])); + return version_str; +} + +} // namespace base diff --git a/base/version.h b/base/version.h new file mode 100644 index 0000000000..b3012eb921 --- /dev/null +++ b/base/version.h @@ -0,0 +1,72 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_VERSION_H_ +#define BASE_VERSION_H_ + +#include +#include + +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace base { + +// Version represents a dotted version number, like "1.2.3.4", supporting +// parsing and comparison. +class BASE_EXPORT Version { + public: + // The only thing you can legally do to a default constructed + // Version object is assign to it. + Version(); + + ~Version(); + + // Initializes from a decimal dotted version number, like "0.1.1". + // Each component is limited to a uint16. Call IsValid() to learn + // the outcome. + explicit Version(const std::string& version_str); + + // Returns true if the object contains a valid version number. + bool IsValid() const; + + // Returns true if the version wildcard string is valid. The version wildcard + // string may end with ".*" (e.g. 1.2.*, 1.*). Any other arrangement with "*" + // is invalid (e.g. 1.*.3 or 1.2.3*). This functions defaults to standard + // Version behavior (IsValid) if no wildcard is present. + static bool IsValidWildcardString(const std::string& wildcard_string); + + // Commonly used pattern. Given a valid version object, compare if a + // |version_str| results in a newer version. Returns true if the + // string represents valid version and if the version is greater than + // than the version of this object. + bool IsOlderThan(const std::string& version_str) const; + + bool Equals(const Version& other) const; + + // Returns -1, 0, 1 for <, ==, >. + int CompareTo(const Version& other) const; + + // Given a valid version object, compare if a |wildcard_string| results in a + // newer version. This function will default to CompareTo if the string does + // not end in wildcard sequence ".*". IsValidWildcard(wildcard_string) must be + // true before using this function. + int CompareToWildcardString(const std::string& wildcard_string) const; + + // Return the string representation of this version. + const std::string GetString() const; + + const std::vector& components() const { return components_; } + + private: + std::vector components_; +}; + +} // namespace base + +// TODO(xhwang) remove this when all users are updated to explicitly use the +// namespace +using base::Version; + +#endif // BASE_VERSION_H_ diff --git a/base/version_unittest.cc b/base/version_unittest.cc new file mode 100644 index 0000000000..2a2309ea31 --- /dev/null +++ b/base/version_unittest.cc @@ -0,0 +1,141 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/version.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +TEST(VersionTest, DefaultConstructor) { + Version v; + EXPECT_FALSE(v.IsValid()); +} + +TEST(VersionTest, ValueSemantics) { + Version v1("1.2.3.4"); + EXPECT_TRUE(v1.IsValid()); + Version v3; + EXPECT_FALSE(v3.IsValid()); + { + Version v2(v1); + v3 = v2; + EXPECT_TRUE(v2.IsValid()); + EXPECT_TRUE(v1.Equals(v2)); + } + EXPECT_TRUE(v3.Equals(v1)); +} + +TEST(VersionTest, GetVersionFromString) { + static const struct version_string { + const char* input; + size_t parts; + bool success; + } cases[] = { + {"", 0, false}, + {" ", 0, false}, + {"\t", 0, false}, + {"\n", 0, false}, + {" ", 0, false}, + {".", 0, false}, + {" . ", 0, false}, + {"0", 1, true}, + {"0.0", 2, true}, + {"65537.0", 0, false}, + {"-1.0", 0, false}, + {"1.-1.0", 0, false}, + {"+1.0", 0, false}, + {"1.+1.0", 0, false}, + {"1.0a", 0, false}, + {"1.2.3.4.5.6.7.8.9.0", 10, true}, + {"02.1", 0, false}, + {"f.1", 0, false}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + Version version(cases[i].input); + EXPECT_EQ(cases[i].success, version.IsValid()); + if (cases[i].success) + EXPECT_EQ(cases[i].parts, version.components().size()); + } +} + +TEST(VersionTest, Compare) { + static const struct version_compare { + const char* lhs; + const char* rhs; + int expected; + } cases[] = { + {"1.0", "1.0", 0}, + {"1.0", "0.0", 1}, + {"1.0", "2.0", -1}, + {"1.0", "1.1", -1}, + {"1.1", "1.0", 1}, + {"1.0", "1.0.1", -1}, + {"1.1", "1.0.1", 1}, + {"1.1", "1.0.1", 1}, + {"1.0.0", "1.0", 0}, + {"1.0.3", "1.0.20", -1}, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + Version lhs(cases[i].lhs); + Version rhs(cases[i].rhs); + EXPECT_EQ(lhs.CompareTo(rhs), cases[i].expected) << + cases[i].lhs << " ? " << cases[i].rhs; + + EXPECT_EQ(lhs.IsOlderThan(cases[i].rhs), (cases[i].expected == -1)); + } +} + +TEST(VersionTest, CompareToWildcardString) { + static const struct version_compare { + const char* lhs; + const char* rhs; + int expected; + } cases[] = { + {"1.0", "1.*", 0}, + {"1.0", "0.*", 1}, + {"1.0", "2.*", -1}, + {"1.2.3", "1.2.3.*", 0}, + {"10.0", "1.0.*", 1}, + {"1.0", "3.0.*", -1}, + {"1.4", "1.3.0.*", 1}, + {"1.3.9", "1.3.*", 0}, + {"1.4.1", "1.3.*", 1}, + {"1.3", "1.4.5.*", -1}, + {"1.5", "1.4.5.*", 1}, + {"1.3.9", "1.3.*", 0}, + {"1.2.0.0.0.0", "1.2.*", 0}, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + const Version version(cases[i].lhs); + const int result = version.CompareToWildcardString(cases[i].rhs); + EXPECT_EQ(result, cases[i].expected) << cases[i].lhs << "?" << cases[i].rhs; + } +} + +TEST(VersionTest, IsValidWildcardString) { + static const struct version_compare { + const char* version; + bool expected; + } cases[] = { + {"1.0", true}, + {"", false}, + {"1.2.3.4.5.6", true}, + {"1.2.3.*", true}, + {"1.2.3.5*", false}, + {"1.2.3.56*", false}, + {"1.*.3", false}, + {"20.*", true}, + {"+2.*", false}, + {"*", false}, + {"*.2", false}, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + EXPECT_EQ(Version::IsValidWildcardString(cases[i].version), + cases[i].expected) << cases[i].version << "?" << cases[i].expected; + } +} + +} // namespace diff --git a/base/vlog.cc b/base/vlog.cc new file mode 100644 index 0000000000..946505497c --- /dev/null +++ b/base/vlog.cc @@ -0,0 +1,177 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/vlog.h" + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" + +namespace logging { + +const int VlogInfo::kDefaultVlogLevel = 0; + +struct VlogInfo::VmodulePattern { + enum MatchTarget { MATCH_MODULE, MATCH_FILE }; + + explicit VmodulePattern(const std::string& pattern); + + VmodulePattern(); + + std::string pattern; + int vlog_level; + MatchTarget match_target; +}; + +VlogInfo::VmodulePattern::VmodulePattern(const std::string& pattern) + : pattern(pattern), + vlog_level(VlogInfo::kDefaultVlogLevel), + match_target(MATCH_MODULE) { + // If the pattern contains a {forward,back} slash, we assume that + // it's meant to be tested against the entire __FILE__ string. + std::string::size_type first_slash = pattern.find_first_of("\\/"); + if (first_slash != std::string::npos) + match_target = MATCH_FILE; +} + +VlogInfo::VmodulePattern::VmodulePattern() + : vlog_level(VlogInfo::kDefaultVlogLevel), + match_target(MATCH_MODULE) {} + +VlogInfo::VlogInfo(const std::string& v_switch, + const std::string& vmodule_switch, + int* min_log_level) + : min_log_level_(min_log_level) { + DCHECK(min_log_level != NULL); + + typedef std::pair KVPair; + int vlog_level = 0; + if (!v_switch.empty()) { + if (base::StringToInt(v_switch, &vlog_level)) { + SetMaxVlogLevel(vlog_level); + } else { + DLOG(WARNING) << "Could not parse v switch \"" << v_switch << "\""; + } + } + + std::vector kv_pairs; + if (!base::SplitStringIntoKeyValuePairs( + vmodule_switch, '=', ',', &kv_pairs)) { + DLOG(WARNING) << "Could not fully parse vmodule switch \"" + << vmodule_switch << "\""; + } + for (std::vector::const_iterator it = kv_pairs.begin(); + it != kv_pairs.end(); ++it) { + VmodulePattern pattern(it->first); + if (!base::StringToInt(it->second, &pattern.vlog_level)) { + DLOG(WARNING) << "Parsed vlog level for \"" + << it->first << "=" << it->second + << "\" as " << pattern.vlog_level; + } + vmodule_levels_.push_back(pattern); + } +} + +VlogInfo::~VlogInfo() {} + +namespace { + +// Given a path, returns the basename with the extension chopped off +// (and any -inl suffix). We avoid using FilePath to minimize the +// number of dependencies the logging system has. +base::StringPiece GetModule(const base::StringPiece& file) { + base::StringPiece module(file); + base::StringPiece::size_type last_slash_pos = + module.find_last_of("\\/"); + if (last_slash_pos != base::StringPiece::npos) + module.remove_prefix(last_slash_pos + 1); + base::StringPiece::size_type extension_start = module.rfind('.'); + module = module.substr(0, extension_start); + static const char kInlSuffix[] = "-inl"; + static const int kInlSuffixLen = arraysize(kInlSuffix) - 1; + if (module.ends_with(kInlSuffix)) + module.remove_suffix(kInlSuffixLen); + return module; +} + +} // namespace + +int VlogInfo::GetVlogLevel(const base::StringPiece& file) const { + if (!vmodule_levels_.empty()) { + base::StringPiece module(GetModule(file)); + for (std::vector::const_iterator it = + vmodule_levels_.begin(); it != vmodule_levels_.end(); ++it) { + base::StringPiece target( + (it->match_target == VmodulePattern::MATCH_FILE) ? file : module); + if (MatchVlogPattern(target, it->pattern)) + return it->vlog_level; + } + } + return GetMaxVlogLevel(); +} + +void VlogInfo::SetMaxVlogLevel(int level) { + // Log severity is the negative verbosity. + *min_log_level_ = -level; +} + +int VlogInfo::GetMaxVlogLevel() const { + return -*min_log_level_; +} + +bool MatchVlogPattern(const base::StringPiece& string, + const base::StringPiece& vlog_pattern) { + base::StringPiece p(vlog_pattern); + base::StringPiece s(string); + // Consume characters until the next star. + while (!p.empty() && !s.empty() && (p[0] != '*')) { + switch (p[0]) { + // A slash (forward or back) must match a slash (forward or back). + case '/': + case '\\': + if ((s[0] != '/') && (s[0] != '\\')) + return false; + break; + + // A '?' matches anything. + case '?': + break; + + // Anything else must match literally. + default: + if (p[0] != s[0]) + return false; + break; + } + p.remove_prefix(1), s.remove_prefix(1); + } + + // An empty pattern here matches only an empty string. + if (p.empty()) + return s.empty(); + + // Coalesce runs of consecutive stars. There should be at least + // one. + while (!p.empty() && (p[0] == '*')) + p.remove_prefix(1); + + // Since we moved past the stars, an empty pattern here matches + // anything. + if (p.empty()) + return true; + + // Since we moved past the stars and p is non-empty, if some + // non-empty substring of s matches p, then we ourselves match. + while (!s.empty()) { + if (MatchVlogPattern(s, p)) + return true; + s.remove_prefix(1); + } + + // Otherwise, we couldn't find a match. + return false; +} + +} // namespace diff --git a/base/vlog.h b/base/vlog.h new file mode 100644 index 0000000000..4ef173ef20 --- /dev/null +++ b/base/vlog.h @@ -0,0 +1,78 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_VLOG_H_ +#define BASE_VLOG_H_ + +#include +#include +#include + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/strings/string_piece.h" + +namespace logging { + +// A helper class containing all the settings for vlogging. +class BASE_EXPORT VlogInfo { + public: + static const int kDefaultVlogLevel; + + // |v_switch| gives the default maximal active V-logging level; 0 is + // the default. Normally positive values are used for V-logging + // levels. + // + // |vmodule_switch| gives the per-module maximal V-logging levels to + // override the value given by |v_switch|. + // E.g. "my_module=2,foo*=3" would change the logging level for all + // code in source files "my_module.*" and "foo*.*" ("-inl" suffixes + // are also disregarded for this matching). + // + // |log_severity| points to an int that stores the log level. If a valid + // |v_switch| is provided, it will set the log level, and the default + // vlog severity will be read from there.. + // + // Any pattern containing a forward or backward slash will be tested + // against the whole pathname and not just the module. E.g., + // "*/foo/bar/*=2" would change the logging level for all code in + // source files under a "foo/bar" directory. + VlogInfo(const std::string& v_switch, + const std::string& vmodule_switch, + int* min_log_level); + ~VlogInfo(); + + // Returns the vlog level for a given file (usually taken from + // __FILE__). + int GetVlogLevel(const base::StringPiece& file) const; + + private: + void SetMaxVlogLevel(int level); + int GetMaxVlogLevel() const; + + // VmodulePattern holds all the information for each pattern parsed + // from |vmodule_switch|. + struct VmodulePattern; + std::vector vmodule_levels_; + int* min_log_level_; + + DISALLOW_COPY_AND_ASSIGN(VlogInfo); +}; + +// Returns true if the string passed in matches the vlog pattern. The +// vlog pattern string can contain wildcards like * and ?. ? matches +// exactly one character while * matches 0 or more characters. Also, +// as a special case, a / or \ character matches either / or \. +// +// Examples: +// "kh?n" matches "khan" but not "khn" or "khaan" +// "kh*n" matches "khn", "khan", or even "khaaaaan" +// "/foo\bar" matches "/foo/bar", "\foo\bar", or "/foo\bar" +// (disregarding C escaping rules) +BASE_EXPORT bool MatchVlogPattern(const base::StringPiece& string, + const base::StringPiece& vlog_pattern); + +} // namespace logging + +#endif // BASE_VLOG_H_ diff --git a/base/vlog_unittest.cc b/base/vlog_unittest.cc new file mode 100644 index 0000000000..3a37cd9573 --- /dev/null +++ b/base/vlog_unittest.cc @@ -0,0 +1,126 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/vlog.h" + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" +#include "base/time/time.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace logging { + +namespace { + +TEST(VlogTest, NoVmodule) { + int min_log_level = 0; + EXPECT_EQ(0, + VlogInfo(std::string(), std::string(), &min_log_level) + .GetVlogLevel("test1")); + EXPECT_EQ(0, + VlogInfo("0", std::string(), &min_log_level).GetVlogLevel("test2")); + EXPECT_EQ( + 0, VlogInfo("blah", std::string(), &min_log_level).GetVlogLevel("test3")); + EXPECT_EQ( + 0, + VlogInfo("0blah1", std::string(), &min_log_level).GetVlogLevel("test4")); + EXPECT_EQ(1, + VlogInfo("1", std::string(), &min_log_level).GetVlogLevel("test5")); + EXPECT_EQ(5, + VlogInfo("5", std::string(), &min_log_level).GetVlogLevel("test6")); +} + +TEST(VlogTest, MatchVlogPattern) { + // Degenerate cases. + EXPECT_TRUE(MatchVlogPattern("", "")); + EXPECT_TRUE(MatchVlogPattern("", "****")); + EXPECT_FALSE(MatchVlogPattern("", "x")); + EXPECT_FALSE(MatchVlogPattern("x", "")); + + // Basic. + EXPECT_TRUE(MatchVlogPattern("blah", "blah")); + + // ? should match exactly one character. + EXPECT_TRUE(MatchVlogPattern("blah", "bl?h")); + EXPECT_FALSE(MatchVlogPattern("blh", "bl?h")); + EXPECT_FALSE(MatchVlogPattern("blaah", "bl?h")); + EXPECT_TRUE(MatchVlogPattern("blah", "?lah")); + EXPECT_FALSE(MatchVlogPattern("lah", "?lah")); + EXPECT_FALSE(MatchVlogPattern("bblah", "?lah")); + + // * can match any number (even 0) of characters. + EXPECT_TRUE(MatchVlogPattern("blah", "bl*h")); + EXPECT_TRUE(MatchVlogPattern("blabcdefh", "bl*h")); + EXPECT_TRUE(MatchVlogPattern("blh", "bl*h")); + EXPECT_TRUE(MatchVlogPattern("blah", "*blah")); + EXPECT_TRUE(MatchVlogPattern("ohblah", "*blah")); + EXPECT_TRUE(MatchVlogPattern("blah", "blah*")); + EXPECT_TRUE(MatchVlogPattern("blahhhh", "blah*")); + EXPECT_TRUE(MatchVlogPattern("blahhhh", "blah*")); + EXPECT_TRUE(MatchVlogPattern("blah", "*blah*")); + EXPECT_TRUE(MatchVlogPattern("blahhhh", "*blah*")); + EXPECT_TRUE(MatchVlogPattern("bbbblahhhh", "*blah*")); + + // Multiple *s should work fine. + EXPECT_TRUE(MatchVlogPattern("ballaah", "b*la*h")); + EXPECT_TRUE(MatchVlogPattern("blah", "b*la*h")); + EXPECT_TRUE(MatchVlogPattern("bbbblah", "b*la*h")); + EXPECT_TRUE(MatchVlogPattern("blaaah", "b*la*h")); + + // There should be no escaping going on. + EXPECT_TRUE(MatchVlogPattern("bl\\ah", "bl\\?h")); + EXPECT_FALSE(MatchVlogPattern("bl?h", "bl\\?h")); + EXPECT_TRUE(MatchVlogPattern("bl\\aaaah", "bl\\*h")); + EXPECT_FALSE(MatchVlogPattern("bl*h", "bl\\*h")); + + // Any slash matches any slash. + EXPECT_TRUE(MatchVlogPattern("/b\\lah", "/b\\lah")); + EXPECT_TRUE(MatchVlogPattern("\\b/lah", "/b\\lah")); +} + +TEST(VlogTest, VmoduleBasic) { + const char kVSwitch[] = "-1"; + const char kVModuleSwitch[] = + "foo=,bar=0,baz=blah,,qux=0blah1,quux=1,corge.ext=5"; + int min_log_level = 0; + VlogInfo vlog_info(kVSwitch, kVModuleSwitch, &min_log_level); + EXPECT_EQ(-1, vlog_info.GetVlogLevel("/path/to/grault.cc")); + EXPECT_EQ(0, vlog_info.GetVlogLevel("/path/to/foo.cc")); + EXPECT_EQ(0, vlog_info.GetVlogLevel("D:\\Path\\To\\bar-inl.mm")); + EXPECT_EQ(-1, vlog_info.GetVlogLevel("D:\\path\\to what/bar_unittest.m")); + EXPECT_EQ(0, vlog_info.GetVlogLevel("baz.h")); + EXPECT_EQ(0, vlog_info.GetVlogLevel("/another/path/to/qux.h")); + EXPECT_EQ(1, vlog_info.GetVlogLevel("/path/to/quux")); + EXPECT_EQ(5, vlog_info.GetVlogLevel("c:\\path/to/corge.ext.h")); +} + +TEST(VlogTest, VmoduleDirs) { + const char kVModuleSwitch[] = + "foo/bar.cc=1,baz\\*\\qux.cc=2,*quux/*=3,*/*-inl.h=4"; + int min_log_level = 0; + VlogInfo vlog_info(std::string(), kVModuleSwitch, &min_log_level); + EXPECT_EQ(0, vlog_info.GetVlogLevel("/foo/bar.cc")); + EXPECT_EQ(0, vlog_info.GetVlogLevel("bar.cc")); + EXPECT_EQ(1, vlog_info.GetVlogLevel("foo/bar.cc")); + + EXPECT_EQ(0, vlog_info.GetVlogLevel("baz/grault/qux.h")); + EXPECT_EQ(0, vlog_info.GetVlogLevel("/baz/grault/qux.cc")); + EXPECT_EQ(2, vlog_info.GetVlogLevel("baz/grault/qux.cc")); + EXPECT_EQ(2, vlog_info.GetVlogLevel("baz/grault/blah/qux.cc")); + EXPECT_EQ(2, vlog_info.GetVlogLevel("baz\\grault\\qux.cc")); + EXPECT_EQ(2, vlog_info.GetVlogLevel("baz\\grault//blah\\qux.cc")); + + EXPECT_EQ(0, vlog_info.GetVlogLevel("/foo/bar/baz/quux.cc")); + EXPECT_EQ(3, vlog_info.GetVlogLevel("/foo/bar/baz/quux/grault.cc")); + EXPECT_EQ(3, vlog_info.GetVlogLevel("/foo\\bar/baz\\quux/grault.cc")); + + EXPECT_EQ(0, vlog_info.GetVlogLevel("foo/bar/test-inl.cc")); + EXPECT_EQ(4, vlog_info.GetVlogLevel("foo/bar/test-inl.h")); + EXPECT_EQ(4, vlog_info.GetVlogLevel("foo/bar/baz/blah-inl.h")); +} + +} // namespace + +} // namespace logging diff --git a/base/win/OWNERS b/base/win/OWNERS new file mode 100644 index 0000000000..3aae3d6bec --- /dev/null +++ b/base/win/OWNERS @@ -0,0 +1 @@ +cpu@chromium.org diff --git a/base/win/dllmain.cc b/base/win/dllmain.cc new file mode 100644 index 0000000000..9d2a6dc76c --- /dev/null +++ b/base/win/dllmain.cc @@ -0,0 +1,123 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Windows doesn't support pthread_key_create's destr_function, and in fact +// it's a bit tricky to get code to run when a thread exits. This is +// cargo-cult magic from http://www.codeproject.com/threads/tls.asp. +// We are trying to be compatible with both a LoadLibrary style invocation, as +// well as static linking. This code only needs to be included if we use +// LoadLibrary, but it hooks into the "standard" set of TLS callbacks that are +// provided for static linking. + +// This code is deliberately written to match the style of calls seen in +// base/threading/thread_local_storage_win.cc. Please keep the two in sync if +// coding conventions are changed. + +// WARNING: Do *NOT* try to include this in the construction of the base +// library, even though it potentially drives code in +// base/threading/thread_local_storage_win.cc. If you do, some users will end +// up getting duplicate definition of DllMain() in some of their later links. + +// Force a reference to _tls_used to make the linker create the TLS directory +// if it's not already there (that is, even if __declspec(thread) is not used). +// Force a reference to p_thread_callback_dllmain_typical_entry to prevent whole +// program optimization from discarding the variables. + +#include + +#include "base/compiler_specific.h" +#include "base/win/win_util.h" + +// Indicate if another service is scanning the callbacks. When this becomes +// set to true, then DllMain() will stop supporting the callback service. This +// value is set to true the first time any of our callbacks are called, as that +// shows that some other service is handling callbacks. +static bool linker_notifications_are_active = false; + +// This will be our mostly no-op callback that we'll list. We won't +// deliberately call it, and if it is called, that means we don't need to do any +// of the callbacks anymore. We expect such a call to arrive via a +// THREAD_ATTACH message, long before we'd have to perform our THREAD_DETACH +// callbacks. +static void NTAPI on_callback(PVOID h, DWORD reason, PVOID reserved); + +#ifdef _WIN64 + +#pragma comment(linker, "/INCLUDE:_tls_used") +#pragma comment(linker, "/INCLUDE:p_thread_callback_dllmain_typical_entry") + +#else // _WIN64 + +#pragma comment(linker, "/INCLUDE:__tls_used") +#pragma comment(linker, "/INCLUDE:_p_thread_callback_dllmain_typical_entry") + +#endif // _WIN64 + +// Explicitly depend on tlssup.cc variable to bracket the list of TLS callbacks. +extern "C" PIMAGE_TLS_CALLBACK __xl_a; +extern "C" PIMAGE_TLS_CALLBACK __xl_z; + +// extern "C" suppresses C++ name mangling so we know the symbol names for the +// linker /INCLUDE:symbol pragmas above. +extern "C" { +#ifdef _WIN64 + +// .CRT section is merged with .rdata on x64 so it must be constant data. +#pragma data_seg(push, old_seg) +// Use a typical possible name in the .CRT$XL? list of segments. +#pragma const_seg(".CRT$XLB") +// When defining a const variable, it must have external linkage to be sure the +// linker doesn't discard it. +extern const PIMAGE_TLS_CALLBACK p_thread_callback_dllmain_typical_entry; +const PIMAGE_TLS_CALLBACK p_thread_callback_dllmain_typical_entry = on_callback; +#pragma data_seg(pop, old_seg) + +#else // _WIN64 + +#pragma data_seg(push, old_seg) +// Use a typical possible name in the .CRT$XL? list of segments. +#pragma data_seg(".CRT$XLB") +PIMAGE_TLS_CALLBACK p_thread_callback_dllmain_typical_entry = on_callback; +#pragma data_seg(pop, old_seg) + +#endif // _WIN64 +} // extern "C" + +// Custom crash code to get a unique entry in crash reports. +NOINLINE static void CrashOnProcessDetach() { + *static_cast(0) = 0x356; +} + +// Make DllMain call the listed callbacks. This way any third parties that are +// linked in will also be called. +BOOL WINAPI DllMain(PVOID h, DWORD reason, PVOID reserved) { + if (DLL_PROCESS_DETACH == reason && base::win::ShouldCrashOnProcessDetach()) + CrashOnProcessDetach(); + + if (DLL_THREAD_DETACH != reason && DLL_PROCESS_DETACH != reason) + return true; // We won't service THREAD_ATTACH calls. + + if (linker_notifications_are_active) + return true; // Some other service is doing this work. + + for (PIMAGE_TLS_CALLBACK* it = &__xl_a; it < &__xl_z; ++it) { + if (*it == NULL || *it == on_callback) + continue; // Don't bother to call our own callback. + (*it)(h, reason, reserved); + } + return true; +} + +static void NTAPI on_callback(PVOID h, DWORD reason, PVOID reserved) { + // Do nothing. We were just a place holder in the list used to test that we + // call all items. + // If we are called, it means that some other system is scanning the callbacks + // and we don't need to do so in DllMain(). + linker_notifications_are_active = true; + // Note: If some other routine some how plays this same game... we could both + // decide not to do the scanning , but this trick should suppress + // duplicate calls on Vista, where the runtime takes care of the callbacks, + // and allow us to do the callbacks on XP, where we are currently devoid of + // callbacks (due to an explicit LoadLibrary call). +} diff --git a/base/win/enum_variant.cc b/base/win/enum_variant.cc new file mode 100644 index 0000000000..2975560a9e --- /dev/null +++ b/base/win/enum_variant.cc @@ -0,0 +1,83 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/win/enum_variant.h" + +#include + +#include "base/logging.h" + +namespace base { +namespace win { + +EnumVariant::EnumVariant(unsigned long count) + : items_(new VARIANT[count]), + count_(count), + current_index_(0) { +} + +EnumVariant::~EnumVariant() { +} + +VARIANT* EnumVariant::ItemAt(unsigned long index) { + DCHECK(index < count_); + return &items_[index]; +} + +ULONG STDMETHODCALLTYPE EnumVariant::AddRef() { + return IUnknownImpl::AddRef(); +} + +ULONG STDMETHODCALLTYPE EnumVariant::Release() { + return IUnknownImpl::Release(); +} + +STDMETHODIMP EnumVariant::QueryInterface(REFIID riid, void** ppv) { + if (riid == IID_IEnumVARIANT) { + *ppv = static_cast(this); + AddRef(); + return S_OK; + } + + return IUnknownImpl::QueryInterface(riid, ppv); +} + +STDMETHODIMP EnumVariant::Next(ULONG requested_count, + VARIANT* out_elements, + ULONG* out_elements_received) { + unsigned long count = std::min(requested_count, count_ - current_index_); + for (unsigned long i = 0; i < count; ++i) + out_elements[i] = items_[current_index_ + i]; + current_index_ += count; + *out_elements_received = count; + + return (count == requested_count ? S_OK : S_FALSE); +} + +STDMETHODIMP EnumVariant::Skip(ULONG skip_count) { + unsigned long count = skip_count; + if (current_index_ + count > count_) + count = count_ - current_index_; + + current_index_ += count; + return (count == skip_count ? S_OK : S_FALSE); +} + +STDMETHODIMP EnumVariant::Reset() { + current_index_ = 0; + return S_OK; +} + +STDMETHODIMP EnumVariant::Clone(IEnumVARIANT** out_cloned_object) { + EnumVariant* other = new EnumVariant(count_); + if (count_ > 0) + memcpy(other->ItemAt(0), &items_[0], count_ * sizeof(VARIANT)); + other->Skip(current_index_); + other->AddRef(); + *out_cloned_object = other; + return S_OK; +} + +} // namespace win +} // namespace base diff --git a/base/win/enum_variant.h b/base/win/enum_variant.h new file mode 100644 index 0000000000..82f8750c2a --- /dev/null +++ b/base/win/enum_variant.h @@ -0,0 +1,52 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_WIN_ENUM_VARIANT_H_ +#define BASE_WIN_ENUM_VARIANT_H_ + +#include + +#include "base/memory/scoped_ptr.h" +#include "base/win/iunknown_impl.h" + +namespace base { +namespace win { + +// A simple implementation of IEnumVARIANT. +class BASE_EXPORT EnumVariant + : public IEnumVARIANT, + public IUnknownImpl { + public: + // The constructor allocates an array of size |count|. Then use + // ItemAt to set the value of each item in the array to initialize it. + explicit EnumVariant(unsigned long count); + + // Returns a mutable pointer to the item at position |index|. + VARIANT* ItemAt(unsigned long index); + + // IUnknown. + ULONG STDMETHODCALLTYPE AddRef() OVERRIDE; + ULONG STDMETHODCALLTYPE Release() OVERRIDE; + STDMETHODIMP QueryInterface(REFIID riid, void** ppv) OVERRIDE; + + // IEnumVARIANT. + STDMETHODIMP Next(ULONG requested_count, + VARIANT* out_elements, + ULONG* out_elements_received); + STDMETHODIMP Skip(ULONG skip_count); + STDMETHODIMP Reset(); + STDMETHODIMP Clone(IEnumVARIANT** out_cloned_object); + + private: + ~EnumVariant(); + + scoped_ptr items_; + unsigned long count_; + unsigned long current_index_; +}; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_ENUM_VARIANT_H_ diff --git a/base/win/enum_variant_unittest.cc b/base/win/enum_variant_unittest.cc new file mode 100644 index 0000000000..99645a2650 --- /dev/null +++ b/base/win/enum_variant_unittest.cc @@ -0,0 +1,118 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/win/enum_variant.h" + +#include "base/win/scoped_com_initializer.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace win { + +TEST(EnumVariantTest, EmptyEnumVariant) { + ScopedCOMInitializer com_initializer; + + EnumVariant* ev = new EnumVariant(0); + ev->AddRef(); + + IUnknown* iunknown; + EXPECT_TRUE(SUCCEEDED( + ev->QueryInterface(IID_IUnknown, reinterpret_cast(&iunknown)))); + iunknown->Release(); + + IEnumVARIANT* ienumvariant; + EXPECT_TRUE(SUCCEEDED( + ev->QueryInterface(IID_IEnumVARIANT, + reinterpret_cast(&ienumvariant)))); + EXPECT_EQ(ev, ienumvariant); + ienumvariant->Release(); + + VARIANT out_element; + ULONG out_received = 0; + EXPECT_EQ(S_FALSE, ev->Next(1, &out_element, &out_received)); + EXPECT_EQ(0, out_received); + + EXPECT_EQ(S_FALSE, ev->Skip(1)); + + EXPECT_EQ(S_OK, ev->Reset()); + + IEnumVARIANT* ev2 = NULL; + EXPECT_EQ(S_OK, ev->Clone(&ev2)); + + EXPECT_NE(static_cast(NULL), ev2); + EXPECT_NE(ev, ev2); + EXPECT_EQ(S_FALSE, ev2->Skip(1)); + EXPECT_EQ(S_OK, ev2->Reset()); + + ULONG ev2_finalrefcount = ev2->Release(); + EXPECT_EQ(0, ev2_finalrefcount); + + ULONG ev_finalrefcount = ev->Release(); + EXPECT_EQ(0, ev_finalrefcount); +} + +TEST(EnumVariantTest, SimpleEnumVariant) { + ScopedCOMInitializer com_initializer; + + EnumVariant* ev = new EnumVariant(3); + ev->AddRef(); + ev->ItemAt(0)->vt = VT_I4; + ev->ItemAt(0)->lVal = 10; + ev->ItemAt(1)->vt = VT_I4; + ev->ItemAt(1)->lVal = 20; + ev->ItemAt(2)->vt = VT_I4; + ev->ItemAt(2)->lVal = 30; + + // Get elements one at a time. + VARIANT out_element; + ULONG out_received = 0; + EXPECT_EQ(S_OK, ev->Next(1, &out_element, &out_received)); + EXPECT_EQ(1, out_received); + EXPECT_EQ(VT_I4, out_element.vt); + EXPECT_EQ(10, out_element.lVal); + EXPECT_EQ(S_OK, ev->Skip(1)); + EXPECT_EQ(S_OK, ev->Next(1, &out_element, &out_received)); + EXPECT_EQ(1, out_received); + EXPECT_EQ(VT_I4, out_element.vt); + EXPECT_EQ(30, out_element.lVal); + EXPECT_EQ(S_FALSE, ev->Next(1, &out_element, &out_received)); + + // Reset and get all elements at once. + VARIANT out_elements[3]; + EXPECT_EQ(S_OK, ev->Reset()); + EXPECT_EQ(S_OK, ev->Next(3, out_elements, &out_received)); + EXPECT_EQ(3, out_received); + EXPECT_EQ(VT_I4, out_elements[0].vt); + EXPECT_EQ(10, out_elements[0].lVal); + EXPECT_EQ(VT_I4, out_elements[1].vt); + EXPECT_EQ(20, out_elements[1].lVal); + EXPECT_EQ(VT_I4, out_elements[2].vt); + EXPECT_EQ(30, out_elements[2].lVal); + EXPECT_EQ(S_FALSE, ev->Next(1, &out_element, &out_received)); + + // Clone it. + IEnumVARIANT* ev2 = NULL; + EXPECT_EQ(S_OK, ev->Clone(&ev2)); + EXPECT_TRUE(ev2 != NULL); + EXPECT_EQ(S_FALSE, ev->Next(1, &out_element, &out_received)); + EXPECT_EQ(S_OK, ev2->Reset()); + EXPECT_EQ(S_OK, ev2->Next(3, out_elements, &out_received)); + EXPECT_EQ(3, out_received); + EXPECT_EQ(VT_I4, out_elements[0].vt); + EXPECT_EQ(10, out_elements[0].lVal); + EXPECT_EQ(VT_I4, out_elements[1].vt); + EXPECT_EQ(20, out_elements[1].lVal); + EXPECT_EQ(VT_I4, out_elements[2].vt); + EXPECT_EQ(30, out_elements[2].lVal); + EXPECT_EQ(S_FALSE, ev2->Next(1, &out_element, &out_received)); + + ULONG ev2_finalrefcount = ev2->Release(); + EXPECT_EQ(0, ev2_finalrefcount); + + ULONG ev_finalrefcount = ev->Release(); + EXPECT_EQ(0, ev_finalrefcount); +} + +} // namespace win +} // namespace base diff --git a/base/win/event_trace_consumer.h b/base/win/event_trace_consumer.h new file mode 100644 index 0000000000..c1b42b4fd9 --- /dev/null +++ b/base/win/event_trace_consumer.h @@ -0,0 +1,148 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Declaration of a Windows event trace consumer base class. +#ifndef BASE_WIN_EVENT_TRACE_CONSUMER_H_ +#define BASE_WIN_EVENT_TRACE_CONSUMER_H_ + +#include +#include +#include +#include +#include "base/basictypes.h" + +namespace base { +namespace win { + +// This class is a base class that makes it easier to consume events +// from realtime or file sessions. Concrete consumers need to sublass +// a specialization of this class and override the ProcessEvent and/or +// the ProcessBuffer methods to implement the event consumption logic. +// Usage might look like: +// class MyConsumer: public EtwTraceConsumerBase { +// protected: +// static VOID WINAPI ProcessEvent(PEVENT_TRACE event); +// }; +// +// MyConsumer consumer; +// consumer.OpenFileSession(file_path); +// consumer.Consume(); +template +class EtwTraceConsumerBase { + public: + // Constructs a closed consumer. + EtwTraceConsumerBase() { + } + + ~EtwTraceConsumerBase() { + Close(); + } + + // Opens the named realtime session, which must be existent. + // Note: You can use OpenRealtimeSession or OpenFileSession + // to open as many as MAXIMUM_WAIT_OBJECTS (63) sessions at + // any one time, though only one of them may be a realtime + // session. + HRESULT OpenRealtimeSession(const wchar_t* session_name); + + // Opens the event trace log in "file_name", which must be a full or + // relative path to an existing event trace log file. + // Note: You can use OpenRealtimeSession or OpenFileSession + // to open as many as kNumSessions at any one time. + HRESULT OpenFileSession(const wchar_t* file_name); + + // Consume all open sessions from beginning to end. + HRESULT Consume(); + + // Close all open sessions. + HRESULT Close(); + + protected: + // Override in subclasses to handle events. + static void ProcessEvent(EVENT_TRACE* event) { + } + // Override in subclasses to handle buffers. + static bool ProcessBuffer(EVENT_TRACE_LOGFILE* buffer) { + return true; // keep going + } + + protected: + // Currently open sessions. + std::vector trace_handles_; + + private: + // These delegate to ImplClass callbacks with saner signatures. + static void WINAPI ProcessEventCallback(EVENT_TRACE* event) { + ImplClass::ProcessEvent(event); + } + static ULONG WINAPI ProcessBufferCallback(PEVENT_TRACE_LOGFILE buffer) { + return ImplClass::ProcessBuffer(buffer); + } + + DISALLOW_COPY_AND_ASSIGN(EtwTraceConsumerBase); +}; + +template inline +HRESULT EtwTraceConsumerBase::OpenRealtimeSession( + const wchar_t* session_name) { + EVENT_TRACE_LOGFILE logfile = {}; + logfile.LoggerName = const_cast(session_name); + logfile.LogFileMode = EVENT_TRACE_REAL_TIME_MODE; + logfile.BufferCallback = &ProcessBufferCallback; + logfile.EventCallback = &ProcessEventCallback; + logfile.Context = this; + TRACEHANDLE trace_handle = ::OpenTrace(&logfile); + if (reinterpret_cast(INVALID_HANDLE_VALUE) == trace_handle) + return HRESULT_FROM_WIN32(::GetLastError()); + + trace_handles_.push_back(trace_handle); + return S_OK; +} + +template inline +HRESULT EtwTraceConsumerBase::OpenFileSession( + const wchar_t* file_name) { + EVENT_TRACE_LOGFILE logfile = {}; + logfile.LogFileName = const_cast(file_name); + logfile.BufferCallback = &ProcessBufferCallback; + logfile.EventCallback = &ProcessEventCallback; + logfile.Context = this; + TRACEHANDLE trace_handle = ::OpenTrace(&logfile); + if (reinterpret_cast(INVALID_HANDLE_VALUE) == trace_handle) + return HRESULT_FROM_WIN32(::GetLastError()); + + trace_handles_.push_back(trace_handle); + return S_OK; +} + +template inline +HRESULT EtwTraceConsumerBase::Consume() { + ULONG err = ::ProcessTrace(&trace_handles_[0], + trace_handles_.size(), + NULL, + NULL); + return HRESULT_FROM_WIN32(err); +} + +template inline +HRESULT EtwTraceConsumerBase::Close() { + HRESULT hr = S_OK; + for (size_t i = 0; i < trace_handles_.size(); ++i) { + if (NULL != trace_handles_[i]) { + ULONG ret = ::CloseTrace(trace_handles_[i]); + trace_handles_[i] = NULL; + + if (FAILED(HRESULT_FROM_WIN32(ret))) + hr = HRESULT_FROM_WIN32(ret); + } + } + trace_handles_.clear(); + + return hr; +} + +} // namespace win +} // namespace base + +#endif // BASE_WIN_EVENT_TRACE_CONSUMER_H_ diff --git a/base/win/event_trace_consumer_unittest.cc b/base/win/event_trace_consumer_unittest.cc new file mode 100644 index 0000000000..d238192c4f --- /dev/null +++ b/base/win/event_trace_consumer_unittest.cc @@ -0,0 +1,383 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Unit tests for event trace consumer base class. +#include "base/win/event_trace_consumer.h" + +#include + +#include + +#include "base/basictypes.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/logging.h" +#include "base/process/process.h" +#include "base/strings/stringprintf.h" +#include "base/win/event_trace_controller.h" +#include "base/win/event_trace_provider.h" +#include "base/win/scoped_handle.h" +#include "testing/gtest/include/gtest/gtest.h" + +#include // NOLINT - has to be last + +namespace base { +namespace win { + +namespace { + +typedef std::list EventQueue; + +class TestConsumer: public EtwTraceConsumerBase { + public: + TestConsumer() { + sank_event_.Set(::CreateEvent(NULL, TRUE, FALSE, NULL)); + ClearQueue(); + } + + ~TestConsumer() { + ClearQueue(); + sank_event_.Close(); + } + + void ClearQueue() { + EventQueue::const_iterator it(events_.begin()), end(events_.end()); + + for (; it != end; ++it) { + delete [] it->MofData; + } + + events_.clear(); + } + + static void EnqueueEvent(EVENT_TRACE* event) { + events_.push_back(*event); + EVENT_TRACE& back = events_.back(); + + if (NULL != event->MofData && 0 != event->MofLength) { + back.MofData = new char[event->MofLength]; + memcpy(back.MofData, event->MofData, event->MofLength); + } + } + + static void ProcessEvent(EVENT_TRACE* event) { + EnqueueEvent(event); + ::SetEvent(sank_event_.Get()); + } + + static ScopedHandle sank_event_; + static EventQueue events_; + + private: + DISALLOW_COPY_AND_ASSIGN(TestConsumer); +}; + +ScopedHandle TestConsumer::sank_event_; +EventQueue TestConsumer::events_; + +class EtwTraceConsumerBaseTest: public testing::Test { + public: + EtwTraceConsumerBaseTest() + : session_name_(StringPrintf(L"TestSession-%d", + Process::Current().pid())) { + } + + virtual void SetUp() { + // Cleanup any potentially dangling sessions. + EtwTraceProperties ignore; + EtwTraceController::Stop(session_name_.c_str(), &ignore); + + // Allocate a new GUID for each provider test. + ASSERT_HRESULT_SUCCEEDED(::CoCreateGuid(&test_provider_)); + } + + virtual void TearDown() { + // Cleanup any potentially danging sessions. + EtwTraceProperties ignore; + EtwTraceController::Stop(session_name_.c_str(), &ignore); + } + + protected: + GUID test_provider_; + std::wstring session_name_; +}; + +} // namespace + +TEST_F(EtwTraceConsumerBaseTest, Initialize) { + TestConsumer consumer_; +} + +TEST_F(EtwTraceConsumerBaseTest, OpenRealtimeSucceedsWhenNoSession) { + TestConsumer consumer_; + + ASSERT_HRESULT_SUCCEEDED( + consumer_.OpenRealtimeSession(session_name_.c_str())); +} + +TEST_F(EtwTraceConsumerBaseTest, ConsumerImmediateFailureWhenNoSession) { + TestConsumer consumer_; + + ASSERT_HRESULT_SUCCEEDED( + consumer_.OpenRealtimeSession(session_name_.c_str())); + ASSERT_HRESULT_FAILED(consumer_.Consume()); +} + +namespace { + +class EtwTraceConsumerRealtimeTest: public EtwTraceConsumerBaseTest { + public: + virtual void SetUp() { + EtwTraceConsumerBaseTest::SetUp(); + + ASSERT_HRESULT_SUCCEEDED( + consumer_.OpenRealtimeSession(session_name_.c_str())); + } + + virtual void TearDown() { + consumer_.Close(); + + EtwTraceConsumerBaseTest::TearDown(); + } + + DWORD ConsumerThread() { + ::SetEvent(consumer_ready_.Get()); + + HRESULT hr = consumer_.Consume(); + return hr; + } + + static DWORD WINAPI ConsumerThreadMainProc(void* arg) { + return reinterpret_cast(arg)-> + ConsumerThread(); + } + + HRESULT StartConsumerThread() { + consumer_ready_.Set(::CreateEvent(NULL, TRUE, FALSE, NULL)); + EXPECT_TRUE(consumer_ready_ != NULL); + consumer_thread_.Set(::CreateThread(NULL, 0, ConsumerThreadMainProc, + this, 0, NULL)); + if (NULL == consumer_thread_.Get()) + return HRESULT_FROM_WIN32(::GetLastError()); + + HRESULT hr = S_OK; + HANDLE events[] = { consumer_ready_, consumer_thread_ }; + DWORD result = ::WaitForMultipleObjects(arraysize(events), events, + FALSE, INFINITE); + switch (result) { + case WAIT_OBJECT_0: + // The event was set, the consumer_ is ready. + return S_OK; + case WAIT_OBJECT_0 + 1: { + // The thread finished. This may race with the event, so check + // explicitly for the event here, before concluding there's trouble. + if (WAIT_OBJECT_0 == ::WaitForSingleObject(consumer_ready_, 0)) + return S_OK; + DWORD exit_code = 0; + if (::GetExitCodeThread(consumer_thread_, &exit_code)) + return exit_code; + else + return HRESULT_FROM_WIN32(::GetLastError()); + break; + } + default: + return E_UNEXPECTED; + break; + } + + return hr; + } + + // Waits for consumer_ thread to exit, and returns its exit code. + HRESULT JoinConsumerThread() { + if (WAIT_OBJECT_0 != ::WaitForSingleObject(consumer_thread_, INFINITE)) + return HRESULT_FROM_WIN32(::GetLastError()); + + DWORD exit_code = 0; + if (::GetExitCodeThread(consumer_thread_, &exit_code)) + return exit_code; + + return HRESULT_FROM_WIN32(::GetLastError()); + } + + TestConsumer consumer_; + ScopedHandle consumer_ready_; + ScopedHandle consumer_thread_; +}; + +} // namespace + +TEST_F(EtwTraceConsumerRealtimeTest, ConsumerReturnsWhenSessionClosed) { + EtwTraceController controller; + + HRESULT hr = controller.StartRealtimeSession(session_name_.c_str(), + 100 * 1024); + if (hr == E_ACCESSDENIED) { + VLOG(1) << "You must be an administrator to run this test on Vista"; + return; + } + + // Start the consumer_. + ASSERT_HRESULT_SUCCEEDED(StartConsumerThread()); + + // Wait around for the consumer_ thread a bit. + ASSERT_EQ(WAIT_TIMEOUT, ::WaitForSingleObject(consumer_thread_, 50)); + + ASSERT_HRESULT_SUCCEEDED(controller.Stop(NULL)); + + // The consumer_ returns success on session stop. + ASSERT_HRESULT_SUCCEEDED(JoinConsumerThread()); +} + +namespace { + +// {57E47923-A549-476f-86CA-503D57F59E62} +DEFINE_GUID(kTestEventType, + 0x57e47923, 0xa549, 0x476f, 0x86, 0xca, 0x50, 0x3d, 0x57, 0xf5, 0x9e, 0x62); + +} // namespace + +TEST_F(EtwTraceConsumerRealtimeTest, ConsumeEvent) { + EtwTraceController controller; + HRESULT hr = controller.StartRealtimeSession(session_name_.c_str(), + 100 * 1024); + if (hr == E_ACCESSDENIED) { + VLOG(1) << "You must be an administrator to run this test on Vista"; + return; + } + + ASSERT_HRESULT_SUCCEEDED(controller.EnableProvider(test_provider_, + TRACE_LEVEL_VERBOSE, 0xFFFFFFFF)); + + EtwTraceProvider provider(test_provider_); + ASSERT_EQ(ERROR_SUCCESS, provider.Register()); + + // Start the consumer_. + ASSERT_HRESULT_SUCCEEDED(StartConsumerThread()); + + ASSERT_EQ(0, TestConsumer::events_.size()); + + EtwMofEvent<1> event(kTestEventType, 1, TRACE_LEVEL_ERROR); + EXPECT_EQ(ERROR_SUCCESS, provider.Log(&event.header)); + + EXPECT_EQ(WAIT_OBJECT_0, ::WaitForSingleObject(TestConsumer::sank_event_, + INFINITE)); + ASSERT_HRESULT_SUCCEEDED(controller.Stop(NULL)); + ASSERT_HRESULT_SUCCEEDED(JoinConsumerThread()); + ASSERT_NE(0u, TestConsumer::events_.size()); +} + +namespace { + +// We run events through a file session to assert that +// the content comes through. +class EtwTraceConsumerDataTest: public EtwTraceConsumerBaseTest { + public: + EtwTraceConsumerDataTest() { + } + + virtual void SetUp() { + EtwTraceConsumerBaseTest::SetUp(); + + EtwTraceProperties prop; + EtwTraceController::Stop(session_name_.c_str(), &prop); + + // Create a temp dir for this test. + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + // Construct a temp file name in our dir. + temp_file_ = temp_dir_.path().Append(L"test.etl"); + } + + virtual void TearDown() { + EXPECT_TRUE(base::DeleteFile(temp_file_, false)); + + EtwTraceConsumerBaseTest::TearDown(); + } + + HRESULT LogEventToTempSession(PEVENT_TRACE_HEADER header) { + EtwTraceController controller; + + // Set up a file session. + HRESULT hr = controller.StartFileSession(session_name_.c_str(), + temp_file_.value().c_str()); + if (FAILED(hr)) + return hr; + + // Enable our provider. + EXPECT_HRESULT_SUCCEEDED(controller.EnableProvider(test_provider_, + TRACE_LEVEL_VERBOSE, 0xFFFFFFFF)); + + EtwTraceProvider provider(test_provider_); + // Then register our provider, means we get a session handle immediately. + EXPECT_EQ(ERROR_SUCCESS, provider.Register()); + // Trace the event, it goes to the temp file. + EXPECT_EQ(ERROR_SUCCESS, provider.Log(header)); + EXPECT_HRESULT_SUCCEEDED(controller.DisableProvider(test_provider_)); + EXPECT_HRESULT_SUCCEEDED(provider.Unregister()); + EXPECT_HRESULT_SUCCEEDED(controller.Flush(NULL)); + EXPECT_HRESULT_SUCCEEDED(controller.Stop(NULL)); + + return S_OK; + } + + HRESULT ConsumeEventFromTempSession() { + // Now consume the event(s). + TestConsumer consumer_; + HRESULT hr = consumer_.OpenFileSession(temp_file_.value().c_str()); + if (SUCCEEDED(hr)) + hr = consumer_.Consume(); + consumer_.Close(); + // And nab the result. + events_.swap(TestConsumer::events_); + return hr; + } + + HRESULT RoundTripEvent(PEVENT_TRACE_HEADER header, PEVENT_TRACE* trace) { + base::DeleteFile(temp_file_, false); + + HRESULT hr = LogEventToTempSession(header); + if (SUCCEEDED(hr)) + hr = ConsumeEventFromTempSession(); + + if (FAILED(hr)) + return hr; + + // We should now have the event in the queue. + if (events_.empty()) + return E_FAIL; + + *trace = &events_.back(); + return S_OK; + } + + EventQueue events_; + ScopedTempDir temp_dir_; + FilePath temp_file_; +}; + +} // namespace + + +TEST_F(EtwTraceConsumerDataTest, RoundTrip) { + EtwMofEvent<1> event(kTestEventType, 1, TRACE_LEVEL_ERROR); + + static const char kData[] = "This is but test data"; + event.fields[0].DataPtr = reinterpret_cast(kData); + event.fields[0].Length = sizeof(kData); + + PEVENT_TRACE trace = NULL; + HRESULT hr = RoundTripEvent(&event.header, &trace); + if (hr == E_ACCESSDENIED) { + VLOG(1) << "You must be an administrator to run this test on Vista"; + return; + } + ASSERT_HRESULT_SUCCEEDED(hr) << "RoundTripEvent failed"; + ASSERT_TRUE(NULL != trace); + ASSERT_EQ(sizeof(kData), trace->MofLength); + ASSERT_STREQ(kData, reinterpret_cast(trace->MofData)); +} + +} // namespace win +} // namespace base diff --git a/base/win/event_trace_controller.cc b/base/win/event_trace_controller.cc new file mode 100644 index 0000000000..0391fbc301 --- /dev/null +++ b/base/win/event_trace_controller.cc @@ -0,0 +1,173 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Implementation of a Windows event trace controller class. +#include "base/win/event_trace_controller.h" +#include "base/logging.h" + +namespace base { +namespace win { + +EtwTraceProperties::EtwTraceProperties() { + memset(buffer_, 0, sizeof(buffer_)); + EVENT_TRACE_PROPERTIES* prop = get(); + + prop->Wnode.BufferSize = sizeof(buffer_); + prop->Wnode.Flags = WNODE_FLAG_TRACED_GUID; + prop->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES); + prop->LogFileNameOffset = sizeof(EVENT_TRACE_PROPERTIES) + + sizeof(wchar_t) * kMaxStringLen; +} + +HRESULT EtwTraceProperties::SetLoggerName(const wchar_t* logger_name) { + size_t len = wcslen(logger_name) + 1; + if (kMaxStringLen < len) + return E_INVALIDARG; + + memcpy(buffer_ + get()->LoggerNameOffset, + logger_name, + sizeof(wchar_t) * len); + return S_OK; +} + +HRESULT EtwTraceProperties::SetLoggerFileName(const wchar_t* logger_file_name) { + size_t len = wcslen(logger_file_name) + 1; + if (kMaxStringLen < len) + return E_INVALIDARG; + + memcpy(buffer_ + get()->LogFileNameOffset, + logger_file_name, + sizeof(wchar_t) * len); + return S_OK; +} + +EtwTraceController::EtwTraceController() : session_(NULL) { +} + +EtwTraceController::~EtwTraceController() { + Stop(NULL); +} + +HRESULT EtwTraceController::Start(const wchar_t* session_name, + EtwTraceProperties* prop) { + DCHECK(NULL == session_ && session_name_.empty()); + EtwTraceProperties ignore; + if (prop == NULL) + prop = &ignore; + + HRESULT hr = Start(session_name, prop, &session_); + if (SUCCEEDED(hr)) + session_name_ = session_name; + + return hr; +} + +HRESULT EtwTraceController::StartFileSession(const wchar_t* session_name, + const wchar_t* logfile_path, bool realtime) { + DCHECK(NULL == session_ && session_name_.empty()); + + EtwTraceProperties prop; + prop.SetLoggerFileName(logfile_path); + EVENT_TRACE_PROPERTIES& p = *prop.get(); + p.Wnode.ClientContext = 1; // QPC timer accuracy. + p.LogFileMode = EVENT_TRACE_FILE_MODE_SEQUENTIAL; // Sequential log. + if (realtime) + p.LogFileMode |= EVENT_TRACE_REAL_TIME_MODE; + + p.MaximumFileSize = 100; // 100M file size. + p.FlushTimer = 30; // 30 seconds flush lag. + return Start(session_name, &prop); +} + +HRESULT EtwTraceController::StartRealtimeSession(const wchar_t* session_name, + size_t buffer_size) { + DCHECK(NULL == session_ && session_name_.empty()); + EtwTraceProperties prop; + EVENT_TRACE_PROPERTIES& p = *prop.get(); + p.LogFileMode = EVENT_TRACE_REAL_TIME_MODE | EVENT_TRACE_USE_PAGED_MEMORY; + p.FlushTimer = 1; // flush every second. + p.BufferSize = 16; // 16 K buffers. + p.LogFileNameOffset = 0; + return Start(session_name, &prop); +} + +HRESULT EtwTraceController::EnableProvider(REFGUID provider, UCHAR level, + ULONG flags) { + ULONG error = ::EnableTrace(TRUE, flags, level, &provider, session_); + return HRESULT_FROM_WIN32(error); +} + +HRESULT EtwTraceController::DisableProvider(REFGUID provider) { + ULONG error = ::EnableTrace(FALSE, 0, 0, &provider, session_); + return HRESULT_FROM_WIN32(error); +} + +HRESULT EtwTraceController::Stop(EtwTraceProperties* properties) { + EtwTraceProperties ignore; + if (properties == NULL) + properties = &ignore; + + ULONG error = ::ControlTrace(session_, NULL, properties->get(), + EVENT_TRACE_CONTROL_STOP); + if (ERROR_SUCCESS != error) + return HRESULT_FROM_WIN32(error); + + session_ = NULL; + session_name_.clear(); + return S_OK; +} + +HRESULT EtwTraceController::Flush(EtwTraceProperties* properties) { + EtwTraceProperties ignore; + if (properties == NULL) + properties = &ignore; + + ULONG error = ::ControlTrace(session_, NULL, properties->get(), + EVENT_TRACE_CONTROL_FLUSH); + if (ERROR_SUCCESS != error) + return HRESULT_FROM_WIN32(error); + + return S_OK; +} + +HRESULT EtwTraceController::Start(const wchar_t* session_name, + EtwTraceProperties* properties, TRACEHANDLE* session_handle) { + DCHECK(properties != NULL); + ULONG err = ::StartTrace(session_handle, session_name, properties->get()); + return HRESULT_FROM_WIN32(err); +} + +HRESULT EtwTraceController::Query(const wchar_t* session_name, + EtwTraceProperties* properties) { + ULONG err = ::ControlTrace(NULL, session_name, properties->get(), + EVENT_TRACE_CONTROL_QUERY); + return HRESULT_FROM_WIN32(err); +}; + +HRESULT EtwTraceController::Update(const wchar_t* session_name, + EtwTraceProperties* properties) { + DCHECK(properties != NULL); + ULONG err = ::ControlTrace(NULL, session_name, properties->get(), + EVENT_TRACE_CONTROL_UPDATE); + return HRESULT_FROM_WIN32(err); +} + +HRESULT EtwTraceController::Stop(const wchar_t* session_name, + EtwTraceProperties* properties) { + DCHECK(properties != NULL); + ULONG err = ::ControlTrace(NULL, session_name, properties->get(), + EVENT_TRACE_CONTROL_STOP); + return HRESULT_FROM_WIN32(err); +} + +HRESULT EtwTraceController::Flush(const wchar_t* session_name, + EtwTraceProperties* properties) { + DCHECK(properties != NULL); + ULONG err = ::ControlTrace(NULL, session_name, properties->get(), + EVENT_TRACE_CONTROL_FLUSH); + return HRESULT_FROM_WIN32(err); +} + +} // namespace win +} // namespace base diff --git a/base/win/event_trace_controller.h b/base/win/event_trace_controller.h new file mode 100644 index 0000000000..69e755b468 --- /dev/null +++ b/base/win/event_trace_controller.h @@ -0,0 +1,151 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Declaration of a Windows event trace controller class. +// The controller takes care of creating and manipulating event trace +// sessions. +// +// Event tracing for Windows is a system-provided service that provides +// logging control and high-performance transport for generic, binary trace +// events. Event trace providers register with the system by their name, +// which is a GUID, and can from that point forward receive callbacks that +// start or end tracing and that change their trace level and enable mask. +// +// A trace controller can create an event tracing session, which either +// sends events to a binary file, or to a realtime consumer, or both. +// +// A trace consumer consumes events from zero or one realtime session, +// as well as potentially from multiple binary trace files. +#ifndef BASE_WIN_EVENT_TRACE_CONTROLLER_H_ +#define BASE_WIN_EVENT_TRACE_CONTROLLER_H_ + +#include +#include +#include +#include + +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace base { +namespace win { + +// Utility class to make it easier to work with EVENT_TRACE_PROPERTIES. +// The EVENT_TRACE_PROPERTIES structure contains information about an +// event tracing session. +class BASE_EXPORT EtwTraceProperties { + public: + EtwTraceProperties(); + + EVENT_TRACE_PROPERTIES* get() { + return &properties_; + } + + const EVENT_TRACE_PROPERTIES* get() const { + return reinterpret_cast(&properties_); + } + + const wchar_t* GetLoggerName() const { + return reinterpret_cast(buffer_ + get()->LoggerNameOffset); + } + + // Copies logger_name to the properties structure. + HRESULT SetLoggerName(const wchar_t* logger_name); + const wchar_t* GetLoggerFileName() const { + return reinterpret_cast(buffer_ + get()->LogFileNameOffset); + } + + // Copies logger_file_name to the properties structure. + HRESULT SetLoggerFileName(const wchar_t* logger_file_name); + + // Max string len for name and session name is 1024 per documentation. + static const size_t kMaxStringLen = 1024; + // Properties buffer allocates space for header and for + // max length for name and session name. + static const size_t kBufSize = sizeof(EVENT_TRACE_PROPERTIES) + + 2 * sizeof(wchar_t) * (kMaxStringLen); + + private: + // The EVENT_TRACE_PROPERTIES structure needs to be overlaid on a + // larger buffer to allow storing the logger name and logger file + // name contiguously with the structure. + union { + public: + // Our properties header. + EVENT_TRACE_PROPERTIES properties_; + // The actual size of the buffer is forced by this member. + char buffer_[kBufSize]; + }; + + DISALLOW_COPY_AND_ASSIGN(EtwTraceProperties); +}; + +// This class implements an ETW controller, which knows how to start and +// stop event tracing sessions, as well as controlling ETW provider +// log levels and enable bit masks under the session. +class BASE_EXPORT EtwTraceController { + public: + EtwTraceController(); + ~EtwTraceController(); + + // Start a session with given name and properties. + HRESULT Start(const wchar_t* session_name, EtwTraceProperties* prop); + + // Starts a session tracing to a file with some default properties. + HRESULT StartFileSession(const wchar_t* session_name, + const wchar_t* logfile_path, + bool realtime = false); + + // Starts a realtime session with some default properties. + HRESULT StartRealtimeSession(const wchar_t* session_name, + size_t buffer_size); + + // Enables "provider" at "level" for this session. + // This will cause all providers registered with the GUID + // "provider" to start tracing at the new level, systemwide. + HRESULT EnableProvider(const GUID& provider, UCHAR level, + ULONG flags = 0xFFFFFFFF); + // Disables "provider". + HRESULT DisableProvider(const GUID& provider); + + // Stops our session and retrieve the new properties of the session, + // properties may be NULL. + HRESULT Stop(EtwTraceProperties* properties); + + // Flushes our session and retrieve the current properties, + // properties may be NULL. + HRESULT Flush(EtwTraceProperties* properties); + + // Static utility functions for controlling + // sessions we don't necessarily own. + static HRESULT Start(const wchar_t* session_name, + EtwTraceProperties* properties, + TRACEHANDLE* session_handle); + + static HRESULT Query(const wchar_t* session_name, + EtwTraceProperties* properties); + + static HRESULT Update(const wchar_t* session_name, + EtwTraceProperties* properties); + + static HRESULT Stop(const wchar_t* session_name, + EtwTraceProperties* properties); + static HRESULT Flush(const wchar_t* session_name, + EtwTraceProperties* properties); + + // Accessors. + TRACEHANDLE session() const { return session_; } + const wchar_t* session_name() const { return session_name_.c_str(); } + + private: + std::wstring session_name_; + TRACEHANDLE session_; + + DISALLOW_COPY_AND_ASSIGN(EtwTraceController); +}; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_EVENT_TRACE_CONTROLLER_H_ diff --git a/base/win/event_trace_controller_unittest.cc b/base/win/event_trace_controller_unittest.cc new file mode 100644 index 0000000000..16bf1e134a --- /dev/null +++ b/base/win/event_trace_controller_unittest.cc @@ -0,0 +1,239 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Unit tests for event trace controller. + +#include +#include + +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/logging.h" +#include "base/process/process.h" +#include "base/strings/stringprintf.h" +#include "base/sys_info.h" +#include "base/win/event_trace_controller.h" +#include "base/win/event_trace_provider.h" +#include "base/win/scoped_handle.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace win { + +namespace { + +DEFINE_GUID(kGuidNull, + 0x0000000, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0); + +const ULONG kTestProviderFlags = 0xCAFEBABE; + +class TestingProvider: public EtwTraceProvider { + public: + explicit TestingProvider(const GUID& provider_name) + : EtwTraceProvider(provider_name) { + callback_event_.Set(::CreateEvent(NULL, TRUE, FALSE, NULL)); + } + + void WaitForCallback() { + ::WaitForSingleObject(callback_event_.Get(), INFINITE); + ::ResetEvent(callback_event_.Get()); + } + + private: + virtual void OnEventsEnabled() { + ::SetEvent(callback_event_.Get()); + } + virtual void PostEventsDisabled() { + ::SetEvent(callback_event_.Get()); + } + + ScopedHandle callback_event_; + + DISALLOW_COPY_AND_ASSIGN(TestingProvider); +}; + +} // namespace + +TEST(EtwTracePropertiesTest, Initialization) { + EtwTraceProperties prop; + + EVENT_TRACE_PROPERTIES* p = prop.get(); + EXPECT_NE(0u, p->Wnode.BufferSize); + EXPECT_EQ(0u, p->Wnode.ProviderId); + EXPECT_EQ(0u, p->Wnode.HistoricalContext); + + EXPECT_TRUE(kGuidNull == p->Wnode.Guid); + EXPECT_EQ(0, p->Wnode.ClientContext); + EXPECT_EQ(WNODE_FLAG_TRACED_GUID, p->Wnode.Flags); + + EXPECT_EQ(0, p->BufferSize); + EXPECT_EQ(0, p->MinimumBuffers); + EXPECT_EQ(0, p->MaximumBuffers); + EXPECT_EQ(0, p->MaximumFileSize); + EXPECT_EQ(0, p->LogFileMode); + EXPECT_EQ(0, p->FlushTimer); + EXPECT_EQ(0, p->EnableFlags); + EXPECT_EQ(0, p->AgeLimit); + + EXPECT_EQ(0, p->NumberOfBuffers); + EXPECT_EQ(0, p->FreeBuffers); + EXPECT_EQ(0, p->EventsLost); + EXPECT_EQ(0, p->BuffersWritten); + EXPECT_EQ(0, p->LogBuffersLost); + EXPECT_EQ(0, p->RealTimeBuffersLost); + EXPECT_EQ(0, p->LoggerThreadId); + EXPECT_NE(0u, p->LogFileNameOffset); + EXPECT_NE(0u, p->LoggerNameOffset); +} + +TEST(EtwTracePropertiesTest, Strings) { + EtwTraceProperties prop; + + ASSERT_STREQ(L"", prop.GetLoggerFileName()); + ASSERT_STREQ(L"", prop.GetLoggerName()); + + std::wstring name(1023, L'A'); + ASSERT_HRESULT_SUCCEEDED(prop.SetLoggerFileName(name.c_str())); + ASSERT_HRESULT_SUCCEEDED(prop.SetLoggerName(name.c_str())); + ASSERT_STREQ(name.c_str(), prop.GetLoggerFileName()); + ASSERT_STREQ(name.c_str(), prop.GetLoggerName()); + + std::wstring name2(1024, L'A'); + ASSERT_HRESULT_FAILED(prop.SetLoggerFileName(name2.c_str())); + ASSERT_HRESULT_FAILED(prop.SetLoggerName(name2.c_str())); +} + +namespace { + +class EtwTraceControllerTest : public testing::Test { + public: + EtwTraceControllerTest() + : session_name_( + StringPrintf(L"TestSession-%d", Process::Current().pid())) { + } + + virtual void SetUp() { + EtwTraceProperties ignore; + EtwTraceController::Stop(session_name_.c_str(), &ignore); + + // Allocate a new provider name GUID for each test. + ASSERT_HRESULT_SUCCEEDED(::CoCreateGuid(&test_provider_)); + } + + virtual void TearDown() { + EtwTraceProperties prop; + EtwTraceController::Stop(session_name_.c_str(), &prop); + } + + protected: + GUID test_provider_; + std::wstring session_name_; +}; + +} // namespace + +TEST_F(EtwTraceControllerTest, Initialize) { + EtwTraceController controller; + + EXPECT_EQ(NULL, controller.session()); + EXPECT_STREQ(L"", controller.session_name()); +} + + +TEST_F(EtwTraceControllerTest, StartRealTimeSession) { + EtwTraceController controller; + + HRESULT hr = controller.StartRealtimeSession(session_name_.c_str(), + 100 * 1024); + if (hr == E_ACCESSDENIED) { + VLOG(1) << "You must be an administrator to run this test on Vista"; + return; + } + + EXPECT_TRUE(NULL != controller.session()); + EXPECT_STREQ(session_name_.c_str(), controller.session_name()); + + EXPECT_HRESULT_SUCCEEDED(controller.Stop(NULL)); + EXPECT_EQ(NULL, controller.session()); + EXPECT_STREQ(L"", controller.session_name()); +} + +TEST_F(EtwTraceControllerTest, StartFileSession) { + ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + FilePath temp; + ASSERT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir.path(), &temp)); + + EtwTraceController controller; + HRESULT hr = controller.StartFileSession(session_name_.c_str(), + temp.value().c_str()); + if (hr == E_ACCESSDENIED) { + VLOG(1) << "You must be an administrator to run this test on Vista"; + base::DeleteFile(temp, false); + return; + } + + EXPECT_TRUE(NULL != controller.session()); + EXPECT_STREQ(session_name_.c_str(), controller.session_name()); + + EXPECT_HRESULT_SUCCEEDED(controller.Stop(NULL)); + EXPECT_EQ(NULL, controller.session()); + EXPECT_STREQ(L"", controller.session_name()); + base::DeleteFile(temp, false); +} + +TEST_F(EtwTraceControllerTest, EnableDisable) { + TestingProvider provider(test_provider_); + + EXPECT_EQ(ERROR_SUCCESS, provider.Register()); + EXPECT_EQ(NULL, provider.session_handle()); + + EtwTraceController controller; + HRESULT hr = controller.StartRealtimeSession(session_name_.c_str(), + 100 * 1024); + if (hr == E_ACCESSDENIED) { + VLOG(1) << "You must be an administrator to run this test on Vista"; + return; + } + + EXPECT_HRESULT_SUCCEEDED(controller.EnableProvider(test_provider_, + TRACE_LEVEL_VERBOSE, kTestProviderFlags)); + + provider.WaitForCallback(); + + EXPECT_EQ(TRACE_LEVEL_VERBOSE, provider.enable_level()); + EXPECT_EQ(kTestProviderFlags, provider.enable_flags()); + + EXPECT_HRESULT_SUCCEEDED(controller.DisableProvider(test_provider_)); + + provider.WaitForCallback(); + + EXPECT_EQ(0, provider.enable_level()); + EXPECT_EQ(0, provider.enable_flags()); + + EXPECT_EQ(ERROR_SUCCESS, provider.Unregister()); + + // Enable the provider again, before registering. + EXPECT_HRESULT_SUCCEEDED(controller.EnableProvider(test_provider_, + TRACE_LEVEL_VERBOSE, kTestProviderFlags)); + + // Register the provider again, the settings above + // should take immediate effect. + EXPECT_EQ(ERROR_SUCCESS, provider.Register()); + + EXPECT_EQ(TRACE_LEVEL_VERBOSE, provider.enable_level()); + EXPECT_EQ(kTestProviderFlags, provider.enable_flags()); + + EXPECT_HRESULT_SUCCEEDED(controller.Stop(NULL)); + + provider.WaitForCallback(); + + // Session should have wound down. + EXPECT_EQ(0, provider.enable_level()); + EXPECT_EQ(0, provider.enable_flags()); +} + +} // namespace win +} // namespace base diff --git a/base/win/event_trace_provider.cc b/base/win/event_trace_provider.cc new file mode 100644 index 0000000000..8fcf67d3d7 --- /dev/null +++ b/base/win/event_trace_provider.cc @@ -0,0 +1,134 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +#include "base/win/event_trace_provider.h" +#include +#include + +namespace base { +namespace win { + +TRACE_GUID_REGISTRATION EtwTraceProvider::obligatory_guid_registration_ = { + &GUID_NULL, + NULL +}; + +EtwTraceProvider::EtwTraceProvider(const GUID& provider_name) + : provider_name_(provider_name), registration_handle_(NULL), + session_handle_(NULL), enable_flags_(0), enable_level_(0) { +} + +EtwTraceProvider::EtwTraceProvider() + : provider_name_(GUID_NULL), registration_handle_(NULL), + session_handle_(NULL), enable_flags_(0), enable_level_(0) { +} + +EtwTraceProvider::~EtwTraceProvider() { + Unregister(); +} + +ULONG EtwTraceProvider::EnableEvents(void* buffer) { + session_handle_ = ::GetTraceLoggerHandle(buffer); + if (NULL == session_handle_) { + return ::GetLastError(); + } + + enable_flags_ = ::GetTraceEnableFlags(session_handle_); + enable_level_ = ::GetTraceEnableLevel(session_handle_); + + // Give subclasses a chance to digest the state change. + OnEventsEnabled(); + + return ERROR_SUCCESS; +} + +ULONG EtwTraceProvider::DisableEvents() { + // Give subclasses a chance to digest the state change. + OnEventsDisabled(); + + enable_level_ = 0; + enable_flags_ = 0; + session_handle_ = NULL; + + PostEventsDisabled(); + + return ERROR_SUCCESS; +} + +ULONG EtwTraceProvider::Callback(WMIDPREQUESTCODE request, void* buffer) { + switch (request) { + case WMI_ENABLE_EVENTS: + return EnableEvents(buffer); + case WMI_DISABLE_EVENTS: + return DisableEvents(); + default: + return ERROR_INVALID_PARAMETER; + } + // Not reached. +} + +ULONG WINAPI EtwTraceProvider::ControlCallback(WMIDPREQUESTCODE request, + void* context, ULONG *reserved, void* buffer) { + EtwTraceProvider *provider = reinterpret_cast(context); + + return provider->Callback(request, buffer); +} + +ULONG EtwTraceProvider::Register() { + if (provider_name_ == GUID_NULL) + return ERROR_INVALID_NAME; + + return ::RegisterTraceGuids(ControlCallback, this, &provider_name_, + 1, &obligatory_guid_registration_, NULL, NULL, ®istration_handle_); +} + +ULONG EtwTraceProvider::Unregister() { + // If a session is active, notify subclasses that it's going away. + if (session_handle_ != NULL) + DisableEvents(); + + ULONG ret = ::UnregisterTraceGuids(registration_handle_); + + registration_handle_ = NULL; + + return ret; +} + +ULONG EtwTraceProvider::Log(const EtwEventClass& event_class, + EtwEventType type, EtwEventLevel level, const char *message) { + if (NULL == session_handle_ || enable_level_ < level) + return ERROR_SUCCESS; // No one listening. + + EtwMofEvent<1> event(event_class, type, level); + + event.fields[0].DataPtr = reinterpret_cast(message); + event.fields[0].Length = message ? + static_cast(sizeof(message[0]) * (1 + strlen(message))) : 0; + + return ::TraceEvent(session_handle_, &event.header); +} + +ULONG EtwTraceProvider::Log(const EtwEventClass& event_class, + EtwEventType type, EtwEventLevel level, const wchar_t *message) { + if (NULL == session_handle_ || enable_level_ < level) + return ERROR_SUCCESS; // No one listening. + + EtwMofEvent<1> event(event_class, type, level); + + event.fields[0].DataPtr = reinterpret_cast(message); + event.fields[0].Length = message ? + static_cast(sizeof(message[0]) * (1 + wcslen(message))) : 0; + + return ::TraceEvent(session_handle_, &event.header); +} + +ULONG EtwTraceProvider::Log(EVENT_TRACE_HEADER* event) { + if (enable_level_ < event->Class.Level) + return ERROR_SUCCESS; + + return ::TraceEvent(session_handle_, event); +} + +} // namespace win +} // namespace base diff --git a/base/win/event_trace_provider.h b/base/win/event_trace_provider.h new file mode 100644 index 0000000000..7907347b72 --- /dev/null +++ b/base/win/event_trace_provider.h @@ -0,0 +1,180 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Declaration of a Windows event trace provider class, to allow using +// Windows Event Tracing for logging transport and control. +#ifndef BASE_WIN_EVENT_TRACE_PROVIDER_H_ +#define BASE_WIN_EVENT_TRACE_PROVIDER_H_ + +#include +#include +#include + +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace base { +namespace win { + +typedef GUID EtwEventClass; +typedef UCHAR EtwEventType; +typedef UCHAR EtwEventLevel; +typedef USHORT EtwEventVersion; +typedef ULONG EtwEventFlags; + +// Base class is a POD for correctness. +template struct EtwMofEventBase { + EVENT_TRACE_HEADER header; + MOF_FIELD fields[N]; +}; + +// Utility class to auto-initialize event trace header structures. +template class EtwMofEvent: public EtwMofEventBase { + public: + typedef EtwMofEventBase Super; + + // Clang and the C++ standard don't allow unqualified lookup into dependent + // bases, hence these using decls to explicitly pull the names out. + using EtwMofEventBase::header; + using EtwMofEventBase::fields; + + EtwMofEvent() { + memset(static_cast(this), 0, sizeof(Super)); + } + + EtwMofEvent(const EtwEventClass& event_class, EtwEventType type, + EtwEventLevel level) { + memset(static_cast(this), 0, sizeof(Super)); + header.Size = sizeof(Super); + header.Guid = event_class; + header.Class.Type = type; + header.Class.Level = level; + header.Flags = WNODE_FLAG_TRACED_GUID | WNODE_FLAG_USE_MOF_PTR; + } + + EtwMofEvent(const EtwEventClass& event_class, EtwEventType type, + EtwEventVersion version, EtwEventLevel level) { + memset(static_cast(this), 0, sizeof(Super)); + header.Size = sizeof(Super); + header.Guid = event_class; + header.Class.Type = type; + header.Class.Version = version; + header.Class.Level = level; + header.Flags = WNODE_FLAG_TRACED_GUID | WNODE_FLAG_USE_MOF_PTR; + } + + void SetField(int field, size_t size, const void *data) { + // DCHECK(field < N); + if ((field < N) && (size <= kuint32max)) { + fields[field].DataPtr = reinterpret_cast(data); + fields[field].Length = static_cast(size); + } + } + + EVENT_TRACE_HEADER* get() { return& header; } + + private: + DISALLOW_COPY_AND_ASSIGN(EtwMofEvent); +}; + +// Trace provider with Event Tracing for Windows. The trace provider +// registers with ETW by its name which is a GUID. ETW calls back to +// the object whenever the trace level or enable flags for this provider +// name changes. +// Users of this class can test whether logging is currently enabled at +// a particular trace level, and whether particular enable flags are set, +// before other resources are consumed to generate and issue the log +// messages themselves. +class BASE_EXPORT EtwTraceProvider { + public: + // Creates an event trace provider identified by provider_name, which + // will be the name registered with Event Tracing for Windows (ETW). + explicit EtwTraceProvider(const GUID& provider_name); + + // Creates an unnamed event trace provider, the provider must be given + // a name before registration. + EtwTraceProvider(); + virtual ~EtwTraceProvider(); + + // Registers the trace provider with Event Tracing for Windows. + // Note: from this point forward ETW may call the provider's control + // callback. If the provider's name is enabled in some trace session + // already, the callback may occur recursively from this call, so + // call this only when you're ready to handle callbacks. + ULONG Register(); + // Unregisters the trace provider with ETW. + ULONG Unregister(); + + // Accessors. + void set_provider_name(const GUID& provider_name) { + provider_name_ = provider_name; + } + const GUID& provider_name() const { return provider_name_; } + TRACEHANDLE registration_handle() const { return registration_handle_; } + TRACEHANDLE session_handle() const { return session_handle_; } + EtwEventFlags enable_flags() const { return enable_flags_; } + EtwEventLevel enable_level() const { return enable_level_; } + + // Returns true iff logging should be performed for "level" and "flags". + // Note: flags is treated as a bitmask, and should normally have a single + // bit set, to test whether to log for a particular sub "facility". + bool ShouldLog(EtwEventLevel level, EtwEventFlags flags) { + return NULL != session_handle_ && level >= enable_level_ && + (0 != (flags & enable_flags_)); + } + + // Simple wrappers to log Unicode and ANSI strings. + // Do nothing if !ShouldLog(level, 0xFFFFFFFF). + ULONG Log(const EtwEventClass& event_class, EtwEventType type, + EtwEventLevel level, const char *message); + ULONG Log(const EtwEventClass& event_class, EtwEventType type, + EtwEventLevel level, const wchar_t *message); + + // Log the provided event. + ULONG Log(EVENT_TRACE_HEADER* event); + + protected: + // Called after events have been enabled, override in subclasses + // to set up state or log at the start of a session. + // Note: This function may be called ETW's thread and may be racy, + // bring your own locking if needed. + virtual void OnEventsEnabled() {} + + // Called just before events are disabled, override in subclasses + // to tear down state or log at the end of a session. + // Note: This function may be called ETW's thread and may be racy, + // bring your own locking if needed. + virtual void OnEventsDisabled() {} + + // Called just after events have been disabled, override in subclasses + // to tear down state at the end of a session. At this point it's + // to late to log anything to the session. + // Note: This function may be called ETW's thread and may be racy, + // bring your own locking if needed. + virtual void PostEventsDisabled() {} + + private: + ULONG EnableEvents(PVOID buffer); + ULONG DisableEvents(); + ULONG Callback(WMIDPREQUESTCODE request, PVOID buffer); + static ULONG WINAPI ControlCallback(WMIDPREQUESTCODE request, PVOID context, + ULONG *reserved, PVOID buffer); + + GUID provider_name_; + TRACEHANDLE registration_handle_; + TRACEHANDLE session_handle_; + EtwEventFlags enable_flags_; + EtwEventLevel enable_level_; + + // We don't use this, but on XP we're obliged to pass one in to + // RegisterTraceGuids. Non-const, because that's how the API needs it. + static TRACE_GUID_REGISTRATION obligatory_guid_registration_; + + DISALLOW_COPY_AND_ASSIGN(EtwTraceProvider); +}; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_EVENT_TRACE_PROVIDER_H_ diff --git a/base/win/event_trace_provider_unittest.cc b/base/win/event_trace_provider_unittest.cc new file mode 100644 index 0000000000..55b5ae6aed --- /dev/null +++ b/base/win/event_trace_provider_unittest.cc @@ -0,0 +1,110 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Unit tests for event trace provider. +#include "base/win/event_trace_provider.h" +#include +#include "testing/gtest/include/gtest/gtest.h" +#include // NOLINT - has to be last + +namespace { + +using base::win::EtwTraceProvider; +using base::win::EtwMofEvent; + +// {7F0FD37F-FA3C-4cd6-9242-DF60967A2CB2} +DEFINE_GUID(kTestProvider, + 0x7f0fd37f, 0xfa3c, 0x4cd6, 0x92, 0x42, 0xdf, 0x60, 0x96, 0x7a, 0x2c, 0xb2); + +// {7F0FD37F-FA3C-4cd6-9242-DF60967A2CB2} +DEFINE_GUID(kTestEventClass, + 0x7f0fd37f, 0xfa3c, 0x4cd6, 0x92, 0x42, 0xdf, 0x60, 0x96, 0x7a, 0x2c, 0xb2); + +} // namespace + +TEST(EtwTraceProviderTest, ToleratesPreCreateInvocations) { + // Because the trace provider is used in logging, it's important that + // it be possible to use static provider instances without regard to + // whether they've been constructed or destructed. + // The interface of the class is designed to tolerate this usage. + char buf[sizeof(EtwTraceProvider)] = {0}; + EtwTraceProvider& provider = reinterpret_cast(buf); + + EXPECT_EQ(NULL, provider.registration_handle()); + EXPECT_EQ(NULL, provider.session_handle()); + EXPECT_EQ(0, provider.enable_flags()); + EXPECT_EQ(0, provider.enable_level()); + + EXPECT_FALSE(provider.ShouldLog(TRACE_LEVEL_FATAL, 0xfffffff)); + + // We expect these not to crash. + provider.Log(kTestEventClass, 0, TRACE_LEVEL_FATAL, "foo"); + provider.Log(kTestEventClass, 0, TRACE_LEVEL_FATAL, L"foo"); + + EtwMofEvent<1> dummy(kTestEventClass, 0, TRACE_LEVEL_FATAL); + DWORD data = 0; + dummy.SetField(0, sizeof(data), &data); + provider.Log(dummy.get()); + + // Placement-new the provider into our buffer. + new (buf) EtwTraceProvider(kTestProvider); + + // Registration is now safe. + EXPECT_EQ(ERROR_SUCCESS, provider.Register()); + + // Destruct the instance, this should unregister it. + provider.EtwTraceProvider::~EtwTraceProvider(); + + // And post-destruction, all of the above should still be safe. + EXPECT_EQ(NULL, provider.registration_handle()); + EXPECT_EQ(NULL, provider.session_handle()); + EXPECT_EQ(0, provider.enable_flags()); + EXPECT_EQ(0, provider.enable_level()); + + EXPECT_FALSE(provider.ShouldLog(TRACE_LEVEL_FATAL, 0xfffffff)); + + // We expect these not to crash. + provider.Log(kTestEventClass, 0, TRACE_LEVEL_FATAL, "foo"); + provider.Log(kTestEventClass, 0, TRACE_LEVEL_FATAL, L"foo"); + provider.Log(dummy.get()); +} + +TEST(EtwTraceProviderTest, Initialize) { + EtwTraceProvider provider(kTestProvider); + + EXPECT_EQ(NULL, provider.registration_handle()); + EXPECT_EQ(NULL, provider.session_handle()); + EXPECT_EQ(0, provider.enable_flags()); + EXPECT_EQ(0, provider.enable_level()); +} + +TEST(EtwTraceProviderTest, Register) { + EtwTraceProvider provider(kTestProvider); + + ASSERT_EQ(ERROR_SUCCESS, provider.Register()); + EXPECT_NE(NULL, provider.registration_handle()); + ASSERT_EQ(ERROR_SUCCESS, provider.Unregister()); + EXPECT_EQ(NULL, provider.registration_handle()); +} + +TEST(EtwTraceProviderTest, RegisterWithNoNameFails) { + EtwTraceProvider provider; + + EXPECT_TRUE(provider.Register() != ERROR_SUCCESS); +} + +TEST(EtwTraceProviderTest, Enable) { + EtwTraceProvider provider(kTestProvider); + + ASSERT_EQ(ERROR_SUCCESS, provider.Register()); + EXPECT_NE(NULL, provider.registration_handle()); + + // No session so far. + EXPECT_EQ(NULL, provider.session_handle()); + EXPECT_EQ(0, provider.enable_flags()); + EXPECT_EQ(0, provider.enable_level()); + + ASSERT_EQ(ERROR_SUCCESS, provider.Unregister()); + EXPECT_EQ(NULL, provider.registration_handle()); +} diff --git a/base/win/i18n.cc b/base/win/i18n.cc new file mode 100644 index 0000000000..9e523a15cb --- /dev/null +++ b/base/win/i18n.cc @@ -0,0 +1,169 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/win/i18n.h" + +#include + +#include "base/logging.h" + +namespace { + +// Keep this enum in sync with kLanguageFunctionNames. +enum LanguageFunction { + SYSTEM_LANGUAGES, + USER_LANGUAGES, + PROCESS_LANGUAGES, + THREAD_LANGUAGES, + NUM_FUNCTIONS +}; + +const char kSystemLanguagesFunctionName[] = "GetSystemPreferredUILanguages"; +const char kUserLanguagesFunctionName[] = "GetUserPreferredUILanguages"; +const char kProcessLanguagesFunctionName[] = "GetProcessPreferredUILanguages"; +const char kThreadLanguagesFunctionName[] = "GetThreadPreferredUILanguages"; + +// Keep this array in sync with enum LanguageFunction. +const char *const kLanguageFunctionNames[] = { + &kSystemLanguagesFunctionName[0], + &kUserLanguagesFunctionName[0], + &kProcessLanguagesFunctionName[0], + &kThreadLanguagesFunctionName[0] +}; + +COMPILE_ASSERT(NUM_FUNCTIONS == arraysize(kLanguageFunctionNames), + language_function_enum_and_names_out_of_sync); + +// Calls one of the MUI Get*PreferredUILanguages functions, placing the result +// in |languages|. |function| identifies the function to call and |flags| is +// the function-specific flags (callers must not specify MUI_LANGUAGE_ID or +// MUI_LANGUAGE_NAME). Returns true if at least one language is placed in +// |languages|. +bool GetMUIPreferredUILanguageList(LanguageFunction function, ULONG flags, + std::vector* languages) { + DCHECK(0 <= function && NUM_FUNCTIONS > function); + DCHECK_EQ(0U, (flags & (MUI_LANGUAGE_ID | MUI_LANGUAGE_NAME))); + DCHECK(languages); + + HMODULE kernel32 = GetModuleHandle(L"kernel32.dll"); + if (NULL != kernel32) { + typedef BOOL (WINAPI* GetPreferredUILanguages_Fn)( + DWORD, PULONG, PZZWSTR, PULONG); + GetPreferredUILanguages_Fn get_preferred_ui_languages = + reinterpret_cast( + GetProcAddress(kernel32, kLanguageFunctionNames[function])); + if (NULL != get_preferred_ui_languages) { + const ULONG call_flags = flags | MUI_LANGUAGE_NAME; + ULONG language_count = 0; + ULONG buffer_length = 0; + if (get_preferred_ui_languages(call_flags, &language_count, NULL, + &buffer_length) && + 0 != buffer_length) { + languages->resize(buffer_length); + if (get_preferred_ui_languages(call_flags, &language_count, + &(*languages)[0], &buffer_length) && + 0 != language_count) { + DCHECK(languages->size() == buffer_length); + return true; + } else { + DPCHECK(0 == language_count) + << "Failed getting preferred UI languages."; + } + } else { + DPCHECK(0 == buffer_length) + << "Failed getting size of preferred UI languages."; + } + } else { + DVLOG(2) << "MUI not available."; + } + } else { + NOTREACHED() << "kernel32.dll not found."; + } + + return false; +} + +bool GetUserDefaultUILanguage(std::wstring* language, std::wstring* region) { + DCHECK(language); + + LANGID lang_id = ::GetUserDefaultUILanguage(); + if (LOCALE_CUSTOM_UI_DEFAULT != lang_id) { + const LCID locale_id = MAKELCID(lang_id, SORT_DEFAULT); + // max size for LOCALE_SISO639LANGNAME and LOCALE_SISO3166CTRYNAME is 9 + wchar_t result_buffer[9]; + int result_length = + GetLocaleInfo(locale_id, LOCALE_SISO639LANGNAME, &result_buffer[0], + arraysize(result_buffer)); + DPCHECK(0 != result_length) << "Failed getting language id"; + if (1 < result_length) { + language->assign(&result_buffer[0], result_length - 1); + region->clear(); + if (SUBLANG_NEUTRAL != SUBLANGID(lang_id)) { + result_length = + GetLocaleInfo(locale_id, LOCALE_SISO3166CTRYNAME, &result_buffer[0], + arraysize(result_buffer)); + DPCHECK(0 != result_length) << "Failed getting region id"; + if (1 < result_length) + region->assign(&result_buffer[0], result_length - 1); + } + return true; + } + } else { + // This is entirely unexpected on pre-Vista, which is the only time we + // should try GetUserDefaultUILanguage anyway. + NOTREACHED() << "Cannot determine language for a supplemental locale."; + } + return false; +} + +bool GetPreferredUILanguageList(LanguageFunction function, ULONG flags, + std::vector* languages) { + std::vector buffer; + std::wstring language; + std::wstring region; + + if (GetMUIPreferredUILanguageList(function, flags, &buffer)) { + std::vector::const_iterator scan = buffer.begin(); + language.assign(&*scan); + while (!language.empty()) { + languages->push_back(language); + scan += language.size() + 1; + language.assign(&*scan); + } + } else if (GetUserDefaultUILanguage(&language, ®ion)) { + // Mimic the MUI behavior of putting the neutral version of the lang after + // the regional one (e.g., "fr-CA, fr"). + if (!region.empty()) + languages->push_back(std::wstring(language) + .append(1, L'-') + .append(region)); + languages->push_back(language); + } else { + return false; + } + + return true; +} + +} // namespace + +namespace base { +namespace win { +namespace i18n { + +bool GetUserPreferredUILanguageList(std::vector* languages) { + DCHECK(languages); + return GetPreferredUILanguageList(USER_LANGUAGES, 0, languages); +} + +bool GetThreadPreferredUILanguageList(std::vector* languages) { + DCHECK(languages); + return GetPreferredUILanguageList( + THREAD_LANGUAGES, MUI_MERGE_SYSTEM_FALLBACK | MUI_MERGE_USER_FALLBACK, + languages); +} + +} // namespace i18n +} // namespace win +} // namespace base diff --git a/base/win/i18n.h b/base/win/i18n.h new file mode 100644 index 0000000000..c0379c1559 --- /dev/null +++ b/base/win/i18n.h @@ -0,0 +1,34 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_WIN_I18N_H_ +#define BASE_WIN_I18N_H_ + +#include +#include + +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace base { +namespace win { +namespace i18n { + +// Adds to |languages| the list of user preferred UI languages from MUI, if +// available, falling-back on the user default UI language otherwise. Returns +// true if at least one language is added. +BASE_EXPORT bool GetUserPreferredUILanguageList( + std::vector* languages); + +// Adds to |languages| the list of thread, process, user, and system preferred +// UI languages from MUI, if available, falling-back on the user default UI +// language otherwise. Returns true if at least one language is added. +BASE_EXPORT bool GetThreadPreferredUILanguageList( + std::vector* languages); + +} // namespace i18n +} // namespace win +} // namespace base + +#endif // BASE_WIN_I18N_H_ diff --git a/base/win/i18n_unittest.cc b/base/win/i18n_unittest.cc new file mode 100644 index 0000000000..781fc39db3 --- /dev/null +++ b/base/win/i18n_unittest.cc @@ -0,0 +1,42 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains unit tests for Windows internationalization funcs. + +#include "testing/gtest/include/gtest/gtest.h" + +#include "base/win/i18n.h" +#include "base/win/windows_version.h" + +namespace base { +namespace win { +namespace i18n { + +// Tests that at least one user preferred UI language can be obtained. +TEST(I18NTest, GetUserPreferredUILanguageList) { + std::vector languages; + EXPECT_TRUE(GetUserPreferredUILanguageList(&languages)); + EXPECT_NE(static_cast::size_type>(0), + languages.size()); + for (std::vector::const_iterator scan = languages.begin(), + end = languages.end(); scan != end; ++scan) { + EXPECT_FALSE((*scan).empty()); + } +} + +// Tests that at least one thread preferred UI language can be obtained. +TEST(I18NTest, GetThreadPreferredUILanguageList) { + std::vector languages; + EXPECT_TRUE(GetThreadPreferredUILanguageList(&languages)); + EXPECT_NE(static_cast::size_type>(0), + languages.size()); + for (std::vector::const_iterator scan = languages.begin(), + end = languages.end(); scan != end; ++scan) { + EXPECT_FALSE((*scan).empty()); + } +} + +} // namespace i18n +} // namespace win +} // namespace base diff --git a/base/win/iat_patch_function.cc b/base/win/iat_patch_function.cc new file mode 100644 index 0000000000..a4a89028b8 --- /dev/null +++ b/base/win/iat_patch_function.cc @@ -0,0 +1,278 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/win/iat_patch_function.h" + +#include "base/logging.h" +#include "base/win/pe_image.h" + +namespace base { +namespace win { + +namespace { + +struct InterceptFunctionInformation { + bool finished_operation; + const char* imported_from_module; + const char* function_name; + void* new_function; + void** old_function; + IMAGE_THUNK_DATA** iat_thunk; + DWORD return_code; +}; + +void* GetIATFunction(IMAGE_THUNK_DATA* iat_thunk) { + if (NULL == iat_thunk) { + NOTREACHED(); + return NULL; + } + + // Works around the 64 bit portability warning: + // The Function member inside IMAGE_THUNK_DATA is really a pointer + // to the IAT function. IMAGE_THUNK_DATA correctly maps to IMAGE_THUNK_DATA32 + // or IMAGE_THUNK_DATA64 for correct pointer size. + union FunctionThunk { + IMAGE_THUNK_DATA thunk; + void* pointer; + } iat_function; + + iat_function.thunk = *iat_thunk; + return iat_function.pointer; +} +// Change the page protection (of code pages) to writable and copy +// the data at the specified location +// +// Arguments: +// old_code Target location to copy +// new_code Source +// length Number of bytes to copy +// +// Returns: Windows error code (winerror.h). NO_ERROR if successful +DWORD ModifyCode(void* old_code, void* new_code, int length) { + if ((NULL == old_code) || (NULL == new_code) || (0 == length)) { + NOTREACHED(); + return ERROR_INVALID_PARAMETER; + } + + // Change the page protection so that we can write. + DWORD error = NO_ERROR; + DWORD old_page_protection = 0; + if (VirtualProtect(old_code, + length, + PAGE_READWRITE, + &old_page_protection)) { + + // Write the data. + CopyMemory(old_code, new_code, length); + + // Restore the old page protection. + error = ERROR_SUCCESS; + VirtualProtect(old_code, + length, + old_page_protection, + &old_page_protection); + } else { + error = GetLastError(); + NOTREACHED(); + } + + return error; +} + +bool InterceptEnumCallback(const base::win::PEImage& image, const char* module, + DWORD ordinal, const char* name, DWORD hint, + IMAGE_THUNK_DATA* iat, void* cookie) { + InterceptFunctionInformation* intercept_information = + reinterpret_cast(cookie); + + if (NULL == intercept_information) { + NOTREACHED(); + return false; + } + + DCHECK(module); + + if ((0 == lstrcmpiA(module, intercept_information->imported_from_module)) && + (NULL != name) && + (0 == lstrcmpiA(name, intercept_information->function_name))) { + // Save the old pointer. + if (NULL != intercept_information->old_function) { + *(intercept_information->old_function) = GetIATFunction(iat); + } + + if (NULL != intercept_information->iat_thunk) { + *(intercept_information->iat_thunk) = iat; + } + + // portability check + COMPILE_ASSERT(sizeof(iat->u1.Function) == + sizeof(intercept_information->new_function), unknown_IAT_thunk_format); + + // Patch the function. + intercept_information->return_code = + ModifyCode(&(iat->u1.Function), + &(intercept_information->new_function), + sizeof(intercept_information->new_function)); + + // Terminate further enumeration. + intercept_information->finished_operation = true; + return false; + } + + return true; +} + +// Helper to intercept a function in an import table of a specific +// module. +// +// Arguments: +// module_handle Module to be intercepted +// imported_from_module Module that exports the symbol +// function_name Name of the API to be intercepted +// new_function Interceptor function +// old_function Receives the original function pointer +// iat_thunk Receives pointer to IAT_THUNK_DATA +// for the API from the import table. +// +// Returns: Returns NO_ERROR on success or Windows error code +// as defined in winerror.h +DWORD InterceptImportedFunction(HMODULE module_handle, + const char* imported_from_module, + const char* function_name, void* new_function, + void** old_function, + IMAGE_THUNK_DATA** iat_thunk) { + if ((NULL == module_handle) || (NULL == imported_from_module) || + (NULL == function_name) || (NULL == new_function)) { + NOTREACHED(); + return ERROR_INVALID_PARAMETER; + } + + base::win::PEImage target_image(module_handle); + if (!target_image.VerifyMagic()) { + NOTREACHED(); + return ERROR_INVALID_PARAMETER; + } + + InterceptFunctionInformation intercept_information = { + false, + imported_from_module, + function_name, + new_function, + old_function, + iat_thunk, + ERROR_GEN_FAILURE}; + + // First go through the IAT. If we don't find the import we are looking + // for in IAT, search delay import table. + target_image.EnumAllImports(InterceptEnumCallback, &intercept_information); + if (!intercept_information.finished_operation) { + target_image.EnumAllDelayImports(InterceptEnumCallback, + &intercept_information); + } + + return intercept_information.return_code; +} + +// Restore intercepted IAT entry with the original function. +// +// Arguments: +// intercept_function Interceptor function +// original_function Receives the original function pointer +// +// Returns: Returns NO_ERROR on success or Windows error code +// as defined in winerror.h +DWORD RestoreImportedFunction(void* intercept_function, + void* original_function, + IMAGE_THUNK_DATA* iat_thunk) { + if ((NULL == intercept_function) || (NULL == original_function) || + (NULL == iat_thunk)) { + NOTREACHED(); + return ERROR_INVALID_PARAMETER; + } + + if (GetIATFunction(iat_thunk) != intercept_function) { + // Check if someone else has intercepted on top of us. + // We cannot unpatch in this case, just raise a red flag. + NOTREACHED(); + return ERROR_INVALID_FUNCTION; + } + + return ModifyCode(&(iat_thunk->u1.Function), + &original_function, + sizeof(original_function)); +} + +} // namespace + +IATPatchFunction::IATPatchFunction() + : module_handle_(NULL), + original_function_(NULL), + iat_thunk_(NULL), + intercept_function_(NULL) { +} + +IATPatchFunction::~IATPatchFunction() { + if (NULL != intercept_function_) { + DWORD error = Unpatch(); + DCHECK_EQ(static_cast(NO_ERROR), error); + } +} + +DWORD IATPatchFunction::Patch(const wchar_t* module, + const char* imported_from_module, + const char* function_name, + void* new_function) { + DCHECK_EQ(static_cast(NULL), original_function_); + DCHECK_EQ(static_cast(NULL), iat_thunk_); + DCHECK_EQ(static_cast(NULL), intercept_function_); + + HMODULE module_handle = LoadLibraryW(module); + + if (module_handle == NULL) { + NOTREACHED(); + return GetLastError(); + } + + DWORD error = InterceptImportedFunction(module_handle, + imported_from_module, + function_name, + new_function, + &original_function_, + &iat_thunk_); + + if (NO_ERROR == error) { + DCHECK_NE(original_function_, intercept_function_); + module_handle_ = module_handle; + intercept_function_ = new_function; + } else { + FreeLibrary(module_handle); + } + + return error; +} + +DWORD IATPatchFunction::Unpatch() { + DWORD error = RestoreImportedFunction(intercept_function_, + original_function_, + iat_thunk_); + DCHECK_EQ(static_cast(NO_ERROR), error); + + // Hands off the intercept if we fail to unpatch. + // If IATPatchFunction::Unpatch fails during RestoreImportedFunction + // it means that we cannot safely unpatch the import address table + // patch. In this case its better to be hands off the intercept as + // trying to unpatch again in the destructor of IATPatchFunction is + // not going to be any safer + if (module_handle_) + FreeLibrary(module_handle_); + module_handle_ = NULL; + intercept_function_ = NULL; + original_function_ = NULL; + iat_thunk_ = NULL; + + return error; +} + +} // namespace win +} // namespace base diff --git a/base/win/iat_patch_function.h b/base/win/iat_patch_function.h new file mode 100644 index 0000000000..3ae1f3c460 --- /dev/null +++ b/base/win/iat_patch_function.h @@ -0,0 +1,72 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_WIN_IAT_PATCH_FUNCTION_H_ +#define BASE_WIN_IAT_PATCH_FUNCTION_H_ + +#include + +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace base { +namespace win { + +// A class that encapsulates Import Address Table patching helpers and restores +// the original function in the destructor. +// +// It will intercept functions for a specific DLL imported from another DLL. +// This is the case when, for example, we want to intercept +// CertDuplicateCertificateContext function (exported from crypt32.dll) called +// by wininet.dll. +class BASE_EXPORT IATPatchFunction { + public: + IATPatchFunction(); + ~IATPatchFunction(); + + // Intercept a function in an import table of a specific + // module. Save the original function and the import + // table address. These values will be used later + // during Unpatch + // + // Arguments: + // module Module to be intercepted + // imported_from_module Module that exports the 'function_name' + // function_name Name of the API to be intercepted + // + // Returns: Windows error code (winerror.h). NO_ERROR if successful + // + // Note: Patching a function will make the IAT patch take some "ownership" on + // |module|. It will LoadLibrary(module) to keep the DLL alive until a call + // to Unpatch(), which will call FreeLibrary() and allow the module to be + // unloaded. The idea is to help prevent the DLL from going away while a + // patch is still active. + DWORD Patch(const wchar_t* module, + const char* imported_from_module, + const char* function_name, + void* new_function); + + // Unpatch the IAT entry using internally saved original + // function. + // + // Returns: Windows error code (winerror.h). NO_ERROR if successful + DWORD Unpatch(); + + bool is_patched() const { + return (NULL != intercept_function_); + } + + private: + HMODULE module_handle_; + void* intercept_function_; + void* original_function_; + IMAGE_THUNK_DATA* iat_thunk_; + + DISALLOW_COPY_AND_ASSIGN(IATPatchFunction); +}; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_IAT_PATCH_FUNCTION_H_ diff --git a/base/win/iunknown_impl.cc b/base/win/iunknown_impl.cc new file mode 100644 index 0000000000..9baa0f3d67 --- /dev/null +++ b/base/win/iunknown_impl.cc @@ -0,0 +1,42 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/win/iunknown_impl.h" + +namespace base { +namespace win { + +IUnknownImpl::IUnknownImpl() + : ref_count_(0) { +} + +IUnknownImpl::~IUnknownImpl() { +} + +ULONG STDMETHODCALLTYPE IUnknownImpl::AddRef() { + base::AtomicRefCountInc(&ref_count_); + return 1; +} + +ULONG STDMETHODCALLTYPE IUnknownImpl::Release() { + if (!base::AtomicRefCountDec(&ref_count_)) { + delete this; + return 0; + } + return 1; +} + +STDMETHODIMP IUnknownImpl::QueryInterface(REFIID riid, void** ppv) { + if (riid == IID_IUnknown) { + *ppv = static_cast(this); + AddRef(); + return S_OK; + } + + *ppv = NULL; + return E_NOINTERFACE; +} + +} // namespace win +} // namespace base diff --git a/base/win/iunknown_impl.h b/base/win/iunknown_impl.h new file mode 100644 index 0000000000..ff7e87039b --- /dev/null +++ b/base/win/iunknown_impl.h @@ -0,0 +1,38 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_WIN_IUNKNOWN_IMPL_H_ +#define BASE_WIN_IUNKNOWN_IMPL_H_ + +#include + +#include "base/atomic_ref_count.h" +#include "base/base_export.h" +#include "base/compiler_specific.h" + +namespace base { +namespace win { + +// IUnknown implementation for other classes to derive from. +class BASE_EXPORT IUnknownImpl : public IUnknown { + public: + IUnknownImpl(); + + virtual ULONG STDMETHODCALLTYPE AddRef() OVERRIDE; + virtual ULONG STDMETHODCALLTYPE Release() OVERRIDE; + + // Subclasses should extend this to return any interfaces they provide. + virtual STDMETHODIMP QueryInterface(REFIID riid, void** ppv) OVERRIDE; + + protected: + virtual ~IUnknownImpl(); + + private: + AtomicRefCount ref_count_; +}; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_IUNKNOWN_IMPL_H_ diff --git a/base/win/iunknown_impl_unittest.cc b/base/win/iunknown_impl_unittest.cc new file mode 100644 index 0000000000..db86214daf --- /dev/null +++ b/base/win/iunknown_impl_unittest.cc @@ -0,0 +1,51 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/win/iunknown_impl.h" + +#include "base/win/scoped_com_initializer.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace win { + +class TestIUnknownImplSubclass : public IUnknownImpl { + public: + TestIUnknownImplSubclass() { + ++instance_count; + } + virtual ~TestIUnknownImplSubclass() { + --instance_count; + } + static int instance_count; +}; + +// static +int TestIUnknownImplSubclass::instance_count = 0; + +TEST(IUnknownImplTest, IUnknownImpl) { + ScopedCOMInitializer com_initializer; + + EXPECT_EQ(0, TestIUnknownImplSubclass::instance_count); + IUnknown* u = new TestIUnknownImplSubclass(); + + EXPECT_EQ(1, TestIUnknownImplSubclass::instance_count); + + EXPECT_EQ(1, u->AddRef()); + EXPECT_EQ(1, u->AddRef()); + + IUnknown* other = NULL; + EXPECT_EQ(E_NOINTERFACE, u->QueryInterface( + IID_IDispatch, reinterpret_cast(&other))); + EXPECT_EQ(S_OK, u->QueryInterface( + IID_IUnknown, reinterpret_cast(&other))); + other->Release(); + + EXPECT_EQ(1, u->Release()); + EXPECT_EQ(0, u->Release()); + EXPECT_EQ(0, TestIUnknownImplSubclass::instance_count); +} + +} // namespace win +} // namespace base diff --git a/base/win/message_window.cc b/base/win/message_window.cc new file mode 100644 index 0000000000..56660740fc --- /dev/null +++ b/base/win/message_window.cc @@ -0,0 +1,165 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/win/message_window.h" + +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/process/memory.h" +#include "base/win/wrapped_window_proc.h" + +const wchar_t kMessageWindowClassName[] = L"Chrome_MessageWindow"; + +namespace base { +namespace win { + +// Used along with LazyInstance to register a window class for message-only +// windows created by MessageWindow. +class MessageWindow::WindowClass { + public: + WindowClass(); + ~WindowClass(); + + ATOM atom() { return atom_; } + HINSTANCE instance() { return instance_; } + + private: + ATOM atom_; + HINSTANCE instance_; + + DISALLOW_COPY_AND_ASSIGN(WindowClass); +}; + +static LazyInstance g_window_class = + LAZY_INSTANCE_INITIALIZER; + +MessageWindow::WindowClass::WindowClass() + : atom_(0), + instance_(base::GetModuleFromAddress(&MessageWindow::WindowProc)) { + WNDCLASSEX window_class; + window_class.cbSize = sizeof(window_class); + window_class.style = 0; + window_class.lpfnWndProc = &base::win::WrappedWindowProc; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = instance_; + window_class.hIcon = NULL; + window_class.hCursor = NULL; + window_class.hbrBackground = NULL; + window_class.lpszMenuName = NULL; + window_class.lpszClassName = kMessageWindowClassName; + window_class.hIconSm = NULL; + atom_ = RegisterClassEx(&window_class); + if (atom_ == 0) { + LOG_GETLASTERROR(ERROR) + << "Failed to register the window class for a message-only window"; + } +} + +MessageWindow::WindowClass::~WindowClass() { + if (atom_ != 0) { + BOOL result = UnregisterClass(MAKEINTATOM(atom_), instance_); + // Hitting this DCHECK usually means that some MessageWindow objects were + // leaked. For example not calling + // ui::Clipboard::DestroyClipboardForCurrentThread() results in a leaked + // MessageWindow. + DCHECK(result); + } +} + +MessageWindow::MessageWindow() + : window_(NULL) { +} + +MessageWindow::~MessageWindow() { + DCHECK(CalledOnValidThread()); + + if (window_ != NULL) { + BOOL result = DestroyWindow(window_); + DCHECK(result); + } +} + +bool MessageWindow::Create(const MessageCallback& message_callback) { + return DoCreate(message_callback, NULL); +} + +bool MessageWindow::CreateNamed(const MessageCallback& message_callback, + const string16& window_name) { + return DoCreate(message_callback, window_name.c_str()); +} + +// static +HWND MessageWindow::FindWindow(const string16& window_name) { + return FindWindowEx(HWND_MESSAGE, NULL, kMessageWindowClassName, + window_name.c_str()); +} + +bool MessageWindow::DoCreate(const MessageCallback& message_callback, + const wchar_t* window_name) { + DCHECK(CalledOnValidThread()); + DCHECK(message_callback_.is_null()); + DCHECK(!window_); + + message_callback_ = message_callback; + + WindowClass& window_class = g_window_class.Get(); + window_ = CreateWindow(MAKEINTATOM(window_class.atom()), window_name, 0, 0, 0, + 0, 0, HWND_MESSAGE, 0, window_class.instance(), this); + if (!window_) { + LOG_GETLASTERROR(ERROR) << "Failed to create a message-only window"; + return false; + } + + return true; +} + +// static +LRESULT CALLBACK MessageWindow::WindowProc(HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam) { + MessageWindow* self = reinterpret_cast( + GetWindowLongPtr(hwnd, GWLP_USERDATA)); + + switch (message) { + // Set up the self before handling WM_CREATE. + case WM_CREATE: { + CREATESTRUCT* cs = reinterpret_cast(lparam); + self = reinterpret_cast(cs->lpCreateParams); + + // Make |hwnd| available to the message handler. At this point the control + // hasn't returned from CreateWindow() yet. + self->window_ = hwnd; + + // Store pointer to the self to the window's user data. + SetLastError(ERROR_SUCCESS); + LONG_PTR result = SetWindowLongPtr( + hwnd, GWLP_USERDATA, reinterpret_cast(self)); + CHECK(result != 0 || GetLastError() == ERROR_SUCCESS); + break; + } + + // Clear the pointer to stop calling the self once WM_DESTROY is + // received. + case WM_DESTROY: { + SetLastError(ERROR_SUCCESS); + LONG_PTR result = SetWindowLongPtr(hwnd, GWLP_USERDATA, NULL); + CHECK(result != 0 || GetLastError() == ERROR_SUCCESS); + break; + } + } + + // Handle the message. + if (self) { + LRESULT message_result; + if (self->message_callback_.Run(message, wparam, lparam, &message_result)) + return message_result; + } + + return DefWindowProc(hwnd, message, wparam, lparam); +} + +} // namespace win +} // namespace base diff --git a/base/win/message_window.h b/base/win/message_window.h new file mode 100644 index 0000000000..ae0c6f0bc7 --- /dev/null +++ b/base/win/message_window.h @@ -0,0 +1,75 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_WIN_MESSAGE_WINDOW_H_ +#define BASE_WIN_MESSAGE_WINDOW_H_ + +#include + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/strings/string16.h" +#include "base/threading/non_thread_safe.h" + +namespace base { +namespace win { + +// Implements a message-only window. +class BASE_EXPORT MessageWindow : public base::NonThreadSafe { + public: + // Used to register a process-wide message window class. + class WindowClass; + + // Implement this callback to handle messages received by the message window. + // If the callback returns |false|, the first four parameters are passed to + // DefWindowProc(). Otherwise, |*result| is returned by the window procedure. + typedef base::Callback MessageCallback; + + MessageWindow(); + ~MessageWindow(); + + // Creates a message-only window. The incoming messages will be passed by + // |message_callback|. |message_callback| must outlive |this|. + bool Create(const MessageCallback& message_callback); + + // Same as Create() but assigns the name to the created window. + bool CreateNamed(const MessageCallback& message_callback, + const string16& window_name); + + HWND hwnd() const { return window_; } + + // Retrieves a handle of the first message-only window with matching + // |window_name|. + static HWND FindWindow(const string16& window_name); + + private: + // Give |WindowClass| access to WindowProc(). + friend class WindowClass; + + // Contains the actual window creation code. + bool DoCreate(const MessageCallback& message_callback, + const wchar_t* window_name); + + // Invoked by the OS to process incoming window messages. + static LRESULT CALLBACK WindowProc(HWND hwnd, UINT message, WPARAM wparam, + LPARAM lparam); + + // Invoked to handle messages received by the window. + MessageCallback message_callback_; + + // Handle of the input window. + HWND window_; + + DISALLOW_COPY_AND_ASSIGN(MessageWindow); +}; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_MESSAGE_WINDOW_H_ diff --git a/base/win/message_window_unittest.cc b/base/win/message_window_unittest.cc new file mode 100644 index 0000000000..00248bfd36 --- /dev/null +++ b/base/win/message_window_unittest.cc @@ -0,0 +1,61 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/bind.h" +#include "base/guid.h" +#include "base/strings/utf_string_conversions.h" +#include "base/win/message_window.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace { + +bool HandleMessage( + UINT message, WPARAM wparam, LPARAM lparam, LRESULT* result) { + // Return |wparam| as the result of WM_USER message. + if (message == WM_USER) { + *result = wparam; + return true; + } + + return false; +} + +} // namespace + +// Checks that a window can be created. +TEST(MessageWindowTest, Create) { + win::MessageWindow window; + EXPECT_TRUE(window.Create(base::Bind(&HandleMessage))); +} + +// Checks that a named window can be created. +TEST(MessageWindowTest, CreateNamed) { + win::MessageWindow window; + EXPECT_TRUE(window.CreateNamed(base::Bind(&HandleMessage), + UTF8ToUTF16("test_message_window"))); +} + +// Verifies that the created window can receive messages. +TEST(MessageWindowTest, SendMessage) { + win::MessageWindow window; + EXPECT_TRUE(window.Create(base::Bind(&HandleMessage))); + + EXPECT_EQ(SendMessage(window.hwnd(), WM_USER, 100, 0), 100); +} + +// Verifies that a named window can be found by name. +TEST(MessageWindowTest, FindWindow) { + string16 name = UTF8ToUTF16(base::GenerateGUID()); + win::MessageWindow window; + EXPECT_TRUE(window.CreateNamed(base::Bind(&HandleMessage), name)); + + HWND hwnd = win::MessageWindow::FindWindow(name); + EXPECT_TRUE(hwnd != NULL); + EXPECT_EQ(SendMessage(hwnd, WM_USER, 200, 0), 200); +} + +} // namespace base diff --git a/base/win/metro.cc b/base/win/metro.cc new file mode 100644 index 0000000000..afe4fcecb8 --- /dev/null +++ b/base/win/metro.cc @@ -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/win/metro.h" + +#include "base/message_loop/message_loop.h" +#include "base/strings/string_util.h" +#include "base/win/scoped_comptr.h" +#include "base/win/windows_version.h" + +namespace base { +namespace win { + +namespace { +bool g_should_tsf_aware_required = false; +} + +HMODULE GetMetroModule() { + const HMODULE kUninitialized = reinterpret_cast(1); + static HMODULE metro_module = kUninitialized; + + if (metro_module == kUninitialized) { + // Initialize the cache, note that the initialization is idempotent + // under the assumption that metro_driver is never unloaded, so the + // race to this assignment is safe. + metro_module = GetModuleHandleA("metro_driver.dll"); + if (metro_module != NULL) { + // This must be a metro process if the metro_driver is loaded. + DCHECK(IsMetroProcess()); + } + } + + DCHECK(metro_module != kUninitialized); + return metro_module; +} + +bool IsMetroProcess() { + enum ImmersiveState { + kImmersiveUnknown, + kImmersiveTrue, + kImmersiveFalse + }; + // The immersive state of a process can never change. + // Look it up once and cache it here. + static ImmersiveState state = kImmersiveUnknown; + + if (state == kImmersiveUnknown) { + if (IsProcessImmersive(::GetCurrentProcess())) { + state = kImmersiveTrue; + } else { + state = kImmersiveFalse; + } + } + DCHECK_NE(kImmersiveUnknown, state); + return state == kImmersiveTrue; +} + +bool IsProcessImmersive(HANDLE process) { + typedef BOOL (WINAPI* IsImmersiveProcessFunc)(HANDLE process); + HMODULE user32 = ::GetModuleHandleA("user32.dll"); + DCHECK(user32 != NULL); + + IsImmersiveProcessFunc is_immersive_process = + reinterpret_cast( + ::GetProcAddress(user32, "IsImmersiveProcess")); + + if (is_immersive_process) + return is_immersive_process(process) ? true: false; + return false; +} + +bool IsTSFAwareRequired() { +#if defined(USE_AURA) + if (base::win::GetVersion() >= base::win::VERSION_WIN8) + return true; +#endif + // Although this function is equal to IsMetroProcess at this moment, + // Chrome for Win7 and Vista may support TSF in the future. + return g_should_tsf_aware_required || IsMetroProcess(); +} + +void SetForceToUseTSF() { + g_should_tsf_aware_required = true; + + // Since Windows 8 Metro mode disables CUAS (Cicero Unaware Application + // Support) via ImmDisableLegacyIME API, Chrome must be fully TSF-aware on + // Metro mode. For debugging purposes, explicitly call ImmDisableLegacyIME so + // that one can test TSF functionality even on Windows 8 desktop mode. Note + // that CUAS cannot be disabled on Windows Vista/7 where ImmDisableLegacyIME + // is not available. + typedef BOOL (* ImmDisableLegacyIMEFunc)(); + HMODULE imm32 = ::GetModuleHandleA("imm32.dll"); + if (imm32 == NULL) + return; + + ImmDisableLegacyIMEFunc imm_disable_legacy_ime = + reinterpret_cast( + ::GetProcAddress(imm32, "ImmDisableLegacyIME")); + + if (imm_disable_legacy_ime == NULL) { + // Unsupported API, just do nothing. + return; + } + + if (!imm_disable_legacy_ime()) { + DVLOG(1) << "Failed to disable legacy IME."; + } +} + +wchar_t* LocalAllocAndCopyString(const string16& src) { + size_t dest_size = (src.length() + 1) * sizeof(wchar_t); + wchar_t* dest = reinterpret_cast(LocalAlloc(LPTR, dest_size)); + base::wcslcpy(dest, src.c_str(), dest_size); + return dest; +} + +bool IsParentalControlActivityLoggingOn() { + // Query this info on Windows Vista and above. + if (base::win::GetVersion() < base::win::VERSION_VISTA) + return false; + + static bool parental_control_logging_required = false; + static bool parental_control_status_determined = false; + + if (parental_control_status_determined) + return parental_control_logging_required; + + parental_control_status_determined = true; + + ScopedComPtr parent_controls; + HRESULT hr = parent_controls.CreateInstance( + __uuidof(WindowsParentalControls)); + if (FAILED(hr)) + return false; + + ScopedComPtr settings; + hr = parent_controls->GetUserSettings(NULL, settings.Receive()); + if (FAILED(hr)) + return false; + + unsigned long restrictions = 0; + settings->GetRestrictions(&restrictions); + + parental_control_logging_required = + (restrictions & WPCFLAG_LOGGING_REQUIRED) == WPCFLAG_LOGGING_REQUIRED; + return parental_control_logging_required; +} + +// Metro driver exports for getting the launch type, initial url, initial +// search term, etc. +extern "C" { +typedef const wchar_t* (*GetInitialUrl)(); +typedef const wchar_t* (*GetInitialSearchString)(); +typedef base::win::MetroLaunchType (*GetLaunchType)( + base::win::MetroPreviousExecutionState* previous_state); +} + +MetroLaunchType GetMetroLaunchParams(string16* params) { + HMODULE metro = base::win::GetMetroModule(); + if (!metro) + return base::win::METRO_LAUNCH_ERROR; + + GetLaunchType get_launch_type = reinterpret_cast( + ::GetProcAddress(metro, "GetLaunchType")); + DCHECK(get_launch_type); + + base::win::MetroLaunchType launch_type = get_launch_type(NULL); + + if ((launch_type == base::win::METRO_PROTOCOL) || + (launch_type == base::win::METRO_LAUNCH)) { + GetInitialUrl initial_metro_url = reinterpret_cast( + ::GetProcAddress(metro, "GetInitialUrl")); + DCHECK(initial_metro_url); + *params = initial_metro_url(); + } else if (launch_type == base::win::METRO_SEARCH) { + GetInitialSearchString initial_search_string = + reinterpret_cast( + ::GetProcAddress(metro, "GetInitialSearchString")); + DCHECK(initial_search_string); + *params = initial_search_string(); + } + return launch_type; +} + +} // namespace win +} // namespace base diff --git a/base/win/metro.h b/base/win/metro.h new file mode 100644 index 0000000000..b2208fcb49 --- /dev/null +++ b/base/win/metro.h @@ -0,0 +1,142 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_WIN_METRO_H_ +#define BASE_WIN_METRO_H_ + +#include +#include + +#include "base/base_export.h" +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/strings/string16.h" + +namespace base { +namespace win { + +// Identifies the type of the metro launch. +enum MetroLaunchType { + METRO_LAUNCH, + METRO_SEARCH, + METRO_SHARE, + METRO_FILE, + METRO_PROTOCOL, + METRO_LAUNCH_ERROR, + METRO_LASTLAUNCHTYPE, +}; + +// In metro mode, this enum identifies the last execution state, i.e. whether +// we crashed, terminated, etc. +enum MetroPreviousExecutionState { + NOTRUNNING, + RUNNING, + SUSPENDED, + TERMINATED, + CLOSEDBYUSER, + LASTEXECUTIONSTATE, +}; + +// Enum values for UMA histogram reporting of site-specific tile pinning. +// TODO(tapted): Move this to win8/util when ready (http://crbug.com/160288). +enum MetroSecondaryTilePinUmaResult { + METRO_PIN_STATE_NONE, + METRO_PIN_INITIATED, + METRO_PIN_LOGO_READY, + METRO_PIN_REQUEST_SHOW_ERROR, + METRO_PIN_RESULT_CANCEL, + METRO_PIN_RESULT_OK, + METRO_PIN_RESULT_OTHER, + METRO_PIN_RESULT_ERROR, + METRO_UNPIN_INITIATED, + METRO_UNPIN_REQUEST_SHOW_ERROR, + METRO_UNPIN_RESULT_CANCEL, + METRO_UNPIN_RESULT_OK, + METRO_UNPIN_RESULT_OTHER, + METRO_UNPIN_RESULT_ERROR, + METRO_PIN_STATE_LIMIT +}; + +// Contains information about the currently displayed tab in metro mode. +struct CurrentTabInfo { + wchar_t* title; + wchar_t* url; +}; + +// Returns the handle to the metro dll loaded in the process. A NULL return +// indicates that the metro dll was not loaded in the process. +BASE_EXPORT HMODULE GetMetroModule(); + +// Returns true if this process is running as an immersive program +// in Windows Metro mode. +BASE_EXPORT bool IsMetroProcess(); + +// Returns true if the process identified by the handle passed in is an +// immersive (Metro) process. +BASE_EXPORT bool IsProcessImmersive(HANDLE process); + +// Returns true if this process is running under Text Services Framework (TSF) +// and browser must be TSF-aware. +BASE_EXPORT bool IsTSFAwareRequired(); + +// Sets browser to use Text Services Framework (TSF) regardless of process +// status. On Windows 8, this function also disables CUAS (Cicero Unaware +// Application Support) to emulate Windows Metro mode in terms of IME +// functionality. This should be beneficial in QA process because on can test +// IME functionality in Windows 8 desktop mode. +BASE_EXPORT void SetForceToUseTSF(); + +// Allocates and returns the destination string via the LocalAlloc API after +// copying the src to it. +BASE_EXPORT wchar_t* LocalAllocAndCopyString(const string16& src); + +// Returns true if Windows Parental control activity logging is enabled. This +// feature is available on Windows Vista and beyond. +// This function should ideally be called on the UI thread. +BASE_EXPORT bool IsParentalControlActivityLoggingOn(); + +// Returns the type of launch and the activation params. For example if the +// the launch is for METRO_PROTOCOL then the params is a url. +BASE_EXPORT MetroLaunchType GetMetroLaunchParams(string16* params); + +// Handler function for the buttons on a metro dialog box +typedef void (*MetroDialogButtonPressedHandler)(); + +// Handler function invoked when a metro style notification is clicked. +typedef void (*MetroNotificationClickedHandler)(const wchar_t* context); + +// Function to display metro style notifications. +typedef void (*MetroNotification)(const char* origin_url, + const char* icon_url, + const wchar_t* title, + const wchar_t* body, + const wchar_t* display_source, + const char* notification_id, + MetroNotificationClickedHandler handler, + const wchar_t* handler_context); + +// Function to cancel displayed notification. +typedef bool (*MetroCancelNotification)(const char* notification_id); + +// Callback for UMA invoked by Metro Pin and UnPin functions after user gesture. +typedef base::Callback + MetroPinUmaResultCallback; + +// Function to pin a site-specific tile (bookmark) to the start screen. +typedef void (*MetroPinToStartScreen)( + const string16& tile_id, + const string16& title, + const string16& url, + const FilePath& logo_path, + const MetroPinUmaResultCallback& callback); + +// Function to un-pin a site-specific tile (bookmark) from the start screen. +typedef void (*MetroUnPinFromStartScreen)( + const string16& title_id, + const MetroPinUmaResultCallback& callback); + +} // namespace win +} // namespace base + +#endif // BASE_WIN_METRO_H_ diff --git a/base/win/object_watcher.cc b/base/win/object_watcher.cc new file mode 100644 index 0000000000..078f5b9fa1 --- /dev/null +++ b/base/win/object_watcher.cc @@ -0,0 +1,111 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/win/object_watcher.h" + +#include "base/bind.h" +#include "base/logging.h" + +namespace base { +namespace win { + +//----------------------------------------------------------------------------- + +ObjectWatcher::ObjectWatcher() + : weak_factory_(this), + object_(NULL), + wait_object_(NULL), + origin_loop_(NULL) { +} + +ObjectWatcher::~ObjectWatcher() { + StopWatching(); +} + +bool ObjectWatcher::StartWatching(HANDLE object, Delegate* delegate) { + CHECK(delegate); + if (wait_object_) { + NOTREACHED() << "Already watching an object"; + return false; + } + + // Since our job is to just notice when an object is signaled and report the + // result back to this thread, we can just run on a Windows wait thread. + DWORD wait_flags = WT_EXECUTEINWAITTHREAD | WT_EXECUTEONLYONCE; + + // DoneWaiting can be synchronously called from RegisterWaitForSingleObject, + // so set up all state now. + callback_ = base::Bind(&ObjectWatcher::Signal, weak_factory_.GetWeakPtr(), + delegate); + object_ = object; + origin_loop_ = MessageLoop::current(); + + if (!RegisterWaitForSingleObject(&wait_object_, object, DoneWaiting, + this, INFINITE, wait_flags)) { + DLOG_GETLASTERROR(FATAL) << "RegisterWaitForSingleObject failed"; + object_ = NULL; + wait_object_ = NULL; + return false; + } + + // We need to know if the current message loop is going away so we can + // prevent the wait thread from trying to access a dead message loop. + MessageLoop::current()->AddDestructionObserver(this); + return true; +} + +bool ObjectWatcher::StopWatching() { + if (!wait_object_) + return false; + + // Make sure ObjectWatcher is used in a single-threaded fashion. + DCHECK_EQ(origin_loop_, MessageLoop::current()); + + // Blocking call to cancel the wait. Any callbacks already in progress will + // finish before we return from this call. + if (!UnregisterWaitEx(wait_object_, INVALID_HANDLE_VALUE)) { + DLOG_GETLASTERROR(FATAL) << "UnregisterWaitEx failed"; + return false; + } + + weak_factory_.InvalidateWeakPtrs(); + object_ = NULL; + wait_object_ = NULL; + + MessageLoop::current()->RemoveDestructionObserver(this); + return true; +} + +HANDLE ObjectWatcher::GetWatchedObject() { + return object_; +} + +// static +void CALLBACK ObjectWatcher::DoneWaiting(void* param, BOOLEAN timed_out) { + DCHECK(!timed_out); + + // The destructor blocks on any callbacks that are in flight, so we know that + // that is always a pointer to a valid ObjectWater. + ObjectWatcher* that = static_cast(param); + that->origin_loop_->PostTask(FROM_HERE, that->callback_); + that->callback_.Reset(); +} + +void ObjectWatcher::Signal(Delegate* delegate) { + // Signaling the delegate may result in our destruction or a nested call to + // StartWatching(). As a result, we save any state we need and clear previous + // watcher state before signaling the delegate. + HANDLE object = object_; + StopWatching(); + delegate->OnObjectSignaled(object); +} + +void ObjectWatcher::WillDestroyCurrentMessageLoop() { + // Need to shutdown the watch so that we don't try to access the MessageLoop + // after this point. + StopWatching(); +} + +} // namespace win +} // namespace base diff --git a/base/win/object_watcher.h b/base/win/object_watcher.h new file mode 100644 index 0000000000..4222c20069 --- /dev/null +++ b/base/win/object_watcher.h @@ -0,0 +1,103 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_WIN_OBJECT_WATCHER_H_ +#define BASE_WIN_OBJECT_WATCHER_H_ + +#include + +#include "base/base_export.h" +#include "base/callback.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop.h" + +namespace base { +namespace win { + +// A class that provides a means to asynchronously wait for a Windows object to +// become signaled. It is an abstraction around RegisterWaitForSingleObject +// that provides a notification callback, OnObjectSignaled, that runs back on +// the origin thread (i.e., the thread that called StartWatching). +// +// This class acts like a smart pointer such that when it goes out-of-scope, +// UnregisterWaitEx is automatically called, and any in-flight notification is +// suppressed. +// +// Typical usage: +// +// class MyClass : public base::ObjectWatcher::Delegate { +// public: +// void DoStuffWhenSignaled(HANDLE object) { +// watcher_.StartWatching(object, this); +// } +// virtual void OnObjectSignaled(HANDLE object) { +// // OK, time to do stuff! +// } +// private: +// base::ObjectWatcher watcher_; +// }; +// +// In the above example, MyClass wants to "do stuff" when object becomes +// signaled. ObjectWatcher makes this task easy. When MyClass goes out of +// scope, the watcher_ will be destroyed, and there is no need to worry about +// OnObjectSignaled being called on a deleted MyClass pointer. Easy! +// If the object is already signaled before being watched, OnObjectSignaled is +// still called after (but not necessarily immediately after) watch is started. +// +class BASE_EXPORT ObjectWatcher : public MessageLoop::DestructionObserver { + public: + class BASE_EXPORT Delegate { + public: + virtual ~Delegate() {} + // Called from the MessageLoop when a signaled object is detected. To + // continue watching the object, StartWatching must be called again. + virtual void OnObjectSignaled(HANDLE object) = 0; + }; + + ObjectWatcher(); + ~ObjectWatcher(); + + // When the object is signaled, the given delegate is notified on the thread + // where StartWatching is called. The ObjectWatcher is not responsible for + // deleting the delegate. + // + // Returns true if the watch was started. Otherwise, false is returned. + // + bool StartWatching(HANDLE object, Delegate* delegate); + + // Stops watching. Does nothing if the watch has already completed. If the + // watch is still active, then it is canceled, and the associated delegate is + // not notified. + // + // Returns true if the watch was canceled. Otherwise, false is returned. + // + bool StopWatching(); + + // Returns the handle of the object being watched, or NULL if the object + // watcher is stopped. + HANDLE GetWatchedObject(); + + private: + // Called on a background thread when done waiting. + static void CALLBACK DoneWaiting(void* param, BOOLEAN timed_out); + + void Signal(Delegate* delegate); + + // MessageLoop::DestructionObserver implementation: + virtual void WillDestroyCurrentMessageLoop(); + + // Internal state. + WeakPtrFactory weak_factory_; + Closure callback_; + HANDLE object_; // The object being watched + HANDLE wait_object_; // Returned by RegisterWaitForSingleObject + MessageLoop* origin_loop_; // Used to get back to the origin thread + + DISALLOW_COPY_AND_ASSIGN(ObjectWatcher); +}; + +} // namespace win +} // namespace base + +#endif // BASE_OBJECT_WATCHER_H_ diff --git a/base/win/object_watcher_unittest.cc b/base/win/object_watcher_unittest.cc new file mode 100644 index 0000000000..46b98de524 --- /dev/null +++ b/base/win/object_watcher_unittest.cc @@ -0,0 +1,174 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/win/object_watcher.h" + +#include + +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace win { + +namespace { + +class QuitDelegate : public ObjectWatcher::Delegate { + public: + virtual void OnObjectSignaled(HANDLE object) { + MessageLoop::current()->QuitWhenIdle(); + } +}; + +class DecrementCountDelegate : public ObjectWatcher::Delegate { + public: + explicit DecrementCountDelegate(int* counter) : counter_(counter) { + } + virtual void OnObjectSignaled(HANDLE object) { + --(*counter_); + } + private: + int* counter_; +}; + +void RunTest_BasicSignal(MessageLoop::Type message_loop_type) { + MessageLoop message_loop(message_loop_type); + + ObjectWatcher watcher; + EXPECT_EQ(NULL, watcher.GetWatchedObject()); + + // A manual-reset event that is not yet signaled. + HANDLE event = CreateEvent(NULL, TRUE, FALSE, NULL); + + QuitDelegate delegate; + bool ok = watcher.StartWatching(event, &delegate); + EXPECT_TRUE(ok); + EXPECT_EQ(event, watcher.GetWatchedObject()); + + SetEvent(event); + + MessageLoop::current()->Run(); + + EXPECT_EQ(NULL, watcher.GetWatchedObject()); + CloseHandle(event); +} + +void RunTest_BasicCancel(MessageLoop::Type message_loop_type) { + MessageLoop message_loop(message_loop_type); + + ObjectWatcher watcher; + + // A manual-reset event that is not yet signaled. + HANDLE event = CreateEvent(NULL, TRUE, FALSE, NULL); + + QuitDelegate delegate; + bool ok = watcher.StartWatching(event, &delegate); + EXPECT_TRUE(ok); + + watcher.StopWatching(); + + CloseHandle(event); +} + +void RunTest_CancelAfterSet(MessageLoop::Type message_loop_type) { + MessageLoop message_loop(message_loop_type); + + ObjectWatcher watcher; + + int counter = 1; + DecrementCountDelegate delegate(&counter); + + // A manual-reset event that is not yet signaled. + HANDLE event = CreateEvent(NULL, TRUE, FALSE, NULL); + + bool ok = watcher.StartWatching(event, &delegate); + EXPECT_TRUE(ok); + + SetEvent(event); + + // Let the background thread do its business + Sleep(30); + + watcher.StopWatching(); + + RunLoop().RunUntilIdle(); + + // Our delegate should not have fired. + EXPECT_EQ(1, counter); + + CloseHandle(event); +} + +void RunTest_SignalBeforeWatch(MessageLoop::Type message_loop_type) { + MessageLoop message_loop(message_loop_type); + + ObjectWatcher watcher; + + // A manual-reset event that is signaled before we begin watching. + HANDLE event = CreateEvent(NULL, TRUE, TRUE, NULL); + + QuitDelegate delegate; + bool ok = watcher.StartWatching(event, &delegate); + EXPECT_TRUE(ok); + + MessageLoop::current()->Run(); + + EXPECT_EQ(NULL, watcher.GetWatchedObject()); + CloseHandle(event); +} + +void RunTest_OutlivesMessageLoop(MessageLoop::Type message_loop_type) { + // Simulate a MessageLoop that dies before an ObjectWatcher. This ordinarily + // doesn't happen when people use the Thread class, but it can happen when + // people use the Singleton pattern or atexit. + HANDLE event = CreateEvent(NULL, TRUE, FALSE, NULL); // not signaled + { + ObjectWatcher watcher; + { + MessageLoop message_loop(message_loop_type); + + QuitDelegate delegate; + watcher.StartWatching(event, &delegate); + } + } + CloseHandle(event); +} + +} // namespace + +//----------------------------------------------------------------------------- + +TEST(ObjectWatcherTest, BasicSignal) { + RunTest_BasicSignal(MessageLoop::TYPE_DEFAULT); + RunTest_BasicSignal(MessageLoop::TYPE_IO); + RunTest_BasicSignal(MessageLoop::TYPE_UI); +} + +TEST(ObjectWatcherTest, BasicCancel) { + RunTest_BasicCancel(MessageLoop::TYPE_DEFAULT); + RunTest_BasicCancel(MessageLoop::TYPE_IO); + RunTest_BasicCancel(MessageLoop::TYPE_UI); +} + +TEST(ObjectWatcherTest, CancelAfterSet) { + RunTest_CancelAfterSet(MessageLoop::TYPE_DEFAULT); + RunTest_CancelAfterSet(MessageLoop::TYPE_IO); + RunTest_CancelAfterSet(MessageLoop::TYPE_UI); +} + +TEST(ObjectWatcherTest, SignalBeforeWatch) { + RunTest_SignalBeforeWatch(MessageLoop::TYPE_DEFAULT); + RunTest_SignalBeforeWatch(MessageLoop::TYPE_IO); + RunTest_SignalBeforeWatch(MessageLoop::TYPE_UI); +} + +TEST(ObjectWatcherTest, OutlivesMessageLoop) { + RunTest_OutlivesMessageLoop(MessageLoop::TYPE_DEFAULT); + RunTest_OutlivesMessageLoop(MessageLoop::TYPE_IO); + RunTest_OutlivesMessageLoop(MessageLoop::TYPE_UI); +} + +} // namespace win +} // namespace base diff --git a/base/win/pe_image.cc b/base/win/pe_image.cc new file mode 100644 index 0000000000..d8a2a4b797 --- /dev/null +++ b/base/win/pe_image.cc @@ -0,0 +1,571 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file implements PEImage, a generic class to manipulate PE files. +// This file was adapted from GreenBorder's Code. + +#include "base/win/pe_image.h" + +namespace base { +namespace win { + +#if defined(_WIN64) && !defined(NACL_WIN64) +// TODO(jschuh): crbug.com/167707 Make sure this is ok. +#pragma message ("Warning: \ + This code is not tested on x64. Please make sure all the base unit tests\ + pass before doing any real work. The current unit tests don't test the\ + differences between 32- and 64-bits implementations. Bugs may slip through.\ + You need to improve the coverage before continuing.") +#endif + +// Structure to perform imports enumerations. +struct EnumAllImportsStorage { + PEImage::EnumImportsFunction callback; + PVOID cookie; +}; + +namespace { + + // Compare two strings byte by byte on an unsigned basis. + // if s1 == s2, return 0 + // if s1 < s2, return negative + // if s1 > s2, return positive + // Exception if inputs are invalid. + int StrCmpByByte(LPCSTR s1, LPCSTR s2) { + while (*s1 != '\0' && *s1 == *s2) { + ++s1; + ++s2; + } + + return (*reinterpret_cast(s1) - + *reinterpret_cast(s2)); + } + +} // namespace + +// Callback used to enumerate imports. See EnumImportChunksFunction. +bool ProcessImportChunk(const PEImage &image, LPCSTR module, + PIMAGE_THUNK_DATA name_table, + PIMAGE_THUNK_DATA iat, PVOID cookie) { + EnumAllImportsStorage &storage = *reinterpret_cast( + cookie); + + return image.EnumOneImportChunk(storage.callback, module, name_table, iat, + storage.cookie); +} + +// Callback used to enumerate delay imports. See EnumDelayImportChunksFunction. +bool ProcessDelayImportChunk(const PEImage &image, + PImgDelayDescr delay_descriptor, + LPCSTR module, PIMAGE_THUNK_DATA name_table, + PIMAGE_THUNK_DATA iat, PIMAGE_THUNK_DATA bound_iat, + PIMAGE_THUNK_DATA unload_iat, PVOID cookie) { + EnumAllImportsStorage &storage = *reinterpret_cast( + cookie); + + return image.EnumOneDelayImportChunk(storage.callback, delay_descriptor, + module, name_table, iat, bound_iat, + unload_iat, storage.cookie); +} + +void PEImage::set_module(HMODULE module) { + module_ = module; +} + +PIMAGE_DOS_HEADER PEImage::GetDosHeader() const { + return reinterpret_cast(module_); +} + +PIMAGE_NT_HEADERS PEImage::GetNTHeaders() const { + PIMAGE_DOS_HEADER dos_header = GetDosHeader(); + + return reinterpret_cast( + reinterpret_cast(dos_header) + dos_header->e_lfanew); +} + +PIMAGE_SECTION_HEADER PEImage::GetSectionHeader(UINT section) const { + PIMAGE_NT_HEADERS nt_headers = GetNTHeaders(); + PIMAGE_SECTION_HEADER first_section = IMAGE_FIRST_SECTION(nt_headers); + + if (section < nt_headers->FileHeader.NumberOfSections) + return first_section + section; + else + return NULL; +} + +WORD PEImage::GetNumSections() const { + return GetNTHeaders()->FileHeader.NumberOfSections; +} + +DWORD PEImage::GetImageDirectoryEntrySize(UINT directory) const { + PIMAGE_NT_HEADERS nt_headers = GetNTHeaders(); + + return nt_headers->OptionalHeader.DataDirectory[directory].Size; +} + +PVOID PEImage::GetImageDirectoryEntryAddr(UINT directory) const { + PIMAGE_NT_HEADERS nt_headers = GetNTHeaders(); + + return RVAToAddr( + nt_headers->OptionalHeader.DataDirectory[directory].VirtualAddress); +} + +PIMAGE_SECTION_HEADER PEImage::GetImageSectionFromAddr(PVOID address) const { + PBYTE target = reinterpret_cast(address); + PIMAGE_SECTION_HEADER section; + + for (UINT i = 0; NULL != (section = GetSectionHeader(i)); i++) { + // Don't use the virtual RVAToAddr. + PBYTE start = reinterpret_cast( + PEImage::RVAToAddr(section->VirtualAddress)); + + DWORD size = section->Misc.VirtualSize; + + if ((start <= target) && (start + size > target)) + return section; + } + + return NULL; +} + +PIMAGE_SECTION_HEADER PEImage::GetImageSectionHeaderByName( + LPCSTR section_name) const { + if (NULL == section_name) + return NULL; + + PIMAGE_SECTION_HEADER ret = NULL; + int num_sections = GetNumSections(); + + for (int i = 0; i < num_sections; i++) { + PIMAGE_SECTION_HEADER section = GetSectionHeader(i); + if (0 == _strnicmp(reinterpret_cast(section->Name), section_name, + sizeof(section->Name))) { + ret = section; + break; + } + } + + return ret; +} + +PDWORD PEImage::GetExportEntry(LPCSTR name) const { + PIMAGE_EXPORT_DIRECTORY exports = GetExportDirectory(); + + if (NULL == exports) + return NULL; + + WORD ordinal = 0; + if (!GetProcOrdinal(name, &ordinal)) + return NULL; + + PDWORD functions = reinterpret_cast( + RVAToAddr(exports->AddressOfFunctions)); + + return functions + ordinal - exports->Base; +} + +FARPROC PEImage::GetProcAddress(LPCSTR function_name) const { + PDWORD export_entry = GetExportEntry(function_name); + if (NULL == export_entry) + return NULL; + + PBYTE function = reinterpret_cast(RVAToAddr(*export_entry)); + + PBYTE exports = reinterpret_cast( + GetImageDirectoryEntryAddr(IMAGE_DIRECTORY_ENTRY_EXPORT)); + DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_EXPORT); + + // Check for forwarded exports as a special case. + if (exports <= function && exports + size > function) +#pragma warning(push) +#pragma warning(disable: 4312) + // This cast generates a warning because it is 32 bit specific. + return reinterpret_cast(0xFFFFFFFF); +#pragma warning(pop) + + return reinterpret_cast(function); +} + +bool PEImage::GetProcOrdinal(LPCSTR function_name, WORD *ordinal) const { + if (NULL == ordinal) + return false; + + PIMAGE_EXPORT_DIRECTORY exports = GetExportDirectory(); + + if (NULL == exports) + return false; + + if (IsOrdinal(function_name)) { + *ordinal = ToOrdinal(function_name); + } else { + PDWORD names = reinterpret_cast(RVAToAddr(exports->AddressOfNames)); + PDWORD lower = names; + PDWORD upper = names + exports->NumberOfNames; + int cmp = -1; + + // Binary Search for the name. + while (lower != upper) { + PDWORD middle = lower + (upper - lower) / 2; + LPCSTR name = reinterpret_cast(RVAToAddr(*middle)); + + // This may be called by sandbox before MSVCRT dll loads, so can't use + // CRT function here. + cmp = StrCmpByByte(function_name, name); + + if (cmp == 0) { + lower = middle; + break; + } + + if (cmp > 0) + lower = middle + 1; + else + upper = middle; + } + + if (cmp != 0) + return false; + + + PWORD ordinals = reinterpret_cast( + RVAToAddr(exports->AddressOfNameOrdinals)); + + *ordinal = ordinals[lower - names] + static_cast(exports->Base); + } + + return true; +} + +bool PEImage::EnumSections(EnumSectionsFunction callback, PVOID cookie) const { + PIMAGE_NT_HEADERS nt_headers = GetNTHeaders(); + UINT num_sections = nt_headers->FileHeader.NumberOfSections; + PIMAGE_SECTION_HEADER section = GetSectionHeader(0); + + for (UINT i = 0; i < num_sections; i++, section++) { + PVOID section_start = RVAToAddr(section->VirtualAddress); + DWORD size = section->Misc.VirtualSize; + + if (!callback(*this, section, section_start, size, cookie)) + return false; + } + + return true; +} + +bool PEImage::EnumExports(EnumExportsFunction callback, PVOID cookie) const { + PVOID directory = GetImageDirectoryEntryAddr(IMAGE_DIRECTORY_ENTRY_EXPORT); + DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_EXPORT); + + // Check if there are any exports at all. + if (NULL == directory || 0 == size) + return true; + + PIMAGE_EXPORT_DIRECTORY exports = reinterpret_cast( + directory); + UINT ordinal_base = exports->Base; + UINT num_funcs = exports->NumberOfFunctions; + UINT num_names = exports->NumberOfNames; + PDWORD functions = reinterpret_cast(RVAToAddr( + exports->AddressOfFunctions)); + PDWORD names = reinterpret_cast(RVAToAddr(exports->AddressOfNames)); + PWORD ordinals = reinterpret_cast(RVAToAddr( + exports->AddressOfNameOrdinals)); + + for (UINT count = 0; count < num_funcs; count++) { + PVOID func = RVAToAddr(functions[count]); + if (NULL == func) + continue; + + // Check for a name. + LPCSTR name = NULL; + UINT hint; + for (hint = 0; hint < num_names; hint++) { + if (ordinals[hint] == count) { + name = reinterpret_cast(RVAToAddr(names[hint])); + break; + } + } + + if (name == NULL) + hint = 0; + + // Check for forwarded exports. + LPCSTR forward = NULL; + if (reinterpret_cast(func) >= reinterpret_cast(directory) && + reinterpret_cast(func) <= reinterpret_cast(directory) + + size) { + forward = reinterpret_cast(func); + func = 0; + } + + if (!callback(*this, ordinal_base + count, hint, name, func, forward, + cookie)) + return false; + } + + return true; +} + +bool PEImage::EnumRelocs(EnumRelocsFunction callback, PVOID cookie) const { + PVOID directory = GetImageDirectoryEntryAddr(IMAGE_DIRECTORY_ENTRY_BASERELOC); + DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_BASERELOC); + PIMAGE_BASE_RELOCATION base = reinterpret_cast( + directory); + + if (directory == NULL || size < sizeof(IMAGE_BASE_RELOCATION)) + return true; + + while (base->SizeOfBlock) { + PWORD reloc = reinterpret_cast(base + 1); + UINT num_relocs = (base->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / + sizeof(WORD); + + for (UINT i = 0; i < num_relocs; i++, reloc++) { + WORD type = *reloc >> 12; + PVOID address = RVAToAddr(base->VirtualAddress + (*reloc & 0x0FFF)); + + if (!callback(*this, type, address, cookie)) + return false; + } + + base = reinterpret_cast( + reinterpret_cast(base) + base->SizeOfBlock); + } + + return true; +} + +bool PEImage::EnumImportChunks(EnumImportChunksFunction callback, + PVOID cookie) const { + DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_IMPORT); + PIMAGE_IMPORT_DESCRIPTOR import = GetFirstImportChunk(); + + if (import == NULL || size < sizeof(IMAGE_IMPORT_DESCRIPTOR)) + return true; + + for (; import->FirstThunk; import++) { + LPCSTR module_name = reinterpret_cast(RVAToAddr(import->Name)); + PIMAGE_THUNK_DATA name_table = reinterpret_cast( + RVAToAddr(import->OriginalFirstThunk)); + PIMAGE_THUNK_DATA iat = reinterpret_cast( + RVAToAddr(import->FirstThunk)); + + if (!callback(*this, module_name, name_table, iat, cookie)) + return false; + } + + return true; +} + +bool PEImage::EnumOneImportChunk(EnumImportsFunction callback, + LPCSTR module_name, + PIMAGE_THUNK_DATA name_table, + PIMAGE_THUNK_DATA iat, PVOID cookie) const { + if (NULL == name_table) + return false; + + for (; name_table && name_table->u1.Ordinal; name_table++, iat++) { + LPCSTR name = NULL; + WORD ordinal = 0; + WORD hint = 0; + + if (IMAGE_SNAP_BY_ORDINAL(name_table->u1.Ordinal)) { + ordinal = static_cast(IMAGE_ORDINAL32(name_table->u1.Ordinal)); + } else { + PIMAGE_IMPORT_BY_NAME import = reinterpret_cast( + RVAToAddr(name_table->u1.ForwarderString)); + + hint = import->Hint; + name = reinterpret_cast(&import->Name); + } + + if (!callback(*this, module_name, ordinal, name, hint, iat, cookie)) + return false; + } + + return true; +} + +bool PEImage::EnumAllImports(EnumImportsFunction callback, PVOID cookie) const { + EnumAllImportsStorage temp = { callback, cookie }; + return EnumImportChunks(ProcessImportChunk, &temp); +} + +bool PEImage::EnumDelayImportChunks(EnumDelayImportChunksFunction callback, + PVOID cookie) const { + PVOID directory = GetImageDirectoryEntryAddr( + IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT); + DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT); + PImgDelayDescr delay_descriptor = reinterpret_cast(directory); + + if (directory == NULL || size == 0) + return true; + + for (; delay_descriptor->rvaHmod; delay_descriptor++) { + PIMAGE_THUNK_DATA name_table; + PIMAGE_THUNK_DATA iat; + PIMAGE_THUNK_DATA bound_iat; // address of the optional bound IAT + PIMAGE_THUNK_DATA unload_iat; // address of optional copy of original IAT + LPCSTR module_name; + + // check if VC7-style imports, using RVAs instead of + // VC6-style addresses. + bool rvas = (delay_descriptor->grAttrs & dlattrRva) != 0; + + if (rvas) { + module_name = reinterpret_cast( + RVAToAddr(delay_descriptor->rvaDLLName)); + name_table = reinterpret_cast( + RVAToAddr(delay_descriptor->rvaINT)); + iat = reinterpret_cast( + RVAToAddr(delay_descriptor->rvaIAT)); + bound_iat = reinterpret_cast( + RVAToAddr(delay_descriptor->rvaBoundIAT)); + unload_iat = reinterpret_cast( + RVAToAddr(delay_descriptor->rvaUnloadIAT)); + } else { +#pragma warning(push) +#pragma warning(disable: 4312) + // These casts generate warnings because they are 32 bit specific. + module_name = reinterpret_cast(delay_descriptor->rvaDLLName); + name_table = reinterpret_cast( + delay_descriptor->rvaINT); + iat = reinterpret_cast(delay_descriptor->rvaIAT); + bound_iat = reinterpret_cast( + delay_descriptor->rvaBoundIAT); + unload_iat = reinterpret_cast( + delay_descriptor->rvaUnloadIAT); +#pragma warning(pop) + } + + if (!callback(*this, delay_descriptor, module_name, name_table, iat, + bound_iat, unload_iat, cookie)) + return false; + } + + return true; +} + +bool PEImage::EnumOneDelayImportChunk(EnumImportsFunction callback, + PImgDelayDescr delay_descriptor, + LPCSTR module_name, + PIMAGE_THUNK_DATA name_table, + PIMAGE_THUNK_DATA iat, + PIMAGE_THUNK_DATA bound_iat, + PIMAGE_THUNK_DATA unload_iat, + PVOID cookie) const { + UNREFERENCED_PARAMETER(bound_iat); + UNREFERENCED_PARAMETER(unload_iat); + + for (; name_table->u1.Ordinal; name_table++, iat++) { + LPCSTR name = NULL; + WORD ordinal = 0; + WORD hint = 0; + + if (IMAGE_SNAP_BY_ORDINAL(name_table->u1.Ordinal)) { + ordinal = static_cast(IMAGE_ORDINAL32(name_table->u1.Ordinal)); + } else { + PIMAGE_IMPORT_BY_NAME import; + bool rvas = (delay_descriptor->grAttrs & dlattrRva) != 0; + + if (rvas) { + import = reinterpret_cast( + RVAToAddr(name_table->u1.ForwarderString)); + } else { +#pragma warning(push) +#pragma warning(disable: 4312) + // This cast generates a warning because it is 32 bit specific. + import = reinterpret_cast( + name_table->u1.ForwarderString); +#pragma warning(pop) + } + + hint = import->Hint; + name = reinterpret_cast(&import->Name); + } + + if (!callback(*this, module_name, ordinal, name, hint, iat, cookie)) + return false; + } + + return true; +} + +bool PEImage::EnumAllDelayImports(EnumImportsFunction callback, + PVOID cookie) const { + EnumAllImportsStorage temp = { callback, cookie }; + return EnumDelayImportChunks(ProcessDelayImportChunk, &temp); +} + +bool PEImage::VerifyMagic() const { + PIMAGE_DOS_HEADER dos_header = GetDosHeader(); + + if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) + return false; + + PIMAGE_NT_HEADERS nt_headers = GetNTHeaders(); + + if (nt_headers->Signature != IMAGE_NT_SIGNATURE) + return false; + + if (nt_headers->FileHeader.SizeOfOptionalHeader != + sizeof(IMAGE_OPTIONAL_HEADER)) + return false; + + if (nt_headers->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC) + return false; + + return true; +} + +bool PEImage::ImageRVAToOnDiskOffset(DWORD rva, DWORD *on_disk_offset) const { + LPVOID address = RVAToAddr(rva); + return ImageAddrToOnDiskOffset(address, on_disk_offset); +} + +bool PEImage::ImageAddrToOnDiskOffset(LPVOID address, + DWORD *on_disk_offset) const { + if (NULL == address) + return false; + + // Get the section that this address belongs to. + PIMAGE_SECTION_HEADER section_header = GetImageSectionFromAddr(address); + if (NULL == section_header) + return false; + +#pragma warning(push) +#pragma warning(disable: 4311) + // These casts generate warnings because they are 32 bit specific. + // Don't follow the virtual RVAToAddr, use the one on the base. + DWORD offset_within_section = reinterpret_cast(address) - + reinterpret_cast(PEImage::RVAToAddr( + section_header->VirtualAddress)); +#pragma warning(pop) + + *on_disk_offset = section_header->PointerToRawData + offset_within_section; + return true; +} + +PVOID PEImage::RVAToAddr(DWORD rva) const { + if (rva == 0) + return NULL; + + return reinterpret_cast(module_) + rva; +} + +PVOID PEImageAsData::RVAToAddr(DWORD rva) const { + if (rva == 0) + return NULL; + + PVOID in_memory = PEImage::RVAToAddr(rva); + DWORD disk_offset; + + if (!ImageAddrToOnDiskOffset(in_memory, &disk_offset)) + return NULL; + + return PEImage::RVAToAddr(disk_offset); +} + +} // namespace win +} // namespace base diff --git a/base/win/pe_image.h b/base/win/pe_image.h new file mode 100644 index 0000000000..878ef528c9 --- /dev/null +++ b/base/win/pe_image.h @@ -0,0 +1,268 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was adapted from GreenBorder's Code. +// To understand what this class is about (for other than well known functions +// as GetProcAddress), a good starting point is "An In-Depth Look into the +// Win32 Portable Executable File Format" by Matt Pietrek: +// http://msdn.microsoft.com/msdnmag/issues/02/02/PE/default.aspx + +#ifndef BASE_WIN_PE_IMAGE_H_ +#define BASE_WIN_PE_IMAGE_H_ + +#include + +#if defined(_WIN32_WINNT_WIN8) +// The Windows 8 SDK defines FACILITY_VISUALCPP in winerror.h. +#undef FACILITY_VISUALCPP +#endif +#include + +namespace base { +namespace win { + +// This class is a wrapper for the Portable Executable File Format (PE). +// It's main purpose is to provide an easy way to work with imports and exports +// from a file, mapped in memory as image. +class PEImage { + public: + // Callback to enumerate sections. + // cookie is the value passed to the enumerate method. + // Returns true to continue the enumeration. + typedef bool (*EnumSectionsFunction)(const PEImage &image, + PIMAGE_SECTION_HEADER header, + PVOID section_start, DWORD section_size, + PVOID cookie); + + // Callback to enumerate exports. + // function is the actual address of the symbol. If forward is not null, it + // contains the dll and symbol to forward this export to. cookie is the value + // passed to the enumerate method. + // Returns true to continue the enumeration. + typedef bool (*EnumExportsFunction)(const PEImage &image, DWORD ordinal, + DWORD hint, LPCSTR name, PVOID function, + LPCSTR forward, PVOID cookie); + + // Callback to enumerate import blocks. + // name_table and iat point to the imports name table and address table for + // this block. cookie is the value passed to the enumerate method. + // Returns true to continue the enumeration. + typedef bool (*EnumImportChunksFunction)(const PEImage &image, LPCSTR module, + PIMAGE_THUNK_DATA name_table, + PIMAGE_THUNK_DATA iat, PVOID cookie); + + // Callback to enumerate imports. + // module is the dll that exports this symbol. cookie is the value passed to + // the enumerate method. + // Returns true to continue the enumeration. + typedef bool (*EnumImportsFunction)(const PEImage &image, LPCSTR module, + DWORD ordinal, LPCSTR name, DWORD hint, + PIMAGE_THUNK_DATA iat, PVOID cookie); + + // Callback to enumerate dalayed import blocks. + // module is the dll that exports this block of symbols. cookie is the value + // passed to the enumerate method. + // Returns true to continue the enumeration. + typedef bool (*EnumDelayImportChunksFunction)(const PEImage &image, + PImgDelayDescr delay_descriptor, + LPCSTR module, + PIMAGE_THUNK_DATA name_table, + PIMAGE_THUNK_DATA iat, + PIMAGE_THUNK_DATA bound_iat, + PIMAGE_THUNK_DATA unload_iat, + PVOID cookie); + + // Callback to enumerate relocations. + // cookie is the value passed to the enumerate method. + // Returns true to continue the enumeration. + typedef bool (*EnumRelocsFunction)(const PEImage &image, WORD type, + PVOID address, PVOID cookie); + + explicit PEImage(HMODULE module) : module_(module) {} + explicit PEImage(const void* module) { + module_ = reinterpret_cast(const_cast(module)); + } + + // Gets the HMODULE for this object. + HMODULE module() const; + + // Sets this object's HMODULE. + void set_module(HMODULE module); + + // Checks if this symbol is actually an ordinal. + static bool IsOrdinal(LPCSTR name); + + // Converts a named symbol to the corresponding ordinal. + static WORD ToOrdinal(LPCSTR name); + + // Returns the DOS_HEADER for this PE. + PIMAGE_DOS_HEADER GetDosHeader() const; + + // Returns the NT_HEADER for this PE. + PIMAGE_NT_HEADERS GetNTHeaders() const; + + // Returns number of sections of this PE. + WORD GetNumSections() const; + + // Returns the header for a given section. + // returns NULL if there is no such section. + PIMAGE_SECTION_HEADER GetSectionHeader(UINT section) const; + + // Returns the size of a given directory entry. + DWORD GetImageDirectoryEntrySize(UINT directory) const; + + // Returns the address of a given directory entry. + PVOID GetImageDirectoryEntryAddr(UINT directory) const; + + // Returns the section header for a given address. + // Use: s = image.GetImageSectionFromAddr(a); + // Post: 's' is the section header of the section that contains 'a' + // or NULL if there is no such section. + PIMAGE_SECTION_HEADER GetImageSectionFromAddr(PVOID address) const; + + // Returns the section header for a given section. + PIMAGE_SECTION_HEADER GetImageSectionHeaderByName(LPCSTR section_name) const; + + // Returns the first block of imports. + PIMAGE_IMPORT_DESCRIPTOR GetFirstImportChunk() const; + + // Returns the exports directory. + PIMAGE_EXPORT_DIRECTORY GetExportDirectory() const; + + // Returns a given export entry. + // Use: e = image.GetExportEntry(f); + // Pre: 'f' is either a zero terminated string or ordinal + // Post: 'e' is a pointer to the export directory entry + // that contains 'f's export RVA, or NULL if 'f' + // is not exported from this image + PDWORD GetExportEntry(LPCSTR name) const; + + // Returns the address for a given exported symbol. + // Use: p = image.GetProcAddress(f); + // Pre: 'f' is either a zero terminated string or ordinal. + // Post: if 'f' is a non-forwarded export from image, 'p' is + // the exported function. If 'f' is a forwarded export + // then p is the special value 0xFFFFFFFF. In this case + // RVAToAddr(*GetExportEntry) can be used to resolve + // the string that describes the forward. + FARPROC GetProcAddress(LPCSTR function_name) const; + + // Retrieves the ordinal for a given exported symbol. + // Returns true if the symbol was found. + bool GetProcOrdinal(LPCSTR function_name, WORD *ordinal) const; + + // Enumerates PE sections. + // cookie is a generic cookie to pass to the callback. + // Returns true on success. + bool EnumSections(EnumSectionsFunction callback, PVOID cookie) const; + + // Enumerates PE exports. + // cookie is a generic cookie to pass to the callback. + // Returns true on success. + bool EnumExports(EnumExportsFunction callback, PVOID cookie) const; + + // Enumerates PE imports. + // cookie is a generic cookie to pass to the callback. + // Returns true on success. + bool EnumAllImports(EnumImportsFunction callback, PVOID cookie) const; + + // Enumerates PE import blocks. + // cookie is a generic cookie to pass to the callback. + // Returns true on success. + bool EnumImportChunks(EnumImportChunksFunction callback, PVOID cookie) const; + + // Enumerates the imports from a single PE import block. + // cookie is a generic cookie to pass to the callback. + // Returns true on success. + bool EnumOneImportChunk(EnumImportsFunction callback, LPCSTR module_name, + PIMAGE_THUNK_DATA name_table, PIMAGE_THUNK_DATA iat, + PVOID cookie) const; + + + // Enumerates PE delay imports. + // cookie is a generic cookie to pass to the callback. + // Returns true on success. + bool EnumAllDelayImports(EnumImportsFunction callback, PVOID cookie) const; + + // Enumerates PE delay import blocks. + // cookie is a generic cookie to pass to the callback. + // Returns true on success. + bool EnumDelayImportChunks(EnumDelayImportChunksFunction callback, + PVOID cookie) const; + + // Enumerates imports from a single PE delay import block. + // cookie is a generic cookie to pass to the callback. + // Returns true on success. + bool EnumOneDelayImportChunk(EnumImportsFunction callback, + PImgDelayDescr delay_descriptor, + LPCSTR module_name, + PIMAGE_THUNK_DATA name_table, + PIMAGE_THUNK_DATA iat, + PIMAGE_THUNK_DATA bound_iat, + PIMAGE_THUNK_DATA unload_iat, + PVOID cookie) const; + + // Enumerates PE relocation entries. + // cookie is a generic cookie to pass to the callback. + // Returns true on success. + bool EnumRelocs(EnumRelocsFunction callback, PVOID cookie) const; + + // Verifies the magic values on the PE file. + // Returns true if all values are correct. + bool VerifyMagic() const; + + // Converts an rva value to the appropriate address. + virtual PVOID RVAToAddr(DWORD rva) const; + + // Converts an rva value to an offset on disk. + // Returns true on success. + bool ImageRVAToOnDiskOffset(DWORD rva, DWORD *on_disk_offset) const; + + // Converts an address to an offset on disk. + // Returns true on success. + bool ImageAddrToOnDiskOffset(LPVOID address, DWORD *on_disk_offset) const; + + private: + HMODULE module_; +}; + +// This class is an extension to the PEImage class that allows working with PE +// files mapped as data instead of as image file. +class PEImageAsData : public PEImage { + public: + explicit PEImageAsData(HMODULE hModule) : PEImage(hModule) {} + + virtual PVOID RVAToAddr(DWORD rva) const; +}; + +inline bool PEImage::IsOrdinal(LPCSTR name) { +#pragma warning(push) +#pragma warning(disable: 4311) + // This cast generates a warning because it is 32 bit specific. + return reinterpret_cast(name) <= 0xFFFF; +#pragma warning(pop) +} + +inline WORD PEImage::ToOrdinal(LPCSTR name) { + return reinterpret_cast(name); +} + +inline HMODULE PEImage::module() const { + return module_; +} + +inline PIMAGE_IMPORT_DESCRIPTOR PEImage::GetFirstImportChunk() const { + return reinterpret_cast( + GetImageDirectoryEntryAddr(IMAGE_DIRECTORY_ENTRY_IMPORT)); +} + +inline PIMAGE_EXPORT_DIRECTORY PEImage::GetExportDirectory() const { + return reinterpret_cast( + GetImageDirectoryEntryAddr(IMAGE_DIRECTORY_ENTRY_EXPORT)); +} + +} // namespace win +} // namespace base + +#endif // BASE_WIN_PE_IMAGE_H_ diff --git a/base/win/pe_image_unittest.cc b/base/win/pe_image_unittest.cc new file mode 100644 index 0000000000..238c924f62 --- /dev/null +++ b/base/win/pe_image_unittest.cc @@ -0,0 +1,271 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains unit tests for PEImage. + +#include "testing/gtest/include/gtest/gtest.h" +#include "base/win/pe_image.h" +#include "base/win/windows_version.h" + +namespace base { +namespace win { + +// Just counts the number of invocations. +bool ExportsCallback(const PEImage &image, + DWORD ordinal, + DWORD hint, + LPCSTR name, + PVOID function, + LPCSTR forward, + PVOID cookie) { + int* count = reinterpret_cast(cookie); + (*count)++; + return true; +} + +// Just counts the number of invocations. +bool ImportsCallback(const PEImage &image, + LPCSTR module, + DWORD ordinal, + LPCSTR name, + DWORD hint, + PIMAGE_THUNK_DATA iat, + PVOID cookie) { + int* count = reinterpret_cast(cookie); + (*count)++; + return true; +} + +// Just counts the number of invocations. +bool SectionsCallback(const PEImage &image, + PIMAGE_SECTION_HEADER header, + PVOID section_start, + DWORD section_size, + PVOID cookie) { + int* count = reinterpret_cast(cookie); + (*count)++; + return true; +} + +// Just counts the number of invocations. +bool RelocsCallback(const PEImage &image, + WORD type, + PVOID address, + PVOID cookie) { + int* count = reinterpret_cast(cookie); + (*count)++; + return true; +} + +// Just counts the number of invocations. +bool ImportChunksCallback(const PEImage &image, + LPCSTR module, + PIMAGE_THUNK_DATA name_table, + PIMAGE_THUNK_DATA iat, + PVOID cookie) { + int* count = reinterpret_cast(cookie); + (*count)++; + return true; +} + +// Just counts the number of invocations. +bool DelayImportChunksCallback(const PEImage &image, + PImgDelayDescr delay_descriptor, + LPCSTR module, + PIMAGE_THUNK_DATA name_table, + PIMAGE_THUNK_DATA iat, + PIMAGE_THUNK_DATA bound_iat, + PIMAGE_THUNK_DATA unload_iat, + PVOID cookie) { + int* count = reinterpret_cast(cookie); + (*count)++; + return true; +} + +// Identifiers for the set of supported expectations. +enum ExpectationSet { + WIN_2K_SET, + WIN_XP_SET, + WIN_VISTA_SET, + WIN_7_SET, + WIN_8_SET, + UNSUPPORTED_SET, +}; + +// We'll be using some known values for the tests. +enum Value { + sections = 0, + imports_dlls, + delay_dlls, + exports, + imports, + delay_imports, + relocs +}; + +ExpectationSet GetExpectationSet(DWORD os) { + if (os == 50) + return WIN_2K_SET; + if (os == 51) + return WIN_XP_SET; + if (os == 60) + return WIN_VISTA_SET; + if (os == 61) + return WIN_7_SET; + if (os >= 62) + return WIN_8_SET; + return UNSUPPORTED_SET; +} + +// Retrieves the expected value from advapi32.dll based on the OS. +int GetExpectedValue(Value value, DWORD os) { + const int xp_delay_dlls = 2; + const int xp_exports = 675; + const int xp_imports = 422; + const int xp_delay_imports = 8; + const int xp_relocs = 9180; + const int vista_delay_dlls = 4; + const int vista_exports = 799; + const int vista_imports = 476; + const int vista_delay_imports = 24; + const int vista_relocs = 10188; + const int w2k_delay_dlls = 0; + const int w2k_exports = 566; + const int w2k_imports = 357; + const int w2k_delay_imports = 0; + const int w2k_relocs = 7388; + const int win7_delay_dlls = 7; + const int win7_exports = 806; + const int win7_imports = 568; + const int win7_delay_imports = 71; + int win7_relocs = 7812; + int win7_sections = 4; + const int win8_delay_dlls = 9; + const int win8_exports = 806; + const int win8_imports = 568; + const int win8_delay_imports = 113; + const int win8_relocs = 9478; + int win8_sections = 4; + int win8_import_dlls = 17; + + base::win::OSInfo* os_info = base::win::OSInfo::GetInstance(); + // 32-bit process on a 32-bit system. + if (os_info->architecture() == base::win::OSInfo::X86_ARCHITECTURE) { + win8_sections = 5; + win8_import_dlls = 19; + + // 64-bit process on a 64-bit system. + } else if (os_info->wow64_status() == base::win::OSInfo::WOW64_DISABLED) { + win7_sections = 6; + win7_relocs = 2712; + } + + // Contains the expected value, for each enumerated property (Value), and the + // OS version: [Value][os_version] + const int expected[][5] = { + {4, 4, 4, win7_sections, win8_sections}, + {3, 3, 3, 13, win8_import_dlls}, + {w2k_delay_dlls, xp_delay_dlls, vista_delay_dlls, win7_delay_dlls, + win8_delay_dlls}, + {w2k_exports, xp_exports, vista_exports, win7_exports, win8_exports}, + {w2k_imports, xp_imports, vista_imports, win7_imports, win8_imports}, + {w2k_delay_imports, xp_delay_imports, + vista_delay_imports, win7_delay_imports, win8_delay_imports}, + {w2k_relocs, xp_relocs, vista_relocs, win7_relocs, win8_relocs} + }; + COMPILE_ASSERT(arraysize(expected[0]) == UNSUPPORTED_SET, + expected_value_set_mismatch); + + if (value > relocs) + return 0; + ExpectationSet expected_set = GetExpectationSet(os); + if (expected_set >= arraysize(expected)) { + // This should never happen. Log a failure if it does. + EXPECT_NE(UNSUPPORTED_SET, expected_set); + expected_set = WIN_2K_SET; + } + + return expected[value][expected_set]; +} + + +// TODO(jschuh): crbug.com/167707 Need to fix test on Win64 bots +#if defined(OS_WIN) && defined(ARCH_CPU_X86_64) +#define MAYBE_EnumeratesPE DISABLED_EnumeratesPE +#else +#define MAYBE_EnumeratesPE EnumeratesPE +#endif + +// Tests that we are able to enumerate stuff from a PE file, and that +// the actual number of items found is within the expected range. +TEST(PEImageTest, MAYBE_EnumeratesPE) { + HMODULE module = LoadLibrary(L"advapi32.dll"); + ASSERT_TRUE(NULL != module); + + PEImage pe(module); + int count = 0; + EXPECT_TRUE(pe.VerifyMagic()); + + DWORD os = pe.GetNTHeaders()->OptionalHeader.MajorOperatingSystemVersion; + os = os * 10 + pe.GetNTHeaders()->OptionalHeader.MinorOperatingSystemVersion; + + // Skip this test for unsupported OS versions. + if (GetExpectationSet(os) == UNSUPPORTED_SET) + return; + + pe.EnumSections(SectionsCallback, &count); + EXPECT_EQ(GetExpectedValue(sections, os), count); + + count = 0; + pe.EnumImportChunks(ImportChunksCallback, &count); + EXPECT_EQ(GetExpectedValue(imports_dlls, os), count); + + count = 0; + pe.EnumDelayImportChunks(DelayImportChunksCallback, &count); + EXPECT_EQ(GetExpectedValue(delay_dlls, os), count); + + count = 0; + pe.EnumExports(ExportsCallback, &count); + EXPECT_GT(count, GetExpectedValue(exports, os) - 20); + EXPECT_LT(count, GetExpectedValue(exports, os) + 100); + + count = 0; + pe.EnumAllImports(ImportsCallback, &count); + EXPECT_GT(count, GetExpectedValue(imports, os) - 20); + EXPECT_LT(count, GetExpectedValue(imports, os) + 100); + + count = 0; + pe.EnumAllDelayImports(ImportsCallback, &count); + EXPECT_GT(count, GetExpectedValue(delay_imports, os) - 2); + EXPECT_LT(count, GetExpectedValue(delay_imports, os) + 8); + + count = 0; + pe.EnumRelocs(RelocsCallback, &count); + EXPECT_GT(count, GetExpectedValue(relocs, os) - 150); + EXPECT_LT(count, GetExpectedValue(relocs, os) + 1500); + + FreeLibrary(module); +} + +// Tests that we can locate an specific exported symbol, by name and by ordinal. +TEST(PEImageTest, RetrievesExports) { + HMODULE module = LoadLibrary(L"advapi32.dll"); + ASSERT_TRUE(NULL != module); + + PEImage pe(module); + WORD ordinal; + + EXPECT_TRUE(pe.GetProcOrdinal("RegEnumKeyExW", &ordinal)); + + FARPROC address1 = pe.GetProcAddress("RegEnumKeyExW"); + FARPROC address2 = pe.GetProcAddress(reinterpret_cast(ordinal)); + EXPECT_TRUE(address1 != NULL); + EXPECT_TRUE(address2 != NULL); + EXPECT_TRUE(address1 == address2); + + FreeLibrary(module); +} + +} // namespace win +} // namespace base diff --git a/base/win/registry.cc b/base/win/registry.cc new file mode 100644 index 0000000000..8bfe432971 --- /dev/null +++ b/base/win/registry.cc @@ -0,0 +1,483 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/win/registry.h" + +#include +#include + +#include "base/logging.h" +#include "base/strings/string_util.h" +#include "base/threading/thread_restrictions.h" + +#pragma comment(lib, "shlwapi.lib") // for SHDeleteKey + +namespace base { +namespace win { + +namespace { + +// RegEnumValue() reports the number of characters from the name that were +// written to the buffer, not how many there are. This constant is the maximum +// name size, such that a buffer with this size should read any name. +const DWORD MAX_REGISTRY_NAME_SIZE = 16384; + +// Registry values are read as BYTE* but can have wchar_t* data whose last +// wchar_t is truncated. This function converts the reported |byte_size| to +// a size in wchar_t that can store a truncated wchar_t if necessary. +inline DWORD to_wchar_size(DWORD byte_size) { + return (byte_size + sizeof(wchar_t) - 1) / sizeof(wchar_t); +} + +} // namespace + +// RegKey ---------------------------------------------------------------------- + +RegKey::RegKey() + : key_(NULL), + watch_event_(0) { +} + +RegKey::RegKey(HKEY key) + : key_(key), + watch_event_(0) { +} + +RegKey::RegKey(HKEY rootkey, const wchar_t* subkey, REGSAM access) + : key_(NULL), + watch_event_(0) { + if (rootkey) { + if (access & (KEY_SET_VALUE | KEY_CREATE_SUB_KEY | KEY_CREATE_LINK)) + Create(rootkey, subkey, access); + else + Open(rootkey, subkey, access); + } else { + DCHECK(!subkey); + } +} + +RegKey::~RegKey() { + Close(); +} + +LONG RegKey::Create(HKEY rootkey, const wchar_t* subkey, REGSAM access) { + DWORD disposition_value; + return CreateWithDisposition(rootkey, subkey, &disposition_value, access); +} + +LONG RegKey::CreateWithDisposition(HKEY rootkey, const wchar_t* subkey, + DWORD* disposition, REGSAM access) { + DCHECK(rootkey && subkey && access && disposition); + Close(); + + LONG result = RegCreateKeyEx(rootkey, subkey, 0, NULL, + REG_OPTION_NON_VOLATILE, access, NULL, &key_, + disposition); + return result; +} + +LONG RegKey::CreateKey(const wchar_t* name, REGSAM access) { + DCHECK(name && access); + HKEY subkey = NULL; + LONG result = RegCreateKeyEx(key_, name, 0, NULL, REG_OPTION_NON_VOLATILE, + access, NULL, &subkey, NULL); + Close(); + + key_ = subkey; + return result; +} + +LONG RegKey::Open(HKEY rootkey, const wchar_t* subkey, REGSAM access) { + DCHECK(rootkey && subkey && access); + Close(); + + LONG result = RegOpenKeyEx(rootkey, subkey, 0, access, &key_); + return result; +} + +LONG RegKey::OpenKey(const wchar_t* relative_key_name, REGSAM access) { + DCHECK(relative_key_name && access); + HKEY subkey = NULL; + LONG result = RegOpenKeyEx(key_, relative_key_name, 0, access, &subkey); + + // We have to close the current opened key before replacing it with the new + // one. + Close(); + + key_ = subkey; + return result; +} + +void RegKey::Close() { + StopWatching(); + if (key_) { + ::RegCloseKey(key_); + key_ = NULL; + } +} + +void RegKey::Set(HKEY key) { + if (key_ != key) { + Close(); + key_ = key; + } +} + +HKEY RegKey::Take() { + StopWatching(); + HKEY key = key_; + key_ = NULL; + return key; +} + +bool RegKey::HasValue(const wchar_t* name) const { + return RegQueryValueEx(key_, name, 0, NULL, NULL, NULL) == ERROR_SUCCESS; +} + +DWORD RegKey::GetValueCount() const { + DWORD count = 0; + LONG result = RegQueryInfoKey(key_, NULL, 0, NULL, NULL, NULL, NULL, &count, + NULL, NULL, NULL, NULL); + return (result == ERROR_SUCCESS) ? count : 0; +} + +LONG RegKey::GetValueNameAt(int index, std::wstring* name) const { + wchar_t buf[256]; + DWORD bufsize = arraysize(buf); + LONG r = ::RegEnumValue(key_, index, buf, &bufsize, NULL, NULL, NULL, NULL); + if (r == ERROR_SUCCESS) + *name = buf; + + return r; +} + +LONG RegKey::DeleteKey(const wchar_t* name) { + DCHECK(key_); + DCHECK(name); + LONG result = SHDeleteKey(key_, name); + return result; +} + +LONG RegKey::DeleteValue(const wchar_t* value_name) { + DCHECK(key_); + LONG result = RegDeleteValue(key_, value_name); + return result; +} + +LONG RegKey::ReadValueDW(const wchar_t* name, DWORD* out_value) const { + DCHECK(out_value); + DWORD type = REG_DWORD; + DWORD size = sizeof(DWORD); + DWORD local_value = 0; + LONG result = ReadValue(name, &local_value, &size, &type); + if (result == ERROR_SUCCESS) { + if ((type == REG_DWORD || type == REG_BINARY) && size == sizeof(DWORD)) + *out_value = local_value; + else + result = ERROR_CANTREAD; + } + + return result; +} + +LONG RegKey::ReadInt64(const wchar_t* name, int64* out_value) const { + DCHECK(out_value); + DWORD type = REG_QWORD; + int64 local_value = 0; + DWORD size = sizeof(local_value); + LONG result = ReadValue(name, &local_value, &size, &type); + if (result == ERROR_SUCCESS) { + if ((type == REG_QWORD || type == REG_BINARY) && + size == sizeof(local_value)) + *out_value = local_value; + else + result = ERROR_CANTREAD; + } + + return result; +} + +LONG RegKey::ReadValue(const wchar_t* name, std::wstring* out_value) const { + DCHECK(out_value); + const size_t kMaxStringLength = 1024; // This is after expansion. + // Use the one of the other forms of ReadValue if 1024 is too small for you. + wchar_t raw_value[kMaxStringLength]; + DWORD type = REG_SZ, size = sizeof(raw_value); + LONG result = ReadValue(name, raw_value, &size, &type); + if (result == ERROR_SUCCESS) { + if (type == REG_SZ) { + *out_value = raw_value; + } else if (type == REG_EXPAND_SZ) { + wchar_t expanded[kMaxStringLength]; + size = ExpandEnvironmentStrings(raw_value, expanded, kMaxStringLength); + // Success: returns the number of wchar_t's copied + // Fail: buffer too small, returns the size required + // Fail: other, returns 0 + if (size == 0 || size > kMaxStringLength) { + result = ERROR_MORE_DATA; + } else { + *out_value = expanded; + } + } else { + // Not a string. Oops. + result = ERROR_CANTREAD; + } + } + + return result; +} + +LONG RegKey::ReadValue(const wchar_t* name, + void* data, + DWORD* dsize, + DWORD* dtype) const { + LONG result = RegQueryValueEx(key_, name, 0, dtype, + reinterpret_cast(data), dsize); + return result; +} + +LONG RegKey::ReadValues(const wchar_t* name, + std::vector* values) { + values->clear(); + + DWORD type = REG_MULTI_SZ; + DWORD size = 0; + LONG result = ReadValue(name, NULL, &size, &type); + if (FAILED(result) || size == 0) + return result; + + if (type != REG_MULTI_SZ) + return ERROR_CANTREAD; + + std::vector buffer(size / sizeof(wchar_t)); + result = ReadValue(name, &buffer[0], &size, NULL); + if (FAILED(result) || size == 0) + return result; + + // Parse the double-null-terminated list of strings. + // Note: This code is paranoid to not read outside of |buf|, in the case where + // it may not be properly terminated. + const wchar_t* entry = &buffer[0]; + const wchar_t* buffer_end = entry + (size / sizeof(wchar_t)); + while (entry < buffer_end && entry[0] != '\0') { + const wchar_t* entry_end = std::find(entry, buffer_end, L'\0'); + values->push_back(std::wstring(entry, entry_end)); + entry = entry_end + 1; + } + return 0; +} + +LONG RegKey::WriteValue(const wchar_t* name, DWORD in_value) { + return WriteValue( + name, &in_value, static_cast(sizeof(in_value)), REG_DWORD); +} + +LONG RegKey::WriteValue(const wchar_t * name, const wchar_t* in_value) { + return WriteValue(name, in_value, + static_cast(sizeof(*in_value) * (wcslen(in_value) + 1)), REG_SZ); +} + +LONG RegKey::WriteValue(const wchar_t* name, + const void* data, + DWORD dsize, + DWORD dtype) { + DCHECK(data || !dsize); + + LONG result = RegSetValueEx(key_, name, 0, dtype, + reinterpret_cast(const_cast(data)), dsize); + return result; +} + +LONG RegKey::StartWatching() { + DCHECK(key_); + if (!watch_event_) + watch_event_ = CreateEvent(NULL, TRUE, FALSE, NULL); + + DWORD filter = REG_NOTIFY_CHANGE_NAME | + REG_NOTIFY_CHANGE_ATTRIBUTES | + REG_NOTIFY_CHANGE_LAST_SET | + REG_NOTIFY_CHANGE_SECURITY; + + // Watch the registry key for a change of value. + LONG result = RegNotifyChangeKeyValue(key_, TRUE, filter, watch_event_, TRUE); + if (result != ERROR_SUCCESS) { + CloseHandle(watch_event_); + watch_event_ = 0; + } + + return result; +} + +bool RegKey::HasChanged() { + if (watch_event_) { + if (WaitForSingleObject(watch_event_, 0) == WAIT_OBJECT_0) { + StartWatching(); + return true; + } + } + return false; +} + +LONG RegKey::StopWatching() { + LONG result = ERROR_INVALID_HANDLE; + if (watch_event_) { + CloseHandle(watch_event_); + watch_event_ = 0; + result = ERROR_SUCCESS; + } + return result; +} + +// RegistryValueIterator ------------------------------------------------------ + +RegistryValueIterator::RegistryValueIterator(HKEY root_key, + const wchar_t* folder_key) + : name_(MAX_PATH, L'\0'), + value_(MAX_PATH, L'\0') { + LONG result = RegOpenKeyEx(root_key, folder_key, 0, KEY_READ, &key_); + if (result != ERROR_SUCCESS) { + key_ = NULL; + } else { + DWORD count = 0; + result = ::RegQueryInfoKey(key_, NULL, 0, NULL, NULL, NULL, NULL, &count, + NULL, NULL, NULL, NULL); + + if (result != ERROR_SUCCESS) { + ::RegCloseKey(key_); + key_ = NULL; + } else { + index_ = count - 1; + } + } + + Read(); +} + +RegistryValueIterator::~RegistryValueIterator() { + if (key_) + ::RegCloseKey(key_); +} + +DWORD RegistryValueIterator::ValueCount() const { + DWORD count = 0; + LONG result = ::RegQueryInfoKey(key_, NULL, 0, NULL, NULL, NULL, NULL, + &count, NULL, NULL, NULL, NULL); + if (result != ERROR_SUCCESS) + return 0; + + return count; +} + +bool RegistryValueIterator::Valid() const { + return key_ != NULL && index_ >= 0; +} + +void RegistryValueIterator::operator++() { + --index_; + Read(); +} + +bool RegistryValueIterator::Read() { + if (Valid()) { + DWORD capacity = static_cast(name_.capacity()); + DWORD name_size = capacity; + // |value_size_| is in bytes. Reserve the last character for a NUL. + value_size_ = static_cast((value_.size() - 1) * sizeof(wchar_t)); + LONG result = ::RegEnumValue( + key_, index_, WriteInto(&name_, name_size), &name_size, NULL, &type_, + reinterpret_cast(vector_as_array(&value_)), &value_size_); + + if (result == ERROR_MORE_DATA) { + // Registry key names are limited to 255 characters and fit within + // MAX_PATH (which is 260) but registry value names can use up to 16,383 + // characters and the value itself is not limited + // (from http://msdn.microsoft.com/en-us/library/windows/desktop/ + // ms724872(v=vs.85).aspx). + // Resize the buffers and retry if their size caused the failure. + DWORD value_size_in_wchars = to_wchar_size(value_size_); + if (value_size_in_wchars + 1 > value_.size()) + value_.resize(value_size_in_wchars + 1, L'\0'); + value_size_ = static_cast((value_.size() - 1) * sizeof(wchar_t)); + name_size = name_size == capacity ? MAX_REGISTRY_NAME_SIZE : capacity; + result = ::RegEnumValue( + key_, index_, WriteInto(&name_, name_size), &name_size, NULL, &type_, + reinterpret_cast(vector_as_array(&value_)), &value_size_); + } + + if (result == ERROR_SUCCESS) { + DCHECK_LT(to_wchar_size(value_size_), value_.size()); + value_[to_wchar_size(value_size_)] = L'\0'; + return true; + } + } + + name_[0] = L'\0'; + value_[0] = L'\0'; + value_size_ = 0; + return false; +} + +// RegistryKeyIterator -------------------------------------------------------- + +RegistryKeyIterator::RegistryKeyIterator(HKEY root_key, + const wchar_t* folder_key) { + LONG result = RegOpenKeyEx(root_key, folder_key, 0, KEY_READ, &key_); + if (result != ERROR_SUCCESS) { + key_ = NULL; + } else { + DWORD count = 0; + LONG result = ::RegQueryInfoKey(key_, NULL, 0, NULL, &count, NULL, NULL, + NULL, NULL, NULL, NULL, NULL); + + if (result != ERROR_SUCCESS) { + ::RegCloseKey(key_); + key_ = NULL; + } else { + index_ = count - 1; + } + } + + Read(); +} + +RegistryKeyIterator::~RegistryKeyIterator() { + if (key_) + ::RegCloseKey(key_); +} + +DWORD RegistryKeyIterator::SubkeyCount() const { + DWORD count = 0; + LONG result = ::RegQueryInfoKey(key_, NULL, 0, NULL, &count, NULL, NULL, + NULL, NULL, NULL, NULL, NULL); + if (result != ERROR_SUCCESS) + return 0; + + return count; +} + +bool RegistryKeyIterator::Valid() const { + return key_ != NULL && index_ >= 0; +} + +void RegistryKeyIterator::operator++() { + --index_; + Read(); +} + +bool RegistryKeyIterator::Read() { + if (Valid()) { + DWORD ncount = arraysize(name_); + FILETIME written; + LONG r = ::RegEnumKeyEx(key_, index_, name_, &ncount, NULL, NULL, + NULL, &written); + if (ERROR_SUCCESS == r) + return true; + } + + name_[0] = '\0'; + return false; +} + +} // namespace win +} // namespace base diff --git a/base/win/registry.h b/base/win/registry.h new file mode 100644 index 0000000000..f97f4f5a37 --- /dev/null +++ b/base/win/registry.h @@ -0,0 +1,219 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_WIN_REGISTRY_H_ +#define BASE_WIN_REGISTRY_H_ + +#include +#include +#include + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/stl_util.h" + +namespace base { +namespace win { + +// Utility class to read, write and manipulate the Windows Registry. +// Registry vocabulary primer: a "key" is like a folder, in which there +// are "values", which are pairs, with an associated data type. +// +// Note: +// ReadValue family of functions guarantee that the return arguments +// are not touched in case of failure. +class BASE_EXPORT RegKey { + public: + RegKey(); + explicit RegKey(HKEY key); + RegKey(HKEY rootkey, const wchar_t* subkey, REGSAM access); + ~RegKey(); + + LONG Create(HKEY rootkey, const wchar_t* subkey, REGSAM access); + + LONG CreateWithDisposition(HKEY rootkey, const wchar_t* subkey, + DWORD* disposition, REGSAM access); + + // Creates a subkey or open it if it already exists. + LONG CreateKey(const wchar_t* name, REGSAM access); + + // Opens an existing reg key. + LONG Open(HKEY rootkey, const wchar_t* subkey, REGSAM access); + + // Opens an existing reg key, given the relative key name. + LONG OpenKey(const wchar_t* relative_key_name, REGSAM access); + + // Closes this reg key. + void Close(); + + // Replaces the handle of the registry key and takes ownership of the handle. + void Set(HKEY key); + + // Transfers ownership away from this object. + HKEY Take(); + + // Returns false if this key does not have the specified value, of if an error + // occurrs while attempting to access it. + bool HasValue(const wchar_t* value_name) const; + + // Returns the number of values for this key, of 0 if the number cannot be + // determined. + DWORD GetValueCount() const; + + // Determine the nth value's name. + LONG GetValueNameAt(int index, std::wstring* name) const; + + // True while the key is valid. + bool Valid() const { return key_ != NULL; } + + // Kill a key and everything that live below it; please be careful when using + // it. + LONG DeleteKey(const wchar_t* name); + + // Deletes a single value within the key. + LONG DeleteValue(const wchar_t* name); + + // Getters: + + // Returns an int32 value. If |name| is NULL or empty, returns the default + // value, if any. + LONG ReadValueDW(const wchar_t* name, DWORD* out_value) const; + + // Returns an int64 value. If |name| is NULL or empty, returns the default + // value, if any. + LONG ReadInt64(const wchar_t* name, int64* out_value) const; + + // Returns a string value. If |name| is NULL or empty, returns the default + // value, if any. + LONG ReadValue(const wchar_t* name, std::wstring* out_value) const; + + // Reads a REG_MULTI_SZ registry field into a vector of strings. Clears + // |values| initially and adds further strings to the list. Returns + // ERROR_CANTREAD if type is not REG_MULTI_SZ. + LONG ReadValues(const wchar_t* name, std::vector* values); + + // Returns raw data. If |name| is NULL or empty, returns the default + // value, if any. + LONG ReadValue(const wchar_t* name, + void* data, + DWORD* dsize, + DWORD* dtype) const; + + // Setters: + + // Sets an int32 value. + LONG WriteValue(const wchar_t* name, DWORD in_value); + + // Sets a string value. + LONG WriteValue(const wchar_t* name, const wchar_t* in_value); + + // Sets raw data, including type. + LONG WriteValue(const wchar_t* name, + const void* data, + DWORD dsize, + DWORD dtype); + + // Starts watching the key to see if any of its values have changed. + // The key must have been opened with the KEY_NOTIFY access privilege. + LONG StartWatching(); + + // If StartWatching hasn't been called, always returns false. + // Otherwise, returns true if anything under the key has changed. + // This can't be const because the |watch_event_| may be refreshed. + bool HasChanged(); + + // Will automatically be called by destructor if not manually called + // beforehand. Returns true if it was watching, false otherwise. + LONG StopWatching(); + + inline bool IsWatching() const { return watch_event_ != 0; } + HANDLE watch_event() const { return watch_event_; } + HKEY Handle() const { return key_; } + + private: + HKEY key_; // The registry key being iterated. + HANDLE watch_event_; + + DISALLOW_COPY_AND_ASSIGN(RegKey); +}; + +// Iterates the entries found in a particular folder on the registry. +class BASE_EXPORT RegistryValueIterator { + public: + RegistryValueIterator(HKEY root_key, const wchar_t* folder_key); + + ~RegistryValueIterator(); + + DWORD ValueCount() const; + + // True while the iterator is valid. + bool Valid() const; + + // Advances to the next registry entry. + void operator++(); + + const wchar_t* Name() const { return name_.c_str(); } + const wchar_t* Value() const { return vector_as_array(&value_); } + // ValueSize() is in bytes. + DWORD ValueSize() const { return value_size_; } + DWORD Type() const { return type_; } + + int Index() const { return index_; } + + private: + // Read in the current values. + bool Read(); + + // The registry key being iterated. + HKEY key_; + + // Current index of the iteration. + int index_; + + // Current values. + std::wstring name_; + std::vector value_; + DWORD value_size_; + DWORD type_; + + DISALLOW_COPY_AND_ASSIGN(RegistryValueIterator); +}; + +class BASE_EXPORT RegistryKeyIterator { + public: + RegistryKeyIterator(HKEY root_key, const wchar_t* folder_key); + + ~RegistryKeyIterator(); + + DWORD SubkeyCount() const; + + // True while the iterator is valid. + bool Valid() const; + + // Advances to the next entry in the folder. + void operator++(); + + const wchar_t* Name() const { return name_; } + + int Index() const { return index_; } + + private: + // Read in the current values. + bool Read(); + + // The registry key being iterated. + HKEY key_; + + // Current index of the iteration. + int index_; + + wchar_t name_[MAX_PATH]; + + DISALLOW_COPY_AND_ASSIGN(RegistryKeyIterator); +}; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_REGISTRY_H_ diff --git a/base/win/registry_unittest.cc b/base/win/registry_unittest.cc new file mode 100644 index 0000000000..155402a351 --- /dev/null +++ b/base/win/registry_unittest.cc @@ -0,0 +1,164 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/win/registry.h" + +#include +#include + +#include "base/compiler_specific.h" +#include "base/stl_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace win { + +namespace { + +const wchar_t kRootKey[] = L"Base_Registry_Unittest"; + +class RegistryTest : public testing::Test { + public: + RegistryTest() {} + + protected: + virtual void SetUp() OVERRIDE { + // Create a temporary key. + RegKey key(HKEY_CURRENT_USER, L"", KEY_ALL_ACCESS); + key.DeleteKey(kRootKey); + ASSERT_NE(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, kRootKey, KEY_READ)); + ASSERT_EQ(ERROR_SUCCESS, key.Create(HKEY_CURRENT_USER, kRootKey, KEY_READ)); + } + + virtual void TearDown() OVERRIDE { + // Clean up the temporary key. + RegKey key(HKEY_CURRENT_USER, L"", KEY_SET_VALUE); + ASSERT_EQ(ERROR_SUCCESS, key.DeleteKey(kRootKey)); + } + + private: + DISALLOW_COPY_AND_ASSIGN(RegistryTest); +}; + +TEST_F(RegistryTest, ValueTest) { + RegKey key; + + std::wstring foo_key(kRootKey); + foo_key += L"\\Foo"; + ASSERT_EQ(ERROR_SUCCESS, key.Create(HKEY_CURRENT_USER, foo_key.c_str(), + KEY_READ)); + + { + ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, foo_key.c_str(), + KEY_READ | KEY_SET_VALUE)); + ASSERT_TRUE(key.Valid()); + + const wchar_t kStringValueName[] = L"StringValue"; + const wchar_t kDWORDValueName[] = L"DWORDValue"; + const wchar_t kInt64ValueName[] = L"Int64Value"; + const wchar_t kStringData[] = L"string data"; + const DWORD kDWORDData = 0xdeadbabe; + const int64 kInt64Data = 0xdeadbabedeadbabeLL; + + // Test value creation + ASSERT_EQ(ERROR_SUCCESS, key.WriteValue(kStringValueName, kStringData)); + ASSERT_EQ(ERROR_SUCCESS, key.WriteValue(kDWORDValueName, kDWORDData)); + ASSERT_EQ(ERROR_SUCCESS, key.WriteValue(kInt64ValueName, &kInt64Data, + sizeof(kInt64Data), REG_QWORD)); + EXPECT_EQ(3U, key.GetValueCount()); + EXPECT_TRUE(key.HasValue(kStringValueName)); + EXPECT_TRUE(key.HasValue(kDWORDValueName)); + EXPECT_TRUE(key.HasValue(kInt64ValueName)); + + // Test Read + std::wstring string_value; + DWORD dword_value = 0; + int64 int64_value = 0; + ASSERT_EQ(ERROR_SUCCESS, key.ReadValue(kStringValueName, &string_value)); + ASSERT_EQ(ERROR_SUCCESS, key.ReadValueDW(kDWORDValueName, &dword_value)); + ASSERT_EQ(ERROR_SUCCESS, key.ReadInt64(kInt64ValueName, &int64_value)); + EXPECT_STREQ(kStringData, string_value.c_str()); + EXPECT_EQ(kDWORDData, dword_value); + EXPECT_EQ(kInt64Data, int64_value); + + // Make sure out args are not touched if ReadValue fails + const wchar_t* kNonExistent = L"NonExistent"; + ASSERT_NE(ERROR_SUCCESS, key.ReadValue(kNonExistent, &string_value)); + ASSERT_NE(ERROR_SUCCESS, key.ReadValueDW(kNonExistent, &dword_value)); + ASSERT_NE(ERROR_SUCCESS, key.ReadInt64(kNonExistent, &int64_value)); + EXPECT_STREQ(kStringData, string_value.c_str()); + EXPECT_EQ(kDWORDData, dword_value); + EXPECT_EQ(kInt64Data, int64_value); + + // Test delete + ASSERT_EQ(ERROR_SUCCESS, key.DeleteValue(kStringValueName)); + ASSERT_EQ(ERROR_SUCCESS, key.DeleteValue(kDWORDValueName)); + ASSERT_EQ(ERROR_SUCCESS, key.DeleteValue(kInt64ValueName)); + EXPECT_EQ(0U, key.GetValueCount()); + EXPECT_FALSE(key.HasValue(kStringValueName)); + EXPECT_FALSE(key.HasValue(kDWORDValueName)); + EXPECT_FALSE(key.HasValue(kInt64ValueName)); + } +} + +TEST_F(RegistryTest, BigValueIteratorTest) { + RegKey key; + std::wstring foo_key(kRootKey); + foo_key += L"\\Foo"; + ASSERT_EQ(ERROR_SUCCESS, key.Create(HKEY_CURRENT_USER, foo_key.c_str(), + KEY_READ)); + ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, foo_key.c_str(), + KEY_READ | KEY_SET_VALUE)); + ASSERT_TRUE(key.Valid()); + + // Create a test value that is larger than MAX_PATH. + std::wstring data(MAX_PATH * 2, L'a'); + + ASSERT_EQ(ERROR_SUCCESS, key.WriteValue(data.c_str(), data.c_str())); + + RegistryValueIterator iterator(HKEY_CURRENT_USER, foo_key.c_str()); + ASSERT_TRUE(iterator.Valid()); + EXPECT_STREQ(data.c_str(), iterator.Name()); + EXPECT_STREQ(data.c_str(), iterator.Value()); + // ValueSize() is in bytes, including NUL. + EXPECT_EQ((MAX_PATH * 2 + 1) * sizeof(wchar_t), iterator.ValueSize()); + ++iterator; + EXPECT_FALSE(iterator.Valid()); +} + +TEST_F(RegistryTest, TruncatedCharTest) { + RegKey key; + std::wstring foo_key(kRootKey); + foo_key += L"\\Foo"; + ASSERT_EQ(ERROR_SUCCESS, key.Create(HKEY_CURRENT_USER, foo_key.c_str(), + KEY_READ)); + ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, foo_key.c_str(), + KEY_READ | KEY_SET_VALUE)); + ASSERT_TRUE(key.Valid()); + + const wchar_t kName[] = L"name"; + // kData size is not a multiple of sizeof(wchar_t). + const uint8 kData[] = { 1, 2, 3, 4, 5 }; + EXPECT_EQ(5, arraysize(kData)); + ASSERT_EQ(ERROR_SUCCESS, key.WriteValue(kName, kData, + arraysize(kData), REG_BINARY)); + + RegistryValueIterator iterator(HKEY_CURRENT_USER, foo_key.c_str()); + ASSERT_TRUE(iterator.Valid()); + EXPECT_STREQ(kName, iterator.Name()); + // ValueSize() is in bytes. + ASSERT_EQ(arraysize(kData), iterator.ValueSize()); + // Value() is NUL terminated. + int end = (iterator.ValueSize() + sizeof(wchar_t) - 1) / sizeof(wchar_t); + EXPECT_NE(L'\0', iterator.Value()[end-1]); + EXPECT_EQ(L'\0', iterator.Value()[end]); + EXPECT_EQ(0, std::memcmp(kData, iterator.Value(), arraysize(kData))); + ++iterator; + EXPECT_FALSE(iterator.Valid()); +} + +} // namespace + +} // namespace win +} // namespace base diff --git a/base/win/resource_util.cc b/base/win/resource_util.cc new file mode 100644 index 0000000000..0c100785e7 --- /dev/null +++ b/base/win/resource_util.cc @@ -0,0 +1,51 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/logging.h" +#include "base/win/resource_util.h" + +namespace base { +namespace win { + +bool GetResourceFromModule(HMODULE module, + int resource_id, + LPCTSTR resource_type, + void** data, + size_t* length) { + if (!module) + return false; + + if (!IS_INTRESOURCE(resource_id)) { + NOTREACHED(); + return false; + } + + HRSRC hres_info = FindResource(module, MAKEINTRESOURCE(resource_id), + resource_type); + if (NULL == hres_info) + return false; + + DWORD data_size = SizeofResource(module, hres_info); + HGLOBAL hres = LoadResource(module, hres_info); + if (!hres) + return false; + + void* resource = LockResource(hres); + if (!resource) + return false; + + *data = resource; + *length = static_cast(data_size); + return true; +} + +bool GetDataResourceFromModule(HMODULE module, + int resource_id, + void** data, + size_t* length) { + return GetResourceFromModule(module, resource_id, L"BINDATA", data, length); +} + +} // namespace win +} // namespace base diff --git a/base/win/resource_util.h b/base/win/resource_util.h new file mode 100644 index 0000000000..f3444ae79f --- /dev/null +++ b/base/win/resource_util.h @@ -0,0 +1,39 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains utility functions for accessing resources in external +// files (DLLs) or embedded in the executable itself. + +#ifndef BASE_WIN_RESOURCE_UTIL_H__ +#define BASE_WIN_RESOURCE_UTIL_H__ + +#include + +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace base { +namespace win { + +// Function for getting a data resource of the specified |resource_type| from +// a dll. Some resources are optional, especially in unit tests, so this +// returns false but doesn't raise an error if the resource can't be loaded. +bool BASE_EXPORT GetResourceFromModule(HMODULE module, + int resource_id, + LPCTSTR resource_type, + void** data, + size_t* length); + +// Function for getting a data resource (BINDATA) from a dll. Some +// resources are optional, especially in unit tests, so this returns false +// but doesn't raise an error if the resource can't be loaded. +bool BASE_EXPORT GetDataResourceFromModule(HMODULE module, + int resource_id, + void** data, + size_t* length); + +} // namespace win +} // namespace base + +#endif // BASE_WIN_RESOURCE_UTIL_H__ diff --git a/base/win/sampling_profiler.cc b/base/win/sampling_profiler.cc new file mode 100644 index 0000000000..150452c447 --- /dev/null +++ b/base/win/sampling_profiler.cc @@ -0,0 +1,238 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/win/sampling_profiler.h" + +#include // for NTSTATUS. + +#include "base/lazy_instance.h" + +// Copied from wdm.h in the WDK as we don't want to take +// a dependency on the WDK. +typedef enum _KPROFILE_SOURCE { + ProfileTime, + ProfileAlignmentFixup, + ProfileTotalIssues, + ProfilePipelineDry, + ProfileLoadInstructions, + ProfilePipelineFrozen, + ProfileBranchInstructions, + ProfileTotalNonissues, + ProfileDcacheMisses, + ProfileIcacheMisses, + ProfileCacheMisses, + ProfileBranchMispredictions, + ProfileStoreInstructions, + ProfileFpInstructions, + ProfileIntegerInstructions, + Profile2Issue, + Profile3Issue, + Profile4Issue, + ProfileSpecialInstructions, + ProfileTotalCycles, + ProfileIcacheIssues, + ProfileDcacheAccesses, + ProfileMemoryBarrierCycles, + ProfileLoadLinkedIssues, + ProfileMaximum +} KPROFILE_SOURCE; + + +namespace { + +// Signatures for the native functions we need to access the sampling profiler. +typedef NTSTATUS (NTAPI *ZwSetIntervalProfileFunc)(ULONG, KPROFILE_SOURCE); +typedef NTSTATUS (NTAPI *ZwQueryIntervalProfileFunc)(KPROFILE_SOURCE, PULONG); + +typedef NTSTATUS (NTAPI *ZwCreateProfileFunc)(PHANDLE profile, + HANDLE process, + PVOID code_start, + ULONG code_size, + ULONG eip_bucket_shift, + PULONG buckets, + ULONG buckets_byte_size, + KPROFILE_SOURCE source, + DWORD_PTR processor_mask); + +typedef NTSTATUS (NTAPI *ZwStartProfileFunc)(HANDLE); +typedef NTSTATUS (NTAPI *ZwStopProfileFunc)(HANDLE); + +// This class is used to lazy-initialize pointers to the native +// functions we need to access. +class ProfilerFuncs { + public: + ProfilerFuncs(); + + ZwSetIntervalProfileFunc ZwSetIntervalProfile; + ZwQueryIntervalProfileFunc ZwQueryIntervalProfile; + ZwCreateProfileFunc ZwCreateProfile; + ZwStartProfileFunc ZwStartProfile; + ZwStopProfileFunc ZwStopProfile; + + // True iff all of the function pointers above were successfully initialized. + bool initialized_; +}; + +ProfilerFuncs::ProfilerFuncs() + : ZwSetIntervalProfile(NULL), + ZwQueryIntervalProfile(NULL), + ZwCreateProfile(NULL), + ZwStartProfile(NULL), + ZwStopProfile(NULL), + initialized_(false) { + HMODULE ntdll = ::GetModuleHandle(L"ntdll.dll"); + if (ntdll != NULL) { + ZwSetIntervalProfile = reinterpret_cast( + ::GetProcAddress(ntdll, "ZwSetIntervalProfile")); + ZwQueryIntervalProfile = reinterpret_cast( + ::GetProcAddress(ntdll, "ZwQueryIntervalProfile")); + ZwCreateProfile = reinterpret_cast( + ::GetProcAddress(ntdll, "ZwCreateProfile")); + ZwStartProfile = reinterpret_cast( + ::GetProcAddress(ntdll, "ZwStartProfile")); + ZwStopProfile = reinterpret_cast( + ::GetProcAddress(ntdll, "ZwStopProfile")); + + if (ZwSetIntervalProfile && + ZwQueryIntervalProfile && + ZwCreateProfile && + ZwStartProfile && + ZwStopProfile) { + initialized_ = true; + } + } +} + +base::LazyInstance::Leaky funcs = LAZY_INSTANCE_INITIALIZER; + +} // namespace + + +namespace base { +namespace win { + +SamplingProfiler::SamplingProfiler() : is_started_(false) { +} + +SamplingProfiler::~SamplingProfiler() { + if (is_started_) { + CHECK(Stop()) << + "Unable to stop sampling profiler, this will cause memory corruption."; + } +} + +bool SamplingProfiler::Initialize(HANDLE process, + void* start, + size_t size, + size_t log2_bucket_size) { + // You only get to initialize each instance once. + DCHECK(!profile_handle_.IsValid()); + DCHECK(!is_started_); + DCHECK(start != NULL); + DCHECK_NE(0U, size); + DCHECK_LE(2, log2_bucket_size); + DCHECK_GE(32, log2_bucket_size); + + // Bail if the native functions weren't found. + if (!funcs.Get().initialized_) + return false; + + size_t bucket_size = 1 << log2_bucket_size; + size_t num_buckets = (size + bucket_size - 1) / bucket_size; + DCHECK(num_buckets != 0); + buckets_.resize(num_buckets); + + // Get our affinity mask for the call below. + DWORD_PTR process_affinity = 0; + DWORD_PTR system_affinity = 0; + if (!::GetProcessAffinityMask(process, &process_affinity, &system_affinity)) { + LOG(ERROR) << "Failed to get process affinity mask."; + return false; + } + + HANDLE profile = NULL; + NTSTATUS status = + funcs.Get().ZwCreateProfile(&profile, + process, + start, + static_cast(size), + static_cast(log2_bucket_size), + &buckets_[0], + static_cast( + sizeof(buckets_[0]) * num_buckets), + ProfileTime, + process_affinity); + + if (!NT_SUCCESS(status)) { + // Might as well deallocate the buckets. + buckets_.resize(0); + LOG(ERROR) << "Failed to create profile, error 0x" << std::hex << status; + return false; + } + + DCHECK(profile != NULL); + profile_handle_.Set(profile); + + return true; +} + +bool SamplingProfiler::Start() { + DCHECK(profile_handle_.IsValid()); + DCHECK(!is_started_); + DCHECK(funcs.Get().initialized_); + + NTSTATUS status = funcs.Get().ZwStartProfile(profile_handle_.Get()); + if (!NT_SUCCESS(status)) + return false; + + is_started_ = true; + + return true; +} + +bool SamplingProfiler::Stop() { + DCHECK(profile_handle_.IsValid()); + DCHECK(is_started_); + DCHECK(funcs.Get().initialized_); + + NTSTATUS status = funcs.Get().ZwStopProfile(profile_handle_.Get()); + if (!NT_SUCCESS(status)) + return false; + is_started_ = false; + + return true; +} + +bool SamplingProfiler::SetSamplingInterval(base::TimeDelta sampling_interval) { + if (!funcs.Get().initialized_) + return false; + + // According to Nebbet, the sampling interval is in units of 100ns. + ULONG interval = sampling_interval.InMicroseconds() * 10; + NTSTATUS status = funcs.Get().ZwSetIntervalProfile(interval, ProfileTime); + if (!NT_SUCCESS(status)) + return false; + + return true; +} + +bool SamplingProfiler::GetSamplingInterval(base::TimeDelta* sampling_interval) { + DCHECK(sampling_interval != NULL); + + if (!funcs.Get().initialized_) + return false; + + ULONG interval = 0; + NTSTATUS status = funcs.Get().ZwQueryIntervalProfile(ProfileTime, &interval); + if (!NT_SUCCESS(status)) + return false; + + // According to Nebbet, the sampling interval is in units of 100ns. + *sampling_interval = base::TimeDelta::FromMicroseconds(interval / 10); + + return true; +} + +} // namespace win +} // namespace base diff --git a/base/win/sampling_profiler.h b/base/win/sampling_profiler.h new file mode 100644 index 0000000000..1950ef5256 --- /dev/null +++ b/base/win/sampling_profiler.h @@ -0,0 +1,73 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_WIN_SAMPLING_PROFILER_H_ +#define BASE_WIN_SAMPLING_PROFILER_H_ + +#include + +#include "base/basictypes.h" +#include "base/time/time.h" +#include "base/win/scoped_handle.h" + +namespace base { +namespace win { + +// This class exposes the functionality of Window's built-in sampling profiler. +// Each profiler instance covers a range of memory, and while the profiler is +// running, its buckets will count the number of times the instruction counter +// lands in the associated range of memory on a sample. +// The sampling interval is settable, but the setting is system-wide. +class BASE_EXPORT SamplingProfiler { + public: + // Create an uninitialized sampling profiler. + SamplingProfiler(); + ~SamplingProfiler(); + + // Initializes the profiler to cover the memory range |start| through + // |start| + |size|, in the process |process_handle| with bucket size + // |2^log2_bucket_size|, |log2_bucket_size| must be in the range 2-31, + // for bucket sizes of 4 bytes to 2 gigabytes. + // The process handle must grant at least PROCESS_QUERY_INFORMATION. + // The memory range should be exectuable code, like e.g. the text segment + // of an exectuable (whether DLL or EXE). + // Returns true on success. + bool Initialize(HANDLE process_handle, + void* start, + size_t size, + size_t log2_bucket_size); + + // Start this profiler, which must be initialized and not started. + bool Start(); + // Stop this profiler, which must be started. + bool Stop(); + + // Get and set the sampling interval. + // Note that this is a system-wide setting. + static bool SetSamplingInterval(base::TimeDelta sampling_interval); + static bool GetSamplingInterval(base::TimeDelta* sampling_interval); + + // Accessors. + bool is_started() const { return is_started_; } + + // It is safe to read the counts in the sampling buckets at any time. + // Note however that there's no guarantee that you'll read consistent counts + // until the profiler has been stopped, as the counts may be updating on other + // CPU cores. + const std::vector& buckets() const { return buckets_; } + + private: + // Handle to the corresponding kernel object. + ScopedHandle profile_handle_; + // True iff this profiler is started. + bool is_started_; + std::vector buckets_; + + DISALLOW_COPY_AND_ASSIGN(SamplingProfiler); +}; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_SAMPLING_PROFILER_H_ diff --git a/base/win/sampling_profiler_unittest.cc b/base/win/sampling_profiler_unittest.cc new file mode 100644 index 0000000000..d022026342 --- /dev/null +++ b/base/win/sampling_profiler_unittest.cc @@ -0,0 +1,120 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/logging.h" +#include "base/test/test_timeouts.h" +#include "base/win/sampling_profiler.h" +#include "base/win/pe_image.h" +#include "base/win/scoped_handle.h" +#include "base/win/windows_version.h" +#include "testing/gtest/include/gtest/gtest.h" + +// The address of our image base. +extern "C" IMAGE_DOS_HEADER __ImageBase; + +namespace base { +namespace win { + +namespace { + +class SamplingProfilerTest : public testing::Test { + public: + SamplingProfilerTest() : code_start(NULL), code_size(0) { + } + + virtual void SetUp() { + process.Set(::OpenProcess(PROCESS_QUERY_INFORMATION, + FALSE, + ::GetCurrentProcessId())); + ASSERT_TRUE(process.IsValid()); + + PEImage image(&__ImageBase); + + // Get the address of the .text section, which is the first section output + // by the VS tools. + ASSERT_TRUE(image.GetNumSections() > 0); + const IMAGE_SECTION_HEADER* text_section = image.GetSectionHeader(0); + ASSERT_EQ(0, strncmp(".text", + reinterpret_cast(text_section->Name), + arraysize(text_section->Name))); + ASSERT_NE(0U, text_section->Characteristics & IMAGE_SCN_MEM_EXECUTE); + + code_start = reinterpret_cast(&__ImageBase) + + text_section->VirtualAddress; + code_size = text_section->Misc.VirtualSize; + } + + protected: + ScopedHandle process; + void* code_start; + size_t code_size; +}; + +} // namespace + +TEST_F(SamplingProfilerTest, Initialize) { + SamplingProfiler profiler; + + ASSERT_TRUE(profiler.Initialize(process.Get(), code_start, code_size, 8)); +} + +TEST_F(SamplingProfilerTest, Sample) { + if (base::win::GetVersion() == base::win::VERSION_WIN8) { + LOG(INFO) << "Not running test on Windows 8"; + return; + } + SamplingProfiler profiler; + + // Initialize with a huge bucket size, aiming for a single bucket. + ASSERT_TRUE( + profiler.Initialize(process.Get(), code_start, code_size, 31)); + + ASSERT_EQ(1, profiler.buckets().size()); + ASSERT_EQ(0, profiler.buckets()[0]); + + // We use a roomy timeout to make sure this test is not flaky. + // On the buildbots, there may not be a whole lot of CPU time + // allotted to our process in this wall-clock time duration, + // and samples will only accrue while this thread is busy on + // a CPU core. + base::TimeDelta spin_time = TestTimeouts::action_timeout(); + + base::TimeDelta save_sampling_interval; + ASSERT_TRUE(SamplingProfiler::GetSamplingInterval(&save_sampling_interval)); + + // Sample every 0.5 millisecs. + ASSERT_TRUE(SamplingProfiler::SetSamplingInterval( + base::TimeDelta::FromMicroseconds(500))); + + ASSERT_TRUE(SamplingProfiler::SetSamplingInterval( + base::TimeDelta::FromMicroseconds(500))); + + // Start the profiler. + ASSERT_TRUE(profiler.Start()); + + // Get a volatile pointer to our bucket to make sure that the compiler + // doesn't optimize out the test in the loop that follows. + volatile const ULONG* bucket_ptr = &profiler.buckets()[0]; + + // Spin for spin_time wall-clock seconds, or until we get some samples. + // Note that sleeping isn't going to do us any good, the samples only + // accrue while we're executing code. + base::Time start = base::Time::Now(); + base::TimeDelta elapsed; + do { + elapsed = base::Time::Now() - start; + } while((elapsed < spin_time) && *bucket_ptr == 0); + + // Stop the profiler. + ASSERT_TRUE(profiler.Stop()); + + // Restore the sampling interval we found. + ASSERT_TRUE(SamplingProfiler::SetSamplingInterval(save_sampling_interval)); + + // Check that we got some samples. + ASSERT_NE(0U, profiler.buckets()[0]); +} + +} // namespace win +} // namespace base diff --git a/base/win/scoped_bstr.cc b/base/win/scoped_bstr.cc new file mode 100644 index 0000000000..63ade0cb42 --- /dev/null +++ b/base/win/scoped_bstr.cc @@ -0,0 +1,71 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/win/scoped_bstr.h" + +#include "base/logging.h" + +namespace base { +namespace win { + +ScopedBstr::ScopedBstr(const char16* non_bstr) + : bstr_(SysAllocString(non_bstr)) { +} + +ScopedBstr::~ScopedBstr() { + COMPILE_ASSERT(sizeof(ScopedBstr) == sizeof(BSTR), ScopedBstrSize); + SysFreeString(bstr_); +} + +void ScopedBstr::Reset(BSTR bstr) { + if (bstr != bstr_) { + // if |bstr_| is NULL, SysFreeString does nothing. + SysFreeString(bstr_); + bstr_ = bstr; + } +} + +BSTR ScopedBstr::Release() { + BSTR bstr = bstr_; + bstr_ = NULL; + return bstr; +} + +void ScopedBstr::Swap(ScopedBstr& bstr2) { + BSTR tmp = bstr_; + bstr_ = bstr2.bstr_; + bstr2.bstr_ = tmp; +} + +BSTR* ScopedBstr::Receive() { + DCHECK(!bstr_) << "BSTR leak."; + return &bstr_; +} + +BSTR ScopedBstr::Allocate(const char16* str) { + Reset(SysAllocString(str)); + return bstr_; +} + +BSTR ScopedBstr::AllocateBytes(size_t bytes) { + Reset(SysAllocStringByteLen(NULL, static_cast(bytes))); + return bstr_; +} + +void ScopedBstr::SetByteLen(size_t bytes) { + DCHECK(bstr_ != NULL) << "attempting to modify a NULL bstr"; + uint32* data = reinterpret_cast(bstr_); + data[-1] = static_cast(bytes); +} + +size_t ScopedBstr::Length() const { + return SysStringLen(bstr_); +} + +size_t ScopedBstr::ByteLength() const { + return SysStringByteLen(bstr_); +} + +} // namespace win +} // namespace base diff --git a/base/win/scoped_bstr.h b/base/win/scoped_bstr.h new file mode 100644 index 0000000000..d703f62dac --- /dev/null +++ b/base/win/scoped_bstr.h @@ -0,0 +1,97 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_WIN_SCOPED_BSTR_H_ +#define BASE_WIN_SCOPED_BSTR_H_ + +#include +#include + +#include "base/base_export.h" +#include "base/logging.h" +#include "base/strings/string16.h" + +namespace base { +namespace win { + +// Manages a BSTR string pointer. +// The class interface is based on scoped_ptr. +class BASE_EXPORT ScopedBstr { + public: + ScopedBstr() : bstr_(NULL) { + } + + // Constructor to create a new BSTR. + // + // NOTE: Do not pass a BSTR to this constructor expecting ownership to + // be transferred - even though it compiles! ;-) + explicit ScopedBstr(const char16* non_bstr); + ~ScopedBstr(); + + // Give ScopedBstr ownership over an already allocated BSTR or NULL. + // If you need to allocate a new BSTR instance, use |allocate| instead. + void Reset(BSTR bstr = NULL); + + // Releases ownership of the BSTR to the caller. + BSTR Release(); + + // Creates a new BSTR from a 16-bit C-style string. + // + // If you already have a BSTR and want to transfer ownership to the + // ScopedBstr instance, call |reset| instead. + // + // Returns a pointer to the new BSTR, or NULL if allocation failed. + BSTR Allocate(const char16* str); + + // Allocates a new BSTR with the specified number of bytes. + // Returns a pointer to the new BSTR, or NULL if allocation failed. + BSTR AllocateBytes(size_t bytes); + + // Sets the allocated length field of the already-allocated BSTR to be + // |bytes|. This is useful when the BSTR was preallocated with e.g. + // SysAllocStringLen or SysAllocStringByteLen (call |AllocateBytes|) and then + // not all the bytes are being used. + // + // Note that if you want to set the length to a specific number of + // characters, you need to multiply by sizeof(wchar_t). Oddly, there's no + // public API to set the length, so we do this ourselves by hand. + // + // NOTE: The actual allocated size of the BSTR MUST be >= bytes. That + // responsibility is with the caller. + void SetByteLen(size_t bytes); + + // Swap values of two ScopedBstr's. + void Swap(ScopedBstr& bstr2); + + // Retrieves the pointer address. + // Used to receive BSTRs as out arguments (and take ownership). + // The function DCHECKs on the current value being NULL. + // Usage: GetBstr(bstr.Receive()); + BSTR* Receive(); + + // Returns number of chars in the BSTR. + size_t Length() const; + + // Returns the number of bytes allocated for the BSTR. + size_t ByteLength() const; + + operator BSTR() const { + return bstr_; + } + + protected: + BSTR bstr_; + + private: + // Forbid comparison of ScopedBstr types. You should never have the same + // BSTR owned by two different scoped_ptrs. + bool operator==(const ScopedBstr& bstr2) const; + bool operator!=(const ScopedBstr& bstr2) const; + DISALLOW_COPY_AND_ASSIGN(ScopedBstr); +}; + +} // namespace win +} // namespace base + +#endif // BASE_SCOPED_BSTR_H_ diff --git a/base/win/scoped_bstr_unittest.cc b/base/win/scoped_bstr_unittest.cc new file mode 100644 index 0000000000..5f6f7dffe4 --- /dev/null +++ b/base/win/scoped_bstr_unittest.cc @@ -0,0 +1,77 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/win/scoped_bstr.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace win { + +namespace { + +static const wchar_t kTestString1[] = L"123"; +static const wchar_t kTestString2[] = L"456789"; +size_t test1_len = arraysize(kTestString1) - 1; +size_t test2_len = arraysize(kTestString2) - 1; + +void DumbBstrTests() { + ScopedBstr b; + EXPECT_TRUE(b == NULL); + EXPECT_EQ(0, b.Length()); + EXPECT_EQ(0, b.ByteLength()); + b.Reset(NULL); + EXPECT_TRUE(b == NULL); + EXPECT_TRUE(b.Release() == NULL); + ScopedBstr b2; + b.Swap(b2); + EXPECT_TRUE(b2 == NULL); +} + +void GiveMeABstr(BSTR* ret) { + *ret = SysAllocString(kTestString1); +} + +void BasicBstrTests() { + ScopedBstr b1(kTestString1); + EXPECT_EQ(test1_len, b1.Length()); + EXPECT_EQ(test1_len * sizeof(kTestString1[0]), b1.ByteLength()); + + ScopedBstr b2; + b1.Swap(b2); + EXPECT_EQ(test1_len, b2.Length()); + EXPECT_EQ(0, b1.Length()); + EXPECT_EQ(0, lstrcmp(b2, kTestString1)); + BSTR tmp = b2.Release(); + EXPECT_TRUE(tmp != NULL); + EXPECT_EQ(0, lstrcmp(tmp, kTestString1)); + EXPECT_TRUE(b2 == NULL); + SysFreeString(tmp); + + GiveMeABstr(b2.Receive()); + EXPECT_TRUE(b2 != NULL); + b2.Reset(); + EXPECT_TRUE(b2.AllocateBytes(100) != NULL); + EXPECT_EQ(100, b2.ByteLength()); + EXPECT_EQ(100 / sizeof(kTestString1[0]), b2.Length()); + lstrcpy(static_cast(b2), kTestString1); + EXPECT_EQ(test1_len, lstrlen(b2)); + EXPECT_EQ(100 / sizeof(kTestString1[0]), b2.Length()); + b2.SetByteLen(lstrlen(b2) * sizeof(kTestString2[0])); + EXPECT_EQ(b2.Length(), lstrlen(b2)); + + EXPECT_TRUE(b1.Allocate(kTestString2) != NULL); + EXPECT_EQ(test2_len, b1.Length()); + b1.SetByteLen((test2_len - 1) * sizeof(kTestString2[0])); + EXPECT_EQ(test2_len - 1, b1.Length()); +} + +} // namespace + +TEST(ScopedBstrTest, ScopedBstr) { + DumbBstrTests(); + BasicBstrTests(); +} + +} // namespace win +} // namespace base diff --git a/base/win/scoped_co_mem.h b/base/win/scoped_co_mem.h new file mode 100644 index 0000000000..572999a26c --- /dev/null +++ b/base/win/scoped_co_mem.h @@ -0,0 +1,64 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_WIN_SCOPED_CO_MEM_H_ +#define BASE_WIN_SCOPED_CO_MEM_H_ + +#include + +#include "base/basictypes.h" +#include "base/logging.h" + +namespace base { +namespace win { + +// Simple scoped memory releaser class for COM allocated memory. +// Example: +// base::win::ScopedCoMem file_item; +// SHGetSomeInfo(&file_item, ...); +// ... +// return; <-- memory released +template +class ScopedCoMem { + public: + ScopedCoMem() : mem_ptr_(NULL) {} + ~ScopedCoMem() { + Reset(NULL); + } + + T** operator&() { // NOLINT + DCHECK(mem_ptr_ == NULL); // To catch memory leaks. + return &mem_ptr_; + } + + operator T*() { + return mem_ptr_; + } + + T* operator->() { + DCHECK(mem_ptr_ != NULL); + return mem_ptr_; + } + + const T* operator->() const { + DCHECK(mem_ptr_ != NULL); + return mem_ptr_; + } + + void Reset(T* ptr) { + if (mem_ptr_) + CoTaskMemFree(mem_ptr_); + mem_ptr_ = ptr; + } + + private: + T* mem_ptr_; + + DISALLOW_COPY_AND_ASSIGN(ScopedCoMem); +}; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_SCOPED_CO_MEM_H_ diff --git a/base/win/scoped_com_initializer.h b/base/win/scoped_com_initializer.h new file mode 100644 index 0000000000..392c351cc7 --- /dev/null +++ b/base/win/scoped_com_initializer.h @@ -0,0 +1,74 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_WIN_SCOPED_COM_INITIALIZER_H_ +#define BASE_WIN_SCOPED_COM_INITIALIZER_H_ + +#include + +#include "base/basictypes.h" +#include "base/logging.h" +#include "build/build_config.h" + +namespace base { +namespace win { + +// Initializes COM in the constructor (STA or MTA), and uninitializes COM in the +// destructor. +class ScopedCOMInitializer { + public: + // Enum value provided to initialize the thread as an MTA instead of STA. + enum SelectMTA { kMTA }; + + // Constructor for STA initialization. + ScopedCOMInitializer() { + Initialize(COINIT_APARTMENTTHREADED); + } + + // Constructor for MTA initialization. + explicit ScopedCOMInitializer(SelectMTA mta) { + Initialize(COINIT_MULTITHREADED); + } + + ~ScopedCOMInitializer() { +#ifndef NDEBUG + // Using the windows API directly to avoid dependency on platform_thread. + DCHECK_EQ(GetCurrentThreadId(), thread_id_); +#endif + if (succeeded()) + CoUninitialize(); + } + + bool succeeded() const { return SUCCEEDED(hr_); } + + private: + void Initialize(COINIT init) { +#ifndef NDEBUG + thread_id_ = GetCurrentThreadId(); +#endif + hr_ = CoInitializeEx(NULL, init); +#ifndef NDEBUG + if (hr_ == S_FALSE) + LOG(ERROR) << "Multiple CoInitialize() calls for thread " << thread_id_; + else + DCHECK_NE(RPC_E_CHANGED_MODE, hr_) << "Invalid COM thread model change"; +#endif + } + + HRESULT hr_; +#ifndef NDEBUG + // In debug builds we use this variable to catch a potential bug where a + // ScopedCOMInitializer instance is deleted on a different thread than it + // was initially created on. If that ever happens it can have bad + // consequences and the cause can be tricky to track down. + DWORD thread_id_; +#endif + + DISALLOW_COPY_AND_ASSIGN(ScopedCOMInitializer); +}; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_SCOPED_COM_INITIALIZER_H_ diff --git a/base/win/scoped_comptr.h b/base/win/scoped_comptr.h new file mode 100644 index 0000000000..98cea0ff4c --- /dev/null +++ b/base/win/scoped_comptr.h @@ -0,0 +1,168 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_WIN_SCOPED_COMPTR_H_ +#define BASE_WIN_SCOPED_COMPTR_H_ + +#include + +#include "base/logging.h" +#include "base/memory/ref_counted.h" + +namespace base { +namespace win { + +// A fairly minimalistic smart class for COM interface pointers. +// Uses scoped_refptr for the basic smart pointer functionality +// and adds a few IUnknown specific services. +template +class ScopedComPtr : public scoped_refptr { + public: + // Utility template to prevent users of ScopedComPtr from calling AddRef + // and/or Release() without going through the ScopedComPtr class. + class BlockIUnknownMethods : public Interface { + private: + STDMETHOD(QueryInterface)(REFIID iid, void** object) = 0; + STDMETHOD_(ULONG, AddRef)() = 0; + STDMETHOD_(ULONG, Release)() = 0; + }; + + typedef scoped_refptr ParentClass; + + ScopedComPtr() { + } + + explicit ScopedComPtr(Interface* p) : ParentClass(p) { + } + + ScopedComPtr(const ScopedComPtr& p) + : ParentClass(p) { + } + + ~ScopedComPtr() { + // We don't want the smart pointer class to be bigger than the pointer + // it wraps. + COMPILE_ASSERT(sizeof(ScopedComPtr) == + sizeof(Interface*), ScopedComPtrSize); + } + + // Explicit Release() of the held object. Useful for reuse of the + // ScopedComPtr instance. + // Note that this function equates to IUnknown::Release and should not + // be confused with e.g. scoped_ptr::release(). + void Release() { + if (ptr_ != NULL) { + ptr_->Release(); + ptr_ = NULL; + } + } + + // Sets the internal pointer to NULL and returns the held object without + // releasing the reference. + Interface* Detach() { + Interface* p = ptr_; + ptr_ = NULL; + return p; + } + + // Accepts an interface pointer that has already been addref-ed. + void Attach(Interface* p) { + DCHECK(!ptr_); + ptr_ = p; + } + + // Retrieves the pointer address. + // Used to receive object pointers as out arguments (and take ownership). + // The function DCHECKs on the current value being NULL. + // Usage: Foo(p.Receive()); + Interface** Receive() { + DCHECK(!ptr_) << "Object leak. Pointer must be NULL"; + return &ptr_; + } + + // A convenience for whenever a void pointer is needed as an out argument. + void** ReceiveVoid() { + return reinterpret_cast(Receive()); + } + + template + HRESULT QueryInterface(Query** p) { + DCHECK(p != NULL); + DCHECK(ptr_ != NULL); + // IUnknown already has a template version of QueryInterface + // so the iid parameter is implicit here. The only thing this + // function adds are the DCHECKs. + return ptr_->QueryInterface(p); + } + + // QI for times when the IID is not associated with the type. + HRESULT QueryInterface(const IID& iid, void** obj) { + DCHECK(obj != NULL); + DCHECK(ptr_ != NULL); + return ptr_->QueryInterface(iid, obj); + } + + // Queries |other| for the interface this object wraps and returns the + // error code from the other->QueryInterface operation. + HRESULT QueryFrom(IUnknown* object) { + DCHECK(object != NULL); + return object->QueryInterface(Receive()); + } + + // Convenience wrapper around CoCreateInstance + HRESULT CreateInstance(const CLSID& clsid, IUnknown* outer = NULL, + DWORD context = CLSCTX_ALL) { + DCHECK(!ptr_); + HRESULT hr = ::CoCreateInstance(clsid, outer, context, *interface_id, + reinterpret_cast(&ptr_)); + return hr; + } + + // Checks if the identity of |other| and this object is the same. + bool IsSameObject(IUnknown* other) { + if (!other && !ptr_) + return true; + + if (!other || !ptr_) + return false; + + ScopedComPtr my_identity; + QueryInterface(my_identity.Receive()); + + ScopedComPtr other_identity; + other->QueryInterface(other_identity.Receive()); + + return static_cast(my_identity) == + static_cast(other_identity); + } + + // Provides direct access to the interface. + // Here we use a well known trick to make sure we block access to + // IUnknown methods so that something bad like this doesn't happen: + // ScopedComPtr p(Foo()); + // p->Release(); + // ... later the destructor runs, which will Release() again. + // and to get the benefit of the DCHECKs we add to QueryInterface. + // There's still a way to call these methods if you absolutely must + // by statically casting the ScopedComPtr instance to the wrapped interface + // and then making the call... but generally that shouldn't be necessary. + BlockIUnknownMethods* operator->() const { + DCHECK(ptr_ != NULL); + return reinterpret_cast(ptr_); + } + + // Pull in operator=() from the parent class. + using scoped_refptr::operator=; + + // static methods + + static const IID& iid() { + return *interface_id; + } +}; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_SCOPED_COMPTR_H_ diff --git a/base/win/scoped_comptr_unittest.cc b/base/win/scoped_comptr_unittest.cc new file mode 100644 index 0000000000..d8d12be87d --- /dev/null +++ b/base/win/scoped_comptr_unittest.cc @@ -0,0 +1,111 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/win/scoped_comptr.h" + +#include + +#include "base/memory/scoped_ptr.h" +#include "base/win/scoped_com_initializer.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace win { + +namespace { + +struct Dummy { + Dummy() : adds(0), releases(0) { } + void AddRef() { ++adds; } + void Release() { ++releases; } + + int adds; + int releases; +}; + +extern const IID dummy_iid; +const IID dummy_iid = { 0x12345678u, 0x1234u, 0x5678u, 01, 23, 45, 67, 89, + 01, 23, 45 }; + +} // namespace + +TEST(ScopedComPtrTest, ScopedComPtr) { + EXPECT_TRUE(memcmp(&ScopedComPtr::iid(), &IID_IUnknown, + sizeof(IID)) == 0); + + base::win::ScopedCOMInitializer com_initializer; + EXPECT_TRUE(com_initializer.succeeded()); + + ScopedComPtr unk; + EXPECT_TRUE(SUCCEEDED(unk.CreateInstance(CLSID_ShellLink))); + ScopedComPtr unk2; + unk2.Attach(unk.Detach()); + EXPECT_TRUE(unk == NULL); + EXPECT_TRUE(unk2 != NULL); + + ScopedComPtr mem_alloc; + EXPECT_TRUE(SUCCEEDED(CoGetMalloc(1, mem_alloc.Receive()))); + + ScopedComPtr qi_test; + EXPECT_HRESULT_SUCCEEDED(mem_alloc.QueryInterface(IID_IUnknown, + reinterpret_cast(qi_test.Receive()))); + EXPECT_TRUE(qi_test.get() != NULL); + qi_test.Release(); + + // test ScopedComPtr& constructor + ScopedComPtr copy1(mem_alloc); + EXPECT_TRUE(copy1.IsSameObject(mem_alloc)); + EXPECT_FALSE(copy1.IsSameObject(unk2)); // unk2 is valid but different + EXPECT_FALSE(copy1.IsSameObject(unk)); // unk is NULL + + IMalloc* naked_copy = copy1.Detach(); + copy1 = naked_copy; // Test the =(T*) operator. + naked_copy->Release(); + + copy1.Release(); + EXPECT_FALSE(copy1.IsSameObject(unk2)); // unk2 is valid, copy1 is not + + // test Interface* constructor + ScopedComPtr copy2(static_cast(mem_alloc)); + EXPECT_TRUE(copy2.IsSameObject(mem_alloc)); + + EXPECT_TRUE(SUCCEEDED(unk.QueryFrom(mem_alloc))); + EXPECT_TRUE(unk != NULL); + unk.Release(); + EXPECT_TRUE(unk == NULL); + EXPECT_TRUE(unk.IsSameObject(copy1)); // both are NULL +} + +TEST(ScopedComPtrTest, ScopedComPtrVector) { + // Verify we don't get error C2558. + typedef ScopedComPtr Ptr; + std::vector bleh; + + scoped_ptr p(new Dummy); + { + Ptr p2(p.get()); + EXPECT_EQ(p->adds, 1); + EXPECT_EQ(p->releases, 0); + Ptr p3 = p2; + EXPECT_EQ(p->adds, 2); + EXPECT_EQ(p->releases, 0); + p3 = p2; + EXPECT_EQ(p->adds, 3); + EXPECT_EQ(p->releases, 1); + // To avoid hitting a reallocation. + bleh.reserve(1); + bleh.push_back(p2); + EXPECT_EQ(p->adds, 4); + EXPECT_EQ(p->releases, 1); + EXPECT_EQ(bleh[0], p.get()); + bleh.pop_back(); + EXPECT_EQ(p->adds, 4); + EXPECT_EQ(p->releases, 2); + } + EXPECT_EQ(p->adds, 4); + EXPECT_EQ(p->releases, 4); +} + +} // namespace win +} // namespace base diff --git a/base/win/scoped_gdi_object.h b/base/win/scoped_gdi_object.h new file mode 100644 index 0000000000..d44310a159 --- /dev/null +++ b/base/win/scoped_gdi_object.h @@ -0,0 +1,77 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_WIN_SCOPED_GDI_OBJECT_H_ +#define BASE_WIN_SCOPED_GDI_OBJECT_H_ + +#include + +#include "base/basictypes.h" +#include "base/logging.h" + +namespace base { +namespace win { + +// Like ScopedHandle but for GDI objects. +template +class ScopedGDIObject { + public: + ScopedGDIObject() : object_(NULL) {} + explicit ScopedGDIObject(T object) : object_(object) {} + + ~ScopedGDIObject() { + Close(); + } + + T Get() { + return object_; + } + + void Set(T object) { + if (object_ && object != object_) + Close(); + object_ = object; + } + + ScopedGDIObject& operator=(T object) { + Set(object); + return *this; + } + + T release() { + T object = object_; + object_ = NULL; + return object; + } + + operator T() { return object_; } + + private: + void Close() { + if (object_) + DeleteObject(object_); + } + + T object_; + DISALLOW_COPY_AND_ASSIGN(ScopedGDIObject); +}; + +// An explicit specialization for HICON because we have to call DestroyIcon() +// instead of DeleteObject() for HICON. +template<> +void ScopedGDIObject::Close() { + if (object_) + DestroyIcon(object_); +} + +// Typedefs for some common use cases. +typedef ScopedGDIObject ScopedBitmap; +typedef ScopedGDIObject ScopedRegion; +typedef ScopedGDIObject ScopedHFONT; +typedef ScopedGDIObject ScopedHICON; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_SCOPED_GDI_OBJECT_H_ diff --git a/base/win/scoped_handle.cc b/base/win/scoped_handle.cc new file mode 100644 index 0000000000..7b38369d5a --- /dev/null +++ b/base/win/scoped_handle.cc @@ -0,0 +1,68 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/win/scoped_handle.h" + +#include + +#include "base/debug/alias.h" +#include "base/lazy_instance.h" +#include "base/synchronization/lock.h" +#include "base/win/windows_version.h" + +namespace { + +struct Info { + const void* owner; + const void* pc1; + const void* pc2; + DWORD thread_id; +}; +typedef std::map HandleMap; + +base::LazyInstance::Leaky g_handle_map = LAZY_INSTANCE_INITIALIZER; +base::LazyInstance::Leaky g_lock = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +namespace base { +namespace win { + +// Static. +void VerifierTraits::StartTracking(HANDLE handle, const void* owner, + const void* pc1, const void* pc2) { + // Grab the thread id before the lock. + DWORD thread_id = GetCurrentThreadId(); + + AutoLock lock(g_lock.Get()); + + Info handle_info = { owner, pc1, pc2, thread_id }; + std::pair item(handle, handle_info); + std::pair result = g_handle_map.Get().insert(item); + if (!result.second) { + Info other = result.first->second; + debug::Alias(&other); + CHECK(false); + } +} + +// Static. +void VerifierTraits::StopTracking(HANDLE handle, const void* owner, + const void* pc1, const void* pc2) { + AutoLock lock(g_lock.Get()); + HandleMap::iterator i = g_handle_map.Get().find(handle); + if (i == g_handle_map.Get().end()) + CHECK(false); + + Info other = i->second; + if (other.owner != owner) { + debug::Alias(&other); + CHECK(false); + } + + g_handle_map.Get().erase(i); +} + +} // namespace win +} // namespace base diff --git a/base/win/scoped_handle.h b/base/win/scoped_handle.h new file mode 100644 index 0000000000..d236a70beb --- /dev/null +++ b/base/win/scoped_handle.h @@ -0,0 +1,205 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_WIN_SCOPED_HANDLE_H_ +#define BASE_WIN_SCOPED_HANDLE_H_ + +#include + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/move.h" + +namespace base { +namespace win { + +// TODO(rvargas): remove this with the rest of the verifier. +#if defined(COMPILER_MSVC) +// MSDN says to #include , but that breaks the VS2005 build. +extern "C" { + void* _ReturnAddress(); +} +#define BASE_WIN_GET_CALLER _ReturnAddress() +#elif defined(COMPILER_GCC) +#define BASE_WIN_GET_CALLER __builtin_extract_return_addr(\\ + __builtin_return_address(0)) +#endif + +// Generic wrapper for raw handles that takes care of closing handles +// automatically. The class interface follows the style of +// the ScopedStdioHandle class with a few additions: +// - IsValid() method can tolerate multiple invalid handle values such as NULL +// and INVALID_HANDLE_VALUE (-1) for Win32 handles. +// - Receive() method allows to receive a handle value from a function that +// takes a raw handle pointer only. +template +class GenericScopedHandle { + MOVE_ONLY_TYPE_FOR_CPP_03(GenericScopedHandle, RValue) + + public: + typedef typename Traits::Handle Handle; + + // Helper object to contain the effect of Receive() to the function that needs + // a pointer, and allow proper tracking of the handle. + class Receiver { + public: + explicit Receiver(GenericScopedHandle* owner) + : handle_(Traits::NullHandle()), + owner_(owner) {} + ~Receiver() { owner_->Set(handle_); } + + operator Handle*() { return &handle_; } + + private: + Handle handle_; + GenericScopedHandle* owner_; + }; + + GenericScopedHandle() : handle_(Traits::NullHandle()) {} + + explicit GenericScopedHandle(Handle handle) : handle_(Traits::NullHandle()) { + Set(handle); + } + + // Move constructor for C++03 move emulation of this type. + GenericScopedHandle(RValue other) : handle_(Traits::NullHandle()) { + Set(other.object->Take()); + } + + ~GenericScopedHandle() { + Close(); + } + + bool IsValid() const { + return Traits::IsHandleValid(handle_); + } + + // Move operator= for C++03 move emulation of this type. + GenericScopedHandle& operator=(RValue other) { + if (this != other.object) { + Set(other.object->Take()); + } + return *this; + } + + void Set(Handle handle) { + if (handle_ != handle) { + Close(); + + if (Traits::IsHandleValid(handle)) { + handle_ = handle; + Verifier::StartTracking(handle, this, BASE_WIN_GET_CALLER, + tracked_objects::GetProgramCounter()); + } + } + } + + Handle Get() const { + return handle_; + } + + operator Handle() const { + return handle_; + } + + // This method is intended to be used with functions that require a pointer to + // a destination handle, like so: + // void CreateRequiredHandle(Handle* out_handle); + // ScopedHandle a; + // CreateRequiredHandle(a.Receive()); + Receiver Receive() { + DCHECK(!Traits::IsHandleValid(handle_)) << "Handle must be NULL"; + return Receiver(this); + } + + // Transfers ownership away from this object. + Handle Take() { + Handle temp = handle_; + handle_ = Traits::NullHandle(); + if (Traits::IsHandleValid(temp)) { + Verifier::StopTracking(temp, this, BASE_WIN_GET_CALLER, + tracked_objects::GetProgramCounter()); + } + return temp; + } + + // Explicitly closes the owned handle. + void Close() { + if (Traits::IsHandleValid(handle_)) { + Verifier::StopTracking(handle_, this, BASE_WIN_GET_CALLER, + tracked_objects::GetProgramCounter()); + + if (!Traits::CloseHandle(handle_)) + CHECK(false); + + handle_ = Traits::NullHandle(); + } + } + + private: + Handle handle_; +}; + +#undef BASE_WIN_GET_CALLER + +// The traits class for Win32 handles that can be closed via CloseHandle() API. +class HandleTraits { + public: + typedef HANDLE Handle; + + // Closes the handle. + static bool CloseHandle(HANDLE handle) { + return ::CloseHandle(handle) != FALSE; + } + + // Returns true if the handle value is valid. + static bool IsHandleValid(HANDLE handle) { + return handle != NULL && handle != INVALID_HANDLE_VALUE; + } + + // Returns NULL handle value. + static HANDLE NullHandle() { + return NULL; + } + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(HandleTraits); +}; + +// Do-nothing verifier. +class DummyVerifierTraits { + public: + typedef HANDLE Handle; + + static void StartTracking(HANDLE handle, const void* owner, + const void* pc1, const void* pc2) {} + static void StopTracking(HANDLE handle, const void* owner, + const void* pc1, const void* pc2) {} + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(DummyVerifierTraits); +}; + +// Performs actual run-time tracking. +class BASE_EXPORT VerifierTraits { + public: + typedef HANDLE Handle; + + static void StartTracking(HANDLE handle, const void* owner, + const void* pc1, const void* pc2); + static void StopTracking(HANDLE handle, const void* owner, + const void* pc1, const void* pc2); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(VerifierTraits); +}; + +typedef GenericScopedHandle ScopedHandle; + +} // namespace win +} // namespace base + +#endif // BASE_SCOPED_HANDLE_WIN_H_ diff --git a/base/win/scoped_handle_unittest.cc b/base/win/scoped_handle_unittest.cc new file mode 100644 index 0000000000..ee2a551d63 --- /dev/null +++ b/base/win/scoped_handle_unittest.cc @@ -0,0 +1,31 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/win/scoped_handle.h" +#include "testing/gtest/include/gtest/gtest.h" + +void CreateHandle(int value, HANDLE* result) { + *result = reinterpret_cast(value); +} + +TEST(ScopedHandleTest, Receive) { + base::win::ScopedHandle handle; + int value = 51; + + { + // This is not really the expected use case, but it is a very explicit test. + base::win::ScopedHandle::Receiver a = handle.Receive(); + HANDLE* pointer = a; + *pointer = reinterpret_cast(value); + } + + EXPECT_EQ(handle.Get(), reinterpret_cast(value)); + HANDLE to_discard = handle.Take(); + + // The standard use case: + value = 183; + CreateHandle(value, handle.Receive()); + EXPECT_EQ(handle.Get(), reinterpret_cast(value)); + to_discard = handle.Take(); +} diff --git a/base/win/scoped_hdc.h b/base/win/scoped_hdc.h new file mode 100644 index 0000000000..9aead96792 --- /dev/null +++ b/base/win/scoped_hdc.h @@ -0,0 +1,76 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_WIN_SCOPED_HDC_H_ +#define BASE_WIN_SCOPED_HDC_H_ + +#include + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/win/scoped_handle.h" + +namespace base { +namespace win { + +// Like ScopedHandle but for HDC. Only use this on HDCs returned from +// GetDC. +class ScopedGetDC { + public: + explicit ScopedGetDC(HWND hwnd) + : hwnd_(hwnd), + hdc_(GetDC(hwnd)) { + if (hwnd_) { + DCHECK(IsWindow(hwnd_)); + DCHECK(hdc_); + } else { + // If GetDC(NULL) returns NULL, something really bad has happened, like + // GDI handle exhaustion. In this case Chrome is going to behave badly no + // matter what, so we may as well just force a crash now. + CHECK(hdc_); + } + } + + ~ScopedGetDC() { + if (hdc_) + ReleaseDC(hwnd_, hdc_); + } + + operator HDC() { return hdc_; } + + private: + HWND hwnd_; + HDC hdc_; + + DISALLOW_COPY_AND_ASSIGN(ScopedGetDC); +}; + +// Like ScopedHandle but for HDC. Only use this on HDCs returned from +// CreateCompatibleDC, CreateDC and CreateIC. +class CreateDCTraits { + public: + typedef HDC Handle; + + static bool CloseHandle(HDC handle) { + return ::DeleteDC(handle) != FALSE; + } + + static bool IsHandleValid(HDC handle) { + return handle != NULL; + } + + static HDC NullHandle() { + return NULL; + } + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(CreateDCTraits); +}; + +typedef GenericScopedHandle ScopedCreateDC; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_SCOPED_HDC_H_ diff --git a/base/win/scoped_hglobal.h b/base/win/scoped_hglobal.h new file mode 100644 index 0000000000..891e6cdfc8 --- /dev/null +++ b/base/win/scoped_hglobal.h @@ -0,0 +1,52 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_WIN_SCOPED_HGLOBAL_H_ +#define BASE_WIN_SCOPED_HGLOBAL_H_ + +#include + +#include "base/basictypes.h" + +namespace base { +namespace win { + +// Like ScopedHandle except for HGLOBAL. +template +class ScopedHGlobal { + public: + explicit ScopedHGlobal(HGLOBAL glob) : glob_(glob) { + data_ = static_cast(GlobalLock(glob_)); + } + ~ScopedHGlobal() { + GlobalUnlock(glob_); + } + + T* get() { return data_; } + + size_t Size() const { return GlobalSize(glob_); } + + T* operator->() const { + assert(data_ != 0); + return data_; + } + + T* release() { + T* data = data_; + data_ = NULL; + return data; + } + + private: + HGLOBAL glob_; + + T* data_; + + DISALLOW_COPY_AND_ASSIGN(ScopedHGlobal); +}; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_SCOPED_HGLOBAL_H_ diff --git a/base/win/scoped_process_information.cc b/base/win/scoped_process_information.cc new file mode 100644 index 0000000000..cb7a30e2f5 --- /dev/null +++ b/base/win/scoped_process_information.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/win/scoped_process_information.h" + +#include "base/logging.h" +#include "base/win/scoped_handle.h" + +namespace base { +namespace win { + +namespace { + +// Duplicates source into target, returning true upon success. |target| is +// guaranteed to be untouched in case of failure. Succeeds with no side-effects +// if source is NULL. +bool CheckAndDuplicateHandle(HANDLE source, HANDLE* target) { + if (!source) + return true; + + HANDLE temp = NULL; + if (!::DuplicateHandle(::GetCurrentProcess(), source, + ::GetCurrentProcess(), &temp, 0, FALSE, + DUPLICATE_SAME_ACCESS)) { + DPLOG(ERROR) << "Failed to duplicate a handle."; + return false; + } + *target = temp; + return true; +} + +} // namespace + +ScopedProcessInformation::ScopedProcessInformation() + : process_id_(0), thread_id_(0) { +} + +ScopedProcessInformation::~ScopedProcessInformation() { + Close(); +} + +ScopedProcessInformation::Receiver ScopedProcessInformation::Receive() { + DCHECK(!IsValid()) << "process_information_ must be NULL"; + return Receiver(this); +} + +bool ScopedProcessInformation::IsValid() const { + return process_id_ || process_handle_.Get() || + thread_id_ || thread_handle_.Get(); +} + +void ScopedProcessInformation::Close() { + process_handle_.Close(); + thread_handle_.Close(); + process_id_ = 0; + thread_id_ = 0; +} + +void ScopedProcessInformation::Set(const PROCESS_INFORMATION& process_info) { + if (IsValid()) + Close(); + + process_handle_.Set(process_info.hProcess); + thread_handle_.Set(process_info.hThread); + process_id_ = process_info.dwProcessId; + thread_id_ = process_info.dwThreadId; +} + +bool ScopedProcessInformation::DuplicateFrom( + const ScopedProcessInformation& other) { + DCHECK(!IsValid()) << "target ScopedProcessInformation must be NULL"; + DCHECK(other.IsValid()) << "source ScopedProcessInformation must be valid"; + + if (CheckAndDuplicateHandle(other.process_handle(), + process_handle_.Receive()) && + CheckAndDuplicateHandle(other.thread_handle(), + thread_handle_.Receive())) { + process_id_ = other.process_id(); + thread_id_ = other.thread_id(); + return true; + } + + return false; +} + +PROCESS_INFORMATION ScopedProcessInformation::Take() { + PROCESS_INFORMATION process_information = {}; + process_information.hProcess = process_handle_.Take(); + process_information.hThread = thread_handle_.Take(); + process_information.dwProcessId = process_id(); + process_information.dwThreadId = thread_id(); + process_id_ = 0; + thread_id_ = 0; + + return process_information; +} + +HANDLE ScopedProcessInformation::TakeProcessHandle() { + process_id_ = 0; + return process_handle_.Take(); +} + +HANDLE ScopedProcessInformation::TakeThreadHandle() { + thread_id_ = 0; + return thread_handle_.Take(); +} + +} // namespace win +} // namespace base diff --git a/base/win/scoped_process_information.h b/base/win/scoped_process_information.h new file mode 100644 index 0000000000..1f404c2dba --- /dev/null +++ b/base/win/scoped_process_information.h @@ -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. + +#ifndef BASE_WIN_SCOPED_PROCESS_INFORMATION_H_ +#define BASE_WIN_SCOPED_PROCESS_INFORMATION_H_ + +#include + +#include "base/basictypes.h" +#include "base/base_export.h" +#include "base/win/scoped_handle.h" + +namespace base { +namespace win { + +// Manages the closing of process and thread handles from PROCESS_INFORMATION +// structures. Allows clients to take ownership of either handle independently. +class BASE_EXPORT ScopedProcessInformation { + public: + // Helper object to contain the effect of Receive() to the funtion that needs + // a pointer. + class Receiver { + public: + explicit Receiver(ScopedProcessInformation* owner) + : info_(), + owner_(owner) {} + ~Receiver() { owner_->Set(info_); } + + operator PROCESS_INFORMATION*() { return &info_; } + + private: + PROCESS_INFORMATION info_; + ScopedProcessInformation* owner_; + }; + + ScopedProcessInformation(); + ~ScopedProcessInformation(); + + // Returns an object that may be passed to API calls such as CreateProcess. + // DCHECKs that the object is not currently holding any handles. + // HANDLEs stored in the returned PROCESS_INFORMATION will be owned by this + // instance. + // The intended use case is something like this: + // if (::CreateProcess(..., startup_info, scoped_proces_info.Receive())) + Receiver Receive(); + + // Returns true iff this instance is holding a thread and/or process handle. + bool IsValid() const; + + // Closes the held thread and process handles, if any. + void Close(); + + // Populates this instance with the provided |process_info|. + void Set(const PROCESS_INFORMATION& process_info); + + // Populates this instance with duplicate handles and the thread/process IDs + // from |other|. Returns false in case of failure, in which case this instance + // will be completely unpopulated. + bool DuplicateFrom(const ScopedProcessInformation& other); + + // Transfers ownership of the held PROCESS_INFORMATION, if any, away from this + // instance. + PROCESS_INFORMATION Take(); + + // Transfers ownership of the held process handle, if any, away from this + // instance. Note that the related process_id will also be cleared. + HANDLE TakeProcessHandle(); + + // Transfers ownership of the held thread handle, if any, away from this + // instance. Note that the related thread_id will also be cleared. + HANDLE TakeThreadHandle(); + + // Returns the held process handle, if any, while retaining ownership. + HANDLE process_handle() const { + return process_handle_.Get(); + } + + // Returns the held thread handle, if any, while retaining ownership. + HANDLE thread_handle() const { + return thread_handle_.Get(); + } + + // Returns the held process id, if any. + DWORD process_id() const { + return process_id_; + } + + // Returns the held thread id, if any. + DWORD thread_id() const { + return thread_id_; + } + + private: + ScopedHandle process_handle_; + ScopedHandle thread_handle_; + DWORD process_id_; + DWORD thread_id_; + + DISALLOW_COPY_AND_ASSIGN(ScopedProcessInformation); +}; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_SCOPED_PROCESS_INFORMATION_H_ diff --git a/base/win/scoped_process_information_unittest.cc b/base/win/scoped_process_information_unittest.cc new file mode 100644 index 0000000000..b8ffc4427c --- /dev/null +++ b/base/win/scoped_process_information_unittest.cc @@ -0,0 +1,160 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +#include "base/command_line.h" +#include "base/process/kill.h" +#include "base/test/multiprocess_test.h" +#include "base/win/scoped_process_information.h" +#include "testing/multiprocess_func_list.h" + +namespace { + +const DWORD kProcessId = 4321; +const DWORD kThreadId = 1234; +const HANDLE kProcessHandle = reinterpret_cast(7651); +const HANDLE kThreadHandle = reinterpret_cast(1567); + +void MockCreateProcess(PROCESS_INFORMATION* process_info) { + process_info->dwProcessId = kProcessId; + process_info->dwThreadId = kThreadId; + process_info->hProcess = kProcessHandle; + process_info->hThread = kThreadHandle; +} + +} // namespace + +class ScopedProcessInformationTest : public base::MultiProcessTest { + protected: + void DoCreateProcess(const std::string& main_id, + PROCESS_INFORMATION* process_handle); +}; + +MULTIPROCESS_TEST_MAIN(ReturnSeven) { + return 7; +} + +MULTIPROCESS_TEST_MAIN(ReturnNine) { + return 9; +} + +void ScopedProcessInformationTest::DoCreateProcess( + const std::string& main_id, PROCESS_INFORMATION* process_handle) { + std::wstring cmd_line = + this->MakeCmdLine(main_id, false).GetCommandLineString(); + STARTUPINFO startup_info = {}; + startup_info.cb = sizeof(startup_info); + + EXPECT_TRUE(::CreateProcess(NULL, + const_cast(cmd_line.c_str()), + NULL, NULL, false, 0, NULL, NULL, + &startup_info, process_handle)); +} + +TEST_F(ScopedProcessInformationTest, InitiallyInvalid) { + base::win::ScopedProcessInformation process_info; + ASSERT_FALSE(process_info.IsValid()); +} + +TEST_F(ScopedProcessInformationTest, Receive) { + base::win::ScopedProcessInformation process_info; + MockCreateProcess(process_info.Receive()); + + EXPECT_TRUE(process_info.IsValid()); + EXPECT_EQ(kProcessId, process_info.process_id()); + EXPECT_EQ(kThreadId, process_info.thread_id()); + EXPECT_EQ(kProcessHandle, process_info.process_handle()); + EXPECT_EQ(kThreadHandle, process_info.thread_handle()); + PROCESS_INFORMATION to_discard = process_info.Take(); +} + +TEST_F(ScopedProcessInformationTest, TakeProcess) { + base::win::ScopedProcessInformation process_info; + MockCreateProcess(process_info.Receive()); + + HANDLE process = process_info.TakeProcessHandle(); + EXPECT_EQ(kProcessHandle, process); + EXPECT_EQ(NULL, process_info.process_handle()); + EXPECT_EQ(0, process_info.process_id()); + EXPECT_TRUE(process_info.IsValid()); + PROCESS_INFORMATION to_discard = process_info.Take(); +} + +TEST_F(ScopedProcessInformationTest, TakeThread) { + base::win::ScopedProcessInformation process_info; + MockCreateProcess(process_info.Receive()); + + HANDLE thread = process_info.TakeThreadHandle(); + EXPECT_EQ(kThreadHandle, thread); + EXPECT_EQ(NULL, process_info.thread_handle()); + EXPECT_EQ(0, process_info.thread_id()); + EXPECT_TRUE(process_info.IsValid()); + PROCESS_INFORMATION to_discard = process_info.Take(); +} + +TEST_F(ScopedProcessInformationTest, TakeBoth) { + base::win::ScopedProcessInformation process_info; + MockCreateProcess(process_info.Receive()); + + HANDLE process = process_info.TakeProcessHandle(); + HANDLE thread = process_info.TakeThreadHandle(); + EXPECT_FALSE(process_info.IsValid()); + PROCESS_INFORMATION to_discard = process_info.Take(); +} + +TEST_F(ScopedProcessInformationTest, TakeWholeStruct) { + base::win::ScopedProcessInformation process_info; + MockCreateProcess(process_info.Receive()); + + PROCESS_INFORMATION to_discard = process_info.Take(); + EXPECT_EQ(kProcessId, to_discard.dwProcessId); + EXPECT_EQ(kThreadId, to_discard.dwThreadId); + EXPECT_EQ(kProcessHandle, to_discard.hProcess); + EXPECT_EQ(kThreadHandle, to_discard.hThread); + EXPECT_FALSE(process_info.IsValid()); +} + +TEST_F(ScopedProcessInformationTest, Duplicate) { + base::win::ScopedProcessInformation process_info; + DoCreateProcess("ReturnSeven", process_info.Receive()); + base::win::ScopedProcessInformation duplicate; + duplicate.DuplicateFrom(process_info); + + ASSERT_TRUE(process_info.IsValid()); + ASSERT_NE(0u, process_info.process_id()); + ASSERT_EQ(duplicate.process_id(), process_info.process_id()); + ASSERT_NE(0u, process_info.thread_id()); + ASSERT_EQ(duplicate.thread_id(), process_info.thread_id()); + + // Validate that we have separate handles that are good. + int exit_code = 0; + ASSERT_TRUE(base::WaitForExitCode(process_info.TakeProcessHandle(), + &exit_code)); + ASSERT_EQ(7, exit_code); + + exit_code = 0; + ASSERT_TRUE(base::WaitForExitCode(duplicate.TakeProcessHandle(), + &exit_code)); + ASSERT_EQ(7, exit_code); + + ASSERT_TRUE(::CloseHandle(process_info.TakeThreadHandle())); + ASSERT_TRUE(::CloseHandle(duplicate.TakeThreadHandle())); +} + +TEST_F(ScopedProcessInformationTest, Set) { + PROCESS_INFORMATION base_process_info = {}; + MockCreateProcess(&base_process_info); + + base::win::ScopedProcessInformation process_info; + process_info.Set(base_process_info); + + EXPECT_EQ(kProcessId, process_info.process_id()); + EXPECT_EQ(kThreadId, process_info.thread_id()); + EXPECT_EQ(kProcessHandle, process_info.process_handle()); + EXPECT_EQ(kThreadHandle, process_info.thread_handle()); + base_process_info = process_info.Take(); +} diff --git a/base/win/scoped_propvariant.h b/base/win/scoped_propvariant.h new file mode 100644 index 0000000000..711d51adb4 --- /dev/null +++ b/base/win/scoped_propvariant.h @@ -0,0 +1,59 @@ +// 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_WIN_SCOPED_PROPVARIANT_H_ +#define BASE_WIN_SCOPED_PROPVARIANT_H_ + +#include + +#include "base/basictypes.h" +#include "base/logging.h" + +namespace base { +namespace win { + +// A PROPVARIANT that is automatically initialized and cleared upon respective +// construction and destruction of this class. +class ScopedPropVariant { + public: + ScopedPropVariant() { + PropVariantInit(&pv_); + } + + ~ScopedPropVariant() { + Reset(); + } + + // Returns a pointer to the underlying PROPVARIANT for use as an out param in + // a function call. + PROPVARIANT* Receive() { + DCHECK_EQ(pv_.vt, VT_EMPTY); + return &pv_; + } + + // Clears the instance to prepare it for re-use (e.g., via Receive). + void Reset() { + if (pv_.vt != VT_EMPTY) { + HRESULT result = PropVariantClear(&pv_); + DCHECK_EQ(result, S_OK); + } + } + + const PROPVARIANT& get() const { return pv_; } + + const PROPVARIANT* operator&() const { return &pv_; } + + private: + PROPVARIANT pv_; + + // Comparison operators for ScopedPropVariant are not supported at this point. + bool operator==(const ScopedPropVariant&) const; + bool operator!=(const ScopedPropVariant&) const; + DISALLOW_COPY_AND_ASSIGN(ScopedPropVariant); +}; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_SCOPED_PROPVARIANT_H_ diff --git a/base/win/scoped_select_object.h b/base/win/scoped_select_object.h new file mode 100644 index 0000000000..347de798e2 --- /dev/null +++ b/base/win/scoped_select_object.h @@ -0,0 +1,43 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_WIN_SCOPED_SELECT_OBJECT_H_ +#define BASE_WIN_SCOPED_SELECT_OBJECT_H_ + +#include + +#include "base/basictypes.h" +#include "base/logging.h" + +namespace base { +namespace win { + +// Helper class for deselecting object from DC. +class ScopedSelectObject { + public: + ScopedSelectObject(HDC hdc, HGDIOBJ object) + : hdc_(hdc), + oldobj_(SelectObject(hdc, object)) { + DCHECK(hdc_); + DCHECK(object); + DCHECK(oldobj_ != NULL && oldobj_ != HGDI_ERROR); + } + + ~ScopedSelectObject() { + HGDIOBJ object = SelectObject(hdc_, oldobj_); + DCHECK((GetObjectType(oldobj_) != OBJ_REGION && object != NULL) || + (GetObjectType(oldobj_) == OBJ_REGION && object != HGDI_ERROR)); + } + + private: + HDC hdc_; + HGDIOBJ oldobj_; + + DISALLOW_COPY_AND_ASSIGN(ScopedSelectObject); +}; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_SCOPED_SELECT_OBJECT_H_ diff --git a/base/win/scoped_variant.cc b/base/win/scoped_variant.cc new file mode 100644 index 0000000000..f57ab93f6c --- /dev/null +++ b/base/win/scoped_variant.cc @@ -0,0 +1,276 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/win/scoped_variant.h" +#include "base/logging.h" + +namespace base { +namespace win { + +// Global, const instance of an empty variant. +const VARIANT ScopedVariant::kEmptyVariant = { VT_EMPTY }; + +ScopedVariant::~ScopedVariant() { + COMPILE_ASSERT(sizeof(ScopedVariant) == sizeof(VARIANT), ScopedVariantSize); + ::VariantClear(&var_); +} + +ScopedVariant::ScopedVariant(const wchar_t* str) { + var_.vt = VT_EMPTY; + Set(str); +} + +ScopedVariant::ScopedVariant(const wchar_t* str, UINT length) { + var_.vt = VT_BSTR; + var_.bstrVal = ::SysAllocStringLen(str, length); +} + +ScopedVariant::ScopedVariant(int value, VARTYPE vt) { + var_.vt = vt; + var_.lVal = value; +} + +ScopedVariant::ScopedVariant(double value, VARTYPE vt) { + DCHECK(vt == VT_R8 || vt == VT_DATE); + var_.vt = vt; + var_.dblVal = value; +} + +ScopedVariant::ScopedVariant(IDispatch* dispatch) { + var_.vt = VT_EMPTY; + Set(dispatch); +} + +ScopedVariant::ScopedVariant(IUnknown* unknown) { + var_.vt = VT_EMPTY; + Set(unknown); +} + +ScopedVariant::ScopedVariant(SAFEARRAY* safearray) { + var_.vt = VT_EMPTY; + Set(safearray); +} + +ScopedVariant::ScopedVariant(const VARIANT& var) { + var_.vt = VT_EMPTY; + Set(var); +} + +void ScopedVariant::Reset(const VARIANT& var) { + if (&var != &var_) { + ::VariantClear(&var_); + var_ = var; + } +} + +VARIANT ScopedVariant::Release() { + VARIANT var = var_; + var_.vt = VT_EMPTY; + return var; +} + +void ScopedVariant::Swap(ScopedVariant& var) { + VARIANT tmp = var_; + var_ = var.var_; + var.var_ = tmp; +} + +VARIANT* ScopedVariant::Receive() { + DCHECK(!IsLeakableVarType(var_.vt)) << "variant leak. type: " << var_.vt; + return &var_; +} + +VARIANT ScopedVariant::Copy() const { + VARIANT ret = { VT_EMPTY }; + ::VariantCopy(&ret, &var_); + return ret; +} + +int ScopedVariant::Compare(const VARIANT& var, bool ignore_case) const { + ULONG flags = ignore_case ? NORM_IGNORECASE : 0; + HRESULT hr = ::VarCmp(const_cast(&var_), const_cast(&var), + LOCALE_USER_DEFAULT, flags); + int ret = 0; + + switch (hr) { + case VARCMP_LT: + ret = -1; + break; + + case VARCMP_GT: + case VARCMP_NULL: + ret = 1; + break; + + default: + // Equal. + break; + } + + return ret; +} + +void ScopedVariant::Set(const wchar_t* str) { + DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; + var_.vt = VT_BSTR; + var_.bstrVal = ::SysAllocString(str); +} + +void ScopedVariant::Set(int8 i8) { + DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; + var_.vt = VT_I1; + var_.cVal = i8; +} + +void ScopedVariant::Set(uint8 ui8) { + DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; + var_.vt = VT_UI1; + var_.bVal = ui8; +} + +void ScopedVariant::Set(int16 i16) { + DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; + var_.vt = VT_I2; + var_.iVal = i16; +} + +void ScopedVariant::Set(uint16 ui16) { + DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; + var_.vt = VT_UI2; + var_.uiVal = ui16; +} + +void ScopedVariant::Set(int32 i32) { + DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; + var_.vt = VT_I4; + var_.lVal = i32; +} + +void ScopedVariant::Set(uint32 ui32) { + DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; + var_.vt = VT_UI4; + var_.ulVal = ui32; +} + +void ScopedVariant::Set(int64 i64) { + DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; + var_.vt = VT_I8; + var_.llVal = i64; +} + +void ScopedVariant::Set(uint64 ui64) { + DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; + var_.vt = VT_UI8; + var_.ullVal = ui64; +} + +void ScopedVariant::Set(float r32) { + DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; + var_.vt = VT_R4; + var_.fltVal = r32; +} + +void ScopedVariant::Set(double r64) { + DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; + var_.vt = VT_R8; + var_.dblVal = r64; +} + +void ScopedVariant::SetDate(DATE date) { + DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; + var_.vt = VT_DATE; + var_.date = date; +} + +void ScopedVariant::Set(IDispatch* disp) { + DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; + var_.vt = VT_DISPATCH; + var_.pdispVal = disp; + if (disp) + disp->AddRef(); +} + +void ScopedVariant::Set(bool b) { + DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; + var_.vt = VT_BOOL; + var_.boolVal = b ? VARIANT_TRUE : VARIANT_FALSE; +} + +void ScopedVariant::Set(IUnknown* unk) { + DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; + var_.vt = VT_UNKNOWN; + var_.punkVal = unk; + if (unk) + unk->AddRef(); +} + +void ScopedVariant::Set(SAFEARRAY* array) { + DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; + if (SUCCEEDED(::SafeArrayGetVartype(array, &var_.vt))) { + var_.vt |= VT_ARRAY; + var_.parray = array; + } else { + DCHECK(!array) << "Unable to determine safearray vartype"; + var_.vt = VT_EMPTY; + } +} + +void ScopedVariant::Set(const VARIANT& var) { + DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; + if (FAILED(::VariantCopy(&var_, &var))) { + DLOG(ERROR) << "VariantCopy failed"; + var_.vt = VT_EMPTY; + } +} + +ScopedVariant& ScopedVariant::operator=(const VARIANT& var) { + if (&var != &var_) { + VariantClear(&var_); + Set(var); + } + return *this; +} + +bool ScopedVariant::IsLeakableVarType(VARTYPE vt) { + bool leakable = false; + switch (vt & VT_TYPEMASK) { + case VT_BSTR: + case VT_DISPATCH: + // we treat VT_VARIANT as leakable to err on the safe side. + case VT_VARIANT: + case VT_UNKNOWN: + case VT_SAFEARRAY: + + // very rarely used stuff (if ever): + case VT_VOID: + case VT_PTR: + case VT_CARRAY: + case VT_USERDEFINED: + case VT_LPSTR: + case VT_LPWSTR: + case VT_RECORD: + case VT_INT_PTR: + case VT_UINT_PTR: + case VT_FILETIME: + case VT_BLOB: + case VT_STREAM: + case VT_STORAGE: + case VT_STREAMED_OBJECT: + case VT_STORED_OBJECT: + case VT_BLOB_OBJECT: + case VT_VERSIONED_STREAM: + case VT_BSTR_BLOB: + leakable = true; + break; + } + + if (!leakable && (vt & VT_ARRAY) != 0) { + leakable = true; + } + + return leakable; +} + +} // namespace win +} // namespace base diff --git a/base/win/scoped_variant.h b/base/win/scoped_variant.h new file mode 100644 index 0000000000..b6e65798fb --- /dev/null +++ b/base/win/scoped_variant.h @@ -0,0 +1,166 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_WIN_SCOPED_VARIANT_H_ +#define BASE_WIN_SCOPED_VARIANT_H_ + +#include +#include + +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace base { +namespace win { + +// Scoped VARIANT class for automatically freeing a COM VARIANT at the +// end of a scope. Additionally provides a few functions to make the +// encapsulated VARIANT easier to use. +// Instead of inheriting from VARIANT, we take the containment approach +// in order to have more control over the usage of the variant and guard +// against memory leaks. +class BASE_EXPORT ScopedVariant { + public: + // Declaration of a global variant variable that's always VT_EMPTY + static const VARIANT kEmptyVariant; + + // Default constructor. + ScopedVariant() { + // This is equivalent to what VariantInit does, but less code. + var_.vt = VT_EMPTY; + } + + // Constructor to create a new VT_BSTR VARIANT. + // NOTE: Do not pass a BSTR to this constructor expecting ownership to + // be transferred + explicit ScopedVariant(const wchar_t* str); + + // Creates a new VT_BSTR variant of a specified length. + ScopedVariant(const wchar_t* str, UINT length); + + // Creates a new integral type variant and assigns the value to + // VARIANT.lVal (32 bit sized field). + explicit ScopedVariant(int value, VARTYPE vt = VT_I4); + + // Creates a new double-precision type variant. |vt| must be either VT_R8 + // or VT_DATE. + explicit ScopedVariant(double value, VARTYPE vt = VT_R8); + + // VT_DISPATCH + explicit ScopedVariant(IDispatch* dispatch); + + // VT_UNKNOWN + explicit ScopedVariant(IUnknown* unknown); + + // SAFEARRAY + explicit ScopedVariant(SAFEARRAY* safearray); + + // Copies the variant. + explicit ScopedVariant(const VARIANT& var); + + ~ScopedVariant(); + + inline VARTYPE type() const { + return var_.vt; + } + + // Give ScopedVariant ownership over an already allocated VARIANT. + void Reset(const VARIANT& var = kEmptyVariant); + + // Releases ownership of the VARIANT to the caller. + VARIANT Release(); + + // Swap two ScopedVariant's. + void Swap(ScopedVariant& var); + + // Returns a copy of the variant. + VARIANT Copy() const; + + // The return value is 0 if the variants are equal, 1 if this object is + // greater than |var|, -1 if it is smaller. + int Compare(const VARIANT& var, bool ignore_case = false) const; + + // Retrieves the pointer address. + // Used to receive a VARIANT as an out argument (and take ownership). + // The function DCHECKs on the current value being empty/null. + // Usage: GetVariant(var.receive()); + VARIANT* Receive(); + + void Set(const wchar_t* str); + + // Setters for simple types. + void Set(int8 i8); + void Set(uint8 ui8); + void Set(int16 i16); + void Set(uint16 ui16); + void Set(int32 i32); + void Set(uint32 ui32); + void Set(int64 i64); + void Set(uint64 ui64); + void Set(float r32); + void Set(double r64); + void Set(bool b); + + // Creates a copy of |var| and assigns as this instance's value. + // Note that this is different from the Reset() method that's used to + // free the current value and assume ownership. + void Set(const VARIANT& var); + + // COM object setters + void Set(IDispatch* disp); + void Set(IUnknown* unk); + + // SAFEARRAY support + void Set(SAFEARRAY* array); + + // Special setter for DATE since DATE is a double and we already have + // a setter for double. + void SetDate(DATE date); + + // Allows const access to the contained variant without DCHECKs etc. + // This support is necessary for the V_XYZ (e.g. V_BSTR) set of macros to + // work properly but still doesn't allow modifications since we want control + // over that. + const VARIANT* operator&() const { + return &var_; + } + + // Like other scoped classes (e.g scoped_refptr, ScopedComPtr, ScopedBstr) + // we support the assignment operator for the type we wrap. + ScopedVariant& operator=(const VARIANT& var); + + // A hack to pass a pointer to the variant where the accepting + // function treats the variant as an input-only, read-only value + // but the function prototype requires a non const variant pointer. + // There's no DCHECK or anything here. Callers must know what they're doing. + VARIANT* AsInput() const { + // The nature of this function is const, so we declare + // it as such and cast away the constness here. + return const_cast(&var_); + } + + // Allows the ScopedVariant instance to be passed to functions either by value + // or by const reference. + operator const VARIANT&() const { + return var_; + } + + // Used as a debug check to see if we're leaking anything. + static bool IsLeakableVarType(VARTYPE vt); + + protected: + VARIANT var_; + + private: + // Comparison operators for ScopedVariant are not supported at this point. + // Use the Compare method instead. + bool operator==(const ScopedVariant& var) const; + bool operator!=(const ScopedVariant& var) const; + DISALLOW_COPY_AND_ASSIGN(ScopedVariant); +}; + +} // namespace win +} // namesoace base + +#endif // BASE_WIN_SCOPED_VARIANT_H_ diff --git a/base/win/scoped_variant_unittest.cc b/base/win/scoped_variant_unittest.cc new file mode 100644 index 0000000000..1f017cf94a --- /dev/null +++ b/base/win/scoped_variant_unittest.cc @@ -0,0 +1,261 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/win/scoped_variant.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace win { + +namespace { + +static const wchar_t kTestString1[] = L"Used to create BSTRs"; +static const wchar_t kTestString2[] = L"Also used to create BSTRs"; + +void GiveMeAVariant(VARIANT* ret) { + EXPECT_TRUE(ret != NULL); + ret->vt = VT_BSTR; + V_BSTR(ret) = ::SysAllocString(kTestString1); +} + +// A dummy IDispatch implementation (if you can call it that). +// The class does nothing intelligent really. Only increments a counter +// when AddRef is called and decrements it when Release is called. +class FakeComObject : public IDispatch { + public: + FakeComObject() : ref_(0) { + } + + STDMETHOD_(DWORD, AddRef)() { + ref_++; + return ref_; + } + + STDMETHOD_(DWORD, Release)() { + ref_--; + return ref_; + } + + STDMETHOD(QueryInterface)(REFIID, void**) { + return E_NOTIMPL; + } + + STDMETHOD(GetTypeInfoCount)(UINT*) { + return E_NOTIMPL; + } + + STDMETHOD(GetTypeInfo)(UINT, LCID, ITypeInfo**) { + return E_NOTIMPL; + } + + STDMETHOD(GetIDsOfNames)(REFIID, LPOLESTR*, UINT, LCID, DISPID*) { + return E_NOTIMPL; + } + + STDMETHOD(Invoke)(DISPID, REFIID, LCID, WORD, DISPPARAMS*, VARIANT*, + EXCEPINFO*, UINT*) { + return E_NOTIMPL; + } + + // A way to check the internal reference count of the class. + int ref_count() const { + return ref_; + } + + protected: + int ref_; +}; + +} // namespace + +TEST(ScopedVariantTest, ScopedVariant) { + ScopedVariant var; + EXPECT_TRUE(var.type() == VT_EMPTY); + // V_BSTR(&var) = NULL; <- NOTE: Assignment like that is not supported + + ScopedVariant var_bstr(L"VT_BSTR"); + EXPECT_EQ(VT_BSTR, V_VT(&var_bstr)); + EXPECT_TRUE(V_BSTR(&var_bstr) != NULL); // can't use EXPECT_NE for BSTR + var_bstr.Reset(); + EXPECT_NE(VT_BSTR, V_VT(&var_bstr)); + var_bstr.Set(kTestString2); + EXPECT_EQ(VT_BSTR, V_VT(&var_bstr)); + + VARIANT tmp = var_bstr.Release(); + EXPECT_EQ(VT_EMPTY, V_VT(&var_bstr)); + EXPECT_EQ(VT_BSTR, V_VT(&tmp)); + EXPECT_EQ(0, lstrcmp(V_BSTR(&tmp), kTestString2)); + + var.Reset(tmp); + EXPECT_EQ(VT_BSTR, V_VT(&var)); + EXPECT_EQ(0, lstrcmpW(V_BSTR(&var), kTestString2)); + + var_bstr.Swap(var); + EXPECT_EQ(VT_EMPTY, V_VT(&var)); + EXPECT_EQ(VT_BSTR, V_VT(&var_bstr)); + EXPECT_EQ(0, lstrcmpW(V_BSTR(&var_bstr), kTestString2)); + var_bstr.Reset(); + + // Test the Compare and Copy routines. + GiveMeAVariant(var_bstr.Receive()); + ScopedVariant var_bstr2(V_BSTR(&var_bstr)); + EXPECT_EQ(0, var_bstr.Compare(var_bstr2)); + var_bstr2.Reset(); + EXPECT_NE(0, var_bstr.Compare(var_bstr2)); + var_bstr2.Reset(var_bstr.Copy()); + EXPECT_EQ(0, var_bstr.Compare(var_bstr2)); + var_bstr2.Reset(); + var_bstr2.Set(V_BSTR(&var_bstr)); + EXPECT_EQ(0, var_bstr.Compare(var_bstr2)); + var_bstr2.Reset(); + var_bstr.Reset(); + + // Test for the SetDate setter. + SYSTEMTIME sys_time; + ::GetSystemTime(&sys_time); + DATE date; + ::SystemTimeToVariantTime(&sys_time, &date); + var.Reset(); + var.SetDate(date); + EXPECT_EQ(VT_DATE, var.type()); + EXPECT_EQ(date, V_DATE(&var)); + + // Simple setter tests. These do not require resetting the variant + // after each test since the variant type is not "leakable" (i.e. doesn't + // need to be freed explicitly). + + // We need static cast here since char defaults to int (!?). + var.Set(static_cast('v')); + EXPECT_EQ(VT_I1, var.type()); + EXPECT_EQ('v', V_I1(&var)); + + var.Set(static_cast(123)); + EXPECT_EQ(VT_I2, var.type()); + EXPECT_EQ(123, V_I2(&var)); + + var.Set(static_cast(123)); + EXPECT_EQ(VT_I4, var.type()); + EXPECT_EQ(123, V_I4(&var)); + + var.Set(static_cast(123)); + EXPECT_EQ(VT_I8, var.type()); + EXPECT_EQ(123, V_I8(&var)); + + var.Set(static_cast(123)); + EXPECT_EQ(VT_UI1, var.type()); + EXPECT_EQ(123, V_UI1(&var)); + + var.Set(static_cast(123)); + EXPECT_EQ(VT_UI2, var.type()); + EXPECT_EQ(123, V_UI2(&var)); + + var.Set(static_cast(123)); + EXPECT_EQ(VT_UI4, var.type()); + EXPECT_EQ(123, V_UI4(&var)); + + var.Set(static_cast(123)); + EXPECT_EQ(VT_UI8, var.type()); + EXPECT_EQ(123, V_UI8(&var)); + + var.Set(123.123f); + EXPECT_EQ(VT_R4, var.type()); + EXPECT_EQ(123.123f, V_R4(&var)); + + var.Set(static_cast(123.123)); + EXPECT_EQ(VT_R8, var.type()); + EXPECT_EQ(123.123, V_R8(&var)); + + var.Set(true); + EXPECT_EQ(VT_BOOL, var.type()); + EXPECT_EQ(VARIANT_TRUE, V_BOOL(&var)); + var.Set(false); + EXPECT_EQ(VT_BOOL, var.type()); + EXPECT_EQ(VARIANT_FALSE, V_BOOL(&var)); + + // Com interface tests + + var.Set(static_cast(NULL)); + EXPECT_EQ(VT_DISPATCH, var.type()); + EXPECT_EQ(NULL, V_DISPATCH(&var)); + var.Reset(); + + var.Set(static_cast(NULL)); + EXPECT_EQ(VT_UNKNOWN, var.type()); + EXPECT_EQ(NULL, V_UNKNOWN(&var)); + var.Reset(); + + FakeComObject faker; + EXPECT_EQ(0, faker.ref_count()); + var.Set(static_cast(&faker)); + EXPECT_EQ(VT_DISPATCH, var.type()); + EXPECT_EQ(&faker, V_DISPATCH(&var)); + EXPECT_EQ(1, faker.ref_count()); + var.Reset(); + EXPECT_EQ(0, faker.ref_count()); + + var.Set(static_cast(&faker)); + EXPECT_EQ(VT_UNKNOWN, var.type()); + EXPECT_EQ(&faker, V_UNKNOWN(&var)); + EXPECT_EQ(1, faker.ref_count()); + var.Reset(); + EXPECT_EQ(0, faker.ref_count()); + + { + ScopedVariant disp_var(&faker); + EXPECT_EQ(VT_DISPATCH, disp_var.type()); + EXPECT_EQ(&faker, V_DISPATCH(&disp_var)); + EXPECT_EQ(1, faker.ref_count()); + } + EXPECT_EQ(0, faker.ref_count()); + + { + ScopedVariant ref1(&faker); + EXPECT_EQ(1, faker.ref_count()); + ScopedVariant ref2(static_cast(ref1)); + EXPECT_EQ(2, faker.ref_count()); + ScopedVariant ref3; + ref3 = static_cast(ref2); + EXPECT_EQ(3, faker.ref_count()); + } + EXPECT_EQ(0, faker.ref_count()); + + { + ScopedVariant unk_var(static_cast(&faker)); + EXPECT_EQ(VT_UNKNOWN, unk_var.type()); + EXPECT_EQ(&faker, V_UNKNOWN(&unk_var)); + EXPECT_EQ(1, faker.ref_count()); + } + EXPECT_EQ(0, faker.ref_count()); + + VARIANT raw; + raw.vt = VT_UNKNOWN; + raw.punkVal = &faker; + EXPECT_EQ(0, faker.ref_count()); + var.Set(raw); + EXPECT_EQ(1, faker.ref_count()); + var.Reset(); + EXPECT_EQ(0, faker.ref_count()); + + { + ScopedVariant number(123); + EXPECT_EQ(VT_I4, number.type()); + EXPECT_EQ(123, V_I4(&number)); + } + + // SAFEARRAY tests + var.Set(static_cast(NULL)); + EXPECT_EQ(VT_EMPTY, var.type()); + + SAFEARRAY* sa = ::SafeArrayCreateVector(VT_UI1, 0, 100); + ASSERT_TRUE(sa != NULL); + + var.Set(sa); + EXPECT_TRUE(ScopedVariant::IsLeakableVarType(var.type())); + EXPECT_EQ(VT_ARRAY | VT_UI1, var.type()); + EXPECT_EQ(sa, V_ARRAY(&var)); + // The array is destroyed in the destructor of var. +} + +} // namespace win +} // namespace base diff --git a/base/win/shortcut.cc b/base/win/shortcut.cc new file mode 100644 index 0000000000..57a93dc652 --- /dev/null +++ b/base/win/shortcut.cc @@ -0,0 +1,249 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/win/shortcut.h" + +#include +#include +#include + +#include "base/file_util.h" +#include "base/threading/thread_restrictions.h" +#include "base/win/scoped_comptr.h" +#include "base/win/win_util.h" +#include "base/win/windows_version.h" + +namespace base { +namespace win { + +namespace { + +// Initializes |i_shell_link| and |i_persist_file| (releasing them first if they +// are already initialized). +// If |shortcut| is not NULL, loads |shortcut| into |i_persist_file|. +// If any of the above steps fail, both |i_shell_link| and |i_persist_file| will +// be released. +void InitializeShortcutInterfaces( + const wchar_t* shortcut, + ScopedComPtr* i_shell_link, + ScopedComPtr* i_persist_file) { + i_shell_link->Release(); + i_persist_file->Release(); + if (FAILED(i_shell_link->CreateInstance(CLSID_ShellLink, NULL, + CLSCTX_INPROC_SERVER)) || + FAILED(i_persist_file->QueryFrom(*i_shell_link)) || + (shortcut && FAILED((*i_persist_file)->Load(shortcut, STGM_READWRITE)))) { + i_shell_link->Release(); + i_persist_file->Release(); + } +} + +} // namespace + +bool CreateOrUpdateShortcutLink(const FilePath& shortcut_path, + const ShortcutProperties& properties, + ShortcutOperation operation) { + base::ThreadRestrictions::AssertIOAllowed(); + + // A target is required unless |operation| is SHORTCUT_UPDATE_EXISTING. + if (operation != SHORTCUT_UPDATE_EXISTING && + !(properties.options & ShortcutProperties::PROPERTIES_TARGET)) { + NOTREACHED(); + return false; + } + + bool shortcut_existed = PathExists(shortcut_path); + + // Interfaces to the old shortcut when replacing an existing shortcut. + ScopedComPtr old_i_shell_link; + ScopedComPtr old_i_persist_file; + + // Interfaces to the shortcut being created/updated. + ScopedComPtr i_shell_link; + ScopedComPtr i_persist_file; + switch (operation) { + case SHORTCUT_CREATE_ALWAYS: + InitializeShortcutInterfaces(NULL, &i_shell_link, &i_persist_file); + break; + case SHORTCUT_UPDATE_EXISTING: + InitializeShortcutInterfaces(shortcut_path.value().c_str(), &i_shell_link, + &i_persist_file); + break; + case SHORTCUT_REPLACE_EXISTING: + InitializeShortcutInterfaces(shortcut_path.value().c_str(), + &old_i_shell_link, &old_i_persist_file); + // Confirm |shortcut_path| exists and is a shortcut by verifying + // |old_i_persist_file| was successfully initialized in the call above. If + // so, initialize the interfaces to begin writing a new shortcut (to + // overwrite the current one if successful). + if (old_i_persist_file.get()) + InitializeShortcutInterfaces(NULL, &i_shell_link, &i_persist_file); + break; + default: + NOTREACHED(); + } + + // Return false immediately upon failure to initialize shortcut interfaces. + if (!i_persist_file.get()) + return false; + + if ((properties.options & ShortcutProperties::PROPERTIES_TARGET) && + FAILED(i_shell_link->SetPath(properties.target.value().c_str()))) { + return false; + } + + if ((properties.options & ShortcutProperties::PROPERTIES_WORKING_DIR) && + FAILED(i_shell_link->SetWorkingDirectory( + properties.working_dir.value().c_str()))) { + return false; + } + + if (properties.options & ShortcutProperties::PROPERTIES_ARGUMENTS) { + if (FAILED(i_shell_link->SetArguments(properties.arguments.c_str()))) + return false; + } else if (old_i_persist_file.get()) { + wchar_t current_arguments[MAX_PATH] = {0}; + if (SUCCEEDED(old_i_shell_link->GetArguments(current_arguments, + MAX_PATH))) { + i_shell_link->SetArguments(current_arguments); + } + } + + if ((properties.options & ShortcutProperties::PROPERTIES_DESCRIPTION) && + FAILED(i_shell_link->SetDescription(properties.description.c_str()))) { + return false; + } + + if ((properties.options & ShortcutProperties::PROPERTIES_ICON) && + FAILED(i_shell_link->SetIconLocation(properties.icon.value().c_str(), + properties.icon_index))) { + return false; + } + + bool has_app_id = + (properties.options & ShortcutProperties::PROPERTIES_APP_ID) != 0; + bool has_dual_mode = + (properties.options & ShortcutProperties::PROPERTIES_DUAL_MODE) != 0; + if ((has_app_id || has_dual_mode) && + GetVersion() >= VERSION_WIN7) { + ScopedComPtr property_store; + if (FAILED(property_store.QueryFrom(i_shell_link)) || !property_store.get()) + return false; + + if (has_app_id && + !SetAppIdForPropertyStore(property_store, properties.app_id.c_str())) { + return false; + } + if (has_dual_mode && + !SetBooleanValueForPropertyStore(property_store, + PKEY_AppUserModel_IsDualMode, + properties.dual_mode)) { + return false; + } + } + + // Release the interfaces to the old shortcut to make sure it doesn't prevent + // overwriting it if needed. + old_i_persist_file.Release(); + old_i_shell_link.Release(); + + HRESULT result = i_persist_file->Save(shortcut_path.value().c_str(), TRUE); + + // Release the interfaces in case the SHChangeNotify call below depends on + // the operations above being fully completed. + i_persist_file.Release(); + i_shell_link.Release(); + + // If we successfully created/updated the icon, notify the shell that we have + // done so. + const bool succeeded = SUCCEEDED(result); + if (succeeded) { + if (shortcut_existed) { + // TODO(gab): SHCNE_UPDATEITEM might be sufficient here; further testing + // required. + SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL); + } else { + SHChangeNotify(SHCNE_CREATE, SHCNF_PATH, shortcut_path.value().c_str(), + NULL); + } + } + + return succeeded; +} + +bool ResolveShortcut(const FilePath& shortcut_path, + FilePath* target_path, + string16* args) { + base::ThreadRestrictions::AssertIOAllowed(); + + HRESULT result; + ScopedComPtr i_shell_link; + + // Get pointer to the IShellLink interface. + result = i_shell_link.CreateInstance(CLSID_ShellLink, NULL, + CLSCTX_INPROC_SERVER); + if (FAILED(result)) + return false; + + ScopedComPtr persist; + // Query IShellLink for the IPersistFile interface. + result = persist.QueryFrom(i_shell_link); + if (FAILED(result)) + return false; + + // Load the shell link. + result = persist->Load(shortcut_path.value().c_str(), STGM_READ); + if (FAILED(result)) + return false; + + WCHAR temp[MAX_PATH]; + if (target_path) { + // Try to find the target of a shortcut. + result = i_shell_link->Resolve(0, SLR_NO_UI | SLR_NOSEARCH); + if (FAILED(result)) + return false; + + result = i_shell_link->GetPath(temp, MAX_PATH, NULL, SLGP_UNCPRIORITY); + if (FAILED(result)) + return false; + + *target_path = FilePath(temp); + } + + if (args) { + result = i_shell_link->GetArguments(temp, MAX_PATH); + if (FAILED(result)) + return false; + + *args = string16(temp); + } + return true; +} + +bool TaskbarPinShortcutLink(const wchar_t* shortcut) { + base::ThreadRestrictions::AssertIOAllowed(); + + // "Pin to taskbar" is only supported after Win7. + if (GetVersion() < VERSION_WIN7) + return false; + + int result = reinterpret_cast(ShellExecute(NULL, L"taskbarpin", shortcut, + NULL, NULL, 0)); + return result > 32; +} + +bool TaskbarUnpinShortcutLink(const wchar_t* shortcut) { + base::ThreadRestrictions::AssertIOAllowed(); + + // "Unpin from taskbar" is only supported after Win7. + if (base::win::GetVersion() < base::win::VERSION_WIN7) + return false; + + int result = reinterpret_cast(ShellExecute(NULL, L"taskbarunpin", + shortcut, NULL, NULL, 0)); + return result > 32; +} + +} // namespace win +} // namespace base diff --git a/base/win/shortcut.h b/base/win/shortcut.h new file mode 100644 index 0000000000..c68edb9c6f --- /dev/null +++ b/base/win/shortcut.h @@ -0,0 +1,143 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_WIN_SHORTCUT_H_ +#define BASE_WIN_SHORTCUT_H_ + +#include + +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/strings/string16.h" + +namespace base { +namespace win { + +enum ShortcutOperation { + // Create a new shortcut (overwriting if necessary). + SHORTCUT_CREATE_ALWAYS = 0, + // Overwrite an existing shortcut (fails if the shortcut doesn't exist). + // If the arguments are not specified on the new shortcut, keep the old + // shortcut's arguments. + SHORTCUT_REPLACE_EXISTING, + // Update specified properties only on an existing shortcut. + SHORTCUT_UPDATE_EXISTING, +}; + +// Properties for shortcuts. Properties set will be applied to the shortcut on +// creation/update, others will be ignored. +// Callers are encouraged to use the setters provided which take care of +// setting |options| as desired. +struct ShortcutProperties { + enum IndividualProperties { + PROPERTIES_TARGET = 1 << 0, + PROPERTIES_WORKING_DIR = 1 << 1, + PROPERTIES_ARGUMENTS = 1 << 2, + PROPERTIES_DESCRIPTION = 1 << 3, + PROPERTIES_ICON = 1 << 4, + PROPERTIES_APP_ID = 1 << 5, + PROPERTIES_DUAL_MODE = 1 << 6, + }; + + ShortcutProperties() : icon_index(-1), dual_mode(false), options(0U) {} + + void set_target(const FilePath& target_in) { + target = target_in; + options |= PROPERTIES_TARGET; + } + + void set_working_dir(const FilePath& working_dir_in) { + working_dir = working_dir_in; + options |= PROPERTIES_WORKING_DIR; + } + + void set_arguments(const string16& arguments_in) { + // Size restriction as per MSDN at http://goo.gl/TJ7q5. + DCHECK(arguments_in.size() < MAX_PATH); + arguments = arguments_in; + options |= PROPERTIES_ARGUMENTS; + } + + void set_description(const string16& description_in) { + // Size restriction as per MSDN at http://goo.gl/OdNQq. + DCHECK(description_in.size() < MAX_PATH); + description = description_in; + options |= PROPERTIES_DESCRIPTION; + } + + void set_icon(const FilePath& icon_in, int icon_index_in) { + icon = icon_in; + icon_index = icon_index_in; + options |= PROPERTIES_ICON; + } + + void set_app_id(const string16& app_id_in) { + app_id = app_id_in; + options |= PROPERTIES_APP_ID; + } + + void set_dual_mode(bool dual_mode_in) { + dual_mode = dual_mode_in; + options |= PROPERTIES_DUAL_MODE; + } + + // The target to launch from this shortcut. This is mandatory when creating + // a shortcut. + FilePath target; + // The name of the working directory when launching the shortcut. + FilePath working_dir; + // The arguments to be applied to |target| when launching from this shortcut. + // The length of this string must be less than MAX_PATH. + string16 arguments; + // The localized description of the shortcut. + // The length of this string must be less than MAX_PATH. + string16 description; + // The path to the icon (can be a dll or exe, in which case |icon_index| is + // the resource id). + FilePath icon; + int icon_index; + // The app model id for the shortcut (Win7+). + string16 app_id; + // Whether this is a dual mode shortcut (Win8+). + bool dual_mode; + // Bitfield made of IndividualProperties. Properties set in |options| will be + // set on the shortcut, others will be ignored. + uint32 options; +}; + +// This method creates (or updates) a shortcut link at |shortcut_path| using the +// information given through |properties|. +// Ensure you have initialized COM before calling into this function. +// |operation|: a choice from the ShortcutOperation enum. +// If |operation| is SHORTCUT_REPLACE_EXISTING or SHORTCUT_UPDATE_EXISTING and +// |shortcut_path| does not exist, this method is a no-op and returns false. +BASE_EXPORT bool CreateOrUpdateShortcutLink( + const FilePath& shortcut_path, + const ShortcutProperties& properties, + ShortcutOperation operation); + +// Resolve Windows shortcut (.LNK file) +// This methods tries to resolve a shortcut .LNK file. The path of the shortcut +// to resolve is in |shortcut_path|. If |target_path| is not NULL, the target +// will be resolved and placed in |target_path|. If |args| is not NULL, the +// arguments will be retrieved and placed in |args|. The function returns true +// if all requested fields are found successfully. +// Callers can safely use the same variable for both |shortcut_path| and +// |target_path|. +BASE_EXPORT bool ResolveShortcut(const FilePath& shortcut_path, + FilePath* target_path, + string16* args); + +// Pins a shortcut to the Windows 7 taskbar. The shortcut file must already +// exist and be a shortcut that points to an executable. +BASE_EXPORT bool TaskbarPinShortcutLink(const wchar_t* shortcut); + +// Unpins a shortcut from the Windows 7 taskbar. The shortcut must exist and +// already be pinned to the taskbar. +BASE_EXPORT bool TaskbarUnpinShortcutLink(const wchar_t* shortcut); + +} // namespace win +} // namespace base + +#endif // BASE_WIN_SHORTCUT_H_ diff --git a/base/win/shortcut_unittest.cc b/base/win/shortcut_unittest.cc new file mode 100644 index 0000000000..b3247b6184 --- /dev/null +++ b/base/win/shortcut_unittest.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/win/shortcut.h" + +#include + +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/test/test_file_util.h" +#include "base/test/test_shortcut_win.h" +#include "base/win/scoped_com_initializer.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace win { + +namespace { + +static const char kFileContents[] = "This is a target."; +static const char kFileContents2[] = "This is another target."; + +class ShortcutTest : public testing::Test { + protected: + virtual void SetUp() OVERRIDE { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + ASSERT_TRUE(temp_dir_2_.CreateUniqueTempDir()); + + link_file_ = temp_dir_.path().Append(L"My Link.lnk"); + + // Shortcut 1's properties + { + const FilePath target_file(temp_dir_.path().Append(L"Target 1.txt")); + file_util::WriteFile(target_file, kFileContents, + arraysize(kFileContents)); + + link_properties_.set_target(target_file); + link_properties_.set_working_dir(temp_dir_.path()); + link_properties_.set_arguments(L"--magic --awesome"); + link_properties_.set_description(L"Chrome is awesome."); + link_properties_.set_icon(link_properties_.target, 4); + link_properties_.set_app_id(L"Chrome"); + link_properties_.set_dual_mode(false); + } + + // Shortcut 2's properties (all different from properties of shortcut 1). + { + const FilePath target_file_2(temp_dir_.path().Append(L"Target 2.txt")); + file_util::WriteFile(target_file_2, kFileContents2, + arraysize(kFileContents2)); + + FilePath icon_path_2; + file_util::CreateTemporaryFileInDir(temp_dir_.path(), &icon_path_2); + + link_properties_2_.set_target(target_file_2); + link_properties_2_.set_working_dir(temp_dir_2_.path()); + link_properties_2_.set_arguments(L"--super --crazy"); + link_properties_2_.set_description(L"The best in the west."); + link_properties_2_.set_icon(icon_path_2, 0); + link_properties_2_.set_app_id(L"Chrome.UserLevelCrazySuffix"); + link_properties_2_.set_dual_mode(true); + } + } + + ScopedCOMInitializer com_initializer_; + ScopedTempDir temp_dir_; + ScopedTempDir temp_dir_2_; + + // The link file to be created/updated in the shortcut tests below. + FilePath link_file_; + + // Properties for the created shortcut. + ShortcutProperties link_properties_; + + // Properties for the updated shortcut. + ShortcutProperties link_properties_2_; +}; + +} // namespace + +TEST_F(ShortcutTest, CreateAndResolveShortcut) { + ShortcutProperties only_target_properties; + only_target_properties.set_target(link_properties_.target); + + ASSERT_TRUE(CreateOrUpdateShortcutLink( + link_file_, only_target_properties, SHORTCUT_CREATE_ALWAYS)); + + FilePath resolved_name; + EXPECT_TRUE(ResolveShortcut(link_file_, &resolved_name, NULL)); + + char read_contents[arraysize(kFileContents)]; + file_util::ReadFile(resolved_name, read_contents, arraysize(read_contents)); + EXPECT_STREQ(kFileContents, read_contents); +} + +TEST_F(ShortcutTest, ResolveShortcutWithArgs) { + ASSERT_TRUE(CreateOrUpdateShortcutLink( + link_file_, link_properties_, SHORTCUT_CREATE_ALWAYS)); + + FilePath resolved_name; + string16 args; + EXPECT_TRUE(ResolveShortcut(link_file_, &resolved_name, &args)); + + char read_contents[arraysize(kFileContents)]; + file_util::ReadFile(resolved_name, read_contents, arraysize(read_contents)); + EXPECT_STREQ(kFileContents, read_contents); + EXPECT_EQ(link_properties_.arguments, args); +} + +TEST_F(ShortcutTest, CreateShortcutWithOnlySomeProperties) { + ShortcutProperties target_and_args_properties; + target_and_args_properties.set_target(link_properties_.target); + target_and_args_properties.set_arguments(link_properties_.arguments); + + ASSERT_TRUE(CreateOrUpdateShortcutLink( + link_file_, target_and_args_properties, + SHORTCUT_CREATE_ALWAYS)); + + ValidateShortcut(link_file_, target_and_args_properties); +} + +TEST_F(ShortcutTest, CreateShortcutVerifyProperties) { + ASSERT_TRUE(CreateOrUpdateShortcutLink( + link_file_, link_properties_, SHORTCUT_CREATE_ALWAYS)); + + ValidateShortcut(link_file_, link_properties_); +} + +TEST_F(ShortcutTest, UpdateShortcutVerifyProperties) { + ASSERT_TRUE(CreateOrUpdateShortcutLink( + link_file_, link_properties_, SHORTCUT_CREATE_ALWAYS)); + + ASSERT_TRUE(CreateOrUpdateShortcutLink( + link_file_, link_properties_2_, SHORTCUT_UPDATE_EXISTING)); + + ValidateShortcut(link_file_, link_properties_2_); +} + +TEST_F(ShortcutTest, UpdateShortcutUpdateOnlyTargetAndResolve) { + ASSERT_TRUE(CreateOrUpdateShortcutLink( + link_file_, link_properties_, SHORTCUT_CREATE_ALWAYS)); + + ShortcutProperties update_only_target_properties; + update_only_target_properties.set_target(link_properties_2_.target); + + ASSERT_TRUE(CreateOrUpdateShortcutLink( + link_file_, update_only_target_properties, + SHORTCUT_UPDATE_EXISTING)); + + ShortcutProperties expected_properties = link_properties_; + expected_properties.set_target(link_properties_2_.target); + ValidateShortcut(link_file_, expected_properties); + + FilePath resolved_name; + EXPECT_TRUE(ResolveShortcut(link_file_, &resolved_name, NULL)); + + char read_contents[arraysize(kFileContents2)]; + file_util::ReadFile(resolved_name, read_contents, arraysize(read_contents)); + EXPECT_STREQ(kFileContents2, read_contents); +} + +TEST_F(ShortcutTest, UpdateShortcutMakeDualMode) { + ASSERT_TRUE(CreateOrUpdateShortcutLink( + link_file_, link_properties_, SHORTCUT_CREATE_ALWAYS)); + + ShortcutProperties make_dual_mode_properties; + make_dual_mode_properties.set_dual_mode(true); + + ASSERT_TRUE(CreateOrUpdateShortcutLink( + link_file_, make_dual_mode_properties, + SHORTCUT_UPDATE_EXISTING)); + + ShortcutProperties expected_properties = link_properties_; + expected_properties.set_dual_mode(true); + ValidateShortcut(link_file_, expected_properties); +} + +TEST_F(ShortcutTest, UpdateShortcutRemoveDualMode) { + ASSERT_TRUE(CreateOrUpdateShortcutLink( + link_file_, link_properties_2_, SHORTCUT_CREATE_ALWAYS)); + + ShortcutProperties remove_dual_mode_properties; + remove_dual_mode_properties.set_dual_mode(false); + + ASSERT_TRUE(CreateOrUpdateShortcutLink( + link_file_, remove_dual_mode_properties, + SHORTCUT_UPDATE_EXISTING)); + + ShortcutProperties expected_properties = link_properties_2_; + expected_properties.set_dual_mode(false); + ValidateShortcut(link_file_, expected_properties); +} + +TEST_F(ShortcutTest, UpdateShortcutClearArguments) { + ASSERT_TRUE(CreateOrUpdateShortcutLink( + link_file_, link_properties_, SHORTCUT_CREATE_ALWAYS)); + + ShortcutProperties clear_arguments_properties; + clear_arguments_properties.set_arguments(string16()); + + ASSERT_TRUE(CreateOrUpdateShortcutLink( + link_file_, clear_arguments_properties, + SHORTCUT_UPDATE_EXISTING)); + + ShortcutProperties expected_properties = link_properties_; + expected_properties.set_arguments(string16()); + ValidateShortcut(link_file_, expected_properties); +} + +TEST_F(ShortcutTest, FailUpdateShortcutThatDoesNotExist) { + ASSERT_FALSE(CreateOrUpdateShortcutLink( + link_file_, link_properties_, SHORTCUT_UPDATE_EXISTING)); + ASSERT_FALSE(PathExists(link_file_)); +} + +TEST_F(ShortcutTest, ReplaceShortcutAllProperties) { + ASSERT_TRUE(CreateOrUpdateShortcutLink( + link_file_, link_properties_, SHORTCUT_CREATE_ALWAYS)); + + ASSERT_TRUE(CreateOrUpdateShortcutLink( + link_file_, link_properties_2_, SHORTCUT_REPLACE_EXISTING)); + + ValidateShortcut(link_file_, link_properties_2_); +} + +TEST_F(ShortcutTest, ReplaceShortcutSomeProperties) { + ASSERT_TRUE(CreateOrUpdateShortcutLink( + link_file_, link_properties_, SHORTCUT_CREATE_ALWAYS)); + + ShortcutProperties new_properties; + new_properties.set_target(link_properties_2_.target); + new_properties.set_arguments(link_properties_2_.arguments); + new_properties.set_description(link_properties_2_.description); + ASSERT_TRUE(CreateOrUpdateShortcutLink( + link_file_, new_properties, SHORTCUT_REPLACE_EXISTING)); + + // Expect only properties in |new_properties| to be set, all other properties + // should have been overwritten. + ShortcutProperties expected_properties(new_properties); + expected_properties.set_working_dir(FilePath()); + expected_properties.set_icon(FilePath(), 0); + expected_properties.set_app_id(string16()); + expected_properties.set_dual_mode(false); + ValidateShortcut(link_file_, expected_properties); +} + +TEST_F(ShortcutTest, FailReplaceShortcutThatDoesNotExist) { + ASSERT_FALSE(CreateOrUpdateShortcutLink( + link_file_, link_properties_, SHORTCUT_REPLACE_EXISTING)); + ASSERT_FALSE(PathExists(link_file_)); +} + +// Test that the old arguments remain on the replaced shortcut when not +// otherwise specified. +TEST_F(ShortcutTest, ReplaceShortcutKeepOldArguments) { + ASSERT_TRUE(CreateOrUpdateShortcutLink( + link_file_, link_properties_, SHORTCUT_CREATE_ALWAYS)); + + // Do not explicitly set the arguments. + link_properties_2_.options &= + ~ShortcutProperties::PROPERTIES_ARGUMENTS; + ASSERT_TRUE(CreateOrUpdateShortcutLink( + link_file_, link_properties_2_, SHORTCUT_REPLACE_EXISTING)); + + ShortcutProperties expected_properties(link_properties_2_); + expected_properties.set_arguments(link_properties_.arguments); + ValidateShortcut(link_file_, expected_properties); +} + +} // namespace win +} // namespace base diff --git a/base/win/startup_information.cc b/base/win/startup_information.cc new file mode 100644 index 0000000000..aff52eb794 --- /dev/null +++ b/base/win/startup_information.cc @@ -0,0 +1,109 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/win/startup_information.h" + +#include "base/logging.h" +#include "base/win/windows_version.h" + +namespace { + +typedef BOOL (WINAPI *InitializeProcThreadAttributeListFunction)( + LPPROC_THREAD_ATTRIBUTE_LIST attribute_list, + DWORD attribute_count, + DWORD flags, + PSIZE_T size); +static InitializeProcThreadAttributeListFunction + initialize_proc_thread_attribute_list; + +typedef BOOL (WINAPI *UpdateProcThreadAttributeFunction)( + LPPROC_THREAD_ATTRIBUTE_LIST attribute_list, + DWORD flags, + DWORD_PTR attribute, + PVOID value, + SIZE_T size, + PVOID previous_value, + PSIZE_T return_size); +static UpdateProcThreadAttributeFunction update_proc_thread_attribute_list; + +typedef VOID (WINAPI *DeleteProcThreadAttributeListFunction)( + LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList); +static DeleteProcThreadAttributeListFunction delete_proc_thread_attribute_list; + +} // namespace + +namespace base { +namespace win { + +StartupInformation::StartupInformation() { + memset(&startup_info_, 0, sizeof(startup_info_)); + + // Pre Windows Vista doesn't support STARTUPINFOEX. + if (base::win::GetVersion() < base::win::VERSION_VISTA) { + startup_info_.StartupInfo.cb = sizeof(STARTUPINFO); + return; + } + + startup_info_.StartupInfo.cb = sizeof(startup_info_); + + // Load the attribute API functions. + if (!initialize_proc_thread_attribute_list || + !update_proc_thread_attribute_list || + !delete_proc_thread_attribute_list) { + HMODULE module = ::GetModuleHandleW(L"kernel32.dll"); + initialize_proc_thread_attribute_list = + reinterpret_cast( + ::GetProcAddress(module, "InitializeProcThreadAttributeList")); + update_proc_thread_attribute_list = + reinterpret_cast( + ::GetProcAddress(module, "UpdateProcThreadAttribute")); + delete_proc_thread_attribute_list = + reinterpret_cast( + ::GetProcAddress(module, "DeleteProcThreadAttributeList")); + } +} + +StartupInformation::~StartupInformation() { + if (startup_info_.lpAttributeList) { + delete_proc_thread_attribute_list(startup_info_.lpAttributeList); + delete [] reinterpret_cast(startup_info_.lpAttributeList); + } +} + +bool StartupInformation::InitializeProcThreadAttributeList( + DWORD attribute_count) { + if (startup_info_.StartupInfo.cb != sizeof(startup_info_) || + startup_info_.lpAttributeList) + return false; + + SIZE_T size = 0; + initialize_proc_thread_attribute_list(NULL, attribute_count, 0, &size); + if (size == 0) + return false; + + startup_info_.lpAttributeList = + reinterpret_cast(new BYTE[size]); + if (!initialize_proc_thread_attribute_list(startup_info_.lpAttributeList, + attribute_count, 0, &size)) { + delete [] reinterpret_cast(startup_info_.lpAttributeList); + startup_info_.lpAttributeList = NULL; + return false; + } + + return true; +} + +bool StartupInformation::UpdateProcThreadAttribute( + DWORD_PTR attribute, + void* value, + size_t size) { + if (!startup_info_.lpAttributeList) + return false; + return !!update_proc_thread_attribute_list(startup_info_.lpAttributeList, 0, + attribute, value, size, NULL, NULL); +} + +} // namespace win +} // namespace base + diff --git a/base/win/startup_information.h b/base/win/startup_information.h new file mode 100644 index 0000000000..7cef81f2c8 --- /dev/null +++ b/base/win/startup_information.h @@ -0,0 +1,49 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_WIN_STARTUP_INFORMATION_H_ +#define BASE_WIN_STARTUP_INFORMATION_H_ + +#include + +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace base { +namespace win { + +// Manages the lifetime of additional attributes in STARTUPINFOEX. +class BASE_EXPORT StartupInformation { + public: + StartupInformation(); + + ~StartupInformation(); + + // Initialize the attribute list for the specified number of entries. + bool InitializeProcThreadAttributeList(DWORD attribute_count); + + // Sets one entry in the initialized attribute list. + bool UpdateProcThreadAttribute(DWORD_PTR attribute, + void* value, + size_t size); + + LPSTARTUPINFOW startup_info() { return &startup_info_.StartupInfo; } + const LPSTARTUPINFOW startup_info() const { + return const_cast(&startup_info_.StartupInfo); + } + + bool has_extended_startup_info() const { + return !!startup_info_.lpAttributeList; + } + + private: + STARTUPINFOEXW startup_info_; + DISALLOW_COPY_AND_ASSIGN(StartupInformation); +}; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_SCOPED_STARTUP_INFO_EX_H_ + diff --git a/base/win/startup_information_unittest.cc b/base/win/startup_information_unittest.cc new file mode 100644 index 0000000000..1903564d87 --- /dev/null +++ b/base/win/startup_information_unittest.cc @@ -0,0 +1,76 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +#include "base/command_line.h" +#include "base/test/multiprocess_test.h" +#include "base/win/scoped_handle.h" +#include "base/win/scoped_process_information.h" +#include "base/win/startup_information.h" +#include "base/win/windows_version.h" +#include "testing/multiprocess_func_list.h" + +const wchar_t kSectionName[] = L"EventTestSection"; +const size_t kSectionSize = 4096; + +MULTIPROCESS_TEST_MAIN(FireInheritedEvents) { + HANDLE section = ::OpenFileMappingW(PAGE_READWRITE, false, kSectionName); + HANDLE* events = reinterpret_cast(::MapViewOfFile(section, + PAGE_READWRITE, 0, 0, kSectionSize)); + // This event should not be valid because it wasn't explicitly inherited. + if (::SetEvent(events[1])) + return -1; + // This event should be valid because it was explicitly inherited. + if (!::SetEvent(events[0])) + return -1; + + return 0; +} + +class StartupInformationTest : public base::MultiProcessTest {}; + +// Verify that only the explicitly specified event is inherited. +TEST_F(StartupInformationTest, InheritStdOut) { + if (base::win::GetVersion() < base::win::VERSION_VISTA) + return; + + base::win::ScopedProcessInformation process_info; + base::win::StartupInformation startup_info; + + HANDLE section = ::CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, + PAGE_READWRITE, 0, kSectionSize, + kSectionName); + ASSERT_TRUE(section); + + HANDLE* events = reinterpret_cast(::MapViewOfFile(section, + FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, kSectionSize)); + + // Make two inheritable events. + SECURITY_ATTRIBUTES security_attributes = { sizeof(security_attributes), + NULL, true }; + events[0] = ::CreateEvent(&security_attributes, false, false, NULL); + ASSERT_TRUE(events[0]); + events[1] = ::CreateEvent(&security_attributes, false, false, NULL); + ASSERT_TRUE(events[1]); + + ASSERT_TRUE(startup_info.InitializeProcThreadAttributeList(1)); + ASSERT_TRUE(startup_info.UpdateProcThreadAttribute( + PROC_THREAD_ATTRIBUTE_HANDLE_LIST, &events[0], + sizeof(events[0]))); + + std::wstring cmd_line = + this->MakeCmdLine("FireInheritedEvents", false).GetCommandLineString(); + + ASSERT_TRUE(::CreateProcess(NULL, const_cast(cmd_line.c_str()), + NULL, NULL, true, EXTENDED_STARTUPINFO_PRESENT, + NULL, NULL, startup_info.startup_info(), + process_info.Receive())) << ::GetLastError(); + // Only the first event should be signalled + EXPECT_EQ(WAIT_OBJECT_0, ::WaitForMultipleObjects(2, events, false, + 4000)); +} + diff --git a/base/win/text_services_message_filter.cc b/base/win/text_services_message_filter.cc new file mode 100644 index 0000000000..7ce233d9fd --- /dev/null +++ b/base/win/text_services_message_filter.cc @@ -0,0 +1,82 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/win/text_services_message_filter.h" + +namespace base { +namespace win { + +TextServicesMessageFilter::TextServicesMessageFilter() + : client_id_(TF_CLIENTID_NULL), + is_initialized_(false) { +} + +TextServicesMessageFilter::~TextServicesMessageFilter() { + if (is_initialized_) + thread_mgr_->Deactivate(); +} + +bool TextServicesMessageFilter::Init() { + if (FAILED(thread_mgr_.CreateInstance(CLSID_TF_ThreadMgr))) + return false; + + if (FAILED(message_pump_.QueryFrom(thread_mgr_))) + return false; + + if (FAILED(keystroke_mgr_.QueryFrom(thread_mgr_))) + return false; + + if (FAILED(thread_mgr_->Activate(&client_id_))) + return false; + + is_initialized_ = true; + return is_initialized_; +} + +// Wraps for ITfMessagePump::PeekMessage with win32 PeekMessage signature. +// Obtains messages from application message queue. +BOOL TextServicesMessageFilter::DoPeekMessage(MSG* msg, + HWND window_handle, + UINT msg_filter_min, + UINT msg_filter_max, + UINT remove_msg) { + BOOL result = FALSE; + if (FAILED(message_pump_->PeekMessage(msg, window_handle, + msg_filter_min, msg_filter_max, + remove_msg, &result))) { + result = FALSE; + } + + return result; +} + +// Sends message to Text Service Manager. +// The message will be used to input composition text. +// Returns true if |message| was consumed by text service manager. +bool TextServicesMessageFilter::ProcessMessage(const MSG& msg) { + if (msg.message == WM_KEYDOWN) { + BOOL eaten = FALSE; + HRESULT hr = keystroke_mgr_->TestKeyDown(msg.wParam, msg.lParam, &eaten); + if (FAILED(hr) && !eaten) + return false; + eaten = FALSE; + hr = keystroke_mgr_->KeyDown(msg.wParam, msg.lParam, &eaten); + return (SUCCEEDED(hr) && !!eaten); + } + + if (msg.message == WM_KEYUP) { + BOOL eaten = FALSE; + HRESULT hr = keystroke_mgr_->TestKeyUp(msg.wParam, msg.lParam, &eaten); + if (FAILED(hr) && !eaten) + return false; + eaten = FALSE; + hr = keystroke_mgr_->KeyUp(msg.wParam, msg.lParam, &eaten); + return (SUCCEEDED(hr) && !!eaten); + } + + return false; +} + +} // namespace win +} // namespace base diff --git a/base/win/text_services_message_filter.h b/base/win/text_services_message_filter.h new file mode 100644 index 0000000000..704c1da640 --- /dev/null +++ b/base/win/text_services_message_filter.h @@ -0,0 +1,48 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_WIN_TEXT_SERVICES_MESSAGE_FILTER_H_ +#define BASE_WIN_TEXT_SERVICES_MESSAGE_FILTER_H_ + +#include +#include + +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_pump_win.h" +#include "base/win/metro.h" +#include "base/win/scoped_comptr.h" + +namespace base { +namespace win { + +// TextServicesMessageFilter extends MessageFilter with methods that are using +// Text Services Framework COM component. +class BASE_EXPORT TextServicesMessageFilter + : public base::MessagePumpForUI::MessageFilter { + public: + TextServicesMessageFilter(); + virtual ~TextServicesMessageFilter(); + virtual BOOL DoPeekMessage(MSG* msg, + HWND window_handle, + UINT msg_filter_min, + UINT msg_filter_max, + UINT remove_msg) OVERRIDE; + virtual bool ProcessMessage(const MSG& msg) OVERRIDE; + + bool Init(); + + private: + TfClientId client_id_; + bool is_initialized_; + base::win::ScopedComPtr thread_mgr_; + base::win::ScopedComPtr message_pump_; + base::win::ScopedComPtr keystroke_mgr_; + + DISALLOW_COPY_AND_ASSIGN(TextServicesMessageFilter); +}; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_TEXT_SERVICES_MESSAGE_FILTER_H_ diff --git a/base/win/win_util.cc b/base/win/win_util.cc new file mode 100644 index 0000000000..cdfff8f7ba --- /dev/null +++ b/base/win/win_util.cc @@ -0,0 +1,350 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/win/win_util.h" + +#include +#include +#include +#include // Must be before propkey. +#include +#include +#include +#include +#include +#include + +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/threading/thread_restrictions.h" +#include "base/win/registry.h" +#include "base/win/scoped_co_mem.h" +#include "base/win/scoped_handle.h" +#include "base/win/scoped_propvariant.h" +#include "base/win/windows_version.h" + +namespace { + +// Sets the value of |property_key| to |property_value| in |property_store|. +bool SetPropVariantValueForPropertyStore( + IPropertyStore* property_store, + const PROPERTYKEY& property_key, + const base::win::ScopedPropVariant& property_value) { + DCHECK(property_store); + + HRESULT result = property_store->SetValue(property_key, property_value.get()); + if (result == S_OK) + result = property_store->Commit(); + return SUCCEEDED(result); +} + +void __cdecl ForceCrashOnSigAbort(int) { + *((int*)0) = 0x1337; +} + +const wchar_t kWindows8OSKRegPath[] = + L"Software\\Classes\\CLSID\\{054AAE20-4BEA-4347-8A35-64A533254A9D}" + L"\\LocalServer32"; + +} // namespace + +namespace base { +namespace win { + +static bool g_crash_on_process_detach = false; + +#define NONCLIENTMETRICS_SIZE_PRE_VISTA \ + SIZEOF_STRUCT_WITH_SPECIFIED_LAST_MEMBER(NONCLIENTMETRICS, lfMessageFont) + +void GetNonClientMetrics(NONCLIENTMETRICS* metrics) { + DCHECK(metrics); + + static const UINT SIZEOF_NONCLIENTMETRICS = + (base::win::GetVersion() >= base::win::VERSION_VISTA) ? + sizeof(NONCLIENTMETRICS) : NONCLIENTMETRICS_SIZE_PRE_VISTA; + metrics->cbSize = SIZEOF_NONCLIENTMETRICS; + const bool success = !!SystemParametersInfo(SPI_GETNONCLIENTMETRICS, + SIZEOF_NONCLIENTMETRICS, metrics, + 0); + DCHECK(success); +} + +bool GetUserSidString(std::wstring* user_sid) { + // Get the current token. + HANDLE token = NULL; + if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &token)) + return false; + base::win::ScopedHandle token_scoped(token); + + DWORD size = sizeof(TOKEN_USER) + SECURITY_MAX_SID_SIZE; + scoped_ptr user_bytes(new BYTE[size]); + TOKEN_USER* user = reinterpret_cast(user_bytes.get()); + + if (!::GetTokenInformation(token, TokenUser, user, size, &size)) + return false; + + if (!user->User.Sid) + return false; + + // Convert the data to a string. + wchar_t* sid_string; + if (!::ConvertSidToStringSid(user->User.Sid, &sid_string)) + return false; + + *user_sid = sid_string; + + ::LocalFree(sid_string); + + return true; +} + +bool IsShiftPressed() { + return (::GetKeyState(VK_SHIFT) & 0x8000) == 0x8000; +} + +bool IsCtrlPressed() { + return (::GetKeyState(VK_CONTROL) & 0x8000) == 0x8000; +} + +bool IsAltPressed() { + return (::GetKeyState(VK_MENU) & 0x8000) == 0x8000; +} + +bool UserAccountControlIsEnabled() { + // This can be slow if Windows ends up going to disk. Should watch this key + // for changes and only read it once, preferably on the file thread. + // http://code.google.com/p/chromium/issues/detail?id=61644 + base::ThreadRestrictions::ScopedAllowIO allow_io; + + base::win::RegKey key(HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System", + KEY_READ); + DWORD uac_enabled; + if (key.ReadValueDW(L"EnableLUA", &uac_enabled) != ERROR_SUCCESS) + return true; + // Users can set the EnableLUA value to something arbitrary, like 2, which + // Vista will treat as UAC enabled, so we make sure it is not set to 0. + return (uac_enabled != 0); +} + +bool SetBooleanValueForPropertyStore(IPropertyStore* property_store, + const PROPERTYKEY& property_key, + bool property_bool_value) { + ScopedPropVariant property_value; + if (FAILED(InitPropVariantFromBoolean(property_bool_value, + property_value.Receive()))) { + return false; + } + + return SetPropVariantValueForPropertyStore(property_store, + property_key, + property_value); +} + +bool SetStringValueForPropertyStore(IPropertyStore* property_store, + const PROPERTYKEY& property_key, + const wchar_t* property_string_value) { + ScopedPropVariant property_value; + if (FAILED(InitPropVariantFromString(property_string_value, + property_value.Receive()))) { + return false; + } + + return SetPropVariantValueForPropertyStore(property_store, + property_key, + property_value); +} + +bool SetAppIdForPropertyStore(IPropertyStore* property_store, + const wchar_t* app_id) { + // App id should be less than 64 chars and contain no space. And recommended + // format is CompanyName.ProductName[.SubProduct.ProductNumber]. + // See http://msdn.microsoft.com/en-us/library/dd378459%28VS.85%29.aspx + DCHECK(lstrlen(app_id) < 64 && wcschr(app_id, L' ') == NULL); + + return SetStringValueForPropertyStore(property_store, + PKEY_AppUserModel_ID, + app_id); +} + +static const char16 kAutoRunKeyPath[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Run"; + +bool AddCommandToAutoRun(HKEY root_key, const string16& name, + const string16& command) { + base::win::RegKey autorun_key(root_key, kAutoRunKeyPath, KEY_SET_VALUE); + return (autorun_key.WriteValue(name.c_str(), command.c_str()) == + ERROR_SUCCESS); +} + +bool RemoveCommandFromAutoRun(HKEY root_key, const string16& name) { + base::win::RegKey autorun_key(root_key, kAutoRunKeyPath, KEY_SET_VALUE); + return (autorun_key.DeleteValue(name.c_str()) == ERROR_SUCCESS); +} + +bool ReadCommandFromAutoRun(HKEY root_key, + const string16& name, + string16* command) { + base::win::RegKey autorun_key(root_key, kAutoRunKeyPath, KEY_QUERY_VALUE); + return (autorun_key.ReadValue(name.c_str(), command) == ERROR_SUCCESS); +} + +void SetShouldCrashOnProcessDetach(bool crash) { + g_crash_on_process_detach = crash; +} + +bool ShouldCrashOnProcessDetach() { + return g_crash_on_process_detach; +} + +void SetAbortBehaviorForCrashReporting() { + // Prevent CRT's abort code from prompting a dialog or trying to "report" it. + // Disabling the _CALL_REPORTFAULT behavior is important since otherwise it + // has the sideffect of clearing our exception filter, which means we + // don't get any crash. + _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); + + // Set a SIGABRT handler for good measure. We will crash even if the default + // is left in place, however this allows us to crash earlier. And it also + // lets us crash in response to code which might directly call raise(SIGABRT) + signal(SIGABRT, ForceCrashOnSigAbort); +} + +bool IsTouchEnabledDevice() { + if (base::win::GetVersion() < base::win::VERSION_WIN7) + return false; + const int kMultiTouch = NID_INTEGRATED_TOUCH | NID_MULTI_INPUT | NID_READY; + int sm = GetSystemMetrics(SM_DIGITIZER); + if ((sm & kMultiTouch) == kMultiTouch) { + return true; + } + return false; +} + +bool DisplayVirtualKeyboard() { + if (base::win::GetVersion() < base::win::VERSION_WIN8) + return false; + + static base::LazyInstance::Leaky osk_path = + LAZY_INSTANCE_INITIALIZER; + + if (osk_path.Get().empty()) { + // We need to launch TabTip.exe from the location specified under the + // LocalServer32 key for the {{054AAE20-4BEA-4347-8A35-64A533254A9D}} + // CLSID. + // TabTip.exe is typically found at + // c:\program files\common files\microsoft shared\ink on English Windows. + // We don't want to launch TabTip.exe from + // c:\program files (x86)\common files\microsoft shared\ink. This path is + // normally found on 64 bit Windows. + base::win::RegKey key(HKEY_LOCAL_MACHINE, + kWindows8OSKRegPath, + KEY_READ | KEY_WOW64_64KEY); + DWORD osk_path_length = 1024; + if (key.ReadValue(NULL, + WriteInto(&osk_path.Get(), osk_path_length), + &osk_path_length, + NULL) != ERROR_SUCCESS) { + DLOG(WARNING) << "Failed to read on screen keyboard path from registry"; + return false; + } + size_t common_program_files_offset = + osk_path.Get().find(L"%CommonProgramFiles%"); + // Typically the path to TabTip.exe read from the registry will start with + // %CommonProgramFiles% which needs to be replaced with the corrsponding + // expanded string. + // If the path does not begin with %CommonProgramFiles% we use it as is. + if (common_program_files_offset != string16::npos) { + // Preserve the beginning quote in the path. + osk_path.Get().erase(common_program_files_offset, + wcslen(L"%CommonProgramFiles%")); + // The path read from the registry contains the %CommonProgramFiles% + // environment variable prefix. On 64 bit Windows the SHGetKnownFolderPath + // function returns the common program files path with the X86 suffix for + // the FOLDERID_ProgramFilesCommon value. + // To get the correct path to TabTip.exe we first read the environment + // variable CommonProgramW6432 which points to the desired common + // files path. Failing that we fallback to the SHGetKnownFolderPath API. + + // We then replace the %CommonProgramFiles% value with the actual common + // files path found in the process. + string16 common_program_files_path; + scoped_ptr common_program_files_wow6432; + DWORD buffer_size = + GetEnvironmentVariable(L"CommonProgramW6432", NULL, 0); + if (buffer_size) { + common_program_files_wow6432.reset(new wchar_t[buffer_size]); + GetEnvironmentVariable(L"CommonProgramW6432", + common_program_files_wow6432.get(), + buffer_size); + common_program_files_path = common_program_files_wow6432.get(); + DCHECK(!common_program_files_path.empty()); + } else { + base::win::ScopedCoMem common_program_files; + if (FAILED(SHGetKnownFolderPath(FOLDERID_ProgramFilesCommon, 0, NULL, + &common_program_files))) { + return false; + } + common_program_files_path = common_program_files; + } + + osk_path.Get().insert(1, common_program_files_path); + } + } + + HINSTANCE ret = ::ShellExecuteW(NULL, + L"", + osk_path.Get().c_str(), + NULL, + NULL, + SW_SHOW); + return reinterpret_cast(ret) > 32; +} + +bool DismissVirtualKeyboard() { + if (base::win::GetVersion() < base::win::VERSION_WIN8) + return false; + + // We dismiss the virtual keyboard by generating the ESC keystroke + // programmatically. + const wchar_t kOSKClassName[] = L"IPTip_Main_Window"; + HWND osk = ::FindWindow(kOSKClassName, NULL); + if (::IsWindow(osk) && ::IsWindowEnabled(osk)) { + PostMessage(osk, WM_SYSCOMMAND, SC_CLOSE, 0); + return true; + } + return false; +} + +} // namespace win +} // namespace base + +#ifdef _MSC_VER + +// There are optimizer bugs in x86 VS2012 pre-Update 1. +#if _MSC_VER == 1700 && defined _M_IX86 && _MSC_FULL_VER < 170051106 + +#pragma message("Relevant defines:") +#define __STR2__(x) #x +#define __STR1__(x) __STR2__(x) +#define __PPOUT__(x) "#define " #x " " __STR1__(x) +#if defined(_M_IX86) + #pragma message(__PPOUT__(_M_IX86)) +#endif +#if defined(_M_X64) + #pragma message(__PPOUT__(_M_X64)) +#endif +#if defined(_MSC_FULL_VER) + #pragma message(__PPOUT__(_MSC_FULL_VER)) +#endif + +#pragma message("Visual Studio 2012 x86 must be updated to at least Update 1") +#error Must install Update 1 to Visual Studio 2012. +#endif + +#endif // _MSC_VER + diff --git a/base/win/win_util.h b/base/win/win_util.h new file mode 100644 index 0000000000..7577ab8810 --- /dev/null +++ b/base/win/win_util.h @@ -0,0 +1,127 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// ============================================================================= +// PLEASE READ +// +// In general, you should not be adding stuff to this file. +// +// - If your thing is only used in one place, just put it in a reasonable +// location in or near that one place. It's nice you want people to be able +// to re-use your function, but realistically, if it hasn't been necessary +// before after so many years of development, it's probably not going to be +// used in other places in the future unless you know of them now. +// +// - If your thing is used by multiple callers and is UI-related, it should +// probably be in app/win/ instead. Try to put it in the most specific file +// possible (avoiding the *_util files when practical). +// +// ============================================================================= + +#ifndef BASE_WIN_WIN_UTIL_H_ +#define BASE_WIN_WIN_UTIL_H_ + +#include + +#include + +#include "base/base_export.h" +#include "base/strings/string16.h" + +struct IPropertyStore; +struct _tagpropertykey; +typedef _tagpropertykey PROPERTYKEY; + +namespace base { +namespace win { + +BASE_EXPORT void GetNonClientMetrics(NONCLIENTMETRICS* metrics); + +// Returns the string representing the current user sid. +BASE_EXPORT bool GetUserSidString(std::wstring* user_sid); + +// Returns true if the shift key is currently pressed. +BASE_EXPORT bool IsShiftPressed(); + +// Returns true if the ctrl key is currently pressed. +BASE_EXPORT bool IsCtrlPressed(); + +// Returns true if the alt key is currently pressed. +BASE_EXPORT bool IsAltPressed(); + +// Returns false if user account control (UAC) has been disabled with the +// EnableLUA registry flag. Returns true if user account control is enabled. +// NOTE: The EnableLUA registry flag, which is ignored on Windows XP +// machines, might still exist and be set to 0 (UAC disabled), in which case +// this function will return false. You should therefore check this flag only +// if the OS is Vista or later. +BASE_EXPORT bool UserAccountControlIsEnabled(); + +// Sets the boolean value for a given key in given IPropertyStore. +BASE_EXPORT bool SetBooleanValueForPropertyStore( + IPropertyStore* property_store, + const PROPERTYKEY& property_key, + bool property_bool_value); + +// Sets the string value for a given key in given IPropertyStore. +BASE_EXPORT bool SetStringValueForPropertyStore( + IPropertyStore* property_store, + const PROPERTYKEY& property_key, + const wchar_t* property_string_value); + +// Sets the application id in given IPropertyStore. The function is intended +// for tagging application/chromium shortcut, browser window and jump list for +// Win7. +BASE_EXPORT bool SetAppIdForPropertyStore(IPropertyStore* property_store, + const wchar_t* app_id); + +// Adds the specified |command| using the specified |name| to the AutoRun key. +// |root_key| could be HKCU or HKLM or the root of any user hive. +BASE_EXPORT bool AddCommandToAutoRun(HKEY root_key, const string16& name, + const string16& command); +// Removes the command specified by |name| from the AutoRun key. |root_key| +// could be HKCU or HKLM or the root of any user hive. +BASE_EXPORT bool RemoveCommandFromAutoRun(HKEY root_key, const string16& name); + +// Reads the command specified by |name| from the AutoRun key. |root_key| +// could be HKCU or HKLM or the root of any user hive. Used for unit-tests. +BASE_EXPORT bool ReadCommandFromAutoRun(HKEY root_key, + const string16& name, + string16* command); + +// Sets whether to crash the process during exit. This is inspected by DLLMain +// and used to intercept unexpected terminations of the process (via calls to +// exit(), abort(), _exit(), ExitProcess()) and convert them into crashes. +// Note that not all mechanisms for terminating the process are covered by +// this. In particular, TerminateProcess() is not caught. +BASE_EXPORT void SetShouldCrashOnProcessDetach(bool crash); +BASE_EXPORT bool ShouldCrashOnProcessDetach(); + +// Adjusts the abort behavior so that crash reports can be generated when the +// process is aborted. +BASE_EXPORT void SetAbortBehaviorForCrashReporting(); + +// A touch enabled device by this definition is something that has +// integrated multi-touch ready to use and has Windows version > Windows7. +BASE_EXPORT bool IsTouchEnabledDevice(); + +// Get the size of a struct up to and including the specified member. +// This is necessary to set compatible struct sizes for different versions +// of certain Windows APIs (e.g. SystemParametersInfo). +#define SIZEOF_STRUCT_WITH_SPECIFIED_LAST_MEMBER(struct_name, member) \ + offsetof(struct_name, member) + \ + (sizeof static_cast(NULL)->member) + +// Displays the on screen keyboard on Windows 8 and above. Returns true on +// success. +BASE_EXPORT bool DisplayVirtualKeyboard(); + +// Dismisses the on screen keyboard if it is being displayed on Windows 8 and. +// above. Returns true on success. +BASE_EXPORT bool DismissVirtualKeyboard(); + +} // namespace win +} // namespace base + +#endif // BASE_WIN_WIN_UTIL_H_ diff --git a/base/win/win_util_unittest.cc b/base/win/win_util_unittest.cc new file mode 100644 index 0000000000..11536a9e82 --- /dev/null +++ b/base/win/win_util_unittest.cc @@ -0,0 +1,58 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/basictypes.h" +#include "base/strings/string_util.h" +#include "base/win/win_util.h" +#include "base/win/windows_version.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace win { + +namespace { + +// Saves the current thread's locale ID when initialized, and restores it when +// the instance is going out of scope. +class ThreadLocaleSaver { + public: + ThreadLocaleSaver() : original_locale_id_(GetThreadLocale()) {} + ~ThreadLocaleSaver() { SetThreadLocale(original_locale_id_); } + + private: + LCID original_locale_id_; + + DISALLOW_COPY_AND_ASSIGN(ThreadLocaleSaver); +}; + +} // namespace + +// The test is somewhat silly, because the Vista bots some have UAC enabled +// and some have it disabled. At least we check that it does not crash. +TEST(BaseWinUtilTest, TestIsUACEnabled) { + if (GetVersion() >= base::win::VERSION_VISTA) { + UserAccountControlIsEnabled(); + } else { + EXPECT_TRUE(UserAccountControlIsEnabled()); + } +} + +TEST(BaseWinUtilTest, TestGetUserSidString) { + std::wstring user_sid; + EXPECT_TRUE(GetUserSidString(&user_sid)); + EXPECT_TRUE(!user_sid.empty()); +} + +TEST(BaseWinUtilTest, TestGetNonClientMetrics) { + NONCLIENTMETRICS metrics = {0}; + GetNonClientMetrics(&metrics); + EXPECT_TRUE(metrics.cbSize > 0); + EXPECT_TRUE(metrics.iScrollWidth > 0); + EXPECT_TRUE(metrics.iScrollHeight > 0); +} + +} // namespace win +} // namespace base diff --git a/base/win/windows_version.cc b/base/win/windows_version.cc new file mode 100644 index 0000000000..9564d0bed8 --- /dev/null +++ b/base/win/windows_version.cc @@ -0,0 +1,111 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/win/windows_version.h" + +#include + +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "base/win/registry.h" + +namespace base { +namespace win { + +// static +OSInfo* OSInfo::GetInstance() { + // Note: we don't use the Singleton class because it depends on AtExitManager, + // and it's convenient for other modules to use this classs without it. This + // pattern is copied from gurl.cc. + static OSInfo* info; + if (!info) { + OSInfo* new_info = new OSInfo(); + if (InterlockedCompareExchangePointer( + reinterpret_cast(&info), new_info, NULL)) { + delete new_info; + } + } + return info; +} + +OSInfo::OSInfo() + : version_(VERSION_PRE_XP), + architecture_(OTHER_ARCHITECTURE), + wow64_status_(GetWOW64StatusForProcess(GetCurrentProcess())) { + OSVERSIONINFOEX version_info = { sizeof version_info }; + GetVersionEx(reinterpret_cast(&version_info)); + version_number_.major = version_info.dwMajorVersion; + version_number_.minor = version_info.dwMinorVersion; + version_number_.build = version_info.dwBuildNumber; + if ((version_number_.major == 5) && (version_number_.minor > 0)) { + // Treat XP Pro x64, Home Server, and Server 2003 R2 as Server 2003. + version_ = (version_number_.minor == 1) ? VERSION_XP : VERSION_SERVER_2003; + } else if (version_number_.major == 6) { + switch (version_number_.minor) { + case 0: + // Treat Windows Server 2008 the same as Windows Vista. + version_ = VERSION_VISTA; + break; + case 1: + // Treat Windows Server 2008 R2 the same as Windows 7. + version_ = VERSION_WIN7; + break; + default: + DCHECK_EQ(version_number_.minor, 2); + // Treat Windows Server 2012 the same as Windows 8. + version_ = VERSION_WIN8; + break; + } + } else if (version_number_.major > 6) { + NOTREACHED(); + version_ = VERSION_WIN_LAST; + } + service_pack_.major = version_info.wServicePackMajor; + service_pack_.minor = version_info.wServicePackMinor; + + SYSTEM_INFO system_info = { 0 }; + GetNativeSystemInfo(&system_info); + switch (system_info.wProcessorArchitecture) { + case PROCESSOR_ARCHITECTURE_INTEL: architecture_ = X86_ARCHITECTURE; break; + case PROCESSOR_ARCHITECTURE_AMD64: architecture_ = X64_ARCHITECTURE; break; + case PROCESSOR_ARCHITECTURE_IA64: architecture_ = IA64_ARCHITECTURE; break; + } + processors_ = system_info.dwNumberOfProcessors; + allocation_granularity_ = system_info.dwAllocationGranularity; +} + +OSInfo::~OSInfo() { +} + +std::string OSInfo::processor_model_name() { + if (processor_model_name_.empty()) { + const wchar_t kProcessorNameString[] = + L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"; + base::win::RegKey key(HKEY_LOCAL_MACHINE, kProcessorNameString, KEY_READ); + string16 value; + key.ReadValue(L"ProcessorNameString", &value); + processor_model_name_ = UTF16ToUTF8(value); + } + return processor_model_name_; +} + +// static +OSInfo::WOW64Status OSInfo::GetWOW64StatusForProcess(HANDLE process_handle) { + typedef BOOL (WINAPI* IsWow64ProcessFunc)(HANDLE, PBOOL); + IsWow64ProcessFunc is_wow64_process = reinterpret_cast( + GetProcAddress(GetModuleHandle(L"kernel32.dll"), "IsWow64Process")); + if (!is_wow64_process) + return WOW64_DISABLED; + BOOL is_wow64 = FALSE; + if (!(*is_wow64_process)(process_handle, &is_wow64)) + return WOW64_UNKNOWN; + return is_wow64 ? WOW64_ENABLED : WOW64_DISABLED; +} + +Version GetVersion() { + return OSInfo::GetInstance()->version(); +} + +} // namespace win +} // namespace base diff --git a/base/win/windows_version.h b/base/win/windows_version.h new file mode 100644 index 0000000000..d466dad98e --- /dev/null +++ b/base/win/windows_version.h @@ -0,0 +1,110 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_WIN_WINDOWS_VERSION_H_ +#define BASE_WIN_WINDOWS_VERSION_H_ + +#include + +#include "base/base_export.h" +#include "base/basictypes.h" + +typedef void* HANDLE; + +namespace base { +namespace win { + +// The running version of Windows. This is declared outside OSInfo for +// syntactic sugar reasons; see the declaration of GetVersion() below. +// NOTE: Keep these in order so callers can do things like +// "if (base::win::GetVersion() >= base::win::VERSION_VISTA) ...". +enum Version { + VERSION_PRE_XP = 0, // Not supported. + VERSION_XP, + VERSION_SERVER_2003, // Also includes XP Pro x64 and Server 2003 R2. + VERSION_VISTA, // Also includes Windows Server 2008. + VERSION_WIN7, // Also includes Windows Server 2008 R2. + VERSION_WIN8, // Also includes Windows Server 2012. + VERSION_WIN_LAST, // Indicates error condition. +}; + +// A singleton that can be used to query various pieces of information about the +// OS and process state. Note that this doesn't use the base Singleton class, so +// it can be used without an AtExitManager. +class BASE_EXPORT OSInfo { + public: + struct VersionNumber { + int major; + int minor; + int build; + }; + + struct ServicePack { + int major; + int minor; + }; + + // The processor architecture this copy of Windows natively uses. For + // example, given an x64-capable processor, we have three possibilities: + // 32-bit Chrome running on 32-bit Windows: X86_ARCHITECTURE + // 32-bit Chrome running on 64-bit Windows via WOW64: X64_ARCHITECTURE + // 64-bit Chrome running on 64-bit Windows: X64_ARCHITECTURE + enum WindowsArchitecture { + X86_ARCHITECTURE, + X64_ARCHITECTURE, + IA64_ARCHITECTURE, + OTHER_ARCHITECTURE, + }; + + // Whether a process is running under WOW64 (the wrapper that allows 32-bit + // processes to run on 64-bit versions of Windows). This will return + // WOW64_DISABLED for both "32-bit Chrome on 32-bit Windows" and "64-bit + // Chrome on 64-bit Windows". WOW64_UNKNOWN means "an error occurred", e.g. + // the process does not have sufficient access rights to determine this. + enum WOW64Status { + WOW64_DISABLED, + WOW64_ENABLED, + WOW64_UNKNOWN, + }; + + static OSInfo* GetInstance(); + + Version version() const { return version_; } + // The next two functions return arrays of values, [major, minor(, build)]. + VersionNumber version_number() const { return version_number_; } + ServicePack service_pack() const { return service_pack_; } + WindowsArchitecture architecture() const { return architecture_; } + int processors() const { return processors_; } + size_t allocation_granularity() const { return allocation_granularity_; } + WOW64Status wow64_status() const { return wow64_status_; } + std::string processor_model_name(); + + // Like wow64_status(), but for the supplied handle instead of the current + // process. This doesn't touch member state, so you can bypass the singleton. + static WOW64Status GetWOW64StatusForProcess(HANDLE process_handle); + + private: + OSInfo(); + ~OSInfo(); + + Version version_; + VersionNumber version_number_; + ServicePack service_pack_; + WindowsArchitecture architecture_; + int processors_; + size_t allocation_granularity_; + WOW64Status wow64_status_; + std::string processor_model_name_; + + DISALLOW_COPY_AND_ASSIGN(OSInfo); +}; + +// Because this is by far the most commonly-requested value from the above +// singleton, we add a global-scope accessor here as syntactic sugar. +BASE_EXPORT Version GetVersion(); + +} // namespace win +} // namespace base + +#endif // BASE_WIN_WINDOWS_VERSION_H_ diff --git a/base/win/wrapped_window_proc.cc b/base/win/wrapped_window_proc.cc new file mode 100644 index 0000000000..61b79eda88 --- /dev/null +++ b/base/win/wrapped_window_proc.cc @@ -0,0 +1,63 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/win/wrapped_window_proc.h" + +#include "base/atomicops.h" +#include "base/logging.h" +#include "base/process/memory.h" + +namespace { + +base::win::WinProcExceptionFilter s_exception_filter = NULL; + +} // namespace. + +namespace base { +namespace win { + +WinProcExceptionFilter SetWinProcExceptionFilter( + WinProcExceptionFilter filter) { + subtle::AtomicWord rv = subtle::NoBarrier_AtomicExchange( + reinterpret_cast(&s_exception_filter), + reinterpret_cast(filter)); + return reinterpret_cast(rv); +} + +int CallExceptionFilter(EXCEPTION_POINTERS* info) { + return s_exception_filter ? s_exception_filter(info) : + EXCEPTION_CONTINUE_SEARCH; +} + +BASE_EXPORT void InitializeWindowClass( + const char16* class_name, + WNDPROC window_proc, + UINT style, + int class_extra, + int window_extra, + HCURSOR cursor, + HBRUSH background, + const char16* menu_name, + HICON large_icon, + HICON small_icon, + WNDCLASSEX* class_out) { + class_out->cbSize = sizeof(WNDCLASSEX); + class_out->style = style; + class_out->lpfnWndProc = window_proc; + class_out->cbClsExtra = class_extra; + class_out->cbWndExtra = window_extra; + class_out->hInstance = base::GetModuleFromAddress(window_proc); + class_out->hIcon = large_icon; + class_out->hCursor = cursor; + class_out->hbrBackground = background; + class_out->lpszMenuName = menu_name; + class_out->lpszClassName = class_name; + class_out->hIconSm = small_icon; + + // Check if |window_proc| is valid. + DCHECK(class_out->hInstance != NULL); +} + +} // namespace win +} // namespace base diff --git a/base/win/wrapped_window_proc.h b/base/win/wrapped_window_proc.h new file mode 100644 index 0000000000..d464a9c279 --- /dev/null +++ b/base/win/wrapped_window_proc.h @@ -0,0 +1,85 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Provides a way to handle exceptions that happen while a WindowProc is +// running. The behavior of exceptions generated inside a WindowProc is OS +// dependent, but it is possible that the OS just ignores the exception and +// continues execution, which leads to unpredictable behavior for Chrome. + +#ifndef BASE_WIN_WRAPPED_WINDOW_PROC_H_ +#define BASE_WIN_WRAPPED_WINDOW_PROC_H_ + +#include + +#include "base/base_export.h" +#include "base/strings/string16.h" + +namespace base { +namespace win { + +// An exception filter for a WindowProc. The return value determines how the +// exception should be handled, following standard SEH rules. However, the +// expected behavior for this function is to not return, instead of returning +// EXCEPTION_EXECUTE_HANDLER or similar, given that in general we are not +// prepared to handle exceptions. +typedef int (__cdecl *WinProcExceptionFilter)(EXCEPTION_POINTERS* info); + +// Sets the filter to deal with exceptions inside a WindowProc. Returns the old +// exception filter, if any. +// This function should be called before any window is created. +BASE_EXPORT WinProcExceptionFilter SetWinProcExceptionFilter( + WinProcExceptionFilter filter); + +// Calls the registered exception filter. +BASE_EXPORT int CallExceptionFilter(EXCEPTION_POINTERS* info); + +// Initializes the WNDCLASSEX structure |*class_out| to be passed to +// RegisterClassEx() making sure that it is associated with the module +// containing the window procedure. +BASE_EXPORT void InitializeWindowClass( + const char16* class_name, + WNDPROC window_proc, + UINT style, + int class_extra, + int window_extra, + HCURSOR cursor, + HBRUSH background, + const char16* menu_name, + HICON large_icon, + HICON small_icon, + WNDCLASSEX* class_out); + +// Wrapper that supplies a standard exception frame for the provided WindowProc. +// The normal usage is something like this: +// +// LRESULT CALLBACK MyWinProc(HWND hwnd, UINT message, +// WPARAM wparam, LPARAM lparam) { +// // Do Something. +// } +// +// ... +// +// WNDCLASSEX wc = {0}; +// wc.lpfnWndProc = WrappedWindowProc; +// wc.lpszClassName = class_name; +// ... +// RegisterClassEx(&wc); +// +// CreateWindowW(class_name, window_name, ... +// +template +LRESULT CALLBACK WrappedWindowProc(HWND hwnd, UINT message, + WPARAM wparam, LPARAM lparam) { + LRESULT rv = 0; + __try { + rv = proc(hwnd, message, wparam, lparam); + } __except(CallExceptionFilter(GetExceptionInformation())) { + } + return rv; +} + +} // namespace win +} // namespace base + +#endif // BASE_WIN_WRAPPED_WINDOW_PROC_H_ diff --git a/base/win/wrapped_window_proc_unittest.cc b/base/win/wrapped_window_proc_unittest.cc new file mode 100644 index 0000000000..161c913481 --- /dev/null +++ b/base/win/wrapped_window_proc_unittest.cc @@ -0,0 +1,79 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/message_loop/message_loop.h" +#include "base/win/wrapped_window_proc.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +DWORD kExceptionCode = 12345; +WPARAM kCrashMsg = 98765; + +// A trivial WindowProc that generates an exception. +LRESULT CALLBACK TestWindowProc(HWND hwnd, UINT message, + WPARAM wparam, LPARAM lparam) { + if (message == kCrashMsg) + RaiseException(kExceptionCode, 0, 0, NULL); + return DefWindowProc(hwnd, message, wparam, lparam); +} + +// This class implements an exception filter that can be queried about a past +// exception. +class TestWrappedExceptionFiter { + public: + TestWrappedExceptionFiter() : called_(false) { + EXPECT_FALSE(s_filter_); + s_filter_ = this; + } + + ~TestWrappedExceptionFiter() { + EXPECT_EQ(s_filter_, this); + s_filter_ = NULL; + } + + bool called() { + return called_; + } + + // The actual exception filter just records the exception. + static int Filter(EXCEPTION_POINTERS* info) { + EXPECT_FALSE(s_filter_->called_); + if (info->ExceptionRecord->ExceptionCode == kExceptionCode) + s_filter_->called_ = true; + return EXCEPTION_EXECUTE_HANDLER; + } + + private: + bool called_; + static TestWrappedExceptionFiter* s_filter_; +}; +TestWrappedExceptionFiter* TestWrappedExceptionFiter::s_filter_ = NULL; + +} // namespace. + +TEST(WrappedWindowProc, CatchesExceptions) { + HINSTANCE hinst = GetModuleHandle(NULL); + std::wstring class_name(L"TestClass"); + + WNDCLASS wc = {0}; + wc.lpfnWndProc = base::win::WrappedWindowProc; + wc.hInstance = hinst; + wc.lpszClassName = class_name.c_str(); + RegisterClass(&wc); + + HWND window = CreateWindow(class_name.c_str(), 0, 0, 0, 0, 0, 0, HWND_MESSAGE, + 0, hinst, 0); + ASSERT_TRUE(window); + + // Before generating the exception we make sure that the filter will see it. + TestWrappedExceptionFiter wrapper; + base::win::WinProcExceptionFilter old_filter = + base::win::SetWinProcExceptionFilter(TestWrappedExceptionFiter::Filter); + + SendMessage(window, kCrashMsg, 0, 0); + EXPECT_TRUE(wrapper.called()); + + base::win::SetWinProcExceptionFilter(old_filter); +} diff --git a/build/OWNERS b/build/OWNERS new file mode 100644 index 0000000000..72e8ffc0db --- /dev/null +++ b/build/OWNERS @@ -0,0 +1 @@ +* diff --git a/build/README.chromium b/build/README.chromium new file mode 100644 index 0000000000..012df35c7a --- /dev/null +++ b/build/README.chromium @@ -0,0 +1,15 @@ +List of property sheets to be included by projects: + common.vsprops + Not used anymore. No-op. Kept for compatibility with current projects. + + debug.vsprops + Enables debug settings. Must be included directly in Debug configuration. Includes internal\essential.vsprops. + + external_code.vsprops + Contains settings made to simplify usage of external (non-Google) code. It relaxes the warning levels. Should be included after debug.vsprops or release.vsprops to override their settings. + + output_dll_copy.rules + Run to enable automatic copy of DLL when they are as an input file in a vcproj project. + + release.vsprops + Enables release settings. Must be included directly in Release configuration. Includes internal\essential.vsprops. Also includes "internal\release_impl$(CHROME_BUILD_TYPE).vsprops". So the behavior is dependant on the CHROME_BUILD_TYPE environment variable. diff --git a/build/all.gyp b/build/all.gyp new file mode 100644 index 0000000000..6c6aee993a --- /dev/null +++ b/build/all.gyp @@ -0,0 +1,908 @@ +# Copyright (c) 2012 The Chromium 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': 'All', + 'type': 'none', + 'xcode_create_dependents_test_runner': 1, + 'dependencies': [ + 'some.gyp:*', + '../base/base.gyp:*', + '../chrome/chrome.gyp:*', + '../content/content.gyp:*', + '../crypto/crypto.gyp:*', + '../media/media.gyp:*', + '../net/net.gyp:*', + '../sdch/sdch.gyp:*', + '../sql/sql.gyp:*', + '../sync/sync.gyp:*', + '../testing/gmock.gyp:*', + '../testing/gtest.gyp:*', + '../third_party/icu/icu.gyp:*', + '../third_party/libxml/libxml.gyp:*', + '../third_party/sqlite/sqlite.gyp:*', + '../third_party/zlib/zlib.gyp:*', + '../ui/snapshot/snapshot.gyp:*', + '../ui/ui.gyp:*', + '../url/url.gyp:*', + ], + 'conditions': [ + ['OS!="ios"', { + 'dependencies': [ + '../cc/cc_tests.gyp:*', + '../components/components.gyp:*', + '../device/bluetooth/bluetooth.gyp:*', + '../device/device_tests.gyp:*', + '../device/usb/usb.gyp:*', + '../gpu/gpu.gyp:*', + '../gpu/tools/tools.gyp:*', + '../ipc/ipc.gyp:*', + '../jingle/jingle.gyp:*', + '../ppapi/ppapi.gyp:*', + '../ppapi/ppapi_internal.gyp:*', + '../printing/printing.gyp:*', + '../skia/skia.gyp:*', + '../third_party/cacheinvalidation/cacheinvalidation.gyp:*', + '../third_party/cld/cld.gyp:*', + '../third_party/codesighs/codesighs.gyp:*', + '../third_party/ffmpeg/ffmpeg.gyp:*', + '../third_party/iccjpeg/iccjpeg.gyp:*', + '../third_party/libpng/libpng.gyp:*', + '../third_party/libusb/libusb.gyp:*', + '../third_party/libwebp/libwebp.gyp:*', + '../third_party/libxslt/libxslt.gyp:*', + '../third_party/lzma_sdk/lzma_sdk.gyp:*', + '../third_party/mesa/mesa.gyp:*', + '../third_party/modp_b64/modp_b64.gyp:*', + '../third_party/npapi/npapi.gyp:*', + '../third_party/ots/ots.gyp:*', + '../third_party/qcms/qcms.gyp:*', + '../third_party/re2/re2.gyp:re2', + '../third_party/WebKit/public/all.gyp:*', + '../tools/perf/clear_system_cache/clear_system_cache.gyp:*', + '../v8/tools/gyp/v8.gyp:*', + '../webkit/renderer/compositor_bindings/compositor_bindings_tests.gyp:*', + '../webkit/support/webkit_support.gyp:*', + '<(libjpeg_gyp_path):*', + ], + }, { # 'OS=="ios"' + 'dependencies': [ + '../ios/ios.gyp:*', + ], + }], + ['os_posix==1 and OS!="android" and OS!="ios"', { + 'dependencies': [ + '../third_party/yasm/yasm.gyp:*#host', + ], + }], + ['OS=="mac" or OS=="ios" or OS=="win"', { + 'dependencies': [ + '../third_party/nss/nss.gyp:*', + ], + }], + ['OS=="win" or OS=="ios" or OS=="linux"', { + 'dependencies': [ + '../breakpad/breakpad.gyp:*', + ], + }], + ['OS=="mac"', { + 'dependencies': [ + '../third_party/ocmock/ocmock.gyp:*', + ], + }], + ['OS=="linux"', { + 'dependencies': [ + '../courgette/courgette.gyp:*', + '../dbus/dbus.gyp:*', + '../sandbox/sandbox.gyp:*', + ], + 'conditions': [ + ['branding=="Chrome"', { + 'dependencies': [ + '../chrome/chrome.gyp:linux_packages_<(channel)', + ], + }], + ['chromeos==0', { + 'dependencies': [ + '../third_party/cros_dbus_cplusplus/cros_dbus_cplusplus.gyp:*', + '../third_party/libmtp/libmtp.gyp:*', + '../third_party/mtpd/mtpd.gyp:*', + ], + }], + ], + }], + ['use_x11==1', { + 'dependencies': [ + '../tools/xdisplaycheck/xdisplaycheck.gyp:*', + ], + }], + ['toolkit_uses_gtk==1', { + 'dependencies': [ + '../tools/gtk_clipboard_dump/gtk_clipboard_dump.gyp:*', + ], + }], + ['OS=="win"', { + 'conditions': [ + ['win_use_allocator_shim==1', { + 'dependencies': [ + '../base/allocator/allocator.gyp:*', + ], + }], + # Don't enable dependencies that don't work on Win64. + ['target_arch!="x64"', { + 'dependencies': [ + # TODO(jschuh) Enable Win64 Memory Watcher. crbug.com/176877 + '../tools/memory_watcher/memory_watcher.gyp:*', + # TODO(jschuh) Enable Win64 Chrome Frame. crbug.com/176875 + '../chrome_frame/chrome_frame.gyp:*', + ], + }], + ], + 'dependencies': [ + '../cloud_print/cloud_print.gyp:*', + '../courgette/courgette.gyp:*', + '../rlz/rlz.gyp:*', + '../sandbox/sandbox.gyp:*', + '../third_party/angle_dx11/src/build_angle.gyp:*', + '../third_party/bspatch/bspatch.gyp:*', + ], + }, { + 'dependencies': [ + '../third_party/libevent/libevent.gyp:*', + ], + }], + ['toolkit_views==1', { + 'dependencies': [ + '../ui/views/controls/webview/webview.gyp:*', + '../ui/views/views.gyp:*', + ], + }], + ['use_aura==1', { + 'dependencies': [ + '../ui/aura/aura.gyp:*', + '../ui/oak/oak.gyp:*', + ], + }], + ['use_ash==1', { + 'dependencies': [ + '../ash/ash.gyp:*', + ], + }], + ['remoting==1', { + 'dependencies': [ + '../remoting/remoting.gyp:*', + ], + }], + ['use_openssl==0', { + 'dependencies': [ + '../net/third_party/nss/ssl.gyp:*', + ], + }], + ['enable_app_list==1', { + 'dependencies': [ + '../ui/app_list/app_list.gyp:*', + ], + }], + ], + }, # target_name: All + { + 'target_name': 'All_syzygy', + 'type': 'none', + 'conditions': [ + ['OS=="win" and fastbuild==0 and target_arch=="ia32"', { + 'dependencies': [ + '../chrome/installer/mini_installer_syzygy.gyp:*', + ], + }, + ], + ], + }, # target_name: All_syzygy + { + 'target_name': 'chromium_builder_tests', + 'type': 'none', + 'dependencies': [ + '../base/base.gyp:base_unittests', + '../chrome/chrome.gyp:unit_tests', + '../crypto/crypto.gyp:crypto_unittests', + '../media/media.gyp:media_unittests', + '../net/net.gyp:net_unittests', + '../sql/sql.gyp:sql_unittests', + '../ui/ui.gyp:ui_unittests', + '../url/url.gyp:url_unittests', + ], + 'conditions': [ + ['OS!="ios"', { + 'dependencies': [ + '../cc/cc_tests.gyp:cc_unittests', + '../chrome/chrome.gyp:browser_tests', + '../chrome/chrome.gyp:chromedriver2_tests', + '../chrome/chrome.gyp:chromedriver2_unittests', + '../chrome/chrome.gyp:interactive_ui_tests', + '../chrome/chrome.gyp:sync_integration_tests', + '../cloud_print/cloud_print.gyp:cloud_print_unittests', + '../components/components.gyp:components_unittests', + '../content/content.gyp:content_browsertests', + '../content/content.gyp:content_shell', + '../content/content.gyp:content_unittests', + '../device/device_tests.gyp:device_unittests', + '../gpu/gpu.gyp:gpu_unittests', + '../gpu/gles2_conform_support/gles2_conform_support.gyp:gles2_conform_support', + '../ipc/ipc.gyp:ipc_tests', + '../jingle/jingle.gyp:jingle_unittests', + '../ppapi/ppapi_internal.gyp:ppapi_unittests', + '../printing/printing.gyp:printing_unittests', + '../remoting/remoting.gyp:remoting_unittests', + '../sync/sync.gyp:sync_unit_tests', + '../third_party/cacheinvalidation/cacheinvalidation.gyp:cacheinvalidation_unittests', + '../third_party/libphonenumber/libphonenumber.gyp:libphonenumber_unittests', + '../webkit/renderer/compositor_bindings/compositor_bindings_tests.gyp:webkit_compositor_bindings_unittests', + '../third_party/WebKit/public/all.gyp:all_blink', + ], + }], + ['OS=="win"', { + 'dependencies': [ + '../chrome/chrome.gyp:crash_service', + '../chrome/chrome.gyp:installer_util_unittests', + '../chrome/chrome.gyp:mini_installer_test', + # mini_installer_tests depends on mini_installer. This should be + # defined in installer.gyp. + '../chrome/installer/mini_installer.gyp:mini_installer', + '../chrome_frame/chrome_frame.gyp:npchrome_frame', + '../courgette/courgette.gyp:courgette_unittests', + '../sandbox/sandbox.gyp:sbox_integration_tests', + '../sandbox/sandbox.gyp:sbox_unittests', + '../sandbox/sandbox.gyp:sbox_validation_tests', + '../third_party/WebKit/public/blink_test_plugin.gyp:blink_test_plugin', + '../ui/app_list/app_list.gyp:app_list_unittests', + '../ui/views/views.gyp:views_unittests', + ], + 'conditions': [ + ['target_arch!="x64"', { + 'dependencies': [ + '../chrome_frame/chrome_frame.gyp:chrome_frame_net_tests', + '../chrome_frame/chrome_frame.gyp:chrome_frame_perftests', + '../chrome_frame/chrome_frame.gyp:chrome_frame_reliability_tests', + '../chrome_frame/chrome_frame.gyp:chrome_frame_tests', + '../chrome_frame/chrome_frame.gyp:chrome_frame_unittests', + ] + }, { # target_arch!="x64" + 'dependencies!': [ + '../chrome_frame/chrome_frame.gyp:npchrome_frame', + ], + 'defines': [ + 'OMIT_CHROME_FRAME', + ], + }], # target_arch=="x64" + # remoting_host_installation uses lots of non-trivial GYP that tend + # to break because of differences between ninja and msbuild. Make + # sure this target is built by the builders on the main waterfall. + # See http://crbug.com/180600. + ['wix_exists == "True" and sas_dll_exists == "True"', { + 'dependencies': [ + '../remoting/remoting.gyp:remoting_host_installation', + ], + }], + ], + }], + ['OS=="linux"', { + 'dependencies': [ + '../sandbox/sandbox.gyp:sandbox_linux_unittests', + '../dbus/dbus.gyp:dbus_unittests', + ], + }], + ['OS=="mac"', { + 'dependencies': [ + '../ui/app_list/app_list.gyp:app_list_unittests', + '../ui/message_center/message_center.gyp:*', + ], + }], + ['test_isolation_mode != "noop"', { + 'dependencies': [ + 'chromium_swarm_tests', + ], + }], + ], + }, # target_name: chromium_builder_tests + { + 'target_name': 'chromium_2010_builder_tests', + 'type': 'none', + 'dependencies': [ + 'chromium_builder_tests', + ], + }, # target_name: chromium_2010_builder_tests + ], + 'conditions': [ + ['OS!="ios"', { + 'targets': [ + { + 'target_name': 'all_webkit', + 'type': 'none', + 'dependencies': [ + '../third_party/WebKit/public/all.gyp:all_blink', + '../content/content.gyp:content_shell', + ], + }, # target_name: all_webkit + { + 'target_name': 'chromium_builder_nacl_win_integration', + 'type': 'none', + 'dependencies': [ + 'chromium_builder_qa', # needed for pyauto + 'chromium_builder_tests', + ], + }, # target_name: chromium_builder_nacl_win_integration + { + 'target_name': 'chromium_builder_perf', + 'type': 'none', + 'dependencies': [ + 'chromium_builder_qa', # needed for pyauto + '../cc/cc_tests.gyp:cc_perftests', + '../chrome/chrome.gyp:performance_browser_tests', + '../chrome/chrome.gyp:performance_ui_tests', + '../chrome/chrome.gyp:sync_performance_tests', + '../tools/perf/clear_system_cache/clear_system_cache.gyp:*', + ], + }, # target_name: chromium_builder_perf + { + 'target_name': 'chromium_gpu_builder', + 'type': 'none', + 'dependencies': [ + '../chrome/chrome.gyp:gpu_tests', + '../chrome/chrome.gyp:performance_browser_tests', + '../chrome/chrome.gyp:performance_ui_tests', + '../content/content.gyp:content_browsertests', + '../content/content.gyp:content_gl_tests', + '../gpu/gpu.gyp:gl_tests', + ], + 'conditions': [ + ['internal_gles2_conform_tests', { + 'dependencies': [ + '../gpu/gles2_conform_test/gles2_conform_test.gyp:gles2_conform_test', + ], + }], # internal_gles2_conform + ], + }, # target_name: chromium_gpu_builder + { + 'target_name': 'chromium_gpu_debug_builder', + 'type': 'none', + 'dependencies': [ + '../chrome/chrome.gyp:gpu_tests', + '../content/content.gyp:content_browsertests', + '../content/content.gyp:content_gl_tests', + '../gpu/gpu.gyp:gl_tests', + ], + 'conditions': [ + ['internal_gles2_conform_tests', { + 'dependencies': [ + '../gpu/gles2_conform_test/gles2_conform_test.gyp:gles2_conform_test', + ], + }], # internal_gles2_conform + ], + }, # target_name: chromium_gpu_debug_builder + { + 'target_name': 'chromium_builder_qa', + 'type': 'none', + 'dependencies': [ + '../chrome/chrome.gyp:chrome', + # Dependencies of pyauto_functional tests. + '../remoting/remoting.gyp:remoting_webapp', + ], + 'conditions': [ + # If you change this condition, make sure you also change it + # in chrome_tests.gypi + ['enable_automation==1 and (OS=="mac" or ((OS=="win" or os_posix==1) and target_arch==python_arch))', { + 'dependencies': [ + '../chrome/chrome.gyp:pyautolib', + ], + }], + ['OS=="mac"', { + 'dependencies': [ + '../remoting/remoting.gyp:remoting_me2me_host_archive', + ], + }], + ['OS=="win"', { + 'dependencies': [ + '../chrome/chrome.gyp:crash_service', + ], + }], + ['OS=="win" and target_arch=="ia32"', { + 'dependencies': [ + '../chrome/chrome.gyp:crash_service_win64', + ], + }], + ['OS=="win" and component != "shared_library" and wix_exists == "True" and sas_dll_exists == "True"', { + 'dependencies': [ + '../remoting/remoting.gyp:remoting_host_installation', + ], + }], + ], + }, # target_name: chromium_builder_qa + { + 'target_name': 'chromium_builder_perf_av', + 'type': 'none', + 'dependencies': [ + 'all_webkit', # to run layout tests + 'chromium_builder_qa', # needed for perf pyauto tests + ], + }, # target_name: chromium_builder_perf_av + { + # This target contains everything we need to run tests on the special + # device-equipped WebRTC bots. We have device-requiring tests in + # PyAuto, browser_tests and content_browsertests. + 'target_name': 'chromium_builder_webrtc', + 'type': 'none', + 'dependencies': [ + 'chromium_builder_qa', # needed for perf pyauto tests + '../chrome/chrome.gyp:browser_tests', + '../content/content.gyp:content_browsertests', + '../content/content.gyp:content_unittests', + '../third_party/libjingle/libjingle.gyp:peerconnection_server', + '../third_party/webrtc/tools/tools.gyp:frame_analyzer', + '../third_party/webrtc/tools/tools.gyp:rgba_to_i420_converter', + ], + 'conditions': [ + ['OS=="win"', { + 'dependencies': [ + '../chrome/chrome.gyp:crash_service', + ], + }], + ], + }, # target_name: chromium_builder_webrtc + { + 'target_name': 'chromium_builder_chromedriver', + 'type': 'none', + 'dependencies': [ + '../chrome/chrome.gyp:chromedriver2_server', + '../chrome/chrome.gyp:chromedriver2_tests', + '../chrome/chrome.gyp:chromedriver2_unittests', + ], + }, # target_name: chromium_builder_chromedriver + { + 'target_name': 'chromium_builder_asan', + 'type': 'none', + 'dependencies': [ + '../chrome/chrome.gyp:chrome', + + # We refer to content_shell directly rather than all_webkit + # because we don't want the _unittests binaries. + '../content/content.gyp:content_browsertests', + '../content/content.gyp:content_shell', + + '../net/net.gyp:dns_fuzz_stub', + ], + }, + ], # targets + }], + ['OS=="mac"', { + 'targets': [ + { + # Target to build everything plus the dmg. We don't put the dmg + # in the All target because developers really don't need it. + 'target_name': 'all_and_dmg', + 'type': 'none', + 'dependencies': [ + 'All', + '../chrome/chrome.gyp:build_app_dmg', + ], + }, + # These targets are here so the build bots can use them to build + # subsets of a full tree for faster cycle times. + { + 'target_name': 'chromium_builder_dbg', + 'type': 'none', + 'dependencies': [ + '../cc/cc_tests.gyp:cc_unittests', + '../chrome/chrome.gyp:browser_tests', + '../chrome/chrome.gyp:interactive_ui_tests', + '../chrome/chrome.gyp:sync_integration_tests', + '../chrome/chrome.gyp:unit_tests', + '../cloud_print/cloud_print.gyp:cloud_print_unittests', + '../components/components.gyp:components_unittests', + '../content/content.gyp:content_browsertests', + '../content/content.gyp:content_unittests', + '../device/device_tests.gyp:device_unittests', + '../gpu/gpu.gyp:gpu_unittests', + '../ipc/ipc.gyp:ipc_tests', + '../jingle/jingle.gyp:jingle_unittests', + '../media/media.gyp:media_unittests', + '../ppapi/ppapi_internal.gyp:ppapi_unittests', + '../printing/printing.gyp:printing_unittests', + '../remoting/remoting.gyp:remoting_unittests', + '../rlz/rlz.gyp:*', + '../sql/sql.gyp:sql_unittests', + '../sync/sync.gyp:sync_unit_tests', + '../third_party/cacheinvalidation/cacheinvalidation.gyp:cacheinvalidation_unittests', + '../third_party/libphonenumber/libphonenumber.gyp:libphonenumber_unittests', + '../tools/perf/clear_system_cache/clear_system_cache.gyp:*', + '../ui/ui.gyp:ui_unittests', + '../url/url.gyp:url_unittests', + '../webkit/renderer/compositor_bindings/compositor_bindings_tests.gyp:webkit_compositor_bindings_unittests', + ], + }, + { + 'target_name': 'chromium_builder_rel', + 'type': 'none', + 'dependencies': [ + '../cc/cc_tests.gyp:cc_unittests', + '../chrome/chrome.gyp:browser_tests', + '../chrome/chrome.gyp:performance_browser_tests', + '../chrome/chrome.gyp:performance_ui_tests', + '../chrome/chrome.gyp:sync_integration_tests', + '../chrome/chrome.gyp:unit_tests', + '../cloud_print/cloud_print.gyp:cloud_print_unittests', + '../components/components.gyp:components_unittests', + '../content/content.gyp:content_browsertests', + '../content/content.gyp:content_unittests', + '../device/device_tests.gyp:device_unittests', + '../gpu/gpu.gyp:gpu_unittests', + '../ipc/ipc.gyp:ipc_tests', + '../jingle/jingle.gyp:jingle_unittests', + '../media/media.gyp:media_unittests', + '../ppapi/ppapi_internal.gyp:ppapi_unittests', + '../printing/printing.gyp:printing_unittests', + '../remoting/remoting.gyp:remoting_unittests', + '../sql/sql.gyp:sql_unittests', + '../sync/sync.gyp:sync_unit_tests', + '../third_party/cacheinvalidation/cacheinvalidation.gyp:cacheinvalidation_unittests', + '../third_party/libphonenumber/libphonenumber.gyp:libphonenumber_unittests', + '../tools/perf/clear_system_cache/clear_system_cache.gyp:*', + '../ui/ui.gyp:ui_unittests', + '../url/url.gyp:url_unittests', + '../webkit/renderer/compositor_bindings/compositor_bindings_tests.gyp:webkit_compositor_bindings_unittests', + ], + }, + { + 'target_name': 'chromium_builder_dbg_tsan_mac', + 'type': 'none', + 'dependencies': [ + '../base/base.gyp:base_unittests', + '../cloud_print/cloud_print.gyp:cloud_print_unittests', + '../crypto/crypto.gyp:crypto_unittests', + '../ipc/ipc.gyp:ipc_tests', + '../jingle/jingle.gyp:jingle_unittests', + '../media/media.gyp:media_unittests', + '../net/net.gyp:net_unittests', + '../printing/printing.gyp:printing_unittests', + '../remoting/remoting.gyp:remoting_unittests', + '../third_party/cacheinvalidation/cacheinvalidation.gyp:cacheinvalidation_unittests', + '../third_party/libphonenumber/libphonenumber.gyp:libphonenumber_unittests', + '../url/url.gyp:url_unittests', + ], + }, + { + # TODO(dpranke): Update the bots to refer to 'chromium_builder_asan'. + 'target_name': 'chromium_builder_asan_mac', + 'type': 'none', + 'dependencies': [ + 'chromium_builder_asan' + ], + }, + { + 'target_name': 'chromium_builder_dbg_valgrind_mac', + 'type': 'none', + 'dependencies': [ + '../base/base.gyp:base_unittests', + '../chrome/chrome.gyp:unit_tests', + '../cloud_print/cloud_print.gyp:cloud_print_unittests', + '../components/components.gyp:components_unittests', + '../content/content.gyp:content_unittests', + '../crypto/crypto.gyp:crypto_unittests', + '../device/device_tests.gyp:device_unittests', + '../ipc/ipc.gyp:ipc_tests', + '../jingle/jingle.gyp:jingle_unittests', + '../media/media.gyp:media_unittests', + '../net/net.gyp:net_unittests', + '../printing/printing.gyp:printing_unittests', + '../remoting/remoting.gyp:remoting_unittests', + '../sql/sql.gyp:sql_unittests', + '../sync/sync.gyp:sync_unit_tests', + '../third_party/cacheinvalidation/cacheinvalidation.gyp:cacheinvalidation_unittests', + '../third_party/libphonenumber/libphonenumber.gyp:libphonenumber_unittests', + '../ui/ui.gyp:ui_unittests', + '../url/url.gyp:url_unittests', + ], + }, + ], # targets + }], # OS="mac" + ['OS=="win"', { + 'targets': [ + # These targets are here so the build bots can use them to build + # subsets of a full tree for faster cycle times. + { + 'target_name': 'chromium_builder', + 'type': 'none', + 'dependencies': [ + '../cc/cc_tests.gyp:cc_unittests', + '../chrome/chrome.gyp:browser_tests', + '../chrome/chrome.gyp:installer_util_unittests', + '../chrome/chrome.gyp:interactive_ui_tests', + '../chrome/chrome.gyp:mini_installer_test', + '../chrome/chrome.gyp:performance_browser_tests', + '../chrome/chrome.gyp:performance_ui_tests', + '../chrome/chrome.gyp:sync_integration_tests', + '../chrome/chrome.gyp:unit_tests', + '../cloud_print/cloud_print.gyp:cloud_print_unittests', + '../components/components.gyp:components_unittests', + '../content/content.gyp:content_browsertests', + '../content/content.gyp:content_unittests', + # mini_installer_tests depends on mini_installer. This should be + # defined in installer.gyp. + '../chrome/installer/mini_installer.gyp:mini_installer', + '../chrome_frame/chrome_frame.gyp:npchrome_frame', + '../courgette/courgette.gyp:courgette_unittests', + '../device/device_tests.gyp:device_unittests', + '../gpu/gpu.gyp:gpu_unittests', + '../ipc/ipc.gyp:ipc_tests', + '../jingle/jingle.gyp:jingle_unittests', + '../media/media.gyp:media_unittests', + '../ppapi/ppapi_internal.gyp:ppapi_unittests', + '../printing/printing.gyp:printing_unittests', + '../remoting/remoting.gyp:remoting_unittests', + '../sql/sql.gyp:sql_unittests', + '../sync/sync.gyp:sync_unit_tests', + '../third_party/cacheinvalidation/cacheinvalidation.gyp:cacheinvalidation_unittests', + '../third_party/libphonenumber/libphonenumber.gyp:libphonenumber_unittests', + '../tools/perf/clear_system_cache/clear_system_cache.gyp:*', + '../ui/ui.gyp:ui_unittests', + '../ui/views/views.gyp:views_unittests', + '../url/url.gyp:url_unittests', + '../webkit/renderer/compositor_bindings/compositor_bindings_tests.gyp:webkit_compositor_bindings_unittests', + '../third_party/WebKit/public/blink_test_plugin.gyp:blink_test_plugin', + ], + 'conditions': [ + ['target_arch!="x64"', { + 'dependencies': [ + '../chrome_frame/chrome_frame.gyp:chrome_frame_net_tests', + '../chrome_frame/chrome_frame.gyp:chrome_frame_perftests', + '../chrome_frame/chrome_frame.gyp:chrome_frame_reliability_tests', + '../chrome_frame/chrome_frame.gyp:chrome_frame_tests', + '../chrome_frame/chrome_frame.gyp:chrome_frame_unittests', + ] + }, { # target_arch!="x64" + 'dependencies!': [ + '../chrome_frame/chrome_frame.gyp:npchrome_frame', + ], + 'defines': [ + 'OMIT_CHROME_FRAME', + ], + }], # target_arch=="x64" + ], + }, + { + 'target_name': 'chromium_builder_win_cf', + 'type': 'none', + 'conditions': [ + ['target_arch!="x64"', { + 'dependencies': [ + '../chrome_frame/chrome_frame.gyp:chrome_frame_net_tests', + '../chrome_frame/chrome_frame.gyp:chrome_frame_perftests', + '../chrome_frame/chrome_frame.gyp:chrome_frame_reliability_tests', + '../chrome_frame/chrome_frame.gyp:chrome_frame_tests', + '../chrome_frame/chrome_frame.gyp:chrome_frame_unittests', + '../chrome_frame/chrome_frame.gyp:npchrome_frame', + ], + }], # target_arch!="x64" + ], + }, + { + 'target_name': 'chromium_builder_dbg_tsan_win', + 'type': 'none', + 'dependencies': [ + '../base/base.gyp:base_unittests', + '../cloud_print/cloud_print.gyp:cloud_print_unittests', + '../components/components.gyp:components_unittests', + '../content/content.gyp:content_unittests', + '../crypto/crypto.gyp:crypto_unittests', + '../ipc/ipc.gyp:ipc_tests', + '../jingle/jingle.gyp:jingle_unittests', + '../media/media.gyp:media_unittests', + '../net/net.gyp:net_unittests', + '../printing/printing.gyp:printing_unittests', + '../remoting/remoting.gyp:remoting_unittests', + '../sql/sql.gyp:sql_unittests', + '../third_party/cacheinvalidation/cacheinvalidation.gyp:cacheinvalidation_unittests', + '../third_party/libphonenumber/libphonenumber.gyp:libphonenumber_unittests', + '../url/url.gyp:url_unittests', + ], + }, + { + 'target_name': 'chromium_builder_dbg_drmemory_win', + 'type': 'none', + 'dependencies': [ + '../base/base.gyp:base_unittests', + '../chrome/chrome.gyp:unit_tests', + '../chrome/chrome.gyp:browser_tests', + '../cloud_print/cloud_print.gyp:cloud_print_unittests', + '../components/components.gyp:components_unittests', + '../content/content.gyp:content_unittests', + '../crypto/crypto.gyp:crypto_unittests', + '../device/device_tests.gyp:device_unittests', + '../ipc/ipc.gyp:ipc_tests', + '../jingle/jingle.gyp:jingle_unittests', + '../media/media.gyp:media_unittests', + '../net/net.gyp:net_unittests', + '../printing/printing.gyp:printing_unittests', + '../remoting/remoting.gyp:remoting_unittests', + '../sql/sql.gyp:sql_unittests', + '../third_party/cacheinvalidation/cacheinvalidation.gyp:cacheinvalidation_unittests', + '../third_party/libphonenumber/libphonenumber.gyp:libphonenumber_unittests', + '../url/url.gyp:url_unittests', + ], + }, + { + 'target_name': 'webkit_builder_win', + 'type': 'none', + 'dependencies': [ + 'all_webkit', + ], + }, + ], # targets + 'conditions': [ + ['branding=="Chrome"', { + 'targets': [ + { + 'target_name': 'chrome_official_builder', + 'type': 'none', + 'dependencies': [ + '../chrome/chrome.gyp:crash_service', + '../chrome/chrome.gyp:policy_templates', + '../chrome/installer/mini_installer.gyp:mini_installer', + '../courgette/courgette.gyp:courgette', + '../cloud_print/cloud_print.gyp:cloud_print', + '../remoting/remoting.gyp:remoting_webapp', + '../third_party/widevine/cdm/widevine_cdm.gyp:widevinecdmadapter', + ], + 'conditions': [ + # If you change this condition, make sure you also change it + # in chrome_tests.gypi + ['enable_automation==1 and (OS=="mac" or (os_posix==1 and target_arch==python_arch))', { + 'dependencies': [ + '../chrome/chrome.gyp:pyautolib', + ], + }], + ['internal_pdf', { + 'dependencies': [ + '../pdf/pdf.gyp:pdf', + ], + }], # internal_pdf + ['target_arch=="ia32"', { + 'dependencies': [ + '../chrome/chrome.gyp:crash_service_win64', + '../chrome_frame/chrome_frame.gyp:npchrome_frame', + '../courgette/courgette.gyp:courgette64', + # Omitting tests from Win64 to speed up cycle times. + '../chrome/chrome.gyp:automated_ui_tests', + '../chrome/chrome.gyp:chromedriver', + '../chrome/chrome.gyp:interactive_ui_tests', + '../chrome/chrome.gyp:reliability_tests', + ], + }], + ['component != "shared_library" and wix_exists == "True" and \ + sas_dll_exists == "True"', { + 'dependencies': [ + '../remoting/remoting.gyp:remoting_host_installation', + ], + }], # component != "shared_library" + ['target_arch=="x64"', { + 'defines': [ + 'OMIT_CHROME_FRAME', + ], + }], # target_arch=="x64" + ] + }, + ], # targets + }], # branding=="Chrome" + ], # conditions + }], # OS="win" + ['use_aura==1', { + 'targets': [ + { + 'target_name': 'aura_builder', + 'type': 'none', + 'dependencies': [ + '../cc/cc_tests.gyp:cc_unittests', + '../chrome/chrome.gyp:browser_tests', + '../chrome/chrome.gyp:chrome', + '../chrome/chrome.gyp:interactive_ui_tests', + '../chrome/chrome.gyp:unit_tests', + '../components/components.gyp:components_unittests', + '../content/content.gyp:content_browsertests', + '../content/content.gyp:content_unittests', + '../device/device_tests.gyp:device_unittests', + '../ppapi/ppapi_internal.gyp:ppapi_unittests', + '../remoting/remoting.gyp:remoting_unittests', + '../ui/app_list/app_list.gyp:*', + '../ui/aura/aura.gyp:*', + '../ui/compositor/compositor.gyp:*', + '../ui/message_center/message_center.gyp:*', + '../ui/ui.gyp:ui_unittests', + '../ui/snapshot/snapshot.gyp:snapshot_unittests', + '../ui/views/views.gyp:views', + '../ui/views/views.gyp:views_examples_with_content_exe', + '../ui/views/views.gyp:views_unittests', + '../ui/keyboard/keyboard.gyp:*', + '../webkit/renderer/compositor_bindings/compositor_bindings_tests.gyp:webkit_compositor_bindings_unittests', + 'all_webkit', + ], + 'conditions': [ + ['OS=="win"', { + 'dependencies': [ + '../chrome/chrome.gyp:crash_service', + '../chrome_frame/chrome_frame.gyp:npchrome_frame', + ], + }], + ['OS=="win" and target_arch!="x64"', { + 'dependencies': [ + '../chrome_frame/chrome_frame.gyp:chrome_frame_net_tests', + '../chrome_frame/chrome_frame.gyp:chrome_frame_perftests', + '../chrome_frame/chrome_frame.gyp:chrome_frame_reliability_tests', + '../chrome_frame/chrome_frame.gyp:chrome_frame_tests', + '../chrome_frame/chrome_frame.gyp:chrome_frame_unittests', + ], + }], + ['OS=="win" and target_arch=="x64"', { + 'dependencies!': [ + '../chrome_frame/chrome_frame.gyp:npchrome_frame', + ], + 'defines': [ + 'OMIT_CHROME_FRAME', + ], + }], + ['OS=="win" and target_arch=="ia32"', { + 'dependencies': [ + '../chrome/chrome.gyp:crash_service_win64', + ], + }], + ['use_ash==1', { + 'dependencies': [ + '../ash/ash.gyp:ash_shell', + '../ash/ash.gyp:ash_unittests', + ], + }], + ['OS=="linux"', { + # Tests that currently only work on Linux. + 'dependencies': [ + '../base/base.gyp:base_unittests', + '../ipc/ipc.gyp:ipc_tests', + '../sql/sql.gyp:sql_unittests', + '../sync/sync.gyp:sync_unit_tests', + ], + }], + ['OS=="mac"', { + # Exclude dependencies that are not currently implemented. + 'dependencies!': [ + '../chrome/chrome.gyp:chrome', + '../chrome/chrome.gyp:unit_tests', + '../device/device_tests.gyp:device_unittests', + '../ui/views/views.gyp:views_unittests', + ], + }], + ['chromeos==1', { + 'dependencies': [ + '../chromeos/chromeos.gyp:chromeos_unittests', + ], + }], + ], + }, + ], # targets + }], # "use_aura==1" + ['test_isolation_mode != "noop"', { + 'targets': [ + { + 'target_name': 'chromium_swarm_tests', + 'type': 'none', + 'dependencies': [ + '../base/base.gyp:base_unittests_run', + '../chrome/chrome.gyp:browser_tests_run', + '../chrome/chrome.gyp:interactive_ui_tests_run', + '../chrome/chrome.gyp:sync_integration_tests_run', + '../chrome/chrome.gyp:unit_tests_run', + '../net/net.gyp:net_unittests_run', + ], + }, # target_name: chromium_swarm_tests + ], + }], + ], # conditions +} diff --git a/build/all_android.gyp b/build/all_android.gyp new file mode 100644 index 0000000000..2e3711e496 --- /dev/null +++ b/build/all_android.gyp @@ -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. + +# This is all.gyp file for Android to prevent breakage in Android and other +# platform; It will be churning a lot in the short term and eventually be merged +# into all.gyp. + +{ + 'variables': { + # A hook that can be overridden in other repositories to add additional + # compilation targets to 'All' + 'android_app_targets%': [], + }, + 'targets': [ + { + 'target_name': 'All', + 'type': 'none', + 'dependencies': [ + '../content/content.gyp:content_shell_apk', + '<@(android_app_targets)', + 'android_builder_tests', + '../android_webview/android_webview.gyp:android_webview_apk', + '../chrome/chrome.gyp:chromium_testshell', + '../remoting/remoting.gyp:remoting_apk', + # TODO(nyquist) This should instead by a target for sync when all of + # the sync-related code for Android has been upstreamed. + # See http://crbug.com/159203 + '../third_party/cacheinvalidation/cacheinvalidation.gyp:cacheinvalidation_javalib', + ], + }, # target_name: All + { + 'target_name': 'all_webkit', + 'type': 'none', + 'dependencies': [ + '../third_party/WebKit/public/all.gyp:all_blink', + '../content/content.gyp:content_shell_apk', + ], + }, # target_name: all_webkit + { + # The current list of tests for android. This is temporary + # until the full set supported. If adding a new test here, + # please also add it to build/android/pylib/gtest/gtest_config.py, + # else the test is not run. + # + # WARNING: + # Do not add targets here without communicating the implications + # on tryserver triggers and load. Discuss with jrg please. + 'target_name': 'android_builder_tests', + 'type': 'none', + 'dependencies': [ + '../android_webview/android_webview.gyp:android_webview_unittests', + '../base/android/jni_generator/jni_generator.gyp:jni_generator_tests', + '../base/base.gyp:base_unittests', + '../breakpad/breakpad.gyp:breakpad_unittests', + # Also compile the tools needed to deal with minidumps, they are + # needed to run minidump tests upstream. + '../breakpad/breakpad.gyp:dump_syms#host', + '../breakpad/breakpad.gyp:symupload#host', + '../breakpad/breakpad.gyp:minidump_dump#host', + '../breakpad/breakpad.gyp:minidump_stackwalk#host', + '../build/android/tests/multiple_proguards/multiple_proguards.gyp:multiple_proguards_test_apk', + '../cc/cc_tests.gyp:cc_perftests_apk', + '../cc/cc_tests.gyp:cc_unittests', + '../chrome/chrome.gyp:unit_tests', + '../components/components.gyp:components_unittests', + '../content/content.gyp:content_browsertests', + '../content/content.gyp:content_shell_test_apk', + '../content/content.gyp:content_unittests', + '../gpu/gpu.gyp:gl_tests', + '../gpu/gpu.gyp:gpu_unittests', + '../ipc/ipc.gyp:ipc_tests', + '../media/media.gyp:media_unittests', + '../net/net.gyp:net_unittests', + '../sandbox/sandbox.gyp:sandbox_linux_unittests', + '../sql/sql.gyp:sql_unittests', + '../sync/sync.gyp:sync_unit_tests', + '../third_party/WebKit/public/all.gyp:*', + '../tools/android/android_tools.gyp:android_tools', + '../tools/android/device_stats_monitor/device_stats_monitor.gyp:device_stats_monitor', + '../tools/android/findbugs_plugin/findbugs_plugin.gyp:findbugs_plugin_test', + '../ui/ui.gyp:ui_unittests', + # Required by ui_unittests. + # TODO(wangxianzhu): It'd better let ui_unittests depend on it, but + # this would cause circular gyp dependency which needs refactoring the + # gyps to resolve. + '../chrome/chrome_resources.gyp:packed_resources', + ], + 'conditions': [ + ['"<(gtest_target_type)"=="shared_library"', { + 'dependencies': [ + # Unit test bundles packaged as an apk. + '../android_webview/android_webview.gyp:android_webview_unittests_apk', + '../base/base.gyp:base_unittests_apk', + '../cc/cc_tests.gyp:cc_unittests_apk', + '../chrome/chrome.gyp:unit_tests_apk', + '../components/components.gyp:components_unittests_apk', + '../content/content.gyp:content_browsertests_apk', + '../content/content.gyp:content_unittests_apk', + '../content/content.gyp:video_decode_accelerator_unittest_apk', + '../gpu/gpu.gyp:gl_tests_apk', + '../gpu/gpu.gyp:gpu_unittests_apk', + '../ipc/ipc.gyp:ipc_tests_apk', + '../media/media.gyp:media_unittests_apk', + '../net/net.gyp:net_unittests_apk', + '../sandbox/sandbox.gyp:sandbox_linux_jni_unittests_apk', + '../sql/sql.gyp:sql_unittests_apk', + '../sync/sync.gyp:sync_unit_tests_apk', + '../ui/ui.gyp:ui_unittests_apk', + '../android_webview/android_webview.gyp:android_webview_test_apk', + '../chrome/chrome.gyp:chromium_testshell_test_apk', + '../chrome/chrome.gyp:chromium_testshell_uiautomator_tests', + '../webkit/renderer/compositor_bindings/compositor_bindings_tests.gyp:webkit_compositor_bindings_unittests_apk' + ], + }], + ], + }, + { + # Experimental / in-progress targets that are expected to fail + # but we still try to compile them on bots (turning the stage + # orange, not red). + 'target_name': 'android_experimental', + 'type': 'none', + 'dependencies': [ + ], + }, + { + # In-progress targets that are expected to fail and are NOT run + # on any bot. + 'target_name': 'android_in_progress', + 'type': 'none', + 'dependencies': [ + ], + }, + ], # targets +} diff --git a/build/android/AndroidManifest.xml b/build/android/AndroidManifest.xml new file mode 100644 index 0000000000..0822e36585 --- /dev/null +++ b/build/android/AndroidManifest.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/build/android/CheckInstallApk-debug.apk b/build/android/CheckInstallApk-debug.apk new file mode 100644 index 0000000000..3dc31910a5 Binary files /dev/null and b/build/android/CheckInstallApk-debug.apk differ diff --git a/build/android/PRESUBMIT.py b/build/android/PRESUBMIT.py new file mode 100644 index 0000000000..d8e5ae34bb --- /dev/null +++ b/build/android/PRESUBMIT.py @@ -0,0 +1,57 @@ +# 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. + +"""Presubmit script for android buildbot. + +See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts for +details on the presubmit API built into gcl. +""" + +_DELETIONS_ONLY_FILES = ( + 'build/android/findbugs_filter/findbugs_known_bugs.txt', +) + + +def _CheckDeletionsOnlyFiles(input_api, output_api): + """Check that a certain listed files only have deletions. + """ + warnings = [] + for f in input_api.AffectedFiles(): + if f.LocalPath() in _DELETIONS_ONLY_FILES: + if f.ChangedContents(): + warnings.append(f.LocalPath()) + results = [] + if warnings: + results.append(output_api.PresubmitPromptWarning( + 'Following files should only contain deletions.', warnings)) + return results + + +def CommonChecks(input_api, output_api): + output = [] + + def J(*dirs): + """Returns a path relative to presubmit directory.""" + return input_api.os_path.join(input_api.PresubmitLocalPath(), *dirs) + + output.extend(input_api.canned_checks.RunPylint( + input_api, + output_api, + white_list=[r'PRESUBMIT\.py$', r'buildbot/.*\.py$'], + extra_paths_list=[ + J(), J('..', '..', 'third_party', 'android_testrunner'), + J('buildbot')])) + + output.extend(input_api.canned_checks.RunUnitTestsInDirectory( + input_api, output_api, J('buildbot', 'tests'))) + output.extend(_CheckDeletionsOnlyFiles(input_api, output_api)) + return output + + +def CheckChangeOnUpload(input_api, output_api): + return CommonChecks(input_api, output_api) + + +def CheckChangeOnCommit(input_api, output_api): + return CommonChecks(input_api, output_api) diff --git a/build/android/adb_android_webview_command_line b/build/android/adb_android_webview_command_line new file mode 100755 index 0000000000..947cfb1eed --- /dev/null +++ b/build/android/adb_android_webview_command_line @@ -0,0 +1,37 @@ +#!/bin/bash +# +# 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. + +# If no flags are given, prints the current content shell flags. +# +# Otherwise, the given flags are used to REPLACE (not modify) the content shell +# flags. For example: +# adb_android_webview_command_line --enable-webgl +# +# To remove all content shell flags, pass an empty string for the flags: +# adb_android_webview_command_line "" + +CMD_LINE_FILE=/data/local/tmp/android-webview-command-line + +if [ $# -eq 0 ] ; then + # If nothing specified, print the command line (stripping off "content_shell") + tempfile=$(tempfile) + adb pull $CMD_LINE_FILE $tempfile 2>/dev/null + if [ $? -eq 0 ] ; then + rm $tempfile + adb shell cat $CMD_LINE_FILE | cut -d " " -f "2-" 2>/dev/null + fi +elif [ $# -eq 1 ] && [ "$1" = '' ] ; then + # If given an empty string, delete the command line. + set -x + adb shell rm $CMD_LINE_FILE >/dev/null +else + # Else set it. + set -x + adb shell "echo 'android_webview $*' > $CMD_LINE_FILE" + # Prevent other apps from modifying flags -- this can create security issues. + adb shell chmod 0664 $CMD_LINE_FILE +fi + diff --git a/build/android/adb_chromium_testshell_command_line b/build/android/adb_chromium_testshell_command_line new file mode 100755 index 0000000000..8c09e3f386 --- /dev/null +++ b/build/android/adb_chromium_testshell_command_line @@ -0,0 +1,37 @@ +#!/bin/bash +# +# Copyright (c) 2012 The Chromium 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 no flags are given, prints the current chromium test shell flags. +# +# Otherwise, the given flags are used to REPLACE (not modify) the chromium +# test shell flags. For example: +# adb_chromium_testshell_command_line --enable-webgl +# +# To remove all chromium test shell flags, pass an empty string for the flags: +# adb_chromium_testshell_command_line "" + +CMD_LINE_FILE=/data/local/tmp/chromium-testshell-command-line + +if [ $# -eq 0 ] ; then + # If nothing specified, print the command line (stripping off "chromium_testshell") + tempfile=$(tempfile) + adb pull $CMD_LINE_FILE $tempfile 2>/dev/null + if [ $? -eq 0 ] ; then + rm $tempfile + adb shell cat $CMD_LINE_FILE | cut -d " " -f "2-" 2>/dev/null + fi +elif [ $# -eq 1 ] && [ "$1" = '' ] ; then + # If given an empty string, delete the command line. + set -x + adb shell rm $CMD_LINE_FILE >/dev/null +else + # Else set it. + set -x + adb shell "echo 'chromium_testshell $*' > $CMD_LINE_FILE" + # Prevent other apps from modifying flags -- this can create security issues. + adb shell chmod 0664 $CMD_LINE_FILE +fi + diff --git a/build/android/adb_content_shell_command_line b/build/android/adb_content_shell_command_line new file mode 100755 index 0000000000..f3c1d4f49f --- /dev/null +++ b/build/android/adb_content_shell_command_line @@ -0,0 +1,37 @@ +#!/bin/bash +# +# Copyright (c) 2012 The Chromium 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 no flags are given, prints the current content shell flags. +# +# Otherwise, the given flags are used to REPLACE (not modify) the content shell +# flags. For example: +# adb_content_shell_command_line --enable-webgl +# +# To remove all content shell flags, pass an empty string for the flags: +# adb_content_shell_command_line "" + +CMD_LINE_FILE=/data/local/tmp/content-shell-command-line + +if [ $# -eq 0 ] ; then + # If nothing specified, print the command line (stripping off "content_shell") + tempfile=$(tempfile) + adb pull $CMD_LINE_FILE $tempfile 2>/dev/null + if [ $? -eq 0 ] ; then + rm $tempfile + adb shell cat $CMD_LINE_FILE | cut -d " " -f "2-" 2>/dev/null + fi +elif [ $# -eq 1 ] && [ "$1" = '' ] ; then + # If given an empty string, delete the command line. + set -x + adb shell rm $CMD_LINE_FILE >/dev/null +else + # Else set it. + set -x + adb shell "echo 'content_shell $*' > $CMD_LINE_FILE" + # Prevent other apps from modifying flags -- this can create security issues. + adb shell chmod 0664 $CMD_LINE_FILE +fi + diff --git a/build/android/adb_device_functions.sh b/build/android/adb_device_functions.sh new file mode 100755 index 0000000000..66cc32fc4e --- /dev/null +++ b/build/android/adb_device_functions.sh @@ -0,0 +1,139 @@ +#!/bin/bash +# +# Copyright (c) 2012 The Chromium 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 collection of functions useful for maintaining android devices + + +# Run an adb command on all connected device in parallel. +# Usage: adb_all command line to eval. Quoting is optional. +# +# Examples: +# adb_all install Chrome.apk +# adb_all 'shell cat /path/to/file' +# +adb_all() { + if [[ $# == 0 ]]; then + echo "Usage: adb_all . Quoting is optional." + echo "Example: adb_all install Chrome.apk" + return 1 + fi + local DEVICES=$(adb_get_devices -b) + local NUM_DEVICES=$(echo $DEVICES | wc -w) + if (( $NUM_DEVICES > 1 )); then + echo "Looping over $NUM_DEVICES devices" + fi + _adb_multi "$DEVICES" "$*" +} + + +# Run a command on each connected device. Quoting the command is suggested but +# not required. The script setups up variable DEVICE to correspond to the +# current serial number. Intended for complex one_liners that don't work in +# adb_all +# Usage: adb_device_loop 'command line to eval' +adb_device_loop() { + if [[ $# == 0 ]]; then + echo "Intended for more complex one-liners that cannot be done with" \ + "adb_all." + echo 'Usage: adb_device_loop "echo $DEVICE: $(adb root &&' \ + 'adb shell cat /data/local.prop)"' + return 1 + fi + local DEVICES=$(adb_get_devices) + if [[ -z $DEVICES ]]; then + return + fi + # Do not change DEVICE variable name - part of api + for DEVICE in $DEVICES; do + DEV_TYPE=$(adb -s $DEVICE shell getprop ro.product.device | sed 's/\r//') + echo "Running on $DEVICE ($DEV_TYPE)" + ANDROID_SERIAL=$DEVICE eval "$*" + done +} + +# Erases data from any devices visible on adb. To preserve a device, +# disconnect it or: +# 1) Reboot it into fastboot with 'adb reboot bootloader' +# 2) Run wipe_all_devices to wipe remaining devices +# 3) Restore device it with 'fastboot reboot' +# +# Usage: wipe_all_devices [-f] +# +wipe_all_devices() { + if [[ -z $(which adb) || -z $(which fastboot) ]]; then + echo "aborting: adb and fastboot not in path" + return 1 + elif ! $(groups | grep -q 'plugdev'); then + echo "If fastboot fails, run: 'sudo adduser $(whoami) plugdev'" + fi + + local DEVICES=$(adb_get_devices -b) + + if [[ $1 != '-f' ]]; then + echo "This will ERASE ALL DATA from $(echo $DEVICES | wc -w) device." + read -p "Hit enter to continue" + fi + + _adb_multi "$DEVICES" "reboot bootloader" + # Subshell to isolate job list + ( + for DEVICE in $DEVICES; do + fastboot_erase $DEVICE & + done + wait + ) + + # Reboot devices together + for DEVICE in $DEVICES; do + fastboot -s $DEVICE reboot + done +} + +# Wipe a device in fastboot. +# Usage fastboot_erase [serial] +fastboot_erase() { + if [[ -n $1 ]]; then + echo "Wiping $1" + local SERIAL="-s $1" + else + if [ -z $(fastboot devices) ]; then + echo "No devices in fastboot, aborting." + echo "Check out wipe_all_devices to see if sufficient" + echo "You can put a device in fastboot using adb reboot bootloader" + return 1 + fi + local SERIAL="" + fi + fastboot $SERIAL erase cache + fastboot $SERIAL erase userdata +} + +# Get list of devices connected via adb +# Args: -b block until adb detects a device +adb_get_devices() { + local DEVICES="$(adb devices | grep 'device$')" + if [[ -z $DEVICES && $1 == '-b' ]]; then + echo '- waiting for device -' >&2 + local DEVICES="$(adb wait-for-device devices | grep 'device$')" + fi + echo "$DEVICES" | awk -vORS=' ' '{print $1}' | sed 's/ $/\n/' +} + +################################################### +## HELPER FUNCTIONS +################################################### + +# Run an adb command in parallel over a device list +_adb_multi() { + local DEVICES=$1 + local ADB_ARGS=$2 + ( + for DEVICE in $DEVICES; do + adb -s $DEVICE $ADB_ARGS & + done + wait + ) +} diff --git a/build/android/adb_gdb b/build/android/adb_gdb new file mode 100755 index 0000000000..53e30ac6a8 --- /dev/null +++ b/build/android/adb_gdb @@ -0,0 +1,963 @@ +#!/bin/bash +# +# Copyright (c) 2012 The Chromium 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 generic script used to attach to a running Chromium process and +# debug it. Most users should not use this directly, but one of the +# wrapper scripts like adb_gdb_content_shell, or adb_gdb_drt +# +# Use --help to print full usage instructions. +# + +PROGNAME=$(basename "$0") +PROGDIR=$(dirname "$0") + +# Location of Chromium-top-level sources. +CHROMIUM_SRC=$(cd "$PROGDIR"/../.. && pwd 2>/dev/null) + +TMPDIR= +GDBSERVER_PIDFILE= +TARGET_GDBSERVER= + +clean_exit () { + if [ "$TMPDIR" ]; then + GDBSERVER_PID=$(cat $GDBSERVER_PIDFILE 2>/dev/null) + if [ "$GDBSERVER_PID" ]; then + log "Killing background gdbserver process: $GDBSERVER_PID" + kill -9 $GDBSERVER_PID >/dev/null 2>&1 + fi + if [ "$TARGET_GDBSERVER" ]; then + log "Removing target gdbserver binary: $TARGET_GDBSERVER." + "$ADB" shell rm "$TARGET_GDBSERVER" >/dev/null 2>&1 + fi + log "Cleaning up: $TMPDIR" + rm -rf "$TMPDIR" + fi + trap "" EXIT + exit $1 +} + +# Ensure clean exit on Ctrl-C or normal exit. +trap "clean_exit 1" INT HUP QUIT TERM +trap "clean_exit \$?" EXIT + +panic () { + echo "ERROR: $@" >&2 + exit 1 +} + +fail_panic () { + if [ $? != 0 ]; then panic "$@"; fi +} + +log () { + if [ "$VERBOSE" -gt 0 ]; then + echo "$@" + fi +} + +DEFAULT_PULL_LIBS_DIR=/tmp/$USER-adb-gdb-libs + +# NOTE: Allow wrapper scripts to set various default through ADB_GDB_XXX +# environment variables. This is only for cosmetic reasons, i.e. to +# display proper + +# Allow wrapper scripts to set the default activity through +# the ADB_GDB_ACTIVITY variable. Users are still able to change the +# final activity name through --activity= option. +# +# This is only for cosmetic reasons, i.e. to display the proper default +# in the --help output. +# +DEFAULT_ACTIVITY=${ADB_GDB_ACTIVITY:-".Main"} + +# Allow wrapper scripts to set the program name through ADB_GDB_PROGNAME +PROGNAME=${ADB_GDB_PROGNAME:-$(basename "$0")} + +ACTIVITY=$DEFAULT_ACTIVITY +ADB= +ANNOTATE= +# Note: Ignore BUILDTYPE variable, because the Ninja build doesn't use it. +BUILDTYPE= +FORCE= +GDBEXEPOSTFIX=gdb +GDBINIT= +GDBSERVER= +HELP= +NDK_DIR= +NO_PULL_LIBS= +PACKAGE_NAME= +PID= +PROGRAM_NAME="activity" +PULL_LIBS= +PULL_LIBS_DIR= +SANDBOXED= +SANDBOXED_INDEX= +START= +SU_PREFIX= +SYMBOL_DIR= +TARGET_ARCH= +TOOLCHAIN= +VERBOSE=0 + +for opt; do + optarg=$(expr "x$opt" : 'x[^=]*=\(.*\)') + case $opt in + --adb=*) + ADB=$optarg + ;; + --activity=*) + ACTIVITY=$optarg + ;; + --annotate=3) + ANNOTATE=$optarg + ;; + --force) + FORCE=true + ;; + --gdbserver=*) + GDBSERVER=$optarg + ;; + --help|-h|-?) + HELP=true + ;; + --ndk-dir=*) + NDK_DIR=$optarg + ;; + --no-pull-libs) + NO_PULL_LIBS=true + ;; + --package-name=*) + PACKAGE_NAME=$optarg + ;; + --pid=*) + PID=$optarg + ;; + --program-name=*) + PROGRAM_NAME=$optarg + ;; + --pull-libs) + PULL_LIBS=true + ;; + --pull-libs-dir=*) + PULL_LIBS_DIR=$optarg + ;; + --sandboxed) + SANDBOXED=true + ;; + --sandboxed=*) + SANDBOXED=true + SANDBOXED_INDEX=$optarg + ;; + --script=*) + GDBINIT=$optarg + ;; + --start) + START=true + ;; + --su-prefix=*) + SU_PREFIX=$optarg + ;; + --symbol-dir=*) + SYMBOL_DIR=$optarg + ;; + --target-arch=*) + TARGET_ARCH=$optarg + ;; + --toolchain=*) + TOOLCHAIN=$optarg + ;; + --ui) + GDBEXEPOSTFIX=gdbtui + ;; + --verbose) + VERBOSE=$(( $VERBOSE + 1 )) + ;; + --debug) + BUILDTYPE=Debug + ;; + --release) + BUILDTYPE=Release + ;; + -*) + panic "Unknown option $OPT, see --help." >&2 + ;; + *) + if [ "$PACKAGE_NAME" ]; then + panic "You can only provide a single package name as argument!\ + See --help." + fi + PACKAGE_NAME=$opt + ;; + esac +done + +print_help_options () { + cat <] + +Attach gdb to a running Android $PROGRAM_NAME process. + +If provided, must be the name of the Android application's +package name to be debugged. You can also use --package-name= to +specify it. +EOF + fi + + cat < option. + +This script needs several things to work properly. It will try to pick +them up automatically for you though: + + - target gdbserver binary + - host gdb client (e.g. arm-linux-androideabi-gdb) + - directory with symbolic version of $PROGRAM_NAME's shared libraries. + +If you have sourced Chromium's build/android/envsetup.sh, this script will +find all of them automatically. This is the recommended way to use it. + +Otherwise, if you have ANDROID_NDK_ROOT defined in your environment, +the script will use it to find the gdb and gdbserver binaries. You can +also use --ndk-dir= to specify an alternative NDK installation +directory. + +The script tries to find the most recent version of the debug version of +shared libraries under one of the following directories: + + \$CHROMIUM_SRC/out/Release/lib/ (used by Ninja builds) + \$CHROMIUM_SRC/out/Debug/lib/ (used by Ninja builds) + \$CHROMIUM_SRC/out/Release/lib.target/ (used by Make builds) + \$CHROMIUM_SRC/out/Debug/lib.target/ (used by Make builds) + +You can restrict this search by using --release or --debug to specify the +build type, or simply use --symbol-dir= to specify the file manually. + +The script tries to extract the target architecture from your GYP_DEFINES, +but if this fails, will default to 'arm'. Use --target-arch= to force +its value. + +Otherwise, the script will complain, but you can use the --gdbserver, +--gdb and --symbol-lib options to specify everything manually. + +An alternative to --gdb= is to use --toollchain= to specify +the path to the host target-specific cross-toolchain. + +You will also need the 'adb' tool in your path. Otherwise, use the --adb +option. The script will complain if there is more than one device connected +and ANDROID_SERIAL is not defined. + +The first time you use it on a device, the script will pull many system +libraries required by the process into a temporary directory. This +is done to strongly improve the debugging experience, like allowing +readable thread stacks and more. The libraries are copied to the following +directory by default: + + $DEFAULT_PULL_LIBS_DIR/ + +But you can use the --pull-libs-dir= option to specify an +alternative. The script can detect when you change the connected device, +and will re-pull the libraries only in this case. You can however force it +with the --pull-libs option. + +Any local .gdbinit script will be ignored, but it is possible to pass a +gdb command script with the --script= option. Note that its commands +will be passed to gdb after the remote connection and library symbol +loading have completed. + +Valid options: + --help|-h|-? Print this message. + --verbose Increase verbosity. + + --sandboxed Debug first sandboxed process we find. + --sandboxed= Debug specific sandboxed process. + --symbol-dir= Specify directory with symbol shared libraries. + --package-name= Specify package name (alternative to 1st argument). + --program-name= Specify program name (cosmetic only). + --pid= Specify application process pid. + --force Kill any previous debugging session, if any. + --start Start package's activity on device. + --ui Use gdbtui instead of gdb + --activity= Activity name for --start [$DEFAULT_ACTIVITY]. + --annotate= Enable gdb annotation. + --script= Specify extra GDB init script. + + --gdbserver= Specify targer gdbserver binary. + --gdb= Specify host gdb client binary. + --target-arch= Specify NDK target arch. + --adb= Specify host ADB binary. + + --su-prefix= Prepend to 'adb shell' commands that are + run by this script. This can be useful to use + the 'su' program on rooted production devices. + + --pull-libs Force system libraries extraction. + --no-pull-libs Do not extract any system library. + --libs-dir= Specify system libraries extraction directory. + + --debug Use libraries under out/Debug. + --release Use libraries under out/Release. + +EOF + exit 0 +fi + +if [ -z "$PACKAGE_NAME" ]; then + panic "Please specify a package name on the command line. See --help." +fi + +if [ -z "$NDK_DIR" ]; then + if [ -z "$ANDROID_NDK_ROOT" ]; then + panic "Can't find NDK directory, please source \ +build/android/envsetup.sh!" + fi +else + if [ ! -d "$NDK_DIR" ]; then + panic "Invalid directory: $NDK_DIR" + fi + if [ ! -f "$NDK_DIR/ndk-build" ]; then + panic "Not a valid NDK directory: $NDK_DIR" + fi + ANDROID_NDK_ROOT=$NDK_DIR +fi + +if [ "$GDBINIT" -a ! -f "$GDBINIT" ]; then + panic "Unknown --script file: $GDBINIT" +fi + +# Find the target architecture from our $GYP_DEFINES +# This returns an NDK-compatible architecture name. +# out: NDK Architecture name, or empty string. +get_gyp_target_arch () { + local ARCH=$(echo $GYP_DEFINES | tr ' ' '\n' | grep '^target_arch=' |\ + cut -d= -f2) + case $ARCH in + ia32|i?86|x86) echo "x86";; + mips|arm) echo "$ARCH";; + *) echo ""; + esac +} + +if [ -z "$TARGET_ARCH" ]; then + TARGET_ARCH=$(get_gyp_target_arch) + if [ -z "$TARGET_ARCH" ]; then + TARGET_ARCH=arm + fi +else + # Nit: accept Chromium's 'ia32' as a valid target architecture. This + # script prefers the NDK 'x86' name instead because it uses it to find + # NDK-specific files (host gdb) with it. + if [ "$TARGET_ARCH" = "ia32" ]; then + TARGET_ARCH=x86 + log "Auto-config: --arch=$TARGET_ARCH (equivalent to ia32)" + fi +fi + +# Detect the NDK system name, i.e. the name used to identify the host. +# out: NDK system name (e.g. 'linux' or 'darwin') +get_ndk_host_system () { + local HOST_OS + if [ -z "$NDK_HOST_SYSTEM" ]; then + HOST_OS=$(uname -s) + case $HOST_OS in + Linux) NDK_HOST_SYSTEM=linux;; + Darwin) NDK_HOST_SYSTEM=darwin;; + *) panic "You can't run this script on this system: $HOST_OS";; + esac + fi + echo "$NDK_HOST_SYSTEM" +} + +# Detect the NDK host architecture name. +# out: NDK arch name (e.g. 'x86' or 'x86_64') +get_ndk_host_arch () { + local HOST_ARCH HOST_OS + if [ -z "$NDK_HOST_ARCH" ]; then + HOST_OS=$(get_ndk_host_system) + HOST_ARCH=$(uname -p) + case $HOST_ARCH in + i?86) NDK_HOST_ARCH=x86;; + x86_64|amd64) NDK_HOST_ARCH=x86_64;; + *) panic "You can't run this script on this host architecture: $HOST_ARCH";; + esac + # Darwin trick: "uname -p" always returns i386 on 64-bit installations. + if [ "$HOST_OS" = darwin -a "$NDK_HOST_ARCH" = "x86" ]; then + # Use '/usr/bin/file', not just 'file' to avoid buggy MacPorts + # implementations of the tool. See http://b.android.com/53769 + HOST_64BITS=$(/usr/bin/file -L "$SHELL" | grep -e "x86[_-]64") + if [ "$HOST_64BITS" ]; then + NDK_HOST_ARCH=x86_64 + fi + fi + fi + echo "$NDK_HOST_ARCH" +} + +# Convert an NDK architecture name into a GNU configure triplet. +# $1: NDK architecture name (e.g. 'arm') +# Out: Android GNU configure triplet (e.g. 'arm-linux-androideabi') +get_arch_gnu_config () { + case $1 in + arm) + echo "arm-linux-androideabi" + ;; + x86) + echo "i686-linux-android" + ;; + mips) + echo "mipsel-linux-android" + ;; + *) + echo "$ARCH-linux-android" + ;; + esac +} + +# Convert an NDK architecture name into a toolchain name prefix +# $1: NDK architecture name (e.g. 'arm') +# Out: NDK toolchain name prefix (e.g. 'arm-linux-androideabi') +get_arch_toolchain_prefix () { + # Return the configure triplet, except for x86! + if [ "$1" = "x86" ]; then + echo "$1" + else + get_arch_gnu_config $1 + fi +} + +# Find a NDK toolchain prebuilt file or sub-directory. +# This will probe the various arch-specific toolchain directories +# in the NDK for the needed file. +# $1: NDK install path +# $2: NDK architecture name +# $3: prebuilt sub-path to look for. +# Out: file path, or empty if none is found. +get_ndk_toolchain_prebuilt () { + local NDK_DIR="${1%/}" + local ARCH="$2" + local SUBPATH="$3" + local NAME="$(get_arch_toolchain_prefix $ARCH)" + local FILE TARGET + FILE=$NDK_DIR/toolchains/$NAME-4.6/prebuilt/$SUBPATH + if [ ! -f "$FILE" ]; then + FILE=$NDK_DIR/toolchains/$NAME-4.4.3/prebuilt/$SUBPATH + if [ ! -f "$FILE" ]; then + FILE= + fi + fi + echo "$FILE" +} + +# Find the path to an NDK's toolchain full prefix for a given architecture +# $1: NDK install path +# $2: NDK target architecture name +# Out: install path + binary prefix (e.g. +# ".../path/to/bin/arm-linux-androideabi-") +get_ndk_toolchain_fullprefix () { + local NDK_DIR="$1" + local ARCH="$2" + local TARGET NAME HOST_OS HOST_ARCH GCC CONFIG + + # NOTE: This will need to be updated if the NDK changes the names or moves + # the location of its prebuilt toolchains. + # + GCC= + HOST_OS=$(get_ndk_host_system) + HOST_ARCH=$(get_ndk_host_arch) + CONFIG=$(get_arch_gnu_config $ARCH) + GCC=$(get_ndk_toolchain_prebuilt \ + "$NDK_DIR" "$ARCH" "$HOST_OS-$HOST_ARCH/bin/$CONFIG-gcc") + if [ -z "$GCC" -a "$HOST_ARCH" = "x86_64" ]; then + GCC=$(get_ndk_toolchain_prebuilt \ + "$NDK_DIR" "$ARCH" "$HOST_OS-x86/bin/$CONFIG-gcc") + fi + if [ ! -f "$GCC" -a "$ARCH" = "x86" ]; then + # Special case, the x86 toolchain used to be incorrectly + # named i686-android-linux-gcc! + GCC=$(get_ndk_toolchain_prebuilt \ + "$NDK_DIR" "$ARCH" "$HOST_OS-x86/bin/i686-android-linux-gcc") + fi + if [ -z "$GCC" ]; then + panic "Cannot find Android NDK toolchain for '$ARCH' architecture. \ +Please verify your NDK installation!" + fi + echo "${GCC%%gcc}" +} + +# $1: NDK install path +# $2: target architecture. +get_ndk_gdbserver () { + local NDK_DIR="$1" + local ARCH=$2 + local BINARY + + # The location has moved after NDK r8 + BINARY=$NDK_DIR/prebuilt/android-$ARCH/gdbserver/gdbserver + if [ ! -f "$BINARY" ]; then + BINARY=$(get_ndk_toolchain_prebuilt "$NDK_DIR" "$ARCH" gdbserver) + fi + echo "$BINARY" +} + +# Check/probe the path to the Android toolchain installation. Always +# use the NDK versions of gdb and gdbserver. They must match to avoid +# issues when both binaries do not speak the same wire protocol. +# +if [ -z "$TOOLCHAIN" ]; then + ANDROID_TOOLCHAIN=$(get_ndk_toolchain_fullprefix \ + "$ANDROID_NDK_ROOT" "$TARGET_ARCH") + ANDROID_TOOLCHAIN=$(dirname "$ANDROID_TOOLCHAIN") + log "Auto-config: --toolchain=$ANDROID_TOOLCHAIN" +else + # Be flexible, allow one to specify either the install path or the bin + # sub-directory in --toolchain: + # + if [ -d "$TOOLCHAIN/bin" ]; then + TOOLCHAIN=$TOOLCHAIN/bin + fi + ANDROID_TOOLCHAIN=$TOOLCHAIN +fi + +# Cosmetic: Remove trailing directory separator. +ANDROID_TOOLCHAIN=${ANDROID_TOOLCHAIN%/} + +# Find host GDB client binary +GDB=$(which $ANDROID_TOOLCHAIN/*-$GDBEXEPOSTFIX 2>/dev/null | head -1) +if [ -z "$GDB" ]; then + panic "Can't find Android gdb client in your path, check your \ +--toolchain path." +fi +log "Host gdb client: $GDB" + +# Find gdbserver binary, we will later push it to /data/local/tmp +# This ensures that both gdbserver and $GDB talk the same binary protocol, +# otherwise weird problems will appear. +# +if [ -z "$GDBSERVER" ]; then + GDBSERVER=$(get_ndk_gdbserver "$ANDROID_NDK_ROOT" "$TARGET_ARCH") + if [ -z "$GDBSERVER" ]; then + panic "Can't find NDK gdbserver binary. use --gdbserver to specify \ +valid one!" + fi + log "Auto-config: --gdbserver=$GDBSERVER" +fi + + + +# Check that ADB is in our path +if [ -z "$ADB" ]; then + ADB=$(which adb 2>/dev/null) + if [ -z "$ADB" ]; then + panic "Can't find 'adb' tool in your path. Install it or use \ +--adb=" + fi + log "Auto-config: --adb=$ADB" +fi + +# Check that it works minimally +ADB_VERSION=$($ADB version 2>/dev/null) +echo "$ADB_VERSION" | fgrep -q -e "Android Debug Bridge" +if [ $? != 0 ]; then + panic "Your 'adb' tool seems invalid, use --adb= to specify a \ +different one: $ADB" +fi + +# If there are more than one device connected, and ANDROID_SERIAL is not +# defined, print an error message. +NUM_DEVICES_PLUS2=$($ADB devices 2>/dev/null | wc -l) +if [ "$NUM_DEVICES_PLUS2" -lt 3 -a -z "$ANDROID_SERIAL" ]; then + echo "ERROR: There is more than one Android device connected to ADB." + echo "Please define ANDROID_SERIAL to specify which one to use." + exit 1 +fi + +# A unique ID for this script's session. This needs to be the same in all +# sub-shell commands we're going to launch, so take the PID of the launcher +# process. +TMP_ID=$$ + +# Temporary directory, will get cleaned up on exit. +TMPDIR=/tmp/$USER-adb-gdb-tmp-$TMP_ID +mkdir -p "$TMPDIR" && rm -rf "$TMPDIR"/* + +GDBSERVER_PIDFILE="$TMPDIR"/gdbserver-$TMP_ID.pid + +# Run a command through adb shell, strip the extra \r from the output +# and return the correct status code to detect failures. This assumes +# that the adb shell command prints a final \n to stdout. +# $1+: command to run +# Out: command's stdout +# Return: command's status +# Note: the command's stderr is lost +adb_shell () { + local TMPOUT="$(mktemp)" + local LASTLINE RET + local ADB=${ADB:-adb} + + # The weird sed rule is to strip the final \r on each output line + # Since 'adb shell' never returns the command's proper exit/status code, + # we force it to print it as '%%' in the temporary output file, + # which we will later strip from it. + $ADB shell $@ ";" echo "%%\$?" 2>/dev/null | \ + sed -e 's![[:cntrl:]]!!g' > $TMPOUT + # Get last line in log, which contains the exit code from the command + LASTLINE=$(sed -e '$!d' $TMPOUT) + # Extract the status code from the end of the line, which must + # be '%%'. + RET=$(echo "$LASTLINE" | \ + awk '{ if (match($0, "%%[0-9]+$")) { print substr($0,RSTART+2); } }') + # Remove the status code from the last line. Note that this may result + # in an empty line. + LASTLINE=$(echo "$LASTLINE" | \ + awk '{ if (match($0, "%%[0-9]+$")) { print substr($0,1,RSTART-1); } }') + # The output itself: all lines except the status code. + sed -e '$d' $TMPOUT && printf "%s" "$LASTLINE" + # Remove temp file. + rm -f $TMPOUT + # Exit with the appropriate status. + return $RET +} + +# If --force is specified, try to kill any gdbserver process started by the +# same user on the device. Normally, these are killed automatically by the +# script on exit, but there are a few corner cases where this would still +# be needed. +if [ "$FORCE" ]; then + GDBSERVER_PIDS=$(adb_shell ps | awk '$9 ~ /gdbserver/ { print $2; }') + for GDB_PID in $GDBSERVER_PIDS; do + log "Killing previous gdbserver (PID=$GDB_PID)" + adb_shell kill -9 $GDB_PID + done +fi + +if [ "$START" ]; then + log "Starting $PROGRAM_NAME on device." + adb_shell am start -n $PACKAGE_NAME/$ACTIVITY 2>/dev/null + adb_shell ps | grep -q $PACKAGE_NAME + fail_panic "Could not start $PROGRAM_NAME on device. Are you sure the \ +package is installed?" +fi + +# Return the timestamp of a given time, as number of seconds since epoch. +# $1: file path +# Out: file timestamp +get_file_timestamp () { + stat -c %Y "$1" 2>/dev/null +} + +# Detect the build type and symbol directory. This is done by finding +# the most recent sub-directory containing debug shared libraries under +# $CHROMIUM_SRC/out/ +# +# $1: $BUILDTYPE value, can be empty +# Out: nothing, but this sets SYMBOL_DIR +# +detect_symbol_dir () { + local SUBDIRS SUBDIR LIST DIR DIR_LIBS TSTAMP + # Note: Ninja places debug libraries under out/$BUILDTYPE/lib/, while + # Make places then under out/$BUILDTYPE/lib.target. + if [ "$1" ]; then + SUBDIRS="$1/lib $1/lib.target" + else + SUBDIRS="Release/lib Debug/lib Release/lib.target Debug/lib.target" + fi + LIST=$TMPDIR/scan-subdirs-$$.txt + printf "" > "$LIST" + for SUBDIR in $SUBDIRS; do + DIR=$CHROMIUM_SRC/out/$SUBDIR + if [ -d "$DIR" ]; then + # Ignore build directories that don't contain symbol versions + # of the shared libraries. + DIR_LIBS=$(ls "$DIR"/lib*.so 2>/dev/null) + if [ -z "$DIR_LIBS" ]; then + echo "No shared libs: $DIR" + continue + fi + TSTAMP=$(get_file_timestamp "$DIR") + printf "%s %s\n" "$TSTAMP" "$SUBDIR" >> "$LIST" + fi + done + SUBDIR=$(cat $LIST | sort -r | head -1 | cut -d" " -f2) + rm -f "$LIST" + + if [ -z "$SUBDIR" ]; then + if [ -z "$1" ]; then + panic "Could not find any build directory under \ +$CHROMIUM_SRC/out. Please build the program first!" + else + panic "Could not find any $1 directory under \ +$CHROMIUM_SRC/out. Check your build type!" + fi + fi + + SYMBOL_DIR=$CHROMIUM_SRC/out/$SUBDIR + log "Auto-config: --symbol-dir=$SYMBOL_DIR" +} + +if [ -z "$SYMBOL_DIR" ]; then + detect_symbol_dir "$BUILDTYPE" +fi + +# Allow several concurrent debugging sessions +TARGET_GDBSERVER=/data/local/tmp/gdbserver-adb-gdb-$TMP_ID + +# Return the build fingerprint contained in a build.prop file. +# $1: path to build.prop file +get_build_fingerprint_from () { + cat "$1" | grep -e '^ro.build.fingerprint=' | cut -d= -f2 +} + + +ORG_PULL_LIBS_DIR=$PULL_LIBS_DIR +PULL_LIBS_DIR=${PULL_LIBS_DIR:-$DEFAULT_PULL_LIBS_DIR} + +HOST_FINGERPRINT= +DEVICE_FINGERPRINT=$(adb_shell getprop ro.build.fingerprint) +log "Device build fingerprint: $DEVICE_FINGERPRINT" + +# If --pull-libs-dir is not specified, and this is a platform build, look +# if we can use the symbolic libraries under $ANDROID_PRODUCT_OUT/symbols/ +# directly, if the build fingerprint matches the device. +if [ -z "$ORG_PULL_LIBS_DIR" -a \ + "$ANDROID_PRODUCT_OUT" -a \ + -f "$ANDROID_PRODUCT_OUT/system/build.prop" ]; then + ANDROID_FINGERPRINT=$(get_build_fingerprint_from \ + "$ANDROID_PRODUCT_OUT"/system/build.prop) + log "Android build fingerprint: $ANDROID_FINGERPRINT" + if [ "$ANDROID_FINGERPRINT" = "$DEVICE_FINGERPRINT" ]; then + log "Perfect match!" + PULL_LIBS_DIR=$ANDROID_PRODUCT_OUT/symbols + HOST_FINGERPRINT=$ANDROID_FINGERPRINT + if [ "$PULL_LIBS" ]; then + log "Ignoring --pull-libs since the device and platform build \ +fingerprints match." + NO_PULL_LIBS=true + fi + fi +fi + +# If neither --pull-libs an --no-pull-libs were specified, check the build +# fingerprints of the device, and the cached system libraries on the host. +# +if [ -z "$NO_PULL_LIBS" -a -z "$PULL_LIBS" ]; then + if [ ! -f "$PULL_LIBS_DIR/build.prop" ]; then + log "Auto-config: --pull-libs (no cached libraries)" + PULL_LIBS=true + else + HOST_FINGERPRINT=$(get_build_fingerprint_from "$PULL_LIBS_DIR/build.prop") + log "Host build fingerprint: $HOST_FINGERPRINT" + if [ "$HOST_FINGERPRINT" == "$DEVICE_FINGERPRINT" ]; then + log "Auto-config: --no-pull-libs (fingerprint match)" + NO_PULL_LIBS=true + else + log "Auto-config: --pull-libs (fingerprint mismatch)" + PULL_LIBS=true + fi + fi +fi + +# Extract the system libraries from the device if necessary. +if [ "$PULL_LIBS" -a -z "$NO_PULL_LIBS" ]; then + echo "Extracting system libraries into: $PULL_LIBS_DIR" +fi + +mkdir -p "$PULL_LIBS_DIR" +fail_panic "Can't create --libs-dir directory: $PULL_LIBS_DIR" + +# If requested, work for M-x gdb. The gdb indirections make it +# difficult to pass --annotate=3 to the gdb binary itself. +GDB_ARGS= +if [ "$ANNOTATE" ]; then + GDB_ARGS=$GDB_ARGS" --annotate=$ANNOTATE" +fi + +# Get the PID from the first argument or else find the PID of the +# browser process. +if [ -z "$PID" ]; then + PROCESSNAME=$PACKAGE_NAME + if [ "$SANDBOXED_INDEX" ]; then + PROCESSNAME=$PROCESSNAME:sandboxed_process$SANDBOXED_INDEX + elif [ "$SANDBOXED" ]; then + PROCESSNAME=$PROCESSNAME:sandboxed_process + PID=$(adb_shell ps | \ + awk '$9 ~ /^'$PROCESSNAME'/ { print $2; }' | head -1) + fi + if [ -z "$PID" ]; then + PID=$(adb_shell ps | \ + awk '$9 == "'$PROCESSNAME'" { print $2; }' | head -1) + fi + if [ -z "$PID" ]; then + if [ "$START" ]; then + panic "Can't find application process PID, did it crash?" + else + panic "Can't find application process PID, are you sure it is \ +running? Try using --start." + fi + fi + log "Found process PID: $PID" +elif [ "$SANDBOXED" ]; then + echo "WARNING: --sandboxed option ignored due to use of --pid." +fi + +# Determine if 'adb shell' runs as root or not. +# If so, we can launch gdbserver directly, otherwise, we have to +# use run-as $PACKAGE_NAME ..., which requires the package to be debuggable. +# +if [ "$SU_PREFIX" ]; then + # Need to check that this works properly. + SU_PREFIX_TEST_LOG=$TMPDIR/su-prefix.log + adb_shell $SU_PREFIX echo "foo" > $SU_PREFIX_TEST_LOG 2>&1 + if [ $? != 0 -o "$(cat $SU_PREFIX_TEST_LOG)" != "foo" ]; then + echo "ERROR: Cannot use '$SU_PREFIX' as a valid su prefix:" + echo "$ adb shell $SU_PREFIX echo foo" + cat $SU_PREFIX_TEST_LOG + exit 1 + fi + COMMAND_PREFIX=$SU_PREFIX +else + SHELL_UID=$(adb shell cat /proc/self/status | \ + awk '$1 == "Uid:" { print $2; }') + log "Shell UID: $SHELL_UID" + if [ "$SHELL_UID" != 0 -o -n "$NO_ROOT" ]; then + COMMAND_PREFIX="run-as $PACKAGE_NAME" + else + COMMAND_PREFIX= + fi +fi +log "Command prefix: '$COMMAND_PREFIX'" + +# Pull device's system libraries that are mapped by our process. +# Pulling all system libraries is too long, so determine which ones +# we need by looking at /proc/$PID/maps instead +if [ "$PULL_LIBS" -a -z "$NO_PULL_LIBS" ]; then + echo "Extracting system libraries into: $PULL_LIBS_DIR" + rm -f $PULL_LIBS_DIR/build.prop + MAPPINGS=$(adb_shell $COMMAND_PREFIX cat /proc/$PID/maps) + if [ $? != 0 ]; then + echo "ERROR: Could not list process's memory mappings." + if [ "$SU_PREFIX" ]; then + panic "Are you sure your --su-prefix is correct?" + else + panic "Use --su-prefix if the application is not debuggable." + fi + fi + SYSTEM_LIBS=$(echo "$MAPPINGS" | \ + awk '$6 ~ /\/system\/.*\.so$/ { print $6; }' | sort -u) + for SYSLIB in /system/bin/linker $SYSTEM_LIBS; do + echo "Pulling from device: $SYSLIB" + DST_FILE=$PULL_LIBS_DIR$SYSLIB + DST_DIR=$(dirname "$DST_FILE") + mkdir -p "$DST_DIR" && adb pull $SYSLIB "$DST_FILE" 2>/dev/null + fail_panic "Could not pull $SYSLIB from device !?" + done + echo "Pulling device build.prop" + adb pull /system/build.prop $PULL_LIBS_DIR/build.prop + fail_panic "Could not pull device build.prop !?" +fi + +# Find all the sub-directories of $PULL_LIBS_DIR, up to depth 4 +# so we can add them to solib-search-path later. +SOLIB_DIRS=$(find $PULL_LIBS_DIR -mindepth 1 -maxdepth 4 -type d | \ + grep -v "^$" | tr '\n' ':') + +# This is a re-implementation of gdbclient, where we use compatible +# versions of gdbserver and $GDBNAME to ensure that everything works +# properly. +# + +# Push gdbserver to the device +log "Pushing gdbserver to $TARGET_GDBSERVER" +adb push $GDBSERVER $TARGET_GDBSERVER &>/dev/null +fail_panic "Could not copy gdbserver to the device!" + +PORT=5039 +HOST_PORT=$PORT +TARGET_PORT=$PORT + +# Pull the app_process binary from the device +GDBEXEC=app_process +log "Pulling $GDBEXEC from device" +adb pull /system/bin/$GDBEXEC "$TMPDIR"/$GDBEXEC &>/dev/null +fail_panic "Could not retrieve $GDBEXEC from the device!" + +# Setup network redirection +log "Setting network redirection (host:$HOST_PORT -> device:$TARGET_PORT)" +adb forward tcp:$HOST_PORT tcp:$TARGET_PORT +fail_panic "Could not setup network redirection from \ +host:localhost:$HOST_PORT to device:localhost:$TARGET_PORT!" + +# Start gdbserver in the background +# Note that using run-as requires the package to be debuggable. +# +# If not, this will fail horribly. The alternative is to run the +# program as root, which requires of course root privileges. +# Maybe we should add a --root option to enable this? +# +log "Starting gdbserver in the background:" +GDBSERVER_LOG=$TMPDIR/gdbserver-$TMP_ID.log +log "adb shell $COMMAND_PREFIX $TARGET_GDBSERVER :$TARGET_PORT \ +--attach $PID" +("$ADB" shell $COMMAND_PREFIX $TARGET_GDBSERVER :$TARGET_PORT \ + --attach $PID > $GDBSERVER_LOG 2>&1) & +GDBSERVER_PID=$! +echo "$GDBSERVER_PID" > $GDBSERVER_PIDFILE +log "background job pid: $GDBSERVER_PID" + +# Check that it is still running after a few seconds. If not, this means we +# could not properly attach to it +sleep 2 +log "Job control: $(jobs -l)" +STATE=$(jobs -l | awk '$2 == "'$GDBSERVER_PID'" { print $3; }') +if [ "$STATE" != "Running" ]; then + echo "ERROR: GDBServer could not attach to PID $PID!" + echo "Failure log (use --verbose for more information):" + cat $GDBSERVER_LOG + exit 1 +fi + +# Generate a file containing useful GDB initialization commands +readonly COMMANDS=$TMPDIR/gdb.init +log "Generating GDB initialization commands file: $COMMANDS" +echo -n "" > $COMMANDS +echo "file $TMPDIR/$GDBEXEC" >> $COMMANDS +echo "directory $CHROMIUM_SRC" >> $COMMANDS +echo "set solib-absolute-prefix $PULL_LIBS_DIR" >> $COMMANDS +echo "set solib-search-path $SOLIB_DIRS:$PULL_LIBS_DIR:$SYMBOL_DIR" \ + >> $COMMANDS +echo "echo Attaching and reading symbols, this may take a while.." \ + >> $COMMANDS +echo "target remote :$HOST_PORT" >> $COMMANDS + +if [ "$GDBINIT" ]; then + cat "$GDBINIT" >> $COMMANDS +fi + +if [ "$VERBOSE" -gt 0 ]; then + echo "### START $COMMANDS" + cat $COMMANDS + echo "### END $COMMANDS" +fi + +log "Launching gdb client: $GDB $GDBARGS -x $COMMANDS" +$GDB $GDBARGS -x $COMMANDS && +rm -f "$GDBSERVER_PIDFILE" diff --git a/build/android/adb_gdb_android_webview_shell b/build/android/adb_gdb_android_webview_shell new file mode 100755 index 0000000000..f685fda77c --- /dev/null +++ b/build/android/adb_gdb_android_webview_shell @@ -0,0 +1,16 @@ +#!/bin/bash +# +# 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. +# +# Attach to or start a ContentShell process and debug it. +# See --help for details. +# +PROGDIR=$(dirname "$0") +export ADB_GDB_PROGNAME=$(basename "$0") +export ADB_GDB_ACTIVITY=.AwShellActivity +"$PROGDIR"/adb_gdb \ + --program-name=AwShellApplication \ + --package-name=org.chromium.android_webview.shell \ + "$@" diff --git a/build/android/adb_gdb_chromium_testshell b/build/android/adb_gdb_chromium_testshell new file mode 100755 index 0000000000..0f1b4a7350 --- /dev/null +++ b/build/android/adb_gdb_chromium_testshell @@ -0,0 +1,16 @@ +#!/bin/bash +# +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# +# Attach to or start a ChromiumTestShell process and debug it. +# See --help for details. +# +PROGDIR=$(dirname "$0") +export ADB_GDB_PROGNAME=$(basename "$0") +export ADB_GDB_ACTIVITY=.ChromiumTestShellActivity +"$PROGDIR"/adb_gdb \ + --program-name=ChromiumTestShell \ + --package-name=org.chromium.chrome.testshell \ + "$@" diff --git a/build/android/adb_gdb_content_shell b/build/android/adb_gdb_content_shell new file mode 100755 index 0000000000..18e1a61d89 --- /dev/null +++ b/build/android/adb_gdb_content_shell @@ -0,0 +1,16 @@ +#!/bin/bash +# +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# +# Attach to or start a ContentShell process and debug it. +# See --help for details. +# +PROGDIR=$(dirname "$0") +export ADB_GDB_PROGNAME=$(basename "$0") +export ADB_GDB_ACTIVITY=.ContentShellActivity +"$PROGDIR"/adb_gdb \ + --program-name=ContentShell \ + --package-name=org.chromium.content_shell_apk \ + "$@" diff --git a/build/android/adb_gdb_drt b/build/android/adb_gdb_drt new file mode 100755 index 0000000000..6157361d2d --- /dev/null +++ b/build/android/adb_gdb_drt @@ -0,0 +1,16 @@ +#!/bin/bash +# +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# +# Attach to or start a DumpRenderTree process and debug it. +# See --help for details. +# +PROGDIR=$(dirname "$0") +export ADB_GDB_PROGNAME=$(basename "$0") +export ADB_GDB_ACTIVITY=.ChromeNativeTestActivity +"$PROGDIR"/adb_gdb \ + --program-name=DumpRenderTree \ + --package-name=org.chromium.native_test \ + "$@" diff --git a/build/android/adb_install_apk.py b/build/android/adb_install_apk.py new file mode 100755 index 0000000000..36adb79d5a --- /dev/null +++ b/build/android/adb_install_apk.py @@ -0,0 +1,77 @@ +#!/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. + +"""Utility script to install APKs from the command line quickly.""" + +import multiprocessing +import optparse +import os +import sys + +from pylib import android_commands +from pylib import constants +from pylib.utils import apk_helper +from pylib.utils import test_options_parser + + +def AddInstallAPKOption(option_parser): + """Adds apk option used to install the APK to the OptionParser.""" + test_options_parser.AddBuildTypeOption(option_parser) + option_parser.add_option('--apk', + help=('The name of the apk containing the ' + ' application (with the .apk extension).')) + option_parser.add_option('--apk_package', + help=('The package name used by the apk containing ' + 'the application.')) + option_parser.add_option('--keep_data', + action='store_true', + default=False, + help=('Keep the package data when installing ' + 'the application.')) + + +def ValidateInstallAPKOption(option_parser, options): + """Validates the apk option and potentially qualifies the path.""" + if not options.apk: + option_parser.error('--apk is mandatory.') + if not os.path.exists(options.apk): + options.apk = os.path.join(constants.DIR_SOURCE_ROOT, + 'out', options.build_type, + 'apks', options.apk) + + +def _InstallApk(args): + apk_path, apk_package, keep_data, device = args + android_commands.AndroidCommands(device=device).ManagedInstall( + apk_path, keep_data, apk_package) + print '----- Installed on %s -----' % device + + +def main(argv): + parser = optparse.OptionParser() + AddInstallAPKOption(parser) + options, args = parser.parse_args(argv) + ValidateInstallAPKOption(parser, options) + if len(args) > 1: + raise Exception('Error: Unknown argument:', args[1:]) + + devices = android_commands.GetAttachedDevices() + if not devices: + raise Exception('Error: no connected devices') + + if not options.apk_package: + options.apk_package = apk_helper.GetPackageName(options.apk) + + pool = multiprocessing.Pool(len(devices)) + # Send a tuple (apk_path, apk_package, device) per device. + pool.map(_InstallApk, zip([options.apk] * len(devices), + [options.apk_package] * len(devices), + [options.keep_data] * len(devices), + devices)) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/build/android/adb_kill_content_shell b/build/android/adb_kill_content_shell new file mode 100755 index 0000000000..64ab5b11fb --- /dev/null +++ b/build/android/adb_kill_content_shell @@ -0,0 +1,24 @@ +#!/bin/bash +# +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# +# Kill a running content shell. +# +# Assumes you have sourced the build/android/envsetup.sh script. + +SHELL_PID_LINES=$(adb shell ps | grep ' org.chromium.content_shell_apk') +VAL=$(echo "$SHELL_PID_LINES" | wc -l) +if [ $VAL -lt 1 ] ; then + echo "Not running Content shell." +else + SHELL_PID=$(echo $SHELL_PID_LINES | awk '{print $2}') + if [ "$SHELL_PID" != "" ] ; then + set -x + adb shell kill $SHELL_PID + set - + else + echo "Content shell does not appear to be running." + fi +fi diff --git a/build/android/adb_logcat_monitor.py b/build/android/adb_logcat_monitor.py new file mode 100755 index 0000000000..35ef7905b2 --- /dev/null +++ b/build/android/adb_logcat_monitor.py @@ -0,0 +1,156 @@ +#!/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. + +"""Saves logcats from all connected devices. + +Usage: adb_logcat_monitor.py [] + +This script will repeatedly poll adb for new devices and save logcats +inside the directory, which it attempts to create. The +script will run until killed by an external signal. To test, run the +script in a shell and -C it after a while. It should be +resilient across phone disconnects and reconnects and start the logcat +early enough to not miss anything. +""" + +import logging +import os +import re +import shutil +import signal +import subprocess +import sys +import time + +# Map from device_id -> (process, logcat_num) +devices = {} + + +class TimeoutException(Exception): + """Exception used to signal a timeout.""" + pass + + +class SigtermError(Exception): + """Exception used to catch a sigterm.""" + pass + + +def StartLogcatIfNecessary(device_id, adb_cmd, base_dir): + """Spawns a adb logcat process if one is not currently running.""" + process, logcat_num = devices[device_id] + if process: + if process.poll() is None: + # Logcat process is still happily running + return + else: + logging.info('Logcat for device %s has died', device_id) + error_filter = re.compile('- waiting for device -') + for line in process.stderr: + if not error_filter.match(line): + logging.error(device_id + ': ' + line) + + logging.info('Starting logcat %d for device %s', logcat_num, + device_id) + logcat_filename = 'logcat_%s_%03d' % (device_id, logcat_num) + logcat_file = open(os.path.join(base_dir, logcat_filename), 'w') + process = subprocess.Popen([adb_cmd, '-s', device_id, + 'logcat', '-v', 'threadtime'], + stdout=logcat_file, + stderr=subprocess.PIPE) + devices[device_id] = (process, logcat_num + 1) + + +def GetAttachedDevices(adb_cmd): + """Gets the device list from adb. + + We use an alarm in this function to avoid deadlocking from an external + dependency. + + Args: + adb_cmd: binary to run adb + + Returns: + list of devices or an empty list on timeout + """ + signal.alarm(2) + try: + out, err = subprocess.Popen([adb_cmd, 'devices'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE).communicate() + if err: + logging.warning('adb device error %s', err.strip()) + return re.findall('^(\w+)\tdevice$', out, re.MULTILINE) + except TimeoutException: + logging.warning('"adb devices" command timed out') + return [] + except (IOError, OSError): + logging.exception('Exception from "adb devices"') + return [] + finally: + signal.alarm(0) + + +def main(base_dir, adb_cmd='adb'): + """Monitor adb forever. Expects a SIGINT (Ctrl-C) to kill.""" + # We create the directory to ensure 'run once' semantics + if os.path.exists(base_dir): + print 'adb_logcat_monitor: %s already exists? Cleaning' % base_dir + shutil.rmtree(base_dir, ignore_errors=True) + + os.makedirs(base_dir) + logging.basicConfig(filename=os.path.join(base_dir, 'eventlog'), + level=logging.INFO, + format='%(asctime)-2s %(levelname)-8s %(message)s') + + # Set up the alarm for calling 'adb devices'. This is to ensure + # our script doesn't get stuck waiting for a process response + def TimeoutHandler(_, unused_frame): + raise TimeoutException() + signal.signal(signal.SIGALRM, TimeoutHandler) + + # Handle SIGTERMs to ensure clean shutdown + def SigtermHandler(_, unused_frame): + raise SigtermError() + signal.signal(signal.SIGTERM, SigtermHandler) + + logging.info('Started with pid %d', os.getpid()) + pid_file_path = os.path.join(base_dir, 'LOGCAT_MONITOR_PID') + + try: + with open(pid_file_path, 'w') as f: + f.write(str(os.getpid())) + while True: + for device_id in GetAttachedDevices(adb_cmd): + if not device_id in devices: + subprocess.call([adb_cmd, '-s', device_id, 'logcat', '-c']) + devices[device_id] = (None, 0) + + for device in devices: + # This will spawn logcat watchers for any device ever detected + StartLogcatIfNecessary(device, adb_cmd, base_dir) + + time.sleep(5) + except SigtermError: + logging.info('Received SIGTERM, shutting down') + except: + logging.exception('Unexpected exception in main.') + finally: + for process, _ in devices.itervalues(): + if process: + try: + process.terminate() + except OSError: + pass + os.remove(pid_file_path) + + +if __name__ == '__main__': + if 2 <= len(sys.argv) <= 3: + print 'adb_logcat_monitor: Initializing' + sys.exit(main(*sys.argv[1:3])) + + print 'Usage: %s []' % sys.argv[0] diff --git a/build/android/adb_logcat_printer.py b/build/android/adb_logcat_printer.py new file mode 100755 index 0000000000..5194668ec6 --- /dev/null +++ b/build/android/adb_logcat_printer.py @@ -0,0 +1,202 @@ +#!/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. + +"""Shutdown adb_logcat_monitor and print accumulated logs. + +To test, call './adb_logcat_printer.py ' where + contains 'adb logcat -v threadtime' files named as +logcat__ + +The script will print the files to out, and will combine multiple +logcats from a single device if there is overlap. + +Additionally, if a /LOGCAT_MONITOR_PID exists, the script +will attempt to terminate the contained PID by sending a SIGINT and +monitoring for the deletion of the aforementioned file. +""" + +import cStringIO +import logging +import os +import re +import signal +import sys +import time + + +# Set this to debug for more verbose output +LOG_LEVEL = logging.INFO + + +def CombineLogFiles(list_of_lists, logger): + """Splices together multiple logcats from the same device. + + Args: + list_of_lists: list of pairs (filename, list of timestamped lines) + logger: handler to log events + + Returns: + list of lines with duplicates removed + """ + cur_device_log = [''] + for cur_file, cur_file_lines in list_of_lists: + # Ignore files with just the logcat header + if len(cur_file_lines) < 2: + continue + common_index = 0 + # Skip this step if list just has empty string + if len(cur_device_log) > 1: + try: + line = cur_device_log[-1] + # Used to make sure we only splice on a timestamped line + if re.match('^\d{2}-\d{2} \d{2}:\d{2}:\d{2}.\d{3} ', line): + common_index = cur_file_lines.index(line) + else: + logger.warning('splice error - no timestamp in "%s"?', line.strip()) + except ValueError: + # The last line was valid but wasn't found in the next file + cur_device_log += ['***** POSSIBLE INCOMPLETE LOGCAT *****'] + logger.info('Unable to splice %s. Incomplete logcat?', cur_file) + + cur_device_log += ['*'*30 + ' %s' % cur_file] + cur_device_log.extend(cur_file_lines[common_index:]) + + return cur_device_log + + +def FindLogFiles(base_dir): + """Search a directory for logcat files. + + Args: + base_dir: directory to search + + Returns: + Mapping of device_id to a sorted list of file paths for a given device + """ + logcat_filter = re.compile('^logcat_(\w+)_(\d+)$') + # list of tuples (, , ) + filtered_list = [] + for cur_file in os.listdir(base_dir): + matcher = logcat_filter.match(cur_file) + if matcher: + filtered_list += [(matcher.group(1), int(matcher.group(2)), + os.path.join(base_dir, cur_file))] + filtered_list.sort() + file_map = {} + for device_id, _, cur_file in filtered_list: + if not device_id in file_map: + file_map[device_id] = [] + + file_map[device_id] += [cur_file] + return file_map + + +def GetDeviceLogs(log_filenames, logger): + """Read log files, combine and format. + + Args: + log_filenames: mapping of device_id to sorted list of file paths + logger: logger handle for logging events + + Returns: + list of formatted device logs, one for each device. + """ + device_logs = [] + + for device, device_files in log_filenames.iteritems(): + logger.debug('%s: %s', device, str(device_files)) + device_file_lines = [] + for cur_file in device_files: + with open(cur_file) as f: + device_file_lines += [(cur_file, f.read().splitlines())] + combined_lines = CombineLogFiles(device_file_lines, logger) + # Prepend each line with a short unique ID so it's easy to see + # when the device changes. We don't use the start of the device + # ID because it can be the same among devices. Example lines: + # AB324: foo + # AB324: blah + device_logs += [('\n' + device[-5:] + ': ').join(combined_lines)] + return device_logs + + +def ShutdownLogcatMonitor(base_dir, logger): + """Attempts to shutdown adb_logcat_monitor and blocks while waiting.""" + try: + monitor_pid_path = os.path.join(base_dir, 'LOGCAT_MONITOR_PID') + with open(monitor_pid_path) as f: + monitor_pid = int(f.readline()) + + logger.info('Sending SIGTERM to %d', monitor_pid) + os.kill(monitor_pid, signal.SIGTERM) + i = 0 + while True: + time.sleep(.2) + if not os.path.exists(monitor_pid_path): + return + if not os.path.exists('/proc/%d' % monitor_pid): + logger.warning('Monitor (pid %d) terminated uncleanly?', monitor_pid) + return + logger.info('Waiting for logcat process to terminate.') + i += 1 + if i >= 10: + logger.warning('Monitor pid did not terminate. Continuing anyway.') + return + + except (ValueError, IOError, OSError): + logger.exception('Error signaling logcat monitor - continuing') + + +def main(base_dir, output_file): + log_stringio = cStringIO.StringIO() + logger = logging.getLogger('LogcatPrinter') + logger.setLevel(LOG_LEVEL) + sh = logging.StreamHandler(log_stringio) + sh.setFormatter(logging.Formatter('%(asctime)-2s %(levelname)-8s' + ' %(message)s')) + logger.addHandler(sh) + + try: + # Wait at least 5 seconds after base_dir is created before printing. + # + # The idea is that 'adb logcat > file' output consists of 2 phases: + # 1 Dump all the saved logs to the file + # 2 Stream log messages as they are generated + # + # We want to give enough time for phase 1 to complete. There's no + # good method to tell how long to wait, but it usually only takes a + # second. On most bots, this code path won't occur at all, since + # adb_logcat_monitor.py command will have spawned more than 5 seconds + # prior to called this shell script. + try: + sleep_time = 5 - (time.time() - os.path.getctime(base_dir)) + except OSError: + sleep_time = 5 + if sleep_time > 0: + logger.warning('Monitor just started? Sleeping %.1fs', sleep_time) + time.sleep(sleep_time) + + assert os.path.exists(base_dir), '%s does not exist' % base_dir + ShutdownLogcatMonitor(base_dir, logger) + separator = '\n' + '*' * 80 + '\n\n' + for log in GetDeviceLogs(FindLogFiles(base_dir), logger): + output_file.write(log) + output_file.write(separator) + with open(os.path.join(base_dir, 'eventlog')) as f: + output_file.write('\nLogcat Monitor Event Log\n') + output_file.write(f.read()) + except: + logger.exception('Unexpected exception') + + logger.info('Done.') + sh.flush() + output_file.write('\nLogcat Printer Event Log\n') + output_file.write(log_stringio.getvalue()) + +if __name__ == '__main__': + if len(sys.argv) == 1: + print 'Usage: %s ' % sys.argv[0] + sys.exit(1) + sys.exit(main(sys.argv[1], sys.stdout)) diff --git a/build/android/adb_profile_chrome b/build/android/adb_profile_chrome new file mode 100755 index 0000000000..c4445d1772 --- /dev/null +++ b/build/android/adb_profile_chrome @@ -0,0 +1,139 @@ +#!/bin/bash +# +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# +# Start / stop profiling in chrome. + +# The profiling data is saved to directory /sdcard/Download. The files +# are named beginning chrome-profile-results- +# +# Assumes you have sourced the android build environment script +# (e.g. 'adb' is on your path). +set -e + +usage() { + echo "adb_profile_chrome [--start [-o file] [-c C]|--stop|-d|-t N] [-v N]" + echo "See http://dev.chromium.org/developers/how-tos/trace-event-profiling-tool for detailed instructions on profiling." + echo "" + echo " --start Start profiling." + echo " --output|-o file Save profile output to file. " + echo " (Default is /sdcard/Download/chrome-profile-results-*)" + echo " --categories|-c C Select categories to trace with comma-delimited wildcards." + echo " e.g. '*', 'cat1*,-cat1a'. Default is '*'." + echo " --stop Stop profiling." + echo " --download|-d Download latest trace." + echo " --time|-t N Profile for N seconds and download the resulting trace." + echo " --version|v N Select among installed browsers." + echo " One of stable (default), beta, dev, build" + echo "" + echo "Profiling data is saved to the device." + exit 0 +} + +send_intent() { + local PACKAGE=$1 + local INTENT=$2 + shift + shift + adb shell am broadcast -a $PACKAGE.$INTENT $* +} + +download_latest_trace() { + TRACE_FILE=$(adb logcat -d | \ + grep "Logging performance trace to file: " | \ + tail -1 | \ + perl -pi -e "s/.*\/storage\/emulated\/.+\/([^\r]+).*/\/sdcard\/Download\/\\1/g") + if [ -z "$TRACE_FILE" ]; then + echo "Unable to determine trace file name" + exit 1 + fi + + adb pull $TRACE_FILE 2> /dev/null + LOCAL_TRACE_FILE=$(basename $TRACE_FILE) + if [ ! -f "$LOCAL_TRACE_FILE" ]; then + echo "Unable to download trace file" + exit 1 + fi +} + +do_timed_capture() { + local PACKAGE=$1 + local INTERVAL=$2 + shift + shift + echo -n "Capturing trace..." + send_intent ${PACKAGE} "GPU_PROFILER_START" $* > /dev/null + sleep ${INTERVAL} + send_intent ${PACKAGE} "GPU_PROFILER_STOP" > /dev/null + echo "done" + + echo -n "Downloading trace..." + sleep $[${INTERVAL} / 4 + 1] + download_latest_trace + echo "done" + + echo "Trace written to ${PWD}/${LOCAL_TRACE_FILE}" +} + +PACKAGE=${DEFAULT_PACKAGE:-com.android.chrome} + +while test -n "$1"; do + case "$1" in + -v|--version) + if [[ -z "$2" ]] ; then + usage + fi + shift + case "$1" in + stable) PACKAGE="com.android.chrome" ;; + beta) PACKAGE="com.chrome.beta" ;; + dev) PACKAGE="com.google.android.apps.chrome_dev" ;; + build) PACKAGE="com.google.android.apps.chrome" ;; + *) usage ;; + esac + ;; + --start) FUNCTION="GPU_PROFILER_START" ;; + --stop) FUNCTION="GPU_PROFILER_STOP" ;; + -o|--output) + if [ -z "$2" ] ; then + usage + fi + OUTPUT="-e file '$2'" + shift + ;; + -c|--categories) + if [ -z "$2" ]; then + usage + fi + CATEGORIES="-e categories '$2'" + shift + ;; + -t|--time) + shift + if [ -z "$1" ] ; then + usage + fi + INTERVAL="$1" + ;; + -d|--download) + shift + download_latest_trace + echo "Trace written to ${PWD}/${LOCAL_TRACE_FILE}" + ;; + *) usage ;; + esac + shift +done + +if [ -z "${INTERVAL}" ] ; then + if [ -z "${FUNCTION}" ] ; then + usage + else + send_intent ${PACKAGE} ${FUNCTION} ${OUTPUT} ${CATEGORIES} + fi +else + do_timed_capture ${PACKAGE} ${INTERVAL} ${CATEGORIES} +fi +exit 0 diff --git a/build/android/adb_reverse_forwarder.py b/build/android/adb_reverse_forwarder.py new file mode 100755 index 0000000000..ee38332b3c --- /dev/null +++ b/build/android/adb_reverse_forwarder.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +# +# 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. + +"""Command line tool for forwarding ports from a device to the host. + +Allows an Android device to connect to services running on the host machine, +i.e., "adb forward" in reverse. Requires |host_forwarder| and |device_forwarder| +to be built. +""" + +import optparse +import sys +import time + +from pylib import android_commands, forwarder +from pylib.utils import run_tests_helper + + +def main(argv): + parser = optparse.OptionParser(usage='Usage: %prog [options] device_port ' + 'host_port [device_port_2 host_port_2] ...', + description=__doc__) + parser.add_option('-v', + '--verbose', + dest='verbose_count', + default=0, + action='count', + help='Verbose level (multiple times for more)') + parser.add_option('--device', + help='Serial number of device we should use.') + parser.add_option('--debug', action='store_const', const='Debug', + dest='build_type', default='Release', + help='Use Debug build of host tools instead of Release.') + + options, args = parser.parse_args(argv) + run_tests_helper.SetLogLevel(options.verbose_count) + + if len(args) < 2 or not len(args) % 2: + parser.error('Need even number of port pairs') + sys.exit(1) + + try: + port_pairs = map(int, args[1:]) + port_pairs = zip(port_pairs[::2], port_pairs[1::2]) + except ValueError: + parser.error('Bad port number') + sys.exit(1) + + adb = android_commands.AndroidCommands(options.device) + try: + forwarder.Forwarder.Map(port_pairs, adb, options.build_type) + while True: + time.sleep(60) + except KeyboardInterrupt: + sys.exit(0) + finally: + forwarder.Forwarder.UnmapAllDevicePorts(adb) + +if __name__ == '__main__': + main(sys.argv) diff --git a/build/android/adb_run_android_webview_shell b/build/android/adb_run_android_webview_shell new file mode 100755 index 0000000000..cc9f6d27e8 --- /dev/null +++ b/build/android/adb_run_android_webview_shell @@ -0,0 +1,15 @@ +#!/bin/bash +# +# 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. + +if [ $# -gt 0 ] ; then + INTENT_ARGS="-d \"$1\"" # e.g. a URL +fi + +adb shell am start \ + -a android.intent.action.VIEW \ + -n org.chromium.android_webview.shell/.AwShellActivity \ + $INTENT_ARGS + diff --git a/build/android/adb_run_chromium_testshell b/build/android/adb_run_chromium_testshell new file mode 100755 index 0000000000..b17482c761 --- /dev/null +++ b/build/android/adb_run_chromium_testshell @@ -0,0 +1,14 @@ +#!/bin/bash +# +# Copyright (c) 2012 The Chromium 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 [ $# -gt 0 ] ; then + INTENT_ARGS="-d \"$1\"" # e.g. a URL +fi + +adb shell am start \ + -a android.intent.action.VIEW \ + -n org.chromium.chrome.testshell/.ChromiumTestShellActivity \ + $INTENT_ARGS diff --git a/build/android/adb_run_content_shell b/build/android/adb_run_content_shell new file mode 100755 index 0000000000..17a734c982 --- /dev/null +++ b/build/android/adb_run_content_shell @@ -0,0 +1,14 @@ +#!/bin/bash +# +# Copyright (c) 2012 The Chromium 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 [ $# -gt 0 ] ; then + INTENT_ARGS="-d \"$1\"" # e.g. a URL +fi + +adb shell am start \ + -a android.intent.action.VIEW \ + -n org.chromium.content_shell_apk/.ContentShellActivity \ + $INTENT_ARGS diff --git a/build/android/ant/apk-codegen.xml b/build/android/ant/apk-codegen.xml new file mode 100644 index 0000000000..37abb077f1 --- /dev/null +++ b/build/android/ant/apk-codegen.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/android/ant/apk-compile.xml b/build/android/ant/apk-compile.xml new file mode 100644 index 0000000000..4f3d664aec --- /dev/null +++ b/build/android/ant/apk-compile.xml @@ -0,0 +1,247 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/android/ant/apk-package.xml b/build/android/ant/apk-package.xml new file mode 100644 index 0000000000..eeb156c94f --- /dev/null +++ b/build/android/ant/apk-package.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/android/ant/chromium-debug.keystore b/build/android/ant/chromium-debug.keystore new file mode 100644 index 0000000000..67eb0aa34c Binary files /dev/null and b/build/android/ant/chromium-debug.keystore differ diff --git a/build/android/ant/create-test-jar.js b/build/android/ant/create-test-jar.js new file mode 100644 index 0000000000..855b47e205 --- /dev/null +++ b/build/android/ant/create-test-jar.js @@ -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. + +/** + * Combines classes from javac.custom.classpath property and ${out.dir}/classes + * into a single jar file ${ant.project.name}.jar and places the file in + * ${lib.java.dir}. + */ + +importClass(java.io.File); +importClass(org.apache.tools.ant.types.Reference); +importClass(org.apache.tools.ant.types.FileSet); +importClass(org.apache.tools.ant.types.ZipFileSet); +importClass(org.apache.tools.ant.taskdefs.Zip); + +var jarTask = project.createTask("jar"); + +// Do not allow duplicates in the jar, the default behavior of Jar task +// is "add" which means duplicates are allowed. +// This can cause a class file to be included multiple times, setting the +// duplicate to "preserve" ensures that only the first definition is included. + +var duplicate = Zip.Duplicate(); +duplicate.setValue("preserve"); +jarTask.setDuplicate(duplicate); + +var destPath = File(project.getProperty("TEST_JAR_PATH")); +jarTask.setDestFile(destPath); + +// Include all the jars in the classpath. +var javacCustomClasspath = + project.getReference("javac.custom.classpath").list(); + +for (var i in javacCustomClasspath) { + var fileName = javacCustomClasspath[i] + var fileExtension = fileName.split("\\.").pop(); + if(fileExtension == "jar") + { + var zipFileSet = ZipFileSet(); + zipFileSet.setIncludes("**/*.class"); + zipFileSet.setSrc(File(fileName)); + jarTask.addFileset(zipFileSet); + } +} + +// Add the compiled classes in ${out.dir}/classes. +var projectClasses = FileSet(); +projectClasses.setIncludes("**/*.class"); +projectClasses.setDir(File(project.getProperty("out.dir") + "/classes")); +jarTask.addFileset(projectClasses); + +// Exclude manifest and resource classes. +var appPackagePath = + (project.getProperty("project.app.package")).replace('.','/'); +var excludedClasses = ["R.class", "R$*.class", "Manifest.class", + "Manifest$*.class", "BuildConfig.class"] + +var exclusionString = ""; +for (var i in excludedClasses) { + exclusionString += appPackagePath+ "/" + excludedClasses[i] + " "; +} + +jarTask.setExcludes(exclusionString); +jarTask.perform(); diff --git a/build/android/ant/empty/res/.keep b/build/android/ant/empty/res/.keep new file mode 100644 index 0000000000..1fd038b8cf --- /dev/null +++ b/build/android/ant/empty/res/.keep @@ -0,0 +1,2 @@ +# This empty res folder can be passed to aapt while building Java libraries or +# APKs that don't have any resources. diff --git a/build/android/arm-linux-androideabi-gold/arm-linux-androideabi-ld b/build/android/arm-linux-androideabi-gold/arm-linux-androideabi-ld new file mode 120000 index 0000000000..5b178e9f42 --- /dev/null +++ b/build/android/arm-linux-androideabi-gold/arm-linux-androideabi-ld @@ -0,0 +1 @@ +../../../third_party/android_tools/ndk/toolchains/arm-linux-androideabi-4.6/prebuilt/linux-x86_64/bin/arm-linux-androideabi-ld.gold \ No newline at end of file diff --git a/build/android/arm-linux-androideabi-gold/ld b/build/android/arm-linux-androideabi-gold/ld new file mode 120000 index 0000000000..2366ddac06 --- /dev/null +++ b/build/android/arm-linux-androideabi-gold/ld @@ -0,0 +1 @@ +../../../third_party/android_tools/ndk/toolchains/arm-linux-androideabi-4.6/prebuilt/linux-x86_64/arm-linux-androideabi/bin/ld.gold \ No newline at end of file diff --git a/build/android/asan_symbolize.py b/build/android/asan_symbolize.py new file mode 100755 index 0000000000..928798f5e5 --- /dev/null +++ b/build/android/asan_symbolize.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python +# +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + + +import collections +import optparse +import os +import re +import sys + +from pylib import constants + +# Uses symbol.py from third_party/android_platform, not python's. +sys.path.insert(0, + os.path.join(constants.DIR_SOURCE_ROOT, + 'third_party/android_platform/development/scripts')) +import symbol + + +_RE_ASAN = re.compile(r'I/asanwrapper\.sh.*?(#\S*?) (\S*?) \((.*?)\+(.*?)\)') + +def _ParseAsanLogLine(line): + m = re.match(_RE_ASAN, line) + if not m: + return None + return { + 'library': m.group(3), + 'pos': m.group(1), + 'rel_address': '%08x' % int(m.group(4), 16), + } + + +def _FindASanLibraries(): + asan_lib_dir = os.path.join(constants.DIR_SOURCE_ROOT, + 'third_party', 'llvm-build', + 'Release+Asserts', 'lib') + asan_libs = [] + for src_dir, _, files in os.walk(asan_lib_dir): + asan_libs += [os.path.relpath(os.path.join(src_dir, f)) + for f in files + if f.endswith('.so')] + return asan_libs + + +def _TranslateLibPath(library, asan_libs): + for asan_lib in asan_libs: + if os.path.basename(library) == os.path.basename(asan_lib): + return '/' + asan_lib + return symbol.TranslateLibPath(library) + + +def _Symbolize(input): + asan_libs = _FindASanLibraries() + libraries = collections.defaultdict(list) + asan_lines = [] + for asan_log_line in [a.strip() for a in input]: + m = _ParseAsanLogLine(asan_log_line) + if m: + libraries[m['library']].append(m) + asan_lines.append({'raw_log': asan_log_line, 'parsed': m}) + + all_symbols = collections.defaultdict(dict) + original_symbols_dir = symbol.SYMBOLS_DIR + for library, items in libraries.iteritems(): + libname = _TranslateLibPath(library, asan_libs) + lib_relative_addrs = set([i['rel_address'] for i in items]) + info_dict = symbol.SymbolInformationForSet(libname, + lib_relative_addrs, + True) + if info_dict: + all_symbols[library]['symbols'] = info_dict + + for asan_log_line in asan_lines: + m = asan_log_line['parsed'] + if not m: + print asan_log_line['raw_log'] + continue + if (m['library'] in all_symbols and + m['rel_address'] in all_symbols[m['library']]['symbols']): + s = all_symbols[m['library']]['symbols'][m['rel_address']][0] + print s[0], s[1], s[2] + else: + print asan_log_line['raw_log'] + + +def main(): + parser = optparse.OptionParser() + parser.add_option('-l', '--logcat', + help='File containing adb logcat output with ASan stacks. ' + 'Use stdin if not specified.') + options, args = parser.parse_args() + if options.logcat: + input = file(options.logcat, 'r') + else: + input = sys.stdin + _Symbolize(input.readlines()) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/build/android/avd.py b/build/android/avd.py new file mode 100755 index 0000000000..2685c592be --- /dev/null +++ b/build/android/avd.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +# 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. + +"""Launches Android Virtual Devices with a set configuration for testing Chrome. + +The script will launch a specified number of Android Virtual Devices (AVD's). +""" + + +import install_emulator_deps +import logging +import optparse +import os +import subprocess +import sys + +from pylib import constants +from pylib.utils import emulator + + +def main(argv): + # ANDROID_SDK_ROOT needs to be set to the location of the SDK used to launch + # the emulator to find the system images upon launch. + emulator_sdk = os.path.join(constants.EMULATOR_SDK_ROOT, + 'android_tools', 'sdk') + os.environ['ANDROID_SDK_ROOT'] = emulator_sdk + + opt_parser = optparse.OptionParser(description='AVD script.') + opt_parser.add_option('-n', '--num', dest='emulator_count', + help='Number of emulators to launch (default is 1).', + type='int', default='1') + opt_parser.add_option('--abi', default='x86', + help='Platform of emulators to launch (x86 default).') + + options, _ = opt_parser.parse_args(argv[1:]) + + logging.basicConfig(level=logging.INFO, + format='# %(asctime)-15s: %(message)s') + logging.root.setLevel(logging.INFO) + + # Check if KVM is enabled for x86 AVD's and check for x86 system images. + if options.abi =='x86': + if not install_emulator_deps.CheckKVM(): + logging.critical('ERROR: KVM must be enabled in BIOS, and installed. ' + 'Enable KVM in BIOS and run install_emulator_deps.py') + return 1 + elif not install_emulator_deps.CheckX86Image(): + logging.critical('ERROR: System image for x86 AVD not installed. Run ' + 'install_emulator_deps.py') + return 1 + + if not install_emulator_deps.CheckSDK(): + logging.critical('ERROR: Emulator SDK not installed. Run ' + 'install_emulator_deps.py.') + return 1 + + emulator.LaunchEmulators(options.emulator_count, options.abi, True) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/build/android/avd_configs/AVD_for_Galaxy_Nexus_by_Google_arm.avd/config.ini b/build/android/avd_configs/AVD_for_Galaxy_Nexus_by_Google_arm.avd/config.ini new file mode 100644 index 0000000000..0b2f972337 --- /dev/null +++ b/build/android/avd_configs/AVD_for_Galaxy_Nexus_by_Google_arm.avd/config.ini @@ -0,0 +1,30 @@ +avd.ini.encoding=ISO-8859-1 +hw.dPad=no +hw.lcd.density=320 +sdcard.size=512M +hw.cpu.arch=arm +hw.device.hash=-708107041 +hw.camera.back=none +disk.dataPartition.size=800M +hw.gpu.enabled=yes +skin.dynamic=yes +skin.path=720x1280 +hw.keyboard=yes +hw.cpu.model=cortex-a8 +hw.ramSize=1024 +hw.device.manufacturer=Google +hw.sdCard=yes +hw.mainKeys=no +hw.accelerometer=yes +skin.name=720x1280 +abi.type=armeabi-v7a +hw.trackBall=no +hw.device.name=Galaxy Nexus +hw.battery=yes +hw.sensors.proximity=yes +image.sysdir.1=system-images/android-17/armeabi-v7a/ +hw.sensors.orientation=yes +hw.audioInput=yes +hw.camera.front=none +hw.gps=yes +vm.heapSize=128 diff --git a/build/android/avd_configs/AVD_for_Galaxy_Nexus_by_Google_x86.avd/config.ini b/build/android/avd_configs/AVD_for_Galaxy_Nexus_by_Google_x86.avd/config.ini new file mode 100644 index 0000000000..567ba78e21 --- /dev/null +++ b/build/android/avd_configs/AVD_for_Galaxy_Nexus_by_Google_x86.avd/config.ini @@ -0,0 +1,29 @@ +avd.ini.encoding=ISO-8859-1 +hw.dPad=no +hw.lcd.density=320 +sdcard.size=512M +hw.cpu.arch=x86 +hw.device.hash=-708107041 +hw.camera.back=none +disk.dataPartition.size=800M +hw.gpu.enabled=yes +skin.path=720x1280 +skin.dynamic=yes +hw.keyboard=yes +hw.ramSize=1024 +hw.device.manufacturer=Google +hw.sdCard=yes +hw.mainKeys=no +hw.accelerometer=yes +skin.name=720x1280 +abi.type=x86 +hw.trackBall=no +hw.device.name=Galaxy Nexus +hw.battery=yes +hw.sensors.proximity=yes +image.sysdir.1=system-images/android-17/x86/ +hw.sensors.orientation=yes +hw.audioInput=yes +hw.camera.front=none +hw.gps=yes +vm.heapSize=128 diff --git a/build/android/bb_run_sharded_steps.py b/build/android/bb_run_sharded_steps.py new file mode 100755 index 0000000000..99841db61e --- /dev/null +++ b/build/android/bb_run_sharded_steps.py @@ -0,0 +1,42 @@ +#!/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. + +"""DEPRECATED! +TODO(bulach): remove me once all other repositories reference +'test_runner.py perf' directly. +""" + +import optparse +import os +import sys + +from pylib import cmd_helper + + +def main(argv): + parser = optparse.OptionParser() + parser.add_option('-s', '--steps', + help='A JSON file containing all the steps to be ' + 'sharded.') + parser.add_option('--flaky_steps', + help='A JSON file containing steps that are flaky and ' + 'will have its exit code ignored.') + parser.add_option('-p', '--print_results', + help='Only prints the results for the previously ' + 'executed step, do not run it again.') + options, urls = parser.parse_args(argv) + if options.print_results: + return cmd_helper.RunCmd(['build/android/test_runner.py', 'perf', + '--print-step', options.print_results]) + flaky_options = [] + if options.flaky_steps: + flaky_options = ['--flaky-steps', options.flaky_steps] + return cmd_helper.RunCmd(['build/android/test_runner.py', 'perf', + '--steps', options.steps] + flaky_options) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/build/android/buildbot/OWNERS b/build/android/buildbot/OWNERS new file mode 100644 index 0000000000..e35895556b --- /dev/null +++ b/build/android/buildbot/OWNERS @@ -0,0 +1,6 @@ +set noparent + +bulach@chromium.org +cmp@chromium.org +ilevy@chromium.org +yfriedman@chromium.org diff --git a/build/android/buildbot/bb_annotations.py b/build/android/buildbot/bb_annotations.py new file mode 100644 index 0000000000..059d673188 --- /dev/null +++ b/build/android/buildbot/bb_annotations.py @@ -0,0 +1,46 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Helper functions to print buildbot messages.""" + +def PrintLink(label, url): + """Adds a link with name |label| linking to |url| to current buildbot step. + + Args: + label: A string with the name of the label. + url: A string of the URL. + """ + print '@@@STEP_LINK@%s@%s@@@' % (label, url) + + +def PrintMsg(msg): + """Appends |msg| to the current buildbot step text. + + Args: + msg: String to be appended. + """ + print '@@@STEP_TEXT@%s@@@' % msg + + +def PrintSummaryText(msg): + """Appends |msg| to main build summary. Visible from waterfall. + + Args: + msg: String to be appended. + """ + print '@@@STEP_SUMMARY_TEXT@%s@@@' % msg + + +def PrintError(): + """Marks the current step as failed.""" + print '@@@STEP_FAILURE@@@' + + +def PrintWarning(): + """Marks the current step with a warning.""" + print '@@@STEP_WARNINGS@@@' + + +def PrintNamedStep(step): + print '@@@BUILD_STEP %s@@@' % step diff --git a/build/android/buildbot/bb_device_status_check.py b/build/android/buildbot/bb_device_status_check.py new file mode 100755 index 0000000000..ae0bb4a7b1 --- /dev/null +++ b/build/android/buildbot/bb_device_status_check.py @@ -0,0 +1,260 @@ +#!/usr/bin/env python +# +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""A class to keep track of devices across builds and report state.""" +import logging +import optparse +import os +import smtplib +import sys +import re +import urllib + +import bb_annotations + +sys.path.append(os.path.join(os.path.dirname(__file__), '..')) +from pylib import android_commands +from pylib import constants +from pylib import perf_tests_helper +from pylib.cmd_helper import GetCmdOutput + + +def DeviceInfo(serial, options): + """Gathers info on a device via various adb calls. + + Args: + serial: The serial of the attached device to construct info about. + + Returns: + Tuple of device type, build id, report as a string, error messages, and + boolean indicating whether or not device can be used for testing. + """ + + device_adb = android_commands.AndroidCommands(serial) + + # TODO(navabi): Replace AdbShellCmd with device_adb. + device_type = device_adb.GetBuildProduct() + device_build = device_adb.GetBuildId() + device_build_type = device_adb.GetBuildType() + device_product_name = device_adb.GetProductName() + + setup_wizard_disabled = device_adb.GetSetupWizardStatus() == 'DISABLED' + battery = device_adb.GetBatteryInfo() + install_output = GetCmdOutput( + ['%s/build/android/adb_install_apk.py' % constants.DIR_SOURCE_ROOT, '--apk', + '%s/build/android/CheckInstallApk-debug.apk' % constants.DIR_SOURCE_ROOT]) + + def _GetData(re_expression, line, lambda_function=lambda x:x): + if not line: + return 'Unknown' + found = re.findall(re_expression, line) + if found and len(found): + return lambda_function(found[0]) + return 'Unknown' + + install_speed = _GetData('(\d+) KB/s', install_output) + ac_power = _GetData('AC powered: (\w+)', battery) + battery_level = _GetData('level: (\d+)', battery) + battery_temp = _GetData('temperature: (\d+)', battery, + lambda x: float(x) / 10.0) + imei_slice = _GetData('Device ID = (\d+)', + device_adb.GetSubscriberInfo(), + lambda x: x[-6:]) + report = ['Device %s (%s)' % (serial, device_type), + ' Build: %s (%s)' % + (device_build, device_adb.GetBuildFingerprint()), + ' Battery: %s%%' % battery_level, + ' Battery temp: %s' % battery_temp, + ' IMEI slice: %s' % imei_slice, + ' Wifi IP: %s' % device_adb.GetWifiIP(), + ' Install Speed: %s KB/s' % install_speed, + ''] + + errors = [] + if battery_level < 15: + errors += ['Device critically low in battery. Turning off device.'] + if (not setup_wizard_disabled and device_build_type != 'user' and + not options.no_provisioning_check): + errors += ['Setup wizard not disabled. Was it provisioned correctly?'] + if device_product_name == 'mantaray' and ac_power != 'true': + errors += ['Mantaray device not connected to AC power.'] + # TODO(navabi): Insert warning once we have a better handle of what install + # speeds to expect. The following lines were causing too many alerts. + # if install_speed < 500: + # errors += ['Device install speed too low. Do not use for testing.'] + + # Causing the device status check step fail for slow install speed or low + # battery currently is too disruptive to the bots (especially try bots). + # Turn off devices with low battery and the step does not fail. + if battery_level < 15: + device_adb.EnableAdbRoot() + device_adb.Shutdown() + full_report = '\n'.join(report) + return device_type, device_build, battery_level, full_report, errors, True + + +def CheckForMissingDevices(options, adb_online_devs): + """Uses file of previous online devices to detect broken phones. + + Args: + options: out_dir parameter of options argument is used as the base + directory to load and update the cache file. + adb_online_devs: A list of serial numbers of the currently visible + and online attached devices. + """ + # TODO(navabi): remove this once the bug that causes different number + # of devices to be detected between calls is fixed. + logger = logging.getLogger() + logger.setLevel(logging.INFO) + + out_dir = os.path.abspath(options.out_dir) + + def ReadDeviceList(file_name): + devices_path = os.path.join(out_dir, file_name) + devices = [] + try: + with open(devices_path) as f: + devices = f.read().splitlines() + except IOError: + # Ignore error, file might not exist + pass + return devices + + def WriteDeviceList(file_name, device_list): + path = os.path.join(out_dir, file_name) + if not os.path.exists(out_dir): + os.makedirs(out_dir) + with open(path, 'w') as f: + # Write devices currently visible plus devices previously seen. + f.write('\n'.join(set(device_list))) + + last_devices_path = os.path.join(out_dir, '.last_devices') + last_devices = ReadDeviceList('.last_devices') + missing_devs = list(set(last_devices) - set(adb_online_devs)) + + all_known_devices = list(set(adb_online_devs) | set(last_devices)) + WriteDeviceList('.last_devices', all_known_devices) + WriteDeviceList('.last_missing', missing_devs) + + if not all_known_devices: + # This can happen if for some reason the .last_devices file is not + # present or if it was empty. + return ['No online devices. Have any devices been plugged in?'] + if missing_devs: + devices_missing_msg = '%d devices not detected.' % len(missing_devs) + bb_annotations.PrintSummaryText(devices_missing_msg) + + # TODO(navabi): Debug by printing both output from GetCmdOutput and + # GetAttachedDevices to compare results. + crbug_link = ('https://code.google.com/p/chromium/issues/entry?summary=' + '%s&comment=%s&labels=Restrict-View-Google,OS-Android,Infra' % + (urllib.quote('Device Offline'), + urllib.quote('Buildbot: %s %s\n' + 'Build: %s\n' + '(please don\'t change any labels)' % + (os.environ.get('BUILDBOT_BUILDERNAME'), + os.environ.get('BUILDBOT_SLAVENAME'), + os.environ.get('BUILDBOT_BUILDNUMBER'))))) + return ['Current online devices: %s' % adb_online_devs, + '%s are no longer visible. Were they removed?\n' % missing_devs, + 'SHERIFF:\n', + '@@@STEP_LINK@Click here to file a bug@%s@@@\n' % crbug_link, + 'Cache file: %s\n\n' % last_devices_path, + 'adb devices: %s' % GetCmdOutput(['adb', 'devices']), + 'adb devices(GetAttachedDevices): %s' % + android_commands.GetAttachedDevices()] + else: + new_devs = set(adb_online_devs) - set(last_devices) + if new_devs and os.path.exists(last_devices_path): + bb_annotations.PrintWarning() + bb_annotations.PrintSummaryText( + '%d new devices detected' % len(new_devs)) + print ('New devices detected %s. And now back to your ' + 'regularly scheduled program.' % list(new_devs)) + + +def SendDeviceStatusAlert(msg): + from_address = 'buildbot@chromium.org' + to_address = 'chromium-android-device-alerts@google.com' + bot_name = os.environ.get('BUILDBOT_BUILDERNAME') + slave_name = os.environ.get('BUILDBOT_SLAVENAME') + subject = 'Device status check errors on %s, %s.' % (slave_name, bot_name) + msg_body = '\r\n'.join(['From: %s' % from_address, 'To: %s' % to_address, + 'Subject: %s' % subject, '', msg]) + try: + server = smtplib.SMTP('localhost') + server.sendmail(from_address, [to_address], msg_body) + server.quit() + except Exception as e: + print 'Failed to send alert email. Error: %s' % e + + +def main(): + parser = optparse.OptionParser() + parser.add_option('', '--out-dir', + help='Directory where the device path is stored', + default=os.path.join(constants.DIR_SOURCE_ROOT, 'out')) + parser.add_option('--no-provisioning-check', + help='Will not check if devices are provisioned properly.') + parser.add_option('--device-status-dashboard', + help='Output device status data for dashboard.') + options, args = parser.parse_args() + if args: + parser.error('Unknown options %s' % args) + devices = android_commands.GetAttachedDevices() + # TODO(navabi): Test to make sure this fails and then fix call + offline_devices = android_commands.GetAttachedDevices(hardware=False, + emulator=False, + offline=True) + + types, builds, batteries, reports, errors = [], [], [], [], [] + fail_step_lst = [] + if devices: + types, builds, batteries, reports, errors, fail_step_lst = ( + zip(*[DeviceInfo(dev, options) for dev in devices])) + + err_msg = CheckForMissingDevices(options, devices) or [] + + unique_types = list(set(types)) + unique_builds = list(set(builds)) + + bb_annotations.PrintMsg('Online devices: %d. Device types %s, builds %s' + % (len(devices), unique_types, unique_builds)) + print '\n'.join(reports) + + for serial, dev_errors in zip(devices, errors): + if dev_errors: + err_msg += ['%s errors:' % serial] + err_msg += [' %s' % error for error in dev_errors] + + if err_msg: + bb_annotations.PrintWarning() + msg = '\n'.join(err_msg) + print msg + SendDeviceStatusAlert(msg) + + if options.device_status_dashboard: + perf_tests_helper.PrintPerfResult('BotDevices', 'OnlineDevices', + [len(devices)], 'devices') + perf_tests_helper.PrintPerfResult('BotDevices', 'OfflineDevices', + [len(offline_devices)], 'devices', + 'unimportant') + for serial, battery in zip(devices, batteries): + perf_tests_helper.PrintPerfResult('DeviceBattery', serial, [battery], '%', + 'unimportant') + + if False in fail_step_lst: + # TODO(navabi): Build fails on device status check step if there exists any + # devices with critically low battery or install speed. Remove those devices + # from testing, allowing build to continue with good devices. + return 1 + + if not devices: + return 1 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/build/android/buildbot/bb_device_steps.py b/build/android/buildbot/bb_device_steps.py new file mode 100755 index 0000000000..1d83a96e23 --- /dev/null +++ b/build/android/buildbot/bb_device_steps.py @@ -0,0 +1,387 @@ +#!/usr/bin/env python +# 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. + +import collections +import glob +import multiprocessing +import os +import shutil +import sys + +import bb_utils +import bb_annotations + +sys.path.append(os.path.join(os.path.dirname(__file__), '..')) +import provision_devices +from pylib import android_commands +from pylib import constants +from pylib.gtest import gtest_config + +sys.path.append(os.path.join( + constants.DIR_SOURCE_ROOT, 'third_party', 'android_testrunner')) +import errors + + +CHROME_SRC = constants.DIR_SOURCE_ROOT +LOGCAT_DIR = os.path.join(CHROME_SRC, 'out', 'logcat') + +# Describes an instrumation test suite: +# test: Name of test we're running. +# apk: apk to be installed. +# apk_package: package for the apk to be installed. +# test_apk: apk to run tests on. +# test_data: data folder in format destination:source. +# host_driven_root: The host-driven test root directory. +# annotation: Annotation of the tests to include. +# exclude_annotation: The annotation of the tests to exclude. +I_TEST = collections.namedtuple('InstrumentationTest', [ + 'name', 'apk', 'apk_package', 'test_apk', 'test_data', 'host_driven_root', + 'annotation', 'exclude_annotation', 'extra_flags']) + +def I(name, apk, apk_package, test_apk, test_data, host_driven_root=None, + annotation=None, exclude_annotation=None, extra_flags=None): + return I_TEST(name, apk, apk_package, test_apk, test_data, host_driven_root, + annotation, exclude_annotation, extra_flags) + +INSTRUMENTATION_TESTS = dict((suite.name, suite) for suite in [ + I('ContentShell', + 'ContentShell.apk', + 'org.chromium.content_shell_apk', + 'ContentShellTest', + 'content:content/test/data/android/device_files'), + I('ChromiumTestShell', + 'ChromiumTestShell.apk', + 'org.chromium.chrome.testshell', + 'ChromiumTestShellTest', + 'chrome:chrome/test/data/android/device_files', + constants.CHROMIUM_TEST_SHELL_HOST_DRIVEN_DIR), + I('AndroidWebView', + 'AndroidWebView.apk', + 'org.chromium.android_webview.shell', + 'AndroidWebViewTest', + 'webview:android_webview/test/data/device_files'), + ]) + +VALID_TESTS = set(['chromedriver', 'ui', 'unit', 'webkit', 'webkit_layout', + 'webrtc']) + +RunCmd = bb_utils.RunCmd + + +# multiprocessing map_async requires a top-level function for pickle library. +def RebootDeviceSafe(device): + """Reboot a device, wait for it to start, and squelch timeout exceptions.""" + try: + android_commands.AndroidCommands(device).Reboot(True) + except errors.DeviceUnresponsiveError as e: + return e + + +def RebootDevices(): + """Reboot all attached and online devices.""" + # Early return here to avoid presubmit dependence on adb, + # which might not exist in this checkout. + if bb_utils.TESTING: + return + devices = android_commands.GetAttachedDevices(emulator=False) + print 'Rebooting: %s' % devices + if devices: + pool = multiprocessing.Pool(len(devices)) + results = pool.map_async(RebootDeviceSafe, devices).get(99999) + + for device, result in zip(devices, results): + if result: + print '%s failed to startup.' % device + + if any(results): + bb_annotations.PrintWarning() + else: + print 'Reboots complete.' + + +def RunTestSuites(options, suites): + """Manages an invocation of test_runner.py for gtests. + + Args: + options: options object. + suites: List of suite names to run. + """ + args = ['--verbose'] + if options.target == 'Release': + args.append('--release') + if options.asan: + args.append('--tool=asan') + for suite in suites: + bb_annotations.PrintNamedStep(suite) + cmd = ['build/android/test_runner.py', 'gtest', '-s', suite] + args + if suite == 'content_browsertests': + cmd.append('--num_retries=1') + RunCmd(cmd) + +def RunChromeDriverTests(_): + """Run all the steps for running chromedriver tests.""" + bb_annotations.PrintNamedStep('chromedriver_annotation') + RunCmd(['chrome/test/chromedriver/run_buildbot_steps.py', + '--android-package=%s' % constants.CHROMIUM_TEST_SHELL_PACKAGE]) + +def InstallApk(options, test, print_step=False): + """Install an apk to all phones. + + Args: + options: options object + test: An I_TEST namedtuple + print_step: Print a buildbot step + """ + if print_step: + bb_annotations.PrintNamedStep('install_%s' % test.name.lower()) + args = ['--apk', test.apk, '--apk_package', test.apk_package] + if options.target == 'Release': + args.append('--release') + + RunCmd(['build/android/adb_install_apk.py'] + args, halt_on_failure=True) + + +def RunInstrumentationSuite(options, test, flunk_on_failure=True, + python_only=False): + """Manages an invocation of test_runner.py for instrumentation tests. + + Args: + options: options object + test: An I_TEST namedtuple + flunk_on_failure: Flunk the step if tests fail. + Python: Run only host driven Python tests. + """ + bb_annotations.PrintNamedStep('%s_instrumentation_tests' % test.name.lower()) + + InstallApk(options, test) + args = ['--test-apk', test.test_apk, '--test_data', test.test_data, + '--verbose'] + if options.target == 'Release': + args.append('--release') + if options.asan: + args.append('--tool=asan') + if options.flakiness_server: + args.append('--flakiness-dashboard-server=%s' % + options.flakiness_server) + if test.host_driven_root: + args.append('--host-driven-root=%s' % test.host_driven_root) + if test.annotation: + args.extend(['-A', test.annotation]) + if test.exclude_annotation: + args.extend(['-E', test.exclude_annotation]) + if test.extra_flags: + args.extend(test.extra_flags) + if python_only: + args.append('-p') + + RunCmd(['build/android/test_runner.py', 'instrumentation'] + args, + flunk_on_failure=flunk_on_failure) + + +def RunWebkitLint(target): + """Lint WebKit's TestExpectation files.""" + bb_annotations.PrintNamedStep('webkit_lint') + RunCmd(['webkit/tools/layout_tests/run_webkit_tests.py', + '--lint-test-files', + '--chromium', + '--target', target]) + + +def RunWebkitLayoutTests(options): + """Run layout tests on an actual device.""" + bb_annotations.PrintNamedStep('webkit_tests') + cmd_args = [ + '--no-show-results', + '--no-new-test-results', + '--full-results-html', + '--clobber-old-results', + '--exit-after-n-failures', '5000', + '--exit-after-n-crashes-or-timeouts', '100', + '--debug-rwt-logging', + '--results-directory', '..layout-test-results', + '--target', options.target, + '--builder-name', options.build_properties.get('buildername', ''), + '--build-number', str(options.build_properties.get('buildnumber', '')), + '--master-name', options.build_properties.get('mastername', ''), + '--build-name', options.build_properties.get('buildername', ''), + '--platform=android'] + + for flag in 'test_results_server', 'driver_name', 'additional_drt_flag': + if flag in options.factory_properties: + cmd_args.extend(['--%s' % flag.replace('_', '-'), + options.factory_properties.get(flag)]) + + for f in options.factory_properties.get('additional_expectations', []): + cmd_args.extend( + ['--additional-expectations=%s' % os.path.join(CHROME_SRC, *f)]) + + # TODO(dpranke): Remove this block after + # https://codereview.chromium.org/12927002/ lands. + for f in options.factory_properties.get('additional_expectations_files', []): + cmd_args.extend( + ['--additional-expectations=%s' % os.path.join(CHROME_SRC, *f)]) + + RunCmd(['webkit/tools/layout_tests/run_webkit_tests.py'] + cmd_args, + flunk_on_failure=False) + + +def SpawnLogcatMonitor(): + shutil.rmtree(LOGCAT_DIR, ignore_errors=True) + bb_utils.SpawnCmd([ + os.path.join(CHROME_SRC, 'build', 'android', 'adb_logcat_monitor.py'), + LOGCAT_DIR]) + + # Wait for logcat_monitor to pull existing logcat + RunCmd(['sleep', '5']) + +def ProvisionDevices(options): + # Restart adb to work around bugs, sleep to wait for usb discovery. + RunCmd(['adb', 'kill-server']) + RunCmd(['adb', 'start-server']) + RunCmd(['sleep', '1']) + + bb_annotations.PrintNamedStep('provision_devices') + if options.reboot: + RebootDevices() + provision_cmd = ['build/android/provision_devices.py', '-t', options.target] + if options.auto_reconnect: + provision_cmd.append('--auto-reconnect') + RunCmd(provision_cmd) + + +def DeviceStatusCheck(_): + bb_annotations.PrintNamedStep('device_status_check') + RunCmd(['build/android/buildbot/bb_device_status_check.py'], + halt_on_failure=True) + + +def GetDeviceSetupStepCmds(): + return [ + ('provision_devices', ProvisionDevices), + ('device_status_check', DeviceStatusCheck) + ] + + +def RunUnitTests(options): + RunTestSuites(options, gtest_config.STABLE_TEST_SUITES) + + +def RunInstrumentationTests(options): + for test in INSTRUMENTATION_TESTS.itervalues(): + RunInstrumentationSuite(options, test) + + +def RunWebkitTests(options): + RunTestSuites(options, ['webkit_unit_tests']) + RunWebkitLint(options.target) + + +def RunWebRTCTests(options): + RunTestSuites(options, gtest_config.WEBRTC_TEST_SUITES) + + +def GetTestStepCmds(): + return [ + ('chromedriver', RunChromeDriverTests), + ('unit', RunUnitTests), + ('ui', RunInstrumentationTests), + ('webkit', RunWebkitTests), + ('webkit_layout', RunWebkitLayoutTests), + ('webrtc', RunWebRTCTests), + ] + + +def LogcatDump(options): + # Print logcat, kill logcat monitor + bb_annotations.PrintNamedStep('logcat_dump') + logcat_file = os.path.join(CHROME_SRC, 'out', options.target, 'full_log') + with open(logcat_file, 'w') as f: + RunCmd([ + os.path.join(CHROME_SRC, 'build', 'android', 'adb_logcat_printer.py'), + LOGCAT_DIR], stdout=f) + RunCmd(['cat', logcat_file]) + + +def GenerateTestReport(options): + bb_annotations.PrintNamedStep('test_report') + for report in glob.glob( + os.path.join(CHROME_SRC, 'out', options.target, 'test_logs', '*.log')): + RunCmd(['cat', report]) + os.remove(report) + + +def MainTestWrapper(options): + try: + # Spawn logcat monitor + SpawnLogcatMonitor() + + # Run all device setup steps + for _, cmd in GetDeviceSetupStepCmds(): + cmd(options) + + if options.install: + test_obj = INSTRUMENTATION_TESTS[options.install] + InstallApk(options, test_obj, print_step=True) + + if options.test_filter: + bb_utils.RunSteps(options.test_filter, GetTestStepCmds(), options) + + if options.experimental: + RunTestSuites(options, gtest_config.EXPERIMENTAL_TEST_SUITES) + + finally: + # Run all post test steps + LogcatDump(options) + GenerateTestReport(options) + # KillHostHeartbeat() has logic to check if heartbeat process is running, + # and kills only if it finds the process is running on the host. + provision_devices.KillHostHeartbeat() + + +def GetDeviceStepsOptParser(): + parser = bb_utils.GetParser() + parser.add_option('--experimental', action='store_true', + help='Run experiemental tests') + parser.add_option('-f', '--test-filter', metavar='', default=[], + action='append', + help=('Run a test suite. Test suites: "%s"' % + '", "'.join(VALID_TESTS))) + parser.add_option('--asan', action='store_true', help='Run tests with asan.') + parser.add_option('--install', metavar='', + help='Install an apk by name') + parser.add_option('--reboot', action='store_true', + help='Reboot devices before running tests') + parser.add_option( + '--flakiness-server', + help='The flakiness dashboard server to which the results should be ' + 'uploaded.') + parser.add_option( + '--auto-reconnect', action='store_true', + help='Push script to device which restarts adbd on disconnections.') + parser.add_option( + '--logcat-dump-output', + help='The logcat dump output will be "tee"-ed into this file') + + return parser + + +def main(argv): + parser = GetDeviceStepsOptParser() + options, args = parser.parse_args(argv[1:]) + + if args: + return sys.exit('Unused args %s' % args) + + unknown_tests = set(options.test_filter) - VALID_TESTS + if unknown_tests: + return sys.exit('Unknown tests %s' % list(unknown_tests)) + + setattr(options, 'target', options.factory_properties.get('target', 'Debug')) + + MainTestWrapper(options) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/build/android/buildbot/bb_host_steps.py b/build/android/buildbot/bb_host_steps.py new file mode 100755 index 0000000000..a28f5765e4 --- /dev/null +++ b/build/android/buildbot/bb_host_steps.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import os +import sys + +import bb_utils +import bb_annotations + +sys.path.append(os.path.join(os.path.dirname(__file__), '..')) +from pylib import constants + + +SLAVE_SCRIPTS_DIR = os.path.join(bb_utils.BB_BUILD_DIR, 'scripts', 'slave') +VALID_HOST_TESTS = set(['check_webview_licenses', 'findbugs']) +EXPERIMENTAL_TARGETS = ['android_experimental'] + +# Short hand for RunCmd which is used extensively in this file. +RunCmd = bb_utils.RunCmd + + +def SrcPath(*path): + return os.path.join(constants.DIR_SOURCE_ROOT, *path) + + +def CheckWebViewLicenses(_): + bb_annotations.PrintNamedStep('check_licenses') + RunCmd([SrcPath('android_webview', 'tools', 'webview_licenses.py'), 'scan'], + warning_code=1) + + +def RunHooks(build_type): + RunCmd([SrcPath('build', 'landmines.py')]) + build_path = SrcPath('out', build_type) + landmine_path = os.path.join(build_path, '.landmines_triggered') + clobber_env = os.environ.get('BUILDBOT_CLOBBER') + if clobber_env or os.path.isfile(landmine_path): + bb_annotations.PrintNamedStep('Clobber') + if not clobber_env: + print 'Clobbering due to triggered landmines:' + with open(landmine_path) as f: + print f.read() + RunCmd(['rm', '-rf', build_path]) + + bb_annotations.PrintNamedStep('runhooks') + RunCmd(['gclient', 'runhooks'], halt_on_failure=True) + + +def Compile(options): + RunHooks(options.target) + cmd = [os.path.join(SLAVE_SCRIPTS_DIR, 'compile.py'), + '--build-tool=ninja', + '--compiler=goma', + '--target=%s' % options.target, + '--goma-dir=%s' % bb_utils.GOMA_DIR] + build_targets = options.build_targets.split(',') + bb_annotations.PrintNamedStep('compile') + for build_target in build_targets: + RunCmd(cmd + ['--build-args=%s' % build_target], halt_on_failure=True) + if options.experimental: + for compile_target in EXPERIMENTAL_TARGETS: + bb_annotations.PrintNamedStep('Experimental Compile %s' % compile_target) + RunCmd(cmd + ['--build-args=%s' % compile_target], flunk_on_failure=False) + + +def ZipBuild(options): + bb_annotations.PrintNamedStep('zip_build') + RunCmd([ + os.path.join(SLAVE_SCRIPTS_DIR, 'zip_build.py'), + '--src-dir', constants.DIR_SOURCE_ROOT, + '--build-dir', SrcPath('out'), + '--exclude-files', 'lib.target,gen,android_webview,jingle_unittests'] + + bb_utils.EncodeProperties(options)) + + +def ExtractBuild(options): + bb_annotations.PrintNamedStep('extract_build') + RunCmd( + [os.path.join(SLAVE_SCRIPTS_DIR, 'extract_build.py'), + '--build-dir', SrcPath('build'), '--build-output-dir', + SrcPath('out')] + bb_utils.EncodeProperties(options), + warning_code=1) + + +def FindBugs(options): + bb_annotations.PrintNamedStep('findbugs') + build_type = [] + if options.target == 'Release': + build_type = ['--release-build'] + RunCmd([SrcPath('build', 'android', 'findbugs_diff.py')] + build_type) + RunCmd([SrcPath( + 'tools', 'android', 'findbugs_plugin', 'test', + 'run_findbugs_plugin_tests.py')] + build_type) + + +def BisectPerfRegression(_): + bb_annotations.PrintNamedStep('Bisect Perf Regression') + RunCmd([SrcPath('tools', 'prepare-bisect-perf-regression.py'), + '-w', os.path.join(constants.DIR_SOURCE_ROOT, os.pardir)]) + RunCmd([SrcPath('tools', 'run-bisect-perf-regression.py'), + '-w', os.path.join(constants.DIR_SOURCE_ROOT, os.pardir)]) + + +def GetHostStepCmds(): + return [ + ('compile', Compile), + ('extract_build', ExtractBuild), + ('check_webview_licenses', CheckWebViewLicenses), + ('bisect_perf_regression', BisectPerfRegression), + ('findbugs', FindBugs), + ('zip_build', ZipBuild) + ] + + +def GetHostStepsOptParser(): + parser = bb_utils.GetParser() + parser.add_option('--steps', help='Comma separated list of host tests.') + parser.add_option('--build-targets', default='All', + help='Comma separated list of build targets.') + parser.add_option('--experimental', action='store_true', + help='Indicate whether to compile experimental targets.') + + return parser + + +def main(argv): + parser = GetHostStepsOptParser() + options, args = parser.parse_args(argv[1:]) + if args: + return sys.exit('Unused args %s' % args) + + setattr(options, 'target', options.factory_properties.get('target', 'Debug')) + + if options.steps: + bb_utils.RunSteps(options.steps.split(','), GetHostStepCmds(), options) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/build/android/buildbot/bb_run_bot.py b/build/android/buildbot/bb_run_bot.py new file mode 100755 index 0000000000..7637024693 --- /dev/null +++ b/build/android/buildbot/bb_run_bot.py @@ -0,0 +1,273 @@ +#!/usr/bin/env python +# +# 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. + +import collections +import copy +import json +import os +import pipes +import re +import subprocess +import sys + +import bb_utils + +sys.path.append(os.path.join(os.path.dirname(__file__), '..')) +from pylib import constants + + +_BotConfig = collections.namedtuple( + 'BotConfig', ['bot_id', 'host_obj', 'test_obj']) + +HostConfig = collections.namedtuple( + 'HostConfig', + ['script', 'host_steps', 'extra_args', 'extra_gyp_defines', 'target_arch']) + +TestConfig = collections.namedtuple('Tests', ['script', 'tests', 'extra_args']) + + +def BotConfig(bot_id, host_object, test_object=None): + return _BotConfig(bot_id, host_object, test_object) + + +def DictDiff(d1, d2): + diff = [] + for key in sorted(set(d1.keys() + d2.keys())): + if key in d1 and d1[key] != d2.get(key): + diff.append('- %s=%s' % (key, pipes.quote(d1[key]))) + if key in d2 and d2[key] != d1.get(key): + diff.append('+ %s=%s' % (key, pipes.quote(d2[key]))) + return '\n'.join(diff) + + +def GetEnvironment(host_obj, testing): + init_env = dict(os.environ) + init_env['GYP_GENERATORS'] = 'ninja' + init_env['GOMA_DIR'] = bb_utils.GOMA_DIR + envsetup_cmd = '. build/android/envsetup.sh' + if host_obj.target_arch: + envsetup_cmd += ' --target-arch=%s' % host_obj.target_arch + if testing: + # Skip envsetup to avoid presubmit dependence on android deps. + print 'Testing mode - skipping "%s"' % envsetup_cmd + envsetup_cmd = ':' + else: + print 'Running %s' % envsetup_cmd + proc = subprocess.Popen(['bash', '-exc', + envsetup_cmd + ' >&2; python build/android/buildbot/env_to_json.py'], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + cwd=bb_utils.CHROME_SRC, env=init_env) + json_env, envsetup_output = proc.communicate() + if proc.returncode != 0: + print >> sys.stderr, 'FATAL Failure in envsetup.' + print >> sys.stderr, envsetup_output + sys.exit(1) + env = json.loads(json_env) + env['GYP_DEFINES'] = env.get('GYP_DEFINES', '') + ' fastbuild=1' + extra_gyp = host_obj.extra_gyp_defines + if extra_gyp: + env['GYP_DEFINES'] += ' %s' % extra_gyp + if re.search('(asan|clang)=1', extra_gyp): + env.pop('CXX_target', None) + + # Bots checkout chrome in /b/build/slave//build/src + build_internal_android = os.path.abspath(os.path.join( + bb_utils.CHROME_SRC, '..', '..', '..', '..', '..', 'build_internal', + 'scripts', 'slave', 'android')) + if os.path.exists(build_internal_android): + env['PATH'] = os.pathsep.join([build_internal_android, env['PATH']]) + return env + + +def GetCommands(options, bot_config): + """Get a formatted list of commands. + + Args: + options: Options object. + bot_config: A BotConfig named tuple. + host_step_script: Host step script. + device_step_script: Device step script. + Returns: + list of Command objects. + """ + property_args = bb_utils.EncodeProperties(options) + commands = [[bot_config.host_obj.script, + '--steps=%s' % ','.join(bot_config.host_obj.host_steps)] + + property_args + (bot_config.host_obj.extra_args or [])] + + test_obj = bot_config.test_obj + if test_obj: + run_test_cmd = [test_obj.script, '--reboot'] + property_args + for test in test_obj.tests: + run_test_cmd.extend(['-f', test]) + if test_obj.extra_args: + run_test_cmd.extend(test_obj.extra_args) + commands.append(run_test_cmd) + return commands + + +def GetBotStepMap(): + compile_step = ['compile'] + std_host_tests = ['check_webview_licenses', 'findbugs'] + std_build_steps = ['compile', 'zip_build'] + std_test_steps = ['extract_build'] + std_tests = ['ui', 'unit'] + flakiness_server = ( + '--flakiness-server=%s' % constants.UPSTREAM_FLAKINESS_SERVER) + experimental = ['--experimental'] + + B = BotConfig + H = (lambda steps, extra_args=None, extra_gyp=None, target_arch=None : + HostConfig('build/android/buildbot/bb_host_steps.py', steps, extra_args, + extra_gyp, target_arch)) + T = (lambda tests, extra_args=None : + TestConfig('build/android/buildbot/bb_device_steps.py', tests, + extra_args)) + + bot_configs = [ + # Main builders + B('main-builder-dbg', H(std_build_steps + std_host_tests)), + B('main-builder-rel', H(std_build_steps)), + B('main-clang-builder', + H(compile_step, extra_gyp='clang=1 component=shared_library')), + B('main-clobber', H(compile_step)), + B('main-tests', H(std_test_steps), T(std_tests, [flakiness_server])), + + # Other waterfalls + B('asan-builder-tests', H(compile_step, extra_gyp='asan=1'), + T(std_tests, ['--asan'])), + B('chromedriver-fyi-tests-dbg', H(std_test_steps), + T(['chromedriver'], ['--install=ChromiumTestShell'])), + B('fyi-x86-builder-dbg', + H(compile_step + std_host_tests, experimental, target_arch='x86')), + B('fyi-builder-dbg', + H(std_build_steps + std_host_tests, experimental)), + B('x86-builder-dbg', + H(compile_step + std_host_tests, target_arch='x86')), + B('fyi-builder-rel', H(std_build_steps, experimental)), + B('fyi-tests', H(std_test_steps), + T(std_tests, ['--experimental', flakiness_server])), + B('fyi-component-builder-tests-dbg', + H(compile_step, extra_gyp='component=shared_library'), + T(std_tests, ['--experimental', flakiness_server])), + B('perf-bisect-builder-tests-dbg', H(['bisect_perf_regression'])), + B('perf-tests-rel', H(std_test_steps), + T([], ['--install=ChromiumTestShell'])), + B('webkit-latest-webkit-tests', H(std_test_steps), + T(['webkit_layout', 'webkit'], ['--auto-reconnect'])), + B('webkit-latest-contentshell', H(compile_step), + T(['webkit_layout'], ['--auto-reconnect'])), + B('builder-unit-tests', H(compile_step), T(['unit'])), + B('webrtc-tests', H(std_test_steps), T(['webrtc'], [flakiness_server])), + + # Generic builder config (for substring match). + B('builder', H(std_build_steps)), + ] + + bot_map = dict((config.bot_id, config) for config in bot_configs) + + # These bots have identical configuration to ones defined earlier. + copy_map = [ + ('lkgr-clobber', 'main-clobber'), + ('try-builder-dbg', 'main-builder-dbg'), + ('try-builder-rel', 'main-builder-rel'), + ('try-clang-builder', 'main-clang-builder'), + ('try-fyi-builder-dbg', 'fyi-builder-dbg'), + ('try-x86-builder-dbg', 'x86-builder-dbg'), + ('try-tests', 'main-tests'), + ('try-fyi-tests', 'fyi-tests'), + ('webkit-latest-tests', 'main-tests'), + ('webrtc-builder', 'main-builder-rel'), + ] + for to_id, from_id in copy_map: + assert to_id not in bot_map + # pylint: disable=W0212 + bot_map[to_id] = copy.deepcopy(bot_map[from_id])._replace(bot_id=to_id) + + # Trybots do not upload to flakiness dashboard. They should be otherwise + # identical in configuration to their trunk building counterparts. + test_obj = bot_map[to_id].test_obj + if to_id.startswith('try') and test_obj: + extra_args = test_obj.extra_args + if extra_args and flakiness_server in extra_args: + extra_args.remove(flakiness_server) + return bot_map + + +# Return an object from the map, looking first for an exact id match. +# If this fails, look for an id which is a substring of the specified id. +# Choose the longest of all substring matches. +# pylint: disable=W0622 +def GetBestMatch(id_map, id): + config = id_map.get(id) + if not config: + substring_matches = filter(lambda x: x in id, id_map.iterkeys()) + if substring_matches: + max_id = max(substring_matches, key=len) + print 'Using config from id="%s" (substring match).' % max_id + config = id_map[max_id] + return config + + +def GetRunBotOptParser(): + parser = bb_utils.GetParser() + parser.add_option('--bot-id', help='Specify bot id directly.') + parser.add_option('--testing', action='store_true', + help='For testing: print, but do not run commands') + + return parser + + +def GetBotConfig(options, bot_step_map): + bot_id = options.bot_id or options.factory_properties.get('android_bot_id') + if not bot_id: + print (sys.stderr, + 'A bot id must be specified through option or factory_props.') + return + + bot_config = GetBestMatch(bot_step_map, bot_id) + if not bot_config: + print 'Error: config for id="%s" cannot be inferred.' % bot_id + return bot_config + + +def RunBotCommands(options, commands, env): + print 'Environment changes:' + print DictDiff(dict(os.environ), env) + + for command in commands: + print bb_utils.CommandToString(command) + sys.stdout.flush() + if options.testing: + env['BUILDBOT_TESTING'] = '1' + return_code = subprocess.call(command, cwd=bb_utils.CHROME_SRC, env=env) + if return_code != 0: + return return_code + + +def main(argv): + parser = GetRunBotOptParser() + options, args = parser.parse_args(argv[1:]) + if args: + parser.error('Unused args: %s' % args) + + bot_config = GetBotConfig(options, GetBotStepMap()) + if not bot_config: + sys.exit(1) + + print 'Using config:', bot_config + + commands = GetCommands(options, bot_config) + for command in commands: + print 'Will run: ', bb_utils.CommandToString(command) + print + + env = GetEnvironment(bot_config.host_obj, options.testing) + return RunBotCommands(options, commands, env) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/build/android/buildbot/bb_utils.py b/build/android/buildbot/bb_utils.py new file mode 100644 index 0000000000..f16540bc69 --- /dev/null +++ b/build/android/buildbot/bb_utils.py @@ -0,0 +1,92 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import json +import optparse +import os +import pipes +import subprocess +import sys + +import bb_annotations + +sys.path.append(os.path.join(os.path.dirname(__file__), '..')) +from pylib import constants + + +TESTING = 'BUILDBOT_TESTING' in os.environ + +BB_BUILD_DIR = os.path.abspath( + os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, os.pardir, + os.pardir, os.pardir, os.pardir, os.pardir)) + +CHROME_SRC = os.path.abspath( + os.path.join(os.path.dirname(__file__), '..', '..', '..')) + +GOMA_DIR = os.environ.get('GOMA_DIR', os.path.join(BB_BUILD_DIR, 'goma')) + +def CommandToString(command): + """Returns quoted command that can be run in bash shell.""" + return ' '.join(map(pipes.quote, command)) + + +def SpawnCmd(command, stdout=None, cwd=CHROME_SRC): + """Spawn a process without waiting for termination.""" + print '>', CommandToString(command) + sys.stdout.flush() + if TESTING: + class MockPopen(object): + @staticmethod + def wait(): + return 0 + return MockPopen() + return subprocess.Popen(command, cwd=cwd, stdout=stdout) + + +def RunCmd(command, flunk_on_failure=True, halt_on_failure=False, + warning_code=constants.WARNING_EXIT_CODE, stdout=None, + cwd=CHROME_SRC): + """Run a command relative to the chrome source root.""" + code = SpawnCmd(command, stdout, cwd).wait() + print '<', CommandToString(command) + if code != 0: + print 'ERROR: process exited with code %d' % code + if code != warning_code and flunk_on_failure: + bb_annotations.PrintError() + else: + bb_annotations.PrintWarning() + # Allow steps to have both halting (i.e. 1) and non-halting exit codes. + if code != warning_code and halt_on_failure: + print 'FATAL %d != %d' % (code, warning_code) + sys.exit(1) + return code + + +def GetParser(): + def ConvertJson(option, _, value, parser): + setattr(parser.values, option.dest, json.loads(value)) + parser = optparse.OptionParser() + parser.add_option('--build-properties', action='callback', + callback=ConvertJson, type='string', default={}, + help='build properties in JSON format') + parser.add_option('--factory-properties', action='callback', + callback=ConvertJson, type='string', default={}, + help='factory properties in JSON format') + return parser + + +def EncodeProperties(options): + return ['--factory-properties=%s' % json.dumps(options.factory_properties), + '--build-properties=%s' % json.dumps(options.build_properties)] + + +def RunSteps(steps, step_cmds, options): + unknown_steps = set(steps) - set(step for step, _ in step_cmds) + if unknown_steps: + print >> sys.stderr, 'FATAL: Unknown steps %s' % list(unknown_steps) + sys.exit(1) + + for step, cmd in step_cmds: + if step in steps: + cmd(options) diff --git a/build/android/buildbot/env_to_json.py b/build/android/buildbot/env_to_json.py new file mode 100755 index 0000000000..f9a7a443d3 --- /dev/null +++ b/build/android/buildbot/env_to_json.py @@ -0,0 +1,11 @@ +#!/usr/bin/python +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Encode current environment into json. + +import json +import os + +print json.dumps(dict(os.environ)) diff --git a/build/android/buildbot/tests/bb_run_bot_test.py b/build/android/buildbot/tests/bb_run_bot_test.py new file mode 100755 index 0000000000..810c60dac2 --- /dev/null +++ b/build/android/buildbot/tests/bb_run_bot_test.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +# 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. + +import os +import subprocess +import sys + +BUILDBOT_DIR = os.path.join(os.path.dirname(__file__), '..') +sys.path.append(BUILDBOT_DIR) +import bb_run_bot + +def RunBotProcesses(bot_process_map): + code = 0 + for bot, proc in bot_process_map: + _, err = proc.communicate() + code |= proc.returncode + if proc.returncode != 0: + print 'Error running the bot script with id="%s"' % bot, err + + return code + + +def main(): + procs = [ + (bot, subprocess.Popen( + [os.path.join(BUILDBOT_DIR, 'bb_run_bot.py'), '--bot-id', bot, + '--testing'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)) + for bot in bb_run_bot.GetBotStepMap()] + return RunBotProcesses(procs) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/build/android/chrome_with_libs.gyp b/build/android/chrome_with_libs.gyp new file mode 100644 index 0000000000..690be885f0 --- /dev/null +++ b/build/android/chrome_with_libs.gyp @@ -0,0 +1,82 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# This file is meant to add more loadable libs into Chrome_apk. +# +# This is useful when building Chrome_apk with some loadable modules which are +# not included in Chrome_apk. +# As an example, when building Chrome_apk with +# libpeer_target_type=loadable_module, +# the libpeerconnection.so is not included in Chrome_apk. To add the missing +# lib, follow the steps below: +# - Run gyp: +# GYP_DEFINES="$GYP_DEFINES libpeer_target_type=loadable_module" CHROMIUM_GYP_FILE="build/android/chrome_with_libs.gyp" build/gyp_chromium +# - Build chrome_with_libs: +# ninja (or make) chrome_with_libs +# +# This tool also allows replacing the loadable module with a new one via the +# following steps: +# - Build Chrome_apk with the gyp define: +# GYP_DEFINES="$GYP_DEFINES libpeer_target_type=loadable_module" build/gyp_chromium +# ninja (or make) Chrome_apk +# - Replace libpeerconnection.so with a new one: +# cp the_new_one path/to/libpeerconnection.so +# - Run gyp: +# GYP_DEFINES="$GYP_DEFINES libpeer_target_type=loadable_module" CHROMIUM_GYP_FILE="build/android/chrome_with_libs.gyp" build/gyp_chromium +# - Build chrome_with_libs: +# ninja (or make) chrome_with_libs +{ + 'targets': [ + { + # An "All" target is required for a top-level gyp-file. + 'target_name': 'All', + 'type': 'none', + 'dependencies': [ + 'chrome_with_libs', + ], + }, + { + 'target_name': 'chrome_with_libs', + 'type': 'none', + 'variables': { + 'intermediate_dir': '<(PRODUCT_DIR)/prebuilt_libs/', + 'chrome_unsigned_path': '<(PRODUCT_DIR)/chrome_apk/Chrome-unsigned.apk', + 'chrome_with_libs_unsigned': '<(intermediate_dir)/Chrome-with-libs-unsigned.apk', + 'chrome_with_libs_final': '<(PRODUCT_DIR)/apks/Chrome-with-libs.apk', + }, + 'dependencies': [ + '<(DEPTH)/clank/native/framework/clank.gyp:chrome_apk' + ], + 'copies': [ + { + 'destination': '<(intermediate_dir)/lib/<(android_app_abi)', + 'files': [ + '<(PRODUCT_DIR)/libpeerconnection.so', + ], + }, + ], + 'actions': [ + { + 'action_name': 'put_libs_in_chrome', + 'variables': { + 'inputs': [ + '<(intermediate_dir)/lib/<(android_app_abi)/libpeerconnection.so', + ], + 'input_apk_path': '<(chrome_unsigned_path)', + 'output_apk_path': '<(chrome_with_libs_unsigned)', + 'libraries_top_dir%': '<(intermediate_dir)', + }, + 'includes': [ 'create_standalone_apk_action.gypi' ], + }, + { + 'action_name': 'finalize_chrome_with_libs', + 'variables': { + 'input_apk_path': '<(chrome_with_libs_unsigned)', + 'output_apk_path': '<(chrome_with_libs_final)', + }, + 'includes': [ 'finalize_apk_action.gypi'], + }, + ], + }], +} diff --git a/build/android/cpufeatures.gypi b/build/android/cpufeatures.gypi new file mode 100644 index 0000000000..c08e95641a --- /dev/null +++ b/build/android/cpufeatures.gypi @@ -0,0 +1,31 @@ +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Depend on the Android NDK's cpu feature detection. The WebView build is part +# of the system and the library already exists; for the normal build there is a +# gyp file in the checked-in NDK to build it. +{ + 'conditions': [ + ['android_webview_build == 1', { + # This is specified twice intentionally: Android provides include paths + # to targets automatically if they depend on libraries, so we add this + # library to every target that includes this .gypi to make the headers + # available, then also add it to targets that link those targets via + # link_settings to ensure it ends up being linked even if the main target + # doesn't include this .gypi. + 'libraries': [ + 'cpufeatures.a', + ], + 'link_settings': { + 'libraries': [ + 'cpufeatures.a', + ], + }, + }, { + 'dependencies': [ + '<(android_ndk_root)/android_tools_ndk.gyp:cpu_features', + ], + }], + ], +} diff --git a/build/android/create_standalone_apk_action.gypi b/build/android/create_standalone_apk_action.gypi new file mode 100644 index 0000000000..d17af7c8e5 --- /dev/null +++ b/build/android/create_standalone_apk_action.gypi @@ -0,0 +1,41 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# This file is meant to be included into an action to provide an action that +# combines a directory of shared libraries and an incomplete APK into a +# standalone APK. +# +# To use this, create a gyp action with the following form: +# { +# 'action_name': 'some descriptive action name', +# 'variables': { +# 'inputs': [ 'input_path1', 'input_path2' ], +# 'input_apk_path': '<(unsigned_apk_path)', +# 'output_apk_path': '<(unsigned_standalone_apk_path)', +# 'libraries_top_dir': '<(libraries_top_dir)', +# }, +# 'includes': [ 'relative/path/to/create_standalone_apk_action.gypi' ], +# }, + +{ + 'message': 'Creating standalone APK: <(output_apk_path)', + 'variables': { + 'inputs': [], + }, + 'inputs': [ + '<(DEPTH)/build/android/gyp/util/build_utils.py', + '<(DEPTH)/build/android/gyp/create_standalone_apk.py', + '<(input_apk_path)', + '>@(inputs)', + ], + 'outputs': [ + '<(output_apk_path)', + ], + 'action': [ + 'python', '<(DEPTH)/build/android/gyp/create_standalone_apk.py', + '--libraries-top-dir=<(libraries_top_dir)', + '--input-apk-path=<(input_apk_path)', + '--output-apk-path=<(output_apk_path)', + ], +} diff --git a/build/android/developer_recommended_flags.gypi b/build/android/developer_recommended_flags.gypi new file mode 100644 index 0000000000..28470963c2 --- /dev/null +++ b/build/android/developer_recommended_flags.gypi @@ -0,0 +1,61 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# This is the set of recommended gyp variable settings for Chrome for Android development. +# +# These can be used by copying this file to $CHROME_SRC/chrome/supplement.gypi. +# +# Even better, create chrome/supplement.gypi containing the following: +# { +# 'includes': [ '../build/android/developer_recommended_flags.gypi' ] +# } +# and you'll get new settings automatically. +# When using this method, you can override individual settings by setting them unconditionally (with +# no %) in chrome/supplement.gypi. +# I.e. to disable optimize_jni_generation but use everything else: +# { +# 'variables': { +# 'optimize_jni_generation': 0, +# }, +# 'includes': [ '../build/android/developer_recommended_flags.gypi' ] +# } + +{ + 'variables': { + # When set to 1, only write jni generated files if they've changed. This can prevent unnecessary + # compiling/linking of native libraries when editing java files. + 'optimize_jni_generation%': 1, + + # Set component to 'shared_library' to enable the component build. This builds native code as + # many small shared libraries instead of one monolithic library. This slightly reduces the time + # required for incremental builds. + 'component%': 'shared_library', + + # When gyp_managed_install is set to 1, building an APK will install that APK on the connected + # device(/emulator). To install on multiple devices (or onto a new device), build the APK once + # with each device attached. This greatly reduces the time required for incremental builds. + # + # This comes with some caveats: + # Only works with a single device connected (it will print a warning if + # zero or multiple devices are attached). + # Some actions are always run (i.e. ninja will never say "no work to do"). + 'gyp_managed_install%': 1, + + # With gyp_managed_install, we do not necessarily need a standalone APK. + # When create_standalone_apk is set to 1, we will build a standalone APK + # anyway. For even faster builds, you can set create_standalone_apk to 0. + 'create_standalone_apk%': 1, + + # Set clang to 1 to use the clang compiler. Clang has much (much, much) better warning/error + # messages than gcc. + # TODO(cjhopman): Enable this when http://crbug.com/156420 is addressed. Until then, users can + # set clang to 1, but Android stack traces will sometimes be incomplete. + #'clang%': 1, + + # Set fastbuild to 1 to build with less debugging information. This can greatly decrease linking + # time. The downside is that stack traces will be missing useful information (like line + # numbers). + #'fastbuild%': 1, + }, +} diff --git a/build/android/device_stats_monitor.py b/build/android/device_stats_monitor.py new file mode 100755 index 0000000000..23506a8899 --- /dev/null +++ b/build/android/device_stats_monitor.py @@ -0,0 +1,43 @@ +#!/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. + +"""Provides iotop/top style profiling for android. + +Usage: + ./device_stats_monitor.py --hz=20 --duration=5 --outfile=/tmp/foo +""" + +import optparse +import os +import sys +import time + +from pylib import android_commands +from pylib import device_stats_monitor +from pylib.utils import test_options_parser + + +def main(argv): + option_parser = optparse.OptionParser() + option_parser.add_option('--hz', type='int', default=20, + help='Number of samples/sec.') + option_parser.add_option('--duration', type='int', default=5, + help='Seconds to monitor.') + option_parser.add_option('--outfile', default='/tmp/devicestatsmonitor', + help='Location to start output file.') + test_options_parser.AddBuildTypeOption(option_parser) + options, args = option_parser.parse_args(argv) + + monitor = device_stats_monitor.DeviceStatsMonitor( + android_commands.AndroidCommands(), options.hz, options.build_type) + monitor.Start() + print 'Waiting for %d seconds while profiling.' % options.duration + time.sleep(options.duration) + url = monitor.StopAndCollect(options.outfile) + print 'View results in browser at %s' % url + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/build/android/dex_action.gypi b/build/android/dex_action.gypi new file mode 100644 index 0000000000..ac956b6e34 --- /dev/null +++ b/build/android/dex_action.gypi @@ -0,0 +1,61 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# This file is meant to be included into an action to provide a rule that dexes +# compiled java files. If proguard_enabled == "true" and CONFIGURATION_NAME == +# "Release", then it will dex the proguard_enabled_input_path instead of the +# normal dex_input_paths/dex_generated_input_paths. +# +# To use this, create a gyp target with the following form: +# { +# 'action_name': 'some name for the action' +# 'actions': [ +# 'variables': { +# 'dex_input_paths': [ 'files to dex (when proguard is not used) and add to input paths' ], +# 'dex_generated_input_dirs': [ 'dirs that contain generated files to dex' ], +# 'input_paths': [ 'additional files to be added to the list of inputs' ], +# +# # For targets that use proguard: +# 'proguard_enabled': 'true', +# 'proguard_enabled_input_path': 'path to dex when using proguard', +# }, +# 'includes': [ 'relative/path/to/dex_action.gypi' ], +# ], +# }, +# + +{ + 'message': 'Creating dex file: <(output_path)', + 'variables': { + 'dex_input_paths': [], + 'dex_generated_input_dirs': [], + 'input_paths': [], + 'proguard_enabled%': 'false', + 'proguard_enabled_input_path%': '', + }, + 'inputs': [ + '<(DEPTH)/build/android/gyp/util/build_utils.py', + '<(DEPTH)/build/android/gyp/util/md5_check.py', + '<(DEPTH)/build/android/gyp/dex.py', + '>@(input_paths)', + '>@(dex_input_paths)', + ], + 'outputs': [ + '<(output_path)', + ], + 'action': [ + 'python', '<(DEPTH)/build/android/gyp/dex.py', + '--dex-path=<(output_path)', + '--android-sdk-tools=<(android_sdk_tools)', + '--configuration-name=<(CONFIGURATION_NAME)', + '--proguard-enabled=<(proguard_enabled)', + '--proguard-enabled-input-path=<(proguard_enabled_input_path)', + + # TODO(newt): remove this once http://crbug.com/177552 is fixed in ninja. + '--ignore=>!(echo \'>(_inputs)\' | md5sum)', + + '>@(dex_input_paths)', + '>@(dex_generated_input_dirs)', + ] +} diff --git a/build/android/empty/src/.keep b/build/android/empty/src/.keep new file mode 100644 index 0000000000..0f710b673d --- /dev/null +++ b/build/android/empty/src/.keep @@ -0,0 +1,6 @@ +This is a file that needs to live here until http://crbug.com/158155 has +been fixed. + +The ant build system requires that a src folder is always present, and for +some of our targets that is not the case. Giving it an empty src-folder works +nicely though. diff --git a/build/android/empty_proguard.flags b/build/android/empty_proguard.flags new file mode 100644 index 0000000000..53484fe815 --- /dev/null +++ b/build/android/empty_proguard.flags @@ -0,0 +1 @@ +# Used for apk targets that do not need proguard. See build/java_apk.gypi. diff --git a/build/android/enable_asserts.py b/build/android/enable_asserts.py new file mode 100755 index 0000000000..5659e9e2a8 --- /dev/null +++ b/build/android/enable_asserts.py @@ -0,0 +1,31 @@ +#!/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. + +"""Enables dalvik vm asserts in the android device.""" + +from pylib import android_commands +import optparse +import sys + + +def main(argv): + option_parser = optparse.OptionParser() + option_parser.add_option('--enable_asserts', dest='set_asserts', + action='store_true', default=None, + help='Sets the dalvik.vm.enableassertions property to "all"') + option_parser.add_option('--disable_asserts', dest='set_asserts', + action='store_false', default=None, + help='Removes the dalvik.vm.enableassertions property') + options, _ = option_parser.parse_args(argv) + + commands = android_commands.AndroidCommands() + if options.set_asserts != None: + if commands.SetJavaAssertsEnabled(options.set_asserts): + commands.Reboot(full_reboot=False) + + +if __name__ == '__main__': + main(sys.argv) diff --git a/build/android/envsetup.sh b/build/android/envsetup.sh new file mode 100755 index 0000000000..f9e3e5ede1 --- /dev/null +++ b/build/android/envsetup.sh @@ -0,0 +1,163 @@ +#!/bin/bash +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Sets up environment for building Chromium on Android. It can either be +# compiled with the Android tree or using the Android SDK/NDK. To build with +# NDK/SDK: ". build/android/envsetup.sh". Environment variable +# ANDROID_SDK_BUILD=1 will then be defined and used in the rest of the setup to +# specifiy build type. + +# TODO(ilevy): Figure out the right check here. This breaks the webkit build as +# is since it's sourced from another script: +# http://build.webkit.org/builders/Chromium%20Android%20Release/builds/34681 +#if [ "$_" == "$0" ]; then +# echo "ERROR: envsetup must be sourced." +# exit 1 +#fi + +# Source functions script. The file is in the same directory as this script. +SCRIPT_DIR="$(dirname "${BASH_SOURCE:-$0}")" +. "${SCRIPT_DIR}"/envsetup_functions.sh + +export ANDROID_SDK_BUILD=1 # Default to SDK build. + +process_options "$@" + +# When building WebView as part of Android we can't use the SDK. Other builds +# default to using the SDK. +if [[ "${CHROME_ANDROID_BUILD_WEBVIEW}" -eq 1 ]]; then + export ANDROID_SDK_BUILD=0 +fi + +if [[ "${ANDROID_SDK_BUILD}" -ne 1 ]]; then + echo "Initializing for non-SDK build." +fi + +# Get host architecture, and abort if it is 32-bit, unless --try-32 +# is also used. +host_arch=$(uname -m) +case "${host_arch}" in + x86_64) # pass + ;; + i?86) + if [[ -z "${try_32bit_host_build}" ]]; then + echo "ERROR: Android build requires a 64-bit host build machine." + echo "If you really want to try it on this machine, use the \ +--try-32bit-host flag." + echo "Be warned that this may fail horribly at link time, due \ +very large binaries." + return 1 + else + echo "WARNING: 32-bit host build enabled. Here be dragons!" + host_arch=x86 + fi + ;; + *) + echo "ERROR: Unsupported host architecture (${host_arch})." + echo "Try running this script on a Linux/x86_64 machine instead." + return 1 +esac + +case "${host_os}" in + "linux") + toolchain_dir="linux-${host_arch}" + ;; + "mac") + toolchain_dir="darwin-${host_arch}" + ;; + *) + echo "Host platform ${host_os} is not supported" >& 2 + return 1 +esac + +CURRENT_DIR="$(readlink -f "${SCRIPT_DIR}/../../")" +if [[ -z "${CHROME_SRC}" ]]; then + # If $CHROME_SRC was not set, assume current directory is CHROME_SRC. + export CHROME_SRC="${CURRENT_DIR}" +fi + +if [[ "${CURRENT_DIR/"${CHROME_SRC}"/}" == "${CURRENT_DIR}" ]]; then + # If current directory is not in $CHROME_SRC, it might be set for other + # source tree. If $CHROME_SRC was set correctly and we are in the correct + # directory, "${CURRENT_DIR/"${CHROME_SRC}"/}" will be "". + # Otherwise, it will equal to "${CURRENT_DIR}" + echo "Warning: Current directory is out of CHROME_SRC, it may not be \ +the one you want." + echo "${CHROME_SRC}" +fi + +if [[ "${ANDROID_SDK_BUILD}" -eq 1 ]]; then + if [[ -z "${TARGET_ARCH}" ]]; then + return 1 + fi + sdk_build_init +# Sets up environment for building Chromium for Android with source. Expects +# android environment setup and lunch. +elif [[ -z "$ANDROID_BUILD_TOP" || \ + -z "$ANDROID_TOOLCHAIN" || \ + -z "$ANDROID_PRODUCT_OUT" ]]; then + echo "Android build environment variables must be set." + echo "Please cd to the root of your Android tree and do: " + echo " . build/envsetup.sh" + echo " lunch" + echo "Then try this again." + echo "Or did you mean NDK/SDK build. Run envsetup.sh without any arguments." + return 1 +elif [[ -n "$CHROME_ANDROID_BUILD_WEBVIEW" ]]; then + webview_build_init +fi + +java -version 2>&1 | grep -qs "Java HotSpot" +if [ $? -ne 0 ]; then + echo "Please check and make sure you are using the Oracle Java SDK, and it" + echo "appears before other Java SDKs in your path." + echo "Refer to the \"Install prerequisites\" section here:" + echo "https://code.google.com/p/chromium/wiki/AndroidBuildInstructions" + return 1 +fi + +if [[ -n "$JAVA_HOME" && -x "$JAVA_HOME/bin/java" ]]; then + "$JAVA_HOME/bin/java" -version 2>&1 | grep -qs "Java HotSpot" + if [ $? -ne 0 ]; then + echo "If JAVA_HOME is defined then it must refer to the install location" + echo "of the Oracle Java SDK." + echo "Refer to the \"Install prerequisites\" section here:" + echo "https://code.google.com/p/chromium/wiki/AndroidBuildInstructions" + return 1 + fi +fi + +# Workaround for valgrind build +if [[ -n "$CHROME_ANDROID_VALGRIND_BUILD" ]]; then +# arm_thumb=0 is a workaround for https://bugs.kde.org/show_bug.cgi?id=270709 + DEFINES+=" arm_thumb=0 release_extra_cflags='-fno-inline\ + -fno-omit-frame-pointer -fno-builtin' release_valgrind_build=1\ + release_optimize=1" +fi + +# Source a bunch of helper functions +. ${CHROME_SRC}/build/android/adb_device_functions.sh + +ANDROID_GOMA_WRAPPER="" +if [[ -d $GOMA_DIR ]]; then + ANDROID_GOMA_WRAPPER="$GOMA_DIR/gomacc" +fi +export ANDROID_GOMA_WRAPPER + +# Declare Android are cross compile. +export GYP_CROSSCOMPILE=1 + +# Performs a gyp_chromium run to convert gyp->Makefile for android code. +android_gyp() { + # This is just a simple wrapper of gyp_chromium, please don't add anything + # in this function. + echo "GYP_GENERATORS set to '$GYP_GENERATORS'" + ( + "${CHROME_SRC}/build/gyp_chromium" --depth="${CHROME_SRC}" --check "$@" + ) +} + +# FLOCK needs to be null on system that has no flock +which flock > /dev/null || export FLOCK= diff --git a/build/android/envsetup_functions.sh b/build/android/envsetup_functions.sh new file mode 100755 index 0000000000..f7cf4a1b2c --- /dev/null +++ b/build/android/envsetup_functions.sh @@ -0,0 +1,326 @@ +#!/bin/bash + +# Copyright (c) 2012 The Chromium 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 functions for envsetup.sh which sets up environment for building +# Chromium on Android. The build can be either use the Android NDK/SDK or +# android source tree. Each has a unique init function which calls functions +# prefixed with "common_" that is common for both environment setups. + +################################################################################ +# Check to make sure the toolchain exists for the NDK version. +################################################################################ +common_check_toolchain() { + if [[ ! -d "${ANDROID_TOOLCHAIN}" ]]; then + echo "Can not find Android toolchain in ${ANDROID_TOOLCHAIN}." >& 2 + echo "The NDK version might be wrong." >& 2 + return 1 + fi +} + +################################################################################ +# Exports environment variables common to both sdk and non-sdk build (e.g. PATH) +# based on CHROME_SRC and ANDROID_TOOLCHAIN, along with DEFINES for GYP_DEFINES. +################################################################################ +common_vars_defines() { + # Set toolchain path according to product architecture. + case "${TARGET_ARCH}" in + "arm") + toolchain_arch="arm-linux-androideabi" + ;; + "x86") + toolchain_arch="x86" + ;; + "mips") + toolchain_arch="mipsel-linux-android" + ;; + *) + echo "TARGET_ARCH: ${TARGET_ARCH} is not supported." >& 2 + print_usage + return 1 + ;; + esac + + toolchain_version="4.6" + # We directly set the gcc_version since we know what we use, and it should + # be set to xx instead of x.x. Refer the output of compiler_version.py. + gcc_version="46" + toolchain_target=$(basename \ + ${ANDROID_NDK_ROOT}/toolchains/${toolchain_arch}-${toolchain_version}) + toolchain_path="${ANDROID_NDK_ROOT}/toolchains/${toolchain_target}"\ +"/prebuilt/${toolchain_dir}/bin/" + + # Set only if not already set. + # Don't override ANDROID_TOOLCHAIN if set by Android configuration env. + export ANDROID_TOOLCHAIN=${ANDROID_TOOLCHAIN:-${toolchain_path}} + + common_check_toolchain + + # Add Android SDK/NDK tools to system path. + export PATH=$PATH:${ANDROID_NDK_ROOT} + export PATH=$PATH:${ANDROID_SDK_ROOT}/tools + export PATH=$PATH:${ANDROID_SDK_ROOT}/platform-tools + export PATH=$PATH:${ANDROID_SDK_ROOT}/build-tools/\ +${ANDROID_SDK_BUILD_TOOLS_VERSION} + + # This must be set before ANDROID_TOOLCHAIN, so that clang could find the + # gold linker. + # TODO(michaelbai): Remove this path once the gold linker become the default + # linker. + export PATH=$PATH:${CHROME_SRC}/build/android/${toolchain_arch}-gold + + # Must have tools like arm-linux-androideabi-gcc on the path for ninja + export PATH=$PATH:${ANDROID_TOOLCHAIN} + + # Add Chromium Android development scripts to system path. + # Must be after CHROME_SRC is set. + export PATH=$PATH:${CHROME_SRC}/build/android + + # TODO(beverloo): Remove these once all consumers updated to --strip-binary. + export OBJCOPY=$(echo ${ANDROID_TOOLCHAIN}/*-objcopy) + export STRIP=$(echo ${ANDROID_TOOLCHAIN}/*-strip) + + # The set of GYP_DEFINES to pass to gyp. Use 'readlink -e' on directories + # to canonicalize them (remove double '/', remove trailing '/', etc). + DEFINES="OS=android" + DEFINES+=" host_os=${host_os}" + DEFINES+=" gcc_version=${gcc_version}" + + if [[ -n "$CHROME_ANDROID_OFFICIAL_BUILD" ]]; then + DEFINES+=" branding=Chrome" + DEFINES+=" buildtype=Official" + + # These defines are used by various chrome build scripts to tag the binary's + # version string as 'official' in linux builds (e.g. in + # chrome/trunk/src/chrome/tools/build/version.py). + export OFFICIAL_BUILD=1 + export CHROMIUM_BUILD="_google_chrome" + export CHROME_BUILD_TYPE="_official" + fi + + # The order file specifies the order of symbols in the .text section of the + # shared library, libchromeview.so. The file is an order list of section + # names and the library is linked with option + # --section-ordering-file=. The order file is updated by profiling + # startup after compiling with the order_profiling=1 GYP_DEFINES flag. + ORDER_DEFINES="order_text_section=${CHROME_SRC}/orderfiles/orderfile.out" + + # The following defines will affect ARM code generation of both C/C++ compiler + # and V8 mksnapshot. + case "${TARGET_ARCH}" in + "arm") + DEFINES+=" ${ORDER_DEFINES}" + DEFINES+=" target_arch=arm" + ;; + "x86") + # TODO(tedbo): The ia32 build fails on ffmpeg, so we disable it here. + DEFINES+=" use_libffmpeg=0" + + host_arch=$(uname -m | sed -e \ + 's/i.86/ia32/;s/x86_64/x64/;s/amd64/x64/;s/arm.*/arm/;s/i86pc/ia32/') + DEFINES+=" host_arch=${host_arch}" + DEFINES+=" target_arch=ia32" + ;; + "mips") + DEFINES+=" target_arch=mipsel" + ;; + *) + echo "TARGET_ARCH: ${TARGET_ARCH} is not supported." >& 2 + print_usage + return 1 + esac +} + + +################################################################################ +# Exports common GYP variables based on variable DEFINES and CHROME_SRC. +################################################################################ +common_gyp_vars() { + export GYP_DEFINES="${DEFINES}" + + # Set GYP_GENERATORS to ninja if it's currently unset or null. + if [ -z "$GYP_GENERATORS" ]; then + echo "Defaulting GYP_GENERATORS to ninja." + GYP_GENERATORS=ninja + elif [ "$GYP_GENERATORS" != "ninja" ]; then + echo "Warning: GYP_GENERATORS set to '$GYP_GENERATORS'." + echo "Only GYP_GENERATORS=ninja has continuous coverage." + fi + export GYP_GENERATORS + + # Use our All target as the default + export GYP_GENERATOR_FLAGS="${GYP_GENERATOR_FLAGS} default_target=All" + + # We want to use our version of "all" targets. + export CHROMIUM_GYP_FILE="${CHROME_SRC}/build/all_android.gyp" +} + + +################################################################################ +# Prints out help message on usage. +################################################################################ +print_usage() { + echo "usage: ${0##*/} [--target-arch=value] [--help]" >& 2 + echo "--target-arch=value target CPU architecture (arm=default, x86)" >& 2 + echo "--host-os=value override host OS detection (linux, mac)" >&2 + echo "--try-32bit-host try building a 32-bit host architecture" >&2 + echo "--help this help" >& 2 +} + +################################################################################ +# Process command line options. +# --target-arch= Specifices target CPU architecture. Currently supported +# architectures are "arm" (default), and "x86". +# --help Prints out help message. +################################################################################ +process_options() { + host_os=$(uname -s | sed -e 's/Linux/linux/;s/Darwin/mac/') + try_32bit_host_build= + while [[ -n $1 ]]; do + case "$1" in + --target-arch=*) + target_arch="$(echo "$1" | sed 's/^[^=]*=//')" + ;; + --host-os=*) + host_os="$(echo "$1" | sed 's/^[^=]*=//')" + ;; + --try-32bit-host) + try_32bit_host_build=true + ;; + --help) + print_usage + return 1 + ;; + *) + # Ignore other command line options + echo "Unknown option: $1" + ;; + esac + shift + done + + # Sets TARGET_ARCH. Defaults to arm if not specified. + TARGET_ARCH=${target_arch:-arm} +} + +################################################################################ +# Initializes environment variables for NDK/SDK build. Only Android NDK Revision +# 7 on Linux or Mac is offically supported. To run this script, the system +# environment ANDROID_NDK_ROOT must be set to Android NDK's root path. The +# ANDROID_SDK_ROOT only needs to be set to override the default SDK which is in +# the tree under $ROOT/src/third_party/android_tools/sdk. +# To build Chromium for Android with NDK/SDK follow the steps below: +# > export ANDROID_NDK_ROOT= +# > export ANDROID_SDK_ROOT= # to override the default sdk +# > . build/android/envsetup.sh +# > make +################################################################################ +sdk_build_init() { + + # Allow the caller to override a few environment variables. If any of them is + # unset, we default to a sane value that's known to work. This allows for + # experimentation with a custom SDK. + if [[ -z "${ANDROID_NDK_ROOT}" || ! -d "${ANDROID_NDK_ROOT}" ]]; then + export ANDROID_NDK_ROOT="${CHROME_SRC}/third_party/android_tools/ndk/" + fi + if [[ -z "${ANDROID_SDK_VERSION}" ]]; then + export ANDROID_SDK_VERSION=18 + fi + local sdk_suffix=platforms/android-${ANDROID_SDK_VERSION} + if [[ -z "${ANDROID_SDK_ROOT}" || \ + ! -d "${ANDROID_SDK_ROOT}/${sdk_suffix}" ]]; then + export ANDROID_SDK_ROOT="${CHROME_SRC}/third_party/android_tools/sdk/" + fi + if [[ -z "${ANDROID_SDK_BUILD_TOOLS_VERSION}" ]]; then + export ANDROID_SDK_BUILD_TOOLS_VERSION=18.0.1 + fi + + unset ANDROID_BUILD_TOP + + # Set default target. + export TARGET_PRODUCT="${TARGET_PRODUCT:-trygon}" + + # Unset toolchain so that it can be set based on TARGET_PRODUCT. + # This makes it easy to switch between architectures. + unset ANDROID_TOOLCHAIN + + common_vars_defines + common_gyp_vars + + if [[ -n "$CHROME_ANDROID_BUILD_WEBVIEW" ]]; then + # Can not build WebView with NDK/SDK because it needs the Android build + # system and build inside an Android source tree. + echo "Can not build WebView with NDK/SDK. Requires android source tree." \ + >& 2 + echo "Try . build/android/envsetup.sh instead." >& 2 + return 1 + fi + + # Directory containing build-tools: aapt, aidl, dx + export ANDROID_SDK_TOOLS="${ANDROID_SDK_ROOT}/build-tools/\ +${ANDROID_SDK_BUILD_TOOLS_VERSION}" +} + +################################################################################ +# To build WebView, we use the Android build system and build inside an Android +# source tree. This method is called from non_sdk_build_init() and adds to the +# settings specified there. +############################################################################# +webview_build_init() { + # Use the latest API in the AOSP prebuilts directory (change with AOSP roll). + export ANDROID_SDK_VERSION=17 + + # For the WebView build we always use the NDK and SDK in the Android tree, + # and we don't touch ANDROID_TOOLCHAIN which is already set by Android. + export ANDROID_NDK_ROOT=${ANDROID_BUILD_TOP}/prebuilts/ndk/8 + export ANDROID_SDK_ROOT=${ANDROID_BUILD_TOP}/prebuilts/sdk/\ +${ANDROID_SDK_VERSION} + + common_vars_defines + + # We need to supply SDK paths relative to the top of the Android tree to make + # sure the generated Android makefiles are portable, as they will be checked + # into the Android tree. + ANDROID_SDK=$(python -c \ + "import os.path; print os.path.relpath('${ANDROID_SDK_ROOT}', \ + '${ANDROID_BUILD_TOP}')") + case "${host_os}" in + "linux") + ANDROID_SDK_TOOLS=$(python -c \ + "import os.path; \ + print os.path.relpath('${ANDROID_SDK_ROOT}/../tools/linux', \ + '${ANDROID_BUILD_TOP}')") + ;; + "mac") + ANDROID_SDK_TOOLS=$(python -c \ + "import os.path; \ + print os.path.relpath('${ANDROID_SDK_ROOT}/../tools/darwin', \ + '${ANDROID_BUILD_TOP}')") + ;; + esac + DEFINES+=" android_webview_build=1" + # temporary until all uses of android_build_type are gone (crbug.com/184431) + DEFINES+=" android_build_type=1" + DEFINES+=" android_src=\$(PWD)" + DEFINES+=" android_sdk=\$(PWD)/${ANDROID_SDK}" + DEFINES+=" android_sdk_root=\$(PWD)/${ANDROID_SDK}" + DEFINES+=" android_sdk_tools=\$(PWD)/${ANDROID_SDK_TOOLS}" + DEFINES+=" android_sdk_version=${ANDROID_SDK_VERSION}" + DEFINES+=" android_toolchain=${ANDROID_TOOLCHAIN}" + if [[ -n "$CHROME_ANDROID_WEBVIEW_ENABLE_DMPROF" ]]; then + DEFINES+=" disable_debugallocation=1" + DEFINES+=" android_full_debug=1" + DEFINES+=" android_use_tcmalloc=1" + fi + export GYP_DEFINES="${DEFINES}" + + export GYP_GENERATORS="android" + + export GYP_GENERATOR_FLAGS="${GYP_GENERATOR_FLAGS} default_target=All" + export GYP_GENERATOR_FLAGS="${GYP_GENERATOR_FLAGS} limit_to_target_all=1" + export GYP_GENERATOR_FLAGS="${GYP_GENERATOR_FLAGS} auto_regeneration=0" + + export CHROMIUM_GYP_FILE="${CHROME_SRC}/android_webview/all_webview.gyp" +} diff --git a/build/android/finalize_apk_action.gypi b/build/android/finalize_apk_action.gypi new file mode 100644 index 0000000000..5ee6043b4e --- /dev/null +++ b/build/android/finalize_apk_action.gypi @@ -0,0 +1,46 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# This file is meant to be included into an action to provide an action that +# signs and zipaligns an APK. +# +# To use this, create a gyp action with the following form: +# { +# 'action_name': 'some descriptive action name', +# 'variables': { +# 'inputs': [ 'input_path1', 'input_path2' ], +# 'input_apk_path': 'relative/path/to/input.apk', +# 'output_apk_path': 'relative/path/to/output.apk', +# }, +# 'includes': [ '../../build/android/finalize_apk.gypi' ], +# }, +# + +{ + 'message': 'Signing/aligning <(_target_name) APK: <(input_apk_path).', + 'variables': { + 'inputs': [], + 'keystore_path%': '<(DEPTH)/build/android/ant/chromium-debug.keystore', + }, + 'inputs': [ + '<(DEPTH)/build/android/gyp/util/build_utils.py', + '<(DEPTH)/build/android/gyp/finalize_apk.py', + '<(keystore_path)', + '<(input_apk_path)', + '>@(inputs)', + ], + 'outputs': [ + '<(output_apk_path)', + ], + 'action': [ + 'python', '<(DEPTH)/build/android/gyp/finalize_apk.py', + '--android-sdk-root=<(android_sdk_root)', + '--unsigned-apk-path=<(input_apk_path)', + '--final-apk-path=<(output_apk_path)', + '--keystore-path=<(keystore_path)', + + # TODO(newt): remove this once crbug.com/177552 is fixed in ninja. + '--ignore=>!(echo \'>(_inputs)\' | md5sum)', + ], +} diff --git a/build/android/findbugs_diff.py b/build/android/findbugs_diff.py new file mode 100755 index 0000000000..eb117da36c --- /dev/null +++ b/build/android/findbugs_diff.py @@ -0,0 +1,50 @@ +#!/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. + +"""Runs findbugs, and returns an error code if there are new warnings. +This runs findbugs with an additional flag to exclude known bugs. +To update the list of known bugs, do this: + + findbugs_diff.py --rebaseline + +Note that this is separate from findbugs_exclude.xml. The "exclude" file has +false positives that we do not plan to fix. The "known bugs" file has real +bugs that we *do* plan to fix (but haven't done so yet). + +Other options + --only-analyze used to only analyze the class you are interested. + --relase-build analyze the classes in out/Release directory. + --findbugs-args used to passin other findbugs's options. + +Run + $CHROM_SRC/third_party/findbugs/bin/findbugs -textui for details. + +""" + +import optparse +import os +import sys + +from pylib import constants +from pylib.utils import findbugs + + +def main(argv): + parser = findbugs.GetCommonParser() + + options, _ = parser.parse_args() + + if not options.base_dir: + options.base_dir = os.path.join(constants.DIR_SOURCE_ROOT, 'build', + 'android', 'findbugs_filter') + if not options.only_analyze: + options.only_analyze = 'org.chromium.-' + + return findbugs.Run(options) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/build/android/findbugs_filter/findbugs_exclude.xml b/build/android/findbugs_filter/findbugs_exclude.xml new file mode 100644 index 0000000000..7b6860d0bb --- /dev/null +++ b/build/android/findbugs_filter/findbugs_exclude.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/android/findbugs_filter/findbugs_known_bugs.txt b/build/android/findbugs_filter/findbugs_known_bugs.txt new file mode 100644 index 0000000000..a2be5f1ad5 --- /dev/null +++ b/build/android/findbugs_filter/findbugs_known_bugs.txt @@ -0,0 +1,47 @@ +H C EC: Using pointer equality to compare a JavaBridgeCoercionTest$CustomType with a JavaBridgeCoercionTest$CustomType2 in org.chromium.content.browser.JavaBridgeCoercionTest.testPassJavaObject() At JavaBridgeCoercionTest.java +M B DE: org.chromium.net.X509Util.clearTestRootCertificates() might ignore java.io.IOException At X509Util.java +M B Nm: The method name org.chromium.base.test.util.ScalableTimeout.ScaleTimeout(long) doesn't start with a lower case letter At ScalableTimeout.java +M B RV: exceptional return value of java.io.File.delete() ignored in org.chromium.android_webview.test.ArchiveTest.doArchiveTest(AwContents, String, boolean, String) At ArchiveTest.java +M B RV: exceptional return value of java.io.File.delete() ignored in org.chromium.android_webview.test.ArchiveTest.testAutoBadPath() At ArchiveTest.java +M B RV: exceptional return value of java.io.File.delete() ignored in org.chromium.android_webview.test.ArchiveTest.testExplicitBadPath() At ArchiveTest.java +M B RV: exceptional return value of java.io.File.delete() ignored in org.chromium.android_webview.test.ArchiveTest.testExplicitGoodPath() At ArchiveTest.java +M B RV: exceptional return value of java.io.File.delete() ignored in org.chromium.base.test.util.TestFileUtil.deleteFile(String) At TestFileUtil.java +M C CSM: Shouldn't use synchronized method, please narrow down the synchronization scope. At HttpAuthDatabase.java +M C CSM: Shouldn't use synchronized method, please narrow down the synchronization scope. At JavaBridgeArrayCoercionTest.java +M C CSM: Shouldn't use synchronized method, please narrow down the synchronization scope. At JavaBridgeArrayTest.java +M C CSM: Shouldn't use synchronized method, please narrow down the synchronization scope. At JavaBridgeBasicsTest.java +M C CSM: Shouldn't use synchronized method, please narrow down the synchronization scope. At JavaBridgeChildFrameTest.java +M C CSM: Shouldn't use synchronized method, please narrow down the synchronization scope. At JavaBridgeCoercionTest.java +M C CSM: Shouldn't use synchronized method, please narrow down the synchronization scope. At JavaBridgeFieldsTest.java +M C CSM: Shouldn't use synchronized method, please narrow down the synchronization scope. At JavaBridgeReturnValuesTest.java +M C CSM: Shouldn't use synchronized method, please narrow down the synchronization scope. At JavaBridgeTestBase.java +M C CSM: Shouldn't use synchronized method, please narrow down the synchronization scope. At PerfTraceEvent.java +M C CSM: Shouldn't use synchronized method, please narrow down the synchronization scope. At SimpleSynchronizedMethod.java +M C CSM: Shouldn't use synchronized method, please narrow down the synchronization scope. At SimpleSynchronizedStaticMethod.java +M C CSM: Shouldn't use synchronized method, please narrow down the synchronization scope. At TraceEvent.java +M C CST: Shouldn't use synchronized(this), please narrow down the synchronization scope. At HttpAuthDatabase.java +M C CST: Shouldn't use synchronized(this), please narrow down the synchronization scope. At SimpleSynchronizedThis.java +M C RCN: Nullcheck of GestureDetector.mVelocityTracker at line 630 of value previously dereferenced in org.chromium.content.browser.third_party.GestureDetector.onTouchEvent(MotionEvent) At GestureDetector.java +M C USELESS_STRING: Invocation of toString on certChain in org.chromium.net.X509Util.verifyServerCertificates(byte[][], String) At X509Util.java +M D DMI: Hard coded reference to an absolute pathname in org.chromium.android_webview.test.ArchiveTest.testAutoBadPath() At ArchiveTest.java +M D DMI: Hard coded reference to an absolute pathname in org.chromium.android_webview.test.ArchiveTest.testExplicitBadPath() At ArchiveTest.java +M D SF: Switch statement found in org.chromium.chrome.browser.ChromeBrowserProvider.insert(Uri, ContentValues) where one case falls through to the next case At ChromeBrowserProvider.java +M D SF: Switch statement found in org.chromium.chrome.browser.database.SQLiteCursor.fillWindow(int, CursorWindow) where default case is missing At SQLiteCursor.java +M D SF: Switch statement found in org.chromium.content.browser.third_party.GestureDetector.onTouchEvent(MotionEvent) where default case is missing At GestureDetector.java +M M UG: org.chromium.content.browser.JavaBridgeReturnValuesTest$TestObject.getBooleanValue() is unsynchronized, org.chromium.content.browser.JavaBridgeReturnValuesTest$TestObject.setBooleanValue(boolean) is synchronized At JavaBridgeReturnValuesTest.java +M M UG: org.chromium.content.browser.JavaBridgeReturnValuesTest$TestObject.getStringValue() is unsynchronized, org.chromium.content.browser.JavaBridgeReturnValuesTest$TestObject.setStringValue(String) is synchronized At JavaBridgeReturnValuesTest.java +M V EI2: new org.chromium.chrome.browser.FindMatchRectsDetails(int, RectF[], RectF) may expose internal representation by storing an externally mutable object into FindMatchRectsDetails.rects At FindMatchRectsDetails.java +M V EI2: org.chromium.chrome.browser.ChromeBrowserProvider$BookmarkNode.setFavicon(byte[]) may expose internal representation by storing an externally mutable object into ChromeBrowserProvider$BookmarkNode.mFavicon At ChromeBrowserProvider.java +M V EI2: org.chromium.chrome.browser.ChromeBrowserProvider$BookmarkNode.setThumbnail(byte[]) may expose internal representation by storing an externally mutable object into ChromeBrowserProvider$BookmarkNode.mThumbnail At ChromeBrowserProvider.java +M V EI2: org.chromium.content.browser.LoadUrlParams.setPostData(byte[]) may expose internal representation by storing an externally mutable object into LoadUrlParams.mPostData At LoadUrlParams.java +M V EI: org.chromium.chrome.browser.ChromeBrowserProvider$BookmarkNode.favicon() may expose internal representation by returning ChromeBrowserProvider$BookmarkNode.mFavicon At ChromeBrowserProvider.java +M V EI: org.chromium.chrome.browser.ChromeBrowserProvider$BookmarkNode.thumbnail() may expose internal representation by returning ChromeBrowserProvider$BookmarkNode.mThumbnail At ChromeBrowserProvider.java +M V MS: org.chromium.android_webview.AwResource.RAW_LOAD_ERROR isn't final and can't be protected from malicious code In AwResource.java +M V MS: org.chromium.android_webview.AwResource.RAW_NO_DOMAIN isn't final and can't be protected from malicious code In AwResource.java +M V MS: org.chromium.android_webview.AwResource.STRING_DEFAULT_TEXT_ENCODING isn't final and can't be protected from malicious code In AwResource.java +M V MS: org.chromium.content.browser.LoadUrlParams.LOAD_TYPE_BROWSER_INITIATED_HTTP_POST should be package protected In LoadUrlParams.java +M V MS: org.chromium.content.browser.LoadUrlParams.LOAD_TYPE_DATA isn't final and can't be protected from malicious code In LoadUrlParams.java +M V MS: org.chromium.content.browser.LoadUrlParams.LOAD_TYPE_DEFAULT should be package protected In LoadUrlParams.java +M V MS: org.chromium.content.browser.LoadUrlParams.UA_OVERRIDE_INHERIT should be package protected In LoadUrlParams.java +M V MS: org.chromium.content.browser.LoadUrlParams.UA_OVERRIDE_TRUE isn't final and can't be protected from malicious code In LoadUrlParams.java +M M LI: Incorrect lazy initialization of static field org.chromium.chrome.browser.sync.ProfileSyncService.sSyncSetupManager in org.chromium.chrome.browser.sync.ProfileSyncService.get(Context) At ProfileSyncService.java diff --git a/build/android/gyp/ant.py b/build/android/gyp/ant.py new file mode 100755 index 0000000000..acf3dccdce --- /dev/null +++ b/build/android/gyp/ant.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python +# +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""An Ant wrapper that suppresses useless Ant output + +Ant build scripts output "BUILD SUCCESSFUL" and build timing at the end of +every build. In the Android build, this just adds a lot of useless noise to the +build output. This script forwards its arguments to ant, and prints Ant's +output up until the BUILD SUCCESSFUL line. +""" + +import sys + +from util import build_utils + + +def main(argv): + stdout = build_utils.CheckCallDie(['ant'] + argv[1:], suppress_output=True) + stdout = stdout.strip().split('\n') + for line in stdout: + if line.strip() == 'BUILD SUCCESSFUL': + break + print line + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) + diff --git a/build/android/gyp/apk_install.py b/build/android/gyp/apk_install.py new file mode 100755 index 0000000000..e308aabdb2 --- /dev/null +++ b/build/android/gyp/apk_install.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python +# +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Installs an APK. + +""" + +import optparse +import os +import re +import subprocess +import sys + +from util import build_device +from util import build_utils +from util import md5_check + +BUILD_ANDROID_DIR = os.path.join(os.path.dirname(__file__), '..') +sys.path.append(BUILD_ANDROID_DIR) + +from pylib.utils import apk_helper + +def GetNewMetadata(device, apk_package): + """Gets the metadata on the device for the apk_package apk.""" + output = device.RunShellCommand('ls -l /data/app/') + # Matches lines like: + # -rw-r--r-- system system 7376582 2013-04-19 16:34 org.chromium.chrome.testshell.apk + # -rw-r--r-- system system 7376582 2013-04-19 16:34 org.chromium.chrome.testshell-1.apk + apk_matcher = lambda s: re.match('.*%s(-[0-9]*)?.apk$' % apk_package, s) + matches = filter(apk_matcher, output) + return matches[0] if matches else None + +def HasInstallMetadataChanged(device, apk_package, metadata_path): + """Checks if the metadata on the device for apk_package has changed.""" + if not os.path.exists(metadata_path): + return True + + with open(metadata_path, 'r') as expected_file: + return expected_file.read() != device.GetInstallMetadata(apk_package) + + +def RecordInstallMetadata(device, apk_package, metadata_path): + """Records the metadata from the device for apk_package.""" + metadata = GetNewMetadata(device, apk_package) + if not metadata: + raise Exception('APK install failed unexpectedly.') + + with open(metadata_path, 'w') as outfile: + outfile.write(metadata) + + +def main(argv): + parser = optparse.OptionParser() + parser.add_option('--apk-path', + help='Path to .apk to install.') + parser.add_option('--install-record', + help='Path to install record (touched only when APK is installed).') + parser.add_option('--build-device-configuration', + help='Path to build device configuration.') + parser.add_option('--stamp', + help='Path to touch on success.') + options, _ = parser.parse_args() + + device = build_device.GetBuildDeviceFromPath( + options.build_device_configuration) + if not device: + return + + serial_number = device.GetSerialNumber() + apk_package = apk_helper.GetPackageName(options.apk_path) + + metadata_path = '%s.%s.device.time.stamp' % (options.apk_path, serial_number) + + # If the APK on the device does not match the one that was last installed by + # the build, then the APK has to be installed (regardless of the md5 record). + force_install = HasInstallMetadataChanged(device, apk_package, metadata_path) + + def Install(): + device.Install(options.apk_path, reinstall=True) + RecordInstallMetadata(device, apk_package, metadata_path) + build_utils.Touch(options.install_record) + + + record_path = '%s.%s.md5.stamp' % (options.apk_path, serial_number) + md5_check.CallAndRecordIfStale( + Install, + record_path=record_path, + input_paths=[options.apk_path], + force=force_install) + + if options.stamp: + build_utils.Touch(options.stamp) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/build/android/gyp/create_device_library_links.py b/build/android/gyp/create_device_library_links.py new file mode 100755 index 0000000000..1df177d6dd --- /dev/null +++ b/build/android/gyp/create_device_library_links.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python +# +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Creates symlinks to native libraries for an APK. + +The native libraries should have previously been pushed to the device (in +options.target_dir). This script then creates links in an apk's lib/ folder to +those native libraries. +""" + +import json +import optparse +import os +import sys + +from util import build_device +from util import build_utils +from util import md5_check + +BUILD_ANDROID_DIR = os.path.join(os.path.dirname(__file__), '..') +sys.path.append(BUILD_ANDROID_DIR) + +from pylib.utils import apk_helper + +def RunShellCommand(device, cmd): + output = device.RunShellCommand(cmd) + + if output: + raise Exception( + 'Unexpected output running command: ' + cmd + '\n' + + '\n'.join(output)) + + +def CreateSymlinkScript(options): + libraries = build_utils.ReadJson(options.libraries_json) + + link_cmd = ( + 'rm $APK_LIBRARIES_DIR/%(lib_basename)s > /dev/null 2>&1 \n' + 'ln -s $STRIPPED_LIBRARIES_DIR/%(lib_basename)s ' + '$APK_LIBRARIES_DIR/%(lib_basename)s \n' + ) + + script = '#!/bin/sh \n' + + for lib in libraries: + script += link_cmd % { 'lib_basename': lib } + + with open(options.script_host_path, 'w') as scriptfile: + scriptfile.write(script) + + +def TriggerSymlinkScript(options): + device = build_device.GetBuildDeviceFromPath( + options.build_device_configuration) + if not device: + return + + apk_package = apk_helper.GetPackageName(options.apk) + apk_libraries_dir = '/data/data/%s/lib' % apk_package + + device_dir = os.path.dirname(options.script_device_path) + mkdir_cmd = ('if [ ! -e %(dir)s ]; then mkdir -p %(dir)s; fi ' % + { 'dir': device_dir }) + RunShellCommand(device, mkdir_cmd) + device.PushIfNeeded(options.script_host_path, options.script_device_path) + + trigger_cmd = ( + 'APK_LIBRARIES_DIR=%(apk_libraries_dir)s; ' + 'STRIPPED_LIBRARIES_DIR=%(target_dir)s; ' + '. %(script_device_path)s' + ) % { + 'apk_libraries_dir': apk_libraries_dir, + 'target_dir': options.target_dir, + 'script_device_path': options.script_device_path + } + RunShellCommand(device, trigger_cmd) + + +def main(argv): + parser = optparse.OptionParser() + parser.add_option('--apk', help='Path to the apk.') + parser.add_option('--script-host-path', + help='Path on the host for the symlink script.') + parser.add_option('--script-device-path', + help='Path on the device to push the created symlink script.') + parser.add_option('--libraries-json', + help='Path to the json list of native libraries.') + parser.add_option('--target-dir', + help='Device directory that contains the target libraries for symlinks.') + parser.add_option('--stamp', help='Path to touch on success.') + parser.add_option('--build-device-configuration', + help='Path to build device configuration.') + options, _ = parser.parse_args() + + required_options = ['apk', 'libraries_json', 'script_host_path', + 'script_device_path', 'target_dir'] + build_utils.CheckOptions(options, parser, required=required_options) + + CreateSymlinkScript(options) + TriggerSymlinkScript(options) + + if options.stamp: + build_utils.Touch(options.stamp) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/build/android/gyp/create_native_libraries_header.py b/build/android/gyp/create_native_libraries_header.py new file mode 100755 index 0000000000..5c1bf1b6a3 --- /dev/null +++ b/build/android/gyp/create_native_libraries_header.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +# +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Writes .h file for NativeLibraries.template + +This header should contain the list of native libraries to load in the form: + = { "lib1", "lib2" } +""" + +import json +import optparse +import os +import sys + +from util import build_utils + + +def main(argv): + parser = optparse.OptionParser() + + parser.add_option('--output', help='Path to generated .java file') + parser.add_option('--ordered-libraries', + help='Path to json file containing list of ordered libraries') + parser.add_option('--stamp', help='Path to touch on success') + + # args should be the list of libraries in dependency order. + options, _ = parser.parse_args() + + build_utils.MakeDirectory(os.path.dirname(options.output)) + + with open(options.ordered_libraries, 'r') as libfile: + libraries = json.load(libfile) + # Generates string of the form '= { "base", "net", + # "content_shell_content_view" }' from a list of the form ["libbase.so", + # libnet.so", "libcontent_shell_content_view.so"] + libraries = ['"' + lib[3:-3] + '"' for lib in libraries] + array = '= { ' + ', '.join(libraries) + '}'; + + with open(options.output, 'w') as header: + header.write(array) + + if options.stamp: + build_utils.Touch(options.stamp) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/build/android/gyp/create_standalone_apk.py b/build/android/gyp/create_standalone_apk.py new file mode 100755 index 0000000000..de541a6d5e --- /dev/null +++ b/build/android/gyp/create_standalone_apk.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Combines stripped libraries and incomplete APK into single standalone APK. + +""" + +import optparse +import os +import shutil +import sys +import tempfile + +from util import build_utils +from util import md5_check + +def CreateStandaloneApk(options): + def DoZip(): + with tempfile.NamedTemporaryFile(suffix='.zip') as intermediate_file: + intermediate_path = intermediate_file.name + shutil.copy(options.input_apk_path, intermediate_path) + apk_path_abs = os.path.abspath(intermediate_path) + build_utils.CheckCallDie( + ['zip', '-r', '-1', apk_path_abs, 'lib'], + cwd=options.libraries_top_dir, + suppress_output=True) + shutil.copy(intermediate_path, options.output_apk_path) + + input_paths = [options.input_apk_path, options.libraries_top_dir] + record_path = '%s.standalone.stamp' % options.input_apk_path + md5_check.CallAndRecordIfStale( + DoZip, + record_path=record_path, + input_paths=input_paths) + + +def main(argv): + parser = optparse.OptionParser() + parser.add_option('--libraries-top-dir', + help='Top directory that contains libraries ' + '(i.e. library paths are like ' + 'libraries_top_dir/lib/android_app_abi/foo.so).') + parser.add_option('--input-apk-path', help='Path to incomplete APK.') + parser.add_option('--output-apk-path', help='Path for standalone APK.') + parser.add_option('--stamp', help='Path to touch on success.') + options, _ = parser.parse_args() + + required_options = ['libraries_top_dir', 'input_apk_path', 'output_apk_path'] + build_utils.CheckOptions(options, parser, required=required_options) + + CreateStandaloneApk(options) + + if options.stamp: + build_utils.Touch(options.stamp) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/build/android/gyp/dex.py b/build/android/gyp/dex.py new file mode 100755 index 0000000000..c1e5869d13 --- /dev/null +++ b/build/android/gyp/dex.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +# +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import fnmatch +import optparse +import os +import sys + +from util import build_utils +from util import md5_check + + +def DoDex(options, paths): + dx_binary = os.path.join(options.android_sdk_tools, 'dx') + + # See http://crbug.com/272064 for context on --force-jumbo. + dex_cmd = [dx_binary, '--dex', '--force-jumbo', '--output', + options.dex_path] + paths + + record_path = '%s.md5.stamp' % options.dex_path + md5_check.CallAndRecordIfStale( + lambda: build_utils.CheckCallDie(dex_cmd, suppress_output=True), + record_path=record_path, + input_paths=paths, + input_strings=dex_cmd) + + build_utils.Touch(options.dex_path) + + +def main(argv): + parser = optparse.OptionParser() + parser.add_option('--android-sdk-tools', + help='Android sdk build tools directory.') + parser.add_option('--dex-path', help='Dex output path.') + parser.add_option('--configuration-name', + help='The build CONFIGURATION_NAME.') + parser.add_option('--proguard-enabled', + help='"true" if proguard is enabled.') + parser.add_option('--proguard-enabled-input-path', + help='Path to dex in Release mode when proguard is enabled.') + parser.add_option('--stamp', help='Path to touch on success.') + + # TODO(newt): remove this once http://crbug.com/177552 is fixed in ninja. + parser.add_option('--ignore', help='Ignored.') + + options, paths = parser.parse_args() + + if (options.proguard_enabled == "true" + and options.configuration_name == "Release"): + paths = [options.proguard_enabled_input_path] + + DoDex(options, paths) + + if options.stamp: + build_utils.Touch(options.stamp) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) + diff --git a/build/android/gyp/finalize_apk.py b/build/android/gyp/finalize_apk.py new file mode 100755 index 0000000000..0b1d2c258a --- /dev/null +++ b/build/android/gyp/finalize_apk.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python +# +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Signs and zipaligns APK. + +""" + +import optparse +import os +import shutil +import sys +import tempfile + +from util import build_utils + +def SignApk(keystore_path, unsigned_path, signed_path): + shutil.copy(unsigned_path, signed_path) + sign_cmd = [ + 'jarsigner', + '-sigalg', 'MD5withRSA', + '-digestalg', 'SHA1', + '-keystore', keystore_path, + '-storepass', 'chromium', + signed_path, + 'chromiumdebugkey', + ] + build_utils.CheckCallDie(sign_cmd) + + +def AlignApk(android_sdk_root, unaligned_path, final_path): + align_cmd = [ + os.path.join(android_sdk_root, 'tools', 'zipalign'), + '-f', '4', # 4 bytes + unaligned_path, + final_path, + ] + build_utils.CheckCallDie(align_cmd) + + +def main(argv): + parser = optparse.OptionParser() + + parser.add_option('--android-sdk-root', help='Android sdk root directory.') + parser.add_option('--unsigned-apk-path', help='Path to input unsigned APK.') + parser.add_option('--final-apk-path', + help='Path to output signed and aligned APK.') + parser.add_option('--keystore-path', help='Path to keystore for signing.') + parser.add_option('--stamp', help='Path to touch on success.') + + # TODO(newt): remove this once http://crbug.com/177552 is fixed in ninja. + parser.add_option('--ignore', help='Ignored.') + + options, _ = parser.parse_args() + + with tempfile.NamedTemporaryFile() as intermediate_file: + signed_apk_path = intermediate_file.name + SignApk(options.keystore_path, options.unsigned_apk_path, signed_apk_path) + AlignApk(options.android_sdk_root, signed_apk_path, options.final_apk_path) + + if options.stamp: + build_utils.Touch(options.stamp) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) + + diff --git a/build/android/gyp/gcc_preprocess.py b/build/android/gyp/gcc_preprocess.py new file mode 100755 index 0000000000..93efdb3a74 --- /dev/null +++ b/build/android/gyp/gcc_preprocess.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +# +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import optparse +import os +import subprocess +import sys + +from util import build_utils + +def DoGcc(options): + build_utils.MakeDirectory(os.path.dirname(options.output)) + + gcc_cmd = [ + 'gcc', # invoke host gcc. + '-E', # stop after preprocessing. + '-D', 'ANDROID', # Specify ANDROID define for pre-processor. + '-x', 'c-header', # treat sources as C header files + '-P', # disable line markers, i.e. '#line 309' + '-I', options.include_path, + '-o', options.output, + options.template + ] + + build_utils.CheckCallDie(gcc_cmd) + + +def main(argv): + parser = optparse.OptionParser() + parser.add_option('--include-path', help='Include path for gcc.') + parser.add_option('--template', help='Path to template.') + parser.add_option('--output', help='Path for generated file.') + parser.add_option('--stamp', help='Path to touch on success.') + + # TODO(newt): remove this once http://crbug.com/177552 is fixed in ninja. + parser.add_option('--ignore', help='Ignored.') + + options, _ = parser.parse_args() + + DoGcc(options) + + if options.stamp: + build_utils.Touch(options.stamp) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/build/android/gyp/generate_v14_compatible_resources.py b/build/android/gyp/generate_v14_compatible_resources.py new file mode 100755 index 0000000000..71557445aa --- /dev/null +++ b/build/android/gyp/generate_v14_compatible_resources.py @@ -0,0 +1,336 @@ +#!/usr/bin/env python +# +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Convert Android xml resources to API 14 compatible. + +There are two reasons that we cannot just use API 17 attributes, +so we are generating another set of resources by this script. + +1. paddingStart attribute can cause a crash on Galaxy Tab 2. +2. There is a bug that paddingStart does not override paddingLeft on + JB-MR1. This is fixed on JB-MR2. + +Therefore, this resource generation script can be removed when +we drop the support for JB-MR1. + +Please refer to http://crbug.com/235118 for the details. +""" + +import optparse +import os +import re +import shutil +import sys +import xml.dom.minidom as minidom + +from util import build_utils + +# Note that we are assuming 'android:' is an alias of +# the namespace 'http://schemas.android.com/apk/res/android'. + +GRAVITY_ATTRIBUTES = ('android:gravity', 'android:layout_gravity') + +# Almost all the attributes that has "Start" or "End" in +# its name should be mapped. +ATTRIBUTES_TO_MAP = {'paddingStart' : 'paddingLeft', + 'drawableStart' : 'drawableLeft', + 'layout_alignStart' : 'layout_alignLeft', + 'layout_marginStart' : 'layout_marginLeft', + 'layout_alignParentStart' : 'layout_alignParentLeft', + 'layout_toStartOf' : 'layout_toLeftOf', + 'paddingEnd' : 'paddingRight', + 'drawableEnd' : 'drawableRight', + 'layout_alignEnd' : 'layout_alignRight', + 'layout_marginEnd' : 'layout_marginRight', + 'layout_alignParentEnd' : 'layout_alignParentRight', + 'layout_toEndOf' : 'layout_toRightOf'} + +ATTRIBUTES_TO_MAP = dict(['android:' + k, 'android:' + v] for k, v + in ATTRIBUTES_TO_MAP.iteritems()) + +ATTRIBUTES_TO_MAP_REVERSED = dict([v,k] for k, v + in ATTRIBUTES_TO_MAP.iteritems()) + + +def IterateXmlElements(node): + """minidom helper function that iterates all the element nodes. + Iteration order is pre-order depth-first.""" + if node.nodeType == node.ELEMENT_NODE: + yield node + for child_node in node.childNodes: + for child_node_element in IterateXmlElements(child_node): + yield child_node_element + + +def WarnIfDeprecatedAttribute(name, value, filename): + """print a warning message if the given attribute is deprecated.""" + if name in ATTRIBUTES_TO_MAP_REVERSED: + print >> sys.stderr, ('warning: ' + filename + ' should use ' + + ATTRIBUTES_TO_MAP_REVERSED[name] + + ' instead of ' + name) + elif name in GRAVITY_ATTRIBUTES and ('left' in value or 'right' in value): + print >> sys.stderr, ('warning: ' + filename + + ' should use start/end instead of left/right for ' + + name) + + +def WriteDomToFile(dom, filename): + """Write the given dom to filename.""" + build_utils.MakeDirectory(os.path.dirname(filename)) + with open(filename, 'w') as f: + dom.writexml(f, '', ' ', '\n', encoding='utf-8') + + +def HasStyleResource(dom): + """Return True if the dom is a style resource, False otherwise.""" + root_node = IterateXmlElements(dom).next() + return bool(root_node.nodeName == 'resources' and + list(root_node.getElementsByTagName('style'))) + + +def ErrorIfStyleResourceExistsInDir(input_dir): + """If a style resource is in input_dir, exist with an error message.""" + for input_filename in build_utils.FindInDirectory(input_dir, '*.xml'): + dom = minidom.parse(input_filename) + if HasStyleResource(dom): + raise Exception('error: style file ' + input_filename + + ' should be under ' + input_dir + + '-v17 directory. Please refer to ' + 'http://crbug.com/243952 for the details.') + + +def GenerateV14LayoutResourceDom(dom, filename_for_warning): + """Convert layout resource to API 14 compatible layout resource. + + Args: + dom: parsed minidom object to be modified. + filename_for_warning: file name to display in case we print warnings. + If None, do not print warning. + Returns: + True if dom is modified, False otherwise. + """ + is_modified = False + + # Iterate all the elements' attributes to find attributes to convert. + for element in IterateXmlElements(dom): + for name, value in list(element.attributes.items()): + # Convert any API 17 Start/End attributes to Left/Right attributes. + # For example, from paddingStart="10dp" to paddingLeft="10dp" + # Note: gravity attributes are not necessary to convert because + # start/end values are backward-compatible. Explained at + # https://plus.sandbox.google.com/+RomanNurik/posts/huuJd8iVVXY?e=Showroom + if name in ATTRIBUTES_TO_MAP: + element.setAttribute(ATTRIBUTES_TO_MAP[name], value) + del element.attributes[name] + is_modified = True + elif filename_for_warning: + WarnIfDeprecatedAttribute(name, value, filename_for_warning) + + return is_modified + + +def GenerateV14StyleResourceDom(dom, filename_for_warning): + """Convert style resource to API 14 compatible style resource. + + Args: + dom: parsed minidom object to be modified. + filename_for_warning: file name to display in case we print warnings. + If None, do not print warning. + Returns: + True if dom is modified, False otherwise. + """ + is_modified = False + + for style_element in dom.getElementsByTagName('style'): + for item_element in style_element.getElementsByTagName('item'): + name = item_element.attributes['name'].value + value = item_element.childNodes[0].nodeValue + if name in ATTRIBUTES_TO_MAP: + item_element.attributes['name'].value = ATTRIBUTES_TO_MAP[name] + is_modified = True + elif filename_for_warning: + WarnIfDeprecatedAttribute(name, value, filename_for_warning) + + return is_modified + + +def GenerateV14LayoutResource(input_filename, output_v14_filename, + output_v17_filename): + """Convert API 17 layout resource to API 14 compatible layout resource. + + It's mostly a simple replacement, s/Start/Left s/End/Right, + on the attribute names. + If the generated resource is identical to the original resource, + don't do anything. If not, write the generated resource to + output_v14_filename, and copy the original resource to output_v17_filename. + """ + dom = minidom.parse(input_filename) + is_modified = GenerateV14LayoutResourceDom(dom, input_filename) + + if is_modified: + # Write the generated resource. + WriteDomToFile(dom, output_v14_filename) + + # Copy the original resource. + build_utils.MakeDirectory(os.path.dirname(output_v17_filename)) + shutil.copy2(input_filename, output_v17_filename) + + +def GenerateV14StyleResource(input_filename, output_v14_filename): + """Convert API 17 style resources to API 14 compatible style resource. + + Write the generated style resource to output_v14_filename. + It's mostly a simple replacement, s/Start/Left s/End/Right, + on the attribute names. + """ + dom = minidom.parse(input_filename) + GenerateV14StyleResourceDom(dom, input_filename) + + # Write the generated resource. + WriteDomToFile(dom, output_v14_filename) + + +def GenerateV14LayoutResourcesInDir(input_dir, output_v14_dir, output_v17_dir): + """Convert layout resources to API 14 compatible resources in input_dir.""" + for input_filename in build_utils.FindInDirectory(input_dir, '*.xml'): + rel_filename = os.path.relpath(input_filename, input_dir) + output_v14_filename = os.path.join(output_v14_dir, rel_filename) + output_v17_filename = os.path.join(output_v17_dir, rel_filename) + GenerateV14LayoutResource(input_filename, output_v14_filename, + output_v17_filename) + + +def GenerateV14StyleResourcesInDir(input_dir, output_v14_dir): + """Convert style resources to API 14 compatible resources in input_dir.""" + for input_filename in build_utils.FindInDirectory(input_dir, '*.xml'): + rel_filename = os.path.relpath(input_filename, input_dir) + output_v14_filename = os.path.join(output_v14_dir, rel_filename) + GenerateV14StyleResource(input_filename, output_v14_filename) + + +def VerifyV14ResourcesInDir(input_dir, resource_type): + """Verify that the resources in input_dir is compatible with v14, i.e., they + don't use attributes that cause crashes on certain devices. Print an error if + they have.""" + for input_filename in build_utils.FindInDirectory(input_dir, '*.xml'): + exception_message = ('error : ' + input_filename + ' has an RTL attribute, ' + 'i.e., attribute that has "start" or "end" in its name.' + ' Pre-v17 resources should not include it because it ' + 'can cause crashes on certain devices. Please refer to ' + 'http://crbug.com/243952 for the details.') + dom = minidom.parse(input_filename) + if resource_type in ('layout', 'xml'): + if GenerateV14LayoutResourceDom(dom, None): + raise Exception(exception_message) + elif resource_type == 'values': + if GenerateV14StyleResourceDom(dom, None): + raise Exception(exception_message) + + +def WarnIfDeprecatedAttributeInDir(input_dir, resource_type): + """Print warning if resources in input_dir have deprecated attributes, e.g., + paddingLeft, PaddingRight""" + for input_filename in build_utils.FindInDirectory(input_dir, '*.xml'): + dom = minidom.parse(input_filename) + if resource_type in ('layout', 'xml'): + GenerateV14LayoutResourceDom(dom, input_filename) + elif resource_type == 'values': + GenerateV14StyleResourceDom(dom, input_filename) + + +def ParseArgs(): + """Parses command line options. + + Returns: + An options object as from optparse.OptionsParser.parse_args() + """ + parser = optparse.OptionParser() + parser.add_option('--res-dir', + help='directory containing resources ' + 'used to generate v14 compatible resources') + parser.add_option('--res-v14-compatibility-dir', + help='output directory into which ' + 'v14 compatible resources will be generated') + parser.add_option('--stamp', help='File to touch on success') + parser.add_option('--verify-only', action="store_true", help='Do not generate' + ' v14 resources. Instead, just verify that the resources are already ' + "compatible with v14, i.e. they don't use attributes that cause crashes " + 'on certain devices.') + + options, args = parser.parse_args() + + if args: + parser.error('No positional arguments should be given.') + + # Check that required options have been provided. + required_options = ('res_dir', 'res_v14_compatibility_dir') + build_utils.CheckOptions(options, parser, required=required_options) + return options + + +def main(argv): + options = ParseArgs() + + build_utils.DeleteDirectory(options.res_v14_compatibility_dir) + build_utils.MakeDirectory(options.res_v14_compatibility_dir) + + for name in os.listdir(options.res_dir): + if not os.path.isdir(os.path.join(options.res_dir, name)): + continue + + dir_pieces = name.split('-') + resource_type = dir_pieces[0] + qualifiers = dir_pieces[1:] + + api_level_qualifier_index = -1 + api_level_qualifier = '' + for index, qualifier in enumerate(qualifiers): + if re.match('v[0-9]+$', qualifier): + api_level_qualifier_index = index + api_level_qualifier = qualifier + break + + # Android pre-v17 API doesn't support RTL. Skip. + if 'ldrtl' in qualifiers: + continue + + input_dir = os.path.abspath(os.path.join(options.res_dir, name)) + + if options.verify_only: + if not api_level_qualifier or int(api_level_qualifier[1:]) < 17: + VerifyV14ResourcesInDir(input_dir, resource_type) + else: + WarnIfDeprecatedAttributeInDir(input_dir, resource_type) + else: + # We also need to copy the original v17 resource to *-v17 directory + # because the generated v14 resource will hide the original resource. + output_v14_dir = os.path.join(options.res_v14_compatibility_dir, name) + output_v17_dir = os.path.join(options.res_v14_compatibility_dir, name + + '-v17') + + # We only convert layout resources under layout*/, xml*/, + # and style resources under values*/. + if resource_type in ('layout', 'xml'): + if not api_level_qualifier: + GenerateV14LayoutResourcesInDir(input_dir, output_v14_dir, + output_v17_dir) + elif resource_type == 'values': + if api_level_qualifier == 'v17': + output_qualifiers = qualifiers[:] + del output_qualifiers[api_level_qualifier_index] + output_v14_dir = os.path.join(options.res_v14_compatibility_dir, + '-'.join([resource_type] + + output_qualifiers)) + GenerateV14StyleResourcesInDir(input_dir, output_v14_dir) + elif not api_level_qualifier: + ErrorIfStyleResourceExistsInDir(input_dir) + + if options.stamp: + build_utils.Touch(options.stamp) + +if __name__ == '__main__': + sys.exit(main(sys.argv)) + diff --git a/build/android/gyp/get_device_configuration.py b/build/android/gyp/get_device_configuration.py new file mode 100755 index 0000000000..f27c12be4b --- /dev/null +++ b/build/android/gyp/get_device_configuration.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python +# +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Gets and writes the configurations of the attached devices. + +This configuration is used by later build steps to determine which devices to +install to and what needs to be installed to those devices. +""" + +import logging +import optparse +import os +import subprocess +import sys + +from util import build_utils +from util import build_device + +BUILD_ANDROID_DIR = os.path.join(os.path.dirname(__file__), '..') +sys.path.append(BUILD_ANDROID_DIR) + +from pylib.utils import apk_helper + + +def main(argv): + parser = optparse.OptionParser() + parser.add_option('--stamp', action='store') + parser.add_option('--output', action='store') + options, _ = parser.parse_args(argv) + + devices = build_device.GetAttachedDevices() + + device_configurations = [] + for d in devices: + configuration, is_online, has_root = ( + build_device.GetConfigurationForDevice(d)) + + if not is_online: + build_utils.PrintBigWarning( + '%s is not online. Skipping managed install for this device. ' + 'Try rebooting the device to fix this warning.' % d) + continue + + if not has_root: + build_utils.PrintBigWarning( + '"adb root" failed on device: %s\n' + 'Skipping managed install for this device.' + % configuration['description']) + continue + + device_configurations.append(configuration) + + if len(device_configurations) == 0: + build_utils.PrintBigWarning( + 'No valid devices attached. Skipping managed install steps.') + elif len(devices) > 1: + # Note that this checks len(devices) and not len(device_configurations). + # This way, any time there are multiple devices attached it is + # explicitly stated which device we will install things to even if all but + # one device were rejected for other reasons (e.g. two devices attached, + # one w/o root). + build_utils.PrintBigWarning( + 'Multiple devices attached. ' + 'Installing to the preferred device: ' + '%(id)s (%(description)s)' % (device_configurations[0])) + + + build_device.WriteConfigurations(device_configurations, options.output) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/build/android/gyp/jar.py b/build/android/gyp/jar.py new file mode 100755 index 0000000000..6d4fe12bfa --- /dev/null +++ b/build/android/gyp/jar.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +# +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import fnmatch +import optparse +import os +import sys + +from util import build_utils +from util import md5_check + + +def DoJar(options): + class_files = build_utils.FindInDirectory(options.classes_dir, '*.class') + for exclude in build_utils.ParseGypList(options.excluded_classes): + class_files = filter( + lambda f: not fnmatch.fnmatch(f, exclude), class_files) + + jar_path = os.path.abspath(options.jar_path) + + # The paths of the files in the jar will be the same as they are passed in to + # the command. Because of this, the command should be run in + # options.classes_dir so the .class file paths in the jar are correct. + jar_cwd = options.classes_dir + class_files_rel = [os.path.relpath(f, jar_cwd) for f in class_files] + jar_cmd = ['jar', 'cf0', jar_path] + class_files_rel + + record_path = '%s.md5.stamp' % options.jar_path + md5_check.CallAndRecordIfStale( + lambda: build_utils.CheckCallDie(jar_cmd, cwd=jar_cwd), + record_path=record_path, + input_paths=class_files, + input_strings=jar_cmd) + + build_utils.Touch(options.jar_path) + + +def main(argv): + parser = optparse.OptionParser() + parser.add_option('--classes-dir', help='Directory containing .class files.') + parser.add_option('--jar-path', help='Jar output path.') + parser.add_option('--excluded-classes', + help='List of .class file patterns to exclude from the jar.') + parser.add_option('--stamp', help='Path to touch on success.') + + # TODO(newt): remove this once http://crbug.com/177552 is fixed in ninja. + parser.add_option('--ignore', help='Ignored.') + + options, _ = parser.parse_args() + + DoJar(options) + + if options.stamp: + build_utils.Touch(options.stamp) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) + diff --git a/build/android/gyp/jar_toc.py b/build/android/gyp/jar_toc.py new file mode 100755 index 0000000000..54e90bcf28 --- /dev/null +++ b/build/android/gyp/jar_toc.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python +# +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Creates a TOC file from a Java jar. + +The TOC file contains the non-package API of the jar. This includes all +public/protected classes/functions/members and the values of static final +variables. Some other information (major/minor javac version) is also included. + +This TOC file then can be used to determine if a dependent library should be +rebuilt when this jar changes. I.e. any change to the jar that would require a +rebuild, will have a corresponding change in the TOC file. +""" + +import optparse +import os +import re +import sys +import zipfile + +from util import build_utils +from util import md5_check + + +def GetClassesInZipFile(zip_file): + classes = [] + files = zip_file.namelist() + for f in files: + if f.endswith('.class'): + # f is of the form org/chromium/base/Class$Inner.class + classes.append(f.replace('/', '.')[:-6]) + return classes + + +def CallJavap(classpath, classes): + javap_cmd = [ + 'javap', + '-protected', # In reality both public & protected. + # -verbose is required to get constant values (which can be inlined in + # dependents). + '-verbose', + '-classpath', classpath + ] + classes + return build_utils.CheckCallDie(javap_cmd, suppress_output=True) + + +def ExtractToc(disassembled_classes): + # javap output is structured by indent (2-space) levels. + good_patterns = [ + '^[^ ]', # This includes all class/function/member signatures. + '^ SourceFile:', + '^ minor version:', + '^ major version:', + '^ Constant value:', + ] + bad_patterns = [ + '^const #', # Matches the constant pool (i.e. literals used in the class). + ] + + def JavapFilter(line): + return (re.match('|'.join(good_patterns), line) and + not re.match('|'.join(bad_patterns), line)) + toc = filter(JavapFilter, disassembled_classes.split('\n')) + + return '\n'.join(toc) + + +def UpdateToc(jar_path, toc_path): + classes = GetClassesInZipFile(zipfile.ZipFile(jar_path)) + javap_output = CallJavap(classpath=jar_path, classes=classes) + toc = ExtractToc(javap_output) + + with open(toc_path, 'w') as tocfile: + tocfile.write(toc) + + +def DoJarToc(options): + jar_path = options.jar_path + toc_path = options.toc_path + record_path = '%s.md5.stamp' % toc_path + md5_check.CallAndRecordIfStale( + lambda: UpdateToc(jar_path, toc_path), + record_path=record_path, + input_paths=[jar_path], + ) + build_utils.Touch(toc_path) + + +def main(argv): + parser = optparse.OptionParser() + parser.add_option('--jar-path', help='Input .jar path.') + parser.add_option('--toc-path', help='Output .jar.TOC path.') + parser.add_option('--stamp', help='Path to touch on success.') + + # TODO(newt): remove this once http://crbug.com/177552 is fixed in ninja. + parser.add_option('--ignore', help='Ignored.') + + options, _ = parser.parse_args() + + DoJarToc(options) + + if options.stamp: + build_utils.Touch(options.stamp) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/build/android/gyp/javac.py b/build/android/gyp/javac.py new file mode 100755 index 0000000000..c1170048b5 --- /dev/null +++ b/build/android/gyp/javac.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python +# +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import fnmatch +import optparse +import os +import sys + +from util import build_utils +from util import md5_check + + +def DoJavac(options): + output_dir = options.output_dir + + src_dirs = build_utils.ParseGypList(options.src_dirs) + java_files = build_utils.FindInDirectories(src_dirs, '*.java') + if options.javac_includes: + javac_includes = build_utils.ParseGypList(options.javac_includes) + filtered_java_files = [] + for f in java_files: + for include in javac_includes: + if fnmatch.fnmatch(f, include): + filtered_java_files.append(f) + break + java_files = filtered_java_files + + # Compiling guava with certain orderings of input files causes a compiler + # crash... Sorted order works, so use that. + # See https://code.google.com/p/guava-libraries/issues/detail?id=950 + java_files.sort() + classpath = build_utils.ParseGypList(options.classpath) + + jar_inputs = [] + for path in classpath: + if os.path.exists(path + '.TOC'): + jar_inputs.append(path + '.TOC') + else: + jar_inputs.append(path) + + javac_cmd = [ + 'javac', + '-g', + '-source', '1.5', + '-target', '1.5', + '-classpath', ':'.join(classpath), + '-d', output_dir, + '-Xlint:unchecked', + '-Xlint:deprecation', + ] + java_files + + def Compile(): + # Delete the classes directory. This ensures that all .class files in the + # output are actually from the input .java files. For example, if a .java + # file is deleted or an inner class is removed, the classes directory should + # not contain the corresponding old .class file after running this action. + build_utils.DeleteDirectory(output_dir) + build_utils.MakeDirectory(output_dir) + suppress_output = not options.chromium_code + build_utils.CheckCallDie(javac_cmd, suppress_output=suppress_output) + + record_path = '%s/javac.md5.stamp' % options.output_dir + md5_check.CallAndRecordIfStale( + Compile, + record_path=record_path, + input_paths=java_files + jar_inputs, + input_strings=javac_cmd) + + +def main(argv): + parser = optparse.OptionParser() + parser.add_option('--src-dirs', help='Directories containing java files.') + parser.add_option('--javac-includes', + help='A list of file patterns. If provided, only java files that match' + + 'one of the patterns will be compiled.') + parser.add_option('--classpath', help='Classpath for javac.') + parser.add_option('--output-dir', help='Directory for javac output.') + parser.add_option('--stamp', help='Path to touch on success.') + parser.add_option('--chromium-code', type='int', help='Whether code being ' + 'compiled should be built with stricter warnings for ' + 'chromium code.') + + # TODO(newt): remove this once http://crbug.com/177552 is fixed in ninja. + parser.add_option('--ignore', help='Ignored.') + + options, _ = parser.parse_args() + + DoJavac(options) + + if options.stamp: + build_utils.Touch(options.stamp) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) + + diff --git a/build/android/gyp/process_resources.py b/build/android/gyp/process_resources.py new file mode 100755 index 0000000000..393a6667e9 --- /dev/null +++ b/build/android/gyp/process_resources.py @@ -0,0 +1,97 @@ +#!/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. + +"""Process Android library resources to generate R.java and crunched images.""" + +import optparse +import os +import shlex + +from util import build_utils + +def ParseArgs(): + """Parses command line options. + + Returns: + An options object as from optparse.OptionsParser.parse_args() + """ + parser = optparse.OptionParser() + parser.add_option('--android-sdk', help='path to the Android SDK folder') + parser.add_option('--android-sdk-tools', + help='path to the Android SDK build tools folder') + parser.add_option('--R-dir', help='directory to hold generated R.java') + parser.add_option('--res-dirs', + help='directories containing resources to be packaged') + parser.add_option('--crunch-input-dir', + help='directory containing images to be crunched') + parser.add_option('--crunch-output-dir', + help='directory to hold crunched resources') + parser.add_option('--non-constant-id', action='store_true') + parser.add_option('--custom-package', help='Java package for R.java') + parser.add_option('--android-manifest', help='AndroidManifest.xml path') + parser.add_option('--stamp', help='File to touch on success') + + # This is part of a temporary fix for crbug.com/177552. + # TODO(newt): remove this once crbug.com/177552 is fixed in ninja. + parser.add_option('--ignore', help='this argument is ignored') + (options, args) = parser.parse_args() + + if args: + parser.error('No positional arguments should be given.') + + # Check that required options have been provided. + required_options = ('android_sdk', 'android_sdk_tools', 'R_dir', + 'res_dirs', 'crunch_input_dir', 'crunch_output_dir') + build_utils.CheckOptions(options, parser, required=required_options) + + return options + + +def main(): + options = ParseArgs() + android_jar = os.path.join(options.android_sdk, 'android.jar') + aapt = os.path.join(options.android_sdk_tools, 'aapt') + + build_utils.MakeDirectory(options.R_dir) + + # Generate R.java. This R.java contains non-final constants and is used only + # while compiling the library jar (e.g. chromium_content.jar). When building + # an apk, a new R.java file with the correct resource -> ID mappings will be + # generated by merging the resources from all libraries and the main apk + # project. + package_command = [aapt, + 'package', + '-m', + '-M', options.android_manifest, + '--auto-add-overlay', + '-I', android_jar, + '--output-text-symbols', options.R_dir, + '-J', options.R_dir] + res_dirs = shlex.split(options.res_dirs) + for res_dir in res_dirs: + package_command += ['-S', res_dir] + if options.non_constant_id: + package_command.append('--non-constant-id') + if options.custom_package: + package_command += ['--custom-package', options.custom_package] + build_utils.CheckCallDie(package_command) + + # Crunch image resources. This shrinks png files and is necessary for 9-patch + # images to display correctly. + build_utils.MakeDirectory(options.crunch_output_dir) + + aapt_cmd = [aapt, + 'crunch', + '-S', options.crunch_input_dir, + '-C', options.crunch_output_dir] + build_utils.CheckCallDie(aapt_cmd, suppress_output=True) + + if options.stamp: + build_utils.Touch(options.stamp) + + +if __name__ == '__main__': + main() diff --git a/build/android/gyp/push_libraries.py b/build/android/gyp/push_libraries.py new file mode 100755 index 0000000000..349e0cbafc --- /dev/null +++ b/build/android/gyp/push_libraries.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +# +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Pushes native libraries to a device. + +""" + +import json +import optparse +import os +import sys + +from util import build_device +from util import build_utils +from util import md5_check + +def DoPush(options): + libraries = build_utils.ReadJson(options.libraries_json) + + device = build_device.GetBuildDeviceFromPath( + options.build_device_configuration) + if not device: + return + + serial_number = device.GetSerialNumber() + # A list so that it is modifiable in Push below. + needs_directory = [True] + for lib in libraries: + device_path = os.path.join(options.device_dir, lib) + host_path = os.path.join(options.libraries_dir, lib) + + def Push(): + if needs_directory: + device.RunShellCommand('mkdir -p ' + options.device_dir) + needs_directory[:] = [] # = False + device.PushIfNeeded(host_path, device_path) + + record_path = '%s.%s.push.md5.stamp' % (host_path, serial_number) + md5_check.CallAndRecordIfStale( + Push, + record_path=record_path, + input_paths=[host_path], + input_strings=[device_path]) + + +def main(argv): + parser = optparse.OptionParser() + parser.add_option('--libraries-dir', + help='Directory that contains stripped libraries.') + parser.add_option('--device-dir', + help='Device directory to push the libraries to.') + parser.add_option('--libraries-json', + help='Path to the json list of native libraries.') + parser.add_option('--stamp', help='Path to touch on success.') + parser.add_option('--build-device-configuration', + help='Path to build device configuration.') + options, _ = parser.parse_args() + + required_options = ['libraries_dir', 'device_dir', 'libraries_json'] + build_utils.CheckOptions(options, parser, required=required_options) + + DoPush(options) + + if options.stamp: + build_utils.Touch(options.stamp) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/build/android/gyp/strip_library_for_device.py b/build/android/gyp/strip_library_for_device.py new file mode 100755 index 0000000000..05eacfe43e --- /dev/null +++ b/build/android/gyp/strip_library_for_device.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +# +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import json +import optparse +import os +import sys + +from util import build_utils + + +def StripLibrary(android_strip, android_strip_args, library_path, output_path): + if build_utils.IsTimeStale(output_path, [library_path]): + strip_cmd = ([android_strip] + + android_strip_args + + ['-o', output_path, library_path]) + build_utils.CheckCallDie(strip_cmd) + + + +def main(argv): + parser = optparse.OptionParser() + + parser.add_option('--android-strip', + help='Path to the toolchain\'s strip binary') + parser.add_option('--android-strip-arg', action='append', + help='Argument to be passed to strip') + parser.add_option('--libraries-dir', + help='Directory for un-stripped libraries') + parser.add_option('--stripped-libraries-dir', + help='Directory for stripped libraries') + parser.add_option('--libraries-file', + help='Path to json file containing list of libraries') + parser.add_option('--stamp', help='Path to touch on success') + + + options, _ = parser.parse_args() + + with open(options.libraries_file, 'r') as libfile: + libraries = json.load(libfile) + + build_utils.MakeDirectory(options.stripped_libraries_dir) + + for library in libraries: + library_path = os.path.join(options.libraries_dir, library) + stripped_library_path = os.path.join( + options.stripped_libraries_dir, library) + StripLibrary(options.android_strip, options.android_strip_arg, library_path, + stripped_library_path) + + if options.stamp: + build_utils.Touch(options.stamp) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/build/android/gyp/util/__init__.py b/build/android/gyp/util/__init__.py new file mode 100644 index 0000000000..727e987e6b --- /dev/null +++ b/build/android/gyp/util/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + diff --git a/build/android/gyp/util/build_device.py b/build/android/gyp/util/build_device.py new file mode 100644 index 0000000000..11f6277453 --- /dev/null +++ b/build/android/gyp/util/build_device.py @@ -0,0 +1,98 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" A simple device interface for build steps. + +""" + +import logging +import os +import re +import sys + +import build_utils + +BUILD_ANDROID_DIR = os.path.join(os.path.dirname(__file__), '..', '..') +sys.path.append(BUILD_ANDROID_DIR) + +from pylib import android_commands + +from pylib.android_commands import GetAttachedDevices + + +class BuildDevice(object): + def __init__(self, configuration): + self.id = configuration['id'] + self.description = configuration['description'] + self.install_metadata = configuration['install_metadata'] + self.adb = android_commands.AndroidCommands(self.id) + + def RunShellCommand(self, *args, **kwargs): + return self.adb.RunShellCommand(*args, **kwargs) + + def PushIfNeeded(self, *args, **kwargs): + return self.adb.PushIfNeeded(*args, **kwargs) + + def GetSerialNumber(self): + return self.id + + def Install(self, *args, **kwargs): + return self.adb.Install(*args, **kwargs) + + def GetInstallMetadata(self, apk_package): + """Gets the metadata on the device for the apk_package apk.""" + # Matches lines like: + # -rw-r--r-- system system 7376582 2013-04-19 16:34 \ + # org.chromium.chrome.testshell.apk + # -rw-r--r-- system system 7376582 2013-04-19 16:34 \ + # org.chromium.chrome.testshell-1.apk + apk_matcher = lambda s: re.match('.*%s(-[0-9]*)?.apk$' % apk_package, s) + matches = filter(apk_matcher, self.install_metadata) + return matches[0] if matches else None + + +def GetConfigurationForDevice(id): + adb = android_commands.AndroidCommands(id) + configuration = None + has_root = False + is_online = adb.IsOnline() + if is_online: + cmd = 'ls -l /data/app; getprop ro.build.description' + cmd_output = adb.RunShellCommand(cmd) + has_root = not 'Permission denied' in cmd_output[0] + if not has_root: + # Disable warning log messages from EnableAdbRoot() + logging.getLogger().disabled = True + has_root = adb.EnableAdbRoot() + logging.getLogger().disabled = False + cmd_output = adb.RunShellCommand(cmd) + + configuration = { + 'id': id, + 'description': cmd_output[-1], + 'install_metadata': cmd_output[:-1], + } + return configuration, is_online, has_root + + +def WriteConfigurations(configurations, path): + # Currently we only support installing to the first device. + build_utils.WriteJson(configurations[:1], path, only_if_changed=True) + + +def ReadConfigurations(path): + return build_utils.ReadJson(path) + + +def GetBuildDevice(configurations): + assert len(configurations) == 1 + return BuildDevice(configurations[0]) + + +def GetBuildDeviceFromPath(path): + configurations = ReadConfigurations(path) + if len(configurations) > 0: + return GetBuildDevice(ReadConfigurations(path)) + return None + diff --git a/build/android/gyp/util/build_utils.py b/build/android/gyp/util/build_utils.py new file mode 100644 index 0000000000..897b6fc949 --- /dev/null +++ b/build/android/gyp/util/build_utils.py @@ -0,0 +1,146 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import fnmatch +import json +import os +import pipes +import shlex +import shutil +import subprocess +import sys +import traceback + + +def MakeDirectory(dir_path): + try: + os.makedirs(dir_path) + except OSError: + pass + + +def DeleteDirectory(dir_path): + if os.path.exists(dir_path): + shutil.rmtree(dir_path) + + +def Touch(path): + MakeDirectory(os.path.dirname(path)) + with open(path, 'a'): + os.utime(path, None) + + +def FindInDirectory(directory, filter): + files = [] + for root, dirnames, filenames in os.walk(directory): + matched_files = fnmatch.filter(filenames, filter) + files.extend((os.path.join(root, f) for f in matched_files)) + return files + + +def FindInDirectories(directories, filter): + all_files = [] + for directory in directories: + all_files.extend(FindInDirectory(directory, filter)) + return all_files + + +def ParseGypList(gyp_string): + # The ninja generator doesn't support $ in strings, so use ## to + # represent $. + # TODO(cjhopman): Remove when + # https://code.google.com/p/gyp/issues/detail?id=327 + # is addressed. + gyp_string = gyp_string.replace('##', '$') + return shlex.split(gyp_string) + + +def CheckOptions(options, parser, required=[]): + for option_name in required: + if not getattr(options, option_name): + parser.error('--%s is required' % option_name.replace('_', '-')) + +def WriteJson(obj, path, only_if_changed=False): + old_dump = None + if os.path.exists(path): + with open(path, 'r') as oldfile: + old_dump = oldfile.read() + + new_dump = json.dumps(obj) + + if not only_if_changed or old_dump != new_dump: + with open(path, 'w') as outfile: + outfile.write(new_dump) + +def ReadJson(path): + with open(path, 'r') as jsonfile: + return json.load(jsonfile) + + +# This can be used in most cases like subprocess.check_call. The output, +# particularly when the command fails, better highlights the command's failure. +# This call will directly exit on a failure in the subprocess so that no python +# stacktrace is printed after the output of the failed command (and will +# instead print a python stack trace before the output of the failed command) +def CheckCallDie(args, suppress_output=False, cwd=None): + if not cwd: + cwd = os.getcwd() + + child = subprocess.Popen(args, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=cwd) + + stdout, _ = child.communicate() + + if child.returncode: + stacktrace = traceback.extract_stack() + print >> sys.stderr, ''.join(traceback.format_list(stacktrace)) + # A user should be able to simply copy and paste the command that failed + # into their shell. + copyable_command = ' '.join(map(pipes.quote, args)) + copyable_command = ('( cd ' + os.path.abspath(cwd) + '; ' + + copyable_command + ' )') + print >> sys.stderr, 'Command failed:', copyable_command, '\n' + + if stdout: + print stdout, + + # Directly exit to avoid printing stacktrace. + sys.exit(child.returncode) + + else: + if stdout and not suppress_output: + print stdout, + return stdout + + +def GetModifiedTime(path): + # For a symlink, the modified time should be the greater of the link's + # modified time and the modified time of the target. + return max(os.lstat(path).st_mtime, os.stat(path).st_mtime) + + +def IsTimeStale(output, inputs): + if not os.path.exists(output): + return True + + output_time = GetModifiedTime(output) + for input in inputs: + if GetModifiedTime(input) > output_time: + return True + return False + + +def IsDeviceReady(): + device_state = CheckCallDie(['adb', 'get-state'], suppress_output=True) + return device_state.strip() == 'device' + + +def PrintWarning(message): + print 'WARNING: ' + message + + +def PrintBigWarning(message): + print '***** ' * 8 + PrintWarning(message) + print '***** ' * 8 diff --git a/build/android/gyp/util/md5_check.py b/build/android/gyp/util/md5_check.py new file mode 100644 index 0000000000..d45bb94d2a --- /dev/null +++ b/build/android/gyp/util/md5_check.py @@ -0,0 +1,76 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import hashlib +import os + + +def CallAndRecordIfStale( + function, record_path=None, input_paths=[], input_strings=[], force=False): + """Calls function if the md5sum of the input paths/strings has changed. + + The md5sum of the inputs is compared with the one stored in record_path. If + this has changed (or the record doesn't exist), function will be called and + the new md5sum will be recorded. + + If force is True, the function will be called regardless of whether the + md5sum is out of date. + """ + md5_checker = _Md5Checker( + record_path=record_path, + input_paths=input_paths, + input_strings=input_strings) + if force or md5_checker.IsStale(): + function() + md5_checker.Write() + + +def _UpdateMd5ForFile(md5, path, block_size=2**16): + with open(path, 'rb') as infile: + while True: + data = infile.read(block_size) + if not data: + break + md5.update(data) + + +def _UpdateMd5ForDirectory(md5, dir_path): + for root, _, files in os.walk(dir_path): + for f in files: + _UpdateMd5ForFile(md5, os.path.join(root, f)) + + +def _UpdateMd5ForPath(md5, path): + if os.path.isdir(path): + _UpdateMd5ForDirectory(md5, path) + else: + _UpdateMd5ForFile(md5, path) + + +class _Md5Checker(object): + def __init__(self, record_path=None, input_paths=[], input_strings=[]): + assert record_path.endswith('.stamp'), ( + 'record paths must end in \'.stamp\' so that they are easy to find ' + 'and delete') + + self.record_path = record_path + + md5 = hashlib.md5() + for i in sorted(input_paths): + _UpdateMd5ForPath(md5, i) + for s in input_strings: + md5.update(s) + self.new_digest = md5.hexdigest() + + self.old_digest = '' + if os.path.exists(self.record_path): + with open(self.record_path, 'r') as old_record: + self.old_digest = old_record.read() + + def IsStale(self): + return self.old_digest != self.new_digest + + def Write(self): + with open(self.record_path, 'w') as new_record: + new_record.write(self.new_digest) diff --git a/build/android/gyp/util/md5_check_test.py b/build/android/gyp/util/md5_check_test.py new file mode 100644 index 0000000000..4a3ee6346b --- /dev/null +++ b/build/android/gyp/util/md5_check_test.py @@ -0,0 +1,69 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import tempfile +import unittest + +import md5_check + + +class TestMd5Check(unittest.TestCase): + def testCallAndRecordIfStale(self): + input_strings = ['string1', 'string2'] + input_file1 = tempfile.NamedTemporaryFile() + input_file2 = tempfile.NamedTemporaryFile() + file1_contents = 'input file 1' + file2_contents = 'input file 2' + input_file1.write(file1_contents) + input_file1.flush() + input_file2.write(file2_contents) + input_file2.flush() + input_files = [input_file1.name, input_file2.name] + + record_path = tempfile.NamedTemporaryFile(suffix='.stamp') + + def CheckCallAndRecord(should_call, message, force=False): + self.called = False + def MarkCalled(): + self.called = True + md5_check.CallAndRecordIfStale( + MarkCalled, + record_path=record_path.name, + input_paths=input_files, + input_strings=input_strings, + force=force) + self.failUnlessEqual(should_call, self.called, message) + + CheckCallAndRecord(True, 'should call when record doesn\'t exist') + CheckCallAndRecord(False, 'should not call when nothing changed') + CheckCallAndRecord(True, force=True, message='should call when forced') + + input_file1.write('some more input') + input_file1.flush() + CheckCallAndRecord(True, 'changed input file should trigger call') + + input_files = input_files[::-1] + CheckCallAndRecord(False, 'reordering of inputs shouldn\'t trigger call') + + input_files = input_files[:1] + CheckCallAndRecord(True, 'removing file should trigger call') + + input_files.append(input_file2.name) + CheckCallAndRecord(True, 'added input file should trigger call') + + input_strings[0] = input_strings[0] + ' a bit longer' + CheckCallAndRecord(True, 'changed input string should trigger call') + + input_strings = input_strings[::-1] + CheckCallAndRecord(True, 'reordering of string inputs should trigger call') + + input_strings = input_strings[:1] + CheckCallAndRecord(True, 'removing a string should trigger call') + + input_strings.append('a brand new string') + CheckCallAndRecord(True, 'added input string should trigger call') + + +if __name__ == '__main__': + unittest.main() diff --git a/build/android/gyp/write_ordered_libraries.py b/build/android/gyp/write_ordered_libraries.py new file mode 100755 index 0000000000..11f12e06e1 --- /dev/null +++ b/build/android/gyp/write_ordered_libraries.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python +# +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Writes dependency ordered list of native libraries. + +The list excludes any Android system libraries, as those are not bundled with +the APK. + +This list of libraries is used for several steps of building an APK. +In the component build, the --input-libraries only needs to be the top-level +library (i.e. libcontent_shell_content_view). This will then use readelf to +inspect the shared libraries and determine the full list of (non-system) +libraries that should be included in the APK. +""" + +# TODO(cjhopman): See if we can expose the list of library dependencies from +# gyp, rather than calculating it ourselves. +# http://crbug.com/225558 + +import json +import optparse +import os +import re +import sys + +from util import build_utils + +_options = None +_library_re = re.compile( + '.*NEEDED.*Shared library: \[(?P[\w/.]+)\]') + + +def FullLibraryPath(library_name): + return '%s/%s' % (_options.libraries_dir, library_name) + + +def IsSystemLibrary(library_name): + # If the library doesn't exist in the libraries directory, assume that it is + # an Android system library. + return not os.path.exists(FullLibraryPath(library_name)) + + +def CallReadElf(library_or_executable): + readelf_cmd = [_options.readelf, + '-d', + library_or_executable] + return build_utils.CheckCallDie(readelf_cmd, suppress_output=True) + + +def GetDependencies(library_or_executable): + elf = CallReadElf(library_or_executable) + return set(_library_re.findall(elf)) + + +def GetNonSystemDependencies(library_name): + all_deps = GetDependencies(FullLibraryPath(library_name)) + return set((lib for lib in all_deps if not IsSystemLibrary(lib))) + + +def GetSortedTransitiveDependencies(libraries): + """Returns all transitive library dependencies in dependency order.""" + def GraphNode(library): + return (library, GetNonSystemDependencies(library)) + + # First: find all library dependencies. + unchecked_deps = libraries + all_deps = set(libraries) + while unchecked_deps: + lib = unchecked_deps.pop() + new_deps = GetNonSystemDependencies(lib).difference(all_deps) + unchecked_deps.extend(new_deps) + all_deps = all_deps.union(new_deps) + + # Then: simple, slow topological sort. + sorted_deps = [] + unsorted_deps = dict(map(GraphNode, all_deps)) + while unsorted_deps: + for library, dependencies in unsorted_deps.items(): + if not dependencies.intersection(unsorted_deps.keys()): + sorted_deps.append(library) + del unsorted_deps[library] + + return sorted_deps + +def GetSortedTransitiveDependenciesForExecutable(executable): + """Returns all transitive library dependencies in dependency order.""" + all_deps = GetDependencies(executable) + libraries = [lib for lib in all_deps if not IsSystemLibrary(lib)] + return GetSortedTransitiveDependencies(libraries) + + +def main(argv): + parser = optparse.OptionParser() + + parser.add_option('--input-libraries', + help='A list of top-level input libraries.') + parser.add_option('--libraries-dir', + help='The directory which contains shared libraries.') + parser.add_option('--readelf', help='Path to the readelf binary.') + parser.add_option('--output', help='Path to the generated .json file.') + parser.add_option('--stamp', help='Path to touch on success.') + + global _options + _options, _ = parser.parse_args() + + libraries = build_utils.ParseGypList(_options.input_libraries) + if libraries[0].endswith('.so'): + libraries = [os.path.basename(lib) for lib in libraries] + libraries = GetSortedTransitiveDependencies(libraries) + else: + libraries = GetSortedTransitiveDependenciesForExecutable(libraries[0]) + + build_utils.WriteJson(libraries, _options.output, only_if_changed=True) + + if _options.stamp: + build_utils.Touch(_options.stamp) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) + + diff --git a/build/android/host_heartbeat.py b/build/android/host_heartbeat.py new file mode 100755 index 0000000000..8321a77258 --- /dev/null +++ b/build/android/host_heartbeat.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python +# +# 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. + +"""Sends a heart beat pulse to the currently online Android devices. +This heart beat lets the devices know that they are connected to a host. +""" + +import os +import sys +import time + +from pylib import android_commands + +PULSE_PERIOD = 20 + +def main(): + while True: + try: + devices = android_commands.GetAttachedDevices() + for device in devices: + android_cmd = android_commands.AndroidCommands(device) + android_cmd.RunShellCommand('touch /sdcard/host_heartbeat') + except: + # Keep the heatbeat running bypassing all errors. + pass + time.sleep(PULSE_PERIOD) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/build/android/install_emulator_deps.py b/build/android/install_emulator_deps.py new file mode 100755 index 0000000000..7b12223626 --- /dev/null +++ b/build/android/install_emulator_deps.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python +# 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. + +"""Installs deps for using SDK emulator for testing. + +The script will download the SDK and system images, if they are not present, and +install and enable KVM, if virtualization has been enabled in the BIOS. +""" + + +import logging +import os +import shutil +import sys + +from pylib import cmd_helper +from pylib import constants +from pylib.utils import run_tests_helper + +# From the Android Developer's website. +SDK_BASE_URL = 'http://dl.google.com/android/adt' +SDK_ZIP = 'adt-bundle-linux-x86_64-20130522.zip' + +# Android x86 system image from the Intel website: +# http://software.intel.com/en-us/articles/intel-eula-x86-android-4-2-jelly-bean-bin +X86_IMG_URL = 'http://download-software.intel.com/sites/landingpage/android/sysimg_x86-17_r01.zip' + +# Android API level +API_TARGET = 'android-%s' % constants.ANDROID_SDK_VERSION + + +def CheckSDK(): + """Check if SDK is already installed. + + Returns: + True if android_tools directory exists in current directory. + """ + return os.path.exists(os.path.join(constants.EMULATOR_SDK_ROOT, + 'android_tools')) + + +def CheckX86Image(): + """Check if Android system images have been installed. + + Returns: + True if android_tools/sdk/system-images directory exists. + """ + return os.path.exists(os.path.join(constants.EMULATOR_SDK_ROOT, + 'android_tools', 'sdk', 'system-images', + API_TARGET, 'x86')) + + +def CheckKVM(): + """Check if KVM is enabled. + + Returns: + True if kvm-ok returns 0 (already enabled) + """ + try: + return not cmd_helper.RunCmd(['kvm-ok']) + except OSError: + logging.info('kvm-ok not installed') + return False + + +def GetSDK(): + """Download the SDK and unzip in android_tools directory.""" + logging.info('Download Android SDK.') + sdk_url = '%s/%s' % (SDK_BASE_URL, SDK_ZIP) + try: + cmd_helper.RunCmd(['curl', '-o', '/tmp/sdk.zip', sdk_url]) + print 'curled unzipping...' + rc = cmd_helper.RunCmd(['unzip', '-o', '/tmp/sdk.zip', '-d', '/tmp/']) + if rc: + logging.critical('ERROR: could not download/unzip Android SDK.') + raise + # Get the name of the sub-directory that everything will be extracted to. + dirname, _ = os.path.splitext(SDK_ZIP) + zip_dir = '/tmp/%s' % dirname + # Move the extracted directory to EMULATOR_SDK_ROOT + dst = os.path.join(constants.EMULATOR_SDK_ROOT, 'android_tools') + shutil.move(zip_dir, dst) + finally: + os.unlink('/tmp/sdk.zip') + + +def InstallKVM(): + """Installs KVM packages.""" + rc = cmd_helper.RunCmd(['sudo', 'apt-get', 'install', 'kvm']) + if rc: + logging.critical('ERROR: Did not install KVM. Make sure hardware ' + 'virtualization is enabled in BIOS (i.e. Intel VT-x or ' + 'AMD SVM).') + # TODO(navabi): Use modprobe kvm-amd on AMD processors. + rc = cmd_helper.RunCmd(['sudo', 'modprobe', 'kvm-intel']) + if rc: + logging.critical('ERROR: Did not add KVM module to Linux Kernal. Make sure ' + 'hardware virtualization is enabled in BIOS.') + # Now check to ensure KVM acceleration can be used. + rc = cmd_helper.RunCmd(['kvm-ok']) + if rc: + logging.critical('ERROR: Can not use KVM acceleration. Make sure hardware ' + 'virtualization is enabled in BIOS (i.e. Intel VT-x or ' + 'AMD SVM).') + + +def GetX86Image(): + """Download x86 system image from Intel's website.""" + logging.info('Download x86 system image directory into sdk directory.') + try: + cmd_helper.RunCmd(['curl', '-o', '/tmp/x86_img.zip', X86_IMG_URL]) + rc = cmd_helper.RunCmd(['unzip', '-o', '/tmp/x86_img.zip', '-d', '/tmp/']) + if rc: + logging.critical('ERROR: Could not download/unzip image zip.') + raise + sys_imgs = os.path.join(constants.EMULATOR_SDK_ROOT, 'android_tools', 'sdk', + 'system-images', API_TARGET, 'x86') + shutil.move('/tmp/x86', sys_imgs) + finally: + os.unlink('/tmp/x86_img.zip') + + +def main(argv): + logging.basicConfig(level=logging.INFO, + format='# %(asctime)-15s: %(message)s') + run_tests_helper.SetLogLevel(verbose_count=1) + + # Calls below will download emulator SDK and/or system images only if needed. + if CheckSDK(): + logging.info('android_tools directory already exists (not downloading).') + else: + GetSDK() + + logging.info('Emulator deps for ARM emulator complete.') + + if CheckX86Image(): + logging.info('system-images directory already exists.') + else: + GetX86Image() + + # Make sure KVM packages are installed and enabled. + if CheckKVM(): + logging.info('KVM already installed and enabled.') + else: + InstallKVM() + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/build/android/java_cpp_template.gypi b/build/android/java_cpp_template.gypi new file mode 100644 index 0000000000..fe4238af84 --- /dev/null +++ b/build/android/java_cpp_template.gypi @@ -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. + +# This file is meant to be included into a target to provide a rule +# to generate Java source files from templates that are processed +# through the host C pre-processor. +# +# To use this, create a gyp target with the following form: +# { +# 'target_name': 'android_net_java_constants', +# 'type': 'none', +# 'sources': [ +# 'net/android/NetError.template', +# ], +# 'variables': { +# 'package_name': 'org/chromium/net', +# 'template_deps': ['net/base/certificate_mime_type_list.h'], +# }, +# 'includes': [ '../build/android/java_cpp_template.gypi' ], +# }, +# +# The 'sources' entry should only list template file. The template file +# itself should use the 'ClassName.template' format, and will generate +# 'gen/templates//ClassName.java. The files which template +# dependents on and typically included by the template should be listed +# in template_deps variables. Any change to them will force a rebuild of +# the template, and hence of any source that depends on it. +# + +{ + # Location where all generated Java sources will be placed. + 'variables': { + 'include_path%': '<(DEPTH)', + 'output_dir': '<(SHARED_INTERMEDIATE_DIR)/templates/<(package_name)', + }, + 'direct_dependent_settings': { + 'variables': { + # Ensure that the output directory is used in the class path + # when building targets that depend on this one. + 'generated_src_dirs': [ + '<(output_dir)/', + ], + # Ensure dependents are rebuilt when sources for this rule change. + 'additional_input_paths': [ + '<@(_sources)', + '<@(template_deps)', + ], + }, + }, + # Define a single rule that will be apply to each .template file + # listed in 'sources'. + 'rules': [ + { + 'rule_name': 'generate_java_constants', + 'extension': 'template', + # Set template_deps as additional dependencies. + 'variables': { + 'output_path': '<(output_dir)/<(RULE_INPUT_ROOT).java', + }, + 'inputs': [ + '<(DEPTH)/build/android/gyp/util/build_utils.py', + '<(DEPTH)/build/android/gyp/gcc_preprocess.py', + '<@(template_deps)' + ], + 'outputs': [ + '<(output_path)', + ], + 'action': [ + 'python', '<(DEPTH)/build/android/gyp/gcc_preprocess.py', + '--include-path=<(include_path)', + '--output=<(output_path)', + '--template=<(RULE_INPUT_PATH)', + ], + 'message': 'Generating Java from cpp template <(RULE_INPUT_PATH)', + } + ], +} diff --git a/build/android/lighttpd_server.py b/build/android/lighttpd_server.py new file mode 100755 index 0000000000..11ae794d4a --- /dev/null +++ b/build/android/lighttpd_server.py @@ -0,0 +1,253 @@ +#!/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. + +"""Provides a convenient wrapper for spawning a test lighttpd instance. + +Usage: + lighttpd_server PATH_TO_DOC_ROOT +""" + +import codecs +import contextlib +import httplib +import os +import random +import shutil +import socket +import subprocess +import sys +import tempfile +import time + +from pylib import constants +from pylib import pexpect + +class LighttpdServer(object): + """Wraps lighttpd server, providing robust startup. + + Args: + document_root: Path to root of this server's hosted files. + port: TCP port on the _host_ machine that the server will listen on. If + ommitted it will attempt to use 9000, or if unavailable it will find + a free port from 8001 - 8999. + lighttpd_path, lighttpd_module_path: Optional paths to lighttpd binaries. + base_config_path: If supplied this file will replace the built-in default + lighttpd config file. + extra_config_contents: If specified, this string will be appended to the + base config (default built-in, or from base_config_path). + config_path, error_log, access_log: Optional paths where the class should + place temprary files for this session. + """ + + def __init__(self, document_root, port=None, + lighttpd_path=None, lighttpd_module_path=None, + base_config_path=None, extra_config_contents=None, + config_path=None, error_log=None, access_log=None): + self.temp_dir = tempfile.mkdtemp(prefix='lighttpd_for_chrome_android') + self.document_root = os.path.abspath(document_root) + self.fixed_port = port + self.port = port or constants.LIGHTTPD_DEFAULT_PORT + self.server_tag = 'LightTPD ' + str(random.randint(111111, 999999)) + self.lighttpd_path = lighttpd_path or '/usr/sbin/lighttpd' + self.lighttpd_module_path = lighttpd_module_path or '/usr/lib/lighttpd' + self.base_config_path = base_config_path + self.extra_config_contents = extra_config_contents + self.config_path = config_path or self._Mktmp('config') + self.error_log = error_log or self._Mktmp('error_log') + self.access_log = access_log or self._Mktmp('access_log') + self.pid_file = self._Mktmp('pid_file') + self.process = None + + def _Mktmp(self, name): + return os.path.join(self.temp_dir, name) + + def _GetRandomPort(self): + # The ports of test server is arranged in constants.py. + return random.randint(constants.LIGHTTPD_RANDOM_PORT_FIRST, + constants.LIGHTTPD_RANDOM_PORT_LAST) + + def StartupHttpServer(self): + """Starts up a http server with specified document root and port.""" + # If we want a specific port, make sure no one else is listening on it. + if self.fixed_port: + self._KillProcessListeningOnPort(self.fixed_port) + while True: + if self.base_config_path: + # Read the config + with codecs.open(self.base_config_path, 'r', 'utf-8') as f: + config_contents = f.read() + else: + config_contents = self._GetDefaultBaseConfig() + if self.extra_config_contents: + config_contents += self.extra_config_contents + # Write out the config, filling in placeholders from the members of |self| + with codecs.open(self.config_path, 'w', 'utf-8') as f: + f.write(config_contents % self.__dict__) + if (not os.path.exists(self.lighttpd_path) or + not os.access(self.lighttpd_path, os.X_OK)): + raise EnvironmentError( + 'Could not find lighttpd at %s.\n' + 'It may need to be installed (e.g. sudo apt-get install lighttpd)' + % self.lighttpd_path) + self.process = pexpect.spawn(self.lighttpd_path, + ['-D', '-f', self.config_path, + '-m', self.lighttpd_module_path], + cwd=self.temp_dir) + client_error, server_error = self._TestServerConnection() + if not client_error: + assert int(open(self.pid_file, 'r').read()) == self.process.pid + break + self.process.close() + + if self.fixed_port or not 'in use' in server_error: + print 'Client error:', client_error + print 'Server error:', server_error + return False + self.port = self._GetRandomPort() + return True + + def ShutdownHttpServer(self): + """Shuts down our lighttpd processes.""" + if self.process: + self.process.terminate() + shutil.rmtree(self.temp_dir, ignore_errors=True) + + def _TestServerConnection(self): + # Wait for server to start + server_msg = '' + for timeout in xrange(1, 5): + client_error = None + try: + with contextlib.closing(httplib.HTTPConnection( + '127.0.0.1', self.port, timeout=timeout)) as http: + http.set_debuglevel(timeout > 3) + http.request('HEAD', '/') + r = http.getresponse() + r.read() + if (r.status == 200 and r.reason == 'OK' and + r.getheader('Server') == self.server_tag): + return (None, server_msg) + client_error = ('Bad response: %s %s version %s\n ' % + (r.status, r.reason, r.version) + + '\n '.join([': '.join(h) for h in r.getheaders()])) + except (httplib.HTTPException, socket.error) as client_error: + pass # Probably too quick connecting: try again + # Check for server startup error messages + ix = self.process.expect([pexpect.TIMEOUT, pexpect.EOF, '.+'], + timeout=timeout) + if ix == 2: # stdout spew from the server + server_msg += self.process.match.group(0) + elif ix == 1: # EOF -- server has quit so giveup. + client_error = client_error or 'Server exited' + break + return (client_error or 'Timeout', server_msg) + + def _KillProcessListeningOnPort(self, port): + """Checks if there is a process listening on port number |port| and + terminates it if found. + + Args: + port: Port number to check. + """ + if subprocess.call(['fuser', '-kv', '%d/tcp' % port]) == 0: + # Give the process some time to terminate and check that it is gone. + time.sleep(2) + assert subprocess.call(['fuser', '-v', '%d/tcp' % port]) != 0, \ + 'Unable to kill process listening on port %d.' % port + + def _GetDefaultBaseConfig(self): + return """server.tag = "%(server_tag)s" +server.modules = ( "mod_access", + "mod_accesslog", + "mod_alias", + "mod_cgi", + "mod_rewrite" ) + +# default document root required +#server.document-root = "." + +# files to check for if .../ is requested +index-file.names = ( "index.php", "index.pl", "index.cgi", + "index.html", "index.htm", "default.htm" ) +# mimetype mapping +mimetype.assign = ( + ".gif" => "image/gif", + ".jpg" => "image/jpeg", + ".jpeg" => "image/jpeg", + ".png" => "image/png", + ".svg" => "image/svg+xml", + ".css" => "text/css", + ".html" => "text/html", + ".htm" => "text/html", + ".xhtml" => "application/xhtml+xml", + ".xhtmlmp" => "application/vnd.wap.xhtml+xml", + ".js" => "application/x-javascript", + ".log" => "text/plain", + ".conf" => "text/plain", + ".text" => "text/plain", + ".txt" => "text/plain", + ".dtd" => "text/xml", + ".xml" => "text/xml", + ".manifest" => "text/cache-manifest", + ) + +# Use the "Content-Type" extended attribute to obtain mime type if possible +mimetype.use-xattr = "enable" + +## +# which extensions should not be handle via static-file transfer +# +# .php, .pl, .fcgi are most often handled by mod_fastcgi or mod_cgi +static-file.exclude-extensions = ( ".php", ".pl", ".cgi" ) + +server.bind = "127.0.0.1" +server.port = %(port)s + +## virtual directory listings +dir-listing.activate = "enable" +#dir-listing.encoding = "iso-8859-2" +#dir-listing.external-css = "style/oldstyle.css" + +## enable debugging +#debug.log-request-header = "enable" +#debug.log-response-header = "enable" +#debug.log-request-handling = "enable" +#debug.log-file-not-found = "enable" + +#### SSL engine +#ssl.engine = "enable" +#ssl.pemfile = "server.pem" + +# Autogenerated test-specific config follows. + +cgi.assign = ( ".cgi" => "/usr/bin/env", + ".pl" => "/usr/bin/env", + ".asis" => "/bin/cat", + ".php" => "/usr/bin/php-cgi" ) + +server.errorlog = "%(error_log)s" +accesslog.filename = "%(access_log)s" +server.upload-dirs = ( "/tmp" ) +server.pid-file = "%(pid_file)s" +server.document-root = "%(document_root)s" + +""" + + +def main(argv): + server = LighttpdServer(*argv[1:]) + try: + if server.StartupHttpServer(): + raw_input('Server running at http://127.0.0.1:%s -' + ' press Enter to exit it.' % server.port) + else: + print 'Server exit code:', server.process.exitstatus + finally: + server.ShutdownHttpServer() + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/build/android/native_app_dependencies.gypi b/build/android/native_app_dependencies.gypi new file mode 100644 index 0000000000..d9241cc303 --- /dev/null +++ b/build/android/native_app_dependencies.gypi @@ -0,0 +1,62 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# This file is meant to be included into a target to provide a rule +# to strip and place dependent shared libraries required by a native binary in a +# single folder that can later be pushed to the device. +# +# NOTE: consider packaging your binary as an apk instead of running a native +# library. +# +# To use this, create a gyp target with the following form: +# { +# 'target_name': 'target_that_depends_on_my_binary', +# 'type': 'none', +# 'dependencies': [ +# 'my_binary', +# ], +# 'variables': { +# 'native_binary': '<(PRODUCT_DIR)/my_binary', +# 'output_dir': 'location to place binary and dependent libraries' +# }, +# 'includes': [ '../../build/android/native_app_dependencies.gypi' ], +# }, +# + +{ + 'copies': [ + { + 'destination': '<(output_dir)', + 'files': [ '<(native_binary)' ], + } + ], + 'conditions': [ + ['component == "shared_library"', { + 'dependencies': [ + '<(DEPTH)/build/android/setup.gyp:copy_system_libraries', + ], + 'variables': { + 'intermediate_dir': '<(PRODUCT_DIR)/<(_target_name)', + 'ordered_libraries_file': '<(intermediate_dir)/native_libraries.json', + }, + 'actions': [ + { + 'variables': { + 'input_libraries': ['<(native_binary)'], + }, + 'includes': ['../../build/android/write_ordered_libraries.gypi'], + }, + { + 'action_name': 'stripping native libraries', + 'variables': { + 'stripped_libraries_dir%': '<(output_dir)', + 'input_paths': ['<(native_binary)'], + 'stamp': '<(intermediate_dir)/strip.stamp', + }, + 'includes': ['../../build/android/strip_native_libraries.gypi'], + }, + ], + }], + ], +} diff --git a/build/android/provision_devices.py b/build/android/provision_devices.py new file mode 100755 index 0000000000..19ab384c3b --- /dev/null +++ b/build/android/provision_devices.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python +# +# 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. + +"""Provisions Android devices with settings required for bots. + +Usage: + ./provision_devices.py [-d ] +""" + +import optparse +import os +import re +import subprocess +import sys +import time + +from pylib import android_commands +from pylib import constants + +def KillHostHeartbeat(): + ps = subprocess.Popen(['ps', 'aux'], stdout = subprocess.PIPE) + stdout, _ = ps.communicate() + matches = re.findall('\\n.*host_heartbeat.*', stdout) + for match in matches: + print 'An instance of host heart beart running... will kill' + pid = re.findall('(\d+)', match)[1] + subprocess.call(['kill', str(pid)]) + + +def LaunchHostHeartbeat(): + # Kill if existing host_heartbeat + KillHostHeartbeat() + # Launch a new host_heartbeat + print 'Spawning host heartbeat...' + subprocess.Popen([os.path.join(constants.DIR_SOURCE_ROOT, + 'build/android/host_heartbeat.py')]) + + +def PushAndLaunchAdbReboot(devices, target): + """Pushes and launches the adb_reboot binary on the device. + + Arguments: + devices: The list of serial numbers of the device to which the + adb_reboot binary should be pushed. + target : The build target (example, Debug or Release) which helps in + locating the adb_reboot binary. + """ + for device in devices: + print 'Will push and launch adb_reboot on %s' % device + android_cmd = android_commands.AndroidCommands(device) + # Kill if adb_reboot is already running. + android_cmd.KillAllBlocking('adb_reboot', 2) + # Push adb_reboot + print ' Pushing adb_reboot ...' + adb_reboot = os.path.join(constants.DIR_SOURCE_ROOT, + 'out/%s/adb_reboot' % target) + android_cmd.PushIfNeeded(adb_reboot, '/data/local/tmp/') + # Launch adb_reboot + print ' Launching adb_reboot ...' + p = subprocess.Popen(['adb', '-s', device, 'shell'], stdin=subprocess.PIPE) + p.communicate('/data/local/tmp/adb_reboot; exit\n') + LaunchHostHeartbeat() + + +def ProvisionDevices(options): + if options.device is not None: + devices = [options.device] + else: + devices = android_commands.GetAttachedDevices() + for device in devices: + android_cmd = android_commands.AndroidCommands(device) + android_cmd.RunShellCommand('su -c date -u %f' % time.time()) + if options.auto_reconnect: + PushAndLaunchAdbReboot(devices, options.target) + + +def main(argv): + parser = optparse.OptionParser() + parser.add_option('-d', '--device', + help='The serial number of the device to be provisioned') + parser.add_option('-t', '--target', default='Debug', help='The build target') + parser.add_option( + '-r', '--auto-reconnect', action='store_true', + help='Push binary which will reboot the device on adb disconnections.') + options, args = parser.parse_args(argv[1:]) + + if args: + print >> sys.stderr, 'Unused args %s' % args + return 1 + + ProvisionDevices(options) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/build/android/push_libraries.gypi b/build/android/push_libraries.gypi new file mode 100644 index 0000000000..1f17660c44 --- /dev/null +++ b/build/android/push_libraries.gypi @@ -0,0 +1,45 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# This file is meant to be included into an action to provide a rule that +# pushes stripped shared libraries to the attached Android device. This should +# only be used with the gyp_managed_install flag set. +# +# To use this, create a gyp target with the following form: +# { +# 'actions': [ +# 'variables': { +# 'ordered_libraries_file': 'file generated by write_ordered_libraries' +# 'strip_stamp': 'stamp from strip action to block on' +# 'libraries_source_dir': 'location where stripped libraries are stored' +# 'device_library_dir': 'location on the device where to put pushed libraries', +# 'push_stamp': 'file to touch when the action is complete' +# }, +# 'includes': [ '../../build/android/push_libraries.gypi' ], +# ], +# }, +# + +{ + 'action_name': 'push_libraries_<(_target_name)', + 'message': 'Pushing libraries to device for <(_target_name)', + 'inputs': [ + '<(DEPTH)/build/android/gyp/util/build_utils.py', + '<(DEPTH)/build/android/gyp/util/md5_check.py', + '<(DEPTH)/build/android/gyp/push_libraries.py', + '<(strip_stamp)', + '<(build_device_config_path)', + ], + 'outputs': [ + '<(push_stamp)', + ], + 'action': [ + 'python', '<(DEPTH)/build/android/gyp/push_libraries.py', + '--build-device-configuration=<(build_device_config_path)', + '--libraries-dir=<(libraries_source_dir)', + '--device-dir=<(device_library_dir)', + '--libraries-json=<(ordered_libraries_file)', + '--stamp=<(push_stamp)', + ], +} diff --git a/build/android/pylib/OWNERS b/build/android/pylib/OWNERS new file mode 100644 index 0000000000..e463404ccf --- /dev/null +++ b/build/android/pylib/OWNERS @@ -0,0 +1,3 @@ +bulach@chromium.org +craigdh@chromium.org +frankf@chromium.org diff --git a/build/android/pylib/__init__.py b/build/android/pylib/__init__.py new file mode 100644 index 0000000000..96196cffb2 --- /dev/null +++ b/build/android/pylib/__init__.py @@ -0,0 +1,3 @@ +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. diff --git a/build/android/pylib/android_commands.py b/build/android/pylib/android_commands.py new file mode 100644 index 0000000000..f8c074758d --- /dev/null +++ b/build/android/pylib/android_commands.py @@ -0,0 +1,1528 @@ +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Provides an interface to communicate with the device via the adb command. + +Assumes adb binary is currently on system path. +""" + +import collections +import datetime +import logging +import os +import re +import shlex +import subprocess +import sys +import tempfile +import time + +import cmd_helper +import constants +import io_stats_parser +try: + import pexpect +except: + pexpect = None + +sys.path.append(os.path.join( + constants.DIR_SOURCE_ROOT, 'third_party', 'android_testrunner')) +import adb_interface +import am_instrument_parser +import errors + + +# Pattern to search for the next whole line of pexpect output and capture it +# into a match group. We can't use ^ and $ for line start end with pexpect, +# see http://www.noah.org/python/pexpect/#doc for explanation why. +PEXPECT_LINE_RE = re.compile('\n([^\r]*)\r') + +# Set the adb shell prompt to be a unique marker that will [hopefully] not +# appear at the start of any line of a command's output. +SHELL_PROMPT = '~+~PQ\x17RS~+~' + +# Java properties file +LOCAL_PROPERTIES_PATH = '/data/local.prop' + +# Property in /data/local.prop that controls Java assertions. +JAVA_ASSERT_PROPERTY = 'dalvik.vm.enableassertions' + +MEMORY_INFO_RE = re.compile('^(?P\w+):\s+(?P\d+) kB$') +NVIDIA_MEMORY_INFO_RE = re.compile('^\s*(?P\S+)\s*(?P\S+)\s*' + '(?P\d+)\s*(?P\d+)$') + +# Keycode "enum" suitable for passing to AndroidCommands.SendKey(). +KEYCODE_HOME = 3 +KEYCODE_BACK = 4 +KEYCODE_DPAD_UP = 19 +KEYCODE_DPAD_DOWN = 20 +KEYCODE_DPAD_RIGHT = 22 +KEYCODE_ENTER = 66 +KEYCODE_MENU = 82 + +MD5SUM_DEVICE_FOLDER = constants.TEST_EXECUTABLE_DIR + '/md5sum/' +MD5SUM_DEVICE_PATH = MD5SUM_DEVICE_FOLDER + 'md5sum_bin' +MD5SUM_LD_LIBRARY_PATH = 'LD_LIBRARY_PATH=%s' % MD5SUM_DEVICE_FOLDER + + +def GetAVDs(): + """Returns a list of AVDs.""" + re_avd = re.compile('^[ ]+Name: ([a-zA-Z0-9_:.-]+)', re.MULTILINE) + avds = re_avd.findall(cmd_helper.GetCmdOutput(['android', 'list', 'avd'])) + return avds + + +def GetAttachedDevices(hardware=True, emulator=True, offline=False): + """Returns a list of attached, android devices and emulators. + + If a preferred device has been set with ANDROID_SERIAL, it will be first in + the returned list. The arguments specify what devices to include in the list. + + Example output: + + * daemon not running. starting it now on port 5037 * + * daemon started successfully * + List of devices attached + 027c10494100b4d7 device + emulator-5554 offline + + Args: + hardware: Include attached actual devices that are online. + emulator: Include emulators (i.e. AVD's) currently on host. + offline: Include devices and emulators that are offline. + + Returns: List of devices. + """ + adb_devices_output = cmd_helper.GetCmdOutput([constants.ADB_PATH, 'devices']) + + re_device = re.compile('^([a-zA-Z0-9_:.-]+)\tdevice$', re.MULTILINE) + online_devices = re_device.findall(adb_devices_output) + + re_device = re.compile('^(emulator-[0-9]+)\tdevice', re.MULTILINE) + emulator_devices = re_device.findall(adb_devices_output) + + re_device = re.compile('^([a-zA-Z0-9_:.-]+)\toffline$', re.MULTILINE) + offline_devices = re_device.findall(adb_devices_output) + + devices = [] + # First determine list of online devices (e.g. hardware and/or emulator). + if hardware and emulator: + devices = online_devices + elif hardware: + devices = [device for device in online_devices + if device not in emulator_devices] + elif emulator: + devices = emulator_devices + + # Now add offline devices if offline is true + if offline: + devices = devices + offline_devices + + preferred_device = os.environ.get('ANDROID_SERIAL') + if preferred_device in devices: + devices.remove(preferred_device) + devices.insert(0, preferred_device) + return devices + + +def IsDeviceAttached(device): + """Return true if the device is attached and online.""" + return device in GetAttachedDevices() + + +def _GetFilesFromRecursiveLsOutput(path, ls_output, re_file, utc_offset=None): + """Gets a list of files from `ls` command output. + + Python's os.walk isn't used because it doesn't work over adb shell. + + Args: + path: The path to list. + ls_output: A list of lines returned by an `ls -lR` command. + re_file: A compiled regular expression which parses a line into named groups + consisting of at minimum "filename", "date", "time", "size" and + optionally "timezone". + utc_offset: A 5-character string of the form +HHMM or -HHMM, where HH is a + 2-digit string giving the number of UTC offset hours, and MM is a + 2-digit string giving the number of UTC offset minutes. If the input + utc_offset is None, will try to look for the value of "timezone" if it + is specified in re_file. + + Returns: + A dict of {"name": (size, lastmod), ...} where: + name: The file name relative to |path|'s directory. + size: The file size in bytes (0 for directories). + lastmod: The file last modification date in UTC. + """ + re_directory = re.compile('^%s/(?P